Index: branches/MetisMQI/.classpath
===================================================================
--- branches/MetisMQI/.classpath	(revision 29)
+++ branches/MetisMQI/.classpath	(revision 29)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src/main/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="lib/java-cup.jar"/>
+	<classpathentry kind="lib" path="lib/JFlex.jar"/>
+	<classpathentry kind="lib" path="lib/junit.jar"/>
+	<classpathentry kind="lib" path="lib/jung2-2_0_1.zip"/>
+	<classpathentry kind="lib" path="lib/collections-generic-4.01.jar"/>
+	<classpathentry kind="lib" path="lib/colt-1.2.0.jar"/>
+	<classpathentry kind="lib" path="lib/concurrent-1.3.4.jar"/>
+	<classpathentry kind="lib" path="lib/j3d-core-1.3.1.jar"/>
+	<classpathentry kind="lib" path="lib/jung-3d-2.0.1.jar" sourcepath="lib/jung-algorithms-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-3d-demos-2.0.1.jar" sourcepath="lib/jung-graph-impl-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-algorithms-2.0.1.jar" sourcepath="lib/jung-algorithms-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-api-2.0.1.jar" sourcepath="lib/jung-api-2.0.1-sources.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:platform:/resource/clust/lib/jung2-2_0_1-apidocs.zip!/apidocs"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="lib/jung-graph-impl-2.0.1.jar" sourcepath="lib/jung-graph-impl-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-io-2.0.1.jar" sourcepath="lib/jung-io-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-jai-2.0.1.jar" sourcepath="lib/jung-jai-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-jai-samples-2.0.1.jar" sourcepath="lib/jung-jai-samples-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-samples-2.0.1.jar" sourcepath="lib/jung-samples-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/jung-visualization-2.0.1.jar" sourcepath="lib/jung-visualization-2.0.1-sources.jar"/>
+	<classpathentry kind="lib" path="lib/stax-api-1.0.1.jar"/>
+	<classpathentry kind="lib" path="lib/vecmath-1.3.1.jar"/>
+	<classpathentry kind="lib" path="lib/wstx-asl-3.2.6.jar"/>
+	<classpathentry kind="lib" path="lib/jdom.jar"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
Index: branches/MetisMQI/.project
===================================================================
--- branches/MetisMQI/.project	(revision 29)
+++ branches/MetisMQI/.project	(revision 29)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>clust</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.python.pydev.PyDevBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.python.pydev.pythonNature</nature>
+	</natures>
+</projectDescription>
Index: branches/MetisMQI/META-INF/MANIFEST.MF
===================================================================
--- branches/MetisMQI/META-INF/MANIFEST.MF	(revision 29)
+++ branches/MetisMQI/META-INF/MANIFEST.MF	(revision 29)
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Ant-Version: Apache Ant 1.7.0
+Created-By: 14.3-b01-101 (Apple Inc.)
+
Index: branches/MetisMQI/README
===================================================================
--- branches/MetisMQI/README	(revision 29)
+++ branches/MetisMQI/README	(revision 29)
@@ -0,0 +1,1 @@
+Ciao Rappo, sono entrato nell'svn e provo un commit..
Index: branches/MetisMQI/build.xml
===================================================================
--- branches/MetisMQI/build.xml	(revision 29)
+++ branches/MetisMQI/build.xml	(revision 29)
@@ -0,0 +1,485 @@
+<project name="weka" default="compile" basedir=".">
+<!-- 
+  ===========================================================================
+   Ant build file for weka. Tested with ant 1.6.5 and Junit 3.8.2. Requires
+   JavaMail and the java activation framework for mailing unit test results.
+
+   Type ant -projecthelp for targets and descriptions.
+   Assumes weka and tests (if unit testing) are in the same directory.
+   Build file can reside and be executed from either inside weka or the
+   directory containing weka.
+
+   $Revision: 5694 $
+  ===========================================================================
+-->
+
+  <!-- set global properties for this build -->
+  <property name="src" value="src/main/java"/>
+  <property name="src-test" value="src/test/java"/>
+  <property name="lib" value="lib" />
+  <property name="regression_tests_root" value="src/test/resources/wekarefs"/>
+  <property name="build" value="build"/>
+  <property name="dist"  value="dist"/>
+  <property name="doc"  value="doc"/>
+  <property name="reports"  value="reports"/>
+  <property name="javac_max_memory" value="256m"/>
+  <property name="run_tests_fail" value="true"/>
+  <property name="headless" value="false"/>
+  <property name="macdistrib" value="osx-distrib"/>
+  <property name="debug" value="on" />
+
+  <target name="init_all">
+    <!-- Create the time stamp -->
+    <tstamp/>
+  </target>
+
+   <!-- general classpath definition, incl. CLASSPATH env. variable,
+   // but jars in lib directory have precedence over the CLASSPATH variable -->
+  <path id="project.class.path">
+    <fileset dir="${lib}">
+      <include name="*.jar"/>
+      <include name="*.zip"/>
+    </fileset>
+    <pathelement location="${build}/classes"/>
+    <pathelement location="${build}/testcases"/>
+    <pathelement path="${java.class.path}" />
+  </path>
+
+<!-- 
+ ============================================================================
+ Compilation and documentation making stuff
+ ============================================================================
+-->
+
+  <target name="init_compile" depends="init_all">
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}/classes"/>
+  </target>
+
+  <!-- Compile the java code from ${src}/weka into ${build}/classes -->
+  <target name="compile" depends="init_compile" 
+   description="Compile weka and deposit class files in build/classes">
+    <javac srcdir="${src}" 
+      fork="yes" memoryMaximumSize="${javac_max_memory}"
+      destdir="${build}/classes"
+      optimize="${optimization}"
+      debug="${debug}"
+      deprecation="${deprecation}"
+      source="1.6" target="1.6">
+
+      <classpath refid="project.class.path" /> 
+    </javac>
+    <copy todir="${build}/classes" >
+       <fileset dir="${src}">
+         <include name="weka/**/*.excludes"/>
+         <include name="weka/**/*.gif"/>
+         <include name="weka/**/*.png"/>
+         <include name="weka/**/*.jpeg"/>
+         <include name="weka/**/*.jpg"/>
+         <include name="weka/**/*.props"/>
+         <include name="weka/**/*.txt"/>
+         <include name="weka/**/*.xml"/>
+         <include name="weka/**/DatabaseUtils.props.*"/>
+         <include name="weka/gui/beans/README*"/>
+         <include name="**/*.cup"/>
+         <include name="**/*.flex"/>
+         <include name="**/*.jflex"/>
+         <include name="**/*.properties"/>
+         <include name="**/*.default"/>
+       </fileset>
+    </copy>
+    <rmic base="${build}/classes"
+       classname="weka.experiment.RemoteEngine"/>
+    <unzip src="${lib}/java-cup.jar" dest="${build}/classes">
+      <patternset>
+        <include name="java_cup/runtime/**/*"/>
+      </patternset>
+    </unzip>
+  </target>
+
+  <!-- Make the javadocs -->
+  <target name="docs" 
+          depends="init_all" 
+          description="Make javadocs into ./doc">
+    <mkdir dir="${doc}"/>
+    <javadoc sourcepath="${src}" 
+             classpathref="project.class.path"
+             destdir="${doc}" 
+             packagenames="weka.*"
+             Author="yes" 
+             Public="yes" 
+             maxmemory="256m"/>
+    
+    <!-- insert the links to our homepage and documentation.html -->
+    <replace dir="${doc}" 
+             token="&#34;help-doc.html&#34;" 
+             value="&#34;../documentation.html&#34; target=&#34;_blank&#34;">
+      <include name="**/*.html"/>
+    </replace>
+      
+    <replace dir="${doc}" 
+             token="Help&lt;/B&gt;&lt;/FONT&gt;&lt;/A&gt;&#38;nbsp;&lt;/TD&gt;" 
+             value="Help&lt;/B&gt;&lt;/FONT&gt;&lt;/A&gt;&#38;nbsp;&lt;/TD&gt; &lt;TD BGCOLOR=&#34;&#35;EEEEFF&#34; CLASS=&#34;NavBarCell1&#34;&gt; &lt;A HREF=&#34;http://www.cs.waikato.ac.nz/ml/weka/&#34; target=&#34;_blank&#34;&gt;&lt;FONT CLASS=&#34;NavBarFont1&#34;&gt;&lt;B&gt;Weka's home&lt;/B&gt;&lt;/FONT&gt;&lt;/A&gt;&#38;nbsp;&lt;/TD&gt;">
+      <include name="**/*.html"/>
+    </replace>
+  </target> 
+
+
+<!--
+  ===========================================================================
+  Unit testing stuff
+  ===========================================================================
+-->
+<!-- Junit testing initialization -->
+  <target name="init_tests" depends="init_all">
+    <available property="junit.present" classname="junit.framework.TestCase"/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}/testcases"/>
+    <mkdir dir="${reports}"/>
+  </target>
+
+  <!-- compile the testcases -->
+  <target name="compile_tests" depends="init_tests, compile" 
+   description="Compile unit tests into build/testcases">
+     <javac srcdir="${src-test}" 
+       fork="yes" memoryMaximumSize="${javac_max_memory}"
+            destdir="${build}/testcases"
+            optimize="${optimization}"
+            debug="${debug}"
+            deprecation="${deprecation}"
+            source="1.5" target="1.5">
+       <classpath refid="project.class.path" /> 
+     </javac>
+     <copy todir="${build}/testcases" >
+       <fileset dir="${src-test}">
+          <include name="**/*.arff"/>
+          <include name="**/*.cost"/>
+          <include name="**/*.xml"/>
+          <include name="**/*.matrix"/>
+       </fileset>
+     </copy>
+  </target>
+
+  <!-- Put everything in ${build}/testcases into the weka-tests.jar file -->
+  <target name="jar_tests" depends="compile_tests, init_dist"
+   description="Creates a jar file with the test cases in ./dist">
+    <jar jarfile="${dist}/weka-tests.jar" 
+      basedir="${build}/testcases">
+    </jar>
+  </target>
+
+  <!-- Run the test cases via junit's testrunner class.
+   This is a faster, simpler option than running the junit task -->
+  <target name="run_tests" depends="compile_tests" 
+   description="Execute unit tests (fast)">
+     <java fork="yes" dir="."
+      classname="junit.textui.TestRunner"
+      taskname="junit" failonerror="${run_tests_fail}">
+	 <arg value="weka.AllTests"/>
+         <classpath refid="project.class.path" /> 
+         <jvmarg value="-Djava.awt.headless=${headless}"/> 
+         <jvmarg value="-Dweka.test.Regression.root=${regression_tests_root}"/>
+     </java>
+  </target>
+
+<!-- Run the test cases and produce an html summary (in ${reports}-->
+  <target name="junit" depends="compile_tests" 
+   description="Execute unit tests and produce html summary (in ./reports)">
+     <junit fork="yes" dir=".">
+       <jvmarg value="-Djava.awt.headless=${headless}"/> 
+       <jvmarg value="-Dweka.test.Regression.root=${regression_tests_root}"/>
+        <formatter type="xml"/>
+        <classpath refid="project.class.path" /> 
+         <batchtest todir="${reports}">
+	    <fileset dir="${build}/testcases">
+	       <include name="**/*Test.class"/>
+	       <exclude name="**/AllTests.class"/>
+	    </fileset>
+	 </batchtest>
+     </junit>
+
+     <junitreport todir="${reports}">
+        <fileset dir="${reports}">
+	   <include name="TEST-*.xml"/>
+	</fileset>
+	<report format="noframes" todir="${reports}"/>
+     </junitreport>
+  </target>
+
+
+<!--
+  ===========================================================================
+  Release making stuff
+  ===========================================================================
+-->
+
+  <target name = "init_dist" depends="init_all">
+    <!-- Create the distribution directory -->
+    <mkdir dir="${dist}"/>
+  </target>
+
+  <!-- Put everything in ${build}/classes into the weka.jar file -->
+  <target name="exejar" depends="compile, init_dist"
+   description="Create an executable jar file in ./dist">
+    <jar jarfile="${dist}/weka.jar" 
+      basedir="${build}/classes">
+     <manifest>
+       <attribute name="Main-Class" value="weka.gui.GUIChooser"/>
+     </manifest>
+    </jar>
+  </target>
+
+  <!-- Put all .java, and .props files into ${dist}/weka-src.jar-->
+  <target name="srcjar" depends="init_dist, init_all"
+   description="Create a jar file containing weka source in ./dist">  
+    <!-- jar up the source -->
+    <jar jarfile="${dist}/weka-src.jar" 
+      basedir=".">
+      <include name="*.xml"/>
+      <include name="src/**/*.excludes"/>
+      <include name="src/**/*.gif"/>
+      <include name="src/**/*.java"/>
+      <include name="src/**/*.jpeg"/>
+      <include name="src/**/*.jpg"/>
+      <include name="src/**/*.props"/>
+      <include name="src/**/*.txt"/>
+      <include name="src/**/*.xml"/>
+      <include name="src/**/DatabaseUtils.props.*"/>
+      <include name="src/**/weka/gui/beans/README*"/>
+      <include name="src/**/*.cup"/>
+      <include name="src/**/*.flex"/>
+      <include name="src/**/*.jflex"/>
+      <include name="src/**/*.properties"/>
+      <include name="src/**/*.default"/>
+      <include name="src/**/*.cost"/>
+      <include name="src/**/*.arff"/>
+      <include name="src/**/*.matrix"/>
+      <include name="lib/**/*.jar"/>
+    </jar>
+  </target>
+
+  <!-- make a jar file containing just the stuff needed for running a remote experiment server -->
+  <target name="remotejar" depends="compile, init_dist"
+   description="Create a jar file containing classes for remote experiments in ./dist">
+     <jar jarfile="${dist}/remoteEngine.jar"
+      basedir="${build}/classes"
+      includes="weka/experiment/*_*.class,weka/experiment/RemoteEngine*.class,weka/experiment/Compute.class,weka/experiment/Task.class,weka/experiment/TaskStatusInfo.class,weka/core/Queue*.class,weka/core/RevisionHandler.class,weka/core/Utils.class,weka/core/RevisionUtils.class"/>
+     <copy todir="${dist}" >
+       <fileset dir="${src}/weka/experiment">
+          <include name="remote.policy"/>
+          <include name="remote.policy.example"/>
+       </fileset>
+    </copy>
+    <jar jarfile="${dist}/remoteExperimentServer.jar"
+     basedir="${dist}"
+     includes="remoteEngine.jar,remote.policy,remote.policy.example"/>
+     <delete file="${dist}/remoteEngine.jar"/>
+     <delete file="${dist}/remote.policy"/>
+     <delete file="${dist}/remote.policy.example"/>
+   </target>
+
+  <!-- Writes $release version number to weka/core/version.txt -->    
+  <target name="set_version">
+    <echo message="${release}" file="${src}/weka/core/version.txt"/>
+    <echo message="${release}" file="${build}/classes/weka/core/version.txt"/>
+  </target>
+
+  <!-- Make a release -->
+  <target name="release" depends="run_tests, release_sub_tasks"
+   description="Make a release in ${release}. Run with -Drelease=&lt;number of release (eg. 3-4-1)&gt;.">
+  </target>
+
+  <target name="release_no_junit" depends="compile, release_sub_tasks"
+   description="Make a release in ${release} without running junit tests. Run with -Drelease=&lt;number of release (eg. 3-4-1)&gt;.">
+  </target>
+
+  <target name="release_sub_tasks" depends="set_version, exejar, remotejar, srcjar, docs, changelog">
+    <!-- copy the docs to dist/docs -->
+
+    <copy todir="weka-${release}/weka-${release}/doc" >
+       <fileset dir="${doc}"/>
+    </copy>
+    <copy todir="weka-${release}/weka-${release}">
+       <fileset dir="${dist}"/>
+    </copy>
+    <copy todir="weka-${release}/weka-${release}/changelogs">
+       <fileset dir="../wekadocs/changelogs"/>
+    </copy>
+    <copy todir="weka-${release}/weka-${release}/data">
+       <fileset dir="../wekadocs/data"/>
+    </copy>
+    <copy todir="weka-${release}/weka-${release}">
+      <fileset dir="../wekadocs">
+        <include name="*.pdf"/>
+      </fileset>
+    </copy>
+    <copy todir="weka-${release}/weka-${release}">
+       <fileset dir="../wekadocs">
+         <include name="README*"/>
+	 <include name="COPYING"/>
+         <include name="documentation.*"/>
+         <include name="weka.gif"/>
+         <include name="weka.ico"/>
+       </fileset>
+    </copy>
+    <zip destfile="weka-${release}.zip" 
+     basedir="weka-${release}"/>
+  </target>
+
+  <!-- Make a Max OSX application (NOTE: assumes that release/release_no_junit or something that calls
+       release has been run first!!!). Also requires Ant version 1.7.0 or higher -->
+  <target name="osx_application" description="Make an OS X application. Run with -Drelease=&lt;number of release (eg. 3-4-1)&gt;. Assumes a release target has been run first!">
+    <mkdir dir="${macdistrib}"/>
+    <mkdir dir="${macdistrib}/weka-${release}"/>
+    <copy todir="${macdistrib}/weka-${release}">
+      <fileset dir="weka-${release}/weka-${release}"/>
+    </copy>
+    <taskdef name="jarbundler" 
+             classname="net.sourceforge.jarbundler.JarBundler" />
+    <jarbundler dir="${macdistrib}"
+                name="weka-${release}"
+                mainclass="weka.gui.GUIChooser"
+                icon="${src}/weka/gui/weka_icon.icns"
+                version="${release}"
+                verbose="true"
+                VMOptions="-Xmx256M"
+                shortname="Weka"
+                arguments=""
+                workingdirectory="$APP_PACKAGE/Contents/Resources"
+                jvmversion="1.5+">
+      <javaproperty name="java.library.path" value="$APP_PACKAGE/Contents/Resources"/>
+      <jarfileset dir="weka-${release}/weka-${release}">
+        <include name="weka.jar"/>
+      </jarfileset>
+    </jarbundler>
+
+    <!-- Make a .dmg disk image -->
+    <exec executable="/usr/bin/hdiutil" os="Mac OS X">
+      <arg value="create"/>
+      <arg value="-srcfolder"/>
+      <arg value="${macdistrib}"/>
+      <arg value="-volname"/>
+      <arg value="weka-${release}"/>
+      <arg value="-ov"/>
+      <arg value="${macdistrib}/weka-${release}.dmg"/>
+    </exec>
+  </target>
+
+<!-- New subversion stuff -->
+ <target name="svnCheckout" description="Checkout from subversion. Run with -Dsvn_username=username -Dsvn_password=password -Dweka_branch=&lt;branch name (e.g. trunk, branches/book2ndEd-branch)&gt;.">
+    <property name="source-root" value="."/>
+    <property name="repository.URL" value="https://svn.scms.waikato.ac.nz/svn/weka"/>
+    <java classname="org.tmatesoft.svn.cli.SVN"
+       dir="${source-root}/" fork="true">
+      <arg value="co"/>
+      <arg value="--username"/>
+      <arg value="${svn_username}"/>
+      <arg value="--password"/>
+      <arg value="${svn_password}"/>
+      <arg value="${repository.URL}/${weka_branch}/weka"/>
+      <classpath>
+        <pathelement location="${ant.home}/lib/svnkit.jar" />
+        <pathelement location="${ant.home}/lib/svnkit-cli.jar" />
+      </classpath>
+    </java>
+    <java classname="org.tmatesoft.svn.cli.SVN"
+       dir="${source-root}/" fork="true">
+      <arg value="co"/>
+      <arg value="--username"/>
+      <arg value="${svn_username}"/>
+      <arg value="--password"/>
+      <arg value="${svn_password}"/>
+      <arg value="${repository.URL}/${weka_branch}/wekadocs"/>
+      <classpath>
+        <pathelement location="${ant.home}/lib/svnkit.jar" />
+        <pathelement location="${ant.home}/lib/svnkit-cli.jar" />
+      </classpath>
+    </java>
+    <java classname="org.tmatesoft.svn.cli.SVN"
+       dir="${source-root}/" fork="true">
+      <arg value="co"/>
+      <arg value="--username"/>
+      <arg value="${svn_username}"/>
+      <arg value="--password"/>
+      <arg value="${svn_password}"/>
+      <arg value="${repository.URL}/${weka_branch}/installer"/>
+      <classpath>
+        <pathelement location="${ant.home}/lib/svnkit.jar" />
+        <pathelement location="${ant.home}/lib/svnkit-cli.jar" />
+      </classpath>
+    </java>
+  </target>
+
+   <target name="changelog"
+    description="Create a changelog file for a release of Weka. Run with -Dsvn_username=username -Dsvn_password=password -Drelease=&lt;number of release (eg. 3-4-1)&gt;, -Ddate_range=&lt;date range for changes (eg. {2008-07-16}:{2008-09-29})&gt;, -Dweka_branch=&lt;branch name (e.g. trunk, branches/book2ndEd-branch)&gt;.">
+    <property name="repository.URL" value="https://svn.scms.waikato.ac.nz/svn/weka"/>
+    <java classname="org.tmatesoft.svn.cli.SVN" fork="true"
+          output="../wekadocs/changelogs/CHANGELOG-${release}">
+      <arg value="log"/>
+      <arg value="--username"/>
+      <arg value="${svn_username}"/>
+      <arg value="--password"/>
+      <arg value="${svn_password}"/>
+      <arg value="-r"/>
+      <arg value="${date_range}"/>
+      <arg value="-v"/>
+      <arg value="${repository.URL}/${weka_branch}/weka/src/main/java/weka"/>
+      <classpath>
+        <pathelement location="${ant.home}/lib/svnkit.jar" />
+        <pathelement location="${ant.home}/lib/svnkit-cli.jar" />
+      </classpath>
+    </java>
+  </target>
+
+  <!-- This target is used for an automated nightly/weekly build job -->
+  <target name="nightly_build"
+          depends="init_tests"
+          description="Does a svn checkout, builds, runs unit tests, and mails the output to mail recipients (run ant with -Dsvn_username=username -Dsvn_password=password -Dweka_branch=branch -Dmail_recipients=&lt;email,email,...&gt; -Dmail_sender=email -Dmail_smtp_host=smtp-server)">
+
+     <!-- run the build process -->
+     <ant antfile="${ant.file}" target="svnCheckout" output="${reports}/nightly_build-init.txt">
+       <property name="mail_smtp_host" value="${mail_smtp_host}"/>
+       <property name="mail_sender" value="${mail_sender}"/>
+       <property name="mail_recipients" value="${mail_recipients}"/>
+       <property name="svn_username" value="${svn_username}"/>
+       <property name="svn_password" value="${svn_password}"/>
+       <property name="weka_branch" value="${weka_branch}"/>
+     </ant>
+
+     <ant antfile="${ant.file}" dir="weka" target="run_tests" output="../${reports}/nightly_build-results.txt">
+       <property name="mail_smtp_host" value="${mail_smtp_host}"/>
+       <property name="mail_sender" value="${mail_sender}"/>
+       <property name="mail_recipients" value="${mail_recipients}"/>
+       <property name="run_tests_fail" value="false"/>
+       <property name="headless" value="${headless}"/>
+     </ant>
+
+     <!-- mail the results -->
+     <mail messageMimeType="text/html"
+	       tolist="${mail_recipients}"
+	       mailhost="${mail_smtp_host}"
+	       subject="nightly build results (${weka_branch}): ${TODAY}"
+	       from="${mail_sender}">
+       <fileset dir="${reports}">
+         <include name="nightly_build*.*"/>
+       </fileset>
+     </mail>
+  </target>
+
+
+<!-- Clean -->
+  <target name="clean" depends="init_all"
+   description="Removes the build, dist and reports directories">
+    <!-- Delete the ${build} and ${dist} directory trees -->
+    <delete dir="${build}"/>
+    <delete dir="${dist}"/>
+    <delete dir="${reports}"/>
+  </target>
+
+  <target name="superclean" depends="clean"
+   description="Clean plus removes source trees! and doc directories">
+  <!-- WARNING, this deletes the source and doc directories -->
+    <delete dir="${src}"/>
+    <delete dir="${doc}"/>
+  </target>
+</project>
Index: branches/MetisMQI/graph.arff
===================================================================
--- branches/MetisMQI/graph.arff	(revision 29)
+++ branches/MetisMQI/graph.arff	(revision 29)
@@ -0,0 +1,35 @@
+@RELATION   graph
+
+@ATTRIBUTE	from	STRING
+@ATTRIBUTE	to			STRING
+@ATTRIBUTE	weigth	NUMERIC
+
+@DATA
+0,1,1
+1,0,1
+1,7,1
+7,1,1
+6,7,1
+7,6,1
+1,3,1
+3,1,1
+3,6,1
+6,3,1
+1,6,1
+6,1,1
+3,7,1
+7,3,1
+5,6,1
+6,5,1
+0,2,1
+2,0,1
+0,4,1
+4,0,1
+0,5,1
+5,0,1
+2,4,1
+4,2,1
+2,5,1
+5,2,1
+4,5,1
+5,4,1
Index: branches/MetisMQI/graph2.arff
===================================================================
--- branches/MetisMQI/graph2.arff	(revision 29)
+++ branches/MetisMQI/graph2.arff	(revision 29)
@@ -0,0 +1,27 @@
+@RELATION   graph
+
+@ATTRIBUTE	from	STRING
+@ATTRIBUTE	to			STRING
+@ATTRIBUTE	weigth	NUMERIC
+
+@DATA
+1,2,1
+1,3,1
+1,4,1
+2,3,1
+2,4,1
+3,4,1
+3,5,1
+5,6,1
+5,7,1
+5,8,1
+6,7,1
+6,8,1
+7,8,1
+7,9,1
+9,10,1
+9,11,1
+9,12,1
+10,11,1
+10,12,1
+11,12,1
Index: branches/MetisMQI/parsers.xml
===================================================================
--- branches/MetisMQI/parsers.xml	(revision 29)
+++ branches/MetisMQI/parsers.xml	(revision 29)
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ANT file for generating JFlex/CUP parsers.
+
+  If you want to add a new parser, you basically only need to do the
+  following two things:
+  - write JFlex and CUP files, i.e., Parser.cup and Scanner.jflex, for your 
+    parser and place them in a separated sub-package
+  - add an "antcall" to the "parsers" target, with the sub-package as parameter
+    (the sub-package your parser files reside in)
+
+  For an example, please refer to the "weka.core.mathematicalexpression" parser.
+
+  URLs:
+  - JFlex
+    http://jflex.de/
+  - CUP
+    http://www2.cs.tum.edu/projects/cup/
+
+  Author:  FracPete (fracpete at waikato dot ac dot nz)
+  Version: $Revision: 1.2 $
+-->
+
+<project name="weka-parsers" default="compile" basedir=".">
+   <property name="build.compiler" value="modern" />
+   <property name="debug"          value="on" />
+   <property name="deprecation"    value="off" />
+   <property name="optimization"   value="off" />
+   <property name="build"          value="build"/>
+   <property name="lib"            value="lib"/>
+   <property name="parserpkg"      value="" />
+   <property name="src"            value="src/main/java" />
+
+   <path id="project.class.path">
+     <fileset dir="${lib}">
+       <include name="*.jar"/>
+       <include name="*.zip"/>
+     </fileset>
+     <pathelement location="${build}/classes"/>
+     <pathelement path="${java.class.path}" />
+   </path>
+
+   <target name="init" description="Initializes the build environment.">
+      <!-- initialize Weka -->
+      <ant antfile="build.xml" target="init_compile"/>
+   </target>
+
+   <target name="compile" depends="init" description="Compiles Weka.">
+      <!-- compile Weka -->
+      <ant antfile="build.xml" target="compile"/>
+      <!-- generate parsers -->
+      <antcall target="parsers"/>
+      <!-- recompile Weka -->
+      <ant antfile="build.xml" target="compile"/>
+   </target>
+   
+   <!-- central target for calling the parser targets -->
+   <target name="parsers" depends="init" description="Generates all the parsers subsequently.">
+      <antcall target="parser">
+         <param name="parserpkg" value="weka/core/mathematicalexpression"/>
+      </antcall>
+      <antcall target="parser">
+         <param name="parserpkg" value="weka/filters/unsupervised/instance/subsetbyexpression"/>
+      </antcall>
+      <antcall target="parser">
+         <param name="parserpkg" value="weka/core/json"/>
+      </antcall>
+      <!-- add calls for more parsers here -->
+   </target>
+
+   <!-- generates a parser situated in the ${parserpkg} sub-package 
+        (using Parser.cup and Scanner.jflex). -->
+   <target name="parser" description="Generates the parser ${parserpkg}.">
+      <echo message="Generating lexer (${parserpkg})."/>
+      <java classname="JFlex.Main">
+      	 <classpath refid="project.class.path"/>
+         <arg value="--jlex"/>
+         <arg value="--quiet"/>
+         <arg value="--nobak"/>
+         <arg value="--outdir"/>
+         <arg value="${src}/${parserpkg}"/>
+         <arg value="${src}/${parserpkg}/Scanner.jflex"/>
+      </java>
+      <echo message="Generating parser (${parserpkg})."/>
+      <java classname="java_cup.Main">
+      	 <classpath refid="project.class.path"/>
+         <arg value="-parser"/>
+         <arg value="Parser"/>
+         <arg value="-interface"/>
+         <arg value="-destdir"/>
+         <arg value="${src}/${parserpkg}"/>
+         <arg value="${src}/${parserpkg}/Parser.cup"/>
+      </java>
+   </target>
+</project>
Index: branches/MetisMQI/script/analizzaRisultati.py
===================================================================
--- branches/MetisMQI/script/analizzaRisultati.py	(revision 29)
+++ branches/MetisMQI/script/analizzaRisultati.py	(revision 29)
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+import statistics
+import sys
+import re
+from re import split
+from math import sqrt
+import os
+
+basedir = sys.argv[1]
+
+filesList = [basedir + file for file in os.listdir(basedir) if (re.match('.*dat$', file) and not re.match('^output.*', file))]
+
+results = []
+for file in filesList:
+    inp = open(file,"r")
+    values = []
+    for line in inp.readlines():
+        values.append(float(line))
+    inp.close()
+    print "File ",file," acquisito."
+    results.append(statistics.mean(values))
+variance = statistics.variance(results, mode = "Unbiased")
+confidence = sqrt(variance / len(results)) * 1.6449
+print "Average: ",statistics.mean(results)," Confidence interval: ",confidence
Index: branches/MetisMQI/script/filtraRisultati.sh
===================================================================
--- branches/MetisMQI/script/filtraRisultati.sh	(revision 29)
+++ branches/MetisMQI/script/filtraRisultati.sh	(revision 29)
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+resultDir=output/random/*dat
+filteredDir=output/random/filtered/
+
+for file in $resultDir; do
+  echo "Processing file $file"
+  cat $file | grep conductance | sed 's/Cluster conductance: //g' | grep -v NaN > $filteredDir`basename $file`
+done
+
+
Index: branches/MetisMQI/script/parseXMLOutput.py
===================================================================
--- branches/MetisMQI/script/parseXMLOutput.py	(revision 29)
+++ branches/MetisMQI/script/parseXMLOutput.py	(revision 29)
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+
+import statistics
+import xml.dom.minidom
+import sys
+import re
+from re import split
+from math import sqrt
+import os
+
+def getText(nodelist):
+    rc = []
+    for node in nodelist:
+        if node.nodeType == node.TEXT_NODE:
+            rc.append(node.data)
+    return ''.join(rc)
+
+def handleCluster(cluster):
+    numberOfVertices = getText(cluster.getElementsByTagName("vertex")[0].childNodes)
+    conductance = getText(cluster.getElementsByTagName("conductance")[0].childNodes)
+    print numberOfVertices + " " + conductance
+
+def handleClusters(clusters):
+    for cluster in clusters.getElementsByTagName("cluster"):
+        handleCluster(cluster)
+
+def handleRun(run):
+    clusters = run.getElementsByTagName("clusters")[0]
+    handleClusters(clusters)
+
+fileXML = sys.argv[1]
+dom = xml.dom.minidom.parse(fileXML)
+handleRun(dom)
+
+
+
+
+
+
+
+
+#basedir = sys.argv[1]
+
+#filesList = [basedir + file for file in os.listdir(basedir) if (re.match('.*dat$', file) and not re.match('^output.*', file))]
+
+#results = []
+#for file in filesList:
+#    inp = open(file,"r")
+#    values = []
+#    for line in inp.readlines():
+#        values.append(float(line))
+#    inp.close()
+#    print "File ",file," acquisito."
+#    results.append(statistics.mean(values))
+#variance = statistics.variance(results, mode = "Unbiased")
+#confidence = sqrt(variance / len(results)) * 1.6449
+#print "Average: ",statistics.mean(results)," Confidence interval: ",confidence
Index: branches/MetisMQI/script/ripetiprova.sh
===================================================================
--- branches/MetisMQI/script/ripetiprova.sh	(revision 29)
+++ branches/MetisMQI/script/ripetiprova.sh	(revision 29)
@@ -0,0 +1,28 @@
+#!/bin/bash
+index=0
+execution=10
+file=../../reteUtentiMikeHidden.arff
+lib=../../lib/*
+bin=build/classes
+curDir=`pwd`
+
+outputFolder=$curDir/output/metis/
+prefixOutput=metis
+random=false
+
+suffixOutput=.xml
+
+cd $bin
+while [ "$index" -lt "$execution" ]
+do
+  echo "Clustering task #$index"
+  fileOutput=$outputFolder$prefixOutput$index$suffixOutput
+  logOutput=$outputFolder$prefixOutput$index.log
+  java -Xms256m -Xmx1024m -cp .:$lib weka.clusterers.MetisMQIClusterer -V 0 -R $random -S 100 -N 30 -o $fileOutput -t $file >> $logOutput
+  index=`expr $index + 1`
+done
+cd $curDir
+
+echo
+
+exit 0
Index: branches/MetisMQI/src/main/java/weka/associations/AbstractAssociator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/AbstractAssociator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/AbstractAssociator.java	(revision 29)
@@ -0,0 +1,149 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Associator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/** 
+ * Abstract scheme for learning associations. All schemes for learning
+ * associations implemement this class
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5505 $ 
+ */
+public abstract class AbstractAssociator 
+  implements Cloneable, Associator, Serializable, CapabilitiesHandler, RevisionHandler {
+ 
+  /** for serialization */
+  private static final long serialVersionUID = -3017644543382432070L;
+  
+  /**
+   * Creates a new instance of a associator given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * associator implements OptionHandler and the options parameter is
+   * non-null, the associator will have it's options set.
+   *
+   * @param associatorName the fully qualified class name of the associator
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created associator, ready for use.
+   * @exception Exception if the associator name is invalid, or the options
+   * supplied are not acceptable to the associator
+   */
+  public static Associator forName(String associatorName,
+				   String [] options) throws Exception {
+
+    return (Associator)Utils.forName(Associator.class,
+				     associatorName,
+				     options);
+  }
+
+  /**
+   * Creates a deep copy of the given associator using serialization.
+   *
+   * @param model the associator to copy
+   * @return a deep copy of the associator
+   * @exception Exception if an error occurs
+   */
+  public static Associator makeCopy(Associator model) throws Exception {
+    return (Associator) new SerializedObject(model).getObject();
+  }
+
+  /**
+   * Creates copies of the current associator. Note that this method
+   * now uses Serialization to perform a deep copy, so the Associator
+   * object must be fully Serializable. Any currently built model will
+   * now be copied as well.
+   *
+   * @param model an example associator to copy
+   * @param num the number of associators copies to create.
+   * @return an array of associators.
+   * @exception Exception if an error occurs 
+   */
+  public static Associator[] makeCopies(Associator model,
+					 int num) throws Exception {
+
+    if (model == null) {
+      throw new Exception("No model associator set");
+    }
+    Associator [] associators = new Associator [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < associators.length; i++) {
+      associators[i] = (Associator) so.getObject();
+    }
+    return associators;
+  }
+
+  /** 
+   * Returns the Capabilities of this associator. Maximally permissive
+   * capabilities are allowed by default. Derived associators should
+   * override this method and first disable all capabilities and then
+   * enable just those capabilities that make sense for the scheme.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities defaultC = new Capabilities(this);
+    defaultC.enableAll();
+    
+    return defaultC;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5505 $");
+  }
+  
+  /**
+   * runs the associator with the given commandline options
+   * 
+   * @param associator	the associator to run
+   * @param options	the commandline options
+   */
+  protected static void runAssociator(Associator associator, String[] options) {
+    try {
+      System.out.println(
+	  AssociatorEvaluation.evaluate(associator, options));
+    }
+    catch (Exception e) {
+      if (    (e.getMessage() != null)
+	   && (e.getMessage().indexOf("General options") == -1) )
+	e.printStackTrace();
+      else
+	System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/Apriori.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/Apriori.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/Apriori.java	(revision 29)
@@ -0,0 +1,1521 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Apriori.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing an Apriori-type algorithm. Iteratively reduces the minimum support until it finds the required number of rules with the given minimum confidence.<br/>
+ * The algorithm has an option to mine class association rules. It is adapted as explained in the second reference.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * R. Agrawal, R. Srikant: Fast Algorithms for Mining Association Rules in Large Databases. In: 20th International Conference on Very Large Data Bases, 478-499, 1994.<br/>
+ * <br/>
+ * Bing Liu, Wynne Hsu, Yiming Ma: Integrating Classification and Association Rule Mining. In: Fourth International Conference on Knowledge Discovery and Data Mining, 80-86, 1998.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Agrawal1994,
+ *    author = {R. Agrawal and R. Srikant},
+ *    booktitle = {20th International Conference on Very Large Data Bases},
+ *    pages = {478-499},
+ *    publisher = {Morgan Kaufmann, Los Altos, CA},
+ *    title = {Fast Algorithms for Mining Association Rules in Large Databases},
+ *    year = {1994}
+ * }
+ * 
+ * &#64;inproceedings{Liu1998,
+ *    author = {Bing Liu and Wynne Hsu and Yiming Ma},
+ *    booktitle = {Fourth International Conference on Knowledge Discovery and Data Mining},
+ *    pages = {80-86},
+ *    publisher = {AAAI Press},
+ *    title = {Integrating Classification and Association Rule Mining},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;required number of rules output&gt;
+ *  The required number of rules. (default = 10)</pre>
+ * 
+ * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+ *  The metric type by which to rank rules. (default = confidence)</pre>
+ * 
+ * <pre> -C &lt;minimum metric score of a rule&gt;
+ *  The minimum confidence of a rule. (default = 0.9)</pre>
+ * 
+ * <pre> -D &lt;delta for minimum support&gt;
+ *  The delta by which the minimum support is decreased in
+ *  each iteration. (default = 0.05)</pre>
+ * 
+ * <pre> -U &lt;upper bound for minimum support&gt;
+ *  Upper bound for minimum support. (default = 1.0)</pre>
+ * 
+ * <pre> -M &lt;lower bound for minimum support&gt;
+ *  The lower bound for the minimum support. (default = 0.1)</pre>
+ * 
+ * <pre> -S &lt;significance level&gt;
+ *  If used, rules are tested for significance at
+ *  the given level. Slower. (default = no significance testing)</pre>
+ * 
+ * <pre> -I
+ *  If set the itemsets found are also output. (default = no)</pre>
+ * 
+ * <pre> -R
+ *  Remove columns that contain all missing values (default = no)</pre>
+ * 
+ * <pre> -V
+ *  Report progress iteratively. (default = no)</pre>
+ * 
+ * <pre> -A
+ *  If set class association rules are mined. (default = no)</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ *  The class index. (default = last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5698 $
+ */
+public class Apriori 
+  extends AbstractAssociator 
+  implements OptionHandler, CARuleMiner, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3277498842319212687L;
+  
+  /** The minimum support. */
+  protected double m_minSupport;
+
+  /** The upper bound on the support */
+  protected double m_upperBoundMinSupport;
+
+  /** The lower bound for the minimum support. */
+  protected double m_lowerBoundMinSupport;
+
+  /** Metric type: Confidence */
+  protected static final int CONFIDENCE = 0;
+  /** Metric type: Lift */
+  protected static final int LIFT = 1;
+  /** Metric type: Leverage */
+  protected static final int LEVERAGE = 2;
+  /** Metric type: Conviction */
+  protected static final int CONVICTION = 3;
+  /** Metric types. */
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(CONFIDENCE, "Confidence"),
+    new Tag(LIFT, "Lift"),
+    new Tag(LEVERAGE, "Leverage"),
+    new Tag(CONVICTION, "Conviction")
+      };
+
+  /** The selected metric type. */
+  protected int m_metricType = CONFIDENCE;
+
+  /** The minimum metric score. */
+  protected double m_minMetric;
+
+  /** The maximum number of rules that are output. */
+  protected int m_numRules;
+
+  /** Delta by which m_minSupport is decreased in each iteration. */
+  protected double m_delta;
+
+  /** Significance level for optional significance test. */
+  protected double m_significanceLevel;
+
+  /** Number of cycles used before required number of rules was one. */
+  protected int m_cycles;
+
+  /** The set of all sets of itemsets L. */
+  protected FastVector m_Ls;
+
+  /** The same information stored in hash tables. */
+  protected FastVector m_hashtables;
+
+  /** The list of all generated rules. */
+  protected FastVector[] m_allTheRules;
+
+  /** The instances (transactions) to be used for generating 
+      the association rules. */
+  protected Instances m_instances;
+
+  /** Output itemsets found? */
+  protected boolean m_outputItemSets;
+
+  /** Remove columns with all missing values */
+  protected boolean m_removeMissingCols;
+
+  /** Report progress iteratively */
+  protected boolean m_verbose;
+  
+  /** Only the class attribute of all Instances.*/
+  protected Instances m_onlyClass;
+  
+  /** The class index. */  
+  protected int m_classIndex;
+  
+  /** Flag indicating whether class association rules are mined. */
+  protected boolean m_car;
+  
+  /** 
+   * Treat zeros as missing (rather than a value in their
+   * own right)
+   */
+  protected boolean m_treatZeroAsMissing = false;
+
+  /**
+   * Returns a string describing this associator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing an Apriori-type algorithm. Iteratively reduces "
+      + "the minimum support until it finds the required number of rules with "
+      + "the given minimum confidence.\n"
+      + "The algorithm has an option to mine class association rules. It is "
+      + "adapted as explained in the second reference.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "R. Agrawal and R. Srikant");
+    result.setValue(Field.TITLE, "Fast Algorithms for Mining Association Rules in Large Databases");
+    result.setValue(Field.BOOKTITLE, "20th International Conference on Very Large Data Bases");
+    result.setValue(Field.YEAR, "1994");
+    result.setValue(Field.PAGES, "478-499");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann, Los Altos, CA");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Bing Liu and Wynne Hsu and Yiming Ma");
+    additional.setValue(Field.TITLE, "Integrating Classification and Association Rule Mining");
+    additional.setValue(Field.BOOKTITLE, "Fourth International Conference on Knowledge Discovery and Data Mining");
+    additional.setValue(Field.YEAR, "1998");
+    additional.setValue(Field.PAGES, "80-86");
+    additional.setValue(Field.PUBLISHER, "AAAI Press");
+    
+    return result;
+  }
+
+  /**
+   * Constructor that allows to sets default values for the 
+   * minimum confidence and the maximum number of rules
+   * the minimum confidence.
+   */
+  public Apriori() {
+
+    resetOptions();
+  }
+
+  /**
+   * Resets the options to the default values.
+   */
+  public void resetOptions() {
+    
+    m_removeMissingCols = false;
+    m_verbose = false;
+    m_delta = 0.05;
+    m_minMetric = 0.90;
+    m_numRules = 10;
+    m_lowerBoundMinSupport = 0.1;
+    m_upperBoundMinSupport = 1.0;
+    m_significanceLevel = -1;
+    m_outputItemSets = false;
+    m_car = false;
+    m_classIndex = -1;
+  }
+
+  /**
+   * Removes columns that are all missing from the data
+   * @param instances the instances
+   * @return a new set of instances with all missing columns removed
+   * @throws Exception if something goes wrong
+   */
+  protected Instances removeMissingColumns(Instances instances) 
+    throws Exception {
+    
+    int numInstances = instances.numInstances();
+    StringBuffer deleteString = new StringBuffer();
+    int removeCount = 0;
+    boolean first = true;
+    int maxCount = 0;
+    
+    for (int i=0;i<instances.numAttributes();i++) {
+      AttributeStats as = instances.attributeStats(i);
+      if (m_upperBoundMinSupport == 1.0 && maxCount != numInstances) {
+	// see if we can decrease this by looking for the most frequent value
+	int [] counts = as.nominalCounts;
+	if (counts[Utils.maxIndex(counts)] > maxCount) {
+	  maxCount = counts[Utils.maxIndex(counts)];
+	}
+      }
+      if (as.missingCount == numInstances) {
+	if (first) {
+	  deleteString.append((i+1));
+	  first = false;
+	} else {
+	  deleteString.append(","+(i+1));
+	}
+	removeCount++;
+      }
+    }
+    if (m_verbose) {
+      System.err.println("Removed : "+removeCount+" columns with all missing "
+			 +"values.");
+    }
+    if (m_upperBoundMinSupport == 1.0 && maxCount != numInstances) {
+      m_upperBoundMinSupport = (double)maxCount / (double)numInstances;
+      if (m_verbose) {
+	System.err.println("Setting upper bound min support to : "
+			   +m_upperBoundMinSupport);
+      }
+    }
+
+    if (deleteString.toString().length() > 0) {
+      Remove af = new Remove();
+      af.setAttributeIndices(deleteString.toString());
+      af.setInvertSelection(false);
+      af.setInputFormat(instances);
+      Instances newInst = Filter.useFilter(instances, af);
+
+      return newInst;
+    }
+    return instances;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // enable what we can handle
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class (can handle a nominal class if CAR rules are selected). This
+    result.enable(Capability.NO_CLASS);
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Method that generates all large itemsets with a minimum support, and from
+   * these all association rules with a minimum confidence.
+   *
+   * @param instances the instances to be used for generating the associations
+   * @throws Exception if rules can't be built successfully
+   */
+  public void buildAssociations(Instances instances) throws Exception {
+
+    double[] confidences, supports;
+    int[] indices;
+    FastVector[] sortedRuleSet;
+    double necSupport=0;
+
+    instances = new Instances(instances);
+    
+    if (m_removeMissingCols) {
+      instances = removeMissingColumns(instances);
+    }
+    if(m_car && m_metricType != CONFIDENCE)
+      throw new Exception("For CAR-Mining metric type has to be confidence!");
+    
+    // only set class index if CAR is requested
+    if (m_car) {
+      if (m_classIndex == -1 ) {
+        instances.setClassIndex(instances.numAttributes()-1);     
+      } else if (m_classIndex <= instances.numAttributes() && m_classIndex > 0) {
+        instances.setClassIndex(m_classIndex - 1);
+      } else {
+        throw new Exception("Invalid class index.");
+      }
+    }
+
+    // can associator handle the data?
+    getCapabilities().testWithFail(instances);
+
+    m_cycles = 0;
+    
+    // make sure that the lower bound is equal to at least one instance
+    double lowerBoundMinSupportToUse = 
+      (m_lowerBoundMinSupport * (double)instances.numInstances() < 1.0)
+      ? 1.0 / (double)instances.numInstances()
+          : m_lowerBoundMinSupport;
+    
+    if(m_car){
+        //m_instances does not contain the class attribute
+        m_instances = LabeledItemSet.divide(instances,false);
+    
+        //m_onlyClass contains only the class attribute
+        m_onlyClass = LabeledItemSet.divide(instances,true);
+    }
+    else
+        m_instances = instances;
+    
+    if(m_car && m_numRules == Integer.MAX_VALUE){
+        // Set desired minimum support
+        m_minSupport = lowerBoundMinSupportToUse;
+    }
+    else{
+        // Decrease minimum support until desired number of rules found.
+        m_minSupport = m_upperBoundMinSupport - m_delta;
+        m_minSupport = (m_minSupport < lowerBoundMinSupportToUse) 
+            ? lowerBoundMinSupportToUse 
+            : m_minSupport;
+    }
+
+    do {
+
+      // Reserve space for variables
+      m_Ls = new FastVector();
+      m_hashtables = new FastVector();
+      m_allTheRules = new FastVector[6];
+      m_allTheRules[0] = new FastVector();
+      m_allTheRules[1] = new FastVector();
+      m_allTheRules[2] = new FastVector();
+      if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	m_allTheRules[3] = new FastVector();
+	m_allTheRules[4] = new FastVector();
+	m_allTheRules[5] = new FastVector();
+      }
+      sortedRuleSet = new FastVector[6];
+      sortedRuleSet[0] = new FastVector();
+      sortedRuleSet[1] = new FastVector();
+      sortedRuleSet[2] = new FastVector();
+      if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	sortedRuleSet[3] = new FastVector();
+	sortedRuleSet[4] = new FastVector();
+	sortedRuleSet[5] = new FastVector();
+      }
+      if(!m_car){
+        // Find large itemsets and rules
+        findLargeItemSets();
+        if (m_significanceLevel != -1 || m_metricType != CONFIDENCE) 
+            findRulesBruteForce();
+        else
+            findRulesQuickly();
+      }
+      else{
+          findLargeCarItemSets();
+          findCarRulesQuickly();
+      }
+      
+      // Sort rules according to their support
+      /*supports = new double[m_allTheRules[2].size()];
+      for (int i = 0; i < m_allTheRules[2].size(); i++) 
+	supports[i] = (double)((AprioriItemSet)m_allTheRules[1].elementAt(i)).support();
+      indices = Utils.stableSort(supports);
+      for (int i = 0; i < m_allTheRules[2].size(); i++) {
+	sortedRuleSet[0].addElement(m_allTheRules[0].elementAt(indices[i]));
+	sortedRuleSet[1].addElement(m_allTheRules[1].elementAt(indices[i]));
+	sortedRuleSet[2].addElement(m_allTheRules[2].elementAt(indices[i]));
+	if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	  sortedRuleSet[3].addElement(m_allTheRules[3].elementAt(indices[i]));
+	  sortedRuleSet[4].addElement(m_allTheRules[4].elementAt(indices[i]));
+	  sortedRuleSet[5].addElement(m_allTheRules[5].elementAt(indices[i]));
+	}
+      }*/
+      int j = m_allTheRules[2].size()-1;
+      supports = new double[m_allTheRules[2].size()];
+      for (int i = 0; i < (j+1); i++) 
+	supports[j-i] = ((double)((ItemSet)m_allTheRules[1].elementAt(j-i)).support())*(-1);
+      indices = Utils.stableSort(supports);
+      for (int i = 0; i < (j+1); i++) {
+	sortedRuleSet[0].addElement(m_allTheRules[0].elementAt(indices[j-i]));
+	sortedRuleSet[1].addElement(m_allTheRules[1].elementAt(indices[j-i]));
+	sortedRuleSet[2].addElement(m_allTheRules[2].elementAt(indices[j-i]));
+	if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	  sortedRuleSet[3].addElement(m_allTheRules[3].elementAt(indices[j-i]));
+	  sortedRuleSet[4].addElement(m_allTheRules[4].elementAt(indices[j-i]));
+	  sortedRuleSet[5].addElement(m_allTheRules[5].elementAt(indices[j-i]));
+	}
+      }
+
+      // Sort rules according to their confidence
+      m_allTheRules[0].removeAllElements();
+      m_allTheRules[1].removeAllElements();
+      m_allTheRules[2].removeAllElements();
+      if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	m_allTheRules[3].removeAllElements();
+	m_allTheRules[4].removeAllElements();
+	m_allTheRules[5].removeAllElements();
+      }
+      confidences = new double[sortedRuleSet[2].size()];
+      int sortType = 2 + m_metricType;
+
+      for (int i = 0; i < sortedRuleSet[2].size(); i++) 
+	confidences[i] = 
+	  ((Double)sortedRuleSet[sortType].elementAt(i)).doubleValue();
+      indices = Utils.stableSort(confidences);
+      for (int i = sortedRuleSet[0].size() - 1; 
+	   (i >= (sortedRuleSet[0].size() - m_numRules)) && (i >= 0); i--) {
+	m_allTheRules[0].addElement(sortedRuleSet[0].elementAt(indices[i]));
+	m_allTheRules[1].addElement(sortedRuleSet[1].elementAt(indices[i]));
+	m_allTheRules[2].addElement(sortedRuleSet[2].elementAt(indices[i]));
+	if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+	  m_allTheRules[3].addElement(sortedRuleSet[3].elementAt(indices[i]));
+	  m_allTheRules[4].addElement(sortedRuleSet[4].elementAt(indices[i]));
+	  m_allTheRules[5].addElement(sortedRuleSet[5].elementAt(indices[i]));
+	}
+      }
+
+      if (m_verbose) {
+	if (m_Ls.size() > 1) {
+	  System.out.println(toString());
+	}
+      }
+      if(m_minSupport == lowerBoundMinSupportToUse || m_minSupport - m_delta >  lowerBoundMinSupportToUse)
+        m_minSupport -= m_delta;
+      else
+        m_minSupport = lowerBoundMinSupportToUse;
+      
+      
+      necSupport = Math.rint(m_minSupport * (double)m_instances.numInstances());
+
+      m_cycles++;
+    } while ((m_allTheRules[0].size() < m_numRules) &&
+	     (Utils.grOrEq(m_minSupport, lowerBoundMinSupportToUse))
+	     /*	     (necSupport >= lowerBoundNumInstancesSupport)*/
+	     /*	     (Utils.grOrEq(m_minSupport, m_lowerBoundMinSupport)) */ &&     
+	     (necSupport >= 1));
+    m_minSupport += m_delta;
+  }
+  
+  
+      /**
+     * Method that mines all class association rules with minimum support and
+     * with a minimum confidence.
+     * @return an sorted array of FastVector (confidence depended) containing the rules and metric information
+     * @param data the instances for which class association rules should be mined
+     * @throws Exception if rules can't be built successfully
+     */
+    public FastVector[] mineCARs(Instances data) throws Exception{
+	 
+        m_car = true;
+	buildAssociations(data);
+	return m_allTheRules;
+    }
+
+   /**
+   * Gets the instances without the class atrribute.
+   *
+   * @return the instances without the class attribute.
+   */ 
+  public Instances getInstancesNoClass() {
+      
+      return m_instances;
+  }  
+  
+  
+  /**
+   * Gets only the class attribute of the instances.
+   *
+   * @return the class attribute of all instances.
+   */ 
+  public Instances getInstancesOnlyClass() {
+      
+      return m_onlyClass;
+  }  
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    String string1 = "\tThe required number of rules. (default = " + m_numRules + ")",
+      string2 = 
+      "\tThe minimum confidence of a rule. (default = " + m_minMetric + ")",
+      string3 = "\tThe delta by which the minimum support is decreased in\n",
+      string4 = "\teach iteration. (default = " + m_delta + ")",
+      string5 = 
+      "\tThe lower bound for the minimum support. (default = " + 
+      m_lowerBoundMinSupport + ")",
+      string6 = "\tIf used, rules are tested for significance at\n",
+      string7 = "\tthe given level. Slower. (default = no significance testing)",
+      string8 = "\tIf set the itemsets found are also output. (default = no)",
+      string9 = "\tIf set class association rules are mined. (default = no)",
+      string10 = "\tThe class index. (default = last)",
+      stringType = "\tThe metric type by which to rank rules. (default = "
+      +"confidence)",
+      stringZeroAsMissing = "\tTreat zero (i.e. first value of nominal attributes) as " +
+      		"missing";
+    
+
+    FastVector newVector = new FastVector(11);
+
+    newVector.addElement(new Option(string1, "N", 1, 
+				    "-N <required number of rules output>"));
+    newVector.addElement(new Option(stringType, "T", 1,
+				    "-T <0=confidence | 1=lift | "
+				    +"2=leverage | 3=Conviction>"));
+    newVector.addElement(new Option(string2, "C", 1, 
+				    "-C <minimum metric score of a rule>"));
+    newVector.addElement(new Option(string3 + string4, "D", 1,
+				    "-D <delta for minimum support>"));
+    newVector.addElement(new Option("\tUpper bound for minimum support. "
+				    +"(default = 1.0)", "U", 1,
+				     "-U <upper bound for minimum support>"));
+    newVector.addElement(new Option(string5, "M", 1,
+				    "-M <lower bound for minimum support>"));
+    newVector.addElement(new Option(string6 + string7, "S", 1,
+				    "-S <significance level>"));
+    newVector.addElement(new Option(string8, "I", 0,
+				    "-I"));
+    newVector.addElement(new Option("\tRemove columns that contain "
+				    +"all missing values (default = no)"
+				    , "R", 0,
+				    "-R"));
+    newVector.addElement(new Option("\tReport progress iteratively. (default "
+				    +"= no)", "V", 0,
+				    "-V"));
+    newVector.addElement(new Option(string9, "A", 0,
+				    "-A"));
+    newVector.addElement(new Option(stringZeroAsMissing, "Z", 0,
+        "-Z"));
+    newVector.addElement(new Option(string10, "c", 1,
+				    "-c <the class index>"));
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;required number of rules output&gt;
+   *  The required number of rules. (default = 10)</pre>
+   * 
+   * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+   *  The metric type by which to rank rules. (default = confidence)</pre>
+   * 
+   * <pre> -C &lt;minimum metric score of a rule&gt;
+   *  The minimum confidence of a rule. (default = 0.9)</pre>
+   * 
+   * <pre> -D &lt;delta for minimum support&gt;
+   *  The delta by which the minimum support is decreased in
+   *  each iteration. (default = 0.05)</pre>
+   * 
+   * <pre> -U &lt;upper bound for minimum support&gt;
+   *  Upper bound for minimum support. (default = 1.0)</pre>
+   * 
+   * <pre> -M &lt;lower bound for minimum support&gt;
+   *  The lower bound for the minimum support. (default = 0.1)</pre>
+   * 
+   * <pre> -S &lt;significance level&gt;
+   *  If used, rules are tested for significance at
+   *  the given level. Slower. (default = no significance testing)</pre>
+   * 
+   * <pre> -I
+   *  If set the itemsets found are also output. (default = no)</pre>
+   * 
+   * <pre> -R
+   *  Remove columns that contain all missing values (default = no)</pre>
+   * 
+   * <pre> -V
+   *  Report progress iteratively. (default = no)</pre>
+   * 
+   * <pre> -A
+   *  If set class association rules are mined. (default = no)</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   *  The class index. (default = last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    resetOptions();
+    String numRulesString = Utils.getOption('N', options),
+      minConfidenceString = Utils.getOption('C', options),
+      deltaString = Utils.getOption('D', options),
+      maxSupportString = Utils.getOption('U', options),
+      minSupportString = Utils.getOption('M', options),
+      significanceLevelString = Utils.getOption('S', options),
+      classIndexString = Utils.getOption('c',options);
+    
+    String metricTypeString = Utils.getOption('T', options);
+    if (metricTypeString.length() != 0) {
+      setMetricType(new SelectedTag(Integer.parseInt(metricTypeString),
+				    TAGS_SELECTION));
+    }
+    
+    if (numRulesString.length() != 0) {
+      m_numRules = Integer.parseInt(numRulesString);
+    }
+    if (classIndexString.length() != 0) {
+      if (classIndexString.equalsIgnoreCase("last")) {
+        m_classIndex = -1;
+      } else if (classIndexString.equalsIgnoreCase("first")) {
+        m_classIndex = 0;
+      } else {
+        m_classIndex = Integer.parseInt(classIndexString);
+      }
+    }
+    if (minConfidenceString.length() != 0) {
+      m_minMetric = (new Double(minConfidenceString)).doubleValue();
+    }
+    if (deltaString.length() != 0) {
+      m_delta = (new Double(deltaString)).doubleValue();
+    }
+    if (maxSupportString.length() != 0) {
+      setUpperBoundMinSupport((new Double(maxSupportString)).doubleValue());
+    }
+    if (minSupportString.length() != 0) {
+      m_lowerBoundMinSupport = (new Double(minSupportString)).doubleValue();
+    }
+    if (significanceLevelString.length() != 0) {
+      m_significanceLevel = (new Double(significanceLevelString)).doubleValue();
+    }
+    m_outputItemSets = Utils.getFlag('I', options);
+    m_car = Utils.getFlag('A', options);
+    m_verbose = Utils.getFlag('V', options);
+    m_treatZeroAsMissing = Utils.getFlag('Z', options);
+    
+    setRemoveAllMissingCols(Utils.getFlag('R', options));
+  }
+
+  /**
+   * Gets the current settings of the Apriori object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [21];
+    int current = 0;
+
+    if (m_outputItemSets) {
+      options[current++] = "-I";
+    }
+
+    if (getRemoveAllMissingCols()) {
+      options[current++] = "-R";
+    }
+
+    options[current++] = "-N"; options[current++] = "" + m_numRules;
+    options[current++] = "-T"; options[current++] = "" + m_metricType;
+    options[current++] = "-C"; options[current++] = "" + m_minMetric;
+    options[current++] = "-D"; options[current++] = "" + m_delta;
+    options[current++] = "-U"; options[current++] = "" + m_upperBoundMinSupport;
+    options[current++] = "-M"; options[current++] = "" + m_lowerBoundMinSupport;
+    options[current++] = "-S"; options[current++] = "" + m_significanceLevel;
+    if (m_car)
+      options[current++] = "-A";
+    if (m_verbose)
+      options[current++] = "-V";
+    
+    if (m_treatZeroAsMissing) {
+      options[current++] = "-Z";
+    }
+    options[current++] = "-c"; options[current++] = "" + m_classIndex;
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Outputs the size of all the generated sets of itemsets and the rules.
+   * 
+   * @return a string representation of the model
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    if (m_Ls.size() <= 1)
+      return "\nNo large itemsets and rules found!\n";
+    text.append("\nApriori\n=======\n\n");
+    text.append("Minimum support: " 
+		+ Utils.doubleToString(m_minSupport,2) 
+		+ " (" + ((int)(m_minSupport * (double)m_instances.numInstances()+0.5)) 
+		+ " instances)"
+		+ '\n');
+    text.append("Minimum metric <");
+    switch(m_metricType) {
+    case CONFIDENCE:
+      text.append("confidence>: ");
+      break;
+    case LIFT:
+      text.append("lift>: ");
+      break;
+    case LEVERAGE:
+      text.append("leverage>: ");
+      break;
+    case CONVICTION:
+      text.append("conviction>: ");
+      break;
+    }
+    text.append(Utils.doubleToString(m_minMetric,2)+'\n');
+   
+    if (m_significanceLevel != -1)
+      text.append("Significance level: "+
+		  Utils.doubleToString(m_significanceLevel,2)+'\n');
+    text.append("Number of cycles performed: " + m_cycles+'\n');
+    text.append("\nGenerated sets of large itemsets:\n");
+    if(!m_car){
+        for (int i = 0; i < m_Ls.size(); i++) {
+            text.append("\nSize of set of large itemsets L("+(i+1)+"): "+
+		  ((FastVector)m_Ls.elementAt(i)).size()+'\n');
+            if (m_outputItemSets) {
+                text.append("\nLarge Itemsets L("+(i+1)+"):\n");
+                for (int j = 0; j < ((FastVector)m_Ls.elementAt(i)).size(); j++)
+                    text.append(((AprioriItemSet)((FastVector)m_Ls.elementAt(i)).elementAt(j)).
+		      toString(m_instances)+"\n");
+            }
+        }
+        text.append("\nBest rules found:\n\n");
+        for (int i = 0; i < m_allTheRules[0].size(); i++) {
+            text.append(Utils.doubleToString((double)i+1, 
+		  (int)(Math.log(m_numRules)/Math.log(10)+1),0)+
+		  ". " + ((AprioriItemSet)m_allTheRules[0].elementAt(i)).
+		  toString(m_instances) 
+		  + " ==> " + ((AprioriItemSet)m_allTheRules[1].elementAt(i)).
+		  toString(m_instances) +"    conf:("+  
+		  Utils.doubleToString(((Double)m_allTheRules[2].
+					elementAt(i)).doubleValue(),2)+")");
+            if (m_metricType != CONFIDENCE || m_significanceLevel != -1) {
+                text.append((m_metricType == LIFT ? " <" : "")+" lift:("+  
+		    Utils.doubleToString(((Double)m_allTheRules[3].
+					  elementAt(i)).doubleValue(),2)
+		    +")"+(m_metricType == LIFT ? ">" : ""));
+                text.append((m_metricType == LEVERAGE ? " <" : "")+" lev:("+  
+		    Utils.doubleToString(((Double)m_allTheRules[4].
+					  elementAt(i)).doubleValue(),2)
+		    +")");
+                text.append(" ["+
+		    (int)(((Double)m_allTheRules[4].elementAt(i))
+			  .doubleValue() * (double)m_instances.numInstances())
+		    +"]"+(m_metricType == LEVERAGE ? ">" : ""));
+                text.append((m_metricType == CONVICTION ? " <" : "")+" conv:("+  
+		    Utils.doubleToString(((Double)m_allTheRules[5].
+					  elementAt(i)).doubleValue(),2)
+		    +")"+(m_metricType == CONVICTION ? ">" : ""));
+            }
+            text.append('\n');
+        }
+    }
+    else{
+        for (int i = 0; i < m_Ls.size(); i++) {
+            text.append("\nSize of set of large itemsets L("+(i+1)+"): "+
+		  ((FastVector)m_Ls.elementAt(i)).size()+'\n');
+            if (m_outputItemSets) {
+                text.append("\nLarge Itemsets L("+(i+1)+"):\n");
+                for (int j = 0; j < ((FastVector)m_Ls.elementAt(i)).size(); j++){
+                    text.append(((ItemSet)((FastVector)m_Ls.elementAt(i)).elementAt(j)).
+		      toString(m_instances)+"\n");
+                    text.append(((LabeledItemSet)((FastVector)m_Ls.elementAt(i)).elementAt(j)).m_classLabel+"  ");
+                    text.append(((LabeledItemSet)((FastVector)m_Ls.elementAt(i)).elementAt(j)).support()+"\n");
+                }
+            }
+        }
+        text.append("\nBest rules found:\n\n");
+        for (int i = 0; i < m_allTheRules[0].size(); i++) {
+            text.append(Utils.doubleToString((double)i+1, 
+					     (int)(Math.log(m_numRules)/Math.log(10)+1),0)+
+			". " + ((ItemSet)m_allTheRules[0].elementAt(i)).
+			toString(m_instances) 
+			+ " ==> " + ((ItemSet)m_allTheRules[1].elementAt(i)).
+			toString(m_onlyClass) +"    conf:("+  
+			Utils.doubleToString(((Double)m_allTheRules[2].
+					      elementAt(i)).doubleValue(),2)+")");
+	
+            text.append('\n');
+        }
+    }
+    return text.toString();
+  }
+  
+   /**
+   * Returns the metric string for the chosen metric type
+   * @return a string describing the used metric for the interestingness of a class association rule
+   */
+  public String metricString() {
+      
+        switch(m_metricType) {
+	case LIFT:
+	    return "lif";
+	case LEVERAGE:
+	    return "leverage"; 
+	case CONVICTION:
+	    return "conviction";
+        default:
+            return "conf";
+	}
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String removeAllMissingColsTipText() {
+    return "Remove columns with all missing values.";
+  }
+
+  /**
+   * Remove columns containing all missing values.
+   * @param r true if cols are to be removed.
+   */
+  public void setRemoveAllMissingCols(boolean r) {
+    m_removeMissingCols = r;
+  }
+
+  /**
+   * Returns whether columns containing all missing values are to be removed
+   * @return true if columns are to be removed.
+   */
+  public boolean getRemoveAllMissingCols() {
+    return m_removeMissingCols;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String upperBoundMinSupportTipText() {
+    return "Upper bound for minimum support. Start iteratively decreasing "
+      +"minimum support from this value.";
+  }
+
+  /**
+   * Get the value of upperBoundMinSupport.
+   *
+   * @return Value of upperBoundMinSupport.
+   */
+  public double getUpperBoundMinSupport() {
+    
+    return m_upperBoundMinSupport;
+  }
+  
+  /**
+   * Set the value of upperBoundMinSupport.
+   *
+   * @param v  Value to assign to upperBoundMinSupport.
+   */
+  public void setUpperBoundMinSupport(double v) {
+    
+    m_upperBoundMinSupport = v;
+  }
+
+   /**
+   * Sets the class index
+   * @param index the class index
+   */  
+  public void setClassIndex(int index){
+      
+      m_classIndex = index;
+  }
+  
+  /**
+   * Gets the class index
+   * @return the index of the class attribute
+   */  
+  public int getClassIndex(){
+      
+      return m_classIndex;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Index of the class attribute. If set to -1, the last attribute is taken as class attribute.";
+
+  }
+
+  /**
+   * Sets class association rule mining
+   * @param flag if class association rules are mined, false otherwise
+   */  
+  public void setCar(boolean flag){
+      m_car = flag;
+  }
+  
+  /**
+   * Gets whether class association ruels are mined
+   * @return true if class association rules are mined, false otherwise
+   */  
+  public boolean getCar(){
+      return m_car;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String carTipText() {
+    return "If enabled class association rules are mined instead of (general) association rules.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lowerBoundMinSupportTipText() {
+    return "Lower bound for minimum support.";
+  }
+
+  /**
+   * Get the value of lowerBoundMinSupport.
+   *
+   * @return Value of lowerBoundMinSupport.
+   */
+  public double getLowerBoundMinSupport() {
+    
+    return m_lowerBoundMinSupport;
+  }
+  
+  /**
+   * Set the value of lowerBoundMinSupport.
+   *
+   * @param v  Value to assign to lowerBoundMinSupport.
+   */
+  public void setLowerBoundMinSupport(double v) {
+    
+    m_lowerBoundMinSupport = v;
+  }
+  
+  /**
+   * Get the metric type
+   *
+   * @return the type of metric to use for ranking rules
+   */
+  public SelectedTag getMetricType() {
+    return new SelectedTag(m_metricType, TAGS_SELECTION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String metricTypeTipText() {
+    return "Set the type of metric by which to rank rules. Confidence is "
+      +"the proportion of the examples covered by the premise that are also "
+      +"covered by the consequence(Class association rules can only be mined using confidence). Lift is confidence divided by the "
+      +"proportion of all examples that are covered by the consequence. This "
+      +"is a measure of the importance of the association that is independent "
+      +"of support. Leverage is the proportion of additional examples covered "
+      +"by both the premise and consequence above those expected if the "
+      +"premise and consequence were independent of each other. The total "
+      +"number of examples that this represents is presented in brackets "
+      +"following the leverage. Conviction is "
+      +"another measure of departure from independence. Conviction is given "
+      +"by ";
+  }
+
+  /**
+   * Set the metric type for ranking rules
+   *
+   * @param d the type of metric
+   */
+  public void setMetricType (SelectedTag d) {
+    
+    if (d.getTags() == TAGS_SELECTION) {
+      m_metricType = d.getSelectedTag().getID();
+    }
+
+    if (m_significanceLevel != -1 && m_metricType != CONFIDENCE) {
+      m_metricType = CONFIDENCE;
+    }
+
+    if (m_metricType == CONFIDENCE) {
+      setMinMetric(0.9);
+    }
+
+    if (m_metricType == LIFT || m_metricType == CONVICTION) {
+      setMinMetric(1.1);
+    }
+  
+    if (m_metricType == LEVERAGE) {
+      setMinMetric(0.1);
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minMetricTipText() {
+    return "Minimum metric score. Consider only rules with scores higher than "
+      +"this value.";
+  }
+
+  /**
+   * Get the value of minConfidence.
+   *
+   * @return Value of minConfidence.
+   */
+  public double getMinMetric() {
+    
+    return m_minMetric;
+  }
+  
+  /**
+   * Set the value of minConfidence.
+   *
+   * @param v  Value to assign to minConfidence.
+   */
+  public void setMinMetric(double v) {
+    
+    m_minMetric = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numRulesTipText() {
+    return "Number of rules to find.";
+  }
+
+  /**
+   * Get the value of numRules.
+   *
+   * @return Value of numRules.
+   */
+  public int getNumRules() {
+    
+    return m_numRules;
+  }
+  
+  /**
+   * Set the value of numRules.
+   *
+   * @param v  Value to assign to numRules.
+   */
+  public void setNumRules(int v) {
+    
+    m_numRules = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String deltaTipText() {
+    return "Iteratively decrease support by this factor. Reduces support "
+      +"until min support is reached or required number of rules has been "
+      +"generated.";
+  }
+    
+  /**
+   * Get the value of delta.
+   *
+   * @return Value of delta.
+   */
+  public double getDelta() {
+    
+    return m_delta;
+  }
+  
+  /**
+   * Set the value of delta.
+   *
+   * @param v  Value to assign to delta.
+   */
+  public void setDelta(double v) {
+    
+    m_delta = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String significanceLevelTipText() {
+    return "Significance level. Significance test (confidence metric only).";
+  }
+
+  /**
+   * Get the value of significanceLevel.
+   *
+   * @return Value of significanceLevel.
+   */
+  public double getSignificanceLevel() {
+    
+    return m_significanceLevel;
+  }
+  
+  /**
+   * Set the value of significanceLevel.
+   *
+   * @param v  Value to assign to significanceLevel.
+   */
+  public void setSignificanceLevel(double v) {
+    
+    m_significanceLevel = v;
+  }
+
+  /**
+   * Sets whether itemsets are output as well
+   * @param flag true if itemsets are to be output as well
+   */  
+  public void setOutputItemSets(boolean flag){
+    m_outputItemSets = flag;
+  }
+  
+  /**
+   * Gets whether itemsets are output as well
+   * @return true if itemsets are output as well
+   */  
+  public boolean getOutputItemSets(){
+    return m_outputItemSets;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String outputItemSetsTipText() {
+    return "If enabled the itemsets are output as well.";
+  }
+
+  /**
+   * Sets verbose mode
+   * @param flag true if algorithm should be run in verbose mode
+   */  
+  public void setVerbose(boolean flag){
+    m_verbose = flag;
+  }
+  
+  /**
+   * Gets whether algorithm is run in verbose mode
+   * @return true if algorithm is run in verbose mode
+   */  
+  public boolean getVerbose(){
+    return m_verbose;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "If enabled the algorithm will be run in verbose mode.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String treatZeroAsMissingTipText() {
+    return "If enabled, zero (that is, the first value of a nominal) is "
+    + "treated in the same way as a missing value.";
+  }
+  
+  /**
+   * Sets whether zeros (i.e. the first value of a nominal attribute)
+   * should be treated as missing values.
+   * 
+   * @param z true if zeros should be treated as missing values.
+   */
+  public void setTreatZeroAsMissing(boolean z) {
+    m_treatZeroAsMissing = z;
+  }
+  
+  /**
+   * Gets whether zeros (i.e. the first value of a nominal attribute)
+   * is to be treated int he same way as missing values.
+   * 
+   * @return true if zeros are to be treated like missing values.
+   */
+  public boolean getTreatZeroAsMissing() {
+    return m_treatZeroAsMissing;
+  }
+
+  /** 
+   * Method that finds all large itemsets for the given set of instances.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+  private void findLargeItemSets() throws Exception {
+    
+    FastVector kMinusOneSets, kSets;
+    Hashtable hashtable;
+    int necSupport, necMaxSupport,i = 0;
+    
+    
+    
+    // Find large itemsets
+
+    // minimum support
+    necSupport = (int)(m_minSupport * (double)m_instances.numInstances()+0.5);
+    necMaxSupport = (int)(m_upperBoundMinSupport * (double)m_instances.numInstances()+0.5);
+   
+    kSets = AprioriItemSet.singletons(m_instances, m_treatZeroAsMissing);
+    AprioriItemSet.upDateCounters(kSets,m_instances);
+    kSets = AprioriItemSet.deleteItemSets(kSets, necSupport, necMaxSupport);
+    if (kSets.size() == 0)
+      return;
+    do {
+      m_Ls.addElement(kSets);
+      kMinusOneSets = kSets;
+      kSets = AprioriItemSet.mergeAllItemSets(kMinusOneSets, i, m_instances.numInstances());
+      hashtable = AprioriItemSet.getHashtable(kMinusOneSets, kMinusOneSets.size());
+      m_hashtables.addElement(hashtable);
+      kSets = AprioriItemSet.pruneItemSets(kSets, hashtable);
+      AprioriItemSet.upDateCounters(kSets, m_instances);
+      kSets = AprioriItemSet.deleteItemSets(kSets, necSupport, necMaxSupport);
+      i++;
+    } while (kSets.size() > 0);
+  }  
+
+  /** 
+   * Method that finds all association rules and performs significance test.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+  private void findRulesBruteForce() throws Exception {
+
+    FastVector[] rules;
+
+    // Build rules
+    for (int j = 1; j < m_Ls.size(); j++) {
+      FastVector currentItemSets = (FastVector)m_Ls.elementAt(j);
+      Enumeration enumItemSets = currentItemSets.elements();
+      while (enumItemSets.hasMoreElements()) {
+	AprioriItemSet currentItemSet = (AprioriItemSet)enumItemSets.nextElement();
+        //AprioriItemSet currentItemSet = new AprioriItemSet((ItemSet)enumItemSets.nextElement());
+	rules=currentItemSet.generateRulesBruteForce(m_minMetric,m_metricType,
+				  m_hashtables,j+1,
+				  m_instances.numInstances(),
+				  m_significanceLevel);
+	for (int k = 0; k < rules[0].size(); k++) {
+	  m_allTheRules[0].addElement(rules[0].elementAt(k));
+	  m_allTheRules[1].addElement(rules[1].elementAt(k));
+	  m_allTheRules[2].addElement(rules[2].elementAt(k));
+
+	  m_allTheRules[3].addElement(rules[3].elementAt(k));
+	  m_allTheRules[4].addElement(rules[4].elementAt(k));
+	  m_allTheRules[5].addElement(rules[5].elementAt(k));
+	}
+      }
+    }
+  }
+
+  /** 
+   * Method that finds all association rules.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+  private void findRulesQuickly() throws Exception {
+
+    FastVector[] rules;
+
+    // Build rules
+    for (int j = 1; j < m_Ls.size(); j++) {
+      FastVector currentItemSets = (FastVector)m_Ls.elementAt(j);
+      Enumeration enumItemSets = currentItemSets.elements();
+      while (enumItemSets.hasMoreElements()) {
+	AprioriItemSet currentItemSet = (AprioriItemSet)enumItemSets.nextElement();
+        //AprioriItemSet currentItemSet = new AprioriItemSet((ItemSet)enumItemSets.nextElement());
+	rules = currentItemSet.generateRules(m_minMetric, m_hashtables, j + 1);
+	for (int k = 0; k < rules[0].size(); k++) {
+	  m_allTheRules[0].addElement(rules[0].elementAt(k));
+	  m_allTheRules[1].addElement(rules[1].elementAt(k));
+	  m_allTheRules[2].addElement(rules[2].elementAt(k));
+	}
+      }
+    }
+  }
+  
+      /**
+     *
+     * Method that finds all large itemsets for class association rules for the given set of instances.
+     * @throws Exception if an attribute is numeric
+     */
+    private void findLargeCarItemSets() throws Exception {
+	
+	FastVector kMinusOneSets, kSets;
+	Hashtable hashtable;
+	int necSupport, necMaxSupport,i = 0;
+	
+	// Find large itemsets
+	
+	// minimum support
+        double nextMinSupport = m_minSupport*(double)m_instances.numInstances();
+        double nextMaxSupport = m_upperBoundMinSupport*(double)m_instances.numInstances();
+	if((double)Math.rint(nextMinSupport) == nextMinSupport){
+            necSupport = (int) nextMinSupport;
+        }
+        else{
+            necSupport = Math.round((float)(nextMinSupport+0.5));
+        }
+        if((double)Math.rint(nextMaxSupport) == nextMaxSupport){
+            necMaxSupport = (int) nextMaxSupport;
+        }
+        else{
+            necMaxSupport = Math.round((float)(nextMaxSupport+0.5));
+        }
+	
+	//find item sets of length one
+	kSets = LabeledItemSet.singletons(m_instances,m_onlyClass);
+	LabeledItemSet.upDateCounters(kSets, m_instances,m_onlyClass);
+        
+        //check if a item set of lentgh one is frequent, if not delete it
+	kSets = LabeledItemSet.deleteItemSets(kSets, necSupport, necMaxSupport);
+        if (kSets.size() == 0)
+	    return;
+	do {
+	    m_Ls.addElement(kSets);
+	    kMinusOneSets = kSets;
+	    kSets = LabeledItemSet.mergeAllItemSets(kMinusOneSets, i, m_instances.numInstances());
+	    hashtable = LabeledItemSet.getHashtable(kMinusOneSets, kMinusOneSets.size());
+	    kSets = LabeledItemSet.pruneItemSets(kSets, hashtable);
+	    LabeledItemSet.upDateCounters(kSets, m_instances,m_onlyClass);
+	    kSets = LabeledItemSet.deleteItemSets(kSets, necSupport, necMaxSupport);
+	    i++;
+	} while (kSets.size() > 0);
+    } 
+
+   
+
+  /** 
+   * Method that finds all class association rules.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+   private void findCarRulesQuickly() throws Exception {
+
+    FastVector[] rules;
+
+    // Build rules
+    for (int j = 0; j < m_Ls.size(); j++) {
+      FastVector currentLabeledItemSets = (FastVector)m_Ls.elementAt(j);
+      Enumeration enumLabeledItemSets = currentLabeledItemSets.elements();
+      while (enumLabeledItemSets.hasMoreElements()) {
+	LabeledItemSet currentLabeledItemSet = (LabeledItemSet)enumLabeledItemSets.nextElement();
+	rules = currentLabeledItemSet.generateRules(m_minMetric,false);
+	for (int k = 0; k < rules[0].size(); k++) {
+	  m_allTheRules[0].addElement(rules[0].elementAt(k));
+	  m_allTheRules[1].addElement(rules[1].elementAt(k));
+	  m_allTheRules[2].addElement(rules[2].elementAt(k));
+	}
+      }
+    }
+  }
+
+  /**
+   * returns all the rules
+   *
+   * @return		all the rules
+   * @see		#m_allTheRules
+   */
+  public FastVector[] getAllTheRules() {
+    return m_allTheRules;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5698 $");
+  }
+
+  /**
+   * Main method.
+   * 
+   * @param args the commandline options
+   */
+  public static void main(String[] args) {
+    runAssociator(new Apriori(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/associations/AprioriItemSet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/AprioriItemSet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/AprioriItemSet.java	(revision 29)
@@ -0,0 +1,545 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AprioriItemSet.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.ContingencyTables;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * Class for storing a set of items. Item sets are stored in a lexicographic
+ * order, which is determined by the header information of the set of instances
+ * used for generating the set of items. All methods in this class assume that
+ * item sets are stored in lexicographic order.
+ * The class provides methods that are used in the Apriori algorithm to construct
+ * association rules.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5130 $
+ */
+public class AprioriItemSet 
+  extends ItemSet 
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 7684467755712672058L;
+
+  /**
+   * Constructor
+   * 
+   * @param totalTrans the total number of transactions in the data
+   */
+  public AprioriItemSet(int totalTrans) {
+    super(totalTrans);
+  }
+
+  
+  /**
+   * Outputs the confidence for a rule.
+   *
+   * @param premise the premise of the rule
+   * @param consequence the consequence of the rule
+   * @return the confidence on the training data
+   */
+  public static double confidenceForRule(AprioriItemSet premise, 
+					 AprioriItemSet consequence) {
+
+    return (double)consequence.m_counter/(double)premise.m_counter;
+  }
+
+  /**
+   * Outputs the lift for a rule. Lift is defined as:<br>
+   * confidence / prob(consequence)
+   *
+   * @param premise the premise of the rule
+   * @param consequence the consequence of the rule
+   * @param consequenceCount how many times the consequence occurs independent
+   * of the premise
+   * @return the lift on the training data
+   */
+  public double liftForRule(AprioriItemSet premise, 
+			    AprioriItemSet consequence,
+			    int consequenceCount) {
+    double confidence = confidenceForRule(premise, consequence);
+
+   return confidence / ((double)consequenceCount / 
+	  (double)m_totalTransactions);
+  }
+
+  /**
+   * Outputs the leverage for a rule. Leverage is defined as: <br>
+   * prob(premise & consequence) - (prob(premise) * prob(consequence))
+   *
+   * @param premise the premise of the rule
+   * @param consequence the consequence of the rule
+   * @param premiseCount how many times the premise occurs independent
+   * of the consequent
+   * @param consequenceCount how many times the consequence occurs independent
+   * of the premise
+   * @return the leverage on the training data
+   */
+  public double leverageForRule(AprioriItemSet premise,
+				AprioriItemSet consequence,
+				int premiseCount,
+				int consequenceCount) {
+    double coverageForItemSet = (double)consequence.m_counter / 
+      (double)m_totalTransactions;
+    double expectedCoverageIfIndependent = 
+      ((double)premiseCount / (double)m_totalTransactions) * 
+      ((double)consequenceCount / (double)m_totalTransactions);
+    double lev = coverageForItemSet - expectedCoverageIfIndependent;
+    return lev;
+  }
+
+  /**
+   * Outputs the conviction for a rule. Conviction is defined as: <br>
+   * prob(premise) * prob(!consequence) / prob(premise & !consequence)
+   *
+   * @param premise the premise of the rule
+   * @param consequence the consequence of the rule
+   * @param premiseCount how many times the premise occurs independent
+   * of the consequent
+   * @param consequenceCount how many times the consequence occurs independent
+   * of the premise
+   * @return the conviction on the training data
+   */
+  public double convictionForRule(AprioriItemSet premise,
+				   AprioriItemSet consequence,
+				   int premiseCount,
+				   int consequenceCount) {
+    double num = 
+      (double)premiseCount * (double)(m_totalTransactions - consequenceCount) /
+       (double)m_totalTransactions;
+    double denom = 
+      ((premiseCount - consequence.m_counter)+1);
+    
+    if (num < 0 || denom < 0) {
+      System.err.println("*** "+num+" "+denom);
+      System.err.println("premis count: "+premiseCount+" consequence count "+consequenceCount+" total trans "+m_totalTransactions);
+    }
+    return num / denom;
+  }
+
+  
+  
+  /**
+   * Generates all rules for an item set.
+   *
+   * @param minConfidence the minimum confidence the rules have to have
+   * @param hashtables containing all(!) previously generated
+   * item sets
+   * @param numItemsInSet the size of the item set for which the rules
+   * are to be generated
+   * @return all the rules with minimum confidence for the given item set
+   */
+  public FastVector[] generateRules(double minConfidence, 
+					  FastVector hashtables,
+					  int numItemsInSet) {
+
+    FastVector premises = new FastVector(),consequences = new FastVector(),
+      conf = new FastVector();
+    FastVector[] rules = new FastVector[3], moreResults;
+    AprioriItemSet premise, consequence;
+    Hashtable hashtable = (Hashtable)hashtables.elementAt(numItemsInSet - 2);
+
+    // Generate all rules with one item in the consequence.
+    for (int i = 0; i < m_items.length; i++) 
+      if (m_items[i] != -1) {
+	premise = new AprioriItemSet(m_totalTransactions);
+	consequence = new AprioriItemSet(m_totalTransactions);
+	premise.m_items = new int[m_items.length];
+	consequence.m_items = new int[m_items.length];
+	consequence.m_counter = m_counter;
+        
+	for (int j = 0; j < m_items.length; j++) 
+	  consequence.m_items[j] = -1;
+	System.arraycopy(m_items, 0, premise.m_items, 0, m_items.length);
+	premise.m_items[i] = -1;
+        
+	consequence.m_items[i] = m_items[i];
+	premise.m_counter = ((Integer)hashtable.get(premise)).intValue();
+	premises.addElement(premise);
+	consequences.addElement(consequence);
+	conf.addElement(new Double(confidenceForRule(premise, consequence)));
+      }
+    rules[0] = premises;
+    rules[1] = consequences;
+    rules[2] = conf;
+    pruneRules(rules, minConfidence);
+
+    // Generate all the other rules
+    moreResults = moreComplexRules(rules, numItemsInSet, 1, minConfidence,
+				   hashtables);
+    if (moreResults != null) 
+      for (int i = 0; i < moreResults[0].size(); i++) {
+	rules[0].addElement(moreResults[0].elementAt(i));
+	rules[1].addElement(moreResults[1].elementAt(i));
+	rules[2].addElement(moreResults[2].elementAt(i));
+      }
+    return rules;
+  }
+
+
+  
+  /**
+   * Generates all significant rules for an item set.
+   *
+   * @param minMetric the minimum metric (confidence, lift, leverage, 
+   * improvement) the rules have to have
+   * @param metricType (confidence=0, lift, leverage, improvement)
+   * @param hashtables containing all(!) previously generated
+   * item sets
+   * @param numItemsInSet the size of the item set for which the rules
+   * are to be generated
+   * @param numTransactions
+   * @param significanceLevel the significance level for testing the rules
+   * @return all the rules with minimum metric for the given item set
+   * @exception Exception if something goes wrong
+   */
+  public final FastVector[] generateRulesBruteForce(double minMetric,
+						    int metricType,
+						FastVector hashtables,
+						int numItemsInSet,
+						int numTransactions,
+						double significanceLevel) 
+  throws Exception {
+
+    FastVector premises = new FastVector(),consequences = new FastVector(),
+      conf = new FastVector(), lift = new FastVector(), lev = new FastVector(),
+      conv = new FastVector(); 
+    FastVector[] rules = new FastVector[6];
+    AprioriItemSet premise, consequence;
+    Hashtable hashtableForPremise, hashtableForConsequence;
+    int numItemsInPremise, help, max, consequenceUnconditionedCounter;
+    double[][] contingencyTable = new double[2][2];
+    double metric, chiSquared;
+
+    // Generate all possible rules for this item set and test their
+    // significance.
+    max = (int)Math.pow(2, numItemsInSet);
+    for (int j = 1; j < max; j++) {
+      numItemsInPremise = 0;
+      help = j;
+      while (help > 0) {
+	if (help % 2 == 1)
+	  numItemsInPremise++;
+	help /= 2;
+      }
+      if (numItemsInPremise < numItemsInSet) {
+	hashtableForPremise = 
+	  (Hashtable)hashtables.elementAt(numItemsInPremise-1);
+	hashtableForConsequence = 
+	  (Hashtable)hashtables.elementAt(numItemsInSet-numItemsInPremise-1);
+	premise = new AprioriItemSet(m_totalTransactions);
+	consequence = new AprioriItemSet(m_totalTransactions);
+	premise.m_items = new int[m_items.length];
+        
+	consequence.m_items = new int[m_items.length];
+	consequence.m_counter = m_counter;
+	help = j;
+	for (int i = 0; i < m_items.length; i++) 
+	  if (m_items[i] != -1) {
+	    if (help % 2 == 1) {          
+	      premise.m_items[i] = m_items[i];
+	      consequence.m_items[i] = -1;
+	    } else {
+	      premise.m_items[i] = -1;
+	      consequence.m_items[i] = m_items[i];
+	    }
+	    help /= 2;
+	  } else {
+	    premise.m_items[i] = -1;
+	    consequence.m_items[i] = -1;
+	  }
+	premise.m_counter = ((Integer)hashtableForPremise.get(premise)).intValue();
+	consequenceUnconditionedCounter =
+	  ((Integer)hashtableForConsequence.get(consequence)).intValue();
+
+	if (metricType == 0) {
+	  contingencyTable[0][0] = (double)(consequence.m_counter);
+	  contingencyTable[0][1] = (double)(premise.m_counter - consequence.m_counter);
+	  contingencyTable[1][0] = (double)(consequenceUnconditionedCounter -
+					    consequence.m_counter);
+	  contingencyTable[1][1] = (double)(numTransactions - premise.m_counter -
+					    consequenceUnconditionedCounter +
+					    consequence.m_counter);
+	  chiSquared = ContingencyTables.chiSquared(contingencyTable, false);
+	
+	  metric = confidenceForRule(premise, consequence);
+	
+	  if ((!(metric < minMetric)) &&
+	      (!(chiSquared > significanceLevel))) {
+	    premises.addElement(premise);
+	    consequences.addElement(consequence);
+	    conf.addElement(new Double(metric));
+	    lift.addElement(new Double(liftForRule(premise, consequence, 
+				       consequenceUnconditionedCounter)));
+	    lev.addElement(new Double(leverageForRule(premise, consequence,
+				     premise.m_counter,
+				     consequenceUnconditionedCounter)));
+	    conv.addElement(new Double(convictionForRule(premise, consequence,
+				       premise.m_counter,
+				       consequenceUnconditionedCounter)));
+	  }
+	} else {
+	  double tempConf = confidenceForRule(premise, consequence);
+	  double tempLift = liftForRule(premise, consequence, 
+					consequenceUnconditionedCounter);
+	  double tempLev = leverageForRule(premise, consequence,
+					   premise.m_counter,
+					   consequenceUnconditionedCounter);
+	  double tempConv = convictionForRule(premise, consequence,
+					      premise.m_counter,
+					      consequenceUnconditionedCounter);
+	  switch(metricType) {
+	  case 1: 
+	    metric = tempLift;
+	    break;
+	  case 2:
+	    metric = tempLev;
+	    break;
+	  case 3: 
+	    metric = tempConv;
+	    break;
+	  default:
+	    throw new Exception("ItemSet: Unknown metric type!");
+	  }
+	  if (!(metric < minMetric)) {
+	    premises.addElement(premise);
+	    consequences.addElement(consequence);
+	    conf.addElement(new Double(tempConf));
+	    lift.addElement(new Double(tempLift));
+	    lev.addElement(new Double(tempLev));
+	    conv.addElement(new Double(tempConv));
+	  }
+	}
+      }
+    }
+    rules[0] = premises;
+    rules[1] = consequences;
+    rules[2] = conf;
+    rules[3] = lift;
+    rules[4] = lev;
+    rules[5] = conv;
+    return rules;
+  }
+
+  /**
+   * Subtracts an item set from another one.
+   *
+   * @param toSubtract the item set to be subtracted from this one.
+   * @return an item set that only contains items form this item sets that
+   * are not contained by toSubtract
+   */
+  public final AprioriItemSet subtract(AprioriItemSet toSubtract) {
+
+    AprioriItemSet result = new AprioriItemSet(m_totalTransactions);
+    
+    result.m_items = new int[m_items.length];
+   
+    for (int i = 0; i < m_items.length; i++) 
+      if (toSubtract.m_items[i] == -1)
+	result.m_items[i] = m_items[i];
+      else
+	result.m_items[i] = -1;
+    result.m_counter = 0;
+    return result;
+  }
+
+
+  /**
+   * Generates rules with more than one item in the consequence.
+   *
+   * @param rules all the rules having (k-1)-item sets as consequences
+   * @param numItemsInSet the size of the item set for which the rules
+   * are to be generated
+   * @param numItemsInConsequence the value of (k-1)
+   * @param minConfidence the minimum confidence a rule has to have
+   * @param hashtables the hashtables containing all(!) previously generated
+   * item sets
+   * @return all the rules having (k)-item sets as consequences
+   */
+  private final FastVector[] moreComplexRules(FastVector[] rules, 
+					      int numItemsInSet, 
+					      int numItemsInConsequence,
+					      double minConfidence, 
+					      FastVector hashtables) {
+
+    AprioriItemSet newPremise;
+    FastVector[] result, moreResults;
+    FastVector newConsequences, newPremises = new FastVector(), 
+      newConf = new FastVector();
+    Hashtable hashtable;
+
+    if (numItemsInSet > numItemsInConsequence + 1) {
+      hashtable =
+	(Hashtable)hashtables.elementAt(numItemsInSet - numItemsInConsequence - 2);
+      newConsequences = mergeAllItemSets(rules[1], 
+					 numItemsInConsequence - 1,
+					 m_totalTransactions);
+      Enumeration enu = newConsequences.elements();
+      while (enu.hasMoreElements()) {
+	AprioriItemSet current = (AprioriItemSet)enu.nextElement();
+	current.m_counter = m_counter;
+	newPremise = subtract(current);
+	newPremise.m_counter = ((Integer)hashtable.get(newPremise)).intValue();
+	newPremises.addElement(newPremise);
+	newConf.addElement(new Double(confidenceForRule(newPremise, current)));
+      }
+      result = new FastVector[3];
+      result[0] = newPremises;
+      result[1] = newConsequences;
+      result[2] = newConf;
+      pruneRules(result, minConfidence);
+      moreResults = moreComplexRules(result,numItemsInSet,numItemsInConsequence+1,
+				     minConfidence, hashtables);
+      if (moreResults != null) 
+	for (int i = 0; i < moreResults[0].size(); i++) {
+	  result[0].addElement(moreResults[0].elementAt(i));
+	  result[1].addElement(moreResults[1].elementAt(i));
+	  result[2].addElement(moreResults[2].elementAt(i));
+	}
+      return result;
+    } else
+      return null;
+  }
+  
+  
+   /**
+   * Returns the contents of an item set as a string.
+   *
+   * @param instances contains the relevant header information
+   * @return string describing the item set
+   */
+  public final String toString(Instances instances) {
+   
+      return super.toString(instances);
+  }
+  
+  /**
+   * Converts the header info of the given set of instances into a set 
+   * of item sets (singletons). The ordering of values in the header file 
+   * determines the lexicographic order.
+   *
+   * @param instances the set of instances whose header info is to be used
+   * @return a set of item sets, each containing a single item
+   * @exception Exception if singletons can't be generated successfully
+   */
+  public static FastVector singletons(Instances instances,
+      boolean treatZeroAsMissing) throws Exception {
+
+    FastVector setOfItemSets = new FastVector();
+    ItemSet current;
+
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      if (instances.attribute(i).isNumeric())
+	throw new Exception("Can't handle numeric attributes!");
+      int j = (treatZeroAsMissing) ? 1 : 0;
+      for (; j < instances.attribute(i).numValues(); j++) {
+	current = new AprioriItemSet(instances.numInstances());
+	current.setTreatZeroAsMissing(treatZeroAsMissing);
+	current.m_items = new int[instances.numAttributes()];
+	for (int k = 0; k < instances.numAttributes(); k++)
+	  current.m_items[k] = -1;
+	current.m_items[i] = j;
+	setOfItemSets.addElement(current);
+      }
+    }
+    return setOfItemSets;
+  }
+  
+  /**
+   * Merges all item sets in the set of (k-1)-item sets 
+   * to create the (k)-item sets and updates the counters.
+   *
+   * @param itemSets the set of (k-1)-item sets
+   * @param size the value of (k-1)
+   * @param totalTrans the total number of transactions in the data
+   * @return the generated (k)-item sets
+   */
+  public static FastVector mergeAllItemSets(FastVector itemSets, int size, 
+					    int totalTrans) {
+
+    FastVector newVector = new FastVector();
+    ItemSet result;
+    int numFound, k;
+
+    for (int i = 0; i < itemSets.size(); i++) {
+      ItemSet first = (ItemSet)itemSets.elementAt(i);
+    out:
+      for (int j = i+1; j < itemSets.size(); j++) {
+	ItemSet second = (ItemSet)itemSets.elementAt(j);
+	result = new AprioriItemSet(totalTrans);
+	result.m_items = new int[first.m_items.length];
+
+	// Find and copy common prefix of size 'size'
+	numFound = 0;
+	k = 0;
+	while (numFound < size) {
+	  if (first.m_items[k] == second.m_items[k]) {
+	    if (first.m_items[k] != -1) 
+	      numFound++;
+	    result.m_items[k] = first.m_items[k];
+	  } else 
+	    break out;
+	  k++;
+	}
+	
+	// Check difference
+	while (k < first.m_items.length) {
+	  if ((first.m_items[k] != -1) && (second.m_items[k] != -1))
+	    break;
+	  else {
+	    if (first.m_items[k] != -1)
+	      result.m_items[k] = first.m_items[k];
+	    else
+	      result.m_items[k] = second.m_items[k];
+	  }
+	  k++;
+	}
+	if (k == first.m_items.length) {
+	  result.m_counter = 0;
+	  newVector.addElement(result);
+	}
+      }
+    }
+    return newVector;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5130 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/Associator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/Associator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/Associator.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Associator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Instances;
+import weka.core.Capabilities;
+
+public interface Associator {
+
+  /**
+   * Generates an associator. Must initialize all fields of the associator
+   * that are not being set via options (ie. multiple calls of buildAssociator
+   * must always lead to the same result). Must not change the dataset
+   * in any way.
+   *
+   * @param data set of instances serving as training data 
+   * @exception Exception if the associator has not been 
+   * generated successfully
+   */
+  void buildAssociations(Instances data) throws Exception;
+
+  /** 
+   * Returns the Capabilities of this associator. Derived associators have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  Capabilities getCapabilities();
+}
Index: branches/MetisMQI/src/main/java/weka/associations/AssociatorEvaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/AssociatorEvaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/AssociatorEvaluation.java	(revision 29)
@@ -0,0 +1,293 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AssociatorEvaluation.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Drawable;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.util.Enumeration;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * Class for evaluating Associaters.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ */
+public class AssociatorEvaluation
+  implements RevisionHandler {
+
+  /** the result string */
+  protected StringBuffer m_Result;
+    
+  /**
+   * default constructor
+   */
+  public AssociatorEvaluation() {
+    super();
+    
+    m_Result = new StringBuffer();
+  }
+  
+  /**
+   * Generates an option string to output on the commandline.
+   * 
+   * @param associator	the associator to generate the string for
+   * @return		the option string
+   */
+  protected static String makeOptionString(Associator associator) {
+    StringBuffer	text;
+    
+    text = new StringBuffer();   
+    
+    // general options
+    text.append("\nGeneral options:\n\n");
+    text.append("-t <training file>\n");
+    text.append("\tThe name of the training file.\n");
+    text.append("-g <name of graph file>\n");
+    text.append("\tOutputs the graph representation (if supported) of the associator to a file.\n");
+    
+    // associator specific options, if any
+    if (associator instanceof OptionHandler) {
+      text.append(
+	  "\nOptions specific to " 
+	  + associator.getClass().getName().replaceAll(".*\\.", "") + ":\n\n");
+      
+      Enumeration enm = ((OptionHandler) associator).listOptions();
+      while (enm.hasMoreElements()) {
+	Option option = (Option) enm.nextElement();
+	text.append(option.synopsis() + "\n");
+	text.append(option.description() + "\n");
+      }
+    }
+    
+    return text.toString();
+  }
+
+  /**
+   * Evaluates an associator with the options given in an array of strings.
+   *
+   * @param associatorString 	class of associator as a string
+   * @param options 		the array of string containing the options
+   * @throws Exception 		if model could not be evaluated successfully
+   * @return 			a string describing the results 
+   */
+  public static String evaluate(String associatorString, String[] options) throws Exception {
+    Associator associator;	 
+
+    // Create associator
+    try {
+      associator = (Associator) Class.forName(associatorString).newInstance();
+    }
+    catch (Exception e) {
+      throw new Exception("Can't find class with name " + associatorString + '.');
+    }
+    
+    return evaluate(associator, options);
+  }
+  
+  /**
+   * Evaluates the associator with the given commandline options and returns
+   * the evaluation string.
+   * 
+   * @param associator	the Associator to evaluate
+   * @param options	the commandline options
+   * @return		the generated output string
+   * @throws Exception	if evaluation fails
+   */
+  public static String evaluate(Associator associator, String[] options) 
+    throws Exception {
+
+    String trainFileString = "";
+    String graphFileName = "";
+    AssociatorEvaluation eval;
+    DataSource loader;
+
+    // help?
+    if (Utils.getFlag('h', options))
+      throw new Exception("\nHelp requested.\n" + makeOptionString(associator));
+    
+    try {
+      // general options
+      trainFileString = Utils.getOption('t', options);
+      if (trainFileString.length() == 0) 
+	throw new Exception("No training file given!");
+      loader = new DataSource(trainFileString);
+
+      graphFileName = Utils.getOption('g', options);
+
+      // associator specific options
+      if (associator instanceof OptionHandler) {
+        ((OptionHandler) associator).setOptions(options);
+      }
+      
+      // left-over options?
+      Utils.checkForRemainingOptions(options);
+    }
+    catch (Exception e) {
+      throw new Exception(
+	  "\nWeka exception: " 
+	  + e.getMessage() + "\n" 
+	  + makeOptionString(associator));
+    }
+    
+    // load file and build associations
+    eval = new AssociatorEvaluation();
+    String results = eval.evaluate(associator, new Instances(loader.getDataSet()));
+
+    // If associator is drawable output string describing graph
+    if ((associator instanceof Drawable) && (graphFileName.length() != 0)) {
+      BufferedWriter writer = new BufferedWriter(new FileWriter(graphFileName));
+      writer.write(((Drawable) associator).graph());
+      writer.newLine();
+      writer.flush();
+      writer.close();
+    }
+
+    return results;
+  }
+  
+  /**
+   * Evaluates the associator with the given commandline options and returns
+   * the evaluation string.
+   * 
+   * @param associator	the Associator to evaluate
+   * @param data	the data to run the associator with
+   * @return		the generated output string
+   * @throws Exception	if evaluation fails
+   */
+  public String evaluate(Associator associator, Instances data) 
+    throws Exception {
+    
+    long startTime;
+    long endTime;
+    
+    // build associations
+    startTime = System.currentTimeMillis();
+    associator.buildAssociations(data);
+    endTime = System.currentTimeMillis();
+
+    m_Result = new StringBuffer(associator.toString());
+    m_Result.append("\n=== Evaluation ===\n\n");
+    m_Result.append("Elapsed time: " + (((double) (endTime - startTime)) / 1000) + "s");
+    m_Result.append("\n");
+    
+    return m_Result.toString();
+  }
+
+  /**
+   * Tests whether the current evaluation object is equal to another
+   * evaluation object
+   *
+   * @param obj the object to compare against
+   * @return true if the two objects are equal
+   */
+  public boolean equals(Object obj) {
+    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
+      return false;
+    
+    AssociatorEvaluation cmp = (AssociatorEvaluation) obj;
+    
+    // TODO: better comparison???
+    String associatingResults1 = m_Result.toString().replaceAll("Elapsed time.*", "");
+    String associatingResults2 = cmp.m_Result.toString().replaceAll("Elapsed time.*", "");
+    if (!associatingResults1.equals(associatingResults2)) 
+      return false;
+    
+    return true;
+  }
+  
+  /**
+   * returns a summary string of the evaluation with a no title
+   * 
+   * @return		the summary string
+   */
+  public String toSummaryString() {
+    return toSummaryString("");
+  }
+  
+  /**
+   * returns a summary string of the evaluation with a default title
+   * 
+   * @param title	the title to print before the result
+   * @return		the summary string
+   */
+  public String toSummaryString(String title) {
+    StringBuffer	result;
+    
+    result = new StringBuffer(title);
+    if (title.length() != 0)
+      result.append("\n");
+    result.append(m_Result);
+    
+    return result.toString();
+  }
+  
+  /**
+   * returns the current result
+   * 
+   * @return		the currently stored result
+   * @see		#toSummaryString()
+   */
+  public String toString() {
+    return toSummaryString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+
+  /**
+   * A test method for this class. Just extracts the first command line
+   * argument as an associator class name and calls evaluate.
+   * 
+   * @param args 	an array of command line arguments, the first of which
+   * 			must be the class name of an associator.
+   */
+  public static void main(String[] args) {
+    try {
+      if (args.length == 0) {
+	throw new Exception(
+	    "The first argument must be the class name of a kernel");
+      }
+      String associator = args[0];
+      args[0] = "";
+      System.out.println(evaluate(associator, args));
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/CARuleMiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/CARuleMiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/CARuleMiner.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CARuleMiner.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+
+/** 
+ * Interface for learning class association rules. All schemes for learning
+ * class association rules implemement this interface.
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 1.3 $ 
+ */
+public interface CARuleMiner extends OptionHandler  {
+    
+    /**
+     * Method for mining class association rules.
+     * Must initialize all fields of the CARuleMiner that are not being set via options (ie. multiple calls of mineCARs
+     * must always lead to the same result). Must not change the dataset in any way.
+     * @param data the insatnces for which class association rules are mined
+     * @throws Exception throws exception if class association rules cannot be mined
+     * @return class association rules and their scoring metric in an FastVector array
+     */    
+ public FastVector[] mineCARs(Instances data) throws Exception;
+
+ /**
+  * Gets the instances without the class attribute
+  * @return the instances withoput the class attribute
+  */ 
+ public Instances getInstancesNoClass();
+ 
+ /**
+  * Gets the class attribute and its values for all instances
+  * @return the class attribute and its values for all instances
+  */ 
+ public Instances getInstancesOnlyClass();
+ 
+ /**
+  * Gets name of the scoring metric used for car mining
+  * @return string containing the name of the scoring metric
+  */ 
+ public String metricString();
+ 
+ /**
+  * Sets the class index for the class association rule miner
+  * @param index the class index
+  */ 
+ public void setClassIndex(int index);
+ 
+}
Index: branches/MetisMQI/src/main/java/weka/associations/CaRuleGeneration.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/CaRuleGeneration.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/CaRuleGeneration.java	(revision 29)
@@ -0,0 +1,257 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CaRuleGeneration.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.UnassignedClassException;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.TreeSet;
+
+
+/**
+ * Class implementing the rule generation procedure of the predictive apriori algorithm for class association rules.
+ *
+ * For association rules in gerneral the method is described in:
+ * T. Scheffer (2001). <i>Finding Association Rules That Trade Support 
+ * Optimally against Confidence</i>. Proc of the 5th European Conf.
+ * on Principles and Practice of Knowledge Discovery in Databases (PKDD'01),
+ * pp. 424-435. Freiburg, Germany: Springer-Verlag. <p>
+ *
+ * The implementation follows the paper expect for adding a rule to the output of the
+ * <i>n</i> best rules. A rule is added if:
+ * the expected predictive accuracy of this rule is among the <i>n</i> best and it is 
+ * not subsumed by a rule with at least the same expected predictive accuracy
+ * (out of an unpublished manuscript from T. Scheffer). 
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $ */
+public class CaRuleGeneration
+  extends RuleGeneration
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3065752149646517703L;
+
+  /**
+   * Constructor
+   * @param itemSet the item set that forms the premise of the rule
+   */
+  public CaRuleGeneration(ItemSet itemSet){
+    super(itemSet);  
+  }
+
+  /**
+   * Generates all rules for an item set. The item set is the premise.
+   * @param numRules the number of association rules the use wants to mine.
+   * This number equals the size <i>n</i> of the list of the
+   * best rules.
+   * @param midPoints the mid points of the intervals
+   * @param priors Hashtable that contains the prior probabilities
+   * @param expectation the minimum value of the expected predictive accuracy
+   * that is needed to get into the list of the best rules
+   * @param instances the instances for which association rules are generated
+   * @param best the list of the <i>n</i> best rules.
+   * The list is implemented as a TreeSet
+   * @param genTime the maximum time of generation
+   * @return all the rules with minimum confidence for the given item set
+   */
+  public TreeSet generateRules(int numRules, double[] midPoints, Hashtable priors, double expectation, Instances instances, TreeSet best, int genTime) {
+
+    boolean redundant = false;
+    FastVector consequences = new FastVector();
+    ItemSet premise;
+    RuleItem current = null, old = null;
+
+    Hashtable hashtable;
+
+    m_change = false;
+    m_midPoints = midPoints;
+    m_priors = priors;
+    m_best = best;
+    m_expectation = expectation;
+    m_count = genTime;
+    m_instances = instances;
+
+    //create rule body
+    premise =null;
+    premise = new ItemSet(m_totalTransactions);
+    int[] premiseItems = new int[m_items.length];
+    System.arraycopy(m_items, 0, premiseItems, 0, m_items.length);
+    premise.setItem(premiseItems);
+    premise.setCounter(m_counter);
+
+    consequences = singleConsequence(instances);
+
+    //create n best rules
+    do{
+      if(premise == null || consequences.size() == 0)
+	return m_best;
+      m_minRuleCount = 1;
+      while(expectation((double)m_minRuleCount,premise.counter(),m_midPoints,m_priors) <= m_expectation){
+	m_minRuleCount++;
+	if(m_minRuleCount > premise.counter())
+	  return m_best;
+      }
+      redundant = false;
+
+      //create possible heads  
+      FastVector allRuleItems = new FastVector();
+      int h = 0;
+      while(h < consequences.size()){
+	RuleItem dummie = new RuleItem();
+	m_count++;
+	current = dummie.generateRuleItem(premise,(ItemSet)consequences.elementAt(h),instances,m_count,m_minRuleCount,m_midPoints,m_priors);
+	if(current != null)
+	  allRuleItems.addElement(current);
+	h++;
+      }
+
+      //update best
+      for(h =0; h< allRuleItems.size();h++){
+	current = (RuleItem)allRuleItems.elementAt(h);
+	if(m_best.size() < numRules){
+	  m_change =true;
+	  redundant = removeRedundant(current);  
+	}
+	else{
+	  m_expectation = ((RuleItem)(m_best.first())).accuracy();
+	  if(current.accuracy() > m_expectation){
+	    boolean remove = m_best.remove(m_best.first());
+	    m_change = true;
+	    redundant = removeRedundant(current);
+	    m_expectation = ((RuleItem)(m_best.first())).accuracy();
+	    while(expectation((double)m_minRuleCount, (current.premise()).counter(),m_midPoints,m_priors) < m_expectation){
+	      m_minRuleCount++;
+	      if(m_minRuleCount > (current.premise()).counter())
+		break;
+	    } 
+	  }  
+	}   
+      }   
+    }while(redundant); 
+    return m_best;
+  }
+
+  /**
+   * Methods that decides whether or not rule a subsumes rule b.
+   * The defintion of subsumption is:
+   * Rule a subsumes rule b, if a subsumes b
+   * AND
+   * a has got least the same expected predictive accuracy as b.
+   * @param a an association rule stored as a RuleItem
+   * @param b an association rule stored as a RuleItem
+   * @return true if rule a subsumes rule b or false otherwise.
+   */  
+  public static boolean aSubsumesB(RuleItem a, RuleItem b){
+
+    if(!a.consequence().equals(b.consequence()))
+      return false;
+    if(a.accuracy() < b.accuracy())
+      return false;
+    for(int k = 0; k < ((a.premise()).items()).length;k++){
+      if((a.premise()).itemAt(k) != (b.premise()).itemAt(k)){
+	if(((a.premise()).itemAt(k) != -1 && (b.premise()).itemAt(k) != -1) || (b.premise()).itemAt(k) == -1)
+	  return false;
+      }
+      /*if(a.m_consequence.m_items[k] != b.m_consequence.m_items[k]){
+            if((a.m_consequence.m_items[k] != -1 && b.m_consequence.m_items[k] != -1) || a.m_consequence.m_items[k] == -1)
+                return false;
+        }*/
+    }
+    return true;
+
+  }
+
+  /**
+   * Converts the header info of the given set of instances into a set 
+   * of item sets (singletons). The ordering of values in the header file 
+   * determines the lexicographic order.
+   *
+   * @param instances the set of instances whose header info is to be used
+   * @return a set of item sets, each containing a single item
+   * @exception Exception if singletons can't be generated successfully
+   */
+  public static FastVector singletons(Instances instances) throws Exception {
+
+    FastVector setOfItemSets = new FastVector();
+    ItemSet current;
+
+    if(instances.classIndex() == -1)
+      throw new UnassignedClassException("Class index is negative (not set)!");
+    Attribute att = instances.classAttribute();
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      if (instances.attribute(i).isNumeric())
+	throw new Exception("Can't handle numeric attributes!");
+      if(i != instances.classIndex()){
+	for (int j = 0; j < instances.attribute(i).numValues(); j++) {
+	  current = new ItemSet(instances.numInstances());
+	  int[] currentItems = new int[instances.numAttributes()];
+	  for (int k = 0; k < instances.numAttributes(); k++)
+	    currentItems[k] = -1;
+	  currentItems[i] = j;
+	  current.setItem(currentItems);
+	  setOfItemSets.addElement(current);
+	}
+      }
+    }
+    return setOfItemSets;
+  }
+
+  /**
+   * generates a consequence of length 1 for a class association rule.
+   * @param instances the instances under consideration
+   * @return FastVector with consequences of length 1
+   */  
+  public static FastVector singleConsequence(Instances instances){
+
+    ItemSet consequence;
+    FastVector consequences = new FastVector();
+
+    for (int j = 0; j < (instances.classAttribute()).numValues(); j++) {
+      consequence = new ItemSet(instances.numInstances());
+      int[] consequenceItems = new int[instances.numAttributes()];
+      consequence.setItem(consequenceItems);
+      for (int k = 0; k < instances.numAttributes(); k++) 
+	consequence.setItemAt(-1,k);
+      consequence.setItemAt(j,instances.classIndex());
+      consequences.addElement(consequence);
+    }
+    return consequences;
+
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/CheckAssociator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/CheckAssociator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/CheckAssociator.java	(revision 29)
@@ -0,0 +1,1542 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckAssociator.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Attribute;
+import weka.core.CheckScheme;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with 
+ * associators. If you implement an associators using the WEKA.libraries,
+ * you should run the checks on it to ensure robustness and correct
+ * operation. Passing all the tests of this object does not mean
+ * bugs in the associators don't exist, but this will help find some
+ * common ones. <p/>
+ * 
+ * Typical usage: <p/>
+ * <code>java weka.associations.CheckAssociator -W associator_name 
+ * -- associator_options </code><p/>
+ * 
+ * CheckAssociator reports on the following:
+ * <ul>
+ *    <li> Associator abilities 
+ *      <ul>
+ *         <li> Possible command line options to the associators </li>
+ *         <li> Whether the associators can predict nominal, numeric, string, 
+ *              date or relational class attributes. </li>
+ *         <li> Whether the associators can handle numeric predictor attributes </li>
+ *         <li> Whether the associators can handle nominal predictor attributes </li>
+ *         <li> Whether the associators can handle string predictor attributes </li>
+ *         <li> Whether the associators can handle date predictor attributes </li>
+ *         <li> Whether the associators can handle relational predictor attributes </li>
+ *         <li> Whether the associators can handle multi-instance data </li>
+ *         <li> Whether the associators can handle missing predictor values </li>
+ *         <li> Whether the associators can handle missing class values </li>
+ *         <li> Whether a nominal associators only handles 2 class problems </li>
+ *         <li> Whether the associators can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning 
+ *      <ul>
+ *         <li> Correct initialisation during buildAssociations (i.e. no result
+ *              changes when buildAssociations called repeatedly) </li>
+ *         <li> Whether the associators alters the data pased to it 
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases 
+ *      <ul>
+ *         <li> building associators with zero training instances </li>
+ *         <li> all but one predictor attribute values missing </li>
+ *         <li> all predictor attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckAssociator with the debug option set will output the 
+ * training dataset for any failed tests.<p/>
+ *
+ * The <code>weka.associations.AbstractAssociatorTest</code> uses this
+ * class to test all the associators. Any changes here, have to be 
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ * 
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 2).</pre>
+ * 
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 1).</pre>
+ * 
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 1).</pre>
+ * 
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 1).</pre>
+ * 
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 1).</pre>
+ * 
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 1).</pre>
+ * 
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ * 
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ * 
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ * 
+ * <pre> -W
+ *  Full name of the associator analysed.
+ *  eg: weka.associations.Apriori
+ *  (default weka.associations.Apriori)</pre>
+ * 
+ * <pre> 
+ * Options specific to associator weka.associations.Apriori:
+ * </pre>
+ * 
+ * <pre> -N &lt;required number of rules output&gt;
+ *  The required number of rules. (default = 10)</pre>
+ * 
+ * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+ *  The metric type by which to rank rules. (default = confidence)</pre>
+ * 
+ * <pre> -C &lt;minimum metric score of a rule&gt;
+ *  The minimum confidence of a rule. (default = 0.9)</pre>
+ * 
+ * <pre> -D &lt;delta for minimum support&gt;
+ *  The delta by which the minimum support is decreased in
+ *  each iteration. (default = 0.05)</pre>
+ * 
+ * <pre> -U &lt;upper bound for minimum support&gt;
+ *  Upper bound for minimum support. (default = 1.0)</pre>
+ * 
+ * <pre> -M &lt;lower bound for minimum support&gt;
+ *  The lower bound for the minimum support. (default = 0.1)</pre>
+ * 
+ * <pre> -S &lt;significance level&gt;
+ *  If used, rules are tested for significance at
+ *  the given level. Slower. (default = no significance testing)</pre>
+ * 
+ * <pre> -I
+ *  If set the itemsets found are also output. (default = no)</pre>
+ * 
+ * <pre> -R
+ *  Remove columns that contain all missing values (default = no)</pre>
+ * 
+ * <pre> -V
+ *  Report progress iteratively. (default = no)</pre>
+ * 
+ * <pre> -A
+ *  If set class association rules are mined. (default = no)</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ *  The class index. (default = last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated associator.<p/>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.7 $
+ * @see TestInstances
+ */
+public class CheckAssociator
+  extends CheckScheme
+  implements RevisionHandler {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+
+  /** a "dummy" class type */
+  public final static int NO_CLASS = -1;
+  
+  /*** The associator to be examined */
+  protected Associator m_Associator = new weka.associations.Apriori();
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    result.addElement(new Option(
+        "\tFull name of the associator analysed.\n"
+        +"\teg: weka.associations.Apriori\n"
+        + "\t(default weka.associations.Apriori)",
+        "W", 1, "-W"));
+    
+    if ((m_Associator != null) 
+        && (m_Associator instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0, 
+          "\nOptions specific to associator "
+          + m_Associator.getClass().getName()
+          + ":"));
+      Enumeration enu = ((OptionHandler)m_Associator).listOptions();
+      while (enu.hasMoreElements())
+        result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   * 
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 2).</pre>
+   * 
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 1).</pre>
+   * 
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 1).</pre>
+   * 
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 1).</pre>
+   * 
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 1).</pre>
+   * 
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 1).</pre>
+   * 
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   * 
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   * 
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   * 
+   * <pre> -W
+   *  Full name of the associator analysed.
+   *  eg: weka.associations.Apriori
+   *  (default weka.associations.Apriori)</pre>
+   * 
+   * <pre> 
+   * Options specific to associator weka.associations.Apriori:
+   * </pre>
+   * 
+   * <pre> -N &lt;required number of rules output&gt;
+   *  The required number of rules. (default = 10)</pre>
+   * 
+   * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+   *  The metric type by which to rank rules. (default = confidence)</pre>
+   * 
+   * <pre> -C &lt;minimum metric score of a rule&gt;
+   *  The minimum confidence of a rule. (default = 0.9)</pre>
+   * 
+   * <pre> -D &lt;delta for minimum support&gt;
+   *  The delta by which the minimum support is decreased in
+   *  each iteration. (default = 0.05)</pre>
+   * 
+   * <pre> -U &lt;upper bound for minimum support&gt;
+   *  Upper bound for minimum support. (default = 1.0)</pre>
+   * 
+   * <pre> -M &lt;lower bound for minimum support&gt;
+   *  The lower bound for the minimum support. (default = 0.1)</pre>
+   * 
+   * <pre> -S &lt;significance level&gt;
+   *  If used, rules are tested for significance at
+   *  the given level. Slower. (default = no significance testing)</pre>
+   * 
+   * <pre> -I
+   *  If set the itemsets found are also output. (default = no)</pre>
+   * 
+   * <pre> -R
+   *  Remove columns that contain all missing values (default = no)</pre>
+   * 
+   * <pre> -V
+   *  Report progress iteratively. (default = no)</pre>
+   * 
+   * <pre> -A
+   *  If set class association rules are mined. (default = no)</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   *  The class index. (default = last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.associations.Apriori.class.getName();
+    setAssociator(
+	(Associator) forName(
+	    "weka.associations", 
+	    Associator.class, 
+	    tmpStr, 
+	    Utils.partitionOptions(options)));
+  }
+  
+  /**
+   * Gets the current settings of the CheckAssociator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getAssociator() != null) {
+      result.add("-W");
+      result.add(getAssociator().getClass().getName());
+    }
+    
+    if ((m_Associator != null) && (m_Associator instanceof OptionHandler))
+      options = ((OptionHandler) m_Associator).getOptions();
+    else
+      options = new String[0];
+    
+    if (options.length > 0) {
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+    
+    if (getAssociator() == null) {
+      println("\n=== No associator set ===");
+      return;
+    }
+    println("\n=== Check on Associator: "
+        + getAssociator().getClass().getName()
+        + " ===\n");
+    
+    // Start tests
+    m_ClasspathProblems = false;
+    println("--> Checking for interfaces");
+    canTakeOptions();
+    boolean weightedInstancesHandler = weightedInstancesHandler()[0];
+    boolean multiInstanceHandler = multiInstanceHandler()[0];
+    println("--> Associator tests");
+    declaresSerialVersionUID();
+    println("--> no class attribute");
+    testsWithoutClass(weightedInstancesHandler, multiInstanceHandler);
+    println("--> with class attribute");
+    testsPerClassType(Attribute.NOMINAL,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.NUMERIC,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.DATE,       weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.STRING,     weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.RELATIONAL, weightedInstancesHandler, multiInstanceHandler);
+  }
+  
+  /**
+   * Set the associator to test. 
+   *
+   * @param newAssociator the Associator to use.
+   */
+  public void setAssociator(Associator newAssociator) {
+    m_Associator = newAssociator;
+  }
+  
+  /**
+   * Get the associator being tested
+   *
+   * @return the associator being tested
+   */
+  public Associator getAssociator() {
+    return m_Associator;
+  }
+  
+  /**
+   * Run a battery of tests for a given class attribute type
+   *
+   * @param classType true if the class attribute should be numeric
+   * @param weighted true if the associator says it handles weights
+   * @param multiInstance true if the associator is a multi-instance associator
+   */
+  protected void testsPerClassType(int classType, 
+                                   boolean weighted,
+                                   boolean multiInstance) {
+    
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance, classType)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance, classType)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance, classType)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance, classType)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance, classType)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      
+      if (classType == Attribute.NOMINAL)
+        canHandleNClasses(PNom, PNum, PStr, PDat, PRel, multiInstance, 4);
+
+      if (!multiInstance) {
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 0);
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 1);
+      }
+      
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          true, false, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, true, false, 100);
+      
+      boolean handleMissingClass = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          false, true, 20)[0];
+      if (handleMissingClass)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, false, true, 100);
+      
+      correctBuildInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, classType,
+          handleMissingPredictors, handleMissingClass);
+    }
+  }
+  
+  /**
+   * Run a battery of tests without a class
+   *
+   * @param weighted true if the associator says it handles weights
+   * @param multiInstance true if the associator is a multi-instance associator
+   */
+  protected void testsWithoutClass(boolean weighted,
+                                   boolean multiInstance) {
+    
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance, NO_CLASS)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance, NO_CLASS)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance, NO_CLASS)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance, NO_CLASS)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance, NO_CLASS)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance, NO_CLASS);
+      
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance, NO_CLASS);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, NO_CLASS, 
+          true, false, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, NO_CLASS, true, false, 100);
+      
+      correctBuildInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance, NO_CLASS);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, NO_CLASS,
+          handleMissingPredictors, false);
+    }
+  }
+  
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the associator can take options
+   */
+  protected boolean[] canTakeOptions() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("options...");
+    if (m_Associator instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler)m_Associator).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n" 
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the associator handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("weighted instances associator...");
+    if (m_Associator instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme handles multi-instance data.
+   * 
+   * @return true if the associator handles multi-instance data
+   */
+  protected boolean[] multiInstanceHandler() {
+    boolean[] result = new boolean[2];
+    
+    print("multi-instance associator...");
+    if (m_Associator instanceof MultiInstanceCapabilitiesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * tests for a serialVersionUID. Fails in case the scheme doesn't declare
+   * a UID.
+   *
+   * @return index 0 is true if the scheme declares a UID
+   */
+  protected boolean[] declaresSerialVersionUID() {
+    boolean[] result = new boolean[2];
+    
+    print("serialVersionUID...");
+    
+    result[0] = !SerializationHelper.needsUID(m_Associator.getClass());
+    
+    if (result[0])
+      println("yes");
+    else
+      println("no");
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic prediction of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canPredict(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("basic predict");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("any");
+    accepts.addElement("unary");
+    accepts.addElement("binary");
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("multi-instance");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether nominal schemes can handle more than two classes.
+   * If a scheme is only designed for two-class problems it should
+   * throw an appropriate exception for multi-class problems.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param numClasses the number of classes to test
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleNClasses(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int numClasses) {
+    
+    print("more than two class problems");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, Attribute.NOMINAL);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("number");
+    accepts.addElement("class");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        Attribute.NOMINAL,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle class attributes as Nth attribute.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class attribute (0-based, -1 means last attribute)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected boolean[] canHandleClassAsNthAttribute(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex) {
+    
+    if (classIndex == TestInstances.CLASS_IS_LAST)
+      print("class attribute as last attribute");
+    else
+      print("class attribute as " + (classIndex + 1) + ". attribute");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    int numTrain = getNumInstances(), numClasses = 2, 
+    missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        classType,
+                        classIndex,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("handle zero training instances");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(
+              nominalPredictor, numericPredictor, stringPredictor, 
+              datePredictor, relationalPredictor, 
+              multiInstance,
+              classType, 
+              missingLevel, predictorMissing, classMissing,
+              numTrain, numClasses, 
+              accepts);
+  }
+  
+  /**
+   * Checks whether the scheme correctly initialises models when 
+   * buildAssociations is called. This test calls buildAssociations with
+   * one training dataset. buildAssociations is then called on a training 
+   * set with different structure, and then again with the original training 
+   * set. If the equals method of the AssociatorEvaluation class returns 
+   * false, this is noted as incorrect build initialisation.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] correctBuildInitialisation(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    boolean[] result = new boolean[2];
+    
+    print("correct initialisation during buildAssociations");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    Instances train1 = null;
+    Instances train2 = null;
+    Associator associator = null;
+    AssociatorEvaluation evaluation1A = null;
+    AssociatorEvaluation evaluation1B = null;
+    AssociatorEvaluation evaluation2 = null;
+    int stage = 0;
+    try {
+      
+      // Make two train sets with different numbers of attributes
+      train1 = makeTestDataset(42, numTrain, 
+                               nominalPredictor    ? getNumNominal()    : 0,
+                               numericPredictor    ? getNumNumeric()    : 0, 
+                               stringPredictor     ? getNumString()     : 0, 
+                               datePredictor       ? getNumDate()       : 0, 
+                               relationalPredictor ? getNumRelational() : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      train2 = makeTestDataset(84, numTrain, 
+                               nominalPredictor    ? getNumNominal() + 1    : 0,
+                               numericPredictor    ? getNumNumeric() + 1    : 0, 
+                               stringPredictor     ? getNumString() + 1     : 0, 
+                               datePredictor       ? getNumDate() + 1       : 0, 
+                               relationalPredictor ? getNumRelational() + 1 : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, predictorMissing, classMissing);
+        addMissing(train2, missingLevel, predictorMissing, classMissing);
+      }
+      
+      associator = AbstractAssociator.makeCopies(getAssociator(), 1)[0];
+      evaluation1A = new AssociatorEvaluation();
+      evaluation1B = new AssociatorEvaluation();
+      evaluation2 = new AssociatorEvaluation();
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      stage = 0;
+      evaluation1A.evaluate(associator, train1);
+      
+      stage = 1;
+      evaluation2.evaluate(associator, train2);
+      
+      stage = 2;
+      evaluation1B.evaluate(associator, train1);
+      
+      stage = 3;
+      if (!evaluation1A.equals(evaluation1B)) {
+        if (m_Debug) {
+          println("\n=== Full report ===\n"
+              + evaluation1A.toSummaryString("\nFirst buildAssociations()")
+                  + "\n\n");
+          println(
+              evaluation1B.toSummaryString("\nSecond buildAssociations()")
+                  + "\n\n");
+        }
+        throw new Exception("Results differ between buildAssociations calls");
+      }
+      println("yes");
+      result[0] = true;
+      
+      if (false && m_Debug) {
+        println("\n=== Full report ===\n"
+            + evaluation1A.toSummaryString("\nFirst buildAssociations()")
+                + "\n\n");
+        println(
+            evaluation1B.toSummaryString("\nSecond buildAssociations()")
+                + "\n\n");
+      }
+    } 
+    catch (Exception ex) {
+      println("no");
+      result[0] = false;
+        
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;	  
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing,
+      int missingLevel) {
+    
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (predictorMissing) {
+      print(" predictor");
+      if (classMissing)
+        print(" and");
+    }
+    if (classMissing)
+      print(" class");
+    print(" values");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    int numTrain = getNumInstances(), numClasses = 2;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether the associator can handle instance weights.
+   * This test compares the associator performance on two datasets
+   * that are identical except for the training weights. If the 
+   * results change, then the associator must be using the weights. It
+   * may be possible to get a false positive from this test if the 
+   * weight changes aren't significant enough to induce a change
+   * in associator performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("associator uses instance weights");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = 2*getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Associator [] associators = null;
+    AssociatorEvaluation evaluationB = null;
+    AssociatorEvaluation evaluationI = null;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0, 
+                              stringPredictor     ? getNumString()      : 0, 
+                              datePredictor       ? getNumDate()        : 0, 
+                              relationalPredictor ? getNumRelational()  : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      associators = AbstractAssociator.makeCopies(getAssociator(), 2);
+      evaluationB = new AssociatorEvaluation();
+      evaluationI = new AssociatorEvaluation();
+      evaluationB.evaluate(associators[0], train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      
+      // Now modify instance weights and re-built/test
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      evaluationI.evaluate(associators[1], train);
+      if (evaluationB.equals(evaluationI)) {
+        //	println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println(evaluationB.toSummaryString("\nboth methods\n"));
+        } else {
+          print("Problem during building");
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1) 
+              + "    " + train.instance(i).weight());
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * building. If the scheme needs to modify the data it should take 
+   * a copy of the training data. Currently checks for changes to header 
+   * structure, number of instances, order of instances, instance weights.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if we know the associator can handle
+   * (at least) moderate missing predictor values
+   * @param classMissing true if we know the associator can handle
+   * (at least) moderate missing class values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing) {
+    
+    print("associator doesn't alter original datasets");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 20;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Associator associator = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0, 
+                              datePredictor       ? getNumDate()       : 0, 
+                              relationalPredictor ? getNumRelational() : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      associator = AbstractAssociator.makeCopies(getAssociator(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      Instances trainCopy = new Instances(train);
+      associator.buildAssociations(trainCopy);
+      compareDatasets(train, trainCopy);
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        println(": " + ex.getMessage() + "\n");
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    return runBasicTest(
+		nominalPredictor, 
+		numericPredictor,
+		stringPredictor,
+		datePredictor,
+		relationalPredictor,
+		multiInstance,
+		classType, 
+		TestInstances.CLASS_IS_LAST,
+		missingLevel,
+		predictorMissing,
+		classMissing,
+		numTrain,
+		numClasses,
+		accepts);
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the attribute index of the class
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Associator associator = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor     ? getNumNominal()    : 0,
+                              numericPredictor     ? getNumNumeric()    : 0, 
+                              stringPredictor      ? getNumString()     : 0,
+                              datePredictor        ? getNumDate()       : 0,
+                              relationalPredictor  ? getNumRelational() : 0,
+                              numClasses, 
+                              classType,
+                              classIndex,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      associator = AbstractAssociator.makeCopies(getAssociator(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      associator.buildAssociations(train);
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+	msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+	m_ClasspathProblems = true;
+      
+      for (int i = 0; i < accepts.size(); i++) {
+	if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+	  acceptable = true;
+	}
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here is the dataset:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Make a simple set of instances, which can later be modified
+   * for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      boolean multiInstance)
+    throws Exception {
+    
+    return makeTestDataset(
+		seed, 
+		numInstances,
+		numNominal,
+		numNumeric,
+		numString,
+		numDate, 
+		numRelational,
+		numClasses, 
+		classType,
+		TestInstances.CLASS_IS_LAST,
+		multiInstance);
+  }
+  
+  /**
+   * Make a simple set of instances with variable position of the class 
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class (0-based, -1 as last)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      int classIndex,
+                                      boolean multiInstance)
+  throws Exception {
+    
+    TestInstances dataset = new TestInstances();
+    
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal(numNominal);
+    dataset.setNumNumeric(numNumeric);
+    dataset.setNumString(numString);
+    dataset.setNumDate(numDate);
+    dataset.setNumRelational(numRelational);
+    dataset.setNumClasses(numClasses);
+    if (classType == NO_CLASS) {
+      dataset.setClassType(Attribute.NOMINAL);  // ignored
+      dataset.setClassIndex(TestInstances.NO_CLASS);
+    }
+    else {
+      dataset.setClassType(classType);
+      dataset.setClassIndex(classIndex);
+    }
+    dataset.setNumClasses(numClasses);
+    dataset.setMultiInstance(multiInstance);
+    dataset.setWords(getWords());
+    dataset.setWordSeparators(getWordSeparators());
+    
+    return process(dataset.generate());
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param nominalPredictor true if nominal predictor attributes are present
+   * @param numericPredictor true if numeric predictor attributes are present
+   * @param stringPredictor true if string predictor attributes are present
+   * @param datePredictor true if date predictor attributes are present
+   * @param relationalPredictor true if relational predictor attributes are present
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(boolean nominalPredictor, 
+                                       boolean numericPredictor, 
+                                       boolean stringPredictor, 
+                                       boolean datePredictor, 
+                                       boolean relationalPredictor, 
+                                       boolean multiInstance,
+                                       int classType) {
+    
+    String str = "";
+
+    if (numericPredictor)
+      str += " numeric";
+    
+    if (nominalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " nominal";
+    }
+    
+    if (stringPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " string";
+    }
+    
+    if (datePredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " date";
+    }
+    
+    if (relationalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " relational";
+    }
+    
+    str += " predictors)";
+    
+    switch (classType) {
+      case Attribute.NUMERIC:
+        str = " (numeric class," + str;
+        break;
+      case Attribute.NOMINAL:
+        str = " (nominal class," + str;
+        break;
+      case Attribute.STRING:
+        str = " (string class," + str;
+        break;
+      case Attribute.DATE:
+        str = " (date class," + str;
+        break;
+      case Attribute.RELATIONAL:
+        str = " (relational class," + str;
+        break;
+      case NO_CLASS:
+        str = " (no class," + str;
+        break;
+    }
+    
+    print(str);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+  
+  /**
+   * Test method for this class
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    runCheck(new CheckAssociator(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/FPGrowth.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/FPGrowth.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/FPGrowth.java	(revision 29)
@@ -0,0 +1,2243 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FPGrowth.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SparseInstance;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the FP-growth algorithm for finding large item sets without candidate generation. Iteratively reduces the minimum support until it finds the required number of rules with the given minimum metric. For more information see:<br/>
+ * <br/>
+ * J. Han, J.Pei, Y. Yin: Mining frequent patterns without candidate generation. In: Proceedings of the 2000 ACM-SIGMID International Conference on Management of Data, 1-12, 2000.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Han2000,
+ *    author = {J. Han and J.Pei and Y. Yin},
+ *    booktitle = {Proceedings of the 2000 ACM-SIGMID International Conference on Management of Data},
+ *    pages = {1-12},
+ *    title = {Mining frequent patterns without candidate generation},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;attribute index of positive value&gt;
+ *  Set the index of the attribute value to consider as 'positive'
+ *  for binary attributes in normal dense instances. Index 2 is always
+ *  used for sparse instances. (default = 2)</pre>
+ * 
+ * <pre> -I &lt;max items&gt;
+ *  The maximum number of items to include in large items sets (and rules). (default = -1, i.e. no limit.)</pre>
+ * 
+ * <pre> -N &lt;require number of rules&gt;
+ *  The required number of rules. (default = 10)</pre>
+ * 
+ * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+ *  The metric by which to rank rules. (default = confidence)</pre>
+ * 
+ * <pre> -C &lt;minimum metric score of a rule&gt;
+ *  The minimum metric score of a rule. (default = 0.9)</pre>
+ * 
+ * <pre> -U &lt;upper bound for minimum support&gt;
+ *  Upper bound for minimum support. (default = 1.0)</pre>
+ * 
+ * <pre> -M &lt;lower bound for minimum support&gt;
+ *  The lower bound for the minimum support. (default = 0.1)</pre>
+ * 
+ * <pre> -D &lt;delta for minimum support&gt;
+ *  The delta by which the minimum support is decreased in
+ *  each iteration. (default = 0.05)</pre>
+ * 
+ * <pre> -S
+ *  Find all rules that meet the lower bound on
+ *  minimum support and the minimum metric constraint.
+ *  Turning this mode on will disable the iterative support reduction
+ *  procedure to find the specified number of rules.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6158 $
+ */
+public class FPGrowth extends AbstractAssociator 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** For serialization */
+  private static final long serialVersionUID = 3620717108603442911L;
+
+  /**
+   * Inner class that handles a single binary item
+   */
+  public static class BinaryItem implements Serializable, Comparable<BinaryItem> {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -3372941834914147669L;
+    
+    /** The frequency of the item */
+    protected int m_frequency;
+    
+    /** The attribute that the item corresponds to */
+    protected Attribute m_attribute;
+    
+    /** The index of the value considered to be positive */
+    protected int m_valueIndex;
+    
+    public BinaryItem(Attribute att, int valueIndex) throws Exception {
+      if (att.isNumeric() || (att.isNominal() && att.numValues() > 2)) {
+        throw new Exception("BinaryItem must be constructed using a nominal attribute" +
+        		" with at most 2 values!");
+      }
+      m_attribute = att;
+      if (m_attribute.numValues() == 1) {
+        m_valueIndex = 0; // unary attribute (? used to indicate absence from a basket)
+      } else {
+        m_valueIndex = valueIndex;
+      }
+    }
+    
+    /**
+     * Increase the frequency of this item.
+     * 
+     * @param f the amount to increase the frequency by.
+     */
+    public void increaseFrequency(int f) {
+      m_frequency += f;
+    }
+    
+    /**
+     * Decrease the frequency of this item.
+     * 
+     * @param f the amount by which to decrease the frequency.
+     */
+    public void decreaseFrequency(int f) {
+      m_frequency -= f;
+    }
+    
+    /**
+     * Increment the frequency of this item.
+     */
+    public void increaseFrequency() {
+      m_frequency++;
+    }
+    
+    /**
+     * Decrement the frequency of this item.
+     */
+    public void decreaseFrequency() {
+      m_frequency--;
+    }
+    
+    /**
+     * Get the frequency of this item.
+     * 
+     * @return the frequency.
+     */
+    public int getFrequency() {
+      return m_frequency;
+    }
+    
+    /**
+     * Get the attribute that this item corresponds to.
+     * 
+     * @return the corresponding attribute.
+     */
+    public Attribute getAttribute() {
+      return m_attribute;
+    }
+    
+    /**
+     * Get the value index for this item.
+     * 
+     * @return the value index.
+     */
+    public int getValueIndex() {
+      return m_valueIndex;
+    }
+    
+    /**
+     * A string representation of this item.
+     * 
+     * @return a string representation of this item.
+     */
+    public String toString() {
+      return toString(false);
+    }
+    
+    /**
+     * A string representation of this item.
+     * 
+     * @param freq true if the frequency should be included.
+     * @return a string representation of this item. 
+     */
+    public String toString(boolean freq) {
+      String result = m_attribute.name() + "=" + m_attribute.value(m_valueIndex);
+      if (freq) {
+        result += ":" + m_frequency;
+      }
+      return result;
+    }
+    
+    /**
+     * Ensures that items will be sorted in descending order of frequency.
+     * Ties are ordered by attribute name.
+     * 
+     * @param comp the BinaryItem to compare against.
+     */
+    public int compareTo(BinaryItem comp) {
+      if (m_frequency == comp.getFrequency()) {
+        // sort by name
+        return -1 * m_attribute.name().compareTo(comp.getAttribute().name());
+      }
+      if (comp.getFrequency() < m_frequency) {
+        return -1;
+      }
+      return 1;
+    }
+    
+    public boolean equals(Object compareTo) {
+      if (!(compareTo instanceof BinaryItem)) {
+        return false;
+      }
+      
+      BinaryItem b = (BinaryItem)compareTo;
+      if (m_attribute.equals(b.getAttribute()) && m_frequency == b.getFrequency()) {
+        return true;
+      }
+      
+      return false;
+    }
+    
+    public int hashCode() {
+      return (m_attribute.name().hashCode() ^ 
+          m_attribute.numValues()) * m_frequency;
+    }
+  }
+  
+  /**
+   * Class for maintaining a frequent item set.
+   */
+  protected static class FrequentBinaryItemSet 
+    implements Serializable, Cloneable {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -6543815873565829448L;
+
+    /** The list of items in the item set */
+    protected ArrayList<BinaryItem> m_items = new ArrayList<BinaryItem>();
+    
+    /** the support of this item set **/
+    protected int m_support;
+    
+    /**
+     * Constructor
+     * 
+     * @param items the items that make up the frequent item set.
+     * @param support the support of this item set.
+     */
+    public FrequentBinaryItemSet(ArrayList<BinaryItem> items, int support) {
+      m_items = items;
+      m_support = support;
+      Collections.sort(m_items);
+    }
+    
+    /**
+     * Add an item to this item set.
+     * 
+     * @param i the item to add.
+     */
+    public void addItem(BinaryItem i) {
+      m_items.add(i);
+      Collections.sort(m_items);
+    }
+    
+    /**
+     * Set the support for this item set.
+     * 
+     * @param support the support for this item set.
+     */
+    public void setSupport(int support) {
+      m_support = support;
+    }
+    
+    /**
+     * Get the support of this item set.
+     * 
+     * @return the support of this item set.
+     */
+    public int getSupport() {
+      return m_support;
+    }
+    
+    /**
+     * Get the items in this item set.
+     * 
+     * @return the items in this item set.
+     */
+    public Collection<BinaryItem> getItems() {
+      return m_items;
+    }
+    
+    /**
+     * Get a particular item from this item set.
+     * 
+     * @param index the index of the item to get.
+     * @return the item.
+     */
+    public BinaryItem getItem(int index) {
+      return m_items.get(index);
+    }
+    
+    /**
+     * Get the number of items in this item set.
+     * 
+     * @return the number of items in this item set.
+     */
+    public int numberOfItems() {
+      return m_items.size();
+    }
+    
+    /**
+     * Get a textual description of this item set.
+     * 
+     * @return a textual description of this item set.
+     */
+    public String toString() {
+      StringBuffer buff = new StringBuffer();
+      Iterator<BinaryItem> i = m_items.iterator();
+      
+      while (i.hasNext()) {
+        buff.append(i.next().toString() + " ");        
+      }
+      buff.append(": " + m_support);
+      return buff.toString();
+    }
+    
+    /**
+     * Make a copy of this item set.
+     * 
+     * @return a copy of this item set.
+     */
+    public Object clone() {
+      ArrayList<BinaryItem> items = new ArrayList<BinaryItem>(m_items);
+      return new FrequentBinaryItemSet(items, m_support);
+    }
+  }
+  
+  /**
+   * Maintains a list of frequent item sets.
+   */
+  protected static class FrequentItemSets implements Serializable {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 4173606872363973588L;
+
+    /** The list of frequent item sets */
+    protected ArrayList<FrequentBinaryItemSet> m_sets = 
+      new ArrayList<FrequentBinaryItemSet>();
+    
+    /** The total number of transactions in the data */
+    protected int m_numberOfTransactions;
+    
+    /**
+     * Constructor.
+     * 
+     * @param numTransactions the total number of transactions in the data.
+     */
+    public FrequentItemSets(int numTransactions) {
+      m_numberOfTransactions = numTransactions;
+    }
+    
+    /**
+     * Get an item set.
+     * 
+     * @param index the index of the item set to get.
+     * @return an item set.
+     */
+    public FrequentBinaryItemSet getItemSet(int index) {
+      return m_sets.get(index);
+    }
+    
+    /**
+     * Get an iterator that can be used to access all the item sets.
+     * 
+     * @return an iterator.
+     */
+    public Iterator<FrequentBinaryItemSet> iterator() {
+      return m_sets.iterator();
+    }
+    
+    /**
+     * Get the total number of transactions in the data that the item
+     * sets were derived from.
+     * 
+     * @return the total number of transactions in the data.
+     */
+    public int getNumberOfTransactions() {
+      return m_numberOfTransactions;
+    }
+    
+    /**
+     * Add an item set.
+     * 
+     * @param setToAdd the item set to add.
+     */
+    public void addItemSet(FrequentBinaryItemSet setToAdd) {
+      m_sets.add(setToAdd);
+    }
+    
+    /**
+     * Sort the item sets according to the supplied comparator.
+     * 
+     * @param comp the comparator to use.
+     */
+    public void sort(Comparator<FrequentBinaryItemSet> comp) {
+      Collections.sort(m_sets, comp);
+    }
+    
+    /**
+     * Get the number of item sets.
+     * 
+     * @return the number of item sets.
+     */
+    public int size() {
+      return m_sets.size();
+    }
+    
+    /**
+     * Sort the item sets. Sorts by item set length. Ties are broken by comparing
+     * the items in the two item sets.
+     */
+    public void sort() {
+      Comparator<FrequentBinaryItemSet> compF = new Comparator<FrequentBinaryItemSet>() {
+        public int compare(FrequentBinaryItemSet one, FrequentBinaryItemSet two) {
+          Collection<BinaryItem> compOne = one.getItems();
+          Collection<BinaryItem> compTwo = two.getItems();
+          
+//          if (one.getSupport() == two.getSupport()) {
+            // if supports are equal then list shorter item sets before longer ones
+            if (compOne.size() < compTwo.size()) {
+              return -1;
+            } else if (compOne.size() > compTwo.size()) {
+              return 1;
+            } else {
+              // compare items
+              Iterator<BinaryItem> twoIterator = compTwo.iterator();
+              for (BinaryItem oneI : compOne) {
+                BinaryItem twoI = twoIterator.next();
+                int result = oneI.compareTo(twoI);
+                if (result != 0) {
+                  return result;
+                }
+              }
+              return 0; // equal
+            }
+            
+//            return 0;
+    /*      } else if (one.getSupport() > two.getSupport()) {
+            // reverse ordering (i.e. descending by support)
+            return -1;
+          } */
+          
+    //      return 1;
+        }
+      };
+      
+      sort(compF);
+    }
+    
+    /**
+     * Get a textual description of this list of item sets.
+     * 
+     * @param numSets the number of item sets to display.
+     * @return a textual description of the item sets.
+     */
+    public String toString(int numSets) {
+      if (m_sets.size() == 0) {
+        return "No frequent items sets found!";
+      }
+      
+      StringBuffer result = new StringBuffer();
+      result.append("" + m_sets.size() + " frequent item sets found");
+      if (numSets > 0) {
+        result.append(" , displaying " + numSets);
+      }
+      result.append(":\n\n");
+      
+      int count = 0;
+      for (FrequentBinaryItemSet i : m_sets) {
+        if (numSets > 0 && count > numSets) {
+          break;
+        }
+        result.append(i.toString() + "\n");
+        count++;
+      }
+      
+      return result.toString();
+    }
+  }
+  
+  /**
+   * This class holds the counts for projected tree nodes
+   * and header lists.
+   */
+  protected static class ShadowCounts implements Serializable {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 4435433714185969155L;
+    
+    /** Holds the counts at different recursion levels */
+    private ArrayList<Integer> m_counts = new ArrayList<Integer>();
+    
+    /**
+     * Get the count at the specified recursion depth.
+     * 
+     * @param recursionLevel the depth of the recursion.
+     * @return the count.
+     */
+    public int getCount(int recursionLevel) {
+      if (recursionLevel >= m_counts.size()) {
+        return 0;
+      } else {
+        return m_counts.get(recursionLevel);
+      }
+    }
+    
+    /**
+     * Increase the count at a given recursion level.
+     * 
+     * @param recursionLevel the level at which to increase the count.
+     * @param incr the amount by which to increase the count.
+     */
+    public void increaseCount(int recursionLevel, int incr) {
+      // basically treat the list like a stack where we
+      // can add a new element, or increment the element
+      // at the top
+      
+      if (recursionLevel == m_counts.size()) {
+        // new element
+        m_counts.add(incr);
+      } else if (recursionLevel == m_counts.size() - 1) {
+        // otherwise increment the top
+        int n = m_counts.get(recursionLevel).intValue();
+        m_counts.set(recursionLevel, (n + incr));
+      }
+    }
+    
+    /**
+     * Remove the count at the given recursion level.
+     * 
+     * @param recursionLevel the level at which to remove the count.
+     */
+    public void removeCount(int recursionLevel) {
+      if (recursionLevel < m_counts.size()) {
+        m_counts.remove(recursionLevel);
+      }
+    }
+  }
+  
+  /**
+   * A node in the FP-tree.
+   */
+  protected static class FPTreeNode implements Serializable {
+                
+    /** For serialization */
+    private static final long serialVersionUID = 4396315323673737660L;
+
+    /** link to another sibling at this level in the tree */
+    protected FPTreeNode m_levelSibling;
+    
+    /** link to the parent node */
+    protected FPTreeNode m_parent;
+    
+    /** item at this node */
+    protected BinaryItem m_item;
+    
+    /** ID (for graphing the tree) */
+    protected int m_ID;
+    
+    /** the children of this node */
+    protected Map<BinaryItem, FPTreeNode> m_children = 
+      new HashMap<BinaryItem, FPTreeNode>();
+    
+    /** counts associated with projected versions of this node */
+    protected ShadowCounts m_projectedCounts = new ShadowCounts();
+    
+    /**
+     * Construct a new node with the given parent link and item.
+     * 
+     * @param parent a pointer to the parent of this node.
+     * @param item the item at this node.
+     */
+    public FPTreeNode(FPTreeNode parent, BinaryItem item) {
+      m_parent = parent;
+      m_item = item;
+    }
+    
+    /**
+     * Insert an item set into the tree at this node. Removes the first item
+     * from the supplied item set and makes a recursive call to insert the
+     * remaining items.
+     * 
+     * @param itemSet the item set to insert.
+     * @param headerTable the header table for the tree.
+     * @param incr the amount by which to increase counts.
+     */
+    public void addItemSet(Collection<BinaryItem> itemSet, 
+        Map<BinaryItem, FPTreeRoot.Header> headerTable, int incr) {
+     
+      Iterator<BinaryItem> i = itemSet.iterator();
+      
+      if (i.hasNext()) {
+        BinaryItem first = i.next();
+        
+        FPTreeNode aChild;
+        if (!m_children.containsKey(first)) {
+          // not in the tree, so add it.
+          aChild = new FPTreeNode(this, first);
+          m_children.put(first, aChild);
+          
+          // update the header
+          if (!headerTable.containsKey(first)) {
+            headerTable.put(first, new FPTreeRoot.Header());
+          }
+          
+          // append new node to header list
+          headerTable.get(first).addToList(aChild);
+        } else {
+          // get the appropriate child node
+          aChild = m_children.get(first);
+        }
+        
+        // update counts in header table
+        headerTable.get(first).getProjectedCounts().increaseCount(0, incr);
+        
+        // increase the child's count
+        aChild.increaseProjectedCount(0, incr);
+        
+        // proceed recursively
+        itemSet.remove(first);        
+        aChild.addItemSet(itemSet, headerTable, incr);
+      }
+    }
+    
+    /**
+     * Increase the projected count at the given recursion level at this
+     * node
+     * 
+     * @param recursionLevel the recursion level to increase the node count
+     * at.
+     * @param incr the amount by which to increase the count.
+     */
+    public void increaseProjectedCount(int recursionLevel, int incr) {
+      m_projectedCounts.increaseCount(recursionLevel, incr);
+    }
+    
+    /**
+     * Remove the projected count at the given recursion level for this
+     * node.
+     * 
+     * @param recursionLevel the recursion level at which to remove the count.
+     */
+    public void removeProjectedCount(int recursionLevel) {
+      m_projectedCounts.removeCount(recursionLevel);
+    }
+    
+    /**
+     * Get the projected count at the given recursion level for this node.
+     * 
+     * @param recursionLevel the recursion level at which to get the count.
+     * @return the count.
+     */
+    public int getProjectedCount(int recursionLevel) {
+      return m_projectedCounts.getCount(recursionLevel);
+    }
+    
+    /**
+     * Get the parent node.
+     * 
+     * @return the parent node.
+     */
+    public FPTreeNode getParent() {
+      return m_parent;
+    }
+    
+    /**
+     * Get the item at this node.
+     * 
+     * @return the item at this node.
+     */
+    public BinaryItem getItem() {
+      return m_item;
+    }    
+    
+    /**
+     * Return a textual description of this node for a given recursion
+     * level.
+     * 
+     * @param recursionLevel the recursion depth to use.
+     * @return a textual description of this node.
+     */
+    public String toString(int recursionLevel) {
+      return toString("", recursionLevel);
+    }
+
+    /**
+     * Return a textual description of this node for a given recursion
+     * level.
+     * 
+     * @param prefix a prefix string to prepend.
+     * @param recursionLevel the recursion level to use.
+     * @return a textual description of this node. 
+     */
+    public String toString(String prefix, int recursionLevel) {
+      StringBuffer buffer = new StringBuffer();
+      buffer.append(prefix);
+      buffer.append("|  ");
+      buffer.append(m_item.toString());
+      buffer.append(" (");
+      buffer.append(m_projectedCounts.getCount(recursionLevel));
+      buffer.append(")\n");
+      
+      for (FPTreeNode node : m_children.values()) {
+        buffer.append(node.toString(prefix + "|  ", recursionLevel));
+      }
+      return buffer.toString();
+    }
+    
+    protected int assignIDs(int lastID) {
+      int currentLastID = lastID + 1;
+      m_ID = currentLastID;
+      if (m_children != null) {
+        Collection<FPTreeNode> kids = m_children.values();
+        for (FPTreeNode n : kids) {
+          currentLastID = n.assignIDs(currentLastID);
+        }
+      }
+      return currentLastID;
+    }
+    
+    /**
+     * Generate a dot graph description string for the tree.
+     * 
+     * @param text a StringBuffer to store the graph description
+     * in.
+     */
+    public void graphFPTree(StringBuffer text) {
+      if (m_children != null) {
+        Collection<FPTreeNode> kids = m_children.values();
+        for (FPTreeNode n : kids) {
+          text.append("N" + n.m_ID);
+          text.append(" [label=\"");
+          text.append(n.getItem().toString() + " (" + n.getProjectedCount(0) + ")\\n");
+          text.append("\"]\n");
+          n.graphFPTree(text);
+          text.append("N" + m_ID + "->" + "N" + n.m_ID + "\n");
+        }
+      }
+    }
+  }
+  
+  /**
+   * Root of the FPTree
+   */
+  private static class FPTreeRoot extends FPTreeNode {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 632150939785333297L;
+
+    /**
+     * Stores a header entry for an FPTree 
+     */
+    protected static class Header implements Serializable {
+      
+      /** For serialization */
+      private static final long serialVersionUID = -6583156284891368909L;
+      
+      /** The list of pointers into the tree structure */
+      protected List<FPTreeNode> m_headerList = new LinkedList<FPTreeNode>();
+      
+      /** Projected header counts for this entry */
+      protected ShadowCounts m_projectedHeaderCounts = new ShadowCounts();
+      
+      /**
+       * Add a tree node into the list for this header entry.
+       * 
+       * @param toAdd the node to add.
+       */
+      public void addToList(FPTreeNode toAdd) {
+        m_headerList.add(toAdd);
+      }
+      
+      /**
+       * Get the list of nodes for this header entry.
+       * 
+       * @return the list of nodes for this header entry.
+       */
+      public List<FPTreeNode> getHeaderList() {
+        return m_headerList;
+      }
+      
+      /**
+       * Get the projected counts for this header entry.
+       * 
+       * @return the projected counts for this header entry.
+       */
+      public ShadowCounts getProjectedCounts() {
+        return m_projectedHeaderCounts;
+      }
+    }
+    
+    /** Stores the header table as mapped Header entries */
+    protected Map<BinaryItem, Header> m_headerTable = 
+      new HashMap<BinaryItem, Header>();
+    
+    /**
+     * Create a new FPTreeRoot.
+     */
+    public FPTreeRoot() {
+      super(null, null);
+    }
+    
+    /**
+     * Insert an item set into the tree.
+     * 
+     * @param itemSet the item set to insert into the tree.
+     * @param incr the increment by which to increase counters.
+     */
+    public void addItemSet(Collection<BinaryItem> itemSet, int incr) {
+      super.addItemSet(itemSet, m_headerTable, incr);
+    }
+    
+    /**
+     * Get the header table for this tree.
+     * 
+     * @return the header table for this tree.
+     */
+    public Map<BinaryItem, Header> getHeaderTable() {
+      return m_headerTable;
+    }
+    
+    public boolean isEmpty(int recursionLevel) {
+      for (FPTreeNode c : m_children.values()) {
+        if (c.getProjectedCount(recursionLevel) > 0) {
+          return false;
+        }
+      }
+      return true;
+    }
+    
+    /**
+     * Get a textual description of the tree at a given recursion
+     * (projection) level.
+     * 
+     * @param pad the string to use as a prefix for indenting nodes.
+     * @param recursionLevel the recursion level (projection) to use.
+     * @return the textual description of the tree.
+     */
+    public String toString(String pad, int recursionLevel) {
+      StringBuffer result = new StringBuffer();
+      result.append(pad);
+      result.append("+ ROOT\n");
+
+      for (FPTreeNode node : m_children.values()) {
+        result.append(node.toString(pad + "|  ", recursionLevel));
+      }
+      return result.toString();
+    }
+
+    /**
+     * Get a textual description of the header table for this tree.
+     * 
+     * @param recursionLevel the recursion level to use.
+     * @return a textual description of the header table for this
+     * tree at a given recursion level.
+     */
+    public String printHeaderTable(int recursionLevel) {
+      StringBuffer buffer = new StringBuffer();
+      for (BinaryItem item : m_headerTable.keySet()) {
+        buffer.append(item.toString());
+        buffer.append(" : ");
+        buffer.append(m_headerTable.get(item).getProjectedCounts().getCount(recursionLevel));
+        buffer.append("\n");
+      }
+      return buffer.toString();
+    }
+    
+    public void graphHeaderTable(StringBuffer text, int maxID) {
+
+      for (BinaryItem item : m_headerTable.keySet()) {
+        Header h = m_headerTable.get(item);
+        List<FPTreeNode> headerList = h.getHeaderList();
+        if (headerList.size() > 1) {
+          text.append("N" + maxID + " [label=\"" + headerList.get(0).getItem().toString() 
+              + " (" + h.getProjectedCounts().getCount(0) + ")"
+              + "\" shape=plaintext]\n");
+
+          text.append("N" + maxID + "->" + "N" + headerList.get(1).m_ID + "\n");
+          for (int i = 1; i < headerList.size() - 1; i++) {
+            text.append("N" + headerList.get(i).m_ID + "->" + "N" + headerList.get(i+1).m_ID + "\n");
+          }
+          maxID++;
+        }
+      }
+    }
+  }
+  
+  /**
+   * Class for storing and manipulating an association rule. Also has a utility
+   * routine for generating (by brute force) all the association rules that meet
+   * a given metric threshold from a list of large item sets.
+   * 
+   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com).
+   */
+  /**
+   * @author mhall
+   *
+   */
+  public static class AssociationRule implements Serializable, Comparable<AssociationRule> {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -661269018702294489L;
+
+    /** Enum for holding different metric types */
+    public static enum METRIC_TYPE {
+      CONFIDENCE("conf") {
+        double compute(int premiseSupport, int consequenceSupport, 
+            int totalSupport, int totalTransactions) {
+          
+          return (double)totalSupport / (double)premiseSupport;
+        }
+      },
+      LIFT("lift") {
+        double compute(int premiseSupport, int consequenceSupport, 
+            int totalSupport, int totalTransactions) {
+          
+          double confidence = 
+            METRIC_TYPE.CONFIDENCE.compute(premiseSupport, consequenceSupport, 
+                totalSupport, totalTransactions);
+          return confidence / ((double)consequenceSupport /
+              (double)totalTransactions);
+        }
+      },
+      LEVERAGE("lev") {
+        double compute(int premiseSupport, int consequenceSupport, 
+            int totalSupport, int totalTransactions) {
+          
+          double coverageForItemSet = (double)totalSupport /
+            (double)totalTransactions;
+          double expectedCoverageIfIndependent = 
+            ((double)premiseSupport / (double)totalTransactions) *
+            ((double)consequenceSupport / (double)totalTransactions);
+          return coverageForItemSet - expectedCoverageIfIndependent;
+        }
+      },
+      CONVICTION("conv") {
+        double compute(int premiseSupport, int consequenceSupport, 
+            int totalSupport, int totalTransactions) {
+          
+          double num = 
+            (double)premiseSupport * (double)(totalTransactions - consequenceSupport) /
+            (double)totalTransactions;
+          double denom = premiseSupport - totalSupport + 1;
+          return num / denom;
+        }
+      };
+      
+      private final String m_stringVal;
+      METRIC_TYPE(String name) {
+        m_stringVal = name;
+      }
+      
+      abstract double compute(int premiseSupport, int consequenceSupport, 
+          int totalSupport, int totalTransactions);
+      
+      public String toString() {
+        return m_stringVal;
+      }
+      
+      public String toStringMetric(int premiseSupport, int consequenceSupport,
+          int totalSupport, int totalTransactions) {
+        return m_stringVal + ":(" + Utils.doubleToString(compute(premiseSupport, consequenceSupport,
+            totalSupport, totalTransactions), 2) + ")";
+      }
+    }
+    
+    /** Tags for display in the GUI */
+    public static final Tag[] TAGS_SELECTION = {
+      new Tag(METRIC_TYPE.CONFIDENCE.ordinal(), "Confidence"),
+      new Tag(METRIC_TYPE.LIFT.ordinal(), "Lift"),
+      new Tag(METRIC_TYPE.LEVERAGE.ordinal(), "Leverage"),
+      new Tag(METRIC_TYPE.CONVICTION.ordinal(), "Conviction")
+    };
+    
+    /** The metric type for this rule */
+    protected METRIC_TYPE m_metricType = METRIC_TYPE.CONFIDENCE;
+    
+    /** The premise of the rule */
+    protected Collection<BinaryItem> m_premise;
+    
+    /** The consequence of the rule */
+    protected Collection<BinaryItem> m_consequence;
+    
+    /** The support for the premise */
+    protected int m_premiseSupport;
+    
+    /** The support for the consequence */
+    protected int m_consequenceSupport;
+    
+    /** The total support for the item set (premise + consequence) */
+    protected int m_totalSupport;
+    
+    /** The total number of transactions in the data */
+    protected int m_totalTransactions;
+    
+    /**
+     * Construct a new association rule.
+     * 
+     * @param premise the premise of the rule
+     * @param consequence the consequence of the rule
+     * @param metric the metric for the rule
+     * @param premiseSupport the support of the premise
+     * @param consequenceSupport the support of the consequence
+     * @param totalSupport the total support of the rule
+     * @param totalTransactions the number of transactions in the data
+     */
+    public AssociationRule(Collection<BinaryItem> premise, 
+        Collection<BinaryItem> consequence, METRIC_TYPE metric,
+        int premiseSupport, int consequenceSupport,
+        int totalSupport, int totalTransactions) {
+      m_premise = premise;
+      m_consequence = consequence;
+      m_metricType = metric;
+      m_premiseSupport = premiseSupport;
+      m_consequenceSupport = consequenceSupport;
+      m_totalSupport = totalSupport;
+      m_totalTransactions = totalTransactions;
+    }
+    
+    /**
+     * Get the premise of this rule.
+     * 
+     * @return the premise of this rule.
+     */
+    public Collection<BinaryItem> getPremise() {
+      return m_premise;
+    }
+    
+    /**
+     * Get the consequence of this rule.
+     * 
+     * @return the consequence of this rule.
+     */
+    public Collection<BinaryItem> getConsequence() {
+      return m_consequence;
+    }
+    
+    /**
+     * Get the metric type of this rule (e.g. confidence).
+     * 
+     * @return the metric type of this rule.
+     */
+    public METRIC_TYPE getMetricType() {
+      return m_metricType;
+    }
+    
+    /**
+     * Get the value of the metric for this rule. 
+     * 
+     * @return the value of the metric for this rule.
+     */
+    public double getMetricValue() {
+      return m_metricType.compute(m_premiseSupport, m_consequenceSupport, 
+          m_totalSupport, m_totalTransactions);
+    }
+    
+    /**
+     * Get the support for the premise.
+     * 
+     * @return the support for the premise.
+     */
+    public int getPremiseSupport() {
+      return m_premiseSupport;
+    }
+    
+    /**
+     * Get the support for the consequence.
+     * 
+     * @return the support for the consequence.
+     */
+    public int getConsequenceSupport() {
+      return m_consequenceSupport;
+    }
+    
+    /**
+     * Get the total support for this rule (premise + consequence).
+     * 
+     * @return the total support for this rule.
+     */
+    public int getTotalSupport() {
+      return m_totalSupport;
+    }
+    
+    /**
+     * Get the total number of transactions in the data.
+     * 
+     * @return the total number of transactions in the data.
+     */
+    public int getTotalTransactions() {
+      return m_totalTransactions;
+    }
+    
+    /**
+     * Compare this rule to the supplied rule.
+     * 
+     * @param other the rule to compare to.
+     * @return the result of the comparison.
+     */
+    public int compareTo(AssociationRule other) {
+      return -Double.compare(getMetricValue(), other.getMetricValue());
+    }
+    
+    /**
+     * Return true if this rule is equal to the supplied one.
+     * 
+     * @return true if this rule is the same as the supplied rule.
+     */
+    public boolean equals(Object other) {
+      if (!(other instanceof AssociationRule)) {
+        return false;
+      }
+      
+      AssociationRule otherRule = (AssociationRule)other;
+      boolean result = m_premise.equals(otherRule.getPremise()) &&
+        m_consequence.equals(otherRule.getConsequence()) && 
+        (getMetricValue() == otherRule.getMetricValue());
+      
+      return result;
+    }
+    
+    /**
+     * Get a textual description of this rule.
+     * 
+     * @return a textual description of this rule.
+     */
+    public String toString() {
+      StringBuffer result = new StringBuffer();
+      
+      result.append(m_premise.toString() + ": " + m_premiseSupport 
+          + " ==> " + m_consequence.toString() + ": " + m_totalSupport 
+          + "   ");
+      for (METRIC_TYPE m : METRIC_TYPE.values()) {
+        if (m.equals(m_metricType)) {
+          result.append("<" + 
+              m.toStringMetric(m_premiseSupport, m_consequenceSupport, 
+                  m_totalSupport, m_totalTransactions) + "> ");
+        } else {
+          result.append("" + 
+              m.toStringMetric(m_premiseSupport, m_consequenceSupport, 
+                  m_totalSupport, m_totalTransactions) + " ");
+        }
+      }
+      return result.toString();
+    }
+    
+    private static void nextSubset(boolean[] subset) {
+      for (int i = 0; i < subset.length; i++) {
+        if (!subset[i]) {
+          subset[i] = true;
+          break;
+        } else {
+          subset[i] = false;
+        }
+      }
+    }
+    
+    private static Collection<BinaryItem> getPremise(FrequentBinaryItemSet fis, 
+        boolean[] subset) {
+      boolean ok = false;
+      for (int i = 0; i < subset.length; i++){
+        if (!subset[i]) {
+          ok = true;
+          break;
+        }
+      }      
+      
+      if (!ok) {
+        return null;
+      }
+      
+      List<BinaryItem> premise = new ArrayList<BinaryItem>();
+      ArrayList<BinaryItem> items = new ArrayList<BinaryItem>(fis.getItems());
+
+      
+      for (int i = 0; i < subset.length; i++) {
+        if (subset[i]) {
+          premise.add(items.get(i));
+        }
+      }
+      return premise;
+    }
+    
+    private static Collection<BinaryItem> getConsequence(FrequentBinaryItemSet fis,
+        boolean[] subset) {
+      List<BinaryItem> consequence = new ArrayList<BinaryItem>();
+      ArrayList<BinaryItem> items = new ArrayList<BinaryItem>(fis.getItems());
+      
+      for (int i = 0; i < subset.length; i++) {
+        if (!subset[i]) {
+          consequence.add(items.get(i));
+        }
+      }
+      return consequence;
+    }
+    
+    
+    /**
+     * Generate all association rules, from the supplied frequet item sets,
+     * that meet a given minimum metric threshold. Uses a brute force approach.
+     * 
+     * @param largeItemSets the set of frequent item sets
+     * @param metricToUse the metric to use
+     * @param metricThreshold the threshold value that a rule must meet
+     * @param totalTransactions the total number of transactions in the data
+     * @return a list of association rules
+     */
+    public static List<AssociationRule> 
+      generateRulesBruteForce(FrequentItemSets largeItemSets, METRIC_TYPE metricToUse, 
+          double metricThreshold, int totalTransactions) {
+      
+      List<AssociationRule> rules = new ArrayList<AssociationRule>();
+      largeItemSets.sort();
+      Map<Collection<BinaryItem>, Integer> frequencyLookup =
+        new HashMap<Collection<BinaryItem>, Integer>();
+      
+      Iterator<FrequentBinaryItemSet> setI = largeItemSets.iterator();
+      // process each large item set
+      while (setI.hasNext()) {
+        FrequentBinaryItemSet fis = setI.next();
+        frequencyLookup.put(fis.getItems(), fis.getSupport());
+        if (fis.getItems().size() > 1) {
+          // generate all the possible subsets for the premise
+          boolean[] subset = new boolean[fis.getItems().size()];
+          Collection<BinaryItem> premise = null;
+          Collection<BinaryItem> consequence = null;
+          while ((premise = getPremise(fis, subset)) != null) {
+            if (premise.size() > 0 && premise.size() < fis.getItems().size()) {
+              consequence = getConsequence(fis, subset);
+              int totalSupport = fis.getSupport();
+              int supportPremise = frequencyLookup.get(premise).intValue();
+              int supportConsequence = frequencyLookup.get(consequence).intValue();
+              
+              // a candidate rule
+              AssociationRule candidate = 
+                new AssociationRule(premise, consequence, metricToUse, supportPremise,
+                    supportConsequence, totalSupport, totalTransactions);
+              if (candidate.getMetricValue() > metricThreshold) {
+                // accept this rule
+                rules.add(candidate);
+              }              
+            }
+            nextSubset(subset);
+          }
+        }
+      }
+      
+      return rules;
+    }
+  }
+  
+  /** The number of rules to find */
+  protected int m_numRulesToFind = 10;
+  //protected double m_upperBoundMinSupport = 0.36;
+  
+  /** The upper bound on the minimum support */
+  protected double m_upperBoundMinSupport = 1.0;
+  
+  /** The lower bound on minimum support */
+  protected double m_lowerBoundMinSupport = 0.1;
+  
+  /** The amount by which to decrease the support in each iteration */
+  protected double m_delta = 0.05;
+  
+  /** 
+   * If true, just all rules meeting the lower bound on the minimum
+   * support will be found. The number of rules to find will be
+   * ignored and the iterative reduction of support will not
+   * be done. 
+   */
+  protected boolean m_findAllRulesForSupportLevel = false;
+  
+  //protected double m_lowerBoundMinSupport = 0.0;
+  
+  /** The index (1 based) of binary attributes to treat as the positive value */
+  protected int m_positiveIndex = 2;
+  
+  protected AssociationRule.METRIC_TYPE m_metric = 
+    AssociationRule.METRIC_TYPE.CONFIDENCE;
+  
+  protected double m_metricThreshold = 0.9;
+  
+  /** Holds the large item sets found */
+  protected FrequentItemSets m_largeItemSets;
+  
+  /** Holds the rules */
+  protected List<AssociationRule> m_rules;
+  
+  // maximum number of items in a large item set (zero means no limit)
+  protected int m_maxItems = -1;
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // enable what we can handle
+    
+    // attributes
+    result.enable(Capability.UNARY_ATTRIBUTES);
+    result.enable(Capability.BINARY_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Returns a string describing this associator
+   * 
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class implementing the FP-growth algorithm for finding" +
+    		" large item sets without candidate generation. Iteratively" +
+    		" reduces the minimum support until it finds the required" +
+    		" number of rules with the given minimum metric." +
+    		" For more information see:\n\n" +
+    		getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "J. Han and J.Pei and Y. Yin");
+    result.setValue(Field.TITLE, "Mining frequent patterns without candidate generation");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the 2000 ACM-SIGMID International" +
+    		" Conference on Management of Data");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.PAGES, "1-12");
+    
+    return result;
+  }
+  
+  
+  /**
+   * Get the singleton items in the data
+   * 
+   * @param data the Instances to process
+   * @return a list of singleton item sets
+   * @throws Exception if the singletons can't be found for some reason
+   */
+  protected ArrayList<BinaryItem> getSingletons(Instances data) throws Exception {
+    ArrayList<BinaryItem> singletons = new ArrayList<BinaryItem>();
+    
+    for (int i = 0; i < data.numAttributes(); i++) {
+      singletons.add(new BinaryItem(data.attribute(i), m_positiveIndex - 1));
+    }
+    
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance current = data.instance(i);
+      if (current instanceof SparseInstance) {
+        for (int j = 0; j < current.numValues(); j++) {
+          int attIndex = current.index(j);
+          singletons.get(attIndex).increaseFrequency();
+        }
+      } else {
+        for (int j = 0; j < data.numAttributes(); j++) {
+          if (!current.isMissing(j)) {
+            if (current.attribute(j).numValues() == 1 
+                || current.value(j) == m_positiveIndex - 1) {
+              singletons.get(j).increaseFrequency();
+            }
+          }
+        }
+      }
+    }
+    
+    return singletons;
+  }
+  
+  /*protected ArrayList<BinaryItem> getFrequent(ArrayList<BinaryItem> items, int minSupport) {
+    ArrayList<BinaryItem> frequent = new ArrayList<BinaryItem>();
+    for (BinaryItem b : items) {
+      if (b.getFrequency() > minSupport) {
+        frequent.add(b);
+      }
+    }
+    
+    // sort in descending order of support
+    Collections.sort(frequent);
+    return frequent;
+  } */
+  
+  /**
+   * Construct the frequent pattern tree by inserting each transaction
+   * in the data into the tree. Only those items from each transaction that
+   * meet the minimum support threshold are inserted.
+   * 
+   * @param singletons the singleton item sets
+   * @param data the Instances containing the transactions
+   * @param minSupport the minimum support
+   * @return the root of the tree
+   */
+  protected FPTreeRoot buildFPTree(ArrayList<BinaryItem> singletons, 
+      Instances data, int minSupport) {
+    
+    FPTreeRoot tree = new FPTreeRoot();
+   
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance current = data.instance(i);
+      ArrayList<BinaryItem> transaction = new ArrayList<BinaryItem>();
+      if (current instanceof SparseInstance) {
+        for (int j = 0; j < current.numValues(); j++) {
+          int attIndex = current.index(j);
+          if (singletons.get(attIndex).getFrequency() >= minSupport) {
+            transaction.add(singletons.get(attIndex));
+          }
+        }
+        Collections.sort(transaction);
+        tree.addItemSet(transaction, 1);
+      } else {
+        for (int j = 0; j < data.numAttributes(); j++) {
+          if (!current.isMissing(j)) {
+            if (current.attribute(j).numValues() == 1 
+                || current.value(j) == m_positiveIndex - 1) {
+              if (singletons.get(j).getFrequency() >= minSupport) {
+                transaction.add(singletons.get(j));
+              }
+            }
+          }
+        }
+        Collections.sort(transaction);
+        tree.addItemSet(transaction, 1);
+      }
+    }
+    
+    return tree;
+  }
+  
+  /**
+   * Find large item sets in the FP-tree.
+   * 
+   * @param tree the root of the tree to mine
+   * @param largeItemSets holds the large item sets found
+   * @param recursionLevel the recursion level for the current projected
+   * counts
+   * @param conditionalItems the current set of items that the current
+   * (projected) tree is conditional on
+   * @param minSupport the minimum acceptable support
+   */
+  protected void mineTree(FPTreeRoot tree, FrequentItemSets largeItemSets, 
+      int recursionLevel, FrequentBinaryItemSet conditionalItems, int minSupport) {
+    
+    if (!tree.isEmpty(recursionLevel)) {
+      if (m_maxItems > 0 && recursionLevel >= m_maxItems) {
+        // don't mine any further
+        return;
+      }
+      
+      Map<BinaryItem, FPTreeRoot.Header> headerTable = tree.getHeaderTable();
+      Set<BinaryItem> keys = headerTable.keySet();
+//      System.err.println("Number of freq item sets collected " + largeItemSets.size());
+      Iterator<BinaryItem> i = keys.iterator();
+      while (i.hasNext()) {
+        BinaryItem item = i.next();
+        FPTreeRoot.Header itemHeader = headerTable.get(item);
+        
+        // check for minimum support at this level
+        int support = itemHeader.getProjectedCounts().getCount(recursionLevel);
+        if (support >= minSupport) {          
+          // process header list at this recursion level
+          for (FPTreeNode n : itemHeader.getHeaderList()) {
+            // push count up path to root
+            int currentCount = n.getProjectedCount(recursionLevel);
+            if (currentCount > 0) {                            
+              FPTreeNode temp = n.getParent();
+              while (temp != tree) {
+                // set/increase for the node
+                temp.increaseProjectedCount(recursionLevel + 1, currentCount);
+
+                // set/increase for the header table
+                headerTable.get(temp.getItem()).
+                getProjectedCounts().increaseCount(recursionLevel + 1, currentCount);
+                
+                temp = temp.getParent();
+              }
+            }
+          }
+          
+          FrequentBinaryItemSet newConditional = 
+            (FrequentBinaryItemSet) conditionalItems.clone();
+          
+          // this item gets added to the conditional items
+          newConditional.addItem(item);
+          newConditional.setSupport(support);
+          
+          // now add this conditional item set to the list of large item sets
+          largeItemSets.addItemSet(newConditional);
+          
+          // now recursively process the new tree
+          mineTree(tree, largeItemSets, recursionLevel + 1, newConditional,
+              minSupport);
+          
+          // reverse the propagated counts
+          for (FPTreeNode n : itemHeader.getHeaderList()) {
+            FPTreeNode temp = n.getParent();
+            while (temp != tree) {
+              temp.removeProjectedCount(recursionLevel + 1);
+              temp = temp.getParent();
+            }
+          }
+          
+          // reverse the propagated counts in the header list
+          // at this recursion level
+          for (FPTreeRoot.Header h : headerTable.values()) {
+            h.getProjectedCounts().removeCount(recursionLevel + 1);
+          }          
+        }
+      }
+    }
+  }
+  
+  /**
+   * Construct a new FPGrowth object.
+   */
+  public FPGrowth() {
+    resetOptions();
+  }
+  
+  /**
+   * Reset all options to their default values.
+   */
+  public void resetOptions() {
+    m_delta = 0.05;
+    m_metricThreshold = 0.9;
+    m_numRulesToFind = 10;
+    m_lowerBoundMinSupport = 0.1;
+    m_upperBoundMinSupport = 1.0;
+//    m_minSupport = -1;
+    m_positiveIndex = 2;
+  }
+  
+  /**
+   * Tip text for this property suitable for displaying
+   * in the GUI.
+   * 
+   * @return the tip text for this property.
+   */
+  public String positiveIndexTipText() {
+    return "Set the index of binary valued attributes that is to be considered" +
+    		" the positive index. Has no effect for sparse data (in this case" +
+    		" the first index (i.e. non-zero values) is always treated as " +
+    		" positive. Also has no effect for unary valued attributes (i.e." +
+    		" when using the Weka Apriori-style format for market basket data," +
+    		" which uses missing value \"?\" to indicate" +
+    		" absence of an item.";
+  }
+  
+  /**
+   * Set the index of the attribute value to consider as positive
+   * for binary attributes in normal dense instances. Index 1 is always
+   * used for sparse instances.
+   * 
+   * @param index the index to use for positive values in binary attributes.
+   */
+  public void setPositiveIndex(int index) {
+    m_positiveIndex = index;
+  }
+  
+  /**
+   * Get the index of the attribute value to consider as positive
+   * for binary attributes in normal dense instances. Index 1 is always
+   * used for sparse instances.
+   * 
+   * @return the index to use for positive values in binary attributes.
+   */
+  public int getPositiveIndex() {
+    return m_positiveIndex;
+  }
+  
+  /**
+   * Set the desired number of rules to find.
+   * 
+   * @param numR the number of rules to find.
+   */
+  public void setNumRulesToFind(int numR) {
+    m_numRulesToFind = numR;
+  }
+  
+  /**
+   * Get the number of rules to find.
+   * 
+   * @return the number of rules to find.
+   */
+  public int getNumRulesToFind() {
+    return m_numRulesToFind;
+  }
+  
+  /**
+   * Tip text for this property suitable for displaying
+   * in the GUI.
+   * 
+   * @return the tip text for this property.
+   */
+  public String numRulesToFindTipText() {
+    return "The number of rules to output";
+  }
+  
+  /**
+   * Set the metric type to use.
+   * 
+   * @param d the metric type
+   */
+  public void setMetricType(SelectedTag d) {
+    int ordinal =  d.getSelectedTag().getID();
+    for (AssociationRule.METRIC_TYPE m : AssociationRule.METRIC_TYPE.values()) {
+      if (m.ordinal() == ordinal) {
+        m_metric = m;
+        break;
+      }
+    }
+  }
+  
+  /**
+   * Set the maximum number of items to include in large items sets.
+   * 
+   * @param max the maxim number of items to include in large item sets.
+   */
+  public void setMaxNumberOfItems(int max) {
+    m_maxItems = max;
+  }
+  
+  /**
+   * Gets the maximum number of items to be included in large item sets.
+   * 
+   * @return the maximum number of items to be included in large items sets.
+   */
+  public int getMaxNumberOfItems() {
+    return m_maxItems;
+  }
+  
+  /**
+   * Tip text for this property suitable for displaying
+   * in the GUI.
+   * 
+   * @return the tip text for this property.
+   */
+  public String maxNumberOfItemsTipText() {
+    return "The maximum number of items to include in frequent item sets. -1 " +
+    		"means no limit.";
+  }
+  
+  /**
+   * Get the metric type to use.
+   * 
+   * @return the metric type to use.
+   */
+  public SelectedTag getMetricType() {
+    return new SelectedTag(m_metric.ordinal(), AssociationRule.TAGS_SELECTION);
+  }
+  
+  /**
+   * Tip text for this property suitable for displaying
+   * in the GUI.
+   * 
+   * @return the tip text for this property.
+   */
+  public String metricTypeTipText() {
+    return "Set the type of metric by which to rank rules. Confidence is "
+    +"the proportion of the examples covered by the premise that are also "
+    +"covered by the consequence(Class association rules can only be mined using confidence). Lift is confidence divided by the "
+    +"proportion of all examples that are covered by the consequence. This "
+    +"is a measure of the importance of the association that is independent "
+    +"of support. Leverage is the proportion of additional examples covered "
+    +"by both the premise and consequence above those expected if the "
+    +"premise and consequence were independent of each other. The total "
+    +"number of examples that this represents is presented in brackets "
+    +"following the leverage. Conviction is "
+    +"another measure of departure from independence.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minMetricTipText() {
+    return "Minimum metric score. Consider only rules with scores higher than "
+      +"this value.";
+  }
+
+  /**
+   * Get the value of minConfidence.
+   *
+   * @return Value of minConfidence.
+   */
+  public double getMinMetric() {
+    
+    return m_metricThreshold;
+  }
+  
+  /**
+   * Set the value of minConfidence.
+   *
+   * @param v  Value to assign to minConfidence.
+   */
+  public void setMinMetric(double v) {
+    
+    m_metricThreshold = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String deltaTipText() {
+    return "Iteratively decrease support by this factor. Reduces support "
+      +"until min support is reached or required number of rules has been "
+      +"generated.";
+  }
+    
+  /**
+   * Get the value of delta.
+   *
+   * @return Value of delta.
+   */
+  public double getDelta() {
+    
+    return m_delta;
+  }
+  
+  /**
+   * Set the value of delta.
+   *
+   * @param v  Value to assign to delta.
+   */
+  public void setDelta(double v) {
+    
+    m_delta = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lowerBoundMinSupportTipText() {
+    return "Lower bound for minimum support.";
+  }
+
+  /**
+   * Get the value of lowerBoundMinSupport.
+   *
+   * @return Value of lowerBoundMinSupport.
+   */
+  public double getLowerBoundMinSupport() {
+    
+    return m_lowerBoundMinSupport;
+  }
+  
+  /**
+   * Set the value of lowerBoundMinSupport.
+   *
+   * @param v  Value to assign to lowerBoundMinSupport.
+   */
+  public void setLowerBoundMinSupport(double v) {
+    
+    m_lowerBoundMinSupport = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String upperBoundMinSupportTipText() {
+    return "Upper bound for minimum support. Start iteratively decreasing "
+      +"minimum support from this value.";
+  }
+
+  /**
+   * Get the value of upperBoundMinSupport.
+   *
+   * @return Value of upperBoundMinSupport.
+   */
+  public double getUpperBoundMinSupport() {
+    
+    return m_upperBoundMinSupport;
+  }
+  
+  /**
+   * Set the value of upperBoundMinSupport.
+   *
+   * @param v  Value to assign to upperBoundMinSupport.
+   */
+  public void setUpperBoundMinSupport(double v) {
+    
+    m_upperBoundMinSupport = v;
+  }
+  
+  /**
+   * Tip text for this property suitable for displaying
+   * in the GUI.
+   * 
+   * @return the tip text for this property.
+   */
+  public String findAllRulesForSupportLevelTipText() {
+    return "Find all rules that meet " +
+    "the lower bound on minimum support and the minimum metric constraint. " +
+    "Turning this mode on will disable the iterative support reduction " +
+    "procedure to find the specified number of rules.";
+  }
+  
+  /**
+   * If true then turn off the iterative support reduction method
+   * of finding x rules that meet the minimum support and metric
+   * thresholds and just return all the rules that meet the
+   * lower bound on minimum support and the minimum metric.
+   * 
+   * @param s true if all rules meeting the lower bound on the support
+   * and minimum metric thresholds are to be found.
+   */
+  public void setFindAllRulesForSupportLevel(boolean s) {
+    m_findAllRulesForSupportLevel = s;
+  }
+  
+  /**
+   * Get whether all rules meeting the lower bound on min support
+   * and the minimum metric threshold are to be found.
+   * 
+   * @return true if all rules meeting the lower bound on min
+   * support and the min metric threshold are to be found.
+   */
+  public boolean getFindAllRulesForSupportLevel() {
+    return m_findAllRulesForSupportLevel;
+  }
+  
+  /* public void setMinimumSupport(double minSupp) {
+    m_minSupport = minSupp;
+  }
+  
+  public double getMinimumSupport() {
+    return m_minSupport;
+  } */    
+  
+  /**
+   * Gets the list of mined association rules.
+   * 
+   * @return the list of association rules discovered during mining.
+   * Returns null if mining hasn't been performed yet.
+   */
+  public List<AssociationRule> getAssociationRules() {
+    return m_rules;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration<Option> listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    String string00 = "\tSet the index of the attribute value to consider as 'positive'\n\t"
+   + "for binary attributes in normal dense instances. Index 2 is always\n\t"
+   + "used for sparse instances. (default = 2)";    
+    String string0 = "\tThe maximum number of items to include " +
+    		"in large items sets (and rules). (default " +
+    		"= -1, i.e. no limit.)"; 
+      
+    String string1 = "\tThe required number of rules. (default = " 
+      + m_numRulesToFind + ")";
+    String string2 = "\tThe minimum metric score of a rule. (default" +
+    		" = " + m_metricThreshold + ")";
+    String string3 = "\tThe metric by which to rank rules. (default"
+      + " = confidence)";
+    String string4 = "\tThe lower bound for the minimum support. (default = "
+      + m_lowerBoundMinSupport + ")";
+    String string5 = "\tUpper bound for minimum support. "
+      + "(default = 1.0)";
+    String string6 = "\tThe delta by which the minimum support is decreased in\n"
+     + "\teach iteration. (default = " + m_delta + ")";
+    String string7 = "\tFind all rules that meet the lower bound on\n\t" +
+    		"minimum support and the minimum metric constraint.\n\t" +
+    		"Turning this mode on will disable the iterative support reduction\n\t" +
+    		"procedure to find the specified number of rules.";
+    
+    newVector.add(new Option(string00, "P", 1, "-P <attribute index of positive value>"));
+    newVector.add(new Option(string0, "I", 1, "-I <max items>"));
+    newVector.add(new Option(string1, "N", 1, "-N <require number of rules>"));
+    newVector.add(new Option(string3, "T", 1, "-T <0=confidence | 1=lift | "
+                                    + "2=leverage | 3=Conviction>"));
+    newVector.add(new Option(string2, "C", 1, "-C <minimum metric score of a rule>"));
+    newVector.add(new Option(string5, "U", 1, "-U <upper bound for minimum support>"));
+    newVector.add(new Option(string4, "M", 1, "-M <lower bound for minimum support>"));
+    newVector.add(new Option(string6, "D", 1, "-D <delta for minimum support>"));
+    newVector.add(new Option(string7, "S", 0, "-S"));
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * 
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;attribute index of positive value&gt;
+   *  Set the index of the attribute value to consider as 'positive'
+   *  for binary attributes in normal dense instances. Index 2 is always
+   *  used for sparse instances. (default = 2)</pre>
+   * 
+   * <pre> -I &lt;max items&gt;
+   *  The maximum number of items to include in large items sets (and rules). (default = -1, i.e. no limit.)</pre>
+   * 
+   * <pre> -N &lt;require number of rules&gt;
+   *  The required number of rules. (default = 10)</pre>
+   * 
+   * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+   *  The metric by which to rank rules. (default = confidence)</pre>
+   * 
+   * <pre> -C &lt;minimum metric score of a rule&gt;
+   *  The minimum metric score of a rule. (default = 0.9)</pre>
+   * 
+   * <pre> -U &lt;upper bound for minimum support&gt;
+   *  Upper bound for minimum support. (default = 1.0)</pre>
+   * 
+   * <pre> -M &lt;lower bound for minimum support&gt;
+   *  The lower bound for the minimum support. (default = 0.1)</pre>
+   * 
+   * <pre> -D &lt;delta for minimum support&gt;
+   *  The delta by which the minimum support is decreased in
+   *  each iteration. (default = 0.05)</pre>
+   * 
+   * <pre> -S
+   *  Find all rules that meet the lower bound on
+   *  minimum support and the minimum metric constraint.
+   *  Turning this mode on will disable the iterative support reduction
+   *  procedure to find the specified number of rules.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    resetOptions();
+    String positiveIndexString = Utils.getOption('P', options);
+    String maxItemsString = Utils.getOption('I', options);
+    String numRulesString = Utils.getOption('N', options);
+    String minMetricString = Utils.getOption('C', options);
+    String metricTypeString = Utils.getOption("T", options);
+    String lowerBoundSupportString = Utils.getOption("M", options);
+    String upperBoundSupportString = Utils.getOption("U", options);
+    String deltaString = Utils.getOption("D", options);
+
+    if (positiveIndexString.length() != 0) {
+      setPositiveIndex(Integer.parseInt(positiveIndexString));
+    }
+    
+    if (maxItemsString.length() != 0) {
+      setMaxNumberOfItems(Integer.parseInt(maxItemsString));
+    }
+    
+    if (metricTypeString.length() != 0) {
+      setMetricType(new SelectedTag(Integer.parseInt(metricTypeString),
+          AssociationRule.TAGS_SELECTION));
+    }
+    
+    if (numRulesString.length() != 0) {
+      setNumRulesToFind(Integer.parseInt(numRulesString));
+    }
+    
+    if (minMetricString.length() != 0) {
+      setMinMetric(Double.parseDouble(minMetricString));
+    }
+    
+    if (deltaString.length() != 0) {
+      setDelta(Double.parseDouble(deltaString));
+    }
+    
+    if (lowerBoundSupportString.length() != 0) {
+      setLowerBoundMinSupport(Double.parseDouble(lowerBoundSupportString));
+    }
+    
+    if (upperBoundSupportString.length() != 0) {
+      setUpperBoundMinSupport(Double.parseDouble(upperBoundSupportString));
+    }
+    
+    setFindAllRulesForSupportLevel(Utils.getFlag('S', options));
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    ArrayList<String> options = new ArrayList<String>();
+    
+    options.add("-P"); options.add("" + getPositiveIndex());
+    options.add("-I"); options.add("" + getMaxNumberOfItems());
+    options.add("-N"); options.add("" + getNumRulesToFind());
+    options.add("-T"); options.add("" + getMetricType().getSelectedTag().getID());
+    options.add("-C"); options.add("" + getMinMetric());
+    options.add("-D"); options.add("" + getDelta());
+    options.add("-U"); options.add("" + getUpperBoundMinSupport());
+    options.add("-M"); options.add("" + getLowerBoundMinSupport());
+    if (getFindAllRulesForSupportLevel()) {
+      options.add("-S");
+    }
+    
+    return options.toArray(new String[1]);
+  }
+
+  /**
+   * Method that generates all large item sets with a minimum support, and from
+   * these all association rules with a minimum metric (i.e. confidence, 
+   * lift etc.).
+   *
+   * @param data the instances to be used for generating the associations
+   * @throws Exception if rules can't be built successfully
+   */
+  public void buildAssociations(Instances data) throws Exception {
+    
+    // can we handle the data?
+    getCapabilities().testWithFail(data);
+    
+    double currentSupport = m_upperBoundMinSupport;
+    
+    if (m_findAllRulesForSupportLevel) {
+      currentSupport = m_lowerBoundMinSupport;
+    }
+    // first compute singletons
+    ArrayList<BinaryItem> singletons = getSingletons(data);
+    //ArrayList<BinaryItem> singletonsCopy = new ArrayList<BinaryItem>(singletons);
+/*    Collections.sort(singletonsCopy);
+    for (int i = 0; i < singletonsCopy.size(); i++) {
+      System.out.println(singletonsCopy.get(i).toString(true));
+    }
+    System.out.println("---------"); */
+//    System.out.println("Finished finding singletons...");
+    
+    // while not enough rules
+    do {
+      int currentSupportAsInstances = (currentSupport > 1)
+      ? (int)currentSupport
+      : (int)Math.ceil(currentSupport * data.numInstances());
+      
+      //System.err.println("Current support " + currentSupportAsInstances);
+      //ArrayList<BinaryItem> prunedSingletons = removeNonFrequent(singletons);
+
+      // build the FPTree
+      FPTreeRoot tree = buildFPTree(singletons, data, currentSupportAsInstances);
+//      System.out.println("Finished building tree...");
+//      System.out.println(tree.toString(0));
+    /*System.out.println(tree.printHeaderTable(0)); */
+
+      FrequentItemSets largeItemSets = new FrequentItemSets(data.numInstances());
+
+      // mine the tree
+      FrequentBinaryItemSet conditionalItems = 
+        new FrequentBinaryItemSet(new ArrayList<BinaryItem>(), 0);
+      mineTree(tree, largeItemSets, 0, conditionalItems, currentSupportAsInstances);
+
+      m_largeItemSets = largeItemSets;
+//         System.err.println("Number of large item sets: " + m_largeItemSets.size());
+  //    System.err.println(m_largeItemSets.toString(100));
+
+      //    m_largeItemSets.sort(compF);
+//      System.err.println("Finished mining tree...");
+      
+      // save memory
+      tree = null;
+      
+      m_rules = 
+        AssociationRule.generateRulesBruteForce(m_largeItemSets, m_metric, 
+            m_metricThreshold, data.numInstances());
+      
+      if (!m_findAllRulesForSupportLevel) {
+        currentSupport -= m_delta;
+        if (currentSupport < m_lowerBoundMinSupport) {
+          if (currentSupport + m_delta > m_lowerBoundMinSupport) {
+            // ensure that the lower bound does get evaluated
+            currentSupport = m_lowerBoundMinSupport;
+          } else {
+            break;
+          }
+        }
+      } else {
+        // just break out of the loop as we are just finding all rules
+        // with a minimum support + metric
+        break;
+      }
+    } while (m_rules.size() < m_numRulesToFind);
+    
+    Collections.sort(m_rules);
+//    for (AssociationRule)
+    
+//    System.out.println(graph(tree));
+  }
+    
+  /**
+   * Output the association rules.
+   * 
+   * @return a string representation of the model.
+   */
+  public String toString() {
+//    return m_largeItemSets.toString(m_numItemSetsToFind);
+    if (m_rules == null) {
+      return "FPGrowth hasn't been trained yet!";
+    }
+
+    StringBuffer result = new StringBuffer();
+    int numRules = (m_rules.size() < m_numRulesToFind)
+      ? m_rules.size()
+      : m_numRulesToFind;
+      
+    if (m_rules.size() == 0) {
+      return "No rules found!";
+    } else {      
+      result.append("FPGrowth found " + m_rules.size() + " rules");
+      if (!m_findAllRulesForSupportLevel) {
+        result.append(" (displaying top " + numRules + ")");
+      }
+      result.append("\n\n");
+    }
+
+    int count = 0;
+    for (AssociationRule r : m_rules) {
+      result.append(Utils.doubleToString((double)count+1,
+          (int)(Math.log(numRules)/Math.log(10)+1), 0) + ". ");
+      result.append(r + "\n");
+      count++;
+      if (!m_findAllRulesForSupportLevel && count == m_numRulesToFind) {
+        break;
+      }
+    }
+    return result.toString();
+  }
+  
+  /**
+   * Assemble a dot graph representation of the FP-tree.
+   * 
+   * @param tree the root of the FP-tree
+   * @return a graph representation as a String in dot format.
+   */
+  public String graph(FPTreeRoot tree) {
+    //int maxID = tree.assignIDs(-1);
+    
+    
+    StringBuffer text = new StringBuffer();
+    text.append("digraph FPTree {\n");
+    text.append("N0 [label=\"ROOT\"]\n");
+    tree.graphFPTree(text);
+    
+//    tree.graphHeaderTable(text, maxID+1);
+    text.append("}\n");
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6158 $");
+  }
+  
+  /**
+   * Main method.
+   * 
+   * @param args the commandline options
+   */
+  public static void main(String[] args) {
+    runAssociator(new FPGrowth(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/associations/FilteredAssociator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/FilteredAssociator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/FilteredAssociator.java	(revision 29)
@@ -0,0 +1,484 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilteredAssociator.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.MultiFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for running an arbitrary associator on data that has been passed through an arbitrary filter. Like the associator, the structure of the filter is based exclusively on the training data and test instances will be processed by the filter without changing their structure.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"
+ *  (default: weka.filters.MultiFilter with
+ *  weka.filters.unsupervised.attribute.ReplaceMissingValues)</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ *  The class index.
+ *  (default: -1, i.e. unset)</pre>
+ * 
+ * <pre> -W
+ *  Full name of base associator.
+ *  (default: weka.associations.Apriori)</pre>
+ * 
+ * <pre> 
+ * Options specific to associator weka.associations.Apriori:
+ * </pre>
+ * 
+ * <pre> -N &lt;required number of rules output&gt;
+ *  The required number of rules. (default = 10)</pre>
+ * 
+ * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+ *  The metric type by which to rank rules. (default = confidence)</pre>
+ * 
+ * <pre> -C &lt;minimum metric score of a rule&gt;
+ *  The minimum confidence of a rule. (default = 0.9)</pre>
+ * 
+ * <pre> -D &lt;delta for minimum support&gt;
+ *  The delta by which the minimum support is decreased in
+ *  each iteration. (default = 0.05)</pre>
+ * 
+ * <pre> -U &lt;upper bound for minimum support&gt;
+ *  Upper bound for minimum support. (default = 1.0)</pre>
+ * 
+ * <pre> -M &lt;lower bound for minimum support&gt;
+ *  The lower bound for the minimum support. (default = 0.1)</pre>
+ * 
+ * <pre> -S &lt;significance level&gt;
+ *  If used, rules are tested for significance at
+ *  the given level. Slower. (default = no significance testing)</pre>
+ * 
+ * <pre> -I
+ *  If set the itemsets found are also output. (default = no)</pre>
+ * 
+ * <pre> -R
+ *  Remove columns that contain all missing values (default = no)</pre>
+ * 
+ * <pre> -V
+ *  Report progress iteratively. (default = no)</pre>
+ * 
+ * <pre> -A
+ *  If set class association rules are mined. (default = no)</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ *  The class index. (default = last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5444 $
+ */
+public class FilteredAssociator 
+  extends SingleAssociatorEnhancer {
+
+  /** for serialization */
+  static final long serialVersionUID = -4523450618538717400L;
+  
+  /** The filter */
+  protected Filter m_Filter;
+
+  /** The instance structure of the filtered instances */
+  protected Instances m_FilteredInstances;
+  
+  /** The class index. */  
+  protected int m_ClassIndex;
+
+  /**
+   * Default constructor.
+   */
+  public FilteredAssociator() {
+    m_Associator = new Apriori();
+    m_Filter     = new MultiFilter();
+    ((MultiFilter) m_Filter).setFilters(new Filter[]{
+	new weka.filters.unsupervised.attribute.ReplaceMissingValues()});
+    m_ClassIndex = -1;
+  }
+
+  /**
+   * Returns a string describing this Associator
+   * 
+   * @return 		a description of the Associator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return   
+        "Class for running an arbitrary associator on data that has been passed "
+      + "through an arbitrary filter. Like the associator, the structure of the filter "
+      + "is based exclusively on the training data and test instances will be processed "
+      + "by the filter without changing their structure.";
+  }
+
+  /**
+   * String describing default associator.
+   * 
+   * @return 		the default associator classname
+   */
+  protected String defaultAssociatorString() {
+    return Apriori.class.getName();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tFull class name of filter to use, followed\n"
+	+ "\tby filter options.\n"
+	+ "\teg: \"weka.filters.unsupervised.attribute.Remove -V -R 1,2\"\n"
+	+ "\t(default: weka.filters.MultiFilter with\n"
+	+ "\tweka.filters.unsupervised.attribute.ReplaceMissingValues)",
+	"F", 1, "-F <filter specification>"));
+
+    result.addElement(new Option(
+	"\tThe class index.\n"
+	+ "\t(default: -1, i.e. unset)", 
+	"c", 1, "-c <the class index>"));
+    
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"
+   *  (default: weka.filters.MultiFilter with
+   *  weka.filters.unsupervised.attribute.ReplaceMissingValues)</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   *  The class index.
+   *  (default: -1, i.e. unset)</pre>
+   * 
+   * <pre> -W
+   *  Full name of base associator.
+   *  (default: weka.associations.Apriori)</pre>
+   * 
+   * <pre> 
+   * Options specific to associator weka.associations.Apriori:
+   * </pre>
+   * 
+   * <pre> -N &lt;required number of rules output&gt;
+   *  The required number of rules. (default = 10)</pre>
+   * 
+   * <pre> -T &lt;0=confidence | 1=lift | 2=leverage | 3=Conviction&gt;
+   *  The metric type by which to rank rules. (default = confidence)</pre>
+   * 
+   * <pre> -C &lt;minimum metric score of a rule&gt;
+   *  The minimum confidence of a rule. (default = 0.9)</pre>
+   * 
+   * <pre> -D &lt;delta for minimum support&gt;
+   *  The delta by which the minimum support is decreased in
+   *  each iteration. (default = 0.05)</pre>
+   * 
+   * <pre> -U &lt;upper bound for minimum support&gt;
+   *  Upper bound for minimum support. (default = 1.0)</pre>
+   * 
+   * <pre> -M &lt;lower bound for minimum support&gt;
+   *  The lower bound for the minimum support. (default = 0.1)</pre>
+   * 
+   * <pre> -S &lt;significance level&gt;
+   *  If used, rules are tested for significance at
+   *  the given level. Slower. (default = no significance testing)</pre>
+   * 
+   * <pre> -I
+   *  If set the itemsets found are also output. (default = no)</pre>
+   * 
+   * <pre> -R
+   *  Remove columns that contain all missing values (default = no)</pre>
+   * 
+   * <pre> -V
+   *  Report progress iteratively. (default = no)</pre>
+   * 
+   * <pre> -A
+   *  If set class association rules are mined. (default = no)</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   *  The class index. (default = last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() > 0) {
+      String[] filterSpec = Utils.splitOptions(tmpStr);
+      if (filterSpec.length == 0)
+	throw new IllegalArgumentException("Invalid filter specification string");
+      String filterName = filterSpec[0];
+      filterSpec[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
+    }
+    else {
+      setFilter(new weka.filters.supervised.attribute.Discretize());
+    }
+    
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() > 0) {
+      if (tmpStr.equalsIgnoreCase("last")) {
+        setClassIndex(0);
+      } else if (tmpStr.equalsIgnoreCase("first")) {
+        setClassIndex(1);
+      } else {
+        setClassIndex(Integer.parseInt(tmpStr));
+      }
+    } else {
+      setClassIndex(-1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Associator.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>    	result;
+    int       		i;
+    String[]  		options;
+
+    result = new Vector<String>();
+    
+    result.add("-F");
+    result.add("" + getFilterSpec());
+
+    result.add("-c");
+    result.add("" + getClassIndex());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return result.toArray(new String[result.size()]);	  
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used.";
+  }
+
+  /**
+   * Sets the filter
+   *
+   * @param value 	the filter with all options set.
+   */
+  public void setFilter(Filter value) {
+    m_Filter = value;
+  }
+
+  /**
+   * Gets the filter used.
+   *
+   * @return 		the current filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Index of the class attribute. If set to -1, the last attribute is taken as class attribute.";
+  }
+
+  /**
+   * Sets the class index
+   * 
+   * @param value 	the class index
+   */  
+  public void setClassIndex(int value){
+    m_ClassIndex = value;
+  }
+
+  /**
+   * Gets the class index
+   * 
+   * @return 		the index of the class attribute
+   */  
+  public int getClassIndex(){
+    return m_ClassIndex;
+  }
+
+  /**
+   * Gets the filter specification string, which contains the class name of
+   * the filter and any options to the filter
+   *
+   * @return 		the filter string.
+   */
+  protected String getFilterSpec() {
+    Filter c = getFilter();
+    
+    if (c instanceof OptionHandler)
+      return   c.getClass().getName() + " " 
+      	     + Utils.joinOptions(((OptionHandler)c).getOptions());
+    else
+      return c.getClass().getName();
+  }
+
+  /**
+   * Returns default capabilities of the associator.
+   *
+   * @return      the capabilities of this associator
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getFilter() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getFilter().getCapabilities();
+    }
+    
+    result.enable(Capability.NO_CLASS);
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Build the associator on the filtered data.
+   *
+   * @param data 	the training data
+   * @throws Exception	if the Associator could not be built successfully
+   */
+  public void buildAssociations(Instances data) throws Exception {
+    if (m_Associator == null)
+      throw new Exception("No base associator has been set!");
+
+    // create copy and set class-index
+    data = new Instances(data);
+    if (getClassIndex() == 0) {
+      data.setClassIndex(data.numAttributes() - 1);
+    } else {
+      data.setClassIndex(getClassIndex() - 1);
+    }
+    
+    if (getClassIndex() != -1) {
+      // remove instances with missing class
+      data.deleteWithMissingClass();
+    }
+    
+    m_Filter.setInputFormat(data);  // filter capabilities are checked here
+    data = Filter.useFilter(data, m_Filter);
+
+    // can associator handle the data?
+    getAssociator().getCapabilities().testWithFail(data);
+
+    m_FilteredInstances = data.stringFreeStructure();
+    m_Associator.buildAssociations(data);
+  }
+
+  /**
+   * Output a representation of this associator
+   * 
+   * @return 		a representation of this associator
+   */
+  public String toString() {
+    String 	result;
+    
+    if (m_FilteredInstances == null) {
+      result = "FilteredAssociator: No model built yet.";
+    }
+    else {
+      result = "FilteredAssociator using "
+	+ getAssociatorSpec()
+	+ " on data filtered through "
+	+ getFilterSpec()
+	+ "\n\nFiltered Header\n"
+	+ m_FilteredInstances.toString()
+	+ "\n\nAssociator Model\n"
+	+ m_Associator.toString();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5444 $");
+  }
+
+  /**
+   * Main method for running this class.
+   *
+   * @param args 	commandline arguments, use "-h" for full list
+   */
+  public static void main(String[] args) {
+    runAssociator(new FilteredAssociator(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/associations/GeneralizedSequentialPatterns.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/GeneralizedSequentialPatterns.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/GeneralizedSequentialPatterns.java	(revision 29)
@@ -0,0 +1,631 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GeneralizedSequentialPatterns.java
+ * Copyright (C) 2007 Sebastian Beer
+ *
+ */
+
+package weka.associations;
+
+import weka.associations.gsp.Element;
+import weka.associations.gsp.Sequence;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing a GSP algorithm for discovering sequential patterns in a sequential data set.<br/>
+ * The attribute identifying the distinct data sequences contained in the set can be determined by the respective option. Furthermore, the set of output results can be restricted by specifying one or more attributes that have to be contained in each element/itemset of a sequence.<br/>
+ * <br/>
+ * For further information see:<br/>
+ * <br/>
+ * Ramakrishnan Srikant, Rakesh Agrawal (1996). Mining Sequential Patterns: Generalizations and Performance Improvements.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;proceedings{Srikant1996,
+ *    author = {Ramakrishnan Srikant and Rakesh Agrawal},
+ *    booktitle = {Advances in Database Technology EDBT '96},
+ *    publisher = {Springer},
+ *    title = {Mining Sequential Patterns: Generalizations and Performance Improvements},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, algorithm is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;minimum support threshold&gt;
+ *  The miminum support threshold.
+ *  (default: 0.9)</pre>
+ * 
+ * <pre> -I &lt;attribute number representing the data sequence ID
+ *  The attribute number representing the data sequence ID.
+ *  (default: 0)</pre>
+ * 
+ * <pre> -F &lt;attribute numbers used for result filtering
+ *  The attribute numbers used for result filtering.
+ *  (default: -1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Sebastian Beer
+ * @version $Revision: 5444 $
+ */
+public class GeneralizedSequentialPatterns
+  extends AbstractAssociator
+  implements OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4119691320812254676L;
+
+  /** the minimum support threshold */
+  protected double m_MinSupport; 
+
+  /** number indicating the attribute holding the data sequence ID */
+  protected int m_DataSeqID;
+
+  /** original sequential data set to be used for sequential patterns extraction */
+  protected Instances m_OriginalDataSet;
+  
+  /** all generated frequent sequences, i.e. sequential patterns */
+  protected FastVector m_AllSequentialPatterns;
+  
+  /** number of cycles performed until termination */
+  protected int m_Cycles;
+  
+  /** String indicating the starting time of an cycle. */
+  protected String m_CycleStart;
+  
+  /** String indicating the ending time of an cycle. */
+  protected String m_CycleEnd;
+  
+  /** String indicating the starting time of the algorithm. */
+  protected String m_AlgorithmStart;
+  
+  /** String containing the attribute numbers that are used for result 
+   * filtering; -1 means no filtering */
+  protected String m_FilterAttributes;
+  
+  /** Vector containing the attribute numbers that are used for result 
+   * filtering; -1 means no filtering */
+  protected FastVector m_FilterAttrVector;
+  
+  /** Whether the classifier is run in debug mode. */
+  protected boolean m_Debug = false;
+
+  /**
+   * Constructor.
+   */
+  public GeneralizedSequentialPatterns() {
+    resetOptions();
+  }
+
+  /**
+   * Returns global information about the algorithm.
+   * 
+   * @return 			the global information
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing a GSP algorithm for discovering sequential "
+      + "patterns in a sequential data set.\n" 
+      + "The attribute identifying the distinct data sequences contained in "
+      + "the set can be determined by the respective option. Furthermore, the "
+      + "set of output results can be restricted by specifying one or more "
+      + "attributes that have to be contained in each element/itemset of a "
+      + "sequence.\n\n" 
+      + "For further information see:\n\n" 
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns TechnicalInformation about the paper related to the algorithm.
+   * 
+   * @return 			the TechnicalInformation
+   */
+  public TechnicalInformation getTechnicalInformation() {	
+    TechnicalInformation paper = new TechnicalInformation(Type.PROCEEDINGS);
+
+    paper.setValue(Field.AUTHOR, "Ramakrishnan Srikant and Rakesh Agrawal");
+    paper.setValue(Field.TITLE, "Mining Sequential Patterns: Generalizations and Performance Improvements");
+    paper.setValue(Field.BOOKTITLE, "Advances in Database Technology EDBT '96");
+    paper.setValue(Field.YEAR, "1996");
+    paper.setValue(Field.PUBLISHER, "Springer");
+
+    return paper;
+  }
+
+  /**
+   * Returns an enumeration of the available options.
+   * 
+   * @return 			the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tIf set, algorithm is run in debug mode and\n"
+	+ "\tmay output additional info to the console",
+	"D", 0, "-D"));
+    
+    result.addElement(new Option(
+	"\tThe miminum support threshold.\n"
+	+ "\t(default: 0.9)",
+	"S", 1, "-S <minimum support threshold>"));
+    
+    result.addElement(new Option(
+	"\tThe attribute number representing the data sequence ID.\n"
+	+ "\t(default: 0)",
+	"I", 1, "-I <attribute number representing the data sequence ID"));
+
+    result.addElement(new Option(
+	"\tThe attribute numbers used for result filtering.\n"
+	+ "\t(default: -1)",
+	"F", 1, "-F <attribute numbers used for result filtering"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, algorithm is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -S &lt;minimum support threshold&gt;
+   *  The miminum support threshold.
+   *  (default: 0.9)</pre>
+   * 
+   * <pre> -I &lt;attribute number representing the data sequence ID
+   *  The attribute number representing the data sequence ID.
+   *  (default: 0)</pre>
+   * 
+   * <pre> -F &lt;attribute numbers used for result filtering
+   *  The attribute numbers used for result filtering.
+   *  (default: -1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 		the Array containing the options
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+  
+    resetOptions();
+
+    setDebug(Utils.getFlag('D', options));
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setMinSupport(Double.parseDouble(tmpStr));
+
+    tmpStr = Utils.getOption('I', options);
+    if (tmpStr.length() != 0)
+      setDataSeqID(Integer.parseInt(tmpStr));
+
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setFilterAttributes(tmpStr);
+  }
+
+  /**
+   * Returns an Array containing the current options settings.
+   * 
+   * @return 			the Array containing the settings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-S");
+    result.add("" + getMinSupport());
+
+    result.add("-I");
+    result.add("" + getDataSeqID());
+    
+    result.add("-F");
+    result.add(getFilterAttributes());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Resets the algorithm's options to the default values.
+   */
+  protected void resetOptions() {
+    m_MinSupport       = 0.9;
+    m_DataSeqID        = 0;
+    m_FilterAttributes = "-1";
+  }
+
+  /**
+   * Returns the Capabilities of the algorithm.
+   * 
+   * @return 			the Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Extracts all sequential patterns out of a given sequential data set and 
+   * prints out the results.
+   * 
+   * @param data 	the original data set
+   */
+  public void buildAssociations(Instances data) throws Exception {
+    // can associator handle the data?
+    getCapabilities().testWithFail(data);
+
+    m_AllSequentialPatterns = new FastVector();
+    m_Cycles                = 0;
+    m_FilterAttrVector      = new FastVector();
+    m_AlgorithmStart        = getTimeAndDate();
+    m_OriginalDataSet       = new Instances(data);
+    
+    extractFilterAttributes(m_FilterAttributes);
+    findFrequentSequences();
+  }
+
+  /**
+   * Calculates the total number of extracted frequent sequences.
+   * 
+   * @return 			the total number of frequent sequences
+   */
+  protected int calcFreqSequencesTotal() {
+    int total = 0;
+    Enumeration allSeqPatternsEnum = m_AllSequentialPatterns.elements();
+
+    while (allSeqPatternsEnum.hasMoreElements()) {
+      FastVector kSequences = (FastVector) allSeqPatternsEnum.nextElement();
+      total += kSequences.size();
+    }
+
+    return total;
+  }
+
+  /**
+   * Extracts the data sequences out of the original data set according to 
+   * their sequence id attribute, which is removed after extraction.
+   * 
+   * @param originalDataSet 	the original data set
+   * @param dataSeqID		the squence ID to use
+   * @return 			set of distinct data sequences
+   */
+  protected FastVector extractDataSequences (Instances originalDataSet, int dataSeqID) {
+    FastVector dataSequences = new FastVector();
+    int firstInstance = 0;
+    int lastInstance = 0;
+    Attribute seqIDAttribute = originalDataSet.attribute(dataSeqID);
+
+    for (int i = 0; i < seqIDAttribute.numValues(); i++) {
+      double sequenceID = originalDataSet.instance(firstInstance).value(dataSeqID);
+      while (lastInstance < originalDataSet.numInstances()
+	  && sequenceID == originalDataSet.instance(lastInstance).value(dataSeqID)) {
+	lastInstance++;
+      }
+      Instances dataSequence = new Instances(originalDataSet, firstInstance, (lastInstance)-firstInstance);
+      dataSequence.deleteAttributeAt(dataSeqID);
+      dataSequences.addElement(dataSequence);
+      firstInstance = lastInstance;
+    }
+    return dataSequences;
+  }
+
+  /**
+   * Parses a given String containing attribute numbers which are used for 
+   * result filtering.
+   * 
+   * @param attrNumbers 	the String of attribute numbers
+   */
+  public void extractFilterAttributes(String attrNumbers) {
+    String numbers = attrNumbers.trim();
+
+    while (!numbers.equals("")) {
+      int commaLoc = numbers.indexOf(',');
+
+      if (commaLoc != -1) {
+	String number = numbers.substring(0, commaLoc);
+	numbers = numbers.substring(commaLoc + 1).trim();
+	m_FilterAttrVector.addElement(Integer.decode(number));
+      } else {
+	m_FilterAttrVector.addElement(Integer.decode(numbers));
+	break;
+      }
+    }
+  }
+
+  /**
+   * The actual method for extracting frequent sequences.
+   * 
+   * @throws CloneNotSupportedException
+   */
+  protected void findFrequentSequences() throws CloneNotSupportedException {
+    m_CycleStart = getTimeAndDate();
+    Instances originalDataSet = m_OriginalDataSet;
+    FastVector dataSequences = extractDataSequences(m_OriginalDataSet, m_DataSeqID);
+    long minSupportCount = Math.round(m_MinSupport * dataSequences.size());
+    FastVector kMinusOneSequences;
+    FastVector kSequences;
+
+    originalDataSet.deleteAttributeAt(0);
+    FastVector oneElements = Element.getOneElements(originalDataSet);
+    m_Cycles = 1;
+
+    kSequences = Sequence.oneElementsToSequences(oneElements);
+    Sequence.updateSupportCount(kSequences, dataSequences);
+    kSequences = Sequence.deleteInfrequentSequences(kSequences, minSupportCount);
+
+    m_CycleEnd = getTimeAndDate();
+
+    if (kSequences.size() == 0) {
+      return;
+    }
+    while (kSequences.size() > 0) {
+      m_CycleStart = getTimeAndDate();
+
+      m_AllSequentialPatterns.addElement(kSequences.copy());
+      kMinusOneSequences = kSequences;
+      kSequences = Sequence.aprioriGen(kMinusOneSequences);
+      Sequence.updateSupportCount(kSequences, dataSequences);
+      kSequences = Sequence.deleteInfrequentSequences(kSequences, minSupportCount);
+
+      m_CycleEnd = getTimeAndDate();
+      
+      if (getDebug())
+	System.out.println(
+	    "Cycle " + m_Cycles + " from " + m_CycleStart + " to " + m_CycleEnd);
+      
+      m_Cycles++;
+    }
+  }
+
+  /**
+   * Returns the dataSeqID option tip text for the Weka GUI.
+   * 
+   * @return 			the option tip text
+   */
+  public String dataSeqIDTipText() {
+    return "The attribute number representing the data sequence ID.";
+  }
+
+  /**
+   * Returns the attribute representing the data sequence ID.
+   * 
+   * @return 			the data sequence ID
+   */
+  public int getDataSeqID() {
+    return m_DataSeqID;
+  }
+
+  /**
+   * Sets the attribute representing the data sequence ID.
+   * 
+   * @param value 		the data sequence ID to set
+   */
+  public void setDataSeqID(int value) {
+    m_DataSeqID = value;
+  }
+
+  /**
+   * Returns the filterAttributes option tip text for the Weka GUI.
+   * 
+   * @return 			the option tip text
+   */
+  public String filterAttributesTipText() {
+    return 
+        "The attribute numbers (eg \"0, 1\") used for result filtering; only "
+      + "sequences containing the specified attributes in each of their "
+      + "elements/itemsets will be output; -1 prints all.";
+  }
+
+  /**
+   * Returns the String containing the attributes which are used for output 
+   * filtering.
+   * 
+   * @return 			the String containing the attributes
+   */
+  public String getFilterAttributes() {
+    return m_FilterAttributes;
+  }
+
+  /**
+   * Sets the String containing the attributes which are used for output 
+   * filtering.
+   * 
+   * @param value 		the String containing the attributes
+   */
+  public void setFilterAttributes(String value) {
+    m_FilterAttributes = value;
+  }
+
+  /**
+   * Returns the minimum support option tip text for the Weka GUI.
+   * 
+   * @return 			the option tip text
+   */
+  public String minSupportTipText() {
+    return "Minimum support threshold.";
+  }
+
+  /**
+   * Returns the minimum support threshold.
+   * 
+   * @return 			the minimum support threshold
+   */
+  public double getMinSupport() {
+    return m_MinSupport;
+  }
+
+  /**
+   * Sets the minimum support threshold.
+   * 
+   * @param value 		the minimum support threshold
+   */
+  public void setMinSupport(double value) {
+    m_MinSupport = value;
+  }
+
+  /**
+   * Set debugging mode.
+   *
+   * @param value 		true if debug output should be printed
+   */
+  public void setDebug(boolean value) {
+    m_Debug = value;
+  }
+
+  /**
+   * Get whether debugging is turned on.
+   *
+   * @return 			true if debugging output is on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 			tip text for this property suitable for
+   * 				displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "If set to true, algorithm may output additional info to the console.";
+  }
+
+  /**
+   * Returns the current time and date.
+   * 
+   * @return 			the time and date
+   */
+  protected String getTimeAndDate() {
+    SimpleDateFormat	dateFormat;
+    
+    dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    
+    return dateFormat.format(new Date());
+  }
+
+  /**
+   * Returns the time/date string the algorithm was started
+   * 
+   * @return			the time and date the algorithm was started
+   */
+  public String getAlgorithmStart() {
+    return m_AlgorithmStart;
+  }
+
+  /**
+   * Returns the time/date string the cycle was started
+   * 
+   * @return			the time and date the cycle was started
+   */
+  public String getCycleStart() {
+    return m_CycleStart;
+  }
+
+  /**
+   * Returns the time/date string the cycle ended
+   * 
+   * @return			the time and date the cycle ended
+   */
+  public String getCycleEnd() {
+    return m_CycleEnd;
+  }
+  
+  /**
+   * Returns a String containing the result information of the algorithm.
+   * 
+   * @return 			the String containing the result information
+   */
+  public String toString() {
+    StringBuffer result = new StringBuffer();
+
+    result.append("GeneralizedSequentialPatterns\n");
+    result.append("=============================\n\n");
+    result.append("Number of cycles performed: " + (m_Cycles-1) + "\n");
+    result.append("Total number of frequent sequences: " + calcFreqSequencesTotal() + "\n\n");
+    result.append("Frequent Sequences Details (filtered):\n\n");
+    for (int i = 0; i < m_AllSequentialPatterns.size(); i++) {
+      result.append("- " + (i+1) + "-sequences\n\n");
+      FastVector kSequences = (FastVector) m_AllSequentialPatterns.elementAt(i);
+      result.append(Sequence.setOfSequencesToString(kSequences, m_OriginalDataSet, m_FilterAttrVector) + "\n");
+    }
+
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5444 $");
+  }
+
+  /**
+   * Main method.
+   * 
+   * @param args 	commandline options, use -h for help
+   */
+  public static void main(String[] args) {
+    runAssociator(new GeneralizedSequentialPatterns(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/HotSpot.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/HotSpot.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/HotSpot.java	(revision 29)
@@ -0,0 +1,1304 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HotSpot.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import java.util.PriorityQueue;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.io.Serializable;
+import weka.core.Instances;
+import weka.core.Instance;
+import weka.core.Attribute;
+import weka.core.Utils;
+import weka.core.OptionHandler;
+import weka.core.Option;
+import weka.core.SingleIndex;
+import weka.core.Drawable;
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ <!-- globalinfo-start -->
+ * HotSpot learns a set of rules (displayed in a tree-like structure) that maximize/minimize a target variable/value of interest. With a nominal target, one might want to look for segments of the data where there is a high probability of a minority value occuring (given the constraint of a minimum support). For a numeric target, one might be interested in finding segments where this is higher on average than in the whole data set. For example, in a health insurance scenario, find which health insurance groups are at the highest risk (have the highest claim ratio), or, which groups have the highest average insurance payout.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -c &lt;num | first | last&gt;
+ *  The target index. (default = last)</pre>
+ * 
+ * <pre> -V &lt;num | first | last&gt;
+ *  The target value (nominal target only, default = first)</pre>
+ * 
+ * <pre> -L
+ *  Minimize rather than maximize.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Minimum value count (nominal target)/segment size (numeric target).
+ *  Values between 0 and 1 are 
+ *  interpreted as a percentage of 
+ *  the total population; values &gt; 1 are 
+ *  interpreted as an absolute number of 
+ *  instances (default = 0.3)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Maximum branching factor (default = 2)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Minimum improvement in target value in order 
+ *  to add a new branch/test (default = 0.01 (1%))</pre>
+ * 
+ * <pre> -D
+ *  Output debugging info (duplicate rule lookup 
+ *  hash table stats)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 6081 $
+ */
+public class HotSpot
+  implements Associator, OptionHandler, RevisionHandler, 
+             CapabilitiesHandler, Drawable, Serializable {
+
+  static final long serialVersionUID = 42972325096347677L;
+
+  /** index of the target attribute */
+  protected SingleIndex m_targetSI = new SingleIndex("last");
+  protected int m_target;
+  
+  /** Support as a fraction of the total training set */
+  protected double m_support;
+  
+  /** Support as an instance count */
+  private int m_supportCount;
+
+  /** The global value of the attribute of interest (mean or probability) */
+  protected double m_globalTarget;
+
+  /** The minimum improvement necessary to justify adding a test */
+  protected double m_minImprovement;
+
+  /** Actual global support of the target value (discrete target only) */
+  protected int m_globalSupport;
+
+  /** For discrete target, the index of the value of interest */
+  protected SingleIndex m_targetIndexSI = new SingleIndex("first");
+  protected int m_targetIndex;
+
+  /** At each level of the tree consider at most this number extensions */
+  protected int m_maxBranchingFactor;
+
+  /** Number of instances in the full data */
+  protected int m_numInstances;
+
+  /** The head of the tree */
+  protected HotNode m_head;
+
+  /** Header of the training data */
+  protected Instances m_header;
+
+  /** Debugging stuff */
+  protected int m_lookups = 0;
+  protected int m_insertions = 0;
+  protected int m_hits = 0;
+
+  protected boolean m_debug;
+  
+  /** Minimize, rather than maximize the target */
+  protected boolean m_minimize;
+
+  /** Error messages relating to too large/small support values */
+  protected String m_errorMessage;
+
+  /** Rule lookup table */
+  protected HashMap<HotSpotHashKey, String> m_ruleLookup;
+
+  /**
+   * Constructor
+   */
+  public HotSpot() {
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "HotSpot learns a set of rules (displayed in a tree-like structure) "
+      + "that maximize/minimize a target variable/value of interest. "
+      + "With a nominal target, one might want to look for segments of the "
+      + "data where there is a high probability of a minority value occuring ("
+      + "given the constraint of a minimum support). For a numeric target, "
+      + "one might be interested in finding segments where this is higher "
+      + "on average than in the whole data set. For example, in a health "
+      + "insurance scenario, find which health insurance groups are at "
+      + "the highest risk (have the highest claim ratio), or, which groups "
+      + "have the highest average insurance payout.";
+  }
+  
+  /**
+   * Returns default capabilities of HotSpot
+   *
+   * @return      the capabilities of HotSpot
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NO_CLASS);
+    //result.enable(Capability.NUMERIC_CLASS);
+   // result.enable(Capability.NOMINAL_CLASS);
+
+    
+    return result;
+  }
+
+  /**
+   * Hash key class for sets of attribute, value tests
+   */
+  protected class HotSpotHashKey {
+    // split values, one for each attribute (0 indicates att not used).
+    // for nominal indexes, 1 is added so that 0 can indicate not used.
+    protected double[] m_splitValues;
+
+    // 0 = not used, 1 = "<=", 2 = "=", 3 = ">"
+    protected byte[] m_testTypes;
+
+    protected boolean m_computed = false;
+    protected int m_key;
+    
+    public HotSpotHashKey(double[] splitValues, byte[] testTypes) {
+      m_splitValues = splitValues.clone();
+      m_testTypes = testTypes.clone();
+    }
+
+    public boolean equals(Object b) {
+      if ((b == null) || !(b.getClass().equals(this.getClass()))) {
+        return false;
+      }
+      HotSpotHashKey comp = (HotSpotHashKey)b;
+      boolean ok = true;
+      for (int i = 0; i < m_splitValues.length; i++) {
+        if (m_splitValues[i] != comp.m_splitValues[i] ||
+            m_testTypes[i] != comp.m_testTypes[i]) {
+          ok = false;
+          break;
+        }
+      }
+      return ok;
+    }
+
+    public int hashCode() {
+
+      if (m_computed) {
+        return m_key;
+      } else {
+        int hv = 0;
+        for (int i = 0; i < m_splitValues.length; i++) {
+          hv += (m_splitValues[i] * 5 * i);
+          hv += (m_testTypes[i] * i * 3);
+        }
+        m_computed = true;
+
+        m_key = hv;
+      }
+      return m_key;
+    }
+  }
+
+  /**
+   * Build the tree
+   *
+   * @param instances the training instances
+   * @throws Exception if something goes wrong
+   */
+  public void buildAssociations(Instances instances) throws Exception {
+    
+    // can associator handle the data?
+    getCapabilities().testWithFail(instances);
+    
+    m_errorMessage = null;
+    m_targetSI.setUpper(instances.numAttributes() - 1);
+    m_target = m_targetSI.getIndex();
+    Instances inst = new Instances(instances);
+    inst.setClassIndex(m_target);
+    inst.deleteWithMissingClass();
+
+    if (inst.attribute(m_target).isNominal()) {
+      m_targetIndexSI.setUpper(inst.attribute(m_target).numValues() - 1);
+      m_targetIndex = m_targetIndexSI.getIndex();
+    } else {
+      m_targetIndexSI.setUpper(1); // just to stop this SingleIndex from moaning
+    }
+    
+    if (m_support <= 0) {
+      throw new Exception("Support must be greater than zero.");
+    }
+
+    m_numInstances = inst.numInstances();
+    if (m_support >= 1) {
+      m_supportCount = (int)m_support;
+      m_support = m_support / (double)m_numInstances;
+    }
+    m_supportCount = (int)Math.floor((m_support * m_numInstances) + 0.5d);
+    //    m_supportCount = (int)(m_support * m_numInstances);
+    if (m_supportCount < 1) {
+      m_supportCount = 1;
+    }
+
+    m_header = new Instances(inst, 0);
+
+    if (inst.attribute(m_target).isNumeric()) {
+      if (m_supportCount > m_numInstances) {
+        m_errorMessage = "Error: support set to more instances than there are in the data!";
+        return;
+      }
+      m_globalTarget = inst.meanOrMode(m_target);
+    } else {
+      double[] probs = new double[inst.attributeStats(m_target).nominalCounts.length];
+      for (int i = 0; i < probs.length; i++) {
+        probs[i] = (double)inst.attributeStats(m_target).nominalCounts[i];
+      }
+      m_globalSupport = (int)probs[m_targetIndex];
+      // check that global support is greater than min support
+      if (m_globalSupport < m_supportCount) {
+        m_errorMessage = "Error: minimum support " + m_supportCount 
+          + " is too high. Target value " 
+          + m_header.attribute(m_target).value(m_targetIndex) + " has support " 
+          + m_globalSupport + ".";
+      }
+
+      Utils.normalize(probs);
+      m_globalTarget = probs[m_targetIndex];
+      /*      System.err.println("Global target " + m_globalTarget); 
+              System.err.println("Min support count " + m_supportCount);  */
+    }
+    
+    m_ruleLookup = new HashMap<HotSpotHashKey, String>();
+    double[] splitVals = new double[m_header.numAttributes()];
+    byte[] tests = new byte[m_header.numAttributes()];
+
+    m_head = new HotNode(inst, m_globalTarget, splitVals, tests);
+    //    m_head = new HotNode(inst, m_globalTarget);
+  }
+
+  /**
+   * Return the tree as a string
+   *
+   * @return a String containing the tree
+   */
+  public String toString() {
+    StringBuffer buff = new StringBuffer();
+    buff.append("\nHot Spot\n========");
+    if (m_errorMessage != null) {
+      buff.append("\n\n" + m_errorMessage + "\n\n");
+      return buff.toString();
+    }
+    if (m_head == null) {
+      buff.append("No model built!");
+      return buff.toString();
+    }
+    buff.append("\nTotal population: ");
+    buff.append("" + m_numInstances + " instances");
+    buff.append("\nTarget attribute: " + m_header.attribute(m_target).name());
+    if (m_header.attribute(m_target).isNominal()) {
+      buff.append("\nTarget value: " + m_header.attribute(m_target).value(m_targetIndex));
+      buff.append(" [value count in total population: " + m_globalSupport + " instances ("
+                  + Utils.doubleToString((m_globalTarget * 100.0), 2) + "%)]");
+
+      buff.append("\nMinimum value count for segments: ");
+    } else {
+      buff.append("\nMinimum segment size: ");
+    }
+    buff.append("" + m_supportCount + " instances (" 
+                + Utils.doubleToString((m_support * 100.0), 2) 
+                + "% of total population)");
+    buff.append("\nMaximum branching factor: " + m_maxBranchingFactor);
+    buff.append("\nMinimum improvement in target: " 
+                + Utils.doubleToString((m_minImprovement * 100.0), 2) + "%");
+    
+    buff.append("\n\n");
+    buff.append(m_header.attribute(m_target).name());
+    if (m_header.attribute(m_target).isNumeric()) {
+      buff.append(" (" + Utils.doubleToString(m_globalTarget, 4) + ")");
+    } else {
+      buff.append("=" + m_header.attribute(m_target).value(m_targetIndex) + " (");
+      buff.append("" + Utils.doubleToString((m_globalTarget * 100.0), 2) + "% [");
+      buff.append("" + m_globalSupport 
+                  + "/" + m_numInstances + "])");
+    }
+    
+    m_head.dumpTree(0, buff);
+    buff.append("\n");
+    if (m_debug) {
+      buff.append("\n=== Duplicate rule lookup hashtable stats ===\n");
+      buff.append("Insertions: "+ m_insertions);
+      buff.append("\nLookups : "+ m_lookups);
+      buff.append("\nHits: "+ m_hits);
+      buff.append("\n");
+    }
+    return buff.toString();
+  }
+
+  public String graph() throws Exception {
+    System.err.println("Here");
+    m_head.assignIDs(-1);
+
+    StringBuffer text = new StringBuffer();
+    
+    text.append("digraph HotSpot {\n");
+    text.append("rankdir=LR;\n");
+    text.append("N0 [label=\"" 
+                + m_header.attribute(m_target).name());
+    
+    if (m_header.attribute(m_target).isNumeric()) {
+      text.append("\\n(" + Utils.doubleToString(m_globalTarget, 4) + ")");
+    } else {
+      text.append("=" + m_header.attribute(m_target).value(m_targetIndex) + "\\n(");
+      text.append("" + Utils.doubleToString((m_globalTarget * 100.0), 2) + "% [");
+      text.append("" + m_globalSupport 
+                  + "/" + m_numInstances + "])");
+    }
+    text.append("\" shape=plaintext]\n");
+
+    m_head.graphHotSpot(text);
+
+    text.append("}\n");
+    return text.toString();
+  }
+
+  /**
+   * Inner class representing a node/leaf in the tree
+   */
+  protected class HotNode implements Serializable {
+    /**
+     * An inner class holding data on a particular attribute value test
+     */
+    protected class HotTestDetails 
+      implements Comparable<HotTestDetails>,
+                 Serializable {
+      public double m_merit;
+      public int m_support;
+      public int m_subsetSize;
+      public int m_splitAttIndex;
+      public double m_splitValue;
+      public boolean m_lessThan;
+
+      public HotTestDetails(int attIndex, 
+                            double splitVal, 
+                            boolean lessThan,
+                            int support,
+                            int subsetSize,
+                            double merit) {
+        m_merit = merit;
+        m_splitAttIndex = attIndex;
+        m_splitValue = splitVal;
+        m_lessThan = lessThan;
+        m_support = support;
+        m_subsetSize = subsetSize;
+      }
+
+      // reverse order for maximize as PriorityQueue has the least element at the head
+      public int compareTo(HotTestDetails comp) {
+        int result = 0;
+        if (m_minimize) {
+          if (m_merit == comp.m_merit) {
+            // larger support is better
+            if (m_support == comp.m_support) {
+            } else if (m_support > comp.m_support) {
+              result = -1;
+            } else {
+              result = 1;
+            }
+          } else if (m_merit < comp.m_merit) {
+            result = -1;
+          } else {
+            result = 1;
+          }
+        } else {
+          if (m_merit == comp.m_merit) {
+            // larger support is better
+            if (m_support == comp.m_support) {
+            } else if (m_support > comp.m_support) {
+              result = -1;
+            } else {
+              result = 1;
+            }
+          } else if (m_merit < comp.m_merit) {
+            result = 1;
+          } else {
+            result = -1;
+          }
+        }
+        return result;
+      }
+    }
+
+    // the instances at this node
+    protected Instances m_insts;
+
+    // the value (to beat) of the target for these instances
+    protected double m_targetValue;
+
+    // child nodes
+    protected HotNode[] m_children;
+    protected HotTestDetails[] m_testDetails;
+
+    public int m_id;
+
+    /**
+     * Constructor
+     *
+     * @param insts the instances at this node
+     * @param targetValue the target value
+     * @param splitVals the values of attributes split on so far down this branch
+     * @param tests the types of tests corresponding to the split values (<=, =, >)
+     */
+    public HotNode(Instances insts, 
+                   double targetValue, 
+                   double[] splitVals,
+                   byte[] tests) {
+      m_insts = insts;
+      m_targetValue = targetValue;
+      PriorityQueue<HotTestDetails> splitQueue = new PriorityQueue<HotTestDetails>();
+
+      // Consider each attribute
+      for (int i = 0; i < m_insts.numAttributes(); i++) {
+        if (i != m_target) {
+          if (m_insts.attribute(i).isNominal()) {
+            evaluateNominal(i, splitQueue);
+          } else {
+            evaluateNumeric(i, splitQueue);
+          }
+        }
+      }
+
+      if (splitQueue.size() > 0) {
+        int queueSize = splitQueue.size();
+
+        // count how many of the potential children are unique
+        ArrayList<HotTestDetails> newCandidates = new ArrayList<HotTestDetails>();
+        ArrayList<HotSpotHashKey> keyList = new ArrayList<HotSpotHashKey>();
+
+        for (int i = 0; i < queueSize; i++) {
+          if (newCandidates.size() < m_maxBranchingFactor) {
+            HotTestDetails temp = splitQueue.poll();
+            double[] newSplitVals = splitVals.clone();
+            byte[] newTests = tests.clone();
+            newSplitVals[temp.m_splitAttIndex] = temp.m_splitValue + 1;
+            newTests[temp.m_splitAttIndex] = 
+              (m_header.attribute(temp.m_splitAttIndex).isNominal())
+              ? (byte)2 // ==
+              : (temp.m_lessThan)
+              ? (byte)1
+              : (byte)3;
+            HotSpotHashKey key = new HotSpotHashKey(newSplitVals, newTests);
+            m_lookups++;
+            if (!m_ruleLookup.containsKey(key)) {
+              // insert it into the hash table
+              m_ruleLookup.put(key, "");            
+              newCandidates.add(temp);
+              keyList.add(key);
+              m_insertions++;
+            } else {
+              m_hits++;
+            }
+          } else {
+            break;
+          }
+        }
+
+        m_children = new HotNode[(newCandidates.size() < m_maxBranchingFactor)
+                                 ? newCandidates.size()
+                                 : m_maxBranchingFactor];
+        // save the details of the tests at this node
+        m_testDetails = new HotTestDetails[m_children.length];
+        for (int i = 0; i < m_children.length; i++) {
+          m_testDetails[i] = newCandidates.get(i);
+        }
+
+        // save memory
+        splitQueue = null;
+        newCandidates = null;
+        m_insts = new Instances(m_insts, 0);
+
+        // process the children
+        for (int i = 0; i < m_children.length; i++) {
+          Instances subset = subset(insts, m_testDetails[i]);
+          HotSpotHashKey tempKey = keyList.get(i);
+          m_children[i] = new HotNode(subset, m_testDetails[i].m_merit, 
+                                      tempKey.m_splitValues, tempKey.m_testTypes);
+
+        }
+      }
+    }
+
+    /**
+     * Create a subset of instances that correspond to the supplied test details
+     *
+     * @param insts the instances to create the subset from
+     * @param test the details of the split
+     */
+    private Instances subset(Instances insts, HotTestDetails test) {
+      Instances sub = new Instances(insts, insts.numInstances());
+      for (int i = 0; i < insts.numInstances(); i++) {
+        Instance temp = insts.instance(i);
+        if (!temp.isMissing(test.m_splitAttIndex)) {
+          if (insts.attribute(test.m_splitAttIndex).isNominal()) {
+            if (temp.value(test.m_splitAttIndex) == test.m_splitValue) {
+              sub.add(temp);
+            }
+          } else {
+            if (test.m_lessThan) {
+              if (temp.value(test.m_splitAttIndex) <= test.m_splitValue) {
+                sub.add(temp);
+              }
+            } else {
+              if (temp.value(test.m_splitAttIndex) > test.m_splitValue) {
+                sub.add(temp);
+              }
+            }
+          }
+        }
+      }
+      sub.compactify();
+      return sub;
+    }
+
+    /**
+     * Evaluate a numeric attribute for a potential split
+     *
+     * @param attIndex the index of the attribute
+     * @param pq the priority queue of candidtate splits
+     */
+    private void evaluateNumeric(int attIndex, PriorityQueue<HotTestDetails> pq) {
+      Instances tempInsts = m_insts;
+      tempInsts.sort(attIndex);
+      
+      // target sums/counts
+      double targetLeft = 0;
+      double targetRight = 0;
+
+      int numMissing = 0;
+      // count missing values and sum/counts for the initial right subset
+      for (int i = tempInsts.numInstances() - 1; i >= 0; i--) {
+        if (!tempInsts.instance(i).isMissing(attIndex)) {
+          targetRight += (tempInsts.attribute(m_target).isNumeric())
+            ? (tempInsts.instance(i).value(m_target))
+            : ((tempInsts.instance(i).value(m_target) == m_targetIndex)
+               ? 1
+               : 0);
+        } else {
+          numMissing++;
+        }
+      }
+      
+      // are there still enough instances?
+      if (tempInsts.numInstances() - numMissing <= m_supportCount) {
+        return;
+      }
+      
+      double bestMerit = 0.0;
+      double bestSplit = 0.0;
+      double bestSupport = 0.0;
+      double bestSubsetSize = 0;
+      boolean lessThan = true;
+
+      // denominators
+      double leftCount = 0;
+      double rightCount = tempInsts.numInstances() - numMissing;
+            
+      /*      targetRight = (tempInsts.attribute(m_target).isNumeric())
+        ? tempInsts.attributeStats(m_target).numericStats.sum
+        : tempInsts.attributeStats(m_target).nominalCounts[m_targetIndex]; */
+      //      targetRight = tempInsts.attributeStats(attIndexnominalCounts[m_targetIndex];
+
+      // consider all splits
+      for (int i = 0; i < tempInsts.numInstances() - numMissing; i++) {
+        Instance inst = tempInsts.instance(i);
+
+        if (tempInsts.attribute(m_target).isNumeric()) {
+          targetLeft += inst.value(m_target);
+          targetRight -= inst.value(m_target);
+        } else {
+          if ((int)inst.value(m_target) == m_targetIndex) {
+            targetLeft++;
+            targetRight--;
+          }          
+        }
+        leftCount++;
+        rightCount--;
+        
+        // move to the end of any ties
+        if (i < tempInsts.numInstances() - 1 &&
+            inst.value(attIndex) == tempInsts.instance(i + 1).value(attIndex)) {
+          continue;
+        }
+
+        // evaluate split
+        if (tempInsts.attribute(m_target).isNominal()) {
+          if (targetLeft >= m_supportCount) {
+            double delta = (m_minimize) 
+              ? (bestMerit - (targetLeft / leftCount))
+              : ((targetLeft / leftCount) - bestMerit);
+            //            if (targetLeft / leftCount > bestMerit) {
+            if (delta > 0) {
+              bestMerit = targetLeft / leftCount;
+              bestSplit = inst.value(attIndex);
+              bestSupport = targetLeft;
+              bestSubsetSize = leftCount;
+              lessThan = true;
+              //            } else if (targetLeft / leftCount == bestMerit) {
+            } else if (delta == 0) {
+              // break ties in favour of higher support
+              if (targetLeft > bestSupport) {
+                bestMerit = targetLeft / leftCount;
+                bestSplit = inst.value(attIndex);
+                bestSupport = targetLeft;
+                bestSubsetSize = leftCount;
+                lessThan = true;
+              }
+            }
+          }
+
+          if (targetRight >= m_supportCount) {
+            double delta = (m_minimize) 
+              ? (bestMerit - (targetRight / rightCount))
+              : ((targetRight / rightCount) - bestMerit);
+            //            if (targetRight / rightCount > bestMerit) {
+            if (delta > 0) {
+              bestMerit = targetRight / rightCount;
+              bestSplit = inst.value(attIndex);
+              bestSupport = targetRight;
+              bestSubsetSize = rightCount;
+              lessThan = false;
+              //            } else if (targetRight / rightCount == bestMerit) {
+            } else if (delta == 0) {
+              // break ties in favour of higher support
+              if (targetRight > bestSupport) {
+                bestMerit = targetRight / rightCount;
+                bestSplit = inst.value(attIndex);
+                bestSupport = targetRight;
+                bestSubsetSize = rightCount;
+                lessThan = false;
+              }
+            }
+          } 
+        } else {
+          if (leftCount >= m_supportCount) {
+            double delta = (m_minimize) 
+              ? (bestMerit - (targetLeft / leftCount))
+              : ((targetLeft / leftCount) - bestMerit);
+            //            if (targetLeft / leftCount > bestMerit) {
+            if (delta > 0) {
+              bestMerit = targetLeft / leftCount;
+              bestSplit = inst.value(attIndex);
+              bestSupport = leftCount;
+              bestSubsetSize = leftCount;
+              lessThan = true;
+              //            } else if (targetLeft / leftCount == bestMerit) {
+            } else if (delta == 0) {
+              // break ties in favour of higher support
+              if (leftCount > bestSupport) {
+                bestMerit = targetLeft / leftCount;
+                bestSplit = inst.value(attIndex);
+                bestSupport = leftCount;
+                bestSubsetSize = leftCount;
+                lessThan = true;
+              }
+            }
+          }
+
+          if (rightCount >= m_supportCount) {
+            double delta = (m_minimize) 
+              ? (bestMerit - (targetRight / rightCount))
+              : ((targetRight / rightCount) - bestMerit);
+            //            if (targetRight / rightCount > bestMerit) {
+            if (delta > 0) {
+              bestMerit = targetRight / rightCount;
+              bestSplit = inst.value(attIndex);
+              bestSupport = rightCount;
+              bestSubsetSize = rightCount;
+              lessThan = false;
+              //            } else if (targetRight / rightCount == bestMerit) {
+            } else if (delta == 0) {
+              // break ties in favour of higher support
+              if (rightCount > bestSupport) {
+                bestMerit = targetRight / rightCount;
+                bestSplit = inst.value(attIndex);
+                bestSupport = rightCount;
+                bestSubsetSize = rightCount;
+                lessThan = false;
+              }
+            }
+          }          
+        }
+      }
+
+      double delta = (m_minimize)
+        ? m_targetValue - bestMerit
+        : bestMerit - m_targetValue;
+
+      // Have we found a candidate split?
+      if (bestSupport > 0 && (delta / m_targetValue >= m_minImprovement)) {
+        /*        System.err.println("Evaluating " + tempInsts.attribute(attIndex).name());
+        System.err.println("Merit : " + bestMerit);
+        System.err.println("Support : " + bestSupport); */
+        //        double suppFraction = bestSupport / m_numInstances;
+
+        HotTestDetails newD = new HotTestDetails(attIndex, bestSplit, 
+                                                 lessThan, (int)bestSupport, 
+                                                 (int)bestSubsetSize, 
+                                                 bestMerit);
+        pq.add(newD);
+      }
+    }
+
+    /**
+     * Evaluate a nominal attribute for a potential split
+     *
+     * @param attIndex the index of the attribute
+     * @param pq the priority queue of candidtate splits
+     */
+    private void evaluateNominal(int attIndex, PriorityQueue<HotTestDetails> pq) {
+      int[] counts = m_insts.attributeStats(attIndex).nominalCounts;
+      boolean ok = false;
+      // only consider attribute values that result in subsets that meet/exceed min support
+      for (int i = 0; i < m_insts.attribute(attIndex).numValues(); i++) {
+        if (counts[i] >= m_supportCount) {
+          ok = true;
+          break;
+        }
+      }
+      if (ok) {
+        double[] subsetMerit = 
+          new double[m_insts.attribute(attIndex).numValues()];
+
+        for (int i = 0; i < m_insts.numInstances(); i++) {
+          Instance temp = m_insts.instance(i);
+          if (!temp.isMissing(attIndex)) {
+            int attVal = (int)temp.value(attIndex);
+            if (m_insts.attribute(m_target).isNumeric()) {
+              subsetMerit[attVal] += temp.value(m_target);
+            } else {
+              subsetMerit[attVal] += 
+                ((int)temp.value(m_target) == m_targetIndex)
+                ? 1.0
+                : 0;
+            }
+          }
+        }
+        
+        // add to queue if it meets min support and exceeds the merit for the full set
+        for (int i = 0; i < m_insts.attribute(attIndex).numValues(); i++) {
+          // does the subset based on this value have enough instances, and, furthermore,
+          // does the target value (nominal only) occur enough times to exceed min support
+          if (counts[i] >= m_supportCount &&  
+              ((m_insts.attribute(m_target).isNominal())
+              ? (subsetMerit[i] >= m_supportCount) // nominal only test
+               : true)) { 
+            double merit = subsetMerit[i] / counts[i]; //subsetMerit[i][1];
+            double delta = (m_minimize)
+              ? m_targetValue - merit
+              : merit - m_targetValue;
+
+            if (delta / m_targetValue >= m_minImprovement) {
+              double support =
+                (m_insts.attribute(m_target).isNominal())
+                ? subsetMerit[i]
+                : counts[i];
+
+              HotTestDetails newD = new HotTestDetails(attIndex, (double)i, 
+                                                       false, (int)support,
+                                                       counts[i], 
+                                                       merit);
+              pq.add(newD);
+            }
+          }
+        }
+      }
+    }
+
+    public int assignIDs(int lastID) {
+      int currentLastID = lastID + 1;
+      m_id = currentLastID;
+      if (m_children != null) {
+        for (int i = 0; i < m_children.length; i++) {
+          currentLastID = m_children[i].assignIDs(currentLastID);
+        }
+      }
+      return currentLastID;
+    }
+
+    private void addNodeDetails(StringBuffer buff, int i, String spacer) {
+      buff.append(m_header.attribute(m_testDetails[i].m_splitAttIndex).name());
+      if (m_header.attribute(m_testDetails[i].m_splitAttIndex).isNumeric()) {
+        if (m_testDetails[i].m_lessThan) {
+          buff.append(" <= ");
+        } else {
+          buff.append(" > ");
+        }
+        buff.append(Utils.doubleToString(m_testDetails[i].m_splitValue, 4));
+      } else {
+        buff.append(" = " + m_header.
+                    attribute(m_testDetails[i].m_splitAttIndex).
+                    value((int)m_testDetails[i].m_splitValue));
+      }
+
+      if (m_header.attribute(m_target).isNumeric()) {
+        buff.append(spacer + "(" + Utils.doubleToString(m_testDetails[i].m_merit, 4) + " ["
+                    + m_testDetails[i].m_support + "])");
+      } else {
+        buff.append(spacer + "(" + Utils.doubleToString((m_testDetails[i].m_merit * 100.0), 2) + "% ["
+                    + m_testDetails[i].m_support 
+                    + "/" + m_testDetails[i].m_subsetSize + "])");
+      }
+    }
+
+    private void graphHotSpot(StringBuffer text) {
+      if (m_children != null) {
+        for (int i = 0; i < m_children.length; i++) {
+          text.append("N" + m_children[i].m_id);
+          text.append(" [label=\"");
+          addNodeDetails(text, i, "\\n");
+          text.append("\" shape=plaintext]\n");
+          m_children[i].graphHotSpot(text);
+          text.append("N" + m_id + "->" + "N" + m_children[i].m_id + "\n");
+        }
+      }
+    }
+
+    /**
+     * Traverse the tree to create a string description
+     * 
+     * @param depth the depth at this point in the tree
+     * @param buff the string buffer to append node details to
+     */
+    protected void dumpTree(int depth, StringBuffer buff) {
+      if (m_children == null) {
+        //        buff.append("\n");
+      } else {
+        for (int i = 0; i < m_children.length; i++) {
+          buff.append("\n  ");
+          for (int j = 0; j < depth; j++) {
+            buff.append("|   ");
+          }
+          addNodeDetails(buff, i, " ");
+
+          m_children[i].dumpTree(depth + 1, buff);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String targetTipText() {
+    return "The target attribute of interest.";
+  }
+
+  /**
+   * Set the target index
+   *
+   * @param target the target index as a string (1-based)
+   */
+  public void setTarget(String target) {
+    m_targetSI.setSingleIndex(target);
+  }
+
+  /**
+   * Get the target index as a string
+   *
+   * @return the target index (1-based)
+   */
+  public String getTarget() {
+    return m_targetSI.getSingleIndex();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String targetIndexTipText() {
+    return "The value of the target (nominal attributes only) of interest.";
+  }
+
+  /**
+   * For a nominal target, set the index of the value of interest (1-based)
+   *
+   * @param index the index of the nominal value of interest
+   */
+  public void setTargetIndex(String index) {
+    m_targetIndexSI.setSingleIndex(index);
+  }
+
+  /**
+   * For a nominal target, get the index of the value of interest (1-based)
+   *
+   * @return the index of the nominal value of interest
+   */
+  public String getTargetIndex() {
+    return m_targetIndexSI.getSingleIndex();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minimizeTargetTipText() {
+    return "Minimize rather than maximize the target.";
+  }
+
+  /**
+   * Set whether to minimize the target rather than maximize
+   *
+   * @param m true if target is to be minimized
+   */
+  public void setMinimizeTarget(boolean m) {
+    m_minimize = m;
+  }
+
+  /**
+   * Get whether to minimize the target rather than maximize
+   *
+   * @return true if target is to be minimized
+   */
+  public boolean getMinimizeTarget() {
+    return m_minimize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String supportTipText() {
+    return "The minimum support. Values between 0 and 1 are interpreted "
+      + "as a percentage of the total population; values > 1 are "
+      + "interpreted as an absolute number of instances";
+  }
+
+  /**
+   * Get the minimum support
+   *
+   * @return the minimum support
+   */
+  public double getSupport() {
+    return m_support;
+  }
+
+  /**
+   * Set the minimum support
+   *
+   * @param s the minimum support
+   */
+  public void setSupport(double s) {
+    m_support = s;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxBranchingFactorTipText() {
+    return "Maximum branching factor. The maximum number of children "
+      + "to consider extending each node with.";
+  }
+
+  /**
+   * Set the maximum branching factor
+   *
+   * @param b the maximum branching factor
+   */
+  public void setMaxBranchingFactor(int b) {
+    m_maxBranchingFactor = b;
+  }
+
+  /**
+   * Get the maximum branching factor
+   *
+   * @return the maximum branching factor
+   */
+  public int getMaxBranchingFactor() {
+    return m_maxBranchingFactor;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minImprovementTipText() {
+    return "Minimum improvement in target value in order to "
+      + "consider adding a new branch/test";
+  }
+
+  /**
+   * Set the minimum improvement in the target necessary to add a test
+   *
+   * @param i the minimum improvement
+   */
+  public void setMinImprovement(double i) {
+    m_minImprovement = i;
+  }
+
+  /**
+   * Get the minimum improvement in the target necessary to add a test
+   *
+   * @return the minimum improvement
+   */
+  public double getMinImprovement() {
+    return m_minImprovement;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Output debugging info (duplicate rule lookup hash table stats).";
+  }
+
+  /**
+   * Set whether debugging info is output
+   *
+   * @param d true to output debugging info
+   */
+  public void setDebug(boolean d) {
+    m_debug = d;
+  }
+
+  /**
+   * Get whether debugging info is output
+   *
+   * @return true if outputing debugging info
+   */
+  public boolean getDebug() {
+    return m_debug;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector();
+    newVector.addElement(new Option("\tThe target index. (default = last)",
+                                    "c", 1,
+                                    "-c <num | first | last>"));
+    newVector.addElement(new Option("\tThe target value (nominal target only, default = first)",
+                                    "V", 1,
+                                    "-V <num | first | last>"));
+    newVector.addElement(new Option("\tMinimize rather than maximize.", "L", 0, "-L"));
+    newVector.addElement(new Option("\tMinimum value count (nominal target)/segment size "
+                                    + "(numeric target)."
+                                    +"\n\tValues between 0 and 1 are "
+                                    + "\n\tinterpreted as a percentage of "
+                                    + "\n\tthe total population; values > 1 are "
+                                    + "\n\tinterpreted as an absolute number of "
+                                    +"\n\tinstances (default = 0.3)",
+                                    "-S", 1,
+                                    "-S <num>"));
+    newVector.addElement(new Option("\tMaximum branching factor (default = 2)",
+                                    "-M", 1,
+                                    "-M <num>"));
+    newVector.addElement(new Option("\tMinimum improvement in target value in order "
+                                    + "\n\tto add a new branch/test (default = 0.01 (1%))",
+                                    "-I", 1,
+                                    "-I <num>"));
+    newVector.addElement(new Option("\tOutput debugging info (duplicate rule lookup "
+                                    + "\n\thash table stats)", "-D", 0, "-D"));
+    return newVector.elements();
+  }
+
+  /**
+   * Reset options to their defaults
+   */
+  public void resetOptions() {
+    m_support = 0.33;
+    m_minImprovement = 0.01; // 1%
+    m_maxBranchingFactor = 2;
+    m_minimize = false;
+    m_debug = false;
+    setTarget("last");
+    setTargetIndex("first");
+    m_errorMessage = null;
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -c &lt;num | first | last&gt;
+   *  The target index. (default = last)</pre>
+   * 
+   * <pre> -V &lt;num | first | last&gt;
+   *  The target value (nominal target only, default = first)</pre>
+   * 
+   * <pre> -L
+   *  Minimize rather than maximize.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Minimum value count (nominal target)/segment size (numeric target).
+   *  Values between 0 and 1 are 
+   *  interpreted as a percentage of 
+   *  the total population; values &gt; 1 are 
+   *  interpreted as an absolute number of 
+   *  instances (default = 0.3)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Maximum branching factor (default = 2)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Minimum improvement in target value in order 
+   *  to add a new branch/test (default = 0.01 (1%))</pre>
+   * 
+   * <pre> -D
+   *  Output debugging info (duplicate rule lookup 
+   *  hash table stats)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    resetOptions();
+
+    String tempString = Utils.getOption('c', options);
+    if (tempString.length() != 0) {
+      setTarget(tempString);
+    }
+    
+    tempString = Utils.getOption('V', options);
+    if (tempString.length() != 0) {
+      setTargetIndex(tempString);
+    }
+
+    setMinimizeTarget(Utils.getFlag('L', options));
+
+    tempString = Utils.getOption('S', options);
+    if (tempString.length() != 0) {
+      setSupport(Double.parseDouble(tempString));
+    }
+
+    tempString = Utils.getOption('M', options);
+    if (tempString.length() != 0) {
+      setMaxBranchingFactor(Integer.parseInt(tempString));
+    }
+
+    tempString = Utils.getOption('I', options);
+    if (tempString.length() != 0) {
+      setMinImprovement(Double.parseDouble(tempString));
+    }
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of HotSpot.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String[] options = new String[12];
+    int current = 0;
+    
+    options[current++] = "-c"; options[current++] = getTarget();
+    options[current++] = "-V"; options[current++] = getTargetIndex();
+    if (getMinimizeTarget()) {
+      options[current++] = "-L";
+    }
+    options[current++] = "-S"; options[current++] = "" + getSupport();
+    options[current++] = "-M"; options[current++] = "" + getMaxBranchingFactor();
+    options[current++] = "-I"; options[current++] = "" + getMinImprovement();
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6081 $");
+  }
+
+  /**
+   *  Returns the type of graph this scheme
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+    return Drawable.TREE;
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    try {
+      HotSpot h = new HotSpot();
+      AbstractAssociator.runAssociator(new HotSpot(), args);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/associations/ItemSet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/ItemSet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/ItemSet.java	(revision 29)
@@ -0,0 +1,500 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ItemSet.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * Class for storing a set of items. Item sets are stored in a lexicographic
+ * order, which is determined by the header information of the set of instances
+ * used for generating the set of items. All methods in this class assume that
+ * item sets are stored in lexicographic order.
+ * The class provides the general methods used for item sets in class - and 
+ * standard association rule mining.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5130 $
+ */
+public class ItemSet
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2724000045282835791L;
+  
+  /** The items stored as an array of of ints. */
+  protected int[] m_items;
+
+  /** Counter for how many transactions contain this item set. */
+  protected int m_counter;
+
+  /** The total number of transactions */
+  protected int m_totalTransactions;
+  
+  /** 
+   * Treat zeros as missing (rather than a value in their
+   * own right)
+   */
+  protected boolean m_treatZeroAsMissing = false;
+
+  /**
+   * Constructor
+   * @param totalTrans the total number of transactions in the data
+   */
+  public ItemSet(int totalTrans) {
+    m_totalTransactions = totalTrans;
+  }
+
+   /**
+   * Constructor
+   * @param totalTrans the total number of transactions in the data
+   * @param array the attribute values encoded in an int array
+   */
+  public ItemSet(int totalTrans, int[] array){
+       
+       m_totalTransactions = totalTrans;
+       m_items = array;
+       m_counter =1;
+   }
+  
+  /** Contsructor
+   * @param array the item set represented as an int array
+   */  
+  public ItemSet(int[] array){
+       
+       m_items = array;
+       m_counter = 0;
+   }
+
+  /**
+   * Checks if an instance contains an item set.
+   *
+   * @param instance the instance to be tested
+   * @return true if the given instance contains this item set
+   */
+  public boolean containedBy(Instance instance) {
+
+    if (instance instanceof weka.core.SparseInstance && m_treatZeroAsMissing) {
+      int numInstVals = instance.numValues();
+      int numItemSetVals = m_items.length;
+
+      for (int p1 = 0, p2 = 0; p1 < numInstVals || p2 < numItemSetVals; ) {
+        int instIndex = Integer.MAX_VALUE;
+        if (p1 < numInstVals) {
+          instIndex = instance.index(p1);
+        }
+        int itemIndex = p2;
+
+        if (m_items[itemIndex] > -1) {
+          if (itemIndex != instIndex) {
+            return false;
+          } else {
+            if (instance.isMissingSparse(p1)) {
+              return false;
+            }
+            if (m_items[itemIndex] != (int)instance.valueSparse(p1)) {
+              return false;
+            }
+          }
+
+          p1++;
+          p2++;
+        } else {
+          if (itemIndex < instIndex) {
+            p2++;
+          } else if (itemIndex == instIndex){
+            p2++;
+            p1++;
+          }
+        }      
+      }
+    } else {
+      for (int i = 0; i < instance.numAttributes(); i++) 
+        if (m_items[i] > -1) {
+          if (instance.isMissing(i) || 
+              (m_treatZeroAsMissing && (int)instance.value(i) == 0))
+            return false;
+          if (m_items[i] != (int)instance.value(i))
+            return false;
+        }
+    }
+
+    return true;
+  }
+
+  /** Deletes all item sets that don't have minimum support.
+   * @return the reduced set of item sets
+   * @param maxSupport the maximum support
+   * @param itemSets the set of item sets to be pruned
+   * @param minSupport the minimum number of transactions to be covered
+   */
+  public static FastVector deleteItemSets(FastVector itemSets, 
+					  int minSupport,
+					  int maxSupport) {
+
+    FastVector newVector = new FastVector(itemSets.size());
+
+    for (int i = 0; i < itemSets.size(); i++) {
+      ItemSet current = (ItemSet)itemSets.elementAt(i);
+      if ((current.m_counter >= minSupport) 
+	  && (current.m_counter <= maxSupport))
+	newVector.addElement(current);
+    }
+    return newVector;
+  }
+
+  /**
+   * Tests if two item sets are equal.
+   *
+   * @param itemSet another item set
+   * @return true if this item set contains the same items as the given one
+   */
+  public boolean equals(Object itemSet) {
+
+    if ((itemSet == null) || !(itemSet.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    if (m_items.length != ((ItemSet)itemSet).m_items.length)
+      return false;
+    for (int i = 0; i < m_items.length; i++)
+      if (m_items[i] != ((ItemSet)itemSet).m_items[i])
+	return false;
+    return true;
+  }
+
+  /**
+   * Return a hashtable filled with the given item sets.
+   *
+   * @param itemSets the set of item sets to be used for filling the hash table
+   * @param initialSize the initial size of the hashtable
+   * @return the generated hashtable
+   */
+  public static Hashtable getHashtable(FastVector itemSets, int initialSize) {
+
+    Hashtable hashtable = new Hashtable(initialSize);
+
+    for (int i = 0; i < itemSets.size(); i++) {
+      ItemSet current = (ItemSet)itemSets.elementAt(i);
+      hashtable.put(current, new Integer(current.m_counter));
+    }
+    return hashtable;
+  }
+
+  /**
+   * Produces a hash code for a item set.
+   *
+   * @return a hash code for a set of items
+   */
+  public int hashCode() {
+
+    long result = 0;
+
+    for (int i = m_items.length-1; i >= 0; i--)
+      result += (i * m_items[i]);
+    return (int)result;
+  }
+
+  /** Merges all item sets in the set of (k-1)-item sets
+   * to create the (k)-item sets and updates the counters.
+   * @return the generated (k)-item sets
+   * @param totalTrans thetotal number of transactions
+   * @param itemSets the set of (k-1)-item sets
+   * @param size the value of (k-1)
+   */
+  public static FastVector mergeAllItemSets(FastVector itemSets, int size, 
+					    int totalTrans) {
+
+    FastVector newVector = new FastVector();
+    ItemSet result;
+    int numFound, k;
+
+    for (int i = 0; i < itemSets.size(); i++) {
+      ItemSet first = (ItemSet)itemSets.elementAt(i);
+    out:
+      for (int j = i+1; j < itemSets.size(); j++) {
+	ItemSet second = (ItemSet)itemSets.elementAt(j);
+	result = new ItemSet(totalTrans);
+	result.m_items = new int[first.m_items.length];
+        
+	// Find and copy common prefix of size 'size'
+	numFound = 0;
+	k = 0;
+	while (numFound < size) {
+	  if (first.m_items[k] == second.m_items[k]) {
+	    if (first.m_items[k] != -1) 
+	      numFound++;
+	    result.m_items[k] = first.m_items[k];
+	  } else 
+	    break out;
+	  k++;
+	}
+	
+	// Check difference
+	while (k < first.m_items.length) {
+	  if ((first.m_items[k] != -1) && (second.m_items[k] != -1))
+	    break;
+	  else {
+	    if (first.m_items[k] != -1)
+	      result.m_items[k] = first.m_items[k];
+	    else
+	      result.m_items[k] = second.m_items[k];
+	  }
+	  k++;
+	}
+	if (k == first.m_items.length) {
+	  result.m_counter = 0;
+         
+	  newVector.addElement(result);
+	}
+      }
+    }
+    return newVector;
+  }
+
+  /**
+   * Prunes a set of (k)-item sets using the given (k-1)-item sets.
+   *
+   * @param toPrune the set of (k)-item sets to be pruned
+   * @param kMinusOne the (k-1)-item sets to be used for pruning
+   * @return the pruned set of item sets
+   */
+  public static FastVector pruneItemSets(FastVector toPrune, Hashtable kMinusOne) {
+
+    FastVector newVector = new FastVector(toPrune.size());
+    int help, j;
+
+    for (int i = 0; i < toPrune.size(); i++) {
+      ItemSet current = (ItemSet)toPrune.elementAt(i);
+      for (j = 0; j < current.m_items.length; j++)
+	if (current.m_items[j] != -1) {
+	  help = current.m_items[j];
+	  current.m_items[j] = -1;
+	  if (kMinusOne.get(current) == null) {
+	    current.m_items[j] = help;
+	    break;
+	  } else{ 
+	    current.m_items[j] = help;
+          }
+	}
+      if (j == current.m_items.length) 
+	newVector.addElement(current);
+    }
+    return newVector;
+  }
+
+  /**
+   * Prunes a set of rules.
+   *
+   * @param rules a two-dimensional array of lists of item sets. The first list
+   * of item sets contains the premises, the second one the consequences.
+   * @param minConfidence the minimum confidence the rules have to have
+   */
+  public static void pruneRules(FastVector[] rules, double minConfidence) {
+
+    FastVector newPremises = new FastVector(rules[0].size()),
+      newConsequences = new FastVector(rules[1].size()),
+      newConf = new FastVector(rules[2].size());
+
+    for (int i = 0; i < rules[0].size(); i++) 
+      if (!(((Double)rules[2].elementAt(i)).doubleValue() <
+	    minConfidence)) {
+	newPremises.addElement(rules[0].elementAt(i));
+	newConsequences.addElement(rules[1].elementAt(i));
+	newConf.addElement(rules[2].elementAt(i));
+      }
+    rules[0] = newPremises;
+    rules[1] = newConsequences;
+    rules[2] = newConf;
+  }
+
+  /**
+   * Converts the header info of the given set of instances into a set 
+   * of item sets (singletons). The ordering of values in the header file 
+   * determines the lexicographic order.
+   *
+   * @param instances the set of instances whose header info is to be used
+   * @return a set of item sets, each containing a single item
+   * @exception Exception if singletons can't be generated successfully
+   */
+  public static FastVector singletons(Instances instances) throws Exception {
+
+    FastVector setOfItemSets = new FastVector();
+    ItemSet current;
+
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      if (instances.attribute(i).isNumeric())
+	throw new Exception("Can't handle numeric attributes!");
+      for (int j = 0; j < instances.attribute(i).numValues(); j++) {
+	current = new ItemSet(instances.numInstances());
+	current.m_items = new int[instances.numAttributes()];
+	for (int k = 0; k < instances.numAttributes(); k++)
+	  current.m_items[k] = -1;
+	current.m_items[i] = j;
+        
+	setOfItemSets.addElement(current);
+      }
+    }
+    return setOfItemSets;
+  }
+
+  /**
+   * Outputs the support for an item set.
+   *
+   * @return the support
+   */
+  public int support() {
+
+    return m_counter;
+  }
+
+  /**
+   * Returns the contents of an item set as a string.
+   *
+   * @param instances contains the relevant header information
+   * @return string describing the item set
+   */
+  public String toString(Instances instances) {
+
+    StringBuffer text = new StringBuffer();
+
+    for (int i = 0; i < instances.numAttributes(); i++)
+      if (m_items[i] != -1) {
+	text.append(instances.attribute(i).name()+'=');
+	text.append(instances.attribute(i).value(m_items[i])+' ');
+      }
+    text.append(m_counter);
+    return text.toString();
+  }
+
+  /**
+   * Updates counter of item set with respect to given transaction.
+   *
+   * @param instance the instance to be used for ubdating the counter
+   */
+  public void upDateCounter(Instance instance) {
+
+    if (containedBy(instance))
+      m_counter++;
+  }
+
+  /**
+   * Updates counters for a set of item sets and a set of instances.
+   *
+   * @param itemSets the set of item sets which are to be updated
+   * @param instances the instances to be used for updating the counters
+   */
+  public static void upDateCounters(FastVector itemSets, Instances instances) {
+
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Enumeration enu = itemSets.elements();
+      while (enu.hasMoreElements()) 
+	((ItemSet)enu.nextElement()).upDateCounter(instances.instance(i));
+    }
+  }
+
+  /** Gets the counter
+   * @return the counter
+   */  
+  public int counter(){
+      
+      return m_counter;
+  }
+  
+  /** Gest the item set as an int array
+   * @return int array represneting an item set
+   */  
+  public int[] items(){
+      
+      return m_items;
+  }
+  
+  /** Gest the index of the value of the specified attribute
+   * @param k the attribute index
+   * @return the index of the attribute value
+   */  
+  public int itemAt(int k){
+      
+      return m_items[k];
+  }
+  
+  /** Sets the counter
+   * @param count the counter
+   */  
+  public void setCounter(int count){
+      
+      m_counter = count;
+  }
+  
+  /** Sets an item sets
+   * @param items an int array representing an item set
+   */  
+  public void setItem(int[] items){
+      
+      m_items = items;
+  }
+  
+  /** Sets the index of an attribute value
+   * @param value the inex of the attribute value
+   * @param k the index of the attribute
+   */  
+  public void setItemAt(int value, int k){
+      
+      m_items[k] = value;
+  }
+  
+  /**
+   * Sets whether zeros (i.e. the first value of a nominal attribute)
+   * should be treated as missing values.
+   * 
+   * @param z true if zeros should be treated as missing values.
+   */
+  public void setTreatZeroAsMissing(boolean z) {
+    m_treatZeroAsMissing = z;
+  }
+  
+  /**
+   * Gets whether zeros (i.e. the first value of a nominal attribute)
+   * is to be treated int he same way as missing values.
+   * 
+   * @return true if zeros are to be treated like missing values.
+   */
+  public boolean getTreatZeroAsMissing() {
+    return m_treatZeroAsMissing;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5130 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/LabeledItemSet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/LabeledItemSet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/LabeledItemSet.java	(revision 29)
@@ -0,0 +1,406 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LabeledItemSet.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * Class for storing a set of items together with a  class label. Item sets are stored in a lexicographic
+ * order, which is determined by the header information of the set of instances
+ * used for generating the set of items. All methods in this class assume that
+ * item sets are stored in lexicographic order.
+ * The class provides the methods used for item sets in class association rule mining.
+ * Because every item set knows its class label the training set can be splitted up virtually.
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+
+public class LabeledItemSet
+  extends ItemSet
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4158771925518299903L;
+
+    /** The class label. */
+    protected int m_classLabel;
+
+     /** The support of the rule. */
+    protected int m_ruleSupCounter;    
+  
+    /**
+     * Constructor
+     *
+     * @param totalTrans the total number of transactions
+     * @param classLabel the class lebel
+     */    
+    public LabeledItemSet(int totalTrans, int classLabel){
+	
+	super(totalTrans);
+	m_classLabel = classLabel;
+    }
+
+    
+
+    /**
+     * Deletes all item sets that don't have minimum support and have more than maximum support
+     * @return the reduced set of item sets
+     * @param maxSupport the maximum support
+     * @param itemSets the set of item sets to be pruned
+     * @param minSupport the minimum number of transactions to be covered
+     */
+  public static FastVector deleteItemSets(FastVector itemSets, 
+					  int minSupport,
+					  int maxSupport) {
+
+    FastVector newVector = new FastVector(itemSets.size());
+
+    for (int i = 0; i < itemSets.size(); i++) {
+      LabeledItemSet current = (LabeledItemSet)itemSets.elementAt(i);
+      if ((current.m_ruleSupCounter >= minSupport) 
+	  && (current.m_ruleSupCounter <= maxSupport))
+            newVector.addElement(current);
+    }
+    return newVector;
+  }
+
+    /**
+   * Tests if two item sets are equal.
+   *
+   * @param itemSet another item set
+   * @return true if this item set contains the same items as the given one
+   */
+  public final boolean equals(Object itemSet) {
+
+    if (!(this.equalCondset(itemSet)))
+      return false;
+    if(m_classLabel != ((LabeledItemSet)itemSet).m_classLabel)
+	return false;
+    
+    return true;
+  }
+
+  /**
+   * Compares two item sets
+   * @param itemSet an item set
+   * @return true if the the item sets are equal, false otherwise
+   */  
+    public final boolean equalCondset(Object itemSet) {
+
+    if ((itemSet == null) || !(itemSet.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    if (m_items.length != ((ItemSet)itemSet).items().length)
+      return false;
+    for (int i = 0; i < m_items.length; i++)
+      if (m_items[i] != ((ItemSet)itemSet).itemAt(i))
+	return false;
+    return true;
+  }
+
+    /**
+   * Return a hashtable filled with the given item sets.
+   *
+   * @param itemSets the set of item sets to be used for filling the hash table
+   * @param initialSize the initial size of the hashtable
+   * @return the generated hashtable
+   */
+  public static Hashtable getHashtable(FastVector itemSets, int initialSize) {
+
+    Hashtable hashtable = new Hashtable(initialSize);
+    for (int i = 0; i < itemSets.size(); i++) {
+      LabeledItemSet current = (LabeledItemSet)itemSets.elementAt(i);
+      hashtable.put(current, new Integer(current.m_classLabel));
+    }
+   
+    return hashtable;
+  }
+
+
+
+    /**
+     * Merges all item sets in the set of (k-1)-item sets
+     * to create the (k)-item sets and updates the counters.
+     * @return the generated (k)-item sets
+     * @param totalTrans the total number of transactions
+     * @param itemSets the set of (k-1)-item sets
+     * @param size the value of (k-1)
+     */
+  public static FastVector mergeAllItemSets(FastVector itemSets, int size, 
+					    int totalTrans) {
+      
+      FastVector newVector = new FastVector();
+      LabeledItemSet result;
+      int numFound, k;
+      
+      for (int i = 0; i < itemSets.size(); i++) {
+	  LabeledItemSet first = (LabeledItemSet)itemSets.elementAt(i);
+      out:
+	  for (int j = i+1; j < itemSets.size(); j++) {
+	      LabeledItemSet second = (LabeledItemSet)itemSets.elementAt(j);
+	      while(first.m_classLabel != second.m_classLabel){
+		  j++;
+		  if(j == itemSets.size())
+		      break out;
+		  second = (LabeledItemSet)itemSets.elementAt(j);
+	      }
+	      result = new LabeledItemSet(totalTrans,first.m_classLabel);
+	      result.m_items = new int[first.m_items.length];
+	      
+	      // Find and copy common prefix of size 'size'
+	      numFound = 0;
+	      k = 0;
+	      while (numFound < size) {
+		  if (first.m_items[k] == second.m_items[k]) {
+		      if (first.m_items[k] != -1) 
+			  numFound++;
+		      result.m_items[k] = first.m_items[k];
+		  } else 
+		      break out;
+		  k++;
+	      }
+	      
+	      // Check difference
+	      while (k < first.m_items.length) {
+		  if ((first.m_items[k] != -1) && (second.m_items[k] != -1))
+		      break;
+		  else {
+		      if (first.m_items[k] != -1)
+			  result.m_items[k] = first.m_items[k];
+		      else
+			  result.m_items[k] = second.m_items[k];
+		  }
+		  k++;
+	      }
+	      if (k == first.m_items.length) {
+		  result.m_ruleSupCounter = 0;
+		  result.m_counter = 0;
+		  newVector.addElement(result);
+	      }
+	  }
+      }
+      
+    return newVector;
+  }
+    
+  /**
+   * Splits the class attribute away. Depending on the invert flag, the instances without class attribute or only the class attribute of all instances is returned
+   * @param instances the instances
+   * @param invert flag; if true only the class attribute remains, otherweise the class attribute is the only attribute that is deleted.
+   * @throws Exception exception if instances cannot be splitted
+   * @return Instances without the class attribute or instances with only the class attribute
+   */  
+    public static Instances divide(Instances instances, boolean invert) throws Exception{
+    
+	Instances newInstances = new Instances(instances);
+	if(instances.classIndex() < 0)
+            throw new Exception("For class association rule mining a class attribute has to be specified.");
+	if(invert){
+		for(int i=0;i<newInstances.numAttributes();i++){
+		    if(i!=newInstances.classIndex()){
+			newInstances.deleteAttributeAt(i);
+			i--;
+		    }
+		}
+	    return newInstances;
+	}
+	else{
+	    newInstances.setClassIndex(-1);
+            newInstances.deleteAttributeAt(instances.classIndex());
+	    return newInstances;
+	}
+    }
+
+   
+    
+     /**
+      * Converts the header info of the given set of instances into a set
+      * of item sets (singletons). The ordering of values in the header file
+      * determines the lexicographic order. Each item set knows its class label.
+      * @return a set of item sets, each containing a single item
+      * @param instancesNoClass instances without the class attribute
+      * @param classes the values of the class attribute sorted according to instances
+      * @exception Exception if singletons can't be generated successfully
+      */
+    public static FastVector singletons(Instances instancesNoClass, Instances classes) throws Exception {
+
+    FastVector cSet, setOfItemSets = new FastVector();
+    LabeledItemSet current;
+    
+    
+    //make singletons
+    for (int i = 0; i < instancesNoClass.numAttributes(); i++) {
+      if (instancesNoClass.attribute(i).isNumeric())
+	  throw new Exception("Can't handle numeric attributes!");
+      for (int j = 0; j < instancesNoClass.attribute(i).numValues(); j++){
+	  for(int k =0; k < (classes.attribute(0)).numValues(); k++){
+	      current = new LabeledItemSet(instancesNoClass.numInstances(),k);
+	      current.m_items = new int[instancesNoClass.numAttributes()];
+	      for (int l = 0; l < instancesNoClass.numAttributes(); l++)
+		  current.m_items[l] = -1;
+	      current.m_items[i] = j;
+	      setOfItemSets.addElement(current);
+	  }      
+      }
+    }
+    return setOfItemSets;
+  }
+    
+ 
+    
+  
+    /**
+   * Prunes a set of (k)-item sets using the given (k-1)-item sets.
+   *
+   * @param toPrune the set of (k)-item sets to be pruned
+   * @param kMinusOne the (k-1)-item sets to be used for pruning
+   * @return the pruned set of item sets
+   */
+  public static FastVector pruneItemSets(FastVector toPrune, Hashtable kMinusOne){
+
+    FastVector newVector = new FastVector(toPrune.size());
+    int help, j;
+
+   
+    for (int i = 0; i < toPrune.size(); i++) {
+      LabeledItemSet current = (LabeledItemSet)toPrune.elementAt(i);
+      
+      for (j = 0; j < current.m_items.length; j++){
+	      if (current.m_items[j] != -1) {
+		  help = current.m_items[j];
+		  current.m_items[j] = -1;
+		  if(kMinusOne.get(current) != null && (current.m_classLabel == (((Integer)kMinusOne.get(current)).intValue())))
+		      current.m_items[j] = help;
+                  else{
+		      current.m_items[j] = help;
+		      break;
+		  } 
+	  }
+      }
+      if (j == current.m_items.length) 
+	newVector.addElement(current);
+    }
+    return newVector;
+  }
+
+    
+  /**
+   * Outputs the support for an item set.
+   *
+   * @return the support
+   */
+  public final int support() {
+
+    return m_ruleSupCounter;
+  }
+
+  
+  
+     /**
+      * Updates counter of item set with respect to given transaction.
+      * @param instanceNoClass instances without the class attribute
+      * @param instanceClass the values of the class attribute sorted according to instances
+      */
+  public final void upDateCounter(Instance instanceNoClass, Instance instanceClass) {
+
+     
+      if (containedBy(instanceNoClass)){
+	  m_counter++;
+	  if(this.m_classLabel == instanceClass.value(0))
+	      m_ruleSupCounter++;
+      }
+  }
+
+  /**
+   * Updates counter of a specific item set
+   * @param itemSets an item sets
+   * @param instancesNoClass instances without the class attribute
+   * @param instancesClass the values of the class attribute sorted according to instances
+   */  
+   public static void upDateCounters(FastVector itemSets, Instances instancesNoClass, Instances instancesClass){
+
+    for (int i = 0; i < instancesNoClass.numInstances(); i++) {
+        Enumeration enu = itemSets.elements();
+	while (enu.hasMoreElements())
+            ((LabeledItemSet)enu.nextElement()).upDateCounter(instancesNoClass.instance(i),instancesClass.instance(i));
+    }
+
+  }
+
+   /**
+    * Generates rules out of item sets
+    * @param minConfidence the minimum confidence
+    * @param noPrune flag indicating whether the rules are pruned accoridng to the minimum confidence value
+    * @return a set of rules
+    */   
+    public final FastVector[] generateRules(double minConfidence, boolean noPrune) {
+
+	FastVector premises = new FastVector(),consequences = new FastVector(),
+	    conf = new FastVector();
+	FastVector[] rules = new FastVector[3];
+	ItemSet premise, consequence;
+	
+    // Generate all rules with class in the consequence. 
+	premise = new ItemSet(m_totalTransactions);
+	consequence = new ItemSet(m_totalTransactions);
+	int[] premiseItems = new int[m_items.length];
+	int[] consequenceItems = new int[1];
+	System.arraycopy(m_items, 0, premiseItems, 0, m_items.length);
+        consequence.setItem(consequenceItems);
+        premise.setItem(premiseItems);
+	consequence.setItemAt(m_classLabel,0);
+	consequence.setCounter(this.m_ruleSupCounter);
+	premise.setCounter(this.m_counter);
+	premises.addElement(premise);
+	consequences.addElement(consequence);
+	conf.addElement(new Double((double)this.m_ruleSupCounter/(double)this.m_counter));
+	    
+	rules[0] = premises;
+	rules[1] = consequences;
+	rules[2] = conf;
+	if(!noPrune)
+	    pruneRules(rules, minConfidence);
+	
+    
+	return rules;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.5 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/PredictiveApriori.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/PredictiveApriori.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/PredictiveApriori.java	(revision 29)
@@ -0,0 +1,795 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PredictiveApriori.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.TreeSet;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the predictive apriori algorithm to mine association rules.<br/>
+ * It searches with an increasing support threshold for the best 'n' rules concerning a support-based corrected confidence value.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Tobias Scheffer: Finding Association Rules That Trade Support Optimally against Confidence. In: 5th European Conference on Principles of Data Mining and Knowledge Discovery, 424-435, 2001.<br/>
+ * <br/>
+ * The implementation follows the paper expect for adding a rule to the output of the 'n' best rules. A rule is added if:<br/>
+ * the expected predictive accuracy of this rule is among the 'n' best and it is not subsumed by a rule with at least the same expected predictive accuracy (out of an unpublished manuscript from T. Scheffer).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Scheffer2001,
+ *    author = {Tobias Scheffer},
+ *    booktitle = {5th European Conference on Principles of Data Mining and Knowledge Discovery},
+ *    pages = {424-435},
+ *    publisher = {Springer},
+ *    title = {Finding Association Rules That Trade Support Optimally against Confidence},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;required number of rules output&gt;
+ *  The required number of rules. (default = 100)</pre>
+ * 
+ * <pre> -A
+ *  If set class association rules are mined. (default = no)</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ *  The class index. (default = last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5444 $ */
+
+public class PredictiveApriori 
+  extends AbstractAssociator 
+  implements OptionHandler, CARuleMiner, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 8109088846865075341L;
+  
+  /** The minimum support. */
+  protected int m_premiseCount;
+ 
+  /** The maximum number of rules that are output. */
+  protected int m_numRules;
+
+  /** The number of rules created for the prior estimation. */
+  protected static final int m_numRandRules = 1000;
+ 
+  /** The number of intervals used for the prior estimation. */
+  protected static final int m_numIntervals = 100;
+  
+  /** The set of all sets of itemsets. */
+  protected FastVector m_Ls;
+
+  /** The same information stored in hash tables. */
+  protected FastVector m_hashtables;
+
+  /** The list of all generated rules. */
+  protected FastVector[] m_allTheRules;
+
+  /** The instances (transactions) to be used for generating 
+      the association rules. */
+  protected Instances m_instances;
+
+  /** The hashtable containing the prior probabilities. */
+  protected Hashtable m_priors;
+  
+  /** The mid points of the intervals used for the prior estimation. */
+  protected double[] m_midPoints;
+
+  /** The expected predictive accuracy a rule needs to be a candidate for the output. */
+  protected double m_expectation;
+  
+  /** The n best rules. */
+  protected TreeSet m_best;
+  
+  /** Flag keeping track if the list of the n best rules has changed. */
+  protected boolean m_bestChanged;
+  
+  /** Counter for the time of generation for an association rule. */
+  protected int m_count;
+  
+  /** The prior estimator. */
+  protected PriorEstimation m_priorEstimator;
+  
+   /** The class index. */  
+  protected int m_classIndex;
+  
+  /** Flag indicating whether class association rules are mined. */
+  protected boolean m_car;
+
+  /**
+   * Returns a string describing this associator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the predictive apriori algorithm to mine "
+      + "association rules.\n"
+      + "It searches with an increasing support threshold for the best 'n' "
+      + "rules concerning a support-based corrected confidence value.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "The implementation follows the paper expect for adding a rule to the "
+      + "output of the 'n' best rules. A rule is added if:\n"
+      + "the expected predictive accuracy of this rule is among the 'n' best "
+      + "and it is not subsumed by a rule with at least the same expected "
+      + "predictive accuracy (out of an unpublished manuscript from T. "
+      + "Scheffer).";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Tobias Scheffer");
+    result.setValue(Field.TITLE, "Finding Association Rules That Trade Support Optimally against Confidence");
+    result.setValue(Field.BOOKTITLE, "5th European Conference on Principles of Data Mining and Knowledge Discovery");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "424-435");
+    result.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Constructor that allows to sets default values for the 
+   * minimum confidence and the maximum number of rules
+   * the minimum confidence.
+   */
+  public PredictiveApriori() {
+
+    resetOptions();
+  }
+
+  /**
+   * Resets the options to the default values.
+   */
+  public void resetOptions() {
+    
+    m_numRules = 105;
+    m_premiseCount = 1;
+    m_best = new TreeSet();
+    m_bestChanged = false;
+    m_expectation = 0;
+    m_count = 1;
+    m_car = false;
+    m_classIndex = -1;
+    m_priors = new Hashtable();
+    
+    
+   
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Method that generates all large itemsets with a minimum support, and from
+   * these all association rules.
+   *
+   * @param instances the instances to be used for generating the associations
+   * @throws Exception if rules can't be built successfully
+   */
+  public void buildAssociations(Instances instances) throws Exception {
+      
+    int temp = m_premiseCount, exactNumber = m_numRules-5; 
+
+    m_premiseCount = 1;
+    m_best = new TreeSet();
+    m_bestChanged = false;
+    m_expectation = 0;
+    m_count = 1;
+    m_instances = new Instances(instances);
+
+    if (m_classIndex == -1)
+      m_instances.setClassIndex(m_instances.numAttributes()-1);     
+    else if (m_classIndex < m_instances.numAttributes() && m_classIndex >= 0)
+      m_instances.setClassIndex(m_classIndex);
+    else
+      throw new Exception("Invalid class index.");
+    
+    // can associator handle the data?
+    getCapabilities().testWithFail(m_instances);
+    
+    //prior estimation
+    m_priorEstimator = new PriorEstimation(m_instances,m_numRandRules,m_numIntervals,m_car);
+    m_priors = m_priorEstimator.estimatePrior();
+    m_midPoints = m_priorEstimator.getMidPoints();
+    
+    m_Ls = new FastVector();
+    m_hashtables = new FastVector();
+    
+    for(int i =1; i < m_instances.numAttributes();i++){
+      m_bestChanged = false;
+      if(!m_car){
+        // find large item sets
+        findLargeItemSets(i);
+      
+        //find association rules (rule generation procedure)
+        findRulesQuickly();
+      }
+      else{
+        findLargeCarItemSets(i);
+        findCaRulesQuickly();
+      }
+      
+      if(m_bestChanged){
+        temp =m_premiseCount;
+        while(RuleGeneration.expectation(m_premiseCount, m_premiseCount,m_midPoints,m_priors) <= m_expectation){
+            m_premiseCount++; 
+            if(m_premiseCount > m_instances.numInstances())
+                break;
+        }
+      }
+      if(m_premiseCount > m_instances.numInstances()){
+          
+         // Reserve space for variables
+        m_allTheRules = new FastVector[3];
+        m_allTheRules[0] = new FastVector();
+        m_allTheRules[1] = new FastVector();
+        m_allTheRules[2] = new FastVector();
+      
+        int k = 0;
+        while(m_best.size()>0 && exactNumber > 0){
+            m_allTheRules[0].insertElementAt((ItemSet)((RuleItem)m_best.last()).premise(),k);
+            m_allTheRules[1].insertElementAt((ItemSet)((RuleItem)m_best.last()).consequence(),k);
+            m_allTheRules[2].insertElementAt(new Double(((RuleItem)m_best.last()).accuracy()),k);
+            m_best.remove(m_best.last());
+            k++;
+            exactNumber--;
+        }
+        return;    
+      }
+      
+      if(temp != m_premiseCount && m_Ls.size() > 0){
+        FastVector kSets = (FastVector)m_Ls.lastElement();
+        m_Ls.removeElementAt(m_Ls.size()-1);
+        kSets = ItemSet.deleteItemSets(kSets, m_premiseCount,Integer.MAX_VALUE);
+        m_Ls.addElement(kSets);
+      }
+    }
+    
+    // Reserve space for variables
+    m_allTheRules = new FastVector[3];
+    m_allTheRules[0] = new FastVector();
+    m_allTheRules[1] = new FastVector();
+    m_allTheRules[2] = new FastVector();
+      
+    int k = 0;
+    while(m_best.size()>0 && exactNumber > 0){
+        m_allTheRules[0].insertElementAt((ItemSet)((RuleItem)m_best.last()).premise(),k);
+        m_allTheRules[1].insertElementAt((ItemSet)((RuleItem)m_best.last()).consequence(),k);
+        m_allTheRules[2].insertElementAt(new Double(((RuleItem)m_best.last()).accuracy()),k);
+        m_best.remove(m_best.last());
+        k++;
+        exactNumber--;
+    }
+  }
+  
+  /**
+     * Method that mines the n best class association rules.
+     * @return an sorted array of FastVector (depending on the expected predictive accuracy) containing the rules and metric information
+     * @param data the instances for which class association rules should be mined
+     * @throws Exception if rules can't be built successfully
+     */
+    public FastVector[] mineCARs(Instances data) throws Exception{
+	 
+        m_car = true;
+        m_best = new TreeSet();
+        m_premiseCount = 1;
+        m_bestChanged = false;
+        m_expectation = 0;
+        m_count = 1;
+	buildAssociations(data);
+        FastVector[] allCARRules = new FastVector[3];
+        allCARRules[0] = new FastVector();
+        allCARRules[1] = new FastVector();
+        allCARRules[2] = new FastVector();
+        for(int k =0; k < m_allTheRules[0].size();k++){
+            int[] newPremiseArray = new int[m_instances.numAttributes()-1];
+            int help = 0;
+            for(int j = 0;j < m_instances.numAttributes();j++){
+                if(j != m_instances.classIndex()){
+                    newPremiseArray[help] = ((ItemSet)m_allTheRules[0].elementAt(k)).itemAt(j);
+                    help++;
+                }
+            }
+            ItemSet newPremise = new ItemSet(m_instances.numInstances(), newPremiseArray);
+            newPremise.setCounter (((ItemSet)m_allTheRules[0].elementAt(k)).counter());
+            allCARRules[0].addElement(newPremise);
+            int[] newConsArray = new int[1];
+            newConsArray[0] =((ItemSet)m_allTheRules[1].elementAt(k)).itemAt(m_instances.classIndex());
+            ItemSet newCons = new ItemSet(m_instances.numInstances(), newConsArray);
+            newCons.setCounter(((ItemSet)m_allTheRules[1].elementAt(k)).counter());
+            allCARRules[1].addElement(newCons);
+            allCARRules[2].addElement(m_allTheRules[2].elementAt(k));
+        }
+        
+	return allCARRules;
+    }
+    
+    /**
+     * Gets the instances without the class attribute
+     * @return instances without class attribute
+     */    
+    public Instances getInstancesNoClass() {
+      
+      Instances noClass = null;
+      try{
+        noClass = LabeledItemSet.divide(m_instances,false);
+      } 
+      catch(Exception e){
+        e.printStackTrace();
+        System.out.println("\n"+e.getMessage());
+      }
+      //System.out.println(noClass);
+      return noClass;
+  }  
+  
+    /**
+     * Gets the class attribute of all instances
+     * @return Instances containing only the class attribute
+     */    
+  public Instances getInstancesOnlyClass() {
+      
+      Instances onlyClass = null;
+      try{
+        onlyClass = LabeledItemSet.divide(m_instances,true);
+      } 
+      catch(Exception e){
+        e.printStackTrace();
+        System.out.println("\n"+e.getMessage());
+      }
+      return onlyClass;
+      
+  }  
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    String string1 = "\tThe required number of rules. (default = " + (m_numRules-5) + ")",
+      string2 = "\tIf set class association rules are mined. (default = no)",
+      string3 = "\tThe class index. (default = last)";
+    FastVector newVector = new FastVector(3);
+
+    newVector.addElement(new Option(string1, "N", 1, 
+				    "-N <required number of rules output>"));
+    newVector.addElement(new Option(string2, "A", 0,
+				    "-A"));
+    newVector.addElement(new Option(string3, "c", 1,
+				    "-c <the class index>"));
+    return newVector.elements();
+  }
+
+ 
+/**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;required number of rules output&gt;
+   *  The required number of rules. (default = 100)</pre>
+   * 
+   * <pre> -A
+   *  If set class association rules are mined. (default = no)</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   *  The class index. (default = last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    resetOptions();
+    
+    String numRulesString = Utils.getOption('N', options);
+    if (numRulesString.length() != 0) 
+      m_numRules = Integer.parseInt(numRulesString)+5;
+    else
+      m_numRules = Integer.MAX_VALUE;
+
+    String classIndexString = Utils.getOption('c',options);
+    if (classIndexString.length() != 0) 
+      m_classIndex = Integer.parseInt(classIndexString);
+
+    m_car = Utils.getFlag('A', options);
+  }
+
+  /**
+   * Gets the current settings of the PredictiveApriori object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector    	result;
+
+    result = new Vector();
+
+    result.add("-N");
+    result.add("" + (m_numRules-5));
+    
+    if (m_car)
+      result.add("-A");
+    
+    result.add("-c");
+    result.add("" + m_classIndex);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+
+  /**
+   * Outputs the association rules.
+   * 
+   * @return a string representation of the model
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    if (m_allTheRules[0].size() == 0)
+      return "\nNo large itemsets and rules found!\n";
+    text.append("\nPredictiveApriori\n===================\n\n");
+    text.append("\nBest rules found:\n\n");
+    
+    for (int i = 0; i < m_allTheRules[0].size(); i++) {
+	    text.append(Utils.doubleToString((double)i+1, 
+					     (int)(Math.log(m_numRules)/Math.log(10)+1),0)+
+			". " + ((ItemSet)m_allTheRules[0].elementAt(i)).
+			toString(m_instances) 
+			+ " ==> " + ((ItemSet)m_allTheRules[1].elementAt(i)).
+			toString(m_instances) +"    acc:("+  
+			Utils.doubleToString(((Double)m_allTheRules[2].
+					      elementAt(i)).doubleValue(),5)+")");
+      
+      text.append('\n');
+    }
+    
+    
+    return text.toString();
+  }
+
+ 
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numRulesTipText() {
+    return "Number of rules to find.";
+  }
+
+  /**
+   * Get the value of the number of required rules.
+   *
+   * @return Value of the number of required rules.
+   */
+  public int getNumRules() {
+      
+    return m_numRules-5;
+  }
+  
+  /**
+   * Set the value of required rules.
+   *
+   * @param v  Value to assign to number of required rules.
+   */
+  public void setNumRules(int v) {
+	 
+      m_numRules = v+5;
+  }
+  
+    /**
+   * Sets the class index
+   * @param index the index of the class attribute
+   */  
+  public void setClassIndex(int index){
+      
+      m_classIndex = index;
+  }
+  
+  /**
+   * Gets the index of the class attribute
+   * @return the index of the class attribute
+   */  
+  public int getClassIndex(){
+      
+      return m_classIndex;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Index of the class attribute.\n If set to -1, the last attribute will be taken as the class attribute.";
+  }
+
+    /**
+   * Sets class association rule mining
+   * @param flag if class association rules are mined, false otherwise
+   */  
+  public void setCar(boolean flag){
+      
+      m_car = flag;
+  }
+  
+  /**
+   * Gets whether class association ruels are mined
+   * @return true if class association rules are mined, false otherwise
+   */  
+  public boolean getCar(){
+      
+      return m_car;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String carTipText() {
+    return "If enabled class association rules are mined instead of (general) association rules.";
+  }
+  
+    /**
+   * Returns the metric string for the chosen metric type.
+   * Predictive apriori uses the estimated predictive accuracy.
+   * Therefore the metric string is "acc".
+   * @return string "acc"
+   */
+  public String metricString() {
+      
+      return "acc";
+  }
+
+ 
+  /** 
+   * Method that finds all large itemsets for the given set of instances.
+   *
+   * @param index the instances to be used
+   * @throws Exception if an attribute is numeric
+   */
+  private void findLargeItemSets(int index) throws Exception {
+    
+    FastVector kMinusOneSets, kSets = new FastVector();
+    Hashtable hashtable;
+    int i = 0;
+    // Find large itemsets
+    //of length 1
+    if(index == 1){
+        kSets = ItemSet.singletons(m_instances);
+        ItemSet.upDateCounters(kSets, m_instances);
+        kSets = ItemSet.deleteItemSets(kSets, m_premiseCount,Integer.MAX_VALUE);
+        if (kSets.size() == 0)
+            return;
+        m_Ls.addElement(kSets);
+    }
+    //of length > 1
+    if(index >1){
+        if(m_Ls.size() > 0)
+            kSets = (FastVector)m_Ls.lastElement();
+        m_Ls.removeAllElements();
+        i = index-2;
+        kMinusOneSets = kSets;
+        kSets = ItemSet.mergeAllItemSets(kMinusOneSets, i, m_instances.numInstances());
+        hashtable = ItemSet.getHashtable(kMinusOneSets, kMinusOneSets.size());
+        m_hashtables.addElement(hashtable);
+        kSets = ItemSet.pruneItemSets(kSets, hashtable);
+        ItemSet.upDateCounters(kSets, m_instances);
+        kSets = ItemSet.deleteItemSets(kSets, m_premiseCount,Integer.MAX_VALUE);
+        if(kSets.size() == 0)
+            return;
+        m_Ls.addElement(kSets);
+    }
+  } 
+
+
+  
+
+  /** 
+   * Method that finds all association rules.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+  private void findRulesQuickly() throws Exception {
+
+    RuleGeneration currentItemSet;
+    
+    // Build rules
+    for (int j = 0; j < m_Ls.size(); j++) {
+      FastVector currentItemSets = (FastVector)m_Ls.elementAt(j);
+      Enumeration enumItemSets = currentItemSets.elements();
+      while (enumItemSets.hasMoreElements()) { 
+        currentItemSet = new RuleGeneration((ItemSet)enumItemSets.nextElement());
+        m_best = currentItemSet.generateRules(m_numRules-5, m_midPoints,m_priors,m_expectation,
+                                        m_instances,m_best,m_count);
+          
+        m_count = currentItemSet.m_count;
+        if(!m_bestChanged && currentItemSet.m_change)
+           m_bestChanged = true;
+        //update minimum expected predictive accuracy to get into the n best
+        if(m_best.size() >= m_numRules-5)
+            m_expectation = ((RuleItem)m_best.first()).accuracy();
+        else m_expectation =0;
+      }
+    }
+  }
+  
+  
+  /**
+   * Method that finds all large itemsets for class association rule mining for the given set of instances.
+   * @param index the size of the large item sets
+   * @throws Exception if an attribute is numeric
+   */
+  private void findLargeCarItemSets(int index) throws Exception {
+    
+    FastVector kMinusOneSets, kSets = new FastVector();
+    Hashtable hashtable;
+    int i = 0;
+    // Find large itemsets
+    if(index == 1){
+        kSets = CaRuleGeneration.singletons(m_instances);
+        ItemSet.upDateCounters(kSets, m_instances);
+        kSets = ItemSet.deleteItemSets(kSets, m_premiseCount,Integer.MAX_VALUE);
+        if (kSets.size() == 0)
+            return;
+        m_Ls.addElement(kSets);
+    }
+    
+    if(index >1){
+        if(m_Ls.size() > 0)
+            kSets = (FastVector)m_Ls.lastElement();
+        m_Ls.removeAllElements();
+        i = index-2;
+        kMinusOneSets = kSets;
+        kSets = ItemSet.mergeAllItemSets(kMinusOneSets, i, m_instances.numInstances());
+        hashtable = ItemSet.getHashtable(kMinusOneSets, kMinusOneSets.size());
+        m_hashtables.addElement(hashtable);
+        kSets = ItemSet.pruneItemSets(kSets, hashtable);
+        ItemSet.upDateCounters(kSets, m_instances);
+        kSets = ItemSet.deleteItemSets(kSets, m_premiseCount,Integer.MAX_VALUE);
+        if(kSets.size() == 0)
+          return;
+        m_Ls.addElement(kSets);
+    }
+  } 
+  
+  /** 
+   * Method that finds all class association rules.
+   *
+   * @throws Exception if an attribute is numeric
+   */
+  private void findCaRulesQuickly() throws Exception {
+    
+    CaRuleGeneration currentLItemSet;
+    // Build rules
+    for (int j = 0; j < m_Ls.size(); j++) {
+      FastVector currentItemSets = (FastVector)m_Ls.elementAt(j);
+      Enumeration enumItemSets = currentItemSets.elements();
+      while (enumItemSets.hasMoreElements()) {
+        currentLItemSet = new CaRuleGeneration((ItemSet)enumItemSets.nextElement());
+        m_best = currentLItemSet.generateRules(m_numRules-5, m_midPoints,m_priors,m_expectation,
+                                        m_instances,m_best,m_count);
+        m_count = currentLItemSet.count();
+        if(!m_bestChanged && currentLItemSet.change())
+                m_bestChanged = true;
+        if(m_best.size() == m_numRules-5)
+            m_expectation = ((RuleItem)m_best.first()).accuracy();
+        else 
+            m_expectation = 0;
+      }
+    }
+  }
+
+  /**
+   * returns all the rules
+   *
+   * @return		all the rules
+   * @see		#m_allTheRules
+   */
+  public FastVector[] getAllTheRules() {
+    return m_allTheRules;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5444 $");
+  }
+
+  /**
+   * Main method.
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String[] args) {
+    runAssociator(new PredictiveApriori(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/associations/PriorEstimation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/PriorEstimation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/PriorEstimation.java	(revision 29)
@@ -0,0 +1,491 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PriorEstimation.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SpecialFunctions;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.Random;
+
+/**
+ * Class implementing the prior estimattion of the predictive apriori algorithm 
+ * for mining association rules. 
+ *
+ * Reference: T. Scheffer (2001). <i>Finding Association Rules That Trade Support 
+ * Optimally against Confidence</i>. Proc of the 5th European Conf.
+ * on Principles and Practice of Knowledge Discovery in Databases (PKDD'01),
+ * pp. 424-435. Freiburg, Germany: Springer-Verlag. <p>
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $ */
+
+ public class PriorEstimation
+   implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 5570863216522496271L;
+
+    /** The number of rnadom rules. */
+    protected int m_numRandRules;
+    
+    /** The number of intervals. */
+    protected int m_numIntervals;
+    
+    /** The random seed used for the random rule generation step. */
+    protected static final int SEED = 0;
+    
+    /** The maximum number of attributes for which a prior can be estimated. */
+    protected static final int MAX_N = 1024;
+    
+    /** The random number generator. */
+    protected Random m_randNum;
+    
+    /** The instances for which association rules are mined. */
+    protected Instances m_instances;
+    
+    /** Flag indicating whether standard association rules or class association rules are mined. */
+    protected boolean m_CARs;
+    
+    /** Hashtable to store the confidence values of randomly generated rules. */    
+    protected Hashtable m_distribution;
+    
+    /** Hashtable containing the estimated prior probabilities. */
+    protected  Hashtable m_priors;
+    
+    /** Sums up the confidences of all rules with a certain length. */
+    protected double m_sum;
+    
+    /** The mid points of the discrete intervals in which the interval [0,1] is divided. */
+    protected double[] m_midPoints;
+    
+    
+    
+   /**
+   * Constructor 
+   *
+   * @param instances the instances to be used for generating the associations
+   * @param numRules the number of random rules used for generating the prior
+   * @param numIntervals the number of intervals to discretise [0,1]
+   * @param car flag indicating whether standard or class association rules are mined
+   */
+    public PriorEstimation(Instances instances,int numRules,int numIntervals,boolean car) {
+        
+       m_instances = instances;
+       m_CARs = car;
+       m_numRandRules = numRules;
+       m_numIntervals = numIntervals;
+       m_randNum = m_instances.getRandomNumberGenerator(SEED);
+    }
+    /**
+   * Calculates the prior distribution.
+   *
+   * @exception Exception if prior can't be estimated successfully
+   */
+    public final void generateDistribution() throws Exception{
+        
+        boolean jump;
+        int i,maxLength = m_instances.numAttributes(), count =0,count1=0, ruleCounter;
+        int [] itemArray;
+        m_distribution = new Hashtable(maxLength*m_numIntervals);
+        RuleItem current;
+        ItemSet generate;
+        
+        if(m_instances.numAttributes() == 0)
+            throw new Exception("Dataset has no attributes!");
+        if(m_instances.numAttributes() >= MAX_N)
+            throw new Exception("Dataset has to many attributes for prior estimation!");
+        if(m_instances.numInstances() == 0)
+            throw new Exception("Dataset has no instances!");
+        for (int h = 0; h < maxLength; h++) {
+            if (m_instances.attribute(h).isNumeric())
+                throw new Exception("Can't handle numeric attributes!");
+        } 
+        if(m_numIntervals  == 0 || m_numRandRules == 0)
+            throw new Exception("Prior initialisation impossible");
+       
+        //calculate mid points for the intervals
+        midPoints();
+        
+        //create random rules of length i and measure their support and if support >0 their confidence
+        for(i = 1;i <= maxLength; i++){
+            m_sum = 0;
+            int j = 0;
+            count = 0;
+            count1 = 0;
+            while(j < m_numRandRules){
+                count++;
+                jump =false;
+                if(!m_CARs){
+                    itemArray = randomRule(maxLength,i,m_randNum);
+                    current = splitItemSet(m_randNum.nextInt(i), itemArray);
+                }
+                else{
+                    itemArray = randomCARule(maxLength,i,m_randNum);
+                    current = addCons(itemArray);
+                }
+                int [] ruleItem = new int[maxLength];
+                for(int k =0; k < itemArray.length;k++){
+                    if(current.m_premise.m_items[k] != -1)
+                        ruleItem[k] = current.m_premise.m_items[k];
+                    else
+                        if(current.m_consequence.m_items[k] != -1)
+                            ruleItem[k] = current.m_consequence.m_items[k];
+                        else
+                            ruleItem[k] = -1;
+                }
+                ItemSet rule = new ItemSet(ruleItem);
+                updateCounters(rule);
+                ruleCounter = rule.m_counter;
+                if(ruleCounter > 0)
+                    jump =true;
+                updateCounters(current.m_premise);
+                j++;
+                if(jump){
+                    buildDistribution((double)ruleCounter/(double)current.m_premise.m_counter, (double)i);
+                }
+             }
+            
+            //normalize
+            if(m_sum > 0){
+                for(int w = 0; w < m_midPoints.length;w++){
+                    String key = (String.valueOf(m_midPoints[w])).concat(String.valueOf((double)i));
+                    Double oldValue = (Double)m_distribution.remove(key);
+                    if(oldValue == null){
+                        m_distribution.put(key,new Double(1.0/m_numIntervals));
+                        m_sum += 1.0/m_numIntervals;
+                    }
+                    else
+                        m_distribution.put(key,oldValue);
+                }
+                for(int w = 0; w < m_midPoints.length;w++){
+                    double conf =0;
+                    String key = (String.valueOf(m_midPoints[w])).concat(String.valueOf((double)i));
+                    Double oldValue = (Double)m_distribution.remove(key);
+                    if(oldValue != null){
+                        conf = oldValue.doubleValue() / m_sum;
+                        m_distribution.put(key,new Double(conf));
+                    }
+                }
+            }
+            else{
+                for(int w = 0; w < m_midPoints.length;w++){
+                    String key = (String.valueOf(m_midPoints[w])).concat(String.valueOf((double)i));
+                    m_distribution.put(key,new Double(1.0/m_numIntervals));
+                }
+            }
+        }
+        
+    }
+    
+    /**
+     * Constructs an item set of certain length randomly.
+     * This method is used for standard association rule mining.
+     * @param maxLength the number of attributes of the instances
+     * @param actualLength the number of attributes that should be present in the item set
+     * @param randNum the random number generator
+     * @return a randomly constructed item set in form of an int array
+     */
+    public final int[] randomRule(int maxLength, int actualLength, Random randNum){
+     
+        int[] itemArray = new int[maxLength];
+        for(int k =0;k < itemArray.length;k++)
+            itemArray[k] = -1;
+        int help =actualLength;
+        if(help == maxLength){
+            help = 0;
+            for(int h = 0; h < itemArray.length; h++){
+                itemArray[h] = m_randNum.nextInt((m_instances.attribute(h)).numValues());
+            }
+        }
+        while(help > 0){
+            int mark = randNum.nextInt(maxLength);
+            if(itemArray[mark] == -1){
+                help--;
+                itemArray[mark] = m_randNum.nextInt((m_instances.attribute(mark)).numValues());
+            }
+       }
+        return itemArray;
+    }
+    
+    
+    /**
+     * Constructs an item set of certain length randomly.
+     * This method is used for class association rule mining.
+     * @param maxLength the number of attributes of the instances
+     * @param actualLength the number of attributes that should be present in the item set
+     * @param randNum the random number generator
+     * @return a randomly constructed item set in form of an int array
+     */
+     public final int[] randomCARule(int maxLength, int actualLength, Random randNum){
+     
+        int[] itemArray = new int[maxLength];
+        for(int k =0;k < itemArray.length;k++)
+            itemArray[k] = -1;
+        if(actualLength == 1)
+            return itemArray;
+        int help =actualLength-1;
+        if(help == maxLength-1){
+            help = 0;
+            for(int h = 0; h < itemArray.length; h++){
+                if(h != m_instances.classIndex()){
+                    itemArray[h] = m_randNum.nextInt((m_instances.attribute(h)).numValues());
+                }
+            }
+        }
+        while(help > 0){
+            int mark = randNum.nextInt(maxLength);
+            if(itemArray[mark] == -1 && mark != m_instances.classIndex()){
+                help--;
+                itemArray[mark] = m_randNum.nextInt((m_instances.attribute(mark)).numValues());
+            }
+       }
+        return itemArray;
+    }
+   
+     /**
+      * updates the distribution of the confidence values.
+      * For every confidence value the interval to which it belongs is searched
+      * and the confidence is added to the confidence already found in this
+      * interval.
+      * @param conf the confidence of the randomly created rule
+      * @param length the legnth of the randomly created rule
+      */     
+    public final void buildDistribution(double conf, double length){
+     
+        double mPoint = findIntervall(conf);
+        String key = (String.valueOf(mPoint)).concat(String.valueOf(length));
+        m_sum += conf;
+        Double oldValue = (Double)m_distribution.remove(key);
+        if(oldValue != null)
+            conf = conf + oldValue.doubleValue();
+        m_distribution.put(key,new Double(conf));
+        
+    }
+    
+    /**
+     * searches the mid point of the interval a given confidence value falls into
+     * @param conf the confidence of a rule
+     * @return the mid point of the interval the confidence belongs to
+     */    
+     public final double findIntervall(double conf){
+        
+        if(conf == 1.0)
+            return m_midPoints[m_midPoints.length-1];
+        int end   = m_midPoints.length-1;
+        int start = 0;
+        while (Math.abs(end-start) > 1) {
+            int mid = (start + end) / 2;
+            if (conf > m_midPoints[mid])
+                start = mid+1;
+            if (conf < m_midPoints[mid]) 
+                end = mid-1;
+            if(conf == m_midPoints[mid])
+                return m_midPoints[mid];
+        }
+        if(Math.abs(conf-m_midPoints[start]) <=  Math.abs(conf-m_midPoints[end]))
+            return m_midPoints[start];
+        else
+            return m_midPoints[end];
+    }
+    
+    
+     /**
+      * calculates the numerator and the denominator of the prior equation
+      * @param weighted indicates whether the numerator or the denominator is calculated
+      * @param mPoint the mid Point of an interval
+      * @return the numerator or denominator of the prior equation
+      */     
+    public final double calculatePriorSum(boolean weighted, double mPoint){
+  
+      double distr, sum =0, max = logbinomialCoefficient(m_instances.numAttributes(),(int)m_instances.numAttributes()/2);
+      
+      
+      for(int i = 1; i <= m_instances.numAttributes(); i++){
+              
+          if(weighted){
+            String key = (String.valueOf(mPoint)).concat(String.valueOf((double)i));
+            Double hashValue = (Double)m_distribution.get(key);
+            
+            if(hashValue !=null)
+                distr = hashValue.doubleValue();
+            else
+                distr = 0;
+                //distr = 1.0/m_numIntervals;
+            if(distr != 0){
+              double addend = Utils.log2(distr) - max + Utils.log2((Math.pow(2,i)-1)) + logbinomialCoefficient(m_instances.numAttributes(),i);
+              sum = sum + Math.pow(2,addend);
+            }
+          }
+          else{
+              double addend = Utils.log2((Math.pow(2,i)-1)) - max + logbinomialCoefficient(m_instances.numAttributes(),i);
+              sum = sum + Math.pow(2,addend);
+          }
+      }
+      return sum;
+  }
+    /**
+     * Method that calculates the base 2 logarithm of a binomial coefficient
+     * @param upperIndex upper Inedx of the binomial coefficient
+     * @param lowerIndex lower index of the binomial coefficient
+     * @return the base 2 logarithm of the binomial coefficient
+     */    
+   public static final double logbinomialCoefficient(int upperIndex, int lowerIndex){
+   
+     double result =1.0;
+     if(upperIndex == lowerIndex || lowerIndex == 0)
+         return result;
+     result = SpecialFunctions.log2Binomial((double)upperIndex, (double)lowerIndex);
+     return result;
+   }
+   
+   /**
+    * Method to estimate the prior probabilities
+    * @throws Exception throws exception if the prior cannot be calculated
+    * @return a hashtable containing the prior probabilities
+    */   
+   public final Hashtable estimatePrior() throws Exception{
+   
+       double distr, prior, denominator, mPoint;
+       
+       Hashtable m_priors = new Hashtable(m_numIntervals);
+       denominator = calculatePriorSum(false,1.0);
+       generateDistribution();
+       for(int i = 0; i < m_numIntervals; i++){ 
+            mPoint = m_midPoints[i];
+            prior = calculatePriorSum(true,mPoint) / denominator;
+            m_priors.put(new Double(mPoint), new Double(prior));
+       }
+       return m_priors;
+   }  
+   
+   /**
+    * split the interval [0,1] into a predefined number of intervals and calculates their mid points
+    */   
+   public final void midPoints(){
+        
+        m_midPoints = new double[m_numIntervals];
+        for(int i = 0; i < m_numIntervals; i++)
+            m_midPoints[i] = midPoint(1.0/m_numIntervals, i);
+   }
+     
+   /**
+    * calculates the mid point of an interval
+    * @param size the size of each interval
+    * @param number the number of the interval.
+    * The intervals are numbered from 0 to m_numIntervals.
+    * @return the mid point of the interval
+    */   
+   public double midPoint(double size, int number){
+    
+       return (size * (double)number) + (size / 2.0);
+   }
+    
+   /**
+    * returns an ordered array of all mid points
+    * @return an ordered array of doubles conatining all midpoints
+    */   
+   public final double[] getMidPoints(){
+    
+       return m_midPoints;
+   }
+   
+   
+   /**
+    * splits an item set into premise and consequence and constructs therefore
+    * an association rule. The length of the premise is given. The attributes
+    * for premise and consequence are chosen randomly. The result is a RuleItem.
+    * @param premiseLength the length of the premise
+    * @param itemArray a (randomly generated) item set
+    * @return a randomly generated association rule stored in a RuleItem
+    */   
+    public final RuleItem splitItemSet (int premiseLength, int[] itemArray){
+        
+       int[] cons = new int[m_instances.numAttributes()];
+       System.arraycopy(itemArray, 0, cons, 0, itemArray.length);
+       int help = premiseLength;
+       while(help > 0){
+            int mark = m_randNum.nextInt(itemArray.length);
+            if(cons[mark] != -1){
+                help--;
+                cons[mark] =-1;
+            }
+       }
+       if(premiseLength == 0)
+            for(int i =0; i < itemArray.length;i++)
+                itemArray[i] = -1;
+       else
+           for(int i =0; i < itemArray.length;i++)
+               if(cons[i] != -1)
+                    itemArray[i] = -1;
+       ItemSet premise = new ItemSet(itemArray);
+       ItemSet consequence = new ItemSet(cons);
+       RuleItem current = new RuleItem();
+       current.m_premise = premise;
+       current.m_consequence = consequence;
+       return current;
+    }
+
+    /**
+     * generates a class association rule out of a given premise.
+     * It randomly chooses a class label as consequence.
+     * @param itemArray the (randomly constructed) premise of the class association rule
+     * @return a class association rule stored in a RuleItem
+     */    
+    public final RuleItem addCons (int[] itemArray){
+        
+        ItemSet premise = new ItemSet(itemArray);
+        int[] cons = new int[itemArray.length];
+        for(int i =0;i < itemArray.length;i++)
+            cons[i] = -1;
+        cons[m_instances.classIndex()] = m_randNum.nextInt((m_instances.attribute(m_instances.classIndex())).numValues());
+        ItemSet consequence = new ItemSet(cons);
+        RuleItem current = new RuleItem();
+        current.m_premise = premise;
+        current.m_consequence = consequence;
+        return current;
+    }
+    
+    /**
+     * updates the support count of an item set
+     * @param itemSet the item set
+     */    
+    public final void updateCounters(ItemSet itemSet){
+        
+        for (int i = 0; i < m_instances.numInstances(); i++) 
+            itemSet.upDateCounter(m_instances.instance(i));
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.7 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/RuleGeneration.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/RuleGeneration.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/RuleGeneration.java	(revision 29)
@@ -0,0 +1,381 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RuleGeneration.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.TreeSet;
+
+/**
+ * Class implementing the rule generation procedure of the predictive apriori algorithm.
+ *
+ * Reference: T. Scheffer (2001). <i>Finding Association Rules That Trade Support 
+ * Optimally against Confidence</i>. Proc of the 5th European Conf.
+ * on Principles and Practice of Knowledge Discovery in Databases (PKDD'01),
+ * pp. 424-435. Freiburg, Germany: Springer-Verlag. <p>
+ *
+ * The implementation follows the paper expect for adding a rule to the output of the
+ * <i>n</i> best rules. A rule is added if:
+ * the expected predictive accuracy of this rule is among the <i>n</i> best and it is 
+ * not subsumed by a rule with at least the same expected predictive accuracy
+ * (out of an unpublished manuscript from T. Scheffer). 
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $ */
+public class RuleGeneration
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8927041669872491432L;
+
+  /** The items stored as an array of of integer. */
+  protected int[] m_items;
+
+  /** Counter for how many transactions contain this item set. */
+  protected int m_counter;
+
+  /** The total number of transactions */
+  protected int m_totalTransactions;
+
+  /** Flag indicating whether the list fo the best rules has changed. */
+  protected boolean m_change = false;
+
+  /** The minimum expected predictive accuracy that is needed to be a candidate for the list of the best rules. */
+  protected double m_expectation;
+
+  /** Threshold. If the support of the premise is higher the binomial distrubution is approximated by a normal one. */
+  protected static final int MAX_N = 300;
+
+  /** The minimum support a rule needs to be a candidate for the list of the best rules. */
+  protected int m_minRuleCount;
+
+  /** Sorted array of the mied points of the intervals used for prior estimation. */
+  protected double[] m_midPoints;
+
+  /** Hashtable conatining the estimated prior probabilities. */
+  protected Hashtable m_priors;
+
+  /** The list of the actual <i>n</i> best rules. */
+  protected TreeSet m_best;
+
+  /** Integer indicating the generation time of a rule. */
+  protected int m_count;
+
+  /** The instances. */
+  protected Instances m_instances;
+
+
+  /**
+   * Constructor
+   * @param itemSet item set for that rules should be generated.
+   * The item set will form the premise of the rules.
+   */
+  public RuleGeneration(ItemSet itemSet){
+
+    m_totalTransactions = itemSet.m_totalTransactions;
+    m_counter = itemSet.m_counter;
+    m_items = itemSet.m_items;
+  }
+
+
+  /**
+   * calculates the probability using a binomial distribution.
+   * If the support of the premise is too large this distribution
+   * is approximated by a normal distribution.
+   * @param accuracy the accuracy value
+   * @param ruleCount the support of the whole rule
+   * @param premiseCount the support of the premise
+   * @return the probability value
+   */  
+  public static final double binomialDistribution(double accuracy, double ruleCount, double premiseCount){
+
+    double mu, sigma;
+
+    if(premiseCount < MAX_N) 
+      return Math.pow(2,(Utils.log2(Math.pow(accuracy,ruleCount))+Utils.log2(Math.pow((1.0-accuracy),(premiseCount-ruleCount)))+PriorEstimation.logbinomialCoefficient((int)premiseCount,(int)ruleCount)));
+    else{
+      mu = premiseCount * accuracy;
+      sigma = Math.sqrt((premiseCount * (1.0 - accuracy))*accuracy);
+      return Statistics.normalProbability(((ruleCount+0.5)-mu)/(sigma*Math.sqrt(2)));
+    }
+  }
+
+  /**
+   * calculates the expected predctive accuracy of a rule
+   * @param ruleCount the support of the rule
+   * @param premiseCount the premise support of the rule
+   * @param midPoints array with all mid points
+   * @param priors hashtable containing the prior probabilities
+   * @return the expected predictive accuracy
+   */  
+  public static final double expectation(double ruleCount, int premiseCount,double[] midPoints, Hashtable priors){
+
+    double numerator = 0, denominator = 0;
+    for(int i = 0;i < midPoints.length; i++){
+      Double actualPrior = (Double)priors.get(new Double(midPoints[i]));
+      if(actualPrior != null){
+	if(actualPrior.doubleValue() != 0){
+	  double addend = actualPrior.doubleValue() * binomialDistribution(midPoints[i], ruleCount, (double)premiseCount);
+	  denominator += addend;
+	  numerator += addend*midPoints[i];
+	}
+      }
+    }
+    if(denominator <= 0 || Double.isNaN(denominator))
+      System.out.println("RuleItem denominator: "+denominator);
+    if(numerator <= 0 || Double.isNaN(numerator))
+      System.out.println("RuleItem numerator: "+numerator);
+    return numerator/denominator;
+  }
+
+  /**
+   * Generates all rules for an item set. The item set is the premise.
+   * @param numRules the number of association rules the use wants to mine.
+   * This number equals the size <i>n</i> of the list of the
+   * best rules.
+   * @param midPoints the mid points of the intervals
+   * @param priors Hashtable that contains the prior probabilities
+   * @param expectation the minimum value of the expected predictive accuracy
+   * that is needed to get into the list of the best rules
+   * @param instances the instances for which association rules are generated
+   * @param best the list of the <i>n</i> best rules.
+   * The list is implemented as a TreeSet
+   * @param genTime the maximum time of generation
+   * @return all the rules with minimum confidence for the given item set
+   */
+  public TreeSet generateRules(int numRules, double[] midPoints, Hashtable priors, double expectation, Instances instances,TreeSet best,int genTime) {
+
+    boolean redundant = false;
+    FastVector consequences = new FastVector(), consequencesMinusOne = new FastVector();
+    ItemSet premise;
+    int s = 0;
+    RuleItem current = null, old;
+
+    Hashtable hashtable;
+
+    m_change = false;
+    m_midPoints = midPoints;
+    m_priors = priors;
+    m_best = best;
+    m_expectation = expectation;
+    m_count = genTime;
+    m_instances = instances;
+
+    //create rule body
+    premise =null;
+    premise = new ItemSet(m_totalTransactions);
+    premise.m_items = new int[m_items.length];
+    System.arraycopy(m_items, 0, premise.m_items, 0, m_items.length);
+    premise.m_counter = m_counter;
+
+
+    do{  
+      m_minRuleCount = 1;
+      while(expectation((double)m_minRuleCount,premise.m_counter,m_midPoints,m_priors) <= m_expectation){
+	m_minRuleCount++;
+	if(m_minRuleCount > premise.m_counter)
+	  return m_best;
+      }
+      redundant = false;
+      for(int i = 0; i < instances.numAttributes();i++){        
+	if(i == 0){    
+	  for(int j = 0; j < m_items.length;j++)
+	    if(m_items[j] == -1)
+	      consequences = singleConsequence(instances, j,consequences);           
+	  if(premise == null || consequences.size() == 0)
+	    return m_best;
+	}
+	FastVector allRuleItems = new FastVector();
+	int index = 0;
+	do {
+	  int h = 0;
+	  while(h < consequences.size()){
+	    RuleItem dummie = new RuleItem();
+	    current = dummie.generateRuleItem(premise,(ItemSet)consequences.elementAt(h),instances,m_count,m_minRuleCount,m_midPoints,m_priors);
+	    if(current != null){
+	      allRuleItems.addElement(current);
+	      h++;
+	    }
+	    else
+	      consequences.removeElementAt(h);
+	  }
+	  if(index == i)
+	    break;
+	  consequencesMinusOne = consequences;
+	  consequences = ItemSet.mergeAllItemSets(consequencesMinusOne, index, instances.numInstances());
+	  hashtable = ItemSet.getHashtable(consequencesMinusOne, consequencesMinusOne.size());
+	  consequences = ItemSet.pruneItemSets(consequences, hashtable);
+	  index++;
+	} while (consequences.size() > 0); 
+	for(int h = 0;h < allRuleItems.size();h++){
+	  current = (RuleItem)allRuleItems.elementAt(h);
+	  m_count++;
+	  if(m_best.size() < numRules){
+	    m_change =true;
+	    redundant = removeRedundant(current);
+	  }
+	  else{
+	    if(current.accuracy() > m_expectation){
+	      m_expectation = ((RuleItem)(m_best.first())).accuracy();
+	      boolean remove = m_best.remove(m_best.first());
+	      m_change = true;
+	      redundant = removeRedundant(current);
+	      m_expectation = ((RuleItem)(m_best.first())).accuracy();
+	      while(expectation((double)m_minRuleCount, (current.premise()).m_counter,m_midPoints,m_priors) < m_expectation){
+		m_minRuleCount++;
+		if(m_minRuleCount > (current.premise()).m_counter)
+		  break;
+	      }
+	    }  
+	  }
+	}   
+
+      }
+    }while(redundant); 
+    return m_best;
+  }
+
+  /**
+   * Methods that decides whether or not rule a subsumes rule b.
+   * The defintion of subsumption is:
+   * Rule a subsumes rule b, if a subsumes b
+   * AND
+   * a has got least the same expected predictive accuracy as b.
+   * @param a an association rule stored as a RuleItem
+   * @param b an association rule stored as a RuleItem
+   * @return true if rule a subsumes rule b or false otherwise.
+   */  
+  public static boolean aSubsumesB(RuleItem a, RuleItem b){
+
+    if(a.m_accuracy < b.m_accuracy)
+      return false;
+    for(int k = 0; k < a.premise().m_items.length;k++){
+      if(a.premise().m_items[k] != b.premise().m_items[k]){
+	if((a.premise().m_items[k] != -1 && b.premise().m_items[k] != -1) || b.premise().m_items[k] == -1)
+	  return false;
+      }
+      if(a.consequence().m_items[k] != b.consequence().m_items[k]){
+	if((a.consequence().m_items[k] != -1 && b.consequence().m_items[k] != -1) || a.consequence().m_items[k] == -1)
+	  return false;
+      }
+    }
+    return true;
+
+  }
+
+  /**
+   * generates a consequence of length 1 for an association rule.
+   * @param instances the instances under consideration
+   * @param attNum an item that does not occur in the premise
+   * @param consequences FastVector that possibly already contains other consequences of length 1
+   * @return FastVector with consequences of length 1
+   */  
+  public static FastVector singleConsequence(Instances instances, int attNum, FastVector consequences){
+
+    ItemSet consequence;
+
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      if( i == attNum){
+	for (int j = 0; j < instances.attribute(i).numValues(); j++) {
+	  consequence = new ItemSet(instances.numInstances());
+	  consequence.m_items = new int[instances.numAttributes()];
+	  for (int k = 0; k < instances.numAttributes(); k++) 
+	    consequence.m_items[k] = -1;
+	  consequence.m_items[i] = j;
+	  consequences.addElement(consequence);
+	}
+      }
+    }
+    return consequences;
+
+  }
+
+  /**
+   * Method that removes redundant rules out of the list of the best rules.
+   * A rule is in that list if:
+   * the expected predictive accuracy of this rule is among the best and it is
+   * not subsumed by a rule with at least the same expected predictive accuracy
+   * @param toInsert the rule that should be inserted into the list
+   * @return true if the method has changed the list, false otherwise
+   */  
+  public boolean removeRedundant(RuleItem toInsert){
+
+    boolean redundant = false, fSubsumesT = false, tSubsumesF = false;
+    RuleItem first;
+    int subsumes = 0;
+    Object [] best = m_best.toArray();
+    for(int i=0; i < best.length; i++){
+      first = (RuleItem)best[i];
+      fSubsumesT = aSubsumesB(first,toInsert);
+      tSubsumesF = aSubsumesB(toInsert, first);
+      if(fSubsumesT){
+	subsumes = 1;
+	break;
+      }
+      else{
+	if(tSubsumesF){
+	  boolean remove = m_best.remove(first);
+	  subsumes = 2;
+	  redundant =true;
+	}
+      }
+    }
+    if(subsumes == 0 || subsumes == 2)
+      m_best.add(toInsert);
+    return redundant;
+  }
+
+  /**
+   * Gets the actual maximum value of the generation time
+   * @return the actual maximum value of the generation time
+   */
+  public int count(){
+
+    return m_count;
+  }
+
+  /**
+   * Gets if the list fo the best rules has been changed
+   * @return whether or not the list fo the best rules has been changed
+   */
+  public boolean change(){
+
+    return m_change;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/RuleItem.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/RuleItem.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/RuleItem.java	(revision 29)
@@ -0,0 +1,203 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RuleItem.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+
+/**
+ * Class for storing an (class) association rule.
+ * The premise and the consequence are stored each as separate item sets.
+ * For every rule their expected predictive accuracy and the time of generation is stored.
+ * These two measures allow to introduce a sort order for rules.
+ *
+ * @author Stefan Mutter
+ * @version $Revision: 1.5 $
+ */
+public class RuleItem
+  implements Comparable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3761299128347476534L;
+
+  /** The premise of a rule. */ 
+  protected ItemSet m_premise;
+
+  /** The consequence of a rule. */ 
+  protected ItemSet m_consequence;
+
+  /** The expected predictive accuracy of a rule. */ 
+  protected double m_accuracy;
+
+  /** The generation time of a rule. */ 
+  protected int m_genTime;
+
+
+  /**
+   * Constructor for an empty RuleItem
+   */    
+  public RuleItem(){
+
+  }
+
+  /**
+   * Constructor that generates a RuleItem out of a given one
+   * @param toCopy RuleItem to copy
+   */    
+  public RuleItem(RuleItem toCopy){
+
+    m_premise = toCopy.m_premise;
+    m_consequence = toCopy.m_consequence;
+    m_accuracy = toCopy.m_accuracy;
+    m_genTime = toCopy.m_genTime;
+  }
+
+  /**
+   * Constructor
+   * @param premise the premise of the future RuleItem
+   * @param consequence the consequence of the future RuleItem
+   * @param genTime the time of generation of the future RuleItem
+   * @param ruleSupport support of the rule
+   * @param m_midPoints the mid poitns of the intervals
+   * @param m_priors Hashtable containing the estimated prior probablilities
+   */      
+  public RuleItem(ItemSet premise, ItemSet consequence, int genTime,int ruleSupport,double [] m_midPoints, Hashtable m_priors){
+
+
+    m_premise = premise;
+    m_consequence = consequence;
+    m_accuracy = RuleGeneration.expectation((double)ruleSupport,m_premise.m_counter,m_midPoints,m_priors);
+    //overflow, underflow
+    if(Double.isNaN(m_accuracy) || m_accuracy < 0){
+      m_accuracy = Double.MIN_VALUE;
+    }
+    m_consequence.m_counter = ruleSupport;
+    m_genTime = genTime;
+  }
+
+  /**
+   * Constructs a new RuleItem if the support of the given rule is above the support threshold.
+   * @param premise the premise
+   * @param consequence the consequence
+   * @param instances the instances
+   * @param genTime the time of generation of the current premise and consequence
+   * @param minRuleCount the support threshold
+   * @param m_midPoints the mid points of the intervals
+   * @param m_priors the estimated priori probabilities (in a hashtable)
+   * @return a RuleItem if its support is above the threshold, null otherwise
+   */      
+  public RuleItem generateRuleItem(ItemSet premise, ItemSet consequence, Instances instances,int genTime, int minRuleCount,double[] m_midPoints, Hashtable m_priors){
+    ItemSet rule = new ItemSet(instances.numInstances());
+    rule.m_items = new int[(consequence.m_items).length];
+    System.arraycopy(premise.m_items, 0, rule.m_items, 0, (premise.m_items).length);
+    for(int k = 0;k < consequence.m_items.length; k++){
+      if(consequence.m_items[k] != -1)
+	rule.m_items[k] = consequence.m_items[k];
+    }
+    for (int i = 0; i < instances.numInstances(); i++) 
+      rule.upDateCounter(instances.instance(i));
+    int ruleSupport = rule.support();
+    if(ruleSupport > minRuleCount){
+      RuleItem newRule = new RuleItem(premise,consequence,genTime,ruleSupport,m_midPoints,m_priors);
+      return newRule;
+    }
+    return null;
+  }
+
+  //Note: this class has a natural ordering that is inconsistent with equals
+  /**
+   * compares two RuleItems and allows an ordering concerning
+   * expected predictive accuracy and time of generation
+   * Note: this class has a natural ordering that is inconsistent with equals
+   * @param o RuleItem to compare
+   * @return integer indicating the sort oder of the two RuleItems
+   */      
+  public int compareTo(Object o) {
+
+    if(this.m_accuracy == ((RuleItem)o).m_accuracy){ 
+      if((this.m_genTime == ((RuleItem)o).m_genTime))
+	return 0;
+      if(this.m_genTime > ((RuleItem)o).m_genTime)
+	return -1;
+      if(this.m_genTime < ((RuleItem)o).m_genTime)
+	return 1;
+    }
+    if(this.m_accuracy < ((RuleItem)o).m_accuracy)
+      return -1;
+    return 1;
+  }
+
+  /**
+   * returns whether two RuleItems are equal
+   * @param o RuleItem to compare
+   * @return true if the rules are equal, false otherwise
+   */      
+  public  boolean equals(Object o){
+
+    if(o == null)
+      return false;
+    if(m_premise.equals(((RuleItem)o).m_premise) && m_consequence.equals(((RuleItem)o).m_consequence))
+      return true;
+    return false;
+  }
+
+  /**
+   * Gets the expected predictive accuracy of a rule
+   * @return the expected predictive accuracy of a rule stored as a RuleItem
+   */      
+  public double accuracy(){
+
+    return m_accuracy;
+  }
+
+  /**
+   * Gets the premise of a rule
+   * @return the premise of a rule stored as a RuleItem
+   */      
+  public ItemSet premise(){
+
+    return m_premise;
+  }
+
+  /**
+   * Gets the consequence of a rule
+   * @return the consequence of a rule stored as a RuleItem
+   */      
+  public ItemSet consequence(){
+
+    return m_consequence;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/SingleAssociatorEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/SingleAssociatorEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/SingleAssociatorEnhancer.java	(revision 29)
@@ -0,0 +1,203 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SingleAssociatorEnhancer.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.associations;
+
+import weka.core.Capabilities;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to meta
+ * associators that use a single base associator.  
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public abstract class SingleAssociatorEnhancer
+  extends AbstractAssociator
+  implements OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3665885256363525164L;
+
+  /** The base associator to use */
+  protected Associator m_Associator = new Apriori();
+
+  /**
+   * String describing default Associator.
+   * 
+   * @return		default classname
+   */
+  protected String defaultAssociatorString() {
+    return Apriori.class.getName();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	      "\tFull name of base associator.\n"
+	      + "\t(default: " + defaultAssociatorString() +")",
+	      "W", 1, "-W"));
+
+    if (m_Associator instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to associator "
+	  + m_Associator.getClass().getName() + ":"));
+
+      Enumeration enm = ((OptionHandler) m_Associator).listOptions();
+      while (enm.hasMoreElements())
+	result.addElement(enm.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base associator.<p>
+   *
+   * Options after -- are passed to the designated associator.<p>
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) { 
+      // This is just to set the associator in case the option 
+      // parsing fails.
+      setAssociator(AbstractAssociator.forName(tmpStr, null));
+      setAssociator(AbstractAssociator.forName(tmpStr, Utils.partitionOptions(options)));
+    }
+    else {
+      // This is just to set the associator in case the option 
+      // parsing fails.
+      setAssociator(AbstractAssociator.forName(defaultAssociatorString(), null));
+      setAssociator(AbstractAssociator.forName(defaultAssociatorString(), Utils.partitionOptions(options)));
+    }
+  }
+
+  /**
+   * Gets the current settings of the associator.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       		i;
+    Vector<String>    	result;
+    String[]		options;
+    
+    result = new Vector<String>();
+
+    result.add("-W");
+    result.add(getAssociator().getClass().getName());
+
+    if (getAssociator() instanceof OptionHandler) {
+      options = ((OptionHandler) getAssociator()).getOptions();
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+	result.add(options[i]);
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String associatorTipText() {
+    return "The base associator to be used.";
+  }
+
+  /**
+   * Set the base associator.
+   *
+   * @param value 	the associator to use.
+   */
+  public void setAssociator(Associator value) {
+    m_Associator = value;
+  }
+
+  /**
+   * Get the associator used as the base associator.
+   *
+   * @return 		the currently used associator
+   */
+  public Associator getAssociator() {
+    return m_Associator;
+  }
+  
+  /**
+   * Gets the associator specification string, which contains the class name of
+   * the associator and any options to the associator
+   *
+   * @return the associator string
+   */
+  protected String getAssociatorSpec() {
+    Associator c = getAssociator();
+    return c.getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)c).getOptions());
+  }
+
+  /**
+   * Returns default capabilities of the base associator.
+   *
+   * @return      the capabilities of the base associator
+   */
+  public Capabilities getCapabilities() {
+    Capabilities        result;
+
+    if (getAssociator() != null)
+      result = getAssociator().getCapabilities();
+    else
+      result = new Capabilities(this);
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    result.setOwner(this);
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/Tertius.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/Tertius.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/Tertius.java	(revision 29)
@@ -0,0 +1,2025 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Tertius.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations;
+
+import weka.associations.tertius.AttributeValueLiteral;
+import weka.associations.tertius.IndividualInstances;
+import weka.associations.tertius.IndividualLiteral;
+import weka.associations.tertius.Literal;
+import weka.associations.tertius.Predicate;
+import weka.associations.tertius.Rule;
+import weka.associations.tertius.SimpleLinkedList;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Label;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Finds rules according to confirmation measure (Tertius-type algorithm).<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * P. A. Flach, N. Lachiche (1999). Confirmation-Guided Discovery of first-order rules with Tertius. Machine Learning. 42:61-95.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Flach1999,
+ *    author = {P. A. Flach and N. Lachiche},
+ *    journal = {Machine Learning},
+ *    pages = {61-95},
+ *    title = {Confirmation-Guided Discovery of first-order rules with Tertius},
+ *    volume = {42},
+ *    year = {1999}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -K &lt;number of values in result&gt;
+ *  Set maximum number of confirmation  values in the result. (default: 10)</pre>
+ * 
+ * <pre> -F &lt;frequency threshold&gt;
+ *  Set frequency threshold for pruning. (default: 0)</pre>
+ * 
+ * <pre> -C &lt;confirmation threshold&gt;
+ *  Set confirmation threshold. (default: 0)</pre>
+ * 
+ * <pre> -N &lt;noise threshold&gt;
+ *  Set noise threshold : maximum frequency of counter-examples.
+ *  0 gives only satisfied rules. (default: 1)</pre>
+ * 
+ * <pre> -R
+ *  Allow attributes to be repeated in a same rule.</pre>
+ * 
+ * <pre> -L &lt;number of literals&gt;
+ *  Set maximum number of literals in a rule. (default: 4)</pre>
+ * 
+ * <pre> -G &lt;0=no negation | 1=body | 2=head | 3=body and head&gt;
+ *  Set the negations in the rule. (default: 0)</pre>
+ * 
+ * <pre> -S
+ *  Consider only classification rules.</pre>
+ * 
+ * <pre> -c &lt;class index&gt;
+ *  Set index of class attribute. (default: last).</pre>
+ * 
+ * <pre> -H
+ *  Consider only horn clauses.</pre>
+ * 
+ * <pre> -E
+ *  Keep equivalent rules.</pre>
+ * 
+ * <pre> -M
+ *  Keep same clauses.</pre>
+ * 
+ * <pre> -T
+ *  Keep subsumed rules.</pre>
+ * 
+ * <pre> -I &lt;0=always match | 1=never match | 2=significant&gt;
+ *  Set the way to handle missing values. (default: 0)</pre>
+ * 
+ * <pre> -O
+ *  Use ROC analysis. </pre>
+ * 
+ * <pre> -p &lt;name of file&gt;
+ *  Set the file containing the parts of the individual for individual-based learning.</pre>
+ * 
+ * <pre> -P &lt;0=no output | 1=on stdout | 2=in separate window&gt;
+ *  Set output of current values. (default: 0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
+ * @version $Revision: 5444 $
+ */
+
+public class Tertius 
+  extends AbstractAssociator 
+  implements OptionHandler, Runnable, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5556726848380738179L;
+  
+  /** The results. */
+  private SimpleLinkedList m_results;
+
+  /** Number of hypotheses considered. */
+  private int m_hypotheses;
+
+  /** Number of hypotheses explored. */
+  private int m_explored;
+
+  /** Time needed for the search. */
+  private Date m_time;
+
+  /** Field to output the current values. */ 
+  private TextField m_valuesText;
+
+  /** Instances used for the search. */
+  private Instances m_instances;
+
+  /** Predicates used in the rules. */
+  private ArrayList m_predicates;
+
+  /** Status of the search. */
+  private int m_status;
+  /** Status of the search: normal */
+  private static final int NORMAL = 0;
+  /** Status of the search: memory problem */
+  private static final int MEMORY = 1;
+  /** Status of the search: user interruption */
+  private static final int STOP = 2;
+  
+  /* Pruning options. */
+
+  /** Number of best confirmation values to search. */
+  private int m_best;
+
+  /** Frequency threshold for the body and the negation of the head. */
+  private double m_frequencyThreshold;
+
+  /** Confirmation threshold for the rules. */
+  private double m_confirmationThreshold;
+
+  /** Maximal number of counter-instances. */
+  private double m_noiseThreshold;
+
+  /* Search space & language bias options. */
+
+  /** Repeat attributes ? */
+  private boolean m_repeat;
+
+  /** Number of literals in a rule. */
+  private int m_numLiterals;
+
+  /** Type of negation: none */
+  private static final int NONE = 0;
+  /** Type of negation: body */
+  private static final int BODY = 1;
+  /** Type of negation: head */
+  private static final int HEAD = 2;
+  /** Type of negation: all */
+  private static final int ALL = 3;
+  /** Types of negation. */
+  private static final Tag [] TAGS_NEGATION = {
+    new Tag(NONE, "None"),
+    new Tag(BODY, "Body"),
+    new Tag(HEAD, "Head"),
+    new Tag(ALL, "Both")
+      };
+
+  /** Type of negation used in the rules. */
+  private int m_negation;
+
+  /** Classification bias. */
+  private boolean m_classification;
+
+  /** Index of class attribute. */
+  private int m_classIndex;
+
+  /** Horn clauses bias. */
+  private boolean m_horn;
+
+  /* Subsumption tests options. */
+
+  /** Perform test on equivalent rules ? */
+  private boolean m_equivalent;
+
+  /** Perform test on same clauses ? */
+  private boolean m_sameClause;
+  
+  /** Perform subsumption test ? */
+  private boolean m_subsumption;
+
+  /** Way of handling missing values: min counterinstances */
+  public static final int EXPLICIT = 0;
+  /** Way of handling missing values: max counterinstances */
+  public static final int IMPLICIT = 1;
+  /** Way of handling missing values: missing as a particular value */
+  public static final int SIGNIFICANT = 2;
+  /** Ways of handling missing values.  */
+  private static final Tag [] TAGS_MISSING = {
+    new Tag(EXPLICIT, "Matches all"),
+    new Tag(IMPLICIT, "Matches none"),
+    new Tag(SIGNIFICANT, "Significant")
+      };
+
+  /** Way of handling missing values in the search. */
+  private int m_missing;
+
+  /** Perform ROC analysis ? */
+  private boolean m_roc;
+
+  /** Name of the file containing the parts for individual-based learning. */
+  private String m_partsString;
+  
+  /** Part instances for individual-based learning. */
+  private Instances m_parts;
+
+  /** Type of values output: No */ 
+  private static final int NO = 0;
+  /** Type of values output: stdout */ 
+  private static final int OUT = 1;
+  /** Type of values output: Window */ 
+  private static final int WINDOW = 2;
+  /** Types of values output. */ 
+  private static final Tag [] TAGS_VALUES = {
+    new Tag(NO, "No"),
+    new Tag(OUT, "stdout"),
+    new Tag(WINDOW, "Window")
+      };
+
+  /** Type of values output. */
+  private int m_printValues;
+
+  /**
+   * Constructor that sets the options to the default values.
+   */
+  public Tertius() {
+
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this associator.
+   *
+   * @return A description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui.
+   */
+  public String globalInfo() {
+    return 
+        "Finds rules according to confirmation measure (Tertius-type "
+      + "algorithm).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "P. A. Flach and N. Lachiche");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.TITLE, "Confirmation-Guided Discovery of first-order rules with Tertius");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "42");
+    result.setValue(Field.PAGES, "61-95");
+    
+    return result;
+  }
+
+  /**
+   * Resets the options to the default values.
+   */
+  public void resetOptions() {
+
+    /* Pruning options. */
+    m_best = 10;
+    m_frequencyThreshold = 0;
+    m_confirmationThreshold = 0;
+    m_noiseThreshold = 1;
+
+    /* Search space & language bias options. */
+    m_repeat = false;
+    m_numLiterals = 4;
+    m_negation = NONE;
+    m_classification = false;
+    m_classIndex = 0;
+    m_horn = false;
+
+    /* Subsumption tests options. */
+    m_equivalent = true;
+    m_sameClause = true;
+    m_subsumption = true;
+
+    /* Missing values. */
+    m_missing = EXPLICIT;
+
+    /* ROC analysis. */
+    m_roc = false;
+
+    /* Individual-based learning. */
+    m_partsString = "";
+    m_parts = null;
+
+    /* Values output. */
+    m_printValues = NO;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return An enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(17);
+
+    /* Pruning options. */
+    newVector.addElement(new Option("\tSet maximum number of confirmation  "
+				    + "values in the result. (default: 10)",
+				    "K", 1, "-K <number of values in result>"));
+    newVector.addElement(new Option("\tSet frequency threshold for pruning. "
+				    + "(default: 0)",
+				    "F", 1, "-F <frequency threshold>"));
+    newVector.addElement(new Option("\tSet confirmation threshold. "
+				    + "(default: 0)",
+				    "C", 1, "-C <confirmation threshold>"));
+    newVector.addElement(new Option("\tSet noise threshold : maximum frequency "
+				    + "of counter-examples.\n\t0 gives only "
+				    + "satisfied rules. (default: 1)",
+				    "N", 1, "-N <noise threshold>"));
+
+    /* Search space & language bias options. */
+    newVector.addElement(new Option("\tAllow attributes to be repeated in a "
+				    + "same rule.",
+				    "R", 0, "-R"));
+    newVector.addElement(new Option("\tSet maximum number of literals in a "
+				    + "rule. (default: 4)",
+				    "L", 1, "-L <number of literals>"));
+    newVector.addElement(new Option("\tSet the negations in the rule. "
+				    + "(default: 0)",
+				    "G", 1, "-G <0=no negation | "
+				    + "1=body | "
+				    + "2=head | "
+				    + "3=body and head>"));
+    newVector.addElement(new Option("\tConsider only classification rules.",
+				    "S", 0, "-S"));
+    newVector.addElement(new Option("\tSet index of class attribute. "
+				    + "(default: last).",
+				    "c", 1, "-c <class index>"));
+    newVector.addElement(new Option("\tConsider only horn clauses.",
+				    "H", 0, "-H"));
+
+    /* Subsumption tests options. */
+    newVector.addElement(new Option("\tKeep equivalent rules.",
+				    "E", 0, "-E"));
+    newVector.addElement(new Option("\tKeep same clauses.",
+				    "M", 0, "-M"));
+    newVector.addElement(new Option("\tKeep subsumed rules.",
+				    "T", 0, "-T"));
+
+    /* Missing values options. */
+    newVector.addElement(new Option("\tSet the way to handle missing values. " 
+				    + "(default: 0)",
+				    "I", 1, "-I <0=always match | "
+				    + "1=never match | "
+				    + "2=significant>"));
+
+    /* ROC analysis. */
+    newVector.addElement(new Option("\tUse ROC analysis. ",
+				    "O", 0, "-O"));
+
+    /* Individual-based learning. */
+    newVector.addElement(new Option("\tSet the file containing the parts of "
+				    + "the individual for individual-based "
+				    + "learning.",
+				    "p", 1, "-p <name of file>"));
+
+    /* Values output. */
+    newVector.addElement(new Option("\tSet output of current values. "
+				    + "(default: 0)",
+				    "P", 1, "-P <0=no output | "
+				    + "1=on stdout | "
+				    + "2=in separate window>"));
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -K &lt;number of values in result&gt;
+   *  Set maximum number of confirmation  values in the result. (default: 10)</pre>
+   * 
+   * <pre> -F &lt;frequency threshold&gt;
+   *  Set frequency threshold for pruning. (default: 0)</pre>
+   * 
+   * <pre> -C &lt;confirmation threshold&gt;
+   *  Set confirmation threshold. (default: 0)</pre>
+   * 
+   * <pre> -N &lt;noise threshold&gt;
+   *  Set noise threshold : maximum frequency of counter-examples.
+   *  0 gives only satisfied rules. (default: 1)</pre>
+   * 
+   * <pre> -R
+   *  Allow attributes to be repeated in a same rule.</pre>
+   * 
+   * <pre> -L &lt;number of literals&gt;
+   *  Set maximum number of literals in a rule. (default: 4)</pre>
+   * 
+   * <pre> -G &lt;0=no negation | 1=body | 2=head | 3=body and head&gt;
+   *  Set the negations in the rule. (default: 0)</pre>
+   * 
+   * <pre> -S
+   *  Consider only classification rules.</pre>
+   * 
+   * <pre> -c &lt;class index&gt;
+   *  Set index of class attribute. (default: last).</pre>
+   * 
+   * <pre> -H
+   *  Consider only horn clauses.</pre>
+   * 
+   * <pre> -E
+   *  Keep equivalent rules.</pre>
+   * 
+   * <pre> -M
+   *  Keep same clauses.</pre>
+   * 
+   * <pre> -T
+   *  Keep subsumed rules.</pre>
+   * 
+   * <pre> -I &lt;0=always match | 1=never match | 2=significant&gt;
+   *  Set the way to handle missing values. (default: 0)</pre>
+   * 
+   * <pre> -O
+   *  Use ROC analysis. </pre>
+   * 
+   * <pre> -p &lt;name of file&gt;
+   *  Set the file containing the parts of the individual for individual-based learning.</pre>
+   * 
+   * <pre> -P &lt;0=no output | 1=on stdout | 2=in separate window&gt;
+   *  Set output of current values. (default: 0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options The list of options as an array of strings.
+   * @throws Exception if an option is not supported.
+   */
+  public void setOptions(String [] options) throws Exception {
+    
+    resetOptions();
+    
+    /* Pruning options. */
+    String bestString = Utils.getOption('K', options);
+    if (bestString.length() != 0) {
+      try {
+	m_best = Integer.parseInt(bestString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -K option: "
+			    + e.getMessage() + ".");
+      }
+      if (m_best < 1) {
+	throw new Exception("Number of confirmation values has to be "
+			    + "greater than one!");
+      }
+    }
+    String frequencyThresholdString = Utils.getOption('F', options);
+    if (frequencyThresholdString.length() != 0) {
+      try {	
+	m_frequencyThreshold 
+	  = (new Double(frequencyThresholdString)).doubleValue();
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -F option: "
+			    + e.getMessage() + ".");
+      }
+      if (m_frequencyThreshold < 0 || m_frequencyThreshold > 1) {
+	throw new Exception("Frequency threshold has to be between "
+			    + "zero and one!");
+      }
+    }
+    String confirmationThresholdString = Utils.getOption('C', options);
+    if (confirmationThresholdString.length() != 0) {
+      try {
+	m_confirmationThreshold 
+	  = (new Double(confirmationThresholdString)).doubleValue();
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -C option: "
+			    + e.getMessage() + ".");
+      }
+      if (m_confirmationThreshold < 0 || m_confirmationThreshold > 1) {
+	throw new Exception("Confirmation threshold has to be between "
+			    + "zero and one!");
+      }
+      if (bestString.length() != 0) {
+	throw new Exception("Specifying both a number of confirmation "
+			    + "values and a confirmation threshold "
+			    + "doesn't make sense!");
+      }
+      if (m_confirmationThreshold != 0) {
+	m_best = 0;
+      }
+    }
+    String noiseThresholdString = Utils.getOption('N', options);
+    if (noiseThresholdString.length() != 0) {
+      try {
+	m_noiseThreshold = (new Double(noiseThresholdString)).doubleValue();
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -N option: "
+			    + e.getMessage() + ".");
+      }
+      if (m_noiseThreshold < 0 || m_noiseThreshold > 1) {
+	throw new Exception("Noise threshold has to be between "
+			    + "zero and one!");
+      }
+    }
+
+    /* Search space and language bias options. */
+    m_repeat = Utils.getFlag('R', options);
+    String numLiteralsString = Utils.getOption('L', options);
+    if (numLiteralsString.length() != 0) {
+      try {
+	m_numLiterals = Integer.parseInt(numLiteralsString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -L option: "
+			    + e.getMessage() + ".");
+      }
+      if (m_numLiterals < 1) {
+	throw new Exception("Number of literals has to be "
+			    + "greater than one!");
+      }
+    }
+    String negationString = Utils.getOption('G', options);
+    if (negationString.length() != 0) {
+      SelectedTag selected;
+      int tag;
+      try {
+	tag = Integer.parseInt(negationString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -G option: "
+			    + e.getMessage() + ".");
+      }
+      try {
+	selected = new SelectedTag(tag, TAGS_NEGATION);
+      } catch (Exception e) {
+	throw new Exception("Value for -G option has to be "
+			    + "between zero and three!");
+      }
+      setNegation(selected);
+    }
+    m_classification = Utils.getFlag('S', options);
+    String classIndexString = Utils.getOption('c', options);
+    if (classIndexString.length() != 0) {
+      try {
+	m_classIndex = Integer.parseInt(classIndexString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -c option: "
+			    + e.getMessage() + ".");
+      }
+    }
+    m_horn = Utils.getFlag('H', options);
+    if (m_horn && (m_negation != NONE)) {
+      throw new Exception("Considering horn clauses doesn't make sense "
+			  + "if negation allowed!");
+    }
+    
+    /* Subsumption tests options. */
+    m_equivalent = !(Utils.getFlag('E', options));
+    m_sameClause = !(Utils.getFlag('M', options));
+    m_subsumption = !(Utils.getFlag('T', options));
+
+    /* Missing values options. */
+    String missingString = Utils.getOption('I', options);
+    if (missingString.length() != 0) {
+      SelectedTag selected;
+      int tag;
+      try {
+	tag = Integer.parseInt(missingString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -I option: "
+			    + e.getMessage() + ".");
+      }
+      try {
+	selected = new SelectedTag(tag, TAGS_MISSING);
+      } catch (Exception e) {
+	throw new Exception("Value for -I option has to be "
+			    + "between zero and two!");
+      }
+      setMissingValues(selected);
+    }
+
+    /* ROC analysis. */
+    m_roc = Utils.getFlag('O', options);
+
+
+    /* Individual-based learning. */
+    m_partsString = Utils.getOption('p', options);
+    if (m_partsString.length() != 0) {
+      Reader reader;
+      try {
+	reader = new BufferedReader(new FileReader(m_partsString));
+      } catch (Exception e) {
+	throw new Exception("Can't open file " + e.getMessage() + ".");
+      }
+      m_parts = new Instances(reader);	
+    }
+
+    /* Values output. */
+    String printValuesString = Utils.getOption('P', options);
+    if (printValuesString.length() != 0) {
+      SelectedTag selected;
+      int tag;
+      try {
+	tag = Integer.parseInt(printValuesString);
+      } catch (Exception e) {
+	throw new Exception("Invalid value for -P option: "
+			    + e.getMessage() + ".");
+      }
+      try {
+	selected = new SelectedTag(tag, TAGS_VALUES);
+      } catch (Exception e) {
+	throw new Exception("Value for -P option has to be "
+			    + "between zero and two!");
+      }
+      setValuesOutput(selected);
+    }
+  }
+
+  /**
+   * Gets the current settings of the Tertius object.
+   *
+   * @return An array of strings suitable for passing to setOptions.
+   */
+  public String [] getOptions() {
+    Vector    	result;
+
+    result = new Vector();
+
+    /* Pruning options. */
+    if (m_best > 0) {
+      result.add("-K");
+      result.add("" + m_best);
+    }
+    
+    result.add("-F");
+    result.add("" + m_frequencyThreshold);
+    
+    if (m_confirmationThreshold > 0) {
+      result.add("-C");
+      result.add("" + m_confirmationThreshold);
+    }
+    
+    result.add("-N");
+    result.add("" + m_noiseThreshold);
+    
+    /* Search space and language bias options. */
+    if (m_repeat)
+      result.add("-R");
+    
+    result.add("-L");
+    result.add("" + m_numLiterals);
+    
+    result.add("-G");
+    result.add("" + m_negation);
+    
+    if (m_classification)
+      result.add("-S");
+      
+    result.add("-c");
+    result.add("" + m_classIndex);
+    
+    if (m_horn)
+      result.add("-H");
+
+    /* Subsumption tests options. */
+    if (!m_equivalent)
+      result.add("-E");
+    
+    if (!m_sameClause)
+      result.add("-M");
+    
+    if (!m_subsumption)
+      result.add("-T");
+
+    /* Missing values options. */
+    result.add("-I");
+    result.add("" + m_missing);
+
+    /* ROC analysis. */
+    if (m_roc)
+      result.add("-O");
+
+    /* Individual-based learning. */
+    if (m_partsString.length() > 0) {
+      result.add("-p");
+      result.add("" + m_partsString);
+    }
+
+    /* Values output. */
+    result.add("-P");
+    result.add("" + m_printValues);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String confirmationValuesTipText() {
+
+    return "Number of best confirmation values to find.";
+  }
+
+  /**
+   * Get the value of confirmationValues.
+   *
+   * @return Value of confirmationValues.
+   */
+  public int getConfirmationValues() {
+
+    return m_best;
+  }
+
+  /**
+   * Set the value of confirmationValues.
+   *
+   * @param v  Value to assign to confirmationValues.
+   */
+  public void setConfirmationValues(int v) {
+
+    m_best = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String frequencyThresholdTipText() {
+    
+    return "Minimum proportion of instances satisfying head and body of rules";
+  }
+
+  /**
+   * Get the value of frequencyThreshold.
+   *
+   * @return Value of frequencyThreshold.
+   */
+  public double getFrequencyThreshold() {
+    
+    return m_frequencyThreshold;
+  }
+
+  /**
+   * Set the value of frequencyThreshold.
+   *
+   * @param v  Value to assign to frequencyThreshold.
+   */
+  public void setFrequencyThreshold(double v) {
+    
+    m_frequencyThreshold = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String confirmationThresholdTipText() {
+    
+    return "Minimum confirmation of the rules.";
+  }
+
+  /**
+   * Get the value of confirmationThreshold.
+   *
+   * @return Value of confirmationThreshold.
+   */
+  public double getConfirmationThreshold() {
+    
+    return m_confirmationThreshold;
+  }
+
+  /**
+   * Set the value of confirmationThreshold.
+   *
+   * @param v  Value to assign to confirmationThreshold.
+   */
+  public void setConfirmationThreshold(double v) {
+    
+    m_confirmationThreshold = v;
+    if (v != 0) {
+      m_best = 0;
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String noiseThresholdTipText() {
+    
+    return "Maximum proportion of counter-instances of rules. "
+      + "If set to 0, only satisfied rules will be given.";
+  }
+
+  /**
+   * Get the value of noiseThreshold.
+   *
+   * @return Value of noiseThreshold.
+   */
+  public double getNoiseThreshold() {
+    
+    return m_noiseThreshold;
+  }
+
+  /**
+   * Set the value of noiseThreshold.
+   *
+   * @param v  Value to assign to noiseThreshold.
+   */
+  public void setNoiseThreshold(double v) {
+    
+    m_noiseThreshold = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String repeatLiteralsTipText() {
+
+    return "Repeated attributes allowed.";
+  }
+
+  /**
+   * Get the value of repeatLiterals.
+   *
+   * @return Value of repeatLiterals.
+   */
+  public boolean getRepeatLiterals() {
+    
+    return m_repeat;
+  }
+
+  /**
+   * Set the value of repeatLiterals.
+   *
+   * @param v  Value to assign to repeatLiterals.
+   */
+  public void setRepeatLiterals(boolean v) {
+    
+    m_repeat = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String numberLiteralsTipText() {
+    
+    return "Maximum number of literals in a rule.";
+  }
+
+  /**
+   * Get the value of numberLiterals.
+   *
+   * @return Value of numberLiterals.
+   */
+  public int getNumberLiterals() {
+
+    return m_numLiterals;
+  }
+
+  /**
+   * Set the value of numberLiterals.
+   *
+   * @param v  Value to assign to numberLiterals.
+   */
+  public void setNumberLiterals(int v) {
+    
+    m_numLiterals = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String negationTipText() {
+    
+    return "Set the type of negation allowed in the rule. "
+      + "Negation can be allowed in the body, in the head, in both "
+      + "or in none.";
+  }
+
+  /**
+   * Get the value of negation.
+   *
+   * @return Value of negation.
+   */
+  public SelectedTag getNegation() {
+    
+    return new SelectedTag(m_negation, TAGS_NEGATION);
+  }
+
+  /**
+   * Set the value of negation.
+   *
+   * @param v  Value to assign to negation.
+   */
+  public void setNegation(SelectedTag v) {
+    
+    if (v.getTags() == TAGS_NEGATION) {
+      m_negation = v.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String classificationTipText() {
+    
+    return "Find only rules with the class in the head.";
+  }
+
+  /**
+   * Get the value of classification.
+   *
+   * @return Value of classification.
+   */
+  public boolean getClassification() {
+
+    return m_classification;
+  }
+
+  /**
+   * Set the value of classification.
+   *
+   * @param v  Value to assign to classification.
+   */
+  public void setClassification(boolean v) {
+
+    m_classification = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String classIndexTipText() {
+
+    return "Index of the class attribute. If set to 0, the class will be the last attribute.";
+  }
+
+  /**
+   * Get the value of classIndex.
+   *
+   * @return Value of classIndex.
+   */
+  public int getClassIndex() {
+
+    return m_classIndex;
+  }
+
+  /**
+   * Set the value of classIndex.
+   *
+   * @param v  Value to assign to classIndex.
+   */
+  public void setClassIndex(int v) {
+
+    m_classIndex = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String hornClausesTipText() {
+    
+    return "Find rules with a single conclusion literal only.";
+  }
+
+  /**
+   * Get the value of hornClauses.
+   *
+   * @return Value of hornClauses.
+   */
+  public boolean getHornClauses() {
+
+    return m_horn;
+  }
+
+  /**
+   * Set the value of hornClauses.
+   *
+   * @param v  Value to assign to hornClauses.
+   */
+  public void setHornClauses(boolean v) {
+
+    m_horn = v;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String equivalentTipText() {
+    
+    return "Keep equivalent rules. "
+      + "A rule r2 is equivalent to a rule r1 if the body of r2 is the "
+      + "negation of the head of r1, and the head of r2 is the "
+      + "negation of the body of r1.";
+  }
+
+  /**
+   * Get the value of equivalent.
+   *
+   * @return Value of equivalent.
+   */
+  public boolean disabled_getEquivalent() {
+    
+    return !m_equivalent;
+  }
+
+  /**
+   * Set the value of equivalent.
+   *
+   * @param v  Value to assign to equivalent.
+   */
+  public void disabled_setEquivalent(boolean v) {
+    
+    m_equivalent = !v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String sameClauseTipText() {
+
+    return "Keep rules corresponding to the same clauses. "
+      + "If set to false, only the rule with the best confirmation "
+      + "value and rules with a lower number of counter-instances "
+      + "will be kept.";
+  }
+
+  /**
+   * Get the value of sameClause.
+   *
+   * @return Value of sameClause.
+   */
+  public boolean disabled_getSameClause() {
+
+    return !m_sameClause;
+  }
+
+  /**
+   * Set the value of sameClause.
+   *
+   * @param v  Value to assign to sameClause.
+   */
+  public void disabled_setSameClause(boolean v) {
+
+    m_sameClause = !v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String subsumptionTipText() {
+
+    return "Keep subsumed rules. "
+      + "If set to false, subsumed rules will only be kept if they "
+      + "have a better confirmation or a lower number of counter-instances.";
+  }
+
+  /**
+   * Get the value of subsumption.
+   *
+   * @return Value of subsumption.
+   */
+  public boolean disabled_getSubsumption() {
+
+    return !m_subsumption;
+  }
+
+  /**
+   * Set the value of subsumption.
+   *
+   * @param v  Value to assign to subsumption.
+   */
+  public void disabled_setSubsumption(boolean v) {
+
+    m_subsumption = !v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String missingValuesTipText() {
+    
+    return "Set the way to handle missing values. "
+      + "Missing values can be set to match any value, or never match values "
+      + "or to be significant and possibly appear in rules.";
+  }
+
+  /**
+   * Get the value of missingValues.
+   *
+   * @return Value of missingValues.
+   */
+  public SelectedTag getMissingValues() {
+    
+    return new SelectedTag(m_missing, TAGS_MISSING);
+  }
+
+  /**
+   * Set the value of missingValues.
+   *
+   * @param v  Value to assign to missingValues.
+   */
+  public void setMissingValues(SelectedTag v) {
+    
+    if (v.getTags() == TAGS_MISSING) {
+      m_missing = v.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String rocAnalysisTipText() {
+    
+    return "Return TP-rate and FP-rate for each rule found.";
+  }
+
+  /**
+   * Get the value of rocAnalysis.
+   *
+   * @return Value of rocAnalysis.
+   */
+  public boolean getRocAnalysis() {
+
+    return m_roc;
+  }
+
+  /**
+   * Set the value of rocAnalysis.
+   *
+   * @param v  Value to assign to rocAnalysis.
+   */
+  public void setRocAnalysis(boolean v) {
+
+    m_roc = v;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String partFileTipText() {
+    
+    return "Set file containing the parts of the individual "
+      + "for individual-based learning.";
+  }
+
+  /**
+   * Get the value of partFile.
+   *
+   * @return Value of partFile.
+   */
+  public File disabled_getPartFile() {
+
+    return new File(m_partsString);
+  }
+
+  /**
+   * Set the value of partFile.
+   *
+   * @param v  Value to assign to partFile.
+   * @throws Exception if file cannot be opened
+   */
+  public void disabled_setPartFile(File v) throws Exception {
+
+    m_partsString = v.getAbsolutePath();
+    if (m_partsString.length() != 0) {
+      Reader reader;
+      try {
+	reader = new BufferedReader(new FileReader(m_partsString));
+      } catch (Exception e) {
+	throw new Exception("Can't open file " + e.getMessage() + ".");
+      }
+      m_parts = new Instances(reader);	
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return Tip text for this property suitable for
+   * displaying in the explorer/experimenter GUI.
+   */
+  public String valuesOutputTipText() {
+    
+    return "Give visual feedback during the search. "
+      + "The current best and worst values can be output either to stdout or to a separate window.";
+  }
+
+  /**
+   * Get the value of valuesOutput.
+   *
+   * @return Value of valuesOutput.
+   */
+  public SelectedTag getValuesOutput() {
+    
+    return new SelectedTag(m_printValues, TAGS_VALUES);
+  }
+
+  /**
+   * Set the value of valuesOutput.
+   *
+   * @param v  Value to assign to valuesOutput.
+   */
+  public void setValuesOutput(SelectedTag v) {
+    
+    if (v.getTags() == TAGS_VALUES) {
+      m_printValues = v.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Build the predicate corresponding to an attribute.
+   *
+   * @param instances The instances this predicates comes from.
+   * @param attr The attribute this predicate corresponds to.
+   * @param isClass Saying if the attribute is the class attribute.
+   * @return The corresponding Predicate.
+   * @throws Exception if the predicate could not be build 
+   * (the attribute is numeric).
+   */
+  private Predicate buildPredicate(Instances instances,
+				   Attribute attr, boolean isClass) 
+    throws Exception {
+
+    Predicate predicate; /* The result. */
+    Literal lit;
+    Literal negation;
+    boolean missingValues; /* Missing values for this attribute ? */
+    boolean individual = (m_parts != null); /* Individual-based learning ? */
+    int type = (instances == m_parts)
+      ? IndividualLiteral.PART_PROPERTY
+      : IndividualLiteral.INDIVIDUAL_PROPERTY; /* Type of property. */
+
+    if (attr.isNumeric()) {
+      throw new Exception("Can't handle numeric attributes!");
+    }
+	
+    missingValues = instances.attributeStats(attr.index()).missingCount > 0;
+
+    /* Build predicate. */
+    if (individual) {
+      predicate = new Predicate(instances.relationName() + "." + attr.name(), 
+				attr.index(), isClass);
+    } else {
+      predicate = new Predicate(attr.name(), attr.index(), isClass);
+    }
+	
+    if (attr.numValues() == 2
+	&& (!missingValues || m_missing == EXPLICIT)) {
+      /* Case of two values.
+       * If there are missing values, this case is treated like other cases.
+       */
+      if (individual) {
+	lit = new IndividualLiteral(predicate, attr.value(0), 0, 
+				    Literal.POS, m_missing, type);
+	negation = new IndividualLiteral(predicate, attr.value(1), 1, 
+					 Literal.POS, m_missing, type);
+      } else {
+	lit = new AttributeValueLiteral(predicate, attr.value(0), 0, 
+					Literal.POS, m_missing);
+	negation = new AttributeValueLiteral(predicate, attr.value(1), 1, 
+					     Literal.POS, m_missing);
+      }
+      lit.setNegation(negation);
+      negation.setNegation(lit);
+      predicate.addLiteral(lit);      
+    } else {
+      /* Case of several values. */
+      for (int i = 0; i < attr.numValues(); i++) {
+	if (individual) {
+	  lit = new IndividualLiteral(predicate, attr.value(i), i,
+				      Literal.POS, m_missing, type);
+	} else {
+	  lit = new AttributeValueLiteral(predicate, attr.value(i), i, 
+					  Literal.POS, m_missing);
+	}
+	if (m_negation != NONE) {
+	  if (individual) {
+	    negation = new IndividualLiteral(predicate, attr.value(i), i, 
+					     Literal.NEG, m_missing, type);
+	  } else {
+	    negation = new AttributeValueLiteral(predicate, attr.value(i), i, 
+						 Literal.NEG, m_missing);
+	  }
+	  lit.setNegation(negation);
+	  negation.setNegation(lit);
+	}
+	predicate.addLiteral(lit);
+      }
+
+      /* One more value if missing is significant. */
+      if (missingValues && m_missing == SIGNIFICANT) {
+	if (individual) {
+	  lit = new IndividualLiteral(predicate, "?", -1, 
+				      Literal.POS, m_missing, type);
+	} else {
+	  lit = new AttributeValueLiteral(predicate, "?", -1, 
+					  Literal.POS, m_missing);
+	}
+	if (m_negation != NONE) {
+	  if (individual) {
+	    negation = new IndividualLiteral(predicate, "?", -1, 
+					     Literal.NEG, m_missing, type);
+	  } else {
+	    negation = new AttributeValueLiteral(predicate, "?", -1, 
+						 Literal.NEG, m_missing);
+	  }
+	  lit.setNegation(negation);
+	  negation.setNegation(lit);
+	}
+	predicate.addLiteral(lit);
+      }
+    }
+    return predicate;
+  }
+   
+  /**
+   * Build the predicates to use in the rules.
+   *
+   * @return the predicates
+   * @throws Exception If the predicates could not be built 
+   * (numeric attribute).
+   */
+  private ArrayList buildPredicates() throws Exception {
+
+    ArrayList predicates = new ArrayList(); /* The result. */
+    Predicate predicate;
+    Attribute attr;
+    Enumeration attributes = m_instances.enumerateAttributes();
+    boolean individual = (m_parts != null); /* Individual-based learning ? */
+
+    /* Attributes. */
+    while (attributes.hasMoreElements()) {
+      attr = (Attribute) attributes.nextElement();
+      /* Identifiers do not appear in rules in individual-based learning. */
+      if (!(individual && attr.name().equals("id"))) {
+	predicate = buildPredicate(m_instances, attr, false);
+	predicates.add(predicate);
+      }
+    }
+    /* Class attribute. */
+    attr = m_instances.classAttribute();
+    /* Identifiers do not appear in rules. */
+    if (!(individual && attr.name().equals("id"))) {
+      predicate = buildPredicate(m_instances, attr, true);
+      predicates.add(predicate);
+    }
+
+    /* Attributes of the parts in individual-based learning. */
+    if (individual) {
+      attributes = m_parts.enumerateAttributes();
+      while (attributes.hasMoreElements()) {
+	attr = (Attribute) attributes.nextElement();
+	/* Identifiers do not appear in rules. */
+	if (!attr.name().equals("id")) {
+	  predicate = buildPredicate(m_parts, attr, false);
+	  predicates.add(predicate);
+	}
+      }
+    }
+	
+    return predicates;
+  }
+
+  /**
+   * Count the number of distinct confirmation values in the results.
+   *
+   * @return Number of confirmation values in the results.
+   */
+  private int numValuesInResult() {
+
+    int result = 0;
+    SimpleLinkedList.LinkedListIterator iter = m_results.iterator();
+    Rule current;
+    Rule next;
+    if (!iter.hasNext()) {
+      return result;
+    } else {
+      current = (Rule) iter.next();
+      while (iter.hasNext()) {
+	next = (Rule) iter.next();
+	if (current.getConfirmation() > next.getConfirmation()) {
+	  result++;
+	}
+	current = next;
+      }
+      return result + 1;
+    }
+  }
+
+  /**
+   * Test if it is worth refining a rule.
+   *
+   * @param rule The rule to consider.
+   * @return True if the rule can be refined, false otherwise.
+   */
+  private boolean canRefine(Rule rule) {
+    if (rule.isEmpty()) {
+      return true;
+    }
+    if (m_best != 0) {
+      if (numValuesInResult() < m_best) {
+	return true;
+      }
+      Rule worstResult = (Rule) m_results.getLast();
+      if (rule.getOptimistic() >= worstResult.getConfirmation()) {
+	return true;
+      }
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  /**
+   * Test if it is worth calculating the optimistic estimate of a rule.
+   *
+   * @param rule The rule to consider.
+   * @return True if the optimistic estimate can be calculated, false otherwise.
+   */
+  private boolean canCalculateOptimistic(Rule rule) {
+    if (rule.hasTrueBody() || rule.hasFalseHead()) {
+      return false;
+    }
+    if (!rule.overFrequencyThreshold(m_frequencyThreshold)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Test if a rule can be explored (if it is interesting for the results 
+   * or for refining).
+   *
+   * @param rule The rule to consider.
+   * @return True if the rule can be explored, false otherwise.
+   */
+  private boolean canExplore(Rule rule) {
+    if (rule.getOptimistic() < m_confirmationThreshold) {
+      return false;
+    }
+    if (m_best != 0) {
+      if (numValuesInResult() < m_best) {
+	return true;
+      }	
+      Rule worstResult = (Rule) m_results.getLast();
+      if (rule.getOptimistic() >= worstResult.getConfirmation()) {
+	return true;
+      }
+      return false;      
+    } else {
+      return true;
+    }
+  }    
+
+  /**
+   * Test if a rule can be stored in the agenda.
+   *
+   * @param rule The rule to consider.
+   * @return True if the rule can be stored, false otherwise.
+   */
+  private boolean canStoreInNodes(Rule rule) {
+    if (rule.getObservedNumber() == 0) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Test if it is worth calculating the confirmation of a rule.
+   *
+   * @param rule The rule to consider.
+   * @return True if the confirmation can be calculated, false otherwise.
+   */
+  private boolean canCalculateConfirmation(Rule rule) {
+    if (rule.getObservedFrequency() > m_noiseThreshold) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Test if a rule can be added to the results.
+   *
+   * @param rule The rule to consider.
+   * @return True if the rule can be stored, false otherwise.
+   */
+  private boolean canStoreInResults(Rule rule) {
+    if (rule.getConfirmation() < m_confirmationThreshold) {
+      return false;
+    }
+    if (m_best != 0) {
+      if (numValuesInResult() < m_best) {
+	return true;
+      }
+      Rule worstResult = (Rule) m_results.getLast();
+      if (rule.getConfirmation() >= worstResult.getConfirmation()) {
+	return true;
+      }
+      return false;    
+    } else {
+      return true;
+    }
+  }
+
+  /**
+   * Add a rule in the appropriate place in the list of the results, 
+   * according to the confirmation and 
+   * number of counter-instances of the rule. <p>
+   * Subsumption tests are performed and the rule may not be added. <p>
+   * Previous results may also be removed because of sumption.
+   */
+  private void addResult(Rule rule) {
+
+    Rule current;
+    boolean added = false;
+
+    /* Iterate the list until we find the right place. */
+    SimpleLinkedList.LinkedListIterator iter = m_results.iterator();
+    while (iter.hasNext()) {
+      current = (Rule) iter.next();
+      if (Rule.confirmationThenObservedComparator.compare(current, rule) > 0) {
+	iter.addBefore(rule);
+	added = true;
+	break;
+      }
+      /* Subsumption tests to see if the rule can be added. */
+      if ((m_subsumption || m_sameClause || m_equivalent)
+	  && current.subsumes(rule)) {
+	if (current.numLiterals() == rule.numLiterals()) {
+	  if (current.equivalentTo(rule)) {
+	    /* Equivalent rules. */
+	    if (m_equivalent) {
+	      return;
+	    }
+	  } else {
+	    /* Same clauses. */
+	    if (m_sameClause
+		&& Rule.confirmationComparator.compare(current, rule) < 0) {
+	      return;
+	    }
+	  }
+	} else {
+	  /* Subsumption. */
+	  if (m_subsumption
+	      && Rule.observedComparator.compare(current, rule) <= 0) {	
+	    return;
+	  }
+	}
+      }
+    }
+
+    if (added == false) {
+      /* The rule must be added in the end of the results. */
+      m_results.add(rule);
+    }
+
+    /* Iterate the results with a lower confirmation 
+     *  to see if some of them must be removed. */
+    SimpleLinkedList.LinkedListInverseIterator inverse 
+      = m_results.inverseIterator();
+    while (inverse.hasPrevious()) {
+      current = (Rule) inverse.previous();
+      if (Rule.confirmationThenObservedComparator.compare(current, rule) < 0) {
+	break;
+      }
+      if (current != rule && rule.subsumes(current)) {
+	if (current.numLiterals() == rule.numLiterals()) {
+	  if (!current.equivalentTo(rule)) {
+	    /* Same clauses. */
+	    if (m_sameClause
+		&& Rule.confirmationComparator.compare(current, rule) > 0) {
+	      inverse.remove();
+	    }
+	  }
+	} else {
+	  /* Subsumption. */
+	  if (m_subsumption
+	      && Rule.observedComparator.compare(rule, current) <= 0) {	
+	    inverse.remove();
+	  }
+	}	
+      }
+    }
+
+    /* Remove the rules with the worst confirmation value 
+     * if there are too many results. */
+    if (m_best != 0 && numValuesInResult() > m_best) {
+      Rule worstRule = (Rule) m_results.getLast();
+      inverse = m_results.inverseIterator();
+      while (inverse.hasPrevious()) {
+	current = (Rule) inverse.previous();
+	if (Rule.confirmationComparator.compare(current, worstRule) < 0) {
+	  break;
+	}
+	inverse.remove();
+      }
+    }
+
+    /* Print the new current values. */
+    printValues();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Method that launches the search to find the rules with the highest 
+   * confirmation.
+   *
+   * @param instances The instances to be used for generating the rules.
+   * @throws Exception if rules can't be built successfully.
+   */
+  public void buildAssociations(Instances instances) throws Exception {
+
+    Frame valuesFrame = null; /* Frame to display the current values. */
+
+    /* Initialization of the search. */
+    if (m_parts == null) {
+      m_instances = new Instances(instances);
+    } else {
+      m_instances = new IndividualInstances(new Instances(instances), m_parts);
+    }    
+    m_results = new SimpleLinkedList();
+    m_hypotheses = 0;
+    m_explored = 0;
+    m_status = NORMAL;
+
+    if (m_classIndex == -1)
+      m_instances.setClassIndex(m_instances.numAttributes()-1);     
+    else if (m_classIndex < m_instances.numAttributes() && m_classIndex >= 0)
+      m_instances.setClassIndex(m_classIndex);
+    else
+      throw new Exception("Invalid class index.");
+    
+    // can associator handle the data?
+    getCapabilities().testWithFail(m_instances);
+    
+    /* Initialization of the window for current values. */
+    if (m_printValues == WINDOW) {
+      m_valuesText = new TextField(37);
+      m_valuesText.setEditable(false);
+      m_valuesText.setFont(new Font("Monospaced", Font.PLAIN, 12));    
+      Label valuesLabel = new Label("Best and worst current values:");
+      Button stop = new Button("Stop search");
+      stop.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    /* Signal the interruption to the search. */
+	    m_status = STOP;
+	  }
+	});
+      valuesFrame = new Frame("Tertius status");
+      valuesFrame.setResizable(false);
+      valuesFrame.add(m_valuesText, BorderLayout.CENTER);
+      valuesFrame.add(stop, BorderLayout.SOUTH);
+      valuesFrame.add(valuesLabel, BorderLayout.NORTH);
+      valuesFrame.pack();
+      valuesFrame.setVisible(true);
+    } else if (m_printValues == OUT) {
+      System.out.println("Best and worst current values:");
+    }
+      
+    Date start = new Date();
+
+    /* Build the predicates and launch the search. */
+    m_predicates = buildPredicates();
+    beginSearch();    
+
+    Date end = new Date();
+
+    if (m_printValues == WINDOW) {
+      valuesFrame.dispose();
+    }
+
+    m_time = new Date(end.getTime() - start.getTime());
+  }
+
+  /**
+   * Run the search.
+   */
+  public void run() {
+    try {
+      search();
+    } catch (OutOfMemoryError e) {
+      /* Garbage collect what can be collected to be able to continue. */
+      System.gc();
+      m_status = MEMORY;
+    }
+    endSearch();
+  }
+
+  /**
+   * Begin the search by starting a new thread.
+   */
+  private synchronized void beginSearch() throws Exception {
+    /* This method must be synchronized to be able to 
+     * call the wait() method. */
+    Thread search = new Thread(this);
+    search.start();
+    try {
+      /* Wait for the end of the thread. */
+      wait();
+    } catch (InterruptedException e) {
+      /* Signal the interruption to the search. */
+      m_status = STOP;
+    }
+  }
+
+  /**
+   * End the search by notifying to the waiting thread that it is finished.
+   */
+  private synchronized void endSearch() {
+    /* This method must be synchronized to be able to
+     * call the notify() method. */
+    notify();
+  }
+
+  /**
+   * Search in the space of hypotheses the rules that have the highest 
+   * confirmation.
+   * The search starts with the empty rule, other rules are generated by 
+   * refinement.
+   */
+  public void search() {
+
+    SimpleLinkedList nodes = new SimpleLinkedList(); /* The agenda. */
+    Rule currentNode;
+    SimpleLinkedList children;
+    SimpleLinkedList.LinkedListIterator iter;
+    Rule child;
+    boolean negBody = (m_negation == BODY || m_negation == ALL);
+    boolean negHead = (m_negation == HEAD || m_negation == ALL);
+
+    /* Start with the empty rule. */
+      nodes.add(new Rule(m_repeat, m_numLiterals, negBody, negHead,
+			 m_classification, m_horn));
+    
+    /* Print the current values. */
+    printValues();
+
+    /* Explore the rules in the agenda. */
+    while (m_status != STOP && !nodes.isEmpty()) {
+      currentNode = (Rule) nodes.removeFirst();
+      if (canRefine(currentNode)) {
+	children = currentNode.refine(m_predicates);
+	iter = children.iterator();
+	/* Calculate the optimistic estimate of the children and 
+	 * consider them for adding to the agenda and to the results. */
+	while (iter.hasNext()) {
+	  m_hypotheses++;
+	  child = (Rule) iter.next();
+	  child.upDate(m_instances);
+	  if (canCalculateOptimistic(child)) {
+	    child.calculateOptimistic();
+	    if (canExplore(child)) {
+	      m_explored++;
+	      if (canStoreInNodes(child)) {
+	      } else {
+		iter.remove();
+	      }
+	      if (canCalculateConfirmation(child)) {
+		child.calculateConfirmation();
+		if (canStoreInResults(child)) {
+		  addResult(child);
+		}	  
+	      }
+	    } else {
+	      iter.remove();
+	    }
+	  } else {
+	    iter.remove();
+	  }
+	}
+	/* Since the agenda is already sorted it is more efficient
+	 * to sort the children only and then merge. */
+	children.sort(Rule.optimisticThenObservedComparator);
+	nodes.merge(children, Rule.optimisticThenObservedComparator);
+      } else {
+	/* The agenda being sorted, it is not worth considering the following 
+	 * nodes. */
+	break;
+      }
+    }
+  }
+
+  /**
+   * returns the results
+   * 
+   * @return		the results
+   */
+  public SimpleLinkedList getResults() {
+    return m_results;
+  }
+
+  /**
+   * Print the current best and worst values. 
+   */
+  private void printValues() {
+
+    if (m_printValues == NO) {
+      return;
+    } else {
+      if (m_results.isEmpty()) {
+	if (m_printValues == OUT) {
+	  System.out.print("0.000000 0.000000 - 0.000000 0.000000");
+	} else { //m_printValues == WINDOW
+	  m_valuesText.setText("0.000000 0.000000 - 0.000000 0.000000");
+	}
+      } else {
+	Rule best = (Rule) m_results.getFirst();
+	Rule worst = (Rule) m_results.getLast();
+	String values = best.valuesToString() + " - " + worst.valuesToString();
+	if (m_printValues == OUT) {
+	  System.out.print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
+			   + "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
+	  System.out.print(values);
+	} else { //m_printValues == WINDOW
+	  m_valuesText.setText(values);
+	}
+      }
+    }
+  }
+
+  /**
+   * Outputs the best rules found with their confirmation value and number 
+   * of counter-instances.
+   * Also gives the number of hypotheses considered and explored, and the 
+   * time needed. 
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    SimpleLinkedList.LinkedListIterator iter = m_results.iterator();
+    int size = m_results.size();
+    int i = 0;
+
+    text.append("\nTertius\n=======\n\n");
+
+    while (iter.hasNext()) {
+      Rule current = (Rule) iter.next();
+      text.append(Utils.doubleToString((double) i + 1,
+				       (int) (Math.log(size) 
+					      / Math.log(10) + 1),
+				       0)
+		  + ". ");
+      text.append("/* ");
+      if (m_roc) {
+	text.append(current.rocToString());
+      } else {
+	text.append(current.valuesToString());
+      }
+      text.append(" */ ");
+      text.append(current.toString());
+      text.append("\n");
+      i++;
+    }
+ 
+    text.append("\nNumber of hypotheses considered: " + m_hypotheses);
+    text.append("\nNumber of hypotheses explored: " + m_explored);
+
+    if (m_status == MEMORY) {
+      text.append("\n\nNot enough memory to continue the search");
+    } else if (m_status == STOP) {
+      text.append("\n\nSearch interrupted");
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5444 $");
+  }
+
+  /**
+   * Main method.
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    runAssociator(new Tertius(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/gsp/Element.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/gsp/Element.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/gsp/Element.java	(revision 29)
@@ -0,0 +1,302 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Element.java
+ * Copyright (C) 2007 Sebastian Beer
+ *
+ */
+
+package weka.associations.gsp;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Class representing an Element, i.e., a set of events/items.
+ * 
+ * @author  Sebastian Beer
+ * @version $Revision: 1.2 $
+ */
+public class Element
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7900701276019516371L;
+  
+  /** events/items stored as an array of ints */
+  protected int[] m_Events;
+
+  /**
+   * Constructor
+   */
+  public Element() {
+  }
+
+  /**
+   * Constructor accepting an initial size of the events Array as parameter.
+   * 
+   * @param size 	the size
+   */
+  public Element(int size) {
+    m_Events = new int[size];
+  }
+
+  /**
+   * Returns all events of the given data set as Elements containing a single 
+   * event. The order of events is determined by the header information of 
+   * the corresponding ARFF file.
+   * 
+   * @param instances 	the data set
+   * @return 		the set of 1-Elements
+   */
+  public static FastVector getOneElements (Instances instances) {
+    FastVector setOfOneElements = new FastVector();
+    Element curElement;
+
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      for (int j = 0; j < instances.attribute(i).numValues(); j++) {
+	curElement = new Element();
+	curElement.setEvents(new int [instances.numAttributes()]);
+	for (int k = 0; k < instances.numAttributes(); k++) {
+	  curElement.getEvents()[k] = -1;
+	}
+	curElement.getEvents()[i] = j;
+	setOfOneElements.addElement(curElement);
+      }
+    }
+    return setOfOneElements;
+  }
+
+  /**
+   * Merges two Elements into one.
+   * 
+   * @param element1 	first Element
+   * @param element2 	second Element
+   * @return 		the merged Element
+   */
+  public static Element merge(Element element1, Element element2) {
+    int[] element1Events = element1.getEvents();
+    int[] element2Events = element2.getEvents();
+    Element resultElement = new Element(element1Events.length);
+    int[] resultEvents = resultElement.getEvents();
+
+    for (int i = 0; i < element1Events.length; i++) {
+      if (element2Events[i] > -1) {
+	resultEvents[i] = element2Events[i];
+      } else {
+	resultEvents[i] = element1Events[i];
+      }
+    }
+    resultElement.setEvents(resultEvents);
+
+    return resultElement;
+  }
+
+  /**
+   * Returns a deep clone of an Element.
+   * 
+   * @return 		the cloned Element
+   */
+  public Element clone() {
+    try {
+      Element clone = (Element) super.clone();
+      int[] cloneEvents = new int[m_Events.length];
+
+      for (int i = 0; i < m_Events.length; i++) {
+	cloneEvents[i] = m_Events[i];
+      }
+      clone.setEvents(cloneEvents);
+
+      return clone;
+    } catch (CloneNotSupportedException exc) {
+      exc.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Checks if an Element contains over one event.
+   * 
+   * @return 		true, if the Element contains over one event, else false
+   */
+  public boolean containsOverOneEvent() {
+    int numEvents = 0;
+    for (int i = 0; i < m_Events.length; i++) {
+      if (m_Events[i] > -1) {
+	numEvents++;
+      }
+      if (numEvents == 2) {
+	return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Deletes the first or last event of an Element.
+   * 
+   * @param position 	the position of the event to be deleted (first or last)
+   */
+  public void deleteEvent(String position) {
+    if (position.equals("first")) {
+      //delete first event
+      for (int i = 0; i < m_Events.length; i++) {
+	if (m_Events[i] > -1) {
+	  m_Events[i] = -1;
+	  break;
+	}
+      }
+    }
+    if (position.equals("last")) {
+      //delete last event
+      for (int i = m_Events.length-1; i >= 0; i--) {
+	if (m_Events[i] > -1) {
+	  m_Events[i] = -1;
+	  break;
+	}
+      }
+    }
+  }
+
+  /**
+   * Checks if two Elements are equal.
+   * 
+   * @return 		true, if the two Elements are equal, else false
+   */
+  public boolean equals(Object obj) {
+    Element element2 = (Element) obj;
+
+    for (int i=0; i < m_Events.length; i++) {
+      if (!(m_Events[i] == element2.getEvents()[i])) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the events Array of an Element.
+   * 
+   * @return 		the events Array
+   */
+  public int[] getEvents() {
+    return m_Events;
+  }
+
+  /**
+   * Checks if an Element is contained by a given Instance.
+   * 
+   * @param instance 	the given Instance
+   * @return 		true, if the Instance contains the Element, else false
+   */
+  public boolean isContainedBy(Instance instance) {
+    for (int i=0; i < instance.numAttributes(); i++) {
+      if (m_Events[i] > -1) {
+	if (instance.isMissing(i)) {
+	  return false;
+	}
+	if (m_Events[i] != (int) instance.value(i)) {
+	  return false;
+	}
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Checks if the Element contains any events.
+   * 
+   * @return 		true, if the Element contains no event, else false 
+   */
+  public boolean isEmpty() {
+    for (int i=0; i < m_Events.length; i++) {
+      if (m_Events[i] > -1) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Sets the events Array of an Element.
+   * 
+   * @param events 	the events Array to set
+   */
+  protected void setEvents(int[] events) {
+    m_Events = events;
+  }
+
+  /**
+   * Returns a String representation of an Element where the numeric value 
+   * of each event/item is represented by its respective nominal value.
+   * 
+   * @param dataSet 	the corresponding data set containing the header information
+   * @return 		the String representation
+   */
+  public String toNominalString(Instances dataSet) {
+    StringBuffer result = new StringBuffer();
+    int addedValues = 0;
+
+    result.append("{");
+
+    for (int i=0; i < m_Events.length; i++) {
+      if (m_Events[i] > -1) {			
+	result.append(dataSet.attribute(i).value(m_Events[i]) + ",");
+	addedValues++;
+      }
+    }
+    result.deleteCharAt(result.length()-1);
+    result.append("}");
+
+    return result.toString();
+  }
+
+  /**
+   * Returns a String representation of an Element.
+   * 
+   * @return 		the String representation
+   */
+  public String toString() {
+    String result = "";
+
+    result += "{";
+
+    for (int i=0; i < m_Events.length; i++) {
+      result += m_Events[i];
+      if (i+1 < m_Events.length) {
+	result += ",";
+      }
+    }
+    result += "}";
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/gsp/Sequence.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/gsp/Sequence.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/gsp/Sequence.java	(revision 29)
@@ -0,0 +1,584 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Sequence.java
+ * Copyright (C) 2007 Sebastian Beer
+ *
+ */
+
+package weka.associations.gsp;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+/**
+ * Class representing a sequence of elements/itemsets.
+ * 
+ * @author  Sebastian Beer
+ * @version $Revision: 1.2 $
+ */
+public class Sequence
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5001018056339156390L;
+
+  /** the support count of the Sequence */
+  protected int m_SupportCount;
+  
+  /** ordered list of the comprised elements/itemsets */
+  protected FastVector m_Elements;
+
+  /**
+   * Constructor.
+   */
+  public Sequence() {
+    m_SupportCount = 0;
+    m_Elements = new FastVector();
+  }
+
+  /**
+   * Constructor accepting a set of elements as parameter.
+   * 
+   * @param elements 		the Elements of the Sequence
+   */
+  public Sequence(FastVector elements) {
+    m_SupportCount = 0;
+    m_Elements = elements;
+  }
+
+  /**
+   * Constructor accepting an int value as parameter to set the support count.
+   * 
+   * @param supportCount 	the support count to set
+   */
+  public Sequence(int supportCount) {
+    m_SupportCount = supportCount;
+    m_Elements = new FastVector();
+  }
+
+  /**
+   * Generates all possible candidate k-Sequences and prunes the ones that 
+   * contain an infrequent (k-1)-Sequence.
+   * 
+   * @param kMinusOneSequences 	the set of (k-1)-Sequences, used for verification
+   * @return 			the generated set of k-candidates
+   * @throws CloneNotSupportedException
+   */
+  public static FastVector aprioriGen(FastVector kMinusOneSequences) throws CloneNotSupportedException {
+    FastVector allCandidates = generateKCandidates(kMinusOneSequences);
+    FastVector prunedCandidates = pruneCadidates(allCandidates, kMinusOneSequences);
+
+    return prunedCandidates;
+  }
+
+  /**
+   * Deletes Sequences of a given set which don't meet the minimum support 
+   * count threshold.
+   * 
+   * @param sequences 		the set Sequences to be checked
+   * @param minSupportCount 	the minimum support count
+   * @return 			the set of Sequences after deleting
+   */
+  public static FastVector deleteInfrequentSequences(FastVector sequences, long minSupportCount) {
+    FastVector deletedSequences = new FastVector();
+    Enumeration seqEnum = sequences.elements();
+
+    while (seqEnum.hasMoreElements()) {
+      Sequence currentSeq = (Sequence) seqEnum.nextElement();
+      long curSupportCount = currentSeq.getSupportCount();
+
+      if (curSupportCount >= minSupportCount) {
+	deletedSequences.addElement(currentSeq);
+      }
+    }
+    return deletedSequences;
+  }
+
+  /**
+   * Generates candidate k-Sequences on the basis of a given (k-1)-Sequence set.
+   * 
+   * @param kMinusOneSequences 	the set of (k-1)-Sequences
+   * @return 			the set of candidate k-Sequences
+   * @throws CloneNotSupportedException
+   */
+  protected static FastVector generateKCandidates(FastVector kMinusOneSequences) throws CloneNotSupportedException {
+    FastVector candidates = new FastVector();
+    FastVector mergeResult = new FastVector();
+
+    for (int i = 0; i < kMinusOneSequences.size(); i++) {
+      for (int j = 0; j < kMinusOneSequences.size(); j++) {
+	Sequence originalSeq1 = (Sequence) kMinusOneSequences.elementAt(i);
+	Sequence seq1 = originalSeq1.clone();
+	Sequence originalSeq2 = (Sequence) kMinusOneSequences.elementAt(j);
+	Sequence seq2 = originalSeq2.clone();
+	Sequence subseq1 = seq1.deleteEvent("first");
+	Sequence subseq2 = seq2.deleteEvent("last");
+
+	if (subseq1.equals(subseq2)) {
+	  //seq1 and seq2 are 1-sequences
+	  if ((subseq1.getElements().size() == 0) && (subseq2.getElements().size() == 0)) {
+	    if (i >= j) {
+	      mergeResult = merge(seq1, seq2, true, true);
+	    } else {
+	      mergeResult = merge(seq1, seq2, true, false);
+	    }
+	    //seq1 and seq2 are k-sequences
+	  } else {
+	    mergeResult = merge(seq1, seq2, false, false);
+	  }
+	  candidates.appendElements(mergeResult);
+	}
+      }
+    }
+    return candidates;
+  }
+
+  /**
+   * Merges two Sequences in the course of candidate generation. Differentiates 
+   * between merging 1-Sequences and k-Sequences, k > 1.
+   * 
+   * @param seq1 		Sequence at first position
+   * @param seq2 		Sequence at second position
+   * @param oneElements 	true, if 1-Elements should be merged, else false
+   * @param mergeElements 	true, if two 1-Elements were not already merged 
+   * 				(regardless of their position), else false
+   * @return 			set of resulting Sequences
+   */
+  protected static FastVector merge(Sequence seq1, Sequence seq2, boolean oneElements, boolean mergeElements) {
+    FastVector mergeResult = new FastVector();
+
+    //merge 1-sequences
+    if (oneElements) {
+      Element element1 = (Element) seq1.getElements().firstElement();
+      Element element2 = (Element) seq2.getElements().firstElement();
+      Element element3 = null;
+      if (mergeElements) {
+	for (int i = 0; i < element1.getEvents().length; i++) {
+	  if (element1.getEvents()[i] > -1) {
+	    if (element2.getEvents()[i] > -1) {
+	      break;
+	    } else {
+	      element3 = Element.merge(element1, element2);
+	    }
+	  }
+	}
+      }
+      FastVector newElements1 = new FastVector();
+      //generate <{x}{y}>
+      newElements1.addElement(element1);
+      newElements1.addElement(element2);
+      mergeResult.addElement(new Sequence(newElements1));
+      //generate <{x,y}>
+      if (element3 != null) {
+	FastVector newElements2 = new FastVector();
+	newElements2.addElement(element3);
+	mergeResult.addElement(new Sequence(newElements2));
+      }
+
+      return mergeResult;
+      //merge k-sequences, k > 1
+    } else {
+      Element lastElementSeq1 = (Element) seq1.getElements().lastElement();
+      Element lastElementSeq2 = (Element) seq2.getElements().lastElement();
+      Sequence resultSeq = new Sequence();
+      FastVector resultSeqElements = resultSeq.getElements();
+
+      //if last two events/items belong to the same element/itemset
+      if (lastElementSeq2.containsOverOneEvent()) {
+	for (int i = 0; i < (seq1.getElements().size()-1); i++) {
+	  resultSeqElements.addElement(seq1.getElements().elementAt(i));
+	}
+	resultSeqElements.addElement(Element.merge(lastElementSeq1, lastElementSeq2));
+	mergeResult.addElement(resultSeq);
+
+	return mergeResult;
+	//if last two events/items belong to different elements/itemsets
+      } else {
+	for (int i = 0; i < (seq1.getElements().size()); i++) {
+	  resultSeqElements.addElement(seq1.getElements().elementAt(i));
+	}
+	resultSeqElements.addElement(lastElementSeq2);
+	mergeResult.addElement(resultSeq);
+
+	return mergeResult;
+      }
+    }
+  }
+
+  /**
+   * Converts a set of 1-Elements into a set of 1-Sequences.
+   * 
+   * @param elements 		the set of 1-Elements
+   * @return 			the set of 1-Sequences
+   */
+  public static FastVector oneElementsToSequences(FastVector elements) {
+    FastVector sequences = new FastVector();
+    Enumeration elementEnum = elements.elements();
+
+    while (elementEnum.hasMoreElements()) {
+      Sequence seq = new Sequence();
+      FastVector seqElements = seq.getElements();
+      seqElements.addElement(elementEnum.nextElement());
+      sequences.addElement(seq);
+    }
+    return sequences;
+  }
+
+  /**
+   * Prints a set of Sequences as String output.
+   * 
+   * @param setOfSequences	the set of sequences
+   */
+  public static void printSetOfSequences(FastVector setOfSequences) {
+    Enumeration seqEnum = setOfSequences.elements();
+    int i = 1;
+
+    while(seqEnum.hasMoreElements()) {
+      Sequence seq = (Sequence) seqEnum.nextElement();
+      System.out.print("[" + i++ + "]" + " " + seq.toString());
+    }
+  }
+
+  /**
+   * Prunes a k-Sequence of a given candidate set if one of its (k-1)-Sequences 
+   * is infrequent.
+   * 
+   * @param allCandidates 	the set of all potential k-Sequences
+   * @param kMinusOneSequences 	the set of (k-1)-Sequences for verification
+   * @return 			the set of the pruned candidates
+   */
+  protected static FastVector pruneCadidates(FastVector allCandidates, FastVector kMinusOneSequences) {
+    FastVector prunedCandidates = new FastVector();
+    boolean isFrequent;
+    //for each candidate
+    for (int i = 0; i < allCandidates.size(); i++) {
+      Sequence candidate = (Sequence) allCandidates.elementAt(i);
+      isFrequent = true;
+      FastVector canElements = candidate.getElements();
+      //generate each possible (k-1)-sequence and verify if it's frequent
+      for (int j = 0; j < canElements.size(); j++) {
+	if(isFrequent) {
+	  Element origElement = (Element) canElements.elementAt(j);
+	  int[] origEvents = origElement.getEvents();
+
+	  for (int k = 0; k < origEvents.length; k++) {
+	    if (origEvents[k] > -1) {
+	      int helpEvent = origEvents[k];
+	      origEvents[k] = -1;
+
+	      if (origElement.isEmpty()) {
+		canElements.removeElementAt(j);
+		//check if the (k-1)-sequence is contained in the set of kMinusOneSequences
+		int containedAt = kMinusOneSequences.indexOf(candidate);
+		if (containedAt != -1) {
+		  origEvents[k] = helpEvent;
+		  canElements.insertElementAt(origElement, j);
+		  break;
+		} else {
+		  isFrequent = false;
+		  break;
+		}
+	      } else {
+		//check if the (k-1)-sequence is contained in the set of kMinusOneSequences
+		int containedAt = kMinusOneSequences.indexOf(candidate);
+		if (containedAt != -1) {
+		  origEvents[k] = helpEvent;
+		  continue;
+		} else {
+		  isFrequent = false;
+		  break;
+		}
+	      }
+	    }
+	  }
+	} else {
+	  break;
+	}
+      }
+      if (isFrequent) {
+	prunedCandidates.addElement(candidate);
+      }
+    }
+    return prunedCandidates;
+  }
+
+  /**
+   * Returns a String representation of a set of Sequences where the numeric 
+   * value of each event/item is represented by its respective nominal value.
+   * 
+   * @param setOfSequences 	the set of Sequences
+   * @param dataSet 		the corresponding data set containing the header 
+   * 				information
+   * @param filterAttributes	the attributes to filter out
+   * @return 			the String representation
+   */
+  public static String setOfSequencesToString(FastVector setOfSequences, Instances dataSet, FastVector filterAttributes) {
+    StringBuffer resString = new StringBuffer();
+    Enumeration SequencesEnum = setOfSequences.elements();
+    int i = 1;
+    boolean printSeq;
+
+    while(SequencesEnum.hasMoreElements()) {
+      Sequence seq = (Sequence) SequencesEnum.nextElement();
+      Integer filterAttr = (Integer) filterAttributes.elementAt(0);
+      printSeq = true;
+
+      if (filterAttr.intValue() != -1) {
+	for (int j=0; j < filterAttributes.size(); j++) {
+	  filterAttr = (Integer) filterAttributes.elementAt(j);
+	  FastVector seqElements = seq.getElements();
+
+	  if (printSeq) {
+	    for (int k=0; k < seqElements.size(); k++) {
+	      Element currentElement = (Element) seqElements.elementAt(k);
+	      int[] currentEvents = currentElement.getEvents();
+
+	      if (currentEvents[filterAttr.intValue()] != -1) {
+		continue;
+	      } else {
+		printSeq = false;
+		break;
+	      }
+	    }
+	  }
+	}
+      }
+      if (printSeq) {
+	resString.append("[" + i++ + "]" + " " + seq.toNominalString(dataSet));
+      }
+    }
+    return resString.toString();
+  }
+
+  /**
+   * Updates the support count of a set of Sequence candidates according to a 
+   * given set of data sequences.
+   * 
+   * @param candidates 		the set of candidates
+   * @param dataSequences 	the set of data sequences
+   */
+  public static void updateSupportCount(FastVector candidates, FastVector dataSequences) {
+    Enumeration canEnumeration = candidates.elements();
+
+    while(canEnumeration.hasMoreElements()){
+      Enumeration dataSeqEnumeration = dataSequences.elements();
+      Sequence candidate = (Sequence) canEnumeration.nextElement();
+
+      while(dataSeqEnumeration.hasMoreElements()) {
+	Instances dataSequence = (Instances) dataSeqEnumeration.nextElement();
+
+	if (candidate.isSubsequenceOf(dataSequence)) {
+	  candidate.setSupportCount(candidate.getSupportCount() + 1);
+	}
+      }
+    }
+  }
+
+  /**
+   * Returns a deep clone of a Sequence.
+   * 
+   * @return 		the cloned Sequence
+   */
+  public Sequence clone() {
+    try {
+      Sequence clone = (Sequence) super.clone();
+
+      clone.setSupportCount(m_SupportCount);
+      FastVector cloneElements = new FastVector(m_Elements.size());
+
+      for (int i = 0; i < m_Elements.size(); i++) {
+	Element helpElement = (Element) m_Elements.elementAt(i);
+	cloneElements.addElement(helpElement.clone());
+      }
+      clone.setElements(cloneElements);
+
+      return clone;
+    } catch (CloneNotSupportedException exc) {
+      exc.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Deletes either the first or the last event/item of a Sequence. If the 
+   * deleted event/item is the only value in the Element, it is removed, as well.
+   * 
+   * @param position 		the position of the event/item (first or last)
+   * @return 			the Sequence with either the first or the last 
+   * 				event/item deleted
+   */
+  protected Sequence deleteEvent(String position) {
+    Sequence cloneSeq = clone();
+
+    if (position.equals("first")) {
+      Element element = (Element) cloneSeq.getElements().firstElement();
+      element.deleteEvent("first");
+      if (element.isEmpty()) {
+	cloneSeq.getElements().removeElementAt(0);
+      }
+      return cloneSeq;
+    }
+    if (position.equals("last")) {
+      Element element = (Element) cloneSeq.getElements().lastElement();
+      element.deleteEvent("last");
+      if (element.isEmpty()) {
+	cloneSeq.getElements().removeElementAt(m_Elements.size()-1);
+      }
+      return cloneSeq;
+    }
+    return null;
+  }
+
+  /**
+   * Checks if two Sequences are equal.
+   * 
+   * @return 			true, if the two Sequences are equal, else false
+   */
+  public boolean equals(Object obj) {
+    Sequence seq2 = (Sequence) obj;
+    FastVector seq2Elements = seq2.getElements();
+
+    for (int i = 0; i < m_Elements.size(); i++) {
+      Element thisElement = (Element) m_Elements.elementAt(i);
+      Element seq2Element = (Element) seq2Elements.elementAt(i);
+      if (!thisElement.equals(seq2Element)) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the Elements of the Sequence.
+   * 
+   * @return 			the Elements
+   */
+  protected FastVector getElements() {
+    return m_Elements;
+  }
+
+  /**
+   * Returns the support count of the Sequence.
+   * 
+   * @return 			the support count
+   */
+  protected int getSupportCount() {
+    return m_SupportCount;
+  }
+
+  /**
+   * Checks if the Sequence is subsequence of a given data sequence.
+   * 
+   * @param dataSequence 	the data sequence to verify against
+   * @return 			true, if the Sequnce is subsequence of the data 
+   * 				sequence, else false
+   */
+  protected boolean isSubsequenceOf(Instances dataSequence) {
+    FastVector elements = getElements();
+    Enumeration elementEnum = elements.elements();
+    Element curElement = (Element) elementEnum.nextElement();
+
+    for (int i = 0; i < dataSequence.numInstances(); i++) {
+      if (curElement.isContainedBy(dataSequence.instance(i))) {
+	if (!elementEnum.hasMoreElements()) {
+	  return true;
+	} else {
+	  curElement = (Element) elementEnum.nextElement();
+	  continue;
+	}
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Sets the Elements of the Sequence.
+   * 
+   * @param elements 		the Elements to set
+   */
+  protected void setElements(FastVector elements) {
+    m_Elements = elements;
+  }
+
+  /**
+   * Sets the support count of the Sequence.
+   * 
+   * @param supportCount 	the support count to set
+   */
+  protected void setSupportCount(int supportCount) {
+    m_SupportCount = supportCount;
+  }
+
+  /**
+   * Returns a String representation of a Sequences where the numeric value 
+   * of each event/item is represented by its respective nominal value.
+   * 
+   * @param dataSet 		the corresponding data set containing the header 
+   * 				information
+   * @return 			the String representation
+   */
+  public String toNominalString(Instances dataSet) {
+    String result = "";
+
+    result += "<";
+
+    for (int i = 0; i < m_Elements.size(); i++) {
+      Element element = (Element) m_Elements.elementAt(i);
+      result += element.toNominalString(dataSet);
+    }
+    result += "> (" + getSupportCount() + ")\n";
+
+    return result;
+  }
+
+  /**
+   * Returns a String representation of a Sequence.
+   * 
+   * @return 			the String representation
+   */
+  public String toString() {
+    String result = "";
+
+    result += "Sequence Output\n";
+    result += "------------------------------\n";
+    result += "Support Count: " + getSupportCount() + "\n";
+    result += "contained elements/itemsets:\n";
+
+    for (int i = 0; i < m_Elements.size(); i++) {
+      Element element = (Element) m_Elements.elementAt(i);
+      result += element.toString();
+    }
+    result += "\n\n";
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/AttributeValueLiteral.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/AttributeValueLiteral.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/AttributeValueLiteral.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeValueLiteral.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.RevisionUtils;
+import weka.associations.Tertius;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.6 $
+ */
+public class AttributeValueLiteral
+  extends Literal {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4077436297281456239L;
+
+  private String m_value;
+  
+  private int m_index;
+  
+  public AttributeValueLiteral(Predicate predicate, String value, 
+			       int index, int sign, int missing) {
+
+    super(predicate, sign, missing);
+    m_value = value;
+    m_index = index;
+  }
+
+  public boolean satisfies(Instance instance) {
+
+    if (m_index == -1) {
+      if (positive()) {
+	return instance.isMissing(getPredicate().getIndex());
+      } else {
+	return !instance.isMissing(getPredicate().getIndex());
+      }
+    } else if (instance.isMissing(getPredicate().getIndex())) {
+      if (positive()) {
+	return false;
+      } else {
+	return m_missing != Tertius.EXPLICIT;
+      }
+    } else {
+      if (positive()) {
+	return (instance.value(getPredicate().getIndex()) == m_index);
+      } else {
+	return (instance.value(getPredicate().getIndex()) != m_index);
+      }
+    }
+  }
+
+  public boolean negationSatisfies(Instance instance) {
+
+    if (m_index == -1) {
+      if (positive()) {
+	return !instance.isMissing(getPredicate().getIndex());
+      } else {
+	return instance.isMissing(getPredicate().getIndex());
+      }
+    } else if (instance.isMissing(getPredicate().getIndex())) {
+      if (positive()) {
+	return m_missing != Tertius.EXPLICIT;
+      } else {
+	return false;
+      }
+    } else {
+      if (positive()) {
+	return (instance.value(getPredicate().getIndex()) != m_index);
+      } else {
+	return (instance.value(getPredicate().getIndex()) == m_index);
+      }
+    }
+  }
+
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    if (negative()) {
+      text.append("not ");
+    }
+    text.append(getPredicate().toString() + " = " + m_value);
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/Body.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/Body.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/Body.java	(revision 29)
@@ -0,0 +1,123 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Body.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.util.Iterator;
+
+/**
+ * Class representing the body of a rule.
+ *
+ * @author  <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
+ * @version $Revision: 1.6 $
+ */
+
+public class Body
+  extends LiteralSet {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 4870689270432218016L;
+
+  /**
+   * Constructor without storing the counter-instances.
+   */
+  public Body() {
+
+    super();
+  }
+
+  /**
+   * Constructor storing the counter-instances.
+   *
+   * @param instances The dataset.
+   */
+  public Body(Instances instances) {
+
+    super(instances);
+  }
+
+  /**
+   * Test if an instance can be kept as a counter-instance,
+   * if a new literal is added to this body.
+   *
+   * @param instance The instance to test.
+   * @param newLit The new literal.
+   * @return True if the instance is still a counter-instance 
+   * (if the new literal satisfies the instance).
+   */
+  public boolean canKeep(Instance instance, Literal newLit) {
+
+    return newLit.satisfies(instance);
+  }
+
+  /**
+   * Test if this Body is included in a rule.
+   * It is the literals of this Body are contained in the body of the other rule,
+   * or if their negation is included in the head of the other rule.
+   */
+  public boolean isIncludedIn(Rule otherRule) {
+
+    Iterator iter = this.enumerateLiterals();
+    while (iter.hasNext()) {
+      Literal current = (Literal) iter.next();
+      if (!otherRule.bodyContains(current)
+	  && !otherRule.headContains(current.getNegation())) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Gives a String representation of this set of literals as a conjunction.
+   */
+  public String toString() {
+    Iterator iter = this.enumerateLiterals();
+
+    if (!iter.hasNext()) {
+      return "TRUE";
+    }
+    StringBuffer text = new StringBuffer();
+    while (iter.hasNext()) {
+      text.append(iter.next().toString());
+      if (iter.hasNext()) {
+	text.append(" and ");
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/Head.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/Head.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/Head.java	(revision 29)
@@ -0,0 +1,121 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Head.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.util.Iterator;
+
+/**
+ * Class representing the head of a rule.
+ *
+ * @author  <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
+ * @version $Revision: 1.6 $
+ */
+
+public class Head
+  extends LiteralSet {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 5068076274253706199L;
+
+  /**
+   * Constructor without storing the counter-instances.
+   */
+  public Head() {
+
+    super();
+  }
+
+  /**
+   * Constructor storing the counter-instances.
+   *
+   * @param instances The dataset.
+   */
+  public Head(Instances instances) {
+
+    super(instances);
+  }
+
+  /**
+   * Test if an instance can be kept as a counter-instance,
+   * if a new literal is added to this head.
+   *
+   * @param instance The instance to test.
+   * @param newLit The new literal.
+   * @return True if the instance is still a counter-instance 
+   * (if the negation of the new literal satisfies the instance).
+   */
+  public boolean canKeep(Instance instance, Literal newLit) {
+    return newLit.negationSatisfies(instance);
+  }
+
+  /**
+   * Test if this Head is included in a rule.
+   * It is the literals of this Head are contained in the head of the other rule,
+   * or if their negation is included in the body of the other rule.
+   */  public boolean isIncludedIn(Rule otherRule) {
+    Iterator iter = this.enumerateLiterals();
+    while (iter.hasNext()) {
+      Literal current = (Literal) iter.next();
+      if (!otherRule.headContains(current)
+	  && !otherRule.bodyContains(current.getNegation())) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Gives a String representation of this set of literals as a disjunction.
+   */
+  public String toString() {
+    Iterator iter = this.enumerateLiterals();
+
+    if (!iter.hasNext()) {
+      return "FALSE";
+    }
+
+    StringBuffer text = new StringBuffer();
+    while (iter.hasNext()) {
+      text.append(iter.next().toString());
+      if (iter.hasNext()) {
+	text.append(" or ");
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstance.java	(revision 29)
@@ -0,0 +1,77 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IndividualInstance.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 5987 $
+ */
+public class IndividualInstance
+  extends DenseInstance {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7903938733476585114L;
+  
+  private Instances m_parts;
+
+  public IndividualInstance(Instance individual, Instances parts) {
+
+    super(individual);
+    m_parts = parts;
+  }
+
+  public IndividualInstance(IndividualInstance instance) {
+
+    super(instance);
+    m_parts = instance.m_parts;
+  }
+
+  public Object copy() {
+
+    IndividualInstance result = new IndividualInstance(this);
+    result.m_Dataset = m_Dataset;
+    return result;
+  }
+
+  public Instances getParts() {
+	
+    return m_parts;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualInstances.java	(revision 29)
@@ -0,0 +1,84 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IndividualInstances.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ *
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.util.Enumeration;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.6 $
+ */
+public class IndividualInstances
+  extends Instances {
+    
+  /** for serialization */
+  private static final long serialVersionUID = -7355054814895636733L;
+
+  public IndividualInstances(Instances individuals, Instances parts) 
+    throws Exception {
+	
+    super(individuals, individuals.numInstances());
+
+    Attribute individualIdentifier = attribute("id");
+    if (individualIdentifier == null) {
+      throw new Exception("No identifier found in individuals dataset.");
+    }
+    Attribute partIdentifier = parts.attribute("id");
+    if (partIdentifier == null) {
+      throw new Exception("No identifier found in parts dataset.");
+    }
+
+    Enumeration enumIndividuals = individuals.enumerateInstances();
+    while (enumIndividuals.hasMoreElements()) {
+      Instance individual = (Instance) enumIndividuals.nextElement();
+      Instances partsOfIndividual = new Instances(parts, 0);
+      Enumeration enumParts = parts.enumerateInstances();
+      while (enumParts.hasMoreElements()) {
+	Instance part = (Instance) enumParts.nextElement();
+	if (individual.value(individualIdentifier)
+	    == part.value(partIdentifier)) {
+	  partsOfIndividual.add(part);
+	}
+      }
+      add(new IndividualInstance(individual, partsOfIndividual));
+    }	
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualLiteral.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualLiteral.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/IndividualLiteral.java	(revision 29)
@@ -0,0 +1,64 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IndividualLiteral.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ *
+ */
+
+package weka.associations.tertius;
+
+import weka.core.RevisionUtils;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.5 $
+ */
+public class IndividualLiteral extends AttributeValueLiteral {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4712404824517887435L;
+  
+  private int m_type;
+  public static int INDIVIDUAL_PROPERTY = 0;
+  public static int PART_PROPERTY = 1;
+  
+  public IndividualLiteral(Predicate predicate, String value, int index,
+			   int sign, int missing, int type) {
+
+    super(predicate, value, index, sign, missing);
+    m_type = type;
+  }
+  
+  public int getType() {
+
+    return m_type;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/Literal.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/Literal.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/Literal.java	(revision 29)
@@ -0,0 +1,100 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Literal.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ *
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.5 $
+ */
+public abstract class Literal
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2675363669503575771L;
+  
+  private Predicate m_predicate;
+
+  public static final int NEG = 0;
+
+  public static final int POS = 1;
+
+  private int m_sign;
+
+  private Literal m_negation;
+
+  protected int m_missing;
+
+  public Literal(Predicate predicate, int sign, int missing) {
+
+    m_predicate = predicate;
+    m_sign = sign;
+    m_negation = null;
+    m_missing = missing;
+  }
+
+  public Predicate getPredicate() {
+
+    return m_predicate;
+  }
+
+  public Literal getNegation() {
+
+    return m_negation;
+  }
+
+  public void setNegation(Literal negation) {
+
+    m_negation = negation;
+  }
+
+  public boolean positive() {
+
+    return m_sign == POS;
+  }
+
+  public boolean negative() {
+
+    return m_sign == NEG;
+  }
+
+  public abstract boolean satisfies(Instance instance);
+  
+  public abstract boolean negationSatisfies(Instance instance);
+  
+  public abstract String toString();
+}
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/LiteralSet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/LiteralSet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/LiteralSet.java	(revision 29)
@@ -0,0 +1,383 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* 
+ *    LiteralSet.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * Class representing a set of literals, being either the body or the head
+ * of a rule.
+ *
+ * @author <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
+ * @version $Revision: 1.7 $
+ */
+
+public abstract class LiteralSet
+  implements Serializable, Cloneable, RevisionHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 6094536488654503152L;
+  
+  /** Literals contained in this set. */
+  private ArrayList m_literals;
+
+  /** Last literal added to this set. */
+  private Literal m_lastLiteral;
+  
+  /** Number of instances in the data the set deals with. */
+  private int m_numInstances;
+
+  /** Set of counter-instances of this part of the rule. */
+  private ArrayList m_counterInstances;
+  /* For a body, counter-instances are the instances satisfying the body.
+   * For a head, conter-instances are the instances satisfying the negation. */
+
+  /** Counter for the number of counter-instances. */
+  private int m_counter;
+
+  /** 
+   * Type of properties expressed in this set 
+   * (individual or parts properties).
+   */
+  private int m_type;
+  
+  /**
+   * Constructor for a set that does not store its counter-instances.
+   */
+  public LiteralSet() {
+
+    m_literals = new ArrayList();
+    m_lastLiteral = null;
+    m_counterInstances = null;
+    m_type = -1;
+  }
+
+  /**
+   * Constructor initializing the set of counter-instances to all the instances.
+   *
+   * @param instances The dataset.
+   */
+  public LiteralSet(Instances instances) {
+
+    this();
+    m_numInstances = instances.numInstances();
+    m_counterInstances = new ArrayList(m_numInstances);
+    Enumeration enu = instances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      m_counterInstances.add(enu.nextElement());
+    }
+  }
+
+  /**
+   * Returns a shallow copy of this set.
+   * The structured is copied but the literals themselves are not copied.
+   *
+   * @return A copy of this LiteralSet.
+   */
+  public Object clone() {
+
+    Object result = null;
+    try {
+      result = super.clone();
+      /* Clone the set of literals, but not the literals themselves. */
+      ((LiteralSet) result).m_literals = (ArrayList) m_literals.clone();
+      if (m_counterInstances != null) {
+	/* Clone the set of instances, but not the instances themselves. */
+	((LiteralSet) result).m_counterInstances 
+	  = (ArrayList) m_counterInstances.clone();
+      }
+    } catch(Exception e) {
+      /* An exception is not supposed to happen here. */
+      e.printStackTrace();
+      System.exit(0);
+    }
+    return result;
+  }
+
+  /**
+   * Update the number of counter-instances of this set in the dataset.
+   * This method should be used is the set does not store its counter-instances.
+   *
+   * @param instances The dataset.
+   */
+  public void upDate(Instances instances) {
+
+    Enumeration enu = instances.enumerateInstances();
+    m_numInstances = instances.numInstances();
+    m_counter = 0;
+    while (enu.hasMoreElements()) {
+      if (this.counterInstance((Instance) enu.nextElement())) {
+	m_counter++;
+      }
+    }
+  }
+
+  /**
+   * Get the number of counter-instances of this LiteralSet.
+   *
+   * @return The number of counter-instances.
+   */
+  public int getCounterInstancesNumber() {
+
+    if (m_counterInstances != null) {
+      return m_counterInstances.size();
+    } else {
+      return m_counter;
+    }
+  }
+
+  /**
+   * Get the frequency of counter-instances of this LiteralSet in the data.
+   *
+   * @return The frequency of counter-instances.
+   */
+  public double getCounterInstancesFrequency() {
+
+    return (double) getCounterInstancesNumber() / (double) m_numInstances;
+  }
+
+  /**
+   * Test if this LiteralSet has more counter-instances than the threshold.
+   *
+   * @param minFrequency The frequency threshold.
+   * @return True if there are more counter-instances than the threshold.
+   */
+  public boolean overFrequencyThreshold(double minFrequency) {
+
+    return getCounterInstancesFrequency() >= minFrequency;
+  }
+
+  /**
+   * Test if all the intances are counter-instances.
+   *
+   * @return True if all the instances are counter-instances.
+   */
+  public boolean hasMaxCounterInstances() {
+
+    return getCounterInstancesNumber() == m_numInstances;
+  }
+
+  /**
+   * Add a Literal to this set.
+   *
+   * @param element The element to add.
+   */
+  public void addElement(Literal element) {
+
+    m_literals.add(element);
+    /* Update the last literal. */
+    m_lastLiteral = element;
+    /* Update the type in the case of individual-based learning. */
+    if (element instanceof IndividualLiteral) {
+      int type = ((IndividualLiteral) element).getType();
+      if (type > m_type) {
+	m_type = type;
+      }
+    }
+    /* Update the set of counter-instances. */
+    if (m_counterInstances != null) {
+      for (int i = m_counterInstances.size() - 1; i >= 0; i--) {
+	Instance current = (Instance) m_counterInstances.get(i);
+	if (!canKeep(current, element)) {
+	  m_counterInstances.remove(i);
+	}
+      }
+    }
+  }
+
+  /**
+   * Test if this set is empty.
+   *
+   * @return True if the set is empty.
+   */
+  public final boolean isEmpty() {
+
+    return m_literals.size() == 0;
+  }
+
+  /**
+   * Give the number of literals in this set.
+   *
+   * @return The number of literals.
+   */
+  public final int numLiterals() {
+
+    return m_literals.size();
+  }
+
+  /**
+   * Enumerate the literals contained in this set.
+   *
+   * @return An Iterator for the literals.
+   */
+  public final Iterator enumerateLiterals() {
+
+    return m_literals.iterator();
+  }
+
+  /**
+   * Give the last literal added to this set.
+   *
+   * @return The last literal added.
+   */
+  public Literal getLastLiteral() {
+
+    return m_lastLiteral;
+  }
+
+  /**
+   * Test if the negation of this LiteralSet is included in another LiteralSet.
+   *
+   * @param otherSet The other LiteralSet.
+   * @return True if the negation of this LiteralSet is included in 
+   * the other LiteralSet.
+   */
+  public boolean negationIncludedIn(LiteralSet otherSet) {
+
+    Iterator iter = this.enumerateLiterals();
+    while (iter.hasNext()) {
+      Literal current = (Literal) iter.next();
+      if (!otherSet.contains(current.getNegation())) {
+	return false;
+      }
+    }      
+    return true;
+  }
+
+  /**
+   * Test if this LiteralSet contains a given Literal.
+   *
+   * @param lit The literal that is looked for.
+   * @return True if this literal is contained in this LiteralSet.
+   */
+  public boolean contains(Literal lit) {
+
+    return m_literals.contains(lit);
+  }
+
+  /**
+   * Give the type of properties in this set (individual or part properties).
+   */
+  public int getType() {
+	
+    return m_type;
+  }
+
+  /**
+   * Test if an individual instance, given a part instance of this individual,
+   * is a counter-instance of this LiteralSet.
+   *
+   * @param individual The individual instance.
+   * @param part The part instance.
+   * @return True if the individual is a counter-instance.
+   */
+  public boolean counterInstance(Instance individual, Instance part) {
+    Iterator iter = this.enumerateLiterals();
+    while (iter.hasNext()) {
+      IndividualLiteral current = (IndividualLiteral) iter.next();
+      if (current.getType() == IndividualLiteral.INDIVIDUAL_PROPERTY
+	  && !canKeep(individual, current))  {
+	return false;
+      } else if (current.getType() == IndividualLiteral.PART_PROPERTY
+		 && !canKeep(part, current)) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Test if an instance is a counter-instance of this LiteralSet.
+   *
+   * @param instance The instance to test.
+   * @return True if the instance is a counter-instance.
+   */
+  public boolean counterInstance(Instance instance) {
+    if ((instance instanceof IndividualInstance)
+	&& (m_type == IndividualLiteral.PART_PROPERTY)) {
+      /* In the case of an individual instance, all the parts of the individual
+       * have to be considered.
+       * Part properties can be found in the body only, so here we test for
+       * an instance satisfying the set.
+       * It satisfies the set if there exists a part satisfying the set.
+       */
+      Enumeration enu 
+	= ((IndividualInstance) instance).getParts().enumerateInstances();
+      while (enu.hasMoreElements()) {
+	if (counterInstance(instance, (Instance) enu.nextElement())) {
+	  return true;
+	}
+      }
+      return false;
+    } else {
+      /* Usual case. */
+      Iterator iter = this.enumerateLiterals();
+      while (iter.hasNext()) {
+	Literal current = (Literal) iter.next();
+	if (!canKeep(instance, current)) {
+	  return false;
+	}
+      }
+      return true;
+    }
+  }
+
+  /**
+   * Test if an instance can be kept as a counter-instance,
+   * given a new literal.
+   *
+   * @param instance The instance to test.
+   * @param newLit The new literal.
+   * @return True if the instance is still a counter-instance.
+   */
+  public abstract boolean canKeep(Instance instance, Literal newLit);
+
+  /**
+   * Test if this LiteralSet is included in a rule.
+   *
+   * @param otherRule The rule to test.
+   * @return True if this set of literals is included in the rule.
+   */
+  public abstract boolean isIncludedIn(Rule otherRule);
+  
+  /**
+   * Gives a String representation for this set of literals.
+   */
+  public abstract String toString();  
+}
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/Predicate.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/Predicate.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/Predicate.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Predicate.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.5 $
+ */
+public class Predicate
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8374702481965026640L;
+
+  private ArrayList m_literals;
+
+  private String m_name;
+
+  private int m_index;
+
+  private boolean m_isClass;
+
+  public Predicate(String name, int index, boolean isClass) {
+
+    m_literals = new ArrayList();
+    m_name = name;
+    m_index = index;
+    m_isClass = isClass;
+  }
+
+  public void addLiteral(Literal lit) {
+
+    m_literals.add(lit);
+  }
+
+  public Literal getLiteral(int index) {
+
+    return (Literal) m_literals.get(index);
+  }
+
+  public int getIndex() {
+
+    return m_index;
+  }
+
+  public int indexOf(Literal lit) {
+
+    int index = m_literals.indexOf(lit);
+    return ((index != -1) 
+	    ? index
+	    : m_literals.indexOf(lit.getNegation()));	     
+  }
+
+  public int numLiterals() {
+
+    return m_literals.size();
+  }
+
+  public boolean isClass() {
+
+    return m_isClass;
+  }
+
+  public String toString() {
+
+    return m_name;
+  }
+
+  public String description() {
+
+    StringBuffer text = new StringBuffer();
+    text.append(this.toString() + "\n");
+    for (int i = 0; i < numLiterals(); i++) {
+	Literal lit = getLiteral(i);
+	Literal neg = lit.getNegation();
+	text.append("\t" + lit + "\t" + neg + "\n");
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/Rule.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/Rule.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/Rule.java	(revision 29)
@@ -0,0 +1,872 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Rule.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Enumeration;
+
+/**
+ * Class representing a rule with a body and a head.
+ *
+ * @author  <a href="mailto:adeltour@netcourrier.com">Amelie Deltour</a>
+ * @version $Revision: 1.7 $
+ */
+
+public class Rule
+  implements Serializable, Cloneable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7763378359090435505L;
+
+  /** The body of the rule. */
+  private Body m_body;
+
+  /** The head of the rule. */
+  private Head m_head;
+
+  /** Can repeat predicates in the rule ? */
+  private boolean m_repeatPredicate;
+
+  /** Maximal number of literals in the rule. */
+  private int m_maxLiterals;
+
+  /** Can there be negations in the body ? */
+  private boolean m_negBody;
+
+  /** Can there be negations in the head ? */
+  private boolean m_negHead;
+
+  /** Is this rule a classification rule ? */
+  private boolean m_classRule;
+
+  /** Can there be only one literal in the head ? */
+  private boolean m_singleHead;
+
+  /** Number of instances in the data this rule deals with. */
+  private int m_numInstances;
+
+  /** Set of counter-instances of this rule. */
+  private ArrayList m_counterInstances;
+
+  /** Counter for the counter-instances of this rule. */
+  private int m_counter;
+
+  /** Confirmation of this rule. */
+  private double m_confirmation;
+
+  /** Optimistic estimate of this rule. */
+  private double m_optimistic;
+
+  /**
+   * Constructor for a rule when the counter-instances are not stored,
+   * giving all the constraints applied to this rule.
+   * 
+   * @param repeatPredicate True if predicates can be repeated.
+   * @param maxLiterals Maximum number of literals.
+   * @param negBody True if negation is allowed in the body.
+   * @param negHead True if negation is allowed in the head. 
+   * @param classRule True if the rule is a classification rule.
+   * @param horn True if the rule is a horn clause.
+   */
+  public Rule(boolean repeatPredicate, int maxLiterals, boolean negBody, 
+      boolean negHead, boolean classRule, boolean horn) {
+
+    m_body = new Body();
+    m_head = new Head();
+    m_repeatPredicate = repeatPredicate;
+    m_maxLiterals = maxLiterals;
+    m_negBody = negBody && !horn;
+    m_negHead = negHead && !horn;
+    m_classRule = classRule;
+    m_singleHead = classRule || horn;
+  }
+
+  /**
+   * Constructor for a rule when the counter-instances are stored,
+   * giving all the constraints applied to this rule.
+   * The counter-instances are initialized to all the instances in the dataset.
+   * 
+   * @param instances The dataset.
+   * @param repeatPredicate True if predicates can be repeated.
+   * @param maxLiterals Maximum number of literals.
+   * @param negBody True if negation is allowed in the body.
+   * @param negHead True if negation is allowed in the head. 
+   * @param classRule True if the rule is a classification rule.
+   * @param horn True if the rule is a horn clause.
+   */
+  public Rule(Instances instances,
+      boolean repeatPredicate, int maxLiterals, boolean negBody, 
+      boolean negHead, boolean classRule, boolean horn) {
+
+    m_body = new Body(instances);
+    m_head = new Head(instances);
+    m_repeatPredicate = repeatPredicate;
+    m_maxLiterals = maxLiterals;
+    m_negBody = negBody && !horn;
+    m_negHead = negHead && !horn;
+    m_classRule = classRule;
+    m_singleHead = classRule || horn;
+    m_numInstances = instances.numInstances();
+    m_counterInstances = new ArrayList(m_numInstances);
+    Enumeration enu = instances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      m_counterInstances.add(enu.nextElement());
+    }
+  }
+
+  /**
+   * Returns a shallow copy of this rule.
+   * The structured is copied but the literals themselves are not copied.
+   *
+   * @return A copy of this Rule.
+   */
+  public Object clone() {
+
+    Object result = null;
+    try {
+      result = super.clone();
+      /* Clone the body and the head. */
+      ((Rule) result).m_body = (Body) m_body.clone();
+      ((Rule) result).m_head = (Head) m_head.clone();
+      /* Clone the set of counter-instances. */
+      if (m_counterInstances != null) {
+	((Rule) result).m_counterInstances
+	= (ArrayList) m_counterInstances.clone();
+      }
+    } catch (Exception e) {
+      /* An exception is not supposed to happen here. */
+      e.printStackTrace();
+      System.exit(0);
+    }
+    return result;
+  }
+
+  /**
+   * Test if an instance is a counter-instance of this rule.
+   * 
+   * @param instance The instance to test.
+   * @return True if the instance is a counter-instance.
+   */
+  public boolean counterInstance(Instance instance) {
+
+    return ((m_body.counterInstance(instance) 
+	&& m_head.counterInstance(instance)));
+  }
+
+  /**
+   * Update the number of counter-instances of this rule in the dataset.
+   * This method should be used is the rule does not store its counter-instances.
+   *
+   * @param instances The dataset.
+   */
+  public void upDate(Instances instances) {
+
+    Enumeration enu = instances.enumerateInstances();
+    m_numInstances = instances.numInstances();
+    m_counter = 0;
+    while (enu.hasMoreElements()) {
+      if (this.counterInstance((Instance) enu.nextElement())) {
+	m_counter++;
+      }
+    }
+    m_head.upDate(instances);
+    m_body.upDate(instances);
+  }
+
+  /**
+   * Get the confirmation value of this rule.
+   *
+   * @return The confirmation.
+   */
+  public double getConfirmation() {
+
+    return m_confirmation;
+  }
+
+  /**
+   * Get the optimistic estimate of the confirmation obtained by refining
+   * this rule.
+   * 
+   * @return The optimistic estimate.
+   */
+  public double getOptimistic() {
+
+    return m_optimistic;
+  }
+
+  /*
+   * Get the expected number of counter-instances of this rule,
+   * calculated from the number of instances satisfying the body and
+   * the number of instances satisfying the negation of the head.
+   *
+   * @return The expected number of counter-instances.
+   */
+  public double getExpectedNumber() {
+
+    return (double) m_body.getCounterInstancesNumber() 
+    * (double) m_head.getCounterInstancesNumber() 
+    / (double) m_numInstances;
+  }
+
+  /**
+   * Get the expected frequency of counter-instances of this rule.
+   *
+   * @return The expected frequency of counter-instances.
+   */
+  public double getExpectedFrequency() {
+
+    return getExpectedNumber() / (double) m_numInstances;
+  }
+
+  /**
+   * Get the observed number of counter-instances of this rule in the dataset.
+   *
+   * @return The observed number of counter-instances.
+   */
+  public int getObservedNumber() {
+
+    if (m_counterInstances != null) {
+      return m_counterInstances.size();
+    } else {
+      return m_counter;
+    }
+  }
+
+  /**
+   * Get the observed frequency of counter-instances of this rule in the dataset.
+   * 
+   * @return The expected frequency of counter-instances.
+   */
+  public double getObservedFrequency() {
+
+    return (double) getObservedNumber() / (double) m_numInstances;
+  }
+
+  /**
+   * Get the rate of True Positive instances of this rule.
+   *
+   * @return The TP-rate.
+   */
+  public double getTPRate() {
+
+    int tp = m_body.getCounterInstancesNumber() - getObservedNumber();
+    int fn = m_numInstances - m_head.getCounterInstancesNumber() - tp;
+    return ((double) tp / (double) (tp + fn));
+  }
+
+  /**
+   * Get the rate of False Positive instances of this rule.
+   *
+   * @return The FP-rate.
+   */
+  public double getFPRate() {
+
+    int fp = getObservedNumber();
+    int tn = m_head.getCounterInstancesNumber() - fp;
+    return ((double) fp / (double) (fp + tn));
+  }
+
+  /**
+   * Calculate the confirmation of this rule.
+   */
+  public void calculateConfirmation() {
+
+    double expected = getExpectedFrequency();
+    double observed = getObservedFrequency();
+    if ((expected == 0) || (expected == 1)) {
+      m_confirmation = 0;
+    } else {
+      m_confirmation = (expected - observed) / (Math.sqrt(expected) - expected);
+    }
+  }
+
+  /**
+   * Calculate the optimistic estimate of this rule.
+   */
+  public void calculateOptimistic() {
+
+    int counterInstances = this.getObservedNumber();
+    int body = m_body.getCounterInstancesNumber();
+    int notHead = m_head.getCounterInstancesNumber();
+    int n = m_numInstances;
+    double expectedOptimistic;
+    /* optimistic expected number of counter-instances */
+    if (counterInstances <= body - notHead) {
+      expectedOptimistic = (double) (notHead * (body - counterInstances)) 
+      / (double) (n * n);
+    } else if (counterInstances <= notHead - body) {
+      expectedOptimistic = (double) (body * (notHead - counterInstances)) 
+      / (double) (n * n);
+    } else {
+      expectedOptimistic = (double) ((notHead + body - counterInstances)
+	  * (notHead + body - counterInstances)) 
+	  / (double) (4 * n * n);
+    }
+    if ((expectedOptimistic == 0) || (expectedOptimistic == 1)) {
+      m_optimistic = 0;
+    } else {
+      m_optimistic = expectedOptimistic 
+      / (Math.sqrt(expectedOptimistic) - expectedOptimistic);
+    }
+  }
+
+  /**
+   * Test if this rule is empty.
+   * 
+   * @return True if it is the empty rule.
+   */
+  public boolean isEmpty() {
+
+    return (m_head.isEmpty() && m_body.isEmpty());
+  }
+
+  /**
+   * Give the number of literals in this rule.
+   * 
+   * @return The number of literals.
+   */
+  public int numLiterals() {
+
+    return m_body.numLiterals() + m_head.numLiterals();
+  }
+
+  /**
+   * Add a literal to the body of the rule.
+   *
+   * @param newLit The literal to add.
+   * @return The rule obtained by adding the literal, null if the literal can
+   * not be added because of the constraints on the rule.
+   */
+  private Rule addTermToBody(Literal newLit) {
+
+    if (!m_negBody && newLit.negative()
+	|| (m_classRule && newLit.getPredicate().isClass())
+	|| (newLit instanceof IndividualLiteral
+	    && (((IndividualLiteral) newLit).getType() 
+		- m_body.getType()) > 1
+		&& (((IndividualLiteral) newLit).getType() 
+		    - m_head.getType()) > 1)) {
+      return null;
+    } else {
+      Rule result = (Rule) this.clone();
+      result.m_body.addElement(newLit);
+      /* Update the counter-instances. */
+      if (m_counterInstances != null) {
+	for (int i = result.m_counterInstances.size() - 1; i >= 0; i--) {
+	  Instance current = (Instance) result.m_counterInstances.get(i);
+	  if (!result.m_body.canKeep(current, newLit)) {
+	    result.m_counterInstances.remove(i);
+	  }
+	}
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Add a literal to the head of the rule.
+   *
+   * @param newLit The literal to add.
+   * @return The rule obtained by adding the literal, null if the literal can
+   * not be added because of the constraints on the rule.
+   */
+  private Rule addTermToHead(Literal newLit) {
+    if ((!m_negHead && newLit.negative())
+	|| (m_classRule && !newLit.getPredicate().isClass()) 
+	|| (m_singleHead && !m_head.isEmpty())
+	|| (newLit instanceof IndividualLiteral
+	    && ((IndividualLiteral) newLit).getType() 
+	    != IndividualLiteral.INDIVIDUAL_PROPERTY)) {
+      return null;
+    } else { 
+      Rule result = (Rule) this.clone();
+      result.m_head.addElement(newLit);
+      /* Update counter-instances. */
+      if (m_counterInstances != null) {
+	for (int i = result.m_counterInstances.size() - 1; i >= 0; i--) {
+	  Instance current = (Instance) result.m_counterInstances.get(i);
+	  if (!result.m_head.canKeep(current, newLit)) {
+	    result.m_counterInstances.remove(i);
+	  }
+	}
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Refine a rule by adding a range of literals of a predicate, either to
+   * the head or to the body of the rule.
+   * 
+   * @param pred The predicate to consider.
+   * @param firstIndex The index of the first literal of the predicate to add.
+   * @param lastIndex The index of the last literal of the predicate to add.
+   * @param addTobody True if the literals should be added to the body.
+   * @param addToHead True if the literals should be added to the head.
+   * @return A list of rules obtained by refinement.
+   */
+  private SimpleLinkedList refine(Predicate pred, int firstIndex, int lastIndex, 
+      boolean addToBody, boolean addToHead) {
+
+    SimpleLinkedList result = new SimpleLinkedList();
+    Literal currentLit;
+    Literal negation;
+    Rule refinement;
+    for (int i = firstIndex; i < lastIndex; i++) {
+      currentLit = pred.getLiteral(i);
+      if (addToBody) {
+	refinement = addTermToBody(currentLit);
+	if (refinement != null) {
+	  result.add(refinement);
+	}
+      }
+      if (addToHead) {
+	refinement = addTermToHead(currentLit);
+	if (refinement != null) {
+	  result.add(refinement);
+	}
+      }
+      negation = currentLit.getNegation();
+      if (negation != null) {
+	if (addToBody) {
+	  refinement = addTermToBody(negation);
+	  if (refinement != null) {
+	    result.add(refinement);
+	  }
+	}
+	if (addToHead) {
+	  refinement = addTermToHead(negation);
+	  if (refinement != null) {
+	    result.add(refinement);
+	  }
+	}
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Refine a rule by adding literal from a set of predictes.
+   * 
+   * @param predicates The predicates available.
+   * @return The list of the children obtained by refining the rule.
+   */
+  public SimpleLinkedList refine(ArrayList predicates) {
+
+    SimpleLinkedList result = new SimpleLinkedList();
+    Predicate currentPred;
+    boolean addToBody;
+    boolean addToHead;
+
+    if (this.numLiterals() == m_maxLiterals) {
+      return result;
+    }
+
+    if (this.isEmpty()) {
+      /* Literals can be added on both sides of the rule. */
+      for (int i = 0; i < predicates.size(); i++) {
+	currentPred = (Predicate) predicates.get(i);
+	result.addAll(refine(currentPred,
+	    0, currentPred.numLiterals(),
+	    true, true));
+      }
+    } else if (m_body.isEmpty() || m_head.isEmpty()) {
+      /* Literals can be added to the empty side only. */
+      LiteralSet side;
+      Literal last;
+      if (m_body.isEmpty()) {
+	side = m_head;
+	addToBody = true;
+	addToHead = false;
+      } else { // m_head.isEmpty()
+	side = m_body;
+	addToBody = false;
+	addToHead = true;
+      }
+      last = side.getLastLiteral();
+      currentPred = last.getPredicate();
+      if (m_repeatPredicate) {
+	result.addAll(refine(currentPred,
+	    currentPred.indexOf(last) + 1, 
+	    currentPred.numLiterals(),
+	    addToBody, addToHead));
+      }
+      for (int i = predicates.indexOf(currentPred) + 1; i < predicates.size(); 
+      i++) {
+	currentPred = (Predicate) predicates.get(i);
+	result.addAll(refine(currentPred,
+	    0, currentPred.numLiterals(),
+	    addToBody, addToHead));		
+      }
+    } else {
+      Literal lastLitBody = m_body.getLastLiteral();
+      Literal lastLitHead = m_head.getLastLiteral();
+      Predicate lastPredBody = lastLitBody.getPredicate();
+      Predicate lastPredHead = lastLitHead.getPredicate();
+      int lastLitBodyIndex = lastPredBody.indexOf(lastLitBody);
+      int lastLitHeadIndex = lastPredHead.indexOf(lastLitHead);
+      int lastPredBodyIndex = predicates.indexOf(lastPredBody);
+      int lastPredHeadIndex = predicates.indexOf(lastPredHead);
+      Predicate inferiorPred;
+      Predicate superiorPred;
+      int inferiorLit;
+      int superiorLit;
+      addToBody = (m_head.numLiterals() == 1
+	  && (lastPredBodyIndex < lastPredHeadIndex 
+	      || (lastPredBodyIndex == lastPredHeadIndex 
+		  && lastLitBodyIndex < lastLitHeadIndex)));
+      addToHead = (m_body.numLiterals() == 1
+	  && (lastPredHeadIndex < lastPredBodyIndex
+	      || (lastPredHeadIndex == lastPredBodyIndex
+		  && lastLitHeadIndex < lastLitBodyIndex)));      
+      if (addToBody || addToHead) {
+	/* Add literals in the gap between the body and the head. */
+	if (addToBody) {
+	  inferiorPred = lastPredBody;
+	  inferiorLit = lastLitBodyIndex;
+	  superiorPred = lastPredHead;
+	  superiorLit = lastLitHeadIndex;
+	} else { // addToHead
+	  inferiorPred = lastPredHead;
+	  inferiorLit = lastLitHeadIndex;
+	  superiorPred = lastPredBody;
+	  superiorLit = lastLitBodyIndex;
+	}
+	if (predicates.indexOf(inferiorPred) 
+	    < predicates.indexOf(superiorPred)) {
+	  if (m_repeatPredicate) {
+	    result.addAll(refine(inferiorPred, 
+		inferiorLit + 1, inferiorPred.numLiterals(),
+		addToBody, addToHead));
+	  }
+	  for (int j = predicates.indexOf(inferiorPred) + 1; 
+	  j < predicates.indexOf(superiorPred); j++) {
+	    currentPred = (Predicate) predicates.get(j);
+	    result.addAll(refine(currentPred,
+		0, currentPred.numLiterals(),
+		addToBody, addToHead));
+	  }
+	  if (m_repeatPredicate) {
+	    result.addAll(refine(superiorPred,
+		0, superiorLit,
+		addToBody, addToHead));
+	  }
+	} else { 
+	  //((inferiorPred.getIndex() == superiorPred.getIndex())
+	  //&& (inferiorLit < superiorLit))
+	  if (m_repeatPredicate) {
+	    result.addAll(refine(inferiorPred,
+		inferiorLit + 1, superiorLit,
+		addToBody, addToHead));
+	  }
+	}	
+      }	
+      /* Add other literals. */
+      if (predicates.indexOf(lastPredBody) > predicates.indexOf(lastPredHead)) {
+	superiorPred = lastPredBody;
+	superiorLit = lastPredBody.indexOf(lastLitBody);
+      } else if (predicates.indexOf(lastPredBody) 
+	  < predicates.indexOf(lastPredHead)) {
+	superiorPred = lastPredHead;
+	superiorLit = lastPredHead.indexOf(lastLitHead);
+      } else {
+	superiorPred = lastPredBody;
+	if (lastLitBodyIndex > lastLitHeadIndex) {
+	  superiorLit = lastPredBody.indexOf(lastLitBody);
+	} else {
+	  superiorLit = lastPredHead.indexOf(lastLitHead);
+	}
+      }
+      if (m_repeatPredicate) {
+	result.addAll(refine(superiorPred,
+	    superiorLit + 1, superiorPred.numLiterals(),
+	    true, true));
+      }
+      for (int j = predicates.indexOf(superiorPred) + 1; j < predicates.size(); 
+      j++) {
+	currentPred = (Predicate) predicates.get(j);
+	result.addAll(refine(currentPred,
+	    0, currentPred.numLiterals(),
+	    true, true));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Test if this rule subsumes another rule.
+   *
+   * @param otherRule The other rule.
+   * @return True if the other rule is subsumed.
+   */
+  public boolean subsumes(Rule otherRule) {    
+
+    if (this.numLiterals() > otherRule.numLiterals()) {
+      return false;
+    }
+    return (m_body.isIncludedIn(otherRule) && m_head.isIncludedIn(otherRule));
+  }
+
+  /**
+   * Test if this rule and another rule correspond to the same clause.
+   *
+   * @param otherRule The other rule.
+   * @return True if both rules correspond to the same clause.
+   */
+  public boolean sameClauseAs(Rule otherRule) {
+
+    return (this.numLiterals() == otherRule.numLiterals()
+	&& this.subsumes(otherRule));
+  }
+
+  /**
+   * Test if this rule is equivalent to another rule.
+   *
+   * @param otherRule The other rule.
+   * @return True if both rules are equivalent.
+   */
+  public boolean equivalentTo(Rule otherRule) {
+
+    return (this.numLiterals() == otherRule.numLiterals()
+	&& m_head.negationIncludedIn(otherRule.m_body)
+	&& m_body.negationIncludedIn(otherRule.m_head));
+  }
+
+  /**
+   * Test if the body of the rule contains a literal.
+   * 
+   * @param lit The literal to look for.
+   * @return True if the literal is contained in the body of the rule.
+   */
+  public boolean bodyContains(Literal lit) {
+
+    return m_body.contains(lit);
+  }
+
+  /**
+   * Test if the head of the rule contains a literal.
+   * 
+   * @param lit The literal to look for.
+   * @return True if the literal is contained in the head of the rule.
+   */
+  public boolean headContains(Literal lit) {
+
+    return m_head.contains(lit);
+  }
+
+  /**
+   * Test if this rule is over the frequency threshold.
+   *
+   * @param minFrequency The frequency threshold.
+   * @return True if the rule is over the threshold.
+   */
+  public boolean overFrequencyThreshold(double minFrequency) {
+
+    return (m_body.overFrequencyThreshold(minFrequency) 
+	&& m_head.overFrequencyThreshold(minFrequency));
+  }
+
+  /**
+   * Test if the body of the rule is true.
+   *
+   * @return True if the body is always satisfied.
+   */
+  public boolean hasTrueBody() {
+
+    return (!m_body.isEmpty()
+	&& m_body.hasMaxCounterInstances());
+  }
+
+  /**
+   * Test if the head of the rule is false.
+   *
+   * @return True if the body is never satisfied.
+   */
+  public boolean hasFalseHead() {
+
+    return (!m_head.isEmpty()
+	&& m_head.hasMaxCounterInstances());
+  }
+
+  /**
+   * Return a String giving the confirmation and optimistic estimate of 
+   * this rule.
+   * 
+   * @return A String with the values of the rule.
+   */
+  public String valuesToString() {
+
+    StringBuffer text = new StringBuffer();
+    DecimalFormat decimalFormat = new DecimalFormat("0.000000");
+    text.append(decimalFormat.format(getConfirmation()));
+    text.append(" ");
+    text.append(decimalFormat.format(getObservedFrequency()));
+    return text.toString();
+  }
+
+  /**
+   * Return a String giving the TP-rate and FP-rate of 
+   * this rule.
+   * 
+   * @return A String with the values of the rule.
+   */
+  public String rocToString() {
+
+    StringBuffer text = new StringBuffer();
+    DecimalFormat decimalFormat = new DecimalFormat("0.000000");
+    text.append(decimalFormat.format(getConfirmation()));
+    text.append(" ");
+    text.append(decimalFormat.format(getTPRate()));
+    text.append(" ");
+    text.append(decimalFormat.format(getFPRate()));
+    return text.toString();
+  }
+
+  /**
+   * Retrun a String for this rule.
+   *
+   * @return The String describing this rule.
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    text.append(m_body.toString());
+    text.append(" ==> ");
+    text.append(m_head.toString());
+    return text.toString();
+  }
+
+  /**
+   * Comparator used to compare two rules according to their confirmation value.
+   */
+  public static Comparator confirmationComparator = new Comparator() {
+
+    public int compare(Object o1, Object o2) {
+
+      Rule r1 = (Rule) o1;
+      Rule r2 = (Rule) o2;
+      double conf1 = r1.getConfirmation();
+      double conf2 = r2.getConfirmation();
+      if (conf1 > conf2) {
+	return -1;
+      } else if (conf1 < conf2) {
+	return 1;
+      } else {
+	return 0;
+      }
+    }
+  };
+
+  /**
+   * Comparator used to compare two rules according to their observed number
+   * of counter-instances.
+   */
+  public static Comparator observedComparator = new Comparator() {
+
+    public int compare(Object o1, Object o2) {
+
+      Rule r1 = (Rule) o1;
+      Rule r2 = (Rule) o2;
+      double obs1 = r1.getObservedFrequency();
+      double obs2 = r2.getObservedFrequency();
+      if (obs1 < obs2) {
+	return -1;
+      } else if (obs1 > obs2) {
+	return 1;
+      } else {
+	return 0;
+      }
+    }
+  };
+
+  /**
+   * Comparator used to compare two rules according to their optimistic estimate.
+   */
+  public static Comparator optimisticComparator = new Comparator() {
+
+    public int compare(Object o1, Object o2) {
+
+      Rule r1 = (Rule) o1;
+      Rule r2 = (Rule) o2;
+      double opt1 = r1.getOptimistic();
+      double opt2 = r2.getOptimistic();
+      if (opt1 > opt2) {
+	return -1;
+      } else if (opt1 < opt2) {
+	return 1;
+      } else {
+	return 0;
+      }
+    }
+  };
+
+  /**
+   * Comparator used to compare two rules according to their confirmation and 
+   * then their observed number of counter-instances.
+   */
+  public static Comparator confirmationThenObservedComparator = new Comparator() {
+    public int compare(Object o1, Object o2) {
+
+      int confirmationComparison = confirmationComparator.compare(o1, o2);
+      if (confirmationComparison != 0) {
+	return confirmationComparison;
+      } else {
+	return observedComparator.compare(o1, o2);
+      }
+    }
+  };
+
+  /**
+   * Comparator used to compare two rules according to their optimistic estimate
+   * and then their observed number of counter-instances.
+   */
+  public static Comparator optimisticThenObservedComparator = new Comparator() {
+    public int compare(Object o1, Object o2) {
+      int optimisticComparison = optimisticComparator.compare(o1, o2);
+      if (optimisticComparison != 0) {
+	return optimisticComparison;
+      } else {
+	return observedComparator.compare(o1, o2);
+      }
+    }
+  };
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/associations/tertius/SimpleLinkedList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/associations/tertius/SimpleLinkedList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/associations/tertius/SimpleLinkedList.java	(revision 29)
@@ -0,0 +1,351 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleLinkedList.java
+ *    Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
+ *
+ *    Thanks to Amelie Deltour for porting the original C code to Java
+ *    and integrating it into Weka.
+ */
+
+package weka.associations.tertius;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.NoSuchElementException;
+
+/**
+ * @author Peter A. Flach
+ * @author Nicolas Lachiche
+ * @version $Revision: 1.6 $
+ */
+public class SimpleLinkedList
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1491148276509976299L;
+
+  public class LinkedListIterator
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -2448555236100426759L;
+    
+    Entry current = first;
+    Entry lastReturned = null;
+    
+    public boolean hasNext() {
+      return current.next != last;
+    }
+
+    public Object next() {
+      if (current == last) {
+	throw new NoSuchElementException();
+      }
+      current = current.next;
+      lastReturned = current;
+      return current.element;
+    }
+
+    public void remove() {
+      if (lastReturned == last
+	  || lastReturned == null) {
+	throw new IllegalStateException();
+      }
+      lastReturned.previous.next = lastReturned.next;
+      lastReturned.next.previous = lastReturned.previous;
+      current = lastReturned.previous;
+      lastReturned = null;
+    }
+
+    public void addBefore(Object o) {
+      if (lastReturned == null) {
+	throw new IllegalStateException();
+      }
+      Entry newEntry = new Entry(o, lastReturned, lastReturned.previous);
+      lastReturned.previous.next = newEntry;
+      lastReturned.previous = newEntry;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.6 $");
+    }
+  }
+
+  public class LinkedListInverseIterator
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 6290379064027832108L;
+    
+    Entry current = last;
+    Entry lastReturned = null;
+    
+    public boolean hasPrevious() {
+      return current.previous != first;
+    }
+
+    public Object previous() {
+      if (current == first) {
+	throw new NoSuchElementException();
+      }
+      current = current.previous;
+      lastReturned = current;
+      return current.element;
+    }
+
+    public void remove() {
+      if (lastReturned == first
+	  || lastReturned == null) {
+	throw new IllegalStateException();
+      }
+      lastReturned.previous.next = lastReturned.next;
+      lastReturned.next.previous = lastReturned.previous;
+      current = lastReturned.next;
+      lastReturned = null;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.6 $");
+    }
+  }
+
+
+  private static class Entry
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 7888492479685339831L;
+    
+    Object element;
+    Entry next;
+    Entry previous;
+    
+    Entry(Object element, Entry next, Entry previous) {
+      this.element = element;
+      this.next = next;
+      this.previous = previous;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.6 $");
+    }
+  }
+
+  private Entry first;
+  private Entry last;
+
+  public SimpleLinkedList() {
+    first = new Entry(null, null, null);
+    last = new Entry(null, null, null);
+    first.next = last;
+    last.previous = first;
+  }
+
+  public Object removeFirst() {
+    if (first.next == last) {
+      throw new NoSuchElementException();
+    }
+    Object result = first.next.element;
+    first.next.next.previous = first;
+    first.next = first.next.next;
+    return result;
+  }
+
+  public Object getFirst() {
+    if (first.next == last) {
+      throw new NoSuchElementException();
+    }
+    return first.next.element;
+  }
+
+  public Object getLast() {
+    if (last.previous == first) {
+      throw new NoSuchElementException();
+    }
+    return last.previous.element;
+  }
+
+  public void addFirst(Object o) {
+    Entry newEntry = new Entry(o, first.next, first);
+    first.next.previous = newEntry;
+    first.next = newEntry;
+  }
+
+  public void add(Object o) {
+    Entry newEntry = new Entry(o, last, last.previous);
+    last.previous.next = newEntry;
+    last.previous = newEntry;
+  }
+
+  public void addAll(SimpleLinkedList list) {
+    last.previous.next = list.first.next;    
+    list.first.next.previous = last.previous;
+    last = list.last;
+  }
+
+  public void clear() {
+    first.next = last;
+    last.previous = first;
+  }
+
+  public boolean isEmpty() {
+    return first.next == last;
+  }
+
+  public LinkedListIterator iterator() {
+    return new LinkedListIterator();
+  }
+
+  public LinkedListInverseIterator inverseIterator() {
+    return new LinkedListInverseIterator();
+  }
+
+  public int size() {
+    int result = 0;
+    LinkedListIterator iter = new LinkedListIterator();
+    while (iter.hasNext()) {
+      result++;
+      iter.next();
+    }
+    return result;
+  }
+
+  public void merge(SimpleLinkedList list, Comparator comp) {
+    LinkedListIterator iter1 = this.iterator();
+    LinkedListIterator iter2 = list.iterator();
+    Object elem1 = iter1.next();
+    Object elem2 = iter2.next();
+    while (elem2 != null) {
+      if ((elem1 == null)
+	  || (comp.compare(elem2, elem1) < 0)) {	    
+	iter1.addBefore(elem2);
+	elem2 = iter2.next();
+      } else {
+	elem1 = iter1.next();
+      }
+    }
+  }
+
+  public void sort(Comparator comp) {
+    LinkedListIterator iter = this.iterator();
+    if (iter.hasNext()) {
+      SimpleLinkedList lower = new SimpleLinkedList();
+      SimpleLinkedList upper = new SimpleLinkedList();
+      Object ref = iter.next();
+      Object elem;
+      while (iter.hasNext()) {
+	elem = iter.next();
+	if (comp.compare(elem, ref) < 0) {
+	  lower.add(elem);
+	} else {
+	  upper.add(elem);
+	}
+      }
+      lower.sort(comp);
+      upper.sort(comp);
+      clear();
+      addAll(lower);
+      add(ref);
+      addAll(upper);
+    } 
+  }
+
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    LinkedListIterator iter = iterator();
+    text.append("[");
+    while (iter.hasNext()) {
+      text.append(String.valueOf(iter.next()));
+      if (iter.hasNext()) {
+	text.append(", ");
+      }
+    }
+    text.append("]");
+    return text.toString();
+  }
+
+    /**
+     * Save the state of this <tt>LinkedList</tt> instance to a stream (that
+     * is, serialize it).
+     *
+     * @serialData The size of the list (the number of elements it
+     *		   contains) is emitted (int), followed by all of its
+     * elements (each an Object) in the proper order.  */
+    private synchronized void writeObject(java.io.ObjectOutputStream s)
+        throws java.io.IOException {
+	// Write out any hidden serialization magic
+	s.defaultWriteObject();
+
+        // Write out size
+        s.writeInt(size());
+
+	// Write out all elements in the proper order.
+        for (Entry e = first.next; e != last; e = e.next)
+            s.writeObject(e.element);
+    }
+
+    /**
+     * Reconstitute this <tt>LinkedList</tt> instance from a stream (that is
+     * deserialize it).
+     */
+    private synchronized void readObject(java.io.ObjectInputStream s)
+        throws java.io.IOException, ClassNotFoundException {
+	// Read in any hidden serialization magic
+	s.defaultReadObject();
+
+        // Read in size
+        int size = s.readInt();
+
+        // Initialize header
+	first = new Entry(null, null, null);
+	last = new Entry(null, null, null);
+	first.next = last;
+	last.previous = first;
+
+	// Read in all elements in the proper order.
+	for (int i=0; i<size; i++)
+            add(s.readObject());
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.6 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ASEvaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ASEvaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ASEvaluation.java	(revision 29)
@@ -0,0 +1,162 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ASEvaluation.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/** 
+ * Abstract attribute selection evaluation class
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5446 $
+ */
+public abstract class ASEvaluation
+  implements Serializable, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2091705669885950849L;
+  
+  // ===============
+  // Public methods.
+  // ===============
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @exception Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public abstract void buildEvaluator(Instances data) throws Exception;
+
+  /**
+   * Provides a chance for a attribute evaluator to do any special
+   * post processing of the selected attribute set.
+   *
+   * @param attributeSet the set of attributes found by the search
+   * @return a possibly ranked list of postprocessed attributes
+   * @exception Exception if postprocessing fails for some reason
+   */
+  public int [] postProcess(int [] attributeSet) 
+    throws Exception {
+    return attributeSet;
+  }
+
+  /**
+   * Creates a new instance of an attribute/subset evaluator 
+   * given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * evaluator implements OptionHandler and the options parameter is
+   * non-null, the evaluator will have it's options set.
+   *
+   * @param evaluatorName the fully qualified class name of the evaluator
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created evaluator, ready for use.
+   * @exception Exception if the evaluator name is invalid, or the options
+   * supplied are not acceptable to the evaluator
+   */
+  public static ASEvaluation forName(String evaluatorName,
+				     String [] options) throws Exception {
+    return (ASEvaluation)Utils.forName(ASEvaluation.class,
+				       evaluatorName,
+				       options);
+  }
+
+  /**
+   * Creates copies of the current evaluator. Note that this method
+   * now uses Serialization to perform a deep copy, so the evaluator
+   * object must be fully Serializable. Any currently built model will
+   * now be copied as well.
+   *
+   * @param model an example evaluator to copy
+   * @param num the number of evaluator copies to create.
+   * @return an array of evaluators.
+   * @exception Exception if an error occurs 
+   */
+  public static ASEvaluation [] makeCopies(ASEvaluation model,
+					 int num) throws Exception {
+
+    if (model == null) {
+      throw new Exception("No model evaluator set");
+    }
+    ASEvaluation [] evaluators = new ASEvaluation [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < evaluators.length; i++) {
+      evaluators[i] = (ASEvaluation) so.getObject();
+    }
+    return evaluators;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.enableAll();
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5446 $");
+  }
+  
+  /**
+   * runs the evaluator with the given commandline options
+   * 
+   * @param evaluator	the evaluator to run
+   * @param options	the commandline options
+   */
+  protected static void runEvaluator(ASEvaluation evaluator, String[] options) {
+    try {
+      System.out.println(
+	  AttributeSelection.SelectAttributes(evaluator, options));
+    }
+    catch (Exception e) {
+      String msg = e.toString().toLowerCase();
+      if (    (msg.indexOf("help requested") == -1)
+           && (msg.indexOf("no training file given") == -1) )
+        e.printStackTrace();
+      System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ASSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ASSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ASSearch.java	(revision 29)
@@ -0,0 +1,112 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ASSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/** 
+ * Abstract attribute selection search class.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.12 $
+ */
+public abstract class ASSearch
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7591673350342236548L;
+  
+  // ===============
+  // Public methods.
+  // ===============
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.12 $");
+  }
+  
+  /**
+   * Searches the attribute subset/ranking space.
+   *
+   * @param ASEvaluator the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public abstract int [] search(ASEvaluation ASEvaluator,
+				Instances data) throws Exception;
+
+  /**
+   * Creates a new instance of a search class given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * search method implements OptionHandler and the options parameter is
+   * non-null, the search method will have it's options set.
+   *
+   * @param searchName the fully qualified class name of the search class
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created search object, ready for use.
+   * @throws Exception if the search class name is invalid, or the options
+   * supplied are not acceptable to the search class.
+   */
+  public static ASSearch forName(String searchName,
+				 String [] options) throws Exception {
+    return (ASSearch)Utils.forName(ASSearch.class,
+				   searchName,
+				   options);
+  }
+
+  /**
+   * Creates copies of the current search scheme. Note that this method
+   * now uses Serialization to perform a deep copy, so the search
+   * object must be fully Serializable. Any currently built model will
+   * now be copied as well.
+   *
+   * @param model an example search scheme to copy
+   * @param num the number of search scheme copies to create.
+   * @return an array of search schemes.
+   * @throws Exception if an error occurs 
+   */
+  public static ASSearch[] makeCopies(ASSearch model, int num) throws Exception {
+
+    if (model == null)
+      throw new Exception("No model search scheme set");
+      
+    ASSearch[] result = new ASSearch[num];
+    SerializedObject so = new SerializedObject(model);
+    for (int i = 0; i < result.length; i++)
+      result[i] = (ASSearch) so.getObject();
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeEvaluator.java	(revision 29)
@@ -0,0 +1,46 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+
+/** 
+ * Interface for classes that evaluate attributes individually.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public interface AttributeEvaluator {
+  
+    /**
+     * evaluates an individual attribute
+     *
+     * @param attribute the index of the attribute to be evaluated
+     * @return the "merit" of the attribute
+     * @exception Exception if the attribute could not be evaluated
+     */
+    public abstract double evaluateAttribute(int attribute) throws Exception;
+}
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSelection.java	(revision 29)
@@ -0,0 +1,1103 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Random;
+
+/** 
+ * Attribute selection class. Takes the name of a search class and
+ * an evaluation class on the command line. <p/>
+ *
+ * Valid options are: <p/>
+ *
+ * -h <br/>
+ * Display help. <p/>
+ *
+ * -i &lt;name of input file&gt; <br/>
+ * Specify the training data file. <p/>
+ * 
+ * -c &lt;class index&gt; <br/>
+ * The index of the attribute to use as the class. <p/>
+ * 
+ * -s &lt;search method&gt; <br/>
+ * The full class name of the search method followed by search method options
+ * (if any).<br/>
+ * Eg. -s "weka.attributeSelection.BestFirst -N 10" <p/>
+ *
+ * -x &lt;number of folds&gt; <br/>
+ * Perform a cross validation. <p/>
+ *
+ * -n &lt;random number seed&gt; <br/>
+ * Specify a random number seed. Use in conjuction with -x. (Default = 1). <p/>
+ * 
+ * ------------------------------------------------------------------------ <p/>
+ * 
+ * Example usage as the main of an attribute evaluator (called FunkyEvaluator):
+ * <pre>
+ * public static void main(String [] args) {
+ *   runEvaluator(new FunkyEvaluator(), args);
+ * }
+ * </pre>
+ * <p/>
+ *
+ * ------------------------------------------------------------------------ <p/>
+ *
+ * @author   Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version  $Revision: 1.47 $
+ */
+public class AttributeSelection 
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4170171824147584330L;
+
+  /** the instances to select attributes from */
+  private Instances m_trainInstances;
+
+  /** the attribute/subset evaluator */
+  private ASEvaluation m_ASEvaluator;
+
+  /** the search method */
+  private ASSearch m_searchMethod;
+
+  /** the number of folds to use for cross validation */
+  private int m_numFolds;
+
+  /** holds a string describing the results of the attribute selection */
+  private StringBuffer m_selectionResults;
+
+  /** rank features (if allowed by the search method) */
+  private boolean m_doRank;
+  
+  /** do cross validation */
+  private boolean m_doXval;
+
+  /** seed used to randomly shuffle instances for cross validation */
+  private int m_seed;
+
+  /** number of attributes requested from ranked results */
+  private int m_numToSelect;
+  
+  /** the selected attributes */
+  private int [] m_selectedAttributeSet;
+
+  /** the attribute indexes and associated merits if a ranking is produced */
+  private double [][] m_attributeRanking;
+
+  /** if a feature selection run involves an attribute transformer */
+  private AttributeTransformer m_transformer = null;
+  
+  /** the attribute filter for processing instances with respect to
+      the most recent feature selection run */
+  private Remove m_attributeFilter = null;
+
+  /** hold statistics for repeated feature selection, such as
+      under cross validation */
+  private double [][] m_rankResults = null;
+  private double [] m_subsetResults = null;
+  private int m_trials = 0;
+
+  /**
+   * Return the number of attributes selected from the most recent
+   * run of attribute selection
+   * @return the number of attributes selected
+   */
+  public int numberAttributesSelected() throws Exception {
+    int [] att = selectedAttributes();
+    return att.length-1;
+  }
+
+  /**
+   * get the final selected set of attributes.
+   * @return an array of attribute indexes
+   * @exception Exception if attribute selection has not been performed yet
+   */
+  public int [] selectedAttributes () throws Exception {
+    if (m_selectedAttributeSet == null) {
+      throw new Exception("Attribute selection has not been performed yet!");
+    }
+    return m_selectedAttributeSet;
+  }
+
+  /**
+   * get the final ranking of the attributes.
+   * @return a two dimensional array of ranked attribute indexes and their
+   * associated merit scores as doubles.
+   * @exception Exception if a ranking has not been produced
+   */
+  public double [][] rankedAttributes () throws Exception {
+    if (m_attributeRanking == null) {
+      throw new Exception("Ranking has not been performed");
+    }
+    return m_attributeRanking;
+  }
+
+  /**
+   * set the attribute/subset evaluator
+   * @param evaluator the evaluator to use
+   */
+  public void setEvaluator (ASEvaluation evaluator) {
+    m_ASEvaluator = evaluator;
+  }
+
+  /**
+   * set the search method
+   * @param search the search method to use
+   */
+  public void setSearch (ASSearch search) {
+    m_searchMethod = search;
+
+    if (m_searchMethod instanceof RankedOutputSearch) {
+      setRanking(((RankedOutputSearch)m_searchMethod).getGenerateRanking());
+    }
+  }
+
+  /**
+   * set the number of folds for cross validation
+   * @param folds the number of folds
+   */
+  public void setFolds (int folds) {
+    m_numFolds = folds;
+  }
+
+  /**
+   * produce a ranking (if possible with the set search and evaluator)
+   * @param r true if a ranking is to be produced
+   */
+  public void setRanking (boolean r) {
+    m_doRank = r;
+  }
+
+  /**
+   * do a cross validation
+   * @param x true if a cross validation is to be performed
+   */
+  public void setXval (boolean x) {
+    m_doXval = x;
+  }
+
+  /**
+   * set the seed for use in cross validation
+   * @param s the seed
+   */
+  public void setSeed (int s) {
+    m_seed = s;
+  }
+
+  /**
+   * get a description of the attribute selection
+   * @return a String describing the results of attribute selection
+   */
+  public String toResultsString() {
+    return m_selectionResults.toString();
+  }
+
+  /**
+   * reduce the dimensionality of a set of instances to include only those 
+   * attributes chosen by the last run of attribute selection.
+   * @param in the instances to be reduced
+   * @return a dimensionality reduced set of instances
+   * @exception Exception if the instances can't be reduced
+   */
+  public Instances reduceDimensionality(Instances in) throws Exception {
+    if (m_attributeFilter == null) {
+      throw new Exception("No feature selection has been performed yet!");
+    }
+
+    if (m_transformer != null) {
+      Instances transformed = new Instances(m_transformer.transformedHeader(),
+					    in.numInstances());
+      for (int i=0;i<in.numInstances();i++) {
+	transformed.add(m_transformer.convertInstance(in.instance(i)));
+      }
+      return Filter.useFilter(transformed, m_attributeFilter);
+    }
+
+    return Filter.useFilter(in, m_attributeFilter);
+  }
+
+  /**
+   * reduce the dimensionality of a single instance to include only those 
+   * attributes chosen by the last run of attribute selection.
+   * @param in the instance to be reduced
+   * @return a dimensionality reduced instance
+   * @exception Exception if the instance can't be reduced
+   */
+  public Instance reduceDimensionality(Instance in) throws Exception {
+    if (m_attributeFilter == null) {
+      throw new Exception("No feature selection has been performed yet!");
+    }
+    if (m_transformer != null) {
+      in = m_transformer.convertInstance(in);
+    }
+    m_attributeFilter.input(in);
+    m_attributeFilter.batchFinished();
+    Instance result = m_attributeFilter.output();
+    return result;
+  }
+
+  /**
+   * constructor. Sets defaults for each member varaible. Default
+   * attribute evaluator is CfsSubsetEval; default search method is
+   * BestFirst.
+   */
+  public AttributeSelection () {
+    setFolds(10);
+    setRanking(false);
+    setXval(false);
+    setSeed(1);
+    setEvaluator(new CfsSubsetEval());
+    setSearch(new GreedyStepwise());
+    m_selectionResults = new StringBuffer();
+    m_selectedAttributeSet = null;
+    m_attributeRanking = null;
+  }
+
+  /**
+   * Perform attribute selection with a particular evaluator and
+   * a set of options specifying search method and input file etc.
+   *
+   * @param ASEvaluator an evaluator object
+   * @param options an array of options, not only for the evaluator
+   * but also the search method (if any) and an input data file
+   * @return the results of attribute selection as a String
+   * @exception Exception if no training file is set
+   */
+  public static String SelectAttributes (ASEvaluation ASEvaluator, 
+					 String[] options)
+    throws Exception {
+    String trainFileName, searchName;
+    Instances train = null;
+    ASSearch searchMethod = null;
+    String[] optionsTmp = (String[]) options.clone();
+    boolean helpRequested = false;
+
+    try {
+      // get basic options (options the same for all attribute selectors
+      trainFileName = Utils.getOption('i', options);
+      helpRequested = Utils.getFlag('h', optionsTmp);
+
+      if (helpRequested || (trainFileName.length() == 0)) {
+        searchName = Utils.getOption('s', optionsTmp);
+        if (searchName.length() != 0) {
+          String[] searchOptions = Utils.splitOptions(searchName);
+          searchMethod = (ASSearch)Class.forName(searchOptions[0]).newInstance();
+        }
+
+        if (helpRequested)
+          throw new Exception("Help requested.");
+        else
+          throw new Exception("No training file given.");
+      }
+    }
+    catch (Exception e) {
+      throw  new Exception('\n' + e.getMessage() 
+			   + makeOptionString(ASEvaluator, searchMethod));
+    }
+
+    DataSource source = new DataSource(trainFileName);
+    train = source.getDataSet();
+    return SelectAttributes(ASEvaluator, options, train);
+  }
+
+  /**
+   * returns a string summarizing the results of repeated attribute
+   * selection runs on splits of a dataset.
+   * @return a summary of attribute selection results
+   * @exception Exception if no attribute selection has been performed.
+   */
+  public String CVResultsString () throws Exception {
+    StringBuffer CvString = new StringBuffer();
+    
+    if ((m_subsetResults == null && m_rankResults == null) ||
+	( m_trainInstances == null)) {
+      throw new Exception("Attribute selection has not been performed yet!");
+    }
+
+    int fieldWidth = (int)(Math.log(m_trainInstances.numAttributes()) +1.0);
+
+    CvString.append("\n\n=== Attribute selection " + m_numFolds 
+		    + " fold cross-validation ");
+
+    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) && 
+	!(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator) &&
+	(m_trainInstances.classAttribute().isNominal())) {
+	CvString.append("(stratified), seed: ");
+	CvString.append(m_seed+" ===\n\n");
+    }
+    else {
+      CvString.append("seed: "+m_seed+" ===\n\n");
+    }
+
+    if ((m_searchMethod instanceof RankedOutputSearch) && (m_doRank == true)) {
+      CvString.append("average merit      average rank  attribute\n");
+
+      // calcualte means and std devs
+      for (int i = 0; i < m_rankResults[0].length; i++) {
+	m_rankResults[0][i] /= m_numFolds; // mean merit
+	double var = m_rankResults[0][i]*m_rankResults[0][i]*m_numFolds;
+	var = (m_rankResults[2][i] - var);
+	var /= m_numFolds;
+
+	if (var <= 0.0) {
+	  var = 0.0;
+	  m_rankResults[2][i] = 0;
+	}
+	else {
+	  m_rankResults[2][i] = Math.sqrt(var);
+	}
+
+	m_rankResults[1][i] /= m_numFolds; // mean rank
+	var = m_rankResults[1][i]*m_rankResults[1][i]*m_numFolds;
+	var = (m_rankResults[3][i] - var);
+	var /= m_numFolds;
+
+	if (var <= 0.0) {
+	  var = 0.0;
+	  m_rankResults[3][i] = 0;
+	}
+	else {
+	  m_rankResults[3][i] = Math.sqrt(var);
+	}
+      }
+
+      // now sort them by mean rank
+      int[] s = Utils.sort(m_rankResults[1]);
+      for (int i=0; i<s.length; i++) {
+	if (m_rankResults[1][s[i]] > 0) {
+	  CvString.append(Utils.doubleToString(Math.
+					       abs(m_rankResults[0][s[i]]),
+					       6, 3) 
+			  + " +-" 
+			  + Utils.doubleToString(m_rankResults[2][s[i]], 6, 3) 
+			  + "   " 
+			  + Utils.doubleToString(m_rankResults[1][s[i]],
+						 fieldWidth+2, 1) 
+			  + " +-" 
+			  + Utils.doubleToString(m_rankResults[3][s[i]], 5, 2) 
+			+"  "
+			  + Utils.doubleToString(((double)(s[i] + 1)), 
+						 fieldWidth, 0)
+			  + " " 
+			  + m_trainInstances.attribute(s[i]).name() 
+			  + "\n");
+	}
+      }
+    }
+    else {
+      CvString.append("number of folds (%)  attribute\n");
+
+      for (int i = 0; i < m_subsetResults.length; i++) {
+	if ((m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) ||
+	    (i != m_trainInstances.classIndex())) {
+	  CvString.append(Utils.doubleToString(m_subsetResults[i], 12, 0) 
+			  + "(" 
+			  + Utils.doubleToString((m_subsetResults[i] / 
+						  m_numFolds * 100.0)
+						 , 3, 0) 
+			  + " %)  " 
+			  + Utils.doubleToString(((double)(i + 1)),
+						 fieldWidth, 0)
+			  + " " 
+			  + m_trainInstances.attribute(i).name() 
+			  + "\n");
+	}
+      }
+    }
+
+    return CvString.toString();
+  }
+
+  /**
+   * Select attributes for a split of the data. Calling this function
+   * updates the statistics on attribute selection. CVResultsString()
+   * returns a string summarizing the results of repeated calls to
+   * this function. Assumes that splits are from the same dataset---
+   * ie. have the same number and types of attributes as previous
+   * splits.
+   *
+   * @param split the instances to select attributes from
+   * @exception Exception if an error occurs
+   */
+  public void selectAttributesCVSplit(Instances split) throws Exception {
+    double[][] attributeRanking = null;
+
+    // if the train instances are null then set equal to this split.
+    // If this is the case then this function is more than likely being
+    // called from outside this class in order to obtain CV statistics
+    // and all we need m_trainIstances for is to get at attribute names
+    // and types etc.
+    if (m_trainInstances == null) {
+      m_trainInstances = split;
+    }
+
+    // create space to hold statistics
+    if (m_rankResults == null && m_subsetResults == null) {
+      m_subsetResults = new double[split.numAttributes()];
+      m_rankResults = new double[4][split.numAttributes()];
+    }
+
+    m_ASEvaluator.buildEvaluator(split);
+    // Do the search
+    int[] attributeSet = m_searchMethod.search(m_ASEvaluator, 
+					       split);
+    // Do any postprocessing that a attribute selection method might 
+    // require
+    attributeSet = m_ASEvaluator.postProcess(attributeSet);
+    
+    if ((m_searchMethod instanceof RankedOutputSearch) && 
+	(m_doRank == true)) {
+      attributeRanking = ((RankedOutputSearch)m_searchMethod).
+	rankedAttributes();
+      
+      // System.out.println(attributeRanking[0][1]);
+      for (int j = 0; j < attributeRanking.length; j++) {
+	// merit
+	m_rankResults[0][(int)attributeRanking[j][0]] += 
+	  attributeRanking[j][1];
+	// squared merit
+	m_rankResults[2][(int)attributeRanking[j][0]] += 
+	  (attributeRanking[j][1]*attributeRanking[j][1]);
+	// rank
+	m_rankResults[1][(int)attributeRanking[j][0]] += (j + 1);
+	// squared rank
+	m_rankResults[3][(int)attributeRanking[j][0]] += (j + 1)*(j + 1);
+	// += (attributeRanking[j][0] * attributeRanking[j][0]);
+      }
+    } else {
+      for (int j = 0; j < attributeSet.length; j++) {
+	m_subsetResults[attributeSet[j]]++;
+      }
+    }
+
+    m_trials++;
+  }
+
+  /**
+   * Perform a cross validation for attribute selection. With subset
+   * evaluators the number of times each attribute is selected over
+   * the cross validation is reported. For attribute evaluators, the
+   * average merit and average ranking + std deviation is reported for
+   * each attribute.
+   *
+   * @return the results of cross validation as a String
+   * @exception Exception if an error occurs during cross validation
+   */
+  public String CrossValidateAttributes () throws Exception {
+    Instances cvData = new Instances(m_trainInstances);
+    Instances train;
+
+    Random random = new Random(m_seed);
+    cvData.randomize(random);
+
+    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) && 
+	!(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) {
+      if (cvData.classAttribute().isNominal()) {
+	cvData.stratify(m_numFolds);
+      }
+
+    }
+
+    for (int i = 0; i < m_numFolds; i++) {
+      // Perform attribute selection
+      train = cvData.trainCV(m_numFolds, i, random);
+      selectAttributesCVSplit(train);
+    }
+
+    return  CVResultsString();
+  }
+
+  /**
+   * Perform attribute selection on the supplied training instances.
+   *
+   * @param data the instances to select attributes from
+   * @exception Exception if there is a problem during selection
+   */
+  public void SelectAttributes (Instances data) throws Exception {
+    int [] attributeSet;
+    
+    m_transformer = null;
+    m_attributeFilter = null;
+    m_trainInstances = data;
+    
+    if (m_doXval == true && (m_ASEvaluator instanceof AttributeTransformer)) {
+      throw new Exception("Can't cross validate an attribute transformer.");
+    }
+
+    if (m_ASEvaluator instanceof SubsetEvaluator &&
+	m_searchMethod instanceof Ranker) {
+      throw new Exception(m_ASEvaluator.getClass().getName()
+			  +" must use a search method other than Ranker");
+    }
+
+    if (m_ASEvaluator instanceof AttributeEvaluator &&
+	!(m_searchMethod instanceof Ranker)) {
+      //      System.err.println("AttributeEvaluators must use a Ranker search "
+      //			 +"method. Switching to Ranker...");
+      //      m_searchMethod = new Ranker();
+      throw new Exception("AttributeEvaluators must use the Ranker search "
+			  + "method");
+    }
+
+    if (m_searchMethod instanceof RankedOutputSearch) {
+      m_doRank = ((RankedOutputSearch)m_searchMethod).getGenerateRanking();
+    }
+
+    if (m_ASEvaluator instanceof UnsupervisedAttributeEvaluator ||
+	m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) {
+      // unset the class index
+      //      m_trainInstances.setClassIndex(-1);
+    } else {
+      // check that a class index has been set
+      if (m_trainInstances.classIndex() < 0) {
+	m_trainInstances.setClassIndex(m_trainInstances.numAttributes()-1);
+      }
+    }
+    
+    // Initialize the attribute evaluator
+    m_ASEvaluator.buildEvaluator(m_trainInstances);
+    if (m_ASEvaluator instanceof AttributeTransformer) {
+      m_trainInstances = 
+	((AttributeTransformer)m_ASEvaluator).transformedHeader();
+      m_transformer = (AttributeTransformer)m_ASEvaluator;
+    }
+    int fieldWidth = (int)(Math.log(m_trainInstances.numAttributes()) +1.0);
+
+    // Do the search
+    attributeSet = m_searchMethod.search(m_ASEvaluator, 
+					 m_trainInstances);
+
+    // try and determine if the search method uses an attribute transformer---
+    // this is a bit of a hack to make things work properly with RankSearch
+    // using PrincipalComponents as its attribute ranker
+     try {
+       BeanInfo bi = Introspector.getBeanInfo(m_searchMethod.getClass());
+       PropertyDescriptor properties[];
+       MethodDescriptor methods[];
+       //       methods = bi.getMethodDescriptors();
+       properties = bi.getPropertyDescriptors();
+       for (int i=0;i<properties.length;i++) {
+	 String name = properties[i].getDisplayName();
+	 Method meth = properties[i].getReadMethod();
+	 Object retType = meth.getReturnType();
+	 if (retType.equals(ASEvaluation.class)) {
+	   Class args [] = { };
+	   ASEvaluation tempEval = (ASEvaluation)(meth.invoke(m_searchMethod,
+							      (Object[])args));
+	   if (tempEval instanceof AttributeTransformer) {
+	     // grab the transformed data header
+	     m_trainInstances = 
+	       ((AttributeTransformer)tempEval).transformedHeader();
+	     m_transformer = (AttributeTransformer)tempEval;
+	   }
+	 }
+       }
+     } catch (IntrospectionException ex) {
+       System.err.println("AttributeSelection: Couldn't "
+			  +"introspect");
+     }
+     
+     
+     // Do any postprocessing that a attribute selection method might require
+     attributeSet = m_ASEvaluator.postProcess(attributeSet);
+     if (!m_doRank) {
+       m_selectionResults.append(printSelectionResults());
+     }
+     
+    if ((m_searchMethod instanceof RankedOutputSearch) && m_doRank == true) {
+      m_attributeRanking = 
+	((RankedOutputSearch)m_searchMethod).rankedAttributes();
+      m_selectionResults.append(printSelectionResults());
+      m_selectionResults.append("Ranked attributes:\n");
+
+      // retrieve the number of attributes to retain
+      m_numToSelect = 
+	((RankedOutputSearch)m_searchMethod).getCalculatedNumToSelect();
+
+      // determine fieldwidth for merit
+      int f_p=0;
+      int w_p=0;
+     
+      for (int i = 0; i < m_numToSelect; i++) {
+	double precision = (Math.abs(m_attributeRanking[i][1]) - 
+			    (int)(Math.abs(m_attributeRanking[i][1])));
+        double intPart = (int)(Math.abs(m_attributeRanking[i][1]));
+
+	if (precision > 0) {
+	  precision = Math.abs((Math.log(Math.abs(precision)) / 
+				Math.log(10)))+3;
+	}
+	if (precision > f_p) {
+	  f_p = (int)precision;
+	}
+
+        if (intPart == 0) {
+          if (w_p < 2) {
+            w_p = 2;
+          }
+        } else if ((Math.abs((Math.log(Math.abs(m_attributeRanking[i][1])) 
+		       / Math.log(10)))+1) > w_p) {
+	  if (m_attributeRanking[i][1] > 0) {
+	    w_p = (int)Math.abs((Math.log(Math.abs(m_attributeRanking[i][1]))
+				 / Math.log(10)))+1;
+	  }
+	}
+      }
+
+      for (int i = 0; i < m_numToSelect; i++) {
+	m_selectionResults.
+	  append(Utils.doubleToString(m_attributeRanking[i][1],
+				      f_p+w_p+1,f_p) 
+		 + Utils.doubleToString((m_attributeRanking[i][0] + 1),
+					fieldWidth+1,0) 
+		 + " " 
+		 + m_trainInstances.
+		 attribute((int)m_attributeRanking[i][0]).name() 
+		 + "\n");
+      }
+       
+      // set up the selected attributes array - usable by a filter or
+      // whatever
+      if (m_trainInstances.classIndex() >= 0) {
+        if ((!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator)        
+             && !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) ||
+            m_ASEvaluator instanceof AttributeTransformer) {
+          // one more for the class
+          m_selectedAttributeSet = new int[m_numToSelect + 1];
+          m_selectedAttributeSet[m_numToSelect] = 
+              m_trainInstances.classIndex();
+        } else {
+          m_selectedAttributeSet = new int[m_numToSelect];
+        }
+      } else {
+	m_selectedAttributeSet = new int[m_numToSelect];
+      }
+
+      m_selectionResults.append("\nSelected attributes: ");
+
+      for (int i = 0; i < m_numToSelect; i++) {
+	m_selectedAttributeSet[i] = (int)m_attributeRanking[i][0];
+	 
+	if (i == m_numToSelect - 1) {
+	  m_selectionResults.append(((int)m_attributeRanking[i][0] + 1) 
+				    + " : " 
+				    + (i + 1) 
+				    + "\n");
+	}
+	else {
+	  m_selectionResults.append(((int)m_attributeRanking[i][0] + 1));
+	  m_selectionResults.append(",");
+	}
+      }
+    } else {
+      // set up the selected attributes array - usable by a filter or
+      // whatever
+      if ((!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) 
+	   && !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) || 
+	  m_trainInstances.classIndex() >= 0) 
+	// one more for the class
+	{
+	  m_selectedAttributeSet = new int[attributeSet.length + 1];
+	  m_selectedAttributeSet[attributeSet.length] = 
+	    m_trainInstances.classIndex();
+	}
+      else {
+	m_selectedAttributeSet = new int[attributeSet.length];
+      }
+      
+      for (int i = 0; i < attributeSet.length; i++) {
+	m_selectedAttributeSet[i] = attributeSet[i];
+      }
+
+      m_selectionResults.append("Selected attributes: ");
+
+      for (int i = 0; i < attributeSet.length; i++) {
+	if (i == (attributeSet.length - 1)) {
+	  m_selectionResults.append((attributeSet[i] + 1) 
+				    + " : " 
+				    + attributeSet.length 
+				    + "\n");
+	}
+	else {
+	  m_selectionResults.append((attributeSet[i] + 1) + ",");
+	}
+      }
+
+      for (int i=0;i<attributeSet.length;i++) {
+	m_selectionResults.append("                     "
+				  +m_trainInstances
+				  .attribute(attributeSet[i]).name()
+				  +"\n");
+      }
+    }
+
+    // Cross validation should be called from here
+    if (m_doXval == true) {
+      m_selectionResults.append(CrossValidateAttributes()); 
+    }
+
+    // set up the attribute filter with the selected attributes
+    if (m_selectedAttributeSet != null && !m_doXval) {
+      m_attributeFilter = new Remove();
+      m_attributeFilter.setAttributeIndicesArray(m_selectedAttributeSet);
+      m_attributeFilter.setInvertSelection(true);
+      m_attributeFilter.setInputFormat(m_trainInstances);  
+    }
+
+    // Save space
+    m_trainInstances = new Instances(m_trainInstances, 0);
+  }
+
+  /**
+   * Perform attribute selection with a particular evaluator and
+   * a set of options specifying search method and options for the
+   * search method and evaluator.
+   *
+   * @param ASEvaluator an evaluator object
+   * @param options an array of options, not only for the evaluator
+   * but also the search method (if any) and an input data file
+   * @param train the input instances
+   * @return the results of attribute selection as a String
+   * @exception Exception if incorrect options are supplied
+   */
+  public static String SelectAttributes (ASEvaluation ASEvaluator, 
+					 String[] options, 
+					 Instances train)
+    throws Exception {
+    int seed = 1, folds = 10;
+    String foldsString, seedString, searchName;
+    String classString;
+    String searchClassName;
+    String[] searchOptions = null; //new String [1];
+    ASSearch searchMethod = null;
+    boolean doCrossVal = false;
+    int classIndex = -1;
+    boolean helpRequested = false;
+    AttributeSelection trainSelector = new AttributeSelection();
+
+    try {
+      if (Utils.getFlag('h', options)) {
+	helpRequested = true;
+      }
+
+      // does data already have a class attribute set?
+      if (train.classIndex() != -1)
+	classIndex = train.classIndex() + 1;
+
+      // get basic options (options the same for all attribute selectors
+      classString = Utils.getOption('c', options);
+
+      if (classString.length() != 0) {
+	if (classString.equals("first")) {
+	  classIndex = 1;
+	} else if (classString.equals("last")) {
+	  classIndex = train.numAttributes();
+	} else {
+	  classIndex = Integer.parseInt(classString);
+	}
+      }
+
+      if ((classIndex != -1) && 
+	  ((classIndex == 0) || (classIndex > train.numAttributes()))) {
+	throw  new Exception("Class index out of range.");
+      }
+
+      if (classIndex != -1) {
+	train.setClassIndex(classIndex - 1);
+      }
+      else {
+	//	classIndex = train.numAttributes();
+	//	train.setClassIndex(classIndex - 1);
+      }
+      
+      foldsString = Utils.getOption('x', options);
+
+      if (foldsString.length() != 0) {
+	folds = Integer.parseInt(foldsString);
+	doCrossVal = true;
+      }
+      
+      trainSelector.setFolds(folds);
+      trainSelector.setXval(doCrossVal);
+
+      seedString = Utils.getOption('n', options);
+
+      if (seedString.length() != 0) {
+	seed = Integer.parseInt(seedString);
+      }
+
+      trainSelector.setSeed(seed);
+
+      searchName = Utils.getOption('s', options);
+
+      if ((searchName.length() == 0) && 
+	  (!(ASEvaluator instanceof AttributeEvaluator))) {
+	throw  new Exception("No search method given.");
+      }
+
+      if (searchName.length() != 0) {
+	searchName = searchName.trim();
+	// split off any search options
+	int breakLoc = searchName.indexOf(' ');
+	searchClassName = searchName;
+	String searchOptionsString = "";
+
+	if (breakLoc != -1) {
+	  searchClassName = searchName.substring(0, breakLoc);
+	  searchOptionsString = searchName.substring(breakLoc).trim();
+	  searchOptions = Utils.splitOptions(searchOptionsString);
+	}
+      }
+      else {
+	try {
+	  searchClassName = new String("weka.attributeSelection.Ranker");
+	  searchMethod = (ASSearch)Class.
+	    forName(searchClassName).newInstance();
+	}
+	catch (Exception e) {
+	  throw  new Exception("Can't create Ranker object");
+	}
+      }
+
+      // if evaluator is a subset evaluator
+      // create search method and set its options (if any)
+      if (searchMethod == null) {
+	searchMethod = ASSearch.forName(searchClassName, searchOptions);
+      }
+
+      // set the search method
+      trainSelector.setSearch(searchMethod);
+    }
+    catch (Exception e) {
+      throw  new Exception('\n' + e.getMessage() 
+			   + makeOptionString(ASEvaluator, searchMethod));
+    }
+
+    try {
+      // Set options for ASEvaluator
+      if (ASEvaluator instanceof OptionHandler) {
+	((OptionHandler)ASEvaluator).setOptions(options);
+      }
+
+      /* // Set options for Search method
+	 if (searchMethod instanceof OptionHandler)
+	 {
+	 if (searchOptions != null)
+	 {
+	 ((OptionHandler)searchMethod).setOptions(searchOptions);
+	 }
+	 }
+	 Utils.checkForRemainingOptions(searchOptions); */
+    }
+    catch (Exception e) {
+      throw  new Exception("\n" + e.getMessage() 
+			   + makeOptionString(ASEvaluator, searchMethod));
+    }
+
+    try {
+      Utils.checkForRemainingOptions(options);
+    }
+    catch (Exception e) {
+      throw  new Exception('\n' + e.getMessage() 
+			   + makeOptionString(ASEvaluator, searchMethod));
+    }
+
+    if (helpRequested) {
+      System.out.println(makeOptionString(ASEvaluator, searchMethod));
+      System.exit(0);
+    }
+
+    // set the attribute evaluator
+    trainSelector.setEvaluator(ASEvaluator);
+
+    // do the attribute selection
+    trainSelector.SelectAttributes(train);
+
+    // return the results string
+    return trainSelector.toResultsString();
+  }
+
+
+  /**
+   * Assembles a text description of the attribute selection results.
+   *
+   * @return a string describing the results of attribute selection.
+   */
+  private String printSelectionResults () {
+    StringBuffer text = new StringBuffer();
+    text.append("\n\n=== Attribute Selection on all input data ===\n\n" 
+		+ "Search Method:\n");
+    text.append(m_searchMethod.toString());
+    text.append("\nAttribute ");
+
+    if (m_ASEvaluator instanceof SubsetEvaluator) {
+      text.append("Subset Evaluator (");
+    }
+    else {
+      text.append("Evaluator (");
+    }
+
+    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) 
+	&& !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) {
+      text.append("supervised, ");
+      text.append("Class (");
+
+      if (m_trainInstances.attribute(m_trainInstances.classIndex())
+	  .isNumeric()) {
+	text.append("numeric): ");
+      }
+      else {
+	text.append("nominal): ");
+      }
+
+      text.append((m_trainInstances.classIndex() + 1) 
+		  + " " 
+		  + m_trainInstances.attribute(m_trainInstances
+					       .classIndex()).name() 
+		  + "):\n");
+    }
+    else {
+      text.append("unsupervised):\n");
+    }
+
+    text.append(m_ASEvaluator.toString() + "\n");
+    return  text.toString();
+  }
+
+
+  /**
+   * Make up the help string giving all the command line options
+   *
+   * @param ASEvaluator the attribute evaluator to include options for
+   * @param searchMethod the search method to include options for
+   * @return a string detailing the valid command line options
+   * @throws Exception if something goes wrong
+   */
+  private static String makeOptionString (ASEvaluation ASEvaluator, 
+					  ASSearch searchMethod)
+    throws Exception {
+    
+    StringBuffer optionsText = new StringBuffer("");
+    // General options
+    optionsText.append("\n\nGeneral options:\n\n");
+    optionsText.append("-h\n\tdisplay this help\n");
+    optionsText.append("-i <name of input file>\n");
+    optionsText.append("\tSets training file.\n");
+    optionsText.append("-c <class index>\n");
+    optionsText.append("\tSets the class index for supervised attribute\n");
+    optionsText.append("\tselection. Default=last column.\n");
+    optionsText.append("-s <class name>\n");
+    optionsText.append("\tSets search method for subset evaluators.\n");
+    optionsText.append("-x <number of folds>\n");
+    optionsText.append("\tPerform a cross validation.\n");
+    optionsText.append("-n <random number seed>\n");
+    optionsText.append("\tUse in conjunction with -x.\n");
+
+    // Get attribute evaluator-specific options
+    if (ASEvaluator instanceof OptionHandler) {
+      optionsText.append("\nOptions specific to " 
+			 + ASEvaluator.getClass().getName() 
+			 + ":\n\n");
+      Enumeration enu = ((OptionHandler)ASEvaluator).listOptions();
+
+      while (enu.hasMoreElements()) {
+	Option option = (Option)enu.nextElement();
+	optionsText.append(option.synopsis() + '\n');
+	optionsText.append(option.description() + "\n");
+      }
+    }
+
+    if (searchMethod != null) {
+      if (searchMethod instanceof OptionHandler) {
+	optionsText.append("\nOptions specific to " 
+			   + searchMethod.getClass().getName() 
+			   + ":\n\n");
+	Enumeration enu = ((OptionHandler)searchMethod).listOptions();
+
+	while (enu.hasMoreElements()) {
+	  Option option = (Option)enu.nextElement();
+	  optionsText.append(option.synopsis() + '\n');
+	  optionsText.append(option.description() + "\n");
+	}
+      }
+    }
+    else {
+      if (ASEvaluator instanceof SubsetEvaluator) {
+	System.out.println("No search method given.");
+      }
+    }
+
+    return  optionsText.toString();
+  }
+
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    try {
+      if (args.length == 0) {
+	throw  new Exception("The first argument must be the name of an " 
+			     + "attribute/subset evaluator");
+      }
+
+      String EvaluatorName = args[0];
+      args[0] = "";
+      ASEvaluation newEval = ASEvaluation.forName(EvaluatorName, null);
+      System.out.println(SelectAttributes(newEval, args));
+    }
+    catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.47 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSetEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSetEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeSetEvaluator.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+ /*
+ *    RELEASE INFORMATION (December 27, 2004)
+ *    
+ *    FCBF algorithm:
+ *      Template obtained from Weka
+ *      Developped for Weka by Zheng Alan Zhao   
+ *      December 27, 2004
+ *
+ *    FCBF algorithm is a feature selection method based on Symmetrical Uncertainty 
+ *    Measurement for relevance redundancy analysis. The details of FCBF algorithm are 
+ *    in L. Yu and H. Liu. Feature selection for high-dimensional data: a fast 
+ *    correlation-based filter solution. In Proceedings of the twentieth International 
+ *    Conference on Machine Learning, pages 856--863, 2003.
+ *    
+ *    
+ *    CONTACT INFORMATION
+ *    
+ *    For algorithm implementation:
+ *    Zheng Zhao: zhaozheng at asu.edu
+ *      
+ *    For the algorithm:
+ *    Lei Yu: leiyu at asu.edu
+ *    Huan Liu: hliu at asu.edu
+ *     
+ *    Data Mining and Machine Learning Lab
+ *    Computer Science and Engineering Department
+ *    Fulton School of Engineering
+ *    Arizona State University
+ *    Tempe, AZ 85287
+ *
+ *    AttributeSetEvaluator.java
+ *
+ *    Copyright (C) 2004 Data Mining and Machine Learning Lab, 
+ *                       Computer Science and Engineering Department, 
+ *       		 Fulton School of Engineering, 
+ *                       Arizona State University
+ *
+ */
+
+package weka.attributeSelection;
+
+
+/**
+ * Abstract attribute set evaluator.
+ *
+ * @author Zheng Zhao: zhaozheng at asu.edu
+ * @version $Revision: 1.3 $
+ */
+public abstract class AttributeSetEvaluator extends ASEvaluation {
+  
+    /** for serialization */
+    private static final long serialVersionUID = -5744881009422257389L;
+  
+    // ===============
+    // Public methods.
+    // ===============
+
+    /**
+     * evaluates an individual attribute
+     *
+     * @param attribute the index of the attribute to be evaluated
+     * @return the "merit" of the attribute
+     * @exception Exception if the attribute could not be evaluated
+     */
+    public abstract double evaluateAttribute(int attribute) throws Exception;
+
+  /**
+   * Evaluates a set of attributes
+   *
+   * @param attributes an <code>int[]</code> value
+   * @param classAttributes an <code>int[]</code> value
+   * @return a <code>double</code> value
+   * @exception Exception if an error occurs
+   */
+  public abstract double evaluateAttribute(int[] attributes, int[] classAttributes) 
+    throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeTransformer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeTransformer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/AttributeTransformer.java	(revision 29)
@@ -0,0 +1,65 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeTransformer.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/** 
+ * Abstract attribute transformer. Transforms the dataset.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 4613 $
+ */
+public interface AttributeTransformer {
+    // ===============
+    // Public methods.
+    // ===============
+
+  /**
+   * Returns just the header for the transformed data (ie. an empty
+   * set of instances. This is so that AttributeSelection can
+   * determine the structure of the transformed data without actually
+   * having to get all the transformed data through getTransformedData().
+   * @return the header of the transformed data.
+   * @exception Exception if the header of the transformed data can't
+   * be determined.
+   */
+  Instances transformedHeader() throws Exception;
+
+  /**
+   * Transform the supplied data set (assumed to be the same format
+   * as the training data)
+   * @return A set of instances representing the transformed data
+   * @exception Exception if the attribute could not be evaluated
+   */
+  Instances transformedData(Instances data) throws Exception;
+
+  /**
+   * Transforms an instance in the format of the original data to the
+   * transformed space
+   * @return a transformed instance
+   * @exception Exception if the instance could not be transformed
+   */
+  Instance convertInstance(Instance instance) throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/BestFirst.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/BestFirst.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/BestFirst.java	(revision 29)
@@ -0,0 +1,932 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BestFirst.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * BestFirst:<br/>
+ * <br/>
+ * Searches the space of attribute subsets by greedy hillclimbing augmented with a backtracking facility. Setting the number of consecutive non-improving nodes allowed controls the level of backtracking done. Best first may start with the empty set of attributes and search forward, or start with the full set of attributes and search backward, or start at any point and search in both directions (by considering all possible single attribute additions and deletions at a given point).<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.</pre>
+ * 
+ * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+ *  Direction of search. (default = 1).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Number of non-improving nodes to
+ *  consider before terminating search.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Size of lookup cache for evaluated subsets.
+ *  Expressed as a multiple of the number of
+ *  attributes in the data set. (default = 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ *         Martin Guetlein (cashing merit of expanded nodes) 
+ * @version $Revision: 1.29 $
+ */
+public class BestFirst 
+  extends ASSearch 
+  implements OptionHandler, StartSetHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 7841338689536821867L;
+
+  // Inner classes
+  /**
+   * Class for a node in a linked list. Used in best first search.
+   * @author Mark Hall (mhall@cs.waikato.ac.nz)
+   **/
+  public class Link2 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -8236598311516351420L;
+    
+    /*    BitSet group; */
+    Object [] m_data;
+    double m_merit;
+
+
+    /** 
+     * Constructor
+     */
+    public Link2 (Object [] data, double mer) {
+      //      group = (BitSet)gr.clone();
+      m_data = data;
+      m_merit = mer;
+    }
+
+
+    /** Get a group */
+    public Object [] getData () {
+      return  m_data;
+    }
+
+
+    public String toString () {
+      return  ("Node: " + m_data.toString() + "  " + m_merit);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.29 $");
+    }
+  }
+
+
+  /**
+   * Class for handling a linked list. Used in best first search.
+   * Extends the Vector class.
+   * @author Mark Hall (mhall@cs.waikato.ac.nz)
+   **/
+  public class LinkedList2
+    extends FastVector {
+    
+    /** for serialization */
+    static final long serialVersionUID = 3250538292330398929L;
+    
+    /** Max number of elements in the list */
+    int m_MaxSize;
+
+    // ================
+    // Public methods
+    // ================
+    public LinkedList2 (int sz) {
+      super();
+      m_MaxSize = sz;
+    }
+
+
+    /**
+     * removes an element (Link) at a specific index from the list.
+     * @param index the index of the element to be removed.
+     **/
+    public void removeLinkAt (int index)
+      throws Exception {
+      
+      if ((index >= 0) && (index < size())) {
+        removeElementAt(index);
+      }
+      else {
+        throw  new Exception("index out of range (removeLinkAt)");
+      }
+    }
+
+
+    /**
+     * returns the element (Link) at a specific index from the list.
+     * @param index the index of the element to be returned.
+     **/
+    public Link2 getLinkAt (int index)
+      throws Exception {
+      
+      if (size() == 0) {
+        throw  new Exception("List is empty (getLinkAt)");
+      }
+      else {if ((index >= 0) && (index < size())) {
+        return  ((Link2)(elementAt(index)));
+      }
+      else {
+        throw  new Exception("index out of range (getLinkAt)");
+      }
+      }
+    }
+
+
+    /**
+     * adds an element (Link) to the list.
+     * @param data the attribute set specification
+     * @param mer the "merit" of this attribute set
+     **/
+    public void addToList (Object [] data, double mer)
+      throws Exception {
+      Link2 newL = new Link2(data, mer);
+
+      if (size() == 0) {
+	addElement(newL);
+      }
+      else {if (mer > ((Link2)(firstElement())).m_merit) {
+	if (size() == m_MaxSize) {
+	  removeLinkAt(m_MaxSize - 1);
+	}
+
+	//----------
+	insertElementAt(newL, 0);
+      }
+      else {
+	int i = 0;
+	int size = size();
+	boolean done = false;
+
+	//------------
+	// don't insert if list contains max elements an this
+	// is worst than the last
+	if ((size == m_MaxSize) && (mer <= ((Link2)(lastElement())).m_merit)) {
+
+	}
+	//---------------
+	else {
+	  while ((!done) && (i < size)) {
+	    if (mer > ((Link2)(elementAt(i))).m_merit) {
+	      if (size == m_MaxSize) {
+		removeLinkAt(m_MaxSize - 1);
+	      }
+
+	      // ---------------------
+	      insertElementAt(newL, i);
+	      done = true;
+	    }
+	    else {if (i == size - 1) {
+	      addElement(newL);
+	      done = true;
+	    }
+	    else {
+	      i++;
+	    }
+	    }
+	  }
+	}
+      }
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.29 $");
+    }
+  }
+
+  // member variables
+  /** maximum number of stale nodes before terminating search */
+  protected int m_maxStale;
+
+  /** 0 == backward search, 1 == forward search, 2 == bidirectional */
+  protected int m_searchDirection;
+
+  /** search direction: backward */
+  protected static final int SELECTION_BACKWARD = 0;
+  /** search direction: forward */
+  protected static final int SELECTION_FORWARD = 1;
+  /** search direction: bidirectional */
+  protected static final int SELECTION_BIDIRECTIONAL = 2;
+  /** search directions */
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(SELECTION_BACKWARD, "Backward"),
+    new Tag(SELECTION_FORWARD, "Forward"),
+    new Tag(SELECTION_BIDIRECTIONAL, "Bi-directional"),
+  };
+
+  /** holds an array of starting attributes */
+  protected int[] m_starting;
+
+  /** holds the start set for the search as a Range */
+  protected Range m_startRange;
+
+  /** does the data have a class */
+  protected boolean m_hasClass;
+
+  /** holds the class index */
+  protected int m_classIndex;
+
+  /** number of attributes in the data */
+  protected int m_numAttribs;
+
+  /** total number of subsets evaluated during a search */
+  protected int m_totalEvals;
+
+  /** for debugging */
+  protected boolean m_debug;
+
+  /** holds the merit of the best subset found */
+  protected double m_bestMerit;
+
+  /** holds the maximum size of the lookup cache for evaluated subsets */
+  protected int m_cacheSize;
+  
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search method suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "BestFirst:\n\n"
+      +"Searches the space of attribute subsets by greedy hillclimbing "
+      +"augmented with a backtracking facility. Setting the number of "
+      +"consecutive non-improving nodes allowed controls the level of "
+      +"backtracking done. Best first may start with the empty set of "
+      +"attributes and search forward, or start with the full set of "
+      +"attributes and search backward, or start at any point and search "
+      +"in both directions (by considering all possible single attribute "
+      +"additions and deletions at a given point).\n";
+  }
+
+  /** 
+   *Constructor 
+   */
+  public BestFirst () {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   *
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+    
+    newVector.addElement(new Option("\tSpecify a starting set of attributes." 
+				    + "\n\tEg. 1,3,5-7."
+				    ,"P",1
+				    , "-P <start set>"));
+    newVector.addElement(new Option("\tDirection of search. (default = 1)."
+				    , "D", 1
+				    , "-D <0 = backward | 1 = forward " 
+				    + "| 2 = bi-directional>"));
+    newVector.addElement(new Option("\tNumber of non-improving nodes to" 
+				    + "\n\tconsider before terminating search."
+				    , "N", 1, "-N <num>"));
+    newVector.addElement(new Option("\tSize of lookup cache for evaluated subsets."
+				    +"\n\tExpressed as a multiple of the number of"
+				    +"\n\tattributes in the data set. (default = 1)",
+				    "S", 1, "-S <num>"));
+				    
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.</pre>
+   * 
+   * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+   *  Direction of search. (default = 1).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Number of non-improving nodes to
+   *  consider before terminating search.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Size of lookup cache for evaluated subsets.
+   *  Expressed as a multiple of the number of
+   *  attributes in the data set. (default = 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('D', options);
+
+    if (optionString.length() != 0) {
+      setDirection(new SelectedTag(Integer.parseInt(optionString),
+				   TAGS_SELECTION));
+    } else {
+      setDirection(new SelectedTag(SELECTION_FORWARD, TAGS_SELECTION));
+    }
+
+    optionString = Utils.getOption('N', options);
+
+    if (optionString.length() != 0) {
+      setSearchTermination(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      setLookupCacheSize(Integer.parseInt(optionString));
+    }
+
+    m_debug = Utils.getFlag('Z', options);
+  }
+
+  /**
+   * Set the maximum size of the evaluated subset cache (hashtable). This is
+   * expressed as a multiplier for the number of attributes in the data set.
+   * (default = 1).
+   *
+   * @param size the maximum size of the hashtable
+   */
+  public void setLookupCacheSize(int size) {
+    if (size >= 0) {
+      m_cacheSize = size;
+    }
+  }
+
+  /**
+   * Return the maximum size of the evaluated subset cache (expressed as a multiplier
+   * for the number of attributes in a data set.
+   *
+   * @return the maximum size of the hashtable.
+   */
+  public int getLookupCacheSize() {
+    return m_cacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lookupCacheSizeTipText() {
+    return "Set the maximum size of the lookup cache of evaluated subsets. This is "
+      +"expressed as a multiplier of the number of attributes in the data set. "
+      +"(default = 1).";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Set the start point for the search. This is specified as a comma "
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchTerminationTipText() {
+    return "Set the amount of backtracking. Specify the number of ";
+  }
+
+  /**
+   * Set the numnber of non-improving nodes to consider before terminating
+   * search.
+   *
+   * @param t the number of non-improving nodes
+   * @throws Exception if t is less than 1
+   */
+  public void setSearchTermination (int t)
+    throws Exception {
+    if (t < 1) {
+      throw  new Exception("Value of -N must be > 0.");
+    }
+
+    m_maxStale = t;
+  }
+
+
+  /**
+   * Get the termination criterion (number of non-improving nodes).
+   *
+   * @return the number of non-improving nodes
+   */
+  public int getSearchTermination () {
+    return  m_maxStale;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String directionTipText() {
+    return "Set the direction of the search.";
+  }
+
+  /**
+   * Set the search direction
+   *
+   * @param d the direction of the search
+   */
+  public void setDirection (SelectedTag d) {
+    
+    if (d.getTags() == TAGS_SELECTION) {
+      m_searchDirection = d.getSelectedTag().getID();
+    }
+  }
+
+
+  /**
+   * Get the search direction
+   *
+   * @return the direction of the search
+   */
+  public SelectedTag getDirection () {
+
+    return new SelectedTag(m_searchDirection, TAGS_SELECTION);
+  }
+
+
+  /**
+   * Gets the current settings of BestFirst.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[6];
+    int current = 0;
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = ""+startSetToString();
+    }
+    options[current++] = "-D";
+    options[current++] = "" + m_searchDirection;
+    options[current++] = "-N";
+    options[current++] = "" + m_maxStale;
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+
+    if (m_starting == null) {
+      return getStartSet();
+    }
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+      
+      if ((m_hasClass == false) || 
+	  (m_hasClass == true && i != m_classIndex)) {
+	FString.append((m_starting[i] + 1));
+	didPrint = true;
+      }
+      
+      if (i == (m_starting.length - 1)) {
+	FString.append("");
+      }
+      else {
+	if (didPrint) {
+	  FString.append(",");
+	  }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * returns a description of the search as a String
+   * @return a description of the search
+   */
+  public String toString () {
+    StringBuffer BfString = new StringBuffer();
+    BfString.append("\tBest first.\n\tStart set: ");
+
+    if (m_starting == null) {
+      BfString.append("no attributes\n");
+    }
+    else {
+      BfString.append(startSetToString()+"\n");
+    }
+
+    BfString.append("\tSearch direction: ");
+
+    if (m_searchDirection == SELECTION_BACKWARD) {
+      BfString.append("backward\n");
+    }
+    else {if (m_searchDirection == SELECTION_FORWARD) {
+      BfString.append("forward\n");
+    }
+    else {
+      BfString.append("bi-directional\n");
+    }
+    }
+
+    BfString.append("\tStale search after " 
+		    + m_maxStale + " node expansions\n");
+    BfString.append("\tTotal number of subsets evaluated: " 
+		    + m_totalEvals + "\n");
+    BfString.append("\tMerit of best subset found: "
+		    +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
+    return  BfString.toString();
+  }
+
+
+  protected void printGroup (BitSet tt, int numAttribs) {
+    int i;
+
+    for (i = 0; i < numAttribs; i++) {
+      if (tt.get(i) == true) {
+	System.out.print((i + 1) + " ");
+      }
+    }
+
+    System.out.println();
+  }
+
+
+  /**
+   * Searches the attribute subset space by best first search
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+    m_totalEvals = 0;
+    if (!(ASEval instanceof SubsetEvaluator)) {
+      throw  new Exception(ASEval.getClass().getName() 
+			   + " is not a " 
+			   + "Subset evaluator!");
+    }
+
+    if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+      m_hasClass = false;
+    } else {
+      m_hasClass = true;
+      m_classIndex = data.classIndex();
+    }
+
+    SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
+    m_numAttribs = data.numAttributes();
+    int i, j;
+    int best_size = 0;
+    int size = 0;
+    int done;
+    int sd = m_searchDirection;
+    BitSet best_group, temp_group;
+    int stale;
+    double best_merit;
+    double merit;
+    boolean z;
+    boolean added;
+    Link2 tl;
+    Hashtable lookup = new Hashtable(m_cacheSize * m_numAttribs);
+    int insertCount = 0;
+    int cacheHits = 0;
+    LinkedList2 bfList = new LinkedList2(m_maxStale);
+    best_merit = -Double.MAX_VALUE;
+    stale = 0;
+    best_group = new BitSet(m_numAttribs);
+
+    m_startRange.setUpper(m_numAttribs-1);
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+    // If a starting subset has been supplied, then initialise the bitset
+    if (m_starting != null) {
+      for (i = 0; i < m_starting.length; i++) {
+	if ((m_starting[i]) != m_classIndex) {
+	  best_group.set(m_starting[i]);
+	}
+      }
+
+      best_size = m_starting.length;
+      m_totalEvals++;
+    } else {
+      if (m_searchDirection == SELECTION_BACKWARD) {
+	setStartSet("1-last");
+	m_starting = new int[m_numAttribs];
+
+	// init initial subset to all attributes
+	for (i = 0, j = 0; i < m_numAttribs; i++) {
+	  if (i != m_classIndex) {
+	    best_group.set(i);
+	    m_starting[j++] = i;
+	  }
+	}
+
+	best_size = m_numAttribs - 1;
+	m_totalEvals++;
+      }
+    }
+
+    // evaluate the initial subset
+    best_merit = ASEvaluator.evaluateSubset(best_group);
+    // add the initial group to the list and the hash table
+    Object [] best = new Object[1];
+    best[0] = best_group.clone();
+    bfList.addToList(best, best_merit);
+    BitSet tt = (BitSet)best_group.clone();
+    String hashC = tt.toString();
+    lookup.put(hashC, new Double(best_merit));
+
+    while (stale < m_maxStale) {
+      added = false;
+
+      if (m_searchDirection == SELECTION_BIDIRECTIONAL) {
+	// bi-directional search
+        done = 2;
+        sd = SELECTION_FORWARD;
+      } else {
+	done = 1;
+      }
+
+      // finished search?
+      if (bfList.size() == 0) {
+	stale = m_maxStale;
+	break;
+      }
+
+      // copy the attribute set at the head of the list
+      tl = bfList.getLinkAt(0);
+      temp_group = (BitSet)(tl.getData()[0]);
+      temp_group = (BitSet)temp_group.clone();
+      // remove the head of the list
+      bfList.removeLinkAt(0);
+      // count the number of bits set (attributes)
+      int kk;
+
+      for (kk = 0, size = 0; kk < m_numAttribs; kk++) {
+	if (temp_group.get(kk)) {
+	  size++;
+	}
+      }
+
+      do {
+	for (i = 0; i < m_numAttribs; i++) {
+	  if (sd == SELECTION_FORWARD) {
+	    z = ((i != m_classIndex) && (!temp_group.get(i)));
+	  } else {
+	    z = ((i != m_classIndex) && (temp_group.get(i)));
+	  }
+          
+	  if (z) {
+	    // set the bit (attribute to add/delete)
+	    if (sd == SELECTION_FORWARD) {
+	      temp_group.set(i);
+	      size++;
+	    } else {
+	      temp_group.clear(i);
+	      size--;
+	    }
+
+	    /* if this subset has been seen before, then it is already 
+	       in the list (or has been fully expanded) */
+	    tt = (BitSet)temp_group.clone();
+	    hashC = tt.toString();
+	    
+	    if (lookup.containsKey(hashC) == false) {
+	      merit = ASEvaluator.evaluateSubset(temp_group);
+	      m_totalEvals++;
+	      
+	      // insert this one in the hashtable
+	      if (insertCount > m_cacheSize * m_numAttribs) {
+		lookup = new Hashtable(m_cacheSize * m_numAttribs);
+		insertCount = 0;
+	      }
+	      hashC = tt.toString();
+    	      lookup.put(hashC, new Double(merit));
+    	      insertCount++;
+	    } else {
+	      merit = ((Double)lookup.get(hashC)).doubleValue();
+	      cacheHits++;  
+	    }
+	    
+	    // insert this one in the list
+	    Object[] add = new Object[1];
+	    add[0] = tt.clone();
+	    bfList.addToList(add, merit);
+	    
+	    if (m_debug) {
+	      System.out.print("Group: ");
+	      printGroup(tt, m_numAttribs);
+	      System.out.println("Merit: " + merit);
+	    }
+
+	    // is this better than the best?
+	    if (sd == SELECTION_FORWARD) {
+	      z = ((merit - best_merit) > 0.00001);
+	    } else {
+	      if (merit == best_merit) {
+		z = (size < best_size);
+	      } else {
+		z = (merit >  best_merit);
+	      } 
+	    }
+
+	    if (z) {
+	      added = true;
+	      stale = 0;
+	      best_merit = merit;
+	      //		best_size = (size + best_size);
+	      best_size = size;
+	      best_group = (BitSet)(temp_group.clone());
+	    }
+
+	    // unset this addition(deletion)
+	    if (sd == SELECTION_FORWARD) {
+	      temp_group.clear(i);
+	      size--;
+	    } else {
+	      temp_group.set(i);
+	      size++;
+	    }
+	  }
+	}
+
+	if (done == 2) {
+	  sd = SELECTION_BACKWARD;
+	}
+
+	done--;
+      } while (done > 0);
+
+      /* if we haven't added a new attribute subset then full expansion 
+	 of this node hasen't resulted in anything better */
+      if (!added) {
+	stale++;
+      }
+    }
+
+    m_bestMerit = best_merit;
+    return  attributeList(best_group);
+  }
+
+
+  /**
+   * Reset options to default values
+   */
+  protected void resetOptions () {
+    m_maxStale = 5;
+    m_searchDirection = SELECTION_FORWARD;
+    m_starting = null;
+    m_startRange = new Range();
+    m_classIndex = -1;
+    m_totalEvals = 0;
+    m_cacheSize = 1;
+    m_debug = false;
+  }
+
+
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  protected int[] attributeList (BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.29 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/CfsSubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/CfsSubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/CfsSubsetEval.java	(revision 29)
@@ -0,0 +1,1140 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CfsSubsetEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * CfsSubsetEval :<br/>
+ * <br/>
+ * Evaluates the worth of a subset of attributes by considering the individual predictive ability of each feature along with the degree of redundancy between them.<br/>
+ * <br/>
+ * Subsets of features that are highly correlated with the class while having low intercorrelation are preferred.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * M. A. Hall (1998). Correlation-based Feature Subset Selection for Machine Learning. Hamilton, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Hall1998,
+ *    address = {Hamilton, New Zealand},
+ *    author = {M. A. Hall},
+ *    school = {University of Waikato},
+ *    title = {Correlation-based Feature Subset Selection for Machine Learning},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  Treat missing values as a separate value.</pre>
+ * 
+ * <pre> -L
+ *  Don't include locally predictive attributes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 6132 $
+ * @see Discretize
+ */
+public class CfsSubsetEval
+  extends ASEvaluation
+  implements SubsetEvaluator, 
+             OptionHandler, 
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 747878400813276317L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+  /** Discretise attributes when class in nominal */
+  private Discretize m_disTransform;
+  /** The class index */
+  private int m_classIndex;
+  /** Is the class numeric */
+  private boolean m_isNumeric;
+  /** Number of attributes in the training data */
+  private int m_numAttribs;
+  /** Number of instances in the training data */
+  private int m_numInstances;
+  /** Treat missing values as separate values */
+  private boolean m_missingSeparate;
+  /** Include locally predictive attributes */
+  private boolean m_locallyPredictive;
+  /** Holds the matrix of attribute correlations */
+  //  private Matrix m_corr_matrix;
+  private float [][] m_corr_matrix;
+  /** Standard deviations of attributes (when using pearsons correlation) */
+  private double[] m_std_devs;
+  /** Threshold for admitting locally predictive features */
+  private double m_c_Threshold;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "CfsSubsetEval :\n\nEvaluates the worth of a subset of attributes "
+      +"by considering the individual predictive ability of each feature "
+      +"along with the degree of redundancy between them.\n\n"
+      +"Subsets of features that are highly correlated with the class "
+      +"while having low intercorrelation are preferred.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "M. A. Hall");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Correlation-based Feature Subset Selection for Machine Learning");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+    
+    return result;
+  }
+
+  /**
+   * Constructor
+   */
+  public CfsSubsetEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   *
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option("\tTreat missing values as a separate " 
+                                    + "value.", "M", 0, "-M"));
+    newVector.addElement(new Option("\tDon't include locally predictive attributes" 
+                                    + ".", "L", 0, "-L"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses and sets a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  Treat missing values as a separate value.</pre>
+   * 
+   * <pre> -L
+   *  Don't include locally predictive attributes.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+
+    resetOptions();
+    setMissingSeparate(Utils.getFlag('M', options));
+    setLocallyPredictive(!Utils.getFlag('L', options));
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String locallyPredictiveTipText() {
+    return "Identify locally predictive attributes. Iteratively adds "
+      +"attributes with the highest correlation with the class as long "
+      +"as there is not already an attribute in the subset that has a "
+      +"higher correlation with the attribute in question";
+  }
+
+  /**
+   * Include locally predictive attributes
+   *
+   * @param b true or false
+   */
+  public void setLocallyPredictive (boolean b) {
+    m_locallyPredictive = b;
+  }
+
+
+  /**
+   * Return true if including locally predictive attributes
+   *
+   * @return true if locally predictive attributes are to be used
+   */
+  public boolean getLocallyPredictive () {
+    return  m_locallyPredictive;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingSeparateTipText() {
+    return "Treat missing as a separate value. Otherwise, counts for missing "
+      +"values are distributed across other values in proportion to their "
+      +"frequency.";
+  }
+
+  /**
+   * Treat missing as a separate value
+   *
+   * @param b true or false
+   */
+  public void setMissingSeparate (boolean b) {
+    m_missingSeparate = b;
+  }
+
+
+  /**
+   * Return true is missing is treated as a separate value
+   *
+   * @return true if missing is to be treated as a separate value
+   */
+  public boolean getMissingSeparate () {
+    return  m_missingSeparate;
+  }
+
+
+  /**
+   * Gets the current settings of CfsSubsetEval
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[2];
+    int current = 0;
+
+    if (getMissingSeparate()) {
+      options[current++] = "-M";
+    }
+
+    if (!getLocallyPredictive()) {
+      options[current++] = "-L";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * CFS also discretises attributes (if necessary) and initializes
+   * the correlation matrix.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = new Instances(data);
+    m_trainInstances.deleteWithMissingClass();
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+    m_isNumeric = m_trainInstances.attribute(m_classIndex).isNumeric();
+
+    if (!m_isNumeric) {
+      m_disTransform = new Discretize();
+      m_disTransform.setUseBetterEncoding(true);
+      m_disTransform.setInputFormat(m_trainInstances);
+      m_trainInstances = Filter.useFilter(m_trainInstances, m_disTransform);
+    }
+
+    m_std_devs = new double[m_numAttribs];
+    m_corr_matrix = new float [m_numAttribs][];
+    for (int i = 0; i < m_numAttribs; i++) {
+      m_corr_matrix[i] = new float [i+1];
+    }
+
+    for (int i = 0; i < m_corr_matrix.length; i++) {
+      m_corr_matrix[i][i] = 1.0f;
+      m_std_devs[i] = 1.0;
+    }
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      for (int j = 0; j < m_corr_matrix[i].length - 1; j++) {
+        m_corr_matrix[i][j] = -999;
+      }
+    }
+  }
+
+
+  /**
+   * evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the merit
+   * @throws Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset (BitSet subset)
+    throws Exception {
+    double num = 0.0;
+    double denom = 0.0;
+    float corr;
+    int larger, smaller;
+    // do numerator
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (i != m_classIndex) {
+        if (subset.get(i)) {
+          if (i > m_classIndex) {
+            larger = i; smaller = m_classIndex;
+          } else {
+            smaller = i; larger = m_classIndex;
+          }
+          /*      int larger = (i > m_classIndex ? i : m_classIndex);
+                  int smaller = (i > m_classIndex ? m_classIndex : i); */
+          if (m_corr_matrix[larger][smaller] == -999) {
+            corr = correlate(i, m_classIndex);
+            m_corr_matrix[larger][smaller] = corr;
+            num += (m_std_devs[i] * corr);
+          }
+          else {
+            num += (m_std_devs[i] * m_corr_matrix[larger][smaller]);
+          }
+        }
+      }
+    }
+
+    // do denominator
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (i != m_classIndex) {
+        if (subset.get(i)) {
+          denom += (1.0 * m_std_devs[i] * m_std_devs[i]);
+
+          for (int j = 0; j < m_corr_matrix[i].length - 1; j++) {
+            if (subset.get(j)) {
+              if (m_corr_matrix[i][j] == -999) {
+                corr = correlate(i, j);
+                m_corr_matrix[i][j] = corr;
+                denom += (2.0 * m_std_devs[i] * m_std_devs[j] * corr);
+              }
+              else {
+                denom += (2.0 * m_std_devs[i] * m_std_devs[j] * m_corr_matrix[i][j]);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (denom < 0.0) {
+      denom *= -1.0;
+    }
+
+    if (denom == 0.0) {
+      return  (0.0);
+    }
+
+    double merit = (num/Math.sqrt(denom));
+
+    if (merit < 0.0) {
+      merit *= -1.0;
+    }
+
+    return  merit;
+  }
+
+
+  private float correlate (int att1, int att2) {
+    if (!m_isNumeric) {
+      return  (float) symmUncertCorr(att1, att2);
+    }
+
+    boolean att1_is_num = (m_trainInstances.attribute(att1).isNumeric());
+    boolean att2_is_num = (m_trainInstances.attribute(att2).isNumeric());
+
+    if (att1_is_num && att2_is_num) {
+      return  (float) num_num(att1, att2);
+    }
+    else {if (att2_is_num) {
+      return  (float) num_nom2(att1, att2);
+    }
+    else {if (att1_is_num) {
+      return  (float) num_nom2(att2, att1);
+    }
+    }
+    }
+
+    return (float) nom_nom(att1, att2);
+  }
+
+
+  private double symmUncertCorr (int att1, int att2) {
+    int i, j, k, ii, jj;
+    int ni, nj;
+    double sum = 0.0;
+    double sumi[], sumj[];
+    double counts[][];
+    Instance inst;
+    double corr_measure;
+    boolean flag = false;
+    double temp = 0.0;
+
+    if (att1 == m_classIndex || att2 == m_classIndex) {
+      flag = true;
+    }
+
+    ni = m_trainInstances.attribute(att1).numValues() + 1;
+    nj = m_trainInstances.attribute(att2).numValues() + 1;
+    counts = new double[ni][nj];
+    sumi = new double[ni];
+    sumj = new double[nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumj[j] = 0.0;
+        counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(att1)) {
+        ii = ni - 1;
+      }
+      else {
+        ii = (int)inst.value(att1);
+      }
+
+      if (inst.isMissing(att2)) {
+        jj = nj - 1;
+      }
+      else {
+        jj = (int)inst.value(att2);
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumi[i] += counts[i][j];
+        sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+        sumj[j] += counts[i][j];
+      }
+    }
+
+    // distribute missing counts
+    if (!m_missingSeparate && 
+        (sumi[ni-1] < m_numInstances) && 
+        (sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+        System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = 
+        (sumi[ni - 1] + sumj[nj - 1] - counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) {
+        for (j = 0; j < nj - 1; j++) {
+          if (counts[ni - 1][j] > 0.0) {
+            for (i = 0; i < ni - 1; i++) {
+              temp = ((i_copy[i]/(sum - i_copy[ni - 1]))*counts[ni - 1][j]);
+              counts[i][j] += temp;
+              sumi[i] += temp;
+            }
+
+            counts[ni - 1][j] = 0.0;
+          }
+        }
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+        for (i = 0; i < ni - 1; i++) {
+          if (counts[i][nj - 1] > 0.0) {
+            for (j = 0; j < nj - 1; j++) {
+              temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+              counts[i][j] += temp;
+              sumj[j] += temp;
+            }
+
+            counts[i][nj - 1] = 0.0;
+          }
+        }
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0 && total_missing != sum) {
+        for (i = 0; i < ni - 1; i++) {
+          for (j = 0; j < nj - 1; j++) {
+            temp = (counts_copy[i][j]/(sum - total_missing)) * 
+              counts_copy[ni - 1][nj - 1];
+            
+            counts[i][j] += temp;
+            sumi[i] += temp;
+            sumj[j] += temp;
+          }
+        }
+
+        counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    corr_measure = ContingencyTables.symmetricalUncertainty(counts);
+
+    if (Utils.eq(corr_measure, 0.0)) {
+      if (flag == true) {
+        return  (0.0);
+      }
+      else {
+        return  (1.0);
+      }
+    }
+    else {
+      return  (corr_measure);
+    }
+  }
+
+
+  private double num_num (int att1, int att2) {
+    int i;
+    Instance inst;
+    double r, diff1, diff2, num = 0.0, sx = 0.0, sy = 0.0;
+    double mx = m_trainInstances.meanOrMode(m_trainInstances.attribute(att1));
+    double my = m_trainInstances.meanOrMode(m_trainInstances.attribute(att2));
+
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+      diff1 = (inst.isMissing(att1))? 0.0 : (inst.value(att1) - mx);
+      diff2 = (inst.isMissing(att2))? 0.0 : (inst.value(att2) - my);
+      num += (diff1*diff2);
+      sx += (diff1*diff1);
+      sy += (diff2*diff2);
+    }
+
+    if (sx != 0.0) {
+      if (m_std_devs[att1] == 1.0) {
+        m_std_devs[att1] = Math.sqrt((sx/m_numInstances));
+      }
+    }
+
+    if (sy != 0.0) {
+      if (m_std_devs[att2] == 1.0) {
+        m_std_devs[att2] = Math.sqrt((sy/m_numInstances));
+      }
+    }
+
+    if ((sx*sy) > 0.0) {
+      r = (num/(Math.sqrt(sx*sy)));
+      return  ((r < 0.0)? -r : r);
+    }
+    else {
+      if (att1 != m_classIndex && att2 != m_classIndex) {
+        return  1.0;
+      }
+      else {
+        return  0.0;
+      }
+    }
+  }
+
+
+  private double num_nom2 (int att1, int att2) {
+    int i, ii, k;
+    double temp;
+    Instance inst;
+    int mx = (int)m_trainInstances.
+      meanOrMode(m_trainInstances.attribute(att1));
+    double my = m_trainInstances.
+      meanOrMode(m_trainInstances.attribute(att2));
+    double stdv_num = 0.0;
+    double diff1, diff2;
+    double r = 0.0, rr;
+    int nx = (!m_missingSeparate) 
+      ? m_trainInstances.attribute(att1).numValues() 
+      : m_trainInstances.attribute(att1).numValues() + 1;
+
+    double[] prior_nom = new double[nx];
+    double[] stdvs_nom = new double[nx];
+    double[] covs = new double[nx];
+
+    for (i = 0; i < nx; i++) {
+      stdvs_nom[i] = covs[i] = prior_nom[i] = 0.0;
+    }
+
+    // calculate frequencies (and means) of the values of the nominal 
+    // attribute
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(att1)) {
+        if (!m_missingSeparate) {
+          ii = mx;
+        }
+        else {
+          ii = nx - 1;
+        }
+      }
+      else {
+        ii = (int)inst.value(att1);
+      }
+
+      // increment freq for nominal
+      prior_nom[ii]++;
+    }
+
+    for (k = 0; k < m_numInstances; k++) {
+      inst = m_trainInstances.instance(k);
+      // std dev of numeric attribute
+      diff2 = (inst.isMissing(att2))? 0.0 : (inst.value(att2) - my);
+      stdv_num += (diff2*diff2);
+
+      // 
+      for (i = 0; i < nx; i++) {
+        if (inst.isMissing(att1)) {
+          if (!m_missingSeparate) {
+            temp = (i == mx)? 1.0 : 0.0;
+          }
+          else {
+            temp = (i == (nx - 1))? 1.0 : 0.0;
+          }
+        }
+        else {
+          temp = (i == inst.value(att1))? 1.0 : 0.0;
+        }
+
+        diff1 = (temp - (prior_nom[i]/m_numInstances));
+        stdvs_nom[i] += (diff1*diff1);
+        covs[i] += (diff1*diff2);
+      }
+    }
+
+    // calculate weighted correlation
+    for (i = 0, temp = 0.0; i < nx; i++) {
+      // calculate the weighted variance of the nominal
+      temp += ((prior_nom[i]/m_numInstances)*(stdvs_nom[i]/m_numInstances));
+
+      if ((stdvs_nom[i]*stdv_num) > 0.0) {
+        //System.out.println("Stdv :"+stdvs_nom[i]);
+        rr = (covs[i]/(Math.sqrt(stdvs_nom[i]*stdv_num)));
+
+        if (rr < 0.0) {
+          rr = -rr;
+        }
+
+        r += ((prior_nom[i]/m_numInstances)*rr);
+      }
+      /* if there is zero variance for the numeric att at a specific 
+         level of the catergorical att then if neither is the class then 
+         make this correlation at this level maximally bad i.e. 1.0. 
+         If either is the class then maximally bad correlation is 0.0 */
+      else {if (att1 != m_classIndex && att2 != m_classIndex) {
+        r += ((prior_nom[i]/m_numInstances)*1.0);
+      }
+      }
+    }
+
+    // set the standard deviations for these attributes if necessary
+    // if ((att1 != classIndex) && (att2 != classIndex)) // =============
+    if (temp != 0.0) {
+      if (m_std_devs[att1] == 1.0) {
+        m_std_devs[att1] = Math.sqrt(temp);
+      }
+    }
+
+    if (stdv_num != 0.0) {
+      if (m_std_devs[att2] == 1.0) {
+        m_std_devs[att2] = Math.sqrt((stdv_num/m_numInstances));
+      }
+    }
+
+    if (r == 0.0) {
+      if (att1 != m_classIndex && att2 != m_classIndex) {
+        r = 1.0;
+      }
+    }
+
+    return  r;
+  }
+
+
+  private double nom_nom (int att1, int att2) {
+    int i, j, ii, jj, z;
+    double temp1, temp2;
+    Instance inst;
+    int mx = (int)m_trainInstances.
+      meanOrMode(m_trainInstances.attribute(att1));
+    int my = (int)m_trainInstances.
+      meanOrMode(m_trainInstances.attribute(att2));
+    double diff1, diff2;
+    double r = 0.0, rr;
+    int nx = (!m_missingSeparate) 
+      ? m_trainInstances.attribute(att1).numValues() 
+      : m_trainInstances.attribute(att1).numValues() + 1;
+
+    int ny = (!m_missingSeparate)
+      ? m_trainInstances.attribute(att2).numValues() 
+      : m_trainInstances.attribute(att2).numValues() + 1;
+
+    double[][] prior_nom = new double[nx][ny];
+    double[] sumx = new double[nx];
+    double[] sumy = new double[ny];
+    double[] stdvsx = new double[nx];
+    double[] stdvsy = new double[ny];
+    double[][] covs = new double[nx][ny];
+
+    for (i = 0; i < nx; i++) {
+      sumx[i] = stdvsx[i] = 0.0;
+    }
+
+    for (j = 0; j < ny; j++) {
+      sumy[j] = stdvsy[j] = 0.0;
+    }
+
+    for (i = 0; i < nx; i++) {
+      for (j = 0; j < ny; j++) {
+        covs[i][j] = prior_nom[i][j] = 0.0;
+      }
+    }
+
+    // calculate frequencies (and means) of the values of the nominal 
+    // attribute
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(att1)) {
+        if (!m_missingSeparate) {
+          ii = mx;
+        }
+        else {
+          ii = nx - 1;
+        }
+      }
+      else {
+        ii = (int)inst.value(att1);
+      }
+
+      if (inst.isMissing(att2)) {
+        if (!m_missingSeparate) {
+          jj = my;
+        }
+        else {
+          jj = ny - 1;
+        }
+      }
+      else {
+        jj = (int)inst.value(att2);
+      }
+
+      // increment freq for nominal
+      prior_nom[ii][jj]++;
+      sumx[ii]++;
+      sumy[jj]++;
+    }
+
+    for (z = 0; z < m_numInstances; z++) {
+      inst = m_trainInstances.instance(z);
+
+      for (j = 0; j < ny; j++) {
+        if (inst.isMissing(att2)) {
+          if (!m_missingSeparate) {
+            temp2 = (j == my)? 1.0 : 0.0;
+          }
+          else {
+            temp2 = (j == (ny - 1))? 1.0 : 0.0;
+          }
+        }
+        else {
+          temp2 = (j == inst.value(att2))? 1.0 : 0.0;
+        }
+
+        diff2 = (temp2 - (sumy[j]/m_numInstances));
+        stdvsy[j] += (diff2*diff2);
+      }
+
+      // 
+      for (i = 0; i < nx; i++) {
+        if (inst.isMissing(att1)) {
+          if (!m_missingSeparate) {
+            temp1 = (i == mx)? 1.0 : 0.0;
+          }
+          else {
+            temp1 = (i == (nx - 1))? 1.0 : 0.0;
+          }
+        }
+        else {
+          temp1 = (i == inst.value(att1))? 1.0 : 0.0;
+        }
+
+        diff1 = (temp1 - (sumx[i]/m_numInstances));
+        stdvsx[i] += (diff1*diff1);
+
+        for (j = 0; j < ny; j++) {
+          if (inst.isMissing(att2)) {
+            if (!m_missingSeparate) {
+              temp2 = (j == my)? 1.0 : 0.0;
+            }
+            else {
+              temp2 = (j == (ny - 1))? 1.0 : 0.0;
+            }
+          }
+          else {
+            temp2 = (j == inst.value(att2))? 1.0 : 0.0;
+          }
+
+          diff2 = (temp2 - (sumy[j]/m_numInstances));
+          covs[i][j] += (diff1*diff2);
+        }
+      }
+    }
+
+    // calculate weighted correlation
+    for (i = 0; i < nx; i++) {
+      for (j = 0; j < ny; j++) {
+        if ((stdvsx[i]*stdvsy[j]) > 0.0) {
+          //System.out.println("Stdv :"+stdvs_nom[i]);
+          rr = (covs[i][j]/(Math.sqrt(stdvsx[i]*stdvsy[j])));
+
+          if (rr < 0.0) {
+            rr = -rr;
+          }
+
+          r += ((prior_nom[i][j]/m_numInstances)*rr);
+        }
+        // if there is zero variance for either of the categorical atts then if
+        // neither is the class then make this
+        // correlation at this level maximally bad i.e. 1.0. If either is 
+        // the class then maximally bad correlation is 0.0
+        else {if (att1 != m_classIndex && att2 != m_classIndex) {
+          r += ((prior_nom[i][j]/m_numInstances)*1.0);
+        }
+        }
+      }
+    }
+
+    // calculate weighted standard deviations for these attributes
+    // (if necessary)
+    for (i = 0, temp1 = 0.0; i < nx; i++) {
+      temp1 += ((sumx[i]/m_numInstances)*(stdvsx[i]/m_numInstances));
+    }
+
+    if (temp1 != 0.0) {
+      if (m_std_devs[att1] == 1.0) {
+        m_std_devs[att1] = Math.sqrt(temp1);
+      }
+    }
+
+    for (j = 0, temp2 = 0.0; j < ny; j++) {
+      temp2 += ((sumy[j]/m_numInstances)*(stdvsy[j]/m_numInstances));
+    }
+
+    if (temp2 != 0.0) {
+      if (m_std_devs[att2] == 1.0) {
+        m_std_devs[att2] = Math.sqrt(temp2);
+      }
+    }
+
+    if (r == 0.0) {
+      if (att1 != m_classIndex && att2 != m_classIndex) {
+        r = 1.0;
+      }
+    }
+
+    return  r;
+  }
+
+
+  /**
+   * returns a string describing CFS
+   *
+   * @return the description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("CFS subset evaluator has not been built yet\n");
+    }
+    else {
+      text.append("\tCFS Subset Evaluator\n");
+
+      if (m_missingSeparate) {
+        text.append("\tTreating missing values as a separate value\n");
+      }
+
+      if (m_locallyPredictive) {
+        text.append("\tIncluding locally predictive attributes\n");
+      }
+    }
+
+    return  text.toString();
+  }
+
+
+  private void addLocallyPredictive (BitSet best_group) {
+    int i, j;
+    boolean done = false;
+    boolean ok = true;
+    double temp_best = -1.0;
+    float corr;
+    j = 0;
+    BitSet temp_group = (BitSet)best_group.clone();
+    int larger, smaller;
+
+    while (!done) {
+      temp_best = -1.0;
+
+      // find best not already in group
+      for (i = 0; i < m_numAttribs; i++) {
+        if (i > m_classIndex) {
+          larger = i; smaller = m_classIndex;
+        } else {
+          smaller = i; larger = m_classIndex;
+        }
+        /*      int larger = (i > m_classIndex ? i : m_classIndex);
+                int smaller = (i > m_classIndex ? m_classIndex : i); */
+        if ((!temp_group.get(i)) && (i != m_classIndex)) {
+          if (m_corr_matrix[larger][smaller] == -999) {
+            corr = correlate(i, m_classIndex);
+            m_corr_matrix[larger][smaller] = corr;
+          }
+
+          if (m_corr_matrix[larger][smaller]  > temp_best) {
+            temp_best = m_corr_matrix[larger][smaller];
+            j = i;
+          }
+        }
+      }
+
+      if (temp_best == -1.0) {
+        done = true;
+      }
+      else {
+        ok = true;
+        temp_group.set(j);
+
+        // check the best against correlations with others already
+        // in group 
+        for (i = 0; i < m_numAttribs; i++) {
+          if (i > j) {
+            larger = i; smaller = j;
+          } else {
+            larger = j; smaller = i;
+          }
+          /*  int larger = (i > j ? i : j);
+              int smaller = (i > j ? j : i); */
+          if (best_group.get(i)) {
+            if (m_corr_matrix[larger][smaller] == -999) {
+              corr = correlate(i, j);
+              m_corr_matrix[larger][smaller] = corr;
+            }
+
+            if (m_corr_matrix[larger][smaller] > temp_best - m_c_Threshold) {
+              ok = false;
+              break;
+            }
+          }
+        }
+
+        // if ok then add to best_group
+        if (ok) {
+          best_group.set(j);
+        }
+      }
+    }
+  }
+
+
+  /**
+   * Calls locallyPredictive in order to include locally predictive
+   * attributes (if requested).
+   *
+   * @param attributeSet the set of attributes found by the search
+   * @return a possibly ranked list of postprocessed attributes
+   * @throws Exception if postprocessing fails for some reason
+   */
+  public int[] postProcess (int[] attributeSet)
+    throws Exception {
+    int j = 0;
+
+    if (!m_locallyPredictive) {
+      //      m_trainInstances = new Instances(m_trainInstances,0);
+      return  attributeSet;
+    }
+
+    BitSet bestGroup = new BitSet(m_numAttribs);
+
+    for (int i = 0; i < attributeSet.length; i++) {
+      bestGroup.set(attributeSet[i]);
+    }
+
+    addLocallyPredictive(bestGroup);
+
+    // count how many are set
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (bestGroup.get(i)) {
+        j++;
+      }
+    }
+
+    int[] newSet = new int[j];
+    j = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (bestGroup.get(i)) {
+        newSet[j++] = i;
+      }
+    }
+
+    //    m_trainInstances = new Instances(m_trainInstances,0);
+    return  newSet;
+  }
+
+
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_missingSeparate = false;
+    m_locallyPredictive = true;
+    m_c_Threshold = 0.0;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6132 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new CfsSubsetEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/CheckAttributeSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/CheckAttributeSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/CheckAttributeSelection.java	(revision 29)
@@ -0,0 +1,1641 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckAttributeSelection.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Attribute;
+import weka.core.CheckScheme;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.SerializedObject;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with 
+ * attribute selection schemes. If you implement an attribute selection using 
+ * the WEKA.libraries, you should run the checks on it to ensure robustness 
+ * and correct operation. Passing all the tests of this object does not mean
+ * bugs in the attribute selection don't exist, but this will help find some
+ * common ones. <p/>
+ * 
+ * Typical usage: <p/>
+ * <code>java weka.attributeSelection.CheckAttributeSelection -W ASscheme_name 
+ * -- ASscheme_options </code><p/>
+ * 
+ * CheckAttributeSelection reports on the following:
+ * <ul>
+ *    <li> Scheme abilities 
+ *      <ul>
+ *         <li> Possible command line options to the scheme </li>
+ *         <li> Whether the scheme can predict nominal, numeric, string, 
+ *              date or relational class attributes. </li>
+ *         <li> Whether the scheme can handle numeric predictor attributes </li>
+ *         <li> Whether the scheme can handle nominal predictor attributes </li>
+ *         <li> Whether the scheme can handle string predictor attributes </li>
+ *         <li> Whether the scheme can handle date predictor attributes </li>
+ *         <li> Whether the scheme can handle relational predictor attributes </li>
+ *         <li> Whether the scheme can handle multi-instance data </li>
+ *         <li> Whether the scheme can handle missing predictor values </li>
+ *         <li> Whether the scheme can handle missing class values </li>
+ *         <li> Whether a nominal scheme only handles 2 class problems </li>
+ *         <li> Whether the scheme can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning 
+ *      <ul>
+ *         <li> Correct initialisation during search (i.e. no result
+ *              changes when search is performed repeatedly) </li>
+ *         <li> Whether the scheme alters the data pased to it 
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases 
+ *      <ul>
+ *         <li> building scheme with zero instances </li>
+ *         <li> all but one predictor attribute values missing </li>
+ *         <li> all predictor attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckAttributeSelection with the debug option set will output the 
+ * training dataset for any failed tests.<p/>
+ *
+ * The <code>weka.attributeSelection.AbstractAttributeSelectionTest</code> 
+ * uses this class to test all the schemes. Any changes here, have to be 
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ * 
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 2).</pre>
+ * 
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 1).</pre>
+ * 
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 1).</pre>
+ * 
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 1).</pre>
+ * 
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 1).</pre>
+ * 
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 1).</pre>
+ * 
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ * 
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ * 
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ * 
+ * <pre> -eval name [options]
+ *  Full name and options of the evaluator analyzed.
+ *  eg: weka.attributeSelection.CfsSubsetEval</pre>
+ * 
+ * <pre> -search name [options]
+ *  Full name and options of the search method analyzed.
+ *  eg: weka.attributeSelection.Ranker</pre>
+ * 
+ * <pre> -test &lt;eval|search&gt;
+ *  The scheme to test, either the evaluator or the search method.
+ *  (Default: eval)</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.CfsSubsetEval:
+ * </pre>
+ * 
+ * <pre> -M
+ *  Treat missing values as a seperate value.</pre>
+ * 
+ * <pre> -L
+ *  Don't include locally predictive attributes.</pre>
+ * 
+ * <pre> 
+ * Options specific to search method weka.attributeSelection.Ranker:
+ * </pre>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.
+ *  Any starting attributes specified are
+ *  ignored during the ranking.</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Specify a theshold by which attributes
+ *  may be discarded from the ranking.</pre>
+ * 
+ * <pre> -N &lt;num to select&gt;
+ *  Specify number of attributes to select</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4783 $
+ * @see TestInstances
+ */
+public class CheckAttributeSelection 
+  extends CheckScheme {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+  
+  /*** The evaluator to be examined */
+  protected ASEvaluation m_Evaluator = new CfsSubsetEval();
+  
+  /*** The search method to be used */
+  protected ASSearch m_Search = new Ranker();
+  
+  /** whether to test the evaluator (default) or the search method */
+  protected boolean m_TestEvaluator = true;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    result.addElement(new Option(
+        "\tFull name and options of the evaluator analyzed.\n"
+        +"\teg: weka.attributeSelection.CfsSubsetEval",
+        "eval", 1, "-eval name [options]"));
+    
+    result.addElement(new Option(
+        "\tFull name and options of the search method analyzed.\n"
+        +"\teg: weka.attributeSelection.Ranker",
+        "search", 1, "-search name [options]"));
+    
+    result.addElement(new Option(
+        "\tThe scheme to test, either the evaluator or the search method.\n"
+        +"\t(Default: eval)",
+        "test", 1, "-test <eval|search>"));
+    
+    if ((m_Evaluator != null) && (m_Evaluator instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0, 
+          "\nOptions specific to evaluator "
+          + m_Evaluator.getClass().getName()
+          + ":"));
+      Enumeration enm = ((OptionHandler) m_Evaluator).listOptions();
+      while (enm.hasMoreElements())
+        result.addElement(enm.nextElement());
+    }
+    
+    if ((m_Search != null) && (m_Search instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0, 
+          "\nOptions specific to search method "
+          + m_Search.getClass().getName()
+          + ":"));
+      Enumeration enm = ((OptionHandler) m_Search).listOptions();
+      while (enm.hasMoreElements())
+        result.addElement(enm.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   * 
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 2).</pre>
+   * 
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 1).</pre>
+   * 
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 1).</pre>
+   * 
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 1).</pre>
+   * 
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 1).</pre>
+   * 
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 1).</pre>
+   * 
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   * 
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   * 
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   * 
+   * <pre> -eval name [options]
+   *  Full name and options of the evaluator analyzed.
+   *  eg: weka.attributeSelection.CfsSubsetEval</pre>
+   * 
+   * <pre> -search name [options]
+   *  Full name and options of the search method analyzed.
+   *  eg: weka.attributeSelection.Ranker</pre>
+   * 
+   * <pre> -test &lt;eval|search&gt;
+   *  The scheme to test, either the evaluator or the search method.
+   *  (Default: eval)</pre>
+   * 
+   * <pre> 
+   * Options specific to evaluator weka.attributeSelection.CfsSubsetEval:
+   * </pre>
+   * 
+   * <pre> -M
+   *  Treat missing values as a seperate value.</pre>
+   * 
+   * <pre> -L
+   *  Don't include locally predictive attributes.</pre>
+   * 
+   * <pre> 
+   * Options specific to search method weka.attributeSelection.Ranker:
+   * </pre>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.
+   *  Any starting attributes specified are
+   *  ignored during the ranking.</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Specify a theshold by which attributes
+   *  may be discarded from the ranking.</pre>
+   * 
+   * <pre> -N &lt;num to select&gt;
+   *  Specify number of attributes to select</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    String[]	tmpOptions;
+    
+    super.setOptions(options);
+    
+    tmpStr     = Utils.getOption("eval", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setEvaluator(
+	  (ASEvaluation) forName(
+	      "weka.attributeSelection", 
+	      ASEvaluation.class, 
+	      tmpStr, 
+	      tmpOptions));
+    }
+    
+    tmpStr     = Utils.getOption("search", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setSearch(
+	  (ASSearch) forName(
+	      "weka.attributeSelection", 
+	      ASSearch.class, 
+	      tmpStr, 
+	      tmpOptions));
+    }
+
+    tmpStr = Utils.getOption("test", options);
+    setTestEvaluator(!tmpStr.equalsIgnoreCase("search"));
+  }
+  
+  /**
+   * Gets the current settings of the CheckAttributeSelection.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    String[]	options;
+    int		i;
+    
+    result = new Vector();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-eval");
+    if (getEvaluator() instanceof OptionHandler)
+      result.add(
+	  getEvaluator().getClass().getName() 
+	  + " " 
+	  + Utils.joinOptions(((OptionHandler) getEvaluator()).getOptions()));
+    else
+      result.add(
+	  getEvaluator().getClass().getName());
+
+    result.add("-search");
+    if (getSearch() instanceof OptionHandler)
+      result.add(
+	  getSearch().getClass().getName() 
+	  + " " 
+	  + Utils.joinOptions(((OptionHandler) getSearch()).getOptions()));
+    else
+      result.add(
+	  getSearch().getClass().getName());
+    
+    result.add("-test");
+    if (getTestEvaluator())
+      result.add("eval");
+    else
+      result.add("search");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+    
+    if (getTestObject() == null) {
+      println("\n=== No scheme set ===");
+      return;
+    }
+    println("\n=== Check on scheme: "
+        + getTestObject().getClass().getName()
+        + " ===\n");
+    
+    // Start tests
+    m_ClasspathProblems = false;
+    println("--> Checking for interfaces");
+    canTakeOptions();
+    boolean weightedInstancesHandler = weightedInstancesHandler()[0];
+    boolean multiInstanceHandler = multiInstanceHandler()[0];
+    println("--> Scheme tests");
+    declaresSerialVersionUID();
+    testsPerClassType(Attribute.NOMINAL,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.NUMERIC,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.DATE,       weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.STRING,     weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.RELATIONAL, weightedInstancesHandler, multiInstanceHandler);
+  }
+  
+  /**
+   * Set the evaluator to test. 
+   *
+   * @param value	the evaluator to use.
+   */
+  public void setEvaluator(ASEvaluation value) {
+    m_Evaluator = value;
+  }
+  
+  /**
+   * Get the current evaluator
+   *
+   * @return 		the current evaluator
+   */
+  public ASEvaluation getEvaluator() {
+    return m_Evaluator;
+  }
+  
+  /**
+   * Set the search method to test. 
+   *
+   * @param value	the search method to use.
+   */
+  public void setSearch(ASSearch value) {
+    m_Search = value;
+  }
+  
+  /**
+   * Get the current search method
+   *
+   * @return 		the current search method
+   */
+  public ASSearch getSearch() {
+    return m_Search;
+  }
+
+  /**
+   * Sets whether the evaluator or the search method is being tested.
+   * 
+   * @param value	if true then the evaluator will be tested
+   */
+  public void setTestEvaluator(boolean value) {
+    m_TestEvaluator = value;
+  }
+  
+  /**
+   * Gets whether the evaluator is being tested or the search method.
+   * 
+   * @return		true if the evaluator is being tested
+   */
+  public boolean getTestEvaluator() {
+    return m_TestEvaluator;
+  }
+  
+  /**
+   * returns either the evaluator or the search method.
+   * 
+   * @return		the object to be tested
+   * @see		#m_TestEvaluator
+   */
+  protected Object getTestObject() {
+    if (getTestEvaluator())
+      return getEvaluator();
+    else
+      return getSearch();
+  }
+  
+  /**
+   * returns deep copies of the given object
+   * 
+   * @param obj		the object to copy
+   * @param num		the number of copies
+   * @return		the deep copies
+   * @throws Exception	if copying fails
+   */
+  protected Object[] makeCopies(Object obj, int num) throws Exception {
+    if (obj == null)
+      throw new Exception("No object set");
+
+    Object[] objs = new Object[num];
+    SerializedObject so = new SerializedObject(obj);
+    for(int i = 0; i < objs.length; i++) {
+      objs[i] = so.getObject();
+    }
+    
+    return objs;
+  }
+  
+  /**
+   * Performs a attribute selection with the given search and evaluation scheme 
+   * on the provided data. The generated AttributeSelection object is returned.
+   * 
+   * @param search	the search scheme to use
+   * @param eval	the evaluator to use
+   * @param data	the data to work on
+   * @return		the used attribute selection object
+   * @throws Exception	if the attribute selection fails
+   */
+  protected AttributeSelection search(ASSearch search, ASEvaluation eval, 
+      Instances data) throws Exception {
+    
+    AttributeSelection	result;
+    
+    result = new AttributeSelection();
+    result.setSeed(42);
+    result.setSearch(search);
+    result.setEvaluator(eval);
+    result.SelectAttributes(data);
+    
+    return result;
+  }
+  
+  /**
+   * Run a battery of tests for a given class attribute type
+   *
+   * @param classType true if the class attribute should be numeric
+   * @param weighted true if the scheme says it handles weights
+   * @param multiInstance true if the scheme handles multi-instance data
+   */
+  protected void testsPerClassType(int classType, 
+                                   boolean weighted,
+                                   boolean multiInstance) {
+    
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance, classType)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance, classType)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance, classType)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance, classType)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance, classType)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      
+      if (classType == Attribute.NOMINAL)
+        canHandleNClasses(PNom, PNum, PStr, PDat, PRel, multiInstance, 4);
+
+      if (!multiInstance) {
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 0);
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 1);
+      }
+      
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          true, false, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, true, false, 100);
+      
+      boolean handleMissingClass = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          false, true, 20)[0];
+      if (handleMissingClass)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, false, true, 100);
+      
+      correctSearchInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, classType,
+          handleMissingPredictors, handleMissingClass);
+    }
+  }
+  
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the scheme can take options
+   */
+  protected boolean[] canTakeOptions() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("options...");
+    if (getTestObject() instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler) getTestObject()).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n" 
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the scheme handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("weighted instances scheme...");
+    if (getTestObject() instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme handles multi-instance data.
+   * 
+   * @return true if the scheme handles multi-instance data
+   */
+  protected boolean[] multiInstanceHandler() {
+    boolean[] result = new boolean[2];
+    
+    print("multi-instance scheme...");
+    if (getTestObject() instanceof MultiInstanceCapabilitiesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * tests for a serialVersionUID. Fails in case the schemes don't declare
+   * a UID (both must!).
+   *
+   * @return index 0 is true if the scheme declares a UID
+   */
+  protected boolean[] declaresSerialVersionUID() {
+    boolean[] result = new boolean[2];
+    boolean eval;
+    boolean search;
+    
+    print("serialVersionUID...");
+    
+    eval   = !SerializationHelper.needsUID(m_Evaluator.getClass());
+    search = !SerializationHelper.needsUID(m_Search.getClass());
+    
+    result[0] = eval && search;
+    
+    if (result[0])
+      println("yes");
+    else
+      println("no");
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic prediction of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canPredict(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("basic predict");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("unary");
+    accepts.addElement("binary");
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("multi-instance");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether nominal schemes can handle more than two classes.
+   * If a scheme is only designed for two-class problems it should
+   * throw an appropriate exception for multi-class problems.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param numClasses the number of classes to test
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleNClasses(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int numClasses) {
+    
+    print("more than two class problems");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, Attribute.NOMINAL);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("number");
+    accepts.addElement("class");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        Attribute.NOMINAL,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle class attributes as Nth attribute.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class attribute (0-based, -1 means last attribute)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected boolean[] canHandleClassAsNthAttribute(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex) {
+    
+    if (classIndex == TestInstances.CLASS_IS_LAST)
+      print("class attribute as last attribute");
+    else
+      print("class attribute as " + (classIndex + 1) + ". attribute");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    int numTrain = getNumInstances(), numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        classType,
+                        classIndex,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("handle zero training instances");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(
+              nominalPredictor, numericPredictor, stringPredictor, 
+              datePredictor, relationalPredictor, 
+              multiInstance,
+              classType, 
+              missingLevel, predictorMissing, classMissing,
+              numTrain, numClasses, 
+              accepts);
+  }
+  
+  /**
+   * Checks whether the scheme correctly initialises models when 
+   * ASSearch.search is called. This test calls search with
+   * one training dataset. ASSearch is then called on a training set with 
+   * different structure, and then again with the original training set. 
+   * If the equals method of the ASEvaluation class returns false, this is 
+   * noted as incorrect search initialisation.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  protected boolean[] correctSearchInitialisation(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    boolean[] result = new boolean[2];
+    print("correct initialisation during search");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    Instances train1 = null;
+    Instances train2 = null;
+    ASSearch search = null;
+    ASEvaluation evaluation1A = null;
+    ASEvaluation evaluation1B = null;
+    ASEvaluation evaluation2 = null;
+    AttributeSelection attsel1A = null;
+    AttributeSelection attsel1B = null;
+    int stage = 0;
+    try {
+      
+      // Make two train sets with different numbers of attributes
+      train1 = makeTestDataset(42, numTrain, 
+                               nominalPredictor    ? getNumNominal()    : 0,
+                               numericPredictor    ? getNumNumeric()    : 0, 
+                               stringPredictor     ? getNumString()     : 0, 
+                               datePredictor       ? getNumDate()       : 0, 
+                               relationalPredictor ? getNumRelational() : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      train2 = makeTestDataset(84, numTrain, 
+                               nominalPredictor    ? getNumNominal() + 1 : 0,
+                               numericPredictor    ? getNumNumeric() + 1 : 0, 
+                               stringPredictor     ? getNumString()      : 0, 
+                               datePredictor       ? getNumDate()        : 0, 
+                               relationalPredictor ? getNumRelational()  : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, predictorMissing, classMissing);
+        addMissing(train2, missingLevel, predictorMissing, classMissing);
+      }
+      
+      search = ASSearch.makeCopies(getSearch(), 1)[0];
+      evaluation1A = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+      evaluation1B = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+      evaluation2 = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      stage = 0;
+      attsel1A = search(search, evaluation1A, train1);
+      
+      stage = 1;
+      search(search, evaluation2, train2);
+      
+      stage = 2;
+      attsel1B = search(search, evaluation1B, train1);
+      
+      stage = 3;
+      if (!attsel1A.toResultsString().equals(attsel1B.toResultsString())) {
+        if (m_Debug) {
+          println(
+              "\n=== Full report ===\n"
+              + "\nFirst search\n"
+              + attsel1A.toResultsString()
+              + "\n\n");
+          println(
+              "\nSecond search\n"
+              + attsel1B.toResultsString()
+              + "\n\n");
+        }
+        throw new Exception("Results differ between search calls");
+      }
+      println("yes");
+      result[0] = true;
+      
+      if (false && m_Debug) {
+        println(
+            "\n=== Full report ===\n"
+            + "\nFirst search\n"
+            + evaluation1A.toString()
+            + "\n\n");
+        println(
+            "\nSecond search\n"
+            + evaluation1B.toString()
+            + "\n\n");
+      }
+    } 
+    catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during  training");
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;	  
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing,
+      int missingLevel) {
+    
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (predictorMissing) {
+      print(" predictor");
+      if (classMissing)
+        print(" and");
+    }
+    if (classMissing)
+      print(" class");
+    print(" values");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    accepts.addElement("no attributes");
+    int numTrain = getNumInstances(), numClasses = 2;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle instance weights.
+   * This test compares the scheme performance on two datasets
+   * that are identical except for the training weights. If the 
+   * results change, then the scheme must be using the weights. It
+   * may be possible to get a false positive from this test if the 
+   * weight changes aren't significant enough to induce a change
+   * in scheme performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("scheme uses instance weights");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = 2*getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    ASSearch[] search = null;
+    ASEvaluation evaluationB = null;
+    ASEvaluation evaluationI = null;
+    AttributeSelection attselB = null;
+    AttributeSelection attselI = null;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0, 
+                              stringPredictor     ? getNumString()      : 0, 
+                              datePredictor       ? getNumDate()        : 0, 
+                              relationalPredictor ? getNumRelational()  : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      search = ASSearch.makeCopies(getSearch(), 2);
+      evaluationB = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+      evaluationI = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+      attselB = search(search[0], evaluationB, train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      
+      // Now modify instance weights and re-built/test
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      attselI = search(search[1], evaluationI, train);
+      if (attselB.toResultsString().equals(attselI.toResultsString())) {
+        //	println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println("\nboth methods\n");
+          println(evaluationB.toString());
+        } else {
+          print("Problem during training");
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1) 
+              + "    " + train.instance(i).weight());
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * training. If the scheme needs to modify the training
+   * data it should take a copy of the training data. Currently checks
+   * for changes to header structure, number of instances, order of
+   * instances, instance weights.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if we know the scheme can handle
+   * (at least) moderate missing predictor values
+   * @param classMissing true if we know the scheme can handle
+   * (at least) moderate missing class values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing) {
+    
+    print("scheme doesn't alter original datasets");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 20;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances trainCopy = null;
+    ASSearch search = null;
+    ASEvaluation evaluation = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0, 
+                              datePredictor       ? getNumDate()       : 0, 
+                              relationalPredictor ? getNumRelational() : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      search = ASSearch.makeCopies(getSearch(), 1)[0];
+      evaluation = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+      trainCopy = new Instances(train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      search(search, evaluation, trainCopy);
+      compareDatasets(train, trainCopy);
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during training");
+        println(": " + ex.getMessage() + "\n");
+        println("Here are the datasets:\n");
+        println("=== Train Dataset (original) ===\n"
+            + trainCopy.toString() + "\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    return runBasicTest(
+		nominalPredictor, 
+		numericPredictor,
+		stringPredictor,
+		datePredictor,
+		relationalPredictor,
+		multiInstance,
+		classType, 
+		TestInstances.CLASS_IS_LAST,
+		missingLevel,
+		predictorMissing,
+		classMissing,
+		numTrain,
+		numClasses,
+		accepts);
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the attribute index of the class
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    ASSearch search = null;
+    ASEvaluation evaluation = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0,
+                              datePredictor       ? getNumDate()       : 0,
+                              relationalPredictor ? getNumRelational() : 0,
+                              numClasses, 
+                              classType,
+                              classIndex,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      search = ASSearch.makeCopies(getSearch(), 1)[0];
+      evaluation = ASEvaluation.makeCopies(getEvaluator(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      search(search, evaluation, train);
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+	msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+	m_ClasspathProblems = true;
+      for (int i = 0; i < accepts.size(); i++) {
+	if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+	  acceptable = true;
+	}
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during training");
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here is the dataset:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Make a simple set of instances, which can later be modified
+   * for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      boolean multiInstance)
+    throws Exception {
+    
+    return makeTestDataset(
+		seed, 
+		numInstances,
+		numNominal,
+		numNumeric,
+		numString,
+		numDate, 
+		numRelational,
+		numClasses, 
+		classType,
+		TestInstances.CLASS_IS_LAST,
+		multiInstance);
+  }
+  
+  /**
+   * Make a simple set of instances with variable position of the class 
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class (0-based, -1 as last)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      int classIndex,
+                                      boolean multiInstance)
+  throws Exception {
+    
+    TestInstances dataset = new TestInstances();
+    
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal(numNominal);
+    dataset.setNumNumeric(numNumeric);
+    dataset.setNumString(numString);
+    dataset.setNumDate(numDate);
+    dataset.setNumRelational(numRelational);
+    dataset.setNumClasses(numClasses);
+    dataset.setClassType(classType);
+    dataset.setClassIndex(classIndex);
+    dataset.setNumClasses(numClasses);
+    dataset.setMultiInstance(multiInstance);
+    dataset.setWords(getWords());
+    dataset.setWordSeparators(getWordSeparators());
+    
+    return process(dataset.generate());
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param nominalPredictor true if nominal predictor attributes are present
+   * @param numericPredictor true if numeric predictor attributes are present
+   * @param stringPredictor true if string predictor attributes are present
+   * @param datePredictor true if date predictor attributes are present
+   * @param relationalPredictor true if relational predictor attributes are present
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(boolean nominalPredictor, 
+                                       boolean numericPredictor, 
+                                       boolean stringPredictor, 
+                                       boolean datePredictor, 
+                                       boolean relationalPredictor, 
+                                       boolean multiInstance,
+                                       int classType) {
+    
+    String str = "";
+
+    if (numericPredictor)
+      str += " numeric";
+    
+    if (nominalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " nominal";
+    }
+    
+    if (stringPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " string";
+    }
+    
+    if (datePredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " date";
+    }
+    
+    if (relationalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " relational";
+    }
+    
+    str += " predictors)";
+    
+    switch (classType) {
+      case Attribute.NUMERIC:
+        str = " (numeric class," + str;
+        break;
+      case Attribute.NOMINAL:
+        str = " (nominal class," + str;
+        break;
+      case Attribute.STRING:
+        str = " (string class," + str;
+        break;
+      case Attribute.DATE:
+        str = " (date class," + str;
+        break;
+      case Attribute.RELATIONAL:
+        str = " (relational class," + str;
+        break;
+    }
+    
+    print(str);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4783 $");
+  }
+  
+  /**
+   * Test method for this class
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    runCheck(new CheckAttributeSelection(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ChiSquaredAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ChiSquaredAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ChiSquaredAttributeEval.java	(revision 29)
@@ -0,0 +1,463 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ChiSquaredAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+import weka.filters.unsupervised.attribute.NumericToBinary;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * ChiSquaredAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by computing the value of the chi-squared statistic with respect to the class.<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ * <pre> -B
+ *  just binarize numeric attributes instead 
+ *  of properly discretizing them.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5447 $ 
+ * @see Discretize
+ * @see NumericToBinary
+ */
+public class ChiSquaredAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8316857822521717692L;
+
+  /** Treat missing values as a seperate value */
+  private boolean m_missing_merge;
+
+  /** Just binarize numeric attributes */
+  private boolean m_Binarize;
+
+  /** The chi-squared value for each attribute */
+  private double[] m_ChiSquareds;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "ChiSquaredAttributeEval :\n\nEvaluates the worth of an attribute "
+      +"by computing the value of the chi-squared statistic with respect to the class.\n";
+  }
+
+  /**
+   * Constructor
+   */
+  public ChiSquaredAttributeEval () {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * @return an enumeration of all the available options
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option("\ttreat missing values as a seperate " 
+                                    + "value.", "M", 0, "-M"));
+    newVector.addElement(new Option("\tjust binarize numeric attributes instead \n" 
+                                    +"\tof properly discretizing them.", "B", 0, 
+                                    "-B"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   * <pre> -B
+   *  just binarize numeric attributes instead 
+   *  of properly discretizing them.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+    setBinarizeNumericAttributes(Utils.getFlag('B', options));
+  }
+
+
+  /**
+   * Gets the current settings.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[2];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+    if (getBinarizeNumericAttributes()) {
+      options[current++] = "-B";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binarizeNumericAttributesTipText() {
+    return "Just binarize numeric attributes instead of properly discretizing them.";
+  }
+
+  /**
+   * Binarize numeric attributes.
+   *
+   * @param b true=binarize numeric attributes
+   */
+  public void setBinarizeNumericAttributes (boolean b) {
+    m_Binarize = b;
+  }
+
+
+  /**
+   * get whether numeric attributes are just being binarized.
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getBinarizeNumericAttributes () {
+    return  m_Binarize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+      +"across other values in proportion to their frequency. Otherwise, "
+      +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a chi-squared attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    int classIndex = data.classIndex();
+    int numInstances = data.numInstances();
+    
+    if (!m_Binarize) {
+      Discretize disTransform = new Discretize();
+      disTransform.setUseBetterEncoding(true);
+      disTransform.setInputFormat(data);
+      data = Filter.useFilter(data, disTransform);
+    } else {
+      NumericToBinary binTransform = new NumericToBinary();
+      binTransform.setInputFormat(data);
+      data = Filter.useFilter(data, binTransform);
+    }      
+    int numClasses = data.attribute(classIndex).numValues();
+
+    // Reserve space and initialize counters
+    double[][][] counts = new double[data.numAttributes()][][];
+    for (int k = 0; k < data.numAttributes(); k++) {
+      if (k != classIndex) {
+        int numValues = data.attribute(k).numValues();
+        counts[k] = new double[numValues + 1][numClasses + 1];
+      }
+    }
+
+    // Initialize counters
+    double[] temp = new double[numClasses + 1];
+    for (int k = 0; k < numInstances; k++) {
+      Instance inst = data.instance(k);
+      if (inst.classIsMissing()) {
+        temp[numClasses] += inst.weight();
+      } else {
+        temp[(int)inst.classValue()] += inst.weight();
+      }
+    }
+    for (int k = 0; k < counts.length; k++) {
+      if (k != classIndex) {
+        for (int i = 0; i < temp.length; i++) {
+          counts[k][0][i] = temp[i];
+        }
+      }
+    }
+
+    // Get counts
+    for (int k = 0; k < numInstances; k++) {
+      Instance inst = data.instance(k);
+      for (int i = 0; i < inst.numValues(); i++) {
+        if (inst.index(i) != classIndex) {
+          if (inst.isMissingSparse(i) || inst.classIsMissing()) {
+            if (!inst.isMissingSparse(i)) {
+              counts[inst.index(i)][(int)inst.valueSparse(i)][numClasses] += 
+                inst.weight();
+              counts[inst.index(i)][0][numClasses] -= inst.weight();
+            } else if (!inst.classIsMissing()) {
+              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
+                [(int)inst.classValue()] += inst.weight();
+              counts[inst.index(i)][0][(int)inst.classValue()] -= 
+                inst.weight();
+            } else {
+              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
+                [numClasses] += inst.weight();
+              counts[inst.index(i)][0][numClasses] -= inst.weight();
+            }
+          } else {
+            counts[inst.index(i)][(int)inst.valueSparse(i)]
+              [(int)inst.classValue()] += inst.weight();
+            counts[inst.index(i)][0][(int)inst.classValue()] -= inst.weight();
+          }
+        }
+      }
+    }
+
+    // distribute missing counts if required
+    if (m_missing_merge) {
+      
+      for (int k = 0; k < data.numAttributes(); k++) {
+        if (k != classIndex) {
+          int numValues = data.attribute(k).numValues();
+
+          // Compute marginals
+          double[] rowSums = new double[numValues];
+          double[] columnSums = new double[numClasses];
+          double sum = 0;
+          for (int i = 0; i < numValues; i++) {
+            for (int j = 0; j < numClasses; j++) {
+              rowSums[i] += counts[k][i][j];
+              columnSums[j] += counts[k][i][j];
+            }
+            sum += rowSums[i];
+          }
+
+          if (Utils.gr(sum, 0)) {
+            double[][] additions = new double[numValues][numClasses];
+
+            // Compute what needs to be added to each row
+            for (int i = 0; i < numValues; i++) {
+              for (int j = 0; j  < numClasses; j++) {
+                additions[i][j] = (rowSums[i] / sum) * counts[k][numValues][j];
+              }
+            }
+            
+            // Compute what needs to be added to each column
+            for (int i = 0; i < numClasses; i++) {
+              for (int j = 0; j  < numValues; j++) {
+                additions[j][i] += (columnSums[i] / sum) * 
+                  counts[k][j][numClasses];
+              }
+            }
+            
+            // Compute what needs to be added to each cell
+            for (int i = 0; i < numClasses; i++) {
+              for (int j = 0; j  < numValues; j++) {
+                additions[j][i] += (counts[k][j][i] / sum) * 
+                  counts[k][numValues][numClasses];
+              }
+            }
+            
+            // Make new contingency table
+            double[][] newTable = new double[numValues][numClasses];
+            for (int i = 0; i < numValues; i++) {
+              for (int j = 0; j < numClasses; j++) {
+                newTable[i][j] = counts[k][i][j] + additions[i][j];
+              }
+            }
+            counts[k] = newTable;
+          }
+        }
+      }
+    }
+
+    // Compute chi-squared values
+    m_ChiSquareds = new double[data.numAttributes()];
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != classIndex) {
+        m_ChiSquareds[i] = ContingencyTables.
+          chiVal(ContingencyTables.reduceMatrix(counts[i]), false); 
+      }
+    }
+  }
+
+  /**
+   * Reset options to their default values
+   */
+  protected void resetOptions () {
+    m_ChiSquareds = null;
+    m_missing_merge = true;
+    m_Binarize = false;
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring its
+   * chi-squared value.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the chi-squared value
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+
+    return m_ChiSquareds[attribute];
+  }
+
+  /**
+   * Describe the attribute evaluator
+   * @return a description of the attribute evaluator as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_ChiSquareds == null) {
+      text.append("Chi-squared attribute evaluator has not been built");
+    }
+    else {
+      text.append("\tChi-squared Ranking Filter");
+      if (!m_missing_merge) {
+        text.append("\n\tMissing values treated as seperate");
+      }
+      if (m_Binarize) {
+        text.append("\n\tNumeric attributes are just binarized");
+      }
+    }
+    
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new ChiSquaredAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierAttributeEval.java	(revision 29)
@@ -0,0 +1,468 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierAttributeEval.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.rules.OneR;
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * ClassifierAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by using a user-specified classifier.<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Random number seed for cross validation.
+ *  (default = 1)</pre>
+ * 
+ * <pre> -F &lt;folds&gt;
+ *  Number of folds for cross validation.
+ *  (default = 10)</pre>
+ * 
+ * <pre> -D
+ *  Use training data for evaluation rather than cross validaton.</pre>
+ * 
+ * <pre> -B &lt;classname + options&gt;
+ *  Classifier to use.
+ *  (default = OneR)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ */
+public class ClassifierAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2442390690522602284L;
+
+  /** The training instances. */
+  protected Instances m_trainInstances;
+
+  /** Random number seed. */
+  protected int m_randomSeed;
+
+  /** Number of folds for cross validation. */
+  protected int m_folds;
+
+  /** Use training data to evaluate merit rather than x-val. */
+  protected boolean m_evalUsingTrainingData;
+
+  /** The classifier to use for evaluating the attribute. */
+  protected Classifier m_Classifier;
+
+  /**
+   * Constructor.
+   */
+  public ClassifierAttributeEval () {
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this attribute evaluator.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "ClassifierAttributeEval :\n\nEvaluates the worth of an attribute by "
+      +"using a user-specified classifier.\n";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+        "\tRandom number seed for cross validation.\n"
+        + "\t(default = 1)",
+        "S", 1, "-S <seed>"));
+
+    result.addElement(new Option(
+        "\tNumber of folds for cross validation.\n"
+        + "\t(default = 10)",
+        "F", 1, "-F <folds>"));
+
+    result.addElement(new Option(
+        "\tUse training data for evaluation rather than cross validaton.",
+        "D", 0, "-D"));
+
+    result.addElement(new Option(
+        "\tClassifier to use.\n"
+        + "\t(default = OneR)",
+        "B", 1, "-B <classname + options>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Random number seed for cross validation.
+   *  (default = 1)</pre>
+   * 
+   * <pre> -F &lt;folds&gt;
+   *  Number of folds for cross validation.
+   *  (default = 10)</pre>
+   * 
+   * <pre> -D
+   *  Use training data for evaluation rather than cross validaton.</pre>
+   * 
+   * <pre> -B &lt;classname + options&gt;
+   *  Classifier to use.
+   *  (default = OneR)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String [] options) throws Exception {
+    String 	tmpStr;
+    String[]	tmpOptions;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setFolds(Integer.parseInt(tmpStr));
+
+    tmpStr = Utils.getOption('B', options);
+    if (tmpStr.length() != 0) {
+      tmpOptions    = Utils.splitOptions(tmpStr);
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setClassifier((Classifier) Utils.forName(Classifier.class, tmpStr, tmpOptions));
+    }
+    
+    setEvalUsingTrainingData(Utils.getFlag('D', options));
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * returns the current setup.
+   * 
+   * @return the options of the current setup
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    if (getEvalUsingTrainingData())
+      result.add("-D");
+    
+    result.add("-S");
+    result.add("" + getSeed());
+    
+    result.add("-F");
+    result.add("" + getFolds());
+    
+    result.add("-B");
+    result.add(
+	new String(
+	    m_Classifier.getClass().getName() + " " 
+	    + Utils.joinOptions(((OptionHandler)m_Classifier).getOptions())).trim());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Set the random number seed for cross validation.
+   *
+   * @param value 	the seed to use
+   */
+  public void setSeed(int value) {
+    m_randomSeed = value;
+  }
+
+  /**
+   * Get the random number seed.
+   *
+   * @return 		an <code>int</code> value
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text.
+   *
+   * @return 		a string describing this option
+   */
+  public String seedTipText() {
+    return "Set the seed for use in cross validation.";
+  }
+
+  /**
+   * Set the number of folds to use for cross validation.
+   *
+   * @param value 	the number of folds
+   */
+  public void setFolds(int value) {
+    m_folds = value;
+    if (m_folds < 2)
+      m_folds = 2;
+  }
+   
+  /**
+   * Get the number of folds used for cross validation.
+   *
+   * @return 		the number of folds
+   */
+  public int getFolds() {
+    return m_folds;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text.
+   *
+   * @return 		a string describing this option
+   */
+  public String foldsTipText() {
+    return "Set the number of folds for cross validation.";
+  }
+
+  /**
+   * Use the training data to evaluate attributes rather than cross validation.
+   *
+   * @param value 	true if training data is to be used for evaluation
+   */
+  public void setEvalUsingTrainingData(boolean value) {
+    m_evalUsingTrainingData = value;
+  }
+
+  /**
+   * Returns true if the training data is to be used for evaluation.
+   *
+   * @return 		true if training data is to be used for evaluation
+   */
+  public boolean getEvalUsingTrainingData() {
+    return m_evalUsingTrainingData;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text.
+   *
+   * @return 		a string describing this option
+   */
+  public String evalUsingTrainingDataTipText() {
+    return "Use the training data to evaluate attributes rather than "
+      + "cross validation.";
+  }
+
+  /**
+   * Set the classifier to use for evaluating the attribute.
+   *
+   * @param value	the classifier to use
+   */
+  public void setClassifier(Classifier value) {
+    m_Classifier = value;
+  }
+
+  /**
+   * Returns the classifier to use for evaluating the attribute.
+   *
+   * @return 		the classifier in use
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text.
+   *
+   * @return 		a string describing this option
+   */
+  public String classifierTipText() {
+    return "The classifier to use for evaluating the attribute.";
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    
+    if (m_Classifier != null) {
+      result = m_Classifier.getCapabilities();
+      result.setOwner(this);
+    }
+    else {
+      result = super.getCapabilities();
+      result.disableAll();
+    }
+    
+    return result;
+  }
+
+  /**
+   * Initializes a ClassifierAttribute attribute evaluator.
+   *
+   * @param data 	set of instances serving as training data 
+   * @throws Exception 	if the evaluator has not been generated successfully
+   */
+  public void buildEvaluator (Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+  }
+
+
+  /**
+   * Resets to defaults.
+   */
+  protected void resetOptions () {
+    m_trainInstances        = null;
+    m_randomSeed            = 1;
+    m_folds                 = 10;
+    m_evalUsingTrainingData = false;
+    m_Classifier            = new OneR();
+  }
+
+
+  /**
+   * Evaluates an individual attribute by measuring the amount
+   * of information gained about the class given the attribute.
+   *
+   * @param attribute 	the index of the attribute to be evaluated
+   * @return		the evaluation
+   * @throws Exception 	if the attribute could not be evaluated
+   */
+  public double evaluateAttribute(int attribute) throws Exception {
+    int[] 	featArray; 
+    double 	errorRate;
+    Evaluation 	eval;
+    Remove 	delTransform;
+    Instances 	train;
+    Classifier 	cls;
+
+    // create tmp dataset
+    featArray    = new int[2]; // feat + class
+    delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    train        = new Instances(m_trainInstances);
+    featArray[0] = attribute;
+    featArray[1] = train.classIndex();
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(train);
+    train = Filter.useFilter(train, delTransform);
+    
+    // evaluate classifier
+    eval = new Evaluation(train);
+    cls  = AbstractClassifier.makeCopy(m_Classifier);
+    if (m_evalUsingTrainingData) {
+      cls.buildClassifier(train);
+      eval.evaluateModel(cls, train);
+    }
+    else {
+      eval.crossValidateModel(cls, train, m_folds, new Random(m_randomSeed));
+    }
+    errorRate = eval.errorRate();
+    
+    return (1 - errorRate)*100.0;
+  }
+
+  /**
+   * Return a description of the evaluator.
+   * 
+   * @return 		description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tClassifier feature evaluator has not been built yet");
+    }
+    else {
+      text.append("\tClassifier feature evaluator.\n\n");
+      text.append("\tUsing ");
+      if (m_evalUsingTrainingData)
+        text.append("training data for evaluation of attributes.\n");
+      else
+        text.append(getFolds()+ " fold cross validation for evaluating attributes.\n");
+      text.append("\tClassifier in use: " + m_Classifier.getClass().getName() + " " + Utils.joinOptions(((OptionHandler)m_Classifier).getOptions()));
+    }
+    text.append("\n");
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args 	the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new ClassifierAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierSubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierSubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ClassifierSubsetEval.java	(revision 29)
@@ -0,0 +1,705 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierSubsetEval.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.io.File;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Classifier subset evaluator:<br/>
+ * <br/>
+ * Evaluates attribute subsets on training data or a seperate hold out testing set. Uses a classifier to estimate the 'merit' of a set of attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;classifier&gt;
+ *  class name of the classifier to use for accuracy estimation.
+ *  Place any classifier options LAST on the command line
+ *  following a "--". eg.:
+ *   -B weka.classifiers.bayes.NaiveBayes ... -- -K
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> -T
+ *  Use the training data to estimate accuracy.</pre>
+ * 
+ * <pre> -H &lt;filename&gt;
+ *  Name of the hold out/test set to 
+ *  estimate accuracy on.</pre>
+ * 
+ * <pre> 
+ * Options specific to scheme weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class ClassifierSubsetEval 
+  extends HoldOutSubsetEvaluator
+  implements OptionHandler, ErrorBasedMeritEvaluator {
+  
+  /** for serialization */
+  static final long serialVersionUID = 7532217899385278710L;
+
+  /** training instances */
+  private Instances m_trainingInstances;
+
+  /** class index */
+  private int m_classIndex;
+
+  /** number of attributes in the training data */
+  private int m_numAttribs;
+  
+  /** number of training instances */
+  private int m_numInstances;
+
+  /** holds the classifier to use for error estimates */
+  private Classifier m_Classifier = new ZeroR();
+
+  /** holds the evaluation object to use for evaluating the classifier */
+  private Evaluation m_Evaluation;
+
+  /** the file that containts hold out/test instances */
+  private File m_holdOutFile = new File("Click to set hold out or "
+					+"test instances");
+
+  /** the instances to test on */
+  private Instances m_holdOutInstances = null;
+
+  /** evaluate on training data rather than seperate hold out/test set */
+  private boolean m_useTraining = true;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Classifier subset evaluator:\n\nEvaluates attribute subsets on training data or a seperate "
+      + "hold out testing set. Uses a classifier to estimate the 'merit' of a set of attributes.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(3);
+    
+    newVector.addElement(new Option(
+	"\tclass name of the classifier to use for accuracy estimation.\n"
+	+ "\tPlace any classifier options LAST on the command line\n"
+	+ "\tfollowing a \"--\". eg.:\n"
+	+ "\t\t-B weka.classifiers.bayes.NaiveBayes ... -- -K\n"
+	+ "\t(default: weka.classifiers.rules.ZeroR)", 
+	"B", 1, "-B <classifier>"));
+    
+    newVector.addElement(new Option(
+	"\tUse the training data to estimate"
+	+" accuracy.",
+	"T",0,"-T"));
+    
+    newVector.addElement(new Option(
+	"\tName of the hold out/test set to "
+	+"\n\testimate accuracy on.",
+	"H", 1,"-H <filename>"));
+
+    if ((m_Classifier != null) && 
+	(m_Classifier instanceof OptionHandler)) {
+      newVector.addElement(new Option("", "", 0, "\nOptions specific to " 
+				      + "scheme " 
+				      + m_Classifier.getClass().getName() 
+				      + ":"));
+      Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;classifier&gt;
+   *  class name of the classifier to use for accuracy estimation.
+   *  Place any classifier options LAST on the command line
+   *  following a "--". eg.:
+   *   -B weka.classifiers.bayes.NaiveBayes ... -- -K
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> -T
+   *  Use the training data to estimate accuracy.</pre>
+   * 
+   * <pre> -H &lt;filename&gt;
+   *  Name of the hold out/test set to 
+   *  estimate accuracy on.</pre>
+   * 
+   * <pre> 
+   * Options specific to scheme weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('B', options);
+    if (optionString.length() == 0)
+      optionString = ZeroR.class.getName();
+    setClassifier(AbstractClassifier.forName(optionString,
+				     Utils.partitionOptions(options)));
+
+    optionString = Utils.getOption('H',options);
+    if (optionString.length() != 0) {
+      setHoldOutFile(new File(optionString));
+    }
+
+    setUseTraining(Utils.getFlag('T',options));
+  }
+
+    /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "Classifier to use for estimating the accuracy of subsets";
+  }
+
+  /**
+   * Set the classifier to use for accuracy estimation
+   *
+   * @param newClassifier the Classifier to use.
+   */
+  public void setClassifier (Classifier newClassifier) {
+    m_Classifier = newClassifier;
+  }
+
+
+  /**
+   * Get the classifier used as the base learner.
+   *
+   * @return the classifier used as the classifier
+   */
+  public Classifier getClassifier () {
+    return  m_Classifier;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String holdOutFileTipText() {
+    return "File containing hold out/test instances.";
+  }
+
+  /**
+   * Gets the file that holds hold out/test instances.
+   * @return File that contains hold out instances
+   */
+  public File getHoldOutFile() {
+    return m_holdOutFile;
+  }
+
+
+  /**
+   * Set the file that contains hold out/test instances
+   * @param h the hold out file
+   */
+  public void setHoldOutFile(File h) {
+    m_holdOutFile = h;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useTrainingTipText() {
+    return "Use training data instead of hold out/test instances.";
+  }
+
+  /**
+   * Get if training data is to be used instead of hold out/test data
+   * @return true if training data is to be used instead of hold out data
+   */
+  public boolean getUseTraining() {
+    return m_useTraining;
+  }
+
+  /**
+   * Set if training data is to be used instead of hold out/test data
+   * @param t true if training data is to be used instead of hold out data
+   */
+  public void setUseTraining(boolean t) {
+    m_useTraining = t;
+  }
+
+  /**
+   * Gets the current settings of ClassifierSubsetEval
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] classifierOptions = new String[0];
+
+    if ((m_Classifier != null) && 
+	(m_Classifier instanceof OptionHandler)) {
+      classifierOptions = ((OptionHandler)m_Classifier).getOptions();
+    }
+
+    String[] options = new String[6 + classifierOptions.length];
+    int current = 0;
+
+    if (getClassifier() != null) {
+      options[current++] = "-B";
+      options[current++] = getClassifier().getClass().getName();
+    }
+
+    if (getUseTraining()) {
+      options[current++] = "-T";
+    }
+    options[current++] = "-H"; options[current++] = getHoldOutFile().getPath();
+
+    if (classifierOptions.length > 0) {
+      options[current++] = "--";
+      System.arraycopy(classifierOptions, 0, options, current, 
+	  classifierOptions.length);
+      current += classifierOptions.length;
+    }
+
+    while (current < options.length) {
+	options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getClassifier() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getClassifier().getCapabilities();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainingInstances = data;
+    m_classIndex = m_trainingInstances.classIndex();
+    m_numAttribs = m_trainingInstances.numAttributes();
+    m_numInstances = m_trainingInstances.numInstances();
+
+    // load the testing data
+    if (!m_useTraining && 
+	(!getHoldOutFile().getPath().startsWith("Click to set"))) {
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(getHoldOutFile().getPath()));
+	m_holdOutInstances = new Instances(r);
+	m_holdOutInstances.setClassIndex(m_trainingInstances.classIndex());
+	if (m_trainingInstances.equalHeaders(m_holdOutInstances) == false) {
+	  throw new Exception("Hold out/test set is not compatable with "
+			      +"training data.\n" 
+			      + m_trainingInstances.equalHeadersMsg(m_holdOutInstances));
+	}
+    }
+  }
+
+  /**
+   * Evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the error rate
+   * @throws Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset (BitSet subset)
+    throws Exception {
+    int i,j;
+    double errorRate = 0;
+    int numAttributes = 0;
+    Instances trainCopy=null;
+    Instances testCopy=null;
+
+    Remove delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    // copy the training instances
+    trainCopy = new Instances(m_trainingInstances);
+    
+    if (!m_useTraining) {
+      if (m_holdOutInstances == null) {
+	throw new Exception("Must specify a set of hold out/test instances "
+			    +"with -H");
+      } 
+      // copy the test instances
+      testCopy = new Instances(m_holdOutInstances);
+    }
+    
+    // count attributes set in the BitSet
+    for (i = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        numAttributes++;
+      }
+    }
+    
+    // set up an array of attribute indexes for the filter (+1 for the class)
+    int[] featArray = new int[numAttributes + 1];
+    
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        featArray[j++] = i;
+      }
+    }
+    
+    featArray[j] = m_classIndex;
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(trainCopy);
+    trainCopy = Filter.useFilter(trainCopy, delTransform);
+    if (!m_useTraining) {
+      testCopy = Filter.useFilter(testCopy, delTransform);
+    }
+
+    // build the classifier
+    m_Classifier.buildClassifier(trainCopy);
+
+    m_Evaluation = new Evaluation(trainCopy);
+    if (!m_useTraining) {
+      m_Evaluation.evaluateModel(m_Classifier, testCopy);
+    } else {
+      m_Evaluation.evaluateModel(m_Classifier, trainCopy);
+    }
+
+    if (m_trainingInstances.classAttribute().isNominal()) {
+      errorRate = m_Evaluation.errorRate();
+    } else {
+      errorRate = m_Evaluation.meanAbsoluteError();
+    }
+
+    m_Evaluation = null;
+    // return the negative of the error rate as search methods  need to
+    // maximize something
+    return -errorRate;
+  }
+
+  /**
+   * Evaluates a subset of attributes with respect to a set of instances.
+   * Calling this function overides any test/hold out instancs set from
+   * setHoldOutFile.
+   * @param subset a bitset representing the attribute subset to be
+   * evaluated
+   * @param holdOut a set of instances (possibly seperate and distinct
+   * from those use to build/train the evaluator) with which to
+   * evaluate the merit of the subset
+   * @return the "merit" of the subset on the holdOut data
+   * @throws Exception if the subset cannot be evaluated
+   */
+  public double evaluateSubset(BitSet subset, Instances holdOut) 
+    throws Exception {
+    int i,j;
+    double errorRate;
+    int numAttributes = 0;
+    Instances trainCopy=null;
+    Instances testCopy=null;
+
+    if (m_trainingInstances.equalHeaders(holdOut) == false) {
+      throw new Exception("evaluateSubset : Incompatable instance types.\n"
+	  + m_trainingInstances.equalHeadersMsg(holdOut));
+    }
+
+    Remove delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    // copy the training instances
+    trainCopy = new Instances(m_trainingInstances);
+    
+    testCopy = new Instances(holdOut);
+
+    // count attributes set in the BitSet
+    for (i = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        numAttributes++;
+      }
+    }
+    
+    // set up an array of attribute indexes for the filter (+1 for the class)
+    int[] featArray = new int[numAttributes + 1];
+    
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        featArray[j++] = i;
+      }
+    }
+    
+    featArray[j] = m_classIndex;
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(trainCopy);
+    trainCopy = Filter.useFilter(trainCopy, delTransform);
+    testCopy = Filter.useFilter(testCopy, delTransform);
+
+    // build the classifier
+    m_Classifier.buildClassifier(trainCopy);
+
+    m_Evaluation = new Evaluation(trainCopy);
+    m_Evaluation.evaluateModel(m_Classifier, testCopy);
+
+    if (m_trainingInstances.classAttribute().isNominal()) {
+      errorRate = m_Evaluation.errorRate();
+    } else {
+      errorRate = m_Evaluation.meanAbsoluteError();
+    }
+
+    m_Evaluation = null;
+    // return the negative of the error as search methods need to
+    // maximize something
+   return -errorRate;
+  }
+
+  /**
+   * Evaluates a subset of attributes with respect to a single instance.
+   * Calling this function overides any hold out/test instances set
+   * through setHoldOutFile.
+   * @param subset a bitset representing the attribute subset to be
+   * evaluated
+   * @param holdOut a single instance (possibly not one of those used to
+   * build/train the evaluator) with which to evaluate the merit of the subset
+   * @param retrain true if the classifier should be retrained with respect
+   * to the new subset before testing on the holdOut instance.
+   * @return the "merit" of the subset on the holdOut instance
+   * @throws Exception if the subset cannot be evaluated
+   */
+  public double evaluateSubset(BitSet subset, Instance holdOut,
+			       boolean retrain) 
+    throws Exception {
+    int i,j;
+    double error;
+    int numAttributes = 0;
+    Instances trainCopy=null;
+    Instance testCopy=null;
+
+    if (m_trainingInstances.equalHeaders(holdOut.dataset()) == false) {
+      throw new Exception("evaluateSubset : Incompatable instance types.\n"
+	  + m_trainingInstances.equalHeadersMsg(holdOut.dataset()));
+    }
+
+    Remove delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    // copy the training instances
+    trainCopy = new Instances(m_trainingInstances);
+    
+    testCopy = (Instance)holdOut.copy();
+
+    // count attributes set in the BitSet
+    for (i = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        numAttributes++;
+      }
+    }
+    
+    // set up an array of attribute indexes for the filter (+1 for the class)
+    int[] featArray = new int[numAttributes + 1];
+    
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        featArray[j++] = i;
+      }
+    }
+    featArray[j] = m_classIndex;
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(trainCopy);
+
+    if (retrain) {
+      trainCopy = Filter.useFilter(trainCopy, delTransform);
+      // build the classifier
+      m_Classifier.buildClassifier(trainCopy);
+    }
+
+    delTransform.input(testCopy);
+    testCopy = delTransform.output();
+
+    double pred;
+    double [] distrib;
+    distrib = m_Classifier.distributionForInstance(testCopy);
+    if (m_trainingInstances.classAttribute().isNominal()) {
+      pred = distrib[(int)testCopy.classValue()];
+    } else {
+      pred = distrib[0];
+    }
+
+    if (m_trainingInstances.classAttribute().isNominal()) {
+      error = 1.0 - pred;
+    } else {
+      error = testCopy.classValue() - pred;
+    }
+
+    // return the negative of the error as search methods need to
+    // maximize something
+    return -error;
+  }
+
+  /**
+   * Returns a string describing classifierSubsetEval
+   *
+   * @return the description as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    if (m_trainingInstances == null) {
+      text.append("\tClassifier subset evaluator has not been built yet\n");
+    }
+    else {
+      text.append("\tClassifier Subset Evaluator\n");
+      text.append("\tLearning scheme: " 
+		  + getClassifier().getClass().getName() + "\n");
+      text.append("\tScheme options: ");
+      String[] classifierOptions = new String[0];
+
+      if (m_Classifier instanceof OptionHandler) {
+        classifierOptions = ((OptionHandler)m_Classifier).getOptions();
+
+        for (int i = 0; i < classifierOptions.length; i++) {
+          text.append(classifierOptions[i] + " ");
+        }
+      }
+
+      text.append("\n");
+      text.append("\tHold out/test set: ");
+      if (!m_useTraining) {
+	if (getHoldOutFile().getPath().startsWith("Click to set")) {
+	  text.append("none\n");
+	} else {
+	  text.append(getHoldOutFile().getPath()+'\n');
+	}
+      } else {
+	text.append("Training data\n");
+      }
+      if (m_trainingInstances.attribute(m_classIndex).isNumeric()) {
+	text.append("\tAccuracy estimation: MAE\n");
+      } else {
+	text.append("\tAccuracy estimation: classification error\n");
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * reset to defaults
+   */
+  protected void resetOptions () {
+    m_trainingInstances = null;
+    m_Evaluation = null;
+    m_Classifier = new ZeroR();
+    m_holdOutFile = new File("Click to set hold out or test instances");
+    m_holdOutInstances = null;
+    m_useTraining = false;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new ClassifierSubsetEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ConsistencySubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ConsistencySubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ConsistencySubsetEval.java	(revision 29)
@@ -0,0 +1,522 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConsistencySubsetEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/** 
+ <!-- globalinfo-start -->
+ * ConsistencySubsetEval :<br/>
+ * <br/>
+ * Evaluates the worth of a subset of attributes by the level of consistency in the class values when the training instances are projected onto the subset of attributes. <br/>
+ * <br/>
+ * Consistency of any subset can never be lower than that of the full set of attributes, hence the usual practice is to use this subset evaluator in conjunction with a Random or Exhaustive search which looks for the smallest subset with consistency equal to that of the full set of attributes.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * H. Liu, R. Setiono: A probabilistic approach to feature selection - A filter solution. In: 13th International Conference on Machine Learning, 319-327, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Liu1996,
+ *    author = {H. Liu and R. Setiono},
+ *    booktitle = {13th International Conference on Machine Learning},
+ *    pages = {319-327},
+ *    title = {A probabilistic approach to feature selection - A filter solution},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5447 $
+ * @see Discretize
+ */
+public class ConsistencySubsetEval 
+  extends ASEvaluation
+  implements SubsetEvaluator,
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -2880323763295270402L;
+  
+  /** training instances */
+  private Instances m_trainInstances;
+
+  /** class index */
+  private int m_classIndex;
+
+  /** number of attributes in the training data */
+  private int m_numAttribs;
+
+  /** number of instances in the training data */
+  private int m_numInstances;
+
+  /** Discretise numeric attributes */
+  private Discretize m_disTransform;
+
+  /** Hash table for evaluating feature subsets */
+  private Hashtable m_table;
+
+  /**
+   * Class providing keys to the hash table.
+   */
+  public class hashKey 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 6144138512017017408L;
+    
+    /** Array of attribute values for an instance */
+    private double [] attributes;
+    
+    /** True for an index if the corresponding attribute value is missing. */
+    private boolean [] missing;
+
+    /** The key */
+    private int key;
+
+    /**
+     * Constructor for a hashKey
+     *
+     * @param t an instance from which to generate a key
+     * @param numAtts the number of attributes
+     * @throws Exception if something goes wrong
+     */
+    public hashKey(Instance t, int numAtts) throws Exception {
+
+      int i;
+      int cindex = t.classIndex();
+
+      key = -999;
+      attributes = new double [numAtts];
+      missing = new boolean [numAtts];
+      for (i=0;i<numAtts;i++) {
+	if (i == cindex) {
+	  missing[i] = true;
+	} else {
+	  if ((missing[i] = t.isMissing(i)) == false) {
+	    attributes[i] = t.value(i);
+	  }
+	}
+      }
+    }
+
+    /**
+     * Convert a hash entry to a string
+     *
+     * @param t the set of instances
+     * @param maxColWidth width to make the fields
+     * @return the hash entry as string
+     */
+    public String toString(Instances t, int maxColWidth) {
+
+      int i;
+      int cindex = t.classIndex();
+      StringBuffer text = new StringBuffer();
+      
+      for (i=0;i<attributes.length;i++) {
+	if (i != cindex) {
+	  if (missing[i]) {
+	    text.append("?");
+	    for (int j=0;j<maxColWidth;j++) {
+	      text.append(" ");
+	    }
+	  } else {
+	    String ss = t.attribute(i).value((int)attributes[i]);
+	    StringBuffer sb = new StringBuffer(ss);
+	    
+	    for (int j=0;j < (maxColWidth-ss.length()+1); j++) {
+		sb.append(" ");
+	    }
+	    text.append(sb);
+	  }
+	}
+      }
+      return text.toString();
+    }
+
+    /**
+     * Constructor for a hashKey
+     *
+     * @param t an array of feature values
+     */
+    public hashKey(double [] t) {
+
+      int i;
+      int l = t.length;
+
+      key = -999;
+      attributes = new double [l];
+      missing = new boolean [l];
+      for (i=0;i<l;i++) {
+	if (t[i] == Double.MAX_VALUE) {
+	  missing[i] = true;
+	} else {
+	  missing[i] = false;
+	  attributes[i] = t[i];
+	}
+      }
+    }
+    
+    /**
+     * Calculates a hash code
+     *
+     * @return the hash code as an integer
+     */
+    public int hashCode() {
+
+      int hv = 0;
+      
+      if (key != -999)
+	return key;
+      for (int i=0;i<attributes.length;i++) {
+	if (missing[i]) {
+	  hv += (i*13);
+	} else {
+	  hv += (i * 5 * (attributes[i]+1));
+	}
+      }
+      if (key == -999) {
+	key = hv;
+      }
+      return hv;
+    }
+
+    /**
+     * Tests if two instances are equal
+     *
+     * @param b a key to compare with
+     * @return true if the objects are equal
+     */
+    public boolean equals(Object b) {
+      
+      if ((b == null) || !(b.getClass().equals(this.getClass()))) {
+        return false;
+      }
+      boolean ok = true;
+      boolean l;
+      if (b instanceof hashKey) {
+	hashKey n = (hashKey)b;
+	for (int i=0;i<attributes.length;i++) {
+	  l = n.missing[i];
+	  if (missing[i] || l) {
+	    if ((missing[i] && !l) || (!missing[i] && l)) {
+	      ok = false;
+	      break;
+	    }
+	  } else {
+	    if (attributes[i] != n.attributes[i]) {
+	      ok = false;
+	      break;
+	    }
+	  }
+	}
+      } else {
+	return false;
+      }
+      return ok;
+    }
+    
+    /**
+     * Prints the hash code
+     */
+    public void print_hash_code() {
+      
+      System.out.println("Hash val: "+hashCode());
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5447 $");
+    }
+  }
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "ConsistencySubsetEval :\n\nEvaluates the worth of a subset of "
+      +"attributes by the level of consistency in the class values when the "
+      +"training instances are projected onto the subset of attributes. "
+      +"\n\nConsistency of any subset can never be lower than that of the "
+      +"full set of attributes, hence the usual practice is to use this "
+      +"subset evaluator in conjunction with a Random or Exhaustive search "
+      +"which looks for the smallest subset with consistency equal to that "
+      +"of the full set of attributes.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "H. Liu and R. Setiono");
+    result.setValue(Field.TITLE, "A probabilistic approach to feature selection - A filter solution");
+    result.setValue(Field.BOOKTITLE, "13th International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "319-327");
+    
+    return result;
+  }
+
+  /**
+   * Constructor. Calls restOptions to set default options
+   **/
+  public ConsistencySubsetEval () {
+    resetOptions();
+  }
+
+  /**
+   * reset to defaults
+   */
+  private void resetOptions () {
+    m_trainInstances = null;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data) throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = new Instances(data);
+    m_trainInstances.deleteWithMissingClass();
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+
+    m_disTransform = new Discretize();
+    m_disTransform.setUseBetterEncoding(true);
+    m_disTransform.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, m_disTransform);
+  }
+
+  /**
+   * Evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @throws Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset (BitSet subset) throws Exception {
+    int [] fs;
+    int i;
+    int count = 0;
+
+    for (i=0;i<m_numAttribs;i++) {
+      if (subset.get(i)) {
+	count++;
+      }
+    }
+
+    double [] instArray = new double[count];
+    int index = 0;
+    fs = new int[count];
+    for (i=0;i<m_numAttribs;i++) {
+      if (subset.get(i)) {
+	fs[index++] = i;
+      }
+    }
+    
+    // create new hash table
+    m_table = new Hashtable((int)(m_numInstances * 1.5));
+    
+    for (i=0;i<m_numInstances;i++) {
+      Instance inst = m_trainInstances.instance(i);
+      for (int j=0;j<fs.length;j++) {
+	if (fs[j] == m_classIndex) {
+	  throw new Exception("A subset should not contain the class!");
+	}
+	if (inst.isMissing(fs[j])) {
+	  instArray[j] = Double.MAX_VALUE;
+	} else {
+	  instArray[j] = inst.value(fs[j]);
+	}
+      }
+      insertIntoTable(inst, instArray);
+    }
+
+    return consistencyCount();
+  }
+
+  /**
+   * calculates the level of consistency in a dataset using a subset of
+   * features. The consistency of a hash table entry is the total number
+   * of instances hashed to that location minus the number of instances in
+   * the largest class hashed to that location. The total consistency is
+   * 1.0 minus the sum of the individual consistencies divided by the
+   * total number of instances.
+   * @return the consistency of the hash table as a value between 0 and 1.
+   */
+  private double consistencyCount() {
+    Enumeration e = m_table.keys();
+    double [] classDist;
+    double count = 0.0;
+    
+    while (e.hasMoreElements()) {
+      hashKey tt = (hashKey)e.nextElement();
+      classDist = (double []) m_table.get(tt);
+      count += Utils.sum(classDist);
+      int max = Utils.maxIndex(classDist);
+      count -= classDist[max];
+    }
+
+    count /= (double)m_numInstances;
+    return (1.0 - count);
+  }
+
+  /**
+   * Inserts an instance into the hash table
+   *
+   * @param inst instance to be inserted
+   * @param instA the instance to be inserted as an array of attribute
+   * values.
+   * @throws Exception if the instance can't be inserted
+   */
+  private void insertIntoTable(Instance inst, double [] instA)
+       throws Exception {
+
+    double [] tempClassDist2;
+    double [] newDist;
+    hashKey thekey;
+
+    thekey = new hashKey(instA);
+
+    // see if this one is already in the table
+    tempClassDist2 = (double []) m_table.get(thekey);
+    if (tempClassDist2 == null) {
+      newDist = new double [m_trainInstances.classAttribute().numValues()];
+      newDist[(int)inst.classValue()] = inst.weight();
+      
+      // add to the table
+      m_table.put(thekey, newDist);
+    } else { 
+      // update the distribution for this instance
+      tempClassDist2[(int)inst.classValue()]+=inst.weight();
+      
+      // update the table
+      m_table.put(thekey, tempClassDist2);
+    }
+  }
+
+  /**
+   * returns a description of the evaluator
+   * @return a description of the evaluator as a String.
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tConsistency subset evaluator has not been built yet\n");
+    }
+    else {
+      text.append("\tConsistency Subset Evaluator\n");
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new ConsistencySubsetEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveASEvaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveASEvaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveASEvaluation.java	(revision 29)
@@ -0,0 +1,581 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostSensitiveASEvaluation.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.classifiers.CostMatrix;
+import weka.core.WeightedInstancesHandler;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ * Abstract base class for cost-sensitive subset and attribute evaluators.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public abstract class CostSensitiveASEvaluation
+  extends ASEvaluation
+  implements OptionHandler, Serializable {
+
+  /** for serialization */
+  static final long serialVersionUID = -7045833833363396977L;
+
+  /** load cost matrix on demand */
+  public static final int MATRIX_ON_DEMAND = 1;
+  /** use explicit cost matrix */
+  public static final int MATRIX_SUPPLIED = 2;
+  /** Specify possible sources of the cost matrix */
+  public static final Tag [] TAGS_MATRIX_SOURCE = {
+    new Tag(MATRIX_ON_DEMAND, "Load cost matrix on demand"),
+    new Tag(MATRIX_SUPPLIED, "Use explicit cost matrix")
+  };
+
+  /** Indicates the current cost matrix source */
+  protected int m_MatrixSource = MATRIX_ON_DEMAND;
+
+  /** 
+   * The directory used when loading cost files on demand, null indicates
+   * current directory 
+   */
+  protected File m_OnDemandDirectory = new File(System.getProperty("user.dir"));
+
+  /** The name of the cost file, for command line options */
+  protected String m_CostFile;
+
+  /** The cost matrix */
+  protected CostMatrix m_CostMatrix = new CostMatrix(1);
+
+  /** The base evaluator to use */
+  protected ASEvaluation m_evaluator;
+
+  /** random number seed */
+  protected int m_seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+                                    "\tFile name of a cost matrix to use. If this is not supplied,\n"
+                                    +"\ta cost matrix will be loaded on demand. The name of the\n"
+                                    +"\ton-demand file is the relation name of the training data\n"
+                                    +"\tplus \".cost\", and the path to the on-demand file is\n"
+                                    +"\tspecified with the -N option.",
+                                    "C", 1, "-C <cost file name>"));
+    newVector.addElement(new Option(
+                                    "\tName of a directory to search for cost files when loading\n"
+                                    +"\tcosts on demand (default current directory).",
+                                    "N", 1, "-N <directory>"));
+    newVector.addElement(new Option(
+                                    "\tThe cost matrix in Matlab single line format.",
+                                    "cost-matrix", 1, "-cost-matrix <matrix>"));
+    newVector.addElement(new Option(
+                                    "\tThe seed to use for random number generation.",
+                                    "S", 1, "-S <integer>"));
+
+    newVector.addElement(new Option(
+                                    "\tFull name of base evaluator. Options after -- are "
+                                    +"passed to the evaluator.\n"
+                                    + "\t(default: " + defaultEvaluatorString() +")",
+                                    "W", 1, "-W"));
+
+    if (m_evaluator instanceof OptionHandler) {
+      newVector.addElement(new Option(
+                                      "",
+                                      "", 0, "\nOptions specific to evaluator "
+                                      + m_evaluator.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_evaluator).listOptions();
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;cost file name&gt;
+   *  File name of a cost matrix to use. If this is not supplied,
+   *  a cost matrix will be loaded on demand. The name of the
+   *  on-demand file is the relation name of the training data
+   *  plus ".cost", and the path to the on-demand file is
+   *  specified with the -N option.</pre>
+   * 
+   * <pre> -N &lt;directory&gt;
+   *  Name of a directory to search for cost files when loading
+   *  costs on demand (default current directory).</pre>
+   * 
+   * <pre> -cost-matrix &lt;matrix&gt;
+   *  The cost matrix in Matlab single line format.</pre>
+   * 
+   * <pre> -S &lt;integer&gt;
+   *  The seed to use for random number generation.</pre>
+   * 
+   * <pre> -W
+   *  Full name of base evaluator.
+   *  (default: weka.attributeSelection.CfsSubsetEval)</pre>
+   *
+   * Options after -- are passed to the designated evaluator.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String costFile = Utils.getOption('C', options);
+    if (costFile.length() != 0) {
+      try {
+	setCostMatrix(new CostMatrix(new BufferedReader(
+                                                        new FileReader(costFile))));
+      } catch (Exception ex) {
+	// now flag as possible old format cost matrix. Delay cost matrix
+	// loading until buildClassifer is called
+	setCostMatrix(null);
+      }
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+      m_CostFile = costFile;
+    } else {
+      setCostMatrixSource(new SelectedTag(MATRIX_ON_DEMAND, 
+                                          TAGS_MATRIX_SOURCE));
+    }
+    
+    String demandDir = Utils.getOption('N', options);
+    if (demandDir.length() != 0) {
+      setOnDemandDirectory(new File(demandDir));
+    }
+
+    String cost_matrix = Utils.getOption("cost-matrix", options);
+    if (cost_matrix.length() != 0) {
+      StringWriter writer = new StringWriter();
+      CostMatrix.parseMatlab(cost_matrix).write(writer);
+      setCostMatrix(new CostMatrix(new StringReader(writer.toString())));
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+    }
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    String evaluatorName = Utils.getOption('W', options);
+    
+    if (evaluatorName.length() > 0) { 
+      
+      // This is just to set the evaluator in case the option 
+      // parsing fails.
+      setEvaluator(ASEvaluation.forName(evaluatorName, null));
+      setEvaluator(ASEvaluation.forName(evaluatorName,
+                                        Utils.partitionOptions(options)));
+    } else {
+      
+      // This is just to set the classifier in case the option 
+      // parsing fails.
+      setEvaluator(ASEvaluation.forName(defaultEvaluatorString(), null));
+      setEvaluator(ASEvaluation.forName(defaultEvaluatorString(),
+                                        Utils.partitionOptions(options)));
+    }
+  }
+
+  /**
+   * Gets the current settings of the subset evaluator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    ArrayList<String> options = new ArrayList<String>();
+
+    if (m_MatrixSource == MATRIX_SUPPLIED) {
+      if (m_CostFile != null) {
+        options.add("-C");
+        options.add("" + m_CostFile);
+      }
+      else {
+        options.add("-cost-matrix");
+        options.add(getCostMatrix().toMatlab());
+      }
+    } else {
+      options.add("-N");
+      options.add("" + getOnDemandDirectory());
+    }
+
+    options.add("-S");
+    options.add("" + getSeed());
+
+    options.add("-W");
+    options.add(m_evaluator.getClass().getName());
+
+    if (m_evaluator instanceof OptionHandler) {
+      String[] evaluatorOptions = ((OptionHandler)m_evaluator).getOptions();
+      if (evaluatorOptions.length > 0) {
+        options.add("--");
+        for (int i = 0; i < evaluatorOptions.length; i++) {
+          options.add(evaluatorOptions[i]);
+        }
+      }
+    }
+
+    return options.toArray(new String[0]);
+  }
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A meta subset evaluator that makes its base subset evaluator cost-sensitive. ";
+  }
+
+  /**
+   * Return the name of the default evaluator.
+   *
+   * @return the name of the default evaluator
+   */
+  public String defaultEvaluatorString() {
+    return "weka.attributeSelection.CfsSubsetEval";
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixSourceTipText() {
+
+    return "Sets where to get the cost matrix. The two options are"
+      + "to use the supplied explicit cost matrix (the setting of the "
+      + "costMatrix property), or to load a cost matrix from a file when "
+      + "required (this file will be loaded from the directory set by the "
+      + "onDemandDirectory property and will be named relation_name" 
+      + CostMatrix.FILE_EXTENSION + ").";
+  }
+
+  /**
+   * Gets the source location method of the cost matrix. Will be one of
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED.
+   *
+   * @return the cost matrix source.
+   */
+  public SelectedTag getCostMatrixSource() {
+
+    return new SelectedTag(m_MatrixSource, TAGS_MATRIX_SOURCE);
+  }
+
+  /**
+   * Sets the source location of the cost matrix. Values other than
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED will be ignored.
+   *
+   * @param newMethod the cost matrix location method.
+   */
+  public void setCostMatrixSource(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_MATRIX_SOURCE) {
+      m_MatrixSource = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String onDemandDirectoryTipText() {
+
+    return "Sets the directory where cost files are loaded from. This option "
+      + "is used when the costMatrixSource is set to \"On Demand\".";
+  }
+
+  /**
+   * Returns the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @return The cost file search directory.
+   */
+  public File getOnDemandDirectory() {
+
+    return m_OnDemandDirectory;
+  }
+
+  /**
+   * Sets the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @param newDir The cost file search directory.
+   */
+  public void setOnDemandDirectory(File newDir) {
+
+    if (newDir.isDirectory()) {
+      m_OnDemandDirectory = newDir;
+    } else {
+      m_OnDemandDirectory = new File(newDir.getParent());
+    }
+    m_MatrixSource = MATRIX_ON_DEMAND;
+  }
+
+  /**
+   * Gets the evaluator specification string, which contains the class name of
+   * the evaluator and any options to the evaluator
+   *
+   * @return the evaluator string.
+   */
+  protected String getEvaluatorSpec() {
+    
+    ASEvaluation ase = getEvaluator();
+    if (ase instanceof OptionHandler) {
+      return ase.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)ase).getOptions());
+    }
+    return ase.getClass().getName();
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixTipText() {
+    return "Sets the cost matrix explicitly. This matrix is used if the "
+      + "costMatrixSource property is set to \"Supplied\".";
+  }
+
+  /**
+   * Gets the misclassification cost matrix.
+   *
+   * @return the cost matrix
+   */
+  public CostMatrix getCostMatrix() {
+    
+    return m_CostMatrix;
+  }
+  
+  /**
+   * Sets the misclassification cost matrix.
+   *
+   * @param newCostMatrix the cost matrix
+   */
+  public void setCostMatrix(CostMatrix newCostMatrix) {
+    
+    m_CostMatrix = newCostMatrix;
+    m_MatrixSource = MATRIX_SUPPLIED;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed 
+   */
+  public void setSeed(int seed) {
+
+    m_seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations.
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+    
+    return m_seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluatorTipText() {
+    return "The base evaluator to be used.";
+  }
+
+  /**
+   * Set the base evaluator.
+   *
+   * @param newEvaluator the evaluator to use.
+   * @throws IllegalArgumentException if the evaluator is of the wrong type
+   */
+  public void setEvaluator(ASEvaluation newEvaluator) throws IllegalArgumentException {
+
+    m_evaluator = newEvaluator;
+  }
+
+  /**
+   * Get the evaluator used as the base evaluator.
+   *
+   * @return the evaluator used as the base evaluator
+   */
+  public ASEvaluation getEvaluator() {
+
+    return m_evaluator;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result;
+
+    if (getEvaluator() != null) {
+      result = getEvaluator().getCapabilities();
+    } else {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @exception Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    if (m_evaluator == null) {
+      throw new Exception("No base evaluator has been set!");
+    }
+
+    if (m_MatrixSource == MATRIX_ON_DEMAND) {
+      String costName = data.relationName() + CostMatrix.FILE_EXTENSION;
+      File costFile = new File(getOnDemandDirectory(), costName);
+      if (!costFile.exists()) {
+        throw new Exception("On-demand cost file doesn't exist: " + costFile);
+      }
+      setCostMatrix(new CostMatrix(new BufferedReader(
+                                                      new FileReader(costFile))));
+    } else if (m_CostMatrix == null) {
+      // try loading an old format cost file
+      m_CostMatrix = new CostMatrix(data.numClasses());
+      m_CostMatrix.readOldFormat(new BufferedReader(
+                                                    new FileReader(m_CostFile)));
+    }
+    
+    Random random = null;
+    if (!(m_evaluator instanceof WeightedInstancesHandler)) {
+      random = new Random(m_seed);
+    }
+    data = m_CostMatrix.applyCostMatrix(data, random);
+    m_evaluator.buildEvaluator(data);
+  }
+
+  /**
+   * Provides a chance for a attribute evaluator to do any special
+   * post processing of the selected attribute set.
+   *
+   * @param attributeSet the set of attributes found by the search
+   * @return a possibly ranked list of postprocessed attributes
+   * @exception Exception if postprocessing fails for some reason
+   */
+  public int [] postProcess(int [] attributeSet) 
+    throws Exception {
+    return m_evaluator.postProcess(attributeSet);
+  }
+
+  /**
+   * Output a representation of this evaluator
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_evaluator == null) {
+      return "CostSensitiveASEvaluation: No model built yet.";
+    }
+  
+    String result = (m_evaluator instanceof AttributeEvaluator)
+      ? "CostSensitiveAttributeEval using "
+      : "CostSensitiveSubsetEval using ";
+
+    result += "\n\n" + getEvaluatorSpec()
+      + "\n\nEvaluator\n"
+      + m_evaluator.toString()
+      + "\n\nCost Matrix\n"
+      + m_CostMatrix.toString();
+    
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveAttributeEval.java	(revision 29)
@@ -0,0 +1,164 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostSensitiveAttributeEval.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.BitSet;
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * A meta subset evaluator that makes its base subset evaluator cost-sensitive.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;cost file name&gt;
+ *  File name of a cost matrix to use. If this is not supplied,
+ *  a cost matrix will be loaded on demand. The name of the
+ *  on-demand file is the relation name of the training data
+ *  plus ".cost", and the path to the on-demand file is
+ *  specified with the -N option.</pre>
+ * 
+ * <pre> -N &lt;directory&gt;
+ *  Name of a directory to search for cost files when loading
+ *  costs on demand (default current directory).</pre>
+ * 
+ * <pre> -cost-matrix &lt;matrix&gt;
+ *  The cost matrix in Matlab single line format.</pre>
+ * 
+ * <pre> -S &lt;integer&gt;
+ *  The seed to use for random number generation.</pre>
+ * 
+ * <pre> -W
+ *  Full name of base evaluator.
+ *  (default: weka.attributeSelection.ReliefFAttributeEval)</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.ReliefFAttributeEval:
+ * </pre>
+ * 
+ * <pre> -M &lt;num instances&gt;
+ *  Specify the number of instances to
+ *  sample when estimating attributes.
+ *  If not specified, then all instances
+ *  will be used.</pre>
+ * 
+ * <pre> -D &lt;seed&gt;
+ *  Seed for randomly sampling instances.
+ *  (Default = 1)</pre>
+ * 
+ * <pre> -K &lt;number of neighbours&gt;
+ *  Number of nearest neighbours (k) used
+ *  to estimate attribute relevances
+ *  (Default = 10).</pre>
+ * 
+ * <pre> -W
+ *  Weight nearest neighbours by distance</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  Specify sigma value (used in an exp
+ *  function to control how quickly
+ *  weights for more distant instances
+ *  decrease. Use in conjunction with -W.
+ *  Sensible value=1/5 to 1/10 of the
+ *  number of nearest neighbours.
+ *  (Default = 2)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class CostSensitiveAttributeEval
+  extends CostSensitiveASEvaluation
+  implements Serializable, AttributeEvaluator, OptionHandler {
+
+  /** For serialization */
+  static final long serialVersionUID = 4484876541145458447L;
+
+  /**
+   * Default constructor.
+   */
+  public CostSensitiveAttributeEval() {
+    setEvaluator(new ReliefFAttributeEval());
+  }
+ 
+  /**
+   * Return the name of the default evaluator.
+   *
+   * @return the name of the default evaluator
+   */
+  public String defaultEvaluatorString() {
+    return "weka.attributeSelection.ReliefFAttributeEval";
+  }
+
+  /**
+   * Set the base evaluator.
+   *
+   * @param newEvaluator the evaluator to use.
+   * @throws IllegalArgumentException if the evaluator is not an instance of AttributeEvaluator
+   */
+  public void setEvaluator(ASEvaluation newEvaluator) throws IllegalArgumentException {
+    if (!(newEvaluator instanceof AttributeEvaluator)) {
+      throw new IllegalArgumentException("Evaluator must be an AttributeEvaluator!");
+    }
+
+    m_evaluator = newEvaluator;
+  }
+
+  /**
+   * Evaluates an individual attribute. Delegates the actual evaluation to the
+   * base attribute evaluator.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the "merit" of the attribute
+   * @exception Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute(int attribute) throws Exception {
+    return ((AttributeEvaluator)m_evaluator).evaluateAttribute(attribute);
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new CostSensitiveAttributeEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveSubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveSubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/CostSensitiveSubsetEval.java	(revision 29)
@@ -0,0 +1,135 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostSensitiveSubsetEval.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.BitSet;
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * A meta subset evaluator that makes its base subset evaluator cost-sensitive.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;cost file name&gt;
+ *  File name of a cost matrix to use. If this is not supplied,
+ *  a cost matrix will be loaded on demand. The name of the
+ *  on-demand file is the relation name of the training data
+ *  plus ".cost", and the path to the on-demand file is
+ *  specified with the -N option.</pre>
+ * 
+ * <pre> -N &lt;directory&gt;
+ *  Name of a directory to search for cost files when loading
+ *  costs on demand (default current directory).</pre>
+ * 
+ * <pre> -cost-matrix &lt;matrix&gt;
+ *  The cost matrix in Matlab single line format.</pre>
+ * 
+ * <pre> -S &lt;integer&gt;
+ *  The seed to use for random number generation.</pre>
+ * 
+ * <pre> -W
+ *  Full name of base evaluator.
+ *  (default: weka.attributeSelection.CfsSubsetEval)</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.CfsSubsetEval:
+ * </pre>
+ * 
+ * <pre> -M
+ *  Treat missing values as a seperate value.</pre>
+ * 
+ * <pre> -L
+ *  Don't include locally predictive attributes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class CostSensitiveSubsetEval
+  extends CostSensitiveASEvaluation
+  implements Serializable, SubsetEvaluator, OptionHandler {
+
+  /** For serialization */
+  static final long serialVersionUID = 2924546096103426700L;
+
+  /**
+   * Default constructor.
+   */
+  public CostSensitiveSubsetEval() {
+    setEvaluator(new CfsSubsetEval());
+  }
+
+  /**
+   * Set the base evaluator.
+   *
+   * @param newEvaluator the evaluator to use.
+   * @throws IllegalArgumentException if the evaluator is not an instance of SubsetEvaluator
+   */
+  public void setEvaluator(ASEvaluation newEvaluator) throws IllegalArgumentException {
+    if (!(newEvaluator instanceof SubsetEvaluator)) {
+      throw new IllegalArgumentException("Evaluator must be an SubsetEvaluator!");
+    }
+
+    m_evaluator = newEvaluator;
+  }
+
+  /**
+   * Evaluates a subset of attributes. Delegates the actual evaluation to
+   * the base subset evaluator.
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the "merit" of the subset
+   * @exception Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset(BitSet subset) throws Exception {
+    return ((SubsetEvaluator)m_evaluator).evaluateSubset(subset);
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new CostSensitiveSubsetEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ErrorBasedMeritEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ErrorBasedMeritEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ErrorBasedMeritEvaluator.java	(revision 29)
@@ -0,0 +1,35 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ErrorBasedMeritEvaluator.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+
+/** 
+ * Interface for evaluators that calculate the "merit" of attributes/subsets
+ * as the error of a learning scheme
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public interface ErrorBasedMeritEvaluator {
+
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ExhaustiveSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ExhaustiveSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ExhaustiveSearch.java	(revision 29)
@@ -0,0 +1,451 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ExhaustiveSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.math.BigInteger;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * ExhaustiveSearch : <br/>
+ * <br/>
+ * Performs an exhaustive search through the space of attribute subsets starting from the empty set of attrubutes. Reports the best subset found.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -V
+ *  Output subsets as the search progresses.
+ *  (default = false).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.15 $
+ */
+public class ExhaustiveSearch 
+  extends ASSearch 
+  implements OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5741842861142379712L;
+  
+  /** the best feature set found during the search */
+  private BitSet m_bestGroup;
+
+  /** the merit of the best subset found */
+  private double m_bestMerit;
+
+ /** does the data have a class */
+  private boolean m_hasClass;
+ 
+  /** holds the class index */
+  private int m_classIndex;
+ 
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** if true, then ouput new best subsets as the search progresses */
+  private boolean m_verbose;
+  
+  /** the number of subsets evaluated during the search */
+  private int m_evaluations;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "ExhaustiveSearch : \n\nPerforms an exhaustive search through "
+      +"the space of attribute subsets starting from the empty set of "
+      +"attrubutes. Reports the best subset found.";
+  }
+
+  /**
+   * Constructor
+   */
+  public ExhaustiveSearch () {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option("\tOutput subsets as the search progresses."
+				    +"\n\t(default = false)."
+				    , "V", 0
+				    , "-V"));
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -V
+   *  Output subsets as the search progresses.
+   *  (default = false).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+
+    resetOptions();
+
+    setVerbose(Utils.getFlag('V',options));
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "Print progress information. Sends progress info to the terminal "
+      +"as the search progresses.";
+  }
+
+  /**
+   * set whether or not to output new best subsets as the search proceeds
+   * @param v true if output is to be verbose
+   */
+  public void setVerbose(boolean v) {
+    m_verbose = v;
+  }
+
+  /**
+   * get whether or not output is verbose
+   * @return true if output is set to verbose
+   */
+  public boolean getVerbose() {
+    return m_verbose;
+  }
+
+  /**
+   * Gets the current settings of RandomSearch.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[1];
+    int current = 0;
+	
+    if (m_verbose) {
+      options[current++] = "-V";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return  options;
+  }
+
+  /**
+   * prints a description of the search
+   * @return a description of the search as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("\tExhaustive Search.\n\tStart set: ");
+
+    text.append("no attributes\n");
+
+    text.append("\tNumber of evaluations: "+m_evaluations+"\n");
+    text.append("\tMerit of best subset found: "
+		+Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
+
+    return text.toString();
+  }
+
+  /**
+   * Searches the attribute subset space using an exhaustive search.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+   public int[] search (ASEvaluation ASEval, Instances data)
+     throws Exception {
+     double best_merit;
+     double tempMerit;
+     boolean done = false;
+     int sizeOfBest;
+     int tempSize;
+     
+     BigInteger space = BigInteger.ZERO;
+
+     m_evaluations = 0;
+     m_numAttribs = data.numAttributes();
+     m_bestGroup = new BitSet(m_numAttribs);
+     
+     if (!(ASEval instanceof SubsetEvaluator)) {
+       throw  new Exception(ASEval.getClass().getName() 
+			    + " is not a " 
+			    + "Subset evaluator!");
+     }
+     
+     if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+       m_hasClass = false;
+     }
+     else {
+       m_hasClass = true;
+       m_classIndex = data.classIndex();
+     }
+     
+     SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
+     m_numAttribs = data.numAttributes();
+
+     best_merit = ASEvaluator.evaluateSubset(m_bestGroup);
+     m_evaluations++;
+     sizeOfBest = countFeatures(m_bestGroup);
+
+     BitSet tempGroup = new BitSet(m_numAttribs);
+     tempMerit = ASEvaluator.evaluateSubset(tempGroup);
+
+     if (m_verbose) {
+       System.out.println("Zero feature subset ("
+			  +Utils.doubleToString(Math.
+						abs(tempMerit),8,5)
+			  +")");
+     }
+
+     if (tempMerit >= best_merit) {
+       tempSize = countFeatures(tempGroup);
+       if (tempMerit > best_merit || 
+	   (tempSize < sizeOfBest)) {
+	 best_merit = tempMerit;
+	 m_bestGroup = (BitSet)(tempGroup.clone());
+	 sizeOfBest = tempSize;
+       }
+     }
+
+     int numatts = (m_hasClass) 
+       ? m_numAttribs - 1
+       : m_numAttribs;
+     BigInteger searchSpaceEnd = 
+       BigInteger.ONE.add(BigInteger.ONE).pow(numatts).subtract(BigInteger.ONE);
+
+     while (!done) {
+       // the next subset
+       space = space.add(BigInteger.ONE);
+       if (space.equals(searchSpaceEnd)) {
+         done = true;
+       }
+       tempGroup.clear();
+       for (int i = 0; i < numatts; i++) {
+         if (space.testBit(i)) {
+           if (!m_hasClass) {
+             tempGroup.set(i);
+           } else {
+             int j = (i >= m_classIndex)
+               ? i + 1
+               : i;
+             tempGroup.set(j);
+           }
+         }
+       }
+
+       tempMerit = ASEvaluator.evaluateSubset(tempGroup);
+       m_evaluations++;
+       if (tempMerit >= best_merit) {
+         tempSize = countFeatures(tempGroup);
+         if (tempMerit > best_merit || 
+             (tempSize < sizeOfBest)) {
+           best_merit = tempMerit;
+           m_bestGroup = (BitSet)(tempGroup.clone());
+           sizeOfBest = tempSize;
+           if (m_verbose) {
+             System.out.println("New best subset ("
+                                +Utils.doubleToString(Math.
+                                                      abs(best_merit),8,5)
+                                +"): "+printSubset(m_bestGroup));
+           }
+         }
+       }
+     }
+
+     m_bestMerit = best_merit;
+     
+     return attributeList(m_bestGroup);
+   }
+
+  /**
+   * counts the number of features in a subset
+   * @param featureSet the feature set for which to count the features
+   * @return the number of features in the subset
+   */
+  private int countFeatures(BitSet featureSet) {
+    int count = 0;
+    for (int i=0;i<m_numAttribs;i++) {
+      if (featureSet.get(i)) {
+	count++;
+      }
+    }
+    return count;
+  }   
+
+  /**
+   * prints a subset as a series of attribute numbers
+   * @param temp the subset to print
+   * @return a subset as a String of attribute numbers
+   */
+  private String printSubset(BitSet temp) {
+    StringBuffer text = new StringBuffer();
+
+    for (int j=0;j<m_numAttribs;j++) {
+      if (temp.get(j)) {
+        text.append((j+1)+" ");
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  private int[] attributeList (BitSet group) {
+    int count = 0;
+    
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	count++;
+      }
+    }
+    
+    int[] list = new int[count];
+    count = 0;
+    
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	list[count++] = i;
+      }
+    }
+    
+    return  list;
+  }
+
+  /**
+   * generates the next subset of size "size" given the subset "temp".
+   * @param size the size of the feature subset (eg. 2 means that the 
+   * current subset contains two features and the next generated subset
+   * should also contain 2 features).
+   * @param temp will hold the generated subset as a BitSet
+   */
+  private void generateNextSubset(int size, BitSet temp) {
+    int i,j;
+    int counter = 0;
+    boolean done = false;
+    BitSet temp2 = (BitSet)temp.clone();
+
+    System.err.println("Size: "+size);
+    for (i=0;i<m_numAttribs;i++) {
+      temp2.clear(i);
+    }
+
+    while ((!done) && (counter < size)) {
+      for (i=m_numAttribs-1-counter;i>=0;i--) {
+        if (temp.get(i)) {
+
+          temp.clear(i);
+
+          int newP;
+	  if (i != (m_numAttribs-1-counter)) {
+            newP = i+1;
+            if (newP == m_classIndex) {
+              newP++;
+            }
+
+            if (newP < m_numAttribs) {
+              temp.set(newP);
+
+              for (j=0;j<counter;j++) {
+                if (newP+1+j == m_classIndex) {
+                  newP++;
+                }
+
+                if (newP+1+j < m_numAttribs) {
+                  temp.set(newP+1+j);
+                }
+              }
+              done = true;
+            } else {
+              counter++;
+            }
+	    break;
+	  } else {
+	    counter++;
+	    break;
+	  }
+	}
+      }
+    }
+
+    if (temp.cardinality() < size) {
+      temp.clear();
+    }
+    System.err.println(printSubset(temp).toString());
+  }
+      
+  /**
+   * resets to defaults
+   */
+  private void resetOptions() {
+    m_verbose = false;
+    m_evaluations = 0;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.15 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/FCBFSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/FCBFSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/FCBFSearch.java	(revision 29)
@@ -0,0 +1,872 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+ /*
+ *    RELEASE INFORMATION (December 27, 2004)
+ *    
+ *    FCBF algorithm:
+ *      Template obtained from Weka
+ *      Developed for Weka by Zheng Alan Zhao   
+ *      December 27, 2004
+ *
+ *    FCBF algorithm is a feature selection method based on Symmetrical Uncertainty Measurement for 
+ *    relevance redundancy analysis. The details of FCBF algorithm are in:
+ *
+ <!-- technical-plaintext-start -->
+ * Lei Yu, Huan Liu: Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution. In: Proceedings of the Twentieth International Conference on Machine Learning, 856-863, 2003.
+ <!-- technical-plaintext-end -->
+ *    
+ *    
+ *    CONTACT INFORMATION
+ *    
+ *    For algorithm implementation:
+ *    Zheng Zhao: zhaozheng at asu.edu
+ *      
+ *    For the algorithm:
+ *    Lei Yu: leiyu at asu.edu
+ *    Huan Liu: hliu at asu.edu
+ *     
+ *    Data Mining and Machine Learning Lab
+ *    Computer Science and Engineering Department
+ *    Fulton School of Engineering
+ *    Arizona State University
+ *    Tempe, AZ 85287
+ *
+ *    FCBFSearch.java
+ *
+ *    Copyright (C) 2004 Data Mining and Machine Learning Lab, 
+ *                       Computer Science and Engineering Department, 
+ *			 Fulton School of Engineering, 
+ *                       Arizona State University
+ *
+ */
+
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * FCBF : <br/>
+ * <br/>
+ * Feature selection method based on correlation measureand relevance&amp;redundancy analysis. Use in conjunction with an attribute set evaluator (SymmetricalUncertAttributeEval).<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Lei Yu, Huan Liu: Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution. In: Proceedings of the Twentieth International Conference on Machine Learning, 856-863, 2003.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Yu2003,
+ *    author = {Lei Yu and Huan Liu},
+ *    booktitle = {Proceedings of the Twentieth International Conference on Machine Learning},
+ *    pages = {856-863},
+ *    publisher = {AAAI Press},
+ *    title = {Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D &lt;create dataset&gt;
+ *  Specify Whether the selector generates a new dataset.</pre>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *   Eg. 1,3,5-7.
+ *  Any starting attributes specified are
+ *  ignored during the ranking.</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Specify a theshold by which attributes
+ *  may be discarded from the ranking.</pre>
+ * 
+ * <pre> -N &lt;num to select&gt;
+ *  Specify number of attributes to select</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Zheng Zhao: zhaozheng at asu.edu
+ * @version $Revision: 1.7 $
+ */
+public class FCBFSearch 
+  extends ASSearch
+  implements RankedOutputSearch, StartSetHandler, OptionHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8209699587428369942L;
+  
+  /** Holds the starting set as an array of attributes */
+  private int[] m_starting;
+
+  /** Holds the start set for the search as a range */
+  private Range m_startRange;
+
+  /** Holds the ordered list of attributes */
+  private int[] m_attributeList;
+
+  /** Holds the list of attribute merit scores */
+  private double[] m_attributeMerit;
+
+  /** Data has class attribute---if unsupervised evaluator then no class */
+  private boolean m_hasClass;
+
+  /** Class index of the data if supervised evaluator */
+  private int m_classIndex;
+
+  /** The number of attribtes */
+  private int m_numAttribs;
+
+  /**
+   * A threshold by which to discard attributes---used by the
+   * AttributeSelection module
+   */
+  private double m_threshold;
+
+  /** The number of attributes to select. -1 indicates that all attributes
+      are to be retained. Has precedence over m_threshold */
+  private int m_numToSelect = -1;
+
+  /** Used to compute the number to select */
+  private int m_calculatedNumToSelect = -1;
+
+  /*-----------------add begin 2004-11-15 by alan-----------------*/
+  /** Used to determine whether we create a new dataset according to the selected features */
+  private boolean m_generateOutput = false;
+
+  /** Used to store the ref of the Evaluator we use*/
+  private ASEvaluation m_asEval;
+
+  /** Holds the list of attribute merit scores generated by FCBF */
+  private double[][] m_rankedFCBF;
+
+  /** Hold the list of selected features*/
+  private double[][] m_selectedFeatures;
+  /*-----------------add end 2004-11-15 by alan-----------------*/
+
+   /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "FCBF : \n\nFeature selection method based on correlation measure"
+      + "and relevance&redundancy analysis. "
+      + "Use in conjunction with an attribute set evaluator (SymmetricalUncertAttributeEval).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lei Yu and Huan Liu");
+    result.setValue(Field.TITLE, "Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the Twentieth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.PAGES, "856-863");
+    result.setValue(Field.PUBLISHER, "AAAI Press");
+    
+    return result;
+  }
+
+  /**
+   * Constructor
+   */
+  public FCBFSearch () {
+    resetOptions();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numToSelectTipText() {
+    return "Specify the number of attributes to retain. The default value "
+      +"(-1) indicates that all attributes are to be retained. Use either "
+      +"this option or a threshold to reduce the attribute set.";
+  }
+
+  /**
+   * Specify the number of attributes to select from the ranked list. -1
+   * indicates that all attributes are to be retained.
+   * @param n the number of attributes to retain
+   */
+  public void setNumToSelect(int n) {
+    m_numToSelect = n;
+  }
+
+  /**
+   * Gets the number of attributes to be retained.
+   * @return the number of attributes to retain
+   */
+  public int getNumToSelect() {
+    return m_numToSelect;
+  }
+
+  /**
+   * Gets the calculated number to select. This might be computed
+   * from a threshold, or if < 0 is set as the number to select then
+   * it is set to the number of attributes in the (transformed) data.
+   * @return the calculated number of attributes to select
+   */
+  public int getCalculatedNumToSelect() {
+    if (m_numToSelect >= 0) {
+      m_calculatedNumToSelect = m_numToSelect;
+    }
+    if (m_selectedFeatures.length>0
+        && m_selectedFeatures.length<m_calculatedNumToSelect)
+    {
+      m_calculatedNumToSelect = m_selectedFeatures.length;
+    }
+
+    return m_calculatedNumToSelect;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Set threshold by which attributes can be discarded. Default value "
+      + "results in no attributes being discarded. Use either this option or "
+      +"numToSelect to reduce the attribute set.";
+  }
+
+  /**
+   * Set the threshold by which the AttributeSelection module can discard
+   * attributes.
+   * @param threshold the threshold.
+   */
+  public void setThreshold(double threshold) {
+    m_threshold = threshold;
+  }
+
+  /**
+   * Returns the threshold so that the AttributeSelection module can
+   * discard attributes from the ranking.
+   * @return the threshold
+   */
+  public double getThreshold() {
+    return m_threshold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String generateRankingTipText() {
+    return "A constant option. FCBF is capable of generating"
+      +" attribute rankings.";
+  }
+
+  /**
+   * This is a dummy set method---Ranker is ONLY capable of producing
+   * a ranked list of attributes for attribute evaluators.
+   * @param doRank this parameter is N/A and is ignored
+   */
+  public void setGenerateRanking(boolean doRank) {
+  }
+
+  /**
+   * This is a dummy method. Ranker can ONLY be used with attribute
+   * evaluators and as such can only produce a ranked list of attributes
+   * @return true all the time.
+   */
+  public boolean getGenerateRanking() {
+    return true;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+
+  public String generateDataOutputTipText() {
+    return "Generating new dataset according to the selected features."
+      +" ";
+  }
+
+  /**
+   * Sets the flag, by which the AttributeSelection module decide
+   * whether create a new dataset according to the selected features.
+   * @param doGenerate the flag, by which the AttributeSelection module
+   * decide whether create a new dataset according to the selected
+   * features
+   */
+  public void setGenerateDataOutput(boolean doGenerate) {
+    this.m_generateOutput = doGenerate;
+
+  }
+
+  /**
+   * Returns the flag, by which the AttributeSelection module decide
+   * whether create a new dataset according to the selected features.
+   * @return the flag, by which the AttributeSelection module decide
+   * whether create a new dataset according to the selected features.
+   */
+  public boolean getGenerateDataOutput() {
+    return this.m_generateOutput;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Specify a set of attributes to ignore. "
+      +" When generating the ranking, FCBF will not evaluate the attributes "
+      +" in this list. "
+      +"This is specified as a comma "
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+	"\tSpecify Whether the selector generates a new dataset.",
+	"D", 1, "-D <create dataset>"));
+
+    newVector.addElement(new Option(
+	"\tSpecify a starting set of attributes.\n"
+	+ "\t\tEg. 1,3,5-7.\n"
+	+ "\tAny starting attributes specified are\n"
+	+ "\tignored during the ranking.",
+	"P", 1 , "-P <start set>"));
+
+    newVector.addElement(new Option(
+	"\tSpecify a theshold by which attributes\n"
+	+ "\tmay be discarded from the ranking.",
+	"T", 1, "-T <threshold>"));
+
+    newVector.addElement(new Option(
+	"\tSpecify number of attributes to select",
+	"N", 1, "-N <num to select>"));
+
+    return newVector.elements();
+
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D &lt;create dataset&gt;
+   *  Specify Whether the selector generates a new dataset.</pre>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *   Eg. 1,3,5-7.
+   *  Any starting attributes specified are
+   *  ignored during the ranking.</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Specify a theshold by which attributes
+   *  may be discarded from the ranking.</pre>
+   * 
+   * <pre> -N &lt;num to select&gt;
+   *  Specify number of attributes to select</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('D', options);
+    if (optionString.length() != 0) {
+      setGenerateDataOutput(Boolean.getBoolean(optionString));
+    }
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() != 0) {
+      Double temp;
+      temp = Double.valueOf(optionString);
+      setThreshold(temp.doubleValue());
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumToSelect(Integer.parseInt(optionString));
+    }
+  }
+
+  /**
+   * Gets the current settings of ReliefFAttributeEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[8];
+    int current = 0;
+
+      options[current++] = "-D";
+      options[current++] = ""+getGenerateDataOutput();
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = ""+startSetToString();
+    }
+
+    options[current++] = "-T";
+    options[current++] = "" + getThreshold();
+
+    options[current++] = "-N";
+    options[current++] = ""+getNumToSelect();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return  options;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+
+    if (m_starting == null) {
+      return getStartSet();
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+
+      if ((m_hasClass == false) ||
+          (m_hasClass == true && i != m_classIndex)) {
+        FString.append((m_starting[i] + 1));
+        didPrint = true;
+      }
+
+      if (i == (m_starting.length - 1)) {
+        FString.append("");
+      }
+      else {
+        if (didPrint) {
+          FString.append(",");
+        }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * Kind of a dummy search algorithm. Calls a Attribute evaluator to
+   * evaluate each attribute not included in the startSet and then sorts
+   * them to produce a ranked list of attributes.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+    int i, j;
+
+    if (!(ASEval instanceof AttributeSetEvaluator)) {
+      throw  new Exception(ASEval.getClass().getName()
+                           + " is not an "
+                           + "Attribute Set evaluator!");
+    }
+
+    m_numAttribs = data.numAttributes();
+
+    if (ASEval instanceof UnsupervisedAttributeEvaluator) {
+      m_hasClass = false;
+    }
+    else {
+      m_classIndex = data.classIndex();
+      if (m_classIndex >= 0) {
+        m_hasClass = true;
+      } else {
+        m_hasClass = false;
+      }
+    }
+
+    // get the transformed data and check to see if the transformer
+    // preserves a class index
+    if (ASEval instanceof AttributeTransformer) {
+      data = ((AttributeTransformer)ASEval).transformedHeader();
+      if (m_classIndex >= 0 && data.classIndex() >= 0) {
+        m_classIndex = data.classIndex();
+        m_hasClass = true;
+      }
+    }
+
+
+    m_startRange.setUpper(m_numAttribs - 1);
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+
+    int sl=0;
+    if (m_starting != null) {
+      sl = m_starting.length;
+    }
+    if ((m_starting != null) && (m_hasClass == true)) {
+      // see if the supplied list contains the class index
+      boolean ok = false;
+      for (i = 0; i < sl; i++) {
+        if (m_starting[i] == m_classIndex) {
+          ok = true;
+          break;
+        }
+      }
+
+      if (ok == false) {
+        sl++;
+      }
+    }
+    else {
+      if (m_hasClass == true) {
+        sl++;
+      }
+    }
+
+
+    m_attributeList = new int[m_numAttribs - sl];
+    m_attributeMerit = new double[m_numAttribs - sl];
+
+    // add in those attributes not in the starting (omit list)
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (!inStarting(i)) {
+        m_attributeList[j++] = i;
+      }
+    }
+
+    this.m_asEval = ASEval;
+    AttributeSetEvaluator ASEvaluator = (AttributeSetEvaluator)ASEval;
+
+    for (i = 0; i < m_attributeList.length; i++) {
+      m_attributeMerit[i] = ASEvaluator.evaluateAttribute(m_attributeList[i]);
+    }
+
+    double[][] tempRanked = rankedAttributes();
+    int[] rankedAttributes = new int[m_selectedFeatures.length];
+
+    for (i = 0; i < m_selectedFeatures.length; i++) {
+      rankedAttributes[i] = (int)tempRanked[i][0];
+    }
+    return  rankedAttributes;
+  }
+
+
+
+  /**
+   * Sorts the evaluated attribute list
+   *
+   * @return an array of sorted (highest eval to lowest) attribute indexes
+   * @throws Exception of sorting can't be done.
+   */
+  public double[][] rankedAttributes ()
+    throws Exception {
+    int i, j;
+
+    if (m_attributeList == null || m_attributeMerit == null) {
+      throw  new Exception("Search must be performed before a ranked "
+                           + "attribute list can be obtained");
+    }
+
+    int[] ranked = Utils.sort(m_attributeMerit);
+    // reverse the order of the ranked indexes
+    double[][] bestToWorst = new double[ranked.length][2];
+
+    for (i = ranked.length - 1, j = 0; i >= 0; i--) {
+      bestToWorst[j++][0] = ranked[i];
+    //alan: means in the arrary ranked, varialbe is from ranked as from small to large
+    }
+
+    // convert the indexes to attribute indexes
+    for (i = 0; i < bestToWorst.length; i++) {
+      int temp = ((int)bestToWorst[i][0]);
+      bestToWorst[i][0] = m_attributeList[temp];     //for the index
+      bestToWorst[i][1] = m_attributeMerit[temp];    //for the value of the index
+    }
+
+    if (m_numToSelect > bestToWorst.length) {
+      throw new Exception("More attributes requested than exist in the data");
+    }
+
+    this.FCBFElimination(bestToWorst);
+
+    if (m_numToSelect <= 0) {
+      if (m_threshold == -Double.MAX_VALUE) {
+        m_calculatedNumToSelect = m_selectedFeatures.length;
+      } else {
+        determineNumToSelectFromThreshold(m_selectedFeatures);
+      }
+    }
+    /*    if (m_numToSelect > 0) {
+      determineThreshFromNumToSelect(bestToWorst);
+      } */
+
+    return  m_selectedFeatures;
+  }
+
+  private void determineNumToSelectFromThreshold(double [][] ranking) {
+    int count = 0;
+    for (int i = 0; i < ranking.length; i++) {
+      if (ranking[i][1] > m_threshold) {
+        count++;
+      }
+    }
+    m_calculatedNumToSelect = count;
+  }
+
+  private void determineThreshFromNumToSelect(double [][] ranking)
+    throws Exception {
+    if (m_numToSelect > ranking.length) {
+      throw new Exception("More attributes requested than exist in the data");
+    }
+
+    if (m_numToSelect == ranking.length) {
+      return;
+    }
+
+    m_threshold = (ranking[m_numToSelect-1][1] +
+                   ranking[m_numToSelect][1]) / 2.0;
+  }
+
+  /**
+   * returns a description of the search as a String
+   * @return a description of the search
+   */
+  public String toString () {
+    StringBuffer BfString = new StringBuffer();
+    BfString.append("\tAttribute ranking.\n");
+
+    if (m_starting != null) {
+      BfString.append("\tIgnored attributes: ");
+
+      BfString.append(startSetToString());
+      BfString.append("\n");
+    }
+
+    if (m_threshold != -Double.MAX_VALUE) {
+      BfString.append("\tThreshold for discarding attributes: "
+                      + Utils.doubleToString(m_threshold,8,4)+"\n");
+    }
+
+    BfString.append("\n\n");
+
+    BfString.append("     J || SU(j,Class) ||    I || SU(i,j). \n");
+
+    for (int i=0; i<m_rankedFCBF.length; i++)
+    {
+      BfString.append(Utils.doubleToString(m_rankedFCBF[i][0]+1,6,0)+" ; "
+                      +Utils.doubleToString(m_rankedFCBF[i][1],12,7)+" ; ");
+      if (m_rankedFCBF[i][2] == m_rankedFCBF[i][0])
+      {
+        BfString.append("    *\n");
+      }
+      else
+      {
+        BfString.append(Utils.doubleToString(m_rankedFCBF[i][2] + 1,5,0) + " ; "
+                     + m_rankedFCBF[i][3] + "\n");
+      }
+    }
+
+    return BfString.toString();
+  }
+
+
+  /**
+   * Resets stuff to default values
+   */
+  protected void resetOptions () {
+    m_starting = null;
+    m_startRange = new Range();
+    m_attributeList = null;
+    m_attributeMerit = null;
+    m_threshold = -Double.MAX_VALUE;
+  }
+
+
+  private boolean inStarting (int feat) {
+    // omit the class from the evaluation
+    if ((m_hasClass == true) && (feat == m_classIndex)) {
+      return  true;
+    }
+
+    if (m_starting == null) {
+      return  false;
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      if (m_starting[i] == feat) {
+        return  true;
+      }
+    }
+
+    return  false;
+  }
+
+  private void FCBFElimination(double[][]rankedFeatures)
+  throws Exception {
+
+    int i,j;
+
+    m_rankedFCBF = new double[m_attributeList.length][4];
+    int[] attributes = new int[1];
+    int[] classAtrributes = new int[1];
+
+    int numSelectedAttributes = 0;
+
+    int startPoint = 0;
+    double tempSUIJ = 0;
+
+    AttributeSetEvaluator ASEvaluator = (AttributeSetEvaluator)m_asEval;
+
+    for (i = 0; i < rankedFeatures.length; i++) {
+      m_rankedFCBF[i][0] = rankedFeatures[i][0];
+      m_rankedFCBF[i][1] = rankedFeatures[i][1];
+      m_rankedFCBF[i][2] = -1;
+    }
+
+    while (startPoint < rankedFeatures.length)
+    {
+      if (m_rankedFCBF[startPoint][2] != -1)
+      {
+        startPoint++;
+        continue;
+      }
+
+      m_rankedFCBF[startPoint][2] = m_rankedFCBF[startPoint][0];
+      numSelectedAttributes++;
+
+      for (i = startPoint + 1; i < m_attributeList.length; i++)
+      {
+        if (m_rankedFCBF[i][2] != -1)
+        {
+          continue;
+        }
+        attributes[0] = (int) m_rankedFCBF[startPoint][0];
+        classAtrributes[0] = (int) m_rankedFCBF[i][0];
+        tempSUIJ = ASEvaluator.evaluateAttribute(attributes, classAtrributes);
+        if (m_rankedFCBF[i][1] < tempSUIJ || Math.abs(tempSUIJ-m_rankedFCBF[i][1])<1E-8)
+        {
+          m_rankedFCBF[i][2] = m_rankedFCBF[startPoint][0];
+          m_rankedFCBF[i][3] = tempSUIJ;
+        }
+      }
+      startPoint++;
+    }
+
+    m_selectedFeatures = new double[numSelectedAttributes][2];
+
+    for (i = 0, j = 0; i < m_attributeList.length; i++)
+    {
+      if (m_rankedFCBF[i][2] == m_rankedFCBF[i][0])
+      {
+        m_selectedFeatures[j][0] = m_rankedFCBF[i][0];
+        m_selectedFeatures[j][1] = m_rankedFCBF[i][1];
+        j++;
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredAttributeEval.java	(revision 29)
@@ -0,0 +1,396 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilteredAttributeEval.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.filters.Filter;
+import weka.core.Instances;
+import weka.core.Capabilities;
+import weka.core.Capabilities.Capability;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.ArrayList;
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for running an arbitrary attribute evaluator on data that has been passed through an 
+ * arbitrary filter (note: filters that alter the order or number of attributes are not allowed). 
+ * Like the evaluator, the structure of the filter is based exclusively on the training data.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;evaluator specification&gt;
+ *  Full name of base evaluator to use, followed by evaluator options.
+ *  eg: "weka.attributeSelection.InfoGainAttributeEval -M"</pre>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.supervised.instance.SpreadSubsample -M 1"</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class FilteredAttributeEval
+  extends ASEvaluation
+  implements Serializable, AttributeEvaluator, OptionHandler {
+
+  /** For serialization */
+  static final long serialVersionUID = 2111121880778327334L;
+
+  /** Base evaluator */
+  protected AttributeEvaluator m_evaluator = new InfoGainAttributeEval();
+
+  /** Filter */
+  protected Filter m_filter = new weka.filters.supervised.instance.SpreadSubsample();
+
+  /** Filtered instances structure */
+  protected Instances m_filteredInstances;
+
+  public FilteredAttributeEval() {
+    m_filteredInstances = null;
+  }
+
+  /**
+   * Returns default capabilities of the evaluator.
+   *
+   * @return      the capabilities of this evaluator.
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result;
+
+    if (getFilter() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getFilter().getCapabilities();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values()) {
+      result.enableDependency(cap);
+    }
+    
+    return result;
+  }
+
+  /**
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for running an arbitrary attribute evaluator on data that has been passed "
+      + "through an arbitrary filter (note: filters that alter the order or number of "
+      + "attributes are not allowed). Like the evaluator, the structure of the filter "
+      + "is based exclusively on the training data.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+                                    "\tFull name of base evaluator to use, followed by "
+                                    +"evaluator options.\n"
+                                    + "\teg: \"weka.attributeSelection.InfoGainAttributeEval -M\"",
+                                    "W", 1, "-W <evaluator specification>"));
+
+    newVector.addElement(new Option(
+	      "\tFull class name of filter to use, followed\n"
+	      + "\tby filter options.\n"
+	      + "\teg: \"weka.filters.supervised.instance.SpreadSubsample -M 1\"",
+	      "F", 1, "-F <filter specification>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;evaluator specification&gt;
+   *  Full name of base evaluator to use, followed by evaluator options.
+   *  eg: "weka.attributeSelection.InfoGainAttributeEval -M"</pre>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.supervised.instance.SpreadSubsample -M 1"</pre>
+   * 
+   <!-- options-end -->  
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String evaluator = Utils.getOption('W', options);
+    
+    if (evaluator.length() > 0) { 
+      String[] evaluatorSpec = Utils.splitOptions(evaluator);
+      if (evaluatorSpec.length == 0) {
+        throw new IllegalArgumentException("Invalid evaluator specification string");
+      }
+      
+      String evaluatorName = evaluatorSpec[0];
+      evaluatorSpec[0] = "";
+      setAttributeEvaluator((ASEvaluation)Utils.forName(AttributeEvaluator.class,
+                                                        evaluatorName, evaluatorSpec));
+
+    } else {      
+      setAttributeEvaluator(new InfoGainAttributeEval());
+    }
+
+    // Same for filter
+    String filterString = Utils.getOption('F', options);
+    if (filterString.length() > 0) {
+      String [] filterSpec = Utils.splitOptions(filterString);
+      if (filterSpec.length == 0) {
+	throw new IllegalArgumentException("Invalid filter specification string");
+      }
+      String filterName = filterSpec[0];
+      filterSpec[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
+    } else {
+      setFilter(new weka.filters.supervised.instance.SpreadSubsample());
+    }
+  }
+
+  /**
+   * Gets the current settings of the subset evaluator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    ArrayList<String> options = new ArrayList<String>();
+    
+    options.add("-W");
+    options.add(getEvaluatorSpec());
+
+    options.add("-F");
+    options.add(getFilterSpec());
+    
+    return options.toArray(new String[0]);
+  }
+
+  /**
+   * Get the evaluator + options as a string
+   *
+   * @return a String containing the name of the evalautor + any options
+   */
+  protected String getEvaluatorSpec() {
+    AttributeEvaluator a = m_evaluator;
+    if (a instanceof OptionHandler) {
+      return a.getClass().getName() + " "
+        + Utils.joinOptions(((OptionHandler)a).getOptions());
+    }
+    return a.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeEvaluatorTipText() {
+    return "The attribute evaluator to be used.";
+  }
+  
+  /**
+   * Set the attribute evaluator to use
+   *
+   * @param newEvaluator the attribute evaluator to use
+   */
+  public void setAttributeEvaluator(ASEvaluation newEvaluator) {
+    if (!(newEvaluator instanceof AttributeEvaluator)) {
+      throw new IllegalArgumentException("Evaluator must be an AttributeEvaluator!");
+    }
+    m_evaluator = (AttributeEvaluator)newEvaluator;
+  }
+
+  /**
+   * Get the attribute evaluator to use
+   *
+   * @return the attribute evaluator to use
+   */
+  public ASEvaluation getAttributeEvaluator() {
+    return (ASEvaluation)m_evaluator;
+  }
+
+  /**
+   * Get the filter + options as a string
+   *
+   * @return a String containing the name of the filter + any options
+   */
+  protected String getFilterSpec() {
+    Filter c = getFilter();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used.";
+  }
+
+  /**
+   * Set the filter to use
+   *
+   * @param newFilter the filter to use
+   */
+  public void setFilter(Filter newFilter) {
+    m_filter = newFilter;
+  }
+
+  /**
+   * Get the filter to use
+   *
+   * @return the filter to use
+   */
+  public Filter getFilter() {
+    return m_filter;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+
+  /**
+   * Initializes a filtered attribute evaluator.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    // Structure of original
+    Instances original = new Instances(data, 0);
+
+    m_filter.setInputFormat(data);
+    data = Filter.useFilter(data, m_filter);
+
+    // Can only proceed if filter has not altered the order or
+    // number of attributes in the data
+    if (data.numAttributes() != original.numAttributes()) {
+      throw new Exception("Filter must not alter the number of "
+                          +"attributes in the data!");
+    }
+
+    // Check the class index (if set)
+    if (original.classIndex() >= 0) {
+      if (data.classIndex() != original.classIndex()) {
+        throw new Exception("Filter must not change the class attribute!");
+      }
+    }
+
+    // check the order
+    for (int i = 0; i < original.numAttributes(); i++) {
+      if (!data.attribute(i).name().equals(original.attribute(i).name())) {
+        throw new Exception("Filter must not alter the order of the attributes!");
+      }
+    }
+
+    // can the evaluator handle this data?
+    ((ASEvaluation)getAttributeEvaluator()).getCapabilities().testWithFail(data);
+    m_filteredInstances = data.stringFreeStructure();
+    
+    ((ASEvaluation)m_evaluator).buildEvaluator(data);
+  }
+
+  /**
+   * Evaluates an individual attribute by delegating to the base
+   * evaluator.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the merit of the attribute according to the base evaluator
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute(int attribute) throws Exception {
+    return m_evaluator.evaluateAttribute(attribute);
+  }
+
+  /**
+   * Describe the attribute evaluator
+   * @return a description of the attribute evaluator as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    if (m_filteredInstances == null) {
+      text.append("Filtered attribute evaluator has not been built");
+    } else {
+      text.append("Filtered Attribute Evaluator");
+      text.append("\nFilter: " + getFilterSpec());
+      text.append("\nAttribute evaluator: " + getEvaluatorSpec());
+      text.append("\n\nFiltered header:\n");
+      text.append(m_filteredInstances);
+    }
+    text.append("\n");
+    return text.toString();
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new FilteredAttributeEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredSubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredSubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/FilteredSubsetEval.java	(revision 29)
@@ -0,0 +1,397 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilteredSubsetEval.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.filters.Filter;
+import weka.core.Instances;
+import weka.core.Capabilities;
+import weka.core.Capabilities.Capability;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for running an arbitrary subset evaluator on data that has been passed through an arbitrary 
+ * filter (note: filters that alter the order or number of attributes are not allowed). 
+ * Like the evaluator, the structure of the filter is based exclusively on the training data.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;evaluator specification&gt;
+ *  Full name of base evaluator to use, followed by evaluator options.
+ *  eg: "weka.attributeSelection.CfsSubsetEval -L"</pre>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.supervised.instance.SpreadSubsample -M 1"</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class FilteredSubsetEval
+  extends ASEvaluation
+  implements Serializable, SubsetEvaluator, OptionHandler {
+
+  /** For serialization */
+  static final long serialVersionUID = 2111121880778327334L;
+
+  /** Base evaluator */
+  protected SubsetEvaluator m_evaluator = new CfsSubsetEval();
+
+  /** Filter */
+  protected Filter m_filter = new weka.filters.supervised.instance.SpreadSubsample();
+
+  /** Filtered instances structure */
+  protected Instances m_filteredInstances;
+
+  public FilteredSubsetEval() {
+    m_filteredInstances = null;
+  }
+
+  /**
+   * Returns default capabilities of the evaluator.
+   *
+   * @return      the capabilities of this evaluator.
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result;
+
+    if (getFilter() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getFilter().getCapabilities();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values()) {
+      result.enableDependency(cap);
+    } 
+    
+    return result; 
+  }
+
+  /**
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for running an arbitrary subset evaluator on data that has been passed "
+      + "through an arbitrary filter (note: filters that alter the order or number of "
+      + "attributes are not allowed). Like the evaluator, the structure of the filter "
+      + "is based exclusively on the training data.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+                                    "\tFull name of base evaluator to use, followed by "
+                                    +"evaluator options.\n"
+                                    + "\teg: \"weka.attributeSelection.CfsSubsetEval -L\"",
+                                    "W", 1, "-W <evaluator specification>"));
+
+    newVector.addElement(new Option(
+	      "\tFull class name of filter to use, followed\n"
+	      + "\tby filter options.\n"
+	      + "\teg: \"weka.filters.supervised.instance.SpreadSubsample -M 1\"",
+	      "F", 1, "-F <filter specification>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;evaluator specification&gt;
+   *  Full name of base evaluator to use, followed by evaluator options.
+   *  eg: "weka.attributeSelection.CfsSubsetEval -L"</pre>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.supervised.instance.SpreadSubsample -M 1"</pre>
+   * 
+   <!-- options-end -->  
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String evaluator = Utils.getOption('W', options);
+    
+    if (evaluator.length() > 0) { 
+      String[] evaluatorSpec = Utils.splitOptions(evaluator);
+      if (evaluatorSpec.length == 0) {
+        throw new IllegalArgumentException("Invalid evaluator specification string");
+      }
+      
+      String evaluatorName = evaluatorSpec[0];
+      evaluatorSpec[0] = "";
+      setSubsetEvaluator((ASEvaluation)Utils.forName(SubsetEvaluator.class,
+                                                     evaluatorName, evaluatorSpec));
+
+    } else {      
+      setSubsetEvaluator(new CfsSubsetEval());
+    }
+
+    // Same for filter
+    String filterString = Utils.getOption('F', options);
+    if (filterString.length() > 0) {
+      String [] filterSpec = Utils.splitOptions(filterString);
+      if (filterSpec.length == 0) {
+	throw new IllegalArgumentException("Invalid filter specification string");
+      }
+      String filterName = filterSpec[0];
+      filterSpec[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
+    } else {
+      setFilter(new weka.filters.supervised.instance.SpreadSubsample());
+    }
+  }
+
+  /**
+   * Gets the current settings of the subset evaluator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    ArrayList<String> options = new ArrayList<String>();
+    
+    options.add("-W");
+    options.add(getEvaluatorSpec());
+
+    options.add("-F");
+    options.add(getFilterSpec());
+    
+    return options.toArray(new String[0]);
+  }
+
+  /**
+   * Get the evaluator + options as a string
+   *
+   * @return a String containing the name of the evalautor + any options
+   */
+  protected String getEvaluatorSpec() {
+    SubsetEvaluator a = m_evaluator;
+    if (a instanceof OptionHandler) {
+      return a.getClass().getName() + " "
+        + Utils.joinOptions(((OptionHandler)a).getOptions());
+    }
+    return a.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String subsetEvaluatorTipText() {
+    return "The subset evaluator to be used.";
+  }
+  
+  /**
+   * Set the subset evaluator to use
+   *
+   * @param newEvaluator the subset evaluator to use
+   */
+  public void setSubsetEvaluator(ASEvaluation newEvaluator) {
+    if (!(newEvaluator instanceof SubsetEvaluator)) {
+      throw new IllegalArgumentException("Evaluator must be a SubsetEvaluator!");
+    }
+    m_evaluator = (SubsetEvaluator)newEvaluator;
+  }
+
+  /**
+   * Get the subset evaluator to use
+   *
+   * @return the subset evaluator to use
+   */
+  public ASEvaluation getSubsetEvaluator() {
+    return (ASEvaluation)m_evaluator;
+  }
+
+  /**
+   * Get the filter + options as a string
+   *
+   * @return a String containing the name of the filter + any options
+   */
+  protected String getFilterSpec() {
+    Filter c = getFilter();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used.";
+  }
+
+  /**
+   * Set the filter to use
+   *
+   * @param newFilter the filter to use
+   */
+  public void setFilter(Filter newFilter) {
+    m_filter = newFilter;
+  }
+
+  /**
+   * Get the filter to use
+   *
+   * @return the filter to use
+   */
+  public Filter getFilter() {
+    return m_filter;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+
+  /**
+   * Initializes a filtered attribute evaluator.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    // Structure of original
+    Instances original = new Instances(data, 0);
+
+    m_filter.setInputFormat(data);
+    data = Filter.useFilter(data, m_filter);
+
+    // Can only proceed if filter has not altered the order or
+    // number of attributes in the data
+    if (data.numAttributes() != original.numAttributes()) {
+      throw new Exception("Filter must not alter the number of "
+                          +"attributes in the data!");
+    }
+
+    // Check the class index (if set)
+    if (original.classIndex() >= 0) {
+      if (data.classIndex() != original.classIndex()) {
+        throw new Exception("Filter must not change the class attribute!");
+      }
+    }
+
+    // check the order
+    for (int i = 0; i < original.numAttributes(); i++) {
+      if (!data.attribute(i).name().equals(original.attribute(i).name())) {
+        throw new Exception("Filter must not alter the order of the attributes!");
+      }
+    }
+
+    // can the evaluator handle this data?
+    ((ASEvaluation)getSubsetEvaluator()).getCapabilities().testWithFail(data);
+    m_filteredInstances = data.stringFreeStructure();
+    
+    ((ASEvaluation)m_evaluator).buildEvaluator(data);
+  }
+
+  /**
+   * evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the "merit" of the subset
+   * @exception Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset(BitSet subset) throws Exception {
+    return m_evaluator.evaluateSubset(subset);
+  }
+
+  /**
+   * Describe the attribute evaluator
+   * @return a description of the attribute evaluator as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    if (m_filteredInstances == null) {
+      text.append("Filtered attribute evaluator has not been built");
+    } else {
+      text.append("Filtered Attribute Evaluator");
+      text.append("\nFilter: " + getFilterSpec());
+      text.append("\nAttribute evaluator: " + getEvaluatorSpec());
+      text.append("\n\nFiltered header:\n");
+      text.append(m_filteredInstances);
+    }
+    text.append("\n");
+    return text.toString();
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new FilteredSubsetEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/GainRatioAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/GainRatioAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/GainRatioAttributeEval.java	(revision 29)
@@ -0,0 +1,431 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GainRatioAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * GainRatioAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by measuring the gain ratio with respect to the class.<br/>
+ * <br/>
+ * GainR(Class, Attribute) = (H(Class) - H(Class | Attribute)) / H(Attribute).<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5447 $
+ * @see Discretize
+ */
+public class GainRatioAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8504656625598579926L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** The number of classes */
+  private int m_numClasses;
+
+  /** Merge missing values */
+  private boolean m_missing_merge;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "GainRatioAttributeEval :\n\nEvaluates the worth of an attribute "
+      +"by measuring the gain ratio with respect to the class.\n\n"
+      +"GainR(Class, Attribute) = (H(Class) - H(Class | Attribute)) / "
+      +"H(Attribute).\n";
+  }
+
+  /**
+   * Constructor
+   */
+  public GainRatioAttributeEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option("\ttreat missing values as a seperate " 
+				    + "value.", "M", 0, "-M"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+      +"across other values in proportion to their frequency. Otherwise, "
+      +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[1];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a gain ratio attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+    Discretize disTransform = new Discretize();
+    disTransform.setUseBetterEncoding(true);
+    disTransform.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, disTransform);
+    m_numClasses = m_trainInstances.attribute(m_classIndex).numValues();
+  }
+
+
+  /**
+   * reset options to default values
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_missing_merge = true;
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring the gain ratio
+   * of the class given the attribute.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the gain ratio
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+    int i, j, ii, jj;
+    int ni, nj;
+    double sum = 0.0;
+    ni = m_trainInstances.attribute(attribute).numValues() + 1;
+    nj = m_numClasses + 1;
+    double[] sumi, sumj;
+    Instance inst;
+    double temp = 0.0;
+    sumi = new double[ni];
+    sumj = new double[nj];
+    double[][] counts = new double[ni][nj];
+    sumi = new double[ni];
+    sumj = new double[nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumj[j] = 0.0;
+        counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(attribute)) {
+        ii = ni - 1;
+      }
+      else {
+        ii = (int)inst.value(attribute);
+      }
+
+      if (inst.isMissing(m_classIndex)) {
+        jj = nj - 1;
+      }
+      else {
+        jj = (int)inst.value(m_classIndex);
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumi[i] += counts[i][j];
+        sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+        sumj[j] += counts[i][j];
+      }
+    }
+
+    // distribute missing counts
+    if (m_missing_merge && 
+	(sumi[ni-1] < m_numInstances) && 
+	(sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+        System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = (sumi[ni - 1] + sumj[nj - 1] - 
+			      counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) {
+        for (j = 0; j < nj - 1; j++) {
+          if (counts[ni - 1][j] > 0.0) {
+            for (i = 0; i < ni - 1; i++) {
+              temp = ((i_copy[i]/(sum - i_copy[ni - 1]))*counts[ni - 1][j]);
+              counts[i][j] += temp;
+              sumi[i] += temp;
+            }
+
+            counts[ni - 1][j] = 0.0;
+          }
+        }
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+        for (i = 0; i < ni - 1; i++) {
+          if (counts[i][nj - 1] > 0.0) {
+            for (j = 0; j < nj - 1; j++) {
+              temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+              counts[i][j] += temp;
+              sumj[j] += temp;
+            }
+
+            counts[i][nj - 1] = 0.0;
+          }
+        }
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0  && total_missing != sum) {
+        for (i = 0; i < ni - 1; i++) {
+          for (j = 0; j < nj - 1; j++) {
+            temp = (counts_copy[i][j]/(sum - total_missing)) * 
+	      counts_copy[ni - 1][nj - 1];
+            counts[i][j] += temp;
+            sumi[i] += temp;
+            sumj[j] += temp;
+          }
+        }
+
+        counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    return  ContingencyTables.gainRatio(counts);
+  }
+
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tGain Ratio evaluator has not been built");
+    }
+    else {
+      text.append("\tGain Ratio feature evaluator");
+
+      if (!m_missing_merge) {
+        text.append("\n\tMissing values treated as seperate");
+      }
+    }
+
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args the options
+   * -t training file
+   */
+  public static void main (String[] args) {
+    runEvaluator(new GainRatioAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/GeneticSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/GeneticSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/GeneticSearch.java	(revision 29)
@@ -0,0 +1,1306 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GeneticSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * GeneticSearch:<br/>
+ * <br/>
+ * Performs a search using the simple genetic algorithm described in Goldberg (1989).<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * David E. Goldberg (1989). Genetic algorithms in search, optimization and machine learning. Addison-Wesley.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Goldberg1989,
+ *    author = {David E. Goldberg},
+ *    publisher = {Addison-Wesley},
+ *    title = {Genetic algorithms in search, optimization and machine learning},
+ *    year = {1989},
+ *    ISBN = {0201157675}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.If supplied, the starting set becomes
+ *  one member of the initial random
+ *  population.</pre>
+ * 
+ * <pre> -Z &lt;population size&gt;
+ *  Set the size of the population (even number).
+ *  (default = 20).</pre>
+ * 
+ * <pre> -G &lt;number of generations&gt;
+ *  Set the number of generations.
+ *  (default = 20)</pre>
+ * 
+ * <pre> -C &lt;probability of crossover&gt;
+ *  Set the probability of crossover.
+ *  (default = 0.6)</pre>
+ * 
+ * <pre> -M &lt;probability of mutation&gt;
+ *  Set the probability of mutation.
+ *  (default = 0.033)</pre>
+ * 
+ * <pre> -R &lt;report frequency&gt;
+ *  Set frequency of generation reports.
+ *  e.g, setting the value to 5 will 
+ *  report every 5th generation
+ *  (default = number of generations)</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Set the random number seed.
+ *  (default = 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5286 $
+ */
+public class GeneticSearch 
+  extends ASSearch 
+  implements StartSetHandler, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -1618264232838472679L;
+  
+  /** 
+   * holds a starting set as an array of attributes. Becomes one member of the
+   * initial random population
+   */
+  private int[] m_starting;
+
+  /** holds the start set for the search as a Range */
+  private Range m_startRange;
+  
+ /** does the data have a class */
+  private boolean m_hasClass;
+ 
+  /** holds the class index */
+  private int m_classIndex;
+ 
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** the current population */
+  private GABitSet [] m_population;
+
+  /** the number of individual solutions */
+  private int m_popSize;
+
+  /** the best population member found during the search */
+  private GABitSet m_best;
+
+  /** the number of features in the best population member */
+  private int m_bestFeatureCount;
+
+  /** the number of entries to cache for lookup */
+  private int m_lookupTableSize;
+
+  /** the lookup table */
+  private Hashtable m_lookupTable;
+
+  /** random number generation */
+  private Random m_random;
+
+  /** seed for random number generation */
+  private int m_seed;
+
+  /** the probability of crossover occuring */
+  private double m_pCrossover;
+
+  /** the probability of mutation occuring */
+  private double m_pMutation;
+
+  /** sum of the current population fitness */
+  private double m_sumFitness;
+
+  private double m_maxFitness;
+  private double m_minFitness;
+  private double m_avgFitness;
+
+  /** the maximum number of generations to evaluate */
+  private int m_maxGenerations;
+
+  /** how often reports are generated */
+  private int m_reportFrequency;
+
+  /** holds the generation reports */
+  private StringBuffer m_generationReports;
+
+  // Inner class
+  /**
+   * A bitset for the genetic algorithm
+   */
+  protected class GABitSet 
+    implements Cloneable, Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2930607837482622224L;
+    
+    /** the bitset */
+    private BitSet m_chromosome;
+
+    /** holds raw merit */
+    private double m_objective = -Double.MAX_VALUE;
+    
+    /** the fitness */
+    private double m_fitness;
+    
+    /**
+     * Constructor
+     */
+    public GABitSet () {
+      m_chromosome = new BitSet();
+    }
+
+    /**
+     * makes a copy of this GABitSet
+     * @return a copy of the object
+     * @throws CloneNotSupportedException if something goes wrong
+     */
+    public Object clone() throws CloneNotSupportedException {
+      GABitSet temp = new GABitSet();
+      
+      temp.setObjective(this.getObjective());
+      temp.setFitness(this.getFitness());
+      temp.setChromosome((BitSet)(this.m_chromosome.clone()));
+      return temp;
+      //return super.clone();
+    }
+
+    /**
+     * sets the objective merit value
+     * @param objective the objective value of this population member
+     */
+    public void setObjective(double objective) {
+      m_objective = objective;
+    }
+      
+    /**
+     * gets the objective merit
+     * @return the objective merit of this population member
+     */
+    public double getObjective() {
+      return m_objective;
+    }
+
+    /**
+     * sets the scaled fitness
+     * @param fitness the scaled fitness of this population member
+     */
+    public void setFitness(double fitness) {
+      m_fitness = fitness;
+    }
+
+    /**
+     * gets the scaled fitness
+     * @return the scaled fitness of this population member
+     */
+    public double getFitness() {
+      return m_fitness;
+    }
+
+    /**
+     * get the chromosome
+     * @return the chromosome of this population member
+     */
+    public BitSet getChromosome() {
+      return m_chromosome;
+    }
+
+    /**
+     * set the chromosome
+     * @param c the chromosome to be set for this population member
+     */
+    public void setChromosome(BitSet c) {
+      m_chromosome = c;
+    }
+
+    /**
+     * unset a bit in the chromosome
+     * @param bit the bit to be cleared
+     */
+    public void clear(int bit) {
+      m_chromosome.clear(bit);
+    }
+
+    /**
+     * set a bit in the chromosome
+     * @param bit the bit to be set
+     */
+    public void set(int bit) {
+      m_chromosome.set(bit);
+    }
+
+    /**
+     * get the value of a bit in the chromosome
+     * @param bit the bit to query
+     * @return the value of the bit
+     */
+    public boolean get(int bit) {
+      return m_chromosome.get(bit);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5286 $");
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option("\tSpecify a starting set of attributes." 
+                                    + "\n\tEg. 1,3,5-7."
+                                    +"If supplied, the starting set becomes"
+                                    +"\n\tone member of the initial random"
+                                    +"\n\tpopulation."
+                                    ,"P",1
+                                    , "-P <start set>"));
+    newVector.addElement(new Option("\tSet the size of the population (even number)."
+                                    +"\n\t(default = 20)."
+                                    , "Z", 1
+                                    , "-Z <population size>"));
+    newVector.addElement(new Option("\tSet the number of generations."
+                                    +"\n\t(default = 20)" 
+                                    , "G", 1, "-G <number of generations>"));
+    newVector.addElement(new Option("\tSet the probability of crossover."
+                                    +"\n\t(default = 0.6)" 
+                                    , "C", 1, "-C <probability of"
+                                    +" crossover>"));    
+    newVector.addElement(new Option("\tSet the probability of mutation."
+                                    +"\n\t(default = 0.033)" 
+                                    , "M", 1, "-M <probability of mutation>"));
+
+    newVector.addElement(new Option("\tSet frequency of generation reports."
+                                    +"\n\te.g, setting the value to 5 will "
+                                    +"\n\treport every 5th generation"
+                                    +"\n\t(default = number of generations)" 
+                                    , "R", 1, "-R <report frequency>"));
+    newVector.addElement(new Option("\tSet the random number seed."
+                                    +"\n\t(default = 1)" 
+                                    , "S", 1, "-S <seed>"));
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.If supplied, the starting set becomes
+   *  one member of the initial random
+   *  population.</pre>
+   * 
+   * <pre> -Z &lt;population size&gt;
+   *  Set the size of the population (even number).
+   *  (default = 20).</pre>
+   * 
+   * <pre> -G &lt;number of generations&gt;
+   *  Set the number of generations.
+   *  (default = 20)</pre>
+   * 
+   * <pre> -C &lt;probability of crossover&gt;
+   *  Set the probability of crossover.
+   *  (default = 0.6)</pre>
+   * 
+   * <pre> -M &lt;probability of mutation&gt;
+   *  Set the probability of mutation.
+   *  (default = 0.033)</pre>
+   * 
+   * <pre> -R &lt;report frequency&gt;
+   *  Set frequency of generation reports.
+   *  e.g, setting the value to 5 will 
+   *  report every 5th generation
+   *  (default = number of generations)</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Set the random number seed.
+   *  (default = 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('Z', options);
+    if (optionString.length() != 0) {
+      setPopulationSize(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('G', options);
+    if (optionString.length() != 0) {
+      setMaxGenerations(Integer.parseInt(optionString));
+      setReportFrequency(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('C', options);
+    if (optionString.length() != 0) {
+      setCrossoverProb((new Double(optionString)).doubleValue());
+    }
+
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMutationProb((new Double(optionString)).doubleValue());
+    }
+
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setReportFrequency(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+  }
+
+  /**
+   * Gets the current settings of ReliefFAttributeEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[14];
+    int current = 0;
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = ""+startSetToString();
+    }
+    options[current++] = "-Z";
+    options[current++] = "" + getPopulationSize();
+    options[current++] = "-G";
+    options[current++] = "" + getMaxGenerations();
+    options[current++] = "-C";
+    options[current++] = "" + getCrossoverProb();
+    options[current++] = "-M";
+    options[current++] = "" + getMutationProb();
+    options[current++] = "-R";
+    options[current++] = "" + getReportFrequency();
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return  options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Set a start point for the search. This is specified as a comma "
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17. The start set becomes one of the population "
+      +"members of the initial population.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Set the random seed.";
+  }
+
+  /**
+   * set the seed for random number generation
+   * @param s seed value
+   */
+  public void setSeed(int s) {
+    m_seed = s;
+  }
+
+  /**
+   * get the value of the random number generator's seed
+   * @return the seed for random number generation
+   */
+  public int getSeed() {
+    return m_seed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String reportFrequencyTipText() {
+    return "Set how frequently reports are generated. Default is equal to "
+      +"the number of generations meaning that a report will be printed for "
+      +"initial and final generations. Setting the value to 5 will result in "
+      +"a report being printed every 5 generations.";
+  }
+
+  /**
+   * set how often reports are generated
+   * @param f generate reports every f generations
+   */
+  public void setReportFrequency(int f) {
+    m_reportFrequency = f;
+  }
+
+  /**
+   * get how often repports are generated
+   * @return how often reports are generated
+   */
+  public int getReportFrequency() {
+    return m_reportFrequency;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String mutationProbTipText() {
+    return "Set the probability of mutation occuring.";
+  }
+
+  /**
+   * set the probability of mutation
+   * @param m the probability for mutation occuring
+   */
+  public void setMutationProb(double m) {
+    m_pMutation = m;
+  }
+
+  /**
+   * get the probability of mutation
+   * @return the probability of mutation occuring
+   */
+  public double getMutationProb() {
+    return m_pMutation;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String crossoverProbTipText() {
+    return "Set the probability of crossover. This is the probability that "
+      +"two population members will exchange genetic material."; 
+  }
+
+  /**
+   * set the probability of crossover
+   * @param c the probability that two population members will exchange
+   * genetic material
+   */
+  public void setCrossoverProb(double c) {
+    m_pCrossover = c;
+  }
+
+  /**
+   * get the probability of crossover
+   * @return the probability of crossover
+   */
+  public double getCrossoverProb() {
+    return m_pCrossover;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxGenerationsTipText() {
+    return "Set the number of generations to evaluate.";
+  }
+
+  /**
+   * set the number of generations to evaluate
+   * @param m the number of generations
+   */
+  public void setMaxGenerations(int m) {
+    m_maxGenerations = m;
+  }
+
+  /**
+   * get the number of generations
+   * @return the maximum number of generations
+   */
+  public int getMaxGenerations() {
+    return m_maxGenerations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String populationSizeTipText() {
+    return "Set the population size (even number), this is the number of individuals "
+      +"(attribute sets) in the population.";
+  }
+
+  /**
+   * set the population size
+   * @param p the size of the population
+   */
+  public void setPopulationSize(int p) {
+    if (p % 2 == 0)
+      m_popSize = p;
+    else
+      System.err.println("Population size needs to be an even number!");
+  }
+
+  /**
+   * get the size of the population
+   * @return the population size
+   */
+  public int getPopulationSize() {
+    return m_popSize;
+  }
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "GeneticSearch:\n\nPerforms a search using the simple genetic "
+      + "algorithm described in Goldberg (1989).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "David E. Goldberg");
+    result.setValue(Field.YEAR, "1989");
+    result.setValue(Field.TITLE, "Genetic algorithms in search, optimization and machine learning");
+    result.setValue(Field.ISBN, "0201157675");
+    result.setValue(Field.PUBLISHER, "Addison-Wesley");
+    
+    return result;
+  }
+
+  /**
+   * Constructor. Make a new GeneticSearch object
+   */
+  public GeneticSearch() {
+    resetOptions();
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+    
+    if (m_starting == null) {
+      return getStartSet();
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+      
+      if ((m_hasClass == false) || 
+          (m_hasClass == true && i != m_classIndex)) {
+        FString.append((m_starting[i] + 1));
+        didPrint = true;
+      }
+      
+      if (i == (m_starting.length - 1)) {
+        FString.append("");
+      }
+      else {
+        if (didPrint) {
+          FString.append(",");
+          }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * returns a description of the search
+   * @return a description of the search as a String
+   */
+  public String toString() {
+    StringBuffer GAString = new StringBuffer();
+    GAString.append("\tGenetic search.\n\tStart set: ");
+
+    if (m_starting == null) {
+      GAString.append("no attributes\n");
+    }
+    else {
+      GAString.append(startSetToString()+"\n");
+    }
+    GAString.append("\tPopulation size: "+m_popSize);
+    GAString.append("\n\tNumber of generations: "+m_maxGenerations);
+    GAString.append("\n\tProbability of crossover: "
+                +Utils.doubleToString(m_pCrossover,6,3));
+    GAString.append("\n\tProbability of mutation: "
+                +Utils.doubleToString(m_pMutation,6,3));
+    GAString.append("\n\tReport frequency: "+m_reportFrequency);
+    GAString.append("\n\tRandom number seed: "+m_seed+"\n");
+    GAString.append(m_generationReports.toString());
+    return GAString.toString();
+  }
+
+  /**
+   * Searches the attribute subset space using a genetic algorithm.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+   public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+
+     m_best = null;
+     m_generationReports = new StringBuffer();
+
+     if (!(ASEval instanceof SubsetEvaluator)) {
+       throw  new Exception(ASEval.getClass().getName() 
+                            + " is not a " 
+                            + "Subset evaluator!");
+     }
+     
+    if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+      m_hasClass = false;
+    }
+    else {
+      m_hasClass = true;
+      m_classIndex = data.classIndex();
+    }
+
+    SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
+    m_numAttribs = data.numAttributes();
+
+    m_startRange.setUpper(m_numAttribs-1);
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+
+    // initial random population
+    m_lookupTable = new Hashtable(m_lookupTableSize);
+    m_random = new Random(m_seed);
+    m_population = new GABitSet [m_popSize];
+
+    // set up random initial population
+    initPopulation();
+    evaluatePopulation(ASEvaluator);
+    populationStatistics();
+    scalePopulation();
+    checkBest();
+    m_generationReports.append(populationReport(0));
+
+    boolean converged;
+    for (int i=1;i<=m_maxGenerations;i++) {
+      generation();
+      evaluatePopulation(ASEvaluator);
+      populationStatistics();
+      scalePopulation();
+      // find the best pop member and check for convergence
+      converged = checkBest();
+
+      if ((i == m_maxGenerations) || 
+          ((i % m_reportFrequency) == 0) ||
+          (converged == true)) {
+        m_generationReports.append(populationReport(i));
+        if (converged == true) {
+          break;
+        }
+      }
+    }
+    return attributeList(m_best.getChromosome());
+   }
+
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  private int[] attributeList (BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+
+  /**
+   * checks to see if any population members in the current
+   * population are better than the best found so far. Also checks
+   * to see if the search has converged---that is there is no difference
+   * in fitness between the best and worse population member
+   * @return true is the search has converged
+   * @throws Exception if something goes wrong
+   */
+  private boolean checkBest() throws Exception {
+    int i,count,lowestCount = m_numAttribs;
+    double b = -Double.MAX_VALUE;
+    GABitSet localbest = null;
+    BitSet temp;
+    boolean converged = false;
+    int oldcount = Integer.MAX_VALUE;
+
+    if (m_maxFitness - m_minFitness > 0) {
+      // find the best in this population
+      for (i=0;i<m_popSize;i++) {
+        if (m_population[i].getObjective() > b) {
+          b = m_population[i].getObjective();
+          localbest = m_population[i];
+          oldcount = countFeatures(localbest.getChromosome());
+        } else if (Utils.eq(m_population[i].getObjective(), b)) {
+          // see if it contains fewer features
+          count = countFeatures(m_population[i].getChromosome());
+          if (count < oldcount) {
+            b = m_population[i].getObjective();
+            localbest = m_population[i];
+            oldcount = count;
+          }
+        }
+      }
+    } else {
+      // look for the smallest subset
+      for (i=0;i<m_popSize;i++) {
+        temp = m_population[i].getChromosome();
+        count = countFeatures(temp);;
+
+        if (count < lowestCount) {
+          lowestCount = count;
+          localbest = m_population[i];
+          b = localbest.getObjective();
+        }
+      }
+      converged = true;
+    }
+
+    // count the number of features in localbest
+    count = 0;
+    temp = localbest.getChromosome();
+    count = countFeatures(temp);
+
+    // compare to the best found so far
+    if (m_best == null) {
+      m_best = (GABitSet)localbest.clone();
+      m_bestFeatureCount = count;
+    } else if (b > m_best.getObjective()) {
+      m_best = (GABitSet)localbest.clone();
+      m_bestFeatureCount = count;
+    } else if (Utils.eq(m_best.getObjective(), b)) {
+      // see if the localbest has fewer features than the best so far
+      if (count < m_bestFeatureCount) {
+        m_best = (GABitSet)localbest.clone();
+        m_bestFeatureCount = count;
+      }
+    }
+    return converged;
+  }
+
+  /**
+   * counts the number of features in a subset
+   * @param featureSet the feature set for which to count the features
+   * @return the number of features in the subset
+   */
+  private int countFeatures(BitSet featureSet) {
+    int count = 0;
+    for (int i=0;i<m_numAttribs;i++) {
+      if (featureSet.get(i)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  /**
+   * performs a single generation---selection, crossover, and mutation
+   * @throws Exception if an error occurs
+   */
+  private void generation () throws Exception {
+    int i,j=0;
+    double best_fit = -Double.MAX_VALUE;
+    int old_count = 0;
+    int count;
+    GABitSet [] newPop = new GABitSet [m_popSize];
+    int parent1,parent2;
+
+    /** first ensure that the population best is propogated into the new
+        generation */
+    for (i=0;i<m_popSize;i++) {
+      if (m_population[i].getFitness() > best_fit) {
+        j = i;
+        best_fit = m_population[i].getFitness();
+        old_count = countFeatures(m_population[i].getChromosome());
+      } else if (Utils.eq(m_population[i].getFitness(), best_fit)) {
+        count = countFeatures(m_population[i].getChromosome());
+        if (count < old_count) {
+          j = i;
+          best_fit = m_population[i].getFitness();
+          old_count = count;
+        }
+      }
+    }
+    newPop[0] = (GABitSet)(m_population[j].clone());
+    newPop[1] = newPop[0];
+
+    for (j=2;j<m_popSize;j+=2) {
+      parent1 = select();
+      parent2 = select();
+      newPop[j] = (GABitSet)(m_population[parent1].clone());
+      newPop[j+1] = (GABitSet)(m_population[parent2].clone());
+      // if parents are equal mutate one bit
+      if (parent1 == parent2) {
+        int r;
+        if (m_hasClass) {
+          while ((r = (Math.abs(m_random.nextInt()) % m_numAttribs)) == m_classIndex);
+        }
+        else {
+          r = m_random.nextInt() % m_numAttribs;
+        }
+        
+        if (newPop[j].get(r)) {
+          newPop[j].clear(r);
+        }
+        else {
+          newPop[j].set(r);
+        }
+      } else {
+        // crossover
+        double r = m_random.nextDouble();
+        if (m_numAttribs >= 3) {
+          if (r < m_pCrossover) {
+            // cross point
+            int cp = Math.abs(m_random.nextInt());
+            
+            cp %= (m_numAttribs-2);
+            cp ++;
+            
+            for (i=0;i<cp;i++) {
+              if (m_population[parent1].get(i)) {
+                newPop[j+1].set(i);
+              }
+              else {
+                newPop[j+1].clear(i);
+              }
+              if (m_population[parent2].get(i)) {
+                newPop[j].set(i);
+              }
+              else {
+                newPop[j].clear(i);
+              }
+            }
+          }
+        }
+
+        // mutate
+        for (int k=0;k<2;k++) {
+          for (i=0;i<m_numAttribs;i++) {
+            r = m_random.nextDouble();
+            if (r < m_pMutation) {
+              if (m_hasClass && (i == m_classIndex)) {
+                // ignore class attribute
+              }
+              else {
+                if (newPop[j+k].get(i)) {
+                  newPop[j+k].clear(i);
+                }
+                else {
+                  newPop[j+k].set(i);
+                }
+              }
+            }
+          }
+        }
+                  
+      }
+    }
+
+    m_population = newPop;
+  }
+
+  /**
+   * selects a population member to be considered for crossover
+   * @return the index of the selected population member
+   */
+  private int select() {
+    int i;
+    double r,partsum;
+
+    partsum = 0;
+    r = m_random.nextDouble() * m_sumFitness;
+    for (i=0;i<m_popSize;i++) {
+      partsum += m_population[i].getFitness();
+      if (partsum >= r) {
+        break;
+      }
+    }
+    
+    // if none was found, take first
+    if (i == m_popSize)
+      i = 0;
+    
+    return i;
+  }
+
+  /**
+   * evaluates an entire population. Population members are looked up in
+   * a hash table and if they are not found then they are evaluated using
+   * ASEvaluator.
+   * @param ASEvaluator the subset evaluator to use for evaluating population
+   * members
+   * @throws Exception if something goes wrong during evaluation
+   */
+  private void evaluatePopulation (SubsetEvaluator ASEvaluator)
+    throws Exception {
+    int i;
+    double merit;
+
+    for (i=0;i<m_popSize;i++) {
+      // if its not in the lookup table then evaluate and insert
+      if (m_lookupTable.containsKey(m_population[i]
+                                    .getChromosome()) == false) {
+        merit = ASEvaluator.evaluateSubset(m_population[i].getChromosome());
+        m_population[i].setObjective(merit);
+        m_lookupTable.put(m_population[i].getChromosome(),m_population[i]);
+      } else {
+        GABitSet temp = (GABitSet)m_lookupTable.
+          get(m_population[i].getChromosome());
+        m_population[i].setObjective(temp.getObjective());
+      }
+    }
+  }
+
+  /**
+   * creates random population members for the initial population. Also
+   * sets the first population member to be a start set (if any) 
+   * provided by the user
+   * @throws Exception if the population can't be created
+   */
+  private void initPopulation () throws Exception {
+    int i,j,bit;
+    int num_bits;
+    boolean ok;
+    int start = 0;
+
+    // add the start set as the first population member (if specified)
+    if (m_starting != null) {
+      m_population[0] = new GABitSet();
+      for (i=0;i<m_starting.length;i++) {
+        if ((m_starting[i]) != m_classIndex) {
+          m_population[0].set(m_starting[i]);
+        }
+      }
+      start = 1;
+    }
+
+    for (i=start;i<m_popSize;i++) {
+      m_population[i] = new GABitSet();
+      
+      num_bits = m_random.nextInt();
+      num_bits = num_bits % m_numAttribs-1;
+      if (num_bits < 0) {
+        num_bits *= -1;
+      }
+      if (num_bits == 0) {
+        num_bits = 1;
+      }
+
+      for (j=0;j<num_bits;j++) {
+        ok = false;
+        do {
+          bit = m_random.nextInt();
+          if (bit < 0) {
+            bit *= -1;
+          }
+          bit = bit % m_numAttribs;
+          if (m_hasClass) {
+            if (bit != m_classIndex) {
+              ok = true;
+            }
+          }
+          else {
+            ok = true;
+          }
+        } while (!ok);
+        
+        if (bit > m_numAttribs) {
+          throw new Exception("Problem in population init");
+        }
+        m_population[i].set(bit);
+      }
+    }
+  }
+
+  /**
+   * calculates summary statistics for the current population
+   */
+  private void populationStatistics() {
+    int i;
+    
+    m_sumFitness = m_minFitness = m_maxFitness = 
+      m_population[0].getObjective();
+
+    for (i=1;i<m_popSize;i++) {
+      m_sumFitness += m_population[i].getObjective();
+      if (m_population[i].getObjective() > m_maxFitness) {
+        m_maxFitness = m_population[i].getObjective();
+      }
+      else if (m_population[i].getObjective() < m_minFitness) {
+        m_minFitness = m_population[i].getObjective();
+      }
+    }
+    m_avgFitness = (m_sumFitness / m_popSize);
+  }
+
+  /**
+   * scales the raw (objective) merit of the population members
+   */
+  private void scalePopulation() {
+    int j;
+    double a = 0;
+    double b = 0;
+    double fmultiple = 2.0;
+    double delta;
+    
+    // prescale
+    if (m_minFitness > ((fmultiple * m_avgFitness - m_maxFitness) / 
+                        (fmultiple - 1.0))) {
+      delta = m_maxFitness - m_avgFitness;
+      a = ((fmultiple - 1.0) * m_avgFitness / delta);
+      b = m_avgFitness * (m_maxFitness - fmultiple * m_avgFitness) / delta;
+    }
+    else {
+      delta = m_avgFitness - m_minFitness;
+      a = m_avgFitness / delta;
+      b = -m_minFitness * m_avgFitness / delta;
+    }
+      
+    // scalepop
+    m_sumFitness = 0;
+    for (j=0;j<m_popSize;j++) {
+      if (a == Double.POSITIVE_INFINITY || a == Double.NEGATIVE_INFINITY ||
+          b == Double.POSITIVE_INFINITY || b == Double.NEGATIVE_INFINITY) {
+        m_population[j].setFitness(m_population[j].getObjective());
+      } else {
+        m_population[j].
+          setFitness(Math.abs((a * m_population[j].getObjective() + b)));
+      }
+      m_sumFitness += m_population[j].getFitness();
+    }
+  }
+  
+  /**
+   * generates a report on the current population
+   * @return a report as a String
+   */
+  private String populationReport (int genNum) {
+    int i;
+    StringBuffer temp = new StringBuffer();
+
+    if (genNum == 0) {
+      temp.append("\nInitial population\n");
+    }
+    else {
+      temp.append("\nGeneration: "+genNum+"\n");
+    }
+    temp.append("merit   \tscaled  \tsubset\n");
+    
+    for (i=0;i<m_popSize;i++) {
+      temp.append(Utils.doubleToString(Math.
+                                       abs(m_population[i].getObjective()),
+                                       8,5)
+                  +"\t"
+                  +Utils.doubleToString(m_population[i].getFitness(),
+                                        8,5)
+                  +"\t");
+
+      temp.append(printPopMember(m_population[i].getChromosome())+"\n");
+    }
+    return temp.toString();
+  }
+
+  /**
+   * prints a population member as a series of attribute numbers
+   * @param temp the chromosome of a population member
+   * @return a population member as a String of attribute numbers
+   */
+  private String printPopMember(BitSet temp) {
+    StringBuffer text = new StringBuffer();
+
+    for (int j=0;j<m_numAttribs;j++) {
+      if (temp.get(j)) {
+        text.append((j+1)+" ");
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * prints a population member's chromosome
+   * @param temp the chromosome of a population member
+   * @return a population member's chromosome as a String
+   */
+  private String printPopChrom(BitSet temp) {
+    StringBuffer text = new StringBuffer();
+
+    for (int j=0;j<m_numAttribs;j++) {
+      if (temp.get(j)) {
+        text.append("1");
+      } else {
+        text.append("0");
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * reset to default values for options
+   */
+  private void resetOptions () {
+    m_population = null;
+    m_popSize = 20;
+    m_lookupTableSize = 1001;
+    m_pCrossover = 0.6;
+    m_pMutation = 0.033;
+    m_maxGenerations = 20;
+    m_reportFrequency = m_maxGenerations;
+    m_starting = null;
+    m_startRange = new Range();
+    m_seed = 1;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5286 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/GreedyStepwise.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/GreedyStepwise.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/GreedyStepwise.java	(revision 29)
@@ -0,0 +1,813 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GreedyStepwise.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * GreedyStepwise :<br/>
+ * <br/>
+ * Performs a greedy forward or backward search through the space of attribute subsets. May start with no/all attributes or from an arbitrary point in the space. Stops when the addition/deletion of any remaining attributes results in a decrease in evaluation. Can also produce a ranked list of attributes by traversing the space from one side to the other and recording the order that attributes are selected.<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C
+ *  Use conservative forward search</pre>
+ * 
+ * <pre> -B
+ *  Use a backward search instead of a
+ *  forward one.</pre>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.</pre>
+ * 
+ * <pre> -R
+ *  Produce a ranked list of attributes.</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Specify a theshold by which attributes
+ *  may be discarded from the ranking.
+ *  Use in conjuction with -R</pre>
+ * 
+ * <pre> -N &lt;num to select&gt;
+ *  Specify number of attributes to select</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.10 $
+ */
+public class GreedyStepwise 
+  extends ASSearch 
+  implements RankedOutputSearch, StartSetHandler, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6312951970168325471L;
+
+  /** does the data have a class */
+  protected boolean m_hasClass;
+ 
+  /** holds the class index */
+  protected int m_classIndex;
+ 
+  /** number of attributes in the data */
+  protected int m_numAttribs;
+
+  /** true if the user has requested a ranked list of attributes */
+  protected boolean m_rankingRequested;
+
+  /** 
+   * go from one side of the search space to the other in order to generate
+   * a ranking
+   */
+  protected boolean m_doRank;
+
+  /** used to indicate whether or not ranking has been performed */
+  protected boolean m_doneRanking;
+
+  /**
+   * A threshold by which to discard attributes---used by the
+   * AttributeSelection module
+   */
+  protected double m_threshold;
+
+  /** The number of attributes to select. -1 indicates that all attributes
+      are to be retained. Has precedence over m_threshold */
+  protected int m_numToSelect = -1;
+
+  protected int m_calculatedNumToSelect;
+
+  /** the merit of the best subset found */
+  protected double m_bestMerit;
+
+  /** a ranked list of attribute indexes */
+  protected double [][] m_rankedAtts;
+  protected int m_rankedSoFar;
+
+  /** the best subset found */
+  protected BitSet m_best_group;
+  protected ASEvaluation m_ASEval;
+
+  protected Instances m_Instances;
+
+  /** holds the start set for the search as a Range */
+  protected Range m_startRange;
+
+  /** holds an array of starting attributes */
+  protected int [] m_starting;
+
+  /** Use a backwards search instead of a forwards one */
+  protected boolean m_backward = false;
+
+  /** If set then attributes will continue to be added during a forward
+      search as long as the merit does not degrade */
+  protected boolean m_conservativeSelection = false;
+
+  /**
+   * Constructor
+   */
+  public GreedyStepwise () {
+    m_threshold = -Double.MAX_VALUE;
+    m_doneRanking = false;
+    m_startRange = new Range();
+    m_starting = null;
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "GreedyStepwise :\n\nPerforms a greedy forward or backward search "
+      +"through "
+      +"the space of attribute subsets. May start with no/all attributes or from "
+      +"an arbitrary point in the space. Stops when the addition/deletion of any "
+      +"remaining attributes results in a decrease in evaluation. "
+      +"Can also produce a ranked list of "
+      +"attributes by traversing the space from one side to the other and "
+      +"recording the order that attributes are selected.\n";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchBackwardsTipText() {
+    return "Search backwards rather than forwards.";
+  }
+
+  /**
+   * Set whether to search backwards instead of forwards
+   *
+   * @param back true to search backwards
+   */
+  public void setSearchBackwards(boolean back) {
+    m_backward = back;
+    if (m_backward) {
+      setGenerateRanking(false);
+    }
+  }
+
+  /**
+   * Get whether to search backwards
+   *
+   * @return true if the search will proceed backwards
+   */
+  public boolean getSearchBackwards() {
+    return m_backward;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Set threshold by which attributes can be discarded. Default value "
+      + "results in no attributes being discarded. Use in conjunction with "
+      + "generateRanking";
+  }
+
+  /**
+   * Set the threshold by which the AttributeSelection module can discard
+   * attributes.
+   * @param threshold the threshold.
+   */
+  public void setThreshold(double threshold) {
+    m_threshold = threshold;
+  }
+
+  /**
+   * Returns the threshold so that the AttributeSelection module can
+   * discard attributes from the ranking.
+   */
+  public double getThreshold() {
+    return m_threshold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numToSelectTipText() {
+    return "Specify the number of attributes to retain. The default value "
+      +"(-1) indicates that all attributes are to be retained. Use either "
+      +"this option or a threshold to reduce the attribute set.";
+  }
+
+  /**
+   * Specify the number of attributes to select from the ranked list
+   * (if generating a ranking). -1
+   * indicates that all attributes are to be retained.
+   * @param n the number of attributes to retain
+   */
+  public void setNumToSelect(int n) {
+    m_numToSelect = n;
+  }
+
+  /**
+   * Gets the number of attributes to be retained.
+   * @return the number of attributes to retain
+   */
+  public int getNumToSelect() {
+    return m_numToSelect;
+  }
+
+  /**
+   * Gets the calculated number of attributes to retain. This is the
+   * actual number of attributes to retain. This is the same as
+   * getNumToSelect if the user specifies a number which is not less
+   * than zero. Otherwise it should be the number of attributes in the
+   * (potentially transformed) data.
+   */
+  public int getCalculatedNumToSelect() {
+    if (m_numToSelect >= 0) {
+      m_calculatedNumToSelect = m_numToSelect;
+    }
+    return m_calculatedNumToSelect;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String generateRankingTipText() {
+    return "Set to true if a ranked list is required.";
+  }
+  
+  /**
+   * Records whether the user has requested a ranked list of attributes.
+   * @param doRank true if ranking is requested
+   */
+  public void setGenerateRanking(boolean doRank) {
+    m_rankingRequested = doRank;
+  }
+
+  /**
+   * Gets whether ranking has been requested. This is used by the
+   * AttributeSelection module to determine if rankedAttributes()
+   * should be called.
+   * @return true if ranking has been requested.
+   */
+  public boolean getGenerateRanking() {
+    return m_rankingRequested;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Set the start point for the search. This is specified as a comma "
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String conservativeForwardSelectionTipText() {
+    return "If true (and forward search is selected) then attributes "
+      +"will continue to be added to the best subset as long as merit does "
+      +"not degrade.";
+  }
+
+  /**
+   * Set whether attributes should continue to be added during
+   * a forward search as long as merit does not decrease
+   * @param c true if atts should continue to be atted
+   */
+  public void setConservativeForwardSelection(boolean c) {
+    m_conservativeSelection = c;
+  }
+
+  /**
+   * Gets whether conservative selection has been enabled
+   * @return true if conservative forward selection is enabled
+   */
+  public boolean getConservativeForwardSelection() {
+    return m_conservativeSelection;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option("\tUse conservative forward search"
+				    ,"-C", 0, "-C"));
+
+    newVector.addElement(new Option("\tUse a backward search instead of a"
+				    +"\n\tforward one."
+				    ,"-B", 0, "-B"));
+    newVector
+      .addElement(new Option("\tSpecify a starting set of attributes." 
+			     + "\n\tEg. 1,3,5-7."
+			     ,"P",1
+			     , "-P <start set>"));
+
+    newVector.addElement(new Option("\tProduce a ranked list of attributes."
+				    ,"R",0,"-R"));
+    newVector
+      .addElement(new Option("\tSpecify a theshold by which attributes" 
+			     + "\n\tmay be discarded from the ranking."
+			     +"\n\tUse in conjuction with -R","T",1
+			     , "-T <threshold>"));
+
+    newVector
+      .addElement(new Option("\tSpecify number of attributes to select" 
+			     ,"N",1
+			     , "-N <num to select>"));
+
+    return newVector.elements();
+
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C
+   *  Use conservative forward search</pre>
+   * 
+   * <pre> -B
+   *  Use a backward search instead of a
+   *  forward one.</pre>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.</pre>
+   * 
+   * <pre> -R
+   *  Produce a ranked list of attributes.</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Specify a theshold by which attributes
+   *  may be discarded from the ranking.
+   *  Use in conjuction with -R</pre>
+   * 
+   * <pre> -N &lt;num to select&gt;
+   *  Specify number of attributes to select</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    setSearchBackwards(Utils.getFlag('B', options));
+
+    setConservativeForwardSelection(Utils.getFlag('C', options));
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    setGenerateRanking(Utils.getFlag('R', options));
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() != 0) {
+      Double temp;
+      temp = Double.valueOf(optionString);
+      setThreshold(temp.doubleValue());
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumToSelect(Integer.parseInt(optionString));
+    }
+  }
+
+  /**
+   * Gets the current settings of ReliefFAttributeEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[9];
+    int current = 0;
+    
+    if (getSearchBackwards()) {
+      options[current++] = "-B";
+    }
+
+    if (getConservativeForwardSelection()) {
+      options[current++] = "-C";
+    }
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = ""+startSetToString();
+    }
+
+    if (getGenerateRanking()) {
+      options[current++] = "-R";
+    }
+    options[current++] = "-T";
+    options[current++] = "" + getThreshold();
+
+    options[current++] = "-N";
+    options[current++] = ""+getNumToSelect();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return  options;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  protected String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+    
+    if (m_starting == null) {
+      return getStartSet();
+    }
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+      
+      if ((m_hasClass == false) || 
+	  (m_hasClass == true && i != m_classIndex)) {
+	FString.append((m_starting[i] + 1));
+	didPrint = true;
+      }
+      
+      if (i == (m_starting.length - 1)) {
+	FString.append("");
+      }
+      else {
+	if (didPrint) {
+	  FString.append(",");
+	  }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * returns a description of the search.
+   * @return a description of the search as a String.
+   */
+  public String toString() {
+    StringBuffer FString = new StringBuffer();
+    FString.append("\tGreedy Stepwise ("
+		   + ((m_backward)
+		      ? "backwards)"
+		      : "forwards)")+".\n\tStart set: ");
+
+    if (m_starting == null) {
+      if (m_backward) {
+	FString.append("all attributes\n");
+      } else {
+	FString.append("no attributes\n");
+      }
+    }
+    else {
+      FString.append(startSetToString()+"\n");
+    }
+    if (!m_doneRanking) {
+      FString.append("\tMerit of best subset found: "
+		     +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
+    }
+    
+    if ((m_threshold != -Double.MAX_VALUE) && (m_doneRanking)) {
+      FString.append("\tThreshold for discarding attributes: "
+		     + Utils.doubleToString(m_threshold,8,4)+"\n");
+    }
+
+    return FString.toString();
+  }
+
+
+  /**
+   * Searches the attribute subset space by forward selection.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+
+    int i;
+    double best_merit = -Double.MAX_VALUE;
+    double temp_best,temp_merit;
+    int temp_index=0;
+    BitSet temp_group;
+
+    if (data != null) { // this is a fresh run so reset
+      resetOptions();
+      m_Instances = data;
+    }
+    m_ASEval = ASEval;
+
+    m_numAttribs = m_Instances.numAttributes();
+
+    if (m_best_group == null) {
+      m_best_group = new BitSet(m_numAttribs);
+    }
+
+    if (!(m_ASEval instanceof SubsetEvaluator)) {
+      throw  new Exception(m_ASEval.getClass().getName() 
+			   + " is not a " 
+			   + "Subset evaluator!");
+    }
+
+    m_startRange.setUpper(m_numAttribs-1);
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+
+    if (m_ASEval instanceof UnsupervisedSubsetEvaluator) {
+      m_hasClass = false;
+      m_classIndex = -1;
+    }
+    else {
+      m_hasClass = true;
+      m_classIndex = m_Instances.classIndex();
+    }
+
+    SubsetEvaluator ASEvaluator = (SubsetEvaluator)m_ASEval;
+
+    if (m_rankedAtts == null) {
+      m_rankedAtts = new double[m_numAttribs][2];
+      m_rankedSoFar = 0;
+    }
+
+    // If a starting subset has been supplied, then initialise the bitset
+    if (m_starting != null && m_rankedSoFar <= 0) {
+      for (i = 0; i < m_starting.length; i++) {
+	if ((m_starting[i]) != m_classIndex) {
+	  m_best_group.set(m_starting[i]);
+	}
+      }
+    } else {
+      if (m_backward && m_rankedSoFar <= 0) {
+	for (i = 0; i < m_numAttribs; i++) {
+	  if (i != m_classIndex) {
+	    m_best_group.set(i);
+	  }
+	}
+      }
+    }
+
+    // Evaluate the initial subset
+    best_merit = ASEvaluator.evaluateSubset(m_best_group);
+
+    // main search loop
+    boolean done = false;
+    boolean addone = false;
+    boolean z;
+    while (!done) {
+      temp_group = (BitSet)m_best_group.clone();
+      temp_best = best_merit;
+      if (m_doRank) {
+	temp_best = -Double.MAX_VALUE;
+      }
+      done = true;
+      addone = false;
+      for (i=0;i<m_numAttribs;i++) {
+	if (m_backward) {
+	  z = ((i != m_classIndex) && (temp_group.get(i)));
+	} else {
+	  z = ((i != m_classIndex) && (!temp_group.get(i)));
+	}
+	if (z) {
+	  // set/unset the bit
+	  if (m_backward) {
+	    temp_group.clear(i);
+	  } else {
+	    temp_group.set(i);
+	  }
+	  temp_merit = ASEvaluator.evaluateSubset(temp_group);
+	  if (m_backward) {
+	    z = (temp_merit >= temp_best);
+	  } else {
+            if (m_conservativeSelection) {
+              z = (temp_merit >= temp_best);
+            } else {
+              z = (temp_merit > temp_best);
+            }
+	  }
+
+	  if (z) {
+            temp_best = temp_merit;
+            temp_index = i;
+            addone = true;
+            done = false;
+	  }
+
+	  // unset this addition/deletion
+	  if (m_backward) {
+	    temp_group.set(i);
+	  } else {
+	    temp_group.clear(i);
+	  }
+	  if (m_doRank) {
+	    done = false;
+	  }
+	}
+      }
+      if (addone) {
+	if (m_backward) {
+	  m_best_group.clear(temp_index);
+	} else {
+	  m_best_group.set(temp_index);
+	}
+	best_merit = temp_best;
+	m_rankedAtts[m_rankedSoFar][0] = temp_index;
+	m_rankedAtts[m_rankedSoFar][1] = best_merit;
+	m_rankedSoFar++;
+      }
+    }
+    m_bestMerit = best_merit;
+    return attributeList(m_best_group);
+  }
+
+  /**
+   * Produces a ranked list of attributes. Search must have been performed
+   * prior to calling this function. Search is called by this function to
+   * complete the traversal of the the search space. A list of
+   * attributes and merits are returned. The attributes a ranked by the
+   * order they are added to the subset during a forward selection search.
+   * Individual merit values reflect the merit associated with adding the
+   * corresponding attribute to the subset; because of this, merit values
+   * may initially increase but then decrease as the best subset is
+   * "passed by" on the way to the far side of the search space.
+   *
+   * @return an array of attribute indexes and associated merit values
+   * @throws Exception if something goes wrong.
+   */
+  public double [][] rankedAttributes() throws Exception {
+    
+    if (m_rankedAtts == null || m_rankedSoFar == -1) {
+      throw new Exception("Search must be performed before attributes "
+			  +"can be ranked.");
+    }
+    
+    m_doRank = true;
+    search (m_ASEval, null);
+
+    double [][] final_rank = new double [m_rankedSoFar][2];
+    for (int i=0;i<m_rankedSoFar;i++) {
+      final_rank[i][0] = m_rankedAtts[i][0];
+      final_rank[i][1] = m_rankedAtts[i][1];
+    }
+    
+    resetOptions();
+    m_doneRanking = true;
+
+    if (m_numToSelect > final_rank.length) {
+      throw new Exception("More attributes requested than exist in the data");
+    }
+
+    if (m_numToSelect <= 0) {
+      if (m_threshold == -Double.MAX_VALUE) {
+	m_calculatedNumToSelect = final_rank.length;
+      } else {
+	determineNumToSelectFromThreshold(final_rank);
+      }
+    }
+
+    return final_rank;
+  }
+
+  private void determineNumToSelectFromThreshold(double [][] ranking) {
+    int count = 0;
+    for (int i = 0; i < ranking.length; i++) {
+      if (ranking[i][1] > m_threshold) {
+	count++;
+      }
+    }
+    m_calculatedNumToSelect = count;
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  protected int[] attributeList (BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+
+  /**
+   * Resets options
+   */
+  protected void resetOptions() {
+    m_doRank = false;
+    m_best_group = null;
+    m_ASEval = null;
+    m_Instances = null;
+    m_rankedSoFar = -1;
+    m_rankedAtts = null;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/HoldOutSubsetEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/HoldOutSubsetEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/HoldOutSubsetEvaluator.java	(revision 29)
@@ -0,0 +1,73 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HoldOutSubsetEvaluator.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.util.BitSet;
+
+/** 
+ * Abstract attribute subset evaluator capable of evaluating subsets with
+ * respect to a data set that is distinct from that used to initialize/
+ * train the subset evaluator.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public abstract class HoldOutSubsetEvaluator 
+  extends ASEvaluation
+  implements SubsetEvaluator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8280529785412054174L;
+  
+  /**
+   * Evaluates a subset of attributes with respect to a set of instances.
+   * @param subset a bitset representing the attribute subset to be
+   * evaluated
+   * @param holdOut a set of instances (possibly seperate and distinct
+   * from those use to build/train the evaluator) with which to
+   * evaluate the merit of the subset
+   * @return the "merit" of the subset on the holdOut data
+   * @exception Exception if the subset cannot be evaluated
+   */
+  public abstract double evaluateSubset(BitSet subset, Instances holdOut)
+    throws Exception;
+
+  /**
+   * Evaluates a subset of attributes with respect to a single instance.
+   * @param subset a bitset representing the attribute subset to be
+   * evaluated
+   * @param holdOut a single instance (possibly not one of those used to
+   * build/train the evaluator) with which to evaluate the merit of the subset
+   * @param retrain true if the classifier should be retrained with respect
+   * to the new subset before testing on the holdOut instance.
+   * @return the "merit" of the subset on the holdOut instance
+   * @exception Exception if the subset cannot be evaluated
+   */
+  public abstract double evaluateSubset(BitSet subset, 
+					Instance holdOut,
+					boolean retrain)
+    throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/InfoGainAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/InfoGainAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/InfoGainAttributeEval.java	(revision 29)
@@ -0,0 +1,470 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InfoGainAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+import weka.filters.unsupervised.attribute.NumericToBinary;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * InfoGainAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by measuring the information gain with respect to the class.<br/>
+ * <br/>
+ * InfoGain(Class,Attribute) = H(Class) - H(Class | Attribute).<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ * <pre> -B
+ *  just binarize numeric attributes instead 
+ *  of properly discretizing them.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5447 $
+ * @see Discretize
+ * @see NumericToBinary
+ */
+public class InfoGainAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1949849512589218930L;
+
+  /** Treat missing values as a seperate value */
+  private boolean m_missing_merge;
+
+  /** Just binarize numeric attributes */
+  private boolean m_Binarize;
+
+  /** The info gain for each attribute */
+  private double[] m_InfoGains;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "InfoGainAttributeEval :\n\nEvaluates the worth of an attribute "
+      +"by measuring the information gain with respect to the class.\n\n"
+      +"InfoGain(Class,Attribute) = H(Class) - H(Class | Attribute).\n";
+  }
+
+  /**
+   * Constructor
+   */
+  public InfoGainAttributeEval () {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option("\ttreat missing values as a seperate " 
+                                    + "value.", "M", 0, "-M"));
+    newVector.addElement(new Option("\tjust binarize numeric attributes instead \n" 
+                                    +"\tof properly discretizing them.", "B", 0, 
+                                    "-B"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   * <pre> -B
+   *  just binarize numeric attributes instead 
+   *  of properly discretizing them.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+    setBinarizeNumericAttributes(Utils.getFlag('B', options));
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[2];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+    if (getBinarizeNumericAttributes()) {
+      options[current++] = "-B";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binarizeNumericAttributesTipText() {
+    return "Just binarize numeric attributes instead of properly discretizing them.";
+  }
+
+  /**
+   * Binarize numeric attributes.
+   *
+   * @param b true=binarize numeric attributes
+   */
+  public void setBinarizeNumericAttributes (boolean b) {
+    m_Binarize = b;
+  }
+
+
+  /**
+   * get whether numeric attributes are just being binarized.
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getBinarizeNumericAttributes () {
+    return  m_Binarize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+      +"across other values in proportion to their frequency. Otherwise, "
+      +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes an information gain attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    int classIndex = data.classIndex();
+    int numInstances = data.numInstances();
+    
+    if (!m_Binarize) {
+      Discretize disTransform = new Discretize();
+      disTransform.setUseBetterEncoding(true);
+      disTransform.setInputFormat(data);
+      data = Filter.useFilter(data, disTransform);
+    } else {
+      NumericToBinary binTransform = new NumericToBinary();
+      binTransform.setInputFormat(data);
+      data = Filter.useFilter(data, binTransform);
+    }      
+    int numClasses = data.attribute(classIndex).numValues();
+
+    // Reserve space and initialize counters
+    double[][][] counts = new double[data.numAttributes()][][];
+    for (int k = 0; k < data.numAttributes(); k++) {
+      if (k != classIndex) {
+        int numValues = data.attribute(k).numValues();
+        counts[k] = new double[numValues + 1][numClasses + 1];
+      }
+    }
+
+    // Initialize counters
+    double[] temp = new double[numClasses + 1];
+    for (int k = 0; k < numInstances; k++) {
+      Instance inst = data.instance(k);
+      if (inst.classIsMissing()) {
+        temp[numClasses] += inst.weight();
+      } else {
+        temp[(int)inst.classValue()] += inst.weight();
+      }
+    }
+    for (int k = 0; k < counts.length; k++) {
+      if (k != classIndex) {
+        for (int i = 0; i < temp.length; i++) {
+          counts[k][0][i] = temp[i];
+        }
+      }
+    }
+
+    // Get counts
+    for (int k = 0; k < numInstances; k++) {
+      Instance inst = data.instance(k);
+      for (int i = 0; i < inst.numValues(); i++) {
+        if (inst.index(i) != classIndex) {
+          if (inst.isMissingSparse(i) || inst.classIsMissing()) {
+            if (!inst.isMissingSparse(i)) {
+              counts[inst.index(i)][(int)inst.valueSparse(i)][numClasses] += 
+                inst.weight();
+              counts[inst.index(i)][0][numClasses] -= inst.weight();
+            } else if (!inst.classIsMissing()) {
+              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
+                [(int)inst.classValue()] += inst.weight();
+              counts[inst.index(i)][0][(int)inst.classValue()] -= 
+                inst.weight();
+            } else {
+              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
+                [numClasses] += inst.weight();
+              counts[inst.index(i)][0][numClasses] -= inst.weight();
+            }
+          } else {
+            counts[inst.index(i)][(int)inst.valueSparse(i)]
+              [(int)inst.classValue()] += inst.weight();
+            counts[inst.index(i)][0][(int)inst.classValue()] -= inst.weight();
+          }
+        }
+      }
+    }
+
+    // distribute missing counts if required
+    if (m_missing_merge) {
+      
+      for (int k = 0; k < data.numAttributes(); k++) {
+        if (k != classIndex) {
+          int numValues = data.attribute(k).numValues();
+
+          // Compute marginals
+          double[] rowSums = new double[numValues];
+          double[] columnSums = new double[numClasses];
+          double sum = 0;
+          for (int i = 0; i < numValues; i++) {
+            for (int j = 0; j < numClasses; j++) {
+              rowSums[i] += counts[k][i][j];
+              columnSums[j] += counts[k][i][j];
+            }
+            sum += rowSums[i];
+          }
+
+          if (Utils.gr(sum, 0)) {
+            double[][] additions = new double[numValues][numClasses];
+
+            // Compute what needs to be added to each row
+            for (int i = 0; i < numValues; i++) {
+              for (int j = 0; j  < numClasses; j++) {
+                additions[i][j] = (rowSums[i] / sum) * counts[k][numValues][j];
+              }
+            }
+            
+            // Compute what needs to be added to each column
+            for (int i = 0; i < numClasses; i++) {
+              for (int j = 0; j  < numValues; j++) {
+                additions[j][i] += (columnSums[i] / sum) * 
+                  counts[k][j][numClasses];
+              }
+            }
+            
+            // Compute what needs to be added to each cell
+            for (int i = 0; i < numClasses; i++) {
+              for (int j = 0; j  < numValues; j++) {
+                additions[j][i] += (counts[k][j][i] / sum) * 
+                  counts[k][numValues][numClasses];
+              }
+            }
+            
+            // Make new contingency table
+            double[][] newTable = new double[numValues][numClasses];
+            for (int i = 0; i < numValues; i++) {
+              for (int j = 0; j < numClasses; j++) {
+                newTable[i][j] = counts[k][i][j] + additions[i][j];
+              }
+            }
+            counts[k] = newTable;
+          }
+        }
+      }
+    }
+
+    // Compute info gains
+    m_InfoGains = new double[data.numAttributes()];
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != classIndex) {
+        m_InfoGains[i] = 
+          (ContingencyTables.entropyOverColumns(counts[i]) 
+           - ContingencyTables.entropyConditionedOnRows(counts[i]));
+      }
+    }
+  }
+
+  /**
+   * Reset options to their default values
+   */
+  protected void resetOptions () {
+    m_InfoGains = null;
+    m_missing_merge = true;
+    m_Binarize = false;
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring the amount
+   * of information gained about the class given the attribute.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the info gain
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+
+    return m_InfoGains[attribute];
+  }
+
+  /**
+   * Describe the attribute evaluator
+   * @return a description of the attribute evaluator as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_InfoGains == null) {
+      text.append("Information Gain attribute evaluator has not been built");
+    }
+    else {
+      text.append("\tInformation Gain Ranking Filter");
+      if (!m_missing_merge) {
+        text.append("\n\tMissing values treated as seperate");
+      }
+      if (m_Binarize) {
+        text.append("\n\tNumeric attributes are just binarized");
+      }
+    }
+    
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+  
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new InfoGainAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/LFSMethods.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/LFSMethods.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/LFSMethods.java	(revision 29)
@@ -0,0 +1,730 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LFSMethods.java
+ *    Copyright (C) 2007 Martin Guetlein
+ *
+ */
+package weka.attributeSelection;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.BitSet;
+import java.util.Hashtable;
+
+/**
+ * @author Martin Guetlein (martin.guetlein@gmail.com)
+ * @version $Revision: 4899 $
+ */
+public class LFSMethods
+  implements RevisionHandler {
+  
+  /** max-size of array bestGroupOfSize, should be suffient */
+  private final static int MAX_SUBSET_SIZE = 200;
+  private BitSet m_bestGroup;
+  private double m_bestMerit;
+  private int m_evalsTotal;
+  private int m_evalsCached;
+  private BitSet[] m_bestGroupOfSize = new BitSet[MAX_SUBSET_SIZE];
+
+  /**
+   * empty constructor
+   *
+   * methods are not static because of access to inner class Link2 and
+   * LinkedList2
+   *
+   */
+  public LFSMethods() {
+  }
+
+  /**
+   * @return best group found by forwardSearch/floatingForwardSearch
+   */
+  public BitSet getBestGroup() {
+    return m_bestGroup;
+  }
+
+  /**
+   * @return merit of best group found by forwardSearch/floatingForwardSearch
+   */
+  public double getBestMerit() {
+    return m_bestMerit;
+  }
+
+  /**
+   * @return best group of size found by forwardSearch
+   */
+  public BitSet getBestGroupOfSize(int size) {
+    return m_bestGroupOfSize[size];
+  }
+
+  /**
+   * @return number of cached / not performed evaluations
+   */
+  public int getNumEvalsCached() {
+    return m_evalsCached;
+  }
+
+  /**
+   * @return number totally performed evaluations
+   */
+  public int getNumEvalsTotal() {
+    return m_evalsTotal;
+  }
+
+  /**
+   * @return ranking (integer array) of attributes in data with evaluator (sorting is NOT stable!)
+   */
+  public int[] rankAttributes(Instances data, SubsetEvaluator evaluator,
+                              boolean verbose) throws Exception {
+    if (verbose) {
+      System.out.println("Ranking attributes with " +
+                         evaluator.getClass().getName());
+    }
+
+    double[] merit = new double[data.numAttributes()];
+    BitSet group = new BitSet(data.numAttributes());
+
+    for (int k = 0; k < data.numAttributes(); k++) {
+      if (k != data.classIndex()) {
+        group.set(k);
+        merit[k] -= evaluator.evaluateSubset(group);
+        m_evalsTotal++;
+        group.clear(k);
+      } else {
+        merit[k] = Double.MAX_VALUE;
+      }
+
+      if (verbose) {
+        System.out.println(k + ": " + merit[k]);
+      }
+    }
+
+    int[] ranking = Utils.sort(merit);
+
+    if (verbose) {
+      System.out.print("Ranking [ ");
+
+      for (int i = 0; i < ranking.length; i++) {
+        System.out.print(ranking[i] + " ");
+      }
+
+      System.out.println("]\n");
+    }
+
+    return ranking;
+  }
+
+  /**
+   * Performs linear forward selection
+   *
+   * @param cacheSize         chacheSize (times number of instances) to store already evaluated sets
+   * @param startGroup        start group for search (can be null)
+   * @param ranking                ranking of attributes (as produced by rankAttributes), no ranking would be [0,1,2,3,4..]
+   * @param k                                number of top k attributes that are taken into account
+   * @param incrementK        true -> fixed-set, false -> fixed-width
+   * @param maxStale                number of times the search proceeds even though no improvement was found (1 = hill-climbing)
+   * @param forceResultSize        stopping criteria changed from no-improvement (forceResultSize=-1) to subset-size
+   * @param data
+   * @param evaluator
+   * @param verbose
+   * @return                                BitSet, that cotains the best-group found
+   * @throws Exception
+   */
+  public BitSet forwardSearch(int cacheSize, BitSet startGroup, int[] ranking,
+                              int k, boolean incrementK, int maxStale, int forceResultSize,
+                              Instances data, SubsetEvaluator evaluator, boolean verbose)
+    throws Exception {
+    if ((forceResultSize > 0) && (maxStale > 1)) {
+      throw new Exception("Forcing result size only works for maxStale=1");
+    }
+
+    if (verbose) {
+      System.out.println("Starting forward selection");
+    }
+
+    BitSet bestGroup;
+    BitSet tempGroup;
+    int bestSize = 0;
+    int tempSize = 0;
+    double bestMerit;
+    double tempMerit = 0;
+    Link2 link;
+    LinkedList2 list = new LinkedList2(maxStale);
+    Hashtable alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
+    int insertCount = 0;
+    int stale = 0;
+    boolean improvement;
+    int thisK = k;
+    int evalsTotal = 0;
+    int evalsCached = 0;
+
+    bestGroup = (BitSet) startGroup.clone();
+
+    String hashKey = bestGroup.toString();
+    bestMerit = evaluator.evaluateSubset(bestGroup);
+
+    if (verbose) {
+      System.out.print("Group: ");
+      printGroup(bestGroup, data.numAttributes());
+      System.out.println("Merit: " + tempMerit);
+      System.out.println("----------");
+    }
+
+    alreadyExpanded.put(hashKey, new Double(bestMerit));
+    insertCount++;
+    bestSize = bestGroup.cardinality();
+
+    //the list is only used if best-first search is applied
+    if (maxStale > 1) {
+      Object[] best = new Object[1];
+      best[0] = bestGroup.clone();
+      list.addToList(best, bestMerit);
+    }
+
+    while (stale < maxStale) {
+      improvement = false;
+
+      //best-first: take first elem from list
+      if (maxStale > 1) {
+        if (list.size() == 0) {
+          stale = maxStale;
+
+          break;
+        }
+
+        link = list.getLinkAt(0);
+        tempGroup = (BitSet) (link.getData()[0]);
+        tempGroup = (BitSet) tempGroup.clone();
+        list.removeLinkAt(0);
+
+        tempSize = 0;
+
+        for (int i = 0; i < data.numAttributes(); i++) {
+          if (tempGroup.get(i)) {
+            tempSize++;
+          }
+        }
+      } else //hill-climbing 
+        {
+          tempGroup = (BitSet) bestGroup.clone();
+          tempSize = bestSize;
+        }
+
+      //set number of top k attributes that are taken into account
+      if (incrementK) {
+        thisK = Math.min(Math.max(thisK, k + tempSize), data.numAttributes());
+      } else {
+        thisK = k;
+      }
+
+      //temporarilly add attributes to current set
+      for (int i = 0; i < thisK; i++) {
+        if ((ranking[i] == data.classIndex()) || tempGroup.get(ranking[i])) {
+          continue;
+        }
+
+        tempGroup.set(ranking[i]);
+        tempSize++;
+        hashKey = tempGroup.toString();
+
+        if (!alreadyExpanded.containsKey(hashKey)) {
+          evalsTotal++;
+          tempMerit = evaluator.evaluateSubset(tempGroup);
+
+          if (insertCount > (cacheSize * data.numAttributes())) {
+            alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
+            insertCount = 0;
+          }
+
+          alreadyExpanded.put(hashKey, new Double(tempMerit));
+          insertCount++;
+        } else {
+          evalsCached++;
+          tempMerit = ((Double) alreadyExpanded.get(hashKey)).doubleValue();
+        }
+
+        if (verbose) {
+          System.out.print("Group: ");
+          printGroup(tempGroup, data.numAttributes());
+          System.out.println("Merit: " + tempMerit);
+        }
+
+        if (((tempMerit - bestMerit) > 0.00001) ||
+            ((forceResultSize >= tempSize) && (tempSize > bestSize))) {
+          improvement = true;
+          stale = 0;
+          bestMerit = tempMerit;
+          bestSize = tempSize;
+          bestGroup = (BitSet) (tempGroup.clone());
+          m_bestGroupOfSize[bestSize] = (BitSet) (tempGroup.clone());
+        }
+
+        if (maxStale > 1) {
+          Object[] add = new Object[1];
+          add[0] = tempGroup.clone();
+          list.addToList(add, tempMerit);
+        }
+
+        tempGroup.clear(ranking[i]);
+        tempSize--;
+      }
+
+      if (verbose) {
+        System.out.println("----------");
+      }
+
+      //handle stopping criteria
+      if (!improvement || (forceResultSize == bestSize)) {
+        stale++;
+      }
+
+      if ((forceResultSize > 0) && (bestSize == forceResultSize)) {
+        break;
+      }
+    }
+
+    if (verbose) {
+      System.out.println("Best Group: ");
+      printGroup(bestGroup, data.numAttributes());
+      System.out.println();
+    }
+
+    m_bestGroup = bestGroup;
+    m_bestMerit = bestMerit;
+    m_evalsTotal += evalsTotal;
+    m_evalsCached += evalsCached;
+
+    return bestGroup;
+  }
+
+  /**
+   * Performs linear floating forward selection
+   * ( the stopping criteria cannot be changed to a specific size value )
+   *
+   *
+   * @param cacheSize         chacheSize (times number of instances) to store already evaluated sets
+   * @param startGroup        start group for search (can be null)
+   * @param ranking                ranking of attributes (as produced by rankAttributes), no ranking would be [0,1,2,3,4..]
+   * @param k                                number of top k attributes that are taken into account
+   * @param incrementK        true -> fixed-set, false -> fixed-width
+   * @param maxStale                number of times the search proceeds even though no improvement was found (1 = hill-climbing)
+   * @param data
+   * @param evaluator
+   * @param verbose
+   * @return                                BitSet, that cotains the best-group found
+   * @throws Exception
+   */
+  public BitSet floatingForwardSearch(int cacheSize, BitSet startGroup,
+                                      int[] ranking, int k, boolean incrementK, int maxStale, Instances data,
+                                      SubsetEvaluator evaluator, boolean verbose) throws Exception {
+    if (verbose) {
+      System.out.println("Starting floating forward selection");
+    }
+
+    BitSet bestGroup;
+    BitSet tempGroup;
+    int bestSize = 0;
+    int tempSize = 0;
+    double bestMerit;
+    double tempMerit = 0;
+    Link2 link;
+    LinkedList2 list = new LinkedList2(maxStale);
+    Hashtable alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
+    int insertCount = 0;
+    int backtrackingSteps = 0;
+    boolean improvement;
+    boolean backward;
+    int thisK = k;
+    int evalsTotal = 0;
+    int evalsCached = 0;
+
+    bestGroup = (BitSet) startGroup.clone();
+
+    String hashKey = bestGroup.toString();
+    bestMerit = evaluator.evaluateSubset(bestGroup);
+
+    if (verbose) {
+      System.out.print("Group: ");
+      printGroup(bestGroup, data.numAttributes());
+      System.out.println("Merit: " + tempMerit);
+      System.out.println("----------");
+    }
+
+    alreadyExpanded.put(hashKey, new Double(bestMerit));
+    insertCount++;
+    bestSize = bestGroup.cardinality();
+
+    if (maxStale > 1) {
+      Object[] best = new Object[1];
+      best[0] = bestGroup.clone();
+      list.addToList(best, bestMerit);
+    }
+
+    backward = improvement = true;
+
+    while (true) {
+      // we are search in backward direction -> 
+      // continue backward search as long as a new best set is found
+      if (backward) {
+        if (!improvement) {
+          backward = false;
+        }
+      }
+      // we are searching forward ->  
+      // stop search or start backward step
+      else {
+        if (!improvement && (backtrackingSteps >= maxStale)) {
+          break;
+        }
+
+        backward = true;
+      }
+
+      improvement = false;
+
+      // best-first: take first elem from list
+      if (maxStale > 1) {
+        if (list.size() == 0) {
+          backtrackingSteps = maxStale;
+
+          break;
+        }
+
+        link = list.getLinkAt(0);
+        tempGroup = (BitSet) (link.getData()[0]);
+        tempGroup = (BitSet) tempGroup.clone();
+        list.removeLinkAt(0);
+
+        tempSize = 0;
+
+        for (int i = 0; i < data.numAttributes(); i++) {
+          if (tempGroup.get(i)) {
+            tempSize++;
+          }
+        }
+      } else //hill-climbing
+        {
+          tempGroup = (BitSet) bestGroup.clone();
+          tempSize = bestSize;
+        }
+
+      //backward search only makes sense for set-size bigger than 2
+      if (backward && (tempSize <= 2)) {
+        backward = false;
+      }
+
+      //set number of top k attributes that are taken into account
+      if (incrementK) {
+        thisK = Math.max(thisK,
+                         Math.min(Math.max(thisK, k + tempSize), data.numAttributes()));
+      } else {
+        thisK = k;
+      }
+
+      //temporarilly add/remove attributes to/from current set
+      for (int i = 0; i < thisK; i++) {
+        if (ranking[i] == data.classIndex()) {
+          continue;
+        }
+
+        if (backward) {
+          if (!tempGroup.get(ranking[i])) {
+            continue;
+          }
+
+          tempGroup.clear(ranking[i]);
+          tempSize--;
+        } else {
+          if ((ranking[i] == data.classIndex()) || tempGroup.get(ranking[i])) {
+            continue;
+          }
+
+          tempGroup.set(ranking[i]);
+          tempSize++;
+        }
+
+        hashKey = tempGroup.toString();
+
+        if (!alreadyExpanded.containsKey(hashKey)) {
+          evalsTotal++;
+          tempMerit = evaluator.evaluateSubset(tempGroup);
+
+          if (insertCount > (cacheSize * data.numAttributes())) {
+            alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
+            insertCount = 0;
+          }
+
+          alreadyExpanded.put(hashKey, new Double(tempMerit));
+          insertCount++;
+        } else {
+          evalsCached++;
+          tempMerit = ((Double) alreadyExpanded.get(hashKey)).doubleValue();
+        }
+
+        if (verbose) {
+          System.out.print("Group: ");
+          printGroup(tempGroup, data.numAttributes());
+          System.out.println("Merit: " + tempMerit);
+        }
+
+        if ((tempMerit - bestMerit) > 0.00001) {
+          improvement = true;
+          backtrackingSteps = 0;
+          bestMerit = tempMerit;
+          bestSize = tempSize;
+          bestGroup = (BitSet) (tempGroup.clone());
+        }
+
+        if (maxStale > 1) {
+          Object[] add = new Object[1];
+          add[0] = tempGroup.clone();
+          list.addToList(add, tempMerit);
+        }
+
+        if (backward) {
+          tempGroup.set(ranking[i]);
+          tempSize++;
+        } else {
+          tempGroup.clear(ranking[i]);
+          tempSize--;
+        }
+      }
+
+      if (verbose) {
+        System.out.println("----------");
+      }
+
+      if ((maxStale > 1) && backward && !improvement) {
+        Object[] add = new Object[1];
+        add[0] = tempGroup.clone();
+        list.addToList(add, Double.MAX_VALUE);
+      }
+
+      if (!backward && !improvement) {
+        backtrackingSteps++;
+      }
+    }
+
+    if (verbose) {
+      System.out.println("Best Group: ");
+      printGroup(bestGroup, data.numAttributes());
+      System.out.println();
+    }
+
+    m_bestGroup = bestGroup;
+    m_bestMerit = bestMerit;
+    m_evalsTotal += evalsTotal;
+    m_evalsCached += evalsCached;
+
+    return bestGroup;
+  }
+
+  /**
+   * Debug-out
+   */
+  protected static void printGroup(BitSet tt, int numAttribs) {
+    System.out.print("{ ");
+
+    for (int i = 0; i < numAttribs; i++) {
+      if (tt.get(i) == true) {
+        System.out.print((i + 1) + " ");
+      }
+    }
+
+    System.out.println("}");
+  }
+
+  // Inner classes
+  /**
+   * Class for a node in a linked list. Used in best first search.
+   * Copied from BestFirstSearch
+   *
+   * @author Mark Hall (mhall@cs.waikato.ac.nz)
+   */
+  public class Link2
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -7422719407475185086L;
+    
+    /* BitSet group; */
+    Object[] m_data;
+    double m_merit;
+
+    // Constructor
+    public Link2(Object[] data, double mer) {
+      // group = (BitSet)gr.clone();
+      m_data = data;
+      m_merit = mer;
+    }
+
+    /** Get a group */
+    public Object[] getData() {
+      return m_data;
+    }
+
+    public String toString() {
+      return ("Node: " + m_data.toString() + "  " + m_merit);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4899 $");
+    }
+  }
+
+  /**
+   * Class for handling a linked list. Used in best first search. Extends the
+   * Vector class.
+   *
+   * @author Mark Hall (mhall@cs.waikato.ac.nz)
+   */
+  public class LinkedList2
+    extends FastVector {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -7776010892419656105L;
+    
+    // Max number of elements in the list
+    int m_MaxSize;
+
+    // ================
+    // Public methods
+    // ================
+    public LinkedList2(int sz) {
+      super();
+      m_MaxSize = sz;
+    }
+
+    /**
+     * removes an element (Link) at a specific index from the list.
+     *
+     * @param index
+     *            the index of the element to be removed.
+     */
+    public void removeLinkAt(int index) throws Exception {
+      if ((index >= 0) && (index < size())) {
+        removeElementAt(index);
+      } else {
+        throw new Exception("index out of range (removeLinkAt)");
+      }
+    }
+
+    /**
+     * returns the element (Link) at a specific index from the list.
+     *
+     * @param index
+     *            the index of the element to be returned.
+     */
+    public Link2 getLinkAt(int index) throws Exception {
+      if (size() == 0) {
+        throw new Exception("List is empty (getLinkAt)");
+      } else {
+        if ((index >= 0) && (index < size())) {
+          return ((Link2) (elementAt(index)));
+        } else {
+          throw new Exception("index out of range (getLinkAt)");
+        }
+      }
+    }
+
+    /**
+     * adds an element (Link) to the list.
+     *
+     * @param data
+     *            the data to add
+     * @param mer
+     *            the "merit" of this attribute set
+     */
+    public void addToList(Object[] data, double mer) throws Exception {
+      Link2 newL = new Link2(data, mer);
+
+      if (size() == 0) {
+        addElement(newL);
+      } else {
+        if (mer > ((Link2) (firstElement())).m_merit) {
+          if (size() == m_MaxSize) {
+            removeLinkAt(m_MaxSize - 1);
+          }
+
+          // ----------
+          insertElementAt(newL, 0);
+        } else {
+          int i = 0;
+          int size = size();
+          boolean done = false;
+
+          // ------------
+          // don't insert if list contains max elements an this
+          // is worst than the last
+          if ((size == m_MaxSize) &&
+              (mer <= ((Link2) (lastElement())).m_merit)) {
+          }
+          // ---------------
+          else {
+            while ((!done) && (i < size)) {
+              if (mer > ((Link2) (elementAt(i))).m_merit) {
+                if (size == m_MaxSize) {
+                  removeLinkAt(m_MaxSize - 1);
+                }
+
+                // ---------------------
+                insertElementAt(newL, i);
+                done = true;
+              } else {
+                if (i == (size - 1)) {
+                  addElement(newL);
+                  done = true;
+                } else {
+                  i++;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4899 $");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4899 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/LatentSemanticAnalysis.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/LatentSemanticAnalysis.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/LatentSemanticAnalysis.java	(revision 29)
@@ -0,0 +1,806 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LatentSemanticAnalysis.java
+ *    Copyright (C) 2008 Amri Napolitano
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Check;
+import weka.core.CheckOptionHandler;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.matrix.Matrix;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.matrix.SingularValueDecomposition;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Performs latent semantic analysis and transformation of the data. 
+ * Use in conjunction with a Ranker search. A low-rank approximation 
+ * of the full data is found by specifying the number of singular values 
+ * to use. The dataset may be transformed to give the relation of either 
+ * the attributes or the instances (default) to the concept space created 
+ * by the transformation.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Normalize input data.</pre>
+ * 
+ * <pre> -R
+ *  Rank approximation used in LSA. May be actual number of 
+ *  LSA attributes to include (if greater than 1) or a proportion 
+ *  of total singular values to account for (if between 0 and 1). 
+ *  A value less than or equal to zero means use all latent variables.
+ *  (default = 0.95)</pre>
+ * 
+ * <pre> -A
+ *  Maximum number of attributes to include in 
+ *  transformed attribute names. (-1 = include all)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Amri Napolitano
+ * @version $Revision: 5987 $
+ */
+
+public class LatentSemanticAnalysis 
+extends UnsupervisedAttributeEvaluator 
+implements AttributeTransformer, OptionHandler {
+  
+  /** For serialization */
+  static final long serialVersionUID = -8712112988018106198L;
+  
+  /** The data to transform analyse/transform */
+  private Instances m_trainInstances;
+  
+  /** 
+   * Keep a copy for the class attribute (if set) and for 
+   * checking for header compatibility 
+   */
+  private Instances m_trainHeader;
+  
+  /** The header for the transformed data format */
+  private Instances m_transformedFormat;
+  
+  /** Data has a class set */
+  private boolean m_hasClass;
+  
+  /** Class index */
+  private int m_classIndex;
+  
+  /** Number of attributes */
+  private int m_numAttributes;
+  
+  /** Number of instances */
+  private int m_numInstances;
+  
+  /** Is transpose necessary because numAttributes < numInstances? */
+  private boolean m_transpose = false;
+  
+  /** Will hold the left singular vectors */
+  private Matrix m_u = null;
+  
+  /** Will hold the singular values */
+  private Matrix m_s = null;
+  
+  /** Will hold the right singular values */
+  private Matrix m_v = null;
+  
+  /** Will hold the matrix used to transform instances to the new feature space */
+  private Matrix m_transformationMatrix = null;
+  
+  /** Filters for original data */
+  private ReplaceMissingValues m_replaceMissingFilter;
+  private Normalize m_normalizeFilter;
+  private NominalToBinary m_nominalToBinaryFilter;
+  private Remove m_attributeFilter;
+  
+  /** The number of attributes in the LSA transformed data */
+  private int m_outputNumAttributes = -1;
+  
+  /** Normalize the input data? */
+  private boolean m_normalize = false;
+  
+  /** The approximation rank to use (between 0 and 1 means coverage proportion) */
+  private double m_rank = 0.95;
+  
+  /** The sum of the squares of the singular values */
+  private double m_sumSquaredSingularValues = 0.0;
+  
+  /** The actual rank number to use for computation */
+  private int m_actualRank = -1;
+  
+  /** Maximum number of attributes in the transformed attribute name */
+  private int m_maxAttributesInName = 5;
+  
+  /**
+   * Returns a string describing this attribute transformer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Performs latent semantic analysis and transformation of the data. Use in " +
+            "conjunction with a Ranker search. A low-rank approximation of the full data is " +
+            "found by either specifying the number of singular values to use or specifying a " +
+            "proportion of the singular values to cover.";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options. <p>
+   *
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector options = new Vector(4);
+    options.addElement(new Option("\tNormalize input data.", "N", 0, "-N"));
+    
+    options.addElement(new Option("\tRank approximation used in LSA. \n" +
+                                   "\tMay be actual number of LSA attributes \n" +
+                                   "\tto include (if greater than 1) or a \n" +
+                                   "\tproportion of total singular values to \n" +
+                                   "\taccount for (if between 0 and 1). \n" +
+                                   "\tA value less than or equal to zero means \n" +
+                                   "\tuse all latent variables.(default = 0.95)",
+                                   "R",1,"-R"));
+    
+    options.addElement(new Option("\tMaximum number of attributes to include\n" +
+                                   "\tin transformed attribute names.\n" +
+                                   "\t(-1 = include all)"
+                                   , "A", 1, "-A"));
+    return  options.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Normalize input data.</pre>
+   * 
+   * <pre> -R
+   *  Rank approximation used in LSA. May be actual number of 
+   *  LSA attributes to include (if greater than 1) or a proportion 
+   *  of total singular values to account for (if between 0 and 1). 
+   *  A value less than or equal to zero means use all latent variables.
+   *  (default = 0.95)</pre>
+   * 
+   * <pre> -A
+   *  Maximum number of attributes to include in 
+   *  transformed attribute names. (-1 = include all)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+  throws Exception {
+    resetOptions();
+    String optionString;
+    
+    //set approximation rank
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      double temp;
+      temp = Double.valueOf(optionString).doubleValue();
+      setRank(temp);
+    }
+    
+    //set number of attributes to use in transformed names
+    optionString = Utils.getOption('A', options);
+    if (optionString.length() != 0) {
+      setMaximumAttributeNames(Integer.parseInt(optionString));
+    }
+    
+    //set normalize option
+    setNormalize(Utils.getFlag('N', options));
+  }
+  
+  /**
+   * Reset to defaults
+   */
+  private void resetOptions() {
+    m_rank = 0.95;
+    m_normalize = true;
+    m_maxAttributesInName = 5;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String normalizeTipText() {
+    return "Normalize input data.";
+  }
+  
+  /**
+   * Set whether input data will be normalized.
+   * @param newNormalize true if input data is to be normalized
+   */
+  public void setNormalize(boolean newNormalize) {
+    m_normalize = newNormalize;
+  }
+  
+  /**
+   * Gets whether or not input data is to be normalized
+   * @return true if input data is to be normalized
+   */
+  public boolean getNormalize() {
+    return m_normalize;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String rankTipText() {
+    return "Matrix rank to use for data reduction. Can be a" +
+    " proportion to indicate desired coverage";
+  }
+  
+  /**
+   * Sets the desired matrix rank (or coverage proportion) for feature-space reduction
+   * @param newRank the desired rank (or coverage) for feature-space reduction
+   */
+  public void setRank(double newRank) {
+      m_rank = newRank;
+  }
+  
+  /**
+   * Gets the desired matrix rank (or coverage proportion) for feature-space reduction
+   * @return the rank (or coverage) for feature-space reduction
+   */
+  public double getRank() {
+    return m_rank;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maximumAttributeNamesTipText() {
+    return "The maximum number of attributes to include in transformed attribute names.";
+  }
+  
+  /**
+   * Sets maximum number of attributes to include in
+   * transformed attribute names.
+   * @param newMaxAttributes the maximum number of attributes
+   */
+  public void setMaximumAttributeNames(int newMaxAttributes) {
+    m_maxAttributesInName = newMaxAttributes;
+  }
+  
+  /**
+   * Gets maximum number of attributes to include in
+   * transformed attribute names.
+   * @return the maximum number of attributes
+   */
+  public int getMaximumAttributeNames() {
+    return m_maxAttributesInName;
+  }
+  
+  /**
+   * Gets the current settings of LatentSemanticAnalysis
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    
+    String[] options = new String[5];
+    int current = 0;
+    
+    if (getNormalize()) {
+      options[current++] = "-N";
+    }
+    
+    options[current++] = "-R";
+    options[current++] = "" + getRank();
+    
+    options[current++] = "-A";
+    options[current++] = "" + getMaximumAttributeNames();
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    
+    return  options;
+  }
+  
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Initializes the singular values/vectors and performs the analysis
+   * @param data the instances to analyse/transform
+   * @throws Exception if analysis fails
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+    
+    buildAttributeConstructor(data);
+  }
+  
+  /**
+   * Initializes the singular values/vectors and performs the analysis
+   * @param data the instances to analyse/transform
+   * @throws Exception if analysis fails
+   */
+  private void buildAttributeConstructor (Instances data) throws Exception {
+    // initialize attributes for performing analysis
+    m_transpose = false;
+    m_s = null;
+    m_u = null;
+    m_v = null;
+    m_outputNumAttributes = -1;
+    m_actualRank = -1;
+    m_sumSquaredSingularValues = 0.0;
+    
+    m_trainInstances = new Instances(data);
+    m_trainHeader = null;
+    
+    m_attributeFilter = null;
+    m_nominalToBinaryFilter = null;
+    
+    m_replaceMissingFilter = new ReplaceMissingValues();
+    m_replaceMissingFilter.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, m_replaceMissingFilter);
+    
+    // vector to hold indices of attributes to delete (class attribute, 
+    // attributes that are all missing, or attributes with one distinct value)
+    Vector attributesToRemove = new Vector();
+    
+    // if data has a class attribute
+    if (m_trainInstances.classIndex() >= 0) {
+      
+      m_hasClass = true;
+      m_classIndex = m_trainInstances.classIndex();
+      
+      // set class attribute to be removed
+      attributesToRemove.addElement(new Integer(m_classIndex));
+    }
+    // make copy of training data so the class values (if set) can be appended to final 
+    // transformed instances and so that we can check header compatibility
+    m_trainHeader = new Instances(m_trainInstances, 0);
+    
+    // normalize data if desired
+    if (m_normalize) {
+      m_normalizeFilter = new Normalize();
+      m_normalizeFilter.setInputFormat(m_trainInstances);
+      m_trainInstances = Filter.useFilter(m_trainInstances, m_normalizeFilter);
+    }
+    
+    // convert any nominal attributes to binary numeric attributes
+    m_nominalToBinaryFilter = new NominalToBinary();
+    m_nominalToBinaryFilter.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, m_nominalToBinaryFilter);
+    
+    // delete any attributes with only one distinct value or are all missing
+    for (int i = 0; i < m_trainInstances.numAttributes(); i++) {
+      if (m_trainInstances.numDistinctValues(i) <= 1) {
+        attributesToRemove.addElement(new Integer(i));
+      }
+    }
+    
+    // remove columns from the data if necessary
+    if (attributesToRemove.size() > 0) {
+      m_attributeFilter = new Remove();
+      int [] todelete = new int[attributesToRemove.size()];
+      for (int i = 0; i < attributesToRemove.size(); i++) {
+        todelete[i] = ((Integer)(attributesToRemove.elementAt(i))).intValue();
+      }
+      m_attributeFilter.setAttributeIndicesArray(todelete);
+      m_attributeFilter.setInvertSelection(false);
+      m_attributeFilter.setInputFormat(m_trainInstances);
+      m_trainInstances = Filter.useFilter(m_trainInstances, m_attributeFilter);
+    }
+    
+    // can evaluator handle the processed data ? e.g., enough attributes?
+    getCapabilities().testWithFail(m_trainInstances);
+    
+    // record properties of final, ready-to-process data
+    m_numInstances = m_trainInstances.numInstances();
+    m_numAttributes = m_trainInstances.numAttributes();
+    
+    // create matrix of attribute values and compute singular value decomposition
+    double [][] trainValues = new double[m_numAttributes][m_numInstances];
+    for (int i = 0; i < m_numAttributes; i++) {
+      trainValues[i] = m_trainInstances.attributeToDoubleArray(i);
+    }
+    Matrix trainMatrix = new Matrix(trainValues);
+    // svd requires rows >= columns, so transpose data if necessary
+    if (m_numAttributes < m_numInstances) {
+      m_transpose = true;
+      trainMatrix = trainMatrix.transpose();
+    }
+    SingularValueDecomposition trainSVD = trainMatrix.svd();
+    m_u = trainSVD.getU(); // left singular vectors
+    m_s = trainSVD.getS(); // singular values
+    m_v = trainSVD.getV(); // right singular vectors
+    
+    // find actual rank to use
+    int maxSingularValues = trainSVD.rank();
+    for (int i = 0; i < m_s.getRowDimension(); i++) {
+      m_sumSquaredSingularValues += m_s.get(i, i) * m_s.get(i, i);
+    }
+    if (maxSingularValues == 0) { // no nonzero singular values (shouldn't happen)
+      // reset values from computation
+      m_s = null;
+      m_u = null;
+      m_v = null;
+      m_sumSquaredSingularValues = 0.0;
+      
+      throw new Exception("SVD computation produced no non-zero singular values.");
+    }
+    if (m_rank > maxSingularValues || m_rank <= 0) { // adjust rank if too high or too low
+      m_actualRank = maxSingularValues;
+    } else if (m_rank < 1.0) { // determine how many singular values to include for desired coverage
+      double currentSumOfSquaredSingularValues = 0.0;
+      for (int i = 0; i < m_s.getRowDimension() && m_actualRank == -1; i++) {
+        currentSumOfSquaredSingularValues += m_s.get(i, i) * m_s.get(i, i);
+        if (currentSumOfSquaredSingularValues / m_sumSquaredSingularValues >= m_rank) {
+          m_actualRank = i + 1;
+        }
+      }
+    } else {
+      m_actualRank = (int) m_rank;
+    }
+    
+    // lower matrix ranks, adjust for transposition (if necessary), and
+    // compute matrix for transforming future instances
+    if (m_transpose) {
+      Matrix tempMatrix = m_u;
+      m_u = m_v;
+      m_v = tempMatrix;
+    }
+    m_u = m_u.getMatrix(0, m_u.getRowDimension() - 1, 0, m_actualRank - 1);
+    m_s = m_s.getMatrix(0, m_actualRank - 1, 0, m_actualRank - 1);
+    m_v = m_v.getMatrix(0, m_v.getRowDimension() - 1, 0, m_actualRank - 1);
+    m_transformationMatrix = m_u.times(m_s.inverse());
+    
+    //create dataset header for transformed instances
+    m_transformedFormat = setOutputFormat();
+  }
+  
+  /**
+   * Set the format for the transformed data
+   * @return a set of empty Instances (header only) in the new format
+   */
+  private Instances setOutputFormat() {
+    // if analysis hasn't been performed (successfully) yet
+    if (m_s == null) {
+      return null;
+    }
+    
+    // set up transformed attributes
+    if (m_hasClass) {
+      m_outputNumAttributes = m_actualRank + 1;
+    } else {
+      m_outputNumAttributes = m_actualRank;
+    }
+    int numAttributesInName = m_maxAttributesInName;
+    if (numAttributesInName <= 0 || numAttributesInName >= m_numAttributes) {
+      numAttributesInName = m_numAttributes;
+    }
+    FastVector attributes = new FastVector(m_outputNumAttributes);
+    for (int i = 0; i < m_actualRank; i++) {
+      // create attribute name
+      String attributeName = "";
+      double [] attributeCoefficients = 
+        m_transformationMatrix.getMatrix(0, m_numAttributes - 1, i, i).getColumnPackedCopy();
+      for (int j = 0; j < numAttributesInName; j++) {
+        if (j > 0) {
+          attributeName += "+";
+        }
+        attributeName += Utils.doubleToString(attributeCoefficients[j], 5, 3);
+        attributeName += m_trainInstances.attribute(j).name();
+      }
+      if (numAttributesInName < m_numAttributes) {
+        attributeName += "...";
+      }
+      // add attribute
+      attributes.addElement(new Attribute(attributeName));
+    }
+    // add original class attribute if present
+    if (m_hasClass) {
+      attributes.addElement(m_trainHeader.classAttribute().copy());
+    }
+    // create blank header
+    Instances outputFormat = new Instances(m_trainInstances.relationName() + "_LSA", 
+        attributes, 0);
+    m_outputNumAttributes = outputFormat.numAttributes();
+    // set class attribute if applicable
+    if (m_hasClass) {
+      outputFormat.setClassIndex(m_outputNumAttributes - 1);
+    }
+    
+    return outputFormat;
+  }
+  
+  /**
+   * Returns just the header for the transformed data (ie. an empty
+   * set of instances. This is so that AttributeSelection can
+   * determine the structure of the transformed data without actually
+   * having to get all the transformed data through getTransformedData().
+   * @return the header of the transformed data.
+   * @throws Exception if the header of the transformed data can't
+   * be determined.
+   */
+  public Instances transformedHeader() throws Exception {
+    if (m_s == null) {
+      throw new Exception("Latent Semantic Analysis hasn't been successfully performed.");
+    }
+    return m_transformedFormat;
+  }
+  
+  /**
+   * Transform the supplied data set (assumed to be the same format
+   * as the training data)
+   * @return the transformed training data
+   * @throws Exception if transformed data can't be returned
+   */
+  public Instances transformedData(Instances data) throws Exception {
+    if (m_s == null) {
+      throw new Exception("Latent Semantic Analysis hasn't been built yet");
+    }
+    
+    Instances output = new Instances(m_transformedFormat, m_numInstances);
+    
+    // the transformed version of instance i from the training data
+    // is stored as the i'th row vector in v (the right singular vectors)
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance currentInstance = data.instance(i);
+      // record attribute values for converted instance
+      double [] newValues = new double[m_outputNumAttributes];
+      for (int j = 0; j < m_actualRank; j++) { // fill in values from v
+        newValues[j] = m_v.get(i, j);
+      }
+      if (m_hasClass) { // copy class value if applicable
+        newValues[m_outputNumAttributes - 1] = currentInstance.classValue();
+      }
+      //create new instance with recorded values and add to output dataset
+      Instance newInstance;
+      if (currentInstance instanceof SparseInstance) {
+        newInstance = new SparseInstance(currentInstance.weight(), newValues);
+      } else {
+        newInstance = new DenseInstance(currentInstance.weight(), newValues);
+      }
+      output.add(newInstance);
+    }
+    
+    return output;
+  }
+  
+  /**
+   * Evaluates the merit of a transformed attribute. This is defined
+   * to be the square of the singular value for the latent variable 
+   * corresponding to the transformed attribute.
+   * @param att the attribute to be evaluated
+   * @return the merit of a transformed attribute
+   * @throws Exception if attribute can't be evaluated
+   */
+  public double evaluateAttribute(int att) throws Exception {
+    if (m_s == null) {
+      throw new Exception("Latent Semantic Analysis hasn't been successfully" +
+                            " performed yet!");
+    }
+    
+    //return the square of the corresponding singular value
+    return (m_s.get(att, att) * m_s.get(att, att)) / m_sumSquaredSingularValues;
+  }
+  
+  /**
+   * Transform an instance in original (unnormalized) format
+   * @param instance an instance in the original (unnormalized) format
+   * @return a transformed instance
+   * @throws Exception if instance can't be transformed
+   */
+  public Instance convertInstance(Instance instance) throws Exception {
+    if (m_s == null) {
+      throw new Exception("convertInstance: Latent Semantic Analysis not " +
+                           "performed yet.");
+    }
+    
+    // array to hold new attribute values
+    double [] newValues = new double[m_outputNumAttributes];
+    
+    // apply filters so new instance is in same format as training instances
+    Instance tempInstance = (Instance)instance.copy();
+    if (!instance.dataset().equalHeaders(m_trainHeader)) {
+      throw new Exception("Can't convert instance: headers don't match: " +
+      "LatentSemanticAnalysis\n" + instance.dataset().equalHeadersMsg(m_trainHeader));
+    }
+    // replace missing values
+    m_replaceMissingFilter.input(tempInstance);
+    m_replaceMissingFilter.batchFinished();
+    tempInstance = m_replaceMissingFilter.output();
+    // normalize
+    if (m_normalize) {
+      m_normalizeFilter.input(tempInstance);
+      m_normalizeFilter.batchFinished();
+      tempInstance = m_normalizeFilter.output();
+    }
+    // convert nominal attributes to binary
+    m_nominalToBinaryFilter.input(tempInstance);
+    m_nominalToBinaryFilter.batchFinished();
+    tempInstance = m_nominalToBinaryFilter.output();
+    // remove class/other attributes
+    if (m_attributeFilter != null) {
+      m_attributeFilter.input(tempInstance);
+      m_attributeFilter.batchFinished();
+      tempInstance = m_attributeFilter.output();
+    }
+    
+    // record new attribute values
+    if (m_hasClass) { // copy class value
+      newValues[m_outputNumAttributes - 1] = instance.classValue();
+    }
+    double [][] oldInstanceValues = new double[1][m_numAttributes];
+    oldInstanceValues[0] = tempInstance.toDoubleArray();
+    Matrix instanceVector = new Matrix(oldInstanceValues); // old attribute values
+    instanceVector = instanceVector.times(m_transformationMatrix); // new attribute values
+    for (int i = 0; i < m_actualRank; i++) {
+      newValues[i] = instanceVector.get(0, i);
+    }
+    
+    // return newly transformed instance
+    if (instance instanceof SparseInstance) {
+      return new SparseInstance(instance.weight(), newValues);
+    } else {
+      return new DenseInstance(instance.weight(), newValues);
+    }
+  }
+  
+  /**
+   * Returns a description of this attribute transformer
+   * @return a String describing this attribute transformer
+   */
+  public String toString() {
+    if (m_s == null) {
+      return "Latent Semantic Analysis hasn't been built yet!";
+    } else {
+      return "\tLatent Semantic Analysis Attribute Transformer\n\n"
+      + lsaSummary();
+    }
+  }
+  
+  /**
+   * Return a summary of the analysis
+   * @return a summary of the analysis.
+   */
+  private String lsaSummary() {
+    StringBuffer result = new StringBuffer();
+    
+    // print number of latent variables used
+    result.append("Number of latent variables utilized: " + m_actualRank);
+    
+    // print singular values
+    result.append("\n\nSingularValue\tLatentVariable#\n");
+    // create single array of singular values rather than diagonal matrix
+    for (int i = 0; i < m_actualRank; i++) {
+      result.append(Utils.doubleToString(m_s.get(i, i), 9, 5) + "\t" + (i + 1) + "\n");
+    }
+    
+    // print attribute vectors
+    result.append("\nAttribute vectors (left singular vectors) -- row vectors show\n" +
+                  "the relation between the original attributes and the latent \n" +
+                  "variables computed by the singular value decomposition:\n");
+    for (int i = 0; i < m_actualRank; i++) {
+      result.append("LatentVariable#" + (i + 1) + "\t");
+    }
+    result.append("AttributeName\n");
+    for (int i = 0; i < m_u.getRowDimension(); i++) { // for each attribute
+      for (int j = 0; j < m_u.getColumnDimension(); j++) { // for each latent variable
+        result.append(Utils.doubleToString(m_u.get(i, j), 9, 5) + "\t\t");
+      }
+      result.append(m_trainInstances.attribute(i).name() + "\n");
+    }
+    
+    // print instance vectors
+    result.append("\n\nInstance vectors (right singular vectors) -- column\n" +
+                  "vectors show the relation between the original instances and the\n" +
+                  "latent variables computed by the singular value decomposition:\n");
+    for (int i = 0; i < m_numInstances; i++) {
+      result.append("Instance#" + (i + 1) + "\t");
+    }
+    result.append("LatentVariable#\n");
+    for (int i = 0; i < m_v.getColumnDimension(); i++) { // for each instance
+      for (int j = 0; j < m_v.getRowDimension(); j++) { // for each latent variable
+        // going down columns instead of across rows because we're
+        // printing v' but have v stored
+        result.append(Utils.doubleToString(m_v.get(j, i), 9, 5) + "\t");
+      }
+      result.append((i + 1) + "\n");
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class
+   * @param argv should contain the command line arguments to the
+   * evaluator/transformer (see AttributeSelection)
+   */
+  public static void main(String [] argv) {
+    runEvaluator(new LatentSemanticAnalysis(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/LinearForwardSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/LinearForwardSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/LinearForwardSelection.java	(revision 29)
@@ -0,0 +1,875 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LinearForwardSelection.java
+ *    Copyright (C) 2007 Martin Guetlein
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+  <!-- globalinfo-start -->
+  * LinearForwardSelection:<br/>
+  * <br/>
+  * Extension of BestFirst. Takes a restricted number of k attributes into account. Fixed-set selects a fixed number k of attributes, whereas k is increased in each step when fixed-width is selected. The search uses either the initial ordering to select the top k attributes, or performs a ranking (with the same evalutator the search uses later on). The search direction can be forward, or floating forward selection (with opitional backward search steps).<br/>
+  * <br/>
+  * For more information see:<br/>
+  * <br/>
+  * Martin Guetlein, Eibe Frank, Mark Hall, Andreas Karwath: Large Scale Attribute Selection Using Wrappers. In: Proc IEEE Symposium on Computational Intelligence and Data Mining, 332-339, 2009.<br/>
+  * <br/>
+  * Martin Guetlein (2006). Large Scale Attribute Selection Using Wrappers. Freiburg, Germany.
+  * <p/>
+  <!-- globalinfo-end -->
+ *
+  <!-- options-start -->
+  * Valid options are: <p/>
+  * 
+  * <pre> -P &lt;start set&gt;
+  *  Specify a starting set of attributes.
+  *  Eg. 1,3,5-7.</pre>
+  * 
+  * <pre> -D &lt;0 = forward selection | 1 = floating forward selection&gt;
+  *  Forward selection method. (default = 0).</pre>
+  * 
+  * <pre> -N &lt;num&gt;
+  *  Number of non-improving nodes to
+  *  consider before terminating search.</pre>
+  * 
+  * <pre> -I
+  *  Perform initial ranking to select the
+  *  top-ranked attributes.</pre>
+  * 
+  * <pre> -K &lt;num&gt;
+  *  Number of top-ranked attributes that are 
+  *  taken into account by the search.</pre>
+  * 
+  * <pre> -T &lt;0 = fixed-set | 1 = fixed-width&gt;
+  *  Type of Linear Forward Selection (default = 0).</pre>
+  * 
+  * <pre> -S &lt;num&gt;
+  *  Size of lookup cache for evaluated subsets.
+  *  Expressed as a multiple of the number of
+  *  attributes in the data set. (default = 1)</pre>
+  * 
+  * <pre> -Z
+  *  verbose on/off</pre>
+  * 
+  <!-- options-end -->
+ *
+ * @author Martin Guetlein (martin.guetlein@gmail.com)
+ * @version $Revision: 6160 $
+ */
+public class LinearForwardSelection 
+  extends ASSearch 
+  implements OptionHandler,
+             StartSetHandler, 
+             TechnicalInformationHandler {
+  /** search directions */
+  protected static final int SEARCH_METHOD_FORWARD = 0;
+  protected static final int SEARCH_METHOD_FLOATING = 1;
+  public static final Tag[] TAGS_SEARCH_METHOD = {
+    new Tag(SEARCH_METHOD_FORWARD, "Forward selection"),
+    new Tag(SEARCH_METHOD_FLOATING, "Floating forward selection"),
+  };
+
+  /** search directions */
+  protected static final int TYPE_FIXED_SET = 0;
+  protected static final int TYPE_FIXED_WIDTH = 1;
+  public static final Tag[] TAGS_TYPE = {
+    new Tag(TYPE_FIXED_SET, "Fixed-set"),
+    new Tag(TYPE_FIXED_WIDTH, "Fixed-width"),
+  };
+
+  // member variables
+  /** maximum number of stale nodes before terminating search */
+  protected int m_maxStale;
+
+  /** 0 == forward selection, 1 == floating forward search */
+  protected int m_forwardSearchMethod;
+
+  /** perform initial ranking to select top-ranked attributes */
+  protected boolean m_performRanking;
+
+  /**
+   * number of top-ranked attributes that are taken into account for the
+   * search
+   */
+  protected int m_numUsedAttributes;
+
+  /** 0 == fixed-set, 1 == fixed-width */
+  protected int m_linearSelectionType;
+
+  /** holds an array of starting attributes */
+  protected int[] m_starting;
+
+  /** holds the start set for the search as a Range */
+  protected Range m_startRange;
+
+  /** does the data have a class */
+  protected boolean m_hasClass;
+
+  /** holds the class index */
+  protected int m_classIndex;
+
+  /** number of attributes in the data */
+  protected int m_numAttribs;
+
+  /** total number of subsets evaluated during a search */
+  protected int m_totalEvals;
+
+  /** for debugging */
+  protected boolean m_verbose;
+
+  /** holds the merit of the best subset found */
+  protected double m_bestMerit;
+
+  /** holds the maximum size of the lookup cache for evaluated subsets */
+  protected int m_cacheSize;
+
+  /**
+   * Constructor
+   */
+  public LinearForwardSelection() {
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this search method
+   *
+   * @return a description of the search method suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "LinearForwardSelection:\n\n" +
+      "Extension of BestFirst. Takes a restricted number of k attributes " +
+      "into account. Fixed-set selects a fixed number k of attributes, " +
+      "whereas k is increased in each step when fixed-width is selected. " +
+      "The search uses either the initial ordering to select the " +
+      "top k attributes, or performs a ranking (with the same evalutator the " +
+      "search uses later on). The search direction can be forward, " +
+      "or floating forward selection (with opitional backward search steps).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    TechnicalInformation        additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Martin Guetlein and Eibe Frank and Mark Hall and Andreas Karwath");
+    result.setValue(Field.YEAR, "2009");
+    result.setValue(Field.TITLE, "Large Scale Attribute Selection Using Wrappers");
+    result.setValue(Field.BOOKTITLE, "Proc IEEE Symposium on Computational Intelligence and Data Mining");
+    result.setValue(Field.PAGES, "332-339");
+    result.setValue(Field.PUBLISHER, "IEEE");
+    
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Martin Guetlein");
+    additional.setValue(Field.YEAR, "2006");
+    additional.setValue(Field.TITLE, "Large Scale Attribute Selection Using Wrappers");
+    additional.setValue(Field.SCHOOL, "Albert-Ludwigs-Universitaet");
+    additional.setValue(Field.ADDRESS, "Freiburg, Germany");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   *
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(8);
+
+    newVector.addElement(new Option("\tSpecify a starting set of attributes." +
+                                    "\n\tEg. 1,3,5-7.", "P", 1, "-P <start set>"));
+    newVector.addElement(new Option(
+                                    "\tForward selection method. (default = 0).", "D", 1,
+                                    "-D <0 = forward selection | 1 = floating forward selection>"));
+    newVector.addElement(new Option("\tNumber of non-improving nodes to" +
+                                    "\n\tconsider before terminating search.", "N", 1, "-N <num>"));
+    newVector.addElement(new Option("\tPerform initial ranking to select the" +
+                                    "\n\ttop-ranked attributes.", "I", 0, "-I"));
+    newVector.addElement(new Option(
+                                    "\tNumber of top-ranked attributes that are " +
+                                    "\n\ttaken into account by the search.", "K", 1, "-K <num>"));
+    newVector.addElement(new Option(
+                                    "\tType of Linear Forward Selection (default = 0).", "T", 1,
+                                    "-T <0 = fixed-set | 1 = fixed-width>"));
+    newVector.addElement(new Option(
+                                    "\tSize of lookup cache for evaluated subsets." +
+                                    "\n\tExpressed as a multiple of the number of" +
+                                    "\n\tattributes in the data set. (default = 1)", "S", 1, "-S <num>"));
+    newVector.addElement(new Option("\tverbose on/off", "Z", 0, "-Z"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * Valid options are:
+   * <p>
+   *
+   * -P <start set> <br>
+   * Specify a starting set of attributes. Eg 1,4,7-9.
+   * <p>
+   *
+   * -D <0 = forward selection | 1 = floating forward selection> <br>
+   * Forward selection method of the search. (default = 0).
+   * <p>
+   *
+   * -N <num> <br>
+   * Number of non improving nodes to consider before terminating search.
+   * (default = 5).
+   * <p>
+   *
+   * -I <br>
+   * Perform initial ranking to select top-ranked attributes.
+   * <p>
+   *
+   * -K <num> <br>
+   * Number of top-ranked attributes that are taken into account.
+   * <p>
+   *
+   * -T <0 = fixed-set | 1 = fixed-width> <br>
+   * Typ of Linear Forward Selection (default = 0).
+   * <p>
+   *
+   * -S <num> <br>
+   * Size of lookup cache for evaluated subsets. Expressed as a multiple of
+   * the number of attributes in the data set. (default = 1).
+   * <p>
+   *
+   * -Z <br>
+   * verbose on/off.
+   * <p>
+   *
+   * @param options
+   *            the list of options as an array of strings
+   * @exception Exception
+   *                if an option is not supported
+   *
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('P', options);
+
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('D', options);
+
+    if (optionString.length() != 0) {
+      setForwardSelectionMethod(new SelectedTag(Integer.parseInt(optionString),
+                                                TAGS_SEARCH_METHOD));
+    } else {
+      setForwardSelectionMethod(new SelectedTag(SEARCH_METHOD_FORWARD,
+                                                TAGS_SEARCH_METHOD));
+    }
+
+    optionString = Utils.getOption('N', options);
+
+    if (optionString.length() != 0) {
+      setSearchTermination(Integer.parseInt(optionString));
+    }
+
+    setPerformRanking(Utils.getFlag('I', options));
+
+    optionString = Utils.getOption('K', options);
+
+    if (optionString.length() != 0) {
+      setNumUsedAttributes(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('T', options);
+
+    if (optionString.length() != 0) {
+      setType(new SelectedTag(Integer.parseInt(optionString), TAGS_TYPE));
+    } else {
+      setType(new SelectedTag(TYPE_FIXED_SET, TAGS_TYPE));
+    }
+
+    optionString = Utils.getOption('S', options);
+
+    if (optionString.length() != 0) {
+      setLookupCacheSize(Integer.parseInt(optionString));
+    }
+
+    m_verbose = Utils.getFlag('Z', options);
+  }
+
+  /**
+   * Set the maximum size of the evaluated subset cache (hashtable). This is
+   * expressed as a multiplier for the number of attributes in the data set.
+   * (default = 1).
+   *
+   * @param size
+   *            the maximum size of the hashtable
+   */
+  public void setLookupCacheSize(int size) {
+    if (size >= 0) {
+      m_cacheSize = size;
+    }
+  }
+
+  /**
+   * Return the maximum size of the evaluated subset cache (expressed as a
+   * multiplier for the number of attributes in a data set.
+   *
+   * @return the maximum size of the hashtable.
+   */
+  public int getLookupCacheSize() {
+    return m_cacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String lookupCacheSizeTipText() {
+    return "Set the maximum size of the lookup cache of evaluated subsets. This is " +
+      "expressed as a multiplier of the number of attributes in the data set. " +
+      "(default = 1).";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Set the start point for the search. This is specified as a comma " +
+      "seperated list off attribute indexes starting at 1. It can include " +
+      "ranges. Eg. 1,2,5-9,17.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the search
+   * method's responsibility to report this start set (if any) in its
+   * toString() method.
+   *
+   * @param startSet
+   *            a string containing a list of attributes (and or ranges), eg.
+   *            1,2,6,10-15.
+   * @exception Exception
+   *                if start set can't be set.
+   */
+  public void setStartSet(String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   *
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet() {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String searchTerminationTipText() {
+    return "Set the amount of backtracking. Specify the number of ";
+  }
+
+  /**
+   * Set the numnber of non-improving nodes to consider before terminating
+   * search.
+   *
+   * @param t
+   *            the number of non-improving nodes
+   * @exception Exception
+   *                if t is less than 1
+   */
+  public void setSearchTermination(int t) throws Exception {
+    if (t < 1) {
+      throw new Exception("Value of -N must be > 0.");
+    }
+
+    m_maxStale = t;
+  }
+
+  /**
+   * Get the termination criterion (number of non-improving nodes).
+   *
+   * @return the number of non-improving nodes
+   */
+  public int getSearchTermination() {
+    return m_maxStale;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String performRankingTipText() {
+    return "Perform initial ranking to select top-ranked attributes.";
+  }
+
+  /**
+   * Perform initial ranking to select top-ranked attributes.
+   *
+   * @param b
+   *            true if initial ranking should be performed
+   */
+  public void setPerformRanking(boolean b) {
+    m_performRanking = b;
+  }
+
+  /**
+   * Get boolean if initial ranking should be performed to select the
+   * top-ranked attributes
+   *
+   * @return true if initial ranking should be performed
+   */
+  public boolean getPerformRanking() {
+    return m_performRanking;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String numUsedAttributesTipText() {
+    return "Set the amount of top-ranked attributes that are taken into account by the search process.";
+  }
+
+  /**
+   * Set the number of top-ranked attributes that taken into account by the
+   * search process.
+   *
+   * @param k
+   *            the number of attributes
+   * @exception Exception
+   *                if k is less than 2
+   */
+  public void setNumUsedAttributes(int k) throws Exception {
+    if (k < 2) {
+      throw new Exception("Value of -K must be >= 2.");
+    }
+
+    m_numUsedAttributes = k;
+  }
+
+  /**
+   * Get the number of top-ranked attributes that taken into account by the
+   * search process.
+   *
+   * @return the number of top-ranked attributes that taken into account
+   */
+  public int getNumUsedAttributes() {
+    return m_numUsedAttributes;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String forwardSelectionMethodTipText() {
+    return "Set the direction of the search.";
+  }
+
+  /**
+   * Set the search direction
+   *
+   * @param d
+   *            the direction of the search
+   */
+  public void setForwardSelectionMethod(SelectedTag d) {
+    if (d.getTags() == TAGS_SEARCH_METHOD) {
+      m_forwardSearchMethod = d.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the search direction
+   *
+   * @return the direction of the search
+   */
+  public SelectedTag getForwardSelectionMethod() {
+    return new SelectedTag(m_forwardSearchMethod, TAGS_SEARCH_METHOD);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String typeTipText() {
+    return "Set the type of the search.";
+  }
+
+  /**
+   * Set the type
+   *
+   * @param t
+   *            the Linear Forward Selection type
+   */
+  public void setType(SelectedTag t) {
+    if (t.getTags() == TAGS_TYPE) {
+      m_linearSelectionType = t.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the type
+   *
+   * @return the Linear Forward Selection type
+   */
+  public SelectedTag getType() {
+    return new SelectedTag(m_linearSelectionType, TAGS_TYPE);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "Turn on verbose output for monitoring the search's progress.";
+  }
+
+  /**
+   * Set whether verbose output should be generated.
+   *
+   * @param b
+   *            true if output is to be verbose.
+   */
+  public void setVerbose(boolean b) {
+    m_verbose = b;
+  }
+
+  /**
+   * Get whether output is to be verbose
+   *
+   * @return true if output will be verbose
+   */
+  public boolean getVerbose() {
+    return m_verbose;
+  }
+
+  /**
+   * Gets the current settings of LinearForwardSelection.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    String[] options = new String[13];
+    int current = 0;
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = "" + startSetToString();
+    }
+
+    options[current++] = "-D";
+    options[current++] = "" + m_forwardSearchMethod;
+    options[current++] = "-N";
+    options[current++] = "" + m_maxStale;
+
+    if (m_performRanking) {
+      options[current++] = "-I";
+    }
+
+    options[current++] = "-K";
+    options[current++] = "" + m_numUsedAttributes;
+    options[current++] = "-T";
+    options[current++] = "" + m_linearSelectionType;
+
+    if (m_verbose)
+      options[current++] = "-Z";
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is used by
+   * getOptions to return the actual attributes specified as the starting set.
+   * This is better than using m_startRanges.getRanges() as the same start set
+   * can be specified in different ways from the command line---eg 1,2,3 ==
+   * 1-3. This is to ensure that stuff that is stored in a database is
+   * comparable.
+   *
+   * @return a comma seperated list of individual attribute numbers as a
+   *         String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+
+    if (m_starting == null) {
+      return getStartSet();
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+
+      if ((m_hasClass == false) ||
+          ((m_hasClass == true) && (i != m_classIndex))) {
+        FString.append((m_starting[i] + 1));
+        didPrint = true;
+      }
+
+      if (i == (m_starting.length - 1)) {
+        FString.append("");
+      } else {
+        if (didPrint) {
+          FString.append(",");
+        }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * returns a description of the search as a String
+   *
+   * @return a description of the search
+   */
+  public String toString() {
+    StringBuffer LFSString = new StringBuffer();
+
+    LFSString.append("\tLinear Forward Selection.\n\tStart set: ");
+
+    if (m_starting == null) {
+      LFSString.append("no attributes\n");
+    } else {
+      LFSString.append(startSetToString() + "\n");
+    }
+
+    LFSString.append("\tForward selection method: ");
+
+    if (m_forwardSearchMethod == SEARCH_METHOD_FORWARD) {
+      LFSString.append("forward selection\n");
+    } else {
+      LFSString.append("floating forward selection\n");
+    }
+
+    LFSString.append("\tStale search after " + m_maxStale +
+                     " node expansions\n");
+
+    LFSString.append("\tLinear Forward Selection Type: ");
+
+    if (m_linearSelectionType == TYPE_FIXED_SET) {
+      LFSString.append("fixed-set\n");
+    } else {
+      LFSString.append("fixed-width\n");
+    }
+
+    LFSString.append("\tNumber of top-ranked attributes that are used: " +
+                     m_numUsedAttributes + "\n");
+
+    LFSString.append("\tTotal number of subsets evaluated: " + m_totalEvals +
+                     "\n");
+    LFSString.append("\tMerit of best subset found: " +
+                     Utils.doubleToString(Math.abs(m_bestMerit), 8, 3) + "\n");
+
+    return LFSString.toString();
+  }
+
+  /**
+   * Searches the attribute subset space by linear forward selection
+   *
+   * @param ASEval
+   *            the attribute evaluator to guide the search
+   * @param data
+   *            the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @exception Exception
+   *                if the search can't be completed
+   */
+  public int[] search(ASEvaluation ASEval, Instances data)
+    throws Exception {
+    m_totalEvals = 0;
+
+    if (!(ASEval instanceof SubsetEvaluator)) {
+      throw new Exception(ASEval.getClass().getName() + " is not a " +
+                          "Subset evaluator!");
+    }
+
+    if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+      m_hasClass = false;
+    } else {
+      m_hasClass = true;
+      m_classIndex = data.classIndex();
+    }
+
+    ((ASEvaluation) ASEval).buildEvaluator(data);
+
+    m_numAttribs = data.numAttributes();
+
+    if (m_numUsedAttributes > m_numAttribs) {
+      System.out.println(
+                         "Decreasing number of top-ranked attributes to total number of attributes: " +
+                         data.numAttributes());
+      m_numUsedAttributes = m_numAttribs;
+    }
+
+    BitSet start_group = new BitSet(m_numAttribs);
+    m_startRange.setUpper(m_numAttribs - 1);
+
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+
+    // If a starting subset has been supplied, then initialise the bitset
+    if (m_starting != null) {
+      for (int i = 0; i < m_starting.length; i++) {
+        if ((m_starting[i]) != m_classIndex) {
+          start_group.set(m_starting[i]);
+        }
+      }
+    }
+
+    LFSMethods LFS = new LFSMethods();
+
+    int[] ranking;
+
+    if (m_performRanking) {
+      ranking = LFS.rankAttributes(data, (SubsetEvaluator) ASEval, m_verbose);
+    } else {
+      ranking = new int[m_numAttribs];
+
+      for (int i = 0; i < ranking.length; i++) {
+        ranking[i] = i;
+      }
+    }
+
+    if (m_forwardSearchMethod == SEARCH_METHOD_FORWARD) {
+      LFS.forwardSearch(m_cacheSize, start_group, ranking, m_numUsedAttributes,
+                        m_linearSelectionType == TYPE_FIXED_WIDTH, m_maxStale, -1, data,
+                        (SubsetEvaluator) ASEval, m_verbose);
+    } else if (m_forwardSearchMethod == SEARCH_METHOD_FLOATING) {
+      LFS.floatingForwardSearch(m_cacheSize, start_group, ranking,
+                                m_numUsedAttributes, m_linearSelectionType == TYPE_FIXED_WIDTH,
+                                m_maxStale, data, (SubsetEvaluator) ASEval, m_verbose);
+    }
+
+    m_totalEvals = LFS.getNumEvalsTotal();
+    m_bestMerit = LFS.getBestMerit();
+
+    return attributeList(LFS.getBestGroup());
+  }
+
+  /**
+   * Reset options to default values
+   */
+  protected void resetOptions() {
+    m_maxStale = 5;
+    m_forwardSearchMethod = SEARCH_METHOD_FORWARD;
+    m_performRanking = true;
+    m_numUsedAttributes = 50;
+    m_linearSelectionType = TYPE_FIXED_SET;
+    m_starting = null;
+    m_startRange = new Range();
+    m_classIndex = -1;
+    m_totalEvals = 0;
+    m_cacheSize = 1;
+    m_verbose = false;
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes
+   *
+   * @param group
+   *            the BitSet to convert
+   * @return an array of attribute indexes
+   */
+  protected int[] attributeList(BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        list[count++] = i;
+      }
+    }
+
+    return list;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6160 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/OneRAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/OneRAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/OneRAttributeEval.java	(revision 29)
@@ -0,0 +1,485 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OneRAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * OneRAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by using the OneR classifier.<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Random number seed for cross validation
+ *  (default = 1)</pre>
+ * 
+ * <pre> -F &lt;folds&gt;
+ *  Number of folds for cross validation
+ *  (default = 10)</pre>
+ * 
+ * <pre> -D
+ *  Use training data for evaluation rather than cross validaton</pre>
+ * 
+ * <pre> -B &lt;minimum bucket size&gt;
+ *  Minimum number of objects in a bucket
+ *  (passed on to OneR, default = 6)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class OneRAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4386514823886856980L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** Random number seed */
+  private int m_randomSeed;
+
+  /** Number of folds for cross validation */
+  private int m_folds;
+
+  /** Use training data to evaluate merit rather than x-val */
+  private boolean m_evalUsingTrainingData;
+
+  /** Passed on to OneR */
+  private int m_minBucketSize;
+
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "OneRAttributeEval :\n\nEvaluates the worth of an attribute by "
+      +"using the OneR classifier.\n";
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text
+   *
+   * @return a string describing this option
+   */
+  public String seedTipText() {
+    return "Set the seed for use in cross validation.";
+  }
+
+  /**
+   * Set the random number seed for cross validation
+   *
+   * @param seed the seed to use
+   */
+  public void setSeed(int seed) {
+    m_randomSeed = seed;
+  }
+
+  /**
+   * Get the random number seed
+   *
+   * @return an <code>int</code> value
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text
+   *
+   * @return a string describing this option
+   */
+  public String foldsTipText() {
+    return "Set the number of folds for cross validation.";
+  }
+
+  /**
+   * Set the number of folds to use for cross validation
+   *
+   * @param folds the number of folds
+   */
+  public void setFolds(int folds) {
+    m_folds = folds;
+    if (m_folds < 2) {
+      m_folds = 2;
+    }
+  }
+   
+  /**
+   * Get the number of folds used for cross validation
+   *
+   * @return the number of folds
+   */
+  public int getFolds() {
+    return m_folds;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text
+   *
+   * @return a string describing this option
+   */
+  public String evalUsingTrainingDataTipText() {
+    return "Use the training data to evaluate attributes rather than "
+      + "cross validation.";
+  }
+
+  /**
+   * Use the training data to evaluate attributes rather than cross validation
+   *
+   * @param e true if training data is to be used for evaluation
+   */
+  public void setEvalUsingTrainingData(boolean e) {
+    m_evalUsingTrainingData = e;
+  }
+
+  /**
+   * Returns a string for this option suitable for display in the gui
+   * as a tip text
+   *
+   * @return a string describing this option
+   */
+  public String minimumBucketSizeTipText() {
+    return "The minimum number of objects in a bucket "
+      + "(passed to OneR).";
+  }
+
+  /**
+   * Set the minumum bucket size used by OneR
+   *
+   * @param minB the minimum bucket size to use
+   */
+  public void setMinimumBucketSize(int minB) {
+    m_minBucketSize = minB;
+  }
+
+  /**
+   * Get the minimum bucket size used by oneR
+   *
+   * @return the minimum bucket size used
+   */
+  public int getMinimumBucketSize() {
+    return m_minBucketSize;
+  }
+
+  /**
+   * Returns true if the training data is to be used for evaluation
+   *
+   * @return true if training data is to be used for evaluation
+   */
+  public boolean getEvalUsingTrainingData() {
+    return m_evalUsingTrainingData;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+        "\tRandom number seed for cross validation\n"
+        + "\t(default = 1)",
+        "S", 1, "-S <seed>"));
+
+    newVector.addElement(new Option(
+        "\tNumber of folds for cross validation\n"
+        + "\t(default = 10)",
+        "F", 1, "-F <folds>"));
+
+    newVector.addElement(new Option(
+        "\tUse training data for evaluation rather than cross validaton",
+        "D", 0, "-D"));
+
+    newVector.addElement(new Option(
+        "\tMinimum number of objects in a bucket\n"
+        + "\t(passed on to "
+        +"OneR, default = 6)",
+        "B", 1, "-B <minimum bucket size>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Random number seed for cross validation
+   *  (default = 1)</pre>
+   * 
+   * <pre> -F &lt;folds&gt;
+   *  Number of folds for cross validation
+   *  (default = 10)</pre>
+   * 
+   * <pre> -D
+   *  Use training data for evaluation rather than cross validaton</pre>
+   * 
+   * <pre> -B &lt;minimum bucket size&gt;
+   *  Minimum number of objects in a bucket
+   *  (passed on to OneR, default = 6)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String [] options) throws Exception {
+    String temp = Utils.getOption('S', options);
+
+    if (temp.length() != 0) {
+      setSeed(Integer.parseInt(temp));
+    }
+    
+    temp = Utils.getOption('F', options);
+    if (temp.length() != 0) {
+      setFolds(Integer.parseInt(temp));
+    }
+
+    temp = Utils.getOption('B', options);
+    if (temp.length() != 0) {
+      setMinimumBucketSize(Integer.parseInt(temp));
+    }
+    
+    setEvalUsingTrainingData(Utils.getFlag('D', options));
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * returns the current setup.
+   * 
+   * @return the options of the current setup
+   */
+  public String[] getOptions() {
+    String [] options = new String [7];
+    int current = 0;
+    
+    if (getEvalUsingTrainingData()) {
+      options[current++] = "-D";
+    }
+    
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+    options[current++] = "-F";
+    options[current++] = "" + getFolds();
+    options[current++] = "-B";
+    options[current++] = "" + getMinimumBucketSize();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Constructor
+   */
+  public OneRAttributeEval () {
+    resetOptions();
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a OneRAttribute attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+  }
+
+
+  /**
+   * rests to defaults.
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_randomSeed = 1;
+    m_folds = 10;
+    m_evalUsingTrainingData = false;
+    m_minBucketSize = 6; // default used by OneR
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring the amount
+   * of information gained about the class given the attribute.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+    int[] featArray = new int[2]; // feat + class
+    double errorRate;
+    Evaluation o_Evaluation;
+    Remove delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    // copy the instances
+    Instances trainCopy = new Instances(m_trainInstances);
+    featArray[0] = attribute;
+    featArray[1] = trainCopy.classIndex();
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(trainCopy);
+    trainCopy = Filter.useFilter(trainCopy, delTransform);
+    o_Evaluation = new Evaluation(trainCopy);
+    String [] oneROpts = { "-B", ""+getMinimumBucketSize()};
+    Classifier oneR = AbstractClassifier.forName("weka.classifiers.rules.OneR", oneROpts);
+    if (m_evalUsingTrainingData) {
+      oneR.buildClassifier(trainCopy);
+      o_Evaluation.evaluateModel(oneR, trainCopy);
+    } else {
+      /*      o_Evaluation.crossValidateModel("weka.classifiers.rules.OneR", 
+              trainCopy, 10, 
+              null, new Random(m_randomSeed)); */
+      o_Evaluation.crossValidateModel(oneR, trainCopy, m_folds, new Random(m_randomSeed));
+    }
+    errorRate = o_Evaluation.errorRate();
+    return  (1 - errorRate)*100.0;
+  }
+
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tOneR feature evaluator has not been built yet");
+    }
+    else {
+      text.append("\tOneR feature evaluator.\n\n");
+      text.append("\tUsing ");
+      if (m_evalUsingTrainingData) {
+        text.append("training data for evaluation of attributes.");
+      } else {
+        text.append(""+getFolds()+" fold cross validation for evaluating "
+                    +"attributes.");
+      }
+      text.append("\n\tMinimum bucket size for OneR: "
+                  +getMinimumBucketSize());
+    }
+
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new OneRAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/PrincipalComponents.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/PrincipalComponents.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/PrincipalComponents.java	(revision 29)
@@ -0,0 +1,962 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PrincipalComponents.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Matrix;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+/**
+ <!-- globalinfo-start -->
+ * Performs a principal components analysis and transformation of the data. Use in conjunction with a Ranker search. Dimensionality reduction is accomplished by choosing enough eigenvectors to account for some percentage of the variance in the original data---default 0.95 (95%). Attribute noise can be filtered by transforming to the PC space, eliminating some of the worst eigenvectors, and then transforming back to the original space.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Don't normalize input data.</pre>
+ * 
+ * <pre> -R
+ *  Retain enough PC attributes to account 
+ *  for this proportion of variance in the original data.
+ *  (default = 0.95)</pre>
+ * 
+ * <pre> -O
+ *  Transform through the PC space and 
+ *  back to the original space.</pre>
+ * 
+ * <pre> -A
+ *  Maximum number of attributes to include in 
+ *  transformed attribute names. (-1 = include all)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class PrincipalComponents 
+  extends UnsupervisedAttributeEvaluator 
+  implements AttributeTransformer, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3310137541055815078L;
+  
+  /** The data to transform analyse/transform */
+  private Instances m_trainInstances;
+
+  /** Keep a copy for the class attribute (if set) */
+  private Instances m_trainHeader;
+
+  /** The header for the transformed data format */
+  private Instances m_transformedFormat;
+
+  /** The header for data transformed back to the original space */
+  private Instances m_originalSpaceFormat;
+
+  /** Data has a class set */
+  private boolean m_hasClass;
+
+  /** Class index */
+  private int m_classIndex;
+
+  /** Number of attributes */
+  private int m_numAttribs;
+
+  /** Number of instances */
+  private int m_numInstances;
+
+  /** Correlation matrix for the original data */
+  private double [][] m_correlation;
+
+  /** Will hold the unordered linear transformations of the (normalized)
+      original data */
+  private double [][] m_eigenvectors;
+  
+  /** Eigenvalues for the corresponding eigenvectors */
+  private double [] m_eigenvalues = null;
+
+  /** Sorted eigenvalues */
+  private int [] m_sortedEigens;
+
+  /** sum of the eigenvalues */
+  private double m_sumOfEigenValues = 0.0;
+  
+  /** Filters for original data */
+  private ReplaceMissingValues m_replaceMissingFilter;
+  private Normalize m_normalizeFilter;
+  private NominalToBinary m_nominalToBinFilter;
+  private Remove m_attributeFilter;
+  
+  /** used to remove the class column if a class column is set */
+  private Remove m_attribFilter;
+
+  /** The number of attributes in the pc transformed data */
+  private int m_outputNumAtts = -1;
+  
+  /** normalize the input data? */
+  private boolean m_normalize = true;
+
+  /** the amount of varaince to cover in the original data when
+      retaining the best n PC's */
+  private double m_coverVariance = 0.95;
+
+  /** transform the data through the pc space and back to the original
+      space ? */
+  private boolean m_transBackToOriginal = false;
+  
+  /** maximum number of attributes in the transformed attribute name */
+  private int m_maxAttrsInName = 5;
+
+  /** holds the transposed eigenvectors for converting back to the
+      original space */
+  private double [][] m_eTranspose;
+
+  /**
+   * Returns a string describing this attribute transformer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Performs a principal components analysis and transformation of "
+      +"the data. Use in conjunction with a Ranker search. Dimensionality "
+      +"reduction is accomplished by choosing enough eigenvectors to "
+      +"account for some percentage of the variance in the original data---"
+      +"default 0.95 (95%). Attribute noise can be filtered by transforming "
+      +"to the PC space, eliminating some of the worst eigenvectors, and "
+      +"then transforming back to the original space.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options. <p>
+   *
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option("\tDon't normalize input data." 
+                                    , "D", 0, "-D"));
+
+    newVector.addElement(new Option("\tRetain enough PC attributes to account "
+                                    +"\n\tfor this proportion of variance in "
+                                    +"the original data.\n"
+                                    + "\t(default = 0.95)",
+                                    "R",1,"-R"));
+    
+    newVector.addElement(new Option("\tTransform through the PC space and "
+                                    +"\n\tback to the original space."
+                                    , "O", 0, "-O"));
+                                    
+    newVector.addElement(new Option("\tMaximum number of attributes to include in "
+                                    + "\n\ttransformed attribute names. (-1 = include all)"
+                                    , "A", 1, "-A"));
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Don't normalize input data.</pre>
+   * 
+   * <pre> -R
+   *  Retain enough PC attributes to account 
+   *  for this proportion of variance in the original data.
+   *  (default = 0.95)</pre>
+   * 
+   * <pre> -O
+   *  Transform through the PC space and 
+   *  back to the original space.</pre>
+   * 
+   * <pre> -A
+   *  Maximum number of attributes to include in 
+   *  transformed attribute names. (-1 = include all)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    resetOptions();
+    String optionString;
+
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      Double temp;
+      temp = Double.valueOf(optionString);
+      setVarianceCovered(temp.doubleValue());
+    }
+    optionString = Utils.getOption('A', options);
+    if (optionString.length() != 0) {
+      setMaximumAttributeNames(Integer.parseInt(optionString));
+    }
+    setNormalize(!Utils.getFlag('D', options));
+
+    setTransformBackToOriginal(Utils.getFlag('O', options));
+  }
+
+  /**
+   * Reset to defaults
+   */
+  private void resetOptions() {
+    m_coverVariance = 0.95;
+    m_normalize = true;
+    m_sumOfEigenValues = 0.0;
+    m_transBackToOriginal = false;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String normalizeTipText() {
+    return "Normalize input data.";
+  }
+
+  /**
+   * Set whether input data will be normalized.
+   * @param n true if input data is to be normalized
+   */
+  public void setNormalize(boolean n) {
+    m_normalize = n;
+  }
+
+  /**
+   * Gets whether or not input data is to be normalized
+   * @return true if input data is to be normalized
+   */
+  public boolean getNormalize() {
+    return m_normalize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String varianceCoveredTipText() {
+    return "Retain enough PC attributes to account for this proportion of "
+      +"variance.";
+  }
+
+  /**
+   * Sets the amount of variance to account for when retaining
+   * principal components
+   * @param vc the proportion of total variance to account for
+   */
+  public void setVarianceCovered(double vc) {
+    m_coverVariance = vc;
+  }
+
+  /**
+   * Gets the proportion of total variance to account for when
+   * retaining principal components
+   * @return the proportion of variance to account for
+   */
+  public double getVarianceCovered() {
+    return m_coverVariance;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maximumAttributeNamesTipText() {
+    return "The maximum number of attributes to include in transformed attribute names.";
+  }
+
+  /**
+   * Sets maximum number of attributes to include in
+   * transformed attribute names.
+   * @param m the maximum number of attributes
+   */
+  public void setMaximumAttributeNames(int m) {
+    m_maxAttrsInName = m;
+  }
+
+  /**
+   * Gets maximum number of attributes to include in
+   * transformed attribute names.
+   * @return the maximum number of attributes
+   */
+  public int getMaximumAttributeNames() {
+    return m_maxAttrsInName;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String transformBackToOriginalTipText() {
+    return "Transform through the PC space and back to the original space. "
+      +"If only the best n PCs are retained (by setting varianceCovered < 1) "
+      +"then this option will give a dataset in the original space but with "
+      +"less attribute noise.";
+  }
+
+  /**
+   * Sets whether the data should be transformed back to the original
+   * space
+   * @param b true if the data should be transformed back to the
+   * original space
+   */
+  public void setTransformBackToOriginal(boolean b) {
+    m_transBackToOriginal = b;
+  }
+  
+  /**
+   * Gets whether the data is to be transformed back to the original
+   * space.
+   * @return true if the data is to be transformed back to the original space
+   */
+  public boolean getTransformBackToOriginal() {
+    return m_transBackToOriginal;
+  }
+
+  /**
+   * Gets the current settings of PrincipalComponents
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+
+    String[] options = new String[6];
+    int current = 0;
+
+    if (!getNormalize()) {
+      options[current++] = "-D";
+    }
+
+    options[current++] = "-R";
+    options[current++] = ""+getVarianceCovered();
+
+    options[current++] = "-A";
+    options[current++] = ""+getMaximumAttributeNames();
+
+    if (getTransformBackToOriginal()) {
+      options[current++] = "-O";
+    }
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Initializes principal components and performs the analysis
+   * @param data the instances to analyse/transform
+   * @throws Exception if analysis fails
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    buildAttributeConstructor(data);
+  }
+
+  private void buildAttributeConstructor (Instances data) throws Exception {
+    m_eigenvalues = null;
+    m_outputNumAtts = -1;
+    m_attributeFilter = null;
+    m_nominalToBinFilter = null;
+    m_sumOfEigenValues = 0.0;
+    m_trainInstances = new Instances(data);
+
+    // make a copy of the training data so that we can get the class
+    // column to append to the transformed data (if necessary)
+    m_trainHeader = new Instances(m_trainInstances, 0);
+    
+    m_replaceMissingFilter = new ReplaceMissingValues();
+    m_replaceMissingFilter.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, 
+                                        m_replaceMissingFilter);
+
+    if (m_normalize) {
+      m_normalizeFilter = new Normalize();
+      m_normalizeFilter.setInputFormat(m_trainInstances);
+      m_trainInstances = Filter.useFilter(m_trainInstances, m_normalizeFilter);
+    }
+
+    m_nominalToBinFilter = new NominalToBinary();
+    m_nominalToBinFilter.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, 
+                                        m_nominalToBinFilter);
+    
+    // delete any attributes with only one distinct value or are all missing
+    Vector deleteCols = new Vector();
+    for (int i=0;i<m_trainInstances.numAttributes();i++) {
+      if (m_trainInstances.numDistinctValues(i) <=1) {
+        deleteCols.addElement(new Integer(i));
+      }
+    }
+
+    if (m_trainInstances.classIndex() >=0) {
+      // get rid of the class column
+      m_hasClass = true;
+      m_classIndex = m_trainInstances.classIndex();
+      deleteCols.addElement(new Integer(m_classIndex));
+    }
+
+    // remove columns from the data if necessary
+    if (deleteCols.size() > 0) {
+      m_attributeFilter = new Remove();
+      int [] todelete = new int [deleteCols.size()];
+      for (int i=0;i<deleteCols.size();i++) {
+        todelete[i] = ((Integer)(deleteCols.elementAt(i))).intValue();
+      }
+      m_attributeFilter.setAttributeIndicesArray(todelete);
+      m_attributeFilter.setInvertSelection(false);
+      m_attributeFilter.setInputFormat(m_trainInstances);
+      m_trainInstances = Filter.useFilter(m_trainInstances, m_attributeFilter);
+    }
+    
+    // can evaluator handle the processed data ? e.g., enough attributes?
+    getCapabilities().testWithFail(m_trainInstances);
+
+    m_numInstances = m_trainInstances.numInstances();
+    m_numAttribs = m_trainInstances.numAttributes();
+
+    fillCorrelation();
+
+    double [] d = new double[m_numAttribs]; 
+    double [][] v = new double[m_numAttribs][m_numAttribs];
+
+    Matrix corr = new Matrix(m_correlation);
+    corr.eigenvalueDecomposition(v, d);
+    m_eigenvectors = (double [][])v.clone();
+    m_eigenvalues = (double [])d.clone();
+
+    // any eigenvalues less than 0 are not worth anything --- change to 0
+    for (int i = 0; i < m_eigenvalues.length; i++) {
+      if (m_eigenvalues[i] < 0) {
+        m_eigenvalues[i] = 0.0;
+      }
+    }
+    m_sortedEigens = Utils.sort(m_eigenvalues);
+    m_sumOfEigenValues = Utils.sum(m_eigenvalues);
+
+    m_transformedFormat = setOutputFormat();
+    if (m_transBackToOriginal) {
+      m_originalSpaceFormat = setOutputFormatOriginal();
+      
+      // new ordered eigenvector matrix
+      int numVectors = (m_transformedFormat.classIndex() < 0) 
+        ? m_transformedFormat.numAttributes()
+        : m_transformedFormat.numAttributes() - 1;
+
+      double [][] orderedVectors = 
+        new double [m_eigenvectors.length][numVectors + 1];
+      
+      // try converting back to the original space
+      for (int i = m_numAttribs - 1; i > (m_numAttribs - numVectors - 1); i--) {
+        for (int j = 0; j < m_numAttribs; j++) {
+          orderedVectors[j][m_numAttribs - i] = 
+            m_eigenvectors[j][m_sortedEigens[i]];
+        }
+      }
+      
+      // transpose the matrix
+      int nr = orderedVectors.length;
+      int nc = orderedVectors[0].length;
+      m_eTranspose = 
+        new double [nc][nr];
+      for (int i = 0; i < nc; i++) {
+        for (int j = 0; j < nr; j++) {
+          m_eTranspose[i][j] = orderedVectors[j][i];
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns just the header for the transformed data (ie. an empty
+   * set of instances. This is so that AttributeSelection can
+   * determine the structure of the transformed data without actually
+   * having to get all the transformed data through transformedData().
+   * @return the header of the transformed data.
+   * @throws Exception if the header of the transformed data can't
+   * be determined.
+   */
+  public Instances transformedHeader() throws Exception {
+    if (m_eigenvalues == null) {
+      throw new Exception("Principal components hasn't been built yet");
+    }
+    if (m_transBackToOriginal) {
+      return m_originalSpaceFormat;
+    } else {
+      return m_transformedFormat;
+    }
+  }
+
+  /**
+   * Gets the transformed training data.
+   * @return the transformed training data
+   * @throws Exception if transformed data can't be returned
+   */
+  public Instances transformedData(Instances data) throws Exception {
+    if (m_eigenvalues == null) {
+      throw new Exception("Principal components hasn't been built yet");
+    }
+
+    Instances output = null;
+
+    if (m_transBackToOriginal) {
+      output = new Instances(m_originalSpaceFormat);
+    } else {
+      output = new Instances(m_transformedFormat);
+    }
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance converted = convertInstance(data.instance(i));
+      output.add(converted);
+    }
+
+    return output;
+  }
+
+  /**
+   * Evaluates the merit of a transformed attribute. This is defined
+   * to be 1 minus the cumulative variance explained. Merit can't
+   * be meaningfully evaluated if the data is to be transformed back
+   * to the original space.
+   * @param att the attribute to be evaluated
+   * @return the merit of a transformed attribute
+   * @throws Exception if attribute can't be evaluated
+   */
+  public double evaluateAttribute(int att) throws Exception {
+    if (m_eigenvalues == null) {
+      throw new Exception("Principal components hasn't been built yet!");
+    }
+
+    if (m_transBackToOriginal) {
+      return 1.0; // can't evaluate back in the original space!
+    }
+
+    // return 1-cumulative variance explained for this transformed att
+    double cumulative = 0.0;
+    for (int i = m_numAttribs - 1; i >= m_numAttribs - att - 1; i--) {
+      cumulative += m_eigenvalues[m_sortedEigens[i]];
+    }
+
+    return 1.0 - cumulative / m_sumOfEigenValues;
+  }
+
+  /**
+   * Fill the correlation matrix
+   */
+  private void fillCorrelation() {
+    m_correlation = new double[m_numAttribs][m_numAttribs];
+    double [] att1 = new double [m_numInstances];
+    double [] att2 = new double [m_numInstances];
+    double corr;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      for (int j = 0; j < m_numAttribs; j++) {
+        if (i == j) {
+          m_correlation[i][j] = 1.0;
+        } else {
+          for (int k = 0; k < m_numInstances; k++) {
+            att1[k] = m_trainInstances.instance(k).value(i);
+            att2[k] = m_trainInstances.instance(k).value(j);
+          }
+          corr = Utils.correlation(att1,att2,m_numInstances);
+          m_correlation[i][j] = corr;
+          m_correlation[j][i] = corr;
+        }
+      }
+    }
+  }
+
+  /**
+   * Return a summary of the analysis
+   * @return a summary of the analysis.
+   */
+  private String principalComponentsSummary() {
+    StringBuffer result = new StringBuffer();
+    double cumulative = 0.0;
+    Instances output = null;
+    int numVectors=0;
+
+    try {
+      output = setOutputFormat();
+      numVectors = (output.classIndex() < 0) 
+        ? output.numAttributes()
+        : output.numAttributes()-1;
+    } catch (Exception ex) {
+    }
+    //tomorrow
+    result.append("Correlation matrix\n"+matrixToString(m_correlation)
+                  +"\n\n");
+    result.append("eigenvalue\tproportion\tcumulative\n");
+    for (int i = m_numAttribs - 1; i > (m_numAttribs - numVectors - 1); i--) {
+      cumulative+=m_eigenvalues[m_sortedEigens[i]];
+      result.append(Utils.doubleToString(m_eigenvalues[m_sortedEigens[i]],9,5)
+                    +"\t"+Utils.
+                    doubleToString((m_eigenvalues[m_sortedEigens[i]] / 
+                                    m_sumOfEigenValues),
+                                     9,5)
+                    +"\t"+Utils.doubleToString((cumulative / 
+                                                m_sumOfEigenValues),9,5)
+                    +"\t"+output.attribute(m_numAttribs - i - 1).name()+"\n");
+    }
+
+    result.append("\nEigenvectors\n");
+    for (int j = 1;j <= numVectors;j++) {
+      result.append(" V"+j+'\t');
+    }
+    result.append("\n");
+    for (int j = 0; j < m_numAttribs; j++) {
+
+      for (int i = m_numAttribs - 1; i > (m_numAttribs - numVectors - 1); i--) {
+        result.append(Utils.
+                      doubleToString(m_eigenvectors[j][m_sortedEigens[i]],7,4)
+                      +"\t");
+      }
+      result.append(m_trainInstances.attribute(j).name()+'\n');
+    }
+
+    if (m_transBackToOriginal) {
+      result.append("\nPC space transformed back to original space.\n"
+                    +"(Note: can't evaluate attributes in the original "
+                    +"space)\n");
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a description of this attribute transformer
+   * @return a String describing this attribute transformer
+   */
+  public String toString() {
+    if (m_eigenvalues == null) {
+      return "Principal components hasn't been built yet!";
+    } else {
+      return "\tPrincipal Components Attribute Transformer\n\n"
+        +principalComponentsSummary();
+    }
+  }
+
+  /**
+   * Return a matrix as a String
+   * @param matrix that is decribed as a string
+   * @return a String describing a matrix
+   */
+  private String matrixToString(double [][] matrix) {
+    StringBuffer result = new StringBuffer();
+    int last = matrix.length - 1;
+
+    for (int i = 0; i <= last; i++) {
+      for (int j = 0; j <= last; j++) {
+        result.append(Utils.doubleToString(matrix[i][j],6,2)+" ");
+        if (j == last) {
+          result.append('\n');
+        }
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * Convert a pc transformed instance back to the original space
+   * 
+   * @param inst        the instance to convert
+   * @return            the processed instance
+   * @throws Exception  if something goes wrong
+   */
+  private Instance convertInstanceToOriginal(Instance inst)
+    throws Exception {
+    double[] newVals = null;
+
+    if (m_hasClass) {
+      newVals = new double[m_numAttribs+1];
+    } else {
+      newVals = new double[m_numAttribs];
+    }
+
+    if (m_hasClass) {
+      // class is always appended as the last attribute
+      newVals[m_numAttribs] = inst.value(inst.numAttributes() - 1);
+    }
+
+    for (int i = 0; i < m_eTranspose[0].length; i++) {
+      double tempval = 0.0;
+      for (int j = 1; j < m_eTranspose.length; j++) {
+        tempval += (m_eTranspose[j][i] * 
+                    inst.value(j - 1));
+       }
+      newVals[i] = tempval;
+    }
+    
+    if (inst instanceof SparseInstance) {
+      return new SparseInstance(inst.weight(), newVals);
+    } else {
+      return new DenseInstance(inst.weight(), newVals);
+    }      
+  }
+
+  /**
+   * Transform an instance in original (unormalized) format. Convert back
+   * to the original space if requested.
+   * @param instance an instance in the original (unormalized) format
+   * @return a transformed instance
+   * @throws Exception if instance cant be transformed
+   */
+  public Instance convertInstance(Instance instance) throws Exception {
+
+    if (m_eigenvalues == null) {
+      throw new Exception("convertInstance: Principal components not "
+                          +"built yet");
+    }
+
+    double[] newVals = new double[m_outputNumAtts];
+    Instance tempInst = (Instance)instance.copy();
+    if (!instance.dataset().equalHeaders(m_trainHeader)) {
+      throw new Exception("Can't convert instance: header's don't match: "
+                          +"PrincipalComponents\n"
+                          + instance.dataset().equalHeadersMsg(m_trainHeader));
+    }
+
+    m_replaceMissingFilter.input(tempInst);
+    m_replaceMissingFilter.batchFinished();
+    tempInst = m_replaceMissingFilter.output();
+
+    if (m_normalize) {
+      m_normalizeFilter.input(tempInst);
+      m_normalizeFilter.batchFinished();
+      tempInst = m_normalizeFilter.output();
+    }
+
+    m_nominalToBinFilter.input(tempInst);
+    m_nominalToBinFilter.batchFinished();
+    tempInst = m_nominalToBinFilter.output();
+
+    if (m_attributeFilter != null) {
+      m_attributeFilter.input(tempInst);
+      m_attributeFilter.batchFinished();
+      tempInst = m_attributeFilter.output();
+    }
+
+    if (m_hasClass) {
+       newVals[m_outputNumAtts - 1] = instance.value(instance.classIndex());
+    }
+
+    double cumulative = 0;
+    for (int i = m_numAttribs - 1; i >= 0; i--) {
+      double tempval = 0.0;
+      for (int j = 0; j < m_numAttribs; j++) {
+        tempval += (m_eigenvectors[j][m_sortedEigens[i]] * 
+                    tempInst.value(j));
+       }
+      newVals[m_numAttribs - i - 1] = tempval;
+      cumulative+=m_eigenvalues[m_sortedEigens[i]];
+      if ((cumulative / m_sumOfEigenValues) >= m_coverVariance) {
+        break;
+      }
+    }
+    
+    if (!m_transBackToOriginal) {
+      if (instance instanceof SparseInstance) {
+      return new SparseInstance(instance.weight(), newVals);
+      } else {
+        return new DenseInstance(instance.weight(), newVals);
+      }      
+    } else {
+      if (instance instanceof SparseInstance) {
+        return convertInstanceToOriginal(new SparseInstance(instance.weight(), 
+                                                            newVals));
+      } else {
+        return convertInstanceToOriginal(new DenseInstance(instance.weight(),
+                                                      newVals));
+      }
+    }
+  }
+
+  /**
+   * Set up the header for the PC->original space dataset
+   * 
+   * @return            the output format
+   * @throws Exception  if something goes wrong
+   */
+  private Instances setOutputFormatOriginal() throws Exception {
+    FastVector attributes = new FastVector();
+    
+    for (int i = 0; i < m_numAttribs; i++) {
+      String att = m_trainInstances.attribute(i).name();
+      attributes.addElement(new Attribute(att));
+    }
+    
+    if (m_hasClass) {
+      attributes.addElement(m_trainHeader.classAttribute().copy());
+    }
+
+    Instances outputFormat = 
+      new Instances(m_trainHeader.relationName()+"->PC->original space",
+                    attributes, 0);
+    
+    // set the class to be the last attribute if necessary
+    if (m_hasClass) {
+      outputFormat.setClassIndex(outputFormat.numAttributes()-1);
+    }
+
+    return outputFormat;
+  }
+
+  /**
+   * Set the format for the transformed data
+   * @return a set of empty Instances (header only) in the new format
+   * @throws Exception if the output format can't be set
+   */
+  private Instances setOutputFormat() throws Exception {
+    if (m_eigenvalues == null) {
+      return null;
+    }
+
+    double cumulative = 0.0;
+    FastVector attributes = new FastVector();
+     for (int i = m_numAttribs - 1; i >= 0; i--) {
+       StringBuffer attName = new StringBuffer();
+       // build array of coefficients
+       double[] coeff_mags = new double[m_numAttribs];
+       for (int j = 0; j < m_numAttribs; j++)
+         coeff_mags[j] = -Math.abs(m_eigenvectors[j][m_sortedEigens[i]]);
+       int num_attrs = (m_maxAttrsInName > 0) ? Math.min(m_numAttribs, m_maxAttrsInName) : m_numAttribs;
+       // this array contains the sorted indices of the coefficients
+       int[] coeff_inds;
+       if (m_numAttribs > 0) {
+          // if m_maxAttrsInName > 0, sort coefficients by decreasing magnitude
+          coeff_inds = Utils.sort(coeff_mags);
+       } else {
+          // if  m_maxAttrsInName <= 0, use all coeffs in original order
+          coeff_inds = new int[m_numAttribs];
+          for (int j=0; j<m_numAttribs; j++)
+            coeff_inds[j] = j;
+       }
+       // build final attName string
+       for (int j = 0; j < num_attrs; j++) {
+         double coeff_value = m_eigenvectors[coeff_inds[j]][m_sortedEigens[i]];
+         if (j > 0 && coeff_value >= 0)
+           attName.append("+");
+         attName.append(Utils.doubleToString(coeff_value,5,3)
+                        +m_trainInstances.attribute(coeff_inds[j]).name());
+       }
+       if (num_attrs < m_numAttribs)
+         attName.append("...");
+         
+       attributes.addElement(new Attribute(attName.toString()));
+       cumulative+=m_eigenvalues[m_sortedEigens[i]];
+
+       if ((cumulative / m_sumOfEigenValues) >= m_coverVariance) {
+         break;
+       }
+     }
+     
+     if (m_hasClass) {
+       attributes.addElement(m_trainHeader.classAttribute().copy());
+     }
+
+     Instances outputFormat = 
+       new Instances(m_trainInstances.relationName()+"_principal components",
+                     attributes, 0);
+
+     // set the class to be the last attribute if necessary
+     if (m_hasClass) {
+       outputFormat.setClassIndex(outputFormat.numAttributes()-1);
+     }
+     
+     m_outputNumAtts = outputFormat.numAttributes();
+     return outputFormat;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class
+   * @param argv should contain the command line arguments to the
+   * evaluator/transformer (see AttributeSelection)
+   */
+  public static void main(String [] argv) {
+    runEvaluator(new PrincipalComponents(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/RaceSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/RaceSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/RaceSearch.java	(revision 29)
@@ -0,0 +1,1779 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RaceSearch.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Statistics;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.experiment.PairedStats;
+import weka.experiment.Stats;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Races the cross validation error of competing attribute subsets. Use in conjuction with a ClassifierSubsetEval. RaceSearch has four modes:<br/>
+ * <br/>
+ * forward selection races all single attribute additions to a base set (initially  no attributes), selects the winner to become the new base set and then iterates until there is no improvement over the base set. <br/>
+ * <br/>
+ * Backward elimination is similar but the initial base set has all attributes included and races all single attribute deletions. <br/>
+ * <br/>
+ * Schemata search is a bit different. Each iteration a series of races are run in parallel. Each race in a set determines whether a particular attribute should be included or not---ie the race is between the attribute being "in" or "out". The other attributes for this race are included or excluded randomly at each point in the evaluation. As soon as one race has a clear winner (ie it has been decided whether a particular attribute should be inor not) then the next set of races begins, using the result of the winning race from the previous iteration as new base set.<br/>
+ * <br/>
+ * Rank race first ranks the attributes using an attribute evaluator and then races the ranking. The race includes no attributes, the top ranked attribute, the top two attributes, the top three attributes, etc.<br/>
+ * <br/>
+ * It is also possible to generate a raked list of attributes through the forward racing process. If generateRanking is set to true then a complete forward race will be run---that is, racing continues until all attributes have been selected. The order that they are added in determines a complete ranking of all the attributes.<br/>
+ * <br/>
+ * Racing uses paired and unpaired t-tests on cross-validation errors of competing subsets. When there is a significant difference between the means of the errors of two competing subsets then the poorer of the two can be eliminated from the race. Similarly, if there is no significant difference between the mean errors of two competing subsets and they are within some threshold of each other, then one can be eliminated from the race.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Andrew W. Moore, Mary S. Lee: Efficient Algorithms for Minimizing Cross Validation Error. In: Eleventh International Conference on Machine Learning, 190-198, 1994.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Moore1994,
+ *    author = {Andrew W. Moore and Mary S. Lee},
+ *    booktitle = {Eleventh International Conference on Machine Learning},
+ *    pages = {190-198},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Efficient Algorithms for Minimizing Cross Validation Error},
+ *    year = {1994}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;0 = forward | 1 = backward race | 2 = schemata | 3 = rank&gt;
+ *  Type of race to perform.
+ *  (default = 0).</pre>
+ * 
+ * <pre> -L &lt;significance&gt;
+ *  Significance level for comaparisons
+ *  (default = 0.001(forward/backward/rank)/0.01(schemata)).</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Threshold for error comparison.
+ *  (default = 0.001).</pre>
+ * 
+ * <pre> -A &lt;attribute evaluator&gt;
+ *  Attribute ranker to use if doing a 
+ *  rank search. Place any
+ *  evaluator options LAST on 
+ *  the command line following a "--".
+ *  eg. -A weka.attributeSelection.GainRatioAttributeEval ... -- -M.
+ *  (default = GainRatioAttributeEval)</pre>
+ * 
+ * <pre> -F &lt;0 = 10 fold | 1 = leave-one-out&gt;
+ *  Folds for cross validation
+ *  (default = 0 (1 if schemata race)</pre>
+ * 
+ * <pre> -Q
+ *  Generate a ranked list of attributes.
+ *  Forces the search to be forward
+ *  and races until all attributes have
+ *  selected, thus producing a ranking.</pre>
+ * 
+ * <pre> -N &lt;num to select&gt;
+ *  Specify number of attributes to retain from 
+ *  the ranking. Overides -T. Use in conjunction with -Q</pre>
+ * 
+ * <pre> -J &lt;threshold&gt;
+ *  Specify a theshold by which attributes
+ *  may be discarded from the ranking.
+ *  Use in conjuction with -Q</pre>
+ * 
+ * <pre> -Z
+ *  Verbose output for monitoring the search.</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.GainRatioAttributeEval:
+ * </pre>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.26 $
+ */
+public class RaceSearch 
+  extends ASSearch 
+  implements RankedOutputSearch, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4015453851212985720L;
+
+  /** the training instances */
+  private Instances m_Instances = null;
+
+  /** search types */
+  private static final int FORWARD_RACE = 0;
+  private static final int BACKWARD_RACE = 1;
+  private static final int SCHEMATA_RACE = 2;
+  private static final int RANK_RACE = 3;
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(FORWARD_RACE, "Forward selection race"),
+    new Tag(BACKWARD_RACE, "Backward elimination race"),
+    new Tag(SCHEMATA_RACE, "Schemata race"),
+    new Tag(RANK_RACE, "Rank race")
+      };
+  
+  /** the selected search type */
+  private int m_raceType = FORWARD_RACE;
+  
+  /** xval types */
+  private static final int TEN_FOLD = 0;
+  private static final int LEAVE_ONE_OUT = 1;
+  public static final Tag [] XVALTAGS_SELECTION = {
+    new Tag(TEN_FOLD, "10 Fold"),
+    new Tag(LEAVE_ONE_OUT, "Leave-one-out"),
+      };
+
+  /** the selected xval type */
+  private int m_xvalType = TEN_FOLD;
+  
+  /** the class index */
+  private int m_classIndex;
+
+  /** the number of attributes in the data */
+  private int m_numAttribs;
+
+  /** the total number of partially/fully evaluated subsets */
+  private int m_totalEvals;
+
+  /** holds the merit of the best subset found */
+  private double m_bestMerit = -Double.MAX_VALUE;
+
+  /** the subset evaluator to use */
+  private HoldOutSubsetEvaluator m_theEvaluator = null;
+
+  /** the significance level for comparisons */
+  private double m_sigLevel = 0.001;
+
+  /** threshold for comparisons */
+  private double m_delta = 0.001;
+
+  /** the number of samples above which to begin testing for similarity
+      between competing subsets */
+  private int m_samples = 20;
+
+  /** number of cross validation folds---equal to the number of instances
+      for leave-one-out cv */
+  private int m_numFolds = 10;
+
+  /** the attribute evaluator to generate the initial ranking when
+      doing a rank race */
+  private ASEvaluation m_ASEval = new GainRatioAttributeEval();
+
+  /** will hold the attribute ranking produced by the above attribute
+      evaluator if doing a rank search */
+  private int [] m_Ranking;
+
+  /** verbose output for monitoring the search and debugging */
+  private boolean m_debug = false;
+
+  /** If true then produce a ranked list of attributes by fully traversing
+      a forward hillclimb race */
+  private boolean m_rankingRequested = false;
+
+  /** The ranked list of attributes produced if m_rankingRequested is true */
+  private double [][] m_rankedAtts;
+
+  /** The number of attributes ranked so far (if ranking is requested) */
+  private int m_rankedSoFar;
+
+  /** The number of attributes to retain if a ranking is requested. -1
+      indicates that all attributes are to be retained. Has precedence over
+      m_threshold */
+  private int m_numToSelect = -1;
+
+  private int m_calculatedNumToSelect = -1;
+
+  /** the threshold for removing attributes if ranking is requested */
+  private double m_threshold = -Double.MAX_VALUE;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search method suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Races the cross validation error of competing "
+      +"attribute subsets. Use in conjuction with a ClassifierSubsetEval. "
+      +"RaceSearch has four modes:\n\nforward selection "
+      +"races all single attribute additions to a base set (initially "
+      +" no attributes), selects the winner to become the new base set "
+      +"and then iterates until there is no improvement over the base set. "
+      +"\n\nBackward elimination is similar but the initial base set has all "
+      +"attributes included and races all single attribute deletions. "
+      +"\n\nSchemata search is a bit different. Each iteration a series of "
+      +"races are run in parallel. Each race in a set determines whether "
+      +"a particular attribute should be included or not---ie the race is "
+      +"between the attribute being \"in\" or \"out\". The other attributes "
+      +"for this race are included or excluded randomly at each point in the "
+      +"evaluation. As soon as one race "
+      +"has a clear winner (ie it has been decided whether a particular "
+      +"attribute should be inor not) then the next set of races begins, "
+      +"using the result of the winning race from the previous iteration as "
+      +"new base set.\n\nRank race first ranks the attributes using an "
+      +"attribute evaluator and then races the ranking. The race includes "
+      +"no attributes, the top ranked attribute, the top two attributes, the "
+      +"top three attributes, etc.\n\nIt is also possible to generate a "
+      +"raked list of attributes through the forward racing process. "
+      +"If generateRanking is set to true then a complete forward race will "
+      +"be run---that is, racing continues until all attributes have been "
+      +"selected. The order that they are added in determines a complete "
+      +"ranking of all the attributes.\n\nRacing uses paired and unpaired "
+      +"t-tests on cross-validation errors of competing subsets. When there "
+      +"is a significant difference between the means of the errors of two "
+      +"competing subsets then the poorer of the two can be eliminated from "
+      +"the race. Similarly, if there is no significant difference between "
+      +"the mean errors of two competing subsets and they are within some "
+      +"threshold of each other, then one can be eliminated from the race.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Andrew W. Moore and Mary S. Lee");
+    result.setValue(Field.TITLE, "Efficient Algorithms for Minimizing Cross Validation Error");
+    result.setValue(Field.BOOKTITLE, "Eleventh International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1994");
+    result.setValue(Field.PAGES, "190-198");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String raceTypeTipText() {
+    return "Set the type of search.";
+  }
+
+  /**
+   * Set the race type
+   *
+   * @param d the type of race
+   */
+  public void setRaceType (SelectedTag d) {
+    
+    if (d.getTags() == TAGS_SELECTION) {
+      m_raceType = d.getSelectedTag().getID();
+    }
+    if (m_raceType == SCHEMATA_RACE && !m_rankingRequested) {
+      try {
+        setFoldsType(new SelectedTag(LEAVE_ONE_OUT,
+                                     XVALTAGS_SELECTION));
+        setSignificanceLevel(0.01);
+      } catch (Exception ex) {
+      }
+    } else {
+      try {
+        setFoldsType(new SelectedTag(TEN_FOLD,
+                                     XVALTAGS_SELECTION));
+        setSignificanceLevel(0.001);
+      } catch (Exception ex) {
+      }
+    }
+  }
+
+  /**
+   * Get the race type
+   *
+   * @return the type of race
+   */
+  public SelectedTag getRaceType() {
+    return new SelectedTag(m_raceType, TAGS_SELECTION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String significanceLevelTipText() {
+    return "Set the significance level to use for t-test comparisons.";
+  }
+
+  /**
+   * Sets the significance level to use
+   * @param sig the significance level
+   */
+  public void setSignificanceLevel(double sig) {
+    m_sigLevel = sig;
+  }
+
+  /**
+   * Get the significance level
+   * @return the current significance level
+   */
+  public double getSignificanceLevel() {
+    return m_sigLevel;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Set the error threshold by which to consider two subsets "
+      +"equivalent.";
+  }
+
+  /**
+   * Sets the threshold for comparisons
+   * @param t the threshold to use
+   */
+  public void setThreshold(double t) {
+    m_delta = t;
+  }
+
+  /**
+   * Get the threshold
+   * @return the current threshold
+   */
+  public double getThreshold() {
+    return m_delta;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTypeTipText() {
+    return "Set the number of folds to use for x-val error estimation; "
+      +"leave-one-out is selected automatically for schemata search.";
+  }
+
+  /**
+   * Set the xfold type
+   *
+   * @param d the type of xval
+   */
+  public void setFoldsType (SelectedTag d) {
+    
+    if (d.getTags() == XVALTAGS_SELECTION) {
+      m_xvalType = d.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the xfold type
+   *
+   * @return the type of xval
+   */
+  public SelectedTag getFoldsType () {
+    return new SelectedTag(m_xvalType, XVALTAGS_SELECTION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Turn on verbose output for monitoring the search's progress.";
+  }
+
+  /**
+   * Set whether verbose output should be generated.
+   * @param d true if output is to be verbose.
+   */
+  public void setDebug(boolean d) {
+    m_debug = d;
+  }
+
+  /**
+   * Get whether output is to be verbose
+   * @return true if output will be verbose
+   */
+  public boolean getDebug() {
+    return m_debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeEvaluatorTipText() {
+    return "Attribute evaluator to use for generating an initial ranking. "
+      +"Use in conjunction with a rank race";    
+  }
+
+  /**
+   * Set the attribute evaluator to use for generating the ranking.
+   * @param newEvaluator the attribute evaluator to use.
+   */
+  public void setAttributeEvaluator(ASEvaluation newEvaluator) {
+    m_ASEval = newEvaluator;
+  }
+
+  /**
+   * Get the attribute evaluator used to generate the ranking.
+   * @return the evaluator used to generate the ranking.
+   */
+  public ASEvaluation getAttributeEvaluator() {
+    return m_ASEval;
+  }
+
+    /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String generateRankingTipText() {
+    return "Use the racing process to generate a ranked list of attributes. "
+      +"Using this mode forces the race to be a forward type and then races "
+      +"until all attributes have been added, thus giving a ranked list";
+  }
+  
+  /**
+   * Records whether the user has requested a ranked list of attributes.
+   * @param doRank true if ranking is requested
+   */
+  public void setGenerateRanking(boolean doRank) {
+    m_rankingRequested = doRank;
+    if (m_rankingRequested) {
+      try {
+        setRaceType(new SelectedTag(FORWARD_RACE,
+                                    TAGS_SELECTION));
+      } catch (Exception ex) {
+      }
+    }
+  }
+
+  /**
+   * Gets whether ranking has been requested. This is used by the
+   * AttributeSelection module to determine if rankedAttributes()
+   * should be called.
+   * @return true if ranking has been requested.
+   */
+  public boolean getGenerateRanking() {
+    return m_rankingRequested;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numToSelectTipText() {
+    return "Specify the number of attributes to retain. Use in conjunction "
+      +"with generateRanking. The default value "
+      +"(-1) indicates that all attributes are to be retained. Use either "
+      +"this option or a threshold to reduce the attribute set.";
+  }
+
+  /**
+   * Specify the number of attributes to select from the ranked list
+   * (if generating a ranking). -1
+   * indicates that all attributes are to be retained.
+   * @param n the number of attributes to retain
+   */
+  public void setNumToSelect(int n) {
+    m_numToSelect = n;
+  }
+
+  /**
+   * Gets the number of attributes to be retained.
+   * @return the number of attributes to retain
+   */
+  public int getNumToSelect() {
+    return m_numToSelect;
+  }
+
+  /**
+   * Gets the calculated number of attributes to retain. This is the
+   * actual number of attributes to retain. This is the same as
+   * getNumToSelect if the user specifies a number which is not less
+   * than zero. Otherwise it should be the number of attributes in the
+   * (potentially transformed) data.
+   */
+  public int getCalculatedNumToSelect() {
+    if (m_numToSelect >= 0) {
+      m_calculatedNumToSelect = m_numToSelect;
+    }
+    return m_calculatedNumToSelect;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String selectionThresholdTipText() {
+    return "Set threshold by which attributes can be discarded. Default value "
+      + "results in no attributes being discarded. Use in conjunction with "
+      + "generateRanking";
+  }
+
+  /**
+   * Set the threshold by which the AttributeSelection module can discard
+   * attributes.
+   * @param threshold the threshold.
+   */
+  public void setSelectionThreshold(double threshold) {
+    m_threshold = threshold;
+  }
+
+  /**
+   * Returns the threshold so that the AttributeSelection module can
+   * discard attributes from the ranking.
+   */
+  public double getSelectionThreshold() {
+    return m_threshold;
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector();
+    
+     newVector.addElement(new Option(
+	 "\tType of race to perform.\n"
+	 + "\t(default = 0).",
+	 "R", 1 ,"-R <0 = forward | 1 = backward race | 2 = schemata | 3 = rank>"));
+     
+     newVector.addElement(new Option(
+	 "\tSignificance level for comaparisons\n"
+	 + "\t(default = 0.001(forward/backward/rank)/0.01(schemata)).",
+	 "L",1,"-L <significance>"));
+     
+     newVector.addElement(new Option(
+	 "\tThreshold for error comparison.\n"
+	 + "\t(default = 0.001).",
+	 "T",1,"-T <threshold>"));
+     
+     newVector.addElement(new Option(
+	 "\tAttribute ranker to use if doing a \n"
+	 + "\trank search. Place any\n"
+	 + "\tevaluator options LAST on \n"
+	 + "\tthe command line following a \"--\".\n" 
+	 + "\teg. -A weka.attributeSelection.GainRatioAttributeEval ... -- -M.\n"
+	 + "\t(default = GainRatioAttributeEval)", 
+	 "A", 1, "-A <attribute evaluator>"));
+    
+     newVector.addElement(new Option(
+	 "\tFolds for cross validation\n"
+	 + "\t(default = 0 (1 if schemata race)",
+	 "F",1,"-F <0 = 10 fold | 1 = leave-one-out>"));
+     
+     newVector.addElement(new Option(
+	 "\tGenerate a ranked list of attributes.\n"
+	 +"\tForces the search to be forward\n"
+	 +"\tand races until all attributes have\n"
+	 +"\tselected, thus producing a ranking.",
+	 "Q",0,"-Q"));
+
+    newVector.addElement(new Option(
+	"\tSpecify number of attributes to retain from \n"
+	+ "\tthe ranking. Overides -T. Use in conjunction with -Q", 
+	"N", 1, "-N <num to select>"));
+
+    newVector.addElement(new Option(
+	"\tSpecify a theshold by which attributes\n" 
+	+ "\tmay be discarded from the ranking.\n"
+	+"\tUse in conjuction with -Q",
+	"J",1, "-J <threshold>"));
+
+     newVector.addElement(new Option(
+	 "\tVerbose output for monitoring the search.",
+	 "Z",0,"-Z"));
+     
+     if ((m_ASEval != null) && 
+         (m_ASEval instanceof OptionHandler)) {
+       newVector.addElement(new Option(
+	   "", 
+	   "", 0, "\nOptions specific to evaluator " 
+	   + m_ASEval.getClass().getName() + ":"));
+
+       Enumeration enu = ((OptionHandler)m_ASEval).listOptions();
+       while (enu.hasMoreElements()) {
+         newVector.addElement(enu.nextElement());
+       }
+     }
+     
+     return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;0 = forward | 1 = backward race | 2 = schemata | 3 = rank&gt;
+   *  Type of race to perform.
+   *  (default = 0).</pre>
+   * 
+   * <pre> -L &lt;significance&gt;
+   *  Significance level for comaparisons
+   *  (default = 0.001(forward/backward/rank)/0.01(schemata)).</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Threshold for error comparison.
+   *  (default = 0.001).</pre>
+   * 
+   * <pre> -A &lt;attribute evaluator&gt;
+   *  Attribute ranker to use if doing a 
+   *  rank search. Place any
+   *  evaluator options LAST on 
+   *  the command line following a "--".
+   *  eg. -A weka.attributeSelection.GainRatioAttributeEval ... -- -M.
+   *  (default = GainRatioAttributeEval)</pre>
+   * 
+   * <pre> -F &lt;0 = 10 fold | 1 = leave-one-out&gt;
+   *  Folds for cross validation
+   *  (default = 0 (1 if schemata race)</pre>
+   * 
+   * <pre> -Q
+   *  Generate a ranked list of attributes.
+   *  Forces the search to be forward
+   *  and races until all attributes have
+   *  selected, thus producing a ranking.</pre>
+   * 
+   * <pre> -N &lt;num to select&gt;
+   *  Specify number of attributes to retain from 
+   *  the ranking. Overides -T. Use in conjunction with -Q</pre>
+   * 
+   * <pre> -J &lt;threshold&gt;
+   *  Specify a theshold by which attributes
+   *  may be discarded from the ranking.
+   *  Use in conjuction with -Q</pre>
+   * 
+   * <pre> -Z
+   *  Verbose output for monitoring the search.</pre>
+   * 
+   * <pre> 
+   * Options specific to evaluator weka.attributeSelection.GainRatioAttributeEval:
+   * </pre>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+    
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setRaceType(new SelectedTag(Integer.parseInt(optionString),
+                                  TAGS_SELECTION));
+    }
+    
+    optionString = Utils.getOption('F', options);
+    if (optionString.length() != 0) {
+      setFoldsType(new SelectedTag(Integer.parseInt(optionString),
+                                  XVALTAGS_SELECTION));
+    }
+
+    optionString = Utils.getOption('L', options);
+    if (optionString.length() !=0) {
+      setSignificanceLevel(Double.parseDouble(optionString));
+    }
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() !=0) {
+      setThreshold(Double.parseDouble(optionString));
+    }
+
+    optionString = Utils.getOption('A', options);
+    if (optionString.length() != 0) {
+      setAttributeEvaluator(ASEvaluation.forName(optionString, 
+                            Utils.partitionOptions(options)));
+    }
+
+    setGenerateRanking(Utils.getFlag('Q', options));
+
+    optionString = Utils.getOption('J', options);
+    if (optionString.length() != 0) {
+      setSelectionThreshold(Double.parseDouble(optionString));
+    }
+    
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumToSelect(Integer.parseInt(optionString));
+    }
+
+    setDebug(Utils.getFlag('Z', options));
+  }
+
+  /**
+   * Gets the current settings of BestFirst.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    int current = 0;
+    String[] evaluatorOptions = new String[0];
+
+    if ((m_ASEval != null) && 
+        (m_ASEval instanceof OptionHandler)) {
+      evaluatorOptions = ((OptionHandler)m_ASEval).getOptions();
+    }
+    String[] options = new String[17+evaluatorOptions.length];
+
+    options[current++] = "-R"; options[current++] = ""+m_raceType;
+    options[current++] = "-L"; options[current++] = ""+getSignificanceLevel();
+    options[current++] = "-T"; options[current++] = ""+getThreshold();
+    options[current++] = "-F"; options[current++] = ""+m_xvalType;
+    if (getGenerateRanking()) {
+      options[current++] = "-Q";
+    }
+    options[current++] = "-N"; options[current++] = ""+getNumToSelect();
+    options[current++] = "-J"; options[current++] = ""+getSelectionThreshold();
+    if (getDebug()) {
+      options[current++] = "-Z";
+    }
+    
+    if (getAttributeEvaluator() != null) {
+      options[current++] = "-A";
+      options[current++] = getAttributeEvaluator().getClass().getName();
+      options[current++] = "--";
+      System.arraycopy(evaluatorOptions, 0, options, current, 
+                       evaluatorOptions.length);
+      current += evaluatorOptions.length;
+    }
+
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+
+
+
+  /**
+   * Searches the attribute subset space by racing cross validation
+   * errors of competing subsets
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+    if (!(ASEval instanceof SubsetEvaluator)) {
+      throw  new Exception(ASEval.getClass().getName() 
+                           + " is not a " 
+                           + "Subset evaluator! (RaceSearch)");
+    }
+
+    if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+      throw new Exception("Can't use an unsupervised subset evaluator "
+                          +"(RaceSearch).");
+    }
+
+    if (!(ASEval instanceof HoldOutSubsetEvaluator)) {
+      throw new Exception("Must use a HoldOutSubsetEvaluator, eg. "
+                          +"weka.attributeSelection.ClassifierSubsetEval "
+                          +"(RaceSearch)");
+    }
+
+    if (!(ASEval instanceof ErrorBasedMeritEvaluator)) {
+      throw new Exception("Only error based subset evaluators can be used, "
+                          +"eg. weka.attributeSelection.ClassifierSubsetEval "
+                          +"(RaceSearch)");
+    }
+
+    m_Instances = new Instances(data);
+    m_Instances.deleteWithMissingClass();
+    if (m_Instances.numInstances() == 0) {
+      throw new Exception("All train instances have missing class! (RaceSearch)");
+    }
+    if (m_rankingRequested && m_numToSelect > m_Instances.numAttributes()-1) {
+      throw new Exception("More attributes requested than exist in the data "
+                          +"(RaceSearch).");
+    }
+    m_theEvaluator = (HoldOutSubsetEvaluator)ASEval;
+    m_numAttribs = m_Instances.numAttributes();
+    m_classIndex = m_Instances.classIndex();
+
+    if (m_rankingRequested) {
+      m_rankedAtts = new double[m_numAttribs-1][2];
+      m_rankedSoFar = 0;
+    }
+
+    if (m_xvalType == LEAVE_ONE_OUT) {
+      m_numFolds = m_Instances.numInstances();
+    } else {
+      m_numFolds = 10;
+    }
+
+    Random random = new Random(1); // I guess this should really be a parameter?
+    m_Instances.randomize(random);
+    int [] bestSubset=null;
+
+    switch (m_raceType) {
+    case FORWARD_RACE:
+    case BACKWARD_RACE: 
+      bestSubset = hillclimbRace(m_Instances, random);
+      break;
+    case SCHEMATA_RACE:
+      bestSubset = schemataRace(m_Instances, random);
+      break;
+    case RANK_RACE:
+      bestSubset = rankRace(m_Instances, random);
+      break;
+    }
+
+    return bestSubset;
+  }
+
+  public double [][] rankedAttributes() throws Exception {
+    if (!m_rankingRequested) {
+      throw new Exception("Need to request a ranked list of attributes "
+                          +"before attributes can be ranked (RaceSearch).");
+    }
+    if (m_rankedAtts == null) {
+      throw new Exception("Search must be performed before attributes "
+                          +"can be ranked (RaceSearch).");
+    }
+    
+    double [][] final_rank = new double [m_rankedSoFar][2];
+    for (int i=0;i<m_rankedSoFar;i++) {
+      final_rank[i][0] = m_rankedAtts[i][0];
+      final_rank[i][1] = m_rankedAtts[i][1];
+    }
+
+    if (m_numToSelect <= 0) {
+      if (m_threshold == -Double.MAX_VALUE) {
+        m_calculatedNumToSelect = final_rank.length;
+      } else {
+        determineNumToSelectFromThreshold(final_rank);
+      }
+    }
+
+    return final_rank;
+  }
+
+  private void determineNumToSelectFromThreshold(double [][] ranking) {
+    int count = 0;
+    for (int i = 0; i < ranking.length; i++) {
+      if (ranking[i][1] > m_threshold) {
+        count++;
+      }
+    }
+    m_calculatedNumToSelect = count;
+  }
+
+  /**
+   * Print an attribute set.
+   */
+  private String printSets(char [][]raceSets) {
+    StringBuffer temp = new StringBuffer();
+    for (int i=0;i<raceSets.length;i++) {
+      for (int j=0;j<m_numAttribs;j++) {
+        temp.append(raceSets[i][j]);
+      }
+      temp.append('\n');
+    }
+    return temp.toString();
+  }
+
+  /**
+   * Performs a schemata race---a series of races in parallel.
+   * @param data the instances to estimate accuracy over.
+   * @param random a random number generator
+   * @return an array of selected attribute indices.
+   */
+  private int [] schemataRace(Instances data, Random random) throws Exception {
+    // # races, 2 (competitors in each race), # attributes
+    char [][][] parallelRaces;
+    int numRaces = m_numAttribs-1;
+    Random r = new Random(42);
+    int numInstances = data.numInstances();
+    Instances trainCV; Instances testCV;
+    Instance testInstance;
+
+    // statistics on the racers
+    Stats [][] raceStats = new Stats[numRaces][2];
+    
+    parallelRaces = new char [numRaces][2][m_numAttribs-1];
+    char [] base = new char [m_numAttribs];
+    for (int i=0;i<m_numAttribs;i++) {
+      base[i] = '*';
+    }
+
+    int count=0;
+    // set up initial races
+    for (int i=0;i<m_numAttribs;i++) {
+      if (i != m_classIndex) {
+        parallelRaces[count][0] = (char [])base.clone();
+        parallelRaces[count][1] = (char [])base.clone();
+        parallelRaces[count][0][i] = '1';
+        parallelRaces[count++][1][i] = '0';
+      }
+    }
+    
+    if (m_debug) {
+      System.err.println("Initial sets:\n");
+      for (int i=0;i<numRaces;i++) {
+        System.err.print(printSets(parallelRaces[i])+"--------------\n");
+      }
+    }
+    
+    BitSet randomB = new BitSet(m_numAttribs);
+    char [] randomBC = new char [m_numAttribs];
+
+    // notes which bit positions have been decided
+    boolean [] attributeConstraints = new boolean[m_numAttribs];
+    double error;
+    int evaluationCount = 0;
+    raceSet: while (numRaces > 0) {
+      boolean won = false;
+      for (int i=0;i<numRaces;i++) {
+        raceStats[i][0] = new Stats();
+        raceStats[i][1] = new Stats();
+      }
+
+      // keep an eye on how many test instances have been randomly sampled
+      int sampleCount = 0;
+      // run the current set of races
+      while (!won) {
+        // generate a random binary string
+        for (int i=0;i<m_numAttribs;i++) {
+          if (i != m_classIndex) {
+            if (!attributeConstraints[i]) {
+              if (r.nextDouble() < 0.5) {
+                randomB.set(i);
+              } else {
+                randomB.clear(i);
+              }
+            } else { // this position has been decided from previous races
+              if (base[i] == '1') { 
+                randomB.set(i);
+              } else {
+                randomB.clear(i);
+              }
+            }
+          }
+        }
+        
+        // randomly select an instance to test on
+        int testIndex = Math.abs(r.nextInt() % numInstances);
+
+
+        // We want to randomize the data the same way for every 
+        // learning scheme.
+        trainCV = data.trainCV(numInstances, testIndex, new Random (1));
+        testCV = data.testCV(numInstances, testIndex);
+        testInstance = testCV.instance(0);
+        sampleCount++;
+        /*      if (sampleCount > numInstances) {
+          throw new Exception("raceSchemata: No clear winner after sampling "
+                              +sampleCount+" instances.");
+                              } */
+        
+        m_theEvaluator.buildEvaluator(trainCV);
+        
+        // the evaluator must retrain for every test point
+        error = -((HoldOutSubsetEvaluator)m_theEvaluator).
+          evaluateSubset(randomB, 
+                         testInstance,
+                         true);
+        evaluationCount++;
+        
+        // see which racers match this random subset
+        for (int i=0;i<m_numAttribs;i++) {
+          if (randomB.get(i)) {
+            randomBC[i] = '1';
+          } else {
+            randomBC[i] = '0';
+          }
+        }
+        //      System.err.println("Random subset: "+(new String(randomBC)));
+
+        checkRaces: for (int i=0;i<numRaces;i++) {
+          // if a pair of racers has evaluated more than num instances
+          // then bail out---unlikely that having any more atts is any
+          // better than the current base set.
+          if (((raceStats[i][0].count + raceStats[i][1].count) / 2) > 
+              (numInstances)) {
+            break raceSet;
+          }
+          for (int j=0;j<2;j++) {
+            boolean matched = true;
+            for (int k =0;k<m_numAttribs;k++) {
+              if (parallelRaces[i][j][k] != '*') {
+                if (parallelRaces[i][j][k] != randomBC[k]) {
+                  matched = false;
+                  break;
+                }
+              }
+            }
+            if (matched) { // update the stats for this racer
+              //              System.err.println("Matched "+i+" "+j);
+              raceStats[i][j].add(error);
+
+                // does this race have a clear winner, meaning we can
+                // terminate the whole set of parallel races?
+                if (raceStats[i][0].count > m_samples &&
+                    raceStats[i][1].count > m_samples) {
+                  raceStats[i][0].calculateDerived();
+                  raceStats[i][1].calculateDerived();
+                  //              System.err.println(j+" : "+(new String(parallelRaces[i][j])));
+                  //              System.err.println(raceStats[i][0]);
+                  //              System.err.println(raceStats[i][1]);
+                  // check the ttest
+                  double prob = ttest(raceStats[i][0], raceStats[i][1]);
+                  //              System.err.println("Prob :"+prob);
+                  if (prob < m_sigLevel) { // stop the races we have a winner!
+                    if (raceStats[i][0].mean < raceStats[i][1].mean) {
+                      base = (char [])parallelRaces[i][0].clone();
+                      m_bestMerit = raceStats[i][0].mean;
+                      if (m_debug) {
+                        System.err.println("contender 0 won ");
+                      }
+                    } else {
+                      base = (char [])parallelRaces[i][1].clone();
+                      m_bestMerit = raceStats[i][1].mean;
+                      if (m_debug) {
+                        System.err.println("contender 1 won");
+                      }
+                    }
+                    if (m_debug) {
+                      System.err.println((new String(parallelRaces[i][0]))
+                                 +" "+(new String(parallelRaces[i][1])));
+                      System.err.println("Means : "+raceStats[i][0].mean
+                                         +" vs"+raceStats[i][1].mean);
+                      System.err.println("Evaluations so far : "
+                                         +evaluationCount);
+                    }
+                    won = true;
+                    break checkRaces;
+                  }
+                }
+             
+            }
+          }
+        }
+      }
+
+      numRaces--;
+      // set up the next set of races if necessary
+      if (numRaces > 0 && won) {
+        parallelRaces = new char [numRaces][2][m_numAttribs-1];
+        raceStats = new Stats[numRaces][2];
+        // update the attribute constraints
+        for (int i=0;i<m_numAttribs;i++) {
+          if (i != m_classIndex && !attributeConstraints[i] &&
+              base[i] != '*') {
+            attributeConstraints[i] = true;
+            break;
+          }
+        }
+        count=0;
+        for (int i=0;i<numRaces;i++) {
+          parallelRaces[i][0] = (char [])base.clone();
+          parallelRaces[i][1] = (char [])base.clone();
+          for (int j=count;j<m_numAttribs;j++) {
+            if (j != m_classIndex && parallelRaces[i][0][j] == '*') {
+              parallelRaces[i][0][j] = '1';
+              parallelRaces[i][1][j] = '0';
+              count = j+1;
+              break;
+            }
+          }
+        }
+        
+        if (m_debug) {
+          System.err.println("Next sets:\n");
+          for (int i=0;i<numRaces;i++) {
+            System.err.print(printSets(parallelRaces[i])+"--------------\n");
+          }
+        }
+      }
+    }
+
+    if (m_debug) {
+      System.err.println("Total evaluations : "
+                         +evaluationCount);
+    }
+    return attributeList(base);
+  }
+
+  /** 
+   * t-test for unequal sample sizes and same variance. Returns probability
+   * that observed difference in means is due to chance.
+   */
+  private double ttest(Stats c1, Stats c2) throws Exception {
+    double n1 = c1.count; double n2 = c2.count;
+    double v1 = c1.stdDev * c1.stdDev;
+    double v2 = c2.stdDev * c2.stdDev;
+    double av1 = c1.mean;
+    double av2 = c2.mean;
+    
+    double df = n1 + n2 - 2;
+    double cv = (((n1 - 1) * v1) + ((n2 - 1) * v2)) /df;
+    double t = (av1 - av2) / Math.sqrt(cv * ((1.0 / n1) + (1.0 / n2)));
+    
+    return Statistics.incompleteBeta(df / 2.0, 0.5,
+                                     df / (df + (t * t)));
+  }
+    
+  /**
+   * Performs a rank race---race consisting of no attributes, the top
+   * ranked attribute, the top two attributes etc. The initial ranking
+   * is determined by an attribute evaluator.
+   * @param data the instances to estimate accuracy over
+   * @param random a random number generator
+   * @return an array of selected attribute indices.
+   */
+  private int [] rankRace(Instances data, Random random) throws Exception {
+    char [] baseSet = new char [m_numAttribs];
+    char [] bestSet;
+    double bestSetError;
+    for (int i=0;i<m_numAttribs;i++) {
+      if (i == m_classIndex) {
+        baseSet[i] = '-';
+      } else {
+        baseSet[i] = '0';
+      }
+    }
+
+    int numCompetitors = m_numAttribs-1;
+    char [][] raceSets = new char [numCompetitors+1][m_numAttribs];
+    
+    if (m_ASEval instanceof AttributeEvaluator) {
+      // generate the attribute ranking first
+      Ranker ranker = new Ranker();
+      m_ASEval.buildEvaluator(data);
+      m_Ranking = ranker.search(m_ASEval,data);
+    } else {
+      GreedyStepwise fs = new GreedyStepwise();
+      double [][]rankres; 
+      fs.setGenerateRanking(true);
+      ((ASEvaluation)m_ASEval).buildEvaluator(data);
+      fs.search(m_ASEval, data);
+      rankres = fs.rankedAttributes();
+      m_Ranking = new int[rankres.length];
+      for (int i=0;i<rankres.length;i++) {
+        m_Ranking[i] = (int)rankres[i][0];
+      }
+    }
+
+    // set up the race
+    raceSets[0] = (char [])baseSet.clone();
+    for (int i=0;i<m_Ranking.length;i++) {
+      raceSets[i+1] = (char [])raceSets[i].clone();
+      raceSets[i+1][m_Ranking[i]] = '1';
+    }
+    
+    if (m_debug) {
+      System.err.println("Initial sets:\n"+printSets(raceSets));
+    }
+    
+    // run the race
+    double [] winnerInfo = raceSubsets(raceSets, data, true, random);
+    bestSetError = winnerInfo[1];
+    bestSet = (char [])raceSets[(int)winnerInfo[0]].clone();
+    m_bestMerit = bestSetError;
+    return attributeList(bestSet);
+  }
+  
+  /**
+   * Performs a hill climbing race---all single attribute changes to a
+   * base subset are raced in parallel. The winner is chosen and becomes
+   * the new base subset and the process is repeated until there is no
+   * improvement in error over the base subset.
+   * @param data the instances to estimate accuracy over
+   * @param random a random number generator
+   * @return an array of selected attribute indices.
+   * @throws Exception if something goes wrong
+   */
+  private int [] hillclimbRace(Instances data, Random random) throws Exception {
+    double baseSetError;
+    char [] baseSet = new char [m_numAttribs];
+
+    for (int i=0;i<m_numAttribs;i++) {
+      if (i != m_classIndex) {
+        if (m_raceType == FORWARD_RACE) {
+          baseSet[i] = '0';
+        } else {
+          baseSet[i] = '1';
+        } 
+      } else {
+        baseSet[i] = '-';
+      }
+    }
+
+    int numCompetitors = m_numAttribs-1;
+    char [][] raceSets = new char [numCompetitors+1][m_numAttribs];
+
+    raceSets[0] = (char [])baseSet.clone();
+    int count = 1;
+    // initialize each race set to 1 attribute
+    for (int i=0;i<m_numAttribs;i++) {
+      if (i != m_classIndex) {
+        raceSets[count] = (char [])baseSet.clone();
+        if (m_raceType == BACKWARD_RACE) {
+          raceSets[count++][i] = '0';
+        } else {
+          raceSets[count++][i] = '1';
+        }
+      }
+    }
+
+    if (m_debug) {
+      System.err.println("Initial sets:\n"+printSets(raceSets));
+    }
+    
+    // race the initial sets (base set either no or all features)
+    double [] winnerInfo = raceSubsets(raceSets, data, true, random);
+    baseSetError = winnerInfo[1];
+    m_bestMerit = baseSetError;
+    baseSet = (char [])raceSets[(int)winnerInfo[0]].clone();
+    if (m_rankingRequested) {
+      m_rankedAtts[m_rankedSoFar][0] = (int)(winnerInfo[0]-1);
+      m_rankedAtts[m_rankedSoFar][1] = winnerInfo[1];
+      m_rankedSoFar++;
+    }
+
+    boolean improved = true;
+    int j;
+    // now race until there is no improvement over the base set or only
+    // one competitor remains
+    while (improved) {
+      // generate the next set of competitors
+      numCompetitors--;
+      if (numCompetitors == 0) { //race finished!
+        break;
+      }
+      j=0;
+      // +1. we'll race against the base set---might be able to bail out
+      // of the race if none from the new set are statistically better
+      // than the base set. Base set is stored in loc 0.
+      raceSets = new char [numCompetitors+1][m_numAttribs];
+      for (int i=0;i<numCompetitors+1;i++) {
+        raceSets[i] = (char [])baseSet.clone();
+        if (i > 0) {
+          for (int k=j;k<m_numAttribs;k++) {
+            if (m_raceType == 1) {
+              if (k != m_classIndex && raceSets[i][k] != '0') {
+                raceSets[i][k] = '0';
+                j = k+1;
+                break;
+              }
+            } else {
+              if (k != m_classIndex && raceSets[i][k] != '1') {
+                raceSets[i][k] = '1';
+                j = k+1;
+                break;
+              }
+            }
+          }
+        }
+      }
+      
+      if (m_debug) {
+        System.err.println("Next set : \n"+printSets(raceSets));
+      }
+      improved = false;
+      winnerInfo = raceSubsets(raceSets, data, true, random);
+      String bs = new String(baseSet); 
+      String win = new String(raceSets[(int)winnerInfo[0]]);
+      if (bs.compareTo(win) == 0) {
+        // race finished
+      } else {
+        if (winnerInfo[1] < baseSetError || m_rankingRequested) {
+          improved = true;
+          baseSetError = winnerInfo[1];
+          m_bestMerit = baseSetError;
+          // find which att is different
+          if (m_rankingRequested) {
+            for (int i = 0; i < baseSet.length; i++) {
+              if (win.charAt(i) != bs.charAt(i)) {
+                m_rankedAtts[m_rankedSoFar][0] = i;
+                m_rankedAtts[m_rankedSoFar][1] = winnerInfo[1];
+                m_rankedSoFar++;
+              }
+            }
+          }
+          baseSet = (char [])raceSets[(int)winnerInfo[0]].clone();
+        } else {
+          // Will get here for a subset whose error is outside the delta
+          // threshold but is not *significantly* worse than the base
+          // subset
+          //throw new Exception("RaceSearch: problem in hillClimbRace");
+        }
+      }
+    }
+    return attributeList(baseSet);
+  }
+
+  /**
+   * Convert an attribute set to an array of indices
+   */
+  private int [] attributeList(char [] list) {
+    int count = 0;
+
+    for (int i=0;i<m_numAttribs;i++) {
+      if (list[i] == '1') {
+        count++;
+      }
+    }
+
+    int [] rlist = new int[count];
+    count = 0;
+     for (int i=0;i<m_numAttribs;i++) {
+       if (list[i] == '1') {
+         rlist[count++] = i;
+       }
+     }
+
+     return rlist;
+  }
+
+  /**
+   * Races the leave-one-out cross validation errors of a set of
+   * attribute subsets on a set of instances.
+   * @param raceSets a set of attribute subset specifications
+   * @param data the instances to use when cross validating
+   * @param baseSetIncluded true if the first attribute set is a
+   * base set generated from the previous race
+   * @param random a random number generator
+   * @return the index of the winning subset
+   * @throws Exception if an error occurs during cross validation
+   */
+  private double [] raceSubsets(char [][]raceSets, Instances data,
+                                boolean baseSetIncluded, Random random) 
+    throws Exception {
+    // the evaluators --- one for each subset
+    ASEvaluation [] evaluators = 
+      ASEvaluation.makeCopies(m_theEvaluator, raceSets.length);
+
+    // array of subsets eliminated from the race
+    boolean [] eliminated = new boolean [raceSets.length];
+
+    // individual statistics
+    Stats [] individualStats = new Stats [raceSets.length];
+
+    // pairwise statistics
+    PairedStats [][] testers = 
+      new PairedStats[raceSets.length][raceSets.length];
+
+    /** do we ignore the base set or not? */
+    int startPt = m_rankingRequested ? 1 : 0;
+
+    for (int i=0;i<raceSets.length;i++) {
+      individualStats[i] = new Stats();
+      for (int j=i+1;j<raceSets.length;j++) {
+        testers[i][j] = new PairedStats(m_sigLevel);
+      }
+    }
+    
+    BitSet [] raceBitSets = new BitSet[raceSets.length];
+    for (int i=0;i<raceSets.length;i++) {
+      raceBitSets[i] = new BitSet(m_numAttribs);
+      for (int j=0;j<m_numAttribs;j++) {
+        if (raceSets[i][j] == '1') {
+          raceBitSets[i].set(j);
+        }
+      }
+    }
+
+    // now loop over the data points collecting leave-one-out errors for
+    // each attribute set
+    Instances trainCV;
+    Instances testCV;
+    Instance testInst;
+    double [] errors = new double [raceSets.length];
+    int eliminatedCount = 0;
+    int processedCount = 0;
+    // if there is one set left in the race then we need to continue to
+    // evaluate it for the remaining instances in order to get an
+    // accurate error estimate
+    processedCount = 0;
+    race: for (int i=0;i<m_numFolds;i++) {
+
+      // We want to randomize the data the same way for every 
+      // learning scheme.
+      trainCV = data.trainCV(m_numFolds, i, new Random (1));
+      testCV = data.testCV(m_numFolds, i);
+      
+      // loop over the surviving attribute sets building classifiers for this
+      // training set
+      for (int j=startPt;j<raceSets.length;j++) {
+        if (!eliminated[j]) {
+          evaluators[j].buildEvaluator(trainCV);
+        }
+      }
+
+      for (int z=0;z<testCV.numInstances();z++) {
+        testInst = testCV.instance(z);
+        processedCount++;
+
+        // loop over surviving attribute sets computing errors for this
+        // test point
+        for (int zz=startPt;zz<raceSets.length;zz++) {
+          if (!eliminated[zz]) {
+            if (z == 0) {// first test instance---make sure classifier is built
+              errors[zz] = -((HoldOutSubsetEvaluator)evaluators[zz]).
+                evaluateSubset(raceBitSets[zz], 
+                               testInst,
+                               true);
+            } else { // must be k fold rather than leave one out
+              errors[zz] = -((HoldOutSubsetEvaluator)evaluators[zz]).
+                evaluateSubset(raceBitSets[zz], 
+                               testInst,
+                               false);
+            }
+          }
+        }
+
+        // now update the stats
+        for (int j=startPt;j<raceSets.length;j++) {
+          if (!eliminated[j]) {
+            individualStats[j].add(errors[j]);
+            for (int k=j+1;k<raceSets.length;k++) {
+              if (!eliminated[k]) {
+                testers[j][k].add(errors[j], errors[k]);
+              }
+            }
+          }
+        }
+      
+        // test for near identical models and models that are significantly
+        // worse than some other model
+        if (processedCount > m_samples-1 && 
+            (eliminatedCount < raceSets.length-1)) {
+          for (int j=0;j<raceSets.length;j++) {
+            if (!eliminated[j]) {
+              for (int k=j+1;k<raceSets.length;k++) {
+                if (!eliminated[k]) {
+                  testers[j][k].calculateDerived();
+                  // near identical ?
+                  if ((testers[j][k].differencesSignificance == 0) && 
+                      (Utils.eq(testers[j][k].differencesStats.mean, 0.0) ||
+                      (Utils.gr(m_delta, Math.abs(testers[j][k].
+                                                  differencesStats.mean))))) {
+                    // if they're exactly the same and there is a base set
+                    // in this race, make sure that the base set is NOT the
+                    // one eliminated.
+                    if (Utils.eq(testers[j][k].differencesStats.mean, 0.0)) {
+
+                      if (baseSetIncluded) { 
+                        if (j != 0) {
+                          eliminated[j] = true;
+                        } else {
+                          eliminated[k] = true;
+                        }
+                        eliminatedCount++;
+                      } else {
+                        eliminated[j] = true;
+                      }
+                      if (m_debug) {
+                        System.err.println("Eliminating (identical) "
+                                           +j+" "+raceBitSets[j].toString()
+                                           +" vs "+k+" "
+                                           +raceBitSets[k].toString()
+                                           +" after "
+                                           +processedCount
+                                           +" evaluations\n"
+                                           +"\nerror "+j+" : "
+                                           +testers[j][k].xStats.mean
+                                           +" vs "+k+" : "
+                                           +testers[j][k].yStats.mean
+                                           +" diff : "
+                                           +testers[j][k].differencesStats
+                                           .mean);
+                      }
+                    } else {
+                      // eliminate the one with the higer error
+                      if (testers[j][k].xStats.mean > 
+                          testers[j][k].yStats.mean) {
+                        eliminated[j] = true;
+                        eliminatedCount++;
+                        if (m_debug) {
+                          System.err.println("Eliminating (near identical) "
+                                           +j+" "+raceBitSets[j].toString()
+                                           +" vs "+k+" "
+                                           +raceBitSets[k].toString()
+                                           +" after "
+                                           +processedCount
+                                           +" evaluations\n"
+                                           +"\nerror "+j+" : "
+                                           +testers[j][k].xStats.mean
+                                           +" vs "+k+" : "
+                                           +testers[j][k].yStats.mean
+                                           +" diff : "
+                                           +testers[j][k].differencesStats
+                                           .mean);
+                        }
+                        break;
+                      } else {
+                        eliminated[k] = true;
+                        eliminatedCount++;
+                        if (m_debug) {
+                          System.err.println("Eliminating (near identical) "
+                                           +k+" "+raceBitSets[k].toString()
+                                           +" vs "+j+" "
+                                           +raceBitSets[j].toString()
+                                           +" after "
+                                           +processedCount
+                                           +" evaluations\n"
+                                           +"\nerror "+k+" : "
+                                           +testers[j][k].yStats.mean
+                                           +" vs "+j+" : "
+                                           +testers[j][k].xStats.mean
+                                           +" diff : "
+                                           +testers[j][k].differencesStats
+                                             .mean);
+                        }
+                      }
+                    }
+                  } else {
+                    // significantly worse ?
+                    if (testers[j][k].differencesSignificance != 0) {
+                      if (testers[j][k].differencesSignificance > 0) {
+                        eliminated[j] = true;
+                        eliminatedCount++;
+                        if (m_debug) {
+                          System.err.println("Eliminating (-worse) "
+                                           +j+" "+raceBitSets[j].toString()
+                                           +" vs "+k+" "
+                                           +raceBitSets[k].toString()
+                                           +" after "
+                                           +processedCount
+                                           +" evaluations"
+                                           +"\nerror "+j+" : "
+                                           +testers[j][k].xStats.mean
+                                           +" vs "+k+" : "
+                                           +testers[j][k].yStats.mean);
+                        }
+                        break;
+                      } else {
+                        eliminated[k] = true;
+                        eliminatedCount++;
+                        if (m_debug) {
+                          System.err.println("Eliminating (worse) "
+                                           +k+" "+raceBitSets[k].toString()
+                                           +" vs "+j+" "
+                                           +raceBitSets[j].toString()
+                                           +" after "
+                                           +processedCount
+                                           +" evaluations"
+                                           +"\nerror "+k+" : "
+                                           +testers[j][k].yStats.mean
+                                           +" vs "+j+" : "
+                                           +testers[j][k].xStats.mean);
+                        }
+                      }
+                    }
+                  }
+                }    
+              }
+            }
+          }
+        }
+        // if there is a base set from the previous race and it's the
+        // only remaining subset then terminate the race.
+        if (eliminatedCount == raceSets.length-1 && baseSetIncluded &&
+            !eliminated[0] && !m_rankingRequested) {
+          break race;
+        }
+      }
+    }
+
+    if (m_debug) {
+      System.err.println("*****eliminated count: "+eliminatedCount);
+    }
+    double bestError = Double.MAX_VALUE;
+    int bestIndex=0;
+    // return the index of the winner
+    for (int i=startPt;i<raceSets.length;i++) {
+      if (!eliminated[i]) {
+        individualStats[i].calculateDerived();
+        if (m_debug) {
+          System.err.println("Remaining error: "+raceBitSets[i].toString()
+                             +" "+individualStats[i].mean);
+        }
+        if (individualStats[i].mean < bestError) {
+          bestError = individualStats[i].mean;
+          bestIndex = i;
+        }
+      }
+    }
+
+    double [] retInfo = new double[2];
+    retInfo[0] = bestIndex;
+    retInfo[1] = bestError;
+    
+    if (m_debug) {
+      System.err.print("Best set from race : ");
+      
+      for (int i=0;i<m_numAttribs;i++) {
+        if (raceSets[bestIndex][i] == '1') {
+          System.err.print('1');
+        } else {
+          System.err.print('0');
+        }
+      }
+      System.err.println(" :"+bestError+" Processed : "+(processedCount)
+                         +"\n"+individualStats[bestIndex].toString());
+    }
+    return retInfo;
+  }
+
+  /**
+   * Returns a string represenation
+   * 
+   * @return a string representation
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("\tRaceSearch.\n\tRace type : ");
+    switch (m_raceType) {
+    case FORWARD_RACE: 
+      text.append("forward selection race\n\tBase set : no attributes");
+      break;
+    case BACKWARD_RACE:
+      text.append("backward elimination race\n\tBase set : all attributes");
+      break;
+    case SCHEMATA_RACE:
+      text.append("schemata race\n\tBase set : no attributes");
+      break;
+    case RANK_RACE:
+      text.append("rank race\n\tBase set : no attributes\n\t");
+      text.append("Attribute evaluator : "
+                  + getAttributeEvaluator().getClass().getName() +" ");
+      if (m_ASEval instanceof OptionHandler) {
+        String[] evaluatorOptions = new String[0];
+        evaluatorOptions = ((OptionHandler)m_ASEval).getOptions();
+        for (int i=0;i<evaluatorOptions.length;i++) {
+          text.append(evaluatorOptions[i]+' ');
+        }
+      }
+      text.append("\n");
+      text.append("\tAttribute ranking : \n");
+      int rlength = (int)(Math.log(m_Ranking.length) / Math.log(10) + 1);
+      for (int i=0;i<m_Ranking.length;i++) {
+        text.append("\t "+Utils.doubleToString((double)(m_Ranking[i]+1),
+                                               rlength,0)
+                    +" "+m_Instances.attribute(m_Ranking[i]).name()+'\n');
+      }
+      break;
+    }
+    text.append("\n\tCross validation mode : ");
+    if (m_xvalType == TEN_FOLD) {
+      text.append("10 fold");
+    } else {
+      text.append("Leave-one-out");
+    }
+
+    text.append("\n\tMerit of best subset found : ");
+    int fieldwidth = 3;
+    double precision = (m_bestMerit - (int)m_bestMerit);
+    if (Math.abs(m_bestMerit) > 0) {
+      fieldwidth = (int)Math.abs((Math.log(Math.abs(m_bestMerit)) / 
+                                  Math.log(10)))+2;
+    }
+    if (Math.abs(precision) > 0) {
+      precision = Math.abs((Math.log(Math.abs(precision)) / Math.log(10)))+3;
+    } else {
+      precision = 2;
+    }
+
+    text.append(Utils.doubleToString(Math.abs(m_bestMerit),
+                                     fieldwidth+(int)precision,
+                                     (int)precision)+"\n");
+    return text.toString();
+    
+  }
+
+  /**
+   * Reset the search method.
+   */
+  protected void resetOptions () {
+    m_sigLevel = 0.001;
+    m_delta = 0.001;
+    m_ASEval = new GainRatioAttributeEval();
+    m_Ranking = null;
+    m_raceType = FORWARD_RACE;
+    m_debug = false;
+    m_theEvaluator = null;
+    m_bestMerit = -Double.MAX_VALUE;
+    m_numFolds = 10;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.26 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/RandomSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/RandomSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/RandomSearch.java	(revision 29)
@@ -0,0 +1,668 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * RandomSearch : <br/>
+ * <br/>
+ * Performs a Random search in the space of attribute subsets. If no start set is supplied, Random search starts from a random point and reports the best subset found. If a start set is supplied, Random searches randomly for subsets that are as good or better than the start point with the same or or fewer attributes. Using RandomSearch in conjunction with a start set containing all attributes equates to the LVF algorithm of Liu and Setiono (ICML-96).<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * H. Liu, R. Setiono: A probabilistic approach to feature selection - A filter solution. In: 13th International Conference on Machine Learning, 319-327, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Liu1996,
+ *    author = {H. Liu and R. Setiono},
+ *    booktitle = {13th International Conference on Machine Learning},
+ *    pages = {319-327},
+ *    title = {A probabilistic approach to feature selection - A filter solution},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.
+ *  If a start point is supplied,
+ *  random search evaluates the start
+ *  point and then randomly looks for
+ *  subsets that are as good as or better
+ *  than the start point with the same
+ *  or lower cardinality.</pre>
+ * 
+ * <pre> -F &lt;percent&gt; 
+ *  Percent of search space to consider.
+ *  (default = 25%).</pre>
+ * 
+ * <pre> -V
+ *  Output subsets as the search progresses.
+ *  (default = false).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.18 $
+ */
+public class RandomSearch 
+  extends ASSearch 
+  implements StartSetHandler, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 7479392617377425484L;
+  
+  /** 
+   * holds a starting set as an array of attributes.
+   */
+  private int[] m_starting;
+  
+  /** holds the start set as a range */
+  private Range m_startRange;
+
+  /** the best feature set found during the search */
+  private BitSet m_bestGroup;
+
+  /** the merit of the best subset found */
+  private double m_bestMerit;
+
+  /** 
+   * only accept a feature set as being "better" than the best if its
+   * merit is better or equal to the best, and it contains fewer
+   * features than the best (this allows LVF to be implimented).
+   */
+  private boolean m_onlyConsiderBetterAndSmaller;
+
+ /** does the data have a class */
+  private boolean m_hasClass;
+ 
+  /** holds the class index */
+  private int m_classIndex;
+ 
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** seed for random number generation */
+  private int m_seed;
+
+  /** percentage of the search space to consider */
+  private double m_searchSize;
+
+  /** the number of iterations performed */
+  private int m_iterations;
+
+  /** random number object */
+  private Random m_random;
+
+  /** output new best subsets as the search progresses */
+  private boolean m_verbose;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "RandomSearch : \n\nPerforms a Random search in "
+      +"the space of attribute subsets. If no start set is supplied, Random "
+      +"search starts from a random point and reports the best subset found. "
+      +"If a start set is supplied, Random searches randomly for subsets "
+      +"that are as good or better than the start point with the same or "
+      +"or fewer attributes. Using RandomSearch in conjunction with a start "
+      +"set containing all attributes equates to the LVF algorithm of Liu "
+      +"and Setiono (ICML-96).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "H. Liu and R. Setiono");
+    result.setValue(Field.TITLE, "A probabilistic approach to feature selection - A filter solution");
+    result.setValue(Field.BOOKTITLE, "13th International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "319-327");
+    
+    return result;
+  }
+
+  /**
+   * Constructor
+   */
+  public RandomSearch () {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(3);
+    
+    newVector.addElement(new Option("\tSpecify a starting set of attributes." 
+				    + "\n\tEg. 1,3,5-7."
+				    +"\n\tIf a start point is supplied,"
+				    +"\n\trandom search evaluates the start"
+				    +"\n\tpoint and then randomly looks for"
+				    +"\n\tsubsets that are as good as or better"
+				    +"\n\tthan the start point with the same"
+				    +"\n\tor lower cardinality."
+				    ,"P",1
+				    , "-P <start set>"));
+
+    newVector.addElement(new Option("\tPercent of search space to consider."
+				    +"\n\t(default = 25%)."
+				    , "F", 1
+				    , "-F <percent> "));
+    newVector.addElement(new Option("\tOutput subsets as the search progresses."
+				    +"\n\t(default = false)."
+				    , "V", 0
+				    , "-V"));
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.
+   *  If a start point is supplied,
+   *  random search evaluates the start
+   *  point and then randomly looks for
+   *  subsets that are as good as or better
+   *  than the start point with the same
+   *  or lower cardinality.</pre>
+   * 
+   * <pre> -F &lt;percent&gt; 
+   *  Percent of search space to consider.
+   *  (default = 25%).</pre>
+   * 
+   * <pre> -V
+   *  Output subsets as the search progresses.
+   *  (default = false).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+    
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('F',options);
+    if (optionString.length() != 0) {
+      setSearchPercent((new Double(optionString)).doubleValue());
+    }
+
+    setVerbose(Utils.getFlag('V',options));
+  }
+
+  /**
+   * Gets the current settings of RandomSearch.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[5];
+    int current = 0;
+
+    if (m_verbose) {
+      options[current++] = "-V";
+    }
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = "" + startSetToString();
+    }
+
+    options[current++] = "-F";
+    options[current++] = "" + getSearchPercent();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Set the start point for the search. This is specified as a comma "
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17. If specified, Random searches for subsets "
+      +"of attributes that are as good as or better than the start set with "
+      +"the same or lower cardinality.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15. "" indicates no start point.
+   * If a start point is supplied, random search evaluates the
+   * start point and then looks for subsets that are as good as or better 
+   * than the start point with the same or lower cardinality.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "Print progress information. Sends progress info to the terminal "
+      +"as the search progresses.";
+  }
+
+  /**
+   * set whether or not to output new best subsets as the search proceeds
+   * @param v true if output is to be verbose
+   */
+  public void setVerbose(boolean v) {
+    m_verbose = v;
+  }
+
+  /**
+   * get whether or not output is verbose
+   * @return true if output is set to verbose
+   */
+  public boolean getVerbose() {
+    return m_verbose;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchPercentTipText() {
+    return "Percentage of the search space to explore.";
+  }
+
+  /**
+   * set the percentage of the search space to consider
+   * @param p percent of the search space ( 0 < p <= 100)
+   */
+  public void setSearchPercent(double p) {
+    p = Math.abs(p);
+    if (p == 0) {
+      p = 25;
+    }
+
+    if (p > 100.0) {
+      p = 100;
+    }
+
+    m_searchSize = (p/100.0);
+  }
+
+  /**
+   * get the percentage of the search space to consider
+   * @return the percent of the search space explored
+   */
+  public double getSearchPercent() {
+    return m_searchSize * 100;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+    
+    if (m_starting == null) {
+      return getStartSet();
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+      
+      if ((m_hasClass == false) || 
+	  (m_hasClass == true && i != m_classIndex)) {
+	FString.append((m_starting[i] + 1));
+	didPrint = true;
+      }
+      
+      if (i == (m_starting.length - 1)) {
+	FString.append("");
+      }
+      else {
+	if (didPrint) {
+	  FString.append(",");
+	  }
+      }
+    }
+
+    return FString.toString();
+  }
+
+  /**
+   * prints a description of the search
+   * @return a description of the search as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("\tRandom search.\n\tStart set: ");
+    if (m_starting == null) {
+      text.append("no attributes\n");
+    }
+    else {
+      text.append(startSetToString()+"\n");
+    }
+    text.append("\tNumber of iterations: "+m_iterations+" ("
+		+(m_searchSize * 100.0)+"% of the search space)\n");
+    text.append("\tMerit of best subset found: "
+		+Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
+
+    return text.toString();
+  }
+
+  /**
+   * Searches the attribute subset space randomly.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+   public int[] search (ASEvaluation ASEval, Instances data)
+     throws Exception {
+     double best_merit;
+     int sizeOfBest = m_numAttribs;
+     BitSet temp;
+     m_bestGroup = new BitSet(m_numAttribs);
+     
+     m_onlyConsiderBetterAndSmaller = false;
+     if (!(ASEval instanceof SubsetEvaluator)) {
+       throw  new Exception(ASEval.getClass().getName() 
+			    + " is not a " 
+			    + "Subset evaluator!");
+     }
+
+     m_random = new Random(m_seed);
+     
+     if (ASEval instanceof UnsupervisedSubsetEvaluator) {
+       m_hasClass = false;
+     }
+     else {
+       m_hasClass = true;
+       m_classIndex = data.classIndex();
+     }
+     
+     SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
+     m_numAttribs = data.numAttributes();
+
+     m_startRange.setUpper(m_numAttribs-1);
+     if (!(getStartSet().equals(""))) {
+       m_starting = m_startRange.getSelection();
+     }
+
+     // If a starting subset has been supplied, then initialise the bitset
+     if (m_starting != null) {
+       for (int i = 0; i < m_starting.length; i++) {
+	 if ((m_starting[i]) != m_classIndex) {
+	   m_bestGroup.set(m_starting[i]);
+	 }
+       }
+       m_onlyConsiderBetterAndSmaller = true;
+       best_merit = ASEvaluator.evaluateSubset(m_bestGroup);
+       sizeOfBest = countFeatures(m_bestGroup);
+     } else {
+       // do initial random subset
+       m_bestGroup = generateRandomSubset();
+       best_merit = ASEvaluator.evaluateSubset(m_bestGroup);
+     }
+     
+     if (m_verbose) {
+       System.out.println("Initial subset ("
+			  +Utils.doubleToString(Math.
+						abs(best_merit),8,5)
+			  +"): "+printSubset(m_bestGroup));
+     }
+
+     int i;
+     if (m_hasClass) {
+       i = m_numAttribs -1;
+     } else {
+       i = m_numAttribs;
+     }
+     m_iterations = (int)((m_searchSize * Math.pow(2, i)));
+     
+     int tempSize;
+     double tempMerit;
+     // main loop
+     for (i=0;i<m_iterations;i++) {
+       temp = generateRandomSubset();
+       if (m_onlyConsiderBetterAndSmaller) {
+	 tempSize = countFeatures(temp);
+	 if (tempSize <= sizeOfBest) {
+	   tempMerit = ASEvaluator.evaluateSubset(temp);
+	   if (tempMerit >= best_merit) {
+	     sizeOfBest = tempSize;
+	     m_bestGroup = temp;
+	     best_merit = tempMerit;
+	     if (m_verbose) {
+	       System.out.print("New best subset ("
+				  +Utils.doubleToString(Math.
+							abs(best_merit),8,5)
+				  +"): "+printSubset(m_bestGroup) + " :");
+	       System.out.println(Utils.
+				  doubleToString((((double)i)/
+						  ((double)m_iterations)*
+						  100.0),5,1)
+				  +"% done");
+	     }
+	   }
+	 }
+       } else {
+	 tempMerit = ASEvaluator.evaluateSubset(temp);
+	 if (tempMerit > best_merit) {
+	   m_bestGroup = temp;
+	   best_merit = tempMerit;
+	   if (m_verbose) {
+	     System.out.print("New best subset ("
+				+Utils.doubleToString(Math.abs(best_merit),8,5)
+				+"): "+printSubset(m_bestGroup) + " :");
+	     System.out.println(Utils.
+				doubleToString((((double)i)/
+						((double)m_iterations)
+						*100.0),5,1)
+				+"% done");
+	   }
+	 }
+       }
+     }
+     m_bestMerit = best_merit;
+     return attributeList(m_bestGroup);
+   }
+
+  /**
+   * prints a subset as a series of attribute numbers
+   * @param temp the subset to print
+   * @return a subset as a String of attribute numbers
+   */
+  private String printSubset(BitSet temp) {
+    StringBuffer text = new StringBuffer();
+
+    for (int j=0;j<m_numAttribs;j++) {
+      if (temp.get(j)) {
+        text.append((j+1)+" ");
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  private int[] attributeList (BitSet group) {
+    int count = 0;
+    
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	count++;
+      }
+    }
+    
+    int[] list = new int[count];
+    count = 0;
+    
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	list[count++] = i;
+      }
+    }
+    
+    return  list;
+  }
+
+  /**
+   * generates a random subset
+   * @return a random subset as a BitSet
+   */
+  private BitSet generateRandomSubset() {
+    BitSet temp = new BitSet(m_numAttribs);
+    double r;
+
+    for (int i=0;i<m_numAttribs;i++) {
+      r = m_random.nextDouble();
+      if (r <= 0.5) {
+	if (m_hasClass && i == m_classIndex) {
+	} else {
+	  temp.set(i);
+	}
+      }
+    }
+    return temp;
+  }
+
+  /**
+   * counts the number of features in a subset
+   * @param featureSet the feature set for which to count the features
+   * @return the number of features in the subset
+   */
+  private int countFeatures(BitSet featureSet) {
+    int count = 0;
+    for (int i=0;i<m_numAttribs;i++) {
+      if (featureSet.get(i)) {
+	count++;
+      }
+    }
+    return count;
+  }
+
+  /**
+   * resets to defaults
+   */
+  private void resetOptions() {
+    m_starting = null;
+    m_startRange = new Range();
+    m_searchSize = 0.25;
+    m_seed = 1;
+    m_onlyConsiderBetterAndSmaller = false;
+    m_verbose = false;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.18 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/RankSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/RankSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/RankSearch.java	(revision 29)
@@ -0,0 +1,522 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RankSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * RankSearch : <br/>
+ * <br/>
+ * Uses an attribute/subset evaluator to rank all attributes. If a subset evaluator is specified, then a forward selection search is used to generate a ranked list. From the ranked list of attributes, subsets of increasing size are evaluated, ie. The best attribute, the best attribute plus the next best attribute, etc.... The best attribute set is reported. RankSearch is linear in the number of attributes if a simple attribute evaluator is used such as GainRatioAttributeEval.<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;attribute evaluator&gt;
+ *  class name of attribute evaluator to use for ranking. Place any
+ *  evaluator options LAST on the command line following a "--".
+ *  eg.:
+ *   -A weka.attributeSelection.GainRatioAttributeEval ... -- -M
+ *  (default: weka.attributeSelection.GainRatioAttributeEval)</pre>
+ * 
+ * <pre> -S &lt;step size&gt;
+ *  number of attributes to be added from the
+ *  ranking in each iteration (default = 1).</pre>
+ * 
+ * <pre> -R &lt;start point&gt;
+ *  point in the ranking to start evaluating from. 
+ *  (default = 0, ie. the head of the ranking).</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.GainRatioAttributeEval:
+ * </pre>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 4614 $
+ */
+public class RankSearch 
+  extends ASSearch 
+  implements OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7992268736874353755L;
+
+  /** does the data have a class */
+  private boolean m_hasClass;
+ 
+  /** holds the class index */
+  private int m_classIndex;
+ 
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** the best subset found */
+  private BitSet m_best_group;
+
+  /** the attribute evaluator to use for generating the ranking */
+  private ASEvaluation m_ASEval;
+
+  /** the subset evaluator with which to evaluate the ranking */
+  private ASEvaluation m_SubsetEval;
+
+  /** the training instances */
+  private Instances m_Instances;
+
+  /** the merit of the best subset found */
+  private double m_bestMerit;
+
+  /** will hold the attribute ranking */
+  private int [] m_Ranking;
+
+  /** add this many attributes in each iteration from the ranking */
+  protected int m_add = 1;
+
+  /** start from this point in the ranking */
+  protected int m_startPoint = 0;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search method suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "RankSearch : \n\n"
+      +"Uses an attribute/subset evaluator to rank all attributes. "
+      +"If a subset evaluator is specified, then a forward selection "
+      +"search is used to generate a ranked list. From the ranked "
+      +"list of attributes, subsets of increasing size are evaluated, ie. "
+      +"The best attribute, the best attribute plus the next best attribute, "
+      +"etc.... The best attribute set is reported. RankSearch is linear in "
+      +"the number of attributes if a simple attribute evaluator is used "
+      +"such as GainRatioAttributeEval.\n";
+  }
+
+  /**
+   * Constructor
+   */
+  public RankSearch () {
+    resetOptions();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeEvaluatorTipText() {
+    return "Attribute evaluator to use for generating a ranking.";    
+  }
+
+  /**
+   * Set the attribute evaluator to use for generating the ranking.
+   * @param newEvaluator the attribute evaluator to use.
+   */
+  public void setAttributeEvaluator(ASEvaluation newEvaluator) {
+    m_ASEval = newEvaluator;
+  }
+
+  /**
+   * Get the attribute evaluator used to generate the ranking.
+   * @return the evaluator used to generate the ranking.
+   */
+  public ASEvaluation getAttributeEvaluator() {
+    return m_ASEval;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String stepSizeTipText() {
+    return "Add this many attributes from the ranking in each iteration.";
+  }
+
+  /**
+   * Set the number of attributes to add from the rankining
+   * in each iteration
+   * @param ss the number of attribes to add.
+   */
+  public void setStepSize(int ss) {
+    if (ss > 0) {
+      m_add = ss;
+    }
+  }
+
+  /**
+   * Get the number of attributes to add from the rankining
+   * in each iteration
+   * @return the number of attributes to add.
+   */
+  public int getStepSize() {
+    return m_add;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startPointTipText() {
+    return "Start evaluating from this point in the ranking.";
+  }
+
+  /**
+   * Set the point at which to start evaluating the ranking
+   * @param sp the position in the ranking to start at
+   */
+  public void setStartPoint(int sp) {
+    if (sp >= 0) {
+      m_startPoint = sp;
+    }
+  }
+
+  /**
+   * Get the point at which to start evaluating the ranking
+   * @return the position in the ranking to start at
+   */
+  public int getStartPoint() {
+    return m_startPoint;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+    
+    newVector.addElement(new Option(
+        "\tclass name of attribute evaluator to use for ranking. Place any\n" 
+        + "\tevaluator options LAST on the command line following a \"--\".\n" 
+        + "\teg.:\n"
+        + "\t\t-A weka.attributeSelection.GainRatioAttributeEval ... -- -M\n"
+        + "\t(default: weka.attributeSelection.GainRatioAttributeEval)", 
+        "A", 1, "-A <attribute evaluator>"));
+    
+    newVector.addElement(new Option(
+        "\tnumber of attributes to be added from the"
+        +"\n\tranking in each iteration (default = 1).", 
+        "S", 1,"-S <step size>"));
+    
+    newVector.addElement(new Option(
+        "\tpoint in the ranking to start evaluating from. "
+        +"\n\t(default = 0, ie. the head of the ranking).", 
+        "R", 1,"-R <start point>"));
+
+    if ((m_ASEval != null) && 
+        (m_ASEval instanceof OptionHandler)) {
+      newVector.addElement(new Option("", "", 0, "\nOptions specific to " 
+                                      + "evaluator " 
+                                      + m_ASEval.getClass().getName() 
+                                      + ":"));
+      Enumeration enu = ((OptionHandler)m_ASEval).listOptions();
+
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -A &lt;attribute evaluator&gt;
+   *  class name of attribute evaluator to use for ranking. Place any
+   *  evaluator options LAST on the command line following a "--".
+   *  eg.:
+   *   -A weka.attributeSelection.GainRatioAttributeEval ... -- -M
+   *  (default: weka.attributeSelection.GainRatioAttributeEval)</pre>
+   * 
+   * <pre> -S &lt;step size&gt;
+   *  number of attributes to be added from the
+   *  ranking in each iteration (default = 1).</pre>
+   * 
+   * <pre> -R &lt;start point&gt;
+   *  point in the ranking to start evaluating from. 
+   *  (default = 0, ie. the head of the ranking).</pre>
+   * 
+   * <pre> 
+   * Options specific to evaluator weka.attributeSelection.GainRatioAttributeEval:
+   * </pre>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      setStepSize(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setStartPoint(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('A', options);
+    if (optionString.length() == 0)
+      optionString = GainRatioAttributeEval.class.getName();
+    setAttributeEvaluator(ASEvaluation.forName(optionString, 
+                                     Utils.partitionOptions(options)));
+  }
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] evaluatorOptions = new String[0];
+
+    if ((m_ASEval != null) && 
+        (m_ASEval instanceof OptionHandler)) {
+      evaluatorOptions = ((OptionHandler)m_ASEval).getOptions();
+    }
+
+    String[] options = new String[8 + evaluatorOptions.length];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = ""+getStepSize();
+
+    options[current++] = "-R"; options[current++] = ""+getStartPoint();
+
+    if (getAttributeEvaluator() != null) {
+      options[current++] = "-A";
+      options[current++] = getAttributeEvaluator().getClass().getName();
+    }
+
+    if (evaluatorOptions.length > 0) {
+      options[current++] = "--";
+      System.arraycopy(evaluatorOptions, 0, options, current, 
+          evaluatorOptions.length);
+      current += evaluatorOptions.length;
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Reset the search method.
+   */
+  protected void resetOptions () {
+    m_ASEval = new GainRatioAttributeEval();
+    m_Ranking = null;
+  }
+
+  /**
+   * Ranks attributes using the specified attribute evaluator and then
+   * searches the ranking using the supplied subset evaluator.
+   *
+   * @param ASEval the subset evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+    
+    double best_merit = -Double.MAX_VALUE;
+    double temp_merit;
+    BitSet temp_group, best_group=null;
+    
+    if (!(ASEval instanceof SubsetEvaluator)) {
+      throw  new Exception(ASEval.getClass().getName() 
+                           + " is not a " 
+                           + "Subset evaluator!");
+    }
+
+    m_SubsetEval = ASEval;
+    m_Instances = data;
+    m_numAttribs = m_Instances.numAttributes();
+
+    /*    if (m_ASEval instanceof AttributeTransformer) {
+      throw new Exception("Can't use an attribute transformer "
+                          +"with RankSearch");
+                          } */
+    if (m_ASEval instanceof UnsupervisedAttributeEvaluator || 
+        m_ASEval instanceof UnsupervisedSubsetEvaluator) {
+      m_hasClass = false;
+      /*      if (!(m_SubsetEval instanceof UnsupervisedSubsetEvaluator)) {
+        throw new Exception("Must use an unsupervised subset evaluator.");
+        } */
+    }
+    else {
+      m_hasClass = true;
+      m_classIndex = m_Instances.classIndex();
+    }
+
+    if (m_ASEval instanceof AttributeEvaluator) {
+      // generate the attribute ranking first
+      Ranker ranker = new Ranker();
+      m_ASEval.buildEvaluator(m_Instances);
+      if (m_ASEval instanceof AttributeTransformer) {
+        // get the transformed data a rebuild the subset evaluator
+        m_Instances = ((AttributeTransformer)m_ASEval).
+          transformedData(m_Instances);
+        ((ASEvaluation)m_SubsetEval).buildEvaluator(m_Instances);
+      }
+      m_Ranking = ranker.search(m_ASEval, m_Instances);
+    } else {
+      GreedyStepwise fs = new GreedyStepwise();
+      double [][]rankres; 
+      fs.setGenerateRanking(true);
+      ((ASEvaluation)m_ASEval).buildEvaluator(m_Instances);
+      fs.search(m_ASEval, m_Instances);
+      rankres = fs.rankedAttributes();
+      m_Ranking = new int[rankres.length];
+      for (int i=0;i<rankres.length;i++) {
+        m_Ranking[i] = (int)rankres[i][0];
+      }
+    }
+
+    // now evaluate the attribute ranking
+    for (int i=m_startPoint;i<m_Ranking.length;i+=m_add) {
+      temp_group = new BitSet(m_numAttribs);
+      for (int j=0;j<=i;j++) {
+        temp_group.set(m_Ranking[j]);
+      }
+      temp_merit = ((SubsetEvaluator)m_SubsetEval).evaluateSubset(temp_group);
+
+      if (temp_merit > best_merit) {
+        best_merit = temp_merit;;
+        best_group = temp_group;
+      }
+    }
+    m_bestMerit = best_merit;
+    return attributeList(best_group);
+  }
+    
+  /**
+   * converts a BitSet into a list of attribute indexes 
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  private int[] attributeList (BitSet group) {
+    int count = 0;
+    
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+
+   /**
+   * returns a description of the search as a String
+   * @return a description of the search
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+    text.append("\tRankSearch :\n");
+    text.append("\tAttribute evaluator : "
+                + getAttributeEvaluator().getClass().getName() +" ");
+    if (m_ASEval instanceof OptionHandler) {
+      String[] evaluatorOptions = new String[0];
+      evaluatorOptions = ((OptionHandler)m_ASEval).getOptions();
+      for (int i=0;i<evaluatorOptions.length;i++) {
+        text.append(evaluatorOptions[i]+' ');
+      }
+    }
+    text.append("\n");
+    text.append("\tAttribute ranking : \n");
+    int rlength = (int)(Math.log(m_Ranking.length) / Math.log(10) + 1);
+    for (int i=0;i<m_Ranking.length;i++) {
+      text.append("\t "+Utils.doubleToString((double)(m_Ranking[i]+1),
+                                             rlength,0)
+                  +" "+m_Instances.attribute(m_Ranking[i]).name()+'\n');
+    }
+    text.append("\tMerit of best subset found : ");
+    int fieldwidth = 3;
+    double precision = (m_bestMerit - (int)m_bestMerit);
+    if (Math.abs(m_bestMerit) > 0) {
+      fieldwidth = (int)Math.abs((Math.log(Math.abs(m_bestMerit)) / Math.log(10)))+2;
+    }
+    if (Math.abs(precision) > 0) {
+      precision = Math.abs((Math.log(Math.abs(precision)) / Math.log(10)))+3;
+    } else {
+      precision = 2;
+    }
+
+    text.append(Utils.doubleToString(Math.abs(m_bestMerit),
+                                     fieldwidth+(int)precision,
+                                     (int)precision)+"\n");
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4614 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/RankedOutputSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/RankedOutputSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/RankedOutputSearch.java	(revision 29)
@@ -0,0 +1,108 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RankedOutputSearch.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.attributeSelection;
+
+
+/** 
+ * Interface for search methods capable of producing a
+ * ranked list of attributes.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public interface RankedOutputSearch {
+
+
+  // ===============
+  // Public methods.
+  // ===============
+  
+  /**
+   * Returns a X by 2 list of attribute indexes and corresponding
+   * evaluations from best (highest) to worst.
+   * @return the ranked list of attribute indexes in an array of ints
+   * @exception Exception if the ranking can't be produced
+   */
+  double[][] rankedAttributes() throws Exception;
+
+  /**
+   * Sets a threshold by which attributes can be discarded from the
+   * ranking. This threshold is used by the AttributeSelection module
+   * which does the actual discarding of attributes---the implementer
+   * of this method needs only to provide a variable in which to store the
+   * supplied threshold. -Double.MAX_VALUE is reserved to mean no threshold,
+   * ie, retain all attributes.
+   * @param threshold the threshold.
+   */
+  void setThreshold(double threshold);
+
+  /**
+   * Gets the threshold by which attributes can be discarded. Discarding
+   * of attributes is done by the AttributeSelection module using the
+   * threshold returned by this method.
+   * @return a threshold by which to discard attributes
+   */
+  double getThreshold();
+
+  /**
+   * Specify the number of attributes to select from the ranked list. < 0
+   * indicates that all attributes are to be retained. NumToSelect has
+   * precedence over threshold, ie. if there is a non -1 value for NumToSelect
+   * then this will take precedence over any threshold value.
+   * @param numToSelect the number of attributes to retain
+   */
+  void setNumToSelect(int numToSelect);
+
+  /**
+   * Gets the user specified number of attributes to be retained.
+   * @return the number of attributes to retain
+   */
+  int getNumToSelect();
+
+  /**
+   * Gets the calculated number of attributes to retain. This is the
+   * actual number of attributes to retain. This is the same as
+   * getNumToSelect if the user specifies a number which is not less
+   * than zero. Otherwise it should be the number of attributes in the
+   * (potentially transformed) data.
+   */
+  int getCalculatedNumToSelect();
+  
+  /**
+   * Sets whether or not ranking is to be performed.
+   * When a search method is capable of producing a ranked list
+   * of attributes, the user has the choice of seeing the results of a
+   * normal search or seeing a ranked list.
+   * @param doRanking true if ranked list is to be produced
+   */
+  void setGenerateRanking(boolean doRanking);
+
+  /**
+   * Gets whether the user has opted to see a ranked list of
+   * attributes rather than the normal result of the search
+   * @return true if a ranked list has been requested.
+   */
+  boolean getGenerateRanking();
+
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/Ranker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/Ranker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/Ranker.java	(revision 29)
@@ -0,0 +1,631 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Ranker.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Ranker : <br/>
+ * <br/>
+ * Ranks attributes by their individual evaluations. Use in conjunction with attribute evaluators (ReliefF, GainRatio, Entropy etc).<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.
+ *  Any starting attributes specified are
+ *  ignored during the ranking.</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Specify a theshold by which attributes
+ *  may be discarded from the ranking.</pre>
+ * 
+ * <pre> -N &lt;num to select&gt;
+ *  Specify number of attributes to select</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.26 $
+ */
+public class Ranker 
+  extends ASSearch 
+  implements RankedOutputSearch, StartSetHandler, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -9086714848510751934L;
+
+  /** Holds the starting set as an array of attributes */
+  private int[] m_starting;
+
+  /** Holds the start set for the search as a range */
+  private Range m_startRange;
+
+  /** Holds the ordered list of attributes */
+  private int[] m_attributeList;
+
+  /** Holds the list of attribute merit scores */
+  private double[] m_attributeMerit;
+
+  /** Data has class attribute---if unsupervised evaluator then no class */
+  private boolean m_hasClass;
+
+  /** Class index of the data if supervised evaluator */
+  private int m_classIndex;
+
+  /** The number of attribtes */
+  private int m_numAttribs;
+
+  /** 
+   * A threshold by which to discard attributes---used by the
+   * AttributeSelection module
+   */
+  private double m_threshold;
+
+  /** The number of attributes to select. -1 indicates that all attributes
+      are to be retained. Has precedence over m_threshold */
+  private int m_numToSelect = -1;
+
+  /** Used to compute the number to select */
+  private int m_calculatedNumToSelect = -1;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Ranker : \n\nRanks attributes by their individual evaluations. "
+      +"Use in conjunction with attribute evaluators (ReliefF, GainRatio, "
+      +"Entropy etc).\n";
+  }
+
+  /**
+   * Constructor
+   */
+  public Ranker () {
+    resetOptions();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numToSelectTipText() {
+    return "Specify the number of attributes to retain. The default value "
+      +"(-1) indicates that all attributes are to be retained. Use either "
+      +"this option or a threshold to reduce the attribute set.";
+  }
+
+  /**
+   * Specify the number of attributes to select from the ranked list. -1
+   * indicates that all attributes are to be retained.
+   * @param n the number of attributes to retain
+   */
+  public void setNumToSelect(int n) {
+    m_numToSelect = n;
+  }
+
+  /**
+   * Gets the number of attributes to be retained.
+   * @return the number of attributes to retain
+   */
+  public int getNumToSelect() {
+    return m_numToSelect;
+  }
+
+  /**
+   * Gets the calculated number to select. This might be computed
+   * from a threshold, or if < 0 is set as the number to select then
+   * it is set to the number of attributes in the (transformed) data.
+   * @return the calculated number of attributes to select
+   */
+  public int getCalculatedNumToSelect() {
+    if (m_numToSelect >= 0) {
+      m_calculatedNumToSelect = m_numToSelect;
+    }
+    return m_calculatedNumToSelect;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Set threshold by which attributes can be discarded. Default value "
+      + "results in no attributes being discarded. Use either this option or "
+      +"numToSelect to reduce the attribute set.";
+  }
+
+  /**
+   * Set the threshold by which the AttributeSelection module can discard
+   * attributes.
+   * @param threshold the threshold.
+   */
+  public void setThreshold(double threshold) {
+    m_threshold = threshold;
+  }
+
+  /**
+   * Returns the threshold so that the AttributeSelection module can
+   * discard attributes from the ranking.
+   */
+  public double getThreshold() {
+    return m_threshold;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String generateRankingTipText() {
+    return "A constant option. Ranker is only capable of generating "
+      +" attribute rankings.";
+  }
+
+  /**
+   * This is a dummy set method---Ranker is ONLY capable of producing
+   * a ranked list of attributes for attribute evaluators.
+   * @param doRank this parameter is N/A and is ignored
+   */
+  public void setGenerateRanking(boolean doRank) {
+    
+  }
+
+  /**
+   * This is a dummy method. Ranker can ONLY be used with attribute
+   * evaluators and as such can only produce a ranked list of attributes
+   * @return true all the time.
+   */
+  public boolean getGenerateRanking() {
+    return true;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String startSetTipText() {
+    return "Specify a set of attributes to ignore. "
+      +" When generating the ranking, Ranker will not evaluate the attributes "
+      +" in this list. "
+      +"This is specified as a comma " 
+      +"seperated list off attribute indexes starting at 1. It can include "
+      +"ranges. Eg. 1,2,5-9,17.";
+  }
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @throws Exception if start set can't be set.
+   */
+  public void setStartSet (String startSet) throws Exception {
+    m_startRange.setRanges(startSet);
+  }
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  public String getStartSet () {
+    return m_startRange.getRanges();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(3);
+
+    newVector
+      .addElement(new Option("\tSpecify a starting set of attributes.\n" 
+                             + "\tEg. 1,3,5-7.\n"
+                             +"\tAny starting attributes specified are\n"
+                             +"\tignored during the ranking."
+                             ,"P",1
+                             , "-P <start set>"));
+    newVector
+      .addElement(new Option("\tSpecify a theshold by which attributes\n" 
+                             + "\tmay be discarded from the ranking.","T",1
+                             , "-T <threshold>"));
+
+    newVector
+      .addElement(new Option("\tSpecify number of attributes to select" 
+                             ,"N",1
+                             , "-N <num to select>"));
+
+    return newVector.elements();
+
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.
+   *  Any starting attributes specified are
+   *  ignored during the ranking.</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Specify a theshold by which attributes
+   *  may be discarded from the ranking.</pre>
+   * 
+   * <pre> -N &lt;num to select&gt;
+   *  Specify number of attributes to select</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setStartSet(optionString);
+    }
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() != 0) {
+      Double temp;
+      temp = Double.valueOf(optionString);
+      setThreshold(temp.doubleValue());
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumToSelect(Integer.parseInt(optionString));
+    }
+  }
+
+  /**
+   * Gets the current settings of ReliefFAttributeEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[6];
+    int current = 0;
+
+    if (!(getStartSet().equals(""))) {
+      options[current++] = "-P";
+      options[current++] = ""+startSetToString();
+    }
+
+    options[current++] = "-T";
+    options[current++] = "" + getThreshold();
+
+    options[current++] = "-N";
+    options[current++] = ""+getNumToSelect();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return  options;
+  }
+
+  /**
+   * converts the array of starting attributes to a string. This is
+   * used by getOptions to return the actual attributes specified
+   * as the starting set. This is better than using m_startRanges.getRanges()
+   * as the same start set can be specified in different ways from the
+   * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
+   * is stored in a database is comparable.
+   * @return a comma seperated list of individual attribute numbers as a String
+   */
+  private String startSetToString() {
+    StringBuffer FString = new StringBuffer();
+    boolean didPrint;
+    
+    if (m_starting == null) {
+      return getStartSet();
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      didPrint = false;
+      
+      if ((m_hasClass == false) || 
+          (m_hasClass == true && i != m_classIndex)) {
+        FString.append((m_starting[i] + 1));
+        didPrint = true;
+      }
+      
+      if (i == (m_starting.length - 1)) {
+        FString.append("");
+      }
+      else {
+        if (didPrint) {
+          FString.append(",");
+        }
+      }
+    }
+    
+    return FString.toString();
+  }
+
+  /**
+   * Kind of a dummy search algorithm. Calls a Attribute evaluator to
+   * evaluate each attribute not included in the startSet and then sorts
+   * them to produce a ranked list of attributes.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @throws Exception if the search can't be completed
+   */
+  public int[] search (ASEvaluation ASEval, Instances data)
+    throws Exception {
+    int i, j;
+
+    if (!(ASEval instanceof AttributeEvaluator)) {
+      throw  new Exception(ASEval.getClass().getName() 
+                           + " is not a" 
+                           + "Attribute evaluator!");
+    }
+
+    m_numAttribs = data.numAttributes();
+
+    if (ASEval instanceof UnsupervisedAttributeEvaluator) {
+      m_hasClass = false;
+    }
+    else {
+      m_classIndex = data.classIndex();
+      if (m_classIndex >= 0) {  
+        m_hasClass = true;
+      } else {
+        m_hasClass = false;
+      }
+    }
+
+    // get the transformed data and check to see if the transformer
+    // preserves a class index
+    if (ASEval instanceof AttributeTransformer) {
+      data = ((AttributeTransformer)ASEval).transformedHeader();
+      if (m_classIndex >= 0 && data.classIndex() >= 0) {
+        m_classIndex = data.classIndex();
+        m_hasClass = true;
+      }
+    }
+
+
+    m_startRange.setUpper(m_numAttribs - 1);
+    if (!(getStartSet().equals(""))) {
+      m_starting = m_startRange.getSelection();
+    }
+    
+    int sl=0;
+    if (m_starting != null) {
+      sl = m_starting.length;
+    }
+    if ((m_starting != null) && (m_hasClass == true)) {
+      // see if the supplied list contains the class index
+      boolean ok = false;
+      for (i = 0; i < sl; i++) {
+        if (m_starting[i] == m_classIndex) {
+          ok = true;
+          break;
+        }
+      }
+      
+      if (ok == false) {
+        sl++;
+      }
+    }
+    else {
+      if (m_hasClass == true) {
+        sl++;
+      }
+    }
+
+
+    m_attributeList = new int[m_numAttribs - sl];
+    m_attributeMerit = new double[m_numAttribs - sl];
+
+    // add in those attributes not in the starting (omit list)
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (!inStarting(i)) {
+        m_attributeList[j++] = i;
+      }
+    }
+
+    AttributeEvaluator ASEvaluator = (AttributeEvaluator)ASEval;
+
+    for (i = 0; i < m_attributeList.length; i++) {
+      m_attributeMerit[i] = ASEvaluator.evaluateAttribute(m_attributeList[i]);
+    }
+
+    double[][] tempRanked = rankedAttributes();
+    int[] rankedAttributes = new int[m_attributeList.length];
+
+    for (i = 0; i < m_attributeList.length; i++) {
+      rankedAttributes[i] = (int)tempRanked[i][0];
+    }
+
+    return  rankedAttributes;
+  }
+
+
+  /**
+   * Sorts the evaluated attribute list
+   *
+   * @return an array of sorted (highest eval to lowest) attribute indexes
+   * @throws Exception of sorting can't be done.
+   */
+  public double[][] rankedAttributes ()
+    throws Exception {
+    int i, j;
+
+    if (m_attributeList == null || m_attributeMerit == null) {
+      throw  new Exception("Search must be performed before a ranked " 
+                           + "attribute list can be obtained");
+    }
+
+    int[] ranked = Utils.sort(m_attributeMerit);
+    // reverse the order of the ranked indexes
+    double[][] bestToWorst = new double[ranked.length][2];
+
+    for (i = ranked.length - 1, j = 0; i >= 0; i--) {
+      bestToWorst[j++][0] = ranked[i];
+    }
+
+    // convert the indexes to attribute indexes
+    for (i = 0; i < bestToWorst.length; i++) {
+      int temp = ((int)bestToWorst[i][0]);
+      bestToWorst[i][0] = m_attributeList[temp];
+      bestToWorst[i][1] = m_attributeMerit[temp];
+    }
+    
+    if (m_numToSelect > bestToWorst.length) {
+      throw new Exception("More attributes requested than exist in the data");
+    }
+
+    if (m_numToSelect <= 0) {
+      if (m_threshold == -Double.MAX_VALUE) {
+        m_calculatedNumToSelect = bestToWorst.length;
+      } else {
+        determineNumToSelectFromThreshold(bestToWorst);
+      }
+    }
+    /*    if (m_numToSelect > 0) {
+      determineThreshFromNumToSelect(bestToWorst);
+      } */
+
+    return  bestToWorst;
+  }
+
+  private void determineNumToSelectFromThreshold(double [][] ranking) {
+    int count = 0;
+    for (int i = 0; i < ranking.length; i++) {
+      if (ranking[i][1] > m_threshold) {
+        count++;
+      }
+    }
+    m_calculatedNumToSelect = count;
+  }
+
+  private void determineThreshFromNumToSelect(double [][] ranking) 
+    throws Exception {
+    if (m_numToSelect > ranking.length) {
+      throw new Exception("More attributes requested than exist in the data");
+    }
+
+    if (m_numToSelect == ranking.length) {
+      return;
+    }
+
+    m_threshold = (ranking[m_numToSelect-1][1] + 
+                   ranking[m_numToSelect][1]) / 2.0;
+  }
+
+  /**
+   * returns a description of the search as a String
+   * @return a description of the search
+   */
+  public String toString () {
+    StringBuffer BfString = new StringBuffer();
+    BfString.append("\tAttribute ranking.\n");
+
+    if (m_starting != null) {
+      BfString.append("\tIgnored attributes: ");
+
+      BfString.append(startSetToString());
+      BfString.append("\n");
+    }
+
+    if (m_threshold != -Double.MAX_VALUE) {
+      BfString.append("\tThreshold for discarding attributes: "
+                      + Utils.doubleToString(m_threshold,8,4)+"\n");
+    }
+
+    return BfString.toString();
+  }
+
+
+  /**
+   * Resets stuff to default values
+   */
+  protected void resetOptions () {
+    m_starting = null;
+    m_startRange = new Range();
+    m_attributeList = null;
+    m_attributeMerit = null;
+    m_threshold = -Double.MAX_VALUE;
+  }
+
+
+  private boolean inStarting (int feat) {
+    // omit the class from the evaluation
+    if ((m_hasClass == true) && (feat == m_classIndex)) {
+      return  true;
+    }
+
+    if (m_starting == null) {
+      return  false;
+    }
+
+    for (int i = 0; i < m_starting.length; i++) {
+      if (m_starting[i] == feat) {
+        return  true;
+      }
+    }
+
+    return  false;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.26 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ReliefFAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ReliefFAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ReliefFAttributeEval.java	(revision 29)
@@ -0,0 +1,1348 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ReliefFAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * ReliefFAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by repeatedly sampling an instance and considering the value of the given attribute for the nearest instance of the same and different class. Can operate on both discrete and continuous class data.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Kenji Kira, Larry A. Rendell: A Practical Approach to Feature Selection. In: Ninth International Workshop on Machine Learning, 249-256, 1992.<br/>
+ * <br/>
+ * Igor Kononenko: Estimating Attributes: Analysis and Extensions of RELIEF. In: European Conference on Machine Learning, 171-182, 1994.<br/>
+ * <br/>
+ * Marko Robnik-Sikonja, Igor Kononenko: An adaptation of Relief for attribute estimation in regression. In: Fourteenth International Conference on Machine Learning, 296-304, 1997.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Kira1992,
+ *    author = {Kenji Kira and Larry A. Rendell},
+ *    booktitle = {Ninth International Workshop on Machine Learning},
+ *    editor = {Derek H. Sleeman and Peter Edwards},
+ *    pages = {249-256},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {A Practical Approach to Feature Selection},
+ *    year = {1992}
+ * }
+ * 
+ * &#64;inproceedings{Kononenko1994,
+ *    author = {Igor Kononenko},
+ *    booktitle = {European Conference on Machine Learning},
+ *    editor = {Francesco Bergadano and Luc De Raedt},
+ *    pages = {171-182},
+ *    publisher = {Springer},
+ *    title = {Estimating Attributes: Analysis and Extensions of RELIEF},
+ *    year = {1994}
+ * }
+ * 
+ * &#64;inproceedings{Robnik-Sikonja1997,
+ *    author = {Marko Robnik-Sikonja and Igor Kononenko},
+ *    booktitle = {Fourteenth International Conference on Machine Learning},
+ *    editor = {Douglas H. Fisher},
+ *    pages = {296-304},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {An adaptation of Relief for attribute estimation in regression},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;num instances&gt;
+ *  Specify the number of instances to
+ *  sample when estimating attributes.
+ *  If not specified, then all instances
+ *  will be used.</pre>
+ * 
+ * <pre> -D &lt;seed&gt;
+ *  Seed for randomly sampling instances.
+ *  (Default = 1)</pre>
+ * 
+ * <pre> -K &lt;number of neighbours&gt;
+ *  Number of nearest neighbours (k) used
+ *  to estimate attribute relevances
+ *  (Default = 10).</pre>
+ * 
+ * <pre> -W
+ *  Weight nearest neighbours by distance</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  Specify sigma value (used in an exp
+ *  function to control how quickly
+ *  weights for more distant instances
+ *  decrease. Use in conjunction with -W.
+ *  Sensible value=1/5 to 1/10 of the
+ *  number of nearest neighbours.
+ *  (Default = 2)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class ReliefFAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator,
+             OptionHandler, 
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8422186665795839379L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** Numeric class */
+  private boolean m_numericClass;
+
+  /** The number of classes if class is nominal */
+  private int m_numClasses;
+
+  /** 
+   * Used to hold the probability of a different class val given nearest
+   * instances (numeric class)
+   */
+  private double m_ndc;
+
+  /** 
+   * Used to hold the prob of different value of an attribute given
+   * nearest instances (numeric class case)
+   */
+  private double[] m_nda;
+
+  /**
+   * Used to hold the prob of a different class val and different att
+   * val given nearest instances (numeric class case)
+   */
+  private double[] m_ndcda;
+
+  /** Holds the weights that relief assigns to attributes */
+  private double[] m_weights;
+
+  /** Prior class probabilities (discrete class case) */
+  private double[] m_classProbs;
+
+  /** 
+   * The number of instances to sample when estimating attributes
+   * default == -1, use all instances
+   */
+  private int m_sampleM;
+
+  /** The number of nearest hits/misses */
+  private int m_Knn;
+
+  /** k nearest scores + instance indexes for n classes */
+  private double[][][] m_karray;
+
+  /** Upper bound for numeric attributes */
+  private double[] m_maxArray;
+
+  /** Lower bound for numeric attributes */
+  private double[] m_minArray;
+
+  /** Keep track of the farthest instance for each class */
+  private double[] m_worst;
+
+  /** Index in the m_karray of the farthest instance for each class */
+  private int[] m_index;
+
+  /** Number of nearest neighbours stored of each class */
+  private int[] m_stored;
+ 
+  /** Random number seed used for sampling instances */
+  private int m_seed;
+
+  /**
+   *  used to (optionally) weight nearest neighbours by their distance
+   *  from the instance in question. Each entry holds 
+   *  exp(-((rank(r_i, i_j)/sigma)^2)) where rank(r_i,i_j) is the rank of
+   *  instance i_j in a sequence of instances ordered by the distance
+   *  from r_i. sigma is a user defined parameter, default=20
+   **/
+  private double[] m_weightsByRank;
+  private int m_sigma;
+  
+  /** Weight by distance rather than equal weights */
+  private boolean m_weightByDistance;
+
+  /**
+   * Constructor
+   */
+  public ReliefFAttributeEval () {
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "ReliefFAttributeEval :\n\nEvaluates the worth of an attribute by "
+      +"repeatedly sampling an instance and considering the value of the "
+      +"given attribute for the nearest instance of the same and different "
+      +"class. Can operate on both discrete and continuous class data.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    TechnicalInformation        additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Kenji Kira and Larry A. Rendell");
+    result.setValue(Field.TITLE, "A Practical Approach to Feature Selection");
+    result.setValue(Field.BOOKTITLE, "Ninth International Workshop on Machine Learning");
+    result.setValue(Field.EDITOR, "Derek H. Sleeman and Peter Edwards");
+    result.setValue(Field.YEAR, "1992");
+    result.setValue(Field.PAGES, "249-256");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Igor Kononenko");
+    additional.setValue(Field.TITLE, "Estimating Attributes: Analysis and Extensions of RELIEF");
+    additional.setValue(Field.BOOKTITLE, "European Conference on Machine Learning");
+    additional.setValue(Field.EDITOR, "Francesco Bergadano and Luc De Raedt");
+    additional.setValue(Field.YEAR, "1994");
+    additional.setValue(Field.PAGES, "171-182");
+    additional.setValue(Field.PUBLISHER, "Springer");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Marko Robnik-Sikonja and Igor Kononenko");
+    additional.setValue(Field.TITLE, "An adaptation of Relief for attribute estimation in regression");
+    additional.setValue(Field.BOOKTITLE, "Fourteenth International Conference on Machine Learning");
+    additional.setValue(Field.EDITOR, "Douglas H. Fisher");
+    additional.setValue(Field.YEAR, "1997");
+    additional.setValue(Field.PAGES, "296-304");
+    additional.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+    newVector
+      .addElement(new Option("\tSpecify the number of instances to\n" 
+                             + "\tsample when estimating attributes.\n" 
+                             + "\tIf not specified, then all instances\n" 
+                             + "\twill be used.", "M", 1
+                             , "-M <num instances>"));
+    newVector.
+      addElement(new Option("\tSeed for randomly sampling instances.\n" 
+                            + "\t(Default = 1)", "D", 1
+                            , "-D <seed>"));
+    newVector.
+      addElement(new Option("\tNumber of nearest neighbours (k) used\n" 
+                            + "\tto estimate attribute relevances\n" 
+                            + "\t(Default = 10).", "K", 1
+                            , "-K <number of neighbours>"));
+    newVector.
+      addElement(new Option("\tWeight nearest neighbours by distance", "W"
+                            , 0, "-W"));
+    newVector.
+      addElement(new Option("\tSpecify sigma value (used in an exp\n" 
+                            + "\tfunction to control how quickly\n" 
+                            + "\tweights for more distant instances\n" 
+                            + "\tdecrease. Use in conjunction with -W.\n" 
+                            + "\tSensible value=1/5 to 1/10 of the\n" 
+                            + "\tnumber of nearest neighbours.\n" 
+                            + "\t(Default = 2)", "A", 1, "-A <num>"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;num instances&gt;
+   *  Specify the number of instances to
+   *  sample when estimating attributes.
+   *  If not specified, then all instances
+   *  will be used.</pre>
+   * 
+   * <pre> -D &lt;seed&gt;
+   *  Seed for randomly sampling instances.
+   *  (Default = 1)</pre>
+   * 
+   * <pre> -K &lt;number of neighbours&gt;
+   *  Number of nearest neighbours (k) used
+   *  to estimate attribute relevances
+   *  (Default = 10).</pre>
+   * 
+   * <pre> -W
+   *  Weight nearest neighbours by distance</pre>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  Specify sigma value (used in an exp
+   *  function to control how quickly
+   *  weights for more distant instances
+   *  decrease. Use in conjunction with -W.
+   *  Sensible value=1/5 to 1/10 of the
+   *  number of nearest neighbours.
+   *  (Default = 2)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+    setWeightByDistance(Utils.getFlag('W', options));
+    optionString = Utils.getOption('M', options);
+
+    if (optionString.length() != 0) {
+      setSampleSize(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('D', options);
+
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('K', options);
+
+    if (optionString.length() != 0) {
+      setNumNeighbours(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('A', options);
+
+    if (optionString.length() != 0) {
+      setWeightByDistance(true); // turn on weighting by distance
+      setSigma(Integer.parseInt(optionString));
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sigmaTipText() {
+    return "Set influence of nearest neighbours. Used in an exp function to "
+      +"control how quickly weights decrease for more distant instances. "
+      +"Use in conjunction with weightByDistance. Sensible values = 1/5 to "
+      +"1/10 the number of nearest neighbours.";
+  }
+
+  /**
+   * Sets the sigma value.
+   *
+   * @param s the value of sigma (> 0)
+   * @throws Exception if s is not positive
+   */
+  public void setSigma (int s)
+    throws Exception {
+    if (s <= 0) {
+      throw  new Exception("value of sigma must be > 0!");
+    }
+
+    m_sigma = s;
+  }
+
+
+  /**
+   * Get the value of sigma.
+   *
+   * @return the sigma value.
+   */
+  public int getSigma () {
+    return  m_sigma;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numNeighboursTipText() {
+    return "Number of nearest neighbours for attribute estimation.";
+  }
+
+  /**
+   * Set the number of nearest neighbours
+   *
+   * @param n the number of nearest neighbours.
+   */
+  public void setNumNeighbours (int n) {
+    m_Knn = n;
+  }
+
+
+  /**
+   * Get the number of nearest neighbours
+   *
+   * @return the number of nearest neighbours
+   */
+  public int getNumNeighbours () {
+    return  m_Knn;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Random seed for sampling instances.";
+  }
+
+  /**
+   * Set the random number seed for randomly sampling instances.
+   *
+   * @param s the random number seed.
+   */
+  public void setSeed (int s) {
+    m_seed = s;
+  }
+
+
+  /**
+   * Get the seed used for randomly sampling instances.
+   *
+   * @return the random number seed.
+   */
+  public int getSeed () {
+    return  m_seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sampleSizeTipText() {
+    return "Number of instances to sample. Default (-1) indicates that all "
+      +"instances will be used for attribute estimation.";
+  }
+
+  /**
+   * Set the number of instances to sample for attribute estimation
+   *
+   * @param s the number of instances to sample.
+   */
+  public void setSampleSize (int s) {
+    m_sampleM = s;
+  }
+
+
+  /**
+   * Get the number of instances used for estimating attributes
+   *
+   * @return the number of instances.
+   */
+  public int getSampleSize () {
+    return  m_sampleM;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightByDistanceTipText() {
+    return "Weight nearest neighbours by their distance.";
+  }
+
+  /**
+   * Set the nearest neighbour weighting method
+   *
+   * @param b true nearest neighbours are to be weighted by distance.
+   */
+  public void setWeightByDistance (boolean b) {
+    m_weightByDistance = b;
+  }
+
+
+  /**
+   * Get whether nearest neighbours are being weighted by distance
+   *
+   * @return m_weightByDiffernce
+   */
+  public boolean getWeightByDistance () {
+    return  m_weightByDistance;
+  }
+
+
+  /**
+   * Gets the current settings of ReliefFAttributeEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[9];
+    int current = 0;
+
+    if (getWeightByDistance()) {
+      options[current++] = "-W";
+    }
+
+    options[current++] = "-M";
+    options[current++] = "" + getSampleSize();
+    options[current++] = "-D";
+    options[current++] = "" + getSeed();
+    options[current++] = "-K";
+    options[current++] = "" + getNumNeighbours();
+    
+    if (getWeightByDistance()) {
+      options[current++] = "-A";
+      options[current++] = "" + getSigma();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+
+  /**
+   * Return a description of the ReliefF attribute evaluator.
+   *
+   * @return a description of the evaluator as a String.
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("ReliefF feature evaluator has not been built yet\n");
+    }
+    else {
+      text.append("\tReliefF Ranking Filter");
+      text.append("\n\tInstances sampled: ");
+
+      if (m_sampleM == -1) {
+        text.append("all\n");
+      }
+      else {
+        text.append(m_sampleM + "\n");
+      }
+
+      text.append("\tNumber of nearest neighbours (k): " + m_Knn + "\n");
+
+      if (m_weightByDistance) {
+        text.append("\tExponentially decreasing (with distance) " 
+                    + "influence for\n" 
+                    + "\tnearest neighbours. Sigma: " 
+                    + m_sigma + "\n");
+      }
+      else {
+        text.append("\tEqual influence nearest neighbours\n");
+      }
+    }
+
+    return  text.toString();
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a ReliefF attribute evaluator. 
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+    
+    int z, totalInstances;
+    Random r = new Random(m_seed);
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+
+    if (m_trainInstances.attribute(m_classIndex).isNumeric()) {
+      m_numericClass = true;
+    }
+    else {
+      m_numericClass = false;
+    }
+
+    if (!m_numericClass) {
+      m_numClasses = m_trainInstances.attribute(m_classIndex).numValues();
+    }
+    else {
+      m_ndc = 0;
+      m_numClasses = 1;
+      m_nda = new double[m_numAttribs];
+      m_ndcda = new double[m_numAttribs];
+    }
+
+    if (m_weightByDistance) // set up the rank based weights
+      {
+        m_weightsByRank = new double[m_Knn];
+
+        for (int i = 0; i < m_Knn; i++) {
+          m_weightsByRank[i] = 
+            Math.exp(-((i/(double)m_sigma)*(i/(double)m_sigma)));
+        }
+      }
+
+    // the final attribute weights
+    m_weights = new double[m_numAttribs];
+    // num classes (1 for numeric class) knn neighbours, 
+    // and 0 = distance, 1 = instance index
+    m_karray = new double[m_numClasses][m_Knn][2];
+
+    if (!m_numericClass) {
+      m_classProbs = new double[m_numClasses];
+
+      for (int i = 0; i < m_numInstances; i++) {
+        m_classProbs[(int)m_trainInstances.instance(i).value(m_classIndex)]++;
+      }
+
+      for (int i = 0; i < m_numClasses; i++) {
+        m_classProbs[i] /= m_numInstances;
+      }
+    }
+
+    m_worst = new double[m_numClasses];
+    m_index = new int[m_numClasses];
+    m_stored = new int[m_numClasses];
+    m_minArray = new double[m_numAttribs];
+    m_maxArray = new double[m_numAttribs];
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      m_minArray[i] = m_maxArray[i] = Double.NaN;
+    }
+
+    for (int i = 0; i < m_numInstances; i++) {
+      updateMinMax(m_trainInstances.instance(i));
+    }
+    
+    if ((m_sampleM > m_numInstances) || (m_sampleM < 0)) {
+      totalInstances = m_numInstances;
+    }
+    else {
+      totalInstances = m_sampleM;
+    }
+
+    // process each instance, updating attribute weights
+    for (int i = 0; i < totalInstances; i++) {
+      if (totalInstances == m_numInstances) {
+        z = i;
+      }
+      else {
+        z = r.nextInt()%m_numInstances;
+      }
+
+      if (z < 0) {
+        z *= -1;
+      }
+
+      if (!(m_trainInstances.instance(z).isMissing(m_classIndex))) {
+        // first clear the knn and worst index stuff for the classes
+        for (int j = 0; j < m_numClasses; j++) {
+          m_index[j] = m_stored[j] = 0;
+
+          for (int k = 0; k < m_Knn; k++) {
+            m_karray[j][k][0] = m_karray[j][k][1] = 0;
+          }
+        }
+
+        findKHitMiss(z);
+
+        if (m_numericClass) {
+          updateWeightsNumericClass(z);
+        }
+        else {
+          updateWeightsDiscreteClass(z);
+        }
+      }
+    }
+
+    // now scale weights by 1/m_numInstances (nominal class) or
+    // calculate weights numeric class
+    // System.out.println("num inst:"+m_numInstances+" r_ndc:"+r_ndc);
+    for (int i = 0; i < m_numAttribs; i++) {if (i != m_classIndex) {
+      if (m_numericClass) {
+        m_weights[i] = m_ndcda[i]/m_ndc - 
+          ((m_nda[i] - m_ndcda[i])/((double)totalInstances - m_ndc));
+      }
+      else {
+        m_weights[i] *= (1.0/(double)totalInstances);
+      }
+
+      //          System.out.println(r_weights[i]);
+    }
+    }
+  }
+
+
+  /**
+   * Evaluates an individual attribute using ReliefF's instance based approach.
+   * The actual work is done by buildEvaluator which evaluates all features.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+    return  m_weights[attribute];
+  }
+
+
+  /**
+   * Reset options to their default values
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_sampleM = -1;
+    m_Knn = 10;
+    m_sigma = 2;
+    m_weightByDistance = false;
+    m_seed = 1;
+  }
+
+
+  /**
+   * Normalizes a given value of a numeric attribute.
+   *
+   * @param x the value to be normalized
+   * @param i the attribute's index
+   * @return the normalized value
+   */
+  private double norm (double x, int i) {
+    if (Double.isNaN(m_minArray[i]) || 
+        Utils.eq(m_maxArray[i], m_minArray[i])) {
+      return  0;
+    }
+    else {
+      return  (x - m_minArray[i])/(m_maxArray[i] - m_minArray[i]);
+    }
+  }
+
+
+  /**
+   * Updates the minimum and maximum values for all the attributes
+   * based on a new instance.
+   *
+   * @param instance the new instance
+   */
+  private void updateMinMax (Instance instance) {
+    //    for (int j = 0; j < m_numAttribs; j++) {
+    try {
+      for (int j = 0; j < instance.numValues(); j++) {
+        if ((instance.attributeSparse(j).isNumeric()) && 
+            (!instance.isMissingSparse(j))) {
+          if (Double.isNaN(m_minArray[instance.index(j)])) {
+            m_minArray[instance.index(j)] = instance.valueSparse(j);
+            m_maxArray[instance.index(j)] = instance.valueSparse(j);
+          }
+        else {
+          if (instance.valueSparse(j) < m_minArray[instance.index(j)]) {
+            m_minArray[instance.index(j)] = instance.valueSparse(j);
+          }
+          else {
+            if (instance.valueSparse(j) > m_maxArray[instance.index(j)]) {
+              m_maxArray[instance.index(j)] = instance.valueSparse(j);
+            }
+          }
+        }
+        }
+      }
+    } catch (Exception ex) {
+      System.err.println(ex);
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Computes the difference between two given attribute
+   * values.
+   */
+  private double difference(int index, double val1, double val2) {
+
+    switch (m_trainInstances.attribute(index).type()) {
+    case Attribute.NOMINAL:
+      
+      // If attribute is nominal
+      if (Utils.isMissingValue(val1) || 
+          Utils.isMissingValue(val2)) {
+        return (1.0 - (1.0/((double)m_trainInstances.
+                            attribute(index).numValues())));
+      } else if ((int)val1 != (int)val2) {
+        return 1;
+      } else {
+        return 0;
+      }
+    case Attribute.NUMERIC:
+
+      // If attribute is numeric
+      if (Utils.isMissingValue(val1) || 
+          Utils.isMissingValue(val2)) {
+        if (Utils.isMissingValue(val1) && 
+            Utils.isMissingValue(val2)) {
+          return 1;
+        } else {
+          double diff;
+          if (Utils.isMissingValue(val2)) {
+            diff = norm(val1, index);
+          } else {
+            diff = norm(val2, index);
+          }
+          if (diff < 0.5) {
+            diff = 1.0 - diff;
+          }
+          return diff;
+        }
+      } else {
+        return Math.abs(norm(val1, index) - norm(val2, index));
+      }
+    default:
+      return 0;
+    }
+  }
+
+  /**
+   * Calculates the distance between two instances
+   *
+   * @param first the first instance
+   * @param second the second instance
+   * @return the distance between the two given instances, between 0 and 1
+   */          
+  private double distance(Instance first, Instance second) {  
+
+    double distance = 0;
+    int firstI, secondI;
+
+    for (int p1 = 0, p2 = 0; 
+         p1 < first.numValues() || p2 < second.numValues();) {
+      if (p1 >= first.numValues()) {
+        firstI = m_trainInstances.numAttributes();
+      } else {
+        firstI = first.index(p1); 
+      }
+      if (p2 >= second.numValues()) {
+        secondI = m_trainInstances.numAttributes();
+      } else {
+        secondI = second.index(p2);
+      }
+      if (firstI == m_trainInstances.classIndex()) {
+        p1++; continue;
+      } 
+      if (secondI == m_trainInstances.classIndex()) {
+        p2++; continue;
+      } 
+      double diff;
+      if (firstI == secondI) {
+        diff = difference(firstI, 
+                          first.valueSparse(p1),
+                          second.valueSparse(p2));
+        p1++; p2++;
+      } else if (firstI > secondI) {
+        diff = difference(secondI, 
+                          0, second.valueSparse(p2));
+        p2++;
+      } else {
+        diff = difference(firstI, 
+                          first.valueSparse(p1), 0);
+        p1++;
+      }
+      //      distance += diff * diff;
+      distance += diff;
+    }
+    
+    //    return Math.sqrt(distance / m_NumAttributesUsed);
+    return distance;
+  }
+
+
+  /**
+   * update attribute weights given an instance when the class is numeric
+   *
+   * @param instNum the index of the instance to use when updating weights
+   */
+  private void updateWeightsNumericClass (int instNum) {
+    int i, j;
+    double temp,temp2;
+    int[] tempSorted = null;
+    double[] tempDist = null;
+    double distNorm = 1.0;
+    int firstI, secondI;
+
+    Instance inst = m_trainInstances.instance(instNum);
+   
+    // sort nearest neighbours and set up normalization variable
+    if (m_weightByDistance) {
+      tempDist = new double[m_stored[0]];
+
+      for (j = 0, distNorm = 0; j < m_stored[0]; j++) {
+        // copy the distances
+        tempDist[j] = m_karray[0][j][0];
+        // sum normalizer
+        distNorm += m_weightsByRank[j];
+      }
+
+      tempSorted = Utils.sort(tempDist);
+    }
+
+    for (i = 0; i < m_stored[0]; i++) {
+      // P diff prediction (class) given nearest instances
+      if (m_weightByDistance) {
+        temp = difference(m_classIndex, 
+                          inst.value(m_classIndex),
+                          m_trainInstances.
+                          instance((int)m_karray[0][tempSorted[i]][1]).
+                          value(m_classIndex));
+        temp *= (m_weightsByRank[i]/distNorm);
+      }
+      else {
+        temp = difference(m_classIndex, 
+                          inst.value(m_classIndex), 
+                          m_trainInstances.
+                          instance((int)m_karray[0][i][1]).
+                          value(m_classIndex));
+        temp *= (1.0/(double)m_stored[0]); // equal influence
+      }
+
+      m_ndc += temp;
+
+      Instance cmp;
+      cmp = (m_weightByDistance) 
+        ? m_trainInstances.instance((int)m_karray[0][tempSorted[i]][1])
+        : m_trainInstances.instance((int)m_karray[0][i][1]);
+ 
+      double temp_diffP_diffA_givNearest = 
+        difference(m_classIndex, inst.value(m_classIndex),
+                   cmp.value(m_classIndex));
+      // now the attributes
+      for (int p1 = 0, p2 = 0; 
+           p1 < inst.numValues() || p2 < cmp.numValues();) {
+        if (p1 >= inst.numValues()) {
+          firstI = m_trainInstances.numAttributes();
+        } else {
+          firstI = inst.index(p1); 
+        }
+        if (p2 >= cmp.numValues()) {
+          secondI = m_trainInstances.numAttributes();
+        } else {
+          secondI = cmp.index(p2);
+        }
+        if (firstI == m_trainInstances.classIndex()) {
+          p1++; continue;
+        } 
+        if (secondI == m_trainInstances.classIndex()) {
+          p2++; continue;
+        } 
+        temp = 0.0;
+        temp2 = 0.0;
+      
+        if (firstI == secondI) {
+          j = firstI;
+          temp = difference(j, inst.valueSparse(p1), cmp.valueSparse(p2)); 
+          p1++;p2++;
+        } else if (firstI > secondI) {
+          j = secondI;
+          temp = difference(j, 0, cmp.valueSparse(p2));
+          p2++;
+        } else {
+          j = firstI;
+          temp = difference(j, inst.valueSparse(p1), 0);
+          p1++;
+        } 
+       
+        temp2 = temp_diffP_diffA_givNearest * temp; 
+        // P of different prediction and different att value given
+        // nearest instances
+        if (m_weightByDistance) {
+          temp2 *= (m_weightsByRank[i]/distNorm);
+        }
+        else {
+          temp2 *= (1.0/(double)m_stored[0]); // equal influence
+        }
+
+        m_ndcda[j] += temp2;
+       
+        // P of different attribute val given nearest instances
+        if (m_weightByDistance) {
+          temp *= (m_weightsByRank[i]/distNorm);
+        }
+        else {
+          temp *= (1.0/(double)m_stored[0]); // equal influence
+        }
+
+        m_nda[j] += temp;
+      }
+    }
+  }
+
+
+  /**
+   * update attribute weights given an instance when the class is discrete
+   *
+   * @param instNum the index of the instance to use when updating weights
+   */
+  private void updateWeightsDiscreteClass (int instNum) {
+    int i, j, k;
+    int cl;
+    double temp_diff, w_norm = 1.0;
+    double[] tempDistClass;
+    int[] tempSortedClass = null;
+    double distNormClass = 1.0;
+    double[] tempDistAtt;
+    int[][] tempSortedAtt = null;
+    double[] distNormAtt = null;
+    int firstI, secondI;
+
+    // store the indexes (sparse instances) of non-zero elements
+    Instance inst = m_trainInstances.instance(instNum);
+
+    // get the class of this instance
+    cl = (int)m_trainInstances.instance(instNum).value(m_classIndex);
+
+    // sort nearest neighbours and set up normalization variables
+    if (m_weightByDistance) {
+      // do class (hits) first
+      // sort the distances
+      tempDistClass = new double[m_stored[cl]];
+
+      for (j = 0, distNormClass = 0; j < m_stored[cl]; j++) {
+        // copy the distances
+        tempDistClass[j] = m_karray[cl][j][0];
+        // sum normalizer
+        distNormClass += m_weightsByRank[j];
+      }
+
+      tempSortedClass = Utils.sort(tempDistClass);
+      // do misses (other classes)
+      tempSortedAtt = new int[m_numClasses][1];
+      distNormAtt = new double[m_numClasses];
+
+      for (k = 0; k < m_numClasses; k++) {
+        if (k != cl) // already done cl
+          {
+            // sort the distances
+            tempDistAtt = new double[m_stored[k]];
+
+            for (j = 0, distNormAtt[k] = 0; j < m_stored[k]; j++) {
+              // copy the distances
+              tempDistAtt[j] = m_karray[k][j][0];
+              // sum normalizer
+              distNormAtt[k] += m_weightsByRank[j];
+            }
+
+            tempSortedAtt[k] = Utils.sort(tempDistAtt);
+          }
+      }
+    }
+
+    if (m_numClasses > 2) {
+      // the amount of probability space left after removing the
+      // probability of this instance's class value
+      w_norm = (1.0 - m_classProbs[cl]);
+    }
+    
+    // do the k nearest hits of the same class
+    for (j = 0, temp_diff = 0.0; j < m_stored[cl]; j++) {
+      Instance cmp;
+      cmp = (m_weightByDistance) 
+        ? m_trainInstances.
+        instance((int)m_karray[cl][tempSortedClass[j]][1])
+        : m_trainInstances.instance((int)m_karray[cl][j][1]);
+
+      for (int p1 = 0, p2 = 0; 
+           p1 < inst.numValues() || p2 < cmp.numValues();) {
+        if (p1 >= inst.numValues()) {
+          firstI = m_trainInstances.numAttributes();
+        } else {
+          firstI = inst.index(p1); 
+        }
+        if (p2 >= cmp.numValues()) {
+          secondI = m_trainInstances.numAttributes();
+        } else {
+          secondI = cmp.index(p2);
+        }
+        if (firstI == m_trainInstances.classIndex()) {
+          p1++; continue;
+        } 
+        if (secondI == m_trainInstances.classIndex()) {
+          p2++; continue;
+        } 
+        if (firstI == secondI) {
+          i = firstI;
+          temp_diff = difference(i, inst.valueSparse(p1), 
+                                 cmp.valueSparse(p2)); 
+          p1++;p2++;
+        } else if (firstI > secondI) {
+          i = secondI;
+          temp_diff = difference(i, 0, cmp.valueSparse(p2));
+          p2++;
+        } else {
+          i = firstI;
+          temp_diff = difference(i, inst.valueSparse(p1), 0);
+          p1++;
+        } 
+        
+        if (m_weightByDistance) {
+          temp_diff *=
+            (m_weightsByRank[j]/distNormClass);
+        } else {
+          if (m_stored[cl] > 0) {
+            temp_diff /= (double)m_stored[cl];
+          }
+        }
+        m_weights[i] -= temp_diff;
+
+      }
+    }
+      
+
+    // now do k nearest misses from each of the other classes
+    temp_diff = 0.0;
+
+    for (k = 0; k < m_numClasses; k++) {
+      if (k != cl) // already done cl
+        {
+          for (j = 0; j < m_stored[k]; j++) {
+            Instance cmp;
+            cmp = (m_weightByDistance) 
+              ? m_trainInstances.
+              instance((int)m_karray[k][tempSortedAtt[k][j]][1])
+              : m_trainInstances.instance((int)m_karray[k][j][1]);
+        
+            for (int p1 = 0, p2 = 0; 
+                 p1 < inst.numValues() || p2 < cmp.numValues();) {
+              if (p1 >= inst.numValues()) {
+                firstI = m_trainInstances.numAttributes();
+              } else {
+                firstI = inst.index(p1); 
+              }
+              if (p2 >= cmp.numValues()) {
+                secondI = m_trainInstances.numAttributes();
+              } else {
+                secondI = cmp.index(p2);
+              }
+              if (firstI == m_trainInstances.classIndex()) {
+                p1++; continue;
+              } 
+              if (secondI == m_trainInstances.classIndex()) {
+                p2++; continue;
+              } 
+              if (firstI == secondI) {
+                i = firstI;
+                temp_diff = difference(i, inst.valueSparse(p1), 
+                                       cmp.valueSparse(p2)); 
+                p1++;p2++;
+              } else if (firstI > secondI) {
+                i = secondI;
+                temp_diff = difference(i, 0, cmp.valueSparse(p2));
+                p2++;
+              } else {
+                i = firstI;
+                temp_diff = difference(i, inst.valueSparse(p1), 0);
+                p1++;
+              } 
+
+              if (m_weightByDistance) {
+                temp_diff *=
+                  (m_weightsByRank[j]/distNormAtt[k]);
+              }
+              else {
+                if (m_stored[k] > 0) {
+                  temp_diff /= (double)m_stored[k];
+                }
+              }
+              if (m_numClasses > 2) {
+                m_weights[i] += ((m_classProbs[k]/w_norm)*temp_diff);
+              } else {
+                m_weights[i] += temp_diff;
+              }
+            }
+          }
+        }
+    }
+  }
+
+
+  /**
+   * Find the K nearest instances to supplied instance if the class is numeric,
+   * or the K nearest Hits (same class) and Misses (K from each of the other
+   * classes) if the class is discrete.
+   *
+   * @param instNum the index of the instance to find nearest neighbours of
+   */
+  private void findKHitMiss (int instNum) {
+    int i, j;
+    int cl;
+    double ww;
+    double temp_diff = 0.0;
+    Instance thisInst = m_trainInstances.instance(instNum);
+
+    for (i = 0; i < m_numInstances; i++) {
+      if (i != instNum) {
+        Instance cmpInst = m_trainInstances.instance(i);
+        temp_diff = distance(cmpInst, thisInst);
+
+        // class of this training instance or 0 if numeric
+        if (m_numericClass) {
+          cl = 0;
+        }
+        else {
+          cl = (int)m_trainInstances.instance(i).value(m_classIndex);
+        }
+
+        // add this diff to the list for the class of this instance
+        if (m_stored[cl] < m_Knn) {
+          m_karray[cl][m_stored[cl]][0] = temp_diff;
+          m_karray[cl][m_stored[cl]][1] = i;
+          m_stored[cl]++;
+
+          // note the worst diff for this class
+          for (j = 0, ww = -1.0; j < m_stored[cl]; j++) {
+            if (m_karray[cl][j][0] > ww) {
+              ww = m_karray[cl][j][0];
+              m_index[cl] = j;
+            }
+          }
+
+          m_worst[cl] = ww;
+        }
+        else 
+          /* if we already have stored knn for this class then check to
+             see if this instance is better than the worst */
+          {
+            if (temp_diff < m_karray[cl][m_index[cl]][0]) {
+              m_karray[cl][m_index[cl]][0] = temp_diff;
+              m_karray[cl][m_index[cl]][1] = i;
+
+              for (j = 0, ww = -1.0; j < m_stored[cl]; j++) {
+                if (m_karray[cl][j][0] > ww) {
+                  ww = m_karray[cl][j][0];
+                  m_index[cl] = j;
+                }
+              }
+
+              m_worst[cl] = ww;
+            }
+          }
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new ReliefFAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SVMAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SVMAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SVMAttributeEval.java	(revision 29)
@@ -0,0 +1,837 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SVMAttributeEval.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.functions.SMO;
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * SVMAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by using an SVM classifier. Attributes are ranked by the square of the weight assigned by the SVM. Attribute selection for multiclass problems is handled by ranking attributes for each class seperately using a one-vs-all method and then "dealing" from the top of each pile to give a final ranking.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * I. Guyon, J. Weston, S. Barnhill, V. Vapnik (2002). Gene selection for cancer classification using support vector machines. Machine Learning. 46:389-422.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Guyon2002,
+ *    author = {I. Guyon and J. Weston and S. Barnhill and V. Vapnik},
+ *    journal = {Machine Learning},
+ *    pages = {389-422},
+ *    title = {Gene selection for cancer classification using support vector machines},
+ *    volume = {46},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;constant rate of elimination&gt;
+ *  Specify the constant rate of attribute
+ *  elimination per invocation of
+ *  the support vector machine.
+ *  Default = 1.</pre>
+ * 
+ * <pre> -Y &lt;percent rate of elimination&gt;
+ *  Specify the percentage rate of attributes to
+ *  elimination per invocation of
+ *  the support vector machine.
+ *  Trumps constant rate (above threshold).
+ *  Default = 0.</pre>
+ * 
+ * <pre> -Z &lt;threshold for percent elimination&gt;
+ *  Specify the threshold below which 
+ *  percentage attribute elimination
+ *  reverts to the constant method.</pre>
+ * 
+ * <pre> -P &lt;epsilon&gt;
+ *  Specify the value of P (epsilon
+ *  parameter) to pass on to the
+ *  support vector machine.
+ *  Default = 1.0e-25</pre>
+ * 
+ * <pre> -T &lt;tolerance&gt;
+ *  Specify the value of T (tolerance
+ *  parameter) to pass on to the
+ *  support vector machine.
+ *  Default = 1.0e-10</pre>
+ * 
+ * <pre> -C &lt;complexity&gt;
+ *  Specify the value of C (complexity
+ *  parameter) to pass on to the
+ *  support vector machine.
+ *  Default = 1.0</pre>
+ * 
+ * <pre> -N
+ *  Whether the SVM should 0=normalize/1=standardize/2=neither.
+ *  (default 0=normalize)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Kieran Holland
+ * @version $Revision: 1.28 $
+ */
+public class SVMAttributeEval 
+  extends ASEvaluation
+  implements AttributeEvaluator, 
+             OptionHandler, 
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6489975709033967447L;
+
+  /** The attribute scores */
+  private double[] m_attScores;
+
+  /** Constant rate of attribute elimination per iteration */
+  private int m_numToEliminate = 1;
+
+  /** Percentage rate of attribute elimination, trumps constant
+      rate (above threshold), ignored if = 0  */
+  private int m_percentToEliminate = 0;
+
+  /** Threshold below which percent elimination switches to
+      constant elimination */
+  private int m_percentThreshold = 0;
+
+  /** Complexity parameter to pass on to SMO */
+  private double m_smoCParameter = 1.0;
+
+  /** Tolerance parameter to pass on to SMO */
+  private double m_smoTParameter = 1.0e-10;
+
+  /** Epsilon parameter to pass on to SMO */
+  private double m_smoPParameter = 1.0e-25;
+
+  /** Filter parameter to pass on to SMO */
+  private int m_smoFilterType = 0;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "SVMAttributeEval :\n\nEvaluates the worth of an attribute by "
+      + "using an SVM classifier. Attributes are ranked by the square of the "
+      + "weight assigned by the SVM. Attribute selection for multiclass "
+      + "problems is handled by ranking attributes for each class seperately "
+      + "using a one-vs-all method and then \"dealing\" from the top of "
+      + "each pile to give a final ranking.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "I. Guyon and J. Weston and S. Barnhill and V. Vapnik");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.TITLE, "Gene selection for cancer classification using support vector machines");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "46");
+    result.setValue(Field.PAGES, "389-422");
+    
+    return result;
+  }
+
+  /**
+   * Constructor
+   */
+  public SVMAttributeEval() {
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing all the available options
+   *
+   * @return an enumeration of options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the constant rate of attribute\n"
+                                    + "\telimination per invocation of\n"
+                                    + "\tthe support vector machine.\n"
+                                    + "\tDefault = 1.",
+                                    "X",
+                                    1,
+                                    "-X <constant rate of elimination>"));
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the percentage rate of attributes to\n"
+                                    + "\telimination per invocation of\n"
+                                    + "\tthe support vector machine.\n"
+                                    + "\tTrumps constant rate (above threshold).\n"
+                                    + "\tDefault = 0.",
+                                    "Y",
+                                    1,
+                                    "-Y <percent rate of elimination>"));
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the threshold below which \n"
+                                    + "\tpercentage attribute elimination\n"
+                                    + "\treverts to the constant method.",
+                                    "Z",
+                                    1,
+                                    "-Z <threshold for percent elimination>"));
+
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the value of P (epsilon\n"
+                                    + "\tparameter) to pass on to the\n"
+                                    + "\tsupport vector machine.\n"
+                                    + "\tDefault = 1.0e-25",
+                                    "P",
+                                    1,
+                                    "-P <epsilon>"));
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the value of T (tolerance\n"
+                                    + "\tparameter) to pass on to the\n"
+                                    + "\tsupport vector machine.\n"
+                                    + "\tDefault = 1.0e-10",
+                                    "T",
+                                    1,
+                                    "-T <tolerance>"));
+
+    newVector.addElement(
+                         new Option(
+                                    "\tSpecify the value of C (complexity\n"
+                                    + "\tparameter) to pass on to the\n"
+                                    + "\tsupport vector machine.\n"
+                                    + "\tDefault = 1.0",
+                                    "C",
+                                    1,
+                                    "-C <complexity>"));
+
+    newVector.addElement(new Option("\tWhether the SVM should "
+                                    + "0=normalize/1=standardize/2=neither.\n"
+                                    + "\t(default 0=normalize)",
+                                    "N",
+                                    1,
+                                    "-N"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;constant rate of elimination&gt;
+   *  Specify the constant rate of attribute
+   *  elimination per invocation of
+   *  the support vector machine.
+   *  Default = 1.</pre>
+   * 
+   * <pre> -Y &lt;percent rate of elimination&gt;
+   *  Specify the percentage rate of attributes to
+   *  elimination per invocation of
+   *  the support vector machine.
+   *  Trumps constant rate (above threshold).
+   *  Default = 0.</pre>
+   * 
+   * <pre> -Z &lt;threshold for percent elimination&gt;
+   *  Specify the threshold below which 
+   *  percentage attribute elimination
+   *  reverts to the constant method.</pre>
+   * 
+   * <pre> -P &lt;epsilon&gt;
+   *  Specify the value of P (epsilon
+   *  parameter) to pass on to the
+   *  support vector machine.
+   *  Default = 1.0e-25</pre>
+   * 
+   * <pre> -T &lt;tolerance&gt;
+   *  Specify the value of T (tolerance
+   *  parameter) to pass on to the
+   *  support vector machine.
+   *  Default = 1.0e-10</pre>
+   * 
+   * <pre> -C &lt;complexity&gt;
+   *  Specify the value of C (complexity
+   *  parameter) to pass on to the
+   *  support vector machine.
+   *  Default = 1.0</pre>
+   * 
+   * <pre> -N
+   *  Whether the SVM should 0=normalize/1=standardize/2=neither.
+   *  (default 0=normalize)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an error occurs
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString;
+
+    optionString = Utils.getOption('X', options);
+    if (optionString.length() != 0) {
+      setAttsToEliminatePerIteration(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('Y', options);
+    if (optionString.length() != 0) {
+      setPercentToEliminatePerIteration(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('Z', options);
+    if (optionString.length() != 0) {
+      setPercentThreshold(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setEpsilonParameter((new Double(optionString)).doubleValue());
+    }
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() != 0) {
+      setToleranceParameter((new Double(optionString)).doubleValue());
+    }
+
+    optionString = Utils.getOption('C', options);
+    if (optionString.length() != 0) {
+      setComplexityParameter((new Double(optionString)).doubleValue());
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(optionString), SMO.TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(SMO.FILTER_NORMALIZE, SMO.TAGS_FILTER));
+    }
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of SVMAttributeEval
+   *
+   * @return an array of strings suitable for passing to setOptions() 
+   */
+  public String[] getOptions() {
+    String[] options = new String[14];
+    int current = 0;
+
+    options[current++] = "-X";
+    options[current++] = "" + getAttsToEliminatePerIteration();
+
+    options[current++] = "-Y";
+    options[current++] = "" + getPercentToEliminatePerIteration();
+
+    options[current++] = "-Z";
+    options[current++] = "" + getPercentThreshold();
+                
+    options[current++] = "-P";
+    options[current++] = "" + getEpsilonParameter();
+
+    options[current++] = "-T";
+    options[current++] = "" + getToleranceParameter();          
+
+    options[current++] = "-C";
+    options[current++] = "" + getComplexityParameter();         
+
+    options[current++] = "-N";
+    options[current++] = "" + m_smoFilterType;          
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  //________________________________________________________________________
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String attsToEliminatePerIterationTipText() {
+    return "Constant rate of attribute elimination.";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String percentToEliminatePerIterationTipText() {
+    return "Percent rate of attribute elimination.";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String percentThresholdTipText() {
+    return "Threshold below which percent elimination reverts to constant elimination.";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String epsilonParameterTipText() {
+    return "P epsilon parameter to pass to the SVM";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String toleranceParameterTipText() {
+    return "T tolerance parameter to pass to the SVM";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String complexityParameterTipText() {
+    return "C complexity parameter to pass to the SVM";
+  }
+
+  /**
+   * Returns a tip text for this property suitable for display in the
+   * GUI
+   *
+   * @return tip text string describing this property
+   */
+  public String filterTypeTipText() {
+    return "filtering used by the SVM";
+  }
+
+  //________________________________________________________________________
+
+  /**
+   * Set the constant rate of attribute elimination per iteration
+   *
+   * @param cRate the constant rate of attribute elimination per iteration
+   */
+  public void setAttsToEliminatePerIteration(int cRate) {
+    m_numToEliminate = cRate;
+  }
+
+  /**
+   * Get the constant rate of attribute elimination per iteration
+   *
+   * @return the constant rate of attribute elimination per iteration
+   */
+  public int getAttsToEliminatePerIteration() {
+    return m_numToEliminate;
+  }
+
+  /**
+   * Set the percentage of attributes to eliminate per iteration
+   *
+   * @param pRate percent of attributes to eliminate per iteration
+   */
+  public void setPercentToEliminatePerIteration(int pRate) {
+    m_percentToEliminate = pRate;
+  }
+
+  /**
+   * Get the percentage rate of attribute elimination per iteration
+   *
+   * @return the percentage rate of attribute elimination per iteration
+   */
+  public int getPercentToEliminatePerIteration() {
+    return m_percentToEliminate;
+  }
+
+  /**
+   * Set the threshold below which percentage elimination reverts to
+   * constant elimination.
+   *
+   * @param pThresh percent of attributes to eliminate per iteration
+   */
+  public void setPercentThreshold(int pThresh) {
+    m_percentThreshold = pThresh;
+  }
+
+  /**
+   * Get the threshold below which percentage elimination reverts to 
+   * constant elimination.
+   *
+   * @return the threshold below which percentage elimination stops
+   */
+  public int getPercentThreshold() {
+    return m_percentThreshold;
+  }
+
+  /**
+   * Set the value of P for SMO
+   *
+   * @param svmP the value of P
+   */
+  public void setEpsilonParameter(double svmP) {
+    m_smoPParameter = svmP;
+  }
+
+  /**
+   * Get the value of P used with SMO
+   *
+   * @return the value of P
+   */
+  public double getEpsilonParameter() {
+    return m_smoPParameter;
+  }
+        
+  /**
+   * Set the value of T for SMO
+   *
+   * @param svmT the value of T
+   */
+  public void setToleranceParameter(double svmT) {
+    m_smoTParameter = svmT;
+  }
+
+  /**
+   * Get the value of T used with SMO
+   *
+   * @return the value of T
+   */
+  public double getToleranceParameter() {
+    return m_smoTParameter;
+  }
+
+
+  /**
+   * Set the value of C for SMO
+   *
+   * @param svmC the value of C
+   */
+  public void setComplexityParameter(double svmC) {
+    m_smoCParameter = svmC;
+  }
+
+  /**
+   * Get the value of C used with SMO
+   *
+   * @return the value of C
+   */
+  public double getComplexityParameter() {
+    return m_smoCParameter;
+  }
+
+  /**
+   * The filtering mode to pass to SMO
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+    
+    if (newType.getTags() == SMO.TAGS_FILTER) {
+      m_smoFilterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the filtering mode passed to SMO
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_smoFilterType, SMO.TAGS_FILTER);
+  }
+
+  //________________________________________________________________________
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities        result;
+    
+    result = new SMO().getCapabilities();
+    
+    result.setOwner(this);
+    
+    // only binary attributes are allowed, otherwise the NominalToBinary
+    // filter inside SMO will increase the number of attributes which in turn
+    // will lead to ArrayIndexOutOfBounds-Exceptions.
+    result.disable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.BINARY_ATTRIBUTES);
+    result.disableAllAttributeDependencies();
+    
+    return result;
+  }
+
+  /**
+   * Initializes the evaluator.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator(Instances data) throws Exception {
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    //System.out.println("Class attribute: " + data.attribute(data.classIndex()).name());
+    // Check settings           
+    m_numToEliminate = (m_numToEliminate > 1) ? m_numToEliminate : 1;
+    m_percentToEliminate = (m_percentToEliminate < 100) ? m_percentToEliminate : 100;
+    m_percentToEliminate = (m_percentToEliminate > 0) ? m_percentToEliminate : 0;
+    m_percentThreshold = (m_percentThreshold < data.numAttributes()) ? m_percentThreshold : data.numAttributes() - 1;
+    m_percentThreshold = (m_percentThreshold > 0) ? m_percentThreshold : 0;
+
+    // Get ranked attributes for each class seperately, one-vs-all
+    int[][] attScoresByClass;
+    int numAttr = data.numAttributes() - 1;
+    if(data.numClasses()>2) {
+      attScoresByClass = new int[data.numClasses()][numAttr];
+      for (int i = 0; i < data.numClasses(); i++) {
+        attScoresByClass[i] = rankBySVM(i, data);
+      }
+    }
+    else {
+      attScoresByClass = new int[1][numAttr];
+      attScoresByClass[0] = rankBySVM(0, data);
+    }
+
+    // Cycle through class-specific ranked lists, poping top one off for each class
+    // and adding it to the overall ranked attribute list if it's not there already
+    ArrayList ordered = new ArrayList(numAttr);
+    for (int i = 0; i < numAttr; i++) {
+      for (int j = 0; j < (data.numClasses()>2 ? data.numClasses() : 1); j++) {
+        Integer rank = new Integer(attScoresByClass[j][i]);
+        if (!ordered.contains(rank))
+          ordered.add(rank);
+      }
+    }
+    m_attScores = new double[data.numAttributes()];
+    Iterator listIt = ordered.iterator();
+    for (double i = (double) numAttr; listIt.hasNext(); i = i - 1.0) {
+      m_attScores[((Integer) listIt.next()).intValue()] = i;
+    }
+  }
+
+  /**
+   * Get SVM-ranked attribute indexes (best to worst) selected for
+   * the class attribute indexed by classInd (one-vs-all).
+   */
+  private int[] rankBySVM(int classInd, Instances data) {
+    // Holds a mapping into the original array of attribute indices
+    int[] origIndices = new int[data.numAttributes()];
+    for (int i = 0; i < origIndices.length; i++)
+      origIndices[i] = i;
+    
+    // Count down of number of attributes remaining
+    int numAttrLeft = data.numAttributes()-1;
+    // Ranked attribute indices for this class, one vs.all (highest->lowest)
+    int[] attRanks = new int[numAttrLeft];
+
+    try {
+      MakeIndicator filter = new MakeIndicator();
+      filter.setAttributeIndex("" + (data.classIndex() + 1));
+      filter.setNumeric(false);
+      filter.setValueIndex(classInd);
+      filter.setInputFormat(data);
+      Instances trainCopy = Filter.useFilter(data, filter);
+      double pctToElim = ((double) m_percentToEliminate) / 100.0;
+      while (numAttrLeft > 0) {
+        int numToElim;
+        if (pctToElim > 0) {
+          numToElim = (int) (trainCopy.numAttributes() * pctToElim);
+          numToElim = (numToElim > 1) ? numToElim : 1;
+          if (numAttrLeft - numToElim <= m_percentThreshold) {
+            pctToElim = 0;
+            numToElim = numAttrLeft - m_percentThreshold;
+          }
+        } else {
+          numToElim = (numAttrLeft >= m_numToEliminate) ? m_numToEliminate : numAttrLeft;
+        }
+        
+        // Build the linear SVM with default parameters
+        SMO smo = new SMO();
+                                
+        // SMO seems to get stuck if data not normalised when few attributes remain
+        // smo.setNormalizeData(numAttrLeft < 40);
+        smo.setFilterType(new SelectedTag(m_smoFilterType, SMO.TAGS_FILTER));
+        smo.setEpsilon(m_smoPParameter);
+        smo.setToleranceParameter(m_smoTParameter);
+        smo.setC(m_smoCParameter);
+        smo.buildClassifier(trainCopy);
+                                
+        // Find the attribute with maximum weight^2
+        double[] weightsSparse = smo.sparseWeights()[0][1];
+        int[] indicesSparse = smo.sparseIndices()[0][1];
+        double[] weights = new double[trainCopy.numAttributes()];
+        for (int j = 0; j < weightsSparse.length; j++) {
+          weights[indicesSparse[j]] = weightsSparse[j] * weightsSparse[j];
+        }
+        weights[trainCopy.classIndex()] = Double.MAX_VALUE;
+        int minWeightIndex;
+        int[] featArray = new int[numToElim];
+        boolean[] eliminated = new boolean[origIndices.length];
+        for (int j = 0; j < numToElim; j++) {
+          minWeightIndex = Utils.minIndex(weights);
+          attRanks[--numAttrLeft] = origIndices[minWeightIndex];
+          featArray[j] = minWeightIndex;
+          eliminated[minWeightIndex] = true;
+          weights[minWeightIndex] = Double.MAX_VALUE;
+        }
+                                
+        // Delete the worst attributes. 
+        weka.filters.unsupervised.attribute.Remove delTransform =
+          new weka.filters.unsupervised.attribute.Remove();
+        delTransform.setInvertSelection(false);
+        delTransform.setAttributeIndicesArray(featArray);
+        delTransform.setInputFormat(trainCopy);
+        trainCopy = Filter.useFilter(trainCopy, delTransform);
+                                
+        // Update the array of remaining attribute indices
+        int[] temp = new int[origIndices.length - numToElim];
+        int k = 0;
+        for (int j = 0; j < origIndices.length; j++) {
+          if (!eliminated[j]) {
+            temp[k++] = origIndices[j];
+          }
+        }
+        origIndices = temp;
+      }                 
+      // Carefully handle all exceptions
+    } catch (Exception e) {
+      e.printStackTrace();                      
+    }
+    return attRanks;
+  }
+
+  /**
+   * Resets options to defaults.
+   */
+  protected void resetOptions() {
+    m_attScores = null;
+  }
+
+  /**
+   * Evaluates an attribute by returning the rank of the square of its coefficient in a
+   * linear support vector machine.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute(int attribute) throws Exception {
+    return m_attScores[attribute];
+  }
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    if (m_attScores == null) {
+      text.append("\tSVM feature evaluator has not been built yet");
+    } else {
+      text.append("\tSVM feature evaluator");
+    }
+
+    text.append("\n");
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.28 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    runEvaluator(new SVMAttributeEval(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/ScatterSearchV1.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/ScatterSearchV1.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/ScatterSearchV1.java	(revision 29)
@@ -0,0 +1,1312 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ScatterSearchV1.java
+ *    Copyright (C) 2008 Adrian Pino
+ *    Copyright (C) 2008 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.attributeSelection;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+
+/**
+ * Class for performing the Sequential Scatter Search. <p>
+ *
+ <!-- globalinfo-start -->
+ * Scatter Search :<br/>
+ * <br/>
+ * Performs an Scatter Search  through the space of attribute subsets. Start with a population of many significants and diverses subset  stops when the result is higher than a given treshold or there's not more improvement<br/>
+ * For more information see:<br/>
+ * <br/>
+ * Felix Garcia Lopez (2004). Solving feature subset selection problem by a Parallel Scatter Search. Elsevier.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -Z &lt;num&gt;
+ *  Specify the number of subsets to generate 
+ *  in the initial population..</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Specify the treshold used for considering when a subset is significant.</pre>
+ * 
+ * <pre> -R &lt;0 = greedy combination | 1 = reduced greedy combination &gt;
+ *  Specify the kind of combiantion 
+ *  for using it in the combination method.</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Set the random number seed.
+ *  (default = 1)</pre>
+ * 
+ * <pre> -D
+ *  Verbose output for monitoring the search.</pre>
+ * 
+ <!-- options-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Lopez2004,
+ *    author = {Felix Garcia Lopez},
+ *    month = {October},
+ *    publisher = {Elsevier},
+ *    title = {Solving feature subset selection problem by a Parallel Scatter Search},
+ *    year = {2004},
+ *    language = {English}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * from the Book: Solving feature subset selection problem by a Parallel Scatter Search, Felix Garcia Lopez.
+ * @author Adrian Pino (apinoa@facinf.uho.edu.cu)
+ * @version $Revision: 4977 $
+ *
+ */
+
+public class ScatterSearchV1 extends ASSearch
+  implements OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8512041420388121326L;
+
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** holds the class index */
+  private int m_classIndex;
+
+  /** holds the treshhold that delimits the good attributes */
+  private double m_treshold;
+
+  /** the initial threshold */
+  private double m_initialThreshold;
+
+  /** the kind of comination betwen parents ((0)greedy combination/(1)reduced greedy combination)*/
+  int m_typeOfCombination;
+
+  /** random number generation */
+  private Random m_random;
+
+  /** seed for random number generation */
+  private int m_seed;
+
+  /** verbose output for monitoring the search and debugging */
+  private boolean m_debug = false;
+
+  /** holds a report of the search */
+  private StringBuffer m_InformationReports;
+
+  /** total number of subsets evaluated during a search */
+  private int m_totalEvals;
+
+  /** holds the merit of the best subset found */
+  protected double m_bestMerit;
+
+  /** time for procesing the search method */
+  private long m_processinTime;
+
+  /** holds the Initial Population of Subsets*/
+  private List<Subset> m_population;
+
+  /** holds the population size*/
+  private int m_popSize;
+
+  /** holds the user selected initial population size */
+  private int m_initialPopSize;
+
+  /** if no initial user pop size, then this holds the initial
+   * pop size calculated from the number of attributes in the data
+   * (for use in the toString() method)
+   */
+  private int m_calculatedInitialPopSize;
+
+  /** holds the subsets most significants and diverses
+   * of the population (ReferenceSet).
+   *
+   * (transient because the subList() method returns
+   * a non serializable Object).
+   */
+  private transient List<Subset> m_ReferenceSet;
+
+  /** holds the greedy combination(reduced or not) of all the subsets of the ReferenceSet*/
+  private transient List<Subset> m_parentsCombination;
+
+  /**holds the attributes ranked*/
+  private List<Subset> m_attributeRanking;
+
+  /**Evaluator used to know the significance of a subset (for guiding the search)*/
+  private SubsetEvaluator ASEvaluator =null;
+
+
+  /** kind of combination */
+  protected static final int COMBINATION_NOT_REDUCED = 0;
+  protected static final int COMBINATION_REDUCED = 1;  ;
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(COMBINATION_NOT_REDUCED, "Greedy Combination"),
+    new Tag(COMBINATION_REDUCED, "Reduced Greedy Combination")
+  };
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Scatter Search :\n\nPerforms an Scatter Search  "
+      +"through "
+      +"the space of attribute subsets. Start with a population of many significants and diverses subset "
+      +" stops when the result is higher than a given treshold or there's not more improvement\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Felix Garcia Lopez");
+    result.setValue(Field.MONTH, "October");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.TITLE, "Solving feature subset selection problem by a Parallel Scatter Search");
+    result.setValue(Field.PUBLISHER, "Elsevier");
+    result.setValue(Field.LANGUAGE, "English");
+
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.0$");
+  }
+
+  public ScatterSearchV1 () {
+    resetOptions();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String tresholdTipText() {
+    return "Set the treshold that subsets most overcome to be considered as significants";
+  }
+
+  /**
+   * Set the treshold
+   *
+   * @param treshold for identifyng significant subsets
+   */
+  public void setTreshold (double treshold) {
+    m_initialThreshold = treshold;
+  }
+
+  /**
+   * Get the treshold
+   *
+   * @return the treshold that subsets most overcome to be considered as significants
+   */
+  public double getTreshold () {
+    return m_initialThreshold;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String populationSizeTipText() {
+    return "Set the number of subset to generate in the initial Population";
+  }
+
+  /**
+   * Set the population size
+   *
+   * @param size the number of subset in the initial population
+   */
+  public void setPopulationSize (int size) {
+    m_initialPopSize = size;
+  }
+
+  /**
+   * Get the population size
+   *
+   * @return the number of subsets to generate in the initial population
+   */
+  public int getPopulationSize () {
+    return m_initialPopSize;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String combinationTipText() {
+    return "Set the kind of combination for using it to combine ReferenceSet subsets.";
+  }
+
+  /**
+   * Set the kind of combination
+   *
+   * @param c the kind of combination of the search
+   */
+  public void setCombination (SelectedTag c) {
+    if (c.getTags() == TAGS_SELECTION) {
+      m_typeOfCombination = c.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the combination
+   *
+   * @return the kind of combination used in the Combination method
+   */
+  public SelectedTag getCombination () {
+    return new SelectedTag(m_typeOfCombination, TAGS_SELECTION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Set the random seed.";
+  }
+
+  /**
+   * set the seed for random number generation
+   * @param s seed value
+   */
+  public void setSeed(int s) {
+    m_seed = s;
+  }
+
+  /**
+   * get the value of the random number generator's seed
+   * @return the seed for random number generation
+   */
+  public int getSeed() {
+    return m_seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Turn on verbose output for monitoring the search's progress.";
+  }
+
+  /**
+   * Set whether verbose output should be generated.
+   * @param d true if output is to be verbose.
+   */
+  public void setDebug(boolean d) {
+    m_debug = d;
+  }
+
+  /**
+   * Get whether output is to be verbose
+   * @return true if output will be verbose
+   */
+  public boolean getDebug() {
+    return m_debug;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option("\tSpecify the number of subsets to generate "
+    	                            + "\n\tin the initial population.."
+				    ,"Z",1
+				    , "-Z <num>"));
+    newVector.addElement(new Option("\tSpecify the treshold used for considering when a subset is significant."
+				    , "T", 1
+				    , "-T <threshold>"));
+    newVector.addElement(new Option("\tSpecify the kind of combiantion "
+				    + "\n\tfor using it in the combination method."
+				    , "R", 1, "-R <0 = greedy combination | 1 = reduced greedy combination >"));
+	  newVector.addElement(new Option("\tSet the random number seed."
+                                    +"\n\t(default = 1)"
+                                    , "S", 1, "-S <seed>"));
+    newVector.addElement(new Option("\tVerbose output for monitoring the search.","D",0,"-D"));
+
+    return  newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p>
+   *
+   * -Z <br>
+   * Specify the number of subsets to generate in the initial population.<p>
+   *
+   * -T <start set> <br>
+   * Specify the treshold used for considering when a subset is significant. <p>
+   *
+   * -R <br>
+   * Specify the kind of combiantion. <p>
+   *
+   * -S <br>
+   *  Set the random number seed.
+   *  (default = 1)
+   *
+   * -D <br>
+   *  Verbose output for monitoring the search
+   *  (default = false)
+   *  
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+
+    optionString = Utils.getOption('Z', options);
+    if (optionString.length() != 0) {
+      setPopulationSize(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('T', options);
+    if (optionString.length() != 0) {
+      setTreshold(Double.parseDouble(optionString));
+    }
+
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setCombination(new SelectedTag(Integer.parseInt(optionString),
+				   TAGS_SELECTION));
+    } else {
+      setCombination(new SelectedTag(COMBINATION_NOT_REDUCED, TAGS_SELECTION));
+    }
+
+    optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of ScatterSearchV1.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[9];
+    int current = 0;
+
+    options[current++] = "-T";
+    options[current++] = "" + getTreshold ();
+
+    options[current++] = "-Z";
+    options[current++] = ""+getPopulationSize ();
+
+    options[current++] = "-R";
+    options[current++] = ""+String.valueOf (getCombination ().getSelectedTag ().getID ());
+
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    if (getDebug())
+      options[current++] = "-D";
+
+    while (current < options.length)
+      options[current++] = "";
+
+    return  options;
+  }
+
+  /**
+   * returns a description of the search.
+   * @return a description of the search as a String.
+   */
+  public String toString() {
+    StringBuffer FString = new StringBuffer();
+    FString.append("\tScatter Search "
+		   + "\n\tInit Population: "+m_calculatedInitialPopSize);
+
+    FString.append("\n\tKind of Combination: "
+		   +getCombination ().getSelectedTag ().getReadable ());
+
+		FString.append("\n\tRandom number seed: "+m_seed);
+
+		FString.append("\n\tDebug: "+m_debug);
+
+    FString.append("\n\tTreshold: "
+		   +Utils.doubleToString(Math.abs(getTreshold ()),8,3)+"\n");
+
+    FString.append("\tTotal number of subsets evaluated: "
+		    + m_totalEvals + "\n");
+
+    FString.append("\tMerit of best subset found: "
+		    +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
+
+    /* FString.append("\tTime procesing the search space: "
+		    +(double)m_processinTime/1000+" seconds\n"); */
+
+    if(m_debug)
+      return FString.toString()+"\n\n"+m_InformationReports.toString ();
+
+    return FString.toString();
+  }
+
+
+  /**
+   * Searches the attribute subset space using Scatter Search.
+   *
+   * @param ASEval the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array of selected attribute indexes
+   * @exception Exception if the search can't be completed
+   */
+  public int[] search(ASEvaluation ASEval, Instances data)
+    throws Exception{
+
+    m_totalEvals = 0;
+    m_popSize = m_initialPopSize;
+    m_calculatedInitialPopSize = m_initialPopSize;
+    m_treshold = m_initialThreshold;
+    m_processinTime =System.currentTimeMillis ();
+    m_InformationReports = new StringBuffer();
+
+    m_numAttribs =data.numAttributes ();
+    m_classIndex =data.classIndex ();
+
+    if(m_popSize<=0) {
+      m_popSize =m_numAttribs/2;
+      m_calculatedInitialPopSize = m_popSize;
+    }
+
+    ASEvaluator =(SubsetEvaluator)ASEval;
+
+    if(!(m_treshold >= 0)){
+      m_treshold =calculateTreshhold();
+      m_totalEvals++;
+    }
+
+    m_random = new Random(m_seed);
+
+    m_attributeRanking =RankEachAttribute();
+
+    CreatePopulation(m_popSize);
+
+    int bestSolutions =m_popSize/4;
+    int divSolutions =m_popSize/4;
+
+    if(m_popSize < 4){
+
+      bestSolutions = m_popSize/2;
+      divSolutions = m_popSize/2;
+
+      if(m_popSize == 1) return attributeList(((Subset)m_population.get (0)).subset);
+    }
+
+
+    m_ReferenceSet =new ArrayList<Subset>();
+
+    for (int i = 0; i<m_population.size (); i++) {
+      m_ReferenceSet.add (m_population.get (i)) ;
+    }
+
+
+    GenerateReferenceSet(m_ReferenceSet, bestSolutions, divSolutions);
+
+
+    m_InformationReports.append ("Population: "+m_population.size ()+"\n");
+    m_InformationReports.append ("merit    \tsubset\n");
+
+    for (int i = 0; i < m_population.size (); i++)
+    	m_InformationReports.append (printSubset (m_population.get (i)));
+
+
+    m_ReferenceSet =m_ReferenceSet.subList (0,bestSolutions+divSolutions);
+
+
+    /*TEST*/
+    m_InformationReports.append ("\nReferenceSet:");
+    m_InformationReports.append ("\n----------------Most Significants Solutions--------------\n");
+    for (int i = 0; i<m_ReferenceSet.size (); i++) {
+      if(i ==bestSolutions) m_InformationReports.append ("----------------Most Diverses Solutions--------------\n");
+      m_InformationReports.append(printSubset (m_ReferenceSet.get (i)));
+    }
+
+
+    Subset bestTemp =new Subset(new BitSet(m_numAttribs),0);
+
+    while (!(bestTemp.isEqual (m_ReferenceSet.get (0))) /*|| (m_treshold > bestTemp.merit)*/) {
+	  //while(){
+	      CombineParents();
+		    ImproveSolutions();
+   // }
+        bestTemp =m_ReferenceSet.get (0);
+
+        int numBest =m_ReferenceSet.size ()/2;
+        int numDiverses =m_ReferenceSet.size ()/2;
+
+	      UpdateReferenceSet(numBest, numDiverses);
+	      m_ReferenceSet = m_ReferenceSet.subList (0,numBest+numDiverses);
+
+    }
+
+    m_InformationReports.append("\nLast Reference Set Updated:\n");
+    m_InformationReports.append ("merit    \tsubset\n");
+
+    for (int i = 0; i <m_ReferenceSet.size (); i++)
+    	m_InformationReports.append (printSubset (m_ReferenceSet.get (i)));
+
+
+ 	  m_bestMerit =bestTemp.merit;
+
+ 	  m_processinTime =System.currentTimeMillis () -m_processinTime;
+
+    return attributeList (bestTemp.subset);
+  }
+
+  /**
+   * Generate the a ReferenceSet containing the n best solutions and the m most diverse solutions of
+   * the initial Population.
+   *
+   * @param ReferenceSet the ReferenceSet for storing these solutions
+   * @param bestSolutions the number of the most pure solutions.
+   * @param divSolutions the number of the most diverses solutions acording to the bestSolutions.
+   */
+  public void GenerateReferenceSet(List<Subset> ReferenceSet, int bestSolutions, int divSolutions){
+
+    //Sorting the Initial ReferenceSet
+    ReferenceSet =bubbleSubsetSort (ReferenceSet);
+
+    // storing all the attributes that are now in the ReferenceSet (just till bestSolutions)
+    BitSet allBits_RefSet =getAllBits (ReferenceSet.subList (0,bestSolutions));
+
+    // for stopping when ReferenceSet.size () ==bestSolutions+divSolutions
+    int refSetlength =bestSolutions;
+    int total =bestSolutions+divSolutions;
+
+    while (refSetlength <total) {
+
+      List<Integer> aux =new ArrayList<Integer>();
+
+      for (int i =refSetlength; i <ReferenceSet.size (); i ++) {
+        aux.add (SimetricDiference (((Subset)ReferenceSet.get (i)).clone (),allBits_RefSet));
+	  }
+
+
+	  int mostDiv =getIndexofBiggest(aux);
+	  ReferenceSet.set(refSetlength, ReferenceSet.get (refSetlength+mostDiv));
+	  //ReferenceSet.remove (refSetlength +mostDiv);
+
+	  refSetlength++;
+
+	  allBits_RefSet =getAllBits (ReferenceSet.subList (0,refSetlength));
+	}
+
+	  ReferenceSet =filterSubset (ReferenceSet,refSetlength);
+  }
+
+  /**
+   * Update the ReferenceSet putting the new obtained Solutions there
+   *
+   * @param numBestSolutions the number of the most pure solutions.
+   * @param numDivsSolutions the number of the most diverses solutions acording to the bestSolutions.
+  */
+  public void UpdateReferenceSet(int numBestSolutions, int numDivsSolutions){
+
+    for (int i = 0; i <m_parentsCombination.size (); i++) m_ReferenceSet.add (i, m_parentsCombination.get (i));
+
+      GenerateReferenceSet (m_ReferenceSet,numBestSolutions,numDivsSolutions);
+  }
+
+  /**
+   * Improve the solutions previously combined by adding the attributes that improve that solution
+   * @exception Exception if there is some trouble evaluating the candidate solutions
+   */
+  public void ImproveSolutions()
+    throws Exception{
+
+    for (int i = 0; i<m_parentsCombination.size (); i++) {
+
+      BitSet aux1 =(BitSet)((Subset)m_parentsCombination.get (i)).subset.clone ();
+      List<Subset> ranking =new ArrayList<Subset>();
+
+      /*
+      for(int j=aux1.nextClearBit (0); j<=m_numAttribs; j=aux1.nextClearBit(j+1)){
+      	if(j ==m_classIndex)continue;
+
+      	BitSet aux2 =new BitSet(m_numAttribs);
+      	aux2.set (j);
+
+      	double merit =ASEvaluator.evaluateSubset (aux2);
+      	m_totalEvals++;
+
+      	ranking.add (new Subset((BitSet)aux2.clone (), merit));
+      }
+
+      ranking =bubbleSubsetSort (ranking);
+      */
+
+      for (int k =0; k <m_attributeRanking.size (); k ++) {
+        Subset s1 =((Subset)m_attributeRanking.get (k)).clone ();
+        BitSet b1 =(BitSet)s1.subset.clone ();
+
+        Subset s2 =((Subset)m_parentsCombination.get (i)).clone ();
+        BitSet b2 =(BitSet)s2.subset.clone ();
+
+        if(b2.get (b1.nextSetBit (0))) continue;
+
+        b2.or (b1);
+        double newMerit =ASEvaluator.evaluateSubset (b2);
+        m_totalEvals++;
+
+        if(newMerit <= s2.merit)break;
+
+        m_parentsCombination.set (i,new Subset(b2,newMerit));
+	    }
+
+      filterSubset (m_parentsCombination,m_ReferenceSet.size());
+    }
+  }
+
+   /**
+   * Combine all the posible pair solutions existing in the Population
+   *
+   * @exception Exception if there is some trouble evaluating the new childs
+   */
+  public void CombineParents()
+    throws Exception{
+
+    m_parentsCombination =new ArrayList<Subset>();
+
+    // this two 'for' are for selecting parents in the refSet
+	for (int i= 0; i <m_ReferenceSet.size ()-1; i ++) {
+	  for (int j= i+1; j <m_ReferenceSet.size (); j ++) {
+
+	    // Selecting parents
+	    Subset parent1 =m_ReferenceSet.get (i);
+	    Subset parent2 =m_ReferenceSet.get (j);
+
+	    // Initializing childs Intersecting parents
+	    Subset child1 = intersectSubsets (parent1, parent2);
+	    Subset child2 =child1.clone ();
+
+	    // Initializing childs Intersecting parents
+	    Subset simDif =simetricDif (parent1, parent2, getCombination ().getSelectedTag ().getID ());
+
+	    BitSet aux =(BitSet)simDif.subset.clone ();
+
+	    boolean improvement =true;
+
+	    while (improvement) {
+
+	      Subset best1 =getBestgen (child1,aux);
+	      Subset best2 =getBestgen (child2,aux);
+
+	      if(best1 !=null || best2!=null){
+
+	        if(best2 ==null){
+	       	  child1 =best1.clone ();
+	       	  continue;
+	       	}
+	       	if(best1 ==null){
+	       	  child2 =best2.clone ();
+	       	  continue;
+	       	}
+	       	if(best1 !=null && best2 !=null){
+	       	  double merit1 =best1.merit;
+	       	  double merit2 =best2.merit;
+
+	       	  if(merit1 >merit2){
+	       	  	child1 =best1.clone ();
+	       	  	continue;
+	       	  }
+	       	  if(merit1 <merit2){
+	       	  	child2 =best2.clone ();
+	       	  	continue;
+	       	  }
+	       	  if(merit1 ==merit2){
+	       	  	if(best1.subset.cardinality () > best2.subset.cardinality ()){
+	       	  	  child2 =best2.clone ();
+	       	  	  continue;
+	       	  	}
+	       	  	if(best1.subset.cardinality () < best2.subset.cardinality ()){
+	       	  	  child1 =best1.clone ();
+	       	  	  continue;
+	       	  	}
+	       	  	if(best1.subset.cardinality () == best2.subset.cardinality ()){
+	       	  	  double random = m_random.nextDouble ();
+	       	  	  if(random < 0.5)child1 =best1.clone ();
+	       	  	  else child2 =best2.clone ();
+	       	  	  continue;
+	       	    }
+	       	  }
+	        }
+
+	      }else{
+	       	m_parentsCombination.add (child1);
+	       	m_parentsCombination.add (child2);
+	       	improvement =false;
+	      }
+	    }
+	  }
+    }
+    m_parentsCombination = filterSubset (m_parentsCombination,m_ReferenceSet.size());
+
+    GenerateReferenceSet (m_parentsCombination,m_ReferenceSet.size ()/2, m_ReferenceSet.size ()/2);
+    m_parentsCombination = m_parentsCombination.subList (0, m_ReferenceSet.size ());
+
+  }
+  /**
+   * Create the initial Population
+   *
+   * @param popSize the size of the initial population
+   * @exception Exception if there is a trouble evaluating any solution
+   */
+  public void CreatePopulation(int popSize)
+    throws Exception{
+
+    InitPopulation(popSize);
+
+	/** Delimit the best attributes from the worst*/
+	int segmentation =m_numAttribs/2;
+
+	/*TEST*/
+  /*  System.out.println ("AttributeRanking");
+    for (int i = 0; i <attributeRanking.size (); i++){
+      if(i ==segmentation)System.out.println ("-------------------------SEGMENTATION------------------------");
+      printSubset (attributeRanking.get (i));
+    }
+    */
+	for (int i = 0; i<m_popSize; i++) {
+
+	  List<Subset> attributeRankingCopy = new ArrayList<Subset>();
+	  for (int j = 0; j<m_attributeRanking.size (); j++) attributeRankingCopy.add (m_attributeRanking.get (j));
+
+
+	  double last_evaluation =-999;
+	  double current_evaluation =0;
+
+	  boolean doneAnew =true;
+
+	  while (true) {
+
+	    // generate a random number in the interval[0..segmentation]
+	  	int random_number = m_random.nextInt (segmentation+1) /*generateRandomNumber (segmentation)*/;
+
+	  	if(doneAnew && i <=segmentation)random_number =i;
+	  	doneAnew =false;
+
+	  	Subset s1 =((Subset)attributeRankingCopy.get (random_number)).clone ();
+	  	Subset s2 =((Subset)m_population.get (i)).clone ();
+
+
+	  	// trying to add a new gen in the chromosome i of the population
+	  	Subset joiners =joinSubsets (s1, s2 );
+
+	  	current_evaluation =joiners.merit;
+
+	  	if(current_evaluation > last_evaluation){
+	  	  m_population.set (i,joiners);
+	  	  last_evaluation =current_evaluation;
+
+	  	  try {
+	        attributeRankingCopy.set (random_number, attributeRankingCopy.get (segmentation+1));
+	  	    attributeRankingCopy.remove (segmentation+1);
+          }catch (IndexOutOfBoundsException ex) {
+            attributeRankingCopy.set (random_number,new Subset(new BitSet(m_numAttribs),0));
+          continue;
+           }
+	  	}
+	  	else{
+	  	// there's not more improvement
+	  	break;
+	  	}
+
+
+	  }
+	}
+
+	//m_population =bubbleSubsetSort (m_population);
+  }
+
+
+    /**
+   * Rank all the attributes individually acording to their merits
+   *
+   * @return an ordered List  of Subsets with just one attribute
+   * @exception Exception if the evaluation can not be completed
+   */
+  public List<Subset> RankEachAttribute()
+    throws Exception{
+
+    List<Subset> result =new ArrayList<Subset>();
+
+    for (int i = 0; i<m_numAttribs; i++) {
+      if(i==m_classIndex)continue;
+
+	  BitSet an_Attribute =new BitSet(m_numAttribs);
+	  an_Attribute.set (i);
+
+	  double merit =ASEvaluator.evaluateSubset (an_Attribute);
+      m_totalEvals++;
+
+	  result.add (new Subset(an_Attribute, merit));
+	}
+
+	return bubbleSubsetSort(result);
+  }
+
+
+    //..........
+
+
+    /**
+   * Evaluate each gen of a BitSet inserted in a Subset and get the most significant for that Subset
+   *
+   * @return a new Subset with the union of subset and the best gen of gens.
+   *  in case that there's not improvement with each gen return null
+   * @exception Exception if the evaluation of can not be completed
+   */
+  public Subset getBestgen(Subset subset, BitSet gens)
+    throws Exception{
+    Subset result =null;
+
+    double merit1 =subset.merit;
+
+    for(int i =gens.nextSetBit(0); i >=0; i =gens.nextSetBit(i+1)){
+      BitSet aux =(BitSet)subset.subset.clone ();
+
+	  if(aux.get (i))continue;
+	  aux.set (i);
+
+	  double merit2 =ASEvaluator.evaluateSubset (aux);
+	  m_totalEvals++;
+
+	  if(merit2 >merit1){
+	    merit1 =merit2;
+	    result =new Subset(aux,merit1);
+	  }
+	}
+
+	return result;
+  }
+
+  /**
+   * Sort a List of subsets according to their merits
+   *
+   * @param subsetList the subsetList to be ordered
+   * @return a List with ordered subsets
+   */
+  public List<Subset> bubbleSubsetSort(List<Subset> subsetList){
+    List<Subset> result =new ArrayList<Subset>();
+
+    for (int i = 0; i<subsetList.size ()-1; i++) {
+      Subset subset1 =subsetList.get (i);
+      double merit1 =subset1.merit;
+
+      for (int j = i+1; j<subsetList.size (); j++) {
+      	Subset subset2 =subsetList.get (j);
+      	double merit2 =subset2.merit;
+
+      	if(merit2 > merit1){
+      	  Subset temp =subset1;
+
+      	  subsetList.set (i,subset2);
+      	  subsetList.set (j,temp);
+
+      	  subset1 =subset2;
+      	  merit1 =subset1.merit;
+      	}
+	    }
+    }
+	  return subsetList;
+  }
+
+
+  /**
+   * get the index in a List where this have the biggest number
+   *
+   * @param simDif the Lists of numbers for getting from them the index of the bigger
+   * @return an index that represents where the bigest number is.
+   */
+  public int getIndexofBiggest(List<Integer> simDif){
+    int aux =-99999;
+    int result1 =-1;
+    List<Integer> equalSimDif =new ArrayList<Integer>();
+
+    if(simDif.size ()==0) return -1;
+
+    for (int i = 0; i<simDif.size (); i++) {
+      if(simDif.get (i) >aux){
+        aux =simDif.get (i);
+      	result1 =i;
+      }
+	}
+
+	for (int i =0; i <simDif.size (); i++) {
+	  if(simDif.get (i) ==aux){
+      	equalSimDif.add (i);
+    }
+	}
+
+	int finalResult =equalSimDif.get (m_random.nextInt (equalSimDif.size ()) /*generateRandomNumber (equalSimDif.size ()-1)*/);
+
+	return finalResult;
+  }
+
+   /**
+   * Save in Bitset all the gens that are in many others subsets.
+   *
+   * @param subsets the Lists of subsets for getting from them all their gens
+   * @return a Bitset with all the gens contained in many others subsets.
+   */
+  public BitSet getAllBits(List<Subset> subsets){
+    BitSet result =new BitSet(m_numAttribs);
+
+    for (int i =0; i <subsets.size (); i ++) {
+      BitSet aux =((Subset)subsets.get (i)).clone ().subset;
+
+      for(int j=aux.nextSetBit(0); j>=0; j=aux.nextSetBit(j+1)) {
+      	result.set (j);
+	    }
+	  }
+
+	return result;
+  }
+
+   /**
+   * Creating space for introducing the population
+   *
+   * @param popSize the number of subset in the initial population
+   */
+  public void InitPopulation(int popSize){
+    m_population =new ArrayList<Subset>();
+    for (int i = 0; i<popSize; i++)m_population.add (new Subset(new BitSet(m_numAttribs),0));
+  }
+
+   /**
+   * Join two subsets
+   *
+   * @param subset1 one of the subsets
+   * @param subset2 the other subset
+   * @return a new Subset that is te result of the Join
+   * @exception Exception if the evaluation of the subsets can not be completed
+   */
+  public Subset joinSubsets(Subset subset1, Subset subset2)
+    throws Exception{
+    BitSet b1 =(BitSet)subset1.subset.clone ();
+    BitSet b2 =(BitSet)subset2.subset.clone ();
+
+    b1.or (b2);
+
+    double newMerit =ASEvaluator.evaluateSubset (b1);
+    m_totalEvals++;
+
+    return new Subset((BitSet)b1.clone (), newMerit);
+    }
+
+  /**
+   * Intersects two subsets
+   *
+   * @param subset1 one of the subsets
+   * @param subset2 the other subset
+   * @return a new Subset that is te result of the intersection
+   * @exception Exception if the evaluation of the subsets can not be completed
+   */
+  public Subset intersectSubsets(Subset subset1, Subset subset2)
+    throws Exception{
+    BitSet b1 =(BitSet)subset1.subset.clone ();
+    BitSet b2 =(BitSet)subset2.subset.clone ();
+
+    b1.and (b2);
+
+    double newMerit =ASEvaluator.evaluateSubset (b1);
+    m_totalEvals++;
+
+    return new Subset((BitSet)b1.clone (), newMerit);
+  }
+
+  public Subset simetricDif(Subset subset1, Subset subset2, int mode)
+    throws Exception{
+    BitSet b1 =(BitSet)subset1.subset.clone ();
+    BitSet b2 =(BitSet)subset2.subset.clone ();
+
+    b1.xor (b2);
+
+    double newMerit =ASEvaluator.evaluateSubset (b1);
+    m_totalEvals++;
+
+    Subset result =new Subset((BitSet)b1.clone (), newMerit);
+
+    if(mode == COMBINATION_REDUCED){
+
+      double avgAcurracy =0;
+      int totalSolutions =0;
+      List<Subset> weightVector =new ArrayList<Subset>();
+
+      BitSet res =result.subset;
+      for(int i=res.nextSetBit(0); i>=0; i=res.nextSetBit(i+1)){
+
+      	double merits =0;
+        int numSolutions =0;
+        Subset solution =null;
+
+        for (int j = 0; j <m_ReferenceSet.size (); j ++) {
+          solution =(Subset)m_ReferenceSet.get (j);
+          if(solution.subset.get (i)){
+            merits +=solution.merit;
+            numSolutions ++;
+          }
+	    }
+	    BitSet b =new BitSet(m_numAttribs);
+	    b.set (i);
+	    Subset s =new Subset(b, merits/(double)numSolutions);
+	    weightVector.add (s);
+
+	    avgAcurracy +=merits;
+        totalSolutions ++;
+
+      }
+      avgAcurracy =avgAcurracy/(double)totalSolutions;
+
+      BitSet newResult =new BitSet(m_numAttribs);
+      for (int i = 0; i<weightVector.size (); i++) {
+      	Subset aux =weightVector.get (i);
+      	if(aux.merit >=avgAcurracy){
+      	  newResult.or (aux.subset);
+      	}
+	  }
+	  double merit =ASEvaluator.evaluateSubset (newResult);
+	  result =new Subset(newResult, merit);
+
+    }
+
+    return result;
+  }
+
+  public int generateRandomNumber(int limit){
+
+    return (int)Math.round (Math.random ()*(limit+0.4));
+  }
+
+
+
+    /**
+   * Calculate the treshold of a dataSet given an evaluator
+   *
+   * @return the treshhold of the dataSet
+   * @exception Exception if the calculation can not be completed
+   */
+  public double calculateTreshhold()
+    throws Exception{
+    BitSet fullSet =new BitSet(m_numAttribs);
+
+    for (int i= 0; i< m_numAttribs; i++) {
+      if(i ==m_classIndex)continue;
+       	fullSet.set (i);
+	}
+
+	return ASEvaluator.evaluateSubset (fullSet);
+  }
+
+    /**
+   * Calculate the Simetric Diference of two subsets
+   *
+   * @return the Simetric Diference
+   * @exception Exception if the calculation can not be completed
+   */
+    public int SimetricDiference(Subset subset, BitSet bitset){
+      BitSet aux =subset.clone ().subset;
+      aux.xor (bitset);
+
+      return aux.cardinality ();
+    }
+
+    /**
+   * Filter a given Lis of Subsets removing the equals subsets
+   * @param subsetList to filter
+   * @param preferredSize the preferred size of the new List (if it is -1, then the filter is make it
+   *                      for all subsets, else then the filter method stops when the given preferred
+   *                      size is reached or all the subset have been filtered).
+   * @return a new List filtered
+   * @exception Exception if the calculation can not be completed
+   */
+    public List<Subset> filterSubset(List<Subset> subsetList, int preferredSize){
+      if(subsetList.size () <=preferredSize && preferredSize !=-1)return subsetList;
+
+      for (int i =0; i <subsetList.size ()-1; i ++) {
+      	for (int j =i+1; j <subsetList.size (); j ++) {
+      	  Subset focus =subsetList.get (i);
+      	  if(focus.isEqual (subsetList.get (j))){
+      	    subsetList.remove (j);
+      	    j--;
+
+      	    if(subsetList.size () <=preferredSize && preferredSize !=-1)return subsetList;
+      	  }
+		}
+	  }
+	  return subsetList;
+    }
+    //..........
+
+
+// Test Methods
+
+  public String printSubset(Subset subset){
+    StringBuffer bufferString = new StringBuffer();
+
+    if(subset == null){
+      //System.out.println ("null");
+      return "";
+    }
+
+    BitSet bits =subset.subset;
+    double merit =subset.merit;
+    List<Integer> indexes =new ArrayList<Integer>();
+
+    for (int i = 0; i<m_numAttribs; i++) {
+      if(bits.get (i)){
+      	//System.out.print ("1");
+        indexes.add (i+1);
+      }
+      //else System.out.print ("0");
+    }
+    bufferString.append (Utils.doubleToString (merit,8,5)+"\t "+indexes.toString ()+"\n");
+    //System.out.print (" with a merit of: "+merit);
+
+    return bufferString.toString ();
+  }
+
+    //........
+
+  protected void resetOptions () {
+  	m_popSize = -1;
+  	m_initialPopSize = -1;
+  	m_calculatedInitialPopSize = -1;
+  	m_treshold = -1;
+  	m_typeOfCombination = COMBINATION_NOT_REDUCED;
+  	m_seed = 1;
+  	m_debug = true;
+  	m_totalEvals = 0;
+  	m_bestMerit = 0;
+  	m_processinTime = 0;
+  }
+
+    /**
+   * converts a BitSet into a list of attribute indexes
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   **/
+  public int[] attributeList (BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+	list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+
+
+    // Auxiliar Class for handling Chromosomes and its respective merit
+
+  public class Subset implements Serializable {
+
+    double merit;
+    BitSet subset;
+
+    public Subset(BitSet subset, double merit){
+      this.subset =(BitSet)subset.clone ();
+      this.merit =merit;
+    }
+
+    public boolean isEqual(Subset othersubset){
+      if(subset.equals (othersubset.subset))return true;
+      return false;
+    }
+
+    public Subset clone(){
+      return new Subset((BitSet)subset.clone (), merit);
+    }
+  }
+  //..........
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SignificanceAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SignificanceAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SignificanceAttributeEval.java	(revision 29)
@@ -0,0 +1,640 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SignificanceAttributeEval.java
+ *    Copyright (C) 2009 Adrian Pino
+ *    Copyright (C) 2009 University of Waikato, Hamilton, NZ
+ *
+ */
+package weka.attributeSelection;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Vector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+/**
+ <!-- globalinfo-start -->
+ * Significance :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by computing the Probabilistic Significance as a two-way function.<br/>
+ * (attribute-classes and classes-attribute association)<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Amir Ahmad, Lipika Dey (2004). A feature selection technique for classificatory analysis.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a separate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Ahmad2004,
+ *    author = {Amir Ahmad and Lipika Dey},
+ *    month = {October},
+ *    publisher = {ELSEVIER},
+ *    title = {A feature selection technique for classificatory analysis},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Adrian Pino (apinoa@facinf.uho.edu.cu)
+ * @version $Revision: 5447 $
+ */
+public class SignificanceAttributeEval
+extends ASEvaluation
+implements AttributeEvaluator, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8504656625598579926L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** The number of classes */
+  private int m_numClasses;
+
+  /** Merge missing values */
+  private boolean m_missing_merge;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Significance :\n\nEvaluates the worth of an attribute "
+    +"by computing the Probabilistic Significance as a two-way function.\n"
+    +"(atributte-classes and classes-atribute association)\n\n"
+    + "For more information see:\n\n"
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "Amir Ahmad and Lipika Dey");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.MONTH, "October");
+    result.setValue(Field.TITLE, "A feature selection technique for classificatory analysis");
+    result.setValue(Field.PUBLISHER, "ELSEVIER");
+
+    return result;
+  }
+
+
+  /**
+   * Constructor
+   */
+  public SignificanceAttributeEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option("\ttreat missing values as a separate "
+        + "value.", "M", 0, "-M"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a separate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   **/
+  public void setOptions (String[] options)
+  throws Exception {
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+    +"across other values in proportion to their frequency. Otherwise, "
+    +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[1];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return the capabilities of this evaluator
+   * @see    Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Initializes the Significance attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data
+   * @throws Exception if the evaluator has not been
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+  throws Exception {
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+    Discretize disTransform = new Discretize();
+    disTransform.setUseBetterEncoding(true);
+    disTransform.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, disTransform);
+    m_numClasses = m_trainInstances.attribute(m_classIndex).numValues();
+  }
+
+
+  /**
+   * reset options to default values
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_missing_merge = true;
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring the Significance
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the Significance of the attribute in the data base
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+  throws Exception {
+    int i, j, ii, jj;
+    int ni, nj;
+    double sum = 0.0;
+    ni = m_trainInstances.attribute(attribute).numValues() + 1;
+    nj = m_numClasses + 1;
+    double[] sumi, sumj;
+    Instance inst;
+    double temp = 0.0;
+    sumi = new double[ni];
+    sumj = new double[nj];
+    double[][] counts = new double[ni][nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumj[j] = 0.0;
+        counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(attribute)) {
+        ii = ni - 1;
+      }
+      else {
+        ii = (int)inst.value(attribute);
+      }
+
+      if (inst.isMissing(m_classIndex)) {
+        jj = nj - 1;
+      }
+      else {
+        jj = (int)inst.value(m_classIndex);
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumi[i] += counts[i][j];
+        sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+        sumj[j] += counts[i][j];
+      }
+    }
+
+
+    // distribute missing counts
+    if (m_missing_merge &&
+        (sumi[ni-1] < m_numInstances) &&
+        (sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+        System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = (sumi[ni - 1] + sumj[nj - 1] -
+          counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) {
+        for (j = 0; j < nj - 1; j++) {
+          if (counts[ni - 1][j] > 0.0) {
+            for (i = 0; i < ni - 1; i++) {
+              temp = ((i_copy[i]/(sum - i_copy[ni - 1]))*counts[ni - 1][j]);
+              counts[i][j] += temp;
+              sumi[i] += temp;
+            }
+
+            counts[ni - 1][j] = 0.0;
+          }
+        }
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+        for (i = 0; i < ni - 1; i++) {
+          if (counts[i][nj - 1] > 0.0) {
+            for (j = 0; j < nj - 1; j++) {
+              temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+              counts[i][j] += temp;
+              sumj[j] += temp;
+            }
+
+            counts[i][nj - 1] = 0.0;
+          }
+        }
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0  && total_missing != sum) {
+        for (i = 0; i < ni - 1; i++) {
+          for (j = 0; j < nj - 1; j++) {
+            temp = (counts_copy[i][j]/(sum - total_missing)) *
+            counts_copy[ni - 1][nj - 1];
+            counts[i][j] += temp;
+            sumi[i] += temp;
+            sumj[j] += temp;
+          }
+        }
+
+        counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    /**Working on the ContingencyTables****/
+    double discriminatingPower = associationAttributeClasses(counts);
+    double separability = associationClassesAttribute(counts);
+    /*...*/
+
+
+    return  discriminatingPower + separability / 2;
+  }
+
+  /**
+   * evaluates an individual attribute by measuring the attribute-classes
+   * association
+   *
+   * @param counts the Contingency table where are the frecuency counts values
+   * @return the discriminating power of the attribute
+   */
+  public double associationAttributeClasses(double[][] counts){
+
+    List<Integer> supportSet = new ArrayList<Integer>();
+    List<Integer> not_supportSet = new ArrayList<Integer>();
+
+    double discriminatingPower = 0;
+
+
+    int numValues = counts.length;
+    int numClasses = counts[0].length;
+
+    int total = 0;
+
+    double[] sumRows = new double[numValues];
+    double[] sumCols = new double[numClasses];
+
+    // get the row totals
+    for (int i = 0; i < numValues; i++) {
+      sumRows[i] = 0.0;
+
+      for (int j = 0; j < numClasses; j++) {
+        sumRows[i] += counts[i][j];
+        total += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (int j = 0; j < numClasses; j++) {
+      sumCols[j] = 0.0;
+
+      for (int i = 0; i < numValues; i++) {
+        sumCols[j] += counts[i][j];
+      }
+    }
+
+    for (int i = 0; i < numClasses; i++) {
+      for (int j = 0; j < numValues; j++) {
+
+        //Computing Conditional Probability P(Clasei | Valuej)
+        double numerator1 = counts[j][i];
+        double denominator1 = sumRows[j];
+        double result1;
+
+        if(denominator1 != 0)
+          result1 = numerator1/denominator1;
+        else
+          result1 = 0;
+
+        //Computing Conditional Probability P(Clasei | ^Valuej)
+        double numerator2 = sumCols[i] - counts[j][i];
+        double denominator2 = total - sumRows[j];
+        double result2;
+
+        if(denominator2 != 0)
+          result2 = numerator2/denominator2;
+        else
+          result2 = 0;
+
+
+        if(result1 > result2){
+          supportSet.add (i);
+          discriminatingPower +=result1;
+        }
+        else{
+          not_supportSet.add (i);
+          discriminatingPower +=result2;
+        }
+      }
+
+    }
+
+    return discriminatingPower/numValues - 1.0;
+  }
+
+  /**
+   * evaluates an individual attribute by measuring the classes-attribute
+   * association
+   *
+   * @param counts the Contingency table where are the frecuency counts values
+   * @return the separability power of the classes
+   */
+  public double associationClassesAttribute(double[][] counts){
+
+    List<Integer> supportSet = new ArrayList<Integer>();
+    List<Integer> not_supportSet = new ArrayList<Integer>();
+
+    double separability = 0;
+
+
+    int numValues = counts.length;
+    int numClasses = counts[0].length;
+
+    int total = 0;
+
+    double[] sumRows = new double[numValues];
+    double[] sumCols = new double[numClasses];
+
+    // get the row totals
+    for (int i = 0; i < numValues; i++) {
+      sumRows[i] = 0.0;
+
+      for (int j = 0; j < numClasses; j++) {
+        sumRows[i] += counts[i][j];
+        total += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (int j = 0; j < numClasses; j++) {
+      sumCols[j] = 0.0;
+
+      for (int i = 0; i < numValues; i++) {
+        sumCols[j] += counts[i][j];
+      }
+    }
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = 0; j < numClasses; j++) {
+
+        //Computing Conditional Probability P(Valuei | Clasej)
+        double numerator1 = counts[i][j];
+        double denominator1 = sumCols[j];
+        double result1;
+
+        if(denominator1 != 0)
+          result1 = numerator1/denominator1;
+        else
+          result1 = 0;
+
+        //Computing Conditional Probability P(Valuei | ^Clasej)
+        double numerator2 = sumRows[i] - counts[i][j];
+        double denominator2 = total - sumCols[j];
+        double result2;
+
+        if(denominator2 != 0)
+          result2 = numerator2/denominator2;
+        else
+          result2 = 0;
+
+
+        if(result1 > result2){
+          supportSet.add (i);
+          separability +=result1;
+        }
+        else{
+          not_supportSet.add (i);
+          separability +=result2;
+        }
+      }
+
+    }
+
+    return separability/numClasses - 1.0;
+  }
+
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tSignificance evaluator has not been built");
+    }
+    else {
+      text.append("\tSignificance feature evaluator");
+
+      if (!m_missing_merge) {
+        text.append("\n\tMissing values treated as seperate");
+      }
+    }
+
+    text.append("\n");
+    return  text.toString();
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new SignificanceAttributeEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/StartSetHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/StartSetHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/StartSetHandler.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StartSetHandler.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.attributeSelection;
+
+/** 
+ * Interface for search methods capable of doing something sensible
+ * given a starting set of attributes.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public interface StartSetHandler {
+
+  /**
+   * Sets a starting set of attributes for the search. It is the
+   * search method's responsibility to report this start set (if any)
+   * in its toString() method.
+   * @param startSet a string containing a list of attributes (and or ranges),
+   * eg. 1,2,6,10-15.
+   * @exception Exception if start set can't be set.
+   */
+  void setStartSet (String startSet) throws Exception;
+
+  /**
+   * Returns a list of attributes (and or attribute ranges) as a String
+   * @return a list of attributes (and or attribute ranges)
+   */
+  String getStartSet ();
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetEvaluator.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SubsetEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import java.util.BitSet;
+
+/** 
+ * Interface for attribute subset evaluators.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public interface SubsetEvaluator {
+    
+  /**
+   * evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the "merit" of the subset
+   * @exception Exception if the subset could not be evaluated
+   */
+  double evaluateSubset(BitSet subset) throws Exception;
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetSizeForwardSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetSizeForwardSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SubsetSizeForwardSelection.java	(revision 29)
@@ -0,0 +1,923 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SubsetSizeForwardSelection.java
+ *    Copyright (C) 2007 Martin Guetlein
+ *
+ */
+package weka.attributeSelection;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * SubsetSizeForwardSelection:<br/>
+ * <br/>
+ * Extension of LinearForwardSelection. The search performs an interior cross-validation (seed and number of folds can be specified). A LinearForwardSelection is performed on each foldto determine the optimal subset-size (using the given SubsetSizeEvaluator). Finally, a LinearForwardSelection up to the optimal subset-size is performed on the whole data.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Martin Guetlein (2006). Large Scale Attribute Selection Using Wrappers. Freiburg, Germany.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I
+ *  Perform initial ranking to select the
+ *  top-ranked attributes.</pre>
+ * 
+ * <pre> -K &lt;num&gt;
+ *  Number of top-ranked attributes that are 
+ *  taken into account by the search.</pre>
+ * 
+ * <pre> -T &lt;0 = fixed-set | 1 = fixed-width&gt;
+ *  Type of Linear Forward Selection (default = 0).</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Size of lookup cache for evaluated subsets.
+ *  Expressed as a multiple of the number of
+ *  attributes in the data set. (default = 1)</pre>
+ * 
+ * <pre> -E &lt;subset evaluator&gt;
+ *  Subset-evaluator used for subset-size determination.-- -M</pre>
+ * 
+ * <pre> -F &lt;num&gt;
+ *  Number of cross validation folds
+ *  for subset size determination (default = 5).</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  Seed for cross validation
+ *  subset size determination. (default = 1)</pre>
+ * 
+ * <pre> -Z
+ *  verbose on/off</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.ClassifierSubsetEval:
+ * </pre>
+ * 
+ * <pre> -B &lt;classifier&gt;
+ *  class name of the classifier to use for accuracy estimation.
+ *  Place any classifier options LAST on the command line
+ *  following a "--". eg.:
+ *   -B weka.classifiers.bayes.NaiveBayes ... -- -K
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> -T
+ *  Use the training data to estimate accuracy.</pre>
+ * 
+ * <pre> -H &lt;filename&gt;
+ *  Name of the hold out/test set to 
+ *  estimate accuracy on.</pre>
+ * 
+ * <pre> 
+ * Options specific to scheme weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Martin Guetlein (martin.guetlein@gmail.com)
+ * @version $Revision: 5604 $
+ */
+public class SubsetSizeForwardSelection extends ASSearch
+  implements OptionHandler {
+  /** search directions */
+  protected static final int TYPE_FIXED_SET = 0;
+  protected static final int TYPE_FIXED_WIDTH = 1;
+  public static final Tag[] TAGS_TYPE = {
+    new Tag(TYPE_FIXED_SET, "Fixed-set"),
+    new Tag(TYPE_FIXED_WIDTH, "Fixed-width"),
+  };
+
+  // member variables
+  /** perform initial ranking to select top-ranked attributes */
+  protected boolean m_performRanking;
+
+  /**
+   * number of top-ranked attributes that are taken into account for the
+   * search
+   */
+  protected int m_numUsedAttributes;
+
+  /** 0 == fixed-set, 1 == fixed-width */
+  protected int m_linearSelectionType;
+
+  /** the subset evaluator to use for subset size determination */
+  private ASEvaluation m_setSizeEval;
+
+  /**
+   * Number of cross validation folds for subset size determination (default =
+   * 5).
+   */
+  protected int m_numFolds;
+
+  /** Seed for cross validation subset size determination. (default = 1) */
+  protected int m_seed;
+
+  /** number of attributes in the data */
+  protected int m_numAttribs;
+
+  /** total number of subsets evaluated during a search */
+  protected int m_totalEvals;
+
+  /** for debugging */
+  protected boolean m_verbose;
+
+  /** holds the merit of the best subset found */
+  protected double m_bestMerit;
+
+  /** holds the maximum size of the lookup cache for evaluated subsets */
+  protected int m_cacheSize;
+
+  /**
+   * Constructor
+   */
+  public SubsetSizeForwardSelection() {
+    resetOptions();
+  }
+
+  /**
+   * Returns a string describing this search method
+   *
+   * @return a description of the search method suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "SubsetSizeForwardSelection:\n\n" +
+      "Extension of LinearForwardSelection. The search performs an interior " +
+      "cross-validation (seed and number of folds can be specified). A " +
+      "LinearForwardSelection is performed on each foldto determine the optimal " +
+      "subset-size (using the given SubsetSizeEvaluator). Finally, a " +
+      "LinearForwardSelection up to the optimal subset-size is performed on " +
+      "the whole data.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    TechnicalInformation        additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Martin Guetlein and Eibe Frank and Mark Hall");
+    result.setValue(Field.YEAR, "2009");
+    result.setValue(Field.TITLE, "Large Scale Attribute Selection Using Wrappers");
+    result.setValue(Field.BOOKTITLE, "Proc IEEE Symposium on Computational Intelligence and Data Mining");
+    result.setValue(Field.PAGES, "332-339");
+    result.setValue(Field.PUBLISHER, "IEEE");
+    
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Martin Guetlein");
+    additional.setValue(Field.YEAR, "2006");
+    additional.setValue(Field.TITLE, "Large Scale Attribute Selection Using Wrappers");
+    additional.setValue(Field.SCHOOL, "Albert-Ludwigs-Universitaet");
+    additional.setValue(Field.ADDRESS, "Freiburg, Germany");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   *
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(9);
+
+    newVector.addElement(new Option("\tPerform initial ranking to select the" +
+                                    "\n\ttop-ranked attributes.", "I", 0, "-I"));
+    newVector.addElement(new Option(
+                                    "\tNumber of top-ranked attributes that are " +
+                                    "\n\ttaken into account by the search.", "K", 1, "-K <num>"));
+    newVector.addElement(new Option(
+                                    "\tType of Linear Forward Selection (default = 0).", "T", 1,
+                                    "-T <0 = fixed-set | 1 = fixed-width>"));
+    newVector.addElement(new Option(
+                                    "\tSize of lookup cache for evaluated subsets." +
+                                    "\n\tExpressed as a multiple of the number of" +
+                                    "\n\tattributes in the data set. (default = 1)", "S", 1, "-S <num>"));
+    newVector.addElement(new Option(
+                                    "\tSubset-evaluator used for subset-size determination." + "-- -M",
+                                    "E", 1, "-E <subset evaluator>"));
+    newVector.addElement(new Option("\tNumber of cross validation folds" +
+                                    "\n\tfor subset size determination (default = 5).", "F", 1, "-F <num>"));
+    newVector.addElement(new Option("\tSeed for cross validation" +
+                                    "\n\tsubset size determination. (default = 1)", "R", 1, "-R <num>"));
+    newVector.addElement(new Option("\tverbose on/off", "Z", 0, "-Z"));
+
+    if ((m_setSizeEval != null) && (m_setSizeEval instanceof OptionHandler)) {
+      newVector.addElement(new Option("", "", 0,
+                                      "\nOptions specific to " + "evaluator " +
+                                      m_setSizeEval.getClass().getName() + ":"));
+
+      Enumeration enu = ((OptionHandler) m_setSizeEval).listOptions();
+
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * Valid options are:
+   * <p>
+   *
+   * -I <br>
+   * Perform initial ranking to select top-ranked attributes.
+   * <p>
+   *
+   * -K <num> <br>
+   * Number of top-ranked attributes that are taken into account.
+   * <p>
+   *
+   * -T <0 = fixed-set | 1 = fixed-width> <br>
+   * Typ of Linear Forward Selection (default = 0).
+   * <p>
+   *
+   * -S <num> <br>
+   * Size of lookup cache for evaluated subsets. Expressed as a multiple of
+   * the number of attributes in the data set. (default = 1).
+   * <p>
+   *
+   * -E <string> <br>
+   * class name of subset evaluator to use for subset size determination
+   * (default = null, same subset evaluator as for ranking and final forward
+   * selection is used). Place any evaluator options LAST on the command line
+   * following a "--". eg. -A weka.attributeSelection.ClassifierSubsetEval ... --
+   * -M
+   *
+   * </pre>
+   *
+   * -F <num> <br>
+   * Number of cross validation folds for subset size determination (default =
+   * 5).
+   * <p>
+   *
+   * -R <num> <br>
+   * Seed for cross validation subset size determination. (default = 1)
+   * <p>
+   *
+   * -Z <br>
+   * verbose on/off.
+   * <p>
+   *
+   * @param options
+   *            the list of options as an array of strings
+   * @exception Exception
+   *                if an option is not supported
+   *
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString;
+    resetOptions();
+
+    setPerformRanking(Utils.getFlag('I', options));
+
+    optionString = Utils.getOption('K', options);
+
+    if (optionString.length() != 0) {
+      setNumUsedAttributes(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('T', options);
+
+    if (optionString.length() != 0) {
+      setType(new SelectedTag(Integer.parseInt(optionString), TAGS_TYPE));
+    } else {
+      setType(new SelectedTag(TYPE_FIXED_SET, TAGS_TYPE));
+    }
+
+    optionString = Utils.getOption('S', options);
+
+    if (optionString.length() != 0) {
+      setLookupCacheSize(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('E', options);
+
+    if (optionString.length() == 0) {
+      System.out.println(
+                         "No subset size evaluator given, using evaluator that is used for final search.");
+      m_setSizeEval = null;
+    } else {
+      setSubsetSizeEvaluator(ASEvaluation.forName(optionString,
+                                                  Utils.partitionOptions(options)));
+    }
+
+    optionString = Utils.getOption('F', options);
+
+    if (optionString.length() != 0) {
+      setNumSubsetSizeCVFolds(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('R', options);
+
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+
+    m_verbose = Utils.getFlag('Z', options);
+  }
+
+  /**
+   * Set the maximum size of the evaluated subset cache (hashtable). This is
+   * expressed as a multiplier for the number of attributes in the data set.
+   * (default = 1).
+   *
+   * @param size
+   *            the maximum size of the hashtable
+   */
+  public void setLookupCacheSize(int size) {
+    if (size >= 0) {
+      m_cacheSize = size;
+    }
+  }
+
+  /**
+   * Return the maximum size of the evaluated subset cache (expressed as a
+   * multiplier for the number of attributes in a data set.
+   *
+   * @return the maximum size of the hashtable.
+   */
+  public int getLookupCacheSize() {
+    return m_cacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String lookupCacheSizeTipText() {
+    return "Set the maximum size of the lookup cache of evaluated subsets. This is " +
+      "expressed as a multiplier of the number of attributes in the data set. " +
+      "(default = 1).";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String performRankingTipText() {
+    return "Perform initial ranking to select top-ranked attributes.";
+  }
+
+  /**
+   * Perform initial ranking to select top-ranked attributes.
+   *
+   * @param b
+   *            true if initial ranking should be performed
+   */
+  public void setPerformRanking(boolean b) {
+    m_performRanking = b;
+  }
+
+  /**
+   * Get boolean if initial ranking should be performed to select the
+   * top-ranked attributes
+   *
+   * @return true if initial ranking should be performed
+   */
+  public boolean getPerformRanking() {
+    return m_performRanking;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String numUsedAttributesTipText() {
+    return "Set the amount of top-ranked attributes that are taken into account by the search process.";
+  }
+
+  /**
+   * Set the number of top-ranked attributes that taken into account by the
+   * search process.
+   *
+   * @param k
+   *            the number of attributes
+   * @exception Exception
+   *                if k is less than 2
+   */
+  public void setNumUsedAttributes(int k) throws Exception {
+    if (k < 2) {
+      throw new Exception("Value of -K must be >= 2.");
+    }
+
+    m_numUsedAttributes = k;
+  }
+
+  /**
+   * Get the number of top-ranked attributes that taken into account by the
+   * search process.
+   *
+   * @return the number of top-ranked attributes that taken into account
+   */
+  public int getNumUsedAttributes() {
+    return m_numUsedAttributes;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String typeTipText() {
+    return "Set the type of the search.";
+  }
+
+  /**
+   * Set the type
+   *
+   * @param t
+   *            the Linear Forward Selection type
+   */
+  public void setType(SelectedTag t) {
+    if (t.getTags() == TAGS_TYPE) {
+      m_linearSelectionType = t.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the type
+   *
+   * @return the Linear Forward Selection type
+   */
+  public SelectedTag getType() {
+    return new SelectedTag(m_linearSelectionType, TAGS_TYPE);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String subsetSizeEvaluatorTipText() {
+    return "Subset evaluator to use for subset size determination.";
+  }
+
+  /**
+   * Set the subset evaluator to use for subset size determination.
+   *
+   * @param eval
+   *            the subset evaluator to use for subset size determination.
+   */
+  public void setSubsetSizeEvaluator(ASEvaluation eval)
+    throws Exception {
+    if (!(eval instanceof SubsetEvaluator)) {
+      throw new Exception(eval.getClass().getName() +
+                          " is no subset evaluator.");
+    }
+
+    m_setSizeEval = eval;
+  }
+
+  /**
+   * Get the subset evaluator used for subset size determination.
+   *
+   * @return the evaluator used for subset size determination.
+   */
+  public ASEvaluation getSubsetSizeEvaluator() {
+    return m_setSizeEval;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String numSubsetSizeCVFoldsTipText() {
+    return "Number of cross validation folds for subset size determination";
+  }
+
+  /**
+   * Set the number of cross validation folds for subset size determination
+   * (default = 5).
+   *
+   * @param f
+   *            number of folds
+   */
+  public void setNumSubsetSizeCVFolds(int f) {
+    m_numFolds = f;
+  }
+
+  /**
+   * Get the number of cross validation folds for subset size determination
+   * (default = 5).
+   *
+   * @return number of folds
+   */
+  public int getNumSubsetSizeCVFolds() {
+    return m_numFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Seed for cross validation subset size determination. (default = 1)";
+  }
+
+  /**
+   * Seed for cross validation subset size determination. (default = 1)
+   *
+   * @param s
+   *            seed
+   */
+  public void setSeed(int s) {
+    m_seed = s;
+  }
+
+  /**
+   * Seed for cross validation subset size determination. (default = 1)
+   *
+   * @return seed
+   */
+  public int getSeed() {
+    return m_seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "Turn on verbose output for monitoring the search's progress.";
+  }
+
+  /**
+   * Set whether verbose output should be generated.
+   *
+   * @param b
+   *            true if output is to be verbose.
+   */
+  public void setVerbose(boolean b) {
+    m_verbose = b;
+  }
+
+  /**
+   * Get whether output is to be verbose
+   *
+   * @return true if output will be verbose
+   */
+  public boolean getVerbose() {
+    return m_verbose;
+  }
+
+  /**
+   * Gets the current settings of LinearForwardSelection.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    String[] evaluatorOptions = new String[0];
+
+    if ((m_setSizeEval != null) && (m_setSizeEval instanceof OptionHandler)) {
+      evaluatorOptions = ((OptionHandler) m_setSizeEval).getOptions();
+    }
+
+    String[] options = new String[15 + evaluatorOptions.length];
+    int current = 0;
+
+    if (m_performRanking) {
+      options[current++] = "-I";
+    }
+
+    options[current++] = "-K";
+    options[current++] = "" + m_numUsedAttributes;
+    options[current++] = "-T";
+    options[current++] = "" + m_linearSelectionType;
+
+    options[current++] = "-F";
+    options[current++] = "" + m_numFolds;
+    options[current++] = "-S";
+    options[current++] = "" + m_seed;
+
+    options[current++] = "-Z";
+    options[current++] = "" + m_verbose;
+
+    if (m_setSizeEval != null) {
+      options[current++] = "-E";
+      options[current++] = m_setSizeEval.getClass().getName();
+    }
+
+    options[current++] = "--";
+    System.arraycopy(evaluatorOptions, 0, options, current,
+                     evaluatorOptions.length);
+    current += evaluatorOptions.length;
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * returns a description of the search as a String
+   *
+   * @return a description of the search
+   */
+  public String toString() {
+    StringBuffer LFSString = new StringBuffer();
+
+    LFSString.append("\tSubset Size Forward Selection.\n");
+
+    LFSString.append("\tLinear Forward Selection Type: ");
+
+    if (m_linearSelectionType == TYPE_FIXED_SET) {
+      LFSString.append("fixed-set\n");
+    } else {
+      LFSString.append("fixed-width\n");
+    }
+
+    LFSString.append("\tNumber of top-ranked attributes that are used: " +
+                     m_numUsedAttributes + "\n");
+
+    LFSString.append(
+                     "\tNumber of cross validation folds for subset size determination: " +
+                     m_numFolds + "\n");
+    LFSString.append("\tSeed for cross validation subset size determination: " +
+                     m_seed + "\n");
+
+    LFSString.append("\tTotal number of subsets evaluated: " + m_totalEvals +
+                     "\n");
+    LFSString.append("\tMerit of best subset found: " +
+                     Utils.doubleToString(Math.abs(m_bestMerit), 8, 3) + "\n");
+
+    return LFSString.toString();
+  }
+
+  /**
+   * Searches the attribute subset space by subset size forward selection
+   *
+   * @param ASEval
+   *            the attribute evaluator to guide the search
+   * @param data
+   *            the training instances.
+   * @return an array (not necessarily ordered) of selected attribute indexes
+   * @exception Exception
+   *                if the search can't be completed
+   */
+  public int[] search(ASEvaluation ASEval, Instances data)
+    throws Exception {
+    m_totalEvals = 0;
+
+    if (!(ASEval instanceof SubsetEvaluator)) {
+      throw new Exception(ASEval.getClass().getName() + " is not a " +
+                          "Subset evaluator!");
+    }
+
+    if (m_setSizeEval == null) {
+      m_setSizeEval = ASEval;
+    }
+
+    m_numAttribs = data.numAttributes();
+
+    if (m_numUsedAttributes > m_numAttribs) {
+      System.out.println(
+                         "Decreasing number of top-ranked attributes to total number of attributes: " +
+                         data.numAttributes());
+      m_numUsedAttributes = m_numAttribs;
+    }
+
+    Instances[] trainData = new Instances[m_numFolds];
+    Instances[] testData = new Instances[m_numFolds];
+    LFSMethods[] searchResults = new LFSMethods[m_numFolds];
+
+    Random random = new Random(m_seed);
+    Instances dataCopy = new Instances(data);
+    dataCopy.randomize(random);
+
+    if (dataCopy.classAttribute().isNominal()) {
+      dataCopy.stratify(m_numFolds);
+    }
+
+    for (int f = 0; f < m_numFolds; f++) {
+      trainData[f] = dataCopy.trainCV(m_numFolds, f, random);
+      testData[f] = dataCopy.testCV(m_numFolds, f);
+    }
+
+    LFSMethods LSF = new LFSMethods();
+
+    int[] ranking;
+
+    if (m_performRanking) {
+      ASEval.buildEvaluator(data);
+      ranking = LSF.rankAttributes(data, (SubsetEvaluator) ASEval, m_verbose);
+    } else {
+      ranking = new int[m_numAttribs];
+
+      for (int i = 0; i < ranking.length; i++) {
+        ranking[i] = i;
+      }
+    }
+
+    int maxSubsetSize = 0;
+
+    for (int f = 0; f < m_numFolds; f++) {
+      if (m_verbose) {
+        System.out.println("perform search on internal fold: " + (f + 1) + "/" +
+                           m_numFolds);
+      }
+
+      m_setSizeEval.buildEvaluator(trainData[f]);
+      searchResults[f] = new LFSMethods();
+      searchResults[f].forwardSearch(m_cacheSize, new BitSet(m_numAttribs),
+                                     ranking, m_numUsedAttributes,
+                                     m_linearSelectionType == TYPE_FIXED_WIDTH, 1, -1, trainData[f],
+                                     (SubsetEvaluator)m_setSizeEval, m_verbose);
+
+      maxSubsetSize = Math.max(maxSubsetSize,
+                               searchResults[f].getBestGroup().cardinality());
+    }
+
+    if (m_verbose) {
+      System.out.println(
+                         "continue searches on internal folds to maxSubsetSize (" +
+                         maxSubsetSize + ")");
+    }
+
+    for (int f = 0; f < m_numFolds; f++) {
+      if (m_verbose) {
+        System.out.print("perform search on internal fold: " + (f + 1) + "/" +
+                         m_numFolds + " with starting set ");
+        LFSMethods.printGroup(searchResults[f].getBestGroup(),
+                              trainData[f].numAttributes());
+      }
+
+      if (searchResults[f].getBestGroup().cardinality() < maxSubsetSize) {
+        m_setSizeEval.buildEvaluator(trainData[f]);
+        searchResults[f].forwardSearch(m_cacheSize,
+                                       searchResults[f].getBestGroup(), ranking, m_numUsedAttributes,
+                                       m_linearSelectionType == TYPE_FIXED_WIDTH, 1, maxSubsetSize,
+                                       trainData[f], (SubsetEvaluator)m_setSizeEval, m_verbose);
+      }
+    }
+
+    double[][] testMerit = new double[m_numFolds][maxSubsetSize + 1];
+
+    for (int f = 0; f < m_numFolds; f++) {
+      for (int s = 1; s <= maxSubsetSize; s++) {
+        if (HoldOutSubsetEvaluator.class.isInstance(m_setSizeEval)) {
+          m_setSizeEval.buildEvaluator(trainData[f]);
+          testMerit[f][s] = ((HoldOutSubsetEvaluator) m_setSizeEval).evaluateSubset(searchResults[f].getBestGroupOfSize(
+                                                                                                                        s), testData[f]);
+        } else {
+          m_setSizeEval.buildEvaluator(testData[f]);
+          testMerit[f][s] = ((SubsetEvaluator)m_setSizeEval).evaluateSubset(searchResults[f].getBestGroupOfSize(
+                                                                                             s));
+        }
+      }
+    }
+
+    double[] avgTestMerit = new double[maxSubsetSize + 1];
+    int finalSubsetSize = -1;
+
+    for (int s = 1; s <= maxSubsetSize; s++) {
+      for (int f = 0; f < m_numFolds; f++) {
+        avgTestMerit[s] = ((avgTestMerit[s] * f) + testMerit[f][s]) / (double) (f +
+                                                                                1);
+      }
+
+      if ((finalSubsetSize == -1) ||
+          (avgTestMerit[s] > avgTestMerit[finalSubsetSize])) {
+        finalSubsetSize = s;
+      }
+
+      if (m_verbose) {
+        System.out.println("average merit for subset-size " + s + ": " +
+                           avgTestMerit[s]);
+      }
+    }
+
+    if (m_verbose) {
+      System.out.println("performing final forward selection to subset-size: " +
+                         finalSubsetSize);
+    }
+
+    ASEval.buildEvaluator(data);
+    LSF.forwardSearch(m_cacheSize, new BitSet(m_numAttribs), ranking,
+                      m_numUsedAttributes, m_linearSelectionType == TYPE_FIXED_WIDTH, 1,
+                      finalSubsetSize, data, (SubsetEvaluator) ASEval, m_verbose);
+
+    m_totalEvals = LSF.getNumEvalsTotal();
+    m_bestMerit = LSF.getBestMerit();
+
+    return attributeList(LSF.getBestGroup());
+  }
+
+  /**
+   * Reset options to default values
+   */
+  protected void resetOptions() {
+    m_performRanking = true;
+    m_numUsedAttributes = 50;
+    m_linearSelectionType = TYPE_FIXED_SET;
+    m_setSizeEval = new ClassifierSubsetEval();
+    m_numFolds = 5;
+    m_seed = 1;
+    m_totalEvals = 0;
+    m_cacheSize = 1;
+    m_verbose = false;
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes
+   *
+   * @param group
+   *            the BitSet to convert
+   * @return an array of attribute indexes
+   */
+  protected int[] attributeList(BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        list[count++] = i;
+      }
+    }
+
+    return list;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5604 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeEval.java	(revision 29)
@@ -0,0 +1,434 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SymmetricalUncertAttributeEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * SymmetricalUncertAttributeEval :<br/>
+ * <br/>
+ * Evaluates the worth of an attribute by measuring the symmetrical uncertainty with respect to the class. <br/>
+ * <br/>
+ *  SymmU(Class, Attribute) = 2 * (H(Class) - H(Class | Attribute)) / H(Class) + H(Attribute).<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5447 $
+ * @see Discretize
+ */
+public class SymmetricalUncertAttributeEval
+  extends ASEvaluation
+  implements AttributeEvaluator, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8096505776132296416L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** The number of classes */
+  private int m_numClasses;
+
+  /** Treat missing values as a seperate value */
+  private boolean m_missing_merge;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "SymmetricalUncertAttributeEval :\n\nEvaluates the worth of an attribute "
+      +"by measuring the symmetrical uncertainty with respect to the class. "
+      +"\n\n SymmU(Class, Attribute) = 2 * (H(Class) - H(Class | Attribute)) "
+      +"/ H(Class) + H(Attribute).\n";
+  }
+  
+  /**
+   * Constructor
+   */
+  public SymmetricalUncertAttributeEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option("\ttreat missing values as a seperate " 
+				    + "value.", "M", 0, "-M"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   **/
+  public void setOptions (String[] options)
+    throws Exception {
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+      +"across other values in proportion to their frequency. Otherwise, "
+      +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[1];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a symmetrical uncertainty attribute evaluator. 
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+    Discretize disTransform = new Discretize();
+    disTransform.setUseBetterEncoding(true);
+    disTransform.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, disTransform);
+    m_numClasses = m_trainInstances.attribute(m_classIndex).numValues();
+  }
+
+
+  /**
+   * set options to default values
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_missing_merge = true;
+  }
+
+
+  /**
+   * evaluates an individual attribute by measuring the symmetrical
+   * uncertainty between it and the class.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the uncertainty
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+    int i, j, ii, jj;
+    int nnj, nni, ni, nj;
+    double sum = 0.0;
+    ni = m_trainInstances.attribute(attribute).numValues() + 1;
+    nj = m_numClasses + 1;
+    double[] sumi, sumj;
+    Instance inst;
+    double temp = 0.0;
+    sumi = new double[ni];
+    sumj = new double[nj];
+    double[][] counts = new double[ni][nj];
+    sumi = new double[ni];
+    sumj = new double[nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+	sumj[j] = 0.0;
+	counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(attribute)) {
+	ii = ni - 1;
+      }
+      else {
+	ii = (int)inst.value(attribute);
+      }
+
+      if (inst.isMissing(m_classIndex)) {
+	jj = nj - 1;
+      }
+      else {
+	jj = (int)inst.value(m_classIndex);
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+	sumi[i] += counts[i][j];
+	sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+	sumj[j] += counts[i][j];
+      }
+    }
+
+    // distribute missing counts
+    if (m_missing_merge && 
+	(sumi[ni-1] < m_numInstances) && 
+	(sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+	System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = (sumi[ni - 1] + sumj[nj - 1] 
+			      - counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) {
+	for (j = 0; j < nj - 1; j++) {
+	  if (counts[ni - 1][j] > 0.0) {
+	    for (i = 0; i < ni - 1; i++) {
+	      temp = ((i_copy[i]/(sum - i_copy[ni - 1])) * 
+		      counts[ni - 1][j]);
+	      counts[i][j] += temp;
+	      sumi[i] += temp;
+	    }
+
+	    counts[ni - 1][j] = 0.0;
+	  }
+	}
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+	for (i = 0; i < ni - 1; i++) {
+	  if (counts[i][nj - 1] > 0.0) {
+	    for (j = 0; j < nj - 1; j++) {
+	      temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+	      counts[i][j] += temp;
+	      sumj[j] += temp;
+	    }
+
+	    counts[i][nj - 1] = 0.0;
+	  }
+	}
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0 && total_missing != sum) {
+	for (i = 0; i < ni - 1; i++) {
+	  for (j = 0; j < nj - 1; j++) {
+	    temp = (counts_copy[i][j]/(sum - total_missing)) * 
+	      counts_copy[ni - 1][nj - 1];
+	    counts[i][j] += temp;
+	    sumi[i] += temp;
+	    sumj[j] += temp;
+	  }
+	}
+
+	counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    return  ContingencyTables.symmetricalUncertainty(counts);
+  }
+
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tSymmetrical Uncertainty evaluator has not been built");
+    }
+    else {
+      text.append("\tSymmetrical Uncertainty Ranking Filter");
+      if (!m_missing_merge) {
+	text.append("\n\tMissing values treated as seperate");
+      }
+    }
+
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file
+   */
+  public static void main (String[] argv) {
+    runEvaluator(new SymmetricalUncertAttributeEval(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeSetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeSetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/SymmetricalUncertAttributeSetEval.java	(revision 29)
@@ -0,0 +1,743 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+ /*
+ *    RELEASE INFORMATION (December 27, 2004)
+ *    
+ *    FCBF algorithm:
+ *      Template obtained from Weka
+ *      Developed for Weka by Zheng Alan Zhao   
+ *      December 27, 2004
+ *
+ *    FCBF algorithm is a feature selection method based on Symmetrical Uncertainty Measurement for 
+ *    relevance redundancy analysis. The details of FCBF algorithm are in:
+ *
+ <!-- technical-plaintext-start -->
+ * Lei Yu, Huan Liu: Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution. In: Proceedings of the Twentieth International Conference on Machine Learning, 856-863, 2003.
+ <!-- technical-plaintext-end -->
+ *    
+ *    
+ *    
+ *    CONTACT INFORMATION
+ *    
+ *    For algorithm implementation:
+ *    Zheng Zhao: zhaozheng at asu.edu
+ *      
+ *    For the algorithm:
+ *    Lei Yu: leiyu at asu.edu
+ *    Huan Liu: hliu at asu.edu
+ *     
+ *    Data Mining and Machine Learning Lab
+ *    Computer Science and Engineering Department
+ *    Fulton School of Engineering
+ *    Arizona State University
+ *    Tempe, AZ 85287
+ *
+ *    SymmetricalUncertAttributeSetEval.java
+ *
+ *    Copyright (C) 2004 Data Mining and Machine Learning Lab, 
+ *                       Computer Science and Engineering Department, 
+ *		    	 Fulton School of Engineering, 
+ *                       Arizona State University
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * SymmetricalUncertAttributeSetEval :<br/>
+ * <br/>
+ * Evaluates the worth of a set attributes by measuring the symmetrical uncertainty with respect to another set of attributes. <br/>
+ * <br/>
+ *  SymmU(AttributeSet2, AttributeSet1) = 2 * (H(AttributeSet2) - H(AttributeSet1 | AttributeSet2)) / H(AttributeSet2) + H(AttributeSet1).<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Lei Yu, Huan Liu: Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution. In: Proceedings of the Twentieth International Conference on Machine Learning, 856-863, 2003.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Yu2003,
+ *    author = {Lei Yu and Huan Liu},
+ *    booktitle = {Proceedings of the Twentieth International Conference on Machine Learning},
+ *    pages = {856-863},
+ *    publisher = {AAAI Press},
+ *    title = {Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  treat missing values as a seperate value.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Zheng Zhao: zhaozheng at asu.edu
+ * @version $Revision: 5447 $
+ * @see Discretize
+ */
+public class SymmetricalUncertAttributeSetEval
+  extends AttributeSetEvaluator
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 8351377335495873202L;
+
+  /** The training instances */
+  private Instances m_trainInstances;
+
+  /** The class index */
+  private int m_classIndex;
+
+  /** The number of attributes */
+  private int m_numAttribs;
+
+  /** The number of instances */
+  private int m_numInstances;
+
+  /** The number of classes */
+  private int m_numClasses;
+
+  /** Treat missing values as a seperate value */
+  private boolean m_missing_merge;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "SymmetricalUncertAttributeSetEval :\n\nEvaluates the worth of a set attributes "
+      +"by measuring the symmetrical uncertainty with respect to another set of attributes. "
+      +"\n\n SymmU(AttributeSet2, AttributeSet1) = 2 * (H(AttributeSet2) - H(AttributeSet1 | AttributeSet2)) "
+      +"/ H(AttributeSet2) + H(AttributeSet1).\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lei Yu and Huan Liu");
+    result.setValue(Field.TITLE, "Feature Selection for High-Dimensional Data: A Fast Correlation-Based Filter Solution");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the Twentieth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.PAGES, "856-863");
+    result.setValue(Field.PUBLISHER, "AAAI Press");
+    
+    return result;
+  }
+
+  /**
+   * Constructor
+   */
+  public SymmetricalUncertAttributeSetEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option("\ttreat missing values as a seperate "
+                                    + "value.", "M", 0, "-M"));
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  treat missing values as a seperate value.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    resetOptions();
+    setMissingMerge(!(Utils.getFlag('M', options)));
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingMergeTipText() {
+    return "Distribute counts for missing values. Counts are distributed "
+      +"across other values in proportion to their frequency. Otherwise, "
+      +"missing is treated as a separate value.";
+  }
+
+  /**
+   * distribute the counts for missing values across observed values
+   *
+   * @param b true=distribute missing values.
+   */
+  public void setMissingMerge (boolean b) {
+    m_missing_merge = b;
+  }
+
+
+  /**
+   * get whether missing values are being distributed or not
+   *
+   * @return true if missing values are being distributed.
+   */
+  public boolean getMissingMerge () {
+    return  m_missing_merge;
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[1];
+    int current = 0;
+
+    if (!getMissingMerge()) {
+      options[current++] = "-M";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Initializes a symmetrical uncertainty attribute evaluator.
+   * Discretizes all attributes that are numeric.
+   *
+   * @param data set of instances serving as training data
+   * @throws Exception if the evaluator has not been
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+    Discretize disTransform = new Discretize();
+    disTransform.setUseBetterEncoding(true);
+    disTransform.setInputFormat(m_trainInstances);
+    m_trainInstances = Filter.useFilter(m_trainInstances, disTransform);
+    m_numClasses = m_trainInstances.attribute(m_classIndex).numValues();
+  }
+
+
+  /**
+   * set options to default values
+   */
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_missing_merge = true;
+  }
+
+  /**
+   * evaluates an individual attribute by measuring the symmetrical
+   * uncertainty between it and the class.
+   *
+   * @param attribute the index of the attribute to be evaluated
+   * @return the uncertainty
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int attribute)
+    throws Exception {
+    int i, j, ii, jj;
+    int ni, nj;
+    double sum = 0.0;
+    ni = m_trainInstances.attribute(attribute).numValues() + 1;
+    nj = m_numClasses + 1;
+    double[] sumi, sumj;
+    Instance inst;
+    double temp = 0.0;
+    sumi = new double[ni];
+    sumj = new double[nj];
+    double[][] counts = new double[ni][nj];
+    sumi = new double[ni];
+    sumj = new double[nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumj[j] = 0.0;
+        counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      if (inst.isMissing(attribute)) {
+        ii = ni - 1;
+      }
+      else {
+        ii = (int)inst.value(attribute);
+      }
+
+      if (inst.isMissing(m_classIndex)) {
+        jj = nj - 1;
+      }
+      else {
+        jj = (int)inst.value(m_classIndex);
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        //there are how many happen of a special feature value
+        sumi[i] += counts[i][j];
+        sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+        //a class value include how many instance.
+        sumj[j] += counts[i][j];
+      }
+    }
+
+    // distribute missing counts
+    if (m_missing_merge &&
+        (sumi[ni-1] < m_numInstances) &&
+        (sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+        System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = (sumi[ni - 1] + sumj[nj - 1]
+                              - counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) { //sumi[ni - 1]: missing value contains how many values.
+        for (j = 0; j < nj - 1; j++) {
+          if (counts[ni - 1][j] > 0.0) {
+            for (i = 0; i < ni - 1; i++) {
+              temp = ((i_copy[i]/(sum - i_copy[ni - 1])) *
+                      counts[ni - 1][j]);
+              counts[i][j] += temp; //according to the probability of value i we distribute account of the missing degree of a class lable to it
+              sumi[i] += temp;
+            }
+
+            counts[ni - 1][j] = 0.0;
+          }
+        }
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+        for (i = 0; i < ni - 1; i++) {
+          if (counts[i][nj - 1] > 0.0) {
+            for (j = 0; j < nj - 1; j++) {
+              temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+              counts[i][j] += temp;
+              sumj[j] += temp;
+            }
+
+            counts[i][nj - 1] = 0.0;
+          }
+        }
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0 && total_missing != sum) {
+        for (i = 0; i < ni - 1; i++) {
+          for (j = 0; j < nj - 1; j++) {
+            temp = (counts_copy[i][j]/(sum - total_missing)) *
+              counts_copy[ni - 1][nj - 1];
+            counts[i][j] += temp;
+            sumi[i] += temp;
+            sumj[j] += temp;
+          }
+        }
+
+        counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    return  ContingencyTables.symmetricalUncertainty(counts);
+  }
+
+  /**
+   * calculate symmetrical uncertainty between sets of attributes
+   *
+   * @param attributes the indexes of the attributes
+   * @param classAttributes the indexes of the attributes whose combination will
+   *                         be used as class label
+   * @return the uncertainty
+   * @throws Exception if the attribute could not be evaluated
+   */
+  public double evaluateAttribute (int[] attributes, int[] classAttributes)
+    throws Exception {
+
+    int i, j;     //variable for looping.
+    int p;     //variable for looping.
+    int ii, jj;   //specifying the position in the contingency table.
+    int nnj, nni; //counting base for attributes[].
+    int ni, nj;   //the nubmer of rows and columns in the ContingencyTables.
+
+    double sum = 0.0;
+    boolean b_missing_attribute = false;
+    boolean b_missing_classAtrribute = false;
+
+    if(attributes.length==0)
+    {
+      throw new Exception("the parameter attributes[] is empty;SEQ:W-FS-Eval-SUAS-001");
+    }
+    if(classAttributes.length==0)
+    {
+      throw new Exception("the parameter classAttributes[] is empty;SEQ:W-FS-Eval-SUAS-002");
+    }
+
+    /*calculate the number of the rows in ContingencyTable*/
+    ni = m_trainInstances.attribute(attributes[0]).numValues();
+    if (ni == 0)
+    {
+      throw new Exception("an attribute is empty;SEQ:W-FS-Eval-SUAS-003;"+1);
+    }
+
+    for (i = 1;i<attributes.length;i++)
+    {
+      if (m_trainInstances.attribute(attributes[i]).numValues() == 0)
+      {
+        throw new Exception("an attribute is empty;SEQ:W-FS-Eval-SUAS-003;" +
+                            (i+1));
+      }
+      ni = ni*m_trainInstances.attribute(attributes[i]).numValues();
+    }
+    ni = ni+1;
+
+    /*calculate the number of the colums in the ContingencyTable*/
+    nj = m_trainInstances.attribute(classAttributes[0]).numValues();
+    if (nj == 0)
+    {
+      throw new Exception("the a classAttribute is empty;SEQ:W-FS-Eval-SUAS-004;"+1);
+    }
+
+    for (i = 1;i<classAttributes.length;i++)
+    {
+      if (m_trainInstances.attribute(classAttributes[i]).numValues() == 0)
+      {
+        throw new Exception("the a classAttribute is empty;SEQ:W-FS-Eval-SUAS-004;" +
+                            (i+1));
+      }
+      nj = nj*m_trainInstances.attribute(classAttributes[i]).numValues();
+    }
+    nj = nj+1;
+
+    double[] sumi, sumj;
+    Instance inst;
+    double temp = 0.0;
+    sumi = new double[ni];
+    sumj = new double[nj];
+    double[][] counts = new double[ni][nj];
+    sumi = new double[ni];
+    sumj = new double[nj];
+
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        sumj[j] = 0.0;
+        counts[i][j] = 0.0;
+      }
+    }
+
+    // Fill the contingency table
+    for (i = 0; i < m_numInstances; i++) {
+      inst = m_trainInstances.instance(i);
+
+      b_missing_attribute = false;
+      b_missing_classAtrribute = false;
+
+      /*get row position in contingency table*/
+      nni = 1;
+      ii = 0;
+      for (p=attributes.length-1; p>=0; p--)
+      {
+        if (inst.isMissing(attributes[p])) {
+           b_missing_attribute = true;
+        }
+        ii = ((int)inst.value(attributes[p])*nni)+ii;
+        if (p<attributes.length-1){
+          nni = nni * (m_trainInstances.attribute(attributes[p]).numValues());
+        }
+        else {
+          nni = m_trainInstances.attribute(attributes[p]).numValues();
+        }
+      }
+      if (b_missing_attribute) {
+        ii = ni-1;
+      }
+
+      /*get colum position in contingency table*/
+      nnj = 1;
+      jj = 0;
+      for (p=classAttributes.length-1; p>=0; p--)
+      {
+        if (inst.isMissing(classAttributes[p])) {
+           b_missing_classAtrribute = true;
+        }
+        jj = ((int)inst.value(classAttributes[p])*nnj)+jj;
+        if (p<attributes.length-1){
+          nnj = nnj * (m_trainInstances.attribute(classAttributes[p]).numValues());
+        }
+        else {
+          nnj = m_trainInstances.attribute(classAttributes[p]).numValues();
+        }
+      }
+      if (b_missing_classAtrribute) {
+        jj = nj-1;
+      }
+
+      counts[ii][jj]++;
+    }
+
+    // get the row totals
+    for (i = 0; i < ni; i++) {
+      sumi[i] = 0.0;
+
+      for (j = 0; j < nj; j++) {
+        //there are how many happen of a special feature value
+        sumi[i] += counts[i][j];
+        sum += counts[i][j];
+      }
+    }
+
+    // get the column totals
+    for (j = 0; j < nj; j++) {
+      sumj[j] = 0.0;
+
+      for (i = 0; i < ni; i++) {
+        //a class value include how many instance.
+        sumj[j] += counts[i][j];
+      }
+    }
+
+    // distribute missing counts
+    if (m_missing_merge &&
+        (sumi[ni-1] < m_numInstances) &&
+        (sumj[nj-1] < m_numInstances)) {
+      double[] i_copy = new double[sumi.length];
+      double[] j_copy = new double[sumj.length];
+      double[][] counts_copy = new double[sumi.length][sumj.length];
+
+      for (i = 0; i < ni; i++) {
+        System.arraycopy(counts[i], 0, counts_copy[i], 0, sumj.length);
+      }
+
+      System.arraycopy(sumi, 0, i_copy, 0, sumi.length);
+      System.arraycopy(sumj, 0, j_copy, 0, sumj.length);
+      double total_missing = (sumi[ni - 1] + sumj[nj - 1]
+                              - counts[ni - 1][nj - 1]);
+
+      // do the missing i's
+      if (sumi[ni - 1] > 0.0) { //sumi[ni - 1]: missing value contains how many values.
+        for (j = 0; j < nj - 1; j++) {
+          if (counts[ni - 1][j] > 0.0) {
+            for (i = 0; i < ni - 1; i++) {
+              temp = ((i_copy[i]/(sum - i_copy[ni - 1])) *
+                      counts[ni - 1][j]);
+              counts[i][j] += temp; //according to the probability of value i we distribute account of the missing degree of a class lable to it
+              sumi[i] += temp;
+            }
+
+            counts[ni - 1][j] = 0.0;
+          }
+        }
+      }
+
+      sumi[ni - 1] = 0.0;
+
+      // do the missing j's
+      if (sumj[nj - 1] > 0.0) {
+        for (i = 0; i < ni - 1; i++) {
+          if (counts[i][nj - 1] > 0.0) {
+            for (j = 0; j < nj - 1; j++) {
+              temp = ((j_copy[j]/(sum - j_copy[nj - 1]))*counts[i][nj - 1]);
+              counts[i][j] += temp;
+              sumj[j] += temp;
+            }
+
+            counts[i][nj - 1] = 0.0;
+          }
+        }
+      }
+
+      sumj[nj - 1] = 0.0;
+
+      // do the both missing
+      if (counts[ni - 1][nj - 1] > 0.0 && total_missing != sum) {
+        for (i = 0; i < ni - 1; i++) {
+          for (j = 0; j < nj - 1; j++) {
+            temp = (counts_copy[i][j]/(sum - total_missing)) *
+              counts_copy[ni - 1][nj - 1];
+            counts[i][j] += temp;
+            sumi[i] += temp;
+            sumj[j] += temp;
+          }
+        }
+
+        counts[ni - 1][nj - 1] = 0.0;
+      }
+    }
+
+    return  ContingencyTables.symmetricalUncertainty(counts);
+  }
+
+
+  /**
+   * Return a description of the evaluator
+   * @return description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tSymmetrical Uncertainty evaluator has not been built");
+    }
+    else {
+      text.append("\tSymmetrical Uncertainty Ranking Filter");
+      if (!m_missing_merge) {
+        text.append("\n\tMissing values treated as seperate");
+      }
+    }
+
+    text.append("\n");
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5447 $");
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file
+   */
+  public static void main (String[] argv) {
+    runEvaluator(new SymmetricalUncertAttributeSetEval(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/TabuSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/TabuSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/TabuSearch.java	(revision 29)
@@ -0,0 +1,980 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TabuSearch.java
+ *    Copyright (C) 2009 Adrian Pino
+ *    Copyright (C) 2009 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.attributeSelection;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+
+/**
+ * Class for performing the TabuSearch method. <p>
+ * 
+ <!-- globalinfo-start -->
+ * Tabu Search :<br/>
+ * <br/>
+ * Performs a search  through the space of attribute subsets. Evading local maximums by accepting bad and diverse solutions and make further search in the best soluions. Stops when there's not more improvement in n iterations<br/>
+ * ;For more information see:<br/>
+ * <br/>
+ * Abdel-Rahman Hedar, Jue Wangy,, Masao Fukushima (2006). Tabu Search for Attribute Reduction in Rough Set Theory. .
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -Z &lt;numInitialSolution&gt;
+ *  Specify the number of attributes 
+ *  in the initial Solution..</pre>
+ * 
+ * <pre> -P &lt;diversificationProb&gt;
+ *  Specify the diversification probabilities,
+ *  if this value is near to 0 then the best attributes
+ *   will have more probabilities of keeping in the new diverse solution</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Set the random number seed.
+ *  (default = 1)</pre>
+ * 
+ * <pre> -N &lt;number of neighbors&gt;
+ *  Set the number of neighbors to generate.</pre>
+ * 
+ <!-- options-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Abdel-RahmanHedar2006,
+ *    author = {Abdel-Rahman Hedar, Jue Wangy, and Masao Fukushima},
+ *    month = {July},
+ *    title = {Tabu Search for Attribute Reduction in Rough Set Theory},
+ *    year = {2006},
+ *    language = {English}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * from the Book: Tabu Search for Attribute Reduction in Rough Set Theory, Abdel-Rahman Hedar, Jue Wangy, and Masao Fukushima.
+ * 
+ * @author Adrian Pino (apinoa@facinf.uho.edu.cu)
+ * @version $Revision: 4976 $
+ *
+ */
+
+public class TabuSearch extends ASSearch
+  implements OptionHandler, TechnicalInformationHandler{
+
+  /** for serialization */
+  static final long serialVersionUID = -8812132617585120414L;
+
+  /** number of attributes in the data */
+  private int m_numAttribs;
+
+  /** holds the class index */
+  private int m_classIndex;
+
+  /** random number generation */
+  private Random m_random;
+
+  /** seed for random number generation */
+  private int m_seed;
+
+  /** probabilities of diversification */
+  private double m_diversificationProb;
+
+  /** number of iterations for getting the best subset*/
+  private int m_numIterations;
+
+  /** total number of subsets evaluated during a search */
+  private int m_totalEvals;
+
+  /** holds the best Subset found */
+  protected Subset m_Sbest;
+
+  /** holds the number of neighborhood to generate from a Subset*/
+  private int m_numNeighborhood;
+
+  /** time for procesing the search method */
+  private long m_processinTime;
+
+  /** holds the solution size*/
+  private int m_initialSize;
+
+  /**Subset holding the initial solution*/
+  private Subset m_initialSolution;
+
+  /**attribute ranking*/
+  private List<Subset> m_rankedAttribs;
+
+  /**tabu List*/
+  private List<BitSet> m_vectorTabu;
+
+  /**Evaluator used to know the significance of a subset (for guiding the search)*/
+  private SubsetEvaluator ASEvaluator =null;
+
+
+  /**
+   * Searches the attribute subset space using Tabu Search.
+   *
+   * @param ASEvaluator the attribute evaluator to guide the search
+   * @param data the training instances.
+   * @return an array of selected attribute indexes
+   * @exception Exception if the search can't be completed
+   */
+  public int[] search(ASEvaluation ASEval, Instances data)
+  throws Exception{
+    m_totalEvals = 0;
+    m_processinTime =System.currentTimeMillis ();
+
+    m_numAttribs =data.numAttributes ();
+    m_classIndex =data.classIndex ();
+
+    ASEvaluator =(SubsetEvaluator)ASEval;
+
+    m_random = new Random(m_seed);
+
+    int numN = m_numNeighborhood;
+    numN = (m_numNeighborhood <= 0) ? 3*m_numAttribs/4 : m_numNeighborhood;
+
+
+    if(m_numAttribs <= 14) m_numIterations = m_numAttribs;
+    else if(m_numAttribs > 14) m_numIterations = m_numAttribs/3;
+
+    m_rankedAttribs = RankEachAttribute ();
+
+    /** generating an initial Solution based in SFS*/
+    if(m_initialSize < 0)m_initialSize = m_numAttribs;
+    m_initialSolution = GenerateInitialSolution(new Subset(new BitSet(m_numAttribs), 0), m_initialSize);
+
+
+    /**Initializing Vector Tabu*/
+    m_vectorTabu =new ArrayList<BitSet>();
+    BitSet tabu1 = new BitSet(m_numAttribs);
+    BitSet tabu2 = new BitSet(m_numAttribs);
+
+    tabu2.set (0,m_numAttribs-1);
+    if (m_classIndex >= 0) tabu2.set (m_classIndex, false);
+
+    m_vectorTabu.add (tabu1);
+    m_vectorTabu.add (tabu2);
+
+
+    /**For Controlling the Search*/
+    int iterationCounter = 0;
+    int numGenerationNeighborForDiv = (m_numAttribs/m_numIterations >= 2) ? m_numAttribs/m_numIterations : 3;
+    int numTotalWImp = 0;
+    int numTotalWImpForFinishing = m_numIterations/2;
+
+    BitSet S = m_initialSolution.subset;
+    m_Sbest = m_initialSolution;
+
+    List<Subset> RedSet = new ArrayList<Subset>();
+    RedSet.add (m_Sbest.clone ());
+
+    while(iterationCounter < m_numIterations && numTotalWImp < numTotalWImpForFinishing){
+      iterationCounter ++;
+
+      List<Subset> neighborhood = null;
+      int counterGenerationNeighborWImp = 0;
+      while (counterGenerationNeighborWImp < numGenerationNeighborForDiv) {
+
+        neighborhood = generateNeighborhood(S, numN);
+        if(neighborhood != null){
+          S =((Subset)neighborhood.get (0)).subset;
+          double Smerit = ASEvaluator.evaluateSubset (S);
+          m_totalEvals ++;
+
+          RedSet.add (new Subset((BitSet)S.clone (), Smerit));
+
+          m_vectorTabu.add ((BitSet)S.clone ());
+
+
+          if(Smerit > m_Sbest.merit){
+            m_Sbest = new Subset((BitSet)S.clone (), Smerit);
+            Subset aux = shake ();
+            if(aux != null)
+              m_Sbest = aux.clone ();
+          }
+          else{
+            counterGenerationNeighborWImp ++;
+            numTotalWImp ++;
+          }
+        }
+        else break;
+      }
+      S = diversify (neighborhood);
+    }
+
+    Subset elite = eliteReducts(RedSet, RedSet.size ());
+
+    if(elite.merit >= m_Sbest.merit)
+      return attributeList (elite.subset);
+
+    m_processinTime = System.currentTimeMillis () - m_processinTime;
+
+    return attributeList (m_Sbest.subset);
+  }
+
+  /**
+   * Generates a list of neighborhood given a Solution, flipping randomly the state of many attributes.
+   *
+   * @param S the Solution from which we are going to find the neighborhood
+   * @param numNeighborhood number of attributes for flipping
+   * @return a Subset list containing the neighborhood
+   * @exception Exception if the evaluation can not be complete
+   */
+  private List<Subset> generateNeighborhood(BitSet S, int numNeighborhood)
+  throws Exception{
+    int counter = 0;
+    List<Subset> neighborhood = new ArrayList<Subset>();
+
+    int numAttribs = (m_classIndex == -1) ? m_numAttribs  : m_numAttribs - 1;
+
+    if(numNeighborhood >= numAttribs){
+      for (int i = 0; i < m_numAttribs; i++) {
+        if(i == m_classIndex)continue;
+
+        BitSet aux = (BitSet)S.clone ();
+        aux.flip (i);
+
+        if(!m_vectorTabu.contains (aux)){
+          neighborhood.add (new Subset((BitSet)aux.clone (), ASEvaluator.evaluateSubset (aux)));
+          m_totalEvals ++;
+        }
+      }
+    }
+    else{
+      while (counter < numNeighborhood) {
+        BitSet aux = (BitSet)S.clone ();
+
+        int randomNumber = m_random.nextInt (m_numAttribs);
+        if(randomNumber == m_classIndex)
+          continue;
+
+        aux.flip (randomNumber);
+        if(!m_vectorTabu.contains (aux)){
+          neighborhood.add (new Subset((BitSet)aux.clone (), ASEvaluator.evaluateSubset (aux)));
+          m_totalEvals ++;
+          counter ++;
+        }
+      }
+    }
+
+    if(neighborhood.isEmpty ())
+      return null;
+
+    return bubbleSubsetSort (neighborhood);
+  }
+
+  /**
+   * Generate a diverse Bitset given a List of Subset
+   *
+   * @param neighborhood the list from which are going to be generate the diverse solution
+   * @return a diverse Bitset
+   */
+  public BitSet diversify(List<Subset> neighborhood){
+
+    if(neighborhood == null)
+      return m_Sbest.subset;
+
+    BitSet result = new BitSet(m_numAttribs);
+
+    double [] counts = new double[m_numAttribs];
+    int numNeighborhood = neighborhood.size ();
+
+
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if(i == m_classIndex)
+        continue;
+
+      int counter = 0;
+
+      for (int j = 0; j < numNeighborhood; j++) {
+        if(((Subset)neighborhood.get (j)).subset.get (i))
+          counter ++;
+      }
+      counts [i] = counter/(double)numNeighborhood;
+    }
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      double randomNumber = m_random.nextDouble ();
+      double ocurrenceAndRank = counts [i] * (m_diversificationProb) + doubleRank (i) * (1 - m_diversificationProb);
+
+      if(randomNumber > ocurrenceAndRank)
+        result.set (i);
+    }
+
+    return result;
+  }
+
+
+  /**
+   * Try to improve the best Solution obtained till now in the way of Sequential Backward Selection(SBS)
+   *
+   * @param ASEvaluator the attribute evaluator to guide the search
+   * @return null if there is not improvement otherwise a Subset better than the obtained till now.
+   */
+  private Subset shake()
+  throws Exception{
+    Subset bestCopy = m_Sbest.clone ();
+    boolean anyImprovement = false;
+
+    for (int i = m_rankedAttribs.size ()-1; i >= 0; i --) {
+      Subset ranking = m_rankedAttribs.get (i);
+      int attributeIndex = ranking.subset.nextSetBit (0);
+
+      BitSet aux = (BitSet)bestCopy.subset.clone ();
+      if(aux.get (attributeIndex)){
+        aux.set (attributeIndex, false);
+
+        if(m_vectorTabu.contains (aux))
+          continue;
+
+        double tempMerit = ASEvaluator.evaluateSubset (aux);
+        m_totalEvals ++;
+
+        if(tempMerit >= bestCopy.merit){
+          bestCopy = new Subset((BitSet)aux.clone (), tempMerit);
+          anyImprovement = true;
+        }
+      }
+    }
+    if(anyImprovement)
+      return bestCopy;
+    return null;
+  }
+
+  /**
+   * Find a better solution by intersecting and SFS of the best elite Reducts found till that moment
+   *
+   * @param RedSet the Subset List of the Elite Reducted found till that moment
+   * @param numSubset number of elites to intersect
+   * @return a the resulting Subset
+   * @exception Exception if any evaluation can't be completed
+   */
+  public Subset eliteReducts(List<Subset>RedSet, int numSubset)
+  throws Exception{
+
+    if(numSubset <= 0)
+      return null;
+
+    List<Subset>orderedRedSet = bubbleSubsetSort (RedSet);
+    int numAttribsOfBest = m_Sbest.cardinality ();
+
+    BitSet result =new BitSet(m_numAttribs);
+    result.set (0,m_numAttribs-1,true);
+
+    BitSet aux;
+
+    for (int i = 0; i < numSubset; i++) {
+      aux = ((Subset)orderedRedSet.get (i)).subset;
+      result.and (aux);
+    }
+
+    int diff = numAttribsOfBest - result.cardinality ();
+
+    Subset resultSet = GenerateInitialSolution (new Subset(result,ASEvaluator.evaluateSubset (result)),result.cardinality () + diff-1);
+    m_totalEvals ++;
+
+    if(resultSet == null)
+      return null;
+
+    return resultSet;
+  }
+
+
+  /**
+   * Generate an initial solution based in a Sequential Forward Selection (SFS)
+   *
+   * @param size number of posible attributes in the initial Solution
+   * @return an initial Subset containing the initial Solution
+   * @exception Exception if the evaluation can not be completed
+   */
+  public Subset GenerateInitialSolution(Subset initial, int size)
+  throws Exception{
+
+    List<Subset> rankedAttribsCopy = new ArrayList<Subset>();
+
+    for (int i = 0; i<m_rankedAttribs.size (); i++)
+      rankedAttribsCopy.add (m_rankedAttribs.get (i));
+
+    Subset solution = initial.clone ();
+
+    while(solution.cardinality () < size) {
+
+      Subset tempSubset =new Subset();
+      double bestMerit =solution.merit;
+      int bestIndex =-1;
+
+      for (int i = 0; i<rankedAttribsCopy.size (); i++) {
+        Subset candidate = ((Subset)rankedAttribsCopy.get (i)).clone ();
+        if(solution.subset.get (candidate.subset.nextSetBit (0)))
+          continue;
+
+        tempSubset =joinSubsets(solution, rankedAttribsCopy.get (i));
+
+        if(tempSubset.merit > bestMerit){
+          bestMerit = tempSubset.merit;
+          bestIndex = i;
+        }
+      }
+
+      if(bestIndex == -1) break;
+
+      solution =joinSubsets (solution, rankedAttribsCopy.get (bestIndex));
+      rankedAttribsCopy.remove (bestIndex);
+    }
+
+    return solution;
+  }
+
+
+
+  //--------Auxiliar methods****
+
+  /**
+   * get the importance of an individual attribute according to his position in m_rankedAttribs List.
+   *
+   * @param idAttrib the index attribute of the attribute that we want to get the doubleRank
+   * @return a double giving the result. If is a good attribute the result will be a number near of 0(inclusive),
+   *                                     If is a bad one the result will be near of 1(exclusive).
+   */
+  private double doubleRank(int idAttrib){
+    int rankSize = m_rankedAttribs.size ();
+    int rankAttribute = -1;
+
+    if(idAttrib == m_classIndex) return -1;
+
+    for (int i = 0; i < rankSize; i++) {
+      if(((Subset)m_rankedAttribs.get (i)).subset.nextSetBit (0) == idAttrib){
+        rankAttribute = i;
+        break;
+      }
+    }
+
+    return rankAttribute/(double)rankSize;
+  }
+
+  /**
+   * Rank all the attributes individually acording to their merits
+   *
+   * @return an ordered List  of Subsets with just one attribute
+   * @exception Exception if the evaluation can not be completed
+   */
+  private List<Subset> RankEachAttribute()
+  throws Exception{
+
+    List<Subset> result =new ArrayList<Subset>();
+
+    for (int i = 0; i<m_numAttribs; i++) {
+      if(i==m_classIndex)continue;
+
+      BitSet an_Attribute =new BitSet(m_numAttribs);
+      an_Attribute.set (i);
+
+      double merit =ASEvaluator.evaluateSubset (an_Attribute);
+      m_totalEvals++;
+
+      result.add (new Subset(an_Attribute, merit));
+    }
+
+    return bubbleSubsetSort(result);
+  }
+
+  /**
+   * Sort a List of subsets according to their merits
+   *
+   * @param subsetList the subsetList to be ordered
+   * @return a List with ordered subsets
+   */
+  public List<Subset> bubbleSubsetSort(List<Subset> subsetList){
+    List<Subset> result =new ArrayList<Subset>();
+
+    for (int i = 0; i<subsetList.size ()-1; i++) {
+      Subset subset1 =subsetList.get (i);
+      double merit1 =subset1.merit;
+
+      for (int j = i+1; j<subsetList.size (); j++) {
+        Subset subset2 =subsetList.get (j);
+        double merit2 =subset2.merit;
+
+        if(merit2 > merit1){
+          Subset temp =subset1;
+
+          subsetList.set (i,subset2);
+          subsetList.set (j,temp);
+
+          subset1 =subset2;
+          merit1 =subset1.merit;
+        }
+      }
+    }
+    return subsetList;
+  }
+
+
+
+  /**
+   * Join two subsets
+   *
+   * @param subset1 one of the subsets
+   * @param subset2 the other subset
+   * @return a new Subset that is te result of the Join
+   * @exception Exception if the evaluation of the subsets can not be completed
+   */
+  public Subset joinSubsets(Subset subset1, Subset subset2)
+  throws Exception{
+    BitSet b1 =(BitSet)subset1.subset.clone ();
+    BitSet b2 =(BitSet)subset2.subset.clone ();
+
+    b1.or (b2);
+
+    double newMerit =ASEvaluator.evaluateSubset (b1);
+    m_totalEvals++;
+
+    return new Subset((BitSet)b1, newMerit);
+  }
+
+  /**
+   * converts a BitSet into a list of attribute indexes
+   *
+   * @param group the BitSet to convert
+   * @return an array of attribute indexes
+   */
+  public int[] attributeList (BitSet group) {
+    int count = 0;
+
+    // count how many were selected
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        count++;
+      }
+    }
+
+    int[] list = new int[count];
+    count = 0;
+
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (group.get(i)) {
+        list[count++] = i;
+      }
+    }
+
+    return  list;
+  }
+
+
+  // Auxiliar Class for handling solutions and its respective merit
+  public class Subset implements Serializable{
+
+    double merit;
+    BitSet subset;
+
+    public Subset(BitSet subset, double merit){
+      this.subset = (BitSet)subset.clone ();
+      this.merit = merit;
+    }
+
+    public Subset(){
+      this.subset =null;
+      this.merit =-1.0;
+    }
+
+    public boolean isEqual(Subset otherSubset){
+      if(subset.equals (otherSubset.subset))return true;
+      return false;
+    }
+
+    public Subset clone(){
+      return new Subset((BitSet)subset.clone (), this.merit);
+    }
+
+    public int cardinality(){
+      if(subset == null) return 0;
+      return subset.cardinality ();
+    }
+
+    public void flip(int index)
+    throws Exception{
+
+      subset.flip (index);
+      merit = ASEvaluator.evaluateSubset (subset);
+      m_totalEvals ++;
+    }
+
+    public boolean contains(int indexAttribute){
+      return subset.get (indexAttribute);
+    }
+  }
+  //..........
+
+
+  // Test Methods
+
+  public String printSubset(Subset subset){
+    StringBuffer bufferString = new StringBuffer();
+
+    if(subset == null){
+      //System.out.println ("null");
+      return "";
+    }
+
+    BitSet bits =subset.subset;
+    double merit =subset.merit;
+    List<Integer> indexes =new ArrayList<Integer>();
+
+    for (int i = 0; i<m_numAttribs; i++) {
+      if(bits.get (i)){
+        //System.out.print ("1");
+        indexes.add (i+1);
+      }
+      //else System.out.print ("0");
+    }
+    bufferString.append (Utils.doubleToString (merit,8,5)+"\t "+indexes.toString ()+"\n");
+    //System.out.print (" with a merit of: "+merit);
+
+    return bufferString.toString ();
+  }
+
+  //........
+
+
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Tabu Search :\n\nPerforms a search  "
+    +"through "
+    +"the space of attribute subsets. Evading local maximums by accepting bad and diverse solutions "
+    +"and make further search in the best soluions."
+    +" Stops when there's not more improvement in n iterations\n;"
+    + "For more information see:\n\n"
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Abdel-Rahman Hedar, Jue Wangy, and Masao Fukushima");
+    result.setValue(Field.MONTH, "July");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "Tabu Search for Attribute Reduction in Rough Set Theory");
+    result.setValue(Field.LANGUAGE, "English");
+
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4976 $");
+  }
+
+  public TabuSearch() {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Set the random seed.";
+  }
+
+  /**
+   * set the seed for random number generation
+   * @param s seed value
+   */
+  public void setSeed(int s) {
+    m_seed = s;
+  }
+
+  /**
+   * get the value of the random number generator's seed
+   * @return the seed for random number generation
+   */
+  public int getSeed() {
+    return m_seed;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String diversificationProbTipText() {
+    return "Set the probability of diversification. This is the probability of "
+    +"change of search subspace in an abrupt way";
+  }
+
+  /**
+   * set the probability of diverification
+   * @param p the probability of change of search subspace in an abrupt way
+   */
+  public void setDiversificationProb(double p) {
+    m_diversificationProb = p;
+  }
+
+  /**
+   * get the probability of diversification
+   * @return the probability of diversification
+   */
+  public double getDiversificationProb() {
+    return m_diversificationProb;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numNeighborhoodTipText() {
+    return "Set the number of current solution's neighborhood"+
+    " to generate for looking for a better solution";
+  }
+
+  /**
+   * set the number of neighborhood
+   * @param n the number of neighborhood
+   */
+  public void setNumNeighborhood(int n) {
+    m_numNeighborhood = n;
+  }
+
+  /**
+   * get the number of neighborhood
+   * @return the number of neighborhood
+   */
+  public int getNumNeighborhood() {
+    return m_numNeighborhood;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String initialSizeTipText() {
+    return "Set the number of attributes that are going to be in the initial Solution";
+  }
+
+  /**
+   * set the number of attributes that are going to be in the initial Solution
+   * @param n the number of attributes
+   */
+  public void setInitialSize(int n) {
+    m_initialSize = n;
+  }
+
+  /**
+   * get the number of of attributes that are going to be in the initial Solution
+   * @return the number of attributes
+   */
+  public int getInitialSize() {
+    return m_initialSize;
+  }
+
+
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option("\tSpecify the number of attributes "
+        + "\n\tin the initial Solution.."
+        ,"Z",1
+        , "-Z <numInitialSolution>"));
+    newVector.addElement(new Option("\tSpecify the diversification probabilities,"
+        + "\n\tif this value is near to 0 then the best attributes"
+        + "\n\t will have more probabilities of keeping in the new diverse solution"
+        , "P", 1
+        , "-P <diversificationProb>"));
+    newVector.addElement(new Option("\tSet the random number seed."
+        +"\n\t(default = 1)"
+        , "S", 1, "-S <seed>"));
+    newVector.addElement(new Option("\tSet the number of neighbors to generate."
+        , "N", 1, "-N <number of neighbors>"));
+
+    return  newVector.elements();
+  }
+
+  /**
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -Z &lt;numInitialSolution&gt;
+   *  Specify the number of attributes 
+   *  in the initial Solution..</pre>
+   * 
+   * <pre> -P &lt;diversificationProb&gt;
+   *  Specify the diversification probabilities,
+   *  if this value is near to 0 then the best attributes
+   *   will have more probabilities of keeping in the new diverse solution</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Set the random number seed.
+   *  (default = 1)</pre>
+   * 
+   * <pre> -N &lt;number of neighbors&gt;
+   *  Set the number of neighbors to generate.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   *
+   **/
+  public void setOptions (String[] options)
+  throws Exception {
+    String optionString;
+    resetOptions();
+
+
+    optionString = Utils.getOption('Z', options);
+    if (optionString.length() != 0) {
+      setInitialSize (Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('P', options);
+    if (optionString.length() != 0) {
+      setDiversificationProb (Double.parseDouble(optionString));
+    }
+
+    optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumNeighborhood (Integer.parseInt(optionString));
+    }
+
+  }
+
+  /**
+   * Gets the current settings of TabuSearch.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[8];
+    int current = 0;
+
+    options[current++] = "-Z";
+    options[current++] = "" + getInitialSize ();
+
+    options[current++] = "-P";
+    options[current++] = ""+getDiversificationProb ();
+
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    options[current++] = "-N";
+    options[current++] = "" + getNumNeighborhood ();
+
+
+    while (current < options.length)
+      options[current++] = "";
+
+    return  options;
+  }
+
+  /**
+   * returns a description of the search.
+   * @return a description of the search as a String.
+   */
+  public String toString() {
+    StringBuffer FString = new StringBuffer();
+
+    FString.append("\nTabu Search "
+        + "\n\tInitial Size: "+getInitialSize ());
+
+
+    if(getInitialSize () > 0){
+      FString.append("\n\tInitial Solution (Generated by SFS):");
+      FString.append("\n\tmerit:"+"\t\t"+" subset");
+      FString.append("\n\t"+ printSubset (m_initialSolution));
+
+    }
+    FString.append("\tdiversificationProb: "
+        +Utils.doubleToString(Math.abs(getDiversificationProb ()),8,3)+"\n");
+
+    FString.append("\tTotal number of subsets evaluated: "
+        + m_totalEvals + "\n");
+
+    FString.append("\tMerit of best subset found: "
+        +Utils.doubleToString(Math.abs(m_Sbest.merit),8,3)+"\n");
+
+    /*FString.append("\tTime procesing the search space: "
+        +(double)m_processinTime/1000+" seconds\n"); */
+
+    return FString.toString();
+  }
+
+  protected void resetOptions () {
+    m_initialSize = -1;
+    m_numNeighborhood = -1;
+    m_seed = 1;
+    m_diversificationProb = 1.0;
+    m_totalEvals = 0;
+    m_processinTime = 0;
+  }
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedAttributeEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedAttributeEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedAttributeEvaluator.java	(revision 29)
@@ -0,0 +1,39 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnsupervisedAttributeEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+/** 
+ * Abstract unsupervised attribute evaluator.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public abstract class UnsupervisedAttributeEvaluator
+  extends ASEvaluation
+          implements AttributeEvaluator {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4100897318675336178L;
+  
+}
+
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedSubsetEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedSubsetEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/UnsupervisedSubsetEvaluator.java	(revision 29)
@@ -0,0 +1,61 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnsupervisedSubsetEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.clusterers.Clusterer;
+
+/** 
+ * Abstract unsupervised attribute subset evaluator.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public abstract class UnsupervisedSubsetEvaluator 
+  extends ASEvaluation
+  implements SubsetEvaluator {
+
+  /** for serialization */
+  static final long serialVersionUID = 627934376267488763L;
+  
+  /**
+   * Return the number of clusters used by the subset evaluator
+   *
+   * @return the number of clusters used
+   * @exception Exception if an error occurs
+   */
+  public abstract int getNumClusters() throws Exception;
+
+  /**
+   * Get the clusterer
+   *
+   * @return the clusterer
+   */
+  public abstract Clusterer getClusterer();
+
+  /**
+   * Set the clusterer to use
+   *
+   * @param d the clusterer to use
+   */
+  public abstract void setClusterer(Clusterer d);
+}
Index: branches/MetisMQI/src/main/java/weka/attributeSelection/WrapperSubsetEval.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/attributeSelection/WrapperSubsetEval.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/attributeSelection/WrapperSubsetEval.java	(revision 29)
@@ -0,0 +1,830 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WrapperSubsetEval.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * WrapperSubsetEval:<br/>
+ * <br/>
+ * Evaluates attribute sets by using a learning scheme. Cross validation is used to estimate the accuracy of the learning scheme for a set of attributes.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Ron Kohavi, George H. John (1997). Wrappers for feature subset selection. Artificial Intelligence. 97(1-2):273-324.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Kohavi1997,
+ *    author = {Ron Kohavi and George H. John},
+ *    journal = {Artificial Intelligence},
+ *    note = {Special issue on relevance},
+ *    number = {1-2},
+ *    pages = {273-324},
+ *    title = {Wrappers for feature subset selection},
+ *    volume = {97},
+ *    year = {1997},
+ *    ISSN = {0004-3702}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;base learner&gt;
+ *  class name of base learner to use for  accuracy estimation.
+ *  Place any classifier options LAST on the command line
+ *  following a "--". eg.:
+ *   -B weka.classifiers.bayes.NaiveBayes ... -- -K
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> -F &lt;num&gt;
+ *  number of cross validation folds to use for estimating accuracy.
+ *  (default=5)</pre>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Seed for cross validation accuracy testimation.
+ *  (default = 1)</pre>
+ * 
+ * <pre> -T &lt;num&gt;
+ *  threshold by which to execute another cross validation
+ *  (standard deviation---expressed as a percentage of the mean).
+ *  (default: 0.01 (1%))</pre>
+ * 
+ * <pre> -E &lt;acc | rmse | mae | f-meas | auc&gt;
+ *  Performance evaluation measure to use for selecting attributes.
+ *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+ * 
+ * <pre> 
+ * Options specific to scheme weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class WrapperSubsetEval
+  extends ASEvaluation
+  implements SubsetEvaluator,
+             OptionHandler, 
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -4573057658746728675L;
+
+  /** training instances */
+  private Instances m_trainInstances;
+  /** class index */
+  private int m_classIndex;
+  /** number of attributes in the training data */
+  private int m_numAttribs;
+  /** number of instances in the training data */
+  private int m_numInstances;
+  /** holds an evaluation object */
+  private Evaluation m_Evaluation;
+  /** holds the base classifier object */
+  private Classifier m_BaseClassifier;
+  /** number of folds to use for cross validation */
+  private int m_folds;
+  /** random number seed */
+  private int m_seed;
+  /** 
+   * the threshold by which to do further cross validations when
+   * estimating the accuracy of a subset
+   */
+  private double m_threshold;
+  
+  public static final int EVAL_DEFAULT = 1;
+  public static final int EVAL_ACCURACY = 2;
+  public static final int EVAL_RMSE = 3;
+  public static final int EVAL_MAE = 4;
+  public static final int EVAL_FMEASURE = 5;
+  public static final int EVAL_AUC = 6;
+  
+  public static final Tag[] TAGS_EVALUATION = {
+    new Tag(EVAL_DEFAULT, "Default: accuracy (discrete class); RMSE (numeric class)"),
+    new Tag(EVAL_ACCURACY, "Accuracy (discrete class only)"),
+    new Tag(EVAL_RMSE, "RMSE (of the class probabilities for discrete class)"),
+    new Tag(EVAL_MAE, "MAE (of the class probabilities for discrete class)"),
+    new Tag(EVAL_FMEASURE, "F-measure (discrete class only)"),
+    new Tag(EVAL_AUC, "AUC (area under the ROC curve - discrete class only)")
+  };
+  
+  /** The evaluation measure to use */
+  protected int m_evaluationMeasure = EVAL_DEFAULT;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "WrapperSubsetEval:\n\n"
+      +"Evaluates attribute sets by using a learning scheme. Cross "
+      +"validation is used to estimate the accuracy of the learning "
+      +"scheme for a set of attributes.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Ron Kohavi and George H. John");
+    result.setValue(Field.YEAR, "1997");
+    result.setValue(Field.TITLE, "Wrappers for feature subset selection");
+    result.setValue(Field.JOURNAL, "Artificial Intelligence");
+    result.setValue(Field.VOLUME, "97");
+    result.setValue(Field.NUMBER, "1-2");
+    result.setValue(Field.PAGES, "273-324");
+    result.setValue(Field.NOTE, "Special issue on relevance");
+    result.setValue(Field.ISSN, "0004-3702");
+    
+    return result;
+  }
+
+  /**
+   * Constructor. Calls restOptions to set default options
+   **/
+  public WrapperSubsetEval () {
+    resetOptions();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector newVector = new Vector(4);
+    newVector.addElement(new Option(
+	"\tclass name of base learner to use for \taccuracy estimation.\n"
+	+ "\tPlace any classifier options LAST on the command line\n"
+	+ "\tfollowing a \"--\". eg.:\n"
+	+ "\t\t-B weka.classifiers.bayes.NaiveBayes ... -- -K\n"
+	+ "\t(default: weka.classifiers.rules.ZeroR)", 
+	"B", 1, "-B <base learner>"));
+    
+    newVector.addElement(new Option(
+	"\tnumber of cross validation folds to use for estimating accuracy.\n" 
+	+ "\t(default=5)", 
+	"F", 1, "-F <num>"));
+    
+    newVector.addElement(new Option(
+	"\tSeed for cross validation accuracy testimation.\n"
+	+ "\t(default = 1)", 
+	"R", 1,"-R <seed>"));
+    
+    newVector.addElement(new Option(
+	"\tthreshold by which to execute another cross validation\n" 
+	+ "\t(standard deviation---expressed as a percentage of the mean).\n"
+	+ "\t(default: 0.01 (1%))", 
+	"T", 1, "-T <num>"));
+    
+    newVector.addElement(new Option(
+        "\tPerformance evaluation measure to use for selecting attributes.\n" +
+        "\t(Default = accuracy for discrete class and rmse for numeric class)",
+        "E", 1, "-E <acc | rmse | mae | f-meas | auc>"));
+
+    if ((m_BaseClassifier != null) && 
+	(m_BaseClassifier instanceof OptionHandler)) {
+      newVector.addElement(new Option("", "", 0, "\nOptions specific to scheme " 
+				      + m_BaseClassifier.getClass().getName() 
+				      + ":"));
+      Enumeration enu = ((OptionHandler)m_BaseClassifier).listOptions();
+
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+
+    return  newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;base learner&gt;
+   *  class name of base learner to use for  accuracy estimation.
+   *  Place any classifier options LAST on the command line
+   *  following a "--". eg.:
+   *   -B weka.classifiers.bayes.NaiveBayes ... -- -K
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> -F &lt;num&gt;
+   *  number of cross validation folds to use for estimating accuracy.
+   *  (default=5)</pre>
+   * 
+   * <pre> -R &lt;seed&gt;
+   *  Seed for cross validation accuracy testimation.
+   *  (default = 1)</pre>
+   * 
+   * <pre> -T &lt;num&gt;
+   *  threshold by which to execute another cross validation
+   *  (standard deviation---expressed as a percentage of the mean).
+   *  (default: 0.01 (1%))</pre>
+   * 
+   * <pre> -E &lt;acc | rmse | mae | f-meas | auc&gt;
+   *  Performance evaluation measure to use for selecting attributes.
+   *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+   * 
+   * <pre> 
+   * Options specific to scheme weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    String optionString;
+    resetOptions();
+    optionString = Utils.getOption('B', options);
+
+    if (optionString.length() == 0)
+      optionString = ZeroR.class.getName();
+    setClassifier(AbstractClassifier.forName(optionString, 
+				     Utils.partitionOptions(options)));
+    optionString = Utils.getOption('F', options);
+
+    if (optionString.length() != 0) {
+      setFolds(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setSeed(Integer.parseInt(optionString));
+    }
+
+    //       optionString = Utils.getOption('S',options);
+    //       if (optionString.length() != 0)
+    //         {
+    //  	 seed = Integer.parseInt(optionString);
+    //         }
+    optionString = Utils.getOption('T', options);
+
+    if (optionString.length() != 0) {
+      Double temp;
+      temp = Double.valueOf(optionString);
+      setThreshold(temp.doubleValue());
+    }
+    
+    optionString = Utils.getOption('E', options);
+    if (optionString.length() != 0) {
+      if (optionString.equals("acc")) {
+        setEvaluationMeasure(new SelectedTag(EVAL_ACCURACY, TAGS_EVALUATION));
+      } else if (optionString.equals("rmse")) {
+        setEvaluationMeasure(new SelectedTag(EVAL_RMSE, TAGS_EVALUATION));
+      } else if (optionString.equals("mae")) {
+        setEvaluationMeasure(new SelectedTag(EVAL_MAE, TAGS_EVALUATION));
+      } else if (optionString.equals("f-meas")) {
+        setEvaluationMeasure(new SelectedTag(EVAL_FMEASURE, TAGS_EVALUATION));
+      } else if (optionString.equals("auc")) {
+        setEvaluationMeasure(new SelectedTag(EVAL_AUC, TAGS_EVALUATION));
+      } else {
+        throw new IllegalArgumentException("Invalid evaluation measure");
+      }
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluationMeasureTipText() {
+    return "The measure used to evaluate the performance of attribute combinations.";
+  }
+  /**
+   * Gets the currently set performance evaluation measure used for selecting
+   * attributes for the decision table
+   * 
+   * @return the performance evaluation measure
+   */
+  public SelectedTag getEvaluationMeasure() {
+    return new SelectedTag(m_evaluationMeasure, TAGS_EVALUATION);
+  }
+
+  /**
+   * Sets the performance evaluation measure to use for selecting attributes
+   * for the decision table
+   * 
+   * @param newMethod the new performance evaluation metric to use
+   */
+  public void setEvaluationMeasure(SelectedTag newMethod) {
+    if (newMethod.getTags() == TAGS_EVALUATION) {
+      m_evaluationMeasure = newMethod.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Repeat xval if stdev of mean exceeds this value.";
+  }
+
+  /**
+   * Set the value of the threshold for repeating cross validation
+   *
+   * @param t the value of the threshold
+   */
+  public void setThreshold (double t) {
+    m_threshold = t;
+  }
+
+
+  /**
+   * Get the value of the threshold
+   *
+   * @return the threshold as a double
+   */
+  public double getThreshold () {
+    return  m_threshold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTipText() {
+    return "Number of xval folds to use when estimating subset accuracy.";
+  }
+
+  /**
+   * Set the number of folds to use for accuracy estimation
+   *
+   * @param f the number of folds
+   */
+  public void setFolds (int f) {
+    m_folds = f;
+  }
+
+
+  /**
+   * Get the number of folds used for accuracy estimation
+   *
+   * @return the number of folds
+   */
+  public int getFolds () {
+    return  m_folds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Seed to use for randomly generating xval splits.";
+  }
+
+  /**
+   * Set the seed to use for cross validation
+   *
+   * @param s the seed
+   */
+  public void setSeed (int s) {
+    m_seed = s;
+  }
+
+
+  /**
+   * Get the random number seed used for cross validation
+   *
+   * @return the seed
+   */
+  public int getSeed () {
+    return  m_seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "Classifier to use for estimating the accuracy of subsets";
+  }
+
+  /**
+   * Set the classifier to use for accuracy estimation
+   *
+   * @param newClassifier the Classifier to use.
+   */
+  public void setClassifier (Classifier newClassifier) {
+    m_BaseClassifier = newClassifier;
+  }
+
+
+  /**
+   * Get the classifier used as the base learner.
+   *
+   * @return the classifier used as the classifier
+   */
+  public Classifier getClassifier () {
+    return  m_BaseClassifier;
+  }
+
+
+  /**
+   * Gets the current settings of WrapperSubsetEval.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] classifierOptions = new String[0];
+
+    if ((m_BaseClassifier != null) && 
+	(m_BaseClassifier instanceof OptionHandler)) {
+      classifierOptions = ((OptionHandler)m_BaseClassifier).getOptions();
+    }
+
+    String[] options = new String[9 + classifierOptions.length];
+    int current = 0;
+
+    if (getClassifier() != null) {
+      options[current++] = "-B";
+      options[current++] = getClassifier().getClass().getName();
+    }
+
+    options[current++] = "-F";
+    options[current++] = "" + getFolds();
+    options[current++] = "-T";
+    options[current++] = "" + getThreshold();
+    options[current++] = "-R";
+    options[current++] = "" + getSeed();
+    options[current++] = "--";
+    System.arraycopy(classifierOptions, 0, options, current, 
+		     classifierOptions.length);
+    current += classifierOptions.length;
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return  options;
+  }
+
+
+  protected void resetOptions () {
+    m_trainInstances = null;
+    m_Evaluation = null;
+    m_BaseClassifier = new ZeroR();
+    m_folds = 5;
+    m_seed = 1;
+    m_threshold = 0.01;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getClassifier() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getClassifier().getCapabilities();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    // adjustment for class based on selected evaluation metric
+    result.disable(Capability.NUMERIC_CLASS);
+    result.disable(Capability.DATE_CLASS);
+    if (m_evaluationMeasure != EVAL_ACCURACY && m_evaluationMeasure != EVAL_FMEASURE &&
+        m_evaluationMeasure != EVAL_AUC) {
+      result.enable(Capability.NUMERIC_CLASS);
+      result.enable(Capability.DATE_CLASS);
+    }
+    
+    result.setMinimumNumberInstances(getFolds());
+    
+    return result;
+  }
+
+  /**
+   * Generates a attribute evaluator. Has to initialize all fields of the 
+   * evaluator that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the evaluator has not been 
+   * generated successfully
+   */
+  public void buildEvaluator (Instances data)
+    throws Exception {
+
+    // can evaluator handle data?
+    getCapabilities().testWithFail(data);
+
+    m_trainInstances = data;
+    m_classIndex = m_trainInstances.classIndex();
+    m_numAttribs = m_trainInstances.numAttributes();
+    m_numInstances = m_trainInstances.numInstances();
+  }
+
+
+  /**
+   * Evaluates a subset of attributes
+   *
+   * @param subset a bitset representing the attribute subset to be 
+   * evaluated 
+   * @return the error rate
+   * @throws Exception if the subset could not be evaluated
+   */
+  public double evaluateSubset (BitSet subset)
+    throws Exception {
+    double evalMetric = 0;
+    double[] repError = new double[5];
+    int numAttributes = 0;
+    int i, j;
+    Random Rnd = new Random(m_seed);
+    Remove delTransform = new Remove();
+    delTransform.setInvertSelection(true);
+    // copy the instances
+    Instances trainCopy = new Instances(m_trainInstances);
+
+    // count attributes set in the BitSet
+    for (i = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        numAttributes++;
+      }
+    }
+
+    // set up an array of attribute indexes for the filter (+1 for the class)
+    int[] featArray = new int[numAttributes + 1];
+
+    for (i = 0, j = 0; i < m_numAttribs; i++) {
+      if (subset.get(i)) {
+        featArray[j++] = i;
+      }
+    }
+
+    featArray[j] = m_classIndex;
+    delTransform.setAttributeIndicesArray(featArray);
+    delTransform.setInputFormat(trainCopy);
+    trainCopy = Filter.useFilter(trainCopy, delTransform);
+
+    // max of 5 repetitions of cross validation
+    for (i = 0; i < 5; i++) {
+      m_Evaluation = new Evaluation(trainCopy);
+      m_Evaluation.crossValidateModel(m_BaseClassifier, trainCopy, m_folds, Rnd);
+      
+      switch (m_evaluationMeasure) {
+      case EVAL_DEFAULT:
+        repError[i] = m_Evaluation.errorRate();
+        break;
+      case EVAL_ACCURACY:
+        repError[i] = m_Evaluation.errorRate();
+        break;
+      case EVAL_RMSE:
+        repError[i] = m_Evaluation.rootMeanSquaredError();
+        break;
+      case EVAL_MAE:
+        repError[i] = m_Evaluation.meanAbsoluteError();
+        break;
+      case EVAL_FMEASURE:
+        repError[i] = m_Evaluation.weightedFMeasure();
+        break;
+      case EVAL_AUC:
+        repError[i] = m_Evaluation.weightedAreaUnderROC();
+        break;
+      }
+
+      // check on the standard deviation
+      if (!repeat(repError, i + 1)) {
+        i++;
+        break;
+      }
+    }
+
+    for (j = 0; j < i; j++) {
+      evalMetric += repError[j];
+    }
+
+    evalMetric /= (double)i;
+    m_Evaluation = null;
+    
+    switch (m_evaluationMeasure) {
+    case EVAL_DEFAULT:
+    case EVAL_ACCURACY:
+    case EVAL_RMSE:
+    case EVAL_MAE:
+      evalMetric = -evalMetric; // maximize
+      break;
+    }
+    
+    return evalMetric;
+  }
+
+
+  /**
+   * Returns a string describing the wrapper
+   *
+   * @return the description as a string
+   */
+  public String toString () {
+    StringBuffer text = new StringBuffer();
+
+    if (m_trainInstances == null) {
+      text.append("\tWrapper subset evaluator has not been built yet\n");
+    }
+    else {
+      text.append("\tWrapper Subset Evaluator\n");
+      text.append("\tLearning scheme: " 
+		  + getClassifier().getClass().getName() + "\n");
+      text.append("\tScheme options: ");
+      String[] classifierOptions = new String[0];
+
+      if (m_BaseClassifier instanceof OptionHandler) {
+        classifierOptions = ((OptionHandler)m_BaseClassifier).getOptions();
+
+        for (int i = 0; i < classifierOptions.length; i++) {
+          text.append(classifierOptions[i] + " ");
+        }
+      }
+
+      text.append("\n");
+      switch (m_evaluationMeasure) {
+      case EVAL_DEFAULT:
+      case EVAL_ACCURACY:
+        if (m_trainInstances.attribute(m_classIndex).isNumeric()) {
+          text.append("\tSubset evaluation: RMSE\n");
+        } else {
+          text.append("\tSubset evaluation: classification error\n");
+        }
+        break;
+      case EVAL_RMSE:
+        if (m_trainInstances.attribute(m_classIndex).isNumeric()) {
+          text.append("\tSubset evaluation: RMSE\n");
+        } else {
+          text.append("\tSubset evaluation: RMSE (probability estimates)\n");
+        }
+        break;
+      case EVAL_MAE:
+        if (m_trainInstances.attribute(m_classIndex).isNumeric()) {
+          text.append("\tSubset evaluation: MAE\n");
+        } else {
+          text.append("\tSubset evaluation: MAE (probability estimates)\n");
+        }
+        break;
+      case EVAL_FMEASURE:
+        text.append("\tSubset evaluation: F-measure\n");
+        break;
+      case EVAL_AUC:
+        text.append("\tSubset evaluation: area under the ROC curve\n");
+        break;
+      }
+      
+      text.append("\tNumber of folds for accuracy estimation: " 
+          + m_folds 
+          + "\n");
+    }
+
+    return  text.toString();
+  }
+
+
+  /**
+   * decides whether to do another repeat of cross validation. If the
+   * standard deviation of the cross validations
+   * is greater than threshold% of the mean (default 1%) then another 
+   * repeat is done. 
+   *
+   * @param repError an array of cross validation results
+   * @param entries the number of cross validations done so far
+   * @return true if another cv is to be done
+   */
+  private boolean repeat (double[] repError, int entries) {
+    int i;
+    double mean = 0;
+    double variance = 0;
+
+    if (entries == 1) {
+      return  true;
+    }
+
+    for (i = 0; i < entries; i++) {
+      mean += repError[i];
+    }
+
+    mean /= (double)entries;
+
+    for (i = 0; i < entries; i++) {
+      variance += ((repError[i] - mean)*(repError[i] - mean));
+    }
+
+    variance /= (double)entries;
+
+    if (variance > 0) {
+      variance = Math.sqrt(variance);
+    }
+
+    if ((variance/mean) > m_threshold) {
+      return  true;
+    }
+
+    return  false;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    runEvaluator(new WrapperSubsetEval(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/AbstractClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/AbstractClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/AbstractClassifier.java	(revision 29)
@@ -0,0 +1,310 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract classifier. All schemes for numeric or nominal prediction in
+ * Weka extend this class. Note that a classifier MUST either implement
+ * distributionForInstance() or classifyInstance().
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class AbstractClassifier
+  implements Classifier, Cloneable, Serializable, OptionHandler,
+             CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6502780192411755341L;
+
+  /** Whether the classifier is run in debug mode. */
+  protected boolean m_Debug = false;
+
+  /**
+   * Classifies the given test instance. The instance has to belong to a
+   * dataset when it's being classified. Note that a classifier MUST
+   * implement either this or distributionForInstance().
+   *
+   * @param instance the instance to be classified
+   * @return the predicted most likely class for the instance or
+   * Utils.missingValue() if no prediction is made
+   * @exception Exception if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    double [] dist = distributionForInstance(instance);
+    if (dist == null) {
+      throw new Exception("Null distribution predicted");
+    }
+    switch (instance.classAttribute().type()) {
+    case Attribute.NOMINAL:
+      double max = 0;
+      int maxIndex = 0;
+
+      for (int i = 0; i < dist.length; i++) {
+        if (dist[i] > max) {
+          maxIndex = i;
+          max = dist[i];
+        }
+      }
+      if (max > 0) {
+        return maxIndex;
+      } else {
+        return Utils.missingValue();
+      }
+    case Attribute.NUMERIC:
+      return dist[0];
+    default:
+      return Utils.missingValue();
+    }
+  }
+
+  /**
+   * Predicts the class memberships for a given instance. If
+   * an instance is unclassified, the returned array elements
+   * must be all zero. If the class is numeric, the array
+   * must consist of only one element, which contains the
+   * predicted value. Note that a classifier MUST implement
+   * either this or classifyInstance().
+   *
+   * @param instance the instance to be classified
+   * @return an array containing the estimated membership
+   * probabilities of the test instance in each class
+   * or the numeric prediction
+   * @exception Exception if distribution could not be
+   * computed successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    double[] dist = new double[instance.numClasses()];
+    switch (instance.classAttribute().type()) {
+      case Attribute.NOMINAL:
+        double classification = classifyInstance(instance);
+        if (Utils.isMissingValue(classification)) {
+          return dist;
+        } else {
+          dist[(int)classification] = 1.0;
+        }
+        return dist;
+      case Attribute.NUMERIC:
+        dist[0] = classifyInstance(instance);
+        return dist;
+      default:
+        return dist;
+    }
+  }
+
+  /**
+   * Creates a new instance of a classifier given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * classifier implements OptionHandler and the options parameter is
+   * non-null, the classifier will have it's options set.
+   *
+   * @param classifierName the fully qualified class name of the classifier
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created classifier, ready for use.
+   * @exception Exception if the classifier name is invalid, or the options
+   * supplied are not acceptable to the classifier
+   */
+  public static Classifier forName(String classifierName,
+      String [] options) throws Exception {
+
+    return ((AbstractClassifier)Utils.forName(Classifier.class,
+                                              classifierName,
+                                              options));
+  }
+
+  /**
+   * Creates a deep copy of the given classifier using serialization.
+   *
+   * @param model the classifier to copy
+   * @return a deep copy of the classifier
+   * @exception Exception if an error occurs
+   */
+  public static Classifier makeCopy(Classifier model) throws Exception {
+
+    return (Classifier)new SerializedObject(model).getObject();
+  }
+
+  /**
+   * Creates a given number of deep copies of the given classifier using serialization.
+   *
+   * @param model the classifier to copy
+   * @param num the number of classifier copies to create.
+   * @return an array of classifiers.
+   * @exception Exception if an error occurs
+   */
+  public static Classifier [] makeCopies(Classifier model, int num) throws Exception {
+
+    if (model == null) {
+      throw new Exception("No model classifier set");
+    }
+    Classifier [] classifiers = new Classifier [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < classifiers.length; i++) {
+      classifiers[i] = (Classifier) so.getObject();
+    }
+    return classifiers;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+          "\tIf set, classifier is run in debug mode and\n"
+          + "\tmay output additional info to the console",
+          "D", 0, "-D"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -D  <br>
+   * If set, classifier is run in debug mode and
+   * may output additional info to the console.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options;
+    if (getDebug()) {
+      options = new String[1];
+      options[0] = "-D";
+    } else {
+      options = new String[0];
+    }
+    return options;
+  }
+
+  /**
+   * Set debugging mode.
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    m_Debug = debug;
+  }
+
+  /**
+   * Get whether debugging is turned on.
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "If set to true, classifier may output additional info to " +
+      "the console.";
+  }
+
+  /**
+   * Returns the Capabilities of this classifier. Maximally permissive
+   * capabilities are allowed by default. Derived classifiers should
+   * override this method and first disable all capabilities and then
+   * enable just those capabilities that make sense for the scheme.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.enableAll();
+
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+
+  /**
+   * runs the classifier instance with the given options.
+   *
+   * @param classifier		the classifier to run
+   * @param options	the commandline options
+   */
+  protected static void runClassifier(Classifier classifier, String[] options) {
+    try {
+      System.out.println(Evaluation.evaluateModel(classifier, options));
+    }
+    catch (Exception e) {
+      if (    ((e.getMessage() != null) && (e.getMessage().indexOf("General options") == -1))
+          || (e.getMessage() == null) )
+        e.printStackTrace();
+      else
+        System.err.println(e.getMessage());
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/BVDecompose.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/BVDecompose.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/BVDecompose.java	(revision 29)
@@ -0,0 +1,718 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BVDecompose.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for performing a Bias-Variance decomposition on any classifier using the method specified in:<br/>
+ * <br/>
+ * Ron Kohavi, David H. Wolpert: Bias Plus Variance Decomposition for Zero-One Loss Functions. In: Machine Learning: Proceedings of the Thirteenth International Conference, 275-283, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Kohavi1996,
+ *    author = {Ron Kohavi and David H. Wolpert},
+ *    booktitle = {Machine Learning: Proceedings of the Thirteenth International Conference},
+ *    editor = {Lorenza Saitta},
+ *    pages = {275-283},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Bias Plus Variance Decomposition for Zero-One Loss Functions},
+ *    year = {1996},
+ *    PS = {http://robotics.stanford.edu/\~ronnyk/biasVar.ps}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ *
+ * <pre> -c &lt;class index&gt;
+ *  The index of the class attribute.
+ *  (default last)</pre>
+ *
+ * <pre> -t &lt;name of arff file&gt;
+ *  The name of the arff file used for the decomposition.</pre>
+ *
+ * <pre> -T &lt;training pool size&gt;
+ *  The number of instances placed in the training pool.
+ *  The remainder will be used for testing. (default 100)</pre>
+ *
+ * <pre> -s &lt;seed&gt;
+ *  The random number seed used.</pre>
+ *
+ * <pre> -x &lt;num&gt;
+ *  The number of training repetitions used.
+ *  (default 50)</pre>
+ *
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ *
+ * <pre> -W &lt;classifier class name&gt;
+ *  Full class name of the learner used in the decomposition.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ *
+ * <pre>
+ * Options specific to learner weka.classifiers.rules.ZeroR:
+ * </pre>
+ *
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ *
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated sub-learner. <p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public class BVDecompose
+  implements OptionHandler, TechnicalInformationHandler, RevisionHandler {
+
+  /** Debugging mode, gives extra output if true */
+  protected boolean m_Debug;
+
+  /** An instantiated base classifier used for getting and testing options. */
+  protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
+
+  /** The options to be passed to the base classifier. */
+  protected String [] m_ClassifierOptions;
+
+  /** The number of train iterations */
+  protected int m_TrainIterations = 50;
+
+  /** The name of the data file used for the decomposition */
+  protected String m_DataFileName;
+
+  /** The index of the class attribute */
+  protected int m_ClassIndex = -1;
+
+  /** The random number seed */
+  protected int m_Seed = 1;
+
+  /** The calculated bias (squared) */
+  protected double m_Bias;
+
+  /** The calculated variance */
+  protected double m_Variance;
+
+  /** The calculated sigma (squared) */
+  protected double m_Sigma;
+
+  /** The error rate */
+  protected double m_Error;
+
+  /** The number of instances used in the training pool */
+  protected int m_TrainPoolSize = 100;
+
+  /**
+   * Returns a string describing this object
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return
+        "Class for performing a Bias-Variance decomposition on any classifier "
+      + "using the method specified in:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ron Kohavi and David H. Wolpert");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.TITLE, "Bias Plus Variance Decomposition for Zero-One Loss Functions");
+    result.setValue(Field.BOOKTITLE, "Machine Learning: Proceedings of the Thirteenth International Conference");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.EDITOR, "Lorenza Saitta");
+    result.setValue(Field.PAGES, "275-283");
+    result.setValue(Field.PS, "http://robotics.stanford.edu/~ronnyk/biasVar.ps");
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(7);
+
+    newVector.addElement(new Option(
+          "\tThe index of the class attribute.\n"+
+          "\t(default last)",
+          "c", 1, "-c <class index>"));
+    newVector.addElement(new Option(
+          "\tThe name of the arff file used for the decomposition.",
+          "t", 1, "-t <name of arff file>"));
+    newVector.addElement(new Option(
+          "\tThe number of instances placed in the training pool.\n"
+          + "\tThe remainder will be used for testing. (default 100)",
+          "T", 1, "-T <training pool size>"));
+    newVector.addElement(new Option(
+          "\tThe random number seed used.",
+          "s", 1, "-s <seed>"));
+    newVector.addElement(new Option(
+          "\tThe number of training repetitions used.\n"
+          +"\t(default 50)",
+          "x", 1, "-x <num>"));
+    newVector.addElement(new Option(
+          "\tTurn on debugging output.",
+          "D", 0, "-D"));
+    newVector.addElement(new Option(
+          "\tFull class name of the learner used in the decomposition.\n"
+          +"\teg: weka.classifiers.bayes.NaiveBayes",
+          "W", 1, "-W <classifier class name>"));
+
+    if ((m_Classifier != null) &&
+        (m_Classifier instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+            "",
+            "", 0, "\nOptions specific to learner "
+            + m_Classifier.getClass().getName()
+            + ":"));
+      Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+      while (enu.hasMoreElements()) {
+        newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   *
+   * <pre> -c &lt;class index&gt;
+   *  The index of the class attribute.
+   *  (default last)</pre>
+   *
+   * <pre> -t &lt;name of arff file&gt;
+   *  The name of the arff file used for the decomposition.</pre>
+   *
+   * <pre> -T &lt;training pool size&gt;
+   *  The number of instances placed in the training pool.
+   *  The remainder will be used for testing. (default 100)</pre>
+   *
+   * <pre> -s &lt;seed&gt;
+   *  The random number seed used.</pre>
+   *
+   * <pre> -x &lt;num&gt;
+   *  The number of training repetitions used.
+   *  (default 50)</pre>
+   *
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   *
+   * <pre> -W &lt;classifier class name&gt;
+   *  Full class name of the learner used in the decomposition.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   *
+   * <pre>
+   * Options specific to learner weka.classifiers.rules.ZeroR:
+   * </pre>
+   *
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   *
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated sub-learner. <p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setDebug(Utils.getFlag('D', options));
+
+    String classIndex = Utils.getOption('c', options);
+    if (classIndex.length() != 0) {
+      if (classIndex.toLowerCase().equals("last")) {
+        setClassIndex(0);
+      } else if (classIndex.toLowerCase().equals("first")) {
+        setClassIndex(1);
+      } else {
+        setClassIndex(Integer.parseInt(classIndex));
+      }
+    } else {
+      setClassIndex(0);
+    }
+
+    String trainIterations = Utils.getOption('x', options);
+    if (trainIterations.length() != 0) {
+      setTrainIterations(Integer.parseInt(trainIterations));
+    } else {
+      setTrainIterations(50);
+    }
+
+    String trainPoolSize = Utils.getOption('T', options);
+    if (trainPoolSize.length() != 0) {
+      setTrainPoolSize(Integer.parseInt(trainPoolSize));
+    } else {
+      setTrainPoolSize(100);
+    }
+
+    String seedString = Utils.getOption('s', options);
+    if (seedString.length() != 0) {
+      setSeed(Integer.parseInt(seedString));
+    } else {
+      setSeed(1);
+    }
+
+    String dataFile = Utils.getOption('t', options);
+    if (dataFile.length() == 0) {
+      throw new Exception("An arff file must be specified"
+          + " with the -t option.");
+    }
+    setDataFileName(dataFile);
+
+    String classifierName = Utils.getOption('W', options);
+    if (classifierName.length() == 0) {
+      throw new Exception("A learner must be specified with the -W option.");
+    }
+    setClassifier(AbstractClassifier.forName(classifierName,
+          Utils.partitionOptions(options)));
+  }
+
+  /**
+   * Gets the current settings of the CheckClassifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] classifierOptions = new String [0];
+    if ((m_Classifier != null) &&
+        (m_Classifier instanceof OptionHandler)) {
+      classifierOptions = ((OptionHandler)m_Classifier).getOptions();
+        }
+    String [] options = new String [classifierOptions.length + 14];
+    int current = 0;
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+    options[current++] = "-c"; options[current++] = "" + getClassIndex();
+    options[current++] = "-x"; options[current++] = "" + getTrainIterations();
+    options[current++] = "-T"; options[current++] = "" + getTrainPoolSize();
+    options[current++] = "-s"; options[current++] = "" + getSeed();
+    if (getDataFileName() != null) {
+      options[current++] = "-t"; options[current++] = "" + getDataFileName();
+    }
+    if (getClassifier() != null) {
+      options[current++] = "-W";
+      options[current++] = getClassifier().getClass().getName();
+    }
+    options[current++] = "--";
+    System.arraycopy(classifierOptions, 0, options, current,
+        classifierOptions.length);
+    current += classifierOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Get the number of instances in the training pool.
+   *
+   * @return number of instances in the training pool.
+   */
+  public int getTrainPoolSize() {
+
+    return m_TrainPoolSize;
+  }
+
+  /**
+   * Set the number of instances in the training pool.
+   *
+   * @param numTrain number of instances in the training pool.
+   */
+  public void setTrainPoolSize(int numTrain) {
+
+    m_TrainPoolSize = numTrain;
+  }
+
+  /**
+   * Set the classifiers being analysed
+   *
+   * @param newClassifier the Classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+
+    m_Classifier = newClassifier;
+  }
+
+  /**
+   * Gets the name of the classifier being analysed
+   *
+   * @return the classifier being analysed.
+   */
+  public Classifier getClassifier() {
+
+    return m_Classifier;
+  }
+
+  /**
+   * Sets debugging mode
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    m_Debug = debug;
+  }
+
+  /**
+   * Gets whether debugging is turned on
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+
+    return m_Debug;
+  }
+
+  /**
+   * Sets the random number seed
+   *
+   * @param seed the random number seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the random number seed
+   *
+   * @return the random number seed
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+
+  /**
+   * Sets the maximum number of boost iterations
+   *
+   * @param trainIterations the number of boost iterations
+   */
+  public void setTrainIterations(int trainIterations) {
+
+    m_TrainIterations = trainIterations;
+  }
+
+  /**
+   * Gets the maximum number of boost iterations
+   *
+   * @return the maximum number of boost iterations
+   */
+  public int getTrainIterations() {
+
+    return m_TrainIterations;
+  }
+
+  /**
+   * Sets the name of the data file used for the decomposition
+   *
+   * @param dataFileName the data file to use
+   */
+  public void setDataFileName(String dataFileName) {
+
+    m_DataFileName = dataFileName;
+  }
+
+  /**
+   * Get the name of the data file used for the decomposition
+   *
+   * @return the name of the data file
+   */
+  public String getDataFileName() {
+
+    return m_DataFileName;
+  }
+
+  /**
+   * Get the index (starting from 1) of the attribute used as the class.
+   *
+   * @return the index of the class attribute
+   */
+  public int getClassIndex() {
+
+    return m_ClassIndex + 1;
+  }
+
+  /**
+   * Sets index of attribute to discretize on
+   *
+   * @param classIndex the index (starting from 1) of the class attribute
+   */
+  public void setClassIndex(int classIndex) {
+
+    m_ClassIndex = classIndex - 1;
+  }
+
+  /**
+   * Get the calculated bias squared
+   *
+   * @return the bias squared
+   */
+  public double getBias() {
+
+    return m_Bias;
+  }
+
+  /**
+   * Get the calculated variance
+   *
+   * @return the variance
+   */
+  public double getVariance() {
+
+    return m_Variance;
+  }
+
+  /**
+   * Get the calculated sigma squared
+   *
+   * @return the sigma squared
+   */
+  public double getSigma() {
+
+    return m_Sigma;
+  }
+
+  /**
+   * Get the calculated error rate
+   *
+   * @return the error rate
+   */
+  public double getError() {
+
+    return m_Error;
+  }
+
+  /**
+   * Carry out the bias-variance decomposition
+   *
+   * @throws Exception if the decomposition couldn't be carried out
+   */
+  public void decompose() throws Exception {
+
+    Reader dataReader = new BufferedReader(new FileReader(m_DataFileName));
+    Instances data = new Instances(dataReader);
+
+    if (m_ClassIndex < 0) {
+      data.setClassIndex(data.numAttributes() - 1);
+    } else {
+      data.setClassIndex(m_ClassIndex);
+    }
+    if (data.classAttribute().type() != Attribute.NOMINAL) {
+      throw new Exception("Class attribute must be nominal");
+    }
+    int numClasses = data.numClasses();
+
+    data.deleteWithMissingClass();
+    if (data.checkForStringAttributes()) {
+      throw new Exception("Can't handle string attributes!");
+    }
+
+    if (data.numInstances() < 2 * m_TrainPoolSize) {
+      throw new Exception("The dataset must contain at least "
+          + (2 * m_TrainPoolSize) + " instances");
+    }
+    Random random = new Random(m_Seed);
+    data.randomize(random);
+    Instances trainPool = new Instances(data, 0, m_TrainPoolSize);
+    Instances test = new Instances(data, m_TrainPoolSize,
+        data.numInstances() - m_TrainPoolSize);
+    int numTest = test.numInstances();
+    double [][] instanceProbs = new double [numTest][numClasses];
+
+    m_Error = 0;
+    for (int i = 0; i < m_TrainIterations; i++) {
+      if (m_Debug) {
+        System.err.println("Iteration " + (i + 1));
+      }
+      trainPool.randomize(random);
+      Instances train = new Instances(trainPool, 0, m_TrainPoolSize / 2);
+
+      Classifier current = AbstractClassifier.makeCopy(m_Classifier);
+      current.buildClassifier(train);
+
+      //// Evaluate the classifier on test, updating BVD stats
+      for (int j = 0; j < numTest; j++) {
+        int pred = (int)current.classifyInstance(test.instance(j));
+        if (pred != test.instance(j).classValue()) {
+          m_Error++;
+        }
+        instanceProbs[j][pred]++;
+      }
+    }
+    m_Error /= (m_TrainIterations * numTest);
+
+    // Average the BV over each instance in test.
+    m_Bias = 0;
+    m_Variance = 0;
+    m_Sigma = 0;
+    for (int i = 0; i < numTest; i++) {
+      Instance current = test.instance(i);
+      double [] predProbs = instanceProbs[i];
+      double pActual, pPred;
+      double bsum = 0, vsum = 0, ssum = 0;
+      for (int j = 0; j < numClasses; j++) {
+        pActual = (current.classValue() == j) ? 1 : 0; // Or via 1NN from test data?
+        pPred = predProbs[j] / m_TrainIterations;
+        bsum += (pActual - pPred) * (pActual - pPred)
+          - pPred * (1 - pPred) / (m_TrainIterations - 1);
+        vsum += pPred * pPred;
+        ssum += pActual * pActual;
+      }
+      m_Bias += bsum;
+      m_Variance += (1 - vsum);
+      m_Sigma += (1 - ssum);
+    }
+    m_Bias /= (2 * numTest);
+    m_Variance /= (2 * numTest);
+    m_Sigma /= (2 * numTest);
+
+    if (m_Debug) {
+      System.err.println("Decomposition finished");
+    }
+  }
+
+
+  /**
+   * Returns description of the bias-variance decomposition results.
+   *
+   * @return the bias-variance decomposition results as a string
+   */
+  public String toString() {
+
+    String result = "\nBias-Variance Decomposition\n";
+
+    if (getClassifier() == null) {
+      return "Invalid setup";
+    }
+
+    result += "\nClassifier   : " + getClassifier().getClass().getName();
+    if (getClassifier() instanceof OptionHandler) {
+      result += Utils.joinOptions(((OptionHandler)m_Classifier).getOptions());
+    }
+    result += "\nData File    : " + getDataFileName();
+    result += "\nClass Index  : ";
+    if (getClassIndex() == 0) {
+      result += "last";
+    } else {
+      result += getClassIndex();
+    }
+    result += "\nTraining Pool: " + getTrainPoolSize();
+    result += "\nIterations   : " + getTrainIterations();
+    result += "\nSeed         : " + getSeed();
+    result += "\nError        : " + Utils.doubleToString(getError(), 6, 4);
+    result += "\nSigma^2      : " + Utils.doubleToString(getSigma(), 6, 4);
+    result += "\nBias^2       : " + Utils.doubleToString(getBias(), 6, 4);
+    result += "\nVariance     : " + Utils.doubleToString(getVariance(), 6, 4);
+
+    return result + "\n";
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+
+  /**
+   * Test method for this class
+   *
+   * @param args the command line arguments
+   */
+  public static void main(String [] args) {
+
+    try {
+      BVDecompose bvd = new BVDecompose();
+
+      try {
+        bvd.setOptions(args);
+        Utils.checkForRemainingOptions(args);
+      } catch (Exception ex) {
+        String result = ex.getMessage() + "\nBVDecompose Options:\n\n";
+        Enumeration enu = bvd.listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          result += option.synopsis() + "\n" + option.description() + "\n";
+        }
+        throw new Exception(result);
+      }
+
+      bvd.decompose();
+      System.out.println(bvd.toString());
+    } catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/BVDecomposeSegCVSub.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/BVDecomposeSegCVSub.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/BVDecomposeSegCVSub.java	(revision 29)
@@ -0,0 +1,1116 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BVDecomposeSegCVSub.java
+ *    Copyright (C) 2003 Paul Conilione
+ *
+ *    Based on the class: BVDecompose.java by Len Trigg (1999)
+ */
+
+
+/*
+ *    DEDICATION
+ *
+ *    Paul Conilione would like to express his deep gratitude and appreciation
+ *    to his Chinese Buddhist Taoist Master Sifu Chow Yuk Nen for the abilities
+ *    and insight that he has been taught, which have allowed him to program in
+ *    a clear and efficient manner.
+ *
+ *    Master Sifu Chow Yuk Nen's Teachings are unique and precious. They are
+ *    applicable to any field of human endeavour. Through his unique and powerful
+ *    ability to skilfully apply Chinese Buddhist Teachings, people have achieved
+ *    success in; Computing, chemical engineering, business, accounting, philosophy
+ *    and more.
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This class performs Bias-Variance decomposion on any classifier using the sub-sampled cross-validation procedure as specified in (1).<br/>
+ * The Kohavi and Wolpert definition of bias and variance is specified in (2).<br/>
+ * The Webb definition of bias and variance is specified in (3).<br/>
+ * <br/>
+ * Geoffrey I. Webb, Paul Conilione (2002). Estimating bias and variance from data. School of Computer Science and Software Engineering, Victoria, Australia.<br/>
+ * <br/>
+ * Ron Kohavi, David H. Wolpert: Bias Plus Variance Decomposition for Zero-One Loss Functions. In: Machine Learning: Proceedings of the Thirteenth International Conference, 275-283, 1996.<br/>
+ * <br/>
+ * Geoffrey I. Webb (2000). MultiBoosting: A Technique for Combining Boosting and Wagging. Machine Learning. 40(2):159-196.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Webb2002,
+ *    address = {School of Computer Science and Software Engineering, Victoria, Australia},
+ *    author = {Geoffrey I. Webb and Paul Conilione},
+ *    institution = {Monash University},
+ *    title = {Estimating bias and variance from data},
+ *    year = {2002},
+ *    PDF = {http://www.csse.monash.edu.au/\~webb/Files/WebbConilione04.pdf}
+ * }
+ *
+ * &#64;inproceedings{Kohavi1996,
+ *    author = {Ron Kohavi and David H. Wolpert},
+ *    booktitle = {Machine Learning: Proceedings of the Thirteenth International Conference},
+ *    editor = {Lorenza Saitta},
+ *    pages = {275-283},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Bias Plus Variance Decomposition for Zero-One Loss Functions},
+ *    year = {1996},
+ *    PS = {http://robotics.stanford.edu/\~ronnyk/biasVar.ps}
+ * }
+ *
+ * &#64;article{Webb2000,
+ *    author = {Geoffrey I. Webb},
+ *    journal = {Machine Learning},
+ *    number = {2},
+ *    pages = {159-196},
+ *    title = {MultiBoosting: A Technique for Combining Boosting and Wagging},
+ *    volume = {40},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ *
+ * <pre> -c &lt;class index&gt;
+ *  The index of the class attribute.
+ *  (default last)</pre>
+ *
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ *
+ * <pre> -l &lt;num&gt;
+ *  The number of times each instance is classified.
+ *  (default 10)</pre>
+ *
+ * <pre> -p &lt;proportion of objects in common&gt;
+ *  The average proportion of instances common between any two training sets</pre>
+ *
+ * <pre> -s &lt;seed&gt;
+ *  The random number seed used.</pre>
+ *
+ * <pre> -t &lt;name of arff file&gt;
+ *  The name of the arff file used for the decomposition.</pre>
+ *
+ * <pre> -T &lt;number of instances in training set&gt;
+ *  The number of instances in the training set.</pre>
+ *
+ * <pre> -W &lt;classifier class name&gt;
+ *  Full class name of the learner used in the decomposition.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ *
+ * <pre>
+ * Options specific to learner weka.classifiers.rules.ZeroR:
+ * </pre>
+ *
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ *
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated sub-learner. <p>
+ *
+ * @author Paul Conilione (paulc4321@yahoo.com.au)
+ * @version $Revision: 6041 $
+ */
+public class BVDecomposeSegCVSub
+    implements OptionHandler, TechnicalInformationHandler, RevisionHandler {
+
+    /** Debugging mode, gives extra output if true. */
+    protected boolean m_Debug;
+
+    /** An instantiated base classifier used for getting and testing options. */
+    protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
+
+    /** The options to be passed to the base classifier. */
+    protected String [] m_ClassifierOptions;
+
+    /** The number of times an instance is classified*/
+    protected int m_ClassifyIterations;
+
+    /** The name of the data file used for the decomposition */
+    protected String m_DataFileName;
+
+    /** The index of the class attribute */
+    protected int m_ClassIndex = -1;
+
+    /** The random number seed */
+    protected int m_Seed = 1;
+
+    /** The calculated Kohavi & Wolpert bias (squared) */
+    protected double m_KWBias;
+
+    /** The calculated Kohavi & Wolpert variance */
+    protected double m_KWVariance;
+
+    /** The calculated Kohavi & Wolpert sigma */
+    protected double m_KWSigma;
+
+    /** The calculated Webb bias */
+    protected double m_WBias;
+
+    /** The calculated Webb variance */
+    protected double m_WVariance;
+
+    /** The error rate */
+    protected double m_Error;
+
+    /** The training set size */
+    protected int m_TrainSize;
+
+    /** Proportion of instances common between any two training sets. */
+    protected double m_P;
+
+    /**
+     * Returns a string describing this object
+     * @return a description of the classifier suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+      return
+          "This class performs Bias-Variance decomposion on any classifier using the "
+        + "sub-sampled cross-validation procedure as specified in (1).\n"
+        + "The Kohavi and Wolpert definition of bias and variance is specified in (2).\n"
+        + "The Webb definition of bias and variance is specified in (3).\n\n"
+        + getTechnicalInformation().toString();
+    }
+
+    /**
+     * Returns an instance of a TechnicalInformation object, containing
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     *
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      TechnicalInformation 	additional;
+
+      result = new TechnicalInformation(Type.MISC);
+      result.setValue(Field.AUTHOR, "Geoffrey I. Webb and Paul Conilione");
+      result.setValue(Field.YEAR, "2002");
+      result.setValue(Field.TITLE, "Estimating bias and variance from data");
+      result.setValue(Field.INSTITUTION, "Monash University");
+      result.setValue(Field.ADDRESS, "School of Computer Science and Software Engineering, Victoria, Australia");
+      result.setValue(Field.PDF, "http://www.csse.monash.edu.au/~webb/Files/WebbConilione04.pdf");
+
+      additional = result.add(Type.INPROCEEDINGS);
+      additional.setValue(Field.AUTHOR, "Ron Kohavi and David H. Wolpert");
+      additional.setValue(Field.YEAR, "1996");
+      additional.setValue(Field.TITLE, "Bias Plus Variance Decomposition for Zero-One Loss Functions");
+      additional.setValue(Field.BOOKTITLE, "Machine Learning: Proceedings of the Thirteenth International Conference");
+      additional.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+      additional.setValue(Field.EDITOR, "Lorenza Saitta");
+      additional.setValue(Field.PAGES, "275-283");
+      additional.setValue(Field.PS, "http://robotics.stanford.edu/~ronnyk/biasVar.ps");
+
+      additional = result.add(Type.ARTICLE);
+      additional.setValue(Field.AUTHOR, "Geoffrey I. Webb");
+      additional.setValue(Field.YEAR, "2000");
+      additional.setValue(Field.TITLE, "MultiBoosting: A Technique for Combining Boosting and Wagging");
+      additional.setValue(Field.JOURNAL, "Machine Learning");
+      additional.setValue(Field.VOLUME, "40");
+      additional.setValue(Field.NUMBER, "2");
+      additional.setValue(Field.PAGES, "159-196");
+
+      return result;
+    }
+
+    /**
+     * Returns an enumeration describing the available options.
+     *
+     * @return an enumeration of all the available options.
+     */
+    public Enumeration listOptions() {
+
+        Vector newVector = new Vector(8);
+
+        newVector.addElement(new Option(
+        "\tThe index of the class attribute.\n"+
+        "\t(default last)",
+        "c", 1, "-c <class index>"));
+        newVector.addElement(new Option(
+        "\tTurn on debugging output.",
+        "D", 0, "-D"));
+        newVector.addElement(new Option(
+        "\tThe number of times each instance is classified.\n"
+        +"\t(default 10)",
+        "l", 1, "-l <num>"));
+        newVector.addElement(new Option(
+        "\tThe average proportion of instances common between any two training sets",
+        "p", 1, "-p <proportion of objects in common>"));
+        newVector.addElement(new Option(
+        "\tThe random number seed used.",
+        "s", 1, "-s <seed>"));
+        newVector.addElement(new Option(
+        "\tThe name of the arff file used for the decomposition.",
+        "t", 1, "-t <name of arff file>"));
+        newVector.addElement(new Option(
+        "\tThe number of instances in the training set.",
+        "T", 1, "-T <number of instances in training set>"));
+        newVector.addElement(new Option(
+        "\tFull class name of the learner used in the decomposition.\n"
+        +"\teg: weka.classifiers.bayes.NaiveBayes",
+        "W", 1, "-W <classifier class name>"));
+
+        if ((m_Classifier != null) &&
+        (m_Classifier instanceof OptionHandler)) {
+            newVector.addElement(new Option(
+            "",
+            "", 0, "\nOptions specific to learner "
+            + m_Classifier.getClass().getName()
+            + ":"));
+            Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+            while (enu.hasMoreElements()) {
+                newVector.addElement(enu.nextElement());
+            }
+        }
+        return newVector.elements();
+    }
+
+
+    /**
+     * Sets the OptionHandler's options using the given list. All options
+     * will be set (or reset) during this call (i.e. incremental setting
+     * of options is not possible). <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     *
+     * <pre> -c &lt;class index&gt;
+     *  The index of the class attribute.
+     *  (default last)</pre>
+     *
+     * <pre> -D
+     *  Turn on debugging output.</pre>
+     *
+     * <pre> -l &lt;num&gt;
+     *  The number of times each instance is classified.
+     *  (default 10)</pre>
+     *
+     * <pre> -p &lt;proportion of objects in common&gt;
+     *  The average proportion of instances common between any two training sets</pre>
+     *
+     * <pre> -s &lt;seed&gt;
+     *  The random number seed used.</pre>
+     *
+     * <pre> -t &lt;name of arff file&gt;
+     *  The name of the arff file used for the decomposition.</pre>
+     *
+     * <pre> -T &lt;number of instances in training set&gt;
+     *  The number of instances in the training set.</pre>
+     *
+     * <pre> -W &lt;classifier class name&gt;
+     *  Full class name of the learner used in the decomposition.
+     *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+     *
+     * <pre>
+     * Options specific to learner weka.classifiers.rules.ZeroR:
+     * </pre>
+     *
+     * <pre> -D
+     *  If set, classifier is run in debug mode and
+     *  may output additional info to the console</pre>
+     *
+     <!-- options-end -->
+     *
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        setDebug(Utils.getFlag('D', options));
+
+        String classIndex = Utils.getOption('c', options);
+        if (classIndex.length() != 0) {
+            if (classIndex.toLowerCase().equals("last")) {
+                setClassIndex(0);
+            } else if (classIndex.toLowerCase().equals("first")) {
+                setClassIndex(1);
+            } else {
+                setClassIndex(Integer.parseInt(classIndex));
+            }
+        } else {
+            setClassIndex(0);
+        }
+
+        String classifyIterations = Utils.getOption('l', options);
+        if (classifyIterations.length() != 0) {
+            setClassifyIterations(Integer.parseInt(classifyIterations));
+        } else {
+            setClassifyIterations(10);
+        }
+
+        String prob = Utils.getOption('p', options);
+        if (prob.length() != 0) {
+            setP( Double.parseDouble(prob));
+        } else {
+            setP(-1);
+        }
+        //throw new Exception("A proportion must be specified" + " with a -p option.");
+
+        String seedString = Utils.getOption('s', options);
+        if (seedString.length() != 0) {
+            setSeed(Integer.parseInt(seedString));
+        } else {
+            setSeed(1);
+        }
+
+        String dataFile = Utils.getOption('t', options);
+        if (dataFile.length() != 0) {
+            setDataFileName(dataFile);
+        } else {
+            throw new Exception("An arff file must be specified"
+            + " with the -t option.");
+        }
+
+        String trainSize = Utils.getOption('T', options);
+        if (trainSize.length() != 0) {
+            setTrainSize(Integer.parseInt(trainSize));
+        } else {
+            setTrainSize(-1);
+        }
+        //throw new Exception("A training set size must be specified" + " with a -T option.");
+
+        String classifierName = Utils.getOption('W', options);
+        if (classifierName.length() != 0) {
+            setClassifier(AbstractClassifier.forName(classifierName, Utils.partitionOptions(options)));
+        } else {
+            throw new Exception("A learner must be specified with the -W option.");
+        }
+    }
+
+    /**
+     * Gets the current settings of the CheckClassifier.
+     *
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String [] getOptions() {
+
+        String [] classifierOptions = new String [0];
+        if ((m_Classifier != null) &&
+        (m_Classifier instanceof OptionHandler)) {
+            classifierOptions = ((OptionHandler)m_Classifier).getOptions();
+        }
+        String [] options = new String [classifierOptions.length + 14];
+        int current = 0;
+        if (getDebug()) {
+            options[current++] = "-D";
+        }
+        options[current++] = "-c"; options[current++] = "" + getClassIndex();
+        options[current++] = "-l"; options[current++] = "" + getClassifyIterations();
+        options[current++] = "-p"; options[current++] = "" + getP();
+        options[current++] = "-s"; options[current++] = "" + getSeed();
+        if (getDataFileName() != null) {
+            options[current++] = "-t"; options[current++] = "" + getDataFileName();
+        }
+        options[current++] = "-T"; options[current++] = "" + getTrainSize();
+        if (getClassifier() != null) {
+            options[current++] = "-W";
+            options[current++] = getClassifier().getClass().getName();
+        }
+
+        options[current++] = "--";
+        System.arraycopy(classifierOptions, 0, options, current,
+        classifierOptions.length);
+        current += classifierOptions.length;
+        while (current < options.length) {
+            options[current++] = "";
+        }
+        return options;
+    }
+
+    /**
+     * Set the classifiers being analysed
+     *
+     * @param newClassifier the Classifier to use.
+     */
+    public void setClassifier(Classifier newClassifier) {
+
+        m_Classifier = newClassifier;
+    }
+
+    /**
+     * Gets the name of the classifier being analysed
+     *
+     * @return the classifier being analysed.
+     */
+    public Classifier getClassifier() {
+
+        return m_Classifier;
+    }
+
+    /**
+     * Sets debugging mode
+     *
+     * @param debug true if debug output should be printed
+     */
+    public void setDebug(boolean debug) {
+
+        m_Debug = debug;
+    }
+
+    /**
+     * Gets whether debugging is turned on
+     *
+     * @return true if debugging output is on
+     */
+    public boolean getDebug() {
+
+        return m_Debug;
+    }
+
+
+    /**
+     * Sets the random number seed
+     *
+     * @param seed the random number seed
+     */
+    public void setSeed(int seed) {
+
+        m_Seed = seed;
+    }
+
+    /**
+     * Gets the random number seed
+     *
+     * @return the random number seed
+     */
+    public int getSeed() {
+
+        return m_Seed;
+    }
+
+    /**
+     * Sets the number of times an instance is classified
+     *
+     * @param classifyIterations number of times an instance is classified
+     */
+    public void setClassifyIterations(int classifyIterations) {
+
+        m_ClassifyIterations = classifyIterations;
+    }
+
+    /**
+     * Gets the number of times an instance is classified
+     *
+     * @return the maximum number of times an instance is classified
+     */
+    public int getClassifyIterations() {
+
+        return m_ClassifyIterations;
+    }
+
+    /**
+     * Sets the name of the dataset file.
+     *
+     * @param dataFileName name of dataset file.
+     */
+    public void setDataFileName(String dataFileName) {
+
+        m_DataFileName = dataFileName;
+    }
+
+    /**
+     * Get the name of the data file used for the decomposition
+     *
+     * @return the name of the data file
+     */
+    public String getDataFileName() {
+
+        return m_DataFileName;
+    }
+
+    /**
+     * Get the index (starting from 1) of the attribute used as the class.
+     *
+     * @return the index of the class attribute
+     */
+    public int getClassIndex() {
+
+        return m_ClassIndex + 1;
+    }
+
+    /**
+     * Sets index of attribute to discretize on
+     *
+     * @param classIndex the index (starting from 1) of the class attribute
+     */
+    public void setClassIndex(int classIndex) {
+
+        m_ClassIndex = classIndex - 1;
+    }
+
+    /**
+     * Get the calculated bias squared according to the Kohavi and Wolpert definition
+     *
+     * @return the bias squared
+     */
+    public double getKWBias() {
+
+        return m_KWBias;
+    }
+
+    /**
+     * Get the calculated bias according to the Webb definition
+     *
+     * @return the bias
+     *
+     */
+    public double getWBias() {
+
+        return m_WBias;
+    }
+
+
+    /**
+     * Get the calculated variance according to the Kohavi and Wolpert definition
+     *
+     * @return the variance
+     */
+    public double getKWVariance() {
+
+        return m_KWVariance;
+    }
+
+    /**
+     * Get the calculated variance according to the Webb definition
+     *
+     * @return the variance according to Webb
+     *
+     */
+    public double getWVariance() {
+
+        return m_WVariance;
+    }
+
+    /**
+     * Get the calculated sigma according to the Kohavi and Wolpert definition
+     *
+     * @return the sigma
+     *
+     */
+    public double getKWSigma() {
+
+        return m_KWSigma;
+    }
+
+    /**
+     * Set the training size.
+     *
+     * @param size the size of the training set
+     *
+     */
+    public void setTrainSize(int size) {
+
+        m_TrainSize = size;
+    }
+
+    /**
+     * Get the training size
+     *
+     * @return the size of the training set
+     *
+     */
+    public int getTrainSize() {
+
+        return m_TrainSize;
+    }
+
+    /**
+     * Set the proportion of instances that are common between two training sets
+     * used to train a classifier.
+     *
+     * @param proportion the proportion of instances that are common between training
+     * sets.
+     *
+     */
+    public void setP(double proportion) {
+
+        m_P = proportion;
+    }
+
+    /**
+     * Get the proportion of instances that are common between two training sets.
+     *
+     * @return the proportion
+     *
+     */
+    public double getP() {
+
+        return m_P;
+    }
+
+    /**
+     * Get the calculated error rate
+     *
+     * @return the error rate
+     */
+    public double getError() {
+
+        return m_Error;
+    }
+
+    /**
+     * Carry out the bias-variance decomposition using the sub-sampled cross-validation method.
+     *
+     * @throws Exception if the decomposition couldn't be carried out
+     */
+    public void decompose() throws Exception {
+
+        Reader dataReader;
+        Instances data;
+
+        int tps; // training pool size, size of segment E.
+        int k; // number of folds in segment E.
+        int q; // number of segments of size tps.
+
+        dataReader = new BufferedReader(new FileReader(m_DataFileName)); //open file
+        data = new Instances(dataReader); // encapsulate in wrapper class called weka.Instances()
+
+        if (m_ClassIndex < 0) {
+            data.setClassIndex(data.numAttributes() - 1);
+        } else {
+            data.setClassIndex(m_ClassIndex);
+        }
+
+        if (data.classAttribute().type() != Attribute.NOMINAL) {
+            throw new Exception("Class attribute must be nominal");
+        }
+        int numClasses = data.numClasses();
+
+        data.deleteWithMissingClass();
+        if ( data.checkForStringAttributes() ) {
+            throw new Exception("Can't handle string attributes!");
+        }
+
+        // Dataset size must be greater than 2
+        if ( data.numInstances() <= 2 ){
+            throw new Exception("Dataset size must be greater than 2.");
+        }
+
+        if ( m_TrainSize == -1 ){ // default value
+            m_TrainSize = (int) Math.floor( (double) data.numInstances() / 2.0 );
+        }else  if ( m_TrainSize < 0 || m_TrainSize >= data.numInstances() - 1 ) {  // Check if 0 < training Size < D - 1
+            throw new Exception("Training set size of "+m_TrainSize+" is invalid.");
+        }
+
+        if ( m_P == -1 ){ // default value
+            m_P = (double) m_TrainSize / ( (double)data.numInstances() - 1 );
+        }else if (  m_P < ( m_TrainSize / ( (double)data.numInstances() - 1 ) ) || m_P >= 1.0  ) { //Check if p is in range: m/(|D|-1) <= p < 1.0
+            throw new Exception("Proportion is not in range: "+ (m_TrainSize / ((double) data.numInstances() - 1 )) +" <= p < 1.0 ");
+        }
+
+        //roundup tps from double to integer
+        tps = (int) Math.ceil( ((double)m_TrainSize / (double)m_P) + 1 );
+        k = (int) Math.ceil( tps / (tps - (double) m_TrainSize));
+
+        // number of folds cannot be more than the number of instances in the training pool
+        if ( k > tps ) {
+            throw new Exception("The required number of folds is too many."
+            + "Change p or the size of the training set.");
+        }
+
+        // calculate the number of segments, round down.
+        q = (int) Math.floor( (double) data.numInstances() / (double)tps );
+
+        //create confusion matrix, columns = number of instances in data set, as all will be used,  by rows = number of classes.
+        double [][] instanceProbs = new double [data.numInstances()][numClasses];
+        int [][] foldIndex = new int [ k ][ 2 ];
+        Vector segmentList = new Vector(q + 1);
+
+        //Set random seed
+        Random random = new Random(m_Seed);
+
+        data.randomize(random);
+
+        //create index arrays for different segments
+
+        int currentDataIndex = 0;
+
+        for( int count = 1; count <= (q + 1); count++ ){
+            if( count > q){
+                int [] segmentIndex = new int [ (data.numInstances() - (q * tps)) ];
+                for(int index = 0; index < segmentIndex.length; index++, currentDataIndex++){
+
+                    segmentIndex[index] = currentDataIndex;
+                }
+                segmentList.add(segmentIndex);
+            } else {
+                int [] segmentIndex = new int [ tps ];
+
+                for(int index = 0; index < segmentIndex.length; index++, currentDataIndex++){
+                    segmentIndex[index] = currentDataIndex;
+                }
+                segmentList.add(segmentIndex);
+            }
+        }
+
+        int remainder = tps % k; // remainder is used to determine when to shrink the fold size by 1.
+
+        //foldSize = ROUNDUP( tps / k ) (round up, eg 3 -> 3,  3.3->4)
+        int foldSize = (int) Math.ceil( (double)tps /(double) k); //roundup fold size double to integer
+        int index = 0;
+        int currentIndex;
+
+        for( int count = 0; count < k; count ++){
+            if( remainder != 0 && count == remainder ){
+                foldSize -= 1;
+            }
+            foldIndex[count][0] = index;
+            foldIndex[count][1] = foldSize;
+            index += foldSize;
+        }
+
+        for( int l = 0; l < m_ClassifyIterations; l++) {
+
+            for(int i = 1; i <= q; i++){
+
+                int [] currentSegment = (int[]) segmentList.get(i - 1);
+
+                randomize(currentSegment, random);
+
+                //CROSS FOLD VALIDATION for current Segment
+                for( int j = 1; j <= k; j++){
+
+                    Instances TP = null;
+                    for(int foldNum = 1; foldNum <= k; foldNum++){
+                        if( foldNum != j){
+
+                            int startFoldIndex = foldIndex[ foldNum - 1 ][ 0 ]; //start index
+                            foldSize = foldIndex[ foldNum - 1 ][ 1 ];
+                            int endFoldIndex = startFoldIndex + foldSize - 1;
+
+                            for(int currentFoldIndex = startFoldIndex; currentFoldIndex <= endFoldIndex; currentFoldIndex++){
+
+                                if( TP == null ){
+                                    TP = new Instances(data, currentSegment[ currentFoldIndex ], 1);
+                                }else{
+                                    TP.add( data.instance( currentSegment[ currentFoldIndex ] ) );
+                                }
+                            }
+                        }
+                    }
+
+                    TP.randomize(random);
+
+                    if( getTrainSize() > TP.numInstances() ){
+                        throw new Exception("The training set size of " + getTrainSize() + ", is greater than the training pool "
+                        + TP.numInstances() );
+                    }
+
+                    Instances train = new Instances(TP, 0, m_TrainSize);
+
+                    Classifier current = AbstractClassifier.makeCopy(m_Classifier);
+                    current.buildClassifier(train); // create a clssifier using the instances in train.
+
+                    int currentTestIndex = foldIndex[ j - 1 ][ 0 ]; //start index
+                    int testFoldSize = foldIndex[ j - 1 ][ 1 ]; //size
+                    int endTestIndex = currentTestIndex + testFoldSize - 1;
+
+                    while( currentTestIndex <= endTestIndex ){
+
+                        Instance testInst = data.instance( currentSegment[currentTestIndex] );
+                        int pred = (int)current.classifyInstance( testInst );
+
+
+                        if(pred != testInst.classValue()) {
+                            m_Error++; // add 1 to mis-classifications.
+                        }
+                        instanceProbs[ currentSegment[ currentTestIndex ] ][ pred ]++;
+                        currentTestIndex++;
+                    }
+
+                    if( i == 1 && j == 1){
+                        int[] segmentElast = (int[])segmentList.lastElement();
+                        for( currentIndex = 0; currentIndex < segmentElast.length; currentIndex++){
+                            Instance testInst = data.instance( segmentElast[currentIndex] );
+                            int pred = (int)current.classifyInstance( testInst );
+                            if(pred != testInst.classValue()) {
+                                m_Error++; // add 1 to mis-classifications.
+                            }
+
+                            instanceProbs[ segmentElast[ currentIndex ] ][ pred ]++;
+                        }
+                    }
+                }
+            }
+        }
+
+        m_Error /= (double)( m_ClassifyIterations * data.numInstances() );
+
+        m_KWBias = 0.0;
+        m_KWVariance = 0.0;
+        m_KWSigma = 0.0;
+
+        m_WBias = 0.0;
+        m_WVariance = 0.0;
+
+        for (int i = 0; i < data.numInstances(); i++) {
+
+            Instance current = data.instance( i );
+
+            double [] predProbs = instanceProbs[ i ];
+            double pActual, pPred;
+            double bsum = 0, vsum = 0, ssum = 0;
+            double wBSum = 0, wVSum = 0;
+
+            Vector centralTendencies = findCentralTendencies( predProbs );
+
+            if( centralTendencies == null ){
+                throw new Exception("Central tendency was null.");
+            }
+
+            for (int j = 0; j < numClasses; j++) {
+                pActual = (current.classValue() == j) ? 1 : 0;
+                pPred = predProbs[j] / m_ClassifyIterations;
+                bsum += (pActual - pPred) * (pActual - pPred) - pPred * (1 - pPred) / (m_ClassifyIterations - 1);
+                vsum += pPred * pPred;
+                ssum += pActual * pActual;
+            }
+
+            m_KWBias += bsum;
+            m_KWVariance += (1 - vsum);
+            m_KWSigma += (1 - ssum);
+
+            for( int count = 0; count < centralTendencies.size(); count++ ) {
+
+                int wB = 0, wV = 0;
+                int centralTendency = ((Integer)centralTendencies.get(count)).intValue();
+
+                // For a single instance xi, find the bias and variance.
+                for (int j = 0; j < numClasses; j++) {
+
+                    //Webb definition
+                    if( j != (int)current.classValue() && j == centralTendency ) {
+                        wB += predProbs[j];
+                    }
+                    if( j != (int)current.classValue() && j != centralTendency ) {
+                        wV += predProbs[j];
+                    }
+
+                }
+                wBSum += (double) wB;
+                wVSum += (double) wV;
+            }
+
+            // calculate bais by dividing bSum by the number of central tendencies and
+            // total number of instances. (effectively finding the average and dividing
+            // by the number of instances to get the nominalised probability).
+
+            m_WBias += ( wBSum / ((double) ( centralTendencies.size() * m_ClassifyIterations )));
+            // calculate variance by dividing vSum by the total number of interations
+            m_WVariance += ( wVSum / ((double) ( centralTendencies.size() * m_ClassifyIterations )));
+
+        }
+
+        m_KWBias /= (2.0 * (double) data.numInstances());
+        m_KWVariance /= (2.0 * (double) data.numInstances());
+        m_KWSigma /= (2.0 * (double) data.numInstances());
+
+        // bias = bias / number of data instances
+        m_WBias /= (double) data.numInstances();
+        // variance = variance / number of data instances.
+        m_WVariance /= (double) data.numInstances();
+
+        if (m_Debug) {
+            System.err.println("Decomposition finished");
+        }
+
+    }
+
+    /** Finds the central tendency, given the classifications for an instance.
+     *
+     * Where the central tendency is defined as the class that was most commonly
+     * selected for a given instance.<p>
+     *
+     * For example, instance 'x' may be classified out of 3 classes y = {1, 2, 3},
+     * so if x is classified 10 times, and is classified as follows, '1' = 2 times, '2' = 5 times
+     * and '3' = 3 times. Then the central tendency is '2'. <p>
+     *
+     * However, it is important to note that this method returns a list of all classes
+     * that have the highest number of classifications.
+     *
+     * In cases where there are several classes with the largest number of classifications, then
+     * all of these classes are returned. For example if 'x' is classified '1' = 4 times,
+     * '2' = 4 times and '3' = 2 times. Then '1' and '2' are returned.<p>
+     *
+     * @param predProbs the array of classifications for a single instance.
+     *
+     * @return a Vector containing Integer objects which store the class(s) which
+     * are the central tendency.
+     */
+    public Vector findCentralTendencies(double[] predProbs) {
+
+        int centralTValue = 0;
+        int currentValue = 0;
+        //array to store the list of classes the have the greatest number of classifictions.
+        Vector centralTClasses;
+
+        centralTClasses = new Vector(); //create an array with size of the number of classes.
+
+        // Go through array, finding the central tendency.
+        for( int i = 0; i < predProbs.length; i++) {
+            currentValue = (int) predProbs[i];
+            // if current value is greater than the central tendency value then
+            // clear vector and add new class to vector array.
+            if( currentValue > centralTValue) {
+                centralTClasses.clear();
+                centralTClasses.addElement( new Integer(i) );
+                centralTValue = currentValue;
+            } else if( currentValue != 0 && currentValue == centralTValue) {
+                centralTClasses.addElement( new Integer(i) );
+            }
+        }
+        //return all classes that have the greatest number of classifications.
+        if( centralTValue != 0){
+            return centralTClasses;
+        } else {
+            return null;
+        }
+
+    }
+
+    /**
+     * Returns description of the bias-variance decomposition results.
+     *
+     * @return the bias-variance decomposition results as a string
+     */
+    public String toString() {
+
+        String result = "\nBias-Variance Decomposition Segmentation, Cross Validation\n" +
+        "with subsampling.\n";
+
+        if (getClassifier() == null) {
+            return "Invalid setup";
+        }
+
+        result += "\nClassifier    : " + getClassifier().getClass().getName();
+        if (getClassifier() instanceof OptionHandler) {
+            result += Utils.joinOptions(((OptionHandler)m_Classifier).getOptions());
+        }
+        result += "\nData File     : " + getDataFileName();
+        result += "\nClass Index   : ";
+        if (getClassIndex() == 0) {
+            result += "last";
+        } else {
+            result += getClassIndex();
+        }
+        result += "\nIterations    : " + getClassifyIterations();
+        result += "\np             : " + getP();
+        result += "\nTraining Size : " + getTrainSize();
+        result += "\nSeed          : " + getSeed();
+
+        result += "\n\nDefinition   : " +"Kohavi and Wolpert";
+        result += "\nError         :" + Utils.doubleToString(getError(), 4);
+        result += "\nBias^2        :" + Utils.doubleToString(getKWBias(), 4);
+        result += "\nVariance      :" + Utils.doubleToString(getKWVariance(), 4);
+        result += "\nSigma^2       :" + Utils.doubleToString(getKWSigma(), 4);
+
+        result += "\n\nDefinition   : " +"Webb";
+        result += "\nError         :" + Utils.doubleToString(getError(), 4);
+        result += "\nBias          :" + Utils.doubleToString(getWBias(), 4);
+        result += "\nVariance      :" + Utils.doubleToString(getWVariance(), 4);
+
+        return result;
+    }
+
+    /**
+     * Returns the revision string.
+     *
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6041 $");
+    }
+
+    /**
+     * Test method for this class
+     *
+     * @param args the command line arguments
+     */
+    public static void main(String [] args) {
+
+        try {
+            BVDecomposeSegCVSub bvd = new BVDecomposeSegCVSub();
+
+            try {
+                bvd.setOptions(args);
+                Utils.checkForRemainingOptions(args);
+            } catch (Exception ex) {
+                String result = ex.getMessage() + "\nBVDecompose Options:\n\n";
+                Enumeration enu = bvd.listOptions();
+                while (enu.hasMoreElements()) {
+                    Option option = (Option) enu.nextElement();
+                    result += option.synopsis() + "\n" + option.description() + "\n";
+                }
+                throw new Exception(result);
+            }
+
+            bvd.decompose();
+
+            System.out.println(bvd.toString());
+
+        } catch (Exception ex) {
+            System.err.println(ex.getMessage());
+        }
+
+    }
+
+    /**
+     * Accepts an array of ints and randomises the values in the array, using the
+     * random seed.
+     *
+     *@param index is the array of integers
+     *@param random is the Random seed.
+     */
+    public final void randomize(int[] index, Random random) {
+        for( int j = index.length - 1; j > 0; j-- ){
+            int k = random.nextInt( j + 1 );
+            int temp = index[j];
+            index[j] = index[k];
+            index[k] = temp;
+        }
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/CheckClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/CheckClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/CheckClassifier.java	(revision 29)
@@ -0,0 +1,1971 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Attribute;
+import weka.core.CheckScheme;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with
+ * classifiers. If you implement a classifier using the WEKA.libraries,
+ * you should run the checks on it to ensure robustness and correct
+ * operation. Passing all the tests of this object does not mean
+ * bugs in the classifier don't exist, but this will help find some
+ * common ones. <p/>
+ *
+ * Typical usage: <p/>
+ * <code>java weka.classifiers.CheckClassifier -W classifier_name
+ * classifier_options </code><p/>
+ *
+ * CheckClassifier reports on the following:
+ * <ul>
+ *    <li> Classifier abilities
+ *      <ul>
+ *         <li> Possible command line options to the classifier </li>
+ *         <li> Whether the classifier can predict nominal, numeric, string,
+ *              date or relational class attributes. Warnings will be displayed if
+ *              performance is worse than ZeroR </li>
+ *         <li> Whether the classifier can be trained incrementally </li>
+ *         <li> Whether the classifier can handle numeric predictor attributes </li>
+ *         <li> Whether the classifier can handle nominal predictor attributes </li>
+ *         <li> Whether the classifier can handle string predictor attributes </li>
+ *         <li> Whether the classifier can handle date predictor attributes </li>
+ *         <li> Whether the classifier can handle relational predictor attributes </li>
+ *         <li> Whether the classifier can handle multi-instance data </li>
+ *         <li> Whether the classifier can handle missing predictor values </li>
+ *         <li> Whether the classifier can handle missing class values </li>
+ *         <li> Whether a nominal classifier only handles 2 class problems </li>
+ *         <li> Whether the classifier can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning
+ *      <ul>
+ *         <li> Correct initialisation during buildClassifier (i.e. no result
+ *              changes when buildClassifier called repeatedly) </li>
+ *         <li> Whether incremental training produces the same results
+ *              as during non-incremental training (which may or may not
+ *              be OK) </li>
+ *         <li> Whether the classifier alters the data pased to it
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *         <li> Whether the toString() method works correctly before the
+ *              classifier has been built. </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases
+ *      <ul>
+ *         <li> building classifier with zero training instances </li>
+ *         <li> all but one predictor attribute values missing </li>
+ *         <li> all predictor attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckClassifier with the debug option set will output the
+ * training and test datasets for any failed tests.<p/>
+ *
+ * The <code>weka.classifiers.AbstractClassifierTest</code> uses this
+ * class to test all the classifiers. Any changes here, have to be
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ *
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ *
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ *
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ *
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 2).</pre>
+ *
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 1).</pre>
+ *
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 1).</pre>
+ *
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 1).</pre>
+ *
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 1).</pre>
+ *
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 1).</pre>
+ *
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ *
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ *
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ *
+ * <pre> -W
+ *  Full name of the classifier analysed.
+ *  eg: weka.classifiers.bayes.NaiveBayes
+ *  (default weka.classifiers.rules.ZeroR)</pre>
+ *
+ * <pre>
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ *
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ *
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p/>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6041 $
+ * @see TestInstances
+ */
+public class CheckClassifier
+  extends CheckScheme {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   * - in case the performance is worse than that of ZeroR both indices are true
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+
+  /*** The classifier to be examined */
+  protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+        "\tFull name of the classifier analysed.\n"
+        +"\teg: weka.classifiers.bayes.NaiveBayes\n"
+        + "\t(default weka.classifiers.rules.ZeroR)",
+        "W", 1, "-W"));
+
+    if ((m_Classifier != null)
+        && (m_Classifier instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0,
+          "\nOptions specific to classifier "
+          + m_Classifier.getClass().getName()
+          + ":"));
+      Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+      while (enu.hasMoreElements())
+        result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   *
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   *
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   *
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   *
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 2).</pre>
+   *
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 1).</pre>
+   *
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 1).</pre>
+   *
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 1).</pre>
+   *
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 1).</pre>
+   *
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 1).</pre>
+   *
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   *
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   *
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   *
+   * <pre> -W
+   *  Full name of the classifier analysed.
+   *  eg: weka.classifiers.bayes.NaiveBayes
+   *  (default weka.classifiers.rules.ZeroR)</pre>
+   *
+   * <pre>
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   *
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   *
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.classifiers.rules.ZeroR.class.getName();
+    setClassifier(
+        (Classifier) forName(
+          "weka.classifiers",
+          Classifier.class,
+          tmpStr,
+          Utils.partitionOptions(options)));
+  }
+
+  /**
+   * Gets the current settings of the CheckClassifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getClassifier() != null) {
+      result.add("-W");
+      result.add(getClassifier().getClass().getName());
+    }
+
+    if ((m_Classifier != null) && (m_Classifier instanceof OptionHandler))
+      options = ((OptionHandler) m_Classifier).getOptions();
+    else
+      options = new String[0];
+
+    if (options.length > 0) {
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+
+    if (getClassifier() == null) {
+      println("\n=== No classifier set ===");
+      return;
+    }
+    println("\n=== Check on Classifier: "
+        + getClassifier().getClass().getName()
+        + " ===\n");
+
+    // Start tests
+    m_ClasspathProblems = false;
+    println("--> Checking for interfaces");
+    canTakeOptions();
+    boolean updateableClassifier = updateableClassifier()[0];
+    boolean weightedInstancesHandler = weightedInstancesHandler()[0];
+    boolean multiInstanceHandler = multiInstanceHandler()[0];
+    println("--> Classifier tests");
+    declaresSerialVersionUID();
+    testToString();
+    testsPerClassType(Attribute.NOMINAL,    updateableClassifier, weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.NUMERIC,    updateableClassifier, weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.DATE,       updateableClassifier, weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.STRING,     updateableClassifier, weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.RELATIONAL, updateableClassifier, weightedInstancesHandler, multiInstanceHandler);
+  }
+
+  /**
+   * Set the classifier for boosting.
+   *
+   * @param newClassifier the Classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+    m_Classifier = newClassifier;
+  }
+
+  /**
+   * Get the classifier used as the classifier
+   *
+   * @return the classifier used as the classifier
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Run a battery of tests for a given class attribute type
+   *
+   * @param classType true if the class attribute should be numeric
+   * @param updateable true if the classifier is updateable
+   * @param weighted true if the classifier says it handles weights
+   * @param multiInstance true if the classifier is a multi-instance classifier
+   */
+  protected void testsPerClassType(int classType,
+                                   boolean updateable,
+                                   boolean weighted,
+                                   boolean multiInstance) {
+
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance, classType)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance, classType)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance, classType)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance, classType)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance, classType)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+
+      canHandleOnlyClass(PNom, PNum, PStr, PDat, PRel, classType);
+
+      if (classType == Attribute.NOMINAL)
+        canHandleNClasses(PNom, PNum, PStr, PDat, PRel, multiInstance, 4);
+
+      if (!multiInstance) {
+        canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 0);
+        canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 1);
+      }
+
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel,
+          multiInstance, classType,
+          true, false, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, true, false, 100);
+
+      boolean handleMissingClass = canHandleMissing(PNom, PNum, PStr, PDat, PRel,
+          multiInstance, classType,
+          false, true, 20)[0];
+      if (handleMissingClass)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, false, true, 100);
+
+      correctBuildInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, classType,
+          handleMissingPredictors, handleMissingClass);
+      doesntUseTestClassVal(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      if (updateable)
+        updatingEquality(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+    }
+  }
+
+  /**
+   * Checks whether the scheme's toString() method works even though the
+   * classifies hasn't been built yet.
+   *
+   * @return index 0 is true if the toString() method works fine
+   */
+  protected boolean[] testToString() {
+    boolean[] result = new boolean[2];
+
+    print("toString...");
+
+    try {
+      Classifier copy = (Classifier) m_Classifier.getClass().newInstance();
+      copy.toString();
+      result[0] = true;
+      println("yes");
+    }
+    catch (Exception e) {
+      result[0] = false;
+      println("no");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        e.printStackTrace();
+        println("\n");
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * tests for a serialVersionUID. Fails in case the scheme doesn't declare
+   * a UID.
+   *
+   * @return index 0 is true if the scheme declares a UID
+   */
+  protected boolean[] declaresSerialVersionUID() {
+    boolean[] result = new boolean[2];
+
+    print("serialVersionUID...");
+
+    result[0] = !SerializationHelper.needsUID(m_Classifier.getClass());
+
+    if (result[0])
+      println("yes");
+    else
+      println("no");
+
+    return result;
+  }
+
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the classifier can take options
+   */
+  protected boolean[] canTakeOptions() {
+
+    boolean[] result = new boolean[2];
+
+    print("options...");
+    if (m_Classifier instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n"
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the scheme can build models incrementally.
+   *
+   * @return index 0 is true if the classifier can train incrementally
+   */
+  protected boolean[] updateableClassifier() {
+
+    boolean[] result = new boolean[2];
+
+    print("updateable classifier...");
+    if (m_Classifier instanceof UpdateableClassifier) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the classifier handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+
+    boolean[] result = new boolean[2];
+
+    print("weighted instances classifier...");
+    if (m_Classifier instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the scheme handles multi-instance data.
+   *
+   * @return true if the classifier handles multi-instance data
+   */
+  protected boolean[] multiInstanceHandler() {
+    boolean[] result = new boolean[2];
+
+    print("multi-instance classifier...");
+    if (m_Classifier instanceof MultiInstanceCapabilitiesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks basic prediction of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] canPredict(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    print("basic predict");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("unary");
+    accepts.addElement("binary");
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("multi-instance");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor,
+        datePredictor, relationalPredictor,
+        multiInstance,
+        classType,
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numTest, numClasses,
+        accepts);
+  }
+
+  /**
+   * Checks whether the scheme can handle data that contains only the class
+   * attribute. If a scheme cannot build a proper model with that data, it
+   * should default back to a ZeroR model.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] canHandleOnlyClass(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      int classType) {
+
+    print("only class in data");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, false, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("class");
+    accepts.addElement("zeror");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    return runBasicTest(false, false, false, false, false,
+                        false,
+                        classType,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numTest, 2,
+                        accepts);
+  }
+
+  /**
+   * Checks whether nominal schemes can handle more than two classes.
+   * If a scheme is only designed for two-class problems it should
+   * throw an appropriate exception for multi-class problems.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param numClasses the number of classes to test
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] canHandleNClasses(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int numClasses) {
+
+    print("more than two class problems");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, Attribute.NOMINAL);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("number");
+    accepts.addElement("class");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor,
+                        datePredictor, relationalPredictor,
+                        multiInstance,
+                        Attribute.NOMINAL,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numTest, numClasses,
+                        accepts);
+  }
+
+  /**
+   * Checks whether the scheme can handle class attributes as Nth attribute.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class attribute (0-based, -1 means last attribute)
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected boolean[] canHandleClassAsNthAttribute(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex) {
+
+    if (classIndex == TestInstances.CLASS_IS_LAST)
+      print("class attribute as last attribute");
+    else
+      print("class attribute as " + (classIndex + 1) + ". attribute");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    int numTrain = getNumInstances(), numTest = getNumInstances(), numClasses = 2,
+    missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor,
+                        datePredictor, relationalPredictor,
+                        multiInstance,
+                        classType,
+                        classIndex,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numTest, numClasses,
+                        accepts);
+  }
+
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    print("handle zero training instances");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, numTest = getNumInstances(), numClasses = 2,
+    missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    return runBasicTest(
+              nominalPredictor, numericPredictor, stringPredictor,
+              datePredictor, relationalPredictor,
+              multiInstance,
+              classType,
+              missingLevel, predictorMissing, classMissing,
+              numTrain, numTest, numClasses,
+              accepts);
+  }
+
+  /**
+   * Checks whether the scheme correctly initialises models when
+   * buildClassifier is called. This test calls buildClassifier with
+   * one training dataset and records performance on a test set.
+   * buildClassifier is then called on a training set with different
+   * structure, and then again with the original training set. The
+   * performance on the test set is compared with the original results
+   * and any performance difference noted as incorrect build initialisation.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if the
+   *         scheme performs worse than ZeroR, but without error (index 0 is
+   *         false)
+   */
+  protected boolean[] correctBuildInitialisation(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    boolean[] result = new boolean[2];
+
+    print("correct initialisation during buildClassifier");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    Instances train1 = null;
+    Instances test1 = null;
+    Instances train2 = null;
+    Instances test2 = null;
+    Classifier classifier = null;
+    Evaluation evaluation1A = null;
+    Evaluation evaluation1B = null;
+    Evaluation evaluation2 = null;
+    boolean built = false;
+    int stage = 0;
+    try {
+
+      // Make two sets of train/test splits with different
+      // numbers of attributes
+      train1 = makeTestDataset(42, numTrain,
+                               nominalPredictor    ? getNumNominal()    : 0,
+                               numericPredictor    ? getNumNumeric()    : 0,
+                               stringPredictor     ? getNumString()     : 0,
+                               datePredictor       ? getNumDate()       : 0,
+                               relationalPredictor ? getNumRelational() : 0,
+                               numClasses,
+                               classType,
+                               multiInstance);
+      train2 = makeTestDataset(84, numTrain,
+                               nominalPredictor    ? getNumNominal() + 1 : 0,
+                               numericPredictor    ? getNumNumeric() + 1 : 0,
+                               stringPredictor     ? getNumString()      : 0,
+                               datePredictor       ? getNumDate()        : 0,
+                               relationalPredictor ? getNumRelational()  : 0,
+                               numClasses,
+                               classType,
+                               multiInstance);
+      test1 = makeTestDataset(24, numTest,
+                              nominalPredictor     ? getNumNominal()    : 0,
+                              numericPredictor     ? getNumNumeric()    : 0,
+                              stringPredictor      ? getNumString()     : 0,
+                              datePredictor        ? getNumDate()       : 0,
+                              relationalPredictor  ? getNumRelational() : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      test2 = makeTestDataset(48, numTest,
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0,
+                              stringPredictor     ? getNumString()      : 0,
+                              datePredictor       ? getNumDate()        : 0,
+                              relationalPredictor ? getNumRelational()  : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, predictorMissing, classMissing);
+        addMissing(test1, Math.min(missingLevel,50), predictorMissing,
+            classMissing);
+        addMissing(train2, missingLevel, predictorMissing, classMissing);
+        addMissing(test2, Math.min(missingLevel,50), predictorMissing,
+            classMissing);
+      }
+
+      classifier = AbstractClassifier.makeCopies(getClassifier(), 1)[0];
+      evaluation1A = new Evaluation(train1);
+      evaluation1B = new Evaluation(train1);
+      evaluation2 = new Evaluation(train2);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      stage = 0;
+      classifier.buildClassifier(train1);
+      built = true;
+      if (!testWRTZeroR(classifier, evaluation1A, train1, test1)[0]) {
+        throw new Exception("Scheme performs worse than ZeroR");
+      }
+
+      stage = 1;
+      built = false;
+      classifier.buildClassifier(train2);
+      built = true;
+      if (!testWRTZeroR(classifier, evaluation2, train2, test2)[0]) {
+        throw new Exception("Scheme performs worse than ZeroR");
+      }
+
+      stage = 2;
+      built = false;
+      classifier.buildClassifier(train1);
+      built = true;
+      if (!testWRTZeroR(classifier, evaluation1B, train1, test1)[0]) {
+        throw new Exception("Scheme performs worse than ZeroR");
+      }
+
+      stage = 3;
+      if (!evaluation1A.equals(evaluation1B)) {
+        if (m_Debug) {
+          println("\n=== Full report ===\n"
+              + evaluation1A.toSummaryString("\nFirst buildClassifier()",
+                  true)
+                  + "\n\n");
+          println(
+              evaluation1B.toSummaryString("\nSecond buildClassifier()",
+                  true)
+                  + "\n\n");
+        }
+        throw new Exception("Results differ between buildClassifier calls");
+      }
+      println("yes");
+      result[0] = true;
+
+      if (false && m_Debug) {
+        println("\n=== Full report ===\n"
+            + evaluation1A.toSummaryString("\nFirst buildClassifier()",
+                true)
+                + "\n\n");
+        println(
+            evaluation1B.toSummaryString("\nSecond buildClassifier()",
+                true)
+                + "\n\n");
+      }
+    }
+    catch (Exception ex) {
+      String msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("worse than zeror") >= 0) {
+        println("warning: performs worse than ZeroR");
+        result[0] = (stage < 1);
+        result[1] = (stage < 1);
+      } else {
+        println("no");
+        result[0] = false;
+      }
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Test1 Dataset ===\n"
+            + test1.toString() + "\n\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+        println("=== Test2 Dataset ===\n"
+            + test2.toString() + "\n\n");
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if the missing values may be in
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing,
+      int missingLevel) {
+
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (predictorMissing) {
+      print(" predictor");
+      if (classMissing)
+        print(" and");
+    }
+    if (classMissing)
+      print(" class");
+    print(" values");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2;
+
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor,
+        datePredictor, relationalPredictor,
+        multiInstance,
+        classType,
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numTest, numClasses,
+        accepts);
+  }
+
+  /**
+   * Checks whether an updateable scheme produces the same model when
+   * trained incrementally as when batch trained. The model itself
+   * cannot be compared, so we compare the evaluation on test data
+   * for both models. It is possible to get a false positive on this
+   * test (likelihood depends on the classifier).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] updatingEquality(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    print("incremental training produces the same results"
+        + " as batch training");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances test = null;
+    Classifier [] classifiers = null;
+    Evaluation evaluationB = null;
+    Evaluation evaluationI = null;
+    boolean built = false;
+    try {
+      train = makeTestDataset(42, numTrain,
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0,
+                              stringPredictor     ? getNumString()     : 0,
+                              datePredictor       ? getNumDate()       : 0,
+                              relationalPredictor ? getNumRelational() : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      test = makeTestDataset(24, numTest,
+                             nominalPredictor    ? getNumNominal()    : 0,
+                             numericPredictor    ? getNumNumeric()    : 0,
+                             stringPredictor     ? getNumString()     : 0,
+                             datePredictor       ? getNumDate()       : 0,
+                             relationalPredictor ? getNumRelational() : 0,
+                             numClasses,
+                             classType,
+                             multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+        addMissing(test, Math.min(missingLevel, 50), predictorMissing,
+            classMissing);
+      }
+      classifiers = AbstractClassifier.makeCopies(getClassifier(), 2);
+      evaluationB = new Evaluation(train);
+      evaluationI = new Evaluation(train);
+      classifiers[0].buildClassifier(train);
+      testWRTZeroR(classifiers[0], evaluationB, train, test);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      classifiers[1].buildClassifier(new Instances(train, 0));
+      for (int i = 0; i < train.numInstances(); i++) {
+        ((UpdateableClassifier)classifiers[1]).updateClassifier(
+            train.instance(i));
+      }
+      built = true;
+      testWRTZeroR(classifiers[1], evaluationI, train, test);
+      if (!evaluationB.equals(evaluationI)) {
+        println("no");
+        result[0] = false;
+
+        if (m_Debug) {
+          println("\n=== Full Report ===");
+          println("Results differ between batch and "
+              + "incrementally built models.\n"
+              + "Depending on the classifier, this may be OK");
+          println("Here are the results:\n");
+          println(evaluationB.toSummaryString(
+              "\nbatch built results\n", true));
+          println(evaluationI.toSummaryString(
+              "\nincrementally built results\n", true));
+          println("Here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+          println("=== Test Dataset ===\n"
+              + test.toString() + "\n\n");
+        }
+      }
+      else {
+        println("yes");
+        result[0] = true;
+      }
+    } catch (Exception ex) {
+      result[0] = false;
+
+      print("Problem during");
+      if (built)
+        print(" testing");
+      else
+        print(" training");
+      println(": " + ex.getMessage() + "\n");
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the classifier erroneously uses the class
+   * value of test instances (if provided). Runs the classifier with
+   * test instance class values set to missing and compares with results
+   * when test instance class values are left intact.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] doesntUseTestClassVal(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    print("classifier ignores test instance class vals");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = 2*getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances test = null;
+    Classifier [] classifiers = null;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain,
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0,
+                              stringPredictor     ? getNumString()      : 0,
+                              datePredictor       ? getNumDate()        : 0,
+                              relationalPredictor ? getNumRelational()  : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      test = makeTestDataset(24, numTest,
+                             nominalPredictor    ? getNumNominal() + 1 : 0,
+                             numericPredictor    ? getNumNumeric() + 1 : 0,
+                             stringPredictor     ? getNumString()      : 0,
+                             datePredictor       ? getNumDate()        : 0,
+                             relationalPredictor ? getNumRelational()  : 0,
+                             numClasses,
+                             classType,
+                             multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+        addMissing(test, Math.min(missingLevel, 50), predictorMissing,
+            classMissing);
+      }
+      classifiers = AbstractClassifier.makeCopies(getClassifier(), 2);
+      classifiers[0].buildClassifier(train);
+      classifiers[1].buildClassifier(train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+
+      // Now set test values to missing when predicting
+      for (int i = 0; i < test.numInstances(); i++) {
+        Instance testInst = test.instance(i);
+        Instance classMissingInst = (Instance)testInst.copy();
+        classMissingInst.setDataset(test);
+        classMissingInst.setClassMissing();
+        double [] dist0 = classifiers[0].distributionForInstance(testInst);
+        double [] dist1 = classifiers[1].distributionForInstance(classMissingInst);
+        for (int j = 0; j < dist0.length; j++) {
+          // ignore, if both are NaNs
+          if (Double.isNaN(dist0[j]) && Double.isNaN(dist1[j])) {
+            if (getDebug())
+              System.out.println("Both predictions are NaN!");
+            continue;
+          }
+          // distribution different?
+          if (dist0[j] != dist1[j]) {
+            throw new Exception("Prediction different for instance " + (i + 1));
+          }
+        }
+      }
+
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+
+        if (evalFail) {
+          println("Results differ between non-missing and "
+              + "missing test class values.");
+        } else {
+          print("Problem during testing");
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here are the datasets:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1)
+              + "    " + train.instance(i).weight());
+        }
+        println("=== Test Dataset ===\n"
+            + test.toString() + "\n\n");
+        println("(test weights all 1.0\n");
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the classifier can handle instance weights.
+   * This test compares the classifier performance on two datasets
+   * that are identical except for the training weights. If the
+   * results change, then the classifier must be using the weights. It
+   * may be possible to get a false positive from this test if the
+   * weight changes aren't significant enough to induce a change
+   * in classifier performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    print("classifier uses instance weights");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = 2*getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances test = null;
+    Classifier [] classifiers = null;
+    Evaluation evaluationB = null;
+    Evaluation evaluationI = null;
+    boolean built = false;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain,
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0,
+                              stringPredictor     ? getNumString()      : 0,
+                              datePredictor       ? getNumDate()        : 0,
+                              relationalPredictor ? getNumRelational()  : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      test = makeTestDataset(24, numTest,
+                             nominalPredictor    ? getNumNominal() + 1 : 0,
+                             numericPredictor    ? getNumNumeric() + 1 : 0,
+                             stringPredictor     ? getNumString()      : 0,
+                             datePredictor       ? getNumDate()        : 0,
+                             relationalPredictor ? getNumRelational()  : 0,
+                             numClasses,
+                             classType,
+                             multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+        addMissing(test, Math.min(missingLevel, 50), predictorMissing,
+            classMissing);
+      }
+      classifiers = AbstractClassifier.makeCopies(getClassifier(), 2);
+      evaluationB = new Evaluation(train);
+      evaluationI = new Evaluation(train);
+      classifiers[0].buildClassifier(train);
+      testWRTZeroR(classifiers[0], evaluationB, train, test);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+
+      // Now modify instance weights and re-built/test
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      classifiers[1].buildClassifier(train);
+      built = true;
+      testWRTZeroR(classifiers[1], evaluationI, train, test);
+      if (evaluationB.equals(evaluationI)) {
+        // println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println(evaluationB.toSummaryString("\nboth methods\n",
+              true));
+        } else {
+          print("Problem during");
+          if (built) {
+            print(" testing");
+          } else {
+            print(" training");
+          }
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here are the datasets:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1)
+              + "    " + train.instance(i).weight());
+        }
+        println("=== Test Dataset ===\n"
+            + test.toString() + "\n\n");
+        println("(test weights all 1.0\n");
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * training. If the scheme needs to modify the training
+   * data it should take a copy of the training data. Currently checks
+   * for changes to header structure, number of instances, order of
+   * instances, instance weights.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if we know the classifier can handle
+   * (at least) moderate missing predictor values
+   * @param classMissing true if we know the classifier can handle
+   * (at least) moderate missing class values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(
+      boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing) {
+
+    print("classifier doesn't alter original datasets");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(),
+    numClasses = 2, missingLevel = 20;
+
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances test = null;
+    Classifier classifier = null;
+    Evaluation evaluation = null;
+    boolean built = false;
+    try {
+      train = makeTestDataset(42, numTrain,
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0,
+                              stringPredictor     ? getNumString()     : 0,
+                              datePredictor       ? getNumDate()       : 0,
+                              relationalPredictor ? getNumRelational() : 0,
+                              numClasses,
+                              classType,
+                              multiInstance);
+      test = makeTestDataset(24, numTest,
+                             nominalPredictor     ? getNumNominal()    : 0,
+                             numericPredictor     ? getNumNumeric()    : 0,
+                             stringPredictor      ? getNumString()     : 0,
+                             datePredictor        ? getNumDate()       : 0,
+                             relationalPredictor  ? getNumRelational() : 0,
+                             numClasses,
+                             classType,
+                             multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+        addMissing(test, Math.min(missingLevel, 50), predictorMissing,
+            classMissing);
+      }
+      classifier = AbstractClassifier.makeCopies(getClassifier(), 1)[0];
+      evaluation = new Evaluation(train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      Instances trainCopy = new Instances(train);
+      Instances testCopy = new Instances(test);
+      classifier.buildClassifier(trainCopy);
+      compareDatasets(train, trainCopy);
+      built = true;
+      testWRTZeroR(classifier, evaluation, trainCopy, testCopy);
+      compareDatasets(test, testCopy);
+
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("Here are the datasets:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Test Dataset ===\n"
+            + test.toString() + "\n\n");
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numTest the number of instaces in the test set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numTest,
+      int numClasses,
+      FastVector accepts) {
+
+    return runBasicTest(
+        nominalPredictor,
+        numericPredictor,
+        stringPredictor,
+        datePredictor,
+        relationalPredictor,
+        multiInstance,
+        classType,
+        TestInstances.CLASS_IS_LAST,
+        missingLevel,
+        predictorMissing,
+        classMissing,
+        numTrain,
+        numTest,
+        numClasses,
+        accepts);
+  }
+
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the attribute index of the class
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numTest the number of instaces in the test set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor,
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numTest,
+      int numClasses,
+      FastVector accepts) {
+
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Instances test = null;
+    Classifier classifier = null;
+    Evaluation evaluation = null;
+    boolean built = false;
+    try {
+      train = makeTestDataset(42, numTrain,
+                              nominalPredictor     ? getNumNominal()    : 0,
+                              numericPredictor     ? getNumNumeric()    : 0,
+                              stringPredictor      ? getNumString()     : 0,
+                              datePredictor        ? getNumDate()       : 0,
+                              relationalPredictor  ? getNumRelational() : 0,
+                              numClasses,
+                              classType,
+                              classIndex,
+                              multiInstance);
+      test = makeTestDataset(24, numTest,
+                             nominalPredictor     ? getNumNominal()    : 0,
+                             numericPredictor     ? getNumNumeric()    : 0,
+                             stringPredictor      ? getNumString()     : 0,
+                             datePredictor        ? getNumDate()       : 0,
+                             relationalPredictor  ? getNumRelational() : 0,
+                             numClasses,
+                             classType,
+                             classIndex,
+                             multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+        addMissing(test, Math.min(missingLevel, 50), predictorMissing,
+            classMissing);
+      }
+      classifier = AbstractClassifier.makeCopies(getClassifier(), 1)[0];
+      evaluation = new Evaluation(train);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      classifier.buildClassifier(train);
+      built = true;
+      if (!testWRTZeroR(classifier, evaluation, train, test)[0]) {
+        result[0] = true;
+        result[1] = true;
+        throw new Exception("Scheme performs worse than ZeroR");
+      }
+
+      println("yes");
+      result[0] = true;
+    }
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+        msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+        m_ClasspathProblems = true;
+      if (msg.indexOf("worse than zeror") >= 0) {
+        println("warning: performs worse than ZeroR");
+        result[0] = true;
+        result[1] = true;
+      } else {
+        for (int i = 0; i < accepts.size(); i++) {
+          if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+            acceptable = true;
+          }
+        }
+
+        println("no" + (acceptable ? " (OK error message)" : ""));
+        result[1] = acceptable;
+      }
+
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+          println("=== Test Dataset ===\n"
+              + test.toString() + "\n\n");
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Determine whether the scheme performs worse than ZeroR during testing
+   *
+   * @param classifier the pre-trained classifier
+   * @param evaluation the classifier evaluation object
+   * @param train the training data
+   * @param test the test data
+   * @return index 0 is true if the scheme performs better than ZeroR
+   * @throws Exception if there was a problem during the scheme's testing
+   */
+  protected boolean[] testWRTZeroR(Classifier classifier,
+                                   Evaluation evaluation,
+                                   Instances train, Instances test)
+  throws Exception {
+
+    boolean[] result = new boolean[2];
+
+    evaluation.evaluateModel(classifier, test);
+    try {
+
+      // Tested OK, compare with ZeroR
+      Classifier zeroR = new weka.classifiers.rules.ZeroR();
+      zeroR.buildClassifier(train);
+      Evaluation zeroREval = new Evaluation(train);
+      zeroREval.evaluateModel(zeroR, test);
+      result[0] = Utils.grOrEq(zeroREval.errorRate(), evaluation.errorRate());
+    }
+    catch (Exception ex) {
+      throw new Error("Problem determining ZeroR performance: "
+          + ex.getMessage());
+    }
+
+    return result;
+  }
+
+  /**
+   * Make a simple set of instances, which can later be modified
+   * for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances,
+                                      int numNominal, int numNumeric,
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      boolean multiInstance)
+    throws Exception {
+
+    return makeTestDataset(
+        seed,
+        numInstances,
+        numNominal,
+        numNumeric,
+        numString,
+        numDate,
+        numRelational,
+        numClasses,
+        classType,
+        TestInstances.CLASS_IS_LAST,
+        multiInstance);
+  }
+
+  /**
+   * Make a simple set of instances with variable position of the class
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class (0-based, -1 as last)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances,
+                                      int numNominal, int numNumeric,
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      int classIndex,
+                                      boolean multiInstance)
+  throws Exception {
+
+    TestInstances dataset = new TestInstances();
+
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal(numNominal);
+    dataset.setNumNumeric(numNumeric);
+    dataset.setNumString(numString);
+    dataset.setNumDate(numDate);
+    dataset.setNumRelational(numRelational);
+    dataset.setNumClasses(numClasses);
+    dataset.setClassType(classType);
+    dataset.setClassIndex(classIndex);
+    dataset.setNumClasses(numClasses);
+    dataset.setMultiInstance(multiInstance);
+    dataset.setWords(getWords());
+    dataset.setWordSeparators(getWordSeparators());
+
+    return process(dataset.generate());
+  }
+
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param nominalPredictor true if nominal predictor attributes are present
+   * @param numericPredictor true if numeric predictor attributes are present
+   * @param stringPredictor true if string predictor attributes are present
+   * @param datePredictor true if date predictor attributes are present
+   * @param relationalPredictor true if relational predictor attributes are present
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(boolean nominalPredictor,
+                                       boolean numericPredictor,
+                                       boolean stringPredictor,
+                                       boolean datePredictor,
+                                       boolean relationalPredictor,
+                                       boolean multiInstance,
+                                       int classType) {
+
+    String str = "";
+
+    if (numericPredictor)
+      str += " numeric";
+
+    if (nominalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " nominal";
+    }
+
+    if (stringPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " string";
+    }
+
+    if (datePredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " date";
+    }
+
+    if (relationalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " relational";
+    }
+
+    str += " predictors)";
+
+    switch (classType) {
+      case Attribute.NUMERIC:
+        str = " (numeric class," + str;
+        break;
+      case Attribute.NOMINAL:
+        str = " (nominal class," + str;
+        break;
+      case Attribute.STRING:
+        str = " (string class," + str;
+        break;
+      case Attribute.DATE:
+        str = " (date class," + str;
+        break;
+      case Attribute.RELATIONAL:
+        str = " (relational class," + str;
+        break;
+    }
+
+    print(str);
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+
+  /**
+   * Test method for this class
+   *
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    runCheck(new CheckClassifier(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/CheckSource.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/CheckSource.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/CheckSource.java	(revision 29)
@@ -0,0 +1,428 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckSource.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A simple class for checking the source generated from Classifiers
+ * implementing the <code>weka.classifiers.Sourcable</code> interface.
+ * It takes a classifier, the classname of the generated source
+ * and the dataset the source was generated with as parameters and tests
+ * the output of the built classifier against the output of the generated
+ * source. Use option '-h' to display all available commandline options.
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ *
+ * <pre> -W &lt;classname and options&gt;
+ *  The classifier (incl. options) that was used to generate
+ *  the source code.</pre>
+ *
+ * <pre> -S &lt;classname&gt;
+ *  The classname of the generated source code.</pre>
+ *
+ * <pre> -t &lt;file&gt;
+ *  The training set with which the source code was generated.</pre>
+ *
+ * <pre> -c &lt;index&gt;
+ *  The class index of the training set. 'first' and 'last' are
+ *  valid indices.
+ *  (default: last)</pre>
+ *
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier (specified with -W).
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6041 $
+ * @see     weka.classifiers.Sourcable
+ */
+public class CheckSource
+  implements OptionHandler, RevisionHandler {
+
+  /** the classifier used for generating the source code */
+  protected Classifier m_Classifier = null;
+
+  /** the generated source code */
+  protected Classifier m_SourceCode = null;
+
+  /** the dataset to use for testing */
+  protected File m_Dataset = null;
+
+  /** the class index */
+  protected int m_ClassIndex = -1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+        "\tThe classifier (incl. options) that was used to generate\n"
+        + "\tthe source code.",
+        "W", 1, "-W <classname and options>"));
+
+    result.addElement(new Option(
+        "\tThe classname of the generated source code.",
+        "S", 1, "-S <classname>"));
+
+    result.addElement(new Option(
+        "\tThe training set with which the source code was generated.",
+        "t", 1, "-t <file>"));
+
+    result.addElement(new Option(
+        "\tThe class index of the training set. 'first' and 'last' are\n"
+        + "\tvalid indices.\n"
+        + "\t(default: last)",
+        "c", 1, "-c <index>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   *
+   * <pre> -W &lt;classname and options&gt;
+   *  The classifier (incl. options) that was used to generate
+   *  the source code.</pre>
+   *
+   * <pre> -S &lt;classname&gt;
+   *  The classname of the generated source code.</pre>
+   *
+   * <pre> -t &lt;file&gt;
+   *  The training set with which the source code was generated.</pre>
+   *
+   * <pre> -c &lt;index&gt;
+   *  The class index of the training set. 'first' and 'last' are
+   *  valid indices.
+   *  (default: last)</pre>
+   *
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier (specified with
+   * -W).
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    String[]    spec;
+    String      classname;
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) {
+      spec = Utils.splitOptions(tmpStr);
+      if (spec.length == 0)
+        throw new IllegalArgumentException("Invalid classifier specification string");
+      classname = spec[0];
+      spec[0]   = "";
+      setClassifier((Classifier) Utils.forName(Classifier.class, classname, spec));
+    }
+    else {
+      throw new Exception("No classifier (classname + options) provided!");
+    }
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() > 0) {
+      spec = Utils.splitOptions(tmpStr);
+      if (spec.length != 1)
+        throw new IllegalArgumentException("Invalid source code specification string");
+      classname = spec[0];
+      spec[0]   = "";
+      setSourceCode((Classifier) Utils.forName(Classifier.class, classname, spec));
+    }
+    else {
+      throw new Exception("No source code (classname) provided!");
+    }
+
+    tmpStr = Utils.getOption('t', options);
+    if (tmpStr.length() != 0)
+      setDataset(new File(tmpStr));
+    else
+      throw new Exception("No dataset provided!");
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0) {
+      if (tmpStr.equals("first"))
+        setClassIndex(0);
+      else if (tmpStr.equals("last"))
+        setClassIndex(-1);
+      else
+        setClassIndex(Integer.parseInt(tmpStr) - 1);
+    }
+    else {
+      setClassIndex(-1);
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>      result;
+
+    result  = new Vector<String>();
+
+    if (getClassifier() != null) {
+      result.add("-W");
+      result.add(getClassifier().getClass().getName() + " "
+          + Utils.joinOptions(((OptionHandler) getClassifier()).getOptions()));
+    }
+
+    if (getSourceCode() != null) {
+      result.add("-S");
+      result.add(getSourceCode().getClass().getName());
+    }
+
+    if (getDataset() != null) {
+      result.add("-t");
+      result.add(m_Dataset.getAbsolutePath());
+    }
+
+    result.add("-c");
+    if (getClassIndex() == -1)
+      result.add("last");
+    else if (getClassIndex() == 0)
+      result.add("first");
+    else
+      result.add("" + (getClassIndex() + 1));
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the classifier to use for the comparison.
+   *
+   * @param value       the classifier to use
+   */
+  public void setClassifier(Classifier value) {
+    m_Classifier = value;
+  }
+
+  /**
+   * Gets the classifier being used for the tests, can be null.
+   *
+   * @return            the currently set classifier
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Sets the class to test.
+   *
+   * @param value       the class to test
+   */
+  public void setSourceCode(Classifier value) {
+    m_SourceCode = value;
+  }
+
+  /**
+   * Gets the class to test.
+   *
+   * @return            the currently set class, can be null.
+   */
+  public Classifier getSourceCode() {
+    return m_SourceCode;
+  }
+
+  /**
+   * Sets the dataset to use for testing.
+   *
+   * @param value       the dataset to use.
+   */
+  public void setDataset(File value) {
+    if (!value.exists())
+      throw new IllegalArgumentException(
+          "Dataset '" + value.getAbsolutePath() + "' does not exist!");
+    else
+      m_Dataset = value;
+  }
+
+  /**
+   * Gets the dataset to use for testing, can be null.
+   *
+   * @return            the dataset to use.
+   */
+  public File getDataset() {
+    return m_Dataset;
+  }
+
+  /**
+   * Sets the class index of the dataset.
+   *
+   * @param value       the class index of the dataset.
+   */
+  public void setClassIndex(int value) {
+    m_ClassIndex = value;
+  }
+
+  /**
+   * Gets the class index of the dataset.
+   *
+   * @return            the current class index.
+   */
+  public int getClassIndex() {
+    return m_ClassIndex;
+  }
+
+  /**
+   * performs the comparison test
+   *
+   * @return            true if tests were successful
+   * @throws Exception  if tests fail
+   */
+  public boolean execute() throws Exception {
+    boolean     result;
+    Classifier  cls;
+    Classifier  code;
+    int         i;
+    Instances   data;
+    DataSource  source;
+    boolean     numeric;
+    boolean     different;
+    double      predClassifier;
+    double      predSource;
+
+    result = true;
+
+    // a few checks
+    if (getClassifier() == null)
+      throw new Exception("No classifier set!");
+    if (getSourceCode() == null)
+      throw new Exception("No source code set!");
+    if (getDataset() == null)
+      throw new Exception("No dataset set!");
+    if (!getDataset().exists())
+      throw new Exception(
+          "Dataset '" + getDataset().getAbsolutePath() + "' does not exist!");
+
+    // load data
+    source = new DataSource(getDataset().getAbsolutePath());
+    data   = source.getDataSet();
+    if (getClassIndex() == -1)
+      data.setClassIndex(data.numAttributes() - 1);
+    else
+      data.setClassIndex(getClassIndex());
+    numeric = data.classAttribute().isNumeric();
+
+    // build classifier
+    cls = AbstractClassifier.makeCopy(getClassifier());
+    cls.buildClassifier(data);
+
+    code = getSourceCode();
+
+    // compare predictions
+    for (i = 0; i < data.numInstances(); i++) {
+      // perform predictions
+      predClassifier = cls.classifyInstance(data.instance(i));
+      predSource     = code.classifyInstance(data.instance(i));
+
+      // compare both results
+      if (Double.isNaN(predClassifier) && Double.isNaN(predSource)) {
+        different = false;
+      }
+      else {
+        if (numeric)
+          different = !Utils.eq(predClassifier, predSource);
+        else
+          different = ((int) predClassifier != (int) predSource);
+      }
+
+      if (different) {
+        result = false;
+        if (numeric)
+          System.out.println(
+              (i+1) + ". instance (Classifier/Source code): "
+              + predClassifier + " != " + predSource);
+        else
+          System.out.println(
+              (i+1) + ". instance (Classifier/Source code): "
+              + data.classAttribute().value((int) predClassifier)
+              + " != " + data.classAttribute().value((int) predSource));
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+
+  /**
+   * Executes the tests, use "-h" to list the commandline options.
+   *
+   * @param args        the commandline parameters
+   * @throws Exception  if something goes wrong
+   */
+  public static void main(String[] args) throws Exception{
+    CheckSource         check;
+    StringBuffer        text;
+    Enumeration         enm;
+
+    check = new CheckSource();
+    if (Utils.getFlag('h', args)) {
+      text = new StringBuffer();
+      text.append("\nHelp requested:\n\n");
+      enm = check.listOptions();
+      while (enm.hasMoreElements()) {
+        Option option = (Option) enm.nextElement();
+        text.append(option.synopsis() + "\n");
+        text.append(option.description() + "\n");
+      }
+      System.out.println("\n" + text + "\n");
+    }
+    else {
+      check.setOptions(args);
+      if (check.execute())
+        System.out.println("Tests OK!");
+      else
+        System.out.println("Tests failed!");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/Classifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/Classifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/Classifier.java	(revision 29)
@@ -0,0 +1,105 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Classifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Classifier interface. All schemes for numeric or nominal prediction in
+ * Weka implement this interface. Note that a classifier MUST either implement
+ * distributionForInstance() or classifyInstance().
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public interface Classifier {
+
+  /**
+   * Generates a classifier. Must initialize all fields of the classifier
+   * that are not being set via options (ie. multiple calls of buildClassifier
+   * must always lead to the same result). Must not change the dataset
+   * in any way.
+   *
+   * @param data set of instances serving as training data
+   * @exception Exception if the classifier has not been
+   * generated successfully
+   */
+  public abstract void buildClassifier(Instances data) throws Exception;
+
+  /**
+   * Classifies the given test instance. The instance has to belong to a
+   * dataset when it's being classified. Note that a classifier MUST
+   * implement either this or distributionForInstance().
+   *
+   * @param instance the instance to be classified
+   * @return the predicted most likely class for the instance or
+   * Utils.missingValue() if no prediction is made
+   * @exception Exception if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception;
+
+  /**
+   * Predicts the class memberships for a given instance. If
+   * an instance is unclassified, the returned array elements
+   * must be all zero. If the class is numeric, the array
+   * must consist of only one element, which contains the
+   * predicted value. Note that a classifier MUST implement
+   * either this or classifyInstance().
+   *
+   * @param instance the instance to be classified
+   * @return an array containing the estimated membership
+   * probabilities of the test instance in each class
+   * or the numeric prediction
+   * @exception Exception if distribution could not be
+   * computed successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception;
+
+  /**
+   * Returns the Capabilities of this classifier. Maximally permissive
+   * capabilities are allowed by default. Derived classifiers should
+   * override this method and first disable all capabilities and then
+   * enable just those capabilities that make sense for the scheme.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities();
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/ConditionalDensityEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/ConditionalDensityEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/ConditionalDensityEstimator.java	(revision 29)
@@ -0,0 +1,46 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConditionalDensityEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Instance;
+
+/**
+ * Interface for numeric prediction schemes that can output conditional
+ * density estimates.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public interface ConditionalDensityEstimator {
+
+  /**
+   * Returns natural logarithm of density estimate for given value based on given instance.
+   *
+   * @param instance the instance to make the prediction for.
+   * @param value the value to make the prediction for.
+   * @return the natural logarithm of the density estimate
+   * @exception Exception if the density cannot be computed
+   */
+  public double logDensity(Instance instance, double value) throws Exception;
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/CostMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/CostMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/CostMatrix.java	(revision 29)
@@ -0,0 +1,805 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostMatrix.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.AttributeExpression;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Matrix;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.io.Writer;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+/**
+ * Class for storing and manipulating a misclassification cost matrix.
+ * The element at position i,j in the matrix is the penalty for classifying
+ * an instance of class j as class i. Cost values can be fixed or
+ * computed on a per-instance basis (cost sensitive evaluation only)
+ * from the value of an attribute or an expression involving
+ * attribute(s).
+ *
+ * @author Mark Hall
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public class CostMatrix implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1973792250544554965L;
+
+  private int m_size;
+
+  /** [rows][columns] */
+  protected Object [][] m_matrix;
+
+  /** The deafult file extension for cost matrix files */
+  public static String FILE_EXTENSION = ".cost";
+
+  /**
+   * Creates a default cost matrix of a particular size.
+   * All diagonal values will be 0 and all non-diagonal values 1.
+   *
+   * @param numOfClasses the number of classes that the cost matrix holds.
+   */
+  public CostMatrix(int numOfClasses) {
+    m_size = numOfClasses;
+    initialize();
+  }
+
+  /**
+   * Creates a cost matrix that is a copy of another.
+   *
+   * @param toCopy the matrix to copy.
+   */
+  public CostMatrix(CostMatrix toCopy) {
+    this(toCopy.size());
+
+    for (int i = 0; i < m_size; i++) {
+      for (int j = 0; j < m_size; j++) {
+        setCell(i, j, toCopy.getCell(i, j));
+      }
+    }
+  }
+
+  /**
+   * Initializes the matrix
+   */
+  public void initialize() {
+    m_matrix = new Object[m_size][m_size];
+    for (int i = 0; i < m_size; i++) {
+      for (int j = 0; j < m_size; j++) {
+        setCell(i, j, i == j ? new Double(0.0) : new Double(1.0));
+      }
+    }
+  }
+
+  /**
+   * The number of rows (and columns)
+   * @return the size of the matrix
+   */
+  public int size() {
+    return m_size;
+  }
+
+  /**
+   * Same as size
+   * @return the number of columns
+   */
+  public int numColumns() {
+    return size();
+  }
+
+  /**
+   * Same as size
+   * @return the number of rows
+   */
+  public int numRows() {
+    return size();
+  }
+
+  private boolean replaceStrings() throws Exception {
+    boolean nonDouble = false;
+
+    for (int i = 0; i < m_size; i++) {
+      for (int j = 0; j < m_size; j++) {
+        if (getCell(i, j) instanceof String) {
+          AttributeExpression temp = new AttributeExpression();
+          temp.convertInfixToPostfix((String)getCell(i, j));
+          setCell(i, j, temp);
+          nonDouble = true;
+        } else if (getCell(i, j) instanceof AttributeExpression) {
+          nonDouble = true;
+        }
+      }
+    }
+
+    return nonDouble;
+  }
+
+  /**
+   * Applies the cost matrix to a set of instances. If a random number generator is
+   * supplied the instances will be resampled, otherwise they will be rewighted.
+   * Adapted from code once sitting in Instances.java
+   *
+   * @param data the instances to reweight.
+   * @param random a random number generator for resampling, if null then instances are
+   * rewighted.
+   * @return a new dataset reflecting the cost of misclassification.
+   * @exception Exception if the data has no class or the matrix in inappropriate.
+   */
+  public Instances applyCostMatrix(Instances data, Random random)
+    throws Exception {
+
+    double sumOfWeightFactors = 0, sumOfMissClassWeights,
+      sumOfWeights;
+    double [] weightOfInstancesInClass, weightFactor, weightOfInstances;
+    Instances newData;
+
+    if (data.classIndex() < 0) {
+      throw new Exception("Class index is not set!");
+    }
+
+    if (size() != data.numClasses()) {
+      throw new Exception("Misclassification cost matrix has wrong format!");
+    }
+
+    // are there any non-fixed, per-instance costs defined in the matrix?
+    if (replaceStrings()) {
+      // could reweight in the two class case
+      if (data.classAttribute().numValues() > 2) {
+        throw new Exception("Can't resample/reweight instances using "
+                            +"non-fixed cost values when there are more "
+                            +"than two classes!");
+      } else {
+        // Store new weights
+        weightOfInstances = new double[data.numInstances()];
+        for (int i = 0; i < data.numInstances(); i++) {
+          Instance inst = data.instance(i);
+          int classValIndex = (int)inst.classValue();
+          double factor = 1.0;
+          Object element = (classValIndex == 0)
+            ? getCell(classValIndex, 1)
+            : getCell(classValIndex, 0);
+          if (element instanceof Double) {
+            factor = ((Double)element).doubleValue();
+          } else {
+            factor = ((AttributeExpression)element).evaluateExpression(inst);
+          }
+          weightOfInstances[i] = inst.weight() * factor;
+          /*          System.err.println("Multiplying " + inst.classAttribute().value((int)inst.classValue())
+                      +" by factor " + factor); */
+        }
+
+        // Change instances weight or do resampling
+        if (random != null) {
+          return data.resampleWithWeights(random, weightOfInstances);
+        } else {
+          Instances instances = new Instances(data);
+          for (int i = 0; i < data.numInstances(); i++) {
+            instances.instance(i).setWeight(weightOfInstances[i]);
+          }
+          return instances;
+        }
+      }
+    }
+
+    weightFactor = new double[data.numClasses()];
+    weightOfInstancesInClass = new double[data.numClasses()];
+    for (int j = 0; j < data.numInstances(); j++) {
+      weightOfInstancesInClass[(int)data.instance(j).classValue()] +=
+        data.instance(j).weight();
+    }
+    sumOfWeights = Utils.sum(weightOfInstancesInClass);
+
+    // normalize the matrix if not already
+    for (int i=0; i< m_size; i++) {
+      if (!Utils.eq(((Double)getCell(i, i)).doubleValue(), 0)) {
+        CostMatrix normMatrix = new CostMatrix(this);
+        normMatrix.normalize();
+        return normMatrix.applyCostMatrix(data, random);
+      }
+    }
+
+    for (int i = 0; i < data.numClasses(); i++) {
+      // Using Kai Ming Ting's formula for deriving weights for
+      // the classes and Breiman's heuristic for multiclass
+      // problems.
+
+      sumOfMissClassWeights = 0;
+      for (int j = 0; j < data.numClasses(); j++) {
+        if (Utils.sm(((Double)getCell(i,j)).doubleValue(),0)) {
+          throw new Exception("Neg. weights in misclassification "+
+              "cost matrix!");
+        }
+        sumOfMissClassWeights
+          += ((Double)getCell(i,j)).doubleValue();
+      }
+      weightFactor[i] = sumOfMissClassWeights * sumOfWeights;
+      sumOfWeightFactors += sumOfMissClassWeights *
+        weightOfInstancesInClass[i];
+    }
+    for (int i = 0; i < data.numClasses(); i++) {
+      weightFactor[i] /= sumOfWeightFactors;
+    }
+
+    // Store new weights
+    weightOfInstances = new double[data.numInstances()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      weightOfInstances[i] = data.instance(i).weight()*
+        weightFactor[(int)data.instance(i).classValue()];
+    }
+
+    // Change instances weight or do resampling
+    if (random != null) {
+      return data.resampleWithWeights(random, weightOfInstances);
+    } else {
+      Instances instances = new Instances(data);
+      for (int i = 0; i < data.numInstances(); i++) {
+        instances.instance(i).setWeight(weightOfInstances[i]);
+      }
+      return instances;
+    }
+  }
+
+  /**
+   * Calculates the expected misclassification cost for each possible class value,
+   * given class probability estimates.
+   *
+   * @param classProbs the class probability estimates.
+   * @return the expected costs.
+   * @exception Exception if the wrong number of class probabilities is supplied.
+   */
+  public double[] expectedCosts(double[] classProbs) throws Exception {
+
+    if (classProbs.length != m_size) {
+      throw new Exception("Length of probability estimates don't "
+                          +"match cost matrix");
+    }
+
+    double[] costs = new double[m_size];
+
+    for (int x = 0; x < m_size; x++) {
+      for (int y = 0; y < m_size; y++) {
+        Object element = getCell(y, x);
+        if (!(element instanceof Double)) {
+          throw new Exception("Can't use non-fixed costs in "
+                              +"computing expected costs.");
+        }
+        costs[x] += classProbs[y] * ((Double)element).doubleValue();
+      }
+    }
+
+    return costs;
+  }
+
+  /**
+   * Calculates the expected misclassification cost for each possible class value,
+   * given class probability estimates.
+   *
+   * @param classProbs the class probability estimates.
+   * @param inst the current instance for which the class probabilites
+   * apply. Is used for computing any non-fixed cost values.
+   * @return the expected costs.
+   * @exception Exception if something goes wrong
+   */
+  public double[] expectedCosts(double [] classProbs,
+                                Instance inst) throws Exception {
+
+    if (classProbs.length != m_size) {
+      throw new Exception("Length of probability estimates don't "
+                          +"match cost matrix");
+    }
+
+    if (!replaceStrings()) {
+      return expectedCosts(classProbs);
+    }
+
+    double[] costs = new double[m_size];
+
+    for (int x = 0; x < m_size; x++) {
+      for (int y = 0; y < m_size; y++) {
+        Object element = getCell(y, x);
+        double costVal;
+        if (!(element instanceof Double)) {
+          costVal =
+            ((AttributeExpression)element).evaluateExpression(inst);
+        } else {
+          costVal = ((Double)element).doubleValue();
+        }
+        costs[x] += classProbs[y] * costVal;
+      }
+    }
+
+    return costs;
+  }
+
+  /**
+   * Gets the maximum cost for a particular class value.
+   *
+   * @param classVal the class value.
+   * @return the maximum cost.
+   * @exception Exception if cost matrix contains non-fixed
+   * costs
+   */
+  public double getMaxCost(int classVal) throws Exception {
+
+    double maxCost = Double.NEGATIVE_INFINITY;
+
+    for (int i = 0; i < m_size; i++) {
+      Object element = getCell(classVal, i);
+      if (!(element instanceof Double)) {
+          throw new Exception("Can't use non-fixed costs when "
+                              +"getting max cost.");
+      }
+      double cost = ((Double)element).doubleValue();
+      if (cost > maxCost) maxCost = cost;
+    }
+
+    return maxCost;
+  }
+
+  /**
+   * Gets the maximum cost for a particular class value.
+   *
+   * @param classVal the class value.
+   * @return the maximum cost.
+   * @exception Exception if cost matrix contains non-fixed
+   * costs
+   */
+  public double getMaxCost(int classVal, Instance inst)
+    throws Exception {
+
+    if (!replaceStrings()) {
+      return getMaxCost(classVal);
+    }
+
+    double maxCost = Double.NEGATIVE_INFINITY;
+    double cost;
+    for (int i = 0; i < m_size; i++) {
+      Object element = getCell(classVal, i);
+      if (!(element instanceof Double)) {
+        cost =
+          ((AttributeExpression)element).evaluateExpression(inst);
+      } else {
+        cost = ((Double)element).doubleValue();
+      }
+      if (cost > maxCost) maxCost = cost;
+    }
+
+    return maxCost;
+  }
+
+
+  /**
+   * Normalizes the matrix so that the diagonal contains zeros.
+   *
+   */
+  public void normalize() {
+
+    for (int y=0; y<m_size; y++) {
+      double diag = ((Double)getCell(y, y)).doubleValue();
+      for (int x=0; x<m_size; x++) {
+        setCell(x, y, new Double(((Double)getCell(x, y)).
+                                    doubleValue() - diag));
+      }
+    }
+  }
+
+  /**
+   * Loads a cost matrix in the old format from a reader. Adapted from code once sitting
+   * in Instances.java
+   *
+   * @param reader the reader to get the values from.
+   * @exception Exception if the matrix cannot be read correctly.
+   */
+  public void readOldFormat(Reader reader) throws Exception {
+
+    StreamTokenizer tokenizer;
+    int currentToken;
+    double firstIndex, secondIndex, weight;
+
+    tokenizer = new StreamTokenizer(reader);
+
+    initialize();
+
+    tokenizer.commentChar('%');
+    tokenizer.eolIsSignificant(true);
+    while (StreamTokenizer.TT_EOF != (currentToken = tokenizer.nextToken())) {
+
+      // Skip empty lines
+      if (currentToken == StreamTokenizer.TT_EOL) {
+        continue;
+      }
+
+      // Get index of first class.
+      if (currentToken != StreamTokenizer.TT_NUMBER) {
+        throw new Exception("Only numbers and comments allowed "+
+            "in cost file!");
+      }
+      firstIndex = tokenizer.nval;
+      if (!Utils.eq((double)(int)firstIndex,firstIndex)) {
+        throw new Exception("First number in line has to be "+
+            "index of a class!");
+      }
+      if ((int)firstIndex >= size()) {
+        throw new Exception("Class index out of range!");
+      }
+
+      // Get index of second class.
+      if (StreamTokenizer.TT_EOF == (currentToken = tokenizer.nextToken())) {
+        throw new Exception("Premature end of file!");
+      }
+      if (currentToken == StreamTokenizer.TT_EOL) {
+        throw new Exception("Premature end of line!");
+      }
+      if (currentToken != StreamTokenizer.TT_NUMBER) {
+        throw new Exception("Only numbers and comments allowed "+
+            "in cost file!");
+      }
+      secondIndex = tokenizer.nval;
+      if (!Utils.eq((double)(int)secondIndex,secondIndex)) {
+        throw new Exception("Second number in line has to be "+
+            "index of a class!");
+      }
+      if ((int)secondIndex >= size()) {
+        throw new Exception("Class index out of range!");
+      }
+      if ((int)secondIndex == (int)firstIndex) {
+        throw new Exception("Diagonal of cost matrix non-zero!");
+      }
+
+      // Get cost factor.
+      if (StreamTokenizer.TT_EOF == (currentToken = tokenizer.nextToken())) {
+        throw new Exception("Premature end of file!");
+      }
+      if (currentToken == StreamTokenizer.TT_EOL) {
+        throw new Exception("Premature end of line!");
+      }
+      if (currentToken != StreamTokenizer.TT_NUMBER) {
+        throw new Exception("Only numbers and comments allowed "+
+            "in cost file!");
+      }
+      weight = tokenizer.nval;
+      if (!Utils.gr(weight,0)) {
+        throw new Exception("Only positive weights allowed!");
+      }
+      setCell((int)firstIndex, (int)secondIndex,
+          new Double(weight));
+    }
+  }
+
+  /**
+   * Reads a matrix from a reader. The first line in the file should
+   * contain the number of rows and columns. Subsequent lines
+   * contain elements of the matrix.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     reader the reader containing the matrix
+   * @throws    Exception if an error occurs
+   * @see       #write(Writer)
+   */
+  public CostMatrix(Reader reader) throws Exception {
+    LineNumberReader lnr = new LineNumberReader(reader);
+    String line;
+    int currentRow = -1;
+
+    while ((line = lnr.readLine()) != null) {
+
+      // Comments
+      if (line.startsWith("%")) {
+        continue;
+      }
+
+      StringTokenizer st = new StringTokenizer(line);
+      // Ignore blank lines
+      if (!st.hasMoreTokens()) {
+        continue;
+      }
+
+      if (currentRow < 0) {
+        int rows = Integer.parseInt(st.nextToken());
+        if (!st.hasMoreTokens()) {
+          throw new Exception("Line " + lnr.getLineNumber()
+              + ": expected number of columns");
+        }
+
+        int cols = Integer.parseInt(st.nextToken());
+        if (rows != cols) {
+          throw new Exception("Trying to create a non-square cost "
+                              +"matrix");
+        }
+        //        m_matrix = new Object[rows][cols];
+        m_size = rows;
+        initialize();
+        currentRow++;
+        continue;
+
+      } else {
+        if (currentRow == m_size) {
+          throw new Exception("Line " + lnr.getLineNumber()
+              + ": too many rows provided");
+        }
+
+        for (int i = 0; i < m_size; i++) {
+          if (!st.hasMoreTokens()) {
+            throw new Exception("Line " + lnr.getLineNumber()
+                + ": too few matrix elements provided");
+          }
+
+          String nextTok = st.nextToken();
+          // try to parse as a double first
+          Double val = null;
+          try {
+            val = new Double(nextTok);
+            double value = val.doubleValue();
+          } catch (Exception ex) {
+            val = null;
+          }
+          if (val == null) {
+            setCell(currentRow, i, nextTok);
+          } else {
+            setCell(currentRow, i, val);
+          }
+        }
+        currentRow++;
+      }
+    }
+
+    if (currentRow == -1) {
+      throw new Exception("Line " + lnr.getLineNumber()
+                          + ": expected number of rows");
+    } else if (currentRow != m_size) {
+      throw new Exception("Line " + lnr.getLineNumber()
+                          + ": too few rows provided");
+    }
+  }
+
+  /**
+   * Writes out a matrix. The format can be read via the
+   * CostMatrix(Reader) constructor.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     w the output Writer
+   * @throws    Exception if an error occurs
+   */
+  public void write(Writer w) throws Exception {
+    w.write("% Rows\tColumns\n");
+    w.write("" + m_size + "\t" + m_size + "\n");
+    w.write("% Matrix elements\n");
+    for(int i = 0; i < m_size; i++) {
+      for(int j = 0; j < m_size; j++) {
+        w.write("" + getCell(i, j) + "\t");
+      }
+      w.write("\n");
+    }
+    w.flush();
+  }
+
+  /**
+   * converts the Matrix into a single line Matlab string: matrix is enclosed
+   * by parentheses, rows are separated by semicolon and single cells by
+   * blanks, e.g., [1 2; 3 4].
+   * @return      the matrix in Matlab single line format
+   */
+  public String toMatlab() {
+    StringBuffer      result;
+    int               i;
+    int               n;
+
+    result = new StringBuffer();
+
+    result.append("[");
+
+    for (i = 0; i < m_size; i++) {
+      if (i > 0) {
+        result.append("; ");
+      }
+
+      for (n = 0; n < m_size; n++) {
+        if (n > 0) {
+          result.append(" ");
+        }
+        result.append(getCell(i, n));
+      }
+    }
+
+    result.append("]");
+
+    return result.toString();
+  }
+
+  /**
+   * Set the value of a particular cell in the matrix
+   *
+   * @param rowIndex the row
+   * @param columnIndex the column
+   * @param value the value to set
+   */
+  public final void setCell(int rowIndex, int columnIndex,
+                               Object value) {
+    m_matrix[rowIndex][columnIndex] = value;
+  }
+
+  /**
+   * Return the contents of a particular cell. Note: this
+   * method returns the Object stored at a particular cell.
+   *
+   * @param rowIndex the row
+   * @param columnIndex the column
+   * @return the value at the cell
+   */
+  public final Object getCell(int rowIndex, int columnIndex) {
+    return m_matrix[rowIndex][columnIndex];
+  }
+
+  /**
+   * Return the value of a cell as a double (for legacy code)
+   *
+   * @param rowIndex the row
+   * @param columnIndex the column
+   * @return the value at a particular cell as a double
+   * @exception Exception if the value is not a double
+   */
+  public final double getElement(int rowIndex, int columnIndex)
+    throws Exception {
+    if (!(m_matrix[rowIndex][columnIndex] instanceof Double)) {
+      throw new Exception("Cost matrix contains non-fixed costs!");
+    }
+    return ((Double)m_matrix[rowIndex][columnIndex]).doubleValue();
+  }
+
+  /**
+   * Return the value of a cell as a double. Computes the
+   * value for non-fixed costs using the supplied Instance
+   *
+   * @param rowIndex the row
+   * @param columnIndex the column
+   * @return the value from a particular cell
+   * @exception Exception if something goes wrong
+   */
+  public final double getElement(int rowIndex, int columnIndex,
+                                 Instance inst) throws Exception {
+
+    if (m_matrix[rowIndex][columnIndex] instanceof Double) {
+      return ((Double)m_matrix[rowIndex][columnIndex]).doubleValue();
+    } else if (m_matrix[rowIndex][columnIndex] instanceof String) {
+      replaceStrings();
+    }
+
+    return ((AttributeExpression)m_matrix[rowIndex][columnIndex]).
+      evaluateExpression(inst);
+  }
+
+  /**
+   * Set the value of a cell as a double
+   *
+   * @param rowIndex the row
+   * @param columnIndex the column
+   * @param value the value (double) to set
+   */
+  public final void setElement(int rowIndex, int columnIndex,
+                               double value) {
+    m_matrix[rowIndex][columnIndex] = new Double(value);
+  }
+
+  /**
+   * creates a matrix from the given Matlab string.
+   * @param matlab  the matrix in matlab format
+   * @return        the matrix represented by the given string
+   */
+  public static Matrix parseMatlab(String matlab) throws Exception {
+    return Matrix.parseMatlab(matlab);
+  }
+
+  /**
+   * Converts a matrix to a string.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @return    the converted string
+   */
+  public String toString() {
+    // Determine the width required for the maximum element,
+    // and check for fractional display requirement.
+    double maxval = 0;
+    boolean fractional = false;
+    Object element = null;
+    int widthNumber = 0;
+    int widthExpression = 0;
+    for (int i = 0; i < size(); i++) {
+      for (int j = 0; j < size(); j++) {
+        element = getCell(i, j);
+        if (element instanceof Double) {
+          double current = ((Double)element).doubleValue();
+
+          if (current < 0)
+            current *= -11;
+          if (current > maxval)
+            maxval = current;
+          double fract = Math.abs(current - Math.rint(current));
+          if (!fractional
+              && ((Math.log(fract) / Math.log(10)) >= -2)) {
+            fractional = true;
+          }
+        } else {
+          if (element.toString().length() > widthExpression) {
+            widthExpression = element.toString().length();
+          }
+        }
+      }
+    }
+    if (maxval > 0) {
+      widthNumber = (int)(Math.log(maxval) / Math.log(10)
+                          + (fractional ? 4 : 1));
+    }
+
+    int width = (widthNumber > widthExpression)
+      ? widthNumber
+      : widthExpression;
+
+    StringBuffer text = new StringBuffer();
+    for (int i = 0; i < size(); i++) {
+      for (int j = 0; j < size(); j++) {
+        element = getCell(i, j);
+        if (element instanceof Double) {
+          text.append(" ").
+            append(Utils.doubleToString(((Double)element).
+                                        doubleValue(),
+                                        width, (fractional ? 2 : 0)));
+        } else {
+          int diff = width - element.toString().length();
+          if (diff > 0) {
+            int left = diff % 2;
+            left += diff / 2;
+            String temp = Utils.padLeft(element.toString(),
+                            element.toString().length()+left);
+            temp = Utils.padRight(temp, width);
+            text.append(" ").append(temp);
+          } else {
+            text.append(" ").
+              append(element.toString());
+          }
+        }
+      }
+      text.append("\n");
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibrary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibrary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibrary.java	(revision 29)
@@ -0,0 +1,392 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleLibrary.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.gui.ensembleLibraryEditor.LibrarySerialization;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+
+/**
+ * This class represents a library of classifiers.  This class
+ * follows the factory design pattern of creating LibraryModels
+ * when asked.  It also has the methods necessary for saving
+ * and loading models from lists.
+ *
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 6041 $
+ */
+public class EnsembleLibrary
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7987178904923706760L;
+
+  /** The default file extension for model list files */
+  public static final String XML_FILE_EXTENSION = ".model.xml";
+
+  /** The flat file extension for model list files */
+  public static final String FLAT_FILE_EXTENSION = ".mlf";
+
+  /** the set of classifiers that constitute the library */
+  public TreeSet m_Models;
+
+  /** A helper class for notifying listeners when the library changes */
+  private transient PropertyChangeSupport m_LibraryPropertySupport = new PropertyChangeSupport(this);
+
+  /**
+   * Constructor is responsible for initializing the data
+   * structure hoilding all of the models
+   *
+   */
+  public EnsembleLibrary() {
+
+    m_Models = new TreeSet(new EnsembleLibraryModelComparator());
+  }
+
+  /**
+   * Returns the number of models in the ensemble library
+   *
+   * @return	the number of models
+   */
+  public int size() {
+    if (m_Models != null)
+      return m_Models.size();
+    else
+      return 0;
+  }
+
+  /**
+   * adds a LibraryModel to the Library
+   *
+   * @param model	the model to add
+   */
+  public void addModel(EnsembleLibraryModel model) {
+    m_Models.add(model);
+    if (m_LibraryPropertySupport != null)
+      m_LibraryPropertySupport.firePropertyChange(null, null, null);
+  }
+
+  /**
+   * adds a LibraryModel to the Library
+   *
+   * @param modelString	the model to add
+   */
+  public void addModel(String modelString) {
+    m_Models.add(createModel(modelString));
+    m_LibraryPropertySupport.firePropertyChange(null, null, null);
+  }
+
+  /**
+   * removes a LibraryModel from the Library
+   *
+   * @param model	the model to remove
+   */
+  public void removeModel(EnsembleLibraryModel model) {
+    m_Models.remove(model);
+    m_LibraryPropertySupport.firePropertyChange(null, null, null);
+  }
+
+  /**
+   * creates a LibraryModel from a string representing the command
+   * line invocation
+   *
+   * @param classifier	the classifier to create a model from
+   * @return		the generated model
+   */
+  public EnsembleLibraryModel createModel(Classifier classifier) {
+    EnsembleLibraryModel model = new EnsembleLibraryModel(classifier);
+
+    return model;
+  }
+
+  /**
+   * This method takes a String argument defining a classifier and
+   * uses it to create a base Classifier.
+   *
+   * @param modelString	the classifier string
+   * @return		the generated model
+   */
+  public EnsembleLibraryModel createModel(String modelString) {
+
+    String[] splitString = modelString.split("\\s+");
+    String className = splitString[0];
+
+    String argString = modelString.replaceAll(splitString[0], "");
+    String[] optionStrings = argString.split("\\s+");
+
+    EnsembleLibraryModel model = null;
+    try {
+      model = new EnsembleLibraryModel(AbstractClassifier.forName(className,
+            optionStrings));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    return model;
+  }
+
+  /**
+   * getter for the set of models in this library
+   *
+   * @return		the current models
+   */
+  public TreeSet getModels() {
+    return m_Models;
+  }
+
+  /**
+   * setter for the set of models in this library
+   *
+   * @param models	the models to use
+   */
+  public void setModels(TreeSet models) {
+    m_Models = models;
+    m_LibraryPropertySupport.firePropertyChange(null, null, null);
+  }
+
+  /**
+   * removes all models from the current library
+   */
+  public void clearModels() {
+    m_Models.clear();
+    m_LibraryPropertySupport.firePropertyChange(null, null, null);
+  }
+
+  /**
+   * Loads and returns a library from the specified file
+   *
+   * @param selectedFile	the file to load from
+   * @param dialogParent	the parent component
+   * @param library		will contain the data after loading
+   */
+  public static void loadLibrary(File selectedFile, JComponent dialogParent,
+      EnsembleLibrary library) {
+
+    try {
+      loadLibrary(selectedFile, library);
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(dialogParent, "Error reading file '"
+          + selectedFile.getName() + "':\n" + ex.getMessage(),
+          "Load failed", JOptionPane.ERROR_MESSAGE);
+      System.err.println(ex.getMessage());
+    }
+  }
+
+  /**
+   * This method takes a model list file and a library object as arguments and
+   * Instantiates all of the models in the library list file.  It is assumed
+   * that the passed library was an associated working directory and can take
+   * care of creating the model objects itself.
+   *
+   * @param selectedFile	the file to load
+   * @param library		the library
+   * @throws Exception		if something goes wrong
+   */
+  public static void loadLibrary(File selectedFile, EnsembleLibrary library)
+    throws Exception {
+
+    //decide what type of model file list we are dealing with and
+    //then load accordingly
+
+    //deal with XML extension for xml files
+    if (selectedFile.getName().toLowerCase().endsWith(
+          EnsembleLibrary.XML_FILE_EXTENSION)) {
+
+      LibrarySerialization librarySerialization;
+
+      Vector classifiers = null;
+
+      try {
+        librarySerialization = new LibrarySerialization();
+        classifiers = (Vector) librarySerialization.read(selectedFile.getPath());
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+
+      //library.setClassifiers(classifiers);
+
+      for (Iterator it = classifiers.iterator(); it.hasNext();) {
+
+        EnsembleLibraryModel model = library.createModel((Classifier) it.next());
+        model.testOptions();
+        library.addModel(model);
+      }
+
+      //deal with MLF extesion for flat files
+    } else if (selectedFile.getName().toLowerCase().endsWith(
+          EnsembleLibrary.FLAT_FILE_EXTENSION)) {
+
+      BufferedReader reader = null;
+
+      reader = new BufferedReader(new FileReader(selectedFile));
+
+      String modelString;
+
+      while ((modelString = reader.readLine()) != null) {
+
+        EnsembleLibraryModel model = library.createModel(modelString);
+
+        if (model != null) {
+          model.testOptions();
+          library.addModel(model);
+        } else {
+          System.err.println("Failed to create model: " + modelString);
+        }
+      }
+
+      reader.close();
+    }
+  }
+
+  /**
+   * This method takes an XML input stream and a library object as arguments
+   * and Instantiates all of the models in the stream.  It is assumed
+   * that the passed library was an associated working directory and can take
+   * care of creating the model objects itself.
+   *
+   * @param stream		the XML stream to load
+   * @param library		the library
+   * @throws Exception		if something goes wrong
+   */
+  public static void loadLibrary(InputStream stream, EnsembleLibrary library)
+    throws Exception {
+
+    Vector classifiers = null;
+
+    try {
+      LibrarySerialization librarySerialization = new LibrarySerialization();
+      classifiers = (Vector) librarySerialization.read(stream);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    for (int i = 0; i < classifiers.size(); i++) {
+      EnsembleLibraryModel model = library.createModel((Classifier) classifiers.get(i));
+      model.testOptions();
+      library.addModel(model);
+    }
+  }
+
+
+  /**
+   * Saves the given library in the specified file.  This saves only
+   * the specification of the models as a model list.
+   *
+   * @param selectedFile	the file to save to
+   * @param library		the library to save
+   * @param dialogParent	the component parent
+   */
+  public static void saveLibrary(File selectedFile, EnsembleLibrary library,
+      JComponent dialogParent) {
+
+    //save decide what type of model file list we are dealing with and
+    //then save accordingly
+
+    //System.out.println("writing to file: "+selectedFile.getPath());
+
+    //deal with XML extension for xml files
+    if (selectedFile.getName().toLowerCase().endsWith(
+          EnsembleLibrary.XML_FILE_EXTENSION)) {
+
+      LibrarySerialization librarySerialization;
+
+      Vector classifiers = new Vector();
+
+      for (Iterator it = library.getModels().iterator(); it.hasNext();) {
+        EnsembleLibraryModel model = (EnsembleLibraryModel) it.next();
+        classifiers.add(model.getClassifier());
+      }
+
+      try {
+        librarySerialization = new LibrarySerialization();
+        librarySerialization.write(selectedFile.getPath(), classifiers);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+
+      //deal with MLF extesion for flat files
+    } else if (selectedFile.getName().toLowerCase().endsWith(
+          EnsembleLibrary.FLAT_FILE_EXTENSION)) {
+
+      Writer writer = null;
+      try {
+        writer = new BufferedWriter(new FileWriter(selectedFile));
+
+        Iterator it = library.getModels().iterator();
+
+        while (it.hasNext()) {
+          EnsembleLibraryModel model = (EnsembleLibraryModel) it.next();
+          writer.write(model.getStringRepresentation() + "\n");
+        }
+
+        writer.close();
+      } catch (Exception ex) {
+        JOptionPane.showMessageDialog(dialogParent,
+            "Error writing file '" + selectedFile.getName()
+            + "':\n" + ex.getMessage(), "Save failed",
+            JOptionPane.ERROR_MESSAGE);
+        System.err.println(ex.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Adds an object to the list of those that wish to be informed when the
+   * library changes.
+   *
+   * @param listener a new listener to add to the list
+   */
+  public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+    if (m_LibraryPropertySupport != null) {
+      m_LibraryPropertySupport.addPropertyChangeListener(listener);
+
+    }
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModel.java	(revision 29)
@@ -0,0 +1,401 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleLibraryModel.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.gui.GenericObjectEditor;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * This class is a light wrapper that is meant to represent a
+ * classifier in a classifier library.  This simple base class
+ * is intended only to store the class type and the parameters
+ * for a specific "type" of classifier.  You will need to
+ * extend this class to add functionality specific to models
+ * that have been trained on data (as we have for Ensemble Selection)
+ *
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 6041 $
+ */
+public class EnsembleLibraryModel
+  implements Serializable, RevisionHandler {
+
+  /** this stores the class of the classifier */
+  //private Class m_ModelClass;
+
+  /**
+   * This is the serialVersionUID that SHOULD stay the same
+   * so that future modified versions of this class will be
+   * backwards compatible with older model versions.
+   */
+  private static final long serialVersionUID = 7932816660173443200L;
+
+  /** this is an array of options*/
+  protected Classifier m_Classifier;
+
+  /** the description of this classifier*/
+  protected String m_DescriptionText;
+
+  /** this is also saved separately */
+  protected String m_ErrorText;
+
+  /** a boolean variable tracking whether or not this classifier was
+   * able to be built successfully with the given set of options*/
+  protected boolean m_OptionsWereValid;
+
+  /** this is stores redundantly to speed up the many operations that
+   frequently need to get the model string representations (like JList
+   renderers) */
+  protected String m_StringRepresentation;
+
+  /**
+   * Default Constructor
+   */
+  public EnsembleLibraryModel() {
+    super();
+  }
+
+  /**
+   * Initializes the class with the given classifier.
+   *
+   * @param classifier	the classifier to use
+   */
+  public EnsembleLibraryModel(Classifier classifier) {
+
+    m_Classifier = classifier;
+
+    //this may seem redundant and stupid, but it really is a good idea
+    //to cache the stringRepresentation to minimize the amount of work
+    //that needs to be done when these Models are rendered
+    m_StringRepresentation = toString();
+
+    updateDescriptionText();
+  }
+
+  /**
+   * This method will attempt to instantiate this classifier with the given
+   * options. If an exception is thrown from the setOptions method of the
+   * classifier then the resulting exception text will be saved in the
+   * description text string.
+   */
+  public void testOptions() {
+
+    Classifier testClassifier = null;
+    try {
+      testClassifier = ((Classifier) m_Classifier.getClass().newInstance());
+    } catch (InstantiationException e) {
+      e.printStackTrace();
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    }
+
+    setOptionsWereValid(true);
+    setErrorText(null);
+    updateDescriptionText();
+
+    try {
+      ((OptionHandler)testClassifier).setOptions(((OptionHandler)m_Classifier).getOptions());
+    } catch (Exception e) {
+
+      setOptionsWereValid(false);
+      setErrorText(e.getMessage());
+    }
+
+    updateDescriptionText();
+  }
+
+  /**
+   * Returns the base classifier this library model represents.
+   *
+   * @return		the base classifier
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * getter for the string representation
+   *
+   * @return		the string representation
+   */
+  public String getStringRepresentation() {
+    return m_StringRepresentation;
+  }
+
+  /**
+   * setter for the description text
+   *
+   * @param descriptionText	the description
+   */
+  public void setDescriptionText(String descriptionText) {
+    m_DescriptionText = descriptionText;
+  }
+
+  /**
+   * getter for the string representation
+   *
+   * @return		the description
+   */
+  public String getDescriptionText() {
+    return m_DescriptionText;
+  }
+
+  /**
+   * setter for the error text
+   *
+   * @param errorText	the error text
+   */
+  public void setErrorText(String errorText) {
+    this.m_ErrorText = errorText;
+  }
+
+  /**
+   * getter for the error text
+   *
+   * @return		the error text
+   */
+  public String getErrorText() {
+    return m_ErrorText;
+  }
+
+  /**
+   * setter for the optionsWereValid member variable
+   *
+   * @param optionsWereValid	if true, the options were valid
+   */
+  public void setOptionsWereValid(boolean optionsWereValid) {
+    this.m_OptionsWereValid = optionsWereValid;
+  }
+
+  /**
+   * getter for the optionsWereValid member variable
+   *
+   * @return		true if the options were valid
+   */
+  public boolean getOptionsWereValid() {
+    return m_OptionsWereValid;
+  }
+
+  /**
+   * This method converts the current set of arguments and the
+   * class name to a string value representing the command line
+   * invocation
+   *
+   * @return		a string representation of classname and options
+   */
+  public String toString() {
+
+    String str = m_Classifier.getClass().getName();
+
+    str += " " + Utils.joinOptions(((OptionHandler) m_Classifier).getOptions());
+
+    return str;
+
+  }
+
+  /**
+   * getter for the modelClass
+   *
+   * @return		the model class
+   */
+  public Class getModelClass() {
+    return m_Classifier.getClass();
+  }
+
+  /**
+   * getter for the classifier options
+   *
+   * @return		the classifier options
+   */
+  public String[] getOptions() {
+    return ((OptionHandler)m_Classifier).getOptions();
+  }
+
+  /**
+   * Custom serialization method
+   *
+   * @param stream		the stream to write the object to
+   * @throws IOException	if something goes wrong IO-wise
+   */
+  private void writeObject(ObjectOutputStream stream) throws IOException {
+    //stream.defaultWriteObject();
+    //stream.writeObject(b);
+
+    //serialize the LibraryModel fields
+
+    stream.writeObject(m_Classifier);
+
+    stream.writeObject(m_DescriptionText);
+
+    stream.writeObject(m_ErrorText);
+
+    stream.writeObject(new Boolean(m_OptionsWereValid));
+
+    stream.writeObject(m_StringRepresentation);
+  }
+
+  /**
+   * Custom serialization method
+   *
+   * @param stream			the stream to write to
+   * @throws IOException		if something goes wrong IO-wise
+   * @throws ClassNotFoundException	if class couldn't be found
+   */
+  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+
+    m_Classifier = (Classifier) stream.readObject();
+
+    m_DescriptionText = (String) stream.readObject();
+
+    m_ErrorText = (String) stream.readObject();
+
+    m_OptionsWereValid = ((Boolean) stream.readObject()).booleanValue();
+
+    m_StringRepresentation = (String) stream.readObject();
+  }
+
+  /**
+   * This method loops through all of the properties of a classifier to
+   * build the html toolTipText that will show all of the property values
+   * for this model.
+   *
+   * Note that this code is copied+adapted from the PropertySheetPanel
+   * class.
+   */
+  public void updateDescriptionText() {
+
+    String toolTipText = new String("<html>");
+
+    if (!m_OptionsWereValid) {
+
+      toolTipText += "<font COLOR=\"#FF0000\">"
+        + "<b>Invalid Model:</b><br>" + m_ErrorText + "<br></font>";
+    }
+
+    toolTipText += "<TABLE>";
+
+    PropertyDescriptor properties[] = null;
+    MethodDescriptor methods[] = null;
+
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(m_Classifier.getClass());
+      properties = bi.getPropertyDescriptors();
+      methods = bi.getMethodDescriptors();
+    } catch (IntrospectionException ex) {
+      System.err.println("LibraryModel: Couldn't introspect");
+      return;
+    }
+
+    for (int i = 0; i < properties.length; i++) {
+
+      if (properties[i].isHidden() || properties[i].isExpert()) {
+        continue;
+      }
+
+      String name = properties[i].getDisplayName();
+      Class type = properties[i].getPropertyType();
+      Method getter = properties[i].getReadMethod();
+      Method setter = properties[i].getWriteMethod();
+
+      // Only display read/write properties.
+      if (getter == null || setter == null) {
+        continue;
+      }
+
+      try {
+        Object args[] = {};
+        Object value = getter.invoke(m_Classifier, args);
+
+        PropertyEditor editor = null;
+        Class pec = properties[i].getPropertyEditorClass();
+        if (pec != null) {
+          try {
+            editor = (PropertyEditor) pec.newInstance();
+          } catch (Exception ex) {
+            // Drop through.
+          }
+        }
+        if (editor == null) {
+          editor = PropertyEditorManager.findEditor(type);
+        }
+        //editors[i] = editor;
+
+        // If we can't edit this component, skip it.
+        if (editor == null) {
+          continue;
+        }
+        if (editor instanceof GenericObjectEditor) {
+          ((GenericObjectEditor) editor).setClassType(type);
+        }
+
+        // Don't try to set null values:
+        if (value == null) {
+          continue;
+        }
+
+        toolTipText += "<TR><TD>" + name + "</TD><TD>"
+          + value.toString() + "</TD></TR>";
+
+      } catch (InvocationTargetException ex) {
+        System.err.println("Skipping property " + name
+            + " ; exception on target: " + ex.getTargetException());
+        ex.getTargetException().printStackTrace();
+        continue;
+      } catch (Exception ex) {
+        System.err.println("Skipping property " + name
+            + " ; exception: " + ex);
+        ex.printStackTrace();
+        continue;
+      }
+    }
+
+    toolTipText += "</TABLE>";
+    toolTipText += "</html>";
+    m_DescriptionText = toolTipText;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModelComparator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModelComparator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/EnsembleLibraryModelComparator.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleLibraryModelComparator.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * This class is a Comparator for the LibraryModel class. It basically ranks
+ * them alphabetically based on their String Representations
+ *
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 6041 $
+ */
+public final class EnsembleLibraryModelComparator
+  implements Comparator, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6522464740036141188L;
+
+  /**
+   * Compares its two arguments for order.
+   *
+   * @param o1		first object
+   * @param o2		second object
+   * @return		a negative integer, zero, or a positive integer as the
+   * 			first argument is less than, equal to, or greater than
+   * 			the second.
+   */
+  public int compare(Object o1, Object o2) {
+
+    int comparison = 0;
+
+    if (o1 instanceof EnsembleLibraryModel
+        && o2 instanceof EnsembleLibraryModel) {
+
+      comparison = ((String) ((EnsembleLibraryModel) o1)
+          .getStringRepresentation())
+        .compareTo(((String) ((EnsembleLibraryModel) o2)
+              .getStringRepresentation()));
+
+    }
+
+    return comparison;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/Evaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/Evaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/Evaluation.java	(revision 29)
@@ -0,0 +1,3641 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Evaluation.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.classifiers.evaluation.NominalPrediction;
+import weka.classifiers.evaluation.NumericPrediction;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.classifiers.evaluation.output.prediction.AbstractOutput;
+import weka.classifiers.evaluation.output.prediction.PlainText;
+import weka.classifiers.pmml.consumer.PMMLClassifier;
+import weka.classifiers.xml.XMLClassifier;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.converters.ConverterUtils.DataSink;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.core.pmml.PMMLFactory;
+import weka.core.pmml.PMMLModel;
+import weka.core.xml.KOML;
+import weka.core.xml.XMLOptions;
+import weka.core.xml.XMLSerialization;
+import weka.estimators.UnivariateKernelEstimator;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Class for evaluating machine learning models. <p/>
+ *
+ * ------------------------------------------------------------------- <p/>
+ *
+ * General options when evaluating a learning scheme from the command-line: <p/>
+ *
+ * -t filename <br/>
+ * Name of the file with the training data. (required) <p/>
+ *
+ * -T filename <br/>
+ * Name of the file with the test data. If missing a cross-validation
+ * is performed. <p/>
+ *
+ * -c index <br/>
+ * Index of the class attribute (1, 2, ...; default: last). <p/>
+ *
+ * -x number <br/>
+ * The number of folds for the cross-validation (default: 10). <p/>
+ *
+ * -no-cv <br/>
+ * No cross validation.  If no test file is provided, no evaluation
+ * is done. <p/>
+ *
+ * -split-percentage percentage <br/>
+ * Sets the percentage for the train/test set split, e.g., 66. <p/>
+ *
+ * -preserve-order <br/>
+ * Preserves the order in the percentage split instead of randomizing
+ * the data first with the seed value ('-s'). <p/>
+ *
+ * -s seed <br/>
+ * Random number seed for the cross-validation and percentage split
+ * (default: 1). <p/>
+ *
+ * -m filename <br/>
+ * The name of a file containing a cost matrix. <p/>
+ *
+ * -l filename <br/>
+ * Loads classifier from the given file. In case the filename ends with ".xml",
+ * a PMML file is loaded or, if that fails, options are loaded from XML. <p/>
+ *
+ * -d filename <br/>
+ * Saves classifier built from the training data into the given file. In case
+ * the filename ends with ".xml" the options are saved XML, not the model. <p/>
+ *
+ * -v <br/>
+ * Outputs no statistics for the training data. <p/>
+ *
+ * -o <br/>
+ * Outputs statistics only, not the classifier. <p/>
+ *
+ * -i <br/>
+ * Outputs information-retrieval statistics per class. <p/>
+ *
+ * -k <br/>
+ * Outputs information-theoretic statistics. <p/>
+ *
+ * -classifications "weka.classifiers.evaluation.output.prediction.AbstractOutput + options" <br/>
+ * Uses the specified class for generating the classification output.
+ * E.g.: weka.classifiers.evaluation.output.prediction.PlainText
+ * or  : weka.classifiers.evaluation.output.prediction.CSV
+ *
+ * -p range <br/>
+ * Outputs predictions for test instances (or the train instances if no test
+ * instances provided and -no-cv is used), along with the attributes in the specified range
+ * (and nothing else). Use '-p 0' if no attributes are desired. <p/>
+ * Deprecated: use "-classifications ..." instead. <p/>
+ *
+ * -distribution <br/>
+ * Outputs the distribution instead of only the prediction
+ * in conjunction with the '-p' option (only nominal classes). <p/>
+ * Deprecated: use "-classifications ..." instead. <p/>
+ *
+ * -r <br/>
+ * Outputs cumulative margin distribution (and nothing else). <p/>
+ *
+ * -g <br/>
+ * Only for classifiers that implement "Graphable." Outputs
+ * the graph representation of the classifier (and nothing
+ * else). <p/>
+ *
+ * -xml filename | xml-string <br/>
+ * Retrieves the options from the XML-data instead of the command line. <p/>
+ *
+ * -threshold-file file <br/>
+ * The file to save the threshold data to.
+ * The format is determined by the extensions, e.g., '.arff' for ARFF
+ * format or '.csv' for CSV. <p/>
+ *
+ * -threshold-label label <br/>
+ * The class label to determine the threshold data for
+ * (default is the first label) <p/>
+ *
+ * ------------------------------------------------------------------- <p/>
+ *
+ * Example usage as the main of a classifier (called FunkyClassifier):
+ * <code> <pre>
+ * public static void main(String [] args) {
+ *   runClassifier(new FunkyClassifier(), args);
+ * }
+ * </pre> </code>
+ * <p/>
+ *
+ * ------------------------------------------------------------------ <p/>
+ *
+ * Example usage from within an application:
+ * <code> <pre>
+ * Instances trainInstances = ... instances got from somewhere
+ * Instances testInstances = ... instances got from somewhere
+ * Classifier scheme = ... scheme got from somewhere
+ *
+ * Evaluation evaluation = new Evaluation(trainInstances);
+ * evaluation.evaluateModel(scheme, testInstances);
+ * System.out.println(evaluation.toSummaryString());
+ * </pre> </code>
+ *
+ *
+ * @author   Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author   Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version  $Revision: 6041 $
+ */
+public class Evaluation
+  implements Summarizable, RevisionHandler {
+
+  /** The number of classes. */
+  protected int m_NumClasses;
+
+  /** The number of folds for a cross-validation. */
+  protected int m_NumFolds;
+
+  /** The weight of all incorrectly classified instances. */
+  protected double m_Incorrect;
+
+  /** The weight of all correctly classified instances. */
+  protected double m_Correct;
+
+  /** The weight of all unclassified instances. */
+  protected double m_Unclassified;
+
+  /*** The weight of all instances that had no class assigned to them. */
+  protected double m_MissingClass;
+
+  /** The weight of all instances that had a class assigned to them. */
+  protected double m_WithClass;
+
+  /** Array for storing the confusion matrix. */
+  protected double [][] m_ConfusionMatrix;
+
+  /** The names of the classes. */
+  protected String [] m_ClassNames;
+
+  /** Is the class nominal or numeric? */
+  protected boolean m_ClassIsNominal;
+
+  /** The prior probabilities of the classes. */
+  protected double [] m_ClassPriors;
+
+  /** The sum of counts for priors. */
+  protected double m_ClassPriorsSum;
+
+  /** The cost matrix (if given). */
+  protected CostMatrix m_CostMatrix;
+
+  /** The total cost of predictions (includes instance weights). */
+  protected double m_TotalCost;
+
+  /** Sum of errors. */
+  protected double m_SumErr;
+
+  /** Sum of absolute errors. */
+  protected double m_SumAbsErr;
+
+  /** Sum of squared errors. */
+  protected double m_SumSqrErr;
+
+  /** Sum of class values. */
+  protected double m_SumClass;
+
+  /** Sum of squared class values. */
+  protected double m_SumSqrClass;
+
+  /*** Sum of predicted values. */
+  protected double m_SumPredicted;
+
+  /** Sum of squared predicted values. */
+  protected double m_SumSqrPredicted;
+
+  /** Sum of predicted * class values. */
+  protected double m_SumClassPredicted;
+
+  /** Sum of absolute errors of the prior. */
+  protected double m_SumPriorAbsErr;
+
+  /** Sum of absolute errors of the prior. */
+  protected double m_SumPriorSqrErr;
+
+  /** Total Kononenko & Bratko Information. */
+  protected double m_SumKBInfo;
+
+  /*** Resolution of the margin histogram. */
+  protected static int k_MarginResolution = 500;
+
+  /** Cumulative margin distribution. */
+  protected double m_MarginCounts [];
+
+  /** Number of non-missing class training instances seen. */
+  protected int m_NumTrainClassVals;
+
+  /** Array containing all numeric training class values seen. */
+  protected double [] m_TrainClassVals;
+
+  /** Array containing all numeric training class weights. */
+  protected double [] m_TrainClassWeights;
+
+  /** Numeric class estimator for prior. */
+  protected UnivariateKernelEstimator m_PriorEstimator;
+
+  /** Whether complexity statistics are available. */
+  protected boolean m_ComplexityStatisticsAvailable = true;
+
+  /**
+   * The minimum probablility accepted from an estimator to avoid
+   * taking log(0) in Sf calculations.
+   */
+  protected static final double MIN_SF_PROB = Double.MIN_VALUE;
+
+  /** Total entropy of prior predictions. */
+  protected double m_SumPriorEntropy;
+
+  /** Total entropy of scheme predictions. */
+  protected double m_SumSchemeEntropy;
+
+  /** Whether coverage statistics are available. */
+  protected boolean m_CoverageStatisticsAvailable = true;
+
+  /**  The confidence level used for coverage statistics. */
+  protected double m_ConfLevel = 0.95;
+
+  /** Total size of predicted regions at the given confidence level. */
+  protected double m_TotalSizeOfRegions;
+
+  /** Total coverage of test cases at the given confidence level. */
+  protected double m_TotalCoverage;
+
+  /** Minimum target value. */
+  protected double m_MinTarget;
+
+  /** Maximum target value. */
+  protected double m_MaxTarget;
+
+  /** The list of predictions that have been generated (for computing AUC). */
+  protected FastVector m_Predictions;
+
+  /** enables/disables the use of priors, e.g., if no training set is
+   * present in case of de-serialized schemes. */
+  protected boolean m_NoPriors = false;
+
+  /** The header of the training set. */
+  protected Instances m_Header;
+
+  /**
+   * Initializes all the counters for the evaluation.
+   * Use <code>useNoPriors()</code> if the dataset is the test set and you
+   * can't initialize with the priors from the training set via
+   * <code>setPriors(Instances)</code>.
+   *
+   * @param data 	set of training instances, to get some header
+   * 			information and prior class distribution information
+   * @throws Exception 	if the class is not defined
+   * @see 		#useNoPriors()
+   * @see 		#setPriors(Instances)
+   */
+  public Evaluation(Instances data) throws Exception {
+
+    this(data, null);
+  }
+
+  /**
+   * Initializes all the counters for the evaluation and also takes a
+   * cost matrix as parameter.
+   * Use <code>useNoPriors()</code> if the dataset is the test set and you
+   * can't initialize with the priors from the training set via
+   * <code>setPriors(Instances)</code>.
+   *
+   * @param data 	set of training instances, to get some header
+   * 			information and prior class distribution information
+   * @param costMatrix 	the cost matrix---if null, default costs will be used
+   * @throws Exception 	if cost matrix is not compatible with
+   * 			data, the class is not defined or the class is numeric
+   * @see 		#useNoPriors()
+   * @see 		#setPriors(Instances)
+   */
+  public Evaluation(Instances data, CostMatrix costMatrix)
+  throws Exception {
+
+    m_Header = new Instances(data, 0);
+    m_NumClasses = data.numClasses();
+    m_NumFolds = 1;
+    m_ClassIsNominal = data.classAttribute().isNominal();
+
+    if (m_ClassIsNominal) {
+      m_ConfusionMatrix = new double [m_NumClasses][m_NumClasses];
+      m_ClassNames = new String [m_NumClasses];
+      for(int i = 0; i < m_NumClasses; i++) {
+        m_ClassNames[i] = data.classAttribute().value(i);
+      }
+    }
+    m_CostMatrix = costMatrix;
+    if (m_CostMatrix != null) {
+      if (!m_ClassIsNominal) {
+        throw new Exception("Class has to be nominal if cost matrix given!");
+      }
+      if (m_CostMatrix.size() != m_NumClasses) {
+        throw new Exception("Cost matrix not compatible with data!");
+      }
+    }
+    m_ClassPriors = new double [m_NumClasses];
+    setPriors(data);
+    m_MarginCounts = new double [k_MarginResolution + 1];
+  }
+
+  /**
+   * Returns the header of the underlying dataset.
+   *
+   * @return		the header information
+   */
+  public Instances getHeader() {
+    return m_Header;
+  }
+
+  /**
+   * Returns the area under ROC for those predictions that have been collected
+   * in the evaluateClassifier(Classifier, Instances) method. Returns
+   * Utils.missingValue() if the area is not available.
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the area under the ROC curve or not a number
+   */
+  public double areaUnderROC(int classIndex) {
+
+    // Check if any predictions have been collected
+    if (m_Predictions == null) {
+      return Utils.missingValue();
+    } else {
+      ThresholdCurve tc = new ThresholdCurve();
+      Instances result = tc.getCurve(m_Predictions, classIndex);
+      return ThresholdCurve.getROCArea(result);
+    }
+  }
+
+  /**
+   * Calculates the weighted (by class size) AUC.
+   *
+   * @return the weighted AUC.
+   */
+  public double weightedAreaUnderROC() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double aucTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = areaUnderROC(i);
+      if (!Utils.isMissingValue(temp)) {
+        aucTotal += (temp * classCounts[i]);
+      }
+    }
+
+    return aucTotal / classCountSum;
+  }
+
+  /**
+   * Returns a copy of the confusion matrix.
+   *
+   * @return a copy of the confusion matrix as a two-dimensional array
+   */
+  public double[][] confusionMatrix() {
+
+    double[][] newMatrix = new double[m_ConfusionMatrix.length][0];
+
+    for (int i = 0; i < m_ConfusionMatrix.length; i++) {
+      newMatrix[i] = new double[m_ConfusionMatrix[i].length];
+      System.arraycopy(m_ConfusionMatrix[i], 0, newMatrix[i], 0,
+          m_ConfusionMatrix[i].length);
+    }
+    return newMatrix;
+  }
+
+  /**
+   * Performs a (stratified if class is nominal) cross-validation
+   * for a classifier on a set of instances. Now performs
+   * a deep copy of the classifier before each call to
+   * buildClassifier() (just in case the classifier is not
+   * initialized properly).
+   *
+   * @param classifier the classifier with any options set.
+   * @param data the data on which the cross-validation is to be
+   * performed
+   * @param numFolds the number of folds for the cross-validation
+   * @param random random number generator for randomization
+   * @param forPredictionsPrinting varargs parameter that, if supplied, is
+   * expected to hold a weka.classifiers.evaluation.output.prediction.AbstractOutput
+   * object
+   * @throws Exception if a classifier could not be generated
+   * successfully or the class is not defined
+   */
+  public void crossValidateModel(Classifier classifier,
+                                 Instances data, int numFolds, Random random,
+                                 Object... forPredictionsPrinting)
+  throws Exception {
+
+    // Make a copy of the data we can reorder
+    data = new Instances(data);
+    data.randomize(random);
+    if (data.classAttribute().isNominal()) {
+      data.stratify(numFolds);
+    }
+
+    // We assume that the first element is a
+    // weka.classifiers.evaluation.output.prediction.AbstractOutput object
+    AbstractOutput classificationOutput = null;
+    if (forPredictionsPrinting.length > 0) {
+      // print the header first
+      classificationOutput = (AbstractOutput) forPredictionsPrinting[0];
+      classificationOutput.setHeader(data);
+      classificationOutput.printHeader();
+    }
+
+    // Do the folds
+    for (int i = 0; i < numFolds; i++) {
+      Instances train = data.trainCV(numFolds, i, random);
+      setPriors(train);
+      Classifier copiedClassifier = AbstractClassifier.makeCopy(classifier);
+      copiedClassifier.buildClassifier(train);
+      Instances test = data.testCV(numFolds, i);
+      evaluateModel(copiedClassifier, test, forPredictionsPrinting);
+    }
+    m_NumFolds = numFolds;
+
+    if (classificationOutput != null)
+      classificationOutput.printFooter();
+  }
+
+  /**
+   * Performs a (stratified if class is nominal) cross-validation
+   * for a classifier on a set of instances.
+   *
+   * @param classifierString a string naming the class of the classifier
+   * @param data the data on which the cross-validation is to be
+   * performed
+   * @param numFolds the number of folds for the cross-validation
+   * @param options the options to the classifier. Any options
+   * @param random the random number generator for randomizing the data
+   * accepted by the classifier will be removed from this array.
+   * @throws Exception if a classifier could not be generated
+   * successfully or the class is not defined
+   */
+  public void crossValidateModel(String classifierString,
+      Instances data, int numFolds,
+      String[] options, Random random)
+    throws Exception {
+
+    crossValidateModel(AbstractClassifier.forName(classifierString, options),
+        data, numFolds, random);
+  }
+
+  /**
+   * Evaluates a classifier with the options given in an array of
+   * strings. <p/>
+   *
+   * Valid options are: <p/>
+   *
+   * -t filename <br/>
+   * Name of the file with the training data. (required) <p/>
+   *
+   * -T filename <br/>
+   * Name of the file with the test data. If missing a cross-validation
+   * is performed. <p/>
+   *
+   * -c index <br/>
+   * Index of the class attribute (1, 2, ...; default: last). <p/>
+   *
+   * -x number <br/>
+   * The number of folds for the cross-validation (default: 10). <p/>
+   *
+   * -no-cv <br/>
+   * No cross validation.  If no test file is provided, no evaluation
+   * is done. <p/>
+   *
+   * -split-percentage percentage <br/>
+   * Sets the percentage for the train/test set split, e.g., 66. <p/>
+   *
+   * -preserve-order <br/>
+   * Preserves the order in the percentage split instead of randomizing
+   * the data first with the seed value ('-s'). <p/>
+   *
+   * -s seed <br/>
+   * Random number seed for the cross-validation and percentage split
+   * (default: 1). <p/>
+   *
+   * -m filename <br/>
+   * The name of a file containing a cost matrix. <p/>
+   *
+   * -l filename <br/>
+   * Loads classifier from the given file. In case the filename ends with
+   * ".xml",a PMML file is loaded or, if that fails, options are loaded from XML. <p/>
+   *
+   * -d filename <br/>
+   * Saves classifier built from the training data into the given file. In case
+   * the filename ends with ".xml" the options are saved XML, not the model. <p/>
+   *
+   * -v <br/>
+   * Outputs no statistics for the training data. <p/>
+   *
+   * -o <br/>
+   * Outputs statistics only, not the classifier. <p/>
+   *
+   * -i <br/>
+   * Outputs detailed information-retrieval statistics per class. <p/>
+   *
+   * -k <br/>
+   * Outputs information-theoretic statistics. <p/>
+   *
+   * -classifications "weka.classifiers.evaluation.output.prediction.AbstractOutput + options" <br/>
+   * Uses the specified class for generating the classification output.
+   * E.g.: weka.classifiers.evaluation.output.prediction.PlainText
+   * or  : weka.classifiers.evaluation.output.prediction.CSV
+   *
+   * -p range <br/>
+   * Outputs predictions for test instances (or the train instances if no test
+   * instances provided and -no-cv is used), along with the attributes in the specified range
+   * (and nothing else). Use '-p 0' if no attributes are desired. <p/>
+   * Deprecated: use "-classifications ..." instead. <p/>
+   *
+   * -distribution <br/>
+   * Outputs the distribution instead of only the prediction
+   * in conjunction with the '-p' option (only nominal classes). <p/>
+   * Deprecated: use "-classifications ..." instead. <p/>
+   *
+   * -r <br/>
+   * Outputs cumulative margin distribution (and nothing else). <p/>
+   *
+   * -g <br/>
+   * Only for classifiers that implement "Graphable." Outputs
+   * the graph representation of the classifier (and nothing
+   * else). <p/>
+   *
+   * -xml filename | xml-string <br/>
+   * Retrieves the options from the XML-data instead of the command line. <p/>
+   *
+   * -threshold-file file <br/>
+   * The file to save the threshold data to.
+   * The format is determined by the extensions, e.g., '.arff' for ARFF
+   * format or '.csv' for CSV. <p/>
+   *
+   * -threshold-label label <br/>
+   * The class label to determine the threshold data for
+   * (default is the first label) <p/>
+   *
+   * @param classifierString class of machine learning classifier as a string
+   * @param options the array of string containing the options
+   * @throws Exception if model could not be evaluated successfully
+   * @return a string describing the results
+   */
+  public static String evaluateModel(String classifierString,
+      String [] options) throws Exception {
+
+    Classifier classifier;
+
+    // Create classifier
+    try {
+      classifier =
+        //  (Classifier)Class.forName(classifierString).newInstance();
+        AbstractClassifier.forName(classifierString, null);
+    } catch (Exception e) {
+      throw new Exception("Can't find class with name "
+          + classifierString + '.');
+    }
+    return evaluateModel(classifier, options);
+  }
+
+  /**
+   * A test method for this class. Just extracts the first command line
+   * argument as a classifier class name and calls evaluateModel.
+   * @param args an array of command line arguments, the first of which
+   * must be the class name of a classifier.
+   */
+  public static void main(String [] args) {
+
+    try {
+      if (args.length == 0) {
+        throw new Exception("The first argument must be the class name"
+            + " of a classifier");
+      }
+      String classifier = args[0];
+      args[0] = "";
+      System.out.println(evaluateModel(classifier, args));
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+
+  /**
+   * Evaluates a classifier with the options given in an array of
+   * strings. <p/>
+   *
+   * Valid options are: <p/>
+   *
+   * -t name of training file <br/>
+   * Name of the file with the training data. (required) <p/>
+   *
+   * -T name of test file <br/>
+   * Name of the file with the test data. If missing a cross-validation
+   * is performed. <p/>
+   *
+   * -c class index <br/>
+   * Index of the class attribute (1, 2, ...; default: last). <p/>
+   *
+   * -x number of folds <br/>
+   * The number of folds for the cross-validation (default: 10). <p/>
+   *
+   * -no-cv <br/>
+   * No cross validation.  If no test file is provided, no evaluation
+   * is done. <p/>
+   *
+   * -split-percentage percentage <br/>
+   * Sets the percentage for the train/test set split, e.g., 66. <p/>
+   *
+   * -preserve-order <br/>
+   * Preserves the order in the percentage split instead of randomizing
+   * the data first with the seed value ('-s'). <p/>
+   *
+   * -s seed <br/>
+   * Random number seed for the cross-validation and percentage split
+   * (default: 1). <p/>
+   *
+   * -m file with cost matrix <br/>
+   * The name of a file containing a cost matrix. <p/>
+   *
+   * -l filename <br/>
+   * Loads classifier from the given file. In case the filename ends with
+   * ".xml",a PMML file is loaded or, if that fails, options are loaded from XML. <p/>
+   *
+   * -d filename <br/>
+   * Saves classifier built from the training data into the given file. In case
+   * the filename ends with ".xml" the options are saved XML, not the model. <p/>
+   *
+   * -v <br/>
+   * Outputs no statistics for the training data. <p/>
+   *
+   * -o <br/>
+   * Outputs statistics only, not the classifier. <p/>
+   *
+   * -i <br/>
+   * Outputs detailed information-retrieval statistics per class. <p/>
+   *
+   * -k <br/>
+   * Outputs information-theoretic statistics. <p/>
+   *
+   * -classifications "weka.classifiers.evaluation.output.prediction.AbstractOutput + options" <br/>
+   * Uses the specified class for generating the classification output.
+   * E.g.: weka.classifiers.evaluation.output.prediction.PlainText
+   * or  : weka.classifiers.evaluation.output.prediction.CSV
+   *
+   * -p range <br/>
+   * Outputs predictions for test instances (or the train instances if no test
+   * instances provided and -no-cv is used), along with the attributes in the specified range
+   * (and nothing else). Use '-p 0' if no attributes are desired. <p/>
+   * Deprecated: use "-classifications ..." instead. <p/>
+   *
+   * -distribution <br/>
+   * Outputs the distribution instead of only the prediction
+   * in conjunction with the '-p' option (only nominal classes). <p/>
+   * Deprecated: use "-classifications ..." instead. <p/>
+   *
+   * -r <br/>
+   * Outputs cumulative margin distribution (and nothing else). <p/>
+   *
+   * -g <br/>
+   * Only for classifiers that implement "Graphable." Outputs
+   * the graph representation of the classifier (and nothing
+   * else). <p/>
+   *
+   * -xml filename | xml-string <br/>
+   * Retrieves the options from the XML-data instead of the command line. <p/>
+   *
+   * @param classifier machine learning classifier
+   * @param options the array of string containing the options
+   * @throws Exception if model could not be evaluated successfully
+   * @return a string describing the results
+   */
+  public static String evaluateModel(Classifier classifier,
+      String [] options) throws Exception {
+
+    Instances train = null, tempTrain, test = null, template = null;
+    int seed = 1, folds = 10, classIndex = -1;
+    boolean noCrossValidation = false;
+    String trainFileName, testFileName, sourceClass,
+    classIndexString, seedString, foldsString, objectInputFileName,
+    objectOutputFileName;
+    boolean noOutput = false,
+    trainStatistics = true,
+    printMargins = false, printComplexityStatistics = false,
+    printGraph = false, classStatistics = false, printSource = false;
+    StringBuffer text = new StringBuffer();
+    DataSource trainSource = null, testSource = null;
+    ObjectInputStream objectInputStream = null;
+    BufferedInputStream xmlInputStream = null;
+    CostMatrix costMatrix = null;
+    StringBuffer schemeOptionsText = null;
+    long trainTimeStart = 0, trainTimeElapsed = 0,
+    testTimeStart = 0, testTimeElapsed = 0;
+    String xml = "";
+    String[] optionsTmp = null;
+    Classifier classifierBackup;
+    Classifier classifierClassifications = null;
+    int actualClassIndex = -1;  // 0-based class index
+    String splitPercentageString = "";
+    int splitPercentage = -1;
+    boolean preserveOrder = false;
+    boolean trainSetPresent = false;
+    boolean testSetPresent = false;
+    String thresholdFile;
+    String thresholdLabel;
+    StringBuffer predsBuff = null; // predictions from cross-validation
+    AbstractOutput classificationOutput = null;
+
+    // help requested?
+    if (Utils.getFlag("h", options) || Utils.getFlag("help", options)) {
+
+      // global info requested as well?
+      boolean globalInfo = Utils.getFlag("synopsis", options) ||
+        Utils.getFlag("info", options);
+
+      throw new Exception("\nHelp requested."
+          + makeOptionString(classifier, globalInfo));
+    }
+
+    try {
+      // do we get the input from XML instead of normal parameters?
+      xml = Utils.getOption("xml", options);
+      if (!xml.equals(""))
+        options = new XMLOptions(xml).toArray();
+
+      // is the input model only the XML-Options, i.e. w/o built model?
+      optionsTmp = new String[options.length];
+      for (int i = 0; i < options.length; i++)
+        optionsTmp[i] = options[i];
+
+      String tmpO = Utils.getOption('l', optionsTmp);
+      //if (Utils.getOption('l', optionsTmp).toLowerCase().endsWith(".xml")) {
+      if (tmpO.endsWith(".xml")) {
+        // try to load file as PMML first
+        boolean success = false;
+        try {
+          PMMLModel pmmlModel = PMMLFactory.getPMMLModel(tmpO);
+          if (pmmlModel instanceof PMMLClassifier) {
+            classifier = ((PMMLClassifier)pmmlModel);
+            success = true;
+          }
+        } catch (IllegalArgumentException ex) {
+          success = false;
+        }
+        if (!success) {
+          // load options from serialized data  ('-l' is automatically erased!)
+          XMLClassifier xmlserial = new XMLClassifier();
+          OptionHandler cl = (OptionHandler) xmlserial.read(Utils.getOption('l', options));
+
+          // merge options
+          optionsTmp = new String[options.length + cl.getOptions().length];
+          System.arraycopy(cl.getOptions(), 0, optionsTmp, 0, cl.getOptions().length);
+          System.arraycopy(options, 0, optionsTmp, cl.getOptions().length, options.length);
+          options = optionsTmp;
+        }
+      }
+
+      noCrossValidation = Utils.getFlag("no-cv", options);
+      // Get basic options (options the same for all schemes)
+      classIndexString = Utils.getOption('c', options);
+      if (classIndexString.length() != 0) {
+        if (classIndexString.equals("first"))
+          classIndex = 1;
+        else if (classIndexString.equals("last"))
+          classIndex = -1;
+        else
+          classIndex = Integer.parseInt(classIndexString);
+      }
+      trainFileName = Utils.getOption('t', options);
+      objectInputFileName = Utils.getOption('l', options);
+      objectOutputFileName = Utils.getOption('d', options);
+      testFileName = Utils.getOption('T', options);
+      foldsString = Utils.getOption('x', options);
+      if (foldsString.length() != 0) {
+        folds = Integer.parseInt(foldsString);
+      }
+      seedString = Utils.getOption('s', options);
+      if (seedString.length() != 0) {
+        seed = Integer.parseInt(seedString);
+      }
+      if (trainFileName.length() == 0) {
+        if (objectInputFileName.length() == 0) {
+          throw new Exception("No training file and no object input file given.");
+        }
+        if (testFileName.length() == 0) {
+          throw new Exception("No training file and no test file given.");
+        }
+      } else if ((objectInputFileName.length() != 0) &&
+          ((!(classifier instanceof UpdateableClassifier)) ||
+           (testFileName.length() == 0))) {
+        throw new Exception("Classifier not incremental, or no " +
+            "test file provided: can't "+
+            "use both train and model file.");
+      }
+      try {
+        if (trainFileName.length() != 0) {
+          trainSetPresent = true;
+          trainSource = new DataSource(trainFileName);
+        }
+        if (testFileName.length() != 0) {
+          testSetPresent = true;
+          testSource = new DataSource(testFileName);
+        }
+        if (objectInputFileName.length() != 0) {
+          if (objectInputFileName.endsWith(".xml")) {
+            // if this is the case then it means that a PMML classifier was
+            // successfully loaded earlier in the code
+            objectInputStream = null;
+            xmlInputStream = null;
+          } else {
+            InputStream is = new FileInputStream(objectInputFileName);
+            if (objectInputFileName.endsWith(".gz")) {
+              is = new GZIPInputStream(is);
+            }
+            // load from KOML?
+            if (!(objectInputFileName.endsWith(".koml") && KOML.isPresent()) ) {
+              objectInputStream = new ObjectInputStream(is);
+              xmlInputStream    = null;
+            }
+            else {
+              objectInputStream = null;
+              xmlInputStream    = new BufferedInputStream(is);
+            }
+          }
+        }
+      } catch (Exception e) {
+        throw new Exception("Can't open file " + e.getMessage() + '.');
+      }
+      if (testSetPresent) {
+        template = test = testSource.getStructure();
+        if (classIndex != -1) {
+          test.setClassIndex(classIndex - 1);
+        } else {
+          if ( (test.classIndex() == -1) || (classIndexString.length() != 0) )
+            test.setClassIndex(test.numAttributes() - 1);
+        }
+        actualClassIndex = test.classIndex();
+      }
+      else {
+        // percentage split
+        splitPercentageString = Utils.getOption("split-percentage", options);
+        if (splitPercentageString.length() != 0) {
+          if (foldsString.length() != 0)
+            throw new Exception(
+                "Percentage split cannot be used in conjunction with "
+                + "cross-validation ('-x').");
+          splitPercentage = Integer.parseInt(splitPercentageString);
+          if ((splitPercentage <= 0) || (splitPercentage >= 100))
+            throw new Exception("Percentage split value needs be >0 and <100.");
+        }
+        else {
+          splitPercentage = -1;
+        }
+        preserveOrder = Utils.getFlag("preserve-order", options);
+        if (preserveOrder) {
+          if (splitPercentage == -1)
+            throw new Exception("Percentage split ('-percentage-split') is missing.");
+        }
+        // create new train/test sources
+        if (splitPercentage > 0) {
+          testSetPresent = true;
+          Instances tmpInst = trainSource.getDataSet(actualClassIndex);
+          if (!preserveOrder)
+            tmpInst.randomize(new Random(seed));
+          int trainSize = tmpInst.numInstances() * splitPercentage / 100;
+          int testSize  = tmpInst.numInstances() - trainSize;
+          Instances trainInst = new Instances(tmpInst, 0, trainSize);
+          Instances testInst  = new Instances(tmpInst, trainSize, testSize);
+          trainSource = new DataSource(trainInst);
+          testSource  = new DataSource(testInst);
+          template = test = testSource.getStructure();
+          if (classIndex != -1) {
+            test.setClassIndex(classIndex - 1);
+          } else {
+            if ( (test.classIndex() == -1) || (classIndexString.length() != 0) )
+              test.setClassIndex(test.numAttributes() - 1);
+          }
+          actualClassIndex = test.classIndex();
+        }
+      }
+      if (trainSetPresent) {
+        template = train = trainSource.getStructure();
+        if (classIndex != -1) {
+          train.setClassIndex(classIndex - 1);
+        } else {
+          if ( (train.classIndex() == -1) || (classIndexString.length() != 0) )
+            train.setClassIndex(train.numAttributes() - 1);
+        }
+        actualClassIndex = train.classIndex();
+        if ((testSetPresent) && !test.equalHeaders(train)) {
+          throw new IllegalArgumentException("Train and test file not compatible!\n" + test.equalHeadersMsg(train));
+        }
+      }
+      if (template == null) {
+        throw new Exception("No actual dataset provided to use as template");
+      }
+      costMatrix = handleCostOption(
+          Utils.getOption('m', options), template.numClasses());
+
+      classStatistics = Utils.getFlag('i', options);
+      noOutput = Utils.getFlag('o', options);
+      trainStatistics = !Utils.getFlag('v', options);
+      printComplexityStatistics = Utils.getFlag('k', options);
+      printMargins = Utils.getFlag('r', options);
+      printGraph = Utils.getFlag('g', options);
+      sourceClass = Utils.getOption('z', options);
+      printSource = (sourceClass.length() != 0);
+      thresholdFile = Utils.getOption("threshold-file", options);
+      thresholdLabel = Utils.getOption("threshold-label", options);
+
+      String classifications = Utils.getOption("classifications", options);
+      String classificationsOld = Utils.getOption("p", options);
+      if (classifications.length() > 0) {
+        noOutput = true;
+        classificationOutput = AbstractOutput.fromCommandline(classifications);
+        classificationOutput.setHeader(template);
+      }
+      // backwards compatible with old "-p range" and "-distribution" options
+      else if (classificationsOld.length() > 0) {
+        noOutput = true;
+        classificationOutput = new PlainText();
+        classificationOutput.setHeader(template);
+        if (!classificationsOld.equals("0"))
+          classificationOutput.setAttributes(classificationsOld);
+        classificationOutput.setOutputDistribution(Utils.getFlag("distribution", options));
+      }
+      // -distribution flag needs -p option
+      else {
+        if (Utils.getFlag("distribution", options))
+          throw new Exception("Cannot print distribution without '-p' option!");
+      }
+
+      // if no training file given, we don't have any priors
+      if ( (!trainSetPresent) && (printComplexityStatistics) )
+        throw new Exception("Cannot print complexity statistics ('-k') without training file ('-t')!");
+
+      // If a model file is given, we can't process
+      // scheme-specific options
+      if (objectInputFileName.length() != 0) {
+        Utils.checkForRemainingOptions(options);
+      } else {
+
+        // Set options for classifier
+        if (classifier instanceof OptionHandler) {
+          for (int i = 0; i < options.length; i++) {
+            if (options[i].length() != 0) {
+              if (schemeOptionsText == null) {
+                schemeOptionsText = new StringBuffer();
+              }
+              if (options[i].indexOf(' ') != -1) {
+                schemeOptionsText.append('"' + options[i] + "\" ");
+              } else {
+                schemeOptionsText.append(options[i] + " ");
+              }
+            }
+          }
+          ((OptionHandler)classifier).setOptions(options);
+        }
+      }
+
+      Utils.checkForRemainingOptions(options);
+    } catch (Exception e) {
+      throw new Exception("\nWeka exception: " + e.getMessage()
+          + makeOptionString(classifier, false));
+    }
+
+    // Setup up evaluation objects
+    Evaluation trainingEvaluation = new Evaluation(new Instances(template, 0), costMatrix);
+    Evaluation testingEvaluation = new Evaluation(new Instances(template, 0), costMatrix);
+
+    // disable use of priors if no training file given
+    if (!trainSetPresent)
+      testingEvaluation.useNoPriors();
+
+    if (objectInputFileName.length() != 0) {
+      // Load classifier from file
+      if (objectInputStream != null) {
+        classifier = (Classifier) objectInputStream.readObject();
+        // try and read a header (if present)
+        Instances savedStructure = null;
+        try {
+          savedStructure = (Instances) objectInputStream.readObject();
+        } catch (Exception ex) {
+          // don't make a fuss
+        }
+        if (savedStructure != null) {
+          // test for compatibility with template
+          if (!template.equalHeaders(savedStructure)) {
+            throw new Exception("training and test set are not compatible\n" + template.equalHeadersMsg(savedStructure));
+          }
+        }
+        objectInputStream.close();
+      }
+      else if (xmlInputStream != null) {
+        // whether KOML is available has already been checked (objectInputStream would null otherwise)!
+        classifier = (Classifier) KOML.read(xmlInputStream);
+        xmlInputStream.close();
+      }
+    }
+
+    // backup of fully setup classifier for cross-validation
+    classifierBackup = AbstractClassifier.makeCopy(classifier);
+
+    // Build the classifier if no object file provided
+    if ((classifier instanceof UpdateableClassifier) &&
+        (testSetPresent || noCrossValidation) &&
+        (costMatrix == null) &&
+        (trainSetPresent)) {
+      // Build classifier incrementally
+      trainingEvaluation.setPriors(train);
+      testingEvaluation.setPriors(train);
+      trainTimeStart = System.currentTimeMillis();
+      if (objectInputFileName.length() == 0) {
+        classifier.buildClassifier(train);
+      }
+      Instance trainInst;
+      while (trainSource.hasMoreElements(train)) {
+        trainInst = trainSource.nextElement(train);
+        trainingEvaluation.updatePriors(trainInst);
+        testingEvaluation.updatePriors(trainInst);
+        ((UpdateableClassifier)classifier).updateClassifier(trainInst);
+      }
+      trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    } else if (objectInputFileName.length() == 0) {
+      // Build classifier in one go
+      tempTrain = trainSource.getDataSet(actualClassIndex);
+      trainingEvaluation.setPriors(tempTrain);
+      testingEvaluation.setPriors(tempTrain);
+      trainTimeStart = System.currentTimeMillis();
+      classifier.buildClassifier(tempTrain);
+      trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    }
+
+    // backup of fully trained classifier for printing the classifications
+    if (classificationOutput != null)
+      classifierClassifications = AbstractClassifier.makeCopy(classifier);
+
+    // Save the classifier if an object output file is provided
+    if (objectOutputFileName.length() != 0) {
+      OutputStream os = new FileOutputStream(objectOutputFileName);
+      // binary
+      if (!(objectOutputFileName.endsWith(".xml") || (objectOutputFileName.endsWith(".koml") && KOML.isPresent()))) {
+        if (objectOutputFileName.endsWith(".gz")) {
+          os = new GZIPOutputStream(os);
+        }
+        ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
+        objectOutputStream.writeObject(classifier);
+        if (template != null) {
+          objectOutputStream.writeObject(template);
+        }
+        objectOutputStream.flush();
+        objectOutputStream.close();
+      }
+      // KOML/XML
+      else {
+        BufferedOutputStream xmlOutputStream = new BufferedOutputStream(os);
+        if (objectOutputFileName.endsWith(".xml")) {
+          XMLSerialization xmlSerial = new XMLClassifier();
+          xmlSerial.write(xmlOutputStream, classifier);
+        }
+        else
+          // whether KOML is present has already been checked
+          // if not present -> ".koml" is interpreted as binary - see above
+          if (objectOutputFileName.endsWith(".koml")) {
+            KOML.write(xmlOutputStream, classifier);
+          }
+        xmlOutputStream.close();
+      }
+    }
+
+    // If classifier is drawable output string describing graph
+    if ((classifier instanceof Drawable) && (printGraph)){
+      return ((Drawable)classifier).graph();
+    }
+
+    // Output the classifier as equivalent source
+    if ((classifier instanceof Sourcable) && (printSource)){
+      return wekaStaticWrapper((Sourcable) classifier, sourceClass);
+    }
+
+    // Output model
+    if (!(noOutput || printMargins)) {
+      if (classifier instanceof OptionHandler) {
+        if (schemeOptionsText != null) {
+          text.append("\nOptions: "+schemeOptionsText);
+          text.append("\n");
+        }
+      }
+      text.append("\n" + classifier.toString() + "\n");
+    }
+
+    if (!printMargins && (costMatrix != null)) {
+      text.append("\n=== Evaluation Cost Matrix ===\n\n");
+      text.append(costMatrix.toString());
+    }
+
+    // Output test instance predictions only
+    if (classificationOutput != null) {
+      DataSource source = testSource;
+      predsBuff = new StringBuffer();
+      classificationOutput.setBuffer(predsBuff);
+      // no test set -> use train set
+      if (source == null && noCrossValidation) {
+        source = trainSource;
+        predsBuff.append("\n=== Predictions on training data ===\n\n");
+      } else {
+        predsBuff.append("\n=== Predictions on test data ===\n\n");
+      }
+      if (source != null)
+        classificationOutput.print(classifierClassifications, source);
+    }
+
+    // Compute error estimate from training data
+    if ((trainStatistics) && (trainSetPresent)) {
+
+      if ((classifier instanceof UpdateableClassifier) &&
+          (testSetPresent) &&
+          (costMatrix == null)) {
+
+        // Classifier was trained incrementally, so we have to
+        // reset the source.
+        trainSource.reset();
+
+        // Incremental testing
+        train = trainSource.getStructure(actualClassIndex);
+        testTimeStart = System.currentTimeMillis();
+        Instance trainInst;
+        while (trainSource.hasMoreElements(train)) {
+          trainInst = trainSource.nextElement(train);
+          trainingEvaluation.evaluateModelOnce((Classifier)classifier, trainInst);
+        }
+        testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+      } else {
+        testTimeStart = System.currentTimeMillis();
+        trainingEvaluation.evaluateModel(
+            classifier, trainSource.getDataSet(actualClassIndex));
+        testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+      }
+
+      // Print the results of the training evaluation
+      if (printMargins) {
+        return trainingEvaluation.toCumulativeMarginDistributionString();
+      } else {
+        if (classificationOutput == null) {
+          text.append("\nTime taken to build model: "
+              + Utils.doubleToString(trainTimeElapsed / 1000.0,2)
+              + " seconds");
+
+          if (splitPercentage > 0)
+            text.append("\nTime taken to test model on training split: ");
+          else
+            text.append("\nTime taken to test model on training data: ");
+          text.append(Utils.doubleToString(testTimeElapsed / 1000.0,2) + " seconds");
+
+          if (splitPercentage > 0)
+            text.append(trainingEvaluation.toSummaryString("\n\n=== Error on training"
+                  + " split ===\n", printComplexityStatistics));
+          else
+            text.append(trainingEvaluation.toSummaryString("\n\n=== Error on training"
+                  + " data ===\n", printComplexityStatistics));
+
+          if (template.classAttribute().isNominal()) {
+            if (classStatistics) {
+              text.append("\n\n" + trainingEvaluation.toClassDetailsString());
+            }
+            if (!noCrossValidation)
+              text.append("\n\n" + trainingEvaluation.toMatrixString());
+          }
+        }
+      }
+    }
+
+    // Compute proper error estimates
+    if (testSource != null) {
+      // Testing is on the supplied test data
+      testSource.reset();
+      test = testSource.getStructure(test.classIndex());
+      Instance testInst;
+      while (testSource.hasMoreElements(test)) {
+        testInst = testSource.nextElement(test);
+        testingEvaluation.evaluateModelOnceAndRecordPrediction(
+            (Classifier)classifier, testInst);
+      }
+
+      if (splitPercentage > 0) {
+        if (classificationOutput == null) {
+          text.append("\n\n" + testingEvaluation.
+              toSummaryString("=== Error on test split ===\n",
+                  printComplexityStatistics));
+        }
+      } else {
+        if (classificationOutput == null) {
+          text.append("\n\n" + testingEvaluation.
+              toSummaryString("=== Error on test data ===\n",
+                  printComplexityStatistics));
+        }
+      }
+
+    } else if (trainSource != null) {
+      if (!noCrossValidation) {
+        // Testing is via cross-validation on training data
+        Random random = new Random(seed);
+        // use untrained (!) classifier for cross-validation
+        classifier = AbstractClassifier.makeCopy(classifierBackup);
+        if (classificationOutput == null) {
+          testingEvaluation.crossValidateModel(classifier,
+                                               trainSource.getDataSet(actualClassIndex),
+                                               folds, random);
+          if (template.classAttribute().isNumeric()) {
+            text.append("\n\n\n" + testingEvaluation.
+                        toSummaryString("=== Cross-validation ===\n",
+                                        printComplexityStatistics));
+          } else {
+            text.append("\n\n\n" + testingEvaluation.
+                        toSummaryString("=== Stratified " +
+                                        "cross-validation ===\n",
+                                        printComplexityStatistics));
+          }
+        } else {
+          predsBuff = new StringBuffer();
+          classificationOutput.setBuffer(predsBuff);
+          predsBuff.append("\n=== Predictions under cross-validation ===\n\n");
+          testingEvaluation.crossValidateModel(classifier,
+                                               trainSource.getDataSet(actualClassIndex),
+                                               folds, random, classificationOutput);
+        }
+      }
+    }
+    if (template.classAttribute().isNominal()) {
+      if (classStatistics && !noCrossValidation && (classificationOutput == null)) {
+        text.append("\n\n" + testingEvaluation.toClassDetailsString());
+      }
+      if (!noCrossValidation && (classificationOutput == null))
+        text.append("\n\n" + testingEvaluation.toMatrixString());
+
+    }
+
+    // predictions from cross-validation?
+    if (predsBuff != null) {
+      text.append("\n" + predsBuff);
+    }
+
+    if ((thresholdFile.length() != 0) && template.classAttribute().isNominal()) {
+      int labelIndex = 0;
+      if (thresholdLabel.length() != 0)
+        labelIndex = template.classAttribute().indexOfValue(thresholdLabel);
+      if (labelIndex == -1)
+        throw new IllegalArgumentException(
+            "Class label '" + thresholdLabel + "' is unknown!");
+      ThresholdCurve tc = new ThresholdCurve();
+      Instances result = tc.getCurve(testingEvaluation.predictions(), labelIndex);
+      DataSink.write(thresholdFile, result);
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Attempts to load a cost matrix.
+   *
+   * @param costFileName the filename of the cost matrix
+   * @param numClasses the number of classes that should be in the cost matrix
+   * (only used if the cost file is in old format).
+   * @return a <code>CostMatrix</code> value, or null if costFileName is empty
+   * @throws Exception if an error occurs.
+   */
+  protected static CostMatrix handleCostOption(String costFileName,
+      int numClasses)
+    throws Exception {
+
+    if ((costFileName != null) && (costFileName.length() != 0)) {
+      System.out.println(
+          "NOTE: The behaviour of the -m option has changed between WEKA 3.0"
+          +" and WEKA 3.1. -m now carries out cost-sensitive *evaluation*"
+          +" only. For cost-sensitive *prediction*, use one of the"
+          +" cost-sensitive metaschemes such as"
+          +" weka.classifiers.meta.CostSensitiveClassifier or"
+          +" weka.classifiers.meta.MetaCost");
+
+      Reader costReader = null;
+      try {
+        costReader = new BufferedReader(new FileReader(costFileName));
+      } catch (Exception e) {
+        throw new Exception("Can't open file " + e.getMessage() + '.');
+      }
+      try {
+        // First try as a proper cost matrix format
+        return new CostMatrix(costReader);
+      } catch (Exception ex) {
+        try {
+          // Now try as the poxy old format :-)
+          //System.err.println("Attempting to read old format cost file");
+          try {
+            costReader.close(); // Close the old one
+            costReader = new BufferedReader(new FileReader(costFileName));
+          } catch (Exception e) {
+            throw new Exception("Can't open file " + e.getMessage() + '.');
+          }
+          CostMatrix costMatrix = new CostMatrix(numClasses);
+          //System.err.println("Created default cost matrix");
+          costMatrix.readOldFormat(costReader);
+          return costMatrix;
+          //System.err.println("Read old format");
+        } catch (Exception e2) {
+          // re-throw the original exception
+          //System.err.println("Re-throwing original exception");
+          throw ex;
+        }
+      }
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Evaluates the classifier on a given set of instances. Note that
+   * the data must have exactly the same format (e.g. order of
+   * attributes) as the data used to train the classifier! Otherwise
+   * the results will generally be meaningless.
+   *
+   * @param classifier machine learning classifier
+   * @param data set of test instances for evaluation
+   * @param forPredictionsPrinting varargs parameter that, if supplied, is
+   * expected to hold a weka.classifiers.evaluation.output.prediction.AbstractOutput
+   * object
+   * @return the predictions
+   * @throws Exception if model could not be evaluated
+   * successfully
+   */
+  public double[] evaluateModel(Classifier classifier,
+                                Instances data,
+                                Object... forPredictionsPrinting) throws Exception {
+    // for predictions printing
+    AbstractOutput classificationOutput = null;
+
+    double predictions[] = new double[data.numInstances()];
+
+    if (forPredictionsPrinting.length > 0) {
+      classificationOutput = (AbstractOutput) forPredictionsPrinting[0];
+    }
+
+    // Need to be able to collect predictions if appropriate (for AUC)
+
+    for (int i = 0; i < data.numInstances(); i++) {
+      predictions[i] = evaluateModelOnceAndRecordPrediction((Classifier)classifier,
+          data.instance(i));
+      if (classificationOutput != null)
+        classificationOutput.printClassification(classifier, data.instance(i), i);
+    }
+
+    return predictions;
+  }
+
+  /**
+   * Evaluates the supplied distribution on a single instance.
+   *
+   * @param dist the supplied distribution
+   * @param instance the test instance to be classified
+   * @param storePredictions whether to store predictions for nominal classifier
+   * @return the prediction
+   * @throws Exception if model could not be evaluated successfully
+   */
+  public double evaluationForSingleInstance(double[] dist, Instance instance,
+                                            boolean storePredictions) throws Exception {
+
+    double pred;
+
+    if (m_ClassIsNominal) {
+      pred = Utils.maxIndex(dist);
+      if (dist[(int)pred] <= 0) {
+        pred = Utils.missingValue();
+      }
+      updateStatsForClassifier(dist, instance);
+      if (storePredictions) {
+        if (m_Predictions == null)
+          m_Predictions = new FastVector();
+        m_Predictions.addElement(new NominalPrediction(instance.classValue(), dist,
+                                                       instance.weight()));
+      }
+    } else {
+      pred = dist[0];
+      updateStatsForPredictor(pred, instance);
+      if (storePredictions) {
+        if (m_Predictions == null)
+          m_Predictions = new FastVector();
+        m_Predictions.addElement(new NumericPrediction(instance.classValue(), pred,
+                                                       instance.weight()));
+      }
+    }
+
+    return pred;
+  }
+
+  /**
+   * Evaluates the classifier on a single instance and records the
+   * prediction.
+   *
+   * @param classifier machine learning classifier
+   * @param instance the test instance to be classified
+   * @param storePredictions whether to store predictions for nominal classifier
+   * @return the prediction made by the clasifier
+   * @throws Exception if model could not be evaluated
+   * successfully or the data contains string attributes
+   */
+  protected double evaluationForSingleInstance(Classifier classifier,
+                                               Instance instance,
+                                               boolean storePredictions) throws Exception {
+
+    Instance classMissing = (Instance)instance.copy();
+    classMissing.setDataset(instance.dataset());
+    classMissing.setClassMissing();
+    double pred = evaluationForSingleInstance(classifier.distributionForInstance(classMissing),
+                                              instance, storePredictions);
+
+    // We don't need to do the following if the class is nominal because in that case
+    // entropy and coverage statistics are always computed.
+    if (!m_ClassIsNominal) {
+      if (!instance.classIsMissing() && !Utils.isMissingValue(pred)) {
+        if (classifier instanceof IntervalEstimator) {
+          updateStatsForIntervalEstimator((IntervalEstimator)classifier, classMissing,
+                                          instance.classValue());
+        } else {
+          m_CoverageStatisticsAvailable = false;
+        }
+        if (classifier instanceof ConditionalDensityEstimator) {
+          updateStatsForConditionalDensityEstimator((ConditionalDensityEstimator)classifier,
+                                                    classMissing, instance.classValue());
+        } else {
+          m_ComplexityStatisticsAvailable = false;
+        }
+      }
+    }
+    return pred;
+  }
+
+  /**
+   * Evaluates the classifier on a single instance and records the
+   * prediction.
+   *
+   * @param classifier machine learning classifier
+   * @param instance the test instance to be classified
+   * @return the prediction made by the clasifier
+   * @throws Exception if model could not be evaluated
+   * successfully or the data contains string attributes
+   */
+  public double evaluateModelOnceAndRecordPrediction(Classifier classifier,
+      Instance instance) throws Exception {
+
+    return evaluationForSingleInstance(classifier, instance, true);
+  }
+
+  /**
+   * Evaluates the classifier on a single instance.
+   *
+   * @param classifier machine learning classifier
+   * @param instance the test instance to be classified
+   * @return the prediction made by the clasifier
+   * @throws Exception if model could not be evaluated
+   * successfully or the data contains string attributes
+   */
+  public double evaluateModelOnce(Classifier classifier, Instance instance) throws Exception {
+
+    return evaluationForSingleInstance(classifier, instance, false);
+  }
+
+  /**
+   * Evaluates the supplied distribution on a single instance.
+   *
+   * @param dist the supplied distribution
+   * @param instance the test instance to be classified
+   * @return the prediction
+   * @throws Exception if model could not be evaluated
+   * successfully
+   */
+  public double evaluateModelOnce(double [] dist, Instance instance) throws Exception {
+
+    return evaluationForSingleInstance(dist, instance, false);
+  }
+
+  /**
+   * Evaluates the supplied distribution on a single instance.
+   *
+   * @param dist the supplied distribution
+   * @param instance the test instance to be classified
+   * @return the prediction
+   * @throws Exception if model could not be evaluated
+   * successfully
+   */
+  public double evaluateModelOnceAndRecordPrediction(double [] dist,
+      Instance instance) throws Exception {
+
+    return evaluationForSingleInstance(dist, instance, true);
+  }
+
+  /**
+   * Evaluates the supplied prediction on a single instance.
+   *
+   * @param prediction the supplied prediction
+   * @param instance the test instance to be classified
+   * @throws Exception if model could not be evaluated
+   * successfully
+   */
+  public void evaluateModelOnce(double prediction,
+      Instance instance) throws Exception {
+
+    evaluateModelOnce(makeDistribution(prediction), instance);
+  }
+
+  /**
+   * Returns the predictions that have been collected.
+   *
+   * @return a reference to the FastVector containing the predictions
+   * that have been collected. This should be null if no predictions
+   * have been collected.
+   */
+  public FastVector predictions() {
+    return m_Predictions;
+  }
+
+  /**
+   * Wraps a static classifier in enough source to test using the weka
+   * class libraries.
+   *
+   * @param classifier a Sourcable Classifier
+   * @param className the name to give to the source code class
+   * @return the source for a static classifier that can be tested with
+   * weka libraries.
+   * @throws Exception if code-generation fails
+   */
+  public static String wekaStaticWrapper(Sourcable classifier, String className)
+    throws Exception {
+
+    StringBuffer result = new StringBuffer();
+    String staticClassifier = classifier.toSource(className);
+
+    result.append("// Generated with Weka " + Version.VERSION + "\n");
+    result.append("//\n");
+    result.append("// This code is public domain and comes with no warranty.\n");
+    result.append("//\n");
+    result.append("// Timestamp: " + new Date() + "\n");
+    result.append("\n");
+    result.append("package weka.classifiers;\n");
+    result.append("\n");
+    result.append("import weka.core.Attribute;\n");
+    result.append("import weka.core.Capabilities;\n");
+    result.append("import weka.core.Capabilities.Capability;\n");
+    result.append("import weka.core.Instance;\n");
+    result.append("import weka.core.Instances;\n");
+    result.append("import weka.core.RevisionUtils;\n");
+    result.append("import weka.classifiers.Classifier;\nimport weka.classifiers.AbstractClassifier;\n");
+    result.append("\n");
+    result.append("public class WekaWrapper\n");
+    result.append("  extends AbstractClassifier {\n");
+
+    // globalInfo
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns only the toString() method.\n");
+    result.append("   *\n");
+    result.append("   * @return a string describing the classifier\n");
+    result.append("   */\n");
+    result.append("  public String globalInfo() {\n");
+    result.append("    return toString();\n");
+    result.append("  }\n");
+
+    // getCapabilities
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns the capabilities of this classifier.\n");
+    result.append("   *\n");
+    result.append("   * @return the capabilities\n");
+    result.append("   */\n");
+    result.append("  public Capabilities getCapabilities() {\n");
+    result.append(((Classifier) classifier).getCapabilities().toSource("result", 4));
+    result.append("    return result;\n");
+    result.append("  }\n");
+
+    // buildClassifier
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * only checks the data against its capabilities.\n");
+    result.append("   *\n");
+    result.append("   * @param i the training data\n");
+    result.append("   */\n");
+    result.append("  public void buildClassifier(Instances i) throws Exception {\n");
+    result.append("    // can classifier handle the data?\n");
+    result.append("    getCapabilities().testWithFail(i);\n");
+    result.append("  }\n");
+
+    // classifyInstance
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Classifies the given instance.\n");
+    result.append("   *\n");
+    result.append("   * @param i the instance to classify\n");
+    result.append("   * @return the classification result\n");
+    result.append("   */\n");
+    result.append("  public double classifyInstance(Instance i) throws Exception {\n");
+    result.append("    Object[] s = new Object[i.numAttributes()];\n");
+    result.append("    \n");
+    result.append("    for (int j = 0; j < s.length; j++) {\n");
+    result.append("      if (!i.isMissing(j)) {\n");
+    result.append("        if (i.attribute(j).isNominal())\n");
+    result.append("          s[j] = new String(i.stringValue(j));\n");
+    result.append("        else if (i.attribute(j).isNumeric())\n");
+    result.append("          s[j] = new Double(i.value(j));\n");
+    result.append("      }\n");
+    result.append("    }\n");
+    result.append("    \n");
+    result.append("    // set class value to missing\n");
+    result.append("    s[i.classIndex()] = null;\n");
+    result.append("    \n");
+    result.append("    return " + className + ".classify(s);\n");
+    result.append("  }\n");
+
+    // getRevision
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns the revision string.\n");
+    result.append("   * \n");
+    result.append("   * @return        the revision\n");
+    result.append("   */\n");
+    result.append("  public String getRevision() {\n");
+    result.append("    return RevisionUtils.extract(\"1.0\");\n");
+    result.append("  }\n");
+
+    // toString
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns only the classnames and what classifier it is based on.\n");
+    result.append("   *\n");
+    result.append("   * @return a short description\n");
+    result.append("   */\n");
+    result.append("  public String toString() {\n");
+    result.append("    return \"Auto-generated classifier wrapper, based on "
+        + classifier.getClass().getName() + " (generated with Weka " + Version.VERSION + ").\\n"
+        + "\" + this.getClass().getName() + \"/" + className + "\";\n");
+    result.append("  }\n");
+
+    // main
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Runs the classfier from commandline.\n");
+    result.append("   *\n");
+    result.append("   * @param args the commandline arguments\n");
+    result.append("   */\n");
+    result.append("  public static void main(String args[]) {\n");
+    result.append("    runClassifier(new WekaWrapper(), args);\n");
+    result.append("  }\n");
+    result.append("}\n");
+
+    // actual classifier code
+    result.append("\n");
+    result.append(staticClassifier);
+
+    return result.toString();
+  }
+
+  /**
+   * Gets the number of test instances that had a known class value
+   * (actually the sum of the weights of test instances with known
+   * class value).
+   *
+   * @return the number of test instances with known class
+   */
+  public final double numInstances() {
+
+    return m_WithClass;
+  }
+
+  /**
+   * Gets the coverage of the test cases by the predicted regions at
+   * the confidence level specified when evaluation was performed.
+   *
+   * @return the coverage of the test cases by the predicted regions
+   */
+  public final double coverageOfTestCasesByPredictedRegions() {
+
+    if (!m_CoverageStatisticsAvailable)
+      return Double.NaN;
+
+    return 100 * m_TotalCoverage / m_WithClass;
+  }
+
+  /**
+   * Gets the average size of the predicted regions, relative to the
+   * range of the target in the training data, at the confidence level
+   * specified when evaluation was performed.
+   *
+   * @return the average size of the predicted regions
+   */
+  public final double sizeOfPredictedRegions() {
+
+    if (m_NoPriors || !m_CoverageStatisticsAvailable)
+      return Double.NaN;
+
+    return 100 * m_TotalSizeOfRegions / m_WithClass;
+  }
+
+  /**
+   * Gets the number of instances incorrectly classified (that is, for
+   * which an incorrect prediction was made). (Actually the sum of the
+   * weights of these instances)
+   *
+   * @return the number of incorrectly classified instances
+   */
+  public final double incorrect() {
+
+    return m_Incorrect;
+  }
+
+  /**
+   * Gets the percentage of instances incorrectly classified (that is,
+   * for which an incorrect prediction was made).
+   *
+   * @return the percent of incorrectly classified instances
+   * (between 0 and 100)
+   */
+  public final double pctIncorrect() {
+
+    return 100 * m_Incorrect / m_WithClass;
+  }
+
+  /**
+   * Gets the total cost, that is, the cost of each prediction times the
+   * weight of the instance, summed over all instances.
+   *
+   * @return the total cost
+   */
+  public final double totalCost() {
+
+    return m_TotalCost;
+  }
+
+  /**
+   * Gets the average cost, that is, total cost of misclassifications
+   * (incorrect plus unclassified) over the total number of instances.
+   *
+   * @return the average cost.
+   */
+  public final double avgCost() {
+
+    return m_TotalCost / m_WithClass;
+  }
+
+  /**
+   * Gets the number of instances correctly classified (that is, for
+   * which a correct prediction was made). (Actually the sum of the weights
+   * of these instances)
+   *
+   * @return the number of correctly classified instances
+   */
+  public final double correct() {
+
+    return m_Correct;
+  }
+
+  /**
+   * Gets the percentage of instances correctly classified (that is, for
+   * which a correct prediction was made).
+   *
+   * @return the percent of correctly classified instances (between 0 and 100)
+   */
+  public final double pctCorrect() {
+
+    return 100 * m_Correct / m_WithClass;
+  }
+
+  /**
+   * Gets the number of instances not classified (that is, for
+   * which no prediction was made by the classifier). (Actually the sum
+   * of the weights of these instances)
+   *
+   * @return the number of unclassified instances
+   */
+  public final double unclassified() {
+
+    return m_Unclassified;
+  }
+
+  /**
+   * Gets the percentage of instances not classified (that is, for
+   * which no prediction was made by the classifier).
+   *
+   * @return the percent of unclassified instances (between 0 and 100)
+   */
+  public final double pctUnclassified() {
+
+    return 100 * m_Unclassified / m_WithClass;
+  }
+
+  /**
+   * Returns the estimated error rate or the root mean squared error
+   * (if the class is numeric). If a cost matrix was given this
+   * error rate gives the average cost.
+   *
+   * @return the estimated error rate (between 0 and 1, or between 0 and
+   * maximum cost)
+   */
+  public final double errorRate() {
+
+    if (!m_ClassIsNominal) {
+      return Math.sqrt(m_SumSqrErr / (m_WithClass - m_Unclassified));
+    }
+    if (m_CostMatrix == null) {
+      return m_Incorrect / m_WithClass;
+    } else {
+      return avgCost();
+    }
+  }
+
+  /**
+   * Returns value of kappa statistic if class is nominal.
+   *
+   * @return the value of the kappa statistic
+   */
+  public final double kappa() {
+
+
+    double[] sumRows = new double[m_ConfusionMatrix.length];
+    double[] sumColumns = new double[m_ConfusionMatrix.length];
+    double sumOfWeights = 0;
+    for (int i = 0; i < m_ConfusionMatrix.length; i++) {
+      for (int j = 0; j < m_ConfusionMatrix.length; j++) {
+        sumRows[i] += m_ConfusionMatrix[i][j];
+        sumColumns[j] += m_ConfusionMatrix[i][j];
+        sumOfWeights += m_ConfusionMatrix[i][j];
+      }
+    }
+    double correct = 0, chanceAgreement = 0;
+    for (int i = 0; i < m_ConfusionMatrix.length; i++) {
+      chanceAgreement += (sumRows[i] * sumColumns[i]);
+      correct += m_ConfusionMatrix[i][i];
+    }
+    chanceAgreement /= (sumOfWeights * sumOfWeights);
+    correct /= sumOfWeights;
+
+    if (chanceAgreement < 1) {
+      return (correct - chanceAgreement) / (1 - chanceAgreement);
+    } else {
+      return 1;
+    }
+  }
+
+  /**
+   * Returns the correlation coefficient if the class is numeric.
+   *
+   * @return the correlation coefficient
+   * @throws Exception if class is not numeric
+   */
+  public final double correlationCoefficient() throws Exception {
+
+    if (m_ClassIsNominal) {
+      throw
+      new Exception("Can't compute correlation coefficient: " +
+      "class is nominal!");
+    }
+
+    double correlation = 0;
+    double varActual =
+      m_SumSqrClass - m_SumClass * m_SumClass /
+      (m_WithClass - m_Unclassified);
+    double varPredicted =
+      m_SumSqrPredicted - m_SumPredicted * m_SumPredicted /
+      (m_WithClass - m_Unclassified);
+    double varProd =
+      m_SumClassPredicted - m_SumClass * m_SumPredicted /
+      (m_WithClass - m_Unclassified);
+
+    if (varActual * varPredicted <= 0) {
+      correlation = 0.0;
+    } else {
+      correlation = varProd / Math.sqrt(varActual * varPredicted);
+    }
+
+    return correlation;
+  }
+
+  /**
+   * Returns the mean absolute error. Refers to the error of the
+   * predicted values for numeric classes, and the error of the
+   * predicted probability distribution for nominal classes.
+   *
+   * @return the mean absolute error
+   */
+  public final double meanAbsoluteError() {
+
+    return m_SumAbsErr / (m_WithClass - m_Unclassified);
+  }
+
+  /**
+   * Returns the mean absolute error of the prior.
+   *
+   * @return the mean absolute error
+   */
+  public final double meanPriorAbsoluteError() {
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return m_SumPriorAbsErr / m_WithClass;
+  }
+
+  /**
+   * Returns the relative absolute error.
+   *
+   * @return the relative absolute error
+   * @throws Exception if it can't be computed
+   */
+  public final double relativeAbsoluteError() throws Exception {
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return 100 * meanAbsoluteError() / meanPriorAbsoluteError();
+  }
+
+  /**
+   * Returns the root mean squared error.
+   *
+   * @return the root mean squared error
+   */
+  public final double rootMeanSquaredError() {
+
+    return Math.sqrt(m_SumSqrErr / (m_WithClass - m_Unclassified));
+  }
+
+  /**
+   * Returns the root mean prior squared error.
+   *
+   * @return the root mean prior squared error
+   */
+  public final double rootMeanPriorSquaredError() {
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return Math.sqrt(m_SumPriorSqrErr / m_WithClass);
+  }
+
+  /**
+   * Returns the root relative squared error if the class is numeric.
+   *
+   * @return the root relative squared error
+   */
+  public final double rootRelativeSquaredError() {
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return 100.0 * rootMeanSquaredError() / rootMeanPriorSquaredError();
+  }
+
+  /**
+   * Calculate the entropy of the prior distribution.
+   *
+   * @return the entropy of the prior distribution
+   * @throws Exception if the class is not nominal
+   */
+  public final double priorEntropy() throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw
+      new Exception("Can't compute entropy of class prior: " +
+      "class numeric!");
+    }
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    double entropy = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      entropy -= m_ClassPriors[i] / m_ClassPriorsSum *
+        Utils.log2(m_ClassPriors[i] / m_ClassPriorsSum);
+    }
+    return entropy;
+  }
+
+  /**
+   * Return the total Kononenko & Bratko Information score in bits.
+   *
+   * @return the K&B information score
+   * @throws Exception if the class is not nominal
+   */
+  public final double KBInformation() throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw
+      new Exception("Can't compute K&B Info score: " +
+      "class numeric!");
+    }
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return m_SumKBInfo;
+  }
+
+  /**
+   * Return the Kononenko & Bratko Information score in bits per
+   * instance.
+   *
+   * @return the K&B information score
+   * @throws Exception if the class is not nominal
+   */
+  public final double KBMeanInformation() throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw
+      new Exception("Can't compute K&B Info score: class numeric!");
+    }
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return m_SumKBInfo / (m_WithClass - m_Unclassified);
+  }
+
+  /**
+   * Return the Kononenko & Bratko Relative Information score.
+   *
+   * @return the K&B relative information score
+   * @throws Exception if the class is not nominal
+   */
+  public final double KBRelativeInformation() throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw
+      new Exception("Can't compute K&B Info score: " +
+      "class numeric!");
+    }
+
+    if (m_NoPriors)
+      return Double.NaN;
+
+    return 100.0 * KBInformation() / priorEntropy();
+  }
+
+  /**
+   * Returns the total entropy for the null model.
+   *
+   * @return the total null model entropy
+   */
+  public final double SFPriorEntropy() {
+
+    if (m_NoPriors || !m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return m_SumPriorEntropy;
+  }
+
+  /**
+   * Returns the entropy per instance for the null model.
+   *
+   * @return the null model entropy per instance
+   */
+  public final double SFMeanPriorEntropy() {
+
+    if (m_NoPriors || !m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return m_SumPriorEntropy / m_WithClass;
+  }
+
+  /**
+   * Returns the total entropy for the scheme.
+   *
+   * @return the total scheme entropy
+   */
+  public final double SFSchemeEntropy() {
+
+    if (!m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return m_SumSchemeEntropy;
+  }
+
+  /**
+   * Returns the entropy per instance for the scheme.
+   *
+   * @return the scheme entropy per instance
+   */
+  public final double SFMeanSchemeEntropy() {
+
+    if (!m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return m_SumSchemeEntropy / (m_WithClass - m_Unclassified);
+  }
+
+  /**
+   * Returns the total SF, which is the null model entropy minus
+   * the scheme entropy.
+   *
+   * @return the total SF
+   */
+  public final double SFEntropyGain() {
+
+    if (m_NoPriors || !m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return m_SumPriorEntropy - m_SumSchemeEntropy;
+  }
+
+  /**
+   * Returns the SF per instance, which is the null model entropy
+   * minus the scheme entropy, per instance.
+   *
+   * @return the SF per instance
+   */
+  public final double SFMeanEntropyGain() {
+
+    if (m_NoPriors || !m_ComplexityStatisticsAvailable)
+      return Double.NaN;
+
+    return (m_SumPriorEntropy - m_SumSchemeEntropy) /
+      (m_WithClass - m_Unclassified);
+  }
+
+  /**
+   * Output the cumulative margin distribution as a string suitable
+   * for input for gnuplot or similar package.
+   *
+   * @return the cumulative margin distribution
+   * @throws Exception if the class attribute is nominal
+   */
+  public String toCumulativeMarginDistributionString() throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw new Exception("Class must be nominal for margin distributions");
+    }
+    String result = "";
+    double cumulativeCount = 0;
+    double margin;
+    for(int i = 0; i <= k_MarginResolution; i++) {
+      if (m_MarginCounts[i] != 0) {
+        cumulativeCount += m_MarginCounts[i];
+        margin = (double)i * 2.0 / k_MarginResolution - 1.0;
+        result = result + Utils.doubleToString(margin, 7, 3) + ' '
+          + Utils.doubleToString(cumulativeCount * 100
+              / m_WithClass, 7, 3) + '\n';
+      } else if (i == 0) {
+        result = Utils.doubleToString(-1.0, 7, 3) + ' '
+          + Utils.doubleToString(0, 7, 3) + '\n';
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Calls toSummaryString() with no title and no complexity stats.
+   *
+   * @return a summary description of the classifier evaluation
+   */
+  public String toSummaryString() {
+
+    return toSummaryString("", false);
+  }
+
+  /**
+   * Calls toSummaryString() with a default title.
+   *
+   * @param printComplexityStatistics if true, complexity statistics are
+   * returned as well
+   * @return the summary string
+   */
+  public String toSummaryString(boolean printComplexityStatistics) {
+
+    return toSummaryString("=== Summary ===\n", printComplexityStatistics);
+  }
+
+  /**
+   * Outputs the performance statistics in summary form. Lists
+   * number (and percentage) of instances classified correctly,
+   * incorrectly and unclassified. Outputs the total number of
+   * instances classified, and the number of instances (if any)
+   * that had no class value provided.
+   *
+   * @param title the title for the statistics
+   * @param printComplexityStatistics if true, complexity statistics are
+   * returned as well
+   * @return the summary as a String
+   */
+  public String toSummaryString(String title,
+      boolean printComplexityStatistics) {
+
+    StringBuffer text = new StringBuffer();
+
+    if (printComplexityStatistics && m_NoPriors) {
+      printComplexityStatistics = false;
+      System.err.println("Priors disabled, cannot print complexity statistics!");
+    }
+
+    text.append(title + "\n");
+    try {
+      if (m_WithClass > 0) {
+        if (m_ClassIsNominal) {
+
+          text.append("Correctly Classified Instances     ");
+          text.append(Utils.doubleToString(correct(), 12, 4) + "     " +
+              Utils.doubleToString(pctCorrect(),
+                12, 4) + " %\n");
+          text.append("Incorrectly Classified Instances   ");
+          text.append(Utils.doubleToString(incorrect(), 12, 4) + "     " +
+              Utils.doubleToString(pctIncorrect(),
+                12, 4) + " %\n");
+          text.append("Kappa statistic                    ");
+          text.append(Utils.doubleToString(kappa(), 12, 4) + "\n");
+
+          if (m_CostMatrix != null) {
+            text.append("Total Cost                         ");
+            text.append(Utils.doubleToString(totalCost(), 12, 4) + "\n");
+            text.append("Average Cost                       ");
+            text.append(Utils.doubleToString(avgCost(), 12, 4) + "\n");
+          }
+          if (printComplexityStatistics) {
+            text.append("K&B Relative Info Score            ");
+            text.append(Utils.doubleToString(KBRelativeInformation(), 12, 4)
+                + " %\n");
+            text.append("K&B Information Score              ");
+            text.append(Utils.doubleToString(KBInformation(), 12, 4)
+                + " bits");
+            text.append(Utils.doubleToString(KBMeanInformation(), 12, 4)
+                + " bits/instance\n");
+          }
+        } else {
+          text.append("Correlation coefficient            ");
+          text.append(Utils.doubleToString(correlationCoefficient(), 12 , 4) +
+              "\n");
+        }
+        if (printComplexityStatistics && m_ComplexityStatisticsAvailable) {
+          text.append("Class complexity | order 0         ");
+          text.append(Utils.doubleToString(SFPriorEntropy(), 12, 4)
+              + " bits");
+          text.append(Utils.doubleToString(SFMeanPriorEntropy(), 12, 4)
+              + " bits/instance\n");
+          text.append("Class complexity | scheme          ");
+          text.append(Utils.doubleToString(SFSchemeEntropy(), 12, 4)
+              + " bits");
+          text.append(Utils.doubleToString(SFMeanSchemeEntropy(), 12, 4)
+              + " bits/instance\n");
+          text.append("Complexity improvement     (Sf)    ");
+          text.append(Utils.doubleToString(SFEntropyGain(), 12, 4) + " bits");
+          text.append(Utils.doubleToString(SFMeanEntropyGain(), 12, 4)
+              + " bits/instance\n");
+        }
+
+        text.append("Mean absolute error                ");
+        text.append(Utils.doubleToString(meanAbsoluteError(), 12, 4)
+            + "\n");
+        text.append("Root mean squared error            ");
+        text.append(Utils.
+            doubleToString(rootMeanSquaredError(), 12, 4)
+            + "\n");
+        if (!m_NoPriors) {
+          text.append("Relative absolute error            ");
+          text.append(Utils.doubleToString(relativeAbsoluteError(),
+                12, 4) + " %\n");
+          text.append("Root relative squared error        ");
+          text.append(Utils.doubleToString(rootRelativeSquaredError(),
+                12, 4) + " %\n");
+        }
+        if (m_CoverageStatisticsAvailable) {
+          text.append("Coverage of cases (" + Utils.doubleToString(m_ConfLevel, 4, 2) + " level)     ");
+          text.append(Utils.doubleToString(coverageOfTestCasesByPredictedRegions(),
+                12, 4) + " %\n");
+          if (!m_NoPriors) {
+            text.append("Mean rel. region size (" + Utils.doubleToString(m_ConfLevel, 4, 2) + " level) ");
+            text.append(Utils.doubleToString(sizeOfPredictedRegions(), 12, 4) + " %\n");
+          }
+        }
+      }
+      if (Utils.gr(unclassified(), 0)) {
+        text.append("UnClassified Instances             ");
+        text.append(Utils.doubleToString(unclassified(), 12,4) +  "     " +
+            Utils.doubleToString(pctUnclassified(),
+              12, 4) + " %\n");
+      }
+      text.append("Total Number of Instances          ");
+      text.append(Utils.doubleToString(m_WithClass, 12, 4) + "\n");
+      if (m_MissingClass > 0) {
+        text.append("Ignored Class Unknown Instances            ");
+        text.append(Utils.doubleToString(m_MissingClass, 12, 4) + "\n");
+      }
+    } catch (Exception ex) {
+      // Should never occur since the class is known to be nominal
+      // here
+      System.err.println("Arggh - Must be a bug in Evaluation class");
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Calls toMatrixString() with a default title.
+   *
+   * @return the confusion matrix as a string
+   * @throws Exception if the class is numeric
+   */
+  public String toMatrixString() throws Exception {
+
+    return toMatrixString("=== Confusion Matrix ===\n");
+  }
+
+  /**
+   * Outputs the performance statistics as a classification confusion
+   * matrix. For each class value, shows the distribution of
+   * predicted class values.
+   *
+   * @param title the title for the confusion matrix
+   * @return the confusion matrix as a String
+   * @throws Exception if the class is numeric
+   */
+  public String toMatrixString(String title) throws Exception {
+
+    StringBuffer text = new StringBuffer();
+    char [] IDChars = {'a','b','c','d','e','f','g','h','i','j',
+      'k','l','m','n','o','p','q','r','s','t',
+      'u','v','w','x','y','z'};
+    int IDWidth;
+    boolean fractional = false;
+
+    if (!m_ClassIsNominal) {
+      throw new Exception("Evaluation: No confusion matrix possible!");
+    }
+
+    // Find the maximum value in the matrix
+    // and check for fractional display requirement
+    double maxval = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      for(int j = 0; j < m_NumClasses; j++) {
+        double current = m_ConfusionMatrix[i][j];
+        if (current < 0) {
+          current *= -10;
+        }
+        if (current > maxval) {
+          maxval = current;
+        }
+        double fract = current - Math.rint(current);
+        if (!fractional && ((Math.log(fract) / Math.log(10)) >= -2)) {
+          fractional = true;
+        }
+      }
+    }
+
+    IDWidth = 1 + Math.max((int)(Math.log(maxval) / Math.log(10)
+          + (fractional ? 3 : 0)),
+        (int)(Math.log(m_NumClasses) /
+          Math.log(IDChars.length)));
+    text.append(title).append("\n");
+    for(int i = 0; i < m_NumClasses; i++) {
+      if (fractional) {
+        text.append(" ").append(num2ShortID(i,IDChars,IDWidth - 3))
+          .append("   ");
+      } else {
+        text.append(" ").append(num2ShortID(i,IDChars,IDWidth));
+      }
+    }
+    text.append("   <-- classified as\n");
+    for(int i = 0; i< m_NumClasses; i++) {
+      for(int j = 0; j < m_NumClasses; j++) {
+        text.append(" ").append(
+            Utils.doubleToString(m_ConfusionMatrix[i][j],
+              IDWidth,
+              (fractional ? 2 : 0)));
+      }
+      text.append(" | ").append(num2ShortID(i,IDChars,IDWidth))
+        .append(" = ").append(m_ClassNames[i]).append("\n");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Generates a breakdown of the accuracy for each class (with default title),
+   * incorporating various information-retrieval statistics, such as
+   * true/false positive rate, precision/recall/F-Measure.  Should be
+   * useful for ROC curves, recall/precision curves.
+   *
+   * @return the statistics presented as a string
+   * @throws Exception if class is not nominal
+   */
+  public String toClassDetailsString() throws Exception {
+
+    return toClassDetailsString("=== Detailed Accuracy By Class ===\n");
+  }
+
+  /**
+   * Generates a breakdown of the accuracy for each class,
+   * incorporating various information-retrieval statistics, such as
+   * true/false positive rate, precision/recall/F-Measure.  Should be
+   * useful for ROC curves, recall/precision curves.
+   *
+   * @param title the title to prepend the stats string with
+   * @return the statistics presented as a string
+   * @throws Exception if class is not nominal
+   */
+  public String toClassDetailsString(String title) throws Exception {
+
+    if (!m_ClassIsNominal) {
+      throw new Exception("Evaluation: No per class statistics possible!");
+    }
+
+    StringBuffer text = new StringBuffer(title
+        + "\n               TP Rate   FP Rate"
+        + "   Precision   Recall"
+        + "  F-Measure   ROC Area  Class\n");
+    for(int i = 0; i < m_NumClasses; i++) {
+      text.append("               " + Utils.doubleToString(truePositiveRate(i), 7, 3))
+        .append("   ");
+      text.append(Utils.doubleToString(falsePositiveRate(i), 7, 3))
+        .append("    ");
+      text.append(Utils.doubleToString(precision(i), 7, 3))
+        .append("   ");
+      text.append(Utils.doubleToString(recall(i), 7, 3))
+        .append("   ");
+      text.append(Utils.doubleToString(fMeasure(i), 7, 3))
+        .append("    ");
+
+      double rocVal = areaUnderROC(i);
+      if (Utils.isMissingValue(rocVal)) {
+        text.append("  ?    ")
+          .append("    ");
+      } else {
+        text.append(Utils.doubleToString(rocVal, 7, 3))
+          .append("    ");
+      }
+      text.append(m_ClassNames[i]).append('\n');
+    }
+
+    text.append("Weighted Avg.  " + Utils.doubleToString(weightedTruePositiveRate(), 7, 3));
+    text.append("   " + Utils.doubleToString(weightedFalsePositiveRate(), 7 ,3));
+    text.append("    " + Utils.doubleToString(weightedPrecision(), 7 ,3));
+    text.append("   " + Utils.doubleToString(weightedRecall(), 7 ,3));
+    text.append("   " + Utils.doubleToString(weightedFMeasure(), 7 ,3));
+    text.append("    " + Utils.doubleToString(weightedAreaUnderROC(), 7 ,3));
+    text.append("\n");
+
+    return text.toString();
+  }
+
+  /**
+   * Calculate the number of true positives with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified positives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the true positive rate
+   */
+  public double numTruePositives(int classIndex) {
+
+    double correct = 0;
+    for (int j = 0; j < m_NumClasses; j++) {
+      if (j == classIndex) {
+        correct += m_ConfusionMatrix[classIndex][j];
+      }
+    }
+    return correct;
+  }
+
+  /**
+   * Calculate the true positive rate with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *       total positives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the true positive rate
+   */
+  public double truePositiveRate(int classIndex) {
+
+    double correct = 0, total = 0;
+    for (int j = 0; j < m_NumClasses; j++) {
+      if (j == classIndex) {
+        correct += m_ConfusionMatrix[classIndex][j];
+      }
+      total += m_ConfusionMatrix[classIndex][j];
+    }
+    if (total == 0) {
+      return 0;
+    }
+    return correct / total;
+  }
+
+  /**
+   * Calculates the weighted (by class size) true positive rate.
+   *
+   * @return the weighted true positive rate.
+   */
+  public double weightedTruePositiveRate() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double truePosTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = truePositiveRate(i);
+      truePosTotal += (temp * classCounts[i]);
+    }
+
+    return truePosTotal / classCountSum;
+  }
+
+  /**
+   * Calculate the number of true negatives with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified negatives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the true positive rate
+   */
+  public double numTrueNegatives(int classIndex) {
+
+    double correct = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i != classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j != classIndex) {
+            correct += m_ConfusionMatrix[i][j];
+          }
+        }
+      }
+    }
+    return correct;
+  }
+
+  /**
+   * Calculate the true negative rate with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified negatives
+   * ------------------------------
+   *       total negatives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the true positive rate
+   */
+  public double trueNegativeRate(int classIndex) {
+
+    double correct = 0, total = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i != classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j != classIndex) {
+            correct += m_ConfusionMatrix[i][j];
+          }
+          total += m_ConfusionMatrix[i][j];
+        }
+      }
+    }
+    if (total == 0) {
+      return 0;
+    }
+    return correct / total;
+  }
+
+  /**
+   * Calculates the weighted (by class size) true negative rate.
+   *
+   * @return the weighted true negative rate.
+   */
+  public double weightedTrueNegativeRate() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double trueNegTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = trueNegativeRate(i);
+      trueNegTotal += (temp * classCounts[i]);
+    }
+
+    return trueNegTotal / classCountSum;
+  }
+
+  /**
+   * Calculate number of false positives with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * incorrectly classified negatives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the false positive rate
+   */
+  public double numFalsePositives(int classIndex) {
+
+    double incorrect = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i != classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j == classIndex) {
+            incorrect += m_ConfusionMatrix[i][j];
+          }
+        }
+      }
+    }
+    return incorrect;
+  }
+
+  /**
+   * Calculate the false positive rate with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * incorrectly classified negatives
+   * --------------------------------
+   *        total negatives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the false positive rate
+   */
+  public double falsePositiveRate(int classIndex) {
+
+    double incorrect = 0, total = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i != classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j == classIndex) {
+            incorrect += m_ConfusionMatrix[i][j];
+          }
+          total += m_ConfusionMatrix[i][j];
+        }
+      }
+    }
+    if (total == 0) {
+      return 0;
+    }
+    return incorrect / total;
+  }
+
+  /**
+   * Calculates the weighted (by class size) false positive rate.
+   *
+   * @return the weighted false positive rate.
+   */
+  public double weightedFalsePositiveRate() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double falsePosTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = falsePositiveRate(i);
+      falsePosTotal += (temp * classCounts[i]);
+    }
+
+    return falsePosTotal / classCountSum;
+  }
+
+
+
+  /**
+   * Calculate number of false negatives with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * incorrectly classified positives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the false positive rate
+   */
+  public double numFalseNegatives(int classIndex) {
+
+    double incorrect = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i == classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j != classIndex) {
+            incorrect += m_ConfusionMatrix[i][j];
+          }
+        }
+      }
+    }
+    return incorrect;
+  }
+
+  /**
+   * Calculate the false negative rate with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * incorrectly classified positives
+   * --------------------------------
+   *        total positives
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the false positive rate
+   */
+  public double falseNegativeRate(int classIndex) {
+
+    double incorrect = 0, total = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i == classIndex) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (j != classIndex) {
+            incorrect += m_ConfusionMatrix[i][j];
+          }
+          total += m_ConfusionMatrix[i][j];
+        }
+      }
+    }
+    if (total == 0) {
+      return 0;
+    }
+    return incorrect / total;
+  }
+
+  /**
+   * Calculates the weighted (by class size) false negative rate.
+   *
+   * @return the weighted false negative rate.
+   */
+  public double weightedFalseNegativeRate() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double falseNegTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = falseNegativeRate(i);
+      falseNegTotal += (temp * classCounts[i]);
+    }
+
+    return falseNegTotal / classCountSum;
+  }
+
+  /**
+   * Calculate the recall with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *       total positives
+   * </pre><p/>
+   * (Which is also the same as the truePositiveRate.)
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the recall
+   */
+  public double recall(int classIndex) {
+
+    return truePositiveRate(classIndex);
+  }
+
+  /**
+   * Calculates the weighted (by class size) recall.
+   *
+   * @return the weighted recall.
+   */
+  public double weightedRecall() {
+    return weightedTruePositiveRate();
+  }
+
+  /**
+   * Calculate the precision with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *  total predicted as positive
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the precision
+   */
+  public double precision(int classIndex) {
+
+    double correct = 0, total = 0;
+    for (int i = 0; i < m_NumClasses; i++) {
+      if (i == classIndex) {
+        correct += m_ConfusionMatrix[i][classIndex];
+      }
+      total += m_ConfusionMatrix[i][classIndex];
+    }
+    if (total == 0) {
+      return 0;
+    }
+    return correct / total;
+  }
+
+  /**
+   * Calculates the weighted (by class size) false precision.
+   *
+   * @return the weighted precision.
+   */
+  public double weightedPrecision() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double precisionTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = precision(i);
+      precisionTotal += (temp * classCounts[i]);
+    }
+
+    return precisionTotal / classCountSum;
+  }
+
+  /**
+   * Calculate the F-Measure with respect to a particular class.
+   * This is defined as<p/>
+   * <pre>
+   * 2 * recall * precision
+   * ----------------------
+   *   recall + precision
+   * </pre>
+   *
+   * @param classIndex the index of the class to consider as "positive"
+   * @return the F-Measure
+   */
+  public double fMeasure(int classIndex) {
+
+    double precision = precision(classIndex);
+    double recall = recall(classIndex);
+    if ((precision + recall) == 0) {
+      return 0;
+    }
+    return 2 * precision * recall / (precision + recall);
+  }
+
+  /**
+   * Calculates the macro weighted (by class size) average
+   * F-Measure.
+   *
+   * @return the weighted F-Measure.
+   */
+  public double weightedFMeasure() {
+    double[] classCounts = new double[m_NumClasses];
+    double classCountSum = 0;
+
+    for (int i = 0; i < m_NumClasses; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+        classCounts[i] += m_ConfusionMatrix[i][j];
+      }
+      classCountSum += classCounts[i];
+    }
+
+    double fMeasureTotal = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      double temp = fMeasure(i);
+      fMeasureTotal += (temp * classCounts[i]);
+    }
+
+    return fMeasureTotal / classCountSum;
+  }
+
+  /**
+   * Unweighted macro-averaged F-measure. If some classes not present in the
+   * test set, they're just skipped (since recall is undefined there anyway) .
+   *
+   * @return unweighted macro-averaged F-measure.
+   * */
+  public double unweightedMacroFmeasure() {
+    weka.experiment.Stats rr = new weka.experiment.Stats();
+    for (int c = 0; c < m_NumClasses; c++) {
+      // skip if no testing positive cases of this class
+      if (numTruePositives(c)+numFalseNegatives(c) > 0) {
+        rr.add(fMeasure(c));
+      }
+    }
+    rr.calculateDerived();
+    return rr.mean;
+  }
+
+  /**
+   * Unweighted micro-averaged F-measure. If some classes not present in the
+   * test set, they have no effect.
+   *
+   * Note: if the test set is *single-label*, then this is the same as accuracy.
+   *
+   * @return unweighted micro-averaged F-measure.
+   */
+  public double unweightedMicroFmeasure() {
+    double tp = 0;
+    double fn = 0;
+    double fp = 0;
+    for (int c = 0; c < m_NumClasses; c++) {
+      tp += numTruePositives(c);
+      fn += numFalseNegatives(c);
+      fp += numFalsePositives(c);
+    }
+    return 2*tp / (2*tp + fn + fp);
+  }
+
+  /**
+   * Sets the class prior probabilities.
+   *
+   * @param train the training instances used to determine the prior probabilities
+   * @throws Exception if the class attribute of the instances is not set
+   */
+  public void setPriors(Instances train) throws Exception {
+
+    m_NoPriors = false;
+
+    if (!m_ClassIsNominal) {
+
+      m_NumTrainClassVals = 0;
+      m_TrainClassVals = null;
+      m_TrainClassWeights = null;
+      m_PriorEstimator = null;
+
+      m_MinTarget = Double.MAX_VALUE;
+      m_MaxTarget = -Double.MAX_VALUE;
+
+      for (int i = 0; i < train.numInstances(); i++) {
+        Instance currentInst = train.instance(i);
+        if (!currentInst.classIsMissing()) {
+          addNumericTrainClass(currentInst.classValue(), currentInst.weight());
+        }
+      }
+
+      m_ClassPriors[0] = m_ClassPriorsSum = 0;
+      for (int i = 0; i < train.numInstances(); i++) {
+        if (!train.instance(i).classIsMissing()) {
+          m_ClassPriors[0] += train.instance(i).classValue() * train.instance(i).weight();
+          m_ClassPriorsSum += train.instance(i).weight();
+        }
+      }
+
+    } else {
+      for (int i = 0; i < m_NumClasses; i++) {
+        m_ClassPriors[i] = 1;
+      }
+      m_ClassPriorsSum = m_NumClasses;
+      for (int i = 0; i < train.numInstances(); i++) {
+        if (!train.instance(i).classIsMissing()) {
+          m_ClassPriors[(int)train.instance(i).classValue()] +=
+            train.instance(i).weight();
+          m_ClassPriorsSum += train.instance(i).weight();
+        }
+      }
+      m_MaxTarget = m_NumClasses;
+      m_MinTarget = 0;
+    }
+  }
+
+  /**
+   * Get the current weighted class counts.
+   *
+   * @return the weighted class counts
+   */
+  public double [] getClassPriors() {
+    return m_ClassPriors;
+  }
+
+  /**
+   * Updates the class prior probabilities or the mean respectively (when incrementally
+   * training).
+   *
+   * @param instance the new training instance seen
+   * @throws Exception if the class of the instance is not set
+   */
+  public void updatePriors(Instance instance) throws Exception {
+    if (!instance.classIsMissing()) {
+      if (!m_ClassIsNominal) {
+        addNumericTrainClass(instance.classValue(), instance.weight());
+        m_ClassPriors[0] += instance.classValue() * instance.weight();
+        m_ClassPriorsSum += instance.weight();
+      } else {
+        m_ClassPriors[(int)instance.classValue()] += instance.weight();
+        m_ClassPriorsSum += instance.weight();
+      }
+    }
+  }
+
+  /**
+   * disables the use of priors, e.g., in case of de-serialized schemes
+   * that have no access to the original training set, but are evaluated
+   * on a set set.
+   */
+  public void useNoPriors() {
+    m_NoPriors = true;
+  }
+
+  /**
+   * Tests whether the current evaluation object is equal to another
+   * evaluation object.
+   *
+   * @param obj the object to compare against
+   * @return true if the two objects are equal
+   */
+  public boolean equals(Object obj) {
+
+    if ((obj == null) || !(obj.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    Evaluation cmp = (Evaluation) obj;
+    if (m_ClassIsNominal != cmp.m_ClassIsNominal) return false;
+    if (m_NumClasses != cmp.m_NumClasses) return false;
+
+    if (m_Incorrect != cmp.m_Incorrect) return false;
+    if (m_Correct != cmp.m_Correct) return false;
+    if (m_Unclassified != cmp.m_Unclassified) return false;
+    if (m_MissingClass != cmp.m_MissingClass) return false;
+    if (m_WithClass != cmp.m_WithClass) return false;
+
+    if (m_SumErr != cmp.m_SumErr) return false;
+    if (m_SumAbsErr != cmp.m_SumAbsErr) return false;
+    if (m_SumSqrErr != cmp.m_SumSqrErr) return false;
+    if (m_SumClass != cmp.m_SumClass) return false;
+    if (m_SumSqrClass != cmp.m_SumSqrClass) return false;
+    if (m_SumPredicted != cmp.m_SumPredicted) return false;
+    if (m_SumSqrPredicted != cmp.m_SumSqrPredicted) return false;
+    if (m_SumClassPredicted != cmp.m_SumClassPredicted) return false;
+
+    if (m_ClassIsNominal) {
+      for (int i = 0; i < m_NumClasses; i++) {
+        for (int j = 0; j < m_NumClasses; j++) {
+          if (m_ConfusionMatrix[i][j] != cmp.m_ConfusionMatrix[i][j]) {
+            return false;
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Make up the help string giving all the command line options.
+   *
+   * @param classifier the classifier to include options for
+   * @param globalInfo include the global information string
+   * for the classifier (if available).
+   * @return a string detailing the valid command line options
+   */
+  protected static String makeOptionString(Classifier classifier,
+                                           boolean globalInfo) {
+
+    StringBuffer optionsText = new StringBuffer("");
+
+    // General options
+    optionsText.append("\n\nGeneral options:\n\n");
+    optionsText.append("-h or -help\n");
+    optionsText.append("\tOutput help information.\n");
+    optionsText.append("-synopsis or -info\n");
+    optionsText.append("\tOutput synopsis for classifier (use in conjunction "
+        + " with -h)\n");
+    optionsText.append("-t <name of training file>\n");
+    optionsText.append("\tSets training file.\n");
+    optionsText.append("-T <name of test file>\n");
+    optionsText.append("\tSets test file. If missing, a cross-validation will be performed\n");
+    optionsText.append("\ton the training data.\n");
+    optionsText.append("-c <class index>\n");
+    optionsText.append("\tSets index of class attribute (default: last).\n");
+    optionsText.append("-x <number of folds>\n");
+    optionsText.append("\tSets number of folds for cross-validation (default: 10).\n");
+    optionsText.append("-no-cv\n");
+    optionsText.append("\tDo not perform any cross validation.\n");
+    optionsText.append("-split-percentage <percentage>\n");
+    optionsText.append("\tSets the percentage for the train/test set split, e.g., 66.\n");
+    optionsText.append("-preserve-order\n");
+    optionsText.append("\tPreserves the order in the percentage split.\n");
+    optionsText.append("-s <random number seed>\n");
+    optionsText.append("\tSets random number seed for cross-validation or percentage split\n");
+    optionsText.append("\t(default: 1).\n");
+    optionsText.append("-m <name of file with cost matrix>\n");
+    optionsText.append("\tSets file with cost matrix.\n");
+    optionsText.append("-l <name of input file>\n");
+    optionsText.append("\tSets model input file. In case the filename ends with '.xml',\n");
+    optionsText.append("\ta PMML file is loaded or, if that fails, options are loaded\n");
+    optionsText.append("\tfrom the XML file.\n");
+    optionsText.append("-d <name of output file>\n");
+    optionsText.append("\tSets model output file. In case the filename ends with '.xml',\n");
+    optionsText.append("\tonly the options are saved to the XML file, not the model.\n");
+    optionsText.append("-v\n");
+    optionsText.append("\tOutputs no statistics for training data.\n");
+    optionsText.append("-o\n");
+    optionsText.append("\tOutputs statistics only, not the classifier.\n");
+    optionsText.append("-i\n");
+    optionsText.append("\tOutputs detailed information-retrieval");
+    optionsText.append(" statistics for each class.\n");
+    optionsText.append("-k\n");
+    optionsText.append("\tOutputs information-theoretic statistics.\n");
+    optionsText.append("-classifications \"weka.classifiers.evaluation.output.prediction.AbstractOutput + options\"\n");
+    optionsText.append("\tUses the specified class for generating the classification output.\n");
+    optionsText.append("\tE.g.: " + PlainText.class.getName() + "\n");
+    optionsText.append("-p range\n");
+    optionsText.append("\tOutputs predictions for test instances (or the train instances if\n");
+    optionsText.append("\tno test instances provided and -no-cv is used), along with the \n");
+    optionsText.append("\tattributes in the specified range (and nothing else). \n");
+    optionsText.append("\tUse '-p 0' if no attributes are desired.\n");
+    optionsText.append("\tDeprecated: use \"-classifications ...\" instead.\n");
+    optionsText.append("-distribution\n");
+    optionsText.append("\tOutputs the distribution instead of only the prediction\n");
+    optionsText.append("\tin conjunction with the '-p' option (only nominal classes).\n");
+    optionsText.append("\tDeprecated: use \"-classifications ...\" instead.\n");
+    optionsText.append("-r\n");
+    optionsText.append("\tOnly outputs cumulative margin distribution.\n");
+    if (classifier instanceof Sourcable) {
+      optionsText.append("-z <class name>\n");
+      optionsText.append("\tOnly outputs the source representation"
+          + " of the classifier,\n\tgiving it the supplied"
+          + " name.\n");
+    }
+    if (classifier instanceof Drawable) {
+      optionsText.append("-g\n");
+      optionsText.append("\tOnly outputs the graph representation"
+          + " of the classifier.\n");
+    }
+    optionsText.append("-xml filename | xml-string\n");
+    optionsText.append("\tRetrieves the options from the XML-data instead of the "
+        + "command line.\n");
+    optionsText.append("-threshold-file <file>\n");
+    optionsText.append("\tThe file to save the threshold data to.\n"
+        + "\tThe format is determined by the extensions, e.g., '.arff' for ARFF \n"
+        + "\tformat or '.csv' for CSV.\n");
+    optionsText.append("-threshold-label <label>\n");
+    optionsText.append("\tThe class label to determine the threshold data for\n"
+        + "\t(default is the first label)\n");
+
+    // Get scheme-specific options
+    if (classifier instanceof OptionHandler) {
+      optionsText.append("\nOptions specific to "
+          + classifier.getClass().getName()
+          + ":\n\n");
+      Enumeration enu = ((OptionHandler)classifier).listOptions();
+      while (enu.hasMoreElements()) {
+        Option option = (Option) enu.nextElement();
+        optionsText.append(option.synopsis() + '\n');
+        optionsText.append(option.description() + "\n");
+      }
+    }
+
+    // Get global information (if available)
+    if (globalInfo) {
+      try {
+        String gi = getGlobalInfo(classifier);
+        optionsText.append(gi);
+      } catch (Exception ex) {
+        // quietly ignore
+      }
+    }
+    return optionsText.toString();
+  }
+
+  /**
+   * Return the global info (if it exists) for the supplied classifier.
+   *
+   * @param classifier the classifier to get the global info for
+   * @return the global info (synopsis) for the classifier
+   * @throws Exception if there is a problem reflecting on the classifier
+   */
+  protected static String getGlobalInfo(Classifier classifier) throws Exception {
+    BeanInfo bi = Introspector.getBeanInfo(classifier.getClass());
+    MethodDescriptor[] methods;
+    methods = bi.getMethodDescriptors();
+    Object[] args = {};
+    String result = "\nSynopsis for " + classifier.getClass().getName()
+      + ":\n\n";
+
+    for (int i = 0; i < methods.length; i++) {
+      String name = methods[i].getDisplayName();
+      Method meth = methods[i].getMethod();
+      if (name.equals("globalInfo")) {
+        String globalInfo = (String)(meth.invoke(classifier, args));
+        result += globalInfo;
+        break;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Method for generating indices for the confusion matrix.
+   *
+   * @param num 	integer to format
+   * @param IDChars	the characters to use
+   * @param IDWidth	the width of the entry
+   * @return 		the formatted integer as a string
+   */
+  protected String num2ShortID(int num, char[] IDChars, int IDWidth) {
+
+    char ID [] = new char [IDWidth];
+    int i;
+
+    for(i = IDWidth - 1; i >=0; i--) {
+      ID[i] = IDChars[num % IDChars.length];
+      num = num / IDChars.length - 1;
+      if (num < 0) {
+        break;
+      }
+    }
+    for(i--; i >= 0; i--) {
+      ID[i] = ' ';
+    }
+
+    return new String(ID);
+  }
+
+  /**
+   * Convert a single prediction into a probability distribution
+   * with all zero probabilities except the predicted value which
+   * has probability 1.0.
+   *
+   * @param predictedClass the index of the predicted class
+   * @return the probability distribution
+   */
+  protected double [] makeDistribution(double predictedClass) {
+
+    double [] result = new double [m_NumClasses];
+    if (Utils.isMissingValue(predictedClass)) {
+      return result;
+    }
+    if (m_ClassIsNominal) {
+      result[(int)predictedClass] = 1.0;
+    } else {
+      result[0] = predictedClass;
+    }
+    return result;
+  }
+
+  /**
+   * Updates all the statistics about a classifiers performance for
+   * the current test instance.
+   *
+   * @param predictedDistribution the probabilities assigned to
+   * each class
+   * @param instance the instance to be classified
+   * @throws Exception if the class of the instance is not
+   * set
+   */
+  protected void updateStatsForClassifier(double [] predictedDistribution,
+      Instance instance)
+  throws Exception {
+
+    int actualClass = (int)instance.classValue();
+
+    if (!instance.classIsMissing()) {
+      updateMargins(predictedDistribution, actualClass, instance.weight());
+
+      // Determine the predicted class (doesn't detect multiple
+      // classifications)
+      int predictedClass = -1;
+      double bestProb = 0.0;
+      for(int i = 0; i < m_NumClasses; i++) {
+        if (predictedDistribution[i] > bestProb) {
+          predictedClass = i;
+          bestProb = predictedDistribution[i];
+        }
+      }
+
+      m_WithClass += instance.weight();
+
+      // Determine misclassification cost
+      if (m_CostMatrix != null) {
+        if (predictedClass < 0) {
+          // For missing predictions, we assume the worst possible cost.
+          // This is pretty harsh.
+          // Perhaps we could take the negative of the cost of a correct
+          // prediction (-m_CostMatrix.getElement(actualClass,actualClass)),
+          // although often this will be zero
+          m_TotalCost += instance.weight() * m_CostMatrix.getMaxCost(actualClass, instance);
+        } else {
+          m_TotalCost += instance.weight() * m_CostMatrix.getElement(actualClass, predictedClass,
+              instance);
+        }
+      }
+
+      // Update counts when no class was predicted
+      if (predictedClass < 0) {
+        m_Unclassified += instance.weight();
+        return;
+      }
+
+      double predictedProb = Math.max(MIN_SF_PROB, predictedDistribution[actualClass]);
+      double priorProb = Math.max(MIN_SF_PROB, m_ClassPriors[actualClass] / m_ClassPriorsSum);
+      if (predictedProb >= priorProb) {
+        m_SumKBInfo += (Utils.log2(predictedProb) - Utils.log2(priorProb)) * instance.weight();
+      } else {
+        m_SumKBInfo -= (Utils.log2(1.0-predictedProb) - Utils.log2(1.0-priorProb))
+          * instance.weight();
+      }
+
+      m_SumSchemeEntropy -= Utils.log2(predictedProb) * instance.weight();
+      m_SumPriorEntropy -= Utils.log2(priorProb) * instance.weight();
+
+      updateNumericScores(predictedDistribution,
+          makeDistribution(instance.classValue()),
+          instance.weight());
+
+      // Update coverage stats
+      int[] indices = Utils.sort(predictedDistribution);
+      double sum = 0, sizeOfRegions = 0;
+      for (int i = predictedDistribution.length - 1; i >= 0; i--) {
+        if (sum >= m_ConfLevel) {
+          break;
+        }
+        sum += predictedDistribution[indices[i]];
+        sizeOfRegions++;
+        if (actualClass == indices[i]) {
+          m_TotalCoverage += instance.weight();
+        }
+      }
+      m_TotalSizeOfRegions += sizeOfRegions / (m_MaxTarget - m_MinTarget);
+
+      // Update other stats
+      m_ConfusionMatrix[actualClass][predictedClass] += instance.weight();
+      if (predictedClass != actualClass) {
+        m_Incorrect += instance.weight();
+      } else {
+        m_Correct += instance.weight();
+      }
+    } else {
+      m_MissingClass += instance.weight();
+    }
+  }
+
+  /**
+   * Updates stats for interval estimator based on current test instance.
+   *
+   * @param classifier the interval estimator
+   * @param classMissing the instance for which the intervals are computed, without a class value
+   * @param classValue the class value of this instance
+   * @throws Exception if intervals could not be computed successfully
+   */
+  protected void updateStatsForIntervalEstimator(IntervalEstimator  classifier, Instance classMissing,
+                                                 double classValue) throws Exception {
+
+    double[][] preds = classifier.predictIntervals(classMissing, m_ConfLevel);
+    if (m_Predictions != null)
+      ((NumericPrediction) m_Predictions.lastElement()).setPredictionIntervals(preds);
+    for (int i = 0; i < preds.length; i++) {
+      m_TotalSizeOfRegions += (preds[i][1] - preds[i][0]) / (m_MaxTarget - m_MinTarget);
+    }
+    for (int i = 0; i < preds.length; i++) {
+      if ((preds[i][1] >= classValue) && (preds[i][0] <= classValue)) {
+        m_TotalCoverage += classMissing.weight();
+        break;
+      }
+    }
+  }
+
+  /**
+   * Updates stats for conditional density estimator based on current test instance.
+   *
+   * @param classifier the conditional density estimator
+   * @param classMissing the instance for which density is to be computed, without a class value
+   * @param classValue the class value of this instance
+   * @throws Exception if density could not be computed successfully
+   */
+  protected void updateStatsForConditionalDensityEstimator(ConditionalDensityEstimator classifier,
+                                                           Instance classMissing,
+                                                           double classValue) throws Exception {
+
+    if (m_PriorEstimator == null) {
+      setNumericPriorsFromBuffer();
+    }
+    m_SumSchemeEntropy -= classifier.logDensity(classMissing, classValue) * classMissing.weight() /
+      Utils.log2;
+    m_SumPriorEntropy -= m_PriorEstimator.logDensity(classValue) * classMissing.weight() /
+      Utils.log2;
+  }
+
+  /**
+   * Updates all the statistics about a predictors performance for
+   * the current test instance.
+   *
+   * @param predictedValue the numeric value the classifier predicts
+   * @param instance the instance to be classified
+   * @throws Exception if the class of the instance is not set
+   */
+  protected void updateStatsForPredictor(double predictedValue, Instance instance)
+    throws Exception {
+
+    if (!instance.classIsMissing()){
+
+      // Update stats
+      m_WithClass += instance.weight();
+      if (Utils.isMissingValue(predictedValue)) {
+        m_Unclassified += instance.weight();
+        return;
+      }
+      m_SumClass += instance.weight() * instance.classValue();
+      m_SumSqrClass += instance.weight() * instance.classValue() * instance.classValue();
+      m_SumClassPredicted += instance.weight() * instance.classValue() * predictedValue;
+      m_SumPredicted += instance.weight() * predictedValue;
+      m_SumSqrPredicted += instance.weight() * predictedValue * predictedValue;
+
+      updateNumericScores(makeDistribution(predictedValue),
+          makeDistribution(instance.classValue()),
+          instance.weight());
+
+    } else
+      m_MissingClass += instance.weight();
+  }
+
+  /**
+   * Update the cumulative record of classification margins.
+   *
+   * @param predictedDistribution the probability distribution predicted for
+   * the current instance
+   * @param actualClass the index of the actual instance class
+   * @param weight the weight assigned to the instance
+   */
+  protected void updateMargins(double [] predictedDistribution,
+      int actualClass, double weight) {
+
+    double probActual = predictedDistribution[actualClass];
+    double probNext = 0;
+
+    for(int i = 0; i < m_NumClasses; i++)
+      if ((i != actualClass) &&
+          (predictedDistribution[i] > probNext))
+        probNext = predictedDistribution[i];
+
+    double margin = probActual - probNext;
+    int bin = (int)((margin + 1.0) / 2.0 * k_MarginResolution);
+    m_MarginCounts[bin] += weight;
+  }
+
+  /**
+   * Update the numeric accuracy measures. For numeric classes, the
+   * accuracy is between the actual and predicted class values. For
+   * nominal classes, the accuracy is between the actual and
+   * predicted class probabilities.
+   *
+   * @param predicted the predicted values
+   * @param actual the actual value
+   * @param weight the weight associated with this prediction
+   */
+  protected void updateNumericScores(double [] predicted,
+      double [] actual, double weight) {
+
+    double diff;
+    double sumErr = 0, sumAbsErr = 0, sumSqrErr = 0;
+    double sumPriorAbsErr = 0, sumPriorSqrErr = 0;
+    for(int i = 0; i < m_NumClasses; i++) {
+      diff = predicted[i] - actual[i];
+      sumErr += diff;
+      sumAbsErr += Math.abs(diff);
+      sumSqrErr += diff * diff;
+      diff = (m_ClassPriors[i] / m_ClassPriorsSum) - actual[i];
+      sumPriorAbsErr += Math.abs(diff);
+      sumPriorSqrErr += diff * diff;
+    }
+    m_SumErr += weight * sumErr / m_NumClasses;
+    m_SumAbsErr += weight * sumAbsErr / m_NumClasses;
+    m_SumSqrErr += weight * sumSqrErr / m_NumClasses;
+    m_SumPriorAbsErr += weight * sumPriorAbsErr / m_NumClasses;
+    m_SumPriorSqrErr += weight * sumPriorSqrErr / m_NumClasses;
+  }
+
+  /**
+   * Adds a numeric (non-missing) training class value and weight to
+   * the buffer of stored values. Also updates minimum and maximum target value.
+   *
+   * @param classValue the class value
+   * @param weight the instance weight
+   */
+  protected void addNumericTrainClass(double classValue, double weight) {
+
+    // Update minimum and maximum target value
+    if (classValue > m_MaxTarget) {
+      m_MaxTarget = classValue;
+    }
+    if (classValue < m_MinTarget) {
+      m_MinTarget = classValue;
+    }
+
+    // Update buffer
+    if (m_TrainClassVals == null) {
+      m_TrainClassVals = new double [100];
+      m_TrainClassWeights = new double [100];
+    }
+    if (m_NumTrainClassVals == m_TrainClassVals.length) {
+      double [] temp = new double [m_TrainClassVals.length * 2];
+      System.arraycopy(m_TrainClassVals, 0,
+          temp, 0, m_TrainClassVals.length);
+      m_TrainClassVals = temp;
+
+      temp = new double [m_TrainClassWeights.length * 2];
+      System.arraycopy(m_TrainClassWeights, 0,
+          temp, 0, m_TrainClassWeights.length);
+      m_TrainClassWeights = temp;
+    }
+    m_TrainClassVals[m_NumTrainClassVals] = classValue;
+    m_TrainClassWeights[m_NumTrainClassVals] = weight;
+    m_NumTrainClassVals++;
+  }
+
+  /**
+   * Sets up the priors for numeric class attributes from the
+   * training class values that have been seen so far.
+   */
+  protected void setNumericPriorsFromBuffer() {
+
+    m_PriorEstimator = new UnivariateKernelEstimator();
+    for (int i = 0; i < m_NumTrainClassVals; i++) {
+      m_PriorEstimator.addValue(m_TrainClassVals[i], m_TrainClassWeights[i]);
+    }
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/IntervalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/IntervalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/IntervalEstimator.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IntervalEstimator.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Instance;
+
+/**
+ * Interface for numeric prediction schemes that can output prediction
+ * intervals.
+ *
+ * @author Kurt Driessens (kurtd@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public interface IntervalEstimator {
+
+  /**
+   * Returns an N * 2 array, where N is the number of prediction
+   * intervals. In each row, the first element contains the lower
+   * boundary of the corresponding prediction interval and the second
+   * element the upper boundary.
+   *
+   * @param inst the instance to make the prediction for.
+   * @param confidenceLevel the percentage of cases that the interval should cover.
+   * @return an array of prediction intervals
+   * @exception Exception if the intervals can't be computed
+   */
+  double[][] predictIntervals(Instance inst, double confidenceLevel) throws Exception;
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/IteratedSingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/IteratedSingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/IteratedSingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,159 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IteratedSingleClassifierEnhancer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to
+ * meta classifiers that build an ensemble from a single base learner.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class IteratedSingleClassifierEnhancer
+  extends SingleClassifierEnhancer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6217979135443319724L;
+
+  /** Array for storing the generated base classifiers. */
+  protected Classifier[] m_Classifiers;
+
+  /** The number of iterations. */
+  protected int m_NumIterations = 10;
+
+  /**
+   * Stump method for building the classifiers.
+   *
+   * @param data the training data to be used for generating the
+   * bagged classifier.
+   * @exception Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    if (m_Classifier == null) {
+      throw new Exception("A base classifier has not been specified!");
+    }
+    m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, m_NumIterations);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+          "\tNumber of iterations.\n"
+          + "\t(default 10)",
+          "I", 1, "-I <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * -I num <br>
+   * Set the number of iterations (default 10). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String iterations = Utils.getOption('I', options);
+    if (iterations.length() != 0) {
+      setNumIterations(Integer.parseInt(iterations));
+    } else {
+      setNumIterations(10);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-I";
+    options[current++] = "" + getNumIterations();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "The number of iterations to be performed.";
+  }
+
+  /**
+   * Sets the number of bagging iterations
+   */
+  public void setNumIterations(int numIterations) {
+
+    m_NumIterations = numIterations;
+  }
+
+  /**
+   * Gets the number of bagging iterations
+   *
+   * @return the maximum number of bagging iterations
+   */
+  public int getNumIterations() {
+
+    return m_NumIterations;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/IterativeClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/IterativeClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/IterativeClassifier.java	(revision 29)
@@ -0,0 +1,69 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IterativeClassifier.java
+ *    Copyright (C) 2001  University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.*;
+
+/**
+ * Interface for classifiers that can induce models of growing
+ * complexity one step at a time.
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+
+public interface IterativeClassifier {
+
+  /**
+   * Inits an iterative classifier.
+   *
+   * @param instances the instances to be used in induction
+   * @exception Exception if the model cannot be initialized
+   */
+  void initClassifier(Instances instances) throws Exception;
+
+  /**
+   * Performs one iteration.
+   *
+   * @param iteration the index of the current iteration (0-based)
+   * @exception Exception if this iteration fails
+   */
+  void next(int iteration) throws Exception;
+
+  /**
+   * Signal end of iterating, useful for any house-keeping/cleanup
+   *
+   * @exception Exception if cleanup fails
+   */
+  void done() throws Exception;
+
+  /**
+    * Performs a deep copy of the classifier, and a reference copy
+    * of the training instances (or a deep copy if required).
+    *
+    * @return a clone of the classifier
+    */
+  Object clone() throws CloneNotSupportedException;
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/MultipleClassifiersCombiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/MultipleClassifiersCombiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/MultipleClassifiersCombiner.java	(revision 29)
@@ -0,0 +1,220 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultipleClassifiersCombiner.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Capabilities;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to
+ * meta classifiers that build an ensemble from multiple classifiers.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class MultipleClassifiersCombiner extends AbstractClassifier {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2776436621129422119L;
+
+  /** Array for storing the generated base classifiers. */
+  protected Classifier[] m_Classifiers = {
+    new weka.classifiers.rules.ZeroR()
+  };
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+          "\tFull class name of classifier to include, followed\n"
+          + "\tby scheme options. May be specified multiple times.\n"
+          + "\t(default: \"weka.classifiers.rules.ZeroR\")",
+          "B", 1, "-B <classifier specification>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -B classifierstring <br>
+   * Classifierstring should contain the full class name of a scheme
+   * included for selection followed by options to the classifier
+   * (required, option should be used once for each classifier).<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    // Iterate through the schemes
+    Vector classifiers = new Vector();
+    while (true) {
+      String classifierString = Utils.getOption('B', options);
+      if (classifierString.length() == 0) {
+        break;
+      }
+      String [] classifierSpec = Utils.splitOptions(classifierString);
+      if (classifierSpec.length == 0) {
+        throw new IllegalArgumentException("Invalid classifier specification string");
+      }
+      String classifierName = classifierSpec[0];
+      classifierSpec[0] = "";
+      classifiers.addElement(AbstractClassifier.forName(classifierName,
+            classifierSpec));
+    }
+    if (classifiers.size() == 0) {
+      classifiers.addElement(new weka.classifiers.rules.ZeroR());
+    }
+    Classifier [] classifiersArray = new Classifier [classifiers.size()];
+    for (int i = 0; i < classifiersArray.length; i++) {
+      classifiersArray[i] = (Classifier) classifiers.elementAt(i);
+    }
+    setClassifiers(classifiersArray);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    int current = 0;
+    String[] options = new String [superOptions.length + m_Classifiers.length * 2];
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      options[current++] = "-B";
+      options[current++] = "" + getClassifierSpec(i);
+    }
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifiersTipText() {
+    return "The base classifiers to be used.";
+  }
+
+  /**
+   * Sets the list of possible classifers to choose from.
+   *
+   * @param classifiers an array of classifiers with all options set.
+   */
+  public void setClassifiers(Classifier [] classifiers) {
+
+    m_Classifiers = classifiers;
+  }
+
+  /**
+   * Gets the list of possible classifers to choose from.
+   *
+   * @return the array of Classifiers
+   */
+  public Classifier [] getClassifiers() {
+
+    return m_Classifiers;
+  }
+
+  /**
+   * Gets a single classifier from the set of available classifiers.
+   *
+   * @param index the index of the classifier wanted
+   * @return the Classifier
+   */
+  public Classifier getClassifier(int index) {
+
+    return m_Classifiers[index];
+  }
+
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier
+   *
+   * @param index the index of the classifier string to retrieve, starting from
+   * 0.
+   * @return the classifier string, or the empty string if no classifier
+   * has been assigned (or the index given is out of range).
+   */
+  protected String getClassifierSpec(int index) {
+
+    if (m_Classifiers.length < index) {
+      return "";
+    }
+    Classifier c = getClassifier(index);
+    return c.getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)c).getOptions());
+  }
+
+  /**
+   * Returns combined capabilities of the base classifiers, i.e., the
+   * capabilities all of them have in common.
+   *
+   * @return      the capabilities of the base classifiers
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+    int               i;
+
+    if (getClassifiers().length == 0) {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+    else {
+      result = (Capabilities) getClassifier(0).getCapabilities().clone();
+      for (i = 1; i < getClassifiers().length; i++)
+        result.and(getClassifier(i).getCapabilities());
+    }
+
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+
+    result.setOwner(this);
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/ParallelIteratedSingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/ParallelIteratedSingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/ParallelIteratedSingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,283 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ParallelIteratedSingleClassifierEnhancer.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Utils;
+
+/**
+ * Abstract utility class for handling settings common to
+ * meta classifiers that build an ensemble in parallel from a single
+ * base learner.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6041 $
+ */
+public abstract class ParallelIteratedSingleClassifierEnhancer extends
+    IteratedSingleClassifierEnhancer {
+
+  /** For serialization */
+  private static final long serialVersionUID = -5026378741833046436L;
+
+  /** The number of threads to have executing at any one time */
+  protected int m_numExecutionSlots = 1;
+
+  /** Pool of threads to train models with */
+  protected transient ThreadPoolExecutor m_executorPool;
+
+  /** The number of classifiers completed so far */
+  protected int m_completed;
+
+  /**
+   * The number of classifiers that experienced a failure of some sort
+   * during construction
+   */
+  protected int m_failed;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tNumber of execution slots.\n"
+              + "\t(default 1 - i.e. no parallelism)",
+              "num-slots", 1, "-num-slots <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -Z num <br>
+   * Set the number of execution slots to use (default 1 - i.e. no parallelism). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String iterations = Utils.getOption("num-slots", options);
+    if (iterations.length() != 0) {
+      setNumExecutionSlots(Integer.parseInt(iterations));
+    } else {
+      setNumExecutionSlots(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-num-slots";
+    options[current++] = "" + getNumExecutionSlots();
+
+    System.arraycopy(superOptions, 0, options, current,
+                     superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Set the number of execution slots (threads) to use for building the
+   * members of the ensemble.
+   *
+   * @param numSlots the number of slots to use.
+   */
+  public void setNumExecutionSlots(int numSlots) {
+    m_numExecutionSlots = numSlots;
+  }
+
+  /**
+   * Get the number of execution slots (threads) to use for building
+   * the members of the ensemble.
+   *
+   * @return the number of slots to use
+   */
+  public int getNumExecutionSlots() {
+    return m_numExecutionSlots;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numExecutionSlotsTipText() {
+    return "The number of execution slots (threads) to use for " +
+      "constructing the ensemble.";
+  }
+
+  /**
+   * Stump method for building the classifiers
+   *
+   * @param data the training data to be used for generating the ensemble
+   * @exception Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    super.buildClassifier(data);
+
+    if (m_numExecutionSlots < 1) {
+      throw new Exception("Number of execution slots needs to be >= 1!");
+    }
+
+    if (m_numExecutionSlots > 1) {
+      startExecutorPool();
+    }
+    m_completed = 0;
+    m_failed = 0;
+  }
+
+  /**
+   * Start the pool of execution threads
+   */
+  protected void startExecutorPool() {
+    if (m_executorPool != null) {
+      m_executorPool.shutdownNow();
+    }
+
+    m_executorPool = new ThreadPoolExecutor(m_numExecutionSlots, m_numExecutionSlots,
+        120, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+  }
+
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+        wait();
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Does the actual construction of the ensemble
+   *
+   * @throws Exception if something goes wrong during the training
+   * process
+   */
+  protected synchronized void buildClassifiers() throws Exception {
+
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      if (m_numExecutionSlots > 1) {
+        final Classifier currentClassifier = m_Classifiers[i];
+        final int iteration = i;
+        if (m_Debug) {
+          System.out.print("Training classifier (" + (i +1) + ")");
+        }
+        Runnable newTask = new Runnable() {
+          public void run() {
+            try {
+              currentClassifier.buildClassifier(getTrainingSet(iteration));
+              completedClassifier(iteration, true);
+            } catch (Exception ex) {
+              ex.printStackTrace();
+              completedClassifier(iteration, false);
+            }
+          }
+        };
+
+        // launch this task
+        m_executorPool.execute(newTask);
+      } else {
+        m_Classifiers[i].buildClassifier(getTrainingSet(i));
+      }
+    }
+
+    if (m_numExecutionSlots > 1 && m_completed + m_failed < m_Classifiers.length) {
+      block(true);
+    }
+  }
+
+  /**
+   * Records the completion of the training of a single classifier. Unblocks if
+   * all classifiers have been trained.
+   *
+   * @param iteration the iteration that has completed
+   * @param success whether the classifier trained successfully
+   */
+  protected synchronized void completedClassifier(int iteration,
+      boolean success) {
+    m_completed++;
+
+    if (!success) {
+      m_failed++;
+      if (m_Debug) {
+        System.err.println("Iteration " + iteration + " failed!");
+      }
+    }
+
+    if (m_completed + m_failed == m_Classifiers.length) {
+      if (m_failed > 0) {
+        if (m_Debug) {
+          System.err.println("Problem building classifiers - some iterations failed.");
+        }
+      }
+
+      // have to shut the pool down or program executes as a server
+      // and when running from the command line does not return to the
+      // prompt
+      m_executorPool.shutdown();
+      block(false);
+    }
+  }
+
+  /**
+   * Gets a training set for a particular iteration. Implementations need
+   * to be careful with thread safety and should probably be synchronized
+   * to be on the safe side.
+   *
+   * @param iteration the number of the iteration for the requested training set
+   * @return the training set for the supplied iteration number
+   * @throws Exception if something goes wrong.
+   */
+  protected abstract Instances getTrainingSet(int iteration) throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/ParallelMultipleClassifiersCombiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/ParallelMultipleClassifiersCombiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/ParallelMultipleClassifiersCombiner.java	(revision 29)
@@ -0,0 +1,278 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ParallelMultipleClassifiersCombiner.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Utils;
+
+/**
+ * Abstract utility class for handling settings common to
+ * meta classifiers that build an ensemble in parallel using multiple
+ * classifiers.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6041 $
+ */
+public abstract class ParallelMultipleClassifiersCombiner extends
+    MultipleClassifiersCombiner {
+
+  /** For serialization */
+  private static final long serialVersionUID = 728109028953726626L;
+
+  /** The number of threads to have executing at any one time */
+  protected int m_numExecutionSlots = 1;
+
+  /** Pool of threads to train models with */
+  protected transient ThreadPoolExecutor m_executorPool;
+
+  /** The number of classifiers completed so far */
+  protected int m_completed;
+
+  /**
+   * The number of classifiers that experienced a failure of some sort
+   * during construction
+   */
+  protected int m_failed;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tNumber of execution slots.\n"
+              + "\t(default 1 - i.e. no parallelism)",
+              "num-slots", 1, "-num-slots <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -Z num <br>
+   * Set the number of execution slots to use (default 1 - i.e. no parallelism). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String iterations = Utils.getOption("num-slots", options);
+    if (iterations.length() != 0) {
+      setNumExecutionSlots(Integer.parseInt(iterations));
+    } else {
+      setNumExecutionSlots(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-num-slots";
+    options[current++] = "" + getNumExecutionSlots();
+
+    System.arraycopy(superOptions, 0, options, current,
+                     superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Set the number of execution slots (threads) to use for building the
+   * members of the ensemble.
+   *
+   * @param numSlots the number of slots to use.
+   */
+  public void setNumExecutionSlots(int numSlots) {
+    m_numExecutionSlots = numSlots;
+  }
+
+  /**
+   * Get the number of execution slots (threads) to use for building
+   * the members of the ensemble.
+   *
+   * @return the number of slots to use
+   */
+  public int getNumExecutionSlots() {
+    return m_numExecutionSlots;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numExecutionSlotsTipText() {
+    return "The number of execution slots (threads) to use for " +
+      "constructing the ensemble.";
+  }
+
+  /**
+   * Stump method for building the classifiers
+   *
+   * @param data the training data to be used for generating the ensemble
+   * @exception Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    if (m_numExecutionSlots < 1) {
+      throw new Exception("Number of execution slots needs to be >= 1!");
+    }
+
+    if (m_numExecutionSlots > 1) {
+      if (m_Debug) {
+        System.out.println("Starting executor pool with " + m_numExecutionSlots
+            + " slots...");
+      }
+      startExecutorPool();
+    }
+    m_completed = 0;
+    m_failed = 0;
+  }
+
+  /**
+   * Start the pool of execution threads
+   */
+  protected void startExecutorPool() {
+    if (m_executorPool != null) {
+      m_executorPool.shutdownNow();
+    }
+
+    m_executorPool = new ThreadPoolExecutor(m_numExecutionSlots, m_numExecutionSlots,
+        120, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+  }
+
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+        wait();
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Does the actual construction of the ensemble
+   *
+   * @throws Exception if something goes wrong during the training
+   * process
+   */
+  protected synchronized void buildClassifiers(final Instances data) throws Exception {
+
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      if (m_numExecutionSlots > 1) {
+        final Classifier currentClassifier = m_Classifiers[i];
+        final int iteration = i;
+        Runnable newTask = new Runnable() {
+          public void run() {
+            try {
+              if (m_Debug) {
+                System.out.println("Training classifier (" + (iteration +1) + ")");
+              }
+              currentClassifier.buildClassifier(data);
+              if (m_Debug) {
+                System.out.println("Finished classifier (" + (iteration +1) + ")");
+              }
+              completedClassifier(iteration, true);
+            } catch (Exception ex) {
+              ex.printStackTrace();
+              completedClassifier(iteration, false);
+            }
+          }
+        };
+
+        // launch this task
+        m_executorPool.execute(newTask);
+      } else {
+        m_Classifiers[i].buildClassifier(data);
+      }
+    }
+
+    if (m_numExecutionSlots > 1 && m_completed + m_failed < m_Classifiers.length) {
+      block(true);
+    }
+  }
+
+  /**
+   * Records the completion of the training of a single classifier. Unblocks if
+   * all classifiers have been trained.
+   *
+   * @param iteration the iteration that has completed
+   * @param success whether the classifier trained successfully
+   */
+  protected synchronized void completedClassifier(int iteration,
+      boolean success) {
+    m_completed++;
+
+    if (!success) {
+      m_failed++;
+      if (m_Debug) {
+        System.err.println("Iteration " + iteration + " failed!");
+      }
+    }
+
+    if (m_completed + m_failed == m_Classifiers.length) {
+      if (m_failed > 0) {
+        if (m_Debug) {
+          System.err.println("Problem building classifiers - some iterations failed.");
+        }
+      }
+
+      // have to shut the pool down or program executes as a server
+      // and when running from the command line does not return to the
+      // prompt
+      m_executorPool.shutdown();
+      block(false);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableClassifier.java	(revision 29)
@@ -0,0 +1,146 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableClassifier.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * classifiers.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableClassifier
+  extends AbstractClassifier implements Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8816375798262351903L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+          "\tRandom number seed.\n"
+          + "\t(default 1)",
+          "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * -I num <br>
+   * Set the number of iterations (default 10). <p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableIteratedSingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableIteratedSingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableIteratedSingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,146 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableIteratedSingleClassifierEnhancer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * meta classifiers that build an ensemble from a single base learner.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableIteratedSingleClassifierEnhancer
+  extends IteratedSingleClassifierEnhancer implements Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5063351391524938557L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+          "\tRandom number seed.\n"
+          + "\t(default 1)",
+          "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * -I num <br>
+   * Set the number of iterations (default 10). <p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableMultipleClassifiersCombiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableMultipleClassifiersCombiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableMultipleClassifiersCombiner.java	(revision 29)
@@ -0,0 +1,144 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableMultipleClassifiersCombiner.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * meta classifiers that build an ensemble from multiple classifiers based
+ * on a given random number seed.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableMultipleClassifiersCombiner
+  extends MultipleClassifiersCombiner implements Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5057936555724785679L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+          "\tRandom number seed.\n"
+          + "\t(default 1)",
+          "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -B classifierstring <br>
+   * Classifierstring should contain the full class name of a scheme
+   * included for selection followed by options to the classifier
+   * (required, option should be used once for each classifier).<p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelIteratedSingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelIteratedSingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelIteratedSingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,149 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableParallelIteratedSingleClassifierEnhancer.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * meta classifiers that build an ensemble in parallel from a single base
+ * learner.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableParallelIteratedSingleClassifierEnhancer
+    extends ParallelIteratedSingleClassifierEnhancer {
+
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = 1298141000373615374L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tRandom number seed.\n"
+              + "\t(default 1)",
+              "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * -I num <br>
+   * Set the number of iterations (default 10). <p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+                     superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelMultipleClassifiersCombiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelMultipleClassifiersCombiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableParallelMultipleClassifiersCombiner.java	(revision 29)
@@ -0,0 +1,143 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableParallelMultipleClassifiersCombiner.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+/**
+ * Abstract utility class for handling settings common to
+ * meta classifiers that build an ensemble in parallel using multiple
+ * classifiers based on a given random number seed.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableParallelMultipleClassifiersCombiner extends
+    ParallelMultipleClassifiersCombiner {
+
+  /** For serialization */
+  private static final long serialVersionUID = 8274061943448676943L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tRandom number seed.\n"
+              + "\t(default 1)",
+              "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -B classifierstring <br>
+   * Classifierstring should contain the full class name of a scheme
+   * included for selection followed by options to the classifier
+   * (required, option should be used once for each classifier).<p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+                     superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/RandomizableSingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/RandomizableSingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/RandomizableSingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,146 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomizableSingleClassifierEnhancer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * meta classifiers that build an ensemble from a single base learner.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class RandomizableSingleClassifierEnhancer
+  extends SingleClassifierEnhancer implements Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 558286687096157160L;
+
+  /** The random number seed. */
+  protected int m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+          "\tRandom number seed.\n"
+          + "\t(default 1)",
+          "S", 1, "-S <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * -I num <br>
+   * Set the number of iterations (default 10). <p>
+   *
+   * -S num <br>
+   * Set the random number seed (default 1). <p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(1);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-S";
+    options[current++] = "" + getSeed();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed
+   */
+  public void setSeed(int seed) {
+
+    m_Seed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/SingleClassifierEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/SingleClassifierEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/SingleClassifierEnhancer.java	(revision 29)
@@ -0,0 +1,222 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SingleClassifierEnhancer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.classifiers.rules.ZeroR;
+import weka.core.Capabilities;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to meta
+ * classifiers that use a single base learner.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public abstract class SingleClassifierEnhancer extends AbstractClassifier {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3665885256363525164L;
+
+  /** The base classifier to use */
+  protected Classifier m_Classifier = new ZeroR();
+
+  /**
+   * String describing default classifier.
+   */
+  protected String defaultClassifierString() {
+
+    return "weka.classifiers.rules.ZeroR";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    newVector.addElement(new Option(
+          "\tFull name of base classifier.\n"
+          + "\t(default: " + defaultClassifierString() +")",
+          "W", 1, "-W"));
+
+    newVector.addElement(new Option(
+          "",
+          "", 0, "\nOptions specific to classifier "
+          + m_Classifier.getClass().getName() + ":"));
+    enu = ((OptionHandler)m_Classifier).listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the base learner.<p>
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    super.setOptions(options);
+
+    String classifierName = Utils.getOption('W', options);
+
+    if (classifierName.length() > 0) {
+
+      // This is just to set the classifier in case the option
+      // parsing fails.
+      setClassifier(AbstractClassifier.forName(classifierName, null));
+      setClassifier(AbstractClassifier.forName(classifierName,
+            Utils.partitionOptions(options)));
+    } else {
+
+      // This is just to set the classifier in case the option
+      // parsing fails.
+      setClassifier(AbstractClassifier.forName(defaultClassifierString(), null));
+      setClassifier(AbstractClassifier.forName(defaultClassifierString(),
+            Utils.partitionOptions(options)));
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] classifierOptions = ((OptionHandler)m_Classifier).getOptions();
+    int extraOptionsLength = classifierOptions.length;
+    if (extraOptionsLength > 0) {
+      extraOptionsLength++; // for the double hyphen
+    }
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length +
+      extraOptionsLength + 2];
+
+    int current = 0;
+    options[current++] = "-W";
+    options[current++] = getClassifier().getClass().getName();
+
+    System.arraycopy(superOptions, 0, options, current,
+        superOptions.length);
+    current += superOptions.length;
+
+    if (classifierOptions.length > 0) {
+      options[current++] = "--";
+      System.arraycopy(classifierOptions, 0, options, current,
+          classifierOptions.length);
+    }
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "The base classifier to be used.";
+  }
+
+  /**
+   * Returns default capabilities of the base classifier.
+   *
+   * @return      the capabilities of the base classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities        result;
+
+    if (getClassifier() != null) {
+      result = getClassifier().getCapabilities();
+    } else {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+
+    result.setOwner(this);
+
+    return result;
+  }
+
+  /**
+   * Set the base learner.
+   *
+   * @param newClassifier the classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+
+    m_Classifier = newClassifier;
+  }
+
+  /**
+   * Get the classifier used as the base learner.
+   *
+   * @return the classifier used as the classifier
+   */
+  public Classifier getClassifier() {
+
+    return m_Classifier;
+  }
+
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier
+   *
+   * @return the classifier string
+   */
+  protected String getClassifierSpec() {
+
+    Classifier c = getClassifier();
+    return c.getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)c).getOptions());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/Sourcable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/Sourcable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/Sourcable.java	(revision 29)
@@ -0,0 +1,58 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Sourcable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+/**
+ * Interface for classifiers that can be converted to Java source.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public interface Sourcable {
+
+  /**
+   * Returns a string that describes the classifier as source. The
+   * classifier will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain a method with the signature:
+   * <pre><code>
+   * public static double classify(Object [] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className the name that should be given to the source class.
+   * @return the object source described by a string
+   * @throws Exception if the source can't be computed
+   */
+  String toSource(String className) throws Exception;
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/UpdateableClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/UpdateableClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/UpdateableClassifier.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UpdateableClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers;
+
+import weka.core.*;
+
+/**
+ * Interface to incremental classification models that can learn using
+ * one instance at a time.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public interface UpdateableClassifier {
+
+  /**
+   * Updates a classifier using the given instance.
+   *
+   * @param instance the instance to included
+   * @exception Exception if instance could not be incorporated
+   * successfully
+   */
+  void updateClassifier(Instance instance) throws Exception;
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODE.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODE.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODE.java	(revision 29)
@@ -0,0 +1,783 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AODE.java
+ *    Copyright (C) 2003
+ *    Algorithm developed by: Geoff Webb
+ *    Code written by: Janice Boughton & Zhihai Wang
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * AODE achieves highly accurate classification by averaging over all of a small space of alternative naive-Bayes-like models that have weaker (and hence less detrimental) independence assumptions than naive Bayes. The resulting algorithm is computationally efficient while delivering highly accurate classification on many learning  tasks.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * G. Webb, J. Boughton, Z. Wang (2005). Not So Naive Bayes: Aggregating One-Dependence Estimators. Machine Learning. 58(1):5-24.<br/>
+ * <br/>
+ * Further papers are available at<br/>
+ *   http://www.csse.monash.edu.au/~webb/.<br/>
+ * <br/>
+ * Can use an m-estimate for smoothing base probability estimates in place of the Laplace correction (via option -M).<br/>
+ * Default frequency limit set to 1.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Webb2005,
+ *    author = {G. Webb and J. Boughton and Z. Wang},
+ *    journal = {Machine Learning},
+ *    number = {1},
+ *    pages = {5-24},
+ *    title = {Not So Naive Bayes: Aggregating One-Dependence Estimators},
+ *    volume = {58},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Output debugging information
+ * </pre>
+ * 
+ * <pre> -F &lt;int&gt;
+ *  Impose a frequency limit for superParents
+ *  (default is 1)</pre>
+ * 
+ * <pre> -M
+ *  Use m-estimate instead of laplace correction
+ * </pre>
+ * 
+ * <pre> -W &lt;int&gt;
+ *  Specify a weight to use with m-estimate
+ *  (default is 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Janice Boughton (jrbought@csse.monash.edu.au)
+ * @author Zhihai Wang (zhw@csse.monash.edu.au)
+ * @version $Revision: 5928 $
+ */
+public class AODE
+    extends AbstractClassifier
+    implements OptionHandler, WeightedInstancesHandler, UpdateableClassifier, 
+               TechnicalInformationHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = 9197439980415113523L;
+  
+  /**
+   * 3D array (m_NumClasses * m_TotalAttValues * m_TotalAttValues)
+   * of attribute counts, i.e., the number of times an attribute value occurs
+   * in conjunction with another attribute value and a class value.  
+   */
+  private double [][][] m_CondiCounts;
+    
+  /** The number of times each class value occurs in the dataset */
+  private double [] m_ClassCounts;
+    
+  /** The sums of attribute-class counts  
+   *    -- if there are no missing values for att, then
+   *       m_SumForCounts[classVal][att] will be the same as
+   *       m_ClassCounts[classVal] 
+   */
+  private double [][] m_SumForCounts;
+
+  /** The number of classes */
+  private int m_NumClasses;
+    
+  /** The number of attributes in dataset, including class */
+  private int m_NumAttributes;
+    
+  /** The number of instances in the dataset */
+  private int m_NumInstances;
+    
+  /** The index of the class attribute */
+  private int m_ClassIndex;
+    
+  /** The dataset */
+  private Instances m_Instances;
+    
+  /**
+   * The total number of values (including an extra for each attribute's 
+   * missing value, which are included in m_CondiCounts) for all attributes 
+   * (not including class). E.g., for three atts each with two possible values,
+   * m_TotalAttValues would be 9 (6 values + 3 missing).
+   * This variable is used when allocating space for m_CondiCounts matrix.
+   */
+  private int m_TotalAttValues;
+    
+  /** The starting index (in the m_CondiCounts matrix) of the values for each
+   * attribute */
+  private int [] m_StartAttIndex;
+    
+  /** The number of values for each attribute */
+  private int [] m_NumAttValues;
+    
+  /** The frequency of each attribute value for the dataset */
+  private double [] m_Frequencies;
+
+  /** The number of valid class values observed in dataset 
+   *  -- with no missing classes, this number is the same as m_NumInstances.
+   */
+  private double m_SumInstances;
+
+  /** An att's frequency must be this value or more to be a superParent */
+  private int m_Limit = 1;
+
+  /** If true, outputs debugging info */
+  private boolean m_Debug = false;
+
+  /** flag for using m-estimates */
+  private boolean m_MEstimates = false;
+
+  /** value for m in m-estimate */
+  private int m_Weight = 1;
+
+ 
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "AODE achieves highly accurate classification by averaging over "
+      +"all of a small space of alternative naive-Bayes-like models that have "
+      +"weaker (and hence less detrimental) independence assumptions than "
+      +"naive Bayes. The resulting algorithm is computationally efficient "
+      +"while delivering highly accurate classification on many learning  "
+      +"tasks.\n\n"
+      +"For more information, see\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      +"Further papers are available at\n"
+      +"  http://www.csse.monash.edu.au/~webb/.\n\n"
+      + "Can use an m-estimate for smoothing base probability estimates "
+      + "in place of the Laplace correction (via option -M).\n"
+      + "Default frequency limit set to 1.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "G. Webb and J. Boughton and Z. Wang");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.TITLE, "Not So Naive Bayes: Aggregating One-Dependence Estimators");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "58");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "5-24");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+ 
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data
+   * @throws Exception if the classifier has not been generated
+   * successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    m_Instances = new Instances(instances);
+    m_Instances.deleteWithMissingClass();
+
+    // reset variable for this fold
+    m_SumInstances = 0;
+    m_ClassIndex = instances.classIndex();
+    m_NumInstances = m_Instances.numInstances();
+    m_NumAttributes = m_Instances.numAttributes();
+    m_NumClasses = m_Instances.numClasses();
+ 
+    // allocate space for attribute reference arrays
+    m_StartAttIndex = new int[m_NumAttributes];
+    m_NumAttValues = new int[m_NumAttributes];
+ 
+    m_TotalAttValues = 0;
+    for(int i = 0; i < m_NumAttributes; i++) {
+       if(i != m_ClassIndex) {
+          m_StartAttIndex[i] = m_TotalAttValues;
+          m_NumAttValues[i] = m_Instances.attribute(i).numValues();
+          m_TotalAttValues += m_NumAttValues[i] + 1;
+          // + 1 so room for missing value count
+       } else {
+          // m_StartAttIndex[i] = -1;  // class isn't included
+	  m_NumAttValues[i] = m_NumClasses;
+       }
+    }
+
+    // allocate space for counts and frequencies
+    m_CondiCounts = new double[m_NumClasses][m_TotalAttValues][m_TotalAttValues];
+    m_ClassCounts = new double[m_NumClasses];
+    m_SumForCounts = new double[m_NumClasses][m_NumAttributes];
+    m_Frequencies = new double[m_TotalAttValues];
+
+    // calculate the counts
+    for(int k = 0; k < m_NumInstances; k++) {
+       addToCounts((Instance)m_Instances.instance(k));
+    }
+
+    // free up some space
+    m_Instances = new Instances(m_Instances, 0);
+  }
+ 
+
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance the new training instance to include in the model 
+   */
+    public void updateClassifier(Instance instance) {
+	this.addToCounts(instance);
+    }
+
+    /** 
+     * Puts an instance's values into m_CondiCounts, m_ClassCounts and 
+     * m_SumInstances.
+     *
+     * @param instance  the instance whose values are to be put into the counts
+     *                  variables
+     */
+  private void addToCounts(Instance instance) {
+ 
+    double [] countsPointer;
+ 
+    if(instance.classIsMissing())
+       return;   // ignore instances with missing class
+
+    int classVal = (int)instance.classValue();
+    double weight = instance.weight();
+ 
+    m_ClassCounts[classVal] += weight;
+    m_SumInstances += weight;
+   
+    // store instance's att val indexes in an array, b/c accessing it 
+    // in loop(s) is more efficient
+    int [] attIndex = new int[m_NumAttributes];
+    for(int i = 0; i < m_NumAttributes; i++) {
+       if(i == m_ClassIndex)
+          attIndex[i] = -1;  // we don't use the class attribute in counts
+       else {
+          if(instance.isMissing(i))
+             attIndex[i] = m_StartAttIndex[i] + m_NumAttValues[i];
+          else
+             attIndex[i] = m_StartAttIndex[i] + (int)instance.value(i);
+       }
+    }
+
+    for(int Att1 = 0; Att1 < m_NumAttributes; Att1++) {
+       if(attIndex[Att1] == -1)
+          continue;   // avoid pointless looping as Att1 is currently the class attribute
+
+       m_Frequencies[attIndex[Att1]] += weight;
+       
+       // if this is a missing value, we don't want to increase sumforcounts
+       if(!instance.isMissing(Att1))
+          m_SumForCounts[classVal][Att1] += weight;
+
+       // save time by referencing this now, rather than do it repeatedly in the loop
+       countsPointer = m_CondiCounts[classVal][attIndex[Att1]];
+
+       for(int Att2 = 0; Att2 < m_NumAttributes; Att2++) {
+          if(attIndex[Att2] != -1) {
+             countsPointer[attIndex[Att2]] += weight;
+          }
+       }
+    }
+  }
+ 
+ 
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+ 
+    // accumulates posterior probabilities for each class
+    double [] probs = new double[m_NumClasses];
+
+    // index for parent attribute value, and a count of parents used
+    int pIndex, parentCount;
+ 
+    // pointers for efficiency
+    // for current class, point to joint frequency for any pair of att values
+    double [][] countsForClass;
+    // for current class & parent, point to joint frequency for any att value
+    double [] countsForClassParent;
+
+    // store instance's att indexes in an int array, so accessing them 
+    // is more efficient in loop(s).
+    int [] attIndex = new int[m_NumAttributes];
+    for(int att = 0; att < m_NumAttributes; att++) {
+       if(instance.isMissing(att) || att == m_ClassIndex)
+          attIndex[att] = -1;   // can't use class or missing values in calculations
+       else
+	  attIndex[att] = m_StartAttIndex[att] + (int)instance.value(att);
+    }
+
+    // calculate probabilities for each possible class value
+    for(int classVal = 0; classVal < m_NumClasses; classVal++) {
+ 
+       probs[classVal] = 0;
+       double spodeP = 0;  // P(X,y) for current parent and class
+       parentCount = 0;
+ 
+       countsForClass = m_CondiCounts[classVal];
+
+       // each attribute has a turn of being the parent
+       for(int parent = 0; parent < m_NumAttributes; parent++) {
+          if(attIndex[parent] == -1)
+             continue;  // skip class attribute or missing value
+
+          // determine correct index for the parent in m_CondiCounts matrix
+          pIndex = attIndex[parent];
+
+          // check that the att value has a frequency of m_Limit or greater
+	  if(m_Frequencies[pIndex] < m_Limit) 
+             continue;
+
+          countsForClassParent = countsForClass[pIndex];
+
+          // block the parent from being its own child
+          attIndex[parent] = -1;
+
+          parentCount++;
+
+          // joint frequency of class and parent
+          double classparentfreq = countsForClassParent[pIndex];
+
+          // find the number of missing values for parent's attribute
+          double missing4ParentAtt = 
+              m_Frequencies[m_StartAttIndex[parent] + m_NumAttValues[parent]];
+
+          // calculate the prior probability -- P(parent & classVal)
+          if (!m_MEstimates) {
+             spodeP = (classparentfreq + 1.0)
+                / ((m_SumInstances - missing4ParentAtt) + m_NumClasses
+	           * m_NumAttValues[parent]);
+          } else {
+             spodeP = (classparentfreq + ((double)m_Weight
+                        / (double)(m_NumClasses * m_NumAttValues[parent])))
+                / ((m_SumInstances - missing4ParentAtt) + m_Weight);
+          }
+
+          // take into account the value of each attribute
+          for(int att = 0; att < m_NumAttributes; att++) {
+             if(attIndex[att] == -1)
+                continue;
+ 
+             double missingForParentandChildAtt = 
+              countsForClassParent[m_StartAttIndex[att] + m_NumAttValues[att]];
+
+             if(!m_MEstimates) {
+                spodeP *= (countsForClassParent[attIndex[att]] + 1.0)
+                    / ((classparentfreq - missingForParentandChildAtt)
+                       + m_NumAttValues[att]);
+             } else {
+                spodeP *= (countsForClassParent[attIndex[att]]
+                           + ((double)m_Weight / (double)m_NumAttValues[att]))
+                    / ((classparentfreq - missingForParentandChildAtt)
+                       + m_Weight);
+             }
+          }
+
+          // add this probability to the overall probability
+          probs[classVal] += spodeP;
+ 
+          // unblock the parent
+          attIndex[parent] = pIndex;
+       }
+ 
+       // check that at least one att was a parent
+       if(parentCount < 1) {
+
+          // do plain naive bayes conditional prob
+	  probs[classVal] = NBconditionalProb(instance, classVal);
+
+       } else {
+ 
+          // divide by number of parent atts to get the mean
+          probs[classVal] /= (double)(parentCount);
+       }
+    }
+ 
+    Utils.normalize(probs);
+    return probs;
+  }
+
+
+  /**
+   * Calculates the probability of the specified class for the given test
+   * instance, using naive Bayes.
+   *
+   * @param instance the instance to be classified
+   * @param classVal the class for which to calculate the probability
+   * @return predicted class probability
+   */
+  public double NBconditionalProb(Instance instance, int classVal) {
+    
+    double prob;
+    double [][] pointer;
+
+    // calculate the prior probability
+    if(!m_MEstimates) {
+       prob = (m_ClassCounts[classVal] + 1.0) / (m_SumInstances + m_NumClasses);
+    } else {
+       prob = (m_ClassCounts[classVal]
+               + ((double)m_Weight / (double)m_NumClasses))
+             / (m_SumInstances + m_Weight);
+    }
+    pointer = m_CondiCounts[classVal];
+    
+    // consider effect of each att value
+    for(int att = 0; att < m_NumAttributes; att++) {
+       if(att == m_ClassIndex || instance.isMissing(att))
+          continue;
+
+       // determine correct index for att in m_CondiCounts
+       int aIndex = m_StartAttIndex[att] + (int)instance.value(att);
+
+       if(!m_MEstimates) {
+          prob *= (double)(pointer[aIndex][aIndex] + 1.0)
+              / ((double)m_SumForCounts[classVal][att] + m_NumAttValues[att]);
+       } else {
+          prob *= (double)(pointer[aIndex][aIndex] 
+                    + ((double)m_Weight / (double)m_NumAttValues[att]))
+                 / (double)(m_SumForCounts[classVal][att] + m_Weight);
+       }
+    }
+    return prob;
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+        
+    Vector newVector = new Vector(4);
+        
+    newVector.addElement(
+       new Option("\tOutput debugging information\n",
+                  "D", 0,"-D"));
+    newVector.addElement(
+       new Option("\tImpose a frequency limit for superParents\n"
+                  + "\t(default is 1)", "F", 1,"-F <int>"));
+    newVector.addElement(
+       new Option("\tUse m-estimate instead of laplace correction\n",
+                  "M", 0,"-M"));
+    newVector.addElement(
+       new Option("\tSpecify a weight to use with m-estimate\n"
+                  + "\t(default is 1)", "W", 1,"-W <int>"));
+    return newVector.elements();
+  }
+
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Output debugging information
+   * </pre>
+   * 
+   * <pre> -F &lt;int&gt;
+   *  Impose a frequency limit for superParents
+   *  (default is 1)</pre>
+   * 
+   * <pre> -M
+   *  Use m-estimate instead of laplace correction
+   * </pre>
+   * 
+   * <pre> -W &lt;int&gt;
+   *  Specify a weight to use with m-estimate
+   *  (default is 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    m_Debug = Utils.getFlag('D', options);
+
+    String Freq = Utils.getOption('F', options);
+    if (Freq.length() != 0) 
+       m_Limit = Integer.parseInt(Freq);
+    else
+       m_Limit = 1;
+ 
+    m_MEstimates = Utils.getFlag('M', options);
+    String weight = Utils.getOption('W', options);
+    if (weight.length() != 0) {
+       if (!m_MEstimates)
+          throw new Exception("Can't use Laplace AND m-estimate weight. Choose one.");
+       m_Weight = Integer.parseInt(weight);
+    } 
+    else {
+       if (m_MEstimates)
+          m_Weight = 1;
+    }
+
+    Utils.checkForRemainingOptions(options);
+  }
+    
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector result  = new Vector();
+
+    if (m_Debug)
+       result.add("-D");
+        
+    result.add("-F");
+    result.add("" + m_Limit);
+
+    if (m_MEstimates) {
+       result.add("-M");
+       result.add("-W");
+       result.add("" + m_Weight);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+    
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightTipText() {
+    return "Set the weight for m-estimate.";
+  }
+
+  /**
+   * Sets the weight for m-estimate
+   *
+   * @param w the weight
+   */
+  public void setWeight(int w) {
+    if (!getUseMEstimates()) {
+      System.out.println(
+          "Weight is only used in conjunction with m-estimate - ignored!");
+    }
+    else {
+      if (w > 0)
+        m_Weight = w;
+      else
+        System.out.println("Weight must be greater than 0!");
+    }
+  }
+
+  /**
+   * Gets the weight used in m-estimate
+   *
+   * @return the frequency limit
+   */
+  public int getWeight() {
+    return m_Weight;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useMEstimatesTipText() {
+    return "Use m-estimate instead of laplace correction.";
+  }
+
+  /**
+   * Gets if m-estimaces is being used.
+   *
+   * @return Value of m_MEstimates.
+   */
+  public boolean getUseMEstimates() {
+    return m_MEstimates;
+  }
+
+  /**
+   * Sets if m-estimates is to be used.
+   *
+   * @param value     Value to assign to m_MEstimates.
+   */
+  public void setUseMEstimates(boolean value) {
+    m_MEstimates = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String frequencyLimitTipText() {
+    return "Attributes with a frequency in the train set below "
+           + "this value aren't used as parents.";
+  }
+
+  /**
+   * Sets the frequency limit
+   *
+   * @param f the frequency limit
+   */
+  public void setFrequencyLimit(int f) {
+    m_Limit = f;
+  }
+
+  /**
+   * Gets the frequency limit.
+   *
+   * @return the frequency limit
+   */
+  public int getFrequencyLimit() {
+    return m_Limit;
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+ 
+    StringBuffer text = new StringBuffer();
+        
+    text.append("The AODE Classifier");
+    if (m_Instances == null) {
+       text.append(": No model built yet.");
+    } else {
+       try {
+          for (int i = 0; i < m_NumClasses; i++) {
+             // print to string, the prior probabilities of class values
+             text.append("\nClass " + m_Instances.classAttribute().value(i) +
+                       ": Prior probability = " + Utils.
+                          doubleToString(((m_ClassCounts[i] + 1)
+                       /(m_SumInstances + m_NumClasses)), 4, 2)+"\n\n");
+          }
+                
+          text.append("Dataset: " + m_Instances.relationName() + "\n"
+                      + "Instances: " + m_NumInstances + "\n"
+                      + "Attributes: " + m_NumAttributes + "\n"
+		      + "Frequency limit for superParents: " + m_Limit + "\n");
+          text.append("Correction: ");
+          if (!m_MEstimates)
+             text.append("laplace\n");
+          else
+             text.append("m-estimate (m=" + m_Weight + ")\n");
+                
+       } catch (Exception ex) {
+          text.append(ex.getMessage());
+       }
+    }
+        
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new AODE(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODEsr.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODEsr.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/AODEsr.java	(revision 29)
@@ -0,0 +1,904 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AODEsr.java
+ *    Copyright (C) 2007
+ *    Algorithm developed by: Fei ZHENG and Geoff Webb
+ *    Code written by: Fei ZHENG and Janice Boughton
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ *
+ <!-- globalinfo-start -->
+ * AODEsr augments AODE with Subsumption Resolution.AODEsr detects specializations between two attribute values at classification time and deletes the generalization attribute value.<br/>
+ * For more information, see:<br/>
+ * Fei Zheng, Geoffrey I. Webb: Efficient Lazy Elimination for Averaged-One Dependence Estimators. In: Proceedings of the Twenty-third International Conference on Machine  Learning (ICML 2006), 1113-1120, 2006.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Zheng2006,
+ *    author = {Fei Zheng and Geoffrey I. Webb},
+ *    booktitle = {Proceedings of the Twenty-third International Conference on Machine  Learning (ICML 2006)},
+ *    pages = {1113-1120},
+ *    publisher = {ACM Press},
+ *    title = {Efficient Lazy Elimination for Averaged-One Dependence Estimators},
+ *    year = {2006},
+ *    ISBN = {1-59593-383-2}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Output debugging information
+ * </pre>
+ * 
+ * <pre> -C
+ *  Impose a critcal value for specialization-generalization relationship
+ *  (default is 50)</pre>
+ * 
+ * <pre> -F
+ *  Impose a frequency limit for superParents
+ *  (default is 1)</pre>
+ * 
+ * <pre> -L
+ *  Using Laplace estimation
+ *  (default is m-esimation (m=1))</pre>
+ * 
+ * <pre> -M
+ *  Weight value for m-estimation
+ *  (default is 1.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Fei Zheng
+ * @author Janice Boughton
+ * @version $Revision: 5928 $
+ */
+public class AODEsr extends AbstractClassifier
+    implements OptionHandler, WeightedInstancesHandler, UpdateableClassifier,
+               TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5602143019183068848L;
+
+  /**
+   * 3D array (m_NumClasses * m_TotalAttValues * m_TotalAttValues)
+   * of attribute counts, i.e. the number of times an attribute value occurs
+   * in conjunction with another attribute value and a class value.  
+   */
+  private double [][][] m_CondiCounts;
+ 
+  /**
+   * 2D array (m_TotalAttValues * m_TotalAttValues) of attributes counts.
+   * similar to m_CondiCounts, but ignoring class value.
+   */  
+  private double [][] m_CondiCountsNoClass; 
+    
+  /** The number of times each class value occurs in the dataset */
+  private double [] m_ClassCounts;
+    
+  /** The sums of attribute-class counts  
+   *    -- if there are no missing values for att, then
+   *       m_SumForCounts[classVal][att] will be the same as
+   *       m_ClassCounts[classVal] 
+   */
+  private double [][] m_SumForCounts;
+
+  /** The number of classes */
+  private int m_NumClasses;
+ 
+  /** The number of attributes in dataset, including class */
+  private int m_NumAttributes;
+    
+  /** The number of instances in the dataset */
+  private int m_NumInstances;
+    
+  /** The index of the class attribute */
+  private int m_ClassIndex;
+    
+  /** The dataset */
+  private Instances m_Instances;
+    
+  /**
+   * The total number of values (including an extra for each attribute's 
+   * missing value, which are included in m_CondiCounts) for all attributes 
+   * (not including class).  Eg. for three atts each with two possible values,
+   * m_TotalAttValues would be 9 (6 values + 3 missing).
+   * This variable is used when allocating space for m_CondiCounts matrix.
+   */
+  private int m_TotalAttValues;
+    
+  /** The starting index (in the m_CondiCounts matrix) of the values for each attribute */
+  private int [] m_StartAttIndex;
+    
+  /** The number of values for each attribute */
+  private int [] m_NumAttValues;
+    
+  /** The frequency of each attribute value for the dataset */
+  private double [] m_Frequencies;
+
+  /** The number of valid class values observed in dataset 
+   *  -- with no missing classes, this number is the same as m_NumInstances.
+   */
+  private double m_SumInstances;
+
+  /** An att's frequency must be this value or more to be a superParent */
+  private int m_Limit = 1;
+
+  /** If true, outputs debugging info */
+  private boolean m_Debug = false;
+  
+  /** m value for m-estimation */
+  protected  double m_MWeight = 1.0;
+  
+  /** Using LapLace estimation or not*/
+  private boolean m_Laplace = false;
+  
+  /** the critical value for the specialization-generalization */
+  private int m_Critical = 50;
+
+ 
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "AODEsr augments AODE with Subsumption Resolution."
+      +"AODEsr detects specializations between two attribute "
+      +"values at classification time and deletes the generalization "
+      +"attribute value.\n"
+      +"For more information, see:\n"
+      + getTechnicalInformation().toString();
+  }
+ 
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Fei Zheng and Geoffrey I. Webb");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "Efficient Lazy Elimination for Averaged-One Dependence Estimators");
+    result.setValue(Field.PAGES, "1113-1120");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the Twenty-third International Conference on Machine  Learning (ICML 2006)");
+    result.setValue(Field.PUBLISHER, "ACM Press");
+    result.setValue(Field.ISBN, "1-59593-383-2");
+
+    return result;
+  }
+
+ /**
+  * Returns default capabilities of the classifier.
+  *
+  * @return      the capabilities of this classifier
+  */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data
+   * @throws Exception if the classifier has not been generated
+   * successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    m_Instances = new Instances(instances);
+    m_Instances.deleteWithMissingClass();
+
+    // reset variable for this fold
+    m_SumInstances = 0;
+    m_ClassIndex = instances.classIndex();
+    m_NumInstances = m_Instances.numInstances();
+    m_NumAttributes = instances.numAttributes();
+    m_NumClasses = instances.numClasses();
+
+    // allocate space for attribute reference arrays
+    m_StartAttIndex = new int[m_NumAttributes];
+    m_NumAttValues = new int[m_NumAttributes];
+ 
+    m_TotalAttValues = 0;
+    for(int i = 0; i < m_NumAttributes; i++) {
+       if(i != m_ClassIndex) {
+          m_StartAttIndex[i] = m_TotalAttValues;
+          m_NumAttValues[i] = m_Instances.attribute(i).numValues();
+          m_TotalAttValues += m_NumAttValues[i] + 1;
+          // + 1 so room for missing value count
+       } else {
+          // m_StartAttIndex[i] = -1;  // class isn't included
+          m_NumAttValues[i] = m_NumClasses;
+       }
+    }
+
+    // allocate space for counts and frequencies
+    m_CondiCounts = new double[m_NumClasses][m_TotalAttValues][m_TotalAttValues];
+    m_ClassCounts = new double[m_NumClasses];
+    m_SumForCounts = new double[m_NumClasses][m_NumAttributes];
+    m_Frequencies = new double[m_TotalAttValues];
+    m_CondiCountsNoClass = new double[m_TotalAttValues][m_TotalAttValues];
+    
+    // calculate the counts
+    for(int k = 0; k < m_NumInstances; k++) {
+       addToCounts((Instance)m_Instances.instance(k));
+    }
+
+    // free up some space
+    m_Instances = new Instances(m_Instances, 0);
+  }
+ 
+
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance the new training instance to include in the model 
+   * @throws Exception if the instance could not be incorporated in
+   * the model.
+   */
+  public void updateClassifier(Instance instance) {
+    this.addToCounts(instance);
+  }
+
+  /**
+   * Puts an instance's values into m_CondiCounts, m_ClassCounts and 
+   * m_SumInstances.
+   *
+   * @param instance the instance whose values are to be put into the 
+   *                 counts variables
+   */
+  private void addToCounts(Instance instance) {
+ 
+    double [] countsPointer;
+    double [] countsNoClassPointer;
+ 
+    if(instance.classIsMissing())
+       return;   // ignore instances with missing class
+
+    int classVal = (int)instance.classValue();
+    double weight = instance.weight();
+ 
+    m_ClassCounts[classVal] += weight;
+    m_SumInstances += weight;
+   
+    // store instance's att val indexes in an array, b/c accessing it 
+    // in loop(s) is more efficient
+    int [] attIndex = new int[m_NumAttributes];
+    for(int i = 0; i < m_NumAttributes; i++) {
+       if(i == m_ClassIndex)
+          attIndex[i] = -1;  // we don't use the class attribute in counts
+       else {
+          if(instance.isMissing(i))
+             attIndex[i] = m_StartAttIndex[i] + m_NumAttValues[i];
+          else
+             attIndex[i] = m_StartAttIndex[i] + (int)instance.value(i);
+       }
+    }
+
+    for(int Att1 = 0; Att1 < m_NumAttributes; Att1++) {
+       if(attIndex[Att1] == -1)
+          continue;   // avoid pointless looping as Att1 is currently the class attribute
+
+       m_Frequencies[attIndex[Att1]] += weight;
+       
+       // if this is a missing value, we don't want to increase sumforcounts
+       if(!instance.isMissing(Att1))
+          m_SumForCounts[classVal][Att1] += weight;
+
+       // save time by referencing this now, rather than repeatedly in the loop
+       countsPointer = m_CondiCounts[classVal][attIndex[Att1]];
+       countsNoClassPointer = m_CondiCountsNoClass[attIndex[Att1]];
+
+       for(int Att2 = 0; Att2 < m_NumAttributes; Att2++) {
+          if(attIndex[Att2] != -1) {
+             countsPointer[attIndex[Att2]] += weight;
+             countsNoClassPointer[attIndex[Att2]] += weight;
+          }
+       }
+    }
+  }
+ 
+ 
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+
+    // accumulates posterior probabilities for each class
+    double [] probs = new double[m_NumClasses];
+
+    // index for parent attribute value, and a count of parents used
+    int pIndex, parentCount; 
+
+    int [] SpecialGeneralArray = new int[m_NumAttributes];
+    
+    // pointers for efficiency
+    double [][] countsForClass;
+    double [] countsForClassParent;
+    double [] countsForAtti;
+    double [] countsForAttj;
+
+    // store instance's att values in an int array, so accessing them 
+    // is more efficient in loop(s).
+    int [] attIndex = new int[m_NumAttributes];
+    for(int att = 0; att < m_NumAttributes; att++) {
+       if(instance.isMissing(att) || att == m_ClassIndex)
+          attIndex[att] = -1; // can't use class & missing vals in calculations
+       else
+          attIndex[att] = m_StartAttIndex[att] + (int)instance.value(att);
+    }
+    // -1 indicates attribute is not a generalization of any other attributes
+    for(int i = 0; i < m_NumAttributes; i++) {
+       SpecialGeneralArray[i] = -1;
+    }
+
+    // calculate the specialization-generalization array
+    for(int i = 0; i < m_NumAttributes; i++){
+       // skip i if it's the class or is missing
+       if(attIndex[i] == -1)  continue;
+       countsForAtti = m_CondiCountsNoClass[attIndex[i]];
+ 
+       for(int j = 0; j < m_NumAttributes; j++) {
+          // skip j if it's the class, missing, is i or a generalization of i
+          if((attIndex[j] == -1) || (i == j) || (SpecialGeneralArray[j] == i))
+            continue;
+         
+          countsForAttj = m_CondiCountsNoClass[attIndex[j]];
+
+          // check j's frequency is above critical value
+          if(countsForAttj[attIndex[j]] > m_Critical) {
+
+             // skip j if the frequency of i and j together is not equivalent
+	     // to the frequency of j alone
+             if(countsForAttj[attIndex[j]] == countsForAtti[attIndex[j]]) {
+
+             // if attributes i and j are both a specialization of each other
+             // avoid deleting both by skipping j
+                if((countsForAttj[attIndex[j]] == countsForAtti[attIndex[i]])
+                 && (i < j)){
+                  continue;
+                } else {
+                    // set the specialization relationship
+                    SpecialGeneralArray[i] = j;
+                    break; // break out of j loop because a specialization has been found
+                }
+             }
+          }
+       }
+    }
+ 
+    // calculate probabilities for each possible class value
+    for(int classVal = 0; classVal < m_NumClasses; classVal++) {
+ 
+       probs[classVal] = 0;
+       double x = 0;
+       parentCount = 0;
+ 
+       countsForClass = m_CondiCounts[classVal];
+
+       // each attribute has a turn of being the parent
+       for(int parent = 0; parent < m_NumAttributes; parent++) {
+          if(attIndex[parent] == -1)
+             continue;  // skip class attribute or missing value
+
+          // determine correct index for the parent in m_CondiCounts matrix
+          pIndex = attIndex[parent];
+
+          // check that the att value has a frequency of m_Limit or greater
+	  if(m_Frequencies[pIndex] < m_Limit) 
+             continue;
+          
+          // delete the generalization attributes.
+          if(SpecialGeneralArray[parent] != -1)
+             continue;
+
+          countsForClassParent = countsForClass[pIndex];
+
+          // block the parent from being its own child
+          attIndex[parent] = -1;
+
+          parentCount++;
+
+          double classparentfreq = countsForClassParent[pIndex];
+
+          // find the number of missing values for parent's attribute
+          double missing4ParentAtt = 
+            m_Frequencies[m_StartAttIndex[parent] + m_NumAttValues[parent]];
+
+          // calculate the prior probability -- P(parent & classVal)
+           if (m_Laplace){
+             x = LaplaceEstimate(classparentfreq, m_SumInstances - missing4ParentAtt, 
+                                    m_NumClasses * m_NumAttValues[parent]);
+          } else {
+          
+             x = MEstimate(classparentfreq, m_SumInstances - missing4ParentAtt, 
+                                    m_NumClasses * m_NumAttValues[parent]);
+          }
+
+
+    
+          // take into account the value of each attribute
+          for(int att = 0; att < m_NumAttributes; att++) {
+             if(attIndex[att] == -1) // skip class attribute or missing value
+                continue;
+             // delete the generalization attributes.
+             if(SpecialGeneralArray[att] != -1)
+                continue;
+            
+ 
+             double missingForParentandChildAtt = 
+                      countsForClassParent[m_StartAttIndex[att] + m_NumAttValues[att]];
+
+             if (m_Laplace){
+                x *= LaplaceEstimate(countsForClassParent[attIndex[att]], 
+                    classparentfreq - missingForParentandChildAtt, m_NumAttValues[att]);
+             } else {
+                x *= MEstimate(countsForClassParent[attIndex[att]], 
+                    classparentfreq - missingForParentandChildAtt, m_NumAttValues[att]);
+             }
+          }
+
+          // add this probability to the overall probability
+          probs[classVal] += x;
+ 
+          // unblock the parent
+          attIndex[parent] = pIndex;
+       }
+ 
+       // check that at least one att was a parent
+       if(parentCount < 1) {
+
+          // do plain naive bayes conditional prob
+	  probs[classVal] = NBconditionalProb(instance, classVal);
+          //probs[classVal] = Double.NaN;
+
+       } else {
+ 
+          // divide by number of parent atts to get the mean
+          probs[classVal] /= (double)(parentCount);
+       }
+    }
+    Utils.normalize(probs);
+    return probs;
+  }
+
+
+  /**
+   * Calculates the probability of the specified class for the given test
+   * instance, using naive Bayes.
+   *
+   * @param instance the instance to be classified
+   * @param classVal the class for which to calculate the probability
+   * @return predicted class probability
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double NBconditionalProb(Instance instance, int classVal)
+                                                     throws Exception {
+    double prob;
+    int attIndex;
+    double [][] pointer;
+
+    // calculate the prior probability
+    if(m_Laplace) {
+       prob = LaplaceEstimate(m_ClassCounts[classVal],m_SumInstances,m_NumClasses); 
+    } else {
+       prob = MEstimate(m_ClassCounts[classVal], m_SumInstances, m_NumClasses);
+    }
+    pointer = m_CondiCounts[classVal];
+    
+    // consider effect of each att value
+    for(int att = 0; att < m_NumAttributes; att++) {
+       if(att == m_ClassIndex || instance.isMissing(att))
+          continue;
+       
+       // determine correct index for att in m_CondiCounts
+       attIndex = m_StartAttIndex[att] + (int)instance.value(att);
+       if (m_Laplace){
+         prob *= LaplaceEstimate((double)pointer[attIndex][attIndex], 
+                   (double)m_SumForCounts[classVal][att], m_NumAttValues[att]);
+       } else {
+           prob *= MEstimate((double)pointer[attIndex][attIndex], 
+                   (double)m_SumForCounts[classVal][att], m_NumAttValues[att]);
+       }
+    }
+    return prob;
+  }
+
+
+  /**
+   * Returns the probability estimate, using m-estimate
+   *
+   * @param frequency frequency of value of interest
+   * @param total count of all values
+   * @param numValues number of different values
+   * @return the probability estimate
+   */
+  public double MEstimate(double frequency, double total,
+                          double numValues) {
+    
+    return (frequency + m_MWeight / numValues) / (total + m_MWeight);
+  }
+   
+  /**
+   * Returns the probability estimate, using laplace correction
+   *
+   * @param frequency frequency of value of interest
+   * @param total count of all values
+   * @param numValues number of different values
+   * @return the probability estimate
+   */
+  public double LaplaceEstimate(double frequency, double total,
+                                double numValues) {
+    
+    return (frequency + 1.0) / (total + numValues);
+  }
+    
+   
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+        
+    newVector.addElement(
+       new Option("\tOutput debugging information\n",
+                  "D", 0,"-D"));
+    newVector.addElement(
+       new Option("\tImpose a critcal value for specialization-generalization relationship\n"
+                  + "\t(default is 50)", "C", 1,"-C"));
+    newVector.addElement(
+       new Option("\tImpose a frequency limit for superParents\n"
+                  + "\t(default is 1)", "F", 2,"-F"));
+    newVector.addElement(
+       new Option("\tUsing Laplace estimation\n"
+                  + "\t(default is m-esimation (m=1))",
+                  "L", 3,"-L"));
+    newVector.addElement(
+       new Option("\tWeight value for m-estimation\n"
+                  + "\t(default is 1.0)", "M", 4,"-M"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Output debugging information
+   * </pre>
+   * 
+   * <pre> -C
+   *  Impose a critcal value for specialization-generalization relationship
+   *  (default is 50)</pre>
+   * 
+   * <pre> -F
+   *  Impose a frequency limit for superParents
+   *  (default is 1)</pre>
+   * 
+   * <pre> -L
+   *  Using Laplace estimation
+   *  (default is m-esimation (m=1))</pre>
+   * 
+   * <pre> -M
+   *  Weight value for m-estimation
+   *  (default is 1.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    m_Debug = Utils.getFlag('D', options);
+
+    String Critical = Utils.getOption('C', options);
+    if(Critical.length() != 0) 
+       m_Critical = Integer.parseInt(Critical);
+    else
+       m_Critical = 50;
+    
+    String Freq = Utils.getOption('F', options);
+    if(Freq.length() != 0) 
+       m_Limit = Integer.parseInt(Freq);
+    else
+       m_Limit = 1;
+    
+    m_Laplace = Utils.getFlag('L', options);
+    String MWeight = Utils.getOption('M', options); 
+    if(MWeight.length() != 0) {
+       if(m_Laplace)
+          throw new Exception("weight for m-estimate is pointless if using laplace estimation!");
+       m_MWeight = Double.parseDouble(MWeight);
+    } else
+       m_MWeight = 1.0;
+    
+    Utils.checkForRemainingOptions(options);
+  }
+    
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+        
+    Vector result  = new Vector();
+
+    if (m_Debug)
+       result.add("-D");
+
+    result.add("-F");
+    result.add("" + m_Limit);
+
+    if (m_Laplace) {
+       result.add("-L");
+    } else {
+       result.add("-M");
+       result.add("" + m_MWeight);
+    }
+        
+    result.add("-C");
+    result.add("" + m_Critical);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+ 
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String mestWeightTipText() {
+    return "Set the weight for m-estimate.";
+  }
+
+  /**
+   * Sets the weight for m-estimate
+   *
+   * @param w the weight
+   */
+  public void setMestWeight(double w) {
+    if (getUseLaplace()) {
+       System.out.println(
+          "Weight is only used in conjunction with m-estimate - ignored!");
+    } else {
+      if(w > 0)
+         m_MWeight = w;
+      else
+         System.out.println("M-Estimate Weight must be greater than 0!");
+    }
+  }
+
+  /**
+   * Gets the weight used in m-estimate
+   *
+   * @return the weight for m-estimation
+   */
+  public double getMestWeight() {
+    return m_MWeight;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useLaplaceTipText() {
+    return "Use Laplace correction instead of m-estimation.";
+  }
+
+  /**
+   * Gets if laplace correction is being used.
+   *
+   * @return Value of m_Laplace.
+   */
+  public boolean getUseLaplace() {
+    return m_Laplace;
+  }
+
+  /**
+   * Sets if laplace correction is to be used.
+   *
+   * @param value Value to assign to m_Laplace.
+   */
+  public void setUseLaplace(boolean value) {
+    m_Laplace = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String frequencyLimitTipText() {
+    return "Attributes with a frequency in the train set below "
+           + "this value aren't used as parents.";
+  }
+
+  /**
+   * Sets the frequency limit
+   *
+   * @param f the frequency limit
+   */
+  public void setFrequencyLimit(int f) {
+    m_Limit = f;
+  }
+
+  /**
+   * Gets the frequency limit.
+   *
+   * @return the frequency limit
+   */
+  public int getFrequencyLimit() {
+    return m_Limit;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String criticalValueTipText() {
+    return "Specify critical value for specialization-generalization "
+           + "relationship (default 50).";
+  }
+
+  /**
+   * Sets the critical value
+   *
+   * @param c the critical value
+   */
+  public void setCriticalValue(int c) {
+    m_Critical = c;
+  }
+
+  /**
+   * Gets the critical value.
+   *
+   * @return the critical value
+   */
+  public int getCriticalValue() {
+    return m_Critical;
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+ 
+    StringBuffer text = new StringBuffer();
+        
+    text.append("The AODEsr Classifier");
+    if (m_Instances == null) {
+       text.append(": No model built yet.");
+    } else {
+       try {
+          for (int i = 0; i < m_NumClasses; i++) {
+             // print to string, the prior probabilities of class values
+             text.append("\nClass " + m_Instances.classAttribute().value(i) +
+                       ": Prior probability = " + Utils.
+                          doubleToString(((m_ClassCounts[i] + 1)
+                       /(m_SumInstances + m_NumClasses)), 4, 2)+"\n\n");
+          }
+                
+          text.append("Dataset: " + m_Instances.relationName() + "\n"
+                      + "Instances: " + m_NumInstances + "\n"
+                      + "Attributes: " + m_NumAttributes + "\n"
+		      + "Frequency limit for superParents: " + m_Limit + "\n"
+                      + "Critical value for the specializtion-generalization "
+                      + "relationship: " + m_Critical + "\n");
+          if(m_Laplace) {
+            text.append("Using LapLace estimation.");
+          } else {
+              text.append("Using m-estimation, m = " + m_MWeight); 
+          }
+       } catch (Exception ex) {
+          text.append(ex.getMessage());
+       }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new AODEsr(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesNet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesNet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesNet.java	(revision 29)
@@ -0,0 +1,1130 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNet.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.bayes.net.ADNode;
+import weka.classifiers.bayes.net.BIFReader;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.classifiers.bayes.net.estimate.BayesNetEstimator;
+import weka.classifiers.bayes.net.estimate.DiscreteEstimatorBayes;
+import weka.classifiers.bayes.net.estimate.SimpleEstimator;
+import weka.classifiers.bayes.net.search.SearchAlgorithm;
+import weka.classifiers.bayes.net.search.local.K2;
+import weka.classifiers.bayes.net.search.local.LocalScoreSearchAlgorithm;
+import weka.classifiers.bayes.net.search.local.Scoreable;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.estimators.Estimator;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Bayes Network learning using various search algorithms and quality measures.<br/>
+ * Base class for a Bayes Network classifier. Provides datastructures (network structure, conditional probability distributions, etc.) and facilities common to Bayes Network learning algorithms like K2 and B.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * http://sourceforge.net/projects/weka/files/documentation/WekaManual-3-7-0.pdf/download
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Do not use ADTree data structure
+ * </pre>
+ * 
+ * <pre> -B &lt;BIF file&gt;
+ *  BIF file to compare with
+ * </pre>
+ * 
+ * <pre> -Q weka.classifiers.bayes.net.search.SearchAlgorithm
+ *  Search algorithm
+ * </pre>
+ * 
+ * <pre> -E weka.classifiers.bayes.net.estimate.SimpleEstimator
+ *  Estimator algorithm
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 5928 $
+ */
+public class BayesNet
+  extends AbstractClassifier
+  implements OptionHandler, WeightedInstancesHandler, Drawable, 
+             AdditionalMeasureProducer {
+
+  /** for serialization */
+  static final long serialVersionUID = 746037443258775954L;
+
+
+  /**
+   * The parent sets.
+   */
+  protected ParentSet[] m_ParentSets;
+
+  /**
+   * The attribute estimators containing CPTs.
+   */
+  public Estimator[][] m_Distributions;
+
+
+  /** filter used to quantize continuous variables, if any **/
+  protected Discretize m_DiscretizeFilter = null;
+
+  /** attribute index of a non-nominal attribute */
+  int m_nNonDiscreteAttribute = -1;
+
+  /** filter used to fill in missing values, if any **/
+  protected ReplaceMissingValues m_MissingValuesFilter = null;	
+
+  /**
+   * The number of classes
+   */
+  protected int m_NumClasses;
+
+  /**
+   * The dataset header for the purposes of printing out a semi-intelligible
+   * model
+   */
+  public Instances m_Instances;
+
+  /**
+   * Datastructure containing ADTree representation of the database.
+   * This may result in more efficient access to the data.
+   */
+  ADNode m_ADTree;
+
+  /**
+   * Bayes network to compare the structure with.
+   */
+  protected BIFReader m_otherBayesNet = null;
+
+  /**
+   * Use the experimental ADTree datastructure for calculating contingency tables
+   */
+  boolean m_bUseADTree = false;
+
+  /**
+   * Search algorithm used for learning the structure of a network.
+   */
+  SearchAlgorithm m_SearchAlgorithm = new K2();
+
+  /**
+   * Search algorithm used for learning the structure of a network.
+   */
+  BayesNetEstimator m_BayesNetEstimator = new SimpleEstimator();
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   * 
+   * @param instances set of instances serving as training data
+   * @throws Exception if the classifier has not been generated
+   * successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    // ensure we have a data set with discrete variables only and with no missing values
+    instances = normalizeDataSet(instances);
+
+    // Copy the instances
+    m_Instances = new Instances(instances);
+
+    // sanity check: need more than 1 variable in datat set
+    m_NumClasses = instances.numClasses();
+
+    // initialize ADTree
+    if (m_bUseADTree) {
+      m_ADTree = ADNode.makeADTree(instances);
+      //      System.out.println("Oef, done!");
+    }
+
+    // build the network structure
+    initStructure();
+
+    // build the network structure
+    buildStructure();
+
+    // build the set of CPTs
+    estimateCPTs();
+
+    // Save space
+    // m_Instances = new Instances(m_Instances, 0);
+    m_ADTree = null;
+  } // buildClassifier
+
+  /** ensure that all variables are nominal and that there are no missing values
+   * @param instances data set to check and quantize and/or fill in missing values
+   * @return filtered instances
+   * @throws Exception if a filter (Discretize, ReplaceMissingValues) fails
+   */
+  protected Instances normalizeDataSet(Instances instances) throws Exception {
+    m_DiscretizeFilter = null;
+    m_MissingValuesFilter = null;
+
+    boolean bHasNonNominal = false;
+    boolean bHasMissingValues = false;
+
+    Enumeration enu = instances.enumerateAttributes();		
+    while (enu.hasMoreElements()) {
+      Attribute attribute = (Attribute) enu.nextElement();
+      if (attribute.type() != Attribute.NOMINAL) {
+	m_nNonDiscreteAttribute = attribute.index();
+	bHasNonNominal = true;
+	//throw new UnsupportedAttributeTypeException("BayesNet handles nominal variables only. Non-nominal variable in dataset detected.");
+      }
+      Enumeration enum2 = instances.enumerateInstances();
+      while (enum2.hasMoreElements()) {
+	if (((Instance) enum2.nextElement()).isMissing(attribute)) {
+	  bHasMissingValues = true;
+	  // throw new NoSupportForMissingValuesException("BayesNet: no missing values, please.");
+	}
+      }
+    }
+
+    if (bHasNonNominal) {
+      System.err.println("Warning: discretizing data set");
+      m_DiscretizeFilter = new Discretize();
+      m_DiscretizeFilter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_DiscretizeFilter);
+    }
+
+    if (bHasMissingValues) {
+      System.err.println("Warning: filling in missing values in data set");
+      m_MissingValuesFilter = new ReplaceMissingValues();
+      m_MissingValuesFilter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_MissingValuesFilter);
+    }
+    return instances;
+  } // normalizeDataSet
+
+  /** ensure that all variables are nominal and that there are no missing values
+   * @param instance instance to check and quantize and/or fill in missing values
+   * @return filtered instance
+   * @throws Exception if a filter (Discretize, ReplaceMissingValues) fails
+   */
+  protected Instance normalizeInstance(Instance instance) throws Exception {
+    if ((m_DiscretizeFilter != null) &&
+	(instance.attribute(m_nNonDiscreteAttribute).type() != Attribute.NOMINAL)) {
+      m_DiscretizeFilter.input(instance);
+      instance = m_DiscretizeFilter.output();
+    }
+    if (m_MissingValuesFilter != null) {
+      m_MissingValuesFilter.input(instance);
+      instance = m_MissingValuesFilter.output();
+    } else {
+      // is there a missing value in this instance?
+      // this can happen when there is no missing value in the training set
+      for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+	if (iAttribute != instance.classIndex() && instance.isMissing(iAttribute)) {
+	  System.err.println("Warning: Found missing value in test set, filling in values.");
+	  m_MissingValuesFilter = new ReplaceMissingValues();
+	  m_MissingValuesFilter.setInputFormat(m_Instances);
+	  Filter.useFilter(m_Instances, m_MissingValuesFilter);
+	  m_MissingValuesFilter.input(instance);
+	  instance = m_MissingValuesFilter.output();
+	  iAttribute = m_Instances.numAttributes();
+	}
+      }
+    }
+    return instance;
+  } // normalizeInstance
+
+  /**
+   * Init structure initializes the structure to an empty graph or a Naive Bayes
+   * graph (depending on the -N flag).
+   * 
+   * @throws Exception in case of an error
+   */
+  public void initStructure() throws Exception {
+
+    // initialize topological ordering
+    //    m_nOrder = new int[m_Instances.numAttributes()];
+    //    m_nOrder[0] = m_Instances.classIndex();
+
+    int nAttribute = 0;
+
+    for (int iOrder = 1; iOrder < m_Instances.numAttributes(); iOrder++) {
+      if (nAttribute == m_Instances.classIndex()) {
+	nAttribute++;
+      }
+
+      //      m_nOrder[iOrder] = nAttribute++;
+    }
+
+    // reserve memory
+    m_ParentSets = new ParentSet[m_Instances.numAttributes()];
+
+    for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+      m_ParentSets[iAttribute] = new ParentSet(m_Instances.numAttributes());
+    }
+  } // initStructure
+
+  /**
+   * buildStructure determines the network structure/graph of the network.
+   * The default behavior is creating a network where all nodes have the first
+   * node as its parent (i.e., a BayesNet that behaves like a naive Bayes classifier).
+   * This method can be overridden by derived classes to restrict the class
+   * of network structures that are acceptable.
+   * 
+   * @throws Exception in case of an error
+   */
+  public void buildStructure() throws Exception {
+    m_SearchAlgorithm.buildStructure(this, m_Instances);
+  } // buildStructure
+
+  /**
+   * estimateCPTs estimates the conditional probability tables for the Bayes
+   * Net using the network structure.
+   * 
+   * @throws Exception in case of an error
+   */
+  public void estimateCPTs() throws Exception {
+    m_BayesNetEstimator.estimateCPTs(this);
+  } // estimateCPTs
+
+  /**
+   * initializes the conditional probabilities
+   * 
+   * @throws Exception in case of an error
+   */
+  public void initCPTs() throws Exception {
+    m_BayesNetEstimator.initCPTs(this);
+  } // estimateCPTs
+
+  /**
+   * Updates the classifier with the given instance.
+   * 
+   * @param instance the new training instance to include in the model
+   * @throws Exception if the instance could not be incorporated in
+   * the model.
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+    instance = normalizeInstance(instance);
+    m_BayesNetEstimator.updateClassifier(this, instance);
+  } // updateClassifier
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   * 
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    instance = normalizeInstance(instance);
+    return m_BayesNetEstimator.distributionForInstance(this, instance);
+  } // distributionForInstance
+
+  /**
+   * Calculates the counts for Dirichlet distribution for the 
+   * class membership probabilities for the given test instance.
+   * 
+   * @param instance the instance to be classified
+   * @return counts for Dirichlet distribution for class probability 
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double[] countsForInstance(Instance instance) throws Exception {
+    double[] fCounts = new double[m_NumClasses];
+
+    for (int iClass = 0; iClass < m_NumClasses; iClass++) {
+      fCounts[iClass] = 0.0;
+    }
+
+    for (int iClass = 0; iClass < m_NumClasses; iClass++) {
+      double fCount = 0;
+
+      for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+	double iCPT = 0;
+
+	for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+	  int nParent = m_ParentSets[iAttribute].getParent(iParent);
+
+	  if (nParent == m_Instances.classIndex()) {
+	    iCPT = iCPT * m_NumClasses + iClass;
+	  } else {
+	    iCPT = iCPT * m_Instances.attribute(nParent).numValues() + instance.value(nParent);
+	  }
+	}
+
+	if (iAttribute == m_Instances.classIndex()) {
+	  fCount += ((DiscreteEstimatorBayes) m_Distributions[iAttribute][(int) iCPT]).getCount(iClass);
+	} else {
+	  fCount
+	  += ((DiscreteEstimatorBayes) m_Distributions[iAttribute][(int) iCPT]).getCount(
+	      instance.value(iAttribute));
+	}
+      }
+
+      fCounts[iClass] += fCount;
+    }
+    return fCounts;
+  } // countsForInstance
+
+  /**
+   * Returns an enumeration describing the available options
+   * 
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option("\tDo not use ADTree data structure\n", "D", 0, "-D"));
+    newVector.addElement(new Option("\tBIF file to compare with\n", "B", 1, "-B <BIF file>"));
+    newVector.addElement(new Option("\tSearch algorithm\n", "Q", 1, "-Q weka.classifiers.bayes.net.search.SearchAlgorithm"));
+    newVector.addElement(new Option("\tEstimator algorithm\n", "E", 1, "-E weka.classifiers.bayes.net.estimate.SimpleEstimator"));
+
+    return newVector.elements();
+  } // listOptions
+
+  /**
+   * Parses a given list of options. <p>
+   * 
+     <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Do not use ADTree data structure
+   * </pre>
+   * 
+   * <pre> -B &lt;BIF file&gt;
+   *  BIF file to compare with
+   * </pre>
+   * 
+   * <pre> -Q weka.classifiers.bayes.net.search.SearchAlgorithm
+   *  Search algorithm
+   * </pre>
+   * 
+   * <pre> -E weka.classifiers.bayes.net.estimate.SimpleEstimator
+   *  Estimator algorithm
+   * </pre>
+   * 
+     <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    m_bUseADTree = !(Utils.getFlag('D', options));
+
+    String sBIFFile = Utils.getOption('B', options);
+    if (sBIFFile != null && !sBIFFile.equals("")) {
+      setBIFFile(sBIFFile);
+    }
+
+    String searchAlgorithmName = Utils.getOption('Q', options);
+    if (searchAlgorithmName.length() != 0) {
+      setSearchAlgorithm(
+	  (SearchAlgorithm) Utils.forName(
+	      SearchAlgorithm.class,
+	      searchAlgorithmName,
+	      partitionOptions(options)));
+    }
+    else {
+      setSearchAlgorithm(new K2());
+    }
+
+
+    String estimatorName = Utils.getOption('E', options);
+    if (estimatorName.length() != 0) {
+      setEstimator(
+	  (BayesNetEstimator) Utils.forName(
+	      BayesNetEstimator.class,
+	      estimatorName,
+	      Utils.partitionOptions(options)));
+    }
+    else {
+      setEstimator(new SimpleEstimator());
+    }
+
+    Utils.checkForRemainingOptions(options);
+  } // setOptions
+
+  /**
+   * Returns the secondary set of options (if any) contained in
+   * the supplied options array. The secondary set is defined to
+   * be any options after the first "--" but before the "-E". These 
+   * options are removed from the original options array.
+   *
+   * @param options the input array of options
+   * @return the array of secondary options
+   */
+  public static String [] partitionOptions(String [] options) {
+
+    for (int i = 0; i < options.length; i++) {
+      if (options[i].equals("--")) {
+	// ensure it follows by a -E option
+	int j = i;
+	while ((j < options.length) && !(options[j].equals("-E"))) {
+	  j++;
+	}
+        /*	if (j >= options.length) {
+	  return new String[0];
+          } */
+	options[i++] = "";
+	String [] result = new String [options.length - i];
+	j = i;
+	while ((j < options.length) && !(options[j].equals("-E"))) {
+	  result[j - i] = options[j];
+	  options[j] = "";
+	  j++;
+	}
+	while(j < options.length) {
+	  result[j - i] = "";
+	  j++;
+	}		 
+	return result;
+      }
+    }
+    return new String [0];
+  }
+
+
+  /**
+   * Gets the current settings of the classifier.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    String[] searchOptions = m_SearchAlgorithm.getOptions();
+    String[] estimatorOptions = m_BayesNetEstimator.getOptions();
+    String[] options = new String[11 + searchOptions.length + estimatorOptions.length];
+    int current = 0;
+
+    if (!m_bUseADTree) {
+      options[current++] = "-D";
+    }
+
+    if (m_otherBayesNet != null) {
+      options[current++] = "-B";
+      options[current++] = ((BIFReader) m_otherBayesNet).getFileName();
+    }
+
+    options[current++] = "-Q";
+    options[current++] = "" + getSearchAlgorithm().getClass().getName();
+    options[current++] = "--";
+    for (int iOption = 0; iOption < searchOptions.length; iOption++) {
+      options[current++] = searchOptions[iOption];
+    }
+
+    options[current++] = "-E";
+    options[current++] = "" + getEstimator().getClass().getName();
+    options[current++] = "--";
+    for (int iOption = 0; iOption < estimatorOptions.length; iOption++) {
+      options[current++] = estimatorOptions[iOption];
+    }
+
+    // Fill up rest with empty strings, not nulls!
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  } // getOptions
+
+  /**
+   * Set the SearchAlgorithm used in searching for network structures. 
+   * @param newSearchAlgorithm the SearchAlgorithm to use.
+   */
+  public void setSearchAlgorithm(SearchAlgorithm newSearchAlgorithm) {
+    m_SearchAlgorithm = newSearchAlgorithm;
+  }
+
+  /**
+   * Get the SearchAlgorithm used as the search algorithm
+   * @return the SearchAlgorithm used as the search algorithm
+   */
+  public SearchAlgorithm getSearchAlgorithm() {
+    return m_SearchAlgorithm;
+  }
+
+  /**
+   * Set the Estimator Algorithm used in calculating the CPTs 
+   * @param newBayesNetEstimator the Estimator to use.
+   */
+  public void setEstimator(BayesNetEstimator newBayesNetEstimator) {
+    m_BayesNetEstimator = newBayesNetEstimator;
+  }
+
+  /**
+   * Get the BayesNetEstimator used for calculating the CPTs
+   * @return the BayesNetEstimator used.
+   */
+  public BayesNetEstimator getEstimator() {
+    return m_BayesNetEstimator;
+  }
+
+  /**
+   * Set whether ADTree structure is used or not
+   * @param bUseADTree true if an ADTree structure is used
+   */
+  public void setUseADTree(boolean bUseADTree) {
+    m_bUseADTree = bUseADTree;
+  }
+
+  /**
+   * Method declaration
+   * @return whether ADTree structure is used or not
+   */
+  public boolean getUseADTree() {
+    return m_bUseADTree;
+  }
+
+  /**
+   * Set name of network in BIF file to compare with
+   * @param sBIFFile the name of the BIF file
+   */
+  public void setBIFFile(String sBIFFile) {
+    try {
+      m_otherBayesNet = new BIFReader().processFile(sBIFFile);
+    } catch (Throwable t) {
+      m_otherBayesNet = null;
+    }
+  }
+
+  /**
+   * Get name of network in BIF file to compare with
+   * @return BIF file name
+   */
+  public String getBIFFile() {
+    if (m_otherBayesNet != null) {
+      return m_otherBayesNet.getFileName();
+    }
+    return "";
+  }
+
+
+  /**
+   * Returns a description of the classifier.
+   * 
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+
+    text.append("Bayes Network Classifier");
+    text.append("\n" + (m_bUseADTree ? "Using " : "not using ") + "ADTree");
+
+    if (m_Instances == null) {
+      text.append(": No model built yet.");
+    } else {
+
+      // flatten BayesNet down to text
+      text.append("\n#attributes=");
+      text.append(m_Instances.numAttributes());
+      text.append(" #classindex=");
+      text.append(m_Instances.classIndex());
+      text.append("\nNetwork structure (nodes followed by parents)\n");
+
+      for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+	text.append(
+	    m_Instances.attribute(iAttribute).name()
+	    + "("
+		+ m_Instances.attribute(iAttribute).numValues()
+		+ "): ");
+
+	for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+	  text.append(m_Instances.attribute(m_ParentSets[iAttribute].getParent(iParent)).name() + " ");
+	}
+
+	text.append("\n");
+
+	// Description of distributions tends to be too much detail, so it is commented out here
+	// for (int iParent = 0; iParent < m_ParentSets[iAttribute].GetCardinalityOfParents(); iParent++) {
+	// text.append('(' + m_Distributions[iAttribute][iParent].toString() + ')');
+	// }
+	// text.append("\n");
+      }
+
+      text.append("LogScore Bayes: " + measureBayesScore() + "\n");
+      text.append("LogScore BDeu: " + measureBDeuScore() + "\n");
+      text.append("LogScore MDL: " + measureMDLScore() + "\n");
+      text.append("LogScore ENTROPY: " + measureEntropyScore() + "\n");
+      text.append("LogScore AIC: " + measureAICScore() + "\n");
+
+      if (m_otherBayesNet != null) {
+	text.append(
+	    "Missing: "
+	    + m_otherBayesNet.missingArcs(this)
+	    + " Extra: "
+	    + m_otherBayesNet.extraArcs(this)
+	    + " Reversed: "
+	    + m_otherBayesNet.reversedArcs(this)
+	    + "\n");
+	text.append("Divergence: " + m_otherBayesNet.divergence(this) + "\n");
+      }
+    }
+
+    return text.toString();
+  } // toString
+
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+    return Drawable.BayesNet;
+  }
+
+  /**
+   * Returns a BayesNet graph in XMLBIF ver 0.3 format.
+   * @return String representing this BayesNet in XMLBIF ver  0.3
+   * @throws Exception in case BIF generation fails
+   */
+  public String graph() throws Exception {
+    return toXMLBIF03();
+  }
+
+  public String getBIFHeader() {
+    StringBuffer text = new StringBuffer();
+    text.append("<?xml version=\"1.0\"?>\n");
+    text.append("<!-- DTD for the XMLBIF 0.3 format -->\n");
+    text.append("<!DOCTYPE BIF [\n");
+    text.append("	<!ELEMENT BIF ( NETWORK )*>\n");
+    text.append("	      <!ATTLIST BIF VERSION CDATA #REQUIRED>\n");
+    text.append("	<!ELEMENT NETWORK ( NAME, ( PROPERTY | VARIABLE | DEFINITION )* )>\n");
+    text.append("	<!ELEMENT NAME (#PCDATA)>\n");
+    text.append("	<!ELEMENT VARIABLE ( NAME, ( OUTCOME |  PROPERTY )* ) >\n");
+    text.append("	      <!ATTLIST VARIABLE TYPE (nature|decision|utility) \"nature\">\n");
+    text.append("	<!ELEMENT OUTCOME (#PCDATA)>\n");
+    text.append("	<!ELEMENT DEFINITION ( FOR | GIVEN | TABLE | PROPERTY )* >\n");
+    text.append("	<!ELEMENT FOR (#PCDATA)>\n");
+    text.append("	<!ELEMENT GIVEN (#PCDATA)>\n");
+    text.append("	<!ELEMENT TABLE (#PCDATA)>\n");
+    text.append("	<!ELEMENT PROPERTY (#PCDATA)>\n");
+    text.append("]>\n");
+    return text.toString();
+  } // getBIFHeader
+
+  /**
+   * Returns a description of the classifier in XML BIF 0.3 format.
+   * See http://www-2.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/
+   * for details on XML BIF.
+   * @return an XML BIF 0.3 description of the classifier as a string.
+   */
+  public String toXMLBIF03() {
+    if (m_Instances == null) {
+      return("<!--No model built yet-->");
+    }
+
+    StringBuffer text = new StringBuffer();
+    text.append(getBIFHeader());
+    text.append("\n");
+    text.append("\n");
+    text.append("<BIF VERSION=\"0.3\">\n");
+    text.append("<NETWORK>\n");
+    text.append("<NAME>" + XMLNormalize(m_Instances.relationName()) + "</NAME>\n");
+    for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+      text.append("<VARIABLE TYPE=\"nature\">\n");
+      text.append("<NAME>" + XMLNormalize(m_Instances.attribute(iAttribute).name()) + "</NAME>\n");
+      for (int iValue = 0; iValue < m_Instances.attribute(iAttribute).numValues(); iValue++) {
+	text.append("<OUTCOME>" + XMLNormalize(m_Instances.attribute(iAttribute).value(iValue)) + "</OUTCOME>\n");
+      }
+      text.append("</VARIABLE>\n");
+    }
+
+    for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+      text.append("<DEFINITION>\n");
+      text.append("<FOR>" + XMLNormalize(m_Instances.attribute(iAttribute).name()) + "</FOR>\n");
+      for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+	text.append("<GIVEN>"
+	    + XMLNormalize(m_Instances.attribute(m_ParentSets[iAttribute].getParent(iParent)).name()) +
+	"</GIVEN>\n");
+      }
+      text.append("<TABLE>\n");
+      for (int iParent = 0; iParent < m_ParentSets[iAttribute].getCardinalityOfParents(); iParent++) {
+	for (int iValue = 0; iValue < m_Instances.attribute(iAttribute).numValues(); iValue++) {
+	  text.append(m_Distributions[iAttribute][iParent].getProbability(iValue));
+	  text.append(' ');
+	}
+	text.append('\n');
+      }
+      text.append("</TABLE>\n");
+      text.append("</DEFINITION>\n");
+    }
+    text.append("</NETWORK>\n");
+    text.append("</BIF>\n");
+    return text.toString();
+  } // toXMLBIF03
+
+
+  /** XMLNormalize converts the five standard XML entities in a string
+   * g.e. the string V&D's is returned as V&amp;D&apos;s
+   * @param sStr string to normalize
+   * @return normalized string
+   */
+  protected String XMLNormalize(String sStr) {
+    StringBuffer sStr2 = new StringBuffer();
+    for (int iStr = 0; iStr < sStr.length(); iStr++) {
+      char c = sStr.charAt(iStr);
+      switch (c) {
+	case '&': sStr2.append("&amp;"); break;
+	case '\'': sStr2.append("&apos;"); break;
+	case '\"': sStr2.append("&quot;"); break;
+	case '<': sStr2.append("&lt;"); break;
+	case '>': sStr2.append("&gt;"); break;
+	default:
+	  sStr2.append(c);
+      }
+    }
+    return sStr2.toString();
+  } // XMLNormalize
+
+
+  /**
+   * @return a string to describe the UseADTreeoption.
+   */
+  public String useADTreeTipText() {
+    return "When ADTree (the data structure for increasing speed on counts,"
+    + " not to be confused with the classifier under the same name) is used"
+    + " learning time goes down typically. However, because ADTrees are memory"
+    + " intensive, memory problems may occur. Switching this option off makes"
+    + " the structure learning algorithms slower, and run with less memory."
+    + " By default, ADTrees are used.";
+  }
+
+  /**
+   * @return a string to describe the SearchAlgorithm.
+   */
+  public String searchAlgorithmTipText() {
+    return "Select method used for searching network structures.";
+  }
+
+  /**
+   * This will return a string describing the BayesNetEstimator.
+   * @return The string.
+   */
+  public String estimatorTipText() {
+    return "Select Estimator algorithm for finding the conditional probability tables"
+    + " of the Bayes Network.";
+  }
+
+  /**
+   * @return a string to describe the BIFFile.
+   */
+  public String BIFFileTipText() {
+    return "Set the name of a file in BIF XML format. A Bayes network learned"
+    + " from data can be compared with the Bayes network represented by the BIF file."
+    + " Statistics calculated are o.a. the number of missing and extra arcs.";
+  }
+
+  /**
+   * This will return a string describing the classifier.
+   * @return The string.
+   */
+  public String globalInfo() {
+    return 
+    "Bayes Network learning using various search algorithms and "
+    + "quality measures.\n"
+    + "Base class for a Bayes Network classifier. Provides "
+    + "datastructures (network structure, conditional probability "
+    + "distributions, etc.) and facilities common to Bayes Network "
+    + "learning algorithms like K2 and B.\n\n"
+    + "For more information see:\n\n"
+    + "http://www.cs.waikato.ac.nz/~remco/weka.pdf";
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param argv the options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new BayesNet(), argv);
+  } // main
+
+  /** get name of the Bayes network
+   * @return name of the Bayes net
+   */
+  public String getName() {
+    return m_Instances.relationName();
+  }
+
+  /** get number of nodes in the Bayes network
+   * @return number of nodes
+   */
+  public int getNrOfNodes() {
+    return m_Instances.numAttributes();
+  }
+
+  /** get name of a node in the Bayes network
+   * @param iNode index of the node
+   * @return name of the specified node
+   */
+  public String getNodeName(int iNode) {
+    return m_Instances.attribute(iNode).name();
+  }
+
+  /** get number of values a node can take
+   * @param iNode index of the node
+   * @return cardinality of the specified node
+   */
+  public int getCardinality(int iNode) {
+    return m_Instances.attribute(iNode).numValues();
+  }
+
+  /** get name of a particular value of a node
+   * @param iNode index of the node
+   * @param iValue index of the value
+   * @return cardinality of the specified node
+   */
+  public String getNodeValue(int iNode, int iValue) {
+    return m_Instances.attribute(iNode).value(iValue);
+  }
+
+  /** get number of parents of a node in the network structure
+   * @param iNode index of the node
+   * @return number of parents of the specified node
+   */
+  public int getNrOfParents(int iNode) {
+    return m_ParentSets[iNode].getNrOfParents();
+  }
+
+  /** get node index of a parent of a node in the network structure
+   * @param iNode index of the node
+   * @param iParent index of the parents, e.g., 0 is the first parent, 1 the second parent, etc.
+   * @return node index of the iParent's parent of the specified node
+   */
+  public int getParent(int iNode, int iParent) {
+    return m_ParentSets[iNode].getParent(iParent);
+  }
+
+  /** Get full set of parent sets.
+   * @return parent sets;
+   */
+  public ParentSet[] getParentSets() { 
+    return m_ParentSets;
+  }
+
+  /** Get full set of estimators.
+   * @return estimators;
+   */
+  public Estimator[][] getDistributions() {
+    return m_Distributions;
+  }
+
+  /** get number of values the collection of parents of a node can take
+   * @param iNode index of the node
+   * @return cardinality of the parent set of the specified node
+   */
+  public int getParentCardinality(int iNode) {
+    return m_ParentSets[iNode].getCardinalityOfParents();
+  }
+
+  /** get particular probability of the conditional probability distribtion
+   * of a node given its parents.
+   * @param iNode index of the node
+   * @param iParent index of the parent set, 0 <= iParent <= getParentCardinality(iNode)
+   * @param iValue index of the value, 0 <= iValue <= getCardinality(iNode)
+   * @return probability
+   */
+  public double getProbability(int iNode, int iParent, int iValue) {
+    return m_Distributions[iNode][iParent].getProbability(iValue);
+  }
+
+  /** get the parent set of a node 
+   * @param iNode index of the node
+   * @return Parent set of the specified node.
+   */
+  public ParentSet getParentSet(int iNode) {
+    return m_ParentSets[iNode];
+  }
+
+  /** get ADTree strucrture containing efficient representation of counts.
+   * @return ADTree strucrture
+   */
+  public ADNode getADTree() { return m_ADTree;}
+
+  // implementation of AdditionalMeasureProducer interface
+  /**
+   * Returns an enumeration of the measure names. Additional measures
+   * must follow the naming convention of starting with "measure", eg.
+   * double measureBlah()
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(4);
+    newVector.addElement("measureExtraArcs");
+    newVector.addElement("measureMissingArcs");
+    newVector.addElement("measureReversedArcs");
+    newVector.addElement("measureDivergence");
+    newVector.addElement("measureBayesScore");
+    newVector.addElement("measureBDeuScore");
+    newVector.addElement("measureMDLScore");
+    newVector.addElement("measureAICScore");
+    newVector.addElement("measureEntropyScore");
+    return newVector.elements();
+  } // enumerateMeasures
+
+  public double measureExtraArcs() {
+    if (m_otherBayesNet != null) {
+      return m_otherBayesNet.extraArcs(this); 
+    }
+    return 0;
+  } // measureExtraArcs
+
+  public double measureMissingArcs() {
+    if (m_otherBayesNet != null) {
+      return m_otherBayesNet.missingArcs(this); 
+    }
+    return 0;
+  } // measureMissingArcs
+
+  public double measureReversedArcs() {
+    if (m_otherBayesNet != null) {
+      return m_otherBayesNet.reversedArcs(this); 
+    }
+    return 0;
+  } // measureReversedArcs
+
+  public double measureDivergence() {
+    if (m_otherBayesNet != null) {
+      return m_otherBayesNet.divergence(this); 
+    }
+    return 0;
+  } // measureDivergence
+
+  public double measureBayesScore() {
+    LocalScoreSearchAlgorithm s = new LocalScoreSearchAlgorithm(this, m_Instances);
+    return s.logScore(Scoreable.BAYES);
+  } // measureBayesScore
+
+  public double measureBDeuScore() {
+    LocalScoreSearchAlgorithm s = new LocalScoreSearchAlgorithm(this, m_Instances);
+    return s.logScore(Scoreable.BDeu);
+  } // measureBDeuScore
+
+  public double measureMDLScore() {
+    LocalScoreSearchAlgorithm s = new LocalScoreSearchAlgorithm(this, m_Instances);
+    return s.logScore(Scoreable.MDL);
+  } // measureMDLScore
+
+  public double measureAICScore() {
+    LocalScoreSearchAlgorithm s = new LocalScoreSearchAlgorithm(this, m_Instances);
+    return s.logScore(Scoreable.AIC);
+  } // measureAICScore
+
+  public double measureEntropyScore() {
+    LocalScoreSearchAlgorithm s = new LocalScoreSearchAlgorithm(this, m_Instances);
+    return s.logScore(Scoreable.ENTROPY);
+  } // measureEntropyScore
+
+  /**
+   * Returns the value of the named measure
+   * @param measureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String measureName) {
+    if (measureName.equals("measureExtraArcs")) {
+      return measureExtraArcs();
+    }
+    if (measureName.equals("measureMissingArcs")) {
+      return measureMissingArcs();
+    }
+    if (measureName.equals("measureReversedArcs")) {
+      return measureReversedArcs();
+    }
+    if (measureName.equals("measureDivergence")) {
+      return measureDivergence();
+    }
+    if (measureName.equals("measureBayesScore")) {
+      return measureBayesScore();
+    }
+    if (measureName.equals("measureBDeuScore")) {
+      return measureBDeuScore();
+    }
+    if (measureName.equals("measureMDLScore")) {
+      return measureMDLScore();
+    }
+    if (measureName.equals("measureAICScore")) {
+      return measureAICScore();
+    }
+    if (measureName.equals("measureEntropyScore")) {
+      return measureEntropyScore();
+    }
+    return 0;
+  } // getMeasure
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+} // class BayesNet
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesianLogisticRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesianLogisticRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/BayesianLogisticRegression.java	(revision 29)
@@ -0,0 +1,1260 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BayesianLogisticRegression.java
+ *    Copyright (C) 2008 Illinois Institute of Technology
+ *
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.bayes.blr.GaussianPriorImpl;
+import weka.classifiers.bayes.blr.LaplacePriorImpl;
+import weka.classifiers.bayes.blr.Prior;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SerializedObject;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements Bayesian Logistic Regression for both Gaussian and Laplace Priors.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Alexander Genkin, David D. Lewis, David Madigan (2004). Large-scale bayesian logistic regression for text categorization. URL http://www.stat.rutgers.edu/~madigan/PAPERS/shortFat-v3a.pdf.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Genkin2004,
+ *    author = {Alexander Genkin and David D. Lewis and David Madigan},
+ *    institution = {DIMACS},
+ *    title = {Large-scale bayesian logistic regression for text categorization},
+ *    year = {2004},
+ *    URL = {http://www.stat.rutgers.edu/\~madigan/PAPERS/shortFat-v3a.pdf}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ *
+ *  @author Navendu Garg (gargnav at iit dot edu)
+ *  @version $Revision: 5928 $
+ */
+public class BayesianLogisticRegression extends AbstractClassifier
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  static final long serialVersionUID = -8013478897911757631L;
+
+  /** Log-likelihood values to be used to choose the best hyperparameter. */
+  public static double[] LogLikelihood;
+
+  /** Set of values to be used as hyperparameter values during Cross-Validation. */
+  public static double[] InputHyperparameterValues;
+
+  /** DEBUG Mode*/
+  boolean debug = false;
+
+  /** Choose whether to normalize data or not */
+  public boolean NormalizeData = false;
+
+  /** Tolerance criteria for the stopping criterion. */
+  public double Tolerance = 0.0005;
+
+  /** Threshold for binary classification of probabilisitic estimate*/
+  public double Threshold = 0.5;
+
+  /** Distributions available */
+  public static final int GAUSSIAN = 1;
+  public static final int LAPLACIAN = 2;
+  
+  public static final Tag[] TAGS_PRIOR = {
+    new Tag(GAUSSIAN, "Gaussian"),
+    new Tag(LAPLACIAN, "Laplacian")
+  };
+
+  /** Distribution Prior class */
+  public int PriorClass = GAUSSIAN;
+
+  /** NumFolds for CV based Hyperparameters selection*/
+  public int NumFolds = 2;
+
+  /** Methods for selecting the hyperparameter value */
+  public static final int NORM_BASED = 1;
+  public static final int CV_BASED = 2;
+  public static final int SPECIFIC_VALUE = 3;
+
+  public static final Tag[] TAGS_HYPER_METHOD = {
+    new Tag(NORM_BASED, "Norm-based"),
+    new Tag(CV_BASED, "CV-based"),
+    new Tag(SPECIFIC_VALUE, "Specific value")
+  };
+
+  /** Hyperparameter selection method */
+  public int HyperparameterSelection = NORM_BASED;
+
+  /** The class index from the training data */
+  public int ClassIndex = -1;
+
+  /** Best hyperparameter for test phase */
+  public double HyperparameterValue = 0.27;
+
+  /** CV Hyperparameter Range */
+  public String HyperparameterRange = "R:0.01-316,3.16";
+
+  /** Maximum number of iterations */
+  public int maxIterations = 100;
+
+  /**Iteration counter */
+  public int iterationCounter = 0;
+
+  /** Array for storing coefficients of Bayesian regression model. */
+  public double[] BetaVector;
+
+  /** Array to store Regression Coefficient updates. */
+  public double[] DeltaBeta;
+
+  /**        Trust Region Radius Update*/
+  public double[] DeltaUpdate;
+
+  /** Trust Region Radius*/
+  public double[] Delta;
+
+  /**  Array to store Hyperparameter values for each feature. */
+  public double[] Hyperparameters;
+
+  /** R(i)= BetaVector X x(i) X y(i).
+   * This an intermediate value with respect to vector BETA, input values and corresponding class labels*/
+  public double[] R;
+
+  /** This vector is used to store the increments on the R(i). It is also used to determining the stopping criterion.*/
+  public double[] DeltaR;
+
+  /**
+   * This variable is used to keep track of change in
+   * the value of delta summation of r(i).
+   */
+  public double Change;
+
+  /**
+   * Bayesian Logistic Regression returns the probability of a given instance will belong to a certain
+   * class (p(y=+1|Beta,X). To obtain a binary value the Threshold value is used.
+   * <pre>
+   * p(y=+1|Beta,X)>Threshold ? 1 : -1
+   * </pre>
+   */
+
+  /** Filter interface used to point to weka.filters.unsupervised.attribute.Normalize object
+   *
+   */
+  public Filter m_Filter;
+
+  /** Dataset provided to do Training/Test set.*/
+  protected Instances m_Instances;
+
+  /**        Prior class object interface*/
+  protected Prior m_PriorUpdate;
+
+  public String globalInfo() {
+    return "Implements Bayesian Logistic Regression "
+      + "for both Gaussian and Laplace Priors.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation();
+  }
+
+  /**
+   * <pre>
+   * (1)Initialize m_Beta[j] to 0.
+   * (2)Initialize m_DeltaUpdate[j].
+   * </pre>
+   *
+   * */
+  public void initialize() throws Exception {
+    int numOfAttributes;
+    int numOfInstances;
+    int i;
+    int j;
+
+    Change = 0.0;
+
+    //Manipulate Data
+    if (NormalizeData) {
+      m_Filter = new Normalize();
+      m_Filter.setInputFormat(m_Instances);
+      m_Instances = Filter.useFilter(m_Instances, m_Filter);
+    }
+
+    //Set the intecept coefficient.
+    Attribute att = new Attribute("(intercept)");
+    Instance instance;
+
+    m_Instances.insertAttributeAt(att, 0);
+
+    for (i = 0; i < m_Instances.numInstances(); i++) {
+      instance = m_Instances.instance(i);
+      instance.setValue(0, 1.0);
+    }
+
+    //Get the number of attributes
+    numOfAttributes = m_Instances.numAttributes();
+    numOfInstances = m_Instances.numInstances();
+    ClassIndex = m_Instances.classIndex();
+    iterationCounter = 0;
+
+    //Initialize Arrays.
+    switch (HyperparameterSelection) {
+    case 1:
+      HyperparameterValue = normBasedHyperParameter();
+
+      if (debug) {
+        System.out.println("Norm-based Hyperparameter: " + HyperparameterValue);
+      }
+
+      break;
+
+    case 2:
+      HyperparameterValue = CVBasedHyperparameter();
+
+      if (debug) {
+        System.out.println("CV-based Hyperparameter: " + HyperparameterValue);
+      }
+
+      break;
+    }
+
+    BetaVector = new double[numOfAttributes];
+    Delta = new double[numOfAttributes];
+    DeltaBeta = new double[numOfAttributes];
+    Hyperparameters = new double[numOfAttributes];
+    DeltaUpdate = new double[numOfAttributes];
+
+    for (j = 0; j < numOfAttributes; j++) {
+      BetaVector[j] = 0.0;
+      Delta[j] = 1.0;
+      DeltaBeta[j] = 0.0;
+      DeltaUpdate[j] = 0.0;
+
+      //TODO: Change the way it takes values.
+      Hyperparameters[j] = HyperparameterValue;
+    }
+
+    DeltaR = new double[numOfInstances];
+    R = new double[numOfInstances];
+
+    for (i = 0; i < numOfInstances; i++) {
+      DeltaR[i] = 0.0;
+      R[i] = 0.0;
+    }
+
+    //Set the Prior interface to the appropriate prior implementation.
+    if (PriorClass == GAUSSIAN) {
+      m_PriorUpdate = new GaussianPriorImpl();
+    } else {
+      m_PriorUpdate = new LaplacePriorImpl();
+    }
+  }
+
+  /**
+   * This method tests what kind of data this classifier can handle.
+   * return Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+
+    result.enable(Capability.BINARY_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * <ul>
+   *         <li>(1) Set the data to the class attribute m_Instances.</li>
+   *  <li>(2)Call the method initialize() to initialize the values.</li>
+   * </ul>
+   *        @param data training data
+   *        @exception Exception if classifier can't be built successfully.
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    Instance instance;
+    int i;
+    int j;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    //(1) Set the data to the class attribute m_Instances.
+    m_Instances = new Instances(data);
+
+    //(2)Call the method initialize() to initialize the values.
+    initialize();
+
+    do {
+      //Compute the prior Trust Region Radius Update;
+      for (j = 0; j < m_Instances.numAttributes(); j++) {
+        if (j != ClassIndex) {
+          DeltaUpdate[j] = m_PriorUpdate.update(j, m_Instances, BetaVector[j],
+              Hyperparameters[j], R, Delta[j]);
+          //limit step to trust region.
+          DeltaBeta[j] = Math.min(Math.max(DeltaUpdate[j], 0 - Delta[j]),
+              Delta[j]);
+
+          //Update the 
+          for (i = 0; i < m_Instances.numInstances(); i++) {
+            instance = m_Instances.instance(i);
+
+            if (instance.value(j) != 0) {
+              DeltaR[i] = DeltaBeta[j] * instance.value(j) * classSgn(instance.classValue());
+              R[i] += DeltaR[i];
+            }
+          }
+
+          //Updated Beta values.
+          BetaVector[j] += DeltaBeta[j];
+
+          //Update size of trust region.
+          Delta[j] = Math.max(2 * Math.abs(DeltaBeta[j]), Delta[j] / 2.0);
+        }
+      }
+    } while (!stoppingCriterion());
+
+    m_PriorUpdate.computelogLikelihood(BetaVector, m_Instances);
+    m_PriorUpdate.computePenalty(BetaVector, Hyperparameters);
+  }
+
+  /**
+   * This class is used to mask the internal class labels.
+   *
+   * @param value internal class label
+   * @return
+   * <pre>
+   * <ul><li>
+   *  -1 for internal class label 0
+   *  </li>
+   *  <li>
+   *  +1 for internal class label 1
+   *  </li>
+   *  </ul>
+   *  </pre>
+   */
+  public static double classSgn(double value) {
+    if (value == 0.0) {
+      return -1.0;
+    } else {
+      return 1.0;
+    }
+  }
+
+  /**
+    * Returns an instance of a TechnicalInformation object, containing
+    * detailed information about the technical background of this class,
+    * e.g., paper reference or book this class is based on.
+    *
+    * @return the technical information about this class
+    */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result = null;
+
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Alexander Genkin and David D. Lewis and David Madigan");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.TITLE, "Large-scale bayesian logistic regression for text categorization");
+    result.setValue(Field.INSTITUTION, "DIMACS");
+    result.setValue(Field.URL, "http://www.stat.rutgers.edu/~madigan/PAPERS/shortFat-v3a.pdf");
+    return result;
+  }
+
+  /**
+   * This is a convient function that defines and upper bound
+   * (Delta>0) for values of r(i) reachable by updates in the
+   * trust region.
+   *
+   * r BetaVector X x(i)y(i).
+   * delta A parameter where sigma > 0
+   * @return double function value
+   */
+  public static double bigF(double r, double sigma) {
+    double funcValue = 0.25;
+    double absR = Math.abs(r);
+
+    if (absR > sigma) {
+      funcValue = 1.0 / (2.0 + Math.exp(absR - sigma) + Math.exp(sigma - absR));
+    }
+
+    return funcValue;
+  }
+
+  /**
+   * This method implements the stopping criterion
+   * function.
+   *
+   * @return boolean whether to stop or not.
+   */
+  public boolean stoppingCriterion() {
+    int i;
+    double sum_deltaR = 0.0;
+    double sum_R = 1.0;
+    boolean shouldStop;
+    double value = 0.0;
+    double delta;
+
+    //Summation of changes in R(i) vector.
+    for (i = 0; i < m_Instances.numInstances(); i++) {
+      sum_deltaR += Math.abs(DeltaR[i]); //Numerator (deltaR(i))
+      sum_R += Math.abs(R[i]); // Denominator (1+sum(R(i))
+    }
+
+    delta = Math.abs(sum_deltaR - Change);
+    Change = delta / sum_R;
+
+    if (debug) {
+      System.out.println(Change + " <= " + Tolerance);
+    }
+
+    shouldStop = ((Change <= Tolerance) || (iterationCounter >= maxIterations))
+      ? true : false;
+    iterationCounter++;
+    Change = sum_deltaR;
+
+    return shouldStop;
+  }
+
+  /**
+   *  This method computes the values for the logistic link function.
+   *  <pre>f(r)=exp(r)/(1+exp(r))</pre>
+   *
+   * @return output value
+   */
+  public static double logisticLinkFunction(double r) {
+    return Math.exp(r) / (1.0 + Math.exp(r));
+  }
+
+  /**
+   * Sign for a given value.
+   * @param r
+   * @return double +1 if r>0, -1 if r<0
+   */
+  public static double sgn(double r) {
+    double sgn = 0.0;
+
+    if (r > 0) {
+      sgn = 1.0;
+    } else if (r < 0) {
+      sgn = -1.0;
+    }
+
+    return sgn;
+  }
+
+  /**
+   *        This function computes the norm-based hyperparameters
+   *        and stores them in the m_Hyperparameters.
+   */
+  public double normBasedHyperParameter() {
+    //TODO: Implement this method.
+    Instance instance;
+
+    double mean = 0.0;
+
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      instance = m_Instances.instance(i);
+
+      double sqr_sum = 0.0;
+
+      for (int j = 0; j < m_Instances.numAttributes(); j++) {
+        if (j != ClassIndex) {
+          sqr_sum += (instance.value(j) * instance.value(j));
+        }
+      }
+
+      //sqr_sum=Math.sqrt(sqr_sum);
+      mean += sqr_sum;
+    }
+
+    mean = mean / (double) m_Instances.numInstances();
+
+    return ((double) m_Instances.numAttributes()) / mean;
+  }
+
+  /**
+   * Classifies the given instance using the Bayesian Logistic Regression function.
+   *
+   * @param instance the test instance
+   * @return the classification
+   * @throws Exception if classification can't be done successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    //TODO: Implement
+    double sum_R = 0.0;
+    double classification = 0.0;
+
+    sum_R = BetaVector[0];
+
+    for (int j = 0; j < instance.numAttributes(); j++) {
+      if (j != (ClassIndex - 1)) {
+        sum_R += (BetaVector[j + 1] * instance.value(j));
+      }
+    }
+
+    sum_R = logisticLinkFunction(sum_R);
+
+    if (sum_R > Threshold) {
+      classification = 1.0;
+    } else {
+      classification = 0.0;
+    }
+
+    return classification;
+  }
+
+  /**
+   * Outputs the linear regression model as a string.
+   *
+   * @return the model as string
+   */
+  public String toString() {
+
+    if (m_Instances == null) {
+      return "Bayesian logistic regression: No model built yet.";
+    }
+
+    StringBuffer buf = new StringBuffer();
+    String text = "";
+
+    switch (HyperparameterSelection) {
+    case 1:
+      text = "Norm-Based Hyperparameter Selection: ";
+
+      break;
+
+    case 2:
+      text = "Cross-Validation Based Hyperparameter Selection: ";
+
+      break;
+
+    case 3:
+      text = "Specified Hyperparameter: ";
+
+      break;
+    }
+
+    buf.append(text).append(HyperparameterValue).append("\n\n");
+
+    buf.append("Regression Coefficients\n");
+    buf.append("=========================\n\n");
+
+    for (int j = 0; j < m_Instances.numAttributes(); j++) {
+      if (j != ClassIndex) {
+        if (BetaVector[j] != 0.0) {
+          buf.append(m_Instances.attribute(j).name()).append(" : ")
+             .append(BetaVector[j]).append("\n");
+        }
+      }
+    }
+
+    buf.append("===========================\n\n");
+    buf.append("Likelihood: " + m_PriorUpdate.getLoglikelihood() + "\n\n");
+    buf.append("Penalty: " + m_PriorUpdate.getPenalty() + "\n\n");
+    buf.append("Regularized Log Posterior: " + m_PriorUpdate.getLogPosterior() +
+      "\n");
+    buf.append("===========================\n\n");
+
+    return buf.toString();
+  }
+
+  /**
+   * Method computes the best hyperparameter value by doing cross
+   * -validation on the training data and compute the likelihood.
+   * The method can parse a range of values or a list of values.
+   * @return Best hyperparameter value with the max likelihood value on the training data.
+   * @throws Exception
+   */
+  public double CVBasedHyperparameter() throws Exception {
+    //TODO: Method incomplete.
+    double start;
+
+    //TODO: Method incomplete.
+    double end;
+
+    //TODO: Method incomplete.
+    double multiplier;
+    int size = 0;
+    double[] list = null;
+    double MaxHypeValue = 0.0;
+    double MaxLikelihood = 0.0;
+    StringTokenizer tokenizer = new StringTokenizer(HyperparameterRange);
+    String rangeType = tokenizer.nextToken(":");
+
+    if (rangeType.equals("R")) {
+      String temp = tokenizer.nextToken();
+      tokenizer = new StringTokenizer(temp);
+      start = Double.parseDouble(tokenizer.nextToken("-"));
+      tokenizer = new StringTokenizer(tokenizer.nextToken());
+      end = Double.parseDouble(tokenizer.nextToken(","));
+      multiplier = Double.parseDouble(tokenizer.nextToken());
+
+      int steps = (int) (((Math.log10(end) - Math.log10(start)) / Math.log10(multiplier)) +
+        1);
+      list = new double[steps];
+
+      int count = 0;
+
+      for (double i = start; i <= end; i *= multiplier) {
+        list[count++] = i;
+      }
+    } else if (rangeType.equals("L")) {
+      Vector vec = new Vector();
+
+      while (tokenizer.hasMoreTokens()) {
+        vec.add(tokenizer.nextToken(","));
+      }
+
+      list = new double[vec.size()];
+
+      for (int i = 0; i < vec.size(); i++) {
+        list[i] = Double.parseDouble((String) vec.get(i));
+      }
+    } else {
+      //throw exception.  
+    }
+
+    // Perform two-fold cross-validation to collect
+    // unbiased predictions
+    if (list != null) {
+      int numFolds = (int) NumFolds;
+      Random random = new Random();
+      m_Instances.randomize(random);
+      m_Instances.stratify(numFolds);
+
+      for (int k = 0; k < list.length; k++) {
+        for (int i = 0; i < numFolds; i++) {
+          Instances train = m_Instances.trainCV(numFolds, i, random);
+          SerializedObject so = new SerializedObject(this);
+          BayesianLogisticRegression blr = (BayesianLogisticRegression) so.getObject();
+          //          blr.setHyperparameterSelection(3);
+          blr.setHyperparameterSelection(new SelectedTag(SPECIFIC_VALUE, 
+                                                         TAGS_HYPER_METHOD));
+          blr.setHyperparameterValue(list[k]);
+          //          blr.setPriorClass(PriorClass);
+          blr.setPriorClass(new SelectedTag(PriorClass,
+                                            TAGS_PRIOR));
+          blr.setThreshold(Threshold);
+          blr.setTolerance(Tolerance);
+          blr.buildClassifier(train);
+
+          Instances test = m_Instances.testCV(numFolds, i);
+          double val = blr.getLoglikeliHood(blr.BetaVector, test);
+
+          if (debug) {
+            System.out.println("Fold " + i + "Hyperparameter: " + list[k]);
+            System.out.println("===================================");
+            System.out.println(" Likelihood: " + val);
+          }
+
+          if ((k == 0) | (val > MaxLikelihood)) {
+            MaxLikelihood = val;
+            MaxHypeValue = list[k];
+          }
+        }
+      }
+    } else {
+      return HyperparameterValue;
+    }
+
+    return MaxHypeValue;
+  }
+
+  /**
+   *
+   * @return likelihood for a given set of betas and instances
+   */
+  public double getLoglikeliHood(double[] betas, Instances instances) {
+    m_PriorUpdate.computelogLikelihood(betas, instances);
+
+    return m_PriorUpdate.getLoglikelihood();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option("\tShow Debugging Output\n", "D", 0, "-D"));
+    newVector.addElement(new Option("\tDistribution of the Prior "
+                                    +"(1=Gaussian, 2=Laplacian)"
+                                    +"\n\t(default: 1=Gaussian)"
+                                    , "P", 1,
+                                    "-P <integer>"));
+    newVector.addElement(new Option("\tHyperparameter Selection Method "
+                                    +"(1=Norm-based, 2=CV-based, 3=specific value)\n"
+                                    +"\t(default: 1=Norm-based)", 
+                                    "H",
+                                    1, 
+                                    "-H <integer>"));
+    newVector.addElement(new Option("\tSpecified Hyperparameter Value (use in conjunction with -H 3)\n"
+                                    +"\t(default: 0.27)", 
+                                    "V", 
+                                    1,
+                                    "-V <double>"));
+    newVector.addElement(new Option(
+        "\tHyperparameter Range (use in conjunction with -H 2)\n"
+        +"\t(format: R:start-end,multiplier OR L:val(1), val(2), ..., val(n))\n"
+        +"\t(default: R:0.01-316,3.16)", 
+        "R", 
+        1,
+        "-R <string>"));
+    newVector.addElement(new Option("\tTolerance Value\n\t(default: 0.0005)", 
+                                    "Tl", 
+                                    1,
+                                    "-Tl <double>"));
+    newVector.addElement(new Option("\tThreshold Value\n\t(default: 0.5)", 
+                                    "S", 
+                                    1, 
+                                    "-S <double>"));
+    newVector.addElement(new Option("\tNumber Of Folds (use in conjuction with -H 2)\n"
+                                    +"\t(default: 2)", 
+                                    "F", 
+                                    1,
+                                    "-F <integer>"));
+    newVector.addElement(new Option("\tMax Number of Iterations\n\t(default: 100)", 
+                                    "I", 
+                                    1,
+                                    "-I <integer>"));
+    newVector.addElement(new Option("\tNormalize the data",
+                                    "N", 0, "-N"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Show Debugging Output
+   * </pre>
+   * 
+   * <pre> -P &lt;integer&gt;
+   *  Distribution of the Prior (1=Gaussian, 2=Laplacian)
+   *  (default: 1=Gaussian)</pre>
+   * 
+   * <pre> -H &lt;integer&gt;
+   *  Hyperparameter Selection Method (1=Norm-based, 2=CV-based, 3=specific value)
+   *  (default: 1=Norm-based)</pre>
+   * 
+   * <pre> -V &lt;double&gt;
+   *  Specified Hyperparameter Value (use in conjunction with -H 3)
+   *  (default: 0.27)</pre>
+   * 
+   * <pre> -R &lt;string&gt;
+   *  Hyperparameter Range (use in conjunction with -H 2)
+   *  (format: R:start-end,multiplier OR L:val(1), val(2), ..., val(n))
+   *  (default: R:0.01-316,3.16)</pre>
+   * 
+   * <pre> -Tl &lt;double&gt;
+   *  Tolerance Value
+   *  (default: 0.0005)</pre>
+   * 
+   * <pre> -S &lt;double&gt;
+   *  Threshold Value
+   *  (default: 0.5)</pre>
+   * 
+   * <pre> -F &lt;integer&gt;
+   *  Number Of Folds (use in conjuction with -H 2)
+   *  (default: 2)</pre>
+   * 
+   * <pre> -I &lt;integer&gt;
+   *  Max Number of Iterations
+   *  (default: 100)</pre>
+   * 
+   * <pre> -N
+   *  Normalize the data</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    //Debug Option
+    debug = Utils.getFlag('D', options);
+
+    // Set Tolerance.
+    String Tol = Utils.getOption("Tl", options);
+
+    if (Tol.length() != 0) {
+      Tolerance = Double.parseDouble(Tol);
+    }
+
+    //Set Threshold
+    String Thres = Utils.getOption('S', options);
+
+    if (Thres.length() != 0) {
+      Threshold = Double.parseDouble(Thres);
+    }
+
+    //Set Hyperparameter Type 
+    String Hype = Utils.getOption('H', options);
+
+    if (Hype.length() != 0) {
+      HyperparameterSelection = Integer.parseInt(Hype);
+    }
+
+    //Set Hyperparameter Value 
+    String HyperValue = Utils.getOption('V', options);
+
+    if (HyperValue.length() != 0) {
+      HyperparameterValue = Double.parseDouble(HyperValue);
+    }
+
+    // Set hyper parameter range or list.
+    String HyperparameterRange = Utils.getOption("R", options);
+
+    //Set Prior class.
+    String strPrior = Utils.getOption('P', options);
+
+    if (strPrior.length() != 0) {
+      PriorClass = Integer.parseInt(strPrior);
+    }
+
+    String folds = Utils.getOption('F', options);
+
+    if (folds.length() != 0) {
+      NumFolds = Integer.parseInt(folds);
+    }
+
+    String iterations = Utils.getOption('I', options);
+
+    if (iterations.length() != 0) {
+      maxIterations = Integer.parseInt(iterations);
+    }
+
+    NormalizeData = Utils.getFlag('N', options);
+
+    //TODO: Implement this method for other options.
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   *
+   */
+  public String[] getOptions() {
+    Vector result = new Vector();
+
+    //Add Debug Mode to options.
+    result.add("-D");
+
+    //Add Tolerance value to options
+    result.add("-Tl");
+    result.add("" + Tolerance);
+
+    //Add Threshold value to options
+    result.add("-S");
+    result.add("" + Threshold);
+
+    //Add Hyperparameter value to options
+    result.add("-H");
+    result.add("" + HyperparameterSelection);
+
+    result.add("-V");
+    result.add("" + HyperparameterValue);
+
+    result.add("-R");
+    result.add("" + HyperparameterRange);
+
+    //Add Prior Class to options
+    result.add("-P");
+    result.add("" + PriorClass);
+
+    result.add("-F");
+    result.add("" + NumFolds);
+
+    result.add("-I");
+    result.add("" + maxIterations);
+
+    result.add("-N");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new BayesianLogisticRegression(), argv);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Turns on debugging mode.";
+  }
+
+  /**
+   *
+   */
+  public void setDebug(boolean debugMode) {
+    debug = debugMode;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String hyperparameterSelectionTipText() {
+    return "Select the type of Hyperparameter to be used.";
+  }
+
+  /**
+   * Get the method used to select the hyperparameter
+   *
+   * @return the method used to select the hyperparameter
+   */
+  public SelectedTag getHyperparameterSelection() {
+    return new SelectedTag(HyperparameterSelection, 
+                           TAGS_HYPER_METHOD);
+  }
+
+  /**
+   * Set the method used to select the hyperparameter
+   *
+   * @param newMethod the method used to set the hyperparameter
+   */
+  public void setHyperparameterSelection(SelectedTag newMethod) {
+    if (newMethod.getTags() == TAGS_HYPER_METHOD) {
+      int c = newMethod.getSelectedTag().getID();
+      if (c >= 1 && c <= 3) {
+        HyperparameterSelection = c;
+      } else {
+        throw new IllegalArgumentException("Wrong selection type, -H value should be: "
+                                           + "1 for norm-based, 2 for CV-based and "
+                                         + "3 for specific value");
+      }
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String priorClassTipText() {
+    return "The type of prior to be used.";
+  }
+
+  /**
+   * Set the type of prior to use.
+   *
+   * @param newMethod the type of prior to use.
+   */
+  public void setPriorClass(SelectedTag newMethod) {
+    if (newMethod.getTags() == TAGS_PRIOR) {
+      int c = newMethod.getSelectedTag().getID();
+      if (c == GAUSSIAN || c == LAPLACIAN) {
+        PriorClass = c;
+      } else {
+        throw new IllegalArgumentException("Wrong selection type, -P value should be: "
+                                           + "1 for Gaussian or 2 for Laplacian");
+      }
+    }
+  }
+
+  /**
+   * Get the type of prior to use.
+   *
+   * @return the type of prior to use
+   */
+  public SelectedTag getPriorClass() {
+    return new SelectedTag(PriorClass,
+                           TAGS_PRIOR);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Set the threshold for classifiction. The logistic function doesn't "
+      + "return a class label but an estimate of p(y=+1|B,x(i)). "
+      + "These estimates need to be converted to binary class label predictions. "
+      + "values above the threshold are assigned class +1.";
+  }
+
+  /**
+   * Return the threshold being used.
+   *
+   * @return the threshold
+   */
+  public double getThreshold() {
+    return Threshold;
+  }
+
+  /**
+   * Set the threshold to use.
+   *
+   * @param threshold the threshold to use
+   */
+  public void setThreshold(double threshold) {
+    Threshold = threshold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String toleranceTipText() {
+    return "This value decides the stopping criterion.";
+  }
+
+  /**
+   * Get the tolerance value
+   *
+   * @return the tolerance value
+   */
+  public double getTolerance() {
+    return Tolerance;
+  }
+
+  /**
+   * Set the tolerance value
+   *
+   * @param tolerance the tolerance value to use
+   */
+  public void setTolerance(double tolerance) {
+    Tolerance = tolerance;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String hyperparameterValueTipText() {
+    return "Specific hyperparameter value. Used when the hyperparameter "
+      + "selection method is set to specific value";
+  }
+
+  /**
+   * Get the hyperparameter value. Used when the hyperparameter
+   * selection method is set to specific value
+   *
+   * @return the hyperparameter value
+   */
+  public double getHyperparameterValue() {
+    return HyperparameterValue;
+  }
+
+  /**
+   * Set the hyperparameter value. Used when the hyperparameter
+   * selection method is set to specific value
+   *
+   * @param hyperparameterValue the value of the hyperparameter
+   */
+  public void setHyperparameterValue(double hyperparameterValue) {
+    HyperparameterValue = hyperparameterValue;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds to use for CV-based hyperparameter selection.";
+  }
+
+  /**
+   * Return the number of folds for CV-based hyperparameter selection
+   *
+   * @return the number of CV folds
+   */
+  public int getNumFolds() {
+    return NumFolds;
+  }
+
+  /**
+   * Set the number of folds to use for CV-based hyperparameter
+   * selection
+   *
+   * @param numFolds number of folds to select
+   */
+  public void setNumFolds(int numFolds) {
+    NumFolds = numFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+    return "The maximum number of iterations to perform.";
+  }
+
+  /**
+   * Get the maximum number of iterations to perform
+   *
+   * @return the maximum number of iterations
+   */
+  public int getMaxIterations() {
+    return maxIterations;
+  }
+
+  /**
+   * Set the maximum number of iterations to perform
+   *
+   * @param maxIterations maximum number of iterations
+   */
+  public void setMaxIterations(int maxIterations) {
+    this.maxIterations = maxIterations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String normalizeDataTipText() {
+    return "Normalize the data.";
+  }
+
+  /**
+   * Returns true if the data is to be normalized first
+   *
+   * @return true if the data is to be normalized
+   */
+  public boolean isNormalizeData() {
+    return NormalizeData;
+  }
+
+  /**
+   * Set whether to normalize the data or not
+   *
+   * @param normalizeData true if data is to be normalized
+   */
+  public void setNormalizeData(boolean normalizeData) {
+    NormalizeData = normalizeData;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String hyperparameterRangeTipText() {
+    return "Hyperparameter value range. In case of CV-based Hyperparameters, "
+      + "you can specify the range in two ways: \n"
+      + "Comma-Separated: L: 3,5,6 (This will be a list of possible values.)\n"
+      + "Range: R:0.01-316,3.16 (This will take values from 0.01-316 (inclusive) "
+      + "in multiplications of 3.16";
+  }
+
+  /**
+   * Get the range of hyperparameter values to consider
+   * during CV-based selection.
+   *
+   * @return the range of hyperparameters as a Stringe
+   */
+  public String getHyperparameterRange() {
+    return HyperparameterRange;
+  }
+
+  /**
+   * Set the range of hyperparameter values to consider
+   * during CV-based selection
+   *
+   * @param hyperparameterRange the range of hyperparameter values
+   */
+  public void setHyperparameterRange(String hyperparameterRange) {
+    HyperparameterRange = hyperparameterRange;
+  }
+
+  /**
+   * Returns true if debug is turned on.
+   *
+   * @return true if debug is turned on
+   */
+  public boolean isDebug() {
+    return debug;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/ComplementNaiveBayes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/ComplementNaiveBayes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/ComplementNaiveBayes.java	(revision 29)
@@ -0,0 +1,498 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ComplementNaiveBayes.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a Complement class Naive Bayes classifier.<br/>
+ * <br/>
+ * For more information see, <br/>
+ * <br/>
+ * Jason D. Rennie, Lawrence Shih, Jaime Teevan, David R. Karger: Tackling the Poor Assumptions of Naive Bayes Text Classifiers. In: ICML, 616-623, 2003.<br/>
+ * <br/>
+ * P.S.: TF, IDF and length normalization transforms, as described in the paper, can be performed through weka.filters.unsupervised.StringToWordVector.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Rennie2003,
+ *    author = {Jason D. Rennie and Lawrence Shih and Jaime Teevan and David R. Karger},
+ *    booktitle = {ICML},
+ *    pages = {616-623},
+ *    publisher = {AAAI Press},
+ *    title = {Tackling the Poor Assumptions of Naive Bayes Text Classifiers},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Normalize the word weights for each class
+ * </pre>
+ * 
+ * <pre> -S
+ *  Smoothing value to avoid zero WordGivenClass probabilities (default=1.0).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class ComplementNaiveBayes extends AbstractClassifier
+    implements OptionHandler, WeightedInstancesHandler, TechnicalInformationHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 7246302925903086397L;
+  
+    /**
+      Weight of words for each class. The weight is actually the
+      log of the probability of a word (w) given a class (c) 
+      (i.e. log(Pr[w|c])). The format of the matrix is: 
+      wordWeights[class][wordAttribute]
+    */
+    private double[][] wordWeights;
+    
+    /** Holds the smoothing value to avoid word probabilities of zero.<br>
+        P.S.: According to the paper this is the Alpha i parameter 
+     */
+    private double smoothingParameter = 1.0;
+    
+    /** True if the words weights are to be normalized */
+    private boolean m_normalizeWordWeights = false;
+    
+    /** Holds the number of Class values present in the set of specified 
+        instances */
+    private int numClasses;
+    
+    /** The instances header that'll be used in toString */
+    private Instances header;
+
+    
+    /**
+     * Returns an enumeration describing the available options.
+     *
+     * @return an enumeration of all the available options.
+     */
+    public java.util.Enumeration listOptions() {
+        FastVector newVector = new FastVector(2);
+        newVector.addElement(
+        new Option("\tNormalize the word weights for each class\n",
+                   "N", 0,"-N"));
+        newVector.addElement(
+        new Option("\tSmoothing value to avoid zero WordGivenClass"+
+                   " probabilities (default=1.0).\n",
+                   "S", 1,"-S"));
+        
+        return newVector.elements();
+    }
+    
+    /**
+     * Gets the current settings of the classifier.
+     *
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+        String options[] = new String[4];
+        int current=0;
+        
+        if(getNormalizeWordWeights())
+            options[current++] = "-N";
+        
+        options[current++] = "-S";
+        options[current++] = Double.toString(smoothingParameter);
+        
+        while (current < options.length) {
+            options[current++] = "";
+        }
+        
+        return options;
+    }        
+
+    /**
+     * Parses a given list of options. <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -N
+     *  Normalize the word weights for each class
+     * </pre>
+     * 
+     * <pre> -S
+     *  Smoothing value to avoid zero WordGivenClass probabilities (default=1.0).
+     * </pre>
+     * 
+     <!-- options-end -->
+     *
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        
+        setNormalizeWordWeights(Utils.getFlag('N', options));
+        
+        String val = Utils.getOption('S', options);
+        if(val.length()!=0)
+          setSmoothingParameter(Double.parseDouble(val));
+        else
+          setSmoothingParameter(1.0);
+    }
+    
+    /**
+     * Returns true if the word weights for each class are to be normalized
+     * 
+     * @return true if the word weights are normalized
+     */
+    public boolean getNormalizeWordWeights() {
+        return m_normalizeWordWeights;
+    }
+    
+    /**
+     * Sets whether if the word weights for each class should be normalized
+     * 
+     * @param doNormalize whether the word weights are to be normalized
+     */
+    public void setNormalizeWordWeights(boolean doNormalize) {
+        m_normalizeWordWeights = doNormalize;
+    }
+    
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String normalizeWordWeightsTipText() {
+        return "Normalizes the word weights for each class.";
+    }
+    
+    /**
+     * Gets the smoothing value to be used to avoid zero WordGivenClass
+     * probabilities.
+     * 
+     * @return the smoothing value
+     */
+    public double getSmoothingParameter() {
+        return smoothingParameter;
+    }
+
+    /**
+     * Sets the smoothing value used to avoid zero WordGivenClass probabilities
+     * 
+     * @param val the new smooting value
+     */
+    public void setSmoothingParameter(double val) {
+        smoothingParameter = val;
+    }
+        
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String smoothingParameterTipText() {
+        return "Sets the smoothing parameter to avoid zero WordGivenClass "+
+               "probabilities (default=1.0).";
+    }
+
+    /**
+     * Returns a string describing this classifier
+     * @return a description of the classifier suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+        
+        return  "Class for building and using a Complement class Naive Bayes "+
+                "classifier.\n\nFor more information see, \n\n"+
+                getTechnicalInformation().toString() + "\n\n" +
+                "P.S.: TF, IDF and length normalization transforms, as "+
+                "described in the paper, can be performed through "+
+                "weka.filters.unsupervised.StringToWordVector.";
+    }
+
+    /**
+     * Returns an instance of a TechnicalInformation object, containing 
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     * 
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      
+      result = new TechnicalInformation(Type.INPROCEEDINGS);
+      result.setValue(Field.AUTHOR, "Jason D. Rennie and Lawrence Shih and Jaime Teevan and David R. Karger");
+      result.setValue(Field.TITLE, "Tackling the Poor Assumptions of Naive Bayes Text Classifiers");
+      result.setValue(Field.BOOKTITLE, "ICML");
+      result.setValue(Field.YEAR, "2003");
+      result.setValue(Field.PAGES, "616-623");
+      result.setValue(Field.PUBLISHER, "AAAI Press");
+      
+      return result;
+    }
+
+    /**
+     * Returns default capabilities of the classifier.
+     *
+     * @return      the capabilities of this classifier
+     */
+    public Capabilities getCapabilities() {
+      Capabilities result = super.getCapabilities();
+      result.disableAll();
+
+      // attributes
+      result.enable(Capability.NUMERIC_ATTRIBUTES);
+      result.enable(Capability.MISSING_VALUES);
+
+      // class
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+      
+      return result;
+    }
+    
+    /**
+     * Generates the classifier.
+     *
+     * @param instances set of instances serving as training data 
+     * @throws Exception if the classifier has not been built successfully
+     */
+    public void buildClassifier(Instances instances) throws Exception {
+
+      // can classifier handle the data?
+      getCapabilities().testWithFail(instances);
+
+      // remove instances with missing class
+      instances = new Instances(instances);
+      instances.deleteWithMissingClass();
+      
+        numClasses = instances.numClasses();
+	int numAttributes = instances.numAttributes();
+        
+        header = new Instances(instances, 0);
+	double [][] ocrnceOfWordInClass = new double[numClasses][numAttributes];        
+        wordWeights = new double[numClasses][numAttributes];
+        //double [] docsPerClass = new double[numClasses];
+	double[] wordsPerClass = new double[numClasses];
+        double totalWordOccurrences = 0;
+        double sumOfSmoothingParams = (numAttributes-1)*smoothingParameter;
+        int classIndex = instances.instance(0).classIndex();        
+	Instance instance;
+	int docClass;
+	double numOccurrences;        
+        
+        java.util.Enumeration enumInsts = instances.enumerateInstances();
+	while (enumInsts.hasMoreElements()) {
+		instance = (Instance) enumInsts.nextElement();
+		docClass = (int)instance.value(classIndex);
+		//docsPerClass[docClass] += instance.weight();
+		
+		for(int a = 0; a<instance.numValues(); a++)
+		    if(instance.index(a) != instance.classIndex()) {
+			    if(!instance.isMissing(a)) {
+				    numOccurrences = instance.valueSparse(a) * instance.weight();
+				    if(numOccurrences < 0)
+					throw new Exception("Numeric attribute"+
+                                                  " values must all be greater"+
+                                                  " or equal to zero.");
+                                    totalWordOccurrences += numOccurrences;
+				    wordsPerClass[docClass] += numOccurrences;
+				    ocrnceOfWordInClass[docClass]
+                                          [instance.index(a)] += numOccurrences;
+                                    //For the time being wordweights[0][i] 
+                                    //will hold the total occurrence of word
+                                    // i over all classes
+                                    wordWeights[0]
+                                          [instance.index(a)] += numOccurrences;
+                            }
+                    }
+        }
+
+	//Calculating the complement class probability for all classes except 0        
+	for(int c=1; c<numClasses; c++) {
+            //total occurrence of words in classes other than c
+            double totalWordOcrnces = totalWordOccurrences - wordsPerClass[c];
+
+            for(int w=0; w<numAttributes; w++) {
+                if(w != classIndex ) {
+                     //occurrence of w in classes other that c
+                    double ocrncesOfWord = 
+                                wordWeights[0][w] - ocrnceOfWordInClass[c][w];
+
+                    wordWeights[c][w] = 
+                        Math.log((ocrncesOfWord+smoothingParameter) / 
+                                (totalWordOcrnces+sumOfSmoothingParams));
+                }
+            }
+        }
+        
+	//Now calculating the complement class probability for class 0
+        for(int w=0; w<numAttributes; w++) {
+            if(w != classIndex) {
+                //occurrence of w in classes other that c
+                double ocrncesOfWord = wordWeights[0][w] - ocrnceOfWordInClass[0][w];
+                //total occurrence of words in classes other than c
+                double totalWordOcrnces = totalWordOccurrences - wordsPerClass[0];
+                
+                wordWeights[0][w] =
+                Math.log((ocrncesOfWord+smoothingParameter) /
+                (totalWordOcrnces+sumOfSmoothingParams));
+            }            
+        }
+        
+       	//Normalizing weights
+        if(m_normalizeWordWeights==true)
+            for(int c=0; c<numClasses; c++) {
+                double sum=0;
+                for(int w=0; w<numAttributes; w++) {
+                    if(w!=classIndex)
+                        sum += Math.abs(wordWeights[c][w]);
+                }
+                for(int w=0; w<numAttributes; w++) {
+                    if(w!=classIndex) {
+                        wordWeights[c][w] = wordWeights[c][w]/sum;
+                    }
+                }
+            }
+
+    }
+
+    
+    /**
+     * Classifies a given instance. <p>
+     *
+     * The classification rule is: <br>
+     *     MinC(forAllWords(ti*Wci)) <br>
+     *      where <br>
+     *         ti is the frequency of word i in the given instance <br>
+     *         Wci is the weight of word i in Class c. <p>
+     *
+     * For more information see section 4.4 of the paper mentioned above
+     * in the classifiers description.
+     *
+     * @param instance the instance to classify
+     * @return the index of the class the instance is most likely to belong.
+     * @throws Exception if the classifier has not been built yet.
+     */
+    public double classifyInstance(Instance instance) throws Exception {
+
+        if(wordWeights==null)
+            throw new Exception("Error. The classifier has not been built "+
+                                "properly.");
+        
+        double [] valueForClass = new double[numClasses];
+	double sumOfClassValues=0;
+	
+	for(int c=0; c<numClasses; c++) {
+	    double sumOfWordValues=0;
+	    for(int w=0; w<instance.numValues(); w++) {
+                if(instance.index(w)!=instance.classIndex()) {
+                    double freqOfWordInDoc = instance.valueSparse(w);
+                    sumOfWordValues += freqOfWordInDoc * 
+                                  wordWeights[c][instance.index(w)];
+                }
+	    }
+	    //valueForClass[c] = Math.log(probOfClass[c]) - sumOfWordValues;
+	    valueForClass[c] = sumOfWordValues;
+	    sumOfClassValues += valueForClass[c];
+	}
+
+        int minidx=0;
+	for(int i=0; i<numClasses; i++)
+	    if(valueForClass[i]<valueForClass[minidx])
+		minidx = i;
+	
+	return minidx;
+    }
+
+
+    /**
+     * Prints out the internal model built by the classifier. In this case
+     * it prints out the word weights calculated when building the classifier.
+     */
+    public String toString() {
+        if(wordWeights==null) {            
+            return "The classifier hasn't been built yet.";
+        }
+        
+        int numAttributes = header.numAttributes();
+        StringBuffer result = new StringBuffer("The word weights for each class are: \n"+
+                                               "------------------------------------\n\t");
+        
+        for(int c = 0; c<numClasses; c++)
+            result.append(header.classAttribute().value(c)).append("\t");
+        
+        result.append("\n");
+        
+        for(int w = 0; w<numAttributes; w++) {
+            result.append(header.attribute(w).name()).append("\t");
+            for(int c = 0; c<numClasses; c++)
+                result.append(Double.toString(wordWeights[c][w])).append("\t");
+            result.append("\n");
+        }
+        
+        return result.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+    
+    /**
+     * Main method for testing this class.
+     *
+     * @param argv the options
+     */
+    public static void main(String [] argv) {
+      runClassifier(new ComplementNaiveBayes(), argv);
+    }        
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/DMNBtext.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/DMNBtext.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/DMNBtext.java	(revision 29)
@@ -0,0 +1,553 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Discriminative Multinomial Naive Bayes for Text Classification
+ *    Copyright (C) 2008 Jiang Su
+ */
+
+package weka.classifiers.bayes;
+
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.classifiers.UpdateableClassifier;
+import java.util.*;
+import java.io.Serializable;
+import weka.core.Capabilities;
+import weka.core.OptionHandler;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a Discriminative Multinomial Naive Bayes classifier. For more information see,<br/>
+ * <br/>
+ * Jiang Su,Harry Zhang,Charles X. Ling,Stan Matwin: Discriminative Parameter Learning for Bayesian Networks. In: ICML 2008', 2008.<br/>
+ * <br/>
+ * The core equation for this classifier:<br/>
+ * <br/>
+ * P[Ci|D] = (P[D|Ci] x P[Ci]) / P[D] (Bayes rule)<br/>
+ * <br/>
+ * where Ci is class i and D is a document.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{JiangSu2008,
+ *    author = {Jiang Su,Harry Zhang,Charles X. Ling,Stan Matwin},
+ *    booktitle = {ICML 2008'},
+ *    title = {Discriminative Parameter Learning for Bayesian Networks},
+ *    year = {2008}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Jiang Su (Jiang.Su@unb.ca) 2008
+ * @version $Revision: 5928 $
+ */
+public class DMNBtext extends AbstractClassifier
+    implements OptionHandler, WeightedInstancesHandler, 
+               TechnicalInformationHandler, UpdateableClassifier {
+
+  /** for serialization */
+  static final long serialVersionUID = 5932177450183457085L;
+  /** The number of iterations. */
+  protected int m_NumIterations = 1;
+  protected boolean m_BinaryWord = true;
+  int m_numClasses=-1;
+  protected Instances m_headerInfo;
+  DNBBinary[] m_binaryClassifiers = null;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+      "Class for building and using a Discriminative Multinomial Naive Bayes classifier. "
+      + "For more information see,\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "The core equation for this classifier:\n\n"
+      + "P[Ci|D] = (P[D|Ci] x P[Ci]) / P[D] (Bayes rule)\n\n"
+      + "where Ci is class i and D is a document.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Jiang Su,Harry Zhang,Charles X. Ling,Stan Matwin");
+    result.setValue(Field.YEAR, "2008");
+    result.setValue(Field.TITLE, "Discriminative Parameter Learning for Bayesian Networks");
+    result.setValue(Field.BOOKTITLE, "ICML 2008'");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param data set of instances serving as training data
+   * @exception Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+    // remove instances with missing class
+    Instances instances =  new Instances(data);
+    instances.deleteWithMissingClass();
+
+    m_binaryClassifiers = new DNBBinary[instances.numClasses()];
+    m_numClasses=instances.numClasses();
+    m_headerInfo = new Instances(instances, 0);
+    for (int i = 0; i < instances.numClasses(); i++) {
+      m_binaryClassifiers[i] = new DNBBinary();
+      m_binaryClassifiers[i].setTargetClass(i);
+      m_binaryClassifiers[i].initClassifier(instances);
+    }
+
+    if (instances.numInstances() == 0)
+      return;
+    //Iterative update
+    Random random = new Random();
+    for (int it = 0; it < m_NumIterations; it++) {
+      for (int i = 0; i < instances.numInstances(); i++) {
+        updateClassifier(instances.instance(i));
+      }
+    }
+
+    //  Utils.normalize(m_oldClassDis);
+    // Utils.normalize(m_ClassDis);
+    // m_originalPositive = m_oldClassDis[0];
+    //   m_positive = m_ClassDis[0];
+
+  }
+
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance the new training instance to include in the model
+   * @exception Exception if the instance could not be incorporated in
+   * the model.
+   */
+
+  public void updateClassifier(Instance instance) throws Exception {
+
+    if (m_numClasses == 2) {
+      m_binaryClassifiers[0].updateClassifier(instance);
+    } else {
+      for (int i = 0; i < instance.numClasses(); i++)
+        m_binaryClassifiers[i].updateClassifier(instance);
+    }
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @exception Exception if there is a problem generating the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    if (m_numClasses == 2) {
+      // System.out.println(m_binaryClassifiers[0].getProbForTargetClass(instance));
+      return m_binaryClassifiers[0].distributionForInstance(instance);
+    }
+    double[] logDocGivenClass = new double[instance.numClasses()];
+    for (int i = 0; i < m_numClasses; i++)
+      logDocGivenClass[i] = m_binaryClassifiers[i].getLogProbForTargetClass(instance);
+
+
+    double max = logDocGivenClass[Utils.maxIndex(logDocGivenClass)];
+    for(int i = 0; i<m_numClasses; i++)
+      logDocGivenClass[i] = Math.exp(logDocGivenClass[i] - max);
+
+
+    try {
+      Utils.normalize(logDocGivenClass);
+    } catch (Exception e) {
+      e.printStackTrace();
+
+
+    }
+
+    return logDocGivenClass;
+  }
+  /**
+   * Returns a string representation of the classifier.
+   *
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+    StringBuffer result = new StringBuffer("");
+    result.append("The log ratio of two conditional probabilities of a word w_i: log(p(w_i)|+)/p(w_i)|-)) in decent order based on their absolute values\n");
+    result.append("Can be used to measure the discriminative power of each word.\n");
+    if (m_numClasses == 2) {
+      // System.out.println(m_binaryClassifiers[0].getProbForTargetClass(instance));
+      return result.append(m_binaryClassifiers[0].toString()).toString();
+    }
+    for (int i = 0; i < m_numClasses; i++)
+      { result.append(i+" against the rest classes\n");
+        result.append(m_binaryClassifiers[i].toString()+"\n");
+      }
+    return result.toString();
+  }
+
+  /*
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String iterations = Utils.getOption('I', options);
+    if (iterations.length() != 0) {
+      setNumIterations(Integer.parseInt(iterations));
+    } else {
+      setNumIterations(m_NumIterations);
+    }
+    iterations = Utils.getOption('B', options);
+    if (iterations.length() != 0) {
+      setBinaryWord(Boolean.parseBoolean(iterations));
+    } else {
+      setBinaryWord(m_BinaryWord);
+    }
+
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+
+    String[] options = new String[4];
+
+    int current = 0;
+    options[current++] = "-I";
+    options[current++] = "" + getNumIterations();
+
+    options[current++] = "-B";
+    options[current++] = "" + getBinaryWord();
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "The number of iterations that the classifier will scan the training data";
+  }
+
+  /**
+   * Sets the number of iterations to be performed
+   */
+  public void setNumIterations(int numIterations) {
+
+    m_NumIterations = numIterations;
+  }
+
+  /**
+   * Gets the number of iterations to be performed
+   *
+   * @return the iterations to be performed
+   */
+  public int getNumIterations() {
+
+    return m_NumIterations;
+  }
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binaryWordTipText() {
+    return " whether ingore the frequency information in data";
+  }
+  /**
+   * Sets whether use binary text representation
+   */
+  public void setBinaryWord(boolean val) {
+
+    m_BinaryWord = val;
+  }
+
+  /**
+   * Gets whether use binary text representation
+   *
+   * @return whether use binary text representation
+   */
+  public boolean getBinaryWord() {
+
+    return m_BinaryWord;
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return "$Revision: 1.0";
+  }
+
+  public class DNBBinary implements Serializable {
+
+    /** The number of iterations. */
+    private double[][] m_perWordPerClass;
+    private double[] m_wordsPerClass;
+    int m_classIndex = -1;
+    private double[] m_classDistribution;
+    /** number of unique words */
+    private int m_numAttributes;
+    //set the target class
+    private int m_targetClass = -1;
+
+    private double m_WordLaplace=1;
+
+    private double[] m_coefficient;
+    private double m_classRatio;
+    private double m_wordRatio;
+
+    public void initClassifier(Instances instances) throws Exception {
+      m_numAttributes = instances.numAttributes();
+      m_perWordPerClass = new double[2][m_numAttributes];
+      m_coefficient = new double[m_numAttributes];
+      m_wordsPerClass = new double[2];
+      m_classDistribution = new double[2];
+      m_WordLaplace = Math.log(m_numAttributes);
+      m_classIndex = instances.classIndex();
+
+      //Laplace
+      for (int c = 0; c < 2; c++) {
+        m_classDistribution[c] = 1;
+        m_wordsPerClass[c] = m_WordLaplace * m_numAttributes;
+        java.util.Arrays.fill(m_perWordPerClass[c], m_WordLaplace);
+      }
+
+    }
+
+    public void updateClassifier(Instance ins) throws
+      Exception {
+      //c=0 is 1, which is the target class, and c=1 is the rest
+      int classIndex = 0;
+      if (ins.value(ins.classIndex()) != m_targetClass)
+        classIndex = 1;
+      double prob = 1 -
+        distributionForInstance(ins)[classIndex];
+
+
+      double weight = prob * ins.weight();
+
+      for (int a = 0; a < ins.numValues(); a++) {
+        if (ins.index(a) != m_classIndex )
+          {
+
+            if (m_BinaryWord) {
+              if (ins.valueSparse(a) > 0) {
+                m_wordsPerClass[classIndex] +=
+                  weight;
+                m_perWordPerClass[classIndex][ins.
+                                              index(a)] +=
+                  weight;
+              }
+            } else {
+              double t = ins.valueSparse(a) * weight;
+              m_wordsPerClass[classIndex] += t;
+              m_perWordPerClass[classIndex][ins.index(a)] += t;
+            }
+            //update coefficient
+            m_coefficient[ins.index(a)] = Math.log(m_perWordPerClass[0][
+                                                                        ins.index(a)] /
+                                                   m_perWordPerClass[1][ins.index(a)]);
+          }
+      }
+      m_wordRatio = Math.log(m_wordsPerClass[0] / m_wordsPerClass[1]);
+      m_classDistribution[classIndex] += weight;
+      m_classRatio = Math.log(m_classDistribution[0] /
+                              m_classDistribution[1]);
+    }
+
+
+    /**
+     * Calculates the class membership probabilities for the given test
+     * instance.
+     *
+     * @param ins the instance to be classified
+     * @return predicted class probability distribution
+     * @exception Exception if there is a problem generating the prediction
+     */
+    public double getLogProbForTargetClass(Instance ins) throws Exception {
+
+      double probLog = m_classRatio;
+      for (int a = 0; a < ins.numValues(); a++) {
+        if (ins.index(a) != m_classIndex )
+          {
+
+            if (m_BinaryWord) {
+              if (ins.valueSparse(a) > 0) {
+                probLog += m_coefficient[ins.index(a)] -
+                  m_wordRatio;
+              }
+            } else {
+              probLog += ins.valueSparse(a) *
+                (m_coefficient[ins.index(a)] - m_wordRatio);
+            }
+          }
+      }
+      return probLog;
+    }
+
+    /**
+     * Calculates the class membership probabilities for the given test
+     * instance.
+     *
+     * @param instance the instance to be classified
+     * @return predicted class probability distribution
+     * @exception Exception if there is a problem generating the prediction
+     */
+    public double[] distributionForInstance(Instance instance) throws
+      Exception {
+      double[] probOfClassGivenDoc = new double[2];
+      double ratio=getLogProbForTargetClass(instance);
+      if (ratio > 709)
+        probOfClassGivenDoc[0]=1;
+      else
+        {
+          ratio = Math.exp(ratio);
+          probOfClassGivenDoc[0]=ratio / (1 + ratio);
+        }
+
+      probOfClassGivenDoc[1] = 1 - probOfClassGivenDoc[0];
+      return probOfClassGivenDoc;
+    }
+
+    /**
+     * Returns a string representation of the classifier.
+     *
+     * @return a string representation of the classifier
+     */
+    public String toString() {
+      //            StringBuffer result = new StringBuffer("The cofficiency of a naive Bayes classifier, can be considered as the discriminative power of a word\n--------------------------------------\n");
+      StringBuffer result = new StringBuffer();
+
+      result.append("\n");
+      TreeMap sort=new TreeMap();
+      double[] absCoeff=new double[m_numAttributes];
+      for(int w = 0; w<m_numAttributes; w++)
+        {
+          if(w==m_headerInfo.classIndex())continue;
+          String val= m_headerInfo.attribute(w).name()+": "+m_coefficient[w];
+          sort.put((-1)*Math.abs(m_coefficient[w]),val);
+        }
+      Iterator it=sort.values().iterator();
+      while(it.hasNext())
+        {
+          result.append((String)it.next());
+          result.append("\n");
+        }
+
+      return result.toString();
+    }
+
+    /**
+     * Sets the Target Class
+     */
+    public void setTargetClass(int targetClass) {
+
+      m_targetClass = targetClass;
+    }
+
+    /**
+     * Gets the Target Class
+     *
+     * @return the Target Class Index
+     */
+    public int getTargetClass() {
+
+      return m_targetClass;
+    }
+
+  }
+
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String[] argv) {
+
+    DMNBtext c = new DMNBtext();
+
+    runClassifier(c, argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/HNB.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/HNB.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/HNB.java	(revision 29)
@@ -0,0 +1,391 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HNB.java
+ *    Copyright (C) 2004 Liangxiao Jiang
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Contructs Hidden Naive Bayes classification model with high classification accuracy and AUC.<br/>
+ * <br/>
+ * For more information refer to:<br/>
+ * <br/>
+ * H. Zhang, L. Jiang, J. Su: Hidden Naive Bayes. In: Twentieth National Conference on Artificial Intelligence, 919-924, 2005.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Zhang2005,
+ *    author = {H. Zhang and L. Jiang and J. Su},
+ *    booktitle = {Twentieth National Conference on Artificial Intelligence},
+ *    pages = {919-924},
+ *    publisher = {AAAI Press},
+ *    title = {Hidden Naive Bayes},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author H. Zhang (hzhang@unb.ca)
+ * @author Liangxiao Jiang (ljiang@cug.edu.cn)
+ * @version $Revision: 5928 $
+ */
+public class HNB  
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -4503874444306113214L;
+  
+  /** The number of each class value occurs in the dataset */
+  private double [] m_ClassCounts;
+
+  /** The number of class and two attributes values occurs in the dataset */
+  private double [][][] m_ClassAttAttCounts;
+
+  /** The number of values for each attribute in the dataset */
+  private int [] m_NumAttValues;
+
+  /** The number of values for all attributes in the dataset */
+  private int m_TotalAttValues;
+
+  /** The number of classes in the dataset */
+  private int m_NumClasses;
+
+  /** The number of attributes including class in the dataset */
+  private int m_NumAttributes;
+
+  /** The number of instances in the dataset */
+  private int m_NumInstances;
+
+  /** The index of the class attribute in the dataset */
+  private int m_ClassIndex;
+
+  /** The starting index of each attribute in the dataset */
+  private int[] m_StartAttIndex;
+
+  /** The 2D array of conditional mutual information of each pair attributes */
+  private double[][] m_condiMutualInfo;
+
+  /**
+   * Returns a string describing this classifier.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return
+      "Contructs Hidden Naive Bayes classification model with high "
+      + "classification accuracy and AUC.\n\n"
+      + "For more information refer to:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "H. Zhang and L. Jiang and J. Su");
+    result.setValue(Field.TITLE, "Hidden Naive Bayes");
+    result.setValue(Field.BOOKTITLE, "Twentieth National Conference on Artificial Intelligence");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.PAGES, "919-924");
+    result.setValue(Field.PUBLISHER, "AAAI Press");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data
+   * @exception Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // reset variable
+    m_NumClasses = instances.numClasses();
+    m_ClassIndex = instances.classIndex();
+    m_NumAttributes = instances.numAttributes();
+    m_NumInstances = instances.numInstances();
+    m_TotalAttValues = 0;
+
+    // allocate space for attribute reference arrays
+    m_StartAttIndex = new int[m_NumAttributes];
+    m_NumAttValues = new int[m_NumAttributes];
+
+    // set the starting index of each attribute and the number of values for
+    // each attribute and the total number of values for all attributes (not including class).
+    for(int i = 0; i < m_NumAttributes; i++) {
+      if(i != m_ClassIndex) {
+        m_StartAttIndex[i] = m_TotalAttValues;
+        m_NumAttValues[i] = instances.attribute(i).numValues();
+        m_TotalAttValues += m_NumAttValues[i];
+      }
+      else {
+        m_StartAttIndex[i] = -1;
+        m_NumAttValues[i] = m_NumClasses;
+      }
+    }
+
+    // allocate space for counts and frequencies
+    m_ClassCounts = new double[m_NumClasses];
+    m_ClassAttAttCounts = new double[m_NumClasses][m_TotalAttValues][m_TotalAttValues];
+
+    // Calculate the counts
+    for(int k = 0; k < m_NumInstances; k++) {
+      int classVal=(int)instances.instance(k).classValue();
+      m_ClassCounts[classVal] ++;
+      int[] attIndex = new int[m_NumAttributes];
+      for(int i = 0; i < m_NumAttributes; i++) {
+        if(i == m_ClassIndex)
+          attIndex[i] = -1;
+        else
+          attIndex[i] = m_StartAttIndex[i] + (int)instances.instance(k).value(i);
+      }
+      for(int Att1 = 0; Att1 < m_NumAttributes; Att1++) {
+        if(attIndex[Att1] == -1) continue;
+        for(int Att2 = 0; Att2 < m_NumAttributes; Att2++) {
+          if((attIndex[Att2] != -1)) {
+            m_ClassAttAttCounts[classVal][attIndex[Att1]][attIndex[Att2]] ++;
+          }
+        }
+      }
+    }
+
+    //compute conditional mutual information of each pair attributes (not including class)
+    m_condiMutualInfo=new double[m_NumAttributes][m_NumAttributes];
+    for(int son=0;son<m_NumAttributes;son++){
+      if(son == m_ClassIndex) continue;
+      for(int parent=0;parent<m_NumAttributes;parent++){
+        if(parent == m_ClassIndex || son==parent) continue;
+        m_condiMutualInfo[son][parent]=conditionalMutualInfo(son,parent);
+      }
+    }
+  }
+
+  /**
+   * Computes conditional mutual information between a pair of attributes.
+   *
+   * @param son the son attribute
+   * @param parent the parent attribute
+   * @return the conditional mutual information between son and parent given class
+   * @throws Exception if computation fails
+   */
+  private double conditionalMutualInfo(int son, int parent) throws Exception{
+
+    double CondiMutualInfo=0;
+    int sIndex=m_StartAttIndex[son];
+    int pIndex=m_StartAttIndex[parent];
+    double[] PriorsClass = new double[m_NumClasses];
+    double[][] PriorsClassSon=new double[m_NumClasses][m_NumAttValues[son]];
+    double[][] PriorsClassParent=new double[m_NumClasses][m_NumAttValues[parent]];
+    double[][][] PriorsClassParentSon=new double[m_NumClasses][m_NumAttValues[parent]][m_NumAttValues[son]];
+
+    for(int i=0;i<m_NumClasses;i++){
+      PriorsClass[i]=m_ClassCounts[i]/m_NumInstances;
+    }
+
+    for(int i=0;i<m_NumClasses;i++){
+      for(int j=0;j<m_NumAttValues[son];j++){
+        PriorsClassSon[i][j]=m_ClassAttAttCounts[i][sIndex+j][sIndex+j]/m_NumInstances;
+      }
+    }
+
+    for(int i=0;i<m_NumClasses;i++){
+      for(int j=0;j<m_NumAttValues[parent];j++){
+        PriorsClassParent[i][j]=m_ClassAttAttCounts[i][pIndex+j][pIndex+j]/m_NumInstances;
+      }
+    }
+
+    for(int i=0;i<m_NumClasses;i++){
+      for(int j=0;j<m_NumAttValues[parent];j++){
+        for(int k=0;k<m_NumAttValues[son];k++){
+          PriorsClassParentSon[i][j][k]=m_ClassAttAttCounts[i][pIndex+j][sIndex+k]/m_NumInstances;
+        }
+      }
+    }
+
+    for(int i=0;i<m_NumClasses;i++){
+      for(int j=0;j<m_NumAttValues[parent];j++){
+        for(int k=0;k<m_NumAttValues[son];k++){
+          CondiMutualInfo+=PriorsClassParentSon[i][j][k]*log2(PriorsClassParentSon[i][j][k]*PriorsClass[i],PriorsClassParent[i][j]*PriorsClassSon[i][k]);
+        }
+      }
+    }
+    return CondiMutualInfo;
+  }
+
+  /**
+   * compute the logarithm whose base is 2.
+   *
+   * @param x numerator of the fraction.
+   * @param y denominator of the fraction.
+   * @return the natual logarithm of this fraction.
+   */
+  private double log2(double x,double y){
+
+    if(x<1e-6||y<1e-6)
+      return 0.0;
+    else
+      return Math.log(x/y)/Math.log(2);
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @exception Exception if there is a problem generating the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    //Definition of local variables
+    double[] probs = new double[m_NumClasses];
+    int sIndex;
+    double prob;
+    double condiMutualInfoSum;
+
+    // store instance's att values in an int array
+    int[] attIndex = new int[m_NumAttributes];
+    for(int att = 0; att < m_NumAttributes; att++) {
+      if(att == m_ClassIndex)
+        attIndex[att] = -1;
+      else
+        attIndex[att] = m_StartAttIndex[att] + (int)instance.value(att);
+    }
+
+    // calculate probabilities for each possible class value
+    for(int classVal = 0; classVal < m_NumClasses; classVal++) {
+      probs[classVal]=(m_ClassCounts[classVal]+1.0/m_NumClasses)/(m_NumInstances+1.0);
+      for(int son = 0; son < m_NumAttributes; son++) {
+        if(attIndex[son]==-1) continue;
+        sIndex=attIndex[son];
+        attIndex[son]=-1;
+        prob=0;
+        condiMutualInfoSum=0;
+        for(int parent=0; parent<m_NumAttributes; parent++) {
+          if(attIndex[parent]==-1) continue;
+          condiMutualInfoSum+=m_condiMutualInfo[son][parent];
+          prob+=m_condiMutualInfo[son][parent]*(m_ClassAttAttCounts[classVal][attIndex[parent]][sIndex]+1.0/m_NumAttValues[son])/(m_ClassAttAttCounts[classVal][attIndex[parent]][attIndex[parent]] + 1.0);
+        }
+        if(condiMutualInfoSum>0){
+          prob=prob/condiMutualInfoSum;
+          probs[classVal] *= prob;
+        }
+        else{
+          prob=(m_ClassAttAttCounts[classVal][sIndex][sIndex]+1.0/m_NumAttValues[son])/(m_ClassCounts[classVal]+1.0);
+          probs[classVal]*= prob;
+        }
+        attIndex[son] = sIndex;
+      }
+    }
+    Utils.normalize(probs);
+    return probs;
+  }
+
+  /**
+   * returns a string representation of the classifier
+   * 
+   * @return a representation of the classifier
+   */
+  public String toString() {
+
+    return "HNB (Hidden Naive Bayes)";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new HNB(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayes.java	(revision 29)
@@ -0,0 +1,948 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NaiveBayes.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.estimators.DiscreteEstimator;
+import weka.estimators.Estimator;
+import weka.estimators.KernelEstimator;
+import weka.estimators.NormalEstimator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for a Naive Bayes classifier using estimator classes. Numeric estimator precision values are chosen based on analysis of the  training data. For this reason, the classifier is not an UpdateableClassifier (which in typical usage are initialized with zero training instances) -- if you need the UpdateableClassifier functionality, use the NaiveBayesUpdateable classifier. The NaiveBayesUpdateable classifier will  use a default precision of 0.1 for numeric attributes when buildClassifier is called with zero training instances.<br/>
+ * <br/>
+ * For more information on Naive Bayes classifiers, see<br/>
+ * <br/>
+ * George H. John, Pat Langley: Estimating Continuous Distributions in Bayesian Classifiers. In: Eleventh Conference on Uncertainty in Artificial Intelligence, San Mateo, 338-345, 1995.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{John1995,
+ *    address = {San Mateo},
+ *    author = {George H. John and Pat Langley},
+ *    booktitle = {Eleventh Conference on Uncertainty in Artificial Intelligence},
+ *    pages = {338-345},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Estimating Continuous Distributions in Bayesian Classifiers},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -K
+ *  Use kernel density estimator rather than normal
+ *  distribution for numeric attributes</pre>
+ * 
+ * <pre> -D
+ *  Use supervised discretization to process numeric attributes
+ * </pre>
+ *
+ * <pre> -O
+ *  Display model in old format (good when there are many classes)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class NaiveBayes extends AbstractClassifier 
+implements OptionHandler, WeightedInstancesHandler, 
+           TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5995231201785697655L;
+
+  /** The attribute estimators. */
+  protected Estimator [][] m_Distributions;
+
+  /** The class estimator. */
+  protected Estimator m_ClassDistribution;
+
+  /**
+   * Whether to use kernel density estimator rather than normal distribution
+   * for numeric attributes
+   */
+  protected boolean m_UseKernelEstimator = false;
+
+  /**
+   * Whether to use discretization than normal distribution
+   * for numeric attributes
+   */
+  protected boolean m_UseDiscretization = false;
+
+  /** The number of classes (or 1 for numeric class) */
+  protected int m_NumClasses;
+
+  /**
+   * The dataset header for the purposes of printing out a semi-intelligible 
+   * model 
+   */
+  protected Instances m_Instances;
+
+  /*** The precision parameter used for numeric attributes */
+  protected static final double DEFAULT_NUM_PRECISION = 0.01;
+
+  /**
+   * The discretization filter.
+   */
+  protected weka.filters.supervised.attribute.Discretize m_Disc = null;
+
+  protected boolean m_displayModelInOldFormat = false;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for a Naive Bayes classifier using estimator classes. Numeric"
+      +" estimator precision values are chosen based on analysis of the "
+      +" training data. For this reason, the classifier is not an"
+      +" UpdateableClassifier (which in typical usage are initialized with zero"
+      +" training instances) -- if you need the UpdateableClassifier functionality,"
+      +" use the NaiveBayesUpdateable classifier. The NaiveBayesUpdateable"
+      +" classifier will  use a default precision of 0.1 for numeric attributes"
+      +" when buildClassifier is called with zero training instances.\n\n"
+      +"For more information on Naive Bayes classifiers, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "George H. John and Pat Langley");
+    result.setValue(Field.TITLE, "Estimating Continuous Distributions in Bayesian Classifiers");
+    result.setValue(Field.BOOKTITLE, "Eleventh Conference on Uncertainty in Artificial Intelligence");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.PAGES, "338-345");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.ADDRESS, "San Mateo");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @exception Exception if the classifier has not been generated 
+   * successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    m_NumClasses = instances.numClasses();
+
+    // Copy the instances
+    m_Instances = new Instances(instances);
+
+    // Discretize instances if required
+    if (m_UseDiscretization) {
+      m_Disc = new weka.filters.supervised.attribute.Discretize();
+      m_Disc.setInputFormat(m_Instances);
+      m_Instances = weka.filters.Filter.useFilter(m_Instances, m_Disc);
+    } else {
+      m_Disc = null;
+    }
+
+    // Reserve space for the distributions
+    m_Distributions = new Estimator[m_Instances.numAttributes() - 1]
+      [m_Instances.numClasses()];
+    m_ClassDistribution = new DiscreteEstimator(m_Instances.numClasses(), 
+                                                true);
+    int attIndex = 0;
+    Enumeration enu = m_Instances.enumerateAttributes();
+    while (enu.hasMoreElements()) {
+      Attribute attribute = (Attribute) enu.nextElement();
+
+      // If the attribute is numeric, determine the estimator 
+      // numeric precision from differences between adjacent values
+      double numPrecision = DEFAULT_NUM_PRECISION;
+      if (attribute.type() == Attribute.NUMERIC) {
+	m_Instances.sort(attribute);
+	if ((m_Instances.numInstances() > 0)
+	    && !m_Instances.instance(0).isMissing(attribute)) {
+	  double lastVal = m_Instances.instance(0).value(attribute);
+	  double currentVal, deltaSum = 0;
+	  int distinct = 0;
+	  for (int i = 1; i < m_Instances.numInstances(); i++) {
+	    Instance currentInst = m_Instances.instance(i);
+	    if (currentInst.isMissing(attribute)) {
+	      break;
+	    }
+	    currentVal = currentInst.value(attribute);
+	    if (currentVal != lastVal) {
+	      deltaSum += currentVal - lastVal;
+	      lastVal = currentVal;
+	      distinct++;
+	    }
+	  }
+	  if (distinct > 0) {
+	    numPrecision = deltaSum / distinct;
+	  }
+	}
+      }
+
+
+      for (int j = 0; j < m_Instances.numClasses(); j++) {
+	switch (attribute.type()) {
+	case Attribute.NUMERIC: 
+	  if (m_UseKernelEstimator) {
+	    m_Distributions[attIndex][j] = 
+	      new KernelEstimator(numPrecision);
+	  } else {
+	    m_Distributions[attIndex][j] = 
+	      new NormalEstimator(numPrecision);
+	  }
+	  break;
+	case Attribute.NOMINAL:
+	  m_Distributions[attIndex][j] = 
+	    new DiscreteEstimator(attribute.numValues(), true);
+	  break;
+	default:
+	  throw new Exception("Attribute type unknown to NaiveBayes");
+	}
+      }
+      attIndex++;
+    }
+
+    // Compute counts
+    Enumeration enumInsts = m_Instances.enumerateInstances();
+    while (enumInsts.hasMoreElements()) {
+      Instance instance = 
+	(Instance) enumInsts.nextElement();
+      updateClassifier(instance);
+    }
+
+    // Save space
+    m_Instances = new Instances(m_Instances, 0);
+  }
+
+
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance the new training instance to include in the model 
+   * @exception Exception if the instance could not be incorporated in
+   * the model.
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+
+    if (!instance.classIsMissing()) {
+      Enumeration enumAtts = m_Instances.enumerateAttributes();
+      int attIndex = 0;
+      while (enumAtts.hasMoreElements()) {
+	Attribute attribute = (Attribute) enumAtts.nextElement();
+	if (!instance.isMissing(attribute)) {
+	  m_Distributions[attIndex][(int)instance.classValue()].
+            addValue(instance.value(attribute), instance.weight());
+	}
+	attIndex++;
+      }
+      m_ClassDistribution.addValue(instance.classValue(),
+                                   instance.weight());
+    }
+  }
+
+
+  /**
+   * Calculates the class membership probabilities for the given test 
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @exception Exception if there is a problem generating the prediction
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception { 
+
+    if (m_UseDiscretization) {
+      m_Disc.input(instance);
+      instance = m_Disc.output();
+    }
+    double [] probs = new double[m_NumClasses];
+    for (int j = 0; j < m_NumClasses; j++) {
+      probs[j] = m_ClassDistribution.getProbability(j);
+    }
+    Enumeration enumAtts = instance.enumerateAttributes();
+    int attIndex = 0;
+    while (enumAtts.hasMoreElements()) {
+      Attribute attribute = (Attribute) enumAtts.nextElement();
+      if (!instance.isMissing(attribute)) {
+	double temp, max = 0;
+	for (int j = 0; j < m_NumClasses; j++) {
+	  temp = Math.max(1e-75, Math.pow(m_Distributions[attIndex][j].
+                                          getProbability(instance.value(attribute)), 
+                                          m_Instances.attribute(attIndex).weight()));
+	  probs[j] *= temp;
+	  if (probs[j] > max) {
+	    max = probs[j];
+	  }
+	  if (Double.isNaN(probs[j])) {
+	    throw new Exception("NaN returned from estimator for attribute "
+                                + attribute.name() + ":\n"
+                                + m_Distributions[attIndex][j].toString());
+	  }
+	}
+	if ((max > 0) && (max < 1e-75)) { // Danger of probability underflow
+	  for (int j = 0; j < m_NumClasses; j++) {
+	    probs[j] *= 1e75;
+	  }
+	}
+      }
+      attIndex++;
+    }
+
+    // Display probabilities
+    Utils.normalize(probs);
+    return probs;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(
+              new Option("\tUse kernel density estimator rather than normal\n"
+                         +"\tdistribution for numeric attributes",
+                         "K", 0,"-K"));
+    newVector.addElement(
+              new Option("\tUse supervised discretization to process numeric attributes\n",
+                         "D", 0,"-D"));
+    
+    newVector.addElement(
+              new Option("\tDisplay model in old format (good when there are "
+                         + "many classes)\n",
+                         "O", 0, "-O"));
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -K
+   *  Use kernel density estimator rather than normal
+   *  distribution for numeric attributes</pre>
+   * 
+   * <pre> -D
+   *  Use supervised discretization to process numeric attributes
+   * </pre>
+   *
+   * <pre> -O
+   *  Display model in old format (good when there are many classes)
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    boolean k = Utils.getFlag('K', options);
+    boolean d = Utils.getFlag('D', options);
+    if (k && d) {
+      throw new IllegalArgumentException("Can't use both kernel density " +
+                                         "estimation and discretization!");
+    }
+    setUseSupervisedDiscretization(d);
+    setUseKernelEstimator(k);
+    setDisplayModelInOldFormat(Utils.getFlag('O', options));
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [3];
+    int current = 0;
+
+    if (m_UseKernelEstimator) {
+      options[current++] = "-K";
+    }
+
+    if (m_UseDiscretization) {
+      options[current++] = "-D";
+    }
+
+    if (m_displayModelInOldFormat) {
+      options[current++] = "-O";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+    if (m_displayModelInOldFormat) {
+      return toStringOriginal();
+    }
+
+    StringBuffer temp = new StringBuffer();
+    temp.append("Naive Bayes Classifier");
+    if (m_Instances == null) {
+      temp.append(": No model built yet.");
+    } else {
+
+      int maxWidth = 0;
+      int maxAttWidth = 0;
+      boolean containsKernel = false;
+
+      // set up max widths
+      // class values
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+        if (m_Instances.classAttribute().value(i).length() > maxWidth) {
+          maxWidth = m_Instances.classAttribute().value(i).length();
+        }
+      }
+      // attributes
+      for (int i = 0; i < m_Instances.numAttributes(); i++) {
+        if (i != m_Instances.classIndex()) {
+          Attribute a = m_Instances.attribute(i);
+          if (a.name().length() > maxAttWidth) {
+            maxAttWidth = m_Instances.attribute(i).name().length();
+          }
+          if (a.isNominal()) {
+            // check values
+            for (int j = 0; j < a.numValues(); j++) {
+              String val = a.value(j) + "  ";
+              if (val.length() > maxAttWidth) {
+                maxAttWidth = val.length();
+              }
+            }
+          }
+        }
+      }
+
+      for (int i = 0; i < m_Distributions.length; i++) {
+        for (int j = 0; j < m_Instances.numClasses(); j++) {
+          if (m_Distributions[i][0] instanceof NormalEstimator) {
+            // check mean/precision dev against maxWidth
+            NormalEstimator n = (NormalEstimator)m_Distributions[i][j];
+            double mean = Math.log(Math.abs(n.getMean())) / Math.log(10.0);
+            double precision = Math.log(Math.abs(n.getPrecision())) / Math.log(10.0);
+            double width = (mean > precision)
+              ? mean
+              : precision;
+            if (width < 0) {
+              width = 1;
+            }
+            // decimal + # decimal places + 1
+            width += 6.0;
+            if ((int)width > maxWidth) {
+              maxWidth = (int)width;
+            }
+          } else if (m_Distributions[i][0] instanceof KernelEstimator) {
+            containsKernel = true;
+            KernelEstimator ke = (KernelEstimator)m_Distributions[i][j];
+            int numK = ke.getNumKernels();
+            String temps = "K" + numK + ": mean (weight)";
+            if (maxAttWidth < temps.length()) {
+              maxAttWidth = temps.length();
+            }
+            // check means + weights against maxWidth
+            if (ke.getNumKernels() > 0) {
+              double[] means = ke.getMeans();
+              double[] weights = ke.getWeights();
+              for (int k = 0; k < ke.getNumKernels(); k++) {
+                String m = Utils.doubleToString(means[k], maxWidth, 4).trim();
+                m += " (" + Utils.doubleToString(weights[k], maxWidth, 1).trim() + ")";
+                if (maxWidth < m.length()) {
+                  maxWidth = m.length();
+                }
+              }
+            }
+          } else if (m_Distributions[i][0] instanceof DiscreteEstimator) {
+            DiscreteEstimator d = (DiscreteEstimator)m_Distributions[i][j];
+            for (int k = 0; k < d.getNumSymbols(); k++) {
+              String size = "" + d.getCount(k);
+              if (size.length() > maxWidth) {
+                maxWidth = size.length();
+              }
+            }
+            int sum = ("" + d.getSumOfCounts()).length();
+            if (sum > maxWidth) {
+              maxWidth = sum;
+            }
+          }
+        }
+      }
+
+      // Check width of class labels
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+        String cSize = m_Instances.classAttribute().value(i);
+        if (cSize.length() > maxWidth) {
+          maxWidth = cSize.length();
+        }
+      }
+
+      // Check width of class priors
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+        String priorP = 
+          Utils.doubleToString(((DiscreteEstimator)m_ClassDistribution).getProbability(i),
+                               maxWidth, 2).trim();
+        priorP = "(" + priorP + ")";
+        if (priorP.length() > maxWidth) {
+          maxWidth = priorP.length();
+        }
+      }
+    
+      if (maxAttWidth < "Attribute".length()) {
+        maxAttWidth = "Attribute".length();
+      }
+
+      if (maxAttWidth < "  weight sum".length()) {
+        maxAttWidth = "  weight sum".length();
+      }
+
+      if (containsKernel) {
+        if (maxAttWidth < "  [precision]".length()) {
+          maxAttWidth = "  [precision]".length();
+        }
+      }
+
+      maxAttWidth += 2;
+    
+
+
+      temp.append("\n\n");
+      temp.append(pad("Class", " ", 
+                      (maxAttWidth + maxWidth + 1) - "Class".length(), 
+                      true));
+
+      temp.append("\n");
+      temp.append(pad("Attribute", " ", maxAttWidth - "Attribute".length(), false));
+      // class labels
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+        String classL = m_Instances.classAttribute().value(i);
+        temp.append(pad(classL, " ", maxWidth + 1 - classL.length(), true));
+      }
+      temp.append("\n");
+      // class priors
+      temp.append(pad("", " ", maxAttWidth, true));
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+        String priorP = 
+          Utils.doubleToString(((DiscreteEstimator)m_ClassDistribution).getProbability(i),
+                               maxWidth, 2).trim();
+        priorP = "(" + priorP + ")";
+        temp.append(pad(priorP, " ", maxWidth + 1 - priorP.length(), true));
+      }
+      temp.append("\n");
+      temp.append(pad("", "=", maxAttWidth + 
+                      (maxWidth * m_Instances.numClasses()) 
+                      + m_Instances.numClasses() + 1, true));
+      temp.append("\n");
+
+      // loop over the attributes
+      int counter = 0;
+      for (int i = 0; i < m_Instances.numAttributes(); i++) {
+        if (i == m_Instances.classIndex()) {
+          continue;
+        }
+        String attName = m_Instances.attribute(i).name();
+        temp.append(attName + "\n");
+          
+        if (m_Distributions[counter][0] instanceof NormalEstimator) {
+          String meanL = "  mean";
+          temp.append(pad(meanL, " ", maxAttWidth + 1 - meanL.length(), false));
+          for (int j = 0; j < m_Instances.numClasses(); j++) {            
+            // means
+            NormalEstimator n = (NormalEstimator)m_Distributions[counter][j];
+            String mean = 
+              Utils.doubleToString(n.getMean(), maxWidth, 4).trim();
+            temp.append(pad(mean, " ", maxWidth + 1 - mean.length(), true));
+          }
+          temp.append("\n");            
+          // now do std deviations
+          String stdDevL = "  std. dev.";
+          temp.append(pad(stdDevL, " ", maxAttWidth + 1 - stdDevL.length(), false));
+          for (int j = 0; j < m_Instances.numClasses(); j++) {
+            NormalEstimator n = (NormalEstimator)m_Distributions[counter][j];
+            String stdDev = 
+              Utils.doubleToString(n.getStdDev(), maxWidth, 4).trim();
+            temp.append(pad(stdDev, " ", maxWidth + 1 - stdDev.length(), true));
+          }
+          temp.append("\n");
+          // now the weight sums
+          String weightL = "  weight sum";
+          temp.append(pad(weightL, " ", maxAttWidth + 1 - weightL.length(), false));
+          for (int j = 0; j < m_Instances.numClasses(); j++) {
+            NormalEstimator n = (NormalEstimator)m_Distributions[counter][j];
+            String weight = 
+              Utils.doubleToString(n.getSumOfWeights(), maxWidth, 4).trim();
+            temp.append(pad(weight, " ", maxWidth + 1 - weight.length(), true));
+          }
+          temp.append("\n");
+          // now the precisions
+          String precisionL = "  precision";
+          temp.append(pad(precisionL, " ", maxAttWidth + 1 - precisionL.length(), false));
+          for (int j = 0; j < m_Instances.numClasses(); j++) {
+            NormalEstimator n = (NormalEstimator)m_Distributions[counter][j];
+            String precision = 
+              Utils.doubleToString(n.getPrecision(), maxWidth, 4).trim();
+            temp.append(pad(precision, " ", maxWidth + 1 - precision.length(), true));
+          }
+          temp.append("\n\n");
+            
+        } else if (m_Distributions[counter][0] instanceof DiscreteEstimator) {
+          Attribute a = m_Instances.attribute(i);
+          for (int j = 0; j < a.numValues(); j++) {
+            String val = "  " + a.value(j);
+            temp.append(pad(val, " ", maxAttWidth + 1 - val.length(), false));
+            for (int k = 0; k < m_Instances.numClasses(); k++) {
+              DiscreteEstimator d = (DiscreteEstimator)m_Distributions[counter][k];
+              String count = "" + d.getCount(j);
+              temp.append(pad(count, " ", maxWidth + 1 - count.length(), true));
+            }
+            temp.append("\n");
+          }
+          // do the totals
+          String total = "  [total]";
+          temp.append(pad(total, " ", maxAttWidth + 1 - total.length(), false));
+          for (int k = 0; k < m_Instances.numClasses(); k++) {
+            DiscreteEstimator d = (DiscreteEstimator)m_Distributions[counter][k];
+            String count = "" + d.getSumOfCounts();
+            temp.append(pad(count, " ", maxWidth + 1 - count.length(), true));
+          }
+          temp.append("\n\n");
+        } else if (m_Distributions[counter][0] instanceof KernelEstimator) {
+          String kL = "  [# kernels]";
+          temp.append(pad(kL, " ", maxAttWidth + 1 - kL.length(), false));
+          for (int k = 0; k < m_Instances.numClasses(); k++) {
+            KernelEstimator ke = (KernelEstimator)m_Distributions[counter][k];
+            String nk = "" + ke.getNumKernels();
+            temp.append(pad(nk, " ", maxWidth + 1 - nk.length(), true));
+          }
+          temp.append("\n");
+          // do num kernels, std. devs and precisions
+          String stdDevL = "  [std. dev]";
+          temp.append(pad(stdDevL, " ", maxAttWidth + 1 - stdDevL.length(), false));
+          for (int k = 0; k < m_Instances.numClasses(); k++) {
+            KernelEstimator ke = (KernelEstimator)m_Distributions[counter][k];
+            String stdD = Utils.doubleToString(ke.getStdDev(), maxWidth, 4).trim(); 
+            temp.append(pad(stdD, " ", maxWidth + 1 - stdD.length(), true));
+          }
+          temp.append("\n");
+          String precL = "  [precision]";
+          temp.append(pad(precL, " ", maxAttWidth + 1 - precL.length(), false));
+          for (int k = 0; k < m_Instances.numClasses(); k++) {
+            KernelEstimator ke = (KernelEstimator)m_Distributions[counter][k];
+            String prec = Utils.doubleToString(ke.getPrecision(), maxWidth, 4).trim(); 
+            temp.append(pad(prec, " ", maxWidth + 1 - prec.length(), true));
+          }
+          temp.append("\n");
+          // first determine max number of kernels accross the classes
+          int maxK = 0;
+          for (int k = 0; k < m_Instances.numClasses(); k++) {
+            KernelEstimator ke = (KernelEstimator)m_Distributions[counter][k];
+            if (ke.getNumKernels() > maxK) {
+              maxK = ke.getNumKernels();
+            }
+          }
+          for (int j = 0; j < maxK; j++) {
+            // means first
+            String meanL = "  K" + (j+1) + ": mean (weight)";
+            temp.append(pad(meanL, " ", maxAttWidth + 1 - meanL.length(), false));
+            for (int k = 0; k < m_Instances.numClasses(); k++) {
+              KernelEstimator ke = (KernelEstimator)m_Distributions[counter][k];
+              double[] means = ke.getMeans();
+              double[] weights = ke.getWeights();
+              String m = "--";
+              if (ke.getNumKernels() == 0) {
+                m = "" + 0;
+              } else if (j < ke.getNumKernels()) {
+                m = Utils.doubleToString(means[j], maxWidth, 4).trim();
+                m += " (" + Utils.doubleToString(weights[j], maxWidth, 1).trim() + ")";
+              }
+              temp.append(pad(m, " ", maxWidth + 1 - m.length(), true));
+            }
+            temp.append("\n");              
+          }
+          temp.append("\n");
+        }
+
+
+        counter++;
+      }
+    }
+      
+    return temp.toString();
+  }
+
+  /**
+   * Returns a description of the classifier in the old format.
+   *
+   * @return a description of the classifier as a string.
+   */
+  protected String toStringOriginal() {
+    
+    StringBuffer text = new StringBuffer();
+
+    text.append("Naive Bayes Classifier");
+    if (m_Instances == null) {
+      text.append(": No model built yet.");
+    } else {
+      try {
+	for (int i = 0; i < m_Distributions[0].length; i++) {
+	  text.append("\n\nClass " + m_Instances.classAttribute().value(i) +
+                      ": Prior probability = " + Utils.
+                      doubleToString(m_ClassDistribution.getProbability(i),
+                                     4, 2) + "\n\n");
+	  Enumeration enumAtts = m_Instances.enumerateAttributes();
+	  int attIndex = 0;
+	  while (enumAtts.hasMoreElements()) {
+	    Attribute attribute = (Attribute) enumAtts.nextElement();
+	    if (attribute.weight() > 0) {
+	      text.append(attribute.name() + ":  " 
+                          + m_Distributions[attIndex][i]);
+	    }
+	    attIndex++;
+	  }
+	}
+      } catch (Exception ex) {
+	text.append(ex.getMessage());
+      }
+    }
+
+    return text.toString();
+  } 
+
+  private String pad(String source, String padChar, 
+                     int length, boolean leftPad) {
+    StringBuffer temp = new StringBuffer();
+
+    if (leftPad) {
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+      temp.append(source);
+    } else {
+      temp.append(source);
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+    }
+    return temp.toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useKernelEstimatorTipText() {
+    return "Use a kernel estimator for numeric attributes rather than a "
+      +"normal distribution.";
+  }
+  /**
+   * Gets if kernel estimator is being used.
+   *
+   * @return Value of m_UseKernelEstimatory.
+   */
+  public boolean getUseKernelEstimator() {
+
+    return m_UseKernelEstimator;
+  }
+
+  /**
+   * Sets if kernel estimator is to be used.
+   *
+   * @param v  Value to assign to m_UseKernelEstimatory.
+   */
+  public void setUseKernelEstimator(boolean v) {
+
+    m_UseKernelEstimator = v;
+    if (v) {
+      setUseSupervisedDiscretization(false);
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useSupervisedDiscretizationTipText() {
+    return "Use supervised discretization to convert numeric attributes to nominal "
+      +"ones.";
+  }
+
+  /**
+   * Get whether supervised discretization is to be used.
+   *
+   * @return true if supervised discretization is to be used.
+   */
+  public boolean getUseSupervisedDiscretization() {
+
+    return m_UseDiscretization;
+  }
+
+  /**
+   * Set whether supervised discretization is to be used.
+   *
+   * @param newblah true if supervised discretization is to be used.
+   */
+  public void setUseSupervisedDiscretization(boolean newblah) {
+
+    m_UseDiscretization = newblah;
+    if (newblah) {
+      setUseKernelEstimator(false);
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String displayModelInOldFormatTipText() {
+    return "Use old format for model output. The old format is "
+      + "better when there are many class values. The new format "
+      + "is better when there are fewer classes and many attributes.";
+  }
+
+  /**
+   * Set whether to display model output in the old, original
+   * format.
+   *
+   * @param d true if model ouput is to be shown in the old format
+   */
+  public void setDisplayModelInOldFormat(boolean d) {
+    m_displayModelInOldFormat = d;
+  }
+
+  /**
+   * Get whether to display model output in the old, original
+   * format.
+   *
+   * @return true if model ouput is to be shown in the old format
+   */
+  public boolean getDisplayModelInOldFormat() {
+    return m_displayModelInOldFormat;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new NaiveBayes(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomial.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomial.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomial.java	(revision 29)
@@ -0,0 +1,386 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NaiveBayesMultinomial.java
+ * Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a multinomial Naive Bayes classifier. For more information see,<br/>
+ * <br/>
+ * Andrew Mccallum, Kamal Nigam: A Comparison of Event Models for Naive Bayes Text Classification. In: AAAI-98 Workshop on 'Learning for Text Categorization', 1998.<br/>
+ * <br/>
+ * The core equation for this classifier:<br/>
+ * <br/>
+ * P[Ci|D] = (P[D|Ci] x P[Ci]) / P[D] (Bayes rule)<br/>
+ * <br/>
+ * where Ci is class i and D is a document.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Mccallum1998,
+ *    author = {Andrew Mccallum and Kamal Nigam},
+ *    booktitle = {AAAI-98 Workshop on 'Learning for Text Categorization'},
+ *    title = {A Comparison of Event Models for Naive Bayes Text Classification},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Andrew Golightly (acg4@cs.waikato.ac.nz)
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class NaiveBayesMultinomial 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler,TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5932177440181257085L;
+  
+  /**
+   * probability that a word (w) exists in a class (H) (i.e. Pr[w|H])
+   * The matrix is in the this format: probOfWordGivenClass[class][wordAttribute]
+   * NOTE: the values are actually the log of Pr[w|H]
+   */
+  protected double[][] m_probOfWordGivenClass;
+    
+  /** the probability of a class (i.e. Pr[H]) */
+  protected double[] m_probOfClass;
+    
+  /** number of unique words */
+  protected int m_numAttributes;
+    
+  /** number of class values */
+  protected int m_numClasses;
+    
+  /** cache lnFactorial computations */
+  protected double[] m_lnFactorialCache = new double[]{0.0,0.0};
+    
+  /** copy of header information for use in toString method */
+  protected Instances m_headerInfo;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class for building and using a multinomial Naive Bayes classifier. "
+      + "For more information see,\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "The core equation for this classifier:\n\n"
+      + "P[Ci|D] = (P[D|Ci] x P[Ci]) / P[D] (Bayes rule)\n\n"
+      + "where Ci is class i and D is a document.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Andrew Mccallum and Kamal Nigam");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "A Comparison of Event Models for Naive Bayes Text Classification");
+    result.setValue(Field.BOOKTITLE, "AAAI-98 Workshop on 'Learning for Text Categorization'");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception 
+  {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_headerInfo = new Instances(instances, 0);
+    m_numClasses = instances.numClasses();
+    m_numAttributes = instances.numAttributes();
+    m_probOfWordGivenClass = new double[m_numClasses][];
+	
+    /*
+      initialising the matrix of word counts
+      NOTE: Laplace estimator introduced in case a word that does not appear for a class in the 
+      training set does so for the test set
+    */
+    for(int c = 0; c<m_numClasses; c++)
+      {
+	m_probOfWordGivenClass[c] = new double[m_numAttributes];
+	for(int att = 0; att<m_numAttributes; att++)
+	  {
+	    m_probOfWordGivenClass[c][att] = 1;
+	  }
+      }
+	
+    //enumerate through the instances 
+    Instance instance;
+    int classIndex;
+    double numOccurences;
+    double[] docsPerClass = new double[m_numClasses];
+    double[] wordsPerClass = new double[m_numClasses];
+	
+    java.util.Enumeration enumInsts = instances.enumerateInstances();
+    while (enumInsts.hasMoreElements()) 
+      {
+	instance = (Instance) enumInsts.nextElement();
+	classIndex = (int)instance.value(instance.classIndex());
+	docsPerClass[classIndex] += instance.weight();
+		
+	for(int a = 0; a<instance.numValues(); a++)
+	  if(instance.index(a) != instance.classIndex())
+	    {
+	      if(!instance.isMissing(a))
+		{
+		  numOccurences = instance.valueSparse(a) * instance.weight();
+		  if(numOccurences < 0)
+		    throw new Exception("Numeric attribute values must all be greater or equal to zero.");
+		  wordsPerClass[classIndex] += numOccurences;
+		  m_probOfWordGivenClass[classIndex][instance.index(a)] += numOccurences;
+		}
+	    } 
+      }
+	
+    /*
+      normalising probOfWordGivenClass values
+      and saving each value as the log of each value
+    */
+    for(int c = 0; c<m_numClasses; c++)
+      for(int v = 0; v<m_numAttributes; v++) 
+	m_probOfWordGivenClass[c][v] = Math.log(m_probOfWordGivenClass[c][v] / (wordsPerClass[c] + m_numAttributes - 1));
+	
+    /*
+      calculating Pr(H)
+      NOTE: Laplace estimator introduced in case a class does not get mentioned in the set of 
+      training instances
+    */
+    final double numDocs = instances.sumOfWeights() + m_numClasses;
+    m_probOfClass = new double[m_numClasses];
+    for(int h=0; h<m_numClasses; h++)
+      m_probOfClass[h] = (double)(docsPerClass[h] + 1)/numDocs; 
+  }
+    
+  /**
+   * Calculates the class membership probabilities for the given test 
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception 
+  {
+    double[] probOfClassGivenDoc = new double[m_numClasses];
+	
+    //calculate the array of log(Pr[D|C])
+    double[] logDocGivenClass = new double[m_numClasses];
+    for(int h = 0; h<m_numClasses; h++)
+      logDocGivenClass[h] = probOfDocGivenClass(instance, h);
+	
+    double max = logDocGivenClass[Utils.maxIndex(logDocGivenClass)];
+    double probOfDoc = 0.0;
+	
+    for(int i = 0; i<m_numClasses; i++) 
+      {
+	probOfClassGivenDoc[i] = Math.exp(logDocGivenClass[i] - max) * m_probOfClass[i];
+	probOfDoc += probOfClassGivenDoc[i];
+      }
+	
+    Utils.normalize(probOfClassGivenDoc,probOfDoc);
+	
+    return probOfClassGivenDoc;
+  }
+    
+  /**
+   * log(N!) + (for all the words)(log(Pi^ni) - log(ni!))
+   *  
+   *  where 
+   *      N is the total number of words
+   *      Pi is the probability of obtaining word i
+   *      ni is the number of times the word at index i occurs in the document
+   *
+   * @param inst       The instance to be classified
+   * @param classIndex The index of the class we are calculating the probability with respect to
+   *
+   * @return The log of the probability of the document occuring given the class
+   */
+    
+  private double probOfDocGivenClass(Instance inst, int classIndex)
+  {
+    double answer = 0;
+    //double totalWords = 0; //no need as we are not calculating the factorial at all.
+	
+    double freqOfWordInDoc;  //should be double
+    for(int i = 0; i<inst.numValues(); i++)
+      if(inst.index(i) != inst.classIndex())
+	{
+	  freqOfWordInDoc = inst.valueSparse(i);
+	  //totalWords += freqOfWordInDoc;
+	  answer += (freqOfWordInDoc * m_probOfWordGivenClass[classIndex][inst.index(i)] 
+		     ); //- lnFactorial(freqOfWordInDoc));
+	}
+	
+    //answer += lnFactorial(totalWords);//The factorial terms don't make 
+    //any difference to the classifier's
+    //accuracy, so not needed.
+	
+    return answer;
+  }
+    
+  /**
+   * Fast computation of ln(n!) for non-negative ints
+   *
+   * negative ints are passed on to the general gamma-function
+   * based version in weka.core.SpecialFunctions
+   *
+   * if the current n value is higher than any previous one,
+   * the cache is extended and filled to cover it
+   *
+   * the common case is reduced to a simple array lookup
+   *
+   * @param  n the integer 
+   * @return ln(n!)
+   */
+    
+  public double lnFactorial(int n) 
+  {
+    if (n < 0) return weka.core.SpecialFunctions.lnFactorial(n);
+	
+    if (m_lnFactorialCache.length <= n) {
+      double[] tmp = new double[n+1];
+      System.arraycopy(m_lnFactorialCache,0,tmp,0,m_lnFactorialCache.length);
+      for(int i = m_lnFactorialCache.length; i < tmp.length; i++) 
+	tmp[i] = tmp[i-1] + Math.log(i);
+      m_lnFactorialCache = tmp;
+    }
+	
+    return m_lnFactorialCache[n];
+  }
+    
+  /**
+   * Returns a string representation of the classifier.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString()
+  {
+    StringBuffer result = new StringBuffer("The independent probability of a class\n--------------------------------------\n");
+	
+    for(int c = 0; c<m_numClasses; c++)
+      result.append(m_headerInfo.classAttribute().value(c)).append("\t").append(Double.toString(m_probOfClass[c])).append("\n");
+	
+    result.append("\nThe probability of a word given the class\n-----------------------------------------\n\t");
+
+    for(int c = 0; c<m_numClasses; c++)
+      result.append(m_headerInfo.classAttribute().value(c)).append("\t");
+	
+    result.append("\n");
+
+    for(int w = 0; w<m_numAttributes; w++)
+      {
+	result.append(m_headerInfo.attribute(w).name()).append("\t");
+	for(int c = 0; c<m_numClasses; c++)
+	  result.append(Double.toString(Math.exp(m_probOfWordGivenClass[c][w]))).append("\t");
+	result.append("\n");
+      }
+
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new NaiveBayesMultinomial(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateable.java	(revision 29)
@@ -0,0 +1,246 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NaiveBayesMultinomialUpdateable.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *    Copyright (C) 2007 Jiang Su (incremental version)
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a multinomial Naive Bayes classifier. For more information see,<br/>
+ * <br/>
+ * Andrew Mccallum, Kamal Nigam: A Comparison of Event Models for Naive Bayes Text Classification. In: AAAI-98 Workshop on 'Learning for Text Categorization', 1998.<br/>
+ * <br/>
+ * The core equation for this classifier:<br/>
+ * <br/>
+ * P[Ci|D] = (P[D|Ci] x P[Ci]) / P[D] (Bayes rule)<br/>
+ * <br/>
+ * where Ci is class i and D is a document.<br/>
+ * <br/>
+ * Incremental version of the algorithm.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Mccallum1998,
+ *    author = {Andrew Mccallum and Kamal Nigam},
+ *    booktitle = {AAAI-98 Workshop on 'Learning for Text Categorization'},
+ *    title = {A Comparison of Event Models for Naive Bayes Text Classification},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Andrew Golightly (acg4@cs.waikato.ac.nz)
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @author Jiang Su
+ * @version $Revision: 1.3 $
+ */
+public class NaiveBayesMultinomialUpdateable
+  extends NaiveBayesMultinomial
+  implements UpdateableClassifier {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7204398796974263186L;
+  
+  /** the word count per class */
+  protected double[] m_wordsPerClass;
+  
+  /**
+   * Returns a string describing this classifier
+   * 
+   * @return 		a description of the classifier suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        super.globalInfo() + "\n\n"
+      + "Incremental version of the algorithm.";
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances 	set of instances serving as training data
+   * @throws Exception 	if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    m_headerInfo = new Instances(instances, 0);
+    m_numClasses = instances.numClasses();
+    m_numAttributes = instances.numAttributes();
+    m_probOfWordGivenClass = new double[m_numClasses][];
+    m_wordsPerClass = new double[m_numClasses];
+    m_probOfClass = new double[m_numClasses];
+
+    // initialising the matrix of word counts
+    // NOTE: Laplace estimator introduced in case a word that does not 
+    // appear for a class in the training set does so for the test set
+    double laplace = 1;
+    for (int c = 0; c < m_numClasses; c++) {
+      m_probOfWordGivenClass[c] = new double[m_numAttributes];
+      m_probOfClass[c]   = laplace;
+      m_wordsPerClass[c] = laplace * m_numAttributes;
+      for(int att = 0; att<m_numAttributes; att++) {
+	m_probOfWordGivenClass[c][att] = laplace;
+      }
+    }
+
+    for (int i = 0; i < instances.numInstances(); i++)
+      updateClassifier(instances.instance(i));
+  }
+
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance 	the new training instance to include in the model
+   * @throws Exception 	if the instance could not be incorporated in
+   * 			the model.
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+    int classIndex = (int) instance.value(instance.classIndex());
+    m_probOfClass[classIndex] += instance.weight();
+
+    for (int a = 0; a < instance.numValues(); a++) {
+      if (instance.index(a) == instance.classIndex() ||
+	  instance.isMissing(a))
+	continue;
+
+      double numOccurences = instance.valueSparse(a) * instance.weight();
+      if (numOccurences < 0)
+	throw new Exception(
+	    "Numeric attribute values must all be greater or equal to zero.");
+      m_wordsPerClass[classIndex] += numOccurences;
+      m_probOfWordGivenClass[classIndex][instance.index(a)] += numOccurences;
+    }
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance 	the instance to be classified
+   * @return 		predicted class probability distribution
+   * @throws Exception 	if there is a problem generating the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[] probOfClassGivenDoc = new double[m_numClasses];
+
+    // calculate the array of log(Pr[D|C])
+    double[] logDocGivenClass = new double[m_numClasses];
+    for (int c = 0; c < m_numClasses; c++) {
+      logDocGivenClass[c] += Math.log(m_probOfClass[c]);
+      int allWords = 0;
+      for (int i = 0; i < instance.numValues(); i++) {
+	if (instance.index(i) == instance.classIndex())
+	  continue;
+	double frequencies = instance.valueSparse(i);
+	allWords += frequencies;
+	logDocGivenClass[c] += frequencies *
+	Math.log(m_probOfWordGivenClass[c][instance.index(i)]);
+      }
+      logDocGivenClass[c] -= allWords * Math.log(m_wordsPerClass[c]);
+    }
+
+    double max = logDocGivenClass[Utils.maxIndex(logDocGivenClass)];
+    for (int i = 0; i < m_numClasses; i++)
+      probOfClassGivenDoc[i] = Math.exp(logDocGivenClass[i] - max);
+
+    Utils.normalize(probOfClassGivenDoc);
+
+    return probOfClassGivenDoc;
+  }
+
+  /**
+   * Returns a string representation of the classifier.
+   *
+   * @return 		a string representation of the classifier
+   */
+  public String toString() {
+    StringBuffer result = new StringBuffer();
+
+    result.append("The independent probability of a class\n");
+    result.append("--------------------------------------\n");
+
+    for (int c = 0; c < m_numClasses; c++)
+      result.append(m_headerInfo.classAttribute().value(c)).append("\t").
+      append(Double.toString(m_probOfClass[c])).append("\n");
+
+    result.append("\nThe probability of a word given the class\n");
+    result.append("-----------------------------------------\n\t");
+
+    for (int c = 0; c < m_numClasses; c++)
+      result.append(m_headerInfo.classAttribute().value(c)).append("\t");
+
+    result.append("\n");
+
+    for (int w = 0; w < m_numAttributes; w++) {
+      result.append(m_headerInfo.attribute(w).name()).append("\t");
+      for (int c = 0; c < m_numClasses; c++)
+	result.append(
+	    Double.toString(Math.exp(m_probOfWordGivenClass[c][w]))).append("\t");
+      result.append("\n");
+    }
+
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.3 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new NaiveBayesMultinomialUpdateable(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesSimple.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesSimple.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesSimple.java	(revision 29)
@@ -0,0 +1,431 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NaiveBayesSimple.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a simple Naive Bayes classifier.Numeric attributes are modelled by a normal distribution.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Richard Duda, Peter Hart (1973). Pattern Classification and Scene Analysis. Wiley, New York.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Duda1973,
+ *    address = {New York},
+ *    author = {Richard Duda and Peter Hart},
+ *    publisher = {Wiley},
+ *    title = {Pattern Classification and Scene Analysis},
+ *    year = {1973}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+*/
+public class NaiveBayesSimple 
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1478242251770381214L;
+
+  /** All the counts for nominal attributes. */
+  protected double [][][] m_Counts;
+  
+  /** The means for numeric attributes. */
+  protected double [][] m_Means;
+
+  /** The standard deviations for numeric attributes. */
+  protected double [][] m_Devs;
+
+  /** The prior probabilities of the classes. */
+  protected double [] m_Priors;
+
+  /** The instances used for training. */
+  protected Instances m_Instances;
+
+  /** Constant for normal distribution. */
+  protected static double NORM_CONST = Math.sqrt(2 * Math.PI);
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class for building and using a simple Naive Bayes classifier."
+      + "Numeric attributes are modelled by a normal distribution.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Richard Duda and Peter Hart");
+    result.setValue(Field.YEAR, "1973");
+    result.setValue(Field.TITLE, "Pattern Classification and Scene Analysis");
+    result.setValue(Field.PUBLISHER, "Wiley");
+    result.setValue(Field.ADDRESS, "New York");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @exception Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    int attIndex = 0;
+    double sum;
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_Instances = new Instances(instances, 0);
+    
+    // Reserve space
+    m_Counts = new double[instances.numClasses()]
+      [instances.numAttributes() - 1][0];
+    m_Means = new double[instances.numClasses()]
+      [instances.numAttributes() - 1];
+    m_Devs = new double[instances.numClasses()]
+      [instances.numAttributes() - 1];
+    m_Priors = new double[instances.numClasses()];
+    Enumeration enu = instances.enumerateAttributes();
+    while (enu.hasMoreElements()) {
+      Attribute attribute = (Attribute) enu.nextElement();
+      if (attribute.isNominal()) {
+	for (int j = 0; j < instances.numClasses(); j++) {
+	  m_Counts[j][attIndex] = new double[attribute.numValues()];
+	}
+      } else {
+	for (int j = 0; j < instances.numClasses(); j++) {
+	  m_Counts[j][attIndex] = new double[1];
+	}
+      }
+      attIndex++;
+    }
+    
+    // Compute counts and sums
+    Enumeration enumInsts = instances.enumerateInstances();
+    while (enumInsts.hasMoreElements()) {
+      Instance instance = (Instance) enumInsts.nextElement();
+      if (!instance.classIsMissing()) {
+	Enumeration enumAtts = instances.enumerateAttributes();
+	attIndex = 0;
+	while (enumAtts.hasMoreElements()) {
+	  Attribute attribute = (Attribute) enumAtts.nextElement();
+	  if (!instance.isMissing(attribute)) {
+	    if (attribute.isNominal()) {
+	      m_Counts[(int)instance.classValue()][attIndex]
+		[(int)instance.value(attribute)]++;
+	    } else {
+	      m_Means[(int)instance.classValue()][attIndex] +=
+		instance.value(attribute);
+	      m_Counts[(int)instance.classValue()][attIndex][0]++;
+	    }
+	  }
+	  attIndex++;
+	}
+	m_Priors[(int)instance.classValue()]++;
+      }
+    }
+    
+    // Compute means
+    Enumeration enumAtts = instances.enumerateAttributes();
+    attIndex = 0;
+    while (enumAtts.hasMoreElements()) {
+      Attribute attribute = (Attribute) enumAtts.nextElement();
+      if (attribute.isNumeric()) {
+	for (int j = 0; j < instances.numClasses(); j++) {
+	  if (m_Counts[j][attIndex][0] < 2) {
+	    throw new Exception("attribute " + attribute.name() +
+				": less than two values for class " +
+				instances.classAttribute().value(j));
+	  }
+	  m_Means[j][attIndex] /= m_Counts[j][attIndex][0];
+	}
+      }
+      attIndex++;
+    }    
+    
+    // Compute standard deviations
+    enumInsts = instances.enumerateInstances();
+    while (enumInsts.hasMoreElements()) {
+      Instance instance = 
+	(Instance) enumInsts.nextElement();
+      if (!instance.classIsMissing()) {
+	enumAtts = instances.enumerateAttributes();
+	attIndex = 0;
+	while (enumAtts.hasMoreElements()) {
+	  Attribute attribute = (Attribute) enumAtts.nextElement();
+	  if (!instance.isMissing(attribute)) {
+	    if (attribute.isNumeric()) {
+	      m_Devs[(int)instance.classValue()][attIndex] +=
+		(m_Means[(int)instance.classValue()][attIndex]-
+		 instance.value(attribute))*
+		(m_Means[(int)instance.classValue()][attIndex]-
+		 instance.value(attribute));
+	    }
+	  }
+	  attIndex++;
+	}
+      }
+    }
+    enumAtts = instances.enumerateAttributes();
+    attIndex = 0;
+    while (enumAtts.hasMoreElements()) {
+      Attribute attribute = (Attribute) enumAtts.nextElement();
+      if (attribute.isNumeric()) {
+	for (int j = 0; j < instances.numClasses(); j++) {
+	  if (m_Devs[j][attIndex] <= 0) {
+	    throw new Exception("attribute " + attribute.name() +
+				": standard deviation is 0 for class " +
+				instances.classAttribute().value(j));
+	  }
+	  else {
+	    m_Devs[j][attIndex] /= m_Counts[j][attIndex][0] - 1;
+	    m_Devs[j][attIndex] = Math.sqrt(m_Devs[j][attIndex]);
+	  }
+	}
+      }
+      attIndex++;
+    } 
+    
+    // Normalize counts
+    enumAtts = instances.enumerateAttributes();
+    attIndex = 0;
+    while (enumAtts.hasMoreElements()) {
+      Attribute attribute = (Attribute) enumAtts.nextElement();
+      if (attribute.isNominal()) {
+	for (int j = 0; j < instances.numClasses(); j++) {
+	  sum = Utils.sum(m_Counts[j][attIndex]);
+	  for (int i = 0; i < attribute.numValues(); i++) {
+	    m_Counts[j][attIndex][i] =
+	      (m_Counts[j][attIndex][i] + 1) 
+	      / (sum + (double)attribute.numValues());
+	  }
+	}
+      }
+      attIndex++;
+    }
+    
+    // Normalize priors
+    sum = Utils.sum(m_Priors);
+    for (int j = 0; j < instances.numClasses(); j++)
+      m_Priors[j] = (m_Priors[j] + 1) 
+	/ (sum + (double)instances.numClasses());
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @exception Exception if distribution can't be computed
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    double [] probs = new double[instance.numClasses()];
+    int attIndex;
+    
+    for (int j = 0; j < instance.numClasses(); j++) {
+      probs[j] = 1;
+      Enumeration enumAtts = instance.enumerateAttributes();
+      attIndex = 0;
+      while (enumAtts.hasMoreElements()) {
+	Attribute attribute = (Attribute) enumAtts.nextElement();
+	if (!instance.isMissing(attribute)) {
+	  if (attribute.isNominal()) {
+	    probs[j] *= m_Counts[j][attIndex][(int)instance.value(attribute)];
+	  } else {
+	    probs[j] *= normalDens(instance.value(attribute),
+				   m_Means[j][attIndex],
+				   m_Devs[j][attIndex]);}
+	}
+	attIndex++;
+      }
+      probs[j] *= m_Priors[j];
+    }
+
+    // Normalize probabilities
+    Utils.normalize(probs);
+
+    return probs;
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+
+    if (m_Instances == null) {
+      return "Naive Bayes (simple): No model built yet.";
+    }
+    try {
+      StringBuffer text = new StringBuffer("Naive Bayes (simple)");
+      int attIndex;
+      
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+	text.append("\n\nClass " + m_Instances.classAttribute().value(i) 
+		    + ": P(C) = " 
+		    + Utils.doubleToString(m_Priors[i], 10, 8)
+		    + "\n\n");
+	Enumeration enumAtts = m_Instances.enumerateAttributes();
+	attIndex = 0;
+	while (enumAtts.hasMoreElements()) {
+	  Attribute attribute = (Attribute) enumAtts.nextElement();
+	  text.append("Attribute " + attribute.name() + "\n");
+	  if (attribute.isNominal()) {
+	    for (int j = 0; j < attribute.numValues(); j++) {
+	      text.append(attribute.value(j) + "\t");
+	    }
+	    text.append("\n");
+	    for (int j = 0; j < attribute.numValues(); j++)
+	      text.append(Utils.
+			  doubleToString(m_Counts[i][attIndex][j], 10, 8)
+			  + "\t");
+	  } else {
+	    text.append("Mean: " + Utils.
+			doubleToString(m_Means[i][attIndex], 10, 8) + "\t");
+	    text.append("Standard Deviation: " 
+			+ Utils.doubleToString(m_Devs[i][attIndex], 10, 8));
+	  }
+	  text.append("\n\n");
+	  attIndex++;
+	}
+      }
+      
+      return text.toString();
+    } catch (Exception e) {
+      return "Can't print Naive Bayes classifier!";
+    }
+  }
+
+  /**
+   * Density function of normal distribution.
+   * 
+   * @param x the value to get the density for
+   * @param mean the mean
+   * @param stdDev the standard deviation
+   * @return the density
+   */
+  protected double normalDens(double x, double mean, double stdDev) {
+    
+    double diff = x - mean;
+    
+    return (1 / (NORM_CONST * stdDev)) 
+      * Math.exp(-(diff * diff / (2 * stdDev * stdDev)));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new NaiveBayesSimple(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesUpdateable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesUpdateable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/NaiveBayesUpdateable.java	(revision 29)
@@ -0,0 +1,140 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NaiveBayesUpdateable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.UpdateableClassifier;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for a Naive Bayes classifier using estimator classes. This is the updateable version of NaiveBayes.<br/>
+ * This classifier will use a default precision of 0.1 for numeric attributes when buildClassifier is called with zero training instances.<br/>
+ * <br/>
+ * For more information on Naive Bayes classifiers, see<br/>
+ * <br/>
+ * George H. John, Pat Langley: Estimating Continuous Distributions in Bayesian Classifiers. In: Eleventh Conference on Uncertainty in Artificial Intelligence, San Mateo, 338-345, 1995.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{John1995,
+ *    address = {San Mateo},
+ *    author = {George H. John and Pat Langley},
+ *    booktitle = {Eleventh Conference on Uncertainty in Artificial Intelligence},
+ *    pages = {338-345},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Estimating Continuous Distributions in Bayesian Classifiers},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -K
+ *  Use kernel density estimator rather than normal
+ *  distribution for numeric attributes</pre>
+ * 
+ * <pre> -D
+ *  Use supervised discretization to process numeric attributes
+ * </pre>
+ * 
+ * <pre> -O
+ *  Display model in old format (good when there are many classes)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public class NaiveBayesUpdateable extends NaiveBayes 
+  implements UpdateableClassifier {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5354015843807192221L;
+ 
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for a Naive Bayes classifier using estimator classes. This is the "
+      +"updateable version of NaiveBayes.\n"
+      +"This classifier will use a default precision of 0.1 for numeric attributes "
+      +"when buildClassifier is called with zero training instances.\n\n"
+      +"For more information on Naive Bayes classifiers, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    return super.getTechnicalInformation();
+  }
+
+  /**
+   * Set whether supervised discretization is to be used.
+   *
+   * @param newblah true if supervised discretization is to be used.
+   */
+  public void setUseSupervisedDiscretization(boolean newblah) {
+
+    if (newblah) {
+      throw new IllegalArgumentException("Can't use discretization " + 
+					 "in NaiveBayesUpdateable!");
+    }
+    m_UseDiscretization = false;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.11 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new NaiveBayesUpdateable(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/WAODE.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/WAODE.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/WAODE.java	(revision 29)
@@ -0,0 +1,543 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WAODE.java
+ *    Copyright 2006 Liangxiao Jiang
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * WAODE contructs the model called Weightily Averaged One-Dependence Estimators.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * L. Jiang, H. Zhang: Weightily Averaged One-Dependence Estimators. In: Proceedings of the 9th Biennial Pacific Rim International Conference on Artificial Intelligence, PRICAI 2006, 970-974, 2006.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Jiang2006,
+ *    author = {L. Jiang and H. Zhang},
+ *    booktitle = {Proceedings of the 9th Biennial Pacific Rim International Conference on Artificial Intelligence, PRICAI 2006},
+ *    pages = {970-974},
+ *    series = {LNAI},
+ *    title = {Weightily Averaged One-Dependence Estimators},
+ *    volume = {4099},
+ *    year = {2006}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -I
+ *  Whether to print some more internals.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Liangxiao Jiang (ljiang@cug.edu.cn)
+ * @author  H. Zhang (hzhang@unb.ca)
+ * @version $Revision: 5928 $
+ */
+public class WAODE 
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 2170978824284697882L;
+
+  /** The number of each class value occurs in the dataset */
+  private double[] m_ClassCounts;
+  
+  /** The number of each attribute value occurs in the dataset */
+  private double[] m_AttCounts;
+  
+  /** The number of two attributes values occurs in the dataset */
+  private double[][] m_AttAttCounts;
+  
+  /** The number of class and two attributes values occurs in the dataset */
+  private double[][][] m_ClassAttAttCounts;
+  
+  /** The number of values for each attribute in the dataset */
+  private int[] m_NumAttValues;
+  
+  /** The number of values for all attributes in the dataset */
+  private int m_TotalAttValues;
+  
+  /** The number of classes in the dataset */
+  private int m_NumClasses;
+  
+  /** The number of attributes including class in the dataset */
+  private int m_NumAttributes;
+  
+  /** The number of instances in the dataset */
+  private int m_NumInstances;
+  
+  /** The index of the class attribute in the dataset */
+  private int m_ClassIndex;
+  
+  /** The starting index of each attribute in the dataset */
+  private int[] m_StartAttIndex;
+  
+  /** The array of mutual information between each attribute and class */
+  private double[] m_mutualInformation;
+  
+  /** the header information of the training data */
+  private Instances m_Header = null;
+  
+  /** whether to print more internals in the toString method
+   * @see #toString() */
+  private boolean m_Internals = false;
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+  
+  /**
+   * Returns a string describing this classifier
+   * 
+   * @return 		a description of the classifier suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "WAODE contructs the model called Weightily Averaged One-Dependence "
+      + "Estimators.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+	"\tWhether to print some more internals.\n"
+	+ "\t(default: no)",
+	"I", 0, "-I"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -I
+   *  Whether to print some more internals.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    setInternals(Utils.getFlag('I', options));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getInternals())
+      result.add("-I");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String internalsTipText() {
+    return "Prints more internals of the classifier.";
+  }
+
+  /** 
+   * Sets whether internals about classifier are printed via toString().
+   *
+   * @param value if internals should be printed
+   * @see #toString()
+   */
+  public void setInternals(boolean value) {
+    m_Internals = value;
+  }
+
+  /**
+   * Gets whether more internals of the classifier are printed.
+   *
+   * @return true if more internals are printed
+   */
+  public boolean getInternals() {
+    return m_Internals;
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "L. Jiang and H. Zhang");
+    result.setValue(Field.TITLE, "Weightily Averaged One-Dependence Estimators");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the 9th Biennial Pacific Rim International Conference on Artificial Intelligence, PRICAI 2006");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.PAGES, "970-974");
+    result.setValue(Field.SERIES, "LNAI");
+    result.setValue(Field.VOLUME, "4099");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // only class? -> build ZeroR model
+    if (instances.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(instances);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+
+    // reset variable
+    m_NumClasses = instances.numClasses();
+    m_ClassIndex = instances.classIndex();
+    m_NumAttributes = instances.numAttributes();
+    m_NumInstances = instances.numInstances();
+    m_TotalAttValues = 0;
+    
+    // allocate space for attribute reference arrays
+    m_StartAttIndex = new int[m_NumAttributes];
+    m_NumAttValues = new int[m_NumAttributes];
+    
+    // set the starting index of each attribute and the number of values for
+    // each attribute and the total number of values for all attributes (not including class).
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (i != m_ClassIndex) {
+	m_StartAttIndex[i] = m_TotalAttValues;
+	m_NumAttValues[i] = instances.attribute(i).numValues();
+	m_TotalAttValues += m_NumAttValues[i];
+      }
+      else {
+	m_StartAttIndex[i] = -1;
+	m_NumAttValues[i] = m_NumClasses;
+      }
+    }
+    
+    // allocate space for counts and frequencies
+    m_ClassCounts = new double[m_NumClasses];
+    m_AttCounts = new double[m_TotalAttValues];
+    m_AttAttCounts = new double[m_TotalAttValues][m_TotalAttValues];
+    m_ClassAttAttCounts = new double[m_NumClasses][m_TotalAttValues][m_TotalAttValues];
+    m_Header = new Instances(instances, 0);
+    
+    // Calculate the counts
+    for (int k = 0; k < m_NumInstances; k++) {
+      int classVal=(int)instances.instance(k).classValue();
+      m_ClassCounts[classVal] ++;
+      int[] attIndex = new int[m_NumAttributes];
+      for (int i = 0; i < m_NumAttributes; i++) {
+	if (i == m_ClassIndex){
+	  attIndex[i] = -1;
+	}
+	else{
+	  attIndex[i] = m_StartAttIndex[i] + (int)instances.instance(k).value(i);
+	  m_AttCounts[attIndex[i]]++;
+	}
+      }
+      for (int Att1 = 0; Att1 < m_NumAttributes; Att1++) {
+	if (attIndex[Att1] == -1) continue;
+	for (int Att2 = 0; Att2 < m_NumAttributes; Att2++) {
+	  if ((attIndex[Att2] != -1)) {
+	    m_AttAttCounts[attIndex[Att1]][attIndex[Att2]] ++;
+	    m_ClassAttAttCounts[classVal][attIndex[Att1]][attIndex[Att2]] ++;
+	  }
+	}
+      }
+    }
+    
+    //compute mutual information between each attribute and class
+    m_mutualInformation=new double[m_NumAttributes];
+    for (int att=0;att<m_NumAttributes;att++){
+      if (att == m_ClassIndex) continue;
+      m_mutualInformation[att]=mutualInfo(att);
+    }
+  }
+  
+  /**
+   * Computes mutual information between each attribute and class attribute.
+   *
+   * @param att is the attribute
+   * @return the conditional mutual information between son and parent given class
+   */
+  private double mutualInfo(int att) {
+    
+    double mutualInfo=0;
+    int attIndex=m_StartAttIndex[att];
+    double[] PriorsClass = new double[m_NumClasses];
+    double[] PriorsAttribute = new double[m_NumAttValues[att]];
+    double[][] PriorsClassAttribute=new double[m_NumClasses][m_NumAttValues[att]];
+    
+    for (int i=0;i<m_NumClasses;i++){
+      PriorsClass[i]=m_ClassCounts[i]/m_NumInstances;
+    }
+    
+    for (int j=0;j<m_NumAttValues[att];j++){
+      PriorsAttribute[j]=m_AttCounts[attIndex+j]/m_NumInstances;
+    }
+    
+    for (int i=0;i<m_NumClasses;i++){
+      for (int j=0;j<m_NumAttValues[att];j++){
+	PriorsClassAttribute[i][j]=m_ClassAttAttCounts[i][attIndex+j][attIndex+j]/m_NumInstances;
+      }
+    }
+    
+    for (int i=0;i<m_NumClasses;i++){
+      for (int j=0;j<m_NumAttValues[att];j++){
+	mutualInfo+=PriorsClassAttribute[i][j]*log2(PriorsClassAttribute[i][j],PriorsClass[i]*PriorsAttribute[j]);
+      }
+    }
+    return mutualInfo;
+  }
+  
+  /**
+   * compute the logarithm whose base is 2.
+   *
+   * @param x numerator of the fraction.
+   * @param y denominator of the fraction.
+   * @return the natual logarithm of this fraction.
+   */
+  private double log2(double x,double y){
+    
+    if (x < Utils.SMALL || y < Utils.SMALL)
+      return 0.0;
+    else
+      return Math.log(x/y)/Math.log(2);
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test instance
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if there is a problem generating the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    //Definition of local variables
+    double[] probs = new double[m_NumClasses];
+    double prob;
+    double mutualInfoSum;
+    
+    // store instance's att values in an int array
+    int[] attIndex = new int[m_NumAttributes];
+    for (int att = 0; att < m_NumAttributes; att++) {
+      if (att == m_ClassIndex)
+	attIndex[att] = -1;
+      else
+	attIndex[att] = m_StartAttIndex[att] + (int)instance.value(att);
+    }
+    
+    // calculate probabilities for each possible class value
+    for (int classVal = 0; classVal < m_NumClasses; classVal++) {
+      probs[classVal] = 0;
+      prob=1;
+      mutualInfoSum=0.0;
+      for (int parent = 0; parent < m_NumAttributes; parent++) {
+	if (attIndex[parent]==-1) continue;
+	prob=(m_ClassAttAttCounts[classVal][attIndex[parent]][attIndex[parent]] + 1.0/(m_NumClasses*m_NumAttValues[parent]))/(m_NumInstances + 1.0);
+	for (int son = 0; son < m_NumAttributes; son++) {
+	  if (attIndex[son]==-1 || son == parent) continue;
+	  prob*=(m_ClassAttAttCounts[classVal][attIndex[parent]][attIndex[son]] + 1.0/m_NumAttValues[son])/(m_ClassAttAttCounts[classVal][attIndex[parent]][attIndex[parent]] + 1.0);
+	}
+	mutualInfoSum+=m_mutualInformation[parent];
+	probs[classVal]+=m_mutualInformation[parent]*prob;
+      }
+      probs[classVal]/=mutualInfoSum;
+    }
+    if (!Double.isNaN(Utils.sum(probs)))
+      Utils.normalize(probs);
+    return probs;
+  }
+  
+  /**
+   * returns a string representation of the classifier
+   * 
+   * @return string representation of the classifier
+   */
+  public String toString() {
+    StringBuffer	result;
+    String		classname;
+    int			i;
+    
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      result = new StringBuffer();
+      result.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      result.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      result.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      result.append(m_ZeroR.toString());
+    }
+    else {
+      classname = this.getClass().getName().replaceAll(".*\\.", "");
+      result    = new StringBuffer();
+      result.append(classname + "\n");
+      result.append(classname.replaceAll(".", "=") + "\n\n");
+
+      if (m_Header == null) {
+	result.append("No Model built yet.\n");
+      }
+      else {
+	if (getInternals()) {
+	  result.append("Mutual information of attributes with class attribute:\n");
+	  for (i = 0; i < m_Header.numAttributes(); i++) {
+	    // skip class
+	    if (i == m_Header.classIndex())
+	      continue;
+
+	    result.append(
+		(i+1) + ". " + m_Header.attribute(i).name() + ": " 
+		+ Utils.doubleToString(m_mutualInformation[i], 6) + "\n");
+	  }
+	}
+	else {
+	  result.append("Model built successfully.\n");
+	}
+      }
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the commandline options, use -h to list all options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new WAODE(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/GaussianPriorImpl.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/GaussianPriorImpl.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/GaussianPriorImpl.java	(revision 29)
@@ -0,0 +1,122 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GaussianPrior.java
+ *    Copyright (C) 2008 Illinois Institute of Technology
+ *
+ */
+package weka.classifiers.bayes.blr;
+
+import weka.classifiers.bayes.BayesianLogisticRegression;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * Implementation of the Gaussian Prior update function based on
+ * CLG Algorithm with a certain Trust Region Update.
+ *
+ * The values are updated in the BayesianLogisticRegressionV variables
+ * used by the algorithm.
+ *
+ *
+ * @author Navendu Garg(gargnav@iit.edu)
+ * @version $Revision: 1.2 $
+ */
+public class GaussianPriorImpl
+  extends Prior {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -2995684220141159223L;
+
+  /**
+   * Update function specific to Laplace Prior.
+   */
+  public double update(int j, Instances instances, double beta,
+    double hyperparameter, double[] r, double deltaV) {
+    int i;
+    double numerator = 0.0;
+    double denominator = 0.0;
+    double value = 0.0;
+    Instance instance;
+
+    m_Instances = instances;
+    Beta = beta;
+    Hyperparameter = hyperparameter;
+    Delta = deltaV;
+    R = r;
+
+    //Compute First Derivative i.e. Numerator
+    //Compute the Second Derivative i.e.
+    for (i = 0; i < m_Instances.numInstances(); i++) {
+      instance = m_Instances.instance(i);
+
+      if (instance.value(j) != 0) {
+        //Compute Numerator (Note: (0.0-1.0/(1.0+Math.exp(R[i]) 
+        numerator += ((instance.value(j) * BayesianLogisticRegression.classSgn(instance.classValue())) * (0.0 -
+        (1.0 / (1.0 + Math.exp(R[i])))));
+
+        //Compute Denominator
+        denominator += (instance.value(j) * instance.value(j) * BayesianLogisticRegression.bigF(R[i],
+          Delta * Math.abs(instance.value(j))));
+      }
+    }
+
+    numerator += ((2.0 * Beta) / Hyperparameter);
+    denominator += (2.0 / Hyperparameter);
+    value = numerator / denominator;
+
+    return (0 - (value));
+  }
+
+  /**
+   * This method calls the log-likelihood implemented in the Prior
+   * abstract class.
+   * @param betas
+   * @param instances
+   */
+  public void computeLoglikelihood(double[] betas, Instances instances) {
+    super.computelogLikelihood(betas, instances);
+  }
+
+  /**
+   * This function computes the penalty term specific to Gaussian distribution.
+   * @param betas
+   * @param hyperparameters
+   */
+  public void computePenalty(double[] betas, double[] hyperparameters) {
+    penalty = 0.0;
+
+    for (int j = 0; j < betas.length; j++) {
+      penalty += (Math.log(Math.sqrt(hyperparameters[j])) +
+      (Math.log(2 * Math.PI) / 2) +
+      ((betas[j] * betas[j]) / (2 * hyperparameters[j])));
+    }
+
+    penalty = 0 - penalty;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/LaplacePriorImpl.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/LaplacePriorImpl.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/LaplacePriorImpl.java	(revision 29)
@@ -0,0 +1,170 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GaussianPrior.java
+ *    Copyright (C) 2008 Illinois Institute of Technology
+ *
+ */
+package weka.classifiers.bayes.blr;
+
+import weka.classifiers.bayes.BayesianLogisticRegression;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * Implementation of the Gaussian Prior update function based on modified
+ *  CLG Algorithm (CLG-Lasso) with a certain Trust Region Update based
+ * on Laplace Priors.
+ *
+ * @author Navendu Garg(gargnav@iit.edu)
+ * @version $Revision: 4899 $
+ */
+public class LaplacePriorImpl
+  extends Prior {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2353576123257012607L;
+  
+  Instances m_Instances;
+  double Beta;
+  double Hyperparameter;
+  double DeltaUpdate;
+  double[] R;
+  double Delta;
+
+  /**
+   * Update function specific to Laplace Prior.
+   */
+  public double update(int j, Instances instances, double beta,
+    double hyperparameter, double[] r, double deltaV) {
+    double sign = 0.0;
+    double change = 0.0;
+    DeltaUpdate = 0.0;
+    m_Instances = instances;
+    Beta = beta;
+    Hyperparameter = hyperparameter;
+    R = r;
+    Delta = deltaV;
+
+    if (Beta == 0) {
+      sign = 1.0;
+      DeltaUpdate = laplaceUpdate(j, sign);
+
+      if (DeltaUpdate <= 0.0) { // positive direction failed.
+        sign = -1.0;
+        DeltaUpdate = laplaceUpdate(j, sign);
+
+        if (DeltaUpdate >= 0.0) {
+          DeltaUpdate = 0;
+        }
+      }
+    } else {
+      sign = Beta / Math.abs(Beta);
+      DeltaUpdate = laplaceUpdate(j, sign);
+      change = Beta + DeltaUpdate;
+      change = change / Math.abs(change);
+
+      if (change < 0) {
+        DeltaUpdate = 0 - Beta;
+      }
+    }
+
+    return DeltaUpdate;
+  }
+
+  /**
+   * This is the CLG-lasso update function described in the
+
+  *<pre>
+  * &#64;TechReport{blrtext04,
+  *author = {Alexander Genkin and David D. Lewis and David Madigan},
+  *title = {Large-scale bayesian logistic regression for text categorization},
+  *institution = {DIMACS},
+  *year = {2004},
+  *url = "http://www.stat.rutgers.edu/~madigan/PAPERS/shortFat-v3a.pdf",
+  *OPTannote = {}
+  *}</pre>
+   *
+   * @param j
+   * @return double value
+   */
+  public double laplaceUpdate(int j, double sign) {
+    double value = 0.0;
+    double numerator = 0.0;
+    double denominator = 0.0;
+
+    Instance instance;
+
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      instance = m_Instances.instance(i);
+
+      if (instance.value(j) != 0) {
+        numerator += (instance.value(j) * BayesianLogisticRegression.classSgn(instance.classValue()) * (1.0 / (1.0 +
+        Math.exp(R[i]))));
+        denominator += (instance.value(j) * instance.value(j) * BayesianLogisticRegression.bigF(R[i],
+          Delta * instance.value(j)));
+      }
+    }
+
+    numerator -= (Math.sqrt(2.0 / Hyperparameter) * sign);
+
+    if (denominator != 0.0) {
+      value = numerator / denominator;
+    }
+
+    return value;
+  }
+
+  /**
+   * Computes the log-likelihood values using the implementation in the Prior class.
+   * @param betas
+   * @param instances
+   */
+  public void computeLogLikelihood(double[] betas, Instances instances) {
+    //Basic implementation done in the prior class.
+    super.computelogLikelihood(betas, instances);
+  }
+
+  /**
+   * This function computes the penalty term specific to Laplacian distribution.
+   * @param betas
+   * @param hyperparameters
+   */
+  public void computePenalty(double[] betas, double[] hyperparameters) {
+    penalty = 0.0;
+
+    double lambda = 0.0;
+
+    for (int j = 0; j < betas.length; j++) {
+      lambda = Math.sqrt(hyperparameters[j]);
+      penalty += (Math.log(2) - Math.log(lambda) +
+      (lambda * Math.abs(betas[j])));
+    }
+
+    penalty = 0 - penalty;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4899 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/Prior.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/Prior.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/blr/Prior.java	(revision 29)
@@ -0,0 +1,124 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Prior.java
+ *    Copyright (C) 2008 Illinois Institute of Technology
+ *
+ */
+
+package weka.classifiers.bayes.blr;
+
+import weka.classifiers.bayes.BayesianLogisticRegression;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * This is an interface to plug various priors into
+ * the Bayesian Logistic Regression Model.
+ *
+ * @version $Revision: 1.2 $
+ * @author Navendu Garg (gargnav@iit.edu)
+ */
+public abstract class Prior
+  implements Serializable, RevisionHandler {
+  
+  protected Instances m_Instances;
+  protected double Beta = 0.0;
+  protected double Hyperparameter = 0.0;
+  protected double DeltaUpdate;
+  protected double[] R;
+  protected double Delta = 0.0;
+  protected double log_posterior = 0.0;
+  protected double log_likelihood = 0.0;
+  protected double penalty = 0.0;
+
+  /**
+   * Interface for the update functions for different types of
+   * priors.
+   *
+   */
+  public double update(int j, Instances instances, double beta,
+    double hyperparameter, double[] r, double deltaV) {
+    return 0.0;
+  }
+
+  /**
+   * Function computes the log-likelihood value:
+   * -sum{1 to n}{ln(1+exp(-Beta*x(i)*y(i))}
+   * @param betas
+   * @param instances
+   */
+  public void computelogLikelihood(double[] betas, Instances instances) {
+    Instance instance;
+    log_likelihood = 0.0;
+
+    for (int i = 0; i < instances.numInstances(); i++) {
+      instance = instances.instance(i);
+
+      double log_row = 0.0;
+
+      for (int j = 0; j < instance.numAttributes(); j++) {
+        if (instance.value(j) != 0.0) {
+          log_row += (betas[j] * instance.value(j) * instance.value(j));
+        }
+      }
+
+      log_row = log_row * BayesianLogisticRegression.classSgn(instance.classValue());
+      log_likelihood += Math.log(1.0 + Math.exp(0.0 - log_row));
+    }
+
+    log_likelihood = 0 - log_likelihood;
+  }
+
+  /**
+   * Skeleton function to compute penalty terms.
+   * @param betas
+   * @param hyperparameters
+   */
+  public void computePenalty(double[] betas, double[] hyperparameters) {
+    //implement specific penalties in the prior implmentation.
+  }
+
+  /**
+   *
+   * @return log-likelihood value.
+   */
+  public double getLoglikelihood() {
+    return log_likelihood;
+  }
+
+  /**
+   *
+   * @return regularized log posterior value.
+   */
+  public double getLogPosterior() {
+    log_posterior = log_likelihood + penalty;
+
+    return log_posterior;
+  }
+
+  /**
+   *
+   * @return penalty term.
+   */
+  public double getPenalty() {
+    return penalty;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ADNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ADNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ADNode.java	(revision 29)
@@ -0,0 +1,297 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ADNode.java
+ * Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.FileReader;
+import java.io.Serializable;
+
+/**
+ * The ADNode class implements the ADTree datastructure which increases
+ * the speed with which sub-contingency tables can be constructed from
+ * a data set in an Instances object. For details, see: <p/>
+ *
+ <!-- technical-plaintext-start -->
+ * Andrew W. Moore, Mary S. Lee (1998). Cached Sufficient Statistics for Efficient Machine Learning with Large Datasets. Journal of Artificial Intelligence Research. 8:67-91.
+ <!-- technical-plaintext-end -->
+ * <p/>
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Moore1998,
+ *    author = {Andrew W. Moore and Mary S. Lee},
+ *    journal = {Journal of Artificial Intelligence Research},
+ *    pages = {67-91},
+ *    title = {Cached Sufficient Statistics for Efficient Machine Learning with Large Datasets},
+ *    volume = {8},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.7 $
+ */
+public class ADNode 
+	implements Serializable, TechnicalInformationHandler, RevisionHandler {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = 397409728366910204L;
+  
+        final static int MIN_RECORD_SIZE = 0;
+	
+	/** list of VaryNode children **/
+	public VaryNode [] m_VaryNodes;
+	/** list of Instance children (either m_Instances or m_VaryNodes is instantiated) **/
+	public Instance [] m_Instances;
+
+        /** count **/
+	public int m_nCount;
+
+        /** first node in VaryNode array **/
+        public int m_nStartNode;
+
+        /** Creates new ADNode */
+        public ADNode() {
+        }
+
+        /**
+         * Returns an instance of a TechnicalInformation object, containing 
+         * detailed information about the technical background of this class,
+         * e.g., paper reference or book this class is based on.
+         * 
+         * @return the technical information about this class
+         */
+        public TechnicalInformation getTechnicalInformation() {
+          TechnicalInformation 	result;
+          
+          result = new TechnicalInformation(Type.ARTICLE);
+          result.setValue(Field.AUTHOR, "Andrew W. Moore and Mary S. Lee");
+          result.setValue(Field.YEAR, "1998");
+          result.setValue(Field.TITLE, "Cached Sufficient Statistics for Efficient Machine Learning with Large Datasets");
+          result.setValue(Field.JOURNAL, "Journal of Artificial Intelligence Research");
+          result.setValue(Field.VOLUME, "8");
+          result.setValue(Field.PAGES, "67-91");
+          
+          return result;
+        }
+
+	/** create sub tree
+	 * @param iNode index of the lowest node in the tree
+	 * @param nRecords set of records in instances to be considered
+	 * @param instances data set
+         * @return VaryNode representing part of an ADTree
+ 	 **/
+	public static VaryNode makeVaryNode(int iNode, FastVector nRecords, Instances instances) {
+		VaryNode _VaryNode = new VaryNode(iNode);
+		int nValues = instances.attribute(iNode).numValues();
+                
+
+		// reserve memory and initialize
+		FastVector [] nChildRecords = new FastVector[nValues];
+		for (int iChild = 0; iChild < nValues; iChild++) {
+			nChildRecords[iChild] = new FastVector();
+		}
+		// divide the records among children
+		for (int iRecord = 0; iRecord < nRecords.size(); iRecord++) {
+			int iInstance = ((Integer) nRecords.elementAt(iRecord)).intValue();
+			nChildRecords[(int) instances.instance(iInstance).value(iNode)].addElement(new Integer(iInstance));
+		}
+
+		// find most common value
+		int nCount = nChildRecords[0].size();
+		int nMCV = 0; 
+		for (int iChild = 1; iChild < nValues; iChild++) {
+			if (nChildRecords[iChild].size() > nCount) {
+				nCount = nChildRecords[iChild].size();
+				nMCV = iChild;
+			}
+		}
+                _VaryNode.m_nMCV = nMCV;
+
+                // determine child nodes
+                _VaryNode.m_ADNodes = new ADNode[nValues];
+		for (int iChild = 0; iChild < nValues; iChild++) {
+			if (iChild == nMCV || nChildRecords[iChild].size() == 0) {
+				_VaryNode.m_ADNodes[iChild] = null;
+			} else {
+				_VaryNode.m_ADNodes[iChild] = makeADTree(iNode + 1, nChildRecords[iChild], instances);
+			}
+		}
+		return _VaryNode;
+	} // MakeVaryNode
+
+	/** 
+	 * create sub tree
+	 * 
+	 * @param iNode index of the lowest node in the tree
+	 * @param nRecords set of records in instances to be considered
+	 * @param instances data set
+         * @return ADNode representing an ADTree
+	 */
+	public static ADNode makeADTree(int iNode, FastVector nRecords, Instances instances) {
+		ADNode _ADNode = new ADNode();
+                _ADNode.m_nCount = nRecords.size();
+                _ADNode.m_nStartNode = iNode;
+                if (nRecords.size() < MIN_RECORD_SIZE) {
+                  _ADNode.m_Instances = new Instance[nRecords.size()];
+                  for (int iInstance = 0; iInstance < nRecords.size(); iInstance++) {
+                    _ADNode.m_Instances[iInstance] = instances.instance(((Integer) nRecords.elementAt(iInstance)).intValue());
+                  }
+                } else {
+                  _ADNode.m_VaryNodes = new VaryNode[instances.numAttributes() - iNode];
+                  for (int iNode2 = iNode; iNode2 < instances.numAttributes(); iNode2++) {
+                          _ADNode.m_VaryNodes[iNode2 - iNode] = makeVaryNode(iNode2, nRecords, instances);
+                  }
+                }
+		return _ADNode;
+	} // MakeADTree
+
+	/** 
+	 * create AD tree from set of instances
+	 * 
+	 * @param instances data set
+         * @return ADNode representing an ADTree
+	 */
+	public static ADNode makeADTree(Instances instances) {
+          FastVector nRecords = new FastVector(instances.numInstances());
+          for (int iRecord = 0; iRecord < instances.numInstances(); iRecord++) {
+            nRecords.addElement(new Integer(iRecord));
+          }
+          return makeADTree(0, nRecords, instances);
+        } // MakeADTree
+        
+          /** 
+           * get counts for specific instantiation of a set of nodes
+           * 
+           * @param nCounts - array for storing counts
+           * @param nNodes - array of node indexes 
+           * @param nOffsets - offset for nodes in nNodes in nCounts
+           * @param iNode - index into nNode indicating current node
+           * @param iOffset - Offset into nCounts due to nodes below iNode
+           * @param bSubstract - indicate whether counts should be added or substracted
+           */
+        public void getCounts(
+              int [] nCounts, 
+              int [] nNodes, 
+              int [] nOffsets, 
+              int iNode, 
+              int iOffset,
+              boolean bSubstract
+        ) {
+//for (int iNode2 = 0; iNode2 < nCounts.length; iNode2++) {
+//   System.out.print(nCounts[iNode2] + " ");
+//}
+//System.out.println();
+          if (iNode >= nNodes.length) {
+            if (bSubstract) {
+              nCounts[iOffset] -= m_nCount;
+            } else {
+              nCounts[iOffset] += m_nCount;
+            }
+            return;
+          } else {
+            if (m_VaryNodes != null) {
+              m_VaryNodes[nNodes[iNode] - m_nStartNode].getCounts(nCounts, nNodes, nOffsets, iNode, iOffset, this, bSubstract);
+            } else {
+              for (int iInstance = 0; iInstance < m_Instances.length; iInstance++) {
+                int iOffset2 = iOffset;
+                Instance instance = m_Instances[iInstance];
+                for (int iNode2 = iNode; iNode2 < nNodes.length; iNode2++) {
+                  iOffset2 = iOffset2 + nOffsets[iNode2] * (int) instance.value(nNodes[iNode2]);
+                }
+                if (bSubstract) {
+	                nCounts[iOffset2]--;
+                } else {
+                	nCounts[iOffset2]++;
+                }
+              }
+            }
+          }
+        } // getCounts
+
+
+        /** 
+         * print is used for debugging only and shows the ADTree in ASCII graphics
+         */
+        public void print() {
+          String sTab = new String();for (int i = 0; i < m_nStartNode; i++) {
+              sTab = sTab + "  ";
+          }
+          System.out.println(sTab + "Count = " + m_nCount);
+          if (m_VaryNodes != null) {
+	          for (int iNode = 0; iNode < m_VaryNodes.length; iNode++) {
+	            System.out.println(sTab + "Node " + (iNode + m_nStartNode));
+	            m_VaryNodes[iNode].print(sTab);
+	          }
+          } else {
+              System.out.println(m_Instances);
+          }
+        }
+        
+        /**
+         * for testing only
+         * 
+         * @param argv the commandline options
+         */
+        public static void main(String [] argv) {
+            try {
+                Instances instances = new Instances(new FileReader("\\iris.2.arff"));
+                ADNode ADTree = ADNode.makeADTree(instances);
+                int [] nCounts = new int[12];
+                int [] nNodes = new int[3];
+                int [] nOffsets = new int[3];
+                nNodes[0] = 0;
+                nNodes[1] = 3;
+                nNodes[2] = 4;
+                nOffsets[0] = 2;
+                nOffsets[1] = 1;
+                nOffsets[2] = 4;
+                ADTree.print();
+                ADTree.getCounts(nCounts, nNodes, nOffsets,0, 0, false); 
+                
+            } catch (Throwable t) {
+                t.printStackTrace();
+            }
+        } // main
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 1.7 $");
+        }
+} // class ADNode
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BIFReader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BIFReader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BIFReader.java	(revision 29)
@@ -0,0 +1,643 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BIFReader.java
+ * Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.estimate.DiscreteEstimatorBayes;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.estimators.Estimator;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ <!-- globalinfo-start -->
+ * Builds a description of a Bayes Net classifier stored in XML BIF 0.3 format.<br/>
+ * <br/>
+ * For more details on XML BIF see:<br/>
+ * <br/>
+ * Fabio Cozman, Marek Druzdzel, Daniel Garcia (1998). XML BIF version 0.3. URL http://www-2.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Cozman1998,
+ *    author = {Fabio Cozman and Marek Druzdzel and Daniel Garcia},
+ *    title = {XML BIF version 0.3},
+ *    year = {1998},
+ *    URL = {http://www-2.cs.cmu.edu/\~fgcozman/Research/InterchangeFormat/}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Do not use ADTree data structure
+ * </pre>
+ * 
+ * <pre> -B &lt;BIF file&gt;
+ *  BIF file to compare with
+ * </pre>
+ * 
+ * <pre> -Q weka.classifiers.bayes.net.search.SearchAlgorithm
+ *  Search algorithm
+ * </pre>
+ * 
+ * <pre> -E weka.classifiers.bayes.net.estimate.SimpleEstimator
+ *  Estimator algorithm
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.15 $
+ */
+public class BIFReader 
+    extends BayesNet
+    implements TechnicalInformationHandler {
+  
+    protected int [] m_nPositionX;
+    protected int [] m_nPositionY;
+    private int [] m_order;
+    
+    /** for serialization */
+    static final long serialVersionUID = -8358864680379881429L;
+
+    /**
+     * This will return a string describing the classifier.
+     * @return The string.
+     */
+    public String globalInfo() {
+        return 
+            "Builds a description of a Bayes Net classifier stored in XML "
+        + "BIF 0.3 format.\n\n"
+        + "For more details on XML BIF see:\n\n"
+        + getTechnicalInformation().toString();
+    }
+
+	/** processFile reads a BIFXML file and initializes a Bayes Net
+	 * @param sFile name of the file to parse
+	 * @return the BIFReader
+	 * @throws Exception if processing fails
+	 */
+	public BIFReader processFile(String sFile) throws Exception {
+		m_sFile = sFile;
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(true);
+        Document doc = factory.newDocumentBuilder().parse(new File(sFile));
+        doc.normalize();
+
+        buildInstances(doc, sFile);
+        buildStructure(doc);
+        return this;
+	} // processFile
+
+	public BIFReader processString(String sStr) throws Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(true);
+		Document doc = factory.newDocumentBuilder().parse(new org.xml.sax.InputSource(new StringReader(sStr)));
+        doc.normalize();
+        buildInstances(doc, "from-string");
+        buildStructure(doc);
+        return this;
+	} // processString
+	
+	
+	/** the current filename */
+	String m_sFile;
+	
+	/**
+	 * returns the current filename
+	 * 
+	 * @return the current filename
+	 */
+	public String getFileName() {
+	  return m_sFile;
+	}
+	
+	
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  
+	  result = new TechnicalInformation(Type.MISC);
+	  result.setValue(Field.AUTHOR, "Fabio Cozman and Marek Druzdzel and Daniel Garcia");
+	  result.setValue(Field.YEAR, "1998");
+	  result.setValue(Field.TITLE, "XML BIF version 0.3");
+	  result.setValue(Field.URL, "http://www-2.cs.cmu.edu/~fgcozman/Research/InterchangeFormat/");
+	  
+	  return result;
+	}
+	
+	/** buildStructure parses the BIF document in the DOM tree contained
+	 * in the doc parameter and specifies the the network structure and 
+	 * probability tables.
+	 * It assumes that buildInstances has been called before
+	 * @param doc DOM document containing BIF document in DOM tree
+	 * @throws Exception if building of structure fails
+	 */
+    void buildStructure(Document doc)  throws Exception {
+        // Get the name of the network
+		// initialize conditional distribution tables
+		m_Distributions = new Estimator[m_Instances.numAttributes()][];
+        for (int iNode = 0; iNode < m_Instances.numAttributes(); iNode++) {
+        	// find definition that goes with this node
+        	String sName = m_Instances.attribute(iNode).name();
+			Element definition = getDefinition(doc, sName);
+/*
+	        if (nodelist.getLength() == 0) {
+	        	throw new Exception("No definition found for node " + sName);
+	        }
+	        if (nodelist.getLength() > 1) {
+	        	System.err.println("More than one definition found for node " + sName + ". Using first definition.");
+	        }
+	        Element definition = (Element) nodelist.item(0);
+*/	        
+	        
+	        // get the parents for this node
+	        // resolve structure
+	        FastVector nodelist = getParentNodes(definition);
+	        for (int iParent = 0; iParent < nodelist.size(); iParent++) {
+	        	Node parentName = ((Node) nodelist.elementAt(iParent)).getFirstChild();
+	        	String sParentName = ((CharacterData) (parentName)).getData();
+	        	int nParent = getNode(sParentName);
+	        	m_ParentSets[iNode].addParent(nParent, m_Instances);
+	        }
+	        // resolve conditional probability table
+		        int nCardinality = m_ParentSets[iNode].getCardinalityOfParents();
+	        int nValues = m_Instances.attribute(iNode).numValues();
+	        m_Distributions[iNode] = new Estimator[nCardinality];
+			for (int i = 0; i < nCardinality; i++) {
+				m_Distributions[iNode][i] = new DiscreteEstimatorBayes(nValues, 0.0f);
+			}
+
+/*
+	        StringBuffer sTable = new StringBuffer();
+	        for (int iText = 0; iText < nodelist.getLength(); iText++) {
+	        	sTable.append(((CharacterData) (nodelist.item(iText))).getData());
+	        	sTable.append(' ');
+	        }
+	        StringTokenizer st = new StringTokenizer(sTable.toString());
+*/
+	        String sTable = getTable(definition);
+			StringTokenizer st = new StringTokenizer(sTable.toString());
+	        
+	        
+			for (int i = 0; i < nCardinality; i++) {
+				DiscreteEstimatorBayes d = (DiscreteEstimatorBayes) m_Distributions[iNode][i];
+				for (int iValue = 0; iValue < nValues; iValue++) {
+					String sWeight = st.nextToken();
+					d.addValue(iValue, new Double(sWeight).doubleValue());
+				}
+			}
+         }
+    } // buildStructure
+
+    /** synchronizes the node ordering of this Bayes network with
+     * those in the other network (if possible).
+     * @param other Bayes network to synchronize with
+     * @throws Exception if nr of attributes differs or not all of the variables have the same name.
+     */
+    public void Sync(BayesNet other) throws Exception {
+    	int nAtts = m_Instances.numAttributes();
+    	if (nAtts != other.m_Instances.numAttributes()) {
+    		throw new Exception ("Cannot synchronize networks: different number of attributes.");
+    	}
+        m_order = new int[nAtts];
+        for (int iNode = 0; iNode < nAtts; iNode++) {
+        	String sName = other.getNodeName(iNode);
+        	m_order[getNode(sName)] = iNode;
+        }
+    } // Sync
+
+
+    /**
+     * Returns all TEXT children of the given node in one string. Between
+     * the node values new lines are inserted.
+     * 
+     * @param node the node to return the content for
+     * @return the content of the node
+     */
+    public String getContent(Element node) {
+      NodeList       list;
+      Node           item;
+      int            i;
+      String         result;
+      
+      result = "";
+      list   = node.getChildNodes();
+      
+      for (i = 0; i < list.getLength(); i++) {
+         item = list.item(i);
+         if (item.getNodeType() == Node.TEXT_NODE)
+            result += "\n" + item.getNodeValue();
+      }
+         
+      return result;
+    }
+
+
+	/** buildInstances parses the BIF document and creates a Bayes Net with its 
+	 * nodes specified, but leaves the network structure and probability tables empty.
+	 * @param doc DOM document containing BIF document in DOM tree
+	 * @param sName default name to give to the Bayes Net. Will be overridden if specified in the BIF document.
+	 * @throws Exception if building fails
+	 */
+	void buildInstances(Document doc, String sName) throws Exception {
+		NodeList nodelist;
+        // Get the name of the network
+        nodelist = selectAllNames(doc);
+        if (nodelist.getLength() > 0) {
+        	sName = ((CharacterData) (nodelist.item(0).getFirstChild())).getData();
+        }
+
+        // Process variables
+        nodelist = selectAllVariables(doc);
+		int nNodes = nodelist.getLength();
+		// initialize structure
+		FastVector attInfo = new FastVector(nNodes);
+
+        // Initialize
+        m_nPositionX = new int[nodelist.getLength()];
+        m_nPositionY = new int[nodelist.getLength()];
+
+        // Process variables
+        for (int iNode = 0; iNode < nodelist.getLength(); iNode++) {
+            // Get element
+			FastVector valueslist;
+	        // Get the name of the network
+    	    valueslist = selectOutCome(nodelist.item(iNode));
+
+			int nValues = valueslist.size();
+			// generate value strings
+	        FastVector nomStrings = new FastVector(nValues + 1);
+	        for (int iValue = 0; iValue < nValues; iValue++) {
+	        	Node node = ((Node) valueslist.elementAt(iValue)).getFirstChild();
+	        	String sValue = ((CharacterData) (node)).getData();
+	        	if (sValue == null) {
+	        		sValue = "Value" + (iValue + 1);
+	        	}
+				nomStrings.addElement(sValue);
+	        }
+			FastVector nodelist2;
+	        // Get the name of the network
+    	    nodelist2 = selectName(nodelist.item(iNode));
+    	    if (nodelist2.size() == 0) {
+    	    	throw new Exception ("No name specified for variable");
+    	    }
+    	    String sNodeName = ((CharacterData) (((Node) nodelist2.elementAt(0)).getFirstChild())).getData();
+
+			weka.core.Attribute att = new weka.core.Attribute(sNodeName, nomStrings);
+			attInfo.addElement(att);
+
+    	    valueslist = selectProperty(nodelist.item(iNode));
+			nValues = valueslist.size();
+			// generate value strings
+	        for (int iValue = 0; iValue < nValues; iValue++) {
+                // parsing for strings of the form "position = (73, 165)"
+	        	Node node = ((Node)valueslist.elementAt(iValue)).getFirstChild();
+	        	String sValue = ((CharacterData) (node)).getData();
+                if (sValue.startsWith("position")) {
+                    int i0 = sValue.indexOf('(');
+                    int i1 = sValue.indexOf(',');
+                    int i2 = sValue.indexOf(')');
+                    String sX = sValue.substring(i0 + 1, i1).trim();
+                    String sY = sValue.substring(i1 + 1, i2).trim();
+                    try {
+                    	m_nPositionX[iNode] = (int) Integer.parseInt(sX);
+                    	m_nPositionY[iNode] = (int) Integer.parseInt(sY);
+                    } catch (NumberFormatException e) {
+                    	System.err.println("Wrong number format in position :(" + sX + "," + sY +")");
+                   	    m_nPositionX[iNode] = 0;
+                   	    m_nPositionY[iNode] = 0;
+                    }
+                }
+            }
+
+        }
+        
+ 		m_Instances = new Instances(sName, attInfo, 100);
+		m_Instances.setClassIndex(nNodes - 1);
+		setUseADTree(false);
+		initStructure();
+	} // buildInstances
+
+//	/** selectNodeList selects list of nodes from document specified in XPath expression
+//	 * @param doc : document (or node) to query
+//	 * @param sXPath : XPath expression
+//	 * @return list of nodes conforming to XPath expression in doc
+//	 * @throws Exception
+//	 */
+//	private NodeList selectNodeList(Node doc, String sXPath) throws Exception {
+//		NodeList nodelist = org.apache.xpath.XPathAPI.selectNodeList(doc, sXPath);
+//		return nodelist;
+//	} // selectNodeList
+
+	NodeList selectAllNames(Document doc) throws Exception {
+		//NodeList nodelist = selectNodeList(doc, "//NAME");
+		NodeList nodelist = doc.getElementsByTagName("NAME");
+		return nodelist;
+	} // selectAllNames
+
+	NodeList selectAllVariables(Document doc) throws Exception {
+		//NodeList nodelist = selectNodeList(doc, "//VARIABLE");
+		NodeList nodelist = doc.getElementsByTagName("VARIABLE");
+		return nodelist;
+	} // selectAllVariables
+
+	Element getDefinition(Document doc, String sName) throws Exception {
+		//NodeList nodelist = selectNodeList(doc, "//DEFINITION[normalize-space(FOR/text())=\"" + sName + "\"]");
+
+		NodeList nodelist = doc.getElementsByTagName("DEFINITION");
+		for (int iNode = 0; iNode < nodelist.getLength(); iNode++) {
+			Node node = nodelist.item(iNode);
+			FastVector list = selectElements(node, "FOR");
+			if (list.size() > 0) {
+				Node forNode = (Node) list.elementAt(0);
+				if (getContent((Element) forNode).trim().equals(sName)) {
+					return (Element) node;
+				}
+			}
+		}
+		throw new Exception("Could not find definition for ((" + sName + "))");
+	} // getDefinition
+
+	FastVector getParentNodes(Node definition) throws Exception {
+		//NodeList nodelist = selectNodeList(definition, "GIVEN");
+		FastVector nodelist = selectElements(definition, "GIVEN");
+		return nodelist;
+	} // getParentNodes
+
+	String getTable(Node definition) throws Exception {
+		//NodeList nodelist = selectNodeList(definition, "TABLE/text()");
+		FastVector nodelist = selectElements(definition, "TABLE");
+		String sTable = getContent((Element) nodelist.elementAt(0));
+		sTable = sTable.replaceAll("\\n"," ");
+		return sTable;
+	} // getTable
+
+	FastVector selectOutCome(Node item) throws Exception {
+		//NodeList nodelist = selectNodeList(item, "OUTCOME");
+		FastVector nodelist = selectElements(item, "OUTCOME");
+		return nodelist;
+	} // selectOutCome
+
+	FastVector selectName(Node item) throws Exception {
+	   //NodeList nodelist = selectNodeList(item, "NAME");
+	   FastVector nodelist = selectElements(item, "NAME");
+	   return nodelist;
+   } // selectName
+
+   FastVector selectProperty(Node item) throws Exception {
+	  // NodeList nodelist = selectNodeList(item, "PROPERTY");
+	  FastVector nodelist = selectElements(item, "PROPERTY");
+	  return nodelist;
+   } // selectProperty
+
+	FastVector selectElements(Node item, String sElement) throws Exception {
+	  NodeList children = item.getChildNodes();
+	  FastVector nodelist = new FastVector();
+	  for (int iNode = 0; iNode < children.getLength(); iNode++) {
+		Node node = children.item(iNode);
+		if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals(sElement)) {
+			nodelist.addElement(node);
+		}
+	  }
+	  return nodelist;
+  } // selectElements
+	/** Count nr of arcs missing from other network compared to current network
+	 * Note that an arc is not 'missing' if it is reversed.
+	 * @param other network to compare with
+	 * @return nr of missing arcs
+	 */
+	public int missingArcs(BayesNet other) {
+		try {
+			Sync(other);
+			int nMissing = 0;
+			for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+				for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+					int nParent = m_ParentSets[iAttribute].getParent(iParent);
+					if (!other.getParentSet(m_order[iAttribute]).contains(m_order[nParent]) && !other.getParentSet(m_order[nParent]).contains(m_order[iAttribute])) {
+						nMissing++;
+					}
+				}
+			}
+			return nMissing;
+		} catch (Exception e) {
+			System.err.println(e.getMessage());
+			return 0;
+		}
+	} // missingArcs
+
+	/** Count nr of exta arcs  from other network compared to current network
+	 * Note that an arc is not 'extra' if it is reversed.
+	 * @param other network to compare with
+	 * @return nr of missing arcs
+	 */
+	public int extraArcs(BayesNet other) {
+		try {
+			Sync(other);
+			int nExtra = 0;
+			for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+				for (int iParent = 0; iParent < other.getParentSet(m_order[iAttribute]).getNrOfParents(); iParent++) {
+					int nParent = m_order[other.getParentSet(m_order[iAttribute]).getParent(iParent)];
+					if (!m_ParentSets[iAttribute].contains(nParent) && !m_ParentSets[nParent].contains(iAttribute)) {
+						nExtra++;
+					}
+				}
+			}
+			return nExtra;
+		} catch (Exception e) {
+			System.err.println(e.getMessage());
+			return 0;
+		}
+	} // extraArcs
+
+
+	/** calculates the divergence between the probability distribution
+	 * represented by this network and that of another, that is,
+	 * \sum_{x\in X} P(x)log P(x)/Q(x)
+	 * where X is the set of values the nodes in the network can take,
+	 * P(x) the probability of this network for configuration x
+	 * Q(x) the probability of the other network for configuration x
+	 * @param other network to compare with
+	 * @return divergence between this and other Bayes Network
+	 */
+	public double divergence(BayesNet other) {
+		try {
+			Sync(other);
+			// D: divergence
+			double D = 0.0;
+			int nNodes = m_Instances.numAttributes();
+			int [] nCard = new int[nNodes];
+			for (int iNode = 0; iNode < nNodes; iNode++) {
+				nCard[iNode] = m_Instances.attribute(iNode).numValues();
+			}
+			// x: holds current configuration of nodes
+			int [] x = new int[nNodes];
+			// simply sum over all configurations to calc divergence D
+			int i = 0;
+			while (i < nNodes) {
+				// update configuration
+				x[i]++;
+				while (i < nNodes && x[i] == m_Instances.attribute(i).numValues()) {
+					x[i] = 0;
+					i++;
+					if (i < nNodes){
+						x[i]++;
+					}
+				}
+				if (i < nNodes) {
+					i = 0;
+					// calc P(x) and Q(x)
+					double P = 1.0;
+					for (int iNode = 0; iNode < nNodes; iNode++) {
+						int iCPT = 0;
+						for (int iParent = 0; iParent < m_ParentSets[iNode].getNrOfParents(); iParent++) {
+					    	int nParent = m_ParentSets[iNode].getParent(iParent);
+						    iCPT = iCPT * nCard[nParent] + x[nParent];
+						} 
+						P = P * m_Distributions[iNode][iCPT].getProbability(x[iNode]);
+					}
+	
+					double Q = 1.0;
+					for (int iNode = 0; iNode < nNodes; iNode++) {
+						int iCPT = 0;
+						for (int iParent = 0; iParent < other.getParentSet(m_order[iNode]).getNrOfParents(); iParent++) {
+					    	int nParent = m_order[other.getParentSet(m_order[iNode]).getParent(iParent)];
+						    iCPT = iCPT * nCard[nParent] + x[nParent];
+						} 
+						Q = Q * other.m_Distributions[m_order[iNode]][iCPT].getProbability(x[iNode]);
+					}
+	
+					// update divergence if probabilities are positive
+					if (P > 0.0 && Q > 0.0) {
+						D = D + P * Math.log(Q / P);
+					}
+				}
+			}
+			return D;
+		} catch (Exception e) {
+			System.err.println(e.getMessage());
+			return 0;
+		}
+	} // divergence
+
+	/** Count nr of reversed arcs from other network compared to current network
+	 * @param other network to compare with
+	 * @return nr of missing arcs
+	 */
+	public int reversedArcs(BayesNet other) {
+		try {
+			Sync(other);
+			int nReversed = 0;
+		    for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+				for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+					int nParent = m_ParentSets[iAttribute].getParent(iParent);
+					if (!other.getParentSet(m_order[iAttribute]).contains(m_order[nParent]) && other.getParentSet(m_order[nParent]).contains(m_order[iAttribute])) {
+						nReversed++;
+					}
+				}
+			}
+			return nReversed;
+		} catch (Exception e) {
+			System.err.println(e.getMessage());
+			return 0;
+		}
+	} // reversedArcs
+	/** getNode finds the index of the node with name sNodeName
+	 * and throws an exception if no such node can be found.
+	 * @param sNodeName name of the node to get the index from
+	 * @return index of the node with name sNodeName
+	 * @throws Exception if node cannot be found
+	 */
+    public int getNode(String sNodeName) throws Exception {
+		int iNode = 0;
+		while (iNode < m_Instances.numAttributes()) {
+			if (m_Instances.attribute(iNode).name().equals(sNodeName)) {
+				return iNode;
+			}
+			iNode++;
+		}
+   		throw new Exception("Could not find node [[" + sNodeName + "]]");
+    } // getNode
+
+    /**
+     * the default constructor
+     */
+    public BIFReader() {
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.15 $");
+    }
+
+    /**
+     * Loads the file specified as first parameter and prints it to stdout.
+     * 
+     * @param args the command line parameters
+     */
+    public static void main(String[] args) {
+        try {
+            BIFReader br = new BIFReader();
+            br.processFile(args[0]);
+	    System.out.println(br.toString());
+        
+        }
+        catch (Throwable t) {
+            t.printStackTrace();
+        }
+    } // main
+} // class BIFReader
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BayesNetGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BayesNetGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/BayesNetGenerator.java	(revision 29)
@@ -0,0 +1,614 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNet.java
+ * Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net;
+
+import weka.classifiers.bayes.net.estimate.DiscreteEstimatorBayes;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.DenseInstance;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.estimators.Estimator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Bayes Network learning using various search algorithms and quality measures.<br/>
+ * Base class for a Bayes Network classifier. Provides datastructures (network structure, conditional probability distributions, etc.) and facilities common to Bayes Network learning algorithms like K2 and B.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * http://www.cs.waikato.ac.nz/~remco/weka.pdf
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B
+ *  Generate network (instead of instances)
+ * </pre>
+ * 
+ * <pre> -N &lt;integer&gt;
+ *  Nr of nodes
+ * </pre>
+ * 
+ * <pre> -A &lt;integer&gt;
+ *  Nr of arcs
+ * </pre>
+ * 
+ * <pre> -M &lt;integer&gt;
+ *  Nr of instances
+ * </pre>
+ * 
+ * <pre> -C &lt;integer&gt;
+ *  Cardinality of the variables
+ * </pre>
+ * 
+ * <pre> -S &lt;integer&gt;
+ *  Seed for random number generator
+ * </pre>
+ * 
+ * <pre> -F &lt;file&gt;
+ *  The BIF file to obtain the structure from.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 5987 $
+ */
+public class BayesNetGenerator extends EditableBayesNet {
+    /** the seed value */
+    int m_nSeed = 1;
+    
+    /** the random number generator */
+    Random random;
+    
+    /** for serialization */
+    static final long serialVersionUID = -7462571170596157720L;
+
+	/**
+	 * Constructor for BayesNetGenerator.
+	 */
+	public BayesNetGenerator() {
+		super();
+	} // c'tor
+
+	/** 
+	 * Generate random connected Bayesian network with discrete nodes
+	 * having all the same cardinality.
+	 * 
+	 * @throws Exception if something goes wrong
+	 */
+	public void generateRandomNetwork () throws Exception {
+		if (m_otherBayesNet == null) {
+			// generate from scratch
+			Init(m_nNrOfNodes, m_nCardinality);
+			generateRandomNetworkStructure(m_nNrOfNodes, m_nNrOfArcs);
+			generateRandomDistributions(m_nNrOfNodes, m_nCardinality);
+		} else {
+			// read from file, just copy parent sets and distributions
+			m_nNrOfNodes = m_otherBayesNet.getNrOfNodes();
+			m_ParentSets = m_otherBayesNet.getParentSets();
+			m_Distributions = m_otherBayesNet.getDistributions();
+
+
+			random = new Random(m_nSeed);
+			// initialize m_Instances
+			FastVector attInfo = new FastVector(m_nNrOfNodes);
+			// generate value strings
+
+			for (int iNode = 0; iNode < m_nNrOfNodes; iNode++) {
+				int nValues = m_otherBayesNet.getCardinality(iNode);
+				FastVector nomStrings = new FastVector(nValues + 1);
+				for (int iValue = 0; iValue < nValues; iValue++) {
+					nomStrings.addElement(m_otherBayesNet.getNodeValue(iNode, iValue));
+				}
+				Attribute att = new Attribute(m_otherBayesNet.getNodeName(iNode), nomStrings);
+				attInfo.addElement(att);
+			}
+
+			m_Instances = new Instances(m_otherBayesNet.getName(), attInfo, 100);
+			m_Instances.setClassIndex(m_nNrOfNodes - 1);
+		}
+	} // GenerateRandomNetwork
+
+	/** 
+	 * Init defines a minimal Bayes net with no arcs
+	 * @param nNodes number of nodes in the Bayes net 
+	 * @param nValues number of values each of the nodes can take
+	 * @throws Exception if something goes wrong
+	 */
+	public void Init(int nNodes, int nValues) throws Exception {
+		random = new Random(m_nSeed);
+		// initialize structure
+		FastVector attInfo = new FastVector(nNodes);
+		// generate value strings
+        FastVector nomStrings = new FastVector(nValues + 1);
+        for (int iValue = 0; iValue < nValues; iValue++) {
+			nomStrings.addElement("Value" + (iValue + 1));
+        }
+
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			Attribute att = new Attribute("Node" + (iNode + 1), nomStrings);
+			attInfo.addElement(att);
+		}
+ 		m_Instances = new Instances("RandomNet", attInfo, 100);
+ 		m_Instances.setClassIndex(nNodes - 1);
+		setUseADTree(false);
+// 		m_bInitAsNaiveBayes = false;
+// 		m_bMarkovBlanketClassifier = false;
+		initStructure();
+		
+		// initialize conditional distribution tables
+		m_Distributions = new Estimator[nNodes][1];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			m_Distributions[iNode][0] = 
+			  new DiscreteEstimatorBayes(nValues, getEstimator().getAlpha());
+		}
+		m_nEvidence = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			m_nEvidence.addElement(-1);
+		}
+		m_fMarginP = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			double[] P = new double[getCardinality(i)];
+			m_fMarginP.addElement(P);
+		}
+
+		m_nPositionX = new FastVector(nNodes);
+		m_nPositionY = new FastVector(nNodes);
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			m_nPositionX.addElement(iNode%10 * 50);
+			m_nPositionY.addElement(((int)(iNode/10)) * 50);
+		}
+	} // DefineNodes
+
+	/** 
+	 * GenerateRandomNetworkStructure generate random connected Bayesian network 
+	 * @param nNodes number of nodes in the Bayes net to generate
+	 * @param nArcs number of arcs to generate. Must be between nNodes - 1 and nNodes * (nNodes-1) / 2
+	 * @throws Exception if number of arcs is incorrect
+	 */
+	public void generateRandomNetworkStructure(int nNodes, int nArcs) 
+		throws Exception
+	{
+		if (nArcs < nNodes - 1) {
+			throw new Exception("Number of arcs should be at least (nNodes - 1) = " + (nNodes - 1) + " instead of " + nArcs);
+		}
+		if (nArcs > nNodes * (nNodes - 1) / 2) {
+			throw new Exception("Number of arcs should be at most nNodes * (nNodes - 1) / 2 = "+ (nNodes * (nNodes - 1) / 2) + " instead of " + nArcs);
+		}
+		if (nArcs == 0) {return;} // deal with  patalogical case for nNodes = 1
+
+	    // first generate tree connecting all nodes
+	    generateTree(nNodes);
+	    // The tree contains nNodes - 1 arcs, so there are 
+	    // nArcs - (nNodes-1) to add at random.
+	    // All arcs point from lower to higher ordered nodes
+	    // so that acyclicity is ensured.
+	    for (int iArc = nNodes - 1; iArc < nArcs; iArc++) {
+	    	boolean bDone = false;
+	    	while (!bDone) {
+				int nNode1 = random.nextInt(nNodes);
+				int nNode2 = random.nextInt(nNodes);
+				if (nNode1 == nNode2) {nNode2 = (nNode1 + 1) % nNodes;}
+				if (nNode2 < nNode1) {int h = nNode1; nNode1 = nNode2; nNode2 = h;}
+				if (!m_ParentSets[nNode2].contains(nNode1)) {
+					m_ParentSets[nNode2].addParent(nNode1, m_Instances);
+					bDone = true;
+				}
+	    	}
+	    }
+
+	} // GenerateRandomNetworkStructure
+	
+	/** 
+	 * GenerateTree creates a tree-like network structure (actually a
+	 * forest) by starting with a randomly selected pair of nodes, add 
+	 * an arc between. Then keep on selecting one of the connected nodes 
+	 * and one of the unconnected ones and add an arrow between them, 
+	 * till all nodes are connected.
+	 * @param nNodes number of nodes in the Bayes net to generate
+	 */
+	void generateTree(int nNodes) {
+        boolean [] bConnected = new boolean [nNodes];
+        // start adding an arc at random
+		int nNode1 = random.nextInt(nNodes);
+		int nNode2 = random.nextInt(nNodes);
+		if (nNode1 == nNode2) {nNode2 = (nNode1 + 1) % nNodes;}
+		if (nNode2 < nNode1) {int h = nNode1; nNode1 = nNode2; nNode2 = h;}
+		m_ParentSets[nNode2].addParent(nNode1, m_Instances);
+		bConnected[nNode1] = true;
+		bConnected[nNode2] = true;
+		// Repeatedly, select one of the connected nodes, and one of 
+		// the unconnected nodes and add an arc.
+	    // All arcs point from lower to higher ordered nodes
+	    // so that acyclicity is ensured.
+		for (int iArc = 2; iArc < nNodes; iArc++ ) {
+			int nNode = random.nextInt(nNodes);
+			nNode1 = 0; //  one of the connected nodes
+			while (nNode >= 0) {
+				nNode1 = (nNode1 + 1) % nNodes;
+				while (!bConnected[nNode1]) {
+					nNode1 = (nNode1 + 1) % nNodes;
+				}
+				nNode--;
+			}
+			nNode = random.nextInt(nNodes);
+			nNode2 = 0; //  one of the unconnected nodes
+			while (nNode >= 0) {
+				nNode2 = (nNode2 + 1) % nNodes;
+				while (bConnected[nNode2]) {
+					nNode2 = (nNode2 + 1) % nNodes;
+				}
+				nNode--;
+			}
+			if (nNode2 < nNode1) {int h = nNode1; nNode1 = nNode2; nNode2 = h;}
+			m_ParentSets[nNode2].addParent(nNode1, m_Instances);
+			bConnected[nNode1] = true;
+			bConnected[nNode2] = true;
+		}
+	} // GenerateTree
+	
+	/** 
+	 * GenerateRandomDistributions generates discrete conditional distribution tables
+	 * for all nodes of a Bayes network once a network structure has been determined.
+	 * @param nNodes number of nodes in the Bayes net 
+	 * @param nValues number of values each of the nodes can take
+	 */
+    void generateRandomDistributions(int nNodes, int nValues) {
+	    // Reserve space for CPTs
+    	int nMaxParentCardinality = 1;
+	    for (int iAttribute = 0; iAttribute < nNodes; iAttribute++) {
+            if (m_ParentSets[iAttribute].getCardinalityOfParents() > nMaxParentCardinality) {
+	             nMaxParentCardinality = m_ParentSets[iAttribute].getCardinalityOfParents();
+            } 
+        } 
+
+        // Reserve plenty of memory
+        m_Distributions = new Estimator[m_Instances.numAttributes()][nMaxParentCardinality];
+
+        // estimate CPTs
+        for (int iAttribute = 0; iAttribute < nNodes; iAttribute++) {
+        	int [] nPs = new int [nValues + 1];
+        	nPs[0] = 0;
+        	nPs[nValues] = 1000;
+            for (int iParent = 0; iParent < m_ParentSets[iAttribute].getCardinalityOfParents(); iParent++) {
+            	// fill array with random nr's
+            	for (int iValue = 1; iValue < nValues; iValue++)  {
+            		nPs[iValue] = random.nextInt(1000);
+            	}
+            	// sort
+            	for (int iValue = 1; iValue < nValues; iValue++)  {
+	            	for (int iValue2 = iValue + 1; iValue2 < nValues; iValue2++)  {
+	            		if (nPs[iValue2] < nPs[iValue]) {
+	            			int h = nPs[iValue2]; nPs[iValue2] = nPs[iValue]; nPs[iValue] = h;
+	            		}
+	            	}
+            	}
+            	// assign to probability tables
+            	DiscreteEstimatorBayes d = new DiscreteEstimatorBayes(nValues, getEstimator().getAlpha());
+            	for (int iValue = 0; iValue < nValues; iValue++)  {
+            		d.addValue(iValue, nPs[iValue + 1] - nPs[iValue]);
+            	}
+	            m_Distributions[iAttribute][iParent] = d;
+            } 
+        } 
+    } // GenerateRandomDistributions
+    
+	/**
+	 * GenerateInstances generates random instances sampling from the
+	 * distribution represented by the Bayes network structure. It assumes
+	 * a Bayes network structure has been initialized
+	 * 
+	 * @throws Exception if something goes wrong
+	 */
+	public void generateInstances () throws Exception {
+	    int [] order = getOrder();
+		for (int iInstance = 0; iInstance < m_nNrOfInstances; iInstance++) {
+		    int nNrOfAtts = m_Instances.numAttributes();
+			Instance instance = new DenseInstance(nNrOfAtts);
+			instance.setDataset(m_Instances);
+			for (int iAtt2 = 0; iAtt2 < nNrOfAtts; iAtt2++) {
+			    int iAtt = order[iAtt2];
+
+				double iCPT = 0;
+
+				for (int iParent = 0; iParent < m_ParentSets[iAtt].getNrOfParents(); iParent++) {
+				  int nParent = m_ParentSets[iAtt].getParent(iParent);
+				  iCPT = iCPT * m_Instances.attribute(nParent).numValues() + instance.value(nParent);
+				} 
+	
+				double fRandom = random.nextInt(1000) / 1000.0f;
+				int iValue = 0;
+				while (fRandom > m_Distributions[iAtt][(int) iCPT].getProbability(iValue)) {
+					fRandom = fRandom - m_Distributions[iAtt][(int) iCPT].getProbability(iValue);
+					iValue++ ;
+				}
+				instance.setValue(iAtt, iValue);
+			}
+			m_Instances.add(instance);
+		}
+	} // GenerateInstances
+
+    /**
+     * @throws Exception if there's a cycle in the graph
+     */	
+    int [] getOrder() throws Exception {
+	int nNrOfAtts = m_Instances.numAttributes();
+	int [] order = new int[nNrOfAtts];
+	boolean [] bDone = new boolean[nNrOfAtts];
+	for (int iAtt = 0; iAtt < nNrOfAtts; iAtt++) {
+	    int iAtt2 = 0; 
+	    boolean allParentsDone = false;
+	    while (!allParentsDone && iAtt2 < nNrOfAtts) {
+		if (!bDone[iAtt2]) {
+		    allParentsDone = true;
+		    int iParent = 0;
+		    while (allParentsDone && iParent < m_ParentSets[iAtt2].getNrOfParents()) {
+			allParentsDone = bDone[m_ParentSets[iAtt2].getParent(iParent++)];
+		    }
+		    if (allParentsDone && iParent == m_ParentSets[iAtt2].getNrOfParents()) {
+			order[iAtt] = iAtt2;
+			bDone[iAtt2] = true;
+		    } else {
+			iAtt2++;
+		    }
+		} else {
+		    iAtt2++;
+		}
+	    }
+	    if (!allParentsDone && iAtt2 == nNrOfAtts) {
+		throw new Exception("There appears to be a cycle in the graph");
+	    }
+	}
+	return order;
+    } // getOrder
+
+    	/**
+    	 * Returns either the net (if BIF format) or the generated instances
+    	 * 
+    	 * @return either the net or the generated instances
+    	 */
+  	public String toString() {
+  	  if (m_bGenerateNet) {
+  	    return toXMLBIF03();
+  	  }
+  	  return m_Instances.toString();
+  	} // toString
+  	
+
+	boolean m_bGenerateNet = false;
+	int m_nNrOfNodes = 10;
+	int m_nNrOfArcs = 10;
+	int m_nNrOfInstances = 10;
+	int m_nCardinality = 2;
+	String m_sBIFFile = "";
+
+	void setNrOfNodes(int nNrOfNodes) {m_nNrOfNodes = nNrOfNodes;}
+	void setNrOfArcs(int nNrOfArcs) {m_nNrOfArcs = nNrOfArcs;}
+	void setNrOfInstances(int nNrOfInstances) {m_nNrOfInstances = nNrOfInstances;}
+	void setCardinality(int nCardinality) {m_nCardinality = nCardinality;}
+	void setSeed(int nSeed) {m_nSeed = nSeed;}
+
+	/**
+	 * Returns an enumeration describing the available options
+	 * 
+	 * @return an enumeration of all the available options
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(6);
+
+		newVector.addElement(new Option("\tGenerate network (instead of instances)\n", "B", 0, "-B"));
+		newVector.addElement(new Option("\tNr of nodes\n", "N", 1, "-N <integer>"));
+		newVector.addElement(new Option("\tNr of arcs\n", "A", 1, "-A <integer>"));
+		newVector.addElement(new Option("\tNr of instances\n", "M", 1, "-M <integer>"));
+		newVector.addElement(new Option("\tCardinality of the variables\n", "C", 1, "-C <integer>"));
+		newVector.addElement(new Option("\tSeed for random number generator\n", "S", 1, "-S <integer>"));
+		newVector.addElement(new Option("\tThe BIF file to obtain the structure from.\n", "F", 1, "-F <file>"));
+
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 * 
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -B
+	 *  Generate network (instead of instances)
+	 * </pre>
+	 * 
+	 * <pre> -N &lt;integer&gt;
+	 *  Nr of nodes
+	 * </pre>
+	 * 
+	 * <pre> -A &lt;integer&gt;
+	 *  Nr of arcs
+	 * </pre>
+	 * 
+	 * <pre> -M &lt;integer&gt;
+	 *  Nr of instances
+	 * </pre>
+	 * 
+	 * <pre> -C &lt;integer&gt;
+	 *  Cardinality of the variables
+	 * </pre>
+	 * 
+	 * <pre> -S &lt;integer&gt;
+	 *  Seed for random number generator
+	 * </pre>
+	 * 
+	 * <pre> -F &lt;file&gt;
+	 *  The BIF file to obtain the structure from.
+	 * </pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @exception Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		m_bGenerateNet = Utils.getFlag('B', options);
+
+		String sNrOfNodes = Utils.getOption('N', options);
+		if (sNrOfNodes.length() != 0) {
+		  setNrOfNodes(Integer.parseInt(sNrOfNodes));
+		} else {
+			setNrOfNodes(10);
+		} 
+
+		String sNrOfArcs = Utils.getOption('A', options);
+		if (sNrOfArcs.length() != 0) {
+		  setNrOfArcs(Integer.parseInt(sNrOfArcs));
+		} else {
+			setNrOfArcs(10);
+		} 
+
+		String sNrOfInstances = Utils.getOption('M', options);
+		if (sNrOfInstances.length() != 0) {
+		  setNrOfInstances(Integer.parseInt(sNrOfInstances));
+		} else {
+			setNrOfInstances(10);
+		} 
+
+		String sCardinality = Utils.getOption('C', options);
+		if (sCardinality.length() != 0) {
+		  setCardinality(Integer.parseInt(sCardinality));
+		} else {
+			setCardinality(2);
+		} 
+
+		String sSeed = Utils.getOption('S', options);
+		if (sSeed.length() != 0) {
+		  setSeed(Integer.parseInt(sSeed));
+		} else {
+			setSeed(1);
+		} 
+
+		String sBIFFile = Utils.getOption('F', options);
+		if ((sBIFFile != null) && (sBIFFile != "")) {
+			setBIFFile(sBIFFile);
+		}
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the classifier.
+	 * 
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] options = new String[13];
+		int current = 0;
+		if (m_bGenerateNet) {
+		  options[current++] = "-B";
+		} 
+
+		options[current++] = "-N";
+		options[current++] = "" + m_nNrOfNodes;
+
+		options[current++] = "-A";
+		options[current++] = "" + m_nNrOfArcs;
+
+		options[current++] = "-M";
+		options[current++] = "" + m_nNrOfInstances;
+
+		options[current++] = "-C";
+		options[current++] = "" + m_nCardinality;
+
+		options[current++] = "-S";
+		options[current++] = "" + m_nSeed;
+
+                if (m_sBIFFile.length() != 0) {
+                  options[current++] = "-F";
+                  options[current++] = "" + m_sBIFFile;
+                }
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+
+		return options;
+	} // getOptions
+
+    /**
+     * prints all the options to stdout
+     */
+    protected static void printOptions(OptionHandler o) {
+      Enumeration enm = o.listOptions();
+      
+      System.out.println("Options for " + o.getClass().getName() + ":\n");
+      
+      while (enm.hasMoreElements()) {
+        Option option = (Option) enm.nextElement();
+        System.out.println(option.synopsis());
+        System.out.println(option.description());
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+
+    /**
+     * Main method
+     * 
+     * @param args the commandline parameters
+     */
+    static public void main(String [] args) {
+		BayesNetGenerator b = new BayesNetGenerator();
+    	try {
+		if ( (args.length == 0) || (Utils.getFlag('h', args)) ) {
+                        printOptions(b);
+                        return;
+		}
+	    	b.setOptions(args);
+	    	
+	    	b.generateRandomNetwork();
+	    	if (!b.m_bGenerateNet) { // skip if not required
+				b.generateInstances();
+	    	}
+	    	System.out.println(b.toString());
+    	} catch (Exception e) {
+    		e.printStackTrace();
+    		printOptions(b);
+    	}
+    } // main
+    
+} // class BayesNetGenerator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/EditableBayesNet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/EditableBayesNet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/EditableBayesNet.java	(revision 29)
@@ -0,0 +1,2670 @@
+package weka.classifiers.bayes.net;
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * EditableBayesNet.java
+ *
+ */
+
+import java.io.Serializable;
+import java.io.StringReader;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.estimate.DiscreteEstimatorBayes;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.estimators.Estimator;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Reorder;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Bayes Network learning using various search algorithms and quality measures.<br/>
+ * Base class for a Bayes Network classifier. Provides datastructures (network structure, conditional probability distributions, etc.) and facilities common to Bayes Network learning algorithms like K2 and B.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * http://www.cs.waikato.ac.nz/~remco/weka.pdf
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Do not use ADTree data structure
+ * </pre>
+ * 
+ * <pre> -B &lt;BIF file&gt;
+ *  BIF file to compare with
+ * </pre>
+ * 
+ * <pre> -Q weka.classifiers.bayes.net.search.SearchAlgorithm
+ *  Search algorithm
+ * </pre>
+ * 
+ * <pre> -E weka.classifiers.bayes.net.estimate.SimpleEstimator
+ *  Estimator algorithm
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 4899 $
+ */
+
+public class EditableBayesNet extends BayesNet {
+	/** for serialization */
+	static final long serialVersionUID = 746037443258735954L;
+
+	/** location of nodes, used for graph drawing * */
+	protected FastVector m_nPositionX;
+
+	protected FastVector m_nPositionY;
+
+	/** marginal distributions * */
+	protected FastVector m_fMarginP;
+
+	/** evidence values, used for evidence propagation * */
+	protected FastVector m_nEvidence;
+
+	/** standard constructor * */
+	public EditableBayesNet() {
+		super();
+		m_nEvidence = new FastVector(0);
+		m_fMarginP = new FastVector(0);
+		m_nPositionX = new FastVector();
+		m_nPositionY = new FastVector();
+		clearUndoStack();
+	} // c'tor
+
+	/** constructor, creates empty network with nodes based on the attributes in a data set */
+	public EditableBayesNet(Instances instances) {
+		try {
+			if (instances.classIndex() < 0) {
+				instances.setClassIndex(instances.numAttributes() - 1);
+			}
+			m_Instances = normalizeDataSet(instances);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		int nNodes = getNrOfNodes();
+		m_ParentSets = new ParentSet[nNodes];
+		for (int i = 0; i < nNodes; i++) {
+			m_ParentSets[i] = new ParentSet();
+		}
+		m_Distributions = new Estimator[nNodes][];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			m_Distributions[iNode] = new Estimator[1];
+			m_Distributions[iNode][0] = new DiscreteEstimatorBayes(getCardinality(iNode), 0.5);
+		}
+
+		m_nEvidence = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			m_nEvidence.addElement(-1);
+		}
+		m_fMarginP = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			double[] P = new double[getCardinality(i)];
+			m_fMarginP.addElement(P);
+		}
+
+		m_nPositionX = new FastVector(nNodes);
+		m_nPositionY = new FastVector(nNodes);
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			m_nPositionX.addElement(iNode%10 * 50);
+			m_nPositionY.addElement(((int)(iNode/10)) * 50);
+		}
+
+	} // c'tor
+
+	/** constructor, copies Bayesian network structure from a Bayesian network
+	 * encapsulated in a BIFReader
+	 */
+	public EditableBayesNet(BIFReader other) {
+		m_Instances = other.m_Instances;
+		m_ParentSets = other.getParentSets();
+		m_Distributions = other.getDistributions();
+
+		int nNodes = getNrOfNodes();
+		m_nPositionX = new FastVector(nNodes);
+		m_nPositionY = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			m_nPositionX.addElement(other.m_nPositionX[i]);
+			m_nPositionY.addElement(other.m_nPositionY[i]);
+		}
+		m_nEvidence = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			m_nEvidence.addElement(-1);
+		}
+		m_fMarginP = new FastVector(nNodes);
+		for (int i = 0; i < nNodes; i++) {
+			double[] P = new double[getCardinality(i)];
+			m_fMarginP.addElement(P);
+		}
+		clearUndoStack();
+	} // c'tor
+
+	/**
+	 * constructor that potentially initializes instances as well
+	 *
+	 * @param bSetInstances
+	 *            flag indicating whether to initialize instances or not
+	 */
+	public EditableBayesNet(boolean bSetInstances) {
+		super();
+		m_nEvidence = new FastVector(0);
+		m_fMarginP = new FastVector(0);
+		m_nPositionX = new FastVector();
+		m_nPositionY = new FastVector();
+		clearUndoStack();
+		if (bSetInstances) {
+			m_Instances = new Instances("New Network", new FastVector(0), 0);
+		}
+	} // c'tor
+
+
+	/** Assuming a network structure is defined and we want to learn from data,
+	 * the data set must be put if correct order first and possibly discretized/missing
+	 * values filled in before proceeding to CPT learning.
+	 * @param instances data set to learn from
+	 * @exception Exception when data sets are not compatible, e.g., a variable is missing
+	 * or a variable has different nr of values.
+	 */
+	public void setData(Instances instances) throws Exception {
+		// sync order of variables
+		int [] order = new int [getNrOfNodes()];
+		for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+			String sName = getNodeName(iNode);
+			int nNode = 0;
+			while (nNode < getNrOfNodes() && !sName.equals(instances.attribute(nNode).name())) {
+				nNode++;
+			}
+			if (nNode >= getNrOfNodes()) {
+				throw new Exception("Cannot find node named [[[" + sName + "]]] in the data");
+			}
+			order[iNode] = nNode;
+		}
+		Reorder reorderFilter = new Reorder();
+		reorderFilter.setAttributeIndicesArray(order);
+		reorderFilter.setInputFormat(instances);
+		instances = Filter.useFilter(instances, reorderFilter);
+		// filter using discretization/missing values filter
+		Instances newInstances = new Instances(m_Instances, 0);
+		if (m_DiscretizeFilter == null && m_MissingValuesFilter == null) {
+			newInstances = normalizeDataSet(instances);
+		} else {
+			for (int iInstance = 0; iInstance < instances.numInstances(); iInstance++) {
+				newInstances.add(normalizeInstance(instances.instance(iInstance)));
+			}
+		}
+		//sanity check
+		for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+			if (newInstances.attribute(iNode).numValues() != getCardinality(iNode)) {
+				throw new Exception("Number of values of node [[[" + getNodeName(iNode) + "]]] differs in (discretized) dataset." );
+			}
+		}
+		// if we got this far, all is ok with the data set and
+		// we can replace data set of Bayes net
+		m_Instances = newInstances;
+	} // setData
+
+	/** returns index of node with given name, or -1 if no such node exists
+	 * @param sNodeName name of the node to get index for
+	 */
+	public int getNode2(String sNodeName) {
+		int iNode = 0;
+		while (iNode < m_Instances.numAttributes()) {
+			if (m_Instances.attribute(iNode).name().equals(sNodeName)) {
+				return iNode;
+			}
+			iNode++;
+		}
+		return -1;
+	} // getNode2
+
+	/** returns index of node with given name. Throws exception if no such node exists
+	 * @param sNodeName name of the node to get index for
+	 */
+	public int getNode(String sNodeName) throws Exception {
+		int iNode = getNode2(sNodeName);
+		if (iNode < 0) {
+			throw new Exception("Could not find node [[" + sNodeName + "]]");
+		}
+		return iNode;
+	} // getNode
+
+	/**
+	 * Add new node to the network, initializing instances, parentsets,
+	 * distributions. Used for manual manipulation of the Bayesian network.
+	 *
+	 * @param sName
+	 *            name of the node. If the name already exists, an x is appended
+	 *            to the name
+	 * @param nCardinality
+	 *            number of values for this node
+	 * @throws Exception
+	 */
+	public void addNode(String sName, int nCardinality) throws Exception {
+		addNode(sName, nCardinality, 100 + getNrOfNodes() * 10, 100 + getNrOfNodes() * 10);
+	} // addNode
+
+	/** Add node to network at a given position, initializing instances, parentsets,
+	 * distributions. Used for manual manipulation of the Bayesian network.
+	 *
+	 * @param sName
+	 *            name of the node. If the name already exists, an x is appended
+	 *            to the name
+	 * @param nCardinality
+	 *            number of values for this node
+	 * @param nPosX x-coordiate of the position to place this node
+	 * @param nPosY y-coordiate of the position to place this node
+	 * @throws Exception
+	 */
+	public void addNode(String sName, int nCardinality, int nPosX, int nPosY) throws Exception {
+		if (getNode2(sName) >= 0) {
+			addNode(sName + "x", nCardinality);
+			return ;
+		}
+		// update instances
+		FastVector values = new FastVector(nCardinality);
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			values.addElement("Value" + (iValue + 1));
+		}
+		Attribute att = new Attribute(sName, values);
+		m_Instances.insertAttributeAt(att, m_Instances.numAttributes());
+		int nAtts = m_Instances.numAttributes();
+		// update parentsets
+		ParentSet[] parentSets = new ParentSet[nAtts];
+		for (int iParentSet = 0; iParentSet < nAtts - 1; iParentSet++) {
+			parentSets[iParentSet] = m_ParentSets[iParentSet];
+		}
+		parentSets[nAtts - 1] = new ParentSet();
+		m_ParentSets = parentSets;
+		// update distributions
+		Estimator[][] distributions = new Estimator[nAtts][];
+		for (int iNode = 0; iNode < nAtts - 1; iNode++) {
+			distributions[iNode] = m_Distributions[iNode];
+		}
+		distributions[nAtts - 1] = new Estimator[1];
+		distributions[nAtts - 1][0] = new DiscreteEstimatorBayes(nCardinality, 0.5);
+		m_Distributions = distributions;
+		// update positions
+		m_nPositionX.addElement(nPosX);
+		m_nPositionY.addElement(nPosY);
+		// update evidence & margins
+		m_nEvidence.addElement(-1);
+		double[] fMarginP = new double[nCardinality];
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			fMarginP[iValue] = 1.0 / nCardinality;
+		}
+		m_fMarginP.addElement(fMarginP);
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new AddNodeAction(sName, nCardinality, nPosX, nPosY));
+		}
+	} // addNode
+
+	/**
+	 * Delete node from the network, updating instances, parentsets,
+	 * distributions Conditional distributions are condensed by taking the
+	 * values for the target node to be its first value. Used for manual
+	 * manipulation of the Bayesian network.
+	 *
+	 * @param sName
+	 *            name of the node. If the name does not exists an exception is
+	 *            thrown
+	 * @throws Exception
+	 */
+	public void deleteNode(String sName) throws Exception {
+		int nTargetNode = getNode(sName);
+		deleteNode(nTargetNode);
+	} // deleteNode
+
+	/**
+	 * Delete node from the network, updating instances, parentsets,
+	 * distributions Conditional distributions are condensed by taking the
+	 * values for the target node to be its first value. Used for manual
+	 * manipulation of the Bayesian network.
+	 *
+	 * @param nTargetNode
+	 *            index of the node to delete.
+	 * @throws Exception
+	 */
+	public void deleteNode(int nTargetNode) throws Exception {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new DeleteNodeAction(nTargetNode));
+		}
+		int nAtts = m_Instances.numAttributes() - 1;
+		int nTargetCard = m_Instances.attribute(nTargetNode).numValues();
+		// update distributions
+		Estimator[][] distributions = new Estimator[nAtts][];
+		for (int iNode = 0; iNode < nAtts; iNode++) {
+			int iNode2 = iNode;
+			if (iNode >= nTargetNode) {
+				iNode2++;
+			}
+			Estimator[] distribution = m_Distributions[iNode2];
+			if (m_ParentSets[iNode2].contains(nTargetNode)) {
+				// condense distribution, use values for targetnode = 0
+				int nParentCard = m_ParentSets[iNode2].getCardinalityOfParents();
+				nParentCard = nParentCard / nTargetCard;
+				Estimator[] distribution2 = new Estimator[nParentCard];
+				for (int iParent = 0; iParent < nParentCard; iParent++) {
+					distribution2[iParent] = distribution[iParent];
+				}
+				distribution = distribution2;
+			}
+			distributions[iNode] = distribution;
+		}
+		m_Distributions = distributions;
+		// update parentsets
+		ParentSet[] parentSets = new ParentSet[nAtts];
+		for (int iParentSet = 0; iParentSet < nAtts; iParentSet++) {
+			int iParentSet2 = iParentSet;
+			if (iParentSet >= nTargetNode) {
+				iParentSet2++;
+			}
+			ParentSet parentset = m_ParentSets[iParentSet2];
+			parentset.deleteParent(nTargetNode, m_Instances);
+			for (int iParent = 0; iParent < parentset.getNrOfParents(); iParent++) {
+				int nParent = parentset.getParent(iParent);
+				if (nParent > nTargetNode) {
+					parentset.SetParent(iParent, nParent - 1);
+				}
+			}
+			parentSets[iParentSet] = parentset;
+		}
+		m_ParentSets = parentSets;
+		// update instances
+		m_Instances.setClassIndex(-1);
+		m_Instances.deleteAttributeAt(nTargetNode);
+		m_Instances.setClassIndex(nAtts - 1);
+
+		// update positions
+		m_nPositionX.removeElementAt(nTargetNode);
+		m_nPositionY.removeElementAt(nTargetNode);
+		// update evidence & margins
+		m_nEvidence.removeElementAt(nTargetNode);
+		m_fMarginP.removeElementAt(nTargetNode);
+	} // deleteNode
+
+	/**
+	 * Delete nodes with indexes in selection from the network, updating instances, parentsets,
+	 * distributions Conditional distributions are condensed by taking the
+	 * values for the target node to be its first value. Used for manual
+	 * manipulation of the Bayesian network.
+	 *
+	 * @param nodes
+	 *            array of indexes of nodes to delete.
+	 * @throws Exception
+	 */
+	public void deleteSelection(FastVector nodes) {
+		// sort before proceeding
+		for (int i = 0; i < nodes.size(); i++) {
+			for (int j = i + 1; j < nodes.size(); j++) {
+				if ((Integer) nodes.elementAt(i) > (Integer) nodes.elementAt(j)) {
+					int h = (Integer) nodes.elementAt(i);
+					nodes.setElementAt(nodes.elementAt(j), i);
+					nodes.setElementAt(h, j);
+				}
+			}
+		}
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new DeleteSelectionAction(nodes));
+		}
+		boolean bNeedsUndoAction = m_bNeedsUndoAction;
+		m_bNeedsUndoAction = false;
+		try {
+			for (int iNode = nodes.size() - 1; iNode >= 0; iNode--) {
+				deleteNode((Integer) nodes.elementAt(iNode));
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		m_bNeedsUndoAction = bNeedsUndoAction;
+	} // deleteSelection
+
+	/** XML helper function for selecting elements under a node with a given name
+	 * @param item XMLNode to select items from
+	 * @param sElement name of the element to return
+	 */
+	FastVector selectElements(Node item, String sElement) throws Exception {
+		NodeList children = item.getChildNodes();
+		FastVector nodelist = new FastVector();
+		for (int iNode = 0; iNode < children.getLength(); iNode++) {
+			Node node = children.item(iNode);
+			if ((node.getNodeType() == Node.ELEMENT_NODE) && node.getNodeName().equals(sElement)) {
+				nodelist.addElement(node);
+			}
+		}
+		return nodelist;
+	} // selectElements
+
+	/**
+	 * XML helper function. Returns all TEXT children of the given node in one string. Between the
+	 * node values new lines are inserted.
+	 *
+	 * @param node
+	 *            the node to return the content for
+	 * @return the content of the node
+	 */
+	public String getContent(Element node) {
+		NodeList list;
+		Node item;
+		int i;
+		String result;
+
+		result = "";
+		list = node.getChildNodes();
+
+		for (i = 0; i < list.getLength(); i++) {
+			item = list.item(i);
+			if (item.getNodeType() == Node.TEXT_NODE)
+				result += "\n" + item.getNodeValue();
+		}
+
+		return result;
+	}
+
+	/** XML helper function that returns DEFINITION element from a XMLBIF document
+	 * for a node with a given name.
+	 * @param doc XMLBIF document
+	 * @param sName name of the node to get the definition for
+	 */
+	Element getDefinition(Document doc, String sName) throws Exception {
+		NodeList nodelist = doc.getElementsByTagName("DEFINITION");
+		for (int iNode = 0; iNode < nodelist.getLength(); iNode++) {
+			Node node = nodelist.item(iNode);
+			FastVector list = selectElements(node, "FOR");
+			if (list.size() > 0) {
+				Node forNode = (Node) list.elementAt(0);
+				if (getContent((Element) forNode).trim().equals(sName)) {
+					return (Element) node;
+				}
+			}
+		}
+		throw new Exception("Could not find definition for ((" + sName + "))");
+	} // getDefinition
+
+
+	/** Paste modes. This allows for verifying that a past action does not cause
+	 * any problems before actually performing the paste operation.
+	 */
+	final static int TEST = 0;
+	final static int EXECUTE = 1;
+
+	/** Apply paste operation with XMLBIF fragment. This adds nodes in the XMLBIF fragment
+	 * to the network, together with its parents. First, paste in test mode to verify
+	 * no problems occur, then execute paste operation. If a problem occurs (e.g. parent
+	 * does not exist) then a exception is thrown.
+	 * @param sXML XMLBIF fragment to paste into the network
+	 */
+	public void paste(String sXML) throws Exception {
+		try {
+			paste(sXML, TEST);
+		} catch (Exception e) {
+			throw e;
+		}
+		paste(sXML, EXECUTE);
+	} // paste
+
+	/** Apply paste operation with XMLBIF fragment. Depending on the paste mode, the
+	 * nodes are actually added to the network or it is just tested that the nodes can
+	 * be added to the network.
+	 * @param sXML XMLBIF fragment to paste into the network
+	 * @param mode paste mode TEST or EXECUTE
+	 */
+	void paste(String sXML, int mode) throws Exception {
+		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+		factory.setValidating(true);
+		Document doc = factory.newDocumentBuilder().parse(new org.xml.sax.InputSource(new StringReader(sXML)));
+		doc.normalize();
+
+		// create nodes first
+		NodeList nodelist = doc.getElementsByTagName("VARIABLE");
+		FastVector sBaseNames = new FastVector();
+		Instances instances = new Instances(m_Instances, 0);
+		int nBase = instances.numAttributes();
+		for (int iNode = 0; iNode < nodelist.getLength(); iNode++) {
+			// Get element
+			FastVector valueslist;
+			// Get the name of the node
+			valueslist = selectElements(nodelist.item(iNode), "OUTCOME");
+
+			int nValues = valueslist.size();
+			// generate value strings
+			FastVector nomStrings = new FastVector(nValues + 1);
+			for (int iValue = 0; iValue < nValues; iValue++) {
+				Node node = ((Node) valueslist.elementAt(iValue)).getFirstChild();
+				String sValue = ((CharacterData) (node)).getData();
+				if (sValue == null) {
+					sValue = "Value" + (iValue + 1);
+				}
+				nomStrings.addElement(sValue);
+			}
+			FastVector nodelist2;
+			// Get the name of the network
+			nodelist2 = selectElements(nodelist.item(iNode), "NAME");
+			if (nodelist2.size() == 0) {
+				throw new Exception("No name specified for variable");
+			}
+			String sBaseName = ((CharacterData) (((Node) nodelist2.elementAt(0)).getFirstChild())).getData();
+			sBaseNames.addElement(sBaseName);
+			String sNodeName = sBaseName;
+			if (getNode2(sNodeName) >= 0) {
+				sNodeName = "Copy of " + sBaseName;
+			}
+			int iAttempt = 2;
+			while (getNode2(sNodeName) >= 0) {
+				sNodeName = "Copy (" + iAttempt + ") of " + sBaseName;
+				iAttempt++;
+			}
+
+			Attribute att = new Attribute(sNodeName, nomStrings);
+			instances.insertAttributeAt(att, instances.numAttributes());
+
+			valueslist = selectElements(nodelist.item(iNode), "PROPERTY");
+			nValues = valueslist.size();
+			// generate value strings
+			int nPosX = iAttempt * 10;
+			int nPosY = iAttempt * 10;
+			for (int iValue = 0; iValue < nValues; iValue++) {
+				// parsing for strings of the form "position = (73, 165)"
+				Node node = ((Node) valueslist.elementAt(iValue)).getFirstChild();
+				String sValue = ((CharacterData) (node)).getData();
+				if (sValue.startsWith("position")) {
+					int i0 = sValue.indexOf('(');
+					int i1 = sValue.indexOf(',');
+					int i2 = sValue.indexOf(')');
+					String sX = sValue.substring(i0 + 1, i1).trim();
+					String sY = sValue.substring(i1 + 1, i2).trim();
+					try {
+						nPosX = (Integer.parseInt(sX) + iAttempt * 10);
+						nPosY = (Integer.parseInt(sY) + iAttempt * 10);
+					} catch (NumberFormatException e) {
+						System.err.println("Wrong number format in position :(" + sX + "," + sY + ")");
+					}
+				}
+			}
+			if (mode == EXECUTE) {
+				m_nPositionX.addElement(nPosX);
+				m_nPositionY.addElement(nPosY);
+			}
+
+		}
+
+		FastVector nodelist2;
+		Estimator[][] distributions = new Estimator[nBase + sBaseNames.size()][];
+		ParentSet[] parentsets = new ParentSet[nBase + sBaseNames.size()];
+		for (int iNode = 0; iNode < nBase; iNode++) {
+			distributions[iNode] = m_Distributions[iNode];
+			parentsets[iNode] = m_ParentSets[iNode];
+		}
+		if (mode == EXECUTE) {
+			m_Instances = instances;
+		}
+		// create arrows & create distributions
+		for (int iNode = 0; iNode < sBaseNames.size(); iNode++) {
+			// find definition that goes with this node
+			String sName = (String) sBaseNames.elementAt(iNode);
+			Element definition = getDefinition(doc, sName);
+			parentsets[nBase + iNode] = new ParentSet();
+
+			// get the parents for this node
+			// resolve structure
+			nodelist2 = selectElements(definition, "GIVEN");
+			for (int iParent = 0; iParent < nodelist2.size(); iParent++) {
+				Node parentName = ((Node) nodelist2.elementAt(iParent)).getFirstChild();
+				String sParentName = ((CharacterData) (parentName)).getData();
+				int nParent = -1;
+				for (int iBase = 0; iBase < sBaseNames.size(); iBase++) {
+					if (sParentName.equals((String) sBaseNames.elementAt(iBase))) {
+						nParent = nBase + iBase;
+					}
+				}
+				if (nParent < 0) {
+					nParent = getNode(sParentName);
+				}
+				parentsets[nBase + iNode].addParent(nParent, instances);
+			}
+			// resolve conditional probability table
+			int nCardinality = parentsets[nBase + iNode].getCardinalityOfParents();
+			int nValues = instances.attribute(nBase + iNode).numValues();
+			distributions[nBase + iNode] = new Estimator[nCardinality];
+			for (int i = 0; i < nCardinality; i++) {
+				distributions[nBase + iNode][i] = new DiscreteEstimatorBayes(nValues, 0.0f);
+			}
+
+			String sTable = getContent((Element) selectElements(definition, "TABLE").elementAt(0));
+			sTable = sTable.replaceAll("\\n", " ");
+			StringTokenizer st = new StringTokenizer(sTable.toString());
+
+			for (int i = 0; i < nCardinality; i++) {
+				DiscreteEstimatorBayes d = (DiscreteEstimatorBayes) distributions[nBase + iNode][i];
+				for (int iValue = 0; iValue < nValues; iValue++) {
+					String sWeight = st.nextToken();
+					d.addValue(iValue, new Double(sWeight).doubleValue());
+				}
+			}
+			if (mode == EXECUTE) {
+				m_nEvidence.insertElementAt(-1, nBase + iNode);
+				m_fMarginP.insertElementAt(new double[getCardinality(nBase + iNode)], nBase + iNode);
+			}
+		}
+		if (mode == EXECUTE) {
+			m_Distributions = distributions;
+			m_ParentSets = parentsets;
+		}
+		// update undo stack
+		if (mode == EXECUTE && m_bNeedsUndoAction) {
+			addUndoAction(new PasteAction(sXML, nBase));
+		}
+	} // paste
+
+	/**
+	 * Add arc between two nodes Distributions are updated by duplication for
+	 * every value of the parent node.
+	 *
+	 * @param sParent
+	 *            name of the parent node
+	 * @param sChild
+	 *            name of the child node
+	 * @throws Exception
+	 *             if parent or child cannot be found in network
+	 */
+	public void addArc(String sParent, String sChild) throws Exception {
+		int nParent = getNode(sParent);
+		int nChild = getNode(sChild);
+		addArc(nParent, nChild);
+	} // addArc
+
+	/**
+	 * Add arc between two nodes Distributions are updated by duplication for
+	 * every value of the parent node.
+	 *
+	 * @param nParent
+	 *            index of the parent node
+	 * @param nChild
+	 *            index of the child node
+	 * @throws Exception
+	 */
+	public void addArc(int nParent, int nChild) throws Exception {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new AddArcAction(nParent, nChild));
+		}
+		int nOldCard = m_ParentSets[nChild].getCardinalityOfParents();
+		// update parentsets
+		m_ParentSets[nChild].addParent(nParent, m_Instances);
+		// update distributions
+		int nNewCard = m_ParentSets[nChild].getCardinalityOfParents();
+		Estimator[] ds = new Estimator[nNewCard];
+		for (int iParent = 0; iParent < nNewCard; iParent++) {
+			ds[iParent] = Estimator.clone(m_Distributions[nChild][iParent % nOldCard]);
+		}
+		m_Distributions[nChild] = ds;
+	} // addArc
+
+	/**
+	 * Add arc between parent node and each of the nodes in a given list.
+	 * Distributions are updated as above.
+	 *
+	 * @param sParent
+	 *            name of the parent node
+	 * @param nodes
+	 *            array of indexes of child nodes
+	 * @throws Exception
+	 */
+	public void addArc(String sParent, FastVector nodes) throws Exception {
+		int nParent = getNode(sParent);
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new AddArcAction(nParent, nodes));
+		}
+		boolean bNeedsUndoAction = m_bNeedsUndoAction;
+		m_bNeedsUndoAction = false;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			addArc(nParent, nNode);
+		}
+		m_bNeedsUndoAction = bNeedsUndoAction;
+	} // addArc
+
+	/**
+	 * Delete arc between two nodes. Distributions are updated by condensing for
+	 * the parent node taking its first value.
+	 *
+	 * @param sParent
+	 *            name of the parent node
+	 * @param sChild
+	 *            name of the child node
+	 * @throws Exception
+	 *             if parent or child cannot be found in network
+	 */
+	public void deleteArc(String sParent, String sChild) throws Exception {
+		int nParent = getNode(sParent);
+		int nChild = getNode(sChild);
+		deleteArc(nParent, nChild);
+	} // deleteArc
+
+	/**
+	 * Delete arc between two nodes. Distributions are updated by condensing for
+	 * the parent node taking its first value.
+	 *
+	 * @param nParent
+	 *            index of the parent node
+	 * @param nChild
+	 *            index of the child node
+	 * @throws Exception
+	 */
+	public void deleteArc(int nParent, int nChild) throws Exception {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new DeleteArcAction(nParent, nChild));
+		}
+		// update distributions
+		// condense distribution, use values for targetnode = 0
+		int nParentCard = m_ParentSets[nChild].getCardinalityOfParents();
+		int nTargetCard = m_Instances.attribute(nChild).numValues();
+		nParentCard = nParentCard / nTargetCard;
+		Estimator[] distribution2 = new Estimator[nParentCard];
+		for (int iParent = 0; iParent < nParentCard; iParent++) {
+			distribution2[iParent] = m_Distributions[nChild][iParent];
+		}
+		m_Distributions[nChild] = distribution2;
+		// update parentsets
+		m_ParentSets[nChild].deleteParent(nParent, m_Instances);
+	} // deleteArc
+
+
+	/** specify distribution of a node
+	 * @param sName name of the node to specify distribution for
+	 * @param P matrix representing distribution with P[i][j] = P(node = j | parent configuration = i)
+	 * @throws Exception
+	 *             if parent or child cannot be found in network
+	 */
+	public void setDistribution(String sName, double[][] P) throws Exception {
+		int nTargetNode = getNode(sName);
+		setDistribution(nTargetNode, P);
+	} // setDistribution
+
+	/** specify distribution of a node
+	 * @param nTargetNode index of the node to specify distribution for
+	 * @param P matrix representing distribution with P[i][j] = P(node = j | parent configuration = i)
+	 * @throws Exception
+	 *             if parent or child cannot be found in network
+	 */
+	public void setDistribution(int nTargetNode, double[][] P) throws Exception {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new SetDistributionAction(nTargetNode, P));
+		}
+		Estimator[] distributions = m_Distributions[nTargetNode];
+		for (int iParent = 0; iParent < distributions.length; iParent++) {
+			DiscreteEstimatorBayes distribution = new DiscreteEstimatorBayes(P[0].length, 0);
+			for (int iValue = 0; iValue < distribution.getNumSymbols(); iValue++) {
+				distribution.addValue(iValue, P[iParent][iValue]);
+			}
+			distributions[iParent] = distribution;
+		}
+		// m_Distributions[nTargetNode] = distributions;
+	} // setDistribution
+
+	/** returns distribution of a node in matrix form with matrix representing distribution
+	 * with P[i][j] = P(node = j | parent configuration = i)
+	 * @param sName name of the node to get distribution from
+	 */
+	public double[][] getDistribution(String sName) {
+		int nTargetNode = getNode2(sName);
+		return getDistribution(nTargetNode);
+	} // getDistribution
+
+	/** returns distribution of a node in matrix form with matrix representing distribution
+	 * with P[i][j] = P(node = j | parent configuration = i)
+	 * @param nTargetNode index of the node to get distribution from
+	 */
+	public double[][] getDistribution(int nTargetNode) {
+		int nParentCard = m_ParentSets[nTargetNode].getCardinalityOfParents();
+		int nCard = m_Instances.attribute(nTargetNode).numValues();
+		double[][] P = new double[nParentCard][nCard];
+		for (int iParent = 0; iParent < nParentCard; iParent++) {
+			for (int iValue = 0; iValue < nCard; iValue++) {
+				P[iParent][iValue] = m_Distributions[nTargetNode][iParent].getProbability(iValue);
+			}
+		}
+		return P;
+	} // getDistribution
+
+	/** returns array of values of a node
+	 * @param sName name of the node to get values from
+	 */
+	public String[] getValues(String sName) {
+		int nTargetNode = getNode2(sName);
+		return getValues(nTargetNode);
+	} // getValues
+
+	/** returns array of values of a node
+	 * @param nTargetNode index of the node to get values from
+	 */
+	public String[] getValues(int nTargetNode) {
+		String[] values = new String[getCardinality(nTargetNode)];
+		for (int iValue = 0; iValue < values.length; iValue++) {
+			values[iValue] = m_Instances.attribute(nTargetNode).value(iValue);
+		}
+		return values;
+	} // getValues
+
+	/** returns value of a node
+	 * @param nTargetNode index of the node to get values from
+	 * @param iValue index of the value
+	 */
+	public String getValueName(int nTargetNode, int iValue) {
+		return m_Instances.attribute(nTargetNode).value(iValue);
+	} // getNodeValue
+
+	/** change the name of a node
+	 * @param nTargetNode index of the node to set name for
+	 * @param sName new name to assign
+	 */
+	public void setNodeName(int nTargetNode, String sName) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new RenameAction(nTargetNode, getNodeName(nTargetNode), sName));
+		}
+		Attribute att = m_Instances.attribute(nTargetNode);
+		int nCardinality = att.numValues();
+		FastVector values = new FastVector(nCardinality);
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			values.addElement(att.value(iValue));
+		}
+		replaceAtt(nTargetNode, sName, values);
+	} // setNodeName
+
+	/** change the name of a value of a node
+	 * @param nTargetNode index of the node to set name for
+	 * @param sValue current name of the value
+	 * @param sNewValue new name of the value
+	 */
+	public void renameNodeValue(int nTargetNode, String sValue, String sNewValue) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new RenameValueAction(nTargetNode, sValue, sNewValue));
+		}
+		Attribute att = m_Instances.attribute(nTargetNode);
+		int nCardinality = att.numValues();
+		FastVector values = new FastVector(nCardinality);
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			if (att.value(iValue).equals(sValue)) {
+				values.addElement(sNewValue);
+			} else {
+				values.addElement(att.value(iValue));
+			}
+		}
+		replaceAtt(nTargetNode, att.name(), values);
+	} // renameNodeValue
+
+
+	/** Add node value to a node. Distributions for the node assign zero probability
+	 * to the new value. Child nodes duplicate CPT conditioned on the new value.
+	 * @param nTargetNode index of the node to add value for
+	 * @param sNewValue name of the value
+	 */
+	public void addNodeValue(int nTargetNode, String sNewValue) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new AddValueAction(nTargetNode, sNewValue));
+		}
+		Attribute att = m_Instances.attribute(nTargetNode);
+		int nCardinality = att.numValues();
+		FastVector values = new FastVector(nCardinality);
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			values.addElement(att.value(iValue));
+		}
+		values.addElement(sNewValue);
+		replaceAtt(nTargetNode, att.name(), values);
+
+		// update distributions of this node
+		Estimator[] distributions = m_Distributions[nTargetNode];
+		int nNewCard = values.size();
+		for (int iParent = 0; iParent < distributions.length; iParent++) {
+			DiscreteEstimatorBayes distribution = new DiscreteEstimatorBayes(nNewCard, 0);
+			for (int iValue = 0; iValue < nNewCard - 1; iValue++) {
+				distribution.addValue(iValue, distributions[iParent].getProbability(iValue));
+			}
+			distributions[iParent] = distribution;
+		}
+
+		// update distributions of all children
+		for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+			if (m_ParentSets[iNode].contains(nTargetNode)) {
+				distributions = m_Distributions[iNode];
+				ParentSet parentSet = m_ParentSets[iNode];
+				int nParentCard = parentSet.getFreshCardinalityOfParents(m_Instances);
+				Estimator[] newDistributions = new Estimator[nParentCard];
+				int nCard = getCardinality(iNode);
+				int nParents = parentSet.getNrOfParents();
+				int[] values2 = new int[nParents];
+				int iOldPos = 0;
+				int iTargetNode = 0;
+				while (parentSet.getParent(iTargetNode) != nTargetNode) {
+					iTargetNode++;
+				}
+				for (int iPos = 0; iPos < nParentCard; iPos++) {
+					DiscreteEstimatorBayes distribution = new DiscreteEstimatorBayes(nCard, 0);
+					for (int iValue = 0; iValue < nCard; iValue++) {
+						distribution.addValue(iValue, distributions[iOldPos].getProbability(iValue));
+					}
+					newDistributions[iPos] = distribution;
+					// update values
+					int i = 0;
+					values2[i]++;
+					while (i < nParents && values2[i] == getCardinality(parentSet.getParent(i))) {
+						values2[i] = 0;
+						i++;
+						if (i < nParents) {
+							values2[i]++;
+						}
+					}
+					if (values2[iTargetNode] != nNewCard - 1) {
+						iOldPos++;
+					}
+				}
+				m_Distributions[iNode] = newDistributions;
+			}
+		}
+	} // addNodeValue
+
+
+	/** Delete node value from a node. Distributions for the node are scaled
+	 * up proportional to existing distribution
+	 * (or made uniform if zero probability is assigned to remainder of values).
+	.* Child nodes delete CPTs conditioned on the new value.
+	 * @param nTargetNode index of the node to delete value from
+	 * @param sValue name of the value to delete
+	 */
+	public void delNodeValue(int nTargetNode, String sValue) throws Exception {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new DelValueAction(nTargetNode, sValue));
+		}
+		Attribute att = m_Instances.attribute(nTargetNode);
+		int nCardinality = att.numValues();
+		FastVector values = new FastVector(nCardinality);
+		int nValue = -1;
+		for (int iValue = 0; iValue < nCardinality; iValue++) {
+			if (att.value(iValue).equals(sValue)) {
+				nValue = iValue;
+			} else {
+				values.addElement(att.value(iValue));
+			}
+		}
+		if (nValue < 0) {
+			// could not find value
+			throw new Exception("Node " + nTargetNode + " does not have value (" + sValue + ")");
+		}
+		replaceAtt(nTargetNode, att.name(), values);
+
+		// update distributions
+		Estimator[] distributions = m_Distributions[nTargetNode];
+		int nCard = values.size();
+		for (int iParent = 0; iParent < distributions.length; iParent++) {
+			DiscreteEstimatorBayes distribution = new DiscreteEstimatorBayes(nCard, 0);
+			double sum = 0;
+			for (int iValue = 0; iValue < nCard; iValue++) {
+				sum += distributions[iParent].getProbability(iValue);
+			}
+			if (sum > 0) {
+				for (int iValue = 0; iValue < nCard; iValue++) {
+					distribution.addValue(iValue, distributions[iParent].getProbability(iValue) / sum);
+				}
+			} else {
+				for (int iValue = 0; iValue < nCard; iValue++) {
+					distribution.addValue(iValue, 1.0 / nCard);
+				}
+			}
+			distributions[iParent] = distribution;
+		}
+
+		// update distributions of all children
+		for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+			if (m_ParentSets[iNode].contains(nTargetNode)) {
+				ParentSet parentSet = m_ParentSets[iNode];
+				distributions = m_Distributions[iNode];
+				Estimator[] newDistributions = new Estimator[distributions.length * nCard / (nCard + 1)];
+				int iCurrentDist = 0;
+
+				int nParents = parentSet.getNrOfParents();
+				int[] values2 = new int[nParents];
+				// fill in the values
+				int nParentCard = parentSet.getFreshCardinalityOfParents(m_Instances) * (nCard + 1) / nCard;
+				int iTargetNode = 0;
+				while (parentSet.getParent(iTargetNode) != nTargetNode) {
+					iTargetNode++;
+				}
+				int[] nCards = new int[nParents];
+				for (int iParent = 0; iParent < nParents; iParent++) {
+					nCards[iParent] = getCardinality(parentSet.getParent(iParent));
+				}
+				nCards[iTargetNode]++;
+				for (int iPos = 0; iPos < nParentCard; iPos++) {
+					if (values2[iTargetNode] != nValue) {
+						newDistributions[iCurrentDist++] = distributions[iPos];
+					}
+					// update values
+					int i = 0;
+					values2[i]++;
+					while (i < nParents && values2[i] == nCards[i]) {
+						values2[i] = 0;
+						i++;
+						if (i < nParents) {
+							values2[i]++;
+						}
+					}
+				}
+
+				m_Distributions[iNode] = newDistributions;
+			}
+		}
+		// update evidence
+		if (getEvidence(nTargetNode) > nValue) {
+			setEvidence(nTargetNode, getEvidence(nTargetNode) - 1);
+		}
+	} // delNodeValue
+
+	/** set position of node
+	 * @param iNode index of node to set position for
+	 * @param nX x position of new position
+	 * @param nY y position of new position
+	 */
+	public void setPosition(int iNode, int nX, int nY) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			boolean isUpdate = false;
+			UndoAction undoAction = null;
+			try {
+				if (m_undoStack.size() > 0) {
+					undoAction = (UndoAction) m_undoStack.elementAt(m_undoStack.size() - 1);
+					SetPositionAction posAction = (SetPositionAction) undoAction;
+					if (posAction.m_nTargetNode == iNode) {
+						isUpdate = true;
+						posAction.setUndoPosition(nX, nY);
+					}
+				}
+			} catch (Exception e) {
+				// ignore. it's not a SetPositionAction
+			}
+			if (!isUpdate) {
+				addUndoAction(new SetPositionAction(iNode, nX, nY));
+			}
+		}
+		m_nPositionX.setElementAt(nX, iNode);
+		m_nPositionY.setElementAt(nY, iNode);
+	} // setPosition
+
+	/** Set position of node. Move set of nodes with the same displacement
+	 * as a specified node.
+	 * @param nNode index of node to set position for
+	 * @param nX x position of new position
+	 * @param nY y position of new position
+	 * @param nodes array of indexes of nodes to move
+	 */
+	public void setPosition(int nNode, int nX, int nY, FastVector nodes) {
+		int dX = nX - getPositionX(nNode);
+		int dY = nY - getPositionY(nNode);
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			boolean isUpdate = false;
+			try {
+				UndoAction undoAction = null;
+				if (m_undoStack.size() > 0) {
+					undoAction = (UndoAction) m_undoStack.elementAt(m_undoStack.size() - 1);
+						SetGroupPositionAction posAction = (SetGroupPositionAction) undoAction;
+						isUpdate = true;
+						int iNode = 0;
+						while (isUpdate && iNode < posAction.m_nodes.size()) {
+							if ((Integer)posAction.m_nodes.elementAt(iNode) != (Integer) nodes.elementAt(iNode)) {
+								isUpdate = false;
+							}
+							iNode++;
+						}
+						if (isUpdate == true) {
+							posAction.setUndoPosition(dX, dY);
+						}
+				}
+			} catch (Exception e) {
+				// ignore. it's not a SetPositionAction
+			}
+			if (!isUpdate) {
+				addUndoAction(new SetGroupPositionAction(nodes, dX, dY));
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionX.setElementAt(getPositionX(nNode) + dX, nNode);
+			m_nPositionY.setElementAt(getPositionY(nNode) + dY, nNode);
+		}
+	} // setPosition
+
+	/** set positions of all nodes
+	 * @param nPosX new x positions for all nodes
+	 * @param nPosY new y positions for all nodes
+	 */
+	public void layoutGraph(FastVector nPosX, FastVector nPosY) {
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new LayoutGraphAction(nPosX, nPosY));
+		}
+		m_nPositionX = nPosX;
+		m_nPositionY = nPosY;
+	} // layoutGraph
+
+	/** get x position of a node
+	 * @param iNode index of node of interest
+	 */
+	public int getPositionX(int iNode) {
+		return (Integer) (m_nPositionX.elementAt(iNode));
+	}
+
+	/** get y position of a node
+	 * @param iNode index of node of interest
+	 */
+	public int getPositionY(int iNode) {
+		return (Integer) (m_nPositionY.elementAt(iNode));
+	}
+
+	/** align set of nodes with the left most node in the list
+	 * @param nodes list of indexes of nodes to align
+	 */
+	public void alignLeft(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new alignLeftAction(nodes));
+		}
+		int nMinX = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nX = getPositionX((Integer) nodes.elementAt(iNode));
+			if (nX < nMinX || iNode == 0) {
+				nMinX = nX;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionX.setElementAt(nMinX, nNode);
+		}
+	} // alignLeft
+
+	/** align set of nodes with the right most node in the list
+	 * @param nodes list of indexes of nodes to align
+	 */
+	public void alignRight(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new alignRightAction(nodes));
+		}
+		int nMaxX = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nX = getPositionX((Integer) nodes.elementAt(iNode));
+			if (nX > nMaxX || iNode == 0) {
+				nMaxX = nX;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionX.setElementAt(nMaxX, nNode);
+		}
+	} // alignRight
+
+	/** align set of nodes with the top most node in the list
+	 * @param nodes list of indexes of nodes to align
+	 */
+	public void alignTop(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new alignTopAction(nodes));
+		}
+		int nMinY = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nY = getPositionY((Integer) nodes.elementAt(iNode));
+			if (nY < nMinY || iNode == 0) {
+				nMinY = nY;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionY.setElementAt(nMinY, nNode);
+		}
+	} // alignTop
+
+	/** align set of nodes with the bottom most node in the list
+	 * @param nodes list of indexes of nodes to align
+	 */
+	public void alignBottom(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new alignBottomAction(nodes));
+		}
+		int nMaxY = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nY = getPositionY((Integer) nodes.elementAt(iNode));
+			if (nY > nMaxY || iNode == 0) {
+				nMaxY = nY;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionY.setElementAt(nMaxY, nNode);
+		}
+	} // alignBottom
+
+	/** center set of nodes half way between left and right most node in the list
+	 * @param nodes list of indexes of nodes to center
+	 */
+	public void centerHorizontal(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new centerHorizontalAction(nodes));
+		}
+		int nMinY = -1;
+		int nMaxY = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nY = getPositionY((Integer) nodes.elementAt(iNode));
+			if (nY < nMinY || iNode == 0) {
+				nMinY = nY;
+			}
+			if (nY > nMaxY || iNode == 0) {
+				nMaxY = nY;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionY.setElementAt((nMinY + nMaxY) / 2, nNode);
+		}
+	} // centerHorizontal
+
+	/** center set of nodes half way between top and bottom most node in the list
+	 * @param nodes list of indexes of nodes to center
+	 */
+	public void centerVertical(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new centerVerticalAction(nodes));
+		}
+		int nMinX = -1;
+		int nMaxX = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nX = getPositionX((Integer) nodes.elementAt(iNode));
+			if (nX < nMinX || iNode == 0) {
+				nMinX = nX;
+			}
+			if (nX > nMaxX || iNode == 0) {
+				nMaxX = nX;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionX.setElementAt((nMinX + nMaxX) / 2, nNode);
+		}
+	} // centerVertical
+
+	/** space out set of nodes evenly between left and right most node in the list
+	 * @param nodes list of indexes of nodes to space out
+	 */
+	public void spaceHorizontal(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new spaceHorizontalAction(nodes));
+		}
+		int nMinX = -1;
+		int nMaxX = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nX = getPositionX((Integer) nodes.elementAt(iNode));
+			if (nX < nMinX || iNode == 0) {
+				nMinX = nX;
+			}
+			if (nX > nMaxX || iNode == 0) {
+				nMaxX = nX;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionX.setElementAt((int) (nMinX + iNode * (nMaxX - nMinX) / (nodes.size() - 1.0)), nNode);
+		}
+	} // spaceHorizontal
+
+	/** space out set of nodes evenly between top and bottom most node in the list
+	 * @param nodes list of indexes of nodes to space out
+	 */
+	public void spaceVertical(FastVector nodes) {
+		// update undo stack
+		if (m_bNeedsUndoAction) {
+			addUndoAction(new spaceVerticalAction(nodes));
+		}
+		int nMinY = -1;
+		int nMaxY = -1;
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nY = getPositionY((Integer) nodes.elementAt(iNode));
+			if (nY < nMinY || iNode == 0) {
+				nMinY = nY;
+			}
+			if (nY > nMaxY || iNode == 0) {
+				nMaxY = nY;
+			}
+		}
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			m_nPositionY.setElementAt((int) (nMinY + iNode * (nMaxY - nMinY) / (nodes.size() - 1.0)), nNode);
+		}
+	} // spaceVertical
+
+
+	/** replace attribute with specified name and values
+	 * @param nTargetNode index of node the replace specification for
+	 * @param sName new name of the node
+	 * @param values array of values of the node
+	 */
+	void replaceAtt(int nTargetNode, String sName, FastVector values) {
+		Attribute newAtt = new Attribute(sName, values);
+		if (m_Instances.classIndex() == nTargetNode) {
+			m_Instances.setClassIndex(-1);
+			m_Instances.insertAttributeAt(newAtt, nTargetNode);
+			m_Instances.deleteAttributeAt(nTargetNode + 1);
+			m_Instances.setClassIndex(nTargetNode);
+		} else {
+			m_Instances.insertAttributeAt(newAtt, nTargetNode);
+			m_Instances.deleteAttributeAt(nTargetNode + 1);
+		}
+	} // replaceAtt
+
+	/** return marginal distibution for a node
+	 * @param iNode index of node of interest
+	 */
+	public double[] getMargin(int iNode) {
+		return (double[]) m_fMarginP.elementAt(iNode);
+	};
+
+	/** set marginal distibution for a node
+	 * @param iNode index of node to set marginal distribution for
+	 * @param fMarginP marginal distribution
+	 */
+	public void setMargin(int iNode, double[] fMarginP) {
+		m_fMarginP.setElementAt(fMarginP, iNode);
+	}
+
+	/** get evidence state of a node. -1 represents no evidence set, otherwise
+	 * the index of a value of the node
+	 * @param iNode index of node of interest
+	 */
+	public int getEvidence(int iNode) {
+		return (Integer) m_nEvidence.elementAt(iNode);
+	}
+
+	/** set evidence state of a node. -1 represents no evidence set, otherwise
+	 * the index of a value of the node
+ 	 * @param iNode index of node of interest
+	 * @param iValue evidence value to set
+	 */
+	public void setEvidence(int iNode, int iValue) {
+		m_nEvidence.setElementAt(iValue, iNode);
+	}
+
+	/** return list of children of a node
+	 * @param nTargetNode index of node of interest
+	 */
+	public FastVector getChildren(int nTargetNode) {
+		FastVector children = new FastVector();
+		for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+			if (m_ParentSets[iNode].contains(nTargetNode)) {
+				children.addElement(iNode);
+			}
+		}
+		return children;
+	} // getChildren
+
+	/** returns network in XMLBIF format
+	*/
+	public String toXMLBIF03() {
+		if (m_Instances == null) {
+			return ("<!--No model built yet-->");
+		}
+
+		StringBuffer text = new StringBuffer();
+		text.append(getBIFHeader());
+		text.append("\n");
+		text.append("\n");
+		text.append("<BIF VERSION=\"0.3\">\n");
+		text.append("<NETWORK>\n");
+		text.append("<NAME>" + XMLNormalize(m_Instances.relationName()) + "</NAME>\n");
+		for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+			text.append("<VARIABLE TYPE=\"nature\">\n");
+			text.append("<NAME>" + XMLNormalize(m_Instances.attribute(iAttribute).name()) + "</NAME>\n");
+			for (int iValue = 0; iValue < m_Instances.attribute(iAttribute).numValues(); iValue++) {
+				text.append("<OUTCOME>" + XMLNormalize(m_Instances.attribute(iAttribute).value(iValue))
+						+ "</OUTCOME>\n");
+			}
+			text.append("<PROPERTY>position = (" + getPositionX(iAttribute) + "," + getPositionY(iAttribute)
+					+ ")</PROPERTY>\n");
+			text.append("</VARIABLE>\n");
+		}
+
+		for (int iAttribute = 0; iAttribute < m_Instances.numAttributes(); iAttribute++) {
+			text.append("<DEFINITION>\n");
+			text.append("<FOR>" + XMLNormalize(m_Instances.attribute(iAttribute).name()) + "</FOR>\n");
+			for (int iParent = 0; iParent < m_ParentSets[iAttribute].getNrOfParents(); iParent++) {
+				text.append("<GIVEN>"
+						+ XMLNormalize(m_Instances.attribute(m_ParentSets[iAttribute].getParent(iParent)).name())
+						+ "</GIVEN>\n");
+			}
+			text.append("<TABLE>\n");
+			for (int iParent = 0; iParent < m_ParentSets[iAttribute].getCardinalityOfParents(); iParent++) {
+				for (int iValue = 0; iValue < m_Instances.attribute(iAttribute).numValues(); iValue++) {
+					text.append(m_Distributions[iAttribute][iParent].getProbability(iValue));
+					text.append(' ');
+				}
+				text.append('\n');
+			}
+			text.append("</TABLE>\n");
+			text.append("</DEFINITION>\n");
+		}
+		text.append("</NETWORK>\n");
+		text.append("</BIF>\n");
+		return text.toString();
+	} // toXMLBIF03
+
+	/** return fragment of network in XMLBIF format
+	 * @param nodes array of indexes of nodes that should be in the fragment
+	 */
+	public String toXMLBIF03(FastVector nodes) {
+		StringBuffer text = new StringBuffer();
+		text.append(getBIFHeader());
+		text.append("\n");
+		text.append("\n");
+		text.append("<BIF VERSION=\"0.3\">\n");
+		text.append("<NETWORK>\n");
+		text.append("<NAME>" + XMLNormalize(m_Instances.relationName()) + "</NAME>\n");
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			text.append("<VARIABLE TYPE=\"nature\">\n");
+			text.append("<NAME>" + XMLNormalize(m_Instances.attribute(nNode).name()) + "</NAME>\n");
+			for (int iValue = 0; iValue < m_Instances.attribute(nNode).numValues(); iValue++) {
+				text.append("<OUTCOME>" + XMLNormalize(m_Instances.attribute(nNode).value(iValue)) + "</OUTCOME>\n");
+			}
+			text.append("<PROPERTY>position = (" + getPositionX(nNode) + "," + getPositionY(nNode) + ")</PROPERTY>\n");
+			text.append("</VARIABLE>\n");
+		}
+
+		for (int iNode = 0; iNode < nodes.size(); iNode++) {
+			int nNode = (Integer) nodes.elementAt(iNode);
+			text.append("<DEFINITION>\n");
+			text.append("<FOR>" + XMLNormalize(m_Instances.attribute(nNode).name()) + "</FOR>\n");
+			for (int iParent = 0; iParent < m_ParentSets[nNode].getNrOfParents(); iParent++) {
+				text.append("<GIVEN>"
+						+ XMLNormalize(m_Instances.attribute(m_ParentSets[nNode].getParent(iParent)).name())
+						+ "</GIVEN>\n");
+			}
+			text.append("<TABLE>\n");
+			for (int iParent = 0; iParent < m_ParentSets[nNode].getCardinalityOfParents(); iParent++) {
+				for (int iValue = 0; iValue < m_Instances.attribute(nNode).numValues(); iValue++) {
+					text.append(m_Distributions[nNode][iParent].getProbability(iValue));
+					text.append(' ');
+				}
+				text.append('\n');
+			}
+			text.append("</TABLE>\n");
+			text.append("</DEFINITION>\n");
+		}
+		text.append("</NETWORK>\n");
+		text.append("</BIF>\n");
+		return text.toString();
+	} // toXMLBIF03
+
+	/** undo stack for undoin edit actions, or redo edit actions */
+	FastVector m_undoStack = new FastVector();
+
+	/** current action in undo stack */
+	int m_nCurrentEditAction = -1;
+
+	/** action that the network is saved */
+	int m_nSavedPointer = -1;
+
+	/***************************************************************************
+	 * flag to indicate whether an edit action needs to introduce an undo
+	 * action. This is only false when an undo or redo action is performed.
+	 **************************************************************************/
+	boolean m_bNeedsUndoAction = true;
+
+	/** return whether there is something on the undo stack that can be performed */
+	public boolean canUndo() {
+		return m_nCurrentEditAction >= 0;
+	}
+
+	/** return whether there is something on the undo stack that can be performed */
+	public boolean canRedo() {
+		return m_nCurrentEditAction < m_undoStack.size() - 1;
+	}
+
+	/** return true when current state differs from the state the network was last saved */
+	public boolean isChanged() {
+		return m_nCurrentEditAction != m_nSavedPointer;
+	}
+
+	/** indicate the network state was saved */
+	public void isSaved() {
+		m_nSavedPointer = m_nCurrentEditAction;
+	}
+
+	/** get message representing the last action performed on the network */
+	public String lastActionMsg() {
+		if (m_undoStack.size() == 0) {
+			return "";
+		}
+		return ((UndoAction) m_undoStack.lastElement()).getRedoMsg();
+	} // lastActionMsg
+
+
+	/** undo the last edit action performed on the network.
+	 * returns message representing the action performed.
+	 */
+	public String undo() {
+		if (!canUndo()) {
+			return "";
+		}
+		UndoAction undoAction = (UndoAction) m_undoStack.elementAt(m_nCurrentEditAction);
+		m_bNeedsUndoAction = false;
+		undoAction.undo();
+		m_bNeedsUndoAction = true;
+		m_nCurrentEditAction--;
+
+		// undo stack debugging
+		/*
+		if (m_nCurrentEditAction>0) {
+			String sXML = (String) m_sXMLStack.elementAt(m_nCurrentEditAction);
+			String sXMLCurrent = toXMLBIF03();
+			if (!sXML.equals(sXMLCurrent)) {
+				String sDiff = "";
+				String sDiff2 = "";
+				for (int i = 0; i < sXML.length() && sDiff.length() < 80; i++) {
+					if (sXML.charAt(i) != sXMLCurrent.charAt(i)) {
+						sDiff += sXML.charAt(i);
+						sDiff2 += sXMLCurrent.charAt(i);
+					}
+				}
+
+				JOptionPane.showMessageDialog(null,"Undo error\n" + sDiff + " \n" + sDiff2);
+			}
+		}
+		*/
+		return undoAction.getUndoMsg();
+	} // undo
+
+	/** redo the last edit action performed on the network.
+	 * returns message representing the action performed.
+	 */
+	public String redo() {
+		if (!canRedo()) {
+			return "";
+		}
+		m_nCurrentEditAction++;
+		UndoAction undoAction = (UndoAction) m_undoStack.elementAt(m_nCurrentEditAction);
+		m_bNeedsUndoAction = false;
+		undoAction.redo();
+		m_bNeedsUndoAction = true;
+
+		// undo stack debugging
+		/*
+		if (m_nCurrentEditAction < m_sXMLStack.size()) {
+			String sXML = (String) m_sXMLStack.elementAt(m_nCurrentEditAction);
+			String sXMLCurrent = toXMLBIF03();
+			if (!sXML.equals(sXMLCurrent)) {
+				String sDiff = "";
+				String sDiff2 = "";
+				for (int i = 0; i < sXML.length() && sDiff.length() < 80; i++) {
+					if (sXML.charAt(i) != sXMLCurrent.charAt(i)) {
+						sDiff += sXML.charAt(i);
+						sDiff2 += sXMLCurrent.charAt(i);
+					}
+				}
+
+				JOptionPane.showMessageDialog(null,"redo error\n" + sDiff + " \n" + sDiff2);
+			}
+		}
+		*/
+		return undoAction.getRedoMsg();
+	} // redo
+
+	/** add undo action to the undo stack.
+	 * @param action operation that needs to be added to the undo stack
+	 */
+	void addUndoAction(UndoAction action) {
+		int iAction = m_undoStack.size() - 1;
+		while (iAction > m_nCurrentEditAction) {
+			m_undoStack.removeElementAt(iAction--);
+		}
+		if (m_nSavedPointer > m_nCurrentEditAction) {
+			m_nSavedPointer = -2;
+		}
+		m_undoStack.addElement(action);
+		//m_sXMLStack.addElement(toXMLBIF03());
+		m_nCurrentEditAction++;
+	} // addUndoAction
+
+	/** remove all actions from the undo stack */
+	public void clearUndoStack() {
+		m_undoStack = new FastVector();
+		//m_sXMLStack = new FastVector();
+		m_nCurrentEditAction = -1;
+		m_nSavedPointer = -1;
+	} // clearUndoStack
+
+	/** base class for actions representing operations on the Bayesian network
+	 * that can be undone/redone
+	 */
+	class UndoAction implements Serializable {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public void undo() {
+		}
+
+		public void redo() {
+		}
+
+		public String getUndoMsg() {
+			return getMsg();
+		}
+
+		public String getRedoMsg() {
+			return getMsg();
+		}
+		String getMsg() {
+			String sStr = toString();
+			int iStart = sStr.indexOf('$');
+			int iEnd = sStr.indexOf('@');
+			StringBuffer sBuffer = new StringBuffer();
+			for(int i= iStart + 1; i < iEnd; i++) {
+				char c = sStr.charAt(i);
+				if (Character.isUpperCase(c)) {
+					sBuffer.append(' ');
+				}
+				sBuffer.append(sStr.charAt(i));
+			}
+			return sBuffer.toString();
+		} // getMsg
+	} // class UndoAction
+
+	class AddNodeAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		String m_sName;
+		int m_nPosX;
+		int m_nPosY;
+
+		int m_nCardinality;
+
+		AddNodeAction(String sName, int nCardinality, int nPosX, int nPosY) {
+			m_sName = sName;
+			m_nCardinality = nCardinality;
+			m_nPosX = nPosX;
+			m_nPosY = nPosY;
+		} // c'tor
+
+		public void undo() {
+			try {
+				deleteNode(getNrOfNodes() - 1);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				addNode(m_sName, m_nCardinality, m_nPosX, m_nPosY);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class AddNodeAction
+
+	class DeleteNodeAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		Attribute m_att;
+
+		Estimator[] m_CPT;
+
+		ParentSet m_ParentSet;
+
+		FastVector m_deleteArcActions;
+
+		int m_nPosX;
+
+		int m_nPosY;
+
+		DeleteNodeAction(int nTargetNode) {
+			m_nTargetNode = nTargetNode;
+			m_att = m_Instances.attribute(nTargetNode);
+			try {
+				SerializedObject so = new SerializedObject(m_Distributions[nTargetNode]);
+				m_CPT = (Estimator[]) so.getObject();
+				;
+				so = new SerializedObject(m_ParentSets[nTargetNode]);
+				m_ParentSet = (ParentSet) so.getObject();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			m_deleteArcActions = new FastVector();
+			for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+				if (m_ParentSets[iNode].contains(nTargetNode)) {
+					m_deleteArcActions.addElement(new DeleteArcAction(nTargetNode, iNode));
+				}
+			}
+			m_nPosX = getPositionX(m_nTargetNode);
+			m_nPosY = getPositionY(m_nTargetNode);
+		} // c'tor
+
+		public void undo() {
+			try {
+				m_Instances.insertAttributeAt(m_att, m_nTargetNode);
+				int nAtts = m_Instances.numAttributes();
+				// update parentsets
+				ParentSet[] parentSets = new ParentSet[nAtts];
+				int nX = 0;
+				for (int iParentSet = 0; iParentSet < nAtts; iParentSet++) {
+					if (iParentSet == m_nTargetNode) {
+						SerializedObject so = new SerializedObject(m_ParentSet);
+						parentSets[iParentSet] = (ParentSet) so.getObject();
+						nX = 1;
+					} else {
+						parentSets[iParentSet] = m_ParentSets[iParentSet - nX];
+						for (int iParent = 0; iParent < parentSets[iParentSet].getNrOfParents(); iParent++) {
+							int nParent = parentSets[iParentSet].getParent(iParent);
+							if (nParent >= m_nTargetNode) {
+								parentSets[iParentSet].SetParent(iParent, nParent + 1);
+							}
+						}
+					}
+				}
+				m_ParentSets = parentSets;
+				// update distributions
+				Estimator[][] distributions = new Estimator[nAtts][];
+				nX = 0;
+				for (int iNode = 0; iNode < nAtts; iNode++) {
+					if (iNode == m_nTargetNode) {
+						SerializedObject so = new SerializedObject(m_CPT);
+						distributions[iNode] = (Estimator[]) so.getObject();
+						nX = 1;
+					} else {
+						distributions[iNode] = m_Distributions[iNode - nX];
+					}
+				}
+				m_Distributions = distributions;
+
+				for (int deletedArc = 0; deletedArc < m_deleteArcActions.size(); deletedArc++) {
+					DeleteArcAction action = (DeleteArcAction) m_deleteArcActions.elementAt(deletedArc);
+					action.undo();
+				}
+				m_nPositionX.insertElementAt(m_nPosX, m_nTargetNode);
+				m_nPositionY.insertElementAt(m_nPosY, m_nTargetNode);
+				m_nEvidence.insertElementAt(-1, m_nTargetNode);
+				m_fMarginP.insertElementAt(new double[getCardinality(m_nTargetNode)], m_nTargetNode);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				deleteNode(m_nTargetNode);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class DeleteNodeAction
+
+	class DeleteSelectionAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		FastVector m_nodes;
+
+		Attribute[] m_att;
+
+		Estimator[][] m_CPT;
+
+		ParentSet[] m_ParentSet;
+
+		FastVector m_deleteArcActions;
+
+		int[] m_nPosX;
+
+		int[] m_nPosY;
+
+		public DeleteSelectionAction(FastVector nodes) {
+			m_nodes = new FastVector();
+			int nNodes = nodes.size();
+			m_att = new Attribute[nNodes];
+			m_CPT = new Estimator[nNodes][];
+			m_ParentSet = new ParentSet[nNodes];
+			m_nPosX = new int[nNodes];
+			m_nPosY = new int[nNodes];
+			m_deleteArcActions = new FastVector();
+			for (int iNode = 0; iNode < nodes.size(); iNode++) {
+				int nTargetNode = (Integer) nodes.elementAt(iNode);
+				m_nodes.addElement(nTargetNode);
+				m_att[iNode] = m_Instances.attribute(nTargetNode);
+				try {
+					SerializedObject so = new SerializedObject(m_Distributions[nTargetNode]);
+					m_CPT[iNode] = (Estimator[]) so.getObject();
+					;
+					so = new SerializedObject(m_ParentSets[nTargetNode]);
+					m_ParentSet[iNode] = (ParentSet) so.getObject();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+				m_nPosX[iNode] = getPositionX(nTargetNode);
+				m_nPosY[iNode] = getPositionY(nTargetNode);
+				for (int iNode2 = 0; iNode2 < getNrOfNodes(); iNode2++) {
+					if (!nodes.contains(iNode2) && m_ParentSets[iNode2].contains(nTargetNode)) {
+						m_deleteArcActions.addElement(new DeleteArcAction(nTargetNode, iNode2));
+					}
+				}
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				for (int iNode = 0; iNode < m_nodes.size(); iNode++) {
+					int nTargetNode = (Integer) m_nodes.elementAt(iNode);
+					m_Instances.insertAttributeAt(m_att[iNode], nTargetNode);
+				}
+				int nAtts = m_Instances.numAttributes();
+				// update parentsets
+				ParentSet[] parentSets = new ParentSet[nAtts];
+				int[] offset = new int[nAtts];
+				for (int iNode = 0; iNode < nAtts; iNode++) {
+					offset[iNode] = iNode;
+				}
+				for (int iNode = m_nodes.size() - 1; iNode >= 0; iNode--) {
+					int nTargetNode = (Integer) m_nodes.elementAt(iNode);
+					for (int i = nTargetNode; i < nAtts - 1; i++) {
+						offset[i] = offset[i + 1];
+					}
+				}
+
+				int iTargetNode = 0;
+				for (int iParentSet = 0; iParentSet < nAtts; iParentSet++) {
+					if (iTargetNode < m_nodes.size()
+							&& (Integer) m_nodes.elementAt(iTargetNode) == (Integer) iParentSet) {
+						SerializedObject so = new SerializedObject(m_ParentSet[iTargetNode]);
+						parentSets[iParentSet] = (ParentSet) so.getObject();
+						iTargetNode++;
+					} else {
+						parentSets[iParentSet] = m_ParentSets[iParentSet - iTargetNode];
+						for (int iParent = 0; iParent < parentSets[iParentSet].getNrOfParents(); iParent++) {
+							int nParent = parentSets[iParentSet].getParent(iParent);
+							parentSets[iParentSet].SetParent(iParent, offset[nParent]);
+						}
+					}
+				}
+				m_ParentSets = parentSets;
+				// update distributions
+				Estimator[][] distributions = new Estimator[nAtts][];
+				iTargetNode = 0;
+				for (int iNode = 0; iNode < nAtts; iNode++) {
+					if (iTargetNode < m_nodes.size() && (Integer) m_nodes.elementAt(iTargetNode) == (Integer) iNode) {
+						SerializedObject so = new SerializedObject(m_CPT[iTargetNode]);
+						distributions[iNode] = (Estimator[]) so.getObject();
+						iTargetNode++;
+					} else {
+						distributions[iNode] = m_Distributions[iNode - iTargetNode];
+					}
+				}
+				m_Distributions = distributions;
+
+				for (int iNode = 0; iNode < m_nodes.size(); iNode++) {
+					int nTargetNode = (Integer) m_nodes.elementAt(iNode);
+					m_nPositionX.insertElementAt(m_nPosX[iNode], nTargetNode);
+					m_nPositionY.insertElementAt(m_nPosY[iNode], nTargetNode);
+					m_nEvidence.insertElementAt(-1, nTargetNode);
+					m_fMarginP.insertElementAt(new double[getCardinality(nTargetNode)], nTargetNode);
+				}
+				for (int deletedArc = 0; deletedArc < m_deleteArcActions.size(); deletedArc++) {
+					DeleteArcAction action = (DeleteArcAction) m_deleteArcActions.elementAt(deletedArc);
+					action.undo();
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				for (int iNode = m_nodes.size() - 1; iNode >= 0; iNode--) {
+					int nNode = (Integer) m_nodes.elementAt(iNode);
+					deleteNode(nNode);
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class DeleteSelectionAction
+
+	class AddArcAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		//int m_nChild;
+		FastVector m_children;
+
+		int m_nParent;
+
+		Estimator[][] m_CPT;
+
+		AddArcAction(int nParent, int nChild) {
+			try {
+				m_nParent = nParent;
+				m_children = new FastVector();
+				m_children.addElement(nChild);
+				//m_nChild = nChild;
+				SerializedObject so = new SerializedObject(m_Distributions[nChild]);
+				m_CPT = new Estimator[1][];
+				m_CPT[0] = (Estimator[]) so.getObject();
+				;
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // c'tor
+
+		AddArcAction(int nParent, FastVector children) {
+			try {
+				m_nParent = nParent;
+				m_children = new FastVector();
+				m_CPT = new Estimator[children.size()][];
+				for (int iChild = 0; iChild < children.size(); iChild++) {
+					int nChild = (Integer) children.elementAt(iChild);
+					m_children.addElement(nChild);
+					SerializedObject so = new SerializedObject(m_Distributions[nChild]);
+					m_CPT[iChild] = (Estimator[]) so.getObject();
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				for (int iChild = 0; iChild < m_children.size(); iChild++) {
+					int nChild = (Integer) m_children.elementAt(iChild);
+					deleteArc(m_nParent, nChild);
+					SerializedObject so = new SerializedObject(m_CPT[iChild]);
+					m_Distributions[nChild] = (Estimator[]) so.getObject();
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				for (int iChild = 0; iChild < m_children.size(); iChild++) {
+					int nChild = (Integer) m_children.elementAt(iChild);
+					addArc(m_nParent, nChild);
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class AddArcAction
+
+	class DeleteArcAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int[] m_nParents;
+		int m_nChild;
+		int m_nParent;
+		Estimator[] m_CPT;
+
+		DeleteArcAction(int nParent, int nChild) {
+			try {
+			m_nChild = nChild;
+			m_nParent = nParent;
+			m_nParents = new int[getNrOfParents(nChild)];
+			for (int iParent = 0; iParent < m_nParents.length; iParent++) {
+				m_nParents[iParent] = getParent(nChild, iParent);
+			}
+			SerializedObject so = new SerializedObject(m_Distributions[nChild]);
+			m_CPT = (Estimator[]) so.getObject();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				SerializedObject so = new SerializedObject(m_CPT);
+				m_Distributions[m_nChild] = (Estimator[]) so.getObject();
+				ParentSet parentSet = new ParentSet();
+				for (int iParent = 0; iParent < m_nParents.length; iParent++) {
+					parentSet.addParent(m_nParents[iParent], m_Instances);
+				}
+				m_ParentSets[m_nChild] = parentSet;
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				deleteArc(m_nParent, m_nChild);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class DeleteArcAction
+
+	class SetDistributionAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		Estimator[] m_CPT;
+
+		double[][] m_P;
+
+		SetDistributionAction(int nTargetNode, double[][] P) {
+			try {
+				m_nTargetNode = nTargetNode;
+				SerializedObject so = new SerializedObject(m_Distributions[nTargetNode]);
+				m_CPT = (Estimator[]) so.getObject();
+				;
+				m_P = P;
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				SerializedObject so = new SerializedObject(m_CPT);
+				m_Distributions[m_nTargetNode] = (Estimator[]) so.getObject();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				setDistribution(m_nTargetNode, m_P);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Distribution of node " + getNodeName(m_nTargetNode) + " changed";
+		}
+
+		public String getRedoMsg() {
+			return "Distribution of node " + getNodeName(m_nTargetNode) + " changed";
+		}
+	} // class SetDistributionAction
+
+	class RenameAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		String m_sNewName;
+
+		String m_sOldName;
+
+		RenameAction(int nTargetNode, String sOldName, String sNewName) {
+			m_nTargetNode = nTargetNode;
+			m_sNewName = sNewName;
+			m_sOldName = sOldName;
+		} // c'tor
+
+		public void undo() {
+			setNodeName(m_nTargetNode, m_sOldName);
+		} // undo
+
+		public void redo() {
+			setNodeName(m_nTargetNode, m_sNewName);
+		} // redo
+	} // class RenameAction
+
+	class RenameValueAction extends RenameAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		RenameValueAction(int nTargetNode, String sOldName, String sNewName) {
+			super(nTargetNode, sOldName, sNewName);
+		} // c'tor
+
+		public void undo() {
+			renameNodeValue(m_nTargetNode, m_sNewName, m_sOldName);
+		} // undo
+
+		public void redo() {
+			renameNodeValue(m_nTargetNode, m_sOldName, m_sNewName);
+		} // redo
+
+		public String getUndoMsg() {
+			return "Value of node " + getNodeName(m_nTargetNode) + " changed from " + m_sNewName + " to " + m_sOldName;
+		}
+
+		public String getRedoMsg() {
+			return "Value of node " + getNodeName(m_nTargetNode) + " changed from " + m_sOldName + " to " + m_sNewName;
+		}
+	} // class RenameValueAction
+
+	class AddValueAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		String m_sValue;
+
+		AddValueAction(int nTargetNode, String sValue) {
+			m_nTargetNode = nTargetNode;
+			m_sValue = sValue;
+		} // c'tor
+
+		public void undo() {
+			try {
+				delNodeValue(m_nTargetNode, m_sValue);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			addNodeValue(m_nTargetNode, m_sValue);
+		} // redo
+
+		public String getUndoMsg() {
+			return "Value " + m_sValue + " removed from node " + getNodeName(m_nTargetNode);
+		}
+
+		public String getRedoMsg() {
+			return "Value " + m_sValue + " added to node " + getNodeName(m_nTargetNode);
+		}
+	} // class AddValueAction
+
+	class DelValueAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		String m_sValue;
+
+		Estimator[] m_CPT;
+
+		FastVector m_children;
+
+		Estimator[][] m_childAtts;
+
+		Attribute m_att;
+
+		DelValueAction(int nTargetNode, String sValue) {
+			try {
+				m_nTargetNode = nTargetNode;
+				m_sValue = sValue;
+				m_att = m_Instances.attribute(nTargetNode);
+				SerializedObject so = new SerializedObject(m_Distributions[nTargetNode]);
+				m_CPT = (Estimator[]) so.getObject();
+				;
+				m_children = new FastVector();
+				for (int iNode = 0; iNode < getNrOfNodes(); iNode++) {
+					if (m_ParentSets[iNode].contains(nTargetNode)) {
+						m_children.addElement(iNode);
+					}
+				}
+				m_childAtts = new Estimator[m_children.size()][];
+				for (int iChild = 0; iChild < m_children.size(); iChild++) {
+					int nChild = (Integer) m_children.elementAt(iChild);
+					m_childAtts[iChild] = m_Distributions[nChild];
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				m_Instances.insertAttributeAt(m_att, m_nTargetNode);
+				SerializedObject so = new SerializedObject(m_CPT);
+				m_Distributions[m_nTargetNode] = (Estimator[]) so.getObject();
+				for (int iChild = 0; iChild < m_children.size(); iChild++) {
+					int nChild = (Integer) m_children.elementAt(iChild);
+					m_Instances.insertAttributeAt(m_att, m_nTargetNode);
+					so = new SerializedObject(m_childAtts[iChild]);
+					m_Distributions[nChild] = (Estimator[]) so.getObject();
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				delNodeValue(m_nTargetNode, m_sValue);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Value " + m_sValue + " added to node " + getNodeName(m_nTargetNode);
+		}
+
+		public String getRedoMsg() {
+			return "Value " + m_sValue + " removed from node " + getNodeName(m_nTargetNode);
+		}
+	} // class DelValueAction
+
+	class alignAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		FastVector m_nodes;
+
+		FastVector m_posX;
+
+		FastVector m_posY;
+
+		alignAction(FastVector nodes) {
+			m_nodes = new FastVector(nodes.size());
+			m_posX = new FastVector(nodes.size());
+			m_posY = new FastVector(nodes.size());
+			for (int iNode = 0; iNode < nodes.size(); iNode++) {
+				int nNode = (Integer) nodes.elementAt(iNode);
+				m_nodes.addElement(nNode);
+				m_posX.addElement(getPositionX(nNode));
+				m_posY.addElement(getPositionY(nNode));
+			}
+		} // c'tor
+
+		public void undo() {
+			try {
+				for (int iNode = 0; iNode < m_nodes.size(); iNode++) {
+					int nNode = (Integer) m_nodes.elementAt(iNode);
+					setPosition(nNode, (Integer) m_posX.elementAt(iNode), (Integer) m_posY.elementAt(iNode));
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+	} // class alignAction
+
+	class alignLeftAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public alignLeftAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				alignLeft(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from aliging nodes to the left.";
+		}
+
+		public String getRedoMsg() {
+			return "Aligning " + m_nodes.size() + " nodes to the left.";
+		}
+	} // class alignLeftAction
+
+	class alignRightAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public alignRightAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				alignRight(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from aliging nodes to the right.";
+		}
+
+		public String getRedoMsg() {
+			return "Aligning " + m_nodes.size() + " nodes to the right.";
+		}
+	} // class alignLeftAction
+
+	class alignTopAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public alignTopAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				alignTop(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from aliging nodes to the top.";
+		}
+
+		public String getRedoMsg() {
+			return "Aligning " + m_nodes.size() + " nodes to the top.";
+		}
+	} // class alignTopAction
+
+	class alignBottomAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public alignBottomAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				alignBottom(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from aliging nodes to the bottom.";
+		}
+
+		public String getRedoMsg() {
+			return "Aligning " + m_nodes.size() + " nodes to the bottom.";
+		}
+	} // class alignBottomAction
+
+	class centerHorizontalAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public centerHorizontalAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				centerHorizontal(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from centering horizontally.";
+		}
+
+		public String getRedoMsg() {
+			return "Centering " + m_nodes.size() + " nodes horizontally.";
+		}
+	} // class centerHorizontalAction
+
+	class centerVerticalAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public centerVerticalAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				centerVertical(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from centering vertically.";
+		}
+
+		public String getRedoMsg() {
+			return "Centering " + m_nodes.size() + " nodes vertically.";
+		}
+	} // class centerVerticalAction
+
+	class spaceHorizontalAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public spaceHorizontalAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				spaceHorizontal(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from spaceing horizontally.";
+		}
+
+		public String getRedoMsg() {
+			return "spaceing " + m_nodes.size() + " nodes horizontally.";
+		}
+	} // class spaceHorizontalAction
+
+	class spaceVerticalAction extends alignAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		public spaceVerticalAction(FastVector nodes) {
+			super(nodes);
+		} // c'tor
+
+		public void redo() {
+			try {
+				spaceVertical(m_nodes);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+
+		public String getUndoMsg() {
+			return "Returning " + m_nodes.size() + " from spaceng vertically.";
+		}
+
+		public String getRedoMsg() {
+			return "Spaceng " + m_nodes.size() + " nodes vertically.";
+		}
+	} // class spaceVerticalAction
+
+	class SetPositionAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nTargetNode;
+
+		int m_nX;
+
+		int m_nY;
+
+		int m_nX2;
+
+		int m_nY2;
+
+		SetPositionAction(int nTargetNode, int nX, int nY) {
+			m_nTargetNode = nTargetNode;
+			m_nX2 = nX;
+			m_nY2 = nY;
+			m_nX = getPositionX(nTargetNode);
+			m_nY = getPositionY(nTargetNode);
+		} // c'tor
+
+		public void undo() {
+			setPosition(m_nTargetNode, m_nX, m_nY);
+		} // undo
+
+		public void redo() {
+			setPosition(m_nTargetNode, m_nX2, m_nY2);
+		} // redo
+
+		public void setUndoPosition(int nX, int nY) {
+			m_nX2 = nX;
+			m_nY2 = nY;
+		} // setPosition
+	} // class SetPositionAction
+
+	class SetGroupPositionAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		FastVector m_nodes;
+		int m_dX;
+		int m_dY;
+
+		SetGroupPositionAction(FastVector nodes, int dX, int dY) {
+			m_nodes = new FastVector(nodes.size());
+			for (int iNode = 0; iNode < nodes.size(); iNode++) {
+				m_nodes.addElement(nodes.elementAt(iNode));
+			}
+			m_dX = dX;
+			m_dY = dY;
+		} // c'tor
+
+		public void undo() {
+			for (int iNode = 0; iNode < m_nodes.size(); iNode++) {
+				int nNode = (Integer) m_nodes.elementAt(iNode);
+				setPosition(nNode, getPositionX(nNode) - m_dX,  getPositionY(nNode) - m_dY);
+			}
+		} // undo
+
+		public void redo() {
+			for (int iNode = 0; iNode < m_nodes.size(); iNode++) {
+				int nNode = (Integer) m_nodes.elementAt(iNode);
+				setPosition(nNode, getPositionX(nNode) + m_dX,  getPositionY(nNode) + m_dY);
+			}
+		} // redo
+		public void setUndoPosition(int dX, int dY) {
+			m_dX += dX;
+			m_dY += dY;
+		} // setPosition
+	} // class SetGroupPositionAction
+
+	class LayoutGraphAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		FastVector m_nPosX;
+		FastVector m_nPosY;
+		FastVector m_nPosX2;
+		FastVector m_nPosY2;
+
+		LayoutGraphAction(FastVector nPosX, FastVector nPosY) {
+			m_nPosX = new FastVector(nPosX.size());
+			m_nPosY = new FastVector(nPosX.size());
+			m_nPosX2 = new FastVector(nPosX.size());
+			m_nPosY2 = new FastVector(nPosX.size());
+			for (int iNode = 0; iNode < nPosX.size(); iNode++) {
+				m_nPosX.addElement(m_nPositionX.elementAt(iNode));
+				m_nPosY.addElement(m_nPositionY.elementAt(iNode));
+				m_nPosX2.addElement(nPosX.elementAt(iNode));
+				m_nPosY2.addElement(nPosY.elementAt(iNode));
+			}
+		} // c'tor
+
+		public void undo() {
+			for (int iNode = 0; iNode < m_nPosX.size(); iNode++) {
+				setPosition(iNode, (Integer) m_nPosX.elementAt(iNode), (Integer) m_nPosY.elementAt(iNode));
+			}
+		} // undo
+
+		public void redo() {
+			for (int iNode = 0; iNode < m_nPosX.size(); iNode++) {
+				setPosition(iNode, (Integer) m_nPosX2.elementAt(iNode), (Integer) m_nPosY2.elementAt(iNode));
+			}
+		} // redo
+	} // class LayoutGraphAction
+
+	class PasteAction extends UndoAction {
+		/** for serialization */
+		static final long serialVersionUID = 1;
+		int m_nBase;
+
+		String m_sXML;
+
+		PasteAction(String sXML, int nBase) {
+			m_sXML = sXML;
+			m_nBase = nBase;
+		} // c'tor
+
+		public void undo() {
+			try {
+				int iNode = getNrOfNodes() - 1;
+				while (iNode >= m_nBase) {
+					deleteNode(iNode);
+					iNode--;
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // undo
+
+		public void redo() {
+			try {
+				paste(m_sXML, EXECUTE);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // redo
+	} // class PasteAction
+	  
+	  /**
+	   * Returns the revision string.
+	   * 
+	   * @return		the revision
+	   */
+	  public String getRevision() {
+	    return RevisionUtils.extract("$Revision: 4899 $");
+	  }
+
+	/**
+	 * @param args
+	 */
+	public static void main(String[] args) {
+	} // main
+} // class EditableBayesNet
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/GUI.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/GUI.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/GUI.java	(revision 29)
@@ -0,0 +1,3518 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GUI.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.classifiers.bayes.net;
+
+import weka.classifiers.bayes.net.MarginCalculator.JunctionTreeNode;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.AbstractFileSaver;
+import weka.core.converters.ArffSaver;
+import weka.core.converters.ConverterUtils;
+import weka.gui.ConverterFileChooser;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.LookAndFeel;
+import weka.gui.PropertyDialog;
+import weka.gui.graphvisualizer.BIFFormatException;
+import weka.gui.graphvisualizer.BIFParser;
+import weka.gui.graphvisualizer.GraphNode;
+import weka.gui.graphvisualizer.HierarchicalBCEngine;
+import weka.gui.graphvisualizer.LayoutCompleteEvent;
+import weka.gui.graphvisualizer.LayoutCompleteEventListener;
+import weka.gui.graphvisualizer.LayoutEngine;
+import weka.gui.visualize.PrintablePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.beans.PropertyEditor;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Random;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.KeyStroke;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * GUI interface to Bayesian Networks. Allows editing Bayesian networks
+ * on screen and provides GUI interface to various Bayesian network facilities
+ * in Weka, including random network generation, data set generation and
+ * Bayesion network inference.
+ * 
+ * @author Remco Bouckaert (remco@cs.waikato.ac.nz)
+ * @version $Revision: 4902 $
+ */
+public class GUI extends JPanel implements LayoutCompleteEventListener {
+
+	/** for serialization */
+	private static final long serialVersionUID = -2038911085935515624L;
+
+	/** The current LayoutEngine */
+	protected LayoutEngine m_layoutEngine;
+
+	/** Panel actually displaying the graph */
+	protected GraphPanel m_GraphPanel;
+
+	/** Container of Bayesian network */
+	EditableBayesNet m_BayesNet = new EditableBayesNet(true);
+
+	/** String containing file name storing current network */
+	protected String m_sFileName = "";
+	/** used for calculating marginals in Bayesian netwowrks */
+	MarginCalculator m_marginCalculator = null;
+
+	/**
+	 * used for calculating marginals in Bayesian netwowrks when evidence is
+	 * present
+	 */
+	MarginCalculator m_marginCalculatorWithEvidence = null;
+
+	/** flag indicating whether marginal distributions of each of the nodes 
+	 * should be shown in display.
+	 */
+	boolean m_bViewMargins = false;
+	boolean m_bViewCliques = false;
+	
+	/** The menu bar */
+	private JMenuBar m_menuBar;
+
+	/** data selected from file. Used to train a Bayesian network on */
+	Instances m_Instances = null;
+
+	/** Text field for specifying zoom */
+	final JTextField m_jTfZoom;
+	/** toolbar containing buttons at top of window */
+	final JToolBar m_jTbTools;
+	/** status bar at bottom of window */
+	final JLabel m_jStatusBar;
+	/** TextField for node's width */
+	private final JTextField m_jTfNodeWidth = new JTextField(3);
+	/** TextField for nodes height */
+	private final JTextField m_jTfNodeHeight = new JTextField(3);
+	/** this contains the m_GraphPanel GraphPanel */
+	JScrollPane m_jScrollPane;
+
+	/** path for icons */
+	private final String ICONPATH = "weka/classifiers/bayes/net/icons/";
+
+	/** current zoom value */
+	private double m_fScale = 1;
+
+	/** standard width of node */
+	private int m_nNodeHeight = 2 * getFontMetrics(getFont()).getHeight();
+	/** standard height of node */
+	final static int DEFAULT_NODE_WIDTH = 50;
+	private int m_nNodeWidth = DEFAULT_NODE_WIDTH;
+	/** width of node, allowing for some padding */	
+	final static int PADDING = 10;
+	private int m_nPaddedNodeWidth = DEFAULT_NODE_WIDTH + PADDING;
+
+
+	/** used when using zoomIn and zoomOut buttons */
+	private int [] m_nZoomPercents = { 10, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 350, 400, 450, 500,
+			550, 600, 650, 700, 800, 900, 999 };
+
+	/** actions triggered by GUI events */
+	Action a_new = new ActionNew();
+
+	Action a_quit = new ActionQuit();
+	Action a_save = new ActionSave();
+	ActionExport a_export = new ActionExport();
+	ActionPrint a_print = new ActionPrint();
+	Action a_load = new ActionLoad();
+	Action a_zoomin = new ActionZoomIn();
+	Action a_zoomout = new ActionZoomOut();
+	Action a_layout = new ActionLayout();
+
+	Action a_saveas = new ActionSaveAs();
+
+	Action a_viewtoolbar = new ActionViewToolbar();
+
+	Action a_viewstatusbar = new ActionViewStatusbar();
+
+	Action a_networkgenerator = new ActionGenerateNetwork();
+
+	Action a_datagenerator = new ActionGenerateData();
+
+	Action a_datasetter = new ActionSetData();
+
+	Action a_learn = new ActionLearn();
+	Action a_learnCPT = new ActionLearnCPT();
+
+	Action a_help = new ActionHelp();
+
+	Action a_about = new ActionAbout();
+
+	ActionAddNode a_addnode = new ActionAddNode();
+
+	Action a_delnode = new ActionDeleteNode();
+	Action a_cutnode = new ActionCutNode();
+	Action a_copynode = new ActionCopyNode();
+	Action a_pastenode = new ActionPasteNode();
+	Action a_selectall = new ActionSelectAll();
+
+	Action a_addarc = new ActionAddArc();
+
+	Action a_delarc = new ActionDeleteArc();
+
+	Action a_undo = new ActionUndo();
+
+	Action a_redo= new ActionRedo();
+	
+	Action a_alignleft = new ActionAlignLeft();
+	Action a_alignright = new ActionAlignRight();
+	Action a_aligntop = new ActionAlignTop();
+	Action a_alignbottom = new ActionAlignBottom();
+	Action a_centerhorizontal = new ActionCenterHorizontal();
+	Action a_centervertical = new ActionCenterVertical();
+	Action a_spacehorizontal = new ActionSpaceHorizontal();
+	Action a_spacevertical = new ActionSpaceVertical();
+	
+	/** node currently selected through right clicking */
+	int m_nCurrentNode = -1;
+	/** selection of nodes */
+	Selection m_Selection = new Selection();
+	/** selection rectangle drawn through dragging with left mouse button */
+	Rectangle m_nSelectedRect = null;
+
+	
+	class Selection {
+		FastVector m_selected;
+		public Selection() {
+			m_selected = new FastVector();
+		} // c'tor
+		public FastVector getSelected() {return m_selected;}
+		void updateGUI() {
+			if (m_selected.size() > 0) {
+				a_cutnode.setEnabled(true);
+				a_copynode.setEnabled(true);
+			} else {
+				a_cutnode.setEnabled(false);
+				a_copynode.setEnabled(false);
+			}
+			if (m_selected.size() > 1) {
+				a_alignleft.setEnabled(true);
+				a_alignright.setEnabled(true);
+				a_aligntop.setEnabled(true);
+				a_alignbottom.setEnabled(true);
+				a_centerhorizontal.setEnabled(true);
+				a_centervertical.setEnabled(true);
+				a_spacehorizontal.setEnabled(true);
+				a_spacevertical.setEnabled(true);
+			} else {
+				a_alignleft.setEnabled(false);
+				a_alignright.setEnabled(false);
+				a_aligntop.setEnabled(false);
+				a_alignbottom.setEnabled(false);
+				a_centerhorizontal.setEnabled(false);
+				a_centervertical.setEnabled(false);
+				a_spacehorizontal.setEnabled(false);
+				a_spacevertical.setEnabled(false);
+			}
+		} // updateGUI
+		
+		public void addToSelection(int nNode) {
+			for (int iNode = 0; iNode < m_selected.size(); iNode++) {
+				if (nNode == (Integer) m_selected.elementAt(iNode)) {
+					return;
+				}
+			}
+			m_selected.addElement(nNode);
+			updateGUI();
+		} // addToSelection
+		
+		public void addToSelection(int [] iNodes) {
+			for (int iNode = 0; iNode < iNodes.length; iNode++) {
+				addToSelection(iNodes[iNode]);
+			}
+			updateGUI();
+		} // addToSelection
+		
+		public void addToSelection(Rectangle selectedRect) {
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				if (contains(selectedRect, iNode)) {
+					addToSelection(iNode);
+				}
+			}
+		} // addToSelection
+		
+		public void selectAll() {
+			m_selected.removeAllElements();
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				m_selected.addElement(iNode);
+			}
+			updateGUI();
+		} // selectAll
+
+		boolean contains(Rectangle rect, int iNode) {
+			return rect.intersects((m_BayesNet.getPositionX(iNode)) * m_fScale,
+					(m_BayesNet.getPositionY(iNode)) * m_fScale,
+					m_nPaddedNodeWidth * m_fScale, m_nNodeHeight * m_fScale);
+		} // contains
+		
+		public void removeFromSelection(int nNode) {
+			for (int iNode = 0; iNode < m_selected.size(); iNode++) {
+				if (nNode == (Integer) m_selected.elementAt(iNode)) {
+					m_selected.removeElementAt(iNode);
+				}
+			}
+			updateGUI();
+		} // removeFromSelection
+
+		public void toggleSelection(int nNode) {
+			for (int iNode = 0; iNode < m_selected.size(); iNode++) {
+				if (nNode == (Integer) m_selected.elementAt(iNode)) {
+					m_selected.removeElementAt(iNode);
+					updateGUI();
+					return;
+				}
+			}
+			addToSelection(nNode);
+		} // toggleSelection
+
+		public void toggleSelection(Rectangle selectedRect)	{
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				if (contains(selectedRect, iNode)) {
+					toggleSelection(iNode);
+				}
+			}
+		} // toggleSelection
+		
+		public void clear() {
+			m_selected.removeAllElements();
+			updateGUI();
+		}
+		
+		public void draw(Graphics g) {
+			if (m_selected.size() == 0) {
+				return;
+			}
+
+			for (int iNode = 0; iNode < m_selected.size(); iNode++) {
+				int nNode = (Integer) m_selected.elementAt(iNode);
+				int nPosX = m_BayesNet.getPositionX(nNode);
+				int nPosY = m_BayesNet.getPositionY(nNode);
+				g.setColor(Color.BLACK);
+				int nXRC = nPosX + m_nPaddedNodeWidth - m_nNodeWidth - (m_nPaddedNodeWidth - m_nNodeWidth) / 2;
+				int nYRC = nPosY;
+				int d = 5;
+				g.fillRect(nXRC, nYRC, d, d); 
+				g.fillRect(nXRC, nYRC + m_nNodeHeight, d, d); 
+				g.fillRect(nXRC + m_nNodeWidth, nYRC, d, d); 
+				g.fillRect(nXRC + m_nNodeWidth, nYRC + m_nNodeHeight, d, d); 
+			}
+		} // draw
+	} // Selection
+
+	ClipBoard m_clipboard = new ClipBoard();
+	
+	class ClipBoard {
+		String m_sText = null;
+		public ClipBoard() {
+			if (a_pastenode != null) {
+				a_pastenode.setEnabled(false);
+			}
+		}
+		public boolean hasText() {return m_sText != null;}
+		public String getText() { 
+			return m_sText;
+		}
+		public void setText(String sText) {
+			m_sText = sText;
+			a_pastenode.setEnabled(true);
+		}
+	} // class ClipBoard
+	
+	/** Base class used for definining actions
+	 * with a name, tool tip text, possibly an icon and accelerator key. 
+	 * */
+	class MyAction extends AbstractAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911111935517L;
+
+		public MyAction(String sName, String sToolTipText, String sIcon, String sAcceleratorKey) {
+			super(sName);
+			//setToolTipText(sToolTipText);
+			putValue(Action.SHORT_DESCRIPTION, sToolTipText);
+			putValue(Action.LONG_DESCRIPTION, sToolTipText);
+			if (sAcceleratorKey.length() > 0) {
+				KeyStroke keyStroke = KeyStroke.getKeyStroke(sAcceleratorKey);
+				putValue(Action.ACCELERATOR_KEY, keyStroke);
+			}
+			putValue(Action.MNEMONIC_KEY, (int) sName.charAt(0));
+			java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH + sIcon + ".png");
+			if (tempURL != null) {
+				putValue(Action.SMALL_ICON, new ImageIcon(tempURL));
+			} else {
+				putValue(Action.SMALL_ICON, new ImageIcon(new BufferedImage(20,20, BufferedImage.TYPE_4BYTE_ABGR)));
+				//System.err.println(ICONPATH + sIcon + ".png not found for weka.gui.graphvisualizer.Graph");
+			}
+		} // c'tor
+
+		/* Place holder. Should be implemented by derived classes.
+		 *  (non-Javadoc)
+		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+		 */
+		public void actionPerformed(ActionEvent ae) {}
+	} // class MyAction
+
+	class ActionGenerateNetwork extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935517L;
+
+		public ActionGenerateNetwork() {
+			super("Generate Network", "Generate Random Bayesian Network", "generate.network", "ctrl N");
+		} // c'tor
+
+		int m_nNrOfNodes = 10;
+
+		int m_nNrOfArcs = 15;
+
+		int m_nCardinality = 2;
+
+		int m_nSeed = 123;
+
+		JDialog dlg = null;
+
+		public void actionPerformed(ActionEvent ae) {
+			if (dlg == null) {
+				dlg = new JDialog();
+				dlg.setTitle("Generate Random Bayesian Network Options");
+
+				final JLabel jLbNrOfNodes = new JLabel("Nr of nodes");
+				final JTextField jTfNrOfNodes = new JTextField(3);
+				jTfNrOfNodes.setHorizontalAlignment(JTextField.CENTER);
+				jTfNrOfNodes.setText("" + m_nNrOfNodes);
+				final JLabel jLbNrOfArcs = new JLabel("Nr of arcs");
+				final JTextField jTfNrOfArcs = new JTextField(3);
+				jTfNrOfArcs.setHorizontalAlignment(JTextField.CENTER);
+				jTfNrOfArcs.setText("" + m_nNrOfArcs);
+				final JLabel jLbCardinality = new JLabel("Cardinality");
+				final JTextField jTfCardinality = new JTextField(3);
+				jTfCardinality.setHorizontalAlignment(JTextField.CENTER);
+				jTfCardinality.setText("" + m_nCardinality);
+				final JLabel jLbSeed = new JLabel("Random seed");
+				final JTextField jTfSeed = new JTextField(3);
+				jTfSeed.setHorizontalAlignment(JTextField.CENTER);
+				jTfSeed.setText("" + m_nSeed);
+
+				JButton jBtGo;
+				jBtGo = new JButton("Generate Network");
+
+				jBtGo.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						try {
+							BayesNetGenerator generator = new BayesNetGenerator();
+							m_BayesNet = generator;
+							m_BayesNet.clearUndoStack();
+							
+							String[] options = new String[8];
+							options[0] = "-N";
+							options[1] = "" + jTfNrOfNodes.getText();
+							options[2] = "-A";
+							options[3] = "" + jTfNrOfArcs.getText();
+							options[4] = "-C";
+							options[5] = "" + jTfCardinality.getText();
+							options[6] = "-S";
+							options[7] = "" + jTfSeed.getText();
+							generator.setOptions(options);
+							generator.generateRandomNetwork();
+							// Convert to EditableBayesNet
+							// This ensures the getOptions() called by GenericObjectEditor to get the correct result.
+							BIFReader bifReader = new BIFReader();
+							bifReader.processString(m_BayesNet.toXMLBIF03());
+							m_BayesNet = new EditableBayesNet(bifReader);
+
+							updateStatus();
+							layoutGraph();
+							a_datagenerator.setEnabled(true);
+							m_Instances = null;;
+							a_learn.setEnabled(false);
+							a_learnCPT.setEnabled(false);
+
+							dlg.setVisible(false);
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+					}
+				});
+
+				JButton jBtCancel;
+				jBtCancel = new JButton("Cancel");
+				jBtCancel.setMnemonic('C');
+				jBtCancel.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						dlg.setVisible(false);
+					}
+				});
+				GridBagConstraints gbc = new GridBagConstraints();
+				dlg.setLayout(new GridBagLayout());
+
+				Container c = new Container();
+				c.setLayout(new GridBagLayout());
+				gbc.gridwidth = 2;
+				gbc.insets = new Insets(8, 0, 0, 0);
+				gbc.anchor = GridBagConstraints.NORTHWEST;
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				c.add(jLbNrOfNodes, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfNrOfNodes, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbNrOfArcs, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfNrOfArcs, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbCardinality, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfCardinality, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbSeed, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfSeed, gbc);
+
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				dlg.add(c, gbc);
+				dlg.add(jBtGo);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				dlg.add(jBtCancel);
+			}
+			dlg.pack();
+			dlg.setLocation(100, 100);
+			dlg.setVisible(true);
+			dlg.setSize(dlg.getPreferredSize());
+			dlg.setVisible(false);
+			dlg.setVisible(true);
+			dlg.repaint();
+		} // actionPerformed
+	} // class ActionGenerate
+
+	class ActionGenerateData extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935516L;
+
+		public ActionGenerateData() {
+			super("Generate Data", "Generate Random Instances from Network", "generate.data", "ctrl D");
+		} // c'tor
+
+		int m_nNrOfInstances = 100;
+
+		int m_nSeed = 1234;
+
+		String m_sFile = "";
+
+		JDialog dlg = null;
+
+		public void actionPerformed(ActionEvent ae) {
+			if (dlg == null) {
+				dlg = new JDialog();
+				dlg.setTitle("Generate Random Data Options");
+
+				final JLabel jLbNrOfInstances = new JLabel("Nr of instances");
+				final JTextField jTfNrOfInstances = new JTextField(3);
+				jTfNrOfInstances.setHorizontalAlignment(JTextField.CENTER);
+				jTfNrOfInstances.setText("" + m_nNrOfInstances);
+				final JLabel jLbSeed = new JLabel("Random seed");
+				final JTextField jTfSeed = new JTextField(3);
+				jTfSeed.setHorizontalAlignment(JTextField.CENTER);
+				jTfSeed.setText("" + m_nSeed);
+				final JLabel jLbFile = new JLabel("Output file (optional)");
+				final JTextField jTfFile = new JTextField(12);
+				jTfFile.setHorizontalAlignment(JTextField.CENTER);
+				jTfFile.setText(m_sFile);
+
+				JButton jBtGo;
+				jBtGo = new JButton("Generate Data");
+
+				jBtGo.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						try {
+							String tmpfilename = "tmp.bif.file.xml";
+							BayesNetGenerator generator = new BayesNetGenerator();
+							String[] options = new String[4];
+							options[0] = "-M";
+							options[1] = "" + jTfNrOfInstances.getText();
+							options[2] = "-F";
+							options[3] = tmpfilename;
+							FileWriter outfile = new FileWriter(tmpfilename);
+							StringBuffer text = new StringBuffer();
+							if (m_marginCalculator == null) {
+								m_marginCalculator = new MarginCalculator();
+								m_marginCalculator.calcMargins(m_BayesNet);
+							}
+							text.append(m_marginCalculator.toXMLBIF03());
+							outfile.write(text.toString());
+							outfile.close();
+
+							generator.setOptions(options);
+							generator.generateRandomNetwork();
+							generator.generateInstances();
+							m_Instances = generator.m_Instances;
+							a_learn.setEnabled(true);
+							a_learnCPT.setEnabled(true);
+
+							m_sFile = jTfFile.getText();
+							if (m_sFile != null && !m_sFile.equals("")) {
+								AbstractFileSaver saver = ConverterUtils.getSaverForFile(m_sFile);
+								// no idea what the format is, so let's save it as ARFF file
+								if (saver == null)
+									saver = new ArffSaver();
+								saver.setFile(new File(m_sFile));
+								saver.setInstances(m_Instances);
+								saver.writeBatch();
+							}
+
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+						dlg.setVisible(false);
+					}
+				});
+
+				JButton jBtFile = new JButton("Browse");
+				jBtFile.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						ConverterFileChooser fc = new ConverterFileChooser(System.getProperty("user.dir"));
+						fc.setDialogTitle("Save Instances As");
+						int rval = fc.showSaveDialog(GUI.this);
+
+						if (rval == JFileChooser.APPROVE_OPTION) {
+							String filename = fc.getSelectedFile().toString();
+							jTfFile.setText(filename);
+						}
+						dlg.setVisible(true);
+					}
+				});
+				JButton jBtCancel;
+				jBtCancel = new JButton("Cancel");
+				jBtCancel.setMnemonic('C');
+				jBtCancel.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						dlg.setVisible(false);
+					}
+				});
+				GridBagConstraints gbc = new GridBagConstraints();
+				dlg.setLayout(new GridBagLayout());
+
+				Container c = new Container();
+				c.setLayout(new GridBagLayout());
+				gbc.gridwidth = 2;
+				gbc.insets = new Insets(8, 0, 0, 0);
+				gbc.anchor = GridBagConstraints.NORTHWEST;
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				c.add(jLbNrOfInstances, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfNrOfInstances, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbSeed, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfSeed, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbFile, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfFile, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jBtFile, gbc);
+
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				dlg.add(c, gbc);
+				dlg.add(jBtGo);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				dlg.add(jBtCancel);
+			}
+			dlg.setLocation(100, 100);
+			dlg.setVisible(true);
+			dlg.setSize(dlg.getPreferredSize());
+			dlg.setVisible(false);
+			dlg.setVisible(true);
+			dlg.repaint();
+
+		} // actionPerformed
+	} // class ActionGenerateData
+
+	class ActionLearn extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935516L;
+
+		public ActionLearn() {
+			super("Learn Network", "Learn Bayesian Network", "learn", "ctrl L");
+			setEnabled(false);
+		} // c'tor
+
+		JDialog dlg = null;
+
+		public void actionPerformed(ActionEvent ae) {
+			if (dlg == null) {
+				dlg = new JDialog();
+				dlg.setTitle("Learn Bayesian Network");
+
+				final JButton jBtOptions = new JButton("Options");
+				jBtOptions.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						//m_BayesNet = new EditableBayesNet();
+						try {
+							GenericObjectEditor.registerEditors();
+							GenericObjectEditor ce = new GenericObjectEditor(true);
+							ce.setClassType(weka.classifiers.Classifier.class);
+							ce.setValue(m_BayesNet);
+
+							PropertyDialog pd;
+							if (PropertyDialog.getParentDialog(GUI.this) != null)
+								pd = new PropertyDialog(PropertyDialog.getParentDialog(GUI.this), ce, 100, 100);
+							else
+								pd = new PropertyDialog(PropertyDialog.getParentFrame(GUI.this), ce, 100, 100);
+							pd.addWindowListener(new WindowAdapter() {
+								public void windowClosing(WindowEvent e) {
+									PropertyEditor pe = ((PropertyDialog) e.getSource()).getEditor();
+									Object c = (Object) pe.getValue();
+									String options = "";
+									if (c instanceof OptionHandler) {
+										options = Utils.joinOptions(((OptionHandler) c).getOptions());
+										try {
+											m_BayesNet.setOptions(((OptionHandler) c).getOptions());
+										} catch (Exception e2) {
+											e2.printStackTrace();
+										}
+									}
+									System.out.println(c.getClass().getName() + " " + options);
+									System.exit(0);
+								}
+							});
+							pd.setVisible(true);
+						} catch (Exception ex) {
+							ex.printStackTrace();
+							System.err.println(ex.getMessage());
+						}
+						m_BayesNet.clearUndoStack();
+						a_undo.setEnabled(false);
+						a_redo.setEnabled(false);
+					}
+				});
+
+				final JTextField jTfOptions = new JTextField(40);
+				jTfOptions.setHorizontalAlignment(JTextField.CENTER);
+				jTfOptions.setText("" + Utils.joinOptions(m_BayesNet.getOptions()));
+
+				JButton jBtGo;
+				jBtGo = new JButton("Learn");
+
+				jBtGo.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						try {
+							m_BayesNet.buildClassifier(m_Instances);
+							layoutGraph();
+							updateStatus();
+							m_BayesNet.clearUndoStack();
+
+							dlg.setVisible(false);
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+						dlg.setVisible(false);
+					}
+				});
+
+				JButton jBtCancel;
+				jBtCancel = new JButton("Cancel");
+				jBtCancel.setMnemonic('C');
+				jBtCancel.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						dlg.setVisible(false);
+					}
+				});
+				GridBagConstraints gbc = new GridBagConstraints();
+				dlg.setLayout(new GridBagLayout());
+
+				Container c = new Container();
+				c.setLayout(new GridBagLayout());
+				gbc.gridwidth = 2;
+				gbc.insets = new Insets(8, 0, 0, 0);
+				gbc.anchor = GridBagConstraints.NORTHWEST;
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				c.add(jBtOptions, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jTfOptions, gbc);
+
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				dlg.add(c, gbc);
+				dlg.add(jBtGo);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				dlg.add(jBtCancel);
+			}
+			dlg.setLocation(100, 100);
+			dlg.setVisible(true);
+			dlg.setSize(dlg.getPreferredSize());
+			dlg.setVisible(false);
+			dlg.setVisible(true);
+			dlg.repaint();
+		} // actionPerformed
+	} // class ActionLearn
+
+	class ActionLearnCPT extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2022211085935516L;
+
+		public ActionLearnCPT() {
+			super("Learn CPT", "Learn conditional probability tables", "learncpt", "");
+			setEnabled(false);
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			if (m_Instances == null) {
+				JOptionPane.showMessageDialog(null, "Select instances to learn from first (menu Tools/Set Data)");
+				return;
+			}
+			try {
+				m_BayesNet.setData(m_Instances);
+			} catch (Exception e) {
+				JOptionPane.showMessageDialog(null, "Data set is not compatible with network.\n"+e.getMessage() + "\nChoose other instances (menu Tools/Set Data)");
+				return;
+			}
+			try {
+				m_BayesNet.estimateCPTs();
+				m_BayesNet.clearUndoStack();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			updateStatus();
+		} // actionPerformed
+	} // class ActionLearnCPT
+
+	class ActionSetData extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935519L;
+
+		public ActionSetData() {
+			super("Set Data", "Set Data File", "setdata", "ctrl A");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			ConverterFileChooser fc = new ConverterFileChooser(System.getProperty("user.dir"));
+			fc.setDialogTitle("Set Data File");
+			int rval = fc.showOpenDialog(GUI.this);
+
+			if (rval == JFileChooser.APPROVE_OPTION) {
+				AbstractFileLoader loader = fc.getLoader();
+				try {
+					if (loader != null)
+					  m_Instances = loader.getDataSet();
+					if (m_Instances.classIndex() == -1)
+					  m_Instances.setClassIndex(m_Instances.numAttributes() - 1);
+					a_learn.setEnabled(true);
+					a_learnCPT.setEnabled(true);
+					repaint();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		}
+	} // class ActionSetData
+
+	class ActionUndo extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -3038910085935519L;
+
+		public ActionUndo() {
+			super("Undo", "Undo", "undo", "ctrl Z");
+			setEnabled(false);
+		} // c'tor
+
+		public boolean isEnabled() {
+			return m_BayesNet.canUndo();
+		}
+
+		public void actionPerformed(ActionEvent ae) {
+			String sMsg = m_BayesNet.undo();
+			m_jStatusBar.setText("Undo action performed: " + sMsg);
+			//if (!sMsg.equals("")) {
+			//	JOptionPane.showMessageDialog(null, sMsg, "Undo action successful", JOptionPane.INFORMATION_MESSAGE);
+			//}
+			a_redo.setEnabled(m_BayesNet.canRedo());
+			a_undo.setEnabled(m_BayesNet.canUndo());
+			m_Selection.clear();
+			updateStatus();
+			repaint();
+		}
+	} // ActionUndo
+	
+	class ActionRedo extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -4038910085935519L;
+
+		public ActionRedo() {
+			super("Redo", "Redo", "redo", "ctrl Y");
+			setEnabled(false);
+		} // c'tor
+
+		public boolean isEnabled() {
+			return m_BayesNet.canRedo();
+		}
+
+		public void actionPerformed(ActionEvent ae) {
+			String sMsg = m_BayesNet.redo();
+			m_jStatusBar.setText("Redo action performed: " + sMsg);
+			//if (!sMsg.equals("")) {
+			//	JOptionPane.showMessageDialog(null, sMsg, "Redo action successful", JOptionPane.INFORMATION_MESSAGE);
+			//}
+			m_Selection.clear();
+			updateStatus();
+			repaint();
+		}
+	} // ActionRedo
+
+	class ActionAddNode extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038910085935519L;
+
+		public ActionAddNode() {
+			super("Add Node", "Add Node", "addnode", "");
+		} // c'tor
+
+		JDialog dlg = null;
+
+		JTextField jTfName = new JTextField(20);
+
+		JTextField jTfCard = new JTextField(3);
+
+		int m_X = Integer.MAX_VALUE;
+		int m_Y;
+		public void addNode(int nX, int nY) {
+			m_X = nX;
+			m_Y = nY;
+			addNode();
+		} // addNode
+		
+		void addNode() {
+			if (dlg == null) {
+				dlg = new JDialog();
+				dlg.setTitle("Add node");
+				JLabel jLbName = new JLabel("Name");
+				jTfName.setHorizontalAlignment(JTextField.CENTER);
+				JLabel jLbCard = new JLabel("Cardinality");
+				jTfCard.setHorizontalAlignment(JTextField.CENTER);
+				jTfCard.setText("2");
+
+				JButton jBtCancel;
+				jBtCancel = new JButton("Cancel");
+				jBtCancel.setMnemonic('C');
+				jBtCancel.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						dlg.setVisible(false);
+					}
+				});
+				JButton jBtOk = new JButton("Ok");
+				jBtOk.setMnemonic('O');
+				jBtOk.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						String sName = jTfName.getText();
+						if (sName.length() <= 0) {
+							JOptionPane.showMessageDialog(null, "Name should have at least one character");
+							return;
+						}
+						int nCard = new Integer(jTfCard.getText()).intValue();
+						if (nCard <= 1) {
+							JOptionPane.showMessageDialog(null, "Cardinality should be larger than 1");
+							return;
+						}
+						try {
+							if (m_X < Integer.MAX_VALUE) {
+								m_BayesNet.addNode(sName, nCard, m_X, m_Y);
+							} else {
+								m_BayesNet.addNode(sName, nCard);
+							}
+						    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+							a_undo.setEnabled(true);
+							a_redo.setEnabled(false);
+							//GraphNode n = new GraphNode("id" + m_nodes.size(), sName);
+							//n.probs = m_BayesNet.getDistribution(sName);
+							//n.outcomes = m_BayesNet.getValues(sName);
+							//n.x = 100 + m_nodes.size() * 10;
+							//n.y = 100 + m_nodes.size() * 10;
+							//m_nodes.addElement(n);
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+						repaint();
+						dlg.setVisible(false);
+					}
+				});
+				dlg.setLayout(new GridLayout(3, 2, 10, 10));
+				dlg.add(jLbName);
+				dlg.add(jTfName);
+				dlg.add(jLbCard);
+				dlg.add(jTfCard);
+				dlg.add(jBtOk);
+				dlg.add(jBtCancel);
+				dlg.setSize(dlg.getPreferredSize());
+			}
+			jTfName.setText("Node" + (m_BayesNet.getNrOfNodes() + 1));
+			dlg.setVisible(true);
+		} // addNode
+		
+		public void actionPerformed(ActionEvent ae) {
+			m_X = Integer.MAX_VALUE;
+			addNode();
+		}
+	} // class ActionAddNode
+
+	class ActionDeleteNode extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038912085935519L;
+
+		public ActionDeleteNode() {
+			super("Delete Node", "Delete Node", "delnode", "DELETE");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			if (m_Selection.getSelected().size() > 0) {
+				m_BayesNet.deleteSelection(m_Selection.getSelected());
+			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+				m_Selection.clear();
+				updateStatus();
+				repaint();
+			} else {
+				String[] options = new String[m_BayesNet.getNrOfNodes()];
+				for (int i = 0; i < options.length; i++) {
+					options[i] = m_BayesNet.getNodeName(i);
+				}
+				String sResult = (String) JOptionPane.showInputDialog(null, "Select node to delete", "Nodes", 0, null,
+						options, options[0]);
+				if (sResult != null && !sResult.equals("")) {
+					int iNode = m_BayesNet.getNode2(sResult);
+					deleteNode(iNode);
+				}
+			}
+		}
+	} // class ActionDeleteNode
+
+	class ActionCopyNode extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038732085935519L;
+
+		public ActionCopyNode() {
+			super("Copy", "Copy Nodes", "copy", "ctrl C");
+		} // c'tor
+		
+		public ActionCopyNode(String sName, String sToolTipText, String sIcon, String sAcceleratorKey) {
+			super(sName, sToolTipText, sIcon, sAcceleratorKey);
+		} // c'rot
+		
+		public void actionPerformed(ActionEvent ae) {
+			copy();
+		}
+		
+		public void copy() {
+			String sXML = m_BayesNet.toXMLBIF03(m_Selection.getSelected());
+			m_clipboard.setText(sXML);
+		} // copy
+	} // class ActionCopyNode
+
+	class ActionCutNode extends ActionCopyNode {
+		/** for serialization */
+		private static final long serialVersionUID = -2038822085935519L;
+
+		public ActionCutNode() {
+			super("Cut", "Cut Nodes", "cut", "ctrl X");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			copy();
+			m_BayesNet.deleteSelection(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			m_Selection.clear();
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionCutNode
+
+	class ActionPasteNode extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038732085935519L;
+
+		public ActionPasteNode() {
+			super("Paste", "Paste Nodes", "paste", "ctrl V");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			try {
+				m_BayesNet.paste(m_clipboard.getText());
+				updateStatus();
+			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+		public boolean isEnabled() {
+			return m_clipboard.hasText();
+		}
+	} // class ActionPasteNode
+
+	class ActionSelectAll extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038642085935519L;
+
+		public ActionSelectAll() {
+			super("Select All", "Select All Nodes", "selectall", "ctrl A");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_Selection.selectAll();
+			repaint();
+		}
+	} // class ActionSelectAll
+
+	class ActionExport extends MyAction {
+		boolean m_bIsExporting = false;		
+		/** for serialization */
+		private static final long serialVersionUID = -3027642085935519L;
+
+		public ActionExport() {
+			super("Export", "Export to graphics file", "export", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_bIsExporting = true;
+			m_GraphPanel.saveComponent();
+			m_bIsExporting = false;
+			repaint();
+		}
+		public boolean isExporting() {return m_bIsExporting;}
+	} // class ActionExport
+
+	class ActionAlignLeft extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -3138642085935519L;
+
+		public ActionAlignLeft() {
+			super("Align Left", "Align Left", "alignleft", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.alignLeft(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionAlignLeft
+	
+	class ActionAlignRight extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -4238642085935519L;
+
+		public ActionAlignRight() {
+			super("Align Right", "Align Right", "alignright", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.alignRight(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionAlignRight
+
+	class ActionAlignTop extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -5338642085935519L;
+
+		public ActionAlignTop() {
+			super("Align Top", "Align Top", "aligntop", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.alignTop(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionAlignTop
+
+	class ActionAlignBottom extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -6438642085935519L;
+
+		public ActionAlignBottom() {
+			super("Align Bottom", "Align Bottom", "alignbottom", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.alignBottom(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionAlignBottom
+
+	class ActionCenterHorizontal extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -7538642085935519L;
+
+		public ActionCenterHorizontal() {
+			super("Center Horizontal", "Center Horizontal", "centerhorizontal", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.centerHorizontal(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionCenterHorizontal
+
+	class ActionCenterVertical extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -8638642085935519L;
+
+		public ActionCenterVertical() {
+			super("Center Vertical", "Center Vertical", "centervertical", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.centerVertical(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionCenterVertical
+
+	class ActionSpaceHorizontal extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -9738642085935519L;
+
+		public ActionSpaceHorizontal() {
+			super("Space Horizontal", "Space Horizontal", "spacehorizontal", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.spaceHorizontal(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionSpaceHorizontal
+
+	class ActionSpaceVertical extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -838642085935519L;
+
+		public ActionSpaceVertical() {
+			super("Space Vertical", "Space Vertical", "spacevertical", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_BayesNet.spaceVertical(m_Selection.getSelected());
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			a_undo.setEnabled(true);
+			a_redo.setEnabled(false);
+			repaint();
+		}
+	} // class ActionSpaceVertical
+
+	class ActionAddArc extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038913085935519L;
+
+		public ActionAddArc() {
+			super("Add Arc", "Add Arc", "addarc", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			try {
+				String[] options = new String[m_BayesNet.getNrOfNodes()];
+				for (int i = 0; i < options.length; i++) {
+					options[i] = m_BayesNet.getNodeName(i);
+				}
+				String sChild = (String) JOptionPane.showInputDialog(null, "Select child node", "Nodes", 0, null,
+						options, options[0]);
+				if (sChild == null || sChild.equals("")) {
+					return;
+				}
+				int iChild = m_BayesNet.getNode(sChild);
+				addArcInto(iChild);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+	} // class ActionAddArc
+
+	class ActionDeleteArc extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038914085935519L;
+
+		public ActionDeleteArc() {
+			super("Delete Arc", "Delete Arc", "delarc", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			int nEdges = 0;
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				nEdges += m_BayesNet.getNrOfParents(iNode);
+			}
+			String[] options = new String[nEdges];
+			int i = 0;
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
+					int nParent = m_BayesNet.getParent(iNode, iParent);
+					String sEdge = m_BayesNet.getNodeName(nParent);
+					sEdge += " -> ";
+					sEdge += m_BayesNet.getNodeName(iNode);
+					options[i++] = sEdge;
+				}
+				
+			}
+			deleteArc(options);
+		}
+	} // class ActionDeleteArc
+
+	class ActionNew extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935515L;
+
+		public ActionNew() {
+			super("New", "New Network", "new", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_sFileName = "";
+			m_BayesNet = new EditableBayesNet(true);
+			updateStatus();
+			layoutGraph();
+			a_datagenerator.setEnabled(false);
+			m_BayesNet.clearUndoStack();
+		    m_jStatusBar.setText("New Network");
+			m_Selection = new Selection();
+			repaint();
+		}
+	} // class ActionNew
+
+	class ActionLoad extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935515L;
+
+		public ActionLoad() {
+			super("Load", "Load Graph", "open", "ctrl O");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
+			ExtensionFileFilter ef1 = new ExtensionFileFilter(".arff", "ARFF files");
+			ExtensionFileFilter ef2 = new ExtensionFileFilter(".xml", "XML BIF files");
+			fc.addChoosableFileFilter(ef1);
+			fc.addChoosableFileFilter(ef2);
+			fc.setDialogTitle("Load Graph");
+			int rval = fc.showOpenDialog(GUI.this);
+
+			if (rval == JFileChooser.APPROVE_OPTION) {
+				String sFileName = fc.getSelectedFile().toString();
+				if (sFileName.endsWith(ef1.getExtensions()[0])) {
+					initFromArffFile(sFileName);
+				} else {
+					try {
+						readBIFFromFile(sFileName);
+					} catch (Exception e) {
+						e.printStackTrace();
+					}
+				}
+			    m_jStatusBar.setText("Loaded " + sFileName);
+			    updateStatus();
+			}
+		}
+	} // class ActionLoad
+	
+	
+	class ActionViewStatusbar extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -20389330812354L;
+
+		public ActionViewStatusbar() {
+			super("View statusbar", "View statusbar", "statusbar", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_jStatusBar.setVisible(!m_jStatusBar.isVisible());
+		} // actionPerformed
+	} // class ActionViewStatusbar
+
+	class ActionViewToolbar extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -20389110812354L;
+
+		public ActionViewToolbar() {
+			super("View toolbar", "View toolbar", "toolbar", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			m_jTbTools.setVisible(!m_jTbTools.isVisible());
+		} // actionPerformed
+	} // class ActionViewToolbar
+
+	class ActionSave extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -20389110859355156L;
+
+		public ActionSave() {
+			super("Save", "Save Graph", "save", "ctrl S");
+		} // c'tor
+
+		public ActionSave(String sName, String sToolTipText, String sIcon, String sAcceleratorKey) {
+			super(sName, sToolTipText, sIcon, sAcceleratorKey);
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			if (!m_sFileName.equals("")) {
+				saveFile(m_sFileName);
+				m_BayesNet.isSaved();
+			    m_jStatusBar.setText("Saved as " + m_sFileName);
+			} else {
+				if (saveAs()) {
+					m_BayesNet.isSaved();
+				    m_jStatusBar.setText("Saved as " + m_sFileName);					
+				}
+			}
+		} // actionPerformed
+
+
+		ExtensionFileFilter ef1 = new ExtensionFileFilter(".xml", "XML BIF files");
+
+		boolean saveAs() {
+			JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
+			fc.addChoosableFileFilter(ef1);
+			fc.setDialogTitle("Save Graph As");
+			if (!m_sFileName.equals("")) {
+				// can happen on actionQuit
+				fc.setSelectedFile(new File(m_sFileName));
+			}
+			int rval = fc.showSaveDialog(GUI.this);
+
+			if (rval == JFileChooser.APPROVE_OPTION) {
+				// System.out.println("Saving to file \""+
+				// f.getAbsoluteFile().toString()+"\"");
+				String sFileName = fc.getSelectedFile().toString();
+				if (!sFileName.endsWith(".xml"))
+					sFileName = sFileName.concat(".xml");
+				saveFile(sFileName);
+				return true;
+			}
+			return false;
+		} // saveAs
+
+		protected void saveFile(String sFileName) {
+		    try {
+		        FileWriter outfile = new FileWriter(sFileName);
+		        outfile.write(m_BayesNet.toXMLBIF03());
+		        outfile.close();
+				m_sFileName = sFileName;
+			    m_jStatusBar.setText("Saved as " + m_sFileName);
+		      }
+		      catch(IOException e) { 
+		    	  e.printStackTrace(); 
+		      }
+		  } // saveFile
+	} // class ActionSave
+	
+	class ActionSaveAs extends ActionSave {
+		/** for serialization */
+		private static final long serialVersionUID = -20389110859354L;
+
+		public ActionSaveAs() {
+			super("Save As", "Save Graph As", "saveas", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			saveAs();
+		} // actionPerformed
+	} // class ActionSaveAs
+
+	class ActionPrint extends ActionSave {
+		/** for serialization */
+		private static final long serialVersionUID = -20389001859354L;
+		boolean m_bIsPrinting = false;
+		public ActionPrint() {
+			super("Print", "Print Graph", "print", "ctrl P");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+		    PrinterJob printJob = PrinterJob.getPrinterJob();
+		    printJob.setPrintable(m_GraphPanel);
+		    if (printJob.printDialog())
+		      try { 
+		  		m_bIsPrinting = true;
+		        printJob.print();
+				m_bIsPrinting = false;
+		      } catch(PrinterException pe) {
+		        m_jStatusBar.setText("Error printing: " + pe);
+				m_bIsPrinting = false;
+		      }
+			    m_jStatusBar.setText("Print");
+		} // actionPerformed
+		public boolean isPrinting() {return m_bIsPrinting;}
+
+	} // class ActionPrint
+
+	class ActionQuit extends ActionSave {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935515L;
+
+		public ActionQuit() {
+			super("Exit", "Exit Program", "exit", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			if (m_BayesNet.isChanged()) {
+				int result = JOptionPane.showConfirmDialog(null, "Network changed. Do you want to save it?", "Save before closing?", JOptionPane.YES_NO_CANCEL_OPTION);
+				if (result == JOptionPane.CANCEL_OPTION) {
+					return;
+				}
+				if (result == JOptionPane.YES_OPTION) {
+					if (!saveAs()) {
+						return;
+					}
+				}
+			}
+			System.exit(0);
+		}
+	} // class ActionQuit
+
+	class ActionHelp extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -20389110859354L;
+
+		public ActionHelp() {
+			super("Help", "Bayesian Network Workbench Help", "help", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			JOptionPane.showMessageDialog(null, "See Weka Homepage\nhttp://www.cs.waikato.ac.nz/ml", "Help Message",
+					JOptionPane.PLAIN_MESSAGE);
+		}
+	} // class ActionHelp
+
+	class ActionAbout extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -20389110859353L;
+
+		public ActionAbout() {
+			super("About", "Help about", "about", "");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			JOptionPane.showMessageDialog(null, "Bayesian Network Workbench\nPart of Weka\n2007", "About Message",
+					JOptionPane.PLAIN_MESSAGE);
+		}
+	} // class ActionAbout
+
+	class ActionZoomIn extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -2038911085935515L;
+
+		public ActionZoomIn() {
+			super("Zoom in", "Zoom in", "zoomin", "+");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			int i = 0, s = (int) (m_fScale * 100);
+			if (s < 300)
+				i = s / 25;
+			else if (s < 700)
+				i = 6 + s / 50;
+			else
+				i = 13 + s / 100;
+
+			if (s >= 999) {
+				setEnabled(false);
+				return;
+			} else if (s >= 10) {
+				if (i >= 22) {
+					setEnabled(false);
+				}
+				if (s == 10 && !a_zoomout.isEnabled()) {
+					a_zoomout.setEnabled(true);
+				}
+				m_jTfZoom.setText(m_nZoomPercents[i + 1] + "%");
+				m_fScale = m_nZoomPercents[i + 1] / 100D;
+			} else {
+				if (!a_zoomout.isEnabled())
+					a_zoomout.setEnabled(true);
+				m_jTfZoom.setText(m_nZoomPercents[0] + "%");
+				m_fScale = m_nZoomPercents[0] / 100D;
+			}
+			setAppropriateSize();
+			m_GraphPanel.repaint();
+			m_GraphPanel.invalidate();
+			m_jScrollPane.revalidate();
+		    m_jStatusBar.setText("Zooming in");
+		}
+	} // class ActionZoomIn
+
+	class ActionZoomOut extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -203891108593551L;
+
+		public ActionZoomOut() {
+			super("Zoom out", "Zoom out", "zoomout", "-");
+		} // c'tor
+
+		public void actionPerformed(ActionEvent ae) {
+			int i = 0, s = (int) (m_fScale * 100);
+			if (s < 300)
+				i = (int) Math.ceil(s / 25D);
+			else if (s < 700)
+				i = 6 + (int) Math.ceil(s / 50D);
+			else
+				i = 13 + (int) Math.ceil(s / 100D);
+
+			if (s <= 10) {
+				setEnabled(false);
+			} else if (s < 999) {
+				if (i <= 1) {
+					setEnabled(false);
+				}
+				m_jTfZoom.setText(m_nZoomPercents[i - 1] + "%");
+				m_fScale = m_nZoomPercents[i - 1] / 100D;
+			} else {
+				if (!a_zoomin.isEnabled())
+					a_zoomin.setEnabled(true);
+				m_jTfZoom.setText(m_nZoomPercents[22] + "%");
+				m_fScale = m_nZoomPercents[22] / 100D;
+			}
+			setAppropriateSize();
+			m_GraphPanel.repaint();
+			m_GraphPanel.invalidate();
+			m_jScrollPane.revalidate();
+		    m_jStatusBar.setText("Zooming out");
+		}
+	} // class ActionZoomOut
+
+	class ActionLayout extends MyAction {
+		/** for serialization */
+		private static final long serialVersionUID = -203891108593551L;
+
+		public ActionLayout() {
+			super("Layout", "Layout Graph", "layout", "ctrl L");
+		} // c'tor
+
+		JDialog dlg = null;
+
+		public void actionPerformed(ActionEvent ae) {
+			if (dlg == null) {
+				dlg = new JDialog();
+				dlg.setTitle("Graph Layout Options");
+				final JCheckBox jCbCustomNodeSize = new JCheckBox("Custom Node Size");
+				final JLabel jLbNodeWidth = new JLabel("Width");
+				final JLabel jLbNodeHeight = new JLabel("Height");
+
+				m_jTfNodeWidth.setHorizontalAlignment(JTextField.CENTER);
+				m_jTfNodeWidth.setText("" + m_nNodeWidth);
+				m_jTfNodeHeight.setHorizontalAlignment(JTextField.CENTER);
+				m_jTfNodeHeight.setText("" + m_nNodeHeight);
+				jLbNodeWidth.setEnabled(false);
+				m_jTfNodeWidth.setEnabled(false);
+				jLbNodeHeight.setEnabled(false);
+				m_jTfNodeHeight.setEnabled(false);
+
+				jCbCustomNodeSize.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						if (((JCheckBox) ae.getSource()).isSelected()) {
+							jLbNodeWidth.setEnabled(true);
+							m_jTfNodeWidth.setEnabled(true);
+							jLbNodeHeight.setEnabled(true);
+							m_jTfNodeHeight.setEnabled(true);
+						} else {
+							jLbNodeWidth.setEnabled(false);
+							m_jTfNodeWidth.setEnabled(false);
+							jLbNodeHeight.setEnabled(false);
+							m_jTfNodeHeight.setEnabled(false);
+							setAppropriateSize();
+							setAppropriateNodeSize();
+						}
+					}
+				});
+				JButton jBtLayout;
+				jBtLayout = new JButton("Layout Graph");
+				jBtLayout.setMnemonic('L');
+
+				jBtLayout.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						int tmpW, tmpH;
+
+						if (jCbCustomNodeSize.isSelected()) {
+							try {
+								tmpW = Integer.parseInt(m_jTfNodeWidth.getText());
+							} catch (NumberFormatException ne) {
+								JOptionPane.showMessageDialog(GUI.this.getParent(),
+										"Invalid integer entered for node width.", "Error", JOptionPane.ERROR_MESSAGE);
+								tmpW = m_nNodeWidth;
+								m_jTfNodeWidth.setText("" + m_nNodeWidth);
+
+							}
+							try {
+								tmpH = Integer.parseInt(m_jTfNodeHeight.getText());
+							} catch (NumberFormatException ne) {
+								JOptionPane.showMessageDialog(GUI.this.getParent(),
+										"Invalid integer entered for node height.", "Error", JOptionPane.ERROR_MESSAGE);
+								tmpH = m_nNodeHeight;
+								m_jTfNodeWidth.setText("" + m_nNodeHeight);
+							}
+
+							if (tmpW != m_nNodeWidth || tmpH != m_nNodeHeight) {
+								m_nNodeWidth = tmpW;
+								m_nPaddedNodeWidth = m_nNodeWidth + PADDING;
+								m_nNodeHeight = tmpH;
+							}
+						}
+						// JButton bt = (JButton) ae.getSource();
+						// bt.setEnabled(false);
+						dlg.setVisible(false);
+						updateStatus();
+						layoutGraph();
+					    m_jStatusBar.setText("Laying out Bayes net");
+					}
+				});
+				JButton jBtCancel;
+				jBtCancel = new JButton("Cancel");
+				jBtCancel.setMnemonic('C');
+				jBtCancel.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent ae) {
+						dlg.setVisible(false);
+					}
+				});
+				GridBagConstraints gbc = new GridBagConstraints();
+				dlg.setLayout(new GridBagLayout());
+				//dlg.add(m_le.getControlPanel());
+
+				Container c = new Container();
+				c.setLayout(new GridBagLayout());
+
+				gbc.gridwidth = 1;
+				gbc.insets = new Insets(8, 0, 0, 0);
+				gbc.anchor = GridBagConstraints.NORTHWEST;
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(jCbCustomNodeSize, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbNodeWidth, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(m_jTfNodeWidth, gbc);
+				gbc.gridwidth = GridBagConstraints.RELATIVE;
+				c.add(jLbNodeHeight, gbc);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				c.add(m_jTfNodeHeight, gbc);
+				gbc.fill = GridBagConstraints.HORIZONTAL;
+				dlg.add(c, gbc);
+				dlg.add(jBtLayout);
+				gbc.gridwidth = GridBagConstraints.REMAINDER;
+				dlg.add(jBtCancel);
+			}
+			dlg.setLocation(100, 100);
+			dlg.setVisible(true);
+			dlg.setSize(dlg.getPreferredSize());
+			dlg.setVisible(false);
+			dlg.setVisible(true);
+			dlg.repaint();
+		}
+	} // class ActionLayout
+
+	/**
+	 * Constructor<br>
+	 * Sets up the gui and initializes all the other previously uninitialized
+	 * variables.
+	 */
+	public GUI() {
+		m_GraphPanel = new GraphPanel();
+		m_jScrollPane = new JScrollPane(m_GraphPanel);
+
+		// creating a new layout engine and adding this class as its listener
+		// to receive layoutComplete events
+	
+		m_jTfZoom = new JTextField("100%");
+		m_jTfZoom.setMinimumSize(m_jTfZoom.getPreferredSize());
+		m_jTfZoom.setHorizontalAlignment(JTextField.CENTER);
+		m_jTfZoom.setToolTipText("Zoom");
+
+		m_jTfZoom.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ae) {
+				JTextField jt = (JTextField) ae.getSource();
+				try {
+					int i = -1;
+					i = jt.getText().indexOf('%');
+					if (i == -1)
+						i = Integer.parseInt(jt.getText());
+					else
+						i = Integer.parseInt(jt.getText().substring(0, i));
+
+					if (i <= 999)
+						m_fScale = i / 100D;
+
+					jt.setText((int) (m_fScale * 100) + "%");
+					if (m_fScale > 0.1) {
+						if (!a_zoomout.isEnabled())
+							a_zoomout.setEnabled(true);
+					} else
+						a_zoomout.setEnabled(false);
+					if (m_fScale < 9.99) {
+						if (!a_zoomin.isEnabled())
+							a_zoomin.setEnabled(true);
+					} else
+						a_zoomin.setEnabled(false);
+					setAppropriateSize();
+					// m_GraphPanel.clearBuffer();
+					m_GraphPanel.repaint();
+					m_GraphPanel.invalidate();
+					m_jScrollPane.revalidate();
+				} catch (NumberFormatException ne) {
+					JOptionPane.showMessageDialog(GUI.this.getParent(),
+							"Invalid integer entered for zoom.", "Error", JOptionPane.ERROR_MESSAGE);
+					jt.setText((m_fScale * 100) + "%");
+				}
+			}
+		});
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		final JPanel p = new JPanel(new GridBagLayout());
+		p.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("ExtraControls"), BorderFactory
+				.createEmptyBorder(4, 4, 4, 4)));
+		p.setPreferredSize(new Dimension(0, 0));
+
+		m_jTbTools = new JToolBar();
+		m_jTbTools.setFloatable(false);
+		m_jTbTools.setLayout(new GridBagLayout());
+		gbc.anchor = GridBagConstraints.NORTHWEST;
+		gbc.gridwidth = GridBagConstraints.REMAINDER;
+		gbc.insets = new Insets(0, 0, 0, 0);
+		m_jTbTools.add(p, gbc);
+		gbc.gridwidth = 1;
+
+
+		m_jTbTools.add(a_new);
+		m_jTbTools.add(a_save);
+		m_jTbTools.add(a_load);
+		m_jTbTools.addSeparator(new Dimension(2, 2));
+		m_jTbTools.add(a_cutnode);
+		m_jTbTools.add(a_copynode);
+		m_jTbTools.add(a_pastenode);
+		m_jTbTools.addSeparator(new Dimension(2, 2));
+		m_jTbTools.add(a_undo);
+		m_jTbTools.add(a_redo);
+		m_jTbTools.addSeparator(new Dimension(2, 2));
+		m_jTbTools.add(a_alignleft);
+		m_jTbTools.add(a_alignright);
+		m_jTbTools.add(a_aligntop);
+		m_jTbTools.add(a_alignbottom);
+		m_jTbTools.add(a_centerhorizontal);
+		m_jTbTools.add(a_centervertical);
+		m_jTbTools.add(a_spacehorizontal);
+		m_jTbTools.add(a_spacevertical);
+
+		
+		m_jTbTools.addSeparator(new Dimension(2, 2));
+		m_jTbTools.add(a_zoomin);
+
+		gbc.fill = GridBagConstraints.VERTICAL;
+		gbc.weighty = 1;
+		JPanel p2 = new JPanel(new BorderLayout());
+		p2.setPreferredSize(m_jTfZoom.getPreferredSize());
+		p2.setMinimumSize(m_jTfZoom.getPreferredSize());
+		p2.add(m_jTfZoom, BorderLayout.CENTER);
+		m_jTbTools.add(p2, gbc);
+		gbc.weighty = 0;
+		gbc.fill = GridBagConstraints.NONE;
+
+		m_jTbTools.add(a_zoomout);
+		m_jTbTools.addSeparator(new Dimension(2, 2));
+
+		// jTbTools.add(jBtExtraControls, gbc);
+		m_jTbTools.add(a_layout);
+		m_jTbTools.addSeparator(new Dimension(4, 2));
+		gbc.weightx = 1;
+		gbc.fill = GridBagConstraints.BOTH;
+		//jTbTools.add(m_layoutEngine.getProgressBar(), gbc);
+		m_jStatusBar = new JLabel("Status bar");
+
+		this.setLayout(new BorderLayout());
+		this.add(m_jTbTools, BorderLayout.NORTH);
+		this.add(m_jScrollPane, BorderLayout.CENTER);
+		this.add(m_jStatusBar, BorderLayout.SOUTH);
+
+		updateStatus();
+		a_datagenerator.setEnabled(false);
+		
+		makeMenuBar();
+	}
+	
+	/**
+	 * Get the menu bar for this application.
+	 * 
+	 * @return the menu bar
+	 */
+	public JMenuBar getMenuBar() {
+	  return m_menuBar;
+	}
+	
+	private void makeMenuBar() {
+          m_menuBar = new JMenuBar();
+          JMenu fileMenu = new JMenu("File");
+          fileMenu.setMnemonic('F');
+
+          m_menuBar.add(fileMenu);
+          fileMenu.add(a_new);
+          fileMenu.add(a_load);
+          fileMenu.add(a_save);
+          fileMenu.add(a_saveas);
+          fileMenu.addSeparator();
+          fileMenu.add(a_print);
+          fileMenu.add(a_export);
+          fileMenu.addSeparator();
+          fileMenu.add(a_quit);
+          JMenu editMenu = new JMenu("Edit");
+          editMenu.setMnemonic('E');
+          m_menuBar.add(editMenu);
+          editMenu.add(a_undo);
+          editMenu.add(a_redo);
+          editMenu.addSeparator();
+          editMenu.add(a_selectall);
+          editMenu.add(a_delnode);
+          editMenu.add(a_cutnode);
+          editMenu.add(a_copynode);
+          editMenu.add(a_pastenode);
+          editMenu.addSeparator();
+          editMenu.add(a_addnode);
+          editMenu.add(a_addarc);
+          editMenu.add(a_delarc);
+          editMenu.addSeparator();
+          editMenu.add(a_alignleft);
+          editMenu.add(a_alignright);
+          editMenu.add(a_aligntop);
+          editMenu.add(a_alignbottom);
+          editMenu.add(a_centerhorizontal);
+          editMenu.add(a_centervertical);
+          editMenu.add(a_spacehorizontal);
+          editMenu.add(a_spacevertical);
+
+          JMenu toolMenu = new JMenu("Tools");
+          toolMenu.setMnemonic('T');
+          toolMenu.add(a_networkgenerator);
+          toolMenu.add(a_datagenerator);
+          toolMenu.add(a_datasetter);
+          toolMenu.add(a_learn);
+          toolMenu.add(a_learnCPT);
+          toolMenu.addSeparator();
+          toolMenu.add(a_layout);
+          toolMenu.addSeparator();
+          final JCheckBoxMenuItem viewMargins = new JCheckBoxMenuItem("Show Margins", false);
+          viewMargins.addActionListener(new ActionListener() {
+                  public void actionPerformed(ActionEvent ae) {
+                          boolean bPrev = m_bViewMargins; 
+                          m_bViewMargins = viewMargins.getState();
+                          if (bPrev == false && viewMargins.getState() == true) {
+                                  updateStatus();
+                          }
+                          repaint();
+                  }
+          });
+          toolMenu.add(viewMargins);
+          final JCheckBoxMenuItem viewCliques = new JCheckBoxMenuItem("Show Cliques", false);
+          viewCliques.addActionListener(new ActionListener() {
+                  public void actionPerformed(ActionEvent ae) {
+                          boolean bPrev = m_bViewCliques; 
+                          m_bViewCliques = viewCliques.getState();
+                          if (bPrev == false && viewCliques.getState() == true) {
+                                  updateStatus();
+                          }
+                          repaint();
+                  }
+          });
+          toolMenu.add(viewCliques);
+
+          m_menuBar.add(toolMenu);
+          JMenu viewMenu = new JMenu("View");
+          viewMenu.setMnemonic('V');
+          m_menuBar.add(viewMenu);
+          viewMenu.add(a_zoomin);
+          viewMenu.add(a_zoomout);
+          viewMenu.addSeparator();
+          viewMenu.add(a_viewtoolbar);
+          viewMenu.add(a_viewstatusbar);
+
+          JMenu helpMenu = new JMenu("Help");
+          helpMenu.setMnemonic('H');
+          m_menuBar.add(helpMenu);
+          helpMenu.add(a_help);
+          helpMenu.add(a_about);
+	}
+
+	/**
+	 * This method sets the node size that is appropriate considering the
+	 * maximum label size that is present. It is used internally when custom
+	 * node size checkbox is unchecked.
+	 */
+	protected void setAppropriateNodeSize() {
+		int strWidth;
+		FontMetrics fm = this.getFontMetrics(this.getFont());
+		int nMaxStringWidth = DEFAULT_NODE_WIDTH;
+		if (nMaxStringWidth == 0)
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				strWidth = fm.stringWidth(m_BayesNet.getNodeName(iNode));
+				if (strWidth > nMaxStringWidth)
+					nMaxStringWidth = strWidth;
+			}
+		m_nNodeWidth = nMaxStringWidth + 4;
+		m_nPaddedNodeWidth = m_nNodeWidth + PADDING;
+		m_jTfNodeWidth.setText("" + m_nNodeWidth);
+
+		m_nNodeHeight = 2 * fm.getHeight();
+		m_jTfNodeHeight.setText("" + m_nNodeHeight);
+	}
+
+	/**
+	 * Sets the preferred size for m_GraphPanel GraphPanel to the minimum size that is
+	 * neccessary to display the graph.
+	 */
+	public void setAppropriateSize() {
+		int maxX = 0, maxY = 0;
+
+		m_GraphPanel.setScale(m_fScale, m_fScale);
+
+		for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+			int nPosX = m_BayesNet.getPositionX(iNode); 
+			int nPosY = m_BayesNet.getPositionY(iNode); 
+			if (maxX < nPosX)
+				maxX = nPosX + 100;
+			if (maxY < nPosY)
+				maxY = nPosY;
+		}
+		m_GraphPanel.setPreferredSize(new Dimension((int) ((maxX + m_nPaddedNodeWidth + 2) * m_fScale),
+				(int) ((maxY + m_nNodeHeight + 2) * m_fScale)));
+		m_GraphPanel.revalidate();
+	} // setAppropriateSize
+
+	/**
+	 * This method is an implementation for LayoutCompleteEventListener class.
+	 * It sets the size appropriate for m_GraphPanel GraphPanel and and revalidates it's
+	 * container JScrollPane once a LayoutCompleteEvent is received from the
+	 * LayoutEngine. Also, it updates positions of the Bayesian network stored
+	 * in m_BayesNet.
+	 */
+	public void layoutCompleted(LayoutCompleteEvent le) {
+		LayoutEngine layoutEngine  = m_layoutEngine; // (LayoutEngine) le.getSource();
+		FastVector nPosX = new FastVector(m_BayesNet.getNrOfNodes());
+		FastVector nPosY = new FastVector(m_BayesNet.getNrOfNodes());
+		for (int iNode = 0; iNode < layoutEngine.getNodes().size(); iNode++) {
+			GraphNode gNode = (GraphNode) layoutEngine.getNodes().elementAt(iNode);
+			if (gNode.nodeType == GraphNode.NORMAL) {
+				nPosX.addElement(gNode.x);
+				nPosY.addElement(gNode.y);
+			}
+		}
+		m_BayesNet.layoutGraph(nPosX, nPosY);
+		m_jStatusBar.setText("Graph layed out");
+		a_undo.setEnabled(true);
+		a_redo.setEnabled(false);
+		setAppropriateSize();
+		m_GraphPanel.invalidate();
+		m_jScrollPane.revalidate();
+		m_GraphPanel.repaint();
+	} // layoutCompleted
+
+
+	/**
+	 * BIF reader<br>
+	 * Reads a graph description in XMLBIF03 from an file
+	 * with name sFileName
+	 */
+	public void readBIFFromFile(String sFileName) throws BIFFormatException, IOException {
+		m_sFileName = sFileName;
+		try {
+
+			BIFReader bayesNet = new BIFReader();
+			bayesNet.processFile(sFileName);
+			m_BayesNet = new EditableBayesNet(bayesNet);
+			updateStatus();
+			a_datagenerator.setEnabled(m_BayesNet.getNrOfNodes() > 0);
+			m_BayesNet.clearUndoStack();
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			return;
+		}
+
+		setAppropriateNodeSize();
+		setAppropriateSize();
+	} // readBIFFromFile
+
+	/* read arff file from file sFileName 
+	 * and start new Bayesian network with nodes 
+	 * representing attributes in data set.
+	 */
+	void initFromArffFile(String sFileName) {
+		try {
+			Instances instances = new Instances(new FileReader(sFileName));
+			m_BayesNet = new EditableBayesNet(instances);
+			m_Instances = instances;
+			a_learn.setEnabled(true);
+			a_learnCPT.setEnabled(true);
+			setAppropriateNodeSize();
+			setAppropriateSize();
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			return;
+		}
+	} // initFromArffFile
+	
+	/**
+	 * The panel which contains the actual Bayeian network.
+	 */
+	private class GraphPanel extends PrintablePanel implements Printable {
+
+		/** for serialization */
+		private static final long serialVersionUID = -3562813603236753173L;
+
+		/** node drawing modes */
+		final static int HIGHLIGHTED = 1;
+		final static int NORMAL = 0;
+
+		public GraphPanel() {
+			super();
+			this.addMouseListener(new GraphVisualizerMouseListener());
+			this.addMouseMotionListener(new GraphVisualizerMouseMotionListener());
+			this.setToolTipText("");
+		} // c'tor
+
+		/* For showing instructions when hovering over a node
+		 *  (non-Javadoc)
+		 * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
+		 */
+		public String getToolTipText(MouseEvent me) {
+			int x, y;
+			Rectangle r;
+			x = y  = 0;
+
+			r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
+			x += me.getX();
+			y += me.getY();
+			
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
+				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
+				if (r.contains(x, y)) {
+					return m_BayesNet.getNodeName(iNode) + " (right click to manipulate this node)";
+				}
+			}
+			return null;
+		} // getToolTipText
+
+		/* Code for showing the graph in the panel.
+		 *  (non-Javadoc)
+		 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+		 */
+		public void paintComponent(Graphics gr) {
+			Graphics2D g = (Graphics2D) gr;
+			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+			g.setRenderingHints(rh);
+			g.scale(m_fScale, m_fScale);
+			Rectangle r = g.getClipBounds();
+			g.clearRect(r.x, r.y, r.width, r.height);
+
+			if (m_bViewCliques) {
+				m_nClique = 1;
+				viewCliques(g, m_marginCalculator.m_root);
+			}			
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				drawNode(g, iNode, NORMAL);
+			}
+			if (!a_export.isExporting() && !a_print.isPrinting()) {
+				m_Selection.draw(g);
+			}
+			if (m_nSelectedRect != null) {
+				g.drawRect((int)(m_nSelectedRect.x/ m_fScale), 
+						(int)(m_nSelectedRect.y/ m_fScale), 
+						(int)(m_nSelectedRect.width/ m_fScale), 
+						(int)(m_nSelectedRect.height/ m_fScale));
+			}
+		} // paintComponent
+
+		/** number of the clique being drawn. Used for selecting the color of the clique */
+		int m_nClique = 1;
+
+		/* draws cliques in junction tree. 
+		 * 
+		 */
+		void viewCliques(Graphics g, JunctionTreeNode node) {
+					int [] nodes = node.m_nNodes;
+					g.setColor(
+							new Color(m_nClique % 7 * 256 /7, 
+							(m_nClique % 2 * 256 / 2),
+							(m_nClique % 3 * 256 / 3))
+							);
+					int dX = m_nPaddedNodeWidth / 2 + m_nClique;
+					int dY = m_nNodeHeight / 2;
+					int nPosX = 0;
+					int nPosY = 0;
+					String sStr = "";
+					for (int j = 0; j < nodes.length; j++) {
+						nPosX += m_BayesNet.getPositionX(nodes[j]);
+						nPosY += m_BayesNet.getPositionY(nodes[j]);
+						sStr += " " + nodes[j];
+						for (int k = j+1; k < nodes.length; k++) {
+							g.drawLine(
+									m_BayesNet.getPositionX(nodes[j]) + dX,
+									m_BayesNet.getPositionY(nodes[j]) + dY,
+									m_BayesNet.getPositionX(nodes[k]) + dX,
+									m_BayesNet.getPositionY(nodes[k]) + dY
+									);
+						}
+					}
+					m_nClique++;
+					nPosX /= nodes.length;
+					nPosY /= nodes.length;
+					g.drawString("Clique " + m_nClique + "("+sStr+")", nPosX, nPosY);
+					for (int iChild = 0; iChild < node.m_children.size(); iChild++) {
+						viewCliques(g, (JunctionTreeNode) node.m_children.elementAt(iChild));
+					}
+		} // viewCliques
+		
+		
+		/* Draw a node with index iNode on Graphics g at position
+		 * Drawing mode can be NORMAL or HIGHLIGHTED.
+		 */
+		protected void drawNode(Graphics g, int iNode, int mode) {
+			int nPosX = m_BayesNet.getPositionX(iNode);
+			int nPosY = m_BayesNet.getPositionY(iNode);
+			g.setColor(this.getBackground().darker().darker());
+			FontMetrics fm = getFontMetrics(getFont());
+			
+			if (mode == HIGHLIGHTED) {
+				g.setXORMode(Color.green); // g.setColor(Color.green);
+			}
+			g.fillOval(nPosX + m_nPaddedNodeWidth - m_nNodeWidth - (m_nPaddedNodeWidth - m_nNodeWidth) / 2, nPosY,
+					m_nNodeWidth, m_nNodeHeight);
+			g.setColor(Color.white);
+			if (mode == HIGHLIGHTED) {
+				g.setXORMode(Color.red);
+			}
+
+			// Draw the node's label if it can fit inside the node's
+			// current width otherwise just display its node nr
+			// if it can fit in node's current width
+			if (fm.stringWidth(m_BayesNet.getNodeName(iNode)) <= m_nNodeWidth) {
+				g.drawString(m_BayesNet.getNodeName(iNode), nPosX + m_nPaddedNodeWidth / 2
+						- fm.stringWidth(m_BayesNet.getNodeName(iNode)) / 2, nPosY + m_nNodeHeight / 2
+						+ fm.getHeight() / 2 - 2);
+			} else if (fm.stringWidth("" + iNode) <= m_nNodeWidth) {
+				g.drawString("" + iNode, nPosX + m_nPaddedNodeWidth / 2 - fm.stringWidth("" + iNode) / 2, 
+						nPosY + m_nNodeHeight / 2 + fm.getHeight() / 2 - 2);
+			}
+
+			if (mode == HIGHLIGHTED) {
+				g.setXORMode(Color.green);
+			}
+
+			if (m_bViewMargins) {
+				if (m_BayesNet.getEvidence(iNode) < 0) {
+					g.setColor(new Color(0, 128, 0));
+				} else {
+					g.setColor(new Color(128, 0, 0));
+				}
+				double[] P = m_BayesNet.getMargin(iNode);
+				for (int iValue = 0; iValue < P.length; iValue++) {
+					String sP = P[iValue] + "";
+					if (sP.charAt(0) == '0') {
+						sP = sP.substring(1);
+					}
+					if (sP.length() > 5) {
+						sP = sP.substring(1, 5);
+					}
+					g.fillRect(nPosX + m_nPaddedNodeWidth, nPosY + iValue * 10 + 2, (int) (P[iValue] * 100), 8);
+					g.drawString(m_BayesNet.getNodeValue(iNode, iValue) + " " + sP, nPosX + m_nPaddedNodeWidth
+							+ (int) (P[iValue] * 100), nPosY + iValue * 10 + 10);
+
+				}
+			}
+			if (m_bViewCliques) {
+					return;
+			}
+			g.setColor(Color.black);
+			// Drawing all incoming edges into the node,
+			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
+				int nParent = m_BayesNet.getParent(iNode, iParent);
+				int nPosX1 = nPosX + m_nPaddedNodeWidth / 2;
+				int nPosY1 = nPosY + m_nNodeHeight;
+				int nPosX2 = m_BayesNet.getPositionX(nParent);
+				int nPosY2 = m_BayesNet.getPositionY(nParent);
+				int nPosX2b = nPosX2 + m_nPaddedNodeWidth / 2;
+				int nPosY2b = nPosY2;
+
+				double phi = Math.atan2((nPosX2b - nPosX1 + 0.0) * m_nNodeHeight, (nPosY2b - nPosY1 + 0.0) * m_nNodeWidth);
+				nPosX1 = (int) (nPosX + m_nPaddedNodeWidth / 2 + Math.sin(phi) * m_nNodeWidth / 2);
+				nPosY1 = (int) (nPosY + m_nNodeHeight / 2 + Math.cos(phi) * m_nNodeHeight / 2);
+				nPosX2b = (int) (nPosX2 + m_nPaddedNodeWidth / 2 - Math.sin(phi) * m_nNodeWidth / 2);
+				nPosY2b = (int) (nPosY2 + m_nNodeHeight / 2 - Math.cos(phi) * m_nNodeHeight / 2);
+				drawArrow(g, nPosX2b, nPosY2b, nPosX1, nPosY1);
+			}
+			if (mode == HIGHLIGHTED) {
+			FastVector children = m_BayesNet.getChildren(iNode);
+			for (int iChild = 0; iChild < children.size(); iChild++) {
+				int nChild = (Integer) children.elementAt(iChild);
+				int nPosX1 = nPosX + m_nPaddedNodeWidth / 2;
+				int nPosY1 = nPosY;
+				int nPosX2 = m_BayesNet.getPositionX(nChild);
+				int nPosY2 = m_BayesNet.getPositionY(nChild);
+				int nPosX2b = nPosX2 + m_nPaddedNodeWidth / 2;
+				int nPosY2b = nPosY2 + m_nNodeHeight;
+
+				double phi = Math.atan2((nPosX2b - nPosX1 + 0.0) * m_nNodeHeight, (nPosY2b - nPosY1 + 0.0) * m_nNodeWidth);
+				nPosX1 = (int) (nPosX + m_nPaddedNodeWidth / 2 + Math.sin(phi) * m_nNodeWidth / 2);
+				nPosY1 = (int) (nPosY + m_nNodeHeight / 2 + Math.cos(phi) * m_nNodeHeight / 2);
+				nPosX2b = (int) (nPosX2 + m_nPaddedNodeWidth / 2 - Math.sin(phi) * m_nNodeWidth / 2);
+				nPosY2b = (int) (nPosY2 + m_nNodeHeight / 2 - Math.cos(phi) * m_nNodeHeight / 2);
+				drawArrow(g, nPosX1, nPosY1, nPosX2b, nPosY2b);
+			}
+			}
+		} // drawNode
+
+		
+		/**
+		 * This method draws an arrow on a line from (x1,y1) to (x2,y2). The
+		 * arrow head is seated on (x2,y2) and is in the direction of the line.
+		 * If the arrow is needed to be drawn in the opposite direction then
+		 * simply swap the order of (x1, y1) and (x2, y2) when calling this
+		 * function.
+		 */
+		protected void drawArrow(Graphics g, int nPosX1, int nPosY1, int nPosX2, int nPosY2) {
+			g.drawLine(nPosX1, nPosY1, nPosX2, nPosY2);
+
+			if (nPosX1 == nPosX2) {
+				if (nPosY1 < nPosY2) {
+					g.drawLine(nPosX2, nPosY2, nPosX2 + 4, nPosY2 - 8);
+					g.drawLine(nPosX2, nPosY2, nPosX2 - 4, nPosY2 - 8);
+				} else {
+					g.drawLine(nPosX2, nPosY2, nPosX2 + 4, nPosY2 + 8);
+					g.drawLine(nPosX2, nPosY2, nPosX2 - 4, nPosY2 + 8);
+				}
+			} else {
+				// theta=line's angle from base, beta=angle of arrow's side from
+				// line
+				double hyp = 0, base = 0, perp = 0, theta, beta;
+				int nPosX3 = 0, nPosY3 = 0;
+
+				if (nPosX2 < nPosX1) {
+					base = nPosX1 - nPosX2;
+					hyp = Math.sqrt((nPosX2 - nPosX1) * (nPosX2 - nPosX1) + (nPosY2 - nPosY1) * (nPosY2 - nPosY1));
+					theta = Math.acos(base / hyp);
+				} else { // x1>x2 as we already checked x1==x2 before
+					base = nPosX1 - nPosX2;
+					hyp = Math.sqrt((nPosX2 - nPosX1) * (nPosX2 - nPosX1) + (nPosY2 - nPosY1) * (nPosY2 - nPosY1));
+					theta = Math.acos(base / hyp);
+				}
+				beta = 30 * Math.PI / 180;
+
+				hyp = 8;
+				base = Math.cos(theta - beta) * hyp;
+				perp = Math.sin(theta - beta) * hyp;
+
+				nPosX3 = (int) (nPosX2 + base);
+				if (nPosY1 < nPosY2)
+					nPosY3 = (int) (nPosY2 - perp);
+				else
+					nPosY3 = (int) (nPosY2 + perp);
+
+				g.drawLine(nPosX2, nPosY2, nPosX3, nPosY3);
+
+				base = Math.cos(theta + beta) * hyp;
+				perp = Math.sin(theta + beta) * hyp;
+
+				nPosX3 = (int) (nPosX2 + base);
+				if (nPosY1 < nPosY2)
+					nPosY3 = (int) (nPosY2 - perp);
+				else
+					nPosY3 = (int) (nPosY2 + perp);
+				g.drawLine(nPosX2, nPosY2, nPosX3, nPosY3);
+			}
+		} // drawArrow
+
+		/**
+		 * This method highlights a given node and all its incoming and outgoing arcs
+		 */
+		public void highLight(int iNode) {
+			Graphics2D g = (Graphics2D) this.getGraphics();
+			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+			g.setRenderingHints(rh);
+			g.setPaintMode();
+			g.scale(m_fScale, m_fScale);
+			drawNode(g, iNode, HIGHLIGHTED);
+		} // highlight
+
+	    /** implementation of Printable, used for printing
+	     * @see Printable
+	     */
+		public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
+	          if (pageIndex > 0) {
+	            return(NO_SUCH_PAGE);
+	          } else {
+	            Graphics2D g2d = (Graphics2D)g;
+	            g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+	            double fHeight = pageFormat.getImageableHeight();
+	            double fWidth = pageFormat.getImageableWidth();
+	            int xMax = 1;
+	            int yMax = 1;
+	            for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+	            	if (xMax < m_BayesNet.getPositionX(iNode)) {
+	            		xMax = m_BayesNet.getPositionX(iNode);
+	            	}
+	            	if (yMax < m_BayesNet.getPositionY(iNode)) {
+	            		yMax = m_BayesNet.getPositionY(iNode);
+	            	}
+	            }
+	            double fCurrentScale = m_fScale;
+	            xMax += m_nPaddedNodeWidth + 100;
+	            if (fWidth/xMax < fHeight/yMax) {
+	            	m_fScale = fWidth/xMax; 
+	            } else {
+	            	m_fScale = fHeight/yMax;
+	            }
+	            
+	            // Turn off double buffering
+	            paint(g2d);
+	            m_fScale = fCurrentScale;
+	            // Turn double buffering back on
+	            return(PAGE_EXISTS);
+	          }
+	        } // print
+				
+	} // class GraphPanel
+
+	/**
+	 * Table Model for the Table for editing CPTs
+	 */
+	private class GraphVisualizerTableModel extends AbstractTableModel {
+
+		/** for serialization */
+		private static final long serialVersionUID = -4789813491347366596L;
+		/** labels for the columns */
+		final String [] m_sColumnNames;
+		/** probability table data **/
+		final double [][] m_fProbs;
+		/** nr of node for currently editted CPT */
+		int m_iNode;
+
+		public GraphVisualizerTableModel(int iNode) {
+			m_iNode = iNode;
+			double [][] probs = m_BayesNet.getDistribution(iNode);
+			m_fProbs = new double[probs.length][probs[0].length];
+			for (int i = 0; i < probs.length; i++) {
+				for (int j = 0; j < probs[0].length; j++) {
+					m_fProbs[i][j] = probs[i][j];
+				}
+			}
+			m_sColumnNames = m_BayesNet.getValues(iNode);
+		} // c'tor
+
+		/** method that generates random CPTs
+		 */
+		public void randomize() {
+			int nProbs = m_fProbs[0].length;
+			Random random = new Random();
+			for (int i = 0; i < m_fProbs.length; i++) {
+				// get random nrs
+				for (int j = 0; j < nProbs-1; j++) {
+					m_fProbs[i][j] = random.nextDouble();
+				}
+				// sort
+				for (int j = 0; j < nProbs-1; j++) {
+					for (int k = j+1; k < nProbs-1; k++) {
+						if (m_fProbs[i][j] > m_fProbs[i][k]) {
+							double h = m_fProbs[i][j]; 
+							m_fProbs[i][j] = m_fProbs[i][k];
+							m_fProbs[i][k] = h;
+						}
+					}
+				}
+				double sum = m_fProbs[i][0];
+				for (int j = 1; j < nProbs-1; j++) {
+					m_fProbs[i][j] = m_fProbs[i][j] - sum;
+					sum += m_fProbs[i][j]; 
+				}
+				m_fProbs[i][nProbs - 1] = 1.0 - sum;
+			}
+		} // randomize
+		
+		public void setData() {}
+
+		/** return nr of colums */
+		public int getColumnCount() {
+			return m_sColumnNames.length;
+		}
+
+		/** return nr of rows */
+		public int getRowCount() {
+			return m_fProbs.length;
+		}
+
+		/** return name of specified colum 
+		 * @param iCol index of the column
+		 */
+		public String getColumnName(int iCol) {
+			return m_sColumnNames[iCol];
+		}
+		/** return data point 
+		 * @param iRow index of row in table
+		 * @param iCol index of column in table
+		 */
+		public Object getValueAt(int iRow, int iCol) {
+			return new Double(m_fProbs[iRow][iCol]);
+		}
+
+		/** Set data point, assigns value to CPT entry
+		 * specified by row and column. The remainder of the
+		 * CPT is normalized so that the values add up to 1.
+		 * IF a value below zero of over 1 is given, no changes
+		 * take place.
+		 * @param oProb data point
+		 * @param iRow index of row in table
+		 * @param iCol index of column in table
+		 */
+		public void setValueAt(Object oProb, int iRow, int iCol) {
+			Double fProb = (Double) oProb;
+			if (fProb < 0 || fProb > 1) {
+				return;
+			}
+			m_fProbs[iRow][iCol] = (double) fProb;
+			double sum = 0;
+			for (int i = 0; i < m_fProbs[iRow].length; i++) {
+				sum += m_fProbs[iRow][i];
+			}
+
+			if (sum > 1) {
+				// handle overflow
+				int i = m_fProbs[iRow].length - 1;
+				while (sum > 1) {
+					if (i != iCol) {
+						if (m_fProbs[iRow][i] > sum - 1) {
+							m_fProbs[iRow][i] -= sum - 1;
+							sum = 1;
+						} else {
+							sum -= m_fProbs[iRow][i];
+							m_fProbs[iRow][i] = 0;
+						}
+					}
+					i--;
+				}
+			} else {
+				// handle underflow
+				int i = m_fProbs[iRow].length - 1;
+				while (sum < 1) {
+					if (i != iCol) {
+						m_fProbs[iRow][i] += 1 - sum;
+						sum = 1;
+					}
+					i--;
+				}
+
+			}
+			validate();
+		} // setData
+
+		/*
+		 * JTable uses this method to determine the default renderer/ editor for
+		 * each cell.
+		 */
+		public Class getColumnClass(int c) {
+			return getValueAt(0, c).getClass();
+		}
+
+		/*
+		 * Implemented this to make sure the table is uneditable.
+		 */
+		public boolean isCellEditable(int row, int col) {
+			return true;
+		}
+	} // class GraphVisualizerTableModel
+
+	/**
+	 * Listener class for processing mouseClicked
+	 */
+	private class GraphVisualizerMouseListener extends MouseAdapter {
+
+		/** A left mouseclick on a node adds node to selection (depending
+		 * on shift and ctrl keys).
+		 * A right mouseclick on a node pops up menu with actions to be
+		 * performed on the node.
+		 * A right mouseclick outside another node pops up menu.
+		 */
+		public void mouseClicked(MouseEvent me) {
+			int x, y;
+
+			Rectangle r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
+			x = me.getX();
+			y = me.getY();
+
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
+				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
+				if (r.contains(x, y)) {
+					m_nCurrentNode = iNode;					
+					if (me.getButton() == MouseEvent.BUTTON3) {
+						handleRightNodeClick(me);
+					}
+					if (me.getButton() == MouseEvent.BUTTON1) {
+						if ((me.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0) {
+							m_Selection.toggleSelection(m_nCurrentNode);
+						} else if ((me.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+							m_Selection.addToSelection(m_nCurrentNode);
+						} else {
+							m_Selection.clear();
+							m_Selection.addToSelection(m_nCurrentNode);
+						}
+						repaint();
+					}
+					return;
+				}
+			}
+			if (me.getButton() == MouseEvent.BUTTON3) {
+				handleRightClick(me, (int)(x/m_fScale), (int)(y/m_fScale));
+			}
+		} // mouseClicked
+		
+		/* update selection
+		 *  (non-Javadoc)
+		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+		 */
+		public void mouseReleased(MouseEvent me) {
+	    	if (m_nSelectedRect != null) {
+				if ((me.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0) {
+					m_Selection.toggleSelection(m_nSelectedRect);
+				} else if ((me.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
+					m_Selection.addToSelection(m_nSelectedRect);
+				} else {
+					m_Selection.clear();
+		    		m_Selection.addToSelection(m_nSelectedRect);
+				}
+	    		m_nSelectedRect = null;
+	    		repaint();
+	    	}
+	    } // mouseReleased
+
+		/** position clicked on */
+	    int m_nPosX = 0, m_nPosY = 0;
+	    
+	    /* pop up menu with actions that apply in general or to selection (if any exists)
+	     */
+	    void handleRightClick(MouseEvent me, int nPosX, int nPosY) {
+	
+			ActionListener act = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					if (ae.getActionCommand().equals("Add node")) {
+						a_addnode.addNode(m_nPosX, m_nPosY);
+						return;
+					}
+					repaint();
+				}
+			};
+			JPopupMenu popupMenu = new JPopupMenu("Choose a value");
+
+			JMenuItem addNodeItem = new JMenuItem("Add node");
+			addNodeItem.addActionListener(act);
+			popupMenu.add(addNodeItem);
+
+			FastVector selected = m_Selection.getSelected();
+			JMenu addArcMenu = new JMenu("Add parent");
+			popupMenu.add(addArcMenu);
+			if (selected.size() == 0) {
+				addArcMenu.setEnabled(false);
+			} else {
+			int nNodes = m_BayesNet.getNrOfNodes();
+			boolean[] isNotAllowedAsParent = new boolean[nNodes];
+			// prevent it being a parent of itself
+			for (int iNode = 0; iNode < selected.size(); iNode++) {
+				isNotAllowedAsParent[(Integer) selected.elementAt(iNode)] = true;
+			}
+			// prevent a descendant being a parent, since it introduces cycles
+			for (int i = 0; i < nNodes; i++) {
+				for (int iNode = 0; iNode < nNodes; iNode++) {
+					for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
+						if (isNotAllowedAsParent[m_BayesNet.getParent(iNode, iParent)]) {
+							isNotAllowedAsParent[iNode] = true;
+						}
+					}
+				}
+			}
+			// prevent nodes that are already a parent
+			for (int iNode = 0; iNode < selected.size(); iNode++) {
+				int nNode = (Integer) selected.elementAt(iNode);
+				for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(nNode); iParent++) {
+					isNotAllowedAsParent[m_BayesNet.getParent(nNode, iParent)] = true;
+				}
+			}
+			ActionListener addParentAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					try {
+						m_BayesNet.addArc(ae.getActionCommand(), m_Selection.getSelected());
+						m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+						updateStatus();
+					} catch (Exception e) {
+						e.printStackTrace();
+					}
+				}
+			};
+			// count nr of remaining candidates
+			int nCandidates = 0;
+			for (int i = 0; i < nNodes; i++) {
+				if (!isNotAllowedAsParent[i]) {
+					JMenuItem item = new JMenuItem(m_BayesNet.getNodeName(i));
+					item.addActionListener(addParentAction);
+					addArcMenu.add(item);
+					nCandidates++;
+				}
+			}
+			if (nCandidates == 0) {
+				addArcMenu.setEnabled(false);
+			}
+			}
+			m_nPosX = nPosX;
+			m_nPosY = nPosY;
+			popupMenu.setLocation(me.getX(), me.getY());
+			popupMenu.show(m_GraphPanel, me.getX(), me.getY());
+	    } // handleRightClick
+		
+	    /* pop up menu with actions that apply to node that was clicked on
+	     */
+	    void handleRightNodeClick(MouseEvent me) {
+			m_Selection.clear();
+			repaint();
+			ActionListener renameValueAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					renameValue(m_nCurrentNode, ae.getActionCommand());
+				}
+			};
+			ActionListener delValueAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					delValue(m_nCurrentNode, ae.getActionCommand());
+				}
+			};
+			ActionListener addParentAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					try {
+						m_BayesNet.addArc(ae.getActionCommand(), m_BayesNet.getNodeName(m_nCurrentNode));
+						m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+						updateStatus();
+					} catch (Exception e) {
+						e.printStackTrace();
+					}
+				}
+			};
+			ActionListener delParentAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					deleteArc(m_nCurrentNode, ae.getActionCommand());
+				}
+			};
+			ActionListener delChildAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					deleteArc(ae.getActionCommand(), m_nCurrentNode);
+				}
+			};
+			ActionListener setAvidenceAction = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					try {
+						String [] outcomes = m_BayesNet.getValues(m_nCurrentNode);
+						int iValue = 0;
+						while (iValue < outcomes.length && !outcomes[iValue].equals(ae.getActionCommand())) {
+							iValue++;
+						}
+
+						if (iValue == outcomes.length) {
+							iValue = -1;
+						}
+						if (iValue < outcomes.length) {
+						    m_jStatusBar.setText("Set evidence for " + m_BayesNet.getNodeName(m_nCurrentNode));
+								if (m_BayesNet.getEvidence(m_nCurrentNode) < 0 && iValue >= 0) {
+									m_BayesNet.setEvidence(m_nCurrentNode, iValue);
+									m_marginCalculatorWithEvidence.setEvidence(m_nCurrentNode, iValue);
+								} else {
+									m_BayesNet.setEvidence(m_nCurrentNode, iValue);
+									SerializedObject so = new SerializedObject(m_marginCalculator);
+									m_marginCalculatorWithEvidence = (MarginCalculator) so.getObject();
+									for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+										if (m_BayesNet.getEvidence(iNode) >= 0) {
+											m_marginCalculatorWithEvidence.setEvidence(iNode, m_BayesNet.getEvidence(iNode));
+										}
+									}
+								}
+							for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+								m_BayesNet.setMargin(iNode, m_marginCalculatorWithEvidence.getMargin(iNode));
+							}
+						}
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+						repaint();
+				}
+			};
+			
+			ActionListener act = new ActionListener() {
+				public void actionPerformed(ActionEvent ae) {
+					if (ae.getActionCommand().equals("Rename")) {
+						renameNode(m_nCurrentNode);
+						return;
+					}
+					if (ae.getActionCommand().equals("Add parent")) {
+						addArcInto(m_nCurrentNode);
+						return;
+					}
+					if (ae.getActionCommand().equals("Add value")) {
+						addValue();
+						return;
+					}
+					if (ae.getActionCommand().equals("Delete node")) {
+						deleteNode(m_nCurrentNode);
+						return;
+					}
+					if (ae.getActionCommand().equals("Edit CPT")) {
+						editCPT(m_nCurrentNode);
+						return;
+					}
+					repaint();
+				}
+			};
+			try {
+			JPopupMenu popupMenu = new JPopupMenu("Choose a value");
+
+			JMenu setEvidenceMenu = new JMenu("Set evidence");
+			String [] outcomes = m_BayesNet.getValues(m_nCurrentNode);
+			for (int iValue = 0; iValue < outcomes.length; iValue++) {
+				JMenuItem item = new JMenuItem(outcomes[iValue]);
+				item.addActionListener(setAvidenceAction);
+				setEvidenceMenu.add(item);
+			}
+			setEvidenceMenu.addSeparator();
+			JMenuItem item = new JMenuItem("Clear");
+			item.addActionListener(setAvidenceAction);
+			setEvidenceMenu.add(item);
+			popupMenu.add(setEvidenceMenu);
+
+			setEvidenceMenu.setEnabled(m_bViewMargins);
+
+			popupMenu.addSeparator();
+
+			JMenuItem renameItem = new JMenuItem("Rename");
+			renameItem.addActionListener(act);
+			popupMenu.add(renameItem);
+
+			JMenuItem delNodeItem = new JMenuItem("Delete node");
+			delNodeItem.addActionListener(act);
+			popupMenu.add(delNodeItem);
+
+			JMenuItem editCPTItem = new JMenuItem("Edit CPT");
+			editCPTItem.addActionListener(act);
+			popupMenu.add(editCPTItem);
+
+			popupMenu.addSeparator();
+			
+			JMenu addArcMenu = new JMenu("Add parent");
+			popupMenu.add(addArcMenu);
+			int nNodes = m_BayesNet.getNrOfNodes();
+			boolean[] isNotAllowedAsParent = new boolean[nNodes];
+			// prevent it being a parent of itself
+			isNotAllowedAsParent[m_nCurrentNode] = true;
+			// prevent a descendant being a parent, since it introduces cycles
+			for (int i = 0; i < nNodes; i++) {
+				for (int iNode = 0; iNode < nNodes; iNode++) {
+					for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
+						if (isNotAllowedAsParent[m_BayesNet.getParent(iNode, iParent)]) {
+							isNotAllowedAsParent[iNode] = true;
+						}
+					}
+				}
+			}
+			// prevent nodes that are already a parent
+			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(m_nCurrentNode); iParent++) {
+				isNotAllowedAsParent[m_BayesNet.getParent(m_nCurrentNode, iParent)] = true;
+			}
+			// count nr of remaining candidates
+			int nCandidates = 0;
+			for (int i = 0; i < nNodes; i++) {
+				if (!isNotAllowedAsParent[i]) {
+					item = new JMenuItem(m_BayesNet.getNodeName(i));
+					item.addActionListener(addParentAction);
+					addArcMenu.add(item);
+					nCandidates++;
+				}
+			}
+			if (nCandidates == 0) {
+				addArcMenu.setEnabled(false);
+			}
+									
+			JMenu delArcMenu = new JMenu("Delete parent");
+			popupMenu.add(delArcMenu);
+			if (m_BayesNet.getNrOfParents(m_nCurrentNode) == 0) {
+				delArcMenu.setEnabled(false);
+			}
+			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(m_nCurrentNode); iParent++) {
+				item = new JMenuItem(m_BayesNet.getNodeName(m_BayesNet.getParent(m_nCurrentNode, iParent)));
+				item.addActionListener(delParentAction);
+				delArcMenu.add(item);
+			}
+			JMenu delChildMenu = new JMenu("Delete child");
+			popupMenu.add(delChildMenu);
+			FastVector nChildren = m_BayesNet.getChildren(m_nCurrentNode); 
+			if (nChildren.size() == 0) {
+				delChildMenu.setEnabled(false);
+			}
+			for (int iChild = 0; iChild < nChildren.size(); iChild++) {
+				item = new JMenuItem(m_BayesNet.getNodeName((Integer) nChildren.elementAt(iChild)));
+				item.addActionListener(delChildAction);
+				delChildMenu.add(item);
+			}
+
+			popupMenu.addSeparator();
+
+			JMenuItem addValueItem = new JMenuItem("Add value");
+			addValueItem.addActionListener(act);
+			popupMenu.add(addValueItem);
+
+			JMenu renameValue = new JMenu("Rename value");
+			popupMenu.add(renameValue);
+			for (int iValue = 0; iValue < outcomes.length; iValue++) {
+				item = new JMenuItem(outcomes[iValue]);
+				item.addActionListener(renameValueAction);
+				renameValue.add(item);
+			}
+
+			JMenu delValue = new JMenu("Delete value");
+			popupMenu.add(delValue);
+			if (m_BayesNet.getCardinality(m_nCurrentNode) <= 2) {
+				delValue.setEnabled(false);
+			}
+			for (int iValue = 0; iValue < outcomes.length; iValue++) {
+				JMenuItem delValueItem = new JMenuItem(outcomes[iValue]);
+				delValueItem.addActionListener(delValueAction);
+				delValue.add(delValueItem);
+			}
+			
+			popupMenu.setLocation(me.getX(), me.getY());
+			popupMenu.show(m_GraphPanel, me.getX(), me.getY());
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} // handleRightNodeClick
+	} // class GraphVisualizerMouseListener
+
+	/**
+	 * private class for handling mouseMoved events to highlight nodes if the
+	 * the mouse is moved on one, move it around or move selection around
+	 */
+	private class GraphVisualizerMouseMotionListener extends MouseMotionAdapter {
+
+		/* last node moved over. Used for turning highlight on and off */
+		int m_nLastNode = -1;
+		/* current mouse position clicked */
+		int m_nPosX, m_nPosY;
+
+		/* identify the node under the mouse
+		 * @returns node index of node under mouse, or -1 if there is no such node 
+		 */
+		int getGraphNode(MouseEvent me) {
+			m_nPosX = m_nPosY  = 0;
+
+			Rectangle r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
+			m_nPosX += me.getX();
+			m_nPosY += me.getY();
+
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {				
+				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
+				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
+				if (r.contains(m_nPosX, m_nPosY)) {
+					return iNode;
+				}
+			}
+			return -1;
+		} // getGraphNode
+
+		/* handle mouse dragging event
+		 *  (non-Javadoc)
+		 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
+		 */
+		public void mouseDragged(MouseEvent me) {
+			if (m_nSelectedRect != null) {
+				m_nSelectedRect.width = me.getPoint().x - m_nSelectedRect.x;
+				m_nSelectedRect.height = me.getPoint().y - m_nSelectedRect.y;
+				repaint();
+				return;
+			}
+			int iNode = getGraphNode(me);
+			if (iNode >= 0) {
+				if (m_Selection.getSelected().size() > 0) {
+					if (m_Selection.getSelected().contains(iNode)) {
+						m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
+							(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)), m_Selection.getSelected());
+					} else {
+						m_Selection.clear();
+						m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
+								(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)));
+					}
+					repaint();
+				} else {
+					m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
+						(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)));
+				}
+			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+				a_undo.setEnabled(true);
+				a_redo.setEnabled(false);
+				m_GraphPanel.highLight(iNode);
+			}
+			if (iNode < 0) {
+				if (m_nLastNode >= 0) {
+					m_GraphPanel.repaint();
+					m_nLastNode = -1;
+				} else {
+					m_nSelectedRect = new Rectangle(me.getPoint().x, me.getPoint().y, 1, 1);
+					m_GraphPanel.repaint();
+				}
+			}
+		} // mouseDragged
+		
+		/* handles mouse move event
+		 *  (non-Javadoc)
+		 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+		 */
+		public void mouseMoved(MouseEvent me) {
+			int iNode = getGraphNode(me);
+			if (iNode >= 0) {
+				if (iNode != m_nLastNode) {
+					m_GraphPanel.highLight(iNode);
+					if (m_nLastNode >= 0) {
+						m_GraphPanel.highLight(m_nLastNode);
+					}
+					m_nLastNode = iNode;
+				}
+			}
+			if (iNode < 0 && m_nLastNode >= 0) {
+				m_GraphPanel.repaint();
+				m_nLastNode = -1;
+			}
+		} // mouseMoved
+		
+	} // class GraphVisualizerMouseMotionListener
+
+	/* apply graph layout algorithm to Bayesian network 
+	 */
+	void layoutGraph() {
+		if (m_BayesNet.getNrOfNodes() == 0) {
+			return;
+		}
+		try {
+			FastVector m_nodes = new FastVector();
+			FastVector m_edges = new FastVector();
+			BIFParser bp = new BIFParser(m_BayesNet.toXMLBIF03(), m_nodes, m_edges);
+			bp.parse();
+			updateStatus();
+			m_layoutEngine = new HierarchicalBCEngine(m_nodes, m_edges, m_nPaddedNodeWidth, m_nNodeHeight);
+			m_layoutEngine.addLayoutCompleteEventListener(this);
+			m_layoutEngine.layoutGraph();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	} // layoutGraph
+	
+	/* Update status of various items that need regular updating
+	 * such as enabled status of some menu items, marginal distributions
+	 * if shown, repainting of graph.
+	 */
+	void updateStatus() {
+		a_undo.setEnabled(m_BayesNet.canUndo());
+		a_redo.setEnabled(m_BayesNet.canRedo());
+
+		a_datagenerator.setEnabled(m_BayesNet.getNrOfNodes() > 0);
+
+		if (!m_bViewMargins && !m_bViewCliques) {
+			repaint();
+			return;
+		}
+
+		try {
+			m_marginCalculator = new MarginCalculator();
+			m_marginCalculator.calcMargins(m_BayesNet);
+			SerializedObject so = new SerializedObject(m_marginCalculator);
+			m_marginCalculatorWithEvidence = (MarginCalculator) so.getObject();
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				if (m_BayesNet.getEvidence(iNode) >= 0) {
+					m_marginCalculatorWithEvidence.setEvidence(iNode, m_BayesNet.getEvidence(iNode));
+				}
+			}
+			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+				m_BayesNet.setMargin(iNode, m_marginCalculatorWithEvidence.getMargin(iNode));
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		repaint();
+	} // updateStatus
+
+	/* add arc with node iChild as child. 
+	 * This pops up a selection list with potential parents for the child.
+	 * All decendants and current parents are excluded from the list as is
+	 * the child node itself.
+	 * @param iChild index of the node for which to add an arc
+	 */
+	void addArcInto(int iChild) {
+		String sChild = m_BayesNet.getNodeName(iChild);
+		try {
+			int nNodes = m_BayesNet.getNrOfNodes();
+			boolean[] isNotAllowedAsParent = new boolean[nNodes];
+			// prevent it being a parent of itself
+			isNotAllowedAsParent[iChild] = true;
+			// prevent a descendant being a parent, since it introduces cycles
+			for (int i = 0; i < nNodes; i++) {
+				for (int iNode = 0; iNode < nNodes; iNode++) {
+					for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
+						if (isNotAllowedAsParent[m_BayesNet.getParent(iNode, iParent)]) {
+							isNotAllowedAsParent[iNode] = true;
+						}
+					}
+				}
+			}
+			// prevent nodes that are already a parent
+			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iChild); iParent++) {
+				isNotAllowedAsParent[m_BayesNet.getParent(iChild, iParent)] = true;
+			}
+			// count nr of remaining candidates
+			int nCandidates = 0;
+			for (int i = 0; i < nNodes; i++) {
+				if (!isNotAllowedAsParent[i]) {
+					nCandidates++;
+				}
+			}
+			if (nCandidates == 0) {
+				JOptionPane.showMessageDialog(null, "No potential parents available for this node (" + sChild
+						+ "). Choose another node as child node.");
+				return;
+			}
+			String[] options = new String[nCandidates];
+			int k = 0;
+			for (int i = 0; i < nNodes; i++) {
+				if (!isNotAllowedAsParent[i]) {
+					options[k++] = m_BayesNet.getNodeName(i);
+				}
+			}
+			String sParent = (String) JOptionPane.showInputDialog(null, "Select parent node for " + sChild, "Nodes", 0,
+					null, options, options[0]);
+			if (sParent == null || sParent.equals("")) {
+				return;
+			}
+			// update all data structures
+			m_BayesNet.addArc(sParent, sChild);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		    updateStatus();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	} // addArcInto
+
+	/* deletes arc from node with name sParent into child with index iChild
+	 * 
+	 */
+	void deleteArc(int iChild, String sParent) {
+		try {
+			m_BayesNet.deleteArc(m_BayesNet.getNode(sParent), iChild);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		updateStatus();
+	} // deleteArc
+	
+	/* deletes arc from node with index iParent into child with name sChild
+	 * 
+	 */
+	void deleteArc(String sChild, int iParent) {
+		try {
+			m_BayesNet.deleteArc(iParent, m_BayesNet.getNode(sChild));
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		updateStatus();
+	} // deleteArc
+
+	/* deletes arc. Pops up list of arcs listed in 'options' as 
+	 * "<Node1> -> <Node2>".
+	 */
+	void deleteArc(String[] options) {
+		String sResult = (String) JOptionPane.showInputDialog(null, "Select arc to delete", "Arcs", 0, null, options,
+				options[0]);
+		if (sResult != null && !sResult.equals("")) {
+			int nPos = sResult.indexOf(" -> ");
+			String sParent = sResult.substring(0, nPos);
+			String sChild = sResult.substring(nPos + 4);
+			try {
+				m_BayesNet.deleteArc(sParent, sChild);
+			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			updateStatus();
+		}
+	} // deleteArc
+
+	/* Rename node with index nTargetNode.
+	 * Pops up window that allwos for entering a new name.
+	 */
+	void renameNode(int nTargetNode) {
+		String sName = (String) JOptionPane.showInputDialog(null, m_BayesNet.getNodeName(nTargetNode), "New name for node",
+				JOptionPane.OK_CANCEL_OPTION);
+		if (sName == null || sName.equals("")) {
+			return;
+		}
+		try {
+			while (m_BayesNet.getNode2(sName) >= 0) {
+				sName = (String) JOptionPane.showInputDialog(null, "Cannot rename to " + sName
+						+ ".\nNode with that name already exists.");
+				if (sName == null || sName.equals("")) {
+					return;
+				}
+			}
+			m_BayesNet.setNodeName(nTargetNode, sName);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		repaint();
+	} // renameNode
+
+	/* Rename value with name sValeu of a node with index nTargetNode.
+	 * Pops up window that allows entering a new name.
+	 */
+	void renameValue(int nTargetNode, String sValue) {
+		String sNewValue = (String) JOptionPane.showInputDialog(null, "New name for value " + sValue, "Node "
+				+ m_BayesNet.getNodeName(nTargetNode), JOptionPane.OK_CANCEL_OPTION);
+		if (sNewValue == null || sNewValue.equals("")) {
+			return;
+		}
+		m_BayesNet.renameNodeValue(nTargetNode, sValue, sNewValue);
+	    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		a_undo.setEnabled(true);
+		a_redo.setEnabled(false);
+		repaint();
+	} // renameValue
+
+	/* delete a single node with index iNode */
+	void deleteNode(int iNode) {
+		try {
+			m_BayesNet.deleteNode(iNode);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		updateStatus();
+	} // deleteNode
+
+	/* Add a value to currently selected node.
+	 * Shows window that allows to enter the name of the value. 
+	 */
+	void addValue() {
+		//GraphNode n = (GraphNode) m_nodes.elementAt(m_nCurrentNode);
+		String sValue = "Value" + (m_BayesNet.getCardinality(m_nCurrentNode) + 1);
+		String sNewValue = (String) JOptionPane.showInputDialog(null, "New value " + sValue, "Node " + m_BayesNet.getNodeName(m_nCurrentNode),
+				JOptionPane.OK_CANCEL_OPTION);
+		if (sNewValue == null || sNewValue.equals("")) {
+			return;
+		}
+		try {
+			m_BayesNet.addNodeValue(m_nCurrentNode, sNewValue);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+			//n.outcomes = m_BayesNet.getValues(m_nCurrentNode);
+			//for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
+			//	n = (GraphNode) m_nodes.elementAt(iNode);
+			//	n.probs = m_BayesNet.getDistribution(iNode);
+			//}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		updateStatus();
+	} // addValue
+
+	/* remove value with name sValue from the node with index nTargetNode
+	 */
+	void delValue(int nTargetNode, String sValue) {
+		try {
+			m_BayesNet.delNodeValue(nTargetNode, sValue);
+		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		updateStatus();
+	} // delValue
+
+	/* Edits CPT of node with index nTargetNode. 
+	 * Pops up table with probability table that the user can change or just view.
+	 */
+	void editCPT(int nTargetNode) {
+		m_nCurrentNode = nTargetNode;
+		final GraphVisualizerTableModel tm = new GraphVisualizerTableModel(nTargetNode);
+
+		JTable jTblProbs = new JTable(tm); 
+
+		JScrollPane js = new JScrollPane(jTblProbs);
+
+		int nParents = m_BayesNet.getNrOfParents(nTargetNode);
+		if (nParents > 0) {
+			GridBagConstraints gbc = new GridBagConstraints();
+			JPanel jPlRowHeader = new JPanel(new GridBagLayout());
+
+			// indices of the parent nodes in the Vector
+			int[] idx = new int[nParents];
+			// max length of values of each parent
+			int[] lengths = new int[nParents];
+
+			// Adding labels for rows
+			gbc.anchor = GridBagConstraints.NORTHWEST;
+			gbc.fill = GridBagConstraints.HORIZONTAL;
+			gbc.insets = new Insets(0, 1, 0, 0);
+			int addNum = 0, temp = 0;
+			boolean dark = false;
+			while (true) {
+				gbc.gridwidth = 1;
+				for (int k = 0; k < nParents; k++) {
+					int iParent2 = m_BayesNet.getParent(nTargetNode, k); 
+					JLabel lb = new JLabel(m_BayesNet.getValueName(iParent2,idx[k]));
+					lb.setFont(new Font("Dialog", Font.PLAIN, 12));
+					lb.setOpaque(true);
+					lb.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 1));
+					lb.setHorizontalAlignment(JLabel.CENTER);
+					if (dark) {
+						lb.setBackground(lb.getBackground().darker());
+						lb.setForeground(Color.white);
+					} else
+						lb.setForeground(Color.black);
+
+					temp = lb.getPreferredSize().width;
+					lb.setPreferredSize(new Dimension(temp, jTblProbs.getRowHeight()));
+					if (lengths[k] < temp)
+						lengths[k] = temp;
+					temp = 0;
+
+					if (k == nParents - 1) {
+						gbc.gridwidth = GridBagConstraints.REMAINDER;
+						dark = (dark == true) ? false : true;
+					}
+					jPlRowHeader.add(lb, gbc);
+					addNum++;
+				}
+
+				for (int k = nParents - 1; k >= 0; k--) {
+					int iParent2 = m_BayesNet.getParent(m_nCurrentNode, k);
+					if (idx[k] == m_BayesNet.getCardinality(iParent2) - 1 && k != 0) {
+						idx[k] = 0;
+						continue;
+					} else {
+						idx[k]++;
+						break;
+					}
+				}
+
+				int iParent2 = m_BayesNet.getParent(m_nCurrentNode, 0);
+				if (idx[0] == m_BayesNet.getCardinality(iParent2)) {
+					JLabel lb = (JLabel) jPlRowHeader.getComponent(addNum - 1);
+					jPlRowHeader.remove(addNum - 1);
+					lb.setPreferredSize(new Dimension(lb.getPreferredSize().width, jTblProbs
+							.getRowHeight()));
+					gbc.gridwidth = GridBagConstraints.REMAINDER;
+					gbc.weighty = 1;
+					jPlRowHeader.add(lb, gbc);
+					gbc.weighty = 0;
+					break;
+				}
+			}
+
+			gbc.gridwidth = 1;
+			// The following panel contains the names of the
+			// parents
+			// and is displayed above the row names to identify
+			// which value belongs to which parent
+			JPanel jPlRowNames = new JPanel(new GridBagLayout());
+			for (int j = 0; j < nParents; j++) {
+				JLabel lb2;
+				JLabel lb1 = new JLabel(m_BayesNet.getNodeName(m_BayesNet.getParent(nTargetNode, j)));
+				lb1.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 1));
+				Dimension tempd = lb1.getPreferredSize();
+				if (tempd.width < lengths[j]) {
+					lb1.setPreferredSize(new Dimension(lengths[j], tempd.height));
+					lb1.setHorizontalAlignment(JLabel.CENTER);
+					lb1.setMinimumSize(new Dimension(lengths[j], tempd.height));
+				} else if (tempd.width > lengths[j]) {
+					lb2 = (JLabel) jPlRowHeader.getComponent(j);
+					lb2.setPreferredSize(new Dimension(tempd.width, lb2.getPreferredSize().height));
+				}
+				jPlRowNames.add(lb1, gbc);
+			}
+			js.setRowHeaderView(jPlRowHeader);
+			js.setCorner(JScrollPane.UPPER_LEFT_CORNER, jPlRowNames);
+		}
+
+		final JDialog dlg = new JDialog((Frame) GUI.this.getTopLevelAncestor(),
+				"Probability Distribution Table For " + m_BayesNet.getNodeName(nTargetNode), true);
+		dlg.setSize(500, 400);
+		dlg.setLocation(GUI.this.getLocation().x + GUI.this.getWidth() / 2
+				- 250, GUI.this.getLocation().y + GUI.this.getHeight() / 2
+				- 200);
+
+		dlg.getContentPane().setLayout(new BorderLayout());
+		dlg.getContentPane().add(js, BorderLayout.CENTER);
+
+		JButton jBtRandomize = new JButton("Randomize");
+		jBtRandomize.setMnemonic('R');
+		jBtRandomize.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ae) {
+				tm.randomize();
+				dlg.repaint();
+			}
+		});
+		
+		JButton jBtOk = new JButton("Ok");
+		jBtOk.setMnemonic('O');
+		jBtOk.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ae) {
+				tm.setData();
+				try {
+					m_BayesNet.setDistribution(m_nCurrentNode, tm.m_fProbs);
+				    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
+					updateStatus();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+				dlg.setVisible(false);
+			}
+		});
+		JButton jBtCancel = new JButton("Cancel");
+		jBtCancel.setMnemonic('C');
+		jBtCancel.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ae) {
+				dlg.setVisible(false);
+			}
+		});
+		Container c = new Container();
+		c.setLayout(new GridBagLayout());
+		c.add(jBtRandomize);
+		c.add(jBtOk);
+		c.add(jBtCancel);
+
+		dlg.getContentPane().add(c, BorderLayout.SOUTH);
+		dlg.setVisible(true);
+	} // editCPT
+
+	/**
+	 * Main method. Builds up menus and reads from file if one is specified.
+	 */
+	public static void main(String[] args) {
+
+		weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+	    
+		LookAndFeel.setLookAndFeel();
+		
+		JFrame jf = new JFrame("Bayes Network Editor");
+		final GUI g = new GUI();
+		JMenuBar menuBar = g.getMenuBar();
+
+		if (args.length>0) {
+			try {
+				g.readBIFFromFile(args[0]);
+			} catch (IOException ex) {
+				ex.printStackTrace();
+			} catch (BIFFormatException bf) {
+				bf.printStackTrace();
+				System.exit(-1);
+			}
+		}
+
+
+
+
+	        jf.setJMenuBar(menuBar);		
+		jf.getContentPane().add(g);
+		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		jf.setSize(800, 600);
+		jf.setVisible(true);
+		g.m_Selection.updateGUI();
+		GenericObjectEditor.registerEditors();
+	} // main
+	
+} // end of class
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/MarginCalculator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/MarginCalculator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/MarginCalculator.java	(revision 29)
@@ -0,0 +1,949 @@
+package weka.classifiers.bayes.net;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+
+public class MarginCalculator implements Serializable, RevisionHandler {
+	  /** for serialization */
+	  private static final long serialVersionUID = 650278019241175534L;
+
+	  boolean m_debug = false;
+	  public JunctionTreeNode m_root = null;
+	JunctionTreeNode [] jtNodes;
+
+	public int getNode(String sNodeName) {
+    	int iNode = 0;
+    	while (iNode < m_root.m_bayesNet.m_Instances.numAttributes()) {
+    		if (m_root.m_bayesNet.m_Instances.attribute(iNode).name().equals(sNodeName)) {
+    			return iNode;
+    		}
+	    	iNode++; 
+    	}
+    	//throw new Exception("Could not find node [[" + sNodeName + "]]");
+    	return -1;
+	}
+	public String toXMLBIF03() {return m_root.m_bayesNet.toXMLBIF03();}
+	
+	/**
+	 * Calc marginal distributions of nodes in Bayesian network
+	 *	 Note that a connected network is assumed. 
+	 *	 Unconnected networks may give unexpected results.
+	 * @param bayesNet
+	 */
+	public void calcMargins(BayesNet bayesNet) throws Exception {
+		//System.out.println(bayesNet.toString());
+		boolean[][] bAdjacencyMatrix = moralize(bayesNet);
+		process(bAdjacencyMatrix, bayesNet);
+	} // calcMargins
+
+	public void calcFullMargins(BayesNet bayesNet) throws Exception {
+		//System.out.println(bayesNet.toString());
+		int nNodes = bayesNet.getNrOfNodes();
+		boolean[][] bAdjacencyMatrix = new boolean[nNodes][nNodes];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			for (int iNode2 = 0; iNode2 < nNodes; iNode2++) {
+				bAdjacencyMatrix[iNode][iNode2] = true;
+			}
+		}
+		process(bAdjacencyMatrix, bayesNet);
+	} // calcMargins
+	
+	
+	public void process(boolean[][] bAdjacencyMatrix, BayesNet bayesNet) throws Exception {
+		int[] order = getMaxCardOrder(bAdjacencyMatrix);
+		bAdjacencyMatrix = fillIn(order, bAdjacencyMatrix);
+		order = getMaxCardOrder(bAdjacencyMatrix);
+		Set [] cliques = getCliques(order, bAdjacencyMatrix);
+		Set [] separators = getSeparators(order, cliques);
+		int [] parentCliques = getCliqueTree(order, cliques, separators);
+		// report cliques
+		int nNodes = bAdjacencyMatrix.length;
+		if (m_debug) {
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (cliques[iNode] != null) {
+				System.out.print("Clique " + iNode + " (");
+				Iterator nodes = cliques[iNode].iterator();
+				while (nodes.hasNext()) {
+					int iNode2 = (Integer) nodes.next();
+					System.out.print(iNode2 + " " + bayesNet.getNodeName(iNode2));
+					if (nodes.hasNext()) {
+						System.out.print(",");
+					}
+				}
+				System.out.print(") S(");
+				nodes = separators[iNode].iterator();
+				while (nodes.hasNext()) {
+					int iNode2 = (Integer) nodes.next();
+					System.out.print(iNode2 + " " + bayesNet.getNodeName(iNode2));
+					if (nodes.hasNext()) {
+						System.out.print(",");
+					}
+				}
+				System.out.println(") parent clique " + parentCliques[iNode]);
+			}		
+		}
+		}
+				
+		jtNodes = getJunctionTree(cliques, separators, parentCliques, order, bayesNet);
+		m_root = null;
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			if (parentCliques[iNode] < 0 && jtNodes[iNode] != null) {
+				m_root = jtNodes[iNode];
+				break;
+			}
+		}
+		m_Margins = new double[nNodes][];
+		initialize(jtNodes, order, cliques, separators, parentCliques);
+		
+		// sanity check
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (cliques[iNode] != null) {
+				if (parentCliques[iNode] == -1 && separators[iNode].size() > 0) {
+					throw new Exception("Something wrong in clique tree");
+				}
+			}
+		}
+		if (m_debug) {
+			//System.out.println(m_root.toString());
+		}
+	} // process
+		
+	void initialize(JunctionTreeNode [] jtNodes, int [] order, Set [] cliques, Set [] separators, int [] parentCliques) {
+		int nNodes = order.length;
+		for (int i = nNodes - 1; i >= 0; i--) {
+			int iNode = order[i];
+			if (jtNodes[iNode]!=null) {
+				jtNodes[iNode].initializeUp();
+			}
+		}	
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (jtNodes[iNode]!=null) {
+				jtNodes[iNode].initializeDown(false);
+			}
+		}	
+	} // initialize
+	
+	JunctionTreeNode [] getJunctionTree(Set [] cliques, Set [] separators, int [] parentCliques, int [] order, BayesNet bayesNet) {
+		int nNodes = order.length;
+		JunctionTreeNode root = null;
+		JunctionTreeNode [] jtns = new JunctionTreeNode[nNodes]; 
+		boolean [] bDone = new boolean[nNodes];
+		// create junction tree nodes
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (cliques[iNode] != null) {
+				jtns[iNode] = new JunctionTreeNode(cliques[iNode], bayesNet, bDone);
+			}
+		}
+		// create junction tree separators
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (cliques[iNode] != null) {
+				JunctionTreeNode parent = null;
+				if (parentCliques[iNode] > 0) {
+					parent = jtns[parentCliques[iNode]];
+					JunctionTreeSeparator jts = new JunctionTreeSeparator(separators[iNode], bayesNet, jtns[iNode], parent);
+					jtns[iNode].setParentSeparator(jts);
+					jtns[parentCliques[iNode]].addChildClique(jtns[iNode]);
+				} else {
+					root = jtns[iNode];	
+				}
+			}
+		}
+		return jtns;
+	} // getJunctionTree
+	
+	public class JunctionTreeSeparator implements Serializable, RevisionHandler {
+	  
+		  private static final long serialVersionUID = 6502780192411755343L;
+		int [] m_nNodes;
+		int m_nCardinality;
+		double [] m_fiParent;
+		double [] m_fiChild;
+		JunctionTreeNode m_parentNode;
+		JunctionTreeNode m_childNode;
+		BayesNet m_bayesNet;
+		
+		JunctionTreeSeparator(Set separator, BayesNet bayesNet, JunctionTreeNode childNode, JunctionTreeNode parentNode) {
+			//////////////////////
+			// initialize node set
+			m_nNodes = new int[separator.size()];
+			int iPos = 0;
+			m_nCardinality = 1;
+			for(Iterator nodes = separator.iterator(); nodes.hasNext();) {
+				int iNode = (Integer) nodes.next();
+				m_nNodes[iPos++] = iNode;
+				m_nCardinality *= bayesNet.getCardinality(iNode);
+			}
+			m_parentNode = parentNode;
+			m_childNode = childNode;
+			m_bayesNet = bayesNet;
+		} // c'tor
+		
+		/** marginalize junciontTreeNode node over all nodes outside the separator set
+		 * of the parent clique
+		 *
+		 */
+		public void updateFromParent() {
+			double [] fis = update(m_parentNode); 
+			if (fis == null) {
+				m_fiParent = null;
+			} else {
+				m_fiParent = fis;
+				// normalize
+				double sum = 0;
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					sum += m_fiParent[iPos];
+				}
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					m_fiParent[iPos] /= sum;
+				}
+			}
+		} // updateFromParent
+
+		/** marginalize junciontTreeNode node over all nodes outside the separator set
+		 * of the child clique
+		 *
+		 */
+		public void updateFromChild() {
+			double [] fis = update(m_childNode); 
+			if (fis == null) {
+				m_fiChild = null;
+			} else {
+				m_fiChild = fis;
+				// normalize
+				double sum = 0;
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					sum += m_fiChild[iPos];
+				}
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					m_fiChild[iPos] /= sum;
+				}
+			}
+		} // updateFromChild
+		
+		/** marginalize junciontTreeNode node over all nodes outside the separator set
+		 * 
+		 * @param node one of the neighboring junciont tree nodes of this separator
+		 */
+		public double [] update(JunctionTreeNode node) {
+			if (node.m_P == null) {
+				return null;
+			}
+			double [] fi = new double[m_nCardinality];
+
+			int [] values = new int[node.m_nNodes.length];
+			int [] order = new int[m_bayesNet.getNrOfNodes()];
+			for (int iNode = 0; iNode < node.m_nNodes.length; iNode++) {
+				order[node.m_nNodes[iNode]] = iNode;
+			}
+			// fill in the values
+			for (int iPos = 0; iPos < node.m_nCardinality; iPos++) {
+				int iNodeCPT = getCPT(node.m_nNodes, node.m_nNodes.length, values, order, m_bayesNet);
+				int iSepCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+				fi[iSepCPT] += node.m_P[iNodeCPT];
+				// update values
+				int i = 0;
+				values[i]++;
+				while (i < node.m_nNodes.length && values[i] == m_bayesNet.getCardinality(node.m_nNodes[i])) {
+					values[i] = 0;
+					i++;
+					if (i < node.m_nNodes.length) {
+						values[i]++;
+					}
+				}
+			}
+			return fi;
+		} // update
+		  
+		/**
+		 * Returns the revision string.
+		 * 
+		 * @return		the revision
+		 */
+		public String getRevision() {
+		  return RevisionUtils.extract("$Revision: 4899 $");
+		}
+
+	} // class JunctionTreeSeparator
+
+	public class JunctionTreeNode implements Serializable, RevisionHandler {
+	  
+		  private static final long serialVersionUID = 650278019241175536L;
+		/** reference Bayes net for information about variables like name, cardinality, etc.
+		 * but not for relations between nodes **/
+		BayesNet m_bayesNet;
+		/** nodes of the Bayes net in this junction node **/
+		public int [] m_nNodes;
+		/** cardinality of the instances of variables in this junction node **/
+		int m_nCardinality;
+		/** potentials for first network **/
+		double [] m_fi;
+
+		/** distribution over this junction node according to first Bayes network **/
+		double [] m_P;
+
+
+		double [][] m_MarginalP;		
+
+		
+		JunctionTreeSeparator m_parentSeparator;
+		public void setParentSeparator(JunctionTreeSeparator parentSeparator) {m_parentSeparator = parentSeparator;}
+		public Vector m_children;
+		public void addChildClique(JunctionTreeNode child) {m_children.add(child);}
+
+		public void initializeUp() {
+			m_P = new double[m_nCardinality];
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				m_P[iPos] = m_fi[iPos];
+			}
+			int [] values = new int[m_nNodes.length];
+			int [] order = new int[m_bayesNet.getNrOfNodes()];
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				order[m_nNodes[iNode]] = iNode;
+			}
+			for (Iterator child = m_children.iterator(); child.hasNext(); ) {
+				JunctionTreeNode childNode = (JunctionTreeNode) child.next();
+				JunctionTreeSeparator separator = childNode.m_parentSeparator;
+			// Update the values
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				int iSepCPT = getCPT(separator.m_nNodes, separator.m_nNodes.length, values, order, m_bayesNet);
+				int iNodeCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+					m_P[iNodeCPT] *= separator.m_fiChild[iSepCPT];					
+				// update values
+				int i = 0;
+				values[i]++;
+				while (i < m_nNodes.length && values[i] == m_bayesNet.getCardinality(m_nNodes[i])) {
+					values[i] = 0;
+					i++;
+					if (i < m_nNodes.length) {
+						values[i]++;
+					}
+				}
+			}
+			}
+			// normalize
+			double sum = 0;
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				sum += m_P[iPos];
+			}
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				m_P[iPos] /= sum;
+			}
+
+			if (m_parentSeparator != null) { // not a root node
+				m_parentSeparator.updateFromChild();
+			}
+		} // initializeUp
+
+		public void initializeDown(boolean recursively) {
+			if (m_parentSeparator == null) { // a root node
+				calcMarginalProbabilities();
+			} else {
+			m_parentSeparator.updateFromParent();
+				int [] values = new int[m_nNodes.length];
+				int [] order = new int[m_bayesNet.getNrOfNodes()];
+				for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+					order[m_nNodes[iNode]] = iNode;
+				}
+
+				
+				// Update the values
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					int iSepCPT = getCPT(m_parentSeparator.m_nNodes, m_parentSeparator.m_nNodes.length, values, order, m_bayesNet);
+					int iNodeCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+					if ( m_parentSeparator.m_fiChild[iSepCPT] > 0) {
+						m_P[iNodeCPT] *= m_parentSeparator.m_fiParent[iSepCPT] / m_parentSeparator.m_fiChild[iSepCPT];
+					} else {
+						m_P[iNodeCPT] = 0;
+					}
+					// update values
+					int i = 0;
+					values[i]++;
+					while (i < m_nNodes.length && values[i] == m_bayesNet.getCardinality(m_nNodes[i])) {
+						values[i] = 0;
+						i++;
+						if (i < m_nNodes.length) {
+							values[i]++;
+						}
+					}
+				}
+				// normalize
+				double sum = 0;
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					sum += m_P[iPos];
+				}
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					m_P[iPos] /= sum;
+				}
+				m_parentSeparator.updateFromChild();
+				calcMarginalProbabilities();
+			}
+			if (recursively) {
+				for (Iterator child = m_children.iterator(); child.hasNext(); ) {
+					JunctionTreeNode childNode = (JunctionTreeNode) child.next();
+					childNode.initializeDown(true);
+				}			
+			}
+		} // initializeDown
+		
+		
+		/** calculate marginal probabilities for the individual nodes in the clique.
+		 * Store results in m_MarginalP 
+		 */
+		void calcMarginalProbabilities() {			
+			// calculate marginal probabilities
+			int [] values = new int[m_nNodes.length];
+			int [] order = new int[m_bayesNet.getNrOfNodes()];
+			m_MarginalP = new double[m_nNodes.length][];
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				order[m_nNodes[iNode]] = iNode;
+				m_MarginalP[iNode]=new double[m_bayesNet.getCardinality(m_nNodes[iNode])];
+			}
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				int iNodeCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+				for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+					m_MarginalP[iNode][values[iNode]] += m_P[iNodeCPT];
+				}
+				// update values
+				int i = 0;
+				values[i]++;
+				while (i < m_nNodes.length && values[i] == m_bayesNet.getCardinality(m_nNodes[i])) {
+					values[i] = 0;
+					i++;
+					if (i < m_nNodes.length) {
+						values[i]++;
+					}
+				}
+			}
+			
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				m_Margins[m_nNodes[iNode]] = m_MarginalP[iNode]; 
+			}
+		} // calcMarginalProbabilities
+		
+		public String toString() {
+			StringBuffer buf = new StringBuffer();
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				buf.append(m_bayesNet.getNodeName(m_nNodes[iNode]) + ": ");
+				for (int iValue = 0; iValue < m_MarginalP[iNode].length; iValue++) {
+					buf.append(m_MarginalP[iNode][iValue] + " ");
+				}
+				buf.append('\n');
+			}
+			for (Iterator child = m_children.iterator(); child.hasNext(); ) {
+				JunctionTreeNode childNode = (JunctionTreeNode) child.next();
+				buf.append("----------------\n");
+				buf.append(childNode.toString());
+			}			
+			return buf.toString();
+		} // toString
+		
+		void calculatePotentials(BayesNet bayesNet, Set clique, boolean [] bDone) {
+			m_fi = new double[m_nCardinality];
+			
+			int [] values = new int[m_nNodes.length];
+			int [] order = new int[bayesNet.getNrOfNodes()];
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				order[m_nNodes[iNode]] = iNode;
+			}
+			// find conditional probabilities that need to be taken in account
+			boolean [] bIsContained = new boolean[m_nNodes.length];
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				int nNode = m_nNodes[iNode];
+				bIsContained[iNode] = !bDone[nNode];
+				for (int iParent = 0; iParent < bayesNet.getNrOfParents(nNode); iParent++) {
+					int nParent = bayesNet.getParent(nNode, iParent);
+					if (!clique.contains(nParent)) {
+						bIsContained[iNode] = false;
+					}
+				}
+				if (bIsContained[iNode]) {
+					bDone[nNode] = true;
+					if (m_debug) {
+						System.out.println("adding node " +nNode);
+					}
+				}
+			}			
+
+			// fill in the values
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				int iCPT = getCPT(m_nNodes, m_nNodes.length, values, order, bayesNet);
+				m_fi[iCPT] = 1.0;
+				for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+					if (bIsContained[iNode]) {
+						int nNode = m_nNodes[iNode];
+						int [] nNodes = bayesNet.getParentSet(nNode).getParents();
+						int iCPT2 = getCPT(nNodes, bayesNet.getNrOfParents(nNode), values, order, bayesNet);
+						double f = bayesNet.getDistributions()[nNode][iCPT2].getProbability(values[iNode]);
+						m_fi[iCPT] *= f;
+					}
+				}
+				
+				// update values
+				int i = 0;
+				values[i]++;
+				while (i < m_nNodes.length && values[i] == bayesNet.getCardinality(m_nNodes[i])) {
+					values[i] = 0;
+					i++;
+					if (i < m_nNodes.length) {
+						values[i]++;
+					}
+				}
+			}
+		} // calculatePotentials
+
+		JunctionTreeNode(Set clique, BayesNet bayesNet, boolean [] bDone) {
+			m_bayesNet = bayesNet;
+			m_children = new Vector();
+			//////////////////////
+			// initialize node set
+			m_nNodes = new int[clique.size()];
+			int iPos = 0;
+			m_nCardinality = 1;
+			for(Iterator nodes = clique.iterator(); nodes.hasNext();) {
+				int iNode = (Integer) nodes.next();
+				m_nNodes[iPos++] = iNode;
+				m_nCardinality *= bayesNet.getCardinality(iNode);
+			}
+			////////////////////////////////
+			// initialize potential function
+			calculatePotentials(bayesNet, clique, bDone);
+       } // JunctionTreeNode c'tor
+
+		/* check whether this junciton tree node contains node nNode
+		 * 
+		 */
+		boolean contains(int nNode) {
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				if (m_nNodes[iNode]== nNode){
+					return true;
+				}
+			}
+			return false;
+		} // contains
+		
+		public void setEvidence(int nNode, int iValue) throws Exception {
+			int [] values = new int[m_nNodes.length];
+			int [] order = new int[m_bayesNet.getNrOfNodes()];
+
+			int nNodeIdx = -1;
+			for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+				order[m_nNodes[iNode]] = iNode;
+				if (m_nNodes[iNode] == nNode) {
+					nNodeIdx = iNode;
+				}
+			}
+			if (nNodeIdx < 0) {
+				throw new Exception("setEvidence: Node " + nNode + " not found in this clique");
+			}
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				if (values[nNodeIdx] != iValue) {
+					int iNodeCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+					m_P[iNodeCPT] = 0;
+				}
+				// update values
+				int i = 0;
+				values[i]++;
+				while (i < m_nNodes.length && values[i] == m_bayesNet.getCardinality(m_nNodes[i])) {
+					values[i] = 0;
+					i++;
+					if (i < m_nNodes.length) {
+						values[i]++;
+					}
+				}
+			}		
+			// normalize
+			double sum = 0;
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				sum += m_P[iPos];
+			}
+			for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+				m_P[iPos] /= sum;
+			}
+			calcMarginalProbabilities();
+			updateEvidence(this);
+		} // setEvidence
+
+		void updateEvidence(JunctionTreeNode source) {
+			if (source != this) {
+				int [] values = new int[m_nNodes.length];
+				int [] order = new int[m_bayesNet.getNrOfNodes()];
+				for (int iNode = 0; iNode < m_nNodes.length; iNode++) {
+					order[m_nNodes[iNode]] = iNode;
+				}
+				int [] nChildNodes = source.m_parentSeparator.m_nNodes;
+				int nNumChildNodes = nChildNodes.length; 
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					int iNodeCPT =  getCPT(m_nNodes, m_nNodes.length, values, order, m_bayesNet);
+					int iChildCPT =  getCPT(nChildNodes, nNumChildNodes, values, order, m_bayesNet);
+					if (source.m_parentSeparator.m_fiParent[iChildCPT] != 0) {
+						m_P[iNodeCPT] *= source.m_parentSeparator.m_fiChild[iChildCPT]/source.m_parentSeparator.m_fiParent[iChildCPT];
+					} else {
+						m_P[iNodeCPT] = 0;
+					}
+					// update values
+					int i = 0;
+					values[i]++;
+					while (i < m_nNodes.length && values[i] == m_bayesNet.getCardinality(m_nNodes[i])) {
+						values[i] = 0;
+						i++;
+						if (i < m_nNodes.length) {
+							values[i]++;
+						}
+					}
+				}		
+				// normalize
+				double sum = 0;
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					sum += m_P[iPos];
+				}
+				for (int iPos = 0; iPos < m_nCardinality; iPos++) {
+					m_P[iPos] /= sum;
+				}
+				calcMarginalProbabilities();
+			}
+			for (Iterator child = m_children.iterator(); child.hasNext(); ) {
+				JunctionTreeNode childNode = (JunctionTreeNode) child.next();
+				if (childNode != source) {
+					childNode.initializeDown(true);
+				}
+			}			
+			if (m_parentSeparator != null) {
+				m_parentSeparator.updateFromChild();
+				m_parentSeparator.m_parentNode.updateEvidence(this);
+				m_parentSeparator.updateFromParent();
+			}
+		} // updateEvidence
+
+		/**
+		 * Returns the revision string.
+		 * 
+		 * @return		the revision
+		 */
+		public String getRevision() {
+		  return RevisionUtils.extract("$Revision: 4899 $");
+		}
+		
+	} // class JunctionTreeNode
+
+	int getCPT(int [] nodeSet, int nNodes, int[] values, int[] order, BayesNet bayesNet) {
+		int iCPTnew = 0;
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			int nNode = nodeSet[iNode];
+			iCPTnew = iCPTnew * bayesNet.getCardinality(nNode);
+			iCPTnew += values[order[nNode]];
+		}
+		return iCPTnew;
+	} // getCPT
+
+	int [] getCliqueTree(int [] order, Set [] cliques, Set [] separators) {
+		int nNodes = order.length;
+		int [] parentCliques = new int[nNodes];
+		//for (int i = nNodes - 1; i >= 0; i--) {
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			parentCliques[iNode] = -1;
+			if (cliques[iNode] != null && separators[iNode].size() > 0) {
+				//for (int j = nNodes - 1; j > i; j--) {
+				for (int j = 0; j < nNodes; j++) {
+					int iNode2 = order[j];
+					if (iNode!= iNode2 && cliques[iNode2] != null && cliques[iNode2].containsAll(separators[iNode])) {
+						parentCliques[iNode] = iNode2;
+						j = i;
+						j = 0;
+						j = nNodes;
+					}
+				}
+				
+			}
+		}
+		return parentCliques;
+	} // getCliqueTree
+	
+	/** calculate separator sets in clique tree
+	 * 
+	 * @param order: maximum cardinality ordering of the graph
+	 * @param cliques: set of cliques
+	 * @return set of separator sets
+	 */
+	Set [] getSeparators(int [] order, Set [] cliques) {
+		int nNodes = order.length;
+		Set [] separators = new HashSet[nNodes];
+		Set processedNodes = new HashSet(); 
+		//for (int i = nNodes - 1; i >= 0; i--) {
+		for (int i = 0; i < nNodes; i++) {
+			int iNode = order[i];
+			if (cliques[iNode] != null) {
+				Set separator = new HashSet();
+				separator.addAll(cliques[iNode]);
+				separator.retainAll(processedNodes);
+				separators[iNode] = separator;
+				processedNodes.addAll(cliques[iNode]);
+			}
+		}
+		return separators;
+	} // getSeparators
+	
+	/**
+	 * get cliques in a decomposable graph represented by an adjacency matrix
+	 * 
+	 * @param order: maximum cardinality ordering of the graph
+	 * @param bAdjacencyMatrix: decomposable graph
+	 * @return set of cliques
+	 */
+	Set [] getCliques(int[] order, boolean[][] bAdjacencyMatrix) throws Exception {
+		int nNodes = bAdjacencyMatrix.length;
+		Set [] cliques = new HashSet[nNodes];
+		//int[] inverseOrder = new int[nNodes];
+		//for (int iNode = 0; iNode < nNodes; iNode++) {
+			//inverseOrder[order[iNode]] = iNode;
+		//}
+		// consult nodes in reverse order
+		for (int i = nNodes - 1; i >= 0; i--) {
+			int iNode = order[i];
+			if (iNode == 22) {
+				int h = 3;
+				h ++;
+			}
+			Set clique = new HashSet();
+			clique.add(iNode);
+			for (int j = 0; j < i; j++) {
+				int iNode2 = order[j];
+				if (bAdjacencyMatrix[iNode][iNode2]) {
+					clique.add(iNode2);
+				}
+			}
+			
+			//for (int iNode2 = 0; iNode2 < nNodes; iNode2++) {
+				//if (bAdjacencyMatrix[iNode][iNode2] && inverseOrder[iNode2] < inverseOrder[iNode]) {
+					//clique.add(iNode2);
+				//}
+			//}
+			cliques[iNode] = clique;
+		}
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			for (int iNode2 = 0; iNode2 < nNodes; iNode2++) {
+				if (iNode != iNode2 && cliques[iNode]!= null && cliques[iNode2]!= null && cliques[iNode].containsAll(cliques[iNode2])) {
+					cliques[iNode2] = null;
+				}
+			}
+		}		
+		// sanity check
+		if (m_debug) {
+		int [] nNodeSet = new int[nNodes];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			if (cliques[iNode] != null) {
+				Iterator it = cliques[iNode].iterator();
+				int k = 0;
+				while (it.hasNext()) {
+					nNodeSet[k++] = (Integer) it.next();
+				}
+				for (int i = 0; i < cliques[iNode].size(); i++) {
+					for (int j = 0; j < cliques[iNode].size(); j++) {
+						if (i!=j && !bAdjacencyMatrix[nNodeSet[i]][nNodeSet[j]]) {
+							throw new Exception("Non clique" + i + " " + j);
+						}
+					}
+				}
+			}
+		}
+		}
+		return cliques;
+	} // getCliques
+
+	/**
+	 * moralize DAG and calculate
+	 * adjacency matrix representation for a Bayes Network, effecively
+	 * converting the directed acyclic graph to an undirected graph.
+	 * 
+	 * @param bayesNet
+	 *            Bayes Network to process
+	 * @return adjacencies in boolean matrix format
+	 */
+	public boolean[][] moralize(BayesNet bayesNet) {
+		int nNodes = bayesNet.getNrOfNodes();
+		boolean[][] bAdjacencyMatrix = new boolean[nNodes][nNodes];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			ParentSet parents = bayesNet.getParentSets()[iNode];
+			moralizeNode(parents, iNode, bAdjacencyMatrix);
+		}
+		return bAdjacencyMatrix;
+	} // moralize
+
+	private void moralizeNode(ParentSet parents, int iNode, boolean[][] bAdjacencyMatrix) {
+		for (int iParent = 0; iParent < parents.getNrOfParents(); iParent++) {
+			int nParent = parents.getParent(iParent);
+			if ( m_debug && !bAdjacencyMatrix[iNode][nParent])
+				System.out.println("Insert " + iNode + "--" + nParent);
+			bAdjacencyMatrix[iNode][nParent] = true;
+			bAdjacencyMatrix[nParent][iNode] = true;
+			for (int iParent2 = iParent + 1; iParent2 < parents.getNrOfParents(); iParent2++) {
+				int nParent2 = parents.getParent(iParent2);
+				if (m_debug && !bAdjacencyMatrix[nParent2][nParent])
+					System.out.println("Mary " + nParent + "--" + nParent2);
+				bAdjacencyMatrix[nParent2][nParent] = true;
+				bAdjacencyMatrix[nParent][nParent2] = true;
+			}
+		}	
+	} // moralizeNode
+	
+	/**
+	 * Apply Tarjan and Yannakakis (1984) fill in algorithm for graph
+	 * triangulation. In reverse order, insert edges between any non-adjacent
+	 * neighbors that are lower numbered in the ordering.
+	 * 
+	 * Side effect: input matrix is used as output
+	 * 
+	 * @param order
+	 *            node ordering
+	 * @param bAdjacencyMatrix
+	 *            boolean matrix representing the graph
+	 * @return boolean matrix representing the graph with fill ins
+	 */
+	public boolean[][] fillIn(int[] order, boolean[][] bAdjacencyMatrix) {
+		int nNodes = bAdjacencyMatrix.length;
+		int[] inverseOrder = new int[nNodes];
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			inverseOrder[order[iNode]] = iNode;
+		}
+		// consult nodes in reverse order
+		for (int i = nNodes - 1; i >= 0; i--) {
+			int iNode = order[i];
+			// find pairs of neighbors with lower order
+			for (int j = 0; j < i; j++) {
+				int iNode2 = order[j];
+				if (bAdjacencyMatrix[iNode][iNode2]) {
+					for (int k = j+1; k < i; k++) {
+						int iNode3 = order[k];
+						if (bAdjacencyMatrix[iNode][iNode3]) {
+							// fill in
+							if (m_debug && (!bAdjacencyMatrix[iNode2][iNode3] || !bAdjacencyMatrix[iNode3][iNode2]) )
+								System.out.println("Fill in " + iNode2 + "--" + iNode3);
+							bAdjacencyMatrix[iNode2][iNode3] = true;
+							bAdjacencyMatrix[iNode3][iNode2] = true;
+						}
+					}
+				}
+			}
+		}
+		return bAdjacencyMatrix;
+	} // fillIn
+
+	/**
+	 * calculate maximum cardinality ordering; start with first node add node
+	 * that has most neighbors already ordered till all nodes are in the
+	 * ordering
+	 * 
+	 * This implementation does not assume the graph is connected
+	 * 
+	 * @param bAdjacencyMatrix:
+	 *            n by n matrix with adjacencies in graph of n nodes
+	 * @return maximum cardinality ordering
+	 */
+	int[] getMaxCardOrder(boolean[][] bAdjacencyMatrix) {
+		int nNodes = bAdjacencyMatrix.length;
+		int[] order = new int[nNodes];
+		if (nNodes==0) {return order;}
+		boolean[] bDone = new boolean[nNodes];
+		// start with node 0
+		order[0] = 0;
+		bDone[0] = true;
+		// order remaining nodes
+		for (int iNode = 1; iNode < nNodes; iNode++) {
+			int nMaxCard = -1;
+			int iBestNode = -1;
+			// find node with higest cardinality of previously ordered nodes
+			for (int iNode2 = 0; iNode2 < nNodes; iNode2++) {
+				if (!bDone[iNode2]) {
+					int nCard = 0;
+					// calculate cardinality for node iNode2
+					for (int iNode3 = 0; iNode3 < nNodes; iNode3++) {
+						if (bAdjacencyMatrix[iNode2][iNode3] && bDone[iNode3]) {
+							nCard++;
+						}
+					}
+					if (nCard > nMaxCard) {
+						nMaxCard = nCard;
+						iBestNode = iNode2;
+					}
+				}
+			}
+			order[iNode] = iBestNode;
+			bDone[iBestNode] = true;
+		}
+		return order;
+	} // getMaxCardOrder
+
+	public void setEvidence(int nNode, int iValue) throws Exception {
+		if (m_root == null) {
+			throw new Exception("Junction tree not initialize yet");
+		}
+		int iJtNode = 0;
+		while (iJtNode < jtNodes.length && (jtNodes[iJtNode] == null ||!jtNodes[iJtNode].contains(nNode))) {
+			iJtNode++;
+		}
+		if (jtNodes.length == iJtNode) {
+			throw new Exception("Could not find node " + nNode + " in junction tree");
+		}
+		jtNodes[iJtNode].setEvidence(nNode, iValue);
+	} // setEvidence
+	
+	public String toString() {
+		return m_root.toString();
+	} // toString
+
+	double [][] m_Margins;
+	public double [] getMargin(int iNode) {
+		return m_Margins[iNode];
+	} // getMargin
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 4899 $");
+	}
+	
+	public static void main(String[] args) {
+		try {
+			BIFReader bayesNet = new BIFReader();
+			bayesNet.processFile(args[0]);
+
+			MarginCalculator dc = new MarginCalculator();
+			dc.calcMargins(bayesNet);
+			int iNode = 2;
+			int iValue = 0;
+			int iNode2 = 4;
+			int iValue2 = 0;
+			dc.setEvidence(iNode, iValue);
+			dc.setEvidence(iNode2, iValue2);
+			System.out.print(dc.toString());
+
+
+			dc.calcFullMargins(bayesNet);
+			dc.setEvidence(iNode, iValue);
+			dc.setEvidence(iNode2, iValue2);
+			System.out.println("==============");
+			System.out.print(dc.toString());
+			
+			
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	} // main
+
+} // class MarginCalculator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ParentSet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ParentSet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/ParentSet.java	(revision 29)
@@ -0,0 +1,268 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ParentSet.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Helper class for Bayes Network classifiers. Provides datastructures to
+ * represent a set of parents in a graph.
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.8 $
+ */
+public class ParentSet 
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4155021284407181838L;
+
+  /**
+   * Holds indexes of parents
+   */
+  private int[] m_nParents;
+
+  /**
+   * returns index parent of parent specified by index
+   * 
+   * @param iParent Index of parent
+   * @return index of parent
+   */
+  public int getParent(int iParent) {
+    return m_nParents[iParent];
+  } 
+  public int [] getParents() {return m_nParents;}
+
+  /**
+   * sets index parent of parent specified by index
+   * 
+   * @param iParent Index of parent
+   * @param nNode index of the node that becomes parent
+   */
+  public void SetParent(int iParent, int nNode) {
+	m_nParents[iParent] = nNode;
+  } // SetParent
+
+
+  /**
+   * Holds number of parents
+   */
+  private int m_nNrOfParents = 0;
+
+  /**
+   * returns number of parents
+   * @return number of parents
+   */
+  public int getNrOfParents() {
+    return m_nNrOfParents;
+  } 
+
+  /**
+   * test if node is contained in parent set
+   * @param iNode node to test for
+   * @return number of parents
+   */
+	public boolean contains(int iNode) {
+		for (int iParent = 0; iParent < m_nNrOfParents; iParent++) {
+			if (m_nParents[iParent] == iNode) {
+				return true;
+			}
+		}
+		return false;
+	}
+  /**
+   * Holds cardinality  of parents (= number of instantiations the parents can take)
+   */
+  private int m_nCardinalityOfParents = 1;
+
+  /**
+   * returns cardinality of parents
+   * 
+   * @return the cardinality
+   */
+  public int getCardinalityOfParents() {
+    return m_nCardinalityOfParents;
+  } 
+
+  /**
+   * returns cardinality of parents after recalculation
+   * 
+   * @return the cardinality
+   */
+  public int getFreshCardinalityOfParents(Instances _Instances) {
+	  m_nCardinalityOfParents = 1;
+	  for (int iParent = 0; iParent < m_nNrOfParents; iParent++) {
+		m_nCardinalityOfParents *= _Instances.attribute(m_nParents[iParent]).numValues();
+	  }
+      return m_nCardinalityOfParents;
+  }
+  /**
+   * default constructor
+   */
+  public ParentSet() {
+    m_nParents = new int[10];
+    m_nNrOfParents = 0;
+    m_nCardinalityOfParents = 1;
+  }    // ParentSet
+
+  /**
+   * constructor
+   * @param nMaxNrOfParents upper bound on nr of parents
+   */
+  public ParentSet(int nMaxNrOfParents) {
+    m_nParents = new int[nMaxNrOfParents];
+    m_nNrOfParents = 0;
+    m_nCardinalityOfParents = 1;
+  }    // ParentSet
+
+  /**
+   * copy constructor
+   * @param other other parent set
+   */
+  public ParentSet(ParentSet other) {
+    m_nNrOfParents = other.m_nNrOfParents;
+    m_nCardinalityOfParents = other.m_nCardinalityOfParents;
+    m_nParents = new int[m_nNrOfParents];
+
+    for (int iParent = 0; iParent < m_nNrOfParents; iParent++) {
+      m_nParents[iParent] = other.m_nParents[iParent];
+    } 
+  }    // ParentSet
+
+  /**
+   * reserve memory for parent set
+   * 
+   * @param nSize maximum size of parent set to reserver memory for
+   */
+  public void maxParentSetSize(int nSize) {
+    m_nParents = new int[nSize];
+  }    // MaxParentSetSize
+ 
+  /**
+   * Add parent to parent set and update internals (specifically the cardinality of the parent set)
+   * 
+   * @param nParent parent to add
+   * @param _Instances used for updating the internals
+   */
+  public void addParent(int nParent, Instances _Instances) {
+   if (m_nNrOfParents == 10) {
+	// reserve more memory
+	int [] nParents = new int[50];
+        for (int i = 0; i < m_nNrOfParents; i++) {
+            nParents[i] = m_nParents[i];
+        }
+        m_nParents = nParents;
+   }
+    m_nParents[m_nNrOfParents] = nParent;
+    m_nNrOfParents++;
+    m_nCardinalityOfParents *= _Instances.attribute(nParent).numValues();
+  }    // AddParent
+
+  /**
+   * Add parent to parent set at specific location 
+   * and update internals (specifically the cardinality of the parent set)
+   * 
+   * @param nParent parent to add
+   * @param iParent location to add parent in parent set
+   * @param _Instances used for updating the internals
+   */
+  public void addParent(int nParent, int iParent, Instances _Instances) {
+   if (m_nNrOfParents == 10) {
+	// reserve more memory
+	int [] nParents = new int[50];
+		for (int i = 0; i < m_nNrOfParents; i++) {
+			nParents[i] = m_nParents[i];
+		}
+		m_nParents = nParents;
+   }
+	for (int iParent2 = m_nNrOfParents; iParent2 > iParent; iParent2--) {
+		m_nParents[iParent2] = m_nParents[iParent2 - 1];		
+	}
+	m_nParents[iParent] = nParent;
+	m_nNrOfParents++;
+	m_nCardinalityOfParents *= _Instances.attribute(nParent).numValues();
+  } // AddParent
+
+  /** delete node from parent set
+   * @param nParent node number of the parent to delete
+   * @param _Instances data set
+   * @return location of the parent in the parent set. This information can be 
+   * used to restore the parent set using the addParent method.
+   */
+  public int deleteParent(int nParent, Instances _Instances) {
+      int iParent = 0;
+      while ((m_nParents[iParent] != nParent) && (iParent < m_nNrOfParents)) {
+          iParent++;
+      }
+      int iParent2 = -1;
+      if (iParent < m_nNrOfParents) {
+      	iParent2 = iParent;
+      }
+      if (iParent < m_nNrOfParents) {
+        while (iParent < m_nNrOfParents - 1) {
+            m_nParents[iParent] = m_nParents[iParent + 1];
+            iParent++;
+        }
+      	m_nNrOfParents--;
+      	m_nCardinalityOfParents /= _Instances.attribute(nParent).numValues();
+      }
+      return iParent2;
+  } // DeleteParent
+  
+  /**
+   * Delete last added parent from parent set and update internals (specifically the cardinality of the parent set)
+   * 
+   * @param _Instances used for updating the internals
+   */
+  public void deleteLastParent(Instances _Instances) {
+    m_nNrOfParents--;
+    m_nCardinalityOfParents = 
+      m_nCardinalityOfParents 
+      / _Instances.attribute(m_nParents[m_nNrOfParents]).numValues();
+  }    // DeleteLastParent
+
+  /** Copy makes current parents set equal to other parent set
+   * 
+   * @param other : parent set to make a copy from
+   */
+  public void copy(ParentSet other) {
+    m_nCardinalityOfParents = other.m_nCardinalityOfParents;
+    m_nNrOfParents = other.m_nNrOfParents;
+    for (int iParent = 0; iParent < m_nNrOfParents; iParent++) {
+      m_nParents[iParent] = other.m_nParents[iParent];
+    }
+  } // Copy
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+ 
+}      // class ParentSet
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/VaryNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/VaryNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/VaryNode.java	(revision 29)
@@ -0,0 +1,136 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * VaryNode.java
+ * Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Part of ADTree implementation. See ADNode.java for more details.
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class VaryNode
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6196294370675872424L;
+
+  /** index of the node varied **/
+  public int m_iNode;
+
+  /** most common value **/
+  public int m_nMCV;
+
+  /** list of ADNode children **/
+  public ADNode [] m_ADNodes;
+
+  /** Creates new VaryNode */
+  public VaryNode(int iNode) {
+    m_iNode = iNode;
+  }
+
+  /**
+   * get counts for specific instantiation of a set of nodes
+   * 
+   * @param nCounts array for storing counts
+   * @param nNodes array of node indexes 
+   * @param nOffsets offset for nodes in nNodes in nCounts
+   * @param iNode index into nNode indicating current node
+   * @param iOffset Offset into nCounts due to nodes below iNode
+   * @param parent parant ADNode of this VaryNode
+   * @param bSubstract indicate whether counts should be added or substracted
+   */
+  public void getCounts(
+      int [] nCounts, 
+      int [] nNodes, 
+      int [] nOffsets, 
+      int iNode, 
+      int iOffset, 
+      ADNode parent,
+      boolean bSubstract) {
+    int nCurrentNode = nNodes[iNode];
+    for (int iValue = 0 ; iValue < m_ADNodes.length; iValue++) {
+      if (iValue != m_nMCV) {
+	if (m_ADNodes[iValue] != null) {
+	  m_ADNodes[iValue].getCounts(nCounts, 
+	      nNodes, 
+	      nOffsets, 
+	      iNode + 1, 
+	      iOffset + nOffsets[iNode] * iValue, 
+	      bSubstract);
+	}
+      } else {
+	parent.getCounts(nCounts, 
+	    nNodes, 
+	    nOffsets, 
+	    iNode + 1, 
+	    iOffset + nOffsets[iNode] * iValue, 
+	    bSubstract);
+	for (int iValue2 = 0; iValue2 < m_ADNodes.length; iValue2++) {
+	  if (iValue2 != m_nMCV && m_ADNodes[iValue2] != null) {
+	    m_ADNodes[iValue2].getCounts(nCounts, 
+		nNodes, 
+		nOffsets, 
+		iNode + 1, 
+		iOffset + nOffsets[iNode] * iValue, 
+		!bSubstract);
+	  }
+	}
+      }
+    }
+  }
+
+  /** 
+   * print is used for debugging only, called from ADNode
+   * 
+   * @param sTab amount of space.
+   */
+  public void print(String sTab) {
+    for (int iValue = 0; iValue < m_ADNodes.length; iValue++) {
+      System.out.print(sTab + iValue + ": ");
+      if (m_ADNodes[iValue] == null) {
+	if (iValue == m_nMCV) {
+	  System.out.println("MCV");
+	} else {
+	  System.out.println("null");
+	}
+      } else {
+	System.out.println();
+	m_ADNodes[iValue].print();
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BMAEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BMAEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BMAEstimator.java	(revision 29)
@@ -0,0 +1,314 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNet.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.estimate;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.search.local.K2;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+import weka.estimators.Estimator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * BMAEstimator estimates conditional probability tables of a Bayes network using Bayes Model Averaging (BMA).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -k2
+ *  Whether to use K2 prior.
+ * </pre>
+ * 
+ * <pre> -A &lt;alpha&gt;
+ *  Initial count (alpha)
+ * </pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.8 $
+ */
+public class BMAEstimator 
+    extends SimpleEstimator {
+
+    /** for serialization */
+    static final long serialVersionUID = -1846028304233257309L;
+  
+    /** whether to use K2 prior */
+    protected boolean m_bUseK2Prior = false;
+    
+    /**
+     * Returns a string describing this object
+     * @return a description of the classifier suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+      return 
+      	  "BMAEstimator estimates conditional probability tables of a Bayes "
+        + "network using Bayes Model Averaging (BMA).";
+    }
+
+    /**
+     * estimateCPTs estimates the conditional probability tables for the Bayes
+     * Net using the network structure.
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception if an error occurs
+     */
+    public void estimateCPTs(BayesNet bayesNet) throws Exception {
+        initCPTs(bayesNet);
+
+        Instances instances = bayesNet.m_Instances;
+        // sanity check to see if nodes have not more than one parent
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            if (bayesNet.getParentSet(iAttribute).getNrOfParents() > 1) {
+                throw new Exception("Cannot handle networks with nodes with more than 1 parent (yet).");
+            }
+        }
+
+        BayesNet EmptyNet = new BayesNet();
+        K2 oSearchAlgorithm = new K2();
+        oSearchAlgorithm.setInitAsNaiveBayes(false);
+        oSearchAlgorithm.setMaxNrOfParents(0);
+        EmptyNet.setSearchAlgorithm(oSearchAlgorithm);
+        EmptyNet.buildClassifier(instances);
+
+        BayesNet NBNet = new BayesNet();
+        oSearchAlgorithm.setInitAsNaiveBayes(true);
+        oSearchAlgorithm.setMaxNrOfParents(1);
+        NBNet.setSearchAlgorithm(oSearchAlgorithm);
+        NBNet.buildClassifier(instances);
+
+        // estimate CPTs
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            if (iAttribute != instances.classIndex()) {
+                  double w1 = 0.0, w2 = 0.0;
+                  int nAttValues = instances.attribute(iAttribute).numValues();
+                  if (m_bUseK2Prior == true) {
+                      // use Cooper and Herskovitz's metric
+                      for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                        w1 += Statistics.lnGamma(1 + ((DiscreteEstimatorBayes)EmptyNet.m_Distributions[iAttribute][0]).getCount(iAttValue))
+                              - Statistics.lnGamma(1);
+                      }
+                      w1 += Statistics.lnGamma(nAttValues) - Statistics.lnGamma(nAttValues + instances.numInstances());
+
+                      for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getCardinalityOfParents(); iParent++) {
+                        int nTotal = 0;
+                          for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                            double nCount = ((DiscreteEstimatorBayes)NBNet.m_Distributions[iAttribute][iParent]).getCount(iAttValue);
+                            w2 += Statistics.lnGamma(1 + nCount)
+                                  - Statistics.lnGamma(1);
+                            nTotal += nCount;
+                          }
+                        w2 += Statistics.lnGamma(nAttValues) - Statistics.lnGamma(nAttValues + nTotal);
+                      }
+                  } else {
+                      // use BDe metric
+                      for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                        w1 += Statistics.lnGamma(1.0/nAttValues + ((DiscreteEstimatorBayes)EmptyNet.m_Distributions[iAttribute][0]).getCount(iAttValue))
+                              - Statistics.lnGamma(1.0/nAttValues);
+                      }
+                      w1 += Statistics.lnGamma(1) - Statistics.lnGamma(1 + instances.numInstances());
+
+                      int nParentValues = bayesNet.getParentSet(iAttribute).getCardinalityOfParents();
+                      for (int iParent = 0; iParent < nParentValues; iParent++) {
+                        int nTotal = 0;
+                          for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                            double nCount = ((DiscreteEstimatorBayes)NBNet.m_Distributions[iAttribute][iParent]).getCount(iAttValue);
+                            w2 += Statistics.lnGamma(1.0/(nAttValues * nParentValues) + nCount)
+                                  - Statistics.lnGamma(1.0/(nAttValues * nParentValues));
+                            nTotal += nCount;
+                          }
+                        w2 += Statistics.lnGamma(1) - Statistics.lnGamma(1 + nTotal);
+                      }
+                  }
+		
+//    System.out.println(w1 + " " + w2 + " " + (w2 - w1));
+                  if (w1 < w2) {
+                    w2 = w2 - w1;
+                    w1 = 0;
+                    w1 = 1 / (1 + Math.exp(w2));
+                    w2 = Math.exp(w2) / (1 + Math.exp(w2));
+                  } else {
+                    w1 = w1 - w2;
+                    w2 = 0;
+                    w2 = 1 / (1 + Math.exp(w1));
+                    w1 = Math.exp(w1) / (1 + Math.exp(w1));
+                  }
+		
+                  for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getCardinalityOfParents(); iParent++) {
+                      bayesNet.m_Distributions[iAttribute][iParent] = 
+                      new DiscreteEstimatorFullBayes(
+                        instances.attribute(iAttribute).numValues(), 
+                        w1, w2,
+                        (DiscreteEstimatorBayes) EmptyNet.m_Distributions[iAttribute][0],
+                        (DiscreteEstimatorBayes) NBNet.m_Distributions[iAttribute][iParent],
+                        m_fAlpha
+                       );
+                  } 
+            }
+        }
+        int iAttribute = instances.classIndex();
+        bayesNet.m_Distributions[iAttribute][0] = EmptyNet.m_Distributions[iAttribute][0];
+    } // estimateCPTs
+
+    /**
+     * Updates the classifier with the given instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the new training instance to include in the model
+     * @throws Exception if the instance could not be incorporated in
+     * the model.
+     */
+    public void updateClassifier(BayesNet bayesNet, Instance instance) throws Exception {
+        throw new Exception("updateClassifier does not apply to BMA estimator");
+    } // updateClassifier
+
+    /** 
+     * initCPTs reserves space for CPTs and set all counts to zero
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception if something goes wrong
+     */
+    public void initCPTs(BayesNet bayesNet) throws Exception {
+        // Reserve space for CPTs
+        int nMaxParentCardinality = 1;
+
+        for (int iAttribute = 0; iAttribute < bayesNet.m_Instances.numAttributes(); iAttribute++) {
+            if (bayesNet.getParentSet(iAttribute).getCardinalityOfParents() > nMaxParentCardinality) {
+                nMaxParentCardinality = bayesNet.getParentSet(iAttribute).getCardinalityOfParents();
+            }
+        }
+
+        // Reserve plenty of memory
+        bayesNet.m_Distributions = new Estimator[bayesNet.m_Instances.numAttributes()][nMaxParentCardinality];
+    } // initCPTs
+
+
+    /**
+     * Returns whether K2 prior is used
+     * 
+     * @return true if K2 prior is used
+     */
+    public boolean isUseK2Prior() {
+        return m_bUseK2Prior;
+    }
+
+    /**
+     * Sets the UseK2Prior.
+     * 
+     * @param bUseK2Prior The bUseK2Prior to set
+     */
+    public void setUseK2Prior(boolean bUseK2Prior) {
+        m_bUseK2Prior = bUseK2Prior;
+    }
+
+    /**
+     * Returns an enumeration describing the available options
+     * 
+     * @return an enumeration of all the available options
+     */
+    public Enumeration listOptions() {
+        Vector newVector = new Vector(1);
+
+        newVector.addElement(new Option(
+            "\tWhether to use K2 prior.\n", 
+            "k2", 0, "-k2"));
+
+        Enumeration enu = super.listOptions();
+        while (enu.hasMoreElements()) {
+                newVector.addElement(enu.nextElement());
+        }
+
+        return newVector.elements();
+    } // listOptions
+
+    /**
+     * Parses a given list of options. <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -k2
+     *  Whether to use K2 prior.
+     * </pre>
+     * 
+     * <pre> -A &lt;alpha&gt;
+     *  Initial count (alpha)
+     * </pre>
+     * 
+     <!-- options-end -->
+     * 
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        setUseK2Prior(Utils.getFlag("k2", options));
+
+        super.setOptions(options);
+    } // setOptions
+
+    /**
+     * Gets the current settings of the classifier.
+     * 
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+        String[] superOptions = super.getOptions();
+        String[] options = new String[1 + superOptions.length];
+        int current = 0;
+
+        if (isUseK2Prior())
+          options[current++] = "-k2";
+
+        // insert options from parent class
+        for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                options[current++] = superOptions[iOption];
+        }
+
+        // Fill up rest with empty strings, not nulls!
+        while (current < options.length) {
+                options[current++] = "";
+        }
+
+        return options;
+    } // getOptions
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.8 $");
+    }
+} // class BMAEstimator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BayesNetEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BayesNetEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/BayesNetEstimator.java	(revision 29)
@@ -0,0 +1,211 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNetEstimator.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net.estimate;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instance;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * BayesNetEstimator is the base class for estimating the conditional probability tables of a Bayes network once the structure has been learned.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;alpha&gt;
+ *  Initial count (alpha)
+ * </pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.4 $
+ */
+public class BayesNetEstimator 
+    implements OptionHandler, Serializable, RevisionHandler {
+  
+    /** for serialization */
+    static final long serialVersionUID = 2184330197666253884L;
+    
+    /**
+     * Holds prior on count
+     */
+    protected double m_fAlpha = 0.5;
+
+    /**
+     * estimateCPTs estimates the conditional probability tables for the Bayes
+     * Net using the network structure.
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception always throws an exception, since subclass needs to be used
+     */
+    public void estimateCPTs(BayesNet bayesNet) throws Exception {
+        throw new Exception("Incorrect BayesNetEstimator: use subclass instead.");
+    }
+
+    /**
+     * Updates the classifier with the given instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the new training instance to include in the model
+     * @throws Exception always throws an exception, since subclass needs to be used
+     */
+    public void updateClassifier(BayesNet bayesNet, Instance instance) throws Exception {
+        throw new Exception("Incorrect BayesNetEstimator: use subclass instead.");
+    }
+
+    /**
+     * Calculates the class membership probabilities for the given test
+     * instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the instance to be classified
+     * @return predicted class probability distribution
+     * @throws Exception always throws an exception, since subclass needs to be used
+     */
+    public double[] distributionForInstance(BayesNet bayesNet, Instance instance) throws Exception {
+        throw new Exception("Incorrect BayesNetEstimator: use subclass instead.");
+    }
+
+    /** 
+     * initCPTs reserves space for CPTs and set all counts to zero
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception always throws an exception, since subclass needs to be used
+     */
+    public void initCPTs(BayesNet bayesNet) throws Exception {
+        throw new Exception("Incorrect BayesNetEstimator: use subclass instead.");
+    } // initCPTs
+
+    /**
+     * Returns an enumeration describing the available options
+     * 
+     * @return an enumeration of all the available options
+     */
+    public Enumeration listOptions() {
+        Vector newVector = new Vector(1);
+
+        newVector.addElement(new Option("\tInitial count (alpha)\n", "A", 1, "-A <alpha>"));
+
+        return newVector.elements();
+    } // listOptions
+
+    /**
+     * Parses a given list of options. <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -A &lt;alpha&gt;
+     *  Initial count (alpha)
+     * </pre>
+     * 
+     <!-- options-end -->
+     * 
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        String sAlpha = Utils.getOption('A', options);
+
+        if (sAlpha.length() != 0) {
+            m_fAlpha = (new Float(sAlpha)).floatValue();
+        } else {
+            m_fAlpha = 0.5f;
+        }
+
+        Utils.checkForRemainingOptions(options);
+    } // setOptions
+
+    /**
+     * Gets the current settings of the classifier.
+     * 
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+        String[] options = new String[2];
+        int current = 0;
+
+        options[current++] = "-A";
+        options[current++] = "" + m_fAlpha;
+
+        return options;
+    } // getOptions
+
+    /**
+     * Set prior used in probability table estimation
+     * @param fAlpha representing prior
+     */
+    public void setAlpha(double fAlpha) {
+        m_fAlpha = fAlpha;
+    }
+
+    /**
+     * Get prior used in probability table estimation
+     * @return prior
+     */
+    public double getAlpha() {
+        return m_fAlpha;
+    }
+
+
+    /**
+     * @return a string to describe the Alpha option.
+     */
+    public String alphaTipText() {
+        return "Alpha is used for estimating the probability tables and can be interpreted"
+            + " as the initial count on each value.";
+    }
+
+    /**
+     * This will return a string describing the class.
+     * @return The string.
+     */
+    public String globalInfo() {
+        return 
+            "BayesNetEstimator is the base class for estimating the "
+          + "conditional probability tables of a Bayes network once the "
+          + "structure has been learned.";
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+
+} // BayesNetEstimator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorBayes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorBayes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorBayes.java	(revision 29)
@@ -0,0 +1,264 @@
+
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DiscreteEstimatorBayes.java
+ * Adapted from DiscreteEstimator.java
+ * 
+ */
+package weka.classifiers.bayes.net.estimate;
+
+import weka.classifiers.bayes.net.search.local.Scoreable;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+import weka.estimators.DiscreteEstimator;
+import weka.estimators.Estimator;
+
+/**
+ * Symbolic probability estimator based on symbol counts and a prior.
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.7 $
+ */
+public class DiscreteEstimatorBayes extends Estimator
+  implements Scoreable {
+
+  /** for serialization */
+  static final long serialVersionUID = 4215400230843212684L;
+  
+  /**
+   * Hold the counts
+   */
+  protected double[] m_Counts;
+
+  /**
+   * Hold the sum of counts
+   */
+  protected double   m_SumOfCounts;
+
+  /**
+   * Holds number of symbols in distribution
+   */
+  protected int      m_nSymbols = 0;
+
+  /**
+   * Holds the prior probability
+   */
+  protected double   m_fPrior = 0.0;
+
+  /**
+   * Constructor
+   * 
+   * @param nSymbols the number of possible symbols (remember to include 0)
+   * @param fPrior
+   */
+  public DiscreteEstimatorBayes(int nSymbols, double fPrior) {
+    m_fPrior = fPrior;
+    m_nSymbols = nSymbols;
+    m_Counts = new double[m_nSymbols];
+
+    for (int iSymbol = 0; iSymbol < m_nSymbols; iSymbol++) {
+      m_Counts[iSymbol] = m_fPrior;
+    } 
+
+    m_SumOfCounts = m_fPrior * (double) m_nSymbols;
+  }    // DiscreteEstimatorBayes
+
+  /**
+   * Add a new data value to the current estimator.
+   * 
+   * @param data the new data value
+   * @param weight the weight assigned to the data value
+   */
+  public void addValue(double data, double weight) {
+    m_Counts[(int) data] += weight;
+    m_SumOfCounts += weight;
+  } 
+
+  /**
+   * Get a probability estimate for a value
+   * 
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+    if (m_SumOfCounts == 0) {
+
+      // this can only happen if numSymbols = 0 in constructor
+      return 0;
+    } 
+
+    return (double) m_Counts[(int) data] / m_SumOfCounts;
+  } 
+
+  /**
+   * Get a counts for a value
+   * 
+   * @param data the value to get the counts for
+   * @return the count of the supplied value
+   */
+  public double getCount(double data) {
+    if (m_SumOfCounts == 0) {
+      // this can only happen if numSymbols = 0 in constructor
+      return 0;
+    } 
+
+    return m_Counts[(int) data];
+  } 
+  
+  /**
+   * Gets the number of symbols this estimator operates with
+   * 
+   * @return the number of estimator symbols
+   */
+  public int getNumSymbols() {
+    return (m_Counts == null) ? 0 : m_Counts.length;
+  } 
+
+  /**
+   * Gets the log score contribution of this distribution
+   * @param nType score type
+   * @return the score
+   */
+  public double logScore(int nType, int nCardinality) {
+	    double fScore = 0.0;
+
+	    switch (nType) {
+
+	    case (Scoreable.BAYES): {
+	      for (int iSymbol = 0; iSymbol < m_nSymbols; iSymbol++) {
+		fScore += Statistics.lnGamma(m_Counts[iSymbol]);
+	      } 
+
+	      fScore -= Statistics.lnGamma(m_SumOfCounts);
+	      if (m_fPrior != 0.0) {
+		      fScore -= m_nSymbols * Statistics.lnGamma(m_fPrior);
+	    	  fScore += Statistics.lnGamma(m_nSymbols * m_fPrior);
+	      }
+	    } 
+
+	      break;
+		  case (Scoreable.BDeu): {
+		  for (int iSymbol = 0; iSymbol < m_nSymbols; iSymbol++) {
+			fScore += Statistics.lnGamma(m_Counts[iSymbol]);
+		  } 
+
+		  fScore -= Statistics.lnGamma(m_SumOfCounts);
+		  //fScore -= m_nSymbols * Statistics.lnGamma(1.0);
+		  //fScore += Statistics.lnGamma(m_nSymbols * 1.0);
+	      fScore -= m_nSymbols * Statistics.lnGamma(1.0/(m_nSymbols * nCardinality));
+	      fScore += Statistics.lnGamma(1.0/nCardinality);
+		} 
+		  break;
+
+	    case (Scoreable.MDL):
+
+	    case (Scoreable.AIC):
+
+	    case (Scoreable.ENTROPY): {
+	      for (int iSymbol = 0; iSymbol < m_nSymbols; iSymbol++) {
+		double fP = getProbability(iSymbol);
+
+		fScore += m_Counts[iSymbol] * Math.log(fP);
+	      } 
+	    } 
+
+	      break;
+
+	    default: {}
+	    }
+
+	    return fScore;
+	  } 
+
+  /**
+   * Display a representation of this estimator
+   * 
+   * @return a string representation of the estimator
+   */
+  public String toString() {
+    String result = "Discrete Estimator. Counts = ";
+
+    if (m_SumOfCounts > 1) {
+      for (int i = 0; i < m_Counts.length; i++) {
+	result += " " + Utils.doubleToString(m_Counts[i], 2);
+      } 
+
+      result += "  (Total = " + Utils.doubleToString(m_SumOfCounts, 2) 
+		+ ")\n";
+    } else {
+      for (int i = 0; i < m_Counts.length; i++) {
+	result += " " + m_Counts[i];
+      } 
+
+      result += "  (Total = " + m_SumOfCounts + ")\n";
+    } 
+
+    return result;
+  } 
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   * 
+   * @param argv should contain a sequence of integers which
+   * will be treated as symbolic.
+   */
+  public static void main(String[] argv) {
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+
+	return;
+      } 
+
+      int current = Integer.parseInt(argv[0]);
+      int max = current;
+
+      for (int i = 1; i < argv.length; i++) {
+	current = Integer.parseInt(argv[i]);
+
+	if (current > max) {
+	  max = current;
+	} 
+      } 
+
+      DiscreteEstimator newEst = new DiscreteEstimator(max + 1, true);
+
+      for (int i = 0; i < argv.length; i++) {
+	current = Integer.parseInt(argv[i]);
+
+	System.out.println(newEst);
+	System.out.println("Prediction for " + current + " = " 
+			   + newEst.getProbability(current));
+	newEst.addValue(current, 1);
+      } 
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    } 
+  }    // main
+ 
+}      // class DiscreteEstimatorBayes
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorFullBayes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorFullBayes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/DiscreteEstimatorFullBayes.java	(revision 29)
@@ -0,0 +1,119 @@
+
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DiscreteEstimatorFullBayes.java
+ * 
+ */
+package weka.classifiers.bayes.net.estimate;
+
+import weka.core.RevisionUtils;
+import weka.estimators.DiscreteEstimator;
+
+/**
+ * Symbolic probability estimator based on symbol counts and a prior.
+ *  
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.3 $
+ */
+public class DiscreteEstimatorFullBayes 
+  extends DiscreteEstimatorBayes {
+
+  /** for serialization */
+  static final long serialVersionUID = 6774941981423312133L;
+  
+  /**
+   * Constructor
+   * 
+   * @param nSymbols the number of possible symbols (remember to include 0)
+   * @param w1
+   * @param w2
+   * @param EmptyDist
+   * @param ClassDist
+   * @param fPrior
+   */
+  public DiscreteEstimatorFullBayes(int nSymbols, 
+    double w1, double w2,
+    DiscreteEstimatorBayes EmptyDist,
+    DiscreteEstimatorBayes ClassDist,
+    double fPrior) {
+    
+    super(nSymbols, fPrior);
+
+    m_SumOfCounts = 0.0;
+    for (int iSymbol = 0; iSymbol < m_nSymbols; iSymbol++) {
+      double p1 = EmptyDist.getProbability(iSymbol);
+      double p2 = ClassDist.getProbability(iSymbol);
+      m_Counts[iSymbol] = w1 * p1 + w2 * p2;
+      m_SumOfCounts += m_Counts[iSymbol];
+    } 
+  } // DiscreteEstimatorFullBayes
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.3 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param argv should contain a sequence of integers which
+   * will be treated as symbolic.
+   */
+  public static void main(String[] argv) {
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+
+	return;
+      } 
+
+      int current = Integer.parseInt(argv[0]);
+      int max = current;
+
+      for (int i = 1; i < argv.length; i++) {
+	current = Integer.parseInt(argv[i]);
+
+	if (current > max) {
+	  max = current;
+	} 
+      } 
+
+      DiscreteEstimator newEst = new DiscreteEstimator(max + 1, true);
+
+      for (int i = 0; i < argv.length; i++) {
+	current = Integer.parseInt(argv[i]);
+
+	System.out.println(newEst);
+	System.out.println("Prediction for " + current + " = " 
+			   + newEst.getProbability(current));
+	newEst.addValue(current, 1);
+      } 
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    } 
+  }    // main
+ 
+}  // class DiscreteEstimatorFullBayes
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/MultiNomialBMAEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/MultiNomialBMAEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/MultiNomialBMAEstimator.java	(revision 29)
@@ -0,0 +1,394 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package weka.classifiers.bayes.net.estimate;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.search.local.K2;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+import weka.estimators.Estimator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Multinomial BMA Estimator.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -k2
+ *  Whether to use K2 prior.
+ * </pre>
+ * 
+ * <pre> -A &lt;alpha&gt;
+ *  Initial count (alpha)
+ * </pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @version $Revision: 5987 $
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ */
+public class MultiNomialBMAEstimator 
+    extends BayesNetEstimator {
+
+    /** for serialization */
+    static final long serialVersionUID = 8330705772601586313L;
+    
+    /** whether to use K2 prior */
+    protected boolean m_bUseK2Prior = true;
+    
+    /**
+     * Returns a string describing this object
+     * @return a description of the classifier suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+      return 
+      	  "Multinomial BMA Estimator.";
+    }
+
+    /**
+     * estimateCPTs estimates the conditional probability tables for the Bayes
+     * Net using the network structure.
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception if number of parents doesn't fit (more than 1)
+     */
+    public void estimateCPTs(BayesNet bayesNet) throws Exception {
+        initCPTs(bayesNet);
+        
+        // sanity check to see if nodes have not more than one parent
+        for (int iAttribute = 0; iAttribute < bayesNet.m_Instances.numAttributes(); iAttribute++) {
+            if (bayesNet.getParentSet(iAttribute).getNrOfParents() > 1) {
+                throw new Exception("Cannot handle networks with nodes with more than 1 parent (yet).");
+            }
+        }
+
+		// filter data to binary
+        Instances instances = new Instances(bayesNet.m_Instances);
+        while (instances.numInstances() > 0) {
+            instances.delete(0);
+        }
+        for (int iAttribute = instances.numAttributes() - 1; iAttribute >= 0; iAttribute--) {
+            if (iAttribute != instances.classIndex()) {
+                FastVector values = new FastVector();
+                values.addElement("0");
+                values.addElement("1");
+                Attribute a = new Attribute(instances.attribute(iAttribute).name(), (FastVector) values);
+                instances.deleteAttributeAt(iAttribute);
+                instances.insertAttributeAt(a,iAttribute);
+            }
+        }
+        
+        for (int iInstance = 0; iInstance < bayesNet.m_Instances.numInstances(); iInstance++) {
+            Instance instanceOrig = bayesNet.m_Instances.instance(iInstance);
+            Instance instance = new DenseInstance(instances.numAttributes());
+            for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+                if (iAttribute != instances.classIndex()) {
+                    if (instanceOrig.value(iAttribute) > 0) {
+                        instance.setValue(iAttribute, 1);
+                    }
+                } else {
+                    instance.setValue(iAttribute, instanceOrig.value(iAttribute));
+                }
+            }
+        }
+        // ok, now all data is binary, except the class attribute
+        // now learn the empty and tree network
+
+        BayesNet EmptyNet = new BayesNet();
+        K2 oSearchAlgorithm = new K2();
+        oSearchAlgorithm.setInitAsNaiveBayes(false);
+        oSearchAlgorithm.setMaxNrOfParents(0);
+        EmptyNet.setSearchAlgorithm(oSearchAlgorithm);
+        EmptyNet.buildClassifier(instances);
+
+        BayesNet NBNet = new BayesNet();
+        oSearchAlgorithm.setInitAsNaiveBayes(true);
+        oSearchAlgorithm.setMaxNrOfParents(1);
+        NBNet.setSearchAlgorithm(oSearchAlgorithm);
+        NBNet.buildClassifier(instances);
+
+        // estimate CPTs
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            if (iAttribute != instances.classIndex()) {
+                  double w1 = 0.0, w2 = 0.0;
+                  int nAttValues = instances.attribute(iAttribute).numValues();
+                  if (m_bUseK2Prior == true) {
+                      // use Cooper and Herskovitz's metric
+                      for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                        w1 += Statistics.lnGamma(1 + ((DiscreteEstimatorBayes)EmptyNet.m_Distributions[iAttribute][0]).getCount(iAttValue))
+                              - Statistics.lnGamma(1);
+                      }
+                      w1 += Statistics.lnGamma(nAttValues) - Statistics.lnGamma(nAttValues + instances.numInstances());
+
+                      for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getCardinalityOfParents(); iParent++) {
+                        int nTotal = 0;
+                          for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                            double nCount = ((DiscreteEstimatorBayes)NBNet.m_Distributions[iAttribute][iParent]).getCount(iAttValue);
+                            w2 += Statistics.lnGamma(1 + nCount)
+                                  - Statistics.lnGamma(1);
+                            nTotal += nCount;
+                          }
+                        w2 += Statistics.lnGamma(nAttValues) - Statistics.lnGamma(nAttValues + nTotal);
+                      }
+                  } else {
+                      // use BDe metric
+                      for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                        w1 += Statistics.lnGamma(1.0/nAttValues + ((DiscreteEstimatorBayes)EmptyNet.m_Distributions[iAttribute][0]).getCount(iAttValue))
+                              - Statistics.lnGamma(1.0/nAttValues);
+                      }
+                      w1 += Statistics.lnGamma(1) - Statistics.lnGamma(1 + instances.numInstances());
+
+					  int nParentValues = bayesNet.getParentSet(iAttribute).getCardinalityOfParents();
+                      for (int iParent = 0; iParent < nParentValues; iParent++) {
+                        int nTotal = 0;
+                          for (int iAttValue = 0; iAttValue < nAttValues; iAttValue++) {
+                            double nCount = ((DiscreteEstimatorBayes)NBNet.m_Distributions[iAttribute][iParent]).getCount(iAttValue);
+                            w2 += Statistics.lnGamma(1.0/(nAttValues * nParentValues) + nCount)
+                                  - Statistics.lnGamma(1.0/(nAttValues * nParentValues));
+                            nTotal += nCount;
+                          }
+                        w2 += Statistics.lnGamma(1) - Statistics.lnGamma(1 + nTotal);
+                      }
+                  }
+		
+//    System.out.println(w1 + " " + w2 + " " + (w2 - w1));
+                  // normalize weigths
+                  if (w1 < w2) {
+                    w2 = w2 - w1;
+                    w1 = 0;
+                    w1 = 1 / (1 + Math.exp(w2));
+                    w2 = Math.exp(w2) / (1 + Math.exp(w2));
+                  } else {
+                    w1 = w1 - w2;
+                    w2 = 0;
+                    w2 = 1 / (1 + Math.exp(w1));
+                    w1 = Math.exp(w1) / (1 + Math.exp(w1));
+                  }
+		
+                  for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getCardinalityOfParents(); iParent++) {
+                      bayesNet.m_Distributions[iAttribute][iParent] = 
+                      new DiscreteEstimatorFullBayes(
+                        instances.attribute(iAttribute).numValues(), 
+                        w1, w2,
+                        (DiscreteEstimatorBayes) EmptyNet.m_Distributions[iAttribute][0],
+                        (DiscreteEstimatorBayes) NBNet.m_Distributions[iAttribute][iParent],
+                        m_fAlpha
+                       );
+                  } 
+            }
+        }
+        int iAttribute = instances.classIndex();
+        bayesNet.m_Distributions[iAttribute][0] = EmptyNet.m_Distributions[iAttribute][0];
+    } // estimateCPTs
+
+    /**
+     * Updates the classifier with the given instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the new training instance to include in the model
+     * @throws Exception if the instance could not be incorporated in
+     * the model.
+     */
+    public void updateClassifier(BayesNet bayesNet, Instance instance) throws Exception {
+        throw new Exception("updateClassifier does not apply to BMA estimator");
+    } // updateClassifier
+
+    /** 
+     * initCPTs reserves space for CPTs and set all counts to zero
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception doesn't apply
+     */
+    public void initCPTs(BayesNet bayesNet) throws Exception {
+        // Reserve sufficient memory
+        bayesNet.m_Distributions = new Estimator[bayesNet.m_Instances.numAttributes()][2];
+    } // initCPTs
+
+
+    /**
+     * @return boolean
+     */
+    public boolean isUseK2Prior() {
+        return m_bUseK2Prior;
+    }
+
+    /**
+     * Sets the UseK2Prior.
+     * 
+     * @param bUseK2Prior The bUseK2Prior to set
+     */
+    public void setUseK2Prior(boolean bUseK2Prior) {
+        m_bUseK2Prior = bUseK2Prior;
+    }
+
+    /**
+     * Calculates the class membership probabilities for the given test
+     * instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the instance to be classified
+     * @return predicted class probability distribution
+     * @throws Exception if there is a problem generating the prediction
+     */
+    public double[] distributionForInstance(BayesNet bayesNet, Instance instance) throws Exception {
+        Instances instances = bayesNet.m_Instances;
+        int nNumClasses = instances.numClasses();
+        double[] fProbs = new double[nNumClasses];
+
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            fProbs[iClass] = 1.0;
+        }
+
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            double logfP = 0;
+
+            for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+                double iCPT = 0;
+
+                for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getNrOfParents(); iParent++) {
+                    int nParent = bayesNet.getParentSet(iAttribute).getParent(iParent);
+
+                    if (nParent == instances.classIndex()) {
+                        iCPT = iCPT * nNumClasses + iClass;
+                    } else {
+                        iCPT = iCPT * instances.attribute(nParent).numValues() + instance.value(nParent);
+                    }
+                }
+
+                if (iAttribute == instances.classIndex()) {
+                    logfP += Math.log(bayesNet.m_Distributions[iAttribute][(int) iCPT].getProbability(iClass));
+                } else {
+                    logfP += instance.value(iAttribute) * Math.log(
+                      bayesNet.m_Distributions[iAttribute][(int) iCPT].getProbability(instance.value(1)));
+                }
+            }
+
+            fProbs[iClass] += logfP;
+        }
+
+        // Find maximum
+        double fMax = fProbs[0];
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            if (fProbs[iClass] > fMax) {
+                fMax = fProbs[iClass];
+            }
+        }
+        // transform from log-space to normal-space
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            fProbs[iClass] = Math.exp(fProbs[iClass] - fMax);
+        }
+
+        // Display probabilities
+        Utils.normalize(fProbs);
+
+        return fProbs;
+    } // distributionForInstance
+
+    /**
+     * Returns an enumeration describing the available options
+     * 
+     * @return an enumeration of all the available options
+     */
+    public Enumeration listOptions() {
+        Vector newVector = new Vector(1);
+
+        newVector.addElement(new Option(
+            "\tWhether to use K2 prior.\n", 
+            "k2", 0, "-k2"));
+
+        Enumeration enu = super.listOptions();
+        while (enu.hasMoreElements()) {
+                newVector.addElement(enu.nextElement());
+        }
+
+        return newVector.elements();
+    } // listOptions
+
+    /**
+     * Parses a given list of options. <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -k2
+     *  Whether to use K2 prior.
+     * </pre>
+     * 
+     * <pre> -A &lt;alpha&gt;
+     *  Initial count (alpha)
+     * </pre>
+     * 
+     <!-- options-end -->
+     * 
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        setUseK2Prior(Utils.getFlag("k2", options));
+
+        super.setOptions(options);
+    } // setOptions
+
+    /**
+     * Gets the current settings of the classifier.
+     * 
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+        String[] superOptions = super.getOptions();
+        String[] options = new String[1 + superOptions.length];
+        int current = 0;
+
+        if (isUseK2Prior())
+          options[current++] = "-k2";
+
+        // insert options from parent class
+        for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                options[current++] = superOptions[iOption];
+        }
+
+        // Fill up rest with empty strings, not nulls!
+        while (current < options.length) {
+                options[current++] = "";
+        }
+
+        return options;
+    } // getOptions
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+} // class MultiNomialBMAEstimator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/SimpleEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/SimpleEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/estimate/SimpleEstimator.java	(revision 29)
@@ -0,0 +1,219 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNet.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.estimate;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.estimators.Estimator;
+
+import java.util.Enumeration;
+
+/** 
+ <!-- globalinfo-start -->
+ * SimpleEstimator is used for estimating the conditional probability tables of a Bayes network once the structure has been learned. Estimates probabilities directly from data.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;alpha&gt;
+ *  Initial count (alpha)
+ * </pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class SimpleEstimator 
+    extends BayesNetEstimator {
+
+    /** for serialization */
+    static final long serialVersionUID = 5874941612331806172L;
+    
+    /**
+     * Returns a string describing this object
+     * @return a description of the classifier suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+      return 
+          "SimpleEstimator is used for estimating the conditional probability "
+      	+ "tables of a Bayes network once the structure has been learned. "
+      	+ "Estimates probabilities directly from data.";
+    }
+  
+    /**
+     * estimateCPTs estimates the conditional probability tables for the Bayes
+     * Net using the network structure.
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception if something goes wrong
+     */
+    public void estimateCPTs(BayesNet bayesNet) throws Exception {
+            initCPTs(bayesNet);
+
+            // Compute counts
+            Enumeration enumInsts = bayesNet.m_Instances.enumerateInstances();
+            while (enumInsts.hasMoreElements()) {
+                Instance instance = (Instance) enumInsts.nextElement();
+
+                updateClassifier(bayesNet, instance);
+            }
+    } // estimateCPTs
+
+    /**
+     * Updates the classifier with the given instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the new training instance to include in the model
+     * @throws Exception if the instance could not be incorporated in
+     * the model.
+     */
+    public void updateClassifier(BayesNet bayesNet, Instance instance) throws Exception {
+        for (int iAttribute = 0; iAttribute < bayesNet.m_Instances.numAttributes(); iAttribute++) {
+            double iCPT = 0;
+
+            for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getNrOfParents(); iParent++) {
+                int nParent = bayesNet.getParentSet(iAttribute).getParent(iParent);
+
+                iCPT = iCPT * bayesNet.m_Instances.attribute(nParent).numValues() + instance.value(nParent);
+            }
+
+            bayesNet.m_Distributions[iAttribute][(int) iCPT].addValue(instance.value(iAttribute), instance.weight());
+        }
+    } // updateClassifier
+
+
+    /** 
+     * initCPTs reserves space for CPTs and set all counts to zero
+     * 
+     * @param bayesNet the bayes net to use
+     * @throws Exception if something goes wrong
+     */
+    public void initCPTs(BayesNet bayesNet) throws Exception {
+        Instances instances = bayesNet.m_Instances;
+        
+        // Reserve space for CPTs
+        int nMaxParentCardinality = 1;
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            if (bayesNet.getParentSet(iAttribute).getCardinalityOfParents() > nMaxParentCardinality) {
+                nMaxParentCardinality = bayesNet.getParentSet(iAttribute).getCardinalityOfParents();
+            }
+        }
+	
+        // Reserve plenty of memory
+        bayesNet.m_Distributions = new Estimator[instances.numAttributes()][nMaxParentCardinality];
+	
+        // estimate CPTs
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getCardinalityOfParents(); iParent++) {
+                bayesNet.m_Distributions[iAttribute][iParent] =
+                    new DiscreteEstimatorBayes(instances.attribute(iAttribute).numValues(), m_fAlpha);
+            }
+        }
+    } // initCPTs
+
+    /**
+     * Calculates the class membership probabilities for the given test
+     * instance.
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instance the instance to be classified
+     * @return predicted class probability distribution
+     * @throws Exception if there is a problem generating the prediction
+     */
+    public double[] distributionForInstance(BayesNet bayesNet, Instance instance) throws Exception {
+        Instances instances = bayesNet.m_Instances;
+        int nNumClasses = instances.numClasses();
+        double[] fProbs = new double[nNumClasses];
+
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            fProbs[iClass] = 1.0;
+        }
+
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            double logfP = 0;
+
+            for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+                double iCPT = 0;
+
+                for (int iParent = 0; iParent < bayesNet.getParentSet(iAttribute).getNrOfParents(); iParent++) {
+                    int nParent = bayesNet.getParentSet(iAttribute).getParent(iParent);
+
+                    if (nParent == instances.classIndex()) {
+                        iCPT = iCPT * nNumClasses + iClass;
+                    } else {
+                        iCPT = iCPT * instances.attribute(nParent).numValues() + instance.value(nParent);
+                    }
+                }
+
+                if (iAttribute == instances.classIndex()) {
+                    //	  fP *= 
+                    //	    m_Distributions[iAttribute][(int) iCPT].getProbability(iClass);
+                    logfP += Math.log(bayesNet.m_Distributions[iAttribute][(int) iCPT].getProbability(iClass));
+                } else {
+                    //	  fP *= 
+                    //	    m_Distributions[iAttribute][(int) iCPT]
+                    //	      .getProbability(instance.value(iAttribute));
+                    logfP
+                        += Math.log(bayesNet.m_Distributions[iAttribute][(int) iCPT].getProbability(instance.value(iAttribute)));
+                }
+            }
+
+            //      fProbs[iClass] *= fP;
+            fProbs[iClass] += logfP;
+        }
+
+        // Find maximum
+        double fMax = fProbs[0];
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            if (fProbs[iClass] > fMax) {
+                fMax = fProbs[iClass];
+            }
+        }
+        // transform from log-space to normal-space
+        for (int iClass = 0; iClass < nNumClasses; iClass++) {
+            fProbs[iClass] = Math.exp(fProbs[iClass] - fMax);
+        }
+
+        // Display probabilities
+        Utils.normalize(fProbs);
+
+        return fProbs;
+    } // distributionForInstance
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.6 $");
+    }
+
+} // SimpleEstimator
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/SearchAlgorithm.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/SearchAlgorithm.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/SearchAlgorithm.java	(revision 29)
@@ -0,0 +1,412 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SearchAlgorithm.java
+ * Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * This is the base class for all search algorithms for learning Bayes networks.
+ * It contains some common code, used by other network structure search algorithms,
+ * and should not be used by itself.
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 1.9 $
+ */
+public class SearchAlgorithm 
+    implements OptionHandler, Serializable, RevisionHandler {
+  
+    /** for serialization */
+    static final long serialVersionUID = 6164792240778525312L;
+  
+    /**
+     * Holds upper bound on number of parents
+     */
+    protected int m_nMaxNrOfParents = 1;
+
+    /**
+     * determines whether initial structure is an empty graph or a Naive Bayes network
+     */
+    protected boolean m_bInitAsNaiveBayes = true;
+
+    /**
+     * Determines whether after structure is found a MarkovBlanketClassifier correction should be applied
+     * If this is true, m_bInitAsNaiveBayes is overridden and interpreted as false.
+     */
+    protected boolean m_bMarkovBlanketClassifier = false;
+
+    /** c'tor **/
+    public SearchAlgorithm() {
+    } // SearchAlgorithm
+
+    /**
+     * AddArcMakesSense checks whether adding the arc from iAttributeTail to iAttributeHead
+     * does not already exists and does not introduce a cycle
+     * 
+     * @param bayesNet
+     * @param instances
+     * @param iAttributeHead index of the attribute that becomes head of the arrow
+     * @param iAttributeTail index of the attribute that becomes tail of the arrow
+     * @return true if adding arc is allowed, otherwise false
+     */
+    protected boolean addArcMakesSense(
+        BayesNet bayesNet,
+        Instances instances,
+        int iAttributeHead,
+        int iAttributeTail) {
+        if (iAttributeHead == iAttributeTail) {
+            return false;
+        }
+
+        // sanity check: arc should not be in parent set already
+        if (isArc(bayesNet, iAttributeHead, iAttributeTail)) {
+               return false;
+        }
+
+        // sanity check: arc should not introduce a cycle
+        int nNodes = instances.numAttributes();
+        boolean[] bDone = new boolean[nNodes];
+
+        for (int iNode = 0; iNode < nNodes; iNode++) {
+            bDone[iNode] = false;
+        }
+
+        // check for cycles
+        bayesNet.getParentSet(iAttributeHead).addParent(iAttributeTail, instances);
+
+        for (int iNode = 0; iNode < nNodes; iNode++) {
+
+            // find a node for which all parents are 'done'
+            boolean bFound = false;
+
+            for (int iNode2 = 0; !bFound && iNode2 < nNodes; iNode2++) {
+                if (!bDone[iNode2]) {
+                    boolean bHasNoParents = true;
+
+                    for (int iParent = 0; iParent < bayesNet.getParentSet(iNode2).getNrOfParents(); iParent++) {
+                        if (!bDone[bayesNet.getParentSet(iNode2).getParent(iParent)]) {
+                            bHasNoParents = false;
+                        }
+                    }
+
+                    if (bHasNoParents) {
+                        bDone[iNode2] = true;
+                        bFound = true;
+                    }
+                }
+            }
+
+            if (!bFound) {
+                bayesNet.getParentSet(iAttributeHead).deleteLastParent(instances);
+
+                return false;
+            }
+        }
+
+        bayesNet.getParentSet(iAttributeHead).deleteLastParent(instances);
+
+        return true;
+    } // AddArcMakesCycle
+
+    /**
+     * reverseArcMakesSense checks whether the arc from iAttributeTail to
+     * iAttributeHead exists and reversing does not introduce a cycle
+     * 
+     * @param bayesNet
+     * @param instances
+     * @param iAttributeHead index of the attribute that is head of the arrow
+     * @param iAttributeTail index of the attribute that is tail of the arrow
+     * @return true if the arc from iAttributeTail to iAttributeHead exists and reversing does not introduce a cycle 
+     */
+    protected boolean reverseArcMakesSense(
+        BayesNet bayesNet,
+        Instances instances,
+        int iAttributeHead,
+        int iAttributeTail) {
+      
+        if (iAttributeHead == iAttributeTail) {
+            return false;
+        }
+
+        // sanity check: arc should be in parent set already
+        if (!isArc(bayesNet, iAttributeHead, iAttributeTail)) {
+            return false;
+        }
+
+        // sanity check: arc should not introduce a cycle
+        int nNodes = instances.numAttributes();
+        boolean[] bDone = new boolean[nNodes];
+
+        for (int iNode = 0; iNode < nNodes; iNode++) {
+            bDone[iNode] = false;
+        }
+
+        // check for cycles
+		bayesNet.getParentSet(iAttributeTail).addParent(iAttributeHead, instances);
+
+        for (int iNode = 0; iNode < nNodes; iNode++) {
+
+            // find a node for which all parents are 'done'
+            boolean bFound = false;
+
+            for (int iNode2 = 0; !bFound && iNode2 < nNodes; iNode2++) {
+                if (!bDone[iNode2]) {
+                	ParentSet parentSet = bayesNet.getParentSet(iNode2);
+                    boolean bHasNoParents = true;
+                    for (int iParent = 0; iParent < parentSet.getNrOfParents(); iParent++) {
+                        if (!bDone[parentSet.getParent(iParent)]) {
+                        	
+                            // this one has a parent which is not 'done' UNLESS it is the arc to be reversed
+                            if (!(iNode2 == iAttributeHead && parentSet.getParent(iParent) == iAttributeTail)) {
+                                bHasNoParents = false;
+                            }
+                        }
+                    }
+
+                    if (bHasNoParents) {
+                        bDone[iNode2] = true;
+                        bFound = true;
+                    }
+                }
+            }
+
+            if (!bFound) {
+                bayesNet.getParentSet(iAttributeTail).deleteLastParent(instances);
+                return false;
+            }
+        }
+
+        bayesNet.getParentSet(iAttributeTail).deleteLastParent(instances);
+        return true;
+    } // ReverseArcMakesCycle
+
+    /**
+     * IsArc checks whether the arc from iAttributeTail to iAttributeHead already exists
+     * 
+     * @param bayesNet
+     * @param iAttributeHead index of the attribute that becomes head of the arrow
+     * @param iAttributeTail index of the attribute that becomes tail of the arrow
+     * @return true if the arc from iAttributeTail to iAttributeHead already exists
+     */
+    protected boolean isArc(BayesNet bayesNet, int iAttributeHead, int iAttributeTail) {
+        for (int iParent = 0; iParent < bayesNet.getParentSet(iAttributeHead).getNrOfParents(); iParent++) {
+            if (bayesNet.getParentSet(iAttributeHead).getParent(iParent) == iAttributeTail) {
+                return true;
+            }
+        }
+
+        return false;
+    } // IsArc
+
+    /**
+     * Returns an enumeration describing the available options.
+     *
+     * @return an enumeration of all the available options.
+     */
+    public Enumeration listOptions() {
+        return new Vector(0).elements();
+    } // listOption
+
+    /**
+     * Parses a given list of options. <p/>
+     * 
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+    } // setOptions
+
+    /**
+     * Gets the current settings of the Classifier.
+     *
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+        return new String[0];
+    } // getOptions
+
+    /**
+     * a string representation of the algorithm
+     * 
+     * @return a string representation
+     */
+    public String toString() {
+        return "SearchAlgorithm\n";
+    } // toString
+
+    /**
+     * buildStructure determines the network structure/graph of the network.
+     * The default behavior is creating a network where all nodes have the first
+     * node as its parent (i.e., a BayesNet that behaves like a naive Bayes classifier).
+     * This method can be overridden by derived classes to restrict the class
+     * of network structures that are acceptable.
+     * 
+     * @param bayesNet the network
+     * @param instances the data to use
+     * @throws Exception if something goes wrong
+     */
+    public void buildStructure(BayesNet bayesNet, Instances instances) throws Exception {
+        if (m_bInitAsNaiveBayes) {
+            int iClass = instances.classIndex();
+            // initialize parent sets to have arrow from classifier node to
+            // each of the other nodes
+            for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+                if (iAttribute != iClass) {
+                    bayesNet.getParentSet(iAttribute).addParent(iClass, instances);
+                }
+            }
+        }
+        search(bayesNet, instances);
+        if (m_bMarkovBlanketClassifier) {
+            doMarkovBlanketCorrection(bayesNet, instances);
+        }
+    } // buildStructure 
+
+    /**
+     * 
+     * @param bayesNet
+     * @param instances
+     */
+    protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        // placeholder with implementation in derived classes
+    } // search
+
+    /** 
+     * for each node in the network make sure it is in the
+     * Markov blanket of the classifier node, and if not,
+     * add arrows so that it is. If the node is an ancestor
+     * of the classifier node, add arrow pointing to the classifier
+     * node, otherwise, add arrow pointing to attribute node.
+     * 
+     * @param bayesNet
+     * @param instances
+     */
+    protected void doMarkovBlanketCorrection(BayesNet bayesNet, Instances instances) {
+        // Add class node as parent if it is not in the Markov Boundary
+        int iClass = instances.classIndex();
+        ParentSet ancestors = new ParentSet();
+        int nOldSize = 0;
+        ancestors.addParent(iClass, instances);
+        while (nOldSize != ancestors.getNrOfParents()) {
+            nOldSize = ancestors.getNrOfParents();
+            for (int iNode = 0; iNode < nOldSize; iNode++) {
+                int iCurrent = ancestors.getParent(iNode);
+                ParentSet p = bayesNet.getParentSet(iCurrent);
+                for (int iParent = 0; iParent < p.getNrOfParents(); iParent++) {
+                    if (!ancestors.contains(p.getParent(iParent))) {
+                        ancestors.addParent(p.getParent(iParent), instances);
+                    }
+                }
+            }
+        }
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            boolean bIsInMarkovBoundary = (iAttribute == iClass)
+                    || bayesNet.getParentSet(iAttribute).contains(iClass)
+                    || bayesNet.getParentSet(iClass).contains(iAttribute);
+            for (int iAttribute2 = 0; !bIsInMarkovBoundary && iAttribute2 < instances.numAttributes(); iAttribute2++) {
+                bIsInMarkovBoundary =
+                    bayesNet.getParentSet(iAttribute2).contains(iAttribute)
+                        && bayesNet.getParentSet(iAttribute2).contains(iClass);
+            }
+            if (!bIsInMarkovBoundary) {
+                if (ancestors.contains(iAttribute)) {
+                	if (bayesNet.getParentSet(iClass).getCardinalityOfParents() < 1024) {
+                		bayesNet.getParentSet(iClass).addParent(iAttribute, instances);
+                	} else {
+                		// too bad
+                	}
+                } else {
+                    bayesNet.getParentSet(iAttribute).addParent(iClass, instances);
+                }
+            }
+        }
+    } // doMarkovBlanketCorrection
+
+    /**
+     * 
+     * @param bMarkovBlanketClassifier
+     */
+    protected void setMarkovBlanketClassifier(boolean bMarkovBlanketClassifier) {
+        m_bMarkovBlanketClassifier = bMarkovBlanketClassifier;
+    }
+
+    /**
+     * 
+     * @return
+     */
+    protected boolean getMarkovBlanketClassifier() {
+        return m_bMarkovBlanketClassifier;
+    }
+
+    /**
+     * @return a string to describe the MaxNrOfParentsoption.
+     */
+    public String maxNrOfParentsTipText() {
+        return "Set the maximum number of parents a node in the Bayes net can have."
+            + " When initialized as Naive Bayes, setting this parameter to 1 results in"
+            + " a Naive Bayes classifier. When set to 2, a Tree Augmented Bayes Network (TAN)"
+            + " is learned, and when set >2, a Bayes Net Augmented Bayes Network (BAN)"
+            + " is learned. By setting it to a value much larger than the number of nodes"
+            + " in the network (the default of 100000 pretty much guarantees this), no"
+            + " restriction on the number of parents is enforced";
+    } // maxNrOfParentsTipText
+
+    /**
+     * @return a string to describe the InitAsNaiveBayes option.
+     */
+    public String initAsNaiveBayesTipText() {
+        return "When set to true (default), the initial network used for structure learning"
+            + " is a Naive Bayes Network, that is, a network with an arrow from the classifier"
+            + " node to each other node. When set to false, an empty network is used as initial"
+            + " network structure";
+    } // initAsNaiveBayesTipText
+
+    /**
+     * @return a string to describe the MarkovBlanketClassifier option.
+     */
+    protected String markovBlanketClassifierTipText() {
+        return "When set to true (default is false), after a network structure is learned"
+            + " a Markov Blanket correction is applied to the network structure. This ensures"
+            + " that all nodes in the network are part of the Markov blanket of the classifier"
+            + " node.";
+    } // markovBlanketClassifierTipText
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.9 $");
+    }
+} // class SearchAlgorithm
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/CISearchAlgorithm.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/CISearchAlgorithm.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/CISearchAlgorithm.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CISearchAlgorithm.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net.search.ci;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.classifiers.bayes.net.search.local.LocalScoreSearchAlgorithm;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/** 
+ <!-- globalinfo-start -->
+ * The CISearchAlgorithm class supports Bayes net structure search algorithms that are based on conditional independence test (as opposed to for example score based of cross validation based search algorithms).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.7 $
+ */
+public class CISearchAlgorithm 
+	extends LocalScoreSearchAlgorithm {
+  	
+  	/** for serialization */
+  	static final long serialVersionUID = 3165802334119704560L;
+  	
+	BayesNet  m_BayesNet;
+	Instances m_instances;
+	    
+	/**
+	 * Returns a string describing this object
+	 * @return a description of the classifier suitable for
+	 * displaying in the explorer/experimenter gui
+	 */
+	public String globalInfo() {
+	  return 
+	      "The CISearchAlgorithm class supports Bayes net structure "
+	    + "search algorithms that are based on conditional independence "
+	    + "test (as opposed to for example score based of cross validation "
+	    + "based search algorithms).";
+	}
+
+	/** IsConditionalIndependent tests whether two nodes X and Y are independent
+	 *  given a set of variables Z. The test compares the score of the Bayes network
+	 * with and without arrow Y->X where all nodes in Z are parents of X.
+	 * @param iAttributeX - index of attribute representing variable X
+	 * @param iAttributeY - index of attribute representing variable Y
+ 	 * @param iAttributesZ - array of integers representing indices of attributes in set Z
+ 	 * @param nAttributesZ - cardinality of Z
+ 	 * @return true if X and Y conditionally independent given Z 
+	 */
+	protected boolean isConditionalIndependent(
+		int iAttributeX, 
+		int iAttributeY, 
+		int [] iAttributesZ, 
+		int nAttributesZ) {
+		ParentSet oParentSetX = m_BayesNet.getParentSet(iAttributeX);
+		// clear parent set of AttributeX
+		while (oParentSetX.getNrOfParents() > 0) {
+			oParentSetX.deleteLastParent(m_instances);
+		}
+		
+		// insert parents in iAttributeZ
+		for (int iAttributeZ = 0; iAttributeZ < nAttributesZ; iAttributeZ++) {
+			oParentSetX.addParent( iAttributesZ[iAttributeZ], m_instances);
+		}
+		
+		double fScoreZ = calcNodeScore(iAttributeX);
+		double fScoreZY = calcScoreWithExtraParent(iAttributeX, iAttributeY);
+		if (fScoreZY <= fScoreZ) {
+			// the score does not improve by adding Y to the parent set of X
+			// so we conclude that nodes X and Y are conditionally independent
+			// given the set of variables Z
+			return true;
+		}
+		return false;
+	} // IsConditionalIndependent
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.7 $");
+	}
+} // class CISearchAlgorithm
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/ICSSearchAlgorithm.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/ICSSearchAlgorithm.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/ci/ICSSearchAlgorithm.java	(revision 29)
@@ -0,0 +1,671 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ICSSearchAlgorithm.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+
+package weka.classifiers.bayes.net.search.ci;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses conditional independence tests to find a skeleton, finds V-nodes and applies a set of rules to find the directions of the remaining arrows.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -cardinality &lt;num&gt;
+ *  When determining whether an edge exists a search is performed 
+ *  for a set Z that separates the nodes. MaxCardinality determines 
+ *  the maximum size of the set Z. This greatly influences the 
+ *  length of the search. (default 2)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 1.8 $
+ */ 
+public class ICSSearchAlgorithm 
+    extends CISearchAlgorithm {
+
+    /** for serialization */
+    static final long serialVersionUID = -2510985917284798576L;
+  
+    /**
+     * returns the name of the attribute with the given index
+     * 
+     * @param iAttribute the index of the attribute
+     * @return the name of the attribute
+     */
+    String name(int iAttribute) {
+      return m_instances.attribute(iAttribute).name();
+    }
+    
+    /**
+     * returns the number of attributes
+     * 
+     * @return the number of attributes
+     */
+    int maxn() {
+      return m_instances.numAttributes();
+    }
+    
+    /** maximum size of separating set **/
+    private int m_nMaxCardinality = 2; 
+
+    /**
+     * sets the cardinality
+     * 
+     * @param nMaxCardinality the max cardinality
+     */
+    public void setMaxCardinality(int nMaxCardinality) {
+      m_nMaxCardinality = nMaxCardinality;
+    }
+    
+    /**
+     * returns the max cardinality
+     * 
+     * @return the max cardinality
+     */
+    public int getMaxCardinality() {
+      return m_nMaxCardinality;
+    }
+	
+
+	
+    class SeparationSet
+        implements RevisionHandler {
+      
+        public int [] m_set;
+        
+        /**
+         * constructor
+         */
+        public SeparationSet() {
+			m_set= new int [getMaxCardinality() + 1];
+        } // c'tor
+
+        public boolean contains(int nItem) {
+        	for (int iItem = 0; iItem < getMaxCardinality() && m_set[iItem] != -1; iItem++) {
+        		if (m_set[iItem] == nItem) {
+					return true;
+				}
+        	}
+			return false;
+    	} // contains
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 1.8 $");
+        }
+
+    } // class sepset
+
+	/**
+	 * Search for Bayes network structure using ICS algorithm
+	 * @param bayesNet datastructure to build network structure for
+	 * @param instances data set to learn from
+	 * @throws Exception if something goes wrong
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        // init
+        m_BayesNet = bayesNet;
+        m_instances = instances;
+
+        boolean edges[][] = new boolean [maxn() + 1][];
+		boolean [] [] arrows = new boolean [maxn() + 1][];
+        SeparationSet [] [] sepsets = new SeparationSet [maxn() + 1][];
+        for (int iNode = 0 ; iNode < maxn() + 1; iNode++) {
+            edges[iNode] = new boolean[maxn()];
+			arrows[iNode] = new boolean[maxn()]; 
+            sepsets[iNode] = new SeparationSet[maxn()];
+        }
+
+        calcDependencyGraph(edges, sepsets);
+        calcVeeNodes(edges, arrows, sepsets);
+        calcArcDirections(edges, arrows);
+
+		// transfrom into BayesNet datastructure 
+		for (int iNode = 0; iNode < maxn(); iNode++) {
+			// clear parent set of AttributeX
+			ParentSet oParentSet = m_BayesNet.getParentSet(iNode);
+			while (oParentSet.getNrOfParents() > 0) {
+				oParentSet.deleteLastParent(m_instances);
+			}
+			for (int iParent = 0; iParent < maxn(); iParent++) {
+				if (arrows[iParent][iNode]) {
+					oParentSet.addParent(iParent, m_instances);
+				}
+			}
+		}
+	} // search
+	
+	
+	/** CalcDependencyGraph determines the skeleton of the BayesNetwork by
+	 * starting with a complete graph and removing edges (a--b) if it can
+	 * find a set Z such that a and b conditionally independent given Z.
+	 * The set Z is found by trying all possible subsets of nodes adjacent 
+	 * to a and b, first of size 0, then of size 1, etc. up to size 
+	 * m_nMaxCardinality
+	 * @param edges boolean matrix representing the edges
+	 * @param sepsets set of separating sets
+	 */
+	void calcDependencyGraph(boolean[][] edges, SeparationSet[][] sepsets) {
+		/*calc undirected graph a-b iff D(a,S,b) for all S)*/
+		SeparationSet oSepSet;
+
+		for (int iNode1 = 0; iNode1 < maxn(); iNode1++) { 
+			/*start with complete graph*/
+			for (int iNode2 = 0; iNode2 < maxn(); iNode2++) {
+				edges[iNode1][iNode2] = true;
+			}
+		}
+		for (int iNode1 = 0; iNode1 < maxn(); iNode1++) {
+			edges[iNode1][iNode1] = false;
+		}
+
+		for (int iCardinality = 0; iCardinality <= getMaxCardinality(); iCardinality++) {
+			for (int iNode1 = 0; iNode1 <= maxn() - 2; iNode1++) {
+				for (int iNode2 = iNode1 + 1; iNode2 < maxn(); iNode2++) {
+					if (edges[iNode1][iNode2]) {
+						oSepSet = existsSepSet(iNode1, iNode2, iCardinality, edges);
+						if (oSepSet != null) {
+							edges[iNode1][iNode2] = false;
+							edges[iNode2][iNode1] = false;
+							sepsets[iNode1][iNode2] = oSepSet;
+							sepsets[iNode2][iNode1] = oSepSet;
+							// report separating set
+							System.err.print("I(" + name(iNode1) + ", {");
+							for (int iNode3 = 0; iNode3 < iCardinality; iNode3++) {
+								System.err.print(name(oSepSet.m_set[iNode3]) + " ");
+							}
+							System.err.print("} ," + name(iNode2) + ")\n");
+						}
+					}
+				}
+			}
+			// report current state of dependency graph
+			System.err.print(iCardinality + " ");
+			for (int iNode1 = 0; iNode1 < maxn(); iNode1++) {
+				System.err.print(name(iNode1) + " ");
+			}
+			System.err.print('\n');
+			for (int iNode1 = 0; iNode1 < maxn(); iNode1++) {
+				for (int iNode2 = 0; iNode2 < maxn(); iNode2++) {
+					if (edges[iNode1][iNode2])
+						System.err.print("X ");
+					else
+						System.err.print(". ");
+				}
+				System.err.print(name(iNode1) + " ");
+				System.err.print('\n');
+			}
+		}
+	} /*CalcDependencyGraph*/
+
+	/** ExistsSepSet tests if a separating set Z of node a and b exists of given 
+	 * cardiniality exists. 
+	 * The set Z is found by trying all possible subsets of nodes adjacent 
+	 * to both a and b of the requested cardinality.
+	 * @param iNode1 index of first node a
+	 * @param iNode2 index of second node b
+	 * @param nCardinality size of the separating set Z
+	 * @param edges
+	 * @return SeparationSet containing set that separates iNode1 and iNode2 or null if no such set exists
+	 */
+    SeparationSet existsSepSet(int iNode1, int iNode2, int nCardinality, boolean [] [] edges)
+    {
+        /*Test if a separating set of node d and e exists of cardiniality k*/
+//        int iNode1_, iNode2_;
+        int iNode3, iZ;
+		SeparationSet Z = new SeparationSet();
+		Z.m_set[nCardinality] = -1;
+
+//        iNode1_ = iNode1;
+//        iNode2_ = iNode2;
+
+		// find first candidate separating set Z
+        if (nCardinality > 0) {
+            Z.m_set[0] = next(-1, iNode1, iNode2, edges);
+            iNode3 = 1;
+            while (iNode3 < nCardinality) {
+              Z.m_set[iNode3] = next(Z.m_set[iNode3 - 1], iNode1, iNode2, edges);
+              iNode3++;
+            }
+        }
+
+        if (nCardinality > 0) {
+	        iZ = maxn() - Z.m_set[nCardinality - 1] - 1;
+        } else {
+    	    iZ = 0;
+        }
+        
+
+        while (iZ >= 0)
+        {  
+        	//check if candidate separating set makes iNode2_ and iNode1_ independent
+            if (isConditionalIndependent(iNode2, iNode1, Z.m_set, nCardinality))	{
+                return Z;
+            }
+			// calc next candidate separating set
+            if (nCardinality > 0) {
+                Z.m_set[nCardinality - 1] = next(Z.m_set[nCardinality - 1], iNode1, iNode2, edges);
+            }
+            iZ = nCardinality - 1;   
+            while (iZ >= 0 && Z.m_set[iZ] >= maxn()) {
+                iZ = nCardinality - 1;
+                while (iZ >= 0 && Z.m_set[iZ] >= maxn()) {
+                	iZ--;
+                }
+                if (iZ < 0) {
+                    break;
+                }
+                Z.m_set[iZ] = next(Z.m_set[iZ], iNode1, iNode2, edges);
+                for (iNode3 = iZ + 1; iNode3 < nCardinality; iNode3++) {
+                    Z.m_set[iNode3] = next(Z.m_set[iNode3 - 1], iNode1, iNode2, edges);
+                }
+                iZ = nCardinality - 1;
+            }
+        }
+
+        return null;
+    }  /*ExistsSepSet*/
+
+	/** 
+	 * determine index of node that makes next candidate separating set
+	 * adjacent to iNode1 and iNode2, but not iNode2 itself
+	 * @param x index of current node
+	 * @param iNode1 first node
+	 * @param iNode2 second node (must be larger than iNode1)
+	 * @param edges skeleton so far
+	 * @return int index of next node adjacent to iNode1 after x
+	 */
+	int next(int x, int iNode1, int iNode2, boolean [] [] edges)
+	{
+		x++;
+		while (x < maxn() && (!edges[iNode1][x] || !edges[iNode2][x] ||x == iNode2)) {
+			x++;
+		}
+		return x;
+	}  /*next*/
+
+
+	/** CalcVeeNodes tries to find V-nodes, i.e. nodes a,b,c such that
+	 * a->c<-b and a-/-b. These nodes are identified by finding nodes
+	 * a,b,c in the skeleton such that a--c, c--b and a-/-b and furthermore
+	 * c is not in the set Z that separates a and b
+	 * @param edges skeleton
+	 * @param arrows resulting partially directed skeleton after all V-nodes 
+	 * have been identified
+	 * @param sepsets separating sets
+	 */
+	void calcVeeNodes(
+		boolean[][] edges,
+		boolean[][] arrows,
+		SeparationSet[][] sepsets) {
+
+		// start with complete empty graph
+		for (int iNode1 = 0; iNode1 < maxn(); iNode1++) {
+			for (int iNode2 = 0; iNode2 < maxn(); iNode2++) {
+				arrows[iNode1][iNode2] = false;
+			}
+		}
+
+		for (int iNode1 = 0; iNode1 < maxn() - 1; iNode1++) {
+			for (int iNode2 = iNode1 + 1; iNode2 < maxn(); iNode2++) {
+				if (!edges[iNode1][iNode2]) { /*i nonadj j*/
+					for (int iNode3 = 0; iNode3 < maxn(); iNode3++) {
+						if ((iNode3 != iNode1
+							&& iNode3 != iNode2
+							&& edges[iNode1][iNode3]
+							&& edges[iNode2][iNode3])
+							& (!sepsets[iNode1][iNode2].contains(iNode3))) {
+							arrows[iNode1][iNode3] = true; /*add arc i->k*/
+							arrows[iNode2][iNode3] = true; /*add arc j->k*/
+						}
+					}
+				}
+			}
+		}
+	} // CalcVeeNodes
+
+
+	/** CalcArcDirections assigns directions to edges that remain after V-nodes have
+	 * been identified. The arcs are directed using the following rules:
+	   Rule 1: i->j--k & i-/-k => j->k
+	   Rule 2: i->j->k & i--k => i->k
+	   Rule 3  m
+			 /|\
+			i | k  => m->j
+	i->j<-k  \|/
+			  j
+	
+	   Rule 4  m
+			 / \
+			i---k  => i->m & k->m
+	  i->j   \ /
+			  j
+	   Rule 5: if no edges are directed then take a random one (first we can find)
+	 * @param edges skeleton
+	 * @param arrows resulting fully directed DAG
+	 */
+	void calcArcDirections(boolean[][] edges, boolean[][] arrows) {
+		/*give direction to remaining arcs*/
+		int i, j, k, m;
+		boolean bFound;
+
+		do {
+			bFound = false;
+
+			/*Rule 1: i->j--k & i-/-k => j->k*/
+
+			for (i = 0; i < maxn(); i++) {
+				for (j = 0; j < maxn(); j++) {
+					if (i != j && arrows[i][j]) {
+						for (k = 0; k < maxn(); k++) {
+							if (i != k
+								&& j != k
+								&& edges[j][k]
+								&& !edges[i][k]
+								&& !arrows[j][k]
+								&& !arrows[k][j]) {
+								arrows[j][k] = true;
+								bFound = true;
+							}
+						}
+					}
+				}
+			}
+
+			/*Rule 2: i->j->k & i--k => i->k*/
+
+			for (i = 0; i < maxn(); i++) {
+				for (j = 0; j < maxn(); j++) {
+					if (i != j && arrows[i][j]) {
+						for (k = 0; k < maxn(); k++) {
+							if (i != k
+								&& j != k
+								&& edges[i][k]
+								&& arrows[j][k]
+								&& !arrows[i][k]
+								&& !arrows[k][i]) {
+								arrows[i][k] = true;
+								bFound = true;
+							}
+						}
+					}
+				}
+			}
+
+			/* Rule 3  m
+			         /|\
+			        i | k  => m->j
+			i->j<-k  \|/
+			          j
+			*/
+			for (i = 0; i < maxn(); i++) {
+				for (j = 0; j < maxn(); j++) {
+					if (i != j && arrows[i][j]) {
+						for (k = 0; k < maxn(); k++) {
+							if (k != i
+								&& k != j
+								&& arrows[k][j]
+								&& !edges[k][i]) {
+								for (m = 0; m < maxn(); m++) {
+									if (m != i
+										&& m != j
+										&& m != k
+										&& edges[m][i]
+										&& !arrows[m][i]
+										&& !arrows[i][m]
+										&& edges[m][j]
+										&& !arrows[m][j]
+										&& !arrows[j][m]
+										&& edges[m][k]
+										&& !arrows[m][k]
+										&& !arrows[k][m]) {
+										arrows[m][j] = true;
+										bFound = true;
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			/* Rule 4  m
+			         / \
+			        i---k  => i->m & k->m
+			  i->j   \ /
+			          j
+			*/
+			for (i = 0; i < maxn(); i++) {
+				for (j = 0; j < maxn(); j++) {
+					if (i != j && arrows[j][i]) {
+						for (k = 0; k < maxn(); k++) {
+							if (k != i
+								&& k != j
+								&& edges[k][j]
+								&& !arrows[k][j]
+								&& !arrows[j][k]
+								&& edges[k][i]
+								&& !arrows[k][i]
+								&& !arrows[i][k]) {
+								for (m = 0; m < maxn(); m++) {
+									if (m != i
+										&& m != j
+										&& m != k
+										&& edges[m][i]
+										&& !arrows[m][i]
+										&& !arrows[i][m]
+										&& edges[m][k]
+										&& !arrows[m][k]
+										&& !arrows[k][m]) {
+										arrows[i][m] = true;
+										arrows[k][m] = true;
+										bFound = true;
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			/*Rule 5: if no edges are directed then take a random one (first we can find)*/
+
+			if (!bFound) {
+				i = 0;
+				while (!bFound && i < maxn()) {
+					j = 0;
+					while (!bFound && j < maxn()) {
+						if (edges[i][j]
+							&& !arrows[i][j]
+							&& !arrows[j][i]) {
+							arrows[i][j] = true;
+							bFound = true;
+						}
+						j++;
+					}
+					i++;
+				}
+			}
+
+		}
+		while (bFound);
+
+	} // CalcArcDirections
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+	  Vector result = new Vector();
+	  
+	  result.addElement(new Option(
+                "\tWhen determining whether an edge exists a search is performed \n"
+              + "\tfor a set Z that separates the nodes. MaxCardinality determines \n"
+              + "\tthe maximum size of the set Z. This greatly influences the \n"
+              + "\tlength of the search. (default 2)",
+	      "cardinality", 1, "-cardinality <num>"));
+	  
+	  Enumeration en = super.listOptions();
+	  while (en.hasMoreElements())
+	    result.addElement(en.nextElement());
+	  
+	  return result.elements();
+	} // listOption
+	
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -cardinality &lt;num&gt;
+	 *  When determining whether an edge exists a search is performed 
+	 *  for a set Z that separates the nodes. MaxCardinality determines 
+	 *  the maximum size of the set Z. This greatly influences the 
+	 *  length of the search. (default 2)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 * 
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+	  String        tmpStr;
+	  
+	  tmpStr = Utils.getOption("cardinality", options);
+	  if (tmpStr.length() != 0)
+	    setMaxCardinality(Integer.parseInt(tmpStr));
+	  else
+            setMaxCardinality(2);
+            
+          super.setOptions(options);
+	} // setOptions
+	
+	/**
+	 * Gets the current settings of the Classifier.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+	  Vector        result;
+	  String[]      options;
+	  int           i;
+	  
+	  result  = new Vector();
+	  options = super.getOptions();
+	  for (i = 0; i < options.length; i++)
+	    result.add(options[i]);
+	  
+	  result.add("-cardinality");
+	  result.add("" + getMaxCardinality());
+	  
+	  return (String[]) result.toArray(new String[result.size()]);
+	} // getOptions
+	
+
+	/**
+	 * @return a string to describe the MaxCardinality option.
+	 */
+	public String maxCardinalityTipText() {
+	  return "When determining whether an edge exists a search is performed for a set Z "+
+	  "that separates the nodes. MaxCardinality determines the maximum size of the set Z. " +
+	  "This greatly influences the length of the search. Default value is 2.";
+	} // maxCardinalityTipText
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return "This Bayes Network learning algorithm uses conditional independence tests " +
+	  "to find a skeleton, finds V-nodes and applies a set of rules to find the directions " +
+	  "of the remaining arrows.";
+	}
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.8 $");
+	}
+
+	/**
+	 * for testing the class
+	 * 
+	 * @param argv the commandline parameters
+	 */
+	static public void main(String [] argv) {
+		try {
+			BayesNet b = new BayesNet();
+			b.setSearchAlgorithm( new ICSSearchAlgorithm());
+			Instances instances = new Instances(new FileReader("C:\\eclipse\\workspace\\weka\\data\\contact-lenses.arff"));
+			instances.setClassIndex(instances.numAttributes() - 1);
+			b.buildClassifier(instances);
+			System.out.println(b.toString());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	} // main
+
+} // class ICSSearchAlgorithm
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/FromFile.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/FromFile.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/FromFile.java	(revision 29)
@@ -0,0 +1,192 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * FromFile.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search.fixed;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.BIFReader;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.classifiers.bayes.net.search.SearchAlgorithm;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * The FromFile reads the structure of a Bayes net from a file in BIFF format.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;BIF File&gt;
+ *  Name of file containing network structure in BIF format
+ * </pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 1.8 $
+ */
+public class FromFile 
+	extends SearchAlgorithm {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = 7334358169507619525L;
+  
+	/** name of file to read structure from **/
+	String m_sBIFFile = "";
+	    
+	/**
+	 * Returns a string describing this object
+	 * @return a description of the classifier suitable for
+	 * displaying in the explorer/experimenter gui
+	 */
+	public String globalInfo() {
+	  return 
+	      "The FromFile reads the structure of a Bayes net from a file "
+	    + "in BIFF format.";
+	}
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param instances the instances to work with
+	 * @throws Exception if attribute from BIF file could not be found
+	 */
+	public void buildStructure (BayesNet bayesNet, Instances instances) throws Exception {
+		// read network structure in BIF format
+		BIFReader bifReader = new BIFReader();
+		bifReader.processFile(m_sBIFFile);
+		// copy parent sets
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            int iBIFAttribute = bifReader.getNode(bayesNet.getNodeName(iAttribute));
+            ParentSet bifParentSet = bifReader.getParentSet(iBIFAttribute);
+        	for (int iBIFParent = 0; iBIFParent < bifParentSet.getNrOfParents(); iBIFParent++) {
+        	    String sParent = bifReader.getNodeName(bifParentSet.getParent(iBIFParent));
+        	    int iParent = 0;
+        	    while (iParent < instances.numAttributes() && !bayesNet.getNodeName(iParent).equals(sParent)) {
+        	        iParent++;
+        	    }
+        	    if (iParent >= instances.numAttributes()) {
+        	        throw new Exception("Could not find attribute " + sParent + " from BIF file in data");
+        	    }
+        		bayesNet.getParentSet(iAttribute).addParent(iParent, instances);
+        	}
+        }
+	} // buildStructure
+
+    /**
+     * Set name of network in BIF file to read structure from
+     * 
+     * @param sBIFFile the name of the BIF file
+     */
+    public void setBIFFile(String sBIFFile) {
+    	m_sBIFFile = sBIFFile;
+    }
+
+    /**
+     * Get name of network in BIF file to read structure from
+     * @return BIF file name
+     */
+    public String getBIFFile() {
+        return m_sBIFFile;
+    }
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+	  Vector newVector = new Vector();
+
+	  newVector.addElement(new Option("\tName of file containing network structure in BIF format\n", 
+					 "B", 1, "-B <BIF File>"));
+
+          Enumeration en = super.listOptions();
+          while (en.hasMoreElements())
+            newVector.addElement(en.nextElement());
+          
+	  return newVector.elements();
+	}
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -B &lt;BIF File&gt;
+	 *  Name of file containing network structure in BIF format
+	 * </pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+	  setBIFFile( Utils.getOption('B', options));
+          
+          super.setOptions(options);
+	}
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String [] getOptions() {
+          String[] superOptions = super.getOptions();
+	  String [] options  = new String [2 + superOptions.length];
+	  int current = 0;
+
+          options[current++] = "-B";
+	  options[current++] = "" + getBIFFile();
+
+          // insert options from parent class
+          for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                  options[current++] = superOptions[iOption];
+          }
+
+	  // Fill up rest with empty strings, not nulls!
+	  while (current < options.length) {
+		options[current++] = "";
+	  }
+	  return options;
+	}
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.8 $");
+	}
+
+} // class FromFile
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/NaiveBayes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/NaiveBayes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/fixed/NaiveBayes.java	(revision 29)
@@ -0,0 +1,82 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NaiveBayes.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search.fixed;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.search.SearchAlgorithm;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/** 
+ <!-- globalinfo-start -->
+ * The NaiveBayes class generates a fixed Bayes network structure with arrows from the class variable to each of the attribute variables.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 1.6 $
+ */
+public class NaiveBayes 
+	extends SearchAlgorithm {
+
+  	/** for serialization */
+  	static final long serialVersionUID = -4808572519709755811L;
+  	    
+  	/**
+  	 * Returns a string describing this object
+  	 * @return a description of the classifier suitable for
+  	 * displaying in the explorer/experimenter gui
+  	 */
+  	public String globalInfo() {
+  	  return 
+  	      "The NaiveBayes class generates a fixed Bayes network structure "
+  	    + "with arrows from the class variable to each of the attribute "
+  	    + "variables.";
+  	}
+	
+  	/**
+  	 * 
+  	 * @param bayesNet
+  	 * @param instances the instances to work with
+  	 * @throws Exception if something goes wrong
+  	 */
+	public void buildStructure (BayesNet bayesNet, Instances instances) throws Exception {
+	  for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+	    if (iAttribute != instances.classIndex()) {
+	      bayesNet.getParentSet(iAttribute).addParent(instances.classIndex(), instances);
+	    }
+	  }
+	} // buildStructure
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.6 $");
+	}
+	
+} // class NaiveBayes
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GeneticSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GeneticSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GeneticSearch.java	(revision 29)
@@ -0,0 +1,746 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GeneticSearch.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses genetic search for finding a well scoring Bayes network structure. Genetic search works by having a population of Bayes network structures and allow them to mutate and apply cross over to get offspring. The best network structure found during the process is returned.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;integer&gt;
+ *  Population size</pre>
+ * 
+ * <pre> -A &lt;integer&gt;
+ *  Descendant population size</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -M
+ *  Use mutation.
+ *  (default true)</pre>
+ * 
+ * <pre> -C
+ *  Use cross-over.
+ *  (default true)</pre>
+ * 
+ * <pre> -O
+ *  Use tournament selection (true) or maximum subpopulatin (false).
+ *  (default false)</pre>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.5 $
+ */
+public class GeneticSearch 
+    extends GlobalScoreSearchAlgorithm {
+
+    /** for serialization */
+    static final long serialVersionUID = 4236165533882462203L;
+  
+    /** number of runs **/
+    int m_nRuns = 10;
+
+	/** size of population **/
+	int m_nPopulationSize = 10;
+
+	/** size of descendant population **/
+	int m_nDescendantPopulationSize = 100;
+
+	/** use cross-over? **/
+	boolean m_bUseCrossOver = true;
+
+	/** use mutation? **/
+	boolean m_bUseMutation = true;
+	
+	/** use tournament selection or take best sub-population **/
+	boolean m_bUseTournamentSelection = false;	
+	
+	/** random number seed **/
+	int m_nSeed = 1;
+	
+	/** random number generator **/
+	Random m_random = null;
+
+
+	/** used in BayesNetRepresentation for efficiently determining
+	 * whether a number is square  
+	 */
+	static boolean [] g_bIsSquare;
+	
+	class BayesNetRepresentation implements RevisionHandler {
+		/** number of nodes in network **/		
+		int m_nNodes = 0;
+
+		/** bit representation of parent sets 
+		 * m_bits[iTail + iHead * m_nNodes] represents arc iTail->iHead
+		 */
+		boolean [] m_bits;
+		
+		/** score of represented network structure **/
+		double m_fScore = 0.0f;
+		
+		/** 
+		 * return score of represented network structure
+		 * 
+		 * @return the score
+		 */
+		public double getScore() {
+			return m_fScore;
+		} // getScore
+
+		/** 
+		 * c'tor 
+		 * 
+		 * @param nNodes the number of nodes
+		 */
+		BayesNetRepresentation (int nNodes) {
+			m_nNodes = nNodes;
+		} // c'tor
+		
+		/** initialize with a random structure by randomly placing
+		 * m_nNodes arcs.
+		 */
+		public void randomInit() {
+			do {
+				m_bits = new boolean [m_nNodes * m_nNodes];
+				for (int i = 0; i < m_nNodes; i++) {
+					int iPos;
+					do {
+						iPos = m_random.nextInt(m_nNodes * m_nNodes);
+					} while (isSquare(iPos));
+					m_bits[iPos] = true;
+				}
+			} while (hasCycles());
+			calcGlobalScore();
+		}
+
+		/** calculate score of current network representation
+		 * As a side effect, the parent sets are set
+		 */
+		void calcGlobalScore() {
+			// clear current network
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+				ParentSet parentSet = m_BayesNet.getParentSet(iNode);
+				while (parentSet.getNrOfParents() > 0) {
+					parentSet.deleteLastParent(m_BayesNet.m_Instances);
+				}
+			}
+			// insert arrows
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+				ParentSet parentSet = m_BayesNet.getParentSet(iNode);
+				for (int iNode2 = 0; iNode2 < m_nNodes; iNode2++) {
+					if (m_bits[iNode2 + iNode * m_nNodes]) {
+						parentSet.addParent(iNode2, m_BayesNet.m_Instances);
+					}
+				}
+			}
+			// calc score
+			try {
+				m_fScore = calcScore(m_BayesNet);
+			} catch (Exception e) {
+				// ignore
+			}
+		} // calcScore
+
+		/** check whether there are cycles in the network
+ 		* 
+ 		* @return true if a cycle is found, false otherwise
+		 */
+		public boolean hasCycles() {
+			// check for cycles
+			boolean[] bDone = new boolean[m_nNodes];
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+
+				// find a node for which all parents are 'done'
+				boolean bFound = false;
+
+				for (int iNode2 = 0; !bFound && iNode2 < m_nNodes; iNode2++) {
+					if (!bDone[iNode2]) {
+						boolean bHasNoParents = true;
+						for (int iParent = 0; iParent < m_nNodes; iParent++) {
+							if (m_bits[iParent + iNode2 * m_nNodes] && !bDone[iParent]) {
+								bHasNoParents = false;
+							}
+						}
+						if (bHasNoParents) {
+							bDone[iNode2] = true;
+							bFound = true;
+						}
+					}
+				}
+				if (!bFound) {
+					return true;
+				}
+			}
+			return false;
+		} // hasCycles
+
+		/** create clone of current object 
+		 * @return cloned object
+		 */
+		BayesNetRepresentation copy() {
+			BayesNetRepresentation b = new BayesNetRepresentation(m_nNodes);
+			b.m_bits = new boolean [m_bits.length];
+			for (int i = 0; i < m_nNodes * m_nNodes; i++) {
+				b.m_bits[i] = m_bits[i];
+			}
+			b.m_fScore = m_fScore;
+			return b;		
+		} // copy
+
+		/** Apply mutation operation to BayesNet
+		 * Calculate score and as a side effect sets BayesNet parent sets.
+		 */
+		void mutate() {
+			// flip a bit
+			do {				
+				int iBit;
+				do {
+					iBit = m_random.nextInt(m_nNodes * m_nNodes);
+				} while (isSquare(iBit));
+				
+				m_bits[iBit] = !m_bits[iBit];
+			} while (hasCycles());
+
+			calcGlobalScore();
+		} // mutate
+
+		/** Apply cross-over operation to BayesNet 
+		 * Calculate score and as a side effect sets BayesNet parent sets.
+		 * @param other BayesNetRepresentation to cross over with
+		 */
+		void crossOver(BayesNetRepresentation other) {
+			boolean [] bits = new boolean [m_bits.length];
+			for (int i = 0; i < m_bits.length; i++) {
+				bits[i] = m_bits[i];
+			}
+			int iCrossOverPoint = m_bits.length;
+			do {
+				// restore to original state
+				for (int i = iCrossOverPoint; i < m_bits.length; i++) {
+					m_bits[i] = bits[i];
+				}
+				// take all bits from cross-over points onwards
+				iCrossOverPoint = m_random.nextInt(m_bits.length);
+				for (int i = iCrossOverPoint; i < m_bits.length; i++) {
+					m_bits[i] = other.m_bits[i];
+				}
+			} while (hasCycles());
+			calcGlobalScore();
+		} // crossOver
+				
+		/** check if number is square and initialize g_bIsSquare structure
+		 * if necessary
+		 * @param nNum number to check (should be below m_nNodes * m_nNodes)
+		 * @return true if number is square
+		 */
+		boolean isSquare(int nNum) {
+			if (g_bIsSquare == null || g_bIsSquare.length < nNum) {
+				g_bIsSquare = new boolean [m_nNodes * m_nNodes];
+				for (int i = 0; i < m_nNodes; i++) {
+					g_bIsSquare[i * m_nNodes + i] = true;
+				}
+			}
+			return g_bIsSquare[nNum];
+		} // isSquare
+
+		/**
+		 * Returns the revision string.
+		 * 
+		 * @return		the revision
+		 */
+		public String getRevision() {
+		  return RevisionUtils.extract("$Revision: 1.5 $");
+		}
+	} // class BayesNetRepresentation 
+	    	
+	/**
+	 * search determines the network structure/graph of the network
+	 * with a genetic search algorithm.
+	 * 
+	 * @param bayesNet the network to search
+	 * @param instances the instances to use
+	 * @throws Exception if population size doesn fit or neither cross-over or mutation was chosen
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+		// sanity check
+		if (getDescendantPopulationSize() < getPopulationSize()) {
+			throw new Exception ("Descendant PopulationSize should be at least Population Size");
+		}
+		if (!getUseCrossOver() && !getUseMutation()) {
+			throw new Exception ("At least one of mutation or cross-over should be used");
+		}
+		
+		m_random = new Random(m_nSeed);
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+		// keeps track of score pf best structure found so far 
+		double fBestScore = calcScore(bayesNet);	
+
+		// initialize bestBayesNet
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // initialize population        
+		BayesNetRepresentation  [] population = new BayesNetRepresentation [getPopulationSize()];
+        for (int i = 0; i < getPopulationSize(); i++) {
+        	population[i] = new BayesNetRepresentation (instances.numAttributes());
+			population[i].randomInit();
+			if (population[i].getScore() > fBestScore) {
+				copyParentSets(bestBayesNet, bayesNet);
+				fBestScore = population[i].getScore();
+				
+			}
+        }
+        
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+        	// create descendants
+			BayesNetRepresentation  [] descendantPopulation = new BayesNetRepresentation  [getDescendantPopulationSize()];
+			for (int i = 0; i < getDescendantPopulationSize(); i++) {
+				descendantPopulation[i] = population[m_random.nextInt(getPopulationSize())].copy();
+				if (getUseMutation()) {
+					if (getUseCrossOver() && m_random.nextBoolean()) {
+						descendantPopulation[i].crossOver(population[m_random.nextInt(getPopulationSize())]);						
+					} else {
+						descendantPopulation[i].mutate();								
+					}
+				} else {
+					// use crossover
+					descendantPopulation[i].crossOver(population[m_random.nextInt(getPopulationSize())]);
+				}
+
+				if (descendantPopulation[i].getScore() > fBestScore) {
+					copyParentSets(bestBayesNet, bayesNet);
+					fBestScore = descendantPopulation[i].getScore();
+				}
+			}
+			// select new population
+			boolean [] bSelected = new boolean [getDescendantPopulationSize()];
+			for (int i = 0; i < getPopulationSize(); i++) {
+				int iSelected = 0;
+				if (m_bUseTournamentSelection) {
+					// use tournament selection
+					iSelected = m_random.nextInt(getDescendantPopulationSize());
+					while (bSelected[iSelected]) {
+						iSelected = (iSelected + 1) % getDescendantPopulationSize();
+					}
+					int iSelected2 =  m_random.nextInt(getDescendantPopulationSize());
+					while (bSelected[iSelected2]) {
+						iSelected2 = (iSelected2 + 1) % getDescendantPopulationSize();
+					}
+					if (descendantPopulation[iSelected2].getScore() > descendantPopulation[iSelected].getScore()) {
+						iSelected = iSelected2;
+					}
+				} else {
+					// find best scoring network in population
+					while (bSelected[iSelected]) {
+						iSelected++;
+					}
+					double fScore = descendantPopulation[iSelected].getScore();
+					for (int j = 0; j < getDescendantPopulationSize(); j++) {
+						if (!bSelected[j] && descendantPopulation[j].getScore() > fScore) {
+							fScore = descendantPopulation[j].getScore();
+							iSelected = j;
+						}
+					}
+				}
+				population[i] = descendantPopulation[iSelected];
+				bSelected[iSelected] = true;
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+    } // search
+
+
+	/** copyParentSets copies parent sets of source to dest BayesNet
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+    /**
+    * @return number of runs
+    */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(7);
+
+		newVector.addElement(new Option("\tPopulation size", "L", 1, "-L <integer>"));
+		newVector.addElement(new Option("\tDescendant population size", "A", 1, "-A <integer>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tUse mutation.\n\t(default true)", "M", 0, "-M"));
+		newVector.addElement(new Option("\tUse cross-over.\n\t(default true)", "C", 0, "-C"));
+		newVector.addElement(new Option("\tUse tournament selection (true) or maximum subpopulatin (false).\n\t(default false)", "O", 0, "-O"));
+		newVector.addElement(new Option("\tRandom number seed", "R", 1, "-R <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -L &lt;integer&gt;
+	 *  Population size</pre>
+	 * 
+	 * <pre> -A &lt;integer&gt;
+	 *  Descendant population size</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -M
+	 *  Use mutation.
+	 *  (default true)</pre>
+	 * 
+	 * <pre> -C
+	 *  Use cross-over.
+	 *  (default true)</pre>
+	 * 
+	 * <pre> -O
+	 *  Use tournament selection (true) or maximum subpopulatin (false).
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -R &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sPopulationSize = Utils.getOption('L', options);
+		if (sPopulationSize.length() != 0) {
+			setPopulationSize(Integer.parseInt(sPopulationSize));
+		}
+		String sDescendantPopulationSize = Utils.getOption('A', options);
+		if (sDescendantPopulationSize.length() != 0) {
+			setDescendantPopulationSize(Integer.parseInt(sDescendantPopulationSize));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		String sSeed = Utils.getOption('R', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+		setUseMutation(Utils.getFlag('M', options));
+		setUseCrossOver(Utils.getFlag('C', options));
+		setUseTournamentSelection(Utils.getFlag('O', options));
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[11 + superOptions.length];
+		int current = 0;
+		
+		options[current++] = "-L";
+		options[current++] = "" + getPopulationSize();
+
+		options[current++] = "-A";
+		options[current++] = "" + getDescendantPopulationSize();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-R";
+		options[current++] = "" + getSeed();
+
+		if (getUseMutation()) {
+		  options[current++] = "-M";
+		}
+		if (getUseCrossOver()) {
+		  options[current++] = "-C";
+		}
+		if (getUseTournamentSelection()) {
+		  options[current++] = "-O";
+		}
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * @return whether cross-over is used
+	 */
+	public boolean getUseCrossOver() {
+		return m_bUseCrossOver;
+	}
+
+	/**
+	 * @return whether mutation is used
+	 */
+	public boolean getUseMutation() {
+		return m_bUseMutation;
+	}
+
+	/**
+	 * @return descendant population size
+	 */
+	public int getDescendantPopulationSize() {
+		return m_nDescendantPopulationSize;
+	}
+
+	/**
+	 * @return population size
+	 */
+	public int getPopulationSize() {
+		return m_nPopulationSize;
+	}
+
+	/**
+	 * @param bUseCrossOver sets whether cross-over is used
+	 */
+	public void setUseCrossOver(boolean bUseCrossOver) {
+		m_bUseCrossOver = bUseCrossOver;
+	}
+
+	/**
+	 * @param bUseMutation sets whether mutation is used
+	 */
+	public void setUseMutation(boolean bUseMutation) {
+		m_bUseMutation = bUseMutation;
+	}
+
+	/**
+	 * @return whether Tournament Selection (true) or Maximum Sub-Population (false) should be used
+	 */
+	public boolean getUseTournamentSelection() {
+		return m_bUseTournamentSelection;
+	}
+
+	/**
+	 * @param bUseTournamentSelection sets whether Tournament Selection or Maximum Sub-Population should be used
+	 */
+	public void setUseTournamentSelection(boolean bUseTournamentSelection) {
+		m_bUseTournamentSelection = bUseTournamentSelection;
+	}
+
+	/**
+	 * @param iDescendantPopulationSize sets descendant population size
+	 */
+	public void setDescendantPopulationSize(int iDescendantPopulationSize) {
+		m_nDescendantPopulationSize = iDescendantPopulationSize;
+	}
+
+	/**
+	 * @param iPopulationSize sets population size
+	 */
+	public void setPopulationSize(int iPopulationSize) {
+		m_nPopulationSize = iPopulationSize;
+	}
+
+	/**
+	* @return random number seed
+	*/
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm uses genetic search for finding a well scoring " +
+		"Bayes network structure. Genetic search works by having a population of Bayes network structures " +
+		"and allow them to mutate and apply cross over to get offspring. The best network structure " +
+		"found during the process is returned.";
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of generations of Bayes network structure populations.";
+	} // runsTipText
+	
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * @return a string to describe the Population Size option.
+	 */
+	public String populationSizeTipText() {
+	  return "Sets the size of the population of network structures that is selected each generation.";
+	} // populationSizeTipText
+
+	/**
+	 * @return a string to describe the Descendant Population Size option.
+	 */
+	public String descendantPopulationSizeTipText() {
+	  return "Sets the size of the population of descendants that is created each generation.";
+	} // descendantPopulationSizeTipText
+
+	/**
+	 * @return a string to describe the Use Mutation option.
+	 */
+	public String useMutationTipText() {
+		return "Determines whether mutation is allowed. Mutation flips a bit in the bit " +
+			"representation of the network structure. At least one of mutation or cross-over " +
+			"should be used.";
+	} // useMutationTipText
+
+	/**
+	 * @return a string to describe the Use Cross-Over option.
+	 */
+	public String useCrossOverTipText() {
+		return "Determines whether cross-over is allowed. Cross over combined the bit " +
+			"representations of network structure by taking a random first k bits of one" +
+			"and adding the remainder of the other. At least one of mutation or cross-over " +
+			"should be used.";
+	} // useCrossOverTipText
+
+	/**
+	 * @return a string to describe the Use Tournament Selection option.
+	 */
+	public String useTournamentSelectionTipText() {
+		return "Determines the method of selecting a population. When set to true, tournament " +
+			"selection is used (pick two at random and the highest is allowed to continue). " +
+			"When set to false, the top scoring network structures are selected.";
+	} // useTournamentSelectionTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.5 $");
+	}
+} // GeneticSearch
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GlobalScoreSearchAlgorithm.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GlobalScoreSearchAlgorithm.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/GlobalScoreSearchAlgorithm.java	(revision 29)
@@ -0,0 +1,543 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GlobalScoreSearchAlgorithm.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.classifiers.bayes.net.search.SearchAlgorithm;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses cross validation to estimate classification accuracy.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 1.10 $
+ */
+public class GlobalScoreSearchAlgorithm 
+	extends SearchAlgorithm {
+
+  	/** for serialization */
+  	static final long serialVersionUID = 7341389867906199781L;
+	
+	/** points to Bayes network for which a structure is searched for **/
+	BayesNet m_BayesNet;
+	
+	/** toggle between scoring using accuracy = 0-1 loss (when false) or class probabilities (when true) **/
+	boolean m_bUseProb = true;
+	
+	/** number of folds for k-fold cross validation **/
+	int m_nNrOfFolds = 10;
+
+	/** constant for score type: LOO-CV */
+	final static int LOOCV = 0;
+	/** constant for score type: k-fold-CV */
+	final static int KFOLDCV = 1;
+	/** constant for score type: Cumulative-CV */
+	final static int CUMCV = 2;
+
+	/** the score types **/
+	public static final Tag[] TAGS_CV_TYPE =
+		{
+			new Tag(LOOCV, "LOO-CV"),
+			new Tag(KFOLDCV, "k-Fold-CV"),
+			new Tag(CUMCV, "Cumulative-CV")
+		};
+	/**
+	 * Holds the cross validation strategy used to measure quality of network
+	 */
+	int m_nCVType = LOOCV;
+
+	/**
+	 * performCV returns the accuracy calculated using cross validation.  
+	 * The dataset used is m_Instances associated with the Bayes Network.
+	 * 
+	 * @param bayesNet : Bayes Network containing structure to evaluate
+	 * @return accuracy (in interval 0..1) measured using cv.
+	 * @throws Exception whn m_nCVType is invalided + exceptions passed on by updateClassifier
+	 */
+	public double calcScore(BayesNet bayesNet) throws Exception {
+		switch (m_nCVType) {
+			case LOOCV: 
+				return leaveOneOutCV(bayesNet);
+			case CUMCV: 
+				return cumulativeCV(bayesNet);
+			case KFOLDCV: 
+				return kFoldCV(bayesNet, m_nNrOfFolds);
+			default:
+				throw new Exception("Unrecognized cross validation type encountered: " + m_nCVType);
+		}
+	} // calcScore
+
+	/**
+	 * Calc Node Score With Added Parent
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @param nCandidateParent candidate parent to add to the existing parent set
+	 * @return log score
+	 * @throws Exception if something goes wrong
+	 */
+	public double calcScoreWithExtraParent(int nNode, int nCandidateParent) throws Exception {
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+		Instances instances = m_BayesNet.m_Instances;
+
+		// sanity check: nCandidateParent should not be in parent set already
+		for (int iParent = 0; iParent < oParentSet.getNrOfParents(); iParent++) {
+			if (oParentSet.getParent(iParent) == nCandidateParent) {
+				return -1e100;
+			}
+		}
+
+		// set up candidate parent
+		oParentSet.addParent(nCandidateParent, instances);
+
+		// calculate the score
+		double fAccuracy = calcScore(m_BayesNet);
+
+		// delete temporarily added parent
+		oParentSet.deleteLastParent(instances);
+
+		return fAccuracy;
+	} // calcScoreWithExtraParent
+
+
+	/**
+	 * Calc Node Score With Parent Deleted
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @param nCandidateParent candidate parent to delete from the existing parent set
+	 * @return log score
+	 * @throws Exception if something goes wrong
+	 */
+	public double calcScoreWithMissingParent(int nNode, int nCandidateParent) throws Exception {
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+		Instances instances = m_BayesNet.m_Instances;
+
+		// sanity check: nCandidateParent should be in parent set already
+		if (!oParentSet.contains( nCandidateParent)) {
+				return -1e100;
+		}
+
+		// set up candidate parent
+		int iParent = oParentSet.deleteParent(nCandidateParent, instances);
+
+		// calculate the score
+		double fAccuracy = calcScore(m_BayesNet);
+
+		// reinsert temporarily deleted parent
+		oParentSet.addParent(nCandidateParent, iParent, instances);
+
+		return fAccuracy;
+	} // calcScoreWithMissingParent
+
+	/**
+	 * Calc Node Score With Arrow reversed
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @param nCandidateParent candidate parent to delete from the existing parent set
+	 * @return log score
+	 * @throws Exception if something goes wrong
+	 */
+	public double calcScoreWithReversedParent(int nNode, int nCandidateParent) throws Exception {
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+		ParentSet oParentSet2 = m_BayesNet.getParentSet(nCandidateParent);
+		Instances instances = m_BayesNet.m_Instances;
+
+		// sanity check: nCandidateParent should be in parent set already
+		if (!oParentSet.contains( nCandidateParent)) {
+				return -1e100;
+		}
+
+		// set up candidate parent
+		int iParent = oParentSet.deleteParent(nCandidateParent, instances);
+		oParentSet2.addParent(nNode, instances);
+
+		// calculate the score
+		double fAccuracy = calcScore(m_BayesNet);
+
+		// restate temporarily reversed arrow
+		oParentSet2.deleteLastParent(instances);
+		oParentSet.addParent(nCandidateParent, iParent, instances);
+
+		return fAccuracy;
+	} // calcScoreWithReversedParent
+
+	/**
+	 * LeaveOneOutCV returns the accuracy calculated using Leave One Out
+	 * cross validation. The dataset used is m_Instances associated with
+	 * the Bayes Network.
+	 * @param bayesNet : Bayes Network containing structure to evaluate
+	 * @return accuracy (in interval 0..1) measured using leave one out cv.
+	 * @throws Exception passed on by updateClassifier
+	 */
+	public double leaveOneOutCV(BayesNet bayesNet) throws Exception {
+		m_BayesNet = bayesNet;
+		double fAccuracy = 0.0;
+		double fWeight = 0.0;
+		Instances instances = bayesNet.m_Instances;
+		bayesNet.estimateCPTs();
+		for (int iInstance = 0; iInstance < instances.numInstances(); iInstance++) {
+			Instance instance = instances.instance(iInstance);
+			instance.setWeight(-instance.weight());
+			bayesNet.updateClassifier(instance);
+			fAccuracy += accuracyIncrease(instance);
+			fWeight += instance.weight();
+			instance.setWeight(-instance.weight());
+			bayesNet.updateClassifier(instance);
+		}
+		return fAccuracy / fWeight;
+	} // LeaveOneOutCV
+
+	/**
+	 * CumulativeCV returns the accuracy calculated using cumulative
+	 * cross validation. The idea is to run through the data set and
+	 * try to classify each of the instances based on the previously
+	 * seen data.
+	 * The data set used is m_Instances associated with the Bayes Network.
+	 * @param bayesNet : Bayes Network containing structure to evaluate
+	 * @return accuracy (in interval 0..1) measured using leave one out cv.
+	 * @throws Exception passed on by updateClassifier
+	 */
+	public double cumulativeCV(BayesNet bayesNet) throws Exception {
+		m_BayesNet = bayesNet;
+		double fAccuracy = 0.0;
+		double fWeight = 0.0;
+		Instances instances = bayesNet.m_Instances;
+		bayesNet.initCPTs();
+		for (int iInstance = 0; iInstance < instances.numInstances(); iInstance++) {
+			Instance instance = instances.instance(iInstance);
+			fAccuracy += accuracyIncrease(instance);
+			bayesNet.updateClassifier(instance);
+			fWeight += instance.weight();
+		}
+		return fAccuracy / fWeight;
+	} // LeaveOneOutCV
+	
+	/**
+	 * kFoldCV uses k-fold cross validation to measure the accuracy of a Bayes
+	 * network classifier.
+	 * @param bayesNet : Bayes Network containing structure to evaluate
+	 * @param nNrOfFolds : the number of folds k to perform k-fold cv
+	 * @return accuracy (in interval 0..1) measured using leave one out cv.
+	 * @throws Exception passed on by updateClassifier
+	 */
+	public double kFoldCV(BayesNet bayesNet, int nNrOfFolds) throws Exception {
+		m_BayesNet = bayesNet;
+		double fAccuracy = 0.0;
+		double fWeight = 0.0;
+		Instances instances = bayesNet.m_Instances;
+		// estimate CPTs based on complete data set
+		bayesNet.estimateCPTs();
+		int nFoldStart = 0;
+		int nFoldEnd = instances.numInstances() / nNrOfFolds;
+		int iFold = 1;
+		while (nFoldStart < instances.numInstances()) {
+			// remove influence of fold iFold from the probability distribution
+			for (int iInstance = nFoldStart; iInstance < nFoldEnd; iInstance++) {
+				Instance instance = instances.instance(iInstance);
+				instance.setWeight(-instance.weight());
+				bayesNet.updateClassifier(instance);
+			}
+			
+			// measure accuracy on fold iFold
+			for (int iInstance = nFoldStart; iInstance < nFoldEnd; iInstance++) {
+				Instance instance = instances.instance(iInstance);
+				instance.setWeight(-instance.weight());
+				fAccuracy += accuracyIncrease(instance);
+				instance.setWeight(-instance.weight());
+				fWeight += instance.weight();
+			}
+
+			// restore influence of fold iFold from the probability distribution
+			for (int iInstance = nFoldStart; iInstance < nFoldEnd; iInstance++) {
+				Instance instance = instances.instance(iInstance);
+				instance.setWeight(-instance.weight());
+				bayesNet.updateClassifier(instance);
+			}
+
+			// go to next fold
+			nFoldStart = nFoldEnd;
+			iFold++;
+			nFoldEnd = iFold * instances.numInstances() / nNrOfFolds;
+		}
+		return fAccuracy / fWeight;
+	} // kFoldCV
+	
+	/** accuracyIncrease determines how much the accuracy estimate should
+	 * be increased due to the contribution of a single given instance. 
+	 * 
+	 * @param instance : instance for which to calculate the accuracy increase.
+	 * @return increase in accuracy due to given instance. 
+	 * @throws Exception passed on by distributionForInstance and classifyInstance
+	 */
+	double accuracyIncrease(Instance instance) throws Exception {
+		if (m_bUseProb) {
+			double [] fProb = m_BayesNet.distributionForInstance(instance);
+			return fProb[(int) instance.classValue()] * instance.weight();
+		} else {
+			if (m_BayesNet.classifyInstance(instance) == instance.classValue()) {
+				return instance.weight();
+			}
+		}
+		return 0;
+	} // accuracyIncrease
+
+	/**
+	 * @return use probabilities or not in accuracy estimate
+	 */
+	public boolean getUseProb() {
+		return m_bUseProb;
+	} // getUseProb
+
+	/**
+	 * @param useProb : use probabilities or not in accuracy estimate
+	 */
+	public void setUseProb(boolean useProb) {
+		m_bUseProb = useProb;
+	} // setUseProb
+	
+	/**
+	 * set cross validation strategy to be used in searching for networks.
+	 * @param newCVType : cross validation strategy 
+	 */
+	public void setCVType(SelectedTag newCVType) {
+		if (newCVType.getTags() == TAGS_CV_TYPE) {
+			m_nCVType = newCVType.getSelectedTag().getID();
+		}
+	} // setCVType
+
+	/**
+	 * get cross validation strategy to be used in searching for networks.
+	 * @return cross validation strategy 
+	 */
+	public SelectedTag getCVType() {
+		return new SelectedTag(m_nCVType, TAGS_CV_TYPE);
+	} // getCVType
+
+	/**
+	 * 
+	 * @param bMarkovBlanketClassifier
+	 */
+	public void setMarkovBlanketClassifier(boolean bMarkovBlanketClassifier) {
+	  super.setMarkovBlanketClassifier(bMarkovBlanketClassifier);
+	}
+
+	/**
+	 * 
+	 * @return
+	 */
+	public boolean getMarkovBlanketClassifier() {
+	  return super.getMarkovBlanketClassifier();
+	}
+
+	/**
+	 * Returns an enumeration describing the available options
+	 * 
+	 * @return an enumeration of all the available options
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector();
+
+		newVector.addElement(new Option(
+		    "\tApplies a Markov Blanket correction to the network structure, \n"
+		    + "\tafter a network structure is learned. This ensures that all \n"
+		    + "\tnodes in the network are part of the Markov blanket of the \n"
+		    + "\tclassifier node.",
+		    "mbc", 0, "-mbc"));
+      
+		newVector.addElement(
+			new Option(
+				"\tScore type (LOO-CV,k-Fold-CV,Cumulative-CV)",
+				"S",
+				1,
+				"-S [LOO-CV|k-Fold-CV|Cumulative-CV]"));
+
+		newVector.addElement(new Option("\tUse probabilistic or 0/1 scoring.\n\t(default probabilistic scoring)", "Q", 0, "-Q"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+
+	  	setMarkovBlanketClassifier(Utils.getFlag("mbc", options));
+
+		String sScore = Utils.getOption('S', options);
+
+		if (sScore.compareTo("LOO-CV") == 0) {
+			setCVType(new SelectedTag(LOOCV, TAGS_CV_TYPE));
+		}
+		if (sScore.compareTo("k-Fold-CV") == 0) {
+			setCVType(new SelectedTag(KFOLDCV, TAGS_CV_TYPE));
+		}
+		if (sScore.compareTo("Cumulative-CV") == 0) {
+			setCVType(new SelectedTag(CUMCV, TAGS_CV_TYPE));
+		}
+		setUseProb(!Utils.getFlag('Q', options));		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[4 + superOptions.length];
+		int current = 0;
+
+		if (getMarkovBlanketClassifier())
+		  options[current++] = "-mbc";
+
+		options[current++] = "-S";
+
+		switch (m_nCVType) {
+			case (LOOCV) :
+				options[current++] = "LOO-CV";
+				break;
+			case (KFOLDCV) :
+				options[current++] = "k-Fold-CV";
+				break;
+			case (CUMCV) :
+				options[current++] = "Cumulative-CV";
+				break;
+		}
+		
+		if (!getUseProb()) {
+		  options[current++] = "-Q";
+		}
+
+                // insert options from parent class
+                for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                        options[current++] = superOptions[iOption];
+                }
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * @return a string to describe the CVType option.
+	 */
+	public String CVTypeTipText() {
+	  return "Select cross validation strategy to be used in searching for networks." +
+	  "LOO-CV = Leave one out cross validation\n" +
+	  "k-Fold-CV = k fold cross validation\n" +
+	  "Cumulative-CV = cumulative cross validation."
+	  ;
+	} // CVTypeTipText
+
+	/**
+	 * @return a string to describe the UseProb option.
+	 */
+	public String useProbTipText() {
+	  return "If set to true, the probability of the class if returned in the estimate of the "+
+	  "accuracy. If set to false, the accuracy estimate is only increased if the classifier returns " +
+	  "exactly the correct class.";
+	} // useProbTipText
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return "This Bayes Network learning algorithm uses cross validation to estimate " +
+	  "classification accuracy.";
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the MarkovBlanketClassifier option.
+	 */
+	public String markovBlanketClassifierTipText() {
+	  return super.markovBlanketClassifierTipText();
+	}
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.10 $");
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/HillClimber.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/HillClimber.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/HillClimber.java	(revision 29)
@@ -0,0 +1,538 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * HillClimber.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses a hill climbing algorithm adding, deleting and reversing arcs. The search is not restricted by an order on the variables (unlike K2). The difference with B and B2 is that this hill climber also considers arrows part of the naive Bayes structure for deletion.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.9 $
+ */
+public class HillClimber 
+    extends GlobalScoreSearchAlgorithm {
+
+    /** for serialization */
+    static final long serialVersionUID = -3885042888195820149L;
+  
+  /** 
+   * the Operation class contains info on operations performed
+   * on the current Bayesian network.
+   */
+    class Operation 
+    	implements Serializable, RevisionHandler {
+      
+      	/** for serialization */
+        static final long serialVersionUID = -2934970456587374967L;
+      
+    	// constants indicating the type of an operation
+    	final static int OPERATION_ADD = 0;
+    	final static int OPERATION_DEL = 1;
+    	final static int OPERATION_REVERSE = 2;
+
+    	/** c'tor **/
+        public Operation() {
+        }
+        
+		/** c'tor + initializers
+		 * 
+		 * @param nTail
+		 * @param nHead
+		 * @param nOperation
+		 */ 
+	    public Operation(int nTail, int nHead, int nOperation) {
+			m_nHead = nHead;
+			m_nTail = nTail;
+			m_nOperation = nOperation;
+		}
+		/** compare this operation with another
+		 * @param other operation to compare with
+		 * @return true if operation is the same
+		 */
+		public boolean equals(Operation other) {
+			if (other == null) {
+				return false;
+			}
+			return ((	m_nOperation == other.m_nOperation) &&
+			(m_nHead == other.m_nHead) &&
+			(m_nTail == other.m_nTail));
+		} // equals
+		/** number of the tail node **/
+        public int m_nTail;
+		/** number of the head node **/
+        public int m_nHead;
+		/** type of operation (ADD, DEL, REVERSE) **/
+        public int m_nOperation;
+        /** change of score due to this operation **/
+        public double m_fScore = -1E100;
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 1.9 $");
+        }
+    } // class Operation
+	
+    /** use the arc reversal operator **/
+    boolean m_bUseArcReversal = false;
+
+    /**
+     * search determines the network structure/graph of the network
+     * with the Taby algorithm.
+     * 
+     * @param bayesNet the network to search
+     * @param instances the instances to work with
+     * @throws Exception if something goes wrong
+     */
+    protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+    	m_BayesNet = bayesNet;
+		double fScore = calcScore(bayesNet);
+        // go do the search        
+		Operation oOperation = getOptimalOperation(bayesNet, instances);
+		while ((oOperation != null) && (oOperation.m_fScore > fScore)) {
+			performOperation(bayesNet, instances, oOperation);
+			fScore = oOperation.m_fScore;
+			oOperation = getOptimalOperation(bayesNet, instances);
+        }        
+    } // search
+
+
+
+	/** check whether the operation is not in the forbidden.
+	 * For base hill climber, there are no restrictions on operations,
+	 * so we always return true.
+	 * @param oOperation operation to be checked
+	 * @return true if operation is not in the tabu list
+	 */
+	boolean isNotTabu(Operation oOperation) {
+		return true;
+	} // isNotTabu
+
+	/** 
+	 * getOptimalOperation finds the optimal operation that can be performed
+	 * on the Bayes network that is not in the tabu list.
+	 * 
+	 * @param bayesNet Bayes network to apply operation on
+	 * @param instances data set to learn from
+	 * @return optimal operation found
+	 * @throws Exception if something goes wrong
+	 */
+    Operation getOptimalOperation(BayesNet bayesNet, Instances instances) throws Exception {
+        Operation oBestOperation = new Operation();
+
+		// Add???
+		oBestOperation = findBestArcToAdd(bayesNet, instances, oBestOperation);
+		// Delete???
+		oBestOperation = findBestArcToDelete(bayesNet, instances, oBestOperation);
+		// Reverse???
+		if (getUseArcReversal()) {
+			oBestOperation = findBestArcToReverse(bayesNet, instances, oBestOperation);
+		}
+
+		// did we find something?
+		if (oBestOperation.m_fScore == -1E100) {
+			return null;
+		}
+
+        return oBestOperation;
+    } // getOptimalOperation
+
+	/** performOperation applies an operation 
+	 * on the Bayes network and update the cache.
+	 * 
+	 * @param bayesNet Bayes network to apply operation on
+	 * @param instances data set to learn from
+	 * @param oOperation operation to perform
+	 * @throws Exception if something goes wrong
+	 */
+	void performOperation(BayesNet bayesNet, Instances instances, Operation oOperation) throws Exception {
+		// perform operation
+		switch (oOperation.m_nOperation) {
+			case Operation.OPERATION_ADD:
+				applyArcAddition(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Add " + oOperation.m_nHead + " -> " + oOperation.m_nTail);
+				}
+				break;
+			case Operation.OPERATION_DEL:
+				applyArcDeletion(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Del " + oOperation.m_nHead + " -> " + oOperation.m_nTail);
+				}
+				break;
+			case Operation.OPERATION_REVERSE:
+				applyArcDeletion(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				applyArcAddition(bayesNet, oOperation.m_nTail, oOperation.m_nHead, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Rev " + oOperation.m_nHead+ " -> " + oOperation.m_nTail);
+				}
+				break;
+		}
+	} // performOperation
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param iHead
+	 * @param iTail
+	 * @param instances
+	 */
+	void applyArcAddition(BayesNet bayesNet, int iHead, int iTail, Instances instances) {
+		ParentSet bestParentSet = bayesNet.getParentSet(iHead);
+		bestParentSet.addParent(iTail, instances);
+	} // applyArcAddition
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param iHead
+	 * @param iTail
+	 * @param instances
+	 */
+	void applyArcDeletion(BayesNet bayesNet, int iHead, int iTail, Instances instances) {
+		ParentSet bestParentSet = bayesNet.getParentSet(iHead);
+		bestParentSet.deleteParent(iTail, instances);
+	} // applyArcAddition
+
+
+	/** 
+	 * find best (or least bad) arc addition operation
+	 * 
+	 * @param bayesNet Bayes network to add arc to
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to add, or null if no arc addition is allowed 
+	 * (this can happen if any arc addition introduces a cycle, or all parent sets are filled
+	 * up to the maximum nr of parents).
+	 * @throws Exception if something goes wrong
+	 */
+	Operation findBestArcToAdd(BayesNet bayesNet, Instances instances, Operation oBestOperation) throws Exception {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to add
+		for (int iAttributeHead = 0; iAttributeHead < nNrOfAtts; iAttributeHead++) {
+			if (bayesNet.getParentSet(iAttributeHead).getNrOfParents() < m_nMaxNrOfParents) {
+				for (int iAttributeTail = 0; iAttributeTail < nNrOfAtts; iAttributeTail++) {
+					if (addArcMakesSense(bayesNet, instances, iAttributeHead, iAttributeTail)) {
+						Operation oOperation = new Operation(iAttributeTail, iAttributeHead, Operation.OPERATION_ADD);
+						double fScore = calcScoreWithExtraParent(oOperation.m_nHead, oOperation.m_nTail);
+						if (fScore > oBestOperation.m_fScore) {
+							if (isNotTabu(oOperation)) {
+								oBestOperation = oOperation;
+								oBestOperation.m_fScore = fScore;
+							}
+						}
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToAdd
+
+	/** 
+	 * find best (or least bad) arc deletion operation
+	 * 
+	 * @param bayesNet Bayes network to delete arc from
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to delete, or null if no deletion can be made 
+	 * (happens when there is no arc in the network yet).
+	 * @throws Exception of something goes wrong
+	 */
+	Operation findBestArcToDelete(BayesNet bayesNet, Instances instances, Operation oBestOperation) throws Exception {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to delete
+		for (int iNode = 0; iNode < nNrOfAtts; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			for (int iParent = 0; iParent < parentSet.getNrOfParents(); iParent++) {
+				Operation oOperation = new Operation(parentSet.getParent(iParent), iNode, Operation.OPERATION_DEL);
+				double fScore = calcScoreWithMissingParent(oOperation.m_nHead, oOperation.m_nTail);
+				if (fScore > oBestOperation.m_fScore) {
+					if (isNotTabu(oOperation)) {
+						oBestOperation = oOperation;
+						oBestOperation.m_fScore = fScore;
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToDelete
+
+	/** 
+	 * find best (or least bad) arc reversal operation
+	 * 
+	 * @param bayesNet Bayes network to reverse arc in
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to reverse, or null if no reversal is allowed
+	 * (happens if there is no arc in the network yet, or when any such reversal introduces
+	 * a cycle).
+	 * @throws Exception if something goes wrong
+	 */
+	Operation findBestArcToReverse(BayesNet bayesNet, Instances instances, Operation oBestOperation) throws Exception {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to reverse
+		for (int iNode = 0; iNode < nNrOfAtts; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			for (int iParent = 0; iParent < parentSet.getNrOfParents(); iParent++) {
+				int iTail = parentSet.getParent(iParent);
+				// is reversal allowed?
+				if (reverseArcMakesSense(bayesNet, instances, iNode, iTail) && 
+				    bayesNet.getParentSet(iTail).getNrOfParents() < m_nMaxNrOfParents) {
+					// go check if reversal results in the best step forward
+					Operation oOperation = new Operation(parentSet.getParent(iParent), iNode, Operation.OPERATION_REVERSE);
+					double fScore = calcScoreWithReversedParent(oOperation.m_nHead, oOperation.m_nTail);
+					if (fScore > oBestOperation.m_fScore) {
+						if (isNotTabu(oOperation)) {
+							oBestOperation = oOperation;
+							oBestOperation.m_fScore = fScore;
+						}
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToReverse
+	
+
+	/**
+	 * Sets the max number of parents
+	 *
+	 * @param nMaxNrOfParents the max number of parents
+	 */
+	public void setMaxNrOfParents(int nMaxNrOfParents) {
+	  m_nMaxNrOfParents = nMaxNrOfParents;
+	} 
+
+	/**
+	 * Gets the max number of parents.
+	 *
+	 * @return the max number of parents
+	 */
+	public int getMaxNrOfParents() {
+	  return m_nMaxNrOfParents;
+	} 
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(2);
+
+		newVector.addElement(new Option("\tMaximum number of parents", "P", 1, "-P <nr of parents>"));
+		newVector.addElement(new Option("\tUse arc reversal operation.\n\t(default false)", "R", 0, "-R"));
+		newVector.addElement(new Option("\tInitial structure is empty (instead of Naive Bayes)", "N", 0, "-N"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		setUseArcReversal(Utils.getFlag('R', options));
+
+		setInitAsNaiveBayes (!(Utils.getFlag('N', options)));
+		
+		String sMaxNrOfParents = Utils.getOption('P', options);
+		if (sMaxNrOfParents.length() != 0) {
+		  setMaxNrOfParents(Integer.parseInt(sMaxNrOfParents));
+		} else {
+		  setMaxNrOfParents(100000);
+		}
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+		if (getUseArcReversal()) {
+		  options[current++] = "-R";
+		}
+		
+		if (!getInitAsNaiveBayes()) {
+		  options[current++] = "-N";
+		} 
+
+		options[current++] = "-P";
+		options[current++] = "" + m_nMaxNrOfParents;
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * Sets whether to init as naive bayes
+	 *
+	 * @param bInitAsNaiveBayes whether to init as naive bayes
+	 */
+	public void setInitAsNaiveBayes(boolean bInitAsNaiveBayes) {
+	  m_bInitAsNaiveBayes = bInitAsNaiveBayes;
+	} 
+
+	/**
+	 * Gets whether to init as naive bayes
+	 *
+	 * @return whether to init as naive bayes
+	 */
+	public boolean getInitAsNaiveBayes() {
+	  return m_bInitAsNaiveBayes;
+	} 
+
+	/** get use the arc reversal operation
+	 * @return whether the arc reversal operation should be used
+	 */
+	public boolean getUseArcReversal() {
+		return m_bUseArcReversal;
+	} // getUseArcReversal
+
+	/** set use the arc reversal operation
+	 * @param bUseArcReversal whether the arc reversal operation should be used
+	 */
+	public void setUseArcReversal(boolean bUseArcReversal) {
+		m_bUseArcReversal = bUseArcReversal;
+	} // setUseArcReversal
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return "This Bayes Network learning algorithm uses a hill climbing algorithm " +
+	  "adding, deleting and reversing arcs. The search is not restricted by an order " +
+	  "on the variables (unlike K2). The difference with B and B2 is that this hill " +	  
+          "climber also considers arrows part of the naive Bayes structure for deletion.";
+	} // globalInfo
+
+	/**
+	 * @return a string to describe the Use Arc Reversal option.
+	 */
+	public String useArcReversalTipText() {
+	  return "When set to true, the arc reversal operation is used in the search.";
+	} // useArcReversalTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.9 $");
+	}
+} // HillClimber
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/K2.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/K2.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/K2.java	(revision 29)
@@ -0,0 +1,409 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * K2.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search.global;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.Random;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses a hill climbing algorithm restricted by an order on the variables.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * G.F. Cooper, E. Herskovits (1990). A Bayesian method for constructing Bayesian belief networks from databases.<br/>
+ * <br/>
+ * G. Cooper, E. Herskovits (1992). A Bayesian method for the induction of probabilistic networks from data. Machine Learning. 9(4):309-347.<br/>
+ * <br/>
+ * Works with nominal variables and no missing values only.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;proceedings{Cooper1990,
+ *    author = {G.F. Cooper and E. Herskovits},
+ *    booktitle = {Proceedings of the Conference on Uncertainty in AI},
+ *    pages = {86-94},
+ *    title = {A Bayesian method for constructing Bayesian belief networks from databases},
+ *    year = {1990}
+ * }
+ * 
+ * &#64;article{Cooper1992,
+ *    author = {G. Cooper and E. Herskovits},
+ *    journal = {Machine Learning},
+ *    number = {4},
+ *    pages = {309-347},
+ *    title = {A Bayesian method for the induction of probabilistic networks from data},
+ *    volume = {9},
+ *    year = {1992}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Random order.
+ *  (default false)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.8 $
+ */
+public class K2 
+	extends GlobalScoreSearchAlgorithm
+	implements TechnicalInformationHandler {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = -6626871067466338256L;
+  	
+	/** Holds flag to indicate ordering should be random **/
+	boolean m_bRandomOrder = false;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  TechnicalInformation 	additional;
+	  
+	  result = new TechnicalInformation(Type.PROCEEDINGS);
+	  result.setValue(Field.AUTHOR, "G.F. Cooper and E. Herskovits");
+	  result.setValue(Field.YEAR, "1990");
+	  result.setValue(Field.TITLE, "A Bayesian method for constructing Bayesian belief networks from databases");
+	  result.setValue(Field.BOOKTITLE, "Proceedings of the Conference on Uncertainty in AI");
+	  result.setValue(Field.PAGES, "86-94");
+	  
+	  additional = result.add(Type.ARTICLE);
+	  additional.setValue(Field.AUTHOR, "G. Cooper and E. Herskovits");
+	  additional.setValue(Field.YEAR, "1992");
+	  additional.setValue(Field.TITLE, "A Bayesian method for the induction of probabilistic networks from data");
+	  additional.setValue(Field.JOURNAL, "Machine Learning");
+	  additional.setValue(Field.VOLUME, "9");
+	  additional.setValue(Field.NUMBER, "4");
+	  additional.setValue(Field.PAGES, "309-347");
+	  
+	  return result;
+	}
+
+	/**
+	 * search determines the network structure/graph of the network
+	 * with the K2 algorithm, restricted by its initial structure (which can
+	 * be an empty graph, or a Naive Bayes graph.
+	 * 
+	 * @param bayesNet the network
+	 * @param instances the data to work with
+	 * @throws Exception if something goes wrong
+	 */
+	public void search (BayesNet bayesNet, Instances instances) throws Exception {
+		int nOrder[] = new int [instances.numAttributes()];
+		nOrder[0] = instances.classIndex();
+
+		int nAttribute = 0;
+
+		for (int iOrder = 1; iOrder < instances.numAttributes(); iOrder++) {
+		  if (nAttribute == instances.classIndex()) {
+		    nAttribute++;
+		  } 
+		  nOrder[iOrder] = nAttribute++;
+		} 
+
+		if (m_bRandomOrder) {
+			// generate random ordering (if required)
+			Random random = new Random();
+					int iClass;
+					if (getInitAsNaiveBayes()) {
+						iClass = 0; 
+					} else {
+						iClass = -1;
+					}
+			for (int iOrder = 0; iOrder < instances.numAttributes(); iOrder++) {
+			int iOrder2 = Math.abs(random.nextInt()) % instances.numAttributes();
+						if (iOrder != iClass && iOrder2 != iClass) {
+							int nTmp = nOrder[iOrder];
+							nOrder[iOrder] = nOrder[iOrder2];
+							nOrder[iOrder2] = nTmp;
+						}
+			}
+		}
+
+		// determine base scores
+		double fBaseScore = calcScore(bayesNet);
+
+		// K2 algorithm: greedy search restricted by ordering 
+		for (int iOrder = 1; iOrder < instances.numAttributes(); iOrder++) {
+			int iAttribute = nOrder[iOrder];
+			double fBestScore = fBaseScore;
+
+			boolean bProgress = (bayesNet.getParentSet(iAttribute).getNrOfParents() < getMaxNrOfParents());
+			while (bProgress && (bayesNet.getParentSet(iAttribute).getNrOfParents() < getMaxNrOfParents())) {
+				int nBestAttribute = -1;
+				for (int iOrder2 = 0; iOrder2 < iOrder; iOrder2++) {
+					int iAttribute2 = nOrder[iOrder2];
+					double fScore = calcScoreWithExtraParent(iAttribute, iAttribute2);
+					if (fScore > fBestScore) {
+						fBestScore = fScore;
+						nBestAttribute = iAttribute2;
+					}
+				}
+				if (nBestAttribute != -1) {
+					bayesNet.getParentSet(iAttribute).addParent(nBestAttribute, instances);
+					fBaseScore = fBestScore;
+					bProgress = true;
+				} else {
+					bProgress = false;
+				}
+			}
+		}
+	} // search 
+
+	/**
+	 * Sets the max number of parents
+	 *
+	 * @param nMaxNrOfParents the max number of parents
+	 */
+	public void setMaxNrOfParents(int nMaxNrOfParents) {
+	  m_nMaxNrOfParents = nMaxNrOfParents;
+	} 
+
+	/**
+	 * Gets the max number of parents.
+	 *
+	 * @return the max number of parents
+	 */
+	public int getMaxNrOfParents() {
+	  return m_nMaxNrOfParents;
+	} 
+
+	/**
+	 * Sets whether to init as naive bayes
+	 *
+	 * @param bInitAsNaiveBayes whether to init as naive bayes
+	 */
+	public void setInitAsNaiveBayes(boolean bInitAsNaiveBayes) {
+	  m_bInitAsNaiveBayes = bInitAsNaiveBayes;
+	} 
+
+	/**
+	 * Gets whether to init as naive bayes
+	 *
+	 * @return whether to init as naive bayes
+	 */
+	public boolean getInitAsNaiveBayes() {
+	  return m_bInitAsNaiveBayes;
+	} 
+
+	/** 
+	 * Set random order flag 
+	 *
+	 * @param bRandomOrder the random order flag
+	 */
+	public void setRandomOrder(boolean bRandomOrder) {
+		m_bRandomOrder = bRandomOrder;
+	} // SetRandomOrder
+
+	/** 
+	 * Get random order flag 
+	 *
+	 * @return the random order flag
+	 */
+	public boolean getRandomOrder() {
+		return m_bRandomOrder;
+	} // getRandomOrder
+  
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+	  Vector newVector = new Vector(0);
+
+	  newVector.addElement(new Option("\tInitial structure is empty (instead of Naive Bayes)", 
+					 "N", 0, "-N"));
+
+	  newVector.addElement(new Option("\tMaximum number of parents", "P", 1, 
+						"-P <nr of parents>"));
+
+	  newVector.addElement(new Option(
+			"\tRandom order.\n"
+			+ "\t(default false)",
+			"R", 0, "-R"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+	  newVector.addElement(enu.nextElement());
+		}
+	  return newVector.elements();
+	}
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Random order.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+    
+	  setRandomOrder(Utils.getFlag('R', options));
+
+	  m_bInitAsNaiveBayes = !(Utils.getFlag('N', options));
+
+	  String sMaxNrOfParents = Utils.getOption('P', options);
+
+	  if (sMaxNrOfParents.length() != 0) {
+		setMaxNrOfParents(Integer.parseInt(sMaxNrOfParents));
+	  } else {
+		setMaxNrOfParents(100000);
+	  } 
+	  super.setOptions(options);	  
+	}
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String [] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[4 + superOptions.length];
+		int current = 0;
+		  options[current++] = "-P";
+		  options[current++] = "" + m_nMaxNrOfParents;
+	    if (!m_bInitAsNaiveBayes) {
+		  options[current++] = "-N";
+	    } 
+	    if (getRandomOrder()) {
+		  options[current++] = "-R";
+	    }
+	    // insert options from parent class
+	    for (int iOption = 0; iOption < superOptions.length; iOption++) {
+		  options[current++] = superOptions[iOption];
+	    }
+	    // Fill up rest with empty strings, not nulls!
+	    while (current < options.length) {
+		  options[current++] = "";
+	    }
+	    // Fill up rest with empty strings, not nulls!
+	    return options;
+	}
+
+	/**
+	 * @return a string to describe the RandomOrder option.
+	 */
+	public String randomOrderTipText() {
+	  return "When set to true, the order of the nodes in the network is random." +
+	  " Default random order is false and the order" +
+	  " of the nodes in the dataset is used." +
+	  " In any case, when the network was initialized as Naive Bayes Network, the" +
+	  " class variable is first in the ordering though.";
+	} // randomOrderTipText
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return 
+	      "This Bayes Network learning algorithm uses a hill climbing algorithm "
+	    + "restricted by an order on the variables.\n\n"
+	    + "For more information see:\n\n"
+	    + getTechnicalInformation().toString() + "\n\n"
+	    + "Works with nominal variables and no missing values only.";
+	}
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.8 $");
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/RepeatedHillClimber.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/RepeatedHillClimber.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/RepeatedHillClimber.java	(revision 29)
@@ -0,0 +1,366 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RepeatedHillClimber.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm repeatedly uses hill climbing starting with a randomly generated network structure and return the best structure of the various runs.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -A &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class RepeatedHillClimber 
+    extends HillClimber {
+
+    /** for serialization */
+    static final long serialVersionUID = -7359197180460703069L;
+  
+    /** number of runs **/
+    int m_nRuns = 10;
+    /** random number seed **/
+    int m_nSeed = 1;
+    /** random number generator **/
+    Random m_random;
+
+	/**
+	* search determines the network structure/graph of the network
+	* with the repeated hill climbing.
+	* 
+	* @param bayesNet the network to use
+	* @param instances the data to use
+	* @throws Exception if something goes wrong
+	**/
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+		m_random = new Random(getSeed());
+		// keeps track of score pf best structure found so far 
+		double fBestScore;	
+		double fCurrentScore = calcScore(bayesNet);
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+
+		// initialize bestBayesNet
+		fBestScore = fCurrentScore;
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+        	// generate random nework
+        	generateRandomNet(bayesNet, instances);
+
+        	// search
+        	super.search(bayesNet, instances);
+
+			// calculate score
+			fCurrentScore = calcScore(bayesNet);
+
+			// keep track of best network seen so far
+			if (fCurrentScore > fBestScore) {
+				fBestScore = fCurrentScore;
+				copyParentSets(bestBayesNet, bayesNet);
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+    } // search
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param instances
+	 */
+	void generateRandomNet(BayesNet bayesNet, Instances instances) {
+		int nNodes = instances.numAttributes();
+		// clear network
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			while (parentSet.getNrOfParents() > 0) {
+				parentSet.deleteLastParent(instances);
+			}
+		}
+		
+		// initialize as naive Bayes?
+		if (getInitAsNaiveBayes()) {
+			int iClass = instances.classIndex();
+			// initialize parent sets to have arrow from classifier node to
+			// each of the other nodes
+			for (int iNode = 0; iNode < nNodes; iNode++) {
+				if (iNode != iClass) {
+					bayesNet.getParentSet(iNode).addParent(iClass, instances);
+				}
+			}
+		}
+
+		// insert random arcs
+		int nNrOfAttempts = m_random.nextInt(nNodes * nNodes);
+		for (int iAttempt = 0; iAttempt < nNrOfAttempts; iAttempt++) {
+			int iTail = m_random.nextInt(nNodes);
+			int iHead = m_random.nextInt(nNodes);
+			if (bayesNet.getParentSet(iHead).getNrOfParents() < getMaxNrOfParents() &&
+			    addArcMakesSense(bayesNet, instances, iHead, iTail)) {
+					bayesNet.getParentSet(iHead).addParent(iTail, instances);
+			}
+		}
+	} // generateRandomNet
+
+	/** 
+	 * copyParentSets copies parent sets of source to dest BayesNet
+	 * 
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+
+    /**
+     * Returns the number of runs
+     * 
+     * @return number of runs
+     */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * 
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+	/**
+	 * Returns the random seed
+	 * 
+	 * @return random number seed
+	 */
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * 
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(4);
+
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tRandom number seed", "A", 1, "-A <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -A &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		
+		String sSeed = Utils.getOption('A', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-A";
+		options[current++] = "" + getSeed();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * 
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm repeatedly uses hill climbing starting " +
+		"with a randomly generated network structure and return the best structure of the " +
+		"various runs.";
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of times hill climbing is performed.";
+	} // runsTipText
+
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.6 $");
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/SimulatedAnnealing.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/SimulatedAnnealing.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/SimulatedAnnealing.java	(revision 29)
@@ -0,0 +1,442 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimulatedAnnealing.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses the general purpose search method of simulated annealing to find a well scoring network structure.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * R.R. Bouckaert (1995). Bayesian Belief Networks: from Construction to Inference. Utrecht, Netherlands.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Bouckaert1995,
+ *    address = {Utrecht, Netherlands},
+ *    author = {R.R. Bouckaert},
+ *    institution = {University of Utrecht},
+ *    title = {Bayesian Belief Networks: from Construction to Inference},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;float&gt;
+ *  Start temperature</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -D &lt;float&gt;
+ *  Delta temperature</pre>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class SimulatedAnnealing 
+	extends GlobalScoreSearchAlgorithm
+	implements TechnicalInformationHandler {
+
+  	/** for serialization */
+  	static final long serialVersionUID = -5482721887881010916L;
+
+    	/** start temperature **/
+	double m_fTStart = 10;
+
+	/** change in temperature at every run **/
+	double m_fDelta = 0.999;
+
+	/** number of runs **/
+	int m_nRuns = 10000;
+
+	/** use the arc reversal operator **/
+	boolean m_bUseArcReversal = false;
+
+	/** random number seed **/
+	int m_nSeed = 1;
+
+	/** random number generator **/
+	Random m_random;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  
+	  result = new TechnicalInformation(Type.PHDTHESIS);
+	  result.setValue(Field.AUTHOR, "R.R. Bouckaert");
+	  result.setValue(Field.YEAR, "1995");
+	  result.setValue(Field.TITLE, "Bayesian Belief Networks: from Construction to Inference");
+	  result.setValue(Field.INSTITUTION, "University of Utrecht");
+	  result.setValue(Field.ADDRESS, "Utrecht, Netherlands");
+	  
+	  return result;
+	}
+	
+    /**
+     * 
+     * @param bayesNet the bayes net to use
+     * @param instances the data to use
+     * @throws Exception if something goes wrong
+     */
+    public void search (BayesNet bayesNet, Instances instances) throws Exception {
+		m_random = new Random(m_nSeed);
+		
+        // determine base scores
+		double fCurrentScore = calcScore(bayesNet);
+
+		// keep track of best scoring network
+		double fBestScore = fCurrentScore;
+		BayesNet bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+
+        double fTemp = m_fTStart;
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+            boolean bRunSucces = false;
+            double fDeltaScore = 0.0;
+            while (!bRunSucces) {
+	            // pick two nodes at random
+	            int iTailNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            int iHeadNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            while (iTailNode == iHeadNode) {
+		            iHeadNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            }
+	            if (isArc(bayesNet, iHeadNode, iTailNode)) {
+                    bRunSucces = true;
+	                // either try a delete
+                    bayesNet.getParentSet(iHeadNode).deleteParent(iTailNode, instances);
+                    double fScore = calcScore(bayesNet);
+                    fDeltaScore = fScore - fCurrentScore;
+//System.out.println("Try delete " + iTailNode + "->" + iHeadNode + " dScore = " + fDeltaScore);                    
+                    if (fTemp * Math.log((Math.abs(m_random.nextInt()) % 10000)/10000.0  + 1e-100) < fDeltaScore) {
+//System.out.println("success!!!");                    
+						fCurrentScore = fScore;
+                    } else {
+                        // roll back
+                        bayesNet.getParentSet(iHeadNode).addParent(iTailNode, instances);
+                    }
+	            } else {
+	                // try to add an arc
+	                if (addArcMakesSense(bayesNet, instances, iHeadNode, iTailNode)) {
+                        bRunSucces = true;
+                        double fScore = calcScoreWithExtraParent(iHeadNode, iTailNode);
+                        fDeltaScore = fScore - fCurrentScore;
+//System.out.println("Try add " + iTailNode + "->" + iHeadNode + " dScore = " + fDeltaScore);                    
+                        if (fTemp * Math.log((Math.abs(m_random.nextInt()) % 10000)/10000.0  + 1e-100) < fDeltaScore) {
+//System.out.println("success!!!");                    
+                            bayesNet.getParentSet(iHeadNode).addParent(iTailNode, instances);
+							fCurrentScore = fScore;
+                        }
+	                }
+	            }
+            }
+			if (fCurrentScore > fBestScore) {
+				copyParentSets(bestBayesNet, bayesNet);				
+			}
+            fTemp = fTemp * m_fDelta;
+        }
+
+		copyParentSets(bayesNet, bestBayesNet);
+    } // buildStructure 
+	
+	/** CopyParentSets copies parent sets of source to dest BayesNet
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+    /**
+     * @return double
+     */
+    public double getDelta() {
+        return m_fDelta;
+    }
+
+    /**
+     * @return double
+     */
+    public double getTStart() {
+        return m_fTStart;
+    }
+
+    /**
+     * @return int
+     */
+    public int getRuns() {
+        return m_nRuns;
+    }
+
+    /**
+     * Sets the m_fDelta.
+     * @param fDelta The m_fDelta to set
+     */
+    public void setDelta(double fDelta) {
+        m_fDelta = fDelta;
+    }
+
+    /**
+     * Sets the m_fTStart.
+     * @param fTStart The m_fTStart to set
+     */
+    public void setTStart(double fTStart) {
+        m_fTStart = fTStart;
+    }
+
+    /**
+     * Sets the m_nRuns.
+     * @param nRuns The m_nRuns to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    }
+
+	/**
+	* @return random number seed
+	*/
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(3);
+
+		newVector.addElement(new Option("\tStart temperature", "A", 1, "-A <float>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tDelta temperature", "D", 1, "-D <float>"));
+		newVector.addElement(new Option("\tRandom number seed", "R", 1, "-R <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	}
+
+	/**
+	 * Parses a given list of options. <p/>
+	 * 
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -A &lt;float&gt;
+	 *  Start temperature</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -D &lt;float&gt;
+	 *  Delta temperature</pre>
+	 * 
+	 * <pre> -R &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sTStart = Utils.getOption('A', options);
+		if (sTStart.length() != 0) {
+			setTStart(Double.parseDouble(sTStart));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		String sDelta = Utils.getOption('D', options);
+		if (sDelta.length() != 0) {
+			setDelta(Double.parseDouble(sDelta));
+		}
+		String sSeed = Utils.getOption('R', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+		super.setOptions(options);
+	}
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[8 + superOptions.length];
+		int current = 0;
+		options[current++] = "-A";
+		options[current++] = "" + getTStart();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-D";
+		options[current++] = "" + getDelta();
+
+		options[current++] = "-R";
+		options[current++] = "" + getSeed();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	}
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return 
+		    "This Bayes Network learning algorithm uses the general purpose search method "
+		  + "of simulated annealing to find a well scoring network structure.\n\n"
+		  + "For more information see:\n\n"
+		  + getTechnicalInformation().toString();
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the TStart option.
+	 */
+	public String TStartTipText() {
+	  return "Sets the start temperature of the simulated annealing search. "+
+	  "The start temperature determines the probability that a step in the 'wrong' direction in the " +
+	  "search space is accepted. The higher the temperature, the higher the probability of acceptance.";
+	} // TStartTipText
+
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of iterations to be performed by the simulated annealing search.";
+	} // runsTipText
+	
+	/**
+	 * @return a string to describe the Delta option.
+	 */
+	public String deltaTipText() {
+	  return "Sets the factor with which the temperature (and thus the acceptance probability of " +
+	  	"steps in the wrong direction in the search space) is decreased in each iteration.";
+	} // deltaTipText
+
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.6 $");
+	}
+} // SimulatedAnnealing
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TAN.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TAN.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TAN.java	(revision 29)
@@ -0,0 +1,280 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TAN.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+
+import java.util.Enumeration;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm determines the maximum weight spanning tree and returns a Naive Bayes network augmented with a tree.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * N. Friedman, D. Geiger, M. Goldszmidt (1997). Bayesian network classifiers. Machine Learning. 29(2-3):131-163.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Friedman1997,
+ *    author = {N. Friedman and D. Geiger and M. Goldszmidt},
+ *    journal = {Machine Learning},
+ *    number = {2-3},
+ *    pages = {131-163},
+ *    title = {Bayesian network classifiers},
+ *    volume = {29},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert
+ * @version $Revision: 1.7 $
+ */
+public class TAN 
+	extends GlobalScoreSearchAlgorithm
+	implements TechnicalInformationHandler {
+
+  	/** for serialization */
+  	static final long serialVersionUID = 1715277053980895298L;
+
+  	/**
+  	 * Returns an instance of a TechnicalInformation object, containing 
+  	 * detailed information about the technical background of this class,
+  	 * e.g., paper reference or book this class is based on.
+  	 * 
+  	 * @return the technical information about this class
+  	 */
+  	public TechnicalInformation getTechnicalInformation() {
+  	  TechnicalInformation 	result;
+  	  
+  	  result = new TechnicalInformation(Type.ARTICLE);
+  	  result.setValue(Field.AUTHOR, "N. Friedman and D. Geiger and M. Goldszmidt");
+  	  result.setValue(Field.YEAR, "1997");
+  	  result.setValue(Field.TITLE, "Bayesian network classifiers");
+  	  result.setValue(Field.JOURNAL, "Machine Learning");
+  	  result.setValue(Field.VOLUME, "29");
+  	  result.setValue(Field.NUMBER, "2-3");
+  	  result.setValue(Field.PAGES, "131-163");
+  	  
+  	  return result;
+  	}
+
+	/**
+	 * buildStructure determines the network structure/graph of the network
+	 * using the maximimum weight spanning tree algorithm of Chow and Liu
+	 * 
+	 * @param bayesNet
+	 * @param instances
+	 * @throws Exception if something goes wrong
+	 */
+	public void buildStructure(BayesNet bayesNet, Instances instances) throws Exception {
+		m_BayesNet = bayesNet;
+
+		m_bInitAsNaiveBayes = true;
+		m_nMaxNrOfParents = 2;
+		super.buildStructure(bayesNet, instances);
+		int      nNrOfAtts = instances.numAttributes();
+
+		// TAN greedy search (not restricted by ordering like K2)
+		// 1. find strongest link
+		// 2. find remaining links by adding strongest link to already
+		//    connected nodes
+		// 3. assign direction to links
+		int nClassNode = instances.classIndex();
+		int [] link1 = new int [nNrOfAtts - 1];
+		int [] link2 = new int [nNrOfAtts - 1];
+		boolean [] linked = new boolean [nNrOfAtts];
+		// 1. find strongest link
+		int    nBestLinkNode1 = -1;
+		int    nBestLinkNode2 = -1;
+		double fBestDeltaScore = 0.0;
+		int iLinkNode1;
+		for (iLinkNode1 = 0; iLinkNode1 < nNrOfAtts; iLinkNode1++) {
+			if (iLinkNode1 != nClassNode) {
+				for (int iLinkNode2 = 0; iLinkNode2 < nNrOfAtts; iLinkNode2++) {
+					if ((iLinkNode1 != iLinkNode2) && (iLinkNode2 != nClassNode)) {
+						double fScore = calcScoreWithExtraParent(iLinkNode1, iLinkNode2);
+					    if ((nBestLinkNode1 == -1) || (fScore > fBestDeltaScore)) {
+					    	fBestDeltaScore = fScore;
+					    	nBestLinkNode1 = iLinkNode2;
+					    	nBestLinkNode2 = iLinkNode1;
+					    } 
+					}
+				}
+			}
+		}
+
+		link1[0] = nBestLinkNode1;
+		link2[0] = nBestLinkNode2;
+		linked[nBestLinkNode1] = true;
+		linked[nBestLinkNode2] = true;
+	
+		// 2. find remaining links by adding strongest link to already
+		//    connected nodes
+		for (int iLink = 1; iLink < nNrOfAtts - 2; iLink++) {
+			nBestLinkNode1 = -1;
+			for (iLinkNode1 = 0; iLinkNode1 < nNrOfAtts; iLinkNode1++) {
+				if (iLinkNode1 != nClassNode) {
+					for (int iLinkNode2 = 0; iLinkNode2 < nNrOfAtts; iLinkNode2++) {
+						if ((iLinkNode1 != iLinkNode2) &&
+						    (iLinkNode2 != nClassNode) && 
+						(linked[iLinkNode1] || linked[iLinkNode2]) &&
+						(!linked[iLinkNode1] || !linked[iLinkNode2])) {
+							double fScore = calcScoreWithExtraParent(iLinkNode1, iLinkNode2);
+
+							if ((nBestLinkNode1 == -1) || (fScore > fBestDeltaScore)) {
+								fBestDeltaScore = fScore;
+								nBestLinkNode1 = iLinkNode2;
+								nBestLinkNode2 = iLinkNode1;
+							} 
+						}
+					} 
+				}
+			}
+			link1[iLink] = nBestLinkNode1;
+			link2[iLink] = nBestLinkNode2;
+			linked[nBestLinkNode1] = true;
+			linked[nBestLinkNode2] = true;
+		}
+		
+		
+//		System.out.println();	
+//		for (int i = 0; i < 3; i++) {
+//			System.out.println(link1[i] + " " + link2[i]);
+//		}
+		// 3. assign direction to links
+		boolean [] hasParent = new boolean [nNrOfAtts];
+		for (int iLink = 0; iLink < nNrOfAtts - 2; iLink++) {
+			if (!hasParent[link1[iLink]]) {
+				bayesNet.getParentSet(link1[iLink]).addParent(link2[iLink], instances);
+				hasParent[link1[iLink]] = true;
+			} else {
+				if (hasParent[link2[iLink]]) {
+					throw new Exception("Bug condition found: too many arrows");
+				}
+				bayesNet.getParentSet(link2[iLink]).addParent(link1[iLink], instances);
+				hasParent[link2[iLink]] = true;
+			}
+		}
+
+	} // buildStructure
+
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+	  return super.listOptions();
+	} // listOption
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 * 
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		super.setOptions(options);
+	} // setOptions
+	
+	/**
+	 * Gets the current settings of the Classifier.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String [] getOptions() {
+		return super.getOptions();
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return 
+	      "This Bayes Network learning algorithm determines the maximum weight spanning tree "
+	    + "and returns a Naive Bayes network augmented with a tree.\n\n"
+	    + "For more information see:\n\n"
+	    + getTechnicalInformation().toString();
+	} // globalInfo
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.7 $");
+	}
+
+} // TAN
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TabuSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TabuSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/global/TabuSearch.java	(revision 29)
@@ -0,0 +1,414 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TabuSearch.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.global;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses tabu search for finding a well scoring Bayes network structure. Tabu search is hill climbing till an optimum is reached. The following step is the least worst possible step. The last X steps are kept in a list and none of the steps in this so called tabu list is considered in taking the next step. The best network found in this traversal is returned.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * R.R. Bouckaert (1995). Bayesian Belief Networks: from Construction to Inference. Utrecht, Netherlands.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Bouckaert1995,
+ *    address = {Utrecht, Netherlands},
+ *    author = {R.R. Bouckaert},
+ *    institution = {University of Utrecht},
+ *    title = {Bayesian Belief Networks: from Construction to Inference},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;integer&gt;
+ *  Tabu list length</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+ *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+ * 
+ * <pre> -Q
+ *  Use probabilistic or 0/1 scoring.
+ *  (default probabilistic scoring)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.5 $
+ */
+public class TabuSearch 
+    extends HillClimber
+    implements TechnicalInformationHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 1176705618756672292L;
+  
+    /** number of runs **/
+    int m_nRuns = 10;
+	    	
+	/** size of tabu list **/
+	int m_nTabuList = 5;
+
+	/** the actual tabu list **/
+	Operation[] m_oTabuList = null;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  
+	  result = new TechnicalInformation(Type.PHDTHESIS);
+	  result.setValue(Field.AUTHOR, "R.R. Bouckaert");
+	  result.setValue(Field.YEAR, "1995");
+	  result.setValue(Field.TITLE, "Bayesian Belief Networks: from Construction to Inference");
+	  result.setValue(Field.INSTITUTION, "University of Utrecht");
+	  result.setValue(Field.ADDRESS, "Utrecht, Netherlands");
+	  
+	  return result;
+	}
+
+	/**
+	 * search determines the network structure/graph of the network
+	 * with the Tabu search algorithm.
+	 * 
+	 * @param bayesNet the network to use
+	 * @param instances the instances to use
+	 * @throws Exception if something goes wrong
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        m_oTabuList = new Operation[m_nTabuList];
+        int iCurrentTabuList = 0;
+
+		// keeps track of score pf best structure found so far 
+		double fBestScore;	
+		double fCurrentScore = calcScore(bayesNet);
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+
+		// initialize bestBayesNet
+		fBestScore = fCurrentScore;
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+            Operation oOperation = getOptimalOperation(bayesNet, instances);
+			performOperation(bayesNet, instances, oOperation);
+            // sanity check
+            if (oOperation  == null) {
+				throw new Exception("Panic: could not find any step to make. Tabu list too long?");
+            }
+            // update tabu list
+            m_oTabuList[iCurrentTabuList] = oOperation;
+            iCurrentTabuList = (iCurrentTabuList + 1) % m_nTabuList;
+
+			fCurrentScore += oOperation.m_fScore;
+			// keep track of best network seen so far
+			if (fCurrentScore > fBestScore) {
+				fBestScore = fCurrentScore;
+				copyParentSets(bestBayesNet, bayesNet);
+			}
+
+			if (bayesNet.getDebug()) {
+				printTabuList();
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+    } // search
+
+
+	/** copyParentSets copies parent sets of source to dest BayesNet
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+	/** check whether the operation is not in the tabu list
+	 * @param oOperation operation to be checked
+	 * @return true if operation is not in the tabu list
+	 */
+	boolean isNotTabu(Operation oOperation) {
+		for (int iTabu = 0; iTabu < m_nTabuList; iTabu++) {
+			if (oOperation.equals(m_oTabuList[iTabu])) {
+					return false;
+				}
+		}
+		return true;
+	} // isNotTabu
+
+	/** print tabu list for debugging purposes.
+	 */
+	void printTabuList() {
+		for (int i = 0; i < m_nTabuList; i++) {
+			Operation o = m_oTabuList[i];
+			if (o != null) {
+				if (o.m_nOperation == 0) {System.out.print(" +(");} else {System.out.print(" -(");}
+				System.out.print(o.m_nTail + "->" + o.m_nHead + ")");
+			}
+		}
+		System.out.println();
+	} // printTabuList
+
+    /**
+    * @return number of runs
+    */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+    /**
+     * @return the Tabu List length
+     */
+    public int getTabuList() {
+        return m_nTabuList;
+    } // getTabuList
+
+    /**
+     * Sets the Tabu List length.
+     * @param nTabuList The nTabuList to set
+     */
+    public void setTabuList(int nTabuList) {
+        m_nTabuList = nTabuList;
+    } // setTabuList
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(4);
+
+		newVector.addElement(new Option("\tTabu list length", "L", 1, "-L <integer>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tMaximum number of parents", "P", 1, "-P <nr of parents>"));
+		newVector.addElement(new Option("\tUse arc reversal operation.\n\t(default false)", "R", 0, "-R"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -L &lt;integer&gt;
+	 *  Tabu list length</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [LOO-CV|k-Fold-CV|Cumulative-CV]
+	 *  Score type (LOO-CV,k-Fold-CV,Cumulative-CV)</pre>
+	 * 
+	 * <pre> -Q
+	 *  Use probabilistic or 0/1 scoring.
+	 *  (default probabilistic scoring)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sTabuList = Utils.getOption('L', options);
+		if (sTabuList.length() != 0) {
+			setTabuList(Integer.parseInt(sTabuList));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+		
+		options[current++] = "-L";
+		options[current++] = "" + getTabuList();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm uses tabu search for finding a well scoring " +
+		"Bayes network structure. Tabu search is hill climbing till an optimum is reached. The " +
+		"following step is the least worst possible step. The last X steps are kept in a list and " +
+		"none of the steps in this so called tabu list is considered in taking the next step. " +
+		"The best network found in this traversal is returned.\n\n"
+		+ "For more information see:\n\n"
+		+ getTechnicalInformation().toString();
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of steps to be performed.";
+	} // runsTipText
+
+	/**
+	 * @return a string to describe the TabuList option.
+	 */
+	public String tabuListTipText() {
+	  return "Sets the length of the tabu list.";
+	} // tabuListTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.5 $");
+	}
+
+} // TabuSearch
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/GeneticSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/GeneticSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/GeneticSearch.java	(revision 29)
@@ -0,0 +1,741 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GeneticSearch.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses genetic search for finding a well scoring Bayes network structure. Genetic search works by having a population of Bayes network structures and allow them to mutate and apply cross over to get offspring. The best network structure found during the process is returned.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;integer&gt;
+ *  Population size</pre>
+ * 
+ * <pre> -A &lt;integer&gt;
+ *  Descendant population size</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -M
+ *  Use mutation.
+ *  (default true)</pre>
+ * 
+ * <pre> -C
+ *  Use cross-over.
+ *  (default true)</pre>
+ * 
+ * <pre> -O
+ *  Use tournament selection (true) or maximum subpopulatin (false).
+ *  (default false)</pre>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.5 $
+ */
+public class GeneticSearch 
+    extends LocalScoreSearchAlgorithm {
+
+    /** for serialization */
+    static final long serialVersionUID = -7037070678911459757L;
+  
+    /** number of runs **/
+    int m_nRuns = 10;
+
+	/** size of population **/
+	int m_nPopulationSize = 10;
+
+	/** size of descendant population **/
+	int m_nDescendantPopulationSize = 100;
+
+	/** use cross-over? **/
+	boolean m_bUseCrossOver = true;
+
+	/** use mutation? **/
+	boolean m_bUseMutation = true;
+	
+	/** use tournament selection or take best sub-population **/
+	boolean m_bUseTournamentSelection = false;	
+	
+	/** random number seed **/
+	int m_nSeed = 1;
+	
+	/** random number generator **/
+	Random m_random = null;
+
+
+	/** used in BayesNetRepresentation for efficiently determining
+	 * whether a number is square  
+	 */
+	static boolean [] g_bIsSquare;
+	
+	class BayesNetRepresentation implements RevisionHandler {
+	  
+		/** number of nodes in network **/		
+		int m_nNodes = 0;
+
+		/** bit representation of parent sets 
+		 * m_bits[iTail + iHead * m_nNodes] represents arc iTail->iHead
+		 */
+		boolean [] m_bits;
+		
+		/** score of represented network structure **/
+		double m_fScore = 0.0f;
+		
+		/** 
+		 * return score of represented network structure
+		 * 
+		 * @return the score
+		 */
+		public double getScore() {
+			return m_fScore;
+		} // getScore
+
+		/** 
+		 * c'tor
+		 * 
+		 * @param nNodes the number of nodes
+		 */
+		BayesNetRepresentation (int nNodes) {
+			m_nNodes = nNodes;
+		} // c'tor
+		
+		/** initialize with a random structure by randomly placing
+		 * m_nNodes arcs.
+		 */
+		public void randomInit() {
+			do {
+				m_bits = new boolean [m_nNodes * m_nNodes];
+				for (int i = 0; i < m_nNodes; i++) {
+					int iPos;
+					do {
+						iPos = m_random.nextInt(m_nNodes * m_nNodes);
+					} while (isSquare(iPos));
+					m_bits[iPos] = true;
+				}
+			} while (hasCycles());
+			calcScore();
+		}
+
+		/** calculate score of current network representation
+		 * As a side effect, the parent sets are set
+		 */
+		void calcScore() {
+			// clear current network
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+				ParentSet parentSet = m_BayesNet.getParentSet(iNode);
+				while (parentSet.getNrOfParents() > 0) {
+					parentSet.deleteLastParent(m_BayesNet.m_Instances);
+				}
+			}
+			// insert arrows
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+				ParentSet parentSet = m_BayesNet.getParentSet(iNode);
+				for (int iNode2 = 0; iNode2 < m_nNodes; iNode2++) {
+					if (m_bits[iNode2 + iNode * m_nNodes]) {
+						parentSet.addParent(iNode2, m_BayesNet.m_Instances);
+					}
+				}
+			}
+			// calc score
+			m_fScore = 0.0;
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+				m_fScore += calcNodeScore(iNode);
+			}
+		} // calcScore
+
+		/** check whether there are cycles in the network
+ 		* 
+ 		* @return true if a cycle is found, false otherwise
+		 */
+		public boolean hasCycles() {
+			// check for cycles
+			boolean[] bDone = new boolean[m_nNodes];
+			for (int iNode = 0; iNode < m_nNodes; iNode++) {
+
+				// find a node for which all parents are 'done'
+				boolean bFound = false;
+
+				for (int iNode2 = 0; !bFound && iNode2 < m_nNodes; iNode2++) {
+					if (!bDone[iNode2]) {
+						boolean bHasNoParents = true;
+						for (int iParent = 0; iParent < m_nNodes; iParent++) {
+							if (m_bits[iParent + iNode2 * m_nNodes] && !bDone[iParent]) {
+								bHasNoParents = false;
+							}
+						}
+						if (bHasNoParents) {
+							bDone[iNode2] = true;
+							bFound = true;
+						}
+					}
+				}
+				if (!bFound) {
+					return true;
+				}
+			}
+			return false;
+		} // hasCycles
+
+		/** create clone of current object 
+		 * @return cloned object
+		 */
+		BayesNetRepresentation copy() {
+			BayesNetRepresentation b = new BayesNetRepresentation(m_nNodes);
+			b.m_bits = new boolean [m_bits.length];
+			for (int i = 0; i < m_nNodes * m_nNodes; i++) {
+				b.m_bits[i] = m_bits[i];
+			}
+			b.m_fScore = m_fScore;
+			return b;		
+		} // copy
+
+		/** Apply mutation operation to BayesNet
+		 * Calculate score and as a side effect sets BayesNet parent sets.
+		 */
+		void mutate() {
+			// flip a bit
+			do {				
+				int iBit;
+				do {
+					iBit = m_random.nextInt(m_nNodes * m_nNodes);
+				} while (isSquare(iBit));
+				
+				m_bits[iBit] = !m_bits[iBit];
+			} while (hasCycles());
+
+			calcScore();
+		} // mutate
+
+		/** Apply cross-over operation to BayesNet 
+		 * Calculate score and as a side effect sets BayesNet parent sets.
+		 * @param other BayesNetRepresentation to cross over with
+		 */
+		void crossOver(BayesNetRepresentation other) {
+			boolean [] bits = new boolean [m_bits.length];
+			for (int i = 0; i < m_bits.length; i++) {
+				bits[i] = m_bits[i];
+			}
+			int iCrossOverPoint = m_bits.length;
+			do {
+				// restore to original state
+				for (int i = iCrossOverPoint; i < m_bits.length; i++) {
+					m_bits[i] = bits[i];
+				}
+				// take all bits from cross-over points onwards
+				iCrossOverPoint = m_random.nextInt(m_bits.length);
+				for (int i = iCrossOverPoint; i < m_bits.length; i++) {
+					m_bits[i] = other.m_bits[i];
+				}
+			} while (hasCycles());
+			calcScore();
+		} // crossOver
+				
+		/** check if number is square and initialize g_bIsSquare structure
+		 * if necessary
+		 * @param nNum number to check (should be below m_nNodes * m_nNodes)
+		 * @return true if number is square
+		 */
+		boolean isSquare(int nNum) {
+			if (g_bIsSquare == null || g_bIsSquare.length < nNum) {
+				g_bIsSquare = new boolean [m_nNodes * m_nNodes];
+				for (int i = 0; i < m_nNodes; i++) {
+					g_bIsSquare[i * m_nNodes + i] = true;
+				}
+			}
+			return g_bIsSquare[nNum];
+		} // isSquare
+
+		/**
+		 * Returns the revision string.
+		 * 
+		 * @return		the revision
+		 */
+		public String getRevision() {
+		  return RevisionUtils.extract("$Revision: 1.5 $");
+		}
+	} // class BayesNetRepresentation 
+	    	
+	/**
+	 * search determines the network structure/graph of the network
+	 * with a genetic search algorithm.
+	 * 
+	 * @param bayesNet the network to use
+	 * @param instances the data to use
+	 * @throws Exception if population size doesn fit or neither cross-over or mutation was chosen
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+		// sanity check
+		if (getDescendantPopulationSize() < getPopulationSize()) {
+			throw new Exception ("Descendant PopulationSize should be at least Population Size");
+		}
+		if (!getUseCrossOver() && !getUseMutation()) {
+			throw new Exception ("At least one of mutation or cross-over should be used");
+		}
+		
+		m_random = new Random(m_nSeed);
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+		// keeps track of score pf best structure found so far 
+		double fBestScore = 0.0;	
+		for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+			fBestScore += calcNodeScore(iAttribute);
+		}
+
+		// initialize bestBayesNet
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // initialize population        
+		BayesNetRepresentation  [] population = new BayesNetRepresentation [getPopulationSize()];
+        for (int i = 0; i < getPopulationSize(); i++) {
+        	population[i] = new BayesNetRepresentation (instances.numAttributes());
+			population[i].randomInit();
+			if (population[i].getScore() > fBestScore) {
+				copyParentSets(bestBayesNet, bayesNet);
+				fBestScore = population[i].getScore();
+				
+			}
+        }
+        
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+        	// create descendants
+			BayesNetRepresentation  [] descendantPopulation = new BayesNetRepresentation  [getDescendantPopulationSize()];
+			for (int i = 0; i < getDescendantPopulationSize(); i++) {
+				descendantPopulation[i] = population[m_random.nextInt(getPopulationSize())].copy();
+				if (getUseMutation()) {
+					if (getUseCrossOver() && m_random.nextBoolean()) {
+						descendantPopulation[i].crossOver(population[m_random.nextInt(getPopulationSize())]);						
+					} else {
+						descendantPopulation[i].mutate();								
+					}
+				} else {
+					// use crossover
+					descendantPopulation[i].crossOver(population[m_random.nextInt(getPopulationSize())]);
+				}
+
+				if (descendantPopulation[i].getScore() > fBestScore) {
+					copyParentSets(bestBayesNet, bayesNet);
+					fBestScore = descendantPopulation[i].getScore();
+				}
+			}
+			// select new population
+			boolean [] bSelected = new boolean [getDescendantPopulationSize()];
+			for (int i = 0; i < getPopulationSize(); i++) {
+				int iSelected = 0;
+				if (m_bUseTournamentSelection) {
+					// use tournament selection
+					iSelected = m_random.nextInt(getDescendantPopulationSize());
+					while (bSelected[iSelected]) {
+						iSelected = (iSelected + 1) % getDescendantPopulationSize();
+					}
+					int iSelected2 =  m_random.nextInt(getDescendantPopulationSize());
+					while (bSelected[iSelected2]) {
+						iSelected2 = (iSelected2 + 1) % getDescendantPopulationSize();
+					}
+					if (descendantPopulation[iSelected2].getScore() > descendantPopulation[iSelected].getScore()) {
+						iSelected = iSelected2;
+					}
+				} else {
+					// find best scoring network in population
+					while (bSelected[iSelected]) {
+						iSelected++;
+					}
+					double fScore = descendantPopulation[iSelected].getScore();
+					for (int j = 0; j < getDescendantPopulationSize(); j++) {
+						if (!bSelected[j] && descendantPopulation[j].getScore() > fScore) {
+							fScore = descendantPopulation[j].getScore();
+							iSelected = j;
+						}
+					}
+				}
+				population[i] = descendantPopulation[iSelected];
+				bSelected[iSelected] = true;
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+    } // search
+
+
+	/** copyParentSets copies parent sets of source to dest BayesNet
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+    /**
+    * @return number of runs
+    */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(7);
+
+		newVector.addElement(new Option("\tPopulation size", "L", 1, "-L <integer>"));
+		newVector.addElement(new Option("\tDescendant population size", "A", 1, "-A <integer>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tUse mutation.\n\t(default true)", "M", 0, "-M"));
+		newVector.addElement(new Option("\tUse cross-over.\n\t(default true)", "C", 0, "-C"));
+		newVector.addElement(new Option("\tUse tournament selection (true) or maximum subpopulatin (false).\n\t(default false)", "O", 0, "-O"));
+		newVector.addElement(new Option("\tRandom number seed", "R", 1, "-R <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -L &lt;integer&gt;
+	 *  Population size</pre>
+	 * 
+	 * <pre> -A &lt;integer&gt;
+	 *  Descendant population size</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -M
+	 *  Use mutation.
+	 *  (default true)</pre>
+	 * 
+	 * <pre> -C
+	 *  Use cross-over.
+	 *  (default true)</pre>
+	 * 
+	 * <pre> -O
+	 *  Use tournament selection (true) or maximum subpopulatin (false).
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -R &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sPopulationSize = Utils.getOption('L', options);
+		if (sPopulationSize.length() != 0) {
+			setPopulationSize(Integer.parseInt(sPopulationSize));
+		}
+		String sDescendantPopulationSize = Utils.getOption('A', options);
+		if (sDescendantPopulationSize.length() != 0) {
+			setDescendantPopulationSize(Integer.parseInt(sDescendantPopulationSize));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		String sSeed = Utils.getOption('R', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+		setUseMutation(Utils.getFlag('M', options));
+		setUseCrossOver(Utils.getFlag('C', options));
+		setUseTournamentSelection(Utils.getFlag('O', options));
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[11 + superOptions.length];
+		int current = 0;
+		
+		options[current++] = "-L";
+		options[current++] = "" + getPopulationSize();
+
+		options[current++] = "-A";
+		options[current++] = "" + getDescendantPopulationSize();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-R";
+		options[current++] = "" + getSeed();
+
+		if (getUseMutation()) {
+		  options[current++] = "-M";
+		}
+		if (getUseCrossOver()) {
+		  options[current++] = "-C";
+		}
+		if (getUseTournamentSelection()) {
+		  options[current++] = "-O";
+		}
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * @return whether cross-over is used
+	 */
+	public boolean getUseCrossOver() {
+		return m_bUseCrossOver;
+	}
+
+	/**
+	 * @return whether mutation is used
+	 */
+	public boolean getUseMutation() {
+		return m_bUseMutation;
+	}
+
+	/**
+	 * @return descendant population size
+	 */
+	public int getDescendantPopulationSize() {
+		return m_nDescendantPopulationSize;
+	}
+
+	/**
+	 * @return population size
+	 */
+	public int getPopulationSize() {
+		return m_nPopulationSize;
+	}
+
+	/**
+	 * @param bUseCrossOver sets whether cross-over is used
+	 */
+	public void setUseCrossOver(boolean bUseCrossOver) {
+		m_bUseCrossOver = bUseCrossOver;
+	}
+
+	/**
+	 * @param bUseMutation sets whether mutation is used
+	 */
+	public void setUseMutation(boolean bUseMutation) {
+		m_bUseMutation = bUseMutation;
+	}
+
+	/**
+	 * @return whether Tournament Selection (true) or Maximum Sub-Population (false) should be used
+	 */
+	public boolean getUseTournamentSelection() {
+		return m_bUseTournamentSelection;
+	}
+
+	/**
+	 * @param bUseTournamentSelection sets whether Tournament Selection or Maximum Sub-Population should be used
+	 */
+	public void setUseTournamentSelection(boolean bUseTournamentSelection) {
+		m_bUseTournamentSelection = bUseTournamentSelection;
+	}
+
+	/**
+	 * @param iDescendantPopulationSize sets descendant population size
+	 */
+	public void setDescendantPopulationSize(int iDescendantPopulationSize) {
+		m_nDescendantPopulationSize = iDescendantPopulationSize;
+	}
+
+	/**
+	 * @param iPopulationSize sets population size
+	 */
+	public void setPopulationSize(int iPopulationSize) {
+		m_nPopulationSize = iPopulationSize;
+	}
+
+	/**
+	* @return random number seed
+	*/
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm uses genetic search for finding a well scoring " +
+		"Bayes network structure. Genetic search works by having a population of Bayes network structures " +
+		"and allow them to mutate and apply cross over to get offspring. The best network structure " +
+		"found during the process is returned.";
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of generations of Bayes network structure populations.";
+	} // runsTipText
+	
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * @return a string to describe the Population Size option.
+	 */
+	public String populationSizeTipText() {
+	  return "Sets the size of the population of network structures that is selected each generation.";
+	} // populationSizeTipText
+
+	/**
+	 * @return a string to describe the Descendant Population Size option.
+	 */
+	public String descendantPopulationSizeTipText() {
+	  return "Sets the size of the population of descendants that is created each generation.";
+	} // descendantPopulationSizeTipText
+
+	/**
+	 * @return a string to describe the Use Mutation option.
+	 */
+	public String useMutationTipText() {
+		return "Determines whether mutation is allowed. Mutation flips a bit in the bit " +
+			"representation of the network structure. At least one of mutation or cross-over " +
+			"should be used.";
+	} // useMutationTipText
+
+	/**
+	 * @return a string to describe the Use Cross-Over option.
+	 */
+	public String useCrossOverTipText() {
+		return "Determines whether cross-over is allowed. Cross over combined the bit " +
+			"representations of network structure by taking a random first k bits of one" +
+			"and adding the remainder of the other. At least one of mutation or cross-over " +
+			"should be used.";
+	} // useCrossOverTipText
+
+	/**
+	 * @return a string to describe the Use Tournament Selection option.
+	 */
+	public String useTournamentSelectionTipText() {
+		return "Determines the method of selecting a population. When set to true, tournament " +
+			"selection is used (pick two at random and the highest is allowed to continue). " +
+			"When set to false, the top scoring network structures are selected.";
+	} // useTournamentSelectionTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.5 $");
+	}
+} // GeneticSearch
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/HillClimber.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/HillClimber.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/HillClimber.java	(revision 29)
@@ -0,0 +1,658 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * HillClimber.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses a hill climbing algorithm adding, deleting and reversing arcs. The search is not restricted by an order on the variables (unlike K2). The difference with B and B2 is that this hill climber also considers arrows part of the naive Bayes structure for deletion.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.9 $
+ */
+public class HillClimber 
+    extends LocalScoreSearchAlgorithm {
+  
+    /** for serialization */
+    static final long serialVersionUID = 4322783593818122403L;
+
+	/** the Operation class contains info on operations performed
+	 * on the current Bayesian network.
+	 */
+    class Operation 
+    	implements Serializable, RevisionHandler {
+      
+      	/** for serialization */
+        static final long serialVersionUID = -4880888790432547895L;
+      
+    	// constants indicating the type of an operation
+    	final static int OPERATION_ADD = 0;
+    	final static int OPERATION_DEL = 1;
+    	final static int OPERATION_REVERSE = 2;
+    	
+    	/** 
+    	 * c'tor
+    	 */
+        public Operation() {
+        }
+        
+		/** c'tor + initializers
+		 * 
+		 * @param nTail
+		 * @param nHead
+		 * @param nOperation
+		 */ 
+	    public Operation(int nTail, int nHead, int nOperation) {
+			m_nHead = nHead;
+			m_nTail = nTail;
+			m_nOperation = nOperation;
+		}
+		/** compare this operation with another
+		 * @param other operation to compare with
+		 * @return true if operation is the same
+		 */
+		public boolean equals(Operation other) {
+			if (other == null) {
+				return false;
+			}
+			return ((	m_nOperation == other.m_nOperation) &&
+			(m_nHead == other.m_nHead) &&
+			(m_nTail == other.m_nTail));
+		} // equals
+		
+		/** number of the tail node **/
+        public int m_nTail;
+        
+		/** number of the head node **/
+        public int m_nHead;
+        
+		/** type of operation (ADD, DEL, REVERSE) **/
+        public int m_nOperation;
+        
+        /** change of score due to this operation **/
+        public double m_fDeltaScore = -1E100;
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 1.9 $");
+        }
+    } // class Operation
+
+	/** cache for remembering the change in score for steps in the search space
+	 */
+	class Cache implements RevisionHandler {
+	  
+		/** change in score due to adding an arc **/
+		double [] [] m_fDeltaScoreAdd;
+		/** change in score due to deleting an arc **/
+		double [] [] m_fDeltaScoreDel;
+		/** c'tor
+		 * @param nNrOfNodes number of nodes in network, used to determine memory size to reserve
+		 */
+		Cache(int nNrOfNodes) {
+			m_fDeltaScoreAdd = new double [nNrOfNodes][nNrOfNodes];
+			m_fDeltaScoreDel = new double [nNrOfNodes][nNrOfNodes];
+		}
+
+		/** set cache entry
+		 * @param oOperation operation to perform
+		 * @param fValue value to put in cache
+		 */
+		public void put(Operation oOperation, double fValue) {
+			if (oOperation.m_nOperation == Operation.OPERATION_ADD) {
+				m_fDeltaScoreAdd[oOperation.m_nTail][oOperation.m_nHead] = fValue;
+			} else {
+				m_fDeltaScoreDel[oOperation.m_nTail][oOperation.m_nHead] = fValue;
+			}
+		} // put
+
+		/** get cache entry
+		 * @param oOperation operation to perform
+		 * @return cache value
+		 */
+		public double get(Operation oOperation) {
+			switch(oOperation.m_nOperation) {
+				case Operation.OPERATION_ADD:
+					return m_fDeltaScoreAdd[oOperation.m_nTail][oOperation.m_nHead];
+				case Operation.OPERATION_DEL:
+					return m_fDeltaScoreDel[oOperation.m_nTail][oOperation.m_nHead];
+				case Operation.OPERATION_REVERSE:
+				return m_fDeltaScoreDel[oOperation.m_nTail][oOperation.m_nHead] + 
+						m_fDeltaScoreAdd[oOperation.m_nHead][oOperation.m_nTail];
+			}
+			// should never get here
+			return 0;
+		} // get
+
+		/**
+		 * Returns the revision string.
+		 * 
+		 * @return		the revision
+		 */
+		public String getRevision() {
+		  return RevisionUtils.extract("$Revision: 1.9 $");
+		}
+	} // class Cache
+
+	/** cache for storing score differences **/
+	Cache m_Cache = null;
+	
+    /** use the arc reversal operator **/
+    boolean m_bUseArcReversal = false;
+    	
+
+    /**
+     * search determines the network structure/graph of the network
+     * with the Taby algorithm.
+     * 
+     * @param bayesNet the network to use
+     * @param instances the data to use
+     * @throws Exception if something goes wrong
+     */
+    protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        initCache(bayesNet, instances);
+
+        // go do the search        
+		Operation oOperation = getOptimalOperation(bayesNet, instances);
+		while ((oOperation != null) && (oOperation.m_fDeltaScore > 0)) {
+			performOperation(bayesNet, instances, oOperation);
+			oOperation = getOptimalOperation(bayesNet, instances);
+        }
+        
+		// free up memory
+		m_Cache = null;
+    } // search
+
+
+	/** 
+	 * initCache initializes the cache
+	 * 
+	 * @param bayesNet Bayes network to be learned
+	 * @param instances data set to learn from
+	 * @throws Exception if something goes wrong
+	 */
+    void initCache(BayesNet bayesNet, Instances instances)  throws Exception {
+    	
+        // determine base scores
+		double[] fBaseScores = new double[instances.numAttributes()];
+        int nNrOfAtts = instances.numAttributes();
+
+		m_Cache = new Cache (nNrOfAtts);
+		
+		for (int iAttribute = 0; iAttribute < nNrOfAtts; iAttribute++) {
+			updateCache(iAttribute, nNrOfAtts, bayesNet.getParentSet(iAttribute));
+		}
+
+
+        for (int iAttribute = 0; iAttribute < nNrOfAtts; iAttribute++) {
+            fBaseScores[iAttribute] = calcNodeScore(iAttribute);
+        }
+
+        for (int iAttributeHead = 0; iAttributeHead < nNrOfAtts; iAttributeHead++) {
+                for (int iAttributeTail = 0; iAttributeTail < nNrOfAtts; iAttributeTail++) {
+                	if (iAttributeHead != iAttributeTail) {
+	                    Operation oOperation = new Operation(iAttributeTail, iAttributeHead, Operation.OPERATION_ADD);
+	                    m_Cache.put(oOperation, calcScoreWithExtraParent(iAttributeHead, iAttributeTail) - fBaseScores[iAttributeHead]);
+					}
+            }
+        }
+
+    } // initCache
+
+	/** check whether the operation is not in the forbidden.
+	 * For base hill climber, there are no restrictions on operations,
+	 * so we always return true.
+	 * @param oOperation operation to be checked
+	 * @return true if operation is not in the tabu list
+	 */
+	boolean isNotTabu(Operation oOperation) {
+		return true;
+	} // isNotTabu
+
+	/** 
+	 * getOptimalOperation finds the optimal operation that can be performed
+	 * on the Bayes network that is not in the tabu list.
+	 * 
+	 * @param bayesNet Bayes network to apply operation on
+	 * @param instances data set to learn from
+	 * @return optimal operation found
+	 * @throws Exception if something goes wrong
+	 */
+    Operation getOptimalOperation(BayesNet bayesNet, Instances instances) throws Exception {
+        Operation oBestOperation = new Operation();
+
+		// Add???
+		oBestOperation = findBestArcToAdd(bayesNet, instances, oBestOperation);
+		// Delete???
+		oBestOperation = findBestArcToDelete(bayesNet, instances, oBestOperation);
+		// Reverse???
+		if (getUseArcReversal()) {
+			oBestOperation = findBestArcToReverse(bayesNet, instances, oBestOperation);
+		}
+
+		// did we find something?
+		if (oBestOperation.m_fDeltaScore == -1E100) {
+			return null;
+		}
+
+        return oBestOperation;
+    } // getOptimalOperation
+
+	/** 
+	 * performOperation applies an operation 
+	 * on the Bayes network and update the cache.
+	 * 
+	 * @param bayesNet Bayes network to apply operation on
+	 * @param instances data set to learn from
+	 * @param oOperation operation to perform
+	 * @throws Exception if something goes wrong
+	 */
+	void performOperation(BayesNet bayesNet, Instances instances, Operation oOperation) throws Exception {
+		// perform operation
+		switch (oOperation.m_nOperation) {
+			case Operation.OPERATION_ADD:
+				applyArcAddition(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Add " + oOperation.m_nHead + " -> " + oOperation.m_nTail);
+				}
+				break;
+			case Operation.OPERATION_DEL:
+				applyArcDeletion(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Del " + oOperation.m_nHead + " -> " + oOperation.m_nTail);
+				}
+				break;
+			case Operation.OPERATION_REVERSE:
+				applyArcDeletion(bayesNet, oOperation.m_nHead, oOperation.m_nTail, instances);
+				applyArcAddition(bayesNet, oOperation.m_nTail, oOperation.m_nHead, instances);
+				if (bayesNet.getDebug()) {
+					System.out.print("Rev " + oOperation.m_nHead+ " -> " + oOperation.m_nTail);
+				}
+				break;
+		}
+	} // performOperation
+
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param iHead
+	 * @param iTail
+	 * @param instances
+	 */
+	void applyArcAddition(BayesNet bayesNet, int iHead, int iTail, Instances instances) {
+		ParentSet bestParentSet = bayesNet.getParentSet(iHead);
+		bestParentSet.addParent(iTail, instances);
+		updateCache(iHead, instances.numAttributes(), bestParentSet);
+	} // applyArcAddition
+
+	/**
+	 * 
+	 * @param bayesNet
+	 * @param iHead
+	 * @param iTail
+	 * @param instances
+	 */
+	void applyArcDeletion(BayesNet bayesNet, int iHead, int iTail, Instances instances) {
+		ParentSet bestParentSet = bayesNet.getParentSet(iHead);
+		bestParentSet.deleteParent(iTail, instances);
+		updateCache(iHead, instances.numAttributes(), bestParentSet);
+	} // applyArcAddition
+
+
+	/** 
+	 * find best (or least bad) arc addition operation
+	 * 
+	 * @param bayesNet Bayes network to add arc to
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to add, or null if no arc addition is allowed 
+	 * (this can happen if any arc addition introduces a cycle, or all parent sets are filled
+	 * up to the maximum nr of parents).
+	 */
+	Operation findBestArcToAdd(BayesNet bayesNet, Instances instances, Operation oBestOperation) {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to add
+		for (int iAttributeHead = 0; iAttributeHead < nNrOfAtts; iAttributeHead++) {
+			if (bayesNet.getParentSet(iAttributeHead).getNrOfParents() < m_nMaxNrOfParents) {
+				for (int iAttributeTail = 0; iAttributeTail < nNrOfAtts; iAttributeTail++) {
+					if (addArcMakesSense(bayesNet, instances, iAttributeHead, iAttributeTail)) {
+						Operation oOperation = new Operation(iAttributeTail, iAttributeHead, Operation.OPERATION_ADD);
+						if (m_Cache.get(oOperation) > oBestOperation.m_fDeltaScore) {
+							if (isNotTabu(oOperation)) {
+								oBestOperation = oOperation;
+								oBestOperation.m_fDeltaScore = m_Cache.get(oOperation);
+							}
+						}
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToAdd
+
+	/** 
+	 * find best (or least bad) arc deletion operation
+	 * 
+	 * @param bayesNet Bayes network to delete arc from
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to delete, or null if no deletion can be made 
+	 * (happens when there is no arc in the network yet).
+	 */
+	Operation findBestArcToDelete(BayesNet bayesNet, Instances instances, Operation oBestOperation) {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to delete
+		for (int iNode = 0; iNode < nNrOfAtts; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			for (int iParent = 0; iParent < parentSet.getNrOfParents(); iParent++) {
+				Operation oOperation = new Operation(parentSet.getParent(iParent), iNode, Operation.OPERATION_DEL);
+				if (m_Cache.get(oOperation) > oBestOperation.m_fDeltaScore) {
+					if (isNotTabu(oOperation)) {
+						oBestOperation = oOperation;
+						oBestOperation.m_fDeltaScore = m_Cache.get(oOperation);
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToDelete
+
+	/** 
+	 * find best (or least bad) arc reversal operation
+	 * 
+	 * @param bayesNet Bayes network to reverse arc in
+	 * @param instances data set
+	 * @param oBestOperation
+	 * @return Operation containing best arc to reverse, or null if no reversal is allowed
+	 * (happens if there is no arc in the network yet, or when any such reversal introduces
+	 * a cycle).
+	 */
+	Operation findBestArcToReverse(BayesNet bayesNet, Instances instances, Operation oBestOperation) {
+		int nNrOfAtts = instances.numAttributes();
+		// find best arc to reverse
+		for (int iNode = 0; iNode < nNrOfAtts; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			for (int iParent = 0; iParent < parentSet.getNrOfParents(); iParent++) {
+				int iTail = parentSet.getParent(iParent);
+				// is reversal allowed?
+				if (reverseArcMakesSense(bayesNet, instances, iNode, iTail) && 
+				    bayesNet.getParentSet(iTail).getNrOfParents() < m_nMaxNrOfParents) {
+					// go check if reversal results in the best step forward
+					Operation oOperation = new Operation(parentSet.getParent(iParent), iNode, Operation.OPERATION_REVERSE);
+					if (m_Cache.get(oOperation) > oBestOperation.m_fDeltaScore) {
+						if (isNotTabu(oOperation)) {
+							oBestOperation = oOperation;
+							oBestOperation.m_fDeltaScore = m_Cache.get(oOperation);
+						}
+					}
+				}
+			}
+		}
+		return oBestOperation;
+	} // findBestArcToReverse
+
+	/** 
+	 * update the cache due to change of parent set of a node
+	 * 
+	 * @param iAttributeHead node that has its parent set changed
+	 * @param nNrOfAtts number of nodes/attributes in data set
+	 * @param parentSet new parents set of node iAttributeHead
+	 */
+	void updateCache(int iAttributeHead, int nNrOfAtts, ParentSet parentSet) {
+		// update cache entries for arrows heading towards iAttributeHead
+		double fBaseScore = calcNodeScore(iAttributeHead);
+		int nNrOfParents = parentSet.getNrOfParents();
+		for (int iAttributeTail = 0; iAttributeTail < nNrOfAtts; iAttributeTail++) {
+			if (iAttributeTail != iAttributeHead) {
+				if (!parentSet.contains(iAttributeTail)) {
+					// add entries to cache for adding arcs
+					if (nNrOfParents < m_nMaxNrOfParents) {
+						Operation oOperation = new Operation(iAttributeTail, iAttributeHead, Operation.OPERATION_ADD);
+						m_Cache.put(oOperation, calcScoreWithExtraParent(iAttributeHead, iAttributeTail) - fBaseScore);
+					}
+				} else {
+					// add entries to cache for deleting arcs
+					Operation oOperation = new Operation(iAttributeTail, iAttributeHead, Operation.OPERATION_DEL);
+					m_Cache.put(oOperation, calcScoreWithMissingParent(iAttributeHead, iAttributeTail) - fBaseScore);
+				}
+			}
+		}
+	} // updateCache
+	
+
+	/**
+	 * Sets the max number of parents
+	 *
+	 * @param nMaxNrOfParents the max number of parents
+	 */
+	public void setMaxNrOfParents(int nMaxNrOfParents) {
+	  m_nMaxNrOfParents = nMaxNrOfParents;
+	} 
+
+	/**
+	 * Gets the max number of parents.
+	 *
+	 * @return the max number of parents
+	 */
+	public int getMaxNrOfParents() {
+	  return m_nMaxNrOfParents;
+	} 
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(2);
+
+		newVector.addElement(new Option("\tMaximum number of parents", "P", 1, "-P <nr of parents>"));
+		newVector.addElement(new Option("\tUse arc reversal operation.\n\t(default false)", "R", 0, "-R"));
+		newVector.addElement(new Option("\tInitial structure is empty (instead of Naive Bayes)", "N", 0, "-N"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		setUseArcReversal(Utils.getFlag('R', options));
+
+		setInitAsNaiveBayes (!(Utils.getFlag('N', options)));
+		
+		String sMaxNrOfParents = Utils.getOption('P', options);
+		if (sMaxNrOfParents.length() != 0) {
+		  setMaxNrOfParents(Integer.parseInt(sMaxNrOfParents));
+		} else {
+		  setMaxNrOfParents(100000);
+		}
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+		if (getUseArcReversal()) {
+		  options[current++] = "-R";
+		}
+		
+		if (!getInitAsNaiveBayes()) {
+		  options[current++] = "-N";
+		} 
+
+		options[current++] = "-P";
+		options[current++] = "" + m_nMaxNrOfParents;
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * Sets whether to init as naive bayes
+	 *
+	 * @param bInitAsNaiveBayes whether to init as naive bayes
+	 */
+	public void setInitAsNaiveBayes(boolean bInitAsNaiveBayes) {
+	  m_bInitAsNaiveBayes = bInitAsNaiveBayes;
+	} 
+
+	/**
+	 * Gets whether to init as naive bayes
+	 *
+	 * @return whether to init as naive bayes
+	 */
+	public boolean getInitAsNaiveBayes() {
+	  return m_bInitAsNaiveBayes;
+	} 
+
+	/** get use the arc reversal operation
+	 * @return whether the arc reversal operation should be used
+	 */
+	public boolean getUseArcReversal() {
+		return m_bUseArcReversal;
+	} // getUseArcReversal
+
+	/** set use the arc reversal operation
+	 * @param bUseArcReversal whether the arc reversal operation should be used
+	 */
+	public void setUseArcReversal(boolean bUseArcReversal) {
+		m_bUseArcReversal = bUseArcReversal;
+	} // setUseArcReversal
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return "This Bayes Network learning algorithm uses a hill climbing algorithm " +
+	  "adding, deleting and reversing arcs. The search is not restricted by an order " +
+	  "on the variables (unlike K2). The difference with B and B2 is that this hill " +	  
+          "climber also considers arrows part of the naive Bayes structure for deletion.";
+	} // globalInfo
+
+	/**
+	 * @return a string to describe the Use Arc Reversal option.
+	 */
+	public String useArcReversalTipText() {
+	  return "When set to true, the arc reversal operation is used in the search.";
+	} // useArcReversalTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.9 $");
+	}
+
+} // HillClimber
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/K2.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/K2.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/K2.java	(revision 29)
@@ -0,0 +1,406 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * K2.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses a hill climbing algorithm restricted by an order on the variables.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * G.F. Cooper, E. Herskovits (1990). A Bayesian method for constructing Bayesian belief networks from databases.<br/>
+ * <br/>
+ * G. Cooper, E. Herskovits (1992). A Bayesian method for the induction of probabilistic networks from data. Machine Learning. 9(4):309-347.<br/>
+ * <br/>
+ * Works with nominal variables and no missing values only.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;proceedings{Cooper1990,
+ *    author = {G.F. Cooper and E. Herskovits},
+ *    booktitle = {Proceedings of the Conference on Uncertainty in AI},
+ *    pages = {86-94},
+ *    title = {A Bayesian method for constructing Bayesian belief networks from databases},
+ *    year = {1990}
+ * }
+ * 
+ * &#64;article{Cooper1992,
+ *    author = {G. Cooper and E. Herskovits},
+ *    journal = {Machine Learning},
+ *    number = {4},
+ *    pages = {309-347},
+ *    title = {A Bayesian method for the induction of probabilistic networks from data},
+ *    volume = {9},
+ *    year = {1992}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Random order.
+ *  (default false)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.8 $
+ */
+public class K2 
+ 	extends LocalScoreSearchAlgorithm
+ 	implements TechnicalInformationHandler {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = 6176545934752116631L;
+  
+	/** Holds flag to indicate ordering should be random **/
+	boolean m_bRandomOrder = false;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  TechnicalInformation 	additional;
+	  
+	  result = new TechnicalInformation(Type.PROCEEDINGS);
+	  result.setValue(Field.AUTHOR, "G.F. Cooper and E. Herskovits");
+	  result.setValue(Field.YEAR, "1990");
+	  result.setValue(Field.TITLE, "A Bayesian method for constructing Bayesian belief networks from databases");
+	  result.setValue(Field.BOOKTITLE, "Proceedings of the Conference on Uncertainty in AI");
+	  result.setValue(Field.PAGES, "86-94");
+	  
+	  additional = result.add(Type.ARTICLE);
+	  additional.setValue(Field.AUTHOR, "G. Cooper and E. Herskovits");
+	  additional.setValue(Field.YEAR, "1992");
+	  additional.setValue(Field.TITLE, "A Bayesian method for the induction of probabilistic networks from data");
+	  additional.setValue(Field.JOURNAL, "Machine Learning");
+	  additional.setValue(Field.VOLUME, "9");
+	  additional.setValue(Field.NUMBER, "4");
+	  additional.setValue(Field.PAGES, "309-347");
+	  
+	  return result;
+	}
+
+	/**
+	 * search determines the network structure/graph of the network
+	 * with the K2 algorithm, restricted by its initial structure (which can
+	 * be an empty graph, or a Naive Bayes graph.
+	 * 
+	 * @param bayesNet the network
+	 * @param instances the data to work with
+	 * @throws Exception if something goes wrong
+	 */
+	public void search (BayesNet bayesNet, Instances instances) throws Exception {
+		int nOrder[] = new int [instances.numAttributes()];
+		nOrder[0] = instances.classIndex();
+
+		int nAttribute = 0;
+
+		for (int iOrder = 1; iOrder < instances.numAttributes(); iOrder++) {
+		  if (nAttribute == instances.classIndex()) {
+		    nAttribute++;
+		  } 
+		  nOrder[iOrder] = nAttribute++;
+		} 
+
+		if (m_bRandomOrder) {
+			// generate random ordering (if required)
+			Random random = new Random();
+					int iClass;
+					if (getInitAsNaiveBayes()) {
+						iClass = 0; 
+					} else {
+						iClass = -1;
+					}
+			for (int iOrder = 0; iOrder < instances.numAttributes(); iOrder++) {
+			int iOrder2 = Math.abs(random.nextInt()) % instances.numAttributes();
+						if (iOrder != iClass && iOrder2 != iClass) {
+							int nTmp = nOrder[iOrder];
+							nOrder[iOrder] = nOrder[iOrder2];
+							nOrder[iOrder2] = nTmp;
+						}
+			}
+		}
+
+		// determine base scores
+		double [] fBaseScores = new double [instances.numAttributes()];
+		for (int iOrder = 0; iOrder < instances.numAttributes(); iOrder++) {
+			int iAttribute = nOrder[iOrder];
+			fBaseScores[iAttribute] = calcNodeScore(iAttribute);
+		}
+
+		// K2 algorithm: greedy search restricted by ordering 
+		for (int iOrder = 1; iOrder < instances.numAttributes(); iOrder++) {
+			int iAttribute = nOrder[iOrder];
+			double fBestScore = fBaseScores[iAttribute];
+
+			boolean bProgress = (bayesNet.getParentSet(iAttribute).getNrOfParents() < getMaxNrOfParents());
+			while (bProgress) {
+				int nBestAttribute = -1;
+				for (int iOrder2 = 0; iOrder2 < iOrder; iOrder2++) {
+					int iAttribute2 = nOrder[iOrder2];
+					double fScore = calcScoreWithExtraParent(iAttribute, iAttribute2);
+					if (fScore > fBestScore) {
+						fBestScore = fScore;
+						nBestAttribute = iAttribute2;
+					}
+				}
+				if (nBestAttribute != -1) {
+					bayesNet.getParentSet(iAttribute).addParent(nBestAttribute, instances);
+					fBaseScores[iAttribute] = fBestScore;
+					bProgress = (bayesNet.getParentSet(iAttribute).getNrOfParents() < getMaxNrOfParents());
+				} else {
+					bProgress = false;
+				}
+			}
+		}
+	} // buildStructure 
+
+	/**
+	 * Sets the max number of parents
+	 *
+	 * @param nMaxNrOfParents the max number of parents
+	 */
+	public void setMaxNrOfParents(int nMaxNrOfParents) {
+	  m_nMaxNrOfParents = nMaxNrOfParents;
+	} 
+
+	/**
+	 * Gets the max number of parents.
+	 *
+	 * @return the max number of parents
+	 */
+	public int getMaxNrOfParents() {
+	  return m_nMaxNrOfParents;
+	} 
+
+	/**
+	 * Sets whether to init as naive bayes
+	 *
+	 * @param bInitAsNaiveBayes whether to init as naive bayes
+	 */
+	public void setInitAsNaiveBayes(boolean bInitAsNaiveBayes) {
+	  m_bInitAsNaiveBayes = bInitAsNaiveBayes;
+	} 
+
+	/**
+	 * Gets whether to init as naive bayes
+	 *
+	 * @return whether to init as naive bayes
+	 */
+	public boolean getInitAsNaiveBayes() {
+	  return m_bInitAsNaiveBayes;
+	} 
+
+	/** 
+	 * Set random order flag 
+	 *
+	 * @param bRandomOrder the random order flag
+	 */
+	public void setRandomOrder(boolean bRandomOrder) {
+		m_bRandomOrder = bRandomOrder;
+	} // SetRandomOrder
+
+	/** 
+	 * Get random order flag 
+	 *
+	 * @return the random order flag
+	 */
+	public boolean getRandomOrder() {
+		return m_bRandomOrder;
+	} // getRandomOrder
+  
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+	  Vector newVector = new Vector(0);
+
+	  newVector.addElement(new Option("\tInitial structure is empty (instead of Naive Bayes)", 
+					 "N", 0, "-N"));
+
+	  newVector.addElement(new Option("\tMaximum number of parents", "P", 1, 
+						"-P <nr of parents>"));
+
+	  newVector.addElement(new Option(
+			"\tRandom order.\n"
+			+ "\t(default false)",
+			"R", 0, "-R"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+	    	newVector.addElement(enu.nextElement());
+		}
+	  return newVector.elements();
+	}
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Random order.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+    
+	  setRandomOrder(Utils.getFlag('R', options));
+
+	  m_bInitAsNaiveBayes = !(Utils.getFlag('N', options));
+
+	  String sMaxNrOfParents = Utils.getOption('P', options);
+
+	  if (sMaxNrOfParents.length() != 0) {
+		setMaxNrOfParents(Integer.parseInt(sMaxNrOfParents));
+	  } else {
+		setMaxNrOfParents(100000);
+	  }
+	  super.setOptions(options);
+	}
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String [] getOptions() {
+          String[] superOptions = super.getOptions();
+	  String [] options  = new String [4 + superOptions.length];
+	  int current = 0;
+	  options[current++] = "-P";
+	  options[current++] = "" + m_nMaxNrOfParents;
+	  if (!m_bInitAsNaiveBayes) {
+		options[current++] = "-N";
+	  }	  if (getRandomOrder()) {
+		options[current++] = "-R";
+	  }
+
+          // insert options from parent class
+          for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                  options[current++] = superOptions[iOption];
+          }
+
+	  while (current < options.length) {
+		options[current++] = "";
+	  }
+	  // Fill up rest with empty strings, not nulls!
+	  return options;
+	}
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return
+	      "This Bayes Network learning algorithm uses a hill climbing algorithm "
+	    + "restricted by an order on the variables.\n\n"
+	    + "For more information see:\n\n"
+	    + getTechnicalInformation().toString() + "\n\n"
+	    + "Works with nominal variables and no missing values only.";
+	}
+
+	/**
+	 * @return a string to describe the RandomOrder option.
+	 */
+	public String randomOrderTipText() {
+	  return "When set to true, the order of the nodes in the network is random." +
+	  " Default random order is false and the order" +
+	  " of the nodes in the dataset is used." +
+	  " In any case, when the network was initialized as Naive Bayes Network, the" +
+	  " class variable is first in the ordering though.";
+	} // randomOrderTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.8 $");
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LAGDHillClimber.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LAGDHillClimber.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LAGDHillClimber.java	(revision 29)
@@ -0,0 +1,452 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LAGDHillClimber.java
+ * Copyright (C) 2005 Manuel Neubach
+ * 
+ */
+
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses a Look Ahead Hill Climbing algorithm called LAGD Hill Climbing. Unlike Greedy Hill Climbing it doesn't calculate a best greedy operation (adding, deleting or reversing an arc) but a sequence of nrOfLookAheadSteps operations, which leads to a network structure whose score is most likely higher in comparison to the network obtained by performing a sequence of nrOfLookAheadSteps greedy operations. The search is not restricted by an order on the variables (unlike K2). The difference with B and B2 is that this hill climber also considers arrows part of the naive Bayes structure for deletion.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;nr of look ahead steps&gt;
+ *  Look Ahead Depth</pre>
+ * 
+ * <pre> -G &lt;nr of good operations&gt;
+ *  Nr of Good Operations</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Manuel Neubach
+ * @version $Revision: 1.7 $
+ */
+public class LAGDHillClimber 
+    extends HillClimber {
+  
+    /** for serialization */
+    static final long serialVersionUID = 7217437499439184344L;
+
+    /** Number of Look Ahead Steps **/
+    int m_nNrOfLookAheadSteps = 2;
+
+    /** Number of Good Operations per Step **/
+    int m_nNrOfGoodOperations = 5;
+
+   /**
+     * search determines the network structure/graph of the network
+     * 
+     * @param bayesNet the network
+     * @param instances the data to use
+     * @throws Exception if something goes wrong
+     */
+   protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        int k=m_nNrOfLookAheadSteps;  // Number of Look Ahead Steps
+        int l=m_nNrOfGoodOperations; // Number of Good Operations per step
+        lookAheadInGoodDirectionsSearch(bayesNet, instances, k, l);
+   } // search
+
+
+   /**
+    * lookAheadInGoodDirectionsSearch determines the network structure/graph of the network
+    * with best score according to LAGD Hill Climbing
+    * 
+    * @param bayesNet the network
+    * @param instances the data to use
+    * @param nrOfLookAheadSteps
+    * @param nrOfGoodOperations
+    * @throws Exception if something goes wrong
+    */
+    protected void lookAheadInGoodDirectionsSearch(BayesNet bayesNet, Instances instances, int nrOfLookAheadSteps, int nrOfGoodOperations) throws Exception {
+         System.out.println("Initializing Cache");
+         initCache(bayesNet, instances);
+
+         while (nrOfLookAheadSteps>1) {         
+            System.out.println("Look Ahead Depth: "+nrOfLookAheadSteps);
+            boolean legalSequence = true;
+            double sequenceDeltaScore = 0;
+            Operation [] bestOperation=new Operation [nrOfLookAheadSteps];
+         
+            bestOperation = getOptimalOperations(bayesNet, instances, nrOfLookAheadSteps, nrOfGoodOperations);
+            for (int i = 0; i < nrOfLookAheadSteps; i++) {
+               if (bestOperation [i] == null) {
+                  legalSequence=false;
+               } else {
+                  sequenceDeltaScore += bestOperation [i].m_fDeltaScore;
+               }
+            }
+            while (legalSequence && sequenceDeltaScore > 0) {
+               System.out.println("Next Iteration..........................");
+               for (int i = 0; i < nrOfLookAheadSteps; i++) {
+                  performOperation(bayesNet, instances,bestOperation [i]);
+               }
+               bestOperation = getOptimalOperations(bayesNet, instances, nrOfLookAheadSteps, nrOfGoodOperations);
+               sequenceDeltaScore = 0;
+               for (int i = 0; i < nrOfLookAheadSteps; i++) {
+                  if (bestOperation [i] != null) {
+                     System.out.println(bestOperation [i].m_nOperation + " " + bestOperation [i].m_nHead + " " + bestOperation [i].m_nTail);
+                     sequenceDeltaScore += bestOperation [i].m_fDeltaScore;
+                  } else {
+                     legalSequence = false;
+
+                  }
+                  System.out.println("DeltaScore: "+sequenceDeltaScore);
+               }
+            }
+            --nrOfLookAheadSteps;
+         }
+
+         /** last steps with greedy HC **/          
+         Operation oOperation = getOptimalOperation(bayesNet, instances);
+         while ((oOperation != null) && (oOperation.m_fDeltaScore > 0)) {
+	    performOperation(bayesNet, instances, oOperation);
+            System.out.println("Performing last greedy steps");
+            oOperation = getOptimalOperation(bayesNet, instances);
+         }               
+	 // free up memory
+	 m_Cache = null;
+    } // lookAheadInGoodDirectionsSearch
+
+    /**
+      * getAntiOperation determines the Operation, which is needed to cancel oOperation
+      * 
+      * @param oOperation Operation to cancel
+      * @return antiOperation to oOperation
+      * @throws Exception if something goes wrong
+      */
+    protected Operation getAntiOperation(Operation oOperation) throws Exception {
+        if (oOperation.m_nOperation == Operation.OPERATION_ADD)
+           return (new Operation (oOperation.m_nTail, oOperation.m_nHead, Operation.OPERATION_DEL));
+        else {
+           if (oOperation.m_nOperation == Operation.OPERATION_DEL)
+              return (new Operation (oOperation.m_nTail, oOperation.m_nHead, Operation.OPERATION_ADD));
+           else {
+              return (new Operation (oOperation.m_nHead, oOperation.m_nTail, Operation.OPERATION_REVERSE));
+           }
+         }
+    } // getAntiOperation
+
+
+    /**
+      * getGoodOperations determines the nrOfGoodOperations best Operations, which are considered for
+      * the calculation of an optimal operationsequence
+      * @param bayesNet Bayes network to apply operation on
+      * @param instances data set to learn from
+      * @param nrOfGoodOperations number of good operations to consider
+      * @return good operations to consider
+      * @throws Exception if something goes wrong
+      **/
+    protected Operation [] getGoodOperations(BayesNet bayesNet, Instances instances, int nrOfGoodOperations) throws Exception {
+		Operation [] goodOperations=new Operation [nrOfGoodOperations];
+       		for (int i = 0; i < nrOfGoodOperations; i++) {
+                   goodOperations [i] = getOptimalOperation(bayesNet, instances);
+                   if (goodOperations[i] != null) {
+                      m_Cache.put(goodOperations [i], -1E100);
+                   } else i=nrOfGoodOperations;
+                }
+                for (int i = 0; i < nrOfGoodOperations; i++) {
+                   if (goodOperations[i] != null) {
+                      if (goodOperations [i].m_nOperation!=Operation.OPERATION_REVERSE) {
+                         m_Cache.put(goodOperations [i], goodOperations [i].m_fDeltaScore);
+                      } else {
+                         m_Cache.put(goodOperations [i], goodOperations [i].m_fDeltaScore - m_Cache.m_fDeltaScoreAdd[goodOperations[i].m_nHead] [goodOperations [i].m_nTail]);
+                      }
+                   } else i=nrOfGoodOperations;
+                }
+                return goodOperations;
+    } // getGoodOperations
+
+    /**
+      * getOptimalOperations determines an optimal operationsequence in respect of the parameters 
+      * nrOfLookAheadSteps and nrOfGoodOperations
+      * @param bayesNet Bayes network to apply operation on
+      * @param instances data set to learn from
+      * @param nrOfLookAheadSteps number of lood ahead steps to use
+      * @param nrOfGoodOperations number of good operations to consider
+      * @return optimal sequence of operations in respect to nrOfLookAheadSteps and nrOfGoodOperations
+      * @throws Exception if something goes wrong
+      **/
+    protected Operation [] getOptimalOperations(BayesNet bayesNet, Instances instances, int nrOfLookAheadSteps, int nrOfGoodOperations) throws Exception {
+       if (nrOfLookAheadSteps == 1) { // Abbruch der Rekursion
+          Operation [] bestOperation = new Operation [1];
+          bestOperation [0] = getOptimalOperation(bayesNet, instances);
+          return(bestOperation);  // Abbruch der Rekursion
+       } else {
+          double bestDeltaScore = 0;
+          double currentDeltaScore = 0;
+          Operation [] bestOperation = new Operation [nrOfLookAheadSteps];
+          Operation [] goodOperations = new Operation [nrOfGoodOperations];
+          Operation [] tempOperation = new Operation [nrOfLookAheadSteps-1];
+          goodOperations = getGoodOperations(bayesNet, instances, nrOfGoodOperations);
+          for (int i = 0; i < nrOfGoodOperations; i++) {
+           if (goodOperations[i] != null) {
+             performOperation(bayesNet, instances, goodOperations [i]);
+             tempOperation = getOptimalOperations(bayesNet, instances, nrOfLookAheadSteps-1, nrOfGoodOperations); // rekursiver Abstieg
+             currentDeltaScore = goodOperations [i].m_fDeltaScore;
+             for (int j = 0; j < nrOfLookAheadSteps-1; j++) {
+                if (tempOperation [j] != null) {
+                   currentDeltaScore += tempOperation [j].m_fDeltaScore;
+                }
+             }
+             performOperation(bayesNet, instances, getAntiOperation(goodOperations [i]));
+                      if (currentDeltaScore > bestDeltaScore) {
+                        bestDeltaScore = currentDeltaScore;
+                        bestOperation [0] = goodOperations [i];
+                        for (int j = 1; j < nrOfLookAheadSteps; j++) {
+                          bestOperation [j] = tempOperation [j-1];
+                        }
+                    }
+            } else i=nrOfGoodOperations;
+           }
+           return(bestOperation);
+       }
+    } // getOptimalOperations
+
+
+	/**
+	 * Sets the max number of parents
+	 *
+	 * @param nMaxNrOfParents the max number of parents
+	 */
+	public void setMaxNrOfParents(int nMaxNrOfParents) {
+	  m_nMaxNrOfParents = nMaxNrOfParents;
+	} 
+
+	/**
+	 * Gets the max number of parents.
+	 *
+	 * @return the max number of parents
+	 */
+	public int getMaxNrOfParents() {
+	  return m_nMaxNrOfParents;
+	} 
+
+	/**
+	 * Sets the number of look-ahead steps
+	 *
+	 * @param nNrOfLookAheadSteps the number of look-ahead steps
+	 */
+	public void setNrOfLookAheadSteps(int nNrOfLookAheadSteps) {
+	  m_nNrOfLookAheadSteps = nNrOfLookAheadSteps;
+	} 
+
+	/**
+	 * Gets the number of look-ahead steps
+	 *
+	 * @return the number of look-ahead step
+	 */
+	public int getNrOfLookAheadSteps() {
+	  return m_nNrOfLookAheadSteps;
+	} 
+
+	/**
+	 * Sets the number of "good operations"
+	 *
+	 * @param nNrOfGoodOperations the number of "good operations"
+	 */
+	public void setNrOfGoodOperations(int nNrOfGoodOperations) {
+	  m_nNrOfGoodOperations = nNrOfGoodOperations;
+	} 
+
+	/**
+	 * Gets the number of "good operations"
+	 *
+	 * @return the number of "good operations"
+	 */
+	public int getNrOfGoodOperations() {
+	  return m_nNrOfGoodOperations;
+	} 
+
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector();
+
+		newVector.addElement(new Option("\tLook Ahead Depth", "L", 2, "-L <nr of look ahead steps>"));
+		newVector.addElement(new Option("\tNr of Good Operations", "G", 5, "-G <nr of good operations>"));
+
+		Enumeration enm = super.listOptions();
+		while (enm.hasMoreElements()) {
+			newVector.addElement(enm.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. Valid options are:<p>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -L &lt;nr of look ahead steps&gt;
+	 *  Look Ahead Depth</pre>
+	 * 
+	 * <pre> -G &lt;nr of good operations&gt;
+	 *  Nr of Good Operations</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+	  	String sNrOfLookAheadSteps = Utils.getOption('L', options);
+		if (sNrOfLookAheadSteps.length() != 0) {
+		  setNrOfLookAheadSteps(Integer.parseInt(sNrOfLookAheadSteps));
+		} else {
+		  setNrOfLookAheadSteps(2);
+		}
+
+                String sNrOfGoodOperations = Utils.getOption('G', options);
+		if (sNrOfGoodOperations.length() != 0) {
+		  setNrOfGoodOperations(Integer.parseInt(sNrOfGoodOperations));
+		} else {
+		  setNrOfGoodOperations(5);
+		}
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[9 + superOptions.length];
+		int current = 0;
+
+		options[current++] = "-L";
+		options[current++] = "" + m_nNrOfLookAheadSteps;
+		
+		options[current++] = "-G";
+		options[current++] = "" + m_nNrOfGoodOperations;
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return "This Bayes Network learning algorithm uses a Look Ahead Hill Climbing algorithm called LAGD Hill Climbing." +
+	  " Unlike Greedy Hill Climbing it doesn't calculate a best greedy operation (adding, deleting or reversing an arc) " + 
+	  "but a sequence of nrOfLookAheadSteps operations, which leads to a network structure whose score is most likely " +
+	  "higher in comparison to the network obtained by performing a sequence of nrOfLookAheadSteps greedy operations. " +
+	  "The search is not restricted by an order " +
+	  "on the variables (unlike K2). The difference with B and B2 is that this hill " +
+          "climber also considers arrows part of the naive Bayes structure for deletion.";
+	} // globalInfo
+
+	/**
+	 * @return a string to describe the Number of Look Ahead Steps option.
+	 */
+	public String nrOfLookAheadStepsTipText() {
+	  return "Sets the Number of Look Ahead Steps. 'nrOfLookAheadSteps = 2' means that all network structures in a " +
+	  "distance of 2 (from the current network structure) are taken into account for the decision which arcs to add, " +
+	  "remove or reverse. 'nrOfLookAheadSteps = 1' results in Greedy Hill Climbing." ;
+	} // nrOfLookAheadStepsTipText
+
+	/**
+	 * @return a string to describe the Number of Good Operations option.
+	 */
+	public String nrOfGoodOperationsTipText() {
+	  return "Sets the Number of Good Operations per Look Ahead Step. 'nrOfGoodOperations = 5' means that for the next " +
+	  "Look Ahead Step only the 5 best Operations (adding, deleting or reversing an arc) are taken into account for the " +
+	  "calculation of the best sequence consisting of nrOfLookAheadSteps operations." ;
+	} // nrOfGoodOperationsTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.7 $");
+	}
+
+} // LAGDHillClimber
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LocalScoreSearchAlgorithm.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LocalScoreSearchAlgorithm.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/LocalScoreSearchAlgorithm.java	(revision 29)
@@ -0,0 +1,720 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LocalScoreSearchAlgorithm.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.classifiers.bayes.net.search.SearchAlgorithm;
+import weka.core.Instances;
+import weka.core.Instance;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Statistics;
+import weka.core.Tag;
+import weka.core.Option;
+import weka.core.SelectedTag;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+/** 
+ <!-- globalinfo-start -->
+ * The ScoreBasedSearchAlgorithm class supports Bayes net structure search algorithms that are based on maximizing scores (as opposed to for example conditional independence based search algorithms).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert
+ * @version $Revision: 5195 $
+ */
+public class LocalScoreSearchAlgorithm 
+	extends SearchAlgorithm {
+
+  	/** for serialization */
+  	static final long serialVersionUID = 3325995552474190374L;
+  	
+	/** points to Bayes network for which a structure is searched for **/
+	BayesNet m_BayesNet;
+	
+	/**
+	 * default constructor
+	 */
+	public LocalScoreSearchAlgorithm() {
+	} // c'tor
+	
+	/**
+	 * constructor
+	 * 
+	 * @param bayesNet the network
+	 * @param instances the data
+	 */
+	public LocalScoreSearchAlgorithm(BayesNet bayesNet, Instances instances) {
+		m_BayesNet = bayesNet;
+//		m_Instances = instances;
+	} // c'tor
+	
+	/**
+	 * Holds prior on count
+	 */
+	double m_fAlpha = 0.5;
+
+	/** the score types */
+	public static final Tag[] TAGS_SCORE_TYPE = {
+	  new Tag(Scoreable.BAYES, "BAYES"),
+	  new Tag(Scoreable.BDeu, "BDeu"),
+	  new Tag(Scoreable.MDL, "MDL"),
+	  new Tag(Scoreable.ENTROPY, "ENTROPY"),
+	  new Tag(Scoreable.AIC, "AIC")
+	};
+
+	/**
+	 * Holds the score type used to measure quality of network
+	 */
+	int m_nScoreType = Scoreable.BAYES;
+
+	/**
+	 * logScore returns the log of the quality of a network
+	 * (e.g. the posterior probability of the network, or the MDL
+	 * value).
+	 * @param nType score type (Bayes, MDL, etc) to calculate score with
+	 * @return log score.
+	 */
+    public double logScore(int nType) {
+    	if (m_BayesNet.m_Distributions == null) {return 0;}
+        if (nType < 0) {
+            nType = m_nScoreType;
+        }
+
+        double fLogScore = 0.0;
+        
+        Instances instances = m_BayesNet.m_Instances;
+
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+        	int nCardinality = m_BayesNet.getParentSet(iAttribute).getCardinalityOfParents();
+            for (int iParent = 0; iParent < nCardinality; iParent++) {
+                fLogScore += ((Scoreable) m_BayesNet.m_Distributions[iAttribute][iParent]).logScore(nType, nCardinality);
+            }
+
+            switch (nType) {
+                case (Scoreable.MDL) :
+                    {
+                        fLogScore -= 0.5
+                            * m_BayesNet.getParentSet(iAttribute).getCardinalityOfParents()
+                            * (instances.attribute(iAttribute).numValues() - 1)
+                            * Math.log(instances.numInstances());
+                    }
+                    break;
+                case (Scoreable.AIC) :
+                    {
+                        fLogScore -= m_BayesNet.getParentSet(iAttribute).getCardinalityOfParents()
+                            * (instances.attribute(iAttribute).numValues() - 1);
+                    }
+                    break;
+            }
+        }
+
+        return fLogScore;
+    } // logScore
+
+	/**
+	* buildStructure determines the network structure/graph of the network
+	* with the K2 algorithm, restricted by its initial structure (which can
+	* be an empty graph, or a Naive Bayes graph.
+	* 
+	* @param bayesNet the network
+	* @param instances the data to use
+	* @throws Exception if something goes wrong
+	*/
+	public void buildStructure (BayesNet bayesNet, Instances instances) throws Exception {
+		m_BayesNet = bayesNet;
+		super.buildStructure(bayesNet, instances);
+	} // buildStructure
+
+
+	/**
+	 * Calc Node Score for given parent set
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @return log score
+	 */
+	public double calcNodeScore(int nNode) {
+		if (m_BayesNet.getUseADTree() && m_BayesNet.getADTree() != null) {
+			return calcNodeScoreADTree(nNode);
+		} else {
+			return calcNodeScorePlain(nNode);
+		}
+	}
+
+	/**
+	 * helper function for CalcNodeScore above using the ADTree data structure
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @return log score
+	 */
+	private double calcNodeScoreADTree(int nNode) {
+		Instances instances = m_BayesNet.m_Instances;
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+		// get set of parents, insert iNode
+		int nNrOfParents = oParentSet.getNrOfParents();
+		int[] nNodes = new int[nNrOfParents + 1];
+		for (int iParent = 0; iParent < nNrOfParents; iParent++) {
+			nNodes[iParent] = oParentSet.getParent(iParent);
+		}
+		nNodes[nNrOfParents] = nNode;
+
+		// calculate offsets
+		int[] nOffsets = new int[nNrOfParents + 1];
+		int nOffset = 1;
+		nOffsets[nNrOfParents] = 1;
+		nOffset *= instances.attribute(nNode).numValues();
+		for (int iNode = nNrOfParents - 1; iNode >= 0; iNode--) {
+			nOffsets[iNode] = nOffset;
+			nOffset *= instances.attribute(nNodes[iNode]).numValues();
+		}
+
+		// sort nNodes & offsets
+		for (int iNode = 1; iNode < nNodes.length; iNode++) {
+			int iNode2 = iNode;
+			while (iNode2 > 0 && nNodes[iNode2] < nNodes[iNode2 - 1]) {
+				int h = nNodes[iNode2];
+				nNodes[iNode2] = nNodes[iNode2 - 1];
+				nNodes[iNode2 - 1] = h;
+				h = nOffsets[iNode2];
+				nOffsets[iNode2] = nOffsets[iNode2 - 1];
+				nOffsets[iNode2 - 1] = h;
+				iNode2--;
+			}
+		}
+
+		// get counts from ADTree
+		int nCardinality = oParentSet.getCardinalityOfParents();
+		int numValues = instances.attribute(nNode).numValues();
+		int[] nCounts = new int[nCardinality * numValues];
+		//if (nNrOfParents > 1) {
+
+		m_BayesNet.getADTree().getCounts(nCounts, nNodes, nOffsets, 0, 0, false);
+
+		return calcScoreOfCounts(nCounts, nCardinality, numValues, instances);
+	} // CalcNodeScore
+
+	private double calcNodeScorePlain(int nNode) {
+		Instances instances = m_BayesNet.m_Instances;
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+
+		// determine cardinality of parent set & reserve space for frequency counts
+		int nCardinality = oParentSet.getCardinalityOfParents();
+		int numValues = instances.attribute(nNode).numValues();
+		int[] nCounts = new int[nCardinality * numValues];
+
+		// initialize (don't need this?)
+		for (int iParent = 0; iParent < nCardinality * numValues; iParent++) {
+			nCounts[iParent] = 0;
+		}
+
+		// estimate distributions
+		Enumeration enumInsts = instances.enumerateInstances();
+
+		while (enumInsts.hasMoreElements()) {
+			Instance instance = (Instance) enumInsts.nextElement();
+
+			// updateClassifier;
+			double iCPT = 0;
+
+			for (int iParent = 0; iParent < oParentSet.getNrOfParents(); iParent++) {
+				int nParent = oParentSet.getParent(iParent);
+
+				iCPT = iCPT * instances.attribute(nParent).numValues() + instance.value(nParent);
+			}
+
+			nCounts[numValues * ((int) iCPT) + (int) instance.value(nNode)]++;
+		}
+
+		return calcScoreOfCounts(nCounts, nCardinality, numValues, instances);
+	} // CalcNodeScore
+
+	/**
+	 * utility function used by CalcScore and CalcNodeScore to determine the score
+	 * based on observed frequencies.
+	 * 
+	 * @param nCounts array with observed frequencies
+	 * @param nCardinality ardinality of parent set
+	 * @param numValues number of values a node can take
+	 * @param instances to calc score with
+	 * @return log score
+	 */
+	protected double calcScoreOfCounts(int[] nCounts, int nCardinality, int numValues, Instances instances) {
+
+		// calculate scores using the distributions
+		double fLogScore = 0.0;
+
+		for (int iParent = 0; iParent < nCardinality; iParent++) {
+			switch (m_nScoreType) {
+
+				case (Scoreable.BAYES) :
+					{
+						double nSumOfCounts = 0;
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							if (m_fAlpha + nCounts[iParent * numValues + iSymbol] != 0) {
+								fLogScore += Statistics.lnGamma(m_fAlpha + nCounts[iParent * numValues + iSymbol]);
+								nSumOfCounts += m_fAlpha + nCounts[iParent * numValues + iSymbol];
+							}
+						}
+
+						if (nSumOfCounts != 0) {
+							fLogScore -= Statistics.lnGamma(nSumOfCounts);
+						}
+
+						if (m_fAlpha != 0) {
+							fLogScore -= numValues * Statistics.lnGamma(m_fAlpha);
+							fLogScore += Statistics.lnGamma(numValues * m_fAlpha);
+						}
+					}
+
+					break;
+                case (Scoreable.BDeu) :
+                {
+                    double nSumOfCounts = 0;
+
+                    for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+                        if (m_fAlpha + nCounts[iParent * numValues + iSymbol] != 0) {
+                            fLogScore += Statistics.lnGamma(1.0/(numValues * nCardinality) + nCounts[iParent * numValues + iSymbol]);
+                            nSumOfCounts += 1.0/(numValues * nCardinality) + nCounts[iParent * numValues + iSymbol];
+                        }
+                    }
+                    fLogScore -= Statistics.lnGamma(nSumOfCounts);
+
+                    fLogScore -= numValues * Statistics.lnGamma(1.0/(numValues * nCardinality));
+                    fLogScore += Statistics.lnGamma(1.0/nCardinality);
+                }
+	                break;
+
+				case (Scoreable.MDL) :
+
+				case (Scoreable.AIC) :
+
+				case (Scoreable.ENTROPY) :
+					{
+						double nSumOfCounts = 0;
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							nSumOfCounts += nCounts[iParent * numValues + iSymbol];
+						}
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							if (nCounts[iParent * numValues + iSymbol] > 0) {
+								fLogScore += nCounts[iParent * numValues
+									+ iSymbol] * Math.log(nCounts[iParent * numValues + iSymbol] / nSumOfCounts);
+							}
+						}
+					}
+
+					break;
+
+				default :
+					{
+					}
+			}
+		}
+
+		switch (m_nScoreType) {
+
+			case (Scoreable.MDL) :
+				{
+					fLogScore -= 0.5 * nCardinality * (numValues - 1) * Math.log(instances.numInstances());
+
+					// it seems safe to assume that numInstances>0 here
+				}
+
+				break;
+
+			case (Scoreable.AIC) :
+				{
+					fLogScore -= nCardinality * (numValues - 1);
+				}
+
+				break;
+		}
+
+		return fLogScore;
+	} // CalcNodeScore
+
+	protected double calcScoreOfCounts2(int[][] nCounts, int nCardinality, int numValues, Instances instances) {
+
+		// calculate scores using the distributions
+		double fLogScore = 0.0;
+
+		for (int iParent = 0; iParent < nCardinality; iParent++) {
+			switch (m_nScoreType) {
+
+				case (Scoreable.BAYES) :
+					{
+						double nSumOfCounts = 0;
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							if (m_fAlpha + nCounts[iParent][iSymbol] != 0) {
+								fLogScore += Statistics.lnGamma(m_fAlpha + nCounts[iParent][iSymbol]);
+								nSumOfCounts += m_fAlpha + nCounts[iParent][iSymbol];
+							}
+						}
+
+						if (nSumOfCounts != 0) {
+							fLogScore -= Statistics.lnGamma(nSumOfCounts);
+						}
+
+						if (m_fAlpha != 0) {
+							fLogScore -= numValues * Statistics.lnGamma(m_fAlpha);
+							fLogScore += Statistics.lnGamma(numValues * m_fAlpha);
+						}
+					}
+
+					break;
+
+				case (Scoreable.BDeu) :
+				{
+					double nSumOfCounts = 0;
+
+					for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+						if (m_fAlpha + nCounts[iParent][iSymbol] != 0) {
+							fLogScore += Statistics.lnGamma(1.0/(numValues * nCardinality) + nCounts[iParent][iSymbol]);
+							nSumOfCounts += 1.0/(numValues * nCardinality) + nCounts[iParent][iSymbol];
+						}
+					}
+					fLogScore -= Statistics.lnGamma(nSumOfCounts);
+
+					fLogScore -= numValues * Statistics.lnGamma(1.0/(nCardinality * numValues));
+					fLogScore += Statistics.lnGamma(1.0/ nCardinality);
+				}
+					break;
+
+				case (Scoreable.MDL) :
+
+				case (Scoreable.AIC) :
+
+				case (Scoreable.ENTROPY) :
+					{
+						double nSumOfCounts = 0;
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							nSumOfCounts += nCounts[iParent][iSymbol];
+						}
+
+						for (int iSymbol = 0; iSymbol < numValues; iSymbol++) {
+							if (nCounts[iParent][iSymbol] > 0) {
+								fLogScore += nCounts[iParent][iSymbol]
+									* Math.log(nCounts[iParent][iSymbol] / nSumOfCounts);
+							}
+						}
+					}
+
+					break;
+
+				default :
+					{
+					}
+			}
+		}
+
+		switch (m_nScoreType) {
+
+			case (Scoreable.MDL) :
+				{
+					fLogScore -= 0.5 * nCardinality * (numValues - 1) * Math.log(instances.numInstances());
+
+					// it seems safe to assume that numInstances>0 here
+				}
+
+				break;
+
+			case (Scoreable.AIC) :
+				{
+					fLogScore -= nCardinality * (numValues - 1);
+				}
+
+				break;
+		}
+
+		return fLogScore;
+	} // CalcNodeScore
+
+
+	/**
+	 * Calc Node Score With AddedParent
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @param nCandidateParent candidate parent to add to the existing parent set
+	 * @return log score
+	 */
+	public double calcScoreWithExtraParent(int nNode, int nCandidateParent) {
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+
+		// sanity check: nCandidateParent should not be in parent set already
+		if (oParentSet.contains(nCandidateParent)) {
+				return -1e100;
+		}
+
+		// set up candidate parent
+		oParentSet.addParent(nCandidateParent, m_BayesNet.m_Instances);
+
+		// calculate the score
+		double logScore = calcNodeScore(nNode);
+
+		// delete temporarily added parent
+		oParentSet.deleteLastParent(m_BayesNet.m_Instances);
+
+		return logScore;
+	} // CalcScoreWithExtraParent
+
+
+	/**
+	 * Calc Node Score With Parent Deleted
+	 * 
+	 * @param nNode node for which the score is calculate
+	 * @param nCandidateParent candidate parent to delete from the existing parent set
+	 * @return log score
+	 */
+	public double calcScoreWithMissingParent(int nNode, int nCandidateParent) {
+		ParentSet oParentSet = m_BayesNet.getParentSet(nNode);
+
+		// sanity check: nCandidateParent should be in parent set already
+		if (!oParentSet.contains( nCandidateParent)) {
+				return -1e100;
+		}
+
+		// set up candidate parent
+		int iParent = oParentSet.deleteParent(nCandidateParent, m_BayesNet.m_Instances);
+
+		// calculate the score
+		double logScore = calcNodeScore(nNode);
+
+		// restore temporarily deleted parent
+		oParentSet.addParent(nCandidateParent, iParent, m_BayesNet.m_Instances);
+
+		return logScore;
+	} // CalcScoreWithMissingParent
+
+	/**
+	 * set quality measure to be used in searching for networks.
+	 * 
+	 * @param newScoreType the new score type
+	 */
+	public void setScoreType(SelectedTag newScoreType) {
+		if (newScoreType.getTags() == TAGS_SCORE_TYPE) {
+			m_nScoreType = newScoreType.getSelectedTag().getID();
+		}
+	}
+
+	/**
+	 * get quality measure to be used in searching for networks.
+	 * @return quality measure
+	 */
+	public SelectedTag getScoreType() {
+		return new SelectedTag(m_nScoreType, TAGS_SCORE_TYPE);
+	}
+
+	/**
+	 * 
+	 * @param bMarkovBlanketClassifier
+	 */
+	public void setMarkovBlanketClassifier(boolean bMarkovBlanketClassifier) {
+	  super.setMarkovBlanketClassifier(bMarkovBlanketClassifier);
+	}
+	
+	/**
+	 * 
+	 * @return
+	 */
+	public boolean getMarkovBlanketClassifier() {
+	  return super.getMarkovBlanketClassifier();
+	}
+
+	/**
+	 * Returns an enumeration describing the available options
+	 * 
+	 * @return an enumeration of all the available options
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector();
+
+		newVector.addElement(new Option(
+		    "\tApplies a Markov Blanket correction to the network structure, \n"
+		  + "\tafter a network structure is learned. This ensures that all \n"
+		  + "\tnodes in the network are part of the Markov blanket of the \n"
+		  + "\tclassifier node.",
+		  "mbc", 0, "-mbc"));
+      
+		newVector.addElement(
+			new Option(
+				"\tScore type (BAYES, BDeu, MDL, ENTROPY and AIC)",
+				"S",
+				1,
+				"-S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]"));
+
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+
+	  	setMarkovBlanketClassifier(Utils.getFlag("mbc", options));
+
+		String sScore = Utils.getOption('S', options);
+
+		if (sScore.compareTo("BAYES") == 0) {
+			setScoreType(new SelectedTag(Scoreable.BAYES, TAGS_SCORE_TYPE));
+		}
+		if (sScore.compareTo("BDeu") == 0) {
+			setScoreType(new SelectedTag(Scoreable.BDeu, TAGS_SCORE_TYPE));
+		}
+		if (sScore.compareTo("MDL") == 0) {
+			setScoreType(new SelectedTag(Scoreable.MDL, TAGS_SCORE_TYPE));
+		}
+		if (sScore.compareTo("ENTROPY") == 0) {
+			setScoreType(new SelectedTag(Scoreable.ENTROPY, TAGS_SCORE_TYPE));
+		}
+		if (sScore.compareTo("AIC") == 0) {
+			setScoreType(new SelectedTag(Scoreable.AIC, TAGS_SCORE_TYPE));
+		}
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+                String[] superOptions = super.getOptions();
+		String[] options = new String[3 + superOptions.length];
+		int current = 0;
+
+		if (getMarkovBlanketClassifier())
+		  options[current++] = "-mbc";
+
+		options[current++] = "-S";
+
+		switch (m_nScoreType) {
+
+			case (Scoreable.BAYES) :
+				options[current++] = "BAYES";
+				break;
+
+			case (Scoreable.BDeu) :
+				options[current++] = "BDeu";
+				break;
+
+			case (Scoreable.MDL) :
+				options[current++] = "MDL";
+				break;
+
+			case (Scoreable.ENTROPY) :
+				options[current++] = "ENTROPY";
+
+				break;
+
+			case (Scoreable.AIC) :
+				options[current++] = "AIC";
+				break;
+		}
+
+                // insert options from parent class
+                for (int iOption = 0; iOption < superOptions.length; iOption++) {
+                        options[current++] = superOptions[iOption];
+                }
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+
+		return options;
+	} // getOptions
+
+	/**
+	 * @return a string to describe the ScoreType option.
+	 */
+	public String scoreTypeTipText() {
+		return "The score type determines the measure used to judge the quality of a"
+			+ " network structure. It can be one of Bayes, BDeu, Minimum Description Length (MDL),"
+			+ " Akaike Information Criterion (AIC), and Entropy.";
+	}
+	
+	/**
+	 * @return a string to describe the MarkovBlanketClassifier option.
+	 */
+	public String markovBlanketClassifierTipText() {
+	  return super.markovBlanketClassifierTipText();
+	}
+
+	/**
+	 * This will return a string describing the search algorithm.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+	  return 
+	      "The ScoreBasedSearchAlgorithm class supports Bayes net "
+	    + "structure search algorithms that are based on maximizing "
+	    + "scores (as opposed to for example conditional independence "
+	    + "based search algorithms).";
+	} // globalInfo
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 5195 $");
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/RepeatedHillClimber.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/RepeatedHillClimber.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/RepeatedHillClimber.java	(revision 29)
@@ -0,0 +1,354 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RepeatedHillClimber.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.classifiers.bayes.net.ParentSet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm repeatedly uses hill climbing starting with a randomly generated network structure and return the best structure of the various runs.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -A &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class RepeatedHillClimber 
+    extends HillClimber {
+  
+    /** for serialization */
+    static final long serialVersionUID = -6574084564213041174L;
+
+    /** number of runs **/
+    int m_nRuns = 10;
+    /** random number seed **/
+    int m_nSeed = 1;
+    /** random number generator **/
+    Random m_random;
+
+	/**
+	 * search determines the network structure/graph of the network
+	 * with the repeated hill climbing.
+	 * 
+	 * @param bayesNet the network
+	 * @param instances the data to use
+	 * @throws Exception if something goes wrong
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+		m_random = new Random(getSeed());
+		// keeps track of score pf best structure found so far 
+		double fBestScore;	
+		double fCurrentScore = 0.0;
+		for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+			fCurrentScore += calcNodeScore(iAttribute);
+		}
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+
+		// initialize bestBayesNet
+		fBestScore = fCurrentScore;
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+        	// generate random nework
+        	generateRandomNet(bayesNet, instances);
+
+        	// search
+        	super.search(bayesNet, instances);
+
+			// calculate score
+			fCurrentScore = 0.0;
+			for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+				fCurrentScore += calcNodeScore(iAttribute);
+			}
+
+			// keep track of best network seen so far
+			if (fCurrentScore > fBestScore) {
+				fBestScore = fCurrentScore;
+				copyParentSets(bestBayesNet, bayesNet);
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+		m_Cache = null;
+    } // search
+
+	void generateRandomNet(BayesNet bayesNet, Instances instances) {
+		int nNodes = instances.numAttributes();
+		// clear network
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			ParentSet parentSet = bayesNet.getParentSet(iNode);
+			while (parentSet.getNrOfParents() > 0) {
+				parentSet.deleteLastParent(instances);
+			}
+		}
+		
+		// initialize as naive Bayes?
+		if (getInitAsNaiveBayes()) {
+			int iClass = instances.classIndex();
+			// initialize parent sets to have arrow from classifier node to
+			// each of the other nodes
+			for (int iNode = 0; iNode < nNodes; iNode++) {
+				if (iNode != iClass) {
+					bayesNet.getParentSet(iNode).addParent(iClass, instances);
+				}
+			}
+		}
+
+		// insert random arcs
+		int nNrOfAttempts = m_random.nextInt(nNodes * nNodes);
+		for (int iAttempt = 0; iAttempt < nNrOfAttempts; iAttempt++) {
+			int iTail = m_random.nextInt(nNodes);
+			int iHead = m_random.nextInt(nNodes);
+			if (bayesNet.getParentSet(iHead).getNrOfParents() < getMaxNrOfParents() &&
+			    addArcMakesSense(bayesNet, instances, iHead, iTail)) {
+					bayesNet.getParentSet(iHead).addParent(iTail, instances);
+			}
+		}
+	} // generateRandomNet
+
+	/** 
+	 * copyParentSets copies parent sets of source to dest BayesNet
+	 * 
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+
+    /**
+    * @return number of runs
+    */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+	/**
+	* @return random number seed
+	*/
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(4);
+
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tRandom number seed", "A", 1, "-A <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -A &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		
+		String sSeed = Utils.getOption('A', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-A";
+		options[current++] = "" + getSeed();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm repeatedly uses hill climbing starting " +
+		"with a randomly generated network structure and return the best structure of the " +
+		"various runs.";
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of times hill climbing is performed.";
+	} // runsTipText
+
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.6 $");
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/Scoreable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/Scoreable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/Scoreable.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scoreable.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+package weka.classifiers.bayes.net.search.local;
+
+/**
+ * Interface for allowing to score a classifier
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.4 $
+ */
+public interface Scoreable {
+
+  /**
+   * score types
+   */
+  int BAYES = 0;
+  int BDeu = 1;
+  int MDL = 2;
+  int ENTROPY = 3;
+  int AIC = 4;
+
+  /**
+   * Returns log-score
+   * 
+   * @param nType the score type
+   * @return the log-score
+   */
+  double logScore(int nType, int nCardinality);
+}    // interface Scoreable
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/SimulatedAnnealing.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/SimulatedAnnealing.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/SimulatedAnnealing.java	(revision 29)
@@ -0,0 +1,442 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimulatedAnnealing.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses the general purpose search method of simulated annealing to find a well scoring network structure.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * R.R. Bouckaert (1995). Bayesian Belief Networks: from Construction to Inference. Utrecht, Netherlands.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Bouckaert1995,
+ *    address = {Utrecht, Netherlands},
+ *    author = {R.R. Bouckaert},
+ *    institution = {University of Utrecht},
+ *    title = {Bayesian Belief Networks: from Construction to Inference},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;float&gt;
+ *  Start temperature</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -D &lt;float&gt;
+ *  Delta temperature</pre>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Random number seed</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.6 $
+ */
+public class SimulatedAnnealing 
+	extends LocalScoreSearchAlgorithm
+	implements TechnicalInformationHandler {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = 6951955606060513191L;
+
+    	/** start temperature **/
+	double m_fTStart = 10;
+
+	/** change in temperature at every run **/
+	double m_fDelta = 0.999;
+
+	/** number of runs **/
+	int m_nRuns = 10000;
+
+	/** use the arc reversal operator **/
+	boolean m_bUseArcReversal = false;
+
+	/** random number seed **/
+	int m_nSeed = 1;
+
+	/** random number generator **/
+	Random m_random;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  
+	  result = new TechnicalInformation(Type.PHDTHESIS);
+	  result.setValue(Field.AUTHOR, "R.R. Bouckaert");
+	  result.setValue(Field.YEAR, "1995");
+	  result.setValue(Field.TITLE, "Bayesian Belief Networks: from Construction to Inference");
+	  result.setValue(Field.INSTITUTION, "University of Utrecht");
+	  result.setValue(Field.ADDRESS, "Utrecht, Netherlands");
+	  
+	  return result;
+	}
+
+    /**
+     * 
+     * @param bayesNet the network
+     * @param instances the data to use
+     * @throws Exception if something goes wrong
+     */
+    public void search (BayesNet bayesNet, Instances instances) throws Exception {
+		m_random = new Random(m_nSeed);
+		
+        // determine base scores
+        double [] fBaseScores = new double [instances.numAttributes()];
+		double fCurrentScore = 0;
+        for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+            fBaseScores[iAttribute] = calcNodeScore(iAttribute);
+			fCurrentScore += fBaseScores[iAttribute];
+        }
+
+		// keep track of best scoring network
+		double fBestScore = fCurrentScore;
+		BayesNet bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+
+        double fTemp = m_fTStart;
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+            boolean bRunSucces = false;
+            double fDeltaScore = 0.0;
+            while (!bRunSucces) {
+	            // pick two nodes at random
+	            int iTailNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            int iHeadNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            while (iTailNode == iHeadNode) {
+		            iHeadNode = Math.abs(m_random.nextInt()) % instances.numAttributes();
+	            }
+	            if (isArc(bayesNet, iHeadNode, iTailNode)) {
+                    bRunSucces = true;
+	                // either try a delete
+                    bayesNet.getParentSet(iHeadNode).deleteParent(iTailNode, instances);
+                    double fScore = calcNodeScore(iHeadNode);
+                    fDeltaScore = fScore - fBaseScores[iHeadNode];
+//System.out.println("Try delete " + iTailNode + "->" + iHeadNode + " dScore = " + fDeltaScore);                    
+                    if (fTemp * Math.log((Math.abs(m_random.nextInt()) % 10000)/10000.0  + 1e-100) < fDeltaScore) {
+//System.out.println("success!!!");                    
+						fCurrentScore += fDeltaScore;
+                        fBaseScores[iHeadNode] = fScore;
+                    } else {
+                        // roll back
+                        bayesNet.getParentSet(iHeadNode).addParent(iTailNode, instances);
+                    }
+	            } else {
+	                // try to add an arc
+	                if (addArcMakesSense(bayesNet, instances, iHeadNode, iTailNode)) {
+                        bRunSucces = true;
+                        double fScore = calcScoreWithExtraParent(iHeadNode, iTailNode);
+                        fDeltaScore = fScore - fBaseScores[iHeadNode];
+//System.out.println("Try add " + iTailNode + "->" + iHeadNode + " dScore = " + fDeltaScore);                    
+                        if (fTemp * Math.log((Math.abs(m_random.nextInt()) % 10000)/10000.0  + 1e-100) < fDeltaScore) {
+//System.out.println("success!!!");                    
+                            bayesNet.getParentSet(iHeadNode).addParent(iTailNode, instances);
+                            fBaseScores[iHeadNode] = fScore;
+							fCurrentScore += fDeltaScore;
+                        }
+	                }
+	            }
+            }
+			if (fCurrentScore > fBestScore) {
+				copyParentSets(bestBayesNet, bayesNet);				
+			}
+            fTemp = fTemp * m_fDelta;
+        }
+
+		copyParentSets(bayesNet, bestBayesNet);
+    } // buildStructure 
+	
+	/** CopyParentSets copies parent sets of source to dest BayesNet
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+    /**
+     * @return double
+     */
+    public double getDelta() {
+        return m_fDelta;
+    }
+
+    /**
+     * @return double
+     */
+    public double getTStart() {
+        return m_fTStart;
+    }
+
+    /**
+     * @return int
+     */
+    public int getRuns() {
+        return m_nRuns;
+    }
+
+    /**
+     * Sets the m_fDelta.
+     * @param fDelta The m_fDelta to set
+     */
+    public void setDelta(double fDelta) {
+        m_fDelta = fDelta;
+    }
+
+    /**
+     * Sets the m_fTStart.
+     * @param fTStart The m_fTStart to set
+     */
+    public void setTStart(double fTStart) {
+        m_fTStart = fTStart;
+    }
+
+    /**
+     * Sets the m_nRuns.
+     * @param nRuns The m_nRuns to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    }
+
+	/**
+	* @return random number seed
+	*/
+	public int getSeed() {
+		return m_nSeed;
+	} // getSeed
+
+	/**
+	 * Sets the random number seed
+	 * @param nSeed The number of the seed to set
+	 */
+	public void setSeed(int nSeed) {
+		m_nSeed = nSeed;
+	} // setSeed
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(3);
+
+		newVector.addElement(new Option("\tStart temperature", "A", 1, "-A <float>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tDelta temperature", "D", 1, "-D <float>"));
+		newVector.addElement(new Option("\tRandom number seed", "R", 1, "-R <seed>"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	}
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -A &lt;float&gt;
+	 *  Start temperature</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -D &lt;float&gt;
+	 *  Delta temperature</pre>
+	 * 
+	 * <pre> -R &lt;seed&gt;
+	 *  Random number seed</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sTStart = Utils.getOption('A', options);
+		if (sTStart.length() != 0) {
+			setTStart(Double.parseDouble(sTStart));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		String sDelta = Utils.getOption('D', options);
+		if (sDelta.length() != 0) {
+			setDelta(Double.parseDouble(sDelta));
+		}
+		String sSeed = Utils.getOption('R', options);
+		if (sSeed.length() != 0) {
+			setSeed(Integer.parseInt(sSeed));
+		}
+		super.setOptions(options);
+	}
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[8 + superOptions.length];
+		int current = 0;
+		options[current++] = "-A";
+		options[current++] = "" + getTStart();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		options[current++] = "-D";
+		options[current++] = "" + getDelta();
+
+		options[current++] = "-R";
+		options[current++] = "" + getSeed();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	}
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return 
+		    "This Bayes Network learning algorithm uses the general purpose search method "
+		  + "of simulated annealing to find a well scoring network structure.\n\n"
+		  + "For more information see:\n\n"
+		  + getTechnicalInformation().toString();
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the TStart option.
+	 */
+	public String TStartTipText() {
+	  return "Sets the start temperature of the simulated annealing search. "+
+	  "The start temperature determines the probability that a step in the 'wrong' direction in the " +
+	  "search space is accepted. The higher the temperature, the higher the probability of acceptance.";
+	} // TStartTipText
+
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of iterations to be performed by the simulated annealing search.";
+	} // runsTipText
+	
+	/**
+	 * @return a string to describe the Delta option.
+	 */
+	public String deltaTipText() {
+	  return "Sets the factor with which the temperature (and thus the acceptance probability of " +
+	  	"steps in the wrong direction in the search space) is decreased in each iteration.";
+	} // deltaTipText
+
+	/**
+	 * @return a string to describe the Seed option.
+	 */
+	public String seedTipText() {
+	  return "Initialization value for random number generator." +
+	  " Setting the seed allows replicability of experiments.";
+	} // seedTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.6 $");
+	}
+
+} // SimulatedAnnealing
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TAN.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TAN.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TAN.java	(revision 29)
@@ -0,0 +1,288 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TAN.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+
+import java.util.Enumeration;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm determines the maximum weight spanning tree  and returns a Naive Bayes network augmented with a tree.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * N. Friedman, D. Geiger, M. Goldszmidt (1997). Bayesian network classifiers. Machine Learning. 29(2-3):131-163.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Friedman1997,
+ *    author = {N. Friedman and D. Geiger and M. Goldszmidt},
+ *    journal = {Machine Learning},
+ *    number = {2-3},
+ *    pages = {131-163},
+ *    title = {Bayesian network classifiers},
+ *    volume = {29},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Remco Bouckaert
+ * @version $Revision: 5333 $
+ */
+public class TAN 
+	extends LocalScoreSearchAlgorithm
+	implements TechnicalInformationHandler {
+  
+  	/** for serialization */
+  	static final long serialVersionUID = 965182127977228690L;
+
+  	/**
+  	 * Returns an instance of a TechnicalInformation object, containing 
+  	 * detailed information about the technical background of this class,
+  	 * e.g., paper reference or book this class is based on.
+  	 * 
+  	 * @return the technical information about this class
+  	 */
+  	public TechnicalInformation getTechnicalInformation() {
+  	  TechnicalInformation 	result;
+  	  
+  	  result = new TechnicalInformation(Type.ARTICLE);
+  	  result.setValue(Field.AUTHOR, "N. Friedman and D. Geiger and M. Goldszmidt");
+  	  result.setValue(Field.YEAR, "1997");
+  	  result.setValue(Field.TITLE, "Bayesian network classifiers");
+  	  result.setValue(Field.JOURNAL, "Machine Learning");
+  	  result.setValue(Field.VOLUME, "29");
+  	  result.setValue(Field.NUMBER, "2-3");
+  	  result.setValue(Field.PAGES, "131-163");
+  	  
+  	  return result;
+  	}
+
+	/**
+	 * buildStructure determines the network structure/graph of the network
+	 * using the maximimum weight spanning tree algorithm of Chow and Liu
+	 * 
+	 * @param bayesNet the network
+	 * @param instances the data to use
+	 * @throws Exception if something goes wrong
+	 */
+	public void buildStructure(BayesNet bayesNet, Instances instances) throws Exception {
+	  
+		m_bInitAsNaiveBayes = true;
+		m_nMaxNrOfParents = 2;
+		super.buildStructure(bayesNet, instances);
+		int      nNrOfAtts = instances.numAttributes();
+		
+	        if (nNrOfAtts <= 1) {
+	            return;
+	        }
+
+		// determine base scores
+		double[] fBaseScores = new double[instances.numAttributes()];
+
+		for (int iAttribute = 0; iAttribute < nNrOfAtts; iAttribute++) {
+		  fBaseScores[iAttribute] = calcNodeScore(iAttribute);
+		} 
+
+		//		// cache scores & whether adding an arc makes sense
+		double[][]  fScore = new double[nNrOfAtts][nNrOfAtts];
+
+		for (int iAttributeHead = 0; iAttributeHead < nNrOfAtts; iAttributeHead++) {
+			for (int iAttributeTail = 0; iAttributeTail < nNrOfAtts; iAttributeTail++) {
+				if (iAttributeHead != iAttributeTail) {
+					fScore[iAttributeHead][iAttributeTail] = calcScoreWithExtraParent(iAttributeHead, iAttributeTail);
+				}
+			} 
+		}
+		
+		// TAN greedy search (not restricted by ordering like K2)
+		// 1. find strongest link
+		// 2. find remaining links by adding strongest link to already
+		//    connected nodes
+		// 3. assign direction to links
+		int nClassNode = instances.classIndex();
+		int [] link1 = new int [nNrOfAtts - 1];
+		int [] link2 = new int [nNrOfAtts - 1];
+		boolean [] linked = new boolean [nNrOfAtts];
+
+		// 1. find strongest link
+		int    nBestLinkNode1 = -1;
+		int    nBestLinkNode2 = -1;
+		double fBestDeltaScore = 0.0;
+		int iLinkNode1;
+		for (iLinkNode1 = 0; iLinkNode1 < nNrOfAtts; iLinkNode1++) {
+			if (iLinkNode1 != nClassNode) {
+			for (int iLinkNode2 = 0; iLinkNode2 < nNrOfAtts; iLinkNode2++) {
+				if ((iLinkNode1 != iLinkNode2) &&
+				    (iLinkNode2 != nClassNode) && (
+				    (nBestLinkNode1 == -1) || (fScore[iLinkNode1][iLinkNode2] - fBaseScores[iLinkNode1] > fBestDeltaScore)
+				)) {
+					fBestDeltaScore = fScore[iLinkNode1][iLinkNode2] - fBaseScores[iLinkNode1];
+					nBestLinkNode1 = iLinkNode2;
+					nBestLinkNode2 = iLinkNode1;
+			    } 
+			} 
+			}
+		}
+		link1[0] = nBestLinkNode1;
+		link2[0] = nBestLinkNode2;
+		linked[nBestLinkNode1] = true;
+		linked[nBestLinkNode2] = true;
+	
+		// 2. find remaining links by adding strongest link to already
+		//    connected nodes
+		for (int iLink = 1; iLink < nNrOfAtts - 2; iLink++) {
+			nBestLinkNode1 = -1;
+			for (iLinkNode1 = 0; iLinkNode1 < nNrOfAtts; iLinkNode1++) {
+				if (iLinkNode1 != nClassNode) {
+				for (int iLinkNode2 = 0; iLinkNode2 < nNrOfAtts; iLinkNode2++) {
+					if ((iLinkNode1 != iLinkNode2) &&
+					    (iLinkNode2 != nClassNode) && 
+					(linked[iLinkNode1] || linked[iLinkNode2]) &&
+					(!linked[iLinkNode1] || !linked[iLinkNode2]) &&
+					(
+					(nBestLinkNode1 == -1) || (fScore[iLinkNode1][iLinkNode2] - fBaseScores[iLinkNode1] > fBestDeltaScore)
+					)) {
+						fBestDeltaScore = fScore[iLinkNode1][iLinkNode2] - fBaseScores[iLinkNode1];
+						nBestLinkNode1 = iLinkNode2;
+						nBestLinkNode2 = iLinkNode1;
+					} 
+				} 
+				}
+			}
+
+			link1[iLink] = nBestLinkNode1;
+			link2[iLink] = nBestLinkNode2;
+			linked[nBestLinkNode1] = true;
+			linked[nBestLinkNode2] = true;
+		}
+		
+		// 3. assign direction to links
+		boolean [] hasParent = new boolean [nNrOfAtts];
+		for (int iLink = 0; iLink < nNrOfAtts - 2; iLink++) {
+			if (!hasParent[link1[iLink]]) {
+				bayesNet.getParentSet(link1[iLink]).addParent(link2[iLink], instances);
+				hasParent[link1[iLink]] = true;
+			} else {
+				if (hasParent[link2[iLink]]) {
+					throw new Exception("Bug condition found: too many arrows");
+				}
+				bayesNet.getParentSet(link2[iLink]).addParent(link1[iLink], instances);
+				hasParent[link2[iLink]] = true;
+			}
+		}
+
+	} // buildStructure
+
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		return super.listOptions();
+	} // listOption
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 * 
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		super.setOptions(options);
+	} // setOptions
+	
+	/**
+	 * Gets the current settings of the Classifier.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String [] getOptions() {
+		return super.getOptions();
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return 
+		    "This Bayes Network learning algorithm determines the maximum weight spanning tree "
+		  + " and returns a Naive Bayes network augmented with a tree.\n\n"
+		  + "For more information see:\n\n"
+		  + getTechnicalInformation().toString();
+	} // globalInfo
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 5333 $");
+	}
+
+} // TAN
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TabuSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TabuSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/bayes/net/search/local/TabuSearch.java	(revision 29)
@@ -0,0 +1,415 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TabuSearch.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+ 
+package weka.classifiers.bayes.net.search.local;
+
+import weka.classifiers.bayes.BayesNet;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This Bayes Network learning algorithm uses tabu search for finding a well scoring Bayes network structure. Tabu search is hill climbing till an optimum is reached. The following step is the least worst possible step. The last X steps are kept in a list and none of the steps in this so called tabu list is considered in taking the next step. The best network found in this traversal is returned.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * R.R. Bouckaert (1995). Bayesian Belief Networks: from Construction to Inference. Utrecht, Netherlands.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Bouckaert1995,
+ *    address = {Utrecht, Netherlands},
+ *    author = {R.R. Bouckaert},
+ *    institution = {University of Utrecht},
+ *    title = {Bayesian Belief Networks: from Construction to Inference},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;integer&gt;
+ *  Tabu list length</pre>
+ * 
+ * <pre> -U &lt;integer&gt;
+ *  Number of runs</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -P &lt;nr of parents&gt;
+ *  Maximum number of parents</pre>
+ * 
+ * <pre> -R
+ *  Use arc reversal operation.
+ *  (default false)</pre>
+ * 
+ * <pre> -N
+ *  Initial structure is empty (instead of Naive Bayes)</pre>
+ * 
+ * <pre> -mbc
+ *  Applies a Markov Blanket correction to the network structure, 
+ *  after a network structure is learned. This ensures that all 
+ *  nodes in the network are part of the Markov blanket of the 
+ *  classifier node.</pre>
+ * 
+ * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+ *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz)
+ * @version $Revision: 1.5 $
+ */
+public class TabuSearch 
+    extends HillClimber
+    implements TechnicalInformationHandler {
+  
+    /** for serialization */
+    static final long serialVersionUID = 1457344073228786447L;
+
+    /** number of runs **/
+    int m_nRuns = 10;
+	    	
+	/** size of tabu list **/
+	int m_nTabuList = 5;
+
+	/** the actual tabu list **/
+	Operation[] m_oTabuList = null;
+
+	/**
+	 * Returns an instance of a TechnicalInformation object, containing 
+	 * detailed information about the technical background of this class,
+	 * e.g., paper reference or book this class is based on.
+	 * 
+	 * @return the technical information about this class
+	 */
+	public TechnicalInformation getTechnicalInformation() {
+	  TechnicalInformation 	result;
+	  
+	  result = new TechnicalInformation(Type.PHDTHESIS);
+	  result.setValue(Field.AUTHOR, "R.R. Bouckaert");
+	  result.setValue(Field.YEAR, "1995");
+	  result.setValue(Field.TITLE, "Bayesian Belief Networks: from Construction to Inference");
+	  result.setValue(Field.INSTITUTION, "University of Utrecht");
+	  result.setValue(Field.ADDRESS, "Utrecht, Netherlands");
+	  
+	  return result;
+	}
+
+	/**
+ 	 * search determines the network structure/graph of the network
+ 	 * with the Tabu search algorithm.
+ 	 * 
+ 	 * @param bayesNet the network
+ 	 * @param instances the data to use
+ 	 * @throws Exception if something goes wrong
+	 */
+	protected void search(BayesNet bayesNet, Instances instances) throws Exception {
+        m_oTabuList = new Operation[m_nTabuList];
+        int iCurrentTabuList = 0;
+        initCache(bayesNet, instances);
+
+		// keeps track of score pf best structure found so far 
+		double fBestScore;	
+		double fCurrentScore = 0.0;
+		for (int iAttribute = 0; iAttribute < instances.numAttributes(); iAttribute++) {
+			fCurrentScore += calcNodeScore(iAttribute);
+		}
+
+		// keeps track of best structure found so far 
+		BayesNet bestBayesNet;
+
+		// initialize bestBayesNet
+		fBestScore = fCurrentScore;
+		bestBayesNet = new BayesNet();
+		bestBayesNet.m_Instances = instances;
+		bestBayesNet.initStructure();
+		copyParentSets(bestBayesNet, bayesNet);
+		
+                
+        // go do the search        
+        for (int iRun = 0; iRun < m_nRuns; iRun++) {
+            Operation oOperation = getOptimalOperation(bayesNet, instances);
+			performOperation(bayesNet, instances, oOperation);
+            // sanity check
+            if (oOperation  == null) {
+				throw new Exception("Panic: could not find any step to make. Tabu list too long?");
+            }
+            // update tabu list
+            m_oTabuList[iCurrentTabuList] = oOperation;
+            iCurrentTabuList = (iCurrentTabuList + 1) % m_nTabuList;
+
+			fCurrentScore += oOperation.m_fDeltaScore;
+			// keep track of best network seen so far
+			if (fCurrentScore > fBestScore) {
+				fBestScore = fCurrentScore;
+				copyParentSets(bestBayesNet, bayesNet);
+			}
+
+			if (bayesNet.getDebug()) {
+				printTabuList();
+			}
+        }
+        
+        // restore current network to best network
+		copyParentSets(bayesNet, bestBayesNet);
+		
+		// free up memory
+		bestBayesNet = null;
+		m_Cache = null;
+    } // search
+
+
+	/** 
+	 * copyParentSets copies parent sets of source to dest BayesNet
+	 * 
+	 * @param dest destination network
+	 * @param source source network
+	 */
+	void copyParentSets(BayesNet dest, BayesNet source) {
+		int nNodes = source.getNrOfNodes();
+		// clear parent set first
+		for (int iNode = 0; iNode < nNodes; iNode++) {
+			dest.getParentSet(iNode).copy(source.getParentSet(iNode));
+		}		
+	} // CopyParentSets
+
+	/** 
+	 * check whether the operation is not in the tabu list
+	 * 
+	 * @param oOperation operation to be checked
+	 * @return true if operation is not in the tabu list
+	 */
+	boolean isNotTabu(Operation oOperation) {
+		for (int iTabu = 0; iTabu < m_nTabuList; iTabu++) {
+			if (oOperation.equals(m_oTabuList[iTabu])) {
+					return false;
+				}
+		}
+		return true;
+	} // isNotTabu
+
+	/** print tabu list for debugging purposes.
+	 */
+	void printTabuList() {
+		for (int i = 0; i < m_nTabuList; i++) {
+			Operation o = m_oTabuList[i];
+			if (o != null) {
+				if (o.m_nOperation == 0) {System.out.print(" +(");} else {System.out.print(" -(");}
+				System.out.print(o.m_nTail + "->" + o.m_nHead + ")");
+			}
+		}
+		System.out.println();
+	} // printTabuList
+
+    /**
+    * @return number of runs
+    */
+    public int getRuns() {
+        return m_nRuns;
+    } // getRuns
+
+    /**
+     * Sets the number of runs
+     * @param nRuns The number of runs to set
+     */
+    public void setRuns(int nRuns) {
+        m_nRuns = nRuns;
+    } // setRuns
+
+    /**
+     * @return the Tabu List length
+     */
+    public int getTabuList() {
+        return m_nTabuList;
+    } // getTabuList
+
+    /**
+     * Sets the Tabu List length.
+     * @param nTabuList The nTabuList to set
+     */
+    public void setTabuList(int nTabuList) {
+        m_nTabuList = nTabuList;
+    } // setTabuList
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 *
+	 * @return an enumeration of all the available options.
+	 */
+	public Enumeration listOptions() {
+		Vector newVector = new Vector(4);
+
+		newVector.addElement(new Option("\tTabu list length", "L", 1, "-L <integer>"));
+		newVector.addElement(new Option("\tNumber of runs", "U", 1, "-U <integer>"));
+		newVector.addElement(new Option("\tMaximum number of parents", "P", 1, "-P <nr of parents>"));
+		newVector.addElement(new Option("\tUse arc reversal operation.\n\t(default false)", "R", 0, "-R"));
+
+		Enumeration enu = super.listOptions();
+		while (enu.hasMoreElements()) {
+			newVector.addElement(enu.nextElement());
+		}
+		return newVector.elements();
+	} // listOptions
+
+	/**
+	 * Parses a given list of options. <p/>
+	 *
+	 <!-- options-start -->
+	 * Valid options are: <p/>
+	 * 
+	 * <pre> -L &lt;integer&gt;
+	 *  Tabu list length</pre>
+	 * 
+	 * <pre> -U &lt;integer&gt;
+	 *  Number of runs</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -P &lt;nr of parents&gt;
+	 *  Maximum number of parents</pre>
+	 * 
+	 * <pre> -R
+	 *  Use arc reversal operation.
+	 *  (default false)</pre>
+	 * 
+	 * <pre> -N
+	 *  Initial structure is empty (instead of Naive Bayes)</pre>
+	 * 
+	 * <pre> -mbc
+	 *  Applies a Markov Blanket correction to the network structure, 
+	 *  after a network structure is learned. This ensures that all 
+	 *  nodes in the network are part of the Markov blanket of the 
+	 *  classifier node.</pre>
+	 * 
+	 * <pre> -S [BAYES|MDL|ENTROPY|AIC|CROSS_CLASSIC|CROSS_BAYES]
+	 *  Score type (BAYES, BDeu, MDL, ENTROPY and AIC)</pre>
+	 * 
+	 <!-- options-end -->
+	 *
+	 * @param options the list of options as an array of strings
+	 * @throws Exception if an option is not supported
+	 */
+	public void setOptions(String[] options) throws Exception {
+		String sTabuList = Utils.getOption('L', options);
+		if (sTabuList.length() != 0) {
+			setTabuList(Integer.parseInt(sTabuList));
+		}
+		String sRuns = Utils.getOption('U', options);
+		if (sRuns.length() != 0) {
+			setRuns(Integer.parseInt(sRuns));
+		}
+		
+		super.setOptions(options);
+	} // setOptions
+
+	/**
+	 * Gets the current settings of the search algorithm.
+	 *
+	 * @return an array of strings suitable for passing to setOptions
+	 */
+	public String[] getOptions() {
+		String[] superOptions = super.getOptions();
+		String[] options = new String[7 + superOptions.length];
+		int current = 0;
+		
+		options[current++] = "-L";
+		options[current++] = "" + getTabuList();
+
+		options[current++] = "-U";
+		options[current++] = "" + getRuns();
+
+		// insert options from parent class
+		for (int iOption = 0; iOption < superOptions.length; iOption++) {
+			options[current++] = superOptions[iOption];
+		}
+
+		// Fill up rest with empty strings, not nulls!
+		while (current < options.length) {
+			options[current++] = "";
+		}
+		return options;
+	} // getOptions
+
+	/**
+	 * This will return a string describing the classifier.
+	 * @return The string.
+	 */
+	public String globalInfo() {
+		return "This Bayes Network learning algorithm uses tabu search for finding a well scoring " +
+		"Bayes network structure. Tabu search is hill climbing till an optimum is reached. The " +
+		"following step is the least worst possible step. The last X steps are kept in a list and " +
+		"none of the steps in this so called tabu list is considered in taking the next step. " +
+		"The best network found in this traversal is returned.\n\n"
+		+ "For more information see:\n\n"
+		+ getTechnicalInformation().toString();
+	} // globalInfo
+	
+	/**
+	 * @return a string to describe the Runs option.
+	 */
+	public String runsTipText() {
+	  return "Sets the number of steps to be performed.";
+	} // runsTipText
+
+	/**
+	 * @return a string to describe the TabuList option.
+	 */
+	public String tabuListTipText() {
+	  return "Sets the length of the tabu list.";
+	} // tabuListTipText
+
+	/**
+	 * Returns the revision string.
+	 * 
+	 * @return		the revision
+	 */
+	public String getRevision() {
+	  return RevisionUtils.extract("$Revision: 1.5 $");
+	}
+
+} // TabuSearch
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ConfusionMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ConfusionMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ConfusionMatrix.java	(revision 29)
@@ -0,0 +1,351 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NominalPrediction.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.classifiers.CostMatrix;
+import weka.core.FastVector;
+import weka.core.Matrix;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Cells of this matrix correspond to counts of the number (or weight)
+ * of predictions for each actual value / predicted value combination.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 1.9 $
+ */
+public class ConfusionMatrix extends Matrix {
+
+  /** for serialization */
+  private static final long serialVersionUID = -181789981401504090L;
+
+  /** Stores the names of the classes */
+  protected String [] m_ClassNames;
+
+  /**
+   * Creates the confusion matrix with the given class names.
+   *
+   * @param classNames an array containing the names the classes.
+   */
+  public ConfusionMatrix(String [] classNames) {
+
+    super(classNames.length, classNames.length);
+    m_ClassNames = (String [])classNames.clone();
+  }
+
+  /**
+   * Makes a copy of this ConfusionMatrix after applying the
+   * supplied CostMatrix to the cells. The resulting ConfusionMatrix
+   * can be used to get cost-weighted statistics.
+   *
+   * @param costs the CostMatrix.
+   * @return a ConfusionMatrix that has had costs applied.
+   * @exception Exception if the CostMatrix is not of the same size
+   * as this ConfusionMatrix.
+   */
+  public ConfusionMatrix makeWeighted(CostMatrix costs) throws Exception {
+
+    if (costs.size() != size()) {
+      throw new Exception("Cost and confusion matrices must be the same size");
+    }
+    ConfusionMatrix weighted = new ConfusionMatrix(m_ClassNames);
+    for (int row = 0; row < size(); row++) {
+      for (int col = 0; col < size(); col++) {
+        weighted.setElement(row, col, getElement(row, col) * 
+                            costs.getElement(row, col));
+      }
+    }
+    return weighted;
+  }
+
+
+  /**
+   * Creates and returns a clone of this object.
+   *
+   * @return a clone of this instance.
+   */
+  public Object clone() {
+
+    ConfusionMatrix m = (ConfusionMatrix)super.clone();
+    m.m_ClassNames = (String [])m_ClassNames.clone();
+    return m;
+  }
+
+  /**
+   * Gets the number of classes.
+   *
+   * @return the number of classes
+   */
+  public int size() {
+
+    return m_ClassNames.length;
+  }
+
+  /**
+   * Gets the name of one of the classes.
+   *
+   * @param index the index of the class.
+   * @return the class name.
+   */
+  public String className(int index) {
+
+    return m_ClassNames[index];
+  }
+
+  /**
+   * Includes a prediction in the confusion matrix.
+   *
+   * @param pred the NominalPrediction to include
+   * @exception Exception if no valid prediction was made (i.e. 
+   * unclassified).
+   */
+  public void addPrediction(NominalPrediction pred) throws Exception {
+
+    if (pred.predicted() == NominalPrediction.MISSING_VALUE) {
+      throw new Exception("No predicted value given.");
+    }
+    if (pred.actual() == NominalPrediction.MISSING_VALUE) {
+      throw new Exception("No actual value given.");
+    }
+    addElement((int)pred.actual(), (int)pred.predicted(), pred.weight());
+  }
+
+  /**
+   * Includes a whole bunch of predictions in the confusion matrix.
+   *
+   * @param predictions a FastVector containing the NominalPredictions
+   * to include
+   * @exception Exception if no valid prediction was made (i.e. 
+   * unclassified).
+   */
+  public void addPredictions(FastVector predictions) throws Exception {
+
+    for (int i = 0; i < predictions.size(); i++) {
+      addPrediction((NominalPrediction)predictions.elementAt(i));
+    }
+  }
+
+  
+  /**
+   * Gets the performance with respect to one of the classes
+   * as a TwoClassStats object.
+   *
+   * @param classIndex the index of the class of interest.
+   * @return the generated TwoClassStats object.
+   */
+  public TwoClassStats getTwoClassStats(int classIndex) {
+
+    double fp = 0, tp = 0, fn = 0, tn = 0;
+    for (int row = 0; row < size(); row++) {
+      for (int col = 0; col < size(); col++) {
+        if (row == classIndex) {
+          if (col == classIndex) {
+            tp += getElement(row, col);
+          } else {
+            fn += getElement(row, col);
+          }          
+        } else {
+          if (col == classIndex) {
+            fp += getElement(row, col);
+          } else {
+            tn += getElement(row, col);
+          }          
+        }
+      }
+    }
+    return new TwoClassStats(tp, fp, tn, fn);
+  }
+
+  /**
+   * Gets the number of correct classifications (that is, for which a
+   * correct prediction was made). (Actually the sum of the weights of
+   * these classifications)
+   *
+   * @return the number of correct classifications 
+   */
+  public double correct() {
+
+    double correct = 0;
+    for (int i = 0; i < size(); i++) {
+      correct += getElement(i, i);
+    }
+    return correct;
+  }
+
+  /**
+   * Gets the number of incorrect classifications (that is, for which an
+   * incorrect prediction was made). (Actually the sum of the weights of
+   * these classifications)
+   *
+   * @return the number of incorrect classifications 
+   */
+  public double incorrect() {
+
+    double incorrect = 0;
+    for (int row = 0; row < size(); row++) {
+      for (int col = 0; col < size(); col++) {
+        if (row != col) {
+          incorrect += getElement(row, col);
+        }
+      }
+    }
+    return incorrect;
+  }
+
+  /**
+   * Gets the number of predictions that were made
+   * (actually the sum of the weights of predictions where the
+   * class value was known).
+   *
+   * @return the number of predictions with known class
+   */
+  public double total() {
+
+    double total = 0;
+    for (int row = 0; row < size(); row++) {
+      for (int col = 0; col < size(); col++) {
+        total += getElement(row, col);
+      }
+    }
+    return total;
+  }
+
+  /**
+   * Returns the estimated error rate.
+   *
+   * @return the estimated error rate (between 0 and 1).
+   */
+  public double errorRate() {
+
+    return incorrect() / total();
+  }
+
+  /**
+   * Calls toString() with a default title.
+   *
+   * @return the confusion matrix as a string
+   */
+  public String toString() {
+
+    return toString("=== Confusion Matrix ===\n");
+  }
+
+  /**
+   * Outputs the performance statistics as a classification confusion
+   * matrix. For each class value, shows the distribution of 
+   * predicted class values.
+   *
+   * @param title the title for the confusion matrix
+   * @return the confusion matrix as a String
+   */
+  public String toString(String title) {
+
+    StringBuffer text = new StringBuffer();
+    char [] IDChars = {'a','b','c','d','e','f','g','h','i','j',
+		       'k','l','m','n','o','p','q','r','s','t',
+		       'u','v','w','x','y','z'};
+    int IDWidth;
+    boolean fractional = false;
+
+    // Find the maximum value in the matrix
+    // and check for fractional display requirement 
+    double maxval = 0;
+    for (int i = 0; i < size(); i++) {
+      for (int j = 0; j < size(); j++) {
+	double current = getElement(i, j);
+        if (current < 0) {
+          current *= -10;
+        }
+	if (current > maxval) {
+	  maxval = current;
+	}
+	double fract = current - Math.rint(current);
+	if (!fractional
+	    && ((Math.log(fract) / Math.log(10)) >= -2)) {
+	  fractional = true;
+	}
+      }
+    }
+
+    IDWidth = 1 + Math.max((int)(Math.log(maxval) / Math.log(10) 
+				 + (fractional ? 3 : 0)),
+			     (int)(Math.log(size()) / 
+				   Math.log(IDChars.length)));
+    text.append(title).append("\n");
+    for (int i = 0; i < size(); i++) {
+      if (fractional) {
+	text.append(" ").append(num2ShortID(i,IDChars,IDWidth - 3))
+          .append("   ");
+      } else {
+	text.append(" ").append(num2ShortID(i,IDChars,IDWidth));
+      }
+    }
+    text.append("     actual class\n");
+    for (int i = 0; i< size(); i++) { 
+      for (int j = 0; j < size(); j++) {
+	text.append(" ").append(
+		    Utils.doubleToString(getElement(i, j),
+					 IDWidth,
+					 (fractional ? 2 : 0)));
+      }
+      text.append(" | ").append(num2ShortID(i,IDChars,IDWidth))
+        .append(" = ").append(m_ClassNames[i]).append("\n");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Method for generating indices for the confusion matrix.
+   *
+   * @param num integer to format
+   * @return the formatted integer as a string
+   */
+  private static String num2ShortID(int num, char [] IDChars, int IDWidth) {
+    
+    char ID [] = new char [IDWidth];
+    int i;
+    
+    for(i = IDWidth - 1; i >=0; i--) {
+      ID[i] = IDChars[num % IDChars.length];
+      num = num / IDChars.length - 1;
+      if (num < 0) {
+	break;
+      }
+    }
+    for(i--; i >= 0; i--) {
+      ID[i] = ' ';
+    }
+
+    return new String(ID);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/CostCurve.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/CostCurve.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/CostCurve.java	(revision 29)
@@ -0,0 +1,177 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostCurve.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Generates points illustrating probablity cost tradeoffs that can be 
+ * obtained by varying the threshold value between classes. For example, 
+ * the typical threshold value of 0.5 means the predicted probability of 
+ * "positive" must be higher than 0.5 for the instance to be predicted as 
+ * "positive".
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+
+public class CostCurve 
+  implements RevisionHandler {
+
+  /** The name of the relation used in cost curve datasets */
+  public static final String RELATION_NAME = "CostCurve";
+
+  /** attribute name: Probability Cost Function */
+  public static final String PROB_COST_FUNC_NAME = "Probability Cost Function";
+  /** attribute name: Normalized Expected Cost */
+  public static final String NORM_EXPECTED_COST_NAME = "Normalized Expected Cost";
+  /** attribute name: Threshold */
+  public static final String THRESHOLD_NAME = "Threshold";
+
+  /**
+   * Calculates the performance stats for the default class and return 
+   * results as a set of Instances. The
+   * structure of these Instances is as follows:<p> <ul> 
+   * <li> <b>Probability Cost Function </b>
+   * <li> <b>Normalized Expected Cost</b>
+   * <li> <b>Threshold</b> contains the probability threshold that gives
+   * rise to the previous performance values. 
+   * </ul> <p>
+   *
+   * @see TwoClassStats
+   * @param predictions the predictions to base the curve on
+   * @return datapoints as a set of instances, null if no predictions
+   * have been made.
+   */
+  public Instances getCurve(FastVector predictions) {
+
+    if (predictions.size() == 0) {
+      return null;
+    }
+    return getCurve(predictions, 
+                    ((NominalPrediction)predictions.elementAt(0))
+                    .distribution().length - 1);
+  }
+
+  /**
+   * Calculates the performance stats for the desired class and return 
+   * results as a set of Instances.
+   *
+   * @param predictions the predictions to base the curve on
+   * @param classIndex index of the class of interest.
+   * @return datapoints as a set of instances.
+   */
+  public Instances getCurve(FastVector predictions, int classIndex) {
+
+    if ((predictions.size() == 0) ||
+        (((NominalPrediction)predictions.elementAt(0))
+         .distribution().length <= classIndex)) {
+      return null;
+    }
+    
+    ThresholdCurve tc = new ThresholdCurve();
+    Instances threshInst = tc.getCurve(predictions, classIndex);
+
+    Instances insts = makeHeader();
+    int fpind = threshInst.attribute(ThresholdCurve.FP_RATE_NAME).index();
+    int tpind = threshInst.attribute(ThresholdCurve.TP_RATE_NAME).index();
+    int threshind = threshInst.attribute(ThresholdCurve.THRESHOLD_NAME).index();
+    
+    double [] vals;
+    double fpval, tpval, thresh;
+    for (int i = 0; i< threshInst.numInstances(); i++) {
+      fpval = threshInst.instance(i).value(fpind);
+      tpval = threshInst.instance(i).value(tpind);
+      thresh = threshInst.instance(i).value(threshind);
+      vals = new double [3];
+      vals[0] = 0; vals[1] = fpval; vals[2] = thresh;
+      insts.add(new DenseInstance(1.0, vals));
+      vals = new double [3];
+      vals[0] = 1; vals[1] = 1.0 - tpval; vals[2] = thresh;
+      insts.add(new DenseInstance(1.0, vals));
+    }
+    
+    return insts;
+  }
+
+  /**
+   * generates the header
+   * 
+   * @return the header
+   */
+  private Instances makeHeader() {
+
+    FastVector fv = new FastVector();
+    fv.addElement(new Attribute(PROB_COST_FUNC_NAME));
+    fv.addElement(new Attribute(NORM_EXPECTED_COST_NAME));
+    fv.addElement(new Attribute(THRESHOLD_NAME));
+    return new Instances(RELATION_NAME, fv, 100);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Tests the CostCurve generation from the command line.
+   * The classifier is currently hardcoded. Pipe in an arff file.
+   *
+   * @param args currently ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      
+      Instances inst = new Instances(new java.io.InputStreamReader(System.in));
+      
+      inst.setClassIndex(inst.numAttributes() - 1);
+      CostCurve cc = new CostCurve();
+      EvaluationUtils eu = new EvaluationUtils();
+      Classifier classifier = new weka.classifiers.functions.Logistic();
+      FastVector predictions = new FastVector();
+      for (int i = 0; i < 2; i++) { // Do two runs.
+	eu.setSeed(i);
+	predictions.appendElements(eu.getCVPredictions(classifier, inst, 10));
+	//System.out.println("\n\n\n");
+      }
+      Instances result = cc.getCurve(predictions);
+      System.out.println(result);
+      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/EvaluationUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/EvaluationUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/EvaluationUtils.java	(revision 29)
@@ -0,0 +1,154 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EvaluationUtils.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Random;
+
+/**
+ * Contains utility functions for generating lists of predictions in 
+ * various manners.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5928 $
+ */
+public class EvaluationUtils
+  implements RevisionHandler {
+
+  /** Seed used to randomize data in cross-validation */
+  private int m_Seed = 1;
+
+  /** Sets the seed for randomization during cross-validation */
+  public void setSeed(int seed) { m_Seed = seed; }
+
+  /** Gets the seed for randomization during cross-validation */
+  public int getSeed() { return m_Seed; }
+  
+  /**
+   * Generate a bunch of predictions ready for processing, by performing a
+   * cross-validation on the supplied dataset.
+   *
+   * @param classifier the Classifier to evaluate
+   * @param data the dataset
+   * @param numFolds the number of folds in the cross-validation.
+   * @exception Exception if an error occurs
+   */
+  public FastVector getCVPredictions(Classifier classifier, 
+                                     Instances data, 
+                                     int numFolds) 
+    throws Exception {
+
+    FastVector predictions = new FastVector();
+    Instances runInstances = new Instances(data);
+    Random random = new Random(m_Seed);
+    runInstances.randomize(random);
+    if (runInstances.classAttribute().isNominal() && (numFolds > 1)) {
+      runInstances.stratify(numFolds);
+    }
+    int inst = 0;
+    for (int fold = 0; fold < numFolds; fold++) {
+      Instances train = runInstances.trainCV(numFolds, fold, random);
+      Instances test = runInstances.testCV(numFolds, fold);
+      FastVector foldPred = getTrainTestPredictions(classifier, train, test);
+      predictions.appendElements(foldPred);
+    } 
+    return predictions;
+  }
+
+  /**
+   * Generate a bunch of predictions ready for processing, by performing a
+   * evaluation on a test set after training on the given training set.
+   *
+   * @param classifier the Classifier to evaluate
+   * @param train the training dataset
+   * @param test the test dataset
+   * @exception Exception if an error occurs
+   */
+  public FastVector getTrainTestPredictions(Classifier classifier, 
+                                            Instances train, Instances test) 
+    throws Exception {
+    
+    classifier.buildClassifier(train);
+    return getTestPredictions(classifier, test);
+  }
+
+  /**
+   * Generate a bunch of predictions ready for processing, by performing a
+   * evaluation on a test set assuming the classifier is already trained.
+   *
+   * @param classifier the pre-trained Classifier to evaluate
+   * @param test the test dataset
+   * @exception Exception if an error occurs
+   */
+  public FastVector getTestPredictions(Classifier classifier, 
+                                       Instances test) 
+    throws Exception {
+    
+    FastVector predictions = new FastVector();
+    for (int i = 0; i < test.numInstances(); i++) {
+      if (!test.instance(i).classIsMissing()) {
+        predictions.addElement(getPrediction(classifier, test.instance(i)));
+      }
+    }
+    return predictions;
+  }
+
+  
+  /**
+   * Generate a single prediction for a test instance given the pre-trained
+   * classifier.
+   *
+   * @param classifier the pre-trained Classifier to evaluate
+   * @param test the test instance
+   * @exception Exception if an error occurs
+   */
+  public Prediction getPrediction(Classifier classifier,
+                                  Instance test)
+    throws Exception {
+   
+    double actual = test.classValue();
+    double [] dist = classifier.distributionForInstance(test);
+    if (test.classAttribute().isNominal()) {
+      return new NominalPrediction(actual, dist, test.weight());
+    } else {
+      return new NumericPrediction(actual, dist[0], test.weight());
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/MarginCurve.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/MarginCurve.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/MarginCurve.java	(revision 29)
@@ -0,0 +1,172 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MarginCurve.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Generates points illustrating the prediction margin. The margin is defined
+ * as the difference between the probability predicted for the actual class and
+ * the highest probability predicted for the other classes. One hypothesis
+ * as to the good performance of boosting algorithms is that they increaes the
+ * margins on the training data and this gives better performance on test data.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5987 $
+ */
+public class MarginCurve
+  implements RevisionHandler {
+
+  /**
+   * Calculates the cumulative margin distribution for the set of
+   * predictions, returning the result as a set of Instances. The
+   * structure of these Instances is as follows:<p> <ul> 
+   * <li> <b>Margin</b> contains the margin value (which should be plotted
+   * as an x-coordinate) 
+   * <li> <b>Current</b> contains the count of instances with the current 
+   * margin (plot as y axis)
+   * <li> <b>Cumulative</b> contains the count of instances with margin
+   * less than or equal to the current margin (plot as y axis)
+   * </ul> <p>
+   *
+   * @return datapoints as a set of instances, null if no predictions
+   * have been made.  
+   */
+  public Instances getCurve(FastVector predictions) {
+
+    if (predictions.size() == 0) {
+      return null;
+    }
+
+    Instances insts = makeHeader();
+    double [] margins = getMargins(predictions);
+    int [] sorted = Utils.sort(margins);
+    int binMargin = 0;
+    int totalMargin = 0;
+    insts.add(makeInstance(-1, binMargin, totalMargin));
+    for (int i = 0; i < sorted.length; i++) {
+      double current = margins[sorted[i]];
+      double weight = ((NominalPrediction)predictions.elementAt(sorted[i]))
+        .weight();
+      totalMargin += weight;
+      binMargin += weight;
+      if (true) {
+        insts.add(makeInstance(current, binMargin, totalMargin));
+        binMargin = 0;
+      }
+    }
+    return insts;
+  }
+
+  /**
+   * Pulls all the margin values out of a vector of NominalPredictions.
+   *
+   * @param predictions a FastVector containing NominalPredictions
+   * @return an array of margin values.
+   */
+  private double [] getMargins(FastVector predictions) {
+
+    // sort by predicted probability of the desired class.
+    double [] margins = new double [predictions.size()];
+    for (int i = 0; i < margins.length; i++) {
+      NominalPrediction pred = (NominalPrediction)predictions.elementAt(i);
+      margins[i] = pred.margin();
+    }
+    return margins;
+  }
+
+  /**
+   * Creates an Instances object with the attributes we will be calculating.
+   *
+   * @return the Instances structure.
+   */
+  private Instances makeHeader() {
+
+    FastVector fv = new FastVector();
+    fv.addElement(new Attribute("Margin"));
+    fv.addElement(new Attribute("Current"));
+    fv.addElement(new Attribute("Cumulative"));
+    return new Instances("MarginCurve", fv, 100);
+  }
+  
+  /**
+   * Creates an Instance object with the attributes calculated.
+   *
+   * @param margin the margin for this data point.
+   * @param current the number of instances with this margin.
+   * @param cumulative the number of instances with margin less than or equal
+   * to this margin.
+   * @return the Instance object.
+   */
+  private Instance makeInstance(double margin, int current, int cumulative) {
+
+    int count = 0;
+    double [] vals = new double[3];
+    vals[count++] = margin;
+    vals[count++] = current;
+    vals[count++] = cumulative;
+    return new DenseInstance(1.0, vals);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Tests the MarginCurve generation from the command line.
+   * The classifier is currently hardcoded. Pipe in an arff file.
+   *
+   * @param args currently ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      Utils.SMALL = 0;
+      Instances inst = new Instances(new java.io.InputStreamReader(System.in));
+      inst.setClassIndex(inst.numAttributes() - 1);
+      MarginCurve tc = new MarginCurve();
+      EvaluationUtils eu = new EvaluationUtils();
+      weka.classifiers.meta.LogitBoost classifier 
+        = new weka.classifiers.meta.LogitBoost();
+      classifier.setNumIterations(20);
+      FastVector predictions 
+        = eu.getTrainTestPredictions(classifier, inst, inst);
+      Instances result = tc.getCurve(predictions);
+      System.out.println(result);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NominalPrediction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NominalPrediction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NominalPrediction.java	(revision 29)
@@ -0,0 +1,248 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NominalPrediction.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Encapsulates an evaluatable nominal prediction: the predicted probability
+ * distribution plus the actual class value.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 1.12 $
+ */
+public class NominalPrediction
+  implements Prediction, Serializable, RevisionHandler {
+
+  /**
+   * Remove this if you change this class so that serialization would be
+   * affected.
+   */
+  static final long serialVersionUID = -8871333992740492788L;
+
+  /** The predicted probabilities */
+  private double [] m_Distribution;
+
+  /** The actual class value */
+  private double m_Actual = MISSING_VALUE;
+
+  /** The predicted class value */
+  private double m_Predicted = MISSING_VALUE;
+
+  /** The weight assigned to this prediction */
+  private double m_Weight = 1;
+
+  /**
+   * Creates the NominalPrediction object with a default weight of 1.0.
+   *
+   * @param actual the actual value, or MISSING_VALUE.
+   * @param distribution the predicted probability distribution. Use 
+   * NominalPrediction.makeDistribution() if you only know the predicted value.
+   */
+  public NominalPrediction(double actual, double [] distribution) {
+
+    this(actual, distribution, 1);
+  }
+
+  /**
+   * Creates the NominalPrediction object.
+   *
+   * @param actual the actual value, or MISSING_VALUE.
+   * @param distribution the predicted probability distribution. Use 
+   * NominalPrediction.makeDistribution() if you only know the predicted value.
+   * @param weight the weight assigned to the prediction.
+   */
+  public NominalPrediction(double actual, double [] distribution, 
+                           double weight) {
+
+    if (distribution == null) {
+      throw new NullPointerException("Null distribution in NominalPrediction.");
+    }
+    m_Actual = actual;
+    m_Distribution = distribution.clone();
+    m_Weight = weight;
+    updatePredicted();
+  }
+
+  /**
+   * Gets the predicted probabilities
+   * 
+   * @return the predicted probabilities
+   */
+  public double [] distribution() { 
+
+    return m_Distribution; 
+  }
+
+  /** 
+   * Gets the actual class value.
+   *
+   * @return the actual class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  public double actual() { 
+
+    return m_Actual; 
+  }
+
+  /**
+   * Gets the predicted class value.
+   *
+   * @return the predicted class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  public double predicted() { 
+
+    return m_Predicted; 
+  }
+
+  /** 
+   * Gets the weight assigned to this prediction. This is typically the weight
+   * of the test instance the prediction was made for.
+   *
+   * @return the weight assigned to this prediction.
+   */
+  public double weight() { 
+
+    return m_Weight; 
+  }
+
+  /**
+   * Calculates the prediction margin. This is defined as the difference
+   * between the probability predicted for the actual class and the highest
+   * predicted probability of the other classes.
+   *
+   * @return the margin for this prediction, or
+   * MISSING_VALUE if either the actual or predicted value
+   * is missing.  
+   */
+  public double margin() {
+
+    if ((m_Actual == MISSING_VALUE) ||
+        (m_Predicted == MISSING_VALUE)) {
+      return MISSING_VALUE;
+    }
+    double probActual = m_Distribution[(int)m_Actual];
+    double probNext = 0;
+    for(int i = 0; i < m_Distribution.length; i++)
+      if ((i != m_Actual) &&
+	  (m_Distribution[i] > probNext))
+	probNext = m_Distribution[i];
+
+    return probActual - probNext;
+  }
+
+  /**
+   * Convert a single prediction into a probability distribution
+   * with all zero probabilities except the predicted value which
+   * has probability 1.0. If no prediction was made, all probabilities
+   * are zero.
+   *
+   * @param predictedClass the index of the predicted class, or 
+   * MISSING_VALUE if no prediction was made.
+   * @param numClasses the number of possible classes for this nominal
+   * prediction.
+   * @return the probability distribution.  
+   */
+  public static double [] makeDistribution(double predictedClass, 
+                                           int numClasses) {
+
+    double [] dist = new double [numClasses];
+    if (predictedClass == MISSING_VALUE) {
+      return dist;
+    }
+    dist[(int)predictedClass] = 1.0;
+    return dist;
+  }
+  
+  /**
+   * Creates a uniform probability distribution -- where each of the
+   * possible classes is assigned equal probability.
+   *
+   * @param numClasses the number of possible classes for this nominal
+   * prediction.
+   * @return the probability distribution.  
+   */
+  public static double [] makeUniformDistribution(int numClasses) {
+    
+    double [] dist = new double [numClasses];
+    for (int i = 0; i < numClasses; i++) {
+      dist[i] = 1.0 / numClasses;
+    }
+    return dist;
+  }
+ 
+  /**
+   * Determines the predicted class (doesn't detect multiple 
+   * classifications). If no prediction was made (i.e. all zero
+   * probababilities in the distribution), m_Prediction is set to
+   * MISSING_VALUE.
+   */
+  private void updatePredicted() {
+
+    int predictedClass = -1;
+    double bestProb = 0.0;
+    for(int i = 0; i < m_Distribution.length; i++) {
+      if (m_Distribution[i] > bestProb) {
+        predictedClass = i;
+        bestProb = m_Distribution[i];
+      }
+    }
+
+    if (predictedClass != -1) {
+      m_Predicted = predictedClass;
+    } else {
+      m_Predicted = MISSING_VALUE;
+    }
+  }
+
+  /**
+   * Gets a human readable representation of this prediction.
+   *
+   * @return a human readable representation of this prediction.
+   */
+  public String toString() {
+
+    StringBuffer sb = new StringBuffer();
+    sb.append("NOM: ").append(actual()).append(" ").append(predicted());
+    sb.append(' ').append(weight());
+    double [] dist = distribution();
+    for (int i = 0; i < dist.length; i++) {
+      sb.append(' ').append(dist[i]);
+    }
+    return sb.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.12 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NumericPrediction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NumericPrediction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/NumericPrediction.java	(revision 29)
@@ -0,0 +1,180 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumericPrediction.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.classifiers.IntervalEstimator;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Encapsulates an evaluatable numeric prediction: the predicted class value
+ * plus the actual class value.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5714 $
+ */
+public class NumericPrediction
+  implements Prediction, Serializable, RevisionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4880216423674233887L;
+
+  /** The actual class value. */
+  private double m_Actual = MISSING_VALUE;
+
+  /** The predicted class value. */
+  private double m_Predicted = MISSING_VALUE;
+
+  /** The weight assigned to this prediction. */
+  private double m_Weight = 1;
+  
+  /** the prediction intervals. */
+  private double[][] m_PredictionIntervals;
+
+  /**
+   * Creates the NumericPrediction object with a default weight of 1.0.
+   *
+   * @param actual the actual value, or MISSING_VALUE.
+   * @param predicted the predicted value, or MISSING_VALUE.
+   */
+  public NumericPrediction(double actual, double predicted) {
+    this(actual, predicted, 1);
+  }
+
+  /**
+   * Creates the NumericPrediction object.
+   *
+   * @param actual the actual value, or MISSING_VALUE.
+   * @param predicted the predicted value, or MISSING_VALUE.
+   * @param weight the weight assigned to the prediction.
+   */
+  public NumericPrediction(double actual, double predicted, double weight) {
+    this(actual, predicted, weight, new double[0][]);
+  }
+
+  /**
+   * Creates the NumericPrediction object.
+   *
+   * @param actual the actual value, or MISSING_VALUE.
+   * @param predicted the predicted value, or MISSING_VALUE.
+   * @param weight the weight assigned to the prediction.
+   * @param predInt the prediction intervals from classifiers implementing
+   * the <code>IntervalEstimator</code> interface.
+   * @see IntervalEstimator
+   */
+  public NumericPrediction(double actual, double predicted, double weight, double[][] predInt) {
+    m_Actual = actual;
+    m_Predicted = predicted;
+    m_Weight = weight;
+    setPredictionIntervals(predInt);
+  }
+
+  /** 
+   * Gets the actual class value.
+   *
+   * @return the actual class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  public double actual() { 
+    return m_Actual; 
+  }
+
+  /**
+   * Gets the predicted class value.
+   *
+   * @return the predicted class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  public double predicted() { 
+    return m_Predicted; 
+  }
+
+  /** 
+   * Gets the weight assigned to this prediction. This is typically the weight
+   * of the test instance the prediction was made for.
+   *
+   * @return the weight assigned to this prediction.
+   */
+  public double weight() { 
+    return m_Weight; 
+  }
+
+  /**
+   * Calculates the prediction error. This is defined as the predicted
+   * value minus the actual value.
+   *
+   * @return the error for this prediction, or
+   * MISSING_VALUE if either the actual or predicted value
+   * is missing.  
+   */
+  public double error() {
+    if ((m_Actual == MISSING_VALUE) ||
+        (m_Predicted == MISSING_VALUE)) {
+      return MISSING_VALUE;
+    }
+    return m_Predicted - m_Actual;
+  }
+  
+  /**
+   * Sets the prediction intervals for this prediction.
+   * 
+   * @param predInt the prediction intervals
+   */
+  public void setPredictionIntervals(double[][] predInt) {
+    m_PredictionIntervals = predInt.clone();
+  }
+  
+  /**
+   * Returns the predictions intervals. Only classifiers implementing the
+   * <code>IntervalEstimator</code> interface.
+   * 
+   * @return the prediction intervals.
+   * @see IntervalEstimator
+   */
+  public double[][] predictionIntervals() {
+    return m_PredictionIntervals;
+  }
+
+  /**
+   * Gets a human readable representation of this prediction.
+   *
+   * @return a human readable representation of this prediction.
+   */
+  public String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("NUM: ").append(actual()).append(' ').append(predicted());
+    sb.append(' ').append(weight());
+    return sb.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5714 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/Prediction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/Prediction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/Prediction.java	(revision 29)
@@ -0,0 +1,65 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Prediction.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+/**
+ * Encapsulates a single evaluatable prediction: the predicted value plus the 
+ * actual class value.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5987 $
+ */
+public interface Prediction {
+
+  /** 
+   * Constant representing a missing value. This should have the same value
+   * as weka.core.Instance.MISSING_VALUE 
+   */
+  double MISSING_VALUE 
+    = weka.core.Utils.missingValue();
+
+  /** 
+   * Gets the weight assigned to this prediction. This is typically the weight
+   * of the test instance the prediction was made for.
+   *
+   * @return the weight assigned to this prediction.
+   */
+  double weight();
+
+  /** 
+   * Gets the actual class value.
+   *
+   * @return the actual class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  double actual();
+
+  /**
+   * Gets the predicted class value.
+   *
+   * @return the predicted class value, or MISSING_VALUE if no
+   * prediction was made.  
+   */
+  double predicted();
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ThresholdCurve.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ThresholdCurve.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/ThresholdCurve.java	(revision 29)
@@ -0,0 +1,470 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ThresholdCurve.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Generates points illustrating prediction tradeoffs that can be obtained
+ * by varying the threshold value between classes. For example, the typical 
+ * threshold value of 0.5 means the predicted probability of "positive" must be
+ * higher than 0.5 for the instance to be predicted as "positive". The 
+ * resulting dataset can be used to visualize precision/recall tradeoff, or 
+ * for ROC curve analysis (true positive rate vs false positive rate).
+ * Weka just varies the threshold on the class probability estimates in each 
+ * case. The Mann Whitney statistic is used to calculate the AUC.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5987 $
+ */
+public class ThresholdCurve
+  implements RevisionHandler {
+
+  /** The name of the relation used in threshold curve datasets */
+  public static final String RELATION_NAME = "ThresholdCurve";
+
+  /** attribute name: True Positives */
+  public static final String TRUE_POS_NAME  = "True Positives";
+  /** attribute name: False Negatives */
+  public static final String FALSE_NEG_NAME = "False Negatives";
+  /** attribute name: False Positives */
+  public static final String FALSE_POS_NAME = "False Positives";
+  /** attribute name: True Negatives */
+  public static final String TRUE_NEG_NAME  = "True Negatives";
+  /** attribute name: False Positive Rate" */
+  public static final String FP_RATE_NAME   = "False Positive Rate";
+  /** attribute name: True Positive Rate */
+  public static final String TP_RATE_NAME   = "True Positive Rate";
+  /** attribute name: Precision */
+  public static final String PRECISION_NAME = "Precision";
+  /** attribute name: Recall */
+  public static final String RECALL_NAME    = "Recall";
+  /** attribute name: Fallout */
+  public static final String FALLOUT_NAME   = "Fallout";
+  /** attribute name: FMeasure */
+  public static final String FMEASURE_NAME  = "FMeasure";
+  /** attribute name: Sample Size */
+  public static final String SAMPLE_SIZE_NAME = "Sample Size";
+  /** attribute name: Lift */
+  public static final String LIFT_NAME = "Lift";
+  /** attribute name: Threshold */
+  public static final String THRESHOLD_NAME = "Threshold";
+
+  /**
+   * Calculates the performance stats for the default class and return 
+   * results as a set of Instances. The
+   * structure of these Instances is as follows:<p> <ul> 
+   * <li> <b>True Positives </b>
+   * <li> <b>False Negatives</b>
+   * <li> <b>False Positives</b>
+   * <li> <b>True Negatives</b>
+   * <li> <b>False Positive Rate</b>
+   * <li> <b>True Positive Rate</b>
+   * <li> <b>Precision</b>
+   * <li> <b>Recall</b>  
+   * <li> <b>Fallout</b>  
+   * <li> <b>Threshold</b> contains the probability threshold that gives
+   * rise to the previous performance values. 
+   * </ul> <p>
+   * For the definitions of these measures, see TwoClassStats <p>
+   *
+   * @see TwoClassStats
+   * @param predictions the predictions to base the curve on
+   * @return datapoints as a set of instances, null if no predictions
+   * have been made.
+   */
+  public Instances getCurve(FastVector predictions) {
+
+    if (predictions.size() == 0) {
+      return null;
+    }
+    return getCurve(predictions, 
+                    ((NominalPrediction)predictions.elementAt(0))
+                    .distribution().length - 1);
+  }
+
+  /**
+   * Calculates the performance stats for the desired class and return 
+   * results as a set of Instances.
+   *
+   * @param predictions the predictions to base the curve on
+   * @param classIndex index of the class of interest.
+   * @return datapoints as a set of instances.
+   */
+  public Instances getCurve(FastVector predictions, int classIndex) {
+
+    if ((predictions.size() == 0) ||
+        (((NominalPrediction)predictions.elementAt(0))
+         .distribution().length <= classIndex)) {
+      return null;
+    }
+
+    double totPos = 0, totNeg = 0;
+    double [] probs = getProbabilities(predictions, classIndex);
+
+    // Get distribution of positive/negatives
+    for (int i = 0; i < probs.length; i++) {
+      NominalPrediction pred = (NominalPrediction)predictions.elementAt(i);
+      if (pred.actual() == Prediction.MISSING_VALUE) {
+        System.err.println(getClass().getName() 
+                           + " Skipping prediction with missing class value");
+        continue;
+      }
+      if (pred.weight() < 0) {
+        System.err.println(getClass().getName() 
+                           + " Skipping prediction with negative weight");
+        continue;
+      }
+      if (pred.actual() == classIndex) {
+        totPos += pred.weight();
+      } else {
+        totNeg += pred.weight();
+      }
+    }
+
+    Instances insts = makeHeader();
+    int [] sorted = Utils.sort(probs);
+    TwoClassStats tc = new TwoClassStats(totPos, totNeg, 0, 0);
+    double threshold = 0;
+    double cumulativePos = 0;
+    double cumulativeNeg = 0;
+    for (int i = 0; i < sorted.length; i++) {
+
+      if ((i == 0) || (probs[sorted[i]] > threshold)) {
+	tc.setTruePositive(tc.getTruePositive() - cumulativePos);
+	tc.setFalseNegative(tc.getFalseNegative() + cumulativePos);
+	tc.setFalsePositive(tc.getFalsePositive() - cumulativeNeg);
+	tc.setTrueNegative(tc.getTrueNegative() + cumulativeNeg);
+	threshold = probs[sorted[i]];
+	insts.add(makeInstance(tc, threshold));
+	cumulativePos = 0;
+	cumulativeNeg = 0;
+	if (i == sorted.length - 1) {
+	  break;
+	}
+      }
+
+      NominalPrediction pred = (NominalPrediction)predictions.elementAt(sorted[i]);
+
+      if (pred.actual() == Prediction.MISSING_VALUE) {
+	System.err.println(getClass().getName()
+			   + " Skipping prediction with missing class value");
+	continue;
+      }
+      if (pred.weight() < 0) {
+	System.err.println(getClass().getName() 
+			   + " Skipping prediction with negative weight");
+	continue;
+      }
+      if (pred.actual() == classIndex) {
+	cumulativePos += pred.weight();
+      } else {
+	cumulativeNeg += pred.weight();
+      }
+
+      /*
+      System.out.println(tc + " " + probs[sorted[i]] 
+                         + " " + (pred.actual() == classIndex));
+      */
+      /*if ((i != (sorted.length - 1)) &&
+          ((i == 0) ||  
+          (probs[sorted[i]] != probs[sorted[i - 1]]))) {
+        insts.add(makeInstance(tc, probs[sorted[i]]));
+	}*/
+    }
+    return insts;
+  }
+
+  /**
+   * Calculates the n point precision result, which is the precision averaged
+   * over n evenly spaced (w.r.t recall) samples of the curve.
+   *
+   * @param tcurve a previously extracted threshold curve Instances.
+   * @param n the number of points to average over.
+   * @return the n-point precision.
+   */
+  public static double getNPointPrecision(Instances tcurve, int n) {
+
+    if (!RELATION_NAME.equals(tcurve.relationName()) 
+        || (tcurve.numInstances() == 0)) {
+      return Double.NaN;
+    }
+    int recallInd = tcurve.attribute(RECALL_NAME).index();
+    int precisInd = tcurve.attribute(PRECISION_NAME).index();
+    double [] recallVals = tcurve.attributeToDoubleArray(recallInd);
+    int [] sorted = Utils.sort(recallVals);
+    double isize = 1.0 / (n - 1);
+    double psum = 0;
+    for (int i = 0; i < n; i++) {
+      int pos = binarySearch(sorted, recallVals, i * isize);
+      double recall = recallVals[sorted[pos]];
+      double precis = tcurve.instance(sorted[pos]).value(precisInd);
+      /*
+      System.err.println("Point " + (i + 1) + ": i=" + pos 
+                         + " r=" + (i * isize)
+                         + " p'=" + precis 
+                         + " r'=" + recall);
+      */
+      // interpolate figures for non-endpoints
+      while ((pos != 0) && (pos < sorted.length - 1)) {
+        pos++;
+        double recall2 = recallVals[sorted[pos]];
+        if (recall2 != recall) {
+          double precis2 = tcurve.instance(sorted[pos]).value(precisInd);
+          double slope = (precis2 - precis) / (recall2 - recall);
+          double offset = precis - recall * slope;
+          precis = isize * i * slope + offset;
+          /*
+          System.err.println("Point2 " + (i + 1) + ": i=" + pos 
+                             + " r=" + (i * isize)
+                             + " p'=" + precis2 
+                             + " r'=" + recall2
+                             + " p''=" + precis);
+          */
+          break;
+        }
+      }
+      psum += precis;
+    }
+    return psum / n;
+  }
+
+  /**
+   * Calculates the area under the ROC curve as the Wilcoxon-Mann-Whitney statistic.
+   *
+   * @param tcurve a previously extracted threshold curve Instances.
+   * @return the ROC area, or Double.NaN if you don't pass in 
+   * a ThresholdCurve generated Instances. 
+   */
+  public static double getROCArea(Instances tcurve) {
+
+    final int n = tcurve.numInstances();
+    if (!RELATION_NAME.equals(tcurve.relationName()) 
+        || (n == 0)) {
+      return Double.NaN;
+    }
+    final int tpInd = tcurve.attribute(TRUE_POS_NAME).index();
+    final int fpInd = tcurve.attribute(FALSE_POS_NAME).index();
+    final double [] tpVals = tcurve.attributeToDoubleArray(tpInd);
+    final double [] fpVals = tcurve.attributeToDoubleArray(fpInd);
+
+    double area = 0.0, cumNeg = 0.0;
+    final double totalPos = tpVals[0];
+    final double totalNeg = fpVals[0];
+    for (int i = 0; i < n; i++) {
+	double cip, cin;
+	if (i < n - 1) {
+	    cip = tpVals[i] - tpVals[i + 1];
+	    cin = fpVals[i] - fpVals[i + 1];
+	} else {
+	    cip = tpVals[n - 1];
+	    cin = fpVals[n - 1];
+	}
+	area += cip * (cumNeg + (0.5 * cin));
+	cumNeg += cin;
+    }
+    area /= (totalNeg * totalPos);
+
+    return area;
+  }
+
+  /**
+   * Gets the index of the instance with the closest threshold value to the
+   * desired target
+   *
+   * @param tcurve a set of instances that have been generated by this class
+   * @param threshold the target threshold
+   * @return the index of the instance that has threshold closest to
+   * the target, or -1 if this could not be found (i.e. no data, or
+   * bad threshold target)
+   */
+  public static int getThresholdInstance(Instances tcurve, double threshold) {
+
+    if (!RELATION_NAME.equals(tcurve.relationName()) 
+        || (tcurve.numInstances() == 0)
+        || (threshold < 0)
+        || (threshold > 1.0)) {
+      return -1;
+    }
+    if (tcurve.numInstances() == 1) {
+      return 0;
+    }
+    double [] tvals = tcurve.attributeToDoubleArray(tcurve.numAttributes() - 1);
+    int [] sorted = Utils.sort(tvals);
+    return binarySearch(sorted, tvals, threshold);
+  }
+
+  /**
+   * performs a binary search
+   * 
+   * @param index the indices
+   * @param vals the values
+   * @param target the target to look for
+   * @return the index of the target
+   */
+  private static int binarySearch(int [] index, double [] vals, double target) {
+    
+    int lo = 0, hi = index.length - 1;
+    while (hi - lo > 1) {
+      int mid = lo + (hi - lo) / 2;
+      double midval = vals[index[mid]];
+      if (target > midval) {
+        lo = mid;
+      } else if (target < midval) {
+        hi = mid;
+      } else {
+        while ((mid > 0) && (vals[index[mid - 1]] == target)) {
+          mid --;
+        }
+        return mid;
+      }
+    }
+    return lo;
+  }
+
+  /**
+   * 
+   * @param predictions the predictions to use
+   * @param classIndex the class index
+   * @return the probabilities
+   */
+  private double [] getProbabilities(FastVector predictions, int classIndex) {
+
+    // sort by predicted probability of the desired class.
+    double [] probs = new double [predictions.size()];
+    for (int i = 0; i < probs.length; i++) {
+      NominalPrediction pred = (NominalPrediction)predictions.elementAt(i);
+      probs[i] = pred.distribution()[classIndex];
+    }
+    return probs;
+  }
+
+  /**
+   * generates the header
+   * 
+   * @return the header
+   */
+  private Instances makeHeader() {
+
+    FastVector fv = new FastVector();
+    fv.addElement(new Attribute(TRUE_POS_NAME));
+    fv.addElement(new Attribute(FALSE_NEG_NAME));
+    fv.addElement(new Attribute(FALSE_POS_NAME));
+    fv.addElement(new Attribute(TRUE_NEG_NAME));
+    fv.addElement(new Attribute(FP_RATE_NAME));
+    fv.addElement(new Attribute(TP_RATE_NAME));
+    fv.addElement(new Attribute(PRECISION_NAME));
+    fv.addElement(new Attribute(RECALL_NAME));
+    fv.addElement(new Attribute(FALLOUT_NAME));
+    fv.addElement(new Attribute(FMEASURE_NAME));
+    fv.addElement(new Attribute(SAMPLE_SIZE_NAME));
+    fv.addElement(new Attribute(LIFT_NAME));
+    fv.addElement(new Attribute(THRESHOLD_NAME));      
+    return new Instances(RELATION_NAME, fv, 100);
+  }
+  
+  /**
+   * generates an instance out of the given data
+   * 
+   * @param tc the statistics
+   * @param prob the probability
+   * @return the generated instance
+   */
+  private Instance makeInstance(TwoClassStats tc, double prob) {
+
+    int count = 0;
+    double [] vals = new double[13];
+    vals[count++] = tc.getTruePositive();
+    vals[count++] = tc.getFalseNegative();
+    vals[count++] = tc.getFalsePositive();
+    vals[count++] = tc.getTrueNegative();
+    vals[count++] = tc.getFalsePositiveRate();
+    vals[count++] = tc.getTruePositiveRate();
+    vals[count++] = tc.getPrecision();
+    vals[count++] = tc.getRecall();
+    vals[count++] = tc.getFallout();
+    vals[count++] = tc.getFMeasure();
+      double ss = (tc.getTruePositive() + tc.getFalsePositive()) / 
+        (tc.getTruePositive() + tc.getFalsePositive() + tc.getTrueNegative() + tc.getFalseNegative());
+    vals[count++] = ss;
+    double expectedByChance = (ss * (tc.getTruePositive() + tc.getFalseNegative()));
+    if (expectedByChance < 1) {
+      vals[count++] = Utils.missingValue();
+    } else {
+    vals[count++] = tc.getTruePositive() / expectedByChance; 
+     
+    }
+    vals[count++] = prob;
+    return new DenseInstance(1.0, vals);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Tests the ThresholdCurve generation from the command line.
+   * The classifier is currently hardcoded. Pipe in an arff file.
+   *
+   * @param args currently ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      
+      Instances inst = new Instances(new java.io.InputStreamReader(System.in));
+      if (false) {
+        System.out.println(ThresholdCurve.getNPointPrecision(inst, 11));
+      } else {
+        inst.setClassIndex(inst.numAttributes() - 1);
+        ThresholdCurve tc = new ThresholdCurve();
+        EvaluationUtils eu = new EvaluationUtils();
+        Classifier classifier = new weka.classifiers.functions.Logistic();
+        FastVector predictions = new FastVector();
+        for (int i = 0; i < 2; i++) { // Do two runs.
+          eu.setSeed(i);
+          predictions.appendElements(eu.getCVPredictions(classifier, inst, 10));
+          //System.out.println("\n\n\n");
+        }
+        Instances result = tc.getCurve(predictions);
+        System.out.println(result);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/TwoClassStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/TwoClassStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/TwoClassStats.java	(revision 29)
@@ -0,0 +1,247 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TwoClassStats.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.evaluation;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Encapsulates performance functions for two-class problems.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 1.9 $
+ */
+public class TwoClassStats
+  implements RevisionHandler {
+
+  /** The names used when converting this object to a confusion matrix */
+  private static final String [] CATEGORY_NAMES = {"negative", "positive"};
+
+  /** Pos predicted as pos */
+  private double m_TruePos;
+
+  /** Neg predicted as pos */
+  private double m_FalsePos;
+
+  /** Neg predicted as neg */
+  private double m_TrueNeg;
+
+  /** Pos predicted as neg */
+  private double m_FalseNeg;
+
+  /**
+   * Creates the TwoClassStats with the given initial performance values.
+   *
+   * @param tp the number of correctly classified positives
+   * @param fp the number of incorrectly classified negatives
+   * @param tn the number of correctly classified negatives
+   * @param fn the number of incorrectly classified positives
+   */
+  public TwoClassStats(double tp, double fp, double tn, double fn) {
+      
+    setTruePositive(tp); 
+    setFalsePositive(fp);
+    setTrueNegative(tn); 
+    setFalseNegative(fn);
+  }
+
+  /** Sets the number of positive instances predicted as positive */
+  public void setTruePositive(double tp) { m_TruePos = tp; }
+
+  /** Sets the number of negative instances predicted as positive */
+  public void setFalsePositive(double fp) { m_FalsePos = fp; }
+
+  /** Sets the number of negative instances predicted as negative */
+  public void setTrueNegative(double tn) { m_TrueNeg = tn; }
+
+  /** Sets the number of positive instances predicted as negative */
+  public void setFalseNegative(double fn) { m_FalseNeg = fn; }
+
+  /** Gets the number of positive instances predicted as positive */
+  public double getTruePositive() { return m_TruePos; }
+
+  /** Gets the number of negative instances predicted as positive */
+  public double getFalsePositive() { return m_FalsePos; }
+
+  /** Gets the number of negative instances predicted as negative */
+  public double getTrueNegative() { return m_TrueNeg; }
+
+  /** Gets the number of positive instances predicted as negative */
+  public double getFalseNegative() { return m_FalseNeg; }
+
+  /**
+   * Calculate the true positive rate. 
+   * This is defined as<p>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *       total positives
+   * </pre>
+   *
+   * @return the true positive rate
+   */
+  public double getTruePositiveRate() { 
+    if (0 == (m_TruePos + m_FalseNeg)) {
+      return 0;
+    } else {
+      return m_TruePos / (m_TruePos + m_FalseNeg); 
+    }
+  }
+
+  /**
+   * Calculate the false positive rate. 
+   * This is defined as<p>
+   * <pre>
+   * incorrectly classified negatives
+   * --------------------------------
+   *        total negatives
+   * </pre>
+   *
+   * @return the false positive rate
+   */
+  public double getFalsePositiveRate() { 
+    if (0 == (m_FalsePos + m_TrueNeg)) {
+      return 0;
+    } else {
+      return m_FalsePos / (m_FalsePos + m_TrueNeg); 
+    }
+  }
+
+  /**
+   * Calculate the precision. 
+   * This is defined as<p>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *  total predicted as positive
+   * </pre>
+   *
+   * @return the precision
+   */
+  public double getPrecision() { 
+    if (0 == (m_TruePos + m_FalsePos)) {
+      return 0;
+    } else {
+      return m_TruePos / (m_TruePos + m_FalsePos); 
+    }
+  }
+
+  /**
+   * Calculate the recall. 
+   * This is defined as<p>
+   * <pre>
+   * correctly classified positives
+   * ------------------------------
+   *       total positives
+   * </pre><p>
+   * (Which is also the same as the truePositiveRate.)
+   *
+   * @return the recall
+   */
+  public double getRecall() { return getTruePositiveRate(); }
+
+  /**
+   * Calculate the F-Measure. 
+   * This is defined as<p>
+   * <pre>
+   * 2 * recall * precision
+   * ----------------------
+   *   recall + precision
+   * </pre>
+   *
+   * @return the F-Measure
+   */
+  public double getFMeasure() {
+
+    double precision = getPrecision();
+    double recall = getRecall();
+    if ((precision + recall) == 0) {
+      return 0;
+    }
+    return 2 * precision * recall / (precision + recall);
+  }
+
+  /**
+   * Calculate the fallout. 
+   * This is defined as<p>
+   * <pre>
+   * incorrectly classified negatives
+   * --------------------------------
+   *   total predicted as positive
+   * </pre>
+   *
+   * @return the fallout
+   */
+  public double getFallout() { 
+    if (0 == (m_TruePos + m_FalsePos)) {
+      return 0;
+    } else {
+      return m_FalsePos / (m_TruePos + m_FalsePos); 
+    }
+  }
+
+  /**
+   * Generates a <code>ConfusionMatrix</code> representing the current
+   * two-class statistics, using class names "negative" and "positive".
+   *
+   * @return a <code>ConfusionMatrix</code>.
+   */
+  public ConfusionMatrix getConfusionMatrix() {
+
+    ConfusionMatrix cm = new ConfusionMatrix(CATEGORY_NAMES);
+    cm.setElement(0, 0, m_TrueNeg);
+    cm.setElement(0, 1, m_FalsePos);
+    cm.setElement(1, 0, m_FalseNeg);
+    cm.setElement(1, 1, m_TruePos);
+    return cm;
+  }
+
+  /**
+   * Returns a string containing the various performance measures
+   * for the current object 
+   */
+  public String toString() {
+
+    StringBuffer res = new StringBuffer();
+    res.append(getTruePositive()).append(' ');
+    res.append(getFalseNegative()).append(' ');
+    res.append(getTrueNegative()).append(' ');
+    res.append(getFalsePositive()).append(' ');
+    res.append(getFalsePositiveRate()).append(' ');
+    res.append(getTruePositiveRate()).append(' ');
+    res.append(getPrecision()).append(' ');
+    res.append(getRecall()).append(' ');
+    res.append(getFMeasure()).append(' ');
+    res.append(getFallout()).append(' ');
+    return res.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/AbstractOutput.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/AbstractOutput.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/AbstractOutput.java	(revision 29)
@@ -0,0 +1,663 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AbstractOutput.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A superclass for outputting the classifications of a classifier.
+ * <p/>
+ * Basic use with a classifier and a test set:
+ * <pre>
+ * Classifier classifier = ... // trained classifier
+ * Instances testset = ... // the test set to output the predictions for
+ * StringBuffer buffer = ... // the string buffer to add the output to
+ * AbstractOutput output = new FunkyOutput();
+ * output.setHeader(...);
+ * output.printClassifications(classifier, testset);
+ * </pre>
+ * 
+ * Basic use with a classifier and a data source:
+ * <pre>
+ * Classifier classifier = ... // trained classifier
+ * DataSource testset = ... // the data source to obtain the test set from to output the predictions for
+ * StringBuffer buffer = ... // the string buffer to add the output to
+ * AbstractOutput output = new FunkyOutput();
+ * output.setHeader(...);
+ * output.printClassifications(classifier, testset);
+ * </pre>
+ * 
+ * In order to make the output generation easily integrate into GUI components,
+ * one can output the header, classifications and footer separately:
+ * <pre>
+ * Classifier classifier = ... // trained classifier
+ * Instances testset = ... // the test set to output the predictions for
+ * StringBuffer buffer = ... // the string buffer to add the output to
+ * AbstractOutput output = new FunkyOutput();
+ * output.setHeader(...);
+ * // print the header
+ * output.printHeader();
+ * // print the classifications one-by-one
+ * for (int i = 0; i &lt; testset.numInstances(); i++) {
+ *   output.printClassification(classifier, testset.instance(i), i);
+ *   // output progress information
+ *   if ((i+1) % 100 == 0)
+ *     System.out.println((i+1) + "/" + testset.numInstances());
+ * }
+ * // print the footer
+ * output.printFooter();
+ * </pre>
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5466 $
+ */
+public abstract class AbstractOutput
+  implements Serializable, OptionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 752696986017306241L;
+
+  /** the header of the dataset. */
+  protected Instances m_Header;
+
+  /** the buffer to write to. */
+  protected StringBuffer m_Buffer;
+  
+  /** the file buffer to write to. */
+  protected StringBuffer m_FileBuffer;
+  
+  /** whether to output the class distribution. */
+  protected boolean m_OutputDistribution;
+  
+  /** the range of attributes to output. */
+  protected Range m_Attributes;
+  
+  /** the number of decimals after the decimal point. */
+  protected int m_NumDecimals;
+
+  /** the file to store the output in. */
+  protected File m_OutputFile;
+  
+  /** whether to suppress the regular output and only store in file. */
+  protected boolean m_SuppressOutput;
+  
+  /**
+   * Initializes the output class. 
+   */
+  public AbstractOutput() {
+    m_Header             = null;
+    m_OutputDistribution = false;
+    m_Attributes         = null;
+    m_Buffer             = null;
+    m_NumDecimals        = 3;
+    m_OutputFile         = new File(".");
+    m_FileBuffer         = new StringBuffer();
+    m_SuppressOutput     = false;
+  }
+  
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public abstract String globalInfo();
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public abstract String getDisplay();
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    
+    result = new Vector();
+    
+    result.addElement(new Option(
+        "\tThe range of attributes to print in addition to the classification.\n"
+	+ "\t(default: none)",
+        "p", 1, "-p <range>"));
+    
+    result.addElement(new Option(
+        "\tWhether to turn on the output of the class distribution.\n"
+	+ "\tOnly for nominal class attributes.\n"
+	+ "\t(default: off)",
+        "distribution", 0, "-distribution"));
+    
+    result.addElement(new Option(
+        "\tThe number of digits after the decimal point.\n"
+	+ "\t(default: " + getDefaultNumDecimals() + ")",
+        "decimals", 1, "-decimals <num>"));
+    
+    result.addElement(new Option(
+        "\tThe file to store the output in, instead of outputting it on stdout.\n"
+	+ "\tGets ignored if the supplied path is a directory.\n"
+	+ "\t(default: .)",
+        "file", 1, "-file <path>"));
+    
+    result.addElement(new Option(
+        "\tIn case the data gets stored in a file, then this flag can be used\n"
+	+ "\tto suppress the regular output.\n"
+	+ "\t(default: not suppressed)",
+        "suppress", 0, "-suppress"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    setAttributes(Utils.getOption("p", options));
+    setOutputDistribution(Utils.getFlag("distribution", options));
+    
+    tmpStr = Utils.getOption("decimals", options);
+    if (tmpStr.length() > 0)
+      setNumDecimals(Integer.parseInt(tmpStr));
+    else
+      setNumDecimals(getDefaultNumDecimals());
+    
+    tmpStr = Utils.getOption("file", options);
+    if (tmpStr.length() > 0)
+      setOutputFile(new File(tmpStr));
+    else
+      setOutputFile(new File("."));
+    
+    setSuppressOutput(Utils.getFlag("suppress", options));
+  }
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    if (getAttributes().length() > 0) {
+      result.add("-p");
+      result.add(getAttributes());
+    }
+    
+    if (getOutputDistribution())
+      result.add("-distribution");
+
+    if (getNumDecimals() != getDefaultNumDecimals()) {
+      result.add("-decimals");
+      result.add("" + getNumDecimals());
+    }
+    
+    if (!getOutputFile().isDirectory()) {
+      result.add("-file");
+      result.add(getOutputFile().getAbsolutePath());
+      if (getSuppressOutput())
+	result.add("-suppress");
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Sets the header of the dataset.
+   * 
+   * @param value	the header
+   */
+  public void setHeader(Instances value) {
+    m_Header = new Instances(value, 0);
+  }
+  
+  /**
+   * Returns the header of the dataset.
+   * 
+   * @return		the header
+   */
+  public Instances getHeader() {
+    return m_Header;
+  }
+  
+  /**
+   * Sets the buffer to use.
+   * 
+   * @param value	the buffer
+   */
+  public void setBuffer(StringBuffer value) {
+    m_Buffer = value;
+  }
+  
+  /**
+   * Returns the current buffer.
+   * 
+   * @return		the buffer, can be null
+   */
+  public StringBuffer getBuffer() {
+    return m_Buffer;
+  }
+  
+  /**
+   * Sets the range of attributes to output.
+   * 
+   * @param value	the range
+   */
+  public void setAttributes(String value) {
+    if (value.length() == 0)
+      m_Attributes = null;
+    else
+      m_Attributes = new Range(value);
+  }
+  
+  /**
+   * Returns the range of attributes to output.
+   * 
+   * @return		the range
+   */
+  public String getAttributes() {
+    if (m_Attributes == null)
+      return "";
+    else
+      return m_Attributes.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String attributesTipText() {
+    return "The indices of the attributes to print in addition.";
+  }
+  
+  /**
+   * Sets whether to output the class distribution or not.
+   * 
+   * @param value	true if the class distribution is to be output as well
+   */
+  public void setOutputDistribution(boolean value) {
+    m_OutputDistribution = value;
+  }
+  
+  /**
+   * Returns whether to output the class distribution as well.
+   * 
+   * @return		true if the class distribution is output as well
+   */
+  public boolean getOutputDistribution() {
+    return m_OutputDistribution;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String outputDistributionTipText() {
+    return "Whether to ouput the class distribution as well (only nominal class attributes).";
+  }
+  
+  /**
+   * Returns the default number of digits to output after the decimal point.
+   * 
+   * @return		the default number of digits
+   */
+  public int getDefaultNumDecimals() {
+    return 3;
+  }
+  
+  /**
+   * Sets the number of digits to output after the decimal point.
+   * 
+   * @param value	the number of digits
+   */
+  public void setNumDecimals(int value) {
+    if (value >= 0)
+      m_NumDecimals = value;
+    else
+      System.err.println(
+	  "Number of decimals cannot be negative (provided: " + value + ")!");
+  }
+  
+  /**
+   * Returns the number of digits to output after the decimal point.
+   * 
+   * @return		the number of digits
+   */
+  public int getNumDecimals() {
+    return m_NumDecimals;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String numDecimalsTipText() {
+    return "The number of digits to output after the decimal point.";
+  }
+  
+  /**
+   * Sets the output file to write to. A directory disables this feature.
+   * 
+   * @param value	the file to write to or a directory
+   */
+  public void setOutputFile(File value) {
+    m_OutputFile = value;
+  }
+  
+  /**
+   * Returns the output file to write to. A directory if turned off.
+   * 
+   * @return		the file to write to or a directory
+   */
+  public File getOutputFile() {
+    return m_OutputFile;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String outputFileTipText() {
+    return "The file to write the generated output to (disabled if path is a directory).";
+  }
+  
+  /**
+   * Sets whether to the regular output is suppressed in case the output is 
+   * stored in a file.
+   * 
+   * @param value	true if the regular output is to be suppressed
+   */
+  public void setSuppressOutput(boolean value) {
+    m_SuppressOutput = value;
+  }
+  
+  /**
+   * Returns whether to the regular output is suppressed in case the output
+   * is stored in a file.
+   * 
+   * @return		true if the regular output is to be suppressed
+   */
+  public boolean getSuppressOutput() {
+    return m_SuppressOutput;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String suppressOutputTipText() {
+    return "Whether to suppress the regular output when storing the output in a file.";
+  }
+  
+  /**
+   * Performs basic checks.
+   * 
+   * @return		null if everything is in order, otherwise the error message
+   */
+  protected String checkBasic() {
+    if (m_Buffer == null)
+      return "Buffer is null!";
+    
+    if (m_Header == null)
+      return "No dataset structure provided!";
+    
+    if (m_Attributes != null)
+      m_Attributes.setUpper(m_Header.numAttributes() - 1);
+    
+    return null;
+  }
+
+  /**
+   * Returns whether regular output is generated or not.
+   * 
+   * @return		true if regular output is generated
+   */
+  public boolean generatesOutput() {
+    return    m_OutputFile.isDirectory() 
+           || (!m_OutputFile.isDirectory() && !m_SuppressOutput);
+  }
+  
+  /**
+   * If an output file was defined, then the string gets added to the file 
+   * buffer, otherwise to the actual buffer.
+   * 
+   * @param s		the string to append
+   * @see		#m_Buffer
+   * @see		#m_FileBuffer
+   */
+  protected void append(String s) {
+    if (generatesOutput())
+      m_Buffer.append(s);
+    if (!m_OutputFile.isDirectory())
+      m_FileBuffer.append(s);
+  }
+  
+  /**
+   * Performs checks whether everything is correctly setup for the header.
+   * 
+   * @return		null if everything is in order, otherwise the error message
+   */
+  protected String checkHeader() {
+    return checkBasic();
+  }
+  
+  /**
+   * Performs the actual printing of the header.
+   */
+  protected abstract void doPrintHeader();
+  
+  /**
+   * Prints the header to the buffer.
+   */
+  public void printHeader() {
+    String	error;
+    
+    if ((error = checkHeader()) != null)
+      throw new IllegalStateException(error);
+    
+    doPrintHeader();
+  }
+  
+  /**
+   * Performs the actual printing of the classification.
+   * 
+   * @param classifier	the classifier to use for printing the classification
+   * @param inst	the instance to print
+   * @param index	the index of the instance
+   * @throws Exception	if printing of classification fails
+   */
+  protected abstract void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception;
+  
+  /**
+   * Prints the classification to the buffer.
+   * 
+   * @param classifier	the classifier to use for printing the classification
+   * @param inst	the instance to print
+   * @param index	the index of the instance
+   * @throws Exception	if check fails or error occurs during printing of classification
+   */
+  public void printClassification(Classifier classifier, Instance inst, int index) throws Exception {
+    String	error;
+    
+    if ((error = checkBasic()) != null)
+      throw new WekaException(error);
+    
+    doPrintClassification(classifier, inst, index);
+  }
+  
+  /**
+   * Prints the classifications to the buffer.
+   * 
+   * @param classifier	the classifier to use for printing the classifications
+   * @param testset	the data source to obtain the test instances from
+   * @throws Exception	if check fails or error occurs during printing of classifications
+   */
+  public void printClassifications(Classifier classifier, DataSource testset) throws Exception {
+    int 	i;
+    Instances 	test;
+    Instance 	inst;
+    
+    i = 0;
+    testset.reset();
+    test = testset.getStructure(m_Header.classIndex());
+    while (testset.hasMoreElements(test)) {
+      inst = testset.nextElement(test);
+      doPrintClassification(classifier, inst, i);
+      i++;
+    }
+  }
+  
+  /**
+   * Prints the classifications to the buffer.
+   * 
+   * @param classifier	the classifier to use for printing the classifications
+   * @param testset	the test instances
+   * @throws Exception	if check fails or error occurs during printing of classifications
+   */
+  public void printClassifications(Classifier classifier, Instances testset) throws Exception {
+    int 	i;
+
+    for (i = 0; i < testset.numInstances(); i++)
+      doPrintClassification(classifier, testset.instance(i), i);
+  }
+  
+  /**
+   * Performs the actual printing of the footer.
+   */
+  protected abstract void doPrintFooter();
+  
+  /**
+   * Prints the footer to the buffer. This will also store the generated 
+   * output in a file if an output file was specified.
+   * 
+   * @throws Exception	if check fails
+   */
+  public void printFooter() throws Exception {
+    String		error;
+    BufferedWriter	writer;
+    
+    if ((error = checkBasic()) != null)
+      throw new WekaException(error);
+    
+    doPrintFooter();
+    
+    // write output to file
+    if (!m_OutputFile.isDirectory()) {
+      try {
+	writer = new BufferedWriter(new FileWriter(m_OutputFile));
+	writer.write(m_FileBuffer.toString());
+	writer.newLine();
+	writer.flush();
+	writer.close();
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * Prints the header, classifications and footer to the buffer.
+   * 
+   * @param classifier	the classifier to use for printing the classifications
+   * @param testset	the data source to obtain the test instances from
+   * @throws Exception	if check fails or error occurs during printing of classifications
+   */
+  public void print(Classifier classifier, DataSource testset) throws Exception {
+    printHeader();
+    printClassifications(classifier, testset);
+    printFooter();
+  }
+  
+  /**
+   * Prints the header, classifications and footer to the buffer.
+   * 
+   * @param classifier	the classifier to use for printing the classifications
+   * @param testset	the test instances
+   * @throws Exception	if check fails or error occurs during printing of classifications
+   */
+  public void print(Classifier classifier, Instances testset) throws Exception {
+    printHeader();
+    printClassifications(classifier, testset);
+    printFooter();
+  }
+  
+  /**
+   * Returns a fully configured object from the given commandline.
+   * 
+   * @param cmdline	the commandline to turn into an object
+   * @return		the object or null in case of an error
+   */
+  public static AbstractOutput fromCommandline(String cmdline) {
+    AbstractOutput	result;
+    String[]				options;
+    String				classname;
+    
+    try {
+      options    = Utils.splitOptions(cmdline);
+      classname  = options[0];
+      options[0] = "";
+      result     = (AbstractOutput) Utils.forName(AbstractOutput.class, classname, options);
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/CSV.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/CSV.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/CSV.java	(revision 29)
@@ -0,0 +1,334 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CSV.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Outputs the predictions as CSV.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -p &lt;range&gt;
+ *  The range of attributes to print in addition to the classification.
+ *  (default: none)</pre>
+ * 
+ * <pre> -distribution
+ *  Whether to turn on the output of the class distribution.
+ *  Only for nominal class attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -decimals &lt;num&gt;
+ *  The number of digits after the decimal point.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -file &lt;path&gt;
+ *  The file to store the output in, instead of outputting it on stdout.
+ *  Gets ignored if the supplied path is a directory.
+ *  (default: .)</pre>
+ * 
+ * <pre> -suppress
+ *  In case the data gets stored in a file, then this flag can be used
+ *  to suppress the regular output.
+ *  (default: not suppressed)</pre>
+ * 
+ * <pre> -use-tab
+ *  Whether to use TAB as separator instead of comma.
+ *  (default: comma)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class CSV
+  extends AbstractOutput {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 3401604538169573720L;
+
+  /** the delimiter. */
+  protected String m_Delimiter = ",";
+  
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public String globalInfo() {
+    return "Outputs the predictions as CSV.";
+  }
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public String getDisplay() {
+    return "CSV";
+  }
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    Enumeration	enm;
+    
+    result = new Vector();
+    
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+    
+    result.addElement(new Option(
+        "\tWhether to use TAB as separator instead of comma.\n"
+	+ "\t(default: comma)",
+        "use-tab", 0, "-use-tab"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setUseTab(Utils.getFlag("use-tab", options));
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getUseTab())
+      result.add("-use-tab");
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Sets whether to use tab instead of comma as separator.
+   * 
+   * @param value	true if tab is to be used
+   */
+  public void setUseTab(boolean value) {
+    if (value)
+      m_Delimiter = "\t";
+    else
+      m_Delimiter = ",";
+  }
+  
+  /**
+   * Returns whether tab is used as separator.
+   * 
+   * @return		true if tab is used instead of comma
+   */
+  public boolean getUseTab() {
+    return m_Delimiter.equals("\t");
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the GUI
+   */
+  public String useTabTipText() {
+    return "Whether to use TAB instead of COMMA as column separator.";
+  }
+
+  /**
+   * Performs the actual printing of the header.
+   */
+  protected void doPrintHeader() {
+    if (m_Header.classAttribute().isNominal()) {
+      if (m_OutputDistribution) {
+	append("inst#" + m_Delimiter + "actual" + m_Delimiter + "predicted" + m_Delimiter + "error" + m_Delimiter + "distribution");
+	for (int i = 1; i < m_Header.classAttribute().numValues(); i++)
+	  append(m_Delimiter);
+      }
+      else {
+	append("inst#" + m_Delimiter + "actual" + m_Delimiter + "predicted" + m_Delimiter + "error" + m_Delimiter + "prediction");
+      }
+    }
+    else {
+      append("inst#" + m_Delimiter + "actual" + m_Delimiter + "predicted" + m_Delimiter + "error");
+    }
+    
+    if (m_Attributes != null) {
+      append(m_Delimiter);
+      boolean first = true;
+      for (int i = 0; i < m_Header.numAttributes(); i++) {
+        if (i == m_Header.classIndex())
+          continue;
+
+        if (m_Attributes.isInRange(i)) {
+          if (!first)
+            append(m_Delimiter);
+          append(m_Header.attribute(i).name());
+          first = false;
+        }
+      }
+    }
+    
+    append("\n");
+  }
+
+  /**
+   * Builds a string listing the attribute values in a specified range of indices,
+   * separated by commas and enclosed in brackets.
+   *
+   * @param instance 	the instance to print the values from
+   * @return 		a string listing values of the attributes in the range
+   */
+  protected String attributeValuesString(Instance instance) {
+    StringBuffer text = new StringBuffer();
+    if (m_Attributes != null) {
+      m_Attributes.setUpper(instance.numAttributes() - 1);
+      boolean first = true;
+      for (int i=0; i<instance.numAttributes(); i++)
+	if (m_Attributes.isInRange(i) && i != instance.classIndex()) {
+	  if (!first)
+	    text.append(m_Delimiter);
+	  text.append(instance.toString(i));
+	  first = false;
+	}
+    }
+    return text.toString();
+  }
+
+  /**
+   * Store the prediction made by the classifier as a string.
+   * 
+   * @param classifier	the classifier to use
+   * @param inst	the instance to generate text from
+   * @param index	the index in the dataset
+   * @throws Exception	if something goes wrong
+   */
+  protected void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception {
+    int prec = m_NumDecimals;
+
+    Instance withMissing = (Instance)inst.copy();
+    withMissing.setDataset(inst.dataset());
+    withMissing.setMissing(withMissing.classIndex());
+    double predValue = classifier.classifyInstance(withMissing);
+
+    // index
+    append("" + (index+1));
+
+    if (inst.dataset().classAttribute().isNumeric()) {
+      // actual
+      if (inst.classIsMissing())
+	append(m_Delimiter + "?");
+      else
+	append(m_Delimiter + Utils.doubleToString(inst.classValue(), prec));
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append(m_Delimiter + "?");
+      else
+	append(m_Delimiter + Utils.doubleToString(predValue, prec));
+      // error
+      if (Utils.isMissingValue(predValue) || inst.classIsMissing())
+	append(m_Delimiter + "?");
+      else
+	append(m_Delimiter + Utils.doubleToString(predValue - inst.classValue(), prec));
+    } else {
+      // actual
+      append(m_Delimiter + ((int) inst.classValue()+1) + ":" + inst.toString(inst.classIndex()));
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append(m_Delimiter + "?");
+      else
+	append(m_Delimiter + ((int) predValue+1) + ":" + inst.dataset().classAttribute().value((int)predValue));
+      // error?
+      if (!Utils.isMissingValue(predValue) && !inst.classIsMissing() && ((int) predValue+1 != (int) inst.classValue()+1))
+	append(m_Delimiter + "+");
+      else
+	append(m_Delimiter + "");
+      // prediction/distribution
+      if (m_OutputDistribution) {
+	if (Utils.isMissingValue(predValue)) {
+	  append(m_Delimiter + "?");
+	}
+	else {
+	  append(m_Delimiter);
+	  double[] dist = classifier.distributionForInstance(withMissing);
+	  for (int n = 0; n < dist.length; n++) {
+	    if (n > 0)
+	      append(m_Delimiter);
+	    if (n == (int) predValue)
+	      append("*");
+            append(Utils.doubleToString(dist[n], prec));
+	  }
+	}
+      }
+      else {
+	if (Utils.isMissingValue(predValue))
+	  append(m_Delimiter + "?");
+	else
+	  append(m_Delimiter + Utils.doubleToString(classifier.distributionForInstance(withMissing) [(int)predValue], prec));
+      }
+    }
+
+    // attributes
+    if (m_Attributes != null)
+      append(m_Delimiter + attributeValuesString(withMissing));
+    append("\n");
+  }
+  
+  /**
+   * Does nothing.
+   */
+  protected void doPrintFooter() {
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/HTML.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/HTML.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/HTML.java	(revision 29)
@@ -0,0 +1,266 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * HTML.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Outputs the predictions in HTML.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -p &lt;range&gt;
+ *  The range of attributes to print in addition to the classification.
+ *  (default: none)</pre>
+ * 
+ * <pre> -distribution
+ *  Whether to turn on the output of the class distribution.
+ *  Only for nominal class attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -decimals &lt;num&gt;
+ *  The number of digits after the decimal point.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -file &lt;path&gt;
+ *  The file to store the output in, instead of outputting it on stdout.
+ *  Gets ignored if the supplied path is a directory.
+ *  (default: .)</pre>
+ * 
+ * <pre> -suppress
+ *  In case the data gets stored in a file, then this flag can be used
+ *  to suppress the regular output.
+ *  (default: not suppressed)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class HTML
+  extends AbstractOutput {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 7241252244954353300L;
+
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public String globalInfo() {
+    return "Outputs the predictions in HTML.";
+  }
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public String getDisplay() {
+    return "HTML";
+  }
+
+  /**
+   * Replaces certain characters with their HTML entities.
+   * 
+   * @param s		the string to process
+   * @return		the processed string
+   */
+  protected String sanitize(String s) {
+    String 	result;
+    
+    result = s;
+    result = result.replaceAll("&", "&amp;");
+    result = result.replaceAll("<", "&lt;");
+    result = result.replaceAll(">", "&gt;");
+    result = result.replaceAll("\"", "&quot;");
+    
+    return result;
+  }
+  
+  /**
+   * Performs the actual printing of the header.
+   */
+  protected void doPrintHeader() {
+    append("<html>\n");
+    append("<head>\n");
+    append("<title>Predictions for dataset " + sanitize(m_Header.relationName()) + "</title>\n");
+    append("</head>\n");
+    append("<body>\n");
+    append("<div align=\"center\">\n");
+    append("<h3>Predictions for dataset " + sanitize(m_Header.relationName()) + "</h3>\n");
+    append("<table border=\"1\">\n");
+    append("<tr>\n");
+    if (m_Header.classAttribute().isNominal())
+      if (m_OutputDistribution)
+	append("<td>inst#</td><td>actual</td><td>predicted</td><td>error</td><td colspan=\"" + m_Header.classAttribute().numValues() + "\">distribution</td>");
+      else
+	append("<td>inst#</td><td>actual</td><td>predicted</td><td>error</td><td>prediction</td>");
+    else
+      append("<td>inst#</td><td>actual</td><td>predicted</td><td>error</td>");
+    
+    if (m_Attributes != null) {
+      append("<td>");
+      boolean first = true;
+      for (int i = 0; i < m_Header.numAttributes(); i++) {
+        if (i == m_Header.classIndex())
+          continue;
+
+        if (m_Attributes.isInRange(i)) {
+          if (!first)
+            append("</td><td>");
+          append(sanitize(m_Header.attribute(i).name()));
+          first = false;
+        }
+      }
+      append("</td>");
+    }
+    
+    append("</tr>\n");
+  }
+
+  /**
+   * Builds a string listing the attribute values in a specified range of indices,
+   * separated by commas and enclosed in brackets.
+   *
+   * @param instance 	the instance to print the values from
+   * @return 		a string listing values of the attributes in the range
+   */
+  protected String attributeValuesString(Instance instance) {
+    StringBuffer text = new StringBuffer();
+    if (m_Attributes != null) {
+      boolean firstOutput = true;
+      m_Attributes.setUpper(instance.numAttributes() - 1);
+      for (int i=0; i<instance.numAttributes(); i++)
+	if (m_Attributes.isInRange(i) && i != instance.classIndex()) {
+	  if (!firstOutput)
+	    text.append("</td>");
+	  if (m_Header.attribute(i).isNumeric())
+	    text.append("<td align=\"right\">");
+	  else
+	    text.append("<td>");
+	  text.append(sanitize(instance.toString(i)));
+	  firstOutput = false;
+	}
+      if (!firstOutput)
+	text.append("</td>");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Store the prediction made by the classifier as a string.
+   * 
+   * @param classifier	the classifier to use
+   * @param inst	the instance to generate text from
+   * @param index	the index in the dataset
+   * @throws Exception	if something goes wrong
+   */
+  protected void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception {
+    int prec = m_NumDecimals;
+
+    Instance withMissing = (Instance)inst.copy();
+    withMissing.setDataset(inst.dataset());
+    withMissing.setMissing(withMissing.classIndex());
+    double predValue = classifier.classifyInstance(withMissing);
+
+    // index
+    append("<tr>");
+    append("<td>" + (index+1) + "</td>");
+
+    if (inst.dataset().classAttribute().isNumeric()) {
+      // actual
+      if (inst.classIsMissing())
+	append("<td align=\"right\">" + "?" + "</td>");
+      else
+	append("<td align=\"right\">" + Utils.doubleToString(inst.classValue(), prec) + "</td>");
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append("<td align=\"right\">" + "?" + "</td>");
+      else
+	append("<td align=\"right\">" + Utils.doubleToString(predValue, prec) + "</td>");
+      // error
+      if (Utils.isMissingValue(predValue) || inst.classIsMissing())
+	append("<td align=\"right\">" + "?" + "</td>");
+      else
+	append("<td align=\"right\">" + Utils.doubleToString(predValue - inst.classValue(), prec) + "</td>");
+    } else {
+      // actual
+      append("<td>" + ((int) inst.classValue()+1) + ":" + sanitize(inst.toString(inst.classIndex())) + "</td>");
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append("<td>" + "?" + "</td>");
+      else
+	append("<td>" + ((int) predValue+1) + ":" + sanitize(inst.dataset().classAttribute().value((int)predValue)) + "</td>");
+      // error?
+      if (!Utils.isMissingValue(predValue) && !inst.classIsMissing() && ((int) predValue+1 != (int) inst.classValue()+1))
+	append("<td>" + "+" + "</td>");
+      else
+	append("<td>" + "&nbsp;" + "</td>");
+      // prediction/distribution
+      if (m_OutputDistribution) {
+	if (Utils.isMissingValue(predValue)) {
+	  append("<td>" + "?" + "</td>");
+	}
+	else {
+	  append("<td align=\"right\">");
+	  double[] dist = classifier.distributionForInstance(withMissing);
+	  for (int n = 0; n < dist.length; n++) {
+	    if (n > 0)
+	      append("</td><td align=\"right\">");
+	    if (n == (int) predValue)
+	      append("*");
+            append(Utils.doubleToString(dist[n], prec));
+	  }
+	  append("</td>");
+	}
+      }
+      else {
+	if (Utils.isMissingValue(predValue))
+	  append("<td align=\"right\">" + "?" + "</td>");
+	else
+	  append("<td align=\"right\">" + Utils.doubleToString(classifier.distributionForInstance(withMissing) [(int)predValue], prec) + "</td>");
+      }
+    }
+
+    // attributes
+    append(attributeValuesString(withMissing) + "</tr>\n");
+  }
+  
+  /**
+   * Does nothing.
+   */
+  protected void doPrintFooter() {
+    append("</table>\n");
+    append("</div>\n");
+    append("</body>\n");
+    append("</html>\n");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/Null.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/Null.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/Null.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Null.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+
+/**
+ <!-- globalinfo-start -->
+ * Suppresses all output.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -p &lt;range&gt;
+ *  The range of attributes to print in addition to the classification.
+ *  (default: none)</pre>
+ * 
+ * <pre> -distribution
+ *  Whether to turn on the output of the class distribution.
+ *  Only for nominal class attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -decimals &lt;num&gt;
+ *  The number of digits after the decimal point.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -file &lt;path&gt;
+ *  The file to store the output in, instead of outputting it on stdout.
+ *  Gets ignored if the supplied path is a directory.
+ *  (default: .)</pre>
+ * 
+ * <pre> -suppress
+ *  In case the data gets stored in a file, then this flag can be used
+ *  to suppress the regular output.
+ *  (default: not suppressed)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5466 $
+ */
+public class Null
+  extends AbstractOutput {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 4988413155999044966L;
+
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public String globalInfo() {
+    return "Suppresses all output.";
+  }
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public String getDisplay() {
+    return "No output";
+  }
+
+  /**
+   * Returns always false.
+   * 
+   * @return		always false
+   */
+  public boolean generatesOutput() {
+    return false;
+  }
+
+  /**
+   * Does nothing.
+   */
+  protected void doPrintHeader() {
+  }
+
+  /**
+   * Does nothing.
+   * 
+   * @param classifier	the classifier to use
+   * @param inst	the instance to generate text from
+   * @param index	the index in the dataset
+   * @throws Exception	if something goes wrong
+   */
+  protected void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception {
+  }
+  
+  /**
+   * Does nothing.
+   */
+  protected void doPrintFooter() {
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/PlainText.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/PlainText.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/PlainText.java	(revision 29)
@@ -0,0 +1,229 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PlainText.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Outputs the predictions in plain text.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -p &lt;range&gt;
+ *  The range of attributes to print in addition to the classification.
+ *  (default: none)</pre>
+ * 
+ * <pre> -distribution
+ *  Whether to turn on the output of the class distribution.
+ *  Only for nominal class attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -decimals &lt;num&gt;
+ *  The number of digits after the decimal point.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -file &lt;path&gt;
+ *  The file to store the output in, instead of outputting it on stdout.
+ *  Gets ignored if the supplied path is a directory.
+ *  (default: .)</pre>
+ * 
+ * <pre> -suppress
+ *  In case the data gets stored in a file, then this flag can be used
+ *  to suppress the regular output.
+ *  (default: not suppressed)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class PlainText
+  extends AbstractOutput {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2033389864898242735L;
+  
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public String globalInfo() {
+    return "Outputs the predictions in plain text.";
+  }
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public String getDisplay() {
+    return "Plain text";
+  }
+
+  /**
+   * Performs the actual printing of the header.
+   */
+  protected void doPrintHeader() {
+    if (m_Header.classAttribute().isNominal())
+      if (m_OutputDistribution)
+	append(" inst#     actual  predicted error distribution");
+      else
+	append(" inst#     actual  predicted error prediction");
+    else
+      append(" inst#     actual  predicted      error");
+    
+    if (m_Attributes != null) {
+      append(" (");
+      boolean first = true;
+      for (int i = 0; i < m_Header.numAttributes(); i++) {
+        if (i == m_Header.classIndex())
+          continue;
+
+        if (m_Attributes.isInRange(i)) {
+          if (!first)
+            append(",");
+          append(m_Header.attribute(i).name());
+          first = false;
+        }
+      }
+      append(")");
+    }
+    
+    append("\n");
+  }
+
+  /**
+   * Builds a string listing the attribute values in a specified range of indices,
+   * separated by commas and enclosed in brackets.
+   *
+   * @param instance 	the instance to print the values from
+   * @return 		a string listing values of the attributes in the range
+   */
+  protected String attributeValuesString(Instance instance) {
+    StringBuffer text = new StringBuffer();
+    if (m_Attributes != null) {
+      boolean firstOutput = true;
+      m_Attributes.setUpper(instance.numAttributes() - 1);
+      for (int i=0; i<instance.numAttributes(); i++)
+	if (m_Attributes.isInRange(i) && i != instance.classIndex()) {
+	  if (firstOutput) text.append("(");
+	  else text.append(",");
+	  text.append(instance.toString(i));
+	  firstOutput = false;
+	}
+      if (!firstOutput) text.append(")");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Store the prediction made by the classifier as a string.
+   * 
+   * @param classifier	the classifier to use
+   * @param inst	the instance to generate text from
+   * @param index	the index in the dataset
+   * @throws Exception	if something goes wrong
+   */
+  protected void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception {
+    int width = 7 + m_NumDecimals;
+    int prec = m_NumDecimals;
+
+    Instance withMissing = (Instance)inst.copy();
+    withMissing.setDataset(inst.dataset());
+    withMissing.setMissing(withMissing.classIndex());
+    double predValue = classifier.classifyInstance(withMissing);
+
+    // index
+    append(Utils.padLeft("" + (index+1), 6));
+
+    if (inst.dataset().classAttribute().isNumeric()) {
+      // actual
+      if (inst.classIsMissing())
+	append(" " + Utils.padLeft("?", width));
+      else
+	append(" " + Utils.doubleToString(inst.classValue(), width, prec));
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append(" " + Utils.padLeft("?", width));
+      else
+	append(" " + Utils.doubleToString(predValue, width, prec));
+      // error
+      if (Utils.isMissingValue(predValue) || inst.classIsMissing())
+	append(" " + Utils.padLeft("?", width));
+      else
+	append(" " + Utils.doubleToString(predValue - inst.classValue(), width, prec));
+    } else {
+      // actual
+      append(" " + Utils.padLeft(((int) inst.classValue()+1) + ":" + inst.toString(inst.classIndex()), width));
+      // predicted
+      if (Utils.isMissingValue(predValue))
+	append(" " + Utils.padLeft("?", width));
+      else
+	append(" " + Utils.padLeft(((int) predValue+1) + ":" + inst.dataset().classAttribute().value((int)predValue), width));
+      // error?
+      if (!Utils.isMissingValue(predValue) && !inst.classIsMissing() && ((int) predValue+1 != (int) inst.classValue()+1))
+	append(" " + "  +  ");
+      else
+	append(" " + "     ");
+      // prediction/distribution
+      if (m_OutputDistribution) {
+	if (Utils.isMissingValue(predValue)) {
+	  append(" " + "?");
+	}
+	else {
+	  append(" ");
+	  double[] dist = classifier.distributionForInstance(withMissing);
+	  for (int n = 0; n < dist.length; n++) {
+	    if (n > 0)
+	      append(",");
+	    if (n == (int) predValue)
+	      append("*");
+            append(Utils.doubleToString(dist[n], prec));
+	  }
+	}
+      }
+      else {
+	if (Utils.isMissingValue(predValue))
+	  append(" " + "?");
+	else
+	  append(" " + Utils.doubleToString(classifier.distributionForInstance(withMissing) [(int)predValue], prec));
+      }
+    }
+
+    // attributes
+    append(" " + attributeValuesString(withMissing) + "\n");
+  }
+  
+  /**
+   * Does nothing.
+   */
+  protected void doPrintFooter() {
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/XML.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/XML.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/evaluation/output/prediction/XML.java	(revision 29)
@@ -0,0 +1,403 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XML.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.evaluation.output.prediction;
+
+import weka.classifiers.Classifier;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.xml.XMLDocument;
+
+/**
+ <!-- globalinfo-start -->
+ * Outputs the predictions in XML.<br/>
+ * <br/>
+ * The following DTD is used:<br/>
+ * <br/>
+ * &lt;!DOCTYPE predictions<br/>
+ * [<br/>
+ *   &lt;!ELEMENT predictions (prediction*)&gt;<br/>
+ *   &lt;!ATTLIST predictions version CDATA "3.5.8"&gt;<br/>
+ *   &lt;!ATTLIST predictions name CDATA #REQUIRED&gt;<br/>
+ * <br/>
+ *   &lt;!ELEMENT prediction ((actual_label,predicted_label,error,(prediction|distribution),attributes?)|(actual_value,predicted_value,error,attributes?))&gt;<br/>
+ *   &lt;!ATTLIST prediction index CDATA #REQUIRED&gt;<br/>
+ * <br/>
+ *   &lt;!ELEMENT actual_label ANY&gt;<br/>
+ *   &lt;!ATTLIST actual_label index CDATA #REQUIRED&gt;<br/>
+ *   &lt;!ELEMENT predicted_label ANY&gt;<br/>
+ *   &lt;!ATTLIST predicted_label index CDATA #REQUIRED&gt;<br/>
+ *   &lt;!ELEMENT error ANY&gt;<br/>
+ *   &lt;!ELEMENT prediction ANY&gt;<br/>
+ *   &lt;!ELEMENT distribution (class_label+)&gt;<br/>
+ *   &lt;!ELEMENT class_label ANY&gt;<br/>
+ *   &lt;!ATTLIST class_label index CDATA #REQUIRED&gt;<br/>
+ *   &lt;!ATTLIST class_label predicted (yes|no) "no"&gt;<br/>
+ *   &lt;!ELEMENT actual_value ANY&gt;<br/>
+ *   &lt;!ELEMENT predicted_value ANY&gt;<br/>
+ *   &lt;!ELEMENT attributes (attribute+)&gt;<br/>
+ *   &lt;!ELEMENT attribute ANY&gt;<br/>
+ *   &lt;!ATTLIST attribute index CDATA #REQUIRED&gt;<br/>
+ *   &lt;!ATTLIST attribute name CDATA #REQUIRED&gt;<br/>
+ *   &lt;!ATTLIST attribute type (numeric|date|nominal|string|relational) #REQUIRED&gt;<br/>
+ * ]<br/>
+ * &gt;
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -p &lt;range&gt;
+ *  The range of attributes to print in addition to the classification.
+ *  (default: none)</pre>
+ * 
+ * <pre> -distribution
+ *  Whether to turn on the output of the class distribution.
+ *  Only for nominal class attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -decimals &lt;num&gt;
+ *  The number of digits after the decimal point.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -file &lt;path&gt;
+ *  The file to store the output in, instead of outputting it on stdout.
+ *  Gets ignored if the supplied path is a directory.
+ *  (default: .)</pre>
+ * 
+ * <pre> -suppress
+ *  In case the data gets stored in a file, then this flag can be used
+ *  to suppress the regular output.
+ *  (default: not suppressed)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class XML
+  extends AbstractOutput {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -3165514277316824801L;
+
+  /** the DocType definition. */
+  public final static String DTD_DOCTYPE = XMLDocument.DTD_DOCTYPE;
+  
+  /** the Element definition. */
+  public final static String DTD_ELEMENT = XMLDocument.DTD_ELEMENT;
+  
+  /** the AttList definition. */
+  public final static String DTD_ATTLIST = XMLDocument.DTD_ATTLIST;
+  
+  /** the optional marker. */
+  public final static String DTD_OPTIONAL = XMLDocument.DTD_OPTIONAL;
+  
+  /** the at least one marker. */
+  public final static String DTD_AT_LEAST_ONE = XMLDocument.DTD_AT_LEAST_ONE;
+  
+  /** the zero or more marker. */
+  public final static String DTD_ZERO_OR_MORE = XMLDocument.DTD_ZERO_OR_MORE;
+  
+  /** the option separator. */
+  public final static String DTD_SEPARATOR = XMLDocument.DTD_SEPARATOR;
+  
+  /** the CDATA placeholder. */
+  public final static String DTD_CDATA = XMLDocument.DTD_CDATA; 
+  
+  /** the ANY placeholder. */
+  public final static String DTD_ANY = XMLDocument.DTD_ANY; 
+  
+  /** the #PCDATA placeholder. */
+  public final static String DTD_PCDATA = XMLDocument.DTD_PCDATA; 
+  
+  /** the #IMPLIED placeholder. */
+  public final static String DTD_IMPLIED = XMLDocument.DTD_IMPLIED; 
+  
+  /** the #REQUIRED placeholder. */
+  public final static String DTD_REQUIRED = XMLDocument.DTD_REQUIRED; 
+
+  /** the "version" attribute. */
+  public final static String ATT_VERSION = XMLDocument.ATT_VERSION;
+ 
+  /** the "name" attribute. */
+  public final static String ATT_NAME = XMLDocument.ATT_NAME;
+  
+  /** the "type" attribute. */
+  public final static String ATT_TYPE = "type";
+
+  /** the value "yes". */
+  public final static String VAL_YES = XMLDocument.VAL_YES;
+  
+  /** the value "no". */
+  public final static String VAL_NO = XMLDocument.VAL_NO;
+  
+  /** the predictions tag. */
+  public final static String TAG_PREDICTIONS = "predictions";
+  
+  /** the prediction tag. */
+  public final static String TAG_PREDICTION = "prediction";
+
+  /** the actual_nominal tag. */
+  public final static String TAG_ACTUAL_LABEL = "actual_label";
+
+  /** the predicted_nominal tag. */
+  public final static String TAG_PREDICTED_LABEL = "predicted_label";
+
+  /** the error tag. */
+  public final static String TAG_ERROR = "error";
+
+  /** the distribution tag. */
+  public final static String TAG_DISTRIBUTION = "distribution";
+
+  /** the class_label tag. */
+  public final static String TAG_CLASS_LABEL = "class_label";
+
+  /** the actual_numeric tag. */
+  public final static String TAG_ACTUAL_VALUE = "actual_value";
+
+  /** the predicted_numeric tag. */
+  public final static String TAG_PREDICTED_VALUE = "predicted_value";
+
+  /** the attributes tag. */
+  public final static String TAG_ATTRIBUTES = "attributes";
+
+  /** the attribute tag. */
+  public final static String TAG_ATTRIBUTE = "attribute";
+
+  /** the index attribute. */
+  public final static String ATT_INDEX = "index";
+
+  /** the predicted attribute. */
+  public final static String ATT_PREDICTED = "predicted";
+  
+  /** the DTD. */
+  public final static String DTD = 
+    "<!" + DTD_DOCTYPE + " " + TAG_PREDICTIONS + "\n"
+    + "[\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_PREDICTIONS + " (" + TAG_PREDICTION + DTD_ZERO_OR_MORE + ")" + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_PREDICTIONS + " " + ATT_VERSION + " " + DTD_CDATA + " \"" + Version.VERSION + "\"" + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_PREDICTIONS + " " + ATT_NAME + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_PREDICTION + " " 
+             + "(" 
+             + "(" + TAG_ACTUAL_LABEL + "," + TAG_PREDICTED_LABEL + "," + TAG_ERROR + "," + "(" + TAG_PREDICTION + DTD_SEPARATOR + TAG_DISTRIBUTION + ")" + "," + TAG_ATTRIBUTES + DTD_OPTIONAL + ")" 
+             + DTD_SEPARATOR
+             + "(" + TAG_ACTUAL_VALUE + "," + TAG_PREDICTED_VALUE + "," + TAG_ERROR + "," + TAG_ATTRIBUTES + DTD_OPTIONAL + ")"
+             + ")" + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_PREDICTION + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_ACTUAL_LABEL + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_ACTUAL_LABEL + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_PREDICTED_LABEL + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_PREDICTED_LABEL + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_ERROR + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_PREDICTION + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_DISTRIBUTION + " (" + TAG_CLASS_LABEL + DTD_AT_LEAST_ONE + ")" + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_CLASS_LABEL + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_CLASS_LABEL + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_CLASS_LABEL + " " + ATT_PREDICTED + " (" + VAL_YES + DTD_SEPARATOR + VAL_NO + ") " + "\"" + VAL_NO + "\"" + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_ACTUAL_VALUE + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_PREDICTED_VALUE + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_ATTRIBUTES + " (" + TAG_ATTRIBUTE + DTD_AT_LEAST_ONE + ")" + ">\n"
+    + "  <!" + DTD_ELEMENT + " " + TAG_ATTRIBUTE + " " + DTD_ANY + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_NAME + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "  <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_TYPE + " " + "(" + Attribute.typeToString(Attribute.NUMERIC) + DTD_SEPARATOR + Attribute.typeToString(Attribute.DATE) + DTD_SEPARATOR + Attribute.typeToString(Attribute.NOMINAL) + DTD_SEPARATOR + Attribute.typeToString(Attribute.STRING) + DTD_SEPARATOR + Attribute.typeToString(Attribute.RELATIONAL) + ")" + " " + DTD_REQUIRED + ">\n"
+    + "]\n"
+    + ">";
+  
+  /**
+   * Returns a string describing the output generator.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the GUI
+   */
+  public String globalInfo() {
+    return 
+        "Outputs the predictions in XML.\n\n"
+      + "The following DTD is used:\n\n"
+      + DTD;
+  }
+  
+  /**
+   * Returns a short display text, to be used in comboboxes.
+   * 
+   * @return 		a short display text
+   */
+  public String getDisplay() {
+    return "XML";
+  }
+
+  /**
+   * Replaces certain characters with their XML entities.
+   * 
+   * @param s		the string to process
+   * @return		the processed string
+   */
+  protected String sanitize(String s) {
+    String 	result;
+    
+    result = s;
+    result = result.replaceAll("&", "&amp;");
+    result = result.replaceAll("<", "&lt;");
+    result = result.replaceAll(">", "&gt;");
+    result = result.replaceAll("\"", "&quot;");
+    
+    return result;
+  }
+  
+  /**
+   * Performs the actual printing of the header.
+   */
+  protected void doPrintHeader() {
+    append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+    append("\n");
+    append(DTD + "\n\n");
+    append("<" + TAG_PREDICTIONS + " " + ATT_VERSION + "=\"" + Version.VERSION + "\"" + " " + ATT_NAME + "=\"" + sanitize(m_Header.relationName()) + "\">\n");
+  }
+
+  /**
+   * Builds a string listing the attribute values in a specified range of indices,
+   * separated by commas and enclosed in brackets.
+   *
+   * @param instance 	the instance to print the values from
+   * @return 		a string listing values of the attributes in the range
+   */
+  protected String attributeValuesString(Instance instance) {
+    StringBuffer text = new StringBuffer();
+    if (m_Attributes != null) {
+      text.append("    <" + TAG_ATTRIBUTES + ">\n");
+      m_Attributes.setUpper(instance.numAttributes() - 1);
+      for (int i=0; i<instance.numAttributes(); i++) {
+	if (m_Attributes.isInRange(i) && i != instance.classIndex()) {
+	  text.append("      <" + TAG_ATTRIBUTE + " " + ATT_INDEX + "=\"" + (i+1) + "\"" + " " + ATT_NAME + "=\"" + sanitize(instance.attribute(i).name()) + "\"" + " " + ATT_TYPE + "=\"" + Attribute.typeToString(instance.attribute(i).type()) + "\"" + ">");
+	  text.append(sanitize(instance.toString(i)));
+	  text.append("</" + TAG_ATTRIBUTE + ">\n");
+	}
+      }
+      text.append("    </" + TAG_ATTRIBUTES + ">\n");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Store the prediction made by the classifier as a string.
+   * 
+   * @param classifier	the classifier to use
+   * @param inst	the instance to generate text from
+   * @param index	the index in the dataset
+   * @throws Exception	if something goes wrong
+   */
+  protected void doPrintClassification(Classifier classifier, Instance inst, int index) throws Exception {
+    int prec = m_NumDecimals;
+
+    Instance withMissing = (Instance)inst.copy();
+    withMissing.setDataset(inst.dataset());
+    withMissing.setMissing(withMissing.classIndex());
+    double predValue = classifier.classifyInstance(withMissing);
+
+    // opening tag
+    append("  <" + TAG_PREDICTION + " " + ATT_INDEX + "=\"" + (index+1) + "\">\n");
+
+    if (inst.dataset().classAttribute().isNumeric()) {
+      // actual
+      append("    <" + TAG_ACTUAL_VALUE + ">");
+      if (inst.classIsMissing())
+	append("?");
+      else
+	append(Utils.doubleToString(inst.classValue(), prec));
+      append("</" + TAG_ACTUAL_VALUE + ">\n");
+      // predicted
+      append("    <" + TAG_PREDICTED_VALUE + ">");
+      if (inst.classIsMissing())
+	append("?");
+      else
+	append(Utils.doubleToString(predValue, prec));
+      append("</" + TAG_PREDICTED_VALUE + ">\n");
+      // error
+      append("    <" + TAG_ERROR + ">");
+      if (Utils.isMissingValue(predValue) || inst.classIsMissing())
+	append("?");
+      else
+	append(Utils.doubleToString(predValue - inst.classValue(), prec));
+      append("</" + TAG_ERROR + ">\n");
+    } else {
+      // actual
+      append("    <" + TAG_ACTUAL_LABEL + " " + ATT_INDEX + "=\"" + ((int) inst.classValue()+1) + "\"" + ">");
+      append(sanitize(inst.toString(inst.classIndex())));
+      append("</" + TAG_ACTUAL_LABEL + ">\n");
+      // predicted
+      append("    <" + TAG_PREDICTED_LABEL + " " + ATT_INDEX + "=\"" + ((int) predValue+1) + "\"" + ">");
+      if (Utils.isMissingValue(predValue))
+	append("?");
+      else
+	append(sanitize(inst.dataset().classAttribute().value((int)predValue)));
+      append("</" + TAG_PREDICTED_LABEL + ">\n");
+      // error?
+      append("    <" + TAG_ERROR + ">");
+      if (!Utils.isMissingValue(predValue) && !inst.classIsMissing() && ((int) predValue+1 != (int) inst.classValue()+1))
+	append(VAL_YES);
+      else
+	append(VAL_NO);
+      append("</" + TAG_ERROR + ">\n");
+      // prediction/distribution
+      if (m_OutputDistribution) {
+	append("    <" + TAG_DISTRIBUTION + ">\n");
+	double[] dist = classifier.distributionForInstance(withMissing);
+	for (int n = 0; n < dist.length; n++) {
+	  append("      <" + TAG_CLASS_LABEL + " " + ATT_INDEX + "=\"" + (n+1) + "\"");
+	  if (!Utils.isMissingValue(predValue) && (n == (int) predValue))
+	    append(" " + ATT_PREDICTED + "=\"" + VAL_YES + "\"");
+	  append(">");
+	  append(Utils.doubleToString(dist[n], prec));
+	  append("</" + TAG_CLASS_LABEL + ">\n");
+	}
+	append("    </" + TAG_DISTRIBUTION + ">\n");
+      }
+      else {
+	append("    <" + TAG_PREDICTION + ">");
+	if (Utils.isMissingValue(predValue))
+	  append("?");
+	else
+	  append(Utils.doubleToString(classifier.distributionForInstance(withMissing) [(int)predValue], prec));
+	append("</" + TAG_PREDICTION + ">\n");
+      }
+    }
+
+    // attributes
+    if (m_Attributes != null)
+      append(attributeValuesString(withMissing));
+    
+    // closing tag
+    append("  </" + TAG_PREDICTION + ">\n");
+  }
+  
+  /**
+   * Does nothing.
+   */
+  protected void doPrintFooter() {
+    append("</" + TAG_PREDICTIONS + ">\n");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/GaussianProcesses.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/GaussianProcesses.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/GaussianProcesses.java	(revision 29)
@@ -0,0 +1,944 @@
+/** 
+ *     This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GaussianProcesses.java
+ *    Copyright (C) 2005-2009 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.ConditionalDensityEstimator;
+import weka.classifiers.Evaluation;
+import weka.classifiers.IntervalEstimator;
+import weka.classifiers.functions.supportVector.CachedKernel;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.classifiers.functions.supportVector.RBFKernel;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.matrix.Matrix;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.SelectedTag;
+import weka.core.Statistics;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.io.FileReader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * <!-- globalinfo-start --> 
+ * Implements Gaussian processes for
+ * regression without hyperparameter-tuning. To make choosing an
+ * appropriate noise level easier, this implementation applies
+ * normalization/standardization to the target attribute as well (if
+ * normalization/standardizaton is turned on). Missing values
+ * are replaced by the global mean/mode. Nominal attributes are
+ * converted to binary ones. 
+ * <!-- globalinfo-end -->
+ * 
+ * <!-- technical-bibtex-start --> BibTeX:
+ * 
+ * <pre>
+ *        @misc{Mackay1998,
+ *          address = {Dept. of Physics, Cambridge University, UK},
+ *          author = {David J.C. Mackay},
+ *          title = {Introduction to Gaussian Processes},
+ *          year = {1998},
+ *          PS = {http://wol.ra.phy.cam.ac.uk/mackay/gpB.ps.gz}
+ *       }
+ * </pre>
+ * 
+ * <p/> <!-- technical-bibtex-end -->
+ * 
+ * <!-- options-start --> Valid options are: <p/>
+ * 
+ * <pre>
+ *       -D
+ *        If set, classifier is run in debug mode and
+ *        may output additional info to the console
+ * </pre>
+ * 
+ * <pre>
+ *       -L &lt;double&gt;
+ *        Level of Gaussian Noise. (default 0.1)
+ * </pre>
+ * 
+ * <pre>
+ *       -N
+ *        Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)
+ * </pre>
+ * 
+ * <pre>
+ *       -K &lt;classname and parameters&gt;
+ *        The Kernel to use.
+ *        (default: weka.classifiers.functions.supportVector.PolyKernel)
+ * </pre>
+ * 
+ * <pre>
+ *       
+ *       Options specific to kernel weka.classifiers.functions.supportVector.RBFKernel:
+ * </pre>
+ * 
+ * <pre>
+ *       -D
+ *        Enables debugging output (if available) to be printed.
+ *        (default: off)
+ * </pre>
+ * 
+ * <pre>
+ *       -no-checks
+ *        Turns off all checks - use with caution!
+ *        (default: checks on)
+ * </pre>
+ * 
+ * <pre>
+ *       -C &lt;num&gt;
+ *        The size of the cache (a prime number).
+ *        (default: 250007)
+ * </pre>
+ * 
+ * <pre>
+ *       -G &lt;num&gt;
+ *        The Gamma parameter.
+ *        (default: 0.01)
+ * </pre>
+ * 
+ * <!-- options-end -->
+ * 
+ * @author Kurt Driessens (kurtd@cs.waikato.ac.nz)
+ * @author Remco Bouckaert (remco@cs.waikato.ac.nz)
+ * @version $Revision: 5952 $
+ */
+public class GaussianProcesses extends AbstractClassifier implements OptionHandler, IntervalEstimator,
+                                                                     ConditionalDensityEstimator,
+                                                                     TechnicalInformationHandler, WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8620066949967678545L;
+
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+
+  /** normalizes the data */
+  public static final int FILTER_NORMALIZE = 0;
+
+  /** standardizes the data */
+  public static final int FILTER_STANDARDIZE = 1;
+
+  /** no filter */
+  public static final int FILTER_NONE = 2;
+
+  /** The filter to apply to the training data */
+  public static final Tag[] TAGS_FILTER = { new Tag(FILTER_NORMALIZE, "Normalize training data"),
+                                            new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+                                            new Tag(FILTER_NONE, "No normalization/standardization"), };
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing;
+
+  /**
+   * Turn off all checks and conversions? Turning them off assumes that data
+   * is purely numeric, doesn't contain any missing values, and has a numeric
+   * class.
+   */
+  protected boolean m_checksTurnedOff = false;
+
+  /** Gaussian Noise Value. */
+  protected double m_delta = 1;
+
+  /**
+   * The parameters of the linear transforamtion realized by the filter on the
+   * class attribute
+   */
+  protected double m_Alin;
+  protected double m_Blin;
+
+  /** Kernel to use * */
+  protected Kernel m_kernel = new PolyKernel();
+
+  /** The number of training instances */
+  protected int m_NumTrain = 0;
+
+  /** The training data. */
+  protected double m_avg_target;
+
+  /** (negative) covariance matrix in symmetric matrix representation **/
+  public double[][] m_L;
+
+  /** The vector of target values. */
+  protected Matrix m_t;
+
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return " Implements Gaussian processes for "
+      + "regression without hyperparameter-tuning. To make choosing an "
+      + "appropriate noise level easier, this implementation applies "
+      + "normalization/standardization to the target attribute as well "
+      + "as the other attributes (if "
+      + " normalization/standardizaton is turned on). Missing values "
+      + "are replaced by the global mean/mode. Nominal attributes are "
+      + "converted to binary ones. Note that kernel caching is turned off "
+      + "if the kernel used implements CachedKernel.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "David J.C. Mackay");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Introduction to Gaussian Processes");
+    result.setValue(Field.ADDRESS, "Dept. of Physics, Cambridge University, UK");
+    result.setValue(Field.PS, "http://wol.ra.phy.cam.ac.uk/mackay/gpB.ps.gz");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   * 
+   * @return the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = getKernel().getCapabilities();
+    result.setOwner(this);
+
+    // attribute
+    result.enableAllAttributeDependencies();
+    // with NominalToBinary we can also handle nominal attributes, but only
+    // if the kernel can handle numeric attributes
+    if (result.handles(Capability.NUMERIC_ATTRIBUTES))
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Method for building the classifier.
+   * 
+   * @param insts
+   *            the set of training instances
+   * @throws Exception
+   *             if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    /* check the set of training instances */
+    if (!m_checksTurnedOff) {
+      // can classifier handle the data?
+      getCapabilities().testWithFail(insts);
+
+      // remove instances with missing class
+      insts = new Instances(insts);
+      insts.deleteWithMissingClass();
+    }
+
+    if (!m_checksTurnedOff) {
+      m_Missing = new ReplaceMissingValues();
+      m_Missing.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Missing);
+    } else {
+      m_Missing = null;
+    }
+
+    if (getCapabilities().handles(Capability.NUMERIC_ATTRIBUTES)) {
+      boolean onlyNumeric = true;
+      if (!m_checksTurnedOff) {
+        for (int i = 0; i < insts.numAttributes(); i++) {
+          if (i != insts.classIndex()) {
+            if (!insts.attribute(i).isNumeric()) {
+              onlyNumeric = false;
+              break;
+            }
+          }
+        }
+      }
+
+      if (!onlyNumeric) {
+        m_NominalToBinary = new NominalToBinary();
+        m_NominalToBinary.setInputFormat(insts);
+        insts = Filter.useFilter(insts, m_NominalToBinary);
+      } else {
+        m_NominalToBinary = null;
+      }
+    } else {
+      m_NominalToBinary = null;
+    }
+
+    if (m_filterType == FILTER_STANDARDIZE) {
+      m_Filter = new Standardize();
+      ((Standardize)m_Filter).setIgnoreClass(true);
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter);
+    } else if (m_filterType == FILTER_NORMALIZE) {
+      m_Filter = new Normalize();
+      ((Normalize)m_Filter).setIgnoreClass(true);
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter);
+    } else {
+      m_Filter = null;
+    }
+
+    m_NumTrain = insts.numInstances();
+
+    // determine which linear transformation has been
+    // applied to the class by the filter
+    if (m_Filter != null) {
+      Instance witness = (Instance) insts.instance(0).copy();
+      witness.setValue(insts.classIndex(), 0);
+      m_Filter.input(witness);
+      m_Filter.batchFinished();
+      Instance res = m_Filter.output();
+      m_Blin = res.value(insts.classIndex());
+      witness.setValue(insts.classIndex(), 1);
+      m_Filter.input(witness);
+      m_Filter.batchFinished();
+      res = m_Filter.output();
+      m_Alin = res.value(insts.classIndex()) - m_Blin;
+    } else {
+      m_Alin = 1.0;
+      m_Blin = 0.0;
+    }
+
+    // Initialize kernel
+    try {
+      CachedKernel cachedKernel = (CachedKernel) m_kernel;
+      cachedKernel.setCacheSize(0);
+    } catch (Exception e) {
+      // ignore
+    }
+    m_kernel.buildKernel(insts);
+
+    // Compute average target value
+    double sum = 0.0;
+    for (int i = 0; i < insts.numInstances(); i++) {
+      sum += insts.instance(i).classValue();
+    }
+    m_avg_target = sum / insts.numInstances();
+
+    // initialize kernel matrix/covariance matrix
+    int n = insts.numInstances();
+    m_L = new double[n][];
+    for (int i = 0; i < n; i++) {
+      m_L[i] = new double[i+1];
+    }
+    double kv = 0;
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < i; j++) {
+        kv = m_kernel.eval(i, j, insts.instance(i));
+        m_L[i][j] = kv;
+      }
+      kv = m_kernel.eval(i, i, insts.instance(i));
+      m_L[i][i] = kv + m_delta * m_delta;
+    }
+
+    // Calculate inverse matrix exploiting symmetry of covariance matrix
+    // NB this replaces the kernel matrix with (the negative of) its inverse and does
+    // not require any extra memory for a solution matrix
+    double [] tmprow = new double [n];
+    double tmp2 = 0, tmp = 0;
+    for (int i = 0; i < n; i++) {
+      tmp = -m_L[i][i];
+      m_L[i][i] = 1.0 / tmp;
+      for (int j = 0; j < n; j++) {
+        if (j != i) {
+          if (j < i) {
+            tmprow[j] = m_L[i][j]; 
+            m_L[i][j] /= tmp;
+            tmp2 = m_L[i][j];
+            m_L[j][j] += tmp2 * tmp2 * tmp;
+          } else if (j > i) {
+            tmprow[j] = m_L[j][i]; 
+            m_L[j][i] /= tmp;
+            tmp2 = m_L[j][i];
+            m_L[j][j] += tmp2 * tmp2 * tmp;
+          }
+        }
+      }
+
+      for (int j = 0; j < n; j++) {
+        if (j != i) {
+          if (i < j) {
+            for (int k = 0; k < i; k++) {
+              m_L[j][k] += tmprow[j] * m_L[i][k];
+            }
+          } else {
+            for (int k = 0; k < j; k++) {
+              m_L[j][k] += tmprow[j] * m_L[i][k];
+            }
+						
+          }
+          for (int k = i + 1; k < j; k++) {
+            m_L[j][k] += tmprow[j] * m_L[k][i];
+          }
+        }
+      }
+    }
+		
+    m_t = new Matrix(insts.numInstances(), 1);		
+    double [] tt = new double[n]; 
+    for (int i = 0; i < n; i++) {
+      tt[i] = insts.instance(i).classValue() - m_avg_target;
+    }
+
+    // calculate m_t = tt . m_L
+    for (int i = 0; i < n; i++) {
+      double s = 0;
+      for (int k = 0; k < i; k++) {
+        s -= m_L[i][k] * tt[k];
+      }
+      for (int k = i; k < n; k++) {
+        s -= m_L[k][i] * tt[k];
+      }
+      m_t.set(i, 0, s);
+    }
+		
+  } // buildClassifier
+
+  /**
+   * Classifies a given instance.
+   * 
+   * @param inst
+   *            the instance to be classified
+   * @return the classification
+   * @throws Exception
+   *             if instance could not be classified successfully
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+		
+    // Filter instance
+    inst = filterInstance(inst);
+
+    // Build K vector
+    Matrix k = new Matrix(m_NumTrain, 1);
+    for (int i = 0; i < m_NumTrain; i++) {
+      k.set(i, 0, m_kernel.eval(-1, i, inst));
+    }
+
+    double result = k.transpose().times(m_t).get(0, 0) + m_avg_target;
+    result = (result - m_Blin) / m_Alin;
+
+    return result;
+
+  }
+
+  /**
+   * Filters an instance.
+   */
+  protected Instance filterInstance(Instance inst) throws Exception {
+
+    if (!m_checksTurnedOff) {
+      m_Missing.input(inst);
+      m_Missing.batchFinished();
+      inst = m_Missing.output();
+    }
+
+    if (m_NominalToBinary != null) {
+      m_NominalToBinary.input(inst);
+      m_NominalToBinary.batchFinished();
+      inst = m_NominalToBinary.output();
+    }
+
+    if (m_Filter != null) {
+      m_Filter.input(inst);
+      m_Filter.batchFinished();
+      inst = m_Filter.output();
+    }
+    return inst;
+  }
+
+  /**
+   * Computes standard deviation for given instance, without
+   * transforming target back into original space.
+   */
+  protected double computeStdDev(Instance inst, Matrix k) throws Exception {
+
+    double kappa = m_kernel.eval(-1, -1, inst) + m_delta * m_delta;
+
+    double s = 0;
+    int n = m_L.length;
+    for (int i = 0; i < n; i++) {
+      double t = 0;
+      for (int j = 0; j < n; j++) {
+        t -= k.get(j,0) * (i>j? m_L[i][j] : m_L[j][i]);
+      }			
+      s += t * k.get(i,0);
+    }
+		
+    double sigma = m_delta;
+    if (kappa > s) {
+      sigma = Math.sqrt(kappa - s);
+    }
+
+    return sigma;
+  }
+
+  /**
+   * Computes a prediction interval for the given instance and confidence
+   * level.
+   * 
+   * @param inst
+   *            the instance to make the prediction for
+   * @param confidenceLevel
+   *            the percentage of cases the interval should cover
+   * @return a 1*2 array that contains the boundaries of the interval
+   * @throws Exception
+   *             if interval could not be estimated successfully
+   */
+  public double[][] predictIntervals(Instance inst, double confidenceLevel) throws Exception {
+
+    inst = filterInstance(inst);
+
+    // Build K vector (and Kappa)
+    Matrix k = new Matrix(m_NumTrain, 1);
+    for (int i = 0; i < m_NumTrain; i++) {
+      k.set(i, 0, m_kernel.eval(-1, i, inst));
+    }
+
+    double estimate = k.transpose().times(m_t).get(0, 0) + m_avg_target;
+
+    double sigma = computeStdDev(inst, k);
+
+    confidenceLevel = 1.0 - ((1.0 - confidenceLevel) / 2.0);
+
+    double z = Statistics.normalInverse(confidenceLevel);
+
+    double[][] interval = new double[1][2];
+
+    interval[0][0] = estimate - z * sigma;
+    interval[0][1] = estimate + z * sigma;
+
+    interval[0][0] = (interval[0][0] - m_Blin) / m_Alin;
+    interval[0][1] = (interval[0][1] - m_Blin) / m_Alin;
+		
+    return interval;
+
+  }
+
+  /**
+   * Gives standard deviation of the prediction at the given instance.
+   * 
+   * @param inst
+   *            the instance to get the standard deviation for
+   * @return the standard deviation
+   * @throws Exception
+   *             if computation fails
+   */
+  public double getStandardDeviation(Instance inst) throws Exception {
+
+    inst = filterInstance(inst);
+
+    // Build K vector (and Kappa)
+    Matrix k = new Matrix(m_NumTrain, 1);
+    for (int i = 0; i < m_NumTrain; i++) {
+      k.set(i, 0, m_kernel.eval(-1, i, inst));
+    }
+
+    return computeStdDev(inst, k) / m_Alin;
+  }
+
+  /**
+   * Returns natural logarithm of density estimate for given value based on given instance.
+   *   
+   * @param instance the instance to make the prediction for.
+   * @param value the value to make the prediction for.
+   * @return the natural logarithm of the density estimate
+   * @exception Exception if the density cannot be computed
+   */
+  public double logDensity(Instance inst, double value) throws Exception {
+    
+    inst = filterInstance(inst);
+
+    // Build K vector (and Kappa)
+    Matrix k = new Matrix(m_NumTrain, 1);
+    for (int i = 0; i < m_NumTrain; i++) {
+      k.set(i, 0, m_kernel.eval(-1, i, inst));
+    }
+    
+    double estimate = k.transpose().times(m_t).get(0, 0) + m_avg_target;
+
+    double sigma = computeStdDev(inst, k);
+    
+    // transform to GP space
+    value = value * m_Alin + m_Blin;
+    // center around estimate
+    value = value - estimate;
+    double z = -Math.log(sigma * Math.sqrt(2 * Math.PI)) 
+      - value * value /(2.0*sigma*sigma); 
+    
+    return z + Math.log(m_Alin);
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector<Option> result = new Vector<Option>();
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement((Option)enm.nextElement());
+
+    result.addElement(new Option("\tLevel of Gaussian Noise wrt transformed target." + " (default 1)", "L", 1, "-L <double>"));
+
+    result.addElement(new Option("\tWhether to 0=normalize/1=standardize/2=neither. " + "(default 0=normalize)",
+                                 "N", 1, "-N"));
+
+    result.addElement(new Option("\tThe Kernel to use.\n"
+                                 + "\t(default: weka.classifiers.functions.supportVector.PolyKernel)", "K", 1,
+                                 "-K <classname and parameters>"));
+
+    result.addElement(new Option("", "", 0, "\nOptions specific to kernel " + getKernel().getClass().getName()
+                                 + ":"));
+
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement((Option)enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   * <!-- options-start --> Valid options are: <p/>
+   * 
+   * <pre>
+   *       -D
+   *        If set, classifier is run in debug mode and
+   *        may output additional info to the console
+   * </pre>
+   * 
+   * <pre>
+   *       -L &lt;double&gt;
+   *        Level of Gaussian Noise. (default 0.1)
+   * </pre>
+   * 
+   * <pre>
+   *       -M &lt;double&gt;
+   *        Level of Gaussian Noise for the class. (default 0.1)
+   * </pre>
+   * 
+   * <pre>
+   *       -N
+   *        Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)
+   * </pre>
+   * 
+   * <pre>
+   *       -K &lt;classname and parameters&gt;
+   *        The Kernel to use.
+   *        (default: weka.classifiers.functions.supportVector.PolyKernel)
+   * </pre>
+   * 
+   * <pre>
+   *       
+   *       Options specific to kernel weka.classifiers.functions.supportVector.RBFKernel:
+   * </pre>
+   * 
+   * <pre>
+   *       -D
+   *        Enables debugging output (if available) to be printed.
+   *        (default: off)
+   * </pre>
+   * 
+   * <pre>
+   *       -no-checks
+   *        Turns off all checks - use with caution!
+   *        (default: checks on)
+   * </pre>
+   * 
+   * <pre>
+   *       -C &lt;num&gt;
+   *        The size of the cache (a prime number).
+   *        (default: 250007)
+   * </pre>
+   * 
+   * <pre>
+   *       -G &lt;num&gt;
+   *        The Gamma parameter.
+   *        (default: 0.01)
+   * </pre>
+   * 
+   * <!-- options-end -->
+   * 
+   * @param options
+   *            the list of options as an array of strings
+   * @throws Exception
+   *             if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String tmpStr;
+    String[] tmpOptions;
+
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0)
+      setNoise(Double.parseDouble(tmpStr));
+    else
+      setNoise(1);
+		
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
+    else
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+
+    tmpStr = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int i;
+    Vector<String> result;
+    String[] options;
+
+    result = new Vector<String>();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.addElement(options[i]);
+
+    result.addElement("-L");
+    result.addElement("" + getNoise());
+
+    result.addElement("-N");
+    result.addElement("" + m_filterType);
+
+    result.addElement("-K");
+    result.addElement("" + m_kernel.getClass().getName() + " " + Utils.joinOptions(m_kernel.getOptions()));
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+
+  /**
+   * Gets the kernel to use.
+   * 
+   * @return the kernel
+   */
+  public Kernel getKernel() {
+    return m_kernel;
+  }
+
+  /**
+   * Sets the kernel to use.
+   * 
+   * @param value
+   *            the new kernel
+   */
+  public void setKernel(Kernel value) {
+    m_kernel = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "Determines how/if the data will be transformed.";
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   * 
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   * 
+   * @param newType
+   *            the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String noiseTipText() {
+    return "The level of Gaussian Noise (added to the diagonal of the Covariance Matrix), after the " +
+      "target has been normalized/standardized/left unchanged).";
+  }
+
+  /**
+   * Get the value of noise.
+   * 
+   * @return Value of noise.
+   */
+  public double getNoise() {
+    return m_delta;
+  }
+
+  /**
+   * Set the level of Gaussian Noise.
+   * 
+   * @param v
+   *            Value to assign to noise.
+   */
+  public void setNoise(double v) {
+    m_delta = v;
+  }
+
+  /**
+   * Prints out the classifier.
+   * 
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    if (m_t == null)
+      return "Gaussian Processes: No model built yet.";
+
+    try {
+
+      text.append("Gaussian Processes\n\n");
+      text.append("Kernel used:\n  " + m_kernel.toString() + "\n\n");
+
+      text.append("All values shown based on: " + 
+                  TAGS_FILTER[m_filterType].getReadable() + "\n\n");
+
+
+      text.append("Average Target Value : " + m_avg_target + "\n");
+
+      text.append("Inverted Covariance Matrix:\n");
+      double min = -m_L[0][0];
+      double max = -m_L[0][0];
+      for (int i = 0; i < m_NumTrain; i++)
+        for (int j = 0; j <= i; j++) {
+          if (-m_L[i][j] < min)
+            min = -m_L[i][j];
+          else if (-m_L[i][j] > max)
+            max = -m_L[i][j];
+        }
+      text.append("    Lowest Value = " + min + "\n");
+      text.append("    Highest Value = " + max + "\n");
+      text.append("Inverted Covariance Matrix * Target-value Vector:\n");
+      min = m_t.get(0, 0);
+      max = m_t.get(0, 0);
+      for (int i = 0; i < m_NumTrain; i++) {
+        if (m_t.get(i, 0) < min)
+          min = m_t.get(i, 0);
+        else if (m_t.get(i, 0) > max)
+          max = m_t.get(i, 0);
+      }
+      text.append("    Lowest Value = " + min + "\n");
+      text.append("    Highest Value = " + max + "\n \n");
+
+    } catch (Exception e) {
+      return "Can't print the classifier.";
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param argv
+   *            the commandline parameters
+   */
+  public static void main(String[] argv) {
+
+    runClassifier(new GaussianProcesses(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/IsotonicRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/IsotonicRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/IsotonicRegression.java	(revision 29)
@@ -0,0 +1,322 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IsotonicRegression.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.util.Arrays;
+
+/**
+ <!-- globalinfo-start -->
+ * Learns an isotonic regression model. Picks the attribute that results in the lowest squared error. Missing values are not allowed. Can only deal with numeric attributes.Considers the monotonically increasing case as well as the monotonicallydecreasing case
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class IsotonicRegression extends AbstractClassifier implements WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 1679336022835454137L;
+  
+  /** The chosen attribute */
+  private Attribute m_attribute;
+
+  /** The array of cut points */
+  private double[] m_cuts;
+  
+  /** The predicted value in each interval. */
+  private double[] m_values;
+
+  /** The minimum mean squared error that has been achieved. */
+  private double m_minMsq;
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Learns an isotonic regression model. "
+      +"Picks the attribute that results in the lowest squared error. "
+      +"Missing values are not allowed. Can only deal with numeric attributes."
+      +"Considers the monotonically increasing case as well as the monotonically"
+      +"decreasing case";
+  }
+
+  /**
+   * Generate a prediction for the supplied instance.
+   *
+   * @param inst the instance to predict.
+   * @return the prediction
+   * @throws Exception if an error occurs
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+    
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.classifyInstance(inst);
+    }
+    
+    if (inst.isMissing(m_attribute.index())) {
+      throw new Exception("IsotonicRegression: No missing values!");
+    }
+    int index = Arrays.binarySearch(m_cuts, inst.value(m_attribute));
+    if (index < 0) {
+      return m_values[-index - 1];
+    } else { 
+      return m_values[index + 1];
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Does the actual regression.
+   */
+  protected void regress(Attribute attribute, Instances insts, boolean ascending) 
+    throws Exception {
+
+    // Sort values according to current attribute
+    insts.sort(attribute);
+    
+    // Initialize arrays
+    double[] values = new double[insts.numInstances()];
+    double[] weights = new double[insts.numInstances()];
+    double[] cuts = new double[insts.numInstances() - 1];
+    int size = 0;
+    values[0] = insts.instance(0).classValue();
+    weights[0] = insts.instance(0).weight();
+    for (int i = 1; i < insts.numInstances(); i++) {
+      if (insts.instance(i).value(attribute) >
+          insts.instance(i - 1).value(attribute)) {
+        cuts[size] = (insts.instance(i).value(attribute) +
+                      insts.instance(i - 1).value(attribute)) / 2;
+        size++;
+      }
+      values[size] += insts.instance(i).classValue();
+      weights[size] += insts.instance(i).weight();
+    }
+    size++;
+    
+    // While there is a pair of adjacent violators
+    boolean violators;
+    do {
+      violators = false;
+      
+      // Initialize arrays
+      double[] tempValues = new double[size];
+      double[] tempWeights = new double[size];
+      double[] tempCuts = new double[size - 1];
+      
+      // Merge adjacent violators
+      int newSize = 0;
+      tempValues[0] = values[0];
+      tempWeights[0] = weights[0];
+      for (int j = 1; j < size; j++) {
+        if ((ascending && (values[j] / weights[j] > 
+                           tempValues[newSize] / tempWeights[newSize])) ||
+            (!ascending && (values[j] / weights[j] < 
+                            tempValues[newSize] / tempWeights[newSize]))) {
+          tempCuts[newSize] = cuts[j - 1];
+          newSize++;
+          tempValues[newSize] = values[j];
+          tempWeights[newSize] = weights[j];
+        } else {
+          tempWeights[newSize] += weights[j];
+          tempValues[newSize] += values[j];
+          violators = true;
+        }
+      }
+      newSize++;
+      
+      // Copy references
+      values = tempValues;
+      weights = tempWeights;
+      cuts = tempCuts;
+      size = newSize;
+    } while (violators);
+    
+    // Compute actual predictions
+    for (int i = 0; i < size; i++) {
+      values[i] /= weights[i];
+    }
+    
+    // Backup best instance variables
+    Attribute attributeBackedup = m_attribute;
+    double[] cutsBackedup = m_cuts;
+    double[] valuesBackedup = m_values;
+    
+    // Set instance variables to values computed for this attribute
+    m_attribute = attribute;
+    m_cuts = cuts;
+    m_values = values;
+    
+    // Compute sum of squared errors
+    Evaluation eval = new Evaluation(insts);
+    eval.evaluateModel(this, insts);
+    double msq = eval.rootMeanSquaredError();
+    
+    // Check whether this is the best attribute
+    if (msq < m_minMsq) {
+      m_minMsq = msq;
+    } else {
+      m_attribute = attributeBackedup;
+      m_cuts = cutsBackedup;
+      m_values = valuesBackedup;
+    }
+  }
+  
+  /**
+   * Builds an isotonic regression model given the supplied training data.
+   *
+   * @param insts the training data.
+   * @throws Exception if an error occurs
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+
+    // only class? -> build ZeroR model
+    if (insts.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(insts);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+
+    // Choose best attribute and mode
+    m_minMsq = Double.MAX_VALUE;
+    m_attribute = null;
+    for (int a = 0; a < insts.numAttributes(); a++) {
+      if (a != insts.classIndex()) {
+        regress(insts.attribute(a), insts, true);
+        regress(insts.attribute(a), insts, false);
+      }
+    }
+  }
+
+  /**
+   * Returns a description of this classifier as a string
+   *
+   * @return a description of the classifier.
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer text = new StringBuffer();
+    text.append("Isotonic regression\n\n");
+    if (m_attribute == null) {
+      text.append("No model built yet!");
+    }
+    else {
+      text.append("Based on attribute: " + m_attribute.name() + "\n\n");
+      for (int i = 0; i < m_values.length; i++) {
+	text.append("prediction: " + Utils.doubleToString(m_values[i], 10, 2));
+	if (i < m_cuts.length) {
+	  text.append("\t\tcut point: " + Utils.doubleToString(m_cuts[i], 10, 2) + "\n");
+	}
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param argv options
+   */
+  public static void main(String [] argv){
+    runClassifier(new IsotonicRegression(), argv);
+  } 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/LeastMedSq.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/LeastMedSq.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/LeastMedSq.java	(revision 29)
@@ -0,0 +1,733 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LeastMedSq.java
+ *
+ *    Copyright (C) 2001 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.instance.RemoveRange;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements a least median sqaured linear regression utilising the existing weka LinearRegression class to form predictions. <br/>
+ * Least squared regression functions are generated from random subsamples of the data. The least squared regression with the lowest meadian squared error is chosen as the final model.<br/>
+ * <br/>
+ * The basis of the algorithm is <br/>
+ * <br/>
+ * Peter J. Rousseeuw, Annick M. Leroy (1987). Robust regression and outlier detection. .
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Rousseeuw1987,
+ *    author = {Peter J. Rousseeuw and Annick M. Leroy},
+ *    title = {Robust regression and outlier detection},
+ *    year = {1987}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;sample size&gt;
+ *  Set sample size
+ *  (default: 4)
+ * </pre>
+ * 
+ * <pre> -G &lt;seed&gt;
+ *  Set the seed used to generate samples
+ *  (default: 0)
+ * </pre>
+ * 
+ * <pre> -D
+ *  Produce debugging output
+ *  (default no debugging output)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Tony Voyle (tv6@waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class LeastMedSq 
+  extends AbstractClassifier 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4288954049987652970L;
+  
+  private double[] m_Residuals;
+  
+  private double[] m_weight;
+  
+  private double m_SSR;
+  
+  private double m_scalefactor;
+  
+  private double m_bestMedian = Double.POSITIVE_INFINITY;
+  
+  private LinearRegression m_currentRegression;
+  
+  private LinearRegression m_bestRegression;
+  
+  private LinearRegression m_ls;
+
+  private Instances m_Data;
+
+  private Instances m_RLSData;
+
+  private Instances m_SubSample;
+
+  private ReplaceMissingValues m_MissingFilter;
+
+  private NominalToBinary m_TransformFilter;
+
+  private RemoveRange m_SplitFilter;
+
+  private int m_samplesize = 4;
+
+  private int m_samples;
+
+  private boolean m_israndom = false;
+
+  private boolean m_debug = false;
+
+  private Random m_random;
+
+  private long m_randomseed = 0;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Implements a least median sqaured linear regression utilising the "
+      +"existing weka LinearRegression class to form predictions. \n"
+      +"Least squared regression functions are generated from random subsamples of "
+      +"the data. The least squared regression with the lowest meadian squared error "
+      +"is chosen as the final model.\n\n"
+      +"The basis of the algorithm is \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Peter J. Rousseeuw and Annick M. Leroy");
+    result.setValue(Field.YEAR, "1987");
+    result.setValue(Field.TITLE, "Robust regression and outlier detection");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Build lms regression
+   *
+   * @param data training data
+   * @throws Exception if an error occurs
+   */
+  public void buildClassifier(Instances data)throws Exception{
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    cleanUpData(data);
+
+    getSamples();
+
+    findBestRegression();
+
+    buildRLSRegression();
+
+  } // buildClassifier
+
+  /**
+   * Classify a given instance using the best generated
+   * LinearRegression Classifier.
+   *
+   * @param instance instance to be classified
+   * @return class value
+   * @throws Exception if an error occurs
+   */
+  public double classifyInstance(Instance instance)throws Exception{
+
+    Instance transformedInstance = instance;
+    m_TransformFilter.input(transformedInstance);
+    transformedInstance = m_TransformFilter.output();
+    m_MissingFilter.input(transformedInstance);
+    transformedInstance = m_MissingFilter.output();
+
+    return m_ls.classifyInstance(transformedInstance);
+  } // classifyInstance
+
+  /**
+   * Cleans up data
+   *
+   * @param data data to be cleaned up
+   * @throws Exception if an error occurs
+   */
+  private void cleanUpData(Instances data)throws Exception{
+
+    m_Data = data;
+    m_TransformFilter = new NominalToBinary();
+    m_TransformFilter.setInputFormat(m_Data);
+    m_Data = Filter.useFilter(m_Data, m_TransformFilter);
+    m_MissingFilter = new ReplaceMissingValues();
+    m_MissingFilter.setInputFormat(m_Data);
+    m_Data = Filter.useFilter(m_Data, m_MissingFilter);
+    m_Data.deleteWithMissingClass();
+  }
+
+  /**
+   * Gets the number of samples to use.
+   * 
+   * @throws Exception if an error occurs
+   */
+  private void getSamples()throws Exception{
+
+    int stuf[] = new int[] {500,50,22,17,15,14};
+    if ( m_samplesize < 7){
+      if ( m_Data.numInstances() < stuf[m_samplesize - 1])
+	m_samples = combinations(m_Data.numInstances(), m_samplesize);
+      else
+	m_samples = m_samplesize * 500;
+
+    } else m_samples = 3000;
+    if (m_debug){
+      System.out.println("m_samplesize: " + m_samplesize);
+      System.out.println("m_samples: " + m_samples);
+      System.out.println("m_randomseed: " + m_randomseed);
+    }
+
+  }
+
+  /**
+   * Set up the random number generator
+   *
+   */
+  private void setRandom(){
+
+    m_random = new Random(getRandomSeed());
+  }
+
+  /**
+   * Finds the best regression generated from m_samples
+   * random samples from the training data
+   *
+   * @throws Exception if an error occurs
+   */
+  private void findBestRegression()throws Exception{
+
+    setRandom();
+    m_bestMedian = Double.POSITIVE_INFINITY;
+    if (m_debug) {
+      System.out.println("Starting:");
+    }
+    for(int s = 0, r = 0; s < m_samples; s++, r++){
+      if (m_debug) {
+	if(s%(m_samples/100)==0)
+	  System.out.print("*");
+      }
+      genRegression();
+      getMedian();
+    }
+    if (m_debug) {
+      System.out.println("");
+    }
+    m_currentRegression = m_bestRegression;
+  }
+
+  /**
+   * Generates a LinearRegression classifier from
+   * the current m_SubSample
+   *
+   * @throws Exception if an error occurs
+   */
+  private void genRegression()throws Exception{
+
+    m_currentRegression = new LinearRegression();
+    m_currentRegression.setOptions(new String[]{"-S", "1"});
+    selectSubSample(m_Data);
+    m_currentRegression.buildClassifier(m_SubSample);
+  }
+
+  /**
+   * Finds residuals (squared) for the current
+   * regression.
+   *
+   * @throws Exception if an error occurs
+   */
+  private void findResiduals()throws Exception{
+
+    m_SSR = 0;
+    m_Residuals = new double [m_Data.numInstances()];
+    for(int i = 0; i < m_Data.numInstances(); i++){
+      m_Residuals[i] = m_currentRegression.classifyInstance(m_Data.instance(i));
+      m_Residuals[i] -= m_Data.instance(i).value(m_Data.classAttribute());
+      m_Residuals[i] *= m_Residuals[i];
+      m_SSR += m_Residuals[i];
+    }
+  }
+
+  /**
+   * finds the median residual squared for the
+   * current regression
+   *
+   * @throws Exception if an error occurs
+   */
+  private void getMedian()throws Exception{
+
+    findResiduals();
+    int p = m_Residuals.length;
+    select(m_Residuals, 0, p - 1, p / 2);
+    if(m_Residuals[p / 2] < m_bestMedian){
+      m_bestMedian = m_Residuals[p / 2];
+      m_bestRegression = m_currentRegression;
+    }
+  }
+
+  /**
+   * Returns a string representing the best
+   * LinearRegression classifier found.
+   *
+   * @return String representing the regression
+   */
+  public String toString(){
+
+    if( m_ls == null){
+      return "model has not been built";
+    }
+    return m_ls.toString();
+  }
+
+  /**
+   * Builds a weight function removing instances with an
+   * abnormally high scaled residual
+   *
+   * @throws Exception if weight building fails
+   */
+  private void buildWeight()throws Exception{
+
+    findResiduals();
+    m_scalefactor = 1.4826 * ( 1 + 5 / (m_Data.numInstances()
+					- m_Data.numAttributes()))
+      * Math.sqrt(m_bestMedian);
+    m_weight = new double[m_Residuals.length];
+    for (int i = 0; i < m_Residuals.length; i++)
+      m_weight[i] = ((Math.sqrt(m_Residuals[i])/m_scalefactor < 2.5)?1.0:0.0);
+  }
+
+  /**
+   * Builds a new LinearRegression without the 'bad' data
+   * found by buildWeight
+   *
+   * @throws Exception if building fails
+   */
+  private void buildRLSRegression()throws Exception{
+
+    buildWeight();
+    m_RLSData = new Instances(m_Data);
+    int x = 0;
+    int y = 0;
+    int n = m_RLSData.numInstances();
+    while(y < n){
+      if (m_weight[x] == 0){
+	m_RLSData.delete(y);
+	n = m_RLSData.numInstances();
+	y--;
+      }
+      x++;
+      y++;
+    }
+    if ( m_RLSData.numInstances() == 0){
+      System.err.println("rls regression unbuilt");
+      m_ls = m_currentRegression;
+    }else{
+      m_ls = new LinearRegression();
+      m_ls.setOptions(new String[]{"-S", "1"});
+      m_ls.buildClassifier(m_RLSData);
+      m_currentRegression = m_ls;
+    }
+
+  }
+
+  /**
+   * Finds the kth number in an array
+   *
+   * @param a an array of numbers
+   * @param l left pointer
+   * @param r right pointer
+   * @param k position of number to be found
+   */
+  private static void select( double [] a, int l, int r, int k){
+
+    if (r <=l) return;
+    int i = partition( a, l, r);
+    if (i > k) select(a, l, i-1, k);
+    if (i < k) select(a, i+1, r, k);
+  }
+
+  /**
+   * Partitions an array of numbers such that all numbers
+   * less than that at index r, between indexes l and r
+   * will have a smaller index and all numbers greater than
+   * will have a larger index
+   *
+   * @param a an array of numbers
+   * @param l left pointer
+   * @param r right pointer
+   * @return final index of number originally at r
+   */
+  private static int partition( double [] a, int l, int r ){
+
+    int i = l-1, j = r;
+    double v = a[r], temp;
+    while(true){
+      while(a[++i] < v);
+      while(v < a[--j]) if(j == l) break;
+      if(i >= j) break;
+      temp = a[i];
+      a[i] = a[j];
+      a[j] = temp;
+    }
+    temp = a[i];
+    a[i] = a[r];
+    a[r] = temp;
+    return i;
+  }
+
+  /**
+   * Produces a random sample from m_Data
+   * in m_SubSample
+   *
+   * @param data data from which to take sample
+   * @throws Exception if an error occurs
+   */
+  private void selectSubSample(Instances data)throws Exception{
+
+    m_SplitFilter = new RemoveRange();
+    m_SplitFilter.setInvertSelection(true);
+    m_SubSample = data;
+    m_SplitFilter.setInputFormat(m_SubSample);
+    m_SplitFilter.setInstancesIndices(selectIndices(m_SubSample));
+    m_SubSample = Filter.useFilter(m_SubSample, m_SplitFilter);
+  }
+
+  /**
+   * Returns a string suitable for passing to RemoveRange consisting
+   * of m_samplesize indices.
+   *
+   * @param data dataset from which to take indicese
+   * @return string of indices suitable for passing to RemoveRange 
+   */
+  private String selectIndices(Instances data){
+
+    StringBuffer text = new StringBuffer();
+    for(int i = 0, x = 0; i < m_samplesize; i++){
+      do{x = (int) (m_random.nextDouble() * data.numInstances());}
+      while(x==0);
+      text.append(Integer.toString(x));
+      if(i < m_samplesize - 1)
+	text.append(",");
+      else
+	text.append("\n");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sampleSizeTipText() {
+    return "Set the size of the random samples used to generate the least sqaured "
+      +"regression functions.";
+  }
+
+  /**
+   * sets number of samples
+   *
+   * @param samplesize value
+   */
+  public void setSampleSize(int samplesize){
+
+    m_samplesize = samplesize;
+  }
+
+  /**
+   * gets number of samples
+   *
+   * @return value
+   */
+  public int getSampleSize(){
+
+    return m_samplesize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Set the seed for selecting random subsamples of the training data.";
+  }
+
+  /**
+   * Set the seed for the random number generator
+   *
+   * @param randomseed the seed
+   */
+  public void setRandomSeed(long randomseed){
+
+    m_randomseed = randomseed;
+  }
+
+  /**
+   * get the seed for the random number generator
+   *
+   * @return the seed value
+   */
+  public long getRandomSeed(){
+
+    return m_randomseed;
+  }
+
+  /**
+   * sets  whether or not debugging output shouild be printed
+   *
+   * @param debug true if debugging output selected
+   */
+  public void setDebug(boolean debug){
+
+    m_debug = debug;
+  }
+
+  /**
+   * Returns whether or not debugging output shouild be printed
+   *
+   * @return true if debuging output selected
+   */
+  public boolean getDebug(){
+
+    return m_debug;
+  }
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return an enumeration of all available options.
+   */
+  public Enumeration listOptions(){
+
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option("\tSet sample size\n"
+				    + "\t(default: 4)\n",
+				    "S", 4, "-S <sample size>"));
+    newVector.addElement(new Option("\tSet the seed used to generate samples\n"
+				    + "\t(default: 0)\n",
+				    "G", 0, "-G <seed>"));
+    newVector.addElement(new Option("\tProduce debugging output\n"
+				    + "\t(default no debugging output)\n",
+				    "D", 0, "-D"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;sample size&gt;
+   *  Set sample size
+   *  (default: 4)
+   * </pre>
+   * 
+   * <pre> -G &lt;seed&gt;
+   *  Set the seed used to generate samples
+   *  (default: 0)
+   * </pre>
+   * 
+   * <pre> -D
+   *  Produce debugging output
+   *  (default no debugging output)
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String curropt = Utils.getOption('S', options);
+    if ( curropt.length() != 0){
+      setSampleSize(Integer.parseInt(curropt));
+    } else
+      setSampleSize(4);
+
+    curropt = Utils.getOption('G', options);
+    if ( curropt.length() != 0){
+      setRandomSeed(Long.parseLong(curropt));
+    } else {
+      setRandomSeed(0);
+    }
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions(){
+
+    String options[] = new String[9];
+    int current = 0;
+
+    options[current++] = "-S";
+    options[current++] = "" + getSampleSize();
+
+    options[current++] = "-G";
+    options[current++] = "" + getRandomSeed();
+
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * Produces the combination nCr
+   *
+   * @param n
+   * @param r
+   * @return the combination
+   * @throws Exception if r is greater than n
+   */
+  public static int combinations (int n, int r)throws Exception {
+
+    int c = 1, denom = 1, num = 1, i,orig=r;
+    if (r > n) throw new Exception("r must be less that or equal to n.");
+    r = Math.min( r , n - r);
+
+    for (i = 1 ; i <= r; i++){
+
+      num *= n-i+1;
+      denom *= i;
+    }
+
+    c = num / denom;
+    if(false)
+      System.out.println( "n: "+n+" r: "+orig+" num: "+num+
+			  " denom: "+denom+" c: "+c);
+    return c;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * generate a Linear regression predictor for testing
+   *
+   * @param argv options
+   */
+  public static void main(String [] argv){
+    runClassifier(new LeastMedSq(), argv);
+  } // main
+} // lmr
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/LibLINEAR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/LibLINEAR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/LibLINEAR.java	(revision 29)
@@ -0,0 +1,1239 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LibLINEAR.java
+ * Copyright (C) Benedikt Waldvogel 
+ */
+package weka.classifiers.functions;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+/**
+  <!-- globalinfo-start -->
+  * A wrapper class for the liblinear tools (the liblinear classes, typically the jar file, need to be in the classpath to use this classifier).<br/>
+  * Rong-En Fan, Kai-Wei Chang, Cho-Jui Hsieh, Xiang-Rui Wang, Chih-Jen Lin (2008). LIBLINEAR - A Library for Large Linear Classification. URL http://www.csie.ntu.edu.tw/~cjlin/liblinear/.
+  * <p/>
+  <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Fan2008,
+ *    author = {Rong-En Fan and Kai-Wei Chang and Cho-Jui Hsieh and Xiang-Rui Wang and Chih-Jen Lin},
+ *    note = {The Weka classifier works with version 1.33 of LIBLINEAR},
+ *    title = {LIBLINEAR - A Library for Large Linear Classification},
+ *    year = {2008},
+ *    URL = {http://www.csie.ntu.edu.tw/\~cjlin/liblinear/}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;int&gt;
+ *  Set type of solver (default: 1)
+ *    0 = L2-regularized logistic regression
+ *    1 = L2-loss support vector machines (dual)
+ *    2 = L2-loss support vector machines (primal)
+ *    3 = L1-loss support vector machines (dual)
+ *    4 = multi-class support vector machines by Crammer and Singer</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  Set the cost parameter C
+ *   (default: 1)</pre>
+ * 
+ * <pre> -Z
+ *  Turn on normalization of input data (default: off)</pre>
+ * 
+ * <pre> -N
+ *  Turn on nominal to binary conversion.</pre>
+ * 
+ * <pre> -M
+ *  Turn off missing value replacement.
+ *  WARNING: use only if your data has no missing values.</pre>
+ * 
+ * <pre> -P
+ *  Use probability estimation (default: off)
+ * currently for L2-regularized logistic regression only! </pre>
+ * 
+ * <pre> -E &lt;double&gt;
+ *  Set tolerance of termination criterion (default: 0.01)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  Set the parameters C of class i to weight[i]*C
+ *   (default: 1)</pre>
+ * 
+ * <pre> -B &lt;double&gt;
+ *  Add Bias term with the given value if &gt;= 0; if &lt; 0, no bias term added (default: 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Benedikt Waldvogel (mail at bwaldvogel.de)
+ * @version $Revision: 5928 $
+ */
+public class LibLINEAR
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+
+  /** the svm classname */
+  protected final static String CLASS_LINEAR = "liblinear.Linear";
+
+  /** the svm_model classname */
+  protected final static String CLASS_MODEL = "liblinear.Model";
+
+  /** the svm_problem classname */
+  protected final static String CLASS_PROBLEM = "liblinear.Problem";
+
+  /** the svm_parameter classname */
+  protected final static String CLASS_PARAMETER = "liblinear.Parameter";
+
+  /** the svm_parameter classname */
+  protected final static String CLASS_SOLVERTYPE = "liblinear.SolverType";
+
+  /** the svm_node classname */
+  protected final static String CLASS_FEATURENODE = "liblinear.FeatureNode";
+
+  /** serial UID */
+  protected static final long serialVersionUID = 230504711;
+
+  /** LibLINEAR Model */
+  protected Object m_Model;
+
+
+  public Object getModel() {
+    return m_Model;
+  }
+
+  /** for normalizing the data */
+  protected Filter m_Filter = null;
+
+  /** normalize input data */
+  protected boolean m_Normalize = false;
+
+  /** SVM solver type L2-regularized logistic regression */
+  public static final int SVMTYPE_L2_LR = 0;
+  /** SVM solver type L2-loss support vector machines (dual) */
+  public static final int SVMTYPE_L2LOSS_SVM_DUAL = 1;
+  /** SVM solver type L2-loss support vector machines (primal) */
+  public static final int SVMTYPE_L2LOSS_SVM = 2;
+  /** SVM solver type L1-loss support vector machines (dual) */
+  public static final int SVMTYPE_L1LOSS_SVM_DUAL = 3;
+  /** SVM solver type multi-class support vector machines by Crammer and Singer */
+  public static final int SVMTYPE_MCSVM_CS = 4;
+  /** SVM solver types */
+  public static final Tag[] TAGS_SVMTYPE = {
+    new Tag(SVMTYPE_L2_LR, "L2-regularized logistic regression"),
+    new Tag(SVMTYPE_L2LOSS_SVM_DUAL, "L2-loss support vector machines (dual)"),
+    new Tag(SVMTYPE_L2LOSS_SVM, "L2-loss support vector machines (primal)"),
+    new Tag(SVMTYPE_L1LOSS_SVM_DUAL, "L1-loss support vector machines (dual)"),
+    new Tag(SVMTYPE_MCSVM_CS, "multi-class support vector machines by Crammer and Singer")
+  };
+
+  /** the SVM solver type */
+  protected int m_SVMType = SVMTYPE_L2LOSS_SVM_DUAL;
+
+  /** stopping criteria */
+  protected double m_eps = 0.01;
+
+  /** cost Parameter C */
+  protected double m_Cost = 1;
+
+  /** bias term value */
+  protected double m_Bias = 1;
+
+  protected int[] m_WeightLabel = new int[0];
+
+  protected double[] m_Weight = new double[0];
+
+  /** whether to generate probability estimates instead of +1/-1 in case of
+   * classification problems */
+  protected boolean m_ProbabilityEstimates = false;
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_ReplaceMissingValues;
+
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+
+  /** If true, the nominal to binary filter is applied */
+  private boolean m_nominalToBinary = false;
+
+  /** If true, the replace missing values filter is not applied */
+  private boolean m_noReplaceMissingValues;
+
+  /** whether the liblinear classes are in the Classpath */
+  protected static boolean m_Present = false;
+  static {
+    try {
+      Class.forName(CLASS_LINEAR);
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+
+  /**
+   * Returns a string describing classifier
+   *
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+      "A wrapper class for the liblinear tools (the liblinear classes, typically "
+      + "the jar file, need to be in the classpath to use this classifier).\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(TechnicalInformation.Field.AUTHOR, "Rong-En Fan and Kai-Wei Chang and Cho-Jui Hsieh and Xiang-Rui Wang and Chih-Jen Lin");
+    result.setValue(TechnicalInformation.Field.TITLE, "LIBLINEAR - A Library for Large Linear Classification");
+    result.setValue(TechnicalInformation.Field.YEAR, "2008");
+    result.setValue(TechnicalInformation.Field.URL, "http://www.csie.ntu.edu.tw/~cjlin/liblinear/");
+    result.setValue(TechnicalInformation.Field.NOTE, "The Weka classifier works with version 1.33 of LIBLINEAR");
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector      result;
+
+    result = new Vector();
+
+    result.addElement(
+        new Option(
+          "\tSet type of solver (default: 1)\n"
+          + "\t\t 0 = L2-regularized logistic regression\n"
+          + "\t\t 1 = L2-loss support vector machines (dual)\n"
+          + "\t\t 2 = L2-loss support vector machines (primal)\n"
+          + "\t\t 3 = L1-loss support vector machines (dual)\n"
+          + "\t\t 4 = multi-class support vector machines by Crammer and Singer",
+          "S", 1, "-S <int>"));
+
+    result.addElement(
+        new Option(
+          "\tSet the cost parameter C\n"
+          + "\t (default: 1)",
+          "C", 1, "-C <double>"));
+
+    result.addElement(
+        new Option(
+          "\tTurn on normalization of input data (default: off)",
+          "Z", 0, "-Z"));
+    
+    result.addElement(
+        new Option("\tTurn on nominal to binary conversion.",
+            "N", 0, "-N"));
+    
+    result.addElement(
+        new Option("\tTurn off missing value replacement."
+            + "\n\tWARNING: use only if your data has no missing "
+            + "values.", "M", 0, "-M"));
+
+    result.addElement(
+        new Option(
+          "\tUse probability estimation (default: off)\n" +
+          "currently for L2-regularized logistic regression only! ",
+          "P", 0, "-P"));
+
+    result.addElement(
+        new Option(
+          "\tSet tolerance of termination criterion (default: 0.01)",
+          "E", 1, "-E <double>"));
+
+    result.addElement(
+        new Option(
+          "\tSet the parameters C of class i to weight[i]*C\n"
+          + "\t (default: 1)",
+          "W", 1, "-W <double>"));
+
+    result.addElement(
+        new Option(
+          "\tAdd Bias term with the given value if >= 0; if < 0, no bias term added (default: 1)",
+          "B", 1, "-B <double>"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Sets the classifier options <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;int&gt;
+   *  Set type of solver (default: 1)
+   *    0 = L2-regularized logistic regression
+   *    1 = L2-loss support vector machines (dual)
+   *    2 = L2-loss support vector machines (primal)
+   *    3 = L1-loss support vector machines (dual)
+   *    4 = multi-class support vector machines by Crammer and Singer</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  Set the cost parameter C
+   *   (default: 1)</pre>
+   * 
+   * <pre> -Z
+   *  Turn on normalization of input data (default: off)</pre>
+   * 
+   * <pre> -N
+   *  Turn on nominal to binary conversion.</pre>
+   * 
+   * <pre> -M
+   *  Turn off missing value replacement.
+   *  WARNING: use only if your data has no missing values.</pre>
+   * 
+   * <pre> -P
+   *  Use probability estimation (default: off)
+   * currently for L2-regularized logistic regression only! </pre>
+   * 
+   * <pre> -E &lt;double&gt;
+   *  Set tolerance of termination criterion (default: 0.01)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  Set the parameters C of class i to weight[i]*C
+   *   (default: 1)</pre>
+   * 
+   * <pre> -B &lt;double&gt;
+   *  Add Bias term with the given value if &gt;= 0; if &lt; 0, no bias term added (default: 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options     the options to parse
+   * @throws Exception  if parsing fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSVMType(
+          new SelectedTag(Integer.parseInt(tmpStr), TAGS_SVMTYPE));
+    else
+      setSVMType(
+          new SelectedTag(SVMTYPE_L2LOSS_SVM_DUAL, TAGS_SVMTYPE));
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setCost(Double.parseDouble(tmpStr));
+    else
+      setCost(1);
+
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setEps(Double.parseDouble(tmpStr));
+    else
+      setEps(1e-3);
+
+    setNormalize(Utils.getFlag('Z', options));
+    
+    setConvertNominalToBinary(Utils.getFlag('N', options));
+    setDoNotReplaceMissingValues(Utils.getFlag('M', options));
+
+    tmpStr = Utils.getOption('B', options);
+    if (tmpStr.length() != 0)
+      setBias(Double.parseDouble(tmpStr));
+    else
+      setBias(1);
+
+    setWeights(Utils.getOption('W', options));
+
+    setProbabilityEstimates(Utils.getFlag('P', options));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Returns the current options
+   *
+   * @return            the current setup
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result  = new Vector();
+
+    result.add("-S");
+    result.add("" + m_SVMType);
+
+    result.add("-C");
+    result.add("" + getCost());
+
+    result.add("-E");
+    result.add("" + getEps());
+
+    result.add("-B");
+    result.add("" + getBias());
+
+    if (getNormalize())
+      result.add("-Z");
+    
+    if (getConvertNominalToBinary())
+      result.add("-N");
+    
+    if (getDoNotReplaceMissingValues())
+      result.add("-M");
+
+    if (getWeights().length() != 0) {
+      result.add("-W");
+      result.add("" + getWeights());
+    }
+
+    if (getProbabilityEstimates())
+      result.add("-P");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns whether the liblinear classes are present or not, i.e. whether the
+   * classes are in the classpath or not
+   *
+   * @return whether the liblinear classes are available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+
+  /**
+   * Sets type of SVM (default SVMTYPE_L2)
+   *
+   * @param value       the type of the SVM
+   */
+  public void setSVMType(SelectedTag value) {
+    if (value.getTags() == TAGS_SVMTYPE)
+      m_SVMType = value.getSelectedTag().getID();
+  }
+
+  /**
+   * Gets type of SVM
+   *
+   * @return            the type of the SVM
+   */
+  public SelectedTag getSVMType() {
+    return new SelectedTag(m_SVMType, TAGS_SVMTYPE);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String SVMTypeTipText() {
+    return "The type of SVM to use.";
+  }
+
+  /**
+   * Sets the cost parameter C (default 1)
+   *
+   * @param value       the cost value
+   */
+  public void setCost(double value) {
+    m_Cost = value;
+  }
+
+  /**
+   * Returns the cost parameter C
+   *
+   * @return            the cost value
+   */
+  public double getCost() {
+    return m_Cost;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String costTipText() {
+    return "The cost parameter C.";
+  }
+
+  /**
+   * Sets tolerance of termination criterion (default 0.001)
+   *
+   * @param value       the tolerance
+   */
+  public void setEps(double value) {
+    m_eps = value;
+  }
+
+  /**
+   * Gets tolerance of termination criterion
+   *
+   * @return            the current tolerance
+   */
+  public double getEps() {
+    return m_eps;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String epsTipText() {
+    return "The tolerance of the termination criterion.";
+  }
+
+  /**
+   * Sets bias term value (default 1)
+   * No bias term is added if value &lt; 0
+   *
+   * @param value       the bias term value
+   */
+  public void setBias(double value) {
+    m_Bias = value;
+  }
+
+  /**
+   * Returns bias term value (default 1)
+   * No bias term is added if value &lt; 0
+   *
+   * @return             the bias term value
+   */
+  public double getBias() {
+    return m_Bias;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String biasTipText() {
+    return "If >= 0, a bias term with that value is added; " +
+      "otherwise (<0) no bias term is added (default: 1).";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String normalizeTipText() {
+    return "Whether to normalize the data.";
+  }
+  
+  /**
+   * whether to normalize input data
+   *
+   * @param value       whether to normalize the data
+   */
+  public void setNormalize(boolean value) {
+    m_Normalize = value;
+  }
+
+  /**
+   * whether to normalize input data
+   *
+   * @return            true, if the data is normalized
+   */
+  public boolean getNormalize() {
+    return m_Normalize;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String convertNominalToBinaryTipText() {
+    return "Whether to turn on conversion of nominal attributes "
+      + "to binary.";
+  }
+  
+  /**
+   * Whether to turn on conversion of nominal attributes
+   * to binary.
+   * 
+   * @param b true if nominal to binary conversion is to be
+   * turned on
+   */
+  public void setConvertNominalToBinary(boolean b) {
+    m_nominalToBinary = b;
+  }
+  
+  /**
+   * Gets whether conversion of nominal to binary is
+   * turned on.
+   * 
+   * @return true if nominal to binary conversion is turned
+   * on.
+   */
+  public boolean getConvertNominalToBinary() {
+    return m_nominalToBinary;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String doNotReplaceMissingValuesTipText() {
+    return "Whether to turn off automatic replacement of missing "
+      + "values. WARNING: set to true only if the data does not "
+      + "contain missing values.";
+  }
+  
+  /**
+   * Whether to turn off automatic replacement of missing values.
+   * Set to true only if the data does not contain missing values.
+   * 
+   * @param b true if automatic missing values replacement is
+   * to be disabled.
+   */
+  public void setDoNotReplaceMissingValues(boolean b) {
+    m_noReplaceMissingValues = b;
+  }
+  
+  /**
+   * Gets whether automatic replacement of missing values is
+   * disabled.
+   * 
+   * @return true if automatic replacement of missing values
+   * is disabled.
+   */
+  public boolean getDoNotReplaceMissingValues() {
+    return m_noReplaceMissingValues;
+  }
+
+  /**
+   * Sets the parameters C of class i to weight[i]*C (default 1).
+   * Blank separated list of doubles.
+   *
+   * @param weightsStr          the weights (doubles, separated by blanks)
+   */
+  public void setWeights(String weightsStr) {
+    StringTokenizer       tok;
+    int                   i;
+
+    tok           = new StringTokenizer(weightsStr, " ");
+    m_Weight      = new double[tok.countTokens()];
+    m_WeightLabel = new int[tok.countTokens()];
+
+    if (m_Weight.length == 0)
+      System.out.println(
+          "Zero Weights processed. Default weights will be used");
+
+    for (i = 0; i < m_Weight.length; i++) {
+      m_Weight[i]      = Double.parseDouble(tok.nextToken());
+      m_WeightLabel[i] = i;
+    }
+  }
+
+  /**
+   * Gets the parameters C of class i to weight[i]*C (default 1).
+   * Blank separated doubles.
+   *
+   * @return            the weights (doubles separated by blanks)
+   */
+  public String getWeights() {
+    String      result;
+    int         i;
+
+    result = "";
+    for (i = 0; i < m_Weight.length; i++) {
+      if (i > 0)
+        result += " ";
+      result += Double.toString(m_Weight[i]);
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String weightsTipText() {
+    return "The weights to use for the classes, if empty 1 is used by default.";
+  }
+
+  /**
+   * Returns whether probability estimates are generated instead of -1/+1 for
+   * classification problems.
+   *
+   * @param value       whether to predict probabilities
+   */
+  public void setProbabilityEstimates(boolean value) {
+    m_ProbabilityEstimates = value;
+  }
+
+  /**
+   * Sets whether to generate probability estimates instead of -1/+1 for
+   * classification problems.
+   *
+   * @return            true, if probability estimates should be returned
+   */
+  public boolean getProbabilityEstimates() {
+    return m_ProbabilityEstimates;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String probabilityEstimatesTipText() {
+    return "Whether to generate probability estimates instead of -1/+1 for classification problems " +
+      "(currently for L2-regularized logistic regression only!)";
+  }
+
+  /**
+   * sets the specified field
+   *
+   * @param o           the object to set the field for
+   * @param name        the name of the field
+   * @param value       the new value of the field
+   */
+  protected void setField(Object o, String name, Object value) {
+    Field       f;
+
+    try {
+      f = o.getClass().getField(name);
+      f.set(o, value);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * sets the specified field in an array
+   *
+   * @param o           the object to set the field for
+   * @param name        the name of the field
+   * @param index       the index in the array
+   * @param value       the new value of the field
+   */
+  protected void setField(Object o, String name, int index, Object value) {
+    Field       f;
+
+    try {
+      f = o.getClass().getField(name);
+      Array.set(f.get(o), index, value);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * returns the current value of the specified field
+   *
+   * @param o           the object the field is member of
+   * @param name        the name of the field
+   * @return            the value
+   */
+  protected Object getField(Object o, String name) {
+    Field       f;
+    Object      result;
+
+    try {
+      f      = o.getClass().getField(name);
+      result = f.get(o);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * sets a new array for the field
+   *
+   * @param o           the object to set the array for
+   * @param name        the name of the field
+   * @param type        the type of the array
+   * @param length      the length of the one-dimensional array
+   */
+  protected void newArray(Object o, String name, Class type, int length) {
+    newArray(o, name, type, new int[]{length});
+  }
+
+  /**
+   * sets a new array for the field
+   *
+   * @param o           the object to set the array for
+   * @param name        the name of the field
+   * @param type        the type of the array
+   * @param dimensions  the dimensions of the array
+   */
+  protected void newArray(Object o, String name, Class type, int[] dimensions) {
+    Field       f;
+
+    try {
+      f = o.getClass().getField(name);
+      f.set(o, Array.newInstance(type, dimensions));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * executes the specified method and returns the result, if any
+   *
+   * @param o                   the object the method should be called from
+   * @param name                the name of the method
+   * @param paramClasses        the classes of the parameters
+   * @param paramValues         the values of the parameters
+   * @return                    the return value of the method, if any (in that case null)
+   */
+  protected Object invokeMethod(Object o, String name, Class[] paramClasses, Object[] paramValues) {
+    Method      m;
+    Object      result;
+
+    result = null;
+
+    try {
+      m      = o.getClass().getMethod(name, paramClasses);
+      result = m.invoke(o, paramValues);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * transfers the local variables into a svm_parameter object
+   *
+   * @return the configured svm_parameter object
+   */
+  protected Object getParameters() {
+    Object      result;
+    int         i;
+
+    try {
+      Class solverTypeEnumClass = Class.forName(CLASS_SOLVERTYPE);
+      Object[] enumValues = solverTypeEnumClass.getEnumConstants();
+      Object solverType = enumValues[m_SVMType];
+
+      Class[] constructorClasses = new Class[] { solverTypeEnumClass, double.class, double.class };
+      Constructor parameterConstructor = Class.forName(CLASS_PARAMETER).getConstructor(constructorClasses);
+
+      result = parameterConstructor.newInstance(solverType, Double.valueOf(m_Cost),
+          Double.valueOf(m_eps));
+
+      if (m_Weight.length > 0) {
+        invokeMethod(result, "setWeights", new Class[] { double[].class, int[].class },
+            new Object[] { m_Weight, m_WeightLabel });
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the svm_problem
+   *
+   * @param vx the x values
+   * @param vy the y values
+   * @param max_index
+   * @return the Problem object
+   */
+  protected Object getProblem(List<Object> vx, List<Integer> vy, int max_index) {
+    Object      result;
+
+    try {
+      result = Class.forName(CLASS_PROBLEM).newInstance();
+
+      setField(result, "l", Integer.valueOf(vy.size()));
+      setField(result, "n", Integer.valueOf(max_index));
+      setField(result, "bias", getBias());
+
+      newArray(result, "x", Class.forName(CLASS_FEATURENODE), new int[]{vy.size(), 0});
+      for (int i = 0; i < vy.size(); i++)
+        setField(result, "x", i, vx.get(i));
+
+      newArray(result, "y", Integer.TYPE, vy.size());
+      for (int i = 0; i < vy.size(); i++)
+        setField(result, "y", i, vy.get(i));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * returns an instance into a sparse liblinear array
+   *
+   * @param instance	the instance to work on
+   * @return		the liblinear array
+   * @throws Exception	if setup of array fails
+   */
+  protected Object instanceToArray(Instance instance) throws Exception {
+    int     index;
+    int     count;
+    int     i;
+    Object  result;
+
+    // determine number of non-zero attributes
+    count = 0;
+
+    for (i = 0; i < instance.numValues(); i++) {
+      if (instance.index(i) == instance.classIndex())
+        continue;
+      if (instance.valueSparse(i) != 0)
+        count++;
+    }
+
+    if (m_Bias >= 0) {
+      count++;
+    }
+
+    Class[] intDouble = new Class[] { int.class, double.class };
+    Constructor nodeConstructor = Class.forName(CLASS_FEATURENODE).getConstructor(intDouble);
+
+    // fill array
+    result = Array.newInstance(Class.forName(CLASS_FEATURENODE), count);
+    index  = 0;
+    for (i = 0; i < instance.numValues(); i++) {
+
+      int idx = instance.index(i);
+      double val = instance.valueSparse(i);
+
+      if (idx == instance.classIndex())
+        continue;
+      if (val == 0)
+        continue;
+
+      Object node = nodeConstructor.newInstance(Integer.valueOf(idx+1), Double.valueOf(val));
+      Array.set(result, index, node);
+      index++;
+    }
+
+    // add bias term
+    if (m_Bias >= 0) {
+      Integer idx = Integer.valueOf(instance.numAttributes()+1);
+      Double value = Double.valueOf(m_Bias);
+      Object node = nodeConstructor.newInstance(idx, value);
+      Array.set(result, index, node);
+    }
+
+    return result;
+  }
+  /**
+   * Computes the distribution for a given instance.
+   *
+   * @param instance 		the instance for which distribution is computed
+   * @return 			the distribution
+   * @throws Exception 		if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance (Instance instance) throws Exception {
+
+    if (!getDoNotReplaceMissingValues()) {
+      m_ReplaceMissingValues.input(instance);
+      m_ReplaceMissingValues.batchFinished();
+      instance = m_ReplaceMissingValues.output();
+    }
+
+    if (getConvertNominalToBinary() 
+        && m_NominalToBinary != null) {
+      m_NominalToBinary.input(instance);
+      m_NominalToBinary.batchFinished();
+      instance = m_NominalToBinary.output();
+    }
+
+    if (m_Filter != null) {
+      m_Filter.input(instance);
+      m_Filter.batchFinished();
+      instance = m_Filter.output();
+    }
+
+    Object x = instanceToArray(instance);
+    double v;
+    double[] result = new double[instance.numClasses()];
+    if (m_ProbabilityEstimates) {
+      if (m_SVMType != SVMTYPE_L2_LR) {
+        throw new WekaException("probability estimation is currently only " +
+            "supported for L2-regularized logistic regression");
+      }
+
+      int[] labels = (int[])invokeMethod(m_Model, "getLabels", null, null);
+      double[] prob_estimates = new double[instance.numClasses()];
+
+      v = ((Integer) invokeMethod(
+            Class.forName(CLASS_LINEAR).newInstance(),
+            "predictProbability",
+            new Class[]{
+              Class.forName(CLASS_MODEL),
+        Array.newInstance(Class.forName(CLASS_FEATURENODE), Array.getLength(x)).getClass(),
+        Array.newInstance(Double.TYPE, prob_estimates.length).getClass()},
+        new Object[]{ m_Model, x, prob_estimates})).doubleValue();
+
+      // Return order of probabilities to canonical weka attribute order
+      for (int k = 0; k < prob_estimates.length; k++) {
+        result[labels[k]] = prob_estimates[k];
+      }
+    }
+    else {
+      v = ((Integer) invokeMethod(
+            Class.forName(CLASS_LINEAR).newInstance(),
+            "predict",
+            new Class[]{
+              Class.forName(CLASS_MODEL),
+        Array.newInstance(Class.forName(CLASS_FEATURENODE), Array.getLength(x)).getClass()},
+        new Object[]{
+          m_Model,
+        x})).doubleValue();
+
+      assert (instance.classAttribute().isNominal());
+      result[(int) v] = 1;
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+//    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    return result;
+  }
+
+  /**
+   * builds the classifier
+   *
+   * @param insts       the training instances
+   * @throws Exception  if liblinear classes not in classpath or liblinear
+   *                    encountered a problem
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+    m_NominalToBinary = null;
+    m_Filter = null;
+    
+    if (!isPresent())
+      throw new Exception("liblinear classes not in CLASSPATH!");
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    if (!getDoNotReplaceMissingValues()) {
+      m_ReplaceMissingValues = new ReplaceMissingValues();
+      m_ReplaceMissingValues.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_ReplaceMissingValues);
+    }
+    
+    // can classifier handle the data?
+    // we check this here so that if the user turns off
+    // replace missing values filtering, it will fail
+    // if the data actually does have missing values
+    getCapabilities().testWithFail(insts);
+
+    if (getConvertNominalToBinary()) {
+      insts = nominalToBinary(insts);
+    }
+
+    if (getNormalize()) {
+      m_Filter = new Normalize();
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter);
+    }
+
+    List<Integer> vy = new ArrayList<Integer>(insts.numInstances());
+    List<Object> vx = new ArrayList<Object>(insts.numInstances());
+    int max_index = 0;
+
+    for (int d = 0; d < insts.numInstances(); d++) {
+      Instance inst = insts.instance(d);
+      Object x = instanceToArray(inst);
+      int m = Array.getLength(x);
+      if (m > 0)
+        max_index = Math.max(max_index, ((Integer) getField(Array.get(x, m - 1), "index")).intValue());
+      vx.add(x);
+      double classValue = inst.classValue();
+      int classValueInt = (int)classValue;
+      if (classValueInt != classValue) throw new RuntimeException("unsupported class value: " + classValue);
+      vy.add(Integer.valueOf(classValueInt));
+    }
+
+    if (!m_Debug) {
+      invokeMethod(
+          Class.forName(CLASS_LINEAR).newInstance(),
+          "disableDebugOutput", null, null);
+    } else {
+      invokeMethod(
+          Class.forName(CLASS_LINEAR).newInstance(),
+          "enableDebugOutput", null, null);
+    }
+
+    // reset the PRNG for regression-stable results
+    invokeMethod(
+        Class.forName(CLASS_LINEAR).newInstance(),
+        "resetRandom", null, null);
+
+    // train model
+    m_Model = invokeMethod(
+        Class.forName(CLASS_LINEAR).newInstance(),
+        "train",
+        new Class[]{
+          Class.forName(CLASS_PROBLEM),
+            Class.forName(CLASS_PARAMETER)},
+            new Object[]{
+              getProblem(vx, vy, max_index),
+            getParameters()});
+  }
+
+  /**
+   * turns on nominal to binary filtering
+   * if there are not only numeric attributes
+   */
+  private Instances nominalToBinary( Instances insts ) throws Exception {
+    boolean onlyNumeric = true;
+    for (int i = 0; i < insts.numAttributes(); i++) {
+      if (i != insts.classIndex()) {
+        if (!insts.attribute(i).isNumeric()) {
+          onlyNumeric = false;
+          break;
+        }
+      }
+    }
+
+    if (!onlyNumeric) {
+      m_NominalToBinary = new NominalToBinary();
+      m_NominalToBinary.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_NominalToBinary);
+    }
+    return insts;
+  }
+
+  /**
+   * returns a string representation
+   *
+   * @return            a string representation
+   */
+  public String toString() {
+    return "LibLINEAR wrapper";
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new LibLINEAR(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/LibSVM.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/LibSVM.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/LibSVM.java	(revision 29)
@@ -0,0 +1,1717 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LibSVM.java
+ * Copyright (C) 2005 Yasser EL-Manzalawy (original code)
+ * Copyright (C) 2005 University of Waikato, Hamilton, NZ (adapted code)
+ * 
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/*
+ * Modifications by FracPete:
+ * - complete overhaul to make it useable in Weka
+ * - accesses libsvm classes only via Reflection to make Weka compile without
+ *   the libsvm classes
+ * - uses more efficient code to transfer the data into the libsvm sparse format
+ */
+
+/** 
+ <!-- globalinfo-start -->
+ * A wrapper class for the libsvm tools (the libsvm classes, typically the jar file, need to be in the classpath to use this classifier).<br/>
+ * LibSVM runs faster than SMO since it uses LibSVM to build the SVM classifier.<br/>
+ * LibSVM allows users to experiment with One-class SVM, Regressing SVM, and nu-SVM supported by LibSVM tool. LibSVM reports many useful statistics about LibSVM classifier (e.g., confusion matrix,precision, recall, ROC score, etc.).<br/>
+ * <br/>
+ * Yasser EL-Manzalawy (2005). WLSVM. URL http://www.cs.iastate.edu/~yasser/wlsvm/.<br/>
+ * <br/>
+ * Chih-Chung Chang, Chih-Jen Lin (2001). LIBSVM - A Library for Support Vector Machines. URL http://www.csie.ntu.edu.tw/~cjlin/libsvm/.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{EL-Manzalawy2005,
+ *    author = {Yasser EL-Manzalawy},
+ *    note = {You don't need to include the WLSVM package in the CLASSPATH},
+ *    title = {WLSVM},
+ *    year = {2005},
+ *    URL = {http://www.cs.iastate.edu/\~yasser/wlsvm/}
+ * }
+ * 
+ * &#64;misc{Chang2001,
+ *    author = {Chih-Chung Chang and Chih-Jen Lin},
+ *    note = {The Weka classifier works with version 2.82 of LIBSVM},
+ *    title = {LIBSVM - A Library for Support Vector Machines},
+ *    year = {2001},
+ *    URL = {http://www.csie.ntu.edu.tw/\~cjlin/libsvm/}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;int&gt;
+ *  Set type of SVM (default: 0)
+ *    0 = C-SVC
+ *    1 = nu-SVC
+ *    2 = one-class SVM
+ *    3 = epsilon-SVR
+ *    4 = nu-SVR</pre>
+ * 
+ * <pre> -K &lt;int&gt;
+ *  Set type of kernel function (default: 2)
+ *    0 = linear: u'*v
+ *    1 = polynomial: (gamma*u'*v + coef0)^degree
+ *    2 = radial basis function: exp(-gamma*|u-v|^2)
+ *    3 = sigmoid: tanh(gamma*u'*v + coef0)</pre>
+ * 
+ * <pre> -D &lt;int&gt;
+ *  Set degree in kernel function (default: 3)</pre>
+ * 
+ * <pre> -G &lt;double&gt;
+ *  Set gamma in kernel function (default: 1/k)</pre>
+ * 
+ * <pre> -R &lt;double&gt;
+ *  Set coef0 in kernel function (default: 0)</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  Set the parameter C of C-SVC, epsilon-SVR, and nu-SVR
+ *   (default: 1)</pre>
+ * 
+ * <pre> -N &lt;double&gt;
+ *  Set the parameter nu of nu-SVC, one-class SVM, and nu-SVR
+ *   (default: 0.5)</pre>
+ * 
+ * <pre> -Z
+ *  Turns on normalization of input data (default: off)</pre>
+ * 
+ * <pre> -J
+ *  Turn off nominal to binary conversion.
+ *  WARNING: use only if your data is all numeric!</pre>
+ * 
+ * <pre> -V
+ *  Turn off missing value replacement.
+ *  WARNING: use only if your data has no missing values.</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  Set the epsilon in loss function of epsilon-SVR (default: 0.1)</pre>
+ * 
+ * <pre> -M &lt;double&gt;
+ *  Set cache memory size in MB (default: 40)</pre>
+ * 
+ * <pre> -E &lt;double&gt;
+ *  Set tolerance of termination criterion (default: 0.001)</pre>
+ * 
+ * <pre> -H
+ *  Turns the shrinking heuristics off (default: on)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  Set the parameters C of class i to weight[i]*C, for C-SVC.
+ *  E.g., for a 3-class problem, you could use "1 1 1" for equally
+ *  weighted classes.
+ *  (default: 1 for all classes)</pre>
+ * 
+ * <pre> -B
+ *  Trains a SVC model instead of a SVR one (default: SVR)</pre>
+ * 
+ * <pre> -model &lt;file&gt;
+ *  Specifies the filename to save the libsvm-internal model to.
+ *  Gets ignored if a directory is provided.</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Yasser EL-Manzalawy
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ * @see     weka.core.converters.LibSVMLoader
+ * @see     weka.core.converters.LibSVMSaver
+ */
+public class LibSVM 
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+  
+  /** the svm classname. */
+  protected final static String CLASS_SVM = "libsvm.svm";
+  
+  /** the svm_model classname. */
+  protected final static String CLASS_SVMMODEL = "libsvm.svm_model";
+  
+  /** the svm_problem classname. */
+  protected final static String CLASS_SVMPROBLEM = "libsvm.svm_problem";
+  
+  /** the svm_parameter classname. */
+  protected final static String CLASS_SVMPARAMETER = "libsvm.svm_parameter";
+  
+  /** the svm_node classname. */
+  protected final static String CLASS_SVMNODE = "libsvm.svm_node";
+  
+  /** serial UID. */
+  protected static final long serialVersionUID = 14172;
+  
+  /** LibSVM Model. */
+  protected Object m_Model;
+  
+  /** for normalizing the data. */
+  protected Filter m_Filter = null;
+    
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_ReplaceMissingValues;
+  
+  /** normalize input data. */
+  protected boolean m_Normalize = false;
+  
+  /** If true, the replace missing values filter is not applied. */
+  private boolean m_noReplaceMissingValues;
+  
+  /** SVM type C-SVC (classification). */
+  public static final int SVMTYPE_C_SVC = 0;
+  /** SVM type nu-SVC (classification). */
+  public static final int SVMTYPE_NU_SVC = 1;
+  /** SVM type one-class SVM (classification). */
+  public static final int SVMTYPE_ONE_CLASS_SVM = 2;
+  /** SVM type epsilon-SVR (regression). */
+  public static final int SVMTYPE_EPSILON_SVR = 3;
+  /** SVM type nu-SVR (regression). */
+  public static final int SVMTYPE_NU_SVR = 4;
+  /** SVM types. */
+  public static final Tag[] TAGS_SVMTYPE = {
+    new Tag(SVMTYPE_C_SVC, "C-SVC (classification)"),
+    new Tag(SVMTYPE_NU_SVC, "nu-SVC (classification)"),
+    new Tag(SVMTYPE_ONE_CLASS_SVM, "one-class SVM (classification)"),
+    new Tag(SVMTYPE_EPSILON_SVR, "epsilon-SVR (regression)"),
+    new Tag(SVMTYPE_NU_SVR, "nu-SVR (regression)")
+  };
+  
+  /** the SVM type. */
+  protected int m_SVMType = SVMTYPE_C_SVC;
+  
+  /** kernel type linear: u'*v. */
+  public static final int KERNELTYPE_LINEAR = 0;
+  /** kernel type polynomial: (gamma*u'*v + coef0)^degree. */
+  public static final int KERNELTYPE_POLYNOMIAL = 1;
+  /** kernel type radial basis function: exp(-gamma*|u-v|^2). */
+  public static final int KERNELTYPE_RBF = 2;
+  /** kernel type sigmoid: tanh(gamma*u'*v + coef0). */
+  public static final int KERNELTYPE_SIGMOID = 3;
+  /** the different kernel types. */
+  public static final Tag[] TAGS_KERNELTYPE = {
+    new Tag(KERNELTYPE_LINEAR, "linear: u'*v"),
+    new Tag(KERNELTYPE_POLYNOMIAL, "polynomial: (gamma*u'*v + coef0)^degree"),
+    new Tag(KERNELTYPE_RBF, "radial basis function: exp(-gamma*|u-v|^2)"),
+    new Tag(KERNELTYPE_SIGMOID, "sigmoid: tanh(gamma*u'*v + coef0)")
+  };
+  
+  /** the kernel type. */
+  protected int m_KernelType = KERNELTYPE_RBF;
+  
+  /** for poly - in older versions of libsvm declared as a double.
+   * At least since 2.82 it is an int. */
+  protected int m_Degree = 3;
+  
+  /** for poly/rbf/sigmoid. */
+  protected double m_Gamma = 0;
+  
+  /** for poly/rbf/sigmoid (the actual gamma). */
+  protected double m_GammaActual = 0;
+  
+  /** for poly/sigmoid. */
+  protected double m_Coef0 = 0;
+  
+  /** in MB. */
+  protected double m_CacheSize = 40;
+  
+  /** stopping criteria. */
+  protected double m_eps = 1e-3;
+  
+  /** cost, for C_SVC, EPSILON_SVR and NU_SVR. */
+  protected double m_Cost = 1;
+  
+  /** for C_SVC. */
+  protected int[] m_WeightLabel = new int[0];
+  
+  /** for C_SVC. */
+  protected double[] m_Weight = new double[0];
+  
+  /** for NU_SVC, ONE_CLASS, and NU_SVR. */
+  protected double m_nu = 0.5;
+  
+  /** loss, for EPSILON_SVR. */
+  protected double m_Loss = 0.1;
+  
+  /** use the shrinking heuristics. */
+  protected boolean m_Shrinking = true;	
+  
+  /** whether to generate probability estimates instead of +1/-1 in case of 
+   * classification problems. */
+  protected boolean m_ProbabilityEstimates = false;
+  
+  /** the file to save the libsvm-internal model to. */
+  protected File m_ModelFile = new File(System.getProperty("user.dir"));
+    
+  /** whether the libsvm classes are in the Classpath. */
+  protected static boolean m_Present = false;
+  static {
+    try {
+      Class.forName(CLASS_SVM);
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+  
+  /**
+   * Returns a string describing classifier.
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+      "A wrapper class for the libsvm tools (the libsvm classes, typically "
+    + "the jar file, need to be in the classpath to use this classifier).\n"
+    + "LibSVM runs faster than SMO since it uses LibSVM to build the SVM "
+    + "classifier.\n"
+    + "LibSVM allows users to experiment with One-class SVM, Regressing SVM, "
+    + "and nu-SVM supported by LibSVM tool. LibSVM reports many useful "
+    + "statistics about LibSVM classifier (e.g., confusion matrix,"
+    + "precision, recall, ROC score, etc.).\n"
+    + "\n"
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(TechnicalInformation.Field.AUTHOR, "Yasser EL-Manzalawy");
+    result.setValue(TechnicalInformation.Field.YEAR, "2005");
+    result.setValue(TechnicalInformation.Field.TITLE, "WLSVM");
+    result.setValue(TechnicalInformation.Field.NOTE, "LibSVM was originally developed as 'WLSVM'");
+    result.setValue(TechnicalInformation.Field.URL, "http://www.cs.iastate.edu/~yasser/wlsvm/");
+    result.setValue(TechnicalInformation.Field.NOTE, "You don't need to include the WLSVM package in the CLASSPATH");
+    
+    additional = result.add(Type.MISC);
+    additional.setValue(TechnicalInformation.Field.AUTHOR, "Chih-Chung Chang and Chih-Jen Lin");
+    additional.setValue(TechnicalInformation.Field.TITLE, "LIBSVM - A Library for Support Vector Machines");
+    additional.setValue(TechnicalInformation.Field.YEAR, "2001");
+    additional.setValue(TechnicalInformation.Field.URL, "http://www.csie.ntu.edu.tw/~cjlin/libsvm/");
+    additional.setValue(TechnicalInformation.Field.NOTE, "The Weka classifier works with version 2.82 of LIBSVM");
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector      result;
+    
+    result = new Vector();
+    
+    result.addElement(
+        new Option(
+            "\tSet type of SVM (default: 0)\n"
+            + "\t\t 0 = C-SVC\n" 
+            + "\t\t 1 = nu-SVC\n"
+            + "\t\t 2 = one-class SVM\n" 
+            + "\t\t 3 = epsilon-SVR\n"
+            + "\t\t 4 = nu-SVR", 
+            "S", 1, "-S <int>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet type of kernel function (default: 2)\n"
+            + "\t\t 0 = linear: u'*v\n"
+            + "\t\t 1 = polynomial: (gamma*u'*v + coef0)^degree\n"
+            + "\t\t 2 = radial basis function: exp(-gamma*|u-v|^2)\n"
+            + "\t\t 3 = sigmoid: tanh(gamma*u'*v + coef0)",
+            "K", 1, "-K <int>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet degree in kernel function (default: 3)", 
+            "D", 1, "-D <int>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet gamma in kernel function (default: 1/k)", 
+            "G", 1, "-G <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet coef0 in kernel function (default: 0)", 
+            "R", 1, "-R <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet the parameter C of C-SVC, epsilon-SVR, and nu-SVR\n"
+            + "\t (default: 1)",
+            "C", 1, "-C <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet the parameter nu of nu-SVC, one-class SVM, and nu-SVR\n"
+            + "\t (default: 0.5)",
+            "N", 1, "-N <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tTurns on normalization of input data (default: off)", 
+            "Z", 0, "-Z"));
+    
+    result.addElement(
+        new Option("\tTurn off nominal to binary conversion."
+            + "\n\tWARNING: use only if your data is all numeric!",
+            "J", 0, "-J"));
+    
+    result.addElement(
+        new Option("\tTurn off missing value replacement."
+            + "\n\tWARNING: use only if your data has no missing "
+            + "values.", "V", 0, "-V"));
+    
+    result.addElement(
+        new Option(
+            "\tSet the epsilon in loss function of epsilon-SVR (default: 0.1)",
+            "P", 1, "-P <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet cache memory size in MB (default: 40)", 
+            "M", 1, "-M <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tSet tolerance of termination criterion (default: 0.001)",
+            "E", 1, "-E <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tTurns the shrinking heuristics off (default: on)",
+            "H", 0, "-H"));
+    
+    result.addElement(
+        new Option(
+            "\tSet the parameters C of class i to weight[i]*C, for C-SVC.\n" 
+            + "\tE.g., for a 3-class problem, you could use \"1 1 1\" for equally\n"
+            + "\tweighted classes.\n"
+            + "\t(default: 1 for all classes)",
+            "W", 1, "-W <double>"));
+    
+    result.addElement(
+        new Option(
+            "\tTrains a SVC model instead of a SVR one (default: SVR)",
+            "B", 0, "-B"));
+    
+    result.addElement(
+        new Option(
+            "\tSpecifies the filename to save the libsvm-internal model to.\n"
+            + "\tGets ignored if a directory is provided.",
+            "model", 1, "-model <file>"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    return result.elements();
+  }
+  
+  /**
+   * Sets the classifier options <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;int&gt;
+   *  Set type of SVM (default: 0)
+   *    0 = C-SVC
+   *    1 = nu-SVC
+   *    2 = one-class SVM
+   *    3 = epsilon-SVR
+   *    4 = nu-SVR</pre>
+   * 
+   * <pre> -K &lt;int&gt;
+   *  Set type of kernel function (default: 2)
+   *    0 = linear: u'*v
+   *    1 = polynomial: (gamma*u'*v + coef0)^degree
+   *    2 = radial basis function: exp(-gamma*|u-v|^2)
+   *    3 = sigmoid: tanh(gamma*u'*v + coef0)</pre>
+   * 
+   * <pre> -D &lt;int&gt;
+   *  Set degree in kernel function (default: 3)</pre>
+   * 
+   * <pre> -G &lt;double&gt;
+   *  Set gamma in kernel function (default: 1/k)</pre>
+   * 
+   * <pre> -R &lt;double&gt;
+   *  Set coef0 in kernel function (default: 0)</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  Set the parameter C of C-SVC, epsilon-SVR, and nu-SVR
+   *   (default: 1)</pre>
+   * 
+   * <pre> -N &lt;double&gt;
+   *  Set the parameter nu of nu-SVC, one-class SVM, and nu-SVR
+   *   (default: 0.5)</pre>
+   * 
+   * <pre> -Z
+   *  Turns on normalization of input data (default: off)</pre>
+   * 
+   * <pre> -J
+   *  Turn off nominal to binary conversion.
+   *  WARNING: use only if your data is all numeric!</pre>
+   * 
+   * <pre> -V
+   *  Turn off missing value replacement.
+   *  WARNING: use only if your data has no missing values.</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  Set the epsilon in loss function of epsilon-SVR (default: 0.1)</pre>
+   * 
+   * <pre> -M &lt;double&gt;
+   *  Set cache memory size in MB (default: 40)</pre>
+   * 
+   * <pre> -E &lt;double&gt;
+   *  Set tolerance of termination criterion (default: 0.001)</pre>
+   * 
+   * <pre> -H
+   *  Turns the shrinking heuristics off (default: on)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  Set the parameters C of class i to weight[i]*C, for C-SVC.
+   *  E.g., for a 3-class problem, you could use "1 1 1" for equally
+   *  weighted classes.
+   *  (default: 1 for all classes)</pre>
+   * 
+   * <pre> -B
+   *  Trains a SVC model instead of a SVR one (default: SVR)</pre>
+   * 
+   * <pre> -model &lt;file&gt;
+   *  Specifies the filename to save the libsvm-internal model to.
+   *  Gets ignored if a directory is provided.</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options     the options to parse
+   * @throws Exception  if parsing fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSVMType(
+          new SelectedTag(Integer.parseInt(tmpStr), TAGS_SVMTYPE));
+    else
+      setSVMType(
+          new SelectedTag(SVMTYPE_C_SVC, TAGS_SVMTYPE));
+    
+    tmpStr = Utils.getOption('K', options);
+    if (tmpStr.length() != 0)
+      setKernelType(
+          new SelectedTag(Integer.parseInt(tmpStr), TAGS_KERNELTYPE));
+    else
+      setKernelType(
+          new SelectedTag(KERNELTYPE_RBF, TAGS_KERNELTYPE));
+    
+    tmpStr = Utils.getOption('D', options);
+    if (tmpStr.length() != 0)
+      setDegree(Integer.parseInt(tmpStr));
+    else
+      setDegree(3);
+    
+    tmpStr = Utils.getOption('G', options);
+    if (tmpStr.length() != 0)
+      setGamma(Double.parseDouble(tmpStr));
+    else
+      setGamma(0);
+    
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setCoef0(Double.parseDouble(tmpStr));
+    else
+      setCoef0(0);
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNu(Double.parseDouble(tmpStr));
+    else
+      setNu(0.5);
+    
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setCacheSize(Double.parseDouble(tmpStr));
+    else
+      setCacheSize(40);
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setCost(Double.parseDouble(tmpStr));
+    else
+      setCost(1);
+    
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setEps(Double.parseDouble(tmpStr));
+    else
+      setEps(1e-3);
+    
+    setNormalize(Utils.getFlag('Z', options));
+    
+    setDoNotReplaceMissingValues(Utils.getFlag("V", options));
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setLoss(Double.parseDouble(tmpStr));
+    else
+      setLoss(0.1);
+    
+    setShrinking(!Utils.getFlag('H', options));
+    
+    setWeights(Utils.getOption('W', options));
+    
+    setProbabilityEstimates(Utils.getFlag('B', options));
+    
+    tmpStr = Utils.getOption("model", options);
+    if (tmpStr.length() == 0)
+      m_ModelFile = new File(System.getProperty("user.dir"));
+    else
+      m_ModelFile = new File(tmpStr);
+  }
+  
+  /**
+   * Returns the current options.
+   * 
+   * @return            the current setup
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result  = new Vector();
+    
+    result.add("-S");
+    result.add("" + m_SVMType);
+    
+    result.add("-K");
+    result.add("" + m_KernelType);
+    
+    result.add("-D");
+    result.add("" + getDegree());
+    
+    result.add("-G");
+    result.add("" + getGamma());
+    
+    result.add("-R");
+    result.add("" + getCoef0());
+    
+    result.add("-N");
+    result.add("" + getNu());
+    
+    result.add("-M");
+    result.add("" + getCacheSize());
+    
+    result.add("-C");
+    result.add("" + getCost());
+    
+    result.add("-E");
+    result.add("" + getEps());
+    
+    result.add("-P");
+    result.add("" + getLoss());
+    
+    if (!getShrinking())
+      result.add("-H");
+    
+    if (getNormalize())
+      result.add("-Z");
+        
+    if (getDoNotReplaceMissingValues())
+      result.add("-V");
+    
+    if (getWeights().length() != 0) {
+      result.add("-W");
+      result.add("" + getWeights());
+    }
+    
+    if (getProbabilityEstimates())
+      result.add("-B");
+    
+    result.add("-model");
+    result.add(m_ModelFile.getAbsolutePath());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * returns whether the libsvm classes are present or not, i.e. whether the 
+   * classes are in the classpath or not
+   *
+   * @return whether the libsvm classes are available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+  
+  /**
+   * Sets type of SVM (default SVMTYPE_C_SVC).
+   * 
+   * @param value       the type of the SVM
+   */
+  public void setSVMType(SelectedTag value) {
+    if (value.getTags() == TAGS_SVMTYPE)
+      m_SVMType = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Gets type of SVM.
+   * 
+   * @return            the type of the SVM
+   */
+  public SelectedTag getSVMType() {
+    return new SelectedTag(m_SVMType, TAGS_SVMTYPE);
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String SVMTypeTipText() {
+    return "The type of SVM to use.";
+  }
+  
+  /**
+   * Sets type of kernel function (default KERNELTYPE_RBF).
+   * 
+   * @param value       the kernel type
+   */
+  public void setKernelType(SelectedTag value) {
+    if (value.getTags() == TAGS_KERNELTYPE)
+      m_KernelType = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Gets type of kernel function.
+   * 
+   * @return            the kernel type
+   */
+  public SelectedTag getKernelType() {
+    return new SelectedTag(m_KernelType, TAGS_KERNELTYPE);
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String kernelTypeTipText() {
+    return "The type of kernel to use";
+  }
+  
+  /**
+   * Sets the degree of the kernel.
+   * 
+   * @param value       the degree of the kernel
+   */
+  public void setDegree(int value) {
+    m_Degree = value;
+  }
+  
+  /**
+   * Gets the degree of the kernel.
+   * 
+   * @return            the degree of the kernel
+   */
+  public int getDegree() {
+    return m_Degree;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String degreeTipText() {
+    return "The degree of the kernel.";
+  }
+  
+  /**
+   * Sets gamma (default = 1/no of attributes).
+   * 
+   * @param value       the gamma value
+   */
+  public void setGamma(double value) {
+    m_Gamma = value;
+  }
+  
+  /**
+   * Gets gamma.
+   * 
+   * @return            the current gamma
+   */
+  public double getGamma() {
+    return m_Gamma;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String gammaTipText() {
+    return "The gamma to use, if 0 then 1/max_index is used.";
+  }
+  
+  /**
+   * Sets coef (default 0).
+   * 
+   * @param value       the coef
+   */
+  public void setCoef0(double value) {
+    m_Coef0 = value;
+  }
+  
+  /**
+   * Gets coef.
+   * 
+   * @return            the coef
+   */
+  public double getCoef0() {
+    return m_Coef0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String coef0TipText() {
+    return "The coefficient to use.";
+  }
+  
+  /**
+   * Sets nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5).
+   * 
+   * @param value       the new nu value
+   */
+  public void setNu(double value) {
+    m_nu = value;
+  }
+  
+  /**
+   * Gets nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5).
+   * 
+   * @return            the current nu value
+   */
+  public double getNu() {
+    return m_nu;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String nuTipText() {
+    return "The value of nu for nu-SVC, one-class SVM and nu-SVR.";
+  }
+  
+  /**
+   * Sets cache memory size in MB (default 40).
+   * 
+   * @param value       the memory size in MB
+   */
+  public void setCacheSize(double value) {
+    m_CacheSize = value;
+  }
+  
+  /**
+   * Gets cache memory size in MB.
+   * 
+   * @return            the memory size in MB
+   */
+  public double getCacheSize() {
+    return m_CacheSize;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String cacheSizeTipText() {
+    return "The cache size in MB.";
+  }
+  
+  /**
+   * Sets the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1).
+   * 
+   * @param value       the cost value
+   */
+  public void setCost(double value) {
+    m_Cost = value;
+  }
+  
+  /**
+   * Sets the parameter C of C-SVC, epsilon-SVR, and nu-SVR.
+   * 
+   * @return            the cost value
+   */
+  public double getCost() {
+    return m_Cost;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String costTipText() {
+    return "The cost parameter C for C-SVC, epsilon-SVR and nu-SVR.";
+  }
+  
+  /**
+   * Sets tolerance of termination criterion (default 0.001).
+   * 
+   * @param value       the tolerance
+   */
+  public void setEps(double value) {
+    m_eps = value;
+  }
+  
+  /**
+   * Gets tolerance of termination criterion.
+   * 
+   * @return            the current tolerance
+   */
+  public double getEps() {
+    return m_eps;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String epsTipText() {
+    return "The tolerance of the termination criterion.";
+  }
+  
+  /**
+   * Sets the epsilon in loss function of epsilon-SVR (default 0.1).
+   * 
+   * @param value       the loss epsilon
+   */
+  public void setLoss(double value) {
+    m_Loss = value;
+  }
+  
+  /**
+   * Gets the epsilon in loss function of epsilon-SVR.
+   * 
+   * @return            the loss epsilon
+   */
+  public double getLoss() {
+    return m_Loss;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String lossTipText() {
+    return "The epsilon for the loss function in epsilon-SVR.";
+  }
+  
+  /**
+   * whether to use the shrinking heuristics.
+   * 
+   * @param value       true uses shrinking
+   */
+  public void setShrinking(boolean value) {
+    m_Shrinking = value;
+  }
+  
+  /**
+   * whether to use the shrinking heuristics.
+   * 
+   * @return            true, if shrinking is used
+   */
+  public boolean getShrinking() {
+    return m_Shrinking;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String shrinkingTipText() {
+    return "Whether to use the shrinking heuristic.";
+  }
+  
+  /**
+   * whether to normalize input data.
+   * 
+   * @param value       whether to normalize the data
+   */
+  public void setNormalize(boolean value) {
+    m_Normalize = value;
+  }
+  
+  /**
+   * whether to normalize input data.
+   * 
+   * @return            true, if the data is normalized
+   */
+  public boolean getNormalize() {
+    return m_Normalize;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String normalizeTipText() {
+    return "Whether to normalize the data.";
+  }
+    
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String doNotReplaceMissingValuesTipText() {
+    return "Whether to turn off automatic replacement of missing "
+      + "values. WARNING: set to true only if the data does not "
+      + "contain missing values.";
+  }
+  
+  /**
+   * Whether to turn off automatic replacement of missing values.
+   * Set to true only if the data does not contain missing values.
+   * 
+   * @param b true if automatic missing values replacement is
+   * to be disabled.
+   */
+  public void setDoNotReplaceMissingValues(boolean b) {
+    m_noReplaceMissingValues = b;
+  }
+  
+  /**
+   * Gets whether automatic replacement of missing values is
+   * disabled.
+   * 
+   * @return true if automatic replacement of missing values
+   * is disabled.
+   */
+  public boolean getDoNotReplaceMissingValues() {
+    return m_noReplaceMissingValues;
+  }
+  
+  /**
+   * Sets the parameters C of class i to weight[i]*C, for C-SVC (default 1).
+   * Blank separated list of doubles.
+   * 
+   * @param weightsStr          the weights (doubles, separated by blanks)
+   */
+  public void setWeights(String weightsStr) {
+    StringTokenizer       tok;
+    int                   i;
+    
+    tok           = new StringTokenizer(weightsStr, " ");
+    m_Weight      = new double[tok.countTokens()];
+    m_WeightLabel = new int[tok.countTokens()];
+    
+    if (m_Weight.length == 0)
+      System.out.println(
+          "Zero Weights processed. Default weights will be used");
+    
+    for (i = 0; i < m_Weight.length; i++) {
+      m_Weight[i]      = Double.parseDouble(tok.nextToken());
+      m_WeightLabel[i] = i;
+    }
+  }
+  
+  /**
+   * Gets the parameters C of class i to weight[i]*C, for C-SVC (default 1).
+   * Blank separated doubles.
+   * 
+   * @return            the weights (doubles separated by blanks)
+   */
+  public String getWeights() {
+    String      result;
+    int         i;
+    
+    result = "";
+    for (i = 0; i < m_Weight.length; i++) {
+      if (i > 0)
+        result += " ";
+      result += Double.toString(m_Weight[i]);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String weightsTipText() {
+    return "The weights to use for the classes (blank-separated list, eg, \"1 1 1\" for a 3-class problem), if empty 1 is used by default.";
+  }
+  
+  /**
+   * Sets whether probability estimates are generated instead of -1/+1 for 
+   * classification problems.
+   * 
+   * @param value       whether to predict probabilities
+   */
+  public void setProbabilityEstimates(boolean value) {
+    m_ProbabilityEstimates = value;
+  }
+  
+  /**
+   * Returns whether to generate probability estimates instead of -1/+1 for 
+   * classification problems.
+   * 
+   * @return            true, if probability estimates should be returned
+   */
+  public boolean getProbabilityEstimates() {
+    return m_ProbabilityEstimates;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String probabilityEstimatesTipText() {
+    return "Whether to generate probability estimates instead of -1/+1 for classification problems.";
+  }
+  
+  /**
+   * Sets the file to save the libsvm-internal model to. No model is saved if
+   * pointing to a directory.
+   * 
+   * @param value       the filename/directory
+   */
+  public void setModelFile(File value) {
+    if (value == null)
+      m_ModelFile = new File(System.getProperty("user.dir"));
+    else
+      m_ModelFile = value;
+  }
+  
+  /**
+   * Returns the file to save the libsvm-internal model to. No model is saved
+   * if pointing to a directory.
+   * 
+   * @return            the file object
+   */
+  public File getModelFile() {
+    return m_ModelFile;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String modelFileTipText() {
+    return "The file to save the libsvm-internal model to; no model is saved if pointing to a directory.";
+  }
+  
+  /**
+   * sets the specified field.
+   * 
+   * @param o           the object to set the field for
+   * @param name        the name of the field
+   * @param value       the new value of the field
+   */
+  protected void setField(Object o, String name, Object value) {
+    Field       f;
+    
+    try {
+      f = o.getClass().getField(name);
+      f.set(o, value);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * sets the specified field in an array.
+   * 
+   * @param o           the object to set the field for
+   * @param name        the name of the field
+   * @param index       the index in the array
+   * @param value       the new value of the field
+   */
+  protected void setField(Object o, String name, int index, Object value) {
+    Field       f;
+    
+    try {
+      f = o.getClass().getField(name);
+      Array.set(f.get(o), index, value);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * returns the current value of the specified field.
+   * 
+   * @param o           the object the field is member of
+   * @param name        the name of the field
+   * @return            the value
+   */
+  protected Object getField(Object o, String name) {
+    Field       f;
+    Object      result;
+    
+    try {
+      f      = o.getClass().getField(name);
+      result = f.get(o);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * sets a new array for the field.
+   * 
+   * @param o           the object to set the array for
+   * @param name        the name of the field
+   * @param type        the type of the array
+   * @param length      the length of the one-dimensional array
+   */
+  protected void newArray(Object o, String name, Class type, int length) {
+    newArray(o, name, type, new int[]{length});
+  }
+  
+  /**
+   * sets a new array for the field.
+   * 
+   * @param o           the object to set the array for
+   * @param name        the name of the field
+   * @param type        the type of the array
+   * @param dimensions  the dimensions of the array
+   */
+  protected void newArray(Object o, String name, Class type, int[] dimensions) {
+    Field       f;
+    
+    try {
+      f = o.getClass().getField(name);
+      f.set(o, Array.newInstance(type, dimensions));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * executes the specified method and returns the result, if any.
+   * 
+   * @param o                   the object the method should be called from
+   * @param name                the name of the method
+   * @param paramClasses        the classes of the parameters
+   * @param paramValues         the values of the parameters
+   * @return                    the return value of the method, if any (in that case null)
+   */
+  protected Object invokeMethod(Object o, String name, Class[] paramClasses, Object[] paramValues) {
+    Method      m;
+    Object      result;
+    
+    result = null;
+    
+    try {
+      m      = o.getClass().getMethod(name, paramClasses);
+      result = m.invoke(o, paramValues);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * transfers the local variables into a svm_parameter object.
+   * 
+   * @return the configured svm_parameter object
+   */
+  protected Object getParameters() {
+    Object      result;
+    int         i;
+    
+    try {
+      result = Class.forName(CLASS_SVMPARAMETER).newInstance();
+      
+      setField(result, "svm_type", new Integer(m_SVMType));
+      setField(result, "kernel_type", new Integer(m_KernelType));
+      setField(result, "degree", new Integer(m_Degree));
+      setField(result, "gamma", new Double(m_GammaActual));
+      setField(result, "coef0", new Double(m_Coef0));
+      setField(result, "nu", new Double(m_nu));
+      setField(result, "cache_size", new Double(m_CacheSize));
+      setField(result, "C", new Double(m_Cost));
+      setField(result, "eps", new Double(m_eps));
+      setField(result, "p", new Double(m_Loss));
+      setField(result, "shrinking", new Integer(m_Shrinking ? 1 : 0));
+      setField(result, "nr_weight", new Integer(m_Weight.length));
+      setField(result, "probability", new Integer(m_ProbabilityEstimates ? 1 : 0));
+      
+      newArray(result, "weight", Double.TYPE, m_Weight.length);
+      newArray(result, "weight_label", Integer.TYPE, m_Weight.length);
+      for (i = 0; i < m_Weight.length; i++) {
+        setField(result, "weight", i, new Double(m_Weight[i]));
+        setField(result, "weight_label", i, new Integer(m_WeightLabel[i]));
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the svm_problem.
+   * 
+   * @param vx the x values
+   * @param vy the y values
+   * @return the svm_problem object
+   */
+  protected Object getProblem(Vector vx, Vector vy) {
+    Object      result;
+    
+    try {
+      result = Class.forName(CLASS_SVMPROBLEM).newInstance();
+      
+      setField(result, "l", new Integer(vy.size()));
+      
+      newArray(result, "x", Class.forName(CLASS_SVMNODE), new int[]{vy.size(), 0});
+      for (int i = 0; i < vy.size(); i++)
+        setField(result, "x", i, vx.elementAt(i));
+      
+      newArray(result, "y", Double.TYPE, vy.size());
+      for (int i = 0; i < vy.size(); i++)
+        setField(result, "y", i, vy.elementAt(i));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns an instance into a sparse libsvm array.
+   * 
+   * @param instance	the instance to work on
+   * @return		the libsvm array
+   * @throws Exception	if setup of array fails
+   */
+  protected Object instanceToArray(Instance instance) throws Exception {
+    int		index;
+    int		count;
+    int 	i;
+    Object 	result;
+    
+    // determine number of non-zero attributes
+    /*for (i = 0; i < instance.numAttributes(); i++) {
+      if (i == instance.classIndex())
+	continue;
+      if (instance.value(i) != 0)
+	count++;
+    } */
+    count = 0;
+    for (i = 0; i < instance.numValues(); i++) {
+      if (instance.index(i) == instance.classIndex())
+        continue;
+      if (instance.valueSparse(i) != 0)
+        count++;
+    }
+
+    // fill array
+    /* result = Array.newInstance(Class.forName(CLASS_SVMNODE), count);
+    index  = 0;
+    for (i = 0; i < instance.numAttributes(); i++) {
+      if (i == instance.classIndex())
+	continue;
+      if (instance.value(i) == 0)
+	continue;
+
+      Array.set(result, index, Class.forName(CLASS_SVMNODE).newInstance());
+      setField(Array.get(result, index), "index", new Integer(i + 1));
+      setField(Array.get(result, index), "value", new Double(instance.value(i)));
+      index++;
+    } */
+    
+    result = Array.newInstance(Class.forName(CLASS_SVMNODE), count);
+    index  = 0;
+    for (i = 0; i < instance.numValues(); i++) {
+      
+      int idx = instance.index(i);
+      if (idx == instance.classIndex())
+        continue;
+      if (instance.valueSparse(i) == 0)
+        continue;
+
+      Array.set(result, index, Class.forName(CLASS_SVMNODE).newInstance());
+      setField(Array.get(result, index), "index", new Integer(idx + 1));
+      setField(Array.get(result, index), "value", new Double(instance.valueSparse(i)));
+      index++;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Computes the distribution for a given instance. 
+   * In case of 1-class classification, 1 is returned at index 0 if libsvm 
+   * returns 1 and NaN (= missing) if libsvm returns -1.
+   *
+   * @param instance 		the instance for which distribution is computed
+   * @return 			the distribution
+   * @throws Exception 		if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance (Instance instance) throws Exception {	
+    int[] labels = new int[instance.numClasses()];
+    double[] prob_estimates = null;
+
+    if (m_ProbabilityEstimates) {
+      invokeMethod(
+	  Class.forName(CLASS_SVM).newInstance(),
+	  "svm_get_labels",
+	  new Class[]{
+	    Class.forName(CLASS_SVMMODEL), 
+	    Array.newInstance(Integer.TYPE, instance.numClasses()).getClass()},
+	    new Object[]{
+	    m_Model, 
+	    labels});
+
+      prob_estimates = new double[instance.numClasses()];
+    }
+    
+    if (!getDoNotReplaceMissingValues()) {
+      m_ReplaceMissingValues.input(instance);
+      m_ReplaceMissingValues.batchFinished();
+      instance = m_ReplaceMissingValues.output();
+    }
+    
+    if (m_Filter != null) {
+      m_Filter.input(instance);
+      m_Filter.batchFinished();
+      instance = m_Filter.output();
+    }
+
+    Object x = instanceToArray(instance);
+    double v;
+    double[] result = new double[instance.numClasses()];
+    if (    m_ProbabilityEstimates 
+	 && ((m_SVMType == SVMTYPE_C_SVC) || (m_SVMType == SVMTYPE_NU_SVC)) ) {
+      v = ((Double) invokeMethod(
+          Class.forName(CLASS_SVM).newInstance(),
+          "svm_predict_probability",
+          new Class[]{
+            Class.forName(CLASS_SVMMODEL), 
+            Array.newInstance(Class.forName(CLASS_SVMNODE), Array.getLength(x)).getClass(),
+            Array.newInstance(Double.TYPE, prob_estimates.length).getClass()},
+          new Object[]{
+            m_Model, 
+            x,
+            prob_estimates})).doubleValue();
+
+      // Return order of probabilities to canonical weka attribute order
+      for (int k = 0; k < prob_estimates.length; k++) {
+        result[labels[k]] = prob_estimates[k];
+      }
+    }
+    else {
+      v = ((Double) invokeMethod(
+          Class.forName(CLASS_SVM).newInstance(),
+          "svm_predict",
+          new Class[]{
+            Class.forName(CLASS_SVMMODEL), 
+            Array.newInstance(Class.forName(CLASS_SVMNODE), Array.getLength(x)).getClass()},
+          new Object[]{
+            m_Model, 
+            x})).doubleValue();
+      
+      if (instance.classAttribute().isNominal()) {
+	if (m_SVMType == SVMTYPE_ONE_CLASS_SVM) {
+	  if (v > 0)
+	    result[0] = 1;
+	  else
+	    result[0] = Double.NaN;  // outlier
+	}
+	else {
+	  result[(int) v] = 1;
+	}
+      }
+      else {
+	result[0] = v;
+      }
+    }
+
+    return result;                
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+
+    // class
+    result.enableDependency(Capability.UNARY_CLASS);
+    result.enableDependency(Capability.NOMINAL_CLASS);
+    result.enableDependency(Capability.NUMERIC_CLASS);
+    result.enableDependency(Capability.DATE_CLASS);
+
+    switch (m_SVMType) {
+      case SVMTYPE_C_SVC:
+      case SVMTYPE_NU_SVC:
+	result.enable(Capability.NOMINAL_CLASS);
+	break;
+	
+      case SVMTYPE_ONE_CLASS_SVM:
+	result.enable(Capability.UNARY_CLASS);
+	break;
+	
+      case SVMTYPE_EPSILON_SVR:
+      case SVMTYPE_NU_SVR:
+	result.enable(Capability.NUMERIC_CLASS);
+	result.enable(Capability.DATE_CLASS);
+	break;
+	
+      default:
+	throw new IllegalArgumentException("SVMType " + m_SVMType + " is not supported!");
+    }
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * builds the classifier.
+   * 
+   * @param insts       the training instances
+   * @throws Exception  if libsvm classes not in classpath or libsvm
+   *                    encountered a problem
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+    m_Filter = null;
+    
+    if (!isPresent())
+      throw new Exception("libsvm classes not in CLASSPATH!");
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    if (!getDoNotReplaceMissingValues()) {
+      m_ReplaceMissingValues = new ReplaceMissingValues();
+      m_ReplaceMissingValues.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_ReplaceMissingValues);
+    }
+    
+    // can classifier handle the data?
+    // we check this here so that if the user turns off
+    // replace missing values filtering, it will fail
+    // if the data actually does have missing values
+    getCapabilities().testWithFail(insts);
+        
+    if (getNormalize()) {
+      m_Filter = new Normalize();
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter);
+    }
+    
+    Vector vy = new Vector();
+    Vector vx = new Vector();
+    int max_index = 0;
+    
+    for (int d = 0; d < insts.numInstances(); d++) {
+      Instance inst = insts.instance(d);
+      Object x = instanceToArray(inst);
+      int m = Array.getLength(x);
+      
+      if (m > 0)
+        max_index = Math.max(max_index, ((Integer) getField(Array.get(x, m - 1), "index")).intValue());
+      vx.addElement(x);
+      vy.addElement(new Double(inst.classValue()));
+    }
+    
+    // calculate actual gamma
+    if (getGamma() == 0)
+      m_GammaActual = 1.0 / max_index;
+    else
+      m_GammaActual = m_Gamma;
+
+    // check parameter
+    String error_msg = (String) invokeMethod(
+        Class.forName(CLASS_SVM).newInstance(), 
+        "svm_check_parameter", 
+        new Class[]{
+          Class.forName(CLASS_SVMPROBLEM), 
+          Class.forName(CLASS_SVMPARAMETER)},
+        new Object[]{
+          getProblem(vx, vy), 
+          getParameters()});
+    
+    if (error_msg != null)
+      throw new Exception("Error: " + error_msg);
+    
+    // train model
+    m_Model = invokeMethod(
+        Class.forName(CLASS_SVM).newInstance(), 
+        "svm_train", 
+        new Class[]{
+          Class.forName(CLASS_SVMPROBLEM), 
+          Class.forName(CLASS_SVMPARAMETER)},
+        new Object[]{
+          getProblem(vx, vy), 
+          getParameters()});
+    
+    // save internal model?
+    if (!m_ModelFile.isDirectory()) {
+      invokeMethod(
+	  Class.forName(CLASS_SVM).newInstance(), 
+	  "svm_save_model", 
+	  new Class[]{
+	    String.class, 
+	    Class.forName(CLASS_SVMMODEL)},
+	    new Object[]{
+	    m_ModelFile.getAbsolutePath(), 
+	    m_Model});
+    }
+  }
+    
+  /**
+   * returns a string representation.
+   * 
+   * @return            a string representation
+   */
+  public String toString() {
+    return "LibSVM wrapper, original code by Yasser EL-Manzalawy (= WLSVM)";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   * 
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new LibSVM(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/LinearRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/LinearRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/LinearRegression.java	(revision 29)
@@ -0,0 +1,925 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LinearRegression.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Matrix;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for using linear regression for prediction. Uses the Akaike criterion for model selection, and is able to deal with weighted instances.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Produce debugging output.
+ *  (default no debugging output)</pre>
+ * 
+ * <pre> -S &lt;number of selection method&gt;
+ *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
+ *  (default 0 = M5' method)</pre>
+ * 
+ * <pre> -C
+ *  Do not try to eliminate colinear attributes.
+ * </pre>
+ * 
+ * <pre> -R &lt;double&gt;
+ *  Set ridge parameter (default 1.0e-8).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class LinearRegression extends AbstractClassifier implements OptionHandler,
+  WeightedInstancesHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3364580862046573747L;
+
+  /** Array for storing coefficients of linear regression. */
+  private double[] m_Coefficients;
+
+  /** Which attributes are relevant? */
+  private boolean[] m_SelectedAttributes;
+
+  /** Variable for storing transformed training data. */
+  private Instances m_TransformedData;
+
+  /** The filter for removing missing values. */
+  private ReplaceMissingValues m_MissingFilter;
+
+  /** The filter storing the transformation from nominal to 
+      binary attributes. */
+  private NominalToBinary m_TransformFilter;
+
+  /** The standard deviations of the class attribute */
+  private double m_ClassStdDev;
+
+  /** The mean of the class attribute */
+  private double m_ClassMean;
+
+  /** The index of the class attribute */
+  private int m_ClassIndex;
+
+  /** The attributes means */
+  private double[] m_Means;
+
+  /** The attribute standard deviations */
+  private double[] m_StdDevs;
+
+  /** True if debug output will be printed */
+  private boolean b_Debug;
+
+  /** The current attribute selection method */
+  private int m_AttributeSelection;
+
+  /** Attribute selection method: M5 method */
+  public static final int SELECTION_M5 = 0;
+  /** Attribute selection method: No attribute selection */
+  public static final int SELECTION_NONE = 1;
+  /** Attribute selection method: Greedy method */
+  public static final int SELECTION_GREEDY = 2;
+  /** Attribute selection methods */
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(SELECTION_NONE, "No attribute selection"),
+    new Tag(SELECTION_M5, "M5 method"),
+    new Tag(SELECTION_GREEDY, "Greedy method")
+  };
+
+  /** Try to eliminate correlated attributes? */
+  private boolean m_EliminateColinearAttributes = true;
+
+  /** Turn off all checks and conversions? */
+  private boolean m_checksTurnedOff = false;
+
+  /** The ridge parameter */
+  private double m_Ridge = 1.0e-8;
+
+  /**
+   * Turns off checks for missing values, etc. Use with caution.
+   * Also turns off scaling.
+   */
+  public void turnChecksOff() {
+
+    m_checksTurnedOff = true;
+  }
+
+  /**
+   * Turns on checks for missing values, etc. Also turns
+   * on scaling.
+   */
+  public void turnChecksOn() {
+
+    m_checksTurnedOff = false;
+  }
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for using linear regression for prediction. Uses the Akaike "
+      +"criterion for model selection, and is able to deal with weighted "
+      +"instances.";
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds a regression model for the given data.
+   *
+   * @param data the training data to be used for generating the
+   * linear regression function
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+  
+    if (!m_checksTurnedOff) {
+      // can classifier handle the data?
+      getCapabilities().testWithFail(data);
+
+      // remove instances with missing class
+      data = new Instances(data);
+      data.deleteWithMissingClass();
+    }
+
+    // Preprocess instances
+    if (!m_checksTurnedOff) {
+      m_TransformFilter = new NominalToBinary();
+      m_TransformFilter.setInputFormat(data);
+      data = Filter.useFilter(data, m_TransformFilter);
+      m_MissingFilter = new ReplaceMissingValues();
+      m_MissingFilter.setInputFormat(data);
+      data = Filter.useFilter(data, m_MissingFilter);
+      data.deleteWithMissingClass();
+    } else {
+      m_TransformFilter = null;
+      m_MissingFilter = null;
+    }
+
+    m_ClassIndex = data.classIndex();
+    m_TransformedData = data;
+
+    // Turn all attributes on for a start
+    m_SelectedAttributes = new boolean[data.numAttributes()];
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != m_ClassIndex) {
+	m_SelectedAttributes[i] = true;
+      }
+    }
+    m_Coefficients = null;
+
+    // Compute means and standard deviations
+    m_Means = new double[data.numAttributes()];
+    m_StdDevs = new double[data.numAttributes()];
+    for (int j = 0; j < data.numAttributes(); j++) {
+      if (j != data.classIndex()) {
+	m_Means[j] = data.meanOrMode(j);
+	m_StdDevs[j] = Math.sqrt(data.variance(j));
+	if (m_StdDevs[j] == 0) {
+	  m_SelectedAttributes[j] = false;
+	} 
+      }
+    }
+
+    m_ClassStdDev = Math.sqrt(data.variance(m_TransformedData.classIndex()));
+    m_ClassMean = data.meanOrMode(m_TransformedData.classIndex());
+
+    // Perform the regression
+    findBestModel();
+
+    // Save memory
+    m_TransformedData = new Instances(data, 0);
+  }
+
+  /**
+   * Classifies the given instance using the linear regression function.
+   *
+   * @param instance the test instance
+   * @return the classification
+   * @throws Exception if classification can't be done successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    // Transform the input instance
+    Instance transformedInstance = instance;
+    if (!m_checksTurnedOff) {
+      m_TransformFilter.input(transformedInstance);
+      m_TransformFilter.batchFinished();
+      transformedInstance = m_TransformFilter.output();
+      m_MissingFilter.input(transformedInstance);
+      m_MissingFilter.batchFinished();
+      transformedInstance = m_MissingFilter.output();
+    }
+
+    // Calculate the dependent variable from the regression model
+    return regressionPrediction(transformedInstance,
+				m_SelectedAttributes,
+				m_Coefficients);
+  }
+
+  /**
+   * Outputs the linear regression model as a string.
+   * 
+   * @return the model as string
+   */
+  public String toString() {
+
+    if (m_TransformedData == null) {
+      return "Linear Regression: No model built yet.";
+    }
+    try {
+      StringBuffer text = new StringBuffer();
+      int column = 0;
+      boolean first = true;
+      
+      text.append("\nLinear Regression Model\n\n");
+      
+      text.append(m_TransformedData.classAttribute().name()+" =\n\n");
+      for (int i = 0; i < m_TransformedData.numAttributes(); i++) {
+	if ((i != m_ClassIndex) 
+	    && (m_SelectedAttributes[i])) {
+	  if (!first) 
+	    text.append(" +\n");
+	  else
+	    first = false;
+	  text.append(Utils.doubleToString(m_Coefficients[column], 12, 4)
+		      + " * ");
+	  text.append(m_TransformedData.attribute(i).name());
+	  column++;
+	}
+      }
+      text.append(" +\n" + 
+		  Utils.doubleToString(m_Coefficients[column], 12, 4));
+      return text.toString();
+    } catch (Exception e) {
+      return "Can't print Linear Regression!";
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(4);
+    newVector.addElement(new Option("\tProduce debugging output.\n"
+				    + "\t(default no debugging output)",
+				    "D", 0, "-D"));
+    newVector.addElement(new Option("\tSet the attribute selection method"
+				    + " to use. 1 = None, 2 = Greedy.\n"
+				    + "\t(default 0 = M5' method)",
+				    "S", 1, "-S <number of selection method>"));
+    newVector.addElement(new Option("\tDo not try to eliminate colinear"
+				    + " attributes.\n",
+				    "C", 0, "-C"));
+    newVector.addElement(new Option("\tSet ridge parameter (default 1.0e-8).\n",
+				    "R", 1, "-R <double>"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Produce debugging output.
+   *  (default no debugging output)</pre>
+   * 
+   * <pre> -S &lt;number of selection method&gt;
+   *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
+   *  (default 0 = M5' method)</pre>
+   * 
+   * <pre> -C
+   *  Do not try to eliminate colinear attributes.
+   * </pre>
+   * 
+   * <pre> -R &lt;double&gt;
+   *  Set ridge parameter (default 1.0e-8).
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String selectionString = Utils.getOption('S', options);
+    if (selectionString.length() != 0) {
+      setAttributeSelectionMethod(new SelectedTag(Integer
+						  .parseInt(selectionString),
+						  TAGS_SELECTION));
+    } else {
+      setAttributeSelectionMethod(new SelectedTag(SELECTION_M5,
+						  TAGS_SELECTION));
+    }
+    String ridgeString = Utils.getOption('R', options);
+    if (ridgeString.length() != 0) {
+      setRidge(new Double(ridgeString).doubleValue());
+    } else {
+      setRidge(1.0e-8);
+    }
+    setDebug(Utils.getFlag('D', options));
+    setEliminateColinearAttributes(!Utils.getFlag('C', options));
+  }
+
+  /**
+   * Returns the coefficients for this linear model.
+   * 
+   * @return the coefficients for this linear model
+   */
+  public double[] coefficients() {
+
+    double[] coefficients = new double[m_SelectedAttributes.length + 1];
+    int counter = 0;
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      if ((m_SelectedAttributes[i]) && ((i != m_ClassIndex))) {
+	coefficients[i] = m_Coefficients[counter++];
+      }
+    }
+    coefficients[m_SelectedAttributes.length] = m_Coefficients[counter];
+    return coefficients;
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [6];
+    int current = 0;
+
+    options[current++] = "-S";
+    options[current++] = "" + getAttributeSelectionMethod()
+      .getSelectedTag().getID();
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+    if (!getEliminateColinearAttributes()) {
+      options[current++] = "-C";
+    }
+    options[current++] = "-R";
+    options[current++] = "" + getRidge();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ridgeTipText() {
+    return "The value of the Ridge parameter.";
+  }
+
+  /**
+   * Get the value of Ridge.
+   *
+   * @return Value of Ridge.
+   */
+  public double getRidge() {
+    
+    return m_Ridge;
+  }
+  
+  /**
+   * Set the value of Ridge.
+   *
+   * @param newRidge Value to assign to Ridge.
+   */
+  public void setRidge(double newRidge) {
+    
+    m_Ridge = newRidge;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String eliminateColinearAttributesTipText() {
+    return "Eliminate colinear attributes.";
+  }
+
+  /**
+   * Get the value of EliminateColinearAttributes.
+   *
+   * @return Value of EliminateColinearAttributes.
+   */
+  public boolean getEliminateColinearAttributes() {
+    
+    return m_EliminateColinearAttributes;
+  }
+  
+  /**
+   * Set the value of EliminateColinearAttributes.
+   *
+   * @param newEliminateColinearAttributes Value to assign to EliminateColinearAttributes.
+   */
+  public void setEliminateColinearAttributes(boolean newEliminateColinearAttributes) {
+    
+    m_EliminateColinearAttributes = newEliminateColinearAttributes;
+  }
+  
+  /**
+   * Get the number of coefficients used in the model
+   *
+   * @return the number of coefficients
+   */
+  public int numParameters()
+  {
+    return m_Coefficients.length-1;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeSelectionMethodTipText() {
+    return "Set the method used to select attributes for use in the linear "
+      +"regression. Available methods are: no attribute selection, attribute "
+      +"selection using M5's method (step through the attributes removing the one "
+      +"with the smallest standardised coefficient until no improvement is observed "
+      +"in the estimate of the error given by the Akaike "
+      +"information criterion), and a greedy selection using the Akaike information "
+      +"metric.";
+  }
+
+  /**
+   * Sets the method used to select attributes for use in the
+   * linear regression. 
+   *
+   * @param method the attribute selection method to use.
+   */
+  public void setAttributeSelectionMethod(SelectedTag method) {
+    
+    if (method.getTags() == TAGS_SELECTION) {
+      m_AttributeSelection = method.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the method used to select attributes for use in the
+   * linear regression. 
+   *
+   * @return the method to use.
+   */
+  public SelectedTag getAttributeSelectionMethod() {
+    
+    return new SelectedTag(m_AttributeSelection, TAGS_SELECTION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Outputs debug information to the console.";
+  }
+
+  /**
+   * Controls whether debugging output will be printed
+   *
+   * @param debug true if debugging output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    b_Debug = debug;
+  }
+
+  /**
+   * Controls whether debugging output will be printed
+   *
+   * @return true if debugging output is printed
+   */
+  public boolean getDebug() {
+
+    return b_Debug;
+  }
+
+  /**
+   * Removes the attribute with the highest standardised coefficient
+   * greater than 1.5 from the selected attributes.
+   *
+   * @param selectedAttributes an array of flags indicating which 
+   * attributes are included in the regression model
+   * @param coefficients an array of coefficients for the regression
+   * model
+   * @return true if an attribute was removed
+   */
+  private boolean deselectColinearAttributes(boolean [] selectedAttributes,
+					     double [] coefficients) {
+
+    double maxSC = 1.5;
+    int maxAttr = -1, coeff = 0;
+    for (int i = 0; i < selectedAttributes.length; i++) {
+      if (selectedAttributes[i]) {
+	double SC = Math.abs(coefficients[coeff] * m_StdDevs[i] 
+			     / m_ClassStdDev);
+	if (SC > maxSC) {
+	  maxSC = SC;
+	  maxAttr = i;
+	}
+	coeff++;
+      }
+    }
+    if (maxAttr >= 0) {
+      selectedAttributes[maxAttr] = false;
+      if (b_Debug) {
+	System.out.println("Deselected colinear attribute:" + (maxAttr + 1)
+			   + " with standardised coefficient: " + maxSC);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Performs a greedy search for the best regression model using
+   * Akaike's criterion.
+   *
+   * @throws Exception if regression can't be done
+   */
+  private void findBestModel() throws Exception {
+
+    // For the weighted case we still use numInstances in
+    // the calculation of the Akaike criterion. 
+    int numInstances = m_TransformedData.numInstances();
+
+    if (b_Debug) {
+      System.out.println((new Instances(m_TransformedData, 0)).toString());
+    }
+
+    // Perform a regression for the full model, and remove colinear attributes
+    do {
+      m_Coefficients = doRegression(m_SelectedAttributes);
+    } while (m_EliminateColinearAttributes && 
+	     deselectColinearAttributes(m_SelectedAttributes, m_Coefficients));
+
+    // Figure out current number of attributes + 1. (We treat this model
+    // as the full model for the Akaike-based methods.)
+    int numAttributes = 1;
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      if (m_SelectedAttributes[i]) {
+	numAttributes++;
+      }
+    }
+
+    double fullMSE = calculateSE(m_SelectedAttributes, m_Coefficients);
+    double akaike = (numInstances - numAttributes) + 2 * numAttributes;
+    if (b_Debug) {
+      System.out.println("Initial Akaike value: " + akaike);
+    }
+
+    boolean improved;
+    int currentNumAttributes = numAttributes;
+    switch (m_AttributeSelection) {
+
+    case SELECTION_GREEDY:
+
+      // Greedy attribute removal
+      do {
+	boolean [] currentSelected = (boolean []) m_SelectedAttributes.clone();
+	improved = false;
+	currentNumAttributes--;
+
+	for (int i = 0; i < m_SelectedAttributes.length; i++) {
+	  if (currentSelected[i]) {
+
+	    // Calculate the akaike rating without this attribute
+	    currentSelected[i] = false;
+	    double [] currentCoeffs = doRegression(currentSelected);
+	    double currentMSE = calculateSE(currentSelected, currentCoeffs);
+	    double currentAkaike = currentMSE / fullMSE 
+	      * (numInstances - numAttributes)
+	      + 2 * currentNumAttributes;
+	    if (b_Debug) {
+	      System.out.println("(akaike: " + currentAkaike);
+	    }
+
+	    // If it is better than the current best
+	    if (currentAkaike < akaike) {
+	      if (b_Debug) {
+		System.err.println("Removing attribute " + (i + 1)
+				   + " improved Akaike: " + currentAkaike);
+	      }
+	      improved = true;
+	      akaike = currentAkaike;
+	      System.arraycopy(currentSelected, 0,
+			       m_SelectedAttributes, 0,
+			       m_SelectedAttributes.length);
+	      m_Coefficients = currentCoeffs;
+	    }
+	    currentSelected[i] = true;
+	  }
+	}
+      } while (improved);
+      break;
+
+    case SELECTION_M5:
+
+      // Step through the attributes removing the one with the smallest 
+      // standardised coefficient until no improvement in Akaike
+      do {
+	improved = false;
+	currentNumAttributes--;
+
+	// Find attribute with smallest SC
+	double minSC = 0;
+	int minAttr = -1, coeff = 0;
+	for (int i = 0; i < m_SelectedAttributes.length; i++) {
+	  if (m_SelectedAttributes[i]) {
+	    double SC = Math.abs(m_Coefficients[coeff] * m_StdDevs[i] 
+				 / m_ClassStdDev);
+	    if ((coeff == 0) || (SC < minSC)) {
+	      minSC = SC;
+	      minAttr = i;
+	    }
+	    coeff++;
+	  }
+	}
+
+	// See whether removing it improves the Akaike score
+	if (minAttr >= 0) {
+	  m_SelectedAttributes[minAttr] = false;
+	  double [] currentCoeffs = doRegression(m_SelectedAttributes);
+	  double currentMSE = calculateSE(m_SelectedAttributes, currentCoeffs);
+	  double currentAkaike = currentMSE / fullMSE 
+	    * (numInstances - numAttributes)
+	    + 2 * currentNumAttributes;
+	  if (b_Debug) {
+	    System.out.println("(akaike: " + currentAkaike);
+	  }
+
+	  // If it is better than the current best
+	  if (currentAkaike < akaike) {
+	    if (b_Debug) {
+	      System.err.println("Removing attribute " + (minAttr + 1)
+				 + " improved Akaike: " + currentAkaike);
+	    }
+	    improved = true;
+	    akaike = currentAkaike;
+	    m_Coefficients = currentCoeffs;
+	  } else {
+	    m_SelectedAttributes[minAttr] = true;
+	  }
+	}
+      } while (improved);
+      break;
+
+    case SELECTION_NONE:
+      break;
+    }
+  }
+
+  /**
+   * Calculate the squared error of a regression model on the 
+   * training data
+   *
+   * @param selectedAttributes an array of flags indicating which 
+   * attributes are included in the regression model
+   * @param coefficients an array of coefficients for the regression
+   * model
+   * @return the mean squared error on the training data
+   * @throws Exception if there is a missing class value in the training
+   * data
+   */
+  private double calculateSE(boolean [] selectedAttributes, 
+			      double [] coefficients) throws Exception {
+
+    double mse = 0;
+    for (int i = 0; i < m_TransformedData.numInstances(); i++) {
+      double prediction = regressionPrediction(m_TransformedData.instance(i),
+					       selectedAttributes,
+					       coefficients);
+      double error = prediction - m_TransformedData.instance(i).classValue();
+      mse += error * error;
+    }
+    return mse;
+  }
+
+  /**
+   * Calculate the dependent value for a given instance for a
+   * given regression model.
+   *
+   * @param transformedInstance the input instance
+   * @param selectedAttributes an array of flags indicating which 
+   * attributes are included in the regression model
+   * @param coefficients an array of coefficients for the regression
+   * model
+   * @return the regression value for the instance.
+   * @throws Exception if the class attribute of the input instance
+   * is not assigned
+   */
+  private double regressionPrediction(Instance transformedInstance,
+				      boolean [] selectedAttributes,
+				      double [] coefficients) 
+  throws Exception {
+    
+    double result = 0;
+    int column = 0;
+    for (int j = 0; j < transformedInstance.numAttributes(); j++) {
+      if ((m_ClassIndex != j) 
+	  && (selectedAttributes[j])) {
+	result += coefficients[column] * transformedInstance.value(j);
+	column++;
+      }
+    }
+    result += coefficients[column];
+    
+    return result;
+  }
+
+  /**
+   * Calculate a linear regression using the selected attributes
+   *
+   * @param selectedAttributes an array of booleans where each element
+   * is true if the corresponding attribute should be included in the
+   * regression.
+   * @return an array of coefficients for the linear regression model.
+   * @throws Exception if an error occurred during the regression.
+   */
+  private double [] doRegression(boolean [] selectedAttributes) 
+  throws Exception {
+
+    if (b_Debug) {
+      System.out.print("doRegression(");
+      for (int i = 0; i < selectedAttributes.length; i++) {
+	System.out.print(" " + selectedAttributes[i]);
+      }
+      System.out.println(" )");
+    }
+    int numAttributes = 0;
+    for (int i = 0; i < selectedAttributes.length; i++) {
+      if (selectedAttributes[i]) {
+	numAttributes++;
+      }
+    }
+
+    // Check whether there are still attributes left
+    Matrix independent = null, dependent = null;
+    double[] weights = null;
+    if (numAttributes > 0) {
+      independent = new Matrix(m_TransformedData.numInstances(), 
+			       numAttributes);
+      dependent = new Matrix(m_TransformedData.numInstances(), 1);
+      for (int i = 0; i < m_TransformedData.numInstances(); i ++) {
+	Instance inst = m_TransformedData.instance(i);
+	int column = 0;
+	for (int j = 0; j < m_TransformedData.numAttributes(); j++) {
+	  if (j == m_ClassIndex) {
+	    dependent.setElement(i, 0, inst.classValue());
+	  } else {
+	    if (selectedAttributes[j]) {
+	      double value = inst.value(j) - m_Means[j];
+	      
+	      // We only need to do this if we want to
+	      // scale the input
+	      if (!m_checksTurnedOff) {
+		value /= m_StdDevs[j];
+	      }
+	      independent.setElement(i, column, value);
+	      column++;
+	    }
+	  }
+	}
+      }
+      
+      // Grab instance weights
+      weights = new double [m_TransformedData.numInstances()];
+      for (int i = 0; i < weights.length; i++) {
+	weights[i] = m_TransformedData.instance(i).weight();
+      }
+    }
+
+    // Compute coefficients (note that we have to treat the
+    // intercept separately so that it doesn't get affected
+    // by the ridge constant.)
+    double[] coefficients = new double[numAttributes + 1];
+    if (numAttributes > 0) {
+      double[] coeffsWithoutIntercept  =
+	independent.regression(dependent, weights, m_Ridge);
+      System.arraycopy(coeffsWithoutIntercept, 0, coefficients, 0,
+		       numAttributes);
+    }
+    coefficients[numAttributes] = m_ClassMean;
+	   
+    // Convert coefficients into original scale
+    int column = 0;
+    for(int i = 0; i < m_TransformedData.numAttributes(); i++) {
+      if ((i != m_TransformedData.classIndex()) &&
+	  (selectedAttributes[i])) {
+
+	// We only need to do this if we have scaled the
+	// input.
+	if (!m_checksTurnedOff) {
+	  coefficients[column] /= m_StdDevs[i];
+	}
+
+	// We have centred the input
+	coefficients[coefficients.length - 1] -= 
+	  coefficients[column] * m_Means[i];
+	column++;
+      }
+    }
+
+    return coefficients;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+ 
+  /**
+   * Generates a linear regression function predictor.
+   *
+   * @param argv the options
+   */
+  public static void main(String argv[]) {
+    runClassifier(new LinearRegression(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/Logistic.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/Logistic.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/Logistic.java	(revision 29)
@@ -0,0 +1,914 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Logistic.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.RemoveUseless;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a multinomial logistic regression model with a ridge estimator.<br/>
+ * <br/>
+ * There are some modifications, however, compared to the paper of leCessie and van Houwelingen(1992): <br/>
+ * <br/>
+ * If there are k classes for n instances with m attributes, the parameter matrix B to be calculated will be an m*(k-1) matrix.<br/>
+ * <br/>
+ * The probability for class j with the exception of the last class is<br/>
+ * <br/>
+ * Pj(Xi) = exp(XiBj)/((sum[j=1..(k-1)]exp(Xi*Bj))+1) <br/>
+ * <br/>
+ * The last class has probability<br/>
+ * <br/>
+ * 1-(sum[j=1..(k-1)]Pj(Xi)) <br/>
+ * 	= 1/((sum[j=1..(k-1)]exp(Xi*Bj))+1)<br/>
+ * <br/>
+ * The (negative) multinomial log-likelihood is thus: <br/>
+ * <br/>
+ * L = -sum[i=1..n]{<br/>
+ * 	sum[j=1..(k-1)](Yij * ln(Pj(Xi)))<br/>
+ * 	+(1 - (sum[j=1..(k-1)]Yij)) <br/>
+ * 	* ln(1 - sum[j=1..(k-1)]Pj(Xi))<br/>
+ * 	} + ridge * (B^2)<br/>
+ * <br/>
+ * In order to find the matrix B for which L is minimised, a Quasi-Newton Method is used to search for the optimized values of the m*(k-1) variables.  Note that before we use the optimization procedure, we 'squeeze' the matrix B into a m*(k-1) vector.  For details of the optimization procedure, please check weka.core.Optimization class.<br/>
+ * <br/>
+ * Although original Logistic Regression does not deal with instance weights, we modify the algorithm a little bit to handle the instance weights.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * le Cessie, S., van Houwelingen, J.C. (1992). Ridge Estimators in Logistic Regression. Applied Statistics. 41(1):191-201.<br/>
+ * <br/>
+ * Note: Missing values are replaced using a ReplaceMissingValuesFilter, and nominal attributes are transformed into numeric attributes using a NominalToBinaryFilter.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{leCessie1992,
+ *    author = {le Cessie, S. and van Houwelingen, J.C.},
+ *    journal = {Applied Statistics},
+ *    number = {1},
+ *    pages = {191-201},
+ *    title = {Ridge Estimators in Logistic Regression},
+ *    volume = {41},
+ *    year = {1992}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -R &lt;ridge&gt;
+ *  Set the ridge in the log-likelihood.</pre>
+ * 
+ * <pre> -M &lt;number&gt;
+ *  Set the maximum number of iterations (default -1, until convergence).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class Logistic extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3932117032546553727L;
+  
+  /** The coefficients (optimized parameters) of the model */
+  protected double [][] m_Par;
+    
+  /** The data saved as a matrix */
+  protected double [][] m_Data;
+    
+  /** The number of attributes in the model */
+  protected int m_NumPredictors;
+    
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+    
+  /** The number of the class labels */
+  protected int m_NumClasses;
+    
+  /** The ridge parameter. */
+  protected double m_Ridge = 1e-8;
+    
+  /** An attribute filter */
+  private RemoveUseless m_AttFilter;
+    
+  /** The filter used to make attributes numeric. */
+  private NominalToBinary m_NominalToBinary;
+    
+  /** The filter used to get rid of missing values. */
+  private ReplaceMissingValues m_ReplaceMissingValues;
+    
+  /** Debugging output */
+  protected boolean m_Debug;
+
+  /** Log-likelihood of the searched model */
+  protected double m_LL;
+    
+  /** The maximum number of iterations. */
+  private int m_MaxIts = -1;
+
+  private Instances m_structure;
+    
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for building and using a multinomial logistic "
+      +"regression model with a ridge estimator.\n\n"
+      +"There are some modifications, however, compared to the paper of "
+      +"leCessie and van Houwelingen(1992): \n\n" 
+      +"If there are k classes for n instances with m attributes, the "
+      +"parameter matrix B to be calculated will be an m*(k-1) matrix.\n\n"
+      +"The probability for class j with the exception of the last class is\n\n"
+      +"Pj(Xi) = exp(XiBj)/((sum[j=1..(k-1)]exp(Xi*Bj))+1) \n\n"
+      +"The last class has probability\n\n"
+      +"1-(sum[j=1..(k-1)]Pj(Xi)) \n\t= 1/((sum[j=1..(k-1)]exp(Xi*Bj))+1)\n\n"
+      +"The (negative) multinomial log-likelihood is thus: \n\n"
+      +"L = -sum[i=1..n]{\n\tsum[j=1..(k-1)](Yij * ln(Pj(Xi)))"
+      +"\n\t+(1 - (sum[j=1..(k-1)]Yij)) \n\t* ln(1 - sum[j=1..(k-1)]Pj(Xi))"
+      +"\n\t} + ridge * (B^2)\n\n"
+      +"In order to find the matrix B for which L is minimised, a "
+      +"Quasi-Newton Method is used to search for the optimized values of "
+      +"the m*(k-1) variables.  Note that before we use the optimization "
+      +"procedure, we 'squeeze' the matrix B into a m*(k-1) vector.  For "
+      +"details of the optimization procedure, please check "
+      +"weka.core.Optimization class.\n\n"
+      +"Although original Logistic Regression does not deal with instance "
+      +"weights, we modify the algorithm a little bit to handle the "
+      +"instance weights.\n\n"
+      +"For more information see:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      +"Note: Missing values are replaced using a ReplaceMissingValuesFilter, and "
+      +"nominal attributes are transformed into numeric attributes using a "
+      +"NominalToBinaryFilter.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "le Cessie, S. and van Houwelingen, J.C.");
+    result.setValue(Field.YEAR, "1992");
+    result.setValue(Field.TITLE, "Ridge Estimators in Logistic Regression");
+    result.setValue(Field.JOURNAL, "Applied Statistics");
+    result.setValue(Field.VOLUME, "41");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "191-201");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option("\tTurn on debugging output.",
+				    "D", 0, "-D"));
+    newVector.addElement(new Option("\tSet the ridge in the log-likelihood.",
+				    "R", 1, "-R <ridge>"));
+    newVector.addElement(new Option("\tSet the maximum number of iterations"+
+				    " (default -1, until convergence).",
+				    "M", 1, "-M <number>"));
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -R &lt;ridge&gt;
+   *  Set the ridge in the log-likelihood.</pre>
+   * 
+   * <pre> -M &lt;number&gt;
+   *  Set the maximum number of iterations (default -1, until convergence).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String ridgeString = Utils.getOption('R', options);
+    if (ridgeString.length() != 0) 
+      m_Ridge = Double.parseDouble(ridgeString);
+    else 
+      m_Ridge = 1.0e-8;
+	
+    String maxItsString = Utils.getOption('M', options);
+    if (maxItsString.length() != 0) 
+      m_MaxIts = Integer.parseInt(maxItsString);
+    else 
+      m_MaxIts = -1;
+  }
+    
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+	
+    String [] options = new String [5];
+    int current = 0;
+	
+    if (getDebug()) 
+      options[current++] = "-D";
+    options[current++] = "-R";
+    options[current++] = ""+m_Ridge;	
+    options[current++] = "-M";
+    options[current++] = ""+m_MaxIts;
+    while (current < options.length) 
+      options[current++] = "";
+    return options;
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Output debug information to the console.";
+  }
+
+  /**
+   * Sets whether debugging output will be printed.
+   *
+   * @param debug true if debugging output should be printed
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+  }
+    
+  /**
+   * Gets whether debugging output will be printed.
+   *
+   * @return true if debugging output will be printed
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }      
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ridgeTipText() {
+    return "Set the Ridge value in the log-likelihood.";
+  }
+
+  /**
+   * Sets the ridge in the log-likelihood.
+   *
+   * @param ridge the ridge
+   */
+  public void setRidge(double ridge) {
+    m_Ridge = ridge;
+  }
+    
+  /**
+   * Gets the ridge in the log-likelihood.
+   *
+   * @return the ridge
+   */
+  public double getRidge() {
+    return m_Ridge;
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxItsTipText() {
+    return "Maximum number of iterations to perform.";
+  }
+
+  /**
+   * Get the value of MaxIts.
+   *
+   * @return Value of MaxIts.
+   */
+  public int getMaxIts() {
+	
+    return m_MaxIts;
+  }
+    
+  /**
+   * Set the value of MaxIts.
+   *
+   * @param newMaxIts Value to assign to MaxIts.
+   */
+  public void setMaxIts(int newMaxIts) {
+	
+    m_MaxIts = newMaxIts;
+  }    
+    
+  private class OptEng extends Optimization{
+    /** Weights of instances in the data */
+    private double[] weights;
+
+    /** Class labels of instances */
+    private int[] cls;
+	
+    /** 
+     * Set the weights of instances
+     * @param w the weights to be set
+     */ 
+    public void setWeights(double[] w) {
+      weights = w;
+    }
+	
+    /** 
+     * Set the class labels of instances
+     * @param c the class labels to be set
+     */ 
+    public void setClassLabels(int[] c) {
+      cls = c;
+    }
+	
+    /** 
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function 
+     */
+    protected double objectiveFunction(double[] x){
+      double nll = 0; // -LogLikelihood
+      int dim = m_NumPredictors+1; // Number of variables per class
+	    
+      for(int i=0; i<cls.length; i++){ // ith instance
+
+	double[] exp = new double[m_NumClasses-1];
+	int index;
+	for(int offset=0; offset<m_NumClasses-1; offset++){ 
+	  index = offset * dim;
+	  for(int j=0; j<dim; j++)
+	    exp[offset] += m_Data[i][j]*x[index + j];
+	}
+	double max = exp[Utils.maxIndex(exp)];
+	double denom = Math.exp(-max);
+	double num;
+	if (cls[i] == m_NumClasses - 1) { // Class of this instance
+	  num = -max;
+	} else {
+	  num = exp[cls[i]] - max;
+	}
+	for(int offset=0; offset<m_NumClasses-1; offset++){
+	  denom += Math.exp(exp[offset] - max);
+	}
+		
+	nll -= weights[i]*(num - Math.log(denom)); // Weighted NLL
+      }
+	    
+      // Ridge: note that intercepts NOT included
+      for(int offset=0; offset<m_NumClasses-1; offset++){
+	for(int r=1; r<dim; r++)
+	  nll += m_Ridge*x[offset*dim+r]*x[offset*dim+r];
+      }
+	    
+      return nll;
+    }
+
+    /** 
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector 
+     */
+    protected double[] evaluateGradient(double[] x){
+      double[] grad = new double[x.length];
+      int dim = m_NumPredictors+1; // Number of variables per class
+	    
+      for(int i=0; i<cls.length; i++){ // ith instance
+	double[] num=new double[m_NumClasses-1]; // numerator of [-log(1+sum(exp))]'
+	int index;
+	for(int offset=0; offset<m_NumClasses-1; offset++){ // Which part of x
+	  double exp=0.0;
+	  index = offset * dim;
+	  for(int j=0; j<dim; j++)
+	    exp += m_Data[i][j]*x[index + j];
+	  num[offset] = exp;
+	}
+
+	double max = num[Utils.maxIndex(num)];
+	double denom = Math.exp(-max); // Denominator of [-log(1+sum(exp))]'
+	for(int offset=0; offset<m_NumClasses-1; offset++){
+	  num[offset] = Math.exp(num[offset] - max);
+	  denom += num[offset];
+	}
+	Utils.normalize(num, denom);
+		
+	// Update denominator of the gradient of -log(Posterior)
+	double firstTerm;
+	for(int offset=0; offset<m_NumClasses-1; offset++){ // Which part of x
+	  index = offset * dim;
+	  firstTerm = weights[i] * num[offset];
+	  for(int q=0; q<dim; q++){
+	    grad[index + q] += firstTerm * m_Data[i][q];
+	  }
+	}
+		
+	if(cls[i] != m_NumClasses-1){ // Not the last class
+	  for(int p=0; p<dim; p++){
+	    grad[cls[i]*dim+p] -= weights[i]*m_Data[i][p]; 
+	  }
+	}
+      }
+	    
+      // Ridge: note that intercepts NOT included
+      for(int offset=0; offset<m_NumClasses-1; offset++){
+	for(int r=1; r<dim; r++)
+	  grad[offset*dim+r] += 2*m_Ridge*x[offset*dim+r];
+      }
+	    
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+    
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    // Replace missing values	
+    m_ReplaceMissingValues = new ReplaceMissingValues();
+    m_ReplaceMissingValues.setInputFormat(train);
+    train = Filter.useFilter(train, m_ReplaceMissingValues);
+
+    // Remove useless attributes
+    m_AttFilter = new RemoveUseless();
+    m_AttFilter.setInputFormat(train);
+    train = Filter.useFilter(train, m_AttFilter);
+	
+    // Transform attributes
+    m_NominalToBinary = new NominalToBinary();
+    m_NominalToBinary.setInputFormat(train);
+    train = Filter.useFilter(train, m_NominalToBinary);
+    
+    // Save the structure for printing the model
+    m_structure = new Instances(train, 0);
+	
+    // Extract data
+    m_ClassIndex = train.classIndex();
+    m_NumClasses = train.numClasses();
+
+    int nK = m_NumClasses - 1;                     // Only K-1 class labels needed 
+    int nR = m_NumPredictors = train.numAttributes() - 1;
+    int nC = train.numInstances();
+	
+    m_Data = new double[nC][nR + 1];               // Data values
+    int [] Y  = new int[nC];                       // Class labels
+    double [] xMean= new double[nR + 1];           // Attribute means
+    double [] xSD  = new double[nR + 1];           // Attribute stddev's
+    double [] sY = new double[nK + 1];             // Number of classes
+    double [] weights = new double[nC];            // Weights of instances
+    double totWeights = 0;                         // Total weights of the instances
+    m_Par = new double[nR + 1][nK];                // Optimized parameter values
+	
+    if (m_Debug) {
+      System.out.println("Extracting data...");
+    }
+	
+    for (int i = 0; i < nC; i++) {
+      // initialize X[][]
+      Instance current = train.instance(i);
+      Y[i] = (int)current.classValue();  // Class value starts from 0
+      weights[i] = current.weight();     // Dealing with weights
+      totWeights += weights[i];
+	    
+      m_Data[i][0] = 1;
+      int j = 1;
+      for (int k = 0; k <= nR; k++) {
+	if (k != m_ClassIndex) {
+	  double x = current.value(k);
+	  m_Data[i][j] = x;
+	  xMean[j] += weights[i]*x;
+	  xSD[j] += weights[i]*x*x;
+	  j++;
+	}
+      }
+	    
+      // Class count
+      sY[Y[i]]++;	
+    }
+	
+    if((totWeights <= 1) && (nC > 1))
+      throw new Exception("Sum of weights of instances less than 1, please reweight!");
+
+    xMean[0] = 0; xSD[0] = 1;
+    for (int j = 1; j <= nR; j++) {
+      xMean[j] = xMean[j] / totWeights;
+      if(totWeights > 1)
+	xSD[j] = Math.sqrt(Math.abs(xSD[j] - totWeights*xMean[j]*xMean[j])/(totWeights-1));
+      else
+	xSD[j] = 0;
+    }
+
+    if (m_Debug) {	    
+      // Output stats about input data
+      System.out.println("Descriptives...");
+      for (int m = 0; m <= nK; m++)
+	System.out.println(sY[m] + " cases have class " + m);
+      System.out.println("\n Variable     Avg       SD    ");
+      for (int j = 1; j <= nR; j++) 
+	System.out.println(Utils.doubleToString(j,8,4) 
+			   + Utils.doubleToString(xMean[j], 10, 4) 
+			   + Utils.doubleToString(xSD[j], 10, 4)
+			   );
+    }
+	
+    // Normalise input data 
+    for (int i = 0; i < nC; i++) {
+      for (int j = 0; j <= nR; j++) {
+	if (xSD[j] != 0) {
+	  m_Data[i][j] = (m_Data[i][j] - xMean[j]) / xSD[j];
+	}
+      }
+    }
+	
+    if (m_Debug) {
+      System.out.println("\nIteration History..." );
+    }
+	
+    double x[] = new double[(nR+1)*nK];
+    double[][] b = new double[2][x.length]; // Boundary constraints, N/A here
+
+    // Initialize
+    for(int p=0; p<nK; p++){
+      int offset=p*(nR+1);	 
+      x[offset] =  Math.log(sY[p]+1.0) - Math.log(sY[nK]+1.0); // Null model
+      b[0][offset] = Double.NaN;
+      b[1][offset] = Double.NaN;   
+      for (int q=1; q <= nR; q++){
+	x[offset+q] = 0.0;		
+	b[0][offset+q] = Double.NaN;
+	b[1][offset+q] = Double.NaN;
+      }	
+    }
+	
+    OptEng opt = new OptEng();	
+    opt.setDebug(m_Debug);
+    opt.setWeights(weights);
+    opt.setClassLabels(Y);
+
+    if(m_MaxIts == -1){  // Search until convergence
+      x = opt.findArgmin(x, b);
+      while(x==null){
+	x = opt.getVarbValues();
+	if (m_Debug)
+	  System.out.println("200 iterations finished, not enough!");
+	x = opt.findArgmin(x, b);
+      }
+      if (m_Debug)
+	System.out.println(" -------------<Converged>--------------");
+    }
+    else{
+      opt.setMaxIteration(m_MaxIts);
+      x = opt.findArgmin(x, b);
+      if(x==null) // Not enough, but use the current value
+	x = opt.getVarbValues();
+    }
+	
+    m_LL = -opt.getMinFunction(); // Log-likelihood
+
+    // Don't need data matrix anymore
+    m_Data = null;
+	    
+    // Convert coefficients back to non-normalized attribute units
+    for(int i=0; i < nK; i++){
+      m_Par[0][i] = x[i*(nR+1)];
+      for(int j = 1; j <= nR; j++) {
+	m_Par[j][i] = x[i*(nR+1)+j];
+	if (xSD[j] != 0) {
+	  m_Par[j][i] /= xSD[j];
+	  m_Par[0][i] -= m_Par[j][i] * xMean[j];
+	}
+      }
+    }
+  }		
+    
+  /**
+   * Computes the distribution for a given instance
+   *
+   * @param instance the instance for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+	
+    m_ReplaceMissingValues.input(instance);
+    instance = m_ReplaceMissingValues.output();
+    m_AttFilter.input(instance);
+    instance = m_AttFilter.output();
+    m_NominalToBinary.input(instance);
+    instance = m_NominalToBinary.output();
+	
+    // Extract the predictor columns into an array
+    double [] instDat = new double [m_NumPredictors + 1];
+    int j = 1;
+    instDat[0] = 1;
+    for (int k = 0; k <= m_NumPredictors; k++) {
+      if (k != m_ClassIndex) {
+	instDat[j++] = instance.value(k);
+      }
+    }
+	
+    double [] distribution = evaluateProbability(instDat);
+    return distribution;
+  }
+
+  /**
+   * Compute the posterior distribution using optimized parameter values
+   * and the testing instance.
+   * @param data the testing instance
+   * @return the posterior probability distribution
+   */ 
+  private double[] evaluateProbability(double[] data){
+    double[] prob = new double[m_NumClasses],
+      v = new double[m_NumClasses];
+
+    // Log-posterior before normalizing
+    for(int j = 0; j < m_NumClasses-1; j++){
+      for(int k = 0; k <= m_NumPredictors; k++){
+	v[j] += m_Par[k][j] * data[k];
+      }
+    }
+    v[m_NumClasses-1] = 0;
+	
+    // Do so to avoid scaling problems
+    for(int m=0; m < m_NumClasses; m++){
+      double sum = 0;
+      for(int n=0; n < m_NumClasses-1; n++)
+	sum += Math.exp(v[n] - v[m]);
+      prob[m] = 1 / (sum + Math.exp(-v[m]));
+    }
+	
+    return prob;
+  } 
+
+  /**
+   * Returns the coefficients for this logistic model.
+   * The first dimension indexes the attributes, and
+   * the second the classes.
+   * 
+   * @return the coefficients for this logistic model
+   */
+  public double [][] coefficients() {
+    return m_Par;
+  }
+    
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+
+    String result = "";
+    temp.append("Logistic Regression with ridge parameter of " + m_Ridge);
+    if (m_Par == null) {
+      return result + ": No model built yet.";
+    }
+
+    // find longest attribute name
+    int attLength = 0;
+    for (int i = 0; i < m_structure.numAttributes(); i++) {
+      if (i != m_structure.classIndex() && 
+          m_structure.attribute(i).name().length() > attLength) {
+        attLength = m_structure.attribute(i).name().length();
+      }
+    }
+
+    if ("Intercept".length() > attLength) {
+      attLength = "Intercept".length();
+    }
+
+    if ("Variable".length() > attLength) {
+      attLength = "Variable".length();
+    }
+    attLength += 2;
+
+    int colWidth = 0;
+    // check length of class names
+    for (int i = 0; i < m_structure.classAttribute().numValues() - 1; i++) {
+      if (m_structure.classAttribute().value(i).length() > colWidth) {
+        colWidth = m_structure.classAttribute().value(i).length();
+      }
+    }
+
+    // check against coefficients and odds ratios
+    for (int j = 1; j <= m_NumPredictors; j++) {
+      for (int k = 0; k < m_NumClasses - 1; k++) {
+        if (Utils.doubleToString(m_Par[j][k], 12, 4).trim().length() > colWidth) {
+          colWidth = Utils.doubleToString(m_Par[j][k], 12, 4).trim().length();
+        }
+        double ORc = Math.exp(m_Par[j][k]);
+	String t = " " + ((ORc > 1e10) ?  "" + ORc : Utils.doubleToString(ORc, 12, 4));
+        if (t.trim().length() > colWidth) {
+          colWidth = t.trim().length();
+        }
+      }
+    }
+
+    if ("Class".length() > colWidth) {
+      colWidth = "Class".length();
+    }
+    colWidth += 2;
+    
+    
+    temp.append("\nCoefficients...\n");
+    temp.append(Utils.padLeft(" ", attLength) + Utils.padLeft("Class", colWidth) + "\n");
+    temp.append(Utils.padRight("Variable", attLength));
+
+    for (int i = 0; i < m_NumClasses - 1; i++) {
+      String className = m_structure.classAttribute().value(i);
+      temp.append(Utils.padLeft(className, colWidth));
+    }
+    temp.append("\n");
+    int separatorL = attLength + ((m_NumClasses - 1) * colWidth);
+    for (int i = 0; i < separatorL; i++) {
+      temp.append("=");
+    }
+    temp.append("\n");
+                
+    int j = 1;
+    for (int i = 0; i < m_structure.numAttributes(); i++) {
+      if (i != m_structure.classIndex()) {
+        temp.append(Utils.padRight(m_structure.attribute(i).name(), attLength));
+        for (int k = 0; k < m_NumClasses-1; k++) {
+          temp.append(Utils.padLeft(Utils.doubleToString(m_Par[j][k], 12, 4).trim(), colWidth));
+        }
+        temp.append("\n");
+        j++;
+      }
+    }
+	
+    temp.append(Utils.padRight("Intercept", attLength));
+    for (int k = 0; k < m_NumClasses-1; k++) {
+      temp.append(Utils.padLeft(Utils.doubleToString(m_Par[0][k], 10, 4).trim(), colWidth)); 
+    }
+    temp.append("\n");
+	
+    temp.append("\n\nOdds Ratios...\n");
+    temp.append(Utils.padLeft(" ", attLength) + Utils.padLeft("Class", colWidth) + "\n");
+    temp.append(Utils.padRight("Variable", attLength));
+
+    for (int i = 0; i < m_NumClasses - 1; i++) {
+      String className = m_structure.classAttribute().value(i);
+      temp.append(Utils.padLeft(className, colWidth));
+    }
+    temp.append("\n");
+    for (int i = 0; i < separatorL; i++) {
+      temp.append("=");
+    }
+    temp.append("\n");
+
+    j = 1;
+    for (int i = 0; i < m_structure.numAttributes(); i++) {
+      if (i != m_structure.classIndex()) {
+        temp.append(Utils.padRight(m_structure.attribute(i).name(), attLength));
+        for (int k = 0; k < m_NumClasses-1; k++) {
+          double ORc = Math.exp(m_Par[j][k]);
+          String ORs = " " + ((ORc > 1e10) ?  "" + ORc : Utils.doubleToString(ORc, 12, 4));
+          temp.append(Utils.padLeft(ORs.trim(), colWidth));
+        }
+        temp.append("\n");
+        j++;
+      }
+    }
+
+    return temp.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new Logistic(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptron.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptron.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptron.java	(revision 29)
@@ -0,0 +1,2689 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultilayerPerceptron.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.neural.LinearUnit;
+import weka.classifiers.functions.neural.NeuralConnection;
+import weka.classifiers.functions.neural.NeuralNode;
+import weka.classifiers.functions.neural.SigmoidUnit;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+/** 
+ <!-- globalinfo-start -->
+ * A Classifier that uses backpropagation to classify instances.<br/>
+ * This network can be built by hand, created by an algorithm or both. The network can also be monitored and modified during training time. The nodes in this network are all sigmoid (except for when the class is numeric in which case the the output nodes become unthresholded linear units).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;learning rate&gt;
+ *  Learning Rate for the backpropagation algorithm.
+ *  (Value should be between 0 - 1, Default = 0.3).</pre>
+ * 
+ * <pre> -M &lt;momentum&gt;
+ *  Momentum Rate for the backpropagation algorithm.
+ *  (Value should be between 0 - 1, Default = 0.2).</pre>
+ * 
+ * <pre> -N &lt;number of epochs&gt;
+ *  Number of epochs to train through.
+ *  (Default = 500).</pre>
+ * 
+ * <pre> -V &lt;percentage size of validation set&gt;
+ *  Percentage size of validation set to use to terminate
+ *  training (if this is non zero it can pre-empt num of epochs.
+ *  (Value should be between 0 - 100, Default = 0).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  The value used to seed the random number generator
+ *  (Value should be &gt;= 0 and and a long, Default = 0).</pre>
+ * 
+ * <pre> -E &lt;threshold for number of consequetive errors&gt;
+ *  The consequetive number of errors allowed for validation
+ *  testing before the netwrok terminates.
+ *  (Value should be &gt; 0, Default = 20).</pre>
+ * 
+ * <pre> -G
+ *  GUI will be opened.
+ *  (Use this to bring up a GUI).</pre>
+ * 
+ * <pre> -A
+ *  Autocreation of the network connections will NOT be done.
+ *  (This will be ignored if -G is NOT set)</pre>
+ * 
+ * <pre> -B
+ *  A NominalToBinary filter will NOT automatically be used.
+ *  (Set this to not use a NominalToBinary filter).</pre>
+ * 
+ * <pre> -H &lt;comma seperated numbers for nodes on each layer&gt;
+ *  The hidden layers to be created for the network.
+ *  (Value should be a list of comma separated Natural 
+ *  numbers or the letters 'a' = (attribs + classes) / 2, 
+ *  'i' = attribs, 'o' = classes, 't' = attribs .+ classes)
+ *  for wildcard values, Default = a).</pre>
+ * 
+ * <pre> -C
+ *  Normalizing a numeric class will NOT be done.
+ *  (Set this to not normalize the class if it's numeric).</pre>
+ * 
+ * <pre> -I
+ *  Normalizing the attributes will NOT be done.
+ *  (Set this to not normalize the attributes).</pre>
+ * 
+ * <pre> -R
+ *  Reseting the network will NOT be allowed.
+ *  (Set this to not allow the network to reset).</pre>
+ * 
+ * <pre> -D
+ *  Learning rate decay will occur.
+ *  (Set this to cause the learning rate to decay).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class MultilayerPerceptron 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, Randomizable {
+  
+  /** for serialization */
+  static final long serialVersionUID = 572250905027665169L;
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line options (see setOptions)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MultilayerPerceptron(), argv);
+  }
+  
+
+  /** 
+   * This inner class is used to connect the nodes in the network up to
+   * the data that they are classifying, Note that objects of this class are
+   * only suitable to go on the attribute side or class side of the network
+   * and not both.
+   */
+  protected class NeuralEnd 
+    extends NeuralConnection {
+    
+    /** for serialization */
+    static final long serialVersionUID = 7305185603191183338L;
+  
+    /** 
+     * the value that represents the instance value this node represents. 
+     * For an input it is the attribute number, for an output, if nominal
+     * it is the class value. 
+     */
+    private int m_link;
+    
+    /** True if node is an input, False if it's an output. */
+    private boolean m_input;
+
+    /**
+     * Constructor
+     */
+    public NeuralEnd(String id) {
+      super(id);
+
+      m_link = 0;
+      m_input = true;
+      
+    }
+  
+    /**
+     * Call this function to determine if the point at x,y is on the unit.
+     * @param g The graphics context for font size info.
+     * @param x The x coord.
+     * @param y The y coord.
+     * @param w The width of the display.
+     * @param h The height of the display.
+     * @return True if the point is on the unit, false otherwise.
+     */
+    public boolean onUnit(Graphics g, int x, int y, int w, int h) {
+      
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      if (x < l || x > l + fm.stringWidth(m_id) + 4 
+	  || y < t || y > t + fm.getHeight() + fm.getDescent() + 4) {
+	return false;
+      }
+      return true;
+      
+    }
+   
+
+    /**
+     * This will draw the node id to the graphics context.
+     * @param g The graphics context.
+     * @param w The width of the drawing area.
+     * @param h The height of the drawing area.
+     */
+    public void drawNode(Graphics g, int w, int h) {
+      
+      if ((m_type & PURE_INPUT) == PURE_INPUT) {
+	g.setColor(Color.green);
+      }
+      else {
+	g.setColor(Color.orange);
+      }
+      
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      g.fill3DRect(l, t, fm.stringWidth(m_id) + 4
+		   , fm.getHeight() + fm.getDescent() + 4
+		   , true);
+      g.setColor(Color.black);
+      
+      g.drawString(m_id, l + 2, t + fm.getHeight() + 2);
+
+    }
+
+
+    /**
+     * Call this function to draw the node highlighted.
+     * @param g The graphics context.
+     * @param w The width of the drawing area.
+     * @param h The height of the drawing area.
+     */
+    public void drawHighlight(Graphics g, int w, int h) {
+      
+      g.setColor(Color.black);
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      g.fillRect(l - 2, t - 2, fm.stringWidth(m_id) + 8
+		 , fm.getHeight() + fm.getDescent() + 8); 
+      drawNode(g, w, h);
+    }
+    
+    /**
+     * Call this to get the output value of this unit. 
+     * @param calculate True if the value should be calculated if it hasn't 
+     * been already.
+     * @return The output value, or NaN, if the value has not been calculated.
+     */
+    public double outputValue(boolean calculate) {
+     
+      if (Double.isNaN(m_unitValue) && calculate) {
+	if (m_input) {
+	  if (m_currentInstance.isMissing(m_link)) {
+	    m_unitValue = 0;
+	  }
+	  else {
+	    
+	    m_unitValue = m_currentInstance.value(m_link);
+	  }
+	}
+	else {
+	  //node is an output.
+	  m_unitValue = 0;
+	  for (int noa = 0; noa < m_numInputs; noa++) {
+	    m_unitValue += m_inputList[noa].outputValue(true);
+	   
+	  }
+	  if (m_numeric && m_normalizeClass) {
+	    //then scale the value;
+	    //this scales linearly from between -1 and 1
+	    m_unitValue = m_unitValue * 
+	      m_attributeRanges[m_instances.classIndex()] + 
+	      m_attributeBases[m_instances.classIndex()];
+	  }
+	}
+      }
+      return m_unitValue;
+      
+      
+    }
+    
+    /**
+     * Call this to get the error value of this unit, which in this case is
+     * the difference between the predicted class, and the actual class.
+     * @param calculate True if the value should be calculated if it hasn't 
+     * been already.
+     * @return The error value, or NaN, if the value has not been calculated.
+     */
+    public double errorValue(boolean calculate) {
+      
+      if (!Double.isNaN(m_unitValue) && Double.isNaN(m_unitError) 
+	  && calculate) {
+	
+	if (m_input) {
+	  m_unitError = 0;
+	  for (int noa = 0; noa < m_numOutputs; noa++) {
+	    m_unitError += m_outputList[noa].errorValue(true);
+	  }
+	}
+	else {
+	  if (m_currentInstance.classIsMissing()) {
+	    m_unitError = .1;  
+	  }
+	  else if (m_instances.classAttribute().isNominal()) {
+	    if (m_currentInstance.classValue() == m_link) {
+	      m_unitError = 1 - m_unitValue;
+	    }
+	    else {
+	      m_unitError = 0 - m_unitValue;
+	    }
+	  }
+	  else if (m_numeric) {
+	    
+	    if (m_normalizeClass) {
+	      if (m_attributeRanges[m_instances.classIndex()] == 0) {
+		m_unitError = 0;
+	      }
+	      else {
+		m_unitError = (m_currentInstance.classValue() - m_unitValue ) /
+		  m_attributeRanges[m_instances.classIndex()];
+		//m_numericRange;
+		
+	      }
+	    }
+	    else {
+	      m_unitError = m_currentInstance.classValue() - m_unitValue;
+	    }
+	  }
+	}
+      }
+      return m_unitError;
+    }
+    
+    
+    /**
+     * Call this to reset the value and error for this unit, ready for the next
+     * run. This will also call the reset function of all units that are 
+     * connected as inputs to this one.
+     * This is also the time that the update for the listeners will be 
+     * performed.
+     */
+    public void reset() {
+      
+      if (!Double.isNaN(m_unitValue) || !Double.isNaN(m_unitError)) {
+	m_unitValue = Double.NaN;
+	m_unitError = Double.NaN;
+	m_weightsUpdated = false;
+	for (int noa = 0; noa < m_numInputs; noa++) {
+	  m_inputList[noa].reset();
+	}
+      }
+    }
+    
+    /**
+     * Call this to have the connection save the current
+     * weights.
+     */
+    public void saveWeights() {
+      for (int i = 0; i < m_numInputs; i++) {
+        m_inputList[i].saveWeights();
+      }
+    }
+    
+    /**
+     * Call this to have the connection restore from the saved
+     * weights.
+     */
+    public void restoreWeights() {
+      for (int i = 0; i < m_numInputs; i++) {
+        m_inputList[i].restoreWeights();
+      }
+    }
+    
+    
+    /** 
+     * Call this function to set What this end unit represents.
+     * @param input True if this unit is used for entering an attribute,
+     * False if it's used for determining a class value.
+     * @param val The attribute number or class type that this unit represents.
+     * (for nominal attributes).
+     */
+    public void setLink(boolean input, int val) throws Exception {
+      m_input = input;
+      
+      if (input) {
+	m_type = PURE_INPUT;
+      }
+      else {
+	m_type = PURE_OUTPUT;
+      }
+      if (val < 0 || (input && val > m_instances.numAttributes()) 
+	  || (!input && m_instances.classAttribute().isNominal() 
+	      && val > m_instances.classAttribute().numValues())) {
+	m_link = 0;
+      }
+      else {
+	m_link = val;
+      }
+    }
+    
+    /**
+     * @return link for this node.
+     */
+    public int getLink() {
+      return m_link;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+
+ 
+  /** Inner class used to draw the nodes onto.(uses the node lists!!) 
+   * This will also handle the user input. */
+  private class NodePanel 
+    extends JPanel
+    implements RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -3067621833388149984L;
+
+    /**
+     * The constructor.
+     */
+    public NodePanel() {
+      
+
+      addMouseListener(new MouseAdapter() {
+	  
+	  public void mousePressed(MouseEvent e) {
+	    
+	    if (!m_stopped) {
+	      return;
+	    }
+	    if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK && 
+		!e.isAltDown()) {
+	      Graphics g = NodePanel.this.getGraphics();
+	      int x = e.getX();
+	      int y = e.getY();
+	      int w = NodePanel.this.getWidth();
+	      int h = NodePanel.this.getHeight();
+	      FastVector tmp = new FastVector(4);
+	      for (int noa = 0; noa < m_numAttributes; noa++) {
+		if (m_inputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_inputs[noa]);
+		  selection(tmp, 
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_numClasses; noa++) {
+		if (m_outputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_outputs[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+		if (m_neuralNodes[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_neuralNodes[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+
+	      }
+	      NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), 
+					       m_random, m_sigmoidUnit);
+	      m_nextId++;
+	      temp.setX((double)e.getX() / w);
+	      temp.setY((double)e.getY() / h);
+	      tmp.addElement(temp);
+	      addNode(temp);
+	      selection(tmp, (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			, true);
+	    }
+	    else {
+	      //then right click
+	      Graphics g = NodePanel.this.getGraphics();
+	      int x = e.getX();
+	      int y = e.getY();
+	      int w = NodePanel.this.getWidth();
+	      int h = NodePanel.this.getHeight();
+	      FastVector tmp = new FastVector(4);
+	      for (int noa = 0; noa < m_numAttributes; noa++) {
+		if (m_inputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_inputs[noa]);
+		  selection(tmp, 
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+		
+		
+	      }
+	      for (int noa = 0; noa < m_numClasses; noa++) {
+		if (m_outputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_outputs[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+		if (m_neuralNodes[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_neuralNodes[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+	      }
+	      selection(null, (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			, false);
+	    }
+	  }
+	});
+    }
+    
+    
+    /**
+     * This function gets called when the user has clicked something
+     * It will amend the current selection or connect the current selection
+     * to the new selection.
+     * Or if nothing was selected and the right button was used it will 
+     * delete the node.
+     * @param v The units that were selected.
+     * @param ctrl True if ctrl was held down.
+     * @param left True if it was the left mouse button.
+     */
+    private void selection(FastVector v, boolean ctrl, boolean left) {
+      
+      if (v == null) {
+	//then unselect all.
+	m_selected.removeAllElements();
+	repaint();
+	return;
+      }
+      
+
+      //then exclusive or the new selection with the current one.
+      if ((ctrl || m_selected.size() == 0) && left) {
+	boolean removed = false;
+	for (int noa = 0; noa < v.size(); noa++) {
+	  removed = false;
+	  for (int nob = 0; nob < m_selected.size(); nob++) {
+	    if (v.elementAt(noa) == m_selected.elementAt(nob)) {
+	      //then remove that element
+	      m_selected.removeElementAt(nob);
+	      removed = true;
+	      break;
+	    }
+	  }
+	  if (!removed) {
+	    m_selected.addElement(v.elementAt(noa));
+	  }
+	}
+	repaint();
+	return;
+      }
+
+      
+      if (left) {
+	//then connect the current selection to the new one.
+	for (int noa = 0; noa < m_selected.size(); noa++) {
+	  for (int nob = 0; nob < v.size(); nob++) {
+	    NeuralConnection
+	      .connect((NeuralConnection)m_selected.elementAt(noa)
+		       , (NeuralConnection)v.elementAt(nob));
+	  }
+	}
+      }
+      else if (m_selected.size() > 0) {
+	//then disconnect the current selection from the new one.
+	
+	for (int noa = 0; noa < m_selected.size(); noa++) {
+	  for (int nob = 0; nob < v.size(); nob++) {
+	    NeuralConnection
+	      .disconnect((NeuralConnection)m_selected.elementAt(noa)
+			  , (NeuralConnection)v.elementAt(nob));
+	    
+	    NeuralConnection
+	      .disconnect((NeuralConnection)v.elementAt(nob)
+			  , (NeuralConnection)m_selected.elementAt(noa));
+	    
+	  }
+	}
+      }
+      else {
+	//then remove the selected node. (it was right clicked while 
+	//no other units were selected
+	for (int noa = 0; noa < v.size(); noa++) {
+	  ((NeuralConnection)v.elementAt(noa)).removeAllInputs();
+	  ((NeuralConnection)v.elementAt(noa)).removeAllOutputs();
+	  removeNode((NeuralConnection)v.elementAt(noa));
+	}
+      }
+      repaint();
+    }
+
+    /**
+     * This will paint the nodes ontot the panel.
+     * @param g The graphics context.
+     */
+    public void paintComponent(Graphics g) {
+
+      super.paintComponent(g);
+      int x = getWidth();
+      int y = getHeight();
+      if (25 * m_numAttributes > 25 * m_numClasses && 
+	  25 * m_numAttributes > y) {
+	setSize(x, 25 * m_numAttributes);
+      }
+      else if (25 * m_numClasses > y) {
+	setSize(x, 25 * m_numClasses);
+      }
+      else {
+	setSize(x, y);
+      }
+
+      y = getHeight();
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	m_inputs[noa].drawInputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_numClasses; noa++) {
+	m_outputs[noa].drawInputLines(g, x, y);
+	m_outputs[noa].drawOutputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+	m_neuralNodes[noa].drawInputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	m_inputs[noa].drawNode(g, x, y);
+      }
+      for (int noa = 0; noa < m_numClasses; noa++) {
+	m_outputs[noa].drawNode(g, x, y);
+      }
+      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+	m_neuralNodes[noa].drawNode(g, x, y);
+      }
+
+      for (int noa = 0; noa < m_selected.size(); noa++) {
+	((NeuralConnection)m_selected.elementAt(noa)).drawHighlight(g, x, y);
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /** 
+   * This provides the basic controls for working with the neuralnetwork
+   * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+   * @version $Revision: 5928 $
+   */
+  class ControlPanel 
+    extends JPanel
+    implements RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 7393543302294142271L;
+    
+    /** The start stop button. */
+    public JButton m_startStop;
+    
+    /** The button to accept the network (even if it hasn't done all epochs. */
+    public JButton m_acceptButton;
+    
+    /** A label to state the number of epochs processed so far. */
+    public JPanel m_epochsLabel;
+    
+    /** A label to state the total number of epochs to be processed. */
+    public JLabel m_totalEpochsLabel;
+    
+    /** A text field to allow the changing of the total number of epochs. */
+    public JTextField m_changeEpochs;
+    
+    /** A label to state the learning rate. */
+    public JLabel m_learningLabel;
+    
+    /** A label to state the momentum. */
+    public JLabel m_momentumLabel;
+    
+    /** A text field to allow the changing of the learning rate. */
+    public JTextField m_changeLearning;
+    
+    /** A text field to allow the changing of the momentum. */
+    public JTextField m_changeMomentum;
+    
+    /** A label to state roughly the accuracy of the network.(because the
+	accuracy is calculated per epoch, but the network is changing 
+	throughout each epoch train).
+    */
+    public JPanel m_errorLabel;
+    
+    /** The constructor. */
+    public ControlPanel() { 
+      setBorder(BorderFactory.createTitledBorder("Controls"));
+      
+      m_totalEpochsLabel = new JLabel("Num Of Epochs  ");
+      m_epochsLabel = new JPanel(){ 
+	  /** for serialization */
+	  private static final long serialVersionUID = 2562773937093221399L;
+
+	  public void paintComponent(Graphics g) {
+	    super.paintComponent(g);
+	    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+	    g.drawString("Epoch  " + m_epoch, 0, 10);
+	  }
+	};
+      m_epochsLabel.setFont(m_totalEpochsLabel.getFont());
+      
+      m_changeEpochs = new JTextField();
+      m_changeEpochs.setText("" + m_numEpochs);
+      m_errorLabel = new JPanel(){
+	  /** for serialization */
+	  private static final long serialVersionUID = 4390239056336679189L;
+
+	  public void paintComponent(Graphics g) {
+	    super.paintComponent(g);
+	    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+	    if (m_valSize == 0) {
+	      g.drawString("Error per Epoch = " + 
+			   Utils.doubleToString(m_error, 7), 0, 10);
+	    }
+	    else {
+	      g.drawString("Validation Error per Epoch = "
+			   + Utils.doubleToString(m_error, 7), 0, 10);
+	    }
+	  }
+	};
+      m_errorLabel.setFont(m_epochsLabel.getFont());
+      
+      m_learningLabel = new JLabel("Learning Rate = ");
+      m_momentumLabel = new JLabel("Momentum = ");
+      m_changeLearning = new JTextField();
+      m_changeMomentum = new JTextField();
+      m_changeLearning.setText("" + m_learningRate);
+      m_changeMomentum.setText("" + m_momentum);
+      setLayout(new BorderLayout(15, 10));
+
+      m_stopIt = true;
+      m_accepted = false;
+      m_startStop = new JButton("Start");
+      m_startStop.setActionCommand("Start");
+      
+      m_acceptButton = new JButton("Accept");
+      m_acceptButton.setActionCommand("Accept");
+      
+      JPanel buttons = new JPanel();
+      buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
+      buttons.add(m_startStop);
+      buttons.add(m_acceptButton);
+      add(buttons, BorderLayout.WEST);
+      JPanel data = new JPanel();
+      data.setLayout(new BoxLayout(data, BoxLayout.Y_AXIS));
+      
+      Box ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_epochsLabel);
+      data.add(ab);
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      Component b = Box.createGlue();
+      ab.add(m_totalEpochsLabel);
+      ab.add(m_changeEpochs);
+      m_changeEpochs.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_errorLabel);
+      data.add(ab);
+      
+      add(data, BorderLayout.CENTER);
+      
+      data = new JPanel();
+      data.setLayout(new BoxLayout(data, BoxLayout.Y_AXIS));
+      ab = new Box(BoxLayout.X_AXIS);
+      b = Box.createGlue();
+      ab.add(m_learningLabel);
+      ab.add(m_changeLearning);
+      m_changeLearning.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      b = Box.createGlue();
+      ab.add(m_momentumLabel);
+      ab.add(m_changeMomentum);
+      m_changeMomentum.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);
+      
+      add(data, BorderLayout.EAST);
+      
+      m_startStop.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (e.getActionCommand().equals("Start")) {
+	      m_stopIt = false;
+	      m_startStop.setText("Stop");
+	      m_startStop.setActionCommand("Stop");
+	      int n = Integer.valueOf(m_changeEpochs.getText()).intValue();
+	      
+	      m_numEpochs = n;
+	      m_changeEpochs.setText("" + m_numEpochs);
+	      
+	      double m=Double.valueOf(m_changeLearning.getText()).
+		doubleValue();
+	      setLearningRate(m);
+	      m_changeLearning.setText("" + m_learningRate);
+	      
+	      m = Double.valueOf(m_changeMomentum.getText()).doubleValue();
+	      setMomentum(m);
+	      m_changeMomentum.setText("" + m_momentum);
+	      
+	      blocker(false);
+	    }
+	    else if (e.getActionCommand().equals("Stop")) {
+	      m_stopIt = true;
+	      m_startStop.setText("Start");
+	      m_startStop.setActionCommand("Start");
+	    }
+	  }
+	});
+      
+      m_acceptButton.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_accepted = true;
+	    blocker(false);
+	  }
+	});
+      
+      m_changeEpochs.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    int n = Integer.valueOf(m_changeEpochs.getText()).intValue();
+	    if (n > 0) {
+	      m_numEpochs = n;
+	      blocker(false);
+	    }
+	  }
+	});
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+    
+  /** The training instances. */
+  private Instances m_instances;
+  
+  /** The current instance running through the network. */
+  private Instance m_currentInstance;
+  
+  /** A flag to say that it's a numeric class. */
+  private boolean m_numeric;
+
+  /** The ranges for all the attributes. */
+  private double[] m_attributeRanges;
+
+  /** The base values for all the attributes. */
+  private double[] m_attributeBases;
+
+  /** The output units.(only feeds the errors, does no calcs) */
+  private NeuralEnd[] m_outputs;
+
+  /** The input units.(only feeds the inputs does no calcs) */
+  private NeuralEnd[] m_inputs;
+
+  /** All the nodes that actually comprise the logical neural net. */
+  private NeuralConnection[] m_neuralNodes;
+
+  /** The number of classes. */
+  private int m_numClasses = 0;
+  
+  /** The number of attributes. */
+  private int m_numAttributes = 0; //note the number doesn't include the class.
+  
+  /** The panel the nodes are displayed on. */
+  private NodePanel m_nodePanel;
+  
+  /** The control panel. */
+  private ControlPanel m_controlPanel;
+
+  /** The next id number available for default naming. */
+  private int m_nextId;
+   
+  /** A Vector list of the units currently selected. */
+  private FastVector m_selected;
+
+  /** A Vector list of the graphers. */
+  private FastVector m_graphers;
+
+  /** The number of epochs to train through. */
+  private int m_numEpochs;
+
+  /** a flag to state if the network should be running, or stopped. */
+  private boolean m_stopIt;
+
+  /** a flag to state that the network has in fact stopped. */
+  private boolean m_stopped;
+
+  /** a flag to state that the network should be accepted the way it is. */
+  private boolean m_accepted;
+  /** The window for the network. */
+  private JFrame m_win;
+
+  /** A flag to tell the build classifier to automatically build a neural net.
+   */
+  private boolean m_autoBuild;
+
+  /** A flag to state that the gui for the network should be brought up.
+      To allow interaction while training. */
+  private boolean m_gui;
+
+  /** An int to say how big the validation set should be. */
+  private int m_valSize;
+
+  /** The number to to use to quit on validation testing. */
+  private int m_driftThreshold;
+
+  /** The number used to seed the random number generator. */
+  private int m_randomSeed;
+
+  /** The actual random number generator. */
+  private Random m_random;
+
+  /** A flag to state that a nominal to binary filter should be used. */
+  private boolean m_useNomToBin;
+  
+  /** The actual filter. */
+  private NominalToBinary m_nominalToBinaryFilter;
+
+  /** The string that defines the hidden layers */
+  private String m_hiddenLayers;
+
+  /** This flag states that the user wants the input values normalized. */
+  private boolean m_normalizeAttributes;
+
+  /** This flag states that the user wants the learning rate to decay. */
+  private boolean m_decay;
+
+  /** This is the learning rate for the network. */
+  private double m_learningRate;
+
+  /** This is the momentum for the network. */
+  private double m_momentum;
+
+  /** Shows the number of the epoch that the network just finished. */
+  private int m_epoch;
+
+  /** Shows the error of the epoch that the network just finished. */
+  private double m_error;
+
+  /** This flag states that the user wants the network to restart if it
+   * is found to be generating infinity or NaN for the error value. This
+   * would restart the network with the current options except that the
+   * learning rate would be smaller than before, (perhaps half of its current
+   * value). This option will not be available if the gui is chosen (if the
+   * gui is open the user can fix the network themselves, it is an 
+   * architectural minefield for the network to be reset with the gui open). */
+  private boolean m_reset;
+
+  /** This flag states that the user wants the class to be normalized while
+   * processing in the network is done. (the final answer will be in the
+   * original range regardless). This option will only be used when the class
+   * is numeric. */
+  private boolean m_normalizeClass;
+
+  /**
+   * this is a sigmoid unit. 
+   */
+  private SigmoidUnit m_sigmoidUnit;
+  
+  /**
+   * This is a linear unit.
+   */
+  private LinearUnit m_linearUnit;
+  
+  /**
+   * The constructor.
+   */
+  public MultilayerPerceptron() {
+    m_instances = null;
+    m_currentInstance = null;
+    m_controlPanel = null;
+    m_nodePanel = null;
+    m_epoch = 0;
+    m_error = 0;
+    
+    
+    m_outputs = new NeuralEnd[0];
+    m_inputs = new NeuralEnd[0];
+    m_numAttributes = 0;
+    m_numClasses = 0;
+    m_neuralNodes = new NeuralConnection[0];
+    m_selected = new FastVector(4);
+    m_graphers = new FastVector(2);
+    m_nextId = 0;
+    m_stopIt = true;
+    m_stopped = true;
+    m_accepted = false;
+    m_numeric = false;
+    m_random = null;
+    m_nominalToBinaryFilter = new NominalToBinary();
+    m_sigmoidUnit = new SigmoidUnit();
+    m_linearUnit = new LinearUnit();
+    //setting all the options to their defaults. To completely change these
+    //defaults they will also need to be changed down the bottom in the 
+    //setoptions function (the text info in the accompanying functions should 
+    //also be changed to reflect the new defaults
+    m_normalizeClass = true;
+    m_normalizeAttributes = true;
+    m_autoBuild = true;
+    m_gui = false;
+    m_useNomToBin = true;
+    m_driftThreshold = 20;
+    m_numEpochs = 500;
+    m_valSize = 0;
+    m_randomSeed = 0;
+    m_hiddenLayers = "a";
+    m_learningRate = .3;
+    m_momentum = .2;
+    m_reset = true;
+    m_decay = false;
+  }
+
+  /**
+   * @param d True if the learning rate should decay.
+   */
+  public void setDecay(boolean d) {
+    m_decay = d;
+  }
+  
+  /**
+   * @return the flag for having the learning rate decay.
+   */
+  public boolean getDecay() {
+    return m_decay;
+  }
+
+  /**
+   * This sets the network up to be able to reset itself with the current 
+   * settings and the learning rate at half of what it is currently. This
+   * will only happen if the network creates NaN or infinite errors. Also this
+   * will continue to happen until the network is trained properly. The 
+   * learning rate will also get set back to it's original value at the end of
+   * this. This can only be set to true if the GUI is not brought up.
+   * @param r True if the network should restart with it's current options
+   * and set the learning rate to half what it currently is.
+   */
+  public void setReset(boolean r) {
+    if (m_gui) {
+      r = false;
+    }
+    m_reset = r;
+      
+  }
+
+  /**
+   * @return The flag for reseting the network.
+   */
+  public boolean getReset() {
+    return m_reset;
+  }
+  
+  /**
+   * @param c True if the class should be normalized (the class will only ever
+   * be normalized if it is numeric). (Normalization puts the range between
+   * -1 - 1).
+   */
+  public void setNormalizeNumericClass(boolean c) {
+    m_normalizeClass = c;
+  }
+  
+  /**
+   * @return The flag for normalizing a numeric class.
+   */
+  public boolean getNormalizeNumericClass() {
+    return m_normalizeClass;
+  }
+
+  /**
+   * @param a True if the attributes should be normalized (even nominal
+   * attributes will get normalized here) (range goes between -1 - 1).
+   */
+  public void setNormalizeAttributes(boolean a) {
+    m_normalizeAttributes = a;
+  }
+
+  /**
+   * @return The flag for normalizing attributes.
+   */
+  public boolean getNormalizeAttributes() {
+    return m_normalizeAttributes;
+  }
+
+  /**
+   * @param f True if a nominalToBinary filter should be used on the
+   * data.
+   */
+  public void setNominalToBinaryFilter(boolean f) {
+    m_useNomToBin = f;
+  }
+
+  /**
+   * @return The flag for nominal to binary filter use.
+   */
+  public boolean getNominalToBinaryFilter() {
+    return m_useNomToBin;
+  }
+
+  /**
+   * This seeds the random number generator, that is used when a random
+   * number is needed for the network.
+   * @param l The seed.
+   */
+  public void setSeed(int l) {
+    if (l >= 0) {
+      m_randomSeed = l;
+    }
+  }
+  
+  /**
+   * @return The seed for the random number generator.
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+
+  /**
+   * This sets the threshold to use for when validation testing is being done.
+   * It works by ending testing once the error on the validation set has 
+   * consecutively increased a certain number of times.
+   * @param t The threshold to use for this.
+   */
+  public void setValidationThreshold(int t) {
+    if (t > 0) {
+      m_driftThreshold = t;
+    }
+  }
+
+  /**
+   * @return The threshold used for validation testing.
+   */
+  public int getValidationThreshold() {
+    return m_driftThreshold;
+  }
+  
+  /**
+   * The learning rate can be set using this command.
+   * NOTE That this is a static variable so it affect all networks that are
+   * running.
+   * Must be greater than 0 and no more than 1.
+   * @param l The New learning rate. 
+   */
+  public void setLearningRate(double l) {
+    if (l > 0 && l <= 1) {
+      m_learningRate = l;
+    
+      if (m_controlPanel != null) {
+	m_controlPanel.m_changeLearning.setText("" + l);
+      }
+    }
+  }
+
+  /**
+   * @return The learning rate for the nodes.
+   */
+  public double getLearningRate() {
+    return m_learningRate;
+  }
+
+  /**
+   * The momentum can be set using this command.
+   * THE same conditions apply to this as to the learning rate.
+   * @param m The new Momentum.
+   */
+  public void setMomentum(double m) {
+    if (m >= 0 && m <= 1) {
+      m_momentum = m;
+  
+      if (m_controlPanel != null) {
+	m_controlPanel.m_changeMomentum.setText("" + m);
+      }
+    }
+  }
+  
+  /**
+   * @return The momentum for the nodes.
+   */
+  public double getMomentum() {
+    return m_momentum;
+  }
+
+  /**
+   * This will set whether the network is automatically built
+   * or if it is left up to the user. (there is nothing to stop a user
+   * from altering an autobuilt network however). 
+   * @param a True if the network should be auto built.
+   */
+  public void setAutoBuild(boolean a) {
+    if (!m_gui) {
+      a = true;
+    }
+    m_autoBuild = a;
+  }
+
+  /**
+   * @return The auto build state.
+   */
+  public boolean getAutoBuild() {
+    return m_autoBuild;
+  }
+
+
+  /**
+   * This will set what the hidden layers are made up of when auto build is
+   * enabled. Note to have no hidden units, just put a single 0, Any more
+   * 0's will indicate that the string is badly formed and make it unaccepted.
+   * Negative numbers, and floats will do the same. There are also some
+   * wildcards. These are 'a' = (number of attributes + number of classes) / 2,
+   * 'i' = number of attributes, 'o' = number of classes, and 't' = number of
+   * attributes + number of classes.
+   * @param h A string with a comma seperated list of numbers. Each number is 
+   * the number of nodes to be on a hidden layer.
+   */
+  public void setHiddenLayers(String h) {
+    String tmp = "";
+    StringTokenizer tok = new StringTokenizer(h, ",");
+    if (tok.countTokens() == 0) {
+      return;
+    }
+    double dval;
+    int val;
+    String c;
+    boolean first = true;
+    while (tok.hasMoreTokens()) {
+      c = tok.nextToken().trim();
+
+      if (c.equals("a") || c.equals("i") || c.equals("o") || 
+	       c.equals("t")) {
+	tmp += c;
+      }
+      else {
+	dval = Double.valueOf(c).doubleValue();
+	val = (int)dval;
+	
+	if ((val == dval && (val != 0 || (tok.countTokens() == 0 && first)) && 
+	     val >= 0)) {
+	  tmp += val;
+	}
+	else {
+	  return;
+	}
+      }
+      
+      first = false;
+      if (tok.hasMoreTokens()) {
+	tmp += ", ";
+      }
+    }
+    m_hiddenLayers = tmp;
+  }
+
+  /**
+   * @return A string representing the hidden layers, each number is the number
+   * of nodes on a hidden layer.
+   */
+  public String getHiddenLayers() {
+    return m_hiddenLayers;
+  }
+
+  /**
+   * This will set whether A GUI is brought up to allow interaction by the user
+   * with the neural network during training.
+   * @param a True if gui should be created.
+   */
+  public void setGUI(boolean a) {
+    m_gui = a;
+    if (!a) {
+      setAutoBuild(true);
+      
+    }
+    else {
+      setReset(false);
+    }
+  }
+
+  /**
+   * @return The true if should show gui.
+   */
+  public boolean getGUI() {
+    return m_gui;
+  }
+
+  /**
+   * This will set the size of the validation set.
+   * @param a The size of the validation set, as a percentage of the whole.
+   */
+  public void setValidationSetSize(int a) {
+    if (a < 0 || a > 99) {
+      return;
+    }
+    m_valSize = a;
+  }
+
+  /**
+   * @return The percentage size of the validation set.
+   */
+  public int getValidationSetSize() {
+    return m_valSize;
+  }
+
+  
+  
+  
+  /**
+   * Set the number of training epochs to perform.
+   * Must be greater than 0.
+   * @param n The number of epochs to train through.
+   */
+  public void setTrainingTime(int n) {
+    if (n > 0) {
+      m_numEpochs = n;
+    }
+  }
+
+  /**
+   * @return The number of epochs to train through.
+   */
+  public int getTrainingTime() {
+    return m_numEpochs;
+  }
+  
+  /**
+   * Call this function to place a node into the network list.
+   * @param n The node to place in the list.
+   */
+  private void addNode(NeuralConnection n) {
+    
+    NeuralConnection[] temp1 = new NeuralConnection[m_neuralNodes.length + 1];
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      temp1[noa] = m_neuralNodes[noa];
+    }
+
+    temp1[temp1.length-1] = n;
+    m_neuralNodes = temp1;
+  }
+
+  /** 
+   * Call this function to remove the passed node from the list.
+   * This will only remove the node if it is in the neuralnodes list.
+   * @param n The neuralConnection to remove.
+   * @return True if removed false if not (because it wasn't there).
+   */
+  private boolean removeNode(NeuralConnection n) {
+    NeuralConnection[] temp1 = new NeuralConnection[m_neuralNodes.length - 1];
+    int skip = 0;
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      if (n == m_neuralNodes[noa]) {
+	skip++;
+      }
+      else if (!((noa - skip) >= temp1.length)) {
+	temp1[noa - skip] = m_neuralNodes[noa];
+      }
+      else {
+	return false;
+      }
+    }
+    m_neuralNodes = temp1;
+    return true;
+  }
+
+  /**
+   * This function sets what the m_numeric flag to represent the passed class
+   * it also performs the normalization of the attributes if applicable
+   * and sets up the info to normalize the class. (note that regardless of
+   * the options it will fill an array with the range and base, set to 
+   * normalize all attributes and the class to be between -1 and 1)
+   * @param inst the instances.
+   * @return The modified instances. This needs to be done. If the attributes
+   * are normalized then deep copies will be made of all the instances which
+   * will need to be passed back out.
+   */
+  private Instances setClassType(Instances inst) throws Exception {
+    if (inst != null) {
+      // x bounds
+      double min=Double.POSITIVE_INFINITY;
+      double max=Double.NEGATIVE_INFINITY;
+      double value;
+      m_attributeRanges = new double[inst.numAttributes()];
+      m_attributeBases = new double[inst.numAttributes()];
+      for (int noa = 0; noa < inst.numAttributes(); noa++) {
+	min = Double.POSITIVE_INFINITY;
+	max = Double.NEGATIVE_INFINITY;
+	for (int i=0; i < inst.numInstances();i++) {
+	  if (!inst.instance(i).isMissing(noa)) {
+	    value = inst.instance(i).value(noa);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+	
+	m_attributeRanges[noa] = (max - min) / 2;
+	m_attributeBases[noa] = (max + min) / 2;
+	if (noa != inst.classIndex() && m_normalizeAttributes) {
+	  for (int i = 0; i < inst.numInstances(); i++) {
+	    if (m_attributeRanges[noa] != 0) {
+	      inst.instance(i).setValue(noa, (inst.instance(i).value(noa)  
+					      - m_attributeBases[noa]) /
+					m_attributeRanges[noa]);
+	    }
+	    else {
+	      inst.instance(i).setValue(noa, inst.instance(i).value(noa) - 
+					m_attributeBases[noa]);
+	    }
+	  }
+	}
+      }
+      if (inst.classAttribute().isNumeric()) {
+	m_numeric = true;
+      }
+      else {
+	m_numeric = false;
+      }
+    }
+    return inst;
+  }
+
+  /**
+   * A function used to stop the code that called buildclassifier
+   * from continuing on before the user has finished the decision tree.
+   * @param tf True to stop the thread, False to release the thread that is
+   * waiting there (if one).
+   */
+  public synchronized void blocker(boolean tf) {
+    if (tf) {
+      try {
+	wait();
+      } catch(InterruptedException e) {
+      }
+    }
+    else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Call this function to update the control panel for the gui.
+   */
+  private void updateDisplay() {
+    
+    if (m_gui) {
+      m_controlPanel.m_errorLabel.repaint();
+      m_controlPanel.m_epochsLabel.repaint();
+    }
+  }
+  
+
+  /**
+   * this will reset all the nodes in the network.
+   */
+  private void resetNetwork() {
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      m_outputs[noc].reset();
+    }
+  }
+  
+  /**
+   * This will cause the output values of all the nodes to be calculated.
+   * Note that the m_currentInstance is used to calculate these values.
+   */
+  private void calculateOutputs() {
+    for (int noc = 0; noc < m_numClasses; noc++) {	
+      //get the values. 
+      m_outputs[noc].outputValue(true);
+    }
+  }
+
+  /**
+   * This will cause the error values to be calculated for all nodes.
+   * Note that the m_currentInstance is used to calculate these values.
+   * Also the output values should have been calculated first.
+   * @return The squared error.
+   */
+  private double calculateErrors() throws Exception {
+    double ret = 0, temp = 0; 
+    for (int noc = 0; noc < m_numAttributes; noc++) {
+      //get the errors.
+      m_inputs[noc].errorValue(true);
+      
+    }
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      temp = m_outputs[noc].errorValue(false);
+      ret += temp * temp;
+    }    
+    return ret;
+    
+  }
+
+  /**
+   * This will cause the weight values to be updated based on the learning
+   * rate, momentum and the errors that have been calculated for each node.
+   * @param l The learning rate to update with.
+   * @param m The momentum to update with.
+   */
+  private void updateNetworkWeights(double l, double m) {
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      //update weights
+      m_outputs[noc].updateWeights(l, m);
+    }
+
+  }
+  
+  /**
+   * This creates the required input units.
+   */
+  private void setupInputs() throws Exception {
+    m_inputs = new NeuralEnd[m_numAttributes];
+    int now = 0;
+    for (int noa = 0; noa < m_numAttributes+1; noa++) {
+      if (m_instances.classIndex() != noa) {
+	m_inputs[noa - now] = new NeuralEnd(m_instances.attribute(noa).name());
+	
+	m_inputs[noa - now].setX(.1);
+	m_inputs[noa - now].setY((noa - now + 1.0) / (m_numAttributes + 1));
+	m_inputs[noa - now].setLink(true, noa);
+      }    
+      else {
+	now = 1;
+      }
+    }
+
+  }
+
+  /**
+   * This creates the required output units.
+   */
+  private void setupOutputs() throws Exception {
+  
+    m_outputs = new NeuralEnd[m_numClasses];
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      if (m_numeric) {
+	m_outputs[noa] = new NeuralEnd(m_instances.classAttribute().name());
+      }
+      else {
+	m_outputs[noa]= new NeuralEnd(m_instances.classAttribute().value(noa));
+      }
+      
+      m_outputs[noa].setX(.9);
+      m_outputs[noa].setY((noa + 1.0) / (m_numClasses + 1));
+      m_outputs[noa].setLink(false, noa);
+      NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,
+				       m_sigmoidUnit);
+      m_nextId++;
+      temp.setX(.75);
+      temp.setY((noa + 1.0) / (m_numClasses + 1));
+      addNode(temp);
+      NeuralConnection.connect(temp, m_outputs[noa]);
+    }
+ 
+  }
+  
+  /**
+   * Call this function to automatically generate the hidden units
+   */
+  private void setupHiddenLayer()
+  {
+    StringTokenizer tok = new StringTokenizer(m_hiddenLayers, ",");
+    int val = 0;  //num of nodes in a layer
+    int prev = 0; //used to remember the previous layer
+    int num = tok.countTokens(); //number of layers
+    String c;
+    for (int noa = 0; noa < num; noa++) {
+      //note that I am using the Double to get the value rather than the
+      //Integer class, because for some reason the Double implementation can
+      //handle leading white space and the integer version can't!?!
+      c = tok.nextToken().trim();
+      if (c.equals("a")) {
+	val = (m_numAttributes + m_numClasses) / 2;
+      }
+      else if (c.equals("i")) {
+	val = m_numAttributes;
+      }
+      else if (c.equals("o")) {
+	val = m_numClasses;
+      }
+      else if (c.equals("t")) {
+	val = m_numAttributes + m_numClasses;
+      }
+      else {
+	val = Double.valueOf(c).intValue();
+      }
+      for (int nob = 0; nob < val; nob++) {
+	NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,
+					 m_sigmoidUnit);
+	m_nextId++;
+	temp.setX(.5 / (num) * noa + .25);
+	temp.setY((nob + 1.0) / (val + 1));
+	addNode(temp);
+	if (noa > 0) {
+	  //then do connections
+	  for (int noc = m_neuralNodes.length - nob - 1 - prev;
+	       noc < m_neuralNodes.length - nob - 1; noc++) {
+	    NeuralConnection.connect(m_neuralNodes[noc], temp);
+	  }
+	}
+      }      
+      prev = val;
+    }
+    tok = new StringTokenizer(m_hiddenLayers, ",");
+    c = tok.nextToken();
+    if (c.equals("a")) {
+      val = (m_numAttributes + m_numClasses) / 2;
+    }
+    else if (c.equals("i")) {
+      val = m_numAttributes;
+    }
+    else if (c.equals("o")) {
+      val = m_numClasses;
+    }
+    else if (c.equals("t")) {
+      val = m_numAttributes + m_numClasses;
+    }
+    else {
+      val = Double.valueOf(c).intValue();
+    }
+    
+    if (val == 0) {
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	for (int nob = 0; nob < m_numClasses; nob++) {
+	  NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
+	}
+      }
+    }
+    else {
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	for (int nob = m_numClasses; nob < m_numClasses + val; nob++) {
+	  NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
+	}
+      }
+      for (int noa = m_neuralNodes.length - prev; noa < m_neuralNodes.length;
+	   noa++) {
+	for (int nob = 0; nob < m_numClasses; nob++) {
+	  NeuralConnection.connect(m_neuralNodes[noa], m_neuralNodes[nob]);
+	}
+      }
+    }
+    
+  }
+  
+  /**
+   * This will go through all the nodes and check if they are connected
+   * to a pure output unit. If so they will be set to be linear units.
+   * If not they will be set to be sigmoid units.
+   */
+  private void setEndsToLinear() {
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      if ((m_neuralNodes[noa].getType() & NeuralConnection.OUTPUT) ==
+	  NeuralConnection.OUTPUT) {
+	((NeuralNode)m_neuralNodes[noa]).setMethod(m_linearUnit);
+      }
+      else {
+	((NeuralNode)m_neuralNodes[noa]).setMethod(m_sigmoidUnit);
+      }
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Call this function to build and train a neural network for the training
+   * data provided.
+   * @param i The training data.
+   * @throws Exception if can't build classification properly.
+   */
+  public void buildClassifier(Instances i) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(i);
+
+    // remove instances with missing class
+    i = new Instances(i);
+    i.deleteWithMissingClass();
+
+    // only class? -> build ZeroR model
+    if (i.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(i);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_epoch = 0;
+    m_error = 0;
+    m_instances = null;
+    m_currentInstance = null;
+    m_controlPanel = null;
+    m_nodePanel = null;
+    
+    
+    m_outputs = new NeuralEnd[0];
+    m_inputs = new NeuralEnd[0];
+    m_numAttributes = 0;
+    m_numClasses = 0;
+    m_neuralNodes = new NeuralConnection[0];
+    
+    m_selected = new FastVector(4);
+    m_graphers = new FastVector(2);
+    m_nextId = 0;
+    m_stopIt = true;
+    m_stopped = true;
+    m_accepted = false;    
+    m_instances = new Instances(i);
+    m_random = new Random(m_randomSeed);
+    m_instances.randomize(m_random);
+
+    if (m_useNomToBin) {
+      m_nominalToBinaryFilter = new NominalToBinary();
+      m_nominalToBinaryFilter.setInputFormat(m_instances);
+      m_instances = Filter.useFilter(m_instances,
+				     m_nominalToBinaryFilter);
+    }
+    m_numAttributes = m_instances.numAttributes() - 1;
+    m_numClasses = m_instances.numClasses();
+ 
+    
+    setClassType(m_instances);
+    
+
+   
+    //this sets up the validation set.
+    Instances valSet = null;
+    //numinval is needed later
+    int numInVal = (int)(m_valSize / 100.0 * m_instances.numInstances());
+    if (m_valSize > 0) {
+      if (numInVal == 0) {
+	numInVal = 1;
+      }
+      valSet = new Instances(m_instances, 0, numInVal);
+    }
+    ///////////
+
+    setupInputs();
+      
+    setupOutputs();    
+    if (m_autoBuild) {
+      setupHiddenLayer();
+    }
+    
+    /////////////////////////////
+    //this sets up the gui for usage
+    if (m_gui) {
+      m_win = new JFrame();
+      
+      m_win.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    boolean k = m_stopIt;
+	    m_stopIt = true;
+	    int well =JOptionPane.showConfirmDialog(m_win, 
+						    "Are You Sure...\n"
+						    + "Click Yes To Accept"
+						    + " The Neural Network" 
+						    + "\n Click No To Return",
+						    "Accept Neural Network", 
+						    JOptionPane.YES_NO_OPTION);
+	    
+	    if (well == 0) {
+	      m_win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+	      m_accepted = true;
+	      blocker(false);
+	    }
+	    else {
+	      m_win.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+	    }
+	    m_stopIt = k;
+	  }
+	});
+      
+      m_win.getContentPane().setLayout(new BorderLayout());
+      m_win.setTitle("Neural Network");
+      m_nodePanel = new NodePanel();
+      // without the following two lines, the NodePanel.paintComponents(Graphics) 
+      // method will go berserk if the network doesn't fit completely: it will
+      // get called on a constant basis, using 100% of the CPU
+      // see the following forum thread:
+      // http://forum.java.sun.com/thread.jspa?threadID=580929&messageID=2945011
+      m_nodePanel.setPreferredSize(new Dimension(640, 480));
+      m_nodePanel.revalidate();
+
+      JScrollPane sp = new JScrollPane(m_nodePanel,
+				       JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
+				       JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+      m_controlPanel = new ControlPanel();
+           
+      m_win.getContentPane().add(sp, BorderLayout.CENTER);
+      m_win.getContentPane().add(m_controlPanel, BorderLayout.SOUTH);
+      m_win.setSize(640, 480);
+      m_win.setVisible(true);
+    }
+   
+    //This sets up the initial state of the gui
+    if (m_gui) {
+      blocker(true);
+      m_controlPanel.m_changeEpochs.setEnabled(false);
+      m_controlPanel.m_changeLearning.setEnabled(false);
+      m_controlPanel.m_changeMomentum.setEnabled(false);
+    } 
+    
+    //For silly situations in which the network gets accepted before training
+    //commenses
+    if (m_numeric) {
+      setEndsToLinear();
+    }
+    if (m_accepted) {
+      m_win.dispose();
+      m_controlPanel = null;
+      m_nodePanel = null;
+      m_instances = new Instances(m_instances, 0);
+      return;
+    }
+
+    //connections done.
+    double right = 0;
+    double driftOff = 0;
+    double lastRight = Double.POSITIVE_INFINITY;
+    double bestError = Double.POSITIVE_INFINITY;
+    double tempRate;
+    double totalWeight = 0;
+    double totalValWeight = 0;
+    double origRate = m_learningRate; //only used for when reset
+    
+    //ensure that at least 1 instance is trained through.
+    if (numInVal == m_instances.numInstances()) {
+      numInVal--;
+    }
+    if (numInVal < 0) {
+      numInVal = 0;
+    }
+    for (int noa = numInVal; noa < m_instances.numInstances(); noa++) {
+      if (!m_instances.instance(noa).classIsMissing()) {
+	totalWeight += m_instances.instance(noa).weight();
+      }
+    }
+    if (m_valSize != 0) {
+      for (int noa = 0; noa < valSet.numInstances(); noa++) {
+	if (!valSet.instance(noa).classIsMissing()) {
+	  totalValWeight += valSet.instance(noa).weight();
+	}
+      }
+    }
+    m_stopped = false;
+     
+
+    for (int noa = 1; noa < m_numEpochs + 1; noa++) {
+      right = 0;
+      for (int nob = numInVal; nob < m_instances.numInstances(); nob++) {
+	m_currentInstance = m_instances.instance(nob);
+	
+	if (!m_currentInstance.classIsMissing()) {
+	   
+	  //this is where the network updating (and training occurs, for the
+	  //training set
+	  resetNetwork();
+	  calculateOutputs();
+	  tempRate = m_learningRate * m_currentInstance.weight();  
+	  if (m_decay) {
+	    tempRate /= noa;
+	  }
+
+	  right += (calculateErrors() / m_instances.numClasses()) *
+	    m_currentInstance.weight();
+	  updateNetworkWeights(tempRate, m_momentum);
+	  
+	}
+	
+      }
+      right /= totalWeight;
+      if (Double.isInfinite(right) || Double.isNaN(right)) {
+	if (!m_reset) {
+	  m_instances = null;
+	  throw new Exception("Network cannot train. Try restarting with a" +
+			      " smaller learning rate.");
+	}
+	else {
+	  //reset the network if possible
+	  if (m_learningRate <= Utils.SMALL)
+	    throw new IllegalStateException(
+		"Learning rate got too small (" + m_learningRate 
+		+ " <= " + Utils.SMALL + ")!");
+	  m_learningRate /= 2;
+	  buildClassifier(i);
+	  m_learningRate = origRate;
+	  m_instances = new Instances(m_instances, 0);	  
+	  return;
+	}
+      }
+
+      ////////////////////////do validation testing if applicable
+      if (m_valSize != 0) {
+	right = 0;
+	for (int nob = 0; nob < valSet.numInstances(); nob++) {
+	  m_currentInstance = valSet.instance(nob);
+	  if (!m_currentInstance.classIsMissing()) {
+	    //this is where the network updating occurs, for the validation set
+	    resetNetwork();
+	    calculateOutputs();
+	    right += (calculateErrors() / valSet.numClasses()) 
+	      * m_currentInstance.weight();
+	    //note 'right' could be calculated here just using
+	    //the calculate output values. This would be faster.
+	    //be less modular
+	  }
+	  
+	}
+	
+	if (right < lastRight) {
+	  if (right < bestError) {
+	    bestError = right;
+	    // save the network weights at this point
+	    for (int noc = 0; noc < m_numClasses; noc++) {
+	      m_outputs[noc].saveWeights();
+	    }
+	    driftOff = 0;
+	  }
+	}
+	else {
+	  driftOff++;
+	}
+	lastRight = right;
+	if (driftOff > m_driftThreshold || noa + 1 >= m_numEpochs) {
+	  for (int noc = 0; noc < m_numClasses; noc++) {
+            m_outputs[noc].restoreWeights();
+          }
+	  m_accepted = true;
+	}
+	right /= totalValWeight;
+      }
+      m_epoch = noa;
+      m_error = right;
+      //shows what the neuralnet is upto if a gui exists. 
+      updateDisplay();
+      //This junction controls what state the gui is in at the end of each
+      //epoch, Such as if it is paused, if it is resumable etc...
+      if (m_gui) {
+	while ((m_stopIt || (m_epoch >= m_numEpochs && m_valSize == 0)) && 
+		!m_accepted) {
+	  m_stopIt = true;
+	  m_stopped = true;
+	  if (m_epoch >= m_numEpochs && m_valSize == 0) {
+	    
+	    m_controlPanel.m_startStop.setEnabled(false);
+	  }
+	  else {
+	    m_controlPanel.m_startStop.setEnabled(true);
+	  }
+	  m_controlPanel.m_startStop.setText("Start");
+	  m_controlPanel.m_startStop.setActionCommand("Start");
+	  m_controlPanel.m_changeEpochs.setEnabled(true);
+	  m_controlPanel.m_changeLearning.setEnabled(true);
+	  m_controlPanel.m_changeMomentum.setEnabled(true);
+	  
+	  blocker(true);
+	  if (m_numeric) {
+	    setEndsToLinear();
+	  }
+	}
+	m_controlPanel.m_changeEpochs.setEnabled(false);
+	m_controlPanel.m_changeLearning.setEnabled(false);
+	m_controlPanel.m_changeMomentum.setEnabled(false);
+	
+	m_stopped = false;
+	//if the network has been accepted stop the training loop
+	if (m_accepted) {
+	  m_win.dispose();
+	  m_controlPanel = null;
+	  m_nodePanel = null;
+	  m_instances = new Instances(m_instances, 0);
+	  return;
+	}
+      }
+      if (m_accepted) {
+	m_instances = new Instances(m_instances, 0);
+	return;
+      }
+    }
+    if (m_gui) {
+      m_win.dispose();
+      m_controlPanel = null;
+      m_nodePanel = null;
+    }
+    m_instances = new Instances(m_instances, 0);  
+  }
+
+  /**
+   * Call this function to predict the class of an instance once a 
+   * classification model has been built with the buildClassifier call.
+   * @param i The instance to classify.
+   * @return A double array filled with the probabilities of each class type.
+   * @throws Exception if can't classify instance.
+   */
+  public double[] distributionForInstance(Instance i) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(i);
+    }
+    
+    if (m_useNomToBin) {
+      m_nominalToBinaryFilter.input(i);
+      m_currentInstance = m_nominalToBinaryFilter.output();
+    }
+    else {
+      m_currentInstance = i;
+    }
+    
+    if (m_normalizeAttributes) {
+      for (int noa = 0; noa < m_instances.numAttributes(); noa++) {
+	if (noa != m_instances.classIndex()) {
+	  if (m_attributeRanges[noa] != 0) {
+	    m_currentInstance.setValue(noa, (m_currentInstance.value(noa) - 
+					     m_attributeBases[noa]) / 
+				       m_attributeRanges[noa]);
+	  }
+	  else {
+	    m_currentInstance.setValue(noa, m_currentInstance.value(noa) -
+				       m_attributeBases[noa]);
+	  }
+	}
+      }
+    }
+    resetNetwork();
+    
+    //since all the output values are needed.
+    //They are calculated manually here and the values collected.
+    double[] theArray = new double[m_numClasses];
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      theArray[noa] = m_outputs[noa].outputValue(true);
+    }
+    if (m_instances.classAttribute().isNumeric()) {
+      return theArray;
+    }
+    
+    //now normalize the array
+    double count = 0;
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      count += theArray[noa];
+    }
+    if (count <= 0) {
+      return null;
+    }
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      theArray[noa] /= count;
+    }
+    return theArray;
+  }
+  
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(14);
+
+    newVector.addElement(new Option(
+	      "\tLearning Rate for the backpropagation algorithm.\n"
+	      +"\t(Value should be between 0 - 1, Default = 0.3).",
+	      "L", 1, "-L <learning rate>"));
+    newVector.addElement(new Option(
+	      "\tMomentum Rate for the backpropagation algorithm.\n"
+	      +"\t(Value should be between 0 - 1, Default = 0.2).",
+	      "M", 1, "-M <momentum>"));
+    newVector.addElement(new Option(
+	      "\tNumber of epochs to train through.\n"
+	      +"\t(Default = 500).",
+	      "N", 1,"-N <number of epochs>"));
+    newVector.addElement(new Option(
+	      "\tPercentage size of validation set to use to terminate\n"
+	      + "\ttraining (if this is non zero it can pre-empt num of epochs.\n"
+	      +"\t(Value should be between 0 - 100, Default = 0).",
+	      "V", 1, "-V <percentage size of validation set>"));
+    newVector.addElement(new Option(
+	      "\tThe value used to seed the random number generator\n"
+	      + "\t(Value should be >= 0 and and a long, Default = 0).",
+	      "S", 1, "-S <seed>"));
+    newVector.addElement(new Option(
+	      "\tThe consequetive number of errors allowed for validation\n"
+	      + "\ttesting before the netwrok terminates.\n"
+	      + "\t(Value should be > 0, Default = 20).",
+	      "E", 1, "-E <threshold for number of consequetive errors>"));
+    newVector.addElement(new Option(
+              "\tGUI will be opened.\n"
+	      +"\t(Use this to bring up a GUI).",
+	      "G", 0,"-G"));
+    newVector.addElement(new Option(
+              "\tAutocreation of the network connections will NOT be done.\n"
+	      +"\t(This will be ignored if -G is NOT set)",
+	      "A", 0,"-A"));
+    newVector.addElement(new Option(
+              "\tA NominalToBinary filter will NOT automatically be used.\n"
+	      +"\t(Set this to not use a NominalToBinary filter).",
+	      "B", 0,"-B"));
+    newVector.addElement(new Option(
+	      "\tThe hidden layers to be created for the network.\n"
+	      + "\t(Value should be a list of comma separated Natural \n"
+	      + "\tnumbers or the letters 'a' = (attribs + classes) / 2, \n"
+	      + "\t'i' = attribs, 'o' = classes, 't' = attribs .+ classes)\n"
+	      + "\tfor wildcard values, Default = a).",
+	      "H", 1, "-H <comma seperated numbers for nodes on each layer>"));
+    newVector.addElement(new Option(
+              "\tNormalizing a numeric class will NOT be done.\n"
+	      +"\t(Set this to not normalize the class if it's numeric).",
+	      "C", 0,"-C"));
+    newVector.addElement(new Option(
+              "\tNormalizing the attributes will NOT be done.\n"
+	      +"\t(Set this to not normalize the attributes).",
+	      "I", 0,"-I"));
+    newVector.addElement(new Option(
+              "\tReseting the network will NOT be allowed.\n"
+	      +"\t(Set this to not allow the network to reset).",
+	      "R", 0,"-R"));
+    newVector.addElement(new Option(
+              "\tLearning rate decay will occur.\n"
+	      +"\t(Set this to cause the learning rate to decay).",
+	      "D", 0,"-D"));
+    
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L &lt;learning rate&gt;
+   *  Learning Rate for the backpropagation algorithm.
+   *  (Value should be between 0 - 1, Default = 0.3).</pre>
+   * 
+   * <pre> -M &lt;momentum&gt;
+   *  Momentum Rate for the backpropagation algorithm.
+   *  (Value should be between 0 - 1, Default = 0.2).</pre>
+   * 
+   * <pre> -N &lt;number of epochs&gt;
+   *  Number of epochs to train through.
+   *  (Default = 500).</pre>
+   * 
+   * <pre> -V &lt;percentage size of validation set&gt;
+   *  Percentage size of validation set to use to terminate
+   *  training (if this is non zero it can pre-empt num of epochs.
+   *  (Value should be between 0 - 100, Default = 0).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  The value used to seed the random number generator
+   *  (Value should be &gt;= 0 and and a long, Default = 0).</pre>
+   * 
+   * <pre> -E &lt;threshold for number of consequetive errors&gt;
+   *  The consequetive number of errors allowed for validation
+   *  testing before the netwrok terminates.
+   *  (Value should be &gt; 0, Default = 20).</pre>
+   * 
+   * <pre> -G
+   *  GUI will be opened.
+   *  (Use this to bring up a GUI).</pre>
+   * 
+   * <pre> -A
+   *  Autocreation of the network connections will NOT be done.
+   *  (This will be ignored if -G is NOT set)</pre>
+   * 
+   * <pre> -B
+   *  A NominalToBinary filter will NOT automatically be used.
+   *  (Set this to not use a NominalToBinary filter).</pre>
+   * 
+   * <pre> -H &lt;comma seperated numbers for nodes on each layer&gt;
+   *  The hidden layers to be created for the network.
+   *  (Value should be a list of comma separated Natural 
+   *  numbers or the letters 'a' = (attribs + classes) / 2, 
+   *  'i' = attribs, 'o' = classes, 't' = attribs .+ classes)
+   *  for wildcard values, Default = a).</pre>
+   * 
+   * <pre> -C
+   *  Normalizing a numeric class will NOT be done.
+   *  (Set this to not normalize the class if it's numeric).</pre>
+   * 
+   * <pre> -I
+   *  Normalizing the attributes will NOT be done.
+   *  (Set this to not normalize the attributes).</pre>
+   * 
+   * <pre> -R
+   *  Reseting the network will NOT be allowed.
+   *  (Set this to not allow the network to reset).</pre>
+   * 
+   * <pre> -D
+   *  Learning rate decay will occur.
+   *  (Set this to cause the learning rate to decay).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    //the defaults can be found here!!!!
+    String learningString = Utils.getOption('L', options);
+    if (learningString.length() != 0) {
+      setLearningRate((new Double(learningString)).doubleValue());
+    } else {
+      setLearningRate(0.3);
+    }
+    String momentumString = Utils.getOption('M', options);
+    if (momentumString.length() != 0) {
+      setMomentum((new Double(momentumString)).doubleValue());
+    } else {
+      setMomentum(0.2);
+    }
+    String epochsString = Utils.getOption('N', options);
+    if (epochsString.length() != 0) {
+      setTrainingTime(Integer.parseInt(epochsString));
+    } else {
+      setTrainingTime(500);
+    }
+    String valSizeString = Utils.getOption('V', options);
+    if (valSizeString.length() != 0) {
+      setValidationSetSize(Integer.parseInt(valSizeString));
+    } else {
+      setValidationSetSize(0);
+    }
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setSeed(Integer.parseInt(seedString));
+    } else {
+      setSeed(0);
+    }
+    String thresholdString = Utils.getOption('E', options);
+    if (thresholdString.length() != 0) {
+      setValidationThreshold(Integer.parseInt(thresholdString));
+    } else {
+      setValidationThreshold(20);
+    }
+    String hiddenLayers = Utils.getOption('H', options);
+    if (hiddenLayers.length() != 0) {
+      setHiddenLayers(hiddenLayers);
+    } else {
+      setHiddenLayers("a");
+    }
+    if (Utils.getFlag('G', options)) {
+      setGUI(true);
+    } else {
+      setGUI(false);
+    } //small note. since the gui is the only option that can change the other
+    //options this should be set first to allow the other options to set 
+    //properly
+    if (Utils.getFlag('A', options)) {
+      setAutoBuild(false);
+    } else {
+      setAutoBuild(true);
+    }
+    if (Utils.getFlag('B', options)) {
+      setNominalToBinaryFilter(false);
+    } else {
+      setNominalToBinaryFilter(true);
+    }
+    if (Utils.getFlag('C', options)) {
+      setNormalizeNumericClass(false);
+    } else {
+      setNormalizeNumericClass(true);
+    }
+    if (Utils.getFlag('I', options)) {
+      setNormalizeAttributes(false);
+    } else {
+      setNormalizeAttributes(true);
+    }
+    if (Utils.getFlag('R', options)) {
+      setReset(false);
+    } else {
+      setReset(true);
+    }
+    if (Utils.getFlag('D', options)) {
+      setDecay(true);
+    } else {
+      setDecay(false);
+    }
+    
+    Utils.checkForRemainingOptions(options);
+  }
+  
+  /**
+   * Gets the current settings of NeuralNet.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [21];
+    int current = 0;
+    options[current++] = "-L"; options[current++] = "" + getLearningRate(); 
+    options[current++] = "-M"; options[current++] = "" + getMomentum();
+    options[current++] = "-N"; options[current++] = "" + getTrainingTime(); 
+    options[current++] = "-V"; options[current++] = "" +getValidationSetSize();
+    options[current++] = "-S"; options[current++] = "" + getSeed();
+    options[current++] = "-E"; options[current++] =""+getValidationThreshold();
+    options[current++] = "-H"; options[current++] = getHiddenLayers();
+    if (getGUI()) {
+      options[current++] = "-G";
+    }
+    if (!getAutoBuild()) {
+      options[current++] = "-A";
+    }
+    if (!getNominalToBinaryFilter()) {
+      options[current++] = "-B";
+    }
+    if (!getNormalizeNumericClass()) {
+      options[current++] = "-C";
+    }
+    if (!getNormalizeAttributes()) {
+      options[current++] = "-I";
+    }
+    if (!getReset()) {
+      options[current++] = "-R";
+    }
+    if (getDecay()) {
+      options[current++] = "-D";
+    }
+
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * @return string describing the model.
+   */
+  public String toString() {
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer model = new StringBuffer(m_neuralNodes.length * 100); 
+    //just a rough size guess
+    NeuralNode con;
+    double[] weights;
+    NeuralConnection[] inputs;
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      con = (NeuralNode) m_neuralNodes[noa];  //this would need a change
+                                              //for items other than nodes!!!
+      weights = con.getWeights();
+      inputs = con.getInputs();
+      if (con.getMethod() instanceof SigmoidUnit) {
+	model.append("Sigmoid ");
+      }
+      else if (con.getMethod() instanceof LinearUnit) {
+	model.append("Linear ");
+      }
+      model.append("Node " + con.getId() + "\n    Inputs    Weights\n");
+      model.append("    Threshold    " + weights[0] + "\n");
+      for (int nob = 1; nob < con.getNumInputs() + 1; nob++) {
+	if ((inputs[nob - 1].getType() & NeuralConnection.PURE_INPUT) 
+	    == NeuralConnection.PURE_INPUT) {
+	  model.append("    Attrib " + 
+		       m_instances.attribute(((NeuralEnd)inputs[nob-1]).
+					     getLink()).name()
+		       + "    " + weights[nob] + "\n");
+	}
+	else {
+	  model.append("    Node " + inputs[nob-1].getId() + "    " +
+		       weights[nob] + "\n");
+	}
+      }      
+    }
+    //now put in the ends
+    for (int noa = 0; noa < m_outputs.length; noa++) {
+      inputs = m_outputs[noa].getInputs();
+      model.append("Class " + 
+		   m_instances.classAttribute().
+		   value(m_outputs[noa].getLink()) + 
+		   "\n    Input\n");
+      for (int nob = 0; nob < m_outputs[noa].getNumInputs(); nob++) {
+	if ((inputs[nob].getType() & NeuralConnection.PURE_INPUT)
+	    == NeuralConnection.PURE_INPUT) {
+	  model.append("    Attrib " +
+		       m_instances.attribute(((NeuralEnd)inputs[nob]).
+					     getLink()).name() + "\n");
+	}
+	else {
+	  model.append("    Node " + inputs[nob].getId() + "\n");
+	}
+      }
+    }
+    return model.toString();
+  }
+
+  /**
+   * This will return a string describing the classifier.
+   * @return The string.
+   */
+  public String globalInfo() {
+    return 
+        "A Classifier that uses backpropagation to classify instances.\n"
+      + "This network can be built by hand, created by an algorithm or both. "
+      + "The network can also be monitored and modified during training time. "
+      + "The nodes in this network are all sigmoid (except for when the class "
+      + "is numeric in which case the the output nodes become unthresholded "
+      + "linear units).";
+  }
+  
+  /**
+   * @return a string to describe the learning rate option.
+   */
+  public String learningRateTipText() {
+    return "The amount the" + 
+      " weights are updated.";
+  }
+  
+  /**
+   * @return a string to describe the momentum option.
+   */
+  public String momentumTipText() {
+    return "Momentum applied to the weights during updating.";
+  }
+
+  /**
+   * @return a string to describe the AutoBuild option.
+   */
+  public String autoBuildTipText() {
+    return "Adds and connects up hidden layers in the network.";
+  }
+
+  /**
+   * @return a string to describe the random seed option.
+   */
+  public String seedTipText() {
+    return "Seed used to initialise the random number generator." +
+      "Random numbers are used for setting the initial weights of the" +
+      " connections betweem nodes, and also for shuffling the training data.";
+  }
+  
+  /**
+   * @return a string to describe the validation threshold option.
+   */
+  public String validationThresholdTipText() {
+    return "Used to terminate validation testing." +
+      "The value here dictates how many times in a row the validation set" +
+      " error can get worse before training is terminated.";
+  }
+  
+  /**
+   * @return a string to describe the GUI option.
+   */
+  public String GUITipText() {
+    return "Brings up a gui interface." +
+      " This will allow the pausing and altering of the nueral network" +
+      " during training.\n\n" +
+      "* To add a node left click (this node will be automatically selected," +
+      " ensure no other nodes were selected).\n" +
+      "* To select a node left click on it either while no other node is" +
+      " selected or while holding down the control key (this toggles that" +
+      " node as being selected and not selected.\n" + 
+      "* To connect a node, first have the start node(s) selected, then click"+
+      " either the end node or on an empty space (this will create a new node"+
+      " that is connected with the selected nodes). The selection status of" +
+      " nodes will stay the same after the connection. (Note these are" +
+      " directed connections, also a connection between two nodes will not" +
+      " be established more than once and certain connections that are" + 
+      " deemed to be invalid will not be made).\n" +
+      "* To remove a connection select one of the connected node(s) in the" +
+      " connection and then right click the other node (it does not matter" +
+      " whether the node is the start or end the connection will be removed" +
+      ").\n" +
+      "* To remove a node right click it while no other nodes (including it)" +
+      " are selected. (This will also remove all connections to it)\n." +
+      "* To deselect a node either left click it while holding down control," +
+      " or right click on empty space.\n" +
+      "* The raw inputs are provided from the labels on the left.\n" +
+      "* The red nodes are hidden layers.\n" +
+      "* The orange nodes are the output nodes.\n" +
+      "* The labels on the right show the class the output node represents." +
+      " Note that with a numeric class the output node will automatically be" +
+      " made into an unthresholded linear unit.\n\n" +
+      "Alterations to the neural network can only be done while the network" +
+      " is not running, This also applies to the learning rate and other" +
+      " fields on the control panel.\n\n" + 
+      "* You can accept the network as being finished at any time.\n" +
+      "* The network is automatically paused at the beginning.\n" +
+      "* There is a running indication of what epoch the network is up to" + 
+      " and what the (rough) error for that epoch was (or for" +
+      " the validation if that is being used). Note that this error value" +
+      " is based on a network that changes as the value is computed." +
+      " (also depending on whether" +
+      " the class is normalized will effect the error reported for numeric" +
+      " classes.\n" +
+      "* Once the network is done it will pause again and either wait to be" +
+      " accepted or trained more.\n\n" +
+      "Note that if the gui is not set the network will not require any" +
+      " interaction.\n";
+  }
+  
+  /**
+   * @return a string to describe the validation size option.
+   */
+  public String validationSetSizeTipText() {
+    return "The percentage size of the validation set." +
+      "(The training will continue until it is observed that" +
+      " the error on the validation set has been consistently getting" +
+      " worse, or if the training time is reached).\n" +
+      "If This is set to zero no validation set will be used and instead" +
+      " the network will train for the specified number of epochs.";
+  }
+  
+  /**
+   * @return a string to describe the learning rate option.
+   */
+  public String trainingTimeTipText() {
+    return "The number of epochs to train through." + 
+      " If the validation set is non-zero then it can terminate the network" +
+      " early";
+  }
+
+
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String nominalToBinaryFilterTipText() {
+    return "This will preprocess the instances with the filter." +
+      " This could help improve performance if there are nominal attributes" +
+      " in the data.";
+  }
+
+  /**
+   * @return a string to describe the hidden layers in the network.
+   */
+  public String hiddenLayersTipText() {
+    return "This defines the hidden layers of the neural network." +
+      " This is a list of positive whole numbers. 1 for each hidden layer." +
+      " Comma seperated. To have no hidden layers put a single 0 here." +
+      " This will only be used if autobuild is set. There are also wildcard" +
+      " values 'a' = (attribs + classes) / 2, 'i' = attribs, 'o' = classes" +
+      " , 't' = attribs + classes.";
+  }
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String normalizeNumericClassTipText() {
+    return "This will normalize the class if it's numeric." +
+      " This could help improve performance of the network, It normalizes" +
+      " the class to be between -1 and 1. Note that this is only internally" +
+      ", the output will be scaled back to the original range.";
+  }
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String normalizeAttributesTipText() {
+    return "This will normalize the attributes." +
+      " This could help improve performance of the network." +
+      " This is not reliant on the class being numeric. This will also" +
+      " normalize nominal attributes as well (after they have been run" +
+      " through the nominal to binary filter if that is in use) so that the" +
+      " nominal values are between -1 and 1";
+  }
+  /**
+   * @return a string to describe the Reset option.
+   */
+  public String resetTipText() {
+    return "This will allow the network to reset with a lower learning rate." +
+      " If the network diverges from the answer this will automatically" +
+      " reset the network with a lower learning rate and begin training" +
+      " again. This option is only available if the gui is not set. Note" +
+      " that if the network diverges but isn't allowed to reset it will" +
+      " fail the training process and return an error message.";
+  }
+  
+  /**
+   * @return a string to describe the Decay option.
+   */
+  public String decayTipText() {
+    return "This will cause the learning rate to decrease." +
+      " This will divide the starting learning rate by the epoch number, to" +
+      " determine what the current learning rate should be. This may help" +
+      " to stop the network from diverging from the target output, as well" +
+      " as improve general performance. Note that the decaying learning" +
+      " rate will not be shown in the gui, only the original learning rate" +
+      ". If the learning rate is changed in the gui, this is treated as the" +
+      " starting learning rate.";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptronCS.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptronCS.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/MultilayerPerceptronCS.java	(revision 29)
@@ -0,0 +1,3130 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultilayerPerceptronCS.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Classifier;
+import weka.classifiers.functions.neural.LinearUnit;
+import weka.classifiers.functions.neural.NeuralConnection;
+import weka.classifiers.functions.neural.NeuralNode;
+import weka.classifiers.functions.neural.SigmoidUnit;
+import weka.core.Capabilities;
+//modCS.V & modCS.S; require the data source converter to read the files.
+import weka.core.converters.ConverterUtils.DataSource;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+/** 
+ <!-- globalinfo-start -->
+ * A Classifier that uses backpropagation to classify instances.<br/>
+ * This network can be built by hand, created by an algorithm or both. The network can also be monitored and modified during training time. The nodes in this network are all sigmoid (except for when the class is numeric in which case the the output nodes become unthresholded linear units).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;learning rate&gt;
+ *  Learning Rate for the backpropagation algorithm.
+ *  (Value should be between 0 - 1, Default = 0.3).</pre>
+ * 
+ * <pre> -M &lt;momentum&gt;
+ *  Momentum Rate for the backpropagation algorithm.
+ *  (Value should be between 0 - 1, Default = 0.2).</pre>
+ * 
+ * <pre> -N &lt;number of epochs&gt;
+ *  Number of epochs to train through.
+ *  (Default = 500).</pre>
+ * 
+ * <pre> -V &lt;percentage size of validation set&gt;
+ *  Percentage size of validation set to use to terminate
+ *  training (if this is non zero it can pre-empt num of epochs.
+ *  (Value should be between 0 - 100, Default = 0).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  The value used to seed the random number generator
+ *  (Value should be &gt;= 0 and and a long, Default = 0).</pre>
+ * 
+ * <pre> -E &lt;threshold for number of consequetive errors&gt;
+ *  The consequetive number of errors allowed for validation
+ *  testing before the netwrok terminates.
+ *  (Value should be &gt; 0, Default = 20).</pre>
+ * 
+ * <pre> -G
+ *  GUI will be opened.
+ *  (Use this to bring up a GUI).</pre>
+ * 
+ * <pre> -A
+ *  Autocreation of the network connections will NOT be done.
+ *  (This will be ignored if -G is NOT set)</pre>
+ * 
+ * <pre> -B
+ *  A NominalToBinary filter will NOT automatically be used.
+ *  (Set this to not use a NominalToBinary filter).</pre>
+ * 
+ * <pre> -H &lt;comma seperated numbers for nodes on each layer&gt;
+ *  The hidden layers to be created for the network.
+ *  (Value should be a list of comma separated Natural 
+ *  numbers or the letters 'a' = (attribs + classes) / 2, 
+ *  'i' = attribs, 'o' = classes, 't' = attribs .+ classes)
+ *  for wildcard values, Default = a).</pre>
+ * 
+ * <pre> -C
+ *  Normalizing a numeric class will NOT be done.
+ *  (Set this to not normalize the class if it's numeric).</pre>
+ * 
+ * <pre> -I
+ *  Normalizing the attributes will NOT be done.
+ *  (Set this to not normalize the attributes).</pre>
+ * 
+ * <pre> -R
+ *  Reseting the network will NOT be allowed.
+ *  (Set this to not allow the network to reset).</pre>
+ * 
+ * <pre> -D
+ *  Learning rate decay will occur.
+ *  (Set this to cause the learning rate to decay).</pre>
+ * 
+ * <pre> -validation-set &lt;data source file&gt;
+ *  Validation set to use,  as drawn from the data source file.
+ * </pre>
+ * 
+ * <pre> -secondary-training &lt;data source file&gt;
+ *  Secondary task training set to use, as drawn from the data source file.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 6202 $
+ */
+public class MultilayerPerceptronCS 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, Randomizable {
+  
+  /** for serialization */
+  static final long serialVersionUID = 572250905027665169L;
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line options (see setOptions)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MultilayerPerceptronCS(), argv);
+  }
+  
+
+  /** 
+   * This inner class is used to connect the nodes in the network up to
+   * the data that they are classifying, Note that objects of this class are
+   * only suitable to go on the attribute side or class side of the network
+   * and not both.
+   */
+  protected class NeuralEnd 
+    extends NeuralConnection {
+    
+    /** for serialization */
+    static final long serialVersionUID = 7305185603191183338L;
+  
+    /** 
+     * the value that represents the instance value this node represents. 
+     * For an input it is the attribute number, for an output, if nominal
+     * it is the class value. 
+     */
+    private int m_link;
+    
+    /** True if node is an input, False if it's an output. */
+    private boolean m_input;
+
+    /**
+     * Constructor
+     */
+    public NeuralEnd(String id) {
+      super(id);
+
+      m_link = 0;
+      m_input = true;
+      
+    }
+  
+    /**
+     * Call this function to determine if the point at x,y is on the unit.
+     * @param g The graphics context for font size info.
+     * @param x The x coord.
+     * @param y The y coord.
+     * @param w The width of the display.
+     * @param h The height of the display.
+     * @return True if the point is on the unit, false otherwise.
+     */
+    public boolean onUnit(Graphics g, int x, int y, int w, int h) {
+      
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      if (x < l || x > l + fm.stringWidth(m_id) + 4 
+	  || y < t || y > t + fm.getHeight() + fm.getDescent() + 4) {
+	return false;
+      }
+      return true;
+      
+    }
+   
+
+    /**
+     * This will draw the node id to the graphics context.
+     * @param g The graphics context.
+     * @param w The width of the drawing area.
+     * @param h The height of the drawing area.
+     */
+    public void drawNode(Graphics g, int w, int h) {
+      
+      if ((m_type & PURE_INPUT) == PURE_INPUT) {
+	g.setColor(Color.green);
+      }
+      else {
+	g.setColor(Color.orange);
+      }
+      
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      g.fill3DRect(l, t, fm.stringWidth(m_id) + 4
+		   , fm.getHeight() + fm.getDescent() + 4
+		   , true);
+      g.setColor(Color.black);
+      
+      g.drawString(m_id, l + 2, t + fm.getHeight() + 2);
+
+    }
+
+
+    /**
+     * Call this function to draw the node highlighted.
+     * @param g The graphics context.
+     * @param w The width of the drawing area.
+     * @param h The height of the drawing area.
+     */
+    public void drawHighlight(Graphics g, int w, int h) {
+      
+      g.setColor(Color.black);
+      FontMetrics fm = g.getFontMetrics();
+      int l = (int)(m_x * w) - fm.stringWidth(m_id) / 2;
+      int t = (int)(m_y * h) - fm.getHeight() / 2;
+      g.fillRect(l - 2, t - 2, fm.stringWidth(m_id) + 8
+		 , fm.getHeight() + fm.getDescent() + 8); 
+      drawNode(g, w, h);
+    }
+    
+    /**
+     * Call this to get the output value of this unit. 
+     * @param calculate True if the value should be calculated if it hasn't 
+     * been already.
+     * @return The output value, or NaN, if the value has not been calculated.
+     */
+    public double outputValue(boolean calculate) {
+     
+      if (Double.isNaN(m_unitValue) && calculate) {
+	if (m_input) {
+	  if (m_currentInstance.isMissing(m_link)) {
+	    m_unitValue = 0;
+	  }
+	  else {
+	    
+	    m_unitValue = m_currentInstance.value(m_link);
+	  }
+	}
+	else {
+	  //node is an output.
+	  m_unitValue = 0;
+	  for (int noa = 0; noa < m_numInputs; noa++) {
+	    m_unitValue += m_inputList[noa].outputValue(true);
+	   
+	  }
+	  if (m_numeric && m_normalizeClass) {
+	    //then scale the value;
+	    //this scales linearly from between -1 and 1
+	    m_unitValue = m_unitValue * 
+	      m_attributeRanges[m_instances.classIndex()] + 
+	      m_attributeBases[m_instances.classIndex()];
+	  }         
+	}
+      }
+      return m_unitValue;
+      
+      
+    }
+    
+    /**
+     * Call this to get the error value of this unit, which in this case is
+     * the difference between the predicted class, and the actual class.
+     * @param calculate True if the value should be calculated if it hasn't 
+     * been already.
+     * @return The error value, or NaN, if the value has not been calculated.
+     */
+    public double errorValue(boolean calculate) {
+      
+      if (!Double.isNaN(m_unitValue) && Double.isNaN(m_unitError) 
+	  && calculate) {
+	
+	if (m_input) {
+	  m_unitError = 0;
+	  for (int noa = 0; noa < m_numOutputs; noa++) {
+	    m_unitError += m_outputList[noa].errorValue(true);
+	  }
+	}
+	else {
+	  if (m_currentInstance.classIsMissing()) {
+	    m_unitError = .1;  
+	  }
+          //dmod
+	  else if (m_instances.classAttribute().isNominal()) {
+	    if (m_currentInstance.classValue() == m_link) {
+	      //1
+                m_unitError = 1 - m_unitValue;
+	    }
+	    else { //0
+	      m_unitError =  0 - m_unitValue;
+	    }
+	  }
+	  else if (m_numeric) {
+	    
+	    if (m_normalizeClass) {
+	      if (m_attributeRanges[m_instances.classIndex()] == 0) {
+		m_unitError = 0;
+	      }
+	      else {
+		m_unitError = (m_currentInstance.classValue() - m_unitValue ) /
+		  m_attributeRanges[m_instances.classIndex()];
+		//m_numericRange;
+		
+	      }
+	    }
+	    else {
+	      m_unitError = m_currentInstance.classValue() - m_unitValue;
+	    }
+	  }
+	}
+      }
+      return m_unitError;
+    }
+    
+    
+    /**
+     * Call this to reset the value and error for this unit, ready for the next
+     * run. This will also call the reset function of all units that are 
+     * connected as inputs to this one.
+     * This is also the time that the update for the listeners will be 
+     * performed.
+     */
+    public void reset() {
+      
+      if (!Double.isNaN(m_unitValue) || !Double.isNaN(m_unitError)) {
+	m_unitValue = Double.NaN;
+	m_unitError = Double.NaN;
+	m_weightsUpdated = false;
+	for (int noa = 0; noa < m_numInputs; noa++) {
+	  m_inputList[noa].reset();
+	}
+      }
+    }
+    
+    /**
+     * Call this to have the connection save the current
+     * weights.
+     */
+    public void saveWeights() {
+      for (int i = 0; i < m_numInputs; i++) {
+        m_inputList[i].saveWeights();
+      }
+    }
+    
+    /**
+     * Call this to have the connection restore from the saved
+     * weights.
+     */
+    public void restoreWeights() {
+      for (int i = 0; i < m_numInputs; i++) {
+        m_inputList[i].restoreWeights();
+      }
+    }
+    
+    
+    /** 
+     * Call this function to set What this end unit represents.
+     * @param input True if this unit is used for entering an attribute,
+     * False if it's used for determining a class value.
+     * @param val The attribute number or class type that this unit represents.
+     * (for nominal attributes).
+     */
+    public void setLink(boolean input, int val) throws Exception {
+      m_input = input;
+      
+      if (input) {
+	m_type = PURE_INPUT;
+      }
+      else {
+	m_type = PURE_OUTPUT;
+      }
+      if (val < 0 || (input && val > m_instances.numAttributes()) 
+	  || (!input && m_instances.classAttribute().isNominal() 
+	      && val > m_instances.classAttribute().numValues())) {
+	m_link = 0;
+      }
+      else {
+	m_link = val;
+      }
+    }
+    
+    /**
+     * @return link for this node.
+     */
+    public int getLink() {
+      return m_link;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6202 $");
+    }
+  }
+  
+
+ 
+  /** Inner class used to draw the nodes onto.(uses the node lists!!) 
+   * This will also handle the user input. */
+  private class NodePanel 
+    extends JPanel
+    implements RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -3067621833388149984L;
+
+    /**
+     * The constructor.
+     */
+    public NodePanel() {
+      
+
+      addMouseListener(new MouseAdapter() {
+	  
+	  public void mousePressed(MouseEvent e) {
+	    
+	    if (!m_stopped) {
+	      return;
+	    }
+	    if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK && 
+		!e.isAltDown()) {
+	      Graphics g = NodePanel.this.getGraphics();
+	      int x = e.getX();
+	      int y = e.getY();
+	      int w = NodePanel.this.getWidth();
+	      int h = NodePanel.this.getHeight();
+	      FastVector tmp = new FastVector(4);
+	      for (int noa = 0; noa < m_numAttributes; noa++) {
+		if (m_inputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_inputs[noa]);
+		  selection(tmp, 
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_numClasses; noa++) {
+		if (m_outputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_outputs[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+		if (m_neuralNodes[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_neuralNodes[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , true);
+		  return;
+		}
+
+	      }
+	      NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), 
+					       m_random, m_sigmoidUnit);
+	      m_nextId++;
+	      temp.setX((double)e.getX() / w);
+	      temp.setY((double)e.getY() / h);
+	      tmp.addElement(temp);
+	      addNode(temp);
+	      selection(tmp, (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			, true);
+	    }
+	    else {
+	      //then right click
+	      Graphics g = NodePanel.this.getGraphics();
+	      int x = e.getX();
+	      int y = e.getY();
+	      int w = NodePanel.this.getWidth();
+	      int h = NodePanel.this.getHeight();
+	      FastVector tmp = new FastVector(4);
+	      for (int noa = 0; noa < m_numAttributes; noa++) {
+		if (m_inputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_inputs[noa]);
+		  selection(tmp, 
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+		
+		
+	      }
+	      for (int noa = 0; noa < m_numClasses; noa++) {
+		if (m_outputs[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_outputs[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+	      }
+	      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+		if (m_neuralNodes[noa].onUnit(g, x, y, w, h)) {
+		  tmp.addElement(m_neuralNodes[noa]);
+		  selection(tmp,
+			    (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			    , false);
+		  return;
+		}
+	      }
+	      selection(null, (e.getModifiers() & MouseEvent.CTRL_MASK) == MouseEvent.CTRL_MASK
+			, false);
+	    }
+	  }
+	});
+    }
+    
+    
+    /**
+     * This function gets called when the user has clicked something
+     * It will amend the current selection or connect the current selection
+     * to the new selection.
+     * Or if nothing was selected and the right button was used it will 
+     * delete the node.
+     * @param v The units that were selected.
+     * @param ctrl True if ctrl was held down.
+     * @param left True if it was the left mouse button.
+     */
+    private void selection(FastVector v, boolean ctrl, boolean left) {
+      
+      if (v == null) {
+	//then unselect all.
+	m_selected.removeAllElements();
+	repaint();
+	return;
+      }
+      
+
+      //then exclusive or the new selection with the current one.
+      if ((ctrl || m_selected.size() == 0) && left) {
+	boolean removed = false;
+	for (int noa = 0; noa < v.size(); noa++) {
+	  removed = false;
+	  for (int nob = 0; nob < m_selected.size(); nob++) {
+	    if (v.elementAt(noa) == m_selected.elementAt(nob)) {
+	      //then remove that element
+	      m_selected.removeElementAt(nob);
+	      removed = true;
+	      break;
+	    }
+	  }
+	  if (!removed) {
+	    m_selected.addElement(v.elementAt(noa));
+	  }
+	}
+	repaint();
+	return;
+      }
+
+      
+      if (left) {
+	//then connect the current selection to the new one.
+	for (int noa = 0; noa < m_selected.size(); noa++) {
+	  for (int nob = 0; nob < v.size(); nob++) {
+	    NeuralConnection
+	      .connect((NeuralConnection)m_selected.elementAt(noa)
+		       , (NeuralConnection)v.elementAt(nob));
+	  }
+	}
+      }
+      else if (m_selected.size() > 0) {
+	//then disconnect the current selection from the new one.
+	
+	for (int noa = 0; noa < m_selected.size(); noa++) {
+	  for (int nob = 0; nob < v.size(); nob++) {
+	    NeuralConnection
+	      .disconnect((NeuralConnection)m_selected.elementAt(noa)
+			  , (NeuralConnection)v.elementAt(nob));
+	    
+	    NeuralConnection
+	      .disconnect((NeuralConnection)v.elementAt(nob)
+			  , (NeuralConnection)m_selected.elementAt(noa));
+	    
+	  }
+	}
+      }
+      else {
+	//then remove the selected node. (it was right clicked while 
+	//no other units were selected
+	for (int noa = 0; noa < v.size(); noa++) {
+	  ((NeuralConnection)v.elementAt(noa)).removeAllInputs();
+	  ((NeuralConnection)v.elementAt(noa)).removeAllOutputs();
+	  removeNode((NeuralConnection)v.elementAt(noa));
+	}
+      }
+      repaint();
+    }
+
+    /**
+     * This will paint the nodes ontot the panel.
+     * @param g The graphics context.
+     */
+    public void paintComponent(Graphics g) {
+
+      super.paintComponent(g);
+      int x = getWidth();
+      int y = getHeight();
+      if (25 * m_numAttributes > 25 * m_numClasses && 
+	  25 * m_numAttributes > y) {
+	setSize(x, 25 * m_numAttributes);
+      }
+      else if (25 * m_numClasses > y) {
+	setSize(x, 25 * m_numClasses);
+      }
+      else {
+	setSize(x, y);
+      }
+
+      y = getHeight();
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	m_inputs[noa].drawInputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_numClasses; noa++) {
+	m_outputs[noa].drawInputLines(g, x, y);
+	m_outputs[noa].drawOutputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+	m_neuralNodes[noa].drawInputLines(g, x, y);
+      }
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	m_inputs[noa].drawNode(g, x, y);
+      }
+      for (int noa = 0; noa < m_numClasses; noa++) {
+	m_outputs[noa].drawNode(g, x, y);
+      }
+      for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+	m_neuralNodes[noa].drawNode(g, x, y);
+      }
+
+      for (int noa = 0; noa < m_selected.size(); noa++) {
+	((NeuralConnection)m_selected.elementAt(noa)).drawHighlight(g, x, y);
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6202 $");
+    }
+  }
+
+  /** 
+   * This provides the basic controls for working with the neuralnetwork
+   * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+   * @version $Revision: 6202 $
+   */
+  class ControlPanel 
+    extends JPanel
+    implements RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 7393543302294142271L;
+    
+    /** The start stop button. */
+    public JButton m_startStop;
+    
+    /** The button to accept the network (even if it hasn't done all epochs. */
+    public JButton m_acceptButton;
+    
+    /** A label to state the number of epochs processed so far. */
+    public JPanel m_epochsLabel;
+    
+    /** A label to state the total number of epochs to be processed. */
+    public JLabel m_totalEpochsLabel;
+    
+    /** A text field to allow the changing of the total number of epochs. */
+    public JTextField m_changeEpochs;
+    
+    /** A label to state the learning rate. */
+    public JLabel m_learningLabel;
+    
+    /** A label to state the momentum. */
+    public JLabel m_momentumLabel;
+    
+    /** A text field to allow the changing of the learning rate. */
+    public JTextField m_changeLearning;
+    
+    /** A text field to allow the changing of the momentum. */
+    public JTextField m_changeMomentum;
+    
+    /** A label to state roughly the accuracy of the network.(because the
+	accuracy is calculated per epoch, but the network is changing 
+	throughout each epoch train).
+    */
+    public JPanel m_errorLabel;
+    //modCS.E
+    /** A label to indicate the currently lowest validation error on the 
+     * neural network GUI. */
+    public JPanel m_lowValErrorLabel;
+    /** 
+     * A label to indicate the epoch index when the lowest validation 
+     * error is found
+     */
+    public JPanel m_epochIndexLabel;    
+    
+    
+    /** The constructor. */
+    public ControlPanel() { 
+      setBorder(BorderFactory.createTitledBorder("Controls"));
+      
+      m_totalEpochsLabel = new JLabel("Num Of Epochs  ");
+      m_epochsLabel = new JPanel(){ 
+	  /** for serialization */
+	  private static final long serialVersionUID = 2562773937093221399L;
+
+	  public void paintComponent(Graphics g) {
+	    super.paintComponent(g);
+	    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+	    g.drawString("Epoch  " + m_epoch, 0, 10);
+	  }
+	};
+      m_epochsLabel.setFont(m_totalEpochsLabel.getFont());
+
+        //modCS.E; Displays lowest validation error output in the neural
+        // network GUI.      
+        m_lowValErrorLabel = new JPanel()
+        {
+            /** for serialization */
+            private static final long serialVersionUID = 4390239056336679189L;
+
+            public void paintComponent(Graphics g)
+            {
+                    super.paintComponent(g);
+                    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+                    if (m_valSize == 0 && m_valSet == null)
+                    {
+                            g.drawString("Lowest Validation Error = NA", 0, 10);
+                    }
+                    else
+                    {
+                            g.drawString("Lowest Validation Error = "
+                                    + Utils.doubleToString(m_lowValError, 7), 0, 10);
+                    }
+            }
+        };
+        m_lowValErrorLabel.setFont(m_epochsLabel.getFont());
+        //added : should not be needed anymore; for val error
+        m_epochIndexLabel = new JPanel()
+        {
+            /** for serialization */
+            private static final long serialVersionUID = 4390239056336679189L;
+
+            public void paintComponent(Graphics g)
+            {
+                    super.paintComponent(g);
+                    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+                    if (m_valSize == 0 && m_valSet == null)
+                    {
+                            g.drawString(
+                                    "Epoch index (lowest validation error) = NA", 0, 10);
+                    }
+                    else
+                    {
+                            g.drawString("Epoch index (lowest validation error) = "
+                                    + m_epochIndex, 0, 10);
+                    }
+            }
+        };
+        m_epochIndexLabel.setFont(m_epochsLabel.getFont());
+      
+      
+      m_changeEpochs = new JTextField();
+      m_changeEpochs.setText("" + m_numEpochs);
+      m_errorLabel = new JPanel(){
+	  /** for serialization */
+	  private static final long serialVersionUID = 4390239056336679189L;
+
+	  public void paintComponent(Graphics g) {
+	    super.paintComponent(g);
+	    g.setColor(m_controlPanel.m_totalEpochsLabel.getForeground());
+	    if (m_valSize == 0 && m_valSet == null) {
+	      g.drawString("Error per Epoch = " + 
+			   Utils.doubleToString(m_error, 7), 0, 10);
+	    }
+	    else {
+	      g.drawString("Validation Error per Epoch = "
+			   + Utils.doubleToString(m_error, 7), 0, 10);
+	    }
+	  }
+	};
+      m_errorLabel.setFont(m_epochsLabel.getFont());
+      
+      m_learningLabel = new JLabel("Learning Rate = ");
+      m_momentumLabel = new JLabel("Momentum = ");
+      m_changeLearning = new JTextField();
+      m_changeMomentum = new JTextField();
+      m_changeLearning.setText("" + m_learningRate);
+      m_changeMomentum.setText("" + m_momentum);
+      setLayout(new BorderLayout(15, 10));
+
+      m_stopIt = true;
+      m_accepted = false;
+      m_startStop = new JButton("Start");
+      m_startStop.setActionCommand("Start");
+      
+      m_acceptButton = new JButton("Accept");
+      m_acceptButton.setActionCommand("Accept");
+      
+      JPanel buttons = new JPanel();
+      buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
+      buttons.add(m_startStop);
+      buttons.add(m_acceptButton);
+      add(buttons, BorderLayout.WEST);
+      JPanel data = new JPanel();
+      data.setLayout(new BoxLayout(data, BoxLayout.Y_AXIS));
+      
+      Box ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_epochsLabel);
+      data.add(ab);
+      
+
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_errorLabel);
+      data.add(ab);
+      
+      //modCS.E
+      //establishes space on the neural network GUI for lowest validation error
+      ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_lowValErrorLabel);
+      data.add(ab);
+      ab = new Box(BoxLayout.X_AXIS);
+      ab.add(m_epochIndexLabel);
+      data.add(ab);      
+         
+      add(data, BorderLayout.CENTER);
+
+      data = new JPanel();
+      data.setLayout(new BoxLayout(data, BoxLayout.Y_AXIS));
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      Component b = Box.createGlue();
+      ab.add(m_totalEpochsLabel);
+      ab.add(m_changeEpochs);
+      m_changeEpochs.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);      
+           
+      ab = new Box(BoxLayout.X_AXIS);
+      b = Box.createGlue();
+      ab.add(m_learningLabel);
+      ab.add(m_changeLearning);
+      m_changeLearning.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);
+      
+      ab = new Box(BoxLayout.X_AXIS);
+      b = Box.createGlue();
+      ab.add(m_momentumLabel);
+      ab.add(m_changeMomentum);
+      m_changeMomentum.setMaximumSize(new Dimension(200, 20));
+      ab.add(b);
+      data.add(ab);
+      
+      add(data, BorderLayout.EAST);
+      
+      m_startStop.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (e.getActionCommand().equals("Start")) {
+	      m_stopIt = false;
+	      m_startStop.setText("Stop");
+	      m_startStop.setActionCommand("Stop");
+	      int n = Integer.valueOf(m_changeEpochs.getText()).intValue();
+	      
+	      m_numEpochs = n;
+	      m_changeEpochs.setText("" + m_numEpochs);
+	      
+	      double m=Double.valueOf(m_changeLearning.getText()).
+		doubleValue();
+	      setLearningRate(m);
+	      m_changeLearning.setText("" + m_learningRate);
+	      
+	      m = Double.valueOf(m_changeMomentum.getText()).doubleValue();
+	      setMomentum(m);
+	      m_changeMomentum.setText("" + m_momentum);
+	      
+	      blocker(false);
+	    }
+	    else if (e.getActionCommand().equals("Stop")) {
+	      m_stopIt = true;
+	      m_startStop.setText("Start");
+	      m_startStop.setActionCommand("Start");
+	    }
+	  }
+	});
+      
+      m_acceptButton.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_accepted = true;
+	    blocker(false);
+	  }
+	});
+      
+      m_changeEpochs.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    int n = Integer.valueOf(m_changeEpochs.getText()).intValue();
+	    if (n > 0) {
+	      m_numEpochs = n;
+	      blocker(false);
+	    }
+	  }
+	});
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6202 $");
+    }
+  }
+  
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+    
+  /** The training instances. */
+  private Instances m_instances;
+  
+  //modCS.S & //modCS.V
+  /**
+   * Declaration of needed structures for loading and storing external
+   * validation and secondary task training files.
+   */
+  protected DataSource m_valSetSource = null;
+  protected Instances m_valSet = null;
+  protected String m_valSetFileName = null;
+  protected DataSource m_secSetSource = null;
+  protected Instances m_secSet = null;
+  protected String m_secSetFileName = null;  
+  
+  /** The current instance running through the network. */
+  private Instance m_currentInstance;
+  
+  /** A flag to say that it's a numeric class. */
+  private boolean m_numeric;
+
+  /** The ranges for all the attributes. */
+  private double[] m_attributeRanges;
+
+  /** The base values for all the attributes. */
+  private double[] m_attributeBases;
+
+  /** The output units.(only feeds the errors, does no calcs) */
+  private NeuralEnd[] m_outputs;
+
+  /** The input units.(only feeds the inputs does no calcs) */
+  private NeuralEnd[] m_inputs;
+
+  /** All the nodes that actually comprise the logical neural net. */
+  private NeuralConnection[] m_neuralNodes;
+
+  /** The number of classes. */
+  private int m_numClasses = 0;
+  
+  /** The number of attributes. */
+  private int m_numAttributes = 0; //note the number doesn't include the class.
+  
+  /** The panel the nodes are displayed on. */
+  private NodePanel m_nodePanel;
+  
+  /** The control panel. */
+  private ControlPanel m_controlPanel;
+
+  /** The next id number available for default naming. */
+  private int m_nextId;
+   
+  /** A Vector list of the units currently selected. */
+  private FastVector m_selected;
+
+  /** A Vector list of the graphers. */
+  private FastVector m_graphers;
+
+  /** The number of epochs to train through. */
+  private int m_numEpochs;
+
+  /** a flag to state if the network should be running, or stopped. */
+  private boolean m_stopIt;
+
+  /** a flag to state that the network has in fact stopped. */
+  private boolean m_stopped;
+
+  /** a flag to state that the network should be accepted the way it is. */
+  private boolean m_accepted;
+  /** The window for the network. */
+  private JFrame m_win;
+
+  /** A flag to tell the build classifier to automatically build a neural net.
+   */
+  private boolean m_autoBuild;
+
+  /** A flag to state that the gui for the network should be brought up.
+      To allow interaction while training. */
+  private boolean m_gui;
+
+  /** An int to say how big the validation set should be. */
+  private int m_valSize;
+
+  /** The number to to use to quit on validation testing. */
+  private int m_driftThreshold;
+
+  /** The number used to seed the random number generator. */
+  private int m_randomSeed;
+
+  /** The actual random number generator. */
+  private Random m_random;
+
+  /** A flag to state that a nominal to binary filter should be used. */
+  private boolean m_useNomToBin;
+  
+  /** The actual filter. */
+  private NominalToBinary m_nominalToBinaryFilter;
+
+  /** The string that defines the hidden layers */
+  private String m_hiddenLayers;
+
+  /** This flag states that the user wants the input values normalized. */
+  private boolean m_normalizeAttributes;
+
+  /** This flag states that the user wants the learning rate to decay. */
+  private boolean m_decay;
+
+  /** This is the learning rate for the network. */
+  private double m_learningRate;
+
+  /** This is the momentum for the network. */
+  private double m_momentum;
+
+  /** Shows the number of the epoch that the network just finished. */
+  private int m_epoch;
+
+  /** Shows the error of the epoch that the network just finished. */
+  private double m_error;
+
+   //modCS.E
+   /** Shows the lowest validation error */
+   private double m_lowValError;
+   /** Shows the epoch index when lowest validation error is found */
+   private int m_epochIndex;  
+
+  /** This flag states that the user wants the network to restart if it
+   * is found to be generating infinity or NaN for the error value. This
+   * would restart the network with the current options except that the
+   * learning rate would be smaller than before, (perhaps half of its current
+   * value). This option will not be available if the gui is chosen (if the
+   * gui is open the user can fix the network themselves, it is an 
+   * architectural minefield for the network to be reset with the gui open). */
+  private boolean m_reset;
+
+  /** This flag states that the user wants the class to be normalized while
+   * processing in the network is done. (the final answer will be in the
+   * original range regardless). This option will only be used when the class
+   * is numeric. */
+  private boolean m_normalizeClass;
+
+  /**
+   * this is a sigmoid unit. 
+   */
+  private SigmoidUnit m_sigmoidUnit;
+  
+  /**
+   * This is a linear unit.
+   */
+  private LinearUnit m_linearUnit;
+  
+  /**
+   * The constructor.
+   */
+  public MultilayerPerceptronCS() {
+    m_instances = null;
+    //modCS.V
+    m_valSet = null;
+    m_valSetSource = null;
+    m_valSetFileName = null;
+    //modCS.S
+    m_secSet = null;
+    m_secSetSource = null;
+    m_secSetFileName = null;    
+    
+    m_currentInstance = null;
+    m_controlPanel = null;
+    m_nodePanel = null;
+    m_epoch = 0;
+    m_error = 0;
+    
+    
+    m_outputs = new NeuralEnd[0];
+    m_inputs = new NeuralEnd[0];
+    m_numAttributes = 0;
+    m_numClasses = 0;
+    m_neuralNodes = new NeuralConnection[0];
+    m_selected = new FastVector(4);
+    m_graphers = new FastVector(2);
+    m_nextId = 0;
+    m_stopIt = true;
+    m_stopped = true;
+    m_accepted = false;
+    m_numeric = false;
+    m_random = null;
+    m_nominalToBinaryFilter = new NominalToBinary();
+    m_sigmoidUnit = new SigmoidUnit();
+    m_linearUnit = new LinearUnit();
+    //setting all the options to their defaults. To completely change these
+    //defaults they will also need to be changed down the bottom in the 
+    //setoptions function (the text info in the accompanying functions should 
+    //also be changed to reflect the new defaults
+    m_normalizeClass = true;
+    m_normalizeAttributes = true;
+    m_autoBuild = true;
+    m_gui = false;
+    m_useNomToBin = true;
+    m_driftThreshold = 20;
+    m_numEpochs = 500;
+    m_valSize = 0;
+    m_randomSeed = 0;
+    m_hiddenLayers = "a";
+    m_learningRate = .3;
+    m_momentum = .2;
+    m_reset = true;
+    m_decay = false;
+  
+    //modCS.E
+    m_lowValError = 0;
+    m_epochIndex = 0;    
+    
+  }
+
+  /**
+   * @param d True if the learning rate should decay.
+   */
+  public void setDecay(boolean d) {
+    m_decay = d;
+  }
+  
+  /**
+   * @return the flag for having the learning rate decay.
+   */
+  public boolean getDecay() {
+    return m_decay;
+  }
+
+  /**
+   * This sets the network up to be able to reset itself with the current 
+   * settings and the learning rate at half of what it is currently. This
+   * will only happen if the network creates NaN or infinite errors. Also this
+   * will continue to happen until the network is trained properly. The 
+   * learning rate will also get set back to it's original value at the end of
+   * this. This can only be set to true if the GUI is not brought up.
+   * @param r True if the network should restart with it's current options
+   * and set the learning rate to half what it currently is.
+   */
+  public void setReset(boolean r) {
+    if (m_gui) {
+      r = false;
+    }
+    m_reset = r;
+      
+  }
+
+  /**
+   * @return The flag for reseting the network.
+   */
+  public boolean getReset() {
+    return m_reset;
+  }
+  
+  /**
+   * @param c True if the class should be normalized (the class will only ever
+   * be normalized if it is numeric). (Normalization puts the range between
+   * -1 - 1).
+   */
+  public void setNormalizeNumericClass(boolean c) {
+    m_normalizeClass = c;
+  }
+  
+  /**
+   * @return The flag for normalizing a numeric class.
+   */
+  public boolean getNormalizeNumericClass() {
+    return m_normalizeClass;
+  }
+
+  /**
+   * @param a True if the attributes should be normalized (even nominal
+   * attributes will get normalized here) (range goes between -1 - 1).
+   */
+  public void setNormalizeAttributes(boolean a) {
+    m_normalizeAttributes = a;
+  }
+
+  /**
+   * @return The flag for normalizing attributes.
+   */
+  public boolean getNormalizeAttributes() {
+    return m_normalizeAttributes;
+  }
+
+  /**
+   * @param f True if a nominalToBinary filter should be used on the
+   * data.
+   */
+  public void setNominalToBinaryFilter(boolean f) {
+    m_useNomToBin = f;
+  }
+
+  /**
+   * @return The flag for nominal to binary filter use.
+   */
+  public boolean getNominalToBinaryFilter() {
+    return m_useNomToBin;
+  }
+
+  /**
+   * This seeds the random number generator, that is used when a random
+   * number is needed for the network.
+   * @param l The seed.
+   */
+  public void setSeed(int l) {
+    if (l >= 0) {
+      m_randomSeed = l;
+    }
+  }
+  
+  /**
+   * @return The seed for the random number generator.
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+
+  /**
+   * This sets the threshold to use for when validation testing is being done.
+   * It works by ending testing once the error on the validation set has 
+   * consecutively increased a certain number of times.
+   * @param t The threshold to use for this.
+   */
+  public void setValidationThreshold(int t) {
+    if (t > 0) {
+      m_driftThreshold = t;
+    }
+  }
+
+  /**
+   * @return The threshold used for validation testing.
+   */
+  public int getValidationThreshold() {
+    return m_driftThreshold;
+  }
+  
+  /**
+   * The learning rate can be set using this command.
+   * NOTE That this is a static variable so it affect all networks that are
+   * running.
+   * Must be greater than 0 and no more than 1.
+   * @param l The New learning rate. 
+   */
+  public void setLearningRate(double l) {
+    if (l > 0 && l <= 1) {
+      m_learningRate = l;
+    
+      if (m_controlPanel != null) {
+	m_controlPanel.m_changeLearning.setText("" + l);
+      }
+    }
+  }
+
+  /**
+   * @return The learning rate for the nodes.
+   */
+  public double getLearningRate() {
+    return m_learningRate;
+  }
+
+  /**
+   * The momentum can be set using this command.
+   * THE same conditions apply to this as to the learning rate.
+   * @param m The new Momentum.
+   */
+  public void setMomentum(double m) {
+    if (m >= 0 && m <= 1) {
+      m_momentum = m;
+  
+      if (m_controlPanel != null) {
+	m_controlPanel.m_changeMomentum.setText("" + m);
+      }
+    }
+  }
+  
+  /**
+   * @return The momentum for the nodes.
+   */
+  public double getMomentum() {
+    return m_momentum;
+  }
+
+  /**
+   * This will set whether the network is automatically built
+   * or if it is left up to the user. (there is nothing to stop a user
+   * from altering an autobuilt network however). 
+   * @param a True if the network should be auto built.
+   */
+  public void setAutoBuild(boolean a) {
+    if (!m_gui) {
+      a = true;
+    }
+    m_autoBuild = a;
+  }
+
+  /**
+   * @return The auto build state.
+   */
+  public boolean getAutoBuild() {
+    return m_autoBuild;
+  }
+
+
+  /**
+   * This will set what the hidden layers are made up of when auto build is
+   * enabled. Note to have no hidden units, just put a single 0, Any more
+   * 0's will indicate that the string is badly formed and make it unaccepted.
+   * Negative numbers, and floats will do the same. There are also some
+   * wildcards. These are 'a' = (number of attributes + number of classes) / 2,
+   * 'i' = number of attributes, 'o' = number of classes, and 't' = number of
+   * attributes + number of classes.
+   * @param h A string with a comma seperated list of numbers. Each number is 
+   * the number of nodes to be on a hidden layer.
+   */
+  public void setHiddenLayers(String h) {
+    String tmp = "";
+    StringTokenizer tok = new StringTokenizer(h, ",");
+    if (tok.countTokens() == 0) {
+      return;
+    }
+    double dval;
+    int val;
+    String c;
+    boolean first = true;
+    while (tok.hasMoreTokens()) {
+      c = tok.nextToken().trim();
+
+      if (c.equals("a") || c.equals("i") || c.equals("o") || 
+	       c.equals("t")) {
+	tmp += c;
+      }
+      else {
+	dval = Double.valueOf(c).doubleValue();
+	val = (int)dval;
+	
+	if ((val == dval && (val != 0 || (tok.countTokens() == 0 && first)) && 
+	     val >= 0)) {
+	  tmp += val;
+	}
+	else {
+	  return;
+	}
+      }
+      
+      first = false;
+      if (tok.hasMoreTokens()) {
+	tmp += ", ";
+      }
+    }
+    m_hiddenLayers = tmp;
+  }
+
+  /**
+   * @return A string representing the hidden layers, each number is the number
+   * of nodes on a hidden layer.
+   */
+  public String getHiddenLayers() {
+    return m_hiddenLayers;
+  }
+
+  /**
+   * This will set whether A GUI is brought up to allow interaction by the user
+   * with the neural network during training.
+   * @param a True if gui should be created.
+   */
+  public void setGUI(boolean a) {
+    m_gui = a;
+    if (!a) {
+      setAutoBuild(true);
+      
+    }
+    else {
+      setReset(false);
+    }
+  }
+
+  /**
+   * @return The true if should show gui.
+   */
+  public boolean getGUI() {
+    return m_gui;
+  }
+
+  /**
+   * This will set the size of the validation set.
+   * @param a The size of the validation set, as a percentage of the whole.
+   */
+  public void setValidationSetSize(int a) {
+    if (a < 0 || a > 99) {
+      return;
+    }
+    m_valSize = a;
+  }
+
+  /**
+   * @return The percentage size of the validation set.
+   */
+  public int getValidationSetSize() {
+    return m_valSize;
+  }
+
+  
+  
+  
+  /**
+   * Set the number of training epochs to perform.
+   * Must be greater than 0.
+   * @param n The number of epochs to train through.
+   */
+  public void setTrainingTime(int n) {
+    if (n > 0) {
+      m_numEpochs = n;
+    }
+  }
+
+  /**
+   * @return The number of epochs to train through.
+   */
+  public int getTrainingTime() {
+    return m_numEpochs;
+  }
+  
+  /**
+   * Call this function to place a node into the network list.
+   * @param n The node to place in the list.
+   */
+  private void addNode(NeuralConnection n) {
+    
+    NeuralConnection[] temp1 = new NeuralConnection[m_neuralNodes.length + 1];
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      temp1[noa] = m_neuralNodes[noa];
+    }
+
+    temp1[temp1.length-1] = n;
+    m_neuralNodes = temp1;
+  }
+
+  /** 
+   * Call this function to remove the passed node from the list.
+   * This will only remove the node if it is in the neuralnodes list.
+   * @param n The neuralConnection to remove.
+   * @return True if removed false if not (because it wasn't there).
+   */
+  private boolean removeNode(NeuralConnection n) {
+    NeuralConnection[] temp1 = new NeuralConnection[m_neuralNodes.length - 1];
+    int skip = 0;
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      if (n == m_neuralNodes[noa]) {
+	skip++;
+      }
+      else if (!((noa - skip) >= temp1.length)) {
+	temp1[noa - skip] = m_neuralNodes[noa];
+      }
+      else {
+	return false;
+      }
+    }
+    m_neuralNodes = temp1;
+    return true;
+  }
+
+  /**
+   * This function sets what the m_numeric flag to represent the passed class
+   * it also performs the normalization of the attributes if applicable
+   * and sets up the info to normalize the class. (note that regardless of
+   * the options it will fill an array with the range and base, set to 
+   * normalize all attributes and the class to be between -1 and 1)
+   * @param inst the instances.
+   * @return The modified instances. This needs to be done. If the attributes
+   * are normalized then deep copies will be made of all the instances which
+   * will need to be passed back out.
+   */
+  private Instances setClassType(Instances inst) throws Exception {
+    if (inst != null) {
+      // x bounds
+      double min=Double.POSITIVE_INFINITY;
+      double max=Double.NEGATIVE_INFINITY;
+      double value;
+      m_attributeRanges = new double[inst.numAttributes()];
+      m_attributeBases = new double[inst.numAttributes()];
+      for (int noa = 0; noa < inst.numAttributes(); noa++) {
+	min = Double.POSITIVE_INFINITY;
+	max = Double.NEGATIVE_INFINITY;
+	for (int i=0; i < inst.numInstances();i++) {
+	  if (!inst.instance(i).isMissing(noa)) {
+	    value = inst.instance(i).value(noa);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+	
+	m_attributeRanges[noa] = (max - min) / 2;
+	m_attributeBases[noa] = (max + min) / 2;
+        //dmod
+    /*    System.out.println("Attribute " + noa + " Range: "  + m_attributeRanges[noa]);
+        System.out.println("Attribute " + noa + " Bases: " + m_attributeBases[noa]);
+        System.out.println(); */
+        
+        //Nominal class; hardcode base and range
+ /*       if(noa == inst.classIndex() && !inst.classAttribute().isNumeric())
+        {
+            m_attributeRanges[noa] = 0.4;
+            m_attributeBases[noa] = 0.5;
+            System.out.println(); 
+            System.out.println(); 
+            System.out.println("Nominal attribute detected; hardcoding range and base.");            
+        } */
+        
+	if (noa != inst.classIndex() && m_normalizeAttributes) {
+	  for (int i = 0; i < inst.numInstances(); i++) {
+	    if (m_attributeRanges[noa] != 0) {
+	      inst.instance(i).setValue(noa, (inst.instance(i).value(noa)  
+					      - m_attributeBases[noa]) /
+					m_attributeRanges[noa]);
+	    }
+	    else {
+	      inst.instance(i).setValue(noa, inst.instance(i).value(noa) - 
+					m_attributeBases[noa]);
+	    }
+	  }
+	}
+      }
+      if (inst.classAttribute().isNumeric()) {
+	m_numeric = true;
+      }
+      else {
+	m_numeric = false;
+        //warningmod
+        //m_numeric = true;
+      }
+    }
+    return inst;
+  }
+
+  /**
+   * A function used to stop the code that called buildclassifier
+   * from continuing on before the user has finished the decision tree.
+   * @param tf True to stop the thread, False to release the thread that is
+   * waiting there (if one).
+   */
+  public synchronized void blocker(boolean tf) {
+    if (tf) {
+      try {
+	wait();
+      } catch(InterruptedException e) {
+      }
+    }
+    else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Call this function to update the control panel for the gui.
+   */
+  private void updateDisplay() {
+    
+    if (m_gui) {
+      m_controlPanel.m_errorLabel.repaint();
+      m_controlPanel.m_epochsLabel.repaint();
+      //modCS.E
+      m_controlPanel.m_lowValErrorLabel.repaint();
+      m_controlPanel.m_epochIndexLabel.repaint();      
+    }
+  }
+  
+
+  /**
+   * this will reset all the nodes in the network.
+   */
+  private void resetNetwork() {
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      m_outputs[noc].reset();
+    }
+  }
+  
+  /**
+   * This will cause the output values of all the nodes to be calculated.
+   * Note that the m_currentInstance is used to calculate these values.
+   */
+  private void calculateOutputs() {
+    for (int noc = 0; noc < m_numClasses; noc++) {	
+      //get the values. 
+      m_outputs[noc].outputValue(true);
+    }
+  }
+
+  /**
+   * This will cause the error values to be calculated for all nodes.
+   * Note that the m_currentInstance is used to calculate these values.
+   * Also the output values should have been calculated first.
+   * @return The squared error.
+   */
+  private double calculateErrors() throws Exception {
+    double ret = 0, temp = 0; 
+    for (int noc = 0; noc < m_numAttributes; noc++) {
+      //get the errors.
+      m_inputs[noc].errorValue(true);
+      
+    }
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      temp = m_outputs[noc].errorValue(false);
+      ret += temp * temp;
+    }    
+    return ret;
+    
+  }
+
+  /**
+   * This will cause the weight values to be updated based on the learning
+   * rate, momentum and the errors that have been calculated for each node.
+   * @param l The learning rate to update with.
+   * @param m The momentum to update with.
+   */
+  private void updateNetworkWeights(double l, double m) {
+    for (int noc = 0; noc < m_numClasses; noc++) {
+      //update weights
+      m_outputs[noc].updateWeights(l, m);
+    }
+
+  }
+  
+  /**
+   * This creates the required input units.
+   */
+  private void setupInputs() throws Exception {
+    m_inputs = new NeuralEnd[m_numAttributes];
+    int now = 0;
+    for (int noa = 0; noa < m_numAttributes+1; noa++) {
+      if (m_instances.classIndex() != noa) {
+	m_inputs[noa - now] = new NeuralEnd(m_instances.attribute(noa).name());
+	
+	m_inputs[noa - now].setX(.1);
+	m_inputs[noa - now].setY((noa - now + 1.0) / (m_numAttributes + 1));
+	m_inputs[noa - now].setLink(true, noa);
+      }    
+      else {
+	now = 1;
+      }
+    }
+
+  }
+
+  /**
+   * This creates the required output units.
+   */
+  private void setupOutputs() throws Exception {
+  
+    m_outputs = new NeuralEnd[m_numClasses];
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      if (m_numeric) {
+	m_outputs[noa] = new NeuralEnd(m_instances.classAttribute().name());
+      }
+      else {
+	m_outputs[noa]= new NeuralEnd(m_instances.classAttribute().value(noa));
+      }
+      
+      m_outputs[noa].setX(.9);
+      m_outputs[noa].setY((noa + 1.0) / (m_numClasses + 1));
+      m_outputs[noa].setLink(false, noa);
+      NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,
+				       m_sigmoidUnit);
+      m_nextId++;
+      temp.setX(.75);
+      temp.setY((noa + 1.0) / (m_numClasses + 1));
+      addNode(temp);
+      NeuralConnection.connect(temp, m_outputs[noa]);
+    }
+ 
+  }
+  
+  /**
+   * Call this function to automatically generate the hidden units
+   */
+  private void setupHiddenLayer()
+  {
+    StringTokenizer tok = new StringTokenizer(m_hiddenLayers, ",");
+    int val = 0;  //num of nodes in a layer
+    int prev = 0; //used to remember the previous layer
+    int num = tok.countTokens(); //number of layers
+    String c;
+    for (int noa = 0; noa < num; noa++) {
+      //note that I am using the Double to get the value rather than the
+      //Integer class, because for some reason the Double implementation can
+      //handle leading white space and the integer version can't!?!
+      c = tok.nextToken().trim();
+      if (c.equals("a")) {
+	val = (m_numAttributes + m_numClasses) / 2;
+      }
+      else if (c.equals("i")) {
+	val = m_numAttributes;
+      }
+      else if (c.equals("o")) {
+	val = m_numClasses;
+      }
+      else if (c.equals("t")) {
+	val = m_numAttributes + m_numClasses;
+      }
+      else {
+	val = Double.valueOf(c).intValue();
+      }
+      for (int nob = 0; nob < val; nob++) {
+	NeuralNode temp = new NeuralNode(String.valueOf(m_nextId), m_random,
+					 m_sigmoidUnit);
+	m_nextId++;
+	temp.setX(.5 / (num) * noa + .25);
+	temp.setY((nob + 1.0) / (val + 1));
+	addNode(temp);
+	if (noa > 0) {
+	  //then do connections
+	  for (int noc = m_neuralNodes.length - nob - 1 - prev;
+	       noc < m_neuralNodes.length - nob - 1; noc++) {
+	    NeuralConnection.connect(m_neuralNodes[noc], temp);
+	  }
+	}
+      }      
+      prev = val;
+    }
+    tok = new StringTokenizer(m_hiddenLayers, ",");
+    c = tok.nextToken();
+    if (c.equals("a")) {
+      val = (m_numAttributes + m_numClasses) / 2;
+    }
+    else if (c.equals("i")) {
+      val = m_numAttributes;
+    }
+    else if (c.equals("o")) {
+      val = m_numClasses;
+    }
+    else if (c.equals("t")) {
+      val = m_numAttributes + m_numClasses;
+    }
+    else {
+      val = Double.valueOf(c).intValue();
+    }
+    
+    if (val == 0) {
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	for (int nob = 0; nob < m_numClasses; nob++) {
+	  NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
+	}
+      }
+    }
+    else {
+      for (int noa = 0; noa < m_numAttributes; noa++) {
+	for (int nob = m_numClasses; nob < m_numClasses + val; nob++) {
+	  NeuralConnection.connect(m_inputs[noa], m_neuralNodes[nob]);
+	}
+      }
+      for (int noa = m_neuralNodes.length - prev; noa < m_neuralNodes.length;
+	   noa++) {
+	for (int nob = 0; nob < m_numClasses; nob++) {
+	  NeuralConnection.connect(m_neuralNodes[noa], m_neuralNodes[nob]);
+	}
+      }
+    }
+    
+  }
+  
+  /**
+   * This will go through all the nodes and check if they are connected
+   * to a pure output unit. If so they will be set to be linear units.
+   * If not they will be set to be sigmoid units.
+   */
+  private void setEndsToLinear() {
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      if ((m_neuralNodes[noa].getType() & NeuralConnection.OUTPUT) ==
+	  NeuralConnection.OUTPUT) {
+	((NeuralNode)m_neuralNodes[noa]).setMethod(m_linearUnit);
+      }
+      else {
+	((NeuralNode)m_neuralNodes[noa]).setMethod(m_sigmoidUnit);
+      }
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Call this function to build and train a neural network for the training
+   * data provided.
+   * @param i The training data.
+   * @throws Exception if can't build classification properly.
+   */
+  public void buildClassifier(Instances i) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(i);
+    
+    i = new Instances(i);
+    
+    //modCS.S Moved randomizer up, so only order of primary data is randomized.    
+    m_random = new Random(m_randomSeed);
+    i.randomize(m_random);
+    
+    //modCS.S, add secondary instances to primary instances, and replicate 
+    //primaries to match the number of secondaries.
+    int m_originalNumInstances = i.numInstances();    
+    if (m_secSet != null)
+    {
+        if (!m_secSet.equalHeaders(i))
+        {
+            throw new Exception("Training and secondary sets "
+                + "have different headers.");
+        }
+        
+        int m_numSecondaries = 0;
+        //calculate how many instances there are for each secondary task.
+        for (int noa = 0; noa < m_secSet.numInstances(); noa++)
+        {
+            //1 indicates the index of the attribute
+            if(m_secSet.instance(noa).value(1) == 0)
+            {              
+            }
+            else if(m_secSet.instance(noa).value(1) == 1)
+            {
+                m_numSecondaries++;
+            }
+            else
+            {
+                throw new Exception("Cannot find appropriate secondary task " +
+                        "attribute(s).");
+            }
+        }
+        
+        if(m_secSet.numInstances() < m_originalNumInstances)
+        {
+            throw new Exception("Secondary task training set has less " +
+                    "instances than the primary training set.");            
+        }
+        
+        /* Do not replicate primaries to be used in a percentage
+           validation set. Duplicates the remaining primaries. */         
+        if (m_valSize > 0)
+        {
+            int exclude = 0;
+            int counter = 0;
+            
+            exclude = (int)(m_valSize / 100.0 * (m_originalNumInstances));
+            if(exclude == 0)
+                exclude = 1;
+            
+            while(i.numInstances() < m_numSecondaries + exclude)
+            {
+                if(counter % m_originalNumInstances >= exclude)
+                {
+                    i.add(i.instance(counter % m_originalNumInstances));
+                    counter++;
+                }
+                else
+                {
+                    counter+=exclude;
+                }
+            }
+            
+            
+        }
+        else
+        {
+            //Replicate the primaries to match the number of secondaries.
+            for(int noa= m_originalNumInstances; noa<m_numSecondaries; noa++)
+            {
+                i.add(i.instance(noa % m_originalNumInstances));
+            }
+            
+        }
+        //add the secondaries
+        for (int noa = 0; noa < m_secSet.numInstances(); noa++)
+        {
+            i.add(m_secSet.instance(noa));
+        }
+    }    
+    // remove instances with missing class
+    i.deleteWithMissingClass();
+           
+    // only class? -> build ZeroR model
+    if (i.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(i);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_epoch = 0;
+    m_error = 0;
+    m_instances = null;
+    m_currentInstance = null;
+    m_controlPanel = null;
+    m_nodePanel = null;
+    
+    
+    m_outputs = new NeuralEnd[0];
+    m_inputs = new NeuralEnd[0];
+    m_numAttributes = 0;
+    m_numClasses = 0;
+    m_neuralNodes = new NeuralConnection[0];
+    
+    m_selected = new FastVector(4);
+    m_graphers = new FastVector(2);
+    m_nextId = 0;
+    m_stopIt = true;
+    m_stopped = true;
+    m_accepted = false;    
+    m_instances = new Instances(i);
+      
+    //modCS.S Moved randomizer up, so only order of primary data is randomized.    
+ 
+    if (m_useNomToBin) 
+    {
+      m_nominalToBinaryFilter = new NominalToBinary();
+      m_nominalToBinaryFilter.setInputFormat(m_instances);
+      m_instances = Filter.useFilter(m_instances,
+				     m_nominalToBinaryFilter);
+      //modCS.V
+      //Run the nominal to binary filter on the validation set, if applicable.
+      if(m_valSet != null)
+      {
+          m_nominalToBinaryFilter.setInputFormat(m_valSet);
+          m_valSet = Filter.useFilter(
+                m_valSet, m_nominalToBinaryFilter);    
+      }
+    }
+    m_numAttributes = m_instances.numAttributes() - 1;
+    m_numClasses = m_instances.numClasses();
+ 
+    
+    setClassType(m_instances);
+    //modCS.V
+    //Run the setClassType procedure on the validation set, as well.
+    if(m_valSet != null)
+        setClassType(m_valSet);
+
+    //modCS.V
+    //Prepare validation instances using either the specified validation set or
+    //using a percentage of the training examples.
+    Instances valSet;
+    int numInVal = 0;
+    //this sets up the validation set.
+    if(m_valSet == null)//sets up a validation set using some primary instances.
+    {
+        valSet = null;
+        //numinval is needed later
+        //modCS.S; properly count the total number of instances if a secondary
+        //set is included.
+        if(m_secSet != null)
+        {
+            numInVal = (int)(m_valSize / 100.0 * (m_originalNumInstances));
+        }
+        else
+        {
+            numInVal = (int)(m_valSize / 100.0 * m_instances.numInstances());
+        }
+        if (m_valSize > 0) {
+          if (numInVal == 0) {
+            numInVal = 1;
+          }
+          valSet = new Instances(m_instances, 0, numInVal);
+        }
+        
+        //debug; see the training and validation sets
+ /*       System.out.println("\n\n\nTraining Set; ");
+        for(int noa = 0; noa < i.numInstances(); noa++)
+            System.out.println(i.instance(noa));
+        
+        System.out.println("\n\n\n\nValidation Set");
+        for(int noa = 0; noa < valSet.numInstances(); noa++)
+            System.out.println(valSet.instance(noa));        
+        
+      */  
+        ///////////        
+    }
+    else //sets up a specified validation set
+    {
+        valSet = new Instances(m_valSet);
+        numInVal = valSet.numInstances();
+        valSet.deleteWithMissingClass();
+        valSet.setClassIndex(m_instances.classIndex());
+        if (!m_instances.equalHeaders(valSet))
+        {
+            throw new Exception("Training and validation sets "
+                + "have different headers.");
+        }
+        setClassType(valSet);
+        if (m_valSize != 0)
+        {
+            throw new Exception("Given both a validation set size split and" 
+                + "a specified validation set. Use only one of either.");
+        }
+    }
+
+    setupInputs();
+      
+    setupOutputs();    
+    if (m_autoBuild) {
+      setupHiddenLayer();
+    }
+    
+    /////////////////////////////
+    //this sets up the gui for usage
+    if (m_gui) {
+      m_win = new JFrame();
+      
+      m_win.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    boolean k = m_stopIt;
+	    m_stopIt = true;
+	    int well =JOptionPane.showConfirmDialog(m_win, 
+						    "Are You Sure...\n"
+						    + "Click Yes To Accept"
+						    + " The Neural Network" 
+						    + "\n Click No To Return",
+						    "Accept Neural Network", 
+						    JOptionPane.YES_NO_OPTION);
+	    
+	    if (well == 0) {
+	      m_win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+	      m_accepted = true;
+	      blocker(false);
+	    }
+	    else {
+	      m_win.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+	    }
+	    m_stopIt = k;
+	  }
+	});
+      
+      m_win.getContentPane().setLayout(new BorderLayout());
+      m_win.setTitle("Neural Network");
+      m_nodePanel = new NodePanel();
+      // without the following two lines, the NodePanel.paintComponents(Graphics) 
+      // method will go berserk if the network doesn't fit completely: it will
+      // get called on a constant basis, using 100% of the CPU
+      // see the following forum thread:
+      // http://forum.java.sun.com/thread.jspa?threadID=580929&messageID=2945011
+      m_nodePanel.setPreferredSize(new Dimension(640, 480));
+      m_nodePanel.revalidate();
+
+      JScrollPane sp = new JScrollPane(m_nodePanel,
+				       JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
+				       JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+      m_controlPanel = new ControlPanel();
+           
+      m_win.getContentPane().add(sp, BorderLayout.CENTER);
+      m_win.getContentPane().add(m_controlPanel, BorderLayout.SOUTH);
+      m_win.setSize(640, 480);
+      m_win.setVisible(true);
+    }
+   
+    //This sets up the initial state of the gui
+    if (m_gui) {
+      blocker(true);
+      m_controlPanel.m_changeEpochs.setEnabled(false);
+      m_controlPanel.m_changeLearning.setEnabled(false);
+      m_controlPanel.m_changeMomentum.setEnabled(false);
+    } 
+    
+    //For silly situations in which the network gets accepted before training
+    //commenses
+    if (m_numeric) {
+      setEndsToLinear();
+    }
+    if (m_accepted) {
+      m_win.dispose();
+      m_controlPanel = null;
+      m_nodePanel = null;
+      m_instances = new Instances(m_instances, 0);
+      return;
+    }
+
+    //connections done.
+    double right = 0;
+    double driftOff = 0;
+    double lastRight = Double.POSITIVE_INFINITY;
+    double bestError = Double.POSITIVE_INFINITY;
+    double tempRate;
+    double totalWeight = 0;
+    double totalValWeight = 0;
+    double origRate = m_learningRate; //only used for when reset
+    
+    //ensure that at least 1 instance is trained through.
+    if (numInVal == m_instances.numInstances()) {
+      numInVal--;
+    }
+    if (numInVal < 0) {
+      numInVal = 0;
+    }
+    //modCS.V; utilizes either the specified training set or one constructed
+    //using the percentage of the primary training set.
+    if (m_valSet == null)
+    {
+        for (int noa = numInVal; noa < m_instances.numInstances(); noa++) {
+          if (!m_instances.instance(noa).classIsMissing()) {
+            totalWeight += m_instances.instance(noa).weight();
+          }
+        }
+        if (m_valSize != 0) {
+          for (int noa = 0; noa < valSet.numInstances(); noa++) {
+            if (!valSet.instance(noa).classIsMissing()) {
+              totalValWeight += valSet.instance(noa).weight();
+            }
+          }
+        }
+        m_stopped = false;
+    }
+    else
+    {
+        for (int noa = 0; noa < m_instances.numInstances(); noa++)
+        {
+                if (!m_instances.instance(noa).classIsMissing())
+                {
+                        totalWeight += m_instances.instance(noa).weight();
+                }
+        }
+        if (valSet.numInstances() != 0)
+        {
+                for (int noa = 0; noa < valSet.numInstances(); noa++)
+                {
+                        if (!valSet.instance(noa).classIsMissing())
+                        {
+                                totalValWeight += valSet.instance(noa).weight();
+                        }
+                }
+        }    
+    }
+
+    for (int noa = 1; noa < m_numEpochs + 1; noa++) {
+      right = 0;
+      for (int nob = numInVal; nob < m_instances.numInstances(); nob++) {
+	m_currentInstance = m_instances.instance(nob);
+	
+	if (!m_currentInstance.classIsMissing()) {
+	   
+	  //this is where the network updating (and training occurs, for the
+	  //training set
+	  resetNetwork();
+	  calculateOutputs();
+	  tempRate = m_learningRate * m_currentInstance.weight();  
+	  if (m_decay) {
+	    tempRate /= noa;
+	  }
+
+	  right += (calculateErrors() / m_instances.numClasses()) *
+	    m_currentInstance.weight();
+	  updateNetworkWeights(tempRate, m_momentum);
+	  
+	}
+	
+      }
+      right /= totalWeight;
+      if (Double.isInfinite(right) || Double.isNaN(right)) {
+	if (!m_reset) {
+	  m_instances = null;
+	  throw new Exception("Network cannot train. Try restarting with a" +
+			      " smaller learning rate.");
+	}
+	else {
+	  //reset the network if possible
+	  if (m_learningRate <= Utils.SMALL)
+	    throw new IllegalStateException(
+		"Learning rate got too small (" + m_learningRate 
+		+ " <= " + Utils.SMALL + ")!");
+	  m_learningRate /= 2;
+	  buildClassifier(i);
+	  m_learningRate = origRate;
+	  m_instances = new Instances(m_instances, 0);	  
+	  return;
+	}
+      }
+
+      ////////////////////////do validation testing if applicable
+      
+      //modCS.V Recent change for validation calcs
+      if (m_valSize != 0 || m_valSet != null) {
+	right = 0;
+	for (int nob = 0; nob < valSet.numInstances(); nob++) {
+	  m_currentInstance = valSet.instance(nob);
+	  if (!m_currentInstance.classIsMissing()) {
+	    //this is where the network updating occurs, for the validation set
+	    resetNetwork();
+	    calculateOutputs();
+	    right += (calculateErrors() / valSet.numClasses()) 
+	      * m_currentInstance.weight();
+	    //note 'right' could be calculated here just using
+	    //the calculate output values. This would be faster.
+	    //be less modular
+	  }
+	  
+	}
+	//mod? If an epoch has less error then the previous, validation
+        //testing won't end even if the threshold is crossed. Consider
+        //noting this in the description of the validation threshold, or
+        //changing this to function as the description currently states.
+	if (right < lastRight) {
+	  if (right < bestError) {
+	    bestError = right;
+            //modCS.E; Calculate the lowest validation error and save its index.
+            m_lowValError = right / totalValWeight;
+            m_epochIndex = noa;
+            
+	    // save the network weights at this point
+	    for (int noc = 0; noc < m_numClasses; noc++) {
+	      m_outputs[noc].saveWeights();
+	    }
+	    driftOff = 0;
+	  }
+	}
+	else {
+	  driftOff++;
+	}
+	lastRight = right;
+	if (driftOff > m_driftThreshold || noa + 1 >= m_numEpochs) {
+	  for (int noc = 0; noc < m_numClasses; noc++) {
+            m_outputs[noc].restoreWeights();
+          }
+	  m_accepted = true;
+	}
+	right /= totalValWeight;
+      }
+      m_epoch = noa;
+      m_error = right;
+      //shows what the neuralnet is upto if a gui exists. 
+      updateDisplay();
+      //This junction controls what state the gui is in at the end of each
+      //epoch, Such as if it is paused, if it is resumable etc...
+      //modCS.V; Extended conditional statements to consider supplied val sets.
+      if (m_gui) {
+	while ((m_stopIt || (m_epoch >= m_numEpochs && (m_valSize == 0 
+                && m_valSet == null))) && !m_accepted) {
+	  m_stopIt = true;
+	  m_stopped = true;
+	  if (m_epoch >= m_numEpochs && (m_valSize == 0 && m_valSet == null)) {
+	    
+	    m_controlPanel.m_startStop.setEnabled(false);
+	  }
+	  else {
+	    m_controlPanel.m_startStop.setEnabled(true);
+	  }
+	  m_controlPanel.m_startStop.setText("Start");
+	  m_controlPanel.m_startStop.setActionCommand("Start");
+	  m_controlPanel.m_changeEpochs.setEnabled(true);
+	  m_controlPanel.m_changeLearning.setEnabled(true);
+	  m_controlPanel.m_changeMomentum.setEnabled(true);
+	  
+	  blocker(true);
+	  if (m_numeric) {
+	    setEndsToLinear();
+	  }
+	}
+	m_controlPanel.m_changeEpochs.setEnabled(false);
+	m_controlPanel.m_changeLearning.setEnabled(false);
+	m_controlPanel.m_changeMomentum.setEnabled(false);
+	
+	m_stopped = false;
+	//if the network has been accepted stop the training loop
+	if (m_accepted) {
+	  m_win.dispose();
+	  m_controlPanel = null;
+	  m_nodePanel = null;
+	  m_instances = new Instances(m_instances, 0);
+	  return;
+	}
+      }
+      if (m_accepted) {
+	m_instances = new Instances(m_instances, 0);
+	return;
+      }
+    }
+    if (m_gui) {
+      m_win.dispose();
+      m_controlPanel = null;
+      m_nodePanel = null;
+    }
+    m_instances = new Instances(m_instances, 0);  
+  }
+
+  /**
+   * Call this function to predict the class of an instance once a 
+   * classification model has been built with the buildClassifier call.
+   * @param i The instance to classify.
+   * @return A double array filled with the probabilities of each class type.
+   * @throws Exception if can't classify instance.
+   */
+  public double[] distributionForInstance(Instance i) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(i);
+    }
+    
+    if (m_useNomToBin) {
+      m_nominalToBinaryFilter.input(i);
+      m_currentInstance = m_nominalToBinaryFilter.output();
+    }
+    else {
+      m_currentInstance = i;
+    }
+    
+    if (m_normalizeAttributes) {
+      for (int noa = 0; noa < m_instances.numAttributes(); noa++) {
+	if (noa != m_instances.classIndex()) {
+	  if (m_attributeRanges[noa] != 0) {
+	    m_currentInstance.setValue(noa, (m_currentInstance.value(noa) - 
+					     m_attributeBases[noa]) / 
+				       m_attributeRanges[noa]);
+	  }
+	  else {
+	    m_currentInstance.setValue(noa, m_currentInstance.value(noa) -
+				       m_attributeBases[noa]);
+	  }
+	}
+      }
+    }
+    resetNetwork();
+    
+    //since all the output values are needed.
+    //They are calculated manually here and the values collected.
+    double[] theArray = new double[m_numClasses];
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      theArray[noa] = m_outputs[noa].outputValue(true);
+    }
+    if (m_instances.classAttribute().isNumeric()) {
+      return theArray;
+    }
+    
+    //now normalize the array
+    double count = 0;
+    for (int noa = 0; noa < m_numClasses; noa++) {
+      count += theArray[noa];
+    }
+  //  System.out.println("Count: " + count + ".");
+    if (count <= 0) {
+      return null;
+    }
+    for (int noa = 0; noa < m_numClasses; noa++) {
+        //dmod
+  //      System.out.println("Array " + noa +":" + theArray[noa]);
+      theArray[noa] /= count;
+   //   System.out.println(" and " + theArray[noa] +".");      
+    }
+  //  System.out.println(); System.out.println(); System.out.println();
+    return theArray;
+  }
+  
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    //modCS.S & modCS.V;
+    //needed more vectors for the increased number of parameters
+    Vector newVector = new Vector(16);
+
+    newVector.addElement(new Option(
+	      "\tLearning Rate for the backpropagation algorithm.\n"
+	      +"\t(Value should be between 0 - 1, Default = 0.3).",
+	      "L", 1, "-L <learning rate>"));
+    newVector.addElement(new Option(
+	      "\tMomentum Rate for the backpropagation algorithm.\n"
+	      +"\t(Value should be between 0 - 1, Default = 0.2).",
+	      "M", 1, "-M <momentum>"));
+    newVector.addElement(new Option(
+	      "\tNumber of epochs to train through.\n"
+	      +"\t(Default = 500).",
+	      "N", 1,"-N <number of epochs>"));
+    newVector.addElement(new Option(
+	      "\tPercentage size of validation set to use to terminate\n"
+	      + "\ttraining (if this is non zero it can pre-empt num of epochs.\n"
+	      +"\t(Value should be between 0 - 100, Default = 0).",
+	      "V", 1, "-V <percentage size of validation set>"));
+    newVector.addElement(new Option(
+	      "\tThe value used to seed the random number generator\n"
+	      + "\t(Value should be >= 0 and and a long, Default = 0).",
+	      "S", 1, "-S <seed>"));
+    newVector.addElement(new Option(
+	      "\tThe consequetive number of errors allowed for validation\n"
+	      + "\ttesting before the netwrok terminates.\n"
+	      + "\t(Value should be > 0, Default = 20).",
+	      "E", 1, "-E <threshold for number of consequetive errors>"));
+    newVector.addElement(new Option(
+              "\tGUI will be opened.\n"
+	      +"\t(Use this to bring up a GUI).",
+	      "G", 0,"-G"));
+    newVector.addElement(new Option(
+              "\tAutocreation of the network connections will NOT be done.\n"
+	      +"\t(This will be ignored if -G is NOT set)",
+	      "A", 0,"-A"));
+    newVector.addElement(new Option(
+              "\tA NominalToBinary filter will NOT automatically be used.\n"
+	      +"\t(Set this to not use a NominalToBinary filter).",
+	      "B", 0,"-B"));
+    newVector.addElement(new Option(
+	      "\tThe hidden layers to be created for the network.\n"
+	      + "\t(Value should be a list of comma separated Natural \n"
+	      + "\tnumbers or the letters 'a' = (attribs + classes) / 2, \n"
+	      + "\t'i' = attribs, 'o' = classes, 't' = attribs .+ classes)\n"
+	      + "\tfor wildcard values, Default = a).",
+	      "H", 1, "-H <comma seperated numbers for nodes on each layer>"));
+    newVector.addElement(new Option(
+              "\tNormalizing a numeric class will NOT be done.\n"
+	      +"\t(Set this to not normalize the class if it's numeric).",
+	      "C", 0,"-C"));
+    newVector.addElement(new Option(
+              "\tNormalizing the attributes will NOT be done.\n"
+	      +"\t(Set this to not normalize the attributes).",
+	      "I", 0,"-I"));
+    newVector.addElement(new Option(
+              "\tReseting the network will NOT be allowed.\n"
+	      +"\t(Set this to not allow the network to reset).",
+	      "R", 0,"-R"));
+    newVector.addElement(new Option(
+              "\tLearning rate decay will occur.\n"
+	      +"\t(Set this to cause the learning rate to decay).",
+	      "D", 0,"-D"));
+    //modCS.V
+    newVector.addElement(new Option("\tValidation set to use, " +
+            " as drawn from the data source file.\n",
+            "validation-set", 1, "-validation-set <data source file>"));
+    //modCS.S
+    newVector.addElement(new Option("\tSecondary task training set to use," +
+            " as drawn from the data source file.\n",
+            "secondary-training", 1, "-secondary-training <data source file>"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L &lt;learning rate&gt;
+   *  Learning Rate for the backpropagation algorithm.
+   *  (Value should be between 0 - 1, Default = 0.3).</pre>
+   * 
+   * <pre> -M &lt;momentum&gt;
+   *  Momentum Rate for the backpropagation algorithm.
+   *  (Value should be between 0 - 1, Default = 0.2).</pre>
+   * 
+   * <pre> -N &lt;number of epochs&gt;
+   *  Number of epochs to train through.
+   *  (Default = 500).</pre>
+   * 
+   * <pre> -V &lt;percentage size of validation set&gt;
+   *  Percentage size of validation set to use to terminate
+   *  training (if this is non zero it can pre-empt num of epochs.
+   *  (Value should be between 0 - 100, Default = 0).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  The value used to seed the random number generator
+   *  (Value should be &gt;= 0 and and a long, Default = 0).</pre>
+   * 
+   * <pre> -E &lt;threshold for number of consequetive errors&gt;
+   *  The consequetive number of errors allowed for validation
+   *  testing before the netwrok terminates.
+   *  (Value should be &gt; 0, Default = 20).</pre>
+   * 
+   * <pre> -G
+   *  GUI will be opened.
+   *  (Use this to bring up a GUI).</pre>
+   * 
+   * <pre> -A
+   *  Autocreation of the network connections will NOT be done.
+   *  (This will be ignored if -G is NOT set)</pre>
+   * 
+   * <pre> -B
+   *  A NominalToBinary filter will NOT automatically be used.
+   *  (Set this to not use a NominalToBinary filter).</pre>
+   * 
+   * <pre> -H &lt;comma seperated numbers for nodes on each layer&gt;
+   *  The hidden layers to be created for the network.
+   *  (Value should be a list of comma separated Natural 
+   *  numbers or the letters 'a' = (attribs + classes) / 2, 
+   *  'i' = attribs, 'o' = classes, 't' = attribs .+ classes)
+   *  for wildcard values, Default = a).</pre>
+   * 
+   * <pre> -C
+   *  Normalizing a numeric class will NOT be done.
+   *  (Set this to not normalize the class if it's numeric).</pre>
+   * 
+   * <pre> -I
+   *  Normalizing the attributes will NOT be done.
+   *  (Set this to not normalize the attributes).</pre>
+   * 
+   * <pre> -R
+   *  Reseting the network will NOT be allowed.
+   *  (Set this to not allow the network to reset).</pre>
+   * 
+   * <pre> -D
+   *  Learning rate decay will occur.
+   *  (Set this to cause the learning rate to decay).</pre>
+   * 
+   * <pre> -validation-set &lt;data source file&gt;
+   *  Validation set to use,  as drawn from the data source file.
+   * </pre>
+   * 
+   * <pre> -secondary-training &lt;data source file&gt;
+   *  Secondary task training set to use, as drawn from the data source file.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    //the defaults can be found here!!!!
+    String learningString = Utils.getOption('L', options);
+    if (learningString.length() != 0) {
+      setLearningRate((new Double(learningString)).doubleValue());
+    } else {
+      setLearningRate(0.3);
+    }
+    String momentumString = Utils.getOption('M', options);
+    if (momentumString.length() != 0) {
+      setMomentum((new Double(momentumString)).doubleValue());
+    } else {
+      setMomentum(0.2);
+    }
+    String epochsString = Utils.getOption('N', options);
+    if (epochsString.length() != 0) {
+      setTrainingTime(Integer.parseInt(epochsString));
+    } else {
+      setTrainingTime(500);
+    }
+    String valSizeString = Utils.getOption('V', options);
+    if (valSizeString.length() != 0) {
+      setValidationSetSize(Integer.parseInt(valSizeString));
+    } else {
+      setValidationSetSize(0);
+    }
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setSeed(Integer.parseInt(seedString));
+    } else {
+      setSeed(0);
+    }
+    String thresholdString = Utils.getOption('E', options);
+    if (thresholdString.length() != 0) {
+      setValidationThreshold(Integer.parseInt(thresholdString));
+    } else {
+      setValidationThreshold(20);
+    }
+    String hiddenLayers = Utils.getOption('H', options);
+    if (hiddenLayers.length() != 0) {
+      setHiddenLayers(hiddenLayers);
+    } else {
+      setHiddenLayers("a");
+    }
+    if (Utils.getFlag('G', options)) {
+      setGUI(true);
+    } else {
+      setGUI(false);
+    } //small note. since the gui is the only option that can change the other
+    //options this should be set first to allow the other options to set 
+    //properly
+    if (Utils.getFlag('A', options)) {
+      setAutoBuild(false);
+    } else {
+      setAutoBuild(true);
+    }
+    if (Utils.getFlag('B', options)) {
+      setNominalToBinaryFilter(false);
+    } else {
+      setNominalToBinaryFilter(true);
+    }
+    if (Utils.getFlag('C', options)) {
+      setNormalizeNumericClass(false);
+    } else {
+      setNormalizeNumericClass(true);
+    }
+    if (Utils.getFlag('I', options)) {
+      setNormalizeAttributes(false);
+    } else {
+      setNormalizeAttributes(true);
+    }
+    if (Utils.getFlag('R', options)) {
+      setReset(false);
+    } else {
+      setReset(true);
+    }
+    if (Utils.getFlag('D', options)) {
+      setDecay(true);
+    } else {
+      setDecay(false);
+    }
+
+    //modCS.V
+    String sValFile = Utils.getOption("validation-set", options);
+    if (sValFile != null && !sValFile.equals("")) {
+      setValFile(sValFile);
+    }
+
+    //modCS.S
+    String sSecFile = Utils.getOption("secondary-training", options);
+    if (sSecFile != null && !sSecFile.equals("")) {
+      setSecFile(sSecFile);
+    }    
+    
+    Utils.checkForRemainingOptions(options);
+  }
+  
+  /**
+   * Gets the current settings of NeuralNet.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+    //modCS.S & modCS.V; more options, so we need a larger array.
+    String [] options = new String [21];
+    int current = 0;
+    options[current++] = "-L"; options[current++] = "" + getLearningRate(); 
+    options[current++] = "-M"; options[current++] = "" + getMomentum();
+    options[current++] = "-N"; options[current++] = "" + getTrainingTime(); 
+    options[current++] = "-V"; options[current++] = "" +getValidationSetSize();
+    options[current++] = "-S"; options[current++] = "" + getSeed();
+    options[current++] = "-E"; options[current++] =""+getValidationThreshold();
+    options[current++] = "-H"; options[current++] = getHiddenLayers();
+    if (getGUI()) {
+      options[current++] = "-G";
+    }
+    if (!getAutoBuild()) {
+      options[current++] = "-A";
+    }
+    if (!getNominalToBinaryFilter()) {
+      options[current++] = "-B";
+    }
+    if (!getNormalizeNumericClass()) {
+      options[current++] = "-C";
+    }
+    if (!getNormalizeAttributes()) {
+      options[current++] = "-I";
+    }
+    if (!getReset()) {
+      options[current++] = "-R";
+    }
+    if (getDecay()) {
+      options[current++] = "-D";
+    }
+
+    //modCS.V
+    if (m_valSet != null) {
+      options[current++] = "-validation-set";
+      options[current++] = m_valSetFileName;
+    }    
+    //modCS.S
+    if (m_secSet != null) {
+      options[current++] = "-secondary-training";
+      options[current++] = m_secSetFileName;
+    }        
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  //modCS.V
+  /**
+   * Loads and stores validation file information from the given file name.
+   * @param sValFile the name of the data source file
+   */
+  public void setValFile(String sValFile) 
+  {     
+      try
+      {
+         m_valSetFileName = sValFile; 
+         m_valSetSource = new DataSource(sValFile);         
+         m_valSet = m_valSetSource.getDataSet();
+         if (m_valSet.classIndex() == -1)
+             m_valSet.setClassIndex(m_valSet.numAttributes() - 1); 
+        
+         m_valSet.deleteWithMissingClass();
+      }
+      catch (Throwable t)
+      {
+          m_valSetFileName = null;
+          m_valSetSource = null;
+          m_valSet = null;
+      }
+  }
+
+  //modCS.V
+  /**
+   * Get the name of the validation file.
+   * @return Validation set file name
+   */
+  public String getValFile() {
+    if (m_valSetFileName != null) 
+    {
+        return m_valSetFileName;
+    }
+    return "";
+  }  
+  
+  //modCS.S
+  /**
+   * Loads and stores secondary task training file information from the given
+   * file name.
+   * @param sSecFile the name of the data source file
+   */
+  public void setSecFile(String sSecFile) 
+  {     
+      try
+      {
+         m_secSetFileName = sSecFile; 
+         m_secSetSource = new DataSource(sSecFile);         
+         m_secSet = m_secSetSource.getDataSet();
+         if (m_secSet.classIndex() == -1)
+             m_secSet.setClassIndex(m_secSet.numAttributes() - 1); 
+                
+         m_secSet.deleteWithMissingClass();
+      }
+      catch (Throwable t)
+      {
+          m_secSetFileName = null;
+          m_secSetSource = null;
+          m_secSet = null;
+      }
+  }
+
+  //modCS.S
+  /**
+   * Get the name of the secondary task training file.
+   * @return Secondary task training set file name
+   */
+  public String getSecFile() {
+    if (m_secSetFileName != null) 
+    {
+        return m_secSetFileName;
+    }
+    return "";
+  }    
+  /**
+   * @return string describing the model.
+   */
+  public String toString() {
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer model = new StringBuffer(m_neuralNodes.length * 100); 
+    //just a rough size guess
+    NeuralNode con;
+    double[] weights;
+    NeuralConnection[] inputs;
+    for (int noa = 0; noa < m_neuralNodes.length; noa++) {
+      con = (NeuralNode) m_neuralNodes[noa];  //this would need a change
+                                              //for items other than nodes!!!
+      weights = con.getWeights();
+      inputs = con.getInputs();
+      if (con.getMethod() instanceof SigmoidUnit) {
+	model.append("Sigmoid ");
+      }
+      else if (con.getMethod() instanceof LinearUnit) {
+	model.append("Linear ");
+      }
+      model.append("Node " + con.getId() + "\n    Inputs    Weights\n");
+      model.append("    Threshold    " + weights[0] + "\n");
+      for (int nob = 1; nob < con.getNumInputs() + 1; nob++) {
+	if ((inputs[nob - 1].getType() & NeuralConnection.PURE_INPUT) 
+	    == NeuralConnection.PURE_INPUT) {
+	  model.append("    Attrib " + 
+		       m_instances.attribute(((NeuralEnd)inputs[nob-1]).
+					     getLink()).name()
+		       + "    " + weights[nob] + "\n");
+	}
+	else {
+	  model.append("    Node " + inputs[nob-1].getId() + "    " +
+		       weights[nob] + "\n");
+	}
+      }      
+    }
+    //now put in the ends
+    for (int noa = 0; noa < m_outputs.length; noa++) {
+      inputs = m_outputs[noa].getInputs();
+      model.append("Class " + 
+		   m_instances.classAttribute().
+		   value(m_outputs[noa].getLink()) + 
+		   "\n    Input\n");
+      for (int nob = 0; nob < m_outputs[noa].getNumInputs(); nob++) {
+	if ((inputs[nob].getType() & NeuralConnection.PURE_INPUT)
+	    == NeuralConnection.PURE_INPUT) {
+	  model.append("    Attrib " +
+		       m_instances.attribute(((NeuralEnd)inputs[nob]).
+					     getLink()).name() + "\n");
+	}
+	else {
+	  model.append("    Node " + inputs[nob].getId() + "\n");
+	}
+      }
+    }
+    return model.toString();
+  }
+
+  /**
+   * This will return a string describing the classifier.
+   * @return The string.
+   */
+  public String globalInfo() {
+    return 
+        "A Classifier that uses backpropagation to classify instances.\n"
+      + "This network can be built by hand, created by an algorithm or both. "
+      + "The network can also be monitored and modified during training time. "
+      + "The nodes in this network are all sigmoid (except for when the class "
+      + "is numeric in which case the the output nodes become unthresholded "
+      + "linear units).";
+  }
+  
+  /**
+   * @return a string to describe the learning rate option.
+   */
+  public String learningRateTipText() {
+    return "The amount the" + 
+      " weights are updated.";
+  }
+  
+  /**
+   * @return a string to describe the momentum option.
+   */
+  public String momentumTipText() {
+    return "Momentum applied to the weights during updating.";
+  }
+
+  /**
+   * @return a string to describe the AutoBuild option.
+   */
+  public String autoBuildTipText() {
+    return "Adds and connects up hidden layers in the network.";
+  }
+
+  /**
+   * @return a string to describe the random seed option.
+   */
+  public String seedTipText() {
+    return "Seed used to initialise the random number generator." +
+      "Random numbers are used for setting the initial weights of the" +
+      " connections betweem nodes, and also for shuffling the training data.";
+  }
+  
+  /**
+   * @return a string to describe the validation threshold option.
+   */
+  public String validationThresholdTipText() {
+    return "Used to terminate validation testing." +
+      "The value here dictates how many times in a row the validation set" +
+      " error can get worse before training is terminated.";
+  }
+  
+  /**
+   * @return a string to describe the GUI option.
+   */
+  public String GUITipText() {
+    return "Brings up a gui interface." +
+      " This will allow the pausing and altering of the nueral network" +
+      " during training.\n\n" +
+      "* To add a node left click (this node will be automatically selected," +
+      " ensure no other nodes were selected).\n" +
+      "* To select a node left click on it either while no other node is" +
+      " selected or while holding down the control key (this toggles that" +
+      " node as being selected and not selected.\n" + 
+      "* To connect a node, first have the start node(s) selected, then click"+
+      " either the end node or on an empty space (this will create a new node"+
+      " that is connected with the selected nodes). The selection status of" +
+      " nodes will stay the same after the connection. (Note these are" +
+      " directed connections, also a connection between two nodes will not" +
+      " be established more than once and certain connections that are" + 
+      " deemed to be invalid will not be made).\n" +
+      "* To remove a connection select one of the connected node(s) in the" +
+      " connection and then right click the other node (it does not matter" +
+      " whether the node is the start or end the connection will be removed" +
+      ").\n" +
+      "* To remove a node right click it while no other nodes (including it)" +
+      " are selected. (This will also remove all connections to it)\n." +
+      "* To deselect a node either left click it while holding down control," +
+      " or right click on empty space.\n" +
+      "* The raw inputs are provided from the labels on the left.\n" +
+      "* The red nodes are hidden layers.\n" +
+      "* The orange nodes are the output nodes.\n" +
+      "* The labels on the right show the class the output node represents." +
+      " Note that with a numeric class the output node will automatically be" +
+      " made into an unthresholded linear unit.\n\n" +
+      "Alterations to the neural network can only be done while the network" +
+      " is not running, This also applies to the learning rate and other" +
+      " fields on the control panel.\n\n" + 
+      "* You can accept the network as being finished at any time.\n" +
+      "* The network is automatically paused at the beginning.\n" +
+      "* There is a running indication of what epoch the network is up to" + 
+      " and what the (rough) error for that epoch was (or for" +
+      " the validation if that is being used). Note that this error value" +
+      " is based on a network that changes as the value is computed." +
+      " (also depending on whether" +
+      " the class is normalized will effect the error reported for numeric" +
+      " classes.\n" +
+      "* Once the network is done it will pause again and either wait to be" +
+      " accepted or trained more.\n\n" +
+      "Note that if the gui is not set the network will not require any" +
+      " interaction.\n";
+  }
+  
+  /**
+   * @return a string to describe the validation size option.
+   */
+  public String validationSetSizeTipText() {
+    return "The percentage size of the validation set." +
+      "(The training will continue until it is observed that" +
+      " the error on the validation set has been consistently getting" +
+      " worse, or if the training time is reached).\n" +
+      "If This is set to zero no validation set will be used and instead" +
+      " the network will train for the specified number of epochs.";
+  }
+  
+  /**
+   * @return a string to describe the learning rate option.
+   */
+  public String trainingTimeTipText() {
+    return "The number of epochs to train through." + 
+      " If the validation set is non-zero then it can terminate the network" +
+      " early";
+  }
+
+
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String nominalToBinaryFilterTipText() {
+    return "This will preprocess the instances with the filter." +
+      " This could help improve performance if there are nominal attributes" +
+      " in the data.";
+  }
+
+  /**
+   * @return a string to describe the hidden layers in the network.
+   */
+  public String hiddenLayersTipText() {
+    return "This defines the hidden layers of the neural network." +
+      " This is a list of positive whole numbers. 1 for each hidden layer." +
+      " Comma seperated. To have no hidden layers put a single 0 here." +
+      " This will only be used if autobuild is set. There are also wildcard" +
+      " values 'a' = (attribs + classes) / 2, 'i' = attribs, 'o' = classes" +
+      " , 't' = attribs + classes.";
+  }
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String normalizeNumericClassTipText() {
+    return "This will normalize the class if it's numeric." +
+      " This could help improve performance of the network, It normalizes" +
+      " the class to be between -1 and 1. Note that this is only internally" +
+      ", the output will be scaled back to the original range.";
+  }
+  /**
+   * @return a string to describe the nominal to binary option.
+   */
+  public String normalizeAttributesTipText() {
+    return "This will normalize the attributes." +
+      " This could help improve performance of the network." +
+      " This is not reliant on the class being numeric. This will also" +
+      " normalize nominal attributes as well (after they have been run" +
+      " through the nominal to binary filter if that is in use) so that the" +
+      " nominal values are between -1 and 1";
+  }
+  /**
+   * @return a string to describe the Reset option.
+   */
+  public String resetTipText() {
+    return "This will allow the network to reset with a lower learning rate." +
+      " If the network diverges from the answer this will automatically" +
+      " reset the network with a lower learning rate and begin training" +
+      " again. This option is only available if the gui is not set. Note" +
+      " that if the network diverges but isn't allowed to reset it will" +
+      " fail the training process and return an error message.";
+  }
+  
+  /**
+   * @return a string to describe the Decay option.
+   */
+  public String decayTipText() {
+    return "This will cause the learning rate to decrease." +
+      " This will divide the starting learning rate by the epoch number, to" +
+      " determine what the current learning rate should be. This may help" +
+      " to stop the network from diverging from the target output, as well" +
+      " as improve general performance. Note that the decaying learning" +
+      " rate will not be shown in the gui, only the original learning rate" +
+      ". If the learning rate is changed in the gui, this is treated as the" +
+      " starting learning rate.";
+  }
+  
+  //modCS.V
+  /**
+   * @return a string to describe the validation set file.
+   */
+  public String valFileTipText() {
+    return "Set the name of a validation file in data source format.";
+  }
+  //modCS.S
+  /**
+   * @return a string to describe the secondary task training set file.
+   */  
+    public String secFileTipText() {
+    return "Set the name of a secondary training file in data source format.";
+  } 
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6202 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/PLSClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/PLSClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/PLSClassifier.java	(revision 29)
@@ -0,0 +1,365 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PLSClassifier.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.PLSFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A wrapper classifier for the PLSFilter, utilizing the PLSFilter's ability to perform predictions.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -filter &lt;filter specification&gt;
+ *  The PLS filter to use. Full classname of filter to include,  followed by scheme options.
+ *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> 
+ * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
+ * </pre>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The number of components to compute.
+ *  (default: 20)</pre>
+ * 
+ * <pre> -U
+ *  Updates the class attribute as well.
+ *  (default: off)</pre>
+ * 
+ * <pre> -M
+ *  Turns replacing of missing values on.
+ *  (default: off)</pre>
+ * 
+ * <pre> -A &lt;SIMPLS|PLS1&gt;
+ *  The algorithm to use.
+ *  (default: PLS1)</pre>
+ * 
+ * <pre> -P &lt;none|center|standardize&gt;
+ *  The type of preprocessing that is applied to the data.
+ *  (default: center)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class PLSClassifier
+  extends AbstractClassifier {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 4819775160590973256L;
+
+  /** the PLS filter */
+  protected PLSFilter m_Filter = new PLSFilter();
+
+  /** the actual filter to use */
+  protected PLSFilter m_ActualFilter = null;
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A wrapper classifier for the PLSFilter, utilizing the PLSFilter's "
+      + "ability to perform predictions.";
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions(){
+    Vector        	result;
+    Enumeration   	en;
+
+    result = new Vector();
+
+    result.addElement(new Option(
+	"\tThe PLS filter to use. Full classname of filter to include, "
+	+ "\tfollowed by scheme options.\n"
+	+ "\t(default: weka.filters.supervised.attribute.PLSFilter)",
+	"filter", 1, "-filter <filter specification>"));
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    if (getFilter() instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to filter "
+	  + getFilter().getClass().getName() + " ('-filter'):"));
+      
+      en = ((OptionHandler) getFilter()).listOptions();
+      while (en.hasMoreElements())
+	result.addElement(en.nextElement());
+    }
+
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    result.add("-filter");
+    if (getFilter() instanceof OptionHandler)
+      result.add(
+  	    getFilter().getClass().getName() 
+	  + " " 
+	  + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
+    else
+      result.add(
+	  getFilter().getClass().getName());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -filter &lt;filter specification&gt;
+   *  The PLS filter to use. Full classname of filter to include,  followed by scheme options.
+   *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> 
+   * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
+   * </pre>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The number of components to compute.
+   *  (default: 20)</pre>
+   * 
+   * <pre> -U
+   *  Updates the class attribute as well.
+   *  (default: off)</pre>
+   * 
+   * <pre> -M
+   *  Turns replacing of missing values on.
+   *  (default: off)</pre>
+   * 
+   * <pre> -A &lt;SIMPLS|PLS1&gt;
+   *  The algorithm to use.
+   *  (default: PLS1)</pre>
+   * 
+   * <pre> -P &lt;none|center|standardize&gt;
+   *  The type of preprocessing that is applied to the data.
+   *  (default: center)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    super.setOptions(options);
+    
+    tmpStr     = Utils.getOption("filter", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The PLS filter to be used (only used for setup).";
+  }
+
+  /**
+   * Set the PLS filter (only used for setup).
+   *
+   * @param value	the kernel filter.
+   * @throws Exception	if not PLSFilter
+   */
+  public void setFilter(Filter value) throws Exception {
+    if (!(value instanceof PLSFilter))
+      throw new Exception("Filter has to be PLSFilter!");
+    else
+      m_Filter = (PLSFilter) value;
+  }
+
+  /**
+   * Get the PLS filter.
+   *
+   * @return 		the PLS filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = getFilter().getCapabilities();
+
+    // class
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // other
+    result.setMinimumNumberInstances(1);
+    
+    return result;
+  }
+
+  /**
+   * builds the classifier
+   * 
+   * @param data        the training instances
+   * @throws Exception  if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    // initialize filter
+    m_ActualFilter = (PLSFilter) Filter.makeCopy(m_Filter);
+    m_ActualFilter.setPerformPrediction(false);
+    m_ActualFilter.setInputFormat(data);
+    Filter.useFilter(data, m_ActualFilter);
+    m_ActualFilter.setPerformPrediction(true);
+  }
+
+  /**
+   * Classifies the given test instance. The instance has to belong to a
+   * dataset when it's being classified.
+   *
+   * @param instance 	the instance to be classified
+   * @return 		the predicted most likely class for the instance or 
+   * 			Utils.missingValue() if no prediction is made
+   * @throws Exception 	if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    double	result;
+    Instance	pred;
+    
+    m_ActualFilter.input(instance);
+    m_ActualFilter.batchFinished();
+    pred   = m_ActualFilter.output();
+    result = pred.classValue();
+    
+    return result;
+  }
+
+  /**
+   * returns a string representation of the classifier
+   * 
+   * @return		a string representation of the classifier
+   */
+  public String toString() {
+    String	result;
+    
+    result =   this.getClass().getName() + "\n" 
+             + this.getClass().getName().replaceAll(".", "=") + "\n\n";
+    result += "# Components..........: " + m_Filter.getNumComponents() + "\n";
+    result += "Algorithm.............: " + m_Filter.getAlgorithm().getSelectedTag().getReadable() + "\n";
+    result += "Replace missing values: " + (m_Filter.getReplaceMissing() ? "yes" : "no") + "\n";
+    result += "Preprocessing.........: " + m_Filter.getPreprocessing().getSelectedTag().getReadable() + "\n";
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for running this classifier from commandline.
+   * 
+   * @param args 	the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new PLSClassifier(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/PaceRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/PaceRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/PaceRegression.java	(revision 29)
@@ -0,0 +1,790 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    PaceRegression.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.pace.ChisqMixture;
+import weka.classifiers.functions.pace.MixtureDistribution;
+import weka.classifiers.functions.pace.NormalMixture;
+import weka.classifiers.functions.pace.PaceMatrix;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.NoSupportForMissingValuesException;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.WekaException;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.IntVector;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building pace regression linear models and using them for prediction. <br/>
+ * <br/>
+ * Under regularity conditions, pace regression is provably optimal when the number of coefficients tends to infinity. It consists of a group of estimators that are either overall optimal or optimal under certain conditions.<br/>
+ * <br/>
+ * The current work of the pace regression theory, and therefore also this implementation, do not handle: <br/>
+ * <br/>
+ * - missing values <br/>
+ * - non-binary nominal attributes <br/>
+ * - the case that n - k is small where n is the number of instances and k is the number of coefficients (the threshold used in this implmentation is 20)<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Wang, Y (2000). A new approach to fitting linear models in high dimensional spaces. Hamilton, New Zealand.<br/>
+ * <br/>
+ * Wang, Y., Witten, I. H.: Modeling for optimal probability prediction. In: Proceedings of the Nineteenth International Conference in Machine Learning, Sydney, Australia, 650-657, 2002.
+ * <p/>
+ <!-- globalinfo-end -->
+ *  
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Wang2000,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Wang, Y},
+ *    school = {Department of Computer Science, University of Waikato},
+ *    title = {A new approach to fitting linear models in high dimensional spaces},
+ *    year = {2000}
+ * }
+ * 
+ * &#64;inproceedings{Wang2002,
+ *    address = {Sydney, Australia},
+ *    author = {Wang, Y. and Witten, I. H.},
+ *    booktitle = {Proceedings of the Nineteenth International Conference in Machine Learning},
+ *    pages = {650-657},
+ *    title = {Modeling for optimal probability prediction},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Produce debugging output.
+ *  (default no debugging output)</pre>
+ * 
+ * <pre> -E &lt;estimator&gt;
+ *  The estimator can be one of the following:
+ *   eb -- Empirical Bayes estimator for noraml mixture (default)
+ *   nested -- Optimal nested model selector for normal mixture
+ *   subset -- Optimal subset selector for normal mixture
+ *   pace2 -- PACE2 for Chi-square mixture
+ *   pace4 -- PACE4 for Chi-square mixture
+ *   pace6 -- PACE6 for Chi-square mixture
+ * 
+ *   ols -- Ordinary least squares estimator
+ *   aic -- AIC estimator
+ *   bic -- BIC estimator
+ *   ric -- RIC estimator
+ *   olsc -- Ordinary least squares subset selector with a threshold</pre>
+ * 
+ * <pre> -S &lt;threshold value&gt;
+ *  Threshold value for the OLSC estimator</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class PaceRegression 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 7230266976059115435L;
+  
+  /** The model used */
+  Instances m_Model = null;
+
+  /** Array for storing coefficients of linear regression. */
+  private double[] m_Coefficients;
+
+  /** The index of the class attribute */
+  private int m_ClassIndex;
+
+  /** True if debug output will be printed */
+  private boolean m_Debug;
+
+  /** estimator type: Ordinary least squares */
+  private static final int olsEstimator = 0;
+  /** estimator type: Empirical Bayes */
+  private static final int ebEstimator = 1;
+  /** estimator type: Nested model selector */
+  private static final int nestedEstimator = 2;
+  /** estimator type: Subset selector */
+  private static final int subsetEstimator = 3; 
+  /** estimator type:PACE2  */
+  private static final int pace2Estimator = 4; 
+  /** estimator type: PACE4 */
+  private static final int pace4Estimator = 5; 
+  /** estimator type: PACE6 */
+  private static final int pace6Estimator = 6; 
+  /** estimator type: Ordinary least squares selection */
+  private static final int olscEstimator = 7;
+  /** estimator type: AIC */
+  private static final int aicEstimator = 8;
+  /** estimator type: BIC */
+  private static final int bicEstimator = 9;
+  /** estimator type: RIC */
+  private static final int ricEstimator = 10;
+  /** estimator types */
+  public static final Tag [] TAGS_ESTIMATOR = {
+    new Tag(olsEstimator, "Ordinary least squares"),
+    new Tag(ebEstimator, "Empirical Bayes"),
+    new Tag(nestedEstimator, "Nested model selector"),
+    new Tag(subsetEstimator, "Subset selector"),
+    new Tag(pace2Estimator, "PACE2"),
+    new Tag(pace4Estimator, "PACE4"),
+    new Tag(pace6Estimator, "PACE6"),
+    new Tag(olscEstimator, "Ordinary least squares selection"),
+    new Tag(aicEstimator, "AIC"),
+    new Tag(bicEstimator, "BIC"),
+    new Tag(ricEstimator, "RIC")
+  };
+
+  /** the estimator */
+  private int paceEstimator = ebEstimator;  
+  
+  private double olscThreshold = 2;  // AIC
+  
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for building pace regression linear models and using them for "
+      +"prediction. \n\n"
+      +"Under regularity conditions, pace regression is provably optimal when "
+      +"the number of coefficients tends to infinity. It consists of a group of "
+      +"estimators that are either overall optimal or optimal under certain "
+      +"conditions.\n\n"
+      +"The current work of the pace regression theory, and therefore also this "
+      +"implementation, do not handle: \n\n"
+      +"- missing values \n"
+      +"- non-binary nominal attributes \n"
+      +"- the case that n - k is small where n is the number of instances and k is "  
+      +"the number of coefficients (the threshold used in this implmentation is 20)\n\n"
+      +"For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "Wang, Y");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.TITLE, "A new approach to fitting linear models in high dimensional spaces");
+    result.setValue(Field.SCHOOL, "Department of Computer Science, University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Wang, Y. and Witten, I. H.");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.TITLE, "Modeling for optimal probability prediction");
+    additional.setValue(Field.BOOKTITLE, "Proceedings of the Nineteenth International Conference in Machine Learning");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.PAGES, "650-657");
+    additional.setValue(Field.ADDRESS, "Sydney, Australia");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.BINARY_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds a pace regression model for the given data.
+   *
+   * @param data the training data to be used for generating the
+   * linear regression function
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    Capabilities cap = getCapabilities();
+    cap.setMinimumNumberInstances(20 + data.numAttributes());
+    cap.testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    /*
+     * initialize the following
+     */
+    m_Model = new Instances(data, 0);
+    m_ClassIndex = data.classIndex();
+    double[][] transformedDataMatrix = 
+    getTransformedDataMatrix(data, m_ClassIndex);
+    double[] classValueVector = data.attributeToDoubleArray(m_ClassIndex);
+    
+    m_Coefficients = null;
+
+    /* 
+     * Perform pace regression
+     */
+    m_Coefficients = pace(transformedDataMatrix, classValueVector);
+  }
+
+  /**
+   * pace regression
+   *
+   * @param matrix_X matrix with observations
+   * @param vector_Y vektor with class values
+   * @return vector with coefficients
+   */
+  private double [] pace(double[][] matrix_X, double [] vector_Y) {
+    
+    PaceMatrix X = new PaceMatrix( matrix_X );
+    PaceMatrix Y = new PaceMatrix( vector_Y, vector_Y.length );
+    IntVector pvt = IntVector.seq(0, X.getColumnDimension()-1);
+    int n = X.getRowDimension();
+    int kr = X.getColumnDimension();
+
+    X.lsqrSelection( Y, pvt, 1 );
+    X.positiveDiagonal( Y, pvt );
+    
+    PaceMatrix sol = (PaceMatrix) Y.clone();
+    X.rsolve( sol, pvt, pvt.size() );
+    DoubleVector r = Y.getColumn( pvt.size(), n-1, 0);
+    double sde = Math.sqrt(r.sum2() / r.size());
+    
+    DoubleVector aHat = Y.getColumn( 0, pvt.size()-1, 0).times( 1./sde );
+
+    DoubleVector aTilde = null;
+    switch( paceEstimator) {
+    case ebEstimator: 
+    case nestedEstimator:
+    case subsetEstimator:
+      NormalMixture d = new NormalMixture();
+      d.fit( aHat, MixtureDistribution.NNMMethod ); 
+      if( paceEstimator == ebEstimator ) 
+	aTilde = d.empiricalBayesEstimate( aHat );
+      else if( paceEstimator == ebEstimator ) 
+	aTilde = d.subsetEstimate( aHat );
+      else aTilde = d.nestedEstimate( aHat );
+      break;
+    case pace2Estimator: 
+    case pace4Estimator:
+    case pace6Estimator:
+      DoubleVector AHat = aHat.square();
+      ChisqMixture dc = new ChisqMixture();
+      dc.fit( AHat, MixtureDistribution.NNMMethod ); 
+      DoubleVector ATilde; 
+      if( paceEstimator == pace6Estimator ) 
+	ATilde = dc.pace6( AHat );
+      else if( paceEstimator == pace2Estimator ) 
+	ATilde = dc.pace2( AHat );
+      else ATilde = dc.pace4( AHat );
+      aTilde = ATilde.sqrt().times( aHat.sign() );
+      break;
+    case olsEstimator: 
+      aTilde = aHat.copy();
+      break;
+    case aicEstimator: 
+    case bicEstimator:
+    case ricEstimator: 
+    case olscEstimator:
+      if(paceEstimator == aicEstimator) olscThreshold = 2;
+      else if(paceEstimator == bicEstimator) olscThreshold = Math.log( n );
+      else if(paceEstimator == ricEstimator) olscThreshold = 2*Math.log( kr );
+      aTilde = aHat.copy();
+      for( int i = 0; i < aTilde.size(); i++ )
+	if( Math.abs(aTilde.get(i)) < Math.sqrt(olscThreshold) ) 
+	  aTilde.set(i, 0);
+    }
+    PaceMatrix YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    DoubleVector betaTilde = YTilde.getColumn(0).unpivoting( pvt, kr );
+    
+    return betaTilde.getArrayCopy();
+  }
+
+  /**
+   * Checks if an instance has a missing value.
+   * @param instance the instance
+   * @param model the data 
+   * @return true if missing value is present
+   */
+  public boolean checkForMissing(Instance instance, Instances model) {
+
+    for (int j = 0; j < instance.numAttributes(); j++) {
+      if (j != model.classIndex()) {
+	if (instance.isMissing(j)) {
+	  return true;
+	}
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Transforms dataset into a two-dimensional array.
+   *
+   * @param data dataset
+   * @param classIndex index of the class attribute
+   * @return the transformed data
+   */
+  private double [][] getTransformedDataMatrix(Instances data, 
+					       int classIndex) {
+    int numInstances = data.numInstances();
+    int numAttributes = data.numAttributes();
+    int middle = classIndex;
+    if (middle < 0) { 
+      middle = numAttributes;
+    }
+
+    double[][] result = new double[numInstances]
+    [numAttributes];
+    for (int i = 0; i < numInstances; i++) {
+      Instance inst = data.instance(i);
+      
+      result[i][0] = 1.0;
+
+      // the class value (lies on index middle) is left out
+      for (int j = 0; j < middle; j++) {
+	result[i][j + 1] = inst.value(j);
+      }
+      for (int j = middle + 1; j < numAttributes; j++) {
+	result[i][j] = inst.value(j);
+      }
+    }
+    return result;
+  }
+
+
+  /**
+   * Classifies the given instance using the linear regression function.
+   *
+   * @param instance the test instance
+   * @return the classification
+   * @throws Exception if classification can't be done successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    
+    if (m_Coefficients == null) {
+      throw new Exception("Pace Regression: No model built yet.");
+    }
+    
+    // check for missing data and throw exception if some are found
+    if (checkForMissing(instance, m_Model)) {
+      throw new NoSupportForMissingValuesException("Can't handle missing values!");
+    }
+
+    // Calculate the dependent variable from the regression model
+    return regressionPrediction(instance,
+				m_Coefficients);
+  }
+
+  /**
+   * Outputs the linear regression model as a string.
+   * 
+   * @return the model as string
+   */
+  public String toString() {
+
+    if (m_Coefficients == null) {
+      return "Pace Regression: No model built yet.";
+    }
+    //    try {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("\nPace Regression Model\n\n");
+    
+    text.append(m_Model.classAttribute().name()+" =\n\n");
+    int index = 0;	  
+    
+    text.append(Utils.doubleToString(m_Coefficients[0],
+				     12, 4) );
+    
+    for (int i = 1; i < m_Coefficients.length; i++) {
+      
+      // jump over the class attribute
+      if (index == m_ClassIndex) index++;
+      
+      if (m_Coefficients[i] != 0.0) {
+	// output a coefficient if unequal zero
+	text.append(" +\n");
+	text.append(Utils.doubleToString(m_Coefficients[i], 12, 4)
+		    + " * ");
+	text.append(m_Model.attribute(index).name());
+      }
+      index ++;
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option("\tProduce debugging output.\n"
+				    + "\t(default no debugging output)",
+				    "D", 0, "-D"));
+    newVector.addElement(new Option("\tThe estimator can be one of the following:\n" + 
+				    "\t\teb -- Empirical Bayes estimator for noraml mixture (default)\n" +
+				    "\t\tnested -- Optimal nested model selector for normal mixture\n" + 
+				    "\t\tsubset -- Optimal subset selector for normal mixture\n" +
+				    "\t\tpace2 -- PACE2 for Chi-square mixture\n" +
+				    "\t\tpace4 -- PACE4 for Chi-square mixture\n" +
+				    "\t\tpace6 -- PACE6 for Chi-square mixture\n\n" + 
+				    "\t\tols -- Ordinary least squares estimator\n" +  
+				    "\t\taic -- AIC estimator\n" +  
+				    "\t\tbic -- BIC estimator\n" +  
+				    "\t\tric -- RIC estimator\n" +  
+				    "\t\tolsc -- Ordinary least squares subset selector with a threshold", 
+				    "E", 0, "-E <estimator>"));
+    newVector.addElement(new Option("\tThreshold value for the OLSC estimator",
+				    "S", 0, "-S <threshold value>"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Produce debugging output.
+   *  (default no debugging output)</pre>
+   * 
+   * <pre> -E &lt;estimator&gt;
+   *  The estimator can be one of the following:
+   *   eb -- Empirical Bayes estimator for noraml mixture (default)
+   *   nested -- Optimal nested model selector for normal mixture
+   *   subset -- Optimal subset selector for normal mixture
+   *   pace2 -- PACE2 for Chi-square mixture
+   *   pace4 -- PACE4 for Chi-square mixture
+   *   pace6 -- PACE6 for Chi-square mixture
+   * 
+   *   ols -- Ordinary least squares estimator
+   *   aic -- AIC estimator
+   *   bic -- BIC estimator
+   *   ric -- RIC estimator
+   *   olsc -- Ordinary least squares subset selector with a threshold</pre>
+   * 
+   * <pre> -S &lt;threshold value&gt;
+   *  Threshold value for the OLSC estimator</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    setDebug(Utils.getFlag('D', options));
+
+    String estimator = Utils.getOption('E', options);
+    if ( estimator.equals("ols") ) paceEstimator = olsEstimator;
+    else if ( estimator.equals("olsc") ) paceEstimator = olscEstimator;
+    else if( estimator.equals("eb") || estimator.equals("") ) 
+      paceEstimator = ebEstimator;
+    else if ( estimator.equals("nested") ) paceEstimator = nestedEstimator;
+    else if ( estimator.equals("subset") ) paceEstimator = subsetEstimator;
+    else if ( estimator.equals("pace2") ) paceEstimator = pace2Estimator; 
+    else if ( estimator.equals("pace4") ) paceEstimator = pace4Estimator;
+    else if ( estimator.equals("pace6") ) paceEstimator = pace6Estimator;
+    else if ( estimator.equals("aic") ) paceEstimator = aicEstimator;
+    else if ( estimator.equals("bic") ) paceEstimator = bicEstimator;
+    else if ( estimator.equals("ric") ) paceEstimator = ricEstimator;
+    else throw new WekaException("unknown estimator " + estimator + 
+				 " for -E option" );
+
+    String string = Utils.getOption('S', options);
+    if( ! string.equals("") ) olscThreshold = Double.parseDouble( string );
+    
+  }
+
+  /**
+   * Returns the coefficients for this linear model.
+   * 
+   * @return the coefficients for this linear model
+   */
+  public double[] coefficients() {
+
+    double[] coefficients = new double[m_Coefficients.length];
+    for (int i = 0; i < coefficients.length; i++) {
+      coefficients[i] = m_Coefficients[i];
+    }
+    return coefficients;
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [6];
+    int current = 0;
+
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+
+    options[current++] = "-E";
+    switch (paceEstimator) {
+    case olsEstimator: options[current++] = "ols";
+      break;
+    case olscEstimator: options[current++] = "olsc";
+      options[current++] = "-S";
+      options[current++] = "" + olscThreshold;
+      break;
+    case ebEstimator: options[current++] = "eb";
+      break;
+    case nestedEstimator: options[current++] = "nested";
+      break;
+    case subsetEstimator: options[current++] = "subset";
+      break;
+    case pace2Estimator: options[current++] = "pace2";
+      break; 
+    case pace4Estimator: options[current++] = "pace4";
+      break;
+    case pace6Estimator: options[current++] = "pace6";
+      break;
+    case aicEstimator: options[current++] = "aic";
+      break;
+    case bicEstimator: options[current++] = "bic";
+      break;
+    case ricEstimator: options[current++] = "ric";
+      break;
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  
+  /**
+   * Get the number of coefficients used in the model
+   *
+   * @return the number of coefficients
+   */
+  public int numParameters()
+  {
+    return m_Coefficients.length-1;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Output debug information to the console.";
+  }
+
+  /**
+   * Controls whether debugging output will be printed
+   *
+   * @param debug true if debugging output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    m_Debug = debug;
+  }
+
+  /**
+   * Controls whether debugging output will be printed
+   *
+   * @return true if debugging output should be printed
+   */
+  public boolean getDebug() {
+
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String estimatorTipText() {
+    return "The estimator to use.\n\n"
+      +"eb -- Empirical Bayes estimator for noraml mixture (default)\n"
+      +"nested -- Optimal nested model selector for normal mixture\n"
+      +"subset -- Optimal subset selector for normal mixture\n"
+      +"pace2 -- PACE2 for Chi-square mixture\n"
+      +"pace4 -- PACE4 for Chi-square mixture\n"
+      +"pace6 -- PACE6 for Chi-square mixture\n"
+      +"ols -- Ordinary least squares estimator\n"
+      +"aic -- AIC estimator\n"
+      +"bic -- BIC estimator\n"
+      +"ric -- RIC estimator\n"
+      +"olsc -- Ordinary least squares subset selector with a threshold";
+  }
+  
+  /**
+   * Gets the estimator
+   *
+   * @return the estimator
+   */
+  public SelectedTag getEstimator() {
+
+    return new SelectedTag(paceEstimator, TAGS_ESTIMATOR);
+  }
+  
+  /**
+   * Sets the estimator.
+   *
+   * @param estimator the new estimator
+   */
+  public void setEstimator(SelectedTag estimator) {
+    
+    if (estimator.getTags() == TAGS_ESTIMATOR) {
+      paceEstimator = estimator.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Threshold for the olsc estimator.";
+  }
+
+  /**
+   * Set threshold for the olsc estimator
+   *
+   * @param newThreshold the threshold for the olsc estimator
+   */
+  public void setThreshold(double newThreshold) {
+
+    olscThreshold = newThreshold;
+  }
+
+  /**
+   * Gets the threshold for olsc estimator
+   *
+   * @return the threshold
+   */
+  public double getThreshold() {
+
+    return olscThreshold;
+  }
+
+
+  /**
+   * Calculate the dependent value for a given instance for a
+   * given regression model.
+   *
+   * @param transformedInstance the input instance
+   * @param coefficients an array of coefficients for the regression
+   * model
+   * @return the regression value for the instance.
+   * @throws Exception if the class attribute of the input instance
+   * is not assigned
+   */
+  private double regressionPrediction(Instance transformedInstance,
+				      double [] coefficients) 
+    throws Exception {
+
+    int column = 0;
+    double result = coefficients[column];
+    for (int j = 0; j < transformedInstance.numAttributes(); j++) {
+      if (m_ClassIndex != j) {
+	column++;
+	result += coefficients[column] * transformedInstance.value(j);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Generates a linear regression function predictor.
+   *
+   * @param argv the options
+   */
+  public static void main(String argv[]) {
+    runClassifier(new PaceRegression(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/RBFNetwork.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/RBFNetwork.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/RBFNetwork.java	(revision 29)
@@ -0,0 +1,531 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+/*
+ *    RBFNetwork.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.clusterers.MakeDensityBasedClusterer;
+import weka.clusterers.SimpleKMeans;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ClusterMembership;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class that implements a normalized Gaussian radial basisbasis function network.<br/>
+ * It uses the k-means clustering algorithm to provide the basis functions and learns either a logistic regression (discrete class problems) or linear regression (numeric class problems) on top of that. Symmetric multivariate Gaussians are fit to the data from each cluster. If the class is nominal it uses the given number of clusters per class.It standardizes all numeric attributes to zero mean and unit variance.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;number&gt;
+ *  Set the number of clusters (basis functions) to generate. (default = 2).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Set the random seed to be used by K-means. (default = 1).</pre>
+ * 
+ * <pre> -R &lt;ridge&gt;
+ *  Set the ridge value for the logistic or linear regression.</pre>
+ * 
+ * <pre> -M &lt;number&gt;
+ *  Set the maximum number of iterations for the logistic regression. (default -1, until convergence).</pre>
+ * 
+ * <pre> -W &lt;number&gt;
+ *  Set the minimum standard deviation for the clusters. (default 0.1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall
+ * @author Eibe Frank
+ * @version $Revision: 5928 $
+ */
+public class RBFNetwork extends AbstractClassifier implements OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3669814959712675720L;
+  
+  /** The logistic regression for classification problems */
+  private Logistic m_logistic;
+
+  /** The linear regression for numeric problems */
+  private LinearRegression m_linear;
+
+  /** The filter for producing the meta data */
+  private ClusterMembership m_basisFilter;
+
+  /** Filter used for normalizing the data */
+  private Standardize m_standardize;
+
+  /** The number of clusters (basis functions to generate) */
+  private int m_numClusters = 2;
+
+  /** The ridge parameter for the logistic regression. */
+  protected double m_ridge = 1e-8;
+
+  /** The maximum number of iterations for logistic regression. */
+  private int m_maxIts = -1;
+
+  /** The seed to pass on to K-means */
+  private int m_clusteringSeed = 1;
+
+  /** The minimum standard deviation */
+  private double m_minStdDev = 0.1;
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+    
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class that implements a normalized Gaussian radial basis" 
+      + "basis function network.\n"
+      + "It uses the k-means clustering algorithm to provide the basis "
+      + "functions and learns either a logistic regression (discrete "
+      + "class problems) or linear regression (numeric class problems) "
+      + "on top of that. Symmetric multivariate Gaussians are fit to "
+      + "the data from each cluster. If the class is "
+      + "nominal it uses the given number of clusters per class."
+      + "It standardizes all numeric "
+      + "attributes to zero mean and unit variance." ;
+  }
+
+  /**
+   * Returns default capabilities of the classifier, i.e.,  and "or" of
+   * Logistic and LinearRegression.
+   *
+   * @return      the capabilities of this classifier
+   * @see         Logistic
+   * @see         LinearRegression
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Logistic().getCapabilities();
+    result.or(new LinearRegression().getCapabilities());
+    Capabilities classes = result.getClassCapabilities();
+    result.and(new SimpleKMeans().getCapabilities());
+    result.or(classes);
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param instances the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (instances.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(instances);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_standardize = new Standardize();
+    m_standardize.setInputFormat(instances);
+    instances = Filter.useFilter(instances, m_standardize);
+
+    SimpleKMeans sk = new SimpleKMeans();
+    sk.setNumClusters(m_numClusters);
+    sk.setSeed(m_clusteringSeed);
+    MakeDensityBasedClusterer dc = new MakeDensityBasedClusterer();
+    dc.setClusterer(sk);
+    dc.setMinStdDev(m_minStdDev);
+    m_basisFilter = new ClusterMembership();
+    m_basisFilter.setDensityBasedClusterer(dc);
+    m_basisFilter.setInputFormat(instances);
+    Instances transformed = Filter.useFilter(instances, m_basisFilter);
+
+    if (instances.classAttribute().isNominal()) {
+      m_linear = null;
+      m_logistic = new Logistic();
+      m_logistic.setRidge(m_ridge);
+      m_logistic.setMaxIts(m_maxIts);
+      m_logistic.buildClassifier(transformed);
+    } else {
+      m_logistic = null;
+      m_linear = new LinearRegression();
+      m_linear.setAttributeSelectionMethod(new SelectedTag(LinearRegression.SELECTION_NONE,
+							   LinearRegression.TAGS_SELECTION));
+      m_linear.setRidge(m_ridge);
+      m_linear.buildClassifier(transformed);
+    }
+  }
+
+  /**
+   * Computes the distribution for a given instance
+   *
+   * @param instance the instance for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    m_standardize.input(instance);
+    m_basisFilter.input(m_standardize.output());
+    Instance transformed = m_basisFilter.output();
+    
+    return ((instance.classAttribute().isNominal()
+	     ? m_logistic.distributionForInstance(transformed)
+	     : m_linear.distributionForInstance(transformed)));
+  }
+  
+  /**
+   * Returns a description of this classifier as a String
+   *
+   * @return a description of this classifier
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_basisFilter == null) {
+      return "No classifier built yet!";
+    }
+
+    StringBuffer sb = new StringBuffer();
+    sb.append("Radial basis function network\n");
+    sb.append((m_linear == null) 
+	      ? "(Logistic regression "
+	      : "(Linear regression ");
+    sb.append("applied to K-means clusters as basis functions):\n\n");
+    sb.append((m_linear == null)
+	      ? m_logistic.toString()
+	      : m_linear.toString());
+    return sb.toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxItsTipText() {
+    return "Maximum number of iterations for the logistic regression to perform. "
+      +"Only applied to discrete class problems.";
+  }
+
+  /**
+   * Get the value of MaxIts.
+   *
+   * @return Value of MaxIts.
+   */
+  public int getMaxIts() {
+	
+    return m_maxIts;
+  }
+    
+  /**
+   * Set the value of MaxIts.
+   *
+   * @param newMaxIts Value to assign to MaxIts.
+   */
+  public void setMaxIts(int newMaxIts) {
+	
+    m_maxIts = newMaxIts;
+  }    
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ridgeTipText() {
+    return "Set the Ridge value for the logistic or linear regression.";
+  }
+
+  /**
+   * Sets the ridge value for logistic or linear regression.
+   *
+   * @param ridge the ridge
+   */
+  public void setRidge(double ridge) {
+    m_ridge = ridge;
+  }
+    
+  /**
+   * Gets the ridge value.
+   *
+   * @return the ridge
+   */
+  public double getRidge() {
+    return m_ridge;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numClustersTipText() {
+    return "The number of clusters for K-Means to generate.";
+  }
+
+  /**
+   * Set the number of clusters for K-means to generate.
+   *
+   * @param numClusters the number of clusters to generate.
+   */
+  public void setNumClusters(int numClusters) {
+    if (numClusters > 0) {
+      m_numClusters = numClusters;
+    }
+  }
+
+  /**
+   * Return the number of clusters to generate.
+   *
+   * @return the number of clusters to generate.
+   */
+  public int getNumClusters() {
+    return m_numClusters;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clusteringSeedTipText() {
+    return "The random seed to pass on to K-means.";
+  }
+  
+  /**
+   * Set the random seed to be passed on to K-means.
+   *
+   * @param seed a seed value.
+   */
+  public void setClusteringSeed(int seed) {
+    m_clusteringSeed = seed;
+  }
+
+  /**
+   * Get the random seed used by K-means.
+   *
+   * @return the seed value.
+   */
+  public int getClusteringSeed() {
+    return m_clusteringSeed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minStdDevTipText() {
+    return "Sets the minimum standard deviation for the clusters.";
+  }
+
+  /**
+   * Get the MinStdDev value.
+   * @return the MinStdDev value.
+   */
+  public double getMinStdDev() {
+    return m_minStdDev;
+  }
+
+  /**
+   * Set the MinStdDev value.
+   * @param newMinStdDev The new MinStdDev value.
+   */
+  public void setMinStdDev(double newMinStdDev) {
+    m_minStdDev = newMinStdDev;
+  }
+
+  
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option("\tSet the number of clusters (basis functions) "
+				    +"to generate. (default = 2).",
+				    "B", 1, "-B <number>"));
+    newVector.addElement(new Option("\tSet the random seed to be used by K-means. "
+				    +"(default = 1).",
+				    "S", 1, "-S <seed>"));
+    newVector.addElement(new Option("\tSet the ridge value for the logistic or "
+				    +"linear regression.",
+				    "R", 1, "-R <ridge>"));
+    newVector.addElement(new Option("\tSet the maximum number of iterations "
+				    +"for the logistic regression."
+				    + " (default -1, until convergence).",
+				    "M", 1, "-M <number>"));
+    newVector.addElement(new Option("\tSet the minimum standard "
+				    +"deviation for the clusters."
+				    + " (default 0.1).",
+				    "W", 1, "-W <number>"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;number&gt;
+   *  Set the number of clusters (basis functions) to generate. (default = 2).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Set the random seed to be used by K-means. (default = 1).</pre>
+   * 
+   * <pre> -R &lt;ridge&gt;
+   *  Set the ridge value for the logistic or linear regression.</pre>
+   * 
+   * <pre> -M &lt;number&gt;
+   *  Set the maximum number of iterations for the logistic regression. (default -1, until convergence).</pre>
+   * 
+   * <pre> -W &lt;number&gt;
+   *  Set the minimum standard deviation for the clusters. (default 0.1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String ridgeString = Utils.getOption('R', options);
+    if (ridgeString.length() != 0) {
+      m_ridge = Double.parseDouble(ridgeString);
+    } else {
+      m_ridge = 1.0e-8;
+    }
+	
+    String maxItsString = Utils.getOption('M', options);
+    if (maxItsString.length() != 0) {
+      m_maxIts = Integer.parseInt(maxItsString);
+    } else {
+      m_maxIts = -1;
+    }
+
+    String numClustersString = Utils.getOption('B', options);
+    if (numClustersString.length() != 0) {
+      setNumClusters(Integer.parseInt(numClustersString));
+    }
+
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setClusteringSeed(Integer.parseInt(seedString));
+    }
+    String stdString = Utils.getOption('W', options);
+    if (stdString.length() != 0) {
+      setMinStdDev(Double.parseDouble(stdString));
+    }
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+	
+    String [] options = new String [10];
+    int current = 0;
+    
+    options[current++] = "-B";
+    options[current++] = "" + m_numClusters;
+    options[current++] = "-S";
+    options[current++] = "" + m_clusteringSeed;
+    options[current++] = "-R";
+    options[current++] = ""+m_ridge;	
+    options[current++] = "-M";
+    options[current++] = ""+m_maxIts;
+    options[current++] = "-W";
+    options[current++] = ""+m_minStdDev;
+
+    while (current < options.length) 
+      options[current++] = "";
+    return options;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new RBFNetwork(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/SMO.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/SMO.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/SMO.java	(revision 29)
@@ -0,0 +1,2143 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SMO.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.classifiers.functions.supportVector.SMOset;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SerializedObject;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements John Platt's sequential minimal optimization algorithm for training a support vector classifier.<br/>
+ * <br/>
+ * This implementation globally replaces all missing values and transforms nominal attributes into binary ones. It also normalizes all attributes by default. (In that case the coefficients in the output are based on the normalized data, not the original data --- this is important for interpreting the classifier.)<br/>
+ * <br/>
+ * Multi-class problems are solved using pairwise classification (1-vs-1 and if logistic models are built pairwise coupling according to Hastie and Tibshirani, 1998).<br/>
+ * <br/>
+ * To obtain proper probability estimates, use the option that fits logistic regression models to the outputs of the support vector machine. In the multi-class case the predicted probabilities are coupled using Hastie and Tibshirani's pairwise coupling method.<br/>
+ * <br/>
+ * Note: for improved speed normalization should be turned off when operating on SparseInstances.<br/>
+ * <br/>
+ * For more information on the SMO algorithm, see<br/>
+ * <br/>
+ * J. Platt: Fast Training of Support Vector Machines using Sequential Minimal Optimization. In B. Schoelkopf and C. Burges and A. Smola, editors, Advances in Kernel Methods - Support Vector Learning, 1998.<br/>
+ * <br/>
+ * S.S. Keerthi, S.K. Shevade, C. Bhattacharyya, K.R.K. Murthy (2001). Improvements to Platt's SMO Algorithm for SVM Classifier Design. Neural Computation. 13(3):637-649.<br/>
+ * <br/>
+ * Trevor Hastie, Robert Tibshirani: Classification by Pairwise Coupling. In: Advances in Neural Information Processing Systems, 1998.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;incollection{Platt1998,
+ *    author = {J. Platt},
+ *    booktitle = {Advances in Kernel Methods - Support Vector Learning},
+ *    editor = {B. Schoelkopf and C. Burges and A. Smola},
+ *    publisher = {MIT Press},
+ *    title = {Fast Training of Support Vector Machines using Sequential Minimal Optimization},
+ *    year = {1998},
+ *    URL = {http://research.microsoft.com/\~jplatt/smo.html},
+ *    PS = {http://research.microsoft.com/\~jplatt/smo-book.ps.gz},
+ *    PDF = {http://research.microsoft.com/\~jplatt/smo-book.pdf}
+ * }
+ * 
+ * &#64;article{Keerthi2001,
+ *    author = {S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy},
+ *    journal = {Neural Computation},
+ *    number = {3},
+ *    pages = {637-649},
+ *    title = {Improvements to Platt's SMO Algorithm for SVM Classifier Design},
+ *    volume = {13},
+ *    year = {2001},
+ *    PS = {http://guppy.mpe.nus.edu.sg/\~mpessk/svm/smo_mod_nc.ps.gz}
+ * }
+ * 
+ * &#64;inproceedings{Hastie1998,
+ *    author = {Trevor Hastie and Robert Tibshirani},
+ *    booktitle = {Advances in Neural Information Processing Systems},
+ *    editor = {Michael I. Jordan and Michael J. Kearns and Sara A. Solla},
+ *    publisher = {MIT Press},
+ *    title = {Classification by Pairwise Coupling},
+ *    volume = {10},
+ *    year = {1998},
+ *    PS = {http://www-stat.stanford.edu/\~hastie/Papers/2class.ps}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  Turning them off assumes that data is purely numeric, doesn't
+ *  contain any missing values, and has a nominal class. Turning them
+ *  off also means that no header information will be stored if the
+ *  machine is linear. Finally, it also assumes that no instance has
+ *  a weight equal to 0.
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  The complexity constant C. (default 1)</pre>
+ * 
+ * <pre> -N
+ *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The tolerance parameter. (default 1.0e-3)</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error. (default 1.0e-12)</pre>
+ * 
+ * <pre> -M
+ *  Fit logistic models to SVM outputs. </pre>
+ * 
+ * <pre> -V &lt;double&gt;
+ *  The number of folds for the internal
+ *  cross-validation. (default -1, use training data)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed. (default 1)</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @version $Revision: 6024 $
+ */
+public class SMO 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -6585883636378691736L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Implements John Platt's sequential minimal optimization "
+      + "algorithm for training a support vector classifier.\n\n"
+      + "This implementation globally replaces all missing values and "
+      + "transforms nominal attributes into binary ones. It also "
+      + "normalizes all attributes by default. (In that case the coefficients "
+      + "in the output are based on the normalized data, not the "
+      + "original data --- this is important for interpreting the classifier.)\n\n"
+      + "Multi-class problems are solved using pairwise classification "
+      + "(1-vs-1 and if logistic models are built pairwise coupling "
+      + "according to Hastie and Tibshirani, 1998).\n\n"
+      + "To obtain proper probability estimates, use the option that fits "
+      + "logistic regression models to the outputs of the support vector "
+      + "machine. In the multi-class case the predicted probabilities "
+      + "are coupled using Hastie and Tibshirani's pairwise coupling "
+      + "method.\n\n"
+      + "Note: for improved speed normalization should be turned off when "
+      + "operating on SparseInstances.\n\n"
+      + "For more information on the SMO algorithm, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INCOLLECTION);
+    result.setValue(Field.AUTHOR, "J. Platt");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Fast Training of Support Vector Machines using Sequential Minimal Optimization");
+    result.setValue(Field.BOOKTITLE, "Advances in Kernel Methods - Support Vector Learning");
+    result.setValue(Field.EDITOR, "B. Schoelkopf and C. Burges and A. Smola");
+    result.setValue(Field.PUBLISHER, "MIT Press");
+    result.setValue(Field.URL, "http://research.microsoft.com/~jplatt/smo.html");
+    result.setValue(Field.PDF, "http://research.microsoft.com/~jplatt/smo-book.pdf");
+    result.setValue(Field.PS, "http://research.microsoft.com/~jplatt/smo-book.ps.gz");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy");
+    additional.setValue(Field.YEAR, "2001");
+    additional.setValue(Field.TITLE, "Improvements to Platt's SMO Algorithm for SVM Classifier Design");
+    additional.setValue(Field.JOURNAL, "Neural Computation");
+    additional.setValue(Field.VOLUME, "13");
+    additional.setValue(Field.NUMBER, "3");
+    additional.setValue(Field.PAGES, "637-649");
+    additional.setValue(Field.PS, "http://guppy.mpe.nus.edu.sg/~mpessk/svm/smo_mod_nc.ps.gz");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Trevor Hastie and Robert Tibshirani");
+    additional.setValue(Field.YEAR, "1998");
+    additional.setValue(Field.TITLE, "Classification by Pairwise Coupling");
+    additional.setValue(Field.BOOKTITLE, "Advances in Neural Information Processing Systems");
+    additional.setValue(Field.VOLUME, "10");
+    additional.setValue(Field.PUBLISHER, "MIT Press");
+    additional.setValue(Field.EDITOR, "Michael I. Jordan and Michael J. Kearns and Sara A. Solla");
+    additional.setValue(Field.PS, "http://www-stat.stanford.edu/~hastie/Papers/2class.ps");
+    
+    return result;
+  }
+
+  /**
+   * Class for building a binary support vector machine.
+   */
+  public class BinarySMO 
+    implements Serializable {
+    
+    /** for serialization */
+    static final long serialVersionUID = -8246163625699362456L;
+    
+    /** The Lagrange multipliers. */
+    protected double[] m_alpha;
+
+    /** The thresholds. */
+    protected double m_b, m_bLow, m_bUp;
+
+    /** The indices for m_bLow and m_bUp */
+    protected int m_iLow, m_iUp;
+
+    /** The training data. */
+    protected Instances m_data;
+
+    /** Weight vector for linear machine. */
+    protected double[] m_weights;
+
+    /** Variables to hold weight vector in sparse form.
+	(To reduce storage requirements.) */
+    protected double[] m_sparseWeights;
+    protected int[] m_sparseIndices;
+
+    /** Kernel to use **/
+    protected Kernel m_kernel;
+
+    /** The transformed class values. */
+    protected double[] m_class;
+
+    /** The current set of errors for all non-bound examples. */
+    protected double[] m_errors;
+
+    /* The five different sets used by the algorithm. */
+    /** {i: 0 < m_alpha[i] < C} */
+    protected SMOset m_I0;
+    /**  {i: m_class[i] = 1, m_alpha[i] = 0} */
+    protected SMOset m_I1; 
+    /**  {i: m_class[i] = -1, m_alpha[i] =C} */
+    protected SMOset m_I2; 
+    /** {i: m_class[i] = 1, m_alpha[i] = C} */
+    protected SMOset m_I3;
+    /**  {i: m_class[i] = -1, m_alpha[i] = 0} */
+    protected SMOset m_I4; 
+
+    /** The set of support vectors */
+    protected SMOset m_supportVectors; // {i: 0 < m_alpha[i]}
+
+    /** Stores logistic regression model for probability estimate */
+    protected Logistic m_logistic = null;
+
+    /** Stores the weight of the training instances */
+    protected double m_sumOfWeights = 0;
+
+    /**
+     * Fits logistic regression model to SVM outputs analogue
+     * to John Platt's method.  
+     *
+     * @param insts the set of training instances
+     * @param cl1 the first class' index
+     * @param cl2 the second class' index
+     * @param numFolds the number of folds for cross-validation
+     * @param random for randomizing the data
+     * @throws Exception if the sigmoid can't be fit successfully
+     */
+    protected void fitLogistic(Instances insts, int cl1, int cl2,
+			     int numFolds, Random random) 
+      throws Exception {
+
+      // Create header of instances object
+      FastVector atts = new FastVector(2);
+      atts.addElement(new Attribute("pred"));
+      FastVector attVals = new FastVector(2);
+      attVals.addElement(insts.classAttribute().value(cl1));
+      attVals.addElement(insts.classAttribute().value(cl2));
+      atts.addElement(new Attribute("class", attVals));
+      Instances data = new Instances("data", atts, insts.numInstances());
+      data.setClassIndex(1);
+
+      // Collect data for fitting the logistic model
+      if (numFolds <= 0) {
+
+	// Use training data
+	for (int j = 0; j < insts.numInstances(); j++) {
+	  Instance inst = insts.instance(j);
+	  double[] vals = new double[2];
+	  vals[0] = SVMOutput(-1, inst);
+	  if (inst.classValue() == cl2) {
+	    vals[1] = 1;
+	  }
+	  data.add(new DenseInstance(inst.weight(), vals));
+	}
+      } else {
+
+	// Check whether number of folds too large
+	if (numFolds > insts.numInstances()) {
+	  numFolds = insts.numInstances();
+	}
+
+	// Make copy of instances because we will shuffle them around
+	insts = new Instances(insts);
+	
+	// Perform three-fold cross-validation to collect
+	// unbiased predictions
+	insts.randomize(random);
+	insts.stratify(numFolds);
+	for (int i = 0; i < numFolds; i++) {
+	  Instances train = insts.trainCV(numFolds, i, random);
+          /*	  SerializedObject so = new SerializedObject(this);
+                  BinarySMO smo = (BinarySMO)so.getObject(); */
+          BinarySMO smo = new BinarySMO();
+          smo.setKernel(Kernel.makeCopy(SMO.this.m_kernel));
+          smo.buildClassifier(train, cl1, cl2, false, -1, -1);
+	  Instances test = insts.testCV(numFolds, i);
+	  for (int j = 0; j < test.numInstances(); j++) {
+	    double[] vals = new double[2];
+	    vals[0] = smo.SVMOutput(-1, test.instance(j));
+	    if (test.instance(j).classValue() == cl2) {
+	      vals[1] = 1;
+	    }
+	    data.add(new DenseInstance(test.instance(j).weight(), vals));
+	  }
+	}
+      }
+
+      // Build logistic regression model
+      m_logistic = new Logistic();
+      m_logistic.buildClassifier(data);
+    }
+    
+    /**
+     * sets the kernel to use
+     * 
+     * @param value	the kernel to use
+     */
+    public void setKernel(Kernel value) {
+      m_kernel = value;
+    }
+    
+    /**
+     * Returns the kernel to use
+     * 
+     * @return 		the current kernel
+     */
+    public Kernel getKernel() {
+      return m_kernel;
+    }
+
+    /**
+     * Method for building the binary classifier.
+     *
+     * @param insts the set of training instances
+     * @param cl1 the first class' index
+     * @param cl2 the second class' index
+     * @param fitLogistic true if logistic model is to be fit
+     * @param numFolds number of folds for internal cross-validation
+     * @param randomSeed random number generator for cross-validation
+     * @throws Exception if the classifier can't be built successfully
+     */
+    protected void buildClassifier(Instances insts, int cl1, int cl2,
+				 boolean fitLogistic, int numFolds,
+				 int randomSeed) throws Exception {
+      
+      // Initialize some variables
+      m_bUp = -1; m_bLow = 1; m_b = 0; 
+      m_alpha = null; m_data = null; m_weights = null; m_errors = null;
+      m_logistic = null; m_I0 = null; m_I1 = null; m_I2 = null;
+      m_I3 = null; m_I4 = null;	m_sparseWeights = null; m_sparseIndices = null;
+
+      // Store the sum of weights
+      m_sumOfWeights = insts.sumOfWeights();
+      
+      // Set class values
+      m_class = new double[insts.numInstances()];
+      m_iUp = -1; m_iLow = -1;
+      for (int i = 0; i < m_class.length; i++) {
+	if ((int) insts.instance(i).classValue() == cl1) {
+	  m_class[i] = -1; m_iLow = i;
+	} else if ((int) insts.instance(i).classValue() == cl2) {
+	  m_class[i] = 1; m_iUp = i;
+	} else {
+	  throw new Exception ("This should never happen!");
+	}
+      }
+
+      // Check whether one or both classes are missing
+      if ((m_iUp == -1) || (m_iLow == -1)) {
+	if (m_iUp != -1) {
+	  m_b = -1;
+	} else if (m_iLow != -1) {
+	  m_b = 1;
+	} else {
+	  m_class = null;
+	  return;
+	}
+	if (m_KernelIsLinear) {
+	  m_sparseWeights = new double[0];
+	  m_sparseIndices = new int[0];
+	  m_class = null;
+	} else {
+	  m_supportVectors = new SMOset(0);
+	  m_alpha = new double[0];
+	  m_class = new double[0];
+	}
+
+	// Fit sigmoid if requested
+	if (fitLogistic) {
+	  fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
+	}
+	return;
+      }
+      
+      // Set the reference to the data
+      m_data = insts;
+
+      // If machine is linear, reserve space for weights
+      if (m_KernelIsLinear) {
+	m_weights = new double[m_data.numAttributes()];
+      } else {
+	m_weights = null;
+      }
+      
+      // Initialize alpha array to zero
+      m_alpha = new double[m_data.numInstances()];
+      
+      // Initialize sets
+      m_supportVectors = new SMOset(m_data.numInstances());
+      m_I0 = new SMOset(m_data.numInstances());
+      m_I1 = new SMOset(m_data.numInstances());
+      m_I2 = new SMOset(m_data.numInstances());
+      m_I3 = new SMOset(m_data.numInstances());
+      m_I4 = new SMOset(m_data.numInstances());
+
+      // Clean out some instance variables
+      m_sparseWeights = null;
+      m_sparseIndices = null;
+      
+      // init kernel
+      m_kernel.buildKernel(m_data);
+      
+      // Initialize error cache
+      m_errors = new double[m_data.numInstances()];
+      m_errors[m_iLow] = 1; m_errors[m_iUp] = -1;
+     
+      // Build up I1 and I4
+      for (int i = 0; i < m_class.length; i++ ) {
+	if (m_class[i] == 1) {
+	  m_I1.insert(i);
+	} else {
+	  m_I4.insert(i);
+	}
+      }
+      
+      // Loop to find all the support vectors
+      int numChanged = 0;
+      boolean examineAll = true;
+      while ((numChanged > 0) || examineAll) {
+	numChanged = 0;
+	if (examineAll) {
+	  for (int i = 0; i < m_alpha.length; i++) {
+	    if (examineExample(i)) {
+	      numChanged++;
+	    }
+	  }
+	} else {
+	  
+	  // This code implements Modification 1 from Keerthi et al.'s paper
+	  for (int i = 0; i < m_alpha.length; i++) {
+	    if ((m_alpha[i] > 0) &&  
+		(m_alpha[i] < m_C * m_data.instance(i).weight())) {
+	      if (examineExample(i)) {
+		numChanged++;
+	      }
+	      
+	      // Is optimality on unbound vectors obtained?
+	      if (m_bUp > m_bLow - 2 * m_tol) {
+		numChanged = 0;
+		break;
+	      }
+	    }
+	  }
+	  
+	  //This is the code for Modification 2 from Keerthi et al.'s paper
+	  /*boolean innerLoopSuccess = true; 
+	    numChanged = 0;
+	    while ((m_bUp < m_bLow - 2 * m_tol) && (innerLoopSuccess == true)) {
+	    innerLoopSuccess = takeStep(m_iUp, m_iLow, m_errors[m_iLow]);
+	    }*/
+	}
+	
+	if (examineAll) {
+	  examineAll = false;
+	} else if (numChanged == 0) {
+	  examineAll = true;
+	}
+      }
+      
+      // Set threshold
+      m_b = (m_bLow + m_bUp) / 2.0;
+      
+      // Save memory
+      m_kernel.clean(); 
+      
+      m_errors = null;
+      m_I0 = m_I1 = m_I2 = m_I3 = m_I4 = null;
+      
+      // If machine is linear, delete training data
+      // and store weight vector in sparse format
+      if (m_KernelIsLinear) {
+	
+	// We don't need to store the set of support vectors
+	m_supportVectors = null;
+
+	// We don't need to store the class values either
+	m_class = null;
+	
+	// Clean out training data
+	if (!m_checksTurnedOff) {
+	  m_data = new Instances(m_data, 0);
+	} else {
+	  m_data = null;
+	}
+	
+	// Convert weight vector
+	double[] sparseWeights = new double[m_weights.length];
+	int[] sparseIndices = new int[m_weights.length];
+	int counter = 0;
+	for (int i = 0; i < m_weights.length; i++) {
+	  if (m_weights[i] != 0.0) {
+	    sparseWeights[counter] = m_weights[i];
+	    sparseIndices[counter] = i;
+	    counter++;
+	  }
+	}
+	m_sparseWeights = new double[counter];
+	m_sparseIndices = new int[counter];
+	System.arraycopy(sparseWeights, 0, m_sparseWeights, 0, counter);
+	System.arraycopy(sparseIndices, 0, m_sparseIndices, 0, counter);
+	
+	// Clean out weight vector
+	m_weights = null;
+	
+	// We don't need the alphas in the linear case
+	m_alpha = null;
+      }
+      
+      // Fit sigmoid if requested
+      if (fitLogistic) {
+	fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
+      }
+
+    }
+    
+    /**
+     * Computes SVM output for given instance.
+     *
+     * @param index the instance for which output is to be computed
+     * @param inst the instance 
+     * @return the output of the SVM for the given instance
+     * @throws Exception in case of an error
+     */
+    public double SVMOutput(int index, Instance inst) throws Exception {
+      
+      double result = 0;
+      
+      // Is the machine linear?
+      if (m_KernelIsLinear) {
+	
+	// Is weight vector stored in sparse format?
+	if (m_sparseWeights == null) {
+	  int n1 = inst.numValues(); 
+	  for (int p = 0; p < n1; p++) {
+	    if (inst.index(p) != m_classIndex) {
+	      result += m_weights[inst.index(p)] * inst.valueSparse(p);
+	    }
+	  }
+	} else {
+	  int n1 = inst.numValues(); int n2 = m_sparseWeights.length;
+	  for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
+	    int ind1 = inst.index(p1); 
+	    int ind2 = m_sparseIndices[p2];
+	    if (ind1 == ind2) {
+	      if (ind1 != m_classIndex) {
+		result += inst.valueSparse(p1) * m_sparseWeights[p2];
+	      }
+	      p1++; p2++;
+	    } else if (ind1 > ind2) {
+	      p2++;
+	    } else { 
+	      p1++;
+	    }
+	  }
+	}
+      } else {
+	for (int i = m_supportVectors.getNext(-1); i != -1; 
+	     i = m_supportVectors.getNext(i)) {
+	  result += m_class[i] * m_alpha[i] * m_kernel.eval(index, i, inst);
+	}
+      }
+      result -= m_b;
+      
+      return result;
+    }
+
+    /**
+     * Prints out the classifier.
+     *
+     * @return a description of the classifier as a string
+     */
+    public String toString() {
+
+      StringBuffer text = new StringBuffer();
+      int printed = 0;
+
+      if ((m_alpha == null) && (m_sparseWeights == null)) {
+	return "BinarySMO: No model built yet.\n";
+      }
+      try {
+	text.append("BinarySMO\n\n");
+
+	// If machine linear, print weight vector
+	if (m_KernelIsLinear) {
+	  text.append("Machine linear: showing attribute weights, ");
+	  text.append("not support vectors.\n\n");
+
+	  // We can assume that the weight vector is stored in sparse
+	  // format because the classifier has been built
+	  for (int i = 0; i < m_sparseWeights.length; i++) {
+	    if (m_sparseIndices[i] != (int)m_classIndex) {
+	      if (printed > 0) {
+		text.append(" + ");
+	      } else {
+		text.append("   ");
+	      }
+	      text.append(Utils.doubleToString(m_sparseWeights[i], 12, 4) +
+			  " * ");
+	      if (m_filterType == FILTER_STANDARDIZE) {
+		text.append("(standardized) ");
+	      } else if (m_filterType == FILTER_NORMALIZE) {
+		text.append("(normalized) ");
+	      }
+	      if (!m_checksTurnedOff) {
+		text.append(m_data.attribute(m_sparseIndices[i]).name()+"\n");
+	      } else {
+		text.append("attribute with index " + 
+			    m_sparseIndices[i] +"\n");
+	      }
+	      printed++;
+	    }
+	  }
+	} else {
+	  for (int i = 0; i < m_alpha.length; i++) {
+	    if (m_supportVectors.contains(i)) {
+	      double val = m_alpha[i];
+	      if (m_class[i] == 1) {
+		if (printed > 0) {
+		  text.append(" + ");
+		}
+	      } else {
+		text.append(" - ");
+	      }
+	      text.append(Utils.doubleToString(val, 12, 4) 
+			  + " * <");
+	      for (int j = 0; j < m_data.numAttributes(); j++) {
+		if (j != m_data.classIndex()) {
+		  text.append(m_data.instance(i).toString(j));
+		}
+		if (j != m_data.numAttributes() - 1) {
+		  text.append(" ");
+		}
+	      }
+	      text.append("> * X]\n");
+	      printed++;
+	    }
+	  }
+	}
+	if (m_b > 0) {
+	  text.append(" - " + Utils.doubleToString(m_b, 12, 4));
+	} else {
+	  text.append(" + " + Utils.doubleToString(-m_b, 12, 4));
+	}
+
+	if (!m_KernelIsLinear) {
+	  text.append("\n\nNumber of support vectors: " + 
+		      m_supportVectors.numElements());
+	}
+	int numEval = 0;
+	int numCacheHits = -1;
+	if (m_kernel != null) {
+	  numEval = m_kernel.numEvals();
+	  numCacheHits = m_kernel.numCacheHits();
+	}
+	text.append("\n\nNumber of kernel evaluations: " + numEval);
+	if (numCacheHits >= 0 && numEval > 0) {
+	  double hitRatio = 1 - numEval*1.0/(numCacheHits+numEval);
+	  text.append(" (" + Utils.doubleToString(hitRatio*100, 7, 3).trim() + "% cached)");
+	}
+
+      } catch (Exception e) {
+	e.printStackTrace();
+
+	return "Can't print BinarySMO classifier.";
+      }
+    
+      return text.toString();
+    }
+
+    /**
+     * Examines instance.
+     *
+     * @param i2 index of instance to examine
+     * @return true if examination was successfull
+     * @throws Exception if something goes wrong
+     */
+    protected boolean examineExample(int i2) throws Exception {
+    
+      double y2, F2;
+      int i1 = -1;
+    
+      y2 = m_class[i2];
+      if (m_I0.contains(i2)) {
+	F2 = m_errors[i2];
+      } else {
+	F2 = SVMOutput(i2, m_data.instance(i2)) + m_b - y2;
+	m_errors[i2] = F2;
+      
+	// Update thresholds
+	if ((m_I1.contains(i2) || m_I2.contains(i2)) && (F2 < m_bUp)) {
+	  m_bUp = F2; m_iUp = i2;
+	} else if ((m_I3.contains(i2) || m_I4.contains(i2)) && (F2 > m_bLow)) {
+	  m_bLow = F2; m_iLow = i2;
+	}
+      }
+
+      // Check optimality using current bLow and bUp and, if
+      // violated, find an index i1 to do joint optimization
+      // with i2...
+      boolean optimal = true;
+      if (m_I0.contains(i2) || m_I1.contains(i2) || m_I2.contains(i2)) {
+	if (m_bLow - F2 > 2 * m_tol) {
+	  optimal = false; i1 = m_iLow;
+	}
+      }
+      if (m_I0.contains(i2) || m_I3.contains(i2) || m_I4.contains(i2)) {
+	if (F2 - m_bUp > 2 * m_tol) {
+	  optimal = false; i1 = m_iUp;
+	}
+      }
+      if (optimal) {
+	return false;
+      }
+
+      // For i2 unbound choose the better i1...
+      if (m_I0.contains(i2)) {
+	if (m_bLow - F2 > F2 - m_bUp) {
+	  i1 = m_iLow;
+	} else {
+	  i1 = m_iUp;
+	}
+      }
+      if (i1 == -1) {
+	throw new Exception("This should never happen!");
+      }
+      return takeStep(i1, i2, F2);
+    }
+
+    /**
+     * Method solving for the Lagrange multipliers for
+     * two instances.
+     *
+     * @param i1 index of the first instance
+     * @param i2 index of the second instance
+     * @param F2
+     * @return true if multipliers could be found
+     * @throws Exception if something goes wrong
+     */
+    protected boolean takeStep(int i1, int i2, double F2) throws Exception {
+
+      double alph1, alph2, y1, y2, F1, s, L, H, k11, k12, k22, eta,
+	a1, a2, f1, f2, v1, v2, Lobj, Hobj;
+      double C1 = m_C * m_data.instance(i1).weight();
+      double C2 = m_C * m_data.instance(i2).weight();
+
+      // Don't do anything if the two instances are the same
+      if (i1 == i2) {
+	return false;
+      }
+
+      // Initialize variables
+      alph1 = m_alpha[i1]; alph2 = m_alpha[i2];
+      y1 = m_class[i1]; y2 = m_class[i2];
+      F1 = m_errors[i1];
+      s = y1 * y2;
+
+      // Find the constraints on a2
+      if (y1 != y2) {
+	L = Math.max(0, alph2 - alph1); 
+	H = Math.min(C2, C1 + alph2 - alph1);
+      } else {
+	L = Math.max(0, alph1 + alph2 - C1);
+	H = Math.min(C2, alph1 + alph2);
+      }
+      if (L >= H) {
+	return false;
+      }
+
+      // Compute second derivative of objective function
+      k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
+      k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
+      k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
+      eta = 2 * k12 - k11 - k22;
+
+      // Check if second derivative is negative
+      if (eta < 0) {
+
+	// Compute unconstrained maximum
+	a2 = alph2 - y2 * (F1 - F2) / eta;
+
+	// Compute constrained maximum
+	if (a2 < L) {
+	  a2 = L;
+	} else if (a2 > H) {
+	  a2 = H;
+	}
+      } else {
+
+	// Look at endpoints of diagonal
+	f1 = SVMOutput(i1, m_data.instance(i1));
+	f2 = SVMOutput(i2, m_data.instance(i2));
+	v1 = f1 + m_b - y1 * alph1 * k11 - y2 * alph2 * k12; 
+	v2 = f2 + m_b - y1 * alph1 * k12 - y2 * alph2 * k22; 
+	double gamma = alph1 + s * alph2;
+	Lobj = (gamma - s * L) + L - 0.5 * k11 * (gamma - s * L) * (gamma - s * L) - 
+	  0.5 * k22 * L * L - s * k12 * (gamma - s * L) * L - 
+	  y1 * (gamma - s * L) * v1 - y2 * L * v2;
+	Hobj = (gamma - s * H) + H - 0.5 * k11 * (gamma - s * H) * (gamma - s * H) - 
+	  0.5 * k22 * H * H - s * k12 * (gamma - s * H) * H - 
+	  y1 * (gamma - s * H) * v1 - y2 * H * v2;
+	if (Lobj > Hobj + m_eps) {
+	  a2 = L;
+	} else if (Lobj < Hobj - m_eps) {
+	  a2 = H;
+	} else {
+	  a2 = alph2;
+	}
+      }
+      if (Math.abs(a2 - alph2) < m_eps * (a2 + alph2 + m_eps)) {
+	return false;
+      }
+      
+      // To prevent precision problems
+      if (a2 > C2 - m_Del * C2) {
+	a2 = C2;
+      } else if (a2 <= m_Del * C2) {
+	a2 = 0;
+      }
+      
+      // Recompute a1
+      a1 = alph1 + s * (alph2 - a2);
+      
+      // To prevent precision problems
+      if (a1 > C1 - m_Del * C1) {
+	a1 = C1;
+      } else if (a1 <= m_Del * C1) {
+	a1 = 0;
+      }
+      
+      // Update sets
+      if (a1 > 0) {
+	m_supportVectors.insert(i1);
+      } else {
+	m_supportVectors.delete(i1);
+      }
+      if ((a1 > 0) && (a1 < C1)) {
+	m_I0.insert(i1);
+      } else {
+	m_I0.delete(i1);
+      }
+      if ((y1 == 1) && (a1 == 0)) {
+	m_I1.insert(i1);
+      } else {
+	m_I1.delete(i1);
+      }
+      if ((y1 == -1) && (a1 == C1)) {
+	m_I2.insert(i1);
+      } else {
+	m_I2.delete(i1);
+      }
+      if ((y1 == 1) && (a1 == C1)) {
+	m_I3.insert(i1);
+      } else {
+	m_I3.delete(i1);
+      }
+      if ((y1 == -1) && (a1 == 0)) {
+	m_I4.insert(i1);
+      } else {
+	m_I4.delete(i1);
+      }
+      if (a2 > 0) {
+	m_supportVectors.insert(i2);
+      } else {
+	m_supportVectors.delete(i2);
+      }
+      if ((a2 > 0) && (a2 < C2)) {
+	m_I0.insert(i2);
+      } else {
+	m_I0.delete(i2);
+      }
+      if ((y2 == 1) && (a2 == 0)) {
+	m_I1.insert(i2);
+      } else {
+	m_I1.delete(i2);
+      }
+      if ((y2 == -1) && (a2 == C2)) {
+	m_I2.insert(i2);
+      } else {
+	m_I2.delete(i2);
+      }
+      if ((y2 == 1) && (a2 == C2)) {
+	m_I3.insert(i2);
+      } else {
+	m_I3.delete(i2);
+      }
+      if ((y2 == -1) && (a2 == 0)) {
+	m_I4.insert(i2);
+      } else {
+	m_I4.delete(i2);
+      }
+      
+      // Update weight vector to reflect change a1 and a2, if linear SVM
+      if (m_KernelIsLinear) {
+	Instance inst1 = m_data.instance(i1);
+	for (int p1 = 0; p1 < inst1.numValues(); p1++) {
+	  if (inst1.index(p1) != m_data.classIndex()) {
+	    m_weights[inst1.index(p1)] += 
+	      y1 * (a1 - alph1) * inst1.valueSparse(p1);
+	  }
+	}
+	Instance inst2 = m_data.instance(i2);
+	for (int p2 = 0; p2 < inst2.numValues(); p2++) {
+	  if (inst2.index(p2) != m_data.classIndex()) {
+	    m_weights[inst2.index(p2)] += 
+	      y2 * (a2 - alph2) * inst2.valueSparse(p2);
+	  }
+	}
+      }
+      
+      // Update error cache using new Lagrange multipliers
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+	if ((j != i1) && (j != i2)) {
+	  m_errors[j] += 
+	    y1 * (a1 - alph1) * m_kernel.eval(i1, j, m_data.instance(i1)) + 
+	    y2 * (a2 - alph2) * m_kernel.eval(i2, j, m_data.instance(i2));
+	}
+      }
+      
+      // Update error cache for i1 and i2
+      m_errors[i1] += y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12;
+      m_errors[i2] += y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22;
+      
+      // Update array with Lagrange multipliers
+      m_alpha[i1] = a1;
+      m_alpha[i2] = a2;
+      
+      // Update thresholds
+      m_bLow = -Double.MAX_VALUE; m_bUp = Double.MAX_VALUE;
+      m_iLow = -1; m_iUp = -1;
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+	if (m_errors[j] < m_bUp) {
+	  m_bUp = m_errors[j]; m_iUp = j;
+	}
+	if (m_errors[j] > m_bLow) {
+	  m_bLow = m_errors[j]; m_iLow = j;
+	}
+      }
+      if (!m_I0.contains(i1)) {
+	if (m_I3.contains(i1) || m_I4.contains(i1)) {
+	  if (m_errors[i1] > m_bLow) {
+	    m_bLow = m_errors[i1]; m_iLow = i1;
+	  } 
+	} else {
+	  if (m_errors[i1] < m_bUp) {
+	    m_bUp = m_errors[i1]; m_iUp = i1;
+	  }
+	}
+      }
+      if (!m_I0.contains(i2)) {
+	if (m_I3.contains(i2) || m_I4.contains(i2)) {
+	  if (m_errors[i2] > m_bLow) {
+	    m_bLow = m_errors[i2]; m_iLow = i2;
+	  }
+	} else {
+	  if (m_errors[i2] < m_bUp) {
+	    m_bUp = m_errors[i2]; m_iUp = i2;
+	  }
+	}
+      }
+      if ((m_iLow == -1) || (m_iUp == -1)) {
+	throw new Exception("This should never happen!");
+      }
+
+      // Made some progress.
+      return true;
+    }
+  
+    /**
+     * Quick and dirty check whether the quadratic programming problem is solved.
+     * 
+     * @throws Exception if checking fails
+     */
+    protected void checkClassifier() throws Exception {
+
+      double sum = 0;
+      for (int i = 0; i < m_alpha.length; i++) {
+	if (m_alpha[i] > 0) {
+	  sum += m_class[i] * m_alpha[i];
+	}
+      }
+      System.err.println("Sum of y(i) * alpha(i): " + sum);
+
+      for (int i = 0; i < m_alpha.length; i++) {
+	double output = SVMOutput(i, m_data.instance(i));
+	if (Utils.eq(m_alpha[i], 0)) {
+	  if (Utils.sm(m_class[i] * output, 1)) {
+	    System.err.println("KKT condition 1 violated: " + m_class[i] * output);
+	  }
+	} 
+	if (Utils.gr(m_alpha[i], 0) && 
+	    Utils.sm(m_alpha[i], m_C * m_data.instance(i).weight())) {
+	  if (!Utils.eq(m_class[i] * output, 1)) {
+	    System.err.println("KKT condition 2 violated: " + m_class[i] * output);
+	  }
+	} 
+	if (Utils.eq(m_alpha[i], m_C * m_data.instance(i).weight())) {
+	  if (Utils.gr(m_class[i] * output, 1)) {
+	    System.err.println("KKT condition 3 violated: " + m_class[i] * output);
+	  }
+	} 
+      }
+    }  
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6024 $");
+    }
+  }
+
+  /** filter: Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** filter: Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** filter: No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** The binary classifier(s) */
+  protected BinarySMO[][] m_classifiers = null;
+  
+  /** The complexity parameter. */
+  protected double m_C = 1.0;
+  
+  /** Epsilon for rounding. */
+  protected double m_eps = 1.0e-12;
+  
+  /** Tolerance for accuracy of result. */
+  protected double m_tol = 1.0e-3;
+
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing;
+
+  /** The class index from the training data */
+  protected int m_classIndex = -1;
+
+  /** The class attribute */
+  protected Attribute m_classAttribute;
+  
+  /** whether the kernel is a linear one */
+  protected boolean m_KernelIsLinear = false;
+
+  /** Turn off all checks and conversions? Turning them off assumes
+      that data is purely numeric, doesn't contain any missing values,
+      and has a nominal class. Turning them off also means that
+      no header information will be stored if the machine is linear. 
+      Finally, it also assumes that no instance has a weight equal to 0.*/
+  protected boolean m_checksTurnedOff;
+
+  /** Precision constant for updating sets */
+  protected static double m_Del = 1000 * Double.MIN_VALUE;
+
+  /** Whether logistic models are to be fit */
+  protected boolean m_fitLogisticModels = false;
+
+  /** The number of folds for the internal cross-validation */
+  protected int m_numFolds = -1;
+
+  /** The random number seed  */
+  protected int m_randomSeed = 1;
+
+  /** the kernel to use */
+  protected Kernel m_kernel = new PolyKernel();
+  
+  /**
+   * Turns off checks for missing values, etc. Use with caution.
+   */
+  public void turnChecksOff() {
+
+    m_checksTurnedOff = true;
+  }
+
+  /**
+   * Turns on checks for missing values, etc.
+   */
+  public void turnChecksOn() {
+
+    m_checksTurnedOff = false;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = getKernel().getCapabilities();
+    result.setOwner(this);
+    
+    // attribute
+    result.enableAllAttributeDependencies();
+    // with NominalToBinary we can also handle nominal attributes, but only
+    // if the kernel can handle numeric attributes
+    if (result.handles(Capability.NUMERIC_ATTRIBUTES))
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Method for building the classifier. Implements a one-against-one
+   * wrapper for multi-class problems.
+   *
+   * @param insts the set of training instances
+   * @throws Exception if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    if (!m_checksTurnedOff) {
+      // can classifier handle the data?
+      getCapabilities().testWithFail(insts);
+
+      // remove instances with missing class
+      insts = new Instances(insts);
+      insts.deleteWithMissingClass();
+      
+      /* Removes all the instances with weight equal to 0.
+       MUST be done since condition (8) of Keerthi's paper 
+       is made with the assertion Ci > 0 (See equation (3a). */
+      Instances data = new Instances(insts, insts.numInstances());
+      for(int i = 0; i < insts.numInstances(); i++){
+        if(insts.instance(i).weight() > 0)
+          data.add(insts.instance(i));
+      }
+      if (data.numInstances() == 0) {
+        throw new Exception("No training instances left after removing " + 
+        "instances with weight 0!");
+      }
+      insts = data;
+    }
+
+    if (!m_checksTurnedOff) {
+      m_Missing = new ReplaceMissingValues();
+      m_Missing.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Missing); 
+    } else {
+      m_Missing = null;
+    }
+
+    if (getCapabilities().handles(Capability.NUMERIC_ATTRIBUTES)) {
+      boolean onlyNumeric = true;
+      if (!m_checksTurnedOff) {
+	for (int i = 0; i < insts.numAttributes(); i++) {
+	  if (i != insts.classIndex()) {
+	    if (!insts.attribute(i).isNumeric()) {
+	      onlyNumeric = false;
+	      break;
+	    }
+	  }
+	}
+      }
+      
+      if (!onlyNumeric) {
+	m_NominalToBinary = new NominalToBinary();
+	m_NominalToBinary.setInputFormat(insts);
+	insts = Filter.useFilter(insts, m_NominalToBinary);
+      } 
+      else {
+	m_NominalToBinary = null;
+      }
+    }
+    else {
+      m_NominalToBinary = null;
+    }
+
+    if (m_filterType == FILTER_STANDARDIZE) {
+      m_Filter = new Standardize();
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter); 
+    } else if (m_filterType == FILTER_NORMALIZE) {
+      m_Filter = new Normalize();
+      m_Filter.setInputFormat(insts);
+      insts = Filter.useFilter(insts, m_Filter); 
+    } else {
+      m_Filter = null;
+    }
+
+    m_classIndex = insts.classIndex();
+    m_classAttribute = insts.classAttribute();
+    m_KernelIsLinear = (m_kernel instanceof PolyKernel) && (((PolyKernel) m_kernel).getExponent() == 1.0);
+    
+    // Generate subsets representing each class
+    Instances[] subsets = new Instances[insts.numClasses()];
+    for (int i = 0; i < insts.numClasses(); i++) {
+      subsets[i] = new Instances(insts, insts.numInstances());
+    }
+    for (int j = 0; j < insts.numInstances(); j++) {
+      Instance inst = insts.instance(j);
+      subsets[(int)inst.classValue()].add(inst);
+    }
+    for (int i = 0; i < insts.numClasses(); i++) {
+      subsets[i].compactify();
+    }
+
+    // Build the binary classifiers
+    Random rand = new Random(m_randomSeed);
+    m_classifiers = new BinarySMO[insts.numClasses()][insts.numClasses()];
+    for (int i = 0; i < insts.numClasses(); i++) {
+      for (int j = i + 1; j < insts.numClasses(); j++) {
+	m_classifiers[i][j] = new BinarySMO();
+	m_classifiers[i][j].setKernel(Kernel.makeCopy(getKernel()));
+	Instances data = new Instances(insts, insts.numInstances());
+	for (int k = 0; k < subsets[i].numInstances(); k++) {
+	  data.add(subsets[i].instance(k));
+	}
+	for (int k = 0; k < subsets[j].numInstances(); k++) {
+	  data.add(subsets[j].instance(k));
+	}
+	data.compactify();
+	data.randomize(rand);
+	m_classifiers[i][j].buildClassifier(data, i, j, 
+					    m_fitLogisticModels,
+					    m_numFolds, m_randomSeed);
+      }
+    }
+  }
+
+  /**
+   * Estimates class probabilities for given instance.
+   * 
+   * @param inst the instance to compute the probabilities for
+   * @throws Exception in case of an error
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+
+    // Filter instance
+    if (!m_checksTurnedOff) {
+      m_Missing.input(inst);
+      m_Missing.batchFinished();
+      inst = m_Missing.output();
+    }
+
+    if (m_NominalToBinary != null) {
+      m_NominalToBinary.input(inst);
+      m_NominalToBinary.batchFinished();
+      inst = m_NominalToBinary.output();
+    }
+    
+    if (m_Filter != null) {
+      m_Filter.input(inst);
+      m_Filter.batchFinished();
+      inst = m_Filter.output();
+    }
+    
+    if (!m_fitLogisticModels) {
+      double[] result = new double[inst.numClasses()];
+      for (int i = 0; i < inst.numClasses(); i++) {
+	for (int j = i + 1; j < inst.numClasses(); j++) {
+	  if ((m_classifiers[i][j].m_alpha != null) || 
+	      (m_classifiers[i][j].m_sparseWeights != null)) {
+	    double output = m_classifiers[i][j].SVMOutput(-1, inst);
+	    if (output > 0) {
+	      result[j] += 1;
+	    } else {
+	      result[i] += 1;
+	    }
+	  }
+	} 
+      }
+      Utils.normalize(result);
+      return result;
+    } else {
+
+      // We only need to do pairwise coupling if there are more
+      // then two classes.
+      if (inst.numClasses() == 2) {
+	double[] newInst = new double[2];
+	newInst[0] = m_classifiers[0][1].SVMOutput(-1, inst);
+	newInst[1] = Utils.missingValue();
+	return m_classifiers[0][1].m_logistic.
+	  distributionForInstance(new DenseInstance(1, newInst));
+      }
+      double[][] r = new double[inst.numClasses()][inst.numClasses()];
+      double[][] n = new double[inst.numClasses()][inst.numClasses()];
+      for (int i = 0; i < inst.numClasses(); i++) {
+	for (int j = i + 1; j < inst.numClasses(); j++) {
+	  if ((m_classifiers[i][j].m_alpha != null) || 
+	      (m_classifiers[i][j].m_sparseWeights != null)) {
+	    double[] newInst = new double[2];
+	    newInst[0] = m_classifiers[i][j].SVMOutput(-1, inst);
+	    newInst[1] = Utils.missingValue();
+	    r[i][j] = m_classifiers[i][j].m_logistic.
+	      distributionForInstance(new DenseInstance(1, newInst))[0];
+	    n[i][j] = m_classifiers[i][j].m_sumOfWeights;
+	  }
+	}
+      }
+      return weka.classifiers.meta.MultiClassClassifier.pairwiseCoupling(n, r);
+    }
+  }
+
+  /**
+   * Returns an array of votes for the given instance.
+   * @param inst the instance
+   * @return array of votex
+   * @throws Exception if something goes wrong
+   */
+  public int[] obtainVotes(Instance inst) throws Exception {
+
+    // Filter instance
+    if (!m_checksTurnedOff) {
+      m_Missing.input(inst);
+      m_Missing.batchFinished();
+      inst = m_Missing.output();
+    }
+
+    if (m_NominalToBinary != null) {
+      m_NominalToBinary.input(inst);
+      m_NominalToBinary.batchFinished();
+      inst = m_NominalToBinary.output();
+    }
+    
+    if (m_Filter != null) {
+      m_Filter.input(inst);
+      m_Filter.batchFinished();
+      inst = m_Filter.output();
+    }
+
+    int[] votes = new int[inst.numClasses()];
+    for (int i = 0; i < inst.numClasses(); i++) {
+      for (int j = i + 1; j < inst.numClasses(); j++) {
+	double output = m_classifiers[i][j].SVMOutput(-1, inst);
+	if (output > 0) {
+	  votes[j] += 1;
+	} else {
+	  votes[i] += 1;
+	}
+      }
+    }
+    return votes;
+  }
+
+  /**
+   * Returns the weights in sparse format.
+   */
+  public double [][][] sparseWeights() {
+    
+    int numValues = m_classAttribute.numValues();
+    double [][][] sparseWeights = new double[numValues][numValues][];
+    
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+	sparseWeights[i][j] = m_classifiers[i][j].m_sparseWeights;
+      }
+    }
+    
+    return sparseWeights;
+  }
+  
+  /**
+   * Returns the indices in sparse format.
+   */
+  public int [][][] sparseIndices() {
+    
+    int numValues = m_classAttribute.numValues();
+    int [][][] sparseIndices = new int[numValues][numValues][];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+	sparseIndices[i][j] = m_classifiers[i][j].m_sparseIndices;
+      }
+    }
+    
+    return sparseIndices;
+  }
+  
+  /**
+   * Returns the bias of each binary SMO.
+   */
+  public double [][] bias() {
+    
+    int numValues = m_classAttribute.numValues();
+    double [][] bias = new double[numValues][numValues];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+	bias[i][j] = m_classifiers[i][j].m_b;
+      }
+    }
+    
+    return bias;
+  }
+  
+  /*
+   * Returns the number of values of the class attribute.
+   */
+  public int numClassAttributeValues() {
+
+    return m_classAttribute.numValues();
+  }
+  
+  /*
+   * Returns the names of the class attributes.
+   */
+  public String [] classAttributeNames() {
+
+    int numValues = m_classAttribute.numValues();
+    
+    String [] classAttributeNames = new String[numValues];
+    
+    for (int i = 0; i < numValues; i++) {
+      classAttributeNames[i] = m_classAttribute.value(i);
+    }
+    
+    return classAttributeNames;
+  }
+  
+  /**
+   * Returns the attribute names.
+   */
+  public String [][][] attributeNames() {
+    
+    int numValues = m_classAttribute.numValues();
+    String [][][] attributeNames = new String[numValues][numValues][];
+    
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+        //	int numAttributes = m_classifiers[i][j].m_data.numAttributes();
+	int numAttributes = m_classifiers[i][j].m_sparseIndices.length;
+	String [] attrNames = new String[numAttributes];
+	for (int k = 0; k < numAttributes; k++) {
+	  attrNames[k] = m_classifiers[i][j].
+            m_data.attribute(m_classifiers[i][j].m_sparseIndices[k]).name();
+	}
+	attributeNames[i][j] = attrNames;          
+      }
+    }
+    return attributeNames;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector result = new Vector();
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tTurns off all checks - use with caution!\n"
+	+ "\tTurning them off assumes that data is purely numeric, doesn't\n"
+	+ "\tcontain any missing values, and has a nominal class. Turning them\n"
+	+ "\toff also means that no header information will be stored if the\n"
+	+ "\tmachine is linear. Finally, it also assumes that no instance has\n"
+	+ "\ta weight equal to 0.\n"
+	+ "\t(default: checks on)",
+	"no-checks", 0, "-no-checks"));
+
+    result.addElement(new Option(
+	"\tThe complexity constant C. (default 1)",
+	"C", 1, "-C <double>"));
+    
+    result.addElement(new Option(
+	"\tWhether to 0=normalize/1=standardize/2=neither. " +
+	"(default 0=normalize)",
+	"N", 1, "-N"));
+    
+    result.addElement(new Option(
+	"\tThe tolerance parameter. " +
+	"(default 1.0e-3)",
+	"L", 1, "-L <double>"));
+    
+    result.addElement(new Option(
+	"\tThe epsilon for round-off error. " +
+	"(default 1.0e-12)",
+	"P", 1, "-P <double>"));
+    
+    result.addElement(new Option(
+	"\tFit logistic models to SVM outputs. ",
+	"M", 0, "-M"));
+    
+    result.addElement(new Option(
+	"\tThe number of folds for the internal\n" +
+	"\tcross-validation. " +
+	"(default -1, use training data)",
+	"V", 1, "-V <double>"));
+    
+    result.addElement(new Option(
+	"\tThe random number seed. " +
+	"(default 1)",
+	"W", 1, "-W <double>"));
+    
+    result.addElement(new Option(
+	"\tThe Kernel to use.\n"
+	+ "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
+	"K", 1, "-K <classname and parameters>"));
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to kernel "
+	+ getKernel().getClass().getName() + ":"));
+    
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  Turning them off assumes that data is purely numeric, doesn't
+   *  contain any missing values, and has a nominal class. Turning them
+   *  off also means that no header information will be stored if the
+   *  machine is linear. Finally, it also assumes that no instance has
+   *  a weight equal to 0.
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  The complexity constant C. (default 1)</pre>
+   * 
+   * <pre> -N
+   *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The tolerance parameter. (default 1.0e-3)</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error. (default 1.0e-12)</pre>
+   * 
+   * <pre> -M
+   *  Fit logistic models to SVM outputs. </pre>
+   * 
+   * <pre> -V &lt;double&gt;
+   *  The number of folds for the internal
+   *  cross-validation. (default -1, use training data)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed. (default 1)</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String 	tmpStr;
+    String[]	tmpOptions;
+    
+    setChecksTurnedOff(Utils.getFlag("no-checks", options));
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setC(Double.parseDouble(tmpStr));
+    else
+      setC(1.0);
+
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0)
+      setToleranceParameter(Double.parseDouble(tmpStr));
+    else
+      setToleranceParameter(1.0e-3);
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setEpsilon(Double.parseDouble(tmpStr));
+    else
+      setEpsilon(1.0e-12);
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
+    else
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+    
+    setBuildLogisticModels(Utils.getFlag('M', options));
+    
+    tmpStr = Utils.getOption('V', options);
+    if (tmpStr.length() != 0)
+      setNumFolds(Integer.parseInt(tmpStr));
+    else
+      setNumFolds(-1);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() != 0)
+      setRandomSeed(Integer.parseInt(tmpStr));
+    else
+      setRandomSeed(1);
+
+    tmpStr     = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getChecksTurnedOff())
+      result.add("-no-checks");
+
+    result.add("-C");
+    result.add("" + getC());
+    
+    result.add("-L");
+    result.add("" + getToleranceParameter());
+    
+    result.add("-P");
+    result.add("" + getEpsilon());
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+    
+    if (getBuildLogisticModels())
+      result.add("-M");
+    
+    result.add("-V");
+    result.add("" + getNumFolds());
+    
+    result.add("-W");
+    result.add("" + getRandomSeed());
+
+    result.add("-K");
+    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
+    
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Disables or enables the checks (which could be time-consuming). Use with
+   * caution!
+   * 
+   * @param value	if true turns off all checks
+   */
+  public void setChecksTurnedOff(boolean value) {
+    if (value)
+      turnChecksOff();
+    else
+      turnChecksOn();
+  }
+  
+  /**
+   * Returns whether the checks are turned off or not.
+   * 
+   * @return		true if the checks are turned off
+   */
+  public boolean getChecksTurnedOff() {
+    return m_checksTurnedOff;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String checksTurnedOffTipText() {
+    return "Turns time-consuming checks off - use with caution.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+  
+  /**
+   * sets the kernel to use
+   * 
+   * @param value	the kernel to use
+   */
+  public void setKernel(Kernel value) {
+    m_kernel = value;
+  }
+  
+  /**
+   * Returns the kernel to use
+   * 
+   * @return 		the current kernel
+   */
+  public Kernel getKernel() {
+    return m_kernel;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String cTipText() {
+    return "The complexity parameter C.";
+  }
+  
+  /**
+   * Get the value of C.
+   *
+   * @return Value of C.
+   */
+  public double getC() {
+    
+    return m_C;
+  }
+  
+  /**
+   * Set the value of C.
+   *
+   * @param v  Value to assign to C.
+   */
+  public void setC(double v) {
+    
+    m_C = v;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String toleranceParameterTipText() {
+    return "The tolerance parameter (shouldn't be changed).";
+  }
+  
+  /**
+   * Get the value of tolerance parameter.
+   * @return Value of tolerance parameter.
+   */
+  public double getToleranceParameter() {
+    
+    return m_tol;
+  }
+  
+  /**
+   * Set the value of tolerance parameter.
+   * @param v  Value to assign to tolerance parameter.
+   */
+  public void setToleranceParameter(double v) {
+    
+    m_tol = v;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String epsilonTipText() {
+    return "The epsilon for round-off error (shouldn't be changed).";
+  }
+  
+  /**
+   * Get the value of epsilon.
+   * @return Value of epsilon.
+   */
+  public double getEpsilon() {
+    
+    return m_eps;
+  }
+  
+  /**
+   * Set the value of epsilon.
+   * @param v  Value to assign to epsilon.
+   */
+  public void setEpsilon(double v) {
+    
+    m_eps = v;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "Determines how/if the data will be transformed.";
+  }
+  
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+  
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+    
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String buildLogisticModelsTipText() {
+    return "Whether to fit logistic models to the outputs (for proper "
+      + "probability estimates).";
+  }
+
+  /**
+   * Get the value of buildLogisticModels.
+   *
+   * @return Value of buildLogisticModels.
+   */
+  public boolean getBuildLogisticModels() {
+    
+    return m_fitLogisticModels;
+  }
+  
+  /**
+   * Set the value of buildLogisticModels.
+   *
+   * @param newbuildLogisticModels Value to assign to buildLogisticModels.
+   */
+  public void setBuildLogisticModels(boolean newbuildLogisticModels) {
+    
+    m_fitLogisticModels = newbuildLogisticModels;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds for cross-validation used to generate "
+      + "training data for logistic models (-1 means use training data).";
+  }
+  
+  /**
+   * Get the value of numFolds.
+   *
+   * @return Value of numFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_numFolds;
+  }
+  
+  /**
+   * Set the value of numFolds.
+   *
+   * @param newnumFolds Value to assign to numFolds.
+   */
+  public void setNumFolds(int newnumFolds) {
+    
+    m_numFolds = newnumFolds;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Random number seed for the cross-validation.";
+  }
+  
+  /**
+   * Get the value of randomSeed.
+   *
+   * @return Value of randomSeed.
+   */
+  public int getRandomSeed() {
+    
+    return m_randomSeed;
+  }
+  
+  /**
+   * Set the value of randomSeed.
+   *
+   * @param newrandomSeed Value to assign to randomSeed.
+   */
+  public void setRandomSeed(int newrandomSeed) {
+    
+    m_randomSeed = newrandomSeed;
+  }
+  
+  /**
+   * Prints out the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+    
+    StringBuffer text = new StringBuffer();
+    
+    if ((m_classAttribute == null)) {
+      return "SMO: No model built yet.";
+    }
+    try {
+      text.append("SMO\n\n");
+      text.append("Kernel used:\n  " + m_kernel.toString() + "\n\n");
+      
+      for (int i = 0; i < m_classAttribute.numValues(); i++) {
+	for (int j = i + 1; j < m_classAttribute.numValues(); j++) {
+	  text.append("Classifier for classes: " + 
+		      m_classAttribute.value(i) + ", " +
+		      m_classAttribute.value(j) + "\n\n");
+	  text.append(m_classifiers[i][j]);
+	  if (m_fitLogisticModels) {
+	    text.append("\n\n");
+	    if ( m_classifiers[i][j].m_logistic == null) {
+	      text.append("No logistic model has been fit.\n");
+	    } else {
+	      text.append(m_classifiers[i][j].m_logistic);
+	    }
+	  }
+	  text.append("\n\n");
+	}
+      }
+    } catch (Exception e) {
+      return "Can't print SMO classifier.";
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6024 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] argv) {
+    runClassifier(new SMO(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/SMOreg.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/SMOreg.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/SMOreg.java	(revision 29)
@@ -0,0 +1,817 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SMOreg.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.classifiers.functions.supportVector.RegOptimizer;
+import weka.classifiers.functions.supportVector.RegSMOImproved;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * SMOreg implements the support vector machine for regression. The parameters can be learned using various algorithms. The algorithm is selected by setting the RegOptimizer. The most popular algorithm (RegSMOImproved) is due to Shevade, Keerthi et al and this is the default RegOptimizer.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * S.K. Shevade, S.S. Keerthi, C. Bhattacharyya, K.R.K. Murthy: Improvements to the SMO Algorithm for SVM Regression. In: IEEE Transactions on Neural Networks, 1999.<br/>
+ * <br/>
+ * A.J. Smola, B. Schoelkopf (1998). A tutorial on support vector regression.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Shevade1999,
+ *    author = {S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy},
+ *    booktitle = {IEEE Transactions on Neural Networks},
+ *    title = {Improvements to the SMO Algorithm for SVM Regression},
+ *    year = {1999},
+ *    PS = {http://guppy.mpe.nus.edu.sg/\~mpessk/svm/ieee_smo_reg.ps.gz}
+ * }
+ * 
+ * &#64;techreport{Smola1998,
+ *    author = {A.J. Smola and B. Schoelkopf},
+ *    note = {NeuroCOLT2 Technical Report NC2-TR-1998-030},
+ *    title = {A tutorial on support vector regression},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  The complexity constant C.
+ *  (default 1)</pre>
+ * 
+ * <pre> -N
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default 0=normalize)</pre>
+ * 
+ * <pre> -I &lt;classname and parameters&gt;
+ *  Optimizer class used for solving quadratic optimization problem
+ *  (default weka.classifiers.functions.supportVector.RegSMOImproved)</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to optimizer ('-I') weka.classifiers.functions.supportVector.RegSMOImproved:
+ * </pre>
+ * 
+ * <pre> -T &lt;double&gt;
+ *  The tolerance parameter for checking the stopping criterion.
+ *  (default 0.001)</pre>
+ * 
+ * <pre> -V
+ *  Use variant 1 of the algorithm when true, otherwise use variant 2.
+ *  (default true)</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error.
+ *  (default 1.0e-12)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The epsilon parameter in epsilon-insensitive loss function.
+ *  (default 1.0e-3)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel ('-K') weka.classifiers.functions.supportVector.PolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Remco Bouckaert (remco@cs.waikato.ac.nz,rrb@xm.co.nz)
+ * @version $Revision: 5928 $
+ */
+public class SMOreg 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, AdditionalMeasureProducer, 
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -7149606251113102827L;
+  
+  /** The filter to apply to the training data: Normalzie */
+  public static final int FILTER_NORMALIZE = 0;
+  /** The filter to apply to the training data: Standardize */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** The filter to apply to the training data: None */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag[] TAGS_FILTER =
+  {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+  
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+  
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+  
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+  
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing;
+  
+  /** Only numeric attributes in the dataset? If so, less need to filter */
+  protected boolean m_onlyNumeric;
+  
+  /** capacity parameter **/
+  protected double m_C = 1.0;
+  
+  /** coefficients used by normalization filter for doing its linear transformation 
+   * so that result = svmoutput * m_x1 + m_x0 **/
+  protected double m_x1 = 1.0;
+  protected double m_x0 = 0.0;
+  
+  /** contains the algorithm used for learning **/
+  protected RegOptimizer m_optimizer = new RegSMOImproved();
+
+  /** the configured kernel */
+  protected Kernel m_kernel = new PolyKernel();
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "SMOreg implements the support vector machine for regression. "
+      + "The parameters can be learned using various algorithms. The "
+      + "algorithm is selected by setting the RegOptimizer. The most "
+      + "popular algorithm (" 
+      + RegSMOImproved.class.getName().replaceAll(".*\\.", "") 
+      + ") is due to Shevade, Keerthi " 
+      + "et al and this is the default RegOptimizer.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy");
+    result.setValue(Field.TITLE, "Improvements to the SMO Algorithm for SVM Regression");
+    result.setValue(Field.BOOKTITLE, "IEEE Transactions on Neural Networks");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.PS, "http://guppy.mpe.nus.edu.sg/~mpessk/svm/ieee_smo_reg.ps.gz");
+
+    additional = result.add(Type.TECHREPORT);
+    additional.setValue(Field.AUTHOR, "A.J. Smola and B. Schoelkopf");
+    additional.setValue(Field.TITLE, "A tutorial on support vector regression");
+    additional.setValue(Field.NOTE, "NeuroCOLT2 Technical Report NC2-TR-1998-030");
+    additional.setValue(Field.YEAR, "1998");
+
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Enumeration enm;
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tThe complexity constant C.\n"
+	+ "\t(default 1)", 
+	"C", 1, "-C <double>"));
+    
+    result.addElement(new Option(
+	"\tWhether to 0=normalize/1=standardize/2=neither.\n" 
+	+ "\t(default 0=normalize)", 
+	"N", 1, "-N"));
+    
+    result.addElement(new Option(
+	"\tOptimizer class used for solving quadratic optimization problem\n" 
+	+ "\t(default " + RegSMOImproved.class.getName() + ")",
+	"I", 1, "-I <classname and parameters>"));
+    
+    result.addElement(new Option(
+	"\tThe Kernel to use.\n"
+	+ "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
+	"K", 1, "-K <classname and parameters>"));
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to optimizer ('-I') "
+	+ getRegOptimizer().getClass().getName() + ":"));
+
+    enm = ((OptionHandler) getRegOptimizer()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to kernel ('-K') "
+	+ getKernel().getClass().getName() + ":"));
+    
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  The complexity constant C.
+   *  (default 1)</pre>
+   * 
+   * <pre> -N
+   *  Whether to 0=normalize/1=standardize/2=neither.
+   *  (default 0=normalize)</pre>
+   * 
+   * <pre> -I &lt;classname and parameters&gt;
+   *  Optimizer class used for solving quadratic optimization problem
+   *  (default weka.classifiers.functions.supportVector.RegSMOImproved)</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to optimizer ('-I') weka.classifiers.functions.supportVector.RegSMOImproved:
+   * </pre>
+   * 
+   * <pre> -T &lt;double&gt;
+   *  The tolerance parameter for checking the stopping criterion.
+   *  (default 0.001)</pre>
+   * 
+   * <pre> -V
+   *  Use variant 1 of the algorithm when true, otherwise use variant 2.
+   *  (default true)</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error.
+   *  (default 1.0e-12)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The epsilon parameter in epsilon-insensitive loss function.
+   *  (default 1.0e-3)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel ('-K') weka.classifiers.functions.supportVector.PolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0) {
+      setC(Double.parseDouble(tmpStr));
+    } else {
+      setC(1.0);
+    }
+    
+    String nString = Utils.getOption('N', options);
+    if (nString.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(nString), TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+    }
+    
+    tmpStr = Utils.getOption('I', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setRegOptimizer(
+	  (RegOptimizer) Utils.forName(RegOptimizer.class, tmpStr, tmpOptions));
+    }
+    else {
+      setRegOptimizer(new RegSMOImproved());
+    }
+
+    tmpStr     = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+    else {
+      setKernel(new PolyKernel());
+    }
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-C");
+    result.add("" + getC());
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+    
+    result.add("-I");
+    result.add("" + getRegOptimizer().getClass().getName() + " " + Utils.joinOptions(getRegOptimizer().getOptions()));
+
+    result.add("-K");
+    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = getKernel().getCapabilities();
+    result.setOwner(this);
+
+    // attribute
+    result.enableAllAttributeDependencies();
+    // with NominalToBinary we can also handle nominal attributes, but only
+    // if the kernel can handle numeric attributes
+    if (result.handles(Capability.NUMERIC_ATTRIBUTES))
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Method for building the classifier.
+   *
+   * @param instances the set of training instances
+   * @throws Exception if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // Removes all the instances with weight equal to 0.
+    // MUST be done since condition (8) of Keerthi's paper 
+    // is made with the assertion Ci > 0 (See equation (3a).
+    Instances data = new Instances(instances, 0);
+    for (int i = 0; i < instances.numInstances(); i++) {
+      if (instances.instance(i).weight() > 0) {
+	data.add(instances.instance(i));
+      }
+    }
+    
+    if (data.numInstances() == 0) {
+      throw new Exception("No training instances left after removing " + "instance with either a weight null or a missing class!");
+    }
+    instances = data;
+    
+    m_onlyNumeric = true;
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      if (i != instances.classIndex()) {
+	if (!instances.attribute(i).isNumeric()) {
+	  m_onlyNumeric = false;
+	  break;
+	}
+      }
+    }
+    m_Missing = new ReplaceMissingValues();
+    m_Missing.setInputFormat(instances);
+    instances = Filter.useFilter(instances, m_Missing);
+    
+    if (!m_onlyNumeric) {
+      m_NominalToBinary = new NominalToBinary();
+      m_NominalToBinary.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_NominalToBinary);
+    } else {
+      m_NominalToBinary = null;
+    }
+    
+    // retrieve two different class values used to determine filter transformation
+    double y0 = instances.instance(0).classValue();
+    int index = 1;
+    while (index < instances.numInstances() && instances.instance(index).classValue() == y0) {
+      index++;
+    }
+    if (index == instances.numInstances()) {
+      // degenerate case, all class values are equal
+      // we don't want to deal with this, too much hassle
+      throw new Exception("All class values are the same. At least two class values should be different");
+    }
+    double y1 = instances.instance(index).classValue();
+    
+    // apply filters
+    if (m_filterType == FILTER_STANDARDIZE) {
+      m_Filter = new Standardize();
+      ((Standardize)m_Filter).setIgnoreClass(true);
+      m_Filter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_Filter);			
+    } else if (m_filterType == FILTER_NORMALIZE) {
+      m_Filter = new Normalize();
+      ((Normalize)m_Filter).setIgnoreClass(true);
+      m_Filter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_Filter);
+    } else {
+      m_Filter = null;
+    }
+    if (m_Filter != null) {
+      double z0 = instances.instance(0).classValue();
+      double z1 = instances.instance(index).classValue();
+      m_x1 = (y0-y1) / (z0 - z1); // no division by zero, since y0 != y1 guaranteed => z0 != z1 ???
+      m_x0 = (y0 - m_x1 * z0); // = y1 - m_x1 * z1
+    } else {
+      m_x1 = 1.0;
+      m_x0 = 0.0; 			
+    }
+    
+    m_optimizer.setSMOReg(this);
+    m_optimizer.buildClassifier(instances);
+  }
+  
+  /**
+   * Classifies the given instance using the linear regression function.
+   *
+   * @param instance the test instance
+   * @return the classification
+   * @throws Exception if classification can't be done successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    // Filter instance
+    m_Missing.input(instance);
+    m_Missing.batchFinished();
+    instance = m_Missing.output();
+    
+    if (!m_onlyNumeric) {
+      m_NominalToBinary.input(instance);
+      m_NominalToBinary.batchFinished();
+      instance = m_NominalToBinary.output();
+    }
+    
+    if (m_Filter != null) {
+      m_Filter.input(instance);
+      m_Filter.batchFinished();
+      instance = m_Filter.output();
+    }
+    
+    double result = m_optimizer.SVMOutput(instance);
+    return result * m_x1 + m_x0;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String regOptimizerTipText() {
+    return "The learning algorithm.";
+  }
+  
+  /**
+   * sets the learning algorithm
+   * 
+   * @param regOptimizer	the learning algorithm
+   */
+  public void setRegOptimizer(RegOptimizer regOptimizer) {
+    m_optimizer = regOptimizer;
+  }
+  
+  /**
+   * returns the learning algorithm
+   * 
+   * @return		the learning algorithm
+   */
+  public RegOptimizer getRegOptimizer() {
+    return m_optimizer;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+  
+  /**
+   * sets the kernel to use
+   * 
+   * @param value	the kernel to use
+   */
+  public void setKernel(Kernel value) {
+    m_kernel = value;
+  }
+  
+  /**
+   * Returns the kernel to use
+   * 
+   * @return 		the current kernel
+   */
+  public Kernel getKernel() {
+    return m_kernel;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String cTipText() {
+    return "The complexity parameter C.";
+  }
+  
+  /**
+   * Get the value of C.
+   *
+   * @return 		Value of C.
+   */
+  public double getC() {
+    return m_C;
+  }
+  
+  /**
+   * Set the value of C.
+   *
+   * @param v  		Value to assign to C.
+   */
+  public void setC(double v) {
+    m_C = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "Determines how/if the data will be transformed.";
+  }
+  
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return 		the filtering mode
+   */
+  public SelectedTag getFilterType() {
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+  
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType 	the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Prints out the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    if (m_optimizer == null || !m_optimizer.modelBuilt()) {
+      return "SMOreg: No model built yet.";
+    }
+    
+    try {
+      text.append(m_optimizer.toString());
+    } 
+    catch (Exception e) {
+      return "Can't print SMVreg classifier.";
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns an enumeration of the measure names. Additional measures
+   * must follow the naming convention of starting with "measure", eg.
+   * double measureBlah()
+   * 
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector result = new Vector();
+    
+    result.addElement("measureKernelEvaluations");
+    result.addElement("measureCacheHits");
+    
+    return result.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param measureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String measureName) {
+    if (measureName.equals("measureKernelEvaluations"))
+      return measureKernelEvaluations();
+    else if (measureName.equals("measureCacheHits"))
+      return measureCacheHits();
+    else
+      throw new IllegalArgumentException("Measure '" +  measureName + "' is not supported!");
+  }
+  
+  /** 
+   * number of kernel evaluations used in learing
+   * 
+   * @return		the number of kernel evaluations
+   */
+  protected double measureKernelEvaluations() {
+    if (m_optimizer != null) {
+      return m_optimizer.getKernelEvaluations();
+    } else {
+      return 0; 
+    }
+  }
+  
+  /** 
+   * number of kernel cache hits used during learing
+   * 
+   * @return		the number of kernel cache hits
+   */
+  protected double measureCacheHits() {
+    if (m_optimizer != null) {
+      return m_optimizer.getCacheHits();
+    } else {
+      return 0; 
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for running this classifier.
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    runClassifier(new SMOreg(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/SPegasos.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/SPegasos.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/SPegasos.java	(revision 29)
@@ -0,0 +1,799 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SPegasos.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Normalize;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements the stochastic variant of the Pegasos (Primal Estimated sub-GrAdient SOlver for SVM) method of Shalev-Shwartz et al. (2007). This implementation globally replaces all missing values and transforms nominal attributes into binary ones. It also normalizes all attributes, so the coefficients in the output are based on the normalized data. Can either minimize the hinge loss (SVM) or log loss (logistic regression). For more information, see<br/>
+ * <br/>
+ * S. Shalev-Shwartz, Y. Singer, N. Srebro: Pegasos: Primal Estimated sub-GrAdient SOlver for SVM. In: 24th International Conference on MachineLearning, 807-814, 2007.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Shalev-Shwartz2007,
+ *    author = {S. Shalev-Shwartz and Y. Singer and N. Srebro},
+ *    booktitle = {24th International Conference on MachineLearning},
+ *    pages = {807-814},
+ *    title = {Pegasos: Primal Estimated sub-GrAdient SOlver for SVM},
+ *    year = {2007}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F
+ *  Set the loss function to minimize. 0 = hinge loss (SVM), 1 = log loss (logistic regression).
+ *  (default = 0)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The lambda regularization constant (default = 0.0001)</pre>
+ * 
+ * <pre> -E &lt;integer&gt;
+ *  The number of epochs to perform (batch learning only, default = 500)</pre>
+ * 
+ * <pre> -N
+ *  Don't normalize the data</pre>
+ * 
+ * <pre> -M
+ *  Don't replace missing values</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6105 $
+ *
+ */
+public class SPegasos extends AbstractClassifier
+  implements TechnicalInformationHandler, UpdateableClassifier,
+    OptionHandler {
+  
+  /** For serialization */
+  private static final long serialVersionUID = -3732968666673530290L;
+  
+  /** Replace missing values */
+  protected ReplaceMissingValues m_replaceMissing;
+  
+  /** Convert nominal attributes to numerically coded binary ones */
+  protected NominalToBinary m_nominalToBinary;
+  
+  /** Normalize the training data */
+  protected Normalize m_normalize;
+  
+  /** The regularization parameter */
+  protected double m_lambda = 0.0001;
+  
+  /** Stores the weights (+ bias in the last element) */
+  protected double[] m_weights;
+  
+  /** Holds the current iteration number */
+  protected double m_t;
+  
+  /**
+   *  The number of epochs to perform (batch learning). Total iterations is
+   *  m_epochs * num instances 
+   */
+  protected int m_epochs = 500;
+  
+  /** 
+   * Turn off normalization of the input data. This option gets
+   * forced for incremental training.
+   */
+  protected boolean m_dontNormalize = false;
+  
+  /**
+   *  Turn off global replacement of missing values. Missing values
+   *  will be ignored instead. This option gets forced for
+   *  incremental training.
+   */
+  protected boolean m_dontReplaceMissing = false;
+  
+  /** Holds the header of the training data */
+  protected Instances m_data;
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    //attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lambdaTipText() {
+    return "The regularization constant. (default = 0.0001)";
+  }
+  
+  /**
+   * Set the value of lambda to use
+   * 
+   * @param lambda the value of lambda to use
+   */
+  public void setLambda(double lambda) {
+    m_lambda = lambda;
+  }
+  
+  /**
+   * Get the current value of lambda
+   * 
+   * @return the current value of lambda
+   */
+  public double getLambda() {
+    return m_lambda;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String epochsTipText() {
+    return "The number of epochs to perform (batch learning). " +
+    		"The total number of iterations is epochs * num" +
+    		" instances.";
+  }
+  
+  /**
+   * Set the number of epochs to use
+   * 
+   * @param e the number of epochs to use
+   */
+  public void setEpochs(int e) {
+    m_epochs = e;
+  }
+  
+  /**
+   * Get current number of epochs
+   * 
+   * @return the current number of epochs
+   */
+  public int getEpochs() {
+    return m_epochs;
+  }
+  
+  /**
+   * Turn normalization off/on.
+   * 
+   * @param m true if normalization is to be disabled.
+   */
+  public void setDontNormalize(boolean m) {
+    m_dontNormalize = m;
+  }
+  
+  /**
+   * Get whether normalization has been turned off.
+   * 
+   * @return true if normalization has been disabled.
+   */
+  public boolean getDontNormalize() {
+    return m_dontNormalize;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String dontNormalizeTipText() {
+    return "Turn normalization off";
+  }
+  
+  /**
+   * Turn global replacement of missing values off/on. If turned off,
+   * then missing values are effectively ignored.
+   * 
+   * @param m true if global replacement of missing values is to be
+   * turned off.
+   */
+  public void setDontReplaceMissing(boolean m) {
+    m_dontReplaceMissing = m;
+  }
+  
+  /**
+   * Get whether global replacement of missing values has been
+   * disabled.
+   * 
+   * @return true if global replacement of missing values has been turned
+   * off
+   */
+  public boolean getDontReplaceMissing() {
+    return m_dontReplaceMissing;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String dontReplaceMissingTipText() {
+    return "Turn off global replacement of missing values";
+  }
+  
+  /**
+   * Set the loss function to use.
+   * 
+   * @param function the loss function to use.
+   */
+  public void setLossFunction(SelectedTag function) {
+    if (function.getTags() == TAGS_SELECTION) {
+      m_loss = function.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Get the current loss function.
+   * 
+   * @return the current loss function.
+   */
+  public SelectedTag getLossFunction() {
+    return new SelectedTag(m_loss, TAGS_SELECTION);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lossFunctionTipText() {
+    return "The loss function to use. Hinge loss (SVM) " +
+    		"or log loss (logistic regression).";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration<Option> listOptions() {
+
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.add(new Option("\tSet the loss function to minimize. 0 = " +
+    		"hinge loss (SVM), 1 = log loss (logistic regression).\n" +
+    		"\t(default = 0)", "F", 1, "-F"));
+    newVector.add(new Option("\tThe lambda regularization constant " +
+    		"(default = 0.0001)",
+    		"L", 1, "-L <double>"));
+    newVector.add(new Option("\tThe number of epochs to perform (" +
+    		"batch learning only, default = 500)", "E", 1,
+    		"-E <integer>"));
+    newVector.add(new Option("\tDon't normalize the data", "N", 0, "-N"));
+    newVector.add(new Option("\tDon't replace missing values", "M", 0, "-M"));
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F
+   *  Set the loss function to minimize. 0 = hinge loss (SVM), 1 = log loss (logistic regression).
+   *  (default = 0)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The lambda regularization constant (default = 0.0001)</pre>
+   * 
+   * <pre> -E &lt;integer&gt;
+   *  The number of epochs to perform (batch learning only, default = 500)</pre>
+   * 
+   * <pre> -N
+   *  Don't normalize the data</pre>
+   * 
+   * <pre> -M
+   *  Don't replace missing values</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    reset();
+    
+    String lossString = Utils.getOption('F', options);
+    if (lossString.length() != 0) {
+      setLossFunction(new SelectedTag(Integer.parseInt(lossString), 
+          TAGS_SELECTION));
+    } else {
+      setLossFunction(new SelectedTag(HINGE, TAGS_SELECTION));
+    }
+    
+    String lambdaString = Utils.getOption('L', options);
+    if (lambdaString.length() > 0) {
+      setLambda(Double.parseDouble(lambdaString));
+    }
+    
+    String epochsString = Utils.getOption("E", options);
+    if (epochsString.length() > 0) {
+      setEpochs(Integer.parseInt(epochsString));
+    }
+    
+    setDontNormalize(Utils.getFlag("N", options));
+    setDontReplaceMissing(Utils.getFlag('M', options));
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    ArrayList<String> options = new ArrayList<String>();
+    
+    options.add("-F"); options.add("" + getLossFunction().getSelectedTag().getID());
+    options.add("-L"); options.add("" + getLambda());
+    options.add("-E"); options.add("" + getEpochs());
+    if (getDontNormalize()) {
+      options.add("-N");
+    }
+    if (getDontReplaceMissing()) {
+      options.add("-M");
+    }
+    
+    return options.toArray(new String[1]);
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Implements the stochastic variant of the Pegasos" +
+    		" (Primal Estimated sub-GrAdient SOlver for SVM)" +
+    		" method of Shalev-Shwartz et al. (2007). This implementation" +
+    		" globally replaces all missing values and transforms nominal" +
+    		" attributes into binary ones. It also normalizes all attributes," +
+    		" so the coefficients in the output are based on the normalized" +
+    		" data. Can either minimize the hinge loss (SVM) or log loss (" +
+    		"logistic regression). For more information, see\n\n" +
+    		getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+   
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "S. Shalev-Shwartz and Y. Singer and N. Srebro");
+    result.setValue(Field.YEAR, "2007");
+    result.setValue(Field.TITLE, "Pegasos: Primal Estimated sub-GrAdient " +
+    		"SOlver for SVM");
+    result.setValue(Field.BOOKTITLE, "24th International Conference on Machine" +
+    		"Learning");
+    result.setValue(Field.PAGES, "807-814");
+    
+    return result;
+  }
+  
+  /**
+   * Reset the classifier.
+   */
+  public void reset() {
+    m_t = 1;
+    m_weights = null;
+    m_normalize = null;
+    m_replaceMissing = null;
+    m_nominalToBinary = null;
+  }
+
+  /**
+   * Method for building the classifier.
+   * 
+   * @param data the set of training instances.
+   * @throws Exception if the classifier can't be built successfully.
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    reset();
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+    
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    if (data.numInstances() > 0 && !m_dontReplaceMissing) {
+      m_replaceMissing = new ReplaceMissingValues();
+      m_replaceMissing.setInputFormat(data);
+      data = Filter.useFilter(data, m_replaceMissing);
+    }
+    
+    // check for only numeric attributes
+    boolean onlyNumeric = true;
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != data.classIndex()) {
+        if (!data.attribute(i).isNumeric()) {
+          onlyNumeric = false;
+          break;
+        }
+      }
+    }
+    
+    if (!onlyNumeric) {
+      m_nominalToBinary = new NominalToBinary();
+      m_nominalToBinary.setInputFormat(data);
+      data = Filter.useFilter(data, m_nominalToBinary);
+    }
+    
+    if (!m_dontNormalize && data.numInstances() > 0) {
+
+      m_normalize = new Normalize();
+      m_normalize.setInputFormat(data);
+      data = Filter.useFilter(data, m_normalize);
+    }
+    
+    m_weights = new double[data.numAttributes() + 1];
+    m_data = new Instances(data, 0);
+    
+    if (data.numInstances() > 0) {
+      train(data);    
+    }
+  }
+  
+  protected static final int HINGE = 0;
+  protected static final int LOGLOSS = 1;
+  
+  /** The current loss function to minimize */
+  protected int m_loss = HINGE;
+  
+  /** Loss functions to choose from */
+  public static final Tag [] TAGS_SELECTION = {
+    new Tag(HINGE, "Hinge loss (SVM)"),
+    new Tag(LOGLOSS, "Log loss (logistic regression)")
+  };
+  
+  protected double dloss(double z) {
+    if (m_loss == HINGE) {
+      return (z < 1) ? 1 : 0;
+    }
+    
+    // log loss
+    if (z < 0) {
+      return 1.0 / (Math.exp(z) + 1.0);  
+    } else {
+      double t = Math.exp(-z);
+      return t / (t + 1);
+    }
+  }
+  
+  private void train(Instances data) {
+    for (int e = 0; e < m_epochs; e++) {
+      for (int i = 0; i < data.numInstances(); i++) {
+        Instance instance = data.instance(i);
+
+        double learningRate = 1.0 / (m_lambda * m_t);
+        //double scale = 1.0 - learningRate * m_lambda;
+        double scale = 1.0 - 1.0 / m_t;
+        double y = (instance.classValue() == 0) ? -1 : 1;
+        double wx = dotProd(instance, m_weights, instance.classIndex());
+        double z = y * (wx + m_weights[m_weights.length - 1]);
+        
+
+        if (m_loss == LOGLOSS || (z < 1)) {
+          double delta = learningRate * dloss(z);
+          int n1 = instance.numValues();
+          int n2 = data.numAttributes();
+          for (int p1 = 0, p2 = 0; p2 < n2;) {
+            int indS = 0;
+            indS = (p1 < n1) ? instance.index(p1) : indS;
+            int indP = p2;
+            if (indP != data.classIndex()) {
+              m_weights[indP] *= scale;
+            }
+            if (indS == indP) {
+              if (indS != data.classIndex() && 
+                  !instance.isMissingSparse(p1)) {
+                //double m = learningRate * (instance.valueSparse(p1) * y);
+                double m = delta * (instance.valueSparse(p1) * y);
+                m_weights[indS] += m;
+              }
+              p1++;             
+            }
+            p2++;
+          }
+
+          // update the bias
+          m_weights[m_weights.length - 1] += delta * y;
+          
+          double norm = 0;
+          for (int k = 0; k < m_weights.length; k++) {
+            if (k != data.classIndex()) {
+              norm += (m_weights[k] * m_weights[k]);
+            }
+          }
+          norm = Math.sqrt(norm);
+          
+          double scale2 = Math.min(1.0, (1.0 / (Math.sqrt(m_lambda) * norm)));
+          if (scale2 < 1.0) {
+            for (int j = 0; j < m_weights.length; j++) {
+              m_weights[j] *= scale2;
+            }
+          }
+        }
+        m_t++;
+      }
+    }
+  }
+  
+  protected static double dotProd(Instance inst1, double[] weights, int classIndex) {
+    double result = 0;
+
+    int n1 = inst1.numValues();
+    int n2 = weights.length - 1; 
+
+    for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
+      int ind1 = inst1.index(p1);
+      int ind2 = p2;
+      if (ind1 == ind2) {
+        if (ind1 != classIndex && !inst1.isMissingSparse(p1)) {
+          result += inst1.valueSparse(p1) * weights[p2];
+        }
+        p1++;
+        p2++;
+      } else if (ind1 > ind2) {
+        p2++;
+      } else {
+        p1++;
+      }
+    }
+    return (result);
+  }
+        
+  /**
+   * Updates the classifier with the given instance.
+   *
+   * @param instance the new training instance to include in the model 
+   * @exception Exception if the instance could not be incorporated in
+   * the model.
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+    if (!instance.classIsMissing()) {
+      double learningRate = 1.0 / (m_lambda * m_t);
+      //double scale = 1.0 - learningRate * m_lambda;
+      double scale = 1.0 - 1.0 / m_t;
+      double y = (instance.classValue() == 0) ? -1 : 1;
+      double wx = dotProd(instance, m_weights, instance.classIndex());
+      double z = y * (wx + m_weights[m_weights.length - 1]);
+      
+      for (int j = 0; j < m_weights.length; j++) {
+        m_weights[j] *= scale;
+      }
+      
+      if (m_loss == LOGLOSS || (z < 1)) {
+        double delta = learningRate * dloss(z);
+        int n1 = instance.numValues();
+        int n2 = instance.numAttributes();
+        for (int p1 = 0, p2 = 0; p2 < n2;) {
+          int indS = 0;
+          indS = (p1 < n1) ? instance.index(p1) : indS;
+          int indP = p2;
+          if (indP != instance.classIndex()) {
+            m_weights[indP] *= scale;
+          }
+          if (indS == indP) {
+            if (indS != instance.classIndex() && 
+                !instance.isMissingSparse(p1)) {
+              double m = delta * (instance.valueSparse(p1) * y);
+              m_weights[indS] += m;
+            }
+            p1++;             
+          }
+          p2++;
+        }                        
+        
+        // update the bias
+        m_weights[m_weights.length - 1] += delta * y;
+        
+        double norm = 0;
+        for (int k = 0; k < m_weights.length; k++) {
+          if (k != instance.classIndex()) {
+            norm += (m_weights[k] * m_weights[k]);
+          }
+        }
+        norm = Math.sqrt(norm);
+        
+        double scale2 = Math.min(1.0, (1.0 / (Math.sqrt(m_lambda) * norm)));
+        if (scale2 < 1.0) {
+          for (int j = 0; j < m_weights.length; j++) {
+            m_weights[j] *= scale2;
+          }
+        }
+      }            
+      
+      m_t++;
+    }
+  }
+  
+  /**
+   * Computes the distribution for a given instance
+   *
+   * @param instance the instance for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    double[] result = new double[2];
+
+    if (m_replaceMissing != null) {
+      m_replaceMissing.input(inst);
+      inst = m_replaceMissing.output();
+    }
+
+    if (m_nominalToBinary != null) {
+      m_nominalToBinary.input(inst);
+      inst = m_nominalToBinary.output();
+    }
+
+    if (m_normalize != null){
+      m_normalize.input(inst);
+      inst = m_normalize.output();
+    }
+
+    double wx = dotProd(inst, m_weights, inst.classIndex());// * m_wScale;
+    double z = (wx + m_weights[m_weights.length - 1]);
+    //System.out.print("" + z + ": ");
+    // System.out.println(1.0 / (1.0 + Math.exp(-z)));
+    if (z <= 0) {
+      //  z = 0;
+      if (m_loss == LOGLOSS) {
+        result[0] = 1.0 / (1.0 + Math.exp(z));
+        result[1] = 1.0 - result[0];
+      } else {
+        result[0] = 1;
+      }
+    } else {
+      if (m_loss == LOGLOSS) {
+        result[1] = 1.0 / (1.0 + Math.exp(-z));
+        result[0] = 1.0 - result[1];
+      } else {
+        result[1] = 1;
+      }
+    }
+    return result;
+  }
+  
+  
+  /**
+   * Prints out the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+    if (m_weights == null) {
+      return "SPegasos: No model built yet.\n";
+    }
+    StringBuffer buff = new StringBuffer();
+    buff.append("Loss function: ");
+    if (m_loss == HINGE) {
+      buff.append("Hinge loss (SVM)\n\n");
+    } else {
+      buff.append("Log loss (logistic regression)\n\n");
+    }
+    int printed = 0;
+    
+    for (int i = 0 ; i < m_weights.length - 1; i++) {
+      if (i != m_data.classIndex()) {
+        if (printed > 0) {
+          buff.append(" + ");
+        } else {
+          buff.append("   ");
+        }
+
+        buff.append(Utils.doubleToString(m_weights[i], 12, 4) +
+            " " + ((m_normalize != null) ? "(normalized) " : "") 
+            + m_data.attribute(i).name() + "\n");
+
+        printed++;
+      }
+    }
+    
+    if (m_weights[m_weights.length - 1] > 0) {
+      buff.append(" + " + Utils.doubleToString(m_weights[m_weights.length - 1], 12, 4));
+    } else {
+      buff.append(" - " + Utils.doubleToString(-m_weights[m_weights.length - 1], 12, 4));
+    }
+    
+    return buff.toString();
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6105 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] args) {
+    runClassifier(new SPegasos(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLinearRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLinearRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLinearRegression.java	(revision 29)
@@ -0,0 +1,294 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleLinearRegression.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+/**
+ <!-- globalinfo-start -->
+ * Learns a simple linear regression model. Picks the attribute that results in the lowest squared error. Missing values are not allowed. Can only deal with numeric attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class SimpleLinearRegression extends AbstractClassifier 
+  implements WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 1679336022895414137L;
+  
+  /** The chosen attribute */
+  private Attribute m_attribute;
+
+  /** The index of the chosen attribute */
+  private int m_attributeIndex;
+
+  /** The slope */
+  private double m_slope;
+  
+  /** The intercept */
+  private double m_intercept;
+
+  /** If true, suppress error message if no useful attribute was found*/   
+  private boolean m_suppressErrorMessage = false;  
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Learns a simple linear regression model. "
+      +"Picks the attribute that results in the lowest squared error. "
+      +"Missing values are not allowed. Can only deal with numeric attributes.";
+  }
+
+  /**
+   * Generate a prediction for the supplied instance.
+   *
+   * @param inst the instance to predict.
+   * @return the prediction
+   * @throws Exception if an error occurs
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+    
+    if (m_attribute == null) {
+      return m_intercept;
+    } else {
+      if (inst.isMissing(m_attribute.index())) {
+	throw new Exception("SimpleLinearRegression: No missing values!");
+      }
+      return m_intercept + m_slope * inst.value(m_attribute.index());
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Builds a simple linear regression model given the supplied training data.
+   *
+   * @param insts the training data.
+   * @throws Exception if an error occurs
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    // Compute mean of target value
+    double yMean = insts.meanOrMode(insts.classIndex());
+
+    // Choose best attribute
+    double minMsq = Double.MAX_VALUE;
+    m_attribute = null;
+    int chosen = -1;
+    double chosenSlope = Double.NaN;
+    double chosenIntercept = Double.NaN;
+    for (int i = 0; i < insts.numAttributes(); i++) {
+      if (i != insts.classIndex()) {
+	m_attribute = insts.attribute(i);
+	
+	// Compute slope and intercept
+	double xMean = insts.meanOrMode(i);
+	double sumWeightedXDiffSquared = 0;
+	double sumWeightedYDiffSquared = 0;
+	m_slope = 0;
+	for (int j = 0; j < insts.numInstances(); j++) {
+	  Instance inst = insts.instance(j);
+	  if (!inst.isMissing(i) && !inst.classIsMissing()) {
+	    double xDiff = inst.value(i) - xMean;
+	    double yDiff = inst.classValue() - yMean;
+	    double weightedXDiff = inst.weight() * xDiff;
+	    double weightedYDiff = inst.weight() * yDiff;
+	    m_slope += weightedXDiff * yDiff;
+	    sumWeightedXDiffSquared += weightedXDiff * xDiff;
+	    sumWeightedYDiffSquared += weightedYDiff * yDiff;
+	  }
+	}
+
+	// Skip attribute if not useful
+	if (sumWeightedXDiffSquared == 0) {
+	  continue;
+	}
+	double numerator = m_slope;
+	m_slope /= sumWeightedXDiffSquared;
+	m_intercept = yMean - m_slope * xMean;
+
+	// Compute sum of squared errors
+	double msq = sumWeightedYDiffSquared - m_slope * numerator;
+
+	// Check whether this is the best attribute
+	if (msq < minMsq) {
+	  minMsq = msq;
+	  chosen = i;
+	  chosenSlope = m_slope;
+	  chosenIntercept = m_intercept;
+	}
+      }
+    }
+
+    // Set parameters
+    if (chosen == -1) {
+      if (!m_suppressErrorMessage) System.err.println("----- no useful attribute found");
+      m_attribute = null;
+      m_attributeIndex = 0;
+      m_slope = 0;
+      m_intercept = yMean;
+    } else {
+      m_attribute = insts.attribute(chosen);
+      m_attributeIndex = chosen;
+      m_slope = chosenSlope;
+      m_intercept = chosenIntercept;
+    }
+  }
+
+  /**
+   * Returns true if a usable attribute was found.
+   *
+   * @return true if a usable attribute was found.
+   */
+  public boolean foundUsefulAttribute(){
+      return (m_attribute != null); 
+  } 
+
+  /**
+   * Returns the index of the attribute used in the regression.
+   *
+   * @return the index of the attribute.
+   */
+  public int getAttributeIndex(){
+      return m_attributeIndex;
+  }
+
+  /**
+   * Returns the slope of the function.
+   *
+   * @return the slope.
+   */
+  public double getSlope(){
+      return m_slope;
+  }
+    
+  /**
+   * Returns the intercept of the function.
+   *
+   * @return the intercept.
+   */
+  public double getIntercept(){
+      return m_intercept;
+  }  
+
+  /**
+   * Turn off the error message that is reported when no useful attribute is found.
+   *
+   * @param s if set to true turns off the error message
+   */
+  public void setSuppressErrorMessage(boolean s){
+      m_suppressErrorMessage = s;
+  }   
+
+  /**
+   * Returns a description of this classifier as a string
+   *
+   * @return a description of the classifier.
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    if (m_attribute == null) {
+      text.append("Predicting constant " + m_intercept);
+    } else {
+      text.append("Linear regression on " + m_attribute.name() + "\n\n");
+      text.append(Utils.doubleToString(m_slope,2) + " * " + 
+		m_attribute.name());
+      if (m_intercept > 0) {
+	text.append(" + " + Utils.doubleToString(m_intercept, 2));
+      } else {
+      text.append(" - " + Utils.doubleToString((-m_intercept), 2)); 
+      }
+    }
+    text.append("\n");
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param argv options
+   */
+  public static void main(String [] argv){
+    runClassifier(new SimpleLinearRegression(), argv);
+  } 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLogistic.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLogistic.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/SimpleLogistic.java	(revision 29)
@@ -0,0 +1,745 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleLogistic.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.trees.lmt.LogisticBase;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Classifier for building linear logistic regression models. LogitBoost with simple regression functions as base learners is used for fitting the logistic models. The optimal number of LogitBoost iterations to perform is cross-validated, which leads to automatic attribute selection. For more information see:<br/>
+ * Niels Landwehr, Mark Hall, Eibe Frank (2005). Logistic Model Trees.<br/>
+ * <br/>
+ * Marc Sumner, Eibe Frank, Mark Hall: Speeding up Logistic Model Tree Induction. In: 9th European Conference on Principles and Practice of Knowledge Discovery in Databases, 675-683, 2005.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Landwehr2005,
+ *    author = {Niels Landwehr and Mark Hall and Eibe Frank},
+ *    booktitle = {Machine Learning},
+ *    number = {1-2},
+ *    pages = {161-205},
+ *    title = {Logistic Model Trees},
+ *    volume = {95},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Sumner2005,
+ *    author = {Marc Sumner and Eibe Frank and Mark Hall},
+ *    booktitle = {9th European Conference on Principles and Practice of Knowledge Discovery in Databases},
+ *    pages = {675-683},
+ *    publisher = {Springer},
+ *    title = {Speeding up Logistic Model Tree Induction},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;iterations&gt;
+ *  Set fixed number of iterations for LogitBoost</pre>
+ * 
+ * <pre> -S
+ *  Use stopping criterion on training set (instead of
+ *  cross-validation)</pre>
+ * 
+ * <pre> -P
+ *  Use error on probabilities (rmse) instead of
+ *  misclassification error for stopping criterion</pre>
+ * 
+ * <pre> -M &lt;iterations&gt;
+ *  Set maximum number of boosting iterations</pre>
+ * 
+ * <pre> -H &lt;iterations&gt;
+ *  Set parameter for heuristic for early stopping of
+ *  LogitBoost.
+ *  If enabled, the minimum is selected greedily, stopping
+ *  if the current minimum has not changed for iter iterations.
+ *  By default, heuristic is enabled with value 50. Set to
+ *  zero to disable heuristic.</pre>
+ * 
+ * <pre> -W &lt;beta&gt;
+ *  Set beta for weight trimming for LogitBoost. Set to 0 for no weight trimming.
+ * </pre>
+ * 
+ * <pre> -A
+ *  The AIC is used to choose the best iteration (instead of CV or training error).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Niels Landwehr 
+ * @author Marc Sumner 
+ * @version $Revision: 5928 $
+ */
+public class SimpleLogistic 
+  extends AbstractClassifier 
+  implements OptionHandler, AdditionalMeasureProducer, WeightedInstancesHandler,
+             TechnicalInformationHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 7397710626304705059L;
+  
+    /**The actual logistic regression model */
+    protected LogisticBase m_boostedModel;
+    
+    /**Filter for converting nominal attributes to binary ones*/
+    protected NominalToBinary m_NominalToBinary = null;
+
+    /**Filter for replacing missing values*/
+    protected ReplaceMissingValues m_ReplaceMissingValues = null;
+    
+    /**If non-negative, use this as fixed number of LogitBoost iterations*/ 
+    protected int m_numBoostingIterations;
+    
+    /**Maximum number of iterations for LogitBoost*/
+    protected int m_maxBoostingIterations = 500;
+    
+    /**Parameter for the heuristic for early stopping of LogitBoost*/
+    protected int m_heuristicStop = 50;
+
+    /**If true, cross-validate number of LogitBoost iterations*/
+    protected boolean m_useCrossValidation;
+
+    /**If true, use minimize error on probabilities instead of misclassification error*/
+    protected boolean m_errorOnProbabilities;
+    
+    /**Threshold for trimming weights. Instances with a weight lower than this (as a percentage
+     * of total weights) are not included in the regression fit.
+     */
+    protected double m_weightTrimBeta = 0;
+    
+    /** If true, the AIC is used to choose the best iteration*/
+    private boolean m_useAIC = false;
+
+    /**
+     * Constructor for creating SimpleLogistic object with standard options.
+     */
+    public SimpleLogistic() {
+	m_numBoostingIterations = 0;
+	m_useCrossValidation = true;
+	m_errorOnProbabilities = false;
+        m_weightTrimBeta = 0;
+        m_useAIC = false;
+    }
+
+    /**
+     * Constructor for creating SimpleLogistic object.
+     * @param numBoostingIterations if non-negative, use this as fixed number of iterations for LogitBoost
+     * @param useCrossValidation cross-validate number of LogitBoost iterations.
+     * @param errorOnProbabilities minimize error on probabilities instead of misclassification error
+     */
+    public SimpleLogistic(int numBoostingIterations, boolean useCrossValidation, 
+			      boolean errorOnProbabilities) { 
+  	m_numBoostingIterations = numBoostingIterations;
+	m_useCrossValidation = useCrossValidation;
+	m_errorOnProbabilities = errorOnProbabilities;
+        m_weightTrimBeta = 0;
+        m_useAIC = false;
+    }
+
+    /**
+     * Returns default capabilities of the classifier.
+     *
+     * @return      the capabilities of this classifier
+     */
+    public Capabilities getCapabilities() {
+      Capabilities result = super.getCapabilities();
+      result.disableAll();
+
+      // attributes
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+      result.enable(Capability.NUMERIC_ATTRIBUTES);
+      result.enable(Capability.DATE_ATTRIBUTES);
+      result.enable(Capability.MISSING_VALUES);
+
+      // class
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+      
+      return result;
+    }
+
+    /**
+     * Builds the logistic regression using LogitBoost.
+     * @param data the training data
+     * @throws Exception if something goes wrong 
+     */
+    public void buildClassifier(Instances data) throws Exception {
+
+      // can classifier handle the data?
+      getCapabilities().testWithFail(data);
+
+      // remove instances with missing class
+      data = new Instances(data);
+      data.deleteWithMissingClass();
+
+	//replace missing values
+	m_ReplaceMissingValues = new ReplaceMissingValues();
+	m_ReplaceMissingValues.setInputFormat(data);
+	data = Filter.useFilter(data, m_ReplaceMissingValues);
+	
+	//convert nominal attributes
+	m_NominalToBinary = new NominalToBinary();
+	m_NominalToBinary.setInputFormat(data);
+	data = Filter.useFilter(data, m_NominalToBinary);
+	
+	//create actual logistic model
+	m_boostedModel = new LogisticBase(m_numBoostingIterations, m_useCrossValidation, m_errorOnProbabilities);
+	m_boostedModel.setMaxIterations(m_maxBoostingIterations);
+	m_boostedModel.setHeuristicStop(m_heuristicStop);
+        m_boostedModel.setWeightTrimBeta(m_weightTrimBeta);
+        m_boostedModel.setUseAIC(m_useAIC);
+	
+	//build logistic model
+	m_boostedModel.buildClassifier(data);
+    }
+    
+    /** 
+     * Returns class probabilities for an instance.
+     *
+     * @param inst the instance to compute the probabilities for
+     * @return the probabilities
+     * @throws Exception if distribution can't be computed successfully
+     */
+    public double[] distributionForInstance(Instance inst) 
+	throws Exception {
+	
+	//replace missing values / convert nominal atts
+	m_ReplaceMissingValues.input(inst);
+	inst = m_ReplaceMissingValues.output();
+	m_NominalToBinary.input(inst);
+	inst = m_NominalToBinary.output();	
+	
+	//obtain probs from logistic model
+	return m_boostedModel.distributionForInstance(inst);	
+    }
+
+    /**
+     * Returns an enumeration describing the available options.
+     *
+     * @return an enumeration of all the available options.
+     */
+    public Enumeration listOptions() {
+	Vector newVector = new Vector();
+	
+	newVector.addElement(new Option(
+	    "\tSet fixed number of iterations for LogitBoost",
+	    "I",1,"-I <iterations>"));
+	
+	newVector.addElement(new Option(
+	    "\tUse stopping criterion on training set (instead of\n"
+	    + "\tcross-validation)",
+	    "S",0,"-S"));
+	
+	newVector.addElement(new Option(
+	    "\tUse error on probabilities (rmse) instead of\n"
+	    + "\tmisclassification error for stopping criterion",
+	    "P",0,"-P"));
+
+	newVector.addElement(new Option(
+	    "\tSet maximum number of boosting iterations",
+	    "M",1,"-M <iterations>"));
+
+	newVector.addElement(new Option(
+	    "\tSet parameter for heuristic for early stopping of\n"
+	    + "\tLogitBoost.\n"
+	    + "\tIf enabled, the minimum is selected greedily, stopping\n"
+	    + "\tif the current minimum has not changed for iter iterations.\n"
+	    + "\tBy default, heuristic is enabled with value 50. Set to\n"
+	    + "\tzero to disable heuristic.",
+	    "H",1,"-H <iterations>"));
+        
+        newVector.addElement(new Option("\tSet beta for weight trimming for LogitBoost. Set to 0 for no weight trimming.\n",
+                                        "W",1,"-W <beta>"));
+        
+        newVector.addElement(new Option("\tThe AIC is used to choose the best iteration (instead of CV or training error).\n",
+                                        "A", 0, "-A"));
+	
+	return newVector.elements();
+    } 
+    
+
+    /**
+     * Parses a given list of options. <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -I &lt;iterations&gt;
+     *  Set fixed number of iterations for LogitBoost</pre>
+     * 
+     * <pre> -S
+     *  Use stopping criterion on training set (instead of
+     *  cross-validation)</pre>
+     * 
+     * <pre> -P
+     *  Use error on probabilities (rmse) instead of
+     *  misclassification error for stopping criterion</pre>
+     * 
+     * <pre> -M &lt;iterations&gt;
+     *  Set maximum number of boosting iterations</pre>
+     * 
+     * <pre> -H &lt;iterations&gt;
+     *  Set parameter for heuristic for early stopping of
+     *  LogitBoost.
+     *  If enabled, the minimum is selected greedily, stopping
+     *  if the current minimum has not changed for iter iterations.
+     *  By default, heuristic is enabled with value 50. Set to
+     *  zero to disable heuristic.</pre>
+     * 
+     * <pre> -W &lt;beta&gt;
+     *  Set beta for weight trimming for LogitBoost. Set to 0 for no weight trimming.
+     * </pre>
+     * 
+     * <pre> -A
+     *  The AIC is used to choose the best iteration (instead of CV or training error).
+     * </pre>
+     * 
+     <!-- options-end -->
+     *
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+
+	String optionString = Utils.getOption('I', options);
+	if (optionString.length() != 0) {
+	    setNumBoostingIterations((new Integer(optionString)).intValue());
+	}
+		
+	setUseCrossValidation(!Utils.getFlag('S', options));
+	setErrorOnProbabilities(Utils.getFlag('P', options));
+	
+	optionString = Utils.getOption('M', options);
+	if (optionString.length() != 0) {
+	    setMaxBoostingIterations((new Integer(optionString)).intValue());
+	}
+
+	optionString = Utils.getOption('H', options);
+	if (optionString.length() != 0) {
+	    setHeuristicStop((new Integer(optionString)).intValue());
+	}
+        
+        optionString = Utils.getOption('W', options);
+        if (optionString.length() != 0) {
+            setWeightTrimBeta((new Double(optionString)).doubleValue());
+        }
+        
+        setUseAIC(Utils.getFlag('A', options));        
+
+	Utils.checkForRemainingOptions(options);
+    } 
+
+    /**
+     * Gets the current settings of the Classifier.
+     *
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String[] getOptions() {
+	String[] options = new String[11];
+	int current = 0;
+		
+	options[current++] = "-I"; 
+	options[current++] = ""+getNumBoostingIterations();
+	
+	if (!getUseCrossValidation()) {
+	    options[current++] = "-S";
+	} 
+
+	if (getErrorOnProbabilities()) {
+	    options[current++] = "-P";
+	} 
+
+	options[current++] = "-M"; 
+	options[current++] = ""+getMaxBoostingIterations();
+	
+	options[current++] = "-H"; 
+	options[current++] = ""+getHeuristicStop();
+        
+        options[current++] = "-W";
+        options[current++] = ""+getWeightTrimBeta();
+        
+        if (getUseAIC()) {
+            options[current++] = "-A";
+        }
+
+	while (current < options.length) {
+	    options[current++] = "";
+	} 
+	return options;
+    } 
+
+    /**
+     * Get the value of numBoostingIterations.
+     * 
+     * @return the number of boosting iterations
+     */
+    public int getNumBoostingIterations(){
+	return m_numBoostingIterations;
+    }
+    /**
+     * Get the value of useCrossValidation.
+     * 
+     * @return true if cross-validation is used
+     */
+    public boolean getUseCrossValidation(){
+	return m_useCrossValidation;
+    }
+
+    /**
+     * Get the value of errorOnProbabilities.
+     * 
+     * @return 	If true, use minimize error on probabilities instead of 
+     * 		misclassification error
+     */
+    public boolean getErrorOnProbabilities(){
+	return m_errorOnProbabilities;
+    }
+    
+    /**
+     * Get the value of maxBoostingIterations.
+     * 
+     * @return the maximum number of boosting iterations
+     */
+    public int getMaxBoostingIterations(){
+	return m_maxBoostingIterations;
+    }
+
+    /**
+     * Get the value of heuristicStop.
+     * 
+     * @return the value of heuristicStop
+     */
+    public int getHeuristicStop(){
+	return m_heuristicStop;
+    }
+    
+    /**
+     * Get the value of weightTrimBeta.
+     */
+    public double getWeightTrimBeta(){
+        return m_weightTrimBeta;
+    }
+    
+    /**
+     * Get the value of useAIC.
+     *
+     * @return Value of useAIC.
+     */
+    public boolean getUseAIC(){
+        return m_useAIC;
+    }
+    
+    /**
+     * Set the value of numBoostingIterations.
+     * 
+     * @param n the number of boosting iterations
+     */
+    public void setNumBoostingIterations(int n){
+	m_numBoostingIterations = n;
+    }
+
+    /**
+     * Set the value of useCrossValidation.
+     * 
+     * @param l whether to use cross-validation
+     */
+    public void setUseCrossValidation(boolean l){
+	m_useCrossValidation = l;
+    }
+
+    /**
+     * Set the value of errorOnProbabilities.
+     * 
+     * @param l If true, use minimize error on probabilities instead of 
+     * 		misclassification error
+     */
+    public void setErrorOnProbabilities(boolean l){
+	m_errorOnProbabilities = l;
+    }
+
+    /**
+     * Set the value of maxBoostingIterations.
+     * 
+     * @param n the maximum number of boosting iterations
+     */
+    public void setMaxBoostingIterations(int n){
+	m_maxBoostingIterations = n;
+    } 
+
+    /**
+     * Set the value of heuristicStop.
+     * 
+     * @param n the value of heuristicStop
+     */
+    public void setHeuristicStop(int n){
+	if (n == 0) 
+	  m_heuristicStop = m_maxBoostingIterations; 
+	else 
+	  m_heuristicStop = n;
+    }
+    
+    /**
+     * Set the value of weightTrimBeta.
+     */
+    public void setWeightTrimBeta(double n){
+        m_weightTrimBeta = n;
+    }
+    
+    /**
+     * Set the value of useAIC.
+     *
+     * @param c Value to assign to useAIC.
+     */
+    public void setUseAIC(boolean c){
+        m_useAIC = c;
+    }
+
+    /**
+     * Get the number of LogitBoost iterations performed (= the number of 
+     * regression functions fit by LogitBoost).
+     * 
+     * @return the number of LogitBoost iterations performed
+     */
+    public int getNumRegressions(){
+	return m_boostedModel.getNumRegressions();
+    }
+
+    /**
+     * Returns a description of the logistic model (attributes/coefficients).
+     * 
+     * @return the model as string
+     */
+    public String toString(){
+	if (m_boostedModel == null) return "No model built";
+	return "SimpleLogistic:\n" + m_boostedModel.toString();
+    }
+
+    /**
+     * Returns the fraction of all attributes in the data that are used in the 
+     * logistic model (in percent). An attribute is used in the model if it is 
+     * used in any of the models for the different classes.
+     * 
+     * @return percentage of attributes used in the model
+     */
+    public double measureAttributesUsed(){
+	return m_boostedModel.percentAttributesUsed();
+    }
+       
+     /**
+     * Returns an enumeration of the additional measure names
+     * @return an enumeration of the measure names
+     */
+    public Enumeration enumerateMeasures() {
+	Vector newVector = new Vector(3);
+	newVector.addElement("measureAttributesUsed");
+	newVector.addElement("measureNumIterations");
+	return newVector.elements();
+    }
+    
+    /**
+     * Returns the value of the named measure
+     * @param additionalMeasureName the name of the measure to query for its value
+     * @return the value of the named measure
+     * @throws IllegalArgumentException if the named measure is not supported
+     */
+    public double getMeasure(String additionalMeasureName) {
+	if (additionalMeasureName.compareToIgnoreCase("measureAttributesUsed") == 0) {
+	    return measureAttributesUsed();
+      	} else if(additionalMeasureName.compareToIgnoreCase("measureNumIterations") == 0){
+	    return getNumRegressions();
+	} else {
+	    throw new IllegalArgumentException(additionalMeasureName 
+					       + " not supported (SimpleLogistic)");
+	}
+    }    
+
+
+    /**
+     * Returns a string describing classifier
+     * @return a description suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String globalInfo() {
+	return "Classifier for building linear logistic regression models. LogitBoost with simple regression "
+	    +"functions as base learners is used for fitting the logistic models. The optimal number of LogitBoost "
+	    +"iterations to perform is cross-validated, which leads to automatic attribute selection. "
+	    +"For more information see:\n"
+	    + getTechnicalInformation().toString();
+    }
+
+    /**
+     * Returns an instance of a TechnicalInformation object, containing 
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     * 
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      TechnicalInformation 	additional;
+      
+      result = new TechnicalInformation(Type.ARTICLE);
+      result.setValue(Field.AUTHOR, "Niels Landwehr and Mark Hall and Eibe Frank");
+      result.setValue(Field.TITLE, "Logistic Model Trees");
+      result.setValue(Field.BOOKTITLE, "Machine Learning");
+      result.setValue(Field.YEAR, "2005");
+      result.setValue(Field.VOLUME, "95");
+      result.setValue(Field.PAGES, "161-205");
+      result.setValue(Field.NUMBER, "1-2");
+      
+      additional = result.add(Type.INPROCEEDINGS);
+      additional.setValue(Field.AUTHOR, "Marc Sumner and Eibe Frank and Mark Hall");
+      additional.setValue(Field.TITLE, "Speeding up Logistic Model Tree Induction");
+      additional.setValue(Field.BOOKTITLE, "9th European Conference on Principles and Practice of Knowledge Discovery in Databases");
+      additional.setValue(Field.YEAR, "2005");
+      additional.setValue(Field.PAGES, "675-683");
+      additional.setValue(Field.PUBLISHER, "Springer");
+      
+      return result;
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String numBoostingIterationsTipText() {
+	return "Set fixed number of iterations for LogitBoost. If >= 0, this sets the number of LogitBoost iterations "
+	    +"to perform. If < 0, the number is cross-validated or a stopping criterion on the training set is used "
+	    +"(depending on the value of useCrossValidation).";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String useCrossValidationTipText() {
+	return "Sets whether the number of LogitBoost iterations is to be cross-validated or the stopping criterion "
+	    +"on the training set should be used. If not set (and no fixed number of iterations was given), "
+	    +"the number of LogitBoost iterations is used that minimizes the error on the training set "
+	    +"(misclassification error or error on probabilities depending on errorOnProbabilities).";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String errorOnProbabilitiesTipText() {
+	return "Use error on the probabilties as error measure when determining the best number of LogitBoost iterations. "
+	    +"If set, the number of LogitBoost iterations is chosen that minimizes the root mean squared error "
+	    +"(either on the training set or in the cross-validation, depending on useCrossValidation).";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String maxBoostingIterationsTipText() {
+	return "Sets the maximum number of iterations for LogitBoost. Default value is 500, for very small/large "
+	    +"datasets a lower/higher value might be preferable.";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String heuristicStopTipText() {
+	return "If heuristicStop > 0, the heuristic for greedy stopping while cross-validating the number of "
+	    +"LogitBoost iterations is enabled. This means LogitBoost is stopped if no new error minimum "
+	    +"has been reached in the last heuristicStop iterations. It is recommended to use this heuristic, "
+	    +"it gives a large speed-up especially on small datasets. The default value is 50.";
+    }    
+    
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String weightTrimBetaTipText() {
+        return "Set the beta value used for weight trimming in LogitBoost. "
+        +"Only instances carrying (1 - beta)% of the weight from previous iteration "
+        +"are used in the next iteration. Set to 0 for no weight trimming. "
+        +"The default value is 0.";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String useAICTipText() {
+        return "The AIC is used to determine when to stop LogitBoost iterations "
+        +"(instead of cross-validation or training error).";
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+
+    /**
+     * Main method for testing this class
+     *
+     * @param argv commandline options 
+     */
+    public static void main(String[] argv) {	
+        runClassifier(new SimpleLogistic(), argv);
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/VotedPerceptron.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/VotedPerceptron.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/VotedPerceptron.java	(revision 29)
@@ -0,0 +1,605 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VotedPerceptron.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implementation of the voted perceptron algorithm by Freund and Schapire. Globally replaces all missing values, and transforms nominal attributes into binary ones.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Y. Freund, R. E. Schapire: Large margin classification using the perceptron algorithm. In: 11th Annual Conference on Computational Learning Theory, New York, NY, 209-217, 1998.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Freund1998,
+ *    address = {New York, NY},
+ *    author = {Y. Freund and R. E. Schapire},
+ *    booktitle = {11th Annual Conference on Computational Learning Theory},
+ *    pages = {209-217},
+ *    publisher = {ACM Press},
+ *    title = {Large margin classification using the perceptron algorithm},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;int&gt;
+ *  The number of iterations to be performed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -E &lt;double&gt;
+ *  The exponent for the polynomial kernel.
+ *  (default 1)</pre>
+ * 
+ * <pre> -S &lt;int&gt;
+ *  The seed for the random number generation.
+ *  (default 1)</pre>
+ * 
+ * <pre> -M &lt;int&gt;
+ *  The maximum number of alterations allowed.
+ *  (default 10000)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class VotedPerceptron 
+  extends AbstractClassifier 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1072429260104568698L;
+  
+  /** The maximum number of alterations to the perceptron */
+  private int m_MaxK = 10000;
+
+  /** The number of iterations */
+  private int m_NumIterations = 1;
+
+  /** The exponent */
+  private double m_Exponent = 1.0;
+
+  /** The actual number of alterations */
+  private int m_K = 0;
+
+  /** The training instances added to the perceptron */
+  private int[] m_Additions = null;
+
+  /** Addition or subtraction? */
+  private boolean[] m_IsAddition = null;
+
+  /** The weights for each perceptron */
+  private int[] m_Weights = null;
+  
+  /** The training instances */
+  private Instances m_Train = null;
+
+  /** Seed used for shuffling the dataset */
+  private int m_Seed = 1;
+
+  /** The filter used to make attributes numeric. */
+  private NominalToBinary m_NominalToBinary;
+
+  /** The filter used to get rid of missing values. */
+  private ReplaceMissingValues m_ReplaceMissingValues;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implementation of the voted perceptron algorithm by Freund and "
+      + "Schapire. Globally replaces all missing values, and transforms "
+      + "nominal attributes into binary ones.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Y. Freund and R. E. Schapire");
+    result.setValue(Field.TITLE, "Large margin classification using the perceptron algorithm");
+    result.setValue(Field.BOOKTITLE, "11th Annual Conference on Computational Learning Theory");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.PAGES, "209-217");
+    result.setValue(Field.PUBLISHER, "ACM Press");
+    result.setValue(Field.ADDRESS, "New York, NY");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option("\tThe number of iterations to be performed.\n"
+				    + "\t(default 1)",
+				    "I", 1, "-I <int>"));
+    newVector.addElement(new Option("\tThe exponent for the polynomial kernel.\n"
+				    + "\t(default 1)",
+				    "E", 1, "-E <double>"));
+    newVector.addElement(new Option("\tThe seed for the random number generation.\n"
+				    + "\t(default 1)",
+				    "S", 1, "-S <int>"));
+    newVector.addElement(new Option("\tThe maximum number of alterations allowed.\n"
+				    + "\t(default 10000)",
+				    "M", 1, "-M <int>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I &lt;int&gt;
+   *  The number of iterations to be performed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -E &lt;double&gt;
+   *  The exponent for the polynomial kernel.
+   *  (default 1)</pre>
+   * 
+   * <pre> -S &lt;int&gt;
+   *  The seed for the random number generation.
+   *  (default 1)</pre>
+   * 
+   * <pre> -M &lt;int&gt;
+   *  The maximum number of alterations allowed.
+   *  (default 10000)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String iterationsString = Utils.getOption('I', options);
+    if (iterationsString.length() != 0) {
+      m_NumIterations = Integer.parseInt(iterationsString);
+    } else {
+      m_NumIterations = 1;
+    }
+    String exponentsString = Utils.getOption('E', options);
+    if (exponentsString.length() != 0) {
+      m_Exponent = (new Double(exponentsString)).doubleValue();
+    } else {
+      m_Exponent = 1.0;
+    }
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      m_Seed = Integer.parseInt(seedString);
+    } else {
+      m_Seed = 1;
+    }
+    String alterationsString = Utils.getOption('M', options);
+    if (alterationsString.length() != 0) {
+      m_MaxK = Integer.parseInt(alterationsString);
+    } else {
+      m_MaxK = 10000;
+    }
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+
+    String[] options = new String [8];
+    int current = 0;
+
+    options[current++] = "-I"; options[current++] = "" + m_NumIterations;
+    options[current++] = "-E"; options[current++] = "" + m_Exponent;
+    options[current++] = "-S"; options[current++] = "" + m_Seed;
+    options[current++] = "-M"; options[current++] = "" + m_MaxK;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Builds the ensemble of perceptrons.
+   *
+   * @param insts the data to train the classifier with
+   * @throws Exception if something goes wrong during building
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+ 
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    // Filter data
+    m_Train = new Instances(insts);
+    m_ReplaceMissingValues = new ReplaceMissingValues();
+    m_ReplaceMissingValues.setInputFormat(m_Train);
+    m_Train = Filter.useFilter(m_Train, m_ReplaceMissingValues);
+    
+    m_NominalToBinary = new NominalToBinary();
+    m_NominalToBinary.setInputFormat(m_Train);
+    m_Train = Filter.useFilter(m_Train, m_NominalToBinary);
+
+    /** Randomize training data */
+    m_Train.randomize(new Random(m_Seed));
+
+    /** Make space to store perceptrons */
+    m_Additions = new int[m_MaxK + 1];
+    m_IsAddition = new boolean[m_MaxK + 1];
+    m_Weights = new int[m_MaxK + 1];
+
+    /** Compute perceptrons */
+    m_K = 0;
+  out:
+    for (int it = 0; it < m_NumIterations; it++) {
+      for (int i = 0; i < m_Train.numInstances(); i++) {
+	Instance inst = m_Train.instance(i);
+	if (!inst.classIsMissing()) {
+	  int prediction = makePrediction(m_K, inst);
+	  int classValue = (int) inst.classValue();
+	  if (prediction == classValue) {
+	    m_Weights[m_K]++;
+	  } else {
+	    m_IsAddition[m_K] = (classValue == 1);
+	    m_Additions[m_K] = i;
+	    m_K++;
+	    m_Weights[m_K]++;
+	  }
+	  if (m_K == m_MaxK) {
+	    break out;
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Outputs the distribution for the given output.
+   *
+   * Pipes output of SVM through sigmoid function.
+   * @param inst the instance for which distribution is to be computed
+   * @return the distribution
+   * @throws Exception if something goes wrong
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+
+    // Filter instance
+    m_ReplaceMissingValues.input(inst);
+    m_ReplaceMissingValues.batchFinished();
+    inst = m_ReplaceMissingValues.output();
+
+    m_NominalToBinary.input(inst);
+    m_NominalToBinary.batchFinished();
+    inst = m_NominalToBinary.output();
+    
+    // Get probabilities
+    double output = 0, sumSoFar = 0;
+    if (m_K > 0) {
+      for (int i = 0; i <= m_K; i++) {
+	if (sumSoFar < 0) {
+	  output -= m_Weights[i];
+	} else {
+	  output += m_Weights[i];
+	}
+	if (m_IsAddition[i]) {
+	  sumSoFar += innerProduct(m_Train.instance(m_Additions[i]), inst);
+	} else {
+	  sumSoFar -= innerProduct(m_Train.instance(m_Additions[i]), inst);
+	}
+      }
+    }
+    double[] result = new double[2];
+    result[1] = 1 / (1 + Math.exp(-output));
+    result[0] = 1 - result[1];
+
+    return result;
+  }
+
+  /**
+   * Returns textual description of classifier.
+   * 
+   * @return the model as string
+   */
+  public String toString() {
+
+    return "VotedPerceptron: Number of perceptrons=" + m_K;
+  }
+ 
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxKTipText() {
+    return "The maximum number of alterations to the perceptron.";
+  }
+
+  /**
+   * Get the value of maxK.
+   *
+   * @return Value of maxK.
+   */
+  public int getMaxK() {
+    
+    return m_MaxK;
+  }
+  
+  /**
+   * Set the value of maxK.
+   *
+   * @param v  Value to assign to maxK.
+   */
+  public void setMaxK(int v) {
+    
+    m_MaxK = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "Number of iterations to be performed.";
+  }
+
+  /**
+   * Get the value of NumIterations.
+   *
+   * @return Value of NumIterations.
+   */
+  public int getNumIterations() {
+    
+    return m_NumIterations;
+  }
+  
+  /**
+   * Set the value of NumIterations.
+   *
+   * @param v  Value to assign to NumIterations.
+   */
+  public void setNumIterations(int v) {
+    
+    m_NumIterations = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String exponentTipText() {
+    return "Exponent for the polynomial kernel.";
+  }
+
+  /**
+   * Get the value of exponent.
+   *
+   * @return Value of exponent.
+   */
+  public double getExponent() {
+    
+    return m_Exponent;
+  }
+  
+  /**
+   * Set the value of exponent.
+   *
+   * @param v  Value to assign to exponent.
+   */
+  public void setExponent(double v) {
+    
+    m_Exponent = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Seed for the random number generator.";
+  }
+
+  /**
+   * Get the value of Seed.
+   *
+   * @return Value of Seed.
+   */
+  public int getSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the value of Seed.
+   *
+   * @param v  Value to assign to Seed.
+   */
+  public void setSeed(int v) {
+    
+    m_Seed = v;
+  }
+
+  /** 
+   * Computes the inner product of two instances
+   * 
+   * @param i1 first instance
+   * @param i2 second instance
+   * @return the inner product
+   * @throws Exception if computation fails
+   */
+  private double innerProduct(Instance i1, Instance i2) throws Exception {
+
+    // we can do a fast dot product
+    double result = 0;
+    int n1 = i1.numValues(); int n2 = i2.numValues();
+    int classIndex = m_Train.classIndex();
+    for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
+        int ind1 = i1.index(p1);
+        int ind2 = i2.index(p2);
+        if (ind1 == ind2) {
+            if (ind1 != classIndex) {
+                result += i1.valueSparse(p1) *
+                          i2.valueSparse(p2);
+            }
+            p1++; p2++;
+        } else if (ind1 > ind2) {
+            p2++;
+        } else {
+            p1++;
+        }
+    }
+    result += 1.0;
+    
+    if (m_Exponent != 1) {
+      return Math.pow(result, m_Exponent);
+    } else {
+      return result;
+    }
+  }
+
+  /** 
+   * Compute a prediction from a perceptron
+   * 
+   * @param k
+   * @param inst the instance to make a prediction for
+   * @return the prediction
+   * @throws Exception if computation fails
+   */
+  private int makePrediction(int k, Instance inst) throws Exception {
+
+    double result = 0;
+    for (int i = 0; i < k; i++) {
+      if (m_IsAddition[i]) {
+	result += innerProduct(m_Train.instance(m_Additions[i]), inst);
+      } else {
+	result -= innerProduct(m_Train.instance(m_Additions[i]), inst);
+      }
+    }
+    if (result < 0) {
+      return 0;
+    } else {
+      return 1;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method.
+   * 
+   * @param argv the commandline options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new VotedPerceptron(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/Winnow.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/Winnow.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/Winnow.java	(revision 29)
@@ -0,0 +1,869 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Winnow.java
+ *    Copyright (C) 2002 J. Lindgren
+ *
+ */
+package weka.classifiers.functions;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements Winnow and Balanced Winnow algorithms by Littlestone.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * N. Littlestone (1988). Learning quickly when irrelevant attributes are abound: A new linear threshold algorithm. Machine Learning. 2:285-318.<br/>
+ * <br/>
+ * N. Littlestone (1989). Mistake bounds and logarithmic linear-threshold learning algorithms. University of California, Santa Cruz.<br/>
+ * <br/>
+ * Does classification for problems with nominal attributes (which it converts into binary attributes).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Littlestone1988,
+ *    author = {N. Littlestone},
+ *    journal = {Machine Learning},
+ *    pages = {285-318},
+ *    title = {Learning quickly when irrelevant attributes are abound: A new linear threshold algorithm},
+ *    volume = {2},
+ *    year = {1988}
+ * }
+ * 
+ * &#64;techreport{Littlestone1989,
+ *    address = {University of California, Santa Cruz},
+ *    author = {N. Littlestone},
+ *    institution = {University of California},
+ *    note = {Technical Report UCSC-CRL-89-11},
+ *    title = {Mistake bounds and logarithmic linear-threshold learning algorithms},
+ *    year = {1989}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L
+ *  Use the baLanced version
+ *  (default false)</pre>
+ * 
+ * <pre> -I &lt;int&gt;
+ *  The number of iterations to be performed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -A &lt;double&gt;
+ *  Promotion coefficient alpha.
+ *  (default 2.0)</pre>
+ * 
+ * <pre> -B &lt;double&gt;
+ *  Demotion coefficient beta.
+ *  (default 0.5)</pre>
+ * 
+ * <pre> -H &lt;double&gt;
+ *  Prediction threshold.
+ *  (default -1.0 == number of attributes)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  Starting weights.
+ *  (default 2.0)</pre>
+ * 
+ * <pre> -S &lt;int&gt;
+ *  Default random seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author J. Lindgren (jtlindgr at cs.helsinki.fi)
+ * @version $Revision: 5928 $ 
+*/
+public class Winnow 
+  extends AbstractClassifier 
+  implements UpdateableClassifier, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3543770107994321324L;
+  
+  /** Use the balanced variant? **/
+  protected boolean m_Balanced;
+ 
+  /** The number of iterations **/
+  protected int m_numIterations = 1;
+
+  /** The promotion coefficient **/
+  protected double m_Alpha = 2.0;
+
+  /** The demotion coefficient **/
+  protected double m_Beta = 0.5;
+
+  /** Prediction threshold, <0 == numAttributes **/
+  protected double m_Threshold = -1.0;
+  
+  /** Random seed used for shuffling the dataset, -1 == disable **/
+  protected int m_Seed = 1;
+
+  /** Accumulated mistake count (for statistics) **/
+  protected int m_Mistakes;
+
+  /** Starting weights for the prediction vector(s) **/
+  protected double m_defaultWeight = 2.0;
+  
+  /** The weight vector for prediction (pos) */
+  private double[] m_predPosVector = null;
+  
+  /** The weight vector for prediction (neg) */
+  private double[] m_predNegVector = null;
+
+  /** The true threshold used for prediction **/
+  private double m_actualThreshold;
+
+  /** The training instances */
+  private Instances m_Train = null;
+
+  /** The filter used to make attributes numeric. */
+  private NominalToBinary m_NominalToBinary;
+
+  /** The filter used to get rid of missing values. */
+  private ReplaceMissingValues m_ReplaceMissingValues;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Implements Winnow and Balanced Winnow algorithms by "
+      + "Littlestone.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString()
+      + "\n\n"
+      + "Does classification for problems with nominal attributes "
+      + "(which it converts into binary attributes).";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "N. Littlestone");
+    result.setValue(Field.YEAR, "1988");
+    result.setValue(Field.TITLE, "Learning quickly when irrelevant attributes are abound: A new linear threshold algorithm");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "2");
+    result.setValue(Field.PAGES, "285-318");
+    
+    additional = result.add(Type.TECHREPORT);
+    additional.setValue(Field.AUTHOR, "N. Littlestone");
+    additional.setValue(Field.YEAR, "1989");
+    additional.setValue(Field.TITLE, "Mistake bounds and logarithmic linear-threshold learning algorithms");
+    additional.setValue(Field.INSTITUTION, "University of California");
+    additional.setValue(Field.ADDRESS, "University of California, Santa Cruz");
+    additional.setValue(Field.NOTE, "Technical Report UCSC-CRL-89-11");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(7);
+    
+    newVector.addElement(new Option("\tUse the baLanced version\n"
+				    + "\t(default false)",
+				    "L", 0, "-L"));
+    newVector.addElement(new Option("\tThe number of iterations to be performed.\n"
+				    + "\t(default 1)",
+				    "I", 1, "-I <int>"));
+    newVector.addElement(new Option("\tPromotion coefficient alpha.\n"
+				    + "\t(default 2.0)",
+				    "A", 1, "-A <double>"));
+    newVector.addElement(new Option("\tDemotion coefficient beta.\n"
+				    + "\t(default 0.5)",
+				    "B", 1, "-B <double>"));
+    newVector.addElement(new Option("\tPrediction threshold.\n"
+				    + "\t(default -1.0 == number of attributes)",
+				    "H", 1, "-H <double>"));
+    newVector.addElement(new Option("\tStarting weights.\n"
+				    + "\t(default 2.0)",
+				    "W", 1, "-W <double>"));
+    newVector.addElement(new Option("\tDefault random seed.\n"
+				    + "\t(default 1)",
+				    "S", 1, "-S <int>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.<p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L
+   *  Use the baLanced version
+   *  (default false)</pre>
+   * 
+   * <pre> -I &lt;int&gt;
+   *  The number of iterations to be performed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -A &lt;double&gt;
+   *  Promotion coefficient alpha.
+   *  (default 2.0)</pre>
+   * 
+   * <pre> -B &lt;double&gt;
+   *  Demotion coefficient beta.
+   *  (default 0.5)</pre>
+   * 
+   * <pre> -H &lt;double&gt;
+   *  Prediction threshold.
+   *  (default -1.0 == number of attributes)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  Starting weights.
+   *  (default 2.0)</pre>
+   * 
+   * <pre> -S &lt;int&gt;
+   *  Default random seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    m_Balanced = Utils.getFlag('L', options);
+	
+    String iterationsString = Utils.getOption('I', options);
+    if (iterationsString.length() != 0) {
+      m_numIterations = Integer.parseInt(iterationsString);
+    }
+    String alphaString = Utils.getOption('A', options);
+    if (alphaString.length() != 0) { 
+      m_Alpha = (new Double(alphaString)).doubleValue();
+    }
+    String betaString = Utils.getOption('B', options);
+    if (betaString.length() != 0) {
+      m_Beta = (new Double(betaString)).doubleValue();
+    }
+    String tString = Utils.getOption('H', options);
+    if (tString.length() != 0) {
+      m_Threshold = (new Double(tString)).doubleValue();
+    }
+    String wString = Utils.getOption('W', options);
+    if (wString.length() != 0) {
+      m_defaultWeight = (new Double(wString)).doubleValue();
+    }
+    String rString = Utils.getOption('S', options);
+    if (rString.length() != 0) {
+      m_Seed = Integer.parseInt(rString);
+    }
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+
+    String[] options = new String [20];
+    int current = 0;
+
+    if(m_Balanced) {
+      options[current++] = "-L"; 
+    }
+    
+    options[current++] = "-I"; options[current++] = "" + m_numIterations;
+    options[current++] = "-A"; options[current++] = "" + m_Alpha;
+    options[current++] = "-B"; options[current++] = "" + m_Beta;
+    options[current++] = "-H"; options[current++] = "" + m_Threshold;
+    options[current++] = "-W"; options[current++] = "" + m_defaultWeight;
+    options[current++] = "-S"; options[current++] = "" + m_Seed;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param insts the data to train the classifier with
+   * @throws Exception if something goes wrong during building
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    // Filter data
+    m_Train = new Instances(insts);
+    
+    m_ReplaceMissingValues = new ReplaceMissingValues();
+    m_ReplaceMissingValues.setInputFormat(m_Train);
+    m_Train = Filter.useFilter(m_Train, m_ReplaceMissingValues);
+    m_NominalToBinary = new NominalToBinary();
+    m_NominalToBinary.setInputFormat(m_Train);
+    m_Train = Filter.useFilter(m_Train, m_NominalToBinary);
+
+    /** Randomize training data */
+    if(m_Seed != -1) {
+      m_Train.randomize(new Random(m_Seed));
+    }
+
+    /** Make space to store weights */
+    m_predPosVector = new double[m_Train.numAttributes()];
+
+    if(m_Balanced) {
+      m_predNegVector = new double[m_Train.numAttributes()];
+    }
+
+    /** Initialize the weights to starting values **/
+    for(int i = 0; i < m_Train.numAttributes(); i++)
+      m_predPosVector[i] = m_defaultWeight;
+
+    if(m_Balanced) {
+      for(int i = 0; i < m_Train.numAttributes(); i++) {
+	m_predNegVector[i] = m_defaultWeight;
+      }
+    }
+	
+    /** Set actual prediction threshold **/
+    if(m_Threshold<0) {
+      m_actualThreshold = (double)m_Train.numAttributes()-1;
+    } else {
+      m_actualThreshold = m_Threshold;
+    }
+
+    m_Mistakes=0;
+
+    /** Compute the weight vectors **/
+    if(m_Balanced) {
+      for (int it = 0; it < m_numIterations; it++) {
+	for (int i = 0; i < m_Train.numInstances(); i++) {
+	  actualUpdateClassifierBalanced(m_Train.instance(i));
+	}
+      }
+    } else {
+      for (int it = 0; it < m_numIterations; it++) {
+	for (int i = 0; i < m_Train.numInstances(); i++) {
+	  actualUpdateClassifier(m_Train.instance(i));
+	}
+      }
+    }
+  }
+  
+  /**
+   * Updates the classifier with a new learning example
+   *
+   * @param instance the instance to update the classifier with
+   * @throws Exception if something goes wrong
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+	
+    m_ReplaceMissingValues.input(instance);
+    m_ReplaceMissingValues.batchFinished();
+    Instance filtered = m_ReplaceMissingValues.output();
+    m_NominalToBinary.input(filtered);
+    m_NominalToBinary.batchFinished();
+    filtered = m_NominalToBinary.output();
+
+    if(m_Balanced) {
+      actualUpdateClassifierBalanced(filtered);
+    } else {
+      actualUpdateClassifier(filtered);
+    }
+  }
+  
+  /**
+   * Actual update routine for prefiltered instances
+   *
+   * @param inst the instance to update the classifier with
+   * @throws Exception if something goes wrong
+   */
+  private void actualUpdateClassifier(Instance inst) throws Exception {
+    
+    double posmultiplier;
+	
+    if (!inst.classIsMissing()) {
+      double prediction = makePrediction(inst);
+   
+      if (prediction != inst.classValue()) {
+	m_Mistakes++;
+
+	if(prediction == 0) {
+	  /* false neg: promote */
+	  posmultiplier=m_Alpha;
+	} else {
+	  /* false pos: demote */
+	  posmultiplier=m_Beta;
+	}
+	int n1 = inst.numValues(); int classIndex = m_Train.classIndex();
+	for(int l = 0 ; l < n1 ; l++) {
+	  if(inst.index(l) != classIndex && inst.valueSparse(l)==1) {
+	    m_predPosVector[inst.index(l)]*=posmultiplier;
+	  }
+	}
+	//Utils.normalize(m_predPosVector);
+      }
+    }
+    else {
+      System.out.println("CLASS MISSING");
+    }
+  }
+  
+  /**
+   * Actual update routine (balanced) for prefiltered instances
+   *
+   * @param inst the instance to update the classifier with
+   * @throws Exception if something goes wrong
+   */
+  private void actualUpdateClassifierBalanced(Instance inst) throws Exception {
+    
+    double posmultiplier,negmultiplier;
+
+    if (!inst.classIsMissing()) {
+      double prediction = makePredictionBalanced(inst);
+        
+      if (prediction != inst.classValue()) {
+	m_Mistakes++;
+	
+	if(prediction == 0) {
+	  /* false neg: promote positive, demote negative*/
+	  posmultiplier=m_Alpha;
+	  negmultiplier=m_Beta;
+	} else {
+	  /* false pos: demote positive, promote negative */
+	  posmultiplier=m_Beta;
+	  negmultiplier=m_Alpha;
+	}
+	int n1 = inst.numValues(); int classIndex = m_Train.classIndex();
+	for(int l = 0 ; l < n1 ; l++) {
+	  if(inst.index(l) != classIndex && inst.valueSparse(l)==1) {
+	    m_predPosVector[inst.index(l)]*=posmultiplier;
+	    m_predNegVector[inst.index(l)]*=negmultiplier;
+	  }
+	}
+	//Utils.normalize(m_predPosVector);
+	//Utils.normalize(m_predNegVector);
+      }
+    }
+    else {
+      System.out.println("CLASS MISSING");
+    }
+  }
+
+  /**
+   * Outputs the prediction for the given instance.
+   *
+   * @param inst the instance for which prediction is to be computed
+   * @return the prediction
+   * @throws Exception if something goes wrong
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+
+    m_ReplaceMissingValues.input(inst);
+    m_ReplaceMissingValues.batchFinished();
+    Instance filtered = m_ReplaceMissingValues.output();
+    m_NominalToBinary.input(filtered);
+    m_NominalToBinary.batchFinished();
+    filtered = m_NominalToBinary.output();
+
+    if(m_Balanced) {
+      return(makePredictionBalanced(filtered));
+    } else {
+      return(makePrediction(filtered));
+    }
+  }
+  
+  /** 
+   * Compute the actual prediction for prefiltered instance
+   *
+   * @param inst the instance for which prediction is to be computed
+   * @return the prediction
+   * @throws Exception if something goes wrong
+   */
+  private double makePrediction(Instance inst) throws Exception {
+
+    double total = 0;
+
+    int n1 = inst.numValues(); int classIndex = m_Train.classIndex();
+	
+    for(int i=0;i<n1;i++) {
+      if(inst.index(i) != classIndex && inst.valueSparse(i)==1) {
+	total+=m_predPosVector[inst.index(i)];
+      }
+    }
+    
+    if(total > m_actualThreshold) {
+      return(1);
+    } else {
+      return(0);
+    }
+  }
+  
+  /** 
+   * Compute our prediction (Balanced) for prefiltered instance 
+   *
+   * @param inst the instance for which prediction is to be computed
+   * @return the prediction
+   * @throws Exception if something goes wrong
+   */
+  private double makePredictionBalanced(Instance inst) throws Exception {
+    double total=0;
+	
+    int n1 = inst.numValues(); int classIndex = m_Train.classIndex();
+    for(int i=0;i<n1;i++) {
+      if(inst.index(i) != classIndex && inst.valueSparse(i)==1) {
+	total+=(m_predPosVector[inst.index(i)]-m_predNegVector[inst.index(i)]);
+      }
+    }
+     
+    if(total > m_actualThreshold) {
+      return(1);
+    } else {
+      return(0);
+    }
+  }
+
+  /**
+   * Returns textual description of the classifier.
+   * 
+   * @return textual description of the classifier
+   */
+  public String toString() {
+
+    if(m_predPosVector==null)
+      return("Winnow: No model built yet.");
+	   
+    String result = "Winnow\n\nAttribute weights\n\n";
+	
+    int classIndex = m_Train.classIndex();
+
+    if(!m_Balanced) {
+      for( int i = 0 ; i < m_Train.numAttributes(); i++) {
+	if(i!=classIndex)
+	  result += "w" + i + " " + m_predPosVector[i] + "\n";
+      }
+    } else {
+      for( int i = 0 ; i < m_Train.numAttributes(); i++) {
+	if(i!=classIndex) {
+	  result += "w" + i + " p " + m_predPosVector[i];
+	  result += " n " + m_predNegVector[i];
+	  
+	  double wdiff=m_predPosVector[i]-m_predNegVector[i];
+	  
+	  result += " d " + wdiff + "\n";
+	}
+      }
+    }
+    result += "\nCumulated mistake count: " + m_Mistakes + "\n\n";
+	
+    return(result);
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String balancedTipText() {
+    return "Whether to use the balanced version of the algorithm.";
+  }
+
+  /**
+   * Get the value of Balanced.
+   *
+   * @return Value of Balanced.
+   */
+  public boolean getBalanced() {
+    
+    return m_Balanced;
+  }
+  
+  /**
+   * Set the value of Balanced.
+   *
+   * @param b  Value to assign to Balanced.
+   */
+  public void setBalanced(boolean b) {
+    
+    m_Balanced = b;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String alphaTipText() {
+    return "Promotion coefficient alpha.";
+  }
+  
+  /**
+   * Get the value of Alpha.
+   *
+   * @return Value of Alpha.
+   */
+  public double getAlpha() {
+    
+    return(m_Alpha);
+  }
+  
+  /**
+   * Set the value of Alpha.
+   *
+   * @param a  Value to assign to Alpha.
+   */
+  public void setAlpha(double a) {
+    
+    m_Alpha = a;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String betaTipText() {
+    return "Demotion coefficient beta.";
+  }
+  
+  /**
+   * Get the value of Beta.
+   *
+   * @return Value of Beta.
+   */
+  public double getBeta() {
+    
+    return(m_Beta);
+  }
+  
+  /**
+   * Set the value of Beta.
+   *
+   * @param b  Value to assign to Beta.
+   */
+  public void setBeta(double b) {
+    
+    m_Beta = b;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+    return "Prediction threshold (-1 means: set to number of attributes).";
+  }
+  
+  /**
+   * Get the value of Threshold.
+   *
+   * @return Value of Threshold.
+   */
+  public double getThreshold() {
+    
+    return m_Threshold;
+  }
+  
+  /**
+   * Set the value of Threshold.
+   *
+   * @param t  Value to assign to Threshold.
+   */
+  public void setThreshold(double t) {
+    
+    m_Threshold = t;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String defaultWeightTipText() {
+    return "Initial value of weights/coefficients.";
+  }
+  
+  /**
+   * Get the value of defaultWeight.
+   *
+   * @return Value of defaultWeight.
+   */
+  public double getDefaultWeight() {
+    
+    return m_defaultWeight;
+  }
+  
+  /**
+   * Set the value of defaultWeight.
+   *
+   * @param w  Value to assign to defaultWeight.
+   */
+  public void setDefaultWeight(double w) {
+    
+    m_defaultWeight = w;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "The number of iterations to be performed.";
+  }
+  
+  /**
+   * Get the value of numIterations.
+   *
+   * @return Value of numIterations.
+   */
+  public int getNumIterations() {
+    
+    return m_numIterations;
+  }
+  
+  /**
+   * Set the value of numIterations.
+   *
+   * @param v  Value to assign to numIterations.
+   */
+  public void setNumIterations(int v) {
+    
+    m_numIterations = v;
+  }
+     
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Random number seed used for data shuffling (-1 means no "
+      + "randomization).";
+  }
+
+  /**
+   * Get the value of Seed.
+   *
+   * @return Value of Seed.
+   */
+  public int getSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the value of Seed.
+   *
+   * @param v  Value to assign to Seed.
+   */
+  public void setSeed(int v) {
+    
+    m_Seed = v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method.
+   * 
+   * @param argv the commandline options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new Winnow(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/LinearUnit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/LinearUnit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/LinearUnit.java	(revision 29)
@@ -0,0 +1,116 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LinearUnit.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.neural;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * This can be used by the 
+ * neuralnode to perform all it's computations (as a Linear unit).
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class LinearUnit
+  implements NeuralMethod, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8572152807755673630L;
+  
+  /**
+   * This function calculates what the output value should be.
+   * @param node The node to calculate the value for.
+   * @return The value.
+   */
+  public double outputValue(NeuralNode node) {
+    double[] weights = node.getWeights();
+    NeuralConnection[] inputs = node.getInputs();
+    double value = weights[0];
+    for (int noa = 0; noa < node.getNumInputs(); noa++) {
+      
+      value += inputs[noa].outputValue(true) 
+	* weights[noa+1];
+    }
+     
+    return value;
+  }
+  
+  /**
+   * This function calculates what the error value should be.
+   * @param node The node to calculate the error for.
+   * @return The error.
+   */
+  public double errorValue(NeuralNode node) {
+    //then calculate the error.
+    
+    NeuralConnection[] outputs = node.getOutputs();
+    int[] oNums = node.getOutputNums();
+    double error = 0;
+ 
+    for (int noa = 0; noa < node.getNumOutputs(); noa++) {
+      error += outputs[noa].errorValue(true) 
+	* outputs[noa].weightValue(oNums[noa]);
+    }
+    return error;
+  }
+
+  /**
+   * This function will calculate what the change in weights should be
+   * and also update them.
+   * @param node The node to update the weights for.
+   * @param learn The learning rate to use.
+   * @param momentum The momentum to use.
+   */
+  public void updateWeights(NeuralNode node, double learn, double momentum) {
+
+    NeuralConnection[] inputs = node.getInputs();
+    double[] cWeights = node.getChangeInWeights();
+    double[] weights = node.getWeights();
+    
+    double learnTimesError = 0;
+    learnTimesError = learn * node.errorValue(false);
+    
+    double c = learnTimesError + momentum * cWeights[0];
+    weights[0] += c;
+    cWeights[0] = c;
+      
+    int stopValue = node.getNumInputs() + 1;
+    for (int noa = 1; noa < stopValue; noa++) {
+      
+      c = learnTimesError * inputs[noa-1].outputValue(false);
+      c += momentum * cWeights[noa];
+      
+      weights[noa] += c;
+      cWeights[noa] = c; 
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralConnection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralConnection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralConnection.java	(revision 29)
@@ -0,0 +1,745 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NeuralConnection.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.neural;
+
+import weka.core.RevisionHandler;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.io.Serializable;
+
+/** 
+ * Abstract unit in a NeuralNetwork.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5402 $
+ */
+public abstract class NeuralConnection
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -286208828571059163L;
+
+  //bitwise flags for the types of unit.
+
+  /** This unit is not connected to any others. */
+  public static final int UNCONNECTED = 0;
+  
+  /** This unit is a pure input unit. */
+  public static final int PURE_INPUT = 1;
+  
+  /** This unit is a pure output unit. */
+  public static final int PURE_OUTPUT = 2;
+  
+  /** This unit is an input unit. */
+  public static final int INPUT = 4;
+  
+  /** This unit is an output unit. */
+  public static final int OUTPUT = 8;
+  
+  /** This flag is set once the unit has a connection. */
+  public static final int CONNECTED = 16;
+
+
+
+  /////The difference between pure and not is that pure is used to feed 
+  /////the neural network the attribute values and the errors on the outputs
+  /////Beyond that they do no calculations, and have certain restrictions
+  /////on the connections they can make.
+
+
+
+  /** The list of inputs to this unit. */
+  protected NeuralConnection[] m_inputList;
+
+  /** The list of outputs from this unit. */
+  protected NeuralConnection[] m_outputList;
+
+  /** The numbering for the connections at the other end of the input lines. */
+  protected int[] m_inputNums;
+  
+  /** The numbering for the connections at the other end of the out lines. */
+  protected int[] m_outputNums;
+
+  /** The number of inputs. */
+  protected int m_numInputs;
+
+  /** The number of outputs. */
+  protected int m_numOutputs;
+
+  /** The output value for this unit, NaN if not calculated. */
+  protected double m_unitValue;
+
+  /** The error value for this unit, NaN if not calculated. */
+  protected double m_unitError;
+  
+  /** True if the weights have already been updated. */
+  protected boolean m_weightsUpdated;
+  
+  /** The string that uniquely (provided naming is done properly) identifies
+   * this unit. */
+  protected String m_id;
+
+  /** The type of unit this is. */
+  protected int m_type;
+
+  /** The x coord of this unit purely for displaying purposes. */
+  protected double m_x;
+  
+  /** The y coord of this unit purely for displaying purposes. */
+  protected double m_y;
+  
+
+  
+  
+  /**
+   * Constructs The unit with the basic connection information prepared for
+   * use. 
+   * 
+   * @param id the unique id of the unit
+   */
+  public NeuralConnection(String id) {
+    
+    m_id = id;
+    m_inputList = new NeuralConnection[0];
+    m_outputList = new NeuralConnection[0];
+    m_inputNums = new int[0];
+    m_outputNums = new int[0];
+
+    m_numInputs = 0;
+    m_numOutputs = 0;
+
+    m_unitValue = Double.NaN;
+    m_unitError = Double.NaN;
+
+    m_weightsUpdated = false;
+    m_x = 0;
+    m_y = 0;
+    m_type = UNCONNECTED;
+  }
+  
+  
+  /**
+   * @return The identity string of this unit.
+   */
+  public String getId() {
+    return m_id;
+  }
+
+  /**
+   * @return The type of this unit.
+   */
+  public int getType() {
+    return m_type;
+  }
+
+  /**
+   * @param t The new type of this unit.
+   */
+  public void setType(int t) {
+    m_type = t;
+  }
+
+  /**
+   * Call this to reset the unit for another run.
+   * It is expected by that this unit will call the reset functions of all 
+   * input units to it. It is also expected that this will not be done
+   * if the unit has already been reset (or atleast appears to be).
+   */
+  public abstract void reset();
+
+  /**
+   * Call this to get the output value of this unit. 
+   * @param calculate True if the value should be calculated if it hasn't been
+   * already.
+   * @return The output value, or NaN, if the value has not been calculated.
+   */
+  public abstract double outputValue(boolean calculate);
+
+  /**
+   * Call this to get the error value of this unit.
+   * @param calculate True if the value should be calculated if it hasn't been
+   * already.
+   * @return The error value, or NaN, if the value has not been calculated.
+   */
+  public abstract double errorValue(boolean calculate);
+  
+  /**
+   * Call this to have the connection save the current
+   * weights.
+   */
+  public abstract void saveWeights();
+  
+  /**
+   * Call this to have the connection restore from the saved
+   * weights.
+   */
+  public abstract void restoreWeights();
+
+  /**
+   * Call this to get the weight value on a particular connection.
+   * @param n The connection number to get the weight for, -1 if The threshold
+   * weight should be returned.
+   * @return This function will default to return 1. If overridden, it should
+   * return the value for the specified connection or if -1 then it should 
+   * return the threshold value. If no value exists for the specified 
+   * connection, NaN will be returned.
+   */
+  public double weightValue(int n) {
+    return 1;
+  }
+
+  /**
+   * Call this function to update the weight values at this unit.
+   * After the weights have been updated at this unit, All the
+   * input connections will then be called from this to have their
+   * weights updated.
+   * @param l The learning Rate to use.
+   * @param m The momentum to use.
+   */
+  public void updateWeights(double l, double m) {
+    
+    //the action the subclasses should perform is upto them 
+    //but if they coverride they should make a call to this to
+    //call the method for all their inputs.
+    
+    if (!m_weightsUpdated) {
+      for (int noa = 0; noa < m_numInputs; noa++) {
+	m_inputList[noa].updateWeights(l, m);
+      }
+      m_weightsUpdated = true;
+    }
+    
+  }
+
+  /**
+   * Use this to get easy access to the inputs.
+   * It is not advised to change the entries in this list
+   * (use the connecting and disconnecting functions to do that)
+   * @return The inputs list.
+   */
+  public NeuralConnection[] getInputs() {
+    return m_inputList;
+  }
+
+  /**
+   * Use this to get easy access to the outputs.
+   * It is not advised to change the entries in this list
+   * (use the connecting and disconnecting functions to do that)
+   * @return The outputs list.
+   */
+  public NeuralConnection[] getOutputs() {
+    return m_outputList;
+  }
+
+  /**
+   * Use this to get easy access to the input numbers.
+   * It is not advised to change the entries in this list
+   * (use the connecting and disconnecting functions to do that)
+   * @return The input nums list.
+   */
+  public int[] getInputNums() {
+    return m_inputNums;
+  }
+
+  /**
+   * Use this to get easy access to the output numbers.
+   * It is not advised to change the entries in this list
+   * (use the connecting and disconnecting functions to do that)
+   * @return The outputs list.
+   */
+  public int[] getOutputNums() {
+    return m_outputNums;
+  }
+
+  /**
+   * @return the x coord.
+   */
+  public double getX() {
+    return m_x;
+  }
+  
+  /**
+   * @return the y coord.
+   */
+  public double getY() {
+    return m_y;
+  }
+  
+  /**
+   * @param x The new value for it's x pos.
+   */
+  public void setX(double x) {
+    m_x = x;
+  }
+  
+  /**
+   * @param y The new value for it's y pos.
+   */
+  public void setY(double y) {
+    m_y = y;
+  }
+  
+  
+  /**
+   * Call this function to determine if the point at x,y is on the unit.
+   * @param g The graphics context for font size info.
+   * @param x The x coord.
+   * @param y The y coord.
+   * @param w The width of the display.
+   * @param h The height of the display.
+   * @return True if the point is on the unit, false otherwise.
+   */
+  public boolean onUnit(Graphics g, int x, int y, int w, int h) {
+
+    int m = (int)(m_x * w);
+    int c = (int)(m_y * h);
+    if (x > m + 10 || x < m - 10 || y > c + 10 || y < c - 10) {
+      return false;
+    }
+    return true;
+
+  }
+  
+  /**
+   * Call this function to draw the node.
+   * @param g The graphics context.
+   * @param w The width of the drawing area.
+   * @param h The height of the drawing area.
+   */
+  public void drawNode(Graphics g, int w, int h) {
+    
+    if ((m_type & OUTPUT) == OUTPUT) {
+      g.setColor(Color.orange);
+    }
+    else {
+      g.setColor(Color.red);
+    }
+    g.fillOval((int)(m_x * w) - 9, (int)(m_y * h) - 9, 19, 19);
+    g.setColor(Color.gray);
+    g.fillOval((int)(m_x * w) - 5, (int)(m_y * h) - 5, 11, 11);
+  }
+
+  /**
+   * Call this function to draw the node highlighted.
+   * @param g The graphics context.
+   * @param w The width of the drawing area.
+   * @param h The height of the drawing area.
+   */
+  public void drawHighlight(Graphics g, int w, int h) {
+   
+    drawNode(g, w, h);
+    g.setColor(Color.yellow);
+    g.fillOval((int)(m_x * w) - 5, (int)(m_y * h) - 5, 11, 11);
+  }
+
+  /** 
+   * Call this function to draw the nodes input connections.
+   * @param g The graphics context.
+   * @param w The width of the drawing area.
+   * @param h The height of the drawing area.
+   */
+  public void drawInputLines(Graphics g, int w, int h) {
+
+    g.setColor(Color.black);
+    
+    int px = (int)(m_x * w);
+    int py = (int)(m_y * h);
+    for (int noa = 0; noa < m_numInputs; noa++) {
+      g.drawLine((int)(m_inputList[noa].getX() * w)
+		 , (int)(m_inputList[noa].getY() * h)
+		 , px, py);
+    }
+  }
+
+  /**
+   * Call this function to draw the nodes output connections.
+   * @param g The graphics context.
+   * @param w The width of the drawing area.
+   * @param h The height of the drawing area.
+   */
+  public void drawOutputLines(Graphics g, int w, int h) {
+    
+    g.setColor(Color.black);
+    
+    int px = (int)(m_x * w);
+    int py = (int)(m_y * h);
+    for (int noa = 0; noa < m_numOutputs; noa++) {
+      g.drawLine(px, py
+		 , (int)(m_outputList[noa].getX() * w)
+		 , (int)(m_outputList[noa].getY() * h));
+    }
+  }
+
+
+  /**
+   * This will connect the specified unit to be an input to this unit.
+   * @param i The unit.
+   * @param n It's connection number for this connection.
+   * @return True if the connection was made, false otherwise.
+   */
+  protected boolean connectInput(NeuralConnection i, int n) {
+    
+    for (int noa = 0; noa < m_numInputs; noa++) {
+      if (i == m_inputList[noa]) {
+	return false;
+      }
+    }
+    if (m_numInputs >= m_inputList.length) {
+      //then allocate more space to it.
+      allocateInputs();
+    }
+    m_inputList[m_numInputs] = i;
+    m_inputNums[m_numInputs] = n;
+    m_numInputs++;
+    return true;
+  }
+  
+  /**
+   * This will allocate more space for input connection information
+   * if the arrays for this have been filled up.
+   */
+  protected void allocateInputs() {
+    
+    NeuralConnection[] temp1 = new NeuralConnection[m_inputList.length + 15];
+    int[] temp2 = new int[m_inputNums.length + 15];
+
+    for (int noa = 0; noa < m_numInputs; noa++) {
+      temp1[noa] = m_inputList[noa];
+      temp2[noa] = m_inputNums[noa];
+    }
+    m_inputList = temp1;
+    m_inputNums = temp2;
+  }
+
+  /** 
+   * This will connect the specified unit to be an output to this unit.
+   * @param o The unit.
+   * @param n It's connection number for this connection.
+   * @return True if the connection was made, false otherwise.
+   */
+  protected boolean connectOutput(NeuralConnection o, int n) {
+    
+    for (int noa = 0; noa < m_numOutputs; noa++) {
+      if (o == m_outputList[noa]) {
+	return false;
+      }
+    }
+    if (m_numOutputs >= m_outputList.length) {
+      //then allocate more space to it.
+      allocateOutputs();
+    }
+    m_outputList[m_numOutputs] = o;
+    m_outputNums[m_numOutputs] = n;
+    m_numOutputs++;
+    return true;
+  }
+  
+  /**
+   * Allocates more space for output connection information
+   * if the arrays have been filled up.
+   */
+  protected void allocateOutputs() {
+    
+    NeuralConnection[] temp1 
+      = new NeuralConnection[m_outputList.length + 15];
+    
+    int[] temp2 = new int[m_outputNums.length + 15];
+    
+    for (int noa = 0; noa < m_numOutputs; noa++) {
+      temp1[noa] = m_outputList[noa];
+      temp2[noa] = m_outputNums[noa];
+    }
+    m_outputList = temp1;
+    m_outputNums = temp2;
+  }
+  
+  /**
+   * This will disconnect the input with the specific connection number
+   * From this node (only on this end however).
+   * @param i The unit to disconnect.
+   * @param n The connection number at the other end, -1 if all the connections
+   * to this unit should be severed.
+   * @return True if the connection was removed, false if the connection was 
+   * not found.
+   */
+  protected boolean disconnectInput(NeuralConnection i, int n) {
+    
+    int loc = -1;
+    boolean removed = false;
+    do {
+      loc = -1;
+      for (int noa = 0; noa < m_numInputs; noa++) {
+	if (i == m_inputList[noa] && (n == -1 || n == m_inputNums[noa])) {
+	  loc = noa;
+	  break;
+	}
+      }
+      
+      if (loc >= 0) {
+	for (int noa = loc+1; noa < m_numInputs; noa++) {
+	  m_inputList[noa-1] = m_inputList[noa];
+	  m_inputNums[noa-1] = m_inputNums[noa];
+	  //set the other end to have the right connection number.
+	  m_inputList[noa-1].changeOutputNum(m_inputNums[noa-1], noa-1);
+	}
+	m_numInputs--;
+	removed = true;
+      }
+    } while (n == -1 && loc != -1);
+
+    return removed;
+  }
+
+  /**
+   * This function will remove all the inputs to this unit.
+   * In doing so it will also terminate the connections at the other end.
+   */
+  public void removeAllInputs() {
+    
+    for (int noa = 0; noa < m_numInputs; noa++) {
+      //this command will simply remove any connections this node has
+      //with the other in 1 go, rather than seperately.
+      m_inputList[noa].disconnectOutput(this, -1);
+    }
+    
+    //now reset the inputs.
+    m_inputList = new NeuralConnection[0];
+    setType(getType() & (~INPUT));
+    if (getNumOutputs() == 0) {
+      setType(getType() & (~CONNECTED));
+    }
+    m_inputNums = new int[0];
+    m_numInputs = 0;
+    
+  }
+
+ 
+
+  /**
+   * Changes the connection value information for one of the connections.
+   * @param n The connection number to change.
+   * @param v The value to change it to.
+   */
+  protected void changeInputNum(int n, int v) {
+    
+    if (n >= m_numInputs || n < 0) {
+      return;
+    }
+
+    m_inputNums[n] = v;
+  }
+  
+  /**
+   * This will disconnect the output with the specific connection number
+   * From this node (only on this end however).
+   * @param o The unit to disconnect.
+   * @param n The connection number at the other end, -1 if all the connections
+   * to this unit should be severed.
+   * @return True if the connection was removed, false if the connection was
+   * not found.
+   */  
+  protected boolean disconnectOutput(NeuralConnection o, int n) {
+    
+    int loc = -1;
+    boolean removed = false;
+    do {
+      loc = -1;
+      for (int noa = 0; noa < m_numOutputs; noa++) {
+	if (o == m_outputList[noa] && (n == -1 || n == m_outputNums[noa])) {
+	  loc =noa;
+	  break;
+	}
+      }
+      
+      if (loc >= 0) {
+	for (int noa = loc+1; noa < m_numOutputs; noa++) {
+	  m_outputList[noa-1] = m_outputList[noa];
+	  m_outputNums[noa-1] = m_outputNums[noa];
+
+	  //set the other end to have the right connection number
+	  m_outputList[noa-1].changeInputNum(m_outputNums[noa-1], noa-1);
+	}
+	m_numOutputs--;
+	removed = true;
+      }
+    } while (n == -1 && loc != -1);
+    
+    return removed;
+  }
+
+  /**
+   * This function will remove all outputs to this unit.
+   * In doing so it will also terminate the connections at the other end.
+   */
+  public void removeAllOutputs() {
+    
+    for (int noa = 0; noa < m_numOutputs; noa++) {
+      //this command will simply remove any connections this node has
+      //with the other in 1 go, rather than seperately.
+      m_outputList[noa].disconnectInput(this, -1);
+    }
+    
+    //now reset the inputs.
+    m_outputList = new NeuralConnection[0];
+    m_outputNums = new int[0];
+    setType(getType() & (~OUTPUT));
+    if (getNumInputs() == 0) {
+      setType(getType() & (~CONNECTED));
+    }
+    m_numOutputs = 0;
+    
+  }
+
+  /**
+   * Changes the connection value information for one of the connections.
+   * @param n The connection number to change.
+   * @param v The value to change it to.
+   */
+  protected void changeOutputNum(int n, int v) {
+    
+    if (n >= m_numOutputs || n < 0) {
+      return;
+    }
+
+    m_outputNums[n] = v;
+  }
+  
+  /**
+   * @return The number of input connections.
+   */
+  public int getNumInputs() {
+    return m_numInputs;
+  }
+
+  /**
+   * @return The number of output connections.
+   */
+  public int getNumOutputs() {
+    return m_numOutputs;
+  }
+
+
+  /**
+   * Connects two units together.
+   * @param s The source unit.
+   * @param t The target unit.
+   * @return True if the units were connected, false otherwise.
+   */
+  public static boolean connect(NeuralConnection s, NeuralConnection t) {
+    
+    if (s == null || t == null) {
+      return false;
+    }
+    //this ensures that there is no existing connection between these 
+    //two units already. This will also cause the current weight there to be 
+    //lost
+ 
+    disconnect(s, t);
+    if (s == t) {
+      return false;
+    }
+    if ((t.getType() & PURE_INPUT) == PURE_INPUT) {
+      return false;   //target is an input node.
+    }
+    if ((s.getType() & PURE_OUTPUT) == PURE_OUTPUT) {
+      return false;   //source is an output node
+    }
+    if ((s.getType() & PURE_INPUT) == PURE_INPUT 
+	&& (t.getType() & PURE_OUTPUT) == PURE_OUTPUT) {      
+      return false;   //there is no actual working node in use
+    }
+    if ((t.getType() & PURE_OUTPUT) == PURE_OUTPUT && t.getNumInputs() > 0) {
+      return false; //more than 1 node is trying to feed a particular output
+    }
+
+    if ((t.getType() & PURE_OUTPUT) == PURE_OUTPUT &&
+	(s.getType() & OUTPUT) == OUTPUT) {
+      return false; //an output node already feeding out a final answer
+    }
+
+    if (!s.connectOutput(t, t.getNumInputs())) {
+      return false;
+    }
+    if (!t.connectInput(s, s.getNumOutputs() - 1)) {
+      
+      s.disconnectOutput(t, t.getNumInputs());
+      return false;
+
+    }
+
+    //now ammend the type.
+    if ((s.getType() & PURE_INPUT) == PURE_INPUT) {
+      t.setType(t.getType() | INPUT);
+    }
+    else if ((t.getType() & PURE_OUTPUT) == PURE_OUTPUT) {
+      s.setType(s.getType() | OUTPUT);
+    }
+    t.setType(t.getType() | CONNECTED);
+    s.setType(s.getType() | CONNECTED);
+    return true;
+  }
+
+  /**
+   * Disconnects two units.
+   * @param s The source unit.
+   * @param t The target unit.
+   * @return True if the units were disconnected, false if they weren't
+   * (probably due to there being no connection).
+   */
+  public static boolean disconnect(NeuralConnection s, NeuralConnection t) {
+    
+    if (s == null || t == null) {
+      return false;
+    }
+
+    boolean stat1 = s.disconnectOutput(t, -1);
+    boolean stat2 = t.disconnectInput(s, -1);
+    if (stat1 && stat2) {
+      if ((s.getType() & PURE_INPUT) == PURE_INPUT) {
+	t.setType(t.getType() & (~INPUT));
+      }
+      else if ((t.getType() & (PURE_OUTPUT)) == PURE_OUTPUT) {
+	s.setType(s.getType() & (~OUTPUT));
+      }
+      if (s.getNumInputs() == 0 && s.getNumOutputs() == 0) {
+	s.setType(s.getType() & (~CONNECTED));
+      }
+      if (t.getNumInputs() == 0 && t.getNumOutputs() == 0) {
+	t.setType(t.getType() & (~CONNECTED));
+      }
+    }
+    return stat1 && stat2;
+  }
+}
+
+
+
+
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralMethod.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralMethod.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralMethod.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NeuralMethod.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+
+package weka.classifiers.functions.neural;
+
+import java.io.Serializable;
+
+/**
+ * This is an interface used to create classes that can be used by the 
+ * neuralnode to perform all it's computations.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public interface NeuralMethod extends Serializable {
+  
+  /**
+   * This function calculates what the output value should be.
+   * @param node The node to calculate the value for.
+   * @return The value.
+   */
+  double outputValue(NeuralNode node);
+
+  /**
+   * This function calculates what the error value should be.
+   * @param node The node to calculate the error for.
+   * @return The error.
+   */
+  double errorValue(NeuralNode node);
+
+  /**
+   * This function will calculate what the change in weights should be
+   * and also update them.
+   * @param node The node to update the weights for.
+   * @param learn The learning rate to use.
+   * @param momentum The momentum to use.
+   */
+  void updateWeights(NeuralNode node, double learn, double momentum);
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/NeuralNode.java	(revision 29)
@@ -0,0 +1,339 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NeuralNode.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.neural;
+
+import weka.core.RevisionUtils;
+
+import java.util.Random;
+
+/**
+ * This class is used to represent a node in the neuralnet.
+ * 
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5402 $
+ */
+public class NeuralNode
+  extends NeuralConnection {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1085750607680839163L;
+    
+  /** The weights for each of the input connections, and the threshold. */
+  private double[] m_weights;
+  
+  /** The best (lowest error) weights. Only used when validation set is used */
+  private double[] m_bestWeights;
+  
+  /** The change in the weights. */
+  private double[] m_changeInWeights;
+  
+  private Random m_random;
+
+  /** Performs the operations for this node. Currently this
+   * defines that the node is either a sigmoid or a linear unit. */
+  private NeuralMethod m_methods;
+
+  /** 
+   * @param id The string name for this node (used to id this node).
+   * @param r A random number generator used to generate initial weights.
+   * @param m The methods this node should use to update.
+   */
+  public NeuralNode(String id, Random r, NeuralMethod m) {
+    super(id);
+    m_weights = new double[1];
+    m_bestWeights = new double[1];
+    m_changeInWeights = new double[1];
+    
+    m_random = r;
+    
+    m_weights[0] = m_random.nextDouble() * .1 - .05;
+    m_changeInWeights[0] = 0;
+
+    m_methods = m;
+  }
+  
+  /**
+   * Set how this node should operate (note that the neural method has no
+   * internal state, so the same object can be used by any number of nodes.
+   * @param m The new method.
+   */
+  public void setMethod(NeuralMethod m) {
+    m_methods = m;
+  } 
+
+  public NeuralMethod getMethod() {
+    return m_methods;
+  }
+
+  /**
+   * Call this to get the output value of this unit. 
+   * @param calculate True if the value should be calculated if it hasn't been
+   * already.
+   * @return The output value, or NaN, if the value has not been calculated.
+   */
+  public double outputValue(boolean calculate) {
+    
+    if (Double.isNaN(m_unitValue) && calculate) {
+      //then calculate the output value;
+      m_unitValue = m_methods.outputValue(this);
+    }
+    
+    return m_unitValue;
+  }
+
+  
+  /**
+   * Call this to get the error value of this unit.
+   * @param calculate True if the value should be calculated if it hasn't been
+   * already.
+   * @return The error value, or NaN, if the value has not been calculated.
+   */
+  public double errorValue(boolean calculate) {
+
+    if (!Double.isNaN(m_unitValue) && Double.isNaN(m_unitError) && calculate) {
+      //then calculate the error.
+      m_unitError = m_methods.errorValue(this);
+    }
+    return m_unitError;
+  }
+
+  /**
+   * Call this to reset the value and error for this unit, ready for the next
+   * run. This will also call the reset function of all units that are 
+   * connected as inputs to this one.
+   * This is also the time that the update for the listeners will be performed.
+   */
+  public void reset() {
+    
+    if (!Double.isNaN(m_unitValue) || !Double.isNaN(m_unitError)) {
+      m_unitValue = Double.NaN;
+      m_unitError = Double.NaN;
+      m_weightsUpdated = false;
+      for (int noa = 0; noa < m_numInputs; noa++) {
+	m_inputList[noa].reset();
+      }
+    }
+  }
+  
+  /**
+   * Call this to have the connection save the current
+   * weights.
+   */
+  public void saveWeights() {
+    // copy the current weights
+    System.arraycopy(m_weights, 0, m_bestWeights, 0, m_weights.length);
+    
+    // tell inputs to save weights
+    for (int i = 0; i < m_numInputs; i++) {
+      m_inputList[i].saveWeights();
+    }
+  }
+  
+  /**
+   * Call this to have the connection restore from the saved
+   * weights.
+   */
+  public void restoreWeights() {
+    // copy the saved best weights back into the weights
+    System.arraycopy(m_bestWeights, 0, m_weights, 0, m_weights.length);
+    
+    // tell inputs to restore weights
+    for (int i = 0; i < m_numInputs; i++) {
+      m_inputList[i].restoreWeights();
+    }
+  }
+
+  /**
+   * Call this to get the weight value on a particular connection.
+   * @param n The connection number to get the weight for, -1 if The threshold
+   * weight should be returned.
+   * @return The value for the specified connection or if -1 then it should 
+   * return the threshold value. If no value exists for the specified 
+   * connection, NaN will be returned.
+   */
+  public double weightValue(int n) {
+    if (n >= m_numInputs || n < -1) {
+      return Double.NaN;
+    }
+    return m_weights[n + 1];
+  }
+
+  /**
+   * call this function to get the weights array.
+   * This will also allow the weights to be updated.
+   * @return The weights array.
+   */
+  public double[] getWeights() {
+    return m_weights;
+  }
+
+  /**
+   * call this function to get the chnage in weights array.
+   * This will also allow the change in weights to be updated.
+   * @return The change in weights array.
+   */
+  public double[] getChangeInWeights() {
+    return m_changeInWeights;
+  }
+
+  /**
+   * Call this function to update the weight values at this unit.
+   * After the weights have been updated at this unit, All the
+   * input connections will then be called from this to have their
+   * weights updated.
+   * @param l The learning rate to use.
+   * @param m The momentum to use.
+   */
+  public void updateWeights(double l, double m) {
+    
+    if (!m_weightsUpdated && !Double.isNaN(m_unitError)) {
+      m_methods.updateWeights(this, l, m);
+     
+      //note that the super call to update the inputs is done here and
+      //not in the m_method updateWeights, because it is not deemed to be
+      //required to update the weights at this node (while the error and output
+      //value ao need to be recursively calculated)
+      super.updateWeights(l, m); //to call all of the inputs.
+    }
+    
+  }
+
+  /**
+   * This will connect the specified unit to be an input to this unit.
+   * @param i The unit.
+   * @param n It's connection number for this connection.
+   * @return True if the connection was made, false otherwise.
+   */
+  protected boolean connectInput(NeuralConnection i, int n) {
+    
+    //the function that this overrides can do most of the work.
+    if (!super.connectInput(i, n)) {
+      return false;
+    }
+    
+    //note that the weights are shifted 1 forward in the array so
+    //it leaves the numinputs aligned on the space the weight needs to go.
+    m_weights[m_numInputs] = m_random.nextDouble() * .1 - .05;
+    m_changeInWeights[m_numInputs] = 0;
+    
+    return true;
+  }
+
+  /**
+   * This will allocate more space for input connection information
+   * if the arrays for this have been filled up.
+   */
+  protected void allocateInputs() {
+    
+    NeuralConnection[] temp1 = new NeuralConnection[m_inputList.length + 15];
+    int[] temp2 = new int[m_inputNums.length + 15];
+    double[] temp4 = new double[m_weights.length + 15];
+    double[] temp5 = new double[m_changeInWeights.length + 15];
+    double[] temp6 = new double[m_bestWeights.length + 15];
+
+    temp4[0] = m_weights[0];
+    temp5[0] = m_changeInWeights[0];
+    temp6[0] = m_bestWeights[0];
+    for (int noa = 0; noa < m_numInputs; noa++) {
+      temp1[noa] = m_inputList[noa];
+      temp2[noa] = m_inputNums[noa];
+      temp4[noa+1] = m_weights[noa+1];
+      temp5[noa+1] = m_changeInWeights[noa+1];
+      temp6[noa+1] = m_bestWeights[noa+1];
+    }
+    
+    m_inputList = temp1;
+    m_inputNums = temp2;
+    m_weights = temp4;
+    m_changeInWeights = temp5;
+    m_bestWeights = temp6;
+  }
+
+  
+  
+
+  /**
+   * This will disconnect the input with the specific connection number
+   * From this node (only on this end however).
+   * @param i The unit to disconnect.
+   * @param n The connection number at the other end, -1 if all the connections
+   * to this unit should be severed (not the same as removeAllInputs).
+   * @return True if the connection was removed, false if the connection was 
+   * not found.
+   */
+  protected boolean disconnectInput(NeuralConnection i, int n) {
+    
+    int loc = -1;
+    boolean removed = false;
+    do {
+      loc = -1;
+      for (int noa = 0; noa < m_numInputs; noa++) {
+	if (i == m_inputList[noa] && (n == -1 || n == m_inputNums[noa])) {
+	  loc = noa;
+	  break;
+	}
+      }
+      
+      if (loc >= 0) {
+	for (int noa = loc+1; noa < m_numInputs; noa++) {
+	  m_inputList[noa-1] = m_inputList[noa];
+	  m_inputNums[noa-1] = m_inputNums[noa];
+	  
+	  m_weights[noa] = m_weights[noa+1];
+	  m_changeInWeights[noa] = m_changeInWeights[noa+1];
+	  
+	  m_inputList[noa-1].changeOutputNum(m_inputNums[noa-1], noa-1);
+	}
+	m_numInputs--;
+	removed = true;
+      }      
+    } while (n == -1 && loc != -1);
+    return removed;
+  }
+  
+  /**
+   * This function will remove all the inputs to this unit.
+   * In doing so it will also terminate the connections at the other end.
+   */
+  public void removeAllInputs() {
+    super.removeAllInputs();
+    
+    double temp1 = m_weights[0];
+    double temp2 = m_changeInWeights[0];
+
+    m_weights = new double[1];
+    m_changeInWeights = new double[1];
+
+    m_weights[0] = temp1;
+    m_changeInWeights[0] = temp2;
+    
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5402 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/SigmoidUnit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/SigmoidUnit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/neural/SigmoidUnit.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SigmoidUnit.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.neural;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * This can be used by the 
+ * neuralnode to perform all it's computations (as a sigmoid unit).
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public class SigmoidUnit
+  implements NeuralMethod, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5162958458177475652L;
+  
+  /**
+   * This function calculates what the output value should be.
+   * @param node The node to calculate the value for.
+   * @return The value.
+   */
+  public double outputValue(NeuralNode node) {
+    double[] weights = node.getWeights();
+    NeuralConnection[] inputs = node.getInputs();
+    double value = weights[0];
+    for (int noa = 0; noa < node.getNumInputs(); noa++) {
+      
+      value += inputs[noa].outputValue(true) 
+	* weights[noa+1];
+    }
+     
+    //this I got from the Neural Network faq to combat overflow
+    //pretty simple solution really :)
+    if (value < -45) {
+      value = 0;
+    }
+    else if (value > 45) {
+      value = 1;
+    }
+    else {
+      value = 1 / (1 + Math.exp(-value));
+    }  
+    return value;
+  }
+  
+  /**
+   * This function calculates what the error value should be.
+   * @param node The node to calculate the error for.
+   * @return The error.
+   */
+  public double errorValue(NeuralNode node) {
+    //then calculate the error.
+    
+    NeuralConnection[] outputs = node.getOutputs();
+    int[] oNums = node.getOutputNums();
+    double error = 0;
+    
+    for (int noa = 0; noa < node.getNumOutputs(); noa++) {
+      error += outputs[noa].errorValue(true) 
+	* outputs[noa].weightValue(oNums[noa]);
+    }
+    double value = node.outputValue(false);
+    error *= value * (1 - value);
+    
+    return error;
+  }
+
+  /**
+   * This function will calculate what the change in weights should be
+   * and also update them.
+   * @param node The node to update the weights for.
+   * @param learn The learning rate to use.
+   * @param momentum The momentum to use.
+   */
+  public void updateWeights(NeuralNode node, double learn, double momentum) {
+
+    NeuralConnection[] inputs = node.getInputs();
+    double[] cWeights = node.getChangeInWeights();
+    double[] weights = node.getWeights();
+    double learnTimesError = 0;
+    learnTimesError = learn * node.errorValue(false);
+    double c = learnTimesError + momentum * cWeights[0];
+    weights[0] += c;
+    cWeights[0] = c;
+ 
+    int stopValue = node.getNumInputs() + 1;
+    for (int noa = 1; noa < stopValue; noa++) {
+      
+      c = learnTimesError * inputs[noa-1].outputValue(false);
+      c += momentum * cWeights[noa];
+      
+      weights[noa] += c;
+      cWeights[noa] = c; 
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/ChisqMixture.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/ChisqMixture.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/ChisqMixture.java	(revision 29)
@@ -0,0 +1,487 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    ChisqMixture.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.pace;
+
+import weka.core.RevisionUtils;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.Maths;
+
+import java.util.Random;
+
+/**
+ * Class for manipulating chi-square mixture distributions. <p/>
+ *
+ * For more information see: <p/>
+ * 
+ <!-- technical-plaintext-start -->
+ * Wang, Y (2000). A new approach to fitting linear models in high dimensional spaces. Hamilton, New Zealand.<br/>
+ * <br/>
+ * Wang, Y., Witten, I. H.: Modeling for optimal probability prediction. In: Proceedings of the Nineteenth International Conference in Machine Learning, Sydney, Australia, 650-657, 2002.
+ <!-- technical-plaintext-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Wang2000,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Wang, Y},
+ *    school = {Department of Computer Science, University of Waikato},
+ *    title = {A new approach to fitting linear models in high dimensional spaces},
+ *    year = {2000}
+ * }
+ * 
+ * &#64;inproceedings{Wang2002,
+ *    address = {Sydney, Australia},
+ *    author = {Wang, Y. and Witten, I. H.},
+ *    booktitle = {Proceedings of the Nineteenth International Conference in Machine Learning},
+ *    pages = {650-657},
+ *    title = {Modeling for optimal probability prediction},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class ChisqMixture 
+  extends MixtureDistribution {
+  
+  /** the separating threshold value */
+  protected double separatingThreshold = 0.05; 
+
+  /** the triming thresholding */
+  protected double trimingThreshold = 0.5;
+
+  protected double supportThreshold = 0.5;
+
+  protected int maxNumSupportPoints = 200; // for computational reason
+
+  protected int fittingIntervalLength = 3;
+    
+  protected double fittingIntervalThreshold = 0.5;
+
+  /** Contructs an empty ChisqMixture
+   */
+  public ChisqMixture() {}
+
+  /** 
+   * Gets the separating threshold value. This value is used by the method
+   * separatable
+   * 
+   * @return the separating threshold
+   */
+  public double getSeparatingThreshold() {
+    return separatingThreshold;
+  }
+  
+  /**
+   * Sets the separating threshold value 
+   * 
+   * @param t the threshold value 
+   */
+  public void setSeparatingThreshold( double t ) {
+    separatingThreshold = t;
+  }
+
+  /** 
+   * Gets the triming thresholding value. This value is usef by the method trim.
+   * 
+   * @return the triming threshold
+   */
+  public double getTrimingThreshold() {
+    return trimingThreshold;
+  }
+
+  /** 
+   * Sets the triming thresholding value.
+   * 
+   * @param t the triming threshold
+   */
+  public void setTrimingThreshold( double t ){
+    trimingThreshold = t;
+  }
+
+  /** 
+   *  Return true if a value can be considered for mixture estimation
+   *  separately from the data indexed between i0 and i1 
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param i0 the index of the first element in the group
+   *  @param i1 the index of the last element in the group
+   *  @param x the value
+   *  @return true if the value can be considered
+   */
+  public boolean separable( DoubleVector data, int i0, int i1, double x ) {
+
+    DoubleVector dataSqrt = data.sqrt();
+    double xh = Math.sqrt( x );
+
+    NormalMixture m = new NormalMixture();
+    m.setSeparatingThreshold( separatingThreshold );
+    return m.separable( dataSqrt, i0, i1, xh );
+  }
+
+  /** 
+   *  Contructs the set of support points for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param ne the number of extra data that are suppposedly discarded
+   *  earlier and not passed into here
+   *  @return the set of support points
+   */
+  public DoubleVector  supportPoints( DoubleVector data, int ne ) {
+
+    DoubleVector sp = new DoubleVector();
+    sp.setCapacity( data.size() + 1 );
+
+    if( data.get(0) < supportThreshold || ne != 0 ) 
+      sp.addElement( 0 );
+    for( int i = 0; i < data.size(); i++ ) 
+      if( data.get( i ) > supportThreshold ) 
+	sp.addElement( data.get(i) );
+	
+    // The following will be fixed later???
+    if( sp.size() > maxNumSupportPoints ) 
+      throw new IllegalArgumentException( "Too many support points. " );
+
+    return sp;
+  }
+    
+  /** 
+   *  Contructs the set of fitting intervals for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @return the set of fitting intervals
+   */
+  public PaceMatrix  fittingIntervals( DoubleVector data ) {
+
+    PaceMatrix a = new PaceMatrix( data.size() * 2, 2 );
+    DoubleVector v = data.sqrt();
+    int count = 0;
+    double left, right;
+    for( int i = 0; i < data.size(); i++ ) {
+      left = v.get(i) - fittingIntervalLength; 
+      if( left < fittingIntervalThreshold ) left = 0;
+      left = left * left;
+      right = data.get(i);
+      if( right < fittingIntervalThreshold ) 
+	right = fittingIntervalThreshold;
+      a.set( count, 0, left );
+      a.set( count, 1, right );
+      count++;
+    }
+    for( int i = 0; i < data.size(); i++ ) {
+      left = data.get(i);
+      if( left < fittingIntervalThreshold ) left = 0;
+      right = v.get(i) + fittingIntervalThreshold;
+      right = right * right;
+      a.set( count, 0, left );
+      a.set( count, 1, right );
+      count++;
+    }
+    a.setRowDimension( count );
+	
+    return a;
+  }
+    
+  /** 
+   *  Contructs the probability matrix for mixture estimation, given a set
+   *  of support points and a set of intervals.
+   *  
+   *  @param s  the set of support points
+   *  @param intervals the intervals
+   *  @return the probability matrix
+   */
+  public PaceMatrix  probabilityMatrix(DoubleVector s, PaceMatrix intervals) {
+    
+    int ns = s.size();
+    int nr = intervals.getRowDimension();
+    PaceMatrix p = new PaceMatrix(nr, ns);
+	
+    for( int i = 0; i < nr; i++ ) {
+      for( int j = 0; j < ns; j++ ) {
+	p.set( i, j,
+	       Maths.pchisq( intervals.get(i, 1), s.get(j) ) - 
+	       Maths.pchisq( intervals.get(i, 0), s.get(j) ) );
+      }
+    }
+	
+    return p;
+  }
+    
+
+  /** 
+   *  Returns the pace6 estimate of a single value.
+   *  
+   *  @param x the value
+   *  @return the pace6 estimate
+   */
+  public double  pace6 ( double x ) { 
+    
+    if( x > 100 ) return x; // pratical consideration. will modify later
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+    DoubleVector mean = points.sqrt();
+	
+    DoubleVector d = Maths.dchisqLog( x, points );
+    d.minusEquals( d.max() );
+    d = d.map("java.lang.Math", "exp").timesEquals( values );
+    double atilde = mean.innerProduct( d ) / d.sum();
+    return atilde * atilde;
+  }
+
+  /** 
+   *  Returns the pace6 estimate of a vector.
+   *  
+   *  @param x the vector
+   *  @return the pace6 estimate
+   */
+  public DoubleVector pace6( DoubleVector x ) {
+
+    DoubleVector pred = new DoubleVector( x.size() );
+    for(int i = 0; i < x.size(); i++ ) 
+      pred.set(i, pace6(x.get(i)) );
+    trim( pred );
+    return pred;
+  }
+
+  /** 
+   *  Returns the pace2 estimate of a vector.
+   *  
+   *  @param x the vector
+   *  @return the pace2 estimate
+   */
+  public DoubleVector  pace2( DoubleVector x ) {
+    
+    DoubleVector chf = new DoubleVector( x.size() );
+    for(int i = 0; i < x.size(); i++ ) chf.set( i, hf( x.get(i) ) );
+
+    chf.cumulateInPlace();
+
+    int index = chf.indexOfMax();
+
+    DoubleVector copy = x.copy();
+    if( index < x.size()-1 ) copy.set( index + 1, x.size()-1, 0 );
+    trim( copy );
+    return copy;
+  }
+
+  /** 
+   *  Returns the pace4 estimate of a vector.
+   *  
+   *  @param x the vector
+   *  @return the pace4 estimate
+   */
+  public DoubleVector  pace4( DoubleVector x ) {
+    
+    DoubleVector h = h( x );
+    DoubleVector copy = x.copy();
+    for( int i = 0; i < x.size(); i++ )
+      if( h.get(i) <= 0 ) copy.set(i, 0);
+    trim( copy );
+    return copy;
+  }
+
+  /** 
+   * Trims the small values of the estaimte
+   * 
+   * @param x the estimate vector
+   */
+  public void trim( DoubleVector x ) {
+    
+    for(int i = 0; i < x.size(); i++ ) {
+      if( x.get(i) <= trimingThreshold ) x.set(i, 0);
+    }
+  }
+    
+  /**
+   *  Computes the value of h(x) / f(x) given the mixture. The
+   *  implementation avoided overflow.
+   *  
+   *  @param AHat the value
+   *  @return the value of h(x) / f(x)
+   */
+  public double hf( double AHat ) {
+    
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+
+    double x = Math.sqrt( AHat );
+    DoubleVector mean = points.sqrt();
+    DoubleVector d1 = Maths.dnormLog( x, mean, 1 );
+    double d1max = d1.max();
+    d1.minusEquals( d1max );
+    DoubleVector d2 = Maths.dnormLog( -x, mean, 1 );
+    d2.minusEquals( d1max );
+
+    d1 = d1.map("java.lang.Math", "exp");
+    d1.timesEquals( values );  
+    d2 = d2.map("java.lang.Math", "exp");
+    d2.timesEquals( values );  
+
+    return ( ( points.minus(x/2)).innerProduct( d1 ) - 
+	     ( points.plus(x/2)).innerProduct( d2 ) ) 
+    / (d1.sum() + d2.sum());
+  }
+    
+  /**
+   *  Computes the value of h(x) given the mixture.
+   *  
+   *  @param AHat the value
+   *  @return the value of h(x)
+   */
+  public double h( double AHat ) {
+    
+    if( AHat == 0.0 ) return 0.0;
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues();
+	
+    double aHat = Math.sqrt( AHat );
+    DoubleVector aStar = points.sqrt();
+    DoubleVector d1 = Maths.dnorm( aHat, aStar, 1 ).timesEquals( values );
+    DoubleVector d2 = Maths.dnorm( -aHat, aStar, 1 ).timesEquals( values );
+
+    return points.minus(aHat/2).innerProduct( d1 ) - 
+           points.plus(aHat/2).innerProduct( d2 );
+  }
+    
+  /**
+   *  Computes the value of h(x) given the mixture, where x is a vector.
+   *  
+   *  @param AHat the vector
+   *  @return the value of h(x)
+   */
+  public DoubleVector h( DoubleVector AHat ) {
+    
+    DoubleVector h = new DoubleVector( AHat.size() );
+    for( int i = 0; i < AHat.size(); i++ ) 
+      h.set( i, h( AHat.get(i) ) );
+    return h;
+  }
+    
+  /**
+   *  Computes the value of f(x) given the mixture.
+   *  
+   *  @param x the value
+   *  @return the value of f(x)
+   */
+  public double f( double x ) {
+    
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+
+    return Maths.dchisq(x, points).timesEquals(values).sum();  
+  }
+    
+  /**
+   *  Computes the value of f(x) given the mixture, where x is a vector.
+   *  
+   *  @param x the vector
+   *  @return the value of f(x)
+   */
+  public DoubleVector f( DoubleVector x ) {
+    
+    DoubleVector f = new DoubleVector( x.size() );
+    for( int i = 0; i < x.size(); i++ ) 
+      f.set( i, h( f.get(i) ) );
+    return f;
+  }
+    
+  /** 
+   * Converts to a string
+   * 
+   * @return a string representation
+   */
+  public String  toString() {
+    return mixingDistribution.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+    
+  /** 
+   * Method to test this class 
+   * 
+   * @param args the commandline arguments
+   */
+  public static void  main(String args[]) {
+    
+    int n1 = 50;
+    int n2 = 50;
+    double ncp1 = 0;
+    double ncp2 = 10; 
+    double mu1 = Math.sqrt( ncp1 );
+    double mu2 = Math.sqrt( ncp2 );
+    DoubleVector a = Maths.rnorm( n1, mu1, 1, new Random() );
+    a = a.cat( Maths.rnorm(n2, mu2, 1, new Random()) );
+    DoubleVector aNormal = a;
+    a = a.square();
+    a.sort();
+	
+    DoubleVector means = (new DoubleVector( n1, mu1 )).cat(new DoubleVector(n2, mu2));
+	
+    System.out.println("==========================================================");
+    System.out.println("This is to test the estimation of the mixing\n" +
+		       "distribution of the mixture of non-central Chi-square\n" + 
+		       "distributions. The example mixture used is of the form: \n\n" + 
+		       "   0.5 * Chi^2_1(ncp1) + 0.5 * Chi^2_1(ncp2)\n" );
+
+    System.out.println("It also tests the PACE estimators. Quadratic losses of the\n" +
+		       "estimators are given, measuring their performance.");
+    System.out.println("==========================================================");
+    System.out.println( "ncp1 = " + ncp1 + " ncp2 = " + ncp2 +"\n" );
+
+    System.out.println( a.size() + " observations are: \n\n" + a );
+
+    System.out.println( "\nQuadratic loss of the raw data (i.e., the MLE) = " + 
+			aNormal.sum2( means ) );
+    System.out.println("==========================================================");
+	
+    // find the mixing distribution
+    ChisqMixture d = new ChisqMixture();
+    d.fit( a, NNMMethod ); 
+    System.out.println( "The estimated mixing distribution is\n" + d );  
+	
+    DoubleVector pred = d.pace2( a.rev() ).rev();
+    System.out.println( "\nThe PACE2 Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + 
+			pred.sqrt().times(aNormal.sign()).sum2( means ) );
+    
+    pred = d.pace4( a );
+    System.out.println( "\nThe PACE4 Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + 
+			pred.sqrt().times(aNormal.sign()).sum2( means ) );
+
+    pred = d.pace6( a );
+    System.out.println( "\nThe PACE6 Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + 
+			pred.sqrt().times(aNormal.sign()).sum2( means ) );
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/DiscreteFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/DiscreteFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/DiscreteFunction.java	(revision 29)
@@ -0,0 +1,307 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    ChisqMixture.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.pace;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.FlexibleDecimalFormat;
+import weka.core.matrix.IntVector;
+
+
+/** Class for handling discrete functions. <p>
+ * 
+ * A discrete function here is one that takes non-zero values over a finite
+ * set of points. <p>
+ * 
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $ */
+
+public class  DiscreteFunction
+  implements RevisionHandler {
+    
+  protected DoubleVector  points;
+  protected DoubleVector  values;
+
+  /** Constructs an empty discrete function */
+  public DiscreteFunction() 
+  {
+    this(null, null);
+  }
+    
+  /** Constructs a discrete function with the point values provides and the
+   *  function values are all 1/n. 
+   * @param p the point values
+   */
+  public DiscreteFunction( DoubleVector p ) 
+  {
+    this( p, null );
+  }
+    
+  /** Constructs a discrete function with both the point values and
+   *  function values provided.
+   * @param p the point values
+   * @param v the function values */
+  public DiscreteFunction( DoubleVector p, DoubleVector v ) 
+  {
+    points = p;
+    values = v;
+    formalize();
+  }
+    
+  private DiscreteFunction  formalize() 
+  {
+    if( points == null ) points = new DoubleVector();
+    if( values == null ) values = new DoubleVector();
+	
+    if( points.isEmpty() ) {
+      if( ! values.isEmpty() )
+	throw new IllegalArgumentException("sizes not match");
+    }
+    else {
+      int n = points.size();
+      if( values.isEmpty() ) {
+	values = new DoubleVector( n, 1./n );
+      }
+      else {
+	if( values.size() != n )
+	  throw new IllegalArgumentException("sizes not match");
+      }
+    }
+    return this;
+  }
+    
+  /** 
+   * Normalizes the function values with L1-norm.
+   */
+  public DiscreteFunction  normalize() 
+  {
+    if ( ! values.isEmpty() ) {
+      double s = values.sum();
+      if( s != 0.0 && s != 1.0 ) values.timesEquals( 1. / s ); 
+    }
+    return this;
+  }
+  
+  /** 
+   * Sorts the point values of the discrete function.
+   */
+  public void  sort() 
+  {
+    IntVector index = points.sortWithIndex();
+    values = values.subvector( index );
+  }
+  
+  /**
+   * Clones the discrete function
+   */
+  public Object  clone() 
+  {
+    DiscreteFunction d = new DiscreteFunction();
+    d.points = (DoubleVector) points.clone();
+    d.values = (DoubleVector) values.clone();
+    return d;
+  }
+  
+  /**
+   * Makes each individual point value unique 
+   */
+  public DiscreteFunction  unique() 
+  {
+    int count = 0;
+    
+    if( size() < 2 ) return this;
+    for(int i = 1; i <= size() - 1; i++ ) {
+      if( points.get( count ) != points.get( i ) ) {
+	count++;
+	points.set( count, points.get( i ) );
+	values.set( count, values.get( i ) );
+      } 
+      else {
+	values.set( count, values.get(count) + values.get(i) );
+      } 
+    }
+    points = (DoubleVector) points.subvector(0, count);
+    values = (DoubleVector) values.subvector(0, count);
+    return this;
+  }
+
+  /** 
+   * Returns the size of the point set.
+   */
+  public int  size() 
+  {
+    if( points == null ) return 0;
+    return points.size();
+  }
+  
+  /**
+   * Gets a particular point value
+   * @param i the index
+   */
+  public double  getPointValue( int i ) 
+  {
+    return points.get(i);
+  }
+  
+  /**
+   * Gets a particular function value
+   * @param i the index
+   */
+  public double  getFunctionValue( int i ) 
+  {
+    return values.get(i);
+  }
+    
+  /**
+   * Sets a particular point value
+   * @param i the index
+   */
+  public void  setPointValue( int i, double p )
+  {
+    points.set(i, p);
+  }
+    
+  /**
+   * Sets a particular function value
+   * @param i the index
+   */
+  public void  setFunctionValue( int i, double v )
+  {
+    values.set(i, v);
+  }
+    
+  /**
+   * Gets all point values
+   */
+  protected DoubleVector  getPointValues() 
+  {
+    return points;
+  }
+    
+  /**
+   * Gets all function values
+   */
+  protected DoubleVector  getFunctionValues() 
+  {
+    return values;
+  }
+  
+  /**
+   * Returns true if it is empty.
+   */
+  public boolean  isEmpty() 
+  {
+    if( size() == 0 ) return true;
+    return false;
+  }
+  
+  //    public void  addPoint( double x, double y ) {
+  //	  points.addPoint( x );
+  //	  values.addPoint( y );
+  //    }
+  
+  /** 
+   * Returns the combined of two discrete functions
+   * @param d the second discrete function
+   * @return the combined discrte function
+   */
+  public DiscreteFunction  plus( DiscreteFunction d ) 
+  {
+    return ((DiscreteFunction) clone()).plusEquals( d );
+  }
+  
+  /** 
+   * Returns the combined of two discrete functions. The first function is
+   * replaced with the new one.
+   * @param d the second discrete function
+   * @return the combined discrte function */
+  public DiscreteFunction  plusEquals( DiscreteFunction d ) 
+  {
+    points = points.cat( d.points );
+    values = values.cat( d.values );
+    return this;
+  }
+  
+  /**
+   * All function values are multiplied by a double
+   * @param x the multiplier
+   */
+  public DiscreteFunction  timesEquals( double x ) 
+  {
+    values.timesEquals( x );
+    return this;
+  }
+
+  /**
+   * Converts the discrete function to string.
+   */
+  public String  toString() 
+  {
+    StringBuffer text = new StringBuffer();
+    FlexibleDecimalFormat nf1 = new FlexibleDecimalFormat( 5 );
+    nf1.grouping( true ); 
+    FlexibleDecimalFormat nf2 = new FlexibleDecimalFormat( 5 );
+    nf2.grouping( true );
+    for(int i = 0; i < size(); i++) {
+      nf1.update( points.get(i) );
+      nf2.update( values.get(i) );
+    }
+
+    text.append("\t" + nf1.formatString("Points") + 
+		"\t" + nf2.formatString("Values") + "\n\n");
+    for(int i = 0; i <= size() - 1; i++) {
+      text.append( "\t" + nf1.format( points.get(i) ) + "\t" + 
+		   nf2.format( values.get(i) ) + "\n" );
+    }
+	
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+
+  public static void main( String args[] )
+  {
+	
+    double points[] = {2,1,2,3,3};
+    double values[] = {3,2,4,1,3};
+    DiscreteFunction d = new DiscreteFunction( new DoubleVector( points ), 
+					       new DoubleVector( values ));
+    System.out.println( d );
+    d.normalize();
+    System.out.println( "d (after normalize) = \n" + d );
+    points[1] = 10;
+    System.out.println( "d (after setting [1]) = \n" + d);
+    d.sort();
+    System.out.println( "d (after sorting) = \n" + d);
+    d.unique();
+    System.out.println( "d (after unique) = \n" + d );
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/MixtureDistribution.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/MixtureDistribution.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/MixtureDistribution.java	(revision 29)
@@ -0,0 +1,273 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    MixtureDistribution.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.pace;
+
+import weka.core.RevisionHandler;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.IntVector;
+
+/**
+ * Abtract class for manipulating mixture distributions. <p>
+ *
+ * REFERENCES <p>
+ * 
+ * Wang, Y. (2000). "A new approach to fitting linear models in high
+ * dimensional spaces." PhD Thesis. Department of Computer Science,
+ * University of Waikato, New Zealand. <p>
+ * 
+ * Wang, Y. and Witten, I. H. (2002). "Modeling for optimal probability
+ * prediction." Proceedings of ICML'2002. Sydney. <p>
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ */
+
+public abstract class MixtureDistribution
+  implements TechnicalInformationHandler, RevisionHandler {
+  
+  protected DiscreteFunction mixingDistribution;
+
+  /** The nonnegative-measure-based method */
+  public static final int NNMMethod = 1; 
+    
+  /** The probability-measure-based method */
+  public static final int PMMethod = 2;
+
+  // The CDF-based method
+  // public static final int CDFMethod = 3;
+    
+  // The method based on the Kolmogrov and von Mises measure
+  // public static final int ModifiedCDFMethod = 4; 
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "Wang, Y");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.TITLE, "A new approach to fitting linear models in high dimensional spaces");
+    result.setValue(Field.SCHOOL, "Department of Computer Science, University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Wang, Y. and Witten, I. H.");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.TITLE, "Modeling for optimal probability prediction");
+    additional.setValue(Field.BOOKTITLE, "Proceedings of the Nineteenth International Conference in Machine Learning");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.PAGES, "650-657");
+    additional.setValue(Field.ADDRESS, "Sydney, Australia");
+    
+    return result;
+  }
+    
+  /** 
+   * Gets the mixing distribution
+   * 
+   * @return the mixing distribution
+   */
+  public DiscreteFunction getMixingDistribution() {
+    return mixingDistribution;
+  }
+
+  /** Sets the mixing distribution
+   *  @param d the mixing distribution
+   */
+  public void  setMixingDistribution( DiscreteFunction d ) {
+    mixingDistribution = d;
+  }
+
+  /** Fits the mixture (or mixing) distribution to the data. The default
+   *  method is the nonnegative-measure-based method.
+   * @param data the data, supposedly generated from the mixture model */
+  public void fit( DoubleVector data ) {
+    fit( data, NNMMethod );
+  }
+
+  /** Fits the mixture (or mixing) distribution to the data.
+   *  @param data the data supposedly generated from the mixture 
+   *  @param method the method to be used. Refer to the static final
+   *  variables of this class. */
+  public void fit( DoubleVector data, int method ) {
+    DoubleVector data2 = (DoubleVector) data.clone();
+    if( data2.unsorted() ) data2.sort();
+
+    int n = data2.size();
+    int start = 0;
+    DoubleVector subset;
+    DiscreteFunction d = new DiscreteFunction();
+    for( int i = 0; i < n-1; i++ ) {
+      if( separable( data2, start, i, data2.get(i+1) ) &&
+	  separable( data2, i+1, n-1, data2.get(i) ) ) {
+	subset = (DoubleVector) data2.subvector( start, i );
+	d.plusEquals( fitForSingleCluster( subset, method ).
+		      timesEquals(i - start + 1) );
+	start = i + 1;
+      }
+    }
+    subset = (DoubleVector) data2.subvector( start, n-1 );
+    d.plusEquals( fitForSingleCluster( subset, method ).
+		  timesEquals(n - start) ); 
+    d.sort();
+    d.normalize();
+    mixingDistribution = d;
+  }
+    
+  /** 
+   *  Fits the mixture (or mixing) distribution to the data. The data is
+   *  not pre-clustered for computational efficiency.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param method the method to be used. Refer to the static final
+   *  variables of this class.
+   *  @return the generated distribution
+   */
+  public DiscreteFunction fitForSingleCluster( DoubleVector data, 
+					       int method ) {
+    
+    if( data.size() < 2 ) return new DiscreteFunction( data );
+    DoubleVector sp = supportPoints( data, 0 );
+    PaceMatrix fi = fittingIntervals( data );
+    PaceMatrix pm = probabilityMatrix( sp, fi );
+    PaceMatrix epm = new 
+      PaceMatrix( empiricalProbability( data, fi ).
+		  timesEquals( 1. / data.size() ) );
+    
+    IntVector pvt = (IntVector) IntVector.seq(0, sp.size()-1);
+    DoubleVector weights;
+    
+    switch( method ) {
+    case NNMMethod: 
+      weights = pm.nnls( epm, pvt );
+      break;
+    case PMMethod:
+      weights = pm.nnlse1( epm, pvt );
+      break;
+    default: 
+      throw new IllegalArgumentException("unknown method");
+    }
+    
+    DoubleVector sp2 = new DoubleVector( pvt.size() );
+    for( int i = 0; i < sp2.size(); i++ ){
+      sp2.set( i, sp.get(pvt.get(i)) );
+    }
+    
+    DiscreteFunction d = new DiscreteFunction( sp2, weights );
+    d.sort();
+    d.normalize();
+    return d;
+  }
+    
+  /** 
+   *  Return true if a value can be considered for mixture estimatino
+   *  separately from the data indexed between i0 and i1 
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param i0 the index of the first element in the group
+   *  @param i1 the index of the last element in the group
+   *  @param x the value
+   *  @return true if a value can be considered
+   */
+  public abstract boolean separable( DoubleVector data, 
+				     int i0, int i1, double x );
+    
+  /** 
+   *  Contructs the set of support points for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param ne the number of extra data that are suppposedly discarded
+   *  earlier and not passed into here
+   *  @return the set of support points
+   */
+  public abstract DoubleVector  supportPoints( DoubleVector data, int ne );
+    
+  /** 
+   *  Contructs the set of fitting intervals for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @return the set of fitting intervals
+   */
+  public abstract PaceMatrix  fittingIntervals( DoubleVector data );
+  
+  /** 
+   *  Contructs the probability matrix for mixture estimation, given a set
+   *  of support points and a set of intervals.
+   *  
+   *  @param s  the set of support points
+   *  @param intervals the intervals
+   *  @return the probability matrix
+   */
+  public abstract PaceMatrix  probabilityMatrix( DoubleVector s, 
+						 PaceMatrix intervals );
+    
+  /** 
+   *  Computes the empirical probabilities of the data over a set of
+   *  intervals.
+   *  
+   *  @param data the data
+   *  @param intervals the intervals 
+   *  @return the empirical probabilities
+   */
+  public PaceMatrix  empiricalProbability( DoubleVector data, 
+					   PaceMatrix intervals )
+  {
+    int n = data.size();
+    int k = intervals.getRowDimension();
+    PaceMatrix epm = new PaceMatrix( k, 1, 0 );
+    
+    double point;
+    for( int j = 0; j < n; j ++ ) {
+      for(int i = 0; i < k; i++ ) {
+	point = 0.0;
+	if( intervals.get(i, 0) == data.get(j) || 
+	    intervals.get(i, 1) == data.get(j) ) point = 0.5;
+	else if( intervals.get(i, 0) < data.get(j) && 
+		 intervals.get(i, 1) > data.get(j) ) point = 1.0;
+	epm.setPlus( i, 0, point);
+      }
+    }
+    return epm;
+  }
+  
+  /** 
+   * Converts to a string
+   * 
+   * @return a string representation
+   */
+  public String  toString() 
+  {
+    return "The mixing distribution:\n" + mixingDistribution.toString();
+  }
+    
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/NormalMixture.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/NormalMixture.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/NormalMixture.java	(revision 29)
@@ -0,0 +1,417 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    NormalMixture.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.pace;
+
+import java.util.Random;
+
+import weka.core.RevisionUtils;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.Maths;
+
+/**
+ * Class for manipulating normal mixture distributions. <p>
+ *
+ * For more information see: <p/>
+ * 
+ <!-- technical-plaintext-start -->
+ * Wang, Y (2000). A new approach to fitting linear models in high dimensional spaces. Hamilton, New Zealand.<br/>
+ * <br/>
+ * Wang, Y., Witten, I. H.: Modeling for optimal probability prediction. In: Proceedings of the Nineteenth International Conference in Machine Learning, Sydney, Australia, 650-657, 2002.
+ <!-- technical-plaintext-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Wang2000,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Wang, Y},
+ *    school = {Department of Computer Science, University of Waikato},
+ *    title = {A new approach to fitting linear models in high dimensional spaces},
+ *    year = {2000}
+ * }
+ * 
+ * &#64;inproceedings{Wang2002,
+ *    address = {Sydney, Australia},
+ *    author = {Wang, Y. and Witten, I. H.},
+ *    booktitle = {Proceedings of the Nineteenth International Conference in Machine Learning},
+ *    pages = {650-657},
+ *    title = {Modeling for optimal probability prediction},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class  NormalMixture 
+  extends MixtureDistribution {
+  
+  /** the separating threshold */
+  protected double separatingThreshold = 0.05;
+
+  /** the triming thresholding */
+  protected double trimingThreshold = 0.7;
+
+  protected double fittingIntervalLength = 3;
+
+  /** 
+   * Contructs an empty NormalMixture
+   */
+  public NormalMixture() {}
+
+  /** 
+   * Gets the separating threshold value. This value is used by the method 
+   * separatable
+   * 
+   * @return the separating threshold 
+   */
+  public double getSeparatingThreshold(){
+    return separatingThreshold;
+  }
+
+  /** 
+   *  Sets the separating threshold value 
+   *  
+   *  @param t the threshold value 
+   */
+  public void setSeparatingThreshold( double t ){
+    separatingThreshold = t;
+  }
+
+  /** 
+   * Gets the triming thresholding value. This value is usef by the method 
+   * trim.
+   * 
+   * @return the triming thresholding 
+   */
+  public double getTrimingThreshold(){ 
+    return trimingThreshold; 
+  }
+
+  /** 
+   * Sets the triming thresholding value.
+   * 
+   * @param t the triming thresholding 
+   */
+  public void setTrimingThreshold( double t ){
+    trimingThreshold = t;
+  }
+
+  /** 
+   *  Return true if a value can be considered for mixture estimatino
+   *  separately from the data indexed between i0 and i1 
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param i0 the index of the first element in the group
+   *  @param i1 the index of the last element in the group
+   *  @param x the value
+   *  @return true if the value can be considered
+   */
+  public boolean separable( DoubleVector data, int i0, int i1, double x ) {
+    double p = 0;
+    for( int i = i0; i <= i1; i++ ) {
+      p += Maths.pnorm( - Math.abs(x - data.get(i)) );
+    }
+    if( p < separatingThreshold ) return true;
+    else return false;
+  }
+
+  /** 
+   *  Contructs the set of support points for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @param ne the number of extra data that are suppposedly discarded
+   *  earlier and not passed into here
+   *  @return the set of support points
+   */
+  public DoubleVector  supportPoints( DoubleVector data, int ne ) {
+    if( data.size() < 2 )
+      throw new IllegalArgumentException("data size < 2");
+	
+    return data.copy();
+  }
+    
+  /** 
+   *  Contructs the set of fitting intervals for mixture estimation.
+   *  
+   *  @param data the data supposedly generated from the mixture 
+   *  @return the set of fitting intervals
+   */
+  public PaceMatrix  fittingIntervals( DoubleVector data ) {
+    DoubleVector left = data.cat( data.minus( fittingIntervalLength ) );
+    DoubleVector right = data.plus( fittingIntervalLength ).cat( data );
+	
+    PaceMatrix a = new PaceMatrix(left.size(), 2);
+	
+    a.setMatrix(0, left.size()-1, 0, left);
+    a.setMatrix(0, right.size()-1, 1, right);
+	
+    return a;
+  }
+    
+  /** 
+   *  Contructs the probability matrix for mixture estimation, given a set
+   *  of support points and a set of intervals.
+   *  
+   *  @param s  the set of support points
+   *  @param intervals the intervals
+   *  @return the probability matrix
+   */
+  public PaceMatrix  probabilityMatrix( DoubleVector s, 
+					PaceMatrix intervals ) {
+    
+    int ns = s.size();
+    int nr = intervals.getRowDimension();
+    PaceMatrix p = new PaceMatrix(nr, ns);
+	
+    for( int i = 0; i < nr; i++ ) {
+      for( int j = 0; j < ns; j++ ) {
+	p.set( i, j,
+	       Maths.pnorm( intervals.get(i, 1), s.get(j), 1 ) - 
+	       Maths.pnorm( intervals.get(i, 0), s.get(j), 1 ) );
+      }
+    }
+	
+    return p;
+  }
+    
+  /** 
+   * Returns the empirical Bayes estimate of a single value.
+   * 
+   * @param x the value
+   * @return the empirical Bayes estimate
+   */
+  public double  empiricalBayesEstimate ( double x ) { 
+    if( Math.abs(x) > 10 ) return x; // pratical consideration; modify later
+    DoubleVector d = 
+    Maths.dnormLog( x, mixingDistribution.getPointValues(), 1 );
+    
+    d.minusEquals( d.max() );
+    d = d.map("java.lang.Math", "exp");
+    d.timesEquals( mixingDistribution.getFunctionValues() );
+    return mixingDistribution.getPointValues().innerProduct( d ) / d.sum();
+  }
+
+  /** 
+   * Returns the empirical Bayes estimate of a vector.
+   * 
+   * @param x the vector
+   * @return the empirical Bayes estimate
+   */
+  public DoubleVector empiricalBayesEstimate( DoubleVector x ) {
+    DoubleVector pred = new DoubleVector( x.size() );
+    for(int i = 0; i < x.size(); i++ ) 
+      pred.set(i, empiricalBayesEstimate(x.get(i)) );
+    trim( pred );
+    return pred;
+  }
+
+  /** 
+   * Returns the optimal nested model estimate of a vector.
+   * 
+   * @param x the vector
+   * @return the optimal nested model estimate 
+   */
+  public DoubleVector  nestedEstimate( DoubleVector x ) {
+    
+    DoubleVector chf = new DoubleVector( x.size() );
+    for(int i = 0; i < x.size(); i++ ) chf.set( i, hf( x.get(i) ) );
+    chf.cumulateInPlace();
+    int index = chf.indexOfMax();
+    DoubleVector copy = x.copy();
+    if( index < x.size()-1 ) copy.set( index + 1, x.size()-1, 0 );
+    trim( copy );
+    return copy;
+  }
+  
+  /** 
+   * Returns the estimate of optimal subset selection.
+   * 
+   * @param x the vector
+   * @return the estimate of optimal subset selection
+   */
+  public DoubleVector  subsetEstimate( DoubleVector x ) {
+
+    DoubleVector h = h( x );
+    DoubleVector copy = x.copy();
+    for( int i = 0; i < x.size(); i++ )
+      if( h.get(i) <= 0 ) copy.set(i, 0);
+    trim( copy );
+    return copy;
+  }
+  
+  /** 
+   * Trims the small values of the estaimte
+   * 
+   * @param x the estimate vector
+   */
+  public void trim( DoubleVector x ) {
+    for(int i = 0; i < x.size(); i++ ) {
+      if( Math.abs(x.get(i)) <= trimingThreshold ) x.set(i, 0);
+    }
+  }
+  
+  /**
+   *  Computes the value of h(x) / f(x) given the mixture. The
+   *  implementation avoided overflow.
+   *  
+   *  @param x the value
+   *  @return the value of h(x) / f(x)
+   */
+  public double hf( double x ) {
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+
+    DoubleVector d = Maths.dnormLog( x, points, 1 );
+    d.minusEquals( d.max() );
+
+    d = (DoubleVector) d.map("java.lang.Math", "exp");
+    d.timesEquals( values );  
+
+    return ((DoubleVector) points.times(2*x).minusEquals(x*x))
+    .innerProduct( d ) / d.sum();
+  }
+    
+  /**
+   *  Computes the value of h(x) given the mixture. 
+   *  
+   *  @param x the value
+   *  @return the value of h(x)
+   */
+  public double h( double x ) {
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+    DoubleVector d = (DoubleVector) Maths.dnorm( x, points, 1 ).timesEquals( values );  
+    return ((DoubleVector) points.times(2*x).minusEquals(x*x))
+    .innerProduct( d );
+  }
+    
+  /**
+   *  Computes the value of h(x) given the mixture, where x is a vector.
+   *  
+   *  @param x the vector
+   *  @return the value of h(x)
+   */
+  public DoubleVector h( DoubleVector x ) {
+    DoubleVector h = new DoubleVector( x.size() );
+    for( int i = 0; i < x.size(); i++ ) 
+      h.set( i, h( x.get(i) ) );
+    return h;
+  }
+    
+  /**
+   *  Computes the value of f(x) given the mixture.
+   *  
+   *  @param x the value
+   *  @return the value of f(x)
+   */
+  public double f( double x ) {
+    DoubleVector points = mixingDistribution.getPointValues();
+    DoubleVector values = mixingDistribution.getFunctionValues(); 
+    return Maths.dchisq( x, points ).timesEquals( values ).sum();
+  }
+    
+  /**
+   *  Computes the value of f(x) given the mixture, where x is a vector.
+   *  
+   *  @param x the vector
+   *  @return the value of f(x)
+   */
+  public DoubleVector f( DoubleVector x ) {
+    DoubleVector f = new DoubleVector( x.size() );
+    for( int i = 0; i < x.size(); i++ ) 
+      f.set( i, h( f.get(i) ) );
+    return f;
+  }
+    
+  /** 
+   * Converts to a string
+   * 
+   * @return a string representation
+   */
+  public String  toString() {
+    return mixingDistribution.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+    
+  /** 
+   * Method to test this class 
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void  main(String args[]) {
+    int n1 = 50;
+    int n2 = 50;
+    double mu1 = 0;
+    double mu2 = 5; 
+    DoubleVector a = Maths.rnorm( n1, mu1, 1, new Random() );
+    a = a.cat( Maths.rnorm( n2, mu2, 1, new Random() ) );
+    DoubleVector means = (new DoubleVector( n1, mu1 )).cat(new DoubleVector(n2, mu2));
+
+    System.out.println("==========================================================");
+    System.out.println("This is to test the estimation of the mixing\n" +
+	    "distribution of the mixture of unit variance normal\n" + 
+	    "distributions. The example mixture used is of the form: \n\n" + 
+	    "   0.5 * N(mu1, 1) + 0.5 * N(mu2, 1)\n" );
+
+    System.out.println("It also tests three estimators: the subset\n" +
+	    "selector, the nested model selector, and the empirical Bayes\n" +
+	    "estimator. Quadratic losses of the estimators are given, \n" +
+	    "and are taken as the measure of their performance.");
+    System.out.println("==========================================================");
+    System.out.println( "mu1 = " + mu1 + " mu2 = " + mu2 +"\n" );
+
+    System.out.println( a.size() + " observations are: \n\n" + a );
+
+    System.out.println( "\nQuadratic loss of the raw data (i.e., the MLE) = " + 
+	     a.sum2( means ) );
+    System.out.println("==========================================================");
+
+    // find the mixing distribution
+    NormalMixture d = new NormalMixture();
+    d.fit( a, NNMMethod ); 
+    System.out.println( "The estimated mixing distribution is:\n" + d );
+	
+    DoubleVector pred = d.nestedEstimate( a.rev() ).rev();
+    System.out.println( "\nThe Nested Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + pred.sum2( means ) );
+
+    pred = d.subsetEstimate( a );
+    System.out.println( "\nThe Subset Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + pred.sum2( means ) );
+
+    pred = d.empiricalBayesEstimate( a );
+    System.out.println( "\nThe Empirical Bayes Estimate = \n" + pred );
+    System.out.println( "Quadratic loss = " + pred.sum2( means ) );
+	
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/PaceMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/PaceMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/pace/PaceMatrix.java	(revision 29)
@@ -0,0 +1,1167 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    PaceMatrix.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.pace;
+
+import weka.core.RevisionUtils;
+import weka.core.matrix.DoubleVector;
+import weka.core.matrix.FlexibleDecimalFormat;
+import weka.core.matrix.IntVector;
+import weka.core.matrix.Matrix;
+import weka.core.matrix.Maths;
+
+import java.util.Random;
+import java.text.DecimalFormat;
+
+/**
+ * Class for matrix manipulation used for pace regression. <p>
+ *
+ * REFERENCES <p>
+ * 
+ * Wang, Y. (2000). "A new approach to fitting linear models in high
+ * dimensional spaces." PhD Thesis. Department of Computer Science,
+ * University of Waikato, New Zealand. <p>
+ * 
+ * Wang, Y. and Witten, I. H. (2002). "Modeling for optimal probability
+ * prediction." Proceedings of ICML'2002. Sydney. <p>
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public class PaceMatrix 
+  extends Matrix {
+  
+  /** for serialization */
+  static final long serialVersionUID = 2699925616857843973L;
+    
+  /* ------------------------
+     Constructors
+     * ------------------------ */
+  
+  /** Construct an m-by-n PACE matrix of zeros. 
+      @param m    Number of rows.
+      @param n    Number of colums.
+  */
+  public PaceMatrix( int m, int n ) {
+    super( m, n );
+  }
+
+  /** Construct an m-by-n constant PACE matrix.
+      @param m    Number of rows.
+      @param n    Number of colums.
+      @param s    Fill the matrix with this scalar value.
+  */
+  public PaceMatrix( int m, int n, double s ) {
+    super( m, n, s );
+  }
+    
+  /** Construct a PACE matrix from a 2-D array.
+      @param A    Two-dimensional array of doubles.
+      @throws  IllegalArgumentException All rows must have the same length
+  */
+  public PaceMatrix( double[][] A ) {
+    super( A );
+  }
+
+  /** Construct a PACE matrix quickly without checking arguments.
+      @param A    Two-dimensional array of doubles.
+      @param m    Number of rows.
+      @param n    Number of colums.
+  */
+  public PaceMatrix( double[][] A, int m, int n ) {
+    super( A, m, n );
+  }
+    
+  /** Construct a PaceMatrix from a one-dimensional packed array
+      @param vals One-dimensional array of doubles, packed by columns (ala Fortran).
+      @param m    Number of rows.
+      @throws  IllegalArgumentException Array length must be a multiple of m.
+  */
+  public PaceMatrix( double vals[], int m ) {
+    super( vals, m );
+  }
+    
+  /** Construct a PaceMatrix with a single column from a DoubleVector 
+      @param v    DoubleVector
+  */
+  public PaceMatrix( DoubleVector v ) {
+    this( v.size(), 1 );
+    setMatrix( 0, v.size()-1, 0, v );
+  }
+    
+  /** Construct a PaceMatrix from a Matrix 
+      @param X    Matrix 
+  */
+  public PaceMatrix( Matrix X ) {
+    super( X.getRowDimension(), X.getColumnDimension() );
+    A = X.getArray();
+  }
+    
+  /* ------------------------
+     Public Methods
+     * ------------------------ */
+
+  /** Set the row dimenion of the matrix
+   *  @param rowDimension the row dimension
+   */
+  public void setRowDimension( int rowDimension ) 
+  {
+    m = rowDimension;
+  }
+
+  /** Set the column dimenion of the matrix
+   *  @param columnDimension the column dimension
+   */
+  public void setColumnDimension( int columnDimension ) 
+  {
+    n = columnDimension;
+  }
+
+  /** 
+   * Clone the PaceMatrix object.
+   * 
+   * @return the clone
+   */
+  public Object clone () {
+    PaceMatrix X = new PaceMatrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+	C[i][j] = A[i][j];
+      }
+    }
+    return (Object) X;
+  }
+    
+  /** Add a value to an element and reset the element
+   *  @param i    the row number of the element
+   *  @param j    the column number of the element
+   *  @param s    the double value to be added with
+   */
+  public void setPlus(int i, int j, double s) {
+    A[i][j] += s;
+  }
+
+  /** Multiply a value with an element and reset the element
+   *  @param i    the row number of the element
+   *  @param j    the column number of the element
+   *  @param s    the double value to be multiplied with
+   */
+  public void setTimes(int i, int j, double s) {
+    A[i][j] *= s;
+  }
+
+  /** Set the submatrix A[i0:i1][j0:j1] with a same value 
+   *  @param i0 the index of the first element of the column
+   *  @param i1 the index of the last element of the column
+   *  @param j0 the index of the first column
+   *  @param j1 the index of the last column
+   *  @param s the value to be set to
+   */
+  public void setMatrix( int i0, int i1, int j0, int j1, double s ) {
+    try {
+      for( int i = i0; i <= i1; i++ ) {
+	for( int j = j0; j <= j1; j++ ) {
+	  A[i][j] = s;
+	}
+      }
+    } catch( ArrayIndexOutOfBoundsException e ) {
+      throw new ArrayIndexOutOfBoundsException( "Index out of bounds" );
+    }
+  }
+  
+  /** Set the submatrix A[i0:i1][j] with the values stored in a
+   *  DoubleVector
+   *  @param i0 the index of the first element of the column
+   *  @param i1 the index of the last element of the column
+   *  @param j  the index of the column
+   *  @param v the vector that stores the values*/
+  public void setMatrix( int i0, int i1, int j, DoubleVector v ) {
+    for( int i = i0; i <= i1; i++ ) {
+      A[i][j] = v.get(i-i0);
+    }
+  }
+
+  /** Set the whole matrix from a 1-D array 
+   *  @param v    1-D array of doubles
+   *  @param columnFirst   Whether to fill the column first or the row.
+   *  @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix ( double[] v, boolean columnFirst ) {
+    try {
+      if( v.length != m * n ) 
+	throw new IllegalArgumentException("sizes not match.");
+      int i, j, count = 0;
+      if( columnFirst ) {
+	for( i = 0; i < m; i++ ) {
+	  for( j = 0; j < n; j++ ) {
+	    A[i][j] = v[count];
+	    count ++;
+	  }
+	}
+      }
+      else {
+	for( j = 0; j < n; j++ ) {
+	  for( i = 0; i < m; i++ ){
+	    A[i][j] = v[count];
+	    count ++;
+	  }
+	}
+      }
+
+    } catch( ArrayIndexOutOfBoundsException e ) {
+      throw new ArrayIndexOutOfBoundsException( "Submatrix indices" );
+    }
+  }
+
+  /** Returns the maximum absolute value of all elements 
+      @return the maximum value
+  */
+  public double maxAbs () {
+    double ma = Math.abs(A[0][0]);
+    for (int j = 0; j < n; j++) {
+      for (int i = 0; i < m; i++) {
+	ma = Math.max(ma, Math.abs(A[i][j]));
+      }
+    }
+    return ma;
+  }
+
+  /** Returns the maximum absolute value of some elements of a column,
+      that is, the elements of A[i0:i1][j].
+      @param i0 the index of the first element of the column
+      @param i1 the index of the last element of the column
+      @param j  the index of the column
+      @return the maximum value */
+  public double maxAbs ( int i0, int i1, int j ) {
+    double m = Math.abs(A[i0][j]);
+    for (int i = i0+1; i <= i1; i++) {
+      m = Math.max(m, Math.abs(A[i][j]));
+    }
+    return m;
+  }
+
+  /** Returns the minimum absolute value of some elements of a column,
+      that is, the elements of A[i0:i1][j].
+      @param i0 the index of the first element of the column
+      @param i1 the index of the last element of the column
+      @param column the index of the column
+      @return the minimum value 
+  */
+  public double minAbs ( int i0, int i1, int column ) {
+    double m = Math.abs(A[i0][column]);
+    for (int i = i0+1; i <= i1; i++) {
+      m = Math.min(m, Math.abs(A[i][column]));
+    }
+    return m;
+  }
+    
+  /** Check if the matrix is empty
+   *   @return true if the matrix is empty
+   */
+  public boolean  isEmpty(){
+    if(m == 0 || n == 0) return true;
+    if(A == null) return true;
+    return false;
+  }
+    
+  /** Return a DoubleVector that stores a column of the matrix 
+   *  @param j the index of the column
+   *  @return the column
+   */
+  public DoubleVector  getColumn( int j ) {
+    DoubleVector v = new DoubleVector( m );
+    double [] a = v.getArray();
+    for(int i = 0; i < m; i++)
+      a[i] = A[i][j];
+    return v;
+  }
+
+  /** Return a DoubleVector that stores some elements of a column of the
+   *  matrix 
+   *  @param i0 the index of the first element of the column
+   *  @param i1 the index of the last element of the column
+   *  @param j  the index of the column
+   *  @return the DoubleVector
+   */
+  public DoubleVector  getColumn( int i0, int i1, int j ) {
+    DoubleVector v = new DoubleVector( i1-i0+1 );
+    double [] a = v.getArray();
+    int count = 0;
+    for( int i = i0; i <= i1; i++ ) {
+      a[count] = A[i][j];
+      count++;
+    }
+    return v;
+  }
+  
+  
+  /** Multiplication between a row (or part of a row) of the first matrix
+   *  and a column (or part or a column) of the second matrix.
+   *  @param i the index of the row in the first matrix
+   *  @param j0 the index of the first column in the first matrix
+   *  @param j1 the index of the last column in the first matrix
+   *  @param B the second matrix
+   *  @param l the index of the column in the second matrix
+   *  @return the result of the multiplication
+   */
+  public double  times( int i, int j0, int j1, PaceMatrix B, int l ) {
+    double s = 0.0;
+    for(int j = j0; j <= j1; j++ ) {
+      s += A[i][j] * B.A[j][l];
+    }
+    return s;
+  }
+  
+  /** Decimal format for converting a matrix into a string
+   *  @return the default decimal format
+   */
+  protected DecimalFormat []  format() {
+    return format(0, m-1, 0, n-1, 7, false );
+  }
+  
+  /** Decimal format for converting a matrix into a string
+   *  @param digits the number of digits
+   *  @return the decimal format
+   */
+  protected DecimalFormat []  format( int digits ) {
+    return format(0, m-1, 0, n-1, digits, false);
+  }
+
+  /** Decimal format for converting a matrix into a string
+   *  @param digits the number of digits
+   *  @param trailing
+   *  @return the decimal format
+   */
+  protected DecimalFormat []  format( int digits, boolean trailing ) {
+    return format(0, m-1, 0, n-1, digits, trailing);
+  }
+  
+  /** Decimal format for converting a matrix into a string
+   *  @param i0
+   *  @param i1
+   *  @param j
+   *  @param digits the number of digits
+   *  @param trailing
+   *  @return the decimal format
+   */
+  protected DecimalFormat  format(int i0, int i1, int j, int digits, 
+				  boolean trailing) {
+    FlexibleDecimalFormat df = new FlexibleDecimalFormat(digits, trailing);
+    df.grouping( true );
+    for(int i = i0; i <= i1; i ++ )
+      df.update( A[i][j] );
+    return df;
+  }
+  
+  /** Decimal format for converting a matrix into a string
+   *  @param i0
+   *  @param i1
+   *  @param j0
+   *  @param j1
+   *  @param trailing
+   *  @param digits the number of digits
+   *  @return the decimal format
+   */
+  protected DecimalFormat []  format(int i0, int i1, int j0, int j1, 
+				     int digits, boolean trailing) {
+    DecimalFormat [] f = new DecimalFormat[j1-j0+1];
+    for( int j = j0; j <= j1; j++ ) {
+      f[j] = format(i0, i1, j, digits, trailing);
+    }
+    return f;
+  }
+  
+  /** 
+   * Converts matrix to string
+   * 
+   * @return the matrix as string
+   */ 
+  public String  toString() {
+    return toString( 5, false );
+  }
+  
+  /** 
+   * Converts matrix to string
+   * 
+   * @param digits number of digits after decimal point
+   * @param trailing true if trailing zeros are padded
+   * @return the matrix as string
+   */ 
+  public String  toString( int digits, boolean trailing ) {
+    
+    if( isEmpty() ) return "null matrix";
+    
+    StringBuffer text = new StringBuffer();
+    DecimalFormat [] nf = format( digits, trailing );
+    int numCols = 0;
+    int count = 0;
+    int width = 80;
+    int lenNumber;
+    
+    int [] nCols = new int[n];
+    int nk=0;
+    for( int j = 0; j < n; j++ ) {
+      lenNumber = nf[j].format( A[0][j]).length(); 
+      if( count + 1 + lenNumber > width -1 ) {
+	nCols[nk++]  = numCols;
+	count = 0;
+	numCols = 0;
+      }
+      count += 1 + lenNumber;
+      ++numCols;
+    }
+    nCols[nk] = numCols;
+    
+    nk = 0;
+    for( int k = 0; k < n; ) {
+      for( int i = 0; i < m; i++ ) {
+	for( int j = k; j < k + nCols[nk]; j++)
+	  text.append( " " + nf[j].format( A[i][j]) );
+	text.append("\n");
+      }
+      k += nCols[nk];
+      ++nk;
+      text.append("\n");
+    }
+    
+    return text.toString();
+  }
+  
+  /** Squared sum of a column or row in a matrix
+   * @param j the index of the column or row
+   * @param i0 the index of the first element
+   * @param i1 the index of the last element
+   * @param col if true, sum over a column; otherwise, over a row
+   * @return the squared sum
+   */
+  public double sum2( int j, int i0, int i1, boolean col ) {
+    double s2 = 0;
+    if( col ) {   // column 
+      for( int i = i0; i <= i1; i++ ) 
+	s2 += A[i][j] * A[i][j];
+    }
+    else {
+      for( int i = i0; i <= i1; i++ ) 
+	s2 += A[j][i] * A[j][i];
+    }
+    return s2;
+  }
+  
+  /** Squared sum of columns or rows of a matrix
+   * @param col if true, sum over columns; otherwise, over rows
+   * @return the squared sum
+   */
+  public double[] sum2( boolean col ) {
+    int l = col ? n : m;
+    int p = col ? m : n;
+    double [] s2 = new double[l];
+    for( int i = 0; i < l; i++ ) 
+      s2[i] = sum2( i, 0, p-1, col );
+    return s2;
+  }
+
+  /** Constructs single Householder transformation for a column
+   *
+   @param j    the index of the column
+   @param k    the index of the row
+   @return     d and q 
+  */
+  public double [] h1( int j, int k ) {
+    double dq[] = new double[2];
+    double s2 = sum2(j, k, m-1, true);  
+    dq[0] = A[k][j] >= 0 ? - Math.sqrt( s2 ) : Math.sqrt( s2 );
+    A[k][j] -= dq[0];
+    dq[1] = A[k][j] * dq[0];
+    return dq;
+  }
+  
+  /** Performs single Householder transformation on one column of a matrix
+   *
+   @param j    the index of the column 
+   @param k    the index of the row
+   @param q    q = - u'u/2; must be negative
+   @param b    the matrix to be transformed
+   @param l    the column of the matrix b
+  */
+  public void h2( int j, int k, double q, PaceMatrix b, int l ) {
+    double s = 0, alpha;
+    for( int i = k; i < m; i++ )
+      s += A[i][j] * b.A[i][l];
+    alpha = s / q;
+    for( int i = k; i < m; i++ )
+      b.A[i][l] += alpha * A[i][j];
+  }
+  
+  /** Constructs the Givens rotation
+   *  @param a 
+   *  @param b
+   *  @return a double array that stores the cosine and sine values
+   */
+  public double []  g1( double a, double b ) {
+    double cs[] = new double[2];
+    double r = Maths.hypot(a, b);
+    if( r == 0.0 ) {
+      cs[0] = 1;
+      cs[1] = 0;
+    }
+    else {
+      cs[0] = a / r;
+      cs[1] = b / r;
+    }
+    return cs;
+  }
+  
+  /** Performs the Givens rotation
+   * @param cs a array storing the cosine and sine values
+   * @param i0 the index of the row of the first element
+   * @param i1 the index of the row of the second element
+   * @param j the index of the column
+   */
+  public void  g2( double cs[], int i0, int i1, int j ){
+    double w =   cs[0] * A[i0][j] + cs[1] * A[i1][j];
+    A[i1][j] = - cs[1] * A[i0][j] + cs[0] * A[i1][j];
+    A[i0][j] = w;
+  }
+  
+  /** Forward ordering of columns in terms of response explanation.  On
+   *  input, matrices A and b are already QR-transformed. The indices of
+   *  transformed columns are stored in the pivoting vector.
+   *  
+   *@param b     the PaceMatrix b
+   *@param pvt   the pivoting vector
+   *@param k0    the first k0 columns (in pvt) of A are not to be changed
+   **/
+  public void forward( PaceMatrix b, IntVector pvt, int k0 ) {
+    for( int j = k0; j < Math.min(pvt.size(), m); j++ ) {
+      steplsqr( b, pvt, j, mostExplainingColumn(b, pvt, j), true );
+    }
+  }
+
+  /** Returns the index of the column that has the largest (squared)
+   *  response, when each of columns pvt[ks:] is moved to become the
+   *  ks-th column. On input, A and b are both QR-transformed.
+   *  
+   * @param b    response
+   * @param pvt  pivoting index of A
+   * @param ks   columns pvt[ks:] of A are to be tested 
+   * @return the index of the column
+   */
+  public int  mostExplainingColumn( PaceMatrix b, IntVector pvt, int ks ) {
+    double val;
+    int [] p = pvt.getArray();
+    double ma = columnResponseExplanation( b, pvt, ks, ks );
+    int jma = ks;
+    for( int i = ks+1; i < pvt.size(); i++ ) {
+      val = columnResponseExplanation( b, pvt, i, ks );
+      if( val > ma ) {
+	ma = val;
+	jma = i;
+      }
+    }
+    return jma;
+  }
+  
+  /** Backward ordering of columns in terms of response explanation.  On
+   *  input, matrices A and b are already QR-transformed. The indices of
+   *  transformed columns are stored in the pivoting vector.
+   * 
+   *  A and b must have the same number of rows, being the (pseudo-)rank. 
+   *  
+   * @param b     PaceMatrix b
+   * @param pvt   pivoting vector
+   * @param ks    number of QR-transformed columns; psuedo-rank of A 
+   * @param k0    first k0 columns in pvt[] are not to be ordered.
+   */
+  public void backward( PaceMatrix b, IntVector pvt, int ks, int k0 ) {
+    for( int j = ks; j > k0; j-- ) {
+      steplsqr( b, pvt, j, leastExplainingColumn(b, pvt, j, k0), false );
+    }
+  }
+
+  /** Returns the index of the column that has the smallest (squared)
+   *  response, when the column is moved to become the (ks-1)-th
+   *  column. On input, A and b are both QR-transformed.
+   *  
+   * @param b    response
+   * @param pvt  pivoting index of A
+   * @param ks   psudo-rank of A
+   * @param k0   A[][pvt[0:(k0-1)]] are excluded from the testing.
+   * @return the index of the column
+   */
+  public int  leastExplainingColumn( PaceMatrix b, IntVector pvt, int ks, 
+				     int k0 ) {
+    double val;
+    int [] p = pvt.getArray();
+    double mi = columnResponseExplanation( b, pvt, ks-1, ks );
+    int jmi = ks-1;
+    for( int i = k0; i < ks - 1; i++ ) {
+      val = columnResponseExplanation( b, pvt, i, ks );
+      if( val <= mi ) {
+	mi = val;
+	jmi = i;
+      }
+    }
+    return jmi;
+  }
+  
+  /** Returns the squared ks-th response value if the j-th column becomes
+   *  the ks-th after orthogonal transformation.  A[][pvt[ks:j]] (or
+   *  A[][pvt[j:ks]], if ks > j) and b[] are already QR-transformed
+   *  on input and will remain unchanged on output.
+   *
+   *  More generally, it returns the inner product of the corresponding
+   *  row vector of the response PaceMatrix. (To be implemented.)
+   *
+   *@param b    PaceMatrix b
+   *@param pvt  pivoting vector
+   *@param j    the column A[pvt[j]][] is to be moved
+   *@param ks   the target column A[pvt[ks]][]
+   *@return     the squared response value */
+  public double  columnResponseExplanation( PaceMatrix b, IntVector pvt,
+					    int j, int ks ) {
+    /*  Implementation: 
+     *
+     *  If j == ks - 1, returns the squared ks-th response directly.
+     *
+     *  If j > ks -1, returns the ks-th response after
+     *  Householder-transforming the j-th column and the response.
+     *
+     *  If j < ks - 1, returns the ks-th response after a sequence of
+     *  Givens rotations starting from the j-th row. */
+
+    int k, l;
+    double [] xxx = new double[n];
+    int [] p = pvt.getArray();
+    double val;
+    
+    if( j == ks -1 ) val = b.A[j][0];
+    else if( j > ks - 1 ) {
+      int jm = Math.min(n-1, j);
+      DoubleVector u = getColumn(ks,jm,p[j]);
+      DoubleVector v = b.getColumn(ks,jm,0);
+      val = v.innerProduct(u) / u.norm2();
+    }
+    else {                 // ks > j
+      for( k = j+1; k < ks; k++ ) // make a copy of A[j][]
+	xxx[k] = A[j][p[k]];
+      val = b.A[j][0];
+      double [] cs;
+      for( k = j+1; k < ks; k++ ) {
+	cs = g1( xxx[k], A[k][p[k]] );
+	for( l = k+1; l < ks; l++ ) 
+	  xxx[l] = - cs[1] * xxx[l] + cs[0] * A[k][p[l]];
+	val = - cs[1] * val + cs[0] * b.A[k][0];
+      }
+    }
+    return val * val;  // or inner product in later implementation???
+  }
+
+  /** 
+   * QR transformation for a least squares problem<br/>
+   *            A x = b<br/>
+   * implicitly both A and b are transformed. pvt.size() is the psuedo-rank of 
+   * A.
+   *  
+   * @param b    PaceMatrix b
+   * @param pvt  pivoting vector
+   * @param k0   the first k0 columns of A (indexed by pvt) are pre-chosen. 
+   *            (But subject to rank examination.) 
+   * 
+   *            For example, the constant term may be reserved, in which
+   *            case k0 = 1.
+   **/
+  public void  lsqr( PaceMatrix b, IntVector pvt, int k0 ) {
+    final double TINY = 1e-15;
+    int [] p = pvt.getArray();
+    int ks = 0;  // psuedo-rank
+    for(int j = 0; j < k0; j++ )   // k0 pre-chosen columns
+      if( sum2(p[j],ks,m-1,true) > TINY ){ // large diagonal element 
+	steplsqr(b, pvt, ks, j, true);
+	ks++;
+      }
+      else {                     // collinear column
+	pvt.shiftToEnd( j );
+	pvt.setSize(pvt.size()-1);
+	k0--;
+	j--;
+      }
+	
+    // initial QR transformation
+    for(int j = k0; j < Math.min( pvt.size(), m ); j++ ) {
+      if( sum2(p[j], ks, m-1, true) > TINY ) { 
+	steplsqr(b, pvt, ks, j, true);
+	ks++;
+      }
+      else {                     // collinear column
+	pvt.shiftToEnd( j );
+	pvt.setSize(pvt.size()-1);
+	j--;
+      }
+    }
+	
+    b.m = m = ks;           // reset number of rows
+    pvt.setSize( ks );
+  }
+    
+  /** QR transformation for a least squares problem <br/>
+   *            A x = b <br/>
+   * implicitly both A and b are transformed. pvt.size() is the psuedo-rank of A.
+   *  
+   * @param b    PaceMatrix b
+   * @param pvt  pivoting vector
+   * @param k0   the first k0 columns of A (indexed by pvt) are pre-chosen. 
+   *            (But subject to rank examination.) 
+   * 
+   *            For example, the constant term may be reserved, in which
+   *            case k0 = 1.
+   **/
+  public void  lsqrSelection( PaceMatrix b, IntVector pvt, int k0 ) {
+    int numObs = m;         // number of instances
+    int numXs = pvt.size();
+
+    lsqr( b, pvt, k0 );
+
+    if( numXs > 200 || numXs > numObs ) { // too many columns.  
+      forward(b, pvt, k0);
+    }
+    backward(b, pvt, pvt.size(), k0);
+  }
+    
+  /** 
+   * Sets all diagonal elements to be positive (or nonnegative) without
+   * changing the least squares solution 
+   * @param Y the response
+   * @param pvt the pivoted column index
+   */
+  public void positiveDiagonal( PaceMatrix Y, IntVector pvt ) {
+     
+    int [] p = pvt.getArray();
+    for( int i = 0; i < pvt.size(); i++ ) {
+      if( A[i][p[i]] < 0.0 ) {
+	for( int j = i; j < pvt.size(); j++ ) 
+	  A[i][p[j]] = - A[i][p[j]];
+	Y.A[i][0] = - Y.A[i][0];
+      }
+    }
+  }
+
+  /** Stepwise least squares QR-decomposition of the problem
+   *	          A x = b
+   @param b    PaceMatrix b
+   @param pvt  pivoting vector
+   @param ks   number of transformed columns
+   @param j    pvt[j], the column to adjoin or delete
+   @param adjoin   to adjoin if true; otherwise, to delete */
+  public void  steplsqr( PaceMatrix b, IntVector pvt, int ks, int j, 
+			 boolean adjoin ) {
+    final int kp = pvt.size(); // number of columns under consideration
+    int [] p = pvt.getArray();
+	
+    if( adjoin ) {     // adjoining 
+      int pj = p[j];
+      pvt.swap( ks, j );
+      double dq[] = h1( pj, ks );
+      int pk;
+      for( int k = ks+1; k < kp; k++ ){
+	pk = p[k];
+	h2( pj, ks, dq[1], this, pk);
+      }
+      h2( pj, ks, dq[1], b, 0 ); // for matrix. ???
+      A[ks][pj] = dq[0];
+      for( int k = ks+1; k < m; k++ )
+	A[k][pj] = 0;
+    }
+    else {          // removing 
+      int pj = p[j];
+      for( int i = j; i < ks-1; i++ ) 
+	p[i] = p[i+1];
+      p[ks-1] = pj;
+      double [] cs;
+      for( int i = j; i < ks-1; i++ ){
+	cs = g1( A[i][p[i]], A[i+1][p[i]] );
+	for( int l = i; l < kp; l++ ) 
+	  g2( cs, i, i+1, p[l] );
+	for( int l = 0; l < b.n; l++ )
+	  b.g2( cs, i, i+1, l );
+      }
+    }
+  }
+    
+  /** Solves upper-triangular equation <br/>
+   *   	R x = b <br/>
+   *  On output, the solution is stored in b
+   *  @param b the response
+   *  @param pvt the pivoting vector
+   *  @param kp the number of the first columns involved 
+   */
+  public void  rsolve( PaceMatrix b, IntVector pvt, int kp) {
+    if(kp == 0) b.m = 0;
+    int i, j, k;
+    int [] p = pvt.getArray();
+    double s;
+    double [][] ba = b.getArray();
+    for( k = 0; k < b.n; k++ ) {
+      ba[kp-1][k] /= A[kp-1][p[kp-1]];
+      for( i = kp - 2; i >= 0; i-- ){
+	s = 0;
+	for( j = i + 1; j < kp; j++ )
+	  s += A[i][p[j]] * ba[j][k];
+	ba[i][k] -= s;
+	ba[i][k] /= A[i][p[i]];
+      }
+    } 
+    b.m = kp;
+  }
+    
+  /** Returns a new matrix which binds two matrices together with rows. 
+   *  @param b  the second matrix
+   *  @return the combined matrix
+   */
+  public PaceMatrix  rbind( PaceMatrix b ){
+    if( n != b.n ) 
+      throw new IllegalArgumentException("unequal numbers of rows.");
+    PaceMatrix c = new PaceMatrix( m + b.m, n );
+    c.setMatrix( 0, m - 1, 0, n - 1, this );
+    c.setMatrix( m, m + b.m - 1, 0, n - 1, b );
+    return c;
+  }
+
+  /** Returns a new matrix which binds two matrices with columns.
+   *  @param b the second matrix 
+   *  @return the combined matrix
+   */
+  public PaceMatrix  cbind( PaceMatrix b ) {
+    if( m != b.m ) 
+      throw new IllegalArgumentException("unequal numbers of rows: " + 
+					 m + " and " + b.m);
+    PaceMatrix c = new PaceMatrix(m, n + b.n);
+    c.setMatrix( 0, m - 1, 0, n - 1, this );
+    c.setMatrix( 0, m - 1, n, n + b.n - 1, b );
+    return c;
+  }
+
+  /** Solves the nonnegative linear squares problem. That is, <p>
+   *   <center>   min || A x - b||, subject to x >= 0.  </center> <p>
+   * 
+   *  For algorithm, refer to P161, Chapter 23 of C. L. Lawson and
+   *  R. J. Hanson (1974).  "Solving Least Squares
+   *  Problems". Prentice-Hall.
+   * 	@param b the response
+   *  @param pvt vector storing pivoting column indices
+   *	@return solution */
+  public DoubleVector nnls( PaceMatrix b, IntVector pvt ) {
+    int j, t, counter = 0, jm = -1, n = pvt.size();
+    double ma, max, alpha, wj;
+    int [] p = pvt.getArray();
+    DoubleVector x = new DoubleVector( n );
+    double [] xA = x.getArray();
+    PaceMatrix z = new PaceMatrix(n, 1);
+    PaceMatrix bt;
+	
+    // step 1 
+    int kp = 0; // #variables in the positive set P
+    while ( true ) {         // step 2 
+      if( ++counter > 3*n )  // should never happen
+	throw new RuntimeException("Does not converge");
+      t = -1;
+      max = 0.0;
+      bt = new PaceMatrix( b.transpose() );
+      for( j = kp; j <= n-1; j++ ) {   // W = A' (b - A x) 
+	wj = bt.times( 0, kp, m-1, this, p[j] );
+	if( wj > max ) {        // step 4
+	  max = wj;
+	  t = j;
+	}
+      }
+	    
+      // step 3 
+      if ( t == -1) break; // optimum achieved 
+	    
+      // step 5 
+      pvt.swap( kp, t );       // move variable from set Z to set P
+      kp++;
+      xA[kp-1] = 0;
+      steplsqr( b, pvt, kp-1, kp-1, true );
+      // step 6
+      ma = 0;
+      while ( ma < 1.5 ) {
+	for( j = 0; j <= kp-1; j++ ) z.A[j][0] = b.A[j][0];
+	rsolve(z, pvt, kp); 
+	ma = 2; jm = -1;
+	for( j = 0; j <= kp-1; j++ ) {  // step 7, 8 and 9
+	  if( z.A[j][0] <= 0.0 ) { // alpha always between 0 and 1
+	    alpha = xA[j] / ( xA[j] - z.A[j][0] ); 
+	    if( alpha < ma ) {
+	      ma = alpha; jm = j;
+	    }
+	  }
+	}
+	if( ma > 1.5 ) 
+	  for( j = 0; j <= kp-1; j++ ) xA[j] = z.A[j][0];  // step 7 
+	else { 
+	  for( j = kp-1; j >= 0; j-- ) { // step 10
+	    // Modified to avoid round-off error (which seemingly 
+	    // can cause infinite loop).
+	    if( j == jm ) { // step 11 
+	      xA[j] = 0.0;
+	      steplsqr( b, pvt, kp, j, false );
+	      kp--;  // move variable from set P to set Z
+	    }
+	    else xA[j] += ma * ( z.A[j][0] - xA[j] );
+	  }
+	}
+      }
+    }
+    x.setSize(kp);
+    pvt.setSize(kp);
+    return x;
+  }
+
+  /** Solves the nonnegative least squares problem with equality
+   *	constraint. That is, <p>
+   *  <center> min ||A x - b||, subject to x >= 0 and c x = d. </center> <p>
+   *
+   *  @param b the response
+   *  @param c coeficients of equality constraints
+   *  @param d constants of equality constraints
+   *  @param pvt vector storing pivoting column indices
+   *  @return the solution
+   */
+  public DoubleVector nnlse( PaceMatrix b, PaceMatrix c, PaceMatrix d, 
+			     IntVector pvt ) {
+    double eps = 1e-10 * Math.max( c.maxAbs(), d.maxAbs() ) /
+    Math.max( maxAbs(), b.maxAbs() );
+	
+    PaceMatrix e = c.rbind( new PaceMatrix( times(eps) ) );
+    PaceMatrix f = d.rbind( new PaceMatrix( b.times(eps) ) );
+
+    return e.nnls( f, pvt );
+  }
+
+  /** Solves the nonnegative least squares problem with equality
+   *	constraint. That is, <p>
+   *  <center> min ||A x - b||,  subject to x >= 0 and || x || = 1. </center>
+   *  <p>
+   *  @param b the response 
+   *  @param pvt vector storing pivoting column indices
+   *  @return the solution
+   */
+  public DoubleVector nnlse1( PaceMatrix b, IntVector pvt ) {
+    PaceMatrix c = new PaceMatrix( 1, n, 1 );
+    PaceMatrix d = new PaceMatrix( 1, b.n, 1 );
+	
+    return nnlse(b, c, d, pvt);
+  }
+
+  /** Generate matrix with standard-normally distributed random elements
+      @param m    Number of rows.
+      @param n    Number of colums.
+      @return An m-by-n matrix with random elements.  */
+  public static Matrix randomNormal( int m, int n ) {
+    Random random = new Random();
+     
+    Matrix A = new Matrix(m,n);
+    double[][] X = A.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+	X[i][j] = random.nextGaussian();
+      }
+    }
+    return A;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+
+  /**
+   * for testing only
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void  main( String args[] ) {
+    System.out.println("================================================" + 
+		       "===========");
+    System.out.println("To test the pace estimators of linear model\n" + 
+		       "coefficients.\n");
+
+    double sd = 2;     // standard deviation of the random error term
+    int n = 200;       // total number of observations
+    double beta0 = 100;   // intercept
+    int k1 = 20;       // number of coefficients of the first cluster
+    double beta1 = 0;  // coefficient value of the first cluster
+    int k2 = 20;      // number of coefficients of the second cluster
+    double beta2 = 5; // coefficient value of the second cluster 
+    int k = 1 + k1 + k2;
+
+    DoubleVector beta = new DoubleVector( 1 + k1 + k2 );
+    beta.set( 0, beta0 );
+    beta.set( 1, k1, beta1 );
+    beta.set( k1+1, k1+k2, beta2 );
+
+    System.out.println("The data set contains " + n + 
+		       " observations plus " + (k1 + k2) + 
+		       " variables.\n\nThe coefficients of the true model"
+		       + " are:\n\n" + beta );
+	
+    System.out.println("\nThe standard deviation of the error term is " + 
+		       sd );
+	
+    System.out.println("===============================================" 
+		       + "============");
+		
+    PaceMatrix X = new PaceMatrix( n, k1+k2+1 );
+    X.setMatrix( 0, n-1, 0, 0, 1 );
+    X.setMatrix( 0, n-1, 1, k1+k2, random(n, k1+k2) );
+	
+    PaceMatrix Y = new 
+      PaceMatrix( X.times( new PaceMatrix(beta) ).
+		  plusEquals( randomNormal(n,1).times(sd) ) );
+
+    IntVector pvt = (IntVector) IntVector.seq(0, k1+k2);
+
+    /*System.out.println( "The OLS estimate (by jama.Matrix.solve()) is:\n\n" + 
+      (new PaceMatrix(X.solve(Y))).getColumn(0) );*/
+	
+    X.lsqrSelection( Y, pvt, 1 );
+    X.positiveDiagonal( Y, pvt );
+
+    PaceMatrix sol = (PaceMatrix) Y.clone();
+    X.rsolve( sol, pvt, pvt.size() );
+    DoubleVector betaHat = sol.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe OLS estimate (through lsqr()) is: \n\n" + 
+			betaHat );
+
+    System.out.println( "\nQuadratic loss of the OLS estimate (||X b - X bHat||^2) = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaHat)) )))
+			.getColumn(0).sum2() );
+
+    System.out.println("=============================================" + 
+		       "==============");
+    System.out.println("             *** Pace estimation *** \n");
+    DoubleVector r = Y.getColumn( pvt.size(), n-1, 0);
+    double sde = Math.sqrt(r.sum2() / r.size());
+	
+    System.out.println( "Estimated standard deviation = " + sde );
+
+    DoubleVector aHat = Y.getColumn( 0, pvt.size()-1, 0).times( 1./sde );
+    System.out.println("\naHat = \n" + aHat );
+	
+    System.out.println("\n========= Based on chi-square mixture ============");
+
+    ChisqMixture d2 = new ChisqMixture();
+    int method = MixtureDistribution.NNMMethod;
+    DoubleVector AHat = aHat.square();
+    d2.fit( AHat, method ); 
+    System.out.println( "\nEstimated mixing distribution is:\n" + d2 );
+	
+    DoubleVector ATilde = d2.pace2( AHat );
+    DoubleVector aTilde = ATilde.sqrt().times(aHat.sign());
+    PaceMatrix YTilde = new 
+      PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    DoubleVector betaTilde = 
+    YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe pace2 estimate of coefficients = \n" + 
+			betaTilde );
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+    ATilde = d2.pace4( AHat );
+    aTilde = ATilde.sqrt().times(aHat.sign());
+    YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe pace4 estimate of coefficients = \n" + 
+			betaTilde );
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+    ATilde = d2.pace6( AHat );
+    aTilde = ATilde.sqrt().times(aHat.sign());
+    YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe pace6 estimate of coefficients = \n" + 
+			betaTilde );
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+    System.out.println("\n========= Based on normal mixture ============");
+	
+    NormalMixture d = new NormalMixture();
+    d.fit( aHat, method ); 
+    System.out.println( "\nEstimated mixing distribution is:\n" + d );
+	
+    aTilde = d.nestedEstimate( aHat );
+    YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "The nested estimate of coefficients = \n" + 
+			betaTilde );
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+	
+    aTilde = d.subsetEstimate( aHat );
+    YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    betaTilde = 
+    YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe subset estimate of coefficients = \n" + 
+			betaTilde );
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+    aTilde = d.empiricalBayesEstimate( aHat );
+    YTilde = new PaceMatrix((new PaceMatrix(aTilde)).times( sde ));
+    X.rsolve( YTilde, pvt, pvt.size() );
+    betaTilde = YTilde.getColumn(0).unpivoting( pvt, k );
+    System.out.println( "\nThe empirical Bayes estimate of coefficients = \n"+
+			betaTilde );
+	
+    System.out.println( "Quadratic loss = " + 
+			( new PaceMatrix( X.times( new 
+			  PaceMatrix(beta.minus(betaTilde)) )))
+			.getColumn(0).sum2() );
+	
+  }
+}
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CachedKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CachedKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CachedKernel.java	(revision 29)
@@ -0,0 +1,392 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CachedKernel.java
+ * 
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Base class for RBFKernel and PolyKernel that implements a simple LRU.
+ * (least-recently-used) cache if the cache size is set to a value > 0.
+ * Otherwise it uses a full cache.
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @author J. Lindgren (jtlindgr{at}cs.helsinki.fi) (RBF kernel)
+ * @author Steven Hugg (hugg@fasterlight.com) (refactored, LRU cache)
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz) (full cache)
+ * @version $Revision: 4549 $
+ */
+public abstract class CachedKernel 
+  extends Kernel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 702810182699015136L;
+    
+  /** Counts the number of kernel evaluations. */
+  protected int m_kernelEvals;
+
+  /** Counts the number of kernel cache hits. */
+  protected int m_cacheHits;
+
+  /** The size of the cache (a prime number) */
+  protected int m_cacheSize = 250007;
+
+  /** Kernel cache */
+  protected double[] m_storage;
+  protected long[] m_keys;
+
+  /** The kernel matrix if full cache is used (i.e. size is set to 0) */
+  protected double[][] m_kernelMatrix;
+
+  /** The number of instance in the dataset */
+  protected int m_numInsts;
+
+  /** number of cache slots in an entry */
+  protected int m_cacheSlots = 4;
+
+  /**
+   * default constructor - does nothing.
+   */
+  public CachedKernel() {
+    super();
+  }
+  
+  /**
+   * Initializes the kernel cache. The actual size of the cache in bytes is
+   * (64 * cacheSize).
+   * 
+   * @param data	the data to use
+   * @param cacheSize	the cache size
+   * @throws Exception	if something goes wrong
+   */
+  protected CachedKernel(Instances data, int cacheSize) throws Exception {
+    super();
+    
+    setCacheSize(cacheSize);
+    
+    buildKernel(data);
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		en;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe size of the cache (a prime number), 0 for full cache and \n"
+	+ "\t-1 to turn it off.\n"
+	+ "\t(default: 250007)",
+	"C", 1, "-C <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setCacheSize(Integer.parseInt(tmpStr));
+    else
+      setCacheSize(250007);
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-C");
+    result.add("" + getCacheSize());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * This method is overridden in subclasses to implement specific kernels.
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected abstract double evaluate(int id1, int id2, Instance inst1)
+    throws Exception;
+
+  /**
+   * Implements the abstract function of Kernel using the cache. This method
+   * uses the evaluate() method to do the actual dot product.
+   *
+   * @param id1 	the index of the first instance in the dataset
+   * @param id2 	the index of the second instance in the dataset
+   * @param inst1 	the instance corresponding to id1 (used if id1 == -1)
+   * @return 		the result of the kernel function
+   * @throws Exception 	if something goes wrong
+   */
+  public double eval(int id1, int id2, Instance inst1) throws Exception {
+		
+    double result = 0;
+    long key = -1;
+    int location = -1;
+
+    // we can only cache if we know the indexes and caching is not 
+    // disbled (m_cacheSize == -1)
+    if ( (id1 >= 0) && (m_cacheSize != -1) ) {
+
+      // Use full cache?
+      if (m_cacheSize == 0) {
+	if (m_kernelMatrix == null) {
+	  m_kernelMatrix = new double[m_data.numInstances()][];
+	  for(int i = 0; i < m_data.numInstances(); i++) {
+	    m_kernelMatrix[i] = new double[i + 1];
+	    for(int j = 0; j <= i; j++) {
+	      m_kernelEvals++;
+	      m_kernelMatrix[i][j] = evaluate(i, j, m_data.instance(i));
+	    }
+	  }
+	} 
+	m_cacheHits++;
+	result = (id1 > id2) ? m_kernelMatrix[id1][id2] : m_kernelMatrix[id2][id1];
+	return result;
+      }
+
+      // Use LRU cache
+      if (id1 > id2) {
+	key = (id1 + ((long) id2 * m_numInsts));
+      } else {
+	key = (id2 + ((long) id1 * m_numInsts));
+      }
+      location = (int) (key % m_cacheSize) * m_cacheSlots;
+      int loc = location;
+      for (int i = 0; i < m_cacheSlots; i++) {
+	long thiskey = m_keys[loc];
+	if (thiskey == 0)
+	  break; // empty slot, so break out of loop early
+	if (thiskey == (key + 1)) {
+	  m_cacheHits++;
+	  // move entry to front of cache (LRU) by swapping
+	  // only if it's not already at the front of cache
+	  if (i > 0) {
+	    double tmps = m_storage[loc];
+	    m_storage[loc] = m_storage[location];
+	    m_keys[loc] = m_keys[location];
+	    m_storage[location] = tmps;
+	    m_keys[location] = thiskey;
+	    return tmps;
+	  } else
+	    return m_storage[loc];
+	}
+	loc++;
+      }
+    }
+
+    result = evaluate(id1, id2, inst1);
+
+    m_kernelEvals++;
+
+    // store result in cache
+    if ( (key != -1) && (m_cacheSize != -1) ) {
+      // move all cache slots forward one array index
+      // to make room for the new entry
+      System.arraycopy(m_keys, location, m_keys, location + 1,
+		       m_cacheSlots - 1);
+      System.arraycopy(m_storage, location, m_storage, location + 1,
+		       m_cacheSlots - 1);
+      m_storage[location] = result;
+      m_keys[location] = (key + 1);
+    }
+    return result;
+  }
+
+  /**
+   * Returns the number of time Eval has been called.
+   * 
+   * @return 		the number of kernel evaluation.
+   */
+  public int numEvals() {
+    return m_kernelEvals;
+  }
+
+  /**
+   * Returns the number of cache hits on dot products.
+   * 
+   * @return 		the number of cache hits.
+   */
+  public int numCacheHits() {
+    return m_cacheHits;
+  }
+
+  /**
+   * Frees the cache used by the kernel.
+   */
+  public void clean() {
+    m_storage = null;
+    m_keys = null;
+    m_kernelMatrix = null;
+  }
+
+  /**
+   * Calculates a dot product between two instances
+   * 
+   * @param inst1	the first instance
+   * @param inst2	the second instance
+   * @return 		the dot product of the two instances.
+   * @throws Exception	if an error occurs
+   */
+  protected final double dotProd(Instance inst1, Instance inst2)
+    throws Exception {
+
+    double result = 0;
+
+    // we can do a fast dot product
+    int n1 = inst1.numValues();
+    int n2 = inst2.numValues();
+    int classIndex = m_data.classIndex();
+    for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
+      int ind1 = inst1.index(p1);
+      int ind2 = inst2.index(p2);
+      if (ind1 == ind2) {
+	if (ind1 != classIndex) {
+	  result += inst1.valueSparse(p1) * inst2.valueSparse(p2);
+	}
+	p1++;
+	p2++;
+      } else if (ind1 > ind2) {
+	p2++;
+      } else {
+	p1++;
+      }
+    }
+    return (result);
+  }
+
+  /**
+   * Sets the size of the cache to use (a prime number)
+   * 
+   * @param value	the size of the cache
+   */
+  public void setCacheSize(int value) {
+    if (value >= -1) {
+      m_cacheSize = value;
+      clean();
+    }
+    else {
+      System.out.println(
+	  "Cache size cannot be smaller than -1 (provided: " + value + ")!");
+    }
+  }
+  
+  /**
+   * Gets the size of the cache
+   * 
+   * @return 		the cache size
+   */
+  public int getCacheSize() {
+    return m_cacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String cacheSizeTipText() {
+    return "The size of the cache (a prime number), 0 for full cache and -1 to turn it off.";
+  }
+
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+    
+    m_kernelEvals = 0;
+    m_cacheHits   = 0;
+    m_numInsts    = m_data.numInstances();
+
+    if (getCacheSize() > 0) {
+      // Use LRU cache
+      m_storage = new double[m_cacheSize * m_cacheSlots];
+      m_keys    = new long[m_cacheSize * m_cacheSlots];
+    } 
+    else {
+      m_storage      = null;
+      m_keys         = null;
+      m_kernelMatrix = null;
+    }
+  }
+  
+  /**
+   * builds the kernel with the given data. Initializes the kernel cache. 
+   * The actual size of the cache in bytes is (64 * cacheSize).
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong
+   */
+  public void buildKernel(Instances data) throws Exception {
+    // does kernel handle the data?
+    if (!getChecksTurnedOff())
+      getCapabilities().testWithFail(data);
+
+    initVars(data);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CheckKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CheckKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/CheckKernel.java	(revision 29)
@@ -0,0 +1,1446 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckKernel.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Attribute;
+import weka.core.CheckScheme;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with 
+ * kernels. If you implement an kernels using the WEKA.libraries,
+ * you should run the checks on it to ensure robustness and correct
+ * operation. Passing all the tests of this object does not mean
+ * bugs in the kernels don't exist, but this will help find some
+ * common ones. <p/>
+ * 
+ * Typical usage: <p/>
+ * <code>java weka.classifiers.functions.supportVector.CheckKernel -W kernel_name 
+ * -- kernel_options </code><p/>
+ * 
+ * CheckKernel reports on the following:
+ * <ul>
+ *    <li> Kernel abilities 
+ *      <ul>
+ *         <li> Possible command line options to the kernels </li>
+ *         <li> Whether the kernels can predict nominal, numeric, string, 
+ *              date or relational class attributes. </li>
+ *         <li> Whether the kernels can handle numeric predictor attributes </li>
+ *         <li> Whether the kernels can handle nominal predictor attributes </li>
+ *         <li> Whether the kernels can handle string predictor attributes </li>
+ *         <li> Whether the kernels can handle date predictor attributes </li>
+ *         <li> Whether the kernels can handle relational predictor attributes </li>
+ *         <li> Whether the kernels can handle multi-instance data </li>
+ *         <li> Whether the kernels can handle missing predictor values </li>
+ *         <li> Whether the kernels can handle missing class values </li>
+ *         <li> Whether a nominal kernels only handles 2 class problems </li>
+ *         <li> Whether the kernels can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning 
+ *      <ul>
+ *         <li> Correct initialisation during buildKernel (i.e. no result
+ *              changes when buildKernel called repeatedly) </li>
+ *         <li> Whether the kernels alters the data passed to it 
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases 
+ *      <ul>
+ *         <li> building kernels with zero training instances </li>
+ *         <li> all but one predictor attribute values missing </li>
+ *         <li> all predictor attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckKernel with the debug option set will output the 
+ * training and test datasets for any failed tests.<p/>
+ *
+ * The <code>weka.classifiers.AbstractKernelTest</code> uses this
+ * class to test all the kernels. Any changes here, have to be 
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ * 
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 2).</pre>
+ * 
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 1).</pre>
+ * 
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 1).</pre>
+ * 
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 1).</pre>
+ * 
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 1).</pre>
+ * 
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 1).</pre>
+ * 
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ * 
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ * 
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ * 
+ * <pre> -W
+ *  Full name of the kernel analysed.
+ *  eg: weka.classifiers.functions.supportVector.RBFKernel
+ *  (default weka.classifiers.functions.supportVector.RBFKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.functions.supportVector.RBFKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -G &lt;num&gt;
+ *  The Gamma parameter.
+ *  (default: 0.01)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated kernel.<p/>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ * @see TestInstances
+ */
+public class CheckKernel
+  extends CheckScheme {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+  
+  /*** The kernel to be examined */
+  protected Kernel m_Kernel = new weka.classifiers.functions.supportVector.RBFKernel();
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    result.addElement(new Option(
+        "\tFull name of the kernel analysed.\n"
+        +"\teg: weka.classifiers.functions.supportVector.RBFKernel\n"
+        + "\t(default weka.classifiers.functions.supportVector.RBFKernel)",
+        "W", 1, "-W"));
+    
+    if ((m_Kernel != null) 
+        && (m_Kernel instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0, 
+          "\nOptions specific to kernel "
+          + m_Kernel.getClass().getName()
+          + ":"));
+      Enumeration enu = ((OptionHandler)m_Kernel).listOptions();
+      while (enu.hasMoreElements())
+        result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   * 
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 2).</pre>
+   * 
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 1).</pre>
+   * 
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 1).</pre>
+   * 
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 1).</pre>
+   * 
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 1).</pre>
+   * 
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 1).</pre>
+   * 
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   * 
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   * 
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   * 
+   * <pre> -W
+   *  Full name of the kernel analysed.
+   *  eg: weka.classifiers.functions.supportVector.RBFKernel
+   *  (default weka.classifiers.functions.supportVector.RBFKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.functions.supportVector.RBFKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -G &lt;num&gt;
+   *  The Gamma parameter.
+   *  (default: 0.01)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.classifiers.functions.supportVector.RBFKernel.class.getName();
+    setKernel(
+	(Kernel) forName(
+	    "weka.classifiers.functions.supportVector", 
+	    Kernel.class, 
+	    tmpStr, 
+	    Utils.partitionOptions(options)));
+  }
+  
+  /**
+   * Gets the current settings of the CheckKernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getKernel() != null) {
+      result.add("-W");
+      result.add(getKernel().getClass().getName());
+    }
+    
+    if ((m_Kernel != null) && (m_Kernel instanceof OptionHandler))
+      options = ((OptionHandler) m_Kernel).getOptions();
+    else
+      options = new String[0];
+    
+    if (options.length > 0) {
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+    
+    if (getKernel() == null) {
+      println("\n=== No kernel set ===");
+      return;
+    }
+    println("\n=== Check on kernel: "
+        + getKernel().getClass().getName()
+        + " ===\n");
+    
+    // Start tests
+    m_ClasspathProblems = false;
+    println("--> Checking for interfaces");
+    canTakeOptions();
+    boolean weightedInstancesHandler = weightedInstancesHandler()[0];
+    boolean multiInstanceHandler = multiInstanceHandler()[0];
+    println("--> Kernel tests");
+    declaresSerialVersionUID();
+    testsPerClassType(Attribute.NOMINAL,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.NUMERIC,    weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.DATE,       weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.STRING,     weightedInstancesHandler, multiInstanceHandler);
+    testsPerClassType(Attribute.RELATIONAL, weightedInstancesHandler, multiInstanceHandler);
+  }
+  
+  /**
+   * Set the lernel to test. 
+   *
+   * @param value the kernel to use.
+   */
+  public void setKernel(Kernel value) {
+    m_Kernel = value;
+  }
+  
+  /**
+   * Get the kernel being tested
+   *
+   * @return the kernel being tested
+   */
+  public Kernel getKernel() {
+    return m_Kernel;
+  }
+  
+  /**
+   * Run a battery of tests for a given class attribute type
+   *
+   * @param classType true if the class attribute should be numeric
+   * @param weighted true if the kernel says it handles weights
+   * @param multiInstance true if the kernel is a multi-instance kernel
+   */
+  protected void testsPerClassType(int classType, 
+                                   boolean weighted,
+                                   boolean multiInstance) {
+    
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance, classType)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance, classType)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance, classType)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance, classType)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance, classType)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      
+      if (classType == Attribute.NOMINAL)
+        canHandleNClasses(PNom, PNum, PStr, PDat, PRel, multiInstance, 4);
+
+      if (!multiInstance) {
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 0);
+	canHandleClassAsNthAttribute(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, 1);
+      }
+      
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          true, false, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, true, false, 100);
+      
+      boolean handleMissingClass = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, classType, 
+          false, true, 20)[0];
+      if (handleMissingClass)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, classType, false, true, 100);
+      
+      correctBuildInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance, classType);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, classType,
+          handleMissingPredictors, handleMissingClass);
+    }
+  }
+  
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the kernel can take options
+   */
+  protected boolean[] canTakeOptions() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("options...");
+    if (m_Kernel instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler)m_Kernel).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n" 
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the kernel handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("weighted instances kernel...");
+    if (m_Kernel instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme handles multi-instance data.
+   * 
+   * @return true if the kernel handles multi-instance data
+   */
+  protected boolean[] multiInstanceHandler() {
+    boolean[] result = new boolean[2];
+    
+    print("multi-instance kernel...");
+    if (m_Kernel instanceof MultiInstanceCapabilitiesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * tests for a serialVersionUID. Fails in case the scheme doesn't declare
+   * a UID.
+   *
+   * @return index 0 is true if the scheme declares a UID
+   */
+  protected boolean[] declaresSerialVersionUID() {
+    boolean[] result = new boolean[2];
+    
+    print("serialVersionUID...");
+    
+    result[0] = !SerializationHelper.needsUID(m_Kernel.getClass());
+    
+    if (result[0])
+      println("yes");
+    else
+      println("no");
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic prediction of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canPredict(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("basic predict");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("unary");
+    accepts.addElement("binary");
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("multi-instance");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether nominal schemes can handle more than two classes.
+   * If a scheme is only designed for two-class problems it should
+   * throw an appropriate exception for multi-class problems.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param numClasses the number of classes to test
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleNClasses(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int numClasses) {
+    
+    print("more than two class problems");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, Attribute.NOMINAL);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("number");
+    accepts.addElement("class");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        Attribute.NOMINAL,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle class attributes as Nth attribute.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class attribute (0-based, -1 means last attribute)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected boolean[] canHandleClassAsNthAttribute(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex) {
+    
+    if (classIndex == TestInstances.CLASS_IS_LAST)
+      print("class attribute as last attribute");
+    else
+      print("class attribute as " + (classIndex + 1) + ". attribute");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    int numTrain = getNumInstances(), numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+                        datePredictor, relationalPredictor, 
+                        multiInstance,
+                        classType,
+                        classIndex,
+                        missingLevel, predictorMissing, classMissing,
+                        numTrain, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("handle zero training instances");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    return runBasicTest(
+              nominalPredictor, numericPredictor, stringPredictor, 
+              datePredictor, relationalPredictor, 
+              multiInstance,
+              classType, 
+              missingLevel, predictorMissing, classMissing,
+              numTrain, numClasses, 
+              accepts);
+  }
+  
+  /**
+   * Checks whether the scheme correctly initialises models when 
+   * buildKernel is called. This test calls buildKernel with
+   * one training dataset. buildKernel is then called on a training 
+   * set with different structure, and then again with the original training 
+   * set. If the equals method of the KernelEvaluation class returns 
+   * false, this is noted as incorrect build initialisation.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] correctBuildInitialisation(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+
+    boolean[] result = new boolean[2];
+    
+    print("correct initialisation during buildKernel");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    Instances train1 = null;
+    Instances train2 = null;
+    Kernel kernel = null;
+    KernelEvaluation evaluation1A = null;
+    KernelEvaluation evaluation1B = null;
+    KernelEvaluation evaluation2 = null;
+    int stage = 0;
+    try {
+      
+      // Make two sets of train/test splits with different 
+      // numbers of attributes
+      train1 = makeTestDataset(42, numTrain, 
+                               nominalPredictor    ? getNumNominal()    : 0,
+                               numericPredictor    ? getNumNumeric()    : 0, 
+                               stringPredictor     ? getNumString()     : 0, 
+                               datePredictor       ? getNumDate()       : 0, 
+                               relationalPredictor ? getNumRelational() : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      train2 = makeTestDataset(84, numTrain, 
+                               nominalPredictor    ? getNumNominal() + 1    : 0,
+                               numericPredictor    ? getNumNumeric() + 1    : 0, 
+                               stringPredictor     ? getNumString() + 1     : 0, 
+                               datePredictor       ? getNumDate() + 1       : 0, 
+                               relationalPredictor ? getNumRelational() + 1 : 0, 
+                               numClasses, 
+                               classType,
+                               multiInstance);
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, predictorMissing, classMissing);
+        addMissing(train2, missingLevel, predictorMissing, classMissing);
+      }
+      
+      kernel = Kernel.makeCopy(getKernel());
+      evaluation1A = new KernelEvaluation();
+      evaluation1B = new KernelEvaluation();
+      evaluation2 = new KernelEvaluation();
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      stage = 0;
+      evaluation1A.evaluate(kernel, train1);
+      
+      stage = 1;
+      evaluation2.evaluate(kernel, train2);
+      
+      stage = 2;
+      evaluation1B.evaluate(kernel, train1);
+      
+      stage = 3;
+      if (!evaluation1A.equals(evaluation1B)) {
+        if (m_Debug) {
+          println("\n=== Full report ===\n"
+              + evaluation1A.toSummaryString("\nFirst buildKernel()")
+                  + "\n\n");
+          println(
+              evaluation1B.toSummaryString("\nSecond buildKernel()")
+                  + "\n\n");
+        }
+        throw new Exception("Results differ between buildKernel calls");
+      }
+      println("yes");
+      result[0] = true;
+      
+      if (false && m_Debug) {
+        println("\n=== Full report ===\n"
+            + evaluation1A.toSummaryString("\nFirst buildKernel()")
+                + "\n\n");
+        println(
+            evaluation1B.toSummaryString("\nSecond buildKernel()")
+                + "\n\n");
+      }
+    } 
+    catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;	  
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing,
+      int missingLevel) {
+    
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (predictorMissing) {
+      print(" predictor");
+      if (classMissing)
+        print(" and");
+    }
+    if (classMissing)
+      print(" class");
+    print(" values");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    int numTrain = getNumInstances(), numClasses = 2;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        classType, 
+        missingLevel, predictorMissing, classMissing,
+        numTrain, numClasses, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether the kernel can handle instance weights.
+   * This test compares the kernel performance on two datasets
+   * that are identical except for the training weights. If the 
+   * results change, then the kernel must be using the weights. It
+   * may be possible to get a false positive from this test if the 
+   * weight changes aren't significant enough to induce a change
+   * in kernel performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType) {
+    
+    print("kernel uses instance weights");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = 2*getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Kernel[] kernels = null;
+    KernelEvaluation evaluationB = null;
+    KernelEvaluation evaluationI = null;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0, 
+                              stringPredictor     ? getNumString()      : 0, 
+                              datePredictor       ? getNumDate()        : 0, 
+                              relationalPredictor ? getNumRelational()  : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      kernels = Kernel.makeCopies(getKernel(), 2);
+      evaluationB = new KernelEvaluation();
+      evaluationI = new KernelEvaluation();
+      evaluationB.evaluate(kernels[0], train);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      
+      // Now modify instance weights and re-built/test
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      evaluationI.evaluate(kernels[1], train);
+      if (evaluationB.equals(evaluationI)) {
+        //	println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println(evaluationB.toSummaryString("\nboth methods\n"));
+        } else {
+          print("Problem during building");
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1) 
+              + "    " + train.instance(i).weight());
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * building. If the scheme needs to modify the data it should take 
+   * a copy of the training data. Currently checks for changes to header 
+   * structure, number of instances, order of instances, instance weights.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param predictorMissing true if we know the kernel can handle
+   * (at least) moderate missing predictor values
+   * @param classMissing true if we know the kernel can handle
+   * (at least) moderate missing class values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      boolean predictorMissing,
+      boolean classMissing) {
+    
+    print("kernel doesn't alter original datasets");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance, classType);
+    print("...");
+    int numTrain = getNumInstances(), 
+    numClasses = 2, missingLevel = 20;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Kernel kernel = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0, 
+                              datePredictor       ? getNumDate()       : 0, 
+                              relationalPredictor ? getNumRelational() : 0, 
+                              numClasses, 
+                              classType,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      kernel = Kernel.makeCopies(getKernel(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      Instances trainCopy = new Instances(train);
+      kernel.buildKernel(trainCopy);
+      compareDatasets(train, trainCopy);
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        println(": " + ex.getMessage() + "\n");
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    return runBasicTest(
+		nominalPredictor, 
+		numericPredictor,
+		stringPredictor,
+		datePredictor,
+		relationalPredictor,
+		multiInstance,
+		classType, 
+		TestInstances.CLASS_IS_LAST,
+		missingLevel,
+		predictorMissing,
+		classMissing,
+		numTrain,
+		numClasses,
+		accepts);
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the attribute index of the class
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int classType,
+      int classIndex,
+      int missingLevel,
+      boolean predictorMissing,
+      boolean classMissing,
+      int numTrain,
+      int numClasses,
+      FastVector accepts) {
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Kernel kernel = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor     ? getNumNominal()    : 0,
+                              numericPredictor     ? getNumNumeric()    : 0, 
+                              stringPredictor      ? getNumString()     : 0,
+                              datePredictor        ? getNumDate()       : 0,
+                              relationalPredictor  ? getNumRelational() : 0,
+                              numClasses, 
+                              classType,
+                              classIndex,
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      kernel = Kernel.makeCopies(getKernel(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      kernel.buildKernel(train);
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+	msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+	m_ClasspathProblems = true;
+
+      for (int i = 0; i < accepts.size(); i++) {
+	if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+	  acceptable = true;
+	}
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during building");
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here is the dataset:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Make a simple set of instances, which can later be modified
+   * for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      boolean multiInstance)
+    throws Exception {
+    
+    return makeTestDataset(
+		seed, 
+		numInstances,
+		numNominal,
+		numNumeric,
+		numString,
+		numDate, 
+		numRelational,
+		numClasses, 
+		classType,
+		TestInstances.CLASS_IS_LAST,
+		multiInstance);
+  }
+  
+  /**
+   * Make a simple set of instances with variable position of the class 
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class (0-based, -1 as last)
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      int numClasses, int classType,
+                                      int classIndex,
+                                      boolean multiInstance)
+  throws Exception {
+    
+    TestInstances dataset = new TestInstances();
+    
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal(numNominal);
+    dataset.setNumNumeric(numNumeric);
+    dataset.setNumString(numString);
+    dataset.setNumDate(numDate);
+    dataset.setNumRelational(numRelational);
+    dataset.setNumClasses(numClasses);
+    dataset.setClassType(classType);
+    dataset.setClassIndex(classIndex);
+    dataset.setNumClasses(numClasses);
+    dataset.setMultiInstance(multiInstance);
+    dataset.setWords(getWords());
+    dataset.setWordSeparators(getWordSeparators());
+    
+    return process(dataset.generate());
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param nominalPredictor true if nominal predictor attributes are present
+   * @param numericPredictor true if numeric predictor attributes are present
+   * @param stringPredictor true if string predictor attributes are present
+   * @param datePredictor true if date predictor attributes are present
+   * @param relationalPredictor true if relational predictor attributes are present
+   * @param multiInstance whether multi-instance is needed
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(boolean nominalPredictor, 
+                                       boolean numericPredictor, 
+                                       boolean stringPredictor, 
+                                       boolean datePredictor, 
+                                       boolean relationalPredictor, 
+                                       boolean multiInstance,
+                                       int classType) {
+    
+    String str = "";
+
+    if (numericPredictor)
+      str += " numeric";
+    
+    if (nominalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " nominal";
+    }
+    
+    if (stringPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " string";
+    }
+    
+    if (datePredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " date";
+    }
+    
+    if (relationalPredictor) {
+      if (str.length() > 0)
+        str += " &";
+      str += " relational";
+    }
+    
+    str += " predictors)";
+    
+    switch (classType) {
+      case Attribute.NUMERIC:
+        str = " (numeric class," + str;
+        break;
+      case Attribute.NOMINAL:
+        str = " (nominal class," + str;
+        break;
+      case Attribute.STRING:
+        str = " (string class," + str;
+        break;
+      case Attribute.DATE:
+        str = " (date class," + str;
+        break;
+      case Attribute.RELATIONAL:
+        str = " (relational class," + str;
+        break;
+    }
+    
+    print(str);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.3 $");
+  }
+  
+  /**
+   * Test method for this class
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    runCheck(new CheckKernel(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Kernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Kernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Kernel.java	(revision 29)
@@ -0,0 +1,315 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Kernel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract kernel. 
+ * Kernels implementing this class must respect Mercer's condition in order 
+ * to ensure a correct behaviour of SMOreg.
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5449 $
+ */
+public abstract class Kernel 
+  implements Serializable, OptionHandler, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6102771099905817064L;
+
+  /** The dataset */
+  protected Instances m_data;
+
+  /** enables debugging output */
+  protected boolean m_Debug = false;
+
+  /** Turns off all checks */
+  protected boolean m_ChecksTurnedOff = false;
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public abstract String globalInfo();
+    
+  /**
+   * Computes the result of the kernel function for two instances.
+   * If id1 == -1, eval use inst1 instead of an instance in the dataset.
+   *
+   * @param id1 the index of the first instance in the dataset
+   * @param id2 the index of the second instance in the dataset
+   * @param inst1 the instance corresponding to id1 (used if id1 == -1)
+   * @return the result of the kernel function
+   * @throws Exception if something goes wrong
+   */
+  public abstract double eval(int id1, int id2, Instance inst1) 
+    throws Exception;
+
+  /**
+   * Frees the memory used by the kernel.
+   * (Useful with kernels which use cache.)
+   * This function is called when the training is done.
+   * i.e. after that, eval will be called with id1 == -1.
+   */
+  public abstract void clean();
+
+  /**
+   * Returns the number of kernel evaluation performed.
+   *
+   * @return the number of kernel evaluation performed.
+   */
+  public abstract int numEvals();
+
+  /**
+   * Returns the number of dot product cache hits.
+   *
+   * @return the number of dot product cache hits, or -1 if not supported by this kernel.
+   */
+  public abstract int numCacheHits();
+    
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    
+    result = new Vector();
+
+    result.addElement(new Option(
+	"\tEnables debugging output (if available) to be printed.\n"
+	+ "\t(default: off)",
+	"D", 0, "-D"));
+
+    result.addElement(new Option(
+	"\tTurns off all checks - use with caution!\n"
+	+ "\t(default: checks on)",
+	"no-checks", 0, "-no-checks"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    setChecksTurnedOff(Utils.getFlag("no-checks", options));
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector    result;
+
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+
+    if (getChecksTurnedOff())
+      result.add("-no-checks");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Enables or disables the output of debug information (if the derived
+   * kernel supports that)
+   * 
+   * @param value	whether to output debugging information
+   */
+  public void setDebug(boolean value) {
+    m_Debug = value;
+  }
+  
+  /**
+   * Gets whether debugging output is turned on or not.
+   * 
+   * @return		true if debugging output is produced.
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Turns on the output of debugging information.";
+  }
+  
+  /**
+   * Disables or enables the checks (which could be time-consuming). Use with
+   * caution!
+   * 
+   * @param value	if true turns off all checks
+   */
+  public void setChecksTurnedOff(boolean value) {
+    m_ChecksTurnedOff = value;
+  }
+  
+  /**
+   * Returns whether the checks are turned off or not.
+   * 
+   * @return		true if the checks are turned off
+   */
+  public boolean getChecksTurnedOff() {
+    return m_ChecksTurnedOff;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String checksTurnedOffTipText() {
+    return "Turns time-consuming checks off - use with caution.";
+  }
+  
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    m_data = data;
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel. Derived kernels have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.enableAll();
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5449 $");
+  }
+  
+  /**
+   * builds the kernel with the given data
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong
+   */
+  public void buildKernel(Instances data) throws Exception {
+    // does kernel handle the data?
+    if (!getChecksTurnedOff())
+      getCapabilities().testWithFail(data);
+    
+    initVars(data);
+  }
+
+  /**
+   * Creates a deep copy of the given kernel using serialization.
+   *
+   * @param kernel 	the kernel to copy
+   * @return 		a deep copy of the kernel
+   * @throws Exception 	if an error occurs
+   */
+  public static Kernel makeCopy(Kernel kernel) throws Exception {
+    return (Kernel) new SerializedObject(kernel).getObject();
+  }
+
+  /**
+   * Creates a given number of deep copies of the given kernel using 
+   * serialization.
+   * 
+   * @param model 	the kernel to copy
+   * @param num 	the number of kernel copies to create.
+   * @return 		an array of kernels.
+   * @throws Exception 	if an error occurs
+   */
+  public static Kernel[] makeCopies(Kernel model, int num) throws Exception {
+    if (model == null)
+      throw new Exception("No model kernel set");
+
+    Kernel[] kernels = new Kernel[num];
+    SerializedObject so = new SerializedObject(model);
+    for (int i = 0; i < kernels.length; i++)
+      kernels[i] = (Kernel) so.getObject();
+
+    return kernels;
+  }
+  
+  /**
+   * Creates a new instance of a kernel given it's class name and
+   * (optional) arguments to pass to it's setOptions method.
+   *
+   * @param kernelName 	the fully qualified class name of the classifier
+   * @param options 	an array of options suitable for passing to setOptions. May
+   * 			be null.
+   * @return 		the newly created classifier, ready for use.
+   * @throws Exception 	if the classifier name is invalid, or the options
+   * 			supplied are not acceptable to the classifier
+   */
+  public static Kernel forName(String kernelName, String[] options) 
+    throws Exception {
+
+    return (Kernel) Utils.forName(Kernel.class, kernelName, options);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/KernelEvaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/KernelEvaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/KernelEvaluation.java	(revision 29)
@@ -0,0 +1,373 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KernelEvaluation.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Enumeration;
+
+/**
+ * Class for evaluating Kernels.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class KernelEvaluation
+  implements RevisionHandler {
+
+  /** the result string */
+  protected StringBuffer m_Result;
+  
+  /** the kernel evaluation results */
+  protected double[][] m_Evaluations;
+  
+  /** the number of performed evaluations */
+  protected int m_NumEvals;
+  
+  /** the number of cache hits */
+  protected int m_NumCacheHits;
+  
+  /** user-supplied options */
+  protected String[] m_Options;
+  
+  /**
+   * default constructor
+   */
+  public KernelEvaluation() {
+    super();
+    
+    m_Result       = new StringBuffer();
+    m_Evaluations  = new double[0][0];
+    m_Options      = new String[0];
+    m_NumEvals     = 0;
+    m_NumCacheHits = 0;
+  }
+  
+  /**
+   * sets the option the user supplied for the kernel
+   * 
+   * @param options	options that were supplied for the kernel
+   */
+  public void setUserOptions(String[] options) {
+    m_Options = (String[]) options.clone();
+  }
+  
+  /**
+   * returns the options the user supplied for the kernel
+   * 
+   * @return		the user supplied options for the kernel
+   */
+  public String[] getUserOptions() {
+    return (String[]) m_Options.clone();
+  }
+  
+  /**
+   * Generates an option string to output on the commandline.
+   * 
+   * @param Kernel	the Kernel to generate the string for
+   * @return		the option string
+   */
+  protected static String makeOptionString(Kernel Kernel) {
+    StringBuffer	text;
+    
+    text = new StringBuffer();   
+    
+    // general options
+    text.append("\nGeneral options:\n\n");
+    text.append("-t <training file>\n");
+    text.append("\tThe name of the training file.\n");
+    text.append("-c <class index>\n");
+    text.append("\tSets index of class attribute (default: last).\n");
+    
+    // Kernel specific options, if any
+    if (Kernel instanceof OptionHandler) {
+      text.append(
+	  "\nOptions specific to " 
+	  + Kernel.getClass().getName().replaceAll(".*\\.", "") + ":\n\n");
+      
+      Enumeration enm = ((OptionHandler) Kernel).listOptions();
+      while (enm.hasMoreElements()) {
+	Option option = (Option) enm.nextElement();
+	text.append(option.synopsis() + "\n");
+	text.append(option.description() + "\n");
+      }
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Evaluates the Kernel with the given commandline options and returns
+   * the evaluation string.
+   * 
+   * @param Kernel	the Kernel to evaluate
+   * @param options	the commandline options
+   * @return		the generated output string
+   * @throws Exception	if evaluation fails
+   */
+  public static String evaluate(Kernel Kernel, String[] options) 
+    throws Exception {
+
+    String trainFileString = "";
+    BufferedReader reader;
+    KernelEvaluation eval;
+    String classIndexString;
+    int classIndex = -1;
+    Instances train;
+    String[] userOptions;
+
+    // help?
+    if (Utils.getFlag('h', options))
+      throw new Exception("\nHelp requested.\n" + makeOptionString(Kernel));
+    
+    try {
+      // general options
+      trainFileString = Utils.getOption('t', options);
+      if (trainFileString.length() == 0) 
+	throw new Exception("No training file given!");
+      reader = new BufferedReader(new FileReader(trainFileString));
+
+      classIndexString = Utils.getOption('c', options);
+      if (classIndexString.length() != 0) {
+	if (classIndexString.equals("first"))
+	  classIndex = 1;
+	else if (classIndexString.equals("last"))
+	  classIndex = -1;
+	else
+	  classIndex = Integer.parseInt(classIndexString);
+      }
+      
+      // Kernel specific options
+      userOptions = (String[]) options.clone();
+      if (Kernel instanceof OptionHandler) {
+        ((OptionHandler) Kernel).setOptions(options);
+      }
+      
+      // left-over options?
+      Utils.checkForRemainingOptions(options);
+    }
+    catch (Exception e) {
+      throw new Exception(
+	  "\nWeka exception: " 
+	  + e.getMessage() + "\n" 
+	  + makeOptionString(Kernel));
+    }
+    
+    // load file and build kernel
+    eval = new KernelEvaluation();
+    eval.setUserOptions(userOptions);
+    train = new Instances(reader);
+    if (classIndex == -1)
+      train.setClassIndex(train.numAttributes() - 1);
+    else
+      train.setClassIndex(classIndex);
+    
+    return eval.evaluate(Kernel, train);
+  }
+
+  /**
+   * Evaluates a kernel with the options given in an array of strings.
+   *
+   * @param kernelString 	class of kernel as a string
+   * @param options 		the array of string containing the options
+   * @throws Exception 		if model could not be evaluated successfully
+   * @return 			a string describing the results 
+   */
+  public static String evaluate(String kernelString, String[] options) throws Exception {
+    Kernel kernel;	 
+
+    // Create kernel
+    try {
+      kernel = (Kernel) Class.forName(kernelString).newInstance();
+    }
+    catch (Exception e) {
+      throw new Exception("Can't find class with name " + kernelString + '.');
+    }
+    
+    return evaluate(kernel, options);
+  }
+  
+  /**
+   * Evaluates the Kernel with the given commandline options and returns
+   * the evaluation string.
+   * 
+   * @param kernel	the Kernel to evaluate
+   * @param data	the data to run the Kernel with
+   * @return		the generated output string
+   * @throws Exception	if evaluation fails
+   */
+  public String evaluate(Kernel kernel, Instances data) 
+    throws Exception {
+    
+    long 	startTime;
+    long 	endTime;
+    int 	i;
+    int		n;
+
+    m_Result = new StringBuffer();
+    
+    // build kernel
+    startTime = System.currentTimeMillis();
+    kernel.buildKernel(data);
+    endTime = System.currentTimeMillis();
+    m_Result.append("\n=== Model ===\n\n");
+    if (Utils.joinOptions(getUserOptions()).trim().length() != 0)
+      m_Result.append("Options: " + Utils.joinOptions(getUserOptions()) + "\n\n");
+    m_Result.append(kernel.toString() + "\n");
+
+    // evaluate dataset
+    m_Evaluations = new double[data.numInstances()][data.numInstances()];
+    for (n = 0; n < data.numInstances(); n++) {
+      for (i = n; i < data.numInstances(); i++) {
+	m_Evaluations[n][i] = kernel.eval(n, i, data.instance(n));
+      }
+    }
+    
+    // test cache for cached kernels
+    if (kernel instanceof CachedKernel) {
+      for (n = 0; n < data.numInstances(); n++) {
+	for (i = n; i < data.numInstances(); i++) {
+	  m_Evaluations[n][i] = kernel.eval(n, i, data.instance(n));
+	}
+      }
+    }
+    
+    m_NumEvals     = kernel.numEvals();
+    m_NumCacheHits = kernel.numCacheHits();
+    
+    // summary
+    m_Result.append("\n=== Evaluation ===\n\n");
+    if (kernel instanceof CachedKernel) {
+      m_Result.append("Cache size   : " + ((CachedKernel) kernel).getCacheSize() + "\n");
+    }
+    m_Result.append("# Evaluations: " + m_NumEvals + "\n");
+    m_Result.append("# Cache hits : " + m_NumCacheHits + "\n");
+    m_Result.append("Elapsed time : " + (((double) (endTime - startTime)) / 1000) + "s\n");
+    
+    return m_Result.toString();
+  }
+
+  /**
+   * Tests whether the current evaluation object is equal to another
+   * evaluation object
+   *
+   * @param obj the object to compare against
+   * @return true if the two objects are equal
+   */
+  public boolean equals(Object obj) {
+    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
+      return false;
+    
+    KernelEvaluation cmp = (KernelEvaluation) obj;
+    
+    if (m_NumEvals != cmp.m_NumEvals) return false;
+    if (m_NumCacheHits != cmp.m_NumCacheHits) return false;
+
+    if (m_Evaluations.length != cmp.m_Evaluations.length) 
+      return false;
+    for (int n = 0; n < m_Evaluations.length; n++) {
+      for (int i = 0; i < m_Evaluations[n].length; i++) {
+	if (Double.isNaN(m_Evaluations[n][i]) && Double.isNaN(cmp.m_Evaluations[n][i]))
+	  continue;
+	if (m_Evaluations[n][i] != cmp.m_Evaluations[n][i])
+	  return false;
+      }
+    }
+    
+    return true;
+  }
+  
+  /**
+   * returns a summary string of the evaluation with a no title
+   * 
+   * @return		the summary string
+   */
+  public String toSummaryString() {
+    return toSummaryString("");
+  }
+  
+  /**
+   * returns a summary string of the evaluation with a default title
+   * 
+   * @param title	the title to print before the result
+   * @return		the summary string
+   */
+  public String toSummaryString(String title) {
+    StringBuffer	result;
+    
+    result = new StringBuffer(title);
+    if (title.length() != 0)
+      result.append("\n");
+    result.append(m_Result);
+    
+    return result.toString();
+  }
+  
+  /**
+   * returns the current result
+   * 
+   * @return		the currently stored result
+   * @see		#toSummaryString()
+   */
+  public String toString() {
+    return toSummaryString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.3 $");
+  }
+
+  /**
+   * A test method for this class. Just extracts the first command line
+   * argument as a kernel class name and calls evaluate.
+   * 
+   * @param args 	an array of command line arguments, the first of which
+   * 			must be the class name of a kernel.
+   */
+  public static void main(String[] args) {
+    try {
+      if (args.length == 0) {
+	throw new Exception(
+	    "The first argument must be the class name of a kernel");
+      }
+      String kernel = args[0];
+      args[0] = "";
+      System.out.println(evaluate(kernel, args));
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/NormalizedPolyKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/NormalizedPolyKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/NormalizedPolyKernel.java	(revision 29)
@@ -0,0 +1,171 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NormalizedPolyKernel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ <!-- globalinfo-start -->
+ * The normalized polynomial kernel.<br/>
+ * K(x,y) = &lt;x,y&gt;/sqrt(&lt;x,x&gt;&lt;y,y&gt;) where &lt;x,y&gt; = PolyKernel(x,y)
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class NormalizedPolyKernel 
+  extends PolyKernel {
+
+  /** for serialization */
+  static final long serialVersionUID = 1248574185532130851L;
+
+  /**
+   * default constructor - does nothing
+   */
+  public NormalizedPolyKernel() {
+    super();
+
+    setExponent(2.0);
+  }
+  
+  /**
+   * Creates a new <code>NormalizedPolyKernel</code> instance.
+   *
+   * @param dataset	the training dataset used.
+   * @param cacheSize	the size of the cache (a prime number)
+   * @param exponent	the exponent to use
+   * @param lowerOrder	whether to use lower-order terms
+   * @throws Exception	if something goes wrong
+   */
+  public NormalizedPolyKernel(Instances dataset, int cacheSize, 
+      double exponent, boolean lowerOrder) throws Exception {
+	
+    super(dataset, cacheSize, exponent, lowerOrder);
+  }
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The normalized polynomial kernel.\n"
+      + "K(x,y) = <x,y>/sqrt(<x,x><y,y>) where <x,y> = PolyKernel(x,y)";
+  }
+   
+  /**
+   * Computes the result of the kernel function for two instances.
+   * If id1 == -1, eval use inst1 instead of an instance in the dataset.
+   * Redefines the eval function of PolyKernel.
+   *
+   * @param id1 the index of the first instance in the dataset
+   * @param id2 the index of the second instance in the dataset
+   * @param inst1 the instance corresponding to id1 (used if id1 == -1)
+   * @return the result of the kernel function
+   * @throws Exception if something goes wrong
+   */
+  public double eval(int id1, int id2, Instance inst1) 
+    throws Exception {
+
+    double div = Math.sqrt(super.eval(id1, id1, inst1) * ((m_keys != null)
+                           ? super.eval(id2, id2, m_data.instance(id2))
+                           : super.eval(-1, -1, m_data.instance(id2))));
+
+    if(div != 0){      
+      return super.eval(id1, id2, inst1) / div;
+    } else {
+      return 0;
+    }
+  }    
+  
+  /**
+   * Sets the exponent value (must be different from 1.0).
+   * 
+   * @param value	the exponent value
+   */
+  public void setExponent(double value) {
+    if (value != 1.0)
+      super.setExponent(value);
+    else
+      System.out.println("A linear kernel, i.e., Exponent=1, is not possible!");
+  }
+  
+  /**
+   * returns a string representation for the Kernel
+   * 
+   * @return 		a string representaiton of the kernel
+   */
+  public String toString() {
+    String	result;
+    
+    if (getUseLowerOrder())
+      result = "Normalized Poly Kernel with lower order: K(x,y) = (<x,y>+1)^" + getExponent() + "/" + 
+      	       "((<x,x>+1)^" + getExponent() + "*" + "(<y,y>+1)^" + getExponent() + ")^(1/2)";
+    else
+      result = "Normalized Poly Kernel: K(x,y) = <x,y>^" + getExponent() + "/" + "(<x,x>^" + 
+               getExponent() + "*" + "<y,y>^" + getExponent() + ")^(1/2)";
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PolyKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PolyKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PolyKernel.java	(revision 29)
@@ -0,0 +1,365 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PolyKernel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * The polynomial kernel : K(x, y) = &lt;x, y&gt;^p or K(x, y) = (&lt;x, y&gt;+1)^p
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @version $Revision: 5807 $
+ */
+public class PolyKernel 
+  extends CachedKernel {
+
+  /** for serialization */
+  static final long serialVersionUID = -321831645846363201L;
+  
+  /** Use lower-order terms? */
+  protected boolean m_lowerOrder = false;
+
+  /** The exponent for the polynomial kernel. */
+  protected double m_exponent = 1.0;
+
+  /**
+   * default constructor - does nothing.
+   */
+  public PolyKernel() {
+    super();
+  }
+
+  /**
+   * Frees the cache used by the kernel.
+   */
+  public void clean() {
+    if (getExponent() == 1.0) {
+      m_data = null;
+    }    
+    super.clean();
+  }
+  
+  /**
+   * Creates a new <code>PolyKernel</code> instance.
+   * 
+   * @param data	the training dataset used.
+   * @param cacheSize	the size of the cache (a prime number)
+   * @param exponent	the exponent to use
+   * @param lowerOrder	whether to use lower-order terms
+   * @throws Exception	if something goes wrong
+   */
+  public PolyKernel(Instances data, int cacheSize, double exponent,
+		    boolean lowerOrder) throws Exception {
+		
+    super();
+    
+    setCacheSize(cacheSize);
+    setExponent(exponent);
+    setUseLowerOrder(lowerOrder);
+
+    buildKernel(data);
+  }
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The polynomial kernel : K(x, y) = <x, y>^p or K(x, y) = (<x, y>+1)^p";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		en;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe Exponent to use.\n"
+	+ "\t(default: 1.0)",
+	"E", 1, "-E <num>"));
+
+    result.addElement(new Option(
+	"\tUse lower-order terms.\n"
+	+ "\t(default: no)",
+	"L", 0, "-L"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setExponent(Double.parseDouble(tmpStr));
+    else
+      setExponent(1.0);
+
+    setUseLowerOrder(Utils.getFlag('L', options));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-E");
+    result.add("" + getExponent());
+
+    if (getUseLowerOrder())
+      result.add("-L");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected double evaluate(int id1, int id2, Instance inst1)
+    throws Exception {
+		
+    double result;
+    if (id1 == id2) {
+      result = dotProd(inst1, inst1);
+    } else {
+      result = dotProd(inst1, m_data.instance(id2));
+    }
+    // Use lower order terms?
+    if (m_lowerOrder) {
+      result += 1.0;
+    }
+    if (m_exponent != 1.0) {
+      result = Math.pow(result, m_exponent);
+    }
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the exponent value.
+   * 
+   * @param value	the exponent value
+   */
+  public void setExponent(double value) {
+    m_exponent = value;
+  }
+  
+  /**
+   * Gets the exponent value.
+   * 
+   * @return		the exponent value
+   */
+  public double getExponent() {
+    return m_exponent;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String exponentTipText() {
+    return "The exponent value.";
+  }
+  
+  /**
+   * Sets whether to use lower-order terms.
+   * 
+   * @param value	true if lower-order terms will be used
+   */
+  public void setUseLowerOrder(boolean value) {
+    m_lowerOrder = value;
+  }
+  
+  /**
+   * Gets whether lower-order terms are used.
+   * 
+   * @return		true if lower-order terms are used
+   */
+  public boolean getUseLowerOrder() {
+    return m_lowerOrder;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useLowerOrderTipText() {
+    return "Whether to use lower-order terms.";
+  }
+  
+  /**
+   * returns a string representation for the Kernel
+   * 
+   * @return 		a string representaiton of the kernel
+   */
+  public String toString() {
+    String	result;
+    
+    if (getExponent() == 1.0) {
+      if (getUseLowerOrder())
+        result = "Linear Kernel with lower order: K(x,y) = <x,y> + 1";
+      else
+        result = "Linear Kernel: K(x,y) = <x,y>";
+    }
+    else {
+      if (getUseLowerOrder())
+	result = "Poly Kernel with lower order: K(x,y) = (<x,y> + 1)^" + getExponent();
+      else
+	result = "Poly Kernel: K(x,y) = <x,y>^" + getExponent();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5807 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernel.java	(revision 29)
@@ -0,0 +1,319 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PrecomputedKernelMatrixKernel.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.matrix.Matrix;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ *
+ <!-- globalinfo-start -->
+ * This kernel is based on a static kernel matrix that is read from a file. Instances must have a single nominal attribute (excluding the class). This attribute must be the first attribute in the file and its values are used to reference rows/columns in the kernel matrix. The second attribute must be the class attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -M &lt;file name&gt;
+ *  The file name of the file that holds the kernel matrix.
+ *  (default: kernelMatrix.matrix)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5450 $
+ */
+public class PrecomputedKernelMatrixKernel extends Kernel {
+
+  /** for serialization */
+  static final long serialVersionUID = -321831645846363333L;
+
+  /** The file holding the kernel matrix. */
+  protected File m_KernelMatrixFile = new File("kernelMatrix.matrix");
+	  
+  /** The kernel matrix. */
+  protected Matrix m_KernelMatrix;
+	  
+  /** A classifier counter. */
+  protected int m_Counter;
+ 
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+      "This kernel is based on a static kernel matrix that is read from a file. " +
+      "Instances must have a single nominal attribute (excluding the class). " +
+      "This attribute must be the first attribute in the file and its values are " +
+      "used to reference rows/columns in the kernel matrix. The second attribute " +
+      "must be the class attribute.";
+  }
+	  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+	    
+    result = new Vector();
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+                                 "\tThe file name of the file that holds the kernel matrix.\n"
+                                 + "\t(default: kernelMatrix.matrix)",
+                                 "M", 1, "-M <file name>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -M &lt;file name&gt;
+   *  The file name of the file that holds the kernel matrix.
+   *  (default: kernelMatrix.matrix)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+	    
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setKernelMatrixFile(new File(tmpStr));
+    else
+      setKernelMatrixFile(new File("kernelMatrix.matrix"));
+	    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector<String>    result;
+    String[]  options;
+
+    result = new Vector<String>();
+	    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-M");
+    result.add("" + getKernelMatrixFile());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  public double eval(int id1, int id2, Instance inst1)
+    throws Exception {
+			
+    if (m_KernelMatrix == null) {
+      throw new IllegalArgumentException("Kernel matrix has not been loaded successfully.");
+    }
+    int index1 = -1;
+    if (id1 > -1) { 
+      index1 = (int)m_data.instance(id1).value(0);
+    } else {
+      index1 = (int)inst1.value(0);
+    }
+    int index2 = (int)m_data.instance(id2).value(0);
+    return m_KernelMatrix.get(index1, index2);
+  }
+	  
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+
+    try {
+      if (m_KernelMatrix == null) {
+        m_KernelMatrix = new Matrix(new FileReader(m_KernelMatrixFile));
+        //        System.err.println("Read kernel matrix.");
+      }
+    } catch (Exception e) {
+      System.err.println("Problem reading matrix from " + m_KernelMatrixFile);
+    }
+    m_Counter++;
+    //    System.err.print("Building classifier: " + m_Counter + "\r");
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+	    
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+	    
+    return result;
+  }
+	  
+  /**
+   * Sets the file holding the kernel matrix
+   * 
+   * @param f	the file holding the matrix
+   */
+  public void setKernelMatrixFile(File f) {
+    m_KernelMatrixFile = f;
+  }
+	  
+  /**
+   * Gets the file containing the kernel matrix.
+   * 
+   * @return		the exponent value
+   */
+  public File getKernelMatrixFile() {
+    return m_KernelMatrixFile;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelMatrixFileTipText() {
+    return "The file holding the kernel matrix.";
+  }
+
+  /**
+   * Set the kernel matrix. This method is used by the
+   * unit test for this class, as it loads at test matrix
+   * as a system resource.
+   *
+   * @param km the kernel matrix to use
+   */
+  protected void setKernelMatrix(Matrix km) {
+    m_KernelMatrix = km;
+  }
+		  
+  /**
+   * returns a string representation for the Kernel
+   * 
+   * @return 		a string representaiton of the kernel
+   */
+  public String toString() {
+    return "Using kernel matrix from file with name: " + getKernelMatrixFile();
+  }
+	  
+  /**
+   * Frees the memory used by the kernel.
+   * (Useful with kernels which use cache.)
+   * This function is called when the training is done.
+   * i.e. after that, eval will be called with id1 == -1.
+   */
+  public void clean() {
+    // do nothing
+  }
+
+  /**
+   * Returns the number of kernel evaluation performed.
+   *
+   * @return the number of kernel evaluation performed.
+   */
+  public int numEvals() {
+    return 0;
+  }
+
+  /**
+   * Returns the number of dot product cache hits.
+   *
+   * @return the number of dot product cache hits, or -1 if not supported by this kernel.
+   */
+  public int numCacheHits() {
+    return 0;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5450 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Puk.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Puk.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/Puk.java	(revision 29)
@@ -0,0 +1,425 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Puk.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * The Pearson VII function-based universal kernel.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * B. Uestuen, W.J. Melssen, L.M.C. Buydens (2006). Facilitating the application of Support Vector Regression by using a universal Pearson VII function based kernel. Chemometrics and Intelligent Laboratory Systems. 81:29-40.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -O &lt;num&gt;
+ *  The Omega parameter.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  The Sigma parameter.
+ *  (default: 1.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @version $Revision: 5450 $
+ */
+public class Puk 
+  extends CachedKernel
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 1682161522559978851L;
+
+  /** The precalculated dotproducts of &lt;inst_i,inst_i&gt; */
+  protected double m_kernelPrecalc[];
+
+  /** Omega for the Puk kernel. */
+  protected double m_omega = 1.0;
+
+  /** Sigma for the Puk kernel. */
+  protected double m_sigma = 1.0;
+
+  /** Cached factor for the Puk kernel. */
+  protected double m_factor = 1.0;
+
+  /**
+   * default constructor - does nothing.
+   */
+  public Puk() {
+    super();
+  }
+  
+  /**
+   * Constructor. Initializes m_kernelPrecalc[].
+   * 
+   * @param data	the data to use
+   * @param cacheSize	the size of the cache
+   * @param omega	the exponent
+   * @param sigma	the bandwidth
+   * @throws Exception	if something goes wrong
+   */
+  public Puk(Instances data, int cacheSize, double omega, double sigma)
+    throws Exception {
+
+    super();
+    
+    setCacheSize(cacheSize);
+    setOmega(omega);
+    setSigma(sigma);
+    
+    buildKernel(data);
+  }
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The Pearson VII function-based universal kernel.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "B. Uestuen and W.J. Melssen and L.M.C. Buydens");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "Facilitating the application of Support Vector Regression by using a universal Pearson VII function based kernel");
+    result.setValue(Field.JOURNAL, "Chemometrics and Intelligent Laboratory Systems");
+    result.setValue(Field.VOLUME, "81");
+    result.setValue(Field.PAGES, "29-40");
+    result.setValue(Field.PDF, "http://www.cac.science.ru.nl/research/publications/PDFs/ustun2006.pdf");
+
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		en;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe Omega parameter.\n"
+	+ "\t(default: 1.0)",
+	"O", 1, "-O <num>"));
+
+    result.addElement(new Option(
+	"\tThe Sigma parameter.\n"
+	+ "\t(default: 1.0)",
+	"S", 1, "-S <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -O &lt;num&gt;
+   *  The Omega parameter.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  The Sigma parameter.
+   *  (default: 1.0)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('O', options);
+    if (tmpStr.length() != 0)
+      setOmega(Double.parseDouble(tmpStr));
+    else
+      setOmega(1.0);
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSigma(Double.parseDouble(tmpStr));
+    else
+      setSigma(1.0);
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-O");
+    result.add("" + getOmega());
+
+    result.add("-S");
+    result.add("" + getSigma());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * returns the dot product
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected double evaluate(int id1, int id2, Instance inst1)
+    throws Exception {
+
+    if (id1 == id2) {
+      return 1.0;
+    } else {
+      double precalc1;
+      if (id1 == -1)
+	precalc1 = dotProd(inst1, inst1);
+      else
+	precalc1 = m_kernelPrecalc[id1];
+      Instance inst2 = m_data.instance(id2);
+      double squaredDifference = -2.0 * dotProd(inst1, inst2) + precalc1 + m_kernelPrecalc[id2];
+      double intermediate = m_factor * Math.sqrt(squaredDifference);
+      double result = 1.0 / Math.pow(1.0 + intermediate * intermediate, getOmega());
+      return result;
+    }
+  }
+    
+  /**
+   * Sets the omega value.
+   * 
+   * @param value	the omega value
+   */
+  public void setOmega(double value) {
+    m_omega  = value;
+    m_factor = computeFactor(m_omega, m_sigma);
+  }
+  
+  /**
+   * Gets the omega value.
+   * 
+   * @return		the omega value
+   */
+  public double getOmega() {
+    return m_omega;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String omegaTipText() {
+    return "The Omega value.";
+  }
+
+  /**
+   * Sets the sigma value.
+   * 
+   * @param value	the sigma value
+   */
+  public void setSigma(double value) {
+    m_sigma  = value;
+    m_factor = computeFactor(m_omega, m_sigma);
+  }
+  
+  /**
+   * Gets the sigma value.
+   * 
+   * @return		the sigma value
+   */
+  public double getSigma() {
+    return m_sigma;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String sigmaTipText() {
+    return "The Sigma value.";
+  }
+
+  /**
+   * computes the factor for curve-fitting (see equation (13) in paper)
+   * 
+   * @param omega	the omega to use
+   * @param sigma	the sigma to use
+   * @return		the factor for curve-fitting
+   */
+  protected double computeFactor(double omega, double sigma) {
+    double root = Math.sqrt(Math.pow(2.0, 1.0 / omega) - 1);
+    return 2.0 * root / sigma;
+  }
+
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+    
+    m_factor        = computeFactor(m_omega, m_sigma);
+    m_kernelPrecalc = new double[data.numInstances()];
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * builds the kernel with the given data. Initializes the kernel cache. 
+   * The actual size of the cache in bytes is (64 * cacheSize).
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong
+   */
+  public void buildKernel(Instances data) throws Exception {
+    // does kernel handle the data?
+    if (!getChecksTurnedOff())
+      getCapabilities().testWithFail(data);
+    
+    initVars(data);
+
+    for (int i = 0; i < data.numInstances(); i++)
+      m_kernelPrecalc[i] = dotProd(data.instance(i), data.instance(i));
+  }
+  
+  /**
+   * returns a string representation for the Kernel
+   * 
+   * @return 		a string representaiton of the kernel
+   */
+  public String toString() {
+    return "Puk kernel";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5450 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RBFKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RBFKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RBFKernel.java	(revision 29)
@@ -0,0 +1,323 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RBFKernel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *    Copyright (C) 2005 J. Lindgren
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Capabilities;
+import weka.core.Capabilities.Capability;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * The RBF kernel. K(x, y) = e^-(gamma * &lt;x-y, x-y&gt;^2)
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -G &lt;num&gt;
+ *  The Gamma parameter.
+ *  (default: 0.01)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @author J. Lindgren (jtlindgr{at}cs.helsinki.fi) (RBF kernel)
+ * @version $Revision: 5450 $
+ */
+public class RBFKernel 
+  extends CachedKernel {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5247117544316387852L;
+
+  /** The precalculated dotproducts of &lt;inst_i,inst_i&gt; */
+  protected double m_kernelPrecalc[];
+
+  /** Gamma for the RBF kernel. */
+  protected double m_gamma = 0.01;
+
+  /**
+   * default constructor - does nothing.
+   */
+  public RBFKernel() {
+    super();
+  }
+  
+  /**
+   * Constructor. Initializes m_kernelPrecalc[].
+   * 
+   * @param data	the data to use
+   * @param cacheSize	the size of the cache
+   * @param gamma	the bandwidth
+   * @throws Exception	if something goes wrong
+   */
+  public RBFKernel(Instances data, int cacheSize, double gamma)
+    throws Exception {
+
+    super();
+    
+    setCacheSize(cacheSize);
+    setGamma(gamma);
+    
+    buildKernel(data);
+  }
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The RBF kernel. K(x, y) = e^-(gamma * <x-y, x-y>^2)";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		en;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe Gamma parameter.\n"
+	+ "\t(default: 0.01)",
+	"G", 1, "-G <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -G &lt;num&gt;
+   *  The Gamma parameter.
+   *  (default: 0.01)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('G', options);
+    if (tmpStr.length() != 0)
+      setGamma(Double.parseDouble(tmpStr));
+    else
+      setGamma(0.01);
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-G");
+    result.add("" + getGamma());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected double evaluate(int id1, int id2, Instance inst1)
+    throws Exception {
+
+    if (id1 == id2) {
+      return 1.0;
+    } else {
+      double precalc1;
+      if (id1 == -1)
+        precalc1 = dotProd(inst1, inst1);
+      else
+        precalc1 = m_kernelPrecalc[id1];
+      Instance inst2 = m_data.instance(id2);
+      double result = Math.exp(m_gamma
+  			     * (2. * dotProd(inst1, inst2) - precalc1 - m_kernelPrecalc[id2]));
+      
+      return result;
+    }
+  }
+    
+  /**
+   * Sets the gamma value.
+   * 
+   * @param value	the gamma value
+   */
+  public void setGamma(double value) {
+    m_gamma = value;
+  }
+  
+  /**
+   * Gets the gamma value.
+   * 
+   * @return		the gamma value
+   */
+  public double getGamma() {
+    return m_gamma;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String gammaTipText() {
+    return "The Gamma value.";
+  }
+
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+    
+    m_kernelPrecalc = new double[data.numInstances()];
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * builds the kernel with the given data. Initializes the kernel cache. 
+   * The actual size of the cache in bytes is (64 * cacheSize).
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong
+   */
+  public void buildKernel(Instances data) throws Exception {
+    // does kernel handle the data?
+    if (!getChecksTurnedOff())
+      getCapabilities().testWithFail(data);
+    
+    initVars(data);
+    
+    for (int i = 0; i < data.numInstances(); i++)
+      m_kernelPrecalc[i] = dotProd(data.instance(i), data.instance(i));
+  }
+  
+  /**
+   * returns a string representation for the Kernel
+   * 
+   * @return 		a string representaiton of the kernel
+   */
+  public String toString() {
+    return "RBF kernel: K(x,y) = e^-(" + getGamma() + "* <x-y,x-y>^2)";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5450 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegOptimizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegOptimizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegOptimizer.java	(revision 29)
@@ -0,0 +1,563 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RegOptimizer.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.SMOreg;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Base class implementation for learning algorithm of SMOreg
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The epsilon parameter in epsilon-insensitive loss function.
+ *  (default 1.0e-3)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Remco Bouckaert (remco@cs.waikato.ac.nz,rrb@xm.co.nz)
+ * @version $Revision: 5434 $
+ */
+public class RegOptimizer 
+  implements OptionHandler, Serializable, RevisionHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -2198266997254461814L;
+  
+  /** loss type **/
+  //protected int m_nLossType = EPSILON;
+  
+  /** the loss type: L1 */
+  //public final static int L1 = 1;
+  /** the loss type: L2 */
+  //public final static int L2 = 2;
+  /** the loss type: HUBER */
+  //public final static int HUBER = 3;
+  /** the loss type: EPSILON */
+  //public final static int EPSILON = 4;
+  /** the loss type */
+  //public static final Tag[] TAGS_LOSS_TYPE = {
+  //  new Tag(L2, "L2"),
+  //  new Tag(L1, "L1"),
+  //  new Tag(HUBER, "Huber"),
+  //  new Tag(EPSILON, "EPSILON"),
+  //};
+  /** alpha and alpha* arrays containing weights for solving dual problem **/
+  public double[] m_alpha;
+  public double[] m_alphaStar;
+  
+  /** offset **/
+  protected double m_b;
+  
+  /** epsilon of epsilon-insensitive cost function **/
+  protected double m_epsilon = 1e-3;
+  
+  /** capacity parameter, copied from SMOreg **/
+  protected double m_C = 1.0;
+  
+  /** class values/desired output vector **/
+  protected double[] m_target;
+  
+  /** points to data set **/
+  protected Instances m_data;
+  
+  /** the kernel */
+  protected Kernel m_kernel;
+  
+  /** index of class variable in data set **/
+  protected int m_classIndex = -1;
+  
+  /** number of instances in data set **/
+  protected int m_nInstances = -1;
+  
+  /** random number generator **/
+  protected Random m_random;
+  
+  /** seed for initializing random number generator **/
+  protected int m_nSeed = 1;
+  
+  /** set of support vectors, that is, vectors with alpha(*)!=0 **/
+  protected SMOset m_supportVectors;
+  
+  /** number of kernel evaluations, used for printing statistics only **/
+  protected int m_nEvals = 0;
+
+  /** number of kernel cache hits, used for printing statistics only **/
+  protected int m_nCacheHits = -1;
+  
+  /** weights for linear kernel **/
+  protected double[] m_weights;
+  
+  /** Variables to hold weight vector in sparse form.
+   (To reduce storage requirements.) */
+  protected double[] m_sparseWeights;
+  protected int[] m_sparseIndices;
+  
+  /** flag to indicate whether the model is built yet **/
+  protected boolean m_bModelBuilt = false;
+
+  /** parent SMOreg class **/
+  protected SMOreg m_SVM = null;
+  
+  /**
+   * the default constructor
+   */
+  public RegOptimizer() {
+    super();
+    m_random = new Random(m_nSeed);
+  }
+  
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tThe epsilon parameter in epsilon-insensitive loss function.\n" 
+	+ "\t(default 1.0e-3)", 
+	"L", 1, "-L <double>"));
+    
+//  result.addElement(new Option(
+//      "\tLoss type (L1, L2, Huber, Epsilon insensitive loss)\n",
+//      "L", 1, "-L [L1|L2|HUBER|EPSILON]"));
+    
+    result.addElement(new Option(
+	"\tThe random number seed.\n" 
+	+ "\t(default 1)", 
+	"W", 1, "-W <double>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The epsilon parameter in epsilon-insensitive loss function.
+   *  (default 1.0e-3)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0) {
+      setEpsilonParameter(Double.parseDouble(tmpStr));
+    } else {
+      setEpsilonParameter(1.0e-3);
+    }
+    
+    /*
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setLossType(new SelectedTag(tmpStr, TAGS_LOSS_TYPE));
+    else
+      setLossType(new SelectedTag(EPSILON, TAGS_LOSS_TYPE));
+    */
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() != 0) {
+      setSeed(Integer.parseInt(tmpStr));
+    } else {
+      setSeed(1);
+    }
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector    	result;
+
+    result = new Vector();
+
+    result.add("-L");
+    result.add("" + getEpsilonParameter());
+    
+    result.add("-W");
+    result.add("" + getSeed());
+    
+    //result.add("-S";
+    //result.add((new SelectedTag(m_nLossType, TAGS_LOSS_TYPE)).getSelectedTag().getReadable();
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * flag to indicate whether the model was built yet
+   * 
+   * @return		true if the model was built
+   */
+  public boolean modelBuilt() {
+    return m_bModelBuilt;
+  }
+
+  /**
+   * sets the parent SVM
+   * 
+   * @param value	the parent SVM
+   */
+  public void setSMOReg(SMOreg value) {
+    m_SVM = value;
+  }
+
+  /**
+   * returns the number of kernel evaluations
+   * 
+   * @return		the number of kernel evaluations
+   */
+  public int getKernelEvaluations() {
+    return m_nEvals;
+  }
+
+  /**
+   * return the number of kernel cache hits
+   * 
+   * @return		the number of hits
+   */
+  public int getCacheHits() {
+    return m_nCacheHits;
+  }
+
+  /**
+   * initializes the algorithm
+   * 
+   * @param data	the data to work with
+   * @throws Exception 	if m_SVM is null
+   */
+  protected void init(Instances data) throws Exception {
+    if (m_SVM == null) {
+      throw new Exception ("SVM not initialized in optimizer. Use RegOptimizer.setSVMReg()");
+    }
+    m_C = m_SVM.getC();
+    m_data = data;
+    m_classIndex = data.classIndex();
+    m_nInstances = data.numInstances();
+    
+    // Initialize kernel
+    m_kernel = Kernel.makeCopy(m_SVM.getKernel());
+    m_kernel.buildKernel(data);
+    
+    //init m_target
+    m_target = new double[m_nInstances];
+    for (int i = 0; i < m_nInstances; i++) {
+      m_target[i] = data.instance(i).classValue();
+    }
+    
+    m_random = new Random(m_nSeed);
+    
+    //		initialize alpha and alpha* array to all zero 
+    m_alpha = new double[m_target.length];
+    m_alphaStar = new double[m_target.length];
+    
+    m_supportVectors = new SMOset(m_nInstances);
+    
+    m_b = 0.0;
+    m_nEvals = 0;
+    m_nCacheHits = -1;
+  }
+  
+  /** 
+   * wrap up various variables to save memeory and do some housekeeping after optimization
+   * has finished.
+   *
+   * @throws Exception	if something goes wrong
+   */
+  protected void wrapUp() throws Exception {
+    m_target = null;
+    
+    m_nEvals = m_kernel.numEvals();
+    m_nCacheHits = m_kernel.numCacheHits();
+    
+    if ((m_SVM.getKernel() instanceof PolyKernel) && ((PolyKernel) m_SVM.getKernel()).getExponent() == 1.0) {
+      // convert alpha's to weights
+      double [] weights = new double[m_data.numAttributes()];
+      for (int k = m_supportVectors.getNext(-1); k != -1; k = m_supportVectors.getNext(k)) {
+	for (int j = 0; j < weights.length; j++) {
+	  if (j != m_classIndex) {
+	    weights[j] += (m_alpha[k] - m_alphaStar[k]) * m_data.instance(k).value(j);
+	  }
+	}
+      }
+      m_weights = weights;
+      
+      // release memory
+      m_alpha = null;
+      m_alphaStar = null;
+      m_kernel = null;
+      
+    }
+    m_bModelBuilt = true;
+  }
+  
+  /**
+   * Compute the value of the objective function.
+   * 
+   * @return		the score
+   * @throws Exception	if something goes wrong
+   */
+  protected double getScore() throws Exception {
+    double res = 0;
+    double t = 0, t2 = 0;
+    double sumAlpha = 0.0;
+    for (int i = 0; i < m_nInstances; i++) {
+      sumAlpha += (m_alpha[i] - m_alphaStar[i]);
+      for (int j = 0; j < m_nInstances; j++) {
+	t += (m_alpha[i] - m_alphaStar[i]) * (m_alpha[j] - m_alphaStar[j]) * m_kernel.eval(i, j, m_data.instance(i));
+      }
+//    switch(m_nLossType) {
+//    case L1:
+//    t2 += m_data.instance(i).classValue() * (m_alpha[i] - m_alpha_[i]);
+//    break;
+//    case L2:
+//    t2 += m_data.instance(i).classValue() * (m_alpha[i] - m_alpha_[i]) - (0.5/m_SVM.getC()) * (m_alpha[i]*m_alpha[i] + m_alpha_[i]*m_alpha_[i]);
+//    break;
+//    case HUBER:
+//    t2 += m_data.instance(i).classValue() * (m_alpha[i] - m_alpha_[i]) - (0.5*m_SVM.getEpsilon()/m_SVM.getC()) * (m_alpha[i]*m_alpha[i] + m_alpha_[i]*m_alpha_[i]);
+//    break;
+//    case EPSILON:
+      //t2 += m_data.instance(i).classValue() * (m_alpha[i] - m_alphaStar[i]) - m_epsilon * (m_alpha[i] + m_alphaStar[i]);
+      t2 += m_target[i] * (m_alpha[i] - m_alphaStar[i]) - m_epsilon * (m_alpha[i] + m_alphaStar[i]);
+//    break;
+//    }
+    }
+    res += -0.5 * t + t2;
+    return res;
+  }
+  
+  /** 
+   * learn SVM parameters from data.
+   * Subclasses should implement something more interesting.
+   * 
+   * @param data	the data to work with
+   * @throws Exception	always an Exceoption since subclasses must override it
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    throw new Exception("Don't call this directly, use subclass instead");
+  }
+  
+  /**
+   * sets the loss type type to use
+   * 
+   * @param newLossType	the loss type to use
+   */
+  //public void setLossType(SelectedTag newLossType) {
+  //  if (newLossType.getTags() == TAGS_LOSS_TYPE) {
+  //    m_nLossType = newLossType.getSelectedTag().getID();
+  //   }
+  //}
+   
+  /**
+   * returns the current loss type
+   * 
+   * @return		the loss type
+   */
+  //public SelectedTag getLossType() {
+  //  return new SelectedTag(m_nLossType, TAGS_LOSS_TYPE);
+  //}
+  
+  /** 
+   * SVMOutput of an instance in the training set, m_data
+   * This uses the cache, unlike SVMOutput(Instance)
+   * 
+   * @param index 	index of the training instance in m_data
+   * @return		the SVM output
+   * @throws Exception	if something goes wrong
+   */
+  protected double SVMOutput(int index) throws Exception {
+    double result = -m_b;
+    for (int i = m_supportVectors.getNext(-1); i != -1; i = m_supportVectors.getNext(i)) {
+      result += (m_alpha[i] - m_alphaStar[i]) * m_kernel.eval(index, i, m_data.instance(index));
+    }
+    return result;
+  }
+  
+  /**
+   * 
+   * @param inst
+   * @return
+   * @throws Exception
+   */
+  public double SVMOutput(Instance inst) throws Exception {
+    
+    double result = -m_b;
+    // Is the machine linear?
+    if (m_weights != null) {
+      // Is weight vector stored in sparse format?
+      for (int i = 0; i < m_weights.length; i++) {
+	if (inst.index(i) != m_classIndex) {
+	  result += m_weights[inst.index(i)] * inst.valueSparse(i);
+	}
+      }
+    } else {
+      for (int i = m_supportVectors.getNext(-1); i != -1; i = m_supportVectors.getNext(i)) {
+	result += (m_alpha[i] - m_alphaStar[i]) * m_kernel.eval(-1, i, inst);
+      }
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Seed for random number generator.";
+  }
+  
+  /**
+   * Gets the current seed value for the random number generator
+   * 
+   * @return		the seed value
+   */
+  public int getSeed() {
+    return m_nSeed;
+  }
+  
+  /**
+   * Sets the seed value for the random number generator
+   * 
+   * @param value	the seed value
+   */
+  public void setSeed(int value) {
+    m_nSeed = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String epsilonParameterTipText() {
+    return "The epsilon parameter of the epsilon insensitive loss function.(default 0.001).";
+  }
+  
+  /**
+   * Get the value of epsilon parameter of the epsilon insensitive loss function.
+   * 
+   * @return 		Value of epsilon parameter.
+   */
+  public double getEpsilonParameter() {
+    return m_epsilon;
+  }
+  
+  /**
+   * Set the value of epsilon parameter of the epsilon insensitive loss function.
+   * 
+   * @param v  		Value to assign to epsilon parameter.
+   */
+  public void setEpsilonParameter(double v) {
+    m_epsilon = v;
+  }
+  
+  /**
+   * Prints out the classifier.
+   *
+   * @return 		a description of the classifier as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    text.append("SMOreg\n\n");
+    if (m_weights != null) {
+      text.append("weights (not support vectors):\n");
+      // it's a linear machine
+      for (int i = 0; i < m_data.numAttributes(); i++) {
+	if (i != m_classIndex) {
+	  text.append((m_weights[i] >= 0 ? " + " : " - ") + Utils.doubleToString(Math.abs(m_weights[i]), 12, 4) + " * ");
+	  if (m_SVM.getFilterType().getSelectedTag().getID() == SMOreg.FILTER_STANDARDIZE) {
+	    text.append("(standardized) ");
+	  } else if (m_SVM.getFilterType().getSelectedTag().getID() == SMOreg.FILTER_NORMALIZE) {
+	    text.append("(normalized) ");
+	  }
+	  text.append(m_data.attribute(i).name() + "\n");
+	}
+      }
+    } else {
+      // non linear, print out all supportvectors
+      text.append("Support vectors:\n");
+      for (int i = 0; i < m_nInstances; i++) {
+	if (m_alpha[i] > 0) {
+	  text.append("+" + m_alpha[i] + " * k[" + i + "]\n");
+	}
+	if (m_alphaStar[i] > 0) {
+	  text.append("-" + m_alphaStar[i] + " * k[" + i + "]\n");
+	}
+      }
+    }
+    
+    text.append((m_b<=0?" + ":" - ") + Utils.doubleToString(Math.abs(m_b), 12, 4) + "\n\n");
+    
+    text.append("\n\nNumber of kernel evaluations: " + m_nEvals);
+    if (m_nCacheHits >= 0 && m_nEvals > 0) {
+      double hitRatio = 1 - m_nEvals * 1.0 / (m_nCacheHits + m_nEvals);
+      text.append(" (" + Utils.doubleToString(hitRatio * 100, 7, 3).trim() + "% cached)");
+    }
+    
+    return text.toString();		
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5434 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMO.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMO.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMO.java	(revision 29)
@@ -0,0 +1,882 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RegSMO.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implementation of SMO for support vector regression as described in :<br/>
+ * <br/>
+ * A.J. Smola, B. Schoelkopf (1998). A tutorial on support vector regression.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Smola1998,
+ *    author = {A.J. Smola and B. Schoelkopf},
+ *    note = {NeuroCOLT2 Technical Report NC2-TR-1998-030},
+ *    title = {A tutorial on support vector regression},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error.
+ *  (default 1.0e-12)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The epsilon parameter in epsilon-insensitive loss function.
+ *  (default 1.0e-3)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Remco Bouckaert (remco@cs.waikato.ac.nz,rrb@xm.co.nz)
+ * @version $Revision: 1.4 $
+ */
+public class RegSMO 
+  extends RegOptimizer
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -7504070793279598638L;
+
+  /** tolerance parameter, smaller changes on alpha in inner loop will be ignored **/
+  protected double m_eps = 1.0e-12;
+  
+  /** Precision constant for updating sets */
+  protected final static double m_Del = 1e-10; //1000 * Double.MIN_VALUE;
+  
+  /** error cache containing m_error[i] = SVMOutput(i) - m_target[i] - m_b <br/>
+   * note, we don't need m_b in the cache, since if we do, we need to maintain 
+   * it when m_b is updated */
+  double[] m_error;
+  
+  /** alpha value for first candidate **/  
+  protected double m_alpha1;
+  
+  /** alpha* value for first candidate **/  
+  protected double m_alpha1Star;
+
+  /** alpha value for second candidate **/  
+  protected double m_alpha2;
+  
+  /** alpha* value for second candidate **/
+  protected double m_alpha2Star;
+  
+  /**
+   * default constructor
+   */
+  public RegSMO() {
+    super();
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Implementation of SMO for support vector regression as described "
+      + "in :\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "A.J. Smola and B. Schoelkopf");
+    result.setValue(Field.TITLE, "A tutorial on support vector regression");
+    result.setValue(Field.NOTE, "NeuroCOLT2 Technical Report NC2-TR-1998-030");
+    result.setValue(Field.YEAR, "1998");
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options
+   * 
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tThe epsilon for round-off error.\n" 
+	+ "\t(default 1.0e-12)", 
+	"P", 1, "-P <double>"));
+    
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements()) {
+      result.addElement(enm.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error.
+   *  (default 1.0e-12)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The epsilon parameter in epsilon-insensitive loss function.
+   *  (default 1.0e-3)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0) {
+      setEpsilon(Double.parseDouble(tmpStr));
+    } else {
+      setEpsilon(1.0e-12);
+    }
+    
+    super.setOptions(options);
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-P");
+    result.add("" + getEpsilon());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String epsilonTipText() {
+    return "The epsilon for round-off error (shouldn't be changed).";
+  }
+  
+  /**
+   * Get the value of epsilon.
+   * 
+   * @return 		Value of epsilon.
+   */
+  public double getEpsilon() {
+    return m_eps;
+  }
+  
+  /**
+   * Set the value of epsilon.
+   * 
+   * @param v  		Value to assign to epsilon.
+   */
+  public void setEpsilon(double v) {
+    m_eps = v;
+  }
+  
+  /** initialize various variables before starting the actual optimizer 
+   * 
+   * @param data 	data set used for learning
+   * @throws Exception	if something goes wrong
+   */
+  protected void init(Instances data) throws Exception {
+    super.init(data);
+    
+    //init error cache
+    m_error = new double[m_nInstances];
+    for (int i = 0; i < m_nInstances; i++) {
+      m_error[i] = -m_target[i];
+    }
+  }
+  
+  /** 
+   * wrap up various variables to save memeory and do some housekeeping after optimization
+   * has finished.
+   *
+   * @throws Exception 	if something goes wrong
+   */
+  protected void wrapUp() throws Exception {
+    m_error = null;
+    super.wrapUp();
+  }
+
+  /** 
+   * Finds optimal point on line constrained by first (i1) and second (i2) 
+   * candidate. Parameters correspond to pseudocode (see technicalinformation)
+   * 
+   * @param i1
+   * @param alpha1
+   * @param alpha1Star
+   * @param C1
+   * @param i2
+   * @param alpha2
+   * @param alpha2Star
+   * @param C2
+   * @param gamma
+   * @param eta
+   * @param deltaPhi
+   * @return
+   */
+  protected boolean findOptimalPointOnLine(int i1, double alpha1, double alpha1Star, double C1, 
+      int i2, double alpha2, double alpha2Star, double C2, 
+      double gamma, double eta, double deltaPhi) {
+    if (eta <= 0) {
+      // this may happen due to numeric instability
+      // due to Mercer's condition, this should not happen, hence we give up
+      return false;
+    }
+    
+    boolean case1 = false;
+    boolean case2 = false;
+    boolean case3 = false;
+    boolean case4 = false;
+    boolean finished = false;
+    
+    //		while !finished 
+    //		% this loop is passed at most three times 
+    //		% case variables needed to avoid attempting small changes twice 
+    while (!finished) {
+      //			if (case1 == 0) && 
+      //				(alpha1 > 0 || (alpha1* == 0 && deltaPhi > 0)) && 
+      //				(alpha2 > 0 || (alpha2* == 0 && deltaPhi < 0)) 
+      //				compute L, H (wrt. alpha1, alpha2) 
+      //				if L < H 
+      //					a2 = alpha2 ? - deltaPhi/eta 
+      //					a2 = min(a2, H) 
+      //					a2 = max(L, a2) 
+      //					a1 = alpha1 ? - (a2 ? alpha2) 
+      //					update alpha1, alpha2 if change is larger than some eps 
+      //				else 
+      //					finished = 1 
+      //				endif 
+      //				case1 = 1; 
+      
+      if ((case1 == false) && 
+	  (alpha1 > 0 || (alpha1Star == 0 && deltaPhi > 0)) && 
+	  (alpha2 > 0 || (alpha2Star == 0 && deltaPhi < 0))) {
+	// compute L, H (wrt. alpha1, alpha2) 
+	double L = Math.max(0, gamma - C1);
+	double H = Math.min(C2, gamma);
+	if (L < H) {
+	  double a2 = alpha2 - deltaPhi / eta;
+	  a2 = Math.min(a2, H);
+	  a2 = Math.max(L, a2);
+	  // To prevent precision problems
+	  if (a2 > C2 - m_Del * C2) {
+	    a2 = C2;
+	  } else if (a2 <= m_Del * C2) {
+	    a2 = 0;
+	  }
+	  double a1 = alpha1 - (a2 - alpha2);
+	  if (a1 > C1 - m_Del * C1) {
+	    a1 = C1;
+	  } else if (a1 <= m_Del * C1) {
+	    a1 = 0;
+	  }
+	  // update alpha1, alpha2 if change is larger than some eps
+	  if (Math.abs(alpha1 - a1) > m_eps) {
+	    deltaPhi += eta * (a2 - alpha2);
+	    alpha1 = a1;
+	    alpha2 = a2;
+	  }
+	} else {
+	  finished = true;
+	}
+	case1 = true;
+      }
+      
+      //			elseif (case2 == 0) && 
+      //				(alpha1 > 0 || (alpha1* == 0 && deltaPhi > 2 epsilon)) && 
+      //				(alpha2* > 0 || (alpha2 == 0 && deltaPhi > 2 epsilon)) 
+      //				compute L, H (wrt. alpha1, alpha2*) 
+      //				if L < H 
+      //					a2 = alpha2* + (deltaPhi ?- 2 epsilon)/eta 
+      //					a2 = min(a2, H) 
+      //					a2 = max(L, a2) 
+      //					a1 = alpha1 + (a2 ? alpha2*) 
+      //					update alpha1, alpha2* if change is larger than some eps 
+      //				else 
+      //					finished = 1 
+      //				endif 
+      //				case2 = 1; 
+      
+      else if (
+	  (case2 == false)
+	  && (alpha1 > 0 || (alpha1Star == 0 && deltaPhi > 2 * m_epsilon))
+	  && (alpha2Star > 0 || (alpha2 == 0 && deltaPhi > 2 * m_epsilon))) {
+	// compute L, H (wrt. alpha1, alpha2*) 
+	double L = Math.max(0, -gamma);
+	double H = Math.min(C2, -gamma + C1);
+	if (L < H) {
+	  double a2 = alpha2Star + (deltaPhi - 2 * m_epsilon) / eta;
+	  a2 = Math.min(a2, H);
+	  a2 = Math.max(L, a2);
+	  // To prevent precision problems
+	  if (a2 > C2 - m_Del * C2) {
+	    a2 = C2;
+	  } else if (a2 <= m_Del * C2) {
+	    a2 = 0;
+	  }
+	  double a1 = alpha1 + (a2 - alpha2Star);
+	  if (a1 > C1 - m_Del * C1) {
+	    a1 = C1;
+	  } else if (a1 <= m_Del * C1) {
+	    a1 = 0;
+	  }
+	  // update alpha1, alpha2* if change is larger than some eps 
+	  if (Math.abs(alpha1 - a1) > m_eps) {
+	    deltaPhi += eta * (-a2 + alpha2Star);
+	    alpha1 = a1;
+	    alpha2Star = a2;
+	  }
+	} else {
+	  finished = true;
+	}
+	case2 = true;
+      }
+      
+      //			elseif (case3 == 0) && 
+      //				(alpha1* > 0 || (alpha1 == 0 && deltaPhi < -2 epsilon)) && 
+      //				(alpha2 > 0 || (alpha2* == 0 && deltaPhi < -2 epsilon)) 
+      //				compute L, H (wrt. alpha1*, alpha2) 
+      //				if L < H 
+      //					a2 = alpha2 ?- (deltaPhi ?+ 2 epsilon)/eta 
+      //					a2 = min(a2, H) 
+      //					a2 = max(L, a2) 
+      //					a1 = alpha1* + (a2 ? alpha2) 
+      //					update alpha1*, alpha2 if change is larger than some eps 
+      //				else 
+      //					finished = 1 
+      //				endif 
+      //				case3 = 1; 
+      
+      else if (
+	  (case3 == false)
+	  && (alpha1Star > 0 || (alpha1 == 0 && deltaPhi < - 2 * m_epsilon))
+	  && (alpha2 > 0 || (alpha2Star == 0 && deltaPhi < - 2 * m_epsilon))) {
+	// compute L, H (wrt. alpha1*, alpha2)
+	double L = Math.max(0, gamma);
+	double H = Math.min(C2, C1 + gamma);
+	if (L < H) {
+	  // note Smola's psuedocode has a minus, where there should be a plus in the following line, Keerthi's is correct
+	  double a2 = alpha2 - (deltaPhi + 2 * m_epsilon) / eta;
+	  a2 = Math.min(a2, H);
+	  a2 = Math.max(L, a2);
+	  // To prevent precision problems
+	  if (a2 > C2 - m_Del * C2) {
+	    a2 = C2;
+	  } else if (a2 <= m_Del * C2) {
+	    a2 = 0;
+	  }
+	  double a1 = alpha1Star + (a2 - alpha2);
+	  if (a1 > C1 - m_Del * C1) {
+	    a1 = C1;
+	  } else if (a1 <= m_Del * C1) {
+	    a1 = 0;
+	  }
+	  // update alpha1*, alpha2 if change is larger than some eps 
+	  if (Math.abs(alpha1Star - a1) > m_eps) {
+	    deltaPhi += eta * (a2 - alpha2);
+	    alpha1Star = a1;
+	    alpha2 = a2;
+	  }
+	} else {
+	  finished = true;
+	}
+	case3 = true;
+      }
+      
+      //			elseif (case4 == 0) && 
+      //				(alpha1* > 0 || (alpha1 == 0 && deltaPhi < 0)) && 
+      //				(alpha2* > 0 || (alpha2 == 0 && deltaPhi > 0)) 
+      //				compute L, H (wrt. alpha1*, alpha2*) 
+      //				if L < H 
+      //					a2 = alpha2* + deltaPhi/eta 
+      //					a2 = min(a2, H) 
+      //					a2 = max(L, a2) 
+      //					a1 = alpha1* ? (a2 ? alpha2*) 
+      //					update alpha1*, alpha2* if change is larger than some eps 
+      //				else 
+      //					finished = 1 
+      //				endif 
+      //				case4 = 1; 
+      //			else 
+      //				finished = 1 
+      //			endif 
+      
+      else if ((case4 == false) && 
+	  (alpha1Star > 0 || (alpha1 == 0 && deltaPhi < 0)) && 
+	  (alpha2Star > 0 || (alpha2 == 0 && deltaPhi > 0))) {
+	// compute L, H (wrt. alpha1*, alpha2*) 
+	double L = Math.max(0, -gamma - C1);
+	double H = Math.min(C2, -gamma);
+	if (L < H) {
+	  double a2 = alpha2Star + deltaPhi / eta;
+	  a2 = Math.min(a2, H);
+	  a2 = Math.max(L, a2);
+	  // To prevent precision problems
+	  if (a2 > C2 - m_Del * C2) {
+	    a2 = C2;
+	  } else if (a2 <= m_Del * C2) {
+	    a2 = 0;
+	  }
+	  double a1 = alpha1Star - (a2 - alpha2Star);
+	  if (a1 > C1 - m_Del * C1) {
+	    a1 = C1;
+	  } else if (a1 <= m_Del * C1) {
+	    a1 = 0;
+	  }
+	  // update alpha1*, alpha2* if change is larger than some eps 
+	  if (Math.abs(alpha1Star - a1) > m_eps) {
+	    deltaPhi += eta * (-a2 + alpha2Star);
+	    
+	    alpha1Star = a1;
+	    alpha2Star = a2;
+	  }
+	} else {
+	  finished = true;
+	}
+	case4 = true;
+      } else {
+	finished = true;
+      }
+      
+      //			update deltaPhi
+      // using 4.36 from Smola's thesis:
+      // deltaPhi = deltaPhi - eta * ((alpha1New-alpha1StarNew)-(alpha1-alpha1Star));
+      // the update is done inside the loop, saving us to remember old values of alpha1(*)
+      //deltaPhi += eta * ((alpha2 - alpha2Star) - dAlpha2Old);
+      //dAlpha2Old = (alpha2 - alpha2Star);
+      
+      //		endwhile 
+      
+    }
+    
+    if (Math.abs(alpha1 - m_alpha[i1]) > m_eps
+	|| Math.abs(alpha1Star - m_alphaStar[i1]) > m_eps
+	|| Math.abs(alpha2 - m_alpha[i2]) > m_eps
+	|| Math.abs(alpha2Star - m_alphaStar[i2]) > m_eps) {
+      
+      if (alpha1 > C1 - m_Del * C1) {
+	alpha1 = C1;
+      } else if (alpha1 <= m_Del * C1) {
+	alpha1 = 0;
+      }
+      if (alpha1Star > C1 - m_Del * C1) {
+	alpha1Star = C1;
+      } else if (alpha1Star <= m_Del * C1) {
+	alpha1Star = 0;
+      }
+      if (alpha2 > C2 - m_Del * C2) {
+	alpha2 = C2;
+      } else if (alpha2 <= m_Del * C2) {
+	alpha2 = 0;
+      }
+      if (alpha2Star > C2 - m_Del * C2) {
+	alpha2Star = C2;
+      } else if (alpha2Star <= m_Del * C2) {
+	alpha2Star = 0;
+      }
+      
+      // store new alpha's
+      m_alpha[i1] = alpha1;
+      m_alphaStar[i1] = alpha1Star;
+      m_alpha[i2] = alpha2;
+      m_alphaStar[i2] = alpha2Star;
+      
+      // update supportvector set
+      if (alpha1 != 0 || alpha1Star != 0){
+	if (!m_supportVectors.contains(i1)) {
+	  m_supportVectors.insert(i1);	
+	}
+      } else {
+	m_supportVectors.delete(i1);
+      }
+      if (alpha2 != 0 || alpha2Star != 0){
+	if (!m_supportVectors.contains(i2)) {
+	  m_supportVectors.insert(i2);
+	}
+      } else {
+	m_supportVectors.delete(i2);
+      }
+      return true;
+    }
+    
+    return false;
+  }
+
+  /** 
+   * takeStep method from pseudocode.
+   * Parameters correspond to pseudocode (see technicalinformation)
+   * 
+   * @param i1
+   * @param i2
+   * @param alpha2
+   * @param alpha2Star
+   * @param phi2
+   * @return
+   * @throws Exception
+   */
+  protected int takeStep(int i1, int i2, double alpha2, double alpha2Star, double phi2) throws Exception {
+    //		if (i1 == i2) return 0 
+    if (i1 == i2) {
+      return 0;
+    }
+    double C1 = m_C * m_data.instance(i1).weight();
+    double C2 = m_C * m_data.instance(i2).weight();
+    //		alpha1, alpha1* = Lagrange multipliers for i1 
+    //		y1 = target[i1] 
+    //		phi1 = SVM output on point[i1] ? y1 (in error cache) 
+    double alpha1 = m_alpha[i1];
+    double alpha1Star = m_alphaStar[i1];
+    double y1 = m_target[i1];
+    double phi1 = m_error[i1];
+    
+    //		k11 = kernel(point[i1],point[i1]) 
+    //		k12 = kernel(point[i1],point[i2]) 
+    //		k22 = kernel(point[i2],point[i2]) 
+    //		eta = 2*k12? - k11? - k22 
+    //		gamma = alpha1 ?- alpha1* + alpha2 ?- alpha2* 
+    
+    double k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
+    double k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
+    double k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
+    double eta = -2 * k12 + k11 + k22; // note, Smola's psuedocode has signs swapped, Keerthi's doesn't
+    if (eta < 0) {
+      // this may happen due to numeric instability
+      // due to Mercer's condition, this should not happen, hence we give up
+      return 0;
+    }
+    double gamma = alpha1 - alpha1Star + alpha2 - alpha2Star;
+    
+    //		% we assume eta < 0. otherwise one has to repeat the complete 
+    //		% reasoning similarly (compute objective function for L and H 
+    //		% and decide which one is largest 
+    //		case1 = case2 = case3 = case4 = finished = 0 
+    //		alpha1old = alpha1, alpha1old* = alpha1* 
+    //		alpha2old = alpha2, alpha2old* = alpha2* 
+    //		deltaPhi = phi1 ?- phi2 
+    
+    double alpha1old = alpha1;
+    double alpha1Starold = alpha1Star;
+    double alpha2old = alpha2;
+    double alpha2Starold = alpha2Star;
+    double deltaPhi = phi2 - phi1;
+    
+    if (findOptimalPointOnLine(i1, alpha1, alpha1Star, C1, i2, alpha2, alpha2Star, C2, gamma, eta, deltaPhi)) {
+      alpha1 = m_alpha[i1];
+      alpha1Star = m_alphaStar[i1];
+      alpha2 = m_alpha[i2];
+      alpha2Star = m_alphaStar[i2];
+      
+      
+      //		Update error cache using new Lagrange multipliers 
+      double dAlpha1 = alpha1 - alpha1old - (alpha1Star - alpha1Starold);
+      double dAlpha2 = alpha2 - alpha2old - (alpha2Star - alpha2Starold);
+      for (int j = 0; j < m_nInstances; j++) {
+	if ((j != i1) && (j != i2)/* && m_error[j] != MAXERR*/) {
+	  m_error[j] += dAlpha1 * m_kernel.eval(i1, j, m_data.instance(i1)) + dAlpha2 * m_kernel.eval(i2, j, m_data.instance(i2));
+	}
+      }
+      m_error[i1] += dAlpha1 * k11 + dAlpha2 * k12;
+      m_error[i2] += dAlpha1 * k12 + dAlpha2 * k22;
+      
+      //		Update threshold to reflect change in Lagrange multipliers
+      double b1 = Double.MAX_VALUE;
+      double b2 = Double.MAX_VALUE;
+      if ((0 < alpha1 && alpha1 < C1) || (0 < alpha1Star && alpha1Star < C1) ||(0 < alpha2 && alpha2 < C2) || (0 < alpha2Star && alpha2Star < C2)) {
+	if (0 < alpha1 && alpha1 < C1) {
+	  b1 = m_error[i1] - m_epsilon;
+	} else if (0 < alpha1Star && alpha1Star < C1) {
+	  b1 = m_error[i1] + m_epsilon;
+	}
+	if (0 < alpha2 && alpha2 < C2) {
+	  b2 = m_error[i2] - m_epsilon;
+	} else if (0 < alpha2Star && alpha2Star < C2) {
+	  b2 = m_error[i2] + m_epsilon;
+	}
+	if (b1 < Double.MAX_VALUE) {
+	  m_b = b1;
+	  if (b2 < Double.MAX_VALUE) {
+	    m_b = (b1 + b2) / 2.0;
+	  }
+	} else if (b2 < Double.MAX_VALUE) {
+	  m_b = b2;
+	}
+      } else if (m_b == 0) {
+	// both alpha's are on the boundary, and m_b is not initialized
+	m_b = (m_error[i1] + m_error[i2])/2.0;
+      }
+      
+      //		if changes in alpha1(*), alpha2(*) are larger than some eps 
+      //			return 1 
+      //		else 
+      //			return 0 
+      //		endif
+      return 1;
+    } else {
+      return 0;
+    }
+    //	endprocedure 
+  }
+  
+  /** 
+   * examineExample method from pseudocode.
+   * Parameters correspond to pseudocode (see technicalinformation)
+   * 
+   * @param i2
+   * @return
+   * @throws Exception
+   */
+  protected int examineExample(int i2) throws Exception {
+    //	procedure examineExample(i2) 
+    //		y2 = target[i2] 
+    double y2 = m_target[i2];
+    //		alpha2, alpha2* = Lagrange multipliers for i2 
+    double alpha2 = m_alpha[i2];
+    double alpha2Star = m_alphaStar[i2];
+    //		C2, C2* = Constraints for i2 
+    double C2 = m_C;
+    double C2Star = m_C;
+    //		phi2 = SVM output on point[i2] ? y2 (in error cache) 
+    double phi2 = m_error[i2];
+    // phi2b contains the error, taking the offset in account
+    double phi2b = phi2 - m_b;
+    //		if ((phi2 > epsilon && alpha2* < C2*) ||
+    //			(phi2 < epsilon && alpha2* > 0 ) ||
+    //			(-?phi2 > epsilon && alpha2 < C2 ) ||
+    //			(?-phi2 > epsilon && alpha2 > 0 )) 
+    if ((phi2b > m_epsilon && alpha2Star < C2Star)
+	|| (phi2b < m_epsilon && alpha2Star > 0)
+	|| (-phi2b > m_epsilon && alpha2 < C2)
+	|| (-phi2b > m_epsilon && alpha2 > 0)) {
+      
+      //			if (number of non?zero & non?C alpha > 1) 
+      //				i1 = result of second choice heuristic 
+      //				if takeStep(i1,i2) return 1 
+      //			endif 
+      int i1 = secondChoiceHeuristic(i2);
+      if (i1 >= 0 && (takeStep(i1, i2, alpha2, alpha2Star, phi2) > 0)) {
+	return 1;
+      }
+      //			loop over all non?zero and non?C alpha, random start 
+      //				i1 = identity of current alpha 
+      //				if takeStep(i1,i2) return 1 
+      //			endloop 
+      for (i1 = 0; i1 < m_target.length; i1++) {
+	if ((m_alpha[i1] > 0 && m_alpha[i1] < m_C) || (m_alphaStar[i1] > 0 && m_alphaStar[i1] < m_C)) {
+	  if (takeStep(i1, i2, alpha2, alpha2Star, phi2) > 0) {
+	    return 1;
+	  }
+	}
+      }
+      //			loop over all possible i1, with random start 
+      //				i1 = loop variable 
+      //				if takeStep(i1,i2) return 1 
+      //			endloop 
+      for (i1 = 0; i1 < m_target.length; i1++) {
+	if (takeStep(i1, i2, alpha2, alpha2Star, phi2) > 0) {
+	  return 1;
+	}
+      }
+      //		endif 
+    }
+    //		return 0 
+    return 0;
+    //	endprocedure 
+  }
+  
+  /** 
+   * applies heuristic for finding candidate that is expected to lead to
+   * good gain when applying takeStep together with second candidate.
+   * 
+   * @param i2 index of second candidate
+   * @return
+   */
+  protected int secondChoiceHeuristic(int i2) {
+    // randomly select an index i1 (not equal to i2) with non?zero and non?C alpha, if any
+    for (int i = 0; i < 59; i++) {
+      int i1 = m_random.nextInt(m_nInstances);
+      if ((i1 != i2) && (m_alpha[i1] > 0 && m_alpha[i1] < m_C) || (m_alphaStar[i1] > 0 && m_alphaStar[i1] < m_C)) {
+	return i1;
+      }
+    }
+    return -1;
+  }
+  
+  /**
+   * finds alpha and alpha* parameters that optimize the SVM target function
+   * 
+   * @throws Exception
+   */
+  public void optimize() throws Exception {
+    
+    //	main routine: 
+    //		initialize threshold to zero 
+    //		numChanged = 0 
+    //		examineAll = 1 
+    //		SigFig = -100 
+    //		LoopCounter = 0 
+    int numChanged = 0;
+    int examineAll = 1;
+    int sigFig = -100;
+    int loopCounter = 0;
+    //		while ((numChanged > 0 | examineAll) | (SigFig < 3)) 
+    while ((numChanged > 0 || (examineAll > 0)) | (sigFig < 3)) {
+      //			LoopCounter++ 
+      //			numChanged = 0; 
+      loopCounter++;
+      numChanged = 0;
+      //			if (examineAll) 
+      //				loop I over all training examples 
+      //				numChanged += examineExample(I)
+      //			else 
+      //				loop I over examples where alpha is not 0 & not C 
+      //				numChanged += examineExample(I) 
+      //			endif
+      int numSamples = 0;
+      if (examineAll > 0) {
+	for (int i = 0; i < m_nInstances; i++) {
+	  numChanged += examineExample(i);
+	}
+      } else {
+	for (int i = 0; i < m_target.length; i++) {
+	  if ((m_alpha[i] > 0 && m_alpha[i] < m_C * m_data.instance(i).weight()) || 
+	      (m_alphaStar[i] > 0 && m_alphaStar[i] < m_C * m_data.instance(i).weight())) {
+	    numSamples++;
+	    numChanged += examineExample(i);
+	  }
+	}
+      }
+      //	
+      //		if (mod(LoopCounter, 2) == 0) 
+      //				MinimumNumChanged = max(1, 0.1*NumSamples) 
+      //			else 
+      //				MinimumNumChanged = 1 
+      //			endif 
+      int minimumNumChanged = 1;
+      if (loopCounter % 2 == 0) {
+	minimumNumChanged = (int) Math.max(1, 0.1 * numSamples);
+      }
+      
+      //			if (examineAll == 1) 
+      //				examineAll = 0 
+      //			elseif (numChanged < MinimumNumChanged) 
+      //				examineAll = 1 
+      //			endif 
+      if (examineAll == 1) {
+	examineAll = 0;
+      } else if (numChanged < minimumNumChanged) {
+	examineAll = 1;
+      }
+      
+      //		endwhile 
+      if (loopCounter == 2500) {
+	break;
+      }
+    }
+    //	endmain 
+  }
+  
+  /** 
+   * learn SVM parameters from data using Smola's SMO algorithm.
+   * Subclasses should implement something more interesting.
+   * 
+   * @param instances	the data to learn from
+   * @throws Exception	if something goes wrong
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // initialize variables
+    init(instances);
+    // solve optimization problem
+    optimize();
+    // clean up
+    wrapUp();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMOImproved.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMOImproved.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/RegSMOImproved.java	(revision 29)
@@ -0,0 +1,972 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RegSMOImproved.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Learn SVM for regression using SMO with Shevade, Keerthi, et al. adaption of the stopping criterion.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * S.K. Shevade, S.S. Keerthi, C. Bhattacharyya, K.R.K. Murthy: Improvements to the SMO Algorithm for SVM Regression. In: IEEE Transactions on Neural Networks, 1999.<br/>
+ * <br/>
+ * S.K. Shevade, S.S. Keerthi, C. Bhattacharyya, K.R.K. Murthy (1999). Improvements to the SMO Algorithm for SVM Regression. Control Division, Dept. of Mechanical Engineering.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Shevade1999,
+ *    author = {S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy},
+ *    booktitle = {IEEE Transactions on Neural Networks},
+ *    title = {Improvements to the SMO Algorithm for SVM Regression},
+ *    year = {1999},
+ *    PS = {http://guppy.mpe.nus.edu.sg/\~mpessk/svm/ieee_smo_reg.ps.gz}
+ * }
+ * 
+ * &#64;techreport{Shevade1999,
+ *    address = {Control Division, Dept. of Mechanical Engineering},
+ *    author = {S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy},
+ *    institution = {National University of Singapore},
+ *    number = {CD-99-16},
+ *    title = {Improvements to the SMO Algorithm for SVM Regression},
+ *    year = {1999},
+ *    PS = {http://guppy.mpe.nus.edu.sg/\~mpessk/svm/smoreg_mod.ps.gz}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -T &lt;double&gt;
+ *  The tolerance parameter for checking the stopping criterion.
+ *  (default 0.001)</pre>
+ * 
+ * <pre> -V
+ *  Use variant 1 of the algorithm when true, otherwise use variant 2.
+ *  (default true)</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error.
+ *  (default 1.0e-12)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The epsilon parameter in epsilon-insensitive loss function.
+ *  (default 1.0e-3)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Remco Bouckaert (remco@cs.waikato.ac.nz,rrb@xm.co.nz)
+ * @version $Revision: 1.4 $
+ */
+public class RegSMOImproved
+  extends RegSMO
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 471692841446029784L;
+  
+  public final static int I0 = 3;
+  public final static int I0a = 1;
+  public final static int I0b = 2;
+  public final static int I1 = 4;
+  public final static int I2 = 8;
+  public final static int I3 = 16;
+  
+  /** The different sets used by the algorithm. */
+  protected SMOset m_I0;
+  
+  /** Index set {i: 0 < m_alpha[i] < C || 0 < m_alphaStar[i] < C}} */
+  protected int [] m_iSet;
+  
+  /** b.up and b.low boundaries used to determine stopping criterion */
+  protected double m_bUp, m_bLow;
+  
+  /** index of the instance that gave us b.up and b.low */
+  protected int m_iUp, m_iLow;
+  
+  /** tolerance parameter used for checking stopping criterion b.up < b.low + 2 tol */
+  double m_fTolerance = 0.001;
+  
+  /** set true to use variant 1 of the paper, otherwise use variant 2 */
+  boolean m_bUseVariant1 = true;
+  
+  /**
+   * Returns a string describing the object
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Learn SVM for regression using SMO with Shevade, Keerthi, et al. " 
+      + "adaption of the stopping criterion.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy");
+    result.setValue(Field.TITLE, "Improvements to the SMO Algorithm for SVM Regression");
+    result.setValue(Field.BOOKTITLE, "IEEE Transactions on Neural Networks");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.PS, "http://guppy.mpe.nus.edu.sg/~mpessk/svm/ieee_smo_reg.ps.gz");
+    
+    additional = result.add(Type.TECHREPORT);
+    additional.setValue(Field.AUTHOR, "S.K. Shevade and S.S. Keerthi and C. Bhattacharyya and K.R.K. Murthy");
+    additional.setValue(Field.TITLE, "Improvements to the SMO Algorithm for SVM Regression");
+    additional.setValue(Field.INSTITUTION, "National University of Singapore");
+    additional.setValue(Field.ADDRESS, "Control Division, Dept. of Mechanical Engineering");
+    additional.setValue(Field.NUMBER, "CD-99-16");
+    additional.setValue(Field.YEAR, "1999");
+    additional.setValue(Field.PS, "http://guppy.mpe.nus.edu.sg/~mpessk/svm/smoreg_mod.ps.gz");
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options
+   * 
+   * @return 		an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tThe tolerance parameter for checking the stopping criterion.\n" 
+	+ "\t(default 0.001)", 
+	"T", 1, "-T <double>"));
+    
+    result.addElement(new Option(
+	"\tUse variant 1 of the algorithm when true, otherwise use variant 2.\n" 
+	+ "\t(default true)", 
+	"V", 0, "-V"));
+    
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements()) {
+      result.addElement(enm.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -T &lt;double&gt;
+   *  The tolerance parameter for checking the stopping criterion.
+   *  (default 0.001)</pre>
+   * 
+   * <pre> -V
+   *  Use variant 1 of the algorithm when true, otherwise use variant 2.
+   *  (default true)</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error.
+   *  (default 1.0e-12)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The epsilon parameter in epsilon-insensitive loss function.
+   *  (default 1.0e-3)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('T', options);
+    if (tmpStr.length() != 0) {
+      setTolerance(Double.parseDouble(tmpStr));
+    } else {
+      setTolerance(0.001);
+    }
+    
+    setUseVariant1(Utils.getFlag('V', options));
+    
+    super.setOptions(options);
+  }
+  
+  /**
+   * Gets the current settings of the object.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-T");
+    result.add("" + getTolerance());
+    
+    if (m_bUseVariant1)
+      result.add("-V");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String toleranceTipText() {
+    return "tolerance parameter used for checking stopping criterion b.up < b.low + 2 tol";
+  }
+  
+  /**
+   * returns the current tolerance
+   * 
+   * @return	the tolerance
+   */
+  public double getTolerance() {
+    return m_fTolerance;
+  }
+  
+  /**
+   * sets the tolerance
+   * 
+   * @param d	the new tolerance
+   */
+  public void setTolerance(double d) {
+    m_fTolerance = d;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useVariant1TipText() {
+    return "set true to use variant 1 of the paper, otherwise use variant 2.";
+  }
+  
+  /**
+   * Whether variant 1 is used
+   * 
+   * @return		true if variant 1 is used
+   */
+  public boolean isUseVariant1() {
+    return m_bUseVariant1;
+  }
+  
+  /**
+   * Sets whether to use variant 1
+   * 
+   * @param b		if true then variant 1 is used
+   */
+  public void setUseVariant1(boolean b) {
+    m_bUseVariant1 = b;
+  }
+  
+  /** 
+   * takeStep method from Shevade et al.s paper.
+   * parameters correspond to pseudocode from paper.
+   * 
+   * @param i1
+   * @param i2
+   * @param alpha2
+   * @param alpha2Star
+   * @param phi2
+   * @return
+   * @throws Exception
+   */
+  protected int takeStep(int i1, int i2, double alpha2, double alpha2Star, double phi2) throws Exception {
+    //procedure takeStep(i1, i2)
+    //
+    //  if (i1 == i2) 
+    //    return 0 
+    if (i1 == i2) {
+      return 0;
+    }
+    double C1 = m_C * m_data.instance(i1).weight();
+    double C2 = m_C * m_data.instance(i2).weight();
+    //  alpha1, alpha1' = Lagrange multipliers for i1 
+    double alpha1 = m_alpha[i1];
+    double alpha1Star = m_alphaStar[i1];
+//  double y1 = m_target[i1];
+    // TODO: verify we do not need to recompute m_error[i1] here
+    // TODO: since m_error is only updated for indices in m_I0
+    double phi1 = m_error[i1];
+//  if ((m_iSet[i1] & I0)==0) {
+//  phi1 = -SVMOutput(i1) - m_b + m_target[i1];
+//  m_error[i1] = phi1;
+//  }
+    //  k11 = kernel(point[i1], point[i1]) 
+    //  k12 = kernel(point[i1], point[i2]) 
+    //  k22 = kernel(point[i2], point[i2]) 
+    //  eta = -2*k12+k11+k22 
+    //  gamma = alpha1-alpha1'+alpha2-alpha2'
+    //
+    double k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
+    double k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
+    double k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
+    double eta = -2 * k12 + k11 + k22;
+    double gamma = alpha1 - alpha1Star + alpha2 - alpha2Star;
+//  if (eta < 0) {
+    // this may happen due to numeric instability
+    // due to Mercer's condition, this should not happen, hence we give up
+//  return 0;
+//  }
+    //  % We assume that eta > 0. Otherwise one has to repeat the complete 
+    //  % reasoning similarly (i.e. compute objective functions at L and H 
+    //  % and decide which one is largest
+    //
+    //  case1 = case2 = case3 = case4 = finished = 0 
+    //  alpha1old = alpha1, 
+    //  alpha1old' = alpha1' 
+    //  alpha2old = alpha2, 
+    //  alpha2old' = alpha2' 
+    //  deltaphi = F1 - F2 
+    //
+    
+    //  while !finished
+    //    % This loop is passed at most three times 
+    //    % Case variables needed to avoid attempting small changes twice 
+    //    if (case1 == 0) &&
+    //       (alpha1 > 0 || (alpha1' == 0 && deltaphi > 0)) && 
+    //       (alpha2 > 0 || (alpha2' == 0 && deltaphi < 0))
+    //        compute L, H (w.r.t. alpha1, alpha2) 
+    //        if (L < H)
+    //          a2 = alpha2 - (deltaphi / eta ) a2 = min(a2, H) a2 = max(L, a2) a1 = alpha1 - (a2 - alpha2) 
+    //          update alpha1, alpha2 if change is larger than some eps 
+    //        else
+    //          finished = 1 
+    //        endif 
+    //      case1 = 1 
+    //    elseif (case2 == 0) &&
+    //           (alpha1 > 0 || (alpha1' == 0 && deltaphi > 2*epsilon)) && 
+    //           (alpha2' > 0 || (alpha2 == 0 && deltaphi > 2*epsilon))
+    //
+    //        compute L, H (w.r.t. alpha1, alpha2') 
+    //        if (L < H)
+    //          a2 = alpha2' + ((deltaphi - 2*epsilon)/eta)) a2 = min(a2, H) a2 = max(L, a2) a1 = alpha1 + (a2-alpha2') 
+    //          update alpha1, alpha2' if change is larger than some eps 
+    //        else
+    //          finished = 1 
+    //        endif 
+    //        case2 = 1 
+    //    elseif (case3 == 0) &&
+    //           (alpha1' > 0 || (alpha1 == 0 && deltaphi < -2*epsilon)) && 
+    //           (alpha2 > 0 || (alpha2' == 0 && deltaphi < -2*epsilon))
+    //         compute L, H (w.r.t. alpha1', alpha2) 
+    //         if (L < H)
+    //           a2 = alpha2 - ((deltaphi + 2*epsilon)/eta) a2 = min(a2, H) a2 = max(L, a2) a1 = alpha1' + (a2 - alpha2) 
+    //           update alpha1', alpha2 if change is larger than some eps 
+    //         else
+    //           finished = 1 
+    //         endif 
+    //         case3 = 1 
+    //    elseif (case4 == 0) &&
+    //           (alpha1' > 0) || (alpha1 == 0 && deltaphi < 0)) && 
+    //           (alpha2' > 0) || (alpha2 == 0 && deltaphi > 0))
+    //         compute L, H (w.r.t. alpha1', alpha2') 
+    //         if (L < H) 
+    //           a2 = alpha2' + deltaphi/eta a2 = min(a2, H) a2 = max(L, a2) a1 = alpha1' - (a2 - alpha2') 
+    //           update alpha1, alpha2' if change is larger than some eps 
+    //         else
+    //           finished = 1 
+    //         endif 
+    //         case4 = 1 
+    //    else
+    //      finished = 1 
+    //    endif 
+    //    update deltaphi 
+    //  endwhile 
+    
+    double alpha1old = alpha1;
+    double alpha1Starold = alpha1Star;
+    double alpha2old = alpha2;
+    double alpha2Starold = alpha2Star;
+    double deltaPhi = phi1 - phi2;
+    
+    if (findOptimalPointOnLine(i1, alpha1, alpha1Star, C1, i2, alpha2, alpha2Star, C2, gamma, eta, deltaPhi)) {
+      
+      alpha1 = m_alpha[i1];
+      alpha1Star = m_alphaStar[i1];
+      alpha2 = m_alpha[i2];
+      alpha2Star = m_alphaStar[i2];
+      
+      //  if changes in alpha('), alpha2(') are larger than some eps
+      //    Update f-cache[i] for i in I.0 using new Lagrange multipliers 
+      //    Store the changes in alpha, alpha' array 
+      //    Update I.0, I.1, I.2, I.3 
+      //    Compute (i.low, b.low) and (i.up, b.up) by applying the conditions mentioned above, using only i1, i2 and indices in I.0 
+      //    return 1 
+      //  else
+      //    return 0
+      //endif endprocedure
+      
+      //		Update error cache using new Lagrange multipliers 
+      double dAlpha1 = alpha1 - alpha1old - (alpha1Star - alpha1Starold);
+      double dAlpha2 = alpha2 - alpha2old - (alpha2Star - alpha2Starold);
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+	if ((j != i1) && (j != i2)) {
+	  m_error[j] -= dAlpha1 * m_kernel.eval(i1, j, m_data.instance(i1)) 
+	  + dAlpha2 * m_kernel.eval(i2, j, m_data.instance(i2));
+	}
+      }
+      m_error[i1] -= dAlpha1 * k11 + dAlpha2 * k12;
+      m_error[i2] -= dAlpha1 * k12 + dAlpha2 * k22;
+      
+      updateIndexSetFor(i1, C1);
+      updateIndexSetFor(i2, C2);
+      
+      //    Compute (i.low, b.low) and (i.up, b.up) by applying the conditions mentioned above, using only i1, i2 and indices in I.0 
+      m_bUp = Double.MAX_VALUE; 
+      m_bLow = -Double.MAX_VALUE; 
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+	updateBoundaries(j, m_error[j]);
+      }
+      if (!m_I0.contains(i1)) {
+	updateBoundaries(i1, m_error[i1]);
+      }
+      if (!m_I0.contains(i2)) {
+	updateBoundaries(i2, m_error[i2]);
+      }
+      
+      return 1;
+    } 
+    else {
+      return 0;
+    }
+  }
+  
+  /**
+   * updates the index sets I0a, IOb, I1, I2 and I3 for vector i
+   * 
+   * @param i index of vector
+   * @param C capacity for vector i
+   * @throws Exception
+   */
+  protected void updateIndexSetFor(int i, double C) throws Exception {
+    /*
+     m_I0a.delete(i);
+     m_I0b.delete(i);
+     m_I1.delete(i);
+     m_I2.delete(i);
+     m_I3.delete(i);
+     */
+    if (m_alpha[i] == 0 && m_alphaStar[i] == 0) {
+      //m_I1.insert(i);
+      m_iSet[i] = I1;
+      m_I0.delete(i);
+    } else if (m_alpha[i] > 0) {
+      if (m_alpha[i] < C) {
+	if ((m_iSet[i] & I0) == 0) {
+	  //m_error[i] = -SVMOutput(i) - m_b + m_target[i];
+	  m_I0.insert(i);
+	}
+	//m_I0a.insert(i);
+	m_iSet[i] = I0a;
+      } else { // m_alpha[i] == C
+	//m_I3.insert(i);
+	m_iSet[i] = I3;
+	m_I0.delete(i);
+      }
+    } else {// m_alphaStar[i] > 0 
+      if (m_alphaStar[i] < C) {
+	if ((m_iSet[i] & I0) == 0) {
+	  //m_error[i] = -SVMOutput(i) - m_b + m_target[i];
+	  m_I0.insert(i);
+	}
+	//m_I0b.insert(i);
+	m_iSet[i] = I0b;
+      } else { // m_alpha[i] == C
+	//m_I2.insert(i);
+	m_iSet[i] = I2;
+	m_I0.delete(i);
+      }
+    }
+  }
+  
+  /**
+   * updates boundaries bLow and bHi and corresponding indexes
+   * 
+   * @param i2 index of vector
+   * @param F2 error of vector i2
+   */
+  protected void updateBoundaries(int i2, double F2) {		
+    int iSet = m_iSet[i2];
+    
+    double FLow = m_bLow;
+    if ((iSet & (I2 | I0b)) > 0) {
+      FLow = F2 + m_epsilon;
+    } else if ((iSet & (I1 | I0a)) > 0) {
+      FLow = F2 - m_epsilon;
+    }
+    if (m_bLow < FLow) {
+      m_bLow = FLow;
+      m_iLow = i2;
+    }
+    double FUp = m_bUp;
+    if ((iSet & (I3 | I0a)) > 0) {
+      FUp = F2 - m_epsilon;
+    } else if ((iSet & (I1 | I0b)) > 0) {
+      FUp = F2 + m_epsilon;
+    }
+    if (m_bUp > FUp) {
+      m_bUp = FUp;
+      m_iUp = i2;
+    }
+  }
+  
+  /** 
+   * parameters correspond to pseudocode from paper.
+   * 
+   * @param i2 index of  candidate
+   * @return
+   * @throws Exception
+   */
+  protected int examineExample(int i2) throws Exception {
+    //procedure examineExample(i2)
+    //
+    //  alpha2, alpha2' = Lagrange multipliers for i2 
+    double alpha2 = m_alpha[i2];
+    double alpha2Star = m_alphaStar[i2];
+    
+    //  if (i2 is in I.0)
+    //    F2 = f-cache[i2] 
+    //  else
+    //    compute F2 = F.i2 and set f-cache[i2] = F2 
+    //    % Update (b.low, i.low) or (b.up, i.up) using (F2, i2)... 
+    //    if (i2 is in I.1)
+    //      if (F2+epsilon < b.up)
+    //        b.up = F2+epsilon, 
+    //        i.up = i2 
+    //      elseif (F2-epsilon > b.low)
+    //        b.low = F2-epsilon, 
+    //        i.low = i2 
+    //      end if 
+    //    elseif ( (i2 is in I.2) && (F2+epsilon > b.low) )
+    //      b.low = F2+epsilon, 
+    //      i.low = i2 
+    //    elseif ( (i2 is in I.3) && (F2-epsilon < b.up) )
+    //      b.up = F2-epsilon, 
+    //      i.up = i2 
+    //    endif 
+    //  endif 
+    
+    int iSet = m_iSet[i2];
+    double F2 = m_error[i2];
+    if (!m_I0.contains(i2)) {
+      F2 = -SVMOutput(i2) - m_b + m_target[i2];
+      m_error[i2] = F2;
+      if (iSet == I1) {
+	if (F2 + m_epsilon < m_bUp) {
+	  m_bUp = F2 + m_epsilon;
+	  m_iUp = i2;
+	} else if (F2 - m_epsilon > m_bLow) {
+	  m_bLow = F2 - m_epsilon;
+	  m_iLow = i2;
+	} 
+      } else if ((iSet == I2) && (F2 + m_epsilon > m_bLow)) {
+	m_bLow = F2 + m_epsilon;
+	m_iLow = i2;
+      } else if ((iSet == I3) && (F2 - m_epsilon < m_bUp)) {
+	m_bUp = F2 - m_epsilon;
+	m_iUp = i2;
+      }
+    }
+    
+    //  % Check optimality using current b.low and b.up and, if 
+    //  % violated, find an index i1 to do joint optimization with i2... 
+    //  optimality = 1;
+    //  case 1: i2 is in I.0a
+    //    if (b.low-(F2-epsilon) > 2 * tol)
+    //      optimality = 0;  
+    //      i1 = i.low; 
+    //      % For i2 in I.0a choose the better i1... 
+    //      if ((F2-epsilon)-b.up > b.low-(F2-epsilon))
+    //        i1 = i.up; 
+    //      endif 
+    //    elseif ((F2-epsilon)-b.up > 2 * tol)
+    //      optimality = 0; 
+    //      i1 = i.up; 
+    //      % For i2 in I.0a choose the better i1... 
+    //      if ((b.low-(F2-epsilon) > (F2-epsilon)-b.up)
+    //        i1 = i.low; 
+    //      endif 
+    //    endif 
+    //  case 2: i2 is in I.0b
+    //    if (b.low-(F2+epsilon) > 2 * tol)
+    //      optimality = 0; 
+    //      i1 = i.low; 
+    //      % For i2 in I.0b choose the better i1... 
+    //      if ((F2+epsilon)-b.up > b.low-(F2+epsilon))
+    //        i1 = i.up; 
+    //      endif 
+    //    elseif ((F2+epsilon)-b.up > 2 * tol)
+    //      optimality = 0; 
+    //      i1 = i.up; 
+    //      % For i2 in I.0b choose the better i1... 
+    //      if ((b.low-(F2+epsilon) > (F2+epsilon)-b.up)
+    //        i1 = i.low; 
+    //      endif 
+    //    endif 
+    //  case 3: i2 is in I.1
+    //    if (b.low-(F2+epsilon) > 2 * tol)
+    //      optimality = 0; 
+    //      i1 = i.low; 
+    //      % For i2 in I1 choose the better i1... 
+    //      if ((F2+epsilon)-b.up > b.low-(F2+epsilon)
+    //        i1 = i.up; 
+    //      endif 
+    //    elseif ((F2-epsilon)-b.up > 2 * tol)
+    //      optimality = 0; 
+    //      i1 = i.up; 
+    //      % For i2 in I1 choose the better i1... 
+    //      if (b.low-(F2-epsilon) > (F2-epsilon)-b.up)
+    //        i1 = i.low; 
+    //      endif 
+    //    endif 
+    //  case 4: i2 is in I.2
+    //    if ((F2+epsilon)-b.up > 2*tol)
+    //      optimality = 0, 
+    //      i1 = i.up 
+    //     endif 
+    //  case 5: i2 is in I.3
+    //    if ((b.low-(F2-epsilon) > 2*tol)
+    //      optimality = 0, i1 = i.low 
+    //    endif
+    
+    int i1 = i2;
+    boolean bOptimality = true;
+    //case 1: i2 is in I.0a
+    if (iSet == I0a) {
+      if (m_bLow - (F2 - m_epsilon) > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iLow;
+	//% For i2 in I .0 a choose the better i1...
+	if ((F2 - m_epsilon) - m_bUp > m_bLow - (F2 - m_epsilon)) {
+	  i1 = m_iUp;
+	}
+      } else if ((F2 - m_epsilon) - m_bUp > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iUp;
+	//% For i2 in I.0a choose the better i1... 
+	if (m_bLow - (F2 - m_epsilon) > (F2 - m_epsilon) - m_bUp) {
+	  i1 = m_iLow;
+	}
+      }
+    } // case 2: i2 is in I.0b
+    else if (iSet == I0b) {
+      if (m_bLow - (F2 + m_epsilon) > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iLow; // % For i2 in I.0b choose the better i1... 
+	if ((F2 + m_epsilon) - m_bUp > m_bLow - (F2 + m_epsilon)) {
+	  i1 = m_iUp;
+	}
+      } else if ((F2 + m_epsilon) - m_bUp > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iUp; // % For i2 in I.0b choose the better i1... 
+	if (m_bLow - (F2 + m_epsilon) > (F2 + m_epsilon) - m_bUp) {
+	  i1 = m_iLow;
+	}
+      }
+    } // case 3: i2 is in I.1
+    else if (iSet == I1) {
+      if (m_bLow - (F2 + m_epsilon) > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iLow;
+	//% For i2 in I1 choose the better i1...
+	if ((F2 + m_epsilon) - m_bUp > m_bLow - (F2 + m_epsilon)) {
+	  i1 = m_iUp;
+	}
+      } else if ((F2 - m_epsilon) - m_bUp > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iUp; // % For i2 in I1 choose the better i1... 
+	if (m_bLow - (F2 - m_epsilon) > (F2 - m_epsilon) - m_bUp) {
+	  i1 = m_iLow;
+	}
+      }
+    } //case 4: i2 is in I.2
+    else if (iSet == I2) {
+      if ((F2 + m_epsilon) - m_bUp > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iUp;
+      }
+    } //case 5: i2 is in I.3
+    else if (iSet == I3) {
+      if (m_bLow - (F2 - m_epsilon) > 2 * m_fTolerance) {
+	bOptimality = false;
+	i1 = m_iLow;
+      }
+    }
+    // if (optimality == 1) 
+    //    return 0
+    //  if (takeStep(i1, i2))
+    //    return 1 
+    //   else
+    //    return 0 
+    //  endif 
+    //endprocedure
+    if (bOptimality) {
+      return 0;
+    }
+    return takeStep(i1, i2, m_alpha[i2], m_alphaStar[i2], F2);
+  }
+  
+  /** 
+   * initialize various variables before starting the actual optimizer 
+   * 
+   * @param data 	data set used for learning
+   * @throws Exception	if something goes wrong
+   */
+  protected void init(Instances data) throws Exception {
+    super.init(data);
+    // from Keerthi's pseudo code:
+    //  set alpha and alpha' to zero for every example set I.1 to contain all the examples 
+    //  Choose any example i from the training set. 
+    //  set b.up = target[i]+epsilon 
+    //  set b.low = target[i]-espilon 
+    //  i.up = i.low = i; 
+    // Initialize sets
+    m_I0 = new SMOset(m_data.numInstances());
+    m_iSet = new int [m_data.numInstances()];
+    for (int i = 0; i < m_nInstances; i++) {
+      m_iSet[i] = I1;
+    }
+    // m_iUp = m_random.nextInt(m_nInstances);
+    m_iUp = 0;
+    m_bUp = m_target[m_iUp] + m_epsilon;
+    m_iLow = m_iUp;
+    m_bLow = m_target[m_iLow] - m_epsilon;
+    //init error cache
+    m_error = new double[m_nInstances];
+    for (int i = 0; i < m_nInstances; i++) {
+      m_error[i] = m_target[i];
+    }
+  }
+  
+  /** 
+   * use variant 1 of Shevade's et al.s paper
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  protected void optimize1() throws Exception {
+    //% main routine for modification 1 procedure main
+    //  while (numChanged > 0 || examineAll)
+    //    numChanged = 0; 
+    int nNumChanged = 0;
+    boolean bExamineAll = true;
+    //  while (numChanged > 0 || examineAll)
+    //    numChanged = 0; 
+    while (nNumChanged > 0 || bExamineAll) {
+      nNumChanged = 0;
+      //    if (examineAll)
+      //      loop I over all the training examples
+      //        numChanged += examineExample(I) 
+      //    else
+      //      loop I over I.0
+      //        numChanged += examineExample(I) 
+      //        % It is easy to check if optimality on I.0 is attained... 
+      //        if (b.up > b.low - 2*tol) at any I
+      //          exit the loop after setting numChanged = 0 
+      //        endif 
+      if (bExamineAll) {
+	for (int i = 0; i < m_nInstances; i++) {
+	  nNumChanged += examineExample(i);
+	}
+      } else {
+	for (int i = m_I0.getNext(-1); i != -1; i = m_I0.getNext(i)) {
+	  
+	  nNumChanged += examineExample(i);
+	  if (m_bLow - m_bUp < 2 * m_fTolerance) {
+	    nNumChanged = 0;
+	    break;
+	  }
+	}
+      } //    if (examineAll == 1)
+      //      examineAll = 0; 
+      //    elseif (numChanged == 0)
+      //      examineAll = 1;
+      //    endif 
+      //  endwhile 
+      //endprocedure
+      if (bExamineAll) {
+	bExamineAll = false;
+      } else if (nNumChanged == 0) {
+	bExamineAll = true;
+      }
+    }
+  }
+  
+  /** 
+   * use variant 2 of Shevade's et al.s paper 
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  protected void optimize2() throws Exception {
+    //% main routine for modification 2 procedure main
+    int nNumChanged = 0;
+    boolean bExamineAll = true;
+    //  while (numChanged > 0 || examineAll)
+    //    numChanged = 0; 
+    while (nNumChanged > 0 || bExamineAll) {
+      nNumChanged = 0;
+      //    if (examineAll)
+      //      loop I over all the training examples
+      //        numChanged += examineExample(I) 
+      //    else
+      //      % The following loop is the only difference between the two 
+      //      % SMO modifications. Whereas, modification 1, the type II 
+      //      % loop selects i2 fro I.0 sequentially, here i2 is always 
+      //      % set to the current i.low and i1 is set to the current i.up; 
+      //      % clearly, this corresponds to choosing the worst violating 
+      //      % pair using members of I.0 and some other indices
+      //      inner.loop.success = 1; 
+      //      do
+      //        i2 = i.low 
+      //	      alpha2, alpha2' = Lagrange multipliers for i2 
+      //	      F2 = f-cache[i2] 
+      //	      i1 = i.up 
+      //	      inner.loop.success = takeStep(i.up, i.low) 
+      //	      numChanged += inner.loop.success 
+      //      until ( (b.up > b.low - 2*tol) || inner.loop.success == 0) 
+      //      numChanged = 0; 
+      //    endif 
+      if (bExamineAll) {
+	for (int i = 0; i < m_nInstances; i++) {
+	  nNumChanged += examineExample(i);
+	}
+      } else {
+	boolean bInnerLoopSuccess = true;
+	do {
+	  if (takeStep(m_iUp, m_iLow, m_alpha[m_iLow], m_alphaStar[m_iLow], m_error[m_iLow]) > 0) {
+	    bInnerLoopSuccess = true;
+	    nNumChanged += 1;
+	  } else {
+	    bInnerLoopSuccess = false;
+	  }
+	} while ((m_bUp <= m_bLow - 2 * m_fTolerance) && bInnerLoopSuccess);
+	nNumChanged = 0;
+      } //
+      //    if (examineAll == 1)
+      //      examineAll = 0 
+      //    elseif (numChanged == 0)
+      //      examineAll = 1 
+      //    endif 
+      //  endwhile 
+      //endprocedure
+      //
+      if (bExamineAll) {
+	bExamineAll = false;
+      } else if (nNumChanged == 0) {
+	bExamineAll = true;
+      }
+    }
+  }
+  
+  /** 
+   * wrap up various variables to save memeory and do some housekeeping after optimization
+   * has finished.
+   *
+   * @throws Exception 	if something goes wrong
+   */
+  protected void wrapUp() throws Exception {
+    m_b = -(m_bLow + m_bUp) / 2.0;
+    m_target = null;
+    m_error = null;
+    super.wrapUp();
+  }
+  
+  /** 
+   * learn SVM parameters from data using Keerthi's SMO algorithm.
+   * Subclasses should implement something more interesting.
+   * 
+   * @param instances	the data to work with
+   * @throws Exception	if something goes wrong
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // initialize variables		
+    init(instances); 
+
+    // solve optimization problem
+    if (m_bUseVariant1) {
+      optimize1();
+    } else {
+      optimize2();
+    } 
+    
+    // clean up
+    wrapUp();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/SMOset.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/SMOset.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/SMOset.java	(revision 29)
@@ -0,0 +1,163 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SMOset.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Stores a set of integer of a given size.
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class SMOset
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8364829283188675777L;
+  
+  /** The current number of elements in the set */
+  private int m_number;
+
+  /** The first element in the set */
+  private int m_first;
+
+  /** Indicators */
+  private boolean[] m_indicators;
+
+  /** The next element for each element */
+  private int[] m_next;
+
+  /** The previous element for each element */
+  private int[] m_previous;
+
+  /**
+   * Creates a new set of the given size.
+   */
+  public SMOset(int size) {
+      
+    m_indicators = new boolean[size];
+    m_next = new int[size];
+    m_previous = new int[size];
+    m_number = 0;
+    m_first = -1;
+  }
+ 
+  /**
+   * Checks whether an element is in the set.
+   */
+  public boolean contains(int index) {
+
+    return m_indicators[index];
+  }
+
+  /**
+   * Deletes an element from the set.
+   */
+  public void delete(int index) {
+
+    if (m_indicators[index]) {
+      if (m_first == index) {
+	m_first = m_next[index];
+      } else {
+	m_next[m_previous[index]] = m_next[index];
+      }
+      if (m_next[index] != -1) {
+	m_previous[m_next[index]] = m_previous[index];
+      }
+      m_indicators[index] = false;
+      m_number--;
+    }
+  }
+
+  /**
+   * Inserts an element into the set.
+   */
+  public void insert(int index) {
+
+    if (!m_indicators[index]) {
+      if (m_number == 0) {
+	m_first = index;
+	m_next[index] = -1;
+	m_previous[index] = -1;
+      } else {
+	m_previous[m_first] = index;
+	m_next[index] = m_first;
+	m_previous[index] = -1;
+	m_first = index;
+      }
+      m_indicators[index] = true;
+      m_number++;
+    }
+  }
+
+  /** 
+   * Gets the next element in the set. -1 gets the first one.
+   */
+  public int getNext(int index) {
+
+    if (index == -1) {
+      return m_first;
+    } else {
+      return m_next[index];
+    }
+  }
+
+  /**
+   * Prints all the current elements in the set.
+   */
+  public void printElements() {
+
+    for (int i = getNext(-1); i != -1; i = getNext(i)) {
+      System.err.print(i + " ");
+    }
+    System.err.println();
+    for (int i = 0; i < m_indicators.length; i++) {
+      if (m_indicators[i]) {
+	System.err.print(i + " ");
+      }
+    }
+    System.err.println();
+    System.err.println(m_number);
+  }
+
+  /** 
+   * Returns the number of elements in the set.
+   */
+  public int numElements() {
+      
+    return m_number;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/StringKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/StringKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/functions/supportVector/StringKernel.java	(revision 29)
@@ -0,0 +1,1512 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * StringKernel.java
+ * 
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implementation of the subsequence kernel (SSK) as described in [1] and of the subsequence kernel with lambda pruning (SSK-LP) as described in [2].<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Huma Lodhi, Craig Saunders, John Shawe-Taylor, Nello Cristianini, Christopher J. C. H. Watkins (2002). Text Classification using String Kernels. Journal of Machine Learning Research. 2:419-444.<br/>
+ * <br/>
+ * F. Kleedorfer, A. Seewald (2005). Implementation of a String Kernel for WEKA. Wien, Austria.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Lodhi2002,
+ *    author = {Huma Lodhi and Craig Saunders and John Shawe-Taylor and Nello Cristianini and Christopher J. C. H. Watkins},
+ *    journal = {Journal of Machine Learning Research},
+ *    pages = {419-444},
+ *    title = {Text Classification using String Kernels},
+ *    volume = {2},
+ *    year = {2002},
+ *    HTTP = {http://www.jmlr.org/papers/v2/lodhi02a.html}
+ * }
+ * 
+ * &#64;techreport{Kleedorfer2005,
+ *    address = {Wien, Austria},
+ *    author = {F. Kleedorfer and A. Seewald},
+ *    institution = {Oesterreichisches Forschungsinstitut fuer Artificial Intelligence},
+ *    number = {TR-2005-13},
+ *    title = {Implementation of a String Kernel for WEKA},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -P &lt;0|1&gt;
+ *  The pruning method to use:
+ *  0 = No pruning
+ *  1 = Lambda pruning
+ *  (default: 0)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number).
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -IC &lt;num&gt;
+ *  The size of the internal cache (a prime number).
+ *  (default: 200003)</pre>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  The lambda constant. Penalizes non-continuous subsequence
+ *  matches. Must be in (0,1).
+ *  (default: 0.5)</pre>
+ * 
+ * <pre> -ssl &lt;num&gt;
+ *  The length of the subsequence.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -ssl-max &lt;num&gt;
+ *  The maximum length of the subsequence.
+ *  (default: 9)</pre>
+ * 
+ * <pre> -N
+ *  Use normalization.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * <h1>Theory</h1>
+ * <h2>Overview</h2>
+ * The algorithm computes a measure of similarity between two texts based on
+ * the number and form of their common subsequences, which need not be
+ * contiguous. This method can be parametrized by specifying the subsequence
+ * length k, the penalty factor lambda, which penalizes non-contiguous matches,
+ * and optional 'lambda pruning', which takes maxLambdaExponent,
+ * <code>m</code>, as parameter. Lambda pruning causes very 'stretched'
+ * substring matches not to be counted, thus speeding up the computation. The
+ * functionality of SSK and SSK-LP is explained in the following using simple
+ * examples.
+ * 
+ * <h2>Explanation &amp; Examples</h2>
+ * for all of the following examples, we assume these parameter values: 
+ *<pre> 
+ *k=2
+ *lambda=0.5
+ *m=8 (for SSK-LP examples)
+ *</pre>
+ * 
+ * <h3>SSK</h3>
+ * 
+ * <h4>Example 1</h4>
+ * 
+ * <pre>
+ *SSK(2,"ab","axb")=0.5^5 = 0,03125
+ *</pre>
+ * There is one subsequence of the length of 2 that both strings have in
+ * common, "ab".  The result of SSK is computed by raising lambda to the power
+ * of L, where L is the length of the subsequence match in the one string plus
+ * the length of the subsequence match in the other, in our case:
+ * <pre>
+ *&nbsp;  ab    axb
+ *L= 2  +   3 = 5
+ * </pre>
+ * hence, the kernel yields 0.5^5 = 0,03125
+ *
+ * <h4>Example 2</h4>
+ * <pre>
+ *SSK(2,"ab","abb")=0.5^5 + 0.5^4 = 0,09375
+ *</pre>
+ * Here, we also have one subsequence of the length of 2 that both strings have
+ * in common, "ab".  The result of SSK is actually computed by summing over all
+ * values computed for each occurrence of a common subsequence match. In this
+ * example, there are two possible cases:
+ * <pre>
+ *ab    abb
+ *--    --  L=4
+ *--    - - L=5
+ * </pre>
+ * we have two matches, one of the length of 2+2=4, one of the length of 2+3=5, 
+ * so we get the result 0.5^5 + 0.5^4 = 0,09375.
+ *
+ * <h3>SSK-LP</h3>
+ * Without lambda pruning, the string kernel finds *all* common subsequences of
+ * the given length, whereas with lambda pruning, common subsequence matches
+ * that are too much stretched in both strings are not taken into account. It
+ * is argued that the value yielded for such a common subsequence is too low
+ * (<code>lambda ^(length[match_in_s] + length[match_in_t]</code>) . Tests have
+ * shown that a tremendous speedup can be achieved using this technique while
+ * suffering from very little quality loss. <br>
+ * Lambda pruning is parametrized by the maximum lambda exponent. It is a good
+ * idea to choose that value to be about 3 or 4 times the subsequence length as
+ * a rule of thumb. YMMV.
+ *
+ * <h4>Example 3</h4>
+ * Without lambda pruning, one common subsequence, 
+ * "AB" would be found in the following two strings. (With k=2)  
+ * <pre>
+ *SSK(2,"ab","axb")=0.5^14 = 0,00006103515625
+ *</pre>
+ * lambda pruning allows for the control of the match length. So, if m 
+ * (the maximum lambda exponent) is e.g. 8, these two strings would 
+ * yield a kernel value of 0:
+ * <pre>
+ *with lambda pruning:    SSK-LP(2,8,"AxxxxxxxxxB","AyB")= 0
+ *without lambda pruning: SSK(2,"AxxxxxxxxxB","AyB")= 0.5^14 = 0,00006103515625  
+ *</pre>
+ * This is because the exponent for lambda (=the length of the subsequence
+ * match) would be 14, which is &gt; 8. In Contrast, the next result is
+ * &gt; 0
+ *<pre>
+ *m=8
+ *SSK-LP(2,8,"AxxB","AyyB")=0.5^8 = 0,00390625
+ *</pre>
+ * because the lambda exponent would be 8, which is just accepted by lambda
+ * pruning.
+ *
+ * <h3>Normalization</h3>
+ * When the string kernel is used for its main purpose, as the kernel of a
+ * support vector machine, it is not normalized.  The normalized kernel can be
+ * switched on by -F (feature space normalization) but is much slower.  Like
+ * most unnormalized kernels, K(x,x) is not a fixed value, see the next
+ * example.
+ *
+ * <h4>Example 4</h4> 
+ *<pre>
+ *SSK(2,"ab","ab")=0.5^4 = 0.0625
+ *SSK(2,"AxxxxxxxxxB","AxxxxxxxxxB") = 12.761724710464478
+ *</pre>
+ * SSK is evaluated twice, each time for two identical strings. A good measure
+ * of similarity would produce the same value in both cases, which should  
+ * indicate the same level of similarity. The value of the normalized SSK would
+ * be 1.0 in both cases. So for the purpose of computing string similarity the
+ * normalized kernel should be used. For SVM the unnormalized kernel is usually
+ * sufficient.
+ *
+ * <h2>Complexity of SSK and SSK-LP</h2>
+ * The time complexity of this method (without lambda pruning and with an
+ * infinitely large cache) is<br> 
+ * <pre>O(k*|s|*|t|)</pre>
+ * Lambda Pruning has a complexity (without caching) of<br> 
+ * <pre>O(m*binom(m,k)^2*(|s|+n)*|t|)</pre> <br>  
+ * <pre>
+ *k...          subsequence length (ssl)
+ *s,t...        strings
+ *|s|...        length of string s
+ *binom(x,y)... binomial coefficient (x!/[(x-y)!y!])
+ *m...          maxLambdaExponent (ssl-max)
+ *</pre>
+ * 
+ * Keep in mind that execution time can increase fast for long strings 
+ * and big values for k, especially if you don't use lambda pruning.
+ * With lambda pruning, computation is usually so fast that switching
+ * on the cache leads to slower computation because of setup costs. Therefore
+ * caching is switched off for lambda pruning.
+ * <br>
+ * <br>
+ * For details and qualitative experiments about SSK, see [1] <br>
+ * For details about lambda pruning and performance comparison of SSK 
+ * and SSK-LP (SSK with lambda pruning), see [2]   
+ * Note that the complexity estimation in [2] assumes no caching of
+ * intermediate results, which has been implemented in the meantime and
+ * greatly improves the speed of the SSK without lambda pruning.
+ *<br>
+ *
+ *<h1>Notes for usage within Weka</h1>
+ * Only instances of the following form can be processed using string kernels:
+ * <pre>
+ *+----------+-------------+---------------+
+ *|attribute#|     0       |       1       |
+ *+----------+-------------+---------------+
+ *| content  | [text data] | [class label] |
+ *+----------------------------------------+
+ * ... or ...
+ *+----------+---------------+-------------+
+ *|attribute#|     0         |     1       |
+ *+----------+---------------+-------------+
+ *| content  | [class label] | [text data] |
+ *+----------------------------------------+
+ *</pre>
+ *
+ * @author Florian Kleedorfer (kleedorfer@austria.fm)
+ * @author Alexander K. Seewald (alex@seewald.at)
+ * @version $Revision: 5450 $
+ */
+public class StringKernel 
+  extends Kernel
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -4902954211202690123L;
+
+  /** The size of the cache (a prime number) */
+  private int m_cacheSize = 250007;
+
+  /** The size of the internal cache for intermediate results (a prime number) */
+  private int m_internalCacheSize = 200003;
+
+  /** The attribute number of the string attribute */
+  private int m_strAttr;
+
+  /** Kernel cache (i.e., cache for kernel evaluations) */
+  private double[] m_storage;
+  private long[] m_keys;
+
+  /** Counts the number of kernel evaluations. */
+  private int m_kernelEvals;
+
+  /** The number of instance in the dataset */
+  private int m_numInsts;
+
+  /** Pruning method: No Pruning */
+  public final static int PRUNING_NONE = 0;
+  /** Pruning method: Lambda See [2] for details. */
+  public final static int PRUNING_LAMBDA = 1;
+  /** Pruning methods */
+  public static final Tag [] TAGS_PRUNING = {
+    new Tag(PRUNING_NONE, "No pruning"),
+    new Tag(PRUNING_LAMBDA, "Lambda pruning"),
+  };
+  
+  /** the pruning method */
+  protected int m_PruningMethod = PRUNING_NONE;
+
+  /** the decay factor that penalizes non-continuous substring matches. See [1]
+   * for details. */
+  protected double m_lambda = 0.5;
+
+  /** The substring length */
+  private int m_subsequenceLength = 3;
+
+  /** The maximum substring length for lambda pruning */
+  private int m_maxSubsequenceLength = 9;
+
+  /** powers of lambda are prepared prior to kernel evaluations.
+   * all powers between 0 and this value are precalculated */
+  protected static final int MAX_POWER_OF_LAMBDA = 10000;
+
+  /** the precalculated powers of lambda */
+  protected double[] m_powersOflambda = null;
+
+  /** flag for switching normalization on or off. This defaults to false and
+   * can be turned on by the switch for feature space normalization in SMO
+   */
+  private boolean m_normalize = false;
+
+  /** private cache for intermediate results */	
+  private int maxCache; // is set in unnormalizedKernel(s1,s2)
+  private double[] cachekh;
+  private int[] cachekhK;
+  private double[] cachekh2;
+  private int[] cachekh2K;
+  /** cached indexes for private cache */
+  private int m_multX;
+  private int m_multY;
+  private int m_multZ;
+  private int m_multZZ;
+
+  private boolean m_useRecursionCache = true;
+
+  /**
+   * default constructor
+   */
+  public StringKernel() {
+    super();
+  }
+  
+  /**
+   * creates a new StringKernel object. Initializes the kernel cache and the
+   * 'lambda cache', i.e. the precalculated powers of lambda from lambda^2 to
+   * lambda^MAX_POWER_OF_LAMBDA
+   *
+   * @param data		the dataset to use
+   * @param cacheSize		the size of the cache
+   * @param subsequenceLength	the subsequence length
+   * @param lambda		the lambda value
+   * @param debug		whether to output debug information
+   * @throws Exception		if something goes wrong
+   */
+  public StringKernel(Instances data, int cacheSize, int subsequenceLength,
+    double lambda, boolean debug) throws Exception {
+
+    setDebug(debug);
+    setCacheSize(cacheSize);
+    setInternalCacheSize(200003);
+    setSubsequenceLength(subsequenceLength);
+    setMaxSubsequenceLength(-1);
+    setLambda(lambda);
+    
+    buildKernel(data);
+  }
+  
+  /**
+   * Returns a string describing the kernel
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implementation of the subsequence kernel (SSK) as described in [1] "
+      + "and of the subsequence kernel with lambda pruning (SSK-LP) as "
+      + "described in [2].\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Huma Lodhi and Craig Saunders and John Shawe-Taylor and Nello Cristianini and Christopher J. C. H. Watkins");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.TITLE, "Text Classification using String Kernels");
+    result.setValue(Field.JOURNAL, "Journal of Machine Learning Research");
+    result.setValue(Field.VOLUME, "2");
+    result.setValue(Field.PAGES, "419-444");
+    result.setValue(Field.HTTP, "http://www.jmlr.org/papers/v2/lodhi02a.html");
+
+    additional = result.add(Type.TECHREPORT);
+    additional.setValue(Field.AUTHOR, "F. Kleedorfer and A. Seewald");
+    additional.setValue(Field.YEAR, "2005");
+    additional.setValue(Field.TITLE, "Implementation of a String Kernel for WEKA");
+    additional.setValue(Field.INSTITUTION, "Oesterreichisches Forschungsinstitut fuer Artificial Intelligence");
+    additional.setValue(Field.ADDRESS, "Wien, Austria");
+    additional.setValue(Field.NUMBER, "TR-2005-13");
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		en;
+    String		desc;
+    String		param;
+    int			i;
+    SelectedTag		tag;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    desc  = "";
+    param = "";
+    for (i = 0; i < TAGS_PRUNING.length; i++) {
+      if (i > 0)
+	param += "|";
+      tag = new SelectedTag(TAGS_PRUNING[i].getID(), TAGS_PRUNING);
+      param += "" + tag.getSelectedTag().getID();
+      desc  +=   "\t" + tag.getSelectedTag().getID() 
+      	       + " = " + tag.getSelectedTag().getReadable()
+      	       + "\n";
+    }
+
+    result.addElement(new Option(
+	"\tThe pruning method to use:\n"
+	+ desc
+	+ "\t(default: " + PRUNING_NONE + ")",
+	"P", 1, "-P <" + param + ">"));
+
+    result.addElement(new Option(
+	"\tThe size of the cache (a prime number).\n"
+	+ "\t(default: 250007)",
+	"C", 1, "-C <num>"));
+
+    result.addElement(new Option(
+	"\tThe size of the internal cache (a prime number).\n"
+	+ "\t(default: 200003)",
+	"IC", 1, "-IC <num>"));
+
+    result.addElement(new Option(
+	"\tThe lambda constant. Penalizes non-continuous subsequence\n"
+	+ "\tmatches. Must be in (0,1).\n"
+	+ "\t(default: 0.5)",
+	"L", 1, "-L <num>"));
+
+    result.addElement(new Option(
+	"\tThe length of the subsequence.\n"
+	+ "\t(default: 3)",
+	"ssl", 1, "-ssl <num>"));
+
+    result.addElement(new Option(
+	"\tThe maximum length of the subsequence.\n"
+	+ "\t(default: 9)",
+	"ssl-max", 1, "-ssl-max <num>"));
+
+    result.addElement(new Option(
+	"\tUse normalization.\n"
+	+ "\t(default: no)",
+	"N", 0, "-N"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -P &lt;0|1&gt;
+   *  The pruning method to use:
+   *  0 = No pruning
+   *  1 = Lambda pruning
+   *  (default: 0)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number).
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -IC &lt;num&gt;
+   *  The size of the internal cache (a prime number).
+   *  (default: 200003)</pre>
+   * 
+   * <pre> -L &lt;num&gt;
+   *  The lambda constant. Penalizes non-continuous subsequence
+   *  matches. Must be in (0,1).
+   *  (default: 0.5)</pre>
+   * 
+   * <pre> -ssl &lt;num&gt;
+   *  The length of the subsequence.
+   *  (default: 3)</pre>
+   * 
+   * <pre> -ssl-max &lt;num&gt;
+   *  The maximum length of the subsequence.
+   *  (default: 9)</pre>
+   * 
+   * <pre> -N
+   *  Use normalization.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setPruningMethod(
+	  new SelectedTag(Integer.parseInt(tmpStr), TAGS_PRUNING));
+    else
+      setPruningMethod(
+	  new SelectedTag(PRUNING_NONE, TAGS_PRUNING));
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setCacheSize(Integer.parseInt(tmpStr));
+    else
+      setCacheSize(250007);
+    
+    tmpStr = Utils.getOption("IC", options);
+    if (tmpStr.length() != 0)
+      setInternalCacheSize(Integer.parseInt(tmpStr));
+    else
+      setInternalCacheSize(200003);
+    
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0)
+      setLambda(Double.parseDouble(tmpStr));
+    else
+      setLambda(0.5);
+    
+    tmpStr = Utils.getOption("ssl", options);
+    if (tmpStr.length() != 0)
+      setSubsequenceLength(Integer.parseInt(tmpStr));
+    else
+      setSubsequenceLength(3);
+    
+    tmpStr = Utils.getOption("ssl-max", options);
+    if (tmpStr.length() != 0)
+      setMaxSubsequenceLength(Integer.parseInt(tmpStr));
+    else
+      setMaxSubsequenceLength(9);
+
+    setUseNormalization(Utils.getFlag('N', options));
+
+    if (getMaxSubsequenceLength()<2*getSubsequenceLength()) {
+      throw new IllegalArgumentException("Lambda Pruning forbids even contiguous substring matches! " +
+      "Use a bigger value for ssl-max (at least 2*ssl).");
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Kernel.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-P");
+    result.add("" + m_PruningMethod);
+
+    result.add("-C");
+    result.add("" + getCacheSize());
+
+    result.add("-IC");
+    result.add("" + getInternalCacheSize());
+
+    result.add("-L");
+    result.add("" + getLambda());
+
+    result.add("-ssl");
+    result.add("" + getSubsequenceLength());
+
+    result.add("-ssl-max");
+    result.add("" + getMaxSubsequenceLength());
+
+    if (getUseNormalization())
+      result.add("-L");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String pruningMethodTipText() {
+    return "The pruning method.";
+  }
+
+  /**
+   * Sets the method used to for pruning. 
+   *
+   * @param value 	the pruning method to use.
+   */
+  public void setPruningMethod(SelectedTag value) {
+    if (value.getTags() == TAGS_PRUNING)
+      m_PruningMethod = value.getSelectedTag().getID();
+  }
+
+  /**
+   * Gets the method used for pruning. 
+   *
+   * @return 		the pruning method to use.
+   */
+  public SelectedTag getPruningMethod() {
+    return new SelectedTag(m_PruningMethod, TAGS_PRUNING);
+  }
+
+
+  /**
+   * Sets the size of the cache to use (a prime number)
+   * 
+   * @param value	the size of the cache
+   */
+  public void setCacheSize(int value) {
+    if (value >= 0) {
+      m_cacheSize = value;
+      clean();
+    }
+    else {
+      System.out.println(
+	  "Cache size cannot be smaller than 0 (provided: " + value + ")!");
+    }
+  }
+  
+  /**
+   * Gets the size of the cache
+   * 
+   * @return 		the cache size
+   */
+  public int getCacheSize() {
+    return m_cacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String cacheSizeTipText() {
+    return "The size of the cache (a prime number).";
+  }
+
+  /** 
+   * sets the size of the internal cache for intermediate results. Memory
+   * consumption is about 16x this amount in bytes. Only use when lambda
+   * pruning is switched off.
+   *
+   * @param value	the size of the internal cache
+   */
+  public void setInternalCacheSize(int value) {
+    if (value >= 0) {
+      m_internalCacheSize = value;
+      clean();
+    } else {
+      System.out.println(
+	  "Cache size cannot be smaller than 0 (provided: " + value + ")!");
+    }
+  }
+  
+  /**
+   * Gets the size of the internal cache
+   * 
+   * @return 		the cache size
+   */
+  public int getInternalCacheSize() {
+    return m_internalCacheSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String internalCacheSizeTipText() {
+    return "The size of the internal cache (a prime number).";
+  }
+
+  /**
+   * Sets the length of the subsequence.
+   * 
+   * @param value	the length
+   */
+  public void setSubsequenceLength(int value) {
+    m_subsequenceLength = value;
+  }
+  
+  /**
+   * Returns the length of the subsequence
+   * 
+   * @return		the length
+   */
+  public int getSubsequenceLength() {
+    return m_subsequenceLength;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String subsequenceLengthTipText() {
+    return "The subsequence length.";
+  }
+
+  /**
+   * Sets the maximum length of the subsequence.
+   * 
+   * @param value	the maximum length
+   */
+  public void setMaxSubsequenceLength(int value) {
+    m_maxSubsequenceLength = value;
+  }
+  
+  /**
+   * Returns the maximum length of the subsequence
+   * 
+   * @return		the maximum length
+   */
+  public int getMaxSubsequenceLength() {
+    return m_maxSubsequenceLength;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxSubsequenceLengthTipText() {
+    return "The maximum subsequence length (theta in the paper)";
+  }
+  
+  /**
+   * Sets the lambda constant used in the string kernel
+   * 
+   * @param value	the lambda value to use
+   */
+  public void setLambda(double value) {
+    m_lambda = value;
+  }
+  
+  /**
+   * Gets the lambda constant used in the string kernel
+   * 
+   * @return		the current lambda constant
+   */  
+  public double getLambda() {
+    return m_lambda;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String lambdaTipText(){
+    return "Penalizes non-continuous subsequence matches, from (0,1)";
+  }
+
+  /**
+   * Sets whether to use normalization.
+   * Each time this value is changed, the kernel cache is cleared.
+   *
+   * @param value	whether to use normalization
+   */
+  public void setUseNormalization(boolean value) {
+    if  (value != m_normalize) 
+      clean();
+    
+    m_normalize = value;
+  }
+  
+  /**
+   * Returns whether normalization is used.
+   * 
+   * @return		true if normalization is used
+   */
+  public boolean getUseNormalization() {
+    return m_normalize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useNormalizationTipText(){
+    return "Whether to use normalization.";
+  }
+
+  /**
+   * Computes the result of the kernel function for two instances.
+   * If id1 == -1, eval use inst1 instead of an instance in the dataset.
+   *
+   * @param id1 the index of the first instance in the dataset
+   * @param id2 the index of the second instance in the dataset
+   * @param inst1 the instance corresponding to id1 (used if id1 == -1)
+   * @return the result of the kernel function
+   * @throws Exception if something goes wrong
+   */
+  public double eval(int id1, int id2, Instance inst1) throws Exception {
+    if (m_Debug && id1>-1 && id2>-1) {
+      System.err.println("\nEvaluation of string kernel for");
+      System.err.println(m_data.instance(id1).stringValue(m_strAttr));
+      System.err.println("and");
+      System.err.println(m_data.instance(id2).stringValue(m_strAttr));
+    }
+
+    //the normalized kernel returns 1 for comparison of 
+    //two identical strings
+    if (id1 == id2 && m_normalize) 
+      return 1.0;
+
+    double result = 0;
+    long key = -1;
+    int location = -1;
+
+    // we can only cache if we know the indexes
+    if ((id1 >= 0) && (m_keys != null)) {
+      if (id1 > id2) {
+        key = (long)id1 * m_numInsts + id2;
+      } else {
+        key = (long)id2 * m_numInsts + id1;
+      }
+      if (key < 0) {
+        throw new Exception("Cache overflow detected!");
+      }
+      location = (int)(key % m_keys.length);
+      if (m_keys[location] == (key + 1)) {
+        if (m_Debug) 
+          System.err.println("result (cached): " + m_storage[location]);
+        return m_storage[location];
+      }
+    }
+
+    m_kernelEvals++;
+    long start = System.currentTimeMillis();
+
+    Instance inst2 = m_data.instance(id2);
+    char[] s1 = inst1.stringValue(m_strAttr).toCharArray();
+    char[] s2 = inst2.stringValue(m_strAttr).toCharArray();
+
+    // prevent the kernel from returning NaN
+    if (s1.length == 0 || s2.length == 0) return 0;
+
+    if (m_normalize) {
+      result = normalizedKernel(s1,s2);
+    } else {
+      result = unnormalizedKernel(s1, s2);
+    }
+
+    if (m_Debug) {
+      long duration = System.currentTimeMillis() - start;
+      System.err.println("result: " + result);
+      System.err.println("evaluation time:" + duration +"\n");
+    }
+
+    // store result in cache
+    if (key != -1){
+      m_storage[location] = result;
+      m_keys[location] = (key + 1);
+    }
+    return result;
+  }
+
+  /**
+   * Frees the memory used by the kernel.
+   * (Useful with kernels which use cache.)
+   * This function is called when the training is done.
+   * i.e. after that, eval will be called with id1 == -1.
+   */
+  public void clean() {
+    m_storage = null;
+    m_keys = null;
+  }
+
+  /**
+   * Returns the number of kernel evaluation performed.
+   *
+   * @return the number of kernel evaluation performed.
+   */
+  public int numEvals() {
+    return m_kernelEvals;
+  }
+
+  /**
+   * Returns the number of dot product cache hits.
+   *
+   * @return the number of dot product cache hits, or -1 if not supported by
+   * this kernel.
+   */
+  public int numCacheHits() {
+    // TODO: implement!
+    return -1;
+  }
+  
+  /**
+   * evaluates the normalized kernel between s and t. See [1] for details about
+   * the normalized SSK. 
+   *
+   * @param s first input string
+   * @param t second input string
+   * @return a double indicating their distance, or similarity
+   */
+  public double normalizedKernel(char[] s, char[] t){
+    double k1 = unnormalizedKernel(s, s);
+    double k2 = unnormalizedKernel(t, t);
+    double normTerm = Math.sqrt( k1*k2 );
+    return unnormalizedKernel(s, t) / normTerm;
+  }
+
+  /**
+   * evaluates the unnormalized kernel between s and t. See [1] for details
+   * about the unnormalized SSK.
+   *
+   * @param s first input string
+   * @param t second input string
+   * @return a double indicating their distance, or similarity
+   */
+  public double unnormalizedKernel(char[] s, char[] t){
+    if (t.length > s.length) {
+      //swap because the algorithm is faster if s is
+      //the longer string
+      char[] buf = s;
+      s = t;
+      t = buf;
+    }
+    if (m_PruningMethod == PRUNING_NONE) {
+      m_multX=(s.length+1)*(t.length+1); 
+      m_multY=(t.length+1); 
+      m_multZ=1;
+      maxCache = m_internalCacheSize;
+      if (maxCache==0) {
+	maxCache=(m_subsequenceLength+1)*m_multX;
+      }
+      else if ((m_subsequenceLength+1)*m_multX<maxCache) { 
+	maxCache=(m_subsequenceLength+1)*m_multX; 
+      }
+      m_useRecursionCache=true;
+      cachekhK = new int[maxCache];
+      cachekh2K = new int[maxCache];
+      cachekh = new double[maxCache];
+      cachekh2 = new double[maxCache];
+    } else if (m_PruningMethod == PRUNING_LAMBDA) {
+      maxCache=0; 
+      m_useRecursionCache=false;
+    }
+
+    double res;
+    if (m_PruningMethod == PRUNING_LAMBDA) {
+      res = kernelLP(
+              m_subsequenceLength,s,s.length-1,t,t.length-1,
+              m_maxSubsequenceLength);
+    } else {
+      res = kernel(
+              m_subsequenceLength,s,s.length-1, t, t.length-1);
+    }
+    cachekh = null;
+    cachekhK = null;
+    cachekh2 = null;
+    cachekh2K = null;
+    
+    return res;
+  }
+
+  /**
+   * Recursion-ending function that is called at the end of each 
+   * recursion branch.
+   * 
+   * @param n
+   * @return
+   */
+  protected double getReturnValue(int n){
+    if (n == 0) return 1; else return 0;
+  }
+
+  /**
+   * the kernel function (Kn). This function performs the outer loop
+   * character-wise over the first input string s. For each character
+   * encountered, a recursion branch is started that identifies all
+   * subsequences in t starting with that character. <br> See [1] for details
+   * but note that this code is optimized and may be hard to recognize.
+   * 
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @return a double indicating the distance or similarity between s and t, 
+   * according to and depending on the initial value for n.
+   */
+  protected double kernel(int n, char[] s,int endIndexS, char[] t, 
+    int endIndexT) {
+
+    //normal recursion ending case
+    if (Math.min(endIndexS+1,endIndexT+1) < n) return getReturnValue(n);
+
+    //accumulate all recursion results in one:
+    double result = 0;
+
+    //the tail-recursive function defined in [1] is turned into a
+    //loop here, preventing stack overflows.
+    //skim s from back to front
+    for (int iS=endIndexS; iS > n-2; iS--) {
+      double buf = 0;
+      //let the current character in s be x 
+      char x = s[iS];
+      // iterate over all occurrences of x in t
+      for (int j=0; j <= endIndexT; j++) {
+        if (t[j] == x){
+          //this is a match for the current character, hence
+          //1. use previous chars in both strings (iS-1, j-1)
+          //2. decrement the remainingMatchLength (n-1)
+          //and start a recursion branch for these parameters
+          buf += kernelHelper(n-1,s,iS-1, t, j-1);
+        }
+      }
+      //ok, all occurrences of x in t have been found
+      //multiply the result with lambda^2
+      //  (one lambda for x, and the other for all matches of x in t)
+      result += buf * m_powersOflambda[2];
+    }
+    return result;
+  }
+
+  /**
+   * The kernel helper function, called K' in [1] and [2].
+   *
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @return a partial result for K
+   */
+  protected double kernelHelper (int n, char[] s,int endIndexS, char[] t, 
+    int endIndexT) {
+
+    //recursion ends if the current subsequence has maximal length, 
+    //which is the case here
+    if (n <= 0 ) {
+      return getReturnValue(n);
+    }
+
+    //recursion ends, too, if the current subsequence is shorter than
+    //maximal length, but there is no chance that it will reach maximal length.
+    //in this case, normally 0 is returned, but the EXPERIMENTAL 
+    //minSubsequenceLength feature allows shorter subsequence matches
+    //also to contribute
+    if (Math.min(endIndexS+1,endIndexT+1) < n) {
+      return getReturnValue(n);
+    }
+    int adr = 0;
+    if (m_useRecursionCache) {
+      adr=m_multX*n+m_multY*endIndexS+m_multZ*endIndexT;
+      if ( cachekhK[adr % maxCache] == adr+1) return cachekh[adr % maxCache];
+    }
+
+    //the tail-recursive function defined in [1] is turned into a
+    //loop here, preventing stack overflows.
+    //loop over s, nearly from the start (skip the first n-1 characters)
+    //and only up until endIndexS, and recursively apply K''. Thus, every
+    //character between n-1 and endIndexS in s is counted once as 
+    //being part of the subsequence match and once just as a gap. 
+    //In both cases lambda is multiplied with the result.
+    double result = 0;
+    /*
+       for (int iS = n-1; iS <= endIndexS;iS++) {
+       result *= m_lambda;
+       result += kernelHelper2(n,s,iS, t, endIndexT);
+       }
+       if (m_useRecursionCache) {
+       cachekhK[adr % maxCache]=adr+1; cachekh[adr % maxCache]=result;
+       }
+       return result;
+       */
+    /* ^^^ again, above code segment does not store some intermediate results... */
+    result = m_lambda*kernelHelper(n,s,endIndexS-1,t,endIndexT)
+             + kernelHelper2(n,s,endIndexS,t,endIndexT);
+    if (m_useRecursionCache) {
+      cachekhK[adr % maxCache]=adr+1; cachekh[adr % maxCache]=result;
+    }
+    return result;
+  }
+
+  /** 
+   * helper function for the evaluation of the kernel K'' see section
+   * 'Efficient Computation of SSK' in [1]
+   *
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @return a partial result for K'
+   */
+  protected double kernelHelper2(int n, char[] s, int endIndexS, char[] t, 
+    int endIndexT) {
+
+    //recursion ends if one of the indices in both strings is <0
+    if (endIndexS <0 || endIndexT <0) {
+      return getReturnValue(n);
+    }
+
+    int adr = 0;
+    if (m_useRecursionCache) {
+      adr=m_multX*n+m_multY*endIndexS+m_multZ*endIndexT;
+      if ( cachekh2K[adr % maxCache] == adr+1) return cachekh2[adr % maxCache];
+    }
+
+    //spot the last character in s, we'll need it
+    char x = s[endIndexS];
+
+    //recurse if the last characters of s and t, x (and y) are identical.
+    //which is an easy case: just add up two recursions, 
+    // 1. one that counts x and y as a part of the subsequence match
+    //	 -> n, endIndexS and endIndexT are decremented for next recursion level
+    // 	 -> lambda^2 is multiplied with the result to account for the length
+    //      of 2 that has been added to the length of the subsequence match
+    //      by accepting x and y.
+    // 2. one that counts y as a gap in the match 
+    //   -> only endIndexT is decremented for next recursion level
+    // 	 -> lambda is multiplied with the result to account for the length
+    //      of 1 that has been added to the length of the subsequence match
+    //		by omitting y.
+    if (x == t[endIndexT]) {
+      double ret =  m_lambda * (kernelHelper2(n,s,endIndexS, t, endIndexT-1)
+          + m_lambda * kernelHelper(n-1,s,endIndexS-1, t, endIndexT-1));
+      if (m_useRecursionCache) {
+        cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret;
+      }
+      return ret;
+    } else {
+      double ret = m_lambda*kernelHelper2(n,s,endIndexS,t,endIndexT-1);
+      if (m_useRecursionCache) {
+        cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret;
+      }
+      return ret;
+    }
+
+    //look for x in t from back to front. 
+    //this is actually an optimization from [1] that spares unneccessary
+    //recursions iff
+    //x is actually found in t, but not at the last position.
+    /*
+       int i;
+       int threshold = n>0?n-1:0;
+       for (i=endIndexT-1; i >= threshold;i--) {
+       if (x == t[i]) {
+       double ret=getPowerOfLambda(endIndexT-i) * kernelHelper2(n,s,endIndexS, t, i);
+       if (m_useRecursionCache) {
+       cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret;
+       }
+       return ret;
+       }
+       }
+       */		
+    //end the recursion if x is not found in t.
+    /*        double ret = getReturnValue(n);
+              if (m_useRecursionCache) {
+              cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret;
+              }
+              return ret;*/
+  }
+
+  /**
+   * the kernel function K explained in [1] using lambda pruning, explained in
+   * [2].  An additional parameter is introduced, which denotes the maximum
+   * length of a subsequence match. This allows for the control of how relaxed
+   * the subsequence matches are. <br>
+   *
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @param remainingMatchLength actually the initial value for
+   * maxLambdaExponent
+   * @return a double indicating the distance or similarity between s and t, 
+   * according to and depending on the initial value for n. 
+   */
+  protected double kernelLP(int n, char[] s, int endIndexS,char[] t,
+      int endIndexT,int remainingMatchLength) {
+    //see code docs in kernel()
+    if (Math.min(endIndexS+1,endIndexT +1) < n) {
+      return getReturnValue(n);
+    }
+    //lambda pruning check 
+    //stops recursion if the match is so long that the resulting
+    //power of lambda is smaller than minLambda
+    //if lambda pruning is not used, the remainingMatchLength is < 0
+    //and this check never stops the recursion
+    if (remainingMatchLength == 0) return getReturnValue(n);
+    double result = 0;
+    //see code docs in kernel()
+    for (int iS =endIndexS; iS > n-2; iS--) {
+      double buf = 0;
+      char x = s[iS];
+      for (int j=0; j <= endIndexT; j++) {
+        if (t[j] == x){
+          //both t[j] and x are considered part of the subsequence match, hence
+          //subtract 2 from the remainingMatchLength
+          buf += kernelHelperLP(n-1,s,iS-1,t,j-1,remainingMatchLength-2);
+        }
+      }
+      result += buf * m_powersOflambda[2];
+    }
+    return result;
+  }
+
+  /**
+   * helper function for the evaluation of the kernel (K'n) using lambda pruning
+   *
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @param remainingMatchLength the number of characters that may still be
+   * used 
+   * for matching (i.e. gaps + matches in both strings)
+   * @return a partial result for K 
+   */
+  protected double kernelHelperLP (int n, char[] s, int endIndexS,char[] t,
+    int endIndexT,int remainingMatchLength) {
+
+    //see code docs in kernelHelper()
+    if (n == 0) {
+      return getReturnValue(n);      
+
+    }
+    //see code docs in kernelHelper()
+    if (Math.min(endIndexS+1,endIndexT +1) < n) {;
+      return getReturnValue(n);
+    }
+
+    //lambda pruning check
+    //stops recursion if the match is so long that the resulting
+    //power of lambda is smaller than minLambda
+    //if lambda pruning is not used, the remainingMatchLength is < 0
+    //and this check never stops the recursion
+    if (remainingMatchLength < 2*n) { 
+      return getReturnValue(n);
+    }
+    int adr=0;
+    if (m_useRecursionCache) {
+      adr = m_multX*n+m_multY*endIndexS+m_multZ*endIndexT 
+            + m_multZZ * remainingMatchLength;
+      if (cachekh2K[adr % maxCache]==adr+1) { 
+        return cachekh2[adr % maxCache]; 
+      }
+    }
+
+    int rml = 0; //counts the remaining match length
+    double result = 0;
+    //see code docs in kernelHelper()
+    //difference to implementation in kernelHelper:
+    //*)choose different starting point, which is found counting
+    //the maximal remaining match length from endIndexS.
+    //*)keep track of the remaining match length, rml, which is
+    //  incremented each loop
+    for (int iS = (endIndexS-remainingMatchLength); iS <= endIndexS;iS++) {
+      result *= m_lambda;
+      result += kernelHelper2LP(n,s,iS, t, endIndexT,rml++);
+    }
+
+    if (m_useRecursionCache && endIndexS >= 0 && endIndexT >= 0 && n >= 0) {
+      cachekhK[adr % maxCache]=adr+1; cachekh[adr % maxCache]=result; 
+    }
+    return result;
+  }
+
+  /**
+   * helper function for the evaluation of the kernel (K''n) using lambda
+   * pruning
+   *
+   * @param n the current length of the matching subsequence
+   * @param s first string, as a char array
+   * @param t second string, as a char array
+   * @param endIndexS the portion of s currently regarded is s[1:endIndexS]
+   * @param endIndexT the portion of t currently regarded is t[1:endIndexT]
+   * @param remainingMatchLength the number of characters that may still be
+   * used 
+   * for matching (i.e. gaps + matches in both strings)
+   * @return a partial result for K' 
+   */
+  protected double kernelHelper2LP(int n, char[] s, int endIndexS,char[] t,
+    int endIndexT,int remainingMatchLength) {
+
+    //lambda pruning check
+    //stops recursion if the match is so long that the resulting
+    //power of lambda is smaller than minLambda
+    //if lambda pruning is not used, the remainingMatchLength is < 0
+    //and this check never stops the recursion
+    //if (remainingMatchLength <= 0) return 0;
+    if (remainingMatchLength < 2*n) return getReturnValue(n);
+
+    //see code docs in kernelHelper2()
+    if (endIndexS <0 || endIndexT <0) return getReturnValue(n);
+    int adr=0;
+    if (m_useRecursionCache){
+      adr = m_multX*n+m_multY*endIndexS+m_multZ*endIndexT
+            + m_multZZ * remainingMatchLength;
+      if (cachekh2K[adr % maxCache]==adr+1) { 
+        return cachekh2[adr % maxCache]; 
+      }
+    }
+
+    char x = s[endIndexS];
+    if (x == t[endIndexT]) {
+      double ret = 
+        m_lambda 
+        * (kernelHelper2LP(n,s,endIndexS,t,endIndexT-1,remainingMatchLength-1)
+        + m_lambda 
+        * kernelHelperLP(n-1,s,endIndexS-1,t,endIndexT-1,remainingMatchLength-2));
+      if (m_useRecursionCache && endIndexS >= 0 && endIndexT >= 0 && n >= 0) {
+        cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret; }
+      return ret;
+    }
+
+    //see code docs in kernelHelper()
+    //differences to implementation in kernelHelper():
+    //*) choose a different ending point for the loop
+    //   based on the remaining match length
+    int i;
+    int minIndex = endIndexT - remainingMatchLength;
+    if (minIndex < 0) minIndex = 0;
+    for (i=endIndexT; i >= minIndex;i--) {
+      if (x == t[i]) {
+        int skipLength = endIndexT -i;
+        double ret = getPowerOfLambda(skipLength) *
+          kernelHelper2LP(n,s,endIndexS,t,i,remainingMatchLength-skipLength);
+        if (m_useRecursionCache && endIndexS >= 0 && endIndexT >= 0 && n >= 0) { 
+          cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret; 
+        }
+        return ret;
+      }
+    }
+    double ret = getReturnValue(n);
+    if (m_useRecursionCache && endIndexS >= 0 && endIndexT >= 0 && n >= 0) { 
+      cachekh2K[adr % maxCache]=adr+1; cachekh2[adr % maxCache]=ret; 
+    }
+    return ret;
+  }
+
+  /**
+   * precalculates small powers of lambda to speed up the kernel evaluation
+   *
+   * @return		the powers
+   */
+  private double[] calculatePowersOfLambda(){
+    double[] powers = new double[MAX_POWER_OF_LAMBDA+1];
+    powers[0] = 1.0;
+    double val = 1.0;
+    for (int i = 1; i<=MAX_POWER_OF_LAMBDA;i++) {
+      val *= m_lambda;
+      powers[i] = val;
+    }
+    return powers;
+  }
+
+  /**
+   * retrieves a power of lambda from the lambda cache or calculates it
+   * directly
+   *
+   * @param exponent	the exponent to calculate
+   * @return 		the exponent-th power of lambda
+   */
+  private double getPowerOfLambda(int exponent){
+    if (exponent > MAX_POWER_OF_LAMBDA) 
+      return Math.pow(m_lambda,exponent);
+    
+    if (exponent < 0) 
+      throw new IllegalArgumentException(
+          "only positive powers of lambda may be computed");
+
+    return m_powersOflambda[exponent];
+  }
+
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+
+    m_kernelEvals    = 0;
+    // take the first string attribute
+    m_strAttr        = -1;
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i == data.classIndex())
+	continue;
+      if (data.attribute(i).type() == Attribute.STRING) {
+	m_strAttr = i;
+	break;
+      }
+    }
+    m_numInsts       = m_data.numInstances();
+    m_storage        = new double[m_cacheSize];
+    m_keys           = new long[m_cacheSize];
+    m_powersOflambda = calculatePowersOfLambda();
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+  /**
+   * builds the kernel with the given data.
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong, e.g., the data does not
+   * 			consist of one string attribute and the class
+   */
+  public void buildKernel(Instances data) throws Exception {
+    super.buildKernel(data);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5450 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/IB1.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/IB1.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/IB1.java	(revision 29)
@@ -0,0 +1,353 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IB1.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * Nearest-neighbour classifier. Uses normalized Euclidean distance to find the training instance closest to the given test instance, and predicts the same class as this training instance. If multiple instances have the same (smallest) distance to the test instance, the first one found is used.<br/>
+ * <br/>
+ * For more information, see <br/>
+ * <br/>
+ * D. Aha, D. Kibler (1991). Instance-based learning algorithms. Machine Learning. 6:37-66.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Aha1991,
+ *    author = {D. Aha and D. Kibler},
+ *    journal = {Machine Learning},
+ *    pages = {37-66},
+ *    title = {Instance-based learning algorithms},
+ *    volume = {6},
+ *    year = {1991}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stuart Inglis (singlis@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class IB1 
+  extends AbstractClassifier 
+  implements UpdateableClassifier, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -6152184127304895851L;
+  
+  /** The training instances used for classification. */
+  private Instances m_Train;
+
+  /** The minimum values for numeric attributes. */
+  private double [] m_MinArray;
+
+  /** The maximum values for numeric attributes. */
+  private double [] m_MaxArray;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Nearest-neighbour classifier. Uses normalized Euclidean distance to " 
+      + "find the training instance closest to the given test instance, and predicts "
+      + "the same class as this training instance. If multiple instances have "
+      + "the same (smallest) distance to the test instance, the first one found is "
+      + "used.\n\n"
+      + "For more information, see \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "D. Aha and D. Kibler");
+    result.setValue(Field.YEAR, "1991");
+    result.setValue(Field.TITLE, "Instance-based learning algorithms");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "6");
+    result.setValue(Field.PAGES, "37-66");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_Train = new Instances(instances, 0, instances.numInstances());
+
+    m_MinArray = new double [m_Train.numAttributes()];
+    m_MaxArray = new double [m_Train.numAttributes()];
+    for (int i = 0; i < m_Train.numAttributes(); i++) {
+      m_MinArray[i] = m_MaxArray[i] = Double.NaN;
+    }
+    Enumeration enu = m_Train.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      updateMinMax((Instance) enu.nextElement());
+    }
+  }
+
+  /**
+   * Updates the classifier.
+   *
+   * @param instance the instance to be put into the classifier
+   * @throws Exception if the instance could not be included successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+  
+    if (m_Train.equalHeaders(instance.dataset()) == false) {
+      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    }
+    if (instance.classIsMissing()) {
+      return;
+    }
+    m_Train.add(instance);
+    updateMinMax(instance);
+  }
+
+  /**
+   * Classifies the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted class for the instance 
+   * @throws Exception if the instance can't be classified
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    
+    if (m_Train.numInstances() == 0) {
+      throw new Exception("No training instances!");
+    }
+
+    double distance, minDistance = Double.MAX_VALUE, classValue = 0;
+    updateMinMax(instance);
+    Enumeration enu = m_Train.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      Instance trainInstance = (Instance) enu.nextElement();
+      if (!trainInstance.classIsMissing()) {
+	distance = distance(instance, trainInstance);
+	if (distance < minDistance) {
+	  minDistance = distance;
+	  classValue = trainInstance.classValue();
+	}
+      }
+    }
+
+    return classValue;
+  }
+
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+
+    return ("IB1 classifier");
+  }
+
+  /**
+   * Calculates the distance between two instances
+   *
+   * @param first the first instance
+   * @param second the second instance
+   * @return the distance between the two given instances
+   */          
+  private double distance(Instance first, Instance second) {
+    
+    double diff, distance = 0;
+
+    for(int i = 0; i < m_Train.numAttributes(); i++) { 
+      if (i == m_Train.classIndex()) {
+	continue;
+      }
+      if (m_Train.attribute(i).isNominal()) {
+
+	// If attribute is nominal
+	if (first.isMissing(i) || second.isMissing(i) ||
+	    ((int)first.value(i) != (int)second.value(i))) {
+	  distance += 1;
+	}
+      } else {
+	
+	// If attribute is numeric
+	if (first.isMissing(i) || second.isMissing(i)){
+	  if (first.isMissing(i) && second.isMissing(i)) {
+	    diff = 1;
+	  } else {
+	    if (second.isMissing(i)) {
+	      diff = norm(first.value(i), i);
+	    } else {
+	      diff = norm(second.value(i), i);
+	    }
+	    if (diff < 0.5) {
+	      diff = 1.0 - diff;
+	    }
+	  }
+	} else {
+	  diff = norm(first.value(i), i) - norm(second.value(i), i);
+	}
+	distance += diff * diff;
+      }
+    }
+    
+    return distance;
+  }
+    
+  /**
+   * Normalizes a given value of a numeric attribute.
+   *
+   * @param x the value to be normalized
+   * @param i the attribute's index
+   * @return the normalized value
+   */
+  private double norm(double x,int i) {
+
+    if (Double.isNaN(m_MinArray[i])
+	|| Utils.eq(m_MaxArray[i], m_MinArray[i])) {
+      return 0;
+    } else {
+      return (x - m_MinArray[i]) / (m_MaxArray[i] - m_MinArray[i]);
+    }
+  }
+
+  /**
+   * Updates the minimum and maximum values for all the attributes
+   * based on a new instance.
+   *
+   * @param instance the new instance
+   */
+  private void updateMinMax(Instance instance) {
+    
+    for (int j = 0;j < m_Train.numAttributes(); j++) {
+      if ((m_Train.attribute(j).isNumeric()) && (!instance.isMissing(j))) {
+	if (Double.isNaN(m_MinArray[j])) {
+	  m_MinArray[j] = instance.value(j);
+	  m_MaxArray[j] = instance.value(j);
+	} else {
+	  if (instance.value(j) < m_MinArray[j]) {
+	    m_MinArray[j] = instance.value(j);
+	  } else {
+	    if (instance.value(j) > m_MaxArray[j]) {
+	      m_MaxArray[j] = instance.value(j);
+	    }
+	  }
+	}
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line arguments for evaluation
+   * (see Evaluation).
+   */
+  public static void main(String [] argv) {
+    runClassifier(new IB1(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/IBk.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/IBk.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/IBk.java	(revision 29)
@@ -0,0 +1,1056 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IBk.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.neighboursearch.LinearNNSearch;
+import weka.core.neighboursearch.NearestNeighbourSearch;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.AdditionalMeasureProducer;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * K-nearest neighbours classifier. Can select appropriate value of K based on cross-validation. Can also do distance weighting.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * D. Aha, D. Kibler (1991). Instance-based learning algorithms. Machine Learning. 6:37-66.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Aha1991,
+ *    author = {D. Aha and D. Kibler},
+ *    journal = {Machine Learning},
+ *    pages = {37-66},
+ *    title = {Instance-based learning algorithms},
+ *    volume = {6},
+ *    year = {1991}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I
+ *  Weight neighbours by the inverse of their distance
+ *  (use when k &gt; 1)</pre>
+ * 
+ * <pre> -F
+ *  Weight neighbours by 1 - their distance
+ *  (use when k &gt; 1)</pre>
+ * 
+ * <pre> -K &lt;number of neighbors&gt;
+ *  Number of nearest neighbours (k) used in classification.
+ *  (Default = 1)</pre>
+ * 
+ * <pre> -E
+ *  Minimise mean squared error rather than mean absolute
+ *  error when using -X option with numeric prediction.</pre>
+ * 
+ * <pre> -W &lt;window size&gt;
+ *  Maximum number of training instances maintained.
+ *  Training instances are dropped FIFO. (Default = no window)</pre>
+ * 
+ * <pre> -X
+ *  Select the number of nearest neighbours between 1
+ *  and the k value specified using hold-one-out evaluation
+ *  on the training data (use when k &gt; 1)</pre>
+ * 
+ * <pre> -A
+ *  The nearest neighbour search algorithm to use (default: weka.core.neighboursearch.LinearNNSearch).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stuart Inglis (singlis@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class IBk 
+  extends AbstractClassifier 
+  implements OptionHandler, UpdateableClassifier, WeightedInstancesHandler,
+             TechnicalInformationHandler, AdditionalMeasureProducer {
+
+  /** for serialization. */
+  static final long serialVersionUID = -3080186098777067172L;
+
+  /** The training instances used for classification. */
+  protected Instances m_Train;
+
+  /** The number of class values (or 1 if predicting numeric). */
+  protected int m_NumClasses;
+
+  /** The class attribute type. */
+  protected int m_ClassType;
+
+  /** The number of neighbours to use for classification (currently). */
+  protected int m_kNN;
+
+  /**
+   * The value of kNN provided by the user. This may differ from
+   * m_kNN if cross-validation is being used.
+   */
+  protected int m_kNNUpper;
+
+  /**
+   * Whether the value of k selected by cross validation has
+   * been invalidated by a change in the training instances.
+   */
+  protected boolean m_kNNValid;
+
+  /**
+   * The maximum number of training instances allowed. When
+   * this limit is reached, old training instances are removed,
+   * so the training data is "windowed". Set to 0 for unlimited
+   * numbers of instances.
+   */
+  protected int m_WindowSize;
+
+  /** Whether the neighbours should be distance-weighted. */
+  protected int m_DistanceWeighting;
+
+  /** Whether to select k by cross validation. */
+  protected boolean m_CrossValidate;
+
+  /**
+   * Whether to minimise mean squared error rather than mean absolute
+   * error when cross-validating on numeric prediction tasks.
+   */
+  protected boolean m_MeanSquared;
+
+  /** no weighting. */
+  public static final int WEIGHT_NONE = 1;
+  /** weight by 1/distance. */
+  public static final int WEIGHT_INVERSE = 2;
+  /** weight by 1-distance. */
+  public static final int WEIGHT_SIMILARITY = 4;
+  /** possible instance weighting methods. */
+  public static final Tag [] TAGS_WEIGHTING = {
+    new Tag(WEIGHT_NONE, "No distance weighting"),
+    new Tag(WEIGHT_INVERSE, "Weight by 1/distance"),
+    new Tag(WEIGHT_SIMILARITY, "Weight by 1-distance")
+  };
+  
+  /** for nearest-neighbor search. */
+  protected NearestNeighbourSearch m_NNSearch = new LinearNNSearch();
+
+  /** The number of attributes the contribute to a prediction. */
+  protected double m_NumAttributesUsed;
+  
+  /**
+   * IBk classifier. Simple instance-based learner that uses the class
+   * of the nearest k training instances for the class of the test
+   * instances.
+   *
+   * @param k the number of nearest neighbors to use for prediction
+   */
+  public IBk(int k) {
+
+    init();
+    setKNN(k);
+  }  
+
+  /**
+   * IB1 classifer. Instance-based learner. Predicts the class of the
+   * single nearest training instance for each test instance.
+   */
+  public IBk() {
+
+    init();
+  }
+  
+  /**
+   * Returns a string describing classifier.
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "K-nearest neighbours classifier. Can "
+      + "select appropriate value of K based on cross-validation. Can also do "
+      + "distance weighting.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "D. Aha and D. Kibler");
+    result.setValue(Field.YEAR, "1991");
+    result.setValue(Field.TITLE, "Instance-based learning algorithms");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "6");
+    result.setValue(Field.PAGES, "37-66");
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String KNNTipText() {
+    return "The number of neighbours to use.";
+  }
+  
+  /**
+   * Set the number of neighbours the learner is to use.
+   *
+   * @param k the number of neighbours.
+   */
+  public void setKNN(int k) {
+    m_kNN = k;
+    m_kNNUpper = k;
+    m_kNNValid = false;
+  }
+
+  /**
+   * Gets the number of neighbours the learner will use.
+   *
+   * @return the number of neighbours.
+   */
+  public int getKNN() {
+
+    return m_kNN;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String windowSizeTipText() {
+    return "Gets the maximum number of instances allowed in the training " +
+      "pool. The addition of new instances above this value will result " +
+      "in old instances being removed. A value of 0 signifies no limit " +
+      "to the number of training instances.";
+  }
+  
+  /**
+   * Gets the maximum number of instances allowed in the training
+   * pool. The addition of new instances above this value will result
+   * in old instances being removed. A value of 0 signifies no limit
+   * to the number of training instances.
+   *
+   * @return Value of WindowSize.
+   */
+  public int getWindowSize() {
+    
+    return m_WindowSize;
+  }
+  
+  /**
+   * Sets the maximum number of instances allowed in the training
+   * pool. The addition of new instances above this value will result
+   * in old instances being removed. A value of 0 signifies no limit
+   * to the number of training instances.
+   *
+   * @param newWindowSize Value to assign to WindowSize.
+   */
+  public void setWindowSize(int newWindowSize) {
+    
+    m_WindowSize = newWindowSize;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String distanceWeightingTipText() {
+
+    return "Gets the distance weighting method used.";
+  }
+  
+  /**
+   * Gets the distance weighting method used. Will be one of
+   * WEIGHT_NONE, WEIGHT_INVERSE, or WEIGHT_SIMILARITY
+   *
+   * @return the distance weighting method used.
+   */
+  public SelectedTag getDistanceWeighting() {
+
+    return new SelectedTag(m_DistanceWeighting, TAGS_WEIGHTING);
+  }
+  
+  /**
+   * Sets the distance weighting method used. Values other than
+   * WEIGHT_NONE, WEIGHT_INVERSE, or WEIGHT_SIMILARITY will be ignored.
+   *
+   * @param newMethod the distance weighting method to use
+   */
+  public void setDistanceWeighting(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_WEIGHTING) {
+      m_DistanceWeighting = newMethod.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String meanSquaredTipText() {
+
+    return "Whether the mean squared error is used rather than mean "
+      + "absolute error when doing cross-validation for regression problems.";
+  }
+
+  /**
+   * Gets whether the mean squared error is used rather than mean
+   * absolute error when doing cross-validation.
+   *
+   * @return true if so.
+   */
+  public boolean getMeanSquared() {
+    
+    return m_MeanSquared;
+  }
+  
+  /**
+   * Sets whether the mean squared error is used rather than mean
+   * absolute error when doing cross-validation.
+   *
+   * @param newMeanSquared true if so.
+   */
+  public void setMeanSquared(boolean newMeanSquared) {
+    
+    m_MeanSquared = newMeanSquared;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String crossValidateTipText() {
+
+    return "Whether hold-one-out cross-validation will be used " +
+      "to select the best k value.";
+  }
+  
+  /**
+   * Gets whether hold-one-out cross-validation will be used
+   * to select the best k value.
+   *
+   * @return true if cross-validation will be used.
+   */
+  public boolean getCrossValidate() {
+    
+    return m_CrossValidate;
+  }
+  
+  /**
+   * Sets whether hold-one-out cross-validation will be used
+   * to select the best k value.
+   *
+   * @param newCrossValidate true if cross-validation should be used.
+   */
+  public void setCrossValidate(boolean newCrossValidate) {
+    
+    m_CrossValidate = newCrossValidate;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String nearestNeighbourSearchAlgorithmTipText() {
+    return "The nearest neighbour search algorithm to use " +
+    	   "(Default: weka.core.neighboursearch.LinearNNSearch).";
+  }
+  
+  /**
+   * Returns the current nearestNeighbourSearch algorithm in use.
+   * @return the NearestNeighbourSearch algorithm currently in use.
+   */
+  public NearestNeighbourSearch getNearestNeighbourSearchAlgorithm() {
+    return m_NNSearch;
+  }
+  
+  /**
+   * Sets the nearestNeighbourSearch algorithm to be used for finding nearest
+   * neighbour(s).
+   * @param nearestNeighbourSearchAlgorithm - The NearestNeighbourSearch class.
+   */
+  public void setNearestNeighbourSearchAlgorithm(NearestNeighbourSearch nearestNeighbourSearchAlgorithm) {
+    m_NNSearch = nearestNeighbourSearchAlgorithm;
+  }
+   
+  /**
+   * Get the number of training instances the classifier is currently using.
+   * 
+   * @return the number of training instances the classifier is currently using
+   */
+  public int getNumTraining() {
+
+    return m_Train.numInstances();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+  
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_NumClasses = instances.numClasses();
+    m_ClassType = instances.classAttribute().type();
+    m_Train = new Instances(instances, 0, instances.numInstances());
+
+    // Throw away initial instances until within the specified window size
+    if ((m_WindowSize > 0) && (instances.numInstances() > m_WindowSize)) {
+      m_Train = new Instances(m_Train, 
+			      m_Train.numInstances()-m_WindowSize, 
+			      m_WindowSize);
+    }
+
+    m_NumAttributesUsed = 0.0;
+    for (int i = 0; i < m_Train.numAttributes(); i++) {
+      if ((i != m_Train.classIndex()) && 
+	  (m_Train.attribute(i).isNominal() ||
+	   m_Train.attribute(i).isNumeric())) {
+	m_NumAttributesUsed += 1.0;
+      }
+    }
+    
+    m_NNSearch.setInstances(m_Train);
+
+    // Invalidate any currently cross-validation selected k
+    m_kNNValid = false;
+  }
+
+  /**
+   * Adds the supplied instance to the training set.
+   *
+   * @param instance the instance to add
+   * @throws Exception if instance could not be incorporated
+   * successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+
+    if (m_Train.equalHeaders(instance.dataset()) == false) {
+      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    }
+    if (instance.classIsMissing()) {
+      return;
+    }
+
+    m_Train.add(instance);
+    m_NNSearch.update(instance);
+    m_kNNValid = false;
+    if ((m_WindowSize > 0) && (m_Train.numInstances() > m_WindowSize)) {
+      boolean deletedInstance=false;
+      while (m_Train.numInstances() > m_WindowSize) {
+	m_Train.delete(0);
+        deletedInstance=true;
+      }
+      //rebuild datastructure KDTree currently can't delete
+      if(deletedInstance==true)
+        m_NNSearch.setInstances(m_Train);
+    }
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if an error occurred during the prediction
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+
+    if (m_Train.numInstances() == 0) {
+      throw new Exception("No training instances!");
+    }
+    if ((m_WindowSize > 0) && (m_Train.numInstances() > m_WindowSize)) {
+      m_kNNValid = false;
+      boolean deletedInstance=false;
+      while (m_Train.numInstances() > m_WindowSize) {
+	m_Train.delete(0);
+      }
+      //rebuild datastructure KDTree currently can't delete
+      if(deletedInstance==true)
+        m_NNSearch.setInstances(m_Train);
+    }
+
+    // Select k by cross validation
+    if (!m_kNNValid && (m_CrossValidate) && (m_kNNUpper >= 1)) {
+      crossValidate();
+    }
+
+    m_NNSearch.addInstanceInfo(instance);
+
+    Instances neighbours = m_NNSearch.kNearestNeighbours(instance, m_kNN);
+    double [] distances = m_NNSearch.getDistances();
+    double [] distribution = makeDistribution( neighbours, distances );
+
+    return distribution;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(8);
+
+    newVector.addElement(new Option(
+	      "\tWeight neighbours by the inverse of their distance\n"+
+	      "\t(use when k > 1)",
+	      "I", 0, "-I"));
+    newVector.addElement(new Option(
+	      "\tWeight neighbours by 1 - their distance\n"+
+	      "\t(use when k > 1)",
+	      "F", 0, "-F"));
+    newVector.addElement(new Option(
+	      "\tNumber of nearest neighbours (k) used in classification.\n"+
+	      "\t(Default = 1)",
+	      "K", 1,"-K <number of neighbors>"));
+    newVector.addElement(new Option(
+          "\tMinimise mean squared error rather than mean absolute\n"+
+	      "\terror when using -X option with numeric prediction.",
+	      "E", 0,"-E"));
+    newVector.addElement(new Option(
+          "\tMaximum number of training instances maintained.\n"+
+	      "\tTraining instances are dropped FIFO. (Default = no window)",
+	      "W", 1,"-W <window size>"));
+    newVector.addElement(new Option(
+	      "\tSelect the number of nearest neighbours between 1\n"+
+	      "\tand the k value specified using hold-one-out evaluation\n"+
+	      "\ton the training data (use when k > 1)",
+	      "X", 0,"-X"));
+    newVector.addElement(new Option(
+	      "\tThe nearest neighbour search algorithm to use "+
+          "(default: weka.core.neighboursearch.LinearNNSearch).\n",
+	      "A", 0, "-A"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I
+   *  Weight neighbours by the inverse of their distance
+   *  (use when k &gt; 1)</pre>
+   * 
+   * <pre> -F
+   *  Weight neighbours by 1 - their distance
+   *  (use when k &gt; 1)</pre>
+   * 
+   * <pre> -K &lt;number of neighbors&gt;
+   *  Number of nearest neighbours (k) used in classification.
+   *  (Default = 1)</pre>
+   * 
+   * <pre> -E
+   *  Minimise mean squared error rather than mean absolute
+   *  error when using -X option with numeric prediction.</pre>
+   * 
+   * <pre> -W &lt;window size&gt;
+   *  Maximum number of training instances maintained.
+   *  Training instances are dropped FIFO. (Default = no window)</pre>
+   * 
+   * <pre> -X
+   *  Select the number of nearest neighbours between 1
+   *  and the k value specified using hold-one-out evaluation
+   *  on the training data (use when k &gt; 1)</pre>
+   * 
+   * <pre> -A
+   *  The nearest neighbour search algorithm to use (default: weka.core.neighboursearch.LinearNNSearch).
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String knnString = Utils.getOption('K', options);
+    if (knnString.length() != 0) {
+      setKNN(Integer.parseInt(knnString));
+    } else {
+      setKNN(1);
+    }
+    String windowString = Utils.getOption('W', options);
+    if (windowString.length() != 0) {
+      setWindowSize(Integer.parseInt(windowString));
+    } else {
+      setWindowSize(0);
+    }
+    if (Utils.getFlag('I', options)) {
+      setDistanceWeighting(new SelectedTag(WEIGHT_INVERSE, TAGS_WEIGHTING));
+    } else if (Utils.getFlag('F', options)) {
+      setDistanceWeighting(new SelectedTag(WEIGHT_SIMILARITY, TAGS_WEIGHTING));
+    } else {
+      setDistanceWeighting(new SelectedTag(WEIGHT_NONE, TAGS_WEIGHTING));
+    }
+    setCrossValidate(Utils.getFlag('X', options));
+    setMeanSquared(Utils.getFlag('E', options));
+
+    String nnSearchClass = Utils.getOption('A', options);
+    if(nnSearchClass.length() != 0) {
+      String nnSearchClassSpec[] = Utils.splitOptions(nnSearchClass);
+      if(nnSearchClassSpec.length == 0) { 
+        throw new Exception("Invalid NearestNeighbourSearch algorithm " +
+                            "specification string."); 
+      }
+      String className = nnSearchClassSpec[0];
+      nnSearchClassSpec[0] = "";
+
+      setNearestNeighbourSearchAlgorithm( (NearestNeighbourSearch)
+                  Utils.forName( NearestNeighbourSearch.class, 
+                                 className, 
+                                 nnSearchClassSpec)
+                                        );
+    }
+    else 
+      this.setNearestNeighbourSearchAlgorithm(new LinearNNSearch());
+    
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of IBk.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [11];
+    int current = 0;
+    options[current++] = "-K"; options[current++] = "" + getKNN();
+    options[current++] = "-W"; options[current++] = "" + m_WindowSize;
+    if (getCrossValidate()) {
+      options[current++] = "-X";
+    }
+    if (getMeanSquared()) {
+      options[current++] = "-E";
+    }
+    if (m_DistanceWeighting == WEIGHT_INVERSE) {
+      options[current++] = "-I";
+    } else if (m_DistanceWeighting == WEIGHT_SIMILARITY) {
+      options[current++] = "-F";
+    }
+
+    options[current++] = "-A";
+    options[current++] = m_NNSearch.getClass().getName()+" "+Utils.joinOptions(m_NNSearch.getOptions()); 
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    
+    return options;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names 
+   * produced by the neighbour search algorithm, plus the chosen K in case
+   * cross-validation is enabled.
+   * 
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    if (m_CrossValidate) {
+      Enumeration enm = m_NNSearch.enumerateMeasures();
+      Vector measures = new Vector();
+      while (enm.hasMoreElements())
+	measures.add(enm.nextElement());
+      measures.add("measureKNN");
+      return measures.elements();
+    }
+    else {
+      return m_NNSearch.enumerateMeasures();
+    }
+  }
+  
+  /**
+   * Returns the value of the named measure from the 
+   * neighbour search algorithm, plus the chosen K in case
+   * cross-validation is enabled.
+   * 
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.equals("measureKNN"))
+      return m_kNN;
+    else
+      return m_NNSearch.getMeasure(additionalMeasureName);
+  }
+  
+  
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+
+    if (m_Train == null) {
+      return "IBk: No model built yet.";
+    }
+
+    if (!m_kNNValid && m_CrossValidate) {
+      crossValidate();
+    }
+
+    String result = "IB1 instance-based classifier\n" +
+      "using " + m_kNN;
+
+    switch (m_DistanceWeighting) {
+    case WEIGHT_INVERSE:
+      result += " inverse-distance-weighted";
+      break;
+    case WEIGHT_SIMILARITY:
+      result += " similarity-weighted";
+      break;
+    }
+    result += " nearest neighbour(s) for classification\n";
+
+    if (m_WindowSize != 0) {
+      result += "using a maximum of " 
+	+ m_WindowSize + " (windowed) training instances\n";
+    }
+    return result;
+  }
+
+  /**
+   * Initialise scheme variables.
+   */
+  protected void init() {
+
+    setKNN(1);
+    m_WindowSize = 0;
+    m_DistanceWeighting = WEIGHT_NONE;
+    m_CrossValidate = false;
+    m_MeanSquared = false;
+  }
+  
+  /**
+   * Turn the list of nearest neighbors into a probability distribution.
+   *
+   * @param neighbours the list of nearest neighboring instances
+   * @param distances the distances of the neighbors
+   * @return the probability distribution
+   * @throws Exception if computation goes wrong or has no class attribute
+   */
+  protected double [] makeDistribution(Instances neighbours, double[] distances)
+    throws Exception {
+
+    double total = 0, weight;
+    double [] distribution = new double [m_NumClasses];
+    
+    // Set up a correction to the estimator
+    if (m_ClassType == Attribute.NOMINAL) {
+      for(int i = 0; i < m_NumClasses; i++) {
+	distribution[i] = 1.0 / Math.max(1,m_Train.numInstances());
+      }
+      total = (double)m_NumClasses / Math.max(1,m_Train.numInstances());
+    }
+
+    for(int i=0; i < neighbours.numInstances(); i++) {
+      // Collect class counts
+      Instance current = neighbours.instance(i);
+      distances[i] = distances[i]*distances[i];
+      distances[i] = Math.sqrt(distances[i]/m_NumAttributesUsed);
+      switch (m_DistanceWeighting) {
+        case WEIGHT_INVERSE:
+          weight = 1.0 / (distances[i] + 0.001); // to avoid div by zero
+          break;
+        case WEIGHT_SIMILARITY:
+          weight = 1.0 - distances[i];
+          break;
+        default:                                 // WEIGHT_NONE:
+          weight = 1.0;
+          break;
+      }
+      weight *= current.weight();
+      try {
+        switch (m_ClassType) {
+          case Attribute.NOMINAL:
+            distribution[(int)current.classValue()] += weight;
+            break;
+          case Attribute.NUMERIC:
+            distribution[0] += current.classValue() * weight;
+            break;
+        }
+      } catch (Exception ex) {
+        throw new Error("Data has no class attribute!");
+      }
+      total += weight;      
+    }
+
+    // Normalise distribution
+    if (total > 0) {
+      Utils.normalize(distribution, total);
+    }
+    return distribution;
+  }
+
+  /**
+   * Select the best value for k by hold-one-out cross-validation.
+   * If the class attribute is nominal, classification error is
+   * minimised. If the class attribute is numeric, mean absolute
+   * error is minimised
+   */
+  protected void crossValidate() {
+
+    try {
+      if (m_NNSearch instanceof weka.core.neighboursearch.CoverTree)
+	throw new Exception("CoverTree doesn't support hold-one-out "+
+			    "cross-validation. Use some other NN " +
+			    "method.");
+
+      double [] performanceStats = new double [m_kNNUpper];
+      double [] performanceStatsSq = new double [m_kNNUpper];
+
+      for(int i = 0; i < m_kNNUpper; i++) {
+	performanceStats[i] = 0;
+	performanceStatsSq[i] = 0;
+      }
+
+
+      m_kNN = m_kNNUpper;
+      Instance instance;
+      Instances neighbours;
+      double[] origDistances, convertedDistances;
+      for(int i = 0; i < m_Train.numInstances(); i++) {
+	if (m_Debug && (i % 50 == 0)) {
+	  System.err.print("Cross validating "
+			   + i + "/" + m_Train.numInstances() + "\r");
+	}
+	instance = m_Train.instance(i);
+	neighbours = m_NNSearch.kNearestNeighbours(instance, m_kNN);
+        origDistances = m_NNSearch.getDistances();
+        
+	for(int j = m_kNNUpper - 1; j >= 0; j--) {
+	  // Update the performance stats
+          convertedDistances = new double[origDistances.length];
+          System.arraycopy(origDistances, 0, 
+                           convertedDistances, 0, origDistances.length);
+	  double [] distribution = makeDistribution(neighbours, 
+                                                    convertedDistances);
+          double thisPrediction = Utils.maxIndex(distribution);
+	  if (m_Train.classAttribute().isNumeric()) {
+	    thisPrediction = distribution[0];
+	    double err = thisPrediction - instance.classValue();
+	    performanceStatsSq[j] += err * err;   // Squared error
+	    performanceStats[j] += Math.abs(err); // Absolute error
+	  } else {
+	    if (thisPrediction != instance.classValue()) {
+	      performanceStats[j] ++;             // Classification error
+	    }
+	  }
+	  if (j >= 1) {
+	    neighbours = pruneToK(neighbours, convertedDistances, j);
+	  }
+	}
+      }
+
+      // Display the results of the cross-validation
+      for(int i = 0; i < m_kNNUpper; i++) {
+	if (m_Debug) {
+	  System.err.print("Hold-one-out performance of " + (i + 1)
+			   + " neighbors " );
+	}
+	if (m_Train.classAttribute().isNumeric()) {
+	  if (m_Debug) {
+	    if (m_MeanSquared) {
+	      System.err.println("(RMSE) = "
+				 + Math.sqrt(performanceStatsSq[i]
+					     / m_Train.numInstances()));
+	    } else {
+	      System.err.println("(MAE) = "
+				 + performanceStats[i]
+				 / m_Train.numInstances());
+	    }
+	  }
+	} else {
+	  if (m_Debug) {
+	    System.err.println("(%ERR) = "
+			       + 100.0 * performanceStats[i]
+			       / m_Train.numInstances());
+	  }
+	}
+      }
+
+
+      // Check through the performance stats and select the best
+      // k value (or the lowest k if more than one best)
+      double [] searchStats = performanceStats;
+      if (m_Train.classAttribute().isNumeric() && m_MeanSquared) {
+	searchStats = performanceStatsSq;
+      }
+      double bestPerformance = Double.NaN;
+      int bestK = 1;
+      for(int i = 0; i < m_kNNUpper; i++) {
+	if (Double.isNaN(bestPerformance)
+	    || (bestPerformance > searchStats[i])) {
+	  bestPerformance = searchStats[i];
+	  bestK = i + 1;
+	}
+      }
+      m_kNN = bestK;
+      if (m_Debug) {
+	System.err.println("Selected k = " + bestK);
+      }
+      
+      m_kNNValid = true;
+    } catch (Exception ex) {
+      throw new Error("Couldn't optimize by cross-validation: "
+		      +ex.getMessage());
+    }
+  }
+  
+  /**
+   * Prunes the list to contain the k nearest neighbors. If there are
+   * multiple neighbors at the k'th distance, all will be kept.
+   *
+   * @param neighbours the neighbour instances.
+   * @param distances the distances of the neighbours from target instance.
+   * @param k the number of neighbors to keep.
+   * @return the pruned neighbours.
+   */
+  public Instances pruneToK(Instances neighbours, double[] distances, int k) {
+    
+    if(neighbours==null || distances==null || neighbours.numInstances()==0) {
+      return null;
+    }
+    if (k < 1) {
+      k = 1;
+    }
+    
+    int currentK = 0;
+    double currentDist;
+    for(int i=0; i < neighbours.numInstances(); i++) {
+      currentK++;
+      currentDist = distances[i];
+      if(currentK>k && currentDist!=distances[i-1]) {
+        currentK--;
+        neighbours = new Instances(neighbours, 0, currentK);
+        break;
+      }
+    }
+
+    return neighbours;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line options (see setOptions)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new IBk(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/KStar.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/KStar.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/KStar.java	(revision 29)
@@ -0,0 +1,719 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KStar.java
+ *    Copyright (C) 1995-97 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.classifiers.lazy.kstar.KStarCache;
+import weka.classifiers.lazy.kstar.KStarConstants;
+import weka.classifiers.lazy.kstar.KStarNominalAttribute;
+import weka.classifiers.lazy.kstar.KStarNumericAttribute;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * K* is an instance-based classifier, that is the class of a test instance is based upon the class of those training instances similar to it, as determined by some similarity function.  It differs from other instance-based learners in that it uses an entropy-based distance function.<br/>
+ * <br/>
+ * For more information on K*, see<br/>
+ * <br/>
+ * John G. Cleary, Leonard E. Trigg: K*: An Instance-based Learner Using an Entropic Distance Measure. In: 12th International Conference on Machine Learning, 108-114, 1995.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Cleary1995,
+ *    author = {John G. Cleary and Leonard E. Trigg},
+ *    booktitle = {12th International Conference on Machine Learning},
+ *    pages = {108-114},
+ *    title = {K*: An Instance-based Learner Using an Entropic Distance Measure},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;num&gt;
+ *  Manual blend setting (default 20%)
+ * </pre>
+ * 
+ * <pre> -E
+ *  Enable entropic auto-blend setting (symbolic class only)
+ * </pre>
+ * 
+ * <pre> -M &lt;char&gt;
+ *  Specify the missing value treatment mode (default a)
+ *  Valid options are: a(verage), d(elete), m(axdiff), n(ormal)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz) - Java port
+ * @version $Revision: 5928 $
+ */
+public class KStar 
+  extends AbstractClassifier
+  implements KStarConstants, UpdateableClassifier, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 332458330800479083L;
+  
+  /** The training instances used for classification. */
+  protected Instances m_Train; 
+
+  /** The number of instances in the dataset */
+  protected int m_NumInstances;
+
+  /** The number of class values */
+  protected int m_NumClasses;
+
+  /** The number of attributes */
+  protected int m_NumAttributes;
+
+  /** The class attribute type */
+  protected int m_ClassType;
+
+  /** Table of random class value colomns */
+  protected int [][] m_RandClassCols;
+
+  /** Flag turning on and off the computation of random class colomns */
+  protected int m_ComputeRandomCols = ON;
+
+  /** Flag turning on and off the initialisation of config variables */
+  protected int m_InitFlag = ON;
+
+  /**
+   * A custom data structure for caching distinct attribute values
+   * and their scale factor or stop parameter.
+   */
+  protected KStarCache [] m_Cache;
+
+  /** missing value treatment */
+  protected int m_MissingMode = M_AVERAGE;
+
+  /** 0 = use specified blend, 1 = entropic blend setting */
+  protected int m_BlendMethod = B_SPHERE;
+
+  /** default sphere of influence blend setting */
+  protected int m_GlobalBlend = 20;
+
+  /** Define possible missing value handling methods */
+  public static final Tag [] TAGS_MISSING = {
+    new Tag(M_DELETE, "Ignore the instances with missing values"),
+    new Tag(M_MAXDIFF, "Treat missing values as maximally different"),
+    new Tag(M_NORMAL, "Normalize over the attributes"),
+    new Tag(M_AVERAGE, "Average column entropy curves")
+      };
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "K* is an instance-based classifier, that is the class of a test "
+      + "instance is based upon the class of those training instances "
+      + "similar to it, as determined by some similarity function.  It differs "
+      + "from other instance-based learners in that it uses an entropy-based "
+      + "distance function.\n\n"
+      + "For more information on K*, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "John G. Cleary and Leonard E. Trigg");
+    result.setValue(Field.TITLE, "K*: An Instance-based Learner Using an Entropic Distance Measure");
+    result.setValue(Field.BOOKTITLE, "12th International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.PAGES, "108-114");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    String debug = "(KStar.buildClassifier) ";
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_Train = new Instances(instances, 0, instances.numInstances());
+
+    // initializes class attributes ** java-speaking! :-) **
+    init_m_Attributes();
+  }
+  
+  /**
+   * Adds the supplied instance to the training set
+   *
+   * @param instance the instance to add
+   * @throws Exception if instance could not be incorporated successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+    String debug = "(KStar.updateClassifier) ";
+
+    if (m_Train.equalHeaders(instance.dataset()) == false)
+      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    if ( instance.classIsMissing() )
+      return;
+    m_Train.add(instance);
+    // update relevant attributes ...
+    update_m_Attributes();
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if an error occurred during the prediction
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+
+    String debug = "(KStar.distributionForInstance) ";
+    double transProb = 0.0, temp = 0.0;
+    double [] classProbability = new double[m_NumClasses];
+    double [] predictedValue = new double[1];
+
+    // initialization ...
+    for (int i=0; i<classProbability.length; i++) {
+      classProbability[i] = 0.0;
+    }
+    predictedValue[0] = 0.0;
+    if (m_InitFlag == ON) {
+	// need to compute them only once and will be used for all instances.
+	// We are doing this because the evaluation module controls the calls. 
+      if (m_BlendMethod == B_ENTROPY) {
+	generateRandomClassColomns();
+      }
+      m_Cache = new KStarCache[m_NumAttributes];
+      for (int i=0; i<m_NumAttributes;i++) {
+	m_Cache[i] = new KStarCache();
+      }
+      m_InitFlag = OFF;
+      //      System.out.println("Computing...");
+    }
+    // init done.
+    Instance trainInstance;
+    Enumeration enu = m_Train.enumerateInstances();
+    while ( enu.hasMoreElements() ) {
+      trainInstance = (Instance)enu.nextElement();
+      transProb = instanceTransformationProbability(instance, trainInstance);      
+      switch ( m_ClassType )
+	{
+	case Attribute.NOMINAL:
+	  classProbability[(int)trainInstance.classValue()] += transProb;
+	  break;
+	case Attribute.NUMERIC:
+	  predictedValue[0] += transProb * trainInstance.classValue();
+	  temp += transProb;
+	  break;
+	}
+    }
+    if (m_ClassType == Attribute.NOMINAL) {
+      double sum = Utils.sum(classProbability);
+      if (sum <= 0.0)
+	for (int i=0; i<classProbability.length; i++)
+	  classProbability[i] = (double) 1/ (double) m_NumClasses;
+      else Utils.normalize(classProbability, sum);
+      return classProbability;
+    }
+    else {
+      predictedValue[0] = (temp != 0) ? predictedValue[0] / temp : 0.0;
+      return predictedValue;
+    }
+  }
+
+  /**
+   * Calculate the probability of the first instance transforming into the 
+   * second instance:
+   * the probability is the product of the transformation probabilities of 
+   * the attributes normilized over the number of instances used.
+   * 
+   * @param first the test instance
+   * @param second the train instance
+   * @return transformation probability value
+   */
+  private double instanceTransformationProbability(Instance first, 
+						   Instance second) {
+    String debug = "(KStar.instanceTransformationProbability) ";
+    double transProb = 1.0;
+    int numMissAttr = 0;
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (i == m_Train.classIndex()) {
+	continue; // ignore class attribute
+      }
+      if (first.isMissing(i)) { // test instance attribute value is missing
+	numMissAttr++;
+	continue;
+      }
+      transProb *= attrTransProb(first, second, i);
+      // normilize for missing values
+      if (numMissAttr != m_NumAttributes) {
+	transProb = Math.pow(transProb, (double)m_NumAttributes / 
+			     (m_NumAttributes - numMissAttr));
+      }
+      else { // weird case!
+	transProb = 0.0;
+      }
+    }
+    // normilize for the train dataset
+     return transProb / m_NumInstances;
+  }
+
+  /**
+   * Calculates the transformation probability of the indexed test attribute 
+   * to the indexed train attribute.
+   *
+   * @param first the test instance.
+   * @param second the train instance.
+   * @param col the index of the attribute in the instance.
+   * @return the value of the transformation probability.
+   */
+  private double attrTransProb(Instance first, Instance second, int col) {
+    String debug = "(KStar.attrTransProb)";
+    double transProb = 0.0;
+    KStarNominalAttribute ksNominalAttr;
+    KStarNumericAttribute ksNumericAttr;
+    switch ( m_Train.attribute(col).type() )
+      {
+      case Attribute.NOMINAL:
+	ksNominalAttr = new KStarNominalAttribute(first, second, col, m_Train, 
+						  m_RandClassCols, 
+						  m_Cache[col]);
+	ksNominalAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
+	transProb = ksNominalAttr.transProb();
+	ksNominalAttr = null;
+	break;
+
+      case Attribute.NUMERIC:
+	ksNumericAttr = new KStarNumericAttribute(first, second, col, 
+						  m_Train, m_RandClassCols, 
+						  m_Cache[col]);
+	ksNumericAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
+	transProb = ksNumericAttr.transProb();
+	ksNumericAttr = null;
+	break;
+      }
+    return transProb;
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String missingModeTipText() {
+    return "Determines how missing attribute values are treated.";
+  }
+
+  /**
+   * Gets the method to use for handling missing values. Will be one of
+   * M_NORMAL, M_AVERAGE, M_MAXDIFF or M_DELETE.
+   *
+   * @return the method used for handling missing values.
+   */
+  public SelectedTag getMissingMode() {
+
+    return new SelectedTag(m_MissingMode, TAGS_MISSING);
+  }
+  
+  /**
+   * Sets the method to use for handling missing values. Values other than
+   * M_NORMAL, M_AVERAGE, M_MAXDIFF and M_DELETE will be ignored.
+   *
+   * @param newMode the method to use for handling missing values.
+   */
+  public void setMissingMode(SelectedTag newMode) {
+    
+    if (newMode.getTags() == TAGS_MISSING) {
+      m_MissingMode = newMode.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector optVector = new Vector( 3 );
+    optVector.addElement(new Option(
+	      "\tManual blend setting (default 20%)\n",
+	      "B", 1, "-B <num>"));
+    optVector.addElement(new Option(
+	      "\tEnable entropic auto-blend setting (symbolic class only)\n",
+	      "E", 0, "-E"));
+    optVector.addElement(new Option(
+	      "\tSpecify the missing value treatment mode (default a)\n"
+	      +"\tValid options are: a(verage), d(elete), m(axdiff), n(ormal)\n",
+	      "M", 1,"-M <char>"));
+    return optVector.elements();
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalBlendTipText() {
+    return "The parameter for global blending. Values are restricted to [0,100].";
+  }
+
+  /**
+   * Set the global blend parameter
+   * @param b the value for global blending
+   */
+  public void setGlobalBlend(int b) {
+     m_GlobalBlend = b;
+      if ( m_GlobalBlend > 100 ) {
+	m_GlobalBlend = 100;
+      }
+      if ( m_GlobalBlend < 0 ) {
+	m_GlobalBlend = 0;
+      }
+  }
+
+  /**
+   * Get the value of the global blend parameter
+   * @return the value of the global blend parameter
+   */
+  public int getGlobalBlend() {
+    return m_GlobalBlend;
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String entropicAutoBlendTipText() {
+    return "Whether entropy-based blending is to be used.";
+  }
+
+  /**
+   * Set whether entropic blending is to be used.
+   * @param e true if entropic blending is to be used
+   */
+  public void setEntropicAutoBlend(boolean e) {
+    if (e) {
+      m_BlendMethod = B_ENTROPY;
+    } else {
+      m_BlendMethod = B_SPHERE;
+    }
+  }
+
+  /**
+   * Get whether entropic blending being used
+   * @return true if entropic blending is used
+   */
+  public boolean getEntropicAutoBlend() {
+    if (m_BlendMethod == B_ENTROPY) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;num&gt;
+   *  Manual blend setting (default 20%)
+   * </pre>
+   * 
+   * <pre> -E
+   *  Enable entropic auto-blend setting (symbolic class only)
+   * </pre>
+   * 
+   * <pre> -M &lt;char&gt;
+   *  Specify the missing value treatment mode (default a)
+   *  Valid options are: a(verage), d(elete), m(axdiff), n(ormal)
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String debug = "(KStar.setOptions)";
+    String blendStr = Utils.getOption('B', options);
+    if (blendStr.length() != 0) {
+      setGlobalBlend(Integer.parseInt(blendStr));
+    }
+
+    setEntropicAutoBlend(Utils.getFlag('E', options));
+    
+    String missingModeStr = Utils.getOption('M', options);
+    if (missingModeStr.length() != 0) {
+      switch ( missingModeStr.charAt(0) ) {
+      case 'a':
+	setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
+	break;
+      case 'd':
+	setMissingMode(new SelectedTag(M_DELETE, TAGS_MISSING));
+	break;
+      case 'm':
+	setMissingMode(new SelectedTag(M_MAXDIFF, TAGS_MISSING));
+	break;
+      case 'n':
+	setMissingMode(new SelectedTag(M_NORMAL, TAGS_MISSING));
+	break;
+      default:
+	setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
+      }
+    }
+    Utils.checkForRemainingOptions(options);
+  }
+
+
+  /**
+   * Gets the current settings of K*.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+    // -B <num> -E -M <char>
+    String [] options = new String [ 5 ];
+    int itr = 0;
+    options[itr++] = "-B";
+    options[itr++] = "" + m_GlobalBlend;
+
+    if (getEntropicAutoBlend()) {
+      options[itr++] = "-E";
+    }
+
+    options[itr++] = "-M";
+    if (m_MissingMode == M_AVERAGE) {
+      options[itr++] = "" + "a";
+    }
+    else if (m_MissingMode == M_DELETE) {
+      options[itr++] = "" + "d";
+    }
+    else if (m_MissingMode == M_MAXDIFF) {
+      options[itr++] = "" + "m";
+    }
+    else if (m_MissingMode == M_NORMAL) {
+      options[itr++] = "" + "n";
+    }
+    while (itr < options.length) {
+      options[itr++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+    StringBuffer st = new StringBuffer();
+    st.append("KStar Beta Verion (0.1b).\n"
+	      +"Copyright (c) 1995-97 by Len Trigg (trigg@cs.waikato.ac.nz).\n"
+	      +"Java port to Weka by Abdelaziz Mahoui "
+	      +"(am14@cs.waikato.ac.nz).\n\nKStar options : ");
+    String [] ops = getOptions();
+    for (int i=0;i<ops.length;i++) {
+      st.append(ops[i]+' ');
+    }
+    return st.toString();
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line options (see setOptions)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new KStar(), argv);
+  }
+
+  /**
+   * Initializes the m_Attributes of the class.
+   */
+  private void init_m_Attributes() {
+    try {
+      m_NumInstances = m_Train.numInstances();
+      m_NumClasses = m_Train.numClasses();
+      m_NumAttributes = m_Train.numAttributes();
+      m_ClassType = m_Train.classAttribute().type();
+      m_InitFlag = ON;
+    } catch(Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Updates the m_attributes of the class.
+   */
+  private void update_m_Attributes() {
+    m_NumInstances = m_Train.numInstances();
+    m_InitFlag = ON;
+  }
+
+  /**
+   * Note: for Nominal Class Only!
+   * Generates a set of random versions of the class colomn.
+   */
+  private void generateRandomClassColomns() {
+    String debug = "(KStar.generateRandomClassColomns)";
+    Random generator = new Random(42);
+    //    Random generator = new Random();
+    m_RandClassCols = new int [NUM_RAND_COLS+1][];
+    int [] classvals = classValues();
+    for (int i=0; i < NUM_RAND_COLS; i++) {
+      // generate a randomized version of the class colomn
+      m_RandClassCols[i] = randomize(classvals, generator);
+    }
+    // original colomn is preserved in colomn NUM_RAND_COLS
+    m_RandClassCols[NUM_RAND_COLS] = classvals;
+  }
+
+  /**
+   * Note: for Nominal Class Only!
+   * Returns an array of the class values
+   *
+   * @return an array of class values
+   */
+  private int [] classValues() {
+    String debug = "(KStar.classValues)";
+    int [] classval = new int[m_NumInstances];
+    for (int i=0; i < m_NumInstances; i++) {
+      try {
+	classval[i] = (int)m_Train.instance(i).classValue();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+    return classval;
+  }
+
+  /**
+   * Returns a copy of the array with its elements randomly redistributed.
+   *
+   * @param array the array to randomize.
+   * @param generator the random number generator to use
+   * @return a copy of the array with its elements randomly redistributed.
+   */
+  private int [] randomize(int [] array, Random generator) {
+    String debug = "(KStar.randomize)";
+    int index;
+    int temp;
+    int [] newArray = new int[array.length];
+    System.arraycopy(array, 0, newArray, 0, array.length);
+    for (int j = newArray.length - 1; j > 0; j--) {
+      index = (int) ( generator.nextDouble() * (double)j );
+      temp = newArray[j];
+      newArray[j] = newArray[index];
+      newArray[index] = temp;
+    }
+    return newArray;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+} // class end
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/LBR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/LBR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/LBR.java	(revision 29)
@@ -0,0 +1,1239 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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
+ *    aint with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+/*
+ *    LBR.java
+ *    The naive Bayesian classifier provides a simple and effective approach to 
+ *    classifier learning, but its attribute independence assumption is often 
+ *    violated in the real world. Lazy Bayesian Rules selectively relaxes the 
+ *    independence assumption, achieving lower error rates over a range of 
+ *    learning tasks.  LBR defers processing to classification time, making it 
+ *    a highly efficient and accurate classification algorithm when small
+ *    numbers of objects are to be classified.
+ *
+ *    For more information, see
+ <!-- technical-plaintext-start -->
+ * Zijian Zheng, G. Webb (2000). Lazy Learning of Bayesian Rules. Machine Learning. 4(1):53-84.
+ <!-- technical-plaintext-end -->
+ *
+ *    http://www.cm.deakin.edu.au/webb
+ *
+ *    Copyright (C) 2001 Deakin University
+ *    School of Computing and Mathematics
+ *    Deakin University
+ *    Geelong, Vic, 3217, Australia
+ *
+ *    Email: zhw@deakin.edu.au
+ *
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Lazy Bayesian Rules Classifier. The naive Bayesian classifier provides a simple and effective approach to classifier learning, but its attribute independence assumption is often violated in the real world. Lazy Bayesian Rules selectively relaxes the independence assumption, achieving lower error rates over a range of learning tasks. LBR defers processing to classification time, making it a highly efficient and accurate classification algorithm when small numbers of objects are to be classified.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Zijian Zheng, G. Webb (2000). Lazy Learning of Bayesian Rules. Machine Learning. 4(1):53-84.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Zheng2000,
+ *    author = {Zijian Zheng and G. Webb},
+ *    journal = {Machine Learning},
+ *    number = {1},
+ *    pages = {53-84},
+ *    title = {Lazy Learning of Bayesian Rules},
+ *    volume = {4},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Zhihai Wang (zhw@deakin.edu.au) : July 2001 implemented the algorithm
+ * @author Jason Wells (wells@deakin.edu.au) : November 2001 added instance referencing via indexes
+ * @version $Revision: 5987 $
+ */
+public class LBR 
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5648559277738985156L;
+  
+  /**
+   * Class for handling instances and the associated attributes. <p>
+   * Enables a set of indexes to a given dataset to be created and used
+   * with an algorithm.  This reduces the memory overheads and time required 
+   * when manipulating and referencing Instances and their Attributes.  
+   */
+  public class Indexes
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -2771490019751421307L;
+    
+    /** the array instance indexes **/
+    public boolean [] m_InstIndexes;
+    
+    /** the array attribute indexes **/
+    public boolean [] m_AttIndexes;
+    
+    /** the number of instances indexed **/
+    private int m_NumInstances;
+    
+    /** the number of attributes indexed **/
+    private int m_NumAtts;
+    
+    /** the array of instance indexes that are set to a either true or false **/
+    public int [] m_SequentialInstIndexes;
+    
+    /** an array of attribute indexes that are set to either true or false **/
+    public int [] m_SequentialAttIndexes;
+    
+    /** flag to check if sequential array must be rebuilt due to changes to the instance index*/
+    private boolean m_SequentialInstanceIndex_valid = false;
+    
+   /** flag to check if sequential array must be rebuilt due to changes to the attribute index */
+    private boolean m_SequentialAttIndex_valid = false;
+    
+    /** the number of instances "in use"  or set to a the original value (true or false) **/
+    public int m_NumInstsSet;
+    
+    /** the number of attributes "in use"  or set to a the original value (true or false) **/
+    public int m_NumAttsSet;
+    
+    /** the number of sequential instances "in use"  or set to a the original value (true or false) **/
+    public int m_NumSeqInstsSet;
+    
+    /** the number of sequential attributes "in use"  or set to a the original value (true or false) **/
+    public int m_NumSeqAttsSet;
+    
+    /** the Class Index for the data set **/
+    public int m_ClassIndex;
+    
+    /**
+     * constructor
+     * @param numInstances the number of instances in dataset
+     * @param numAtts the number of attributes in dataset
+     * @param value either true or false
+     * @param classIndex  Set to -1 if you want class attribute switched on or the value of the instances 
+     * class index will be switched of and the class attibute will not be considered.
+     */
+    public Indexes(int numInstances, int numAtts, boolean value, int classIndex) {
+      /* to create an empty DATASET with all attributes indexed use FALSE
+       * to create a index of all instances and attributes use TRUE
+       */
+      // initialise counts
+      m_NumInstsSet =  m_NumInstances = numInstances;
+      m_NumAttsSet = m_NumAtts = numAtts;
+      
+      m_InstIndexes = new boolean [(int)numInstances];
+      
+      /* set all indexes to value */
+      int i = 0;
+      while(i < numInstances) {
+	m_InstIndexes[i] = value;
+	i++;
+      }
+      
+      m_AttIndexes = new boolean [(int)numAtts];
+      
+      /* set all indexes to true */
+      i = 0;
+      while(i < numAtts) {
+	m_AttIndexes[i] = true;
+	i++;
+      }
+      // if the value is false the dataset has no instances therefore no instances are set
+      if(value == false)
+        m_NumInstsSet = 0;
+      // no sequential array has been created
+      m_SequentialInstanceIndex_valid = false;
+      m_SequentialAttIndex_valid = false;
+      
+      // switch class attr to false as the class is not used in the dataset.  Set to -1 if you want the class attr included
+      if(classIndex != -1)
+        setAttIndex(classIndex, false);
+      m_ClassIndex = classIndex;
+    }
+    
+    /**
+     * constructor
+     * @param FromIndexes the object you want to copy
+     */
+    public Indexes(Indexes FromIndexes) {
+      // set counts to the FromIndexes counts
+      m_NumInstances = FromIndexes.getNumInstances();
+      m_NumInstsSet = FromIndexes.m_NumInstsSet;
+      m_NumAtts = FromIndexes.m_NumAtts;
+      m_NumAttsSet = FromIndexes.m_NumAttsSet;
+      m_InstIndexes = new boolean [m_NumInstances];
+      
+      System.arraycopy(FromIndexes.m_InstIndexes, 0, m_InstIndexes, 0, m_NumInstances);
+      
+      m_AttIndexes = new boolean [(int)m_NumAtts];
+      
+      System.arraycopy(FromIndexes.m_AttIndexes, 0, m_AttIndexes, 0, m_NumAtts);
+      m_ClassIndex = FromIndexes.m_ClassIndex;
+      m_SequentialInstanceIndex_valid = false;
+      m_SequentialAttIndex_valid = false;
+    }
+    
+    /**
+     * 
+     * Changes the boolean value at the specified index in the InstIndexes array
+     *
+     * @param index the index of the instance
+     * @param value the value to set at the specified index
+     *
+     */
+    public void setInstanceIndex(int index, boolean value) {
+      if(index < 0 || index >= m_NumInstances)
+	throw new IllegalArgumentException("Invalid Instance Index value");
+      // checks that the index isn't alreading set to value
+      if(m_InstIndexes[(int)index] != value) {
+	
+	// set the value
+	m_InstIndexes[(int)index] = value;
+	
+	// a change has been made, so sequential array is invalid
+	m_SequentialInstanceIndex_valid = false;
+	
+	// change the number of values "in use" to appropriate value
+	if(value == false)
+	  m_NumInstsSet--;
+	else
+	  m_NumInstsSet++;
+      }
+   }
+    
+    /**
+     * 
+     * Changes the boolean value at the specified index in the InstIndexes array
+     *
+     * @param Attributes array of attributes
+     * @param value the value to set at the specified index
+     *
+     */
+    public void setAtts(int [] Attributes, boolean value) {
+      for(int i = 0; i < m_NumAtts; i++) {
+        m_AttIndexes[i] = !value;
+      }
+      for (int i = 0; i < Attributes.length; i++)  {
+        m_AttIndexes[Attributes[i]] = value;
+      }
+      m_NumAttsSet = Attributes.length;
+      m_SequentialAttIndex_valid = false;
+    }
+    
+    /**
+     * 
+     * Changes the boolean value at the specified index in the InstIndexes array
+     *
+     * @param Instances array of instances
+     * @param value the value to set at the specified index
+     *
+     */
+    public void setInsts(int [] Instances, boolean value) {
+      resetInstanceIndex(!value);
+      for (int i = 0; i < Instances.length; i++)  {
+        m_InstIndexes[Instances[i]] = value;
+      }
+      m_NumInstsSet = Instances.length;
+      m_SequentialInstanceIndex_valid = false;
+    }
+    
+    
+    /**
+     * 
+     * Changes the boolean value at the specified index in the AttIndexes array
+     *
+     * @param index the index of the instance
+     * @param value the value to set at the specified index
+     *
+     */
+    public void setAttIndex(int index, boolean value) {
+      if(index < 0 || index >= m_NumAtts)
+	throw new IllegalArgumentException("Invalid Attribute Index value");
+      // checks that the index isn't alreading set to value
+      if(m_AttIndexes[(int)index] != value) {
+	
+	// set the value
+	m_AttIndexes[(int)index] = value;
+	
+	// a change has been made, so sparse array is invalid
+	m_SequentialAttIndex_valid = false;  
+	
+	 // change the number of values "in use" to appropriate value
+	if(value == false)
+	  m_NumAttsSet--;
+	else
+	  m_NumAttsSet++;
+      }
+    }
+    
+    /**
+     * 
+     * Returns the boolean value at the specified index in the Instance Index array
+     *
+     * @param index the index of the instance
+     * @return the boolean value at the specified index
+     */
+    public boolean getInstanceIndex(int index) {
+      
+      if(index < 0 || index >= m_NumInstances)
+	throw new IllegalArgumentException("Invalid index value");
+      
+      return m_InstIndexes[(int)index]; 
+    }
+    
+    /**
+     * 
+     * Returns the boolean value at the specified index in the Sequential Instance Indexes array
+     *
+     * @param index the index of the instance
+     * @return the requested value
+     */
+    public int getSequentialInstanceIndex(int index) {
+      
+      if(index < 0 || index >= m_NumInstances)
+	throw new IllegalArgumentException("Invalid index value");
+      
+      return m_SequentialInstIndexes[(int)index]; 
+    }
+    
+    /**
+     * 
+     * Resets the boolean value in the Instance Indexes array to a specified value
+     *
+     * @param value the value to set all indexes
+     * 
+    */
+    public void resetInstanceIndex(boolean value) {
+      m_NumInstsSet = m_NumInstances;
+      for(int i = 0; i < m_NumInstances; i++) {
+	m_InstIndexes[i] = value;
+      }
+      if(value == false)
+	m_NumInstsSet =  0;
+      m_SequentialInstanceIndex_valid = false;
+    }
+    
+   /**
+    * 
+    * Resets the boolean values in Attribute and Instance array to reflect an empty dataset withthe same attributes set as in the incoming Indexes Object
+    *
+    * @param FromIndexes the Indexes to be copied
+    * 
+    */
+    public void resetDatasetBasedOn(Indexes FromIndexes) {
+      resetInstanceIndex(false);
+      resetAttIndexTo(FromIndexes);
+    }
+   
+    /**
+     * 
+     * Resets the boolean value in AttIndexes array
+     *
+     * @param value the value to set the attributes to
+     * 
+     */
+    public void resetAttIndex(boolean value) {
+      m_NumAttsSet =  m_NumAtts;
+      for(int i = 0; i < m_NumAtts; i++) {
+	m_AttIndexes[i] = value;
+      }
+      if(m_ClassIndex != -1)
+	setAttIndex(m_ClassIndex, false);
+      if(value == false)
+	m_NumAttsSet =  0;
+     m_SequentialAttIndex_valid = false;
+    }
+    
+    /**
+     * 
+     * Resets the boolean value in AttIndexes array based on another set of Indexes
+     *
+     * @param FromIndexes the Indexes to be copied
+     * 
+    */
+    public void resetAttIndexTo(Indexes FromIndexes) {
+      System.arraycopy(FromIndexes.m_AttIndexes, 0, m_AttIndexes, 0, m_NumAtts);
+      m_NumAttsSet =  FromIndexes.getNumAttributesSet();
+      m_ClassIndex = FromIndexes.m_ClassIndex;
+      m_SequentialAttIndex_valid = false;
+    }
+    
+    /**
+     * 
+     * Returns the boolean value at the specified index in the Attribute Indexes array
+     *
+     * @param index the index of the Instance
+     * @return the boolean value
+     */
+    public boolean getAttIndex(int index) {
+      
+      if(index < 0 || index >= m_NumAtts)
+         throw new IllegalArgumentException("Invalid index value");
+      
+      return m_AttIndexes[(int)index];
+    }
+    
+    /**
+     * 
+     * Returns the boolean value at the specified index in the Sequential Attribute Indexes array
+     *
+     * @param index the index of the Attribute
+     * @return the requested value
+     */
+    public int getSequentialAttIndex(int index) {
+      
+      if(index < 0 || index >= m_NumAtts)
+	throw new IllegalArgumentException("Invalid index value");
+      
+      return m_SequentialAttIndexes[(int)index];
+    }
+    
+    /**
+     * 
+     * Returns the number of instances "in use"
+     * 
+     * @return the number of instances "in use"
+     */
+    public int getNumInstancesSet() {
+      
+      return m_NumInstsSet;
+   }
+
+    /**
+     * 
+     * Returns the number of instances in the dataset
+     * 
+     * @return the number of instances in the dataset
+     */
+    public int getNumInstances() {
+      
+      return m_NumInstances;
+    }
+
+    /**
+     * 
+     * Returns the number of instances in the Sequential array
+     * 
+     * @return the number of instances in the sequential array
+     */
+    public int getSequentialNumInstances() {
+      // will always be the number set as the sequential array is for referencing only
+      return m_NumSeqInstsSet;
+    }
+    
+    /**
+     * 
+     * Returns the number of attributes in the dataset
+     * 
+     * @return the number of attributes
+     */
+    public int getNumAttributes() {
+      
+      return m_NumAtts;
+    }
+   
+    /**
+     * 
+     * Returns the number of attributes "in use"
+     * 
+     * @return the number of attributes "in use"
+     */
+    public int getNumAttributesSet() {
+      
+      return m_NumAttsSet;
+    }
+    
+    /**
+     * 
+     * Returns the number of attributes in the Sequential array
+     * 
+     * @return the number of attributes in the sequentual array
+     */
+    public int getSequentialNumAttributes() {
+      // will always be the number set as the sequential array is for referencing only
+      return m_NumSeqAttsSet;
+    }
+    
+    /**
+     * 
+     * Returns whether or not the Sequential Instance Index requires rebuilding due to a change 
+     * 
+     * @return true if the sequential instance index needs rebuilding
+     */
+    public boolean isSequentialInstanceIndexValid() {
+      
+      return m_SequentialInstanceIndex_valid;
+    }
+    
+    /**
+     * 
+     * Returns whether or not the Sequential Attribute Index requires rebuilding due to a change 
+     * 
+     * @return true if the sequential attribute index needs rebuilding
+     */
+    public boolean isSequentialAttIndexValid() {
+      
+      return m_SequentialAttIndex_valid;
+    }
+    
+    /**
+     * 
+     * Sets both the Instance and Attribute indexes to a specified value
+     * 
+     * @param value the value for the Instance and Attribute indices
+     */
+    public void setSequentialDataset(boolean value) {
+      setSequentialInstanceIndex(value);
+      setSequentialAttIndex(value);
+    }
+    
+    /**
+     * 
+     * A Sequential Instance index is all those Instances that are set to the specified value placed in a sequential array.
+     * Each value in the sequential array contains the Instance index within the Indexes.
+     *
+     * @param value the sequential instance index
+     */
+    public void setSequentialInstanceIndex(boolean value) {
+      
+      if(m_SequentialInstanceIndex_valid == true)
+	return;
+      
+      /* needs to be recalculated */
+      int size;
+      size = m_NumInstsSet;
+      
+      m_SequentialInstIndexes = new int [(int)size];
+      
+      int j = 0;
+      for(int i = 0; i < m_NumInstances; i++) {
+	if(m_InstIndexes[i] == value) {
+	  m_SequentialInstIndexes[j] = i;
+	  j++;
+	}
+      }
+      
+      m_SequentialInstanceIndex_valid = true;
+      m_NumSeqInstsSet = j;
+    }
+    
+    /**
+     * 
+     * A Sequential Attribute index is all those Attributes that are set to the specified value placed in a sequential array.
+     * Each value in the sequential array contains the Attribute index within the Indexes
+     * 
+     * @param value the sequential attribute index
+     */
+    public void setSequentialAttIndex(boolean value) {
+      
+      if(m_SequentialAttIndex_valid == true)
+	return;
+      
+      /* needs to be recalculated */
+      int size;
+      size = m_NumAttsSet;
+      
+      m_SequentialAttIndexes = new int [(int)size];
+      
+      int j = 0;
+      for(int i = 0; i < m_NumAtts; i++) {
+	if(m_AttIndexes[i] == value) {
+	  m_SequentialAttIndexes[j] = i;
+	  j++;
+	 }
+      }
+      
+      m_SequentialAttIndex_valid = true;
+      m_NumSeqAttsSet = j;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  } /* end of Indexes inner-class */
+  
+
+  /** All the counts for nominal attributes. */
+  protected int [][][] m_Counts;
+  /** All the counts for nominal attributes. */
+  protected int [][][] m_tCounts;
+  /** The prior probabilities of the classes. */
+  protected int [] m_Priors;
+  /** The prior probabilities of the classes. */
+  protected int [] m_tPriors;
+  
+  /** number of attributes for the dataset ***/
+  protected int m_numAtts;
+  
+  /** number of classes for dataset ***/
+  protected int m_numClasses;
+  
+ /** number of instances in dataset ***/
+  protected int m_numInsts;
+  
+  /** The set of instances used for current training. */
+  protected Instances m_Instances = null;
+  
+  /** leave-one-out errors on the training dataset. */
+  protected int m_Errors;
+  
+  /** leave-one-out error flags on the training dataaet. */
+  protected boolean [] m_ErrorFlags;
+  
+  /** best attribute's index list. maybe as output result */
+  protected ArrayList leftHand = new ArrayList();
+  
+  /** significantly lower */
+  protected static final double SIGNLOWER = 0.05;
+  
+  /** following is defined by wangzh, 
+   * the number of instances to be classified incorrectly
+   * on the subset. */
+  protected boolean [] m_subOldErrorFlags;
+  
+  /** the number of instances to be classified incorrectly
+   * besides the subset. */
+  protected int m_RemainderErrors = 0;
+  
+  /** the number of instance to be processed */
+  protected int m_Number = 0;
+  
+  /** the Number of Instances to be used in building a classifiers */
+  protected int m_NumberOfInstances = 0;
+  
+  /** for printing in n-fold cross validation */
+  protected boolean m_NCV = false;
+  
+  /** index of instances and attributes for the given dataset */
+  protected Indexes m_subInstances;
+  
+  /** index of instances and attributes for the given dataset */
+  protected Indexes tempSubInstances;
+  
+  /** probability values array */
+  protected double [] posteriorsArray;
+  protected int bestCnt;
+  protected int tempCnt;
+  protected int forCnt;
+  protected int whileCnt;
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return 
+        "Lazy Bayesian Rules Classifier. The naive Bayesian classifier "
+      + "provides a simple and effective approach to classifier learning, "
+      + "but its attribute independence assumption is often violated in the "
+      + "real world. Lazy Bayesian Rules selectively relaxes the independence "
+      + "assumption, achieving lower error rates over a range of learning "
+      + "tasks. LBR defers processing to classification time, making it a "
+      + "highly efficient and accurate classification algorithm when small "
+      + "numbers of objects are to be classified.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Zijian Zheng and G. Webb");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.TITLE, "Lazy Learning of Bayesian Rules");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "4");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "53-84");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * For lazy learning, building classifier is only to prepare their inputs
+   * until classification time.
+   *
+   * @param instances set of instances serving as training data
+   * @throws Exception if the preparation has not been generated.
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    int attIndex, i, j;
+    bestCnt = 0;
+    tempCnt = 0;
+    forCnt = 0;
+    whileCnt = 0;
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_numAtts = instances.numAttributes();
+    m_numClasses = instances.numClasses();
+    m_numInsts = instances.numInstances();
+
+    // Reserve space
+    m_Counts = new int[m_numClasses][m_numAtts][0];
+    m_Priors = new int[m_numClasses];
+    m_tCounts = new int[m_numClasses][m_numAtts][0];
+    m_tPriors = new int[m_numClasses];
+    m_subOldErrorFlags = new boolean[m_numInsts+1];
+    
+    m_Instances = instances;
+    
+    m_subInstances = new Indexes(m_numInsts, m_numAtts, true, m_Instances.classIndex());
+    tempSubInstances = new Indexes(m_numInsts, m_numAtts, true, m_Instances.classIndex());
+    
+    
+    posteriorsArray = new double[m_numClasses];
+    
+    // prepare arrays
+    for (attIndex = 0; attIndex < m_numAtts; attIndex++) {
+      Attribute attribute = (Attribute) instances.attribute(attIndex);
+      for (j = 0; j < m_numClasses; j++) {
+        m_Counts[j][attIndex] = new int[attribute.numValues()];
+        m_tCounts[j][attIndex] = new int[attribute.numValues()];
+      }
+    }
+
+    // Compute counts and priors
+    for(i = 0; i < m_numInsts; i++) {
+      Instance instance = (Instance) instances.instance(i);
+      int classValue = (int)instance.classValue();
+      // pointer for more efficient access to counts matrix in loop
+      int [][] countsPointer = m_tCounts[classValue];
+      for(attIndex = 0; attIndex < m_numAtts; attIndex++) {
+        countsPointer[attIndex][(int)instance.value(attIndex)]++;
+      }
+      m_tPriors[classValue]++;
+    }
+    
+    // Step 2: Leave-one-out on the training data set.
+    // get m_Errors and its flags array using leave-one-out.
+    m_ErrorFlags = new boolean[m_numInsts];
+    
+    m_Errors = leaveOneOut(m_subInstances, m_tCounts, m_tPriors, m_ErrorFlags);
+
+    if (m_Number == 0) {
+      m_NumberOfInstances = m_Instances.numInstances();
+    } else {
+      System.out.println(" ");
+      System.out.println("N-Fold Cross Validation: ");
+      m_NCV = true;
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities
+   * for the given test instance.
+   * This is the most important method for Lazy Bayesian Rule algorithm.
+   *
+   * @param testInstance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if distribution can't be computed
+   */
+  public double[] distributionForInstance(Instance testInstance)
+  throws Exception {
+    
+    int inst;
+    int subAttrIndex = 0;
+    int subInstIndex = 0;
+    int tempInstIndex = 0;
+    int attributeBest;
+    int subLocalErrors = 0;
+    int tempErrorsBest = 0;
+    boolean [] tempErrorFlagBest = null;
+    int [] tempD_subsetBestInsts = null;
+    int [] tempD_subsetBestAtts = null;
+    Indexes subInstances = new Indexes(m_numInsts, m_numAtts, true, m_Instances.classIndex());
+    
+    boolean [] subLocalErrorFlags = new  boolean [(int)subInstances.getNumInstances()+1];
+    // Step 2': Get localErrors, localErrorFlags, and training data set.
+    int localErrors = m_Errors;
+    boolean [] localErrorFlags = (boolean []) m_ErrorFlags.clone();
+    
+    // The number of errors on New, Not on Old in the subset.
+    int errorsNewNotOld = 0;
+    // The number of errors on Old, Not on New in the subset.
+    int errorsOldNotNew = 0;
+    
+    // Step 3:
+    leftHand.clear();
+
+    // Step 4: Beginning Repeat.
+    // Selecting all the attributes that can be moved to the lefthand.
+    while (localErrors >= 5) {
+      attributeBest = -1;
+      whileCnt++;
+      // Step 5:
+      tempErrorsBest = subInstances.getNumInstancesSet() + 1;
+      subInstances.setSequentialDataset(true);
+      // Step 6: selecting an attribute.
+      for (int attr = 0; attr < subInstances.m_NumSeqAttsSet; attr++){
+        forCnt++;
+        subAttrIndex = subInstances.m_SequentialAttIndexes[attr];
+        // Step 7: get the corresponding subset.
+        
+        m_RemainderErrors = 0;
+
+        // reset array to true
+        for(int i = 0; i < m_numInsts; i++) {
+          m_subOldErrorFlags[i] = true;
+        }
+        // reset indexes to reflect an empty dataset but with the same attrs as another dataset
+        tempSubInstances.resetDatasetBasedOn(subInstances);
+        // Get subset of the instances and its m_LastSecondErrors
+        for(inst = 0; inst < subInstances.m_NumSeqInstsSet; inst++) {
+          subInstIndex = subInstances.m_SequentialInstIndexes[inst];
+          if (m_Instances.instance(subInstIndex).value(subAttrIndex) == testInstance.value(subAttrIndex))  {
+            // add instance to subset list
+            tempSubInstances.setInstanceIndex(subInstIndex, true);
+            if (localErrorFlags[subInstIndex] == false ) {
+              m_subOldErrorFlags[subInstIndex] = false;
+            }
+          }
+          else  {
+            if (localErrorFlags[subInstIndex] == false ) {
+              m_RemainderErrors++;
+            }
+          }
+        } // end of for
+
+        // Step 7':
+        if (tempSubInstances.m_NumInstsSet < subInstances.m_NumInstsSet) {
+          // remove attribute from index
+          tempSubInstances.setAttIndex(subAttrIndex, false);
+          // Step 9: create a classifier on the subset.
+          // Compute counts and priors
+          // create sequential index of instances and attributes that are to be considered
+                
+          localNaiveBayes(tempSubInstances);
+          
+          subLocalErrors = leaveOneOut(tempSubInstances, m_Counts, m_Priors, subLocalErrorFlags);
+
+          errorsNewNotOld = 0;
+          errorsOldNotNew = 0;
+          
+          tempSubInstances.setSequentialDataset(true);
+          
+          for(int t_inst = 0; t_inst < tempSubInstances.m_NumSeqInstsSet; t_inst++) {
+            tempInstIndex = tempSubInstances.m_SequentialInstIndexes[t_inst];
+            if (subLocalErrorFlags[tempInstIndex] == false) {
+              // The number of errors on New, Not on Old in the subset.
+              if (m_subOldErrorFlags[tempInstIndex] == true) {
+                errorsNewNotOld ++;
+              }
+            } else {
+              // The number of errors on Old, Not on New in the subset.
+              if(m_subOldErrorFlags[tempInstIndex] == false) {
+                errorsOldNotNew ++;
+              }
+            }
+          } //end of for
+          
+          // Step 10 and Step 11:
+          int tempErrors = subLocalErrors + m_RemainderErrors;
+          // Step 12:
+          // Step 13: stopping criteria.
+          if((tempErrors < tempErrorsBest) && (binomP(errorsNewNotOld, errorsNewNotOld + errorsOldNotNew, 0.5 ) < SIGNLOWER))      {
+            // Step 14:
+            tempCnt++;
+            // --------------------------------------------------
+            //tempD_subsetBest = new Indexes(tempSubInstances);
+            
+            // -------------------------------------------------------------------------------
+            tempSubInstances.setSequentialDataset(true);
+            tempD_subsetBestInsts = (int []) tempSubInstances.m_SequentialInstIndexes.clone();
+            tempD_subsetBestAtts = (int []) tempSubInstances.m_SequentialAttIndexes.clone();
+            // -------------------------------------------------------------------------------
+            // Step 15:
+            tempErrorsBest = tempErrors;
+
+            tempErrorFlagBest = (boolean []) subLocalErrorFlags.clone();
+
+            // Step 16:
+            attributeBest = subAttrIndex;
+          } // end of if
+        } // end of if
+      } // end of main for
+      
+      // Step 20:
+      if(attributeBest != -1)  {
+        bestCnt++;
+        // Step 21:
+        leftHand.add(testInstance.attribute(attributeBest));
+        // ------------------------------------------------
+        // Step 22:
+        //tempD_subsetBest.setAttIndex(attributeBest, false);
+        //subInstances = tempD_subsetBest;
+        // ------------------------------------------------ 
+        subInstances.setInsts(tempD_subsetBestInsts, true);
+        subInstances.setAtts(tempD_subsetBestAtts, true);
+        subInstances.setAttIndex(attributeBest, false);
+        // -------------------------------------------------
+        // Step 25:
+        localErrors = tempErrorsBest;
+        localErrorFlags =  tempErrorFlagBest;
+        
+      } else {
+        break;
+      }
+    } // end of while
+    
+    // Step 27:
+    localNaiveBayes(subInstances);
+    return localDistributionForInstance(testInstance, subInstances);
+  }
+  
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+    
+    if (m_Instances == null) {
+      return "Lazy Bayesian Rule: No model built yet.";
+    }
+    
+    try {
+      StringBuffer text = new StringBuffer
+      ("=== LBR Run information ===\n\n");
+      
+      text.append("Scheme:       weka.classifiers.LBR\n");
+      
+      text.append("Relation:     "
+      + m_Instances.attribute(m_Instances.classIndex()).name()
+      + "\n");
+      
+      text.append("Instances:    "+m_Instances.numInstances()+"\n");
+      
+      text.append("Attributes:   "+m_Instances.numAttributes()+"\n");
+      
+      // Remains are printed by Evaulation.java
+      return text.toString();
+    } catch (Exception e) {
+      e.printStackTrace();
+      return "Can't Print Lazy Bayes Rule Classifier!";
+    }
+  }
+
+  /**
+   * Leave-one-out strategy. For a given sample data set with n instances,
+   * using (n - 1) instances by leaving one out and tested on the single
+   * remaining case.
+   * This is repeated n times in turn.
+   * The final "Error" is the sum of the instances to be classified
+   * incorrectly.
+   *
+   * @param instanceIndex set of instances serving as training data.
+   * @param counts serving as all the counts of training data.
+   * @param priors serving as the number of instances in each class.
+   * @param errorFlags for the errors
+   *
+   * @return error flag array about each instance.
+   * @throws Exception if something goes wrong
+   **/
+  public int leaveOneOut(Indexes instanceIndex, int [][][] counts, int [] priors, boolean [] errorFlags)  throws Exception {
+    
+    
+    // ###### START LEAVE ONE OUT #############
+    int tempClassValue;
+    double posteriors;
+    double sumForPriors;
+    double sumForCounts;
+    double max = 0;
+    int maxIndex = 0;
+    int AIndex, attIndex, clss;
+    int inst;
+    int errors = 0;
+    int instIndex;
+    
+    instanceIndex.setSequentialDataset(true);
+    int tempInstanceClassValue;
+    int [] tempAttributeValues = new int[(int)instanceIndex.m_NumSeqAttsSet+1];
+    Instance tempInstance;
+    for(inst = 0; inst < instanceIndex.m_NumSeqInstsSet; inst++) {
+      instIndex = instanceIndex.m_SequentialInstIndexes[inst];
+      //get the leave-one-out instance
+      tempInstance = (Instance) m_Instances.instance(instIndex);
+      if (!tempInstance.classIsMissing()) {
+      tempInstanceClassValue = (int)tempInstance.classValue();
+      // pointer to first index of counts matrix for efficiency
+      int [][] countsPointer = counts[tempInstanceClassValue];
+      // Compute the counts and priors for (n-1) instances.
+      for(attIndex = 0; attIndex < instanceIndex.m_NumSeqAttsSet; attIndex++) {
+        AIndex = instanceIndex.m_SequentialAttIndexes[attIndex];
+        tempAttributeValues[attIndex] = (int)tempInstance.value(AIndex);
+        countsPointer[AIndex][tempAttributeValues[attIndex]]--;
+      }
+      
+      priors[tempInstanceClassValue]--;
+      max = 0;
+      maxIndex= 0;
+      // ###### LOCAL CLASSIFY INSTANCE ###########
+      sumForPriors = Utils.sum(priors);
+      for (clss = 0; clss < m_numClasses; clss++) {
+        posteriors = 0.0;
+        posteriors = (priors[clss] + 1) / (sumForPriors + m_numClasses);
+        
+	countsPointer = counts[clss];
+        for(attIndex = 0; attIndex < instanceIndex.m_NumSeqAttsSet; attIndex++) {
+          AIndex = instanceIndex.m_SequentialAttIndexes[attIndex];
+          if (!tempInstance.isMissing(AIndex)) {
+            sumForCounts = Utils.sum(countsPointer[AIndex]);
+            posteriors *= ((countsPointer[AIndex][tempAttributeValues[attIndex]] + 1) / (sumForCounts + (double)tempInstance.attribute(AIndex).numValues()));
+          }
+        }
+        
+        if (posteriors > max) {
+          maxIndex = clss;
+          max = posteriors;
+        }
+      } // end of for
+      
+      if (max > 0) {
+        tempClassValue = maxIndex;
+      } else {
+        tempClassValue = (int)Utils.missingValue();
+      }
+      // ###### END LOCAL CLASSIFY INSTANCE ###########
+      
+      // Adjudge error. Here using classIndex is incorrect,
+      // it is index of the class attribute.
+      if(tempClassValue == tempInstanceClassValue){
+        errorFlags[instIndex] = true;
+      } else {
+        errorFlags[instIndex] = false;
+        errors++;
+      }
+      
+      countsPointer = counts[tempInstanceClassValue];
+      for(attIndex = 0; attIndex < instanceIndex.m_NumSeqAttsSet; attIndex++) {
+        AIndex = instanceIndex.m_SequentialAttIndexes[attIndex];
+        counts[tempInstanceClassValue][AIndex][tempAttributeValues[attIndex]]++;
+      }
+      
+      priors[tempInstanceClassValue]++;
+      }
+    } // end of for
+    // ###### END LEAVE ONE OUT #############
+    return errors;
+  }
+ 
+  /**
+   * Class for building and using a simple Naive Bayes classifier.
+   * For more information, see<p>
+   *
+   * Richard Duda and Peter Hart (1973).<i>Pattern
+   * Classification and Scene Analysis</i>. Wiley, New York.
+   *
+   * This method only get m_Counts and m_Priors.
+   *
+   * @param instanceIndex set of instances serving as training data
+   * @throws Exception if m_Counts and m_Priors have not been
+   *  generated successfully
+   */
+  public void localNaiveBayes(Indexes instanceIndex) throws Exception {
+    int attIndex = 0;
+    int i, AIndex;
+    int attVal = 0;
+    int classVal = 0;
+    Instance instance;
+
+    instanceIndex.setSequentialDataset(true);
+
+    // reset local counts
+    for(classVal = 0; classVal < m_numClasses; classVal++) {
+      // counts pointer mcTimesaver
+      int [][] countsPointer1 = m_Counts[classVal];
+      for(attIndex = 0; attIndex < m_numAtts; attIndex++) {
+        Attribute attribute = m_Instances.attribute(attIndex);
+         // love those pointers for saving time
+         int [] countsPointer2 = countsPointer1[attIndex];
+        for(attVal = 0; attVal < attribute.numValues(); attVal++)  {
+          countsPointer2[attVal] = 0;
+        }
+     }
+     m_Priors[classVal] = 0;
+   }
+
+    for(i = 0; i < instanceIndex.m_NumSeqInstsSet; i++) {
+      instance = (Instance) m_Instances.instance(instanceIndex.m_SequentialInstIndexes[i]);
+      for(attIndex = 0; attIndex < instanceIndex.m_NumSeqAttsSet; attIndex++) {
+        AIndex = instanceIndex.m_SequentialAttIndexes[attIndex];
+        m_Counts[(int)instance.classValue()][AIndex][(int)instance.value(AIndex)]++;
+      }
+      m_Priors[(int)instance.classValue()]++;
+    }
+  }
+    
+  /**
+   * Calculates the class membership probabilities.
+   * for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @param instanceIndex 
+   *
+   * @return predicted class probability distribution
+   * @throws Exception if distribution can't be computed
+   */
+  public double[] localDistributionForInstance(Instance instance, Indexes instanceIndex) throws Exception {
+    
+    double sumForPriors = 0;
+    double sumForCounts = 0;
+    int attIndex, AIndex;
+    int numClassesOfInstance = instance.numClasses();
+    
+    sumForPriors = 0;
+    sumForCounts = 0;
+    instanceIndex.setSequentialDataset(true);
+    // Calculate all of conditional probabilities.
+    sumForPriors = Utils.sum(m_Priors) + numClassesOfInstance;
+    for (int j = 0; j < numClassesOfInstance; j++) {
+      // pointer to counts to make access more efficient in loop
+      int [][] countsPointer = m_Counts[j];
+      posteriorsArray[j] = (m_Priors[j] + 1) / (sumForPriors);
+      for(attIndex = 0; attIndex < instanceIndex.m_NumSeqAttsSet; attIndex++) {
+        AIndex = instanceIndex.m_SequentialAttIndexes[attIndex];
+        sumForCounts = Utils.sum(countsPointer[AIndex]);
+        if (!instance.isMissing(AIndex)) {
+          posteriorsArray[j] *= ((countsPointer[AIndex][(int)instance.value(AIndex)] + 1) / (sumForCounts + (double)instance.attribute(AIndex).numValues()));
+        }
+      }
+    }
+    
+    // Normalize probabilities
+    Utils.normalize(posteriorsArray);
+    
+    return posteriorsArray;
+  }
+  
+  /**
+   * Significance test
+   * binomp:
+   *
+   * @param r
+   * @param n
+   * @param p
+   * @return returns the probability of obtaining r or fewer out of n
+   * if the probability of an event is p.
+   * @throws Exception if computation fails
+   */
+  public double binomP(double r, double n, double p) throws Exception {
+    
+    if (n == r) return 1.0;
+    return Statistics.incompleteBeta(n-r, r+1.0, 1.0-p);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new LBR(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/LWL.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/LWL.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/LWL.java	(revision 29)
@@ -0,0 +1,754 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LWL.java
+ *    Copyright (C) 1999, 2002, 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.neighboursearch.LinearNNSearch;
+import weka.core.neighboursearch.NearestNeighbourSearch;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Locally weighted learning. Uses an instance-based algorithm to assign instance weights which are then used by a specified WeightedInstancesHandler.<br/>
+ * Can do classification (e.g. using naive Bayes) or regression (e.g. using linear regression).<br/>
+ * <br/>
+ * For more info, see<br/>
+ * <br/>
+ * Eibe Frank, Mark Hall, Bernhard Pfahringer: Locally Weighted Naive Bayes. In: 19th Conference in Uncertainty in Artificial Intelligence, 249-256, 2003.<br/>
+ * <br/>
+ * C. Atkeson, A. Moore, S. Schaal (1996). Locally weighted learning. AI Review..
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Frank2003,
+ *    author = {Eibe Frank and Mark Hall and Bernhard Pfahringer},
+ *    booktitle = {19th Conference in Uncertainty in Artificial Intelligence},
+ *    pages = {249-256},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Locally Weighted Naive Bayes},
+ *    year = {2003}
+ * }
+ * 
+ * &#64;article{Atkeson1996,
+ *    author = {C. Atkeson and A. Moore and S. Schaal},
+ *    journal = {AI Review},
+ *    title = {Locally weighted learning},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A
+ *  The nearest neighbour search algorithm to use (default: weka.core.neighboursearch.LinearNNSearch).
+ * </pre>
+ * 
+ * <pre> -K &lt;number of neighbours&gt;
+ *  Set the number of neighbours used to set the kernel bandwidth.
+ *  (default all)</pre>
+ * 
+ * <pre> -U &lt;number of weighting method&gt;
+ *  Set the weighting kernel shape to use. 0=Linear, 1=Epanechnikov,
+ *  2=Tricube, 3=Inverse, 4=Gaussian.
+ *  (default 0 = Linear)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 6055 $ 
+ */
+public class LWL 
+  extends SingleClassifierEnhancer
+  implements UpdateableClassifier, WeightedInstancesHandler, 
+             TechnicalInformationHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = 1979797405383665815L;
+
+  /** The training instances used for classification. */
+  protected Instances m_Train;
+    
+  /** The number of neighbours used to select the kernel bandwidth. */
+  protected int m_kNN = -1;
+
+  /** The weighting kernel method currently selected. */
+  protected int m_WeightKernel = LINEAR;
+
+  /** True if m_kNN should be set to all instances. */
+  protected boolean m_UseAllK = true;
+  
+  /** The nearest neighbour search algorithm to use. 
+   * (Default: weka.core.neighboursearch.LinearNNSearch) 
+   */
+  protected NearestNeighbourSearch m_NNSearch =  new LinearNNSearch();
+  
+  /** The available kernel weighting methods. */
+  public static final int LINEAR       = 0;
+  public static final int EPANECHNIKOV = 1;
+  public static final int TRICUBE      = 2;  
+  public static final int INVERSE      = 3;
+  public static final int GAUSS        = 4;
+  public static final int CONSTANT     = 5;
+
+  /** a ZeroR model in case no model can be built from the data. */
+  protected Classifier m_ZeroR;
+    
+  /**
+   * Returns a string describing classifier.
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Locally weighted learning. Uses an instance-based algorithm to "
+      + "assign instance weights which are then used by a specified "
+      + "WeightedInstancesHandler.\n"
+      + "Can do classification (e.g. using naive Bayes) or regression "
+      + "(e.g. using linear regression).\n\n"
+      + "For more info, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Eibe Frank and Mark Hall and Bernhard Pfahringer");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.TITLE, "Locally Weighted Naive Bayes");
+    result.setValue(Field.BOOKTITLE, "19th Conference in Uncertainty in Artificial Intelligence");
+    result.setValue(Field.PAGES, "249-256");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "C. Atkeson and A. Moore and S. Schaal");
+    additional.setValue(Field.YEAR, "1996");
+    additional.setValue(Field.TITLE, "Locally weighted learning");
+    additional.setValue(Field.JOURNAL, "AI Review");
+    
+    return result;
+  }
+    
+  /**
+   * Constructor.
+   */
+  public LWL() {    
+    m_Classifier = new weka.classifiers.trees.DecisionStump();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names 
+   * produced by the neighbour search algorithm.
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    return m_NNSearch.enumerateMeasures();
+  }
+  
+  /**
+   * Returns the value of the named measure from the 
+   * neighbour search algorithm.
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    return m_NNSearch.getMeasure(additionalMeasureName);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option("\tThe nearest neighbour search " +
+                                    "algorithm to use " +
+                                    "(default: weka.core.neighboursearch.LinearNNSearch).\n",
+                                    "A", 0, "-A"));
+    newVector.addElement(new Option("\tSet the number of neighbours used to set"
+				    +" the kernel bandwidth.\n"
+				    +"\t(default all)",
+				    "K", 1, "-K <number of neighbours>"));
+    newVector.addElement(new Option("\tSet the weighting kernel shape to use."
+				    +" 0=Linear, 1=Epanechnikov,\n"
+				    +"\t2=Tricube, 3=Inverse, 4=Gaussian.\n"
+				    +"\t(default 0 = Linear)",
+				    "U", 1,"-U <number of weighting method>"));
+    
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -A
+   *  The nearest neighbour search algorithm to use (default: weka.core.neighboursearch.LinearNNSearch).
+   * </pre>
+   * 
+   * <pre> -K &lt;number of neighbours&gt;
+   *  Set the number of neighbours used to set the kernel bandwidth.
+   *  (default all)</pre>
+   * 
+   * <pre> -U &lt;number of weighting method&gt;
+   *  Set the weighting kernel shape to use. 0=Linear, 1=Epanechnikov,
+   *  2=Tricube, 3=Inverse, 4=Gaussian.
+   *  (default 0 = Linear)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String knnString = Utils.getOption('K', options);
+    if (knnString.length() != 0) {
+      setKNN(Integer.parseInt(knnString));
+    } else {
+      setKNN(-1);
+    }
+
+    String weightString = Utils.getOption('U', options);
+    if (weightString.length() != 0) {
+      setWeightingKernel(Integer.parseInt(weightString));
+    } else {
+      setWeightingKernel(LINEAR);
+    }
+    
+    String nnSearchClass = Utils.getOption('A', options);
+    if(nnSearchClass.length() != 0) {
+      String nnSearchClassSpec[] = Utils.splitOptions(nnSearchClass);
+      if(nnSearchClassSpec.length == 0) { 
+        throw new Exception("Invalid NearestNeighbourSearch algorithm " +
+                            "specification string."); 
+      }
+      String className = nnSearchClassSpec[0];
+      nnSearchClassSpec[0] = "";
+
+      setNearestNeighbourSearchAlgorithm( (NearestNeighbourSearch)
+                  Utils.forName( NearestNeighbourSearch.class, 
+                                 className, 
+                                 nnSearchClassSpec)
+                                        );
+    }
+    else 
+      this.setNearestNeighbourSearchAlgorithm(new LinearNNSearch());
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 6];
+
+    int current = 0;
+
+    options[current++] = "-U"; options[current++] = "" + getWeightingKernel();
+    if ( (getKNN() == 0) && m_UseAllK) {
+      options[current++] = "-K"; options[current++] = "-1";
+    }
+    else {
+      options[current++] = "-K"; options[current++] = "" + getKNN();
+    }
+    options[current++] = "-A";
+    options[current++] = m_NNSearch.getClass().getName()+" "+Utils.joinOptions(m_NNSearch.getOptions()); 
+
+    System.arraycopy(superOptions, 0, options, current,
+                     superOptions.length);
+
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String KNNTipText() {
+    return "How many neighbours are used to determine the width of the "
+      + "weighting function (<= 0 means all neighbours).";
+  }
+
+  /**
+   * Sets the number of neighbours used for kernel bandwidth setting.
+   * The bandwidth is taken as the distance to the kth neighbour.
+   *
+   * @param knn the number of neighbours included inside the kernel
+   * bandwidth, or 0 to specify using all neighbors.
+   */
+  public void setKNN(int knn) {
+
+    m_kNN = knn;
+    if (knn <= 0) {
+      m_kNN = 0;
+      m_UseAllK = true;
+    } else {
+      m_UseAllK = false;
+    }
+  }
+
+  /**
+   * Gets the number of neighbours used for kernel bandwidth setting.
+   * The bandwidth is taken as the distance to the kth neighbour.
+   *
+   * @return the number of neighbours included inside the kernel
+   * bandwidth, or 0 for all neighbours
+   */
+  public int getKNN() {
+
+    return m_kNN;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightingKernelTipText() {
+    return "Determines weighting function. [0 = Linear, 1 = Epnechnikov,"+
+	   "2 = Tricube, 3 = Inverse, 4 = Gaussian and 5 = Constant. "+
+	   "(default 0 = Linear)].";
+  }
+
+  /**
+   * Sets the kernel weighting method to use. Must be one of LINEAR, 
+   * EPANECHNIKOV,  TRICUBE, INVERSE, GAUSS or CONSTANT, other values
+   * are ignored.
+   *
+   * @param kernel the new kernel method to use. Must be one of LINEAR,
+   * EPANECHNIKOV,  TRICUBE, INVERSE, GAUSS or CONSTANT.
+   */
+  public void setWeightingKernel(int kernel) {
+
+    if ((kernel != LINEAR)
+	&& (kernel != EPANECHNIKOV)
+	&& (kernel != TRICUBE)
+	&& (kernel != INVERSE)
+	&& (kernel != GAUSS)
+	&& (kernel != CONSTANT)) {
+      return;
+    }
+    m_WeightKernel = kernel;
+  }
+
+  /**
+   * Gets the kernel weighting method to use.
+   *
+   * @return the new kernel method to use. Will be one of LINEAR,
+   * EPANECHNIKOV,  TRICUBE, INVERSE, GAUSS or CONSTANT.
+   */
+  public int getWeightingKernel() {
+
+    return m_WeightKernel;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String nearestNeighbourSearchAlgorithmTipText() {
+    return "The nearest neighbour search algorithm to use (Default: LinearNN).";
+  }
+  
+  /**
+   * Returns the current nearestNeighbourSearch algorithm in use.
+   * @return the NearestNeighbourSearch algorithm currently in use.
+   */
+  public NearestNeighbourSearch getNearestNeighbourSearchAlgorithm() {
+    return m_NNSearch;
+  }
+  
+  /**
+   * Sets the nearestNeighbourSearch algorithm to be used for finding nearest
+   * neighbour(s).
+   * @param nearestNeighbourSearchAlgorithm - The NearestNeighbourSearch class.
+   */
+  public void setNearestNeighbourSearchAlgorithm(NearestNeighbourSearch nearestNeighbourSearchAlgorithm) {
+    m_NNSearch = nearestNeighbourSearchAlgorithm;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+    
+    if (m_Classifier != null) {
+      result = m_Classifier.getCapabilities();
+    } else {
+      result = super.getCapabilities();
+    }
+    
+    result.setMinimumNumberInstances(0);
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+  
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    if (!(m_Classifier instanceof WeightedInstancesHandler)) {
+      throw new IllegalArgumentException("Classifier must be a "
+					 + "WeightedInstancesHandler!");
+    }
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (instances.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(instances);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_Train = new Instances(instances, 0, instances.numInstances());
+
+    m_NNSearch.setInstances(m_Train);
+  }
+
+  /**
+   * Adds the supplied instance to the training set.
+   *
+   * @param instance the instance to add
+   * @throws Exception if instance could not be incorporated
+   * successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+
+    if (m_Train == null) {
+      throw new Exception("No training instance structure set!");
+    }
+    else if (m_Train.equalHeaders(instance.dataset()) == false) {
+      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    }
+    if (!instance.classIsMissing()) {
+      m_NNSearch.update(instance);
+      m_Train.add(instance);
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    if (m_Train.numInstances() == 0) {
+      throw new Exception("No training instances!");
+    }
+    
+    m_NNSearch.addInstanceInfo(instance);
+    
+    int k = m_Train.numInstances();
+    if( (!m_UseAllK && (m_kNN < k)) /*&&
+       !(m_WeightKernel==INVERSE ||
+         m_WeightKernel==GAUSS)*/ ) {
+      k = m_kNN;
+    }
+    
+    Instances neighbours = m_NNSearch.kNearestNeighbours(instance, k);
+    double distances[] = m_NNSearch.getDistances();
+
+    if (m_Debug) {
+      System.out.println("Test Instance: "+instance);
+      System.out.println("For "+k+" kept " + neighbours.numInstances() + " out of " + 
+                         m_Train.numInstances() + " instances.");
+    }
+    
+    //IF LinearNN has skipped so much that <k neighbours are remaining.
+    if(k>distances.length)
+      k = distances.length;
+
+    if (m_Debug) {
+      System.out.println("Instance Distances");
+      for (int i = 0; i < distances.length; i++) {
+	System.out.println("" + distances[i]);
+      }
+    }
+
+    // Determine the bandwidth
+    double bandwidth = distances[k-1];
+
+    // Check for bandwidth zero
+    if (bandwidth <= 0) {
+      //if the kth distance is zero than give all instances the same weight
+      for(int i=0; i < distances.length; i++)
+        distances[i] = 1;
+    } else {
+      // Rescale the distances by the bandwidth
+      for (int i = 0; i < distances.length; i++)
+        distances[i] = distances[i] / bandwidth;
+    }
+    
+    // Pass the distances through a weighting kernel
+    for (int i = 0; i < distances.length; i++) {
+      switch (m_WeightKernel) {
+        case LINEAR:
+          distances[i] = 1.0001 - distances[i];
+          break;
+        case EPANECHNIKOV:
+          distances[i] = 3/4D*(1.0001 - distances[i]*distances[i]);
+          break;
+        case TRICUBE:
+          distances[i] = Math.pow( (1.0001 - Math.pow(distances[i], 3)), 3 );
+          break;
+        case CONSTANT:
+          //System.err.println("using constant kernel");
+          distances[i] = 1;
+          break;
+        case INVERSE:
+          distances[i] = 1.0 / (1.0 + distances[i]);
+          break;
+        case GAUSS:
+          distances[i] = Math.exp(-distances[i] * distances[i]);
+          break;
+      }
+    }
+
+    if (m_Debug) {
+      System.out.println("Instance Weights");
+      for (int i = 0; i < distances.length; i++) {
+	System.out.println("" + distances[i]);
+      }
+    }
+    
+    // Set the weights on the training data
+    double sumOfWeights = 0, newSumOfWeights = 0;
+    for (int i = 0; i < distances.length; i++) {
+      double weight = distances[i];
+      Instance inst = (Instance) neighbours.instance(i);
+      sumOfWeights += inst.weight();
+      newSumOfWeights += inst.weight() * weight;
+      inst.setWeight(inst.weight() * weight);
+      //weightedTrain.add(newInst);
+    }
+    
+    // Rescale weights
+    for (int i = 0; i < neighbours.numInstances(); i++) {
+      Instance inst = neighbours.instance(i);
+      inst.setWeight(inst.weight() * sumOfWeights / newSumOfWeights);
+    }
+
+    // Create a weighted classifier
+    m_Classifier.buildClassifier(neighbours);
+
+    if (m_Debug) {
+      System.out.println("Classifying test instance: " + instance);
+      System.out.println("Built base classifier:\n" 
+			 + m_Classifier.toString());
+    }
+
+    // Return the classifier's predictions
+    return m_Classifier.distributionForInstance(instance);
+  }
+ 
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_Train == null) {
+      return "Locally weighted learning: No model built yet.";
+    }
+    String result = "Locally weighted learning\n"
+      + "===========================\n";
+
+    result += "Using classifier: " + m_Classifier.getClass().getName() + "\n";
+
+    switch (m_WeightKernel) {
+    case LINEAR:
+      result += "Using linear weighting kernels\n";
+      break;
+    case EPANECHNIKOV:
+      result += "Using epanechnikov weighting kernels\n";
+      break;
+    case TRICUBE:
+      result += "Using tricube weighting kernels\n";
+      break;
+    case INVERSE:
+      result += "Using inverse-distance weighting kernels\n";
+      break;
+    case GAUSS:
+      result += "Using gaussian weighting kernels\n";
+      break;
+    case CONSTANT:
+      result += "Using constant weighting kernels\n";
+      break;
+    }
+    result += "Using " + (m_UseAllK ? "all" : "" + m_kNN) + " neighbours";
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6055 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new LWL(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarCache.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarCache.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarCache.java	(revision 29)
@@ -0,0 +1,322 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *    KStarCache.java
+ *    Copyright (C) 1995 University of Waikato
+ *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
+ *
+ */
+
+
+package weka.classifiers.lazy.kstar;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * A class representing the caching system used to keep track of each attribute
+ * value and its corresponding scale factor or stop parameter.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public class KStarCache
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7693632394267140678L;
+  
+  /**
+   * cache table
+   */
+  CacheTable m_Cache = new CacheTable();
+  
+  /**
+   * Stores the specified values in the cahce table for easy retrieval.
+   *
+   * @param key attribute value used key to lookup the cache table.
+   * @param value cache parameter: attribute scale/stop parameter.
+   * @param pmiss cache parameter: transformation probability to 
+   * attribute with missing value.
+   */
+  public void store(double key, double value, double pmiss) {
+    if ( !m_Cache.containsKey(key) ) {
+      m_Cache.insert(key, value, pmiss);
+    }
+  }
+  
+  /**
+   * Checks if the specified key maps with an entry in the cache table
+   *
+   * @param key the key to map with an entry in the hashtable.
+   */
+  public boolean containsKey(double key) {
+    if ( m_Cache.containsKey(key) ) {
+      return true;
+    }
+    return false;
+  }
+  
+  /**
+   * Returns the values in the cache mapped by the specified key
+   *
+   * @param key the key used to retrieve the table entry.
+   */
+  public TableEntry getCacheValues( double key ) {
+    if ( m_Cache.containsKey(key) ) {
+      return m_Cache.getEntry(key);
+    }
+    return null;
+  }
+
+  /**
+   * A custom hashtable class to support the caching system.
+   *
+   */
+  public class CacheTable
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -8086106452588253423L;
+
+    /** The hash table data. */
+    private TableEntry [] m_Table;
+
+    /** The total number of entries in the hash table. */
+    private int m_Count;
+
+    /** Rehashes the table when count exceeds this threshold. */
+    private int m_Threshold;
+
+    /** The load factor for the hashtable. */
+    private float m_LoadFactor;
+
+    /** The default size of the hashtable */
+    private final int DEFAULT_TABLE_SIZE = 101;
+
+    /** The default load factor for the hashtable */
+    private final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+    //    private final float DEFAULT_LOAD_FACTOR = 0.5f;
+    /** Accuracy value for equality */
+    private final double EPSILON = 1.0E-5;
+    
+    /**
+     * Constructs a new hashtable with a default capacity and load factor.
+     */
+    public CacheTable(int size, float loadFactor) {
+      m_Table = new TableEntry[size];
+      m_LoadFactor = loadFactor;
+      m_Threshold = (int)(size * loadFactor);
+      m_Count = 0;
+    }
+    
+    /**
+     * Constructs a new hashtable with a default capacity and load factor.
+     */
+    public CacheTable() {
+      this(101, 0.75f);
+    }
+    
+    /**
+     * Tests if the specified double is a key in this hashtable.
+     */
+    public boolean containsKey(double key) {
+      TableEntry [] table = m_Table;
+      int hash = hashCode(key);
+      int index = (hash & 0x7FFFFFFF) % table.length;
+      for (TableEntry e = table[index] ; e != null ; e = e.next) {
+	if ((e.hash == hash) && (Math.abs(e.key - key) < EPSILON)) {
+	  return true;
+	}
+      }
+      return false;
+    }
+    
+    /**
+     * Inserts a new entry in the hashtable using the specified key. 
+     * If the key already exist in the hashtable, do nothing.
+     */
+    public void insert(double key, double value, double pmiss) {
+      // Makes sure the key is not already in the hashtable.
+      TableEntry e, ne;
+      TableEntry [] table = m_Table;
+      int hash = hashCode(key);
+      int index = (hash & 0x7FFFFFFF) % table.length;
+      // start looking along the chain
+      for (e = table[index] ; e != null ; e = e.next) {
+	if ((e.hash == hash) && (Math.abs(e.key - key) < EPSILON)) {
+	  return;
+	}
+      }
+      // At this point, key is not in table.
+      // Creates a new entry.
+      ne = new TableEntry( hash, key, value, pmiss, table[index] );
+      // Put entry at the head of the chain.
+      table[index] = ne;
+      m_Count++;
+      // Rehash the table if the threshold is exceeded
+      if (m_Count >= m_Threshold) {
+	rehash();
+      }
+    }
+    
+    /**
+     * Returns the table entry to which the specified key is mapped in 
+     * this hashtable.
+     * @return a table entry.
+     */
+    public TableEntry getEntry(double key) {
+      TableEntry [] table = m_Table;
+      int hash = hashCode(key);
+      int index = (hash & 0x7FFFFFFF) % table.length;
+      for (TableEntry e = table[index] ; e != null ; e = e.next) {
+	if ((e.hash == hash) && (Math.abs(e.key - key) < EPSILON)) {
+	  return e;
+	}
+      }
+      return null;
+    }
+    
+    /**
+     * Returns the number of keys in this hashtable.
+     * @return the number of keys in this hashtable.
+     */
+    public int size() {
+      return m_Count;
+    }
+    
+    /**
+     * Tests if this hashtable maps no keys to values.
+     * @return true if this hastable maps no keys to values.
+     */
+    public boolean isEmpty() {
+      return m_Count == 0;
+    }
+    
+    /**
+     * Clears this hashtable so that it contains no keys.
+     */
+    public void clear() {
+      TableEntry table[] = m_Table;
+      for (int index = table.length; --index >= 0; ) {
+	table[index] = null;
+      }
+      m_Count = 0;
+    }
+    
+    /**
+     * Rehashes the contents of the hashtable into a hashtable with a 
+     * larger capacity. This method is called automatically when the 
+     * number of keys in the hashtable exceeds this hashtable's capacity 
+     * and load factor. 
+     */
+    private void rehash() {
+      int oldCapacity = m_Table.length;
+      TableEntry [] oldTable = m_Table;    
+      int newCapacity = oldCapacity * 2 + 1;
+      TableEntry [] newTable = new TableEntry[newCapacity];
+      m_Threshold = (int)(newCapacity * m_LoadFactor);
+      m_Table = newTable;
+      TableEntry e, old;
+      for (int i = oldCapacity ; i-- > 0 ;) {
+	for (old = oldTable[i] ; old != null ; ) {
+	  e = old;
+	  old = old.next;
+	  int index = (e.hash & 0x7FFFFFFF) % newCapacity;
+	  e.next = newTable[index];
+	  newTable[index] = e;
+	}
+      }
+    }
+    
+    /**
+     * Returns the hash code of the specified double.
+     * @return the hash code of the specified double.
+     */
+    private int hashCode(double key) {
+      long bits = Double.doubleToLongBits(key);
+      return (int)(bits ^ (bits >> 32));
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.11 $");
+    }
+  } // CacheTable
+  
+  /**
+   * Hashtable collision list.
+   */
+  public class TableEntry
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 4057602386766259138L;
+
+    /** attribute value hash code */
+    public int hash;
+
+    /** attribute value */
+    public double key;
+
+    /** scale factor or stop parameter */
+    public double value;
+
+    /** transformation probability to missing value */
+    public double pmiss;
+
+    /** next table entry (separate chaining) */
+    public TableEntry next = null;
+
+    /** Constructor */
+    public TableEntry(int hash, double key, double value, 
+		      double pmiss, TableEntry next) {
+      this.hash  = hash;
+      this.key   = key;
+      this.value = value;
+      this.pmiss = pmiss;
+      this.next  = next;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.11 $");
+    }
+  }  // TableEntry
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.11 $");
+  }
+  
+} // Cache
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarConstants.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarConstants.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarConstants.java	(revision 29)
@@ -0,0 +1,63 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *    KStarConstants.java
+ *    Copyright (C) 1995 Univeristy of Waikato
+ *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
+ *
+ */
+
+
+package weka.classifiers.lazy.kstar;
+
+/*
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
+ * @version $Revision 1.0 $
+ */
+public interface KStarConstants {
+
+  /** Some usefull constants */
+  int    ON            = 1;
+  int    OFF           = 0;
+  int    NUM_RAND_COLS = 5;
+  double FLOOR         = 0.0;
+  double FLOOR1        = 0.1;
+  double INITIAL_STEP  = 0.05;
+  double LOG2          = 0.693147181;
+  double EPSILON       = 1.0e-5;
+
+  /** How close the root finder for numeric and nominal have to get */
+  int    ROOT_FINDER_MAX_ITER = 40;
+  double ROOT_FINDER_ACCURACY = 0.01;
+
+  /** Blend setting modes */
+  int B_SPHERE  = 1; /* Use sphere of influence */
+  int B_ENTROPY = 2; /* Use entropic blend setting */
+
+  /** Missing value handling mode */
+
+  /* Ignore the instance with the missing value */
+  int M_DELETE  = 1; 
+  /* Treat missing values as maximally different */
+  int M_MAXDIFF = 2; 
+  /* Normilize over the attributes */
+  int M_NORMAL  = 3; 
+  /* Average column entropy curves */
+  int M_AVERAGE = 4; 
+  
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNominalAttribute.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNominalAttribute.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNominalAttribute.java	(revision 29)
@@ -0,0 +1,617 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *    KStarNominalAttribute.java
+ *    Copyright (C) 1995 Univeristy of Waikato
+ *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
+ *
+ */
+
+
+package weka.classifiers.lazy.kstar;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * A custom class which provides the environment for computing the
+ * transformation probability of a specified test instance nominal
+ * attribute to a specified train instance nominal attribute.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
+ * @version $Revision 1.0 $
+ */
+public class KStarNominalAttribute
+  implements KStarConstants, RevisionHandler {
+  
+  /** The training instances used for classification. */
+  protected Instances m_TrainSet;
+
+  /** The test instance */
+  protected Instance m_Test;
+
+  /** The train instance */
+  protected Instance m_Train;
+
+  /** The index of the nominal attribute in the test and train instances */
+  protected int m_AttrIndex;
+
+  /** The stop parameter */
+  protected double m_Stop = 1.0;
+
+  /** Probability of test attribute transforming into train attribute 
+      with missing value */
+  protected double m_MissingProb = 1.0;
+
+  /** Average probability of test attribute transforming into train 
+      attribute */
+  protected double m_AverageProb = 1.0;
+
+  /** Smallest probability of test attribute transforming into 
+      train attribute */
+  protected double m_SmallestProb = 1.0;
+
+  /** Number of trai instances with no missing attribute values */
+  protected int m_TotalCount;
+
+  /** Distribution of the attribute value in the train dataset */
+  protected int [] m_Distribution;
+
+  /** Set of colomns: each colomn representing a randomised version 
+      of the train dataset class colomn */
+  protected int [][] m_RandClassCols;
+
+  /** A cache for storing attribute values and their corresponding 
+      stop parameters */
+  protected KStarCache m_Cache;
+
+  // KStar Global settings
+
+  /** The number of instances in the dataset */
+  protected int m_NumInstances;
+
+  /** The number of class values */
+  protected int m_NumClasses;
+
+  /** The number of attributes */
+  protected int m_NumAttributes;
+
+  /** The class attribute type */
+  protected int m_ClassType;
+
+  /** missing value treatment */
+  protected int m_MissingMode = M_AVERAGE;
+
+  /** B_SPHERE = use specified blend, B_ENTROPY = entropic blend setting */
+  protected int m_BlendMethod = B_SPHERE ;
+
+  /** default sphere of influence blend setting */
+  protected int m_BlendFactor = 20;
+  
+  /**
+   * Constructor
+   */
+  public KStarNominalAttribute(Instance test, Instance train, int attrIndex,
+			       Instances trainSet, int [][] randClassCol, 
+			       KStarCache cache)
+  {
+    m_Test = test;
+    m_Train = train;
+    m_AttrIndex = attrIndex;
+    m_TrainSet = trainSet;
+    m_RandClassCols = randClassCol;
+    m_Cache = cache;
+    init();
+  }
+
+  /**
+   * Initializes the m_Attributes of the class.
+   */
+  private void init() {
+    try {
+      m_NumInstances  = m_TrainSet.numInstances();
+      m_NumClasses    = m_TrainSet.numClasses();
+      m_NumAttributes = m_TrainSet.numAttributes();
+      m_ClassType     = m_TrainSet.classAttribute().type();
+    } catch(Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Calculates the probability of the indexed nominal attribute of the test
+   * instance transforming into the indexed nominal attribute of the training 
+   * instance.
+   *
+   * @return the value of the transformation probability.
+   */
+  public double transProb() {
+    String debug = "(KStarNominalAttribute.transProb) ";
+    double transProb = 0.0;
+    // check if the attribute value has been encountred before
+    // in which case it should be in the nominal cache
+    if (m_Cache.containsKey(m_Test.value(m_AttrIndex))) {
+      KStarCache.TableEntry te = 
+	m_Cache.getCacheValues(m_Test.value(m_AttrIndex));
+      m_Stop = te.value;
+      m_MissingProb = te.pmiss;
+    }
+    else {
+      generateAttrDistribution();
+      // we have to compute the parameters
+      if (m_BlendMethod == B_ENTROPY) {
+	m_Stop = stopProbUsingEntropy();
+      }
+      else { // default is B_SPHERE
+	m_Stop = stopProbUsingBlend();
+      }
+      // store the values in cache
+      m_Cache.store( m_Test.value(m_AttrIndex), m_Stop, m_MissingProb );
+    }
+    // we've got our m_Stop, then what?
+    if (m_Train.isMissing(m_AttrIndex)) {
+      transProb = m_MissingProb;
+    }
+    else {
+      try {
+	transProb = (1.0 - m_Stop) / m_Test.attribute(m_AttrIndex).numValues();
+	if ( (int)m_Test.value(m_AttrIndex) == 
+	     (int)m_Train.value(m_AttrIndex) )
+	  {
+	    transProb += m_Stop;
+	  }
+      } catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+    return transProb;
+  }
+  
+  /**
+   * Calculates the "stop parameter" for this attribute using
+   * the entropy method: the value is computed using a root finder
+   * algorithm. The method takes advantage of the calculation to
+   * compute the smallest and average transformation probabilities
+   * once the stop factor is obtained. It also sets the transformation
+   * probability to an attribute with a missing value.
+   *
+   * @return the value of the stop parameter.
+   *
+   */
+  private double stopProbUsingEntropy() {
+    String debug = "(KStarNominalAttribute.stopProbUsingEntropy)";
+    if ( m_ClassType != Attribute.NOMINAL ) {
+      System.err.println("Error: "+debug+" attribute class must be nominal!");
+      System.exit(1);
+    }
+    int itcount = 0;
+    double stopProb;
+    double lower, upper, pstop;
+    double bestminprob = 0.0, bestpsum = 0.0;
+    double bestdiff = 0.0, bestpstop = 0.0;
+    double currentdiff, lastdiff, stepsize, delta;
+    
+    KStarWrapper botvals = new KStarWrapper();
+    KStarWrapper upvals = new KStarWrapper();
+    KStarWrapper vals = new KStarWrapper();
+
+    // Initial values for root finder
+    lower = 0.0 + ROOT_FINDER_ACCURACY/2.0;
+    upper = 1.0 - ROOT_FINDER_ACCURACY/2.0;
+    
+    // Find (approx) entropy ranges
+    calculateEntropy(upper, upvals);
+    calculateEntropy(lower, botvals);
+    
+    if (upvals.avgProb == 0) {
+      // When there are no training instances with the test value:
+      // doesn't matter what exact value we use for pstop, just acts as
+      // a constant scale factor in this case.
+      calculateEntropy(lower, vals);
+    }
+    else
+      {
+	// Optimise the scale factor
+	if ( (upvals.randEntropy - upvals.actEntropy < 
+	      botvals.randEntropy - botvals.actEntropy) &&
+	     (botvals.randEntropy - botvals.actEntropy > FLOOR) )
+	  {
+	    bestpstop = pstop = lower;
+	    stepsize = INITIAL_STEP;
+	    bestminprob = botvals.minProb;
+	    bestpsum = botvals.avgProb;
+	  }
+	else {
+	  bestpstop = pstop = upper;
+	  stepsize = -INITIAL_STEP;
+	  bestminprob = upvals.minProb;
+	  bestpsum = upvals.avgProb;
+	}
+	bestdiff = currentdiff = FLOOR;
+	itcount = 0;
+	/* Enter the root finder */
+	while (true)
+	  {
+	    itcount++;	
+	    lastdiff = currentdiff;
+	    pstop += stepsize;
+	    if (pstop <= lower) {
+	      pstop = lower;
+	      currentdiff = 0.0;
+	      delta = -1.0;
+	    }
+	    else if (pstop >= upper) {
+	      pstop = upper;
+	      currentdiff = 0.0;
+	      delta = -1.0;
+	    }
+	    else {
+	      calculateEntropy(pstop, vals);
+	      currentdiff = vals.randEntropy - vals.actEntropy;
+
+	      if (currentdiff < FLOOR) {
+		currentdiff = FLOOR;
+		if ((Math.abs(stepsize) < INITIAL_STEP) && 
+		    (bestdiff == FLOOR)) {
+		  bestpstop = lower;
+		  bestminprob = botvals.minProb;
+		  bestpsum = botvals.avgProb;
+		  break;
+		}
+	      }
+	      delta = currentdiff - lastdiff;
+	    }
+	    if (currentdiff > bestdiff) {
+	      bestdiff = currentdiff;
+	      bestpstop = pstop;
+	      bestminprob = vals.minProb;
+	      bestpsum = vals.avgProb;
+	    }
+	    if (delta < 0) {
+	      if (Math.abs(stepsize) < ROOT_FINDER_ACCURACY) {
+		break;
+	      }
+	      else {
+		stepsize /= -2.0;
+	      }
+	    }
+	    if (itcount > ROOT_FINDER_MAX_ITER) {
+	      break;
+	    }
+	  }
+      }
+    
+    m_SmallestProb = bestminprob;
+    m_AverageProb = bestpsum;
+    // Set the probability of transforming to a missing value
+    switch ( m_MissingMode )
+      {
+      case M_DELETE:
+	m_MissingProb = 0.0;
+	break;
+      case M_NORMAL:
+	m_MissingProb = 1.0;
+	break;
+      case M_MAXDIFF:
+	m_MissingProb = m_SmallestProb;
+	break;
+      case M_AVERAGE:
+	m_MissingProb = m_AverageProb;
+	break;
+      }
+
+    if ( Math.abs(bestpsum - (double)m_TotalCount) < EPSILON) { 
+      // No difference in the values
+      stopProb = 1.0;
+    }
+    else {
+      stopProb = bestpstop;
+    }
+    return stopProb;
+  }
+
+  /**
+   * Calculates the entropy of the actual class prediction
+   * and the entropy for random class prediction. It also
+   * calculates the smallest and average transformation probabilities.
+   *
+   * @param stop the stop parameter
+   * @param params the object wrapper for the parameters:
+   * actual entropy, random entropy, average probability and smallest 
+   * probability.
+   * @return the values are returned in the object "params".
+   *
+   */
+  private void calculateEntropy( double stop, KStarWrapper params) {
+    String debug = "(KStarNominalAttribute.calculateEntropy)";
+    int i,j,k;
+    Instance train;
+    double actent = 0.0, randent=0.0;
+    double pstar, tprob, psum=0.0, minprob=1.0;
+    double actClassProb, randClassProb;
+    double [][] pseudoClassProb = new double[NUM_RAND_COLS+1][m_NumClasses];
+    // init ...
+    for(j = 0; j <= NUM_RAND_COLS; j++) {
+      for(i = 0; i < m_NumClasses; i++) {
+	pseudoClassProb[j][i] = 0.0;
+      }
+    }
+    for (i=0; i < m_NumInstances; i++) {
+      train = m_TrainSet.instance(i);
+      if (!train.isMissing(m_AttrIndex)) {
+	pstar = PStar(m_Test, train, m_AttrIndex, stop);
+	tprob = pstar / m_TotalCount;
+	if (pstar < minprob) {
+	  minprob = pstar;
+	}
+	psum += tprob;
+	// filter instances with same class value
+	for (k=0 ; k <= NUM_RAND_COLS ; k++) {
+	  // instance i is assigned a random class value in colomn k;
+	  // colomn k = NUM_RAND_COLS contains the original mapping: 
+	  // instance -> class vlaue
+	  pseudoClassProb[k][ m_RandClassCols[k][i] ] += tprob;
+	}
+      }
+    }
+    // compute the actual entropy using the class probs
+    // with the original class value mapping (colomn NUM_RAND_COLS)
+    for (j=m_NumClasses-1; j>=0; j--) {
+      actClassProb = pseudoClassProb[NUM_RAND_COLS][j] / psum;
+      if (actClassProb > 0) {
+    	actent -= actClassProb * Math.log(actClassProb) / LOG2;
+      }
+    }
+    // compute a random entropy using the pseudo class probs
+    // excluding the colomn NUM_RAND_COLS
+    for (k=0; k < NUM_RAND_COLS;k++) {
+      for (i = m_NumClasses-1; i >= 0; i--) {
+  	randClassProb = pseudoClassProb[k][i] / psum;
+  	if (randClassProb > 0) {
+  	  randent -= randClassProb * Math.log(randClassProb) / LOG2;
+	}
+      }
+    }
+    randent /= NUM_RAND_COLS;
+    // return the results ... Yuk !!!
+    params.actEntropy = actent;
+    params.randEntropy = randent;
+    params.avgProb = psum;
+    params.minProb = minprob;
+  }
+  
+  /**
+   * Calculates the "stop parameter" for this attribute using
+   * the blend method: the value is computed using a root finder
+   * algorithm. The method takes advantage of this calculation to
+   * compute the smallest and average transformation probabilities
+   * once the stop factor is obtained. It also sets the transformation
+   * probability to an attribute with a missing value.
+   *
+   * @return the value of the stop parameter.
+   *
+   */
+  private double stopProbUsingBlend() {
+    String debug = "(KStarNominalAttribute.stopProbUsingBlend) ";
+    int itcount = 0;
+    double stopProb, aimfor;
+    double lower, upper, tstop;
+
+    KStarWrapper botvals = new KStarWrapper();
+    KStarWrapper upvals = new KStarWrapper();
+    KStarWrapper vals = new KStarWrapper();
+
+    int testvalue = (int)m_Test.value(m_AttrIndex);
+    aimfor = (m_TotalCount - m_Distribution[testvalue]) * 
+      (double)m_BlendFactor / 100.0 + m_Distribution[testvalue];
+
+    // Initial values for root finder
+    tstop = 1.0 - (double)m_BlendFactor / 100.0;
+    lower = 0.0 + ROOT_FINDER_ACCURACY/2.0;
+    upper = 1.0 - ROOT_FINDER_ACCURACY/2.0;
+
+    // Find out function border values
+    calculateSphereSize(testvalue, lower, botvals);
+    botvals.sphere -= aimfor;
+    calculateSphereSize(testvalue, upper, upvals);
+    upvals.sphere -= aimfor;
+    
+    if (upvals.avgProb == 0) {
+      // When there are no training instances with the test value:
+      // doesn't matter what exact value we use for tstop, just acts as
+      // a constant scale factor in this case.
+      calculateSphereSize(testvalue, tstop, vals);
+    }
+    else if (upvals.sphere > 0) {
+      // Can't include aimfor instances, going for min possible
+      tstop = upper;
+      vals.avgProb = upvals.avgProb;
+    }
+    else {
+      // Enter the root finder
+      for (;;) {
+	itcount++;
+	calculateSphereSize(testvalue, tstop, vals);
+	vals.sphere -= aimfor;
+	if ( Math.abs(vals.sphere) <= ROOT_FINDER_ACCURACY ||
+	     itcount >= ROOT_FINDER_MAX_ITER )
+	  {
+	    break;
+	  }
+	if (vals.sphere > 0.0) {
+	  lower = tstop;
+	  tstop = (upper + lower) / 2.0;
+	}
+	else {
+	  upper = tstop;
+	  tstop = (upper + lower) / 2.0;
+	}
+      }
+    }
+
+    m_SmallestProb = vals.minProb;
+    m_AverageProb = vals.avgProb;
+    // Set the probability of transforming to a missing value
+    switch ( m_MissingMode )
+      {
+      case M_DELETE:
+	m_MissingProb = 0.0;
+	break;
+      case M_NORMAL:
+	m_MissingProb = 1.0;
+	break;
+      case M_MAXDIFF:
+	m_MissingProb = m_SmallestProb;
+	break;
+      case M_AVERAGE:
+	m_MissingProb = m_AverageProb;
+	break;
+      }
+    
+    if ( Math.abs(vals.avgProb - m_TotalCount) < EPSILON) { 
+      // No difference in the values
+      stopProb = 1.0;
+    }
+    else {
+      stopProb = tstop;
+    }
+    return stopProb;
+  }
+  
+  /**
+   * Calculates the size of the "sphere of influence" defined as:
+   * sphere = sum(P^2)/sum(P)^2
+   * P(i|j) = (1-tstop)*P(i) + ((i==j)?tstop:0).
+   * This method takes advantage of the calculation to compute the values of
+   * the "smallest" and "average" transformation probabilities when using
+   * the specified stop parameter.
+   *
+   * @param testValue the value of the test instance
+   * @param stop the stop parameter
+   * @param params a wrapper of the parameters to be computed:
+   * "sphere" the sphere size
+   * "avgprob" the average transformation probability
+   * "minProb" the smallest transformation probability
+   * @return the values are returned in "params" object.
+   *
+   */
+  private void calculateSphereSize(int testvalue, double stop, 
+				   KStarWrapper params) {
+    String debug = "(KStarNominalAttribute.calculateSphereSize) ";
+    int i, thiscount;
+    double tprob, tval = 0.0, t1 = 0.0;
+    double sphere, minprob = 1.0, transprob = 0.0;
+
+    for(i = 0; i < m_Distribution.length; i++) {
+      thiscount = m_Distribution[i];
+      if ( thiscount != 0 ) {
+	if ( testvalue == i ) {
+	  tprob = (stop + (1 - stop) / m_Distribution.length) / m_TotalCount;
+	  tval += tprob * thiscount;
+	  t1 += tprob * tprob * thiscount;
+	}
+	else {
+	  tprob = ((1 - stop) / m_Distribution.length) / m_TotalCount;
+	  tval += tprob * thiscount;
+	  t1 += tprob * tprob * thiscount;
+	}
+	if ( minprob > tprob * m_TotalCount ) {
+	  minprob = tprob * m_TotalCount;
+	}
+      }
+    }
+    transprob = tval;
+    sphere = (t1 == 0) ? 0 : ((tval * tval) / t1);
+    // return values ... Yck!!!
+    params.sphere = sphere;
+    params.avgProb = transprob;
+    params.minProb = minprob;
+  }
+  
+  /**
+   * Calculates the nominal probability function defined as:
+   * P(i|j) = (1-stop) * P(i) + ((i==j) ? stop : 0)
+   * In this case, it calculates the transformation probability of the
+   * indexed test attribute to the indexed train attribute.
+   *
+   * @param test the test instance
+   * @param train the train instance
+   * @param col the attribute index
+   * @return the value of the tranformation probability.
+   *
+   */
+  private double PStar(Instance test, Instance train, int col, double stop) {
+    String debug = "(KStarNominalAttribute.PStar) ";
+    double pstar;
+    int numvalues = 0;
+    try {
+      numvalues = test.attribute(col).numValues();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    if ( (int)test.value(col) == (int)train.value(col) ) {
+      pstar = stop + (1 - stop) / numvalues;
+    }
+    else {
+      pstar = (1 - stop) / numvalues;
+    }
+    return pstar;
+  }
+  
+  /**
+   * Calculates the distribution, in the dataset, of the indexed nominal
+   * attribute values. It also counts the actual number of training instances
+   * that contributed (those with non-missing values) to calculate the 
+   * distribution.
+   */
+  private void generateAttrDistribution() {
+    String debug = "(KStarNominalAttribute.generateAttrDistribution)";
+    m_Distribution = new int[ m_TrainSet.attribute(m_AttrIndex).numValues() ];
+    int i;
+    Instance train;
+    for (i=0; i < m_NumInstances; i++) {
+      train = m_TrainSet.instance(i);
+      if ( !train.isMissing(m_AttrIndex) ) {
+	m_TotalCount++;
+	m_Distribution[(int)train.value(m_AttrIndex)]++;
+      }
+    }
+  }
+
+  /**
+   * Sets the options.
+   *
+   */
+  public void setOptions(int missingmode, int blendmethod, int blendfactor) {
+    m_MissingMode = missingmode;
+    m_BlendMethod = blendmethod;
+    m_BlendFactor = blendfactor;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+} // class
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNumericAttribute.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNumericAttribute.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarNumericAttribute.java	(revision 29)
@@ -0,0 +1,645 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *    KStarNumericAttribute.java
+ *    Copyright (C) 1995 Univeristy of Waikato
+ *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
+ *
+ */
+
+package weka.classifiers.lazy.kstar;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * A custom class which provides the environment for computing the
+ * transformation probability of a specified test instance numeric
+ * attribute to a specified train instance numeric attribute.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
+ * @version $Revision 1.0 $
+ */
+public class KStarNumericAttribute
+  implements KStarConstants, RevisionHandler {
+
+  /** The training instances used for classification. */
+  protected Instances m_TrainSet;
+
+  /** The test instance */
+  protected Instance m_Test;
+
+  /** The train instance */
+  protected Instance m_Train;
+
+  /** The index of the attribute in the test and train instances */
+  protected int m_AttrIndex;
+
+  /** The scale parameter */
+  protected double m_Scale = 1.0;
+
+  /** Probability of test attribute transforming into train attribute 
+      with missing value */
+  protected double m_MissingProb = 1.0;
+
+  /** Average probability of test attribute transforming into train 
+      attribute */
+  protected double m_AverageProb = 1.0;
+
+  /** Smallest probability of test attribute transforming into train 
+      attribute */
+  protected double m_SmallestProb = 1.0;
+
+  /** The set of disctances from the test attribute to the set of train 
+      attributes */
+  protected double [] m_Distances;
+
+  /** Set of colomns: each colomn representing a randomised version of 
+      the train dataset class colomn */
+  protected int [][] m_RandClassCols;
+
+  /** The number of train instances with no missing attribute values */
+  protected int m_ActualCount = 0;
+
+  /** A cache for storing attribute values and their corresponding scale 
+      parameters */
+  protected KStarCache m_Cache;
+
+  /** The number of instances in the dataset */
+  protected int m_NumInstances;
+
+  /** The number of class values */
+  protected int m_NumClasses;
+
+  /** The number of attributes */
+  protected int m_NumAttributes;
+
+  /** The class attribute type */
+  protected int m_ClassType;
+
+  /** missing value treatment */
+  protected int m_MissingMode = M_AVERAGE;
+
+  /** 0 = use specified blend, 1 = entropic blend setting */
+  protected int m_BlendMethod = B_SPHERE ;
+
+  /** default sphere of influence blend setting */
+  protected int m_BlendFactor = 20;
+  
+  /**
+   * Constructor
+   */
+  public KStarNumericAttribute(Instance test, Instance train, int attrIndex,
+			       Instances trainSet, 
+			       int [][] randClassCols, 
+			       KStarCache cache)
+  {
+    m_Test      = test;
+    m_Train     = train;
+    m_AttrIndex = attrIndex;
+    m_TrainSet  = trainSet;
+    m_RandClassCols = randClassCols;
+    m_Cache = cache;
+    init();
+  }
+
+  /**
+   * Initializes the m_Attributes of the class.
+   */
+  private void init() {
+    try {
+      m_NumInstances  = m_TrainSet.numInstances();
+      m_NumClasses    = m_TrainSet.numClasses();
+      m_NumAttributes = m_TrainSet.numAttributes();
+      m_ClassType     = m_TrainSet.classAttribute().type();
+    } catch(Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Calculates the transformation probability of the attribute indexed
+   * "m_AttrIndex" in test instance "m_Test" to the same attribute in
+   * the train instance "m_Train".
+   *
+   * @return the probability value
+   */
+  public double transProb() {
+    String debug = "(KStarNumericAttribute.transProb) ";
+    double transProb, distance, scale;
+    // check if the attribute value has been encountred before
+    // in which case it should be in the numeric cache
+    if ( m_Cache.containsKey(m_Test.value(m_AttrIndex))) {
+      KStarCache.TableEntry te = 
+	m_Cache.getCacheValues( m_Test.value(m_AttrIndex) );
+      m_Scale = te.value;
+      m_MissingProb = te.pmiss;
+    }
+    else {
+      if (m_BlendMethod == B_ENTROPY) {
+	m_Scale = scaleFactorUsingEntropy();
+      }
+      else { // default is B_SPHERE
+	m_Scale = scaleFactorUsingBlend();
+      }
+      m_Cache.store( m_Test.value(m_AttrIndex), m_Scale, m_MissingProb );
+    }
+    // now what???
+    if (m_Train.isMissing(m_AttrIndex)) {
+      transProb = m_MissingProb;
+    }
+    else {
+      distance = 
+	Math.abs( m_Test.value(m_AttrIndex) - m_Train.value(m_AttrIndex) );
+      transProb = PStar( distance, m_Scale );
+    }
+    return transProb;
+  }
+  
+  /**
+   * Calculates the scale factor for the attribute indexed
+   * "m_AttrIndex" in test instance "m_Test" using a global
+   * blending factor (default value is 20%).
+   *
+   * @return the scale factor value
+   */
+  private double scaleFactorUsingBlend() {
+    String debug = "(KStarNumericAttribute.scaleFactorUsingBlend)";
+    int i, j, lowestcount = 0, count = 0;
+    double lowest = -1.0, nextlowest = -1.0;
+    double root, broot, up, bot;
+    double aimfor, min_val = 9e300, scale = 1.0;
+    double avgprob = 0.0, minprob = 0.0, min_pos = 0.0;
+
+    KStarWrapper botvals = new KStarWrapper();
+    KStarWrapper upvals = new KStarWrapper();
+    KStarWrapper vals = new KStarWrapper();
+
+    m_Distances = new double [m_NumInstances];
+
+    for (j=0; j<m_NumInstances; j++) {
+      if ( m_TrainSet.instance(j).isMissing(m_AttrIndex) ) {
+	// mark the train instance with a missing value by setting 
+	// the distance to -1.0
+	m_Distances[j] = -1.0;
+      }
+      else {
+	m_Distances[j] = Math.abs(m_TrainSet.instance(j).value(m_AttrIndex) - 
+				  m_Test.value(m_AttrIndex));
+	if ( (m_Distances[j]+1e-5) < nextlowest || nextlowest == -1.0 ) {
+	  if ( (m_Distances[j]+1e-5) < lowest || lowest == -1.0 ) {
+	    nextlowest = lowest;
+	    lowest = m_Distances[j];
+	    lowestcount = 1;
+	  }
+	  else if ( Math.abs(m_Distances[j]-lowest) < 1e-5 ) {
+	    // record the number training instances (number n0) at
+	    // the smallest distance from test instance
+	    lowestcount++;
+	  }
+	  else {
+	    nextlowest = m_Distances[j];
+	  }
+	}
+	// records the actual number of instances with no missing value
+	m_ActualCount++;
+      }
+    }
+    
+    if (nextlowest == -1 || lowest == -1) { // Data values are all the same
+      scale = 1.0;
+      m_SmallestProb = m_AverageProb = 1.0;
+      return scale;
+    }
+    else {
+      // starting point for root
+      root = 1.0 / (nextlowest - lowest);
+      i = 0;
+      // given the expression: n0 <= E(scale) <= N
+      // E(scale) =  (N - n0) * b + n0  with blending factor: 0 <= b <= 1
+      // aimfor = (N - n0) * b + n0
+      aimfor = (m_ActualCount - lowestcount) * 
+	(double)m_BlendFactor / 100.0 + lowestcount;
+      if (m_BlendFactor == 0) {
+	aimfor += 1.0;
+      }
+      // root is bracketed in interval [bot,up]
+      bot = 0.0 + ROOT_FINDER_ACCURACY / 2.0;
+      up = root * 16;     // This is bodgy
+      // E(bot)
+      calculateSphereSize(bot, botvals);
+      botvals.sphere -= aimfor;
+      // E(up)
+      calculateSphereSize(up, upvals);
+      upvals.sphere -= aimfor;
+      
+      if (botvals.sphere < 0) {    // Couldn't include that many 
+	                           // instances - going for max possible
+	min_pos = bot;
+	avgprob = botvals.avgProb;
+	minprob = botvals.minProb;
+      }
+      else if (upvals.sphere > 0) { // Couldn't include that few, 
+	                            // going for min possible
+	min_pos = up;
+	avgprob = upvals.avgProb;
+	minprob = upvals.minProb;
+      }
+      else {
+	// Root finding Algorithm starts here !
+	for (;;) {
+	  calculateSphereSize(root, vals);
+	  vals.sphere -= aimfor;
+	  if ( Math.abs(vals.sphere) < min_val ) {
+	    min_val = Math.abs(vals.sphere);
+	    min_pos = root;
+	    avgprob = vals.avgProb;
+	    minprob = vals.minProb;
+	  }
+	  if ( Math.abs(vals.sphere) <= ROOT_FINDER_ACCURACY ) {
+	    break;        // converged to a solution, done!
+	  }
+	  if (vals.sphere > 0.0) {
+	    broot = (root + up) / 2.0;
+	    bot = root;
+	    root = broot;
+	  }
+	  else {
+	    broot = (root + bot) / 2.0;
+	    up = root;
+	    root = broot;
+	  }
+	  i++;
+	  if (i > ROOT_FINDER_MAX_ITER) {
+	    //	    System.err.println("Warning: "+debug+" 
+	    // ROOT_FINDER_MAX_ITER exceeded");
+	    root = min_pos;
+	    break;
+	  }
+	}
+      }
+
+      m_SmallestProb = minprob;
+      m_AverageProb = avgprob;
+      // Set the probability of transforming to a missing value
+      switch ( m_MissingMode )
+	{
+	case M_DELETE:
+	  m_MissingProb = 0.0;
+	  break;
+	case M_NORMAL:
+	  m_MissingProb = 1.0;
+	  break;
+	case M_MAXDIFF:
+	  m_MissingProb = m_SmallestProb;
+	  break;
+	case M_AVERAGE:
+	  m_MissingProb = m_AverageProb;
+	  break;
+	}
+      // set the scale factor value
+      scale = min_pos;
+      return scale;
+    }
+  }
+  
+  /**
+   * Calculates the size of the "sphere of influence" defined as:
+   * sphere = sum(P)^2/sum(P^2) where
+   * P(i) = root*exp(-2*i*root).
+   * Since there are n different training instances we multiply P(i) by 1/n.
+   */
+  private void calculateSphereSize(double scale, KStarWrapper params) {
+    String debug = "(KStarNumericAttribute.calculateSphereSize)";
+    int i;
+    double sphereSize, minprob = 1.0;
+    double pstar;                // P*(b|a)
+    double pstarSum = 0.0;       // sum(P*)
+    double pstarSquareSum = 0.0; // sum(P*^2)
+    double inc;
+    for (i = 0; i < m_NumInstances; i++) {
+      if (m_Distances[i] < 0) {
+	// instance with missing value
+	continue;
+      }
+      else {
+	pstar = PStar( m_Distances[i], scale );
+	if (minprob > pstar) {
+	  minprob = pstar;
+	}
+	inc = pstar / m_ActualCount;
+	pstarSum += inc;
+	pstarSquareSum += inc * inc;
+      }
+    }
+    sphereSize = (pstarSquareSum == 0 ? 0 
+		  : pstarSum * pstarSum / pstarSquareSum);
+    // return the values
+    params.sphere = sphereSize;
+    params.avgProb = pstarSum;
+    params.minProb = minprob;
+  }
+  
+  /**
+   * Calculates the scale factor using entropy.
+   *
+   * @return the scale factor value
+   */
+  private double scaleFactorUsingEntropy() {
+    String debug = "(KStarNumericAttribute.scaleFactorUsingEntropy)";
+    if ( m_ClassType != Attribute.NOMINAL ) {
+      System.err.println("Error: "+debug+" attribute class must be nominal!");
+      System.exit(1);
+    }
+    int i,j, lowestcount = 0, count, itcount;
+    double lowest = -1.0, nextlowest = -1.0;
+    double root, up, bot, stepsize, delta;
+    double actentropy = 0.0, randentropy = 0.0, actscale, randscale;
+    double minrand = 0.0, minact = 0.0, maxrand = 0.0, maxact = 0.0;
+    double bestdiff, bestroot, currentdiff, lastdiff;
+    double bestpsum, bestminprob, scale = 1.0;
+
+    KStarWrapper botvals = new KStarWrapper();
+    KStarWrapper upvals = new KStarWrapper();
+    KStarWrapper vals = new KStarWrapper();
+
+    m_Distances = new double [m_NumInstances];
+
+    for (j=0; j<m_NumInstances; j++) {
+      if ( m_TrainSet.instance(j).isMissing(m_AttrIndex) ) {
+	// mark the train instance with a missing value by setting 
+	// the distance to -1.0
+	m_Distances[j] = -1.0;
+      }
+      else {
+	m_Distances[j] = Math.abs(m_TrainSet.instance(j).value(m_AttrIndex) - 
+				  m_Test.value(m_AttrIndex));
+	
+	if ( (m_Distances[j]+1e-5) < nextlowest || nextlowest == -1.0 ) {
+	  if ( (m_Distances[j]+1e-5) < lowest || lowest == -1.0 ) {
+	    nextlowest = lowest;
+	    lowest = m_Distances[j];
+	    lowestcount = 1;
+	  }
+	  else if ( Math.abs(m_Distances[j]-lowest) < 1e-5 ) {
+	    // record the number training instances (number n0) at
+	    // the smallest distance from test instance
+	    lowestcount++;
+	  }
+	  else {
+	    nextlowest = m_Distances[j];
+	  }
+	}
+	// records the actual number of instances with no missing value
+	m_ActualCount++;
+      }
+    } // for
+    
+    if (nextlowest == -1 || lowest == -1) { // Data values are all the same
+      scale = 1.0;
+      m_SmallestProb = m_AverageProb = 1.0;
+      return scale;
+    }
+    else {
+      // starting point for root
+      root = 1.0 / (nextlowest - lowest);
+      // root is bracketed in interval [bot,up]
+      bot = 0.0 + ROOT_FINDER_ACCURACY / 2;  
+      up = root * 8; // This is bodgy
+      // Find (approx) entropy ranges
+      calculateEntropy(up, upvals);
+      calculateEntropy(bot, botvals);
+      actscale = botvals.actEntropy - upvals.actEntropy;
+      randscale = botvals.randEntropy - upvals.randEntropy;
+      // Optimise the scale factor
+      bestroot = root = bot;
+      bestdiff = currentdiff = FLOOR1;
+      bestpsum = botvals.avgProb;
+      bestminprob = botvals.minProb;
+      stepsize = (up - bot) / 20.0;
+      itcount = 0;
+      // Root finding algorithm starts here!
+      while (true)
+	{
+	  itcount++;
+	  lastdiff = currentdiff;
+	  root += Math.log(root + 1.0) * stepsize;
+	  if (root <= bot) {
+	    root = bot;
+	    currentdiff = 0.0;
+	    delta = -1.0;
+	  }
+	  else if (root >= up) {
+	    root = up;
+	    currentdiff = 0.0;
+	    delta = -1.0;
+	  }
+	  else {
+	    calculateEntropy(root, vals);
+	    // Normalise entropies
+	    vals.randEntropy = (vals.randEntropy - upvals.randEntropy) / 
+	      randscale;
+	    vals.actEntropy = (vals.actEntropy - upvals.actEntropy) / 
+	      randscale;
+	    currentdiff = vals.randEntropy - vals.actEntropy;
+
+	    if (currentdiff < FLOOR1) {
+	      currentdiff = FLOOR1;
+	      if (stepsize < 0) { 
+		// If we've hit the end and turned around we can't 
+		// have found any peaks
+		bestdiff = currentdiff;
+		bestroot = bot;
+		bestpsum = botvals.avgProb;
+		bestminprob = botvals.minProb;
+		break;
+	      }
+	    }
+	    delta = currentdiff - lastdiff;
+	  }
+	  if (currentdiff > bestdiff) {
+	    bestdiff = currentdiff;
+	    bestroot = root;
+	    bestminprob = vals.minProb;
+	    bestpsum = vals.avgProb;
+	  }
+	  if (delta < 0) {
+	    if (Math.abs(stepsize) < ROOT_FINDER_ACCURACY) {
+	      break;
+	    }
+	    else {
+	      stepsize /= -4.0;
+	    }
+	  }
+	  if (itcount > ROOT_FINDER_MAX_ITER) {
+	    //  System.err.println("Warning: "+debug+" ROOT_FINDER_MAX_ITER 
+	    // exceeded");
+	    break;
+	  }
+	} // while
+
+      m_SmallestProb = bestminprob;
+      m_AverageProb = bestpsum;
+      // Set the probability of transforming to a missing value
+      switch ( m_MissingMode )
+	{
+	case M_DELETE:
+	  m_MissingProb = 0.0;
+	  break;
+	case M_NORMAL:
+	  m_MissingProb = 1.0;
+	  break;
+	case M_MAXDIFF:
+	  m_MissingProb = m_SmallestProb;
+	  break;
+	case M_AVERAGE:
+	  m_MissingProb = m_AverageProb;
+	  break;
+	}
+      // set scale factor
+      scale = bestroot;
+    } // else
+    return scale;
+  }
+
+  /**
+   * Calculates several parameters aside from the entropy: for a specified
+   * scale factor, calculates the actual entropy, a random entropy using a
+   * randomized set of class value colomns, and records the average and
+   * smallest probabilities (for use in missing value case).
+   */
+  private void calculateEntropy(double scale, KStarWrapper params) {    
+    String debug = "(KStarNumericAttribute.calculateEntropy)";
+    int i,j,k;
+    double actent = 0.0, randent = 0.0;
+    double pstar, tprob, avgprob = 0.0, minprob = 1.0;
+    double actClassProb, randClassProb;
+    double [][] pseudoClassProbs = new double[NUM_RAND_COLS+1][m_NumClasses];
+    // init
+    for(j = 0; j <= NUM_RAND_COLS; j++) {
+      for(i = 0; i < m_NumClasses; i++) {
+	pseudoClassProbs[j][i] = 0.0;
+      }
+    }
+    for (i=0; i < m_NumInstances; i++) {
+      if (m_Distances[i] < 0) {
+	// train instance has mising value
+	continue;
+      }
+      else {
+	pstar = PStar(m_Distances[i], scale);
+	tprob = pstar / m_ActualCount;
+	avgprob += tprob;
+	if (pstar < minprob) {
+	  minprob = pstar;
+	}
+	// filter instances with same class value
+	for (k=0; k <= NUM_RAND_COLS; k++) {
+	  // instance i is assigned a random class value in colomn k;
+	  // colomn k = NUM_RAND_COLS contains the original mapping: 
+	  // instance -> class vlaue
+	  pseudoClassProbs[k][ m_RandClassCols[k][i] ] += tprob;
+	}
+      }
+    }
+    // compute the actual entropy using the class probabilities
+    // with the original class value mapping (colomn NUM_RAND_COLS)
+    for (j = m_NumClasses-1; j >= 0; j--) {
+      actClassProb = pseudoClassProbs[NUM_RAND_COLS][j] / avgprob;
+      if (actClassProb > 0) {
+    	actent -= actClassProb * Math.log(actClassProb) / LOG2;
+      }
+    }
+    // compute a random entropy using the pseudo class probs
+    // excluding the colomn NUM_RAND_COLS
+    for (k=0; k < NUM_RAND_COLS; k++) {
+      for (i = m_NumClasses-1; i >= 0; i--) {
+  	randClassProb = pseudoClassProbs[k][i] / avgprob;
+  	if (randClassProb > 0) {
+  	  randent -= randClassProb * Math.log(randClassProb) / LOG2;
+	}
+      }
+    }
+    randent /= NUM_RAND_COLS;
+    // return the values
+    params.actEntropy = actent;
+    params.randEntropy = randent;
+    params.avgProb = avgprob;
+    params.minProb = minprob;
+  }
+
+  /**
+   * Calculates the value of P for a given value x using the expression:
+   * P(x) = scale * exp( -2.0 * x * scale )
+   *
+   * @param x input value
+   * @param scale the scale factor
+   * @return output of the function P(x)
+   */          
+  private double PStar(double x, double scale) {
+    return scale * Math.exp( -2.0 * x * scale );
+  }
+
+  /**
+   * Set options.
+   * @param missingmode the missing value treatment to use
+   * @param blendmethod the blending method to use
+   * @param blendfactor the level of blending to use
+   */
+  public void setOptions(int missingmode, int blendmethod, int blendfactor) {
+    m_MissingMode = missingmode;
+    m_BlendMethod = blendmethod;
+    m_BlendFactor = blendfactor;
+  }
+
+  /**
+   * Set the missing value mode.
+   * @param mode the type of missing value treatment to use
+   */
+  public void setMissingMode(int mode) {
+    m_MissingMode = mode;
+  }
+
+  /**
+   * Set the blending method
+   * @param method the blending method to use
+   */
+  public void setBlendMethod(int method) {
+    m_BlendMethod = method;
+  }
+
+  /**
+   * Set the blending factor
+   * @param factor the level of blending to use
+   */
+  public void setBlendFactor(int factor) {
+    m_BlendFactor = factor;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+} // class
Index: branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarWrapper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarWrapper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/lazy/kstar/KStarWrapper.java	(revision 29)
@@ -0,0 +1,61 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *    KStarWrapper.java
+ *    Copyright (C) 1995 Univeristy of Waikato
+ *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
+ *
+ */
+
+package weka.classifiers.lazy.kstar;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/*
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
+ * @version $Revision 1.0 $
+ */
+public class KStarWrapper
+  implements RevisionHandler {
+
+  /** used/reused to hold the sphere size */
+  public double sphere = 0.0;
+
+  /** used/reused to hold the actual entropy */
+  public double actEntropy = 0.0;
+
+  /** used/reused to hold the random entropy */
+  public double randEntropy = 0.0;
+
+  /** used/reused to hold the average transformation probability */
+  public double avgProb = 0.0;
+
+  /** used/reused to hold the smallest transformation probability */
+  public double minProb = 0.0;
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/AdaBoostM1.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/AdaBoostM1.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/AdaBoostM1.java	(revision 29)
@@ -0,0 +1,782 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AdaBoostM1.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.classifiers.Sourcable;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for boosting a nominal class classifier using the Adaboost M1 method. Only nominal class problems can be tackled. Often dramatically improves performance, but sometimes overfits.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Yoav Freund, Robert E. Schapire: Experiments with a new boosting algorithm. In: Thirteenth International Conference on Machine Learning, San Francisco, 148-156, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Freund1996,
+ *    address = {San Francisco},
+ *    author = {Yoav Freund and Robert E. Schapire},
+ *    booktitle = {Thirteenth International Conference on Machine Learning},
+ *    pages = {148-156},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Experiments with a new boosting algorithm},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  Percentage of weight mass to base training on.
+ *  (default 100, reduce to around 90 speed up)</pre>
+ * 
+ * <pre> -Q
+ *  Use resampling for boosting.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class AdaBoostM1 
+  extends RandomizableIteratedSingleClassifierEnhancer 
+  implements WeightedInstancesHandler, Sourcable, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -7378107808933117974L;
+  
+  /** Max num iterations tried to find classifier with non-zero error. */ 
+  private static int MAX_NUM_RESAMPLING_ITERATIONS = 10;
+  
+  /** Array for storing the weights for the votes. */
+  protected double [] m_Betas;
+
+  /** The number of successfully generated base classifiers. */
+  protected int m_NumIterationsPerformed;
+
+  /** Weight Threshold. The percentage of weight mass used in training */
+  protected int m_WeightThreshold = 100;
+
+  /** Use boosting with reweighting? */
+  protected boolean m_UseResampling;
+
+  /** The number of classes */
+  protected int m_NumClasses;
+  
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+    
+  /**
+   * Constructor.
+   */
+  public AdaBoostM1() {
+    
+    m_Classifier = new weka.classifiers.trees.DecisionStump();
+  }
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for boosting a nominal class classifier using the Adaboost "
+      + "M1 method. Only nominal class problems can be tackled. Often "
+      + "dramatically improves performance, but sometimes overfits.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Yoav Freund and Robert E. Schapire");
+    result.setValue(Field.TITLE, "Experiments with a new boosting algorithm");
+    result.setValue(Field.BOOKTITLE, "Thirteenth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "148-156");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.ADDRESS, "San Francisco");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+  /**
+   * Select only instances with weights that contribute to 
+   * the specified quantile of the weight distribution
+   *
+   * @param data the input instances
+   * @param quantile the specified quantile eg 0.9 to select 
+   * 90% of the weight mass
+   * @return the selected instances
+   */
+  protected Instances selectWeightQuantile(Instances data, double quantile) { 
+
+    int numInstances = data.numInstances();
+    Instances trainData = new Instances(data, numInstances);
+    double [] weights = new double [numInstances];
+
+    double sumOfWeights = 0;
+    for(int i = 0; i < numInstances; i++) {
+      weights[i] = data.instance(i).weight();
+      sumOfWeights += weights[i];
+    }
+    double weightMassToSelect = sumOfWeights * quantile;
+    int [] sortedIndices = Utils.sort(weights);
+
+    // Select the instances
+    sumOfWeights = 0;
+    for(int i = numInstances - 1; i >= 0; i--) {
+      Instance instance = (Instance)data.instance(sortedIndices[i]).copy();
+      trainData.add(instance);
+      sumOfWeights += weights[sortedIndices[i]];
+      if ((sumOfWeights > weightMassToSelect) && 
+	  (i > 0) && 
+	  (weights[sortedIndices[i]] != weights[sortedIndices[i - 1]])) {
+	break;
+      }
+    }
+    if (m_Debug) {
+      System.err.println("Selected " + trainData.numInstances()
+			 + " out of " + numInstances);
+    }
+    return trainData;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+	"\tPercentage of weight mass to base training on.\n"
+	+"\t(default 100, reduce to around 90 speed up)",
+	"P", 1, "-P <num>"));
+    
+    newVector.addElement(new Option(
+	"\tUse resampling for boosting.",
+	"Q", 0, "-Q"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  Percentage of weight mass to base training on.
+   *  (default 100, reduce to around 90 speed up)</pre>
+   * 
+   * <pre> -Q
+   *  Use resampling for boosting.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String thresholdString = Utils.getOption('P', options);
+    if (thresholdString.length() != 0) {
+      setWeightThreshold(Integer.parseInt(thresholdString));
+    } else {
+      setWeightThreshold(100);
+    }
+      
+    setUseResampling(Utils.getFlag('Q', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+
+    if (getUseResampling())
+      result.add("-Q");
+
+    result.add("-P");
+    result.add("" + getWeightThreshold());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightThresholdTipText() {
+    return "Weight threshold for weight pruning.";
+  }
+
+  /**
+   * Set weight threshold
+   *
+   * @param threshold the percentage of weight mass used for training
+   */
+  public void setWeightThreshold(int threshold) {
+
+    m_WeightThreshold = threshold;
+  }
+
+  /**
+   * Get the degree of weight thresholding
+   *
+   * @return the percentage of weight mass used for training
+   */
+  public int getWeightThreshold() {
+
+    return m_WeightThreshold;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useResamplingTipText() {
+    return "Whether resampling is used instead of reweighting.";
+  }
+
+  /**
+   * Set resampling mode
+   *
+   * @param r true if resampling should be done
+   */
+  public void setUseResampling(boolean r) {
+
+    m_UseResampling = r;
+  }
+
+  /**
+   * Get whether resampling is turned on
+   *
+   * @return true if resampling output is on
+   */
+  public boolean getUseResampling() {
+
+    return m_UseResampling;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    if (super.getCapabilities().handles(Capability.NOMINAL_CLASS))
+      result.enable(Capability.NOMINAL_CLASS);
+    if (super.getCapabilities().handles(Capability.BINARY_CLASS))
+      result.enable(Capability.BINARY_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Boosting method.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+
+  public void buildClassifier(Instances data) throws Exception {
+
+    super.buildClassifier(data);
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (data.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(data);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_NumClasses = data.numClasses();
+    if ((!m_UseResampling) && 
+	(m_Classifier instanceof WeightedInstancesHandler)) {
+      buildClassifierWithWeights(data);
+    } else {
+      buildClassifierUsingResampling(data);
+    }
+  }
+
+  /**
+   * Boosting method. Boosts using resampling
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  protected void buildClassifierUsingResampling(Instances data) 
+    throws Exception {
+
+    Instances trainData, sample, training;
+    double epsilon, reweight, sumProbs;
+    Evaluation evaluation;
+    int numInstances = data.numInstances();
+    Random randomInstance = new Random(m_Seed);
+    int resamplingIterations = 0;
+
+    // Initialize data
+    m_Betas = new double [m_Classifiers.length];
+    m_NumIterationsPerformed = 0;
+    // Create a copy of the data so that when the weights are diddled
+    // with it doesn't mess up the weights for anyone else
+    training = new Instances(data, 0, numInstances);
+    sumProbs = training.sumOfWeights();
+    for (int i = 0; i < training.numInstances(); i++) {
+      training.instance(i).setWeight(training.instance(i).
+				      weight() / sumProbs);
+    }
+    
+    // Do boostrap iterations
+    for (m_NumIterationsPerformed = 0; m_NumIterationsPerformed < m_Classifiers.length; 
+	 m_NumIterationsPerformed++) {
+      if (m_Debug) {
+	System.err.println("Training classifier " + (m_NumIterationsPerformed + 1));
+      }
+
+      // Select instances to train the classifier on
+      if (m_WeightThreshold < 100) {
+	trainData = selectWeightQuantile(training, 
+					 (double)m_WeightThreshold / 100);
+      } else {
+	trainData = new Instances(training);
+      }
+      
+      // Resample
+      resamplingIterations = 0;
+      double[] weights = new double[trainData.numInstances()];
+      for (int i = 0; i < weights.length; i++) {
+	weights[i] = trainData.instance(i).weight();
+      }
+      do {
+	sample = trainData.resampleWithWeights(randomInstance, weights);
+
+	// Build and evaluate classifier
+	m_Classifiers[m_NumIterationsPerformed].buildClassifier(sample);
+	evaluation = new Evaluation(data);
+	evaluation.evaluateModel(m_Classifiers[m_NumIterationsPerformed], 
+				 training);
+	epsilon = evaluation.errorRate();
+	resamplingIterations++;
+      } while (Utils.eq(epsilon, 0) && 
+	      (resamplingIterations < MAX_NUM_RESAMPLING_ITERATIONS));
+      	
+      // Stop if error too big or 0
+      if (Utils.grOrEq(epsilon, 0.5) || Utils.eq(epsilon, 0)) {
+	if (m_NumIterationsPerformed == 0) {
+	  m_NumIterationsPerformed = 1; // If we're the first we have to to use it
+	}
+	break;
+      }
+      
+      // Determine the weight to assign to this model
+      m_Betas[m_NumIterationsPerformed] = Math.log((1 - epsilon) / epsilon);
+      reweight = (1 - epsilon) / epsilon;
+      if (m_Debug) {
+	System.err.println("\terror rate = " + epsilon
+			   +"  beta = " + m_Betas[m_NumIterationsPerformed]);
+      }
+ 
+      // Update instance weights
+      setWeights(training, reweight);
+    }
+  }
+
+  /**
+   * Sets the weights for the next iteration.
+   * 
+   * @param training the training instances
+   * @param reweight the reweighting factor
+   * @throws Exception if something goes wrong
+   */
+  protected void setWeights(Instances training, double reweight) 
+    throws Exception {
+
+    double oldSumOfWeights, newSumOfWeights;
+
+    oldSumOfWeights = training.sumOfWeights();
+    Enumeration enu = training.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      Instance instance = (Instance) enu.nextElement();
+      if (!Utils.eq(m_Classifiers[m_NumIterationsPerformed].classifyInstance(instance), 
+		    instance.classValue()))
+	instance.setWeight(instance.weight() * reweight);
+    }
+    
+    // Renormalize weights
+    newSumOfWeights = training.sumOfWeights();
+    enu = training.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      Instance instance = (Instance) enu.nextElement();
+      instance.setWeight(instance.weight() * oldSumOfWeights 
+			 / newSumOfWeights);
+    }
+  }
+
+  /**
+   * Boosting method. Boosts any classifier that can handle weighted
+   * instances.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  protected void buildClassifierWithWeights(Instances data) 
+    throws Exception {
+
+    Instances trainData, training;
+    double epsilon, reweight;
+    Evaluation evaluation;
+    int numInstances = data.numInstances();
+    Random randomInstance = new Random(m_Seed);
+
+    // Initialize data
+    m_Betas = new double [m_Classifiers.length];
+    m_NumIterationsPerformed = 0;
+
+    // Create a copy of the data so that when the weights are diddled
+    // with it doesn't mess up the weights for anyone else
+    training = new Instances(data, 0, numInstances);
+    
+    // Do boostrap iterations
+    for (m_NumIterationsPerformed = 0; m_NumIterationsPerformed < m_Classifiers.length; 
+	 m_NumIterationsPerformed++) {
+      if (m_Debug) {
+	System.err.println("Training classifier " + (m_NumIterationsPerformed + 1));
+      }
+      // Select instances to train the classifier on
+      if (m_WeightThreshold < 100) {
+	trainData = selectWeightQuantile(training, 
+					 (double)m_WeightThreshold / 100);
+      } else {
+	trainData = new Instances(training, 0, numInstances);
+      }
+
+      // Build the classifier
+      if (m_Classifiers[m_NumIterationsPerformed] instanceof Randomizable)
+	((Randomizable) m_Classifiers[m_NumIterationsPerformed]).setSeed(randomInstance.nextInt());
+      m_Classifiers[m_NumIterationsPerformed].buildClassifier(trainData);
+
+      // Evaluate the classifier
+      evaluation = new Evaluation(data);
+      evaluation.evaluateModel(m_Classifiers[m_NumIterationsPerformed], training);
+      epsilon = evaluation.errorRate();
+
+      // Stop if error too small or error too big and ignore this model
+      if (Utils.grOrEq(epsilon, 0.5) || Utils.eq(epsilon, 0)) {
+	if (m_NumIterationsPerformed == 0) {
+	  m_NumIterationsPerformed = 1; // If we're the first we have to to use it
+	}
+	break;
+      }
+      // Determine the weight to assign to this model
+      m_Betas[m_NumIterationsPerformed] = Math.log((1 - epsilon) / epsilon);
+      reweight = (1 - epsilon) / epsilon;
+      if (m_Debug) {
+	System.err.println("\terror rate = " + epsilon
+			   +"  beta = " + m_Betas[m_NumIterationsPerformed]);
+      }
+ 
+      // Update instance weights
+      setWeights(training, reweight);
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+      
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    if (m_NumIterationsPerformed == 0) {
+      throw new Exception("No model built");
+    }
+    double [] sums = new double [instance.numClasses()]; 
+    
+    if (m_NumIterationsPerformed == 1) {
+      return m_Classifiers[0].distributionForInstance(instance);
+    } else {
+      for (int i = 0; i < m_NumIterationsPerformed; i++) {
+	sums[(int)m_Classifiers[i].classifyInstance(instance)] += m_Betas[i];
+      }
+      return Utils.logs2probs(sums);
+    }
+  }
+
+  /**
+   * Returns the boosted model as Java source code.
+   *
+   * @param className the classname of the generated class
+   * @return the tree as Java source code
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) throws Exception {
+
+    if (m_NumIterationsPerformed == 0) {
+      throw new Exception("No model built yet");
+    }
+    if (!(m_Classifiers[0] instanceof Sourcable)) {
+      throw new Exception("Base learner " + m_Classifier.getClass().getName()
+			  + " is not Sourcable");
+    }
+
+    StringBuffer text = new StringBuffer("class ");
+    text.append(className).append(" {\n\n");
+
+    text.append("  public static double classify(Object[] i) {\n");
+
+    if (m_NumIterationsPerformed == 1) {
+      text.append("    return " + className + "_0.classify(i);\n");
+    } else {
+      text.append("    double [] sums = new double [" + m_NumClasses + "];\n");
+      for (int i = 0; i < m_NumIterationsPerformed; i++) {
+	text.append("    sums[(int) " + className + '_' + i 
+		    + ".classify(i)] += " + m_Betas[i] + ";\n");
+      }
+      text.append("    double maxV = sums[0];\n" +
+		  "    int maxI = 0;\n"+
+		  "    for (int j = 1; j < " + m_NumClasses + "; j++) {\n"+
+		  "      if (sums[j] > maxV) { maxV = sums[j]; maxI = j; }\n"+
+		  "    }\n    return (double) maxI;\n");
+    }
+    text.append("  }\n}\n");
+
+    for (int i = 0; i < m_Classifiers.length; i++) {
+	text.append(((Sourcable)m_Classifiers[i])
+		    .toSource(className + '_' + i));
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns description of the boosted classifier.
+   *
+   * @return description of the boosted classifier as a string
+   */
+  public String toString() {
+    
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer text = new StringBuffer();
+    
+    if (m_NumIterationsPerformed == 0) {
+      text.append("AdaBoostM1: No model built yet.\n");
+    } else if (m_NumIterationsPerformed == 1) {
+      text.append("AdaBoostM1: No boosting possible, one classifier used!\n");
+      text.append(m_Classifiers[0].toString() + "\n");
+    } else {
+      text.append("AdaBoostM1: Base classifiers and their weights: \n\n");
+      for (int i = 0; i < m_NumIterationsPerformed ; i++) {
+	text.append(m_Classifiers[i].toString() + "\n\n");
+	text.append("Weight: " + Utils.roundDouble(m_Betas[i], 2) + "\n\n");
+      }
+      text.append("Number of performed Iterations: " 
+		  + m_NumIterationsPerformed + "\n");
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new AdaBoostM1(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/AdditiveRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/AdditiveRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/AdditiveRegression.java	(revision 29)
@@ -0,0 +1,537 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AdditiveRegression.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.IteratedSingleClassifierEnhancer;
+import weka.classifiers.rules.ZeroR;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Meta classifier that enhances the performance of a regression base classifier. Each iteration fits a model to the residuals left by the classifier on the previous iteration. Prediction is accomplished by adding the predictions of each classifier. Reducing the shrinkage (learning rate) parameter helps prevent overfitting and has a smoothing effect but increases the learning time.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * J.H. Friedman (1999). Stochastic Gradient Boosting.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Friedman1999,
+ *    author = {J.H. Friedman},
+ *    institution = {Stanford University},
+ *    title = {Stochastic Gradient Boosting},
+ *    year = {1999},
+ *    PS = {http://www-stat.stanford.edu/\~jhf/ftp/stobst.ps}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S
+ *  Specify shrinkage rate. (default = 1.0, ie. no shrinkage)
+ * </pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class AdditiveRegression 
+  extends IteratedSingleClassifierEnhancer 
+  implements OptionHandler,
+	     AdditionalMeasureProducer,
+	     WeightedInstancesHandler,
+	     TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -2368937577670527151L;
+  
+  /**
+   * Shrinkage (Learning rate). Default = no shrinkage.
+   */
+  protected double m_shrinkage = 1.0;
+
+  /** The number of successfully generated base classifiers. */
+  protected int m_NumIterationsPerformed;
+
+  /** The model for the mean */
+  protected ZeroR m_zeroR;
+
+  /** whether we have suitable data or nor (if not, ZeroR model is used) */
+  protected boolean m_SuitableData = true;
+  
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return " Meta classifier that enhances the performance of a regression "
+      +"base classifier. Each iteration fits a model to the residuals left "
+      +"by the classifier on the previous iteration. Prediction is "
+      +"accomplished by adding the predictions of each classifier. "
+      +"Reducing the shrinkage (learning rate) parameter helps prevent "
+      +"overfitting and has a smoothing effect but increases the learning "
+      +"time.\n\n"
+      +"For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "J.H. Friedman");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.TITLE, "Stochastic Gradient Boosting");
+    result.setValue(Field.INSTITUTION, "Stanford University");
+    result.setValue(Field.PS, "http://www-stat.stanford.edu/~jhf/ftp/stobst.ps");
+    
+    return result;
+  }
+
+  /**
+   * Default constructor specifying DecisionStump as the classifier
+   */
+  public AdditiveRegression() {
+
+    this(new weka.classifiers.trees.DecisionStump());
+  }
+
+  /**
+   * Constructor which takes base classifier as argument.
+   *
+   * @param classifier the base classifier to use
+   */
+  public AdditiveRegression(Classifier classifier) {
+
+    m_Classifier = classifier;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+	      "\tSpecify shrinkage rate. "
+	      +"(default = 1.0, ie. no shrinkage)\n", 
+	      "S", 1, "-S"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S
+   *  Specify shrinkage rate. (default = 1.0, ie. no shrinkage)
+   * </pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      Double temp = Double.valueOf(optionString);
+      setShrinkage(temp.doubleValue());
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = "" + getShrinkage();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String shrinkageTipText() {
+    return "Shrinkage rate. Smaller values help prevent overfitting and "
+      + "have a smoothing effect (but increase learning time). "
+      +"Default = 1.0, ie. no shrinkage."; 
+  }
+
+  /**
+   * Set the shrinkage parameter
+   *
+   * @param l the shrinkage rate.
+   */
+  public void setShrinkage(double l) {
+    m_shrinkage = l;
+  }
+
+  /**
+   * Get the shrinkage rate.
+   *
+   * @return the value of the learning rate
+   */
+  public double getShrinkage() {
+    return m_shrinkage;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Build the classifier on the supplied data
+   *
+   * @param data the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    super.buildClassifier(data);
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+    newData.deleteWithMissingClass();
+
+    double sum = 0;
+    double temp_sum = 0;
+    // Add the model for the mean first
+    m_zeroR = new ZeroR();
+    m_zeroR.buildClassifier(newData);
+    
+    // only class? -> use only ZeroR model
+    if (newData.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_SuitableData = false;
+      return;
+    }
+    else {
+      m_SuitableData = true;
+    }
+    
+    newData = residualReplace(newData, m_zeroR, false);
+    for (int i = 0; i < newData.numInstances(); i++) {
+      sum += newData.instance(i).weight() *
+	newData.instance(i).classValue() * newData.instance(i).classValue();
+    }
+    if (m_Debug) {
+      System.err.println("Sum of squared residuals "
+			 +"(predicting the mean) : " + sum);
+    }
+
+    m_NumIterationsPerformed = 0;
+    do {
+      temp_sum = sum;
+
+      // Build the classifier
+      m_Classifiers[m_NumIterationsPerformed].buildClassifier(newData);
+
+      newData = residualReplace(newData, m_Classifiers[m_NumIterationsPerformed], true);
+      sum = 0;
+      for (int i = 0; i < newData.numInstances(); i++) {
+	sum += newData.instance(i).weight() *
+	  newData.instance(i).classValue() * newData.instance(i).classValue();
+      }
+      if (m_Debug) {
+	System.err.println("Sum of squared residuals : "+sum);
+      }
+      m_NumIterationsPerformed++;
+    } while (((temp_sum - sum) > Utils.SMALL) && 
+	     (m_NumIterationsPerformed < m_Classifiers.length));
+  }
+
+  /**
+   * Classify an instance.
+   *
+   * @param inst the instance to predict
+   * @return a prediction for the instance
+   * @throws Exception if an error occurs
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+
+    double prediction = m_zeroR.classifyInstance(inst);
+
+    // default model?
+    if (!m_SuitableData) {
+      return prediction;
+    }
+    
+    for (int i = 0; i < m_NumIterationsPerformed; i++) {
+      double toAdd = m_Classifiers[i].classifyInstance(inst);
+      toAdd *= getShrinkage();
+      prediction += toAdd;
+    }
+
+    return prediction;
+  }
+
+  /**
+   * Replace the class values of the instances from the current iteration
+   * with residuals ater predicting with the supplied classifier.
+   *
+   * @param data the instances to predict
+   * @param c the classifier to use
+   * @param useShrinkage whether shrinkage is to be applied to the model's output
+   * @return a new set of instances with class values replaced by residuals
+   * @throws Exception if something goes wrong
+   */
+  private Instances residualReplace(Instances data, Classifier c, 
+				    boolean useShrinkage) throws Exception {
+    double pred,residual;
+    Instances newInst = new Instances(data);
+
+    for (int i = 0; i < newInst.numInstances(); i++) {
+      pred = c.classifyInstance(newInst.instance(i));
+      if (useShrinkage) {
+	pred *= getShrinkage();
+      }
+      residual = newInst.instance(i).classValue() - pred;
+      newInst.instance(i).setClassValue(residual);
+    }
+    //    System.err.print(newInst);
+    return newInst;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumIterations");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumIterations") == 0) {
+      return measureNumIterations();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (AdditiveRegression)");
+    }
+  }
+
+  /**
+   * return the number of iterations (base classifiers) completed
+   * @return the number of iterations (same as number of base classifier
+   * models)
+   */
+  public double measureNumIterations() {
+    return m_NumIterationsPerformed;
+  }
+
+  /**
+   * Returns textual description of the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+
+    // only ZeroR model?
+    if (!m_SuitableData) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_zeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_NumIterations == 0) {
+      return "Classifier hasn't been built yet!";
+    }
+
+    text.append("Additive Regression\n\n");
+
+    text.append("ZeroR model\n\n" + m_zeroR + "\n\n");
+
+    text.append("Base classifier " 
+		+ getClassifier().getClass().getName()
+		+ "\n\n");
+    text.append("" + m_NumIterationsPerformed + " models generated.\n");
+
+    for (int i = 0; i < m_NumIterationsPerformed; i++) {
+      text.append("\nModel number " + i + "\n\n" +
+		  m_Classifiers[i] + "\n");
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new AdditiveRegression(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/AttributeSelectedClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/AttributeSelectedClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/AttributeSelectedClassifier.java	(revision 29)
@@ -0,0 +1,684 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSelectedClassifier.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.attributeSelection.ASEvaluation;
+import weka.attributeSelection.ASSearch;
+import weka.attributeSelection.AttributeSelection;
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Dimensionality of training and test data is reduced by attribute selection before being passed on to a classifier.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;attribute evaluator specification&gt;
+ *  Full class name of attribute evaluator, followed
+ *  by its options.
+ *  eg: "weka.attributeSelection.CfsSubsetEval -L"
+ *  (default weka.attributeSelection.CfsSubsetEval)</pre>
+ * 
+ * <pre> -S &lt;search method specification&gt;
+ *  Full class name of search method, followed
+ *  by its options.
+ *  eg: "weka.attributeSelection.BestFirst -D 1"
+ *  (default weka.attributeSelection.BestFirst)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.26 $
+ */
+public class AttributeSelectedClassifier 
+  extends SingleClassifierEnhancer
+  implements OptionHandler, Drawable, AdditionalMeasureProducer,
+             WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -5951805453487947577L;
+  
+  /** The attribute selection object */
+  protected AttributeSelection m_AttributeSelection = null;
+
+  /** The attribute evaluator to use */
+  protected ASEvaluation m_Evaluator = 
+    new weka.attributeSelection.CfsSubsetEval();
+
+  /** The search method to use */
+  protected ASSearch m_Search = new weka.attributeSelection.BestFirst();
+
+  /** The header of the dimensionally reduced data */
+  protected Instances m_ReducedHeader;
+
+  /** The number of class vals in the training data (1 if class is numeric) */
+  protected int m_numClasses;
+
+  /** The number of attributes selected by the attribute selection phase */
+  protected double m_numAttributesSelected;
+
+  /** The time taken to select attributes in milliseconds */
+  protected double m_selectionTime;
+
+  /** The time taken to select attributes AND build the classifier */
+  protected double m_totalTime;
+
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+  
+  /**
+   * Default constructor.
+   */
+  public AttributeSelectedClassifier() {
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search method suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Dimensionality of training and test data is reduced by "
+      +"attribute selection before being passed on to a classifier.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+     Vector newVector = new Vector(3);
+    
+    newVector.addElement(new Option(
+	      "\tFull class name of attribute evaluator, followed\n"
+	      + "\tby its options.\n"
+	      + "\teg: \"weka.attributeSelection.CfsSubsetEval -L\"\n"
+	      + "\t(default weka.attributeSelection.CfsSubsetEval)",
+	      "E", 1, "-E <attribute evaluator specification>"));
+
+    newVector.addElement(new Option(
+	      "\tFull class name of search method, followed\n"
+	      + "\tby its options.\n"
+	      + "\teg: \"weka.attributeSelection.BestFirst -D 1\"\n"
+	      + "\t(default weka.attributeSelection.BestFirst)",
+	      "S", 1, "-S <search method specification>"));
+    
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -E &lt;attribute evaluator specification&gt;
+   *  Full class name of attribute evaluator, followed
+   *  by its options.
+   *  eg: "weka.attributeSelection.CfsSubsetEval -L"
+   *  (default weka.attributeSelection.CfsSubsetEval)</pre>
+   * 
+   * <pre> -S &lt;search method specification&gt;
+   *  Full class name of search method, followed
+   *  by its options.
+   *  eg: "weka.attributeSelection.BestFirst -D 1"
+   *  (default weka.attributeSelection.BestFirst)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.J48)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.J48:
+   * </pre>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    // same for attribute evaluator
+    String evaluatorString = Utils.getOption('E', options);
+    if (evaluatorString.length() == 0)
+      evaluatorString = weka.attributeSelection.CfsSubsetEval.class.getName();
+    String [] evaluatorSpec = Utils.splitOptions(evaluatorString);
+    if (evaluatorSpec.length == 0) {
+      throw new Exception("Invalid attribute evaluator specification string");
+    }
+    String evaluatorName = evaluatorSpec[0];
+    evaluatorSpec[0] = "";
+    setEvaluator(ASEvaluation.forName(evaluatorName, evaluatorSpec));
+
+    // same for search method
+    String searchString = Utils.getOption('S', options);
+    if (searchString.length() == 0)
+      searchString = weka.attributeSelection.BestFirst.class.getName();
+    String [] searchSpec = Utils.splitOptions(searchString);
+    if (searchSpec.length == 0) {
+      throw new Exception("Invalid search specification string");
+    }
+    String searchName = searchSpec[0];
+    searchSpec[0] = "";
+    setSearch(ASSearch.forName(searchName, searchSpec));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 4];
+
+    int current = 0;
+
+    // same attribute evaluator
+    options[current++] = "-E";
+    options[current++] = "" +getEvaluatorSpec();
+    
+    // same for search
+    options[current++] = "-S";
+    options[current++] = "" + getSearchSpec();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluatorTipText() {
+    return "Set the attribute evaluator to use. This evaluator is used "
+      +"during the attribute selection phase before the classifier is "
+      +"invoked.";
+  }
+
+  /**
+   * Sets the attribute evaluator
+   *
+   * @param evaluator the evaluator with all options set.
+   */
+  public void setEvaluator(ASEvaluation evaluator) {
+    m_Evaluator = evaluator;
+  }
+
+  /**
+   * Gets the attribute evaluator used
+   *
+   * @return the attribute evaluator
+   */
+  public ASEvaluation getEvaluator() {
+    return m_Evaluator;
+  }
+
+  /**
+   * Gets the evaluator specification string, which contains the class name of
+   * the attribute evaluator and any options to it
+   *
+   * @return the evaluator string.
+   */
+  protected String getEvaluatorSpec() {
+    
+    ASEvaluation e = getEvaluator();
+    if (e instanceof OptionHandler) {
+      return e.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)e).getOptions());
+    }
+    return e.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchTipText() {
+    return "Set the search method. This search method is used "
+      +"during the attribute selection phase before the classifier is "
+      +"invoked.";
+  }
+  
+  /**
+   * Sets the search method
+   *
+   * @param search the search method with all options set.
+   */
+  public void setSearch(ASSearch search) {
+    m_Search = search;
+  }
+
+  /**
+   * Gets the search method used
+   *
+   * @return the search method
+   */
+  public ASSearch getSearch() {
+    return m_Search;
+  }
+
+  /**
+   * Gets the search specification string, which contains the class name of
+   * the search method and any options to it
+   *
+   * @return the search string.
+   */
+  protected String getSearchSpec() {
+    
+    ASSearch s = getSearch();
+    if (s instanceof OptionHandler) {
+      return s.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)s).getOptions());
+    }
+    return s.getClass().getName();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getEvaluator() == null)
+      result = super.getCapabilities();
+    else
+      result = getEvaluator().getCapabilities();
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Build the classifier on the dimensionally reduced data.
+   *
+   * @param data the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    if (m_Classifier == null) {
+      throw new Exception("No base classifier has been set!");
+    }
+
+    if (m_Evaluator == null) {
+      throw new Exception("No attribute evaluator has been set!");
+    }
+
+    if (m_Search == null) {
+      throw new Exception("No search method has been set!");
+    }
+   
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+    newData.deleteWithMissingClass();
+    
+    if (newData.numInstances() == 0) {
+      m_Classifier.buildClassifier(newData);
+      return;
+    }
+    if (newData.classAttribute().isNominal()) {
+      m_numClasses = newData.classAttribute().numValues();
+    } else {
+      m_numClasses = 1;
+    }
+
+    Instances resampledData = null;
+    // check to see if training data has all equal weights
+    double weight = newData.instance(0).weight();
+    boolean ok = false;
+    for (int i = 1; i < newData.numInstances(); i++) {
+      if (newData.instance(i).weight() != weight) {
+        ok = true;
+        break;
+      }
+    }
+    
+    if (ok) {
+      if (!(m_Evaluator instanceof WeightedInstancesHandler) || 
+          !(m_Classifier instanceof WeightedInstancesHandler)) {
+        Random r = new Random(1);
+        for (int i = 0; i < 10; i++) {
+          r.nextDouble();
+        }
+        resampledData = newData.resampleWithWeights(r);
+      }
+    } else {
+      // all equal weights in the training data so just use as is
+      resampledData = newData;
+    }
+
+    m_AttributeSelection = new AttributeSelection();
+    m_AttributeSelection.setEvaluator(m_Evaluator);
+    m_AttributeSelection.setSearch(m_Search);
+    long start = System.currentTimeMillis();
+    m_AttributeSelection.
+      SelectAttributes((m_Evaluator instanceof WeightedInstancesHandler) 
+                       ? newData
+                       : resampledData);
+    long end = System.currentTimeMillis();
+    if (m_Classifier instanceof WeightedInstancesHandler) {
+      newData = m_AttributeSelection.reduceDimensionality(newData);
+      m_Classifier.buildClassifier(newData);
+    } else {
+      resampledData = m_AttributeSelection.reduceDimensionality(resampledData);
+      m_Classifier.buildClassifier(resampledData);
+    }
+
+    long end2 = System.currentTimeMillis();
+    m_numAttributesSelected = m_AttributeSelection.numberAttributesSelected();
+    m_ReducedHeader = 
+      new Instances((m_Classifier instanceof WeightedInstancesHandler) ?
+                    newData
+                    : resampledData, 0);
+    m_selectionTime = (double)(end - start);
+    m_totalTime = (double)(end2 - start);
+  }
+
+  /**
+   * Classifies a given instance after attribute selection
+   *
+   * @param instance the instance to be classified
+   * @return the class distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance)
+    throws Exception {
+
+    Instance newInstance;
+    if (m_AttributeSelection == null) {
+      //      throw new Exception("AttributeSelectedClassifier: No model built yet!");
+      newInstance = instance;
+    } else {
+      newInstance = m_AttributeSelection.reduceDimensionality(instance);
+    }
+
+    return m_Classifier.distributionForInstance(newInstance);
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  
+   *  @return the type of graph
+   */   
+  public int graphType() {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graphType();
+    else 
+      return Drawable.NOT_DRAWABLE;
+  }
+
+  /**
+   * Returns graph describing the classifier (if possible).
+   *
+   * @return the graph of the classifier in dotty format
+   * @throws Exception if the classifier cannot be graphed
+   */
+  public String graph() throws Exception {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graph();
+    else throw new Exception("Classifier: " + getClassifierSpec()
+			     + " cannot be graphed");
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a representation of this classifier
+   */
+  public String toString() {
+    if (m_AttributeSelection == null) {
+      return "AttributeSelectedClassifier: No attribute selection possible.\n\n"
+	+m_Classifier.toString();
+    }
+
+    StringBuffer result = new StringBuffer();
+    result.append("AttributeSelectedClassifier:\n\n");
+    result.append(m_AttributeSelection.toResultsString());
+    result.append("\n\nHeader of reduced data:\n"+m_ReducedHeader.toString());
+    result.append("\n\nClassifier Model\n"+m_Classifier.toString());
+
+    return result.toString();
+  }
+
+  /**
+   * Additional measure --- number of attributes selected
+   * @return the number of attributes selected
+   */
+  public double measureNumAttributesSelected() {
+    return m_numAttributesSelected;
+  }
+
+  /**
+   * Additional measure --- time taken (milliseconds) to select the attributes
+   * @return the time taken to select attributes
+   */
+  public double measureSelectionTime() {
+    return m_selectionTime;
+  }
+
+  /**
+   * Additional measure --- time taken (milliseconds) to select attributes
+   * and build the classifier
+   * @return the total time (select attributes + build classifier)
+   */
+  public double measureTime() {
+    return m_totalTime;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(3);
+    newVector.addElement("measureNumAttributesSelected");
+    newVector.addElement("measureSelectionTime");
+    newVector.addElement("measureTime");
+    if (m_Classifier instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_Classifier).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumAttributesSelected") == 0) {
+      return measureNumAttributesSelected();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureSelectionTime") == 0) {
+      return measureSelectionTime();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTime") == 0) {
+      return measureTime();
+    } else if (m_Classifier instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_Classifier).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (AttributeSelectedClassifier)");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.26 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new AttributeSelectedClassifier(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Bagging.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Bagging.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Bagging.java	(revision 29)
@@ -0,0 +1,681 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Bagging.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.classifiers.RandomizableParallelIteratedSingleClassifierEnhancer;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for bagging a classifier to reduce variance. Can do classification and regression depending on the base learner. <br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Leo Breiman (1996). Bagging predictors. Machine Learning. 24(2):123-140.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Breiman1996,
+ *    author = {Leo Breiman},
+ *    journal = {Machine Learning},
+ *    number = {2},
+ *    pages = {123-140},
+ *    title = {Bagging predictors},
+ *    volume = {24},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P
+ *  Size of each bag, as a percentage of the
+ *  training set size. (default 100)</pre>
+ * 
+ * <pre> -O
+ *  Calculate the out of bag error.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.REPTree)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.REPTree:
+ * </pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf (default 2).</pre>
+ * 
+ * <pre> -V &lt;minimum variance for split&gt;
+ *  Set minimum numeric class variance proportion
+ *  of train variance for split (default 1e-3).</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Number of folds for reduced error pruning (default 3).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ * <pre> -P
+ *  No pruning.</pre>
+ * 
+ * <pre> -L
+ *  Maximum tree depth (default -1, no maximum)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5801 $
+ */
+public class Bagging
+  extends RandomizableParallelIteratedSingleClassifierEnhancer 
+  implements WeightedInstancesHandler, AdditionalMeasureProducer,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -505879962237199703L;
+  
+  /** The size of each bag sample, as a percentage of the training size */
+  protected int m_BagSizePercent = 100;
+
+  /** Whether to calculate the out of bag error */
+  protected boolean m_CalcOutOfBag = false;
+
+  /** The out of bag error that has been calculated */
+  protected double m_OutOfBagError;  
+    
+  /**
+   * Constructor.
+   */
+  public Bagging() {
+    
+    m_Classifier = new weka.classifiers.trees.REPTree();
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for bagging a classifier to reduce variance. Can do classification "
+      + "and regression depending on the base learner. \n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Leo Breiman");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.TITLE, "Bagging predictors");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "24");
+    result.setValue(Field.NUMBER, "2");
+    result.setValue(Field.PAGES, "123-140");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.REPTree";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tSize of each bag, as a percentage of the\n" 
+              + "\ttraining set size. (default 100)",
+              "P", 1, "-P"));
+    newVector.addElement(new Option(
+              "\tCalculate the out of bag error.",
+              "O", 0, "-O"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P
+   *  Size of each bag, as a percentage of the
+   *  training set size. (default 100)</pre>
+   * 
+   * <pre> -O
+   *  Calculate the out of bag error.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.REPTree)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.REPTree:
+   * </pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf (default 2).</pre>
+   * 
+   * <pre> -V &lt;minimum variance for split&gt;
+   *  Set minimum numeric class variance proportion
+   *  of train variance for split (default 1e-3).</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Number of folds for reduced error pruning (default 3).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   * <pre> -P
+   *  No pruning.</pre>
+   * 
+   * <pre> -L
+   *  Maximum tree depth (default -1, no maximum)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String bagSize = Utils.getOption('P', options);
+    if (bagSize.length() != 0) {
+      setBagSizePercent(Integer.parseInt(bagSize));
+    } else {
+      setBagSizePercent(100);
+    }
+
+    setCalcOutOfBag(Utils.getFlag('O', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 3];
+
+    int current = 0;
+    options[current++] = "-P"; 
+    options[current++] = "" + getBagSizePercent();
+
+    if (getCalcOutOfBag()) { 
+      options[current++] = "-O";
+    }
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String bagSizePercentTipText() {
+    return "Size of each bag, as a percentage of the training set size.";
+  }
+
+  /**
+   * Gets the size of each bag, as a percentage of the training set size.
+   *
+   * @return the bag size, as a percentage.
+   */
+  public int getBagSizePercent() {
+
+    return m_BagSizePercent;
+  }
+  
+  /**
+   * Sets the size of each bag, as a percentage of the training set size.
+   *
+   * @param newBagSizePercent the bag size, as a percentage.
+   */
+  public void setBagSizePercent(int newBagSizePercent) {
+
+    m_BagSizePercent = newBagSizePercent;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String calcOutOfBagTipText() {
+    return "Whether the out-of-bag error is calculated.";
+  }
+
+  /**
+   * Set whether the out of bag error is calculated.
+   *
+   * @param calcOutOfBag whether to calculate the out of bag error
+   */
+  public void setCalcOutOfBag(boolean calcOutOfBag) {
+
+    m_CalcOutOfBag = calcOutOfBag;
+  }
+
+  /**
+   * Get whether the out of bag error is calculated.
+   *
+   * @return whether the out of bag error is calculated
+   */
+  public boolean getCalcOutOfBag() {
+
+    return m_CalcOutOfBag;
+  }
+
+  /**
+   * Gets the out of bag error that was calculated as the classifier
+   * was built.
+   *
+   * @return the out of bag error 
+   */
+  public double measureOutOfBagError() {
+    
+    return m_OutOfBagError;
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names.
+   *
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureOutOfBagError");
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   *
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    
+    if (additionalMeasureName.equalsIgnoreCase("measureOutOfBagError")) {
+      return measureOutOfBagError();
+    }
+    else {throw new IllegalArgumentException(additionalMeasureName 
+					     + " not supported (Bagging)");
+    }
+  }
+
+  /**
+   * Creates a new dataset of the same size using random sampling
+   * with replacement according to the given weight vector. The
+   * weights of the instances in the new dataset are set to one.
+   * The length of the weight vector has to be the same as the
+   * number of instances in the dataset, and all weights have to
+   * be positive.
+   *
+   * @param data the data to be sampled from
+   * @param random a random number generator
+   * @param sampled indicating which instance has been sampled
+   * @return the new dataset
+   * @throws IllegalArgumentException if the weights array is of the wrong
+   * length or contains negative weights.
+   */
+  public final Instances resampleWithWeights(Instances data,
+					     Random random, 
+					     boolean[] sampled) {
+
+    double[] weights = new double[data.numInstances()];
+    for (int i = 0; i < weights.length; i++) {
+      weights[i] = data.instance(i).weight();
+    }
+    Instances newData = new Instances(data, data.numInstances());
+    if (data.numInstances() == 0) {
+      return newData;
+    }
+    double[] probabilities = new double[data.numInstances()];
+    double sumProbs = 0, sumOfWeights = Utils.sum(weights);
+    for (int i = 0; i < data.numInstances(); i++) {
+      sumProbs += random.nextDouble();
+      probabilities[i] = sumProbs;
+    }
+    Utils.normalize(probabilities, sumProbs / sumOfWeights);
+
+    // Make sure that rounding errors don't mess things up
+    probabilities[data.numInstances() - 1] = sumOfWeights;
+    int k = 0; int l = 0;
+    sumProbs = 0;
+    while ((k < data.numInstances() && (l < data.numInstances()))) {
+      if (weights[l] < 0) {
+	throw new IllegalArgumentException("Weights have to be positive.");
+      }
+      sumProbs += weights[l];
+      while ((k < data.numInstances()) &&
+	     (probabilities[k] <= sumProbs)) { 
+	newData.add(data.instance(l));
+	sampled[l] = true;
+	newData.instance(k).setWeight(1);
+	k++;
+      }
+      l++;
+    }
+    return newData;
+  }
+  
+  protected Random m_random;
+  protected boolean[][] m_inBag;
+  protected Instances m_data;
+  
+  /**
+   * Returns a training set for a particular iteration.
+   * 
+   * @param iteration the number of the iteration for the requested training set.
+   * @return the training set for the supplied iteration number
+   * @throws Exception if something goes wrong when generating a training set.
+   */
+  protected synchronized Instances getTrainingSet(int iteration) throws Exception {
+    int bagSize = m_data.numInstances() * m_BagSizePercent / 100;
+    Instances bagData = null;
+
+    // create the in-bag dataset
+    if (m_CalcOutOfBag) {
+      m_inBag[iteration] = new boolean[m_data.numInstances()];
+      bagData = resampleWithWeights(m_data, m_random, m_inBag[iteration]);
+    } else {
+      bagData = m_data.resampleWithWeights(m_random);
+      if (bagSize < m_data.numInstances()) {
+        bagData.randomize(m_random);
+        Instances newBagData = new Instances(bagData, 0, bagSize);
+        bagData = newBagData;
+      }
+    }
+    
+    return bagData;
+  }
+  
+  /**
+   * Bagging method.
+   *
+   * @param data the training data to be used for generating the
+   * bagged classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    m_data = new Instances(data);
+    m_data.deleteWithMissingClass();
+    
+    super.buildClassifier(m_data);
+
+    if (m_CalcOutOfBag && (m_BagSizePercent != 100)) {
+      throw new IllegalArgumentException("Bag size needs to be 100% if " +
+					 "out-of-bag error is to be calculated!");
+    }
+
+    int bagSize = m_data.numInstances() * m_BagSizePercent / 100;
+    m_random = new Random(m_Seed);
+    
+    m_inBag = null;
+    if (m_CalcOutOfBag)
+      m_inBag = new boolean[m_Classifiers.length][];
+    
+    for (int j = 0; j < m_Classifiers.length; j++) {      
+      if (m_Classifier instanceof Randomizable) {
+	((Randomizable) m_Classifiers[j]).setSeed(m_random.nextInt());
+      }
+    }
+    
+    buildClassifiers();
+    
+    // calc OOB error?
+    if (getCalcOutOfBag()) {
+      double outOfBagCount = 0.0;
+      double errorSum = 0.0;
+      boolean numeric = m_data.classAttribute().isNumeric();
+      
+      for (int i = 0; i < m_data.numInstances(); i++) {
+        double vote;
+        double[] votes;
+        if (numeric)
+          votes = new double[1];
+        else
+          votes = new double[m_data.numClasses()];
+        
+        // determine predictions for instance
+        int voteCount = 0;
+        for (int j = 0; j < m_Classifiers.length; j++) {
+          if (m_inBag[j][i])
+            continue;
+          
+          voteCount++;
+          double pred = m_Classifiers[j].classifyInstance(m_data.instance(i));
+          if (numeric)
+            votes[0] += pred;
+          else
+            votes[(int) pred]++;
+        }
+        
+        // "vote"
+        if (numeric) {
+          vote = votes[0];
+          if (voteCount > 0) {
+            vote  /= voteCount;    // average
+          }
+        } else {
+          vote = Utils.maxIndex(votes);   // majority vote
+        }
+        
+        // error for instance
+        outOfBagCount += m_data.instance(i).weight();
+        if (numeric) {
+          errorSum += StrictMath.abs(vote - m_data.instance(i).classValue()) 
+          * m_data.instance(i).weight();
+        }
+        else {
+          if (vote != m_data.instance(i).classValue())
+            errorSum += m_data.instance(i).weight();
+        }
+      }
+      
+      m_OutOfBagError = errorSum / outOfBagCount;
+    }
+    else {
+      m_OutOfBagError = 0;
+    }
+    
+    // save memory
+    m_data = null;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    double [] sums = new double [instance.numClasses()], newProbs; 
+    
+    for (int i = 0; i < m_NumIterations; i++) {
+      if (instance.classAttribute().isNumeric() == true) {
+	sums[0] += m_Classifiers[i].classifyInstance(instance);
+      } else {
+	newProbs = m_Classifiers[i].distributionForInstance(instance);
+	for (int j = 0; j < newProbs.length; j++)
+	  sums[j] += newProbs[j];
+      }
+    }
+    if (instance.classAttribute().isNumeric() == true) {
+      sums[0] /= (double)m_NumIterations;
+      return sums;
+    } else if (Utils.eq(Utils.sum(sums), 0)) {
+      return sums;
+    } else {
+      Utils.normalize(sums);
+      return sums;
+    }
+  }
+
+  /**
+   * Returns description of the bagged classifier.
+   *
+   * @return description of the bagged classifier as a string
+   */
+  public String toString() {
+    
+    if (m_Classifiers == null) {
+      return "Bagging: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("All the base classifiers: \n\n");
+    for (int i = 0; i < m_Classifiers.length; i++)
+      text.append(m_Classifiers[i].toString() + "\n\n");
+    
+    if (m_CalcOutOfBag) {
+      text.append("Out of bag error: "
+		  + Utils.doubleToString(m_OutOfBagError, 4)
+		  + "\n\n");
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5801 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new Bagging(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/CVParameterSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/CVParameterSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/CVParameterSelection.java	(revision 29)
@@ -0,0 +1,853 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CVParameterSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for performing parameter selection by cross-validation for any classifier.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * R. Kohavi (1995). Wrappers for Performance Enhancement and Oblivious Decision Graphs. Department of Computer Science, Stanford University.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Kohavi1995,
+ *    address = {Department of Computer Science, Stanford University},
+ *    author = {R. Kohavi},
+ *    school = {Stanford University},
+ *    title = {Wrappers for Performance Enhancement and Oblivious Decision Graphs},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Number of folds used for cross validation (default 10).</pre>
+ * 
+ * <pre> -P &lt;classifier parameter&gt;
+ *  Classifier parameter options.
+ *  eg: "N 1 5 10" Sets an optimisation parameter for the
+ *  classifier with name -N, with lower bound 1, upper bound
+ *  5, and 10 optimisation steps. The upper bound may be the
+ *  character 'A' or 'I' to substitute the number of
+ *  attributes or instances in the training data,
+ *  respectively. This parameter may be supplied more than
+ *  once to optimise over several classifier options
+ *  simultaneously.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated sub-classifier. <p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+*/
+public class CVParameterSelection 
+  extends RandomizableSingleClassifierEnhancer
+  implements Drawable, Summarizable, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -6529603380876641265L;
+  
+  /**
+   * A data structure to hold values associated with a single
+   * cross-validation search parameter
+   */
+  protected class CVParameter 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -4668812017709421953L;
+
+    /**  Char used to identify the option of interest */
+    private char m_ParamChar;    
+
+    /**  Lower bound for the CV search */
+    private double m_Lower;      
+
+    /**  Upper bound for the CV search */
+    private double m_Upper;      
+
+    /**  Number of steps during the search */
+    private double m_Steps;      
+
+    /**  The parameter value with the best performance */
+    private double m_ParamValue; 
+
+    /**  True if the parameter should be added at the end of the argument list */
+    private boolean m_AddAtEnd;  
+
+    /**  True if the parameter should be rounded to an integer */
+    private boolean m_RoundParam;
+
+    /**
+     * Constructs a CVParameter.
+     * 
+     * @param param the parameter definition
+     * @throws Exception if construction of CVParameter fails
+     */
+    public CVParameter(String param) throws Exception {
+     
+      // Tokenize the string into it's parts
+      StreamTokenizer st = new StreamTokenizer(new StringReader(param));
+      if (st.nextToken() != StreamTokenizer.TT_WORD) {
+	throw new Exception("CVParameter " + param 
+			    + ": Character parameter identifier expected");
+      }
+      m_ParamChar = st.sval.charAt(0);
+      if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
+	throw new Exception("CVParameter " + param 
+			    + ": Numeric lower bound expected");
+      }
+      m_Lower = st.nval;
+      if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
+	m_Upper = st.nval;
+	if (m_Upper < m_Lower) {
+	  throw new Exception("CVParameter " + param
+			      + ": Upper bound is less than lower bound");
+	}
+      } else if (st.ttype == StreamTokenizer.TT_WORD) {
+	if (st.sval.toUpperCase().charAt(0) == 'A') {
+	  m_Upper = m_Lower - 1;
+	} else if (st.sval.toUpperCase().charAt(0) == 'I') {
+	  m_Upper = m_Lower - 2;
+	} else {
+	  throw new Exception("CVParameter " + param 
+	      + ": Upper bound must be numeric, or 'A' or 'N'");
+	}
+      } else {
+	throw new Exception("CVParameter " + param 
+	      + ": Upper bound must be numeric, or 'A' or 'N'");
+      }
+      if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
+	throw new Exception("CVParameter " + param 
+			    + ": Numeric number of steps expected");
+      }
+      m_Steps = st.nval;
+      if (st.nextToken() == StreamTokenizer.TT_WORD) {
+	if (st.sval.toUpperCase().charAt(0) == 'R') {
+	  m_RoundParam = true;
+	}
+      }
+    }
+
+    /**
+     * Returns a CVParameter as a string.
+     * 
+     * @return the CVParameter as string
+     */
+    public String toString() {
+
+      String result = m_ParamChar + " " + m_Lower + " ";
+      switch ((int)(m_Lower - m_Upper + 0.5)) {
+      case 1:
+	result += "A";
+	break;
+      case 2:
+	result += "I";
+	break;
+      default:
+	result += m_Upper;
+	break;
+      }
+      result += " " + m_Steps;
+      if (m_RoundParam) {
+	result += " R";
+      }
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * The base classifier options (not including those being set
+   * by cross-validation)
+   */
+  protected String [] m_ClassifierOptions;
+
+  /** The set of all classifier options as determined by cross-validation */
+  protected String [] m_BestClassifierOptions;
+
+  /** The set of all options at initialization time. So that getOptions
+      can return this. */
+  protected String [] m_InitOptions;
+
+  /** The cross-validated performance of the best options */
+  protected double m_BestPerformance;
+
+  /** The set of parameters to cross-validate over */
+  protected FastVector m_CVParams = new FastVector();
+
+  /** The number of attributes in the data */
+  protected int m_NumAttributes;
+
+  /** The number of instances in a training fold */
+  protected int m_TrainFoldSize;
+  
+  /** The number of folds used in cross-validation */
+  protected int m_NumFolds = 10;
+
+  /**
+   * Create the options array to pass to the classifier. The parameter
+   * values and positions are taken from m_ClassifierOptions and
+   * m_CVParams.
+   *
+   * @return the options array
+   */
+  protected String [] createOptions() {
+    
+    String [] options = new String [m_ClassifierOptions.length 
+				   + 2 * m_CVParams.size()];
+    int start = 0, end = options.length;
+
+    // Add the cross-validation parameters and their values
+    for (int i = 0; i < m_CVParams.size(); i++) {
+      CVParameter cvParam = (CVParameter)m_CVParams.elementAt(i);
+      double paramValue = cvParam.m_ParamValue;
+      if (cvParam.m_RoundParam) {
+        //	paramValue = (double)((int) (paramValue + 0.5));
+        paramValue = Math.rint(paramValue);
+      }
+      if (cvParam.m_AddAtEnd) {
+	options[--end] = "" + 
+	Utils.doubleToString(paramValue,4);
+	options[--end] = "-" + cvParam.m_ParamChar;
+      } else {
+	options[start++] = "-" + cvParam.m_ParamChar;
+	options[start++] = "" 
+	+ Utils.doubleToString(paramValue,4);
+      }
+    }
+    // Add the static parameters
+    System.arraycopy(m_ClassifierOptions, 0,
+		     options, start,
+		     m_ClassifierOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Finds the best parameter combination. (recursive for each parameter
+   * being optimised).
+   * 
+   * @param depth the index of the parameter to be optimised at this level
+   * @param trainData the data the search is based on
+   * @param random a random number generator
+   * @throws Exception if an error occurs
+   */
+  protected void findParamsByCrossValidation(int depth, Instances trainData,
+					     Random random)
+    throws Exception {
+
+    if (depth < m_CVParams.size()) {
+      CVParameter cvParam = (CVParameter)m_CVParams.elementAt(depth);
+
+      double upper;
+      switch ((int)(cvParam.m_Lower - cvParam.m_Upper + 0.5)) {
+      case 1:
+	upper = m_NumAttributes;
+	break;
+      case 2:
+	upper = m_TrainFoldSize;
+	break;
+      default:
+	upper = cvParam.m_Upper;
+	break;
+      }
+      double increment = (upper - cvParam.m_Lower) / (cvParam.m_Steps - 1);
+      for(cvParam.m_ParamValue = cvParam.m_Lower; 
+	  cvParam.m_ParamValue <= upper; 
+	  cvParam.m_ParamValue += increment) {
+	findParamsByCrossValidation(depth + 1, trainData, random);
+      }
+    } else {
+      
+      Evaluation evaluation = new Evaluation(trainData);
+
+      // Set the classifier options
+      String [] options = createOptions();
+      if (m_Debug) {
+	System.err.print("Setting options for " 
+			 + m_Classifier.getClass().getName() + ":");
+	for (int i = 0; i < options.length; i++) {
+	  System.err.print(" " + options[i]);
+	}
+	System.err.println("");
+      }
+      ((OptionHandler)m_Classifier).setOptions(options);
+      for (int j = 0; j < m_NumFolds; j++) {
+
+        // We want to randomize the data the same way for every 
+        // learning scheme.
+	Instances train = trainData.trainCV(m_NumFolds, j, new Random(1));
+	Instances test = trainData.testCV(m_NumFolds, j);
+	m_Classifier.buildClassifier(train);
+	evaluation.setPriors(train);
+	evaluation.evaluateModel(m_Classifier, test);
+      }
+      double error = evaluation.errorRate();
+      if (m_Debug) {
+	System.err.println("Cross-validated error rate: " 
+			   + Utils.doubleToString(error, 6, 4));
+      }
+      if ((m_BestPerformance == -99) || (error < m_BestPerformance)) {
+	
+	m_BestPerformance = error;
+	m_BestClassifierOptions = createOptions();
+      }
+    }
+  }
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return    "Class for performing parameter selection by cross-validation "
+	    + "for any classifier.\n\n"
+            + "For more information, see:\n\n"
+            + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "R. Kohavi");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.TITLE, "Wrappers for Performance Enhancement and Oblivious Decision Graphs");
+    result.setValue(Field.SCHOOL, "Stanford University");
+    result.setValue(Field.ADDRESS, "Department of Computer Science, Stanford University");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	      "\tNumber of folds used for cross validation (default 10).",
+	      "X", 1, "-X <number of folds>"));
+    newVector.addElement(new Option(
+	      "\tClassifier parameter options.\n"
+	      + "\teg: \"N 1 5 10\" Sets an optimisation parameter for the\n"
+	      + "\tclassifier with name -N, with lower bound 1, upper bound\n"
+	      + "\t5, and 10 optimisation steps. The upper bound may be the\n"
+	      + "\tcharacter 'A' or 'I' to substitute the number of\n"
+	      + "\tattributes or instances in the training data,\n"
+	      + "\trespectively. This parameter may be supplied more than\n"
+	      + "\tonce to optimise over several classifier options\n"
+	      + "\tsimultaneously.",
+	      "P", 1, "-P <classifier parameter>"));
+
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Number of folds used for cross validation (default 10).</pre>
+   * 
+   * <pre> -P &lt;classifier parameter&gt;
+   *  Classifier parameter options.
+   *  eg: "N 1 5 10" Sets an optimisation parameter for the
+   *  classifier with name -N, with lower bound 1, upper bound
+   *  5, and 10 optimisation steps. The upper bound may be the
+   *  character 'A' or 'I' to substitute the number of
+   *  attributes or instances in the training data,
+   *  respectively. This parameter may be supplied more than
+   *  once to optimise over several classifier options
+   *  simultaneously.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated sub-classifier. <p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String foldsString = Utils.getOption('X', options);
+    if (foldsString.length() != 0) {
+      setNumFolds(Integer.parseInt(foldsString));
+    } else {
+      setNumFolds(10);
+    }
+
+    String cvParam;
+    m_CVParams = new FastVector();
+    do {
+      cvParam = Utils.getOption('P', options);
+      if (cvParam.length() != 0) {
+	addCVParameter(cvParam);
+      }
+    } while (cvParam.length() != 0);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String[] superOptions;
+
+    if (m_InitOptions != null) {
+      try {
+	((OptionHandler)m_Classifier).setOptions((String[])m_InitOptions.clone());
+	superOptions = super.getOptions();
+	((OptionHandler)m_Classifier).setOptions((String[])m_BestClassifierOptions.clone());
+      } catch (Exception e) {
+	throw new RuntimeException("CVParameterSelection: could not set options " +
+				   "in getOptions().");
+      } 
+    } else {
+      superOptions = super.getOptions();
+    }
+    String [] options = new String [superOptions.length + m_CVParams.size() * 2 + 2];
+
+    int current = 0;
+    for (int i = 0; i < m_CVParams.size(); i++) {
+      options[current++] = "-P"; options[current++] = "" + getCVParameter(i);
+    }
+    options[current++] = "-X"; options[current++] = "" + getNumFolds();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    return options;
+  }
+
+  /**
+   * Returns (a copy of) the best options found for the classifier.
+   * 
+   * @return the best options
+   */
+  public String[] getBestClassifierOptions() {
+    return (String[]) m_BestClassifierOptions.clone();
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    result.setMinimumNumberInstances(m_NumFolds);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    Instances trainData = new Instances(instances);
+    trainData.deleteWithMissingClass();
+    
+    if (!(m_Classifier instanceof OptionHandler)) {
+      throw new IllegalArgumentException("Base classifier should be OptionHandler.");
+    }
+    m_InitOptions = ((OptionHandler)m_Classifier).getOptions();
+    m_BestPerformance = -99;
+    m_NumAttributes = trainData.numAttributes();
+    Random random = new Random(m_Seed);
+    trainData.randomize(random);
+    m_TrainFoldSize = trainData.trainCV(m_NumFolds, 0).numInstances();
+
+    // Check whether there are any parameters to optimize
+    if (m_CVParams.size() == 0) {
+       m_Classifier.buildClassifier(trainData);
+       m_BestClassifierOptions = m_InitOptions;
+       return;
+    }
+
+    if (trainData.classAttribute().isNominal()) {
+      trainData.stratify(m_NumFolds);
+    }
+    m_BestClassifierOptions = null;
+    
+    // Set up m_ClassifierOptions -- take getOptions() and remove
+    // those being optimised.
+    m_ClassifierOptions = ((OptionHandler)m_Classifier).getOptions();
+    for (int i = 0; i < m_CVParams.size(); i++) {
+      Utils.getOption(((CVParameter)m_CVParams.elementAt(i)).m_ParamChar,
+		      m_ClassifierOptions);
+    }
+    findParamsByCrossValidation(0, trainData, random);
+
+    String [] options = (String [])m_BestClassifierOptions.clone();
+    ((OptionHandler)m_Classifier).setOptions(options);
+    m_Classifier.buildClassifier(trainData);
+  }
+
+
+  /**
+   * Predicts the class distribution for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted class value
+   * @throws Exception if an error occurred during the prediction
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    return m_Classifier.distributionForInstance(instance);
+  }
+
+  /**
+   * Adds a scheme parameter to the list of parameters to be set
+   * by cross-validation
+   *
+   * @param cvParam the string representation of a scheme parameter. The
+   * format is: <br>
+   * param_char lower_bound upper_bound number_of_steps <br>
+   * eg to search a parameter -P from 1 to 10 by increments of 1: <br>
+   * P 1 10 11 <br>
+   * @throws Exception if the parameter specifier is of the wrong format
+   */
+  public void addCVParameter(String cvParam) throws Exception {
+
+    CVParameter newCV = new CVParameter(cvParam);
+    
+    m_CVParams.addElement(newCV);
+  }
+
+  /**
+   * Gets the scheme paramter with the given index.
+   * 
+   * @param index the index for the parameter
+   * @return the scheme parameter
+   */
+  public String getCVParameter(int index) {
+
+    if (m_CVParams.size() <= index) {
+      return "";
+    }
+    return ((CVParameter)m_CVParams.elementAt(index)).toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String CVParametersTipText() {
+    return "Sets the scheme parameters which are to be set "+
+	   "by cross-validation.\n"+
+	   "The format for each string should be:\n"+
+	   "param_char lower_bound upper_bound number_of_steps\n"+
+	   "eg to search a parameter -P from 1 to 10 by increments of 1:\n"+
+	   "    \"P 1 10 10\" ";
+  }
+
+  /**
+   * Get method for CVParameters.
+   * 
+   * @return the CVParameters
+   */
+  public Object[] getCVParameters() {
+      
+      Object[] CVParams = m_CVParams.toArray();
+      
+      String params[] = new String[CVParams.length];
+      
+      for(int i=0; i<CVParams.length; i++) 
+          params[i] = CVParams[i].toString();
+      
+      return params;
+      
+  }
+  
+  /**
+   * Set method for CVParameters.
+   * 
+   * @param params the CVParameters to use
+   * @throws Exception if the setting of the CVParameters fails
+   */
+  public void setCVParameters(Object[] params) throws Exception {
+      
+      FastVector backup = m_CVParams;
+      m_CVParams = new FastVector();
+      
+      for(int i=0; i<params.length; i++) {
+          try{
+          addCVParameter((String)params[i]);
+          }
+          catch(Exception ex) { m_CVParams = backup; throw ex; }
+      }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Get the number of folds used for cross-validation.";
+  }
+
+  /** 
+   * Gets the number of folds for the cross-validation.
+   *
+   * @return the number of folds for the cross-validation
+   */
+  public int getNumFolds() {
+
+    return m_NumFolds;
+  }
+
+  /**
+   * Sets the number of folds for the cross-validation.
+   *
+   * @param numFolds the number of folds for the cross-validation
+   * @throws Exception if parameter illegal
+   */
+  public void setNumFolds(int numFolds) throws Exception {
+    
+    if (numFolds < 0) {
+      throw new IllegalArgumentException("Stacking: Number of cross-validation " +
+					 "folds must be positive.");
+    }
+    m_NumFolds = numFolds;
+  }
+ 
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  
+   *  @return the type of graph this classifier represents
+   */   
+  public int graphType() {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graphType();
+    else 
+      return Drawable.NOT_DRAWABLE;
+  }
+
+  /**
+   * Returns graph describing the classifier (if possible).
+   *
+   * @return the graph of the classifier in dotty format
+   * @throws Exception if the classifier cannot be graphed
+   */
+  public String graph() throws Exception {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graph();
+    else throw new Exception("Classifier: " + 
+			     m_Classifier.getClass().getName() + " " +
+			     Utils.joinOptions(m_BestClassifierOptions)
+			     + " cannot be graphed");
+  }
+
+  /**
+   * Returns description of the cross-validated classifier.
+   *
+   * @return description of the cross-validated classifier as a string
+   */
+  public String toString() {
+
+    if (m_InitOptions == null)
+      return "CVParameterSelection: No model built yet.";
+
+    String result = "Cross-validated Parameter selection.\n"
+    + "Classifier: " + m_Classifier.getClass().getName() + "\n";
+    try {
+      for (int i = 0; i < m_CVParams.size(); i++) {
+	CVParameter cvParam = (CVParameter)m_CVParams.elementAt(i);
+	result += "Cross-validation Parameter: '-" 
+	  + cvParam.m_ParamChar + "'"
+	  + " ranged from " + cvParam.m_Lower 
+	  + " to ";
+	switch ((int)(cvParam.m_Lower - cvParam.m_Upper + 0.5)) {
+	case 1:
+	  result += m_NumAttributes;
+	  break;
+	case 2:
+	  result += m_TrainFoldSize;
+	  break;
+	default:
+	  result += cvParam.m_Upper;
+	  break;
+	}
+	result += " with " + cvParam.m_Steps + " steps\n";
+      }
+    } catch (Exception ex) {
+      result += ex.getMessage();
+    }
+    result += "Classifier Options: "
+      + Utils.joinOptions(m_BestClassifierOptions)
+      + "\n\n" + m_Classifier.toString();
+    return result;
+  }
+
+  /**
+   * A concise description of the model.
+   * 
+   * @return a concise description of the model
+   */
+  public String toSummaryString() {
+
+    String result = "Selected values: "
+      + Utils.joinOptions(m_BestClassifierOptions);
+    return result + '\n';
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new CVParameterSelection(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaClustering.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaClustering.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaClustering.java	(revision 29)
@@ -0,0 +1,517 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClassificationViaClustering.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.rules.ZeroR;
+import weka.clusterers.ClusterEvaluation;
+import weka.clusterers.Clusterer;
+import weka.clusterers.AbstractClusterer;
+import weka.clusterers.SimpleKMeans;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A simple meta-classifier that uses a clusterer for classification. For cluster algorithms that use a fixed number of clusterers, like SimpleKMeans, the user has to make sure that the number of clusters to generate are the same as the number of class labels in the dataset in order to obtain a useful model.<br/>
+ * <br/>
+ * Note: at prediction time, a missing value is returned if no cluster is found for the instance.<br/>
+ * <br/>
+ * The code is based on the 'clusters to classes' functionality of the weka.clusterers.ClusterEvaluation class by Mark Hall.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of clusterer.
+ *  (default: weka.clusterers.SimpleKMeans)</pre>
+ * 
+ * <pre> 
+ * Options specific to clusterer weka.clusterers.SimpleKMeans:
+ * </pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -V
+ *  Display std. deviations for centroids.
+ * </pre>
+ * 
+ * <pre> -M
+ *  Replace missing values with mean/mode.
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class ClassificationViaClustering
+  extends AbstractClassifier {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -5687069451420259135L;
+
+  /** the cluster algorithm used (template) */
+  protected Clusterer m_Clusterer;
+
+  /** the actual cluster algorithm being used */
+  protected Clusterer m_ActualClusterer;
+  
+  /** the original training data header */
+  protected Instances m_OriginalHeader;
+  
+  /** the modified training data header */
+  protected Instances m_ClusteringHeader;
+  
+  /** the mapping between clusters and classes */
+  protected double[] m_ClustersToClasses;
+  
+  /** the default model */
+  protected Classifier m_ZeroR;
+  
+  /**
+   * default constructor
+   */
+  public ClassificationViaClustering() {
+    super();
+    
+    m_Clusterer = new SimpleKMeans();
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A simple meta-classifier that uses a clusterer for classification. "
+      + "For cluster algorithms that use a fixed number of clusterers, like "
+      + "SimpleKMeans, the user has to make sure that the number of clusters "
+      + "to generate are the same as the number of class labels in the dataset "
+      + "in order to obtain a useful model.\n"
+      + "\n"
+      + "Note: at prediction time, a missing value is returned if no cluster "
+      + "is found for the instance.\n"
+      + "\n"
+      + "The code is based on the 'clusters to classes' functionality of the "
+      + "weka.clusterers.ClusterEvaluation class by Mark Hall.";
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions(){
+    Vector 		result;
+    Enumeration		enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tFull name of clusterer.\n"
+	+ "\t(default: " + defaultClustererString() +")",
+	"W", 1, "-W"));
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to clusterer "
+	+ m_Clusterer.getClass().getName() + ":"));
+    enm = ((OptionHandler) m_Clusterer).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       		i;
+    Vector<String>    	result;
+    String[]  		options;
+
+    result = new Vector<String>();
+
+    result.add("-W");
+    result.add("" + getClusterer().getClass().getName());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getClusterer() instanceof OptionHandler) {
+      result.add("--");
+      options = ((OptionHandler) getClusterer()).getOptions();
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of clusterer.
+   *  (default: weka.clusterers.SimpleKMeans)</pre>
+   * 
+   * <pre> 
+   * Options specific to clusterer weka.clusterers.SimpleKMeans:
+   * </pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -V
+   *  Display std. deviations for centroids.
+   * </pre>
+   * 
+   * <pre> -M
+   *  Replace missing values with mean/mode.
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) { 
+      // This is just to set the classifier in case the option 
+      // parsing fails.
+      setClusterer(AbstractClusterer.forName(tmpStr, null));
+      setClusterer(AbstractClusterer.forName(tmpStr, Utils.partitionOptions(options)));
+    }
+    else {
+      // This is just to set the classifier in case the option 
+      // parsing fails.
+      setClusterer(AbstractClusterer.forName(defaultClustererString(), null));
+      setClusterer(AbstractClusterer.forName(defaultClustererString(), Utils.partitionOptions(options)));
+    }
+  }
+
+  /**
+   * String describing default clusterer.
+   * 
+   * @return		the classname
+   */
+  protected String defaultClustererString() {
+    return SimpleKMeans.class.getName();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String clustererTipText() {
+    return "The clusterer to be used.";
+  }
+
+  /**
+   * Set the base clusterer.
+   *
+   * @param value 	the clusterer to use.
+   */
+  public void setClusterer(Clusterer value) {
+    m_Clusterer = value;
+  }
+
+  /**
+   * Get the clusterer used as the base learner.
+   *
+   * @return 		the current clusterer
+   */
+  public Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+
+  /**
+   * Classifies the given test instance.
+   *
+   * @param instance 	the instance to be classified
+   * @return 		the predicted most likely class for the instance or 
+   * 			Utils.missingValue() if no prediction is made
+   * @throws Exception 	if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    double	result;
+    double[]	values;
+    Instance	newInst;
+    int		i;
+    int		n;
+    
+    if (m_ZeroR != null) {
+      result = m_ZeroR.classifyInstance(instance);
+    }
+    else {
+      if (m_ActualClusterer != null) {
+	// build new instance
+	values = new double[m_ClusteringHeader.numAttributes()];
+	n = 0;
+	for (i = 0; i < instance.numAttributes(); i++) {
+	  if (i == instance.classIndex())
+	    continue;
+	  values[n] = instance.value(i);
+	  n++;
+	}
+	newInst = new DenseInstance(instance.weight(), values);
+	newInst.setDataset(m_ClusteringHeader);
+
+	// determine cluster/class
+	result = m_ClustersToClasses[m_ActualClusterer.clusterInstance(newInst)];
+	if (result == -1)
+	  result = Utils.missingValue();
+      }
+      else {
+	result = Utils.missingValue();
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    result = m_Clusterer.getCapabilities();
+    
+    // class
+    result.disableAllClasses();
+    result.disable(Capability.NO_CLASS);
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * builds the classifier
+   * 
+   * @param data        the training instances
+   * @throws Exception  if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    Instances		clusterData;
+    ClusterEvaluation	eval;
+    int			i;
+    Instance		instance;
+    int[][] 		counts;
+    int[] 		clusterTotals;
+    double[] 		best;
+    double[] 		current;
+    double[] 		clusterAssignments;
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    // save original header (needed for clusters to classes output)
+    m_OriginalHeader = new Instances(data, 0);
+    
+    // remove class attribute for clusterer
+    clusterData = new Instances(data);
+    clusterData.setClassIndex(-1);
+    clusterData.deleteAttributeAt(m_OriginalHeader.classIndex());
+    m_ClusteringHeader = new Instances(clusterData, 0);
+
+    if (m_ClusteringHeader.numAttributes() == 0) {
+      System.err.println("Data contains only class attribute, defaulting to ZeroR model.");
+      m_ZeroR = new ZeroR();
+      m_ZeroR.buildClassifier(data);
+    }
+    else {
+      m_ZeroR = null;
+      
+      // build clusterer
+      m_ActualClusterer = AbstractClusterer.makeCopy(m_Clusterer);
+      m_ActualClusterer.buildClusterer(clusterData);
+
+      // evaluate clusterer on training set
+      eval = new ClusterEvaluation();
+      eval.setClusterer(m_ActualClusterer);
+      eval.evaluateClusterer(clusterData);
+      clusterAssignments = eval.getClusterAssignments();
+
+      // determine classes-to-clusters mapping
+      counts        = new int [eval.getNumClusters()][m_OriginalHeader.numClasses()];
+      clusterTotals = new int[eval.getNumClusters()];
+      best          = new double[eval.getNumClusters()+1];
+      current       = new double[eval.getNumClusters()+1];
+      for (i = 0; i < data.numInstances(); i++) {
+	instance = data.instance(i);
+	counts[(int) clusterAssignments[i]][(int) instance.classValue()]++;
+	clusterTotals[(int) clusterAssignments[i]]++;
+	i++;
+      }
+      best[eval.getNumClusters()] = Double.MAX_VALUE;
+      ClusterEvaluation.mapClasses(eval.getNumClusters(), 0, counts, clusterTotals, current, best, 0);
+      m_ClustersToClasses = new double[best.length];
+      System.arraycopy(best, 0, m_ClustersToClasses, 0, best.length);
+    }
+  }
+
+  /**
+   * Returns a string representation of the classifier.
+   * 
+   * @return		a string representation of the classifier.
+   */
+  public String toString() {
+    StringBuffer	result;
+    int			i;
+    int			n;
+    boolean		found;
+    
+    result = new StringBuffer();
+    
+    // title
+    result.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+    result.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n");
+    
+    // model
+    if (m_ActualClusterer != null) {
+      // output clusterer
+      result.append(m_ActualClusterer + "\n");
+      
+      // clusters to classes
+      result.append("Clusters to classes mapping:\n");
+      for (i = 0; i < m_ClustersToClasses.length - 1; i++) {
+	result.append("  " + (i+1) + ". Cluster: ");
+	if (m_ClustersToClasses[i] < 0)
+	  result.append("no class");
+	else
+	  result.append(
+	      m_OriginalHeader.classAttribute().value((int) m_ClustersToClasses[i])
+	      + " (" + ((int) m_ClustersToClasses[i] + 1) + ")");
+	result.append("\n");
+      }
+      result.append("\n");
+      
+      // classes to clusters
+      result.append("Classes to clusters mapping:\n");
+      for (i = 0; i < m_OriginalHeader.numClasses(); i++) {
+	result.append(
+	    "  " + (i+1) + ". Class (" 
+	    + m_OriginalHeader.classAttribute().value(i) + "): ");
+	
+	found = false;
+	for (n = 0; n < m_ClustersToClasses.length - 1; n++) {
+	  if (((int) m_ClustersToClasses[n]) == i) {
+	    found = true;
+	    result.append((n+1) + ". Cluster");
+	    break;
+	  }
+	}
+	
+	if (!found)
+	  result.append("no cluster");
+
+	result.append("\n");
+      }
+      
+      result.append("\n");
+    }
+    else {
+      result.append("no model built yet\n");
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Runs the classifier with the given options
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    runClassifier(new ClassificationViaClustering(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ClassificationViaRegression.java	(revision 29)
@@ -0,0 +1,283 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassificationViaRegression.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for doing classification using regression methods. Class is binarized and one regression model is built for each class value. For more information, see, for example<br/>
+ * <br/>
+ * E. Frank, Y. Wang, S. Inglis, G. Holmes, I.H. Witten (1998). Using model trees for classification. Machine Learning. 32(1):63-76.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Frank1998,
+ *    author = {E. Frank and Y. Wang and S. Inglis and G. Holmes and I.H. Witten},
+ *    journal = {Machine Learning},
+ *    number = {1},
+ *    pages = {63-76},
+ *    title = {Using model trees for classification},
+ *    volume = {32},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.M5P)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.M5P:
+ * </pre>
+ * 
+ * <pre> -N
+ *  Use unpruned tree/rules</pre>
+ * 
+ * <pre> -U
+ *  Use unsmoothed predictions</pre>
+ * 
+ * <pre> -R
+ *  Build regression tree/rule rather than a model tree/rule</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf
+ *  (default 4)</pre>
+ * 
+ * <pre> -L
+ *  Save instances at the nodes in
+ *  the tree (for visualization purposes)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+*/
+public class ClassificationViaRegression 
+  extends SingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4500023123618669859L;
+  
+  /** The classifiers. (One for each class.) */
+  private Classifier[] m_Classifiers;
+
+  /** The filters used to transform the class. */
+  private MakeIndicator[] m_ClassFilters;
+
+  /**
+   * Default constructor.
+   */
+  public ClassificationViaRegression() {
+    
+    m_Classifier = new weka.classifiers.trees.M5P();
+  }
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for doing classification using regression methods. Class is "
+      + "binarized and one regression model is built for each class value. For more "
+      + "information, see, for example\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "E. Frank and Y. Wang and S. Inglis and G. Holmes and I.H. Witten");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Using model trees for classification");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "32");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "63-76");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.M5P";
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifiers.
+   *
+   * @param insts the training data.
+   * @throws Exception if a classifier can't be built
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    Instances newInsts;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, insts.numClasses());
+    m_ClassFilters = new MakeIndicator[insts.numClasses()];
+    for (int i = 0; i < insts.numClasses(); i++) {
+      m_ClassFilters[i] = new MakeIndicator();
+      m_ClassFilters[i].setAttributeIndex("" + (insts.classIndex() + 1));
+      m_ClassFilters[i].setValueIndex(i);
+      m_ClassFilters[i].setNumeric(true);
+      m_ClassFilters[i].setInputFormat(insts);
+      newInsts = Filter.useFilter(insts, m_ClassFilters[i]);
+      m_Classifiers[i].buildClassifier(newInsts);
+    }
+  }
+
+  /**
+   * Returns the distribution for an instance.
+   *
+   * @param inst the instance to get the distribution for
+   * @return the computed distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    
+    double[] probs = new double[inst.numClasses()];
+    Instance newInst;
+    double sum = 0;
+
+    for (int i = 0; i < inst.numClasses(); i++) {
+      m_ClassFilters[i].input(inst);
+      m_ClassFilters[i].batchFinished();
+      newInst = m_ClassFilters[i].output();
+      probs[i] = m_Classifiers[i].classifyInstance(newInst);
+      if (probs[i] > 1) {
+        probs[i] = 1;
+      }
+      if (probs[i] < 0){
+	probs[i] = 0;
+      }
+      sum += probs[i];
+    }
+    if (sum != 0) {
+      Utils.normalize(probs, sum);
+    } 
+    return probs;
+  }
+
+  /**
+   * Prints the classifiers.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifiers == null) {
+      return "Classification via Regression: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("Classification via Regression\n\n");
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      text.append("Classifier for class with index " + i + ":\n\n");
+      text.append(m_Classifiers[i].toString() + "\n\n");
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options for the learner
+   */
+  public static void main(String [] argv){
+    runClassifier(new ClassificationViaRegression(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/CostSensitiveClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/CostSensitiveClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/CostSensitiveClassifier.java	(revision 29)
@@ -0,0 +1,669 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostSensitiveClassifier.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.CostMatrix;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A metaclassifier that makes its base classifier cost-sensitive. Two methods can be used to introduce cost-sensitivity: reweighting training instances according to the total cost assigned to each class; or predicting the class with minimum expected misclassification cost (rather than the most likely class). Performance can often be improved by using a Bagged classifier to improve the probability estimates of the base classifier.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M
+ *  Minimize expected misclassification cost. Default is to
+ *  reweight training instances according to costs per class</pre>
+ * 
+ * <pre> -C &lt;cost file name&gt;
+ *  File name of a cost matrix to use. If this is not supplied,
+ *  a cost matrix will be loaded on demand. The name of the
+ *  on-demand file is the relation name of the training data
+ *  plus ".cost", and the path to the on-demand file is
+ *  specified with the -N option.</pre>
+ * 
+ * <pre> -N &lt;directory&gt;
+ *  Name of a directory to search for cost files when loading
+ *  costs on demand (default current directory).</pre>
+ * 
+ * <pre> -cost-matrix &lt;matrix&gt;
+ *  The cost matrix in Matlab single line format.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5928 $
+ */
+public class CostSensitiveClassifier 
+  extends RandomizableSingleClassifierEnhancer
+  implements OptionHandler, Drawable {
+
+  /** for serialization */
+  static final long serialVersionUID = -720658209263002404L;
+  
+  /** load cost matrix on demand */
+  public static final int MATRIX_ON_DEMAND = 1;
+  /** use explicit cost matrix */
+  public static final int MATRIX_SUPPLIED = 2;
+  /** Specify possible sources of the cost matrix */
+  public static final Tag [] TAGS_MATRIX_SOURCE = {
+    new Tag(MATRIX_ON_DEMAND, "Load cost matrix on demand"),
+    new Tag(MATRIX_SUPPLIED, "Use explicit cost matrix")
+  };
+
+  /** Indicates the current cost matrix source */
+  protected int m_MatrixSource = MATRIX_ON_DEMAND;
+
+  /** 
+   * The directory used when loading cost files on demand, null indicates
+   * current directory 
+   */
+  protected File m_OnDemandDirectory = new File(System.getProperty("user.dir"));
+
+  /** The name of the cost file, for command line options */
+  protected String m_CostFile;
+
+  /** The cost matrix */
+  protected CostMatrix m_CostMatrix = new CostMatrix(1);
+
+  /** 
+   * True if the costs should be used by selecting the minimum expected
+   * cost (false means weight training data by the costs)
+   */
+  protected boolean m_MinimizeExpectedCost;
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname 
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.rules.ZeroR";
+  }
+
+  /**
+   * Default constructor.
+   */
+  public CostSensitiveClassifier() {
+    m_Classifier = new weka.classifiers.rules.ZeroR();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+	      "\tMinimize expected misclassification cost. Default is to\n"
+	      +"\treweight training instances according to costs per class",
+	      "M", 0, "-M"));
+    newVector.addElement(new Option(
+	      "\tFile name of a cost matrix to use. If this is not supplied,\n"
+              +"\ta cost matrix will be loaded on demand. The name of the\n"
+              +"\ton-demand file is the relation name of the training data\n"
+              +"\tplus \".cost\", and the path to the on-demand file is\n"
+              +"\tspecified with the -N option.",
+	      "C", 1, "-C <cost file name>"));
+    newVector.addElement(new Option(
+              "\tName of a directory to search for cost files when loading\n"
+              +"\tcosts on demand (default current directory).",
+              "N", 1, "-N <directory>"));
+    newVector.addElement(new Option(
+              "\tThe cost matrix in Matlab single line format.",
+              "cost-matrix", 1, "-cost-matrix <matrix>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M
+   *  Minimize expected misclassification cost. Default is to
+   *  reweight training instances according to costs per class</pre>
+   * 
+   * <pre> -C &lt;cost file name&gt;
+   *  File name of a cost matrix to use. If this is not supplied,
+   *  a cost matrix will be loaded on demand. The name of the
+   *  on-demand file is the relation name of the training data
+   *  plus ".cost", and the path to the on-demand file is
+   *  specified with the -N option.</pre>
+   * 
+   * <pre> -N &lt;directory&gt;
+   *  Name of a directory to search for cost files when loading
+   *  costs on demand (default current directory).</pre>
+   * 
+   * <pre> -cost-matrix &lt;matrix&gt;
+   *  The cost matrix in Matlab single line format.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setMinimizeExpectedCost(Utils.getFlag('M', options));
+
+    String costFile = Utils.getOption('C', options);
+    if (costFile.length() != 0) {
+      try {
+	setCostMatrix(new CostMatrix(new BufferedReader(
+				     new FileReader(costFile))));
+      } catch (Exception ex) {
+	// now flag as possible old format cost matrix. Delay cost matrix
+	// loading until buildClassifer is called
+	setCostMatrix(null);
+      }
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+      m_CostFile = costFile;
+    } else {
+      setCostMatrixSource(new SelectedTag(MATRIX_ON_DEMAND, 
+                                          TAGS_MATRIX_SOURCE));
+    }
+    
+    String demandDir = Utils.getOption('N', options);
+    if (demandDir.length() != 0) {
+      setOnDemandDirectory(new File(demandDir));
+    }
+
+    String cost_matrix = Utils.getOption("cost-matrix", options);
+    if (cost_matrix.length() != 0) {
+      StringWriter writer = new StringWriter();
+      CostMatrix.parseMatlab(cost_matrix).write(writer);
+      setCostMatrix(new CostMatrix(new StringReader(writer.toString())));
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+    }
+    
+    super.setOptions(options);
+  }
+
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 7];
+
+    int current = 0;
+
+    if (m_MatrixSource == MATRIX_SUPPLIED) {
+      if (m_CostFile != null) {
+        options[current++] = "-C";
+        options[current++] = "" + m_CostFile;
+      }
+      else {
+        options[current++] = "-cost-matrix";
+        options[current++] = getCostMatrix().toMatlab();
+      }
+    } else {
+      options[current++] = "-N";
+      options[current++] = "" + getOnDemandDirectory();
+    }
+
+    if (getMinimizeExpectedCost()) {
+      options[current++] = "-M";
+    }
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    while (current < options.length) {
+      if (options[current] == null) {
+        options[current] = "";
+      }
+      current++;
+    }
+
+    return options;
+  }
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A metaclassifier that makes its base classifier cost-sensitive. "
+      + "Two methods can be used to introduce cost-sensitivity: reweighting "
+      + "training instances according to the total cost assigned to each "
+      + "class; or predicting the class with minimum expected "
+      + "misclassification cost (rather than the most likely class). "
+      + "Performance can often be "
+      + "improved by using a Bagged classifier to improve the probability "
+      + "estimates of the base classifier.";
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixSourceTipText() {
+
+    return "Sets where to get the cost matrix. The two options are"
+      + "to use the supplied explicit cost matrix (the setting of the "
+      + "costMatrix property), or to load a cost matrix from a file when "
+      + "required (this file will be loaded from the directory set by the "
+      + "onDemandDirectory property and will be named relation_name" 
+      + CostMatrix.FILE_EXTENSION + ").";
+  }
+
+  /**
+   * Gets the source location method of the cost matrix. Will be one of
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED.
+   *
+   * @return the cost matrix source.
+   */
+  public SelectedTag getCostMatrixSource() {
+
+    return new SelectedTag(m_MatrixSource, TAGS_MATRIX_SOURCE);
+  }
+  
+  /**
+   * Sets the source location of the cost matrix. Values other than
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED will be ignored.
+   *
+   * @param newMethod the cost matrix location method.
+   */
+  public void setCostMatrixSource(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_MATRIX_SOURCE) {
+      m_MatrixSource = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String onDemandDirectoryTipText() {
+
+    return "Sets the directory where cost files are loaded from. This option "
+      + "is used when the costMatrixSource is set to \"On Demand\".";
+  }
+
+  /**
+   * Returns the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @return The cost file search directory.
+   */
+  public File getOnDemandDirectory() {
+
+    return m_OnDemandDirectory;
+  }
+
+  /**
+   * Sets the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @param newDir The cost file search directory.
+   */
+  public void setOnDemandDirectory(File newDir) {
+
+    if (newDir.isDirectory()) {
+      m_OnDemandDirectory = newDir;
+    } else {
+      m_OnDemandDirectory = new File(newDir.getParent());
+    }
+    m_MatrixSource = MATRIX_ON_DEMAND;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minimizeExpectedCostTipText() {
+
+    return "Sets whether the minimum expected cost criteria will be used. If "
+      + "this is false, the training data will be reweighted according to the "
+      + "costs assigned to each class. If true, the minimum expected cost "
+      + "criteria will be used.";
+  }
+
+  /**
+   * Gets the value of MinimizeExpectedCost.
+   *
+   * @return Value of MinimizeExpectedCost.
+   */
+  public boolean getMinimizeExpectedCost() {
+    
+    return m_MinimizeExpectedCost;
+  }
+  
+  /**
+   * Set the value of MinimizeExpectedCost.
+   *
+   * @param newMinimizeExpectedCost Value to assign to MinimizeExpectedCost.
+   */
+  public void setMinimizeExpectedCost(boolean newMinimizeExpectedCost) {
+    
+    m_MinimizeExpectedCost = newMinimizeExpectedCost;
+  }
+  
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier
+   *
+   * @return the classifier string.
+   */
+  protected String getClassifierSpec() {
+    
+    Classifier c = getClassifier();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+  
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixTipText() {
+    return "Sets the cost matrix explicitly. This matrix is used if the "
+      + "costMatrixSource property is set to \"Supplied\".";
+  }
+
+  /**
+   * Gets the misclassification cost matrix.
+   *
+   * @return the cost matrix
+   */
+  public CostMatrix getCostMatrix() {
+    
+    return m_CostMatrix;
+  }
+  
+  /**
+   * Sets the misclassification cost matrix.
+   *
+   * @param newCostMatrix the cost matrix
+   */
+  public void setCostMatrix(CostMatrix newCostMatrix) {
+    
+    m_CostMatrix = newCostMatrix;
+    m_MatrixSource = MATRIX_SUPPLIED;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the model of the base learner.
+   *
+   * @param data the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("No base classifier has been set!");
+    }
+    if (m_MatrixSource == MATRIX_ON_DEMAND) {
+      String costName = data.relationName() + CostMatrix.FILE_EXTENSION;
+      File costFile = new File(getOnDemandDirectory(), costName);
+      if (!costFile.exists()) {
+        throw new Exception("On-demand cost file doesn't exist: " + costFile);
+      }
+      setCostMatrix(new CostMatrix(new BufferedReader(
+                                   new FileReader(costFile))));
+    } else if (m_CostMatrix == null) {
+      // try loading an old format cost file
+      m_CostMatrix = new CostMatrix(data.numClasses());
+      m_CostMatrix.readOldFormat(new BufferedReader(
+			       new FileReader(m_CostFile)));
+    }
+
+    if (!m_MinimizeExpectedCost) {
+      Random random = null;
+      if (!(m_Classifier instanceof WeightedInstancesHandler)) {
+	random = new Random(m_Seed);
+      }
+      data = m_CostMatrix.applyCostMatrix(data, random);      
+    }
+    m_Classifier.buildClassifier(data);
+  }
+
+  /**
+   * Returns class probabilities. When minimum expected cost approach is chosen,
+   * returns probability one for class with the minimum expected misclassification
+   * cost. Otherwise it returns the probability distribution returned by
+   * the base classifier.
+   *
+   * @param instance the instance to be classified
+   * @return the computed distribution for the given instance
+   * @throws Exception if instance could not be classified
+   * successfully */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    if (!m_MinimizeExpectedCost) {
+      return m_Classifier.distributionForInstance(instance);
+    }
+    double [] pred = m_Classifier.distributionForInstance(instance);
+    double [] costs = m_CostMatrix.expectedCosts(pred, instance);
+    /*
+    for (int i = 0; i < pred.length; i++) {
+      System.out.print(pred[i] + " ");
+    }
+    System.out.println();
+    for (int i = 0; i < costs.length; i++) {
+      System.out.print(costs[i] + " ");
+    }
+    System.out.println("\n");
+    */
+
+    // This is probably not ideal
+    int classIndex = Utils.minIndex(costs);
+    for (int i = 0; i  < pred.length; i++) {
+      if (i == classIndex) {
+	pred[i] = 1.0;
+      } else {
+	pred[i] = 0.0;
+      }
+    }
+    return pred; 
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  
+   *  @return the type of graph this classifier represents
+   */   
+  public int graphType() {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graphType();
+    else 
+      return Drawable.NOT_DRAWABLE;
+  }
+
+  /**
+   * Returns graph describing the classifier (if possible).
+   *
+   * @return the graph of the classifier in dotty format
+   * @throws Exception if the classifier cannot be graphed
+   */
+  public String graph() throws Exception {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graph();
+    else throw new Exception("Classifier: " + getClassifierSpec()
+			     + " cannot be graphed");
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifier == null) {
+      return "CostSensitiveClassifier: No model built yet.";
+    }
+
+    String result = "CostSensitiveClassifier using ";
+      if (m_MinimizeExpectedCost) {
+	result += "minimized expected misclasification cost\n";
+      } else {
+	result += "reweighted training instances\n";
+      }
+      result += "\n" + getClassifierSpec()
+	+ "\n\nClassifier Model\n"
+	+ m_Classifier.toString()
+	+ "\n\nCost Matrix\n"
+	+ m_CostMatrix.toString();
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new CostSensitiveClassifier(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Dagging.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Dagging.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Dagging.java	(revision 29)
@@ -0,0 +1,574 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Dagging.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This meta classifier creates a number of disjoint, stratified folds out of the data and feeds each chunk of data to a copy of the supplied base classifier. Predictions are made via majority vote, since all the generated base classifiers are put into the Vote meta classifier. <br/>
+ * Useful for base classifiers that are quadratic or worse in time behavior, regarding number of instances in the training data. <br/>
+ * <br/>
+ * For more information, see: <br/>
+ * Ting, K. M., Witten, I. H.: Stacking Bagged and Dagged Models. In: Fourteenth international Conference on Machine Learning, San Francisco, CA, 367-375, 1997.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Ting1997,
+ *    address = {San Francisco, CA},
+ *    author = {Ting, K. M. and Witten, I. H.},
+ *    booktitle = {Fourteenth international Conference on Machine Learning},
+ *    editor = {D. H. Fisher},
+ *    pages = {367-375},
+ *    publisher = {Morgan Kaufmann Publishers},
+ *    title = {Stacking Bagged and Dagged Models},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;folds&gt;
+ *  The number of folds for splitting the training set into
+ *  smaller chunks for the base classifier.
+ *  (default 10)</pre>
+ * 
+ * <pre> -verbose
+ *  Whether to print some more information during building the
+ *  classifier.
+ *  (default is off)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.functions.SMO)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.functions.SMO:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  Turning them off assumes that data is purely numeric, doesn't
+ *  contain any missing values, and has a nominal class. Turning them
+ *  off also means that no header information will be stored if the
+ *  machine is linear. Finally, it also assumes that no instance has
+ *  a weight equal to 0.
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  The complexity constant C. (default 1)</pre>
+ * 
+ * <pre> -N
+ *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The tolerance parameter. (default 1.0e-3)</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error. (default 1.0e-12)</pre>
+ * 
+ * <pre> -M
+ *  Fit logistic models to SVM outputs. </pre>
+ * 
+ * <pre> -V &lt;double&gt;
+ *  The number of folds for the internal
+ *  cross-validation. (default -1, use training data)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed. (default 1)</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p/>
+ *
+ * @author Bernhard Pfahringer (bernhard at cs dot waikato dot ac dot nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ * @see       Vote
+ */
+public class Dagging
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4560165876570074309L;
+
+  /** the number of folds to use to split the training data */
+  protected int m_NumFolds = 10;
+
+  /** the classifier used for voting */
+  protected Vote m_Vote = null;
+
+  /** whether to output some progress information during building */
+  protected boolean m_Verbose = false;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+     "This meta classifier creates a number of disjoint, stratified folds out "
+     + "of the data and feeds each chunk of data to a copy of the supplied "
+     + "base classifier. Predictions are made via averaging, since all the "
+     + "generated base classifiers are put into the Vote meta classifier. \n"
+     + "Useful for base classifiers that are quadratic or worse in time "
+     + "behavior, regarding number of instances in the training data. \n"
+     + "\n"
+     + "For more information, see: \n"
+     + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ting, K. M. and Witten, I. H.");
+    result.setValue(Field.TITLE, "Stacking Bagged and Dagged Models");
+    result.setValue(Field.BOOKTITLE, "Fourteenth international Conference on Machine Learning");
+    result.setValue(Field.EDITOR, "D. H. Fisher");
+    result.setValue(Field.YEAR, "1997");
+    result.setValue(Field.PAGES, "367-375");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+    result.setValue(Field.ADDRESS, "San Francisco, CA");
+    
+    return result;
+  }
+    
+  /**
+   * Constructor.
+   */
+  public Dagging() {
+    m_Classifier = new weka.classifiers.functions.SMO();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    return weka.classifiers.functions.SMO.class.getName();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+        "\tThe number of folds for splitting the training set into\n"
+        + "\tsmaller chunks for the base classifier.\n"
+        + "\t(default 10)",
+        "F", 1, "-F <folds>"));
+    
+    result.addElement(new Option(
+        "\tWhether to print some more information during building the\n"
+        + "\tclassifier.\n"
+        + "\t(default is off)",
+        "verbose", 0, "-verbose"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+      
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;folds&gt;
+   *  The number of folds for splitting the training set into
+   *  smaller chunks for the base classifier.
+   *  (default 10)</pre>
+   * 
+   * <pre> -verbose
+   *  Whether to print some more information during building the
+   *  classifier.
+   *  (default is off)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.functions.SMO)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.functions.SMO:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  Turning them off assumes that data is purely numeric, doesn't
+   *  contain any missing values, and has a nominal class. Turning them
+   *  off also means that no header information will be stored if the
+   *  machine is linear. Finally, it also assumes that no instance has
+   *  a weight equal to 0.
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  The complexity constant C. (default 1)</pre>
+   * 
+   * <pre> -N
+   *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The tolerance parameter. (default 1.0e-3)</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error. (default 1.0e-12)</pre>
+   * 
+   * <pre> -M
+   *  Fit logistic models to SVM outputs. </pre>
+   * 
+   * <pre> -V &lt;double&gt;
+   *  The number of folds for the internal
+   *  cross-validation. (default -1, use training data)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed. (default 1)</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setNumFolds(Integer.parseInt(tmpStr));
+    else
+      setNumFolds(10);
+    
+    setVerbose(Utils.getFlag("verbose", options));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+
+    result.add("-F");
+    result.add("" + getNumFolds());
+    
+    if (getVerbose())
+      result.add("-verbose");
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Gets the number of folds to use for splitting the training set.
+   *
+   * @return the number of folds
+   */
+  public int getNumFolds() {
+    return m_NumFolds;
+  }
+  
+  /**
+   * Sets the number of folds to use for splitting the training set.
+   *
+   * @param value     the new number of folds
+   */
+  public void setNumFolds(int value) {
+    if (value > 0)
+      m_NumFolds = value;
+    else
+      System.out.println(
+          "At least 1 fold is necessary (provided: " + value + ")!");
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds to use for splitting the training set into smaller chunks for the base classifier.";
+  }
+  
+  /**
+   * Set the verbose state.
+   *
+   * @param value the verbose state
+   */
+  public void setVerbose(boolean value) {
+    m_Verbose = value;
+  }
+  
+  /**
+   * Gets the verbose state
+   *
+   * @return the verbose state
+   */
+  public boolean getVerbose() {
+    return m_Verbose;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String verboseTipText() {
+    return "Whether to ouput some additional information during building.";
+  }
+
+  /**
+   * Bagging method.
+   *
+   * @param data the training data to be used for generating the
+   * bagged classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    Classifier[]        base;
+    int                 i;
+    int                 n;
+    int                 fromIndex;
+    int                 toIndex;
+    Instances           train;
+    double              chunkSize;
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    m_Vote    = new Vote();
+    base      = new Classifier[getNumFolds()];
+    chunkSize = (double) data.numInstances() / (double) getNumFolds();
+    
+    // stratify data
+    if (getNumFolds() > 1) {
+      data.randomize(data.getRandomNumberGenerator(getSeed()));
+      data.stratify(getNumFolds());
+    }
+
+    // generate <folds> classifiers
+    for (i = 0; i < getNumFolds(); i++) {
+      base[i] = makeCopy(getClassifier());
+
+      // generate training data
+      if (getNumFolds() > 1) {
+        // some progress information
+        if (getVerbose())
+          System.out.print(".");
+        
+        train     = data.testCV(getNumFolds(), i);
+      }
+      else {
+        train = data;
+      }
+
+      // train classifier
+      base[i].buildClassifier(train);
+    }
+    
+    // init vote
+    m_Vote.setClassifiers(base);
+    
+    if (getVerbose())
+      System.out.println();
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    return m_Vote.distributionForInstance(instance);
+  }
+
+  /**
+   * Returns description of the classifier.
+   *
+   * @return description of the classifier as a string
+   */
+  public String toString() {
+    if (m_Vote == null)
+      return this.getClass().getName().replaceAll(".*\\.", "") 
+             + ": No model built yet.";
+    else
+      return m_Vote.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new Dagging(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Decorate.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Decorate.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Decorate.java	(revision 29)
@@ -0,0 +1,789 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Decorate.java
+ *    Copyright (C) 2002 Prem Melville
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.UnsupportedClassTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * DECORATE is a meta-learner for building diverse ensembles of classifiers by using specially constructed artificial training examples. Comprehensive experiments have demonstrated that this technique is consistently more accurate than the base classifier, Bagging and Random Forests.Decorate also obtains higher accuracy than Boosting on small training sets, and achieves comparable performance on larger training sets. <br/>
+ * <br/>
+ * For more details see: <br/>
+ * <br/>
+ * P. Melville, R. J. Mooney: Constructing Diverse Classifier Ensembles Using Artificial Training Examples. In: Eighteenth International Joint Conference on Artificial Intelligence, 505-510, 2003.<br/>
+ * <br/>
+ * P. Melville, R. J. Mooney (2004). Creating Diversity in Ensembles Using Artificial Data. Information Fusion: Special Issue on Diversity in Multiclassifier Systems..
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Melville2003,
+ *    author = {P. Melville and R. J. Mooney},
+ *    booktitle = {Eighteenth International Joint Conference on Artificial Intelligence},
+ *    pages = {505-510},
+ *    title = {Constructing Diverse Classifier Ensembles Using Artificial Training Examples},
+ *    year = {2003}
+ * }
+ * 
+ * &#64;article{Melville2004,
+ *    author = {P. Melville and R. J. Mooney},
+ *    journal = {Information Fusion: Special Issue on Diversity in Multiclassifier Systems},
+ *    note = {submitted},
+ *    title = {Creating Diversity in Ensembles Using Artificial Data},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E
+ *  Desired size of ensemble.
+ *  (default 10)</pre>
+ * 
+ * <pre> -R
+ *  Factor that determines number of artificial examples to generate.
+ *  Specified proportional to training set size.
+ *  (default 1.0)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Prem Melville (melville@cs.utexas.edu)
+ * @version $Revision: 5987 $ 
+ */
+public class Decorate 
+    extends RandomizableIteratedSingleClassifierEnhancer
+    implements TechnicalInformationHandler {
+      
+    /** for serialization */
+    static final long serialVersionUID = -6020193348750269931L;
+  
+    /** Vector of classifiers that make up the committee/ensemble. */
+    protected Vector m_Committee = null;
+    
+    /** The desired ensemble size. */
+    protected int m_DesiredSize = 10;
+    
+    /** Amount of artificial/random instances to use - specified as a
+        fraction of the training data size. */
+    protected double m_ArtSize = 1.0 ;
+
+    /** The random number generator. */
+    protected Random m_Random = new Random(0);
+    
+    /** Attribute statistics - used for generating artificial examples. */
+    protected Vector m_AttributeStats = null;
+
+    
+  /**
+   * Constructor.
+   */
+  public Decorate() {
+    
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+    
+    /**
+     * Returns an enumeration describing the available options
+     *
+     * @return an enumeration of all the available options
+     */
+    public Enumeration listOptions() {
+	Vector newVector = new Vector(8);
+
+	newVector.addElement(new Option(
+              "\tDesired size of ensemble.\n" 
+              + "\t(default 10)",
+              "E", 1, "-E"));
+	newVector.addElement(new Option(
+ 	    "\tFactor that determines number of artificial examples to generate.\n"
+           +"\tSpecified proportional to training set size.\n" 
+          + "\t(default 1.0)",
+	    "R", 1, "-R"));
+
+        Enumeration enu = super.listOptions();
+        while (enu.hasMoreElements()) {
+          newVector.addElement(enu.nextElement());
+        }
+        return newVector.elements();
+    }
+
+    
+    /**
+     * Parses a given list of options. <p/>
+     *   
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -E
+     *  Desired size of ensemble.
+     *  (default 10)</pre>
+     * 
+     * <pre> -R
+     *  Factor that determines number of artificial examples to generate.
+     *  Specified proportional to training set size.
+     *  (default 1.0)</pre>
+     * 
+     * <pre> -S &lt;num&gt;
+     *  Random number seed.
+     *  (default 1)</pre>
+     * 
+     * <pre> -I &lt;num&gt;
+     *  Number of iterations.
+     *  (default 10)</pre>
+     * 
+     * <pre> -D
+     *  If set, classifier is run in debug mode and
+     *  may output additional info to the console</pre>
+     * 
+     * <pre> -W
+     *  Full name of base classifier.
+     *  (default: weka.classifiers.trees.J48)</pre>
+     * 
+     * <pre> 
+     * Options specific to classifier weka.classifiers.trees.J48:
+     * </pre>
+     * 
+     * <pre> -U
+     *  Use unpruned tree.</pre>
+     * 
+     * <pre> -C &lt;pruning confidence&gt;
+     *  Set confidence threshold for pruning.
+     *  (default 0.25)</pre>
+     * 
+     * <pre> -M &lt;minimum number of instances&gt;
+     *  Set minimum number of instances per leaf.
+     *  (default 2)</pre>
+     * 
+     * <pre> -R
+     *  Use reduced error pruning.</pre>
+     * 
+     * <pre> -N &lt;number of folds&gt;
+     *  Set number of folds for reduced error
+     *  pruning. One fold is used as pruning set.
+     *  (default 3)</pre>
+     * 
+     * <pre> -B
+     *  Use binary splits only.</pre>
+     * 
+     * <pre> -S
+     *  Don't perform subtree raising.</pre>
+     * 
+     * <pre> -L
+     *  Do not clean up after the tree has been built.</pre>
+     * 
+     * <pre> -A
+     *  Laplace smoothing for predicted probabilities.</pre>
+     * 
+     * <pre> -Q &lt;seed&gt;
+     *  Seed for random data shuffling (default 1).</pre>
+     * 
+     <!-- options-end -->
+     *
+     * Options after -- are passed to the designated classifier.<p>
+     *
+     * @param options the list of options as an array of strings
+     * @throws Exception if an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+
+	String desiredSize = Utils.getOption('E', options);
+	if (desiredSize.length() != 0) {
+	    setDesiredSize(Integer.parseInt(desiredSize));
+	} else {
+	    setDesiredSize(10);
+	}
+	
+	String artSize = Utils.getOption('R', options);
+	if (artSize.length() != 0) {
+	    setArtificialSize(Double.parseDouble(artSize));
+	} else {
+	    setArtificialSize(1.0);
+	}
+
+        super.setOptions(options);
+    }
+    
+    /**
+     * Gets the current settings of the Classifier.
+     *
+     * @return an array of strings suitable for passing to setOptions
+     */
+    public String [] getOptions() {
+
+      String [] superOptions = super.getOptions();
+      String [] options = new String [superOptions.length + 4];
+      
+      int current = 0;
+      options[current++] = "-E"; options[current++] = "" + getDesiredSize();
+      options[current++] = "-R"; options[current++] = "" + getArtificialSize();
+      
+      System.arraycopy(superOptions, 0, options, current, 
+                       superOptions.length);
+      
+      current += superOptions.length;
+      while (current < options.length) {
+        options[current++] = "";
+      }
+      return options;
+    }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String desiredSizeTipText() {
+      return "the desired number of member classifiers in the Decorate ensemble. Decorate may terminate "
+	+"before this size is reached (depending on the value of numIterations). "
+	+"Larger ensemble sizes usually lead to more accurate models, but increases "
+	+"training time and model complexity.";
+  }
+    
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "the maximum number of Decorate iterations to run. Each iteration generates a classifier, "
+	+"but does not necessarily add it to the ensemble. Decorate stops when the desired ensemble "
+	+"size is reached. This parameter should be greater than "
+	+"equal to the desiredSize. If the desiredSize is not being reached it may help to "
+	+"increase this value.";
+  }
+    
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String artificialSizeTipText() {
+    return "determines the number of artificial examples to use during training. Specified as "
+	+"a proportion of the training data. Higher values can increase ensemble diversity.";
+  }
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+      return "DECORATE is a meta-learner for building diverse ensembles of "
+	  +"classifiers by using specially constructed artificial training "
+	  +"examples. Comprehensive experiments have demonstrated that this "
+	  +"technique is consistently more accurate than the base classifier, Bagging and Random Forests."
+	  +"Decorate also obtains higher accuracy than Boosting on small training sets, and achieves "
+	  +"comparable performance on larger training sets. \n\n"
+	  +"For more details see: \n\n"
+	  + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "P. Melville and R. J. Mooney");
+    result.setValue(Field.TITLE, "Constructing Diverse Classifier Ensembles Using Artificial Training Examples");
+    result.setValue(Field.BOOKTITLE, "Eighteenth International Joint Conference on Artificial Intelligence");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.PAGES, "505-510");
+
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "P. Melville and R. J. Mooney");
+    additional.setValue(Field.TITLE, "Creating Diversity in Ensembles Using Artificial Data");
+    additional.setValue(Field.JOURNAL, "Information Fusion: Special Issue on Diversity in Multiclassifier Systems");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.NOTE, "submitted");
+    
+    return result;
+  }
+
+    /**
+     * Factor that determines number of artificial examples to generate.
+     *
+     * @return factor that determines number of artificial examples to generate
+     */
+    public double getArtificialSize() {
+	return m_ArtSize;
+    }
+  
+    /**
+     * Sets factor that determines number of artificial examples to generate.
+     *
+     * @param newArtSize factor that determines number of artificial examples to generate
+     */
+    public void setArtificialSize(double newArtSize) {
+	m_ArtSize = newArtSize;
+    }
+    
+    /**
+     * Gets the desired size of the committee.
+     *
+     * @return the desired size of the committee
+     */
+    public int getDesiredSize() {
+	return m_DesiredSize;
+    }
+    
+    /**
+     * Sets the desired size of the committee.
+     *
+     * @param newDesiredSize the desired size of the committee
+     */
+    public void setDesiredSize(int newDesiredSize) {
+	m_DesiredSize = newDesiredSize;
+    }
+
+    /**
+     * Returns default capabilities of the classifier.
+     *
+     * @return      the capabilities of this classifier
+     */
+    public Capabilities getCapabilities() {
+      Capabilities result = super.getCapabilities();
+
+      // class
+      result.disableAllClasses();
+      result.disableAllClassDependencies();
+      result.enable(Capability.NOMINAL_CLASS);
+
+      // instances
+      result.setMinimumNumberInstances(m_DesiredSize);
+
+      return result;
+    }
+
+    /**
+     * Build Decorate classifier
+     *
+     * @param data the training data to be used for generating the classifier
+     * @throws Exception if the classifier could not be built successfully
+     */
+    public void buildClassifier(Instances data) throws Exception {
+      if(m_Classifier == null) {
+        throw new Exception("A base classifier has not been specified!");
+      }
+      
+      // can classifier handle the data?
+      getCapabilities().testWithFail(data);
+      
+      // remove instances with missing class
+      data = new Instances(data);
+      data.deleteWithMissingClass();
+  
+	//initialize random number generator
+	if(m_Seed==-1) m_Random = new Random();
+	else m_Random = new Random(m_Seed);
+	
+	int i = 1;//current committee size
+	int numTrials = 1;//number of Decorate iterations 
+	Instances divData = new Instances(data);//local copy of data - diversity data
+	Instances artData = null;//artificial data
+
+	//compute number of artficial instances to add at each iteration
+	int artSize = (int) (Math.abs(m_ArtSize)*divData.numInstances());
+	if(artSize==0) artSize=1;//atleast add one random example
+	computeStats(data);//Compute training data stats for creating artificial examples
+	
+	//initialize new committee
+	m_Committee = new Vector();
+	Classifier newClassifier = m_Classifier;
+	newClassifier.buildClassifier(divData);
+	m_Committee.add(newClassifier);
+	double eComm = computeError(divData);//compute ensemble error
+	if(m_Debug) System.out.println("Initialize:\tClassifier "+i+" added to ensemble. Ensemble error = "+eComm);
+	
+	//repeat till desired committee size is reached OR the max number of iterations is exceeded 
+	while(i<m_DesiredSize && numTrials<m_NumIterations){
+	    //Generate artificial training examples
+	    artData = generateArtificialData(artSize, data);
+	    
+	    //Label artificial examples
+	    labelData(artData);
+	    addInstances(divData, artData);//Add new artificial data
+	    
+	    //Build new classifier
+	    Classifier tmp[] = AbstractClassifier.makeCopies(m_Classifier,1);
+	    newClassifier = tmp[0]; 
+	    newClassifier.buildClassifier(divData);
+	    //Remove all the artificial data
+	    removeInstances(divData, artSize);
+	    
+	    //Test if the new classifier should be added to the ensemble
+	    m_Committee.add(newClassifier);//add new classifier to current committee
+	    double currError = computeError(divData);
+	    if(currError <= eComm){//adding the new member did not increase the error
+		i++;
+		eComm = currError;
+		if(m_Debug) System.out.println("Iteration: "+(1+numTrials)+"\tClassifier "+i+" added to ensemble. Ensemble error = "+eComm);
+	    }else{//reject the current classifier because it increased the ensemble error 
+		m_Committee.removeElementAt(m_Committee.size()-1);//pop the last member
+	    }
+	    numTrials++;
+	}
+    }
+    
+    /** 
+     * Compute and store statistics required for generating artificial data.
+     *
+     * @param data training instances
+     * @throws Exception if statistics could not be calculated successfully
+     */
+    protected void computeStats(Instances data) throws Exception{
+	int numAttributes = data.numAttributes();
+	m_AttributeStats = new Vector(numAttributes);//use to map attributes to their stats
+	
+	for(int j=0; j<numAttributes; j++){
+	    if(data.attribute(j).isNominal()){
+		//Compute the probability of occurence of each distinct value 
+		int []nomCounts = (data.attributeStats(j)).nominalCounts;
+		double []counts = new double[nomCounts.length];
+		if(counts.length < 2) throw new Exception("Nominal attribute has less than two distinct values!"); 
+		//Perform Laplace smoothing
+		for(int i=0; i<counts.length; i++)
+		    counts[i] = nomCounts[i] + 1;
+		Utils.normalize(counts);
+		double []stats = new double[counts.length - 1];
+		stats[0] = counts[0];
+		//Calculate cumulative probabilities
+		for(int i=1; i<stats.length; i++)
+		    stats[i] = stats[i-1] + counts[i];
+		m_AttributeStats.add(j,stats);
+	    }else if(data.attribute(j).isNumeric()){
+		//Get mean and standard deviation from the training data
+		double []stats = new double[2];
+		stats[0] = data.meanOrMode(j);
+		stats[1] = Math.sqrt(data.variance(j));
+		m_AttributeStats.add(j,stats);
+	    }else System.err.println("Decorate can only handle numeric and nominal values.");
+	}
+    }
+
+    /**
+     * Generate artificial training examples.
+     * @param artSize size of examples set to create
+     * @param data training data
+     * @return the set of unlabeled artificial examples
+     */
+    protected Instances generateArtificialData(int artSize, Instances data){
+	int numAttributes = data.numAttributes();
+	Instances artData = new Instances(data, artSize);
+	double []att; 
+	Instance artInstance;
+	
+	for(int i=0; i<artSize; i++){
+	    att = new double[numAttributes];
+	    for(int j=0; j<numAttributes; j++){
+		if(data.attribute(j).isNominal()){
+		    //Select nominal value based on the frequency of occurence in the training data  
+		    double []stats = (double [])m_AttributeStats.get(j);
+		    att[j] =  (double) selectIndexProbabilistically(stats);
+		}
+		else if(data.attribute(j).isNumeric()){
+		    //Generate numeric value from the Guassian distribution 
+		    //defined by the mean and std dev of the attribute
+		    double []stats = (double [])m_AttributeStats.get(j);
+		    att[j] = (m_Random.nextGaussian()*stats[1])+stats[0];
+		}else System.err.println("Decorate can only handle numeric and nominal values.");
+	    }
+	    artInstance = new DenseInstance(1.0, att);
+	    artData.add(artInstance);
+	}
+	return artData;
+    }
+    
+    
+    /** 
+     * Labels the artificially generated data.
+     *
+     * @param artData the artificially generated instances
+     * @throws Exception if instances cannot be labeled successfully 
+     */
+    protected void labelData(Instances artData) throws Exception {
+	Instance curr;
+	double []probs;
+	
+	for(int i=0; i<artData.numInstances(); i++){
+	    curr = artData.instance(i);
+	    //compute the class membership probs predicted by the current ensemble 
+	    probs = distributionForInstance(curr);
+	    //select class label inversely proportional to the ensemble predictions
+	    curr.setClassValue(inverseLabel(probs));
+	}	
+    }
+    
+
+    /** 
+     * Select class label such that the probability of selection is
+     * inversely proportional to the ensemble's predictions.
+     *
+     * @param probs class membership probabilities of instance
+     * @return index of class label selected
+     * @throws Exception if instances cannot be labeled successfully 
+     */
+    protected int inverseLabel(double []probs) throws Exception{
+	double []invProbs = new double[probs.length];
+	//Produce probability distribution inversely proportional to the given
+	for(int i=0; i<probs.length; i++){
+	    if(probs[i]==0){
+		invProbs[i] = Double.MAX_VALUE/probs.length; 
+		//Account for probability values of 0 - to avoid divide-by-zero errors
+		//Divide by probs.length to make sure normalizing works properly
+	    }else{
+		invProbs[i] = 1.0 / probs[i];
+	    }
+	}
+	Utils.normalize(invProbs);
+	double []cdf = new double[invProbs.length];
+	//Compute cumulative probabilities 
+	cdf[0] = invProbs[0];
+	for(int i=1; i<invProbs.length; i++){
+	    cdf[i] = invProbs[i]+cdf[i-1];
+	}
+	
+	if(Double.isNaN(cdf[invProbs.length-1]))
+	    System.err.println("Cumulative class membership probability is NaN!"); 
+	return selectIndexProbabilistically(cdf);
+    }
+    
+    /** 
+     * Given cumulative probabilities select a nominal attribute value index 
+     *
+     * @param cdf array of cumulative probabilities
+     * @return index of attribute selected based on the probability distribution 
+     */
+    protected int selectIndexProbabilistically(double []cdf){
+	double rnd = m_Random.nextDouble();
+	int index = 0;
+	while(index < cdf.length && rnd > cdf[index]){
+	    index++;
+	}
+	return index;
+    } 
+    
+    /**
+     * Removes a specified number of instances from the given set of instances.
+     *
+     * @param data given instances
+     * @param numRemove number of instances to delete from the given instances
+     */
+    protected void removeInstances(Instances data, int numRemove){
+	int num = data.numInstances();
+	for(int i=num - 1; i>num - 1 - numRemove;i--){
+	    data.delete(i);
+	}
+    }
+    
+    /**
+     * Add new instances to the given set of instances.
+     *
+     * @param data given instances
+     * @param newData set of instances to add to given instances
+     */
+    protected void addInstances(Instances data, Instances newData){
+	for(int i=0; i<newData.numInstances(); i++)
+	    data.add(newData.instance(i));
+    }
+    
+    /** 
+     * Computes the error in classification on the given data.
+     *
+     * @param data the instances to be classified
+     * @return classification error
+     * @throws Exception if error can not be computed successfully
+     */
+    protected double computeError(Instances data) throws Exception {
+	double error = 0.0;
+	int numInstances = data.numInstances();
+	Instance curr;
+	
+	for(int i=0; i<numInstances; i++){
+	    curr = data.instance(i);
+	    //Check if the instance has been misclassified
+	    if(curr.classValue() != ((int) classifyInstance(curr))) error++;
+	}
+	return (error/numInstances);
+    }
+    
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+      if (instance.classAttribute().isNumeric()) {
+	  throw new UnsupportedClassTypeException("Decorate can't handle a numeric class!");
+      }
+      double [] sums = new double [instance.numClasses()], newProbs; 
+      Classifier curr;
+      
+      for (int i = 0; i < m_Committee.size(); i++) {
+	  curr = (Classifier) m_Committee.get(i);
+	  newProbs = curr.distributionForInstance(instance);
+	  for (int j = 0; j < newProbs.length; j++)
+	    sums[j] += newProbs[j];
+      }
+      if (Utils.eq(Utils.sum(sums), 0)) {
+	  return sums;
+      } else {
+	  Utils.normalize(sums);
+	  return sums;
+      }
+  }
+    
+    /**
+     * Returns description of the Decorate classifier.
+     *
+     * @return description of the Decorate classifier as a string
+     */
+    public String toString() {
+	
+	if (m_Committee == null) {
+	    return "Decorate: No model built yet.";
+	}
+	StringBuffer text = new StringBuffer();
+	text.append("Decorate base classifiers: \n\n");
+	for (int i = 0; i < m_Committee.size(); i++)
+	    text.append(((Classifier) m_Committee.get(i)).toString() + "\n\n");
+	text.append("Number of classifier in the ensemble: "+m_Committee.size()+"\n");
+	return text.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+    
+    /**
+     * Main method for testing this class.
+     *
+     * @param argv the options
+     */
+    public static void main(String [] argv) {
+        runClassifier(new Decorate(), argv);
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/END.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/END.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/END.java	(revision 29)
@@ -0,0 +1,358 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    END.java
+ *    Copyright (C) 2004-2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Hashtable;
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * A meta classifier for handling multi-class datasets with 2-class classifiers by building an ensemble of nested dichotomies.<br/>
+ * <br/>
+ * For more info, check<br/>
+ * <br/>
+ * Lin Dong, Eibe Frank, Stefan Kramer: Ensembles of Balanced Nested Dichotomies for Multi-class Problems. In: PKDD, 84-95, 2005.<br/>
+ * <br/>
+ * Eibe Frank, Stefan Kramer: Ensembles of nested dichotomies for multi-class problems. In: Twenty-first International Conference on Machine Learning, 2004.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Dong2005,
+ *    author = {Lin Dong and Eibe Frank and Stefan Kramer},
+ *    booktitle = {PKDD},
+ *    pages = {84-95},
+ *    publisher = {Springer},
+ *    title = {Ensembles of Balanced Nested Dichotomies for Multi-class Problems},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Frank2004,
+ *    author = {Eibe Frank and Stefan Kramer},
+ *    booktitle = {Twenty-first International Conference on Machine Learning},
+ *    publisher = {ACM},
+ *    title = {Ensembles of nested dichotomies for multi-class problems},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.meta.nestedDichotomies.ND)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.meta.nestedDichotomies.ND:
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Eibe Frank
+ * @author Lin Dong
+ * @version $Revision: 5928 $
+ */
+public class END 
+  extends RandomizableIteratedSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -4143242362912214956L;
+  
+  /**
+   * The hashtable containing the classifiers for the END.
+   */
+  protected Hashtable m_hashtable = null;
+  
+  /**
+   * Constructor.
+   */
+  public END() {
+    
+    m_Classifier = new weka.classifiers.meta.nestedDichotomies.ND();
+  }
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.meta.nestedDichotomies.ND";
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    
+    return "A meta classifier for handling multi-class datasets with 2-class "
+      + "classifiers by building an ensemble of nested dichotomies.\n\n"
+      + "For more info, check\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lin Dong and Eibe Frank and Stefan Kramer");
+    result.setValue(Field.TITLE, "Ensembles of Balanced Nested Dichotomies for Multi-class Problems");
+    result.setValue(Field.BOOKTITLE, "PKDD");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.PAGES, "84-95");
+    result.setValue(Field.PUBLISHER, "Springer");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Eibe Frank and Stefan Kramer");
+    additional.setValue(Field.TITLE, "Ensembles of nested dichotomies for multi-class problems");
+    additional.setValue(Field.BOOKTITLE, "Twenty-first International Conference on Machine Learning");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.PUBLISHER, "ACM");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // instances
+    result.setMinimumNumberInstances(1);  // at least 1 for the RandomNumberGenerator!
+    
+    return result;
+  }
+  
+  /**
+   * Builds the committee of randomizable classifiers.
+   *
+   * @param data the training data to be used for generating the
+   * bagged classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    if (!(m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.ND) && 
+	!(m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.ClassBalancedND) &&  
+	!(m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.DataNearBalancedND)) {
+      throw new IllegalArgumentException("END only works with ND, ClassBalancedND " +
+					 "or DataNearBalancedND classifier");
+    }
+    
+    m_hashtable = new Hashtable();
+    
+    m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, m_NumIterations);
+    
+    Random random = data.getRandomNumberGenerator(m_Seed);
+    for (int j = 0; j < m_Classifiers.length; j++) {
+      
+      // Set the random number seed for the current classifier.
+      ((Randomizable) m_Classifiers[j]).setSeed(random.nextInt());
+      
+      // Set the hashtable
+      if (m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.ND) 
+	((weka.classifiers.meta.nestedDichotomies.ND)m_Classifiers[j]).setHashtable(m_hashtable);
+      else if (m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.ClassBalancedND) 
+	((weka.classifiers.meta.nestedDichotomies.ClassBalancedND)m_Classifiers[j]).setHashtable(m_hashtable);
+      else if (m_Classifier instanceof weka.classifiers.meta.nestedDichotomies.DataNearBalancedND) 
+	((weka.classifiers.meta.nestedDichotomies.DataNearBalancedND)m_Classifiers[j]).
+	  setHashtable(m_hashtable);
+      
+      // Build the classifier.
+      m_Classifiers[j].buildClassifier(data);
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    double [] sums = new double [instance.numClasses()], newProbs; 
+    
+    for (int i = 0; i < m_NumIterations; i++) {
+      if (instance.classAttribute().isNumeric() == true) {
+	sums[0] += m_Classifiers[i].classifyInstance(instance);
+      } else {
+	newProbs = m_Classifiers[i].distributionForInstance(instance);
+	for (int j = 0; j < newProbs.length; j++)
+	  sums[j] += newProbs[j];
+      }
+    }
+    if (instance.classAttribute().isNumeric() == true) {
+      sums[0] /= (double)m_NumIterations;
+      return sums;
+    } else if (Utils.eq(Utils.sum(sums), 0)) {
+      return sums;
+    } else {
+      Utils.normalize(sums);
+      return sums;
+    }
+  }
+  
+  /**
+   * Returns description of the committee.
+   *
+   * @return description of the committee as a string
+   */
+  public String toString() {
+    
+    if (m_Classifiers == null) {
+      return "END: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("All the base classifiers: \n\n");
+    for (int i = 0; i < m_Classifiers.length; i++)
+      text.append(m_Classifiers[i].toString() + "\n\n");
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new END(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/EnsembleSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/EnsembleSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/EnsembleSelection.java	(revision 29)
@@ -0,0 +1,1796 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleSelection.java
+ *    Copyright (C) 2006 David Michael
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableClassifier;
+import weka.classifiers.meta.ensembleSelection.EnsembleMetricHelper;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibraryModel;
+import weka.classifiers.meta.ensembleSelection.ModelBag;
+import weka.classifiers.trees.REPTree;
+import weka.classifiers.xml.XMLClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.xml.KOML;
+import weka.core.xml.XMLOptions;
+import weka.core.xml.XMLSerialization;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Combines several classifiers using the ensemble selection method. For more information, see: Caruana, Rich, Niculescu, Alex, Crew, Geoff, and Ksikes, Alex, Ensemble Selection from Libraries of Models, The International Conference on Machine Learning (ICML'04), 2004.  Implemented in Weka by Bob Jung and David Michael.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{RichCaruana2004,
+ *    author = {Rich Caruana, Alex Niculescu, Geoff Crew, and Alex Ksikes},
+ *    booktitle = {21st International Conference on Machine Learning},
+ *    title = {Ensemble Selection from Libraries of Models},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * Our implementation of ensemble selection is a bit different from the other
+ * classifiers because we assume that the list of models to be trained is too 
+ * large to fit in memory and that our base classifiers will need to be
+ * serialized to the file system (in the directory listed in the "workingDirectory
+ * option).  We have adopted the term "model library" for this large set of 
+ * classifiers keeping in line with the original paper. 
+ * <p/>
+ * 
+ * If you are planning to use this classifier, we highly recommend you take a 
+ * quick look at our FAQ/tutorial on the WIKI.  There are a few things that
+ * are unique to this classifier that could trip you up.  Otherwise, this
+ * method is a great way to get really great classifier performance without 
+ * having to do too much parameter tuning.  What is nice is that in the worst 
+ * case you get a nice summary of how s large number of diverse models 
+ * performed on your data set.  
+ * <p/>
+ * 
+ * This class relies on the package weka.classifiers.meta.ensembleSelection. 
+ * <p/>
+ * 
+ * When run from the Explorer or another GUI, the classifier depends on the
+ * package weka.gui.libraryEditor. 
+ * <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;/path/to/modelLibrary&gt;
+ *  Specifies the Model Library File, continuing the list of all models.</pre>
+ * 
+ * <pre> -W &lt;/path/to/working/directory&gt;
+ *  Specifies the Working Directory, where all models will be stored.</pre>
+ * 
+ * <pre> -B &lt;numModelBags&gt;
+ *  Set the number of bags, i.e., number of iterations to run 
+ *  the ensemble selection algorithm.</pre>
+ * 
+ * <pre> -E &lt;modelRatio&gt;
+ *  Set the ratio of library models that will be randomly chosen 
+ *  to populate each bag of models.</pre>
+ * 
+ * <pre> -V &lt;validationRatio&gt;
+ *  Set the ratio of the training data set that will be reserved 
+ *  for validation.</pre>
+ * 
+ * <pre> -H &lt;hillClimbIterations&gt;
+ *  Set the number of hillclimbing iterations to be performed 
+ *  on each model bag.</pre>
+ * 
+ * <pre> -I &lt;sortInitialization&gt;
+ *  Set the the ratio of the ensemble library that the sort 
+ *  initialization algorithm will be able to choose from while 
+ *  initializing the ensemble for each model bag</pre>
+ * 
+ * <pre> -X &lt;numFolds&gt;
+ *  Sets the number of cross-validation folds.</pre>
+ * 
+ * <pre> -P &lt;hillclimbMettric&gt;
+ *  Specify the metric that will be used for model selection 
+ *  during the hillclimbing algorithm.
+ *  Valid metrics are: 
+ *   accuracy, rmse, roc, precision, recall, fscore, all</pre>
+ * 
+ * <pre> -A &lt;algorithm&gt;
+ *  Specifies the algorithm to be used for ensemble selection. 
+ *  Valid algorithms are:
+ *   "forward" (default) for forward selection.
+ *   "backward" for backward elimination.
+ *   "both" for both forward and backward elimination.
+ *   "best" to simply print out top performer from the 
+ *      ensemble library
+ *   "library" to only train the models in the ensemble 
+ *      library</pre>
+ * 
+ * <pre> -R
+ *  Flag whether or not models can be selected more than once 
+ *  for an ensemble.</pre>
+ * 
+ * <pre> -G
+ *  Whether sort initialization greedily stops adding models 
+ *  when performance degrades.</pre>
+ * 
+ * <pre> -O
+ *  Flag for verbose output. Prints out performance of all 
+ *  selected models.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Robert Jung
+ * @author David Michael
+ * @version $Revision: 5480 $
+ */
+public class EnsembleSelection 
+  extends RandomizableClassifier 
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1744155148765058511L;
+
+  /**
+   * The Library of models, from which we can select our ensemble. Usually
+   * loaded from a model list file (.mlf or .model.xml) using the -L
+   * command-line option.
+   */
+  protected EnsembleSelectionLibrary m_library = new EnsembleSelectionLibrary();
+  
+  /**
+   * List of models chosen by EnsembleSelection. Populated by buildClassifier.
+   */
+  protected EnsembleSelectionLibraryModel[] m_chosen_models = null;
+  
+  /**
+   * An array of weights for the chosen models. Elements are parallel to those
+   * in m_chosen_models. That is, m_chosen_model_weights[i] is the weight
+   * associated with the model at m_chosen_models[i].
+   */
+  protected int[] m_chosen_model_weights = null;
+  
+  /** Total weight of all chosen models. */
+  protected int m_total_weight = 0;
+  
+  /**
+   * ratio of library models that will be randomly chosen to be used for each
+   * model bag
+   */
+  protected double m_modelRatio = 0.5;
+  
+  /**
+   * Indicates the fraction of the given training set that should be used for
+   * hillclimbing/validation. This fraction is set aside and not used for
+   * training. It is assumed that any loaded models were also not trained on
+   * set-aside data. (If the same percentage and random seed were used
+   * previously to train the models in the library, this will work as expected -
+   * i.e., those models will be valid)
+   */
+  protected double m_validationRatio = 0.25;
+  
+  /** defines metrics that can be chosen for hillclimbing */
+  public static final Tag[] TAGS_METRIC = {
+    new Tag(EnsembleMetricHelper.METRIC_ACCURACY, "Optimize with Accuracy"),
+    new Tag(EnsembleMetricHelper.METRIC_RMSE, "Optimize with RMSE"),
+    new Tag(EnsembleMetricHelper.METRIC_ROC, "Optimize with ROC"),
+    new Tag(EnsembleMetricHelper.METRIC_PRECISION, "Optimize with precision"),
+    new Tag(EnsembleMetricHelper.METRIC_RECALL, "Optimize with recall"),
+    new Tag(EnsembleMetricHelper.METRIC_FSCORE, "Optimize with fscore"),
+    new Tag(EnsembleMetricHelper.METRIC_ALL, "Optimize with all metrics"), };
+  
+  /**
+   * The "enumeration" of the algorithms we can use. Forward - forward
+   * selection. For hillclimb iterations,
+   */
+  public static final int ALGORITHM_FORWARD = 0;
+  
+  public static final int ALGORITHM_BACKWARD = 1;
+  
+  public static final int ALGORITHM_FORWARD_BACKWARD = 2;
+  
+  public static final int ALGORITHM_BEST = 3;
+  
+  public static final int ALGORITHM_BUILD_LIBRARY = 4;
+  
+  /** defines metrics that can be chosen for hillclimbing */
+  public static final Tag[] TAGS_ALGORITHM = {
+    new Tag(ALGORITHM_FORWARD, "Forward selection"),
+    new Tag(ALGORITHM_BACKWARD, "Backward elimation"),
+    new Tag(ALGORITHM_FORWARD_BACKWARD, "Forward Selection + Backward Elimination"),
+    new Tag(ALGORITHM_BEST, "Best model"),
+    new Tag(ALGORITHM_BUILD_LIBRARY, "Build Library Only") };
+  
+  /**
+   * this specifies the number of "Ensembl-X" directories that are allowed to
+   * be created in the users home directory where X is the number of the
+   * ensemble
+   */
+  private static final int MAX_DEFAULT_DIRECTORIES = 1000;
+  
+  /**
+   * The name of the Model Library File (if one is specified) which lists
+   * models from which ensemble selection will choose. This is only used when
+   * run from the command-line, as otherwise m_library is responsible for
+   * this.
+   */
+  protected String m_modelLibraryFileName = null;
+  
+  /**
+   * The number of "model bags". Using 1 is equivalent to no bagging at all.
+   */
+  protected int m_numModelBags = 10;
+  
+  /** The metric for which the ensemble will be optimized. */
+  protected int m_hillclimbMetric = EnsembleMetricHelper.METRIC_RMSE;
+  
+  /** The algorithm used for ensemble selection. */
+  protected int m_algorithm = ALGORITHM_FORWARD;
+  
+  /**
+   * number of hillclimbing iterations for the ensemble selection algorithm
+   */
+  protected int m_hillclimbIterations = 100;
+  
+  /** ratio of library models to be used for sort initialization */
+  protected double m_sortInitializationRatio = 1.0;
+  
+  /**
+   * specifies whether or not the ensemble algorithm is allowed to include a
+   * specific model in the library more than once in each ensemble
+   */
+  protected boolean m_replacement = true;
+  
+  /**
+   * specifies whether we use "greedy" sort initialization. If false, we
+   * simply add the best m_sortInitializationRatio models of the bag blindly.
+   * If true, we add the best models in order up to m_sortInitializationRatio
+   * until adding the next model would not help performance.
+   */
+  protected boolean m_greedySortInitialization = true;
+  
+  /**
+   * Specifies whether or not we will output metrics for all models
+   */
+  protected boolean m_verboseOutput = false;
+  
+  /**
+   * Hash map of cached predictions. The key is a stringified Instance. Each
+   * entry is a 2d array, first indexed by classifier index (i.e., the one
+   * used in m_chosen_model). The second index is the usual "distribution"
+   * index across classes.
+   */
+  protected Map m_cachedPredictions = null;
+  
+  /**
+   * This string will store the working directory where all models , temporary
+   * prediction values, and modellist logs are to be built and stored.
+   */
+  protected File m_workingDirectory = new File(getDefaultWorkingDirectory());
+  
+  /**
+   * Indicates the number of folds for cross-validation. A value of 1
+   * indicates there is no cross-validation. Cross validation is done in the
+   * "embedded" fashion described by Caruana, Niculescu, and Munson
+   * (unpublished work - tech report forthcoming)
+   */
+  protected int m_NumFolds = 1;
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    
+    return "Combines several classifiers using the ensemble "
+    + "selection method. For more information, see: "
+    + "Caruana, Rich, Niculescu, Alex, Crew, Geoff, and Ksikes, Alex, "
+    + "Ensemble Selection from Libraries of Models, "
+    + "The International Conference on Machine Learning (ICML'04), 2004.  "
+    + "Implemented in Weka by Bob Jung and David Michael.";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tSpecifies the Model Library File, continuing the list of all models.",
+	"L", 1, "-L </path/to/modelLibrary>"));
+    
+    result.addElement(new Option(
+	"\tSpecifies the Working Directory, where all models will be stored.",
+	"W", 1, "-W </path/to/working/directory>"));
+    
+    result.addElement(new Option(
+	"\tSet the number of bags, i.e., number of iterations to run \n"
+	+ "\tthe ensemble selection algorithm.",
+	"B", 1, "-B <numModelBags>"));
+    
+    result.addElement(new Option(
+	"\tSet the ratio of library models that will be randomly chosen \n"
+	+ "\tto populate each bag of models.",
+	"E", 1, "-E <modelRatio>"));
+    
+    result.addElement(new Option(
+	"\tSet the ratio of the training data set that will be reserved \n"
+	+ "\tfor validation.",
+	"V", 1, "-V <validationRatio>"));
+    
+    result.addElement(new Option(
+	"\tSet the number of hillclimbing iterations to be performed \n"
+	+ "\ton each model bag.",
+	"H", 1, "-H <hillClimbIterations>"));
+    
+    result.addElement(new Option(
+	"\tSet the the ratio of the ensemble library that the sort \n"
+	+ "\tinitialization algorithm will be able to choose from while \n"
+	+ "\tinitializing the ensemble for each model bag",
+	"I", 1, "-I <sortInitialization>"));
+    
+    result.addElement(new Option(
+	"\tSets the number of cross-validation folds.", 
+	"X", 1, "-X <numFolds>"));
+    
+    result.addElement(new Option(
+	"\tSpecify the metric that will be used for model selection \n"
+	+ "\tduring the hillclimbing algorithm.\n"
+	+ "\tValid metrics are: \n"
+	+ "\t\taccuracy, rmse, roc, precision, recall, fscore, all",
+	"P", 1, "-P <hillclimbMettric>"));
+    
+    result.addElement(new Option(
+	"\tSpecifies the algorithm to be used for ensemble selection. \n"
+	+ "\tValid algorithms are:\n"
+	+ "\t\t\"forward\" (default) for forward selection.\n"
+	+ "\t\t\"backward\" for backward elimination.\n"
+	+ "\t\t\"both\" for both forward and backward elimination.\n"
+	+ "\t\t\"best\" to simply print out top performer from the \n"
+	+ "\t\t   ensemble library\n"
+	+ "\t\t\"library\" to only train the models in the ensemble \n"
+	+ "\t\t   library",
+	"A", 1, "-A <algorithm>"));
+    
+    result.addElement(new Option(
+	"\tFlag whether or not models can be selected more than once \n"
+	+ "\tfor an ensemble.",
+	"R", 0, "-R"));
+    
+    result.addElement(new Option(
+	"\tWhether sort initialization greedily stops adding models \n"
+	+ "\twhen performance degrades.",
+	"G", 0, "-G"));
+    
+    result.addElement(new Option(
+	"\tFlag for verbose output. Prints out performance of all \n"
+	+ "\tselected models.",
+	"O", 0, "-O"));
+    
+    // TODO - Add more options here
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * We return true for basically everything except for Missing class values,
+   * because we can't really answer for all the models in our library. If any of
+   * them don't work with the supplied data then we just trap the exception.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities(); // returns the object
+    result.disableAll();
+    // from
+    // weka.classifiers.Classifier
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    result.enable(Capability.BINARY_ATTRIBUTES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.BINARY_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L &lt;/path/to/modelLibrary&gt;
+   *  Specifies the Model Library File, continuing the list of all models.</pre>
+   * 
+   * <pre> -W &lt;/path/to/working/directory&gt;
+   *  Specifies the Working Directory, where all models will be stored.</pre>
+   * 
+   * <pre> -B &lt;numModelBags&gt;
+   *  Set the number of bags, i.e., number of iterations to run 
+   *  the ensemble selection algorithm.</pre>
+   * 
+   * <pre> -E &lt;modelRatio&gt;
+   *  Set the ratio of library models that will be randomly chosen 
+   *  to populate each bag of models.</pre>
+   * 
+   * <pre> -V &lt;validationRatio&gt;
+   *  Set the ratio of the training data set that will be reserved 
+   *  for validation.</pre>
+   * 
+   * <pre> -H &lt;hillClimbIterations&gt;
+   *  Set the number of hillclimbing iterations to be performed 
+   *  on each model bag.</pre>
+   * 
+   * <pre> -I &lt;sortInitialization&gt;
+   *  Set the the ratio of the ensemble library that the sort 
+   *  initialization algorithm will be able to choose from while 
+   *  initializing the ensemble for each model bag</pre>
+   * 
+   * <pre> -X &lt;numFolds&gt;
+   *  Sets the number of cross-validation folds.</pre>
+   * 
+   * <pre> -P &lt;hillclimbMettric&gt;
+   *  Specify the metric that will be used for model selection 
+   *  during the hillclimbing algorithm.
+   *  Valid metrics are: 
+   *   accuracy, rmse, roc, precision, recall, fscore, all</pre>
+   * 
+   * <pre> -A &lt;algorithm&gt;
+   *  Specifies the algorithm to be used for ensemble selection. 
+   *  Valid algorithms are:
+   *   "forward" (default) for forward selection.
+   *   "backward" for backward elimination.
+   *   "both" for both forward and backward elimination.
+   *   "best" to simply print out top performer from the 
+   *      ensemble library
+   *   "library" to only train the models in the ensemble 
+   *      library</pre>
+   * 
+   * <pre> -R
+   *  Flag whether or not models can be selected more than once 
+   *  for an ensemble.</pre>
+   * 
+   * <pre> -G
+   *  Whether sort initialization greedily stops adding models 
+   *  when performance degrades.</pre>
+   * 
+   * <pre> -O
+   *  Flag for verbose output. Prints out performance of all 
+   *  selected models.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options
+   *            the list of options as an array of strings
+   * @throws Exception
+   *                if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0) {
+      m_modelLibraryFileName = tmpStr;
+      m_library = new EnsembleSelectionLibrary(m_modelLibraryFileName);
+    } else {
+      setLibrary(new EnsembleSelectionLibrary());
+      // setLibrary(new Library(super.m_Classifiers));
+    }
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() != 0 && validWorkingDirectory(tmpStr)) {
+      m_workingDirectory = new File(tmpStr);
+    } else {
+      m_workingDirectory = new File(getDefaultWorkingDirectory());
+    }
+    m_library.setWorkingDirectory(m_workingDirectory);
+    
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0) {
+      setModelRatio(Double.parseDouble(tmpStr));
+    } else {
+      setModelRatio(1.0);
+    }
+    
+    tmpStr = Utils.getOption('V', options);
+    if (tmpStr.length() != 0) {
+      setValidationRatio(Double.parseDouble(tmpStr));
+    } else {
+      setValidationRatio(0.25);
+    }
+    
+    tmpStr = Utils.getOption('B', options);
+    if (tmpStr.length() != 0) {
+      setNumModelBags(Integer.parseInt(tmpStr));
+    } else {
+      setNumModelBags(10);
+    }
+    
+    tmpStr = Utils.getOption('H', options);
+    if (tmpStr.length() != 0) {
+      setHillclimbIterations(Integer.parseInt(tmpStr));
+    } else {
+      setHillclimbIterations(100);
+    }
+    
+    tmpStr = Utils.getOption('I', options);
+    if (tmpStr.length() != 0) {
+      setSortInitializationRatio(Double.parseDouble(tmpStr));
+    } else {
+      setSortInitializationRatio(1.0);
+    }
+    
+    tmpStr = Utils.getOption('X', options);
+    if (tmpStr.length() != 0) {
+      setNumFolds(Integer.parseInt(tmpStr));
+    } else {
+      setNumFolds(10);
+    }
+    
+    setReplacement(Utils.getFlag('R', options));
+    
+    setGreedySortInitialization(Utils.getFlag('G', options));
+    
+    setVerboseOutput(Utils.getFlag('O', options));
+    
+    tmpStr = Utils.getOption('P', options);
+    // if (hillclimbMetricString.length() != 0) {
+    
+    if (tmpStr.toLowerCase().equals("accuracy")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_ACCURACY, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("rmse")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_RMSE, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("roc")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_ROC, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("precision")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_PRECISION, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("recall")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_RECALL, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("fscore")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_FSCORE, TAGS_METRIC));
+    } else if (tmpStr.toLowerCase().equals("all")) {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_ALL, TAGS_METRIC));
+    } else {
+      setHillclimbMetric(new SelectedTag(
+	  EnsembleMetricHelper.METRIC_RMSE, TAGS_METRIC));
+    }
+    
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.toLowerCase().equals("forward")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_FORWARD, TAGS_ALGORITHM));
+    } else if (tmpStr.toLowerCase().equals("backward")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_BACKWARD, TAGS_ALGORITHM));
+    } else if (tmpStr.toLowerCase().equals("both")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_FORWARD_BACKWARD, TAGS_ALGORITHM));
+    } else if (tmpStr.toLowerCase().equals("forward")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_FORWARD, TAGS_ALGORITHM));
+    } else if (tmpStr.toLowerCase().equals("best")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_BEST, TAGS_ALGORITHM));
+    } else if (tmpStr.toLowerCase().equals("library")) {
+      setAlgorithm(new SelectedTag(ALGORITHM_BUILD_LIBRARY, TAGS_ALGORITHM));
+    } else {
+      setAlgorithm(new SelectedTag(ALGORITHM_FORWARD, TAGS_ALGORITHM));
+    }
+    
+    super.setOptions(options);
+    
+    m_library.setDebug(m_Debug);
+  }
+  
+  
+  /**
+   * Gets the current settings of the Classifier.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    
+    if (m_library.getModelListFile() != null) {
+      result.add("-L");
+      result.add("" + m_library.getModelListFile());
+    }
+    
+    if (!m_workingDirectory.equals("")) {
+      result.add("-W");
+      result.add("" + getWorkingDirectory());
+    }
+    
+    result.add("-P");
+    switch (getHillclimbMetric().getSelectedTag().getID()) {
+      case (EnsembleMetricHelper.METRIC_ACCURACY):
+	result.add("accuracy");
+      break;
+      case (EnsembleMetricHelper.METRIC_RMSE):
+	result.add("rmse");
+      break;
+      case (EnsembleMetricHelper.METRIC_ROC):
+	result.add("roc");
+      break;
+      case (EnsembleMetricHelper.METRIC_PRECISION):
+	result.add("precision");
+      break;
+      case (EnsembleMetricHelper.METRIC_RECALL):
+	result.add("recall");
+      break;
+      case (EnsembleMetricHelper.METRIC_FSCORE):
+	result.add("fscore");
+      break;
+      case (EnsembleMetricHelper.METRIC_ALL):
+	result.add("all");
+      break;
+    }
+    
+    result.add("-A");
+    switch (getAlgorithm().getSelectedTag().getID()) {
+      case (ALGORITHM_FORWARD):
+	result.add("forward");
+      break;
+      case (ALGORITHM_BACKWARD):
+	result.add("backward");
+      break;
+      case (ALGORITHM_FORWARD_BACKWARD):
+	result.add("both");
+      break;
+      case (ALGORITHM_BEST):
+	result.add("best");
+      break;
+      case (ALGORITHM_BUILD_LIBRARY):
+	result.add("library");
+      break;
+    }
+    
+    result.add("-B");
+    result.add("" + getNumModelBags());
+    result.add("-V");
+    result.add("" + getValidationRatio());
+    result.add("-E");
+    result.add("" + getModelRatio());
+    result.add("-H");
+    result.add("" + getHillclimbIterations());
+    result.add("-I");
+    result.add("" + getSortInitializationRatio());
+    result.add("-X");
+    result.add("" + getNumFolds());
+    
+    if (m_replacement)
+      result.add("-R");
+    if (m_greedySortInitialization)
+      result.add("-G");
+    if (m_verboseOutput)
+      result.add("-O");
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds used for cross-validation.";
+  }
+  
+  /**
+   * Gets the number of folds for the cross-validation.
+   * 
+   * @return the number of folds for the cross-validation
+   */
+  public int getNumFolds() {
+    return m_NumFolds;
+  }
+  
+  /**
+   * Sets the number of folds for the cross-validation.
+   * 
+   * @param numFolds
+   *            the number of folds for the cross-validation
+   * @throws Exception
+   *                if parameter illegal
+   */
+  public void setNumFolds(int numFolds) throws Exception {
+    if (numFolds < 0) {
+      throw new IllegalArgumentException(
+	  "EnsembleSelection: Number of cross-validation "
+	  + "folds must be positive.");
+    }
+    m_NumFolds = numFolds;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String libraryTipText() {
+    return "An ensemble library.";
+  }
+  
+  /**
+   * Gets the ensemble library.
+   * 
+   * @return the ensemble library
+   */
+  public EnsembleSelectionLibrary getLibrary() {
+    return m_library;
+  }
+  
+  /**
+   * Sets the ensemble library.
+   * 
+   * @param newLibrary
+   *            the ensemble library
+   */
+  public void setLibrary(EnsembleSelectionLibrary newLibrary) {
+    m_library = newLibrary;
+    m_library.setDebug(m_Debug);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String modelRatioTipText() {
+    return "The ratio of library models that will be randomly chosen to be used for each iteration.";
+  }
+  
+  /**
+   * Get the value of modelRatio.
+   * 
+   * @return Value of modelRatio.
+   */
+  public double getModelRatio() {
+    return m_modelRatio;
+  }
+  
+  /**
+   * Set the value of modelRatio.
+   * 
+   * @param v
+   *            Value to assign to modelRatio.
+   */
+  public void setModelRatio(double v) {
+    m_modelRatio = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String validationRatioTipText() {
+    return "The ratio of the training data set that will be reserved for validation.";
+  }
+  
+  /**
+   * Get the value of validationRatio.
+   * 
+   * @return Value of validationRatio.
+   */
+  public double getValidationRatio() {
+    return m_validationRatio;
+  }
+  
+  /**
+   * Set the value of validationRatio.
+   * 
+   * @param v
+   *            Value to assign to validationRatio.
+   */
+  public void setValidationRatio(double v) {
+    m_validationRatio = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String hillclimbMetricTipText() {
+    return "the metric that will be used to optimizer the chosen ensemble..";
+  }
+  
+  /**
+   * Gets the hill climbing metric. Will be one of METRIC_ACCURACY,
+   * METRIC_RMSE, METRIC_ROC, METRIC_PRECISION, METRIC_RECALL, METRIC_FSCORE,
+   * METRIC_ALL
+   * 
+   * @return the hillclimbMetric
+   */
+  public SelectedTag getHillclimbMetric() {
+    return new SelectedTag(m_hillclimbMetric, TAGS_METRIC);
+  }
+  
+  /**
+   * Sets the hill climbing metric. Will be one of METRIC_ACCURACY,
+   * METRIC_RMSE, METRIC_ROC, METRIC_PRECISION, METRIC_RECALL, METRIC_FSCORE,
+   * METRIC_ALL
+   * 
+   * @param newType
+   *            the new hillclimbMetric
+   */
+  public void setHillclimbMetric(SelectedTag newType) {
+    if (newType.getTags() == TAGS_METRIC) {
+      m_hillclimbMetric = newType.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String algorithmTipText() {
+    return "the algorithm used to optimizer the ensemble";
+  }
+  
+  /**
+   * Gets the algorithm
+   * 
+   * @return the algorithm
+   */
+  public SelectedTag getAlgorithm() {
+    return new SelectedTag(m_algorithm, TAGS_ALGORITHM);
+  }
+  
+  /**
+   * Sets the Algorithm to use
+   * 
+   * @param newType
+   *            the new algorithm
+   */
+  public void setAlgorithm(SelectedTag newType) {
+    if (newType.getTags() == TAGS_ALGORITHM) {
+      m_algorithm = newType.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String hillclimbIterationsTipText() {
+    return "The number of hillclimbing iterations for the ensemble selection algorithm.";
+  }
+  
+  /**
+   * Gets the number of hillclimbIterations.
+   * 
+   * @return the number of hillclimbIterations
+   */
+  public int getHillclimbIterations() {
+    return m_hillclimbIterations;
+  }
+  
+  /**
+   * Sets the number of hillclimbIterations.
+   * 
+   * @param n
+   *            the number of hillclimbIterations
+   * @throws Exception
+   *                if parameter illegal
+   */
+  public void setHillclimbIterations(int n) throws Exception {
+    if (n < 0) {
+      throw new IllegalArgumentException(
+	  "EnsembleSelection: Number of hillclimb iterations "
+	  + "must be positive.");
+    }
+    m_hillclimbIterations = n;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String numModelBagsTipText() {
+    return "The number of \"model bags\" used in the ensemble selection algorithm.";
+  }
+  
+  /**
+   * Gets numModelBags.
+   * 
+   * @return numModelBags
+   */
+  public int getNumModelBags() {
+    return m_numModelBags;
+  }
+  
+  /**
+   * Sets numModelBags.
+   * 
+   * @param n
+   *            the new value for numModelBags
+   * @throws Exception
+   *                if parameter illegal
+   */
+  public void setNumModelBags(int n) throws Exception {
+    if (n <= 0) {
+      throw new IllegalArgumentException(
+	  "EnsembleSelection: Number of model bags "
+	  + "must be positive.");
+    }
+    m_numModelBags = n;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String sortInitializationRatioTipText() {
+    return "The ratio of library models to be used for sort initialization.";
+  }
+  
+  /**
+   * Get the value of sortInitializationRatio.
+   * 
+   * @return Value of sortInitializationRatio.
+   */
+  public double getSortInitializationRatio() {
+    return m_sortInitializationRatio;
+  }
+  
+  /**
+   * Set the value of sortInitializationRatio.
+   * 
+   * @param v
+   *            Value to assign to sortInitializationRatio.
+   */
+  public void setSortInitializationRatio(double v) {
+    m_sortInitializationRatio = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String replacementTipText() {
+    return "Whether models in the library can be included more than once in an ensemble.";
+  }
+  
+  /**
+   * Get the value of replacement.
+   * 
+   * @return Value of replacement.
+   */
+  public boolean getReplacement() {
+    return m_replacement;
+  }
+  
+  /**
+   * Set the value of replacement.
+   * 
+   * @param newReplacement
+   *            Value to assign to replacement.
+   */
+  public void setReplacement(boolean newReplacement) {
+    m_replacement = newReplacement;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String greedySortInitializationTipText() {
+    return "Whether sort initialization greedily stops adding models when performance degrades.";
+  }
+  
+  /**
+   * Get the value of greedySortInitialization.
+   * 
+   * @return Value of replacement.
+   */
+  public boolean getGreedySortInitialization() {
+    return m_greedySortInitialization;
+  }
+  
+  /**
+   * Set the value of greedySortInitialization.
+   * 
+   * @param newGreedySortInitialization
+   *            Value to assign to replacement.
+   */
+  public void setGreedySortInitialization(boolean newGreedySortInitialization) {
+    m_greedySortInitialization = newGreedySortInitialization;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String verboseOutputTipText() {
+    return "Whether metrics are printed for each model.";
+  }
+  
+  /**
+   * Get the value of verboseOutput.
+   * 
+   * @return Value of verboseOutput.
+   */
+  public boolean getVerboseOutput() {
+    return m_verboseOutput;
+  }
+  
+  /**
+   * Set the value of verboseOutput.
+   * 
+   * @param newVerboseOutput
+   *            Value to assign to verboseOutput.
+   */
+  public void setVerboseOutput(boolean newVerboseOutput) {
+    m_verboseOutput = newVerboseOutput;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String workingDirectoryTipText() {
+    return "The working directory of the ensemble - where trained models will be stored.";
+  }
+  
+  /**
+   * Get the value of working directory.
+   * 
+   * @return Value of working directory.
+   */
+  public File getWorkingDirectory() {
+    return m_workingDirectory;
+  }
+  
+  /**
+   * Set the value of working directory.
+   * 
+   * @param newWorkingDirectory	directory Value.
+   */
+  public void setWorkingDirectory(File newWorkingDirectory) {
+    if (m_Debug) {
+      System.out.println("working directory changed to: "
+	  + newWorkingDirectory);
+    }
+    m_library.setWorkingDirectory(newWorkingDirectory);
+    
+    m_workingDirectory = newWorkingDirectory;
+  }
+  
+  /**
+   * Buildclassifier selects a classifier from the set of classifiers by
+   * minimising error on the training data.
+   * 
+   * @param trainData	the training data to be used for generating the boosted
+   *            	classifier.
+   * @throws Exception	if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances trainData) throws Exception {
+    
+    getCapabilities().testWithFail(trainData);
+    
+    // First we need to make sure that some library models
+    // were specified. If not, then use the default list
+    if (m_library.m_Models.size() == 0) {
+      
+      System.out
+      .println("WARNING: No library file specified.  Using some default models.");
+      System.out
+      .println("You should specify a model list with -L <file> from the command line.");
+      System.out
+      .println("Or edit the list directly with the LibraryEditor from the GUI");
+      
+      for (int i = 0; i < 10; i++) {
+	
+	REPTree tree = new REPTree();
+	tree.setSeed(i);
+	m_library.addModel(new EnsembleSelectionLibraryModel(tree));
+	
+      }
+      
+    }
+    
+    if (m_library == null) {
+      m_library = new EnsembleSelectionLibrary();
+      m_library.setDebug(m_Debug);
+    }
+    
+    m_library.setNumFolds(getNumFolds());
+    m_library.setValidationRatio(getValidationRatio());
+    // train all untrained models, and set "data" to the hillclimbing set.
+    Instances data = m_library.trainAll(trainData, m_workingDirectory.getAbsolutePath(),
+	m_algorithm);
+    // We cache the hillclimb predictions from all of the models in
+    // the library so that we can evaluate their performances when we
+    // combine them
+    // in various ways (without needing to keep the classifiers in memory).
+    double predictions[][][] = m_library.getHillclimbPredictions();
+    int numModels = predictions.length;
+    int modelWeights[] = new int[numModels];
+    m_total_weight = 0;
+    Random rand = new Random(m_Seed);
+    
+    if (m_algorithm == ALGORITHM_BUILD_LIBRARY) {
+      return;
+      
+    } else if (m_algorithm == ALGORITHM_BEST) {
+      // If we want to choose the best model, just make a model bag that
+      // includes all the models, then sort initialize to find the 1 that
+      // performs best.
+      ModelBag model_bag = new ModelBag(predictions, 1.0, m_Debug);
+      int[] modelPicked = model_bag.sortInitialize(1, false, data,
+	  m_hillclimbMetric);
+      // Then give it a weight of 1, while all others remain 0.
+      modelWeights[modelPicked[0]] = 1;
+    } else {
+      
+      if (m_Debug)
+	System.out.println("Starting hillclimbing algorithm: "
+	    + m_algorithm);
+      
+      for (int i = 0; i < getNumModelBags(); ++i) {
+	// For the number of bags,
+	if (m_Debug)
+	  System.out.println("Starting on ensemble bag: " + i);
+	// Create a new bag of the appropriate size
+	ModelBag modelBag = new ModelBag(predictions, getModelRatio(),
+	    m_Debug);
+	// And shuffle it.
+	modelBag.shuffle(rand);
+	if (getSortInitializationRatio() > 0.0) {
+	  // Sort initialize, if the ratio greater than 0.
+	  modelBag.sortInitialize((int) (getSortInitializationRatio()
+	      * getModelRatio() * numModels),
+	      getGreedySortInitialization(), data,
+	      m_hillclimbMetric);
+	}
+	
+	if (m_algorithm == ALGORITHM_BACKWARD) {
+	  // If we're doing backwards elimination, we just give all
+	  // models
+	  // a weight of 1 initially. If the # of hillclimb iterations
+	  // is too high, we'll end up with just one model in the end
+	  // (we never delete all models from a bag). TODO - it might
+	  // be
+	  // smarter to base this weight off of how many models we
+	  // have.
+	  modelBag.weightAll(1); // for now at least, I'm just
+	  // assuming 1.
+	}
+	// Now the bag is initialized, and we're ready to hillclimb.
+	for (int j = 0; j < getHillclimbIterations(); ++j) {
+	  if (m_algorithm == ALGORITHM_FORWARD) {
+	    modelBag.forwardSelect(getReplacement(), data,
+		m_hillclimbMetric);
+	  } else if (m_algorithm == ALGORITHM_BACKWARD) {
+	    modelBag.backwardEliminate(data, m_hillclimbMetric);
+	  } else if (m_algorithm == ALGORITHM_FORWARD_BACKWARD) {
+	    modelBag.forwardSelectOrBackwardEliminate(
+		getReplacement(), data, m_hillclimbMetric);
+	  }
+	}
+	// Now that we've done all the hillclimbing steps, we can just
+	// get
+	// the model weights that the bag determined, and add them to
+	// our
+	// running total.
+	int[] bagWeights = modelBag.getModelWeights();
+	for (int j = 0; j < bagWeights.length; ++j) {
+	  modelWeights[j] += bagWeights[j];
+	}
+      }
+    }
+    // Now we've done the hard work of actually learning the ensemble. Now
+    // we set up the appropriate data structures so that Ensemble Selection
+    // can
+    // make predictions for future test examples.
+    Set modelNames = m_library.getModelNames();
+    String[] modelNamesArray = new String[m_library.size()];
+    Iterator iter = modelNames.iterator();
+    // libraryIndex indexes over all the models in the library (not just
+    // those
+    // which we chose for the ensemble).
+    int libraryIndex = 0;
+    // chosenModels will count the total number of models which were
+    // selected
+    // by EnsembleSelection (those that have non-zero weight).
+    int chosenModels = 0;
+    while (iter.hasNext()) {
+      // Note that we have to be careful of order. Our model_weights array
+      // is in the same order as our list of models in m_library.
+      
+      // Get the name of the model,
+      modelNamesArray[libraryIndex] = (String) iter.next();
+      // and its weight.
+      int weightOfModel = modelWeights[libraryIndex++];
+      m_total_weight += weightOfModel;
+      if (weightOfModel > 0) {
+	// If the model was chosen at least once, increment the
+	// number of chosen models.
+	++chosenModels;
+      }
+    }
+    if (m_verboseOutput) {
+      // Output every model and its performance with respect to the
+      // validation
+      // data.
+      ModelBag bag = new ModelBag(predictions, 1.0, m_Debug);
+      int modelIndexes[] = bag.sortInitialize(modelNamesArray.length,
+	  false, data, m_hillclimbMetric);
+      double modelPerformance[] = bag.getIndividualPerformance(data,
+	  m_hillclimbMetric);
+      for (int i = 0; i < modelIndexes.length; ++i) {
+	// TODO - Could do this in a more readable way.
+	System.out.println("" + modelPerformance[i] + " "
+	    + modelNamesArray[modelIndexes[i]]);
+      }
+    }
+    // We're now ready to build our array of the models which were chosen
+    // and there associated weights.
+    m_chosen_models = new EnsembleSelectionLibraryModel[chosenModels];
+    m_chosen_model_weights = new int[chosenModels];
+    
+    libraryIndex = 0;
+    // chosenIndex indexes over the models which were chosen by
+    // EnsembleSelection
+    // (those which have non-zero weight).
+    int chosenIndex = 0;
+    iter = m_library.getModels().iterator();
+    while (iter.hasNext()) {
+      int weightOfModel = modelWeights[libraryIndex++];
+      
+      EnsembleSelectionLibraryModel model = (EnsembleSelectionLibraryModel) iter
+      .next();
+      
+      if (weightOfModel > 0) {
+	// If the model was chosen at least once, add it to our array
+	// of chosen models and weights.
+	m_chosen_models[chosenIndex] = model;
+	m_chosen_model_weights[chosenIndex] = weightOfModel;
+	// Note that the EnsembleSelectionLibraryModel may not be
+	// "loaded" -
+	// that is, its classifier(s) may be null pointers. That's okay
+	// -
+	// we'll "rehydrate" them later, if and when we need to.
+	++chosenIndex;
+      }
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    String stringInstance = instance.toString();
+    double cachedPreds[][] = null;
+    
+    if (m_cachedPredictions != null) {
+      // If we have any cached predictions (i.e., if cachePredictions was
+      // called), look for a cached set of predictions for this instance.
+      if (m_cachedPredictions.containsKey(stringInstance)) {
+	cachedPreds = (double[][]) m_cachedPredictions.get(stringInstance);
+      }
+    }
+    double[] prediction = new double[instance.numClasses()];
+    for (int i = 0; i < prediction.length; ++i) {
+      prediction[i] = 0.0;
+    }
+    
+    // Now do a weighted average of the predictions of each of our models.
+    for (int i = 0; i < m_chosen_models.length; ++i) {
+      double[] predictionForThisModel = null;
+      if (cachedPreds == null) {
+	// If there are no predictions cached, we'll load the model's
+	// classifier(s) in to memory and get the predictions.
+	m_chosen_models[i].rehydrateModel(m_workingDirectory.getAbsolutePath());
+	predictionForThisModel = m_chosen_models[i].getAveragePrediction(instance);
+	// We could release the model here to save memory, but we assume
+	// that there is enough available since we're not using the
+	// prediction caching functionality. If we load and release a
+	// model
+	// every time we need to get a prediction for an instance, it
+	// can be
+	// prohibitively slow.
+      } else {
+	// If it's cached, just get it from the array of cached preds
+	// for this instance.
+	predictionForThisModel = cachedPreds[i];
+      }
+      // We have encountered a bug where MultilayerPerceptron returns a
+      // null
+      // prediction array. If that happens, we just don't count that model
+      // in
+      // our ensemble prediction.
+      if (predictionForThisModel != null) {
+	// Okay, the model returned a valid prediction array, so we'll
+	// add the appropriate fraction of this model's prediction.
+	for (int j = 0; j < prediction.length; ++j) {
+	  prediction[j] += m_chosen_model_weights[i] * predictionForThisModel[j] / m_total_weight;
+	}
+      }
+    }
+    // normalize to add up to 1.
+    if (instance.classAttribute().isNominal()) {
+      if (Utils.sum(prediction) > 0)
+	Utils.normalize(prediction);
+    }
+    return prediction;
+  }
+  
+  /**
+   * This function tests whether or not a given path is appropriate for being
+   * the working directory. Specifically, we care that we can write to the
+   * path and that it doesn't point to a "non-directory" file handle.
+   * 
+   * @param dir		the directory to test
+   * @return 		true if the directory is valid
+   */
+  private boolean validWorkingDirectory(String dir) {
+    
+    boolean valid = false;
+    
+    File f = new File((dir));
+    
+    if (f.exists()) {
+      if (f.isDirectory() && f.canWrite())
+	valid = true;
+    } else {
+      if (f.canWrite())
+	valid = true;
+    }
+    
+    return valid;
+    
+  }
+  
+  /**
+   * This method tries to find a reasonable path name for the ensemble working
+   * directory where models and files will be stored.
+   * 
+   * 
+   * @return true if m_workingDirectory now has a valid file name
+   */
+  public static String getDefaultWorkingDirectory() {
+    
+    String defaultDirectory = new String("");
+    
+    boolean success = false;
+    
+    int i = 1;
+    
+    while (i < MAX_DEFAULT_DIRECTORIES && !success) {
+      
+      File f = new File(System.getProperty("user.home"), "Ensemble-" + i);
+      
+      if (!f.exists() && f.getParentFile().canWrite()) {
+	defaultDirectory = f.getPath();
+	success = true;
+      }
+      i++;
+      
+    }
+    
+    if (!success) {
+      defaultDirectory = new String("");
+      // should we print an error or something?
+    }
+    
+    return defaultDirectory;
+  }
+  
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return	a string representation of the classifier
+   */
+  public String toString() {
+    // We just print out the models which were selected, and the number
+    // of times each was selected.
+    String result = new String();
+    if (m_chosen_models != null) {
+      for (int i = 0; i < m_chosen_models.length; ++i) {
+	result += m_chosen_model_weights[i];
+	result += " " + m_chosen_models[i].getStringRepresentation()
+	+ "\n";
+      }
+    } else {
+      result = "No models selected.";
+    }
+    return result;
+  }
+  
+  /**
+   * Cache predictions for the individual base classifiers in the ensemble
+   * with respect to the given dataset. This is used so that when testing a
+   * large ensemble on a test set, we don't have to keep the models in memory.
+   * 
+   * @param test 	The instances for which to cache predictions.
+   * @throws Exception 	if somethng goes wrong
+   */
+  private void cachePredictions(Instances test) throws Exception {
+    m_cachedPredictions = new HashMap();
+    Evaluation evalModel = null;
+    Instances originalInstances = null;
+    // If the verbose flag is set, we'll also print out the performances of
+    // all the individual models w.r.t. this test set while we're at it.
+    boolean printModelPerformances = getVerboseOutput();
+    if (printModelPerformances) {
+      // To get performances, we need to keep the class attribute.
+      originalInstances = new Instances(test);
+    }
+    
+    // For each model, we'll go through the dataset and get predictions.
+    // The idea is we want to only have one model in memory at a time, so
+    // we'll
+    // load one model in to memory, get all its predictions, and add them to
+    // the
+    // hash map. Then we can release it from memory and move on to the next.
+    for (int i = 0; i < m_chosen_models.length; ++i) {
+      if (printModelPerformances) {
+	// If we're going to print predictions, we need to make a new
+	// Evaluation object.
+	evalModel = new Evaluation(originalInstances);
+      }
+      
+      Date startTime = new Date();
+      
+      // Load the model in to memory.
+      m_chosen_models[i].rehydrateModel(m_workingDirectory.getAbsolutePath());
+      // Now loop through all the instances and get the model's
+      // predictions.
+      for (int j = 0; j < test.numInstances(); ++j) {
+	Instance currentInstance = test.instance(j);
+	// When we're looking for a cached prediction later, we'll only
+	// have the non-class attributes, so we set the class missing
+	// here
+	// in order to make the string match up properly.
+	currentInstance.setClassMissing();
+	String stringInstance = currentInstance.toString();
+	
+	// When we come in here with the first model, the instance will
+	// not
+	// yet be part of the map.
+	if (!m_cachedPredictions.containsKey(stringInstance)) {
+	  // The instance isn't in the map yet, so add it.
+	  // For each instance, we store a two-dimensional array - the
+	  // first
+	  // index is over all the models in the ensemble, and the
+	  // second
+	  // index is over the (i.e., typical prediction array).
+	  int predSize = test.classAttribute().isNumeric() ? 1 : test
+	      .classAttribute().numValues();
+	  double predictionArray[][] = new double[m_chosen_models.length][predSize];
+	  m_cachedPredictions.put(stringInstance, predictionArray);
+	}
+	// Get the array from the map which is associated with this
+	// instance
+	double predictions[][] = (double[][]) m_cachedPredictions
+	.get(stringInstance);
+	// And add our model's prediction for it.
+	predictions[i] = m_chosen_models[i].getAveragePrediction(test
+	    .instance(j));
+	
+	if (printModelPerformances) {
+	  evalModel.evaluateModelOnceAndRecordPrediction(
+	      predictions[i], originalInstances.instance(j));
+	}
+      }
+      // Now we're done with model #i, so we can release it.
+      m_chosen_models[i].releaseModel();
+      
+      Date endTime = new Date();
+      long diff = endTime.getTime() - startTime.getTime();
+      
+      if (m_Debug)
+	System.out.println("Test time for "
+	    + m_chosen_models[i].getStringRepresentation()
+	    + " was: " + diff);
+      
+      if (printModelPerformances) {
+	String output = new String(m_chosen_models[i]
+	                                           .getStringRepresentation()
+	                                           + ": ");
+	output += "\tRMSE:" + evalModel.rootMeanSquaredError();
+	output += "\tACC:" + evalModel.pctCorrect();
+	if (test.numClasses() == 2) {
+	  // For multiclass problems, we could print these too, but
+	  // it's
+	  // not clear which class we should use in that case... so
+	  // instead
+	  // we only print these metrics for binary classification
+	  // problems.
+	  output += "\tROC:" + evalModel.areaUnderROC(1);
+	  output += "\tPREC:" + evalModel.precision(1);
+	  output += "\tFSCR:" + evalModel.fMeasure(1);
+	}
+	System.out.println(output);
+      }
+    }
+  }
+  
+  /**
+   * Return the technical information.  There is actually another
+   * paper that describes our current method of CV for this classifier
+   * TODO: Cite Technical report when published
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    
+    TechnicalInformation result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Rich Caruana, Alex Niculescu, Geoff Crew, and Alex Ksikes");
+    result.setValue(Field.TITLE, "Ensemble Selection from Libraries of Models");
+    result.setValue(Field.BOOKTITLE, "21st International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2004");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5480 $");
+  }
+  
+  /**
+   * Executes the classifier from commandline.
+   * 
+   * @param argv
+   *            should contain the following arguments: -t training file [-T
+   *            test file] [-c class index]
+   */
+  public static void main(String[] argv) {
+    
+    try {
+      
+      String options[] = (String[]) argv.clone();
+      
+      // do we get the input from XML instead of normal parameters?
+      String xml = Utils.getOption("xml", options);
+      if (!xml.equals(""))
+	options = new XMLOptions(xml).toArray();
+      
+      String trainFileName = Utils.getOption('t', options);
+      String objectInputFileName = Utils.getOption('l', options);
+      String testFileName = Utils.getOption('T', options);
+      
+      if (testFileName.length() != 0 && objectInputFileName.length() != 0
+	  && trainFileName.length() == 0) {
+	
+	System.out.println("Caching predictions");
+	
+	EnsembleSelection classifier = null;
+	
+	BufferedReader testReader = new BufferedReader(new FileReader(
+	    testFileName));
+	
+	// Set up the Instances Object
+	Instances test;
+	int classIndex = -1;
+	String classIndexString = Utils.getOption('c', options);
+	if (classIndexString.length() != 0) {
+	  classIndex = Integer.parseInt(classIndexString);
+	}
+	
+	test = new Instances(testReader, 1);
+	if (classIndex != -1) {
+	  test.setClassIndex(classIndex - 1);
+	} else {
+	  test.setClassIndex(test.numAttributes() - 1);
+	}
+	if (classIndex > test.numAttributes()) {
+	  throw new Exception("Index of class attribute too large.");
+	}
+	
+	while (test.readInstance(testReader)) {
+	  
+	}
+	testReader.close();
+	
+	// Now yoink the EnsembleSelection Object from the fileSystem
+	
+	InputStream is = new FileInputStream(objectInputFileName);
+	if (objectInputFileName.endsWith(".gz")) {
+	  is = new GZIPInputStream(is);
+	}
+	
+	// load from KOML?
+	if (!(objectInputFileName.endsWith("UpdateableClassifier.koml") && KOML
+	    .isPresent())) {
+	  ObjectInputStream objectInputStream = new ObjectInputStream(
+	      is);
+	  classifier = (EnsembleSelection) objectInputStream
+	  .readObject();
+	  objectInputStream.close();
+	} else {
+	  BufferedInputStream xmlInputStream = new BufferedInputStream(
+	      is);
+	  classifier = (EnsembleSelection) KOML.read(xmlInputStream);
+	  xmlInputStream.close();
+	}
+	
+	String workingDir = Utils.getOption('W', argv);
+	if (!workingDir.equals("")) {
+	  classifier.setWorkingDirectory(new File(workingDir));
+	}
+	
+	classifier.setDebug(Utils.getFlag('D', argv));
+	classifier.setVerboseOutput(Utils.getFlag('O', argv));
+	
+	classifier.cachePredictions(test);
+	
+	// Now we write the model back out to the file system.
+	String objectOutputFileName = objectInputFileName;
+	OutputStream os = new FileOutputStream(objectOutputFileName);
+	// binary
+	if (!(objectOutputFileName.endsWith(".xml") || (objectOutputFileName
+	    .endsWith(".koml") && KOML.isPresent()))) {
+	  if (objectOutputFileName.endsWith(".gz")) {
+	    os = new GZIPOutputStream(os);
+	  }
+	  ObjectOutputStream objectOutputStream = new ObjectOutputStream(
+	      os);
+	  objectOutputStream.writeObject(classifier);
+	  objectOutputStream.flush();
+	  objectOutputStream.close();
+	}
+	// KOML/XML
+	else {
+	  BufferedOutputStream xmlOutputStream = new BufferedOutputStream(
+	      os);
+	  if (objectOutputFileName.endsWith(".xml")) {
+	    XMLSerialization xmlSerial = new XMLClassifier();
+	    xmlSerial.write(xmlOutputStream, classifier);
+	  } else
+	    // whether KOML is present has already been checked
+	    // if not present -> ".koml" is interpreted as binary - see
+	    // above
+	    if (objectOutputFileName.endsWith(".koml")) {
+	      KOML.write(xmlOutputStream, classifier);
+	    }
+	  xmlOutputStream.close();
+	}
+	
+      }
+      
+      System.out.println(Evaluation.evaluateModel(
+	  new EnsembleSelection(), argv));
+      
+    } catch (Exception e) {
+      if (    (e.getMessage() != null)
+ 	   && (e.getMessage().indexOf("General options") == -1) )
+	e.printStackTrace();
+      else
+	System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/FilteredClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/FilteredClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/FilteredClassifier.java	(revision 29)
@@ -0,0 +1,480 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilteredClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for running an arbitrary classifier on data that has been passed through an arbitrary filter. Like the classifier, the structure of the filter is based exclusively on the training data and test instances will be processed by the filter without changing their structure.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class FilteredClassifier 
+  extends SingleClassifierEnhancer 
+  implements Drawable {
+
+  /** for serialization */
+  static final long serialVersionUID = -4523450618538717400L;
+  
+  /** The filter */
+  protected Filter m_Filter = new weka.filters.supervised.attribute.AttributeSelection();
+
+  /** The instance structure of the filtered instances */
+  protected Instances m_FilteredInstances;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return   "Class for running an arbitrary classifier on data that has been passed "
+      + "through an arbitrary filter. Like the classifier, the structure of the filter "
+      + "is based exclusively on the training data and test instances will be processed "
+      + "by the filter without changing their structure.";
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Default constructor.
+   */
+  public FilteredClassifier() {
+
+    m_Classifier = new weka.classifiers.trees.J48();
+    m_Filter = new weka.filters.supervised.attribute.Discretize();
+  }
+
+  /**
+   * Returns the type of graph this classifier
+   * represents.
+   *  
+   * @return the graph type of this classifier
+   */   
+  public int graphType() {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graphType();
+    else 
+      return Drawable.NOT_DRAWABLE;
+  }
+
+  /**
+   * Returns graph describing the classifier (if possible).
+   *
+   * @return the graph of the classifier in dotty format
+   * @throws Exception if the classifier cannot be graphed
+   */
+  public String graph() throws Exception {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graph();
+    else throw new Exception("Classifier: " + getClassifierSpec()
+			     + " cannot be graphed");
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option(
+	      "\tFull class name of filter to use, followed\n"
+	      + "\tby filter options.\n"
+	      + "\teg: \"weka.filters.unsupervised.attribute.Remove -V -R 1,2\"",
+	      "F", 1, "-F <filter specification>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.J48)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.J48:
+   * </pre>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    // Same for filter
+    String filterString = Utils.getOption('F', options);
+    if (filterString.length() > 0) {
+      String [] filterSpec = Utils.splitOptions(filterString);
+      if (filterSpec.length == 0) {
+	throw new IllegalArgumentException("Invalid filter specification string");
+      }
+      String filterName = filterSpec[0];
+      filterSpec[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
+    } else {
+      setFilter(new weka.filters.supervised.attribute.Discretize());
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+    int current = 0;
+
+    options[current++] = "-F";
+    options[current++] = "" + getFilterSpec();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used.";
+  }
+
+  /**
+   * Sets the filter
+   *
+   * @param filter the filter with all options set.
+   */
+  public void setFilter(Filter filter) {
+
+    m_Filter = filter;
+  }
+
+  /**
+   * Gets the filter used.
+   *
+   * @return the filter
+   */
+  public Filter getFilter() {
+
+    return m_Filter;
+  }
+  
+  /**
+   * Gets the filter specification string, which contains the class name of
+   * the filter and any options to the filter
+   *
+   * @return the filter string.
+   */
+  protected String getFilterSpec() {
+    
+    Filter c = getFilter();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getFilter() == null)
+      result = super.getCapabilities();
+    else
+      result = getFilter().getCapabilities();
+    
+    // the filtered classifier always needs a class
+    result.disable(Capability.NO_CLASS);
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Build the classifier on the filtered data.
+   *
+   * @param data the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    if (m_Classifier == null) {
+      throw new Exception("No base classifiers have been set!");
+    }
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    /*
+    String fname = m_Filter.getClass().getName();
+    fname = fname.substring(fname.lastIndexOf('.') + 1);
+    util.Timer t = util.Timer.getTimer("FilteredClassifier::" + fname);
+    t.start();
+    */
+    m_Filter.setInputFormat(data);  // filter capabilities are checked here
+    data = Filter.useFilter(data, m_Filter);
+    //t.stop();
+
+    // can classifier handle the data?
+    getClassifier().getCapabilities().testWithFail(data);
+
+    m_FilteredInstances = data.stringFreeStructure();
+    m_Classifier.buildClassifier(data);
+  }
+
+  /**
+   * Classifies a given instance after filtering.
+   *
+   * @param instance the instance to be classified
+   * @return the class distribution for the given instance
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance)
+    throws Exception {
+
+    /*
+      System.err.println("FilteredClassifier:: " 
+                         + m_Filter.getClass().getName()
+                         + " in: " + instance);
+    */
+    if (m_Filter.numPendingOutput() > 0) {
+      throw new Exception("Filter output queue not empty!");
+    }
+    /*
+    String fname = m_Filter.getClass().getName();
+    fname = fname.substring(fname.lastIndexOf('.') + 1);
+    util.Timer t = util.Timer.getTimer("FilteredClassifier::" + fname);
+    t.start();
+    */
+    if (!m_Filter.input(instance)) {
+      throw new Exception("Filter didn't make the test instance"
+			  + " immediately available!");
+    }
+    m_Filter.batchFinished();
+    Instance newInstance = m_Filter.output();
+    //t.stop();
+    /*
+    System.err.println("FilteredClassifier:: " 
+                       + m_Filter.getClass().getName()
+                       + " out: " + newInstance);
+    */
+    return m_Classifier.distributionForInstance(newInstance);
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a representation of this classifier
+   */
+  public String toString() {
+
+    if (m_FilteredInstances == null) {
+      return "FilteredClassifier: No model built yet.";
+    }
+
+    String result = "FilteredClassifier using "
+      + getClassifierSpec()
+      + " on data filtered through "
+      + getFilterSpec()
+      + "\n\nFiltered Header\n"
+      + m_FilteredInstances.toString()
+      + "\n\nClassifier Model\n"
+      + m_Classifier.toString();
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv)  {
+    runClassifier(new FilteredClassifier(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Grading.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Grading.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Grading.java	(revision 29)
@@ -0,0 +1,392 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Grading.java
+ *    Copyright (C) 2000 University of Waikato
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements Grading. The base classifiers are "graded".<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * A.K. Seewald, J. Fuernkranz: An Evaluation of Grading Classifiers. In: Advances in Intelligent Data Analysis: 4th International Conference, Berlin/Heidelberg/New York/Tokyo, 115-124, 2001.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Seewald2001,
+ *    address = {Berlin/Heidelberg/New York/Tokyo},
+ *    author = {A.K. Seewald and J. Fuernkranz},
+ *    booktitle = {Advances in Intelligent Data Analysis: 4th International Conference},
+ *    editor = {F. Hoffmann et al.},
+ *    pages = {115-124},
+ *    publisher = {Springer},
+ *    title = {An Evaluation of Grading Classifiers},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;scheme specification&gt;
+ *  Full name of meta classifier, followed by options.
+ *  (default: "weka.classifiers.rules.Zero")</pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Sets the number of cross-validation folds.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -B &lt;classifier specification&gt;
+ *  Full class name of classifier to include, followed
+ *  by scheme options. May be specified multiple times.
+ *  (default: "weka.classifiers.rules.ZeroR")</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Alexander K. Seewald (alex@seewald.at)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class Grading 
+  extends Stacking
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5207837947890081170L;
+  
+  /** The meta classifiers, one for each base classifier. */
+  protected Classifier [] m_MetaClassifiers = new Classifier[0];
+
+  /** InstPerClass */
+  protected double [] m_InstPerClass = null;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return 
+        "Implements Grading. The base classifiers are \"graded\".\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "A.K. Seewald and J. Fuernkranz");
+    result.setValue(Field.TITLE, "An Evaluation of Grading Classifiers");
+    result.setValue(Field.BOOKTITLE, "Advances in Intelligent Data Analysis: 4th International Conference");
+    result.setValue(Field.EDITOR, "F. Hoffmann et al.");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "115-124");
+    result.setValue(Field.PUBLISHER, "Springer");
+    result.setValue(Field.ADDRESS, "Berlin/Heidelberg/New York/Tokyo");
+    
+    return result;
+  }
+
+  /**
+   * Generates the meta data
+   * 
+   * @param newData the data to work on
+   * @param random the random number generator used in the generation
+   * @throws Exception if generation fails
+   */
+  protected void generateMetaLevel(Instances newData, Random random) 
+    throws Exception {
+
+    m_MetaFormat = metaFormat(newData);
+    Instances [] metaData = new Instances[m_Classifiers.length];
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      metaData[i] = metaFormat(newData);
+    }
+    for (int j = 0; j < m_NumFolds; j++) {
+
+      Instances train = newData.trainCV(m_NumFolds, j, random);
+      Instances test = newData.testCV(m_NumFolds, j);
+
+      // Build base classifiers
+      for (int i = 0; i < m_Classifiers.length; i++) {
+	getClassifier(i).buildClassifier(train);
+        for (int k = 0; k < test.numInstances(); k++) {
+	  metaData[i].add(metaInstance(test.instance(k),i));
+        }
+      }
+    }
+        
+    // calculate InstPerClass
+    m_InstPerClass = new double[newData.numClasses()];
+    for (int i=0; i < newData.numClasses(); i++) m_InstPerClass[i]=0.0;
+    for (int i=0; i < newData.numInstances(); i++) {
+      m_InstPerClass[(int)newData.instance(i).classValue()]++;
+    }
+    
+    m_MetaClassifiers = AbstractClassifier.makeCopies(m_MetaClassifier,
+					      m_Classifiers.length);
+
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      m_MetaClassifiers[i].buildClassifier(metaData[i]);
+    }
+  }
+
+  /**
+   * Returns class probabilities for a given instance using the stacked classifier.
+   * One class will always get all the probability mass (i.e. probability one).
+   *
+   * @param instance the instance to be classified
+   * @throws Exception if instance could not be classified
+   * successfully
+   * @return the class distribution for the given instance
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    double maxPreds;
+    int numPreds=0;
+    int numClassifiers=m_Classifiers.length;
+    int idxPreds;
+    double [] predConfs = new double[numClassifiers];
+    double [] preds;
+
+    for (int i=0; i<numClassifiers; i++) {
+      preds = m_MetaClassifiers[i].distributionForInstance(metaInstance(instance,i));
+      if (m_MetaClassifiers[i].classifyInstance(metaInstance(instance,i))==1)
+        predConfs[i]=preds[1];
+      else
+        predConfs[i]=-preds[0];
+    }
+    if (predConfs[Utils.maxIndex(predConfs)]<0.0) { // no correct classifiers
+      for (int i=0; i<numClassifiers; i++)   // use neg. confidences instead
+        predConfs[i]=1.0+predConfs[i];
+    } else {
+      for (int i=0; i<numClassifiers; i++)   // otherwise ignore neg. conf
+        if (predConfs[i]<0) predConfs[i]=0.0;
+    }
+
+    /*System.out.print(preds[0]);
+    System.out.print(":");
+    System.out.print(preds[1]);
+    System.out.println("#");*/
+
+    preds=new double[instance.numClasses()];
+    for (int i=0; i<instance.numClasses(); i++) preds[i]=0.0;
+    for (int i=0; i<numClassifiers; i++) {
+      idxPreds=(int)(m_Classifiers[i].classifyInstance(instance));
+      preds[idxPreds]+=predConfs[i];
+    }
+
+    maxPreds=preds[Utils.maxIndex(preds)];
+    int MaxInstPerClass=-100;
+    int MaxClass=-1;
+    for (int i=0; i<instance.numClasses(); i++) {
+      if (preds[i]==maxPreds) {
+        numPreds++;
+        if (m_InstPerClass[i]>MaxInstPerClass) {
+          MaxInstPerClass=(int)m_InstPerClass[i];
+          MaxClass=i;
+        }
+      }
+    }
+
+    int predictedIndex;
+    if (numPreds==1)
+      predictedIndex = Utils.maxIndex(preds);
+    else
+    {
+      // System.out.print("?");
+      // System.out.print(instance.toString());
+      // for (int i=0; i<instance.numClasses(); i++) {
+      //   System.out.print("/");
+      //   System.out.print(preds[i]);
+      // }
+      // System.out.println(MaxClass);
+      predictedIndex = MaxClass;
+    }
+    double[] classProbs = new double[instance.numClasses()];
+    classProbs[predictedIndex] = 1.0;
+    return classProbs;
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifiers.length == 0) {
+      return "Grading: No base schemes entered.";
+    }
+    if (m_MetaClassifiers.length == 0) {
+      return "Grading: No meta scheme selected.";
+    }
+    if (m_MetaFormat == null) {
+      return "Grading: No model built yet.";
+    }
+    String result = "Grading\n\nBase classifiers\n\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += getClassifier(i).toString() +"\n\n";
+    }
+   
+    result += "\n\nMeta classifiers\n\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += m_MetaClassifiers[i].toString() +"\n\n";
+    }
+
+    return result;
+  }
+
+  /**
+   * Makes the format for the level-1 data.
+   *
+   * @param instances the level-0 format
+   * @return the format for the meta data
+   * @throws Exception if an error occurs
+   */
+  protected Instances metaFormat(Instances instances) throws Exception {
+
+    FastVector attributes = new FastVector();
+    Instances metaFormat;
+    
+    for (int i = 0; i<instances.numAttributes(); i++) {
+	if ( i != instances.classIndex() ) {
+	    attributes.addElement(instances.attribute(i));
+	}
+    }
+
+    FastVector nomElements = new FastVector(2);
+    nomElements.addElement("0");
+    nomElements.addElement("1");
+    attributes.addElement(new Attribute("PredConf",nomElements));
+
+    metaFormat = new Instances("Meta format", attributes, 0);
+    metaFormat.setClassIndex(metaFormat.numAttributes()-1);
+    return metaFormat;
+  }
+
+  /**
+   * Makes a level-1 instance from the given instance.
+   * 
+   * @param instance the instance to be transformed
+   * @param k index of the classifier
+   * @return the level-1 instance
+   * @throws Exception if an error occurs
+   */
+  protected Instance metaInstance(Instance instance, int k) throws Exception {
+
+    double[] values = new double[m_MetaFormat.numAttributes()];
+    Instance metaInstance;
+    double predConf;
+    int i;
+    int maxIdx;
+    double maxVal;
+
+    int idx = 0;
+    for (i = 0; i < instance.numAttributes(); i++) {
+	if (i != instance.classIndex()) {
+	    values[idx] = instance.value(i);
+	    idx++;
+	}
+    }
+
+    Classifier classifier = getClassifier(k);
+
+    if (m_BaseFormat.classAttribute().isNumeric()) {
+      throw new Exception("Class Attribute must not be numeric!");
+    } else {
+      double[] dist = classifier.distributionForInstance(instance);
+      
+      maxIdx=0;
+      maxVal=dist[0];
+      for (int j = 1; j < dist.length; j++) {
+	if (dist[j]>maxVal) {
+	  maxVal=dist[j];
+	  maxIdx=j;
+	}
+      }
+      predConf= (instance.classValue()==maxIdx) ? 1:0;
+    }
+    
+    values[idx]=predConf;
+    metaInstance = new DenseInstance(1, values);
+    metaInstance.setDataset(m_MetaFormat);
+    return metaInstance;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new Grading(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/GridSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/GridSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/GridSearch.java	(revision 29)
@@ -0,0 +1,3508 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GridSearch.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.functions.LinearRegression;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Debug;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MathematicalExpression;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.PropertyPath;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SerializedObject;
+import weka.core.Summarizable;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.PLSFilter;
+import weka.filters.unsupervised.attribute.MathExpression;
+import weka.filters.unsupervised.attribute.NumericCleaner;
+import weka.filters.unsupervised.instance.Resample;
+
+import java.beans.PropertyDescriptor;
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Performs a grid search of parameter pairs for the a classifier (Y-axis, default is LinearRegression with the "Ridge" parameter) and the PLSFilter (X-axis, "# of Components") and chooses the best pair found for the actual predicting.<br/>
+ * <br/>
+ * The initial grid is worked on with 2-fold CV to determine the values of the parameter pairs for the selected type of evaluation (e.g., accuracy). The best point in the grid is then taken and a 10-fold CV is performed with the adjacent parameter pairs. If a better pair is found, then this will act as new center and another 10-fold CV will be performed (kind of hill-climbing). This process is repeated until no better pair is found or the best pair is on the border of the grid.<br/>
+ * In case the best pair is on the border, one can let GridSearch automatically extend the grid and continue the search. Check out the properties 'gridIsExtendable' (option '-extend-grid') and 'maxGridExtensions' (option '-max-grid-extensions &lt;num&gt;').<br/>
+ * <br/>
+ * GridSearch can handle doubles, integers (values are just cast to int) and booleans (0 is false, otherwise true). float, char and long are supported as well.<br/>
+ * <br/>
+ * The best filter/classifier setup can be accessed after the buildClassifier call via the getBestFilter/getBestClassifier methods.<br/>
+ * Note on the implementation: after the data has been passed through the filter, a default NumericCleaner filter is applied to the data in order to avoid numbers that are getting too small and might produce NaNs in other schemes.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC|KAP&gt;
+ *  Determines the parameter used for evaluation:
+ *  CC = Correlation coefficient
+ *  RMSE = Root mean squared error
+ *  RRSE = Root relative squared error
+ *  MAE = Mean absolute error
+ *  RAE = Root absolute error
+ *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
+ *  ACC = Accuracy
+ *  KAP = Kappa
+ *  (default: CC)</pre>
+ * 
+ * <pre> -y-property &lt;option&gt;
+ *  The Y option to test (without leading dash).
+ *  (default: classifier.ridge)</pre>
+ * 
+ * <pre> -y-min &lt;num&gt;
+ *  The minimum for Y.
+ *  (default: -10)</pre>
+ * 
+ * <pre> -y-max &lt;num&gt;
+ *  The maximum for Y.
+ *  (default: +5)</pre>
+ * 
+ * <pre> -y-step &lt;num&gt;
+ *  The step size for Y.
+ *  (default: 1)</pre>
+ * 
+ * <pre> -y-base &lt;num&gt;
+ *  The base for Y.
+ *  (default: 10)</pre>
+ * 
+ * <pre> -y-expression &lt;expr&gt;
+ *  The expression for Y.
+ *  Available parameters:
+ *   BASE
+ *   FROM
+ *   TO
+ *   STEP
+ *   I - the current iteration value
+ *   (from 'FROM' to 'TO' with stepsize 'STEP')
+ *  (default: 'pow(BASE,I)')</pre>
+ * 
+ * <pre> -filter &lt;filter specification&gt;
+ *  The filter to use (on X axis). Full classname of filter to include, 
+ *  followed by scheme options.
+ *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
+ * 
+ * <pre> -x-property &lt;option&gt;
+ *  The X option to test (without leading dash).
+ *  (default: filter.numComponents)</pre>
+ * 
+ * <pre> -x-min &lt;num&gt;
+ *  The minimum for X.
+ *  (default: +5)</pre>
+ * 
+ * <pre> -x-max &lt;num&gt;
+ *  The maximum for X.
+ *  (default: +20)</pre>
+ * 
+ * <pre> -x-step &lt;num&gt;
+ *  The step size for X.
+ *  (default: 1)</pre>
+ * 
+ * <pre> -x-base &lt;num&gt;
+ *  The base for X.
+ *  (default: 10)</pre>
+ * 
+ * <pre> -x-expression &lt;expr&gt;
+ *  The expression for the X value.
+ *  Available parameters:
+ *   BASE
+ *   MIN
+ *   MAX
+ *   STEP
+ *   I - the current iteration value
+ *   (from 'FROM' to 'TO' with stepsize 'STEP')
+ *  (default: 'pow(BASE,I)')</pre>
+ * 
+ * <pre> -extend-grid
+ *  Whether the grid can be extended.
+ *  (default: no)</pre>
+ * 
+ * <pre> -max-grid-extensions &lt;num&gt;
+ *  The maximum number of grid extensions (-1 is unlimited).
+ *  (default: 3)</pre>
+ * 
+ * <pre> -sample-size &lt;num&gt;
+ *  The size (in percent) of the sample to search the inital grid with.
+ *  (default: 100)</pre>
+ * 
+ * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
+ *  The type of traversal for the grid.
+ *  (default: COLUMN-WISE)</pre>
+ * 
+ * <pre> -log-file &lt;filename&gt;
+ *  The log file to log the messages to.
+ *  (default: none)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.functions.LinearRegression)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.functions.LinearRegression:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Produce debugging output.
+ *  (default no debugging output)</pre>
+ * 
+ * <pre> -S &lt;number of selection method&gt;
+ *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
+ *  (default 0 = M5' method)</pre>
+ * 
+ * <pre> -C
+ *  Do not try to eliminate colinear attributes.
+ * </pre>
+ * 
+ * <pre> -R &lt;double&gt;
+ *  Set ridge parameter (default 1.0e-8).
+ * </pre>
+ * 
+ * <pre> 
+ * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
+ * </pre>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The number of components to compute.
+ *  (default: 20)</pre>
+ * 
+ * <pre> -U
+ *  Updates the class attribute as well.
+ *  (default: off)</pre>
+ * 
+ * <pre> -M
+ *  Turns replacing of missing values on.
+ *  (default: off)</pre>
+ * 
+ * <pre> -A &lt;SIMPLS|PLS1&gt;
+ *  The algorithm to use.
+ *  (default: PLS1)</pre>
+ * 
+ * <pre> -P &lt;none|center|standardize&gt;
+ *  The type of preprocessing that is applied to the data.
+ *  (default: center)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Examples:
+ * <ul>
+ *   <li>
+ *     <b>Optimizing SMO with RBFKernel (C and gamma)</b>
+ *     <ul>
+ *       <li>Set the evaluation to <i>Accuracy</i>.</li>
+ *       <li>Set the filter to <code>weka.filters.AllFilter</code> since we
+ *           don't need any special data processing and we don't optimize the
+ *           filter in this case (data gets always passed through filter!).</li>
+ *       <li>Set <code>weka.classifiers.functions.SMO</code> as classifier
+ *           with <code>weka.classifiers.functions.supportVector.RBFKernel</code>
+ *           as kernel.
+ *       </li>
+ *       <li>Set the XProperty to "classifier.c", XMin to "1", XMax to "16", 
+ *           XStep to "1" and the XExpression to "I". This will test the "C"
+ *           parameter of SMO for the values from 1 to 16.</li>
+ *       <li>Set the YProperty to "classifier.kernel.gamma", YMin to "-5",
+ *           YMax to "2", YStep to "1" YBase to "10" and YExpression to 
+ *           "pow(BASE,I)". This will test the gamma of the RBFKernel with the
+ *           values 10^-5, 10^-4,..,10^2.</li>
+ *     </ul>
+ *   </li>
+ *   <li>
+ *     <b>Optimizing PLSFilter with LinearRegression (# of components and ridge) - default setup</b>
+ *     <ul>
+ *       <li>Set the evaluation to <i>Correlation coefficient</i>.</li>
+ *       <li>Set the filter to <code>weka.filters.supervised.attribute.PLSFilter</code>.</li>
+ *       <li>Set <code>weka.classifiers.functions.LinearRegression</code> as 
+ *           classifier and use no attribute selection and no elimination of
+ *           colinear attributes.</li>
+ *       <li>Set the XProperty to "filter.numComponents", XMin to "5", XMax 
+ *           to "20" (this depends heavily on your dataset, should be no more
+ *           than the number of attributes!), XStep to "1" and XExpression to
+ *           "I". This will test the number of components the PLSFilter will
+ *           produce from 5 to 20.</li>
+ *       <li>Set the YProperty to "classifier.ridge", XMin to "-10", XMax to 
+ *           "5", YStep to "1" and YExpression to "pow(BASE,I)". This will
+ *           try ridge parameters from 10^-10 to 10^5.</li>
+ *     </ul>
+ *   </li>
+ * </ul>
+ * 
+ * General notes:
+ * <ul>
+ *   <li>Turn the <i>debug</i> flag on in order to see some progress output in the
+ *       console</li>
+ *   <li>If you want to view the fitness landscape that GridSearch explores,
+ *       select a <i>log file</i>. This log will then contain Gnuplot data and 
+ *       script block for viewing the landscape. Just copy paste those blocks 
+ *       into files named accordingly and run Gnuplot with them.</li>
+ * </ul>
+ *
+ * @author  Bernhard Pfahringer (bernhard at cs dot waikato dot ac dot nz)
+ * @author  Geoff Holmes (geoff at cs dot waikato dot ac dot nz)
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ * @see     PLSFilter
+ * @see     LinearRegression
+ * @see	    NumericCleaner
+ */
+public class GridSearch
+  extends RandomizableSingleClassifierEnhancer
+  implements AdditionalMeasureProducer, Summarizable {
+
+  /**
+   * a serializable version of Point2D.Double
+   * 
+   * @see java.awt.geom.Point2D.Double
+   */
+  protected class PointDouble
+    extends java.awt.geom.Point2D.Double
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 7151661776161898119L;
+    
+    /**
+     * the default constructor
+     * 
+     * @param x		the x value of the point
+     * @param y		the y value of the point
+     */
+    public PointDouble(double x, double y) {
+      super(x, y);
+    }
+
+    /**
+     * Determines whether or not two points are equal.
+     * 
+     * @param obj 	an object to be compared with this PointDouble
+     * @return 		true if the object to be compared has the same values; 
+     * 			false otherwise.
+     */
+    public boolean equals(Object obj) {
+      PointDouble 	pd;
+      
+      pd = (PointDouble) obj;
+      
+      return (Utils.eq(this.getX(), pd.getX()) && Utils.eq(this.getY(), pd.getY()));
+    }
+    
+    /**
+     * returns a string representation of the Point
+     * 
+     * @return the point as string
+     */
+    public String toString() {
+      return super.toString().replaceAll(".*\\[", "[");
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * a serializable version of Point
+   * 
+   * @see java.awt.Point
+   */
+  protected class PointInt
+    extends java.awt.Point
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -5900415163698021618L;
+
+    /**
+     * the default constructor
+     * 
+     * @param x		the x value of the point
+     * @param y		the y value of the point
+     */
+    public PointInt(int x, int y) {
+      super(x, y);
+    }
+    
+    /**
+     * returns a string representation of the Point
+     * 
+     * @return the point as string
+     */
+    public String toString() {
+      return super.toString().replaceAll(".*\\[", "[");
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /**
+   * for generating the parameter pairs in a grid
+   */
+  protected class Grid
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 7290732613611243139L;
+    
+    /** the minimum on the X axis */
+    protected double m_MinX;
+    
+    /** the maximum on the X axis */
+    protected double m_MaxX;
+    
+    /** the step size for the X axis */
+    protected double m_StepX;
+
+    /** the label for the X axis */
+    protected String m_LabelX;
+    
+    /** the minimum on the Y axis */
+    protected double m_MinY;
+    
+    /** the maximum on the Y axis */
+    protected double m_MaxY;
+    
+    /** the step size for the Y axis */
+    protected double m_StepY;
+
+    /** the label for the Y axis */
+    protected String m_LabelY;
+    
+    /** the number of points on the X axis */
+    protected int m_Width;
+    
+    /** the number of points on the Y axis */
+    protected int m_Height;
+    
+    /**
+     * initializes the grid
+     * 
+     * @param minX 	the minimum on the X axis
+     * @param maxX 	the maximum on the X axis
+     * @param stepX 	the step size for the X axis
+     * @param minY 	the minimum on the Y axis
+     * @param maxY 	the maximum on the Y axis
+     * @param stepY 	the step size for the Y axis
+     */
+    public Grid(double minX, double maxX, double stepX, 
+	        double minY, double maxY, double stepY) {
+      this(minX, maxX, stepX, "", minY, maxY, stepY, "");
+    }
+
+    
+    /**
+     * initializes the grid
+     * 
+     * @param minX 	the minimum on the X axis
+     * @param maxX 	the maximum on the X axis
+     * @param stepX 	the step size for the X axis
+     * @param labelX	the label for the X axis
+     * @param minY 	the minimum on the Y axis
+     * @param maxY 	the maximum on the Y axis
+     * @param stepY 	the step size for the Y axis
+     * @param labelY	the label for the Y axis
+     */
+    public Grid(double minX, double maxX, double stepX, String labelX,
+	        double minY, double maxY, double stepY, String labelY) {
+
+      super();
+      
+      m_MinX   = minX;
+      m_MaxX   = maxX;
+      m_StepX  = stepX;
+      m_LabelX = labelX;
+      m_MinY   = minY;
+      m_MaxY   = maxY;
+      m_StepY  = stepY;
+      m_LabelY = labelY;
+      m_Height = (int) StrictMath.round((m_MaxY - m_MinY) / m_StepY) + 1;
+      m_Width  = (int) StrictMath.round((m_MaxX - m_MinX) / m_StepX) + 1;
+      
+      // is min < max?
+      if (m_MinX >= m_MaxX)
+	throw new IllegalArgumentException("XMin must be smaller than XMax!");
+      if (m_MinY >= m_MaxY)
+	throw new IllegalArgumentException("YMin must be smaller than YMax!");
+      
+      // steps positive?
+      if (m_StepX <= 0)
+	throw new IllegalArgumentException("XStep must be a positive number!");
+      if (m_StepY <= 0)
+	throw new IllegalArgumentException("YStep must be a positive number!");
+      
+      // check borders
+      if (!Utils.eq(m_MinX + (m_Width-1)*m_StepX, m_MaxX))
+	throw new IllegalArgumentException(
+	    "X axis doesn't match! Provided max: " + m_MaxX 
+	    + ", calculated max via min and step size: " 
+	    + (m_MinX + (m_Width-1)*m_StepX));
+      if (!Utils.eq(m_MinY + (m_Height-1)*m_StepY, m_MaxY))
+	throw new IllegalArgumentException(
+	    "Y axis doesn't match! Provided max: " + m_MaxY 
+	    + ", calculated max via min and step size: " 
+	    + (m_MinY + (m_Height-1)*m_StepY));
+    }
+
+    /**
+     * Tests itself against the provided grid object
+     * 
+     * @param o		the grid object to compare against
+     * @return		if the two grids have the same setup
+     */
+    public boolean equals(Object o) {
+      boolean	result;
+      Grid	g;
+      
+      g = (Grid) o;
+      
+      result =    (width() == g.width())
+               && (height() == g.height())
+               && (getMinX() == g.getMinX())
+               && (getMinY() == g.getMinY())
+               && (getStepX() == g.getStepX())
+               && (getStepY() == g.getStepY())
+               && getLabelX().equals(g.getLabelX())
+               && getLabelY().equals(g.getLabelY());
+      
+      return result;
+    }
+    
+    /**
+     * returns the left border
+     * 
+     * @return 		the left border
+     */
+    public double getMinX() {
+      return m_MinX;
+    }
+    
+    /**
+     * returns the right border
+     * 
+     * @return 		the right border
+     */
+    public double getMaxX() {
+      return m_MaxX;
+    }
+    
+    /**
+     * returns the step size on the X axis
+     * 
+     * @return 		the step size
+     */
+    public double getStepX() {
+      return m_StepX;
+    }
+    
+    /**
+     * returns the label for the X axis
+     * 
+     * @return		the label
+     */
+    public String getLabelX() {
+      return m_LabelX;
+    }
+    
+    /**
+     * returns the bottom border
+     * 
+     * @return 		the bottom border
+     */
+    public double getMinY() {
+      return m_MinY;
+    }
+    
+    /**
+     * returns the top border
+     * 
+     * @return 		the top border
+     */
+    public double getMaxY() {
+      return m_MaxY;
+    }
+    
+    /**
+     * returns the step size on the Y axis
+     * 
+     * @return 		the step size
+     */
+    public double getStepY() {
+      return m_StepY;
+    }
+    
+    /**
+     * returns the label for the Y axis
+     * 
+     * @return		the label
+     */
+    public String getLabelY() {
+      return m_LabelY;
+    }
+    
+    /**
+     * returns the number of points in the grid on the Y axis (incl. borders)
+     * 
+     * @return 		the number of points in the grid on the Y axis
+     */
+    public int height() {
+      return m_Height;
+    }
+    
+    /**
+     * returns the number of points in the grid on the X axis (incl. borders)
+     * 
+     * @return 		the number of points in the grid on the X axis
+     */
+    public int width() {
+      return m_Width;
+    }
+
+    /**
+     * returns the values at the given point in the grid
+     * 
+     * @param x		the x-th point on the X axis
+     * @param y		the y-th point on the Y axis
+     * @return		the value pair at the given position
+     */
+    public PointDouble getValues(int x, int y) {
+      if (x >= width())
+	throw new IllegalArgumentException("Index out of scope on X axis (" + x + " >= " + width() + ")!");
+      if (y >= height())
+	throw new IllegalArgumentException("Index out of scope on Y axis (" + y + " >= " + height() + ")!");
+      
+      return new PointDouble(m_MinX + m_StepX*x, m_MinY + m_StepY*y);
+    }
+
+    /**
+     * returns the closest index pair for the given value pair in the grid.
+     * 
+     * @param values	the values to get the indices for
+     * @return		the closest indices in the grid
+     */
+    public PointInt getLocation(PointDouble values) {
+      PointInt	result;
+      int	x;
+      int	y;
+      double	distance;
+      double	currDistance;
+      int	i;
+
+      // determine x
+      x        = 0;
+      distance = m_StepX;
+      for (i = 0; i < width(); i++) {
+	currDistance = StrictMath.abs(values.getX() - getValues(i, 0).getX());
+	if (Utils.sm(currDistance, distance)) {
+	  distance = currDistance;
+	  x        = i;
+	}
+      }
+      
+      // determine y
+      y        = 0;
+      distance = m_StepY;
+      for (i = 0; i < height(); i++) {
+	currDistance = StrictMath.abs(values.getY() - getValues(0, i).getY());
+	if (Utils.sm(currDistance, distance)) {
+	  distance = currDistance;
+	  y        = i;
+	}
+      }
+      
+      result = new PointInt(x, y);
+      return result;
+    }
+
+    /**
+     * checks whether the given values are on the border of the grid
+     * 
+     * @param values		the values to check
+     * @return			true if the the values are on the border
+     */
+    public boolean isOnBorder(PointDouble values) {
+      return isOnBorder(getLocation(values));
+    }
+
+    /**
+     * checks whether the given location is on the border of the grid
+     * 
+     * @param location 		the location to check
+     * @return			true if the the location is on the border
+     */
+    public boolean isOnBorder(PointInt location) {
+      if (location.getX() == 0)
+	return true;
+      else if (location.getX() == width() - 1)
+	return true;
+      if (location.getY() == 0)
+	return true;
+      else if (location.getY() == height() - 1)
+	return true;
+      else
+	return false;
+    }
+    
+    /**
+     * returns a subgrid with the same step sizes, but different borders
+     * 
+     * @param top	the top index
+     * @param left	the left index
+     * @param bottom	the bottom index
+     * @param right	the right index
+     * @return 		the Sub-Grid
+     */
+    public Grid subgrid(int top, int left, int bottom, int right) {
+      return new Grid(
+                   getValues(left, top).getX(), getValues(right, top).getX(), getStepX(), getLabelX(),
+                   getValues(left, bottom).getY(), getValues(left, top).getY(), getStepY(), getLabelY());
+    }
+    
+    /**
+     * returns an extended grid that encompasses the given point (won't be on
+     * the border of the grid).
+     * 
+     * @param values	the point that the grid should contain
+     * @return		the extended grid
+     */
+    public Grid extend(PointDouble values) {
+      double	minX;
+      double	maxX;
+      double	minY;
+      double	maxY;
+      double	distance;
+      Grid	result;
+      
+      // left
+      if (Utils.smOrEq(values.getX(), getMinX())) {
+	distance = getMinX() - values.getX();
+	// exactly on grid point?
+	if (Utils.eq(distance, 0))
+	  minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()) + 1);
+	else
+	  minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()));
+      }
+      else {
+	minX = getMinX();
+      }
+      
+      // right
+      if (Utils.grOrEq(values.getX(), getMaxX())) {
+	distance = values.getX() - getMaxX();
+	// exactly on grid point?
+	if (Utils.eq(distance, 0))
+	  maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()) + 1);
+	else
+	  maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()));
+      }
+      else {
+	maxX = getMaxX();
+      }
+      
+      // bottom
+      if (Utils.smOrEq(values.getY(), getMinY())) {
+	distance = getMinY() - values.getY();
+	// exactly on grid point?
+	if (Utils.eq(distance, 0))
+	  minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()) + 1);
+	else
+	  minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()));
+      }
+      else {
+	minY = getMinY();
+      }
+      
+      // top
+      if (Utils.grOrEq(values.getY(), getMaxY())) {
+	distance = values.getY() - getMaxY();
+	// exactly on grid point?
+	if (Utils.eq(distance, 0))
+	  maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()) + 1);
+	else
+	  maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()));
+      }
+      else {
+	maxY = getMaxY();
+      }
+      
+      result = new Grid(minX, maxX, getStepX(), getLabelX(), minY, maxY, getStepY(), getLabelY());
+      
+      // did the grid really extend?
+      if (equals(result))
+	throw new IllegalStateException("Grid extension failed!");
+      
+      return result;
+    }
+    
+    /**
+     * returns an Enumeration over all pairs in the given row
+     * 
+     * @param y		the row to retrieve
+     * @return		an Enumeration over all pairs
+     * @see #getValues(int, int)
+     */
+    public Enumeration<PointDouble> row(int y) {
+      Vector	result;
+      int	i;
+      
+      result = new Vector();
+      
+      for (i = 0; i < width(); i++)
+	result.add(getValues(i, y));
+      
+      return result.elements();
+    }
+    
+    /**
+     * returns an Enumeration over all pairs in the given column
+     * 
+     * @param x		the column to retrieve
+     * @return		an Enumeration over all pairs
+     * @see #getValues(int, int)
+     */
+    public Enumeration<PointDouble> column(int x) {
+      Vector	result;
+      int	i;
+      
+      result = new Vector();
+      
+      for (i = 0; i < height(); i++)
+	result.add(getValues(x, i));
+      
+      return result.elements();
+    }
+    
+    /**
+     * returns a string representation of the grid
+     * 
+     * @return a string representation
+     */
+    public String toString() {
+      String	result;
+      
+      result  = "X: " + m_MinX + " - " + m_MaxX + ", Step " + m_StepX;
+      if (m_LabelX.length() != 0)
+	result += " (" + m_LabelX + ")";
+      result += "\n";
+      
+      result += "Y: " + m_MinY + " - " + m_MaxY + ", Step " + m_StepY;
+      if (m_LabelY.length() != 0)
+	result += " (" + m_LabelY + ")";
+      result += "\n";
+
+      result += "Dimensions (Rows x Columns): " + height() + " x " + width();
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  
+  /**
+   * A helper class for storing the performance of a values-pair.
+   * Can be sorted with the PerformanceComparator class.
+   * 
+   * @see PerformanceComparator
+   */
+  protected class Performance
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4374706475277588755L;
+    
+    /** the value pair the classifier was built with */
+    protected PointDouble m_Values;
+    
+    /** the Correlation coefficient */
+    protected double m_CC;
+    
+    /** the Root mean squared error */
+    protected double m_RMSE;
+    
+    /** the Root relative squared error */
+    protected double m_RRSE;
+    
+    /** the Mean absolute error */
+    protected double m_MAE;
+    
+    /** the Relative absolute error */
+    protected double m_RAE;
+    
+    /** the Accuracy */
+    protected double m_ACC;
+    
+    /** the kappa value */
+    protected double m_Kappa;
+    
+    /**
+     * initializes the performance container
+     * 
+     * @param values		the values-pair
+     * @param evaluation	the evaluation to extract the performance
+     * 				measures from
+     * @throws Exception	if retrieving of measures fails
+     */
+    public Performance(PointDouble values, Evaluation evaluation) throws Exception {
+      super();
+      
+      m_Values = values;
+      
+      m_RMSE  = evaluation.rootMeanSquaredError();
+      m_RRSE  = evaluation.rootRelativeSquaredError();
+      m_MAE   = evaluation.meanAbsoluteError();
+      m_RAE   = evaluation.relativeAbsoluteError();
+
+      try {
+	m_CC = evaluation.correlationCoefficient();
+      }
+      catch (Exception e) {
+	m_CC = Double.NaN;
+      }
+      try {
+	m_ACC = evaluation.pctCorrect();
+      }
+      catch (Exception e) {
+	m_ACC = Double.NaN;
+      }
+      try {
+	m_Kappa = evaluation.kappa();
+      }
+      catch (Exception e) {
+	m_Kappa = Double.NaN;
+      }
+    }
+    
+    /**
+     * returns the performance measure
+     * 
+     * @param evaluation	the type of measure to return
+     * @return 			the performance measure
+     */
+    public double getPerformance(int evaluation) {
+      double	result;
+      
+      result = Double.NaN;
+      
+      switch (evaluation) {
+	case EVALUATION_CC:
+	  result = m_CC;
+	  break;
+	case EVALUATION_RMSE:
+	  result = m_RMSE;
+	  break;
+	case EVALUATION_RRSE:
+	  result = m_RRSE;
+	  break;
+	case EVALUATION_MAE:
+	  result = m_MAE;
+	  break;
+	case EVALUATION_RAE:
+	  result = m_RAE;
+	  break;
+	case EVALUATION_COMBINED:
+	  result = (1 - StrictMath.abs(m_CC)) + m_RRSE + m_RAE;
+	  break;
+	case EVALUATION_ACC:
+	  result = m_ACC;
+	  break;
+	case EVALUATION_KAPPA:
+	  result = m_Kappa;
+	  break;
+	default:
+	  throw new IllegalArgumentException("Evaluation type '" + evaluation + "' not supported!");
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns the values-pair for this performance
+     * 
+     * @return the values-pair
+     */
+    public PointDouble getValues() {
+      return m_Values;
+    }
+    
+    /**
+     * returns a string representation of this performance object
+     * 
+     * @param evaluation	the type of performance to return
+     * @return 			a string representation
+     */
+    public String toString(int evaluation) {
+      String	result;
+      
+      result =   "Performance (" + getValues() + "): " 
+      	       + getPerformance(evaluation) 
+      	       + " (" + new SelectedTag(evaluation, TAGS_EVALUATION) + ")";
+      
+      return result;
+    }
+    
+    /**
+     * returns a Gnuplot string of this performance object
+     * 
+     * @param evaluation	the type of performance to return
+     * @return 			the gnuplot string (x, y, z)
+     */
+    public String toGnuplot(int evaluation) {
+      String	result;
+      
+      result =   getValues().getX() + "\t" 
+      	       + getValues().getY() + "\t"
+      	       + getPerformance(evaluation);
+      
+      return result;
+    }
+    
+    /**
+     * returns a string representation of this performance object
+     * 
+     * @return a string representation
+     */
+    public String toString() {
+      String	result;
+      int	i;
+      
+      result = "Performance (" + getValues() + "): ";
+      
+      for (i = 0; i < TAGS_EVALUATION.length; i++) {
+	if (i > 0)
+	  result += ", ";
+        result +=   getPerformance(TAGS_EVALUATION[i].getID()) 
+        	  + " (" + new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION) + ")";
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /**
+   * A concrete Comparator for the Performance class.
+   * 
+   * @see Performance
+   */
+  protected class PerformanceComparator
+    implements Comparator<Performance>, Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 6507592831825393847L;
+    
+    /** the performance measure to use for comparison 
+     * @see GridSearch#TAGS_EVALUATION */
+    protected int m_Evaluation;
+    
+    /**
+     * initializes the comparator with the given performance measure
+     * 
+     * @param evaluation	the performance measure to use
+     * @see GridSearch#TAGS_EVALUATION
+     */
+    public PerformanceComparator(int evaluation) {
+      super();
+      
+      m_Evaluation = evaluation;
+    }
+    
+    /**
+     * returns the performance measure that's used to compare the objects
+     * 
+     * @return the performance measure
+     * @see GridSearch#TAGS_EVALUATION
+     */
+    public int getEvaluation() {
+      return m_Evaluation;
+    }
+    
+    /**
+     * Compares its two arguments for order. Returns a negative integer, 
+     * zero, or a positive integer as the first argument is less than, 
+     * equal to, or greater than the second.
+     * 
+     * @param o1 	the first performance
+     * @param o2 	the second performance
+     * @return 		the order
+     */
+    public int compare(Performance o1, Performance o2) {
+      int	result;
+      double	p1;
+      double	p2;
+      
+      p1 = o1.getPerformance(getEvaluation());
+      p2 = o2.getPerformance(getEvaluation());
+      
+      if (Utils.sm(p1, p2))
+	result = -1;
+      else if (Utils.gr(p1, p2))
+	result = 1;
+      else
+	result = 0;
+	
+      // only correlation coefficient/accuracy/kappa obey to this order, for the
+      // errors (and the combination of all three), the smaller the number the
+      // better -> hence invert them
+      if (    (getEvaluation() != EVALUATION_CC) 
+           && (getEvaluation() != EVALUATION_ACC) 
+           && (getEvaluation() != EVALUATION_KAPPA) )
+	result = -result;
+	
+      return result;
+    }
+    
+    /**
+     * Indicates whether some other object is "equal to" this Comparator.
+     * 
+     * @param obj	the object to compare with
+     * @return		true if the same evaluation type is used
+     */
+    public boolean equals(Object obj) {
+      if (!(obj instanceof PerformanceComparator))
+	throw new IllegalArgumentException("Must be PerformanceComparator!");
+      
+      return (m_Evaluation == ((PerformanceComparator) obj).m_Evaluation);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /**
+   * Generates a 2-dim array for the performances from a grid for a certain 
+   * type. x-min/y-min is in the bottom-left corner, i.e., getTable()[0][0]
+   * returns the performance for the x-min/y-max pair.
+   * <pre>
+   * x-min     x-max
+   * |-------------|
+   *                - y-max
+   *                |
+   *                |
+   *                - y-min
+   * </pre>
+   */
+  protected class PerformanceTable 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 5486491313460338379L;
+
+    /** the corresponding grid */
+    protected Grid m_Grid;
+    
+    /** the performances */
+    protected Vector<Performance> m_Performances;
+    
+    /** the type of performance the table was generated for */
+    protected int m_Type;
+    
+    /** the table with the values */
+    protected double[][] m_Table;
+    
+    /** the minimum performance */
+    protected double m_Min;
+    
+    /** the maximum performance */
+    protected double m_Max;
+    
+    /**
+     * initializes the table
+     * 
+     * @param grid		the underlying grid
+     * @param performances	the performances
+     * @param type		the type of performance
+     */
+    public PerformanceTable(Grid grid, Vector<Performance> performances, int type) {
+      super();
+      
+      m_Grid         = grid;
+      m_Type         = type;
+      m_Performances = performances;
+      
+      generate();
+    }
+    
+    /**
+     * generates the table
+     */
+    protected void generate() {
+      Performance 	perf;
+      int 		i;
+      PointInt 		location;
+      
+      m_Table = new double[getGrid().height()][getGrid().width()];
+      m_Min   = 0;
+      m_Max   = 0;
+      
+      for (i = 0; i < getPerformances().size(); i++) {
+	perf     = (Performance) getPerformances().get(i);
+	location = getGrid().getLocation(perf.getValues());
+	m_Table[getGrid().height() - (int) location.getY() - 1][(int) location.getX()] = perf.getPerformance(getType());
+	
+	// determine min/max
+	if (i == 0) {
+	  m_Min = perf.getPerformance(m_Type);
+	  m_Max = m_Min;
+	}
+	else {
+	  if (perf.getPerformance(m_Type) < m_Min)
+	    m_Min = perf.getPerformance(m_Type);
+	  if (perf.getPerformance(m_Type) > m_Max)
+	    m_Max = perf.getPerformance(m_Type);
+	}
+      }
+    }
+    
+    /**
+     * returns the corresponding grid
+     * 
+     * @return		the underlying grid
+     */
+    public Grid getGrid() {
+      return m_Grid;
+    }
+
+    /**
+     * returns the underlying performances
+     * 
+     * @return		the underlying performances
+     */
+    public Vector<Performance> getPerformances() {
+      return m_Performances;
+    }
+    
+    /**
+     * returns the type of performance
+     * 
+     * @return		the type of performance
+     */
+    public int getType() {
+      return m_Type;
+    }
+    
+    /**
+     * returns the generated table
+     * 
+     * @return 		the performance table
+     * @see		#m_Table
+     * @see		#generate()
+     */
+    public double[][] getTable() {
+      return m_Table;
+    }
+    
+    /**
+     * the minimum performance
+     * 
+     * @return		the performance
+     */
+    public double getMin() {
+      return m_Min;
+    }
+    
+    /**
+     * the maximum performance
+     * 
+     * @return		the performance
+     */
+    public double getMax() {
+      return m_Max;
+    }
+    
+    /**
+     * returns the table as string
+     * 
+     * @return		the table as string
+     */
+    public String toString() {
+      String	result;
+      int	i;
+      int	n;
+      
+      result =   "Table (" 
+	       + new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag().getReadable() 
+               + ") - "
+               + "X: " + getGrid().getLabelX() + ", Y: " + getGrid().getLabelY()
+               + ":\n";
+      
+      for (i = 0; i < getTable().length; i++) {
+	if (i > 0)
+	  result += "\n";
+	
+	for (n = 0; n < getTable()[i].length; n++) {
+	  if (n > 0)
+	    result += ",";
+	  result += getTable()[i][n];
+	}
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns a string containing a gnuplot script+data file
+     * 
+     * @return		the data in gnuplot format
+     */
+    public String toGnuplot() {
+      StringBuffer	result;
+      Tag		type;
+      int		i;
+      
+      result = new StringBuffer();
+      type   = new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag();
+      
+      result.append("Gnuplot (" + type.getReadable() + "):\n");
+      result.append("# begin 'gridsearch.data'\n");
+      result.append("# " + type.getReadable() + "\n");
+      for (i = 0; i < getPerformances().size(); i++)
+        result.append(getPerformances().get(i).toGnuplot(type.getID()) + "\n");
+      result.append("# end 'gridsearch.data'\n\n");
+      
+      result.append("# begin 'gridsearch.plot'\n");
+      result.append("# " + type.getReadable() + "\n");
+      result.append("set data style lines\n");
+      result.append("set contour base\n");
+      result.append("set surface\n");
+      result.append("set title '" + m_Data.relationName() + "'\n");
+      result.append("set xrange [" + getGrid().getMinX() + ":" + getGrid().getMaxX() + "]\n");
+      result.append("set xlabel 'x (" + getFilter().getClass().getName() + ": " + getXProperty() + ")'\n");
+      result.append("set yrange [" + getGrid().getMinY() + ":" + getGrid().getMaxY() + "]\n");
+      result.append("set ylabel 'y - (" + getClassifier().getClass().getName() + ": " + getYProperty() + ")'\n");
+      result.append("set zrange [" + (getMin() - (getMax() - getMin())*0.1) + ":" + (getMax() + (getMax() - getMin())*0.1) + "]\n");
+      result.append("set zlabel 'z - " + type.getReadable() + "'\n");
+      result.append("set dgrid3d " + getGrid().height() + "," + getGrid().width() + ",1\n");
+      result.append("show contour\n");
+      result.append("splot 'gridsearch.data'\n");
+      result.append("pause -1\n");
+      result.append("# end 'gridsearch.plot'");
+
+      return result.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /**
+   * Represents a simple cache for performance objects.
+   */
+  protected class PerformanceCache
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 5838863230451530252L;
+    
+    /** the cache for points in the grid that got calculated */
+    protected Hashtable m_Cache = new Hashtable();
+    
+    /**
+     * returns the ID string for a cache item
+     * 
+     * @param cv		the number of folds in the cross-validation
+     * @param values	the point in the grid
+     * @return		the ID string
+     */
+    protected String getID(int cv, PointDouble values) {
+      return cv + "\t" + values.getX() + "\t" + values.getY();
+    }
+    
+    /**
+     * checks whether the point was already calculated ones
+     * 
+     * @param cv	the number of folds in the cross-validation
+     * @param values	the point in the grid
+     * @return		true if the value is already cached
+     */
+    public boolean isCached(int cv, PointDouble values) {
+      return (get(cv, values) != null);
+    }
+    
+    /**
+     * returns a cached performance object, null if not yet in the cache
+     * 
+     * @param cv	the number of folds in the cross-validation
+     * @param values	the point in the grid
+     * @return		the cached performance item, null if not in cache
+     */
+    public Performance get(int cv, PointDouble values) {
+      return (Performance) m_Cache.get(getID(cv, values));
+    }
+    
+    /**
+     * adds the performance to the cache
+     * 
+     * @param cv	the number of folds in the cross-validation
+     * @param p		the performance object to store
+     */
+    public void add(int cv, Performance p) {
+      m_Cache.put(getID(cv, p.getValues()), p);
+    }
+    
+    /**
+     * returns a string representation of the cache
+     * 
+     * @return		the string representation of the cache
+     */
+    public String toString() {
+      return m_Cache.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /** for serialization */
+  private static final long serialVersionUID = -3034773968581595348L;
+
+  /** evaluation via: Correlation coefficient */
+  public static final int EVALUATION_CC = 0;
+  /** evaluation via: Root mean squared error */
+  public static final int EVALUATION_RMSE = 1;
+  /** evaluation via: Root relative squared error */
+  public static final int EVALUATION_RRSE = 2;
+  /** evaluation via: Mean absolute error */
+  public static final int EVALUATION_MAE = 3;
+  /** evaluation via: Relative absolute error */
+  public static final int EVALUATION_RAE = 4;
+  /** evaluation via: Combined = (1-CC) + RRSE + RAE */
+  public static final int EVALUATION_COMBINED = 5;
+  /** evaluation via: Accuracy */
+  public static final int EVALUATION_ACC = 6;
+  /** evaluation via: kappa statistic */
+  public static final int EVALUATION_KAPPA = 7;
+  /** evaluation */
+  public static final Tag[] TAGS_EVALUATION = {
+    new Tag(EVALUATION_CC, "CC", "Correlation coefficient"),
+    new Tag(EVALUATION_RMSE, "RMSE", "Root mean squared error"),
+    new Tag(EVALUATION_RRSE, "RRSE", "Root relative squared error"),
+    new Tag(EVALUATION_MAE, "MAE", "Mean absolute error"),
+    new Tag(EVALUATION_RAE, "RAE", "Root absolute error"),
+    new Tag(EVALUATION_COMBINED, "COMB", "Combined = (1-abs(CC)) + RRSE + RAE"),
+    new Tag(EVALUATION_ACC, "ACC", "Accuracy"),
+    new Tag(EVALUATION_KAPPA, "KAP", "Kappa")
+  };
+  
+  /** row-wise grid traversal */
+  public static final int TRAVERSAL_BY_ROW = 0;
+  /** column-wise grid traversal */
+  public static final int TRAVERSAL_BY_COLUMN = 1;
+  /** traversal */
+  public static final Tag[] TAGS_TRAVERSAL = {
+    new Tag(TRAVERSAL_BY_ROW, "row-wise", "row-wise"),
+    new Tag(TRAVERSAL_BY_COLUMN, "column-wise", "column-wise")
+  };
+
+  /** the prefix to indicate that the option is for the classifier */
+  public final static String PREFIX_CLASSIFIER = "classifier.";
+
+  /** the prefix to indicate that the option is for the filter */
+  public final static String PREFIX_FILTER = "filter.";
+  
+  /** the Filter */
+  protected Filter m_Filter;
+  
+  /** the Filter with the best setup */
+  protected Filter m_BestFilter;
+  
+  /** the Classifier with the best setup */
+  protected Classifier m_BestClassifier;
+
+  /** the best values */
+  protected PointDouble m_Values = null;
+  
+  /** the type of evaluation */
+  protected int m_Evaluation = EVALUATION_CC;
+
+  /** the Y option to work on (without leading dash, preceding 'classifier.' 
+   * means to set the option for the classifier 'filter.' for the filter) */
+  protected String m_Y_Property = PREFIX_CLASSIFIER + "ridge";
+  
+  /** the minimum of Y */
+  protected double m_Y_Min = -10;
+  
+  /** the maximum of Y */
+  protected double m_Y_Max = +5;
+  
+  /** the step size of Y */
+  protected double m_Y_Step = 1;
+  
+  /** the base for Y */
+  protected double m_Y_Base = 10;
+  
+  /** 
+   * The expression for the Y property. Available parameters for the
+   * expression:
+   * <ul>
+   *   <li>BASE</li>
+   *   <li>FROM (= min)</li>
+   *   <li>TO (= max)</li>
+   *   <li>STEP</li>
+   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
+   * </ul>
+   * 
+   * @see MathematicalExpression
+   * @see MathExpression
+   */
+  protected String m_Y_Expression = "pow(BASE,I)";
+
+  /** the X option to work on (without leading dash, preceding 'classifier.' 
+   * means to set the option for the classifier 'filter.' for the filter) */
+  protected String m_X_Property = PREFIX_FILTER + "numComponents";
+  
+  /** the minimum of X */
+  protected double m_X_Min = +5;
+  
+  /** the maximum of X */
+  protected double m_X_Max = +20;
+  
+  /** the step size of  */
+  protected double m_X_Step = 1;
+  
+  /** the base for  */
+  protected double m_X_Base = 10;
+  
+  /** 
+   * The expression for the X property. Available parameters for the
+   * expression:
+   * <ul>
+   *   <li>BASE</li>
+   *   <li>FROM (= min)</li>
+   *   <li>TO (= max)</li>
+   *   <li>STEP</li>
+   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
+   * </ul>
+   * 
+   * @see MathematicalExpression
+   * @see MathExpression
+   */
+  protected String m_X_Expression = "I";
+
+  /** whether the grid can be extended */
+  protected boolean m_GridIsExtendable = false;
+  
+  /** maximum number of grid extensions (-1 means unlimited) */
+  protected int m_MaxGridExtensions = 3;
+  
+  /** the number of extensions performed */
+  protected int m_GridExtensionsPerformed = 0;
+
+  /** the sample size to search the initial grid with */
+  protected double m_SampleSize = 100;
+  
+  /** the traversal */
+  protected int m_Traversal = TRAVERSAL_BY_COLUMN;
+
+  /** the log file to use */
+  protected File m_LogFile = new File(System.getProperty("user.dir"));
+  
+  /** the value-pairs grid */
+  protected Grid m_Grid;
+
+  /** the training data */
+  protected Instances m_Data;
+
+  /** the cache for points in the grid that got calculated */
+  protected PerformanceCache m_Cache;
+
+  /** whether all performances in the grid are the same */
+  protected boolean m_UniformPerformance = false;
+  
+  /**
+   * the default constructor
+   */
+  public GridSearch() {
+    super();
+    
+    // classifier
+    m_Classifier = new LinearRegression();
+    ((LinearRegression) m_Classifier).setAttributeSelectionMethod(new SelectedTag(LinearRegression.SELECTION_NONE, LinearRegression.TAGS_SELECTION));
+    ((LinearRegression) m_Classifier).setEliminateColinearAttributes(false);
+    
+    // filter
+    m_Filter = new PLSFilter();
+    PLSFilter filter = new PLSFilter();
+    filter.setPreprocessing(new SelectedTag(PLSFilter.PREPROCESSING_STANDARDIZE, PLSFilter.TAGS_PREPROCESSING));
+    filter.setReplaceMissing(true);
+    
+    try {
+      m_BestClassifier = AbstractClassifier.makeCopy(m_Classifier);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    try {
+      m_BestFilter = Filter.makeCopy(filter);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Performs a grid search of parameter pairs for the a classifier "
+      + "(Y-axis, default is LinearRegression with the \"Ridge\" parameter) "
+      + "and the PLSFilter (X-axis, \"# of Components\") and chooses the best "
+      + "pair found for the actual predicting.\n\n"
+      + "The initial grid is worked on with 2-fold CV to determine the values "
+      + "of the parameter pairs for the selected type of evaluation (e.g., "
+      + "accuracy). The best point in the grid is then taken and a 10-fold CV "
+      + "is performed with the adjacent parameter pairs. If a better pair is "
+      + "found, then this will act as new center and another 10-fold CV will "
+      + "be performed (kind of hill-climbing). This process is repeated until "
+      + "no better pair is found or the best pair is on the border of the grid.\n"
+      + "In case the best pair is on the border, one can let GridSearch "
+      + "automatically extend the grid and continue the search. Check out the "
+      + "properties 'gridIsExtendable' (option '-extend-grid') and "
+      + "'maxGridExtensions' (option '-max-grid-extensions <num>').\n\n"
+      + "GridSearch can handle doubles, integers (values are just cast to int) "
+      + "and booleans (0 is false, otherwise true). float, char and long are "
+      + "supported as well.\n\n"
+      + "The best filter/classifier setup can be accessed after the buildClassifier "
+      + "call via the getBestFilter/getBestClassifier methods.\n"
+      + "Note on the implementation: after the data has been passed through "
+      + "the filter, a default NumericCleaner filter is applied to the data in "
+      + "order to avoid numbers that are getting too small and might produce "
+      + "NaNs in other schemes.";
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return		the classname of the default classifier
+   */
+  protected String defaultClassifierString() {
+    return LinearRegression.class.getName();
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions(){
+    Vector        	result;
+    Enumeration   	en;
+    String		desc;
+    SelectedTag		tag;
+    int			i;
+
+    result = new Vector();
+
+    desc  = "";
+    for (i = 0; i < TAGS_EVALUATION.length; i++) {
+      tag = new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION);
+      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
+      	       + " = " + tag.getSelectedTag().getReadable()
+      	       + "\n";
+    }
+    result.addElement(new Option(
+	"\tDetermines the parameter used for evaluation:\n"
+	+ desc
+	+ "\t(default: " + new SelectedTag(EVALUATION_CC, TAGS_EVALUATION) + ")",
+	"E", 1, "-E " + Tag.toOptionList(TAGS_EVALUATION)));
+
+    result.addElement(new Option(
+	"\tThe Y option to test (without leading dash).\n"
+	+ "\t(default: " + PREFIX_CLASSIFIER + "ridge)",
+	"y-property", 1, "-y-property <option>"));
+
+    result.addElement(new Option(
+	"\tThe minimum for Y.\n"
+	+ "\t(default: -10)",
+	"y-min", 1, "-y-min <num>"));
+
+    result.addElement(new Option(
+	"\tThe maximum for Y.\n"
+	+ "\t(default: +5)",
+	"y-max", 1, "-y-max <num>"));
+
+    result.addElement(new Option(
+	"\tThe step size for Y.\n"
+	+ "\t(default: 1)",
+	"y-step", 1, "-y-step <num>"));
+
+    result.addElement(new Option(
+	"\tThe base for Y.\n"
+	+ "\t(default: 10)",
+	"y-base", 1, "-y-base <num>"));
+
+    result.addElement(new Option(
+	"\tThe expression for Y.\n"
+	+ "\tAvailable parameters:\n"
+	+ "\t\tBASE\n"
+	+ "\t\tFROM\n"
+	+ "\t\tTO\n"
+	+ "\t\tSTEP\n"
+	+ "\t\tI - the current iteration value\n"
+	+ "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
+	+ "\t(default: 'pow(BASE,I)')",
+	"y-expression", 1, "-y-expression <expr>"));
+
+    result.addElement(new Option(
+	"\tThe filter to use (on X axis). Full classname of filter to include, \n"
+	+ "\tfollowed by scheme options.\n"
+	+ "\t(default: weka.filters.supervised.attribute.PLSFilter)",
+	"filter", 1, "-filter <filter specification>"));
+
+    result.addElement(new Option(
+	"\tThe X option to test (without leading dash).\n"
+	+ "\t(default: " + PREFIX_FILTER + "numComponents)",
+	"x-property", 1, "-x-property <option>"));
+
+    result.addElement(new Option(
+	"\tThe minimum for X.\n"
+	+ "\t(default: +5)",
+	"x-min", 1, "-x-min <num>"));
+
+    result.addElement(new Option(
+	"\tThe maximum for X.\n"
+	+ "\t(default: +20)",
+	"x-max", 1, "-x-max <num>"));
+
+    result.addElement(new Option(
+	"\tThe step size for X.\n"
+	+ "\t(default: 1)",
+	"x-step", 1, "-x-step <num>"));
+
+    result.addElement(new Option(
+	"\tThe base for X.\n"
+	+ "\t(default: 10)",
+	"x-base", 1, "-x-base <num>"));
+
+    result.addElement(new Option(
+	"\tThe expression for the X value.\n"
+	+ "\tAvailable parameters:\n"
+	+ "\t\tBASE\n"
+	+ "\t\tMIN\n"
+	+ "\t\tMAX\n"
+	+ "\t\tSTEP\n"
+	+ "\t\tI - the current iteration value\n"
+	+ "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
+	+ "\t(default: 'pow(BASE,I)')",
+	"x-expression", 1, "-x-expression <expr>"));
+
+    result.addElement(new Option(
+	"\tWhether the grid can be extended.\n"
+	+ "\t(default: no)",
+	"extend-grid", 0, "-extend-grid"));
+
+    result.addElement(new Option(
+	"\tThe maximum number of grid extensions (-1 is unlimited).\n"
+	+ "\t(default: 3)",
+	"max-grid-extensions", 1, "-max-grid-extensions <num>"));
+
+    result.addElement(new Option(
+	"\tThe size (in percent) of the sample to search the inital grid with.\n"
+	+ "\t(default: 100)",
+	"sample-size", 1, "-sample-size <num>"));
+
+    result.addElement(new Option(
+	"\tThe type of traversal for the grid.\n"
+	+ "\t(default: " + new SelectedTag(TRAVERSAL_BY_COLUMN, TAGS_TRAVERSAL) + ")",
+	"traversal", 1, "-traversal " + Tag.toOptionList(TAGS_TRAVERSAL)));
+
+    result.addElement(new Option(
+	"\tThe log file to log the messages to.\n"
+	+ "\t(default: none)",
+	"log-file", 1, "-log-file <filename>"));
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    if (getFilter() instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to filter "
+	  + getFilter().getClass().getName() + " ('-filter'):"));
+      
+      en = ((OptionHandler) getFilter()).listOptions();
+      while (en.hasMoreElements())
+	result.addElement(en.nextElement());
+    }
+
+
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    result.add("-E");
+    result.add("" + getEvaluation());
+
+    result.add("-y-property");
+    result.add("" + getYProperty());
+
+    result.add("-y-min");
+    result.add("" + getYMin());
+
+    result.add("-y-max");
+    result.add("" + getYMax());
+
+    result.add("-y-step");
+    result.add("" + getYStep());
+
+    result.add("-y-base");
+    result.add("" + getYBase());
+
+    result.add("-y-expression");
+    result.add("" + getYExpression());
+
+    result.add("-filter");
+    if (getFilter() instanceof OptionHandler)
+      result.add(
+  	    getFilter().getClass().getName() 
+	  + " " 
+	  + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
+    else
+      result.add(
+	  getFilter().getClass().getName());
+
+    result.add("-x-property");
+    result.add("" + getXProperty());
+
+    result.add("-x-min");
+    result.add("" + getXMin());
+
+    result.add("-x-max");
+    result.add("" + getXMax());
+
+    result.add("-x-step");
+    result.add("" + getXStep());
+
+    result.add("-x-base");
+    result.add("" + getXBase());
+
+    result.add("-x-expression");
+    result.add("" + getXExpression());
+
+    if (getGridIsExtendable()) {
+      result.add("-extend-grid");
+      result.add("-max-grid-extensions");
+      result.add("" + getMaxGridExtensions());
+    }
+    
+    result.add("-sample-size");
+    result.add("" + getSampleSizePercent());
+
+    result.add("-traversal");
+    result.add("" + getTraversal());
+
+    result.add("-log-file");
+    result.add("" + getLogFile());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC|KAP&gt;
+   *  Determines the parameter used for evaluation:
+   *  CC = Correlation coefficient
+   *  RMSE = Root mean squared error
+   *  RRSE = Root relative squared error
+   *  MAE = Mean absolute error
+   *  RAE = Root absolute error
+   *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
+   *  ACC = Accuracy
+   *  KAP = Kappa
+   *  (default: CC)</pre>
+   * 
+   * <pre> -y-property &lt;option&gt;
+   *  The Y option to test (without leading dash).
+   *  (default: classifier.ridge)</pre>
+   * 
+   * <pre> -y-min &lt;num&gt;
+   *  The minimum for Y.
+   *  (default: -10)</pre>
+   * 
+   * <pre> -y-max &lt;num&gt;
+   *  The maximum for Y.
+   *  (default: +5)</pre>
+   * 
+   * <pre> -y-step &lt;num&gt;
+   *  The step size for Y.
+   *  (default: 1)</pre>
+   * 
+   * <pre> -y-base &lt;num&gt;
+   *  The base for Y.
+   *  (default: 10)</pre>
+   * 
+   * <pre> -y-expression &lt;expr&gt;
+   *  The expression for Y.
+   *  Available parameters:
+   *   BASE
+   *   FROM
+   *   TO
+   *   STEP
+   *   I - the current iteration value
+   *   (from 'FROM' to 'TO' with stepsize 'STEP')
+   *  (default: 'pow(BASE,I)')</pre>
+   * 
+   * <pre> -filter &lt;filter specification&gt;
+   *  The filter to use (on X axis). Full classname of filter to include, 
+   *  followed by scheme options.
+   *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
+   * 
+   * <pre> -x-property &lt;option&gt;
+   *  The X option to test (without leading dash).
+   *  (default: filter.numComponents)</pre>
+   * 
+   * <pre> -x-min &lt;num&gt;
+   *  The minimum for X.
+   *  (default: +5)</pre>
+   * 
+   * <pre> -x-max &lt;num&gt;
+   *  The maximum for X.
+   *  (default: +20)</pre>
+   * 
+   * <pre> -x-step &lt;num&gt;
+   *  The step size for X.
+   *  (default: 1)</pre>
+   * 
+   * <pre> -x-base &lt;num&gt;
+   *  The base for X.
+   *  (default: 10)</pre>
+   * 
+   * <pre> -x-expression &lt;expr&gt;
+   *  The expression for the X value.
+   *  Available parameters:
+   *   BASE
+   *   MIN
+   *   MAX
+   *   STEP
+   *   I - the current iteration value
+   *   (from 'FROM' to 'TO' with stepsize 'STEP')
+   *  (default: 'pow(BASE,I)')</pre>
+   * 
+   * <pre> -extend-grid
+   *  Whether the grid can be extended.
+   *  (default: no)</pre>
+   * 
+   * <pre> -max-grid-extensions &lt;num&gt;
+   *  The maximum number of grid extensions (-1 is unlimited).
+   *  (default: 3)</pre>
+   * 
+   * <pre> -sample-size &lt;num&gt;
+   *  The size (in percent) of the sample to search the inital grid with.
+   *  (default: 100)</pre>
+   * 
+   * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
+   *  The type of traversal for the grid.
+   *  (default: COLUMN-WISE)</pre>
+   * 
+   * <pre> -log-file &lt;filename&gt;
+   *  The log file to log the messages to.
+   *  (default: none)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.functions.LinearRegression)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.functions.LinearRegression:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Produce debugging output.
+   *  (default no debugging output)</pre>
+   * 
+   * <pre> -S &lt;number of selection method&gt;
+   *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
+   *  (default 0 = M5' method)</pre>
+   * 
+   * <pre> -C
+   *  Do not try to eliminate colinear attributes.
+   * </pre>
+   * 
+   * <pre> -R &lt;double&gt;
+   *  Set ridge parameter (default 1.0e-8).
+   * </pre>
+   * 
+   * <pre> 
+   * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
+   * </pre>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The number of components to compute.
+   *  (default: 20)</pre>
+   * 
+   * <pre> -U
+   *  Updates the class attribute as well.
+   *  (default: off)</pre>
+   * 
+   * <pre> -M
+   *  Turns replacing of missing values on.
+   *  (default: off)</pre>
+   * 
+   * <pre> -A &lt;SIMPLS|PLS1&gt;
+   *  The algorithm to use.
+   *  (default: PLS1)</pre>
+   * 
+   * <pre> -P &lt;none|center|standardize&gt;
+   *  The type of preprocessing that is applied to the data.
+   *  (default: center)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setEvaluation(new SelectedTag(tmpStr, TAGS_EVALUATION));
+    else
+      setEvaluation(new SelectedTag(EVALUATION_CC, TAGS_EVALUATION));
+    
+    tmpStr = Utils.getOption("y-property", options);
+    if (tmpStr.length() != 0)
+      setYProperty(tmpStr);
+    else
+      setYProperty(PREFIX_CLASSIFIER + "ridge");
+    
+    tmpStr = Utils.getOption("y-min", options);
+    if (tmpStr.length() != 0)
+      setYMin(Double.parseDouble(tmpStr));
+    else
+      setYMin(-10);
+    
+    tmpStr = Utils.getOption("y-max", options);
+    if (tmpStr.length() != 0)
+      setYMax(Double.parseDouble(tmpStr));
+    else
+      setYMax(10);
+    
+    tmpStr = Utils.getOption("y-step", options);
+    if (tmpStr.length() != 0)
+      setYStep(Double.parseDouble(tmpStr));
+    else
+      setYStep(1);
+    
+    tmpStr = Utils.getOption("y-base", options);
+    if (tmpStr.length() != 0)
+      setYBase(Double.parseDouble(tmpStr));
+    else
+      setYBase(10);
+    
+    tmpStr = Utils.getOption("y-expression", options);
+    if (tmpStr.length() != 0)
+      setYExpression(tmpStr);
+    else
+      setYExpression("pow(BASE,I)");
+    
+    tmpStr     = Utils.getOption("filter", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
+    }
+    
+    tmpStr = Utils.getOption("x-property", options);
+    if (tmpStr.length() != 0)
+      setXProperty(tmpStr);
+    else
+      setXProperty(PREFIX_FILTER + "filters[0].kernel.gamma");
+    
+    tmpStr = Utils.getOption("x-min", options);
+    if (tmpStr.length() != 0)
+      setXMin(Double.parseDouble(tmpStr));
+    else
+      setXMin(-10);
+    
+    tmpStr = Utils.getOption("x-max", options);
+    if (tmpStr.length() != 0)
+      setXMax(Double.parseDouble(tmpStr));
+    else
+      setXMax(10);
+    
+    tmpStr = Utils.getOption("x-step", options);
+    if (tmpStr.length() != 0)
+      setXStep(Double.parseDouble(tmpStr));
+    else
+      setXStep(1);
+    
+    tmpStr = Utils.getOption("x-base", options);
+    if (tmpStr.length() != 0)
+      setXBase(Double.parseDouble(tmpStr));
+    else
+      setXBase(10);
+    
+    tmpStr = Utils.getOption("x-expression", options);
+    if (tmpStr.length() != 0)
+      setXExpression(tmpStr);
+    else
+      setXExpression("pow(BASE,I)");
+    
+    setGridIsExtendable(Utils.getFlag("extend-grid", options));
+    if (getGridIsExtendable()) {
+      tmpStr = Utils.getOption("max-grid-extensions", options);
+      if (tmpStr.length() != 0)
+        setMaxGridExtensions(Integer.parseInt(tmpStr));
+      else
+        setMaxGridExtensions(3);
+    }
+    
+    tmpStr = Utils.getOption("sample-size", options);
+    if (tmpStr.length() != 0)
+      setSampleSizePercent(Double.parseDouble(tmpStr));
+    else
+      setSampleSizePercent(100);
+    
+    tmpStr = Utils.getOption("traversal", options);
+    if (tmpStr.length() != 0)
+      setTraversal(new SelectedTag(tmpStr, TAGS_TRAVERSAL));
+    else
+      setTraversal(new SelectedTag(TRAVERSAL_BY_ROW, TAGS_TRAVERSAL));
+    
+    tmpStr = Utils.getOption("log-file", options);
+    if (tmpStr.length() != 0)
+      setLogFile(new File(tmpStr));
+    else
+      setLogFile(new File(System.getProperty("user.dir")));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Set the base learner.
+   *
+   * @param newClassifier 	the classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+    boolean	numeric;
+    boolean	nominal;
+    
+    Capabilities cap = newClassifier.getCapabilities();
+
+    numeric =    cap.handles(Capability.NUMERIC_CLASS) 
+    	      || cap.hasDependency(Capability.NUMERIC_CLASS);
+    
+    nominal =    cap.handles(Capability.NOMINAL_CLASS)
+              || cap.hasDependency(Capability.NOMINAL_CLASS)
+              || cap.handles(Capability.BINARY_CLASS)
+              || cap.hasDependency(Capability.BINARY_CLASS)
+              || cap.handles(Capability.UNARY_CLASS)
+              || cap.hasDependency(Capability.UNARY_CLASS);
+    
+    if ((m_Evaluation == EVALUATION_CC) && !numeric)
+      throw new IllegalArgumentException(
+	  "Classifier needs to handle numeric class for chosen type of evaluation!");
+
+    if (((m_Evaluation == EVALUATION_ACC) || (m_Evaluation == EVALUATION_KAPPA)) && !nominal)
+      throw new IllegalArgumentException(
+	  "Classifier needs to handle nominal class for chosen type of evaluation!");
+    
+    super.setClassifier(newClassifier);
+    
+    try {
+      m_BestClassifier = AbstractClassifier.makeCopy(m_Classifier);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used (only used for setup).";
+  }
+
+  /**
+   * Set the kernel filter (only used for setup).
+   *
+   * @param value	the kernel filter.
+   */
+  public void setFilter(Filter value) {
+    m_Filter = value;
+
+    try {
+      m_BestFilter = Filter.makeCopy(m_Filter);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Get the kernel filter.
+   *
+   * @return 		the kernel filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String evaluationTipText() {
+    return 
+        "Sets the criterion for evaluating the classifier performance and "
+      + "choosing the best one.";
+  }
+
+  /**
+   * Sets the criterion to use for evaluating the classifier performance. 
+   *
+   * @param value 	.the evaluation criterion
+   */
+  public void setEvaluation(SelectedTag value) {
+    if (value.getTags() == TAGS_EVALUATION) {
+      m_Evaluation = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the criterion used for evaluating the classifier performance. 
+   *
+   * @return 		the current evaluation criterion.
+   */
+  public SelectedTag getEvaluation() {
+    return new SelectedTag(m_Evaluation, TAGS_EVALUATION);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YPropertyTipText() {
+    return "The Y property to test (normally the classifier).";
+  }
+
+  /**
+   * Get the Y property (normally the classifier).
+   *
+   * @return 		Value of the property.
+   */
+  public String getYProperty() {
+    return m_Y_Property;
+  }
+  
+  /**
+   * Set the Y property (normally the classifier).
+   *
+   * @param value 	the Y property.
+   */
+  public void setYProperty(String value) {
+    m_Y_Property = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YMinTipText() {
+    return "The minimum of Y (normally the classifier).";
+  }
+
+  /**
+   * Get the value of the minimum of Y.
+   *
+   * @return 		Value of the minimum of Y.
+   */
+  public double getYMin() {
+    return m_Y_Min;
+  }
+  
+  /**
+   * Set the value of the minimum of Y.
+   *
+   * @param value 	Value to use as minimum of Y.
+   */
+  public void setYMin(double value) {
+    m_Y_Min = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YMaxTipText() {
+    return "The maximum of Y.";
+  }
+
+  /**
+   * Get the value of the Maximum of Y.
+   *
+   * @return 		Value of the Maximum of Y.
+   */
+  public double getYMax() {
+    return m_Y_Max;
+  }
+  
+  /**
+   * Set the value of the Maximum of Y.
+   *
+   * @param value 	Value to use as Maximum of Y.
+   */
+  public void setYMax(double value) {
+    m_Y_Max = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YStepTipText() {
+    return "The step size of Y.";
+  }
+
+  /**
+   * Get the value of the step size for Y.
+   *
+   * @return 		Value of the step size for Y.
+   */
+  public double getYStep() {
+    return m_Y_Step;
+  }
+  
+  /**
+   * Set the value of the step size for Y.
+   *
+   * @param value 	Value to use as the step size for Y.
+   */
+  public void setYStep(double value) {
+    m_Y_Step = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YBaseTipText() {
+    return "The base of Y.";
+  }
+
+  /**
+   * Get the value of the base for Y.
+   *
+   * @return 		Value of the base for Y.
+   */
+  public double getYBase() {
+    return m_Y_Base;
+  }
+  
+  /**
+   * Set the value of the base for Y.
+   *
+   * @param value Value to use as the base for Y.
+   */
+  public void setYBase(double value) {
+    m_Y_Base = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String YExpressionTipText() {
+    return "The expression for the Y value (parameters: BASE, FROM, TO, STEP, I).";
+  }
+
+  /**
+   * Get the expression for the Y value.
+   *
+   * @return Expression for the Y value.
+   */
+  public String getYExpression() {
+    return m_Y_Expression;
+  }
+  
+  /**
+   * Set the expression for the Y value.
+   *
+   * @param value Expression for the Y value.
+   */
+  public void setYExpression(String value) {
+    m_Y_Expression = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XPropertyTipText() {
+    return "The X property to test (normally the filter).";
+  }
+
+  /**
+   * Get the X property to test (normally the filter).
+   *
+   * @return 		Value of the X property.
+   */
+  public String getXProperty() {
+    return m_X_Property;
+  }
+  
+  /**
+   * Set the X property.
+   *
+   * @param value 	the X property.
+   */
+  public void setXProperty(String value) {
+    m_X_Property = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XMinTipText() {
+    return "The minimum of X.";
+  }
+
+  /**
+   * Get the value of the minimum of X.
+   *
+   * @return Value of the minimum of X.
+   */
+  public double getXMin() {
+    return m_X_Min;
+  }
+  
+  /**
+   * Set the value of the minimum of X.
+   *
+   * @param value Value to use as minimum of X.
+   */
+  public void setXMin(double value) {
+    m_X_Min = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XMaxTipText() {
+    return "The maximum of X.";
+  }
+
+  /**
+   * Get the value of the Maximum of X.
+   *
+   * @return Value of the Maximum of X.
+   */
+  public double getXMax() {
+    return m_X_Max;
+  }
+  
+  /**
+   * Set the value of the Maximum of X.
+   *
+   * @param value Value to use as Maximum of X.
+   */
+  public void setXMax(double value) {
+    m_X_Max = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XStepTipText() {
+    return "The step size of X.";
+  }
+
+  /**
+   * Get the value of the step size for X.
+   *
+   * @return Value of the step size for X.
+   */
+  public double getXStep() {
+    return m_X_Step;
+  }
+  
+  /**
+   * Set the value of the step size for X.
+   *
+   * @param value Value to use as the step size for X.
+   */
+  public void setXStep(double value) {
+    m_X_Step = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XBaseTipText() {
+    return "The base of X.";
+  }
+
+  /**
+   * Get the value of the base for X.
+   *
+   * @return Value of the base for X.
+   */
+  public double getXBase() {
+    return m_X_Base;
+  }
+  
+  /**
+   * Set the value of the base for X.
+   *
+   * @param value Value to use as the base for X.
+   */
+  public void setXBase(double value) {
+    m_X_Base = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String XExpressionTipText() {
+    return "The expression for the X value (parameters: BASE, FROM, TO, STEP, I).";
+  }
+
+  /**
+   * Get the expression for the X value.
+   *
+   * @return Expression for the X value.
+   */
+  public String getXExpression() {
+    return m_X_Expression;
+  }
+  
+  /**
+   * Set the expression for the X value.
+   *
+   * @param value Expression for the X value.
+   */
+  public void setXExpression(String value) {
+    m_X_Expression = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String gridIsExtendableTipText() {
+    return "Whether the grid can be extended.";
+  }
+
+  /**
+   * Get whether the grid can be extended dynamically.
+   *
+   * @return true if the grid can be extended.
+   */
+  public boolean getGridIsExtendable() {
+    return m_GridIsExtendable;
+  }
+  
+  /**
+   * Set whether the grid can be extended dynamically.
+   *
+   * @param value whether the grid can be extended dynamically.
+   */
+  public void setGridIsExtendable(boolean value) {
+    m_GridIsExtendable = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxGridExtensionsTipText() {
+    return "The maximum number of grid extensions, -1 for unlimited.";
+  }
+
+  /**
+   * Gets the maximum number of grid extensions, -1 for unlimited.
+   *
+   * @return the max number of grid extensions
+   */
+  public int getMaxGridExtensions() {
+    return m_MaxGridExtensions;
+  }
+  
+  /**
+   * Sets the maximum number of grid extensions, -1 for unlimited.
+   *
+   * @param value the maximum of grid extensions.
+   */
+  public void setMaxGridExtensions(int value) {
+    m_MaxGridExtensions = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String sampleSizePercentTipText() {
+    return "The sample size (in percent) to use in the initial grid search.";
+  }
+
+  /**
+   * Gets the sample size for the initial grid search.
+   *
+   * @return the sample size.
+   */
+  public double getSampleSizePercent() {
+    return m_SampleSize;
+  }
+  
+  /**
+   * Sets the sample size for the initial grid search.
+   *
+   * @param value the sample size for the initial grid search.
+   */
+  public void setSampleSizePercent(double value) {
+    m_SampleSize = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String traversalTipText() {
+    return "Sets type of traversal of the grid, either by rows or columns.";
+  }
+
+  /**
+   * Sets the type of traversal for the grid. 
+   *
+   * @param value 	the traversal type
+   */
+  public void setTraversal(SelectedTag value) {
+    if (value.getTags() == TAGS_TRAVERSAL) {
+      m_Traversal = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of traversal for the grid. 
+   *
+   * @return 		the current traversal type.
+   */
+  public SelectedTag getTraversal() {
+    return new SelectedTag(m_Traversal, TAGS_TRAVERSAL);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String logFileTipText() {
+    return "The log file to log the messages to.";
+  }
+
+  /**
+   * Gets current log file.
+   *
+   * @return 		the log file.
+   */
+  public File getLogFile() {
+    return m_LogFile;
+  }
+  
+  /**
+   * Sets the log file to use.
+   *
+   * @param value 	the log file.
+   */
+  public void setLogFile(File value) {
+    m_LogFile = value;
+  }
+
+  /**
+   * returns the best filter setup
+   * 
+   * @return		the best filter setup
+   */
+  public Filter getBestFilter() {
+    return m_BestFilter;
+  }
+
+  /**
+   * returns the best Classifier setup
+   * 
+   * @return		the best Classifier setup
+   */
+  public Classifier getBestClassifier() {
+    return m_BestClassifier;
+  }
+  
+  /**
+   * Returns an enumeration of the measure names.
+   * 
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector	result;
+    
+    result = new Vector();
+    
+    result.add("measureX");
+    result.add("measureY");
+    result.add("measureGridExtensionsPerformed");
+    
+    return result.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * 
+   * @param measureName the name of the measure to query for its value
+   * @return the value of the named measure
+   */
+  public double getMeasure(String measureName) {
+    if (measureName.equalsIgnoreCase("measureX"))
+      return evaluate(getValues().getX(), true);
+    else if (measureName.equalsIgnoreCase("measureY"))
+      return evaluate(getValues().getY(), false);
+    else if (measureName.equalsIgnoreCase("measureGridExtensionsPerformed"))
+      return getGridExtensionsPerformed();
+    else
+      throw new IllegalArgumentException("Measure '" + measureName + "' not supported!");
+  }
+  
+  /**
+   * returns the parameter pair that was found to work best
+   * 
+   * @return		the best parameter combination
+   */
+  public PointDouble getValues() {
+    return m_Values;
+  }
+
+  /**
+   * returns the number of grid extensions that took place during the search
+   * (only applicable if the grid was extendable).
+   * 
+   * @return 		the number of grid extensions that were performed
+   * @see 		#getGridIsExtendable()
+   */
+  public int getGridExtensionsPerformed() {
+    return m_GridExtensionsPerformed;
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    Capabilities	classes;
+    Iterator		iter;
+    Capability		capab;
+    
+    if (getFilter() == null)
+      result = super.getCapabilities();
+    else
+      result = getFilter().getCapabilities();
+    
+    // only nominal and numeric classes allowed
+    classes = result.getClassCapabilities();
+    iter = classes.capabilities();
+    while (iter.hasNext()) {
+      capab = (Capability) iter.next();
+      if (    (capab != Capability.BINARY_CLASS)
+	   && (capab != Capability.NOMINAL_CLASS)
+	   && (capab != Capability.NUMERIC_CLASS)
+	   && (capab != Capability.DATE_CLASS) )
+	result.disable(capab);
+    }
+    
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    if (result.getMinimumNumberInstances() < 1)
+      result.setMinimumNumberInstances(1);
+
+    result.setOwner(this);
+    
+    return result;
+  }
+
+  /**
+   * prints the specified message to stdout if debug is on and can also dump
+   * the message to a log file
+   * 
+   * @param message	the message to print or store in a log file
+   */
+  protected void log(String message) {
+    log(message, false);
+  }
+
+  /**
+   * prints the specified message to stdout if debug is on and can also dump
+   * the message to a log file
+   * 
+   * @param message	the message to print or store in a log file
+   * @param onlyLog	if true the message will only be put into the log file
+   * 			but not to stdout
+   */
+  protected void log(String message, boolean onlyLog) {
+    // print to stdout?
+    if (getDebug() && (!onlyLog))
+      System.out.println(message);
+    
+    // log file?
+    if (!getLogFile().isDirectory())
+      Debug.writeToFile(getLogFile().getAbsolutePath(), message, true);
+  }
+  
+  /**
+   * replaces the current option in the options array with a new value
+   * 
+   * @param options	the current options
+   * @param option	the option to set a new value for
+   * @param value	the value to set
+   * @return		the updated array
+   * @throws Exception	if something goes wrong
+   */
+  protected String[] updateOption(String[] options, String option, String value) 
+    throws Exception {
+    
+    String[]		result;
+    Vector		tmpOptions;
+    int			i;
+
+    // remove old option
+    Utils.getOption(option, options);
+    
+    // add option with new value at the beginning (to avoid clashes with "--")
+    tmpOptions = new Vector();
+    tmpOptions.add("-" + option);
+    tmpOptions.add("" + value);
+
+    // move options into vector
+    for (i = 0; i < options.length; i++) {
+      if (options[i].length() != 0)
+	tmpOptions.add(options[i]);
+    }
+    
+    result = (String[]) tmpOptions.toArray(new String[tmpOptions.size()]);
+    
+    return result;
+  }
+  
+  /**
+   * evalutes the expression for the current iteration
+   * 
+   * @param value	the current iteration value (from 'min' to 'max' with  
+   * 			stepsize 'step')
+   * @param isX		true if X is to be evaluated otherwise Y
+   * @return		the generated value, NaN if the evaluation fails
+   */
+  protected double evaluate(double value, boolean isX) {
+    double	result;
+    HashMap	symbols;
+    String	expr;
+    double	base;
+    double	min;
+    double	max;
+    double	step;
+
+    if (isX) {
+      expr = getXExpression();
+      base = getXBase();
+      min  = getXMin();
+      max  = getXMax();
+      step = getXStep();
+    }
+    else {
+      expr = getYExpression();
+      base = getYBase();
+      min  = getYMin();
+      max  = getYMax();
+      step = getYStep();
+    }
+
+    try {
+      symbols = new HashMap();
+      symbols.put("BASE", new Double(base));
+      symbols.put("FROM", new Double(min));
+      symbols.put("TO",   new Double(max));
+      symbols.put("STEP", new Double(step));
+      symbols.put("I",    new Double(value));
+      result = MathematicalExpression.evaluate(expr, symbols);
+    }
+    catch (Exception e) {
+      result = Double.NaN;
+    }
+    
+    return result;
+  }
+
+  /**
+   * tries to set the value as double, integer (just casts it to int!) or
+   * boolean (false if 0, otherwise true) in the object according to the 
+   * specified path. float, char and long are also supported.
+   * 
+   * @param o		the object to modify
+   * @param path	the property path
+   * @param value	the value to set
+   * @return		the modified object
+   * @throws Exception	if neither double nor int could be set
+   */
+  protected Object setValue(Object o, String path, double value) throws Exception {
+    PropertyDescriptor	desc;
+    Class		c;
+    
+    desc = PropertyPath.getPropertyDescriptor(o, path);
+    c    = desc.getPropertyType();
+
+    // float
+    if ((c == Float.class) || (c == Float.TYPE))
+      PropertyPath.setValue(o, path, new Float((float) value));
+    // double
+    else if ((c == Double.class) || (c == Double.TYPE))
+      PropertyPath.setValue(o, path, new Double(value));
+    // char
+    else if ((c == Character.class) || (c == Character.TYPE))
+      PropertyPath.setValue(o, path, new Integer((char) value));
+    // int
+    else if ((c == Integer.class) || (c == Integer.TYPE))
+      PropertyPath.setValue(o, path, new Integer((int) value));
+    // long
+    else if ((c == Long.class) || (c == Long.TYPE))
+      PropertyPath.setValue(o, path, new Long((long) value));
+    // boolean
+    else if ((c == Boolean.class) || (c == Boolean.TYPE))
+      PropertyPath.setValue(o, path, (value == 0 ? new Boolean(false) : new Boolean(true)));
+    else throw new Exception(
+	"Could neither set double nor integer nor boolean value for '" + path + "'!");
+    
+    return o;
+  }
+  
+  /**
+   * returns a fully configures object (a copy of the provided one)
+   * 
+   * @param original	the object to create a copy from and set the parameters
+   * @param valueX	the current iteration value for X 
+   * @param valueY	the current iteration value for Y
+   * @return		the configured classifier
+   * @throws Exception	if setup fails
+   */
+  protected Object setup(Object original, double valueX, double valueY) throws Exception {
+    Object	result;
+    
+    result = new SerializedObject(original).getObject();
+    
+    if (original instanceof Classifier) {
+      if (getXProperty().startsWith(PREFIX_CLASSIFIER))
+	setValue(
+	    result,
+	    getXProperty().substring(PREFIX_CLASSIFIER.length()),
+	    valueX);
+      
+      if (getYProperty().startsWith(PREFIX_CLASSIFIER))
+	setValue(
+	    result,
+	    getYProperty().substring(PREFIX_CLASSIFIER.length()), 
+	    valueY);
+    }
+    else if (original instanceof Filter) {
+      if (getXProperty().startsWith(PREFIX_FILTER))
+	setValue(
+	    result,
+	    getXProperty().substring(PREFIX_FILTER.length()), 
+	    valueX);
+      
+      if (getYProperty().startsWith(PREFIX_FILTER))
+	setValue(
+	    result,
+	    getYProperty().substring(PREFIX_FILTER.length()), 
+	    valueY);
+    }
+    else {
+      throw new IllegalArgumentException("Object must be either classifier or filter!");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * generates a table string for all the performances in the grid and returns
+   * that.
+   * 
+   * @param grid		the current grid to align the performances to
+   * @param performances	the performances to align
+   * @param type		the type of performance
+   * @return			the table string
+   */
+  protected String logPerformances(Grid grid, Vector<Performance> performances, Tag type) {
+    StringBuffer	result;
+    PerformanceTable	table;
+    
+    result = new StringBuffer(type.getReadable() + ":\n");
+    table  = new PerformanceTable(grid, performances, type.getID());
+    
+    result.append(table.toString() + "\n");
+    result.append("\n");
+    result.append(table.toGnuplot() + "\n");
+    result.append("\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * aligns all performances in the grid and prints those tables to the log
+   * file.
+   * 
+   * @param grid		the current grid to align the performances to
+   * @param performances	the performances to align
+   */
+  protected void logPerformances(Grid grid, Vector performances) {
+    int		i;
+    
+    for (i = 0; i < TAGS_EVALUATION.length; i++)
+      log("\n" + logPerformances(grid, performances, TAGS_EVALUATION[i]), true);
+  }
+  
+  /**
+   * determines the best values-pair for the given grid, using CV with 
+   * specified number of folds.
+   * 
+   * @param grid	the grid to work on
+   * @param inst	the data to work with
+   * @param cv		the number of folds for the cross-validation
+   * @return		the best values pair
+   * @throws Exception	if setup or training fails
+   */
+  protected PointDouble determineBestInGrid(Grid grid, Instances inst, int cv) throws Exception {
+    int				i;
+    Enumeration<PointDouble>	enm;
+    Vector<Performance>		performances;
+    PointDouble			values;
+    Instances			data;
+    Evaluation			eval;
+    PointDouble			result;
+    Classifier			classifier;
+    Filter			filter;
+    int				size;
+    boolean			cached;
+    boolean			allCached;
+    Performance			p1;
+    Performance			p2;
+    double			x;
+    double			y;
+    
+    performances = new Vector();
+    
+    log("Determining best pair with " + cv + "-fold CV in Grid:\n" + grid + "\n");
+    
+    if (m_Traversal == TRAVERSAL_BY_COLUMN)
+      size = grid.width();
+    else
+      size = grid.height();
+    
+    allCached = true;
+
+    for (i = 0; i < size; i++) {
+      if (m_Traversal == TRAVERSAL_BY_COLUMN)
+	enm = grid.column(i);
+      else
+	enm = grid.row(i);
+      
+      filter = null;
+      data   = null;
+      
+      while (enm.hasMoreElements()) {
+	values = enm.nextElement();
+	
+	// already calculated?
+	cached = m_Cache.isCached(cv, values);
+	if (cached) {
+	  performances.add(m_Cache.get(cv, values));
+	}
+	else {
+	  allCached = false;
+	  
+	  x = evaluate(values.getX(), true);
+	  y = evaluate(values.getY(), false);
+	  
+	  // data pass through filter
+	  if (filter == null) {
+	    filter = (Filter) setup(getFilter(), x, y);
+	    filter.setInputFormat(inst);
+	    data = Filter.useFilter(inst, filter);
+	    // make sure that the numbers don't get too small - otherwise NaNs!
+	    Filter cleaner = new NumericCleaner();
+	    cleaner.setInputFormat(data);
+	    data = Filter.useFilter(data, cleaner);
+	  }
+
+	  // setup classifier
+	  classifier = (Classifier) setup(getClassifier(), x, y);
+
+	  // evaluate
+	  eval = new Evaluation(data);
+	  eval.crossValidateModel(classifier, data, cv, new Random(getSeed()));
+	  performances.add(new Performance(values, eval));
+	  
+	  // add to cache
+	  m_Cache.add(cv, new Performance(values, eval));
+	}
+
+	log("" + performances.get(performances.size() - 1) + ": cached=" + cached);
+      }
+    }
+
+    if (allCached) {
+      log("All points were already cached - abnormal state!");
+      throw new IllegalStateException("All points were already cached - abnormal state!");
+    }
+    
+    // sort list
+    Collections.sort(performances, new PerformanceComparator(m_Evaluation));
+
+    result = performances.get(performances.size() - 1).getValues();
+
+    // check whether all performances are the same
+    m_UniformPerformance = true;
+    p1 = performances.get(0);
+    for (i = 1; i < performances.size(); i++) {
+      p2 = performances.get(i);
+      if (p2.getPerformance(m_Evaluation) != p1.getPerformance(m_Evaluation)) {
+	m_UniformPerformance = false;
+	break;
+      }
+    }
+    if (m_UniformPerformance)
+      log("All performances are the same!");
+    
+    logPerformances(grid, performances);
+    log("\nBest performance:\n" + performances.get(performances.size() - 1));
+    
+    return result;
+  }
+  
+  /**
+   * returns the best values-pair in the grid
+   * 
+   * @return 		the best values pair
+   * @throws Exception 	if something goes wrong
+   */
+  protected PointDouble findBest() throws Exception {
+    PointInt		center;
+    Grid		neighborGrid;
+    boolean		finished;
+    PointDouble		result;
+    PointDouble		resultOld;
+    int			iteration;
+    Instances		sample;
+    Resample		resample;
+
+    log("Step 1:\n");
+
+    // generate sample?
+    if (getSampleSizePercent() == 100) {
+      sample = m_Data;
+    }
+    else {
+      log("Generating sample (" + getSampleSizePercent() + "%)");
+      resample = new Resample();
+      resample.setRandomSeed(getSeed());
+      resample.setSampleSizePercent(getSampleSizePercent());
+      resample.setInputFormat(m_Data);
+      sample = Filter.useFilter(m_Data, resample);
+    }
+    
+    finished                  = false;
+    iteration                 = 0;
+    m_GridExtensionsPerformed = 0;
+    m_UniformPerformance      = false;
+    
+    // find first center
+    log("\n=== Initial grid - Start ===");
+    result = determineBestInGrid(m_Grid, sample, 2);
+    log("\nResult of Step 1: " + result + "\n");
+    log("=== Initial grid - End ===\n");
+
+    finished = m_UniformPerformance;
+    
+    if (!finished) {
+      do {
+	iteration++;
+	resultOld = (PointDouble) result.clone();
+	center    = m_Grid.getLocation(result);
+	// on border? -> finished (if it cannot be extended)
+	if (m_Grid.isOnBorder(center)) {
+	  log("Center is on border of grid.");
+
+	  // can we extend grid?
+	  if (getGridIsExtendable()) {
+	    // max number of extensions reached?
+	    if (m_GridExtensionsPerformed == getMaxGridExtensions()) {
+	      log("Maximum number of extensions reached!\n");
+	      finished = true;
+	    }
+	    else {
+	      m_GridExtensionsPerformed++;
+	      m_Grid = m_Grid.extend(result);
+	      center = m_Grid.getLocation(result);
+	      log("Extending grid (" + m_GridExtensionsPerformed + "/" 
+		  + getMaxGridExtensions() + "):\n" + m_Grid + "\n");
+	    }
+	  }
+	  else {
+	    finished = true;
+	  }
+	}
+
+	// new grid with current best one at center and immediate neighbors 
+	// around it
+	if (!finished) {
+	  neighborGrid = m_Grid.subgrid(
+	      (int) center.getY() + 1, (int) center.getX() - 1, 
+	      (int) center.getY() - 1, (int) center.getX() + 1);
+	  result = determineBestInGrid(neighborGrid, sample, 10);
+	  log("\nResult of Step 2/Iteration " + (iteration) + ":\n" + result);
+	  finished = m_UniformPerformance;
+
+	  // no improvement?
+	  if (result.equals(resultOld)) {
+	    finished = true;
+	    log("\nNo better point found.");
+	  }
+	}
+      }
+      while (!finished);
+    }
+    
+    log("\nFinal result: " + result);
+
+    return result;
+  }
+  
+  /**
+   * builds the classifier
+   * 
+   * @param data        the training instances
+   * @throws Exception  if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    String	strX;
+    String	strY;
+    double	x;
+    double	y;
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    m_Data = new Instances(data);
+    m_Data.deleteWithMissingClass();
+    
+    m_Cache = new PerformanceCache();
+    
+    if (getXProperty().startsWith(PREFIX_FILTER))
+      strX = m_Filter.getClass().getName();
+    else
+      strX = m_Classifier.getClass().getName();
+    
+    if (getYProperty().startsWith(PREFIX_CLASSIFIER))
+      strY = m_Classifier.getClass().getName();
+    else
+      strY = m_Filter.getClass().getName();
+    
+    m_Grid = new Grid(getXMin(), getXMax(), getXStep(), 
+		      strX + ", property " + getXProperty() + ", expr. " + getXExpression() + ", base " + getXBase(),
+	              getYMin(), getYMax(), getYStep(),
+	              strY + ", property " + getYProperty() + ", expr. " + getYExpression() + ", base " + getYBase());
+
+    log("\n"  
+	+ this.getClass().getName() + "\n" 
+	+ this.getClass().getName().replaceAll(".", "=") + "\n"
+	+ "Options: " + Utils.joinOptions(getOptions()) + "\n");
+    
+    // find best
+    m_Values = findBest();
+
+    // setup best configurations
+    x                = evaluate(m_Values.getX(), true);
+    y                = evaluate(m_Values.getY(), false);
+    m_BestFilter     = (Filter) setup(getFilter(), x, y);
+    m_BestClassifier = (Classifier) setup(getClassifier(), x, y);
+    
+    // process data
+    m_Filter = (Filter) setup(getFilter(), x, y);
+    m_Filter.setInputFormat(m_Data);
+    Instances transformed = Filter.useFilter(m_Data, m_Filter);
+    
+    // train classifier
+    m_Classifier = (Classifier) setup(getClassifier(), x, y);
+    m_Classifier.buildClassifier(transformed);
+  }
+
+  /**
+   * Classifies the given instance.
+   *
+   * @param instance 	the test instance
+   * @return 		the classification
+   * @throws Exception 	if classification can't be done successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    // transform instance
+    m_Filter.input(instance);
+    m_Filter.batchFinished();
+    Instance transformed = m_Filter.output();
+    
+    // classify instance
+    return m_Classifier.classifyInstance(transformed);
+  }
+
+  /**
+   * returns a string representation of the classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+    String	result;
+    
+    result = "";
+    
+    if (m_Values == null) {
+      result = "No search performed yet.";
+    }
+    else {
+      result = 
+      	  this.getClass().getName() + ":\n"
+      	+ "Filter: " + getFilter().getClass().getName() 
+      	+ (getFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()) : "") + "\n"
+      	+ "Classifier: " + getClassifier().getClass().getName() 
+      	+ " " + Utils.joinOptions(((OptionHandler)getClassifier()).getOptions()) + "\n\n"
+      	+ "X property: " + getXProperty() + "\n"
+      	+ "Y property: " + getYProperty() + "\n\n"
+      	+ "Evaluation: " + getEvaluation().getSelectedTag().getReadable() + "\n"
+      	+ "Coordinates: " + getValues() + "\n";
+      
+      if (getGridIsExtendable())
+	result += "Grid-Extensions: " + getGridExtensionsPerformed() + "\n";
+      
+      result += 
+	"Values: "
+	+ evaluate(getValues().getX(), true) + " (X coordinate)" 
+	+ ", "
+	+ evaluate(getValues().getY(), false) + " (Y coordinate)"
+	+ "\n\n"
+	+ m_Classifier.toString();
+    }
+    
+    return result;
+  }
+
+  /**
+   * Returns a string that summarizes the object.
+   *
+   * @return 		the object summarized as a string
+   */
+  public String toSummaryString() {
+    String	result;
+    
+    result = 
+        "Best filter: " + getBestFilter().getClass().getName() 
+      + (getBestFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getBestFilter()).getOptions()) : "") + "\n"
+      + "Best classifier: " + getBestClassifier().getClass().getName() 
+      + " " + Utils.joinOptions(((OptionHandler)getBestClassifier()).getOptions());
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for running this classifier from commandline.
+   * 
+   * @param args 	the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new GridSearch(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/LogitBoost.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/LogitBoost.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/LogitBoost.java	(revision 29)
@@ -0,0 +1,1172 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LogitBoost.java
+ *    Copyright (C) 1999, 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.classifiers.Sourcable;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for performing additive logistic regression. <br/>
+ * This class performs classification using a regression scheme as the base learner, and can handle multi-class problems.  For more information, see<br/>
+ * <br/>
+ * J. Friedman, T. Hastie, R. Tibshirani (1998). Additive Logistic Regression: a Statistical View of Boosting. Stanford University.<br/>
+ * <br/>
+ * Can do efficient internal cross-validation to determine appropriate number of iterations.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Friedman1998,
+ *    address = {Stanford University},
+ *    author = {J. Friedman and T. Hastie and R. Tibshirani},
+ *    title = {Additive Logistic Regression: a Statistical View of Boosting},
+ *    year = {1998},
+ *    PS = {http://www-stat.stanford.edu/\~jhf/ftp/boost.ps}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -Q
+ *  Use resampling instead of reweighting for boosting.</pre>
+ * 
+ * <pre> -P &lt;percent&gt;
+ *  Percentage of weight mass to base training on.
+ *  (default 100, reduce to around 90 speed up)</pre>
+ * 
+ * <pre> -F &lt;num&gt;
+ *  Number of folds for internal cross-validation.
+ *  (default 0 -- no cross-validation)</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  Number of runs for internal cross-validation.
+ *  (default 1)</pre>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  Threshold on the improvement of the likelihood.
+ *  (default -Double.MAX_VALUE)</pre>
+ * 
+ * <pre> -H &lt;num&gt;
+ *  Shrinkage parameter.
+ *  (default 1)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated learner.<p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6091 $ 
+ */
+public class LogitBoost 
+  extends RandomizableIteratedSingleClassifierEnhancer
+  implements Sourcable, WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3905660358715833753L;
+  
+  /** Array for storing the generated base classifiers. 
+   Note: we are hiding the variable from IteratedSingleClassifierEnhancer*/
+  protected Classifier [][] m_Classifiers;
+
+  /** The number of classes */
+  protected int m_NumClasses;
+
+  /** The number of successfully generated base classifiers. */
+  protected int m_NumGenerated;
+
+  /** The number of folds for the internal cross-validation. */
+  protected int m_NumFolds = 0;
+
+  /** The number of runs for the internal cross-validation. */
+  protected int m_NumRuns = 1;
+
+  /** Weight thresholding. The percentage of weight mass used in training */
+  protected int m_WeightThreshold = 100;
+
+  /** A threshold for responses (Friedman suggests between 2 and 4) */
+  protected static final double Z_MAX = 3;
+
+  /** Dummy dataset with a numeric class */
+  protected Instances m_NumericClassData;
+
+  /** The actual class attribute (for getting class names) */
+  protected Attribute m_ClassAttribute;
+
+  /** Use boosting with reweighting? */
+  protected boolean m_UseResampling;
+
+  /** The threshold on the improvement of the likelihood */   
+  protected double m_Precision = -Double.MAX_VALUE;
+
+  /** The value of the shrinkage parameter */
+  protected double m_Shrinkage = 1;
+
+  /** The random number generator used */
+  protected Random m_RandomInstance = null;
+
+  /** The value by which the actual target value for the
+      true class is offset. */
+  protected double m_Offset = 0.0;
+    
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Class for performing additive logistic regression. \n"
+      + "This class performs classification using a regression scheme as the "
+      + "base learner, and can handle multi-class problems.  For more "
+      + "information, see\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "Can do efficient internal cross-validation to determine "
+      + "appropriate number of iterations.";
+  }
+    
+  /**
+   * Constructor.
+   */
+  public LogitBoost() {
+    
+    m_Classifier = new weka.classifiers.trees.DecisionStump();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "J. Friedman and T. Hastie and R. Tibshirani");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Additive Logistic Regression: a Statistical View of Boosting");
+    result.setValue(Field.ADDRESS, "Stanford University");
+    result.setValue(Field.PS, "http://www-stat.stanford.edu/~jhf/ftp/boost.ps");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+  /**
+   * Select only instances with weights that contribute to 
+   * the specified quantile of the weight distribution
+   *
+   * @param data the input instances
+   * @param quantile the specified quantile eg 0.9 to select 
+   * 90% of the weight mass
+   * @return the selected instances
+   */
+  protected Instances selectWeightQuantile(Instances data, double quantile) { 
+
+    int numInstances = data.numInstances();
+    Instances trainData = new Instances(data, numInstances);
+    double [] weights = new double [numInstances];
+
+    double sumOfWeights = 0;
+    for (int i = 0; i < numInstances; i++) {
+      weights[i] = data.instance(i).weight();
+      sumOfWeights += weights[i];
+    }
+    double weightMassToSelect = sumOfWeights * quantile;
+    int [] sortedIndices = Utils.sort(weights);
+
+    // Select the instances
+    sumOfWeights = 0;
+    for (int i = numInstances-1; i >= 0; i--) {
+      Instance instance = (Instance)data.instance(sortedIndices[i]).copy();
+      trainData.add(instance);
+      sumOfWeights += weights[sortedIndices[i]];
+      if ((sumOfWeights > weightMassToSelect) && 
+	  (i > 0) && 
+	  (weights[sortedIndices[i]] != weights[sortedIndices[i-1]])) {
+	break;
+      }
+    }
+    if (m_Debug) {
+      System.err.println("Selected " + trainData.numInstances()
+			 + " out of " + numInstances);
+    }
+    return trainData;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	      "\tUse resampling instead of reweighting for boosting.",
+	      "Q", 0, "-Q"));
+    newVector.addElement(new Option(
+	      "\tPercentage of weight mass to base training on.\n"
+	      +"\t(default 100, reduce to around 90 speed up)",
+	      "P", 1, "-P <percent>"));
+    newVector.addElement(new Option(
+	      "\tNumber of folds for internal cross-validation.\n"
+	      +"\t(default 0 -- no cross-validation)",
+	      "F", 1, "-F <num>"));
+    newVector.addElement(new Option(
+	      "\tNumber of runs for internal cross-validation.\n"
+	      +"\t(default 1)",
+	      "R", 1, "-R <num>"));
+    newVector.addElement(new Option(
+	      "\tThreshold on the improvement of the likelihood.\n"
+	      +"\t(default -Double.MAX_VALUE)",
+	      "L", 1, "-L <num>"));
+    newVector.addElement(new Option(
+	      "\tShrinkage parameter.\n"
+	      +"\t(default 1)",
+	      "H", 1, "-H <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -Q
+   *  Use resampling instead of reweighting for boosting.</pre>
+   * 
+   * <pre> -P &lt;percent&gt;
+   *  Percentage of weight mass to base training on.
+   *  (default 100, reduce to around 90 speed up)</pre>
+   * 
+   * <pre> -F &lt;num&gt;
+   *  Number of folds for internal cross-validation.
+   *  (default 0 -- no cross-validation)</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  Number of runs for internal cross-validation.
+   *  (default 1)</pre>
+   * 
+   * <pre> -L &lt;num&gt;
+   *  Threshold on the improvement of the likelihood.
+   *  (default -Double.MAX_VALUE)</pre>
+   * 
+   * <pre> -H &lt;num&gt;
+   *  Shrinkage parameter.
+   *  (default 1)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated learner.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String numFolds = Utils.getOption('F', options);
+    if (numFolds.length() != 0) {
+      setNumFolds(Integer.parseInt(numFolds));
+    } else {
+      setNumFolds(0);
+    }
+    
+    String numRuns = Utils.getOption('R', options);
+    if (numRuns.length() != 0) {
+      setNumRuns(Integer.parseInt(numRuns));
+    } else {
+      setNumRuns(1);
+    }
+
+    String thresholdString = Utils.getOption('P', options);
+    if (thresholdString.length() != 0) {
+      setWeightThreshold(Integer.parseInt(thresholdString));
+    } else {
+      setWeightThreshold(100);
+    }
+
+    String precisionString = Utils.getOption('L', options);
+    if (precisionString.length() != 0) {
+      setLikelihoodThreshold(new Double(precisionString).
+	doubleValue());
+    } else {
+      setLikelihoodThreshold(-Double.MAX_VALUE);
+    }
+
+    String shrinkageString = Utils.getOption('H', options);
+    if (shrinkageString.length() != 0) {
+      setShrinkage(new Double(shrinkageString).
+	doubleValue());
+    } else {
+      setShrinkage(1.0);
+    }
+
+    setUseResampling(Utils.getFlag('Q', options));
+    if (m_UseResampling && (thresholdString.length() != 0)) {
+      throw new Exception("Weight pruning with resampling"+
+			  "not allowed.");
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 10];
+
+    int current = 0;
+    if (getUseResampling()) {
+      options[current++] = "-Q";
+    } else {
+      options[current++] = "-P"; 
+      options[current++] = "" + getWeightThreshold();
+    }
+    options[current++] = "-F"; options[current++] = "" + getNumFolds();
+    options[current++] = "-R"; options[current++] = "" + getNumRuns();
+    options[current++] = "-L"; options[current++] = "" + getLikelihoodThreshold();
+    options[current++] = "-H"; options[current++] = "" + getShrinkage();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String shrinkageTipText() {
+    return "Shrinkage parameter (use small value like 0.1 to reduce "
+      + "overfitting).";
+  }
+			 
+  /**
+   * Get the value of Shrinkage.
+   *
+   * @return Value of Shrinkage.
+   */
+  public double getShrinkage() {
+    
+    return m_Shrinkage;
+  }
+  
+  /**
+   * Set the value of Shrinkage.
+   *
+   * @param newShrinkage Value to assign to Shrinkage.
+   */
+  public void setShrinkage(double newShrinkage) {
+    
+    m_Shrinkage = newShrinkage;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String likelihoodThresholdTipText() {
+    return "Threshold on improvement in likelihood.";
+  }
+			 
+  /**
+   * Get the value of Precision.
+   *
+   * @return Value of Precision.
+   */
+  public double getLikelihoodThreshold() {
+    
+    return m_Precision;
+  }
+  
+  /**
+   * Set the value of Precision.
+   *
+   * @param newPrecision Value to assign to Precision.
+   */
+  public void setLikelihoodThreshold(double newPrecision) {
+    
+    m_Precision = newPrecision;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numRunsTipText() {
+    return "Number of runs for internal cross-validation.";
+  }
+  
+  /**
+   * Get the value of NumRuns.
+   *
+   * @return Value of NumRuns.
+   */
+  public int getNumRuns() {
+    
+    return m_NumRuns;
+  }
+  
+  /**
+   * Set the value of NumRuns.
+   *
+   * @param newNumRuns Value to assign to NumRuns.
+   */
+  public void setNumRuns(int newNumRuns) {
+    
+    m_NumRuns = newNumRuns;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Number of folds for internal cross-validation (default 0 "
+      + "means no cross-validation is performed).";
+  }
+  
+  /**
+   * Get the value of NumFolds.
+   *
+   * @return Value of NumFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_NumFolds;
+  }
+  
+  /**
+   * Set the value of NumFolds.
+   *
+   * @param newNumFolds Value to assign to NumFolds.
+   */
+  public void setNumFolds(int newNumFolds) {
+    
+    m_NumFolds = newNumFolds;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useResamplingTipText() {
+    return "Whether resampling is used instead of reweighting.";
+  }
+  
+  /**
+   * Set resampling mode
+   *
+   * @param r true if resampling should be done
+   */
+  public void setUseResampling(boolean r) {
+    
+    m_UseResampling = r;
+  }
+
+  /**
+   * Get whether resampling is turned on
+   *
+   * @return true if resampling output is on
+   */
+  public boolean getUseResampling() {
+    
+    return m_UseResampling;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightThresholdTipText() {
+    return "Weight threshold for weight pruning (reduce to 90 "
+      + "for speeding up learning process).";
+  }
+
+  /**
+   * Set weight thresholding
+   *
+   * @param threshold the percentage of weight mass used for training
+   */
+  public void setWeightThreshold(int threshold) {
+
+    m_WeightThreshold = threshold;
+  }
+
+  /**
+   * Get the degree of weight thresholding
+   *
+   * @return the percentage of weight mass used for training
+   */
+  public int getWeightThreshold() {
+
+    return m_WeightThreshold;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the boosted classifier
+   * 
+   * @param data the data to train the classifier with
+   * @throws Exception if building fails, e.g., can't handle data
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    m_RandomInstance = new Random(m_Seed);
+    int classIndex = data.classIndex();
+
+    if (m_Classifier == null) {
+      throw new Exception("A base classifier has not been specified!");
+    }
+    
+    if (!(m_Classifier instanceof WeightedInstancesHandler) &&
+	!m_UseResampling) {
+      m_UseResampling = true;
+    }
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    if (m_Debug) {
+      System.err.println("Creating copy of the training data");
+    }
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (data.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(data);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_NumClasses = data.numClasses();
+    m_ClassAttribute = data.classAttribute();
+
+    // Create the base classifiers
+    if (m_Debug) {
+      System.err.println("Creating base classifiers");
+    }
+    m_Classifiers = new Classifier [m_NumClasses][];
+    for (int j = 0; j < m_NumClasses; j++) {
+      m_Classifiers[j] = AbstractClassifier.makeCopies(m_Classifier,
+					       getNumIterations());
+    }
+
+    // Do we want to select the appropriate number of iterations
+    // using cross-validation?
+    int bestNumIterations = getNumIterations();
+    if (m_NumFolds > 1) {
+      if (m_Debug) {
+	System.err.println("Processing first fold.");
+      }
+
+      // Array for storing the results
+      double[] results = new double[getNumIterations()];
+
+      // Iterate throught the cv-runs
+      for (int r = 0; r < m_NumRuns; r++) {
+
+	// Stratify the data
+	data.randomize(m_RandomInstance);
+	data.stratify(m_NumFolds);
+	
+	// Perform the cross-validation
+	for (int i = 0; i < m_NumFolds; i++) {
+	  
+	  // Get train and test folds
+	  Instances train = data.trainCV(m_NumFolds, i, m_RandomInstance);
+	  Instances test = data.testCV(m_NumFolds, i);
+	  
+	  // Make class numeric
+	  Instances trainN = new Instances(train);
+	  trainN.setClassIndex(-1);
+	  trainN.deleteAttributeAt(classIndex);
+	  trainN.insertAttributeAt(new Attribute("'pseudo class'"), classIndex);
+	  trainN.setClassIndex(classIndex);
+	  m_NumericClassData = new Instances(trainN, 0);
+	  
+	  // Get class values
+	  int numInstances = train.numInstances();
+	  double [][] trainFs = new double [numInstances][m_NumClasses];
+	  double [][] trainYs = new double [numInstances][m_NumClasses];
+	  for (int j = 0; j < m_NumClasses; j++) {
+	    for (int k = 0; k < numInstances; k++) {
+	      trainYs[k][j] = (train.instance(k).classValue() == j) ? 
+		1.0 - m_Offset: 0.0 + (m_Offset / (double)m_NumClasses);
+	    }
+	  }
+	  
+	  // Perform iterations
+	  double[][] probs = initialProbs(numInstances);
+	  m_NumGenerated = 0;
+	  double sumOfWeights = train.sumOfWeights();
+	  for (int j = 0; j < getNumIterations(); j++) {
+	    performIteration(trainYs, trainFs, probs, trainN, sumOfWeights);
+	    Evaluation eval = new Evaluation(train);
+	    eval.evaluateModel(this, test);
+	    results[j] += eval.correct();
+	  }
+	}
+      }
+      
+      // Find the number of iterations with the lowest error
+      double bestResult = -Double.MAX_VALUE;
+      for (int j = 0; j < getNumIterations(); j++) {
+	if (results[j] > bestResult) {
+	  bestResult = results[j];
+	  bestNumIterations = j;
+	}
+      }
+      if (m_Debug) {
+	System.err.println("Best result for " + 
+			   bestNumIterations + " iterations: " +
+			   bestResult);
+      }
+    }
+
+    // Build classifier on all the data
+    int numInstances = data.numInstances();
+    double [][] trainFs = new double [numInstances][m_NumClasses];
+    double [][] trainYs = new double [numInstances][m_NumClasses];
+    for (int j = 0; j < m_NumClasses; j++) {
+      for (int i = 0, k = 0; i < numInstances; i++, k++) {
+	trainYs[i][j] = (data.instance(k).classValue() == j) ? 
+	  1.0 - m_Offset: 0.0 + (m_Offset / (double)m_NumClasses);
+      }
+    }
+    
+    // Make class numeric
+    data.setClassIndex(-1);
+    data.deleteAttributeAt(classIndex);
+    data.insertAttributeAt(new Attribute("'pseudo class'"), classIndex);
+    data.setClassIndex(classIndex);
+    m_NumericClassData = new Instances(data, 0);
+	
+    // Perform iterations
+    double[][] probs = initialProbs(numInstances);
+    double logLikelihood = logLikelihood(trainYs, probs);
+    m_NumGenerated = 0;
+    if (m_Debug) {
+      System.err.println("Avg. log-likelihood: " + logLikelihood);
+    }
+    double sumOfWeights = data.sumOfWeights();
+    for (int j = 0; j < bestNumIterations; j++) {
+      double previousLoglikelihood = logLikelihood;
+      performIteration(trainYs, trainFs, probs, data, sumOfWeights);
+      logLikelihood = logLikelihood(trainYs, probs);
+      if (m_Debug) {
+	System.err.println("Avg. log-likelihood: " + logLikelihood);
+      }
+      if (Math.abs(previousLoglikelihood - logLikelihood) < m_Precision) {
+	return;
+      }
+    }
+  }
+
+  /**
+   * Gets the intial class probabilities.
+   * 
+   * @param numInstances the number of instances
+   * @return the initial class probabilities
+   */
+  private double[][] initialProbs(int numInstances) {
+
+    double[][] probs = new double[numInstances][m_NumClasses];
+    for (int i = 0; i < numInstances; i++) {
+      for (int j = 0 ; j < m_NumClasses; j++) {
+	probs[i][j] = 1.0 / m_NumClasses;
+      }
+    }
+    return probs;
+  }
+
+  /**
+   * Computes loglikelihood given class values
+   * and estimated probablities.
+   * 
+   * @param trainYs class values
+   * @param probs estimated probabilities
+   * @return the computed loglikelihood
+   */
+  private double logLikelihood(double[][] trainYs, double[][] probs) {
+
+    double logLikelihood = 0;
+    for (int i = 0; i < trainYs.length; i++) {
+      for (int j = 0; j < m_NumClasses; j++) {
+	if (trainYs[i][j] == 1.0 - m_Offset) {
+	  logLikelihood -= Math.log(probs[i][j]);
+	}
+      }
+    }
+    return logLikelihood / (double)trainYs.length;
+  }
+
+  /**
+   * Performs one boosting iteration.
+   * 
+   * @param trainYs class values
+   * @param trainFs F scores
+   * @param probs probabilities
+   * @param data the data to run the iteration on
+   * @param origSumOfWeights the original sum of weights
+   * @throws Exception in case base classifiers run into problems
+   */
+  private void performIteration(double[][] trainYs,
+				double[][] trainFs,
+				double[][] probs,
+				Instances data,
+				double origSumOfWeights) throws Exception {
+
+    if (m_Debug) {
+      System.err.println("Training classifier " + (m_NumGenerated + 1));
+    }
+
+    // Build the new models
+    for (int j = 0; j < m_NumClasses; j++) {
+      if (m_Debug) {
+	System.err.println("\t...for class " + (j + 1)
+			   + " (" + m_ClassAttribute.name() 
+			   + "=" + m_ClassAttribute.value(j) + ")");
+      }
+    
+      // Make copy because we want to save the weights
+      Instances boostData = new Instances(data);
+      
+      // Set instance pseudoclass and weights
+      for (int i = 0; i < probs.length; i++) {
+
+	// Compute response and weight
+	double p = probs[i][j];
+	double z, actual = trainYs[i][j];
+	if (actual == 1 - m_Offset) {
+	  z = 1.0 / p;
+	  if (z > Z_MAX) { // threshold
+	    z = Z_MAX;
+	  }
+	} else {
+	  z = -1.0 / (1.0 - p);
+	  if (z < -Z_MAX) { // threshold
+	    z = -Z_MAX;
+	  }
+	}
+	double w = (actual - p) / z;
+
+	// Set values for instance
+	Instance current = boostData.instance(i);
+	current.setValue(boostData.classIndex(), z);
+	current.setWeight(current.weight() * w);
+      }
+      
+      // Scale the weights (helps with some base learners)
+      double sumOfWeights = boostData.sumOfWeights();
+      double scalingFactor = (double)origSumOfWeights / sumOfWeights;
+      for (int i = 0; i < probs.length; i++) {
+	Instance current = boostData.instance(i);
+	current.setWeight(current.weight() * scalingFactor);
+      }
+
+      // Select instances to train the classifier on
+      Instances trainData = boostData;
+      if (m_WeightThreshold < 100) {
+	trainData = selectWeightQuantile(boostData, 
+					 (double)m_WeightThreshold / 100);
+      } else {
+	if (m_UseResampling) {
+	  double[] weights = new double[boostData.numInstances()];
+	  for (int kk = 0; kk < weights.length; kk++) {
+	    weights[kk] = boostData.instance(kk).weight();
+	  }
+	  trainData = boostData.resampleWithWeights(m_RandomInstance, 
+						    weights);
+	}
+      }
+      
+      // Build the classifier
+      m_Classifiers[j][m_NumGenerated].buildClassifier(trainData);
+    }      
+    
+    // Evaluate / increment trainFs from the classifier
+    for (int i = 0; i < trainFs.length; i++) {
+      double [] pred = new double [m_NumClasses];
+      double predSum = 0;
+      for (int j = 0; j < m_NumClasses; j++) {
+	pred[j] = m_Shrinkage * m_Classifiers[j][m_NumGenerated]
+	  .classifyInstance(data.instance(i));
+	predSum += pred[j];
+      }
+      predSum /= m_NumClasses;
+      for (int j = 0; j < m_NumClasses; j++) {
+	trainFs[i][j] += (pred[j] - predSum) * (m_NumClasses - 1) 
+	  / m_NumClasses;
+      }
+    }
+    m_NumGenerated++;
+    
+    // Compute the current probability estimates
+    for (int i = 0; i < trainYs.length; i++) {
+      probs[i] = probs(trainFs[i]);
+    }
+  }
+
+  /**
+   * Returns the array of classifiers that have been built.
+   * 
+   * @return the built classifiers
+   */
+  public Classifier[][] classifiers() {
+
+    Classifier[][] classifiers = 
+      new Classifier[m_NumClasses][m_NumGenerated];
+    for (int j = 0; j < m_NumClasses; j++) {
+      for (int i = 0; i < m_NumGenerated; i++) {
+	classifiers[j][i] = m_Classifiers[j][i];
+      }
+    }
+    return classifiers;
+  }
+
+  /**
+   * Computes probabilities from F scores
+   * 
+   * @param Fs the F scores
+   * @return the computed probabilities
+   */
+  private double[] probs(double[] Fs) {
+
+    double maxF = -Double.MAX_VALUE;
+    for (int i = 0; i < Fs.length; i++) {
+      if (Fs[i] > maxF) {
+	maxF = Fs[i];
+      }
+    }
+    double sum = 0;
+    double[] probs = new double[Fs.length];
+    for (int i = 0; i < Fs.length; i++) {
+      probs[i] = Math.exp(Fs[i] - maxF);
+      sum += probs[i];
+    }
+    Utils.normalize(probs, sum);
+    return probs;
+  }
+    
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    instance = (Instance)instance.copy();
+    instance.setDataset(m_NumericClassData);
+    double [] pred = new double [m_NumClasses];
+    double [] Fs = new double [m_NumClasses]; 
+    for (int i = 0; i < m_NumGenerated; i++) {
+      double predSum = 0;
+      for (int j = 0; j < m_NumClasses; j++) {
+	pred[j] = m_Shrinkage * m_Classifiers[j][i].classifyInstance(instance);
+	predSum += pred[j];
+      }
+      predSum /= m_NumClasses;
+      for (int j = 0; j < m_NumClasses; j++) {
+	Fs[j] += (pred[j] - predSum) * (m_NumClasses - 1) 
+	  / m_NumClasses;
+      }
+    }
+
+    return probs(Fs);
+  }
+
+  /**
+   * Returns the boosted model as Java source code.
+   *
+   * @param className the classname in the generated code
+   * @return the tree as Java source code
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) throws Exception {
+
+    if (m_NumGenerated == 0) {
+      throw new Exception("No model built yet");
+    }
+    if (!(m_Classifiers[0][0] instanceof Sourcable)) {
+      throw new Exception("Base learner " + m_Classifier.getClass().getName()
+			  + " is not Sourcable");
+    }
+
+    StringBuffer text = new StringBuffer("class ");
+    text.append(className).append(" {\n\n");
+    text.append("  private static double RtoP(double []R, int j) {\n"+
+		"    double Rcenter = 0;\n"+
+		"    for (int i = 0; i < R.length; i++) {\n"+
+		"      Rcenter += R[i];\n"+
+		"    }\n"+
+		"    Rcenter /= R.length;\n"+
+		"    double Rsum = 0;\n"+
+		"    for (int i = 0; i < R.length; i++) {\n"+
+		"      Rsum += Math.exp(R[i] - Rcenter);\n"+
+		"    }\n"+
+		"    return Math.exp(R[j]) / Rsum;\n"+
+		"  }\n\n");
+
+    text.append("  public static double classify(Object[] i) {\n" +
+                "    double [] d = distribution(i);\n" +
+                "    double maxV = d[0];\n" +
+		"    int maxI = 0;\n"+
+		"    for (int j = 1; j < " + m_NumClasses + "; j++) {\n"+
+		"      if (d[j] > maxV) { maxV = d[j]; maxI = j; }\n"+
+		"    }\n    return (double) maxI;\n  }\n\n");
+
+    text.append("  public static double [] distribution(Object [] i) {\n");
+    text.append("    double [] Fs = new double [" + m_NumClasses + "];\n");
+    text.append("    double [] Fi = new double [" + m_NumClasses + "];\n");
+    text.append("    double Fsum;\n");
+    for (int i = 0; i < m_NumGenerated; i++) {
+      text.append("    Fsum = 0;\n");
+      for (int j = 0; j < m_NumClasses; j++) {
+	text.append("    Fi[" + j + "] = " + className + '_' +j + '_' + i 
+		    + ".classify(i); Fsum += Fi[" + j + "];\n");
+      }
+      text.append("    Fsum /= " + m_NumClasses + ";\n");
+      text.append("    for (int j = 0; j < " + m_NumClasses + "; j++) {");
+      text.append(" Fs[j] += (Fi[j] - Fsum) * "
+		  + (m_NumClasses - 1) + " / " + m_NumClasses + "; }\n");
+    }
+    
+    text.append("    double [] dist = new double [" + m_NumClasses + "];\n" +
+		"    for (int j = 0; j < " + m_NumClasses + "; j++) {\n"+
+		"      dist[j] = RtoP(Fs, j);\n"+
+		"    }\n    return dist;\n");
+    text.append("  }\n}\n");
+
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      for (int j = 0; j < m_Classifiers[i].length; j++) {
+	text.append(((Sourcable)m_Classifiers[i][j])
+		    .toSource(className + '_' + i + '_' + j));
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns description of the boosted classifier.
+   *
+   * @return description of the boosted classifier as a string
+   */
+  public String toString() {
+    
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer text = new StringBuffer();
+    
+    if (m_NumGenerated == 0) {
+      text.append("LogitBoost: No model built yet.");
+      //      text.append(m_Classifiers[0].toString()+"\n");
+    } else {
+      text.append("LogitBoost: Base classifiers and their weights: \n");
+      for (int i = 0; i < m_NumGenerated; i++) {
+	text.append("\nIteration "+(i+1));
+	for (int j = 0; j < m_NumClasses; j++) {
+	  text.append("\n\tClass " + (j + 1) 
+		      + " (" + m_ClassAttribute.name() 
+		      + "=" + m_ClassAttribute.value(j) + ")\n\n"
+		      + m_Classifiers[j][i].toString() + "\n");
+	}
+      }
+      text.append("Number of performed iterations: " +
+		    m_NumGenerated + "\n");
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6091 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new LogitBoost(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/MetaCost.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/MetaCost.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/MetaCost.java	(revision 29)
@@ -0,0 +1,671 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MetaCost.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.CostMatrix;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * This metaclassifier makes its base classifier cost-sensitive using the method specified in<br/>
+ * <br/>
+ * Pedro Domingos: MetaCost: A general method for making classifiers cost-sensitive. In: Fifth International Conference on Knowledge Discovery and Data Mining, 155-164, 1999.<br/>
+ * <br/>
+ * This classifier should produce similar results to one created by passing the base learner to Bagging, which is in turn passed to a CostSensitiveClassifier operating on minimum expected cost. The difference is that MetaCost produces a single cost-sensitive classifier of the base learner, giving the benefits of fast classification and interpretable output (if the base learner itself is interpretable). This implementation  uses all bagging iterations when reclassifying training data (the MetaCost paper reports a marginal improvement when only those iterations containing each training instance are used in reclassifying that instance).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Domingos1999,
+ *    author = {Pedro Domingos},
+ *    booktitle = {Fifth International Conference on Knowledge Discovery and Data Mining},
+ *    pages = {155-164},
+ *    title = {MetaCost: A general method for making classifiers cost-sensitive},
+ *    year = {1999}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of bagging iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -C &lt;cost file name&gt;
+ *  File name of a cost matrix to use. If this is not supplied,
+ *  a cost matrix will be loaded on demand. The name of the
+ *  on-demand file is the relation name of the training data
+ *  plus ".cost", and the path to the on-demand file is
+ *  specified with the -N option.</pre>
+ * 
+ * <pre> -N &lt;directory&gt;
+ *  Name of a directory to search for cost files when loading
+ *  costs on demand (default current directory).</pre>
+ * 
+ * <pre> -cost-matrix &lt;matrix&gt;
+ *  The cost matrix in Matlab single line format.</pre>
+ * 
+ * <pre> -P
+ *  Size of each bag, as a percentage of the
+ *  training set size. (default 100)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5928 $ 
+ */
+public class MetaCost 
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 1205317833344726855L;
+  
+  /** load cost matrix on demand */
+  public static final int MATRIX_ON_DEMAND = 1;
+  /** use explicit matrix */
+  public static final int MATRIX_SUPPLIED = 2;
+  /** Specify possible sources of the cost matrix */
+  public static final Tag [] TAGS_MATRIX_SOURCE = {
+    new Tag(MATRIX_ON_DEMAND, "Load cost matrix on demand"),
+    new Tag(MATRIX_SUPPLIED, "Use explicit cost matrix")
+  };
+
+  /** Indicates the current cost matrix source */
+  protected int m_MatrixSource = MATRIX_ON_DEMAND;
+
+  /** 
+   * The directory used when loading cost files on demand, null indicates
+   * current directory 
+   */
+  protected File m_OnDemandDirectory = new File(System.getProperty("user.dir"));
+
+  /** The name of the cost file, for command line options */
+  protected String m_CostFile;
+
+  /** The cost matrix */
+  protected CostMatrix m_CostMatrix = new CostMatrix(1);
+
+  /** The number of iterations. */
+  protected int m_NumIterations = 10;
+
+  /** The size of each bag sample, as a percentage of the training size */
+  protected int m_BagSizePercent = 100;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return  "This metaclassifier makes its base classifier cost-sensitive using the "
+      + "method specified in\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "This classifier should produce similar results to one created by "
+      + "passing the base learner to Bagging, which is in turn passed to a "
+      + "CostSensitiveClassifier operating on minimum expected cost. The difference "
+      + "is that MetaCost produces a single cost-sensitive classifier of the "
+      + "base learner, giving the benefits of fast classification and interpretable "
+      + "output (if the base learner itself is interpretable). This implementation  "
+      + "uses all bagging iterations when reclassifying training data (the MetaCost "
+      + "paper reports a marginal improvement when only those iterations containing "
+      + "each training instance are used in reclassifying that instance).";
+ 
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Pedro Domingos");
+    result.setValue(Field.TITLE, "MetaCost: A general method for making classifiers cost-sensitive");
+    result.setValue(Field.BOOKTITLE, "Fifth International Conference on Knowledge Discovery and Data Mining");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.PAGES, "155-164");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	      "\tNumber of bagging iterations.\n"
+	      + "\t(default 10)",
+	      "I", 1, "-I <num>"));
+    newVector.addElement(new Option(
+	      "\tFile name of a cost matrix to use. If this is not supplied,\n"
+              +"\ta cost matrix will be loaded on demand. The name of the\n"
+              +"\ton-demand file is the relation name of the training data\n"
+              +"\tplus \".cost\", and the path to the on-demand file is\n"
+              +"\tspecified with the -N option.",
+	      "C", 1, "-C <cost file name>"));
+    newVector.addElement(new Option(
+              "\tName of a directory to search for cost files when loading\n"
+              +"\tcosts on demand (default current directory).",
+              "N", 1, "-N <directory>"));
+    newVector.addElement(new Option(
+              "\tThe cost matrix in Matlab single line format.",
+              "cost-matrix", 1, "-cost-matrix <matrix>"));
+    newVector.addElement(new Option(
+              "\tSize of each bag, as a percentage of the\n" 
+              + "\ttraining set size. (default 100)",
+              "P", 1, "-P"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of bagging iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -C &lt;cost file name&gt;
+   *  File name of a cost matrix to use. If this is not supplied,
+   *  a cost matrix will be loaded on demand. The name of the
+   *  on-demand file is the relation name of the training data
+   *  plus ".cost", and the path to the on-demand file is
+   *  specified with the -N option.</pre>
+   * 
+   * <pre> -N &lt;directory&gt;
+   *  Name of a directory to search for cost files when loading
+   *  costs on demand (default current directory).</pre>
+   * 
+   * <pre> -cost-matrix &lt;matrix&gt;
+   *  The cost matrix in Matlab single line format.</pre>
+   * 
+   * <pre> -P
+   *  Size of each bag, as a percentage of the
+   *  training set size. (default 100)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String bagIterations = Utils.getOption('I', options);
+    if (bagIterations.length() != 0) {
+      setNumIterations(Integer.parseInt(bagIterations));
+    } else {
+      setNumIterations(10);
+    }
+
+    String bagSize = Utils.getOption('P', options);
+    if (bagSize.length() != 0) {
+      setBagSizePercent(Integer.parseInt(bagSize));
+    } else {
+      setBagSizePercent(100);
+    }
+
+    String costFile = Utils.getOption('C', options);
+    if (costFile.length() != 0) {
+      setCostMatrix(new CostMatrix(new BufferedReader(
+                                   new FileReader(costFile))));
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+      m_CostFile = costFile;
+    } else {
+      setCostMatrixSource(new SelectedTag(MATRIX_ON_DEMAND, 
+                                          TAGS_MATRIX_SOURCE));
+    }
+    
+    String demandDir = Utils.getOption('N', options);
+    if (demandDir.length() != 0) {
+      setOnDemandDirectory(new File(demandDir));
+    }
+
+    String cost_matrix= Utils.getOption("cost-matrix", options);
+    if (cost_matrix.length() != 0) {
+      StringWriter writer = new StringWriter();
+      CostMatrix.parseMatlab(cost_matrix).write(writer);
+      setCostMatrix(new CostMatrix(new StringReader(writer.toString())));
+      setCostMatrixSource(new SelectedTag(MATRIX_SUPPLIED,
+                                          TAGS_MATRIX_SOURCE));
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+
+    String [] superOptions = super.getOptions();
+    String [] options;
+
+    options = new String [superOptions.length + 6];
+    int current = 0;
+
+    if (m_MatrixSource == MATRIX_SUPPLIED) {
+      if (m_CostFile != null) {
+        options[current++] = "-C";
+        options[current++] = "" + m_CostFile;
+      }
+      else {
+        options[current++] = "-cost-matrix";
+        options[current++] = getCostMatrix().toMatlab();
+      }
+    } else {
+      options[current++] = "-N";
+      options[current++] = "" + getOnDemandDirectory();
+    }
+    options[current++] = "-I"; options[current++] = "" + getNumIterations();
+    options[current++] = "-P"; options[current++] = "" + getBagSizePercent();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixSourceTipText() {
+    return "Gets the source location method of the cost matrix. Will "
+      + "be one of MATRIX_ON_DEMAND or MATRIX_SUPPLIED.";
+  }
+
+  /**
+   * Gets the source location method of the cost matrix. Will be one of
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED.
+   *
+   * @return the cost matrix source.
+   */
+  public SelectedTag getCostMatrixSource() {
+
+    return new SelectedTag(m_MatrixSource, TAGS_MATRIX_SOURCE);
+  }
+  
+  /**
+   * Sets the source location of the cost matrix. Values other than
+   * MATRIX_ON_DEMAND or MATRIX_SUPPLIED will be ignored.
+   *
+   * @param newMethod the cost matrix location method.
+   */
+  public void setCostMatrixSource(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_MATRIX_SOURCE) {
+      m_MatrixSource = newMethod.getSelectedTag().getID();
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String onDemandDirectoryTipText() {
+    return "Name of directory to search for cost files when loading "
+      + "costs on demand.";
+  }
+
+  /**
+   * Returns the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @return The cost file search directory.
+   */
+  public File getOnDemandDirectory() {
+
+    return m_OnDemandDirectory;
+  }
+
+  /**
+   * Sets the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @param newDir The cost file search directory.
+   */
+  public void setOnDemandDirectory(File newDir) {
+
+    if (newDir.isDirectory()) {
+      m_OnDemandDirectory = newDir;
+    } else {
+      m_OnDemandDirectory = new File(newDir.getParent());
+    }
+    m_MatrixSource = MATRIX_ON_DEMAND;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String bagSizePercentTipText() {
+    return "The size of each bag, as a percentage of the training set "
+      + "size.";
+  }
+
+  /**
+   * Gets the size of each bag, as a percentage of the training set size.
+   *
+   * @return the bag size, as a percentage.
+   */
+  public int getBagSizePercent() {
+
+    return m_BagSizePercent;
+  }
+  
+  /**
+   * Sets the size of each bag, as a percentage of the training set size.
+   *
+   * @param newBagSizePercent the bag size, as a percentage.
+   */
+  public void setBagSizePercent(int newBagSizePercent) {
+
+    m_BagSizePercent = newBagSizePercent;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "The number of bagging iterations.";
+  }
+  
+  /**
+   * Sets the number of bagging iterations
+   * 
+   * @param numIterations the number of iterations to use
+   */
+  public void setNumIterations(int numIterations) {
+
+    m_NumIterations = numIterations;
+  }
+
+  /**
+   * Gets the number of bagging iterations
+   *
+   * @return the maximum number of bagging iterations
+   */
+  public int getNumIterations() {
+    
+    return m_NumIterations;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String costMatrixTipText() {
+    return "A misclassification cost matrix.";
+  }
+
+  /**
+   * Gets the misclassification cost matrix.
+   *
+   * @return the cost matrix
+   */
+  public CostMatrix getCostMatrix() {
+    
+    return m_CostMatrix;
+  }
+  
+  /**
+   * Sets the misclassification cost matrix.
+   *
+   * @param newCostMatrix the cost matrix
+   */
+  public void setCostMatrix(CostMatrix newCostMatrix) {
+    
+    m_CostMatrix = newCostMatrix;
+    m_MatrixSource = MATRIX_SUPPLIED;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the model of the base learner.
+   *
+   * @param data the training data
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    if (m_MatrixSource == MATRIX_ON_DEMAND) {
+      String costName = data.relationName() + CostMatrix.FILE_EXTENSION;
+      File costFile = new File(getOnDemandDirectory(), costName);
+      if (!costFile.exists()) {
+        throw new Exception("On-demand cost file doesn't exist: " + costFile);
+      }
+      setCostMatrix(new CostMatrix(new BufferedReader(
+                                   new FileReader(costFile))));
+    }
+
+    // Set up the bagger
+    Bagging bagger = new Bagging();
+    bagger.setClassifier(getClassifier());
+    bagger.setSeed(getSeed());
+    bagger.setNumIterations(getNumIterations());
+    bagger.setBagSizePercent(getBagSizePercent());
+    bagger.buildClassifier(data);
+    
+    // Use the bagger to reassign class values according to minimum expected
+    // cost
+    Instances newData = new Instances(data);
+    for (int i = 0; i < newData.numInstances(); i++) {
+      Instance current = newData.instance(i);
+      double [] pred = bagger.distributionForInstance(current);
+      int minCostPred = Utils.minIndex(m_CostMatrix.expectedCosts(pred));
+      current.setClassValue(minCostPred);
+    }
+
+    // Build a classifier using the reassigned data
+    m_Classifier.buildClassifier(newData);
+  }
+
+  /**
+   * Classifies a given instance after filtering.
+   *
+   * @param instance the instance to be classified
+   * @return the class distribution for the given instance
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    return m_Classifier.distributionForInstance(instance);
+  }
+
+  /**
+   * Gets the classifier specification string, which contains the
+   * class name of the classifier and any options to the classifier
+   *
+   * @return the classifier string.
+   */
+  protected String getClassifierSpec() {
+    
+    Classifier c = getClassifier();
+    return c.getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)c).getOptions());
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representaiton of the classifier 
+   */
+  public String toString() {
+
+    if (m_Classifier == null) {
+      return "MetaCost: No model built yet.";
+    }
+
+    String result = "MetaCost cost sensitive classifier induction";
+    result += "\nOptions: " + Utils.joinOptions(getOptions());
+    result += "\nBase learner: " + getClassifierSpec()
+      + "\n\nClassifier Model\n"
+      + m_Classifier.toString()
+      + "\n\nCost Matrix\n"
+      + m_CostMatrix.toString();
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MetaCost(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiBoostAB.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiBoostAB.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiBoostAB.java	(revision 29)
@@ -0,0 +1,424 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultiBoostAB.java
+ *
+ *    MultiBoosting is an extension to the highly successful AdaBoost
+ *    technique for forming decision committees. MultiBoosting can be
+ *    viewed as combining AdaBoost with wagging. It is able to harness
+ *    both AdaBoost's high bias and variance reduction with wagging's
+ *    superior variance reduction. Using C4.5 as the base learning
+ *    algorithm, Multi-boosting is demonstrated to produce decision
+ *    committees with lower error than either AdaBoost or wagging
+ *    significantly more often than the reverse over a large
+ *    representative cross-section of UCI data sets. It offers the
+ *    further advantage over AdaBoost of suiting parallel execution.
+ *    
+ *    For more info refer to :
+ <!-- technical-plaintext-start -->
+ * Geoffrey I. Webb (2000). MultiBoosting: A Technique for Combining Boosting and Wagging. Machine Learning. Vol.40(No.2).
+ <!-- technical-plaintext-end -->
+ *
+ *    Originally based on AdaBoostM1.java
+ *    
+ *    http://www.cm.deakin.edu.au/webb
+ *
+ *    School of Computing and Mathematics
+ *    Deakin University
+ *    Geelong, Vic, 3217, Australia
+ *    Copyright (C) 2001 Deakin University
+ * 
+ */
+
+package weka.classifiers.meta;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for boosting a classifier using the MultiBoosting method.<br/>
+ * <br/>
+ * MultiBoosting is an extension to the highly successful AdaBoost technique for forming decision committees. MultiBoosting can be viewed as combining AdaBoost with wagging. It is able to harness both AdaBoost's high bias and variance reduction with wagging's superior variance reduction. Using C4.5 as the base learning algorithm, Multi-boosting is demonstrated to produce decision committees with lower error than either AdaBoost or wagging significantly more often than the reverse over a large representative cross-section of UCI data sets. It offers the further advantage over AdaBoost of suiting parallel execution.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Geoffrey I. Webb (2000). MultiBoosting: A Technique for Combining Boosting and Wagging. Machine Learning. Vol.40(No.2).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Webb2000,
+ *    address = {Boston},
+ *    author = {Geoffrey I. Webb},
+ *    journal = {Machine Learning},
+ *    number = {No.2},
+ *    publisher = {Kluwer Academic Publishers},
+ *    title = {MultiBoosting: A Technique for Combining Boosting and Wagging},
+ *    volume = {Vol.40},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  Number of sub-committees. (Default 3)</pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  Percentage of weight mass to base training on.
+ *  (default 100, reduce to around 90 speed up)</pre>
+ * 
+ * <pre> -Q
+ *  Use resampling for boosting.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Shane Butler (sbutle@deakin.edu.au)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.16 $ 
+ */
+public class MultiBoostAB 
+  extends AdaBoostM1
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -6681619178187935148L;
+  
+  /** The number of sub-committees to use */
+  protected int m_NumSubCmtys = 3;
+
+  /** Random number generator */
+  protected Random m_Random = null;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for boosting a classifier using the MultiBoosting method.\n\n"
+      + "MultiBoosting is an extension to the highly successful AdaBoost "
+      + "technique for forming decision committees. MultiBoosting can be "
+      + "viewed as combining AdaBoost with wagging. It is able to harness "
+      + "both AdaBoost's high bias and variance reduction with wagging's "
+      + "superior variance reduction. Using C4.5 as the base learning "
+      + "algorithm, Multi-boosting is demonstrated to produce decision "
+      + "committees with lower error than either AdaBoost or wagging "
+      + "significantly more often than the reverse over a large "
+      + "representative cross-section of UCI data sets. It offers the "
+      + "further advantage over AdaBoost of suiting parallel execution.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Geoffrey I. Webb");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.TITLE, "MultiBoosting: A Technique for Combining Boosting and Wagging");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "Vol.40");
+    result.setValue(Field.NUMBER, "No.2");
+    result.setValue(Field.PUBLISHER, "Kluwer Academic Publishers");
+    result.setValue(Field.ADDRESS, "Boston");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Enumeration enu = super.listOptions();
+    Vector vec = new Vector(1);
+
+    vec.addElement(new Option(
+	      "\tNumber of sub-committees. (Default 3)",
+	      "C", 1, "-C <num>"));
+    while (enu.hasMoreElements()) {
+      vec.addElement(enu.nextElement());
+    }
+    return vec.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  Number of sub-committees. (Default 3)</pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  Percentage of weight mass to base training on.
+   *  (default 100, reduce to around 90 speed up)</pre>
+   * 
+   * <pre> -Q
+   *  Use resampling for boosting.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String subcmtyString = Utils.getOption('C', options);
+    if (subcmtyString.length() != 0) {
+      setNumSubCmtys(Integer.parseInt(subcmtyString));
+    } else {
+      setNumSubCmtys(3);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] ops = super.getOptions();
+    String [] options = new String[ops.length + 2];
+    options[0] = "-C"; options[1] = "" + getNumSubCmtys();
+    System.arraycopy(ops, 0, options, 2, ops.length);
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numSubCmtysTipText() {
+    return "Sets the (approximate) number of subcommittees.";
+  }
+
+
+  /**
+   * Set the number of sub committees to use
+   *
+   * @param subc the number of sub committees
+   */
+  public void setNumSubCmtys(int subc) {
+
+    m_NumSubCmtys = subc;
+  }
+
+  /**
+   * Get the number of sub committees to use
+   *
+   * @return the seed for resampling
+   */
+  public int getNumSubCmtys() {
+
+    return m_NumSubCmtys;
+  }
+
+  /**
+   * Method for building this classifier.
+   * 
+   * @param training the data to train with
+   * @throws Exception if the training fails
+   */
+  public void buildClassifier(Instances training) throws Exception {
+
+    m_Random = new Random(m_Seed);
+
+    super.buildClassifier(training);
+
+    m_Random = null;
+  }
+
+  /**
+   * Sets the weights for the next iteration.
+   * 
+   * @param training the data to train with
+   * @param reweight the reweighting factor
+   * @throws Exception in case of an error
+   */
+  protected void setWeights(Instances training, double reweight) 
+    throws Exception {
+
+    int subCmtySize = m_Classifiers.length / m_NumSubCmtys;
+
+    if ((m_NumIterationsPerformed + 1) % subCmtySize == 0) {
+
+      if (getDebug())
+	System.err.println(m_NumIterationsPerformed + " " + subCmtySize);
+
+      double oldSumOfWeights = training.sumOfWeights();
+
+      // Randomly set the weights of the training instances to the poisson distributon
+      for (int i = 0; i < training.numInstances(); i++) {
+	training.instance(i).setWeight( - Math.log((m_Random.nextDouble() * 9999) / 10000) );
+      }
+
+      // Renormailise weights
+      double sumProbs = training.sumOfWeights();
+      for (int i = 0; i < training.numInstances(); i++) {
+	training.instance(i).setWeight(training.instance(i).weight() * oldSumOfWeights / sumProbs);
+      }
+    } else {
+      super.setWeights(training, reweight);
+    }
+  }
+  
+  /**
+   * Returns description of the boosted classifier.
+   *
+   * @return description of the boosted classifier as a string
+   */
+  public String toString() {
+    
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    StringBuffer text = new StringBuffer();
+    
+    if (m_NumIterations == 0) {
+      text.append("MultiBoostAB: No model built yet.\n");
+    } else if (m_NumIterations == 1) {
+      text.append("MultiBoostAB: No boosting possible, one classifier used!\n");
+      text.append(m_Classifiers[0].toString() + "\n");
+    } else {
+      text.append("MultiBoostAB: Base classifiers and their weights: \n\n");
+      for (int i = 0; i < m_NumIterations ; i++) {
+        if ( (m_Classifiers != null) && (m_Classifiers[i] != null) ) {
+          text.append(m_Classifiers[i].toString() + "\n\n");
+          text.append("Weight: " + Utils.roundDouble(m_Betas[i], 2) + "\n\n");
+        }
+        else {
+          text.append("not yet initialized!\n\n");
+        }
+      }
+      text.append("Number of performed Iterations: " + m_NumIterations + "\n");
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.16 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MultiBoostAB(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiClassClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiClassClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiClassClassifier.java	(revision 29)
@@ -0,0 +1,997 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultiClassClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+import weka.filters.unsupervised.instance.RemoveWithValues;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A metaclassifier for handling multi-class datasets with 2-class classifiers. This classifier is also capable of applying error correcting output codes for increased accuracy.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Sets the method to use. Valid values are 0 (1-against-all),
+ *  1 (random codes), 2 (exhaustive code), and 3 (1-against-1). (default 0)
+ * </pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  Sets the multiplier when using random codes. (default 2.0)</pre>
+ * 
+ * <pre> -P
+ *  Use pairwise coupling (only has an effect for 1-against1)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.functions.Logistic)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.functions.Logistic:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -R &lt;ridge&gt;
+ *  Set the ridge in the log-likelihood.</pre>
+ * 
+ * <pre> -M &lt;number&gt;
+ *  Set the maximum number of iterations (default -1, until convergence).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class MultiClassClassifier 
+  extends RandomizableSingleClassifierEnhancer 
+  implements OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3879602011542849141L;
+  
+  /** The classifiers. */
+  private Classifier [] m_Classifiers;
+
+  /** Use pairwise coupling with 1-vs-1 */
+  private boolean m_pairwiseCoupling = false;
+
+  /** Needed for pairwise coupling */
+  private double [] m_SumOfWeights;
+
+  /** The filters used to transform the class. */
+  private Filter[] m_ClassFilters;
+
+  /** ZeroR classifier for when all base classifier return zero probability. */
+  private ZeroR m_ZeroR;
+
+  /** Internal copy of the class attribute for output purposes */
+  private Attribute m_ClassAttribute;
+  
+  /** A transformed dataset header used by the  1-against-1 method */
+  private Instances m_TwoClassDataset;
+
+  /** 
+   * The multiplier when generating random codes. Will generate
+   * numClasses * m_RandomWidthFactor codes
+   */
+  private double m_RandomWidthFactor = 2.0;
+
+  /** The multiclass method to use */
+  private int m_Method = METHOD_1_AGAINST_ALL;
+
+  /** 1-against-all */
+  public static final int METHOD_1_AGAINST_ALL    = 0;
+  /** random correction code */
+  public static final int METHOD_ERROR_RANDOM     = 1;
+  /** exhaustive correction code */
+  public static final int METHOD_ERROR_EXHAUSTIVE = 2;
+  /** 1-against-1 */
+  public static final int METHOD_1_AGAINST_1      = 3;
+  /** The error correction modes */
+  public static final Tag [] TAGS_METHOD = {
+    new Tag(METHOD_1_AGAINST_ALL, "1-against-all"),
+    new Tag(METHOD_ERROR_RANDOM, "Random correction code"),
+    new Tag(METHOD_ERROR_EXHAUSTIVE, "Exhaustive correction code"),
+    new Tag(METHOD_1_AGAINST_1, "1-against-1")
+  };
+    
+  /**
+   * Constructor.
+   */
+  public MultiClassClassifier() {
+    
+    m_Classifier = new weka.classifiers.functions.Logistic();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.functions.Logistic";
+  }
+
+  /** 
+   * Interface for the code constructors 
+   */
+  private abstract class Code 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 418095077487120846L;
+    
+    /**
+     * Subclasses must allocate and fill these. 
+     * First dimension is number of codes.
+     * Second dimension is number of classes.
+     */
+    protected boolean [][]m_Codebits;
+
+    /** 
+     * Returns the number of codes. 
+     * @return the number of codes
+     */
+    public int size() {
+      return m_Codebits.length;
+    }
+
+    /** 
+     * Returns the indices of the values set to true for this code, 
+     * using 1-based indexing (for input to Range).
+     * 
+     * @param which the index
+     * @return the 1-based indices
+     */
+    public String getIndices(int which) {
+      StringBuffer sb = new StringBuffer();
+      for (int i = 0; i < m_Codebits[which].length; i++) {
+        if (m_Codebits[which][i]) {
+          if (sb.length() != 0) {
+            sb.append(',');
+          }
+          sb.append(i + 1);
+        }
+      }
+      return sb.toString();
+    }
+
+    /** 
+     * Returns a human-readable representation of the codes. 
+     * @return a string representation of the codes
+     */
+    public String toString() {
+      StringBuffer sb = new StringBuffer();
+      for(int i = 0; i < m_Codebits[0].length; i++) {
+        for (int j = 0; j < m_Codebits.length; j++) {
+          sb.append(m_Codebits[j][i] ? " 1" : " 0");
+        }
+        sb.append('\n');
+      }
+      return sb.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /** 
+   * Constructs a code with no error correction 
+   */
+  private class StandardCode 
+    extends Code {
+    
+    /** for serialization */
+    static final long serialVersionUID = 3707829689461467358L;
+    
+    /**
+     * constructor
+     * 
+     * @param numClasses the number of classes
+     */
+    public StandardCode(int numClasses) {
+      m_Codebits = new boolean[numClasses][numClasses];
+      for (int i = 0; i < numClasses; i++) {
+        m_Codebits[i][i] = true;
+      }
+      //System.err.println("Code:\n" + this);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /** 
+   * Constructs a random code assignment 
+   */
+  private class RandomCode 
+    extends Code {
+
+    /** for serialization */
+    static final long serialVersionUID = 4413410540703926563L;
+    
+    /** random number generator */
+    Random r = null;
+   
+    /**
+     * constructor
+     * 
+     * @param numClasses the number of classes
+     * @param numCodes the number of codes
+     * @param data the data to use
+     */
+    public RandomCode(int numClasses, int numCodes, Instances data) {
+      r = data.getRandomNumberGenerator(m_Seed);
+      numCodes = Math.max(2, numCodes); // Need at least two classes
+      m_Codebits = new boolean[numCodes][numClasses];
+      int i = 0;
+      do {
+        randomize();
+        //System.err.println(this);
+      } while (!good() && (i++ < 100));
+      //System.err.println("Code:\n" + this);
+    }
+
+    private boolean good() {
+      boolean [] ninClass = new boolean[m_Codebits[0].length];
+      boolean [] ainClass = new boolean[m_Codebits[0].length];
+      for (int i = 0; i < ainClass.length; i++) {
+	ainClass[i] = true;
+      }
+
+      for (int i = 0; i < m_Codebits.length; i++) {
+        boolean ninCode = false;
+        boolean ainCode = true;
+        for (int j = 0; j < m_Codebits[i].length; j++) {
+          boolean current = m_Codebits[i][j];
+          ninCode = ninCode || current;
+          ainCode = ainCode && current;
+          ninClass[j] = ninClass[j] || current;
+          ainClass[j] = ainClass[j] && current;
+        }
+        if (!ninCode || ainCode) {
+          return false;
+        }
+      }
+      for (int j = 0; j < ninClass.length; j++) {
+        if (!ninClass[j] || ainClass[j]) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * randomizes
+     */
+    private void randomize() {
+      for (int i = 0; i < m_Codebits.length; i++) {
+        for (int j = 0; j < m_Codebits[i].length; j++) {
+	  double temp = r.nextDouble();
+          m_Codebits[i][j] = (temp < 0.5) ? false : true;
+        }
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /*
+   * TODO: Constructs codes as per:
+   * Bose, R.C., Ray Chaudhuri (1960), On a class of error-correcting
+   * binary group codes, Information and Control, 3, 68-79.
+   * Hocquenghem, A. (1959) Codes corecteurs d'erreurs, Chiffres, 2, 147-156. 
+   */
+  //private class BCHCode extends Code {...}
+
+  /** Constructs an exhaustive code assignment */
+  private class ExhaustiveCode 
+    extends Code {
+
+    /** for serialization */
+    static final long serialVersionUID = 8090991039670804047L;
+    
+    /**
+     * constructor
+     * 
+     * @param numClasses the number of classes
+     */
+    public ExhaustiveCode(int numClasses) {
+      int width = (int)Math.pow(2, numClasses - 1) - 1;
+      m_Codebits = new boolean[width][numClasses];
+      for (int j = 0; j < width; j++) {
+        m_Codebits[j][0] = true;
+      }
+      for (int i = 1; i < numClasses; i++) {
+        int skip = (int) Math.pow(2, numClasses - (i + 1));
+        for(int j = 0; j < width; j++) {
+          m_Codebits[j][i] = ((j / skip) % 2 != 0);
+        }
+      }
+      //System.err.println("Code:\n" + this);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifiers.
+   *
+   * @param insts the training data.
+   * @throws Exception if a classifier can't be built
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    Instances newInsts;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("No base classifier has been set!");
+    }
+    m_ZeroR = new ZeroR();
+    m_ZeroR.buildClassifier(insts);
+
+    m_TwoClassDataset = null;
+
+    int numClassifiers = insts.numClasses();
+    if (numClassifiers <= 2) {
+
+      m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, 1);
+      m_Classifiers[0].buildClassifier(insts);
+
+      m_ClassFilters = null;
+
+    } else if (m_Method == METHOD_1_AGAINST_1) {
+      // generate fastvector of pairs
+      FastVector pairs = new FastVector();
+      for (int i=0; i<insts.numClasses(); i++) {
+	for (int j=0; j<insts.numClasses(); j++) {
+	  if (j<=i) continue;
+	  int[] pair = new int[2];
+	  pair[0] = i; pair[1] = j;
+	  pairs.addElement(pair);
+	}
+      }
+
+      numClassifiers = pairs.size();
+      m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, numClassifiers);
+      m_ClassFilters = new Filter[numClassifiers];
+      m_SumOfWeights = new double[numClassifiers];
+
+      // generate the classifiers
+      for (int i=0; i<numClassifiers; i++) {
+	RemoveWithValues classFilter = new RemoveWithValues();
+	classFilter.setAttributeIndex("" + (insts.classIndex() + 1));
+	classFilter.setModifyHeader(true);
+	classFilter.setInvertSelection(true);
+	classFilter.setNominalIndicesArr((int[])pairs.elementAt(i));
+	Instances tempInstances = new Instances(insts, 0);
+	tempInstances.setClassIndex(-1);
+	classFilter.setInputFormat(tempInstances);
+	newInsts = Filter.useFilter(insts, classFilter);
+	if (newInsts.numInstances() > 0) {
+	  newInsts.setClassIndex(insts.classIndex());
+	  m_Classifiers[i].buildClassifier(newInsts);
+	  m_ClassFilters[i] = classFilter;
+          m_SumOfWeights[i] = newInsts.sumOfWeights();
+	} else {
+	  m_Classifiers[i] = null;
+	  m_ClassFilters[i] = null;
+	}
+      }
+
+      // construct a two-class header version of the dataset
+      m_TwoClassDataset = new Instances(insts, 0);
+      int classIndex = m_TwoClassDataset.classIndex();
+      m_TwoClassDataset.setClassIndex(-1);
+      m_TwoClassDataset.deleteAttributeAt(classIndex);
+      FastVector classLabels = new FastVector();
+      classLabels.addElement("class0");
+      classLabels.addElement("class1");
+      m_TwoClassDataset.insertAttributeAt(new Attribute("class", classLabels),
+					  classIndex);
+      m_TwoClassDataset.setClassIndex(classIndex);
+
+    } else { // use error correcting code style methods
+      Code code = null;
+      switch (m_Method) {
+      case METHOD_ERROR_EXHAUSTIVE:
+        code = new ExhaustiveCode(numClassifiers);
+        break;
+      case METHOD_ERROR_RANDOM:
+        code = new RandomCode(numClassifiers, 
+                              (int)(numClassifiers * m_RandomWidthFactor),
+			      insts);
+        break;
+      case METHOD_1_AGAINST_ALL:
+        code = new StandardCode(numClassifiers);
+        break;
+      default:
+        throw new Exception("Unrecognized correction code type");
+      }
+      numClassifiers = code.size();
+      m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, numClassifiers);
+      m_ClassFilters = new MakeIndicator[numClassifiers];
+      for (int i = 0; i < m_Classifiers.length; i++) {
+	m_ClassFilters[i] = new MakeIndicator();
+	MakeIndicator classFilter = (MakeIndicator) m_ClassFilters[i];
+	classFilter.setAttributeIndex("" + (insts.classIndex() + 1));
+	classFilter.setValueIndices(code.getIndices(i));
+	classFilter.setNumeric(false);
+	classFilter.setInputFormat(insts);
+	newInsts = Filter.useFilter(insts, m_ClassFilters[i]);
+	m_Classifiers[i].buildClassifier(newInsts);
+      }
+    }
+    m_ClassAttribute = insts.classAttribute();
+  }
+
+  /**
+   * Returns the individual predictions of the base classifiers
+   * for an instance. Used by StackedMultiClassClassifier.
+   * Returns the probability for the second "class" predicted
+   * by each base classifier.
+   *
+   * @param inst the instance to get the prediction for
+   * @return the individual predictions
+   * @throws Exception if the predictions can't be computed successfully
+   */
+  public double[] individualPredictions(Instance inst) throws Exception {
+    
+    double[] result = null;
+
+    if (m_Classifiers.length == 1) {
+      result = new double[1];
+      result[0] = m_Classifiers[0].distributionForInstance(inst)[1];
+    } else {
+      result = new double[m_ClassFilters.length];
+      for(int i = 0; i < m_ClassFilters.length; i++) {
+	if (m_Classifiers[i] != null) {
+	  if (m_Method == METHOD_1_AGAINST_1) {    
+	    Instance tempInst = (Instance)inst.copy(); 
+	    tempInst.setDataset(m_TwoClassDataset);
+	    result[i] = m_Classifiers[i].distributionForInstance(tempInst)[1];  
+	  } else {
+	    m_ClassFilters[i].input(inst);
+	    m_ClassFilters[i].batchFinished();
+	    result[i] = m_Classifiers[i].
+	      distributionForInstance(m_ClassFilters[i].output())[1];
+	  }
+	}
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the distribution for an instance.
+   *
+   * @param inst the instance to get the distribution for
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    
+    if (m_Classifiers.length == 1) {
+      return m_Classifiers[0].distributionForInstance(inst);
+    }
+    
+    double[] probs = new double[inst.numClasses()];
+
+    if (m_Method == METHOD_1_AGAINST_1) {
+      double[][] r = new double[inst.numClasses()][inst.numClasses()];
+      double[][] n = new double[inst.numClasses()][inst.numClasses()];
+
+      for(int i = 0; i < m_ClassFilters.length; i++) {
+	if (m_Classifiers[i] != null) {
+	  Instance tempInst = (Instance)inst.copy(); 
+	  tempInst.setDataset(m_TwoClassDataset);
+	  double [] current = m_Classifiers[i].distributionForInstance(tempInst);  
+	  Range range = new Range(((RemoveWithValues)m_ClassFilters[i])
+				  .getNominalIndices());
+	  range.setUpper(m_ClassAttribute.numValues());
+	  int[] pair = range.getSelection();
+          if (m_pairwiseCoupling && inst.numClasses() > 2) {
+            r[pair[0]][pair[1]] = current[0];
+            n[pair[0]][pair[1]] = m_SumOfWeights[i];
+          } else {
+            if (current[0] > current[1]) {
+              probs[pair[0]] += 1.0;
+            } else {
+              probs[pair[1]] += 1.0;
+            }
+          }
+        }
+      }
+      if (m_pairwiseCoupling && inst.numClasses() > 2) {
+        return pairwiseCoupling(n, r);
+      }
+    } else {
+      // error correcting style methods
+      for(int i = 0; i < m_ClassFilters.length; i++) {
+	m_ClassFilters[i].input(inst);
+	m_ClassFilters[i].batchFinished();
+	double [] current = m_Classifiers[i].
+	  distributionForInstance(m_ClassFilters[i].output());
+	for (int j = 0; j < m_ClassAttribute.numValues(); j++) {
+	  if (((MakeIndicator)m_ClassFilters[i]).getValueRange().isInRange(j)) {
+	    probs[j] += current[1];
+	  } else {
+	    probs[j] += current[0];
+	  }
+	}
+      }
+    }
+    
+    if (Utils.gr(Utils.sum(probs), 0)) {
+      Utils.normalize(probs);
+      return probs;
+    } else {
+      return m_ZeroR.distributionForInstance(inst);
+    }
+  }
+
+  /**
+   * Prints the classifiers.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifiers == null) {
+      return "MultiClassClassifier: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("MultiClassClassifier\n\n");
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      text.append("Classifier ").append(i + 1);
+      if (m_Classifiers[i] != null) {
+        if ((m_ClassFilters != null) && (m_ClassFilters[i] != null)) {
+	  if (m_ClassFilters[i] instanceof RemoveWithValues) {
+	    Range range = new Range(((RemoveWithValues)m_ClassFilters[i])
+				    .getNominalIndices());
+	    range.setUpper(m_ClassAttribute.numValues());
+	    int[] pair = range.getSelection();
+	    text.append(", " + (pair[0]+1) + " vs " + (pair[1]+1));
+	  } else if (m_ClassFilters[i] instanceof MakeIndicator) {
+	    text.append(", using indicator values: ");
+	    text.append(((MakeIndicator)m_ClassFilters[i]).getValueRange());
+	  }
+        }
+        text.append('\n');
+        text.append(m_Classifiers[i].toString() + "\n\n");
+      } else {
+        text.append(" Skipped (no training examples)\n");
+      }
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions()  {
+
+    Vector vec = new Vector(4);
+    
+    vec.addElement(new Option(
+       "\tSets the method to use. Valid values are 0 (1-against-all),\n"
+       +"\t1 (random codes), 2 (exhaustive code), and 3 (1-against-1). (default 0)\n",
+       "M", 1, "-M <num>"));
+    vec.addElement(new Option(
+       "\tSets the multiplier when using random codes. (default 2.0)",
+       "R", 1, "-R <num>"));
+    vec.addElement(new Option(
+        "\tUse pairwise coupling (only has an effect for 1-against1)",
+        "P", 0, "-P"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      vec.addElement(enu.nextElement());
+    }
+    return vec.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Sets the method to use. Valid values are 0 (1-against-all),
+   *  1 (random codes), 2 (exhaustive code), and 3 (1-against-1). (default 0)
+   * </pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  Sets the multiplier when using random codes. (default 2.0)</pre>
+   * 
+   * <pre> -P
+   *  Use pairwise coupling (only has an effect for 1-against1)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.functions.Logistic)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.functions.Logistic:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -R &lt;ridge&gt;
+   *  Set the ridge in the log-likelihood.</pre>
+   * 
+   * <pre> -M &lt;number&gt;
+   *  Set the maximum number of iterations (default -1, until convergence).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+  
+    String errorString = Utils.getOption('M', options);
+    if (errorString.length() != 0) {
+      setMethod(new SelectedTag(Integer.parseInt(errorString), 
+                                             TAGS_METHOD));
+    } else {
+      setMethod(new SelectedTag(METHOD_1_AGAINST_ALL, TAGS_METHOD));
+    }
+
+    String rfactorString = Utils.getOption('R', options);
+    if (rfactorString.length() != 0) {
+      setRandomWidthFactor((new Double(rfactorString)).doubleValue());
+    } else {
+      setRandomWidthFactor(2.0);
+    }
+
+    setUsePairwiseCoupling(Utils.getFlag('P', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 5];
+
+    int current = 0;
+
+
+    options[current++] = "-M";
+    options[current++] = "" + m_Method;
+
+    if (getUsePairwiseCoupling()) {
+      options[current++] = "-P";
+    }
+    
+    options[current++] = "-R";
+    options[current++] = "" + m_RandomWidthFactor;
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A metaclassifier for handling multi-class datasets with 2-class "
+      + "classifiers. This classifier is also capable of "
+      + "applying error correcting output codes for increased accuracy.";
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomWidthFactorTipText() {
+
+    return "Sets the width multiplier when using random codes. The number "
+      + "of codes generated will be thus number multiplied by the number of "
+      + "classes.";
+  }
+
+  /**
+   * Gets the multiplier when generating random codes. Will generate
+   * numClasses * m_RandomWidthFactor codes.
+   *
+   * @return the width multiplier
+   */
+  public double getRandomWidthFactor() {
+
+    return m_RandomWidthFactor;
+  }
+  
+  /**
+   * Sets the multiplier when generating random codes. Will generate
+   * numClasses * m_RandomWidthFactor codes.
+   *
+   * @param newRandomWidthFactor the new width multiplier
+   */
+  public void setRandomWidthFactor(double newRandomWidthFactor) {
+
+    m_RandomWidthFactor = newRandomWidthFactor;
+  }
+  
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String methodTipText() {
+    return "Sets the method to use for transforming the multi-class problem into "
+      + "several 2-class ones."; 
+  }
+
+  /**
+   * Gets the method used. Will be one of METHOD_1_AGAINST_ALL,
+   * METHOD_ERROR_RANDOM, METHOD_ERROR_EXHAUSTIVE, or METHOD_1_AGAINST_1.
+   *
+   * @return the current method.
+   */
+  public SelectedTag getMethod() {
+      
+    return new SelectedTag(m_Method, TAGS_METHOD);
+  }
+
+  /**
+   * Sets the method used. Will be one of METHOD_1_AGAINST_ALL,
+   * METHOD_ERROR_RANDOM, METHOD_ERROR_EXHAUSTIVE, or METHOD_1_AGAINST_1.
+   *
+   * @param newMethod the new method.
+   */
+  public void setMethod(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_METHOD) {
+      m_Method = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Set whether to use pairwise coupling with 1-vs-1 
+   * classification to improve probability estimates.
+   *
+   * @param p true if pairwise coupling is to be used
+   */
+  public void setUsePairwiseCoupling(boolean p) {
+    m_pairwiseCoupling = p;
+  }
+
+  /**
+   * Gets whether to use pairwise coupling with 1-vs-1 
+   * classification to improve probability estimates.
+   *
+   * @return true if pairwise coupling is to be used
+   */
+  public boolean getUsePairwiseCoupling() {
+    return m_pairwiseCoupling;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String usePairwiseCouplingTipText() {
+    return "Use pairwise coupling (only has an effect for 1-against-1).";
+  }
+
+  /**
+   * Implements pairwise coupling.
+   *
+   * @param n the sum of weights used to train each model
+   * @param r the probability estimate from each model
+   * @return the coupled estimates
+   */
+  public static double[] pairwiseCoupling(double[][] n, double[][] r) {
+
+    // Initialize p and u array
+    double[] p = new double[r.length];
+    for (int i =0; i < p.length; i++) {
+      p[i] = 1.0 / (double)p.length;
+    }
+    double[][] u = new double[r.length][r.length];
+    for (int i = 0; i < r.length; i++) {
+      for (int j = i + 1; j < r.length; j++) {
+	u[i][j] = 0.5;
+      }
+    }
+
+    // firstSum doesn't change
+    double[] firstSum = new double[p.length];
+    for (int i = 0; i < p.length; i++) {
+      for (int j = i + 1; j < p.length; j++) {
+	firstSum[i] += n[i][j] * r[i][j];
+	firstSum[j] += n[i][j] * (1 - r[i][j]);
+      }
+    }
+
+    // Iterate until convergence
+    boolean changed;
+    do {
+      changed = false;
+      double[] secondSum = new double[p.length];
+      for (int i = 0; i < p.length; i++) {
+	for (int j = i + 1; j < p.length; j++) {
+	  secondSum[i] += n[i][j] * u[i][j];
+	  secondSum[j] += n[i][j] * (1 - u[i][j]);
+	}
+      }
+      for (int i = 0; i < p.length; i++) {
+	if ((firstSum[i] == 0) || (secondSum[i] == 0)) {
+	  if (p[i] > 0) {
+	    changed = true;
+	  }
+	  p[i] = 0;
+	} else {
+	  double factor = firstSum[i] / secondSum[i];
+	  double pOld = p[i];
+	  p[i] *= factor;
+	  if (Math.abs(pOld - p[i]) > 1.0e-3) {
+	    changed = true;
+	  }
+	}
+      }
+      Utils.normalize(p);
+      for (int i = 0; i < r.length; i++) {
+	for (int j = i + 1; j < r.length; j++) {
+	  u[i][j] = p[i] / (p[i] + p[j]);
+	}
+      }
+    } while (changed);
+    return p;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MultiClassClassifier(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiScheme.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiScheme.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/MultiScheme.java	(revision 29)
@@ -0,0 +1,479 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultiScheme.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableMultipleClassifiersCombiner;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for selecting a classifier from among several using cross validation on the training data or the performance on the training data. Performance is measured based on percent correct (classification) or mean-squared error (regression).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Use cross validation for model selection using the
+ *  given number of folds. (default 0, is to
+ *  use training error)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -B &lt;classifier specification&gt;
+ *  Full class name of classifier to include, followed
+ *  by scheme options. May be specified multiple times.
+ *  (default: "weka.classifiers.rules.ZeroR")</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class MultiScheme 
+  extends RandomizableMultipleClassifiersCombiner {
+
+  /** for serialization */
+  static final long serialVersionUID = 5710744346128957520L;
+  
+  /** The classifier that had the best performance on training data. */
+  protected Classifier m_Classifier;
+ 
+  /** The index into the vector for the selected scheme */
+  protected int m_ClassifierIndex;
+
+  /**
+   * Number of folds to use for cross validation (0 means use training
+   * error for selection)
+   */
+  protected int m_NumXValFolds;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for selecting a classifier from among several using cross "
+      + "validation on the training data or the performance on the "
+      + "training data. Performance is measured based on percent correct "
+      + "(classification) or mean-squared error (regression).";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option(
+	      "\tUse cross validation for model selection using the\n"
+	      + "\tgiven number of folds. (default 0, is to\n"
+	      + "\tuse training error)",
+	      "X", 1, "-X <number of folds>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Use cross validation for model selection using the
+   *  given number of folds. (default 0, is to
+   *  use training error)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -B &lt;classifier specification&gt;
+   *  Full class name of classifier to include, followed
+   *  by scheme options. May be specified multiple times.
+   *  (default: "weka.classifiers.rules.ZeroR")</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String numFoldsString = Utils.getOption('X', options);
+    if (numFoldsString.length() != 0) {
+      setNumFolds(Integer.parseInt(numFoldsString));
+    } else {
+      setNumFolds(0);
+    }
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 2];
+
+    int current = 0;
+    options[current++] = "-X"; options[current++] = "" + getNumFolds();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifiersTipText() {
+    return "The classifiers to be chosen from.";
+  }
+
+  /**
+   * Sets the list of possible classifers to choose from.
+   *
+   * @param classifiers an array of classifiers with all options set.
+   */
+  public void setClassifiers(Classifier [] classifiers) {
+
+    m_Classifiers = classifiers;
+  }
+
+  /**
+   * Gets the list of possible classifers to choose from.
+   *
+   * @return the array of Classifiers
+   */
+  public Classifier [] getClassifiers() {
+
+    return m_Classifiers;
+  }
+  
+  /**
+   * Gets a single classifier from the set of available classifiers.
+   *
+   * @param index the index of the classifier wanted
+   * @return the Classifier
+   */
+  public Classifier getClassifier(int index) {
+
+    return m_Classifiers[index];
+  }
+  
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier
+   *
+   * @param index the index of the classifier string to retrieve, starting from
+   * 0.
+   * @return the classifier string, or the empty string if no classifier
+   * has been assigned (or the index given is out of range).
+   */
+  protected String getClassifierSpec(int index) {
+    
+    if (m_Classifiers.length < index) {
+      return "";
+    }
+    Classifier c = getClassifier(index);
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data " +
+      "for cross-validation.";
+  }
+
+  /**
+   * Sets the seed for random number generation.
+   *
+   * @param seed the random number seed
+   */
+  public void setSeed(int seed) {
+    
+    m_Seed = seed;;
+  }
+
+  /**
+   * Gets the random number seed.
+   * 
+   * @return the random number seed
+   */
+  public int getSeed() {
+
+    return m_Seed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds used for cross-validation (if 0, " +
+      "performance on training data will be used).";
+  }
+
+  /** 
+   * Gets the number of folds for cross-validation. A number less
+   * than 2 specifies using training error rather than cross-validation.
+   *
+   * @return the number of folds for cross-validation
+   */
+  public int getNumFolds() {
+
+    return m_NumXValFolds;
+  }
+
+  /**
+   * Sets the number of folds for cross-validation. A number less
+   * than 2 specifies using training error rather than cross-validation.
+   *
+   * @param numFolds the number of folds for cross-validation
+   */
+  public void setNumFolds(int numFolds) {
+    
+    m_NumXValFolds = numFolds;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Whether debug information is output to console.";
+  }
+
+  /**
+   * Set debugging mode
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    m_Debug = debug;
+  }
+
+  /**
+   * Get whether debugging is turned on
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+
+    return m_Debug;
+  }
+  
+  /**
+   * Get the index of the classifier that was determined as best during 
+   * cross-validation.
+   * 
+   * @return the index in the classifier array
+   */
+  public int getBestClassifierIndex() {
+    return m_ClassifierIndex;
+  }
+
+  /**
+   * Buildclassifier selects a classifier from the set of classifiers
+   * by minimising error on the training data.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    if (m_Classifiers.length == 0) {
+      throw new Exception("No base classifiers have been set!");
+    }
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+    newData.deleteWithMissingClass();
+    
+    Random random = new Random(m_Seed);
+    newData.randomize(random);
+    if (newData.classAttribute().isNominal() && (m_NumXValFolds > 1)) {
+      newData.stratify(m_NumXValFolds);
+    }
+    Instances train = newData;               // train on all data by default
+    Instances test = newData;               // test on training data by default
+    Classifier bestClassifier = null;
+    int bestIndex = -1;
+    double bestPerformance = Double.NaN;
+    int numClassifiers = m_Classifiers.length;
+    for (int i = 0; i < numClassifiers; i++) {
+      Classifier currentClassifier = getClassifier(i);
+      Evaluation evaluation;
+      if (m_NumXValFolds > 1) {
+	evaluation = new Evaluation(newData);
+	for (int j = 0; j < m_NumXValFolds; j++) {
+
+          // We want to randomize the data the same way for every 
+          // learning scheme.
+	  train = newData.trainCV(m_NumXValFolds, j, new Random (1));
+	  test = newData.testCV(m_NumXValFolds, j);
+	  currentClassifier.buildClassifier(train);
+	  evaluation.setPriors(train);
+	  evaluation.evaluateModel(currentClassifier, test);
+	}
+      } else {
+	currentClassifier.buildClassifier(train);
+	evaluation = new Evaluation(train);
+	evaluation.evaluateModel(currentClassifier, test);
+      }
+
+      double error = evaluation.errorRate();
+      if (m_Debug) {
+	System.err.println("Error rate: " + Utils.doubleToString(error, 6, 4)
+			   + " for classifier "
+			   + currentClassifier.getClass().getName());
+      }
+
+      if ((i == 0) || (error < bestPerformance)) {
+	bestClassifier = currentClassifier;
+	bestPerformance = error;
+	bestIndex = i;
+      }
+    }
+    m_ClassifierIndex = bestIndex;
+    if (m_NumXValFolds > 1) {
+      bestClassifier.buildClassifier(newData);
+    }
+    m_Classifier = bestClassifier;
+  }
+
+  /**
+   * Returns class probabilities.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution for the instance
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    return m_Classifier.distributionForInstance(instance);
+  }
+
+  /**
+   * Output a representation of this classifier
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifier == null) {
+      return "MultiScheme: No model built yet.";
+    }
+
+    String result = "MultiScheme selection using";
+    if (m_NumXValFolds > 1) {
+      result += " cross validation error";
+    } else {
+      result += " error on training data";
+    }
+    result += " from the following:\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += '\t' + getClassifierSpec(i) + '\n';
+    }
+
+    result += "Selected scheme: "
+      + getClassifierSpec(m_ClassifierIndex)
+      + "\n\n"
+      + m_Classifier.toString();
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new MultiScheme(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/OneClassClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/OneClassClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/OneClassClassifier.java	(revision 29)
@@ -0,0 +1,1436 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   OneClassClassifier.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.meta.generators.GaussianGenerator;
+import weka.classifiers.meta.generators.Generator;
+import weka.classifiers.meta.generators.InstanceHandler;
+import weka.classifiers.meta.generators.Mean;
+import weka.classifiers.meta.generators.NominalGenerator;
+import weka.classifiers.meta.generators.NominalAttributeGenerator;
+import weka.classifiers.meta.generators.NumericAttributeGenerator;
+import weka.classifiers.meta.generators.Ranged;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.AddValues;
+import weka.filters.unsupervised.attribute.MergeManyValues;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Performs one-class classification on a dataset.<br/>
+ * <br/>
+ * Classifier reduces the class being classified to just a single class, and learns the datawithout using any information from other classes.  The testing stage will classify as 'target'or 'outlier' - so in order to calculate the outlier pass rate the dataset must contain informationfrom more than one class.<br/>
+ * <br/>
+ * Also, the output varies depending on whether the label 'outlier' exists in the instances usedto build the classifier.  If so, then 'outlier' will be predicted, if not, then the label willbe considered missing when the prediction does not favour the target class.  The 'outlier' classwill not be used to build the model if there are instances of this class in the dataset.  It cansimply be used as a flag, you do not need to relabel any classes.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Kathryn Hempstalk, Eibe Frank, Ian H. Witten: One-Class Classification by Combining Density and Class Probability Estimation. In: Proceedings of the 12th European Conference on Principles and Practice of Knowledge Discovery in Databases and 19th European Conference on Machine Learning, ECMLPKDD2008, Berlin, 505--519, 2008.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;conference{Hempstalk2008,
+ *    address = {Berlin},
+ *    author = {Kathryn Hempstalk and Eibe Frank and Ian H. Witten},
+ *    booktitle = {Proceedings of the 12th European Conference on Principles and Practice of Knowledge Discovery in Databases and 19th European Conference on Machine Learning, ECMLPKDD2008},
+ *    month = {September},
+ *    pages = {505--519},
+ *    publisher = {Springer},
+ *    series = {Lecture Notes in Computer Science},
+ *    title = {One-Class Classification by Combining Density and Class Probability Estimation},
+ *    volume = {Vol. 5211},
+ *    year = {2008},
+ *    location = {Antwerp, Belgium}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -trr &lt;rate&gt;
+ *  Sets the target rejection rate
+ *  (default: 0.1)</pre>
+ * 
+ * <pre> -tcl &lt;label&gt;
+ *  Sets the target class label
+ *  (default: 'target')</pre>
+ * 
+ * <pre> -cvr &lt;rep&gt;
+ *  Sets the number of times to repeat cross validation
+ *  to find the threshold
+ *  (default: 10)</pre>
+ * 
+ * <pre> -P &lt;prop&gt;
+ *  Sets the proportion of generated data
+ *  (default: 0.5)</pre>
+ * 
+ * <pre> -cvf &lt;perc&gt;
+ *  Sets the percentage of heldout data for each cross validation
+ *  fold
+ *  (default: 10)</pre>
+ * 
+ * <pre> -num &lt;classname + options&gt;
+ *  Sets the numeric generator
+ *  (default: weka.classifiers.meta.generators.GaussianGenerator)</pre>
+ * 
+ * <pre> -nom &lt;classname + options&gt;
+ *  Sets the nominal generator
+ *  (default: weka.classifiers.meta.generators.NominalGenerator)</pre>
+ * 
+ * <pre> -L
+ *  Sets whether to correct the number of classes to two,
+ *  if omitted no correction will be made.</pre>
+ * 
+ * <pre> -E
+ *  Sets whether to exclusively use the density estimate.</pre>
+ * 
+ * <pre> -I
+ *  Sets whether to use instance weights.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.meta.Bagging)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.meta.Bagging:
+ * </pre>
+ * 
+ * <pre> -P
+ *  Size of each bag, as a percentage of the
+ *  training set size. (default 100)</pre>
+ * 
+ * <pre> -O
+ *  Calculate the out of bag error.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.REPTree)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.REPTree:
+ * </pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf (default 2).</pre>
+ * 
+ * <pre> -V &lt;minimum variance for split&gt;
+ *  Set minimum numeric class variance proportion
+ *  of train variance for split (default 1e-3).</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Number of folds for reduced error pruning (default 3).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ * <pre> -P
+ *  No pruning.</pre>
+ * 
+ * <pre> -L
+ *  Maximum tree depth (default -1, no maximum)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe at cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class OneClassClassifier
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 6199125385010158931L;
+
+  /**
+   * The rejection rate of valid target objects (used to set the threshold).
+   */
+  protected double m_TargetRejectionRate = 0.1;
+
+  /**
+   * The probability threshold (only classes above this will be considered target).
+   */
+  protected double m_Threshold = 0.5;
+
+  /**
+   * The generators for the numeric attributes.
+   */
+  protected ArrayList m_Generators;
+
+  /**
+   * The value of the class attribute to consider the target class.
+   */
+  protected String m_TargetClassLabel = "target";
+
+  /**
+   * The number of times to repeat cross validation during learning.
+   */
+  protected int m_NumRepeats = 10;  
+
+  /**
+   * The percentage of heldout data.
+   */
+  protected double m_PercentHeldout = 10;
+
+  /**
+   * The proportion of the data that will be generated.
+   */
+  protected double m_ProportionGenerated = 0.5;
+
+  /**
+   * The default data generator for numeric attributes.
+   */
+    protected NumericAttributeGenerator m_DefaultNumericGenerator = (NumericAttributeGenerator) new GaussianGenerator();
+
+  /**
+   * The default data generator for nominal attributes.
+   */
+    protected NominalAttributeGenerator m_DefaultNominalGenerator = (NominalAttributeGenerator) new NominalGenerator();
+
+  /**
+   * Adds the outlier class if it doesn't already exist.
+   */
+  protected AddValues m_AddOutlierFilter;
+
+  /**
+   * Whether to include laplace correction so if there are multiple
+   * values for a class, it is reduced to just two so that any laplace
+   * correction in another classifier corrects with one possible other class
+   * rather than several.  
+   */
+  protected boolean m_UseLaplaceCorrection = false;
+
+  /**
+   * The filter that merges the instances down to two values.
+   */
+  protected MergeManyValues m_MergeFilter;
+
+  /**
+   * The label for the outlier class.
+   */
+  public static final String OUTLIER_LABEL = "outlier";
+
+
+  /**
+   * Whether to use only the density estimate, or to include the
+   * base classifier in the probability estimates.
+   */
+  protected boolean m_UseDensityOnly = false;
+
+  /**
+   * Whether to weight instances based on their prevalence in the
+   * test set used for calculating P(X|T).
+   */
+  protected boolean m_UseInstanceWeights = false;
+  
+  /** The random number generator used internally. */
+  protected Random m_Random;
+
+  
+  /**
+   * Default constructor.
+   */
+  public OneClassClassifier() {
+    super();
+    
+    m_Classifier = new weka.classifiers.meta.Bagging();
+  }
+
+  /**
+   * Returns a string describing this classes ability.
+   *
+   * @return A description of the method.
+   */
+  public String globalInfo() {
+    return 
+       "Performs one-class classification on a dataset.\n\n"
+     + "Classifier reduces the class being classified to just a single class, and learns the data"
+     + "without using any information from other classes.  The testing stage will classify as 'target'"
+     + "or 'outlier' - so in order to calculate the outlier pass rate the dataset must contain information"
+     + "from more than one class.\n"
+     + "\n"
+     + "Also, the output varies depending on whether the label 'outlier' exists in the instances used"
+     + "to build the classifier.  If so, then 'outlier' will be predicted, if not, then the label will"
+     + "be considered missing when the prediction does not favour the target class.  The 'outlier' class"
+     + "will not be used to build the model if there are instances of this class in the dataset.  It can"
+     + "simply be used as a flag, you do not need to relabel any classes.\n"
+     + "\n"
+     + "For more information, see:\n"
+     + "\n"
+     + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.CONFERENCE);
+    result.setValue(Field.AUTHOR, "Kathryn Hempstalk and Eibe Frank and Ian H. Witten");
+    result.setValue(Field.YEAR, "2008");
+    result.setValue(Field.TITLE, "One-Class Classification by Combining Density and Class Probability Estimation");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the 12th European Conference on Principles and Practice of Knowledge Discovery in Databases and 19th European Conference on Machine Learning, ECMLPKDD2008");
+    result.setValue(Field.VOLUME, "Vol. 5211");
+    result.setValue(Field.PAGES, "505--519");
+    result.setValue(Field.PUBLISHER, "Springer");
+    result.setValue(Field.ADDRESS, "Berlin");
+    result.setValue(Field.SERIES, "Lecture Notes in Computer Science");
+    result.setValue(Field.LOCATION, "Antwerp, Belgium");
+    result.setValue(Field.MONTH, "September");
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return An enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();   
+
+    result.addElement(new Option(
+	"\tSets the target rejection rate\n"
+	+ "\t(default: 0.1)",
+	"trr", 1, "-trr <rate>"));
+
+    result.addElement(new Option(
+	"\tSets the target class label\n"
+	+ "\t(default: 'target')",
+	"tcl", 1, "-tcl <label>"));
+
+    result.addElement(new Option(
+	"\tSets the number of times to repeat cross validation\n" 
+	+ "\tto find the threshold\n"
+	+ "\t(default: 10)",
+	"cvr", 1, "-cvr <rep>"));
+
+    result.addElement(new Option(
+	"\tSets the proportion of generated data\n"
+	+ "\t(default: 0.5)",
+	"P", 1, "-P <prop>"));
+
+    result.addElement(new Option(
+	"\tSets the percentage of heldout data for each cross validation\n"
+	+ "\tfold\n"
+	+ "\t(default: 10)",
+	"cvf", 1, "-cvf <perc>"));
+
+    result.addElement(new Option(
+	"\tSets the numeric generator\n"
+	+ "\t(default: " + GaussianGenerator.class.getName() + ")",
+	"num", 1, "-num <classname + options>"));
+
+    result.addElement(new Option(
+	"\tSets the nominal generator\n"
+	+ "\t(default: " + NominalGenerator.class.getName() + ")",
+	"nom", 1, "-nom <classname + options>"));
+
+    result.addElement(new Option(
+	"\tSets whether to correct the number of classes to two,\n"
+	+ "\tif omitted no correction will be made.",
+	"L", 1, "-L"));
+
+    result.addElement(new Option(
+	"\tSets whether to exclusively use the density estimate.",
+	"E", 0, "-E"));
+
+    result.addElement(new Option(
+	"\tSets whether to use instance weights.",
+	"I", 0, "-I"));
+    
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements())
+      result.addElement(enu.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -trr &lt;rate&gt;
+   *  Sets the target rejection rate
+   *  (default: 0.1)</pre>
+   * 
+   * <pre> -tcl &lt;label&gt;
+   *  Sets the target class label
+   *  (default: 'target')</pre>
+   * 
+   * <pre> -cvr &lt;rep&gt;
+   *  Sets the number of times to repeat cross validation
+   *  to find the threshold
+   *  (default: 10)</pre>
+   * 
+   * <pre> -P &lt;prop&gt;
+   *  Sets the proportion of generated data
+   *  (default: 0.5)</pre>
+   * 
+   * <pre> -cvf &lt;perc&gt;
+   *  Sets the percentage of heldout data for each cross validation
+   *  fold
+   *  (default: 10)</pre>
+   * 
+   * <pre> -num &lt;classname + options&gt;
+   *  Sets the numeric generator
+   *  (default: weka.classifiers.meta.generators.GaussianGenerator)</pre>
+   * 
+   * <pre> -nom &lt;classname + options&gt;
+   *  Sets the nominal generator
+   *  (default: weka.classifiers.meta.generators.NominalGenerator)</pre>
+   * 
+   * <pre> -L
+   *  Sets whether to correct the number of classes to two,
+   *  if omitted no correction will be made.</pre>
+   * 
+   * <pre> -E
+   *  Sets whether to exclusively use the density estimate.</pre>
+   * 
+   * <pre> -I
+   *  Sets whether to use instance weights.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.meta.Bagging)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.meta.Bagging:
+   * </pre>
+   * 
+   * <pre> -P
+   *  Size of each bag, as a percentage of the
+   *  training set size. (default 100)</pre>
+   * 
+   * <pre> -O
+   *  Calculate the out of bag error.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.REPTree)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.REPTree:
+   * </pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf (default 2).</pre>
+   * 
+   * <pre> -V &lt;minimum variance for split&gt;
+   *  Set minimum numeric class variance proportion
+   *  of train variance for split (default 1e-3).</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Number of folds for reduced error pruning (default 3).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   * <pre> -P
+   *  No pruning.</pre>
+   * 
+   * <pre> -L
+   *  Maximum tree depth (default -1, no maximum)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options The list of options as an array of strings.
+   * @throws Exception If an option is not supported.
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+
+    // numeric generator
+    tmpStr = Utils.getOption("num", options);
+    if (tmpStr.length() != 0) { 
+      tmpOptions    = Utils.splitOptions(tmpStr);
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setNumericGenerator((NumericAttributeGenerator) Utils.forName(Generator.class, tmpStr, tmpOptions));
+    }
+    else {
+      setNumericGenerator((NumericAttributeGenerator) Utils.forName(Generator.class, defaultNumericGeneratorString(), null));
+    }
+
+    // nominal generator
+    tmpStr = Utils.getOption("nom", options);
+    if (tmpStr.length() != 0) { 
+      tmpOptions    = Utils.splitOptions(tmpStr);
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setNominalGenerator((NominalAttributeGenerator) Utils.forName(Generator.class, tmpStr, tmpOptions));
+    }
+    else {
+      setNominalGenerator((NominalAttributeGenerator) Utils.forName(Generator.class, defaultNominalGeneratorString(), null));
+    }
+
+    //target rejection rate
+    tmpStr = Utils.getOption("trr", options);
+    if (tmpStr.length() != 0)
+      setTargetRejectionRate(Double.parseDouble(tmpStr));
+    else
+      setTargetRejectionRate(0.1);
+
+    //target class label
+    tmpStr = Utils.getOption("tcl", options);
+    if (tmpStr.length() != 0)
+      setTargetClassLabel(tmpStr);
+    else
+      setTargetClassLabel("target");
+
+    //cross validation repeats
+    tmpStr = Utils.getOption("cvr", options);
+    if (tmpStr.length() != 0)
+      setNumRepeats(Integer.parseInt(tmpStr));
+    else
+      setNumRepeats(10);
+
+    //cross validation fold size
+    tmpStr = Utils.getOption("cvf", options);
+    if (tmpStr.length() != 0)
+      setPercentageHeldout(Double.parseDouble(tmpStr));
+    else
+      setPercentageHeldout(10.0);
+
+    //proportion generated
+    tmpStr = Utils.getOption("P", options);
+    if (tmpStr.length() != 0)
+      setProportionGenerated(Double.parseDouble(tmpStr));
+    else
+      setProportionGenerated(0.5);
+
+    //use laplace
+    setUseLaplaceCorrection(Utils.getFlag('L',options));
+
+    //set whether to exclusively use the density estimate
+    setDensityOnly(Utils.getFlag('E', options));
+
+    //use instance weights
+    setUseInstanceWeights(Utils.getFlag('I', options));
+    
+    // set the parent's options first
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return An array of strings suitable for passing to setOptions.
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+    
+    result.add("-num");
+    result.add(
+	  m_DefaultNumericGenerator.getClass().getName() 
+	+ " " 
+	  + Utils.joinOptions(((Generator)m_DefaultNumericGenerator).getOptions()));
+
+    result.add("-nom");
+    result.add(
+	  m_DefaultNominalGenerator.getClass().getName() 
+	+ " " 
+	  + Utils.joinOptions(((Generator)m_DefaultNominalGenerator).getOptions()));
+    
+    result.add("-trr");
+    result.add("" + m_TargetRejectionRate);
+    
+    result.add("-tcl");
+    result.add("" + m_TargetClassLabel);
+    
+    result.add("-cvr");
+    result.add("" + m_NumRepeats);
+    
+    result.add("-cvf");
+    result.add("" + m_PercentHeldout);
+    
+    result.add("-P");
+    result.add("" + m_ProportionGenerated);
+    
+    if (m_UseLaplaceCorrection)
+      result.add("-L");    
+  
+    if (m_UseDensityOnly)
+      result.add("-E");
+    
+    if (m_UseInstanceWeights)
+      result.add("-I");
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Gets whether only the density estimate should be used by the classifier.  If false,
+   * the base classifier's estimate will be incorporated using bayes rule for two classes.
+   *
+   * @return Whether to use only the density estimate.
+   */
+  public boolean getDensityOnly() {
+    return m_UseDensityOnly;
+  }
+
+  /**
+   * Sets whether the density estimate will be used by itself.
+   *
+   * @param density Whether to use the density estimate exclusively or not.
+   */
+  public void setDensityOnly(boolean density) {
+    m_UseDensityOnly = density;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String densityOnlyTipText() {
+    return "If true, the density estimate will be used by itself.";
+  }
+
+  /**
+   * Gets the target rejection rate - the proportion of target class samples
+   * that will be rejected in order to build a threshold.
+   *
+   * @return The target rejection rate.
+   */
+  public double getTargetRejectionRate() {
+    return m_TargetRejectionRate;
+  }
+
+  /**
+   * Sets the target rejection rate.
+   *
+   * @param rate The new target rejection rate.
+   */
+  public void setTargetRejectionRate(double rate) {
+    m_TargetRejectionRate = rate;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String targetRejectionRateTipText() {
+    return 
+        "The target rejection rate, ie, the proportion of target class "
+      + "samples that will be rejected in order to build a threshold.";
+  }
+
+  /**
+   * Gets the target class label - the class label to perform one
+   * class classification on.
+   *
+   * @return The target class label.
+   */
+  public String getTargetClassLabel() {
+    return m_TargetClassLabel;
+  }
+
+  /**
+   * Sets the target class label to a new value.
+   *
+   * @param label The target class label to classify for.
+   */
+  public void setTargetClassLabel(String label) {
+    m_TargetClassLabel = label;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String targetClassLabelTipText() {
+    return "The class label to perform one-class classification on.";
+  }
+
+  /**
+   * Gets the number of repeats for (internal) cross validation.
+   *
+   * @return The number of repeats for internal cross validation.
+   */
+  public int getNumRepeats() {
+    return m_NumRepeats;
+  }
+
+  /**
+   * Sets the number of repeats for (internal) cross validation to a new value.
+   *
+   * @param repeats The new number of repeats for cross validation.
+   */
+  public void setNumRepeats(int repeats) {
+    m_NumRepeats = repeats;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numRepeatsTipText() {
+    return "The number of repeats for (internal) cross-validation.";
+  }
+
+  /**
+   * Sets the proportion of generated data to a new value.
+   *
+   * @param prop The new proportion.
+   */
+  public void setProportionGenerated(double prop) {
+    m_ProportionGenerated = prop;
+  }
+
+  /**
+   * Gets the proportion of data that will be generated compared to the 
+   * target class label.
+   *
+   * @return The proportion of generated data.
+   */
+  public double getProportionGenerated() {
+    return m_ProportionGenerated;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String proportionGeneratedTipText() {
+    return 
+        "The proportion of data that will be generated compared to the "
+      + "target class label.";
+  }
+
+  /**
+   * Sets the percentage heldout in each CV fold.
+   *
+   * @param percent The new percent of heldout data.
+   */
+  public void setPercentageHeldout(double percent) {
+    m_PercentHeldout = percent;
+  }
+
+  /**
+   * Gets the percentage of data that will be heldout in each
+   * iteration of cross validation.
+   *
+   * @return The percentage of heldout data.
+   */
+  public double getPercentageHeldout() {
+    return m_PercentHeldout;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String percentageHeldoutTipText() {
+    return 
+        "The percentage of data that will be heldout in each iteration "
+      + "of (internal) cross-validation.";
+  }
+
+  /**
+   * Gets thegenerator that will be used by default to generate 
+   * numeric outlier data.
+   *
+   * @return The numeric data generator.
+   */
+  public NumericAttributeGenerator getNumericGenerator() {
+    return m_DefaultNumericGenerator;
+  }
+
+  /**
+   * Sets the generator that will be used by default to generate
+   * numeric outlier data.
+   *
+   * @param agen The new numeric data generator to use.
+   */
+  public void setNumericGenerator(NumericAttributeGenerator agen) {
+    m_DefaultNumericGenerator = agen;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numericGeneratorTipText() {
+    return "The numeric data generator to use.";
+  }
+
+  /**
+   * Gets the generator that will be used by default to generate 
+   * nominal outlier data.
+   *
+   * @return The nominal data generator.
+   */
+  public NominalAttributeGenerator getNominalGenerator() {
+    return m_DefaultNominalGenerator;
+  }
+
+  /**
+   * Sets the generator that will be used by default to generate
+   * nominal outlier data.
+   *
+   * @param agen The new nominal data generator to use.
+   */
+  public void setNominalGenerator(NominalAttributeGenerator agen) {
+    m_DefaultNominalGenerator = agen;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String nominalGeneratorTipText() {
+    return "The nominal data generator to use.";
+  }
+
+  /**
+   * Gets whether a laplace correction should be used.
+   *
+   * @return Whether a laplace correction should be used.
+   */
+  public boolean getUseLaplaceCorrection() {
+    return m_UseLaplaceCorrection;
+  }
+
+  /**
+   * Sets whether a laplace correction should be used.  A laplace
+   * correction will reduce the number of class labels to two, the 
+   * target and outlier classes, regardless of how many labels 
+   * actually exist.  This is useful for classifiers that use
+   * the number of class labels to make use a laplace value
+   * based on the unseen class.
+   *
+   * @param newuse Whether to use the laplace correction (default: true).
+   */
+  public void setUseLaplaceCorrection(boolean newuse) {
+    m_UseLaplaceCorrection = newuse;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useLaplaceCorrectionTipText() {
+    return 
+        "If true, then Laplace correction will be used (reduces the "
+      + "number of class labels to two, target and outlier class, regardless "
+      + "of how many class labels actually exist) - useful for classifiers "
+      + "that use the number of class labels to make use of a Laplace value "
+      + "based on the unseen class.";
+  }
+
+
+  /**
+   * Sets whether to perform weighting on instances based on their
+   * prevalence in the data.
+   *
+   * @param newuse Whether or not to use instance weighting.
+   */
+  public void setUseInstanceWeights(boolean newuse) {
+    m_UseInstanceWeights = newuse;
+  }
+
+  /**
+   * Gets whether instance weighting will be performed.
+   *
+   * @return Whether instance weighting will be performed.
+   */
+  public boolean getUseInstanceWeights() {
+    return m_UseInstanceWeights;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useInstanceWeightsTipText() {
+    return 
+        "If true, the weighting on instances is based on their prevalence "
+      + "in the data.";
+  }
+
+  /**
+   * String describing default internal classifier.
+   * 
+   * @return The default classifier classname.
+   */
+  protected String defaultClassifierString() {    
+    return "weka.classifiers.meta.Bagging";
+  }
+
+  /**
+   * String describing default generator / density estimator.
+   * 
+   * @return The default numeric generator classname.
+   */
+  protected String defaultNumericGeneratorString() {    
+    return "weka.classifiers.meta.generators.GaussianGenerator";
+  }
+
+  /**
+   * String describing default generator / density estimator.
+   * 
+   * @return The default nominal generator classname.
+   */
+  protected String defaultNominalGeneratorString() {    
+    return "weka.classifiers.meta.generators.NominalGenerator";
+  }
+
+  /**
+   * Returns default capabilities of the base classifier.
+   *
+   * @return      the capabilities of the base classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    result = super.getCapabilities();
+    
+    // only nominal classes can be processed!
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Build the one-class classifier, any non-target data values
+   * are ignored.  The target class label must exist in the arff
+   * file or else an exception will be thrown.
+   *
+   * @param data The training data.
+   * @throws Exception If the classifier could not be built successfully.
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    if (m_Classifier == null) {
+      throw new Exception("No base classifier has been set!");
+    }
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+
+    m_Random = new Random(m_Seed);
+    
+    //delete the data that's not of the class we are trying to classify.
+    Attribute classAttribute = newData.classAttribute();
+    double targetClassValue = classAttribute.indexOfValue(m_TargetClassLabel);	     
+    if (targetClassValue == -1) {
+      throw new Exception("Target class value doesn't exist!");
+    }
+
+    int index = 0;
+    while(index < newData.numInstances()) {
+      Instance aninst = newData.instance(index);
+      if (aninst.classValue() != targetClassValue) {
+	newData.delete(index);
+      } else
+	index++;
+    }
+
+    if (newData.numInstances() == 0) {
+      throw new Exception("No instances found belonging to the target class!");
+    }
+
+    //now we need to add the "outlier" attribute if it doesn't already exist.
+    m_AddOutlierFilter = new AddValues();
+    m_AddOutlierFilter.setAttributeIndex("" + (newData.classIndex() + 1));
+    m_AddOutlierFilter.setLabels(OneClassClassifier.OUTLIER_LABEL);
+    m_AddOutlierFilter.setInputFormat(newData);
+    newData = Filter.useFilter(newData, m_AddOutlierFilter);
+
+    if (m_UseLaplaceCorrection) {
+      newData = this.mergeToTwo(newData);   
+    }	
+
+    //make sure the generators are created.
+    m_Generators = new ArrayList();
+
+    //one for each column
+    //need to work out the range or mean/stddev for each attribute
+    int numAttributes = newData.numAttributes();
+
+    //work out the ranges
+    double lowranges[] = new double[numAttributes - 1];
+    double highranges[] = new double[numAttributes - 1];
+    double means[] = new double[numAttributes - 1];
+    double stddevs[] = new double[numAttributes - 1];
+    double instanceCount[] = new double[numAttributes - 1];
+    int attrIndexes[] = new int[numAttributes - 1];
+
+    //initialise
+    for (int i = 0; i < numAttributes - 1; i++) {
+      lowranges[i] = Double.MAX_VALUE;
+      highranges[i] = -1 * Double.MAX_VALUE;
+      means[i] = 0;
+      stddevs[i] = 0;
+      attrIndexes[i] = 0;
+      instanceCount[i] = 0;
+    }
+
+    //calculate low/high ranges and means.
+    //missing attributes are ignored
+    for (int i = 0; i < newData.numInstances(); i++) {
+      Instance anInst = newData.instance(i);
+      int attCount = 0;
+      for (int j = 0; j < numAttributes; j++) {
+	if (j != newData.classIndex()) {
+	  double attVal = anInst.value(j);
+	  if (!anInst.isMissing(j)) {
+	    if (attVal > highranges[attCount])
+	      highranges[attCount] = attVal;
+	    if (attVal < lowranges[attCount])
+	      lowranges[attCount] = attVal;
+
+	    means[attCount] += attVal;
+	    instanceCount[attCount] += 1;			
+	  }
+	  attrIndexes[attCount] = j;
+	  attCount++;
+
+	}
+      }
+    }
+
+    //calculate means...
+    for (int i = 0; i < numAttributes - 1; i++) {
+      if (instanceCount[i] > 0) {
+	means[i] = means[i] / instanceCount[i];
+      }
+    }
+
+    //and now standard deviations
+    for (int i = 0; i < newData.numInstances(); i++) {
+      Instance anInst = newData.instance(i);
+      int attCount = 0;
+      for (int j = 0; j < numAttributes - 1; j++) {
+	if (instanceCount[j] > 0) {
+	  stddevs[attCount] += Math.pow(anInst.value(j) - means[attCount], 2);
+	  attCount++;
+	}
+      }
+    }
+
+    for (int i = 0; i < numAttributes - 1; i++) {
+      if (instanceCount[i] > 0) {
+	stddevs[i] = Math.sqrt(stddevs[i] / instanceCount[i]);		
+      }
+    }
+
+
+    //ok, now we have everything, need to make a generator for each column
+    for (int i = 0; i < numAttributes - 1; i++) {
+      Generator agen;
+      if (newData.attribute(attrIndexes[i]).isNominal()) {
+	agen = ((Generator)m_DefaultNominalGenerator).copy();
+	((NominalAttributeGenerator)agen).buildGenerator(newData, newData.attribute(attrIndexes[i]));
+      } else {
+	agen = ((Generator)m_DefaultNumericGenerator).copy();
+	
+	if (agen instanceof Ranged) {
+	  ((Ranged)agen).setLowerRange(lowranges[i]);
+	  ((Ranged)agen).setUpperRange(highranges[i]);
+	}
+	
+	if (agen instanceof Mean) {
+	  ((Mean)agen).setMean(means[i]);
+	  ((Mean)agen).setStandardDeviation(stddevs[i]);
+	}
+
+	if (agen instanceof InstanceHandler) {
+	  //generator needs to be setup with the instances, 
+	  //need to pass over a set of instances with just the current 
+	  //attribute.
+	  StringBuffer sb = new StringBuffer("@relation OneClass-SingleAttribute\n\n");
+	  sb.append("@attribute tempName numeric\n\n");
+	  sb.append("@data\n\n");
+	  Enumeration instancesEnum = newData.enumerateInstances();
+	  while(instancesEnum.hasMoreElements()) {
+	    Instance aninst = (Instance)instancesEnum.nextElement();
+	    if (!aninst.isMissing(attrIndexes[i]))
+	      sb.append("" + aninst.value(attrIndexes[i]) + "\n");
+	  }
+	  sb.append("\n\n");
+	  Instances removed = new Instances(new StringReader(sb.toString()));
+	  removed.deleteWithMissing(0);
+	  ((InstanceHandler)agen).buildGenerator(removed);
+	}
+      }
+
+      m_Generators.add(agen);
+    }
+
+
+    //REPEAT
+    ArrayList thresholds = new ArrayList();
+    for (int i = 0; i < m_NumRepeats; i++) {
+
+      //hold some data out 
+      Instances copyData = new Instances(newData);
+      Instances heldout = new Instances(newData, 0);
+      for (int k = 0; k < newData.numInstances() / m_PercentHeldout; k++) {
+	int anindex = m_Random.nextInt(copyData.numInstances());
+	heldout.add(copyData.instance(anindex));
+	copyData.delete(anindex);
+      }
+
+
+      //generate some data
+      this.generateData(copyData);
+
+      //build the classifier on the generated data   
+      if (!m_UseDensityOnly)
+	m_Classifier.buildClassifier(copyData);
+
+      //test the generated data, work out the threshold (average it later)
+      double[] scores = new double[heldout.numInstances()];
+      Enumeration iterInst = heldout.enumerateInstances();
+      int classIndex = heldout.classAttribute().indexOfValue(m_TargetClassLabel);
+      int count = 0; 
+      while(iterInst.hasMoreElements()) {
+	Instance anInst = (Instance)iterInst.nextElement();
+	scores[count] = this.getProbXGivenC(anInst, classIndex);	
+	count++;
+      }
+
+      Arrays.sort(scores);
+      //work out the where the threshold should be
+      //higher probabilities = passes
+      //sorted into ascending order (getting bigger)
+      int passposition = (int)((double)heldout.numInstances() * m_TargetRejectionRate);
+      if (passposition >=  heldout.numInstances())
+	passposition = heldout.numInstances() - 1;
+
+      thresholds.add(new Double(scores[passposition]));
+    }
+    //END REPEAT
+
+
+
+    //build the classifier on the generated data  
+
+    //set the threshold
+    m_Threshold = 0;
+    for (int k = 0; k < thresholds.size(); k++) {
+      m_Threshold += ((Double)thresholds.get(k)).doubleValue();
+    }
+    m_Threshold /= (double)thresholds.size();
+
+    //rebuild the classifier using all the data
+    this.generateData(newData);
+
+    if (!m_UseDensityOnly)
+      m_Classifier.buildClassifier(newData);
+
+  }
+  
+
+  /**
+   * Merges the class values of the instances down to two values,
+   * the target class and the "outlier" class.
+   *
+   * @param newData The data to merge.
+   * @return The merged data.
+   */
+  protected Instances mergeToTwo(Instances newData) throws Exception{
+
+    m_MergeFilter = new MergeManyValues();
+    m_MergeFilter.setAttributeIndex("" + (newData.classIndex() + 1));
+
+    //figure out the indexes that aren't the outlier label or
+    //the target label
+    StringBuffer sb = new StringBuffer("");
+
+    Attribute theAttr = newData.classAttribute();
+    for (int i = 0; i < theAttr.numValues(); i++) {
+      if (! (theAttr.value(i).equalsIgnoreCase(OneClassClassifier.OUTLIER_LABEL) 
+	  || theAttr.value(i).equalsIgnoreCase(m_TargetClassLabel))) {
+	//add it to the merge list
+	sb.append((i + 1) + ",");
+      }
+    }
+    String mergeList = sb.toString();
+    if (mergeList.length() != 0) {
+      mergeList = mergeList.substring(0, mergeList.length() - 1);
+      int classIndex = newData.classIndex();
+      newData.setClassIndex(-1);
+      m_MergeFilter.setMergeValueRange(mergeList);
+      m_MergeFilter.setLabel(OneClassClassifier.OUTLIER_LABEL);
+      m_MergeFilter.setInputFormat(newData);
+      newData = Filter.useFilter(newData, m_MergeFilter);
+      newData.setClassIndex(classIndex);
+    } else {
+      m_MergeFilter = null;
+    }
+
+    return newData;
+  }
+
+  /**
+   * Gets the probability that an instance, X, belongs to the target class, C.  
+   *
+   * @param instance The instance X.
+   * @param targetClassIndex The index of the target class label for the class attribute.
+   * @return The probability of X given C, P(X|C).
+   */
+  protected double getProbXGivenC(Instance instance, int targetClassIndex) throws Exception{
+    double probC = 1 - m_ProportionGenerated;	
+    double probXgivenA = 0;
+    int count = 0;
+    for (int i = 0; i < instance.numAttributes(); i++) {
+      if (i != instance.classIndex()) {
+	Generator agen = (Generator)m_Generators.get(count);
+	if (!instance.isMissing(i)) {
+	  probXgivenA += agen.getLogProbabilityOf(instance.value(i));
+	}
+	count++;	
+      }
+    }
+
+    if (m_UseDensityOnly)
+      return probXgivenA;
+
+    double[] distribution = m_Classifier.distributionForInstance(instance);
+    double probCgivenX = distribution[targetClassIndex];
+    if(probCgivenX == 1)
+	return Double.POSITIVE_INFINITY;
+
+    //final calculation
+    double top = Math.log(1 - probC) + Math.log(probCgivenX);
+    double bottom = Math.log(probC) + Math.log(1 - probCgivenX);   
+
+    return (top - bottom) + probXgivenA;	
+  }
+
+
+  /**
+   * Generates some outlier data and returns the targetData with some outlier data included.
+   *
+   * @param targetData The data for the target class.
+   * @return The outlier and target data together.
+   */
+  protected Instances generateData(Instances targetData) {
+    double totalInstances = ((double)targetData.numInstances()) / (1 - m_ProportionGenerated);
+
+    int numInstances = (int)(totalInstances - (double)targetData.numInstances());
+
+    //first reweight the target data
+    if (m_UseInstanceWeights) {
+      for (int i = 0; i < targetData.numInstances(); i++) {
+	targetData.instance(i).setWeight(0.5 * (1 / (1 - m_ProportionGenerated)));
+      }
+    }
+
+    for (int j = 0; j < numInstances; j++) {
+      //add to the targetData the instances that we generate...
+      Instance anInst = new DenseInstance(targetData.numAttributes());
+      anInst.setDataset(targetData);
+      int position = 0;
+      for (int i = 0; i < targetData.numAttributes(); i++) {
+	if (targetData.classIndex() != i) {
+	  //not the class attribute
+	  Generator agen = (Generator)m_Generators.get(position);
+	  anInst.setValue(i, agen.generate());
+	  position++;
+	} else {
+	  //is the class attribute
+	  anInst.setValue(i, OneClassClassifier.OUTLIER_LABEL);
+	  if (m_UseInstanceWeights)
+	    anInst.setWeight(0.5 * (1 / m_ProportionGenerated));
+	}
+
+      }
+      targetData.add(anInst);	    
+    }
+
+    return targetData;
+  }
+
+  /**
+   * Returns a probability distribution for a given instance.
+   * 
+   * @param instance The instance to calculate the probability distribution for.
+   * @return The probability for each class.
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    Instance filtered = (Instance)instance.copy();
+
+    m_AddOutlierFilter.input(instance);
+    filtered = m_AddOutlierFilter.output();
+
+    if (m_UseLaplaceCorrection && m_MergeFilter != null) {
+      m_MergeFilter.input(filtered);
+      filtered = m_MergeFilter.output();
+    }
+
+    double[] dist = new double[instance.numClasses()];
+    double probForOutlierClass = 1 / (1 + Math.exp(this.getProbXGivenC(filtered, filtered.classAttribute().indexOfValue(m_TargetClassLabel)) - m_Threshold));
+    if(this.getProbXGivenC(filtered, filtered.classAttribute().indexOfValue(m_TargetClassLabel)) == Double.POSITIVE_INFINITY)
+	probForOutlierClass = 0;
+
+    dist[instance.classAttribute().indexOfValue(m_TargetClassLabel)] = 1 - probForOutlierClass; 
+    if (instance.classAttribute().indexOfValue(OneClassClassifier.OUTLIER_LABEL) == -1) {
+      if (this.getProbXGivenC(filtered, filtered.classAttribute().indexOfValue(m_TargetClassLabel)) >= m_Threshold)
+	dist[instance.classAttribute().indexOfValue(m_TargetClassLabel)] = 1;
+      else
+	dist[instance.classAttribute().indexOfValue(m_TargetClassLabel)] = 0;
+    } else
+      dist[instance.classAttribute().indexOfValue(OneClassClassifier.OUTLIER_LABEL)] = probForOutlierClass;
+
+    return dist;
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a representation of this classifier
+   */
+  public String toString() {
+
+    StringBuffer result = new StringBuffer();
+    result.append("\n\nClassifier Model\n"+m_Classifier.toString());
+
+    return result.toString();
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return The revision string.
+   */
+  public String getRevision() {
+    return "$Revision: 5987 $";
+  }
+
+  /**
+   * Main method for executing this classifier.
+   *
+   * @param args 	use -h to see all available options
+   */
+  public static void main(String[] args) {
+    runClassifier(new OneClassClassifier(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/OrdinalClassClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/OrdinalClassClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/OrdinalClassClassifier.java	(revision 29)
@@ -0,0 +1,573 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OrdinalClassClassifier.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Meta classifier that allows standard classification algorithms to be applied to ordinal class problems.<br/>
+ * <br/>
+ * For more information see: <br/>
+ * <br/>
+ * Eibe Frank, Mark Hall: A Simple Approach to Ordinal Classification. In: 12th European Conference on Machine Learning, 145-156, 2001.<br/>
+ * <br/>
+ * Robert E. Schapire, Peter Stone, David A. McAllester, Michael L. Littman, Janos A. Csirik: Modeling Auction Price Uncertainty Using Boosting-based Conditional Density Estimation. In: Machine Learning, Proceedings of the Nineteenth International Conference (ICML 2002), 546-553, 2002.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Frank2001,
+ *    author = {Eibe Frank and Mark Hall},
+ *    booktitle = {12th European Conference on Machine Learning},
+ *    pages = {145-156},
+ *    publisher = {Springer},
+ *    title = {A Simple Approach to Ordinal Classification},
+ *    year = {2001}
+ * }
+ * 
+ * &#64;inproceedings{Schapire2002,
+ *    author = {Robert E. Schapire and Peter Stone and David A. McAllester and Michael L. Littman and Janos A. Csirik},
+ *    booktitle = {Machine Learning, Proceedings of the Nineteenth International Conference (ICML 2002)},
+ *    pages = {546-553},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Modeling Auction Price Uncertainty Using Boosting-based Conditional Density Estimation},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S
+ *  Turn off Schapire et al.'s smoothing heuristic (ICML02, pp. 550).</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall
+ * @author Eibe Frank
+ * @version $Revision: 5928 $
+ * @see OptionHandler
+ */
+public class OrdinalClassClassifier 
+  extends SingleClassifierEnhancer 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3461971774059603636L;
+
+  /** The classifiers. (One for each class.) */
+  private Classifier [] m_Classifiers;
+
+  /** The filters used to transform the class. */
+  private MakeIndicator[] m_ClassFilters;
+
+  /** ZeroR classifier for when all base classifier return zero probability. */
+  private ZeroR m_ZeroR;
+
+  /** Whether to use smoothing to prevent negative "probabilities". */
+  private boolean m_UseSmoothing = true;
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Default constructor.
+   */
+  public OrdinalClassClassifier() {
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Meta classifier that allows standard classification algorithms "
+      +"to be applied to ordinal class problems.\n\n"
+      + "For more information see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation        additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Eibe Frank and Mark Hall");
+    result.setValue(Field.TITLE, "A Simple Approach to Ordinal Classification");
+    result.setValue(Field.BOOKTITLE, "12th European Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "145-156");
+    result.setValue(Field.PUBLISHER, "Springer");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Robert E. Schapire and Peter Stone and David A. McAllester " +
+    		"and Michael L. Littman and Janos A. Csirik");
+    additional.setValue(Field.TITLE, "Modeling Auction Price Uncertainty Using Boosting-based " +
+    		"Conditional Density Estimation");
+    additional.setValue(Field.BOOKTITLE, "Machine Learning, Proceedings of the Nineteenth " +
+    		"International Conference (ICML 2002)");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.PAGES, "546-553");
+    additional.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifiers.
+   *
+   * @param insts the training data.
+   * @throws Exception if a classifier can't be built
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+
+    Instances newInsts;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(insts);
+
+    // remove instances with missing class
+    insts = new Instances(insts);
+    insts.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("No base classifier has been set!");
+    }
+    m_ZeroR = new ZeroR();
+    m_ZeroR.buildClassifier(insts);
+
+    int numClassifiers = insts.numClasses() - 1;
+
+    numClassifiers = (numClassifiers == 0) ? 1 : numClassifiers;
+
+    if (numClassifiers == 1) {
+      m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, 1);
+      m_Classifiers[0].buildClassifier(insts);
+    } else {
+      m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, numClassifiers);
+      m_ClassFilters = new MakeIndicator[numClassifiers];
+
+      for (int i = 0; i < m_Classifiers.length; i++) {
+	m_ClassFilters[i] = new MakeIndicator();
+	m_ClassFilters[i].setAttributeIndex("" + (insts.classIndex() + 1));
+	m_ClassFilters[i].setValueIndices(""+(i+2)+"-last");
+	m_ClassFilters[i].setNumeric(false);
+	m_ClassFilters[i].setInputFormat(insts);
+	newInsts = Filter.useFilter(insts, m_ClassFilters[i]);
+	m_Classifiers[i].buildClassifier(newInsts);
+      }
+    }
+  }
+  
+  /**
+   * Returns the distribution for an instance.
+   *
+   * @param inst the instance to compute the distribution for
+   * @return the class distribution for the given instance
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double [] distributionForInstance(Instance inst) throws Exception {
+    
+    if (m_Classifiers.length == 1) {
+      return m_Classifiers[0].distributionForInstance(inst);
+    }
+
+    double [] probs = new double[inst.numClasses()];
+    
+    double [][] distributions = new double[m_ClassFilters.length][0];
+    for(int i = 0; i < m_ClassFilters.length; i++) {
+      m_ClassFilters[i].input(inst);
+      m_ClassFilters[i].batchFinished();
+      
+      distributions[i] = m_Classifiers[i].
+	distributionForInstance(m_ClassFilters[i].output());
+      
+    }
+
+    // Use Schapire et al.'s smoothing heuristic?
+    if (getUseSmoothing()) {
+      
+      double[] fScores = new double[distributions.length + 2];
+      fScores[0] = 1;
+      fScores[distributions.length + 1] = 0;
+      for (int i = 0; i < distributions.length; i++) {
+        fScores[i + 1] = distributions[i][1];
+      }
+      
+      // Sort scores in ascending order
+      int[] sortOrder = Utils.sort(fScores);
+      
+      // Compute pointwise maximum of lower bound
+      int minSoFar = sortOrder[0];
+      int index = 0;
+      double[] pointwiseMaxLowerBound = new double[fScores.length];
+      for (int i = 0; i < sortOrder.length; i++) {
+
+        // Progress to next higher value if possible
+        while (minSoFar > sortOrder.length - i - 1) {
+          minSoFar = sortOrder[++index];
+        }
+        pointwiseMaxLowerBound[sortOrder.length - i - 1] = fScores[minSoFar];
+      }
+      
+      // Get scores in descending order
+      int[] newSortOrder = new int[sortOrder.length];
+      for (int i = sortOrder.length - 1; i >= 0; i--) {
+        newSortOrder[sortOrder.length - i - 1] = sortOrder[i];
+      }
+      sortOrder = newSortOrder;
+      
+      // Compute pointwise minimum of upper bound
+      int maxSoFar = sortOrder[0];
+      index = 0;
+      double[] pointwiseMinUpperBound = new double[fScores.length];
+      for (int i = 0; i < sortOrder.length; i++) {
+        
+        // Progress to next lower value if possible
+        while (maxSoFar < i) {
+          maxSoFar = sortOrder[++index];
+        }
+        pointwiseMinUpperBound[i] = fScores[maxSoFar];
+      }
+
+      // Compute average
+      for (int i = 0; i < distributions.length; i++) {
+        distributions[i][1] = (pointwiseMinUpperBound[i + 1] +
+                               pointwiseMaxLowerBound[i + 1]) / 2.0;
+      }
+    }
+
+    for (int i = 0; i < inst.numClasses(); i++) {
+      if (i == 0) {
+        probs[i] = 1.0 - distributions[0][1];
+      } else if (i == inst.numClasses() - 1) {
+        probs[i] = distributions[i - 1][1];
+      } else {
+        probs[i] = distributions[i - 1][1] - distributions[i][1];
+        if (!(probs[i] >= 0)) {
+          System.err.println("Warning: estimated probability " + probs[i] +
+                             ". Rounding to 0.");
+          probs[i] = 0;
+        }
+      }
+    }
+    
+    if (Utils.gr(Utils.sum(probs), 0)) {
+      Utils.normalize(probs);
+      return probs;
+    } else {
+      return m_ZeroR.distributionForInstance(inst);
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions()  {
+
+    Vector vec = new Vector();
+    vec.addElement(new Option(
+	      "\tTurn off Schapire et al.'s smoothing " + 
+              "heuristic (ICML02, pp. 550).",
+	      "S", 0, "-S"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      vec.addElement(enu.nextElement());
+    }
+    return vec.elements();
+  }
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S
+   *  Turn off Schapire et al.'s smoothing heuristic (ICML02, pp. 550).</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.J48)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.J48:
+   * </pre>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setUseSmoothing(!Utils.getFlag('S', options));
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 1];
+
+    int current = 0;
+    if (!getUseSmoothing()) {
+      options[current++] = "-S";
+    }
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * Tip text method.
+   * 
+   * @return a tip text string suitable for displaying as a popup in the GUI.
+   */
+  public String useSmoothingTipText() {
+    return "If true, use Schapire et al.'s heuristic (ICML02, pp. 550).";
+  }
+
+  /**
+   * Determines whether Schapire et al.'s smoothing method is used.
+   * 
+   * @param b true if the smoothing heuristic is to be used.
+   */
+  public void setUseSmoothing(boolean b) {
+    
+    m_UseSmoothing = b;
+  }
+
+  /**
+   * Checks whether Schapire et al.'s smoothing method is used.
+   * 
+   * @return true if the smoothing heuristic is to be used.
+   */
+  public boolean getUseSmoothing() {
+    
+    return m_UseSmoothing;
+  }
+
+  
+  /**
+   * Prints the classifiers.
+   * 
+   * @return a string representation of this classifier
+   */
+  public String toString() {
+    
+    if (m_Classifiers == null) {
+      return "OrdinalClassClassifier: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("OrdinalClassClassifier\n\n");
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      text.append("Classifier ").append(i + 1);
+      if (m_Classifiers[i] != null) {
+	 if ((m_ClassFilters != null) && (m_ClassFilters[i] != null)) {
+          text.append(", using indicator values: ");
+          text.append(m_ClassFilters[i].getValueRange());
+        }
+        text.append('\n');
+        text.append(m_Classifiers[i].toString() + "\n");
+      } else {
+        text.append(" Skipped (no training examples)\n");
+      }
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new OrdinalClassClassifier(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RacedIncrementalLogitBoost.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RacedIncrementalLogitBoost.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RacedIncrementalLogitBoost.java	(revision 29)
@@ -0,0 +1,1292 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RacedIncrementalLogitBoost.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.UpdateableClassifier;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Classifier for incremental learning of large datasets by way of racing logit-boosted committees.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  Minimum size of chunks.
+ *  (default 500)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Maximum size of chunks.
+ *  (default 2000)</pre>
+ * 
+ * <pre> -V &lt;num&gt;
+ *  Size of validation set.
+ *  (default 1000)</pre>
+ * 
+ * <pre> -P &lt;pruning type&gt;
+ *  Committee pruning to perform.
+ *  0=none, 1=log likelihood (default)</pre>
+ * 
+ * <pre> -Q
+ *  Use resampling for boosting.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated learner.<p>
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class RacedIncrementalLogitBoost 
+  extends RandomizableSingleClassifierEnhancer
+  implements UpdateableClassifier {
+  
+  /** for serialization */
+  static final long serialVersionUID = 908598343772170052L;
+
+  /** no pruning */
+  public static final int PRUNETYPE_NONE = 0;
+  /** log likelihood pruning */
+  public static final int PRUNETYPE_LOGLIKELIHOOD = 1;
+  /** The pruning types */
+  public static final Tag [] TAGS_PRUNETYPE = {
+    new Tag(PRUNETYPE_NONE, "No pruning"),
+    new Tag(PRUNETYPE_LOGLIKELIHOOD, "Log likelihood pruning")
+  };
+
+  /** The committees */   
+  protected FastVector m_committees;
+
+  /** The pruning type used */
+  protected int m_PruningType = PRUNETYPE_LOGLIKELIHOOD;
+
+  /** Whether to use resampling */
+  protected boolean m_UseResampling = false;
+
+  /** The number of classes */
+  protected int m_NumClasses;
+
+  /** A threshold for responses (Friedman suggests between 2 and 4) */
+  protected static final double Z_MAX = 4;
+
+  /** Dummy dataset with a numeric class */
+  protected Instances m_NumericClassData;
+
+  /** The actual class attribute (for getting class names) */
+  protected Attribute m_ClassAttribute;  
+
+  /** The minimum chunk size used for training */
+  protected int m_minChunkSize = 500;
+
+  /** The maimum chunk size used for training */
+  protected int m_maxChunkSize = 2000;
+
+  /** The size of the validation set */
+  protected int m_validationChunkSize = 1000;
+
+  /** The number of instances consumed */  
+  protected int m_numInstancesConsumed;
+
+  /** The instances used for validation */    
+  protected Instances m_validationSet;
+
+  /** The instances currently in memory for training */   
+  protected Instances m_currentSet;
+
+  /** The current best committee */   
+  protected Committee m_bestCommittee;
+
+  /** The default scheme used when committees aren't ready */    
+  protected ZeroR m_zeroR = null;
+
+  /** Whether the validation set has recently been changed */ 
+  protected boolean m_validationSetChanged;
+
+  /** The maximum number of instances required for processing */   
+  protected int m_maxBatchSizeRequired;
+
+  /** The random number generator used */
+  protected Random m_RandomInstance = null;
+
+    
+  /**
+   * Constructor.
+   */
+  public RacedIncrementalLogitBoost() {
+    
+    m_Classifier = new weka.classifiers.trees.DecisionStump();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+
+  /** 
+   * Class representing a committee of LogitBoosted models
+   */
+  protected class Committee 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 5559880306684082199L;
+
+    protected int m_chunkSize;
+    
+    /** number eaten from m_currentSet */
+    protected int m_instancesConsumed; 
+    
+    protected FastVector m_models;
+    protected double m_lastValidationError;
+    protected double m_lastLogLikelihood;
+    protected boolean m_modelHasChanged;
+    protected boolean m_modelHasChangedLL;
+    protected double[][] m_validationFs;
+    protected double[][] m_newValidationFs;
+
+    /** 
+     * constructor 
+     * 
+     * @param chunkSize the size of the chunk
+     */
+    public Committee(int chunkSize) {
+
+      m_chunkSize = chunkSize;
+      m_instancesConsumed = 0;
+      m_models = new FastVector();
+      m_lastValidationError = 1.0;
+      m_lastLogLikelihood = Double.MAX_VALUE;
+      m_modelHasChanged = true;
+      m_modelHasChangedLL = true;
+      m_validationFs = new double[m_validationChunkSize][m_NumClasses];
+      m_newValidationFs = new double[m_validationChunkSize][m_NumClasses];
+    } 
+
+    /** 
+     * update the committee 
+     * 
+     * @return true if the committee has changed
+     * @throws Exception if anything goes wrong
+     */
+    public boolean update() throws Exception {
+
+      boolean hasChanged = false;
+      while (m_currentSet.numInstances() - m_instancesConsumed >= m_chunkSize) {
+	Classifier[] newModel = boost(new Instances(m_currentSet, m_instancesConsumed, m_chunkSize));
+	for (int i=0; i<m_validationSet.numInstances(); i++) {
+	  m_newValidationFs[i] = updateFS(m_validationSet.instance(i), newModel, m_validationFs[i]);
+	}
+	m_models.addElement(newModel);
+	m_instancesConsumed += m_chunkSize;
+	hasChanged = true;
+      }
+      if (hasChanged) {
+	m_modelHasChanged = true;
+	m_modelHasChangedLL = true;
+      }
+      return hasChanged;
+    }
+
+    /** reset consumation counts */
+    public void resetConsumed() {
+
+      m_instancesConsumed = 0;
+    }
+
+    /** remove the last model from the committee */
+    public void pruneLastModel() {
+
+      if (m_models.size() > 0) {
+	m_models.removeElementAt(m_models.size()-1);
+	m_modelHasChanged = true;
+	m_modelHasChangedLL = true;
+      }
+    }
+
+    /** 
+     * decide to keep the last model in the committee 
+     * @throws Exception if anything goes wrong
+     */
+    public void keepLastModel() throws Exception {
+
+      m_validationFs = m_newValidationFs;
+      m_newValidationFs = new double[m_validationChunkSize][m_NumClasses];
+      m_modelHasChanged = true;
+      m_modelHasChangedLL = true;
+    }
+
+    /** 
+     * calculate the log likelihood on the validation data 
+     * @return the log likelihood
+     * @throws Exception if computation fails
+     */        
+    public double logLikelihood() throws Exception {
+
+      if (m_modelHasChangedLL) {
+
+	Instance inst;
+	double llsum = 0.0;
+	for (int i=0; i<m_validationSet.numInstances(); i++) {
+	  inst = m_validationSet.instance(i);
+	  llsum += (logLikelihood(m_validationFs[i],(int) inst.classValue()));
+	}
+	m_lastLogLikelihood = llsum / (double) m_validationSet.numInstances();
+	m_modelHasChangedLL = false;
+      }
+      return m_lastLogLikelihood;
+    }
+
+    /** 
+     * calculate the log likelihood on the validation data after adding the last model 
+     * @return the log likelihood
+     * @throws Exception if computation fails
+     */
+    public double logLikelihoodAfter() throws Exception {
+
+	Instance inst;
+	double llsum = 0.0;
+	for (int i=0; i<m_validationSet.numInstances(); i++) {
+	  inst = m_validationSet.instance(i);
+	  llsum += (logLikelihood(m_newValidationFs[i],(int) inst.classValue()));
+	}
+	return llsum / (double) m_validationSet.numInstances();
+    }
+
+    
+    /** 
+     * calculates the log likelihood of an instance 
+     * @param Fs the Fs values
+     * @param classIndex the class index
+     * @return the log likelihood
+     * @throws Exception if computation fails
+     */
+    private double logLikelihood(double[] Fs, int classIndex) throws Exception {
+
+      return -Math.log(distributionForInstance(Fs)[classIndex]);
+    }
+
+    /** 
+     * calculates the validation error of the committee 
+     * @return the validation error
+     * @throws Exception if computation fails
+     */
+    public double validationError() throws Exception {
+
+      if (m_modelHasChanged) {
+
+	Instance inst;
+	int numIncorrect = 0;
+	for (int i=0; i<m_validationSet.numInstances(); i++) {
+	  inst = m_validationSet.instance(i);
+	  if (classifyInstance(m_validationFs[i]) != inst.classValue())
+	    numIncorrect++;
+	}
+	m_lastValidationError = (double) numIncorrect / (double) m_validationSet.numInstances();
+	m_modelHasChanged = false;
+      }
+      return m_lastValidationError;
+    }
+
+    /** 
+     * returns the chunk size used by the committee 
+     * 
+     * @return the chunk size
+     */
+    public int chunkSize() {
+
+      return m_chunkSize;
+    }
+
+    /** 
+     * returns the number of models in the committee 
+     * 
+     * @return the committee size
+     */
+    public int committeeSize() {
+
+      return m_models.size();
+    }
+
+    
+    /** 
+     * classifies an instance (given Fs values) with the committee 
+     * 
+     * @param Fs the Fs values
+     * @return the classification
+     * @throws Exception if anything goes wrong
+     */
+    public double classifyInstance(double[] Fs) throws Exception {
+      
+      double [] dist = distributionForInstance(Fs);
+
+      double max = 0;
+      int maxIndex = 0;
+      
+      for (int i = 0; i < dist.length; i++) {
+	if (dist[i] > max) {
+	  maxIndex = i;
+	  max = dist[i];
+	}
+      }
+      if (max > 0) {
+	return maxIndex;
+      } else {
+	return Utils.missingValue();
+      }
+    }
+
+    /** 
+     * classifies an instance with the committee 
+     * 
+     * @param instance the instance to classify
+     * @return the classification
+     * @throws Exception if anything goes wrong
+     */
+    public double classifyInstance(Instance instance) throws Exception {
+      
+      double [] dist = distributionForInstance(instance);
+      switch (instance.classAttribute().type()) {
+      case Attribute.NOMINAL:
+	double max = 0;
+	int maxIndex = 0;
+	
+	for (int i = 0; i < dist.length; i++) {
+	  if (dist[i] > max) {
+	    maxIndex = i;
+	    max = dist[i];
+	  }
+	}
+	if (max > 0) {
+	  return maxIndex;
+	} else {
+	  return Utils.missingValue();
+	}
+      case Attribute.NUMERIC:
+	return dist[0];
+      default:
+	return Utils.missingValue();
+      }
+    }
+
+    /** 
+     * returns the distribution the committee generates for an instance (given Fs values) 
+     * 
+     * @param Fs the Fs values
+     * @return the distribution
+     * @throws Exception if anything goes wrong
+     */
+    public double[] distributionForInstance(double[] Fs) throws Exception {
+      
+      double [] distribution = new double [m_NumClasses];
+      for (int j = 0; j < m_NumClasses; j++) {
+	distribution[j] = RtoP(Fs, j);
+      }
+      return distribution;
+    }
+    
+    /** 
+     * updates the Fs values given a new model in the committee 
+     * 
+     * @param instance the instance to use
+     * @param newModel the new model
+     * @param Fs the Fs values to update
+     * @return the updated Fs values
+     * @throws Exception if anything goes wrong
+     */
+    public double[] updateFS(Instance instance, Classifier[] newModel, double[] Fs) throws Exception {
+      
+      instance = (Instance)instance.copy();
+      instance.setDataset(m_NumericClassData);
+      
+      double [] Fi = new double [m_NumClasses];
+      double Fsum = 0;
+      for (int j = 0; j < m_NumClasses; j++) {
+	Fi[j] = newModel[j].classifyInstance(instance);
+	Fsum += Fi[j];
+      }
+      Fsum /= m_NumClasses;
+      
+      double[] newFs = new double[Fs.length];
+      for (int j = 0; j < m_NumClasses; j++) {
+	newFs[j] = Fs[j] + ((Fi[j] - Fsum) * (m_NumClasses - 1) / m_NumClasses);
+      }
+      return newFs;
+    }
+
+    /** 
+     * returns the distribution the committee generates for an instance
+     * 
+     * @param instance the instance to get the distribution for
+     * @return the distribution
+     * @throws Exception if anything goes wrong
+     */
+    public double[] distributionForInstance(Instance instance) throws Exception {
+
+      instance = (Instance)instance.copy();
+      instance.setDataset(m_NumericClassData);
+      double [] Fs = new double [m_NumClasses]; 
+      for (int i = 0; i < m_models.size(); i++) {
+	double [] Fi = new double [m_NumClasses];
+	double Fsum = 0;
+	Classifier[] model = (Classifier[]) m_models.elementAt(i);
+	for (int j = 0; j < m_NumClasses; j++) {
+	  Fi[j] = model[j].classifyInstance(instance);
+	  Fsum += Fi[j];
+	}
+	Fsum /= m_NumClasses;
+	for (int j = 0; j < m_NumClasses; j++) {
+	  Fs[j] += (Fi[j] - Fsum) * (m_NumClasses - 1) / m_NumClasses;
+	}
+      }
+      double [] distribution = new double [m_NumClasses];
+      for (int j = 0; j < m_NumClasses; j++) {
+	distribution[j] = RtoP(Fs, j);
+      }
+      return distribution;
+    }
+
+    /** 
+     * performs a boosting iteration, returning a new model for the committee
+     * 
+     * @param data the data to boost on
+     * @return the new model
+     * @throws Exception if anything goes wrong
+     */
+    protected Classifier[] boost(Instances data) throws Exception {
+      
+      Classifier[] newModel = AbstractClassifier.makeCopies(m_Classifier, m_NumClasses);
+      
+      // Create a copy of the data with the class transformed into numeric
+      Instances boostData = new Instances(data);
+      boostData.deleteWithMissingClass();
+      int numInstances = boostData.numInstances();
+      
+      // Temporarily unset the class index
+      int classIndex = data.classIndex();
+      boostData.setClassIndex(-1);
+      boostData.deleteAttributeAt(classIndex);
+      boostData.insertAttributeAt(new Attribute("'pseudo class'"), classIndex);
+      boostData.setClassIndex(classIndex);
+      double [][] trainFs = new double [numInstances][m_NumClasses];
+      double [][] trainYs = new double [numInstances][m_NumClasses];
+      for (int j = 0; j < m_NumClasses; j++) {
+	for (int i = 0, k = 0; i < numInstances; i++, k++) {
+	  while (data.instance(k).classIsMissing()) k++;
+	  trainYs[i][j] = (data.instance(k).classValue() == j) ? 1 : 0;
+	}
+      }
+      
+      // Evaluate / increment trainFs from the classifiers
+      for (int x = 0; x < m_models.size(); x++) {
+	for (int i = 0; i < numInstances; i++) {
+	  double [] pred = new double [m_NumClasses];
+	  double predSum = 0;
+	  Classifier[] model = (Classifier[]) m_models.elementAt(x);
+	  for (int j = 0; j < m_NumClasses; j++) {
+	    pred[j] = model[j].classifyInstance(boostData.instance(i));
+	    predSum += pred[j];
+	  }
+	  predSum /= m_NumClasses;
+	  for (int j = 0; j < m_NumClasses; j++) {
+	    trainFs[i][j] += (pred[j] - predSum) * (m_NumClasses-1) 
+	      / m_NumClasses;
+	  }
+	}
+      }
+
+      for (int j = 0; j < m_NumClasses; j++) {
+	
+	// Set instance pseudoclass and weights
+	for (int i = 0; i < numInstances; i++) {
+	  double p = RtoP(trainFs[i], j);
+	  Instance current = boostData.instance(i);
+	  double z, actual = trainYs[i][j];
+	  if (actual == 1) {
+	    z = 1.0 / p;
+	    if (z > Z_MAX) { // threshold
+	      z = Z_MAX;
+	    }
+	  } else if (actual == 0) {
+	    z = -1.0 / (1.0 - p);
+	    if (z < -Z_MAX) { // threshold
+	      z = -Z_MAX;
+	    }
+	  } else {
+	    z = (actual - p) / (p * (1 - p));
+	  }
+
+	  double w = (actual - p) / z;
+	  current.setValue(classIndex, z);
+	  current.setWeight(numInstances * w);
+	}
+	
+	Instances trainData = boostData;
+	if (m_UseResampling) {
+	  double[] weights = new double[boostData.numInstances()];
+	  for (int kk = 0; kk < weights.length; kk++) {
+	    weights[kk] = boostData.instance(kk).weight();
+	  }
+	  trainData = boostData.resampleWithWeights(m_RandomInstance, 
+						    weights);
+	}
+	
+	// Build the classifier
+	newModel[j].buildClassifier(trainData);
+      }      
+      
+      return newModel;
+    }
+
+    /** 
+     * outputs description of the committee
+     * 
+     * @return a string representation of the classifier
+     */
+    public String toString() {
+      
+      StringBuffer text = new StringBuffer();
+      
+      text.append("RacedIncrementalLogitBoost: Best committee on validation data\n");
+      text.append("Base classifiers: \n");
+      
+      for (int i = 0; i < m_models.size(); i++) {
+	text.append("\nModel "+(i+1));
+	Classifier[] cModels = (Classifier[]) m_models.elementAt(i);
+	for (int j = 0; j < m_NumClasses; j++) {
+	  text.append("\n\tClass " + (j + 1) 
+		      + " (" + m_ClassAttribute.name() 
+		      + "=" + m_ClassAttribute.value(j) + ")\n\n"
+		      + cModels[j].toString() + "\n");
+	}
+      }
+      text.append("Number of models: " +
+		  m_models.size() + "\n");      
+      text.append("Chunk size per model: " + m_chunkSize + "\n");
+      
+      return text.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+ /**
+   * Builds the classifier.
+   *
+   * @param data the instances to train the classifier with
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    m_RandomInstance = new Random(m_Seed);
+
+    Instances boostData;
+    int classIndex = data.classIndex();
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("A base classifier has not been specified!");
+    }
+
+    if (!(m_Classifier instanceof WeightedInstancesHandler) &&
+	!m_UseResampling) {
+      m_UseResampling = true;
+    }
+
+    m_NumClasses = data.numClasses();
+    m_ClassAttribute = data.classAttribute();
+
+    // Create a copy of the data with the class transformed into numeric
+    boostData = new Instances(data);
+
+    // Temporarily unset the class index
+    boostData.setClassIndex(-1);
+    boostData.deleteAttributeAt(classIndex);
+    boostData.insertAttributeAt(new Attribute("'pseudo class'"), classIndex);
+    boostData.setClassIndex(classIndex);
+    m_NumericClassData = new Instances(boostData, 0);
+
+    data.randomize(m_RandomInstance);
+
+    // create the committees
+    int cSize = m_minChunkSize;
+    m_committees = new FastVector();
+    while (cSize <= m_maxChunkSize) {
+      m_committees.addElement(new Committee(cSize));
+      m_maxBatchSizeRequired = cSize;
+      cSize *= 2;
+    }
+
+    // set up for consumption
+    m_validationSet = new Instances(data, m_validationChunkSize);
+    m_currentSet = new Instances(data, m_maxBatchSizeRequired);
+    m_bestCommittee = null;
+    m_numInstancesConsumed = 0;
+
+    // start eating what we've been given
+    for (int i=0; i<data.numInstances(); i++) updateClassifier(data.instance(i));
+  }
+
+ /**
+   * Updates the classifier.
+   *
+   * @param instance the next instance in the stream of training data
+   * @throws Exception if something goes wrong
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+
+    m_numInstancesConsumed++;
+
+    if (m_validationSet.numInstances() < m_validationChunkSize) {
+      m_validationSet.add(instance);
+      m_validationSetChanged = true;
+    } else {
+      m_currentSet.add(instance);
+      boolean hasChanged = false;
+      
+      // update each committee
+      for (int i=0; i<m_committees.size(); i++) {
+	Committee c = (Committee) m_committees.elementAt(i);
+	if (c.update()) {
+	  
+	  hasChanged = true;
+	  
+	  if (m_PruningType == PRUNETYPE_LOGLIKELIHOOD) {
+	    double oldLL = c.logLikelihood();
+	    double newLL = c.logLikelihoodAfter();
+	    if (newLL >= oldLL && c.committeeSize() > 1) {
+	      c.pruneLastModel();
+	      if (m_Debug) System.out.println("Pruning " + c.chunkSize()+ " committee (" +
+					      oldLL + " < " + newLL + ")");
+	    } else c.keepLastModel();
+	  } else c.keepLastModel(); // no pruning
+	} 
+      }
+      if (hasChanged) {
+
+	if (m_Debug) System.out.println("After consuming " + m_numInstancesConsumed
+					+ " instances... (" + m_validationSet.numInstances()
+					+ " + " + m_currentSet.numInstances()
+					+ " instances currently in memory)");
+	
+	// find best committee
+	double lowestError = 1.0;
+	for (int i=0; i<m_committees.size(); i++) {
+	  Committee c = (Committee) m_committees.elementAt(i);
+
+	  if (c.committeeSize() > 0) {
+
+	    double err = c.validationError();
+	    double ll = c.logLikelihood();
+
+	    if (m_Debug) System.out.println("Chunk size " + c.chunkSize() + " with "
+					    + c.committeeSize() + " models, has validation error of "
+					    + err + ", log likelihood of " + ll);
+	    if (err < lowestError) {
+	      lowestError = err;
+	      m_bestCommittee = c;
+	    }
+	  }
+	}
+      }
+      if (m_currentSet.numInstances() >= m_maxBatchSizeRequired) {
+	m_currentSet = new Instances(m_currentSet, m_maxBatchSizeRequired);
+
+	// reset consumation counts
+	for (int i=0; i<m_committees.size(); i++) {
+	  Committee c = (Committee) m_committees.elementAt(i);
+	  c.resetConsumed();
+	}
+      }
+    }
+  }
+
+  /**
+   * Convert from function responses to probabilities
+   *
+   * @param Fs an array containing the responses from each function
+   * @param j the class value of interest
+   * @return the probability prediction for j
+   * @throws Exception if can't normalize
+   */
+  protected static double RtoP(double []Fs, int j) 
+    throws Exception {
+
+    double maxF = -Double.MAX_VALUE;
+    for (int i = 0; i < Fs.length; i++) {
+      if (Fs[i] > maxF) {
+	maxF = Fs[i];
+      }
+    }
+    double sum = 0;
+    double[] probs = new double[Fs.length];
+    for (int i = 0; i < Fs.length; i++) {
+      probs[i] = Math.exp(Fs[i] - maxF);
+      sum += probs[i];
+    }
+    if (sum == 0) {
+      throw new Exception("Can't normalize");
+    }
+    return probs[j] / sum;
+  }
+
+  /**
+   * Computes class distribution of an instance using the best committee.
+   * 
+   * @param instance the instance to get the distribution for
+   * @return the distribution
+   * @throws Exception if anything goes wrong
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    if (m_bestCommittee != null) return m_bestCommittee.distributionForInstance(instance);
+    else {
+      if (m_validationSetChanged || m_zeroR == null) {
+	m_zeroR = new ZeroR();
+	m_zeroR.buildClassifier(m_validationSet);
+	m_validationSetChanged = false;
+      }
+      return m_zeroR.distributionForInstance(instance);
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(9);
+
+    newVector.addElement(new Option(
+	      "\tMinimum size of chunks.\n"
+	      +"\t(default 500)",
+	      "C", 1, "-C <num>"));
+
+    newVector.addElement(new Option(
+	      "\tMaximum size of chunks.\n"
+	      +"\t(default 2000)",
+	      "M", 1, "-M <num>"));
+
+    newVector.addElement(new Option(
+	      "\tSize of validation set.\n"
+	      +"\t(default 1000)",
+	      "V", 1, "-V <num>"));
+
+    newVector.addElement(new Option(
+	      "\tCommittee pruning to perform.\n"
+	      +"\t0=none, 1=log likelihood (default)",
+	      "P", 1, "-P <pruning type>"));
+
+    newVector.addElement(new Option(
+	      "\tUse resampling for boosting.",
+	      "Q", 0, "-Q"));
+
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  Minimum size of chunks.
+   *  (default 500)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Maximum size of chunks.
+   *  (default 2000)</pre>
+   * 
+   * <pre> -V &lt;num&gt;
+   *  Size of validation set.
+   *  (default 1000)</pre>
+   * 
+   * <pre> -P &lt;pruning type&gt;
+   *  Committee pruning to perform.
+   *  0=none, 1=log likelihood (default)</pre>
+   * 
+   * <pre> -Q
+   *  Use resampling for boosting.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String minChunkSize = Utils.getOption('C', options);
+    if (minChunkSize.length() != 0) {
+      setMinChunkSize(Integer.parseInt(minChunkSize));
+    } else {
+      setMinChunkSize(500);
+    }
+
+    String maxChunkSize = Utils.getOption('M', options);
+    if (maxChunkSize.length() != 0) {
+      setMaxChunkSize(Integer.parseInt(maxChunkSize));
+    } else {
+      setMaxChunkSize(2000);
+    }
+
+    String validationChunkSize = Utils.getOption('V', options);
+    if (validationChunkSize.length() != 0) {
+      setValidationChunkSize(Integer.parseInt(validationChunkSize));
+    } else {
+      setValidationChunkSize(1000);
+    }
+
+    String pruneType = Utils.getOption('P', options);
+    if (pruneType.length() != 0) {
+      setPruningType(new SelectedTag(Integer.parseInt(pruneType), TAGS_PRUNETYPE));
+    } else {
+      setPruningType(new SelectedTag(PRUNETYPE_LOGLIKELIHOOD, TAGS_PRUNETYPE));
+    }
+
+    setUseResampling(Utils.getFlag('Q', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 9];
+
+    int current = 0;
+
+    if (getUseResampling()) {
+      options[current++] = "-Q";
+    }
+    options[current++] = "-C"; options[current++] = "" + getMinChunkSize();
+
+    options[current++] = "-M"; options[current++] = "" + getMaxChunkSize();
+
+    options[current++] = "-V"; options[current++] = "" + getValidationChunkSize();
+
+    options[current++] = "-P"; options[current++] = "" + m_PruningType;
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Classifier for incremental learning of large datasets by way of racing logit-boosted committees.";
+  }
+
+  /**
+   * Set the base learner.
+   *
+   * @param newClassifier 		the classifier to use.
+   * @throws IllegalArgumentException 	if base classifier cannot handle numeric 
+   * 					class
+   */
+  public void setClassifier(Classifier newClassifier) {
+    Capabilities cap = newClassifier.getCapabilities();
+    
+    if (!cap.handles(Capability.NUMERIC_CLASS))
+      throw new IllegalArgumentException("Base classifier cannot handle numeric class!");
+      
+    super.setClassifier(newClassifier);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minChunkSizeTipText() {
+
+    return "The minimum number of instances to train the base learner with.";
+  }
+
+  /**
+   * Set the minimum chunk size
+   *
+   * @param chunkSize the minimum chunk size
+   */
+  public void setMinChunkSize(int chunkSize) {
+
+    m_minChunkSize = chunkSize;
+  }
+
+  /**
+   * Get the minimum chunk size
+   *
+   * @return the chunk size
+   */
+  public int getMinChunkSize() {
+
+    return m_minChunkSize;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxChunkSizeTipText() {
+
+    return "The maximum number of instances to train the base learner with. The chunk sizes used will start at minChunkSize and grow twice as large for as many times as they are less than or equal to the maximum size.";
+  }
+
+  /**
+   * Set the maximum chunk size
+   *
+   * @param chunkSize the maximum chunk size
+   */
+  public void setMaxChunkSize(int chunkSize) {
+
+    m_maxChunkSize = chunkSize;
+  }
+
+  /**
+   * Get the maximum chunk size
+   *
+   * @return the chunk size
+   */
+  public int getMaxChunkSize() {
+
+    return m_maxChunkSize;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String validationChunkSizeTipText() {
+
+    return "The number of instances to hold out for validation. These instances will be taken from the beginning of the stream, so learning will not start until these instances have been consumed first.";
+  }
+
+  /**
+   * Set the validation chunk size
+   *
+   * @param chunkSize the validation chunk size
+   */
+  public void setValidationChunkSize(int chunkSize) {
+
+    m_validationChunkSize = chunkSize;
+  }
+
+  /**
+   * Get the validation chunk size
+   *
+   * @return the chunk size
+   */
+  public int getValidationChunkSize() {
+
+    return m_validationChunkSize;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String pruningTypeTipText() {
+
+    return "The pruning method to use within each committee. Log likelihood pruning will discard new models if they have a negative effect on the log likelihood of the validation data.";
+  }
+
+  /**
+   * Set the pruning type
+   *
+   * @param pruneType the pruning type
+   */
+  public void setPruningType(SelectedTag pruneType) {
+
+    if (pruneType.getTags() == TAGS_PRUNETYPE) {
+      m_PruningType = pruneType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Get the pruning type
+   *
+   * @return the type
+   */
+  public SelectedTag getPruningType() {
+
+    return new SelectedTag(m_PruningType, TAGS_PRUNETYPE);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useResamplingTipText() {
+
+    return "Force the use of resampling data rather than using the weight-handling capabilities of the base classifier. Resampling is always used if the base classifier cannot handle weighted instances.";
+  }
+
+  /**
+   * Set resampling mode
+   *
+   * @param r true if resampling should be done
+   */
+  public void setUseResampling(boolean r) {
+    
+    m_UseResampling = r;
+  }
+
+  /**
+   * Get whether resampling is turned on
+   *
+   * @return true if resampling output is on
+   */
+  public boolean getUseResampling() {
+    
+    return m_UseResampling;
+  }
+
+  /**
+   * Get the best committee chunk size
+   * 
+   * @return the best committee chunk size
+   */
+  public int getBestCommitteeChunkSize() {
+
+    if (m_bestCommittee != null) {
+      return m_bestCommittee.chunkSize();
+    }
+    else return 0;
+  }
+
+  /**
+   * Get the number of members in the best committee
+   * 
+   * @return the number of members
+   */
+  public int getBestCommitteeSize() {
+
+    if (m_bestCommittee != null) {
+      return m_bestCommittee.committeeSize();
+    }
+    else return 0;
+  }
+
+  /**
+   * Get the best committee's error on the validation data
+   * 
+   * @return the best committee's error
+   */
+  public double getBestCommitteeErrorEstimate() {
+
+    if (m_bestCommittee != null) {
+      try {
+	return m_bestCommittee.validationError() * 100.0;
+      } catch (Exception e) {
+	System.err.println(e.getMessage());
+	return 100.0;
+      }
+    }
+    else return 100.0;
+  }
+
+  /**
+   * Get the best committee's log likelihood on the validation data
+   * 
+   * @return best committee's log likelihood
+   */
+  public double getBestCommitteeLLEstimate() {
+
+    if (m_bestCommittee != null) {
+      try {
+	return m_bestCommittee.logLikelihood();
+      } catch (Exception e) {
+	System.err.println(e.getMessage());
+	return Double.MAX_VALUE;
+      }
+    }
+    else return Double.MAX_VALUE;
+  }
+  
+  /**
+   * Returns description of the boosted classifier.
+   *
+   * @return description of the boosted classifier as a string
+   */
+  public String toString() {
+        
+    if (m_bestCommittee != null) {
+      return m_bestCommittee.toString();
+    } else {
+      if ((m_validationSetChanged || m_zeroR == null) && m_validationSet != null
+	  && m_validationSet.numInstances() > 0) {
+	m_zeroR = new ZeroR();
+	try {
+	  m_zeroR.buildClassifier(m_validationSet);
+	} catch (Exception e) {}
+	m_validationSetChanged = false;
+      }
+      if (m_zeroR != null) {
+	return ("RacedIncrementalLogitBoost: insufficient data to build model, resorting to ZeroR:\n\n"
+		+ m_zeroR.toString());
+      }
+      else return ("RacedIncrementalLogitBoost: no model built yet.");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for this class.
+   * 
+   * @param argv the commandline parameters
+   */
+  public static void main(String[] argv) {
+    runClassifier(new RacedIncrementalLogitBoost(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomCommittee.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomCommittee.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomCommittee.java	(revision 29)
@@ -0,0 +1,254 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomCommittee.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.classifiers.RandomizableParallelIteratedSingleClassifierEnhancer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building an ensemble of randomizable base classifiers. Each base classifiers is built using a different random number seed (but based one the same data). The final prediction is a straight average of the predictions generated by the individual base classifiers.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.RandomTree)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.RandomTree:
+ * </pre>
+ * 
+ * <pre> -K &lt;number of attributes&gt;
+ *  Number of attributes to randomly investigate
+ *  (&lt;1 = int(log(#attributes)+1)).</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Seed for random number generator.
+ *  (default 1)</pre>
+ * 
+ * <pre> -depth &lt;num&gt;
+ *  The maximum depth of the tree, 0 for unlimited.
+ *  (default 0)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RandomCommittee 
+  extends RandomizableParallelIteratedSingleClassifierEnhancer
+  implements WeightedInstancesHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -9204394360557300092L;
+  
+  /** training data */
+  protected Instances m_data;
+  
+  /**
+   * Constructor.
+   */
+  public RandomCommittee() {
+    
+    m_Classifier = new weka.classifiers.trees.RandomTree();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.RandomTree";
+  }
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for building an ensemble of randomizable base classifiers. Each "
+      + "base classifiers is built using a different random number seed (but based "
+      + "one the same data). The final prediction is a straight average of the "
+      + "predictions generated by the individual base classifiers.";
+  }
+
+  /**
+   * Builds the committee of randomizable classifiers.
+   *
+   * @param data the training data to be used for generating the
+   * bagged classifier.
+   * @exception Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    m_data = new Instances(data);
+    m_data.deleteWithMissingClass();
+    super.buildClassifier(m_data);
+    
+    if (!(m_Classifier instanceof Randomizable)) {
+      throw new IllegalArgumentException("Base learner must implement Randomizable!");
+    }
+
+    m_Classifiers = AbstractClassifier.makeCopies(m_Classifier, m_NumIterations);
+
+    Random random = m_data.getRandomNumberGenerator(m_Seed);
+    for (int j = 0; j < m_Classifiers.length; j++) {
+
+      // Set the random number seed for the current classifier.
+      ((Randomizable) m_Classifiers[j]).setSeed(random.nextInt());
+      
+      // Build the classifier.
+//      m_Classifiers[j].buildClassifier(m_data);
+    }
+    
+    buildClassifiers();
+    
+    // save memory
+    m_data = null;
+  }
+  
+  /**
+   * Returns a training set for a particular iteration.
+   * 
+   * @param iteration the number of the iteration for the requested training set.
+   * @return the training set for the supplied iteration number
+   * @throws Exception if something goes wrong when generating a training set.
+   */
+  protected synchronized Instances getTrainingSet(int iteration) throws Exception {
+    
+    // we don't manipulate the training data in any way.
+    return m_data;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @exception Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    double [] sums = new double [instance.numClasses()], newProbs; 
+    
+    for (int i = 0; i < m_NumIterations; i++) {
+      if (instance.classAttribute().isNumeric() == true) {
+	sums[0] += m_Classifiers[i].classifyInstance(instance);
+      } else {
+	newProbs = m_Classifiers[i].distributionForInstance(instance);
+	for (int j = 0; j < newProbs.length; j++)
+	  sums[j] += newProbs[j];
+      }
+    }
+    if (instance.classAttribute().isNumeric() == true) {
+      sums[0] /= (double)m_NumIterations;
+      return sums;
+    } else if (Utils.eq(Utils.sum(sums), 0)) {
+      return sums;
+    } else {
+      Utils.normalize(sums);
+      return sums;
+    }
+  }
+
+  /**
+   * Returns description of the committee.
+   *
+   * @return description of the committee as a string
+   */
+  public String toString() {
+    
+    if (m_Classifiers == null) {
+      return "RandomCommittee: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("All the base classifiers: \n\n");
+    for (int i = 0; i < m_Classifiers.length; i++)
+      text.append(m_Classifiers[i].toString() + "\n\n");
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new RandomCommittee(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomSubSpace.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomSubSpace.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RandomSubSpace.java	(revision 29)
@@ -0,0 +1,554 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomSubSpace.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.filters.unsupervised.attribute.Remove;
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.classifiers.RandomizableParallelIteratedSingleClassifierEnhancer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ <!-- globalinfo-start -->
+ * This method constructs a decision tree based classifier that maintains highest accuracy on training data and improves on generalization accuracy as it grows in complexity. The classifier consists of multiple trees constructed systematically by pseudorandomly selecting subsets of components of the feature vector, that is, trees constructed in randomly chosen subspaces.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Tin Kam Ho (1998). The Random Subspace Method for Constructing Decision Forests. IEEE Transactions on Pattern Analysis and Machine Intelligence. 20(8):832-844. URL http://citeseer.ist.psu.edu/ho98random.html.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Ho1998,
+ *    author = {Tin Kam Ho},
+ *    journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence},
+ *    number = {8},
+ *    pages = {832-844},
+ *    title = {The Random Subspace Method for Constructing Decision Forests},
+ *    volume = {20},
+ *    year = {1998},
+ *    ISSN = {0162-8828},
+ *    URL = {http://citeseer.ist.psu.edu/ho98random.html}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P
+ *  Size of each subspace:
+ *   &lt; 1: percentage of the number of attributes
+ *   &gt;=1: absolute number of attributes
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.REPTree)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.REPTree:
+ * </pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf (default 2).</pre>
+ * 
+ * <pre> -V &lt;minimum variance for split&gt;
+ *  Set minimum numeric class variance proportion
+ *  of train variance for split (default 1e-3).</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Number of folds for reduced error pruning (default 3).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ * <pre> -P
+ *  No pruning.</pre>
+ * 
+ * <pre> -L
+ *  Maximum tree depth (default -1, no maximum)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @author Peter Reutemann (fracpete@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RandomSubSpace
+  extends RandomizableParallelIteratedSingleClassifierEnhancer 
+  implements WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1278172513912424947L;
+  
+  /** The size of each bag sample, as a percentage of the training size */
+  protected double m_SubSpaceSize = 0.5;
+
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+  
+  /** Training data */
+  protected Instances m_data;
+    
+  /**
+   * Constructor.
+   */
+  public RandomSubSpace() {
+    super();
+    
+    m_Classifier = new weka.classifiers.trees.REPTree();
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "This method constructs a decision tree based classifier that "
+      + "maintains highest accuracy on training data and improves on "
+      + "generalization accuracy as it grows in complexity. The classifier "
+      + "consists of multiple trees constructed systematically by "
+      + "pseudorandomly selecting subsets of components of the feature vector, "
+      + "that is, trees constructed in randomly chosen subspaces.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Tin Kam Ho");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "The Random Subspace Method for Constructing Decision Forests");
+    result.setValue(Field.JOURNAL, "IEEE Transactions on Pattern Analysis and Machine Intelligence");
+    result.setValue(Field.VOLUME, "20");
+    result.setValue(Field.NUMBER, "8");
+    result.setValue(Field.PAGES, "832-844");
+    result.setValue(Field.URL, "http://citeseer.ist.psu.edu/ho98random.html");
+    result.setValue(Field.ISSN, "0162-8828");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return 		the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    return "weka.classifiers.trees.REPTree";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSize of each subspace:\n"
+	+ "\t\t< 1: percentage of the number of attributes\n"
+	+ "\t\t>=1: absolute number of attributes\n",
+	"P", 1, "-P"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P
+   *  Size of each subspace:
+   *   &lt; 1: percentage of the number of attributes
+   *   &gt;=1: absolute number of attributes
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.REPTree)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.REPTree:
+   * </pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf (default 2).</pre>
+   * 
+   * <pre> -V &lt;minimum variance for split&gt;
+   *  Set minimum numeric class variance proportion
+   *  of train variance for split (default 1e-3).</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Number of folds for reduced error pruning (default 3).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   * <pre> -P
+   *  No pruning.</pre>
+   * 
+   * <pre> -L
+   *  Maximum tree depth (default -1, no maximum)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String tmpStr;
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setSubSpaceSize(Double.parseDouble(tmpStr));
+    else
+      setSubSpaceSize(0.5);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+
+    result.add("-P");
+    result.add("" + getSubSpaceSize());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String subSpaceSizeTipText() {
+    return 
+        "Size of each subSpace: if less than 1 as a percentage of the "
+      + "number of attributes, otherwise the absolute number of attributes.";
+  }
+
+  /**
+   * Gets the size of each subSpace, as a percentage of the training set size.
+   *
+   * @return 		the subSpace size, as a percentage.
+   */
+  public double getSubSpaceSize() {
+    return m_SubSpaceSize;
+  }
+  
+  /**
+   * Sets the size of each subSpace, as a percentage of the training set size.
+   *
+   * @param value 	the subSpace size, as a percentage.
+   */
+  public void setSubSpaceSize(double value) {
+    m_SubSpaceSize = value;
+  }
+
+  /**
+   * calculates the number of attributes
+   * 
+   * @param total	the available number of attributes
+   * @param fraction	the fraction - if less than 1 it represents the
+   * 			percentage, otherwise the absolute number of attributes
+   * @return		the number of attributes to use
+   */
+  protected int numberOfAttributes(int total, double fraction) {
+    int k = (int) Math.round((fraction < 1.0) ? total*fraction : fraction);
+    
+    if (k > total)
+      k = total;
+    if (k < 1)
+      k = 1;
+
+    return k;
+  }
+
+  /**
+   * generates an index string describing a random subspace, suitable for
+   * the Remove filter.
+   * 
+   * @param indices		the attribute indices
+   * @param subSpaceSize	the size of the subspace
+   * @param classIndex		the class index
+   * @param random		the random number generator
+   * @return			the generated string describing the subspace
+   */
+  protected String randomSubSpace(Integer[] indices, int subSpaceSize, int classIndex, Random random) {
+    Collections.shuffle(Arrays.asList(indices), random);
+    StringBuffer sb = new StringBuffer("");
+    for(int i = 0; i < subSpaceSize; i++) {
+      sb.append(indices[i]+",");
+    }
+    sb.append(classIndex);
+    
+    if (getDebug())
+      System.out.println("subSPACE = " + sb);
+
+    return sb.toString();
+  }
+
+  /**
+   * builds the classifier.
+   *
+   * @param data 	the training data to be used for generating the
+   * 			classifier.
+   * @throws Exception 	if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    m_data = new Instances(data);
+    m_data.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (m_data.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(m_data);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    super.buildClassifier(data);
+
+    Integer[] indices = new Integer[data.numAttributes()-1];
+    int classIndex = data.classIndex();
+    int offset = 0;
+    for(int i = 0; i < indices.length+1; i++) {
+      if (i != classIndex) {
+	indices[offset++] = i+1;
+      }
+    }
+    int subSpaceSize = numberOfAttributes(indices.length, getSubSpaceSize());
+    Random random = data.getRandomNumberGenerator(m_Seed);
+    
+    for (int j = 0; j < m_Classifiers.length; j++) {
+      if (m_Classifier instanceof Randomizable) {
+	((Randomizable) m_Classifiers[j]).setSeed(random.nextInt());
+      }
+      FilteredClassifier fc = new FilteredClassifier();
+      fc.setClassifier(m_Classifiers[j]);
+      m_Classifiers[j] = fc;
+      Remove rm = new Remove();
+      rm.setOptions(new String[]{"-V", "-R", randomSubSpace(indices,subSpaceSize,classIndex+1,random)});
+      fc.setFilter(rm);
+
+      // build the classifier
+      //m_Classifiers[j].buildClassifier(m_data);
+    }
+    
+    buildClassifiers();
+    
+    // save memory
+    m_data = null;
+  }
+  
+  /**
+   * Returns a training set for a particular iteration.
+   * 
+   * @param iteration the number of the iteration for the requested training set.
+   * @return the training set for the supplied iteration number
+   * @throws Exception if something goes wrong when generating a training set.
+   */
+  protected synchronized Instances getTrainingSet(int iteration) throws Exception {
+    
+    // We don't manipulate the training data in any way. The FilteredClassifiers
+    // take care of generating the sub-spaces.
+    return m_data;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance 	the instance to be classified
+   * @return 		preedicted class probability distribution
+   * @throws Exception 	if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    double[] sums = new double [instance.numClasses()], newProbs; 
+    
+    for (int i = 0; i < m_NumIterations; i++) {
+      if (instance.classAttribute().isNumeric() == true) {
+	sums[0] += m_Classifiers[i].classifyInstance(instance);
+      } else {
+	newProbs = m_Classifiers[i].distributionForInstance(instance);
+	for (int j = 0; j < newProbs.length; j++)
+	  sums[j] += newProbs[j];
+      }
+    }
+    if (instance.classAttribute().isNumeric() == true) {
+      sums[0] /= (double)m_NumIterations;
+      return sums;
+    } else if (Utils.eq(Utils.sum(sums), 0)) {
+      return sums;
+    } else {
+      Utils.normalize(sums);
+      return sums;
+    }
+  }
+
+  /**
+   * Returns description of the bagged classifier.
+   *
+   * @return 		description of the bagged classifier as a string
+   */
+  public String toString() {
+    
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_Classifiers == null) {
+      return "RandomSubSpace: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("All the base classifiers: \n\n");
+    for (int i = 0; i < m_Classifiers.length; i++)
+      text.append(m_Classifiers[i].toString() + "\n\n");
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	the options
+   */
+  public static void main(String[] args) {
+    runClassifier(new RandomSubSpace(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RealAdaBoost.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RealAdaBoost.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RealAdaBoost.java	(revision 29)
@@ -0,0 +1,766 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RealAdaBoost.java
+ *    Copyright (C) 1999, 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableIteratedSingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for boosting a 2-class classifier using the Real Adaboost method.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * J. Friedman, T. Hastie, R. Tibshirani (2000). Additive Logistic Regression: a Statistical View of Boosting. Annals of Statistics. 95(2):337-407.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Friedman2000,
+ *    author = {J. Friedman and T. Hastie and R. Tibshirani},
+ *    journal = {Annals of Statistics},
+ *    number = {2},
+ *    pages = {337-407},
+ *    title = {Additive Logistic Regression: a Statistical View of Boosting},
+ *    volume = {95},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  Percentage of weight mass to base training on.
+ *  (default 100, reduce to around 90 speed up)</pre>
+ * 
+ * <pre> -Q
+ *  Use resampling for boosting.</pre>
+ * 
+ * <pre> -H &lt;num&gt;
+ *  Shrinkage parameter.
+ *  (default 1)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.DecisionStump)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.DecisionStump:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated classifier.<p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6136 $ 
+ */
+public class RealAdaBoost 
+  extends RandomizableIteratedSingleClassifierEnhancer 
+  implements WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -7378109809933197974L;
+
+  /** The number of successfully generated base classifiers. */
+  protected int m_NumIterationsPerformed;
+
+  /** Weight Threshold. The percentage of weight mass used in training */
+  protected int m_WeightThreshold = 100;
+
+  /** The value of the shrinkage parameter */
+  protected double m_Shrinkage = 1;
+
+  /** Use boosting with reweighting? */
+  protected boolean m_UseResampling;
+  
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+
+  /** Sum of weights on training data */
+  protected double m_SumOfWeights;
+    
+  /**
+   * Constructor.
+   */
+  public RealAdaBoost() {
+    
+    m_Classifier = new weka.classifiers.trees.DecisionStump();
+  }
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for boosting a 2-class classifier using the Real Adaboost method.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "J. Friedman and T. Hastie and R. Tibshirani");
+    result.setValue(Field.TITLE, "Additive Logistic Regression: a Statistical View of Boosting");
+    result.setValue(Field.JOURNAL, "Annals of Statistics");
+    result.setValue(Field.VOLUME, "95");
+    result.setValue(Field.NUMBER, "2");
+    result.setValue(Field.PAGES, "337-407");
+    result.setValue(Field.YEAR, "2000");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.DecisionStump";
+  }
+
+  /**
+   * Select only instances with weights that contribute to 
+   * the specified quantile of the weight distribution
+   *
+   * @param data the input instances
+   * @param quantile the specified quantile eg 0.9 to select 
+   * 90% of the weight mass
+   * @return the selected instances
+   */
+  protected Instances selectWeightQuantile(Instances data, double quantile) { 
+
+    int numInstances = data.numInstances();
+    Instances trainData = new Instances(data, numInstances);
+    double [] weights = new double [numInstances];
+
+    double sumOfWeights = 0;
+    for(int i = 0; i < numInstances; i++) {
+      weights[i] = data.instance(i).weight();
+      sumOfWeights += weights[i];
+    }
+    double weightMassToSelect = sumOfWeights * quantile;
+    int [] sortedIndices = Utils.sort(weights);
+
+    // Select the instances
+    sumOfWeights = 0;
+    for(int i = numInstances - 1; i >= 0; i--) {
+      Instance instance = (Instance)data.instance(sortedIndices[i]).copy();
+      trainData.add(instance);
+      sumOfWeights += weights[sortedIndices[i]];
+      if ((sumOfWeights > weightMassToSelect) && 
+	  (i > 0) && 
+	  (weights[sortedIndices[i]] != weights[sortedIndices[i - 1]])) {
+	break;
+      }
+    }
+    if (m_Debug) {
+      System.err.println("Selected " + trainData.numInstances()
+			 + " out of " + numInstances);
+    }
+    return trainData;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+	"\tPercentage of weight mass to base training on.\n"
+	+"\t(default 100, reduce to around 90 speed up)",
+	"P", 1, "-P <num>"));
+    
+    newVector.addElement(new Option(
+	"\tUse resampling for boosting.",
+	"Q", 0, "-Q"));
+
+    newVector.addElement(new Option(
+	      "\tShrinkage parameter.\n"
+	      +"\t(default 1)",
+	      "H", 1, "-H <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  Percentage of weight mass to base training on.
+   *  (default 100, reduce to around 90 speed up)</pre>
+   * 
+   * <pre> -Q
+   *  Use resampling for boosting.</pre>
+   * 
+   * <pre> -H &lt;num&gt;
+   *  Shrinkage parameter.
+   *  (default 1)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.DecisionStump)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.DecisionStump:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated classifier.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String thresholdString = Utils.getOption('P', options);
+    if (thresholdString.length() != 0) {
+      setWeightThreshold(Integer.parseInt(thresholdString));
+    } else {
+      setWeightThreshold(100);
+    }
+
+    String shrinkageString = Utils.getOption('H', options);
+    if (shrinkageString.length() != 0) {
+      setShrinkage(new Double(shrinkageString).
+	doubleValue());
+    } else {
+      setShrinkage(1.0);
+    }
+      
+    setUseResampling(Utils.getFlag('Q', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+
+    if (getUseResampling())
+      result.add("-Q");
+
+    result.add("-P");
+    result.add("" + getWeightThreshold());
+
+    result.add("-H");
+    result.add("" + getShrinkage());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String shrinkageTipText() {
+    return "Shrinkage parameter (use small value like 0.1 to reduce "
+      + "overfitting).";
+  }
+			 
+  /**
+   * Get the value of Shrinkage.
+   *
+   * @return Value of Shrinkage.
+   */
+  public double getShrinkage() {
+    
+    return m_Shrinkage;
+  }
+  
+  /**
+   * Set the value of Shrinkage.
+   *
+   * @param newShrinkage Value to assign to Shrinkage.
+   */
+  public void setShrinkage(double newShrinkage) {
+    
+    m_Shrinkage = newShrinkage;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightThresholdTipText() {
+    return "Weight threshold for weight pruning.";
+  }
+
+  /**
+   * Set weight threshold
+   *
+   * @param threshold the percentage of weight mass used for training
+   */
+  public void setWeightThreshold(int threshold) {
+
+    m_WeightThreshold = threshold;
+  }
+
+  /**
+   * Get the degree of weight thresholding
+   *
+   * @return the percentage of weight mass used for training
+   */
+  public int getWeightThreshold() {
+
+    return m_WeightThreshold;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useResamplingTipText() {
+    return "Whether resampling is used instead of reweighting.";
+  }
+
+  /**
+   * Set resampling mode
+   *
+   * @param r true if resampling should be done
+   */
+  public void setUseResampling(boolean r) {
+
+    m_UseResampling = r;
+  }
+
+  /**
+   * Get whether resampling is turned on
+   *
+   * @return true if resampling output is on
+   */
+  public boolean getUseResampling() {
+
+    return m_UseResampling;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    if (super.getCapabilities().handles(Capability.BINARY_CLASS))
+      result.enable(Capability.BINARY_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Boosting method.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+
+  public void buildClassifier(Instances data) throws Exception {
+
+    super.buildClassifier(data);
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    m_SumOfWeights = data.sumOfWeights();
+
+    if ((!m_UseResampling) && 
+	(m_Classifier instanceof WeightedInstancesHandler)) {
+      buildClassifierWithWeights(data);
+    } else {
+      buildClassifierUsingResampling(data);
+    }
+  }
+
+  /**
+   * Boosting method. Boosts using resampling
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  protected void buildClassifierUsingResampling(Instances data) 
+    throws Exception {
+
+    Instances trainData, sample, training, trainingWeightsNotNormalized;
+    double sumProbs;
+    int numInstances = data.numInstances();
+    Random randomInstance = new Random(m_Seed);
+    double minLoss = Double.MAX_VALUE;
+
+    // Create a copy of the data so that when the weights are diddled
+    // with it doesn't mess up the weights for anyone else
+    trainingWeightsNotNormalized = new Instances(data, 0, numInstances);
+    
+    // Do boostrap iterations
+    for (m_NumIterationsPerformed = -1; m_NumIterationsPerformed < m_Classifiers.length; 
+	 m_NumIterationsPerformed++) {
+      if (m_Debug) {
+	System.err.println("Training classifier " + (m_NumIterationsPerformed + 1));
+      }
+
+      training = new Instances(trainingWeightsNotNormalized);
+      normalizeWeights(training, 1.0);
+
+      // Select instances to train the classifier on
+      if (m_WeightThreshold < 100) {
+	trainData = selectWeightQuantile(training, 
+					 (double)m_WeightThreshold / 100);
+      } else {
+	trainData = new Instances(training);
+      }
+      
+      // Resample
+      double[] weights = new double[trainData.numInstances()];
+      for (int i = 0; i < weights.length; i++) {
+	weights[i] = trainData.instance(i).weight();
+      }
+
+      sample = trainData.resampleWithWeights(randomInstance, weights);
+      
+      // Build classifier
+      if (m_NumIterationsPerformed == -1) {
+        m_ZeroR = new weka.classifiers.rules.ZeroR();
+        m_ZeroR.buildClassifier(data);
+      } else {
+        m_Classifiers[m_NumIterationsPerformed].buildClassifier(sample);
+      }
+ 
+      // Update instance weights
+      setWeights(trainingWeightsNotNormalized, m_NumIterationsPerformed);
+
+      // Has progress been made?
+      double loss = 0;
+      for (Instance inst : trainingWeightsNotNormalized) {
+        loss += Math.log(inst.weight());
+      }
+      if (m_Debug) {
+        System.err.println("Current loss on log scale: " + loss);
+      }
+      if ((m_NumIterationsPerformed > -1) && (loss > minLoss)) {
+        if (m_Debug) {
+          System.err.println("Loss has increased: bailing out.");
+        }
+        break;
+      }
+      minLoss = loss;
+    }
+  }
+
+  /**
+   * Sets the weights for the next iteration.
+   * 
+   * @param training the training instances
+   * @throws Exception if something goes wrong
+   */
+  protected void setWeights(Instances training, int iteration) 
+    throws Exception {
+
+    for (Instance instance: training) {
+      double reweight = 1;
+      double prob = 1, shrinkage = m_Shrinkage;
+
+      if (iteration == -1) {
+        prob = m_ZeroR.distributionForInstance(instance)[0]; 
+        shrinkage = 1.0;
+      } else {
+        prob = m_Classifiers[iteration].distributionForInstance(instance)[0]; 
+
+        // Make sure that probabilities are never 0 or 1 using ad-hoc smoothing
+        prob = (m_SumOfWeights * prob + 1) / (m_SumOfWeights + 2);
+      }
+
+      if (instance.classValue() == 1) {
+        reweight = shrinkage * 0.5 * (Math.log(prob) - Math.log(1 - prob));
+      } else {
+        reweight = shrinkage * 0.5 * (Math.log(1 - prob) - Math.log(prob));
+      }
+      instance.setWeight(instance.weight() * Math.exp(reweight));
+    }
+  }
+
+  /**
+   * Normalize the weights for the next iteration.
+   * 
+   * @param training the training instances
+   * @throws Exception if something goes wrong
+   */
+  protected void normalizeWeights(Instances training, double oldSumOfWeights) 
+    throws Exception {
+
+    // Renormalize weights
+    double newSumOfWeights = training.sumOfWeights();
+    for (Instance instance: training) {
+      instance.setWeight(instance.weight() * oldSumOfWeights / newSumOfWeights);
+    }
+  }
+
+  /**
+   * Boosting method. Boosts any classifier that can handle weighted
+   * instances.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  protected void buildClassifierWithWeights(Instances data) 
+    throws Exception {
+
+    Instances trainData, training, trainingWeightsNotNormalized;
+    int numInstances = data.numInstances();
+    Random randomInstance = new Random(m_Seed);
+    double minLoss = Double.MAX_VALUE;
+
+    // Create a copy of the data so that when the weights are diddled
+    // with it doesn't mess up the weights for anyone else
+    trainingWeightsNotNormalized = new Instances(data, 0, numInstances);
+    
+    // Do boostrap iterations
+    for (m_NumIterationsPerformed = -1; m_NumIterationsPerformed < m_Classifiers.length; 
+	 m_NumIterationsPerformed++) {
+      if (m_Debug) {
+	System.err.println("Training classifier " + (m_NumIterationsPerformed + 1));
+      }
+
+      training = new Instances(trainingWeightsNotNormalized);
+      normalizeWeights(training, m_SumOfWeights);
+
+      // Select instances to train the classifier on
+      if (m_WeightThreshold < 100) {
+	trainData = selectWeightQuantile(training, 
+					 (double)m_WeightThreshold / 100);
+      } else {
+	trainData = new Instances(training, 0, numInstances);
+      }
+
+      // Build classifier
+      if (m_NumIterationsPerformed == -1) {
+        m_ZeroR = new weka.classifiers.rules.ZeroR();
+        m_ZeroR.buildClassifier(data);
+      } else {
+        if (m_Classifiers[m_NumIterationsPerformed] instanceof Randomizable)
+          ((Randomizable) m_Classifiers[m_NumIterationsPerformed]).setSeed(randomInstance.nextInt());
+        m_Classifiers[m_NumIterationsPerformed].buildClassifier(trainData);
+      }
+
+ 
+      // Update instance weights
+      setWeights(trainingWeightsNotNormalized, m_NumIterationsPerformed);
+
+      // Has progress been made?
+      double loss = 0;
+      for (Instance inst : trainingWeightsNotNormalized) {
+        loss += Math.log(inst.weight());
+      }
+      if (m_Debug) {
+        System.err.println("Current loss on log scale: " + loss);
+      }
+      if ((m_NumIterationsPerformed > -1) && (loss > minLoss)) {
+        if (m_Debug) {
+          System.err.println("Loss has increased: bailing out.");
+        }
+        break;
+      }
+      minLoss = loss;
+    }
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+
+    double [] sums = new double [instance.numClasses()]; 
+    for (int i = -1; i < m_NumIterationsPerformed; i++) {
+      double prob = 1, shrinkage = m_Shrinkage;
+      if (i == -1) {
+        prob = m_ZeroR.distributionForInstance(instance)[0]; 
+        shrinkage = 1.0;
+      } else {
+        prob = m_Classifiers[i].distributionForInstance(instance)[0]; 
+        
+        // Make sure that probabilities are never 0 or 1 using ad-hoc smoothing
+        prob = (m_SumOfWeights * prob + 1) / (m_SumOfWeights + 2);
+      }
+      sums[0] += shrinkage * 0.5 * (Math.log(prob) - Math.log(1 - prob));
+    }
+    sums[1] = -sums[0];
+    return Utils.logs2probs(sums);
+  }
+
+  /**
+   * Returns description of the boosted classifier.
+   *
+   * @return description of the boosted classifier as a string
+   */
+  public String toString() {
+    
+    StringBuffer text = new StringBuffer();
+
+    if (m_ZeroR == null) {
+      text.append("No model built yet.\n\n");
+    } else {
+      text.append("RealAdaBoost: Base classifiers: \n\n");
+      text.append(m_ZeroR.toString() + "\n\n");    
+      for (int i = 0; i < m_NumIterationsPerformed ; i++) {
+        text.append(m_Classifiers[i].toString() + "\n\n");
+      }
+      text.append("Number of performed Iterations: " 
+                  + m_NumIterationsPerformed + "\n");
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6136 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new RealAdaBoost(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RegressionByDiscretization.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RegressionByDiscretization.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RegressionByDiscretization.java	(revision 29)
@@ -0,0 +1,809 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RegressionByDiscretization.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.classifiers.IntervalEstimator;
+import weka.classifiers.ConditionalDensityEstimator;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.Tag;
+import weka.core.SelectedTag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Discretize;
+
+import weka.estimators.UnivariateDensityEstimator;
+import weka.estimators.UnivariateIntervalEstimator;
+import weka.estimators.UnivariateQuantileEstimator;
+import weka.estimators.UnivariateEqualFrequencyHistogramEstimator;
+import weka.estimators.UnivariateKernelEstimator;
+import weka.estimators.UnivariateNormalEstimator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A regression scheme that employs any classifier on a copy of the data that has the class attribute (equal-width) discretized. The predicted value is the expected value of the mean class value for each discretized interval (based on the predicted probabilities for each interval).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;int&gt;
+ *  Number of bins for equal-width discretization
+ *  (default 10).
+ * </pre>
+ * 
+ * <pre> -E
+ *  Whether to delete empty bins after discretization
+ *  (default false).
+ * </pre>
+ * 
+ * <pre> -F
+ *  Use equal-frequency instead of equal-width discretization.</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5925 $
+ */
+public class RegressionByDiscretization 
+  extends SingleClassifierEnhancer implements IntervalEstimator, ConditionalDensityEstimator {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5066426153134050378L;
+  
+  /** The discretization filter. */
+  protected Discretize m_Discretizer = new Discretize();
+
+  /** The number of discretization intervals. */
+  protected int m_NumBins = 10;
+
+  /** The mean values for each Discretized class interval. */
+  protected double [] m_ClassMeans;
+
+  /** The class counts for each Discretized class interval. */
+  protected int [] m_ClassCounts;
+
+  /** Whether to delete empty intervals. */
+  protected boolean m_DeleteEmptyBins;
+
+  /** Header of discretized data. */
+  protected Instances m_DiscretizedHeader = null;
+
+  /** Use equal-frequency binning */
+  protected boolean m_UseEqualFrequency = false;
+
+  /** Whether to minimize absolute error, rather than squared error. */
+  protected boolean m_MinimizeAbsoluteError = false;
+
+  /** Use histogram estimator */
+  public static final int ESTIMATOR_HISTOGRAM = 0;
+  /** filter: Standardize training data */
+  public static final int ESTIMATOR_KERNEL = 1;
+  /** filter: No normalization/standardization */
+  public static final int ESTIMATOR_NORMAL = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_ESTIMATOR = {
+    new Tag(ESTIMATOR_HISTOGRAM, "Histogram density estimator"),
+    new Tag(ESTIMATOR_KERNEL, "Kernel density estimator"),
+    new Tag(ESTIMATOR_NORMAL, "Normal density estimator"),
+  };
+
+  /** Which estimator to use (default: histogram) */
+  protected int m_estimatorType = ESTIMATOR_HISTOGRAM;
+
+  /** The original target values in the training data */
+  protected double[] m_OriginalTargetValues = null;
+
+  /** The converted target values in the training data */
+  protected int[] m_NewTargetValues = null;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A regression scheme that employs any "
+      + "classifier on a copy of the data that has the class attribute "
+      + "discretized. The predicted value is the expected value of the "
+      + "mean class value for each discretized interval (based on the "
+      + "predicted probabilities for each interval). This class now "
+      + "also supports conditional density estimation by building "
+      + "a univariate density estimator from the target values in "
+      + "the training data, weighted by the class probabilities. \n\n"
+      + "For more information on this process, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Eibe Frank and Remco R. Bouckaert");
+    result.setValue(Field.TITLE, "Conditional Density Estimation with Class Probability Estimators");
+    result.setValue(Field.BOOKTITLE, "First Asian Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2009");
+    result.setValue(Field.PAGES, "65-81");
+    result.setValue(Field.PUBLISHER, "Springer Verlag");
+    result.setValue(Field.ADDRESS, "Berlin");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Default constructor.
+   */
+  public RegressionByDiscretization() {
+
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    
+    result.setMinimumNumberInstances(2);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // Discretize the training data
+    m_Discretizer.setIgnoreClass(true);
+    m_Discretizer.setAttributeIndices("" + (instances.classIndex() + 1));
+    m_Discretizer.setBins(getNumBins());
+    m_Discretizer.setUseEqualFrequency(getUseEqualFrequency());
+    m_Discretizer.setInputFormat(instances);
+    Instances newTrain = Filter.useFilter(instances, m_Discretizer);
+
+    // Should empty bins be deleted?
+    if (m_DeleteEmptyBins) {
+
+      // Figure out which classes are empty after discretization
+      int numNonEmptyClasses = 0;
+      boolean[] notEmptyClass = new boolean[newTrain.numClasses()];
+      for (int i = 0; i < newTrain.numInstances(); i++) {
+        if (!notEmptyClass[(int)newTrain.instance(i).classValue()]) {
+          numNonEmptyClasses++;
+          notEmptyClass[(int)newTrain.instance(i).classValue()] = true;
+        }
+      }
+      
+      // Compute new list of non-empty classes and mapping of indices
+      FastVector newClassVals = new FastVector(numNonEmptyClasses);
+      int[] oldIndexToNewIndex = new int[newTrain.numClasses()];
+      for (int i = 0; i < newTrain.numClasses(); i++) {
+        if (notEmptyClass[i]) {
+         oldIndexToNewIndex[i] = newClassVals.size();
+          newClassVals.addElement(newTrain.classAttribute().value(i));
+        }
+      }
+      
+      // Compute new header information
+      Attribute newClass = new Attribute(newTrain.classAttribute().name(), 
+                                         newClassVals);
+      FastVector newAttributes = new FastVector(newTrain.numAttributes());
+      for (int i = 0; i < newTrain.numAttributes(); i++) {
+        if (i != newTrain.classIndex()) {
+          newAttributes.addElement(newTrain.attribute(i).copy());
+        } else {
+          newAttributes.addElement(newClass);
+        }
+      }
+      
+      // Create new header and modify instances
+      Instances newTrainTransformed = new Instances(newTrain.relationName(), 
+                                                    newAttributes,
+                                                    newTrain.numInstances());
+      newTrainTransformed.setClassIndex(newTrain.classIndex());
+      for (int i = 0; i < newTrain.numInstances(); i++) {
+        Instance inst = newTrain.instance(i);
+        newTrainTransformed.add(inst);
+        newTrainTransformed.lastInstance().
+          setClassValue(oldIndexToNewIndex[(int)inst.classValue()]);
+      }
+      newTrain = newTrainTransformed;
+    }
+
+    // Store target values, in case a prediction interval or computation of median is required
+    m_OriginalTargetValues = new double[instances.numInstances()];
+    m_NewTargetValues = new int[instances.numInstances()];
+    for (int i = 0; i < m_OriginalTargetValues.length; i++) {
+      m_OriginalTargetValues[i] = instances.instance(i).classValue();
+      m_NewTargetValues[i] = (int)newTrain.instance(i).classValue();
+    }
+
+    m_DiscretizedHeader = new Instances(newTrain, 0);
+
+    int numClasses = newTrain.numClasses();
+
+    // Calculate the mean value for each bin of the new class attribute
+    m_ClassMeans = new double [numClasses];
+    m_ClassCounts = new int [numClasses];
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Instance inst = newTrain.instance(i);
+      if (!inst.classIsMissing()) {
+	int classVal = (int) inst.classValue();
+	m_ClassCounts[classVal]++;
+	m_ClassMeans[classVal] += instances.instance(i).classValue();
+      }
+    }
+
+    for (int i = 0; i < numClasses; i++) {
+      if (m_ClassCounts[i] > 0) {
+	m_ClassMeans[i] /= m_ClassCounts[i];
+      }
+    }
+
+    if (m_Debug) {
+      System.out.println("Bin Means");
+      System.out.println("==========");
+      for (int i = 0; i < m_ClassMeans.length; i++) {
+	System.out.println(m_ClassMeans[i]);
+      }
+      System.out.println();
+    }
+
+    // Train the sub-classifier
+    m_Classifier.buildClassifier(newTrain);
+  }
+
+  /**
+   * Get density estimator for given instance.
+   * 
+   * @param inst the instance
+   * @return the univariate density estimator
+   * @exception Exception if the estimator can't be computed
+   */
+  protected UnivariateDensityEstimator getDensityEstimator(Instance instance, boolean print) throws Exception {
+
+    // Initialize estimator
+    UnivariateDensityEstimator e;
+    
+    if (m_estimatorType == ESTIMATOR_KERNEL) {
+      e = new UnivariateKernelEstimator();
+    } else if (m_estimatorType == ESTIMATOR_NORMAL) {
+      e = new UnivariateNormalEstimator();
+    } else {
+      e = new UnivariateEqualFrequencyHistogramEstimator();
+
+      // Set the number of bins appropriately
+      ((UnivariateEqualFrequencyHistogramEstimator)e).setNumBins(getNumBins());
+
+      // Initialize boundaries of equal frequency estimator
+      for (int i = 0; i < m_OriginalTargetValues.length; i++) {
+        e.addValue(m_OriginalTargetValues[i], 1.0);
+      }
+      
+      // Construct estimator, then initialize statistics, so that only boundaries will be kept
+      ((UnivariateEqualFrequencyHistogramEstimator)e).initializeStatistics();
+
+      // Now that boundaries have been determined, we only need to update the bin weights
+      ((UnivariateEqualFrequencyHistogramEstimator)e).setUpdateWeightsOnly(true);      
+    }
+
+    // Make sure structure of class attribute correct
+    Instance newInstance = (Instance)instance.copy();
+    newInstance.setDataset(m_DiscretizedHeader);
+    double [] probs = m_Classifier.distributionForInstance(newInstance);
+
+    // Add values to estimator
+    for (int i = 0; i < m_OriginalTargetValues.length; i++) {
+      e.addValue(m_OriginalTargetValues[i], probs[m_NewTargetValues[i]] * 
+                 m_OriginalTargetValues.length / m_ClassCounts[m_NewTargetValues[i]]);
+    }
+
+    // Return estimator
+    return e;
+  }
+  
+  /**
+   * Returns an N * 2 array, where N is the number of prediction
+   * intervals. In each row, the first element contains the lower
+   * boundary of the corresponding prediction interval and the second
+   * element the upper boundary.
+   *
+   * @param inst the instance to make the prediction for.
+   * @param confidenceLevel the percentage of cases that the interval should cover.
+   * @return an array of prediction intervals
+   * @exception Exception if the intervals can't be computed
+   */
+  public double[][] predictIntervals(Instance instance, double confidenceLevel) throws Exception {
+    
+    // Get density estimator
+    UnivariateIntervalEstimator e = (UnivariateIntervalEstimator)getDensityEstimator(instance, false);
+
+    // Return intervals
+    return e.predictIntervals(confidenceLevel);
+  }
+
+  /**
+   * Returns natural logarithm of density estimate for given value based on given instance.
+   *
+   * @param inst the instance to make the prediction for.
+   * @param the value to make the prediction for.
+   * @return the natural logarithm of the density estimate
+   * @exception Exception if the intervals can't be computed
+   */
+  public double logDensity(Instance instance, double value) throws Exception {
+    
+    // Get density estimator
+    UnivariateDensityEstimator e = getDensityEstimator(instance, true);
+
+    // Return estimate
+    return e.logDensity(value);
+  }
+
+  /**
+   * Returns a predicted class for the test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class value
+   * @throws Exception if the prediction couldn't be made
+   */
+  public double classifyInstance(Instance instance) throws Exception {  
+
+    // Make sure structure of class attribute correct
+    Instance newInstance = (Instance)instance.copy();
+    newInstance.setDataset(m_DiscretizedHeader);
+    double [] probs = m_Classifier.distributionForInstance(newInstance);
+
+    if (!m_MinimizeAbsoluteError) {
+
+      // Compute actual prediction
+      double prediction = 0, probSum = 0;
+      for (int j = 0; j < probs.length; j++) {
+        prediction += probs[j] * m_ClassMeans[j];
+        probSum += probs[j];
+      }
+      
+      return prediction /  probSum;
+    } else {
+    
+      // Get density estimator
+      UnivariateQuantileEstimator e = (UnivariateQuantileEstimator)getDensityEstimator(instance, true);
+      
+      // Return estimate
+      return e.predictQuantile(0.5);
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+	      "\tNumber of bins for equal-width discretization\n"
+	      + "\t(default 10).\n",
+	      "B", 1, "-B <int>"));
+
+    newVector.addElement(new Option(
+	      "\tWhether to delete empty bins after discretization\n"
+	      + "\t(default false).\n",
+	      "E", 0, "-E"));
+
+    newVector.addElement(new Option(
+	      "\tWhether to minimize absolute error, rather than squared error.\n"
+	      + "\t(default false).\n",
+	      "A", 0, "-A"));
+    
+    newVector.addElement(new Option(
+	     "\tUse equal-frequency instead of equal-width discretization.",
+	     "F", 0, "-F"));
+    
+    newVector.addElement(new Option(
+	     "\tWhat type of density estimator to use: 0=histogram/1=kernel/2=normal (default: 0).",
+	     "K", 1, "-K"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String binsString = Utils.getOption('B', options);
+    if (binsString.length() != 0) {
+      setNumBins(Integer.parseInt(binsString));
+    } else {
+      setNumBins(10);
+    }
+
+    setDeleteEmptyBins(Utils.getFlag('E', options));
+    setUseEqualFrequency(Utils.getFlag('F', options));
+    setMinimizeAbsoluteError(Utils.getFlag('A', options));
+
+    String tmpStr = Utils.getOption('K', options);
+    if (tmpStr.length() != 0)
+      setEstimatorType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_ESTIMATOR));
+    else
+      setEstimatorType(new SelectedTag(ESTIMATOR_HISTOGRAM, TAGS_ESTIMATOR));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 7];
+    int current = 0;
+
+    options[current++] = "-B";
+    options[current++] = "" + getNumBins();
+
+    if (getDeleteEmptyBins()) {
+      options[current++] = "-E";
+    }
+    
+    if (getUseEqualFrequency()) {
+      options[current++] = "-F";
+    }
+
+    if (getMinimizeAbsoluteError()) {
+      options[current++] = "-A";
+    }
+    
+    options[current++] = "-K";
+    options[current++] = "" + m_estimatorType;
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numBinsTipText() {
+
+    return "Number of bins for discretization.";
+  }
+
+  /**
+   * Gets the number of bins numeric attributes will be divided into
+   *
+   * @return the number of bins.
+   */
+  public int getNumBins() {
+
+    return m_NumBins;
+  }
+
+  /**
+   * Sets the number of bins to divide each selected numeric attribute into
+   *
+   * @param numBins the number of bins
+   */
+  public void setNumBins(int numBins) {
+
+    m_NumBins = numBins;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String deleteEmptyBinsTipText() {
+
+    return "Whether to delete empty bins after discretization.";
+  }
+
+
+  /**
+   * Gets whether empty bins are deleted.
+   *
+   * @return true if empty bins get deleted.
+   */
+  public boolean getDeleteEmptyBins() {
+
+    return m_DeleteEmptyBins;
+  }
+
+  /**
+   * Sets whether to delete empty bins.
+   *
+   * @param b if true, empty bins will be deleted
+   */
+  public void setDeleteEmptyBins(boolean b) {
+
+    m_DeleteEmptyBins = b;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minimizeAbsoluteErrorTipText() {
+
+    return "Whether to minimize absolute error.";
+  }
+
+
+  /**
+   * Gets whether to min. abs. error
+   *
+   * @return true if abs. err. is to be minimized
+   */
+  public boolean getMinimizeAbsoluteError() {
+
+    return m_MinimizeAbsoluteError;
+  }
+
+  /**
+   * Sets whether to min. abs. error.
+   *
+   * @param b if true, abs. err. is minimized
+   */
+  public void setMinimizeAbsoluteError(boolean b) {
+
+    m_MinimizeAbsoluteError = b;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useEqualFrequencyTipText() {
+
+    return "If set to true, equal-frequency binning will be used instead of" +
+      " equal-width binning.";
+  }
+  
+  /**
+   * Get the value of UseEqualFrequency.
+   *
+   * @return Value of UseEqualFrequency.
+   */
+  public boolean getUseEqualFrequency() {
+    
+    return m_UseEqualFrequency;
+  }
+  
+  /**
+   * Set the value of UseEqualFrequency.
+   *
+   * @param newUseEqualFrequency Value to assign to UseEqualFrequency.
+   */
+  public void setUseEqualFrequency(boolean newUseEqualFrequency) {
+    
+    m_UseEqualFrequency = newUseEqualFrequency;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String estimatorTypeTipText() {
+
+    return "The density estimator to use.";
+  }
+  
+  /**
+   * Get the estimator type
+   *
+   * @return the estimator type
+   */
+  public  SelectedTag getEstimatorType() {
+    
+    return new SelectedTag(m_estimatorType, TAGS_ESTIMATOR);
+  }
+  
+  /**
+   * Set the estimator
+   *
+   * @param newEstimator the estimator to use
+   */
+  public void setEstimatorType(SelectedTag newEstimator) {
+        
+    if (newEstimator.getTags() == TAGS_ESTIMATOR) {
+      m_estimatorType = newEstimator.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    text.append("Regression by discretization");
+    if (m_ClassMeans == null) {
+      text.append(": No model built yet.");
+    } else {
+      text.append("\n\nClass attribute discretized into " 
+		  + m_ClassMeans.length + " values\n");
+
+      text.append("\nClassifier spec: " + getClassifierSpec() 
+		  + "\n");
+      text.append(m_Classifier.toString());
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5925 $");
+  }
+ 
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new RegressionByDiscretization(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/RotationForest.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/RotationForest.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/RotationForest.java	(revision 29)
@@ -0,0 +1,1226 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RotationForest.java
+ *    Copyright (C) 2008 Juan Jose Rodriguez
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.classifiers.meta;
+
+import weka.classifiers.RandomizableParallelIteratedSingleClassifierEnhancer;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.PrincipalComponents;
+import weka.filters.unsupervised.attribute.RemoveUseless;
+import weka.filters.unsupervised.instance.RemovePercentage;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for construction a Rotation Forest. Can do classification and regression depending on the base learner. <br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Juan J. Rodriguez, Ludmila I. Kuncheva, Carlos J. Alonso (2006). Rotation Forest: A new classifier ensemble method. IEEE Transactions on Pattern Analysis and Machine Intelligence. 28(10):1619-1630. URL http://doi.ieeecomputersociety.org/10.1109/TPAMI.2006.211.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Rodriguez2006,
+ *    author = {Juan J. Rodriguez and Ludmila I. Kuncheva and Carlos J. Alonso},
+ *    journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence},
+ *    number = {10},
+ *    pages = {1619-1630},
+ *    title = {Rotation Forest: A new classifier ensemble method},
+ *    volume = {28},
+ *    year = {2006},
+ *    ISSN = {0162-8828},
+ *    URL = {http://doi.ieeecomputersociety.org/10.1109/TPAMI.2006.211}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Whether minGroup (-G) and maxGroup (-H) refer to
+ *  the number of groups or their size.
+ *  (default: false)</pre>
+ * 
+ * <pre> -G &lt;num&gt;
+ *  Minimum size of a group of attributes:
+ *   if numberOfGroups is true, the minimum number
+ *   of groups.
+ *   (default: 3)</pre>
+ * 
+ * <pre> -H &lt;num&gt;
+ *  Maximum size of a group of attributes:
+ *   if numberOfGroups is true, the maximum number
+ *   of groups.
+ *   (default: 3)</pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  Percentage of instances to be removed.
+ *   (default: 50)</pre>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.unsupervised.attribute.PrincipalComponents-R 1.0"</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Number of iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Juan Jose Rodriguez (jjrodriguez@ubu.es)
+ * @version $Revision: 5987 $
+ */
+public class RotationForest 
+  extends RandomizableParallelIteratedSingleClassifierEnhancer
+  implements WeightedInstancesHandler, TechnicalInformationHandler {
+  // It implements WeightedInstancesHandler because the base classifier 
+  // can implement this interface, but in this method the weights are
+  // not used
+
+  /** for serialization */
+  static final long serialVersionUID = -3255631880798499936L;
+
+  /** The minimum size of a group */
+  protected int m_MinGroup = 3;
+
+  /** The maximum size of a group */
+  protected int m_MaxGroup = 3;
+
+  /** 
+   * Whether minGroup and maxGroup refer to the number of groups or their 
+   * size */
+  protected boolean m_NumberOfGroups = false;
+
+  /** The percentage of instances to be removed */
+  protected int m_RemovedPercentage = 50;
+
+  /** The attributes of each group */
+  protected int [][][] m_Groups = null;
+
+  /** The type of projection filter */
+  protected Filter m_ProjectionFilter = null;
+
+  /** The projection filters */
+  protected Filter [][] m_ProjectionFilters = null;
+
+  /** Headers of the transformed dataset */
+  protected Instances [] m_Headers = null;
+
+  /** Headers of the reduced datasets */
+  protected Instances [][] m_ReducedHeaders = null;
+
+  /** Filter that remove useless attributes */
+  protected RemoveUseless m_RemoveUseless = null;
+
+  /** Filter that normalized the attributes */
+  protected Normalize m_Normalize = null;
+  
+  /** Training data */
+  protected Instances m_data;
+
+  protected Instances [] m_instancesOfClasses;
+
+  protected Random m_random;
+
+  /**
+   * Constructor.
+   */
+  public RotationForest() {
+    
+    m_Classifier = new weka.classifiers.trees.J48();
+    m_ProjectionFilter = defaultFilter();
+  }
+
+  /**
+   * Default projection method.
+   */
+  protected Filter defaultFilter() {
+    PrincipalComponents filter = new PrincipalComponents();
+    filter.setNormalize(false);
+    filter.setVarianceCovered(1.0);
+    return filter;
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+ 
+    return "Class for construction a Rotation Forest. Can do classification "
+      + "and regression depending on the base learner. \n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Juan J. Rodriguez and Ludmila I. Kuncheva and Carlos J. Alonso");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "Rotation Forest: A new classifier ensemble method");
+    result.setValue(Field.JOURNAL, "IEEE Transactions on Pattern Analysis and Machine Intelligence");
+    result.setValue(Field.VOLUME, "28");
+    result.setValue(Field.NUMBER, "10");
+    result.setValue(Field.PAGES, "1619-1630");
+    result.setValue(Field.ISSN, "0162-8828");
+    result.setValue(Field.URL, "http://doi.ieeecomputersociety.org/10.1109/TPAMI.2006.211");
+    
+    return result;
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+              "\tWhether minGroup (-G) and maxGroup (-H) refer to"
+              + "\n\tthe number of groups or their size."
+              + "\n\t(default: false)",
+              "N", 0, "-N"));
+
+    newVector.addElement(new Option(
+              "\tMinimum size of a group of attributes:"
+              + "\n\t\tif numberOfGroups is true, the minimum number"
+              + "\n\t\tof groups."
+              + "\n\t\t(default: 3)",
+              "G", 1, "-G <num>"));
+
+    newVector.addElement(new Option(
+              "\tMaximum size of a group of attributes:"
+              + "\n\t\tif numberOfGroups is true, the maximum number" 
+              + "\n\t\tof groups."
+              + "\n\t\t(default: 3)",
+              "H", 1, "-H <num>"));
+
+    newVector.addElement(new Option(
+              "\tPercentage of instances to be removed."
+              + "\n\t\t(default: 50)",
+              "P", 1, "-P <num>"));
+
+    newVector.addElement(new Option(
+	      "\tFull class name of filter to use, followed\n"
+	      + "\tby filter options.\n"
+	      + "\teg: \"weka.filters.unsupervised.attribute.PrincipalComponents-R 1.0\"",
+	      "F", 1, "-F <filter specification>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Whether minGroup (-G) and maxGroup (-H) refer to
+   *  the number of groups or their size.
+   *  (default: false)</pre>
+   * 
+   * <pre> -G &lt;num&gt;
+   *  Minimum size of a group of attributes:
+   *   if numberOfGroups is true, the minimum number
+   *   of groups.
+   *   (default: 3)</pre>
+   * 
+   * <pre> -H &lt;num&gt;
+   *  Maximum size of a group of attributes:
+   *   if numberOfGroups is true, the maximum number
+   *   of groups.
+   *   (default: 3)</pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  Percentage of instances to be removed.
+   *   (default: 50)</pre>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.unsupervised.attribute.PrincipalComponents-R 1.0"</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Number of iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.trees.J48)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.trees.J48:
+   * </pre>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    /* Taken from FilteredClassifier */
+    String filterString = Utils.getOption('F', options);
+    if (filterString.length() > 0) {
+      String [] filterSpec = Utils.splitOptions(filterString);
+      if (filterSpec.length == 0) {
+	throw new IllegalArgumentException("Invalid filter specification string");
+      }
+      String filterName = filterSpec[0];
+      filterSpec[0] = "";
+      setProjectionFilter((Filter) Utils.forName(Filter.class, filterName, filterSpec));
+    } else {
+      setProjectionFilter(defaultFilter());
+    }
+
+    String tmpStr;
+    
+    tmpStr = Utils.getOption('G', options);
+    if (tmpStr.length() != 0)
+      setMinGroup(Integer.parseInt(tmpStr));
+    else
+      setMinGroup(3);
+
+    tmpStr = Utils.getOption('H', options);
+    if (tmpStr.length() != 0)
+      setMaxGroup(Integer.parseInt(tmpStr));
+    else
+      setMaxGroup(3);
+
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setRemovedPercentage(Integer.parseInt(tmpStr));
+    else
+      setRemovedPercentage(50);
+
+    setNumberOfGroups(Utils.getFlag('N', options));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 9];
+
+    int current = 0;
+
+    if (getNumberOfGroups()) { 
+      options[current++] = "-N";
+    }
+
+    options[current++] = "-G"; 
+    options[current++] = "" + getMinGroup();
+
+    options[current++] = "-H"; 
+    options[current++] = "" + getMaxGroup();
+
+    options[current++] = "-P"; 
+    options[current++] = "" + getRemovedPercentage();
+
+    options[current++] = "-F";
+    options[current++] = getProjectionFilterSpec();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numberOfGroupsTipText() {
+    return "Whether minGroup and maxGroup refer to the number of groups or their size.";
+  }
+
+  /**
+   * Set whether minGroup and maxGroup refer to the number of groups or their 
+   * size
+   *
+   * @param numberOfGroups whether minGroup and maxGroup refer to the number 
+   * of groups or their size
+   */
+  public void setNumberOfGroups(boolean numberOfGroups) {
+
+    m_NumberOfGroups = numberOfGroups;
+  }
+
+  /**
+   * Get whether minGroup and maxGroup refer to the number of groups or their 
+   * size
+   *
+   * @return whether minGroup and maxGroup refer to the number of groups or 
+   * their size
+   */
+  public boolean getNumberOfGroups() {
+
+    return m_NumberOfGroups;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for displaying in the 
+   * explorer/experimenter gui
+   */
+  public String minGroupTipText() {
+    return "Minimum size of a group (if numberOfGrups is true, the minimum number of groups.";
+  }
+
+  /**
+   * Sets the minimum size of a group.
+   *
+   * @param minGroup the minimum value.
+   * of attributes.
+   */
+  public void setMinGroup( int minGroup ) throws IllegalArgumentException {
+
+    if( minGroup <= 0 )
+      throw new IllegalArgumentException( "MinGroup has to be positive." );
+    m_MinGroup = minGroup;
+  }
+
+  /**
+   * Gets the minimum size of a group.
+   *
+   * @return 		the minimum value.
+   */
+  public int getMinGroup() {
+    return m_MinGroup;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxGroupTipText() {
+    return "Maximum size of a group (if numberOfGrups is true, the maximum number of groups.";
+  }
+
+  /**
+   * Sets the maximum size of a group.
+   *
+   * @param maxGroup the maximum value.
+   * of attributes.
+   */
+  public void setMaxGroup( int maxGroup ) throws IllegalArgumentException {
+ 
+    if( maxGroup <= 0 )
+      throw new IllegalArgumentException( "MaxGroup has to be positive." );
+    m_MaxGroup = maxGroup;
+  }
+
+  /**
+   * Gets the maximum size of a group.
+   *
+   * @return 		the maximum value.
+   */
+  public int getMaxGroup() {
+    return m_MaxGroup;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String removedPercentageTipText() {
+    return "The percentage of instances to be removed.";
+  }
+
+  /**
+   * Sets the percentage of instance to be removed
+   *
+   * @param removedPercentage the percentage.
+   */
+  public void setRemovedPercentage( int removedPercentage ) throws IllegalArgumentException {
+
+    if( removedPercentage < 0 )
+      throw new IllegalArgumentException( "RemovedPercentage has to be >=0." );
+    if( removedPercentage >= 100 )
+      throw new IllegalArgumentException( "RemovedPercentage has to be <100." );
+ 
+    m_RemovedPercentage = removedPercentage;
+  }
+
+  /**
+   * Gets the percentage of instances to be removed
+   *
+   * @return 		the percentage.
+   */
+  public int getRemovedPercentage() {
+    return m_RemovedPercentage;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String projectionFilterTipText() {
+    return "The filter used to project the data (e.g., PrincipalComponents).";
+  }
+
+  /**
+   * Sets the filter used to project the data.
+   *
+   * @param projectionFilter the filter.
+   */
+  public void setProjectionFilter( Filter projectionFilter ) {
+
+    m_ProjectionFilter = projectionFilter;
+  }
+
+  /**
+   * Gets the filter used to project the data.
+   *
+   * @return 		the filter.
+   */
+  public Filter getProjectionFilter() {
+    return m_ProjectionFilter;
+  }
+
+  /**
+   * Gets the filter specification string, which contains the class name of
+   * the filter and any options to the filter
+   *
+   * @return the filter string.
+   */
+  /* Taken from FilteredClassifier */
+  protected String getProjectionFilterSpec() {
+    
+    Filter c = getProjectionFilter();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns description of the Rotation Forest classifier.
+   *
+   * @return description of the Rotation Forest classifier as a string
+   */
+  public String toString() {
+    
+    if (m_Classifiers == null) {
+      return "RotationForest: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("All the base classifiers: \n\n");
+    for (int i = 0; i < m_Classifiers.length; i++)
+      text.append(m_Classifiers[i].toString() + "\n\n");
+    
+    return text.toString();
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  protected class ClassifierWrapper extends weka.classifiers.AbstractClassifier {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 2327175798869994435L;
+    
+    protected weka.classifiers.Classifier m_wrappedClassifier;
+    protected int m_classifierNumber;
+    
+    public ClassifierWrapper(weka.classifiers.Classifier classifier, int classifierNumber) {
+      super();
+      
+      m_wrappedClassifier = classifier;
+      m_classifierNumber = classifierNumber;
+    }
+    
+    public void buildClassifier(Instances data) throws Exception {
+      m_ReducedHeaders[m_classifierNumber] = new Instances[ m_Groups[m_classifierNumber].length ];
+      FastVector transformedAttributes = new FastVector( m_data.numAttributes() );
+      
+      // Construction of the dataset for each group of attributes
+      for( int j = 0; j < m_Groups[ m_classifierNumber ].length; j++ ) {
+        FastVector fv = new FastVector( m_Groups[m_classifierNumber][j].length + 1 );
+        for( int k = 0; k < m_Groups[m_classifierNumber][j].length; k++ ) {
+          fv.addElement( m_data.attribute( m_Groups[m_classifierNumber][j][k] ).copy() );
+        }
+        fv.addElement( m_data.classAttribute( ).copy() );
+        Instances dataSubSet = new Instances( "rotated-" + m_classifierNumber + "-" + j + "-", 
+            fv, 0);
+        dataSubSet.setClassIndex( dataSubSet.numAttributes() - 1 );
+        
+        // Select instances for the dataset
+        m_ReducedHeaders[m_classifierNumber][j] = new Instances( dataSubSet, 0 );
+        boolean [] selectedClasses = selectClasses( m_instancesOfClasses.length, 
+              m_random );
+        for( int c = 0; c < selectedClasses.length; c++ ) {
+          if( !selectedClasses[c] )
+            continue;
+          Enumeration enu = m_instancesOfClasses[c].enumerateInstances();
+          while( enu.hasMoreElements() ) {
+            Instance instance = (Instance)enu.nextElement();
+            Instance newInstance = new DenseInstance(dataSubSet.numAttributes());
+            newInstance.setDataset( dataSubSet );
+            for( int k = 0; k < m_Groups[m_classifierNumber][j].length; k++ ) {
+              newInstance.setValue( k, instance.value( m_Groups[m_classifierNumber][j][k] ) );
+            }
+            newInstance.setClassValue( instance.classValue( ) );
+            dataSubSet.add( newInstance );
+          }
+        }
+        
+        dataSubSet.randomize(m_random);
+        // Remove a percentage of the instances
+        Instances originalDataSubSet = dataSubSet;
+        dataSubSet.randomize(m_random);
+        RemovePercentage rp = new RemovePercentage();
+        rp.setPercentage( m_RemovedPercentage );
+        rp.setInputFormat( dataSubSet );
+        dataSubSet = Filter.useFilter( dataSubSet, rp );
+        if( dataSubSet.numInstances() < 2 ) {
+          dataSubSet = originalDataSubSet;
+        }
+        
+        // Project de data
+        m_ProjectionFilters[m_classifierNumber][j].setInputFormat( dataSubSet );
+        Instances projectedData = null;
+        do {
+          try {
+            projectedData = Filter.useFilter( dataSubSet, 
+                m_ProjectionFilters[m_classifierNumber][j] );
+          } catch ( Exception e ) {
+            // The data could not be projected, we add some random instances
+            addRandomInstances( dataSubSet, 10, m_random );
+          }
+        } while( projectedData == null );
+
+        // Include the projected attributes in the attributes of the 
+        // transformed dataset
+        for( int a = 0; a < projectedData.numAttributes() - 1; a++ ) {
+          transformedAttributes.addElement( projectedData.attribute(a).copy());
+        }                        
+      }
+      
+      transformedAttributes.addElement( m_data.classAttribute().copy() );
+      Instances transformedData = new Instances( "rotated-" + m_classifierNumber + "-", 
+        transformedAttributes, 0 );
+      transformedData.setClassIndex( transformedData.numAttributes() - 1 );
+      m_Headers[ m_classifierNumber ] = new Instances( transformedData, 0 );
+
+      // Project all the training data
+      Enumeration enu = m_data.enumerateInstances();
+      while( enu.hasMoreElements() ) {
+        Instance instance = (Instance)enu.nextElement();
+        Instance newInstance = convertInstance( instance, m_classifierNumber );
+        transformedData.add( newInstance );
+      }
+
+      // Build the base classifier
+      if (m_wrappedClassifier instanceof Randomizable) {
+        ((Randomizable) m_wrappedClassifier).setSeed(m_random.nextInt());
+      }
+      m_wrappedClassifier.buildClassifier( transformedData );            
+    }
+    
+    public double classifierInstance(Instance instance) throws Exception {
+      return m_wrappedClassifier.classifyInstance(instance);
+    }
+    
+    public double[] distributionForInstance(Instance instance) throws Exception {
+      return m_wrappedClassifier.distributionForInstance(instance);
+    }
+    
+    public String toString() {
+      return m_wrappedClassifier.toString();
+    }
+  }
+  
+  protected Instances getTrainingSet(int iteration) throws Exception {
+    
+    // The wrapped base classifiers' buildClassifier method creates the
+    // transformed training data
+    return m_data;
+  }
+
+  /**
+   * builds the classifier.
+   *
+   * @param data 	the training data to be used for generating the
+   * 			classifier.
+   * @throws Exception 	if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    m_data = new Instances( data );
+    super.buildClassifier(m_data);
+    
+    // Wrap up the base classifiers
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      ClassifierWrapper cw = new ClassifierWrapper(m_Classifiers[i], i);
+      
+      m_Classifiers[i] = cw;
+    }
+
+    checkMinMax(m_data);
+
+    if( m_data.numInstances() > 0 ) {
+      // This function fails if there are 0 instances
+      m_random = m_data.getRandomNumberGenerator(m_Seed);
+    }
+    else {
+      m_random = new Random(m_Seed);
+    }
+
+    m_RemoveUseless = new RemoveUseless();
+    m_RemoveUseless.setInputFormat(m_data);
+    m_data = Filter.useFilter(data, m_RemoveUseless);
+
+    m_Normalize = new Normalize();
+    m_Normalize.setInputFormat(m_data);
+    m_data = Filter.useFilter(m_data, m_Normalize);
+
+    if(m_NumberOfGroups) {
+      generateGroupsFromNumbers(m_data, m_random);
+    }
+    else {
+      generateGroupsFromSizes(m_data, m_random);
+    }
+
+    m_ProjectionFilters = new Filter[m_Groups.length][];
+    for(int i = 0; i < m_ProjectionFilters.length; i++ ) {
+      m_ProjectionFilters[i] = Filter.makeCopies( m_ProjectionFilter, 
+          m_Groups[i].length );
+    }
+
+    int numClasses = m_data.numClasses();
+
+    m_instancesOfClasses = new Instances[numClasses + 1]; 
+    if( m_data.classAttribute().isNumeric() ) {
+      m_instancesOfClasses = new Instances[numClasses]; 
+      m_instancesOfClasses[0] = m_data;
+    }
+    else {
+      m_instancesOfClasses = new Instances[numClasses+1]; 
+      for( int i = 0; i < m_instancesOfClasses.length; i++ ) {
+        m_instancesOfClasses[ i ] = new Instances( m_data, 0 );
+      }
+      Enumeration enu = m_data.enumerateInstances();
+      while( enu.hasMoreElements() ) {
+        Instance instance = (Instance)enu.nextElement();
+        if( instance.classIsMissing() ) {
+          m_instancesOfClasses[numClasses].add( instance );
+	}
+	else {
+          int c = (int)instance.classValue();
+          m_instancesOfClasses[c].add( instance );
+        }
+      }
+      // If there are not instances with a missing class, we do not need to
+      // consider them
+      if( m_instancesOfClasses[numClasses].numInstances() == 0 ) {
+        Instances [] tmp = m_instancesOfClasses;
+        m_instancesOfClasses =  new Instances[ numClasses ];
+        System.arraycopy( tmp, 0, m_instancesOfClasses, 0, numClasses );
+      }
+    }
+
+    // These arrays keep the information of the transformed data set
+    m_Headers = new Instances[ m_Classifiers.length ];
+    m_ReducedHeaders = new Instances[ m_Classifiers.length ][];
+    
+    buildClassifiers();
+
+    if(m_Debug){
+      printGroups();
+    }
+    
+    // save memory
+    m_data = null;
+    m_instancesOfClasses = null;
+    m_random = null;
+  }
+
+  /** 
+   * Adds random instances to the dataset.
+   * 
+   * @param dataset the dataset
+   * @param numInstances the number of instances
+   * @param random a random number generator
+   */
+  protected void addRandomInstances( Instances dataset, int numInstances, 
+                                  Random random ) {
+    int n = dataset.numAttributes();				
+    double [] v = new double[ n ];
+    for( int i = 0; i < numInstances; i++ ) {
+      for( int j = 0; j < n; j++ ) {
+        Attribute att = dataset.attribute( j );
+        if( att.isNumeric() ) {
+	  v[ j ] = random.nextDouble();
+	}
+	else if ( att.isNominal() ) { 
+	  v[ j ] = random.nextInt( att.numValues() );
+	}
+      }
+      dataset.add( new DenseInstance( 1, v ) );
+    }
+  }
+
+  /** 
+   * Checks m_MinGroup and m_MaxGroup
+   * 
+   * @param data the dataset
+   */
+  protected void checkMinMax(Instances data) {
+    if( m_MinGroup > m_MaxGroup ) {
+      int tmp = m_MaxGroup;
+      m_MaxGroup = m_MinGroup;
+      m_MinGroup = tmp;
+    }
+    
+    int n = data.numAttributes();
+    if( m_MaxGroup >= n )
+      m_MaxGroup = n - 1;
+    if( m_MinGroup >= n )
+      m_MinGroup = n - 1;
+  }
+
+  /** 
+   * Selects a non-empty subset of the classes
+   * 
+   * @param numClasses         the number of classes
+   * @param random 	       the random number generator.
+   * @return a random subset of classes
+   */
+  protected boolean [] selectClasses( int numClasses, Random random ) {
+
+    int numSelected = 0;
+    boolean selected[] = new boolean[ numClasses ];
+
+    for( int i = 0; i < selected.length; i++ ) {
+      if(random.nextBoolean()) {
+        selected[i] = true;
+        numSelected++;
+      }
+    }
+    if( numSelected == 0 ) {
+      selected[random.nextInt( selected.length )] = true;
+    }
+    return selected;
+  }
+
+  /**
+   * generates the groups of attributes, given their minimum and maximum
+   * sizes.
+   *
+   * @param data 	the training data to be used for generating the
+   * 			groups.
+   * @param random 	the random number generator.
+   */
+  protected void generateGroupsFromSizes(Instances data, Random random) {
+    m_Groups = new int[m_Classifiers.length][][];
+    for( int i = 0; i < m_Classifiers.length; i++ ) {
+      int [] permutation = attributesPermutation(data.numAttributes(), 
+                           data.classIndex(), random);
+
+      // The number of groups that have a given size 
+      int [] numGroupsOfSize = new int[m_MaxGroup - m_MinGroup + 1];
+
+      int numAttributes = 0;
+      int numGroups;
+
+      // Select the size of each group
+      for( numGroups = 0; numAttributes < permutation.length; numGroups++ ) {
+        int n = random.nextInt( numGroupsOfSize.length );
+        numGroupsOfSize[n]++;
+        numAttributes += m_MinGroup + n;
+      }
+
+      m_Groups[i] = new int[numGroups][];
+      int currentAttribute = 0;
+      int currentSize = 0;
+      for( int j = 0; j < numGroups; j++ ) {
+        while( numGroupsOfSize[ currentSize ] == 0 )
+          currentSize++;
+        numGroupsOfSize[ currentSize ]--;
+        int n = m_MinGroup + currentSize;
+        m_Groups[i][j] = new int[n];
+        for( int k = 0; k < n; k++ ) {
+          if( currentAttribute < permutation.length )
+            m_Groups[i][j][k] = permutation[ currentAttribute ];
+          else
+	    // For the last group, it can be necessary to reuse some attributes
+            m_Groups[i][j][k] = permutation[ random.nextInt( 
+	        permutation.length ) ];
+          currentAttribute++;
+        }
+      }
+    }
+  }
+
+  /**
+   * generates the groups of attributes, given their minimum and maximum
+   * numbers.
+   *
+   * @param data 	the training data to be used for generating the
+   * 			groups.
+   * @param random 	the random number generator.
+   */
+  protected void generateGroupsFromNumbers(Instances data, Random random) {
+    m_Groups = new int[m_Classifiers.length][][];
+    for( int i = 0; i < m_Classifiers.length; i++ ) {
+      int [] permutation = attributesPermutation(data.numAttributes(), 
+                           data.classIndex(), random);
+      int numGroups = m_MinGroup + random.nextInt(m_MaxGroup - m_MinGroup + 1);
+      m_Groups[i] = new int[numGroups][];
+      int groupSize = permutation.length / numGroups;
+
+      // Some groups will have an additional attribute
+      int numBiggerGroups = permutation.length % numGroups;
+
+      // Distribute the attributes in the groups
+      int currentAttribute = 0;
+      for( int j = 0; j < numGroups; j++ ) {
+        if( j < numBiggerGroups ) {
+          m_Groups[i][j] = new int[groupSize + 1];
+        }
+        else {
+          m_Groups[i][j] = new int[groupSize];
+        }
+        for( int k = 0; k < m_Groups[i][j].length; k++ ) {
+          m_Groups[i][j][k] = permutation[currentAttribute++];
+        }
+      }
+    }
+  }
+
+  /**
+   * generates a permutation of the attributes.
+   *
+   * @param numAttributes       the number of attributes.
+   * @param classAttributes     the index of the class attribute.
+   * @param random 	        the random number generator.
+   * @return a permutation of the attributes
+   */
+  protected int [] attributesPermutation(int numAttributes, int classAttribute,
+                                         Random random) {
+    int [] permutation = new int[numAttributes-1];
+    int i = 0;
+    for(; i < classAttribute; i++){
+      permutation[i] = i;
+    }
+    for(; i < permutation.length; i++){
+      permutation[i] = i + 1;
+    }
+
+    permute( permutation, random );
+
+    return permutation;
+  }
+
+  /**
+   * permutes the elements of a given array.
+   *
+   * @param v       the array to permute
+   * @param random  the random number generator.
+   */
+  protected void permute( int v[], Random random ) {
+
+    for(int i = v.length - 1; i > 0; i-- ) {
+      int j = random.nextInt( i + 1 );
+      if( i != j ) {
+        int tmp = v[i];
+        v[i] = v[j];
+        v[j] = tmp;
+      }
+    }
+  }
+
+  /**
+   * prints the groups.
+   */
+  protected void printGroups( ) {
+    for( int i = 0; i < m_Groups.length; i++ ) {
+      for( int j = 0; j < m_Groups[i].length; j++ ) {
+        System.err.print( "( " );
+        for( int k = 0; k < m_Groups[i][j].length; k++ ) {
+          System.err.print( m_Groups[i][j][k] );
+          System.err.print( " " );
+        }
+        System.err.print( ") " );
+      }
+      System.err.println( );
+    }
+  }
+
+  /** 
+   * Transforms an instance for the i-th classifier.
+   *
+   * @param instance the instance to be transformed
+   * @param i the base classifier number
+   * @return the transformed instance
+   * @throws Exception if the instance can't be converted successfully 
+   */
+  protected Instance convertInstance( Instance instance, int i ) 
+  throws Exception {
+    Instance newInstance = new DenseInstance( m_Headers[ i ].numAttributes( ) );
+    newInstance.setDataset( m_Headers[ i ] );
+    int currentAttribute = 0;
+
+    // Project the data for each group
+    for( int j = 0; j < m_Groups[i].length; j++ ) {
+      Instance auxInstance = new DenseInstance( m_Groups[i][j].length + 1 );
+      int k;
+      for( k = 0; k < m_Groups[i][j].length; k++ ) {
+        auxInstance.setValue( k, instance.value( m_Groups[i][j][k] ) );
+      }
+      auxInstance.setValue( k, instance.classValue( ) );
+      auxInstance.setDataset( m_ReducedHeaders[ i ][ j ] );
+      m_ProjectionFilters[i][j].input( auxInstance );
+      auxInstance = m_ProjectionFilters[i][j].output( );
+      m_ProjectionFilters[i][j].batchFinished();
+      for( int a = 0; a < auxInstance.numAttributes() - 1; a++ ) {
+        newInstance.setValue( currentAttribute++, auxInstance.value( a ) );
+      }
+    }
+
+    newInstance.setClassValue( instance.classValue() );
+    return newInstance;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    m_RemoveUseless.input(instance);
+    instance =m_RemoveUseless.output();
+    m_RemoveUseless.batchFinished();
+
+    m_Normalize.input(instance);
+    instance =m_Normalize.output();
+    m_Normalize.batchFinished();
+
+    double [] sums = new double [instance.numClasses()], newProbs; 
+    
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      Instance convertedInstance = convertInstance(instance, i);
+      if (instance.classAttribute().isNumeric() == true) {
+	sums[0] += m_Classifiers[i].classifyInstance(convertedInstance);
+      } else {
+	newProbs = m_Classifiers[i].distributionForInstance(convertedInstance);
+	for (int j = 0; j < newProbs.length; j++)
+	  sums[j] += newProbs[j];
+      }
+    }
+    if (instance.classAttribute().isNumeric() == true) {
+      sums[0] /= (double)m_NumIterations;
+      return sums;
+    } else if (Utils.eq(Utils.sum(sums), 0)) {
+      return sums;
+    } else {
+      Utils.normalize(sums);
+      return sums;
+    }
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new RotationForest(), argv);
+  }
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Stacking.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Stacking.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Stacking.java	(revision 29)
@@ -0,0 +1,545 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Stacking.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableMultipleClassifiersCombiner;
+import weka.classifiers.RandomizableParallelMultipleClassifiersCombiner;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Combines several classifiers using the stacking method. Can do classification or regression.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * David H. Wolpert (1992). Stacked generalization. Neural Networks. 5:241-259.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Wolpert1992,
+ *    author = {David H. Wolpert},
+ *    journal = {Neural Networks},
+ *    pages = {241-259},
+ *    publisher = {Pergamon Press},
+ *    title = {Stacked generalization},
+ *    volume = {5},
+ *    year = {1992}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;scheme specification&gt;
+ *  Full name of meta classifier, followed by options.
+ *  (default: "weka.classifiers.rules.Zero")</pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Sets the number of cross-validation folds.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -B &lt;classifier specification&gt;
+ *  Full class name of classifier to include, followed
+ *  by scheme options. May be specified multiple times.
+ *  (default: "weka.classifiers.rules.ZeroR")</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class Stacking 
+  extends RandomizableParallelMultipleClassifiersCombiner
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5134738557155845452L;
+  
+  /** The meta classifier */
+  protected Classifier m_MetaClassifier = new ZeroR();
+ 
+  /** Format for meta data */
+  protected Instances m_MetaFormat = null;
+
+  /** Format for base data */
+  protected Instances m_BaseFormat = null;
+
+  /** Set the number of folds for the cross-validation */
+  protected int m_NumFolds = 10;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Combines several classifiers using the stacking method. "
+      + "Can do classification or regression.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "David H. Wolpert");
+    result.setValue(Field.YEAR, "1992");
+    result.setValue(Field.TITLE, "Stacked generalization");
+    result.setValue(Field.JOURNAL, "Neural Networks");
+    result.setValue(Field.VOLUME, "5");
+    result.setValue(Field.PAGES, "241-259");
+    result.setValue(Field.PUBLISHER, "Pergamon Press");
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option(
+	      metaOption(),
+	      "M", 0, "-M <scheme specification>"));
+    newVector.addElement(new Option(
+	      "\tSets the number of cross-validation folds.",
+	      "X", 1, "-X <number of folds>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * String describing option for setting meta classifier
+   * 
+   * @return the string describing the option
+   */
+  protected String metaOption() {
+
+    return "\tFull name of meta classifier, followed by options.\n" +
+      "\t(default: \"weka.classifiers.rules.Zero\")";
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;scheme specification&gt;
+   *  Full name of meta classifier, followed by options.
+   *  (default: "weka.classifiers.rules.Zero")</pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Sets the number of cross-validation folds.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -B &lt;classifier specification&gt;
+   *  Full class name of classifier to include, followed
+   *  by scheme options. May be specified multiple times.
+   *  (default: "weka.classifiers.rules.ZeroR")</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String numFoldsString = Utils.getOption('X', options);
+    if (numFoldsString.length() != 0) {
+      setNumFolds(Integer.parseInt(numFoldsString));
+    } else {
+      setNumFolds(10);
+    }
+    processMetaOptions(options);
+    super.setOptions(options);
+  }
+
+  /**
+   * Process options setting meta classifier.
+   * 
+   * @param options the options to parse
+   * @throws Exception if the parsing fails
+   */
+  protected void processMetaOptions(String[] options) throws Exception {
+
+    String classifierString = Utils.getOption('M', options);
+    String [] classifierSpec = Utils.splitOptions(classifierString);
+    String classifierName;
+    if (classifierSpec.length == 0) {
+      classifierName = "weka.classifiers.rules.ZeroR";
+    } else {
+      classifierName = classifierSpec[0];
+      classifierSpec[0] = "";
+    }
+    setMetaClassifier(AbstractClassifier.forName(classifierName, classifierSpec));
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 4];
+
+    int current = 0;
+    options[current++] = "-X"; options[current++] = "" + getNumFolds();
+    options[current++] = "-M";
+    options[current++] = getMetaClassifier().getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)getMetaClassifier()).getOptions());
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    return options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds used for cross-validation.";
+  }
+
+  /** 
+   * Gets the number of folds for the cross-validation.
+   *
+   * @return the number of folds for the cross-validation
+   */
+  public int getNumFolds() {
+
+    return m_NumFolds;
+  }
+
+  /**
+   * Sets the number of folds for the cross-validation.
+   *
+   * @param numFolds the number of folds for the cross-validation
+   * @throws Exception if parameter illegal
+   */
+  public void setNumFolds(int numFolds) throws Exception {
+    
+    if (numFolds < 0) {
+      throw new IllegalArgumentException("Stacking: Number of cross-validation " +
+					 "folds must be positive.");
+    }
+    m_NumFolds = numFolds;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String metaClassifierTipText() {
+    return "The meta classifiers to be used.";
+  }
+
+  /**
+   * Adds meta classifier
+   *
+   * @param classifier the classifier with all options set.
+   */
+  public void setMetaClassifier(Classifier classifier) {
+
+    m_MetaClassifier = classifier;
+  }
+  
+  /**
+   * Gets the meta classifier.
+   *
+   * @return the meta classifier
+   */
+  public Classifier getMetaClassifier() {
+    
+    return m_MetaClassifier;
+  }
+
+  /**
+   * Returns combined capabilities of the base classifiers, i.e., the
+   * capabilities all of them have in common.
+   *
+   * @return      the capabilities of the base classifiers
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+    
+    result = super.getCapabilities();
+    result.setMinimumNumberInstances(getNumFolds());
+
+    return result;
+  }
+  
+  /**
+   * Buildclassifier selects a classifier from the set of classifiers
+   * by minimising error on the training data.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    if (m_MetaClassifier == null) {
+      throw new IllegalArgumentException("No meta classifier has been set");
+    }
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+    m_BaseFormat = new Instances(data, 0);
+    newData.deleteWithMissingClass();
+    
+    Random random = new Random(m_Seed);
+    newData.randomize(random);
+    if (newData.classAttribute().isNominal()) {
+      newData.stratify(m_NumFolds);
+    }
+
+    // Create meta level
+    generateMetaLevel(newData, random);
+  
+    // restart the executor pool because at the end of processing
+    // a set of classifiers it gets shutdown to prevent the program
+    // executing as a server
+    super.buildClassifier(newData);
+    
+    // Rebuild all the base classifiers on the full training data
+    buildClassifiers(newData);
+  }
+
+  /**
+   * Generates the meta data
+   * 
+   * @param newData the data to work on
+   * @param random the random number generator to use for cross-validation
+   * @throws Exception if generation fails
+   */
+  protected void generateMetaLevel(Instances newData, Random random) 
+    throws Exception {
+
+    Instances metaData = metaFormat(newData);
+    m_MetaFormat = new Instances(metaData, 0);
+    for (int j = 0; j < m_NumFolds; j++) {
+      Instances train = newData.trainCV(m_NumFolds, j, random);
+      
+      // start the executor pool (if necessary)
+      // has to be done after each set of classifiers as the
+      // executor pool gets shut down in order to prevent the
+      // program executing as a server (and not returning to
+      // the command prompt when run from the command line
+      super.buildClassifier(train);
+      
+      // construct the actual classifiers
+      buildClassifiers(train);
+      
+      // Classify test instances and add to meta data
+      Instances test = newData.testCV(m_NumFolds, j);
+      for (int i = 0; i < test.numInstances(); i++) {
+	metaData.add(metaInstance(test.instance(i)));
+      }
+    }
+
+    m_MetaClassifier.buildClassifier(metaData);    
+  }
+
+  /**
+   * Returns class probabilities.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    return m_MetaClassifier.distributionForInstance(metaInstance(instance));
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifiers.length == 0) {
+      return "Stacking: No base schemes entered.";
+    }
+    if (m_MetaClassifier == null) {
+      return "Stacking: No meta scheme selected.";
+    }
+    if (m_MetaFormat == null) {
+      return "Stacking: No model built yet.";
+    }
+    String result = "Stacking\n\nBase classifiers\n\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += getClassifier(i).toString() +"\n\n";
+    }
+   
+    result += "\n\nMeta classifier\n\n";
+    result += m_MetaClassifier.toString();
+
+    return result;
+  }
+
+  /**
+   * Makes the format for the level-1 data.
+   *
+   * @param instances the level-0 format
+   * @return the format for the meta data
+   * @throws Exception if the format generation fails
+   */
+  protected Instances metaFormat(Instances instances) throws Exception {
+
+    FastVector attributes = new FastVector();
+    Instances metaFormat;
+
+    for (int k = 0; k < m_Classifiers.length; k++) {
+      Classifier classifier = (Classifier) getClassifier(k);
+      String name = classifier.getClass().getName();
+      if (m_BaseFormat.classAttribute().isNumeric()) {
+	attributes.addElement(new Attribute(name));
+      } else {
+	for (int j = 0; j < m_BaseFormat.classAttribute().numValues(); j++) {
+	  attributes.addElement(new Attribute(name + ":" + 
+					      m_BaseFormat
+					      .classAttribute().value(j)));
+	}
+      }
+    }
+    attributes.addElement(m_BaseFormat.classAttribute().copy());
+    metaFormat = new Instances("Meta format", attributes, 0);
+    metaFormat.setClassIndex(metaFormat.numAttributes() - 1);
+    return metaFormat;
+  }
+
+  /**
+   * Makes a level-1 instance from the given instance.
+   * 
+   * @param instance the instance to be transformed
+   * @return the level-1 instance
+   * @throws Exception if the instance generation fails
+   */
+  protected Instance metaInstance(Instance instance) throws Exception {
+
+    double[] values = new double[m_MetaFormat.numAttributes()];
+    Instance metaInstance;
+    int i = 0;
+    for (int k = 0; k < m_Classifiers.length; k++) {
+      Classifier classifier = getClassifier(k);
+      if (m_BaseFormat.classAttribute().isNumeric()) {
+	values[i++] = classifier.classifyInstance(instance);
+      } else {
+	double[] dist = classifier.distributionForInstance(instance);
+	for (int j = 0; j < dist.length; j++) {
+	  values[i++] = dist[j];
+	}
+      }
+    }
+    values[i] = instance.classValue();
+    metaInstance = new DenseInstance(1, values);
+    metaInstance.setDataset(m_MetaFormat);
+    return metaInstance;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new Stacking(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/StackingC.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/StackingC.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/StackingC.java	(revision 29)
@@ -0,0 +1,335 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StackingC.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.LinearRegression;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements StackingC (more efficient version of stacking).<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * A.K. Seewald: How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness. In: Nineteenth International Conference on Machine Learning, 554-561, 2002.<br/>
+ * <br/>
+ * Note: requires meta classifier to be a numeric prediction scheme.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Seewald2002,
+ *    author = {A.K. Seewald},
+ *    booktitle = {Nineteenth International Conference on Machine Learning},
+ *    editor = {C. Sammut and A. Hoffmann},
+ *    pages = {554-561},
+ *    publisher = {Morgan Kaufmann Publishers},
+ *    title = {How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;scheme specification&gt;
+ *  Full name of meta classifier, followed by options.
+ *  Must be a numeric prediction scheme. Default: Linear Regression.</pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Sets the number of cross-validation folds.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -B &lt;classifier specification&gt;
+ *  Full class name of classifier to include, followed
+ *  by scheme options. May be specified multiple times.
+ *  (default: "weka.classifiers.rules.ZeroR")</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Alexander K. Seewald (alex@seewald.at)
+ * @version $Revision: 5928 $ 
+ */
+public class StackingC 
+  extends Stacking 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6717545616603725198L;
+  
+  /** The meta classifiers (one for each class, like in ClassificationViaRegression) */
+  protected Classifier [] m_MetaClassifiers = null;
+  
+  /** Filter to transform metaData - Remove */
+  protected Remove m_attrFilter = null;
+  /** Filter to transform metaData - MakeIndicator */
+  protected MakeIndicator m_makeIndicatorFilter = null;
+
+  /**
+   * The constructor.
+   */
+  public StackingC() {
+    m_MetaClassifier = new weka.classifiers.functions.LinearRegression();
+    ((LinearRegression)(getMetaClassifier())).
+      setAttributeSelectionMethod(new 
+	weka.core.SelectedTag(1, LinearRegression.TAGS_SELECTION));
+  }  
+      
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Implements StackingC (more efficient version of stacking).\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "Note: requires meta classifier to be a numeric prediction scheme.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "A.K. Seewald");
+    result.setValue(Field.TITLE, "How to Make Stacking Better and Faster While Also Taking Care of an Unknown Weakness");
+    result.setValue(Field.BOOKTITLE, "Nineteenth International Conference on Machine Learning");
+    result.setValue(Field.EDITOR, "C. Sammut and A. Hoffmann");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.PAGES, "554-561");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+    
+    return result;
+  }
+
+  /**
+   * String describing option for setting meta classifier
+   * 
+   * @return string describing the option
+   */
+  protected String metaOption() {
+
+    return "\tFull name of meta classifier, followed by options.\n"
+      + "\tMust be a numeric prediction scheme. Default: Linear Regression.";
+  }
+
+  /**
+   * Process options setting meta classifier.
+   * 
+   * @param options the meta options to parse
+   * @throws Exception if parsing fails
+   */
+  protected void processMetaOptions(String[] options) throws Exception {
+
+    String classifierString = Utils.getOption('M', options);
+    String [] classifierSpec = Utils.splitOptions(classifierString);
+    if (classifierSpec.length != 0) {
+      String classifierName = classifierSpec[0];
+      classifierSpec[0] = "";
+      setMetaClassifier(AbstractClassifier.forName(classifierName, classifierSpec));
+    } else {
+        ((LinearRegression)(getMetaClassifier())).
+	  setAttributeSelectionMethod(new 
+	    weka.core.SelectedTag(1,LinearRegression.TAGS_SELECTION));
+    }
+  }
+
+  /**
+   * Method that builds meta level.
+   * 
+   * @param newData the data to work with
+   * @param random the random number generator to use for cross-validation
+   * @throws Exception if generation fails
+   */
+  protected void generateMetaLevel(Instances newData, Random random) 
+    throws Exception {
+
+    Instances metaData = metaFormat(newData);
+    m_MetaFormat = new Instances(metaData, 0);
+    for (int j = 0; j < m_NumFolds; j++) {
+      Instances train = newData.trainCV(m_NumFolds, j, random);
+
+      // Build base classifiers
+      for (int i = 0; i < m_Classifiers.length; i++) {
+	getClassifier(i).buildClassifier(train);
+      }
+
+      // Classify test instances and add to meta data
+      Instances test = newData.testCV(m_NumFolds, j);
+      for (int i = 0; i < test.numInstances(); i++) {
+	metaData.add(metaInstance(test.instance(i)));
+      }
+    }
+    
+    m_MetaClassifiers = AbstractClassifier.makeCopies(m_MetaClassifier,
+					      m_BaseFormat.numClasses());
+    
+    int [] arrIdc = new int[m_Classifiers.length + 1];
+    arrIdc[m_Classifiers.length] = metaData.numAttributes() - 1;
+    Instances newInsts;
+    for (int i = 0; i < m_MetaClassifiers.length; i++) {
+      for (int j = 0; j < m_Classifiers.length; j++) {
+	arrIdc[j] = m_BaseFormat.numClasses() * j + i;
+      }
+      m_makeIndicatorFilter = new weka.filters.unsupervised.attribute.MakeIndicator();
+      m_makeIndicatorFilter.setAttributeIndex("" + (metaData.classIndex() + 1));
+      m_makeIndicatorFilter.setNumeric(true);
+      m_makeIndicatorFilter.setValueIndex(i);
+      m_makeIndicatorFilter.setInputFormat(metaData);
+      newInsts = Filter.useFilter(metaData,m_makeIndicatorFilter);
+      
+      m_attrFilter = new weka.filters.unsupervised.attribute.Remove();
+      m_attrFilter.setInvertSelection(true);
+      m_attrFilter.setAttributeIndicesArray(arrIdc);
+      m_attrFilter.setInputFormat(m_makeIndicatorFilter.getOutputFormat());
+      newInsts = Filter.useFilter(newInsts,m_attrFilter);
+      
+      newInsts.setClassIndex(newInsts.numAttributes()-1);
+      
+      m_MetaClassifiers[i].buildClassifier(newInsts);
+    }
+  }
+
+  /**
+   * Classifies a given instance using the stacked classifier.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    int [] arrIdc = new int[m_Classifiers.length+1];
+    arrIdc[m_Classifiers.length] = m_MetaFormat.numAttributes() - 1;
+    double [] classProbs = new double[m_BaseFormat.numClasses()];
+    Instance newInst;
+    double sum = 0;
+
+    for (int i = 0; i < m_MetaClassifiers.length; i++) {
+      for (int j = 0; j < m_Classifiers.length; j++) {
+          arrIdc[j] = m_BaseFormat.numClasses() * j + i;
+      }
+      m_makeIndicatorFilter.setAttributeIndex("" + (m_MetaFormat.classIndex() + 1));
+      m_makeIndicatorFilter.setNumeric(true);
+      m_makeIndicatorFilter.setValueIndex(i);
+      m_makeIndicatorFilter.setInputFormat(m_MetaFormat);
+      m_makeIndicatorFilter.input(metaInstance(instance));
+      m_makeIndicatorFilter.batchFinished();
+      newInst = m_makeIndicatorFilter.output();
+
+      m_attrFilter.setAttributeIndicesArray(arrIdc);
+      m_attrFilter.setInvertSelection(true);
+      m_attrFilter.setInputFormat(m_makeIndicatorFilter.getOutputFormat());
+      m_attrFilter.input(newInst);
+      m_attrFilter.batchFinished();
+      newInst = m_attrFilter.output();
+
+      classProbs[i]=m_MetaClassifiers[i].classifyInstance(newInst);
+      if (classProbs[i] > 1) { classProbs[i] = 1; }
+      if (classProbs[i] < 0) { classProbs[i] = 0; }
+      sum += classProbs[i];
+    }
+
+    if (sum!=0) Utils.normalize(classProbs,sum);
+
+    return classProbs;
+  }
+
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_MetaFormat == null) {
+      return "StackingC: No model built yet.";
+    }
+    String result = "StackingC\n\nBase classifiers\n\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += getClassifier(i).toString() +"\n\n";
+    }
+   
+    result += "\n\nMeta classifiers (one for each class)\n\n";
+    for (int i = 0; i< m_MetaClassifiers.length; i++) {
+      result += m_MetaClassifiers[i].toString() +"\n\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new StackingC(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ThresholdSelector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ThresholdSelector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ThresholdSelector.java	(revision 29)
@@ -0,0 +1,1090 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ThresholdSelector.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.evaluation.EvaluationUtils;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A metaclassifier that selecting a mid-point threshold on the probability output by a Classifier. The midpoint threshold is set so that a given performance measure is optimized. Currently this is the F-measure. Performance is measured either on the training data, a hold-out set or using cross-validation. In addition, the probabilities returned by the base learner can have their range expanded so that the output probabilities will reside between 0 and 1 (this is useful if the scheme normally produces probabilities in a very narrow range).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;integer&gt;
+ *  The class for which threshold is determined. Valid values are:
+ *  1, 2 (for first and second classes, respectively), 3 (for whichever
+ *  class is least frequent), and 4 (for whichever class value is most
+ *  frequent), and 5 (for the first class named any of "yes","pos(itive)"
+ *  "1", or method 3 if no matches). (default 5).</pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Number of folds used for cross validation. If just a
+ *  hold-out set is used, this determines the size of the hold-out set
+ *  (default 3).</pre>
+ * 
+ * <pre> -R &lt;integer&gt;
+ *  Sets whether confidence range correction is applied. This
+ *  can be used to ensure the confidences range from 0 to 1.
+ *  Use 0 for no range correction, 1 for correction based on
+ *  the min/max values seen during threshold selection
+ *  (default 0).</pre>
+ * 
+ * <pre> -E &lt;integer&gt;
+ *  Sets the evaluation mode. Use 0 for
+ *  evaluation using cross-validation,
+ *  1 for evaluation using hold-out set,
+ *  and 2 for evaluation on the
+ *  training data (default 1).</pre>
+ * 
+ * <pre> -M [FMEASURE|ACCURACY|TRUE_POS|TRUE_NEG|TP_RATE|PRECISION|RECALL]
+ *  Measure used for evaluation (default is FMEASURE).
+ * </pre>
+ * 
+ * <pre> -manual &lt;real&gt;
+ *  Set a manual threshold to use. This option overrides
+ *  automatic selection and options pertaining to
+ *  automatic selection will be ignored.
+ *  (default -1, i.e. do not use a manual threshold).</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.functions.Logistic)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.functions.Logistic:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -R &lt;ridge&gt;
+ *  Set the ridge in the log-likelihood.</pre>
+ * 
+ * <pre> -M &lt;number&gt;
+ *  Set the maximum number of iterations (default -1, until convergence).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated sub-classifier. <p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.43 $ 
+ */
+public class ThresholdSelector 
+  extends RandomizableSingleClassifierEnhancer 
+  implements OptionHandler, Drawable {
+
+  /** for serialization */
+  static final long serialVersionUID = -1795038053239867444L;
+
+  /** no range correction */
+  public static final int RANGE_NONE = 0;
+  /** Correct based on min/max observed */
+  public static final int RANGE_BOUNDS = 1;
+  /** Type of correction applied to threshold range */ 
+  public static final Tag [] TAGS_RANGE = {
+    new Tag(RANGE_NONE, "No range correction"),
+    new Tag(RANGE_BOUNDS, "Correct based on min/max observed")
+  };
+
+  /** entire training set */
+  public static final int EVAL_TRAINING_SET = 2;
+  /** single tuned fold */
+  public static final int EVAL_TUNED_SPLIT = 1;
+  /** n-fold cross-validation */
+  public static final int EVAL_CROSS_VALIDATION = 0;
+  /** The evaluation modes */
+  public static final Tag [] TAGS_EVAL = {
+    new Tag(EVAL_TRAINING_SET, "Entire training set"),
+    new Tag(EVAL_TUNED_SPLIT, "Single tuned fold"),
+    new Tag(EVAL_CROSS_VALIDATION, "N-Fold cross validation")
+  };
+
+  /** first class value */
+  public static final int OPTIMIZE_0     = 0;
+  /** second class value */
+  public static final int OPTIMIZE_1     = 1;
+  /** least frequent class value */
+  public static final int OPTIMIZE_LFREQ = 2;
+  /** most frequent class value */
+  public static final int OPTIMIZE_MFREQ = 3;
+  /** class value name, either 'yes' or 'pos(itive)' */
+  public static final int OPTIMIZE_POS_NAME = 4;
+  /** How to determine which class value to optimize for */
+  public static final Tag [] TAGS_OPTIMIZE = {
+    new Tag(OPTIMIZE_0, "First class value"),
+    new Tag(OPTIMIZE_1, "Second class value"),
+    new Tag(OPTIMIZE_LFREQ, "Least frequent class value"),
+    new Tag(OPTIMIZE_MFREQ, "Most frequent class value"),
+    new Tag(OPTIMIZE_POS_NAME, "Class value named: \"yes\", \"pos(itive)\",\"1\"")
+  };
+
+  /** F-measure */
+  public static final int FMEASURE  = 1;
+  /** accuracy */
+  public static final int ACCURACY  = 2;
+  /** true-positive */
+  public static final int TRUE_POS  = 3;
+  /** true-negative */
+  public static final int TRUE_NEG  = 4;
+  /** true-positive rate */
+  public static final int TP_RATE   = 5;
+  /** precision */
+  public static final int PRECISION = 6;
+  /** recall */
+  public static final int RECALL    = 7;
+  /** the measure to use */
+  public static final Tag[] TAGS_MEASURE = {
+    new Tag(FMEASURE,  "FMEASURE"),
+    new Tag(ACCURACY,  "ACCURACY"),
+    new Tag(TRUE_POS,  "TRUE_POS"),
+    new Tag(TRUE_NEG,  "TRUE_NEG"), 
+    new Tag(TP_RATE,   "TP_RATE"),   
+    new Tag(PRECISION, "PRECISION"), 
+    new Tag(RECALL,    "RECALL")
+  };
+
+  /** The upper threshold used as the basis of correction */
+  protected double m_HighThreshold = 1;
+
+  /** The lower threshold used as the basis of correction */
+  protected double m_LowThreshold = 0;
+
+  /** The threshold that lead to the best performance */
+  protected double m_BestThreshold = -Double.MAX_VALUE;
+
+  /** The best value that has been observed */
+  protected double m_BestValue = - Double.MAX_VALUE;
+  
+  /** The number of folds used in cross-validation */
+  protected int m_NumXValFolds = 3;
+
+  /** Designated class value, determined during building */
+  protected int m_DesignatedClass = 0;
+
+  /** Method to determine which class to optimize for */
+  protected int m_ClassMode = OPTIMIZE_POS_NAME;
+
+  /** The evaluation mode */
+  protected int m_EvalMode = EVAL_TUNED_SPLIT;
+
+  /** The range correction mode */
+  protected int m_RangeMode = RANGE_NONE;
+
+  /** evaluation measure used for determining threshold **/
+  int m_nMeasure = FMEASURE;
+
+  /** True if a manually set threshold is being used */
+  protected boolean m_manualThreshold = false;
+  /** -1 = not used by default */
+  protected double m_manualThresholdValue = -1;
+
+  /** The minimum value for the criterion. If threshold adjustment
+      yields less than that, the default threshold of 0.5 is used. */
+  protected static final double MIN_VALUE = 0.05;
+    
+  /**
+   * Constructor.
+   */
+  public ThresholdSelector() {
+    
+    m_Classifier = new weka.classifiers.functions.Logistic();
+  }
+
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.functions.Logistic";
+  }
+
+  /**
+   * Collects the classifier predictions using the specified evaluation method.
+   *
+   * @param instances the set of <code>Instances</code> to generate
+   * predictions for.
+   * @param mode the evaluation mode.
+   * @param numFolds the number of folds to use if not evaluating on the
+   * full training set.
+   * @return a <code>FastVector</code> containing the predictions.
+   * @throws Exception if an error occurs generating the predictions.
+   */
+  protected FastVector getPredictions(Instances instances, int mode, int numFolds) 
+    throws Exception {
+
+    EvaluationUtils eu = new EvaluationUtils();
+    eu.setSeed(m_Seed);
+    
+    switch (mode) {
+    case EVAL_TUNED_SPLIT:
+      Instances trainData = null, evalData = null;
+      Instances data = new Instances(instances);
+      Random random = new Random(m_Seed);
+      data.randomize(random);
+      data.stratify(numFolds);
+      
+      // Make sure that both subsets contain at least one positive instance
+      for (int subsetIndex = 0; subsetIndex < numFolds; subsetIndex++) {
+        trainData = data.trainCV(numFolds, subsetIndex, random);
+        evalData = data.testCV(numFolds, subsetIndex);
+        if (checkForInstance(trainData) && checkForInstance(evalData)) {
+          break;
+        }
+      }
+      return eu.getTrainTestPredictions(m_Classifier, trainData, evalData);
+    case EVAL_TRAINING_SET:
+      return eu.getTrainTestPredictions(m_Classifier, instances, instances);
+    case EVAL_CROSS_VALIDATION:
+      return eu.getCVPredictions(m_Classifier, instances, numFolds);
+    default:
+      throw new RuntimeException("Unrecognized evaluation mode");
+    }
+  }
+
+  /**
+   * Tooltip for this property.
+   * 
+   * @return 	tip text for this property suitable for
+   * 		displaying in the explorer/experimenter gui
+   */
+  public String measureTipText() {
+    return "Sets the measure for determining the threshold.";
+  }
+
+  /** 
+   * set measure used for determining threshold
+   * 
+   * @param newMeasure Tag representing measure to be used
+   */
+  public void setMeasure(SelectedTag newMeasure) {
+    if (newMeasure.getTags() == TAGS_MEASURE) {
+      m_nMeasure = newMeasure.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * get measure used for determining threshold 
+   * 
+   * @return Tag representing measure used
+   */
+  public SelectedTag getMeasure() {
+    return new SelectedTag(m_nMeasure, TAGS_MEASURE);
+  }
+
+
+  /**
+   * Finds the best threshold, this implementation searches for the
+   * highest FMeasure. If no FMeasure higher than MIN_VALUE is found,
+   * the default threshold of 0.5 is used.
+   *
+   * @param predictions a <code>FastVector</code> containing the predictions.
+   */
+  protected void findThreshold(FastVector predictions) {
+
+    Instances curve = (new ThresholdCurve()).getCurve(predictions, m_DesignatedClass);
+
+    double low = 1.0;
+    double high = 0.0;
+
+    //System.err.println(curve);
+    if (curve.numInstances() > 0) {
+      Instance maxInst = curve.instance(0);
+      double maxValue = 0; 
+      int index1 = 0;
+      int index2 = 0;
+      switch (m_nMeasure) {
+        case FMEASURE:
+          index1 = curve.attribute(ThresholdCurve.FMEASURE_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case TRUE_POS:
+          index1 = curve.attribute(ThresholdCurve.TRUE_POS_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case TRUE_NEG:
+          index1 = curve.attribute(ThresholdCurve.TRUE_NEG_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case TP_RATE:
+          index1 = curve.attribute(ThresholdCurve.TP_RATE_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case PRECISION:
+          index1 = curve.attribute(ThresholdCurve.PRECISION_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case RECALL:
+          index1 = curve.attribute(ThresholdCurve.RECALL_NAME).index();
+          maxValue = maxInst.value(index1);
+          break;
+        case ACCURACY:
+          index1 = curve.attribute(ThresholdCurve.TRUE_POS_NAME).index();
+          index2 = curve.attribute(ThresholdCurve.TRUE_NEG_NAME).index();
+          maxValue = maxInst.value(index1) + maxInst.value(index2);
+          break;
+      }
+      int indexThreshold = curve.attribute(ThresholdCurve.THRESHOLD_NAME).index();
+      for (int i = 1; i < curve.numInstances(); i++) {
+        Instance current = curve.instance(i);
+        double currentValue = 0;
+        if (m_nMeasure ==  ACCURACY) {
+          currentValue= current.value(index1) + current.value(index2);
+	  } else {
+	      currentValue= current.value(index1);
+	  }
+
+	  if (currentValue> maxValue) {
+	      maxInst = current;
+	      maxValue = currentValue;
+	  }
+	  if (m_RangeMode == RANGE_BOUNDS) {
+	      double thresh = current.value(indexThreshold);
+	      if (thresh < low) {
+		  low = thresh;
+	      }
+	      if (thresh > high) {
+		  high = thresh;
+	      }
+	  }
+      }
+      if (maxValue > MIN_VALUE) {
+        m_BestThreshold = maxInst.value(indexThreshold);
+        m_BestValue = maxValue;
+        //System.err.println("maxFM: " + maxFM);
+      }
+      if (m_RangeMode == RANGE_BOUNDS) {
+	  m_LowThreshold = low;
+	  m_HighThreshold = high;
+        //System.err.println("Threshold range: " + low + " - " + high);
+      }
+    }
+
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+        "\tThe class for which threshold is determined. Valid values are:\n" +
+        "\t1, 2 (for first and second classes, respectively), 3 (for whichever\n" +
+        "\tclass is least frequent), and 4 (for whichever class value is most\n" +
+        "\tfrequent), and 5 (for the first class named any of \"yes\",\"pos(itive)\"\n" +
+        "\t\"1\", or method 3 if no matches). (default 5).",
+        "C", 1, "-C <integer>"));
+    
+    newVector.addElement(new Option(
+	      "\tNumber of folds used for cross validation. If just a\n" +
+	      "\thold-out set is used, this determines the size of the hold-out set\n" +
+	      "\t(default 3).",
+	      "X", 1, "-X <number of folds>"));
+    
+    newVector.addElement(new Option(
+        "\tSets whether confidence range correction is applied. This\n" +
+        "\tcan be used to ensure the confidences range from 0 to 1.\n" +
+        "\tUse 0 for no range correction, 1 for correction based on\n" +
+        "\tthe min/max values seen during threshold selection\n"+
+        "\t(default 0).",
+        "R", 1, "-R <integer>"));
+    
+    newVector.addElement(new Option(
+	      "\tSets the evaluation mode. Use 0 for\n" +
+	      "\tevaluation using cross-validation,\n" +
+	      "\t1 for evaluation using hold-out set,\n" +
+	      "\tand 2 for evaluation on the\n" +
+	      "\ttraining data (default 1).",
+	      "E", 1, "-E <integer>"));
+
+    newVector.addElement(new Option(
+	      "\tMeasure used for evaluation (default is FMEASURE).\n",
+	      "M", 1, "-M [FMEASURE|ACCURACY|TRUE_POS|TRUE_NEG|TP_RATE|PRECISION|RECALL]"));
+    
+    newVector.addElement(new Option(
+              "\tSet a manual threshold to use. This option overrides\n"
+              + "\tautomatic selection and options pertaining to\n"
+              + "\tautomatic selection will be ignored.\n"
+              + "\t(default -1, i.e. do not use a manual threshold).",
+              "manual", 1, "-manual <real>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;integer&gt;
+   *  The class for which threshold is determined. Valid values are:
+   *  1, 2 (for first and second classes, respectively), 3 (for whichever
+   *  class is least frequent), and 4 (for whichever class value is most
+   *  frequent), and 5 (for the first class named any of "yes","pos(itive)"
+   *  "1", or method 3 if no matches). (default 5).</pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Number of folds used for cross validation. If just a
+   *  hold-out set is used, this determines the size of the hold-out set
+   *  (default 3).</pre>
+   * 
+   * <pre> -R &lt;integer&gt;
+   *  Sets whether confidence range correction is applied. This
+   *  can be used to ensure the confidences range from 0 to 1.
+   *  Use 0 for no range correction, 1 for correction based on
+   *  the min/max values seen during threshold selection
+   *  (default 0).</pre>
+   * 
+   * <pre> -E &lt;integer&gt;
+   *  Sets the evaluation mode. Use 0 for
+   *  evaluation using cross-validation,
+   *  1 for evaluation using hold-out set,
+   *  and 2 for evaluation on the
+   *  training data (default 1).</pre>
+   * 
+   * <pre> -M [FMEASURE|ACCURACY|TRUE_POS|TRUE_NEG|TP_RATE|PRECISION|RECALL]
+   *  Measure used for evaluation (default is FMEASURE).
+   * </pre>
+   * 
+   * <pre> -manual &lt;real&gt;
+   *  Set a manual threshold to use. This option overrides
+   *  automatic selection and options pertaining to
+   *  automatic selection will be ignored.
+   *  (default -1, i.e. do not use a manual threshold).</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.functions.Logistic)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.functions.Logistic:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -R &lt;ridge&gt;
+   *  Set the ridge in the log-likelihood.</pre>
+   * 
+   * <pre> -M &lt;number&gt;
+   *  Set the maximum number of iterations (default -1, until convergence).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated sub-classifier. <p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String manualS = Utils.getOption("manual", options);
+    if (manualS.length() > 0) {
+      double val = Double.parseDouble(manualS);
+      if (val >= 0.0) {
+        setManualThresholdValue(val);
+      } 
+    }
+
+    String classString = Utils.getOption('C', options);
+    if (classString.length() != 0) {
+      setDesignatedClass(new SelectedTag(Integer.parseInt(classString) - 1, 
+                                         TAGS_OPTIMIZE));
+    } else {
+      setDesignatedClass(new SelectedTag(OPTIMIZE_POS_NAME, TAGS_OPTIMIZE));
+    }
+
+    String modeString = Utils.getOption('E', options);
+    if (modeString.length() != 0) {
+      setEvaluationMode(new SelectedTag(Integer.parseInt(modeString), 
+                                         TAGS_EVAL));
+    } else {
+      setEvaluationMode(new SelectedTag(EVAL_TUNED_SPLIT, TAGS_EVAL));
+    }
+
+    String rangeString = Utils.getOption('R', options);
+    if (rangeString.length() != 0) {
+      setRangeCorrection(new SelectedTag(Integer.parseInt(rangeString), 
+                                         TAGS_RANGE));
+    } else {
+      setRangeCorrection(new SelectedTag(RANGE_NONE, TAGS_RANGE));
+    }
+
+    String measureString = Utils.getOption('M', options);
+    if (measureString.length() != 0) {
+      setMeasure(new SelectedTag(measureString, TAGS_MEASURE));
+    } else {
+      setMeasure(new SelectedTag(FMEASURE, TAGS_MEASURE));
+    }
+
+    String foldsString = Utils.getOption('X', options);
+    if (foldsString.length() != 0) {
+      setNumXValFolds(Integer.parseInt(foldsString));
+    } else {
+      setNumXValFolds(3);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 12];
+
+    int current = 0;
+
+    if (m_manualThreshold) {
+      options[current++] = "-manual"; options[current++] = "" + getManualThresholdValue();
+    }
+    options[current++] = "-C"; options[current++] = "" + (m_ClassMode + 1);
+    options[current++] = "-X"; options[current++] = "" + getNumXValFolds();
+    options[current++] = "-E"; options[current++] = "" + m_EvalMode;
+    options[current++] = "-R"; options[current++] = "" + m_RangeMode;
+    options[current++] = "-M"; options[current++] = "" + getMeasure().getSelectedTag().getReadable();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.BINARY_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) 
+    throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    AttributeStats stats = instances.attributeStats(instances.classIndex());
+    if (m_manualThreshold) {
+      m_BestThreshold = m_manualThresholdValue;
+    } else {
+      m_BestThreshold = 0.5;
+    }
+    m_BestValue = MIN_VALUE;
+    m_HighThreshold = 1;
+    m_LowThreshold = 0;
+
+    // If data contains only one instance of positive data
+    // optimize on training data
+    if (stats.distinctCount != 2) {
+      System.err.println("Couldn't find examples of both classes. No adjustment.");
+      m_Classifier.buildClassifier(instances);
+    } else {
+      
+      // Determine which class value to look for
+      switch (m_ClassMode) {
+      case OPTIMIZE_0:
+        m_DesignatedClass = 0;
+        break;
+      case OPTIMIZE_1:
+        m_DesignatedClass = 1;
+        break;
+      case OPTIMIZE_POS_NAME:
+        Attribute cAtt = instances.classAttribute();
+        boolean found = false;
+        for (int i = 0; i < cAtt.numValues() && !found; i++) {
+          String name = cAtt.value(i).toLowerCase();
+          if (name.startsWith("yes") || name.equals("1") || 
+              name.startsWith("pos")) {
+            found = true;
+            m_DesignatedClass = i;
+          }
+        }
+        if (found) {
+          break;
+        }
+        // No named class found, so fall through to default of least frequent
+      case OPTIMIZE_LFREQ:
+        m_DesignatedClass = (stats.nominalCounts[0] > stats.nominalCounts[1]) ? 1 : 0;
+        break;
+      case OPTIMIZE_MFREQ:
+        m_DesignatedClass = (stats.nominalCounts[0] > stats.nominalCounts[1]) ? 0 : 1;
+        break;
+      default:
+        throw new Exception("Unrecognized class value selection mode");
+      }
+      
+      /*
+        System.err.println("ThresholdSelector: Using mode=" 
+        + TAGS_OPTIMIZE[m_ClassMode].getReadable());
+        System.err.println("ThresholdSelector: Optimizing using class "
+        + m_DesignatedClass + "/" 
+        + instances.classAttribute().value(m_DesignatedClass));
+      */
+      
+      if (m_manualThreshold) {
+        m_Classifier.buildClassifier(instances);
+        return;
+      }
+
+      if (stats.nominalCounts[m_DesignatedClass] == 1) {
+        System.err.println("Only 1 positive found: optimizing on training data");
+        findThreshold(getPredictions(instances, EVAL_TRAINING_SET, 0));
+      } else {
+        int numFolds = Math.min(m_NumXValFolds, stats.nominalCounts[m_DesignatedClass]);
+        //System.err.println("Number of folds for threshold selector: " + numFolds);
+        findThreshold(getPredictions(instances, m_EvalMode, numFolds));
+        if (m_EvalMode != EVAL_TRAINING_SET) {
+          m_Classifier.buildClassifier(instances);
+        }
+      }
+    }
+  }
+
+  /**
+   * Checks whether instance of designated class is in subset.
+   * 
+   * @param data the data to check for instance
+   * @return true if the instance is in the subset
+   * @throws Exception if checking fails
+   */
+  private boolean checkForInstance(Instances data) throws Exception {
+
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (((int)data.instance(i).classValue()) == m_DesignatedClass) {
+	return true;
+      }
+    }
+    return false;
+  }
+
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+    
+    double [] pred = m_Classifier.distributionForInstance(instance);
+    double prob = pred[m_DesignatedClass];
+
+    // Warp probability
+    if (prob > m_BestThreshold) {
+      prob = 0.5 + (prob - m_BestThreshold) / 
+        ((m_HighThreshold - m_BestThreshold) * 2);
+    } else {
+      prob = (prob - m_LowThreshold) / 
+        ((m_BestThreshold - m_LowThreshold) * 2);
+    }
+    if (prob < 0) {
+      prob = 0.0;
+    } else if (prob > 1) {
+      prob = 1.0;
+    }
+
+    // Alter the distribution
+    pred[m_DesignatedClass] = prob;
+    if (pred.length == 2) { // Handle case when there's only one class
+      pred[(m_DesignatedClass + 1) % 2] = 1.0 - prob;
+    }
+    return pred;
+  }
+
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A metaclassifier that selecting a mid-point threshold on the "
+      + "probability output by a Classifier. The midpoint "
+      + "threshold is set so that a given performance measure is optimized. "
+      + "Currently this is the F-measure. Performance is measured either on "
+      + "the training data, a hold-out set or using cross-validation. In "
+      + "addition, the probabilities returned by the base learner can "
+      + "have their range expanded so that the output probabilities will "
+      + "reside between 0 and 1 (this is useful if the scheme normally "
+      + "produces probabilities in a very narrow range).";
+  }
+    
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String designatedClassTipText() {
+
+    return "Sets the class value for which the optimization is performed. "
+      + "The options are: pick the first class value; pick the second "
+      + "class value; pick whichever class is least frequent; pick whichever "
+      + "class value is most frequent; pick the first class named any of "
+      + "\"yes\",\"pos(itive)\", \"1\", or the least frequent if no matches).";
+  }
+
+  /**
+   * Gets the method to determine which class value to optimize. Will
+   * be one of OPTIMIZE_0, OPTIMIZE_1, OPTIMIZE_LFREQ, OPTIMIZE_MFREQ,
+   * OPTIMIZE_POS_NAME.
+   *
+   * @return the class selection mode.
+   */
+  public SelectedTag getDesignatedClass() {
+
+    return new SelectedTag(m_ClassMode, TAGS_OPTIMIZE);
+  }
+  
+  /**
+   * Sets the method to determine which class value to optimize. Will
+   * be one of OPTIMIZE_0, OPTIMIZE_1, OPTIMIZE_LFREQ, OPTIMIZE_MFREQ,
+   * OPTIMIZE_POS_NAME.
+   *
+   * @param newMethod the new class selection mode.
+   */
+  public void setDesignatedClass(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_OPTIMIZE) {
+      m_ClassMode = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluationModeTipText() {
+
+    return "Sets the method used to determine the threshold/performance "
+      + "curve. The options are: perform optimization based on the entire "
+      + "training set (may result in overfitting); perform an n-fold "
+      + "cross-validation (may be time consuming); perform one fold of "
+      + "an n-fold cross-validation (faster but likely less accurate).";
+  }
+
+  /**
+   * Sets the evaluation mode used. Will be one of
+   * EVAL_TRAINING, EVAL_TUNED_SPLIT, or EVAL_CROSS_VALIDATION
+   *
+   * @param newMethod the new evaluation mode.
+   */
+  public void setEvaluationMode(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_EVAL) {
+      m_EvalMode = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the evaluation mode used. Will be one of
+   * EVAL_TRAINING, EVAL_TUNED_SPLIT, or EVAL_CROSS_VALIDATION
+   *
+   * @return the evaluation mode.
+   */
+  public SelectedTag getEvaluationMode() {
+
+    return new SelectedTag(m_EvalMode, TAGS_EVAL);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String rangeCorrectionTipText() {
+
+    return "Sets the type of prediction range correction performed. "
+      + "The options are: do not do any range correction; "
+      + "expand predicted probabilities so that the minimum probability "
+      + "observed during the optimization maps to 0, and the maximum "
+      + "maps to 1 (values outside this range are clipped to 0 and 1).";
+  }
+
+  /**
+   * Sets the confidence range correction mode used. Will be one of
+   * RANGE_NONE, or RANGE_BOUNDS
+   *
+   * @param newMethod the new correciton mode.
+   */
+  public void setRangeCorrection(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_RANGE) {
+      m_RangeMode = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the confidence range correction mode used. Will be one of
+   * RANGE_NONE, or RANGE_BOUNDS
+   *
+   * @return the confidence correction mode.
+   */
+  public SelectedTag getRangeCorrection() {
+
+    return new SelectedTag(m_RangeMode, TAGS_RANGE);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numXValFoldsTipText() {
+
+    return "Sets the number of folds used during full cross-validation "
+      + "and tuned fold evaluation. This number will be automatically "
+      + "reduced if there are insufficient positive examples.";
+  }
+
+  /**
+   * Get the number of folds used for cross-validation.
+   *
+   * @return the number of folds used for cross-validation.
+   */
+  public int getNumXValFolds() {
+    
+    return m_NumXValFolds;
+  }
+  
+  /**
+   * Set the number of folds used for cross-validation.
+   *
+   * @param newNumFolds the number of folds used for cross-validation.
+   */
+  public void setNumXValFolds(int newNumFolds) {
+    
+    if (newNumFolds < 2) {
+      throw new IllegalArgumentException("Number of folds must be greater than 1");
+    }
+    m_NumXValFolds = newNumFolds;
+  }
+
+  /**
+   * Returns the type of graph this classifier
+   * represents.
+   *  
+   * @return the type of graph this classifier represents
+   */   
+  public int graphType() {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graphType();
+    else 
+      return Drawable.NOT_DRAWABLE;
+  }
+
+  /**
+   * Returns graph describing the classifier (if possible).
+   *
+   * @return the graph of the classifier in dotty format
+   * @throws Exception if the classifier cannot be graphed
+   */
+  public String graph() throws Exception {
+    
+    if (m_Classifier instanceof Drawable)
+      return ((Drawable)m_Classifier).graph();
+    else throw new Exception("Classifier: " + getClassifierSpec()
+			     + " cannot be graphed");
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String manualThresholdValueTipText() {
+
+    return "Sets a manual threshold value to use. "
+      + "If this is set (non-negative value between 0 and 1), then "
+      + "all options pertaining to automatic threshold selection are "
+      + "ignored. ";
+  }
+
+  /**
+   * Sets the value for a manual threshold. If this option
+   * is set (non-negative value between 0 and 1), then options 
+   * pertaining to automatic threshold selection are ignored.
+   *
+   * @param threshold the manual threshold to use
+   */
+  public void setManualThresholdValue(double threshold) throws Exception {
+    m_manualThresholdValue = threshold;
+    if (threshold >= 0.0 && threshold <= 1.0) {
+      m_manualThreshold = true;
+    } else {
+      m_manualThreshold = false;
+      if (threshold >= 0) {
+        throw new IllegalArgumentException("Threshold must be in the "
+                                           + "range 0..1.");
+      }
+    }
+  }
+
+  /**
+   * Returns the value of the manual threshold. (a negative
+   * value indicates that no manual threshold is being used.
+   *
+   * @return the value of the manual threshold.
+   */
+  public double getManualThresholdValue() {
+    return m_manualThresholdValue;
+  }
+ 
+  /**
+   * Returns description of the cross-validated classifier.
+   *
+   * @return description of the cross-validated classifier as a string
+   */
+  public String toString() {
+
+    if (m_BestValue == -Double.MAX_VALUE)
+      return "ThresholdSelector: No model built yet.";
+
+    String result = "Threshold Selector.\n"
+    + "Classifier: " + m_Classifier.getClass().getName() + "\n";
+
+    result += "Index of designated class: " + m_DesignatedClass + "\n";
+
+    if (m_manualThreshold) {
+      result += "User supplied threshold: " + m_BestThreshold + "\n";
+    } else {
+      result += "Evaluation mode: ";
+      switch (m_EvalMode) {
+      case EVAL_CROSS_VALIDATION:
+        result += m_NumXValFolds + "-fold cross-validation";
+        break;
+      case EVAL_TUNED_SPLIT:
+        result += "tuning on 1/" + m_NumXValFolds + " of the data";
+        break;
+      case EVAL_TRAINING_SET:
+      default:
+        result += "tuning on the training data";
+      }
+      result += "\n";
+
+      result += "Threshold: " + m_BestThreshold + "\n";
+      result += "Best value: " + m_BestValue + "\n";
+      if (m_RangeMode == RANGE_BOUNDS) {
+        result += "Expanding range [" + m_LowThreshold + "," + m_HighThreshold
+          + "] to [0, 1]\n";
+      }
+      result += "Measure: " + getMeasure().getSelectedTag().getReadable() + "\n";
+    }
+    result += m_Classifier.toString();
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.43 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new ThresholdSelector(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/Vote.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/Vote.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/Vote.java	(revision 29)
@@ -0,0 +1,670 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Vote.java
+ *    Copyright (C) 2000 University of Waikato
+ *    Copyright (C) 2006 Roberto Perdisci
+ *
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.RandomizableMultipleClassifiersCombiner;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for combining classifiers. Different combinations of probability estimates for classification are available.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Ludmila I. Kuncheva (2004). Combining Pattern Classifiers: Methods and Algorithms. John Wiley and Sons, Inc..<br/>
+ * <br/>
+ * J. Kittler, M. Hatef, Robert P.W. Duin, J. Matas (1998). On combining classifiers. IEEE Transactions on Pattern Analysis and Machine Intelligence. 20(3):226-239.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -B &lt;classifier specification&gt;
+ *  Full class name of classifier to include, followed
+ *  by scheme options. May be specified multiple times.
+ *  (default: "weka.classifiers.rules.ZeroR")</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -R &lt;AVG|PROD|MAJ|MIN|MAX|MED&gt;
+ *  The combination rule to use
+ *  (default: AVG)</pre>
+ * 
+ <!-- options-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Kuncheva2004,
+ *    author = {Ludmila I. Kuncheva},
+ *    publisher = {John Wiley and Sons, Inc.},
+ *    title = {Combining Pattern Classifiers: Methods and Algorithms},
+ *    year = {2004}
+ * }
+ * 
+ * &#64;article{Kittler1998,
+ *    author = {J. Kittler and M. Hatef and Robert P.W. Duin and J. Matas},
+ *    journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence},
+ *    number = {3},
+ *    pages = {226-239},
+ *    title = {On combining classifiers},
+ *    volume = {20},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author Alexander K. Seewald (alex@seewald.at)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Roberto Perdisci (roberto.perdisci@gmail.com)
+ * @version $Revision: 5987 $
+ */
+public class Vote
+  extends RandomizableMultipleClassifiersCombiner
+  implements TechnicalInformationHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -637891196294399624L;
+  
+  /** combination rule: Average of Probabilities */
+  public static final int AVERAGE_RULE = 1;
+  /** combination rule: Product of Probabilities (only nominal classes) */
+  public static final int PRODUCT_RULE = 2;
+  /** combination rule: Majority Voting (only nominal classes) */
+  public static final int MAJORITY_VOTING_RULE = 3;
+  /** combination rule: Minimum Probability */
+  public static final int MIN_RULE = 4;
+  /** combination rule: Maximum Probability */
+  public static final int MAX_RULE = 5;
+  /** combination rule: Median Probability (only numeric class) */
+  public static final int MEDIAN_RULE = 6;
+  /** combination rules */
+  public static final Tag[] TAGS_RULES = {
+    new Tag(AVERAGE_RULE, "AVG", "Average of Probabilities"),
+    new Tag(PRODUCT_RULE, "PROD", "Product of Probabilities"),
+    new Tag(MAJORITY_VOTING_RULE, "MAJ", "Majority Voting"),
+    new Tag(MIN_RULE, "MIN", "Minimum Probability"),
+    new Tag(MAX_RULE, "MAX", "Maximum Probability"),
+    new Tag(MEDIAN_RULE, "MED", "Median")
+  };
+  
+  /** Combination Rule variable */
+  protected int m_CombinationRule = AVERAGE_RULE;
+  
+  /** the random number generator used for breaking ties in majority voting
+   * @see #distributionForInstanceMajorityVoting(Instance) */
+  protected Random m_Random;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class for combining classifiers. Different combinations of "
+      + "probability estimates for classification are available.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Enumeration 	enm;
+    Vector		result;
+    
+    result = new Vector();
+    
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe combination rule to use\n"
+	+ "\t(default: AVG)",
+	"R", 1, "-R " + Tag.toOptionList(TAGS_RULES)));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Gets the current settings of Vote.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-R");
+    result.add("" + getCombinationRule());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -B &lt;classifier specification&gt;
+   *  Full class name of classifier to include, followed
+   *  by scheme options. May be specified multiple times.
+   *  (default: "weka.classifiers.rules.ZeroR")</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -R &lt;AVG|PROD|MAJ|MIN|MAX|MED&gt;
+   *  The combination rule to use
+   *  (default: AVG)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String 	tmpStr;
+    
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0) 
+      setCombinationRule(new SelectedTag(tmpStr, TAGS_RULES));
+    else
+      setCombinationRule(new SelectedTag(AVERAGE_RULE, TAGS_RULES));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Ludmila I. Kuncheva");
+    result.setValue(Field.TITLE, "Combining Pattern Classifiers: Methods and Algorithms");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.PUBLISHER, "John Wiley and Sons, Inc.");
+
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "J. Kittler and M. Hatef and Robert P.W. Duin and J. Matas");
+    additional.setValue(Field.YEAR, "1998");
+    additional.setValue(Field.TITLE, "On combining classifiers");
+    additional.setValue(Field.JOURNAL, "IEEE Transactions on Pattern Analysis and Machine Intelligence");
+    additional.setValue(Field.VOLUME, "20");
+    additional.setValue(Field.NUMBER, "3");
+    additional.setValue(Field.PAGES, "226-239");
+    
+    return result;
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    if (    (m_CombinationRule == PRODUCT_RULE) 
+	 || (m_CombinationRule == MAJORITY_VOTING_RULE) ) {
+      result.disableAllClasses();
+      result.disableAllClassDependencies();
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enableDependency(Capability.NOMINAL_CLASS);
+    }
+    else if (m_CombinationRule == MEDIAN_RULE) {
+      result.disableAllClasses();
+      result.disableAllClassDependencies();
+      result.enable(Capability.NUMERIC_CLASS);
+      result.enableDependency(Capability.NUMERIC_CLASS);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Buildclassifier selects a classifier from the set of classifiers
+   * by minimising error on the training data.
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances newData = new Instances(data);
+    newData.deleteWithMissingClass();
+
+    m_Random = new Random(getSeed());
+    
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      getClassifier(i).buildClassifier(newData);
+    }
+  }
+
+  /**
+   * Classifies the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted most likely class for the instance or 
+   * Utils.missingValue() if no prediction is made
+   * @throws Exception if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    double result;
+    double[] dist;
+    int index;
+    
+    switch (m_CombinationRule) {
+      case AVERAGE_RULE:
+      case PRODUCT_RULE:
+      case MAJORITY_VOTING_RULE:
+      case MIN_RULE:
+      case MAX_RULE:
+	dist = distributionForInstance(instance);
+	if (instance.classAttribute().isNominal()) {
+	  index = Utils.maxIndex(dist);
+	  if (dist[index] == 0)
+	    result = Utils.missingValue();
+	  else
+	    result = index;
+	}
+	else if (instance.classAttribute().isNumeric()){
+	  result = dist[0];
+	}
+	else {
+	  result = Utils.missingValue();
+	}
+	break;
+      case MEDIAN_RULE:
+	result = classifyInstanceMedian(instance);
+	break;
+      default:
+	throw new IllegalStateException("Unknown combination rule '" + m_CombinationRule + "'!");
+    }
+    
+    return result;
+  }
+
+  /**
+   * Classifies the given test instance, returning the median from all
+   * classifiers.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted most likely class for the instance or 
+   * Utils.missingValue() if no prediction is made
+   * @throws Exception if an error occurred during the prediction
+   */
+  protected double classifyInstanceMedian(Instance instance) throws Exception {
+    double[] results = new double[m_Classifiers.length];
+    double result;
+
+    for (int i = 0; i < results.length; i++)
+      results[i] = m_Classifiers[i].classifyInstance(instance);
+    
+    if (results.length == 0)
+      result = 0;
+    else if (results.length == 1)
+      result = results[0];
+    else
+      result = Utils.kthSmallestValue(results, results.length / 2);
+    
+    return result;
+  }
+
+  /**
+   * Classifies a given instance using the selected combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[] result = new double[instance.numClasses()];
+    
+    switch (m_CombinationRule) {
+      case AVERAGE_RULE:
+	result = distributionForInstanceAverage(instance);
+	break;
+      case PRODUCT_RULE:
+	result = distributionForInstanceProduct(instance);
+	break;
+      case MAJORITY_VOTING_RULE:
+	result = distributionForInstanceMajorityVoting(instance);
+	break;
+      case MIN_RULE:
+	result = distributionForInstanceMin(instance);
+	break;
+      case MAX_RULE:
+	result = distributionForInstanceMax(instance);
+	break;
+      case MEDIAN_RULE:
+	result[0] = classifyInstance(instance);
+	break;
+      default:
+	throw new IllegalStateException("Unknown combination rule '" + m_CombinationRule + "'!");
+    }
+    
+    if (!instance.classAttribute().isNumeric() && (Utils.sum(result) > 0))
+      Utils.normalize(result);
+    
+    return result;
+  }
+  
+  /**
+   * Classifies a given instance using the Average of Probabilities 
+   * combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  protected double[] distributionForInstanceAverage(Instance instance) throws Exception {
+
+    double[] probs = getClassifier(0).distributionForInstance(instance);
+    for (int i = 1; i < m_Classifiers.length; i++) {
+      double[] dist = getClassifier(i).distributionForInstance(instance);
+      for (int j = 0; j < dist.length; j++) {
+    	  probs[j] += dist[j];
+      }
+    }
+    for (int j = 0; j < probs.length; j++) {
+      probs[j] /= (double)m_Classifiers.length;
+    }
+    return probs;
+  }
+  
+  /**
+   * Classifies a given instance using the Product of Probabilities 
+   * combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  protected double[] distributionForInstanceProduct(Instance instance) throws Exception {
+
+    double[] probs = getClassifier(0).distributionForInstance(instance);
+    for (int i = 1; i < m_Classifiers.length; i++) {
+      double[] dist = getClassifier(i).distributionForInstance(instance);
+      for (int j = 0; j < dist.length; j++) {
+    	  probs[j] *= dist[j];
+      }
+    }
+    
+    return probs;
+  }
+  
+  /**
+   * Classifies a given instance using the Majority Voting combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  protected double[] distributionForInstanceMajorityVoting(Instance instance) throws Exception {
+
+    double[] probs = new double[instance.classAttribute().numValues()];
+    double[] votes = new double[probs.length];
+    
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      probs = getClassifier(i).distributionForInstance(instance);
+      int maxIndex = 0;
+      for(int j = 0; j<probs.length; j++) {
+          if(probs[j] > probs[maxIndex])
+        	  maxIndex = j;
+      }
+      
+      // Consider the cases when multiple classes happen to have the same probability
+      for (int j=0; j<probs.length; j++) {
+	if (probs[j] == probs[maxIndex])
+	  votes[j]++;
+      }
+    }
+    
+    int tmpMajorityIndex = 0;
+    for (int k = 1; k < votes.length; k++) {
+      if (votes[k] > votes[tmpMajorityIndex])
+	tmpMajorityIndex = k;
+    }
+    
+    // Consider the cases when multiple classes receive the same amount of votes
+    Vector<Integer> majorityIndexes = new Vector<Integer>();
+    for (int k = 0; k < votes.length; k++) {
+      if (votes[k] == votes[tmpMajorityIndex])
+	majorityIndexes.add(k);
+     }
+    // Resolve the ties according to a uniform random distribution
+    int majorityIndex = majorityIndexes.get(m_Random.nextInt(majorityIndexes.size()));
+    
+    //set probs to 0
+    for (int k = 0; k<probs.length; k++)
+      probs[k] = 0;
+    probs[majorityIndex] = 1; //the class that have been voted the most receives 1
+    
+    return probs;
+  }
+  
+  /**
+   * Classifies a given instance using the Maximum Probability combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  protected double[] distributionForInstanceMax(Instance instance) throws Exception {
+
+    double[] max = getClassifier(0).distributionForInstance(instance);
+    for (int i = 1; i < m_Classifiers.length; i++) {
+      double[] dist = getClassifier(i).distributionForInstance(instance);
+      for (int j = 0; j < dist.length; j++) {
+    	  if(max[j]<dist[j])
+    		  max[j]=dist[j];
+      }
+    }
+    
+    return max;
+  }
+  
+  /**
+   * Classifies a given instance using the Minimum Probability combination rule.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  protected double[] distributionForInstanceMin(Instance instance) throws Exception {
+
+    double[] min = getClassifier(0).distributionForInstance(instance);
+    for (int i = 1; i < m_Classifiers.length; i++) {
+      double[] dist = getClassifier(i).distributionForInstance(instance);
+      for (int j = 0; j < dist.length; j++) {
+    	  if(dist[j]<min[j])
+    		  min[j]=dist[j];
+      }
+    }
+    
+    return min;
+  } 
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String combinationRuleTipText() {
+    return "The combination rule used.";
+  }
+  
+  /**
+   * Gets the combination rule used
+   *
+   * @return 		the combination rule used
+   */
+  public SelectedTag getCombinationRule() {
+    return new SelectedTag(m_CombinationRule, TAGS_RULES);
+  }
+
+  /**
+   * Sets the combination rule to use. Values other than
+   *
+   * @param newRule 	the combination rule method to use
+   */
+  public void setCombinationRule(SelectedTag newRule) {
+    if (newRule.getTags() == TAGS_RULES)
+      m_CombinationRule = newRule.getSelectedTag().getID();
+  }
+  
+  /**
+   * Output a representation of this classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_Classifiers == null) {
+      return "Vote: No model built yet.";
+    }
+
+    String result = "Vote combines";
+    result += " the probability distributions of these base learners:\n";
+    for (int i = 0; i < m_Classifiers.length; i++) {
+      result += '\t' + getClassifierSpec(i) + '\n';
+    }
+    result += "using the '";
+    
+    switch (m_CombinationRule) {
+      case AVERAGE_RULE:
+	result += "Average of Probabilities";
+	break;
+	
+      case PRODUCT_RULE:
+	result += "Product of Probabilities";
+	break;
+	
+      case MAJORITY_VOTING_RULE:
+	result += "Majority Voting";
+	break;
+	
+      case MIN_RULE:
+	result += "Minimum Probability";
+	break;
+	
+      case MAX_RULE:
+	result += "Maximum Probability";
+	break;
+	
+      case MEDIAN_RULE:
+	result += "Median Probability";
+	break;
+	
+      default:
+	throw new IllegalStateException("Unknown combination rule '" + m_CombinationRule + "'!");
+    }
+    
+    result += "' combination rule \n";
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments:
+   * -t training file [-T test file] [-c class index]
+   */
+  public static void main(String [] argv) {
+    runClassifier(new Vote(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/DefaultModels.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/DefaultModels.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/DefaultModels.props	(revision 29)
@@ -0,0 +1,32 @@
+# This file stores the properties used by the default models panel 
+# to determine which .model.xml default model lists to include.  It 
+# also stores lists of regular expressions that are used to prune 
+# the default lists as specified by the user.
+#
+# ONE VERY IMPORTANT CAVEAT! While the ability to use regular 
+# expressions is a nice and powerful way to specify model strings
+# you should try not to use commas fillowed by a space: ", " in
+# these regexp's since that's what the properties are being split
+# apart with when they are parsed at run time.
+#
+# version: $Revision: 1.1 $
+
+#list of .model.xml files
+files=toylist, large_binary_class, large_multi_class
+
+#list of regexp's defining models with large train times
+train_time=.*functions.MultilayerPerceptron.*, \
+	.*trees.LMT.*, \
+	.*AdaBoost.*, \
+	.*Bagging.*	
+
+#list of regexp's defining models with large test times
+test_time=.*lazy.LWL.*, \
+	.*lazy.KStar.*, \
+	.*lazy.IBk.*
+
+#list of regexp's defining models with large file sizes
+file_size=.*functions.SimpleLogistic.*, \
+	.*rules.JRip.*, \
+	.*AdaBoost.*, \
+	.*Bagging.*	
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleMetricHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleMetricHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleMetricHelper.java	(revision 29)
@@ -0,0 +1,108 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleMetricHelper.java
+ *    Copyright (C) 2006 David Michael
+ *
+ */
+
+package weka.classifiers.meta.ensembleSelection;
+
+import weka.classifiers.Evaluation;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * This class is used by Ensemble Selection.  It provides the "enumeration" of the
+ * metrics that can be used by ensemble selection, as well as a helper function for
+ * computing the metric using an Evaluation class.
+ * 
+ * @author  David Michael
+ * @version $Revision: 1.2 $
+ */
+public class EnsembleMetricHelper
+  implements RevisionHandler {
+  
+  /** metric: Accuracy */
+  public static final int METRIC_ACCURACY = 0;
+  /** metric: RMSE */
+  public static final int METRIC_RMSE = 1;
+  /** metric: ROC */
+  public static final int METRIC_ROC = 2;
+  /** metric: Precision */
+  public static final int METRIC_PRECISION = 3;
+  /** metric: Recall */
+  public static final int METRIC_RECALL = 4;
+  /** metric: FScore */
+  public static final int METRIC_FSCORE = 5;
+  /** metric: All */
+  public static final int METRIC_ALL = 6;
+  
+  /**
+   * Given an Evaluation object and metric, call the appropriate function to get
+   * the value for that metric and return it.  Metrics are returned so that
+   * "bigger is better".  For instance, we return 1.0 - RMSE instead of RMSE, because
+   * bigger RMSE is better. 
+   * 
+   * @param eval		the evaluation object to use
+   * @param metric_index	the metric to use
+   * @return			the value for the metric
+   */
+  public static double getMetric(Evaluation eval, int metric_index) {
+    switch (metric_index) {
+      case METRIC_ACCURACY:
+	return eval.pctCorrect();
+      case METRIC_RMSE:
+	return 1.0 - eval.rootMeanSquaredError();
+      case METRIC_ROC:
+	return eval.areaUnderROC(1); //TODO - is 1 right?
+      case METRIC_PRECISION:
+	return eval.precision(1); //TODO - same question
+      case METRIC_RECALL:
+	return eval.recall(1); //TODO - same question
+      case METRIC_FSCORE:
+	return eval.fMeasure(1); //TODO - same question
+      case METRIC_ALL:
+	double average = 0;
+	int num_metrics = 0;
+	average += eval.pctCorrect();
+	++num_metrics;
+	average += 1.0 - eval.rootMeanSquaredError();
+	++num_metrics;
+	average += eval.areaUnderROC(1);
+	++num_metrics;
+	average += eval.precision(1);
+	++num_metrics;
+	average += eval.recall(1);
+	++num_metrics;
+	average += eval.fMeasure(1);
+	++num_metrics;
+	return average / num_metrics;
+      default:
+	return 0.0; //FIXME TODO - this should probably be an exception?
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleModelMismatchException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleModelMismatchException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleModelMismatchException.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleModelMismatchException.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.classifiers.meta.ensembleSelection;
+
+/**
+ * This excpetion gets thrown when a models don't match.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class EnsembleModelMismatchException
+  extends Exception {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 4660917211181280739L;
+
+  /**
+   * constructor of the exception
+   * 
+   * @param s		the message for the exception
+   */
+  public EnsembleModelMismatchException(String s) {
+    super(s);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibrary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibrary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibrary.java	(revision 29)
@@ -0,0 +1,594 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleSelectionLibrary.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.classifiers.meta.ensembleSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.EnsembleLibrary;
+import weka.classifiers.EnsembleLibraryModel;
+import weka.classifiers.meta.EnsembleSelection;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.zip.Adler32;
+
+/**
+ * This class represents an ensemble library.  That is a 
+ * collection of models that will be combined via the 
+ * ensemble selection algorithm.  This class is responsible for
+ * tracking all of the unique model specifications in the current 
+ * library and trainined them when asked.  There are also methods
+ * to save/load library model list files.  
+ *
+ * @author  Robert Jung
+ * @author  David Michael
+ * @version $Revision: 5928 $
+ */
+public class EnsembleSelectionLibrary 
+  extends EnsembleLibrary 
+  implements Serializable {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -6444026512552917835L;
+
+  /** the working ensemble library directory. */
+  private File m_workingDirectory;
+  
+  /** tha name of the model list file storing the list of
+   * models currently being used by the model library */
+  private String m_modelListFile = null;
+  
+  /** the training data used to build the library.  One per fold.*/
+  private Instances[] m_trainingData;
+  
+  /** the test data used for hillclimbing.  One per fold. */
+  private Instances[] m_hillclimbData;
+  
+  /** the predictions of each model.  Built by trainAll.  First index is
+   * for the model.  Second is for the instance.  third is for the class
+   * (we use distributionForInstance).
+   */
+  private double[][][] m_predictions;
+  
+  /** the random seed used to partition the training data into
+   * validation and training folds */
+  private int m_seed;
+  
+  /** the number of folds */
+  private int m_folds;
+  
+  /** the ratio of validation data used to train the model */
+  private double m_validationRatio;
+  
+  /** A helper class for notifying listeners when working directory changes */
+  private transient PropertyChangeSupport m_workingDirectoryPropertySupport = new PropertyChangeSupport(this);
+  
+  /** Whether we should print debug messages. */
+  public transient boolean m_Debug = true;
+  
+  /**
+   * Creates a default libary.  Library should be associated with 
+   *
+   */  
+  public EnsembleSelectionLibrary() {
+    super();
+    
+    m_workingDirectory = new File(EnsembleSelection.getDefaultWorkingDirectory());
+  }
+  
+  /**
+   * Creates a default libary.  Library should be associated with 
+   * a working directory
+   *
+   * @param dir 		the working directory form the ensemble library
+   * @param seed		the seed value
+   * @param folds		the number of folds
+   * @param validationRatio	the ratio to use
+   */  
+  public EnsembleSelectionLibrary(String dir, int seed, 
+      int folds, double validationRatio) {
+    
+    super();
+    
+    if (dir != null)
+      m_workingDirectory = new File(dir);
+    m_seed = seed;
+    m_folds = folds;
+    m_validationRatio = validationRatio;
+    
+  }
+  
+  /**
+   * This constructor will create a library from a model
+   * list file given by the file name argument
+   * 
+   * @param libraryFileName	the library filename
+   */
+  public EnsembleSelectionLibrary(String libraryFileName) {		
+    super();
+    
+    File libraryFile = new File(libraryFileName);
+    try {
+      EnsembleLibrary.loadLibrary(libraryFile, this);
+    } catch (Exception e) {
+      System.err.println("Could not load specified library file: "+libraryFileName);
+    }
+  }
+  
+  /**
+   * This constructor will create a library from the given XML stream.
+   * 
+   * @param stream	the XML library stream
+   */
+  public EnsembleSelectionLibrary(InputStream stream) {		
+    super();
+    
+    try {
+      EnsembleLibrary.loadLibrary(stream, this);
+    }
+    catch (Exception e) {
+      System.err.println("Could not load library from XML stream: " + e);
+    }
+  }
+  
+  /**
+   * Set debug flag for the library and all its models.  The debug flag
+   * determines whether we print debugging information to stdout.
+   * 
+   * @param debug 	if true debug mode is on
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+    
+    Iterator it = getModels().iterator();
+    while (it.hasNext()) {
+      ((EnsembleSelectionLibraryModel)it.next()).setDebug(m_Debug);
+    }
+  }
+  
+  /**
+   * Sets the validation-set ratio.  This is the portion of the
+   * training set that is set aside for hillclimbing.  Note that
+   * this value is ignored if we are doing cross-validation
+   * (indicated by the number of folds being > 1).
+   *  
+   * @param validationRatio	the new ratio
+   */
+  public void setValidationRatio(double validationRatio) {
+    m_validationRatio = validationRatio;
+  }
+  
+  /**
+   * Set the number of folds for cross validation.  If the number
+   * of folds is > 1, the validation ratio is ignored.
+   * 
+   * @param numFolds		the number of folds to use
+   */
+  public void setNumFolds(int numFolds) {
+    m_folds = numFolds;
+  }
+  
+  /**
+   * This method will iterate through the TreeMap of models and
+   * train all models that do not currently exist (are not 
+   * yet trained). 
+   * <p/>
+   * Returns the data set which should be used for hillclimbing.
+   * <p/>
+   * If training a model fails then an error will
+   * be sent to stdout and that model will be removed from the 
+   * TreeMap.   FIXME Should we maybe raise an exception instead?
+   * 
+   * @param data	the data to work on
+   * @param directory 	the working directory 
+   * @param algorithm	the type of algorithm
+   * @return		the data that should be used for hillclimbing
+   * @throws Exception	if something goes wrong
+   */
+  public Instances trainAll(Instances data, String directory, int algorithm) throws Exception {
+    
+    createWorkingDirectory(directory);
+    
+    //craete the directory if it doesn't already exist
+    String dataDirectoryName = getDataDirectoryName(data);
+    File dataDirectory = new File(directory, dataDirectoryName);
+    
+    if (!dataDirectory.exists()) {
+      dataDirectory.mkdirs();
+    }
+    
+    //Now create a record of all the models trained.  This will be a .mlf 
+    //flat file with a file name based on the time/date of training
+    //DateFormat formatter = new SimpleDateFormat("yyyy.MM.dd.HH.mm");
+    //String dateString = formatter.format(new Date());
+    
+    //Go ahead and save in both formats just in case:
+    DateFormat formatter = new SimpleDateFormat("yyyy.MM.dd.HH.mm");
+    String modelListFileName = formatter.format(new Date())+"_"+size()+"_models.mlf";
+    //String modelListFileName = dataDirectory.getName()+".mlf";
+    File modelListFile = new File(dataDirectory.getPath(), modelListFileName);
+    EnsembleLibrary.saveLibrary(modelListFile, this, null);
+    
+    //modelListFileName = dataDirectory.getName()+".model.xml";
+    modelListFileName = formatter.format(new Date())+"_"+size()+"_models.model.xml";
+    modelListFile = new File(dataDirectory.getPath(), modelListFileName);
+    EnsembleLibrary.saveLibrary(modelListFile, this, null);
+    
+    
+    //log the instances used just in case we need to know...
+    String arf = data.toString();
+    FileWriter f = new FileWriter(new File(dataDirectory.getPath(), dataDirectory.getName()+".arff"));
+    f.write(arf);
+    f.close();
+    
+    // m_trainingData will contain the datasets used for training models for each fold.
+    m_trainingData = new Instances[m_folds];
+    // m_hillclimbData will contain the dataset which we will use for hillclimbing -
+    // m_hillclimbData[i] should be disjoint from m_trainingData[i].
+    m_hillclimbData = new Instances[m_folds];
+    // validationSet is all of the hillclimbing data from all folds, in the same
+    // order as it is in m_hillclimbData
+    Instances validationSet;
+    if (m_folds > 1) {
+      validationSet = new Instances(data, data.numInstances());  //make a new set
+      //with the same capacity and header as data.
+      //instances may come from CV functions in
+      //different order, so we'll make sure the
+      //validation set's order matches that of
+      //the concatenated testCV sets
+      for (int i=0; i < m_folds; ++i) {
+	m_trainingData[i] = data.trainCV(m_folds, i);
+	m_hillclimbData[i] = data.testCV(m_folds, i);
+      }
+      // If we're doing "embedded CV" we can hillclimb on
+      // the entire training set, so we just put all of the hillclimbData
+      // from all folds in to validationSet (making sure it's in the appropriate
+      // order).
+      for (int i=0; i < m_folds; ++i) {
+	for (int j=0; j < m_hillclimbData[i].numInstances(); ++j) {
+	  validationSet.add(m_hillclimbData[i].instance(j));
+	}
+      }
+    }
+    else {
+      // Otherwise, we're not doing CV, we're just using a validation set.
+      // Partition the data set in to a training set and a hillclimb set
+      // based on the m_validationRatio.
+      int validation_size = (int)(data.numInstances() * m_validationRatio);
+      m_trainingData[0] = new Instances(data, 0, data.numInstances() - validation_size);
+      m_hillclimbData[0] = new Instances(data, data.numInstances() - validation_size, validation_size);
+      validationSet = m_hillclimbData[0];
+    }
+    
+    // Now we have all the data chopped up appropriately, and we can train all models
+    Iterator it = m_Models.iterator();
+    int model_index = 0;
+    m_predictions = new double[m_Models.size()][validationSet.numInstances()][data.numClasses()];
+    
+    // We'll keep a set of all the models which fail so that we can remove them from
+    // our library.
+    Set invalidModels = new HashSet();
+    
+    while (it.hasNext()) {
+      // For each model,
+      EnsembleSelectionLibraryModel model = (EnsembleSelectionLibraryModel)it.next();
+      
+      // set the appropriate options
+      model.setDebug(m_Debug);
+      model.setFolds(m_folds);
+      model.setSeed(m_seed);
+      model.setValidationRatio(m_validationRatio);
+      model.setChecksum(getInstancesChecksum(data));
+      
+      try {
+	// Create the model.  This will attempt to load the model, if it
+	// alreay exists.  If it does not, it will train the model using
+	// m_trainingData and cache the model's predictions for 
+	// m_hillclimbData.
+	model.createModel(m_trainingData, m_hillclimbData, dataDirectory.getPath(), algorithm);
+      } catch (Exception e) {
+	// If the model failed, print a message and add it to our set of
+	// invalid models.
+	System.out.println("**Couldn't create model "+model.getStringRepresentation()
+	    +" because of following exception: "+e.getMessage());
+	
+	invalidModels.add(model);
+	continue;
+      }
+      
+      if (!invalidModels.contains(model)) {
+	// If the model succeeded, add its predictions to our array
+	// of predictions.  Note that the successful models' predictions
+	// are packed in to the front of m_predictions.
+	m_predictions[model_index] = model.getValidationPredictions();
+	++model_index;
+	// We no longer need it in memory, so release it.
+	model.releaseModel();				
+      }
+      
+      
+    }
+    
+    // Remove all invalidModels from m_Models.
+    it = invalidModels.iterator();
+    while (it.hasNext()) {
+      EnsembleSelectionLibraryModel model = (EnsembleSelectionLibraryModel)it.next();
+      if (m_Debug) System.out.println("removing invalid library model: "+model.getStringRepresentation());
+      m_Models.remove(model);
+    }
+    
+    if (m_Debug) System.out.println("model index: "+model_index+" tree set size: "+m_Models.size());
+    
+    if (invalidModels.size() > 0) {
+      // If we had any invalid models, we have some bad predictions in the back
+      // of m_predictions, so we'll shrink it to the right size.
+      double tmpPredictions[][][] = new double[m_Models.size()][][];
+      
+      for (int i = 0; i < m_Models.size(); i++) {
+	tmpPredictions[i] = m_predictions[i];
+      }
+      m_predictions = tmpPredictions;
+    }
+    
+    if (m_Debug) System.out.println("Finished remapping models");
+    
+    return validationSet;  	//Give the appropriate "hillclimb" set back to ensemble
+    				//selection.  
+  }
+  
+  /**
+   * Creates the working directory associated with this library
+   * 
+   * @param dirName	the new directory
+   */
+  public void createWorkingDirectory(String dirName) {
+    File directory = new File(dirName);
+    
+    if (!directory.exists())
+      directory.mkdirs();
+  }
+  
+  /**
+   * This will remove the model associated with the given String
+   * from the model libraryHashMap
+   * 
+   * @param modelKey	the key of the model
+   */
+  public void removeModel(String modelKey) {
+    m_Models.remove(modelKey);  //TODO - is this really all there is to it??
+  }
+  
+  /**
+   * This method will return a Set object containing all the 
+   * String representations of the models.  The iterator across
+   * this Set object will return the model name in alphebetical
+   * order.
+   * 
+   * @return		all model representations
+   */
+  public Set getModelNames() {
+    Set names = new TreeSet();
+    
+    Iterator it = m_Models.iterator();
+    
+    while (it.hasNext()) {
+      names.add(((EnsembleLibraryModel)it.next()).getStringRepresentation());
+    }
+    
+    return names;
+  }
+  
+  /**
+   * This method will get the predictions for all the models in the
+   * ensemble library.  If cross validaiton is used, then predictions
+   * will be returned for the entire training set.  If cross validation
+   * is not used, then predictions will only be returned for the ratio 
+   * of the training set reserved for validation.
+   * 
+   * @return		the predictions
+   */
+  public double[][][] getHillclimbPredictions() {
+    return m_predictions;
+  }
+  
+  /**
+   * Gets the working Directory of the ensemble library.
+   *
+   * @return the working directory.
+   */
+  public File getWorkingDirectory() {
+    return m_workingDirectory;
+  }
+  
+  /**
+   * Sets the working Directory of the ensemble library.
+   *
+   * @param workingDirectory 	the working directory to use.
+   */
+  public void setWorkingDirectory(File workingDirectory) {
+    m_workingDirectory = workingDirectory;
+    if (m_workingDirectoryPropertySupport != null) {
+      m_workingDirectoryPropertySupport.firePropertyChange(null, null, null);
+    }
+  }
+  
+  /**
+   * Gets the model list file that holds the list of models
+   * in the ensemble library.
+   *
+   * @return the working directory.
+   */
+  public String getModelListFile() {
+    return m_modelListFile;
+  }
+  
+  /**
+   * Sets the model list file that holds the list of models
+   * in the ensemble library.
+   *
+   * @param modelListFile 	the model list file to use
+   */
+  public void setModelListFile(String modelListFile) {
+    m_modelListFile = modelListFile;
+  }
+  
+  /**
+   * creates a LibraryModel from a set of arguments
+   * 
+   * @param classifier	the classifier to use
+   * @return		the generated library model
+   */
+  public EnsembleLibraryModel createModel(Classifier classifier) {
+    EnsembleSelectionLibraryModel model = new EnsembleSelectionLibraryModel(classifier);
+    model.setDebug(m_Debug);
+    
+    return model;
+  }
+  
+  /**
+   * This method takes a String argument defining a classifier and
+   * uses it to create a base Classifier.  
+   * 
+   * WARNING! This method is only called when trying to craete models
+   * from flat files (.mlf).  This method is highly untested and 
+   * foreseeably will cause problems when trying to nest arguments
+   * within multiplte meta classifiers.  To avoid any problems we
+   * recommend using only XML serialization, via saving to 
+   * .model.xml and using only the createModel(Classifier) method
+   * above.
+   * 
+   * @param modelString		the classifier definition
+   * @return			the generated library model
+   */
+  public EnsembleLibraryModel createModel(String modelString) {
+    
+    String[] splitString = modelString.split("\\s+");
+    String className = splitString[0];
+    
+    String argString = modelString.replaceAll(splitString[0], "");
+    String[] optionStrings = argString.split("\\s+"); 
+    
+    EnsembleSelectionLibraryModel model = null;
+    try {
+      model = new EnsembleSelectionLibraryModel(AbstractClassifier.forName(className, optionStrings));
+      model.setDebug(m_Debug);
+      
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return model;
+  }
+  
+  
+  /**
+   * This method takes an Instances object and returns a checksum of its
+   * toString method - that is the checksum of the .arff file that would 
+   * be created if the Instances object were transformed into an arff file 
+   * in the file system.
+   * 
+   * @param instances	the data to get the checksum for
+   * @return		the checksum
+   */
+  public static String getInstancesChecksum(Instances instances) {
+    
+    String checksumString = null;
+    
+    try {
+      
+      Adler32 checkSummer = new Adler32();
+      
+      byte[] utf8 = instances.toString().getBytes("UTF8");;
+      
+      checkSummer.update(utf8);
+      checksumString = Long.toHexString(checkSummer.getValue());
+      
+    } catch (UnsupportedEncodingException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    
+    
+    return checksumString;
+  }
+  
+  /**
+   * Returns the unique name for the set of instances supplied.  This is 
+   * used to create a directory for all of the models corresponding to that 
+   * set of instances.  This was intended as a way to keep Working Directories 
+   * "organized"
+   * 
+   * @param instances	the data to get the directory for
+   * @return		the directory
+   */
+  public static String getDataDirectoryName(Instances instances) {
+    
+    String directory = null;
+    
+    
+    directory = new String(instances.numInstances()+
+	"_instances_"+getInstancesChecksum(instances));
+    
+    //System.out.println("generated directory name: "+directory);
+    
+    return directory;
+    
+  }
+  
+  /**
+   * Adds an object to the list of those that wish to be informed when the
+   * eotking directory changes.
+   *
+   * @param listener a new listener to add to the list
+   */   
+  public void addWorkingDirectoryListener(PropertyChangeListener listener) {
+    
+    if (m_workingDirectoryPropertySupport != null) {
+      m_workingDirectoryPropertySupport.addPropertyChangeListener(listener);
+      
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibraryModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibraryModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/EnsembleSelectionLibraryModel.java	(revision 29)
@@ -0,0 +1,850 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleSelection.java
+ *    Copyright (C) 2006 David Michael
+ *
+ */
+
+package weka.classifiers.meta.ensembleSelection;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.EnsembleLibraryModel;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.OptionHandler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.zip.Adler32;
+
+/**
+ * This class represents a library model that is used for EnsembleSelection. At
+ * this level the concept of cross validation is abstracted away. This class
+ * keeps track of the performance statistics and bookkeeping information for its
+ * "model type" accross all the CV folds. By "model type", I mean the
+ * combination of both the Classifier type (e.g. J48), and its set of parameters
+ * (e.g. -C 0.5 -X 1 -Y 5). So for example, if you are using 5 fold cross
+ * validaiton, this model will keep an array of classifiers[] of length 5 and
+ * will keep track of their performances accordingly. This class also has
+ * methods to deal with serializing all of this information into the .elm file
+ * that will represent this model.
+ * <p/>
+ * Also it is worth mentioning that another important function of this class is
+ * to track all of the dataset information that was used to create this model.
+ * This is because we want to protect users from doing foreseeably bad things.
+ * e.g., trying to build an ensemble for a dataset with models that were trained
+ * on the wrong partitioning of the dataset. This could lead to artificially high
+ * performance due to the fact that instances used for the test set to gauge
+ * performance could have accidentally been used to train the base classifiers.
+ * So in a nutshell, we are preventing people from unintentionally "cheating" by
+ * enforcing that the seed, #folds, validation ration, and the checksum of the 
+ * Instances.toString() method ALL match exactly.  Otherwise we throw an 
+ * exception.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 5928 $ 
+ */
+public class EnsembleSelectionLibraryModel
+  extends EnsembleLibraryModel
+  implements Serializable {
+  
+  /**
+   * This is the serialVersionUID that SHOULD stay the same so that future
+   * modified versions of this class will be backwards compatible with older
+   * model versions.
+   */
+  private static final long serialVersionUID = -6426075459862947640L;
+  
+  /** The default file extension for ensemble library models */
+  public static final String FILE_EXTENSION = ".elm";
+  
+  /** the models */
+  private Classifier[] m_models = null;
+  
+  /** The seed that was used to create this model */
+  private int m_seed;
+  
+  /**
+   * The checksum of the instances.arff object that was used to create this
+   * model
+   */
+  private String m_checksum;
+  
+  /** The validation ratio that was used to create this model */
+  private double m_validationRatio;
+  
+  /**
+   * The number of folds, or number of CV models that was used to create this
+   * "model"
+   */
+  private int m_folds;
+  
+  /**
+   * The .elm file name that this model should be saved/loaded to/from
+   */
+  private String m_fileName;
+  
+  /**
+   * The debug flag as propagated from the main EnsembleSelection class.
+   */
+  public transient boolean m_Debug = true;
+  
+  /**
+   * the validation predictions of this model. First index for the instance.
+   * third is for the class (we use distributionForInstance).
+   */
+  private double[][] m_validationPredictions = null; // = new double[0][0];
+  
+  /**
+   * Default Constructor
+   */
+  public EnsembleSelectionLibraryModel() {
+  }
+  
+  /**
+   * Constructor for LibaryModel
+   * 
+   * @param classifier		the classifier to use
+   * @param seed		the random seed value
+   * @param checksum		the checksum
+   * @param validationRatio	the ration to use
+   * @param folds		the number of folds to use
+   */
+  public EnsembleSelectionLibraryModel(Classifier classifier, int seed,
+      String checksum, double validationRatio, int folds) {
+    
+    super(classifier);
+    
+    m_seed = seed;
+    m_checksum = checksum;
+    m_validationRatio = validationRatio;
+    m_models = null;
+    m_folds = folds;
+  }
+  
+  /**
+   * This is used to propagate the m_Debug flag of the EnsembleSelection
+   * classifier to this class. There are things we would want to print out
+   * here also.
+   * 
+   * @param debug	if true additional information is output
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+  }
+  
+  /**
+   * Returns the average of the prediction of the models across all folds.
+   * 
+   * @param instance	the instance to get predictions for
+   * @return		the average prediction
+   * @throws Exception	if something goes wrong
+   */
+  public double[] getAveragePrediction(Instance instance) throws Exception {
+    
+    // Return the average prediction from all classifiers that make up
+    // this model.
+    double average[] = new double[instance.numClasses()];
+    for (int i = 0; i < m_folds; ++i) {
+      // Some models alter the instance (MultiLayerPerceptron), so we need
+      // to copy it.
+      Instance temp_instance = (Instance) instance.copy();
+      double[] pred = getFoldPrediction(temp_instance, i);
+      if (pred == null) {
+	// Some models have bugs whereby they can return a null
+	// prediction
+	// array (again, MultiLayerPerceptron). We return null, and this
+	// should be handled above in EnsembleSelection.
+	System.err.println("Null validation predictions given: "
+	    + getStringRepresentation());
+	return null;
+      }
+      if (i == 0) {
+	// The first time through the loop, just use the first returned
+	// prediction array. Just a simple optimization.
+	average = pred;
+      } else {
+	// For the rest, add the prediction to the average array.
+	for (int j = 0; j < pred.length; ++j) {
+	  average[j] += pred[j];
+	}
+      }
+    }
+    if (instance.classAttribute().isNominal()) {
+      // Normalize predictions for classes to add up to 1.
+      Utils.normalize(average);
+    } else {
+      average[0] /= m_folds;
+    }
+    return average;
+  }
+  
+  /**
+   * Basic Constructor
+   * 
+   * @param classifier	the classifier to use
+   */
+  public EnsembleSelectionLibraryModel(Classifier classifier) {
+    super(classifier);
+  }
+  
+  /**
+   * Returns prediction of the classifier for the specified fold.
+   * 
+   * @param instance
+   *            instance for which to make a prediction.
+   * @param fold
+   *            fold number of the classifier to use.
+   * @return the prediction for the classes
+   * @throws Exception if prediction fails
+   */
+  public double[] getFoldPrediction(Instance instance, int fold)
+    throws Exception {
+    
+    return m_models[fold].distributionForInstance(instance);
+  }
+  
+  /**
+   * Creates the model. If there are n folds, it constructs n classifiers
+   * using the current Classifier class and options. If the model has already
+   * been created or loaded, starts fresh.
+   * 
+   * @param data		the data to work with
+   * @param hillclimbData	the data for hillclimbing
+   * @param dataDirectoryName	the directory to use
+   * @param algorithm		the type of algorithm
+   * @throws Exception		if something goeds wrong
+   */
+  public void createModel(Instances[] data, Instances[] hillclimbData,
+      String dataDirectoryName, int algorithm) throws Exception {
+    
+    String modelFileName = getFileName(getStringRepresentation());
+    
+    File modelFile = new File(dataDirectoryName, modelFileName);
+    
+    String relativePath = (new File(dataDirectoryName)).getName()
+    + File.separatorChar + modelFileName;
+    // if (m_Debug) System.out.println("setting relative path to:
+    // "+relativePath);
+    setFileName(relativePath);
+    
+    if (!modelFile.exists()) {
+      
+      Date startTime = new Date();
+      
+      String lockFileName = EnsembleSelectionLibraryModel
+      .getFileName(getStringRepresentation());
+      lockFileName = lockFileName.substring(0, lockFileName.length() - 3)
+      + "LCK";
+      File lockFile = new File(dataDirectoryName, lockFileName);
+      
+      if (lockFile.exists()) {
+	if (m_Debug)
+	  System.out.println("Detected lock file.  Skipping: "
+	      + lockFileName);
+	throw new Exception("Lock File Detected: " + lockFile.getName());
+	
+      } else { // if (algorithm ==
+	// EnsembleSelection.ALGORITHM_BUILD_LIBRARY) {
+	// This lock file lets other computers that might be sharing the
+	// same file
+	// system that this model is already being trained so they know
+	// to move ahead
+	// and train other models.
+	
+	if (lockFile.createNewFile()) {
+	  
+	  if (m_Debug)
+	    System.out
+	    .println("lock file created: " + lockFileName);
+	  
+	  if (m_Debug)
+	    System.out.println("Creating model in locked mode: "
+		+ modelFile.getPath());
+	  
+	  m_models = new Classifier[m_folds];
+	  for (int i = 0; i < m_folds; ++i) {
+	    
+	    try {
+	      m_models[i] = AbstractClassifier.forName(getModelClass()
+		  .getName(), null);
+	      ((OptionHandler)m_models[i]).setOptions(getOptions());
+	    } catch (Exception e) {
+	      throw new Exception("Invalid Options: "
+		  + e.getMessage());
+	    }
+	  }
+	  
+	  try {
+	    for (int i = 0; i < m_folds; ++i) {
+	      train(data[i], i);
+	    }
+	  } catch (Exception e) {
+	    throw new Exception("Could not Train: "
+		+ e.getMessage());
+	  }
+	  
+	  Date endTime = new Date();
+	  int diff = (int) (endTime.getTime() - startTime.getTime());
+	  
+	  // We don't need the actual model for hillclimbing. To save
+	  // memory, release
+	  // it.
+	  
+	  // if (!invalidModels.contains(model)) {
+	  // EnsembleLibraryModel.saveModel(dataDirectory.getPath(),
+	  // model);
+	  // model.releaseModel();
+	  // }
+	  if (m_Debug)
+	    System.out.println("Train time for " + modelFileName
+		+ " was: " + diff);
+	  
+	  if (m_Debug)
+	    System.out
+	    .println("Generating validation set predictions");
+	  
+	  startTime = new Date();
+	  
+	  int total = 0;
+	  for (int i = 0; i < m_folds; ++i) {
+	    total += hillclimbData[i].numInstances();
+	  }
+	  
+	  m_validationPredictions = new double[total][];
+	  
+	  int preds_index = 0;
+	  for (int i = 0; i < m_folds; ++i) {
+	    for (int j = 0; j < hillclimbData[i].numInstances(); ++j) {
+	      Instance temp = (Instance) hillclimbData[i]
+	                                               .instance(j).copy();// new
+	      // Instance(m_hillclimbData[i].instance(j));
+	      // must copy the instance because SOME classifiers
+	      // (I'm not pointing fingers...
+	      // MULTILAYERPERCEPTRON)
+	      // change the instance!
+	      
+	      m_validationPredictions[preds_index] = getFoldPrediction(
+		  temp, i);
+	      
+	      if (m_validationPredictions[preds_index] == null) {
+		throw new Exception(
+		    "Null validation predictions given: "
+		    + getStringRepresentation());
+	      }
+	      
+	      ++preds_index;
+	    }
+	  }
+	  
+	  endTime = new Date();
+	  diff = (int) (endTime.getTime() - startTime.getTime());
+	  
+	  // if (m_Debug) System.out.println("Generated a validation
+	  // set array of size: "+m_validationPredictions.length);
+	  if (m_Debug)
+	    System.out
+	    .println("Time to create validation predictions was: "
+		+ diff);
+	  
+	  EnsembleSelectionLibraryModel.saveModel(dataDirectoryName,
+	      this);
+	  
+	  if (m_Debug)
+	    System.out.println("deleting lock file: "
+		+ lockFileName);
+	  lockFile.delete();
+	  
+	} else {
+	  
+	  if (m_Debug)
+	    System.out
+	    .println("Could not create lock file.  Skipping: "
+		+ lockFileName);
+	  throw new Exception(
+	      "Could not create lock file.  Skipping: "
+	      + lockFile.getName());
+	  
+	}
+	
+      }
+      
+    } else {
+      // This branch is responsible for loading a model from a .elm file
+      
+      if (m_Debug)
+	System.out.println("Loading model: " + modelFile.getPath());
+      // now we need to check to see if the model is valid, if so then
+      // load it
+      Date startTime = new Date();
+      
+      EnsembleSelectionLibraryModel newModel = loadModel(modelFile
+	  .getPath());
+      
+      if (!newModel.getStringRepresentation().equals(
+	  getStringRepresentation()))
+	throw new EnsembleModelMismatchException(
+	    "String representations "
+	    + newModel.getStringRepresentation() + " and "
+	    + getStringRepresentation() + " not equal");
+      
+      if (!newModel.getChecksum().equals(getChecksum()))
+	throw new EnsembleModelMismatchException("Checksums "
+	    + newModel.getChecksum() + " and " + getChecksum()
+	    + " not equal");
+      
+      if (newModel.getSeed() != getSeed())
+	throw new EnsembleModelMismatchException("Seeds "
+	    + newModel.getSeed() + " and " + getSeed()
+	    + " not equal");
+      
+      if (newModel.getFolds() != getFolds())
+	throw new EnsembleModelMismatchException("Folds "
+	    + newModel.getFolds() + " and " + getFolds()
+	    + " not equal");
+      
+      if (newModel.getValidationRatio() != getValidationRatio())
+	throw new EnsembleModelMismatchException("Validation Ratios "
+	    + newModel.getValidationRatio() + " and "
+	    + getValidationRatio() + " not equal");
+      
+      // setFileName(modelFileName);
+      
+      m_models = newModel.getModels();
+      m_validationPredictions = newModel.getValidationPredictions();
+      
+      Date endTime = new Date();
+      int diff = (int) (endTime.getTime() - startTime.getTime());
+      if (m_Debug)
+	System.out.println("Time to load " + modelFileName + " was: "
+	    + diff);
+    }
+  }
+  
+  /**
+   * The purpose of this method is to "rehydrate" the classifier object fot
+   * this library model from the filesystem.
+   * 
+   * @param workingDirectory	the working directory to use
+   */
+  public void rehydrateModel(String workingDirectory) {
+    
+    if (m_models == null) {
+      
+      File file = new File(workingDirectory, m_fileName);
+      
+      if (m_Debug)
+	System.out.println("Rehydrating Model: " + file.getPath());
+      EnsembleSelectionLibraryModel model = EnsembleSelectionLibraryModel
+      .loadModel(file.getPath());
+      
+      m_models = model.getModels();
+      
+    }
+  }
+  
+  /**
+   * Releases the model from memory. TODO - need to be saving these so we can
+   * retrieve them later!!
+   */
+  public void releaseModel() {
+    /*
+     * if (m_unsaved) { saveModel(); }
+     */
+    m_models = null;
+  }
+  
+  /** 
+   * Train the classifier for the specified fold on the given data
+   * 
+   * @param trainData	the data to train with
+   * @param fold	the fold number
+   * @throws Exception	if something goes wrong, e.g., out of memory
+   */
+  public void train(Instances trainData, int fold) throws Exception {
+    if (m_models != null) {
+      
+      try {
+	// OK, this is it... this is the point where our code surrenders
+	// to the weka classifiers.
+	m_models[fold].buildClassifier(trainData);
+      } catch (Throwable t) {
+	m_models[fold] = null;
+	throw new Exception(
+	    "Exception caught while training: (null could mean out of memory)"
+	    + t.getMessage());
+      }
+      
+    } else {
+      throw new Exception("Cannot train: model was null");
+      // TODO: throw Exception?
+    }
+  }
+  
+  /**
+   * Set the seed
+   * 
+   * @param seed	the seed value
+   */
+  public void setSeed(int seed) {
+    m_seed = seed;
+  }
+  
+  /**
+   * Get the seed
+   * 
+   * @return the seed value
+   */
+  public int getSeed() {
+    return m_seed;
+  }
+  
+  /**
+   * Sets the validation set ratio (only meaningful if folds == 1)
+   * 
+   * @param validationRatio	the new ration
+   */
+  public void setValidationRatio(double validationRatio) {
+    m_validationRatio = validationRatio;
+  }
+  
+  /**
+   * get validationRatio
+   * 
+   * @return		the current ratio
+   */
+  public double getValidationRatio() {
+    return m_validationRatio;
+  }
+  
+  /**
+   * Set the number of folds for cross validation. The number of folds also
+   * indicates how many classifiers will be built to represent this model.
+   * 
+   * @param folds	the number of folds to use
+   */
+  public void setFolds(int folds) {
+    m_folds = folds;
+  }
+  
+  /**
+   * get the number of folds
+   * 
+   * @return		the current number of folds
+   */
+  public int getFolds() {
+    return m_folds;
+  }
+  
+  /**
+   * set the checksum
+   * 
+   * @param instancesChecksum	the new checksum
+   */
+  public void setChecksum(String instancesChecksum) {
+    m_checksum = instancesChecksum;
+  }
+  
+  /**
+   * get the checksum
+   * 
+   * @return		the current checksum
+   */
+  public String getChecksum() {
+    return m_checksum;
+  }
+  
+  /**
+   * Returs the array of classifiers
+   * 
+   * @return		the current models
+   */
+  public Classifier[] getModels() {
+    return m_models;
+  }
+  
+  /**
+   * Sets the .elm file name for this library model
+   * 
+   * @param fileName	the new filename
+   */
+  public void setFileName(String fileName) {
+    m_fileName = fileName;
+  }
+  
+  /**
+   * Gets a checksum for the string defining this classifier. This is used to
+   * preserve uniqueness in the classifier names.
+   * 
+   * @param string	the classifier definition
+   * @return		the checksum string
+   */
+  public static String getStringChecksum(String string) {
+    
+    String checksumString = null;
+    
+    try {
+      
+      Adler32 checkSummer = new Adler32();
+      
+      byte[] utf8 = string.toString().getBytes("UTF8");
+      ;
+      
+      checkSummer.update(utf8);
+      checksumString = Long.toHexString(checkSummer.getValue());
+      
+    } catch (UnsupportedEncodingException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    
+    return checksumString;
+  }
+  
+  /**
+   * The purpose of this method is to get an appropriate file name for a model
+   * based on its string representation of a model. All generated filenames
+   * are limited to less than 128 characters and all of them will end with a
+   * 64 bit checksum value of their string representation to try to maintain
+   * some uniqueness of file names.
+   * 
+   * @param stringRepresentation	string representation of model
+   * @return				unique filename
+   */
+  public static String getFileName(String stringRepresentation) {
+    
+    // Get rid of space and quote marks(windows doesn't lke them)
+    String fileName = stringRepresentation.trim().replace(' ', '_')
+    .replace('"', '_');
+    
+    if (fileName.length() > 115) {
+      
+      fileName = fileName.substring(0, 115);
+      
+    }
+    
+    fileName += getStringChecksum(stringRepresentation)
+    + EnsembleSelectionLibraryModel.FILE_EXTENSION;
+    
+    return fileName;
+  }
+  
+  /**
+   * Saves the given model to the specified file.
+   * 
+   * @param directory	the directory to save the model to
+   * @param model	the model to save
+   */
+  public static void saveModel(String directory,
+      EnsembleSelectionLibraryModel model) {
+    
+    try {
+      String fileName = getFileName(model.getStringRepresentation());
+      
+      File file = new File(directory, fileName);
+      
+      // System.out.println("Saving model: "+file.getPath());
+      
+      // model.setFileName(new String(file.getPath()));
+      
+      // Serialize to a file
+      ObjectOutput out = new ObjectOutputStream(
+	  new FileOutputStream(file));
+      out.writeObject(model);
+      
+      out.close();
+      
+    } catch (IOException e) {
+      
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * loads the specified model
+   * 
+   * @param modelFilePath	the path of the model
+   * @return			the model
+   */
+  public static EnsembleSelectionLibraryModel loadModel(String modelFilePath) {
+    
+    EnsembleSelectionLibraryModel model = null;
+    
+    try {
+      
+      File file = new File(modelFilePath);
+      
+      ObjectInputStream in = new ObjectInputStream(new FileInputStream(
+	  file));
+      
+      model = (EnsembleSelectionLibraryModel) in.readObject();
+      
+      in.close();
+      
+    } catch (ClassNotFoundException e) {
+      
+      e.printStackTrace();
+      
+    } catch (IOException e) {
+      
+      e.printStackTrace();
+      
+    }
+    
+    return model;
+  }
+  
+  /*
+   * Problems persist in this code so we left it commented out. The intent was
+   * to create the methods necessary for custom serialization to allow for
+   * forwards/backwards compatability of .elm files accross multiple versions
+   * of this classifier. The main problem however is that these methods do not
+   * appear to be called. I'm not sure what the problem is, but this would be
+   * a great feature. If anyone is a seasoned veteran of this serialization
+   * stuff, please help!
+   * 
+   * private void writeObject(ObjectOutputStream stream) throws IOException {
+   * //stream.defaultWriteObject(); //stream.writeObject(b);
+   * 
+   * //first serialize the LibraryModel fields
+   * 
+   * //super.writeObject(stream);
+   * 
+   * //now serialize the LibraryModel fields
+   * 
+   * stream.writeObject(m_Classifier);
+   * 
+   * stream.writeObject(m_DescriptionText);
+   * 
+   * stream.writeObject(m_ErrorText);
+   * 
+   * stream.writeObject(new Boolean(m_OptionsWereValid));
+   * 
+   * stream.writeObject(m_StringRepresentation);
+   * 
+   * stream.writeObject(m_models);
+   * 
+   * 
+   * //now serialize the EnsembleLibraryModel fields //stream.writeObject(new
+   * String("blah"));
+   * 
+   * stream.writeObject(new Integer(m_seed));
+   * 
+   * stream.writeObject(m_checksum);
+   * 
+   * stream.writeObject(new Double(m_validationRatio));
+   * 
+   * stream.writeObject(new Integer(m_folds));
+   * 
+   * stream.writeObject(m_fileName);
+   * 
+   * stream.writeObject(new Boolean(m_isTrained));
+   * 
+   * 
+   * if (m_validationPredictions == null) {
+   *  }
+   * 
+   * if (m_Debug) System.out.println("Saving
+   * "+m_validationPredictions.length+" indexed array");
+   * stream.writeObject(m_validationPredictions);
+   *  }
+   * 
+   * private void readObject(ObjectInputStream stream) throws IOException,
+   * ClassNotFoundException { //stream.defaultReadObject(); //b = (String)
+   * stream.readObject();
+   * 
+   * //super.readObject(stream);
+   * 
+   * //deserialize the LibraryModel fields m_Classifier =
+   * (Classifier)stream.readObject();
+   * 
+   * m_DescriptionText = (String)stream.readObject();
+   * 
+   * m_ErrorText = (String)stream.readObject();
+   * 
+   * m_OptionsWereValid = ((Boolean)stream.readObject()).booleanValue();
+   * 
+   * m_StringRepresentation = (String)stream.readObject();
+   * 
+   * 
+   * 
+   * //now deserialize the EnsembleLibraryModel fields m_models =
+   * (Classifier[])stream.readObject();
+   * 
+   * m_seed = ((Integer)stream.readObject()).intValue();
+   * 
+   * m_checksum = (String)stream.readObject();
+   * 
+   * m_validationRatio = ((Double)stream.readObject()).doubleValue();
+   * 
+   * m_folds = ((Integer)stream.readObject()).intValue();
+   * 
+   * m_fileName = (String)stream.readObject();
+   * 
+   * m_isTrained = ((Boolean)stream.readObject()).booleanValue();
+   * 
+   * m_validationPredictions = (double[][])stream.readObject();
+   * 
+   * if (m_Debug) System.out.println("Loaded
+   * "+m_validationPredictions.length+" indexed array"); }
+   * 
+   */
+  
+  /**
+   * getter for validation predictions
+   * 
+   * @return		the current validation predictions
+   */
+  public double[][] getValidationPredictions() {
+    return m_validationPredictions;
+  }
+  
+  /**
+   * setter for validation predictions
+   * 
+   * @param predictions	the new validation predictions
+   */
+  public void setValidationPredictions(double[][] predictions) {
+    if (m_Debug)
+      System.out.println("Saving validation array of size "
+	  + predictions.length);
+    m_validationPredictions = new double[predictions.length][];
+    System.arraycopy(predictions, 0, m_validationPredictions, 0,
+	predictions.length);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/ModelBag.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/ModelBag.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/ModelBag.java	(revision 29)
@@ -0,0 +1,647 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleSelection.java
+ *    Copyright (C) 2006 David Michael
+ *
+ */
+
+package weka.classifiers.meta.ensembleSelection;
+
+import weka.classifiers.Evaluation;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Random;
+
+/**
+ * This class is responsible for the duties of a bag of models. It is designed
+ * for use with the EnsembleSelection meta classifier. It handles shuffling the
+ * models, doing sort initialization, performing forward selection/ backwards
+ * elimination, etc.
+ * <p/>
+ * We utilize a simple "virtual indexing" scheme inside. If we shuffle and/or
+ * sort the models, we change the "virtual" order around. The elements of the
+ * bag are always those elements with virtual index 0..(m_bagSize-1). Each
+ * "virtual" index maps to some real index in m_models. Not every model in
+ * m_models gets a virtual index... the virtual indexing is what defines the
+ * subset of models of which our Bag is composed. This makes it easy to refer to
+ * models in the bag, by their virtual index, while maintaining the original
+ * indexing for our clients.
+ * 
+ * @author  David Michael
+ * @version $Revision: 1.2 $
+ */
+public class ModelBag
+  implements RevisionHandler {
+  
+  /**
+   * The "models", as a multidimensional array of predictions for the
+   * validation set. The first index is the model index, the second index is
+   * the index of the instance, and the third is the typical "class" index for
+   * a prediction's distribution. This is given to us in the constructor, and
+   * we never change it.
+   */
+  private double m_models[][][];
+  
+  /**
+   * Maps each model in our virtual indexing scheme to its original index as
+   * it is in m_models. The first m_bag_size elements here are considered our
+   * bag. Throughout the code, we use the index in to this array to refer to a
+   * model. When we shuffle the models, we really simply shuffle this array.
+   * When we want to refer back to the original model, it is easily looked up
+   * in this array. That is, if j = m_model_index[i], then m_models[j] is the
+   * model referred to by "virtual index" i. Models can easily be accessed by
+   * their virtual index using the "model()" method.
+   */
+  private int m_modelIndex[];
+  
+  /**
+   * The number of models in our bag. 1 <= m_bag_size <= m_models.length
+   */
+  private int m_bagSize;
+  
+  /**
+   * The total number of models chosen thus far for this bag. This value is
+   * important when calculating the predictions for the bag. (See
+   * computePredictions).
+   */
+  private int m_numChosen;
+  
+  /**
+   * The number of times each model has been chosen. Also can be thought of as
+   * the weight for each model. Indexed by the "virtual index".
+   */
+  private int m_timesChosen[];
+  
+  /**
+   * If true, print out debug information.
+   */
+  private boolean m_debug;
+  
+  /**
+   * Double representing the best performance achieved thus far in this bag.
+   * This Must be updated each time we make a change to the bag that improves
+   * performance. This is so that after all hillclimbing is completed, we can
+   * go back to the best ensemble that we encountered during hillclimbing.
+   */
+  private double m_bestPerformance;
+  
+  /**
+   * Array representing the weights for all the models which achieved the best
+   * performance thus far for the bag (i.e., the weights that achieved
+   * m_bestPerformance. This Must be updated each time we make a change to the
+   * bag (that improves performance, by calling updateBestTimesChosen. This is
+   * so that after all hillclimbing is completed, we can go back to the best
+   * ensemble that we encountered during hillclimbing. This array, unlike
+   * m_timesChosen, uses the original indexing as taken from m_models. That
+   * way, any time getModelWeights is called (which returns this array), the
+   * array is in the correct format for our client.
+   */
+  private int m_bestTimesChosen[];
+  
+  /**
+   * Constructor for ModelBag.
+   * 
+   * @param models
+   *            The complete set of models from which to draw our bag. First
+   *            index is for the model, second is for the instance. The last
+   *            is a prediction distribution for that instance. Models are
+   *            represented by this array of predictions for validation data,
+   *            since that's all ensemble selection needs to know.
+   * @param bag_percent
+   *            The percentage of the set of given models that should be used
+   *            in the Model Bag.
+   * @param debug
+   *            Whether the ModelBag should print debug information.
+   * 
+   */
+  public ModelBag(double models[][][], double bag_percent, boolean debug) {
+    m_debug = debug;
+    if (models.length == 0) {
+      throw new IllegalArgumentException(
+      "ModelBag needs at least 1 model.");
+    }
+    m_bagSize = (int) ((double) models.length * bag_percent);
+    m_models = models;
+    m_modelIndex = new int[m_models.length];
+    m_timesChosen = new int[m_models.length];
+    m_bestTimesChosen = m_timesChosen;
+    m_bestPerformance = 0.0;
+    
+    // Initially, no models are chosen.
+    m_numChosen = 0;
+    // Prepare our virtual indexing scheme. Initially, the indexes are
+    // the same as the original.
+    for (int i = 0; i < m_models.length; ++i) {
+      m_modelIndex[i] = i;
+      m_timesChosen[i] = 0;
+    }
+  }
+  
+  /**
+   * Swap model at virtual index i with model at virtual index j. This is used
+   * to shuffle the models. We do not change m_models, only the arrays which
+   * use the virtual indexing; m_modelIndex and m_timesChosen.
+   * 
+   * @param i	first index
+   * @param j	second index
+   */
+  private void swap(int i, int j) {
+    if (i != j) {
+      int temp_index = m_modelIndex[i];
+      m_modelIndex[i] = m_modelIndex[j];
+      m_modelIndex[j] = temp_index;
+      
+      int tempWeight = m_timesChosen[i];
+      m_timesChosen[i] = m_timesChosen[j];
+      m_timesChosen[j] = tempWeight;
+    }
+  }
+  
+  /**
+   * Shuffle the models. The order in m_models is preserved, but we change our
+   * virtual indexes around.
+   * 
+   * @param rand	the random number generator to use
+   */
+  public void shuffle(Random rand) {
+    if (m_models.length < 2)
+      return;
+    
+    for (int i = 0; i < m_models.length; ++i) {
+      int swap_index = rand.nextInt(m_models.length - 1);
+      if (swap_index >= i)
+	++swap_index; // don't swap with itself
+      swap(i, swap_index);
+    }
+  }
+  
+  /**
+   * Convert an array of weights using virtual indices to an array of weights
+   * using real indices.
+   * 
+   * @param virtual_weights	the virtual indices
+   * @return			the real indices
+   */
+  private int[] virtualToRealWeights(int virtual_weights[]) {
+    int real_weights[] = new int[virtual_weights.length];
+    for (int i = 0; i < real_weights.length; ++i) {
+      real_weights[m_modelIndex[i]] = virtual_weights[i];
+    }
+    return real_weights;
+  }
+  
+  /**
+   * 
+   */
+  private void updateBestTimesChosen() {
+    m_bestTimesChosen = virtualToRealWeights(m_timesChosen);
+  }
+  
+  /**
+   * Sort initialize the bag.
+   * 
+   * @param num
+   *            the Maximum number of models to initialize with
+   * @param greedy
+   *            True if we do greedy addition, up to num. Greedy sort
+   *            initialization adds models (up to num) in order of best to
+   *            worst performance until performance no longer improves.
+   * @param instances
+   *            the data set (needed for performance evaluation)
+   * @param metric
+   *            metric for which to optimize. See EnsembleMetricHelper
+   * @return returns an array of indexes which were selected, in order
+   *         starting from the model with best performance.
+   * @throws Exception if something goes wrong
+   */
+  public int[] sortInitialize(int num, boolean greedy, Instances instances,
+      int metric) throws Exception {
+    
+    // First, get the performance of each model
+    double performance[] = new double[m_bagSize];
+    for (int i = 0; i < m_bagSize; ++i) {
+      performance[i] = evaluatePredictions(instances, model(i), metric);
+    }
+    int bestModels[] = new int[num]; // we'll use this to save model info
+    // Now sort the models by their performance... note we only need the
+    // first "num",
+    // so we don't actually bother to sort the whole thing... instead, we
+    // pick the num best
+    // by running num iterations of selection sort.
+    for (int i = 0; i < num; ++i) {
+      int max_index = i;
+      double max_value = performance[i];
+      for (int j = i + 1; j < m_bagSize; ++j) {
+	// Find the best model which we haven't already selected
+	if (performance[j] > max_value) {
+	  max_value = performance[j];
+	  max_index = j;
+	}
+      }
+      // Swap ith model in to the ith position (selection sort)
+      this.swap(i, max_index);
+      // swap performance numbers, too
+      double temp_perf = performance[i];
+      performance[i] = performance[max_index];
+      performance[max_index] = temp_perf;
+      
+      bestModels[i] = m_modelIndex[i];
+      if (!greedy) {
+	// If we're not being greedy, we just throw the model in
+	// no matter what
+	++m_timesChosen[i];
+	++m_numChosen;
+      }
+    }
+    // Now the best "num" models are all sorted and in position.
+    if (greedy) {
+      // If the "greedy" option was specified, do a smart sort
+      // initialization
+      // that adds models only so long as they help overall performance.
+      // This is what was done in the original Caruana paper.
+      double[][] tempPredictions = null;
+      double bestPerformance = 0.0;
+      if (num > 0) {
+	++m_timesChosen[0];
+	++m_numChosen;
+	updateBestTimesChosen();
+      }
+      for (int i = 1; i < num; ++i) {
+	tempPredictions = computePredictions(i, true);
+	double metric_value = evaluatePredictions(instances,
+	    tempPredictions, metric);
+	if (metric_value > bestPerformance) {
+	  // If performance improved, update the appropriate info.
+	  bestPerformance = metric_value;
+	  ++m_timesChosen[i];
+	  ++m_numChosen;
+	  updateBestTimesChosen();
+	} else {
+	  // We found a model that doesn't help performance, so we
+	  // stop adding models.
+	  break;
+	}
+      }
+    }
+    updateBestTimesChosen();
+    if (m_debug) {
+      System.out.println("Sort Initialization added best " + m_numChosen
+	  + " models to the bag.");
+    }
+    return bestModels;
+  }
+  
+  /**
+   * Add "weight" to the number of times each model in the bag was chosen.
+   * Typically for use with backward elimination.
+   * 
+   * @param weight	the weight to add
+   */
+  public void weightAll(int weight) {
+    for (int i = 0; i < m_bagSize; ++i) {
+      m_timesChosen[i] += weight;
+      m_numChosen += weight;
+    }
+    updateBestTimesChosen();
+  }
+  
+  /**
+   * Forward select one model. Will add the model which has the best effect on
+   * performance. If replacement is false, and all models are chosen, no
+   * action is taken. If a model can be added, one always is (even if it hurts
+   * performance).
+   * 
+   * @param withReplacement
+   *            whether a model can be added more than once.
+   * @param instances
+   *            The dataset, for calculating performance.
+   * @param metric
+   *            The metric to which we will optimize. See EnsembleMetricHelper
+   * @throws Exception if something goes wrong
+   */
+  public void forwardSelect(boolean withReplacement, Instances instances,
+      int metric) throws Exception {
+    
+    double bestPerformance = -1.0;
+    int bestIndex = -1;
+    double tempPredictions[][];
+    for (int i = 0; i < m_bagSize; ++i) {
+      // For each model in the bag
+      if ((m_timesChosen[i] == 0) || withReplacement) {
+	// If the model has not been chosen, or we're allowing
+	// replacement
+	// Get the predictions we would have if we add this model to the
+	// ensemble
+	tempPredictions = computePredictions(i, true);
+	// And find out how the hypothetical ensemble would perform.
+	double metric_value = evaluatePredictions(instances,
+	    tempPredictions, metric);
+	if (metric_value > bestPerformance) {
+	  // If it's better than our current best, make it our NEW
+	  // best.
+	  bestIndex = i;
+	  bestPerformance = metric_value;
+	}
+      }
+    }
+    if (bestIndex == -1) {
+      // Replacement must be false, with more hillclimb iterations than
+      // models. Do nothing and return.
+      if (m_debug) {
+	System.out.println("Couldn't add model.  No action performed.");
+      }
+      return;
+    }
+    // We picked bestIndex as our best model. Update appropriate info.
+    m_timesChosen[bestIndex]++;
+    m_numChosen++;
+    if (bestPerformance > m_bestPerformance) {
+      // We find the peak of our performance over all hillclimb
+      // iterations.
+      // If this forwardSelect step improved our overall performance,
+      // update
+      // our best ensemble info.
+      updateBestTimesChosen();
+      m_bestPerformance = bestPerformance;
+    }
+  }
+  
+  /**
+   * Find the model whose removal will help the ensemble's performance the
+   * most, and remove it. If there is only one model left, we leave it in. If
+   * we can remove a model, we always do, even if it hurts performance.
+   * 
+   * @param instances
+   *            The data set, for calculating performance
+   * @param metric
+   *            Metric to optimize for. See EnsembleMetricHelper.
+   * @throws Exception if something goes wrong
+   */
+  public void backwardEliminate(Instances instances, int metric)
+  throws Exception {
+    
+    // Find the best model to remove. I.e., model for which removal improves
+    // performance the most (or hurts it least), and remove it.
+    if (m_numChosen <= 1) {
+      // If we only have one model left, keep it, as a bag
+      // which chooses no models doesn't make much sense.
+      return;
+    }
+    double bestPerformance = -1.0;
+    int bestIndex = -1;
+    double tempPredictions[][];
+    for (int i = 0; i < m_bagSize; ++i) {
+      // For each model in the bag
+      if (m_timesChosen[i] > 0) {
+	// If the model has been chosen at least once,
+	// Get the predictions we would have if we remove this model
+	tempPredictions = computePredictions(i, false);
+	// And find out how the hypothetical ensemble would perform.
+	double metric_value = evaluatePredictions(instances,
+	    tempPredictions, metric);
+	if (metric_value > bestPerformance) {
+	  // If it's better than our current best, make it our NEW
+	  // best.
+	  bestIndex = i;
+	  bestPerformance = metric_value;
+	}
+      }
+    }
+    if (bestIndex == -1) {
+      // The most likely cause of this is that we didn't have any models
+      // we could
+      // remove. Do nothing & return.
+      if (m_debug) {
+	System.out
+	.println("Couldn't remove model.  No action performed.");
+      }
+      return;
+    }
+    // We picked bestIndex as our best model. Update appropriate info.
+    m_timesChosen[bestIndex]--;
+    m_numChosen--;
+    if (m_debug) {
+      System.out.println("Removing model " + m_modelIndex[bestIndex]
+                                                          + " (" + bestIndex + ") " + bestPerformance);
+    }
+    if (bestPerformance > m_bestPerformance) {
+      // We find the peak of our performance over all hillclimb
+      // iterations.
+      // If this forwardSelect step improved our overall performance,
+      // update
+      // our best ensemble info.
+      updateBestTimesChosen();
+      m_bestPerformance = bestPerformance;
+    }
+    // return m_model_index[best_index]; //translate to original indexing
+    // and return
+  }
+  
+  /**
+   * Find the best action to perform, be it adding a model or removing a
+   * model, and perform it. Some action is always performed, even if it hurts
+   * performance.
+   * 
+   * @param with_replacement
+   *            whether we can add a model more than once
+   * @param instances
+   *            The dataset, for determining performance.
+   * @param metric
+   *            The metric for which to optimize. See EnsembleMetricHelper.
+   * @throws Exception if something goes wrong
+   */
+  public void forwardSelectOrBackwardEliminate(boolean with_replacement,
+      Instances instances, int metric) throws Exception {
+    
+    // Find the best action to perform, be it adding a model or removing a
+    // model,
+    // and do it.
+    double bestPerformance = -1.0;
+    int bestIndex = -1;
+    boolean added = true;
+    double tempPredictions[][];
+    for (int i = 0; i < m_bagSize; ++i) {
+      // For each model in the bag:
+      // Try removing the model
+      if (m_timesChosen[i] > 0) {
+	// If the model has been chosen at least once,
+	// Get the predictions we would have if we remove this model
+	tempPredictions = computePredictions(i, false);
+	// And find out how the hypothetical ensemble would perform.
+	double metric_value = evaluatePredictions(instances,
+	    tempPredictions, metric);
+	if (metric_value > bestPerformance) {
+	  // If it's better than our current best, make it our NEW
+	  // best.
+	  bestIndex = i;
+	  bestPerformance = metric_value;
+	  added = false;
+	}
+      }
+      if ((m_timesChosen[i] == 0) || with_replacement) {
+	// If the model hasn't been chosen, or if we can choose it more
+	// than once, try adding it:
+	// Get the predictions we would have if we added the model
+	tempPredictions = computePredictions(i, true);
+	// And find out how the hypothetical ensemble would perform.
+	double metric_value = evaluatePredictions(instances,
+	    tempPredictions, metric);
+	if (metric_value > bestPerformance) {
+	  // If it's better than our current best, make it our NEW
+	  // best.
+	  bestIndex = i;
+	  bestPerformance = metric_value;
+	  added = true;
+	}
+      }
+    }
+    if (bestIndex == -1) {
+      // Shouldn't really happen. Possible (I think) if the model bag is
+      // empty. Just return.
+      if (m_debug) {
+	System.out.println("Couldn't add or remove model.  No action performed.");
+      }
+      return;
+    }
+    // Now we've found the best change to make:
+    // * bestIndex is the (virtual) index of the model we should change
+    // * added is true if the model should be added (false if should be
+    // removed)
+    int changeInWeight = added ? 1 : -1;
+    m_timesChosen[bestIndex] += changeInWeight;
+    m_numChosen += changeInWeight;
+    if (bestPerformance > m_bestPerformance) {
+      // We find the peak of our performance over all hillclimb
+      // iterations.
+      // If this forwardSelect step improved our overall performance,
+      // update
+      // our best ensemble info.
+      updateBestTimesChosen();
+      m_bestPerformance = bestPerformance;
+    }
+  }
+  
+  /**
+   * returns the model weights
+   * 
+   * @return		the model weights
+   */
+  public int[] getModelWeights() {
+    return m_bestTimesChosen;
+  }
+  
+  /**
+   * Returns the "model" at the given virtual index. Here, by "model" we mean
+   * its predictions with respect to the validation set. This is just a
+   * convenience method, since we use the "virtual" index more than the real
+   * one inside this class.
+   * 
+   * @param index
+   *            the "virtual" index - the one for internal use
+   * @return the predictions for the model for all validation instances.
+   */
+  private double[][] model(int index) {
+    return m_models[m_modelIndex[index]];
+  }
+  
+  /**
+   * Compute predictions based on the current model, adding (or removing) the
+   * model at the given (internal) index.
+   * 
+   * @param index_to_change
+   *            index of model we're adding or removing
+   * @param add
+   *            whether we add it. If false, we remove it.
+   * @return the predictions for all validation instances
+   */
+  private double[][] computePredictions(int index_to_change, boolean add) {
+    double[][] predictions = new double[m_models[0].length][m_models[0][0].length];
+    for (int i = 0; i < m_bagSize; ++i) {
+      if (m_timesChosen[i] > 0) {
+	for (int j = 0; j < m_models[0].length; ++j) {
+	  for (int k = 0; k < m_models[0][j].length; ++k) {
+	    predictions[j][k] += model(i)[j][k] * m_timesChosen[i];
+	  }
+	}
+      }
+    }
+    for (int j = 0; j < m_models[0].length; ++j) {
+      int change = add ? 1 : -1;
+      for (int k = 0; k < m_models[0][j].length; ++k) {
+	predictions[j][k] += change * model(index_to_change)[j][k];
+	predictions[j][k] /= (m_numChosen + change);
+      }
+    }
+    return predictions;
+  }
+  
+  /**
+   * Return the performance of the given predictions on the given instances
+   * with respect to the given metric (see EnsembleMetricHelper).
+   * 
+   * @param instances
+   *            the validation data
+   * @param temp_predictions
+   *            the predictions to evaluate
+   * @param metric
+   *            the metric for which to optimize (see EnsembleMetricHelper)
+   * @return the performance
+   * @throws Exception if something goes wrong
+   */
+  private double evaluatePredictions(Instances instances,
+      double[][] temp_predictions, int metric) throws Exception {
+    
+    Evaluation eval = new Evaluation(instances);
+    for (int i = 0; i < instances.numInstances(); ++i) {
+      eval.evaluateModelOnceAndRecordPrediction(temp_predictions[i],
+	  instances.instance(i));
+    }
+    return EnsembleMetricHelper.getMetric(eval, metric);
+  }
+  
+  /**
+   * Gets the individual performances of all the models in the bag.
+   * 
+   * @param instances
+   *            The validation data, for which we want performance.
+   * @param metric
+   *            The desired metric (see EnsembleMetricHelper).
+   * @return the performance
+   * @throws Exception if something goes wrong
+   */
+  public double[] getIndividualPerformance(Instances instances, int metric)
+    throws Exception {
+    
+    double[] performance = new double[m_bagSize];
+    for (int i = 0; i < m_bagSize; ++i) {
+      performance[i] = evaluatePredictions(instances, model(i), metric);
+    }
+    return performance;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_binary_class.model.xml
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_binary_class.model.xml	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_binary_class.model.xml	(revision 29)
@@ -0,0 +1,20409 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE object
+[
+   <!ELEMENT object (#PCDATA | object)*>
+   <!ATTLIST object name      CDATA #REQUIRED>
+   <!ATTLIST object class     CDATA #REQUIRED>
+   <!ATTLIST object primitive CDATA "no">
+   <!ATTLIST object array     CDATA "no">   <!-- the dimensions of the array; no=0, yes=1 -->
+   <!ATTLIST object null      CDATA "no">
+   <!ATTLIST object version   CDATA "3.7.0">
+]
+>
+
+<object class="java.util.Vector" name="__root__" version="3.5.3">
+   <object class="weka.classifiers.bayes.BayesNet" name="0">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="1">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="2">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="3">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="4">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="5">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="6">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="7">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="8">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="9">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="10">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">AIC</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="11">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="12">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BDeu</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="13">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">ENTROPY</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="14">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">MDL</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="15">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="16">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="17">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="18">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="19">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="20">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="21">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="22">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="23">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="24">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="25">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">AIC</object>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="26">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">BAYES</object>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="27">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">BDeu</object>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="28">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">ENTROPY</object>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="29">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">-P</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">MDL</object>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="30">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="31">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="32">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="33">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="34">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="35">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="36">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="37">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="38">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="39">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-R</object>
+         <object class="java.lang.String" name="5">-P</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14">-E</object>
+         <object class="java.lang.String" name="15">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="16">--</object>
+         <object class="java.lang.String" name="17">-A</object>
+         <object class="java.lang.String" name="18">0.5</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="40">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="41">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="42">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="43">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="44">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="45">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">AIC</object>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="46">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">BAYES</object>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="47">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">BDeu</object>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="48">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">ENTROPY</object>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="49">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-R</object>
+         <object class="java.lang.String" name="8">-mbc</object>
+         <object class="java.lang.String" name="9">-S</object>
+         <object class="java.lang.String" name="10">MDL</object>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="50">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="51">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="52">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="53">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="54">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="55">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="56">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="57">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="58">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="59">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="60">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="61">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="62">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="63">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="64">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="65">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">AIC</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="66">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BAYES</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="67">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">BDeu</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="68">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">ENTROPY</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="69">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-R</object>
+         <object class="java.lang.String" name="7">-mbc</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">MDL</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="70">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">AIC</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="71">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.MultiNomialBMAEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="72">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.MultiNomialBMAEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-k2</object>
+         <object class="java.lang.String" name="15">-A</object>
+         <object class="java.lang.String" name="16">0.5</object>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="73">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.20000000298023224</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="74">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.4000000059604645</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="75">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="76">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.6000000238418579</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="77">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BAYES</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.800000011920929</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="78">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">BDeu</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="79">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">ENTROPY</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="80">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">MDL</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="81">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">AIC</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="82">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BAYES</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="83">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">BDeu</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="84">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">ENTROPY</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="85">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.K2</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-mbc</object>
+         <object class="java.lang.String" name="7">-S</object>
+         <object class="java.lang.String" name="8">MDL</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11">-E</object>
+         <object class="java.lang.String" name="12">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="13">--</object>
+         <object class="java.lang.String" name="14">-A</object>
+         <object class="java.lang.String" name="15">0.5</object>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="86">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-S</object>
+         <object class="java.lang.String" name="5">AIC</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="87">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-S</object>
+         <object class="java.lang.String" name="5">BAYES</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="88">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-S</object>
+         <object class="java.lang.String" name="5">BDeu</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="89">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-S</object>
+         <object class="java.lang.String" name="5">ENTROPY</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="90">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-S</object>
+         <object class="java.lang.String" name="5">MDL</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="91">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-mbc</object>
+         <object class="java.lang.String" name="5">-S</object>
+         <object class="java.lang.String" name="6">AIC</object>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="92">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-mbc</object>
+         <object class="java.lang.String" name="5">-S</object>
+         <object class="java.lang.String" name="6">BAYES</object>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="93">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-mbc</object>
+         <object class="java.lang.String" name="5">-S</object>
+         <object class="java.lang.String" name="6">BDeu</object>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="94">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-mbc</object>
+         <object class="java.lang.String" name="5">-S</object>
+         <object class="java.lang.String" name="6">ENTROPY</object>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.BayesNet" name="95">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1">-Q</object>
+         <object class="java.lang.String" name="2">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object class="java.lang.String" name="3">--</object>
+         <object class="java.lang.String" name="4">-mbc</object>
+         <object class="java.lang.String" name="5">-S</object>
+         <object class="java.lang.String" name="6">MDL</object>
+         <object class="java.lang.String" name="7">-E</object>
+         <object class="java.lang.String" name="8">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object class="java.lang.String" name="9">--</object>
+         <object class="java.lang.String" name="10">-A</object>
+         <object class="java.lang.String" name="11">0.5</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.NaiveBayes" name="96">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0"/>
+         <object class="java.lang.String" name="1"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.NaiveBayes" name="97">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+         <object class="java.lang.String" name="1"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.NaiveBayes" name="98">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.bayes.NaiveBayesMultinomial" name="99">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.bayes.NaiveBayesSimple" name="100">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="101">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">0.0010</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="102">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">0.01</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="103">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="104">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1.0</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="105">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1.0E-4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="106">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1.0E-5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="107">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1.0E-6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="108">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1.0E-7</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="109">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">10.0</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="110">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">100.0</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="111">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">1000.0</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.Logistic" name="112">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">10000.0</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">-1</object>
+         <object class="java.lang.String" name="4"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="113">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="114">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="115">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="116">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="117">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="118">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="119">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="120">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="121">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="122">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="123">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="124">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="125">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="126">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="127">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="128">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.0125</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="129">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="130">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="131">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="132">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="133">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="134">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="135">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="136">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="137">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="138">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="139">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="140">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="141">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="142">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="143">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="144">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.025</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="145">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="146">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="147">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="148">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="149">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="150">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="151">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="152">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="153">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="154">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="155">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="156">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="157">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="158">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="159">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="160">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.05</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="161">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="162">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="163">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="164">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="165">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="166">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="167">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="168">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="169">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="170">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="171">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="172">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="173">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="174">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="175">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="176">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="177">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="178">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="179">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="180">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="181">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="182">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="183">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="184">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="185">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="186">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="187">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="188">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="189">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="190">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="191">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="192">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="193">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="194">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="195">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="196">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="197">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="198">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="199">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="200">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="201">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="202">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="203">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="204">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="205">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="206">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="207">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="208">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="209">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="210">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="211">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="212">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.0</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="213">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="214">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="215">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="216">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.3</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="217">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="218">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="219">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="220">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.6</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="221">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">16</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="222">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">4</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="223">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">64</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.MultilayerPerceptron" name="224">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-L</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">0.9</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">500</object>
+         <object class="java.lang.String" name="6">-V</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-S</object>
+         <object class="java.lang.String" name="9">0</object>
+         <object class="java.lang.String" name="10">-E</object>
+         <object class="java.lang.String" name="11">20</object>
+         <object class="java.lang.String" name="12">-H</object>
+         <object class="java.lang.String" name="13">a</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="225">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.0010</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="226">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.01</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="227">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="228">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1.0</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="229">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1.0E-4</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="230">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1.0E-5</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="231">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1.0E-6</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="232">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1.0E-7</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="233">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">10.0</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="234">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">100.0</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.functions.SMO" name="235">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">1000.0</object>
+         <object class="java.lang.String" name="2">-L</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">1.0E-12</object>
+         <object class="java.lang.String" name="6">-N</object>
+         <object class="java.lang.String" name="7">0</object>
+         <object class="java.lang.String" name="8">-V</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-W</object>
+         <object class="java.lang.String" name="11">1</object>
+         <object class="java.lang.String" name="12">-K</object>
+         <object class="java.lang.String" name="13">weka.classifiers.functions.supportVector.PolyKernel -C 250007 -E 1.0</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="236">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">1</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="237">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">1</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="238">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">11</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="239">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">11</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.LinearNN -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="240">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">13</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="241">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">13</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="242">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">149</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="243">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">149</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="244">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">15</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="245">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">15</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="246">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">17</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="247">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">17</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="248">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">19</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="249">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">19</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="250">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">249</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="251">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">249</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="252">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">25</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="253">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">25</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="254">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="255">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="256">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">399</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="257">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">399</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="258">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">49</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="259">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">49</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="260">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">5</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="261">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">5</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="262">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">7</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="263">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">7</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="264">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">9</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="265">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">9</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="266">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">99</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.lazy.IBk" name="267">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-K</object>
+         <object class="java.lang.String" name="1">99</object>
+         <object class="java.lang.String" name="2">-W</object>
+         <object class="java.lang.String" name="3">0</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="268">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="269">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="270">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="271">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="272">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="273">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">128</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="274">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="275">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="276">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="277">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="278">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="279">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="280">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="281">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="282">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="283">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="284">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="285">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="286">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="287">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="288">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="289">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="290">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="291">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">256</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="292">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="293">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="294">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="295">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="296">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="297">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">32</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="298">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="299">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="300">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="301">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="302">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="303">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="304">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="305">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="306">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="307">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="308">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="309">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">64</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="310">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="311">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-B</object>
+         <object class="java.lang.String" name="12">-M</object>
+         <object class="java.lang.String" name="13">2</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="312">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-C</object>
+         <object class="java.lang.String" name="10">0.25</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="313">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="314">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.AdaBoostM1" name="315">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="316">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.DecisionStump</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="317">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+         <object class="java.lang.String" name="23"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="318">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-B</object>
+         <object class="java.lang.String" name="11">-M</object>
+         <object class="java.lang.String" name="12">2</object>
+         <object class="java.lang.String" name="13">-A</object>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+         <object class="java.lang.String" name="23"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="319">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+         <object class="java.lang.String" name="23"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="320">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.J48</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-U</object>
+         <object class="java.lang.String" name="10">-M</object>
+         <object class="java.lang.String" name="11">2</object>
+         <object class="java.lang.String" name="12">-A</object>
+         <object class="java.lang.String" name="13"/>
+         <object class="java.lang.String" name="14"/>
+         <object class="java.lang.String" name="15"/>
+         <object class="java.lang.String" name="16"/>
+         <object class="java.lang.String" name="17"/>
+         <object class="java.lang.String" name="18"/>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+         <object class="java.lang.String" name="22"/>
+         <object class="java.lang.String" name="23"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.meta.Bagging" name="321">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-P</object>
+         <object class="java.lang.String" name="1">100</object>
+         <object class="java.lang.String" name="2">-S</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-I</object>
+         <object class="java.lang.String" name="5">100</object>
+         <object class="java.lang.String" name="6">-W</object>
+         <object class="java.lang.String" name="7">weka.classifiers.trees.REPTree</object>
+         <object class="java.lang.String" name="8">--</object>
+         <object class="java.lang.String" name="9">-M</object>
+         <object class="java.lang.String" name="10">2</object>
+         <object class="java.lang.String" name="11">-V</object>
+         <object class="java.lang.String" name="12">0.0010</object>
+         <object class="java.lang.String" name="13">-N</object>
+         <object class="java.lang.String" name="14">3</object>
+         <object class="java.lang.String" name="15">-S</object>
+         <object class="java.lang.String" name="16">1</object>
+         <object class="java.lang.String" name="17">-L</object>
+         <object class="java.lang.String" name="18">-1</object>
+         <object class="java.lang.String" name="19"/>
+         <object class="java.lang.String" name="20"/>
+         <object class="java.lang.String" name="21"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.HyperPipes" name="322">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="323">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="324">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="325">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="326">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">0.8</object>
+         <object class="java.lang.String" name="2"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="327">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">0.2</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="328">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">0.4</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="329">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">0.6</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.misc.VFI" name="330">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">0.8</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="331">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="332">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-E</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="333">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="334">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8">-E</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="335">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="336">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-E</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="337">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="338">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8">-E</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="339">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">5</object>
+         <object class="java.lang.String" name="8"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ConjunctiveRule" name="340">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-N</object>
+         <object class="java.lang.String" name="1">3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2.0</object>
+         <object class="java.lang.String" name="4">-P</object>
+         <object class="java.lang.String" name="5">-1</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">5</object>
+         <object class="java.lang.String" name="8">-E</object>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="341">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="342">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="343">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="344">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="345">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="346">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="347">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="348">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="349">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-B</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="350">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.25</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="351">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.5</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="352">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.75</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="353">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.25</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="354">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.5</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="355">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.75</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="356">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.25</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="357">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.5</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="358">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-C</object>
+         <object class="java.lang.String" name="3">0.75</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="359">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="360">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="361">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="362">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-N</object>
+         <object class="java.lang.String" name="4">3</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="363">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-N</object>
+         <object class="java.lang.String" name="4">3</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="364">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-N</object>
+         <object class="java.lang.String" name="4">3</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="365">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.25</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="366">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.5</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="367">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.75</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="368">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.25</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="369">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.5</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="370">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.75</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="371">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.25</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="372">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.5</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="373">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-C</object>
+         <object class="java.lang.String" name="5">0.75</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="374">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="375">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="376">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="377">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="378">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="379">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="380">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.25</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="381">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.5</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="382">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-C</object>
+         <object class="java.lang.String" name="4">0.75</object>
+         <object class="java.lang.String" name="5">-Q</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="383">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">3</object>
+         <object class="java.lang.String" name="7">-Q</object>
+         <object class="java.lang.String" name="8">1</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="384">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">3</object>
+         <object class="java.lang.String" name="7">-Q</object>
+         <object class="java.lang.String" name="8">1</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="385">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-N</object>
+         <object class="java.lang.String" name="6">3</object>
+         <object class="java.lang.String" name="7">-Q</object>
+         <object class="java.lang.String" name="8">1</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="386">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="387">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.PART" name="388">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-Q</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.rules.ZeroR" name="389">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.trees.DecisionStump" name="390">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="391">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="392">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="393">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="394">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="395">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="396">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="397">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="398">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="399">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="400">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="401">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="402">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="403">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="404">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="405">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="406">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="407">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="408">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="409">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="410">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.1</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="411">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="412">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="413">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="414">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="415">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="416">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="417">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="418">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="419">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="420">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="421">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="422">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="423">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="424">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="425">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="426">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="427">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="428">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="429">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="430">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.2</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="431">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="432">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="433">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="434">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="435">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="436">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="437">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="438">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="439">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="440">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="441">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="442">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="443">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="444">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="445">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="446">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="447">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="448">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="449">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="450">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.3</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="451">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="452">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="453">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="454">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="455">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="456">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="457">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="458">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="459">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="460">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="461">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="462">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="463">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="464">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="465">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="466">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="467">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="468">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="469">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="470">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.4</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="471">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="472">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="473">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="474">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="475">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="476">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="477">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="478">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="479">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="480">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="481">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="482">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="483">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="484">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="485">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="486">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="487">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="488">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="489">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="490">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.5</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="491">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="492">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="493">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="494">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="495">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="496">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="497">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="498">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="499">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="500">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="501">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="502">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="503">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="504">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="505">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="506">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="507">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="508">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="509">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="510">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.6</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="511">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="512">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="513">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="514">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="515">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="516">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="517">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="518">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="519">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="520">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="521">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="522">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="523">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="524">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="525">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="526">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="527">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="528">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="529">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="530">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.7000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="531">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="532">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="533">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="534">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="535">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="536">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="537">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="538">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="539">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="540">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="541">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="542">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="543">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="544">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="545">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="546">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="547">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="548">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="549">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="550">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.8000001</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="551">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="552">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="553">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="554">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="555">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="556">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="557">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="558">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="559">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="560">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-B</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="561">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="562">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="563">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="564">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="565">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="566">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="567">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="568">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="569">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="570">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-C</object>
+         <object class="java.lang.String" name="1">0.9</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="571">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="572">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="573">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">16</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="574">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">16</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="575">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="576">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="577">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="578">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="579">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">8</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="580">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-B</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">8</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="581">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="582">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">1</object>
+         <object class="java.lang.String" name="7">-A</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="583">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">16</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="584">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">16</object>
+         <object class="java.lang.String" name="7">-A</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="585">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">2</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="586">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">2</object>
+         <object class="java.lang.String" name="7">-A</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="587">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">4</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="588">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">4</object>
+         <object class="java.lang.String" name="7">-A</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="589">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">8</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="590">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-R</object>
+         <object class="java.lang.String" name="1">-N</object>
+         <object class="java.lang.String" name="2">3</object>
+         <object class="java.lang.String" name="3">-Q</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-M</object>
+         <object class="java.lang.String" name="6">8</object>
+         <object class="java.lang.String" name="7">-A</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="591">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="592">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="593">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="594">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="595">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="596">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="597">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="598">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="599">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="600">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="601">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="602">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="603">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="604">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="605">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="606">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="607">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="608">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="609">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="610">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.1</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="611">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="612">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="613">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="614">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="615">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="616">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="617">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="618">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="619">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="620">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="621">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="622">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="623">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="624">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="625">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="626">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="627">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="628">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="629">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="630">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.2</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="631">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="632">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="633">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="634">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="635">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="636">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="637">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="638">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="639">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="640">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="641">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="642">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="643">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="644">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="645">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="646">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="647">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="648">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="649">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="650">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.3</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="651">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="652">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="653">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="654">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="655">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="656">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="657">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="658">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="659">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="660">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="661">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="662">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="663">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="664">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="665">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="666">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="667">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="668">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="669">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="670">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.4</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="671">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="672">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="673">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="674">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="675">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="676">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="677">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="678">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="679">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="680">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="681">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="682">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="683">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="684">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="685">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="686">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="687">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="688">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="689">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="690">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="691">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="692">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="693">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="694">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="695">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="696">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="697">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="698">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="699">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="700">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="701">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="702">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="703">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="704">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="705">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="706">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="707">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="708">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="709">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="710">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.6</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="711">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="712">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="713">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="714">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="715">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="716">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="717">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="718">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="719">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="720">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="721">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="722">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="723">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="724">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="725">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="726">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="727">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="728">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="729">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="730">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.7000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="731">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="732">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="733">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="734">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="735">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="736">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="737">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="738">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="739">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="740">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="741">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="742">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="743">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="744">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="745">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="746">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="747">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="748">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="749">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="750">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.8000001</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="751">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="752">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="753">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="754">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">16</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="755">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="756">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="757">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="758">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">4</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="759">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="760">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">8</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="761">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="762">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">1</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="763">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="764">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">16</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="765">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="766">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="767">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="768">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">4</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="769">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="770">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.9</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">8</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="771">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">1</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="772">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">1</object>
+         <object class="java.lang.String" name="9">-A</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="773">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">16</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="774">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">16</object>
+         <object class="java.lang.String" name="9">-A</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="775">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">2</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="776">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">2</object>
+         <object class="java.lang.String" name="9">-A</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="777">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">4</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="778">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">4</object>
+         <object class="java.lang.String" name="9">-A</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="779">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">8</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="780">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-B</object>
+         <object class="java.lang.String" name="7">-M</object>
+         <object class="java.lang.String" name="8">8</object>
+         <object class="java.lang.String" name="9">-A</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="781">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="782">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="783">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">16</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="784">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">16</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="785">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="786">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">2</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="787">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="788">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">4</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="789">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">8</object>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="790">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-R</object>
+         <object class="java.lang.String" name="2">-N</object>
+         <object class="java.lang.String" name="3">3</object>
+         <object class="java.lang.String" name="4">-Q</object>
+         <object class="java.lang.String" name="5">1</object>
+         <object class="java.lang.String" name="6">-M</object>
+         <object class="java.lang.String" name="7">8</object>
+         <object class="java.lang.String" name="8">-A</object>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="791">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="792">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">1</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="793">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="794">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">16</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="795">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="796">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="797">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="798">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">4</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="799">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="800">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">8</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="801">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">1</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="802">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">1</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="803">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">16</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="804">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">16</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="805">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="806">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="807">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="808">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">4</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="809">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="810">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">8</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.NBTree" name="811">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="812">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="813">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="814">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="815">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="816">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="817">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="818">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="819">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="820">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="821">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="822">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="823">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="824">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="825">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="826">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="827">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="828">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="829">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="830">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="831">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="832">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="833">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="834">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="835">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="836">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="837">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="838">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="839">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="840">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="841">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="842">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="843">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="844">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="845">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="846">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="847">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="848">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="849">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="850">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="851">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="852">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="853">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="854">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="855">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="856">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="857">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="858">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="859">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="860">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="861">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="862">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="863">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="864">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="865">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="866">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="867">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="868">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="869">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="870">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="871">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="872">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="873">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="874">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="875">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="876">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="877">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="878">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="879">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="880">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="881">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="882">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="883">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">16</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="884">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="885">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="886">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="887">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="888">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="889">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="890">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="891">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="892">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="893">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="894">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="895">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="896">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="897">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="898">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="899">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="900">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="901">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="902">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="903">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="904">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="905">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="906">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="907">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="908">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="909">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="910">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="911">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="912">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="913">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="914">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="915">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="916">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="917">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="918">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="919">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="920">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="921">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="922">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="923">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="924">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="925">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="926">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="927">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="928">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="929">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="930">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="931">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="932">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="933">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="934">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="935">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="936">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="937">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="938">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="939">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="940">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="941">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="942">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="943">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="944">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="945">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="946">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="947">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="948">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="949">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="950">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="951">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="952">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="953">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="954">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="955">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">2</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="956">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="957">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="958">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="959">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="960">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="961">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="962">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="963">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="964">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="965">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="966">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="967">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="968">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="969">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="970">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="971">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="972">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="973">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="974">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="975">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="976">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="977">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="978">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="979">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="980">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="981">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="982">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="983">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="984">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="985">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="986">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="987">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="988">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="989">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="990">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="991">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="992">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="993">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="994">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="995">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="996">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="997">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="998">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="999">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1000">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1001">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1002">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1003">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1004">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1005">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1006">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1007">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1008">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1009">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1010">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1011">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1012">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1013">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1014">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1015">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1016">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1017">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1018">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1019">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1020">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1021">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1022">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1023">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1024">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1025">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1026">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1027">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">4</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1028">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1029">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1030">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1031">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1032">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1033">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1034">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1035">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1036">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1037">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1038">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1039">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1040">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1041">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1042">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1043">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1044">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1045">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1046">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1047">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1048">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1049">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1050">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1051">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.0010</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1052">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1053">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1054">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1055">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1056">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1057">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1058">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1059">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1060">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1061">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1062">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1063">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1064">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1065">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1066">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1067">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1068">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1069">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1070">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1071">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1072">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1073">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1074">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1075">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.01</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1076">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1077">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1078">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1079">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1080">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1081">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1082">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1083">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1084">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1085">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1086">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1087">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">1</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1088">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1089">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">-1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1090">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1091">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">1</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1092">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1093">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">3</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1094">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1095">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">5</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1096">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1097">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">7</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1098">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.REPTree" name="1099">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-M</object>
+         <object class="java.lang.String" name="1">8</object>
+         <object class="java.lang.String" name="2">-V</object>
+         <object class="java.lang.String" name="3">0.1</object>
+         <object class="java.lang.String" name="4">-N</object>
+         <object class="java.lang.String" name="5">3</object>
+         <object class="java.lang.String" name="6">-S</object>
+         <object class="java.lang.String" name="7">3</object>
+         <object class="java.lang.String" name="8">-L</object>
+         <object class="java.lang.String" name="9">9</object>
+         <object class="java.lang.String" name="10">-P</object>
+         <object class="java.lang.String" name="11"/>
+      </object>
+   </object>
+</object>
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_multi_class.model.xml
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_multi_class.model.xml	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/large_multi_class.model.xml	(revision 29)
@@ -0,0 +1,19001 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE object
+[
+   <!ELEMENT object (#PCDATA | object)*>
+   <!ATTLIST object name      CDATA #REQUIRED>
+   <!ATTLIST object class     CDATA #REQUIRED>
+   <!ATTLIST object primitive CDATA "no">
+   <!ATTLIST object array     CDATA "no">   <!-- the dimensions of the array; no=0, yes=1 -->
+   <!ATTLIST object null      CDATA "no">
+   <!ATTLIST object version   CDATA "3.7.0">
+]
+>
+
+<object version="3.5.3" name="__root__" class="java.util.Vector">
+   <object name="0" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="2" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="3" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="4" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="5" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="6" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="7" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="8" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="9" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="10" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">AIC</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="11" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="12" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BDeu</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="13" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">ENTROPY</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="14" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">MDL</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="15" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="16" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="17" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="18" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="19" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="20" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="21" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="22" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="23" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="24" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="25" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">AIC</object>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="26" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">BAYES</object>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="27" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">BDeu</object>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="28" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">ENTROPY</object>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="29" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">-P</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">MDL</object>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="30" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="31" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="32" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="33" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="34" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="35" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="36" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="37" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="38" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="39" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.HillClimber</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-R</object>
+         <object name="5" class="java.lang.String">-P</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String">-E</object>
+         <object name="15" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="16" class="java.lang.String">--</object>
+         <object name="17" class="java.lang.String">-A</object>
+         <object name="18" class="java.lang.String">0.5</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="40" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="41" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="42" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="43" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="44" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="45" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">AIC</object>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="46" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">BAYES</object>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="47" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">BDeu</object>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="48" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">ENTROPY</object>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="49" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-R</object>
+         <object name="8" class="java.lang.String">-mbc</object>
+         <object name="9" class="java.lang.String">-S</object>
+         <object name="10" class="java.lang.String">MDL</object>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="50" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="51" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="52" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="53" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="54" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="55" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="56" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="57" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="58" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="59" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-N</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="60" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="61" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="62" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="63" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="64" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="65" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">AIC</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="66" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BAYES</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="67" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">BDeu</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="68" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">ENTROPY</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="69" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-R</object>
+         <object name="7" class="java.lang.String">-mbc</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">MDL</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="70" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">AIC</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="71" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.20000000298023224</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="72" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.4000000059604645</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="73" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="74" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.6000000238418579</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="75" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BAYES</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.800000011920929</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="76" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">BDeu</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="77" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">ENTROPY</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="78" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">MDL</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="79" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">AIC</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="80" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BAYES</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="81" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">BDeu</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="82" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">ENTROPY</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="83" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.K2</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-mbc</object>
+         <object name="7" class="java.lang.String">-S</object>
+         <object name="8" class="java.lang.String">MDL</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String">-E</object>
+         <object name="12" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="13" class="java.lang.String">--</object>
+         <object name="14" class="java.lang.String">-A</object>
+         <object name="15" class="java.lang.String">0.5</object>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="84" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-S</object>
+         <object name="5" class="java.lang.String">AIC</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="85" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-S</object>
+         <object name="5" class="java.lang.String">BAYES</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="86" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-S</object>
+         <object name="5" class="java.lang.String">BDeu</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="87" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-S</object>
+         <object name="5" class="java.lang.String">ENTROPY</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="88" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-S</object>
+         <object name="5" class="java.lang.String">MDL</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="89" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-mbc</object>
+         <object name="5" class="java.lang.String">-S</object>
+         <object name="6" class="java.lang.String">AIC</object>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="90" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-mbc</object>
+         <object name="5" class="java.lang.String">-S</object>
+         <object name="6" class="java.lang.String">BAYES</object>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="91" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-mbc</object>
+         <object name="5" class="java.lang.String">-S</object>
+         <object name="6" class="java.lang.String">BDeu</object>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="92" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-mbc</object>
+         <object name="5" class="java.lang.String">-S</object>
+         <object name="6" class="java.lang.String">ENTROPY</object>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="93" class="weka.classifiers.bayes.BayesNet">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String">-Q</object>
+         <object name="2" class="java.lang.String">weka.classifiers.bayes.net.search.local.TAN</object>
+         <object name="3" class="java.lang.String">--</object>
+         <object name="4" class="java.lang.String">-mbc</object>
+         <object name="5" class="java.lang.String">-S</object>
+         <object name="6" class="java.lang.String">MDL</object>
+         <object name="7" class="java.lang.String">-E</object>
+         <object name="8" class="java.lang.String">weka.classifiers.bayes.net.estimate.SimpleEstimator</object>
+         <object name="9" class="java.lang.String">--</object>
+         <object name="10" class="java.lang.String">-A</object>
+         <object name="11" class="java.lang.String">0.5</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="94" class="weka.classifiers.bayes.NaiveBayes">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String"/>
+         <object name="1" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="95" class="weka.classifiers.bayes.NaiveBayes">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-D</object>
+         <object name="1" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="96" class="weka.classifiers.bayes.NaiveBayes">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="97" class="weka.classifiers.bayes.NaiveBayesMultinomial">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="98" class="weka.classifiers.bayes.NaiveBayesSimple">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="99" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">0.0010</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="100" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">0.01</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="101" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="102" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1.0</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="103" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1.0E-4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="104" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1.0E-5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="105" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1.0E-6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="106" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1.0E-7</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="107" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">10.0</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="108" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">100.0</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="109" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">1000.0</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="110" class="weka.classifiers.functions.Logistic">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">10000.0</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">-1</object>
+         <object name="4" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="111" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="112" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="113" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="114" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="115" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="116" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="117" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="118" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="119" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="120" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="121" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="122" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="123" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="124" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="125" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="126" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.0125</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="127" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="128" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="129" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="130" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="131" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="132" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="133" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="134" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="135" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="136" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="137" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="138" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="139" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="140" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="141" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="142" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.025</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="143" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="144" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="145" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="146" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="147" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="148" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="149" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="150" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="151" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="152" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="153" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="154" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="155" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="156" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="157" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="158" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.05</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="159" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="160" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="161" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="162" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="163" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="164" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="165" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="166" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="167" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="168" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="169" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="170" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="171" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="172" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="173" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="174" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="175" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="176" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="177" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="178" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="179" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="180" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="181" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="182" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="183" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="184" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="185" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="186" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="187" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="188" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="189" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="190" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="191" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="192" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="193" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="194" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="195" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="196" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="197" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="198" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="199" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="200" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="201" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="202" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="203" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="204" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="205" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="206" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="207" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="208" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="209" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="210" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.0</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="211" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="212" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="213" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="214" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.3</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="215" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="216" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="217" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="218" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.6</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="219" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">16</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="220" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">4</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="221" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">64</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="222" class="weka.classifiers.functions.MultilayerPerceptron">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-L</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">0.9</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">500</object>
+         <object name="6" class="java.lang.String">-V</object>
+         <object name="7" class="java.lang.String">0</object>
+         <object name="8" class="java.lang.String">-S</object>
+         <object name="9" class="java.lang.String">0</object>
+         <object name="10" class="java.lang.String">-E</object>
+         <object name="11" class="java.lang.String">20</object>
+         <object name="12" class="java.lang.String">-H</object>
+         <object name="13" class="java.lang.String">a</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="223" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">1</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="224" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">1</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="225" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">11</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="226" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">11</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="227" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">13</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="228" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">13</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="229" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">149</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="230" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">149</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="231" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">15</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="232" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">15</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="233" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">17</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="234" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">17</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="235" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">19</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="236" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">19</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="237" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">249</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="238" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">249</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="239" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">25</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="240" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">25</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="241" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="242" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="243" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">399</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="244" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">399</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="245" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">49</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="246" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">49</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="247" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">5</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="248" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">5</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="249" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">7</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="250" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">7</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="251" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">9</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="252" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">9</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="253" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">99</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="254" class="weka.classifiers.lazy.IBk">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-K</object>
+         <object name="1" class="java.lang.String">99</object>
+         <object name="2" class="java.lang.String">-W</object>
+         <object name="3" class="java.lang.String">0</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String">weka.core.neighboursearch.LinearNNSearch -A weka.core.EuclideanDistance</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="255" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.DecisionStump</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="256" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.J48</object>
+         <object name="8" class="java.lang.String">--</object>
+         <object name="9" class="java.lang.String">-U</object>
+         <object name="10" class="java.lang.String">-B</object>
+         <object name="11" class="java.lang.String">-M</object>
+         <object name="12" class="java.lang.String">2</object>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+         <object name="23" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="257" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.J48</object>
+         <object name="8" class="java.lang.String">--</object>
+         <object name="9" class="java.lang.String">-U</object>
+         <object name="10" class="java.lang.String">-B</object>
+         <object name="11" class="java.lang.String">-M</object>
+         <object name="12" class="java.lang.String">2</object>
+         <object name="13" class="java.lang.String">-A</object>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+         <object name="23" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="258" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.J48</object>
+         <object name="8" class="java.lang.String">--</object>
+         <object name="9" class="java.lang.String">-U</object>
+         <object name="10" class="java.lang.String">-M</object>
+         <object name="11" class="java.lang.String">2</object>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+         <object name="23" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="259" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.J48</object>
+         <object name="8" class="java.lang.String">--</object>
+         <object name="9" class="java.lang.String">-U</object>
+         <object name="10" class="java.lang.String">-M</object>
+         <object name="11" class="java.lang.String">2</object>
+         <object name="12" class="java.lang.String">-A</object>
+         <object name="13" class="java.lang.String"/>
+         <object name="14" class="java.lang.String"/>
+         <object name="15" class="java.lang.String"/>
+         <object name="16" class="java.lang.String"/>
+         <object name="17" class="java.lang.String"/>
+         <object name="18" class="java.lang.String"/>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+         <object name="22" class="java.lang.String"/>
+         <object name="23" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="260" class="weka.classifiers.meta.Bagging">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-P</object>
+         <object name="1" class="java.lang.String">100</object>
+         <object name="2" class="java.lang.String">-S</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-I</object>
+         <object name="5" class="java.lang.String">100</object>
+         <object name="6" class="java.lang.String">-W</object>
+         <object name="7" class="java.lang.String">weka.classifiers.trees.REPTree</object>
+         <object name="8" class="java.lang.String">--</object>
+         <object name="9" class="java.lang.String">-M</object>
+         <object name="10" class="java.lang.String">2</object>
+         <object name="11" class="java.lang.String">-V</object>
+         <object name="12" class="java.lang.String">0.0010</object>
+         <object name="13" class="java.lang.String">-N</object>
+         <object name="14" class="java.lang.String">3</object>
+         <object name="15" class="java.lang.String">-S</object>
+         <object name="16" class="java.lang.String">1</object>
+         <object name="17" class="java.lang.String">-L</object>
+         <object name="18" class="java.lang.String">-1</object>
+         <object name="19" class="java.lang.String"/>
+         <object name="20" class="java.lang.String"/>
+         <object name="21" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="261" class="weka.classifiers.misc.HyperPipes">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="262" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="263" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="264" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="265" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">0.8</object>
+         <object name="2" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="266" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">0.2</object>
+      </object>
+   </object>
+   <object name="267" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">0.4</object>
+      </object>
+   </object>
+   <object name="268" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">0.6</object>
+      </object>
+   </object>
+   <object name="269" class="weka.classifiers.misc.VFI">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">0.8</object>
+      </object>
+   </object>
+   <object name="270" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="271" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-E</object>
+      </object>
+   </object>
+   <object name="272" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="273" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String">-E</object>
+      </object>
+   </object>
+   <object name="274" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="275" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-E</object>
+      </object>
+   </object>
+   <object name="276" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="277" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String">-E</object>
+      </object>
+   </object>
+   <object name="278" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">5</object>
+         <object name="8" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="279" class="weka.classifiers.rules.ConjunctiveRule">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-N</object>
+         <object name="1" class="java.lang.String">3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2.0</object>
+         <object name="4" class="java.lang.String">-P</object>
+         <object name="5" class="java.lang.String">-1</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">5</object>
+         <object name="8" class="java.lang.String">-E</object>
+      </object>
+   </object>
+   <object name="280" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="281" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="282" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="283" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="284" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="285" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="286" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="287" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="288" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-B</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="289" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.25</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="290" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.5</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="291" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.75</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="292" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.25</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="293" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.5</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="294" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.75</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="295" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.25</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="296" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.5</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="297" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-C</object>
+         <object name="3" class="java.lang.String">0.75</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="298" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="299" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="300" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="301" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-N</object>
+         <object name="4" class="java.lang.String">3</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="302" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-N</object>
+         <object name="4" class="java.lang.String">3</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="303" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-N</object>
+         <object name="4" class="java.lang.String">3</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="304" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.25</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="305" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.5</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="306" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.75</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="307" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.25</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="308" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.5</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="309" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.75</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="310" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.25</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="311" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.5</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="312" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-C</object>
+         <object name="5" class="java.lang.String">0.75</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="313" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="314" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="315" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="316" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="317" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="318" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="319" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.25</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="320" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.5</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="321" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-C</object>
+         <object name="4" class="java.lang.String">0.75</object>
+         <object name="5" class="java.lang.String">-Q</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="322" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">3</object>
+         <object name="7" class="java.lang.String">-Q</object>
+         <object name="8" class="java.lang.String">1</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="323" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">3</object>
+         <object name="7" class="java.lang.String">-Q</object>
+         <object name="8" class="java.lang.String">1</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="324" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-N</object>
+         <object name="6" class="java.lang.String">3</object>
+         <object name="7" class="java.lang.String">-Q</object>
+         <object name="8" class="java.lang.String">1</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="325" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="326" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="327" class="weka.classifiers.rules.PART">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-Q</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="328" class="weka.classifiers.rules.ZeroR">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="329" class="weka.classifiers.trees.DecisionStump">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="330" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="331" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="332" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="333" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="334" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="335" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="336" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="337" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="338" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="339" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="340" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="341" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="342" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="343" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="344" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="345" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="346" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="347" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="348" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="349" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.1</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="350" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="351" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="352" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="353" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="354" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="355" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="356" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="357" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="358" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="359" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="360" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="361" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="362" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="363" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="364" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="365" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="366" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="367" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="368" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="369" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.2</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="370" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="371" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="372" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="373" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="374" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="375" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="376" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="377" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="378" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="379" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="380" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="381" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="382" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="383" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="384" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="385" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="386" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="387" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="388" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="389" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.3</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="390" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="391" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="392" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="393" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="394" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="395" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="396" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="397" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="398" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="399" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="400" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="401" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="402" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="403" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="404" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="405" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="406" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="407" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="408" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="409" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.4</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="410" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="411" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="412" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="413" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="414" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="415" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="416" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="417" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="418" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="419" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="420" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="421" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="422" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="423" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="424" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="425" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="426" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="427" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="428" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="429" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.5</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="430" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="431" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="432" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="433" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="434" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="435" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="436" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="437" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="438" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="439" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="440" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="441" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="442" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="443" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="444" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="445" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="446" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="447" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="448" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="449" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.6</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="450" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="451" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="452" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="453" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="454" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="455" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="456" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="457" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="458" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="459" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="460" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="461" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="462" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="463" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="464" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="465" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="466" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="467" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="468" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="469" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.7000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="470" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="471" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="472" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="473" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="474" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="475" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="476" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="477" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="478" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="479" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="480" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="481" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="482" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="483" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="484" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="485" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="486" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="487" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="488" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="489" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.8000001</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="490" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="491" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="492" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="493" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="494" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="495" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="496" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="497" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="498" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="499" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-B</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="500" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="501" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="502" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="503" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="504" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="505" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="506" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="507" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="508" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="509" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-C</object>
+         <object name="1" class="java.lang.String">0.9</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="510" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="511" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="512" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">16</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="513" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">16</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="514" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="515" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="516" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="517" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="518" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">8</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="519" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-B</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">8</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="520" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="521" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">1</object>
+         <object name="7" class="java.lang.String">-A</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="522" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">16</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="523" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">16</object>
+         <object name="7" class="java.lang.String">-A</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="524" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">2</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="525" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">2</object>
+         <object name="7" class="java.lang.String">-A</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="526" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">4</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="527" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">4</object>
+         <object name="7" class="java.lang.String">-A</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="528" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">8</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="529" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-R</object>
+         <object name="1" class="java.lang.String">-N</object>
+         <object name="2" class="java.lang.String">3</object>
+         <object name="3" class="java.lang.String">-Q</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-M</object>
+         <object name="6" class="java.lang.String">8</object>
+         <object name="7" class="java.lang.String">-A</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="530" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="531" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="532" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="533" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="534" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="535" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="536" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="537" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="538" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="539" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="540" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="541" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="542" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="543" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="544" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="545" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="546" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="547" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="548" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="549" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.1</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="550" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="551" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="552" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="553" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="554" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="555" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="556" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="557" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="558" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="559" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="560" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="561" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="562" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="563" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="564" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="565" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="566" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="567" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="568" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="569" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.2</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="570" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="571" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="572" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="573" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="574" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="575" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="576" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="577" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="578" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="579" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="580" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="581" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="582" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="583" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="584" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="585" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="586" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="587" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="588" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="589" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.3</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="590" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="591" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="592" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="593" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="594" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="595" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="596" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="597" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="598" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="599" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="600" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="601" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="602" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="603" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="604" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="605" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="606" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="607" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="608" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="609" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.4</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="610" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="611" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="612" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="613" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="614" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="615" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="616" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="617" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="618" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="619" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="620" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="621" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="622" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="623" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="624" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="625" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="626" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="627" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="628" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="629" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.5</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="630" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="631" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="632" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="633" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="634" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="635" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="636" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="637" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="638" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="639" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="640" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="641" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="642" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="643" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="644" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="645" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="646" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="647" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="648" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="649" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.6</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="650" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="651" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="652" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="653" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="654" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="655" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="656" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="657" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="658" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="659" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="660" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="661" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="662" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="663" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="664" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="665" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="666" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="667" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="668" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="669" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.7000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="670" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="671" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="672" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="673" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="674" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="675" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="676" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="677" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="678" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="679" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="680" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="681" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="682" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="683" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="684" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="685" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="686" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="687" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="688" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="689" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.8000001</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="690" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="691" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="692" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="693" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">16</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="694" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="695" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">2</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="696" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="697" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">4</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="698" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="699" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-B</object>
+         <object name="4" class="java.lang.String">-M</object>
+         <object name="5" class="java.lang.String">8</object>
+         <object name="6" class="java.lang.String">-A</object>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="700" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="701" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">1</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="702" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="703" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">16</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="704" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="705" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">2</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="706" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="707" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">4</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="708" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="709" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-C</object>
+         <object name="2" class="java.lang.String">0.9</object>
+         <object name="3" class="java.lang.String">-M</object>
+         <object name="4" class="java.lang.String">8</object>
+         <object name="5" class="java.lang.String">-A</object>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="710" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">1</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="711" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">1</object>
+         <object name="9" class="java.lang.String">-A</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="712" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">16</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="713" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">16</object>
+         <object name="9" class="java.lang.String">-A</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="714" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">2</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="715" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">2</object>
+         <object name="9" class="java.lang.String">-A</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="716" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">4</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="717" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">4</object>
+         <object name="9" class="java.lang.String">-A</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="718" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">8</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="719" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-B</object>
+         <object name="7" class="java.lang.String">-M</object>
+         <object name="8" class="java.lang.String">8</object>
+         <object name="9" class="java.lang.String">-A</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="720" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="721" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="722" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">16</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="723" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">16</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="724" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="725" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">2</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="726" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="727" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">4</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="728" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">8</object>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="729" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-S</object>
+         <object name="1" class="java.lang.String">-R</object>
+         <object name="2" class="java.lang.String">-N</object>
+         <object name="3" class="java.lang.String">3</object>
+         <object name="4" class="java.lang.String">-Q</object>
+         <object name="5" class="java.lang.String">1</object>
+         <object name="6" class="java.lang.String">-M</object>
+         <object name="7" class="java.lang.String">8</object>
+         <object name="8" class="java.lang.String">-A</object>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="730" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="731" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">1</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="732" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="733" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">16</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="734" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="735" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">2</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="736" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="737" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">4</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="738" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="739" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-B</object>
+         <object name="2" class="java.lang.String">-M</object>
+         <object name="3" class="java.lang.String">8</object>
+         <object name="4" class="java.lang.String">-A</object>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="740" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">1</object>
+         <object name="3" class="java.lang.String"/>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="741" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">1</object>
+         <object name="3" class="java.lang.String">-A</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="742" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">16</object>
+         <object name="3" class="java.lang.String"/>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="743" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">16</object>
+         <object name="3" class="java.lang.String">-A</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="744" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String"/>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="745" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">2</object>
+         <object name="3" class="java.lang.String">-A</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="746" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String"/>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="747" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">4</object>
+         <object name="3" class="java.lang.String">-A</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="748" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String"/>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="749" class="weka.classifiers.trees.J48">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-U</object>
+         <object name="1" class="java.lang.String">-M</object>
+         <object name="2" class="java.lang.String">8</object>
+         <object name="3" class="java.lang.String">-A</object>
+         <object name="4" class="java.lang.String"/>
+         <object name="5" class="java.lang.String"/>
+         <object name="6" class="java.lang.String"/>
+         <object name="7" class="java.lang.String"/>
+         <object name="8" class="java.lang.String"/>
+         <object name="9" class="java.lang.String"/>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+         <object name="12" class="java.lang.String"/>
+         <object name="13" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="750" class="weka.classifiers.trees.NBTree">
+      <object name="options" class="java.lang.String" array="yes"/>
+   </object>
+   <object name="751" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="752" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="753" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="754" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="755" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="756" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="757" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="758" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="759" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="760" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="761" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="762" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="763" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="764" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="765" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="766" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="767" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="768" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="769" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="770" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="771" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="772" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="773" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="774" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="775" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="776" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="777" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="778" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="779" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="780" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="781" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="782" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="783" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="784" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="785" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="786" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="787" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="788" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="789" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="790" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="791" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="792" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="793" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="794" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="795" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="796" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="797" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="798" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="799" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="800" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="801" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="802" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="803" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="804" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="805" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="806" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="807" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="808" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="809" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="810" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="811" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="812" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="813" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="814" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="815" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="816" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="817" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="818" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="819" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="820" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="821" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="822" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">16</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="823" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="824" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="825" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="826" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="827" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="828" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="829" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="830" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="831" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="832" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="833" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="834" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="835" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="836" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="837" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="838" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="839" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="840" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="841" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="842" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="843" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="844" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="845" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="846" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="847" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="848" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="849" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="850" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="851" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="852" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="853" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="854" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="855" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="856" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="857" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="858" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="859" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="860" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="861" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="862" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="863" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="864" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="865" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="866" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="867" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="868" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="869" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="870" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="871" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="872" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="873" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="874" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="875" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="876" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="877" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="878" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="879" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="880" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="881" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="882" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="883" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="884" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="885" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="886" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="887" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="888" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="889" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="890" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="891" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="892" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="893" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="894" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">2</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="895" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="896" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="897" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="898" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="899" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="900" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="901" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="902" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="903" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="904" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="905" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="906" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="907" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="908" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="909" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="910" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="911" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="912" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="913" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="914" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="915" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="916" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="917" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="918" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="919" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="920" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="921" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="922" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="923" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="924" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="925" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="926" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="927" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="928" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="929" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="930" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="931" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="932" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="933" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="934" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="935" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="936" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="937" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="938" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="939" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="940" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="941" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="942" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="943" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="944" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="945" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="946" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="947" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="948" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="949" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="950" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="951" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="952" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="953" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="954" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="955" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="956" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="957" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="958" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="959" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="960" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="961" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="962" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="963" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="964" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="965" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="966" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">4</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="967" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="968" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="969" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="970" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="971" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="972" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="973" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="974" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="975" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="976" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="977" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="978" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="979" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="980" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="981" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="982" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="983" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="984" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="985" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="986" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="987" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="988" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="989" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="990" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.0010</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="991" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="992" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="993" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="994" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="995" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="996" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="997" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="998" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="999" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1000" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1001" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1002" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1003" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1004" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1005" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1006" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1007" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1008" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1009" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1010" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1011" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1012" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1013" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1014" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.01</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1015" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1016" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1017" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1018" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1019" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1020" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1021" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1022" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1023" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1024" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1025" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1026" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">1</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1027" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1028" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">-1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1029" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1030" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">1</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1031" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1032" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">3</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1033" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1034" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">5</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1035" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1036" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">7</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1037" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String"/>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+   <object name="1038" class="weka.classifiers.trees.REPTree">
+      <object name="options" class="java.lang.String" array="yes">
+         <object name="0" class="java.lang.String">-M</object>
+         <object name="1" class="java.lang.String">8</object>
+         <object name="2" class="java.lang.String">-V</object>
+         <object name="3" class="java.lang.String">0.1</object>
+         <object name="4" class="java.lang.String">-N</object>
+         <object name="5" class="java.lang.String">3</object>
+         <object name="6" class="java.lang.String">-S</object>
+         <object name="7" class="java.lang.String">3</object>
+         <object name="8" class="java.lang.String">-L</object>
+         <object name="9" class="java.lang.String">9</object>
+         <object name="10" class="java.lang.String">-P</object>
+         <object name="11" class="java.lang.String"/>
+      </object>
+   </object>
+</object>
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/toylist.model.xml
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/toylist.model.xml	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/ensembleSelection/toylist.model.xml	(revision 29)
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE object
+[
+   <!ELEMENT object (#PCDATA | object)*>
+   <!ATTLIST object name      CDATA #REQUIRED>
+   <!ATTLIST object class     CDATA #REQUIRED>
+   <!ATTLIST object primitive CDATA "no">
+   <!ATTLIST object array     CDATA "no">   <!-- the dimensions of the array; no=0, yes=1 -->
+   <!ATTLIST object null      CDATA "no">
+   <!ATTLIST object version   CDATA "3.5.3">
+]
+>
+
+<object class="java.util.Vector" name="__root__" version="3.5.3">
+   <object class="weka.classifiers.trees.J48" name="0">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.25</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="1">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.25</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="2">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.25</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="3">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.25</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="4">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="5">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-B</object>
+         <object class="java.lang.String" name="4">-M</object>
+         <object class="java.lang.String" name="5">2</object>
+         <object class="java.lang.String" name="6">-A</object>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="6">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="7">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-S</object>
+         <object class="java.lang.String" name="1">-C</object>
+         <object class="java.lang.String" name="2">0.5</object>
+         <object class="java.lang.String" name="3">-M</object>
+         <object class="java.lang.String" name="4">2</object>
+         <object class="java.lang.String" name="5">-A</object>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="8">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="9">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-B</object>
+         <object class="java.lang.String" name="2">-M</object>
+         <object class="java.lang.String" name="3">2</object>
+         <object class="java.lang.String" name="4">-A</object>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="10">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3"/>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+   <object class="weka.classifiers.trees.J48" name="11">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-U</object>
+         <object class="java.lang.String" name="1">-M</object>
+         <object class="java.lang.String" name="2">2</object>
+         <object class="java.lang.String" name="3">-A</object>
+         <object class="java.lang.String" name="4"/>
+         <object class="java.lang.String" name="5"/>
+         <object class="java.lang.String" name="6"/>
+         <object class="java.lang.String" name="7"/>
+         <object class="java.lang.String" name="8"/>
+         <object class="java.lang.String" name="9"/>
+         <object class="java.lang.String" name="10"/>
+         <object class="java.lang.String" name="11"/>
+         <object class="java.lang.String" name="12"/>
+         <object class="java.lang.String" name="13"/>
+      </object>
+   </object>
+</object>
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteGenerator.java	(revision 29)
@@ -0,0 +1,203 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   DiscreteGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Capabilities.Capability;
+
+import java.util.Arrays;
+
+/**
+ <!-- globalinfo-start -->
+ * An artificial data generator that uses discrete buckets for values.<br/>
+ * <br/>
+ * In this discrete generator, values are ranked according to how often they appear.  This is not to be confused with the discrete uniform generator which gives every bucket the  same probability.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ * @see DiscreteUniformGenerator
+ */
+public class DiscreteGenerator
+  extends RandomizableGenerator
+    implements InstanceHandler, NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -2990312384506940726L;
+
+  /**
+   * The array of probabilities for this generator.
+   */
+  protected double[][] m_Probabilities;
+
+  /**
+   * The probability of an unseen event.
+   */
+  protected double m_Unseen = Double.MIN_VALUE;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "An artificial data generator that uses discrete buckets "
+      + "for values.\n"
+      + "\n"
+      + "In this discrete generator, values are ranked according to "
+      + "how often they appear.  This is not to be confused with the "
+      + "discrete uniform generator which gives every bucket the  "
+      + "same probability.";
+  }
+
+  /** 
+   * Returns the Capabilities of this object
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Builds the generator with a given set of instances.
+   *
+   * @param someinstances The instances that will be used to 
+   * build up the probabilities for this generator.
+   * @throws Exception if data cannot be handled
+   */
+  public void buildGenerator(Instances someinstances) throws Exception {
+    // can generator handle the data?
+    getCapabilities().testWithFail(someinstances);
+    
+    someinstances = new Instances(someinstances);
+    someinstances.deleteWithMissing(0);
+
+    //put all the values in an array
+    double[] values = new double[someinstances.numInstances()];
+
+    for(int i = 0; i < someinstances.numInstances(); i++) {
+      Instance aninst = someinstances.instance(i);
+      values[i] = aninst.value(0);
+    }
+
+    Arrays.sort(values);
+
+    double count = 1;
+    for(int i = 1; i < values.length; i++) {
+      if(values[i] != values[i - 1])
+	count++;
+    }
+
+    //now we know how many values we have
+    double[][] allvals = new double[(int)count][2];
+    int position = 0;
+    allvals [0][0] = values[0];
+    allvals [0][1] = 1;
+
+    for(int i = 1; i < values.length; i++) {
+      if(values[i] != values[i - 1]) {
+	position++;
+	allvals[position][0] = values[i];
+	allvals[position][1] = 1;
+      } else
+	allvals[position][1]++;
+    }
+
+    //turn the counts into probabilities
+    for(int i = 0; i < count; i++) {
+      allvals[i][1] /=  ((double)values.length + 1);
+    }
+
+    m_Probabilities = allvals;
+    m_Unseen = 1 / ((double)values.length + 1);
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {	
+    double aprob = m_Random.nextDouble();
+    double currentprob = 0;
+    for(int i = 0; i < m_Probabilities.length; i++) {
+      if(currentprob + m_Probabilities[i][1] >= aprob) {
+	return m_Probabilities[i][0];
+      } else {
+	currentprob += m_Probabilities[i][1];
+      }
+    }
+    return 0;	
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param valuex The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double valuex) {	
+    for(int i = 0; i < m_Probabilities.length; i++) {
+      if(valuex == m_Probabilities[i][0])
+	return m_Probabilities[i][1];
+    }
+
+    return m_Unseen;
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */ 
+  public double getLogProbabilityOf(double valuex) {	
+    return Math.log(this.getProbabilityOf(valuex));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteUniformGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteUniformGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/DiscreteUniformGenerator.java	(revision 29)
@@ -0,0 +1,190 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   DiscreteUniformGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Capabilities.Capability;
+
+import java.util.Arrays;
+
+/**
+ <!-- globalinfo-start -->
+ * An artificial data generator that uses discrete buckets for values.<br/>
+ * <br/>
+ * In this discrete uniform generator, all buckets are given the same probability, regardless of how many values fall into each bucket.  This is not to be confused with the discrete generator which gives every bucket a probability based on how full the bucket is.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ * @see DiscreteGenerator
+ */
+public class DiscreteUniformGenerator
+  extends RandomizableGenerator
+    implements InstanceHandler, NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 3639933602298510366L;
+
+  /**
+   * The array of probabilities for this generator.
+   */
+  protected double[] m_Probabilities;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "An artificial data generator that uses discrete buckets "
+      + "for values.\n"
+      + "\n"
+      + "In this discrete uniform generator, all buckets are given "
+      + "the same probability, regardless of how many values fall into "
+      + "each bucket.  This is not to be confused with the "
+      + "discrete generator which gives every bucket a probability "
+      + "based on how full the bucket is.";
+  }
+
+  /** 
+   * Returns the Capabilities of this object
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Builds the generator with a given set of instances.
+   *
+   * @param someinstances The instances that will be used to 
+   * build up the probabilities for this generator.
+   * @throws Exception if data cannot be processed
+   */
+  public void buildGenerator(Instances someinstances) throws Exception {
+    // can generator handle the data?
+    getCapabilities().testWithFail(someinstances);
+    
+    someinstances = new Instances(someinstances);
+    someinstances.deleteWithMissing(0);
+
+    //put all the values in an array
+    double[] values = new double[someinstances.numInstances()];
+
+    for(int i = 0; i < someinstances.numInstances(); i++) {
+      Instance aninst = someinstances.instance(i);
+      values[i] = aninst.value(0);
+    }
+
+    Arrays.sort(values);
+
+    double count = 1;
+    for(int i = 1; i < values.length; i++) {
+      if(values[i] != values[i - 1])
+	count++;
+    }
+
+    //now we know how many values we have
+    double[] allvals = new double[(int)count];
+    int position = 0;
+    allvals [0] = values[0];
+
+
+    for(int i = 1; i < values.length; i++) {
+      if(values[i] != values[i - 1]) {
+	position++;
+	allvals[position]= values[i];	    
+      }
+    }	
+
+    m_Probabilities = allvals;
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {	
+    double aprob = m_Random.nextDouble();
+    double gap = 1 / ((double)m_Probabilities.length);
+    int position = (int) (aprob / gap);
+    if(position < 0)
+      position = 0;
+    if(position > m_Probabilities.length - 1)
+      position = m_Probabilities.length - 1;
+
+    return m_Probabilities[position];		
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param valuex The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double valuex) {	
+    for(int i = 0; i < m_Probabilities.length; i++) {
+      if(valuex == m_Probabilities[i])
+	return 1 / ((double)m_Probabilities.length + 1);
+    }
+
+    return 1 / ((double) m_Probabilities.length + 1);
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */ 
+  public double getLogProbabilityOf(double valuex) {	
+    return Math.log(this.getProbabilityOf(valuex));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/EMGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/EMGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/EMGenerator.java	(revision 29)
@@ -0,0 +1,179 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   EMGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.clusterers.EM;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Capabilities.Capability;
+
+/**
+ <!-- globalinfo-start -->
+ * A generator that uses EM as an underlying model.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class EMGenerator
+  extends RandomizableGenerator
+    implements InstanceHandler, NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 2769416817955024550L;
+
+  /**
+   * The underlying EM model.
+   */
+  protected EM m_EMModel;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return "A generator that uses EM as an underlying model.";
+  }
+
+  /** 
+   * Returns the Capabilities of this object
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+
+    // TODO: shouldn't that return EM's capabilities?
+    
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Builds the generator with a given set of instances.
+   *
+   * @param someinstances The instances that will be used to 
+   * build up the probabilities for this generator.
+   * @throws Exception if data cannot be processed
+   */
+  public void buildGenerator(Instances someinstances) throws Exception {
+    // can generator handle the data?
+    getCapabilities().testWithFail(someinstances);
+    
+    someinstances = new Instances(someinstances);
+    someinstances.deleteWithMissing(0);
+    
+    m_EMModel = new EM();	
+    m_EMModel.setMaxIterations(10);
+    m_EMModel.buildClusterer(someinstances);
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {	
+    //one attribute
+    //get the cluster priors
+    double[] clusterProbabilities = m_EMModel.getClusterPriors();
+    double clusterPicked = m_Random.nextDouble();
+
+    //find the cluster we are going to generate data for
+    double sum = 0;
+    int clusterID = 0;
+    for(int i = 0; i < clusterProbabilities.length; i++) {
+      if(clusterPicked > sum && 
+	  clusterPicked <= (sum + clusterProbabilities[i])) {
+	//it's this one
+	clusterID = i;
+	break;
+      } else {
+	sum = sum + clusterProbabilities[i];
+	clusterID = i;
+      }
+    }
+    //System.out.println("Selecting cluster: " + clusterID + " of " + numClusters);
+    //get the mean and standard deviation of this cluster
+    double[][][] normalDists = m_EMModel.getClusterModelsNumericAtts();
+    double mean = normalDists[clusterID][0][0];
+    double sd = normalDists[clusterID][0][1];
+
+    double gaussian = m_Random.nextGaussian();
+    double value = mean + (gaussian * sd);
+
+    return value;	
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param valuex The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double valuex) {
+    //find the cluster closest to the value of x
+    Instance inst = new DenseInstance(1);
+    inst.setValue(0, valuex);
+    try{			
+      return Math.exp(m_EMModel.logDensityForInstance(inst));			
+    }catch(Exception e) {
+      e.printStackTrace();
+      System.exit(-1);
+    }
+
+    return 0;
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */
+  public double getLogProbabilityOf(double valuex) {
+    return Math.log(this.getProbabilityOf(valuex));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/GaussianGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/GaussianGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/GaussianGenerator.java	(revision 29)
@@ -0,0 +1,122 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   GaussianGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+/**
+ <!-- globalinfo-start -->
+ * An artificial data generator that uses a single Gaussian distribution.<br/>
+ * <br/>
+ * If a mixture of Gaussians is required, use the EM Generator.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Sets the mean of the generator
+ *  (default: 0)</pre>
+ * 
+ * <pre> -SD &lt;num&gt;
+ *  Sets the standard deviation of the generator
+ *  (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ * @see EMGenerator
+ */
+public class GaussianGenerator
+    extends RandomizableDistributionGenerator
+    implements NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4860675869078046797L;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "An artificial data generator that uses a single Gaussian distribution.\n"
+      + "\n"
+      + "If a mixture of Gaussians is required, use the EM Generator.";
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {
+    double gaussian = m_Random.nextGaussian();
+    double value = m_Mean + (gaussian * m_StandardDeviation);
+    return value;		
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   *
+   * @param valuex The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double valuex) {	
+    double twopisqrt = Math.sqrt(2 * Math.PI);
+    double left = 1 / (m_StandardDeviation * twopisqrt);
+    double diffsquared = Math.pow((valuex - m_Mean), 2);
+    double bottomright = 2 * Math.pow(m_StandardDeviation, 2);
+    double brackets = -1 * (diffsquared / bottomright);
+
+    double probx = left * Math.exp(brackets);
+
+    return probx;
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */
+  public double getLogProbabilityOf(double valuex) {
+    double twopisqrt = Math.log(Math.sqrt(2 * Math.PI));
+    double left = - (Math.log(m_StandardDeviation) + twopisqrt);
+    double diffsquared = Math.pow((valuex - m_Mean), 2);
+    double bottomright = 2 * Math.pow(m_StandardDeviation, 2);
+    double brackets = -1 * (diffsquared / bottomright);
+
+    double probx = left + brackets;
+
+    return probx;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Generator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Generator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Generator.java	(revision 29)
@@ -0,0 +1,192 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   Generator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * An artificial data generator.
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5861 $
+ */
+public abstract class Generator 
+  implements Serializable, OptionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 2412127331483792089L;
+
+  /** Whether the generator is run in debug mode. */
+  protected boolean m_Debug = false;
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param somedata The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public abstract double getProbabilityOf(double somedata);
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param somedata The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */
+  public abstract double getLogProbabilityOf(double somedata);
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public abstract double generate();
+
+  /**
+   * Clones this generator.  It is a shallow copy,
+   * only settings are copied, not probabilities.
+   *
+   * @return A copy of this generator.
+   */
+  public Generator copy() {
+    Generator	result;
+    
+    try {
+      result = getClass().newInstance();
+      result.setOptions(getOptions());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Creates a new instance of a generator given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * classifier implements OptionHandler and the options parameter is
+   * non-null, the classifier will have it's options set.
+   *
+   * @param generatorName the fully qualified class name of the generator
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created classifier, ready for use.
+   * @throws Exception if the classifier name is invalid, or the options
+   * supplied are not acceptable to the classifier
+   */
+  public static Generator forName(String generatorName,
+      String[] options) throws Exception {
+
+    return (Generator)Utils.forName(Generator.class,
+	generatorName,
+	options);
+  }
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public abstract String globalInfo();
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tIf set, generator is run in debug mode and\n"
+	+ "\tmay output additional info to the console",
+	"D", 0, "-D"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    if (getDebug())
+      result.add("-D");
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Set debugging mode.
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+  }
+
+  /**
+   * Get whether debugging is turned on.
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return 
+        "If set to true, the generator might output debugging information "
+      + "in the console.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/InstanceHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/InstanceHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/InstanceHandler.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   InstanceHandler.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.CapabilitiesHandler;
+import weka.core.Instances;
+
+/**
+ * Whether the generator can handle instances directly
+ * for setting the parameters.
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public interface InstanceHandler
+  extends CapabilitiesHandler {
+
+  /**
+   * Builds the generator with a given set of instances.
+   *
+   * @param someinstances The instances that will be used to 
+   * build up the probabilities for this generator.
+   * @throws Exception if data cannot be handled
+   */
+  public void buildGenerator(Instances someinstances) throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Mean.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Mean.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Mean.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   Mean.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+/**
+ * A interface indicating that a class expects the 
+ * mean and standard deviation to be set.
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public interface Mean {
+
+  /**
+   * Sets the mean of the Gaussian distribution to a new 
+   * mean.
+   *
+   * @param newmean The new mean for the distribution.
+   */
+  public void setMean(double newmean);
+
+  /**
+   * Sets the standard deviation of the Gaussian distribution
+   * to a new value.
+   *
+   * @param newsd The new standard deviation.
+   */
+  public void setStandardDeviation(double newsd);
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/MixedGaussianGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/MixedGaussianGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/MixedGaussianGenerator.java	(revision 29)
@@ -0,0 +1,341 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   MixedGaussianGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A mixed Gaussian artificial data generator.<br/>
+ * <br/>
+ * This generator only has two Gaussians, each sitting 3 standard deviations (by default) away from the mean of the main distribution.  Each model has half of the probability.  The idea is that the two sub-models form a boundary either side of the main distribution.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Sets the mean of the generator
+ *  (default: 0)</pre>
+ * 
+ * <pre> -SD &lt;num&gt;
+ *  Sets the standard deviation of the generator
+ *  (default: 1)</pre>
+ * 
+ * <pre> -di &lt;distance&gt;
+ *  Sets the difference between the mean and what will be used
+ *  on the lower and higher distributions for the generator. (default: 3)</pre>
+ * 
+ * <pre> -da
+ *  If set, the generator will use the absolute value of the
+ *  difference. If not set, it will multiply the difference by
+ *  the standard deviation.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public class MixedGaussianGenerator
+  extends RandomizableDistributionGenerator 
+  implements NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1516470615315381362L;
+
+  /**
+   * The distance between the main distribution and each model.
+   */
+  protected double m_Distance = 3;
+
+  /**
+   * Whether the difference is absolute, or a modifier to the
+   * standard deviation.
+   */
+  protected boolean m_DistanceAbsolute = false;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "A mixed Gaussian artificial data generator.\n"
+      + "\n"
+      + "This generator only has two Gaussians, each sitting "
+      + "3 standard deviations (by default) away from the mean "
+      + "of the main distribution.  Each model has half of the "
+      + "probability.  The idea is that the two sub-models "
+      + "form a boundary either side of the main distribution.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();   
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements())
+      result.addElement(enu.nextElement());
+
+    result.addElement(new Option(
+	"\tSets the difference between the mean and what will be used\n"
+	+ "\ton the lower and higher distributions for the generator."
+	+ "\t(default: 3)",
+	"di", 1, "-di <distance>"));
+    
+    result.addElement(new Option(
+	"\tIf set, the generator will use the absolute value of the\n"
+	+ "\tdifference. If not set, it will multiply the difference by\n"
+	+ "\tthe standard deviation.",
+	"da", 0, "-da"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, generator is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Sets the mean of the generator
+   *  (default: 0)</pre>
+   * 
+   * <pre> -SD &lt;num&gt;
+   *  Sets the standard deviation of the generator
+   *  (default: 1)</pre>
+   * 
+   * <pre> -di &lt;distance&gt;
+   *  Sets the difference between the mean and what will be used
+   *  on the lower and higher distributions for the generator. (default: 3)</pre>
+   * 
+   * <pre> -da
+   *  If set, the generator will use the absolute value of the
+   *  difference. If not set, it will multiply the difference by
+   *  the standard deviation.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    setDistanceAbsolute(Utils.getFlag("da", options));
+
+    tmpStr = Utils.getOption("di", options);
+    if (tmpStr.length() != 0)
+      setDistance(Double.parseDouble(tmpStr));
+    else
+      setDistance(3.0);
+  }
+
+  /**
+   * Gets the current settings of the generator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getDistanceAbsolute()) 
+      result.add("-da");
+ 
+    result.add("-di");
+    result.add("" + m_Distance);
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Gets the difference between the main distribution and each
+   * of the models.  The default difference is 3, and will
+   * cause each model to be 3 standard deviations away from the mean.
+   * One model is created either side of the mean.
+   *
+   * @return The difference between the main distribution and a model.
+   */
+  public double getDistance() {
+    return m_Distance;
+  }
+
+  /**
+   * Sets the difference between the main distribution and the models.
+   * See getDistance() for a longer explanation.
+   *
+   * @param diff The new difference.
+   */
+  public void setDistance(double diff) {
+    m_Distance = diff;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String distanceTipText() {
+    return "The difference between the main distribution and the models.";
+  }
+
+  /**
+   * Gets whether the difference will be an absolute value,
+   * or something that is used as a multiplier to the 
+   * standard deviation.
+   *
+   * @return Whether the difference will be absolute or not.
+   */
+  public boolean getDistanceAbsolute() {
+    return m_DistanceAbsolute;
+  }
+
+  /**
+   * Sets the difference to be absolute (or not).
+   *
+   * @param newdiff Whether the difference should be absolute or
+   * a standard deviation modifier.
+   */
+  public void setDistanceAbsolute(boolean newdiff) {
+    m_DistanceAbsolute = newdiff;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String distanceAbsoluteTipText() {
+    return "If true, then the distance is absolute.";
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {
+    double difference = m_Distance;
+    if(!m_DistanceAbsolute)
+      difference = m_StandardDeviation * m_Distance;
+
+    if(m_Random.nextBoolean()) {
+      //lower distribution
+      double gaussian = m_Random.nextGaussian();
+      double value = (m_Mean - difference) + (gaussian * m_StandardDeviation);
+      return value;		
+    } else {
+      //higher distribution
+      double gaussian = m_Random.nextGaussian();
+      double value = (m_Mean + difference) + (gaussian * m_StandardDeviation);
+      return value;
+    }
+  }
+
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param valuex The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double valuex) {
+    double difference = m_Distance;
+    if(!m_DistanceAbsolute)
+      difference = m_StandardDeviation * m_Distance;
+
+    double prob1 = 0.5 * this.getProbability(valuex, m_Mean - difference, m_StandardDeviation);
+    double prob2 = 0.5 * this.getProbability(valuex, m_Mean + difference, m_StandardDeviation);
+    return prob1 + prob2;
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * a given Gaussian distribution.
+   * 
+   *
+   * @param valuex The value to get the probability of.
+   * @param mean The mean of the Gaussian distribution.
+   * @param stddev The standard deviation of the Gaussian distribution.
+   * @return The probability of the given value.
+   */
+  public double getProbability(double valuex, double mean, double stddev) {
+    double twopisqrt = Math.sqrt(2 * Math.PI);
+    double left = 1 / (stddev * twopisqrt);
+    double diffsquared = Math.pow((valuex - mean), 2);
+    double bottomright = 2 * Math.pow(stddev, 2);
+    double brackets = -1 * (diffsquared / bottomright);
+
+    double probx = left * Math.exp(brackets);
+
+    return probx;
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */
+  public double getLogProbabilityOf(double valuex) {
+    return Math.log(this.getProbabilityOf(valuex));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalAttributeGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalAttributeGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalAttributeGenerator.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   NominalAttributeGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Instances;
+import weka.core.Attribute;
+
+/**
+ * Used to indicate this generator can be used to generate 
+ * artificial instances for nominal attributes.
+ *
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public interface NominalAttributeGenerator{
+
+   /**
+   * Sets up the generator with the counts required for generation.
+   *
+   * @param someinstances The instances to count up.
+   * @param att The attribute to count up with.
+   */
+    public void buildGenerator(Instances someinstances, Attribute att);
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NominalGenerator.java	(revision 29)
@@ -0,0 +1,145 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   NominalGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * A generator for nominal attributes.<br/>
+ * <br/>
+ * Generates artificial data for nominal attributes.  Each attribute value is considered to be possible, i.e. the probability of any value is always non-zero.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public class NominalGenerator
+  extends RandomizableGenerator
+  implements NominalAttributeGenerator{
+  
+
+  /** for serialization. */
+  private static final long serialVersionUID = 5254947213887016283L;
+
+  /**
+   * Counts (turned into probabilities) of each attribute value.
+   */
+  protected double[] m_AttCounts;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "A generator for nominal attributes.\n"
+      + "\n"
+      + "Generates artificial data for nominal attributes.  Each attribute value "
+      + "is considered to be possible, i.e. the probability of any value is "
+      + "always non-zero.";
+  }
+
+  /**
+   * Sets up the generator with the counts required for generation.
+   *
+   * @param someinstances The instances to count up.
+   * @param att The attribute to count up with.
+   */
+  public void buildGenerator(Instances someinstances, Attribute att) {
+    m_AttCounts = new double[(int)att.numValues()];
+    for(int i = 0; i < m_AttCounts.length; i++) {
+      m_AttCounts[i] = 1;
+    }
+
+    //count up the number of each instance
+    Enumeration instancesEnum = someinstances.enumerateInstances();
+    int totalCounts = m_AttCounts.length;
+    while(instancesEnum.hasMoreElements()) {
+      Instance aninst = (Instance)instancesEnum.nextElement();
+      if(!aninst.isMissing(att)) {
+	m_AttCounts[(int)aninst.value(att)] += 1;
+	totalCounts++;
+      }
+    }
+
+    //calculate the probability of each.
+    for(int i = 0; i < m_AttCounts.length; i++) {
+      m_AttCounts[i] /= (double)totalCounts;	    
+    }
+  }
+
+  /**
+   * Generates an index of a nominal attribute as artificial data.
+   *
+   * @return The index of the nominal attribute's value.
+   */
+  public double generate() {	
+    double prob = m_Random.nextDouble();
+    //find the index of the attribute value with this position
+    double probSoFar = 0;
+    for(int i = 0; i < m_AttCounts.length; i++) {
+      probSoFar += m_AttCounts[i];
+      if(prob <= probSoFar)
+	return i;
+    }
+    return 0;
+  }
+
+  /**
+   * Gets the probability of a given attribute value (provided as an index).
+   *
+   * @param valuex The index to the attribute value.
+   * @return The probability of this value.
+   */
+  public double getProbabilityOf(double valuex) {
+    return m_AttCounts[(int)valuex];
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param valuex The index of the nominal value.
+   * @return The natural log of the probability of valuex.
+   */
+  public double getLogProbabilityOf(double valuex) {
+    return Math.log(this.getProbabilityOf(valuex));
+  }       
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NumericAttributeGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NumericAttributeGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/NumericAttributeGenerator.java	(revision 29)
@@ -0,0 +1,35 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   NominalAttributeGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+/**
+ * Used to indicate this generator can be used to generate 
+ * artificial instances for numeric attributes.
+ *
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public interface NumericAttributeGenerator{
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableDistributionGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableDistributionGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableDistributionGenerator.java	(revision 29)
@@ -0,0 +1,184 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableDistributionGenerator.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * An abstract superclass for randomizable generators that make use of
+ * mean and standard deviation.
+ * 
+ * @author  fracpete (fracpet at waikato dot ac dot nz)
+ * @version $Revision: 5793 $
+ */
+public abstract class RandomizableDistributionGenerator
+  extends RandomizableGenerator
+  implements Mean {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 955762136858704289L;
+
+  /** The mean of the underlying distribution. */
+  protected double m_Mean = 0.0;
+
+  /** The standard deviation of the underlying distribution. */
+  protected double m_StandardDeviation = 1.0;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();   
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements())
+      result.addElement(enu.nextElement());
+
+    result.addElement(new Option(
+	"\tSets the mean of the generator\n"
+	+ "\t(default: 0)",
+	"M", 1, "-M <num>"));
+    
+    result.addElement(new Option(
+	"\tSets the standard deviation of the generator\n"
+	+ "\t(default: 1)",
+	"SD", 1, "-SD <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("M", options);
+    if (tmpStr.length() != 0)
+      setMean(Double.parseDouble(tmpStr));
+    else
+      setMean(0.0);
+
+    tmpStr = Utils.getOption("SD", options);
+    if (tmpStr.length() != 0)
+      setStandardDeviation(Double.parseDouble(tmpStr));
+    else
+      setStandardDeviation(1);
+  }
+
+  /**
+   * Gets the current settings of the generator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-M");
+    result.add("" + m_Mean);
+    
+    result.add("-SD"); 
+    result.add("" + m_StandardDeviation);
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Gets the current mean of the underlying Gaussian
+   * distribution.
+   * 
+   * @return The current mean of the Gaussian distribution.
+   */
+  public double getMean() {
+    return m_Mean;
+  }
+
+  /**
+   * Sets the mean of the Gaussian distribution to a new 
+   * mean.
+   *
+   * @param value The new mean for the distribution.
+   */
+  public void setMean(double value) {
+    m_Mean = value;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String meanTipText() {
+    return "The mean of the underlying distribution.";
+  }
+
+  /**
+   * Gets the current standard deviation of the underlying distribution.
+   *
+   * @return 		The current standard deviation of the distribution.
+   */
+  public double getStandardDeviation() {
+    return m_StandardDeviation;
+  }
+
+  /**
+   * Sets the standard deviation of the distribution to a new value.
+   *
+   * @param value 	The new standard deviation.
+   */
+  public void setStandardDeviation(double value) {
+    if (value > 0)
+      m_StandardDeviation = value;
+    else
+      m_StandardDeviation = 0.01;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String standardDeviationTipText() {
+    return "The standard deviation of the underlying distribution.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableGenerator.java	(revision 29)
@@ -0,0 +1,138 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableGenerator.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * An abstract superclass for generators that use a seeded internal random 
+ * number generator.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5793 $
+ */
+public abstract class RandomizableGenerator
+  extends Generator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4182619078970023472L;
+
+  /** The random number generator. */
+  protected Random m_Random = new Random(1);
+
+  /** The seed to the random number generator. */
+  protected long m_Seed = 1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements())
+      result.addElement(enu.nextElement());
+
+    result.addElement(new Option(
+	"\tSets the seed of the random number generator of the generator"
+	+ "\t(default: 1)",
+	"S", 1, "-S <seed>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("S", options);
+    if (tmpStr.length() != 0)
+      setSeed(Long.parseLong(tmpStr));
+    else
+      setSeed(1);
+  }
+
+  /**
+   * Gets the current settings of the generator.
+   *
+   * @return 		An array of strings suitable for passing to setOptions.
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-S");
+    result.add("" + m_Seed);
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the seed to the random number generator.
+   *
+   * @param value 	The new seed for the random number generator.
+   */
+  public void setSeed(long value) {
+    m_Seed   = value;
+    m_Random = new Random(m_Seed);
+  }
+
+  /**
+   * Gets the current random number generator seed.
+   *
+   * @return 		The current random number generator seed.
+   */
+  public long getSeed() {
+    return m_Seed;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed value for the random number generator.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableRangedGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableRangedGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/RandomizableRangedGenerator.java	(revision 29)
@@ -0,0 +1,179 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableRangedGenerator.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.generators;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract superclass for generators that take ranges and use a seeded random
+ * number generator internally
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5793 $
+ */
+public abstract class RandomizableRangedGenerator
+  extends RandomizableGenerator
+  implements Ranged {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -5766761200929361752L;
+
+  /** The lower range of this generator. */
+  protected double m_LowerRange = 0.0;
+
+  /** The upper range of this generator. */
+  protected double m_UpperRange = 1.0;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements())
+      result.addElement(enu.nextElement());
+
+    result.addElement(new Option(
+	"\tSets the lower range of the generator\n"
+	+ "\t(default: 0)",
+	"L", 1, "-L <num>"));
+
+    result.addElement(new Option(
+	"\tSets the upper range of the generator\n"
+	+ "\t(default: 1)",
+	"U", 1, "-U <num>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("L", options);
+    if (tmpStr.length() != 0)
+      setLowerRange(Double.parseDouble(tmpStr));
+    else
+      setLowerRange(0.0);
+
+    tmpStr = Utils.getOption("U", options);
+    if (tmpStr.length() != 0)
+      setUpperRange(Double.parseDouble(tmpStr));
+    else
+      setUpperRange(1.0);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-L");
+    result.add("" + m_LowerRange);
+    
+    result.add("-U");
+    result.add("" + m_UpperRange);
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Gets the lower range of the generator.
+   *
+   * @return 		The lower range of this generator.
+   */
+  public double getLowerRange() {
+    return m_LowerRange;
+  }
+
+  /**
+   * Sets the lower range.
+   *
+   * @param value 	The lower range of the generator.
+   */
+  public void setLowerRange(double value) {
+    m_LowerRange = value;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String lowerRangeTipText() {
+    return "The lower range.";
+  }
+
+  /**
+   * Gets the upper range of the generator.
+   *
+   * @return 		The upper range of this generator.
+   */
+  public double getUpperRange() {
+    return m_UpperRange;
+  }
+
+  /**
+   * Sets the upper range.
+   *
+   * @param value 	The upper range of the generator.
+   */
+  public void setUpperRange(double value) {
+    m_UpperRange = value;
+  }    
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String upperRangeTipText() {
+    return "The upper range.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Ranged.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Ranged.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/Ranged.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   Ranged.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+/**
+ * An interface indicating that this generator 
+ * expect to be given a range of values to operate
+ * within.
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5793 $
+ */
+public interface Ranged {
+
+  /**
+   * Sets the lower range.
+   *
+   * @param lower The lower range of the generator.
+   */
+  public void setLowerRange(double lower);
+
+  /**
+   * Sets the upper range.
+   *
+   * @param upper The upper range of the generator.
+   */
+  public void setUpperRange(double upper);
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/UniformDataGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/UniformDataGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/generators/UniformDataGenerator.java	(revision 29)
@@ -0,0 +1,116 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/**
+ *   UniformDataGenerator.java
+ *   Copyright (C) 2008 K.Hempstalk, University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.classifiers.meta.generators;
+
+/**
+ <!-- globalinfo-start -->
+ * A uniform artificial data generator.<br/>
+ * <br/>
+ * This generator uses a uniform data model - all values have the same probability, and generated values must fall within the range given to the generator.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, generator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Sets the seed of the random number generator of the generator (default: 1)</pre>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  Sets the lower range of the generator
+ *  (default: 0)</pre>
+ * 
+ * <pre> -U &lt;num&gt;
+ *  Sets the upper range of the generator
+ *  (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz)
+ * @version $Revision: 5861 $
+ */
+public class UniformDataGenerator 
+  extends RandomizableRangedGenerator
+  implements NumericAttributeGenerator {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -6390354660638644832L;
+
+  /**
+   * Returns a string describing this class' ability.
+   *
+   * @return A description of the class.
+   */
+  public String globalInfo() {
+    return 
+        "A uniform artificial data generator.\n"
+      + "\n"
+      + "This generator uses a uniform data model - all values have "
+      + "the same probability, and generated values must fall within "
+      + "the range given to the generator.";
+  }
+
+  /**
+   * Generates a value that falls under this distribution.
+   *
+   * @return A generated value.
+   */
+  public double generate() {
+    double range = (m_UpperRange - m_LowerRange);	
+    return (m_Random.nextDouble() * range) + m_LowerRange;
+  }
+
+  /**
+   * Gets the probability that a value falls under
+   * this distribution.
+   * 
+   *
+   * @param somedata The value to get the probability of.
+   * @return The probability of the given value.
+   */
+  public double getProbabilityOf(double somedata) {
+    double range = (m_UpperRange - m_LowerRange);
+    if (range <= 0 || somedata > m_UpperRange || somedata < m_LowerRange) {
+      return Double.MIN_VALUE;
+    }
+
+    return 1 / (range);
+  }
+
+  /**
+   * Gets the (natural) log of the probability of a given value.
+   *
+   * @param somedata The value to get the log probability of.
+   * @return The (natural) log of the probability.
+   */ 
+  public double getLogProbabilityOf(double somedata) {
+    double range = (m_UpperRange - m_LowerRange);	
+    if ((range <= 0) || (((somedata < m_LowerRange) || (somedata > m_UpperRange)))) {	    
+      return Math.log(Double.MIN_VALUE);
+    }
+    return -Math.log(range);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedND.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedND.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedND.java	(revision 29)
@@ -0,0 +1,528 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassBalancedND.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+import weka.filters.unsupervised.instance.RemoveWithValues;
+
+import java.util.Hashtable;
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * A meta classifier for handling multi-class datasets with 2-class classifiers by building a random class-balanced tree structure.<br/>
+ * <br/>
+ * For more info, check<br/>
+ * <br/>
+ * Lin Dong, Eibe Frank, Stefan Kramer: Ensembles of Balanced Nested Dichotomies for Multi-class Problems. In: PKDD, 84-95, 2005.<br/>
+ * <br/>
+ * Eibe Frank, Stefan Kramer: Ensembles of nested dichotomies for multi-class problems. In: Twenty-first International Conference on Machine Learning, 2004.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Dong2005,
+ *    author = {Lin Dong and Eibe Frank and Stefan Kramer},
+ *    booktitle = {PKDD},
+ *    pages = {84-95},
+ *    publisher = {Springer},
+ *    title = {Ensembles of Balanced Nested Dichotomies for Multi-class Problems},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Frank2004,
+ *    author = {Eibe Frank and Stefan Kramer},
+ *    booktitle = {Twenty-first International Conference on Machine Learning},
+ *    publisher = {ACM},
+ *    title = {Ensembles of nested dichotomies for multi-class problems},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lin Dong
+ * @author Eibe Frank
+ */
+public class ClassBalancedND 
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5944063630650811903L;
+  
+  /** The filtered classifier in which the base classifier is wrapped. */
+  protected FilteredClassifier m_FilteredClassifier;
+    
+  /** The hashtable for this node. */
+  protected Hashtable m_classifiers;
+
+  /** The first successor */
+  protected ClassBalancedND m_FirstSuccessor = null;
+
+  /** The second successor */
+  protected ClassBalancedND m_SecondSuccessor = null;
+  
+  /** The classes that are grouped together at the current node */
+  protected Range m_Range = null;
+    
+  /** Is Hashtable given from END? */
+  protected boolean m_hashtablegiven = false;
+    
+  /**
+   * Constructor.
+   */
+  public ClassBalancedND() {
+    
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lin Dong and Eibe Frank and Stefan Kramer");
+    result.setValue(Field.TITLE, "Ensembles of Balanced Nested Dichotomies for Multi-class Problems");
+    result.setValue(Field.BOOKTITLE, "PKDD");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.PAGES, "84-95");
+    result.setValue(Field.PUBLISHER, "Springer");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Eibe Frank and Stefan Kramer");
+    additional.setValue(Field.TITLE, "Ensembles of nested dichotomies for multi-class problems");
+    additional.setValue(Field.BOOKTITLE, "Twenty-first International Conference on Machine Learning");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.PUBLISHER, "ACM");
+    
+    return result;
+  }
+
+  /**
+   * Set hashtable from END.
+   * 
+   * @param table the hashtable to use
+   */
+  public void setHashtable(Hashtable table) {
+
+    m_hashtablegiven = true;
+    m_classifiers = table;
+  }
+    
+  /**
+   * Generates a classifier for the current node and proceeds recursively.
+   *
+   * @param data contains the (multi-class) instances
+   * @param classes contains the indices of the classes that are present
+   * @param rand the random number generator to use
+   * @param classifier the classifier to use
+   * @param table the Hashtable to use
+   * @throws Exception if anything goes worng
+   */
+  private void generateClassifierForNode(Instances data, Range classes,
+                                         Random rand, Classifier classifier, Hashtable table) 
+    throws Exception {
+	
+    // Get the indices
+    int[] indices = classes.getSelection();
+
+    // Randomize the order of the indices
+    for (int j = indices.length - 1; j > 0; j--) {
+      int randPos = rand.nextInt(j + 1);
+      int temp = indices[randPos];
+      indices[randPos] = indices[j];
+      indices[j] = temp;
+    }
+
+    // Pick the classes for the current split
+    int first = indices.length / 2;
+    int second = indices.length - first;
+    int[] firstInds = new int[first];
+    int[] secondInds = new int[second];
+    System.arraycopy(indices, 0, firstInds, 0, first);
+    System.arraycopy(indices, first, secondInds, 0, second);
+    	
+    // Sort the indices (important for hash key)!
+    int[] sortedFirst = Utils.sort(firstInds);
+    int[] sortedSecond = Utils.sort(secondInds);
+    int[] firstCopy = new int[first];
+    int[] secondCopy = new int[second];
+    for (int i = 0; i < sortedFirst.length; i++) {
+      firstCopy[i] = firstInds[sortedFirst[i]];
+    }
+    firstInds = firstCopy;
+    for (int i = 0; i < sortedSecond.length; i++) {
+      secondCopy[i] = secondInds[sortedSecond[i]];
+    }
+    secondInds = secondCopy;
+		
+    // Unify indices to improve hashing
+    if (firstInds[0] > secondInds[0]) {
+      int[] help = secondInds;
+      secondInds = firstInds;
+      firstInds = help;
+      int help2 = second;
+      second = first;
+      first = help2;
+    }
+
+    m_Range = new Range(Range.indicesToRangeList(firstInds));
+    m_Range.setUpper(data.numClasses() - 1);
+
+    Range secondRange = new Range(Range.indicesToRangeList(secondInds));
+    secondRange.setUpper(data.numClasses() - 1);
+       
+    // Change the class labels and build the classifier
+    MakeIndicator filter = new MakeIndicator();
+    filter.setAttributeIndex("" + (data.classIndex() + 1));
+    filter.setValueIndices(m_Range.getRanges());
+    filter.setNumeric(false);
+    filter.setInputFormat(data);
+    m_FilteredClassifier = new FilteredClassifier();
+    if (data.numInstances() > 0) {
+      m_FilteredClassifier.setClassifier(AbstractClassifier.makeCopies(classifier, 1)[0]);
+    } else {
+      m_FilteredClassifier.setClassifier(new weka.classifiers.rules.ZeroR());
+    }
+    m_FilteredClassifier.setFilter(filter);
+
+    // Save reference to hash table at current node
+    m_classifiers=table;
+	
+    if (!m_classifiers.containsKey( getString(firstInds) + "|" + getString(secondInds))) {
+      m_FilteredClassifier.buildClassifier(data);
+      m_classifiers.put(getString(firstInds) + "|" + getString(secondInds), m_FilteredClassifier);
+    } else {
+      m_FilteredClassifier=(FilteredClassifier)m_classifiers.get(getString(firstInds) + "|" + 
+								 getString(secondInds));	
+    }
+				
+    // Create two successors if necessary
+    m_FirstSuccessor = new ClassBalancedND();
+    if (first == 1) {
+      m_FirstSuccessor.m_Range = m_Range;
+    } else {
+      RemoveWithValues rwv = new RemoveWithValues();
+      rwv.setInvertSelection(true);
+      rwv.setNominalIndices(m_Range.getRanges());
+      rwv.setAttributeIndex("" + (data.classIndex() + 1));
+      rwv.setInputFormat(data);
+      Instances firstSubset = Filter.useFilter(data, rwv);
+      m_FirstSuccessor.generateClassifierForNode(firstSubset, m_Range, 
+                                                 rand, classifier, m_classifiers);
+    }
+    m_SecondSuccessor = new ClassBalancedND();
+    if (second == 1) {
+      m_SecondSuccessor.m_Range = secondRange;
+    } else {
+      RemoveWithValues rwv = new RemoveWithValues();
+      rwv.setInvertSelection(true);
+      rwv.setNominalIndices(secondRange.getRanges());
+      rwv.setAttributeIndex("" + (data.classIndex() + 1));
+      rwv.setInputFormat(data);
+      Instances secondSubset = Filter.useFilter(data, rwv);
+      m_SecondSuccessor = new ClassBalancedND();
+      
+      m_SecondSuccessor.generateClassifierForNode(secondSubset, secondRange, 
+                                                  rand, classifier, m_classifiers);
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(1);
+    
+    return result;
+  }
+    
+  /**
+   * Builds tree recursively.
+   *
+   * @param data contains the (multi-class) instances
+   * @throws Exception if the building fails
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    Random random = data.getRandomNumberGenerator(m_Seed);
+	
+    if (!m_hashtablegiven) {
+      m_classifiers = new Hashtable();
+    }
+	
+    // Check which classes are present in the
+    // data and construct initial list of classes
+    boolean[] present = new boolean[data.numClasses()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      present[(int)data.instance(i).classValue()] = true;
+    }
+    StringBuffer list = new StringBuffer();
+    for (int i = 0; i < present.length; i++) {
+      if (present[i]) {
+        if (list.length() > 0) {
+          list.append(",");
+        }
+        list.append(i + 1);
+      }
+    }
+      
+    Range newRange = new Range(list.toString());
+    newRange.setUpper(data.numClasses() - 1);
+	
+    generateClassifierForNode(data, newRange, random, m_Classifier, m_classifiers);
+  }
+    
+  /**
+   * Predicts the class distribution for a given instance
+   *
+   * @param inst the (multi-class) instance to be classified
+   * @return the class distribution
+   * @throws Exception if computing fails
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+	
+    double[] newDist = new double[inst.numClasses()];
+    if (m_FirstSuccessor == null) {
+      for (int i = 0; i < inst.numClasses(); i++) {
+        if (m_Range.isInRange(i)) {
+          newDist[i] = 1;
+        }
+      }
+      return newDist;
+    } else {
+      double[] firstDist = m_FirstSuccessor.distributionForInstance(inst);
+      double[] secondDist = m_SecondSuccessor.distributionForInstance(inst);
+      double[] dist = m_FilteredClassifier.distributionForInstance(inst);
+      for (int i = 0; i < inst.numClasses(); i++) {
+        if ((firstDist[i] > 0) && (secondDist[i] > 0)) {
+          System.err.println("Panik!!");
+        }
+        if (m_Range.isInRange(i)) {
+          newDist[i] = dist[1] * firstDist[i];
+        } else {
+          newDist[i] = dist[0] * secondDist[i];
+        }
+      }
+      return newDist;
+    }
+  }
+    
+  /**
+   * Returns the list of indices as a string.
+   * 
+   * @param indices the indices to return as string
+   * @return the indices as string
+   */
+  public String getString(int [] indices) {
+
+    StringBuffer string = new StringBuffer();
+    for (int i = 0; i < indices.length; i++) {
+      if (i > 0) {
+        string.append(',');
+      }
+      string.append(indices[i]);
+    }
+    return string.toString();
+  }
+	
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+	    
+    return 
+        "A meta classifier for handling multi-class datasets with 2-class "
+      + "classifiers by building a random class-balanced tree structure.\n\n"
+      + "For more info, check\n\n"
+      + getTechnicalInformation().toString();
+  }
+	
+  /**
+   * Outputs the classifier as a string.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+	    
+    if (m_classifiers == null) {
+      return "ClassBalancedND: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("ClassBalancedND");
+    treeToString(text, 0);
+	    
+    return text.toString();
+  }
+	
+  /**
+   * Returns string description of the tree.
+   * 
+   * @param text the buffer to add the node to
+   * @param nn the node number
+   * @return the next node number
+   */
+  private int treeToString(StringBuffer text, int nn) {
+	    
+    nn++;
+    text.append("\n\nNode number: " + nn + "\n\n");
+    if (m_FilteredClassifier != null) {
+      text.append(m_FilteredClassifier);
+    } else {
+      text.append("null");
+    }
+    if (m_FirstSuccessor != null) {
+      nn = m_FirstSuccessor.treeToString(text, nn);
+      nn = m_SecondSuccessor.treeToString(text, nn);
+    }
+    return nn;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    	
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new ClassBalancedND(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedND.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedND.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedND.java	(revision 29)
@@ -0,0 +1,582 @@
+/*
+ *    This program is free software; you can redistribsute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataNearBalancedND.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+import weka.filters.unsupervised.instance.RemoveWithValues;
+
+import java.util.Hashtable;
+import java.util.Random;
+
+
+/**
+ <!-- globalinfo-start -->
+ * A meta classifier for handling multi-class datasets with 2-class classifiers by building a random data-balanced tree structure.<br/>
+ * <br/>
+ * For more info, check<br/>
+ * <br/>
+ * Lin Dong, Eibe Frank, Stefan Kramer: Ensembles of Balanced Nested Dichotomies for Multi-class Problems. In: PKDD, 84-95, 2005.<br/>
+ * <br/>
+ * Eibe Frank, Stefan Kramer: Ensembles of nested dichotomies for multi-class problems. In: Twenty-first International Conference on Machine Learning, 2004.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Dong2005,
+ *    author = {Lin Dong and Eibe Frank and Stefan Kramer},
+ *    booktitle = {PKDD},
+ *    pages = {84-95},
+ *    publisher = {Springer},
+ *    title = {Ensembles of Balanced Nested Dichotomies for Multi-class Problems},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Frank2004,
+ *    author = {Eibe Frank and Stefan Kramer},
+ *    booktitle = {Twenty-first International Conference on Machine Learning},
+ *    publisher = {ACM},
+ *    title = {Ensembles of nested dichotomies for multi-class problems},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lin Dong
+ * @author Eibe Frank
+ */
+public class DataNearBalancedND 
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5117477294209496368L;
+  
+  /** The filtered classifier in which the base classifier is wrapped. */
+  protected FilteredClassifier m_FilteredClassifier;
+    
+  /** The hashtable for this node. */
+  protected Hashtable m_classifiers=new Hashtable();
+
+  /** The first successor */
+  protected DataNearBalancedND m_FirstSuccessor = null;
+
+  /** The second successor */
+  protected DataNearBalancedND m_SecondSuccessor = null;
+  
+  /** The classes that are grouped together at the current node */
+  protected Range m_Range = null;
+    
+  /** Is Hashtable given from END? */
+  protected boolean m_hashtablegiven = false;
+    
+  /**
+   * Constructor.
+   */
+  public DataNearBalancedND() {
+    
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lin Dong and Eibe Frank and Stefan Kramer");
+    result.setValue(Field.TITLE, "Ensembles of Balanced Nested Dichotomies for Multi-class Problems");
+    result.setValue(Field.BOOKTITLE, "PKDD");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.PAGES, "84-95");
+    result.setValue(Field.PUBLISHER, "Springer");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Eibe Frank and Stefan Kramer");
+    additional.setValue(Field.TITLE, "Ensembles of nested dichotomies for multi-class problems");
+    additional.setValue(Field.BOOKTITLE, "Twenty-first International Conference on Machine Learning");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.PUBLISHER, "ACM");
+    
+    return result;
+  }
+
+  /**
+   * Set hashtable from END.
+   * 
+   * @param table the hashtable to use
+   */
+  public void setHashtable(Hashtable table) {
+
+    m_hashtablegiven = true;
+    m_classifiers = table;
+  }
+    
+  /**
+   * Generates a classifier for the current node and proceeds recursively.
+   *
+   * @param data contains the (multi-class) instances
+   * @param classes contains the indices of the classes that are present
+   * @param rand the random number generator to use
+   * @param classifier the classifier to use
+   * @param table the Hashtable to use
+   * @param instsNumAllClasses
+   * @throws Exception if anything goes worng
+   */
+  private void generateClassifierForNode(Instances data, Range classes,
+                                         Random rand, Classifier classifier, Hashtable table,
+                                         double[] instsNumAllClasses) 
+    throws Exception {
+	
+    // Get the indices
+    int[] indices = classes.getSelection();
+
+    // Randomize the order of the indices
+    for (int j = indices.length - 1; j > 0; j--) {
+      int randPos = rand.nextInt(j + 1);
+      int temp = indices[randPos];
+      indices[randPos] = indices[j];
+      indices[j] = temp;
+    }
+
+    // Pick the classes for the current split
+    double total = 0;
+    for (int j = 0; j < indices.length; j++) {
+      total += instsNumAllClasses[indices[j]];
+    }
+    double halfOfTotal = total / 2;
+	
+    // Go through the list of classes until the either the left or
+    // right subset exceeds half the total weight
+    double sumLeft = 0, sumRight = 0;
+    int i = 0, j = indices.length - 1;
+    do {
+      if (i == j) {
+        if (rand.nextBoolean()) {
+          sumLeft += instsNumAllClasses[indices[i++]];
+        } else {
+          sumRight += instsNumAllClasses[indices[j--]];
+        }
+      } else {
+        sumLeft += instsNumAllClasses[indices[i++]];
+        sumRight += instsNumAllClasses[indices[j--]];
+      }
+    } while (Utils.sm(sumLeft, halfOfTotal) && Utils.sm(sumRight, halfOfTotal));
+
+    int first = 0, second = 0;
+    if (!Utils.sm(sumLeft, halfOfTotal)) {
+      first = i;
+    } else {
+      first = j + 1;
+    }
+    second = indices.length - first;
+
+    int[] firstInds = new int[first];
+    int[] secondInds = new int[second];
+    System.arraycopy(indices, 0, firstInds, 0, first);
+    System.arraycopy(indices, first, secondInds, 0, second);
+    	
+    // Sort the indices (important for hash key)!
+    int[] sortedFirst = Utils.sort(firstInds);
+    int[] sortedSecond = Utils.sort(secondInds);
+    int[] firstCopy = new int[first];
+    int[] secondCopy = new int[second];
+       for (int k = 0; k < sortedFirst.length; k++) {
+      firstCopy[k] = firstInds[sortedFirst[k]];
+    }
+    firstInds = firstCopy;
+    for (int k = 0; k < sortedSecond.length; k++) {
+      secondCopy[k] = secondInds[sortedSecond[k]];
+    }
+    secondInds = secondCopy;
+		
+    // Unify indices to improve hashing
+    if (firstInds[0] > secondInds[0]) {
+      int[] help = secondInds;
+      secondInds = firstInds;
+      firstInds = help;
+      int help2 = second;
+      second = first;
+      first = help2;
+    }
+
+    m_Range = new Range(Range.indicesToRangeList(firstInds));
+    m_Range.setUpper(data.numClasses() - 1);
+
+    Range secondRange = new Range(Range.indicesToRangeList(secondInds));
+    secondRange.setUpper(data.numClasses() - 1);
+       
+    // Change the class labels and build the classifier
+    MakeIndicator filter = new MakeIndicator();
+    filter.setAttributeIndex("" + (data.classIndex() + 1));
+    filter.setValueIndices(m_Range.getRanges());
+    filter.setNumeric(false);
+    filter.setInputFormat(data);
+    m_FilteredClassifier = new FilteredClassifier();
+    if (data.numInstances() > 0) {
+      m_FilteredClassifier.setClassifier(AbstractClassifier.makeCopies(classifier, 1)[0]);
+    } else {
+      m_FilteredClassifier.setClassifier(new weka.classifiers.rules.ZeroR());
+    }
+    m_FilteredClassifier.setFilter(filter);
+
+    // Save reference to hash table at current node
+    m_classifiers=table;
+	
+    if (!m_classifiers.containsKey( getString(firstInds) + "|" + getString(secondInds))) {
+      m_FilteredClassifier.buildClassifier(data);
+      m_classifiers.put(getString(firstInds) + "|" + getString(secondInds), m_FilteredClassifier);
+    } else {
+      m_FilteredClassifier=(FilteredClassifier)m_classifiers.get(getString(firstInds) + "|" + 
+								 getString(secondInds));	
+    }
+				
+    // Create two successors if necessary
+    m_FirstSuccessor = new DataNearBalancedND();
+    if (first == 1) {
+      m_FirstSuccessor.m_Range = m_Range;
+    } else {
+      RemoveWithValues rwv = new RemoveWithValues();
+      rwv.setInvertSelection(true);
+      rwv.setNominalIndices(m_Range.getRanges());
+      rwv.setAttributeIndex("" + (data.classIndex() + 1));
+      rwv.setInputFormat(data);
+      Instances firstSubset = Filter.useFilter(data, rwv);
+      m_FirstSuccessor.generateClassifierForNode(firstSubset, m_Range, 
+                                                 rand, classifier, m_classifiers,
+                                                 instsNumAllClasses);
+    }
+    m_SecondSuccessor = new DataNearBalancedND();
+    if (second == 1) {
+      m_SecondSuccessor.m_Range = secondRange;
+    } else {
+      RemoveWithValues rwv = new RemoveWithValues();
+      rwv.setInvertSelection(true);
+      rwv.setNominalIndices(secondRange.getRanges());
+      rwv.setAttributeIndex("" + (data.classIndex() + 1));
+      rwv.setInputFormat(data);
+      Instances secondSubset = Filter.useFilter(data, rwv);
+      m_SecondSuccessor = new DataNearBalancedND();
+      
+      m_SecondSuccessor.generateClassifierForNode(secondSubset, secondRange, 
+                                                  rand, classifier, m_classifiers,
+                                                  instsNumAllClasses);
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(1);
+    
+    return result;
+  }
+    
+  /**
+   * Builds tree recursively.
+   *
+   * @param data contains the (multi-class) instances
+   * @throws Exception if the building fails
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    Random random = data.getRandomNumberGenerator(m_Seed);
+	
+    if (!m_hashtablegiven) {
+      m_classifiers = new Hashtable();
+    }
+	
+    // Check which classes are present in the
+    // data and construct initial list of classes
+    boolean[] present = new boolean[data.numClasses()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      present[(int)data.instance(i).classValue()] = true;
+    }
+    StringBuffer list = new StringBuffer();
+    for (int i = 0; i < present.length; i++) {
+      if (present[i]) {
+        if (list.length() > 0) {
+          list.append(",");
+        }
+        list.append(i + 1);
+      }
+    }
+
+    // Determine the number of instances in each class
+    double[] instsNum = new double[data.numClasses()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      instsNum[(int)data.instance(i).classValue()] += data.instance(i).weight();
+    }
+      
+    Range newRange = new Range(list.toString());
+    newRange.setUpper(data.numClasses() - 1);
+	
+    generateClassifierForNode(data, newRange, random, m_Classifier, m_classifiers, instsNum);
+  }
+    
+  /**
+   * Predicts the class distribution for a given instance
+   *
+   * @param inst the (multi-class) instance to be classified
+   * @return the class distribution
+   * @throws Exception if computing fails
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+	
+    double[] newDist = new double[inst.numClasses()];
+    if (m_FirstSuccessor == null) {
+      for (int i = 0; i < inst.numClasses(); i++) {
+        if (m_Range.isInRange(i)) {
+          newDist[i] = 1;
+        }
+      }
+      return newDist;
+    } else {
+      double[] firstDist = m_FirstSuccessor.distributionForInstance(inst);
+      double[] secondDist = m_SecondSuccessor.distributionForInstance(inst);
+      double[] dist = m_FilteredClassifier.distributionForInstance(inst);
+      for (int i = 0; i < inst.numClasses(); i++) {
+        if ((firstDist[i] > 0) && (secondDist[i] > 0)) {
+          System.err.println("Panik!!");
+        }
+        if (m_Range.isInRange(i)) {
+          newDist[i] = dist[1] * firstDist[i];
+        } else {
+          newDist[i] = dist[0] * secondDist[i];
+        }
+      }
+      if  (!Utils.eq(Utils.sum(newDist), 1)) {
+        System.err.println(Utils.sum(newDist));
+        for (int j = 0; j < dist.length; j++) {
+          System.err.print(dist[j] + " ");
+        }
+        System.err.println();
+        for (int j = 0; j < newDist.length; j++) {
+          System.err.print(newDist[j] + " ");
+        }
+        System.err.println();
+        System.err.println(inst);
+        System.err.println(m_FilteredClassifier);
+        //System.err.println(m_Data);
+        System.err.println("bad");
+      }
+      return newDist;
+    }
+  }
+    
+  /**
+   * Returns the list of indices as a string.
+   * 
+   * @param indices the indices to return as string
+   * @return the indices as string
+   */
+  public String getString(int [] indices) {
+
+    StringBuffer string = new StringBuffer();
+    for (int i = 0; i < indices.length; i++) {
+      if (i > 0) {
+        string.append(',');
+      }
+      string.append(indices[i]);
+    }
+    return string.toString();
+  }
+	
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+	    
+    return 
+        "A meta classifier for handling multi-class datasets with 2-class "
+      + "classifiers by building a random data-balanced tree structure.\n\n"
+      + "For more info, check\n\n"
+      + getTechnicalInformation().toString();
+  }
+	
+  /**
+   * Outputs the classifier as a string.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+	    
+    if (m_classifiers == null) {
+      return "DataNearBalancedND: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("DataNearBalancedND");
+    treeToString(text, 0);
+	    
+    return text.toString();
+  }
+	
+  /**
+   * Returns string description of the tree.
+   * 
+   * @param text the buffer to add the node to
+   * @param nn the node number
+   * @return the next node number
+   */
+  private int treeToString(StringBuffer text, int nn) {
+	    
+    nn++;
+    text.append("\n\nNode number: " + nn + "\n\n");
+    if (m_FilteredClassifier != null) {
+      text.append(m_FilteredClassifier);
+    } else {
+      text.append("null");
+    }
+    if (m_FirstSuccessor != null) {
+      nn = m_FirstSuccessor.treeToString(text, nn);
+      nn = m_SecondSuccessor.treeToString(text, nn);
+    }
+    return nn;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    	
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new DataNearBalancedND(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ND.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ND.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/meta/nestedDichotomies/ND.java	(revision 29)
@@ -0,0 +1,632 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ND.java
+ *    Copyright (C) 2003-2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.RandomizableSingleClassifierEnhancer;
+import weka.classifiers.meta.FilteredClassifier;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MakeIndicator;
+import weka.filters.unsupervised.instance.RemoveWithValues;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.Random;
+
+/**
+ <!-- globalinfo-start -->
+ * A meta classifier for handling multi-class datasets with 2-class classifiers by building a random tree structure.<br/>
+ * <br/>
+ * For more info, check<br/>
+ * <br/>
+ * Lin Dong, Eibe Frank, Stefan Kramer: Ensembles of Balanced Nested Dichotomies for Multi-class Problems. In: PKDD, 84-95, 2005.<br/>
+ * <br/>
+ * Eibe Frank, Stefan Kramer: Ensembles of nested dichotomies for multi-class problems. In: Twenty-first International Conference on Machine Learning, 2004.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Dong2005,
+ *    author = {Lin Dong and Eibe Frank and Stefan Kramer},
+ *    booktitle = {PKDD},
+ *    pages = {84-95},
+ *    publisher = {Springer},
+ *    title = {Ensembles of Balanced Nested Dichotomies for Multi-class Problems},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Frank2004,
+ *    author = {Eibe Frank and Stefan Kramer},
+ *    booktitle = {Twenty-first International Conference on Machine Learning},
+ *    publisher = {ACM},
+ *    title = {Ensembles of nested dichotomies for multi-class problems},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.trees.J48)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.trees.J48:
+ * </pre>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank
+ * @author Lin Dong
+ */
+public class ND 
+  extends RandomizableSingleClassifierEnhancer
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6355893369855683820L;
+
+  /**
+   * a node class
+   */
+  protected class NDTree
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 4284655952754474880L;
+    
+    /** The indices associated with this node */
+    protected FastVector m_indices = null;
+    
+    /** The parent */
+    protected NDTree m_parent = null;
+    
+    /** The left successor */
+    protected NDTree m_left = null;
+    
+    /** The right successor */
+    protected NDTree m_right = null;
+    
+    /**
+     * Constructor.
+     */
+    protected NDTree() {
+      
+      m_indices = new FastVector(1);
+      m_indices.addElement(new Integer(Integer.MAX_VALUE));
+    }
+    
+    /**
+     * Locates the node with the given index (depth-first traversal).
+     */
+    protected NDTree locateNode(int nodeIndex, int[] currentIndex) {
+      
+      if (nodeIndex == currentIndex[0]) {
+	return this;
+      } else if (m_left == null) {
+	return null;
+      } else {
+	currentIndex[0]++;
+	NDTree leftresult = m_left.locateNode(nodeIndex, currentIndex);
+	if (leftresult != null) {
+	  return leftresult;
+	} else {
+	  currentIndex[0]++;
+	  return m_right.locateNode(nodeIndex, currentIndex);
+	}
+      }
+    }
+      
+    /**
+     * Inserts a class index into the tree. 
+     * 
+     * @param classIndex the class index to insert
+     */
+    protected void insertClassIndex(int classIndex) {
+
+      // Create new nodes
+      NDTree right = new NDTree();
+      if (m_left != null) {
+	m_right.m_parent = right;
+	m_left.m_parent = right;
+	right.m_right = m_right;
+	right.m_left = m_left;
+      }
+      m_right = right;
+      m_right.m_indices = (FastVector)m_indices.copy();
+      m_right.m_parent = this;
+      m_left = new NDTree();
+      m_left.insertClassIndexAtNode(classIndex);
+      m_left.m_parent = this; 
+
+      // Propagate class Index
+      propagateClassIndex(classIndex);
+    }
+
+    /**
+     * Propagates class index to the root.
+     * 
+     * @param classIndex the index to propagate to the root
+     */
+    protected void propagateClassIndex(int classIndex) {
+
+      insertClassIndexAtNode(classIndex);
+      if (m_parent != null) {
+	m_parent.propagateClassIndex(classIndex);
+      }
+    }
+    
+    /**
+     * Inserts the class index at a given node.
+     * 
+     * @param classIndex the classIndex to insert
+     */
+    protected void insertClassIndexAtNode(int classIndex) {
+
+      int i = 0;
+      while (classIndex > ((Integer)m_indices.elementAt(i)).intValue()) {
+	i++;
+      }
+      m_indices.insertElementAt(new Integer(classIndex), i);
+    }
+
+    /**
+     * Gets the indices in an array of ints.
+     * 
+     * @return the indices
+     */
+    protected int[] getIndices() {
+
+      int[] ints = new int[m_indices.size() - 1];
+      for (int i = 0; i < m_indices.size() - 1; i++) {
+	ints[i] = ((Integer)m_indices.elementAt(i)).intValue();
+      }
+      return ints;
+    }
+
+    /**
+     * Checks whether an index is in the array.
+     * 
+     * @param index the index to check
+     * @return true of the index is in the array
+     */
+    protected boolean contains(int index) {
+
+      for (int i = 0; i < m_indices.size() - 1; i++) {
+	if (index == ((Integer)m_indices.elementAt(i)).intValue()) {
+	  return true;
+	}
+      }
+      return false;
+    }
+
+    /**
+     * Returns the list of indices as a string.
+     * 
+     * @return the indices as string
+     */
+    protected String getString() {
+
+      StringBuffer string = new StringBuffer();
+      for (int i = 0; i < m_indices.size() - 1; i++) {
+	if (i > 0) {
+	  string.append(',');
+	}
+	string.append(((Integer)m_indices.elementAt(i)).intValue() + 1);
+      }
+      return string.toString();
+    }
+
+    /**
+     * Unifies tree for improve hashing.
+     */
+    protected void unifyTree() {
+
+      if (m_left != null) {
+        if (((Integer)m_left.m_indices.elementAt(0)).intValue() >
+            ((Integer)m_right.m_indices.elementAt(0)).intValue()) {
+          NDTree temp = m_left;
+          m_left = m_right;
+          m_right = temp;
+        }
+        m_left.unifyTree();
+        m_right.unifyTree();
+      }
+    }
+
+    /**
+     * Returns a description of the tree rooted at this node.
+     * 
+     * @param text the buffer to add the node to
+     * @param id the node id
+     * @param level the level of the tree
+     */
+    protected void toString(StringBuffer text, int[] id, int level) {
+
+      for (int i = 0; i < level; i++) {
+	text.append("   | ");
+      }
+      text.append(id[0] + ": " + getString() + "\n");
+      if (m_left != null) {
+	id[0]++;
+	m_left.toString(text, id, level + 1);
+	id[0]++;
+	m_right.toString(text, id, level + 1);
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /** The tree of classes */
+  protected NDTree m_ndtree = null;
+  
+  /** The hashtable containing all the classifiers */
+  protected Hashtable m_classifiers = null;
+
+  /** Is Hashtable given from END? */
+  protected boolean m_hashtablegiven = false;
+    
+  /**
+   * Constructor.
+   */
+  public ND() {
+    
+    m_Classifier = new weka.classifiers.trees.J48();
+  }
+  
+  /**
+   * String describing default classifier.
+   * 
+   * @return the default classifier classname
+   */
+  protected String defaultClassifierString() {
+    
+    return "weka.classifiers.trees.J48";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Lin Dong and Eibe Frank and Stefan Kramer");
+    result.setValue(Field.TITLE, "Ensembles of Balanced Nested Dichotomies for Multi-class Problems");
+    result.setValue(Field.BOOKTITLE, "PKDD");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.PAGES, "84-95");
+    result.setValue(Field.PUBLISHER, "Springer");
+
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Eibe Frank and Stefan Kramer");
+    additional.setValue(Field.TITLE, "Ensembles of nested dichotomies for multi-class problems");
+    additional.setValue(Field.BOOKTITLE, "Twenty-first International Conference on Machine Learning");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.PUBLISHER, "ACM");
+    
+    return result;
+  }
+
+  /**
+   * Set hashtable from END.
+   * 
+   * @param table the hashtable to use
+   */
+  public void setHashtable(Hashtable table) {
+
+    m_hashtablegiven = true;
+    m_classifiers = table;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(1);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier.
+   * 
+   * @param data the data to train the classifier with
+   * @throws Exception if anything goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    Random random = data.getRandomNumberGenerator(m_Seed);
+
+    if (!m_hashtablegiven) {
+      m_classifiers = new Hashtable();
+    }
+
+    // Generate random class hierarchy
+    int[] indices = new int[data.numClasses()];
+    for (int i = 0; i < indices.length; i++) {
+      indices[i] = i;
+    }
+
+    // Randomize list of class indices
+    for (int i = indices.length - 1; i > 0; i--) {
+      int help = indices[i];
+      int index = random.nextInt(i + 1);
+      indices[i] = indices[index];
+      indices[index] = help;
+    }
+
+    // Insert random class index at randomly chosen node
+    m_ndtree = new NDTree();
+    m_ndtree.insertClassIndexAtNode(indices[0]);
+    for (int i = 1; i < indices.length; i++) {
+      int nodeIndex = random.nextInt(2 * i - 1);
+     
+      NDTree node = m_ndtree.locateNode(nodeIndex, new int[1]);
+      node.insertClassIndex(indices[i]);
+    }
+    m_ndtree.unifyTree();
+    
+
+    // Build classifiers
+    buildClassifierForNode(m_ndtree, data);
+  }
+
+  /**
+   * Builds the classifier for one node.
+   * 
+   * @param node the node to build the classifier for
+   * @param data the data to work with
+   * @throws Exception if anything goes wrong
+   */
+  public void buildClassifierForNode(NDTree node, Instances data) throws Exception {
+
+    // Are we at a leaf node ?
+    if (node.m_left != null) {
+      
+      // Create classifier
+      MakeIndicator filter = new MakeIndicator();
+      filter.setAttributeIndex("" + (data.classIndex() + 1));
+      filter.setValueIndices(node.m_right.getString());
+      filter.setNumeric(false);
+      filter.setInputFormat(data);
+      FilteredClassifier classifier = new FilteredClassifier();
+      if (data.numInstances() > 0) {
+	classifier.setClassifier(AbstractClassifier.makeCopies(m_Classifier, 1)[0]);
+      } else {
+	classifier.setClassifier(new ZeroR());
+      }
+      classifier.setFilter(filter);
+      
+      if (!m_classifiers.containsKey(node.m_left.getString() + "|" + node.m_right.getString())) {
+	classifier.buildClassifier(data);
+	m_classifiers.put(node.m_left.getString() + "|" + node.m_right.getString(), classifier);
+      } else {
+	classifier=(FilteredClassifier)m_classifiers.get(node.m_left.getString() + "|" + 
+							 node.m_right.getString());
+      }
+      
+      // Generate successors
+      if (node.m_left.m_left != null) {
+        RemoveWithValues rwv = new RemoveWithValues();
+        rwv.setInvertSelection(true);
+        rwv.setNominalIndices(node.m_left.getString());
+        rwv.setAttributeIndex("" + (data.classIndex() + 1));
+        rwv.setInputFormat(data);
+        Instances firstSubset = Filter.useFilter(data, rwv);
+        buildClassifierForNode(node.m_left, firstSubset);
+      }
+      if (node.m_right.m_left != null) {
+        RemoveWithValues rwv = new RemoveWithValues();
+        rwv.setInvertSelection(true);
+        rwv.setNominalIndices(node.m_right.getString());
+        rwv.setAttributeIndex("" + (data.classIndex() + 1));
+        rwv.setInputFormat(data);
+        Instances secondSubset = Filter.useFilter(data, rwv);
+        buildClassifierForNode(node.m_right, secondSubset);
+      }
+    }
+  }
+    
+  /**
+   * Predicts the class distribution for a given instance
+   *
+   * @param inst the (multi-class) instance to be classified
+   * @return the class distribution
+   * @throws Exception if computing fails
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+	
+    return distributionForInstance(inst, m_ndtree);
+  }
+
+  /**
+   * Predicts the class distribution for a given instance
+   *
+   * @param inst the (multi-class) instance to be classified
+   * @param node the node to do get the distribution for
+   * @return the class distribution
+   * @throws Exception if computing fails
+   */
+  protected double[] distributionForInstance(Instance inst, NDTree node) throws Exception {
+
+    double[] newDist = new double[inst.numClasses()];
+    if (node.m_left == null) {
+      newDist[node.getIndices()[0]] = 1.0;
+      return newDist;
+    } else {
+      Classifier classifier = (Classifier)m_classifiers.get(node.m_left.getString() + "|" +
+							    node.m_right.getString());
+      double[] leftDist = distributionForInstance(inst, node.m_left);
+      double[] rightDist = distributionForInstance(inst, node.m_right);
+      double[] dist = classifier.distributionForInstance(inst);
+
+      for (int i = 0; i < inst.numClasses(); i++) {
+	if (node.m_right.contains(i)) {
+	  newDist[i] = dist[1] * rightDist[i];
+	} else {
+	  newDist[i] = dist[0] * leftDist[i];
+	}
+      }
+      return newDist;
+    }
+  }
+
+  /**
+   * Outputs the classifier as a string.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+	
+    if (m_classifiers == null) {
+      return "ND: No model built yet.";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("ND\n\n");
+    m_ndtree.toString(text, new int[1], 0);
+	
+    return text.toString();
+  }
+	
+  /**
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+	    
+    return 
+        "A meta classifier for handling multi-class datasets with 2-class "
+      + "classifiers by building a random tree structure.\n\n"
+      + "For more info, check\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new ND(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/CitationKNN.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/CitationKNN.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/CitationKNN.java	(revision 29)
@@ -0,0 +1,1165 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CitationKNN.java
+ * Copyright (C) 2005 Miguel Garcia Torres
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+/**
+ <!-- globalinfo-start -->
+ * Modified version of the Citation kNN multi instance classifier.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Jun Wang, Zucker, Jean-Daniel: Solving Multiple-Instance Problem: A Lazy Learning Approach. In: 17th International Conference on Machine Learning, 1119-1125, 2000.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Wang2000,
+ *    author = {Jun Wang and Zucker and Jean-Daniel},
+ *    booktitle = {17th International Conference on Machine Learning},
+ *    editor = {Pat Langley},
+ *    pages = {1119-1125},
+ *    title = {Solving Multiple-Instance Problem: A Lazy Learning Approach},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;number of references&gt;
+ *  Number of Nearest References (default 1)</pre>
+ * 
+ * <pre> -C &lt;number of citers&gt;
+ *  Number of Nearest Citers (default 1)</pre>
+ * 
+ * <pre> -H &lt;rank&gt;
+ *  Rank of the Hausdorff Distance (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Miguel Garcia Torres (mgarciat@ull.es)
+ * @version $Revision: 5928 $ 
+ */
+public class CitationKNN 
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8435377743874094852L;
+  
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** */
+  protected int m_IdIndex;    
+
+  /** Debugging output */
+  protected boolean m_Debug;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** attribute name structure of the relational attribute*/
+  protected Instances m_Attributes;
+
+  /** Number of references */
+  protected int m_NumReferences = 1;
+
+  /** Number of citers*/
+  protected int m_NumCiters = 1;
+
+  /** Training bags*/
+  protected Instances m_TrainBags;
+
+  /** Different debugging output */
+  protected boolean m_CNNDebug = false;
+
+  protected boolean m_CitersDebug = false;
+
+  protected boolean m_ReferencesDebug = false;
+
+  protected boolean m_HDistanceDebug = false;
+
+  protected boolean m_NeighborListDebug = false;
+
+  /** C nearest neighbors considering all the bags*/
+  protected NeighborList[] m_CNN;
+
+  /** C nearest citers */
+  protected int[] m_Citers;
+
+  /** R nearest references */
+  protected int[] m_References;
+
+  /** Rank associated to the Hausdorff distance*/
+  protected int m_HDRank = 1;
+
+  /** Normalization of the euclidean distance */
+  private double[] m_Diffs;
+
+  private double[] m_Min;
+
+  private double m_MinNorm = 0.95;
+
+  private double[] m_Max;
+
+  private double m_MaxNorm = 1.05;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Modified version of the Citation kNN multi instance classifier.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Jun Wang and Zucker and Jean-Daniel");
+    result.setValue(Field.TITLE, "Solving Multiple-Instance Problem: A Lazy Learning Approach");
+    result.setValue(Field.BOOKTITLE, "17th International Conference on Machine Learning");
+    result.setValue(Field.EDITOR, "Pat Langley");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.PAGES, "1119-1125");
+    
+    return result;
+  }
+
+  /** 
+   * Calculates the normalization of each attribute.
+   */
+  public void preprocessData(){
+    int i,j, k;
+    double min, max;
+    Instances instances;
+    Instance instance;
+    // compute the min/max of each feature
+
+    for (i=0;i<m_Attributes.numAttributes();i++) {
+      min=Double.POSITIVE_INFINITY ;
+      max=Double.NEGATIVE_INFINITY ;
+      for(j = 0; j < m_TrainBags.numInstances(); j++){
+        instances = m_TrainBags.instance(j).relationalValue(1);
+        for (k=0;k<instances.numInstances();k++) {
+          instance = instances.instance(k);
+          if(instance.value(i) < min)
+            min= instance.value(i);
+          if(instance.value(i) > max)
+            max= instance.value(i);
+        }
+      }
+      m_Min[i] = min * m_MinNorm;
+      m_Max[i] = max * m_MaxNorm;
+      m_Diffs[i]= max * m_MaxNorm - min * m_MinNorm;
+    }	    
+
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String HDRankTipText() {
+    return "The rank associated to the Hausdorff distance.";
+  }
+
+  /**
+   * Sets the rank associated to the Hausdorff distance
+   * @param hDRank the rank of the Hausdorff distance
+   */
+  public void setHDRank(int hDRank){
+    m_HDRank = hDRank;
+  }
+
+  /**
+   * Returns the rank associated to the Hausdorff distance
+   * @return the rank number
+   */
+  public int getHDRank(){
+    return m_HDRank;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numReferencesTipText() {
+    return 
+        "The number of references considered to estimate the class "
+      + "prediction of tests bags.";
+  }
+
+  /**
+   * Sets the number of references considered to estimate
+   * the class prediction of tests bags
+   * @param numReferences the number of references
+   */
+  public void setNumReferences(int numReferences){
+    m_NumReferences = numReferences;
+  }
+
+  /**
+   * Returns the number of references considered to estimate
+   * the class prediction of tests bags
+   * @return the number of references
+   */
+  public int getNumReferences(){
+    return m_NumReferences;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numCitersTipText() {
+    return 
+        "The number of citers considered to estimate the class "
+      + "prediction of test bags.";
+  }
+
+  /**
+   * Sets the number of citers considered to estimate
+   * the class prediction of tests bags
+   * @param numCiters the number of citers
+   */
+  public void setNumCiters(int numCiters){
+    m_NumCiters = numCiters;
+  }
+
+  /**
+   * Returns the number of citers considered to estimate
+   * the class prediction of tests bags
+   * @return the number of citers
+   */
+  public int getNumCiters(){
+    return m_NumCiters;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    m_TrainBags = train;
+    m_ClassIndex = train.classIndex();
+    m_IdIndex = 0;
+    m_NumClasses = train.numClasses();
+
+    m_Classes  = new int [train.numInstances()]; // Class values
+    m_Attributes = train.instance(0).relationalValue(1).stringFreeStructure();
+
+    m_Citers = new int[train.numClasses()];
+    m_References = new int[train.numClasses()];
+
+    m_Diffs = new double[m_Attributes.numAttributes()];
+    m_Min = new double[m_Attributes.numAttributes()];
+    m_Max = new double[m_Attributes.numAttributes()];	
+
+    preprocessData();
+
+    buildCNN();
+
+    if(m_CNNDebug){
+      System.out.println("########################################### ");
+      System.out.println("###########CITATION######################## ");
+      System.out.println("########################################### ");
+      for(int i = 0; i < m_CNN.length; i++){
+        System.out.println("Bag: " + i);
+        m_CNN[i].printReducedList();
+      }
+    }		
+  }
+
+  /**
+   * generates all the variables associated to the citation
+   * classifier
+   * 
+   * @throws Exception if generation fails
+   */
+  public void buildCNN() throws Exception {
+
+    int numCiters = 0;
+
+    if((m_NumCiters >= m_TrainBags.numInstances()) ||
+        (m_NumCiters < 0))
+      throw new Exception("Number of citers is out of the range [0, numInstances)");
+    else
+      numCiters = m_NumCiters;
+
+    m_CNN = new NeighborList[m_TrainBags.numInstances()]; 
+    Instance bag;
+
+    for(int i = 0; i< m_TrainBags.numInstances(); i++){
+      bag = m_TrainBags.instance(i);
+      //first we find its neighbors
+      NeighborList neighborList = findNeighbors(bag, numCiters, m_TrainBags);
+      m_CNN[i] = neighborList;
+    }
+  }
+
+  /**
+   * calculates the citers associated to a bag
+   * @param bag the bag cited
+   */
+  public void countBagCiters(Instance bag){
+
+    //Initialization of the vector
+    for(int i = 0; i < m_TrainBags.numClasses(); i++)
+      m_Citers[i] = 0;
+    //
+    if(m_CitersDebug == true)
+      System.out.println("-------CITERS--------");
+
+    NeighborList neighborList;
+    NeighborNode current;
+    boolean stopSearch = false;
+    int index;
+
+    // compute the distance between the test bag and each training bag. Update
+    // the bagCiter count in case it be a neighbour
+
+    double bagDistance = 0;
+    for(int i = 0; i < m_TrainBags.numInstances(); i++){
+      //measure the distance
+      bagDistance =  distanceSet(bag, m_TrainBags.instance(i));
+      if(m_CitersDebug == true){
+        System.out.print("bag - bag(" + i + "): " + bagDistance);
+        System.out.println("   <" + m_TrainBags.instance(i).classValue() + ">");
+      }
+      //compare the distance to see if it would belong to the
+      // neighborhood of each training exemplar
+      neighborList = m_CNN[i];
+      current = neighborList.mFirst;
+
+      while((current != null) && (!stopSearch)) {
+        if(m_CitersDebug == true)
+          System.out.println("\t\tciter Distance: " + current.mDistance);
+        if(current.mDistance < bagDistance){
+          current = current.mNext;
+        } else{  
+          stopSearch = true;		    
+          if(m_CitersDebug == true){
+            System.out.println("\t***");
+          }
+        }
+      } 
+
+      if(stopSearch == true){
+        stopSearch = false;
+        index = (int)(m_TrainBags.instance(i)).classValue();
+        m_Citers[index] += 1;
+      }
+
+    }
+
+    if(m_CitersDebug == true){
+      for(int i= 0; i < m_Citers.length; i++){
+        System.out.println("[" + i + "]: " + m_Citers[i]);
+      }
+    }
+
+  }
+
+  /**
+   * Calculates the references of the exemplar bag
+   * @param bag the exemplar to which the nearest references
+   * will be calculated
+   */
+  public void countBagReferences(Instance bag){
+    int index = 0, referencesIndex = 0;
+
+    if(m_TrainBags.numInstances() < m_NumReferences)
+      referencesIndex = m_TrainBags.numInstances() - 1;
+    else
+      referencesIndex = m_NumReferences;
+
+    if(m_CitersDebug == true){
+      System.out.println("-------References (" + referencesIndex+ ")--------");
+    }
+    //Initialization of the vector
+    for(int i = 0; i < m_References.length; i++)
+      m_References[i] = 0;
+
+    if(referencesIndex > 0){
+      //first we find its neighbors
+      NeighborList neighborList = findNeighbors(bag, referencesIndex, m_TrainBags);
+      if(m_ReferencesDebug == true){
+        System.out.println("Bag: " + bag + " Neighbors: ");
+        neighborList.printReducedList();
+      }
+      NeighborNode current = neighborList.mFirst;
+      while(current != null){
+        index = (int) current.mBag.classValue();
+        m_References[index] += 1;
+        current = current.mNext;
+      }
+    }
+    if(m_ReferencesDebug == true){
+      System.out.println("References:");
+      for(int j = 0; j < m_References.length; j++)
+        System.out.println("[" + j + "]: " + m_References[j]);
+    }
+  }
+
+  /**
+   * Build the list of nearest k neighbors to the given test instance.
+   * @param bag the bag to search for neighbors of
+   * @param kNN the number of nearest neighbors
+   * @param bags the data
+   * @return a list of neighbors
+   */
+  protected NeighborList findNeighbors(Instance bag, int kNN, Instances bags){
+    double distance;
+    int index = 0;
+
+    if(kNN > bags.numInstances())
+      kNN = bags.numInstances() - 1;
+
+    NeighborList neighborList = new NeighborList(kNN);
+    for(int i = 0; i < bags.numInstances(); i++){
+      if(bag != bags.instance(i)){ // for hold-one-out cross-validation
+        distance =  distanceSet(bag, bags.instance(i)) ; //mDistanceSet.distance(bag, mInstances, bags.exemplar(i), mInstances);
+        if(m_NeighborListDebug)
+          System.out.println("distance(bag, " + i + "): " + distance);
+        if(neighborList.isEmpty() || (index < kNN) || (distance <= neighborList.mLast.mDistance))
+          neighborList.insertSorted(distance, bags.instance(i), i);
+        index++;
+      } 
+    }
+
+    if(m_NeighborListDebug){
+      System.out.println("bag neighbors:");
+      neighborList.printReducedList();
+    }
+
+    return neighborList;
+  }
+
+  /**
+   * Calculates the distance between two instances
+   * @param first instance
+   * @param second instance
+   * @return the distance value
+   */
+  public double distanceSet(Instance first, Instance second){
+    double[] h_f = new double[first.relationalValue(1).numInstances()];
+    double distance;
+
+    //initilization
+    for(int i = 0; i < h_f.length; i++)
+      h_f[i] = Double.MAX_VALUE;
+
+
+    int rank;
+
+
+    if(m_HDRank >= first.relationalValue(1).numInstances())
+      rank = first.relationalValue(1).numInstances();
+    else if(m_HDRank < 1)
+      rank = 1;
+    else 
+      rank = m_HDRank;
+
+    if(m_HDistanceDebug){
+      System.out.println("-------HAUSDORFF DISTANCE--------");
+      System.out.println("rank: " + rank + "\nset of instances:");
+      System.out.println("\tset 1:");
+      for(int i = 0; i < first.relationalValue(1).numInstances(); i++)
+        System.out.println(first.relationalValue(1).instance(i));
+
+      System.out.println("\n\tset 2:");
+      for(int i = 0; i < second.relationalValue(1).numInstances(); i++)
+        System.out.println(second.relationalValue(1).instance(i));
+
+      System.out.println("\n");
+    }
+
+    //for each instance in bag first
+    for(int i = 0; i < first.relationalValue(1).numInstances(); i++){
+      // calculate the distance to each instance in 
+      // bag second
+      if(m_HDistanceDebug){
+        System.out.println("\nDistances:");
+      }
+      for(int j = 0; j < second.relationalValue(1).numInstances(); j++){
+        distance = distance(first.relationalValue(1).instance(i), second.relationalValue(1).instance(j));
+        if(distance < h_f[i])
+          h_f[i] = distance;
+        if(m_HDistanceDebug){
+          System.out.println("\tdist(" + i + ", "+ j + "): " + distance + "  --> h_f[" + i + "]: " + h_f[i]);
+        }
+      }
+    }
+    int[] index_f = Utils.stableSort(h_f);
+
+    if(m_HDistanceDebug){
+      System.out.println("\nRanks:\n");
+      for(int i = 0; i < index_f.length; i++)
+        System.out.println("\trank " + (i + 1) + ": " + h_f[index_f[i]]);
+
+      System.out.println("\n\t\t>>>>> rank " + rank + ": " + h_f[index_f[rank - 1]] + " <<<<<");
+    }
+
+    return h_f[index_f[rank - 1]];
+  }
+
+  /**
+   * distance between two instances
+   * @param first the first instance
+   * @param second the other instance
+   * @return the distance in double precision
+   */
+  public double distance(Instance first, Instance second){
+
+    double sum = 0, diff;
+    for(int i = 0; i < m_Attributes.numAttributes(); i++){
+      diff = (first.value(i) - m_Min[i])/ m_Diffs[i] - 
+        (second.value(i) - m_Min[i])/ m_Diffs[i];
+      sum += diff * diff;
+    }
+    return sum = Math.sqrt(sum);
+  }
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param bag the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance bag) 
+    throws Exception {
+
+    if(m_TrainBags.numInstances() == 0)
+      throw new Exception("No training bags!");
+
+    updateNormalization(bag);
+
+    //build references (R nearest neighbors)
+    countBagReferences(bag);
+
+    //build citers
+    countBagCiters(bag);
+
+    return makeDistribution();
+  }
+
+  /** 
+   * Updates the normalization of each attribute.
+   * 
+   * @param bag the exemplar to update the normalization for
+   */
+  public void updateNormalization(Instance bag){
+    int i, k;
+    double min, max;
+    Instances instances;
+    Instance instance;
+    // compute the min/max of each feature
+    for (i = 0; i < m_TrainBags.attribute(1).relation().numAttributes(); i++) {
+      min = m_Min[i] / m_MinNorm;
+      max = m_Max[i] / m_MaxNorm;
+
+      instances = bag.relationalValue(1);
+      for (k=0;k<instances.numInstances();k++) {
+        instance = instances.instance(k);
+        if(instance.value(i) < min)
+          min = instance.value(i);
+        if(instance.value(i) > max)
+          max = instance.value(i);
+      }
+      m_Min[i] = min * m_MinNorm;
+      m_Max[i] = max * m_MaxNorm;
+      m_Diffs[i]= max * m_MaxNorm - min * m_MinNorm;
+    }
+  }
+
+  /**
+   * Wether the instances of two exemplars are or  are not equal
+   * @param exemplar1 first exemplar
+   * @param exemplar2 second exemplar
+   * @return if the instances of the exemplars are equal or not
+   */
+  public boolean equalExemplars(Instance exemplar1, Instance exemplar2){
+    if(exemplar1.relationalValue(1).numInstances() == 
+        exemplar2.relationalValue(1).numInstances()){
+      Instances instances1 = exemplar1.relationalValue(1);
+      Instances instances2 = exemplar2.relationalValue(1);
+      for(int i = 0; i < instances1.numInstances(); i++){
+        Instance instance1 = instances1.instance(i);
+        Instance instance2 = instances2.instance(i);
+        for(int j = 0; j < instance1.numAttributes(); j++){
+          if(instance1.value(j) != instance2.value(j)){
+            return false;
+          }
+        }
+      }
+      return true;
+        }
+    return false;
+  }
+
+  /**
+   * Turn the references and citers list into a probability distribution
+   *
+   * @return the probability distribution
+   * @throws Exception if computation of distribution fails
+   */
+  protected double[] makeDistribution() throws Exception {
+    
+    double total = 0;
+    double[] distribution = new double[m_TrainBags.numClasses()];
+    boolean debug = false;
+
+    total = (double)m_TrainBags.numClasses() / Math.max(1, m_TrainBags.numInstances());
+
+    for(int i = 0; i < m_TrainBags.numClasses(); i++){
+      distribution[i] = 1.0 / Math.max(1, m_TrainBags.numInstances());
+      if(debug) System.out.println("distribution[" + i + "]: " + distribution[i]);
+    }
+
+    if(debug)System.out.println("total: " + total);
+
+    for(int i = 0; i < m_TrainBags.numClasses(); i++){
+      distribution[i] += m_References[i];
+      distribution[i] += m_Citers[i];
+    }
+
+    total = 0;
+    //total
+    for(int i = 0; i < m_TrainBags.numClasses(); i++){
+      total += distribution[i];
+      if(debug)System.out.println("distribution[" + i + "]: " + distribution[i]);
+    }
+
+    for(int i = 0; i < m_TrainBags.numClasses(); i++){
+      distribution[i] = distribution[i] / total;
+      if(debug)System.out.println("distribution[" + i + "]: " + distribution[i]);
+
+    }
+
+    return distribution;
+  }
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return an enumeration of all available options.
+   */
+  public Enumeration listOptions(){
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tNumber of Nearest References (default 1)",
+          "R", 0, "-R <number of references>"));
+    
+    result.addElement(new Option(
+          "\tNumber of Nearest Citers (default 1)",
+          "C", 0, "-C <number of citers>"));
+    
+    result.addElement(new Option(
+          "\tRank of the Hausdorff Distance (default 1)",
+          "H", 0, "-H <rank>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible). <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;number of references&gt;
+   *  Number of Nearest References (default 1)</pre>
+   * 
+   * <pre> -C &lt;number of citers&gt;
+   *  Number of Nearest Citers (default 1)</pre>
+   * 
+   * <pre> -H &lt;rank&gt;
+   *  Rank of the Hausdorff Distance (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception{
+    setDebug(Utils.getFlag('D', options));
+
+    String option = Utils.getOption('R', options);
+    if(option.length() != 0)
+      setNumReferences(Integer.parseInt(option));
+    else
+      setNumReferences(1);
+
+    option = Utils.getOption('C', options);
+    if(option.length() != 0)
+      setNumCiters(Integer.parseInt(option));
+    else
+      setNumCiters(1);
+
+    option = Utils.getOption('H', options);
+    if(option.length() != 0)
+      setHDRank(Integer.parseInt(option));
+    else
+      setHDRank(1);
+  }
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-R");
+    result.add("" + getNumReferences());
+    
+    result.add("-C");
+    result.add("" + getNumCiters());
+    
+    result.add("-H");
+    result.add("" + getHDRank());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns a string representation of the classifier
+   * 
+   * @return		the string representation
+   */
+  public String toString() {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // title
+    result.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+    result.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+
+    if (m_Citers == null) {
+      result.append("no model built yet!\n");
+    }
+    else {
+      // internal representation
+      result.append("Citers....: " + Utils.arrayToString(m_Citers) + "\n");
+
+      result.append("References: " + Utils.arrayToString(m_References) + "\n");
+
+      result.append("Min.......: ");
+      for (i = 0; i < m_Min.length; i++) {
+	if (i > 0)
+	  result.append(",");
+	result.append(Utils.doubleToString(m_Min[i], 3));
+      }
+      result.append("\n");
+
+      result.append("Max.......: ");
+      for (i = 0; i < m_Max.length; i++) {
+	if (i > 0)
+	  result.append(",");
+	result.append(Utils.doubleToString(m_Max[i], 3));
+      }
+      result.append("\n");
+
+      result.append("Diffs.....: ");
+      for (i = 0; i < m_Diffs.length; i++) {
+	if (i > 0)
+	  result.append(",");
+	result.append(Utils.doubleToString(m_Diffs[i], 3));
+      }
+      result.append("\n");
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new CitationKNN(), argv);
+  }
+
+  //########################################################################
+  //########################################################################
+  //########################################################################
+  //########################################################################
+  //########################################################################
+
+  /**
+   * A class for storing data about a neighboring instance
+   */
+  private class NeighborNode 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -3947320761906511289L;
+    
+    /** The neighbor bag */
+    private Instance mBag;
+
+    /** The distance from the current instance to this neighbor */
+    private double mDistance;
+
+    /** A link to the next neighbor instance */
+    private NeighborNode mNext;
+
+    /** the position in the bag */
+    private int mBagPosition;    
+    
+    /**
+     * Create a new neighbor node.
+     *
+     * @param distance the distance to the neighbor
+     * @param bag the bag instance
+     * @param position the position in the bag
+     * @param next the next neighbor node
+     */
+    public NeighborNode(double distance, Instance bag, int position, NeighborNode next){
+      mDistance = distance;
+      mBag = bag;
+      mNext = next;
+      mBagPosition = position;
+    }
+
+    /**
+     * Create a new neighbor node that doesn't link to any other nodes.
+     *
+     * @param distance the distance to the neighbor
+     * @param bag the neighbor instance
+     * @param position the position in the bag
+     */
+    public NeighborNode(double distance, Instance bag, int position) {
+      this(distance, bag, position, null);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  //##################################################
+  /**
+   * A class for a linked list to store the nearest k neighbours
+   * to an instance. We use a list so that we can take care of
+   * cases where multiple neighbours are the same distance away.
+   * i.e. the minimum length of the list is k.
+   */
+  private class NeighborList 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 3432555644456217394L;
+    
+    /** The first node in the list */
+    private NeighborNode mFirst;
+    /** The last node in the list */
+    private NeighborNode mLast;
+
+    /** The number of nodes to attempt to maintain in the list */
+    private int mLength = 1;
+
+    /**
+     * Creates the neighborlist with a desired length
+     *
+     * @param length the length of list to attempt to maintain
+     */
+    public NeighborList(int length) {
+      mLength = length;
+    }
+    /**
+     * Gets whether the list is empty.
+     *
+     * @return true if so
+     */
+    public boolean isEmpty() {
+      return (mFirst == null);
+    }
+    /**
+     * Gets the current length of the list.
+     *
+     * @return the current length of the list
+     */
+    public int currentLength() {
+
+      int i = 0;
+      NeighborNode current = mFirst;
+      while (current != null) {
+        i++;
+        current = current.mNext;
+      }
+      return i;
+    }
+
+    /**
+     * Inserts an instance neighbor into the list, maintaining the list
+     * sorted by distance.
+     *
+     * @param distance the distance to the instance
+     * @param bag the neighboring instance
+     * @param position the position in the bag
+     */
+    public void insertSorted(double distance, Instance bag, int position) {
+
+      if (isEmpty()) {
+        mFirst = mLast = new NeighborNode(distance, bag, position);
+      } else {
+        NeighborNode current = mFirst;
+        if (distance < mFirst.mDistance) {// Insert at head
+          mFirst = new NeighborNode(distance, bag, position, mFirst);
+        } else { // Insert further down the list
+          for( ;(current.mNext != null) && 
+              (current.mNext.mDistance < distance); 
+              current = current.mNext);
+          current.mNext = new NeighborNode(distance, bag, position, current.mNext);
+          if (current.equals(mLast)) {
+            mLast = current.mNext;
+          }
+        }
+
+        // Trip down the list until we've got k list elements (or more if the
+        // distance to the last elements is the same).
+        int valcount = 0;
+        for(current = mFirst; current.mNext != null; 
+            current = current.mNext) {
+          valcount++;
+          if ((valcount >= mLength) && (current.mDistance != 
+                current.mNext.mDistance)) {
+            mLast = current;
+            current.mNext = null;
+            break;
+                }
+            }
+      }
+    }
+
+    /**
+     * Prunes the list to contain the k nearest neighbors. If there are
+     * multiple neighbors at the k'th distance, all will be kept.
+     *
+     * @param k the number of neighbors to keep in the list.
+     */
+    public void pruneToK(int k) {
+      if (isEmpty())
+        return;
+      if (k < 1)
+        k = 1;
+
+      int currentK = 0;
+      double currentDist = mFirst.mDistance;
+      NeighborNode current = mFirst;
+      for(; current.mNext != null; current = current.mNext) {
+        currentK++;
+        currentDist = current.mDistance;
+        if ((currentK >= k) && (currentDist != current.mNext.mDistance)) {
+          mLast = current;
+          current.mNext = null;
+          break;
+        }
+      }
+    }
+
+    /**
+     * Prints out the contents of the neighborlist
+     */
+    public void printList() {
+
+      if (isEmpty()) {
+        System.out.println("Empty list");
+      } else {
+        NeighborNode current = mFirst;
+        while (current != null) {
+          System.out.print("Node: instance " + current.mBagPosition + "\n");
+          System.out.println(current.mBag);
+          System.out.println(", distance " + current.mDistance);
+          current = current.mNext;
+        }
+        System.out.println();
+      }
+    }
+    /**
+     * Prints out the contents of the neighborlist
+     */
+    public void printReducedList() {
+
+      if (isEmpty()) {
+        System.out.println("Empty list");
+      } else {
+        NeighborNode current = mFirst;
+        while (current != null) {
+          System.out.print("Node: bag " + current.mBagPosition + "  (" + current.mBag.relationalValue(1).numInstances() +"): ");
+          //for(int i = 0; i < current.mBag.getInstances().numInstances(); i++){
+          //System.out.print(" " + (current.mBag).getInstances().instance(i));
+          //}
+          System.out.print("   <" + current.mBag.classValue() + ">");
+          System.out.println("  (d: " + current.mDistance + ")");
+          current = current.mNext;
+        }
+        System.out.println();
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MDD.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MDD.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MDD.java	(revision 29)
@@ -0,0 +1,648 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MDD.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Modified Diverse Density algorithm, with collective assumption.<br/>
+ * <br/>
+ * More information about DD:<br/>
+ * <br/>
+ * Oded Maron (1998). Learning from ambiguity.<br/>
+ * <br/>
+ * O. Maron, T. Lozano-Perez (1998). A Framework for Multiple Instance Learning. Neural Information Processing Systems. 10.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Maron1998,
+ *    author = {Oded Maron},
+ *    school = {Massachusetts Institute of Technology},
+ *    title = {Learning from ambiguity},
+ *    year = {1998}
+ * }
+ * 
+ * &#64;article{Maron1998,
+ *    author = {O. Maron and T. Lozano-Perez},
+ *    journal = {Neural Information Processing Systems},
+ *    title = {A Framework for Multiple Instance Learning},
+ *    volume = {10},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default 1=standardize)</pre>
+ * 
+ <!-- options-end -->
+ *    
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class MDD 
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7273119490545290581L;
+
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  protected double[] m_Par;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** MI data */ 
+  protected double[][][] m_Data;
+
+  /** All attribute names */
+  protected Instances m_Attributes;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter =null;
+
+  /** Whether to normalize/standardize/neither, default:standardize */
+  protected int m_filterType = FILTER_STANDARDIZE;
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing = new ReplaceMissingValues();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Modified Diverse Density algorithm, with collective assumption.\n\n"
+      + "More information about DD:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "Oded Maron");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Learning from ambiguity");
+    result.setValue(Field.SCHOOL, "Massachusetts Institute of Technology");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "O. Maron and T. Lozano-Perez");
+    additional.setValue(Field.YEAR, "1998");
+    additional.setValue(Field.TITLE, "A Framework for Multiple Instance Learning");
+    additional.setValue(Field.JOURNAL, "Neural Information Processing Systems");
+    additional.setValue(Field.VOLUME, "10");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+          "\tTurn on debugging output.",
+          "D", 0, "-D"));
+    
+    result.addElement(new Option(
+          "\tWhether to 0=normalize/1=standardize/2=neither.\n"
+          + "\t(default 1=standardize)",
+          "N", 1, "-N <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String nString = Utils.getOption('N', options);
+    if (nString.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(nString), TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(FILTER_STANDARDIZE, TAGS_FILTER));
+    }     
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "The filter type for transforming the training data.";
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+
+  private class OptEng 
+    extends Optimization {
+    
+    /** 
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function 
+     */
+    protected double objectiveFunction(double[] x){
+      double nll = 0; // -LogLikelihood
+      for(int i=0; i<m_Classes.length; i++){ // ith bag
+        int nI = m_Data[i][0].length; // numInstances in ith bag
+        double bag = 0;  // NLL of each bag
+
+        for(int j=0; j<nI; j++){
+          double ins=0.0; 
+          for(int k=0; k<m_Data[i].length; k++) {
+            ins += (m_Data[i][k][j]-x[k*2])*(m_Data[i][k][j]-x[k*2])/
+              (x[k*2+1]*x[k*2+1]);
+          }
+          ins = Math.exp(-ins); 
+
+          if(m_Classes[i] == 1)
+            bag += ins/(double)nI;
+          else
+            bag += (1.0-ins)/(double)nI;   
+        }		
+        if(bag<=m_Zero) bag=m_Zero; 
+        nll -= Math.log(bag);
+      }		
+
+      return nll;
+    }
+
+    /** 
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector 
+     */
+    protected double[] evaluateGradient(double[] x){
+      double[] grad = new double[x.length];
+      for(int i=0; i<m_Classes.length; i++){ // ith bag
+        int nI = m_Data[i][0].length; // numInstances in ith bag 
+
+        double denom=0.0;
+        double[] numrt = new double[x.length];
+
+        for(int j=0; j<nI; j++){
+          double exp=0.0;
+          for(int k=0; k<m_Data[i].length; k++)
+            exp += (m_Data[i][k][j]-x[k*2])*(m_Data[i][k][j]-x[k*2])/
+              (x[k*2+1]*x[k*2+1]);			
+          exp = Math.exp(-exp);
+          if(m_Classes[i]==1)
+            denom += exp;
+          else
+            denom += (1.0-exp);		   
+
+          // Instance-wise update
+          for(int p=0; p<m_Data[i].length; p++){  // pth variable
+            numrt[2*p] += exp*2.0*(x[2*p]-m_Data[i][p][j])/
+              (x[2*p+1]*x[2*p+1]);
+            numrt[2*p+1] += 
+              exp*(x[2*p]-m_Data[i][p][j])*(x[2*p]-m_Data[i][p][j])/
+              (x[2*p+1]*x[2*p+1]*x[2*p+1]);
+          }			
+        }
+
+        if(denom <= m_Zero){
+          denom = m_Zero;
+        }
+
+        // Bag-wise update 
+        for(int q=0; q<m_Data[i].length; q++){
+          if(m_Classes[i]==1){
+            grad[2*q] += numrt[2*q]/denom;
+            grad[2*q+1] -= numrt[2*q+1]/denom;
+          }else{
+            grad[2*q] -= numrt[2*q]/denom;
+            grad[2*q+1] += numrt[2*q+1]/denom;
+          }
+        }
+      }
+
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    m_ClassIndex = train.classIndex();
+    m_NumClasses = train.numClasses();
+
+    int nR = train.attribute(1).relation().numAttributes();
+    int nC = train.numInstances();
+    int [] bagSize=new int [nC];
+    Instances datasets= new Instances(train.attribute(1).relation(),0);
+
+    m_Data  = new double [nC][nR][];              // Data values
+    m_Classes  = new int [nC];                    // Class values
+    m_Attributes = datasets.stringFreeStructure();		
+    double sY1=0, sY0=0;                          // Number of classes
+
+    if (m_Debug) {
+      System.out.println("Extracting data...");
+    }
+    FastVector maxSzIdx=new FastVector();
+    int maxSz=0;
+
+    for(int h=0; h<nC; h++){
+      Instance current = train.instance(h);
+      m_Classes[h] = (int)current.classValue();  // Class value starts from 0
+      Instances currInsts = current.relationalValue(1);
+      int nI = currInsts.numInstances();
+      bagSize[h]=nI;
+
+      for (int i=0; i<nI;i++){
+        Instance inst=currInsts.instance(i);
+        datasets.add(inst);
+      }
+
+      if(m_Classes[h]==1){
+        if(nI>maxSz){
+          maxSz=nI;
+          maxSzIdx=new FastVector(1);
+          maxSzIdx.addElement(new Integer(h));
+        }
+        else if(nI == maxSz)
+          maxSzIdx.addElement(new Integer(h));
+      }
+    }
+
+    /* filter the training data */
+    if (m_filterType == FILTER_STANDARDIZE)  
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null; 
+
+    if (m_Filter!=null) {
+      m_Filter.setInputFormat(datasets);
+      datasets = Filter.useFilter(datasets, m_Filter); 	
+    }
+
+    m_Missing.setInputFormat(datasets);
+    datasets = Filter.useFilter(datasets, m_Missing);
+
+    int instIndex=0;
+    int start=0;	
+    for(int h=0; h<nC; h++)  {	
+      for (int i = 0; i < datasets.numAttributes(); i++) {
+        // initialize m_data[][][]
+        m_Data[h][i] = new double[bagSize[h]];
+        instIndex=start;
+        for (int k=0; k<bagSize[h]; k++){
+          m_Data[h][i][k]=datasets.instance(instIndex).value(i);
+          instIndex ++;
+        }
+      }
+      start=instIndex;
+
+      // Class count	
+      if (m_Classes[h] == 1)
+        sY1++;		
+      else
+        sY0++;
+    }
+
+    if (m_Debug) {
+      System.out.println("\nIteration History..." );
+    }
+
+    double[] x = new double[nR*2], tmp = new double[x.length];
+    double[][] b = new double[2][x.length]; 
+
+    OptEng opt;
+    double nll, bestnll = Double.MAX_VALUE;
+    for (int t=0; t<x.length; t++){
+      b[0][t] = Double.NaN;
+      b[1][t] = Double.NaN; 
+    }
+
+    // Largest positive exemplar
+    for(int s=0; s<maxSzIdx.size(); s++){
+      int exIdx = ((Integer)maxSzIdx.elementAt(s)).intValue();
+      for(int p=0; p<m_Data[exIdx][0].length; p++){
+        for (int q=0; q < nR;q++){
+          x[2*q] = m_Data[exIdx][q][p];  // pick one instance
+          x[2*q+1] = 1.0;
+        }		
+
+        opt = new OptEng();	
+        tmp = opt.findArgmin(x, b);
+        while(tmp==null){
+          tmp = opt.getVarbValues();
+          if (m_Debug)
+            System.out.println("200 iterations finished, not enough!");
+          tmp = opt.findArgmin(tmp, b);
+        }
+        nll = opt.getMinFunction();
+
+        if(nll < bestnll){
+          bestnll = nll;
+          m_Par = tmp;
+          if (m_Debug)
+            System.out.println("!!!!!!!!!!!!!!!!Smaller NLL found: "+nll);
+        }
+        if (m_Debug)
+          System.out.println(exIdx+":  -------------<Converged>--------------");
+      }
+    }
+    }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp) 
+    throws Exception {
+
+    // Extract the data
+    Instances ins = exmp.relationalValue(1);
+    if(m_Filter!=null)
+      ins = Filter.useFilter(ins, m_Filter);
+
+    ins = Filter.useFilter(ins, m_Missing);
+
+    int nI = ins.numInstances(), nA = ins.numAttributes();
+    double[][] dat = new double [nI][nA];
+    for(int j=0; j<nI; j++){
+      for(int k=0; k<nA; k++){ 
+        dat[j][k] = ins.instance(j).value(k);
+      }
+    }
+
+    // Compute the probability of the bag
+    double [] distribution = new double[2];
+    distribution[1]=0.0;  // Prob. for class 1
+
+    for(int i=0; i<nI; i++){
+      double exp = 0.0;
+      for(int r=0; r<nA; r++)
+        exp += (m_Par[r*2]-dat[i][r])*(m_Par[r*2]-dat[i][r])/
+          ((m_Par[r*2+1])*(m_Par[r*2+1]));
+      exp = Math.exp(-exp);
+
+      // Prob. updated for one instance
+      distribution[1] += exp/(double)nI;
+      distribution[0] += (1.0-exp)/(double)nI;
+    }
+
+    return distribution;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+
+    String result = "Modified Logistic Regression";
+    if (m_Par == null) {
+      return result + ": No model built yet.";
+    }
+
+    result += "\nCoefficients...\n"
+      + "Variable      Coeff.\n";
+    for (int j = 0, idx=0; j < m_Par.length/2; j++, idx++) {
+
+      result += m_Attributes.attribute(idx).name();
+      result += " "+Utils.doubleToString(m_Par[j*2], 12, 4); 
+      result += " "+Utils.doubleToString(m_Par[j*2+1], 12, 4)+"\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MDD(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MIBoost.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MIBoost.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MIBoost.java	(revision 29)
@@ -0,0 +1,706 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIBoost.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Discretize;
+import weka.filters.unsupervised.attribute.MultiInstanceToPropositional;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * MI AdaBoost method, considers the geometric mean of posterior of instances inside a bag (arithmatic mean of log-posterior) and the expectation for a bag is taken inside the loss function.<br/>
+ * <br/>
+ * For more information about Adaboost, see:<br/>
+ * <br/>
+ * Yoav Freund, Robert E. Schapire: Experiments with a new boosting algorithm. In: Thirteenth International Conference on Machine Learning, San Francisco, 148-156, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Freund1996,
+ *    address = {San Francisco},
+ *    author = {Yoav Freund and Robert E. Schapire},
+ *    booktitle = {Thirteenth International Conference on Machine Learning},
+ *    pages = {148-156},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Experiments with a new boosting algorithm},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -B &lt;num&gt;
+ *  The number of bins in discretization
+ *  (default 0, no discretization)</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  Maximum number of boost iterations.
+ *  (default 10)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  Full name of classifier to boost.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class MIBoost 
+  extends SingleClassifierEnhancer
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3808427225599279539L;
+  
+  /** the models for the iterations */
+  protected Classifier[] m_Models;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** attributes name for the new dataset used to build the model  */
+  protected Instances m_Attributes;
+
+  /** Number of iterations */   
+  private int m_NumIterations = 100;
+
+  /** Voting weights of models */ 
+  protected double[] m_Beta;
+
+  /** the maximum number of boost iterations */
+  protected int m_MaxIterations = 10;
+
+  /** the number of discretization bins */
+  protected int m_DiscretizeBin = 0;
+
+  /** filter used for discretization */
+  protected Discretize m_Filter = null;
+
+  /** filter used to convert the MI dataset into single-instance dataset */
+  protected MultiInstanceToPropositional m_ConvertToSI = new MultiInstanceToPropositional();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "MI AdaBoost method, considers the geometric mean of posterior "
+      + "of instances inside a bag (arithmatic mean of log-posterior) and "
+      + "the expectation for a bag is taken inside the loss function.\n\n"
+      + "For more information about Adaboost, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Yoav Freund and Robert E. Schapire");
+    result.setValue(Field.TITLE, "Experiments with a new boosting algorithm");
+    result.setValue(Field.BOOKTITLE, "Thirteenth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "148-156");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.ADDRESS, "San Francisco");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tTurn on debugging output.",
+          "D", 0, "-D"));
+
+    result.addElement(new Option(
+          "\tThe number of bins in discretization\n"
+          + "\t(default 0, no discretization)",
+          "B", 1, "-B <num>"));	
+
+    result.addElement(new Option(
+          "\tMaximum number of boost iterations.\n"
+          + "\t(default 10)",
+          "R", 1, "-R <num>"));	
+
+    result.addElement(new Option(
+          "\tFull name of classifier to boost.\n"
+          + "\teg: weka.classifiers.bayes.NaiveBayes",
+          "W", 1, "-W <class name>"));
+
+    Enumeration enu = ((OptionHandler)m_Classifier).listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -B &lt;num&gt;
+   *  The number of bins in discretization
+   *  (default 0, no discretization)</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  Maximum number of boost iterations.
+   *  (default 10)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  Full name of classifier to boost.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String bin = Utils.getOption('B', options);
+    if (bin.length() != 0) {
+      setDiscretizeBin(Integer.parseInt(bin));
+    } else {
+      setDiscretizeBin(0);
+    }
+
+    String boostIterations = Utils.getOption('R', options);
+    if (boostIterations.length() != 0) {
+      setMaxIterations(Integer.parseInt(boostIterations));
+    } else {
+      setMaxIterations(10);
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+
+    result.add("-R");
+    result.add("" + getMaxIterations());
+
+    result.add("-B");
+    result.add("" + getDiscretizeBin());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+    return "The maximum number of boost iterations.";
+  }
+
+  /**
+   * Set the maximum number of boost iterations
+   *
+   * @param maxIterations the maximum number of boost iterations
+   */
+  public void setMaxIterations(int maxIterations) {	
+    m_MaxIterations = maxIterations;
+  }
+
+  /**
+   * Get the maximum number of boost iterations
+   *
+   * @return the maximum number of boost iterations
+   */
+  public int getMaxIterations() {
+
+    return m_MaxIterations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String discretizeBinTipText() {
+    return "The number of bins in discretization.";
+  }
+
+  /**
+   * Set the number of bins in discretization
+   *
+   * @param bin the number of bins in discretization
+   */
+  public void setDiscretizeBin(int bin) {	
+    m_DiscretizeBin = bin;
+  }
+
+  /**
+   * Get the number of bins in discretization
+   *
+   * @return the number of bins in discretization
+   */
+  public int getDiscretizeBin() {	
+    return m_DiscretizeBin;
+  }
+
+  private class OptEng 
+    extends Optimization {
+    
+    private double[] weights, errs;
+
+    public void setWeights(double[] w){
+      weights = w;
+    }
+
+    public void setErrs(double[] e){
+      errs = e;
+    }
+
+    /** 
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function 
+     * @throws Exception if result is NaN
+     */
+    protected double objectiveFunction(double[] x) throws Exception{
+      double obj=0;
+      for(int i=0; i<weights.length; i++){
+        obj += weights[i]*Math.exp(x[0]*(2.0*errs[i]-1.0));
+        if(Double.isNaN(obj))
+          throw new Exception("Objective function value is NaN!");
+
+      }
+      return obj;
+    }
+
+    /** 
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector 
+     * @throws Exception if gradient is NaN
+     */
+    protected double[] evaluateGradient(double[] x)  throws Exception{
+      double[] grad = new double[1];
+      for(int i=0; i<weights.length; i++){
+        grad[0] += weights[i]*(2.0*errs[i]-1.0)*Math.exp(x[0]*(2.0*errs[i]-1.0));
+        if(Double.isNaN(grad[0]))
+          throw new Exception("Gradient is NaN!");
+
+      }
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    if (super.getCapabilities().handles(Capability.BINARY_CLASS))
+      result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param exps the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances exps) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(exps);
+
+    // remove instances with missing class
+    Instances train = new Instances(exps);
+    train.deleteWithMissingClass();
+
+    m_NumClasses = train.numClasses();
+    m_NumIterations = m_MaxIterations;
+
+    if (m_Classifier == null)
+      throw new Exception("A base classifier has not been specified!");
+    if(!(m_Classifier instanceof WeightedInstancesHandler))
+      throw new Exception("Base classifier cannot handle weighted instances!");
+
+    m_Models = AbstractClassifier.makeCopies(m_Classifier, getMaxIterations());
+    if(m_Debug)
+      System.err.println("Base classifier: "+m_Classifier.getClass().getName());
+
+    m_Beta = new double[m_NumIterations];
+
+    /* modified by Lin Dong. (use MIToSingleInstance filter to convert the MI datasets) */
+
+    //Initialize the bags' weights
+    double N = (double)train.numInstances(), sumNi=0;
+    for(int i=0; i<N; i++)
+      sumNi += train.instance(i).relationalValue(1).numInstances();	
+    for(int i=0; i<N; i++){
+      train.instance(i).setWeight(sumNi/N);
+    }
+
+    //convert the training dataset into single-instance dataset
+    m_ConvertToSI.setInputFormat(train);
+    Instances data = Filter.useFilter( train, m_ConvertToSI);
+    data.deleteAttributeAt(0); //remove the bagIndex attribute;
+
+
+    // Assume the order of the instances are preserved in the Discretize filter
+    if(m_DiscretizeBin > 0){
+      m_Filter = new Discretize();
+      m_Filter.setInputFormat(new Instances(data, 0));
+      m_Filter.setBins(m_DiscretizeBin);
+      data = Filter.useFilter(data, m_Filter);
+    }
+
+    // Main algorithm
+    int dataIdx;
+iterations:
+    for(int m=0; m < m_MaxIterations; m++){
+      if(m_Debug)
+        System.err.println("\nIteration "+m); 
+
+
+      // Build a model
+      m_Models[m].buildClassifier(data);
+
+      // Prediction of each bag
+      double[] err=new double[(int)N], weights=new double[(int)N];
+      boolean perfect = true, tooWrong=true;
+      dataIdx = 0;
+      for(int n=0; n<N; n++){
+        Instance exn = train.instance(n);
+        // Prediction of each instance and the predicted class distribution
+        // of the bag		
+        double nn = (double)exn.relationalValue(1).numInstances();
+        for(int p=0; p<nn; p++){
+          Instance testIns = data.instance(dataIdx++);			
+          if((int)m_Models[m].classifyInstance(testIns) 
+              != (int)exn.classValue()) // Weighted instance-wise 0-1 errors
+            err[n] ++;		       		       
+        }
+        weights[n] = exn.weight();
+        err[n] /= nn;
+        if(err[n] > 0.5)
+          perfect = false;
+        if(err[n] < 0.5)
+          tooWrong = false;
+      }
+
+      if(perfect || tooWrong){ // No or 100% classification error, cannot find beta
+        if (m == 0)
+          m_Beta[m] = 1.0;
+        else		    
+          m_Beta[m] = 0;		
+        m_NumIterations = m+1;
+        if(m_Debug)  System.err.println("No errors");
+        break iterations;
+      }
+
+      double[] x = new double[1];
+      x[0] = 0;
+      double[][] b = new double[2][x.length];
+      b[0][0] = Double.NaN;
+      b[1][0] = Double.NaN;
+
+      OptEng opt = new OptEng();	
+      opt.setWeights(weights);
+      opt.setErrs(err);
+      //opt.setDebug(m_Debug);
+      if (m_Debug)
+        System.out.println("Start searching for c... ");
+      x = opt.findArgmin(x, b);
+      while(x==null){
+        x = opt.getVarbValues();
+        if (m_Debug)
+          System.out.println("200 iterations finished, not enough!");
+        x = opt.findArgmin(x, b);
+      }	
+      if (m_Debug)
+        System.out.println("Finished.");    
+      m_Beta[m] = x[0];
+
+      if(m_Debug)
+        System.err.println("c = "+m_Beta[m]);
+
+      // Stop if error too small or error too big and ignore this model
+      if (Double.isInfinite(m_Beta[m]) 
+          || Utils.smOrEq(m_Beta[m], 0)
+         ) {
+        if (m == 0)
+          m_Beta[m] = 1.0;
+        else		    
+          m_Beta[m] = 0;
+        m_NumIterations = m+1;
+        if(m_Debug)
+          System.err.println("Errors out of range!");
+        break iterations;
+         }
+
+      // Update weights of data and class label of wfData
+      dataIdx=0;
+      double totWeights=0;
+      for(int r=0; r<N; r++){		
+        Instance exr = train.instance(r);
+        exr.setWeight(weights[r]*Math.exp(m_Beta[m]*(2.0*err[r]-1.0)));
+        totWeights += exr.weight();
+      }
+
+      if(m_Debug)
+        System.err.println("Total weights = "+totWeights);
+
+      for(int r=0; r<N; r++){		
+        Instance exr = train.instance(r);
+        double num = (double)exr.relationalValue(1).numInstances();
+        exr.setWeight(sumNi*exr.weight()/totWeights);
+        //if(m_Debug)
+        //    System.err.print("\nExemplar "+r+"="+exr.weight()+": \t");
+        for(int s=0; s<num; s++){
+          Instance inss = data.instance(dataIdx);	
+          inss.setWeight(exr.weight()/num);		   
+          //    if(m_Debug)
+          //  System.err.print("instance "+s+"="+inss.weight()+
+          //			 "|ew*iw*sumNi="+data.instance(dataIdx).weight()+"\t");
+          if(Double.isNaN(inss.weight()))
+            throw new Exception("instance "+s+" in bag "+r+" has weight NaN!"); 
+          dataIdx++;
+        }
+        //if(m_Debug)
+        //    System.err.println();
+      }	       
+    }
+  }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the classification
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp) 
+    throws Exception { 
+
+    double[] rt = new double[m_NumClasses];
+
+    Instances insts = new Instances(exmp.dataset(), 0);
+    insts.add(exmp);
+
+    // convert the training dataset into single-instance dataset
+    insts = Filter.useFilter( insts, m_ConvertToSI);
+    insts.deleteAttributeAt(0); //remove the bagIndex attribute	
+
+    double n = insts.numInstances();
+
+    if(m_DiscretizeBin > 0)
+      insts = Filter.useFilter(insts, m_Filter);
+
+    for(int y=0; y<n; y++){
+      Instance ins = insts.instance(y);	
+      for(int x=0; x<m_NumIterations; x++){ 
+        rt[(int)m_Models[x].classifyInstance(ins)] += m_Beta[x]/n;
+      }
+    }
+
+    for(int i=0; i<rt.length; i++)
+      rt[i] = Math.exp(rt[i]);
+
+    Utils.normalize(rt);
+    return rt;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+
+    if (m_Models == null) {
+      return "No model built yet!";
+    }
+    StringBuffer text = new StringBuffer();
+    text.append("MIBoost: number of bins in discretization = "+m_DiscretizeBin+"\n");
+    if (m_NumIterations == 0) {
+      text.append("No model built yet.\n");
+    } else if (m_NumIterations == 1) {
+      text.append("No boosting possible, one classifier used: Weight = " 
+          + Utils.roundDouble(m_Beta[0], 2)+"\n");
+      text.append("Base classifiers:\n"+m_Models[0].toString());
+    } else {
+      text.append("Base classifiers and their weights: \n");
+      for (int i = 0; i < m_NumIterations ; i++) {
+        text.append("\n\n"+i+": Weight = " + Utils.roundDouble(m_Beta[i], 2)
+            +"\nBase classifier:\n"+m_Models[i].toString() );
+      }
+    }
+
+    text.append("\n\nNumber of performed Iterations: " 
+        + m_NumIterations + "\n");
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MIBoost(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MIDD.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MIDD.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MIDD.java	(revision 29)
@@ -0,0 +1,658 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIDD.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Re-implement the Diverse Density algorithm, changes the testing procedure.<br/>
+ * <br/>
+ * Oded Maron (1998). Learning from ambiguity.<br/>
+ * <br/>
+ * O. Maron, T. Lozano-Perez (1998). A Framework for Multiple Instance Learning. Neural Information Processing Systems. 10.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;phdthesis{Maron1998,
+ *    author = {Oded Maron},
+ *    school = {Massachusetts Institute of Technology},
+ *    title = {Learning from ambiguity},
+ *    year = {1998}
+ * }
+ * 
+ * &#64;article{Maron1998,
+ *    author = {O. Maron and T. Lozano-Perez},
+ *    journal = {Neural Information Processing Systems},
+ *    title = {A Framework for Multiple Instance Learning},
+ *    volume = {10},
+ *    year = {1998}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default 1=standardize)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class MIDD 
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4263507733600536168L;
+  
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  protected double[] m_Par;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** MI data */ 
+  protected double[][][] m_Data;
+
+  /** All attribute names */
+  protected Instances m_Attributes;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** Whether to normalize/standardize/neither, default:standardize */
+  protected int m_filterType = FILTER_STANDARDIZE;
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing = new ReplaceMissingValues();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Re-implement the Diverse Density algorithm, changes the testing "
+      + "procedure.\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.PHDTHESIS);
+    result.setValue(Field.AUTHOR, "Oded Maron");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Learning from ambiguity");
+    result.setValue(Field.SCHOOL, "Massachusetts Institute of Technology");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "O. Maron and T. Lozano-Perez");
+    additional.setValue(Field.YEAR, "1998");
+    additional.setValue(Field.TITLE, "A Framework for Multiple Instance Learning");
+    additional.setValue(Field.JOURNAL, "Neural Information Processing Systems");
+    additional.setValue(Field.VOLUME, "10");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tTurn on debugging output.",
+          "D", 0, "-D"));
+
+    result.addElement(new Option(
+          "\tWhether to 0=normalize/1=standardize/2=neither.\n"
+          + "\t(default 1=standardize)",
+          "N", 1, "-N <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *     
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Whether to 0=normalize/1=standardize/2=neither.
+   *  (default 1=standardize)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String nString = Utils.getOption('N', options);
+    if (nString.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(nString), TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(FILTER_STANDARDIZE, TAGS_FILTER));
+    }     
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "The filter type for transforming the training data.";
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  private class OptEng 
+    extends Optimization {
+
+    /** 
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function 
+     */
+    protected double objectiveFunction(double[] x){
+      double nll = 0; // -LogLikelihood
+      for(int i=0; i<m_Classes.length; i++){ // ith bag
+        int nI = m_Data[i][0].length; // numInstances in ith bag
+        double bag = 0.0;  // NLL of pos bag
+
+        for(int j=0; j<nI; j++){
+          double ins=0.0;
+          for(int k=0; k<m_Data[i].length; k++)
+            ins += (m_Data[i][k][j]-x[k*2])*(m_Data[i][k][j]-x[k*2])*
+              x[k*2+1]*x[k*2+1];
+          ins = Math.exp(-ins);
+          ins = 1.0-ins;
+
+          if(m_Classes[i] == 1)
+            bag += Math.log(ins);
+          else{
+            if(ins<=m_Zero) ins=m_Zero;
+            nll -= Math.log(ins);
+          }   
+        }		
+
+        if(m_Classes[i] == 1){
+          bag = 1.0 - Math.exp(bag);
+          if(bag<=m_Zero) bag=m_Zero;
+          nll -= Math.log(bag);
+        }
+      }		
+      return nll;
+    }
+
+    /** 
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector 
+     */
+    protected double[] evaluateGradient(double[] x){
+      double[] grad = new double[x.length];
+      for(int i=0; i<m_Classes.length; i++){ // ith bag
+        int nI = m_Data[i][0].length; // numInstances in ith bag 
+
+        double denom=0.0;	
+        double[] numrt = new double[x.length];
+
+        for(int j=0; j<nI; j++){
+          double exp=0.0;
+          for(int k=0; k<m_Data[i].length; k++)
+            exp += (m_Data[i][k][j]-x[k*2])*(m_Data[i][k][j]-x[k*2])
+              *x[k*2+1]*x[k*2+1];			
+          exp = Math.exp(-exp);
+          exp = 1.0-exp;
+          if(m_Classes[i]==1)
+            denom += Math.log(exp);		   		    
+
+          if(exp<=m_Zero) exp=m_Zero;
+          // Instance-wise update
+          for(int p=0; p<m_Data[i].length; p++){  // pth variable
+            numrt[2*p] += (1.0-exp)*2.0*(x[2*p]-m_Data[i][p][j])*x[p*2+1]*x[p*2+1]
+              /exp;
+            numrt[2*p+1] += 2.0*(1.0-exp)*(x[2*p]-m_Data[i][p][j])*(x[2*p]-m_Data[i][p][j])
+              *x[p*2+1]/exp;
+          }					    
+        }		    
+
+        // Bag-wise update 
+        denom = 1.0-Math.exp(denom);
+        if(denom <= m_Zero) denom = m_Zero;
+        for(int q=0; q<m_Data[i].length; q++){
+          if(m_Classes[i]==1){
+            grad[2*q] += numrt[2*q]*(1.0-denom)/denom;
+            grad[2*q+1] += numrt[2*q+1]*(1.0-denom)/denom;
+          }else{
+            grad[2*q] -= numrt[2*q];
+            grad[2*q+1] -= numrt[2*q+1];
+          }
+        }
+      } // one bag
+
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    m_ClassIndex = train.classIndex();
+    m_NumClasses = train.numClasses();
+
+    int nR = train.attribute(1).relation().numAttributes();
+    int nC = train.numInstances();
+    FastVector maxSzIdx=new FastVector();
+    int maxSz=0;
+    int [] bagSize=new int [nC];
+    Instances datasets= new Instances(train.attribute(1).relation(),0);
+
+    m_Data  = new double [nC][nR][];              // Data values
+    m_Classes  = new int [nC];                    // Class values
+    m_Attributes = datasets.stringFreeStructure();	
+    if (m_Debug) {
+      System.out.println("Extracting data...");
+    }
+
+    for(int h=0; h<nC; h++)  {//h_th bag
+      Instance current = train.instance(h);
+      m_Classes[h] = (int)current.classValue();  // Class value starts from 0
+      Instances currInsts = current.relationalValue(1);
+      for (int i=0; i<currInsts.numInstances();i++){
+        Instance inst=currInsts.instance(i);
+        datasets.add(inst);
+      }
+
+      int nI = currInsts.numInstances();
+      bagSize[h]=nI;
+      if(m_Classes[h]==1){  
+        if(nI>maxSz){
+          maxSz=nI;
+          maxSzIdx=new FastVector(1);
+          maxSzIdx.addElement(new Integer(h));
+        }
+        else if(nI == maxSz)
+          maxSzIdx.addElement(new Integer(h));
+      }
+
+    }
+
+    /* filter the training data */
+    if (m_filterType == FILTER_STANDARDIZE)  
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null; 
+
+    if (m_Filter!=null) {
+      m_Filter.setInputFormat(datasets);
+      datasets = Filter.useFilter(datasets, m_Filter); 	
+    }
+
+    m_Missing.setInputFormat(datasets);
+    datasets = Filter.useFilter(datasets, m_Missing);
+
+
+    int instIndex=0;
+    int start=0;	
+    for(int h=0; h<nC; h++)  {	
+      for (int i = 0; i < datasets.numAttributes(); i++) {
+        // initialize m_data[][][]
+        m_Data[h][i] = new double[bagSize[h]];
+        instIndex=start;
+        for (int k=0; k<bagSize[h]; k++){
+          m_Data[h][i][k]=datasets.instance(instIndex).value(i);
+          instIndex ++;
+        }
+      }
+      start=instIndex;
+    }
+
+
+    if (m_Debug) {
+      System.out.println("\nIteration History..." );
+    }
+
+    double[] x = new double[nR*2], tmp = new double[x.length];
+    double[][] b = new double[2][x.length]; 
+
+    OptEng opt;
+    double nll, bestnll = Double.MAX_VALUE;
+    for (int t=0; t<x.length; t++){
+      b[0][t] = Double.NaN; 
+      b[1][t] = Double.NaN;
+    }
+
+    // Largest Positive exemplar
+    for(int s=0; s<maxSzIdx.size(); s++){
+      int exIdx = ((Integer)maxSzIdx.elementAt(s)).intValue();
+      for(int p=0; p<m_Data[exIdx][0].length; p++){
+        for (int q=0; q < nR;q++){
+          x[2*q] = m_Data[exIdx][q][p];  // pick one instance
+          x[2*q+1] = 1.0;
+        }
+
+        opt = new OptEng();	
+        //opt.setDebug(m_Debug);
+        tmp = opt.findArgmin(x, b);
+        while(tmp==null){
+          tmp = opt.getVarbValues();
+          if (m_Debug)
+            System.out.println("200 iterations finished, not enough!");
+          tmp = opt.findArgmin(tmp, b);
+        }
+        nll = opt.getMinFunction();
+
+        if(nll < bestnll){
+          bestnll = nll;
+          m_Par = tmp;
+          tmp = new double[x.length]; // Save memory
+          if (m_Debug)
+            System.out.println("!!!!!!!!!!!!!!!!Smaller NLL found: "+nll);
+        }
+        if (m_Debug)
+          System.out.println(exIdx+":  -------------<Converged>--------------");
+      }	
+    }
+  }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp) 
+    throws Exception {
+
+    // Extract the data
+    Instances ins = exmp.relationalValue(1);
+    if(m_Filter!=null)
+      ins = Filter.useFilter(ins, m_Filter);
+
+    ins = Filter.useFilter(ins, m_Missing);
+
+    int nI = ins.numInstances(), nA = ins.numAttributes();
+    double[][] dat = new double [nI][nA];
+    for(int j=0; j<nI; j++){
+      for(int k=0; k<nA; k++){ 
+        dat[j][k] = ins.instance(j).value(k);
+      }
+    }
+
+    // Compute the probability of the bag
+    double [] distribution = new double[2];
+    distribution[0]=0.0;  // log-Prob. for class 0
+
+    for(int i=0; i<nI; i++){
+      double exp = 0.0;
+      for(int r=0; r<nA; r++)
+        exp += (m_Par[r*2]-dat[i][r])*(m_Par[r*2]-dat[i][r])*
+          m_Par[r*2+1]*m_Par[r*2+1];
+      exp = Math.exp(-exp);
+
+      // Prob. updated for one instance
+      distribution[0] += Math.log(1.0-exp);
+    }
+
+    distribution[0] = Math.exp(distribution[0]);
+    distribution[1] = 1.0-distribution[0];
+
+    return distribution;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+
+    //double CSq = m_LLn - m_LL;
+    //int df = m_NumPredictors;
+    String result = "Diverse Density";
+    if (m_Par == null) {
+      return result + ": No model built yet.";
+    }
+
+    result += "\nCoefficients...\n"
+      + "Variable       Point       Scale\n";
+    for (int j = 0, idx=0; j < m_Par.length/2; j++, idx++) {
+      result += m_Attributes.attribute(idx).name();
+      result += " "+Utils.doubleToString(m_Par[j*2], 12, 4); 
+      result += " "+Utils.doubleToString(m_Par[j*2+1], 12, 4)+"\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MIDD(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MIEMDD.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MIEMDD.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MIEMDD.java	(revision 29)
@@ -0,0 +1,764 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIEMDD.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.RandomizableClassifier;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * EMDD model builds heavily upon Dietterich's Diverse Density (DD) algorithm.<br/>
+ * It is a general framework for MI learning of converting the MI problem to a single-instance setting using EM. In this implementation, we use most-likely cause DD model and only use 3 random selected postive bags as initial starting points of EM.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Qi Zhang, Sally A. Goldman: EM-DD: An Improved Multiple-Instance Learning Technique. In: Advances in Neural Information Processing Systems 14, 1073-108, 2001.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Zhang2001,
+ *    author = {Qi Zhang and Sally A. Goldman},
+ *    booktitle = {Advances in Neural Information Processing Systems 14},
+ *    pages = {1073-108},
+ *    publisher = {MIT Press},
+ *    title = {EM-DD: An Improved Multiple-Instance Learning Technique},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default 1=standardize)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *     
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Lin Dong (ld21@cs.waikato.ac.nz)
+ * @version $Revision: 5481 $ 
+ */
+public class MIEMDD 
+  extends RandomizableClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 3899547154866223734L;
+  
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  protected double[] m_Par;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** MI data */
+  protected double[][][] m_Data;
+
+  /** All attribute names */
+  protected Instances m_Attributes;
+
+  /** MI data */	
+  protected double[][] m_emData;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** Whether to normalize/standardize/neither, default:standardize */
+  protected int m_filterType = FILTER_STANDARDIZE;
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag[] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing = new ReplaceMissingValues();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "EMDD model builds heavily upon Dietterich's Diverse Density (DD) "
+      + "algorithm.\nIt is a general framework for MI learning of converting "
+      + "the MI problem to a single-instance setting using EM. In this "
+      + "implementation, we use most-likely cause DD model and only use 3 "
+      + "random selected postive bags as initial starting points of EM.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Qi Zhang and Sally A. Goldman");
+    result.setValue(Field.TITLE, "EM-DD: An Improved Multiple-Instance Learning Technique");
+    result.setValue(Field.BOOKTITLE, "Advances in Neural Information Processing Systems 14");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "1073-108");
+    result.setValue(Field.PUBLISHER, "MIT Press");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+          "\tWhether to 0=normalize/1=standardize/2=neither.\n" 
+          + "\t(default 1=standardize)",
+          "N", 1, "-N <num>"));
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Whether to 0=normalize/1=standardize/2=neither.
+   *  (default 1=standardize)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String 	tmpStr;
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(FILTER_STANDARDIZE, TAGS_FILTER));
+    }     
+
+    super.setOptions(options);
+  }
+
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    String[]	options;
+    int		i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "The filter type for transforming the training data.";
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  private class OptEng 
+    extends Optimization {
+    /**
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function
+     */
+    protected double objectiveFunction(double[] x){
+      double nll = 0; // -LogLikelihood
+      for (int i=0; i<m_Classes.length; i++){ // ith bag
+        double ins=0.0;
+        for (int k=0; k<m_emData[i].length; k++)  //attribute index
+          ins += (m_emData[i][k]-x[k*2])*(m_emData[i][k]-x[k*2])*
+            x[k*2+1]*x[k*2+1];
+        ins = Math.exp(-ins); // Pr. of being positive
+
+        if (m_Classes[i]==1){
+          if (ins <= m_Zero) ins = m_Zero;
+          nll -= Math.log(ins); //bag level -LogLikelihood
+        }
+        else{
+          ins = 1.0 - ins;  //Pr. of being negative
+          if(ins<=m_Zero) ins=m_Zero;
+          nll -= Math.log(ins);
+        }
+      }
+      return nll;
+    }
+
+    /**
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector
+     */
+    protected double[] evaluateGradient(double[] x){
+      double[] grad = new double[x.length];
+      for (int i=0; i<m_Classes.length; i++){ // ith bag
+        double[] numrt = new double[x.length];
+        double exp=0.0;
+        for (int k=0; k<m_emData[i].length; k++) //attr index
+          exp += (m_emData[i][k]-x[k*2])*(m_emData[i][k]-x[k*2])
+            *x[k*2+1]*x[k*2+1];
+        exp = Math.exp(-exp);  //Pr. of being positive
+
+        //Instance-wise update
+        for (int p=0; p<m_emData[i].length; p++){  // pth variable
+          numrt[2*p] = 2.0*(x[2*p]-m_emData[i][p])*x[p*2+1]*x[p*2+1];
+          numrt[2*p+1] = 2.0*(x[2*p]-m_emData[i][p])*(x[2*p]-m_emData[i][p])
+            *x[p*2+1];
+        }
+
+        //Bag-wise update
+        for (int q=0; q<m_emData[i].length; q++){
+          if (m_Classes[i] == 1) {//derivation of (-LogLikeliHood) for positive bags
+            grad[2*q] += numrt[2*q];
+            grad[2*q+1] += numrt[2*q+1];
+          }
+          else{ //derivation of (-LogLikeliHood) for negative bags
+            grad[2*q] -= numrt[2*q]*exp/(1.0-exp);
+            grad[2*q+1] -= numrt[2*q+1]*exp/(1.0-exp);
+          }
+        }
+      } // one bag
+
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5481 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    m_ClassIndex = train.classIndex();
+    m_NumClasses = train.numClasses();
+
+    int nR = train.attribute(1).relation().numAttributes();
+    int nC = train.numInstances();
+    int[] bagSize = new int[nC];
+    Instances datasets = new Instances(train.attribute(1).relation(), 0);
+
+    m_Data = new double [nC][nR][];              // Data values
+    m_Classes = new int [nC];                    // Class values
+    m_Attributes = datasets.stringFreeStructure();
+    if (m_Debug) {
+      System.out.println("\n\nExtracting data...");
+    }
+
+    for (int h = 0; h < nC; h++)  {//h_th bag
+      Instance current = train.instance(h);
+      m_Classes[h] = (int)current.classValue();  // Class value starts from 0
+      Instances currInsts = current.relationalValue(1);
+      for (int i = 0; i < currInsts.numInstances(); i++){
+        Instance inst = currInsts.instance(i);
+        datasets.add(inst);
+      }
+
+      int nI = currInsts.numInstances();
+      bagSize[h] = nI;
+    }
+
+
+    /* filter the training data */
+    if (m_filterType == FILTER_STANDARDIZE)  
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null; 
+
+    if (m_Filter != null) {    
+      m_Filter.setInputFormat(datasets);
+      datasets = Filter.useFilter(datasets, m_Filter); 	
+    }
+
+    m_Missing.setInputFormat(datasets);
+    datasets = Filter.useFilter(datasets, m_Missing);
+
+    int instIndex = 0;
+    int start = 0;	
+    for (int h = 0; h < nC; h++)  {	
+      for (int i = 0; i < datasets.numAttributes(); i++) {
+        // initialize m_data[][][]
+        m_Data[h][i] = new double[bagSize[h]];
+        instIndex=start;
+        for (int k = 0; k < bagSize[h]; k++){
+          m_Data[h][i][k] = datasets.instance(instIndex).value(i);
+          instIndex++;
+        }
+      }
+      start=instIndex;
+    }
+
+    if (m_Debug) {
+      System.out.println("\n\nIteration History..." );
+    }
+
+    m_emData =new double[nC][nR];
+    m_Par= new double[2*nR];
+
+    double[] x = new double[nR*2];
+    double[] tmp = new double[x.length];
+    double[] pre_x = new double[x.length];
+    double[] best_hypothesis = new double[x.length];
+    double[][] b = new double[2][x.length];
+
+    OptEng opt;
+    double bestnll = Double.MAX_VALUE;
+    double min_error = Double.MAX_VALUE;
+    double nll, pre_nll;
+    int iterationCount;
+
+
+    for (int t = 0; t < x.length; t++) {
+      b[0][t] = Double.NaN;
+      b[1][t] = Double.NaN;
+    }
+
+    //random pick 3 positive bags 
+    Random r = new Random(getSeed());
+    FastVector index = new FastVector(); 
+    int n1, n2, n3;
+    do {
+      n1 = r.nextInt(nC-1);	
+    } while (m_Classes[n1] == 0);
+    index.addElement(new Integer(n1)); 
+
+    do {
+      n2 = r.nextInt(nC-1);
+    } while (n2 == n1|| m_Classes[n2] == 0);
+    index.addElement(new Integer(n2)); 
+
+    do {
+      n3 = r.nextInt(nC-1);
+    } while (n3 == n1 || n3 == n2 || m_Classes[n3] == 0);
+    index.addElement(new Integer(n3));
+
+    for (int s = 0; s < index.size(); s++){
+      int exIdx = ((Integer)index.elementAt(s)).intValue();
+      if (m_Debug)
+        System.out.println("\nH0 at "+exIdx);
+
+
+      for (int p = 0; p < m_Data[exIdx][0].length; p++) {
+        //initialize a hypothesis
+        for (int q = 0; q < nR; q++) {
+          x[2 * q] = m_Data[exIdx][q][p];
+          x[2 * q + 1] = 1.0;
+        } 
+
+        pre_nll = Double.MAX_VALUE;
+        nll = Double.MAX_VALUE/10.0;
+        iterationCount = 0;
+        //while (Math.abs(nll-pre_nll)>0.01*pre_nll && iterationCount<10) {  //stop condition
+        while (nll < pre_nll && iterationCount < 10) {
+          iterationCount++;
+          pre_nll = nll;
+
+          if (m_Debug) 
+            System.out.println("\niteration: "+iterationCount);
+
+          //E-step (find one instance from each bag with max likelihood )
+          for (int i = 0; i < m_Data.length; i++) { //for each bag
+
+            int insIndex = findInstance(i, x); 
+
+            for (int att = 0; att < m_Data[0].length; att++) //for each attribute
+              m_emData[i][att] = m_Data[i][att][insIndex];
+          }
+          if (m_Debug)
+            System.out.println("E-step for new H' finished");
+
+          //M-step
+          opt = new OptEng();
+          tmp = opt.findArgmin(x, b);
+          while (tmp == null) {
+            tmp = opt.getVarbValues();
+            if (m_Debug)
+              System.out.println("200 iterations finished, not enough!");
+            tmp = opt.findArgmin(tmp, b);
+          }
+          nll = opt.getMinFunction();
+
+          pre_x = x;
+          x = tmp; // update hypothesis 
+
+
+          //keep the track of the best target point which has the minimum nll
+          /* if (nll < bestnll) {
+             bestnll = nll;
+             m_Par = tmp;
+             if (m_Debug)
+             System.out.println("!!!!!!!!!!!!!!!!Smaller NLL found: " + nll);
+             }*/
+
+          //if (m_Debug)
+          //System.out.println(exIdx+" "+p+": "+nll+" "+pre_nll+" " +bestnll);
+
+        } //converged for one instance
+
+        //evaluate the hypothesis on the training data and
+        //keep the track of the hypothesis with minimum error on training data
+        double distribution[] = new double[2];
+        int error = 0;
+        if (nll > pre_nll)
+          m_Par = pre_x; 
+        else
+          m_Par = x;
+
+        for (int i = 0; i<train.numInstances(); i++) {
+          distribution = distributionForInstance (train.instance(i));
+          if (distribution[1] >= 0.5 && m_Classes[i] == 0)
+            error++;
+          else if (distribution[1]<0.5 && m_Classes[i] == 1)
+            error++;
+        }
+        if (error < min_error) {
+          best_hypothesis = m_Par;
+          min_error = error;
+          if (nll > pre_nll)
+            bestnll = pre_nll;
+          else
+            bestnll = nll;
+          if (m_Debug)
+            System.out.println("error= "+ error +"  nll= " + bestnll);
+        }
+      }
+      if (m_Debug) {
+        System.out.println(exIdx+ ":  -------------<Converged>--------------");
+        System.out.println("current minimum error= "+min_error+"  nll= "+bestnll);
+      }
+    } 
+    m_Par = best_hypothesis;
+  }
+
+
+  /**
+   * given x, find the instance in ith bag with the most likelihood
+   * probability, which is most likely to responsible for the label of the
+   * bag For a positive bag, find the instance with the maximal probability
+   * of being positive For a negative bag, find the instance with the minimal
+   * probability of being negative
+   *
+   * @param i the bag index
+   * @param x the current values of variables
+   * @return index of the instance in the bag
+   */
+  protected int findInstance(int i, double[] x){
+
+    double min=Double.MAX_VALUE;
+    int insIndex=0;
+    int nI = m_Data[i][0].length; // numInstances in ith bag
+
+    for (int j=0; j<nI; j++){
+      double ins=0.0;
+      for (int k=0; k<m_Data[i].length; k++)  // for each attribute
+        ins += (m_Data[i][k][j]-x[k*2])*(m_Data[i][k][j]-x[k*2])*
+          x[k*2+1]*x[k*2+1];
+
+      //the probability can be calculated as Math.exp(-ins)
+      //to find the maximum Math.exp(-ins) is equivalent to find the minimum of (ins)
+      if (ins<min)  {
+        min=ins;
+        insIndex=j;
+      }
+    }
+    return insIndex;
+  }
+
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp)
+    throws Exception {
+
+    // Extract the data
+    Instances ins = exmp.relationalValue(1);
+    if (m_Filter != null)
+      ins = Filter.useFilter(ins, m_Filter);
+
+    ins = Filter.useFilter(ins, m_Missing);
+
+    int nI = ins.numInstances(), nA = ins.numAttributes();
+    double[][] dat = new double [nI][nA];
+    for (int j = 0; j < nI; j++){
+      for (int k=0; k<nA; k++){
+        dat[j][k] = ins.instance(j).value(k);
+      }
+    }
+    //find the concept instance in the exemplar
+    double min = Double.MAX_VALUE;
+    double maxProb = -1.0;
+    for (int j = 0; j < nI; j++){
+      double exp = 0.0;
+      for (int k = 0; k<nA; k++)  // for each attribute
+        exp += (dat[j][k]-m_Par[k*2])*(dat[j][k]-m_Par[k*2])*m_Par[k*2+1]*m_Par[k*2+1];
+      //the probability can be calculated as Math.exp(-exp)
+      //to find the maximum Math.exp(-exp) is equivalent to find the minimum of (exp)
+      if (exp < min)  {
+        min     = exp;
+        maxProb = Math.exp(-exp); //maximum probability of being positive   
+      }
+    }	
+
+    // Compute the probability of the bag
+    double[] distribution = new double[2];
+    distribution[1] = maxProb; 
+    distribution[0] = 1.0 - distribution[1];  //mininum prob. of being negative
+
+    return distribution;
+  }
+
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+
+    String result = "MIEMDD";
+    if (m_Par == null) {
+      return result + ": No model built yet.";
+    }
+
+    result += "\nCoefficients...\n"
+      + "Variable       Point       Scale\n";
+    for (int j = 0, idx=0; j < m_Par.length/2; j++, idx++) {
+      result += m_Attributes.attribute(idx).name();
+      result += " "+Utils.doubleToString(m_Par[j*2], 12, 4);
+      result += " "+Utils.doubleToString(m_Par[j*2+1], 12, 4)+"\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5481 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MIEMDD(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MILR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MILR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MILR.java	(revision 29)
@@ -0,0 +1,840 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MILR.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Uses either standard or collective multi-instance assumption, but within linear regression. For the collective assumption, it offers arithmetic or geometric mean for the posteriors.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -R &lt;ridge&gt;
+ *  Set the ridge in the log-likelihood.</pre>
+ * 
+ * <pre> -A [0|1|2]
+ *  Defines the type of algorithm:
+ *   0. standard MI assumption
+ *   1. collective MI assumption, arithmetic mean for posteriors
+ *   2. collective MI assumption, geometric mean for posteriors</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class MILR
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 1996101190172373826L;
+  
+  protected double[] m_Par;
+
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** The ridge parameter. */
+  protected double m_Ridge = 1e-6;
+
+  /** Class labels for each bag */
+  protected int[] m_Classes;
+
+  /** MI data */ 
+  protected double[][][] m_Data;
+
+  /** All attribute names */
+  protected Instances m_Attributes;
+
+  protected double[] xMean = null, xSD = null;
+
+  /** the type of processing */
+  protected int m_AlgorithmType = ALGORITHMTYPE_DEFAULT;
+
+  /** standard MI assumption */
+  public static final int ALGORITHMTYPE_DEFAULT = 0;
+  /** collective MI assumption, arithmetic mean for posteriors */
+  public static final int ALGORITHMTYPE_ARITHMETIC = 1;
+  /** collective MI assumption, geometric mean for posteriors */
+  public static final int ALGORITHMTYPE_GEOMETRIC = 2;
+  /** the types of algorithms */
+  public static final Tag [] TAGS_ALGORITHMTYPE = {
+    new Tag(ALGORITHMTYPE_DEFAULT, "standard MI assumption"),
+    new Tag(ALGORITHMTYPE_ARITHMETIC, "collective MI assumption, arithmetic mean for posteriors"),
+    new Tag(ALGORITHMTYPE_GEOMETRIC, "collective MI assumption, geometric mean for posteriors"),
+  };
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Uses either standard or collective multi-instance assumption, but "
+      + "within linear regression. For the collective assumption, it offers "
+      + "arithmetic or geometric mean for the posteriors.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+          "\tTurn on debugging output.",
+          "D", 0, "-D"));
+    
+    result.addElement(new Option(
+        "\tSet the ridge in the log-likelihood.",
+        "R", 1, "-R <ridge>"));
+
+    result.addElement(new Option(
+        "\tDefines the type of algorithm:\n"
+        + "\t 0. standard MI assumption\n"
+        + "\t 1. collective MI assumption, arithmetic mean for posteriors\n"
+        + "\t 2. collective MI assumption, geometric mean for posteriors",
+        "A", 1, "-A [0|1|2]"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    setDebug(Utils.getFlag('D', options));
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0) 
+      setRidge(Double.parseDouble(tmpStr));
+    else 
+      setRidge(1.0e-6);
+
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.length() != 0) {
+      setAlgorithmType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_ALGORITHMTYPE));
+    } else {
+      setAlgorithmType(new SelectedTag(ALGORITHMTYPE_DEFAULT, TAGS_ALGORITHMTYPE));
+    }     
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-R");
+    result.add("" + getRidge());
+    
+    result.add("-A");
+    result.add("" + m_AlgorithmType);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ridgeTipText() {
+    return "The ridge in the log-likelihood.";
+  }
+
+  /**
+   * Sets the ridge in the log-likelihood.
+   *
+   * @param ridge the ridge
+   */
+  public void setRidge(double ridge) {
+    m_Ridge = ridge;
+  }
+
+  /**
+   * Gets the ridge in the log-likelihood.
+   *
+   * @return the ridge
+   */
+  public double getRidge() {
+    return m_Ridge;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String algorithmTypeTipText() {
+    return "The mean type for the posteriors.";
+  }
+
+  /**
+   * Gets the type of algorithm.
+   *
+   * @return the algorithm type
+   */
+  public SelectedTag getAlgorithmType() {
+    return new SelectedTag(m_AlgorithmType, TAGS_ALGORITHMTYPE);
+  }
+
+  /**
+   * Sets the algorithm type.
+   *
+   * @param newType the new algorithm type
+   */
+  public void setAlgorithmType(SelectedTag newType) {
+    if (newType.getTags() == TAGS_ALGORITHMTYPE) {
+      m_AlgorithmType = newType.getSelectedTag().getID();
+    }
+  }
+
+  private class OptEng 
+    extends Optimization {
+    
+    /** the type to use 
+     * @see MILR#TAGS_ALGORITHMTYPE */
+    private int m_Type;
+    
+    /**
+     * initializes the object
+     * 
+     * @param type      the type top use
+     * @see MILR#TAGS_ALGORITHMTYPE
+     */
+    public OptEng(int type) {
+      super();
+      
+      m_Type = type;
+    }
+    
+    /** 
+     * Evaluate objective function
+     * @param x the current values of variables
+     * @return the value of the objective function 
+     */
+    protected double objectiveFunction(double[] x){
+      double nll = 0; // -LogLikelihood
+      
+      switch (m_Type) {
+        case ALGORITHMTYPE_DEFAULT:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag
+            double bag = 0.0, // NLL of each bag 
+                   prod = 0.0;   // Log-prob. 
+
+            for(int j=0; j<nI; j++){
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+              exp = Math.exp(exp);
+
+              if(m_Classes[i]==1)
+                prod -= Math.log(1.0+exp);
+              else
+                bag += Math.log(1.0+exp);
+            }
+
+            if(m_Classes[i]==1)
+              bag = -Math.log(1.0-Math.exp(prod));
+
+            nll += bag;
+          }   
+          break;
+        
+        case ALGORITHMTYPE_ARITHMETIC:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag
+            double bag = 0;  // NLL of each bag
+
+            for(int j=0; j<nI; j++){
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+              exp = Math.exp(exp);
+
+              if(m_Classes[i] == 1)
+                bag += 1.0-1.0/(1.0+exp); // To avoid exp infinite
+              else
+                bag += 1.0/(1.0+exp);                  
+            }   
+            bag /= (double)nI;
+
+            nll -= Math.log(bag);
+          }   
+          break;
+          
+        case ALGORITHMTYPE_GEOMETRIC:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag
+            double bag = 0;   // Log-prob. 
+
+            for(int j=0; j<nI; j++){
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+
+              if(m_Classes[i]==1)
+                bag -= exp/(double)nI;
+              else
+                bag += exp/(double)nI;
+            }
+
+            nll += Math.log(1.0+Math.exp(bag));
+          }   
+          break;
+      }
+
+      // ridge: note that intercepts NOT included
+      for(int r=1; r<x.length; r++)
+        nll += m_Ridge*x[r]*x[r];
+
+      return nll;
+    }
+
+    /** 
+     * Evaluate Jacobian vector
+     * @param x the current values of variables
+     * @return the gradient vector 
+     */
+    protected double[] evaluateGradient(double[] x){
+      double[] grad = new double[x.length];
+      
+      switch (m_Type) {
+        case ALGORITHMTYPE_DEFAULT:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag
+
+            double denom = 0.0; // denominator, in log-scale       
+            double[] bag = new double[grad.length]; //gradient update with ith bag
+
+            for(int j=0; j<nI; j++){
+              // Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+              exp = Math.exp(exp)/(1.0+Math.exp(exp));
+
+              if(m_Classes[i]==1)
+                // Bug fix: it used to be denom += Math.log(1.0+exp);
+                // Fixed 21 Jan 2005 (Eibe)
+                denom -= Math.log(1.0-exp);
+
+              // Instance-wise update of dNLL/dBk
+              for(int p=0; p<x.length; p++){  // pth variable
+                double m = 1.0;
+                if(p>0) m=m_Data[i][p-1][j];
+                bag[p] += m*exp;
+              }     
+            }
+
+            denom = Math.exp(denom);
+
+            // Bag-wise update of dNLL/dBk
+            for(int q=0; q<grad.length; q++){
+              if(m_Classes[i]==1)
+                grad[q] -= bag[q]/(denom-1.0);
+              else
+                grad[q] += bag[q];
+            }   
+          }
+          break;
+        
+        case ALGORITHMTYPE_ARITHMETIC:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag 
+
+            double denom=0.0;
+            double[] numrt = new double[x.length];
+
+            for(int j=0; j<nI; j++){
+              // Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+              exp = Math.exp(exp);
+              if(m_Classes[i]==1)
+                denom += exp/(1.0+exp);
+              else
+                denom += 1.0/(1.0+exp);      
+
+              // Instance-wise update of dNLL/dBk
+              for(int p=0; p<x.length; p++){  // pth variable
+                double m = 1.0;
+                if(p>0) m=m_Data[i][p-1][j];
+                numrt[p] += m*exp/((1.0+exp)*(1.0+exp));   
+              }     
+            }
+
+            // Bag-wise update of dNLL/dBk
+            for(int q=0; q<grad.length; q++){
+              if(m_Classes[i]==1)
+                grad[q] -= numrt[q]/denom;
+              else
+                grad[q] += numrt[q]/denom;          
+            }
+          }
+          break;
+
+        case ALGORITHMTYPE_GEOMETRIC:
+          for(int i=0; i<m_Classes.length; i++){ // ith bag
+            int nI = m_Data[i][0].length; // numInstances in ith bag    
+            double bag = 0;
+            double[] sumX = new double[x.length];
+            for(int j=0; j<nI; j++){
+              // Compute exp(b0+b1*Xi1j+...)/[1+exp(b0+b1*Xi1j+...)]
+              double exp=0.0;
+              for(int k=m_Data[i].length-1; k>=0; k--)
+                exp += m_Data[i][k][j]*x[k+1];
+              exp += x[0];
+
+              if(m_Classes[i]==1){
+                bag -= exp/(double)nI;
+                for(int q=0; q<grad.length; q++){
+                  double m = 1.0;
+                  if(q>0) m=m_Data[i][q-1][j];
+                  sumX[q] -= m/(double)nI;
+                }
+              }
+              else{
+                bag += exp/(double)nI;
+                for(int q=0; q<grad.length; q++){
+                  double m = 1.0;
+                  if(q>0) m=m_Data[i][q-1][j];
+                  sumX[q] += m/(double)nI;
+                }     
+              }
+            }
+
+            for(int p=0; p<x.length; p++)
+              grad[p] += Math.exp(bag)*sumX[p]/(1.0+Math.exp(bag));
+          }
+          break;
+      }
+
+      // ridge: note that intercepts NOT included
+      for(int r=1; r<x.length; r++){
+        grad[r] += 2.0*m_Ridge*x[r];
+      }
+
+      return grad;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+
+    m_NumClasses = train.numClasses();
+
+    int nR = train.attribute(1).relation().numAttributes();
+    int nC = train.numInstances();
+
+    m_Data  = new double [nC][nR][];              // Data values
+    m_Classes  = new int [nC];                    // Class values
+    m_Attributes = train.attribute(1).relation();
+
+    xMean = new double [nR];             // Mean of mean
+    xSD   = new double [nR];             // Mode of stddev
+
+    double sY1=0, sY0=0, totIns=0;                          // Number of classes
+    int[] missingbags = new int[nR];
+
+    if (m_Debug) {
+      System.out.println("Extracting data...");
+    }
+
+    for(int h=0; h<m_Data.length; h++){
+      Instance current = train.instance(h);
+      m_Classes[h] = (int)current.classValue();  // Class value starts from 0
+      Instances currInsts = current.relationalValue(1);
+      int nI = currInsts.numInstances();
+      totIns += (double)nI;
+
+      for (int i = 0; i < nR; i++) {  		
+        // initialize m_data[][][]		
+        m_Data[h][i] = new double[nI];
+        double avg=0, std=0, num=0;
+        for (int k=0; k<nI; k++){
+          if(!currInsts.instance(k).isMissing(i)){
+            m_Data[h][i][k] = currInsts.instance(k).value(i);
+            avg += m_Data[h][i][k];
+            std += m_Data[h][i][k]*m_Data[h][i][k];
+            num++;
+          }
+          else
+            m_Data[h][i][k] = Double.NaN;
+        }
+        
+        if(num > 0){
+          xMean[i] += avg/num;
+          xSD[i] += std/num;
+        }
+        else
+          missingbags[i]++;
+      }	    
+
+      // Class count	
+      if (m_Classes[h] == 1)
+        sY1++;
+      else
+        sY0++;
+    }
+
+    for (int j = 0; j < nR; j++) {
+      xMean[j] = xMean[j]/(double)(nC-missingbags[j]);
+      xSD[j] = Math.sqrt(Math.abs(xSD[j]/((double)(nC-missingbags[j])-1.0)
+            -xMean[j]*xMean[j]*(double)(nC-missingbags[j])/
+            ((double)(nC-missingbags[j])-1.0)));
+    }
+
+    if (m_Debug) {	    
+      // Output stats about input data
+      System.out.println("Descriptives...");
+      System.out.println(sY0 + " bags have class 0 and " +
+          sY1 + " bags have class 1");
+      System.out.println("\n Variable     Avg       SD    ");
+      for (int j = 0; j < nR; j++) 
+        System.out.println(Utils.doubleToString(j,8,4) 
+            + Utils.doubleToString(xMean[j], 10, 4) 
+            + Utils.doubleToString(xSD[j], 10,4));
+    }
+
+    // Normalise input data and remove ignored attributes
+    for (int i = 0; i < nC; i++) {
+      for (int j = 0; j < nR; j++) {
+        for(int k=0; k < m_Data[i][j].length; k++){
+          if(xSD[j] != 0){
+            if(!Double.isNaN(m_Data[i][j][k]))
+              m_Data[i][j][k] = (m_Data[i][j][k] - xMean[j]) / xSD[j];
+            else
+              m_Data[i][j][k] = 0;
+          }
+        }
+      }
+    }
+
+    if (m_Debug) {
+      System.out.println("\nIteration History..." );
+    }
+
+    double x[] = new double[nR + 1];
+    x[0] =  Math.log((sY1+1.0) / (sY0+1.0));
+    double[][] b = new double[2][x.length];
+    b[0][0] = Double.NaN;
+    b[1][0] = Double.NaN;
+    for (int q=1; q < x.length;q++){
+      x[q] = 0.0;		
+      b[0][q] = Double.NaN;
+      b[1][q] = Double.NaN;
+    }
+
+    OptEng opt = new OptEng(m_AlgorithmType);	
+    opt.setDebug(m_Debug);
+    m_Par = opt.findArgmin(x, b);
+    while(m_Par==null){
+      m_Par = opt.getVarbValues();
+      if (m_Debug)
+        System.out.println("200 iterations finished, not enough!");
+      m_Par = opt.findArgmin(m_Par, b);
+    }
+    if (m_Debug)
+      System.out.println(" -------------<Converged>--------------");
+
+    // feature selection use
+    if (m_AlgorithmType == ALGORITHMTYPE_ARITHMETIC) {
+      double[] fs = new double[nR];
+      for(int k=1; k<nR+1; k++)
+        fs[k-1] = Math.abs(m_Par[k]);
+      int[] idx = Utils.sort(fs);
+      double max = fs[idx[idx.length-1]];
+      for(int k=idx.length-1; k>=0; k--)
+        System.out.println(m_Attributes.attribute(idx[k]).name()+"\t"+(fs[idx[k]]*100/max));
+    }
+
+    // Convert coefficients back to non-normalized attribute units
+    for(int j = 1; j < nR+1; j++) {
+      if (xSD[j-1] != 0) {
+        m_Par[j] /= xSD[j-1];
+        m_Par[0] -= m_Par[j] * xMean[j-1];
+      }
+    }
+  }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp) 
+    throws Exception {
+
+    // Extract the data
+    Instances ins = exmp.relationalValue(1);
+    int nI = ins.numInstances(), nA = ins.numAttributes();
+    double[][] dat = new double [nI][nA+1];
+    for(int j=0; j<nI; j++){
+      dat[j][0]=1.0;
+      int idx=1;
+      for(int k=0; k<nA; k++){ 
+        if(!ins.instance(j).isMissing(k))
+          dat[j][idx] = ins.instance(j).value(k);
+        else
+          dat[j][idx] = xMean[idx-1];
+        idx++;
+      }
+    }
+
+    // Compute the probability of the bag
+    double [] distribution = new double[2];
+    switch (m_AlgorithmType) {
+      case ALGORITHMTYPE_DEFAULT:
+        distribution[0]=0.0;  // Log-Prob. for class 0
+
+        for(int i=0; i<nI; i++){
+          double exp = 0.0; 
+          for(int r=0; r<m_Par.length; r++)
+            exp += m_Par[r]*dat[i][r];
+          exp = Math.exp(exp);
+
+          // Prob. updated for one instance
+          distribution[0] -= Math.log(1.0+exp);
+        }
+
+        // Prob. for class 0
+        distribution[0] = Math.exp(distribution[0]);
+        // Prob. for class 1
+        distribution[1] = 1.0 - distribution[0];
+        break;
+      
+      case ALGORITHMTYPE_ARITHMETIC:
+        distribution[0]=0.0;  // Prob. for class 0
+
+        for(int i=0; i<nI; i++){
+          double exp = 0.0;
+          for(int r=0; r<m_Par.length; r++)
+            exp += m_Par[r]*dat[i][r];
+          exp = Math.exp(exp);
+
+          // Prob. updated for one instance
+          distribution[0] += 1.0/(1.0+exp);
+        }
+
+        // Prob. for class 0
+        distribution[0] /= (double)nI;
+        // Prob. for class 1
+        distribution[1] = 1.0 - distribution[0];
+        break;
+
+      case ALGORITHMTYPE_GEOMETRIC:
+        for(int i=0; i<nI; i++){
+          double exp = 0.0;
+          for(int r=0; r<m_Par.length; r++)
+            exp += m_Par[r]*dat[i][r];
+          distribution[1] += exp/(double)nI; 
+        }
+
+        // Prob. for class 1
+        distribution[1] = 1.0/(1.0+Math.exp(-distribution[1]));
+        // Prob. for class 0
+        distribution[0] = 1-distribution[1];
+        break;
+    }
+
+    return distribution;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {
+
+    String result = "Modified Logistic Regression";
+    if (m_Par == null) {
+      return result + ": No model built yet.";
+    }
+
+    result += "\nMean type: " + getAlgorithmType().getSelectedTag().getReadable() + "\n";
+    result += "\nCoefficients...\n"
+      + "Variable      Coeff.\n";
+    for (int j = 1, idx=0; j < m_Par.length; j++, idx++) {
+      result += m_Attributes.attribute(idx).name();
+      result += " "+Utils.doubleToString(m_Par[j], 12, 4); 
+      result += "\n";
+    }
+
+    result += "Intercept:";
+    result += " "+Utils.doubleToString(m_Par[0], 10, 4); 
+    result += "\n";
+
+    result += "\nOdds Ratios...\n"
+      + "Variable         O.R.\n";
+    for (int j = 1, idx=0; j < m_Par.length; j++, idx++) {
+      result += " " + m_Attributes.attribute(idx).name(); 
+      double ORc = Math.exp(m_Par[j]);
+      result += " " + ((ORc > 1e10) ?  "" + ORc : Utils.doubleToString(ORc, 12, 4));
+    }
+    result += "\n";
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MILR(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MINND.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MINND.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MINND.java	(revision 29)
@@ -0,0 +1,1033 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MINND.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Multiple-Instance Nearest Neighbour with Distribution learner.<br/>
+ * <br/>
+ * It uses gradient descent to find the weight for each dimension of each exeamplar from the starting point of 1.0. In order to avoid overfitting, it uses mean-square function (i.e. the Euclidean distance) to search for the weights.<br/>
+ *  It then uses the weights to cleanse the training data. After that it searches for the weights again from the starting points of the weights searched before.<br/>
+ *  Finally it uses the most updated weights to cleanse the test exemplar and then finds the nearest neighbour of the test exemplar using partly-weighted Kullback distance. But the variances in the Kullback distance are the ones before cleansing.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Xin Xu (2001). A nearest distribution approach to multiple-instance learning. Hamilton, NZ.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Xu2001,
+ *    address = {Hamilton, NZ},
+ *    author = {Xin Xu},
+ *    note = {0657.591B},
+ *    school = {University of Waikato},
+ *    title = {A nearest distribution approach to multiple-instance learning},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -K &lt;number of neighbours&gt;
+ *  Set number of nearest neighbour for prediction
+ *  (default 1)</pre>
+ * 
+ * <pre> -S &lt;number of neighbours&gt;
+ *  Set number of nearest neighbour for cleansing the training data
+ *  (default 1)</pre>
+ * 
+ * <pre> -E &lt;number of neighbours&gt;
+ *  Set number of nearest neighbour for cleansing the testing data
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class MINND 
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -4512599203273864994L;
+  
+  /** The number of nearest neighbour for prediction */
+  protected int m_Neighbour = 1;
+
+  /** The mean for each attribute of each exemplar */
+  protected double[][] m_Mean = null;
+
+  /** The variance for each attribute of each exemplar */
+  protected double[][] m_Variance = null;
+
+  /** The dimension of each exemplar, i.e. (numAttributes-2) */
+  protected int m_Dimension = 0;
+
+  /** header info of the data */
+  protected Instances m_Attributes;;
+
+  /** The class label of each exemplar */
+  protected double[] m_Class = null;
+
+  /** The number of class labels in the data */
+  protected int m_NumClasses = 0;
+
+  /** The weight of each exemplar */
+  protected double[] m_Weights = null;
+
+  /** The very small number representing zero */
+  static private double m_ZERO = 1.0e-45;
+
+  /** The learning rate in the gradient descent */
+  protected double m_Rate = -1;
+
+  /** The minimum values for numeric attributes. */
+  private double [] m_MinArray=null;
+
+  /** The maximum values for numeric attributes. */
+  private double [] m_MaxArray=null;
+
+  /** The stopping criteria of gradient descent*/
+  private double m_STOP = 1.0e-45;
+
+  /** The weights that alter the dimnesion of each exemplar */
+  private double[][] m_Change=null;
+
+  /** The noise data of each exemplar */
+  private double[][] m_NoiseM = null, m_NoiseV = null, m_ValidM = null, 
+          m_ValidV = null;
+
+  /** The number of nearest neighbour instances in the selection of noises 
+    in the training data*/
+  private int m_Select = 1;
+
+  /** The number of nearest neighbour exemplars in the selection of noises 
+    in the test data */
+  private int m_Choose = 1;
+
+  /** The decay rate of learning rate */
+  private double m_Decay = 0.5;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Multiple-Instance Nearest Neighbour with Distribution learner.\n\n"
+      + "It uses gradient descent to find the weight for each dimension of "
+      + "each exeamplar from the starting point of 1.0. In order to avoid "
+      + "overfitting, it uses mean-square function (i.e. the Euclidean "
+      + "distance) to search for the weights.\n "
+      + "It then uses the weights to cleanse the training data. After that "
+      + "it searches for the weights again from the starting points of the "
+      + "weights searched before.\n "
+      + "Finally it uses the most updated weights to cleanse the test exemplar "
+      + "and then finds the nearest neighbour of the test exemplar using "
+      + "partly-weighted Kullback distance. But the variances in the Kullback "
+      + "distance are the ones before cleansing.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Xin Xu");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.TITLE, "A nearest distribution approach to multiple-instance learning");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, NZ");
+    result.setValue(Field.NOTE, "0657.591B");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * As normal Nearest Neighbour algorithm does, it's lazy and simply
+   * records the exemplar information (i.e. mean and variance for each
+   * dimension of each exemplar and their classes) when building the model.
+   * There is actually no need to store the exemplars themselves.
+   *
+   * @param exs the training exemplars
+   * @throws Exception if the model cannot be built properly
+   */    
+  public void buildClassifier(Instances exs)throws Exception{
+    // can classifier handle the data?
+    getCapabilities().testWithFail(exs);
+
+    // remove instances with missing class
+    Instances newData = new Instances(exs);
+    newData.deleteWithMissingClass();
+    
+    int numegs = newData.numInstances();
+    m_Dimension = newData.attribute(1).relation().numAttributes();
+    m_Attributes = newData.stringFreeStructure(); 
+    m_Change = new double[numegs][m_Dimension];
+    m_NumClasses = exs.numClasses();
+    m_Mean = new double[numegs][m_Dimension];
+    m_Variance = new double[numegs][m_Dimension];
+    m_Class = new double[numegs];
+    m_Weights = new double[numegs];
+    m_NoiseM = new double[numegs][m_Dimension];
+    m_NoiseV = new double[numegs][m_Dimension];
+    m_ValidM = new double[numegs][m_Dimension];
+    m_ValidV = new double[numegs][m_Dimension];
+    m_MinArray = new double[m_Dimension];
+    m_MaxArray = new double[m_Dimension];
+    for(int v=0; v < m_Dimension; v++)
+      m_MinArray[v] = m_MaxArray[v] = Double.NaN;
+
+    for(int w=0; w < numegs; w++){
+      updateMinMax(newData.instance(w));
+    }
+
+    // Scale exemplars
+    Instances data = m_Attributes;
+
+    for(int x=0; x < numegs; x++){
+      Instance example = newData.instance(x);
+      example = scale(example);
+      for (int i=0; i<m_Dimension; i++) {
+        m_Mean[x][i] = example.relationalValue(1).meanOrMode(i);	
+        m_Variance[x][i] = example.relationalValue(1).variance(i);
+        if(Utils.eq(m_Variance[x][i],0.0))
+          m_Variance[x][i] = m_ZERO;
+        m_Change[x][i] = 1.0;
+      }
+      /* for(int y=0; y < m_Variance[x].length; y++){
+         if(Utils.eq(m_Variance[x][y],0.0))
+         m_Variance[x][y] = m_ZERO;
+         m_Change[x][y] = 1.0;
+         }  */	
+
+      data.add(example);
+      m_Class[x] = example.classValue();
+      m_Weights[x] = example.weight();	
+    }
+
+    for(int z=0; z < numegs; z++)
+      findWeights(z, m_Mean);
+
+    // Pre-process and record "true estimated" parameters for distributions 
+    for(int x=0; x < numegs; x++){
+      Instance example = preprocess(data, x);
+      if (getDebug())
+        System.out.println("???Exemplar "+x+" has been pre-processed:"+
+            data.instance(x).relationalValue(1).sumOfWeights()+
+            "|"+example.relationalValue(1).sumOfWeights()+
+            "; class:"+m_Class[x]);
+      if(Utils.gr(example.relationalValue(1).sumOfWeights(), 0)){	
+        for (int i=0; i<m_Dimension; i++) {
+          m_ValidM[x][i] = example.relationalValue(1).meanOrMode(i);
+          m_ValidV[x][i] = example.relationalValue(1).variance(i);
+          if(Utils.eq(m_ValidV[x][i],0.0))
+            m_ValidV[x][i] = m_ZERO;
+        }
+        /*	for(int y=0; y < m_ValidV[x].length; y++){
+                if(Utils.eq(m_ValidV[x][y],0.0))
+                m_ValidV[x][y] = m_ZERO;
+                }*/	
+      }
+      else{
+        m_ValidM[x] = null;
+        m_ValidV[x] = null;
+      }
+    }
+
+    for(int z=0; z < numegs; z++)
+      if(m_ValidM[z] != null)
+        findWeights(z, m_ValidM);	
+
+  }
+
+  /**
+   * Pre-process the given exemplar according to the other exemplars 
+   * in the given exemplars.  It also updates noise data statistics.
+   *
+   * @param data the whole exemplars
+   * @param pos the position of given exemplar in data
+   * @return the processed exemplar
+   * @throws Exception if the returned exemplar is wrong 
+   */
+  public Instance preprocess(Instances data, int pos)
+    throws Exception{
+    Instance before = data.instance(pos);
+    if((int)before.classValue() == 0){
+      m_NoiseM[pos] = null;
+      m_NoiseV[pos] = null;
+      return before;
+    }
+
+    Instances after_relationInsts =before.attribute(1).relation().stringFreeStructure();
+    Instances noises_relationInsts =before.attribute(1).relation().stringFreeStructure();
+
+    Instances newData = m_Attributes;
+    Instance after = new DenseInstance(before.numAttributes());
+    Instance noises =  new DenseInstance(before.numAttributes());
+    after.setDataset(newData);
+    noises.setDataset(newData);
+
+    for(int g=0; g < before.relationalValue(1).numInstances(); g++){
+      Instance datum = before.relationalValue(1).instance(g);
+      double[] dists = new double[data.numInstances()];
+
+      for(int i=0; i < data.numInstances(); i++){
+        if(i != pos)
+          dists[i] = distance(datum, m_Mean[i], m_Variance[i], i);
+        else
+          dists[i] = Double.POSITIVE_INFINITY;
+      }		   
+
+      int[] pred = new int[m_NumClasses];
+      for(int n=0; n < pred.length; n++)
+        pred[n] = 0;
+
+      for(int o=0; o<m_Select; o++){
+        int index = Utils.minIndex(dists);
+        pred[(int)m_Class[index]]++;
+        dists[index] = Double.POSITIVE_INFINITY;
+      }
+
+      int clas = Utils.maxIndex(pred);
+      if((int)before.classValue() != clas)
+        noises_relationInsts.add(datum);
+      else
+        after_relationInsts.add(datum);		
+    }
+
+    int relationValue;
+    relationValue = noises.attribute(1).addRelation( noises_relationInsts);
+    noises.setValue(0,before.value(0));
+    noises.setValue(1, relationValue);
+    noises.setValue(2, before.classValue());
+
+    relationValue = after.attribute(1).addRelation( after_relationInsts);
+    after.setValue(0,before.value(0));
+    after.setValue(1, relationValue);
+    after.setValue(2, before.classValue());
+
+
+    if(Utils.gr(noises.relationalValue(1).sumOfWeights(), 0)){	
+      for (int i=0; i<m_Dimension; i++) {
+        m_NoiseM[pos][i] = noises.relationalValue(1).meanOrMode(i);
+        m_NoiseV[pos][i] = noises.relationalValue(1).variance(i);
+        if(Utils.eq(m_NoiseV[pos][i],0.0))
+          m_NoiseV[pos][i] = m_ZERO;
+      }
+      /* for(int y=0; y < m_NoiseV[pos].length; y++){
+         if(Utils.eq(m_NoiseV[pos][y],0.0))
+         m_NoiseV[pos][y] = m_ZERO;
+         } */	
+    }
+    else{
+      m_NoiseM[pos] = null;
+      m_NoiseV[pos] = null;
+    }
+
+    return after;
+  }
+
+  /**
+   * Calculates the distance between two instances
+   *
+   * @param first the first instance
+   * @param second the second instance
+   * @return the distance between the two given instances
+   */          
+  private double distance(Instance first, double[] mean, double[] var, int pos) {
+
+    double diff, distance = 0;
+
+    for(int i = 0; i < m_Dimension; i++) { 
+      // If attribute is numeric
+      if(first.attribute(i).isNumeric()){
+        if (!first.isMissing(i)){      
+          diff = first.value(i) - mean[i];
+          if(Utils.gr(var[i], m_ZERO))
+            distance += m_Change[pos][i] * var[i] * diff * diff;
+          else
+            distance += m_Change[pos][i] * diff * diff; 
+        }
+        else{
+          if(Utils.gr(var[i], m_ZERO))
+            distance += m_Change[pos][i] * var[i];
+          else
+            distance += m_Change[pos][i] * 1.0;
+        }
+      }
+
+    }
+
+    return distance;
+  }
+
+  /**
+   * Updates the minimum and maximum values for all the attributes
+   * based on a new exemplar.
+   *
+   * @param ex the new exemplar
+   */
+  private void updateMinMax(Instance ex) {	
+    Instances insts = ex.relationalValue(1);
+    for (int j = 0;j < m_Dimension; j++) {
+      if (insts.attribute(j).isNumeric()){
+        for(int k=0; k < insts.numInstances(); k++){
+          Instance ins = insts.instance(k);
+          if(!ins.isMissing(j)){
+            if (Double.isNaN(m_MinArray[j])) {
+              m_MinArray[j] = ins.value(j);
+              m_MaxArray[j] = ins.value(j);
+            } else {
+              if (ins.value(j) < m_MinArray[j])
+                m_MinArray[j] = ins.value(j);
+              else if (ins.value(j) > m_MaxArray[j])
+                m_MaxArray[j] = ins.value(j);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Scale the given exemplar so that the returned exemplar
+   * has the value of 0 to 1 for each dimension
+   * 
+   * @param before the given exemplar
+   * @return the resultant exemplar after scaling
+   * @throws Exception if given exampler cannot be scaled properly
+   */
+  private Instance scale(Instance before) throws Exception{
+
+    Instances afterInsts = before.relationalValue(1).stringFreeStructure();
+    Instance after = new DenseInstance(before.numAttributes());
+    after.setDataset(m_Attributes);
+
+    for(int i=0; i < before.relationalValue(1).numInstances(); i++){
+      Instance datum = before.relationalValue(1).instance(i);
+      Instance inst = (Instance)datum.copy();
+
+      for(int j=0; j < m_Dimension; j++){
+        if(before.relationalValue(1).attribute(j).isNumeric())
+          inst.setValue(j, (datum.value(j) - m_MinArray[j])/(m_MaxArray[j] - m_MinArray[j]));	
+      }
+      afterInsts.add(inst);
+    }
+
+    int attValue = after.attribute(1).addRelation(afterInsts);
+    after.setValue(0, before.value( 0));
+    after.setValue(1, attValue);	
+    after.setValue(2, before.value( 2));
+
+    return after;
+  }
+
+  /**
+   * Use gradient descent to distort the MU parameter for
+   * the exemplar.  The exemplar can be in the specified row in the 
+   * given matrix, which has numExemplar rows and numDimension columns;
+   * or not in the matrix.
+   * 
+   * @param row the given row index
+   * @param mean
+   */
+  public void findWeights(int row, double[][] mean){
+
+    double[] neww = new double[m_Dimension];
+    double[] oldw = new double[m_Dimension];
+    System.arraycopy(m_Change[row], 0, neww, 0, m_Dimension);
+    //for(int z=0; z<m_Dimension; z++)
+    //System.out.println("mu("+row+"): "+origin[z]+" | "+newmu[z]);
+    double newresult = target(neww, mean, row, m_Class);
+    double result = Double.POSITIVE_INFINITY;
+    double rate= 0.05;
+    if(m_Rate != -1)
+      rate = m_Rate;
+    //System.out.println("???Start searching ...");
+search: 
+    while(Utils.gr((result-newresult), m_STOP)){ // Full step
+      oldw = neww;
+      neww= new double[m_Dimension];
+
+      double[] delta = delta(oldw, mean, row, m_Class);
+
+      for(int i=0; i < m_Dimension; i++)
+        if(Utils.gr(m_Variance[row][i], 0.0))
+          neww[i] = oldw[i] + rate * delta[i];
+
+      result = newresult;
+      newresult = target(neww, mean, row, m_Class);
+
+      //System.out.println("???old: "+result+"|new: "+newresult);
+      while(Utils.gr(newresult, result)){ // Search back
+        //System.out.println("search back");
+        if(m_Rate == -1){
+          rate *= m_Decay; // Decay
+          for(int i=0; i < m_Dimension; i++)
+            if(Utils.gr(m_Variance[row][i], 0.0))
+              neww[i] = oldw[i] + rate * delta[i];
+          newresult = target(neww, mean, row, m_Class);
+        }
+        else{
+          for(int i=0; i < m_Dimension; i++)
+            neww[i] = oldw[i];
+          break search;
+        }
+      }
+    }
+    //System.out.println("???Stop");
+    m_Change[row] = neww;
+  }
+
+  /**
+   * Delta of x in one step of gradient descent:
+   * delta(Wij) = 1/2 * sum[k=1..N, k!=i](sqrt(P)*(Yi-Yk)/D - 1) * (MUij -
+   * MUkj)^2 where D = sqrt(sum[j=1..P]Kkj(MUij - MUkj)^2)
+   * N is number of exemplars and P is number of dimensions
+   *
+   * @param x the weights of the exemplar in question
+   * @param rowpos row index of x in X
+   * @param Y the observed class label
+   * @return the delta for all dimensions
+   */
+  private double[] delta(double[] x, double[][] X, int rowpos, double[] Y){
+    double y = Y[rowpos];
+
+    double[] delta=new double[m_Dimension];
+    for(int h=0; h < m_Dimension; h++)
+      delta[h] = 0.0;
+
+    for(int i=0; i < X.length; i++){
+      if((i != rowpos) && (X[i] != null)){
+        double var = (y==Y[i]) ? 0.0 : Math.sqrt((double)m_Dimension - 1);
+        double distance=0;
+        for(int j=0; j < m_Dimension; j++)
+          if(Utils.gr(m_Variance[rowpos][j], 0.0))
+            distance += x[j]*(X[rowpos][j]-X[i][j]) * (X[rowpos][j]-X[i][j]);
+        distance = Math.sqrt(distance);
+        if(distance != 0)
+          for(int k=0; k < m_Dimension; k++)
+            if(m_Variance[rowpos][k] > 0.0)
+              delta[k] += (var/distance - 1.0) * 0.5 *
+                (X[rowpos][k]-X[i][k]) *
+                (X[rowpos][k]-X[i][k]);
+      }
+    }
+    //System.out.println("???delta: "+delta);
+    return delta;
+  }
+
+  /**
+   * Compute the target function to minimize in gradient descent
+   * The formula is:<br/>
+   * 1/2*sum[i=1..p](f(X, Xi)-var(Y, Yi))^2 <p/>
+   * where p is the number of exemplars and Y is the class label.
+   * In the case of X=MU, f() is the Euclidean distance between two
+   * exemplars together with the related weights and var() is 
+   * sqrt(numDimension)*(Y-Yi) where Y-Yi is either 0 (when Y==Yi)
+   * or 1 (Y!=Yi) 
+   *
+   * @param x the weights of the exemplar in question
+   * @param rowpos row index of x in X
+   * @param Y the observed class label
+   * @return the result of the target function
+   */
+  public double target(double[] x, double[][] X, int rowpos, double[] Y){
+    double y = Y[rowpos], result=0;
+
+    for(int i=0; i < X.length; i++){
+      if((i != rowpos) && (X[i] != null)){
+        double var = (y==Y[i]) ? 0.0 : Math.sqrt((double)m_Dimension - 1);
+        double f=0;
+        for(int j=0; j < m_Dimension; j++)
+          if(Utils.gr(m_Variance[rowpos][j], 0.0)){
+            f += x[j]*(X[rowpos][j]-X[i][j]) * (X[rowpos][j]-X[i][j]);     
+            //System.out.println("i:"+i+" j: "+j+" row: "+rowpos);
+          }
+        f = Math.sqrt(f);
+        //System.out.println("???distance between "+rowpos+" and "+i+": "+f+"|y:"+y+" vs "+Y[i]);
+        if(Double.isInfinite(f))
+          System.exit(1);
+        result += 0.5 * (f - var) * (f - var);
+      }
+    }
+    //System.out.println("???target: "+result);
+    return result;
+  }    
+
+  /**
+   * Use Kullback Leibler distance to find the nearest neighbours of
+   * the given exemplar.
+   * It also uses K-Nearest Neighbour algorithm to classify the 
+   * test exemplar
+   *
+   * @param ex the given test exemplar
+   * @return the classification 
+   * @throws Exception if the exemplar could not be classified
+   * successfully
+   */
+  public double classifyInstance(Instance ex)throws Exception{
+
+    ex = scale(ex);
+
+    double[] var = new double [m_Dimension];
+    for (int i=0; i<m_Dimension; i++) 
+      var[i]= ex.relationalValue(1).variance(i);	
+
+    // The Kullback distance to all exemplars
+    double[] kullback = new double[m_Class.length];
+
+    // The first K nearest neighbours' predictions */
+  double[] predict = new double[m_NumClasses];
+  for(int h=0; h < predict.length; h++)
+    predict[h] = 0;
+  ex = cleanse(ex);
+
+  if(ex.relationalValue(1).numInstances() == 0){
+    if (getDebug())
+      System.out.println("???Whole exemplar falls into ambiguous area!");
+    return 1.0;                          // Bias towards positive class
+  }
+
+  double[] mean = new double[m_Dimension];	
+  for (int i=0; i<m_Dimension; i++)
+    mean [i]=ex.relationalValue(1).meanOrMode(i);
+
+  // Avoid zero sigma
+  for(int h=0; h < var.length; h++){
+    if(Utils.eq(var[h],0.0))
+      var[h] = m_ZERO;
+  }	
+
+  for(int i=0; i < m_Class.length; i++){
+    if(m_ValidM[i] != null)
+      kullback[i] = kullback(mean, m_ValidM[i], var, m_Variance[i], i);
+    else
+      kullback[i] = Double.POSITIVE_INFINITY;
+  }
+
+  for(int j=0; j < m_Neighbour; j++){
+    int pos = Utils.minIndex(kullback);
+    predict[(int)m_Class[pos]] += m_Weights[pos];	   
+    kullback[pos] = Double.POSITIVE_INFINITY;
+  }	
+
+  if (getDebug())
+    System.out.println("???There are still some unambiguous instances in this exemplar! Predicted as: "+Utils.maxIndex(predict));
+  return (double)Utils.maxIndex(predict);	
+  } 
+
+  /**
+   * Cleanse the given exemplar according to the valid and noise data
+   * statistics
+   *
+   * @param before the given exemplar
+   * @return the processed exemplar
+   * @throws Exception if the returned exemplar is wrong 
+   */
+  public Instance cleanse(Instance before) throws Exception{
+
+    Instances insts = before.relationalValue(1).stringFreeStructure();
+    Instance after = new DenseInstance(before.numAttributes());
+    after.setDataset(m_Attributes);
+
+    for(int g=0; g < before.relationalValue(1).numInstances(); g++){
+      Instance datum = before.relationalValue(1).instance(g);
+      double[] minNoiDists = new double[m_Choose];
+      double[] minValDists = new double[m_Choose];
+      int noiseCount = 0, validCount = 0;
+      double[] nDist = new double[m_Mean.length]; 
+      double[] vDist = new double[m_Mean.length]; 
+
+      for(int h=0; h < m_Mean.length; h++){
+        if(m_ValidM[h] == null)
+          vDist[h] = Double.POSITIVE_INFINITY;
+        else
+          vDist[h] = distance(datum, m_ValidM[h], m_ValidV[h], h);
+
+        if(m_NoiseM[h] == null)
+          nDist[h] = Double.POSITIVE_INFINITY;
+        else
+          nDist[h] = distance(datum, m_NoiseM[h], m_NoiseV[h], h);
+      }
+
+      for(int k=0; k < m_Choose; k++){
+        int pos = Utils.minIndex(vDist);
+        minValDists[k] = vDist[pos];
+        vDist[pos] = Double.POSITIVE_INFINITY;
+        pos = Utils.minIndex(nDist);
+        minNoiDists[k] = nDist[pos];
+        nDist[pos] = Double.POSITIVE_INFINITY;
+      }
+
+      int x = 0,y = 0;
+      while((x+y) < m_Choose){
+        if(minValDists[x] <= minNoiDists[y]){
+          validCount++;
+          x++;
+        }
+        else{
+          noiseCount++;
+          y++;
+        }
+      }
+      if(x >= y)
+        insts.add (datum);
+
+    }
+
+    after.setValue(0, before.value( 0));
+    after.setValue(1, after.attribute(1).addRelation(insts));
+    after.setValue(2, before.value( 2));
+
+    return after;
+  }    
+
+  /**
+   * This function calculates the Kullback Leibler distance between
+   * two normal distributions.  This distance is always positive. 
+   * Kullback Leibler distance = integral{f(X)ln(f(X)/g(X))}
+   * Note that X is a vector.  Since we assume dimensions are independent
+   * f(X)(g(X) the same) is actually the product of normal density
+   * functions of each dimensions.  Also note that it should be log2
+   * instead of (ln) in the formula, but we use (ln) simply for computational
+   * convenience.
+   *
+   * The result is as follows, suppose there are P dimensions, and f(X)
+   * is the first distribution and g(X) is the second:
+   * Kullback = sum[1..P](ln(SIGMA2/SIGMA1)) +
+   *            sum[1..P](SIGMA1^2 / (2*(SIGMA2^2))) +
+   *            sum[1..P]((MU1-MU2)^2 / (2*(SIGMA2^2))) -
+   *            P/2
+   *
+   * @param mu1 mu of the first normal distribution
+   * @param mu2 mu of the second normal distribution 
+   * @param var1 variance(SIGMA^2) of the first normal distribution
+   * @param var2 variance(SIGMA^2) of the second normal distribution
+   * @return the Kullback distance of two distributions
+   */
+  public double kullback(double[] mu1, double[] mu2,
+      double[] var1, double[] var2, int pos){
+    int p = mu1.length;
+    double result = 0;
+
+    for(int y=0; y < p; y++){
+      if((Utils.gr(var1[y], 0)) && (Utils.gr(var2[y], 0))){
+        result +=  
+          ((Math.log(Math.sqrt(var2[y]/var1[y]))) +
+           (var1[y] / (2.0*var2[y])) + 
+           (m_Change[pos][y] * (mu1[y]-mu2[y])*(mu1[y]-mu2[y]) / (2.0*var2[y])) -
+           0.5);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tSet number of nearest neighbour for prediction\n"
+          + "\t(default 1)",
+          "K", 1, "-K <number of neighbours>"));
+    
+    result.addElement(new Option(
+          "\tSet number of nearest neighbour for cleansing the training data\n"
+          + "\t(default 1)",
+          "S", 1, "-S <number of neighbours>"));
+    
+    result.addElement(new Option(
+          "\tSet number of nearest neighbour for cleansing the testing data\n"
+          + "\t(default 1)",
+          "E", 1, "-E <number of neighbours>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -K &lt;number of neighbours&gt;
+   *  Set number of nearest neighbour for prediction
+   *  (default 1)</pre>
+   * 
+   * <pre> -S &lt;number of neighbours&gt;
+   *  Set number of nearest neighbour for cleansing the training data
+   *  (default 1)</pre>
+   * 
+   * <pre> -E &lt;number of neighbours&gt;
+   *  Set number of nearest neighbour for cleansing the testing data
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception{
+
+    setDebug(Utils.getFlag('D', options));
+
+    String numNeighbourString = Utils.getOption('K', options);
+    if (numNeighbourString.length() != 0) 
+      setNumNeighbours(Integer.parseInt(numNeighbourString));
+    else 
+      setNumNeighbours(1);
+
+    numNeighbourString = Utils.getOption('S', options);
+    if (numNeighbourString.length() != 0) 
+      setNumTrainingNoises(Integer.parseInt(numNeighbourString));
+    else 
+      setNumTrainingNoises(1);
+
+    numNeighbourString = Utils.getOption('E', options);
+    if (numNeighbourString.length() != 0) 
+      setNumTestingNoises(Integer.parseInt(numNeighbourString));
+    else 
+      setNumTestingNoises(1);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-K");
+    result.add("" + getNumNeighbours());
+    
+    result.add("-S");
+    result.add("" + getNumTrainingNoises());
+    
+    result.add("-E");
+    result.add("" + getNumTestingNoises());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numNeighboursTipText() {
+    return "The number of nearest neighbours to the estimate the class prediction of test bags.";
+  }
+
+  /**
+   * Sets the number of nearest neighbours to estimate
+   * the class prediction of tests bags
+   * @param numNeighbour the number of citers
+   */
+  public void setNumNeighbours(int numNeighbour){
+    m_Neighbour = numNeighbour;
+  }
+
+  /**
+   * Returns the number of nearest neighbours to estimate
+   * the class prediction of tests bags
+   * @return the number of neighbours
+   */
+  public int getNumNeighbours(){
+    return m_Neighbour;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numTrainingNoisesTipText() {
+    return "The number of nearest neighbour instances in the selection of noises in the training data.";
+  }
+
+  /**
+   * Sets the number of nearest neighbour instances in the 
+   * selection of noises in the training data
+   * 
+   * @param numTraining the number of noises in training data 
+   */
+  public void setNumTrainingNoises (int numTraining){
+    m_Select = numTraining;
+  }
+
+  /**
+   * Returns the number of nearest neighbour instances in the 
+   * selection of noises in the training data
+   * 
+   * @return the number of noises in training data
+   */
+  public int getNumTrainingNoises(){
+    return m_Select;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numTestingNoisesTipText() {
+    return "The number of nearest neighbour instances in the selection of noises in the test data.";
+  }
+
+  /**
+   * Returns The number of nearest neighbour instances in the 
+   * selection of noises in the test data 
+   * @return the number of noises in test data
+   */
+  public int getNumTestingNoises(){
+    return m_Choose;
+  }
+
+  /**
+   * Sets The number of nearest neighbour exemplars in the 
+   * selection of noises in the test data 
+   * @param numTesting the number of noises in test data
+   */
+  public void setNumTestingNoises (int numTesting){
+    m_Choose = numTesting;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new MINND(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MIOptimalBall.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MIOptimalBall.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MIOptimalBall.java	(revision 29)
@@ -0,0 +1,555 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIOptimalBall.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.DoubleVector;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MultiInstanceToPropositional;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.PropositionalToMultiInstance;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This classifier tries to find a suitable ball in the multiple-instance space, with a certain data point in the instance space as a ball center. The possible ball center is a certain instance in a positive bag. The possible radiuses are those which can achieve the highest classification accuracy. The model selects the maximum radius as the radius of the optimal ball.<br/>
+ * <br/>
+ * For more information about this algorithm, see:<br/>
+ * <br/>
+ * Peter Auer, Ronald Ortner: A Boosting Approach to Multiple Instance Learning. In: 15th European Conference on Machine Learning, 63-74, 2004.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Auer2004,
+ *    author = {Peter Auer and Ronald Ortner},
+ *    booktitle = {15th European Conference on Machine Learning},
+ *    note = {LNAI 3201},
+ *    pages = {63-74},
+ *    publisher = {Springer},
+ *    title = {A Boosting Approach to Multiple Instance Learning},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Whether to 0=normalize/1=standardize/2=neither. 
+ *  (default 0=normalize)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Lin Dong (ld21@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class MIOptimalBall 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, 
+             MultiInstanceCapabilitiesHandler, TechnicalInformationHandler {  
+
+  /** for serialization */
+  static final long serialVersionUID = -6465750129576777254L;
+  
+  /** center of the optimal ball */
+  protected double[] m_Center;
+
+  /** radius of the optimal ball */
+  protected double m_Radius;
+
+  /** the distances from each instance in a positive bag to each bag*/
+  protected double [][][]m_Distance;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** filter used to convert the MI dataset into single-instance dataset */
+  protected MultiInstanceToPropositional m_ConvertToSI = new MultiInstanceToPropositional();
+
+  /** filter used to convert the single-instance dataset into MI dataset */
+  protected PropositionalToMultiInstance m_ConvertToMI = new PropositionalToMultiInstance();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+         "This classifier tries to find a suitable ball in the "
+       + "multiple-instance space, with a certain data point in the instance "
+       + "space as a ball center. The possible ball center is a certain "
+       + "instance in a positive bag. The possible radiuses are those which can "
+       + "achieve the highest classification accuracy. The model selects the "
+       + "maximum radius as the radius of the optimal ball.\n\n"
+       + "For more information about this algorithm, see:\n\n"
+       + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Peter Auer and Ronald Ortner");
+    result.setValue(Field.TITLE, "A Boosting Approach to Multiple Instance Learning");
+    result.setValue(Field.BOOKTITLE, "15th European Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.PAGES, "63-74");
+    result.setValue(Field.PUBLISHER, "Springer");
+    result.setValue(Field.NOTE, "LNAI 3201");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances train = new Instances(data);
+    train.deleteWithMissingClass();
+    
+    int numAttributes = train.attribute(1).relation().numAttributes();	
+    m_Center = new double[numAttributes];
+
+    if (getDebug())
+      System.out.println("Start training ..."); 
+
+    // convert the training dataset into single-instance dataset
+    m_ConvertToSI.setInputFormat(train);	
+    train = Filter.useFilter( train, m_ConvertToSI);
+
+    if (m_filterType == FILTER_STANDARDIZE) 
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null;
+
+    if (m_Filter!=null) {
+      // normalize/standardize the converted training dataset
+      m_Filter.setInputFormat(train);
+      train = Filter.useFilter(train, m_Filter);
+    }
+
+    // convert the single-instance dataset into multi-instance dataset
+    m_ConvertToMI.setInputFormat(train);
+    train = Filter.useFilter(train, m_ConvertToMI);
+
+    /*calculate all the distances (and store them in m_Distance[][][]), which
+      are from each instance in all positive bags to all bags */
+    calculateDistance(train);
+
+    /*find the suitable ball center (m_Center) and the corresponding radius (m_Radius)*/
+    findRadius(train); 
+
+    if (getDebug())
+      System.out.println("Finish building optimal ball model");
+  }		
+
+
+
+  /** 
+   * calculate the distances from each instance in a positive bag to each bag.
+   * All result distances are stored in m_Distance[i][j][k], where
+   * m_Distance[i][j][k] refers the distances from the jth instance in ith bag
+   * to the kth bag 
+   * 
+   * @param train the multi-instance dataset (with relational attribute)   
+   */
+  public void calculateDistance (Instances train) {
+    int numBags =train.numInstances();
+    int numInstances;
+    Instance tempCenter;
+
+    m_Distance = new double [numBags][][];
+    for (int i=0; i<numBags; i++) {
+      if (train.instance(i).classValue() == 1.0) { //positive bag
+        numInstances = train.instance(i).relationalValue(1).numInstances();
+        m_Distance[i]= new double[numInstances][];
+        for (int j=0; j<numInstances; j++) {
+          tempCenter = train.instance(i).relationalValue(1).instance(j);
+          m_Distance[i][j]=new double [numBags];  //store the distance from one center to all the bags
+          for (int k=0; k<numBags; k++){
+            if (i==k)
+              m_Distance[i][j][k]= 0;     
+            else 
+              m_Distance[i][j][k]= minBagDistance (tempCenter, train.instance(k));    	    
+          }
+        }
+      } 
+    }
+  } 
+
+  /**
+   * Calculate the distance from one data point to a bag
+   *
+   * @param center the data point in instance space
+   * @param bag the bag 
+   * @return the double value as the distance.
+   */
+  public double minBagDistance (Instance center, Instance bag){
+    double distance;
+    double minDistance = Double.MAX_VALUE;
+    Instances temp = bag.relationalValue(1);  
+    //calculate the distance from the data point to each instance in the bag and return the minimum distance 
+    for (int i=0; i<temp.numInstances(); i++){
+      distance =0;
+      for (int j=0; j<center.numAttributes(); j++)
+        distance += (center.value(j)-temp.instance(i).value(j))*(center.value(j)-temp.instance(i).value(j));
+
+      if (minDistance>distance)
+        minDistance = distance;
+    }
+    return Math.sqrt(minDistance); 
+  }
+
+  /**
+   * Find the maximum radius for the optimal ball.
+   *
+   * @param train the multi-instance data 
+   */ 
+  public void findRadius(Instances train) {
+    int numBags, numInstances;
+    double radius, bagDistance;
+    int highestCount=0;
+
+    numBags = train.numInstances();
+    //try each instance in all positive bag as a ball center (tempCenter),   	
+    for (int i=0; i<numBags; i++) {
+      if (train.instance(i).classValue()== 1.0) {//positive bag   
+        numInstances = train.instance(i).relationalValue(1).numInstances();
+        for (int j=0; j<numInstances; j++) {   	    		
+          Instance tempCenter = train.instance(i).relationalValue(1).instance(j);
+
+          //set the possible set of ball radius corresponding to each tempCenter,
+          double sortedDistance[] = sortArray(m_Distance[i][j]); //sort the distance value    	          
+          for (int k=1; k<sortedDistance.length; k++){
+            radius = sortedDistance[k]-(sortedDistance[k]-sortedDistance[k-1])/2.0 ;
+
+            //evaluate the performance on the training data according to
+            //the curren selected tempCenter and the set of radius   
+            int correctCount =0;
+            for (int n=0; n<numBags; n++){
+              bagDistance=m_Distance[i][j][n];  
+              if ((bagDistance <= radius && train.instance(n).classValue()==1.0) 
+                  ||(bagDistance > radius && train.instance(n).classValue ()==0.0))
+                correctCount += train.instance(n).weight();
+
+            }
+
+            //and keep the track of the ball center and the maximum radius which can achieve the highest accuracy. 
+            if (correctCount > highestCount || (correctCount==highestCount && radius > m_Radius)){
+              highestCount = correctCount;
+              m_Radius = radius;
+              for (int p=0; p<tempCenter.numAttributes(); p++)
+                m_Center[p]= tempCenter.value(p);
+            }      
+          }
+        }
+      }
+    } 
+  }
+
+  /**
+   * Sort the array.
+   *
+   * @param distance the array need to be sorted
+   * @return sorted array
+   */ 
+  public double [] sortArray(double [] distance) {
+    double [] sorted = new double [distance.length];
+
+    //make a copy of the array
+    double []disCopy = new double[distance.length];
+    for (int i=0;i<distance.length; i++)
+      disCopy[i]= distance[i];
+
+    DoubleVector sortVector = new DoubleVector(disCopy);
+    sortVector.sort();
+    sorted = sortVector.getArrayCopy(); 
+    return sorted;
+  }
+
+
+  /**
+   * Computes the distribution for a given multiple instance
+   *
+   * @param newBag the instance for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance newBag)
+    throws Exception {  
+
+    double [] distribution = new double[2];	
+    double distance; 
+    distribution[0]=0;	 
+    distribution[1]=0;
+
+    Instances insts = new Instances(newBag.dataset(),0);
+    insts.add(newBag);  
+
+    // Filter instances 
+    insts= Filter.useFilter( insts, m_ConvertToSI); 	
+    if (m_Filter!=null) 
+      insts = Filter.useFilter(insts, m_Filter);     
+
+    //calculate the distance from each single instance to the ball center
+    int numInsts = insts.numInstances(); 		
+    insts.deleteAttributeAt(0); //remove the bagIndex attribute, no use for the distance calculation
+
+    for (int i=0; i<numInsts; i++){
+      distance =0;	   
+      for (int j=0; j<insts.numAttributes()-1; j++)
+        distance += (insts.instance(i).value(j) - m_Center[j])*(insts.instance(i).value(j)-m_Center[j]);  
+
+      if (distance <=m_Radius*m_Radius){  // check whether this single instance is inside the ball
+        distribution[1]=1.0;  //predicted as a positive bag   	  
+        break;
+      }	
+    }
+
+    distribution[0]= 1-distribution[1]; 
+
+    return distribution; 
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tWhether to 0=normalize/1=standardize/2=neither. \n"
+          + "\t(default 0=normalize)",
+          "N", 1, "-N <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Whether to 0=normalize/1=standardize/2=neither. 
+   *  (default 0=normalize)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    String nString = Utils.getOption('N', options);
+    if (nString.length() != 0) {
+      setFilterType(new SelectedTag(Integer.parseInt(nString), TAGS_FILTER));
+    } else {
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "The filter type for transforming the training data.";
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MIOptimalBall(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MISMO.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MISMO.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MISMO.java	(revision 29)
@@ -0,0 +1,2131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MISMO.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.Logistic;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.SMOset;
+import weka.classifiers.mi.supportVector.MIPolyKernel;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SerializedObject;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MultiInstanceToPropositional;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.PropositionalToMultiInstance;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements John Platt's sequential minimal optimization algorithm for training a support vector classifier.<br/>
+ * <br/>
+ * This implementation globally replaces all missing values and transforms nominal attributes into binary ones. It also normalizes all attributes by default. (In that case the coefficients in the output are based on the normalized data, not the original data --- this is important for interpreting the classifier.)<br/>
+ * <br/>
+ * Multi-class problems are solved using pairwise classification.<br/>
+ * <br/>
+ * To obtain proper probability estimates, use the option that fits logistic regression models to the outputs of the support vector machine. In the multi-class case the predicted probabilities are coupled using Hastie and Tibshirani's pairwise coupling method.<br/>
+ * <br/>
+ * Note: for improved speed normalization should be turned off when operating on SparseInstances.<br/>
+ * <br/>
+ * For more information on the SMO algorithm, see<br/>
+ * <br/>
+ * J. Platt: Machines using Sequential Minimal Optimization. In B. Schoelkopf and C. Burges and A. Smola, editors, Advances in Kernel Methods - Support Vector Learning, 1998.<br/>
+ * <br/>
+ * S.S. Keerthi, S.K. Shevade, C. Bhattacharyya, K.R.K. Murthy (2001). Improvements to Platt's SMO Algorithm for SVM Classifier Design. Neural Computation. 13(3):637-649.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;incollection{Platt1998,
+ *    author = {J. Platt},
+ *    booktitle = {Advances in Kernel Methods - Support Vector Learning},
+ *    editor = {B. Schoelkopf and C. Burges and A. Smola},
+ *    publisher = {MIT Press},
+ *    title = {Machines using Sequential Minimal Optimization},
+ *    year = {1998}
+ * }
+ * 
+ * &#64;article{Keerthi2001,
+ *    author = {S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy},
+ *    journal = {Neural Computation},
+ *    number = {3},
+ *    pages = {637-649},
+ *    title = {Improvements to Platt's SMO Algorithm for SVM Classifier Design},
+ *    volume = {13},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  Turning them off assumes that data is purely numeric, doesn't
+ *  contain any missing values, and has a nominal class. Turning them
+ *  off also means that no header information will be stored if the
+ *  machine is linear. Finally, it also assumes that no instance has
+ *  a weight equal to 0.
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  The complexity constant C. (default 1)</pre>
+ * 
+ * <pre> -N
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default 0=normalize)</pre>
+ * 
+ * <pre> -I
+ *  Use MIminimax feature space. </pre>
+ * 
+ * <pre> -L &lt;double&gt;
+ *  The tolerance parameter. (default 1.0e-3)</pre>
+ * 
+ * <pre> -P &lt;double&gt;
+ *  The epsilon for round-off error. (default 1.0e-12)</pre>
+ * 
+ * <pre> -M
+ *  Fit logistic models to SVM outputs. </pre>
+ * 
+ * <pre> -V &lt;double&gt;
+ *  The number of folds for the internal cross-validation. 
+ *  (default -1, use training data)</pre>
+ * 
+ * <pre> -W &lt;double&gt;
+ *  The random number seed. (default 1)</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.mi.supportVector.MIPolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @author Lin Dong (ld21@cs.waikato.ac.nz) (code for adapting to MI data)
+ * @version $Revision: 5987 $ 
+ */
+public class MISMO 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -5834036950143719712L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Implements John Platt's sequential minimal optimization "
+      + "algorithm for training a support vector classifier.\n\n"
+      + "This implementation globally replaces all missing values and "
+      + "transforms nominal attributes into binary ones. It also "
+      + "normalizes all attributes by default. (In that case the coefficients "
+      + "in the output are based on the normalized data, not the "
+      + "original data --- this is important for interpreting the classifier.)\n\n"
+      + "Multi-class problems are solved using pairwise classification.\n\n"
+      + "To obtain proper probability estimates, use the option that fits "
+      + "logistic regression models to the outputs of the support vector "
+      + "machine. In the multi-class case the predicted probabilities "
+      + "are coupled using Hastie and Tibshirani's pairwise coupling "
+      + "method.\n\n"
+      + "Note: for improved speed normalization should be turned off when "
+      + "operating on SparseInstances.\n\n"
+      + "For more information on the SMO algorithm, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INCOLLECTION);
+    result.setValue(Field.AUTHOR, "J. Platt");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.TITLE, "Machines using Sequential Minimal Optimization");
+    result.setValue(Field.BOOKTITLE, "Advances in Kernel Methods - Support Vector Learning");
+    result.setValue(Field.EDITOR, "B. Schoelkopf and C. Burges and A. Smola");
+    result.setValue(Field.PUBLISHER, "MIT Press");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy");
+    additional.setValue(Field.YEAR, "2001");
+    additional.setValue(Field.TITLE, "Improvements to Platt's SMO Algorithm for SVM Classifier Design");
+    additional.setValue(Field.JOURNAL, "Neural Computation");
+    additional.setValue(Field.VOLUME, "13");
+    additional.setValue(Field.NUMBER, "3");
+    additional.setValue(Field.PAGES, "637-649");
+    
+    return result;
+  }
+
+  /**
+   * Class for building a binary support vector machine.
+   */
+  protected class BinaryMISMO 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -7107082483475433531L;
+    
+    /** The Lagrange multipliers. */
+    protected double[] m_alpha;
+
+    /** The thresholds. */
+    protected double m_b, m_bLow, m_bUp;
+
+    /** The indices for m_bLow and m_bUp */
+    protected int m_iLow, m_iUp;
+
+    /** The training data. */
+    protected Instances m_data;
+
+    /** Weight vector for linear machine. */
+    protected double[] m_weights;
+
+    /** Variables to hold weight vector in sparse form.
+      (To reduce storage requirements.) */
+    protected double[] m_sparseWeights;
+    protected int[] m_sparseIndices;
+
+    /** Kernel to use **/
+    protected Kernel m_kernel;
+
+    /** The transformed class values. */
+    protected double[] m_class;
+
+    /** The current set of errors for all non-bound examples. */
+    protected double[] m_errors;
+
+    /* The five different sets used by the algorithm. */
+    /** {i: 0 < m_alpha[i] < C} */
+    protected SMOset m_I0;
+    /** {i: m_class[i] = 1, m_alpha[i] = 0} */
+    protected SMOset m_I1; 
+    /** {i: m_class[i] = -1, m_alpha[i] = C} */
+    protected SMOset m_I2; 
+    /** {i: m_class[i] = 1, m_alpha[i] = C} */
+    protected SMOset m_I3; 
+    /** {i: m_class[i] = -1, m_alpha[i] = 0} */
+    protected SMOset m_I4; 
+
+    /** The set of support vectors {i: 0 < m_alpha[i]} */
+    protected SMOset m_supportVectors;
+
+    /** Stores logistic regression model for probability estimate */
+    protected Logistic m_logistic = null;
+
+    /** Stores the weight of the training instances */
+    protected double m_sumOfWeights = 0;
+
+    /**
+     * Fits logistic regression model to SVM outputs analogue
+     * to John Platt's method.  
+     *
+     * @param insts the set of training instances
+     * @param cl1 the first class' index
+     * @param cl2 the second class' index
+     * @param numFolds the number of folds for cross-validation
+     * @param random the random number generator for cross-validation
+     * @throws Exception if the sigmoid can't be fit successfully
+     */
+    protected void fitLogistic(Instances insts, int cl1, int cl2,
+        int numFolds, Random random) 
+      throws Exception {
+
+      // Create header of instances object
+      FastVector atts = new FastVector(2);
+      atts.addElement(new Attribute("pred"));
+      FastVector attVals = new FastVector(2);
+      attVals.addElement(insts.classAttribute().value(cl1));
+      attVals.addElement(insts.classAttribute().value(cl2));
+      atts.addElement(new Attribute("class", attVals));
+      Instances data = new Instances("data", atts, insts.numInstances());
+      data.setClassIndex(1);
+
+      // Collect data for fitting the logistic model
+      if (numFolds <= 0) {
+
+        // Use training data
+        for (int j = 0; j < insts.numInstances(); j++) {
+          Instance inst = insts.instance(j);
+          double[] vals = new double[2];
+          vals[0] = SVMOutput(-1, inst);
+          if (inst.classValue() == cl2) {
+            vals[1] = 1;
+          }
+          data.add(new DenseInstance(inst.weight(), vals));
+        }
+      } else {
+
+        // Check whether number of folds too large
+        if (numFolds > insts.numInstances()) {
+          numFolds = insts.numInstances();
+        }
+
+        // Make copy of instances because we will shuffle them around
+        insts = new Instances(insts);
+
+        // Perform three-fold cross-validation to collect
+        // unbiased predictions
+        insts.randomize(random);
+        insts.stratify(numFolds);
+        for (int i = 0; i < numFolds; i++) {
+          Instances train = insts.trainCV(numFolds, i, random);
+          SerializedObject so = new SerializedObject(this);
+          BinaryMISMO smo = (BinaryMISMO)so.getObject();
+          smo.buildClassifier(train, cl1, cl2, false, -1, -1);
+          Instances test = insts.testCV(numFolds, i);
+          for (int j = 0; j < test.numInstances(); j++) {
+            double[] vals = new double[2];
+            vals[0] = smo.SVMOutput(-1, test.instance(j));
+            if (test.instance(j).classValue() == cl2) {
+              vals[1] = 1;
+            }
+            data.add(new DenseInstance(test.instance(j).weight(), vals));
+          }
+        }
+      }
+
+      // Build logistic regression model
+      m_logistic = new Logistic();
+      m_logistic.buildClassifier(data);
+    }
+    
+    /**
+     * sets the kernel to use
+     * 
+     * @param value	the kernel to use
+     */
+    public void setKernel(Kernel value) {
+      m_kernel = value;
+    }
+    
+    /**
+     * Returns the kernel to use
+     * 
+     * @return 		the current kernel
+     */
+    public Kernel getKernel() {
+      return m_kernel;
+    }
+
+    /**
+     * Method for building the binary classifier.
+     *
+     * @param insts the set of training instances
+     * @param cl1 the first class' index
+     * @param cl2 the second class' index
+     * @param fitLogistic true if logistic model is to be fit
+     * @param numFolds number of folds for internal cross-validation
+     * @param randomSeed seed value for random number generator for cross-validation
+     * @throws Exception if the classifier can't be built successfully
+     */
+    protected void buildClassifier(Instances insts, int cl1, int cl2,
+        boolean fitLogistic, int numFolds,
+        int randomSeed) throws Exception {
+
+      // Initialize some variables
+      m_bUp = -1; m_bLow = 1; m_b = 0; 
+      m_alpha = null; m_data = null; m_weights = null; m_errors = null;
+      m_logistic = null; m_I0 = null; m_I1 = null; m_I2 = null;
+      m_I3 = null; m_I4 = null;	m_sparseWeights = null; m_sparseIndices = null;
+
+      // Store the sum of weights
+      m_sumOfWeights = insts.sumOfWeights();
+
+      // Set class values
+      m_class = new double[insts.numInstances()];
+      m_iUp = -1; m_iLow = -1;
+      for (int i = 0; i < m_class.length; i++) {
+        if ((int) insts.instance(i).classValue() == cl1) {
+          m_class[i] = -1; m_iLow = i;
+        } else if ((int) insts.instance(i).classValue() == cl2) {
+          m_class[i] = 1; m_iUp = i;
+        } else {
+          throw new Exception ("This should never happen!");
+        }
+      }
+
+      // Check whether one or both classes are missing
+      if ((m_iUp == -1) || (m_iLow == -1)) {
+        if (m_iUp != -1) {
+          m_b = -1;
+        } else if (m_iLow != -1) {
+          m_b = 1;
+        } else {
+          m_class = null;
+          return;
+        }
+        m_supportVectors = new SMOset(0);
+        m_alpha = new double[0];
+        m_class = new double[0];
+
+        // Fit sigmoid if requested
+        if (fitLogistic) {
+          fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
+        }
+        return;
+      }
+
+      // Set the reference to the data
+      m_data = insts;
+      m_weights = null;
+
+      // Initialize alpha array to zero
+      m_alpha = new double[m_data.numInstances()];
+
+      // Initialize sets
+      m_supportVectors = new SMOset(m_data.numInstances());
+      m_I0 = new SMOset(m_data.numInstances());
+      m_I1 = new SMOset(m_data.numInstances());
+      m_I2 = new SMOset(m_data.numInstances());
+      m_I3 = new SMOset(m_data.numInstances());
+      m_I4 = new SMOset(m_data.numInstances());
+
+      // Clean out some instance variables
+      m_sparseWeights = null;
+      m_sparseIndices = null;
+
+      // Initialize error cache
+      m_errors = new double[m_data.numInstances()];
+      m_errors[m_iLow] = 1; m_errors[m_iUp] = -1;
+
+      // Initialize kernel
+      m_kernel.buildKernel(m_data);
+
+      // Build up I1 and I4
+      for (int i = 0; i < m_class.length; i++ ) {
+        if (m_class[i] == 1) {
+          m_I1.insert(i);
+        } else {
+          m_I4.insert(i);
+        }
+      }
+
+      // Loop to find all the support vectors
+      int numChanged = 0;
+      boolean examineAll = true;
+      while ((numChanged > 0) || examineAll) {
+        numChanged = 0;
+        if (examineAll) {
+          for (int i = 0; i < m_alpha.length; i++) {
+            if (examineExample(i)) {
+              numChanged++;
+            }
+          }
+        } else {
+
+          // This code implements Modification 1 from Keerthi et al.'s paper
+          for (int i = 0; i < m_alpha.length; i++) {
+            if ((m_alpha[i] > 0) &&  
+                (m_alpha[i] < m_C * m_data.instance(i).weight())) {
+              if (examineExample(i)) {
+                numChanged++;
+              }
+
+              // Is optimality on unbound vectors obtained?
+              if (m_bUp > m_bLow - 2 * m_tol) {
+                numChanged = 0;
+                break;
+              }
+                }
+          }
+
+          //This is the code for Modification 2 from Keerthi et al.'s paper
+          /*boolean innerLoopSuccess = true; 
+            numChanged = 0;
+            while ((m_bUp < m_bLow - 2 * m_tol) && (innerLoopSuccess == true)) {
+            innerLoopSuccess = takeStep(m_iUp, m_iLow, m_errors[m_iLow]);
+            }*/
+        }
+
+        if (examineAll) {
+          examineAll = false;
+        } else if (numChanged == 0) {
+          examineAll = true;
+        }
+      }
+
+      // Set threshold
+      m_b = (m_bLow + m_bUp) / 2.0;
+
+      // Save memory
+      m_kernel.clean(); 
+
+      m_errors = null;
+      m_I0 = m_I1 = m_I2 = m_I3 = m_I4 = null;
+
+      // Fit sigmoid if requested
+      if (fitLogistic) {
+        fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
+      }
+
+    }
+
+    /**
+     * Computes SVM output for given instance.
+     *
+     * @param index the instance for which output is to be computed
+     * @param inst the instance 
+     * @return the output of the SVM for the given instance
+     * @throws Exception if something goes wrong
+     */
+    protected double SVMOutput(int index, Instance inst) throws Exception {
+
+      double result = 0;
+
+      for (int i = m_supportVectors.getNext(-1); i != -1; 
+          i = m_supportVectors.getNext(i)) {
+        result += m_class[i] * m_alpha[i] * m_kernel.eval(index, i, inst);
+      }
+      result -= m_b;
+
+      return result;
+    }
+
+    /**
+     * Prints out the classifier.
+     *
+     * @return a description of the classifier as a string
+     */
+    public String toString() {
+
+      StringBuffer text = new StringBuffer();
+      int printed = 0;
+
+      if ((m_alpha == null) && (m_sparseWeights == null)) {
+        return "BinaryMISMO: No model built yet.\n";
+      }
+      try {
+        text.append("BinaryMISMO\n\n");
+
+        for (int i = 0; i < m_alpha.length; i++) {
+          if (m_supportVectors.contains(i)) {
+            double val = m_alpha[i];
+            if (m_class[i] == 1) {
+              if (printed > 0) {
+                text.append(" + ");
+              }
+            } else {
+              text.append(" - ");
+            }
+            text.append(Utils.doubleToString(val, 12, 4) 
+                + " * <");
+            for (int j = 0; j < m_data.numAttributes(); j++) {
+              if (j != m_data.classIndex()) {
+                text.append(m_data.instance(i).toString(j));
+              }
+              if (j != m_data.numAttributes() - 1) {
+                text.append(" ");
+              }
+            }
+            text.append("> * X]\n");
+            printed++;
+          }
+        }
+
+        if (m_b > 0) {
+          text.append(" - " + Utils.doubleToString(m_b, 12, 4));
+        } else {
+          text.append(" + " + Utils.doubleToString(-m_b, 12, 4));
+        }
+
+        text.append("\n\nNumber of support vectors: " + 
+            m_supportVectors.numElements());
+        int numEval = 0;
+        int numCacheHits = -1;
+        if(m_kernel != null)
+        {
+          numEval = m_kernel.numEvals();
+          numCacheHits = m_kernel.numCacheHits();
+        }
+        text.append("\n\nNumber of kernel evaluations: " + numEval);
+        if (numCacheHits >= 0 && numEval > 0)
+        {
+          double hitRatio = 1 - numEval*1.0/(numCacheHits+numEval);
+          text.append(" (" + Utils.doubleToString(hitRatio*100, 7, 3).trim() + "% cached)");
+        }
+
+      } catch (Exception e) {
+        e.printStackTrace();
+
+        return "Can't print BinaryMISMO classifier.";
+      }
+
+      return text.toString();
+    }
+
+    /**
+     * Examines instance.
+     *
+     * @param i2 index of instance to examine
+     * @return true if examination was successfull
+     * @throws Exception if something goes wrong
+     */
+    protected boolean examineExample(int i2) throws Exception {
+
+      double y2, F2;
+      int i1 = -1;
+
+      y2 = m_class[i2];
+      if (m_I0.contains(i2)) {
+        F2 = m_errors[i2];
+      } else { 
+        F2 = SVMOutput(i2, m_data.instance(i2)) + m_b - y2;
+        m_errors[i2] = F2;
+
+        // Update thresholds
+        if ((m_I1.contains(i2) || m_I2.contains(i2)) && (F2 < m_bUp)) {
+          m_bUp = F2; m_iUp = i2;
+        } else if ((m_I3.contains(i2) || m_I4.contains(i2)) && (F2 > m_bLow)) {
+          m_bLow = F2; m_iLow = i2;
+        }
+      }
+
+      // Check optimality using current bLow and bUp and, if
+      // violated, find an index i1 to do joint optimization
+      // with i2...
+      boolean optimal = true;
+      if (m_I0.contains(i2) || m_I1.contains(i2) || m_I2.contains(i2)) {
+        if (m_bLow - F2 > 2 * m_tol) {
+          optimal = false; i1 = m_iLow;
+        }
+      }
+      if (m_I0.contains(i2) || m_I3.contains(i2) || m_I4.contains(i2)) {
+        if (F2 - m_bUp > 2 * m_tol) {
+          optimal = false; i1 = m_iUp;
+        }
+      }
+      if (optimal) {
+        return false;
+      }
+
+      // For i2 unbound choose the better i1...
+      if (m_I0.contains(i2)) {
+        if (m_bLow - F2 > F2 - m_bUp) {
+          i1 = m_iLow;
+        } else {
+          i1 = m_iUp;
+        }
+      }
+      if (i1 == -1) {
+        throw new Exception("This should never happen!");
+      }
+      return takeStep(i1, i2, F2);
+    }
+
+    /**
+     * Method solving for the Lagrange multipliers for
+     * two instances.
+     *
+     * @param i1 index of the first instance
+     * @param i2 index of the second instance
+     * @param F2
+     * @return true if multipliers could be found
+     * @throws Exception if something goes wrong
+     */
+    protected boolean takeStep(int i1, int i2, double F2) throws Exception {
+
+      double alph1, alph2, y1, y2, F1, s, L, H, k11, k12, k22, eta,
+             a1, a2, f1, f2, v1, v2, Lobj, Hobj;
+      double C1 = m_C * m_data.instance(i1).weight();
+      double C2 = m_C * m_data.instance(i2).weight();
+
+      // Don't do anything if the two instances are the same
+      if (i1 == i2) {
+        return false;
+      }
+
+      // Initialize variables
+      alph1 = m_alpha[i1]; alph2 = m_alpha[i2];
+      y1 = m_class[i1]; y2 = m_class[i2];
+      F1 = m_errors[i1];
+      s = y1 * y2;
+
+      // Find the constraints on a2
+      if (y1 != y2) {
+        L = Math.max(0, alph2 - alph1); 
+        H = Math.min(C2, C1 + alph2 - alph1);
+      } else {
+        L = Math.max(0, alph1 + alph2 - C1);
+        H = Math.min(C2, alph1 + alph2);
+      }
+      if (L >= H) {
+        return false;
+      }
+
+      // Compute second derivative of objective function
+      k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
+      k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
+      k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
+      eta = 2 * k12 - k11 - k22;
+
+      // Check if second derivative is negative
+      if (eta < 0) {
+
+        // Compute unconstrained maximum
+        a2 = alph2 - y2 * (F1 - F2) / eta;
+
+        // Compute constrained maximum
+        if (a2 < L) {
+          a2 = L;
+        } else if (a2 > H) {
+          a2 = H;
+        }
+      } else {
+
+        // Look at endpoints of diagonal
+        f1 = SVMOutput(i1, m_data.instance(i1));
+        f2 = SVMOutput(i2, m_data.instance(i2));
+        v1 = f1 + m_b - y1 * alph1 * k11 - y2 * alph2 * k12; 
+        v2 = f2 + m_b - y1 * alph1 * k12 - y2 * alph2 * k22; 
+        double gamma = alph1 + s * alph2;
+        Lobj = (gamma - s * L) + L - 0.5 * k11 * (gamma - s * L) * (gamma - s * L) - 
+          0.5 * k22 * L * L - s * k12 * (gamma - s * L) * L - 
+          y1 * (gamma - s * L) * v1 - y2 * L * v2;
+        Hobj = (gamma - s * H) + H - 0.5 * k11 * (gamma - s * H) * (gamma - s * H) - 
+          0.5 * k22 * H * H - s * k12 * (gamma - s * H) * H - 
+          y1 * (gamma - s * H) * v1 - y2 * H * v2;
+        if (Lobj > Hobj + m_eps) {
+          a2 = L;
+        } else if (Lobj < Hobj - m_eps) {
+          a2 = H;
+        } else {
+          a2 = alph2;
+        }
+      }
+      if (Math.abs(a2 - alph2) < m_eps * (a2 + alph2 + m_eps)) {
+        return false;
+      }
+
+      // To prevent precision problems
+      if (a2 > C2 - m_Del * C2) {
+        a2 = C2;
+      } else if (a2 <= m_Del * C2) {
+        a2 = 0;
+      }
+
+      // Recompute a1
+      a1 = alph1 + s * (alph2 - a2);
+
+      // To prevent precision problems
+      if (a1 > C1 - m_Del * C1) {
+        a1 = C1;
+      } else if (a1 <= m_Del * C1) {
+        a1 = 0;
+      }
+
+      // Update sets
+      if (a1 > 0) {
+        m_supportVectors.insert(i1);
+      } else {
+        m_supportVectors.delete(i1);
+      }
+      if ((a1 > 0) && (a1 < C1)) {
+        m_I0.insert(i1);
+      } else {
+        m_I0.delete(i1);
+      }
+      if ((y1 == 1) && (a1 == 0)) {
+        m_I1.insert(i1);
+      } else {
+        m_I1.delete(i1);
+      }
+      if ((y1 == -1) && (a1 == C1)) {
+        m_I2.insert(i1);
+      } else {
+        m_I2.delete(i1);
+      }
+      if ((y1 == 1) && (a1 == C1)) {
+        m_I3.insert(i1);
+      } else {
+        m_I3.delete(i1);
+      }
+      if ((y1 == -1) && (a1 == 0)) {
+        m_I4.insert(i1);
+      } else {
+        m_I4.delete(i1);
+      }
+      if (a2 > 0) {
+        m_supportVectors.insert(i2);
+      } else {
+        m_supportVectors.delete(i2);
+      }
+      if ((a2 > 0) && (a2 < C2)) {
+        m_I0.insert(i2);
+      } else {
+        m_I0.delete(i2);
+      }
+      if ((y2 == 1) && (a2 == 0)) {
+        m_I1.insert(i2);
+      } else {
+        m_I1.delete(i2);
+      }
+      if ((y2 == -1) && (a2 == C2)) {
+        m_I2.insert(i2);
+      } else {
+        m_I2.delete(i2);
+      }
+      if ((y2 == 1) && (a2 == C2)) {
+        m_I3.insert(i2);
+      } else {
+        m_I3.delete(i2);
+      }
+      if ((y2 == -1) && (a2 == 0)) {
+        m_I4.insert(i2);
+      } else {
+        m_I4.delete(i2);
+      }
+
+      // Update error cache using new Lagrange multipliers
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+        if ((j != i1) && (j != i2)) {
+          m_errors[j] += 
+            y1 * (a1 - alph1) * m_kernel.eval(i1, j, m_data.instance(i1)) + 
+            y2 * (a2 - alph2) * m_kernel.eval(i2, j, m_data.instance(i2));
+        }
+      }
+
+      // Update error cache for i1 and i2
+      m_errors[i1] += y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12;
+      m_errors[i2] += y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22;
+
+      // Update array with Lagrange multipliers
+      m_alpha[i1] = a1;
+      m_alpha[i2] = a2;
+
+      // Update thresholds
+      m_bLow = -Double.MAX_VALUE; m_bUp = Double.MAX_VALUE;
+      m_iLow = -1; m_iUp = -1;
+      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
+        if (m_errors[j] < m_bUp) {
+          m_bUp = m_errors[j]; m_iUp = j;
+        }
+        if (m_errors[j] > m_bLow) {
+          m_bLow = m_errors[j]; m_iLow = j;
+        }
+      }
+      if (!m_I0.contains(i1)) {
+        if (m_I3.contains(i1) || m_I4.contains(i1)) {
+          if (m_errors[i1] > m_bLow) {
+            m_bLow = m_errors[i1]; m_iLow = i1;
+          } 
+        } else {
+          if (m_errors[i1] < m_bUp) {
+            m_bUp = m_errors[i1]; m_iUp = i1;
+          }
+        }
+      }
+      if (!m_I0.contains(i2)) {
+        if (m_I3.contains(i2) || m_I4.contains(i2)) {
+          if (m_errors[i2] > m_bLow) {
+            m_bLow = m_errors[i2]; m_iLow = i2;
+          }
+        } else {
+          if (m_errors[i2] < m_bUp) {
+            m_bUp = m_errors[i2]; m_iUp = i2;
+          }
+        }
+      }
+      if ((m_iLow == -1) || (m_iUp == -1)) {
+        throw new Exception("This should never happen!");
+      }
+
+      // Made some progress.
+      return true;
+    }
+
+    /**
+     * Quick and dirty check whether the quadratic programming problem is solved.
+     * 
+     * @throws Exception if something goes wrong
+     */
+    protected void checkClassifier() throws Exception {
+
+      double sum = 0;
+      for (int i = 0; i < m_alpha.length; i++) {
+        if (m_alpha[i] > 0) {
+          sum += m_class[i] * m_alpha[i];
+        }
+      }
+      System.err.println("Sum of y(i) * alpha(i): " + sum);
+
+      for (int i = 0; i < m_alpha.length; i++) {
+        double output = SVMOutput(i, m_data.instance(i));
+        if (Utils.eq(m_alpha[i], 0)) {
+          if (Utils.sm(m_class[i] * output, 1)) {
+            System.err.println("KKT condition 1 violated: " + m_class[i] * output);
+          }
+        } 
+        if (Utils.gr(m_alpha[i], 0) && 
+            Utils.sm(m_alpha[i], m_C * m_data.instance(i).weight())) {
+          if (!Utils.eq(m_class[i] * output, 1)) {
+            System.err.println("KKT condition 2 violated: " + m_class[i] * output);
+          }
+            } 
+        if (Utils.eq(m_alpha[i], m_C * m_data.instance(i).weight())) {
+          if (Utils.gr(m_class[i] * output, 1)) {
+            System.err.println("KKT condition 3 violated: " + m_class[i] * output);
+          }
+        } 
+      }
+    }  
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** The binary classifier(s) */
+  protected BinaryMISMO[][] m_classifiers = null;
+
+  /** The complexity parameter. */
+  protected double m_C = 1.0;
+
+  /** Epsilon for rounding. */
+  protected double m_eps = 1.0e-12;
+
+  /** Tolerance for accuracy of result. */
+  protected double m_tol = 1.0e-3;
+
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+
+  /** Use MIMinimax feature space?  */
+  protected boolean m_minimax = false;   
+
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter = null;
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing;
+
+  /** The class index from the training data */
+  protected int m_classIndex = -1;
+
+  /** The class attribute */
+  protected Attribute m_classAttribute;
+  
+  /** Kernel to use **/
+  protected Kernel m_kernel = new MIPolyKernel();
+
+  /** Turn off all checks and conversions? Turning them off assumes
+    that data is purely numeric, doesn't contain any missing values,
+    and has a nominal class. Turning them off also means that
+    no header information will be stored if the machine is linear. 
+    Finally, it also assumes that no instance has a weight equal to 0.*/
+  protected boolean m_checksTurnedOff;
+
+  /** Precision constant for updating sets */
+  protected static double m_Del = 1000 * Double.MIN_VALUE;
+
+  /** Whether logistic models are to be fit */
+  protected boolean m_fitLogisticModels = false;
+
+  /** The number of folds for the internal cross-validation */
+  protected int m_numFolds = -1;
+
+  /** The random number seed  */
+  protected int m_randomSeed = 1;
+
+  /**
+   * Turns off checks for missing values, etc. Use with caution.
+   */
+  public void turnChecksOff() {
+
+    m_checksTurnedOff = true;
+  }
+
+  /**
+   * Turns on checks for missing values, etc.
+   */
+  public void turnChecksOn() {
+
+    m_checksTurnedOff = false;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = getKernel().getCapabilities();
+    result.setOwner(this);
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = ((MultiInstanceCapabilitiesHandler) getKernel()).getMultiInstanceCapabilities();
+    result.setOwner(this);
+
+    // attribute
+    result.enableAllAttributeDependencies();
+    // with NominalToBinary we can also handle nominal attributes, but only
+    // if the kernel can handle numeric attributes
+    if (result.handles(Capability.NUMERIC_ATTRIBUTES))
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Method for building the classifier. Implements a one-against-one
+   * wrapper for multi-class problems.
+   *
+   * @param insts the set of training instances
+   * @throws Exception if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances insts) throws Exception {
+    if (!m_checksTurnedOff) {
+      // can classifier handle the data?
+      getCapabilities().testWithFail(insts);
+
+      // remove instances with missing class
+      insts = new Instances(insts);
+      insts.deleteWithMissingClass();
+
+      /* Removes all the instances with weight equal to 0.
+         MUST be done since condition (8) of Keerthi's paper 
+         is made with the assertion Ci > 0 (See equation (3a). */
+      Instances data = new Instances(insts, insts.numInstances());
+      for(int i = 0; i < insts.numInstances(); i++){
+        if(insts.instance(i).weight() > 0)
+          data.add(insts.instance(i));
+      }
+      if (data.numInstances() == 0) {
+        throw new Exception("No training instances left after removing " + 
+            "instance with either a weight null or a missing class!");
+      }
+      insts = data;     
+    }
+
+    // filter data
+    if (!m_checksTurnedOff) 
+      m_Missing = new ReplaceMissingValues();
+    else 
+      m_Missing = null;
+
+    if (getCapabilities().handles(Capability.NUMERIC_ATTRIBUTES)) {
+      boolean onlyNumeric = true;
+      if (!m_checksTurnedOff) {
+	for (int i = 0; i < insts.numAttributes(); i++) {
+	  if (i != insts.classIndex()) {
+	    if (!insts.attribute(i).isNumeric()) {
+	      onlyNumeric = false;
+	      break;
+	    }
+	  }
+	}
+      }
+      
+      if (!onlyNumeric) {
+	m_NominalToBinary = new NominalToBinary();
+	// exclude the bag attribute
+	m_NominalToBinary.setAttributeIndices("2-last");
+      }
+      else {
+	m_NominalToBinary = null;
+      }
+    }
+    else {
+      m_NominalToBinary = null;
+    }
+
+    if (m_filterType == FILTER_STANDARDIZE) 
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null;
+
+
+    Instances transformedInsts;
+    Filter convertToProp = new MultiInstanceToPropositional();
+    Filter convertToMI = new PropositionalToMultiInstance();
+
+    //transform the data into single-instance format
+    if (m_minimax){ 
+      /* using SimpleMI class minimax transform method. 
+         this method transforms the multi-instance dataset into minmax feature space (single-instance) */
+      SimpleMI transMinimax = new SimpleMI();
+      transMinimax.setTransformMethod(
+          new SelectedTag(
+            SimpleMI.TRANSFORMMETHOD_MINIMAX, SimpleMI.TAGS_TRANSFORMMETHOD));
+      transformedInsts = transMinimax.transform(insts);
+    }
+    else { 
+      convertToProp.setInputFormat(insts);
+      transformedInsts=Filter.useFilter(insts, convertToProp);
+    }
+
+    if (m_Missing != null) {
+      m_Missing.setInputFormat(transformedInsts);
+      transformedInsts = Filter.useFilter(transformedInsts, m_Missing); 
+    }
+
+    if (m_NominalToBinary != null) { 
+      m_NominalToBinary.setInputFormat(transformedInsts);
+      transformedInsts = Filter.useFilter(transformedInsts, m_NominalToBinary); 
+    }
+
+    if (m_Filter != null) {
+      m_Filter.setInputFormat(transformedInsts);
+      transformedInsts = Filter.useFilter(transformedInsts, m_Filter); 
+    }
+
+    // convert the single-instance format to multi-instance format
+    convertToMI.setInputFormat(transformedInsts);
+    insts = Filter.useFilter( transformedInsts, convertToMI);
+
+    m_classIndex = insts.classIndex();
+    m_classAttribute = insts.classAttribute();
+
+    // Generate subsets representing each class
+    Instances[] subsets = new Instances[insts.numClasses()];
+    for (int i = 0; i < insts.numClasses(); i++) {
+      subsets[i] = new Instances(insts, insts.numInstances());
+    }
+    for (int j = 0; j < insts.numInstances(); j++) {
+      Instance inst = insts.instance(j);
+      subsets[(int)inst.classValue()].add(inst);
+    }
+    for (int i = 0; i < insts.numClasses(); i++) {
+      subsets[i].compactify();
+    }
+
+    // Build the binary classifiers
+    Random rand = new Random(m_randomSeed);
+    m_classifiers = new BinaryMISMO[insts.numClasses()][insts.numClasses()];
+    for (int i = 0; i < insts.numClasses(); i++) {
+      for (int j = i + 1; j < insts.numClasses(); j++) {
+        m_classifiers[i][j] = new BinaryMISMO();  
+        m_classifiers[i][j].setKernel(Kernel.makeCopy(getKernel()));
+        Instances data = new Instances(insts, insts.numInstances());
+        for (int k = 0; k < subsets[i].numInstances(); k++) {
+          data.add(subsets[i].instance(k));
+        }
+        for (int k = 0; k < subsets[j].numInstances(); k++) {
+          data.add(subsets[j].instance(k));
+        }  
+        data.compactify(); 
+        data.randomize(rand);
+        m_classifiers[i][j].buildClassifier(data, i, j, 
+            m_fitLogisticModels,
+            m_numFolds, m_randomSeed);
+      }
+    } 
+
+  }
+
+  /**
+   * Estimates class probabilities for given instance.
+   * 
+   * @param inst the instance to compute the distribution for
+   * @return the class probabilities
+   * @throws Exception if computation fails
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception { 
+
+    //convert instance into instances
+    Instances insts = new Instances(inst.dataset(), 0);
+    insts.add(inst);
+
+    //transform the data into single-instance format
+    Filter convertToProp = new MultiInstanceToPropositional();
+    Filter convertToMI = new PropositionalToMultiInstance();
+
+    if (m_minimax){ // using minimax feature space
+      SimpleMI transMinimax = new SimpleMI();
+      transMinimax.setTransformMethod(
+          new SelectedTag(
+            SimpleMI.TRANSFORMMETHOD_MINIMAX, SimpleMI.TAGS_TRANSFORMMETHOD));
+      insts = transMinimax.transform (insts);
+    }
+    else{
+      convertToProp.setInputFormat(insts);
+      insts=Filter.useFilter( insts, convertToProp);
+    }
+
+    // Filter instances 
+    if (m_Missing!=null) 
+      insts = Filter.useFilter(insts, m_Missing); 
+
+    if (m_Filter!=null)
+      insts = Filter.useFilter(insts, m_Filter);     
+
+    // convert the single-instance format to multi-instance format
+    convertToMI.setInputFormat(insts);
+    insts=Filter.useFilter( insts, convertToMI);
+
+    inst = insts.instance(0);  
+
+    if (!m_fitLogisticModels) {
+      double[] result = new double[inst.numClasses()];
+      for (int i = 0; i < inst.numClasses(); i++) {
+        for (int j = i + 1; j < inst.numClasses(); j++) {
+          if ((m_classifiers[i][j].m_alpha != null) || 
+              (m_classifiers[i][j].m_sparseWeights != null)) {
+            double output = m_classifiers[i][j].SVMOutput(-1, inst);
+            if (output > 0) {
+              result[j] += 1;
+            } else {
+              result[i] += 1;
+            }
+              }
+        } 
+      }
+      Utils.normalize(result);
+      return result;
+    } else {
+
+      // We only need to do pairwise coupling if there are more
+      // then two classes.
+      if (inst.numClasses() == 2) {
+        double[] newInst = new double[2];
+        newInst[0] = m_classifiers[0][1].SVMOutput(-1, inst);
+        newInst[1] = Utils.missingValue();
+        return m_classifiers[0][1].m_logistic.
+          distributionForInstance(new DenseInstance(1, newInst));
+      }
+      double[][] r = new double[inst.numClasses()][inst.numClasses()];
+      double[][] n = new double[inst.numClasses()][inst.numClasses()];
+      for (int i = 0; i < inst.numClasses(); i++) {
+        for (int j = i + 1; j < inst.numClasses(); j++) {
+          if ((m_classifiers[i][j].m_alpha != null) || 
+              (m_classifiers[i][j].m_sparseWeights != null)) {
+            double[] newInst = new double[2];
+            newInst[0] = m_classifiers[i][j].SVMOutput(-1, inst);
+            newInst[1] = Utils.missingValue();
+            r[i][j] = m_classifiers[i][j].m_logistic.
+              distributionForInstance(new DenseInstance(1, newInst))[0];
+            n[i][j] = m_classifiers[i][j].m_sumOfWeights;
+              }
+        }
+      }
+      return pairwiseCoupling(n, r);
+    }
+  }
+
+  /**
+   * Implements pairwise coupling.
+   *
+   * @param n the sum of weights used to train each model
+   * @param r the probability estimate from each model
+   * @return the coupled estimates
+   */
+  public double[] pairwiseCoupling(double[][] n, double[][] r) {
+
+    // Initialize p and u array
+    double[] p = new double[r.length];
+    for (int i =0; i < p.length; i++) {
+      p[i] = 1.0 / (double)p.length;
+    }
+    double[][] u = new double[r.length][r.length];
+    for (int i = 0; i < r.length; i++) {
+      for (int j = i + 1; j < r.length; j++) {
+        u[i][j] = 0.5;
+      }
+    }
+
+    // firstSum doesn't change
+    double[] firstSum = new double[p.length];
+    for (int i = 0; i < p.length; i++) {
+      for (int j = i + 1; j < p.length; j++) {
+        firstSum[i] += n[i][j] * r[i][j];
+        firstSum[j] += n[i][j] * (1 - r[i][j]);
+      }
+    }
+
+    // Iterate until convergence
+    boolean changed;
+    do {
+      changed = false;
+      double[] secondSum = new double[p.length];
+      for (int i = 0; i < p.length; i++) {
+        for (int j = i + 1; j < p.length; j++) {
+          secondSum[i] += n[i][j] * u[i][j];
+          secondSum[j] += n[i][j] * (1 - u[i][j]);
+        }
+      }
+      for (int i = 0; i < p.length; i++) {
+        if ((firstSum[i] == 0) || (secondSum[i] == 0)) {
+          if (p[i] > 0) {
+            changed = true;
+          }
+          p[i] = 0;
+        } else {
+          double factor = firstSum[i] / secondSum[i];
+          double pOld = p[i];
+          p[i] *= factor;
+          if (Math.abs(pOld - p[i]) > 1.0e-3) {
+            changed = true;
+          }
+        }
+      }
+      Utils.normalize(p);
+      for (int i = 0; i < r.length; i++) {
+        for (int j = i + 1; j < r.length; j++) {
+          u[i][j] = p[i] / (p[i] + p[j]);
+        }
+      }
+    } while (changed);
+    return p;
+  }
+
+  /**
+   * Returns the weights in sparse format.
+   * 
+   * @return the weights in sparse format
+   */
+  public double [][][] sparseWeights() {
+
+    int numValues = m_classAttribute.numValues();
+    double [][][] sparseWeights = new double[numValues][numValues][];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+        sparseWeights[i][j] = m_classifiers[i][j].m_sparseWeights;
+      }
+    }
+
+    return sparseWeights;
+  }
+
+  /**
+   * Returns the indices in sparse format.
+   * 
+   * @return the indices in sparse format
+   */
+  public int [][][] sparseIndices() {
+
+    int numValues = m_classAttribute.numValues();
+    int [][][] sparseIndices = new int[numValues][numValues][];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+        sparseIndices[i][j] = m_classifiers[i][j].m_sparseIndices;
+      }
+    }
+
+    return sparseIndices;
+  }
+
+  /**
+   * Returns the bias of each binary SMO.
+   * 
+   * @return the bias of each binary SMO
+   */
+  public double [][] bias() {
+
+    int numValues = m_classAttribute.numValues();
+    double [][] bias = new double[numValues][numValues];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+        bias[i][j] = m_classifiers[i][j].m_b;
+      }
+    }
+
+    return bias;
+  }
+
+  /**
+   * Returns the number of values of the class attribute.
+   * 
+   * @return the number values of the class attribute
+   */
+  public int numClassAttributeValues() {
+
+    return m_classAttribute.numValues();
+  }
+
+  /**
+   * Returns the names of the class attributes.
+   * 
+   * @return the names of the class attributes
+   */
+  public String[] classAttributeNames() {
+
+    int numValues = m_classAttribute.numValues();
+
+    String[] classAttributeNames = new String[numValues];
+
+    for (int i = 0; i < numValues; i++) {
+      classAttributeNames[i] = m_classAttribute.value(i);
+    }
+
+    return classAttributeNames;
+  }
+
+  /**
+   * Returns the attribute names.
+   * 
+   * @return the attribute names
+   */
+  public String[][][] attributeNames() {
+
+    int numValues = m_classAttribute.numValues();
+    String[][][] attributeNames = new String[numValues][numValues][];
+
+    for (int i = 0; i < numValues; i++) {
+      for (int j = i + 1; j < numValues; j++) {
+        int numAttributes = m_classifiers[i][j].m_data.numAttributes();
+        String[] attrNames = new String[numAttributes];
+        for (int k = 0; k < numAttributes; k++) {
+          attrNames[k] = m_classifiers[i][j].m_data.attribute(k).name();
+        }
+        attributeNames[i][j] = attrNames;          
+      }
+    }
+    return attributeNames;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector result = new Vector();
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tTurns off all checks - use with caution!\n"
+	+ "\tTurning them off assumes that data is purely numeric, doesn't\n"
+	+ "\tcontain any missing values, and has a nominal class. Turning them\n"
+	+ "\toff also means that no header information will be stored if the\n"
+	+ "\tmachine is linear. Finally, it also assumes that no instance has\n"
+	+ "\ta weight equal to 0.\n"
+	+ "\t(default: checks on)",
+	"no-checks", 0, "-no-checks"));
+
+    result.addElement(new Option(
+          "\tThe complexity constant C. (default 1)",
+          "C", 1, "-C <double>"));
+    
+    result.addElement(new Option(
+          "\tWhether to 0=normalize/1=standardize/2=neither.\n" 
+          + "\t(default 0=normalize)",
+          "N", 1, "-N"));
+    
+    result.addElement(new Option(
+          "\tUse MIminimax feature space. ",
+          "I", 0, "-I"));
+    
+    result.addElement(new Option(
+          "\tThe tolerance parameter. (default 1.0e-3)",
+          "L", 1, "-L <double>"));
+    
+    result.addElement(new Option(
+          "\tThe epsilon for round-off error. (default 1.0e-12)",
+          "P", 1, "-P <double>"));
+    
+    result.addElement(new Option(
+          "\tFit logistic models to SVM outputs. ",
+          "M", 0, "-M"));
+    
+    result.addElement(new Option(
+          "\tThe number of folds for the internal cross-validation. \n"
+          + "\t(default -1, use training data)",
+          "V", 1, "-V <double>"));
+    
+    result.addElement(new Option(
+          "\tThe random number seed. (default 1)",
+          "W", 1, "-W <double>"));
+    
+    result.addElement(new Option(
+	"\tThe Kernel to use.\n"
+	+ "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
+	"K", 1, "-K <classname and parameters>"));
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to kernel "
+	+ getKernel().getClass().getName() + ":"));
+    
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  Turning them off assumes that data is purely numeric, doesn't
+   *  contain any missing values, and has a nominal class. Turning them
+   *  off also means that no header information will be stored if the
+   *  machine is linear. Finally, it also assumes that no instance has
+   *  a weight equal to 0.
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  The complexity constant C. (default 1)</pre>
+   * 
+   * <pre> -N
+   *  Whether to 0=normalize/1=standardize/2=neither.
+   *  (default 0=normalize)</pre>
+   * 
+   * <pre> -I
+   *  Use MIminimax feature space. </pre>
+   * 
+   * <pre> -L &lt;double&gt;
+   *  The tolerance parameter. (default 1.0e-3)</pre>
+   * 
+   * <pre> -P &lt;double&gt;
+   *  The epsilon for round-off error. (default 1.0e-12)</pre>
+   * 
+   * <pre> -M
+   *  Fit logistic models to SVM outputs. </pre>
+   * 
+   * <pre> -V &lt;double&gt;
+   *  The number of folds for the internal cross-validation. 
+   *  (default -1, use training data)</pre>
+   * 
+   * <pre> -W &lt;double&gt;
+   *  The random number seed. (default 1)</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.mi.supportVector.MIPolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    setChecksTurnedOff(Utils.getFlag("no-checks", options));
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setC(Double.parseDouble(tmpStr));
+    else
+      setC(1.0);
+
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0)
+      setToleranceParameter(Double.parseDouble(tmpStr));
+    else
+      setToleranceParameter(1.0e-3);
+    
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setEpsilon(new Double(tmpStr));
+    else
+      setEpsilon(1.0e-12);
+
+    setMinimax(Utils.getFlag('I', options));
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
+    else
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+    
+    setBuildLogisticModels(Utils.getFlag('M', options));
+    
+    tmpStr = Utils.getOption('V', options);
+    if (tmpStr.length() != 0)
+      m_numFolds = Integer.parseInt(tmpStr);
+    else
+      m_numFolds = -1;
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() != 0)
+      setRandomSeed(Integer.parseInt(tmpStr));
+    else
+      setRandomSeed(1);
+
+    tmpStr     = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getChecksTurnedOff())
+      result.add("-no-checks");
+
+    result.add("-C"); 
+    result.add("" + getC());
+    
+    result.add("-L");
+    result.add("" + getToleranceParameter());
+    
+    result.add("-P");
+    result.add("" + getEpsilon());
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+    
+    if (getMinimax())
+      result.add("-I");
+
+    if (getBuildLogisticModels())
+      result.add("-M");
+    
+    result.add("-V");
+    result.add("" + getNumFolds());
+    
+    result.add("-W");
+    result.add("" + getRandomSeed());
+    
+    result.add("-K");
+    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
+    
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Disables or enables the checks (which could be time-consuming). Use with
+   * caution!
+   * 
+   * @param value	if true turns off all checks
+   */
+  public void setChecksTurnedOff(boolean value) {
+    if (value)
+      turnChecksOff();
+    else
+      turnChecksOn();
+  }
+  
+  /**
+   * Returns whether the checks are turned off or not.
+   * 
+   * @return		true if the checks are turned off
+   */
+  public boolean getChecksTurnedOff() {
+    return m_checksTurnedOff;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String checksTurnedOffTipText() {
+    return "Turns time-consuming checks off - use with caution.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+
+  /**
+   * Gets the kernel to use.
+   *
+   * @return 		the kernel
+   */
+  public Kernel getKernel() {
+    return m_kernel;
+  }
+    
+  /**
+   * Sets the kernel to use.
+   *
+   * @param value	the kernel
+   */
+  public void setKernel(Kernel value) {
+    if (!(value instanceof MultiInstanceCapabilitiesHandler))
+      throw new IllegalArgumentException(
+	  "Kernel must be able to handle multi-instance data!\n"
+	  + "(This one does not implement " + MultiInstanceCapabilitiesHandler.class.getName() + ")");
+    
+    m_kernel = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String cTipText() {
+    return "The complexity parameter C.";
+  }
+
+  /**
+   * Get the value of C.
+   *
+   * @return Value of C.
+   */
+  public double getC() {
+
+    return m_C;
+  }
+
+  /**
+   * Set the value of C.
+   *
+   * @param v  Value to assign to C.
+   */
+  public void setC(double v) {
+
+    m_C = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String toleranceParameterTipText() {
+    return "The tolerance parameter (shouldn't be changed).";
+  }
+
+  /**
+   * Get the value of tolerance parameter.
+   * @return Value of tolerance parameter.
+   */
+  public double getToleranceParameter() {
+
+    return m_tol;
+  }
+
+  /**
+   * Set the value of tolerance parameter.
+   * @param v  Value to assign to tolerance parameter.
+   */
+  public void setToleranceParameter(double v) {
+
+    m_tol = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String epsilonTipText() {
+    return "The epsilon for round-off error (shouldn't be changed).";
+  }
+
+  /**
+   * Get the value of epsilon.
+   * @return Value of epsilon.
+   */
+  public double getEpsilon() {
+
+    return m_eps;
+  }
+
+  /**
+   * Set the value of epsilon.
+   * @param v  Value to assign to epsilon.
+   */
+  public void setEpsilon(double v) {
+
+    m_eps = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "Determines how/if the data will be transformed.";
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minimaxTipText() {
+    return "Whether the MIMinimax feature space is to be used.";
+  }
+
+  /**
+   * Check if the MIMinimax feature space is to be used.
+   * @return true if minimax
+   */
+  public boolean getMinimax() {
+
+    return m_minimax;
+  }
+
+  /**
+   * Set if the MIMinimax feature space is to be used.
+   * @param v  true if RBF
+   */
+  public void setMinimax(boolean v) {
+    m_minimax = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String buildLogisticModelsTipText() {
+    return "Whether to fit logistic models to the outputs (for proper "
+      + "probability estimates).";
+  }
+
+  /**
+   * Get the value of buildLogisticModels.
+   *
+   * @return Value of buildLogisticModels.
+   */
+  public boolean getBuildLogisticModels() {
+
+    return m_fitLogisticModels;
+  }
+
+  /**
+   * Set the value of buildLogisticModels.
+   *
+   * @param newbuildLogisticModels Value to assign to buildLogisticModels.
+   */
+  public void setBuildLogisticModels(boolean newbuildLogisticModels) {
+
+    m_fitLogisticModels = newbuildLogisticModels;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "The number of folds for cross-validation used to generate "
+      + "training data for logistic models (-1 means use training data).";
+  }
+
+  /**
+   * Get the value of numFolds.
+   *
+   * @return Value of numFolds.
+   */
+  public int getNumFolds() {
+
+    return m_numFolds;
+  }
+
+  /**
+   * Set the value of numFolds.
+   *
+   * @param newnumFolds Value to assign to numFolds.
+   */
+  public void setNumFolds(int newnumFolds) {
+
+    m_numFolds = newnumFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Random number seed for the cross-validation.";
+  }
+
+  /**
+   * Get the value of randomSeed.
+   *
+   * @return Value of randomSeed.
+   */
+  public int getRandomSeed() {
+
+    return m_randomSeed;
+  }
+
+  /**
+   * Set the value of randomSeed.
+   *
+   * @param newrandomSeed Value to assign to randomSeed.
+   */
+  public void setRandomSeed(int newrandomSeed) {
+
+    m_randomSeed = newrandomSeed;
+  }
+
+  /**
+   * Prints out the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    if ((m_classAttribute == null)) {
+      return "SMO: No model built yet.";
+    }
+    try {
+      text.append("SMO\n\n");
+      for (int i = 0; i < m_classAttribute.numValues(); i++) {
+        for (int j = i + 1; j < m_classAttribute.numValues(); j++) {
+          text.append("Classifier for classes: " + 
+              m_classAttribute.value(i) + ", " +
+              m_classAttribute.value(j) + "\n\n");
+          text.append(m_classifiers[i][j]);
+          if (m_fitLogisticModels) {
+            text.append("\n\n");
+            if ( m_classifiers[i][j].m_logistic == null) {
+              text.append("No logistic model has been fit.\n");
+            } else {
+              text.append(m_classifiers[i][j].m_logistic);
+            }
+          }
+          text.append("\n\n");
+        }
+      }
+    } catch (Exception e) {
+      return "Can't print SMO classifier.";
+    }
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param argv the commandline parameters
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MISMO(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MISVM.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MISVM.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MISVM.java	(revision 29)
@@ -0,0 +1,814 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, InumBag., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MISVM.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.SMO;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MultiInstanceToPropositional;
+import weka.filters.unsupervised.attribute.Normalize;
+import weka.filters.unsupervised.attribute.Standardize;
+import weka.filters.unsupervised.instance.SparseToNonSparse;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Implements Stuart Andrews' mi_SVM (Maximum pattern Margin Formulation of MIL). Applying weka.classifiers.functions.SMO to solve multiple instances problem.<br/>
+ * The algorithm first assign the bag label to each instance in the bag as its initial class label.  After that applying SMO to compute SVM solution for all instances in positive bags And then reassign the class label of each instance in the positive bag according to the SVM result Keep on iteration until labels do not change anymore.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Stuart Andrews, Ioannis Tsochantaridis, Thomas Hofmann: Support Vector Machines for Multiple-Instance Learning. In: Advances in Neural Information Processing Systems 15, 561-568, 2003.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Andrews2003,
+ *    author = {Stuart Andrews and Ioannis Tsochantaridis and Thomas Hofmann},
+ *    booktitle = {Advances in Neural Information Processing Systems 15},
+ *    pages = {561-568},
+ *    publisher = {MIT Press},
+ *    title = {Support Vector Machines for Multiple-Instance Learning},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -C &lt;double&gt;
+ *  The complexity constant C. (default 1)</pre>
+ * 
+ * <pre> -N &lt;default 0&gt;
+ *  Whether to 0=normalize/1=standardize/2=neither.
+ *  (default: 0=normalize)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  The maximum number of iterations to perform.
+ *  (default: 500)</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lin Dong (ld21@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ * @see weka.classifiers.functions.SMO
+ */
+public class MISVM 
+  extends AbstractClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 7622231064035278145L;
+  
+  /** The filter used to transform the sparse datasets to nonsparse */
+  protected Filter m_SparseFilter = new SparseToNonSparse();
+
+  /** The SMO classifier used to compute SVM soluton w,b for the dataset */
+  protected SVM m_SVM;
+
+  /** the kernel to use */
+  protected Kernel m_kernel = new PolyKernel();
+
+  /** The complexity parameter. */
+  protected double m_C = 1.0;
+
+  /** The filter used to standardize/normalize all values. */
+  protected Filter m_Filter =null;
+
+  /** Whether to normalize/standardize/neither */
+  protected int m_filterType = FILTER_NORMALIZE;
+
+  /** Normalize training data */
+  public static final int FILTER_NORMALIZE = 0;
+  /** Standardize training data */
+  public static final int FILTER_STANDARDIZE = 1;
+  /** No normalization/standardization */
+  public static final int FILTER_NONE = 2;
+  /** The filter to apply to the training data */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NORMALIZE, "Normalize training data"),
+    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
+    new Tag(FILTER_NONE, "No normalization/standardization"),
+  };
+
+  /** the maximum number of iterations to perform */
+  protected int m_MaxIterations = 500;
+  
+  /** filter used to convert the MI dataset into single-instance dataset */
+  protected MultiInstanceToPropositional m_ConvertToProp = new MultiInstanceToPropositional();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+         "Implements Stuart Andrews' mi_SVM (Maximum pattern Margin "
+       + "Formulation of MIL). Applying weka.classifiers.functions.SMO "
+       + "to solve multiple instances problem.\n"
+       + "The algorithm first assign the bag label to each instance in the "
+       + "bag as its initial class label.  After that applying SMO to compute "
+       + "SVM solution for all instances in positive bags And then reassign "
+       + "the class label of each instance in the positive bag according to "
+       + "the SVM result Keep on iteration until labels do not change "
+       + "anymore.\n\n"
+       + "For more information see:\n\n"
+       + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Stuart Andrews and Ioannis Tsochantaridis and Thomas Hofmann");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.TITLE, "Support Vector Machines for Multiple-Instance Learning");
+    result.setValue(Field.BOOKTITLE, "Advances in Neural Information Processing Systems 15");
+    result.setValue(Field.PUBLISHER, "MIT Press");
+    result.setValue(Field.PAGES, "561-568");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+          "\tThe complexity constant C. (default 1)",
+          "C", 1, "-C <double>"));
+    
+    result.addElement(new Option(
+        "\tWhether to 0=normalize/1=standardize/2=neither.\n"
+        + "\t(default: 0=normalize)",
+        "N", 1, "-N <default 0>"));
+  
+    result.addElement(new Option(
+        "\tThe maximum number of iterations to perform.\n"
+        + "\t(default: 500)",
+        "I", 1, "-I <num>"));
+  
+    result.addElement(new Option(
+	"\tThe Kernel to use.\n"
+	+ "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
+	"K", 1, "-K <classname and parameters>"));
+
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to kernel "
+	+ getKernel().getClass().getName() + ":"));
+    
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -C &lt;double&gt;
+   *  The complexity constant C. (default 1)</pre>
+   * 
+   * <pre> -N &lt;default 0&gt;
+   *  Whether to 0=normalize/1=standardize/2=neither.
+   *  (default: 0=normalize)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  The maximum number of iterations to perform.
+   *  (default: 500)</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setC(Double.parseDouble(tmpStr));
+    else
+      setC(1.0);
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
+    else
+      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
+
+    tmpStr = Utils.getOption('I', options);
+    if (tmpStr.length() != 0)
+      setMaxIterations(Integer.parseInt(tmpStr));
+    else
+      setMaxIterations(500);
+    
+    tmpStr     = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+    
+    super.setOptions(options);
+  }
+
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+    
+    result.add("-C");
+    result.add("" + getC());
+    
+    result.add("-N");
+    result.add("" + m_filterType);
+
+    result.add("-K");
+    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+
+  /**
+   * Gets the kernel to use.
+   *
+   * @return 		the kernel
+   */
+  public Kernel getKernel() {
+    return m_kernel;
+  }
+    
+  /**
+   * Sets the kernel to use.
+   *
+   * @param value	the kernel
+   */
+  public void setKernel(Kernel value) {
+    m_kernel = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String filterTypeTipText() {
+    return "The filter type for transforming the training data.";
+  }
+
+  /**
+   * Sets how the training data will be transformed. Should be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @param newType the new filtering mode
+   */
+  public void setFilterType(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets how the training data will be transformed. Will be one of
+   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
+   *
+   * @return the filtering mode
+   */
+  public SelectedTag getFilterType() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String cTipText() {
+    return "The value for C.";
+  }
+
+  /**
+   * Get the value of C.
+   *
+   * @return Value of C.
+   */
+  public double getC() {
+
+    return m_C;
+  }
+
+  /**
+   * Set the value of C.
+   *
+   * @param v  Value to assign to C.
+   */
+  public void setC(double v) {
+    m_C = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+    return "The maximum number of iterations to perform.";
+  }
+
+  /**
+   * Gets the maximum number of iterations.
+   *
+   * @return 		the maximum number of iterations.
+   */
+  public int getMaxIterations() {
+    return m_MaxIterations;
+  }
+
+  /**
+   * Sets the maximum number of iterations.
+   *
+   * @param value	the maximum number of iterations.
+   */
+  public void setMaxIterations(int value) {
+    if (value < 1)
+      System.out.println(
+	  "At least 1 iteration is necessary (provided: " + value + ")!");
+    else
+      m_MaxIterations = value;
+  }
+
+  /**
+   * adapted version of SMO
+   */
+  private class SVM
+    extends SMO {
+    
+    /** for serialization */
+    static final long serialVersionUID = -8325638229658828931L;
+    
+    /**
+     * Constructor
+     */
+    protected SVM (){
+      super();
+    }
+
+    /**
+     * Computes SVM output for given instance.
+     *
+     * @param index the instance for which output is to be computed
+     * @param inst the instance 
+     * @return the output of the SVM for the given instance
+     * @throws Exception in case of an error
+     */
+    protected double output(int index, Instance inst) throws Exception {
+      double output = 0;
+      output = m_classifiers[0][1].SVMOutput(index, inst);
+      return output;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    SVM 		classifier;
+    Capabilities 	result;
+
+    classifier = null;
+    result     = null;
+    
+    try {
+      classifier = new SVM();
+      classifier.setKernel(Kernel.makeCopy(getKernel()));
+      result = classifier.getCapabilities();
+      result.setOwner(this);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    int numBags = train.numInstances(); //number of bags
+    int []bagSize= new int [numBags];   
+    int classes [] = new int [numBags];
+
+    Vector instLabels = new Vector();  //store the class label assigned to each single instance
+    Vector pre_instLabels=new Vector();
+
+    for(int h=0; h<numBags; h++)  {//h_th bag 
+      classes[h] = (int) train.instance(h).classValue();  
+      bagSize[h]=train.instance(h).relationalValue(1).numInstances();
+      for (int i=0; i<bagSize[h];i++)
+        instLabels.addElement(new Double(classes[h]));	       	  
+    }
+
+    // convert the training dataset into single-instance dataset
+    m_ConvertToProp.setWeightMethod(
+        new SelectedTag(
+          MultiInstanceToPropositional.WEIGHTMETHOD_1, 
+          MultiInstanceToPropositional.TAGS_WEIGHTMETHOD)); 
+    m_ConvertToProp.setInputFormat(train);
+    train = Filter.useFilter( train, m_ConvertToProp);
+    train.deleteAttributeAt(0); //remove the bagIndex attribute;
+
+    if (m_filterType == FILTER_STANDARDIZE)  
+      m_Filter = new Standardize();
+    else if (m_filterType == FILTER_NORMALIZE)
+      m_Filter = new Normalize();
+    else 
+      m_Filter = null;
+
+    if (m_Filter!=null) {
+      m_Filter.setInputFormat(train);
+      train = Filter.useFilter(train, m_Filter);
+    }	
+
+    if (m_Debug) {
+      System.out.println("\nIteration History..." );
+    }
+
+    if (getDebug())
+      System.out.println("\nstart building model ...");
+
+    int index;
+    double sum, max_output; 
+    Vector max_index = new Vector();
+    Instance inst=null;
+
+    int loopNum=0;
+    do {
+      loopNum++;
+      index=-1;
+      if (m_Debug)
+        System.out.println("=====================loop: "+loopNum);
+
+      //store the previous label information
+      pre_instLabels=(Vector)instLabels.clone();   
+
+      // set the proper SMO options in order to build a SVM model
+      m_SVM = new SVM();
+      m_SVM.setC(getC());
+      m_SVM.setKernel(Kernel.makeCopy(getKernel()));
+      // SVM model do not normalize / standardize the input dataset as the the dataset has already been processed  
+      m_SVM.setFilterType(new SelectedTag(FILTER_NONE, TAGS_FILTER));  
+
+      m_SVM.buildClassifier(train); 
+
+      for(int h=0; h<numBags; h++)  {//h_th bag
+        if (classes[h]==1) { //positive bag
+          if (m_Debug)
+            System.out.println("--------------- "+h+" ----------------");
+          sum=0;
+
+          //compute outputs f=(w,x)+b for all instance in positive bags
+          for (int i=0; i<bagSize[h]; i++){
+            index ++; 
+
+            inst=train.instance(index); 
+            double output =m_SVM.output(-1, inst); //System.out.println(output); 
+            if (output<=0){
+              if (inst.classValue()==1.0) {	
+                train.instance(index).setClassValue(0.0);
+                instLabels.set(index, new Double(0.0));
+
+                if (m_Debug)
+                  System.out.println( index+ "- changed to 0");
+              }
+            }
+            else { 
+              if (inst.classValue()==0.0) {
+                train.instance(index).setClassValue(1.0);
+                instLabels.set(index, new Double(1.0));
+
+                if (m_Debug)
+                  System.out.println(index+ "+ changed to 1");
+              }
+            }
+            sum += train.instance(index).classValue();  
+          }
+
+          /* if class value of all instances in a positive bag 
+             are changed to 0.0, find the instance with max SVMOutput value 
+             and assign the class value 1.0 to it.
+             */
+          if (sum==0){ 
+            //find the instance with max SVMOutput value  
+            max_output=-Double.MAX_VALUE;
+            max_index.clear();
+            for (int j=index-bagSize[h]+1; j<index+1; j++){
+              inst=train.instance(j);
+              double output = m_SVM.output(-1, inst);
+              if(max_output<output) {
+                max_output=output;
+                max_index.clear();
+                max_index.add(new Integer(j));
+              }
+              else if(max_output==output) 
+                max_index.add(new Integer(j));
+            }
+
+            //assign the class value 1.0 to the instances with max SVMOutput
+            for (int vecIndex=0; vecIndex<max_index.size(); vecIndex ++) {
+              Integer i =(Integer)max_index.get(vecIndex);
+              train.instance(i.intValue()).setClassValue(1.0);
+              instLabels.set(i.intValue(), new Double(1.0));
+
+              if (m_Debug)
+                System.out.println("##change to 1 ###outpput: "+max_output+" max_index: "+i+ " bag: "+h);
+            }
+
+          }
+        }else   //negative bags
+          index += bagSize[h];
+      }
+    }while(!instLabels.equals(pre_instLabels) && loopNum < m_MaxIterations);
+
+    if (getDebug())
+      System.out.println("finish building model.");
+  }
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp)
+    throws Exception {
+
+    double sum=0;
+    double classValue;
+    double[] distribution = new double[2];
+
+    Instances testData = new Instances(exmp.dataset(), 0);
+    testData.add(exmp);
+
+    // convert the training dataset into single-instance dataset
+    testData = Filter.useFilter(testData, m_ConvertToProp);	
+    testData.deleteAttributeAt(0); //remove the bagIndex attribute	
+
+    if (m_Filter != null)	
+      testData = Filter.useFilter(testData, m_Filter); 
+
+    for(int j = 0; j < testData.numInstances(); j++){
+      Instance inst = testData.instance(j);
+      double output = m_SVM.output(-1, inst); 
+      if (output <= 0)
+        classValue = 0.0;
+      else
+        classValue = 1.0;
+      sum += classValue;
+    }
+    if (sum == 0)
+      distribution[0] = 1.0;
+    else 
+      distribution[0] = 0.0;
+    distribution [1] = 1.0 - distribution[0];
+
+    return distribution;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MISVM(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/MIWrapper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/MIWrapper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/MIWrapper.java	(revision 29)
@@ -0,0 +1,550 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIWrapper.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ * 
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.MultiInstanceToPropositional;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A simple Wrapper method for applying standard propositional learners to multi-instance data.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * E. T. Frank, X. Xu (2003). Applying propositional learning algorithms to multi-instance data. Department of Computer Science, University of Waikato, Hamilton, NZ.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Frank2003,
+ *    address = {Department of Computer Science, University of Waikato, Hamilton, NZ},
+ *    author = {E. T. Frank and X. Xu},
+ *    institution = {University of Waikato},
+ *    month = {06},
+ *    title = {Applying propositional learning algorithms to multi-instance data},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P [1|2|3]
+ *  The method used in testing:
+ *  1.arithmetic average
+ *  2.geometric average
+ *  3.max probability of positive bag.
+ *  (default: 1)</pre>
+ * 
+ * <pre> -A [0|1|2|3]
+ *  The type of weight setting for each single-instance:
+ *  0.keep the weight to be the same as the original value;
+ *  1.weight = 1.0
+ *  2.weight = 1.0/Total number of single-instance in the
+ *   corresponding bag
+ *  3. weight = Total number of single-instance / (Total
+ *   number of bags * Total number of single-instance 
+ *   in the corresponding bag).
+ *  (default: 3)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ 
+ */
+public class MIWrapper 
+  extends SingleClassifierEnhancer
+  implements MultiInstanceCapabilitiesHandler, OptionHandler,
+             TechnicalInformationHandler {  
+
+  /** for serialization */
+  static final long serialVersionUID = -7707766152904315910L;
+  
+  /** The number of the class labels */
+  protected int m_NumClasses;
+
+  /** arithmetic average */
+  public static final int TESTMETHOD_ARITHMETIC = 1;
+  /** geometric average */
+  public static final int TESTMETHOD_GEOMETRIC = 2;
+  /** max probability of positive bag */
+  public static final int TESTMETHOD_MAXPROB = 3;
+  /** the test methods */
+  public static final Tag[] TAGS_TESTMETHOD = {
+    new Tag(TESTMETHOD_ARITHMETIC, "arithmetic average"),
+    new Tag(TESTMETHOD_GEOMETRIC, "geometric average"),
+    new Tag(TESTMETHOD_MAXPROB, "max probability of positive bag")
+  };
+
+  /** the test method  */
+  protected int m_Method = TESTMETHOD_GEOMETRIC;
+
+  /** Filter used to convert MI dataset into single-instance dataset */
+  protected MultiInstanceToPropositional m_ConvertToProp = new MultiInstanceToPropositional();
+
+  /** the single-instance weight setting method */
+  protected int m_WeightMethod = MultiInstanceToPropositional.WEIGHTMETHOD_INVERSE2;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+         "A simple Wrapper method for applying standard propositional learners "
+       + "to multi-instance data.\n\n"
+       + "For more information see:\n\n"
+       + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "E. T. Frank and X. Xu");
+    result.setValue(Field.TITLE, "Applying propositional learning algorithms to multi-instance data");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.MONTH, "06");
+    result.setValue(Field.INSTITUTION, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Department of Computer Science, University of Waikato, Hamilton, NZ");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tThe method used in testing:\n"
+          + "\t1.arithmetic average\n"
+          + "\t2.geometric average\n"
+          + "\t3.max probability of positive bag.\n"
+          + "\t(default: 1)",
+          "P", 1, "-P [1|2|3]"));
+    
+    result.addElement(new Option(
+          "\tThe type of weight setting for each single-instance:\n"
+          + "\t0.keep the weight to be the same as the original value;\n"
+          + "\t1.weight = 1.0\n"
+          + "\t2.weight = 1.0/Total number of single-instance in the\n"
+          + "\t\tcorresponding bag\n"
+          + "\t3. weight = Total number of single-instance / (Total\n"
+          + "\t\tnumber of bags * Total number of single-instance \n"
+          + "\t\tin the corresponding bag).\n"
+          + "\t(default: 3)",
+          "A", 1, "-A [0|1|2|3]"));	
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P [1|2|3]
+   *  The method used in testing:
+   *  1.arithmetic average
+   *  2.geometric average
+   *  3.max probability of positive bag.
+   *  (default: 1)</pre>
+   * 
+   * <pre> -A [0|1|2|3]
+   *  The type of weight setting for each single-instance:
+   *  0.keep the weight to be the same as the original value;
+   *  1.weight = 1.0
+   *  2.weight = 1.0/Total number of single-instance in the
+   *   corresponding bag
+   *  3. weight = Total number of single-instance / (Total
+   *   number of bags * Total number of single-instance 
+   *   in the corresponding bag).
+   *  (default: 3)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setDebug(Utils.getFlag('D', options));
+
+    String methodString = Utils.getOption('P', options);
+    if (methodString.length() != 0) {
+      setMethod(
+          new SelectedTag(Integer.parseInt(methodString), TAGS_TESTMETHOD));
+    } else {
+      setMethod(
+          new SelectedTag(TESTMETHOD_ARITHMETIC, TAGS_TESTMETHOD));
+    }
+
+    String weightString = Utils.getOption('A', options);
+    if (weightString.length() != 0) {
+      setWeightMethod(
+          new SelectedTag(
+            Integer.parseInt(weightString), 
+            MultiInstanceToPropositional.TAGS_WEIGHTMETHOD));
+    } else {
+      setWeightMethod(
+          new SelectedTag(
+            MultiInstanceToPropositional.WEIGHTMETHOD_INVERSE2, 
+            MultiInstanceToPropositional.TAGS_WEIGHTMETHOD));
+    }	
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+
+    result.add("-P");
+    result.add("" + m_Method);
+
+    result.add("-A");
+    result.add("" + m_WeightMethod);
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightMethodTipText() {
+    return "The method used for weighting the instances.";
+  }
+
+  /**
+   * The new method for weighting the instances.
+   *
+   * @param method      the new method
+   */
+  public void setWeightMethod(SelectedTag method){
+    if (method.getTags() == MultiInstanceToPropositional.TAGS_WEIGHTMETHOD)
+      m_WeightMethod = method.getSelectedTag().getID();
+  }
+
+  /**
+   * Returns the current weighting method for instances.
+   * 
+   * @return the current weighting method
+   */
+  public SelectedTag getWeightMethod(){
+    return new SelectedTag(
+                  m_WeightMethod, MultiInstanceToPropositional.TAGS_WEIGHTMETHOD);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String methodTipText() {
+    return "The method used for testing.";
+  }
+
+  /**
+   * Set the method used in testing. 
+   *
+   * @param method the index of method to use.
+   */
+  public void setMethod(SelectedTag method) {
+    if (method.getTags() == TAGS_TESTMETHOD)
+      m_Method = method.getSelectedTag().getID();
+  }
+
+  /**
+   * Get the method used in testing.
+   *
+   * @return the index of method used in testing.
+   */
+  public SelectedTag getMethod() {
+    return new SelectedTag(m_Method, TAGS_TESTMETHOD);
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    if (super.getCapabilities().handles(Capability.NOMINAL_CLASS))
+      result.enable(Capability.NOMINAL_CLASS);
+    if (super.getCapabilities().handles(Capability.BINARY_CLASS))
+      result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param data the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances train = new Instances(data);
+    train.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("A base classifier has not been specified!");
+    }
+
+    if (getDebug())
+      System.out.println("Start training ...");
+    m_NumClasses = train.numClasses();
+
+    //convert the training dataset into single-instance dataset
+    m_ConvertToProp.setWeightMethod(getWeightMethod());
+    m_ConvertToProp.setInputFormat(train);
+    train = Filter.useFilter(train, m_ConvertToProp);
+    train.deleteAttributeAt(0); // remove the bag index attribute
+
+    m_Classifier.buildClassifier(train);
+  }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param exmp the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance exmp) 
+    throws Exception {	
+
+    Instances testData = new Instances (exmp.dataset(),0);
+    testData.add(exmp);
+
+    // convert the training dataset into single-instance dataset
+    m_ConvertToProp.setWeightMethod(
+        new SelectedTag(
+          MultiInstanceToPropositional.WEIGHTMETHOD_ORIGINAL, 
+          MultiInstanceToPropositional.TAGS_WEIGHTMETHOD));
+    testData = Filter.useFilter(testData, m_ConvertToProp);
+    testData.deleteAttributeAt(0); //remove the bag index attribute
+
+    // Compute the log-probability of the bag
+    double [] distribution = new double[m_NumClasses];
+    double nI = (double)testData.numInstances();
+    double [] maxPr = new double [m_NumClasses];
+
+    for(int i=0; i<nI; i++){
+      double[] dist = m_Classifier.distributionForInstance(testData.instance(i));
+      for(int j=0; j<m_NumClasses; j++){
+
+        switch(m_Method){
+          case TESTMETHOD_ARITHMETIC:
+            distribution[j] += dist[j]/nI;
+            break;
+          case TESTMETHOD_GEOMETRIC:
+            // Avoid 0/1 probability
+            if(dist[j]<0.001)
+              dist[j] = 0.001;
+            else if(dist[j]>0.999)
+              dist[j] = 0.999;
+
+            distribution[j] += Math.log(dist[j])/nI;
+            break;
+          case TESTMETHOD_MAXPROB:
+            if (dist[j]>maxPr[j]) 
+              maxPr[j] = dist[j];
+            break;
+        }
+      }
+    }
+
+    if(m_Method == TESTMETHOD_GEOMETRIC)
+      for(int j=0; j<m_NumClasses; j++)
+        distribution[j] = Math.exp(distribution[j]);
+
+    if(m_Method == TESTMETHOD_MAXPROB){   // for positive bag
+      distribution[1] = maxPr[1];
+      distribution[0] = 1 - distribution[1];
+    }
+
+    if (Utils.eq(Utils.sum(distribution), 0)) {
+      for (int i = 0; i < distribution.length; i++)
+	distribution[i] = 1.0 / (double) distribution.length;
+    }
+    else {
+      Utils.normalize(distribution);
+    }
+    
+    return distribution;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {	
+    return "MIWrapper with base classifier: \n"+m_Classifier.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new MIWrapper(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/SimpleMI.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/SimpleMI.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/SimpleMI.java	(revision 29)
@@ -0,0 +1,505 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleMI.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.SingleClassifierEnhancer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Reduces MI data into mono-instance data.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M [1|2|3]
+ *  The method used in transformation:
+ *  1.arithmatic average; 2.geometric centor;
+ *  3.using minimax combined features of a bag (default: 1)
+ * 
+ *  Method 3:
+ *  Define s to be the vector of the coordinate-wise maxima
+ *  and minima of X, ie., 
+ *  s(X)=(minx1, ..., minxm, maxx1, ...,maxxm), transform
+ *  the exemplars into mono-instance which contains attributes
+ *  s(X)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -W
+ *  Full name of base classifier.
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @author Lin Dong (ld21@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class SimpleMI 
+  extends SingleClassifierEnhancer
+  implements OptionHandler, MultiInstanceCapabilitiesHandler {  
+
+  /** for serialization */
+  static final long serialVersionUID = 9137795893666592662L;
+  
+  /** arithmetic average */
+  public static final int TRANSFORMMETHOD_ARITHMETIC = 1;
+  /** geometric average */
+  public static final int TRANSFORMMETHOD_GEOMETRIC = 2;
+  /** using minimax combined features of a bag */
+  public static final int TRANSFORMMETHOD_MINIMAX = 3;
+  /** the transformation methods */
+  public static final Tag[] TAGS_TRANSFORMMETHOD = {
+    new Tag(TRANSFORMMETHOD_ARITHMETIC, "arithmetic average"),
+    new Tag(TRANSFORMMETHOD_GEOMETRIC, "geometric average"),
+    new Tag(TRANSFORMMETHOD_MINIMAX, "using minimax combined features of a bag")
+  };
+
+  /** the method used in transformation */
+  protected int m_TransformMethod = TRANSFORMMETHOD_ARITHMETIC;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Reduces MI data into mono-instance data.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tThe method used in transformation:\n"
+          + "\t1.arithmatic average; 2.geometric centor;\n"
+          + "\t3.using minimax combined features of a bag (default: 1)\n\n"
+          + "\tMethod 3:\n"
+          + "\tDefine s to be the vector of the coordinate-wise maxima\n"
+          + "\tand minima of X, ie., \n"
+          + "\ts(X)=(minx1, ..., minxm, maxx1, ...,maxxm), transform\n"
+          + "\tthe exemplars into mono-instance which contains attributes\n"
+          + "\ts(X)",
+          "M", 1, "-M [1|2|3]"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M [1|2|3]
+   *  The method used in transformation:
+   *  1.arithmatic average; 2.geometric centor;
+   *  3.using minimax combined features of a bag (default: 1)
+   * 
+   *  Method 3:
+   *  Define s to be the vector of the coordinate-wise maxima
+   *  and minima of X, ie., 
+   *  s(X)=(minx1, ..., minxm, maxx1, ...,maxxm), transform
+   *  the exemplars into mono-instance which contains attributes
+   *  s(X)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -W
+   *  Full name of base classifier.
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {	
+
+    setDebug(Utils.getFlag('D', options));
+
+    String methodString = Utils.getOption('M', options);
+    if (methodString.length() != 0) {
+      setTransformMethod(
+          new SelectedTag(
+            Integer.parseInt(methodString), TAGS_TRANSFORMMETHOD));
+    } else {
+      setTransformMethod(
+          new SelectedTag(
+            TRANSFORMMETHOD_ARITHMETIC, TAGS_TRANSFORMMETHOD));
+    }	
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+
+    result.add("-M");
+    result.add("" + m_TransformMethod);
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String transformMethodTipText() {
+    return "The method used in transformation.";
+  }
+
+  /**
+   * Set the method used in transformation. 
+   *
+   * @param newMethod the index of method to use.
+   */
+  public void setTransformMethod(SelectedTag newMethod) {
+    if (newMethod.getTags() == TAGS_TRANSFORMMETHOD)
+      m_TransformMethod = newMethod.getSelectedTag().getID();
+  }
+
+  /**
+   * Get the method used in transformation.
+   *
+   * @return the index of method used.
+   */
+  public SelectedTag getTransformMethod() {
+    return new SelectedTag(m_TransformMethod, TAGS_TRANSFORMMETHOD);
+  }
+
+  /** 
+   * Implements MITransform (3 type of transformation) 1.arithmatic average;
+   * 2.geometric centor; 3.merge minima and maxima attribute value together
+   *
+   * @param train the multi-instance dataset (with relational attribute)  
+   * @return the transformed dataset with each bag contain mono-instance
+   * (without relational attribute) so that any classifier not for MI dataset
+   * can be applied on it.
+   * @throws Exception if the transformation fails
+   */
+  public Instances transform(Instances train) throws Exception{
+
+    Attribute classAttribute = (Attribute) train.classAttribute().copy();
+    Attribute bagLabel = (Attribute) train.attribute(0);
+    double labelValue;
+
+    Instances newData = train.attribute(1).relation().stringFreeStructure();
+
+    //insert a bag label attribute at the begining
+    newData.insertAttributeAt(bagLabel, 0);
+
+    //insert a class attribute at the end
+    newData.insertAttributeAt(classAttribute, newData.numAttributes());
+    newData.setClassIndex(newData.numAttributes()-1);
+
+    Instances mini_data = newData.stringFreeStructure();
+    Instances max_data = newData.stringFreeStructure();
+
+    Instance newInst = new DenseInstance(newData.numAttributes()); 
+    Instance mini_Inst = new DenseInstance(mini_data.numAttributes());
+    Instance max_Inst = new DenseInstance(max_data.numAttributes());
+    newInst.setDataset(newData);
+    mini_Inst.setDataset(mini_data);
+    max_Inst.setDataset(max_data);
+
+    double N= train.numInstances( );//number of bags   
+    for(int i=0; i<N; i++){	
+      int attIdx =1;
+      Instance bag = train.instance(i); //retrieve the bag instance
+      labelValue= bag.value(0);
+      if (m_TransformMethod != TRANSFORMMETHOD_MINIMAX)	    
+        newInst.setValue(0, labelValue);
+      else {
+        mini_Inst.setValue(0, labelValue);
+        max_Inst.setValue(0, labelValue);
+      }
+
+      Instances data = bag.relationalValue(1); // retrieve relational value for each bag 
+      for(int j=0; j<data.numAttributes( ); j++){ 	
+        double value;
+        if(m_TransformMethod == TRANSFORMMETHOD_ARITHMETIC){
+          value = data.meanOrMode(j); 
+          newInst.setValue(attIdx++, value);
+        }
+        else if (m_TransformMethod == TRANSFORMMETHOD_GEOMETRIC){
+          double[] minimax = minimax(data, j);
+          value = (minimax[0]+minimax[1])/2.0;
+          newInst.setValue(attIdx++, value);
+        }
+        else {  //m_TransformMethod == TRANSFORMMETHOD_MINIMAX
+          double[] minimax = minimax(data, j);
+          mini_Inst.setValue(attIdx, minimax[0]);//minima value
+          max_Inst.setValue(attIdx, minimax[1]);//maxima value
+          attIdx++;
+        }
+      }
+
+      if (m_TransformMethod == TRANSFORMMETHOD_MINIMAX) {
+        if (!bag.classIsMissing())
+          max_Inst.setClassValue(bag.classValue()); //set class value
+        mini_data.add(mini_Inst); 
+        max_data.add(max_Inst);
+      }
+      else{
+        if (!bag.classIsMissing())
+          newInst.setClassValue(bag.classValue()); //set class value
+        newData.add(newInst);		
+      }  
+    }
+
+    if (m_TransformMethod == TRANSFORMMETHOD_MINIMAX) {
+      mini_data.setClassIndex(-1);
+      mini_data.deleteAttributeAt(mini_data.numAttributes()-1); //delete class attribute for the minima data
+      max_data.deleteAttributeAt(0); // delete the bag label attribute for the maxima data
+
+      newData = Instances.mergeInstances(mini_data, max_data); //merge minima and maxima data
+      newData.setClassIndex(newData.numAttributes()-1);
+
+    }	
+
+    return newData;
+  }
+
+  /**
+   * Get the minimal and maximal value of a certain attribute in a certain data
+   *
+   * @param data the data
+   * @param attIndex the index of the attribute
+   * @return the double array containing in entry 0 for min and 1 for max.
+   */
+  public static double[] minimax(Instances data, int attIndex){
+    double[] rt = {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
+    for(int i=0; i<data.numInstances(); i++){
+      double val = data.instance(i).value(attIndex);
+      if(val > rt[1])
+        rt[1] = val;
+      if(val < rt[0])
+        rt[0] = val;
+    }
+
+    for(int j=0; j<2; j++)
+      if(Double.isInfinite(rt[j]))
+        rt[j] = Double.NaN;
+
+    return rt;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.disableAllClassDependencies();
+    if (super.getCapabilities().handles(Capability.NOMINAL_CLASS))
+      result.enable(Capability.NOMINAL_CLASS);
+    if (super.getCapabilities().handles(Capability.BINARY_CLASS))
+      result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier
+   *
+   * @param train the training data to be used for generating the
+   * boosted classifier.
+   * @throws Exception if the classifier could not be built successfully
+   */
+  public void buildClassifier(Instances train) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(train);
+
+    // remove instances with missing class
+    train = new Instances(train);
+    train.deleteWithMissingClass();
+    
+    if (m_Classifier == null) {
+      throw new Exception("A base classifier has not been specified!");
+    }
+
+    if (getDebug())
+      System.out.println("Start training ...");
+    Instances data = transform(train); 
+
+    data.deleteAttributeAt(0); // delete the bagID attribute
+    m_Classifier.buildClassifier(data);
+
+    if (getDebug())
+      System.out.println("Finish building model");
+  }		
+
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param newBag the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance newBag)
+    throws Exception {
+
+    double [] distribution = new double[2];
+    Instances test = new Instances (newBag.dataset(), 0);	
+    test.add(newBag);	
+
+    test = transform(test);
+    test.deleteAttributeAt(0);
+    Instance newInst=test.firstInstance();
+
+    distribution = m_Classifier.distributionForInstance(newInst);
+
+    return distribution;	   
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString() {	
+    return "SimpleMI with base classifier: \n"+m_Classifier.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the command line arguments to the
+   * scheme (see Evaluation)
+   */
+  public static void main(String[] argv) {
+    runClassifier(new SimpleMI(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/TLD.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/TLD.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/TLD.java	(revision 29)
@@ -0,0 +1,1159 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TLD.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.RandomizableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Two-Level Distribution approach, changes the starting value of the searching algorithm, supplement the cut-off modification and check missing values.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Xin Xu (2003). Statistical learning in multiple instance problem. Hamilton, NZ.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;mastersthesis{Xu2003,
+ *    address = {Hamilton, NZ},
+ *    author = {Xin Xu},
+ *    note = {0657.594},
+ *    school = {University of Waikato},
+ *    title = {Statistical learning in multiple instance problem},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C
+ *  Set whether or not use empirical
+ *  log-odds cut-off instead of 0</pre>
+ * 
+ * <pre> -R &lt;numOfRuns&gt;
+ *  Set the number of multiple runs 
+ *  needed for searching the MLE.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5481 $ 
+ */
+public class TLD 
+  extends RandomizableClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 6657315525171152210L;
+  
+  /** The mean for each attribute of each positive exemplar */
+  protected double[][] m_MeanP = null;
+
+  /** The variance for each attribute of each positive exemplar */
+  protected double[][] m_VarianceP = null;
+
+  /** The mean for each attribute of each negative exemplar */
+  protected double[][] m_MeanN = null;
+
+  /** The variance for each attribute of each negative exemplar */
+  protected double[][] m_VarianceN = null;
+
+  /** The effective sum of weights of each positive exemplar in each dimension*/
+  protected double[][] m_SumP = null;
+
+  /** The effective sum of weights of each negative exemplar in each dimension*/
+  protected double[][] m_SumN = null;
+
+  /** The parameters to be estimated for each positive exemplar*/
+  protected double[] m_ParamsP = null;
+
+  /** The parameters to be estimated for each negative exemplar*/
+  protected double[] m_ParamsN = null;
+
+  /** The dimension of each exemplar, i.e. (numAttributes-2) */
+  protected int m_Dimension = 0;
+
+  /** The class label of each exemplar */
+  protected double[] m_Class = null;
+
+  /** The number of class labels in the data */
+  protected int m_NumClasses = 2;
+
+  /** The very small number representing zero */
+  static public double ZERO = 1.0e-6;   
+
+  /** The number of runs to perform */
+  protected int m_Run = 1;
+
+  protected double m_Cutoff;
+
+  protected boolean m_UseEmpiricalCutOff = false;   
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Two-Level Distribution approach, changes the starting value of "
+      + "the searching algorithm, supplement the cut-off modification and "
+      + "check missing values.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MASTERSTHESIS);
+    result.setValue(Field.AUTHOR, "Xin Xu");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.TITLE, "Statistical learning in multiple instance problem");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, NZ");
+    result.setValue(Field.NOTE, "0657.594");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   *
+   * @param exs the training exemplars
+   * @throws Exception if the model cannot be built properly
+   */    
+  public void buildClassifier(Instances exs)throws Exception{
+    // can classifier handle the data?
+    getCapabilities().testWithFail(exs);
+
+    // remove instances with missing class
+    exs = new Instances(exs);
+    exs.deleteWithMissingClass();
+    
+    int numegs = exs.numInstances();
+    m_Dimension = exs.attribute(1).relation(). numAttributes();
+    Instances pos = new Instances(exs, 0), neg = new Instances(exs, 0);
+
+    for(int u=0; u<numegs; u++){
+      Instance example = exs.instance(u);
+      if(example.classValue() == 1)
+        pos.add(example);
+      else
+        neg.add(example);
+    }
+
+    int pnum = pos.numInstances(), nnum = neg.numInstances();	
+
+    m_MeanP = new double[pnum][m_Dimension];
+    m_VarianceP = new double[pnum][m_Dimension];
+    m_SumP = new double[pnum][m_Dimension];
+    m_MeanN = new double[nnum][m_Dimension];
+    m_VarianceN = new double[nnum][m_Dimension];
+    m_SumN = new double[nnum][m_Dimension];
+    m_ParamsP = new double[4*m_Dimension];
+    m_ParamsN = new double[4*m_Dimension];
+
+    // Estimation of the parameters: as the start value for search
+    double[] pSumVal=new double[m_Dimension], // for m 
+      nSumVal=new double[m_Dimension]; 
+    double[] maxVarsP=new double[m_Dimension], // for a
+      maxVarsN=new double[m_Dimension]; 
+    // Mean of sample variances: for b, b=a/E(\sigma^2)+2
+    double[] varMeanP = new double[m_Dimension],
+      varMeanN = new double[m_Dimension]; 
+    // Variances of sample means: for w, w=E[var(\mu)]/E[\sigma^2]
+    double[] meanVarP = new double[m_Dimension],
+      meanVarN = new double[m_Dimension];
+    // number of exemplars without all values missing
+    double[] numExsP = new double[m_Dimension],
+      numExsN = new double[m_Dimension];
+
+    // Extract metadata fro both positive and negative bags
+    for(int v=0; v < pnum; v++){
+      /*Exemplar px = pos.exemplar(v);
+        m_MeanP[v] = px.meanOrMode();
+        m_VarianceP[v] = px.variance();
+        Instances pxi =  px.getInstances();
+        */
+
+      Instances pxi =  pos.instance(v).relationalValue(1);
+      for (int k=0; k<pxi.numAttributes(); k++) { 
+        m_MeanP[v][k] = pxi.meanOrMode(k);
+        m_VarianceP[v][k] = pxi.variance(k);
+      }
+
+      for (int w=0,t=0; w < m_Dimension; w++,t++){		
+        //if((t==m_ClassIndex) || (t==m_IdIndex))
+        //  t++;		
+
+        if(!Double.isNaN(m_MeanP[v][w])){
+          for(int u=0;u<pxi.numInstances();u++){
+            Instance ins = pxi.instance(u);			
+            if(!ins.isMissing(t))
+              m_SumP[v][w] += ins.weight();			   
+          }   
+          numExsP[w]++;  
+          pSumVal[w] += m_MeanP[v][w];
+          meanVarP[w] += m_MeanP[v][w]*m_MeanP[v][w];    
+          if(maxVarsP[w] < m_VarianceP[v][w])
+            maxVarsP[w] = m_VarianceP[v][w];
+          varMeanP[w] += m_VarianceP[v][w];
+          m_VarianceP[v][w] *= (m_SumP[v][w]-1.0);
+          if(m_VarianceP[v][w] < 0.0)
+            m_VarianceP[v][w] = 0.0;
+        }
+      }
+    }
+
+    for(int v=0; v < nnum; v++){
+      /*Exemplar nx = neg.exemplar(v);
+        m_MeanN[v] = nx.meanOrMode();
+        m_VarianceN[v] = nx.variance();
+        Instances nxi =  nx.getInstances();
+        */
+      Instances nxi =  neg.instance(v).relationalValue(1);
+      for (int k=0; k<nxi.numAttributes(); k++) {
+        m_MeanN[v][k] = nxi.meanOrMode(k);
+        m_VarianceN[v][k] = nxi.variance(k);
+      }
+
+      for (int w=0,t=0; w < m_Dimension; w++,t++){		
+        //if((t==m_ClassIndex) || (t==m_IdIndex))
+        //  t++;		
+
+        if(!Double.isNaN(m_MeanN[v][w])){
+          for(int u=0;u<nxi.numInstances();u++)
+            if(!nxi.instance(u).isMissing(t))
+              m_SumN[v][w] += nxi.instance(u).weight();
+          numExsN[w]++; 	
+          nSumVal[w] += m_MeanN[v][w];
+          meanVarN[w] += m_MeanN[v][w]*m_MeanN[v][w]; 
+          if(maxVarsN[w] < m_VarianceN[v][w])
+            maxVarsN[w] = m_VarianceN[v][w];
+          varMeanN[w] += m_VarianceN[v][w];
+          m_VarianceN[v][w] *= (m_SumN[v][w]-1.0);
+          if(m_VarianceN[v][w] < 0.0)
+            m_VarianceN[v][w] = 0.0;
+        }
+      }
+    }
+
+    for(int w=0; w<m_Dimension; w++){
+      pSumVal[w] /= numExsP[w];
+      nSumVal[w] /= numExsN[w];
+      if(numExsP[w]>1)
+        meanVarP[w] = meanVarP[w]/(numExsP[w]-1.0) 
+          - pSumVal[w]*numExsP[w]/(numExsP[w]-1.0);
+      if(numExsN[w]>1)
+        meanVarN[w] = meanVarN[w]/(numExsN[w]-1.0) 
+          - nSumVal[w]*numExsN[w]/(numExsN[w]-1.0);
+      varMeanP[w] /= numExsP[w];
+      varMeanN[w] /= numExsN[w];
+    }
+
+    //Bounds and parameter values for each run
+    double[][] bounds = new double[2][4];
+    double[] pThisParam = new double[4], 
+      nThisParam = new double[4];
+
+    // Initial values for parameters
+    double a, b, w, m;
+
+    // Optimize for one dimension
+    for (int x=0; x < m_Dimension; x++){
+      if (getDebug())
+	System.err.println("\n\n!!!!!!!!!!!!!!!!!!!!!!???Dimension #"+x);
+
+      // Positive examplars: first run
+      a = (maxVarsP[x]>ZERO) ? maxVarsP[x]:1.0; 
+      if (varMeanP[x]<=ZERO)   varMeanP[x] = ZERO;  // modified by LinDong (09/2005)
+      b = a/varMeanP[x]+2.0; // a/(b-2) = E(\sigma^2)
+      w = meanVarP[x]/varMeanP[x]; // E[var(\mu)] = w*E[\sigma^2]	    
+      if(w<=ZERO)  w=1.0;
+
+      m = pSumVal[x]; 	  
+      pThisParam[0] = a;    // a
+      pThisParam[1] = b;  // b
+      pThisParam[2] = w;  // w
+      pThisParam[3] = m;  // m
+
+      // Negative examplars: first run
+      a = (maxVarsN[x]>ZERO) ? maxVarsN[x]:1.0; 
+      if (varMeanN[x]<=ZERO)   varMeanN[x] = ZERO; // modified by LinDong (09/2005)
+      b = a/varMeanN[x]+2.0; // a/(b-2) = E(\sigma^2)
+      w = meanVarN[x]/varMeanN[x]; // E[var(\mu)] = w*E[\sigma^2]	    
+      if(w<=ZERO) w=1.0;
+
+      m = nSumVal[x]; 	  
+      nThisParam[0] = a;    // a
+      nThisParam[1] = b;  // b
+      nThisParam[2] = w;  // w
+      nThisParam[3] = m;  // m
+
+      // Bound constraints
+      bounds[0][0] = ZERO; // a > 0
+      bounds[0][1] = 2.0+ZERO;  // b > 2 
+      bounds[0][2] = ZERO; // w > 0
+      bounds[0][3] = Double.NaN;
+
+      for(int t=0; t<4; t++){
+        bounds[1][t] = Double.NaN;
+        m_ParamsP[4*x+t] = pThisParam[t];	
+        m_ParamsN[4*x+t] = nThisParam[t];
+      }
+      double pminVal=Double.MAX_VALUE, nminVal=Double.MAX_VALUE;
+      Random whichEx = new Random(m_Seed); 
+      TLD_Optm pOp=null, nOp=null;	
+      boolean isRunValid = true;
+      double[] sumP=new double[pnum], meanP=new double[pnum],
+        varP=new double[pnum];
+      double[] sumN=new double[nnum], meanN=new double[nnum],
+        varN=new double[nnum];
+
+      // One dimension
+      for(int p=0; p<pnum; p++){
+        sumP[p] = m_SumP[p][x];
+        meanP[p] = m_MeanP[p][x];
+        varP[p] = m_VarianceP[p][x];
+      }
+      for(int q=0; q<nnum; q++){
+        sumN[q] = m_SumN[q][x];
+        meanN[q] = m_MeanN[q][x];
+        varN[q] = m_VarianceN[q][x];
+      }
+
+      for(int y=0; y<m_Run;){
+	if (getDebug())
+	  System.err.println("\n\n!!!!!!!!!!!!!!!!!!!!!!???Run #"+y);
+        double thisMin;
+
+        if (getDebug())
+          System.err.println("\nPositive exemplars");
+        pOp = new TLD_Optm();
+        pOp.setNum(sumP);
+        pOp.setSSquare(varP);
+        pOp.setXBar(meanP);
+
+        pThisParam = pOp.findArgmin(pThisParam, bounds);
+        while(pThisParam==null){
+          pThisParam = pOp.getVarbValues();		    
+          if (getDebug())
+            System.err.println("!!! 200 iterations finished, not enough!");
+          pThisParam = pOp.findArgmin(pThisParam, bounds);
+        }	
+
+        thisMin = pOp.getMinFunction();
+        if(!Double.isNaN(thisMin) && (thisMin<pminVal)){
+          pminVal = thisMin;
+          for(int z=0; z<4; z++)
+            m_ParamsP[4*x+z] = pThisParam[z];
+        }
+
+        if(Double.isNaN(thisMin)){
+          pThisParam = new double[4];
+          isRunValid =false;
+        }
+
+        if (getDebug())
+          System.err.println("\nNegative exemplars");
+        nOp = new TLD_Optm();
+        nOp.setNum(sumN);
+        nOp.setSSquare(varN);
+        nOp.setXBar(meanN);
+
+        nThisParam = nOp.findArgmin(nThisParam, bounds);
+        while(nThisParam==null){
+          nThisParam = nOp.getVarbValues();
+          if (getDebug())
+            System.err.println("!!! 200 iterations finished, not enough!");
+          nThisParam = nOp.findArgmin(nThisParam, bounds);
+        }	
+        thisMin = nOp.getMinFunction();
+        if(!Double.isNaN(thisMin) && (thisMin<nminVal)){
+          nminVal = thisMin;
+          for(int z=0; z<4; z++)
+            m_ParamsN[4*x+z] = nThisParam[z];     
+        }
+
+        if(Double.isNaN(thisMin)){
+          nThisParam = new double[4];
+          isRunValid =false;
+        }
+
+        if(!isRunValid){ y--; isRunValid=true; } 		
+
+        if(++y<m_Run){
+          // Change the initial parameters and restart	   	    
+          int pone = whichEx.nextInt(pnum), // Randomly pick one pos. exmpl.
+              none = whichEx.nextInt(nnum);
+
+          // Positive exemplars: next run 
+          while((m_SumP[pone][x]<=1.0)||Double.isNaN(m_MeanP[pone][x]))
+            pone = whichEx.nextInt(pnum);
+
+          a = m_VarianceP[pone][x]/(m_SumP[pone][x]-1.0); 		
+          if(a<=ZERO) a=m_ParamsN[4*x]; // Change to negative params
+          m = m_MeanP[pone][x];
+          double sq = (m-m_ParamsP[4*x+3])*(m-m_ParamsP[4*x+3]);
+
+          b = a*m_ParamsP[4*x+2]/sq+2.0; // b=a/Var+2, assuming Var=Sq/w'
+          if((b<=ZERO) || Double.isNaN(b) || Double.isInfinite(b))
+            b=m_ParamsN[4*x+1];
+
+          w = sq*(m_ParamsP[4*x+1]-2.0)/m_ParamsP[4*x];//w=Sq/Var, assuming Var=a'/(b'-2)
+          if((w<=ZERO) || Double.isNaN(w) || Double.isInfinite(w))
+            w=m_ParamsN[4*x+2];
+
+          pThisParam[0] = a;    // a
+          pThisParam[1] = b;  // b
+          pThisParam[2] = w;  // w
+          pThisParam[3] = m;  // m	    
+
+          // Negative exemplars: next run 
+          while((m_SumN[none][x]<=1.0)||Double.isNaN(m_MeanN[none][x]))
+            none = whichEx.nextInt(nnum);	    
+
+          a = m_VarianceN[none][x]/(m_SumN[none][x]-1.0);	
+          if(a<=ZERO) a=m_ParamsP[4*x];       
+          m = m_MeanN[none][x];
+          sq = (m-m_ParamsN[4*x+3])*(m-m_ParamsN[4*x+3]);
+
+          b = a*m_ParamsN[4*x+2]/sq+2.0; // b=a/Var+2, assuming Var=Sq/w'
+          if((b<=ZERO) || Double.isNaN(b) || Double.isInfinite(b))
+            b=m_ParamsP[4*x+1];
+
+          w = sq*(m_ParamsN[4*x+1]-2.0)/m_ParamsN[4*x];//w=Sq/Var, assuming Var=a'/(b'-2)
+          if((w<=ZERO) || Double.isNaN(w) || Double.isInfinite(w))
+            w=m_ParamsP[4*x+2];
+
+          nThisParam[0] = a;    // a
+          nThisParam[1] = b;  // b
+          nThisParam[2] = w;  // w
+          nThisParam[3] = m;  // m	    		
+        }
+      }	    	    	    
+    }
+
+    for (int x=0, y=0; x<m_Dimension; x++, y++){
+      //if((x==exs.classIndex()) || (x==exs.idIndex()))
+      //y++;
+      a=m_ParamsP[4*x]; b=m_ParamsP[4*x+1]; 
+      w=m_ParamsP[4*x+2]; m=m_ParamsP[4*x+3];
+      if (getDebug())
+	System.err.println("\n\n???Positive: ( "+exs.attribute(1).relation().attribute(y)+
+          "): a="+a+", b="+b+", w="+w+", m="+m);
+
+      a=m_ParamsN[4*x]; b=m_ParamsN[4*x+1]; 
+      w=m_ParamsN[4*x+2]; m=m_ParamsN[4*x+3];
+      if (getDebug())
+	System.err.println("???Negative: ("+exs.attribute(1).relation().attribute(y)+
+          "): a="+a+", b="+b+", w="+w+", m="+m);
+    }
+
+    if(m_UseEmpiricalCutOff){	
+      // Find the empirical cut-off
+      double[] pLogOdds=new double[pnum], nLogOdds=new double[nnum];  
+      for(int p=0; p<pnum; p++)
+        pLogOdds[p] = 
+          likelihoodRatio(m_SumP[p], m_MeanP[p], m_VarianceP[p]);
+
+      for(int q=0; q<nnum; q++)
+        nLogOdds[q] = 
+          likelihoodRatio(m_SumN[q], m_MeanN[q], m_VarianceN[q]);
+
+      // Update m_Cutoff
+      findCutOff(pLogOdds, nLogOdds);
+    }
+    else
+      m_Cutoff = -Math.log((double)pnum/(double)nnum);
+
+    if (getDebug())
+      System.err.println("???Cut-off="+m_Cutoff);
+  }        
+
+  /**
+   *
+   * @param ex the given test exemplar
+   * @return the classification 
+   * @throws Exception if the exemplar could not be classified
+   * successfully
+   */
+  public double classifyInstance(Instance ex)throws Exception{
+    //Exemplar ex = new Exemplar(e);
+    Instances exi = ex.relationalValue(1);
+    double[] n = new double[m_Dimension];
+    double [] xBar = new double[m_Dimension];
+    double [] sSq = new double[m_Dimension];
+    for (int i=0; i<exi.numAttributes() ; i++){
+      xBar[i] = exi.meanOrMode(i);
+      sSq[i] = exi.variance(i);
+    }
+
+    for (int w=0, t=0; w < m_Dimension; w++, t++){
+      //if((t==m_ClassIndex) || (t==m_IdIndex))
+      //t++;	
+      for(int u=0;u<exi.numInstances();u++)
+        if(!exi.instance(u).isMissing(t))
+          n[w] += exi.instance(u).weight();
+
+      sSq[w] = sSq[w]*(n[w]-1.0);
+      if(sSq[w] <= 0.0)
+        sSq[w] = 0.0;
+    }
+
+    double logOdds = likelihoodRatio(n, xBar, sSq);
+    return (logOdds > m_Cutoff) ? 1 : 0 ;
+  }
+
+  private double likelihoodRatio(double[] n, double[] xBar, double[] sSq){	
+    double LLP = 0.0, LLN = 0.0;
+
+    for (int x=0; x<m_Dimension; x++){
+      if(Double.isNaN(xBar[x])) continue; // All missing values
+
+      int halfN = ((int)n[x])/2;	
+      //Log-likelihood for positive 
+      double a=m_ParamsP[4*x], b=m_ParamsP[4*x+1], 
+             w=m_ParamsP[4*x+2], m=m_ParamsP[4*x+3];
+      LLP += 0.5*b*Math.log(a) + 0.5*(b+n[x]-1.0)*Math.log(1.0+n[x]*w)
+        - 0.5*(b+n[x])*Math.log((1.0+n[x]*w)*(a+sSq[x])+
+            n[x]*(xBar[x]-m)*(xBar[x]-m))
+        - 0.5*n[x]*Math.log(Math.PI);
+      for(int y=1; y<=halfN; y++)
+        LLP += Math.log(b/2.0+n[x]/2.0-(double)y);
+
+      if(n[x]/2.0 > halfN) // n is odd
+        LLP += TLD_Optm.diffLnGamma(b/2.0);
+
+      //Log-likelihood for negative 
+      a=m_ParamsN[4*x];
+      b=m_ParamsN[4*x+1]; 
+      w=m_ParamsN[4*x+2];
+      m=m_ParamsN[4*x+3];
+      LLN += 0.5*b*Math.log(a) + 0.5*(b+n[x]-1.0)*Math.log(1.0+n[x]*w)
+        - 0.5*(b+n[x])*Math.log((1.0+n[x]*w)*(a+sSq[x])+
+            n[x]*(xBar[x]-m)*(xBar[x]-m))
+        - 0.5*n[x]*Math.log(Math.PI);
+      for(int y=1; y<=halfN; y++)
+        LLN += Math.log(b/2.0+n[x]/2.0-(double)y);	
+
+      if(n[x]/2.0 > halfN) // n is odd
+        LLN += TLD_Optm.diffLnGamma(b/2.0);   
+    }
+
+    return LLP - LLN;
+  }
+
+  private void findCutOff(double[] pos, double[] neg){
+    int[] pOrder = Utils.sort(pos),
+      nOrder = Utils.sort(neg);
+    /*
+       System.err.println("\n\n???Positive: ");
+       for(int t=0; t<pOrder.length; t++)
+       System.err.print(t+":"+Utils.doubleToString(pos[pOrder[t]],0,2)+" ");
+       System.err.println("\n\n???Negative: ");
+       for(int t=0; t<nOrder.length; t++)
+       System.err.print(t+":"+Utils.doubleToString(neg[nOrder[t]],0,2)+" ");
+       */
+    int pNum = pos.length, nNum = neg.length, count, p=0, n=0;	
+    double fstAccu=0.0, sndAccu=(double)pNum, split; 
+    double maxAccu = 0, minDistTo0 = Double.MAX_VALUE;
+
+    // Skip continuous negatives	
+    for(;(n<nNum)&&(pos[pOrder[0]]>=neg[nOrder[n]]); n++, fstAccu++);
+
+    if(n>=nNum){ // totally seperate
+      m_Cutoff = (neg[nOrder[nNum-1]]+pos[pOrder[0]])/2.0;	
+      //m_Cutoff = neg[nOrder[nNum-1]];
+      return;  
+    }	
+
+    count=n;
+    while((p<pNum)&&(n<nNum)){
+      // Compare the next in the two lists
+      if(pos[pOrder[p]]>=neg[nOrder[n]]){ // Neg has less log-odds
+        fstAccu += 1.0;    
+        split=neg[nOrder[n]];
+        n++;	 
+      }
+      else{
+        sndAccu -= 1.0;
+        split=pos[pOrder[p]];
+        p++;
+      }	    	  
+      count++;
+      if((fstAccu+sndAccu > maxAccu) 
+          || ((fstAccu+sndAccu == maxAccu) && (Math.abs(split)<minDistTo0))){
+        maxAccu = fstAccu+sndAccu;
+        m_Cutoff = split;
+        minDistTo0 = Math.abs(split);
+      }	    
+    }		
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+          "\tSet whether or not use empirical\n"
+          + "\tlog-odds cut-off instead of 0",
+          "C", 0, "-C"));
+    
+    result.addElement(new Option(
+          "\tSet the number of multiple runs \n"
+          + "\tneeded for searching the MLE.",
+          "R", 1, "-R <numOfRuns>"));
+    
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C
+   *  Set whether or not use empirical
+   *  log-odds cut-off instead of 0</pre>
+   * 
+   * <pre> -R &lt;numOfRuns&gt;
+   *  Set the number of multiple runs 
+   *  needed for searching the MLE.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception{
+    setDebug(Utils.getFlag('D', options));
+
+    setUsingCutOff(Utils.getFlag('C', options));
+
+    String runString = Utils.getOption('R', options);
+    if (runString.length() != 0) 
+      setNumRuns(Integer.parseInt(runString));
+    else 
+      setNumRuns(1);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getDebug())
+      result.add("-D");
+    
+    if (getUsingCutOff())
+      result.add("-C");
+
+    result.add("-R");
+    result.add("" + getNumRuns());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numRunsTipText() {
+    return "The number of runs to perform.";
+  }
+
+  /**
+   * Sets the number of runs to perform.
+   *
+   * @param numRuns   the number of runs to perform
+   */
+  public void setNumRuns(int numRuns) {
+    m_Run = numRuns;
+  }
+
+  /**
+   * Returns the number of runs to perform.
+   *
+   * @return          the number of runs to perform
+   */
+  public int getNumRuns() {
+    return m_Run;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String usingCutOffTipText() {
+    return "Whether to use an empirical cutoff.";
+  }
+
+  /**
+   * Sets whether to use an empirical cutoff.
+   *
+   * @param cutOff      whether to use an empirical cutoff
+   */
+  public void setUsingCutOff (boolean cutOff) {
+    m_UseEmpiricalCutOff = cutOff;
+  }
+
+  /**
+   * Returns whether an empirical cutoff is used
+   *
+   * @return            true if an empirical cutoff is used
+   */
+  public boolean getUsingCutOff() {
+    return m_UseEmpiricalCutOff;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5481 $");
+  }
+
+  /**
+   * Main method for testing.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new TLD(), args);
+  }
+}
+
+class TLD_Optm extends Optimization {
+
+  private double[] num;
+  private double[] sSq;
+  private double[] xBar;
+
+  public void setNum(double[] n) {num = n;}
+  public void setSSquare(double[] s){sSq = s;}
+  public void setXBar(double[] x){xBar = x;}
+
+  /**
+   * Compute Ln[Gamma(b+0.5)] - Ln[Gamma(b)]
+   *
+   * @param b the value in the above formula
+   * @return the result
+   */    
+  public static double diffLnGamma(double b){
+    double[] coef= {76.18009172947146, -86.50532032941677,
+      24.01409824083091, -1.231739572450155, 
+      0.1208650973866179e-2, -0.5395239384953e-5};
+    double rt = -0.5;
+    rt += (b+1.0)*Math.log(b+6.0) - (b+0.5)*Math.log(b+5.5);
+    double series1=1.000000000190015, series2=1.000000000190015;
+    for(int i=0; i<6; i++){
+      series1 += coef[i]/(b+1.5+(double)i);
+      series2 += coef[i]/(b+1.0+(double)i);
+    }
+
+    rt += Math.log(series1*b)-Math.log(series2*(b+0.5));
+    return rt;
+  }
+
+  /**
+   * Compute dLn[Gamma(x+0.5)]/dx - dLn[Gamma(x)]/dx
+   *
+   * @param x the value in the above formula
+   * @return the result
+   */    
+  protected double diffFstDervLnGamma(double x){
+    double rt=0, series=1.0;// Just make it >0
+    for(int i=0;series>=m_Zero*1e-3;i++){
+      series = 0.5/((x+(double)i)*(x+(double)i+0.5));
+      rt += series;
+    }
+    return rt;
+  }
+
+  /**
+   * Compute {Ln[Gamma(x+0.5)]}'' - {Ln[Gamma(x)]}''
+   *
+   * @param x the value in the above formula
+   * @return the result
+   */    
+  protected double diffSndDervLnGamma(double x){
+    double rt=0, series=1.0;// Just make it >0
+    for(int i=0;series>=m_Zero*1e-3;i++){
+      series = (x+(double)i+0.25)/
+        ((x+(double)i)*(x+(double)i)*(x+(double)i+0.5)*(x+(double)i+0.5));
+      rt -= series;
+    }
+    return rt;
+  }
+
+  /**
+   * Implement this procedure to evaluate objective
+   * function to be minimized
+   */
+  protected double objectiveFunction(double[] x){
+    int numExs = num.length;
+    double NLL = 0; // Negative Log-Likelihood
+
+    double a=x[0], b=x[1], w=x[2], m=x[3];
+    for(int j=0; j < numExs; j++){
+
+      if(Double.isNaN(xBar[j])) continue; // All missing values
+
+      NLL += 0.5*(b+num[j])*
+        Math.log((1.0+num[j]*w)*(a+sSq[j]) + 
+            num[j]*(xBar[j]-m)*(xBar[j]-m));	    
+
+      if(Double.isNaN(NLL) && m_Debug){
+        System.err.println("???????????1: "+a+" "+b+" "+w+" "+m
+            +"|x-: "+xBar[j] + 
+            "|n: "+num[j] + "|S^2: "+sSq[j]);
+        System.exit(1);
+      }
+
+      // Doesn't affect optimization
+      //NLL += 0.5*num[j]*Math.log(Math.PI);		
+
+      NLL -= 0.5*(b+num[j]-1.0)*Math.log(1.0+num[j]*w);
+
+
+      if(Double.isNaN(NLL) && m_Debug){
+        System.err.println("???????????2: "+a+" "+b+" "+w+" "+m
+            +"|x-: "+xBar[j] + 
+            "|n: "+num[j] + "|S^2: "+sSq[j]);
+        System.exit(1);
+      }
+
+      int halfNum = ((int)num[j])/2;
+      for(int z=1; z<=halfNum; z++)
+        NLL -= Math.log(0.5*b+0.5*num[j]-(double)z);
+
+      if(0.5*num[j] > halfNum) // num[j] is odd
+        NLL -= diffLnGamma(0.5*b);
+
+      if(Double.isNaN(NLL) && m_Debug){
+        System.err.println("???????????3: "+a+" "+b+" "+w+" "+m
+            +"|x-: "+xBar[j] + 
+            "|n: "+num[j] + "|S^2: "+sSq[j]);
+        System.exit(1);
+      }				
+
+      NLL -= 0.5*Math.log(a)*b;
+      if(Double.isNaN(NLL) && m_Debug){
+        System.err.println("???????????4:"+a+" "+b+" "+w+" "+m);
+        System.exit(1);
+      }	    
+    }
+    if(m_Debug)
+      System.err.println("?????????????5: "+NLL);
+    if(Double.isNaN(NLL))	   
+      System.exit(1);
+
+    return NLL;
+  }
+
+  /**
+   * Subclass should implement this procedure to evaluate gradient
+   * of the objective function
+   */
+  protected double[] evaluateGradient(double[] x){
+    double[] g = new double[x.length];
+    int numExs = num.length;
+
+    double a=x[0],b=x[1],w=x[2],m=x[3];
+
+    double da=0.0, db=0.0, dw=0.0, dm=0.0; 
+    for(int j=0; j < numExs; j++){
+
+      if(Double.isNaN(xBar[j])) continue; // All missing values
+
+      double denorm = (1.0+num[j]*w)*(a+sSq[j]) + 
+        num[j]*(xBar[j]-m)*(xBar[j]-m);
+
+      da += 0.5*(b+num[j])*(1.0+num[j]*w)/denorm-0.5*b/a;
+
+      db += 0.5*Math.log(denorm) 
+        - 0.5*Math.log(1.0+num[j]*w)
+        - 0.5*Math.log(a);
+
+      int halfNum = ((int)num[j])/2;
+      for(int z=1; z<=halfNum; z++)
+        db -= 1.0/(b+num[j]-2.0*(double)z);		
+      if(num[j]/2.0 > halfNum) // num[j] is odd
+        db -= 0.5*diffFstDervLnGamma(0.5*b);		
+
+      dw += 0.5*(b+num[j])*(a+sSq[j])*num[j]/denorm -
+        0.5*(b+num[j]-1.0)*num[j]/(1.0+num[j]*w);
+
+      dm += num[j]*(b+num[j])*(m-xBar[j])/denorm;
+    }
+
+    g[0] = da;
+    g[1] = db;
+    g[2] = dw;
+    g[3] = dm;
+    return g;
+  }
+
+  /**
+   * Subclass should implement this procedure to evaluate second-order
+   * gradient of the objective function
+   */
+  protected double[] evaluateHessian(double[] x, int index){
+    double[] h = new double[x.length];
+
+    // # of exemplars, # of dimensions
+    // which dimension and which variable for 'index'
+    int numExs = num.length;
+    double a,b,w,m;
+    // Take the 2nd-order derivative
+    switch(index){
+      case 0:  // a	   
+        a=x[0];b=x[1];w=x[2];m=x[3];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+          double denorm = (1.0+num[j]*w)*(a+sSq[j]) + 
+            num[j]*(xBar[j]-m)*(xBar[j]-m);
+
+          h[0] += 0.5*b/(a*a) 
+            - 0.5*(b+num[j])*(1.0+num[j]*w)*(1.0+num[j]*w)
+            /(denorm*denorm);
+
+          h[1] += 0.5*(1.0+num[j]*w)/denorm - 0.5/a;
+
+          h[2] += 0.5*num[j]*num[j]*(b+num[j])*
+            (xBar[j]-m)*(xBar[j]-m)/(denorm*denorm);
+
+          h[3] -= num[j]*(b+num[j])*(m-xBar[j])
+            *(1.0+num[j]*w)/(denorm*denorm);
+        }
+        break;
+
+      case 1: // b      
+        a=x[0];b=x[1];w=x[2];m=x[3];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+          double denorm = (1.0+num[j]*w)*(a+sSq[j]) + 
+            num[j]*(xBar[j]-m)*(xBar[j]-m);
+
+          h[0] += 0.5*(1.0+num[j]*w)/denorm - 0.5/a;
+
+          int halfNum = ((int)num[j])/2;
+          for(int z=1; z<=halfNum; z++)
+            h[1] += 
+              1.0/((b+num[j]-2.0*(double)z)*(b+num[j]-2.0*(double)z));
+          if(num[j]/2.0 > halfNum) // num[j] is odd
+            h[1] -= 0.25*diffSndDervLnGamma(0.5*b); 
+
+          h[2] += 0.5*(a+sSq[j])*num[j]/denorm -
+            0.5*num[j]/(1.0+num[j]*w);
+
+          h[3] += num[j]*(m-xBar[j])/denorm;
+        }
+        break;
+
+      case 2: // w   
+        a=x[0];b=x[1];w=x[2];m=x[3];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+          double denorm = (1.0+num[j]*w)*(a+sSq[j]) + 
+            num[j]*(xBar[j]-m)*(xBar[j]-m);
+
+          h[0] += 0.5*num[j]*num[j]*(b+num[j])*
+            (xBar[j]-m)*(xBar[j]-m)/(denorm*denorm);
+
+          h[1] += 0.5*(a+sSq[j])*num[j]/denorm -
+            0.5*num[j]/(1.0+num[j]*w);
+
+          h[2] += 0.5*(b+num[j]-1.0)*num[j]*num[j]/
+            ((1.0+num[j]*w)*(1.0+num[j]*w)) -
+            0.5*(b+num[j])*(a+sSq[j])*(a+sSq[j])*
+            num[j]*num[j]/(denorm*denorm);
+
+          h[3] -= num[j]*num[j]*(b+num[j])*
+            (m-xBar[j])*(a+sSq[j])/(denorm*denorm);
+        }
+        break;
+
+      case 3: // m
+        a=x[0];b=x[1];w=x[2];m=x[3];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+          double denorm = (1.0+num[j]*w)*(a+sSq[j]) + 
+            num[j]*(xBar[j]-m)*(xBar[j]-m);
+
+          h[0] -= num[j]*(b+num[j])*(m-xBar[j])
+            *(1.0+num[j]*w)/(denorm*denorm);
+
+          h[1] += num[j]*(m-xBar[j])/denorm;
+
+          h[2] -= num[j]*num[j]*(b+num[j])*
+            (m-xBar[j])*(a+sSq[j])/(denorm*denorm);
+
+          h[3] += num[j]*(b+num[j])*
+            ((1.0+num[j]*w)*(a+sSq[j])-
+             num[j]*(m-xBar[j])*(m-xBar[j]))
+            /(denorm*denorm);
+        }
+    }
+
+    return h;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5481 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/TLDSimple.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/TLDSimple.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/TLDSimple.java	(revision 29)
@@ -0,0 +1,1040 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TLDSimple.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.RandomizableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Optimization;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A simpler version of TLD, mu random but sigma^2 fixed and estimated via data.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Xin Xu (2003). Statistical learning in multiple instance problem. Hamilton, NZ.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;mastersthesis{Xu2003,
+ *    address = {Hamilton, NZ},
+ *    author = {Xin Xu},
+ *    note = {0657.594},
+ *    school = {University of Waikato},
+ *    title = {Statistical learning in multiple instance problem},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C
+ *  Set whether or not use empirical
+ *  log-odds cut-off instead of 0</pre>
+ * 
+ * <pre> -R &lt;numOfRuns&gt;
+ *  Set the number of multiple runs 
+ *  needed for searching the MLE.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5481 $ 
+ */
+public class TLDSimple 
+  extends RandomizableClassifier 
+  implements OptionHandler, MultiInstanceCapabilitiesHandler,
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 9040995947243286591L;
+  
+  /** The mean for each attribute of each positive exemplar */
+  protected double[][] m_MeanP = null;
+
+  /** The mean for each attribute of each negative exemplar */
+  protected double[][] m_MeanN = null;
+
+  /** The effective sum of weights of each positive exemplar in each dimension*/
+  protected double[][] m_SumP = null;
+
+  /** The effective sum of weights of each negative exemplar in each dimension*/
+  protected double[][] m_SumN = null;
+
+  /** Estimated sigma^2 in positive bags*/
+  protected double[] m_SgmSqP;
+
+  /** Estimated sigma^2 in negative bags*/
+  protected double[] m_SgmSqN;
+
+  /** The parameters to be estimated for each positive exemplar*/
+  protected double[] m_ParamsP = null;
+
+  /** The parameters to be estimated for each negative exemplar*/
+  protected double[] m_ParamsN = null;
+
+  /** The dimension of each exemplar, i.e. (numAttributes-2) */
+  protected int m_Dimension = 0;
+
+  /** The class label of each exemplar */
+  protected double[] m_Class = null;
+
+  /** The number of class labels in the data */
+  protected int m_NumClasses = 2;
+
+  /** The very small number representing zero */
+  static public double ZERO = 1.0e-12;   
+
+  protected int m_Run = 1;
+
+  protected double m_Cutoff;
+
+  protected boolean m_UseEmpiricalCutOff = false;    
+
+  private double[] m_LkRatio;
+
+  private Instances m_Attribute = null;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A simpler version of TLD, mu random but sigma^2 fixed and estimated "
+      + "via data.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MASTERSTHESIS);
+    result.setValue(Field.AUTHOR, "Xin Xu");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.TITLE, "Statistical learning in multiple instance problem");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, NZ");
+    result.setValue(Field.NOTE, "0657.594");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   *
+   * @param exs the training exemplars
+   * @throws Exception if the model cannot be built properly
+   */    
+  public void buildClassifier(Instances exs)throws Exception{
+    // can classifier handle the data?
+    getCapabilities().testWithFail(exs);
+
+    // remove instances with missing class
+    exs = new Instances(exs);
+    exs.deleteWithMissingClass();
+    
+    int numegs = exs.numInstances();
+    m_Dimension = exs.attribute(1).relation().numAttributes();
+    m_Attribute = exs.attribute(1).relation().stringFreeStructure();
+    Instances pos = new Instances(exs, 0), neg = new Instances(exs, 0);
+
+    // Divide into two groups
+    for(int u=0; u<numegs; u++){
+      Instance example = exs.instance(u);
+      if(example.classValue() == 1)
+        pos.add(example);
+      else
+        neg.add(example);
+    }	
+    int pnum = pos.numInstances(), nnum = neg.numInstances();	
+
+    // xBar, n
+    m_MeanP = new double[pnum][m_Dimension];
+    m_SumP = new double[pnum][m_Dimension];
+    m_MeanN = new double[nnum][m_Dimension];
+    m_SumN = new double[nnum][m_Dimension];
+    // w, m
+    m_ParamsP = new double[2*m_Dimension];
+    m_ParamsN = new double[2*m_Dimension];
+    // \sigma^2
+    m_SgmSqP = new double[m_Dimension];
+    m_SgmSqN = new double[m_Dimension];
+    // S^2
+    double[][] varP=new double[pnum][m_Dimension], 
+      varN=new double[nnum][m_Dimension];
+    // numOfEx 'e' without all missing
+    double[] effNumExP=new double[m_Dimension], 
+      effNumExN=new double[m_Dimension];
+    // For the starting values
+    double[] pMM=new double[m_Dimension], 
+      nMM=new double[m_Dimension],
+      pVM=new double[m_Dimension],
+      nVM=new double[m_Dimension];
+    // # of exemplars with only one instance
+    double[] numOneInsExsP=new double[m_Dimension],
+      numOneInsExsN=new double[m_Dimension];
+    // sum_i(1/n_i)
+    double[] pInvN = new double[m_Dimension], nInvN = new double[m_Dimension];
+
+    // Extract metadata from both positive and negative bags
+    for(int v=0; v < pnum; v++){
+      //Instance px = pos.instance(v);
+      Instances pxi =  pos.instance(v).relationalValue(1);
+      for (int k=0; k<pxi.numAttributes(); k++) {
+        m_MeanP[v][k] = pxi.meanOrMode(k);
+        varP[v][k] = pxi.variance(k);
+      }
+
+      for (int w=0,t=0; w < m_Dimension; w++,t++){		
+        //if((t==m_ClassIndex) || (t==m_IdIndex))
+        //  t++;	
+        if(varP[v][w] <= 0.0)
+          varP[v][w] = 0.0;
+        if(!Double.isNaN(m_MeanP[v][w])){
+
+          for(int u=0;u<pxi.numInstances();u++)
+            if(!pxi.instance(u).isMissing(t))			    
+              m_SumP[v][w] += pxi.instance(u).weight();
+
+          pMM[w] += m_MeanP[v][w];
+          pVM[w] += m_MeanP[v][w]*m_MeanP[v][w];		    
+          if((m_SumP[v][w]>1) && (varP[v][w]>ZERO)){	
+
+            m_SgmSqP[w] += varP[v][w]*(m_SumP[v][w]-1.0)/m_SumP[v][w];
+
+            //m_SgmSqP[w] += varP[v][w]*(m_SumP[v][w]-1.0);
+            effNumExP[w]++; // Not count exemplars with 1 instance
+            pInvN[w] += 1.0/m_SumP[v][w];
+            //pInvN[w] += m_SumP[v][w];
+          }
+          else
+            numOneInsExsP[w]++;
+        }
+
+      }			    
+    }
+
+
+    for(int v=0; v < nnum; v++){
+      //Instance nx = neg.instance(v);
+      Instances nxi = neg.instance(v).relationalValue(1);
+      for (int k=0; k<nxi.numAttributes(); k++) {
+        m_MeanN[v][k] = nxi.meanOrMode(k);
+        varN[v][k] = nxi.variance(k);
+      }
+      //Instances nxi =  nx.getInstances();
+
+      for (int w=0,t=0; w < m_Dimension; w++,t++){
+
+        //if((t==m_ClassIndex) || (t==m_IdIndex))
+        //  t++;	
+        if(varN[v][w] <= 0.0)
+          varN[v][w] = 0.0;
+        if(!Double.isNaN(m_MeanN[v][w])){
+          for(int u=0;u<nxi.numInstances();u++)
+            if(!nxi.instance(u).isMissing(t))
+              m_SumN[v][w] += nxi.instance(u).weight();	
+
+          nMM[w] += m_MeanN[v][w]; 
+          nVM[w] += m_MeanN[v][w]*m_MeanN[v][w];
+          if((m_SumN[v][w]>1) && (varN[v][w]>ZERO)){			
+            m_SgmSqN[w] += varN[v][w]*(m_SumN[v][w]-1.0)/m_SumN[v][w];
+            //m_SgmSqN[w] += varN[v][w]*(m_SumN[v][w]-1.0);
+            effNumExN[w]++; // Not count exemplars with 1 instance
+            nInvN[w] += 1.0/m_SumN[v][w];
+            //nInvN[w] += m_SumN[v][w];
+          }
+          else
+            numOneInsExsN[w]++;
+        }					
+      }
+    }
+
+    // Expected \sigma^2
+    /* if m_SgmSqP[u] or m_SgmSqN[u] is 0, assign 0 to sigma^2. 
+     * Otherwise, may cause k m_SgmSqP / m_SgmSqN to be NaN.
+     * Modified by Lin Dong (Sep. 2005)
+     */
+    for (int u=0; u < m_Dimension; u++){
+      // For exemplars with only one instance, use avg(\sigma^2) of other exemplars
+      if (m_SgmSqP[u]!=0)
+        m_SgmSqP[u] /= (effNumExP[u]-pInvN[u]);
+      else
+        m_SgmSqP[u] = 0;
+      if (m_SgmSqN[u]!=0)
+        m_SgmSqN[u] /= (effNumExN[u]-nInvN[u]);
+      else
+        m_SgmSqN[u] = 0;
+
+      //m_SgmSqP[u] /= (pInvN[u]-effNumExP[u]);
+      //m_SgmSqN[u] /= (nInvN[u]-effNumExN[u]);
+      effNumExP[u] += numOneInsExsP[u];
+      effNumExN[u] += numOneInsExsN[u];
+      pMM[u] /= effNumExP[u];
+      nMM[u] /= effNumExN[u];
+      pVM[u] = pVM[u]/(effNumExP[u]-1.0) - pMM[u]*pMM[u]*effNumExP[u]/(effNumExP[u]-1.0);
+      nVM[u] = nVM[u]/(effNumExN[u]-1.0) - nMM[u]*nMM[u]*effNumExN[u]/(effNumExN[u]-1.0);
+    }
+
+    //Bounds and parameter values for each run
+    double[][] bounds = new double[2][2];
+    double[] pThisParam = new double[2], 
+      nThisParam = new double[2];
+
+    // Initial values for parameters
+    double w, m;
+    Random whichEx = new Random(m_Seed);
+
+    // Optimize for one dimension
+    for (int x=0; x < m_Dimension; x++){     
+      // System.out.println("\n\n!!!!!!!!!!!!!!!!!!!!!!???Dimension #"+x);
+
+      // Positive examplars: first run 
+      pThisParam[0] = pVM[x];  // w
+      if( pThisParam[0] <= ZERO)
+        pThisParam[0] = 1.0;
+      pThisParam[1] = pMM[x];  // m
+
+      // Negative examplars: first run
+      nThisParam[0] = nVM[x];  // w
+      if(nThisParam[0] <= ZERO)
+        nThisParam[0] = 1.0;
+      nThisParam[1] = nMM[x];  // m
+
+      // Bound constraints
+      bounds[0][0] = ZERO; // w > 0
+      bounds[0][1] = Double.NaN;
+      bounds[1][0] = Double.NaN; 
+      bounds[1][1] = Double.NaN;
+
+      double pminVal=Double.MAX_VALUE, nminVal=Double.MAX_VALUE; 
+      TLDSimple_Optm pOp=null, nOp=null;	
+      boolean isRunValid = true;
+      double[] sumP=new double[pnum], meanP=new double[pnum];
+      double[] sumN=new double[nnum], meanN=new double[nnum];
+
+      // One dimension
+      for(int p=0; p<pnum; p++){
+        sumP[p] = m_SumP[p][x];
+        meanP[p] = m_MeanP[p][x];
+      }
+      for(int q=0; q<nnum; q++){
+        sumN[q] = m_SumN[q][x];
+        meanN[q] = m_MeanN[q][x];
+      }
+
+      for(int y=0; y<m_Run; y++){
+        //System.out.println("\n\n!!!!!!!!!Positive exemplars: Run #"+y);
+        double thisMin;
+        pOp = new TLDSimple_Optm();
+        pOp.setNum(sumP);
+        pOp.setSgmSq(m_SgmSqP[x]);
+        if (getDebug())
+          System.out.println("m_SgmSqP["+x+"]= " +m_SgmSqP[x]);
+        pOp.setXBar(meanP);
+        //pOp.setDebug(true);
+        pThisParam = pOp.findArgmin(pThisParam, bounds);
+        while(pThisParam==null){
+          pThisParam = pOp.getVarbValues();		    
+          if (getDebug())
+            System.out.println("!!! 200 iterations finished, not enough!");
+          pThisParam = pOp.findArgmin(pThisParam, bounds);
+        }	
+
+        thisMin = pOp.getMinFunction();
+        if(!Double.isNaN(thisMin) && (thisMin<pminVal)){
+          pminVal = thisMin;
+          for(int z=0; z<2; z++)
+            m_ParamsP[2*x+z] = pThisParam[z];
+        }
+
+        if(Double.isNaN(thisMin)){
+          pThisParam = new double[2];
+          isRunValid =false;
+        }
+        if(!isRunValid){ y--; isRunValid=true; } 
+
+        // Change the initial parameters and restart
+        int pone = whichEx.nextInt(pnum);
+
+        // Positive exemplars: next run 
+        while(Double.isNaN(m_MeanP[pone][x]))
+          pone = whichEx.nextInt(pnum);
+
+        m = m_MeanP[pone][x];
+        w = (m-pThisParam[1])*(m-pThisParam[1]);
+        pThisParam[0] = w;  // w
+        pThisParam[1] = m;  // m	    
+      }
+
+      for(int y=0; y<m_Run; y++){
+        //System.out.println("\n\n!!!!!!!!!Negative exemplars: Run #"+y);
+        double thisMin;
+        nOp = new TLDSimple_Optm();
+        nOp.setNum(sumN);
+        nOp.setSgmSq(m_SgmSqN[x]);
+        if (getDebug())
+          System.out.println(m_SgmSqN[x]);
+        nOp.setXBar(meanN);
+        //nOp.setDebug(true);
+        nThisParam = nOp.findArgmin(nThisParam, bounds);
+
+        while(nThisParam==null){	
+          nThisParam = nOp.getVarbValues();
+          if (getDebug())
+            System.out.println("!!! 200 iterations finished, not enough!");
+          nThisParam = nOp.findArgmin(nThisParam, bounds);
+        }			
+
+        thisMin = nOp.getMinFunction();	
+        if(!Double.isNaN(thisMin) && (thisMin<nminVal)){
+          nminVal = thisMin;
+          for(int z=0; z<2; z++)
+            m_ParamsN[2*x+z] = nThisParam[z];     
+        }
+
+        if(Double.isNaN(thisMin)){
+          nThisParam = new double[2];
+          isRunValid =false;
+        }
+
+        if(!isRunValid){ y--; isRunValid=true; } 		
+
+        // Change the initial parameters and restart	   	    
+        int none = whichEx.nextInt(nnum);// Randomly pick one pos. exmpl.
+
+        // Negative exemplars: next run 
+        while(Double.isNaN(m_MeanN[none][x]))
+          none = whichEx.nextInt(nnum);
+
+        m = m_MeanN[none][x];
+        w = (m-nThisParam[1])*(m-nThisParam[1]);
+        nThisParam[0] = w;  // w
+        nThisParam[1] = m;  // m	 		
+      }		  	    	    
+    }
+
+    m_LkRatio = new double[m_Dimension];
+
+    if(m_UseEmpiricalCutOff){	
+      // Find the empirical cut-off
+      double[] pLogOdds=new double[pnum], nLogOdds=new double[nnum];  
+      for(int p=0; p<pnum; p++)
+        pLogOdds[p] = 
+          likelihoodRatio(m_SumP[p], m_MeanP[p]);
+
+      for(int q=0; q<nnum; q++)
+        nLogOdds[q] = 
+          likelihoodRatio(m_SumN[q], m_MeanN[q]);
+
+      // Update m_Cutoff
+      findCutOff(pLogOdds, nLogOdds);
+    }
+    else
+      m_Cutoff = -Math.log((double)pnum/(double)nnum);
+
+    /* 
+       for(int x=0, y=0; x<m_Dimension; x++, y++){
+       if((x==exs.classIndex()) || (x==exs.idIndex()))
+       y++;
+
+       w=m_ParamsP[2*x]; m=m_ParamsP[2*x+1];
+       System.err.println("\n\n???Positive: ( "+exs.attribute(y)+
+       "):  w="+w+", m="+m+", sgmSq="+m_SgmSqP[x]);
+
+       w=m_ParamsN[2*x]; m=m_ParamsN[2*x+1];
+       System.err.println("???Negative: ("+exs.attribute(y)+
+       "):  w="+w+", m="+m+", sgmSq="+m_SgmSqN[x]+
+       "\nAvg. log-likelihood ratio in training data="
+       +(m_LkRatio[x]/(pnum+nnum)));
+       }	
+       */
+    if (getDebug())
+      System.err.println("\n\n???Cut-off="+m_Cutoff);
+  }        
+
+  /**
+   *
+   * @param ex the given test exemplar
+   * @return the classification 
+   * @throws Exception if the exemplar could not be classified
+   * successfully
+   */
+  public double classifyInstance(Instance ex)throws Exception{
+    //Instance ex = new Exemplar(e);
+    Instances exi = ex.relationalValue(1);
+    double[] n = new double[m_Dimension];
+    double [] xBar = new double[m_Dimension];
+    for (int i=0; i<exi.numAttributes() ; i++)
+      xBar[i] = exi.meanOrMode(i);
+
+    for (int w=0, t=0; w < m_Dimension; w++, t++){
+      // if((t==m_ClassIndex) || (t==m_IdIndex))
+      //t++;	
+      for(int u=0;u<exi.numInstances();u++)
+        if(!exi.instance(u).isMissing(t))
+          n[w] += exi.instance(u).weight();
+    }
+
+    double logOdds = likelihoodRatio(n, xBar);
+    return (logOdds > m_Cutoff) ? 1 : 0 ;
+  }
+  
+  /**
+   * Computes the distribution for a given exemplar
+   *
+   * @param ex the exemplar for which distribution is computed
+   * @return the distribution
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public double[] distributionForInstance(Instance ex) throws Exception {
+    
+    double[] distribution = new double[2];
+    Instances exi = ex.relationalValue(1);
+    double[] n = new double[m_Dimension];
+    double[] xBar = new double[m_Dimension];
+    for (int i = 0; i < exi.numAttributes() ; i++)
+      xBar[i] = exi.meanOrMode(i);
+    
+    for (int w = 0, t = 0; w < m_Dimension; w++, t++){
+      for (int u = 0; u < exi.numInstances(); u++)
+	if (!exi.instance(u).isMissing(t))
+	  n[w] += exi.instance(u).weight();
+    }
+    
+    double logOdds = likelihoodRatio(n, xBar);
+    
+    // returned logOdds value has been divided by m_Dimension to avoid 
+    // Math.exp(logOdds) getting too large or too small, 
+    // that may result in two fixed distribution value (1 or 0).
+    distribution[0] = 1 / (1 + Math.exp(logOdds)); // Prob. for class 0 (negative)
+    distribution[1] = 1 - distribution[0];
+    
+    return distribution;
+  }	
+
+  /**
+   * Compute the log-likelihood ratio
+   */
+  private double likelihoodRatio(double[] n, double[] xBar){	
+    double LLP = 0.0, LLN = 0.0;
+
+    for (int x=0; x<m_Dimension; x++){
+      if(Double.isNaN(xBar[x])) continue; // All missing values
+      //if(Double.isNaN(xBar[x]) || (m_ParamsP[2*x] <= ZERO) 
+      //  || (m_ParamsN[2*x]<=ZERO)) 
+      //	continue; // All missing values
+
+      //Log-likelihood for positive 
+      double w=m_ParamsP[2*x], m=m_ParamsP[2*x+1];
+      double llp = Math.log(w*n[x]+m_SgmSqP[x])
+        + n[x]*(m-xBar[x])*(m-xBar[x])/(w*n[x]+m_SgmSqP[x]);
+      LLP -= llp;
+
+      //Log-likelihood for negative 
+      w=m_ParamsN[2*x]; m=m_ParamsN[2*x+1]; 
+      double lln = Math.log(w*n[x]+m_SgmSqN[x])
+        + n[x]*(m-xBar[x])*(m-xBar[x])/(w*n[x]+m_SgmSqN[x]);
+      LLN -= lln;
+
+      m_LkRatio[x] += llp - lln;
+    }
+
+    return LLP - LLN / m_Dimension;
+  }
+
+  private void findCutOff(double[] pos, double[] neg){
+    int[] pOrder = Utils.sort(pos),
+      nOrder = Utils.sort(neg);
+    /*
+       System.err.println("\n\n???Positive: ");
+       for(int t=0; t<pOrder.length; t++)
+       System.err.print(t+":"+Utils.doubleToString(pos[pOrder[t]],0,2)+" ");
+       System.err.println("\n\n???Negative: ");
+       for(int t=0; t<nOrder.length; t++)
+       System.err.print(t+":"+Utils.doubleToString(neg[nOrder[t]],0,2)+" ");
+       */
+    int pNum = pos.length, nNum = neg.length, count, p=0, n=0;	
+    double fstAccu=0.0, sndAccu=(double)pNum, split; 
+    double maxAccu = 0, minDistTo0 = Double.MAX_VALUE;
+
+    // Skip continuous negatives	
+    for(;(n<nNum)&&(pos[pOrder[0]]>=neg[nOrder[n]]); n++, fstAccu++);
+
+    if(n>=nNum){ // totally seperate
+      m_Cutoff = (neg[nOrder[nNum-1]]+pos[pOrder[0]])/2.0;	
+      //m_Cutoff = neg[nOrder[nNum-1]];
+      return;  
+    }	
+
+    count=n;
+    while((p<pNum)&&(n<nNum)){
+      // Compare the next in the two lists
+      if(pos[pOrder[p]]>=neg[nOrder[n]]){ // Neg has less log-odds
+        fstAccu += 1.0;    
+        split=neg[nOrder[n]];
+        n++;	 
+      }
+      else{
+        sndAccu -= 1.0;
+        split=pos[pOrder[p]];
+        p++;
+      }	    	  
+      count++;
+      /*
+         double entropy=0.0, cover=(double)count;
+         if(fstAccu>0.0)
+         entropy -= fstAccu*Math.log(fstAccu/cover);
+         if(sndAccu>0.0)
+         entropy -= sndAccu*Math.log(sndAccu/(total-cover));
+
+         if(entropy < minEntropy){
+         minEntropy = entropy;
+      //find the next smallest
+      //double next = neg[nOrder[n]];
+      //if(pos[pOrder[p]]<neg[nOrder[n]])
+      //    next = pos[pOrder[p]];	
+      //m_Cutoff = (split+next)/2.0;
+      m_Cutoff = split;
+         }
+         */
+      if ((fstAccu+sndAccu > maxAccu) || 
+          ((fstAccu+sndAccu == maxAccu) && (Math.abs(split)<minDistTo0))){
+        maxAccu = fstAccu+sndAccu;
+        m_Cutoff = split;
+        minDistTo0 = Math.abs(split);
+     }	    
+    }		
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+          "\tSet whether or not use empirical\n"
+          + "\tlog-odds cut-off instead of 0",
+          "C", 0, "-C"));
+    
+    result.addElement(new Option(
+          "\tSet the number of multiple runs \n"
+          + "\tneeded for searching the MLE.",
+          "R", 1, "-R <numOfRuns>"));
+    
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      result.addElement(enu.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C
+   *  Set whether or not use empirical
+   *  log-odds cut-off instead of 0</pre>
+   * 
+   * <pre> -R &lt;numOfRuns&gt;
+   *  Set the number of multiple runs 
+   *  needed for searching the MLE.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception{
+    setDebug(Utils.getFlag('D', options));
+
+    setUsingCutOff(Utils.getFlag('C', options));
+
+    String runString = Utils.getOption('R', options);
+    if (runString.length() != 0) 
+      setNumRuns(Integer.parseInt(runString));
+    else 
+      setNumRuns(1);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getDebug())
+      result.add("-D");
+    
+    if (getUsingCutOff())
+      result.add("-C");
+
+    result.add("-R");
+    result.add("" + getNumRuns());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numRunsTipText() {
+    return "The number of runs to perform.";
+  }
+  
+  /**
+   * Sets the number of runs to perform.
+   *
+   * @param numRuns   the number of runs to perform
+   */
+  public void setNumRuns(int numRuns) {
+    m_Run = numRuns;
+  }
+
+  /**
+   * Returns the number of runs to perform.
+   *
+   * @return          the number of runs to perform
+   */
+  public int getNumRuns() {
+    return m_Run;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String usingCutOffTipText() {
+    return "Whether to use an empirical cutoff.";
+  }
+
+  /**
+   * Sets whether to use an empirical cutoff.
+   *
+   * @param cutOff      whether to use an empirical cutoff
+   */
+  public void setUsingCutOff (boolean cutOff) {
+    m_UseEmpiricalCutOff =cutOff;
+  }
+
+  /**
+   * Returns whether an empirical cutoff is used
+   *
+   * @return            true if an empirical cutoff is used
+   */
+  public boolean getUsingCutOff() {
+    return m_UseEmpiricalCutOff ;
+  }
+
+  /**
+   * Gets a string describing the classifier.
+   *
+   * @return a string describing the classifer built.
+   */
+  public String toString(){
+    StringBuffer text = new StringBuffer("\n\nTLDSimple:\n");
+    double sgm, w, m;
+    for (int x=0, y=0; x<m_Dimension; x++, y++){
+      // if((x==m_ClassIndex) || (x==m_IdIndex))
+      //y++;
+      sgm = m_SgmSqP[x];
+      w=m_ParamsP[2*x]; 
+      m=m_ParamsP[2*x+1];
+      text.append("\n"+m_Attribute.attribute(y).name()+"\nPositive: "+
+          "sigma^2="+sgm+", w="+w+", m="+m+"\n");
+      sgm = m_SgmSqN[x];
+      w=m_ParamsN[2*x]; 
+      m=m_ParamsN[2*x+1];
+      text.append("Negative: "+
+          "sigma^2="+sgm+", w="+w+", m="+m+"\n");
+    }
+
+    return text.toString();
+  }     
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5481 $");
+  }
+
+  /**
+   * Main method for testing.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new TLDSimple(), args);
+  }
+}
+
+class TLDSimple_Optm extends Optimization {
+
+  private double[] num;
+  private double sSq;
+  private double[] xBar;
+
+  public void setNum(double[] n) {num = n;}
+  public void setSgmSq(double s){
+
+    sSq = s;
+  }
+  public void setXBar(double[] x){xBar = x;}
+
+  /**
+   * Implement this procedure to evaluate objective
+   * function to be minimized
+   */
+  protected double objectiveFunction(double[] x){
+    int numExs = num.length;
+    double NLL=0; // Negative Log-Likelihood
+
+    double w=x[0], m=x[1]; 
+    for(int j=0; j < numExs; j++){
+
+      if(Double.isNaN(xBar[j])) continue; // All missing values
+      double bag=0; 
+
+      bag += Math.log(w*num[j]+sSq);
+
+      if(Double.isNaN(bag) && m_Debug){
+        System.out.println("???????????1: "+w+" "+m
+            +"|x-: "+xBar[j] + 
+            "|n: "+num[j] + "|S^2: "+sSq);
+        //System.exit(1);
+      }
+
+      bag += num[j]*(m-xBar[j])*(m-xBar[j])/(w*num[j]+sSq);	    	    
+      if(Double.isNaN(bag) && m_Debug){
+        System.out.println("???????????2: "+w+" "+m
+            +"|x-: "+xBar[j] + 
+            "|n: "+num[j] + "|S^2: "+sSq);
+        //System.exit(1);
+      }	    	       
+
+      //if(bag<0) bag=0;
+      NLL += bag;
+    }
+
+    //System.out.println("???????????NLL:"+NLL);
+    return NLL;
+  }
+
+  /**
+   * Subclass should implement this procedure to evaluate gradient
+   * of the objective function
+   */
+  protected double[] evaluateGradient(double[] x){
+    double[] g = new double[x.length];
+    int numExs = num.length;
+
+    double w=x[0],m=x[1];	
+    double dw=0.0, dm=0.0;
+
+    for(int j=0; j < numExs; j++){
+
+      if(Double.isNaN(xBar[j])) continue; // All missing values	    
+      dw += num[j]/(w*num[j]+sSq) 
+        - num[j]*num[j]*(m-xBar[j])*(m-xBar[j])/((w*num[j]+sSq)*(w*num[j]+sSq));
+
+      dm += 2.0*num[j]*(m-xBar[j])/(w*num[j]+sSq);
+    }
+
+    g[0] = dw;
+    g[1] = dm;
+    return g;
+  }
+
+  /**
+   * Subclass should implement this procedure to evaluate second-order
+   * gradient of the objective function
+   */
+  protected double[] evaluateHessian(double[] x, int index){
+    double[] h = new double[x.length];
+
+    // # of exemplars, # of dimensions
+    // which dimension and which variable for 'index'
+    int numExs = num.length;
+    double w,m;
+    // Take the 2nd-order derivative
+    switch(index){	
+      case 0: // w   
+        w=x[0];m=x[1];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+
+          h[0] += 2.0*Math.pow(num[j],3)*(m-xBar[j])*(m-xBar[j])/Math.pow(w*num[j]+sSq,3)
+            - num[j]*num[j]/((w*num[j]+sSq)*(w*num[j]+sSq));
+
+          h[1] -= 2.0*(m-xBar[j])*num[j]*num[j]/((num[j]*w+sSq)*(num[j]*w+sSq));		
+        }
+        break;
+
+      case 1: // m
+        w=x[0];m=x[1];
+
+        for(int j=0; j < numExs; j++){
+          if(Double.isNaN(xBar[j])) continue; //All missing values
+
+          h[0] -= 2.0*(m-xBar[j])*num[j]*num[j]/((num[j]*w+sSq)*(num[j]*w+sSq));
+
+          h[1] += 2.0*num[j]/(w*num[j]+sSq);				
+        }
+    }
+
+    return h;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5481 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIPolyKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIPolyKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIPolyKernel.java	(revision 29)
@@ -0,0 +1,197 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIPolyKernel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi.supportVector;
+
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+/**
+ <!-- globalinfo-start -->
+ * The polynomial kernel : K(x, y) = &lt;x, y&gt;^p or K(x, y) = (&lt;x, y&gt;+1)^p
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @author Lin Dong (ld21@cs.waikato.ac.nz) (MIkernel)
+ * @version $Revision: 5154 $ 
+ */
+public class MIPolyKernel 
+  extends PolyKernel 
+  implements MultiInstanceCapabilitiesHandler {
+
+  /** for serialiation */
+  private static final long serialVersionUID = 7926421479341051777L;
+
+  /**
+   * default constructor - does nothing.
+   */
+  public MIPolyKernel() {
+    super();
+  }
+
+  /**
+   * Creates a new <code>MIPolyKernel</code> instance.
+   * 
+   * @param data	the training dataset used.
+   * @param cacheSize	the size of the cache (a prime number)
+   * @param exponent	the exponent to use
+   * @param lowerOrder	whether to use lower-order terms
+   * @throws Exception	if something goes wrong
+   */
+  public MIPolyKernel(Instances data, int cacheSize, double exponent,
+      boolean lowerOrder) throws Exception {
+
+    super(data, cacheSize, exponent, lowerOrder);
+  }
+
+  /**
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected double evaluate(int id1, int id2, Instance inst1)
+    throws Exception {
+
+    double result, res;
+    Instances data1= new Instances(inst1.relationalValue(1));
+    Instances data2;
+    if(id1==id2)
+      data2= new Instances(data1);
+    else
+      data2 = new Instances (m_data.instance(id2).relationalValue(1));
+
+    res=0;
+    for(int i=0; i<data1.numInstances();i++){
+      for (int j=0; j<data2.numInstances(); j++){
+        result = dotProd(data1.instance(i), data2.instance(j));
+
+        // Use lower order terms?
+        if (getUseLowerOrder()) {
+          result += 1.0; 
+        }
+        if (getExponent() != 1.0) {
+          result = Math.pow(result, getExponent());
+        }
+
+        res += result;
+      }
+    }
+
+    return res;
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enableAllClasses();
+
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance kernel for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Frees the cache used by the kernel.
+   */
+  public void clean() {
+    m_storage = null;
+    m_keys = null;
+    m_kernelMatrix = null;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5154 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIRBFKernel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIRBFKernel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/mi/supportVector/MIRBFKernel.java	(revision 29)
@@ -0,0 +1,217 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MIRBFKernel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.mi.supportVector;
+
+import weka.classifiers.functions.supportVector.RBFKernel;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+/**
+ <!-- globalinfo-start -->
+ * The RBF kernel. K(x, y) = e^-(gamma * &lt;x-y, x-y&gt;^2)
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -G &lt;num&gt;
+ *  The Gamma parameter.
+ *  (default: 0.01)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
+ * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
+ * @author J. Lindgren (jtlindgr{at}cs.helsinki.fi) (RBF kernel)
+ * @author Lin Dong (ld21@cs.waikato.ac.nz) (MIkernel)
+ * @version $Revision: 1.4 $ 
+ */
+public class MIRBFKernel 
+  extends RBFKernel
+  implements MultiInstanceCapabilitiesHandler {
+
+  /** for serialiation */
+  private static final long serialVersionUID = -8711882393708956962L;
+  
+  /** The precalculated dotproducts of &lt;inst_i,inst_i&gt; */
+  protected double m_kernelPrecalc[][];
+
+  /**
+   * default constructor - does nothing.
+   */
+  public MIRBFKernel() {
+    super();
+  }
+
+  /**
+   * Constructor. 
+   * 
+   * @param data	the data to use
+   * @param cacheSize	the size of the cache
+   * @param gamma	the bandwidth
+   * @throws Exception	if something goes wrong
+   */
+  public MIRBFKernel(Instances data, int cacheSize, double gamma)
+    throws Exception {
+
+    super(data, cacheSize, gamma);
+  }
+
+  /**
+   * 
+   * @param id1   	the index of instance 1
+   * @param id2		the index of instance 2
+   * @param inst1	the instance 1 object
+   * @return 		the dot product
+   * @throws Exception 	if something goes wrong
+   */
+  protected double evaluate(int id1, int id2, Instance inst1)
+    throws Exception {
+
+    double result = 0;
+    Instances insts1, insts2;
+    if (id1 == -1)
+      insts1 = new Instances(inst1.relationalValue(1));
+    else
+      insts1 = new Instances(m_data.instance(id1).relationalValue(1));
+    insts2 = new Instances (m_data.instance(id2).relationalValue(1));
+
+    double precalc1=0;
+    for(int i = 0; i < insts1.numInstances(); i++){
+      for (int j = 0; j < insts2.numInstances(); j++){
+        if (id1 == -1)
+          precalc1 = dotProd(insts1.instance(i), insts1.instance(i));
+        else 
+          precalc1 =  m_kernelPrecalc[id1][i];
+
+        double res = Math.exp(m_gamma*(2. * dotProd(insts1.instance(i), insts2.instance(j)) -precalc1 -  m_kernelPrecalc[id2][j] ) );
+
+        result += res;
+      }
+    }
+
+    return result;
+  }   
+
+  /**
+   * initializes variables etc.
+   * 
+   * @param data	the data to use
+   */
+  protected void initVars(Instances data) {
+    super.initVars(data);
+    
+    m_kernelPrecalc = new double[data.numInstances()][];
+  }
+
+  /** 
+   * Returns the Capabilities of this kernel.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enableAllClasses();
+
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance kernel for the
+   * relational data.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // class
+    result.disableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * builds the kernel with the given data. Initializes the kernel cache. 
+   * The actual size of the cache in bytes is (64 * cacheSize).
+   * 
+   * @param data	the data to base the kernel on
+   * @throws Exception	if something goes wrong
+   */
+  public void buildKernel(Instances data) throws Exception {
+    // does kernel handle the data?
+    if (!getChecksTurnedOff())
+      getCapabilities().testWithFail(data);
+    
+    initVars(data);
+
+    for (int i = 0; i < data.numInstances(); i++){
+      Instances insts = new Instances(data.instance(i).relationalValue(1));
+      m_kernelPrecalc[i] = new double [insts.numInstances()];
+      for (int j = 0; j < insts.numInstances(); j++)
+        m_kernelPrecalc[i][j] = dotProd(insts.instance(j), insts.instance(j));
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/FLR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/FLR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/FLR.java	(revision 29)
@@ -0,0 +1,959 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FLR.java
+ *    Copyright (C) 2002 Ioannis N. Athanasiadis
+ *
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Fuzzy Lattice Reasoning Classifier (FLR) v5.0<br/>
+ * <br/>
+ * The Fuzzy Lattice Reasoning Classifier uses the notion of Fuzzy Lattices for creating a Reasoning Environment.<br/>
+ * The current version can be used for classification using numeric predictors.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * I. N. Athanasiadis, V. G. Kaburlasos, P. A. Mitkas, V. Petridis: Applying Machine Learning Techniques on Air Quality Data for Real-Time Decision Support. In: 1st Intl. NAISO Symposium on Information Technologies in Environmental Engineering (ITEE-2003), Gdansk, Poland, 2003.<br/>
+ * <br/>
+ * V. G. Kaburlasos, I. N. Athanasiadis, P. A. Mitkas, V. Petridis (2003). Fuzzy Lattice Reasoning (FLR) Classifier and its Application on Improved Estimation of Ambient Ozone Concentration.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Athanasiadis2003,
+ *    address = {Gdansk, Poland},
+ *    author = {I. N. Athanasiadis and V. G. Kaburlasos and P. A. Mitkas and V. Petridis},
+ *    booktitle = {1st Intl. NAISO Symposium on Information Technologies in Environmental Engineering (ITEE-2003)},
+ *    note = {Abstract in ICSC-NAISO Academic Press, Canada (ISBN:3906454339), pg.51},
+ *    publisher = {ICSC-NAISO Academic Press},
+ *    title = {Applying Machine Learning Techniques on Air Quality Data for Real-Time Decision Support},
+ *    year = {2003}
+ * }
+ * 
+ * &#64;unpublished{Kaburlasos2003,
+ *    author = {V. G. Kaburlasos and I. N. Athanasiadis and P. A. Mitkas and V. Petridis},
+ *    title = {Fuzzy Lattice Reasoning (FLR) Classifier and its Application on Improved Estimation of Ambient Ozone Concentration},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R
+ *  Set vigilance parameter rhoa.
+ *  (a float in range [0,1])</pre>
+ * 
+ * <pre> -B
+ *  Set boundaries File
+ *  Note:  The boundaries file is a simple text file containing 
+ *  a row with a Fuzzy Lattice defining the metric space.
+ *  For example, the boundaries file could contain the following 
+ *  the metric space for the iris dataset:
+ *  [ 4.3  7.9 ]  [ 2.0  4.4 ]  [ 1.0  6.9 ]  [ 0.1  2.5 ]  in Class:  -1
+ *  This lattice just contains the min and max value in each 
+ *  dimension.
+ *  In other kind of problems this may not be just a min-max 
+ *  operation, but it could contain limits defined by the problem 
+ *  itself.
+ *  Thus, this option should be set by the user.
+ *  If ommited, the metric space used contains the mins and maxs 
+ *  of the training split.</pre>
+ * 
+ * <pre> -Y
+ *  Show Rules</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * For further information contact I.N.Athanasiadis (ionathan@iti.gr)
+ *
+ * @author Ioannis N. Athanasiadis (email: ionathan@iti.gr, alias: ionathan@ieee.org)
+ * @version 5.0
+ * @version $Revision: 5928 $
+ */
+public class FLR
+    extends AbstractClassifier
+    implements Serializable, Summarizable, AdditionalMeasureProducer,
+               TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 3337906540579569626L;
+  
+  public static final float EPSILON = 0.000001f;
+  
+  /** the RuleSet: a vector keeping the learned Fuzzy Lattices */
+  private Vector learnedCode; 
+  /** a double keeping the vignilance parameter rhoa */
+  private double m_Rhoa = 0.5; 
+  /** a Fuzzy Lattice keeping the metric space */
+  private FuzzyLattice bounds; 
+  /** a File pointing to the boundaries file (bounds.txt) */
+  private File m_BoundsFile = new File(""); 
+  /** a flag indicating whether the RuleSet will be displayed */
+  private boolean m_showRules = true; 
+  /** an index of the RuleSet (keeps how many rules are needed for each class) */
+  private int index[]; 
+  /** an array of the names of the classes */
+  private String classNames[]; 
+
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds the FLR Classifier
+   *
+   * @param data the training dataset (Instances)
+       * @throws Exception if the training dataset is not supported or is erroneous
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    // Exceptions statements
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != data.classIndex()) {
+        AttributeStats stats = data.attributeStats(i);
+        if(data.numInstances()==stats.missingCount ||
+           Double.isNaN(stats.numericStats.min) ||
+           Double.isInfinite(stats.numericStats.min))
+          throw new Exception("All values are missing!" +
+              data.attribute(i).toString());
+      } //fi
+    } //for
+
+    if (!m_BoundsFile.canRead()) {
+      setBounds(data);
+    }
+    else
+      try {
+        BufferedReader in = new BufferedReader(new FileReader(m_BoundsFile));
+        String line = in.readLine();
+        bounds = new FuzzyLattice(line);
+      }
+      catch (Exception e) {
+        throw new Exception("Boundaries File structure error");
+      }
+
+    if (bounds.length() != data.numAttributes() - 1) {
+      throw new Exception("Incompatible bounds file!");
+    }
+    checkBounds();
+
+    // Variable Declerations and Initialization
+    index = new int[data.numClasses()];
+    classNames = new String[data.numClasses()];
+    for (int i = 0; i < data.numClasses(); i++) {
+      index[i] = 0;
+      classNames[i] = "missing Class Name";
+    }
+
+    double rhoa = m_Rhoa;
+    learnedCode = new Vector();
+    int searching;
+    FuzzyLattice inputBuffer;
+
+    // Build Classifier (Training phase)
+    if (data.firstInstance().classIsMissing())
+      throw new Exception("In first instance, class is missing!");
+
+    // set the first instance to be the first Rule in the model
+    FuzzyLattice Code = new FuzzyLattice(data.firstInstance(), bounds);
+    learnedCode.addElement(Code);
+    index[Code.getCateg()]++;
+    classNames[Code.getCateg()] = data.firstInstance().stringValue(data.
+        firstInstance().classIndex());
+    // training iteration
+    for (int i = 1; i < data.numInstances(); i++) { //for all instances
+      Instance inst = data.instance(i);
+      int flag =0;
+      for(int w=0;w<inst.numAttributes()-1;w++){
+        if(w!=inst.classIndex() && inst.isMissing(w))
+          flag=flag+1;
+      }
+      if (!inst.classIsMissing()&&flag!=inst.numAttributes()-1) {
+        inputBuffer = new FuzzyLattice( (Instance) data.instance(i), bounds);
+        double[] sigma = new double[ (learnedCode.size())];
+
+        for (int j = 0; j < learnedCode.size(); j++) {
+          FuzzyLattice num = (FuzzyLattice) learnedCode.get(j);
+          FuzzyLattice den = inputBuffer.join(num);
+          double numden = num.valuation(bounds) / den.valuation(bounds);
+          sigma[j] = numden;
+        } //for int j
+
+        do {
+          int winner = 0;
+          double winnerf = sigma[0];
+
+          for (int j = 1; j < learnedCode.size(); j++) {
+            if (winnerf < sigma[j]) {
+              winner = j;
+              winnerf = sigma[j];
+            } //if
+          } //for
+
+          FuzzyLattice num = inputBuffer;
+          FuzzyLattice winnerBox = (FuzzyLattice) learnedCode.get(winner);
+          FuzzyLattice den = winnerBox.join(num);
+          double numden = num.valuation(bounds) / den.valuation(bounds);
+
+          if ( (inputBuffer.getCateg() == winnerBox.getCateg()) &&
+              (rhoa < (numden))) {
+            learnedCode.setElementAt(winnerBox.join(inputBuffer), winner);
+            searching = 0;
+          }
+          else {
+            sigma[winner] = 0;
+            rhoa += EPSILON;
+            searching = 0;
+            for (int j = 0; j < learnedCode.size(); j++) {
+              if (sigma[j] != 0.0) {
+                searching = 1;
+              } //fi
+            } //for
+
+            if (searching == 0) {
+              learnedCode.addElement(inputBuffer);
+              index[inputBuffer.getCateg()]++;
+              classNames[inputBuffer.getCateg()] = data.instance(i).stringValue(
+                  data.instance(i).classIndex());
+            } //fi
+          } //else
+        }
+        while (searching == 1);
+      } //if Class is missing
+
+    } //for all instances
+  } //buildClassifier
+
+  /**
+   * Classifies a given instance using the FLR Classifier model
+   *
+   * @param instance the instance to be classified
+   * @return the class index into which the instance is classfied
+   */
+
+  public double classifyInstance(Instance instance) {
+
+    FuzzyLattice num, den, inputBuffer;
+    inputBuffer = new FuzzyLattice(instance, bounds); // transform instance to fuzzy lattice
+
+    // calculate excitations and winner
+    double[] sigma = new double[ (learnedCode.size())];
+    for (int j = 0; j < learnedCode.size(); j++) {
+      num = (FuzzyLattice) learnedCode.get(j);
+      den = inputBuffer.join(num);
+      sigma[j] = (num.valuation(bounds) / den.valuation(bounds));
+    } //for j
+
+    //find the winner Code (hyperbox)
+    int winner = 0;
+    double winnerf = sigma[0];
+    for (int j = 1; j < learnedCode.size(); j++) {
+      if (winnerf < sigma[j]) {
+        winner = j;
+        winnerf = sigma[j];
+      } //fi
+    } //for j
+
+    FuzzyLattice currentBox = (FuzzyLattice) learnedCode.get(winner);
+    return (double) currentBox.getCateg();
+  } //classifyInstance
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return String describing the FLR model
+   */
+  public String toString() {
+    if (learnedCode != null) {
+      String output = "";
+      output = "FLR classifier\n=======================\n Rhoa = " + m_Rhoa;
+      if (m_showRules) {
+        output = output + "\n Extracted Rules (Fuzzy Lattices):\n\n";
+        output = output + showRules();
+        output = output + "\n\n Metric Space:\n" + bounds.toString();
+      }
+      output = output + "\n Total Number of Rules:    " + learnedCode.size() +
+          "\n";
+      for (int i = 0; i < index.length; i++) {
+        output = output + " Rules pointing in Class " + classNames[i] + " :" +
+            index[i] + "\n";
+      }
+      return output;
+    }
+    else {
+      String output = "FLR classifier\n=======================\n Rhoa = " +
+          m_Rhoa;
+      output = output + "No model built";
+      return output;
+    }
+  } //toString
+
+  /**
+   * Returns a superconcise version of the model
+   *
+   * @return String descibing the FLR model very shortly
+   */
+  public String toSummaryString() {
+    String output = "";
+    if (learnedCode == null) {
+      output += "No model built";
+    }
+    else {
+      output = output + "Total Number of Rules: " + learnedCode.size();
+    }
+    return output;
+  } //toSummaryString
+
+  /**
+   * Returns the induced set of Fuzzy Lattice Rules
+   *
+   * @return String containing the ruleset
+   *
+   */
+  public String showRules() {
+    String output = "";
+    for (int i = 0; i < learnedCode.size(); i++) {
+      FuzzyLattice Code = (FuzzyLattice) learnedCode.get(i);
+      output = output + "Rule: " + i + " " + Code.toString();
+    }
+    return output;
+  } //showRules
+
+  /**
+   * Returns an enumeration describing the available options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R
+   *  Set vigilance parameter rhoa.
+   *  (a float in range [0,1])</pre>
+   * 
+   * <pre> -B
+   *  Set boundaries File
+   *  Note:  The boundaries file is a simple text file containing 
+   *  a row with a Fuzzy Lattice defining the metric space.
+   *  For example, the boundaries file could contain the following 
+   *  the metric space for the iris dataset:
+   *  [ 4.3  7.9 ]  [ 2.0  4.4 ]  [ 1.0  6.9 ]  [ 0.1  2.5 ]  in Class:  -1
+   *  This lattice just contains the min and max value in each 
+   *  dimension.
+   *  In other kind of problems this may not be just a min-max 
+   *  operation, but it could contain limits defined by the problem 
+   *  itself.
+   *  Thus, this option should be set by the user.
+   *  If ommited, the metric space used contains the mins and maxs 
+   *  of the training split.</pre>
+   * 
+   * <pre> -Y
+   *  Show Rules</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @return enumeration an enumeration of valid options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+	"\tSet vigilance parameter rhoa.\n"
+	+ "\t(a float in range [0,1])", 
+	"R", 1, "-R"));
+    
+    newVector.addElement(new Option(
+	"\tSet boundaries File\n"
+	+ "\tNote:  The boundaries file is a simple text file containing \n"
+	+ "\ta row with a Fuzzy Lattice defining the metric space.\n"
+	+ "\tFor example, the boundaries file could contain the following \n"
+	+ "\tthe metric space for the iris dataset:\n"
+	+ "\t[ 4.3  7.9 ]  [ 2.0  4.4 ]  [ 1.0  6.9 ]  [ 0.1  2.5 ]  in Class:  -1\n"
+	+ "\tThis lattice just contains the min and max value in each \n"
+	+ "\tdimension.\n"
+	+ "\tIn other kind of problems this may not be just a min-max \n"
+	+ "\toperation, but it could contain limits defined by the problem \n"
+	+ "\titself.\n"
+	+ "\tThus, this option should be set by the user.\n"
+	+ "\tIf ommited, the metric space used contains the mins and maxs \n"
+	+ "\tof the training split.", 
+	"B", 1, "-B"));
+
+    newVector.addElement(new Option(
+	"\tShow Rules", 
+	"Y", 0, "-Y"));
+    
+    return newVector.elements();
+  } //listOptions
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R
+   *  Set vigilance parameter rhoa.
+   *  (a float in range [0,1])</pre>
+   * 
+   * <pre> -B
+   *  Set boundaries File
+   *  Note:  The boundaries file is a simple text file containing 
+   *  a row with a Fuzzy Lattice defining the metric space.
+   *  For example, the boundaries file could contain the following 
+   *  the metric space for the iris dataset:
+   *  [ 4.3  7.9 ]  [ 2.0  4.4 ]  [ 1.0  6.9 ]  [ 0.1  2.5 ]  in Class:  -1
+   *  This lattice just contains the min and max value in each 
+   *  dimension.
+   *  In other kind of problems this may not be just a min-max 
+   *  operation, but it could contain limits defined by the problem 
+   *  itself.
+   *  Thus, this option should be set by the user.
+   *  If ommited, the metric space used contains the mins and maxs 
+   *  of the training split.</pre>
+   * 
+   * <pre> -Y
+   *  Show Rules</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported (
+   */
+  public void setOptions(String[] options) throws Exception {
+    // Option -Y
+    m_showRules = Utils.getFlag('Y', options);
+    // Option -R
+    String rhoaString = Utils.getOption('R', options);
+    if (rhoaString.length() != 0) {
+      m_Rhoa = Double.parseDouble(rhoaString);
+      if (m_Rhoa < 0 || m_Rhoa > 1) {
+        throw new Exception(
+            "Vigilance parameter (rhoa) should be a real number in range [0,1]");
+      }
+    }
+    else
+      m_Rhoa = 0.5;
+
+      // Option -B
+    String boundsString = Utils.getOption('B', options);
+    if (boundsString.length() != 0) {
+      m_BoundsFile = new File(boundsString);
+    } //fi
+    Utils.checkForRemainingOptions(options);
+  } //setOptions
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    String[] options = new String[5];
+    int current = 0;
+    options[current++] = "-R";
+    options[current++] = "" + getRhoa();
+    if (m_showRules) {
+      options[current++] = "-Y";
+    }
+    if (m_BoundsFile.toString() != "") {
+      options[current++] = "-B";
+      options[current++] = "" + getBoundsFile();
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  } // getOptions
+
+  /**
+   * Get rhoa
+   * @return the value of this parameter
+   */
+  public double getRhoa() {
+    return m_Rhoa;
+  }
+
+  /**
+   * Get boundaries File
+   * @return the value of this parameter
+   */
+  public String getBoundsFile() {
+    return m_BoundsFile.toString();
+  }
+
+  /**
+   * Get ShowRules parameter
+   * @return the value of this parameter
+   */
+  public boolean getShowRules() {
+    return m_showRules;
+  }
+
+  /**
+   * Set rhoa
+   * @param newRhoa sets the rhoa value
+   * @throws Exception if rhoa is not in range [0,1]
+   */
+  public void setRhoa(double newRhoa) throws Exception {
+    if (newRhoa < 0 || newRhoa > 1) {
+      throw new Exception(
+          "Vigilance parameter (rhoa) should be a real number in range [0,1]!!!");
+    }
+    m_Rhoa = newRhoa;
+  }
+
+  /**
+   * Set Boundaries File
+   * @param newBoundsFile a new file containing the boundaries
+   */
+  public void setBoundsFile(String newBoundsFile) {
+    m_BoundsFile = new File(newBoundsFile);
+  }
+
+  /**
+   * Set ShowRules flag
+   * @param flag the new value of this parameter
+   */
+  public void setShowRules(boolean flag) {
+    m_showRules = flag;
+  }
+
+  /**
+   * Sets the metric space from the training set using the min-max stats, in case -B option is not used.
+   * @param data is the training set
+   */
+  public void setBounds(Instances data) {
+    // Initialize minmax stats
+    bounds = new FuzzyLattice(data.numAttributes() - 1);
+    int k = 0;
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i != data.classIndex()) {
+        AttributeStats stats = data.attributeStats(i);
+        bounds.setMin(k, stats.numericStats.min);
+        bounds.setMax(k, stats.numericStats.max);
+        k = k + 1;
+      } //if
+    } //for
+  } //setBounds
+
+  /**
+   * Checks the metric space
+   */
+  public void checkBounds() {
+    for (int i = 0; i < bounds.length(); i++) {
+      if (bounds.getMin(i) == bounds.getMax(i))
+        bounds.setMax(i, bounds.getMax(i) + EPSILON);
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String rhoaTipText() {
+    return " The vigilance parameter value" + " (default = 0.75)";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String boundsFileTipText() {
+    return " Point the filename containing the metric space";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String showRulesTipText() {
+    return " If true, displays the ruleset.";
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    }
+    else {
+      throw new IllegalArgumentException(additionalMeasureName +
+                                         " not supported (FLR)");
+    }
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Additional measure Number of Rules
+   * @return the number of rules induced
+   */
+  public double measureNumRules() {
+    if (learnedCode == null)
+      return 0.0;
+    else
+      return (double) learnedCode.size();
+  }
+
+  /**
+   * Returns a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   * @return the description
+   */
+  public String globalInfo() {
+    return 
+        "Fuzzy Lattice Reasoning Classifier (FLR) v5.0\n\n"
+      + "The Fuzzy Lattice Reasoning Classifier uses the notion of Fuzzy "
+      + "Lattices for creating a Reasoning Environment.\n"
+      + "The current version can be used for classification using numeric predictors.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+   
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "I. N. Athanasiadis and V. G. Kaburlasos and P. A. Mitkas and V. Petridis");
+    result.setValue(Field.TITLE, "Applying Machine Learning Techniques on Air Quality Data for Real-Time Decision Support");
+    result.setValue(Field.BOOKTITLE, "1st Intl. NAISO Symposium on Information Technologies in Environmental Engineering (ITEE-2003)");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.ADDRESS, "Gdansk, Poland");
+    result.setValue(Field.PUBLISHER, "ICSC-NAISO Academic Press");
+    result.setValue(Field.NOTE, "Abstract in ICSC-NAISO Academic Press, Canada (ISBN:3906454339), pg.51");
+    
+    additional = result.add(Type.UNPUBLISHED);
+    additional.setValue(Field.AUTHOR, "V. G. Kaburlasos and I. N. Athanasiadis and P. A. Mitkas and V. Petridis");
+    additional.setValue(Field.TITLE, "Fuzzy Lattice Reasoning (FLR) Classifier and its Application on Improved Estimation of Ambient Ozone Concentration");
+    additional.setValue(Field.YEAR, "2003");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain command line arguments for evaluation
+   * (see Evaluation).
+   */
+
+  public static void main(String[] args) {
+    runClassifier(new FLR(), args);
+  }
+
+  /**
+   * <p>Fuzzy Lattice implementation in WEKA </p>
+   *
+   * @author Ioannis N. Athanasiadis
+   * email: ionathan@iti.gr
+   * alias: ionathan@ieee.org
+   * @version 5.0
+   */
+  private class FuzzyLattice
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -3568003680327062404L;
+    
+    private double min[];
+    private double max[];
+    private int categ;
+    private String className;
+
+    //Constructors
+
+    /**
+     * Constructs a Fuzzy Lattice from a instance
+     * @param dR the instance
+     * @param bounds the boundaries file
+     */
+    public FuzzyLattice(Instance dR, FuzzyLattice bounds) {
+      min = new double[dR.numAttributes() - 1];
+      max = new double[dR.numAttributes() - 1];
+      int k = 0;
+      for (int i = 0; i < dR.numAttributes(); i++) {
+        if (i != dR.classIndex()) {
+          if (!dR.isMissing(i)) {
+            min[k] = (dR.value(i) > bounds.getMin(k)) ? dR.value(i) :
+                bounds.getMin(k);
+            max[k] = (dR.value(i) < bounds.getMax(k)) ? dR.value(i) :
+                bounds.getMax(k);
+            k = k + 1;
+          } //if(!dR.isMissing(i))
+          else {
+            min[k] = bounds.getMax(k);
+            max[k] = bounds.getMin(k);
+            k = k + 1;
+          } //else
+        } //if(i!=dR.classIndex())
+      } //for (int i=0; i<dR.numAttributes();i++)
+      categ = (int) dR.value(dR.classIndex());
+      className = dR.stringValue(dR.classIndex());
+    } //FuzzyLattice
+
+    /**
+     * Constructs an empty Fuzzy Lattice of a specific dimension pointing
+     * in Class "Metric Space" (-1)
+     * @param length the dimention of the Lattice
+     */
+    public FuzzyLattice(int length) {
+      min = new double[length];
+      max = new double[length];
+
+      for (int i = 0; i < length; i++) {
+        min[i] = 0;
+        max[i] = 0;
+      }
+      categ = -1;
+      className = "Metric Space";
+    }
+
+    /**
+     * Converts a String to a Fuzzy Lattice pointing in Class "Metric Space" (-1)
+     * Note that the input String should be compatible with the toString() method.
+     * @param rule the input String.
+     */
+    public FuzzyLattice(String rule) {
+      int size = 0;
+      for (int i = 0; i < rule.length(); i++) {
+        String s = rule.substring(i, i + 1);
+        if (s.equalsIgnoreCase("[")) {
+          size++;
+        }
+      }
+      min = new double[size];
+      max = new double[size];
+
+      int i = 0;
+      int k = 0;
+      String temp = "";
+      int s = 0;
+      do {
+        String character = rule.substring(s, s + 1);
+        temp = temp + character;
+        if (character.equalsIgnoreCase(" ")) {
+          if (!temp.equalsIgnoreCase(" ")) {
+            k = k + 1;
+            if (k % 4 == 2) {
+              min[i] = Double.parseDouble(temp);
+            } //if
+            else if (k % 4 == 3) {
+              max[i] = Double.parseDouble(temp);
+              i = i + 1;
+            } //else
+          } // if (!temp.equalsIgnoreCase(" ") ){
+          temp = "";
+        } //if (character.equalsIgnoreCase(seperator)){
+        s = s + 1;
+      }
+      while (i < size);
+      categ = -1;
+      className = "Metric Space";
+    }
+
+    // Functions
+
+    /**
+     * Calculates the valuation function of the FuzzyLattice
+     * @param bounds corresponding boundaries
+     * @return the value of the valuation function
+     */
+    public double valuation(FuzzyLattice bounds) {
+      double resp = 0.0;
+      for (int i = 0; i < min.length; i++) {
+        resp += 1 -
+            (min[i] - bounds.getMin(i)) / (bounds.getMax(i) - bounds.getMin(i));
+        resp += (max[i] - bounds.getMin(i)) /
+            (bounds.getMax(i) - bounds.getMin(i));
+      }
+      return resp;
+    }
+
+    /**
+     * Calcualtes the length of the FuzzyLattice
+     * @return the length
+     */
+    public int length() {
+      return min.length;
+    }
+
+    /**
+     * Implements the Join Function
+     * @param lattice the second fuzzy lattice
+     * @return the joint lattice
+     */
+    public FuzzyLattice join(FuzzyLattice lattice) { // Lattice Join
+      FuzzyLattice b = new FuzzyLattice(lattice.length());
+      int i;
+      for (i = 0; i < lattice.min.length; i++) {
+        b.min[i] = (lattice.min[i] < min[i]) ? lattice.min[i] :
+            min[i];
+        b.max[i] = (lattice.max[i] > max[i]) ? lattice.max[i] :
+            max[i];
+      }
+      b.categ = categ;
+      b.className = className;
+      return b;
+    }
+
+    // Get-Set Functions
+
+    public int getCateg() {
+      return categ;
+    }
+
+    public void setCateg(int i) {
+      categ = i;
+    }
+
+    public String getClassName() {
+      return className;
+    }
+
+    public void setClassName(String s) {
+      className = s;
+    }
+
+    public double getMin(int i) {
+      return min[i];
+    }
+
+    public double getMax(int i) {
+      return max[i];
+    }
+
+    public void setMin(int i, double val) {
+      min[i] = val;
+    }
+
+    public void setMax(int i, double val) {
+      max[i] = val;
+    }
+
+    /**
+     * Returns a description of the Fuzzy Lattice
+     * @return the Fuzzy Lattice and the corresponding Class
+     */
+    public String toString() {
+      String rule = "";
+      for (int i = 0; i < min.length; i++) {
+        rule = rule + "[ " + min[i] + "  " + max[i] + " ]  ";
+      }
+      rule = rule + "in Class:  " + className + " \n";
+      return rule;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/HyperPipes.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/HyperPipes.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/HyperPipes.java	(revision 29)
@@ -0,0 +1,389 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HyperPipes.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing a HyperPipe classifier. For each category a HyperPipe is constructed that contains all points of that category (essentially records the attribute bounds observed for each category). Test instances are classified according to the category that "most contains the instance".<br/>
+ * Does not handle numeric class, or missing values in test cases. Extremely simple algorithm, but has the advantage of being extremely fast, and works quite well when you have "smegloads" of attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lucio de Souza Coelho (lucio@intelligenesis.net)
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5928 $
+ */ 
+public class HyperPipes 
+  extends AbstractClassifier {
+
+  /** for serialization */
+  static final long serialVersionUID = -7527596632268975274L;
+  
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  /** The structure of the training data */
+  protected Instances m_Instances;
+
+  /** Stores the HyperPipe for each class */
+  protected HyperPipe [] m_HyperPipes;
+
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Class implementing a HyperPipe classifier. For each category a "
+    +  "HyperPipe is constructed that contains all points of that category "
+      + "(essentially records the attribute bounds observed for each category). "
+      + "Test instances are classified according to the category that \"most "
+      + "contains the instance\".\n" 
+      + "Does not handle numeric class, or missing values in test cases. Extremely "
+      + "simple algorithm, but has the advantage of being extremely fast, and "
+      + "works quite well when you have \"smegloads\" of attributes.";
+  }
+
+  /**
+   * Represents an n-dimensional structure that bounds all instances 
+   * passed to it (generally all of a given class value).
+   */
+  class HyperPipe 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 3972254260367902025L;
+
+    /** Contains the numeric bounds of all instances in the HyperPipe */
+    protected double [][] m_NumericBounds;
+
+    /** Contains the nominal bounds of all instances in the HyperPipe */
+    protected boolean [][] m_NominalBounds;
+
+    /**
+     * Creates the HyperPipe as the n-dimensional parallel-piped 
+     * with minimum volume containing all the points in
+     * pointSet.
+     *
+     * @param instances all instances belonging to the same class
+     * @throws Exception if missing values are found
+     */
+    public HyperPipe(Instances instances) throws Exception {
+      
+      m_NumericBounds = new double [instances.numAttributes()][];
+      m_NominalBounds = new boolean [instances.numAttributes()][];
+
+      for (int i = 0; i < instances.numAttributes(); i++) {
+	switch (instances.attribute(i).type()) {
+	case Attribute.NUMERIC:
+	  m_NumericBounds[i] = new double [2];
+	  m_NumericBounds[i][0] = Double.POSITIVE_INFINITY;
+	  m_NumericBounds[i][1] = Double.NEGATIVE_INFINITY;
+	  break;
+	case Attribute.NOMINAL:
+	  m_NominalBounds[i] = new boolean [instances.attribute(i).numValues()];
+	  break;
+	default:
+	  throw new UnsupportedAttributeTypeException("Cannot process string attributes!");
+	}
+      }
+
+      for (int i = 0; i < instances.numInstances(); i++) {
+	addInstance(instances.instance(i));
+      }
+    }
+
+
+    /**
+     * Updates the bounds arrays with a single instance. Missing values
+     * are ignored (i.e. they don't change the bounds for that attribute)
+     *
+     * @param instance the instance
+     * @throws Exception if any missing values are encountered
+     */
+    public void addInstance(Instance instance) throws Exception {
+
+      for (int j = 0; j < instance.numAttributes(); j++) {
+	if ((j != m_ClassIndex) && (!instance.isMissing(j))) {
+
+	  double current = instance.value(j);
+
+	  if (m_NumericBounds[j] != null) { // i.e. a numeric attribute
+	    if (current < m_NumericBounds[j][0])
+	      m_NumericBounds[j][0] = current;
+	    if (current > m_NumericBounds[j][1])
+	      m_NumericBounds[j][1] = current;
+
+	  } else { // i.e. a nominal attribute
+	    m_NominalBounds[j][(int) current] = true;
+	  }
+	}
+      }
+    }
+
+
+    /**
+     * Returns the fraction of the dimensions of a given instance with
+     * values lying within the corresponding bounds of the HyperPipe.
+     *
+     * @param instance the instance
+     * @return the fraction of dimensions
+     * @throws Exception if any missing values are encountered
+     */
+    public double partialContains(Instance instance) throws Exception {
+      
+      int count = 0;
+      for (int i = 0; i < instance.numAttributes(); i++) {
+
+	if (i == m_ClassIndex) {
+	  continue;
+	}
+	if (instance.isMissing(i)) {
+	  continue;
+	}
+
+	double current = instance.value(i);
+
+	if (m_NumericBounds[i] != null) { // i.e. a numeric attribute
+	  if ((current >= m_NumericBounds[i][0]) 
+	      && (current <= m_NumericBounds[i][1])) {
+	    count++;
+	  }
+	} else { // i.e. a nominal attribute
+	  if (m_NominalBounds[i][(int) current]) {
+	    count++;
+	  }
+	}
+      }
+
+      return ((double)count) / (instance.numAttributes() - 1);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (instances.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(instances);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    m_ClassIndex = instances.classIndex();
+    m_Instances = new Instances(instances, 0); // Copy the structure for ref
+
+    // Create the HyperPipe for each class
+    m_HyperPipes = new HyperPipe [instances.numClasses()];
+    for (int i = 0; i < m_HyperPipes.length; i++) {
+      m_HyperPipes[i] = new HyperPipe(new Instances(instances, 0));
+    }
+
+    // Add the instances
+    for (int i = 0; i < instances.numInstances(); i++) {
+      updateClassifier(instances.instance(i));
+    }
+  }
+
+
+  /**
+   * Updates the classifier.
+   *
+   * @param instance the instance to be put into the classifier
+   * @throws Exception if the instance could not be included successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+  
+    if (instance.classIsMissing()) {
+      return;
+    }
+    m_HyperPipes[(int) instance.classValue()].addInstance(instance);
+  }
+
+
+  /**
+   * Classifies the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted class for the instance 
+   * @throws Exception if the instance can't be classified
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+        
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    double [] dist = new double[m_HyperPipes.length];
+
+    for (int j = 0; j < m_HyperPipes.length; j++) {
+      dist[j] = m_HyperPipes[j].partialContains(instance);
+    }
+
+    double sum = Utils.sum(dist);
+    if (sum <= 0) {
+      for (int j = 0; j < dist.length; j++) {
+	dist[j] = 1.0 / (double)dist.length;
+      }
+      return dist;
+    } else {
+      Utils.normalize(dist, sum);
+      return dist;
+    }
+  }
+
+
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_HyperPipes == null) {
+      return ("HyperPipes classifier");
+    }
+
+    StringBuffer text = new StringBuffer("HyperPipes classifier\n");
+
+    /* Perhaps print out the bounds for each HyperPipe.
+    for (int i = 0; i < m_HyperPipes.length; i++) {
+      text.append("HyperPipe for class: " 
+		  + m_Instances.attribute(m_ClassIndex).value(i) + "\n");
+      text.append(m_HyperPipes[i] + "\n\n");
+    }
+    */
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line arguments for evaluation
+   * (see Evaluation).
+   */
+  public static void main(String [] argv) {
+    runClassifier(new HyperPipes(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/OSDL.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/OSDL.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/OSDL.java	(revision 29)
@@ -0,0 +1,211 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OSDL.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.misc.monotone.OSDLCore;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * This class is an implementation of the Ordinal Stochastic Dominance Learner.<br/>
+ * Further information regarding the OSDL-algorithm can be found in:<br/>
+ * <br/>
+ * S. Lievens, B. De Baets, K. Cao-Van (2006). A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting. Annals of Operations Research..<br/>
+ * <br/>
+ * Kim Cao-Van (2003). Supervised ranking: from semantics to algorithms.<br/>
+ * <br/>
+ * Stijn Lievens (2004). Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken.<br/>
+ * <br/>
+ * For more information about supervised ranking, see<br/>
+ * <br/>
+ * http://users.ugent.be/~slievens/supervised_ranking.php
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Lievens2006,
+ *    author = {S. Lievens and B. De Baets and K. Cao-Van},
+ *    journal = {Annals of Operations Research},
+ *    title = {A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting},
+ *    year = {2006}
+ * }
+ * 
+ * &#64;phdthesis{Cao-Van2003,
+ *    author = {Kim Cao-Van},
+ *    school = {Ghent University},
+ *    title = {Supervised ranking: from semantics to algorithms},
+ *    year = {2003}
+ * }
+ * 
+ * &#64;mastersthesis{Lievens2004,
+ *    author = {Stijn Lievens},
+ *    school = {Ghent University},
+ *    title = {Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -C &lt;REG|WSUM|MAX|MED|RMED&gt;
+ *  Sets the classification type to be used.
+ *  (Default: MED)</pre>
+ * 
+ * <pre> -B
+ *  Use the balanced version of the Ordinal Stochastic Dominance Learner</pre>
+ * 
+ * <pre> -W
+ *  Use the weighted version of the Ordinal Stochastic Dominance Learner</pre>
+ * 
+ * <pre> -S &lt;value of interpolation parameter&gt;
+ *  Sets the value of the interpolation parameter (not with -W/T/P/L/U)
+ *  (default: 0.5).</pre>
+ * 
+ * <pre> -T
+ *  Tune the interpolation parameter (not with -W/S)
+ *  (default: off)</pre>
+ * 
+ * <pre> -L &lt;Lower bound for interpolation parameter&gt;
+ *  Lower bound for the interpolation parameter (not with -W/S)
+ *  (default: 0)</pre>
+ * 
+ * <pre> -U &lt;Upper bound for interpolation parameter&gt;
+ *  Upper bound for the interpolation parameter (not with -W/S)
+ *  (default: 1)</pre>
+ * 
+ * <pre> -P &lt;Number of parts&gt;
+ *  Determines the step size for tuning the interpolation
+ *  parameter, nl. (U-L)/P (not with -W/S)
+ *  (default: 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * More precisely, this is a simple extension of the OSDLCore class, 
+ * so that the OSDLCore class can be used within the WEKA environment.
+ * The problem with OSDLCore is that it implements both
+ * <code> classifyInstance </code> and <code> distributionForInstance </code>
+ * in a non trivial way.
+ * <p>
+ * One can evaluate a model easily with the method <code> evaluateModel </code>
+ * from the <code> Evaluation </code> class.  However, for nominal classes
+ * they do the following: they use <code> distributionForInstance </code> 
+ * and then pick the class with maximal probability.  This procedure
+ * is <b> not </b> valid for a ranking algorithm, since this destroys
+ * the required monotonicity property.
+ * </p>
+ * <p> 
+ * This class reimplements <code> distributionForInstance </code> in the 
+ * following way:  first <code> classifyInstance </code> of 
+ * <code> OSDLCore </code>  is used and the chosen label then gets 
+ * assigned probability one.  This ensures that the classification 
+ * accuracy is calculated correctly, but possibly some other statistics 
+ * are no longer meaningful.
+ * </p>
+ *
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5987 $
+ */
+public class OSDL
+  extends OSDLCore {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4534219825732505381L;
+
+  /**
+   * Use <code> classifyInstance </code> from <code> OSDLCore </code> and
+   * assign probability one to the chosen label.
+   * The implementation is heavily based on the same method in 
+   * the <code> Classifier </code> class.
+   * 
+   * @param instance the instance to be classified
+   * @return an array containing a single '1' on the index
+   * that <code> classifyInstance </code> returns.
+   */
+  public double[] distributionForInstance(Instance instance) {
+
+    // based on the code from the Classifier class
+    double[] dist = new double[instance.numClasses()];
+    int classification = 0;
+    switch (instance.classAttribute().type()) {
+      case Attribute.NOMINAL:
+	try {
+	  classification = 
+	    (int) Math.round(classifyInstance(instance));
+	} catch (Exception e) {
+	  System.out.println("There was a problem with classifyIntance");
+	  System.out.println(e.getMessage());
+	  e.printStackTrace();
+	}
+	if (Utils.isMissingValue(classification)) {
+	  return dist;
+	} 
+	dist[classification] = 1.0;
+	return dist;
+	
+      case Attribute.NUMERIC:
+	try {
+	  dist[0] = classifyInstance(instance);
+	} catch (Exception e) {
+	  System.out.println("There was a problem with classifyIntance");
+	  System.out.println(e.getMessage());
+	  e.printStackTrace();
+	}
+	return dist;
+	
+      default:
+	return dist;
+    }
+  }    
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class and for using it from the
+   * command line.
+   *
+   * @param args array of options for both the classifier <code>
+   * OSDL </code> and for <code> evaluateModel </code>
+   */
+  public static void main(String[] args) {
+    runClassifier(new OSDL(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/SerializedClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/SerializedClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/SerializedClassifier.java	(revision 29)
@@ -0,0 +1,339 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SerializedClassifier.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A wrapper around a serialized classifier model. This classifier loads a serialized models and uses it to make predictions.<br/>
+ * <br/>
+ * Warning: since the serialized model doesn't get changed, cross-validation cannot bet used with this classifier.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -model &lt;filename&gt;
+ *  The file containing the serialized model.
+ *  (required)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ */
+public class SerializedClassifier
+  extends AbstractClassifier {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4599593909947628642L;
+
+  /** the serialized classifier model used for making predictions */
+  protected transient Classifier m_Model = null;
+  
+  /** the file where the serialized model is stored */
+  protected File m_ModelFile = new File(System.getProperty("user.dir"));
+  
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A wrapper around a serialized classifier model. This classifier loads "
+      + "a serialized models and uses it to make predictions.\n\n"
+      + "Warning: since the serialized model doesn't get changed, cross-validation "
+      + "cannot bet used with this classifier.";
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions(){
+    Vector        	result;
+    Enumeration   	enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe file containing the serialized model.\n"
+	+ "\t(required)",
+	"model", 1, "-model <filename>"));
+
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-model");
+    result.add("" + getModelFile());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -model &lt;filename&gt;
+   *  The file containing the serialized model.
+   *  (required)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption("model", options);
+    if (tmpStr.length() != 0)
+      setModelFile(new File(tmpStr));
+    else
+      setModelFile(new File(System.getProperty("user.dir")));
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String modelFileTipText() {
+    return "The serialized classifier model to use for predictions.";
+  }
+
+  /**
+   * Gets the file containing the serialized model.
+   *
+   * @return 		the file.
+   */
+  public File getModelFile() {
+    return m_ModelFile;
+  }
+  
+  /**
+   * Sets the file containing the serialized model.
+   *
+   * @param value 	the file.
+   */
+  public void setModelFile(File value) {
+    m_ModelFile = value;
+    
+    if (value.exists() && value.isFile()) {
+      try {
+	initModel();
+      }
+      catch (Exception e) {
+	throw new IllegalArgumentException("Cannot load model from file '" + value + "': " + e);
+      }
+    }
+  }
+
+  /**
+   * Sets the fully built model to use, if one doesn't want to load a model
+   * from a file or already deserialized a model from somewhere else.
+   * 
+   * @param value	the built model
+   * @see		#getCurrentModel()
+   */
+  public void setModel(Classifier value) {
+    m_Model = value;
+  }
+  
+  /**
+   * Gets the currently loaded model (can be null). Call buildClassifier method
+   * to load model from file.
+   * 
+   * @return		the current model
+   * @see		#setModel(Classifier)
+   */
+  public Classifier getCurrentModel() {
+    return m_Model;
+  }
+  
+  /**
+   * loads the serialized model if necessary, throws an Exception if the
+   * derserialization fails.
+   * 
+   * @throws Exception	if deserialization fails
+   */
+  protected void initModel() throws Exception {
+    if (m_Model == null)
+      m_Model = (Classifier) SerializationHelper.read(m_ModelFile.getAbsolutePath());
+  }
+
+  /**
+   * Returns default capabilities of the base classifier.
+   *
+   * @return      the capabilities of the base classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities        result;
+
+    // init model if necessary
+    try {
+      initModel();
+    }
+    catch (Exception e) {
+      System.err.println(e);
+    }
+
+    if (m_Model != null) {
+      result = m_Model.getCapabilities();
+    } else {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    result.setOwner(this);
+    
+    return result;
+  }
+  
+  /**
+   * Calculates the class membership probabilities for the given test
+   * instance.
+   *
+   * @param instance the instance to be classified
+   * @return preedicted class probability distribution
+   * @throws Exception if distribution can't be computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[]	result;
+
+    // init model if necessary
+    initModel();
+    
+    result = m_Model.distributionForInstance(instance);
+    
+    return result;
+  }
+  
+  /**
+   * loads only the serialized classifier
+   * 
+   * @param data        the training instances
+   * @throws Exception  if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // init model if necessary
+    initModel();
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+  }
+
+  /**
+   * Returns a string representation of the classifier
+   * 
+   * @return		the string representation of the classifier
+   */
+  public String toString() {
+    StringBuffer	result;
+    
+    if (m_Model == null) {
+      result = new StringBuffer("No model loaded yet.");
+    }
+    else {
+      result = new StringBuffer();
+      result.append("SerializedClassifier\n");
+      result.append("====================\n\n");
+      result.append("File: " + getModelFile() + "\n\n");
+      result.append(m_Model.toString());
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Runs the classifier with the given options
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    runClassifier(new SerializedClassifier(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/VFI.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/VFI.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/VFI.java	(revision 29)
@@ -0,0 +1,639 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VFI.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Classification by voting feature intervals. Intervals are constucted around each class for each attribute (basically discretization). Class counts are recorded for each interval on each attribute. Classification is by voting. For more info see:<br/>
+ * <br/>
+ * G. Demiroz, A. Guvenir: Classification by voting feature intervals. In: 9th European Conference on Machine Learning, 85-92, 1997.<br/>
+ * <br/>
+ * Have added a simple attribute weighting scheme. Higher weight is assigned to more confident intervals, where confidence is a function of entropy:<br/>
+ * weight (att_i) = (entropy of class distrib att_i / max uncertainty)^-bias
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Demiroz1997,
+ *    author = {G. Demiroz and A. Guvenir},
+ *    booktitle = {9th European Conference on Machine Learning},
+ *    pages = {85-92},
+ *    publisher = {Springer},
+ *    title = {Classification by voting feature intervals},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * Faster than NaiveBayes but slower than HyperPipes. <p><p>
+ *
+ * <pre>
+ *  Confidence: 0.01 (two tailed)
+ *
+ * Dataset                   (1) VFI '-B  | (2) Hyper (3) Naive
+ *                         ------------------------------------
+ * anneal.ORIG               (10)   74.56 |   97.88 v   74.77
+ * anneal                    (10)   71.83 |   97.88 v   86.51 v
+ * audiology                 (10)   51.69 |   66.26 v   72.25 v
+ * autos                     (10)   57.63 |   62.79 v   57.76
+ * balance-scale             (10)   68.72 |   46.08 *   90.5  v
+ * breast-cancer             (10)   67.25 |   69.84 v   73.12 v
+ * wisconsin-breast-cancer   (10)   95.72 |   88.31 *   96.05 v
+ * horse-colic.ORIG          (10)   66.13 |   70.41 v   66.12
+ * horse-colic               (10)   78.36 |   62.07 *   78.28
+ * credit-rating             (10)   85.17 |   44.58 *   77.84 *
+ * german_credit             (10)   70.81 |   69.89 *   74.98 v
+ * pima_diabetes             (10)   62.13 |   65.47 v   75.73 v
+ * Glass                     (10)   56.82 |   50.19 *   47.43 *
+ * cleveland-14-heart-diseas (10)   80.01 |   55.18 *   83.83 v
+ * hungarian-14-heart-diseas (10)   82.8  |   65.55 *   84.37 v
+ * heart-statlog             (10)   79.37 |   55.56 *   84.37 v
+ * hepatitis                 (10)   83.78 |   63.73 *   83.87
+ * hypothyroid               (10)   92.64 |   93.33 v   95.29 v
+ * ionosphere                (10)   94.16 |   35.9  *   82.6  *
+ * iris                      (10)   96.2  |   91.47 *   95.27 *
+ * kr-vs-kp                  (10)   88.22 |   54.1  *   87.84 *
+ * labor                     (10)   86.73 |   87.67     93.93 v
+ * lymphography              (10)   78.48 |   58.18 *   83.24 v
+ * mushroom                  (10)   99.85 |   99.77 *   95.77 *
+ * primary-tumor             (10)   29    |   24.78 *   49.35 v
+ * segment                   (10)   77.42 |   75.15 *   80.1  v
+ * sick                      (10)   65.92 |   93.85 v   92.71 v
+ * sonar                     (10)   58.02 |   57.17     67.97 v
+ * soybean                   (10)   86.81 |   86.12 *   92.9  v
+ * splice                    (10)   88.61 |   41.97 *   95.41 v
+ * vehicle                   (10)   52.94 |   32.77 *   44.8  *
+ * vote                      (10)   91.5  |   61.38 *   90.19 *
+ * vowel                     (10)   57.56 |   36.34 *   62.81 v
+ * waveform                  (10)   56.33 |   46.11 *   80.02 v
+ * zoo                       (10)   94.05 |   94.26     95.04 v
+ *                          ------------------------------------
+ *                                (v| |*) |  (9|3|23)  (22|5|8) 
+ * </pre> 					
+ * <p>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C
+ *  Don't weight voting intervals by confidence</pre>
+ * 
+ * <pre> -B &lt;bias&gt;
+ *  Set exponential bias towards confident intervals
+ *  (default = 1.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class VFI 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8081692166331321866L;
+  
+  /** The index of the class attribute */
+  protected int m_ClassIndex;
+
+  /** The number of classes */
+  protected int m_NumClasses;
+
+  /** The training data */
+  protected Instances m_Instances = null;
+
+  /** The class counts for each interval of each attribute */
+  protected double [][][] m_counts;
+
+  /** The global class counts */
+  protected double [] m_globalCounts;
+
+  /** The lower bounds for each attribute */
+  protected double [][] m_intervalBounds;
+
+  /** The maximum entropy for the class */
+  protected double m_maxEntrop;
+
+  /** Exponentially bias more confident intervals */
+  protected boolean m_weightByConfidence = true;
+
+  /** Bias towards more confident intervals */
+  protected double m_bias = -0.6;
+
+  private double TINY = 0.1e-10;
+
+  /**
+   * Returns a string describing this search method
+   * @return a description of the search method suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Classification by voting feature intervals. Intervals are "
+      +"constucted around each class for each attribute ("
+      +"basically discretization). Class counts are "
+      +"recorded for each interval on each attribute. Classification is by "
+      +"voting. For more info see:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      +"Have added a simple attribute weighting scheme. Higher weight is "
+      +"assigned to more confident intervals, where confidence is a function "
+      +"of entropy:\nweight (att_i) = (entropy of class distrib att_i / "
+      +"max uncertainty)^-bias";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "G. Demiroz and A. Guvenir");
+    result.setValue(Field.TITLE, "Classification by voting feature intervals");
+    result.setValue(Field.BOOKTITLE, "9th European Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1997");
+    result.setValue(Field.PAGES, "85-92");
+    result.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(
+    new Option("\tDon't weight voting intervals by confidence",
+	       "C", 0,"-C"));
+    newVector.addElement(
+    new Option("\tSet exponential bias towards confident intervals\n"
+	       +"\t(default = 1.0)",
+	       "B", 1,"-B <bias>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C
+   *  Don't weight voting intervals by confidence</pre>
+   * 
+   * <pre> -B &lt;bias&gt;
+   *  Set exponential bias towards confident intervals
+   *  (default = 1.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString;
+    
+    setWeightByConfidence(!Utils.getFlag('C', options));
+    
+    optionString = Utils.getOption('B', options);
+    if (optionString.length() != 0) {
+      Double temp = new Double(optionString);
+      setBias(temp.doubleValue());
+    }
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightByConfidenceTipText() {
+    return "Weight feature intervals by confidence";
+  }
+
+  /**
+   * Set weighting by confidence
+   * @param c true if feature intervals are to be weighted by confidence
+   */
+  public void setWeightByConfidence(boolean c) {
+    m_weightByConfidence = c;
+  }
+
+  /**
+   * Get whether feature intervals are being weighted by confidence
+   * @return true if weighting by confidence is selected
+   */
+  public boolean getWeightByConfidence() {
+    return m_weightByConfidence;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String biasTipText() {
+    return "Strength of bias towards more confident features";
+  }
+
+  /**
+   * Set the value of the exponential bias towards more confident intervals
+   * @param b the value of the bias parameter
+   */
+  public void setBias(double b) {
+    m_bias = -b;
+  }
+
+  /**
+   * Get the value of the bias parameter
+   * @return the bias parameter
+   */
+  public double getBias() {
+    return -m_bias;
+  }
+
+  /**
+   * Gets the current settings of VFI
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    String[] options = new String[3];
+    int current = 0;
+    
+    if (!getWeightByConfidence()) {
+      options[current++] = "-C";
+    }
+
+    options[current++] = "-B"; options[current++] = ""+getBias();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    
+    return options;
+  }
+  
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    if (!m_weightByConfidence) {
+      TINY = 0.0;
+    }
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    m_ClassIndex = instances.classIndex();
+    m_NumClasses = instances.numClasses();
+    m_globalCounts = new double [m_NumClasses];
+    m_maxEntrop = Math.log(m_NumClasses) / Math.log(2);
+
+    m_Instances = new Instances(instances, 0); // Copy the structure for ref
+
+    m_intervalBounds = 
+      new double[instances.numAttributes()][2+(2*m_NumClasses)];
+
+    for (int j = 0; j < instances.numAttributes(); j++) {
+      boolean alt = false;
+      for (int i = 0; i < m_NumClasses*2+2; i++) {
+	if (i == 0) {
+	  m_intervalBounds[j][i] = Double.NEGATIVE_INFINITY;
+	} else if (i == m_NumClasses*2+1) {
+	  m_intervalBounds[j][i] = Double.POSITIVE_INFINITY;
+	} else {
+	  if (alt) {
+	    m_intervalBounds[j][i] = Double.NEGATIVE_INFINITY;
+	    alt = false;
+	  } else {
+	    m_intervalBounds[j][i] = Double.POSITIVE_INFINITY;
+	    alt = true;
+	  }
+	}
+      }
+    }
+
+    // find upper and lower bounds for numeric attributes
+    for (int j = 0; j < instances.numAttributes(); j++) {
+      if (j != m_ClassIndex && instances.attribute(j).isNumeric()) {
+	for (int i = 0; i < instances.numInstances(); i++) {
+	  Instance inst = instances.instance(i);
+	  if (!inst.isMissing(j)) {
+	    if (inst.value(j) < 
+		m_intervalBounds[j][((int)inst.classValue()*2+1)]) {
+	      m_intervalBounds[j][((int)inst.classValue()*2+1)] = 
+		inst.value(j);
+	    }
+	    if (inst.value(j) > 
+		m_intervalBounds[j][((int)inst.classValue()*2+2)]) {
+	      m_intervalBounds[j][((int)inst.classValue()*2+2)] = 
+		inst.value(j);
+	    }
+	  }
+	}
+      }
+    }
+
+    m_counts = new double [instances.numAttributes()][][];
+
+    // sort intervals
+    for (int i = 0 ; i < instances.numAttributes(); i++) {
+      if (instances.attribute(i).isNumeric()) {
+	int [] sortedIntervals = Utils.sort(m_intervalBounds[i]);
+	// remove any duplicate bounds
+	int count = 1;
+	for (int j = 1; j < sortedIntervals.length; j++) {
+	  if (m_intervalBounds[i][sortedIntervals[j]] != 
+	      m_intervalBounds[i][sortedIntervals[j-1]]) {
+	    count++;
+	  }
+	}
+	double [] reordered = new double [count];
+	count = 1;
+	reordered[0] = m_intervalBounds[i][sortedIntervals[0]];
+	for (int j = 1; j < sortedIntervals.length; j++) {
+	   if (m_intervalBounds[i][sortedIntervals[j]] != 
+	      m_intervalBounds[i][sortedIntervals[j-1]]) {
+	     reordered[count] =  m_intervalBounds[i][sortedIntervals[j]];
+	     count++;
+	   }
+	}
+	m_intervalBounds[i] = reordered;
+	m_counts[i] = new double [count][m_NumClasses];
+      } else if (i != m_ClassIndex) { // nominal attribute
+	m_counts[i] = 
+	  new double [instances.attribute(i).numValues()][m_NumClasses];
+      }
+    }
+
+    // collect class counts
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Instance inst = instances.instance(i);
+      m_globalCounts[(int)instances.instance(i).classValue()] += inst.weight();
+      for (int j = 0; j < instances.numAttributes(); j++) {
+	if (!inst.isMissing(j) && j != m_ClassIndex) {
+	  if (instances.attribute(j).isNumeric()) {
+	    double val = inst.value(j);
+	   
+	    int k;
+	    for (k = m_intervalBounds[j].length-1; k >= 0; k--) {
+	      if (val > m_intervalBounds[j][k]) {
+		m_counts[j][k][(int)inst.classValue()] += inst.weight();
+		break;
+	      } else if (val == m_intervalBounds[j][k]) {
+		m_counts[j][k][(int)inst.classValue()] += 
+		  (inst.weight() / 2.0);
+		m_counts[j][k-1][(int)inst.classValue()] += 
+		  (inst.weight() / 2.0);;
+		break;
+	      }
+	    }
+	   
+	  } else {
+	    // nominal attribute
+	    m_counts[j][(int)inst.value(j)][(int)inst.classValue()] += 
+	      inst.weight();;
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString() {
+    if (m_Instances == null) {
+      return "FVI: Classifier not built yet!";
+    }
+    StringBuffer sb = 
+      new StringBuffer("Voting feature intervals classifier\n");
+
+    /* Output the intervals and class counts for each attribute */
+    /*    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      if (i != m_ClassIndex) {
+	sb.append("\n"+m_Instances.attribute(i).name()+" :\n");
+	 if (m_Instances.attribute(i).isNumeric()) {
+	   for (int j = 0; j < m_intervalBounds[i].length; j++) {
+	     sb.append(m_intervalBounds[i][j]).append("\n");
+	     if (j != m_intervalBounds[i].length-1) {
+	       for (int k = 0; k < m_NumClasses; k++) {
+		 sb.append(m_counts[i][j][k]+" ");
+	       }
+	     }
+	     sb.append("\n");
+	   }
+	 } else {
+	   for (int j = 0; j < m_Instances.attribute(i).numValues(); j++) {
+	     sb.append(m_Instances.attribute(i).value(j)).append("\n");
+	     for (int k = 0; k < m_NumClasses; k++) {
+	       sb.append(m_counts[i][j][k]+" ");
+	     }
+	     sb.append("\n");
+	   }
+	 }
+      }
+      } */
+    return sb.toString();
+  }
+
+  /**
+   * Classifies the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return the predicted class for the instance 
+   * @throws Exception if the instance can't be classified
+   */
+  public double [] distributionForInstance(Instance instance) 
+    throws Exception {
+    double [] dist = new double[m_NumClasses];
+    double [] temp = new double[m_NumClasses];
+    double weight = 1.0;
+
+
+    for (int i = 0; i < instance.numAttributes(); i++) {
+      if (i != m_ClassIndex && !instance.isMissing(i)) {
+	double val = instance.value(i);
+	boolean ok = false;
+	if (instance.attribute(i).isNumeric()) {
+	  int k;
+	  for (k = m_intervalBounds[i].length-1; k >= 0; k--) {
+	    if (val > m_intervalBounds[i][k]) {
+	      for (int j = 0; j < m_NumClasses; j++) {
+		if (m_globalCounts[j] > 0) {
+		  temp[j] = ((m_counts[i][k][j]+TINY) / 
+			     (m_globalCounts[j]+TINY));
+		}
+	      }
+	      ok = true;
+	      break;
+	    } else if (val == m_intervalBounds[i][k]) {
+	      for (int j = 0; j < m_NumClasses; j++) {
+		if (m_globalCounts[j] > 0) {
+		  temp[j] = ((m_counts[i][k][j] + m_counts[i][k-1][j]) / 2.0) +
+		    TINY;
+		  temp[j] /= (m_globalCounts[j]+TINY);
+		}
+	      }
+	      ok = true;
+	      break;
+	    }
+	  }
+	  if (!ok) {
+	    throw new Exception("This shouldn't happen");
+	  }
+	} else { // nominal attribute
+	  ok = true;
+	  for (int j = 0; j < m_NumClasses; j++) {
+	    if (m_globalCounts[j] > 0) {
+	      temp[j] = ((m_counts[i][(int)val][j]+TINY) / 
+			 (m_globalCounts[j]+TINY));
+	    }
+	  }	  
+	}
+	
+	double sum = Utils.sum(temp);
+	if (sum <= 0) {
+	  for (int j = 0; j < temp.length; j++) {
+	    temp[j] = 1.0 / (double)temp.length;
+	  }
+	} else {
+	  Utils.normalize(temp, sum);
+	}
+
+	if (m_weightByConfidence) {
+	  weight = weka.core.ContingencyTables.entropy(temp);
+	  weight = Math.pow(weight, m_bias);
+	  if (weight < 1.0) {
+	    weight = 1.0;
+	  }
+	}
+
+	for (int j = 0; j < m_NumClasses; j++) {
+	  dist[j] += (temp[j] * weight);
+	}
+      }
+    }
+   
+    double sum = Utils.sum(dist);
+    if (sum <= 0) {
+      for (int j = 0; j < dist.length; j++) {
+	dist[j] = 1.0 / (double)dist.length;
+      }
+      return dist;
+    } else {
+      Utils.normalize(dist, sum);
+      return dist;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+	
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain command line arguments for evaluation
+   * (see Evaluation).
+   */
+  public static void main(String [] args) {
+    runClassifier(new VFI(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/AbsoluteLossFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/AbsoluteLossFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/AbsoluteLossFunction.java	(revision 29)
@@ -0,0 +1,74 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbsoluteLossFunction.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Class implementing the absolute loss function, this means 
+ * the returned loss is the abolute value of the difference
+ * between the predicted and actual value.
+ *
+ * <p>
+ * This implementation is done as part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class AbsoluteLossFunction
+  implements NominalLossFunction, RevisionHandler {
+
+  /**
+   * Returns the absolute loss function between two class values.
+   * 
+   * @param actual the actual class value
+   * @param predicted the predicted class value
+   * @return the absolute value of the difference between the actual 
+   * and predicted value
+   */
+  public final double loss(double actual, double predicted) {
+    return Math.abs(actual - predicted);
+  }
+
+  /**
+   * Returns a string with the name of the loss function.
+   *
+   * @return a string with the name of the loss function
+   */
+  public String toString() {
+    return "AbsoluteLossFunction";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BitMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BitMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BitMatrix.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BitMatrix.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+/**
+ * Interface specifying a simple matrix of booleans.  Operations are
+ * limited to setting, getting, clearing and counting.
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public interface BitMatrix {
+
+  /**
+   * Return the element a the specified position.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit at the 
+   * specified position is set, <code> false </code>
+   * otherwise
+   */
+  public boolean get(int row, int column);
+
+  /** 
+   * Sets the bit at the specified position to the specified
+   * value.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @param bool the value to fill in
+   * @return the value of <code> bool </code>
+   */
+  public boolean set(int row, int column, boolean bool);
+
+  /**
+   * Sets the bit at the specified position to <code> true. </code>
+   * The return value indicates whether anything has changed, 
+   * i.e.&nbsp; if the bit at the specified position was <code> true
+   * </code> before calling this method, then <code> false </code> is
+   * returned (and the bit remains <code> true </code> of course).
+   * In the other case <code> true </code> is returned.
+   * 
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit was actually
+   * set, <code> false </code> otherwise
+   */
+  public boolean set(int row, int column);
+
+  /**
+   * Clears the bit at the specified position.  The return value indicates
+   * whether the bit was actually cleared, i.e.&nbsp; if the bit was 
+   * originally <code> true </code> then <code> true </code> is returned.
+   * In the other case <code> false </code> is returned.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit was actually
+   * cleared, <code> false </code> otherwise
+   */
+  public boolean clear(int row, int column);
+
+  /** 
+   * Gets the number of rows.
+   * 
+   * @return the number of rows of the matrix
+   */
+  public int rows();
+
+  /**
+   * Gets the number of columns.
+   * 
+   * @return the number of columns of the matrix
+   */
+  public int columns();
+
+  /**
+   * Counts the number of bits that are set in the specified column. 
+   *
+   * @param column index of the column
+   * @return the number of bits that are set in the requested column 
+   */
+  public int columnCount(int column);
+
+  /**
+   * Counts the number of bits that are set in the specified row.
+   *
+   * @param row index of the row
+   * @return the number of bits that are set in the requested row
+   */
+  public int rowCount(int row);
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BooleanBitMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BooleanBitMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/BooleanBitMatrix.java	(revision 29)
@@ -0,0 +1,255 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BooleanBitMatrix.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * This class is a very simple implementation of a BitMatrix.
+ * In fact, it uses a two-dimensional array of booleans, so it is 
+ * not very space efficient.
+ * 
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class BooleanBitMatrix
+  implements BitMatrix, RevisionHandler {
+
+  /** The two-dimensional array of booleans. */
+  private boolean[][] m_bits;
+
+  /** The number of rows. */
+  private int m_rows;
+
+  /** The number of columns */
+  private int m_columns;
+
+  /**
+   * Construct a <code> BitMatrix </code> with the indicated
+   * number of rows and columns.  All bits are initially 
+   * set to <code> false </code>.
+   *
+   * @param rows the number of rows
+   * @param columns the number of column
+   */
+  public BooleanBitMatrix(int rows, int columns) {
+    m_bits = new boolean[rows][columns];
+    m_rows = rows;
+    m_columns = columns;
+  }
+
+  /**
+   * A copy constructor.  Constructs a copy of the given 
+   * <code> BitMatrix </code>.
+   *
+   * @param bm the <code> BitMatrix </code> to be copied.
+   */
+  public BooleanBitMatrix(BooleanBitMatrix bm) {
+    this(bm.m_rows, bm.m_columns);
+    for (int i = 0; i < m_rows; i++) {
+      System.arraycopy(bm.m_bits[i], 0, m_bits[i], 0, m_columns);
+    }
+  }
+
+  /**
+   * Returns the element a the specified position.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit at the 
+   * specified position is set, <code> false </code>
+   * otherwise
+   */
+  public boolean get(int row, int column) {
+    return m_bits[row][column];
+  }
+
+  /** 
+   * Sets the bit at the specified position to the specified
+   * value.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @param bool the value to fill in
+   * @return the value of <code> bool </code>
+   */
+  public boolean set(int row, int column, boolean bool) {
+    m_bits[row][column] = bool;
+    return bool;
+  }
+
+  /**
+   * Sets the bit at the specified position to <code> true. </code>
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit was actually
+   * set, <code> false </code> otherwise
+   */
+  public boolean set(int row, int column) {
+    return !get(row, column) && set(row, column, true);
+  }
+
+  /**
+   * Clears the bit at the specified position.
+   *
+   * @param row the row of the position
+   * @param column the column of the position
+   * @return <code> true </code> if the bit was actually
+   * cleared, <code> false </code> otherwise
+   */
+  public boolean clear(int row, int column) {
+    return get(row, column) && !set(row, column, false);
+  }
+
+  /** 
+   * Gets the number of rows.
+   *
+   * @return the number of rows of the matrix
+   */
+  public int rows() {
+    return m_rows;
+  }
+
+  /**
+   * Gets the number of columns.
+   *
+   * @return the number of columns of the matrix
+   */
+  public int columns() {
+    return m_columns;
+  }
+
+  /**
+   * Counts the number of bits that are set in the specified column. 
+   *
+   * @param column index of the column of which the bits are to be counted
+   * @return the number of bits that are set in the requested column 
+   */
+  public int columnCount(int column) {
+    int count = 0;
+    for (int i = 0; i < m_rows; i++) {
+      count += (m_bits[i][column] ? 1 : 0);
+    }
+    return count;
+  }
+
+  /**
+   * Counts the number of bits that are set in the specified row.
+   * 
+   * @param row index of the row of which the bits are to be counted
+   * @return the number of bits that are set in the requested row
+   */
+  public int rowCount(int row) {
+    int count = 0;
+    for (int i = 0; i < m_columns; i++) {
+      count += (m_bits[row][i] ? 1 : 0);
+    }
+    return count;
+  }
+
+  /**
+   * Swap the rows and the columns of the <code> BooleanBitMatrix. </code>
+   *
+   * @return the transposed matrix
+   */
+  public BooleanBitMatrix transpose() {
+    BooleanBitMatrix transposed = new BooleanBitMatrix(m_columns, m_rows);
+
+    for (int i = 0; i < m_rows; i++) {
+      for (int j = 0; j < m_columns; j++) {
+	transposed.set(j, i, get(i, j));
+      }
+    }
+
+    return transposed;
+  }
+
+  /**
+   * Swaps the rows and the columns of the <code> BooleanBitMatrix, </code>
+   * without creating a new object.
+   * The <code> BooleanBitMatrix </code> must be a square matrix.
+   *
+   * @throws IllegalArgumentException if the <code> BooleanBitMatrix </code>
+   * is not square.
+   */
+  public void transposeInPlace() throws IllegalArgumentException {
+    if (m_rows != m_columns) {
+      throw new IllegalArgumentException
+      ("The BooleanBitMatrix is not square"); 
+    }
+    for (int i = 0; i < m_rows; i++) {
+      for (int j = i + 1; j < m_columns; j++) {
+	swap(i, j, j, i);
+      }
+    }
+  }
+
+  /**
+   * Swap the elements with coordinates <code> (r1,c1) </code>  and 
+   * <code> (r2,c2). </code>
+   *
+   * @param r1 index of first row
+   * @param c1 index of first column
+   * @param r2 index of second row
+   * @param c2 index of second column
+   */
+  private void swap(int r1, int c1, int r2, int c2) {
+    boolean tmp = get(r1, c1);
+    set(r1, c1, get(r2, c2));
+    set(r2, c2, tmp);
+  }
+
+  /**
+   * Create a compact string representation of the matrix.
+   * 
+   * @return a <code> String </code> representing the matrix,
+   * row by row.
+   */
+  public String toString() {
+    StringBuffer sb = new StringBuffer(m_rows * (m_columns + 1) );
+    for (int i = 0; i < m_rows; i++) {
+      for (int j = 0; j < m_columns; j++) { 
+	sb.append(get(i, j) ? 1 : 0);
+      }
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/Coordinates.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/Coordinates.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/Coordinates.java	(revision 29)
@@ -0,0 +1,252 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Coordinates.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.Instance;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * This is a simple implementation of the data space.  The <code>
+ * Coordinates </code> holds the internal weka value of an instance,
+ * but <i> with the class removed </i>.  The class is immutable,
+ * and works best when all attibutes are nominal (ordinal), although
+ * it will not give an error when working with numeric attributes.
+ * In the latter case, performance will degrade because of the way
+ * in which the hashcode is calculated.
+ * 
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class Coordinates
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2319016195345994738L;
+
+  /**
+   * The internal weka values of the attributes of the instance, minus
+   * the class attribute.
+   */
+  private int[] m_coord;
+
+  /**
+   * The hashcode of this object.  Calculated during construction.
+   */
+  private int m_hashCode;
+
+  /**
+   * Create the <code> Coordinates </code> for the given instance.
+   *
+   * @param instance the <code> Instance </code> on which the <code>
+   * Coordinates </code> will be based
+   */
+  public Coordinates(Instance instance) {
+
+    double[] values = instance.toDoubleArray();
+    int classIndex = instance.classIndex();
+    if (classIndex == -1) {
+      m_coord = new int[values.length];
+    } else {
+      m_coord = new int[values.length - 1];
+    }
+
+    m_hashCode = 0;
+    int factor=1;
+    for (int i = 0,j = 0;i < values.length; i++) { 
+      if (i != classIndex) {
+	m_coord[j] = (int) values[i];
+	if (i > 0 && i - 1 != classIndex) {
+	  factor *= instance.attribute(i-1).numValues();
+	} else if (i - 1 == classIndex && classIndex != -1 
+	    && classIndex != 0) {
+	  factor *= instance.attribute(i - 2).numValues();
+	}
+	m_hashCode += (m_coord[j])*factor;
+	j++;    
+      }
+    }
+  }
+
+  /**
+   * Get the value of the attribute with index <code> index, </code>
+   * ignoring the class attribute.  Indices are counted starting from
+   * 0.  
+   *
+   * @param index the index of the requested attribute
+   * @return the value of this attribute, in internal floating point format.
+   */
+  public double getValue(int index) {
+    return m_coord[index];
+  }
+
+  /**
+   * Get the values of the coordinates.
+   * 
+   * @param values array serving as output, and the first 
+   * <code> dimension() </code> values are filled in.
+   */
+  public void getValues(double[] values) {
+    // XXX this is a rather strange method, maybe it should be changed
+    // into one that returns (using System.arraycopy) the requested values
+    for (int i = 0; i < m_coord.length; i++) {
+      values[i] = m_coord[i];
+    }
+  }
+
+  /**
+   * Indicates if the object <code> o </code> equals <code> this. </code>
+   *
+   * @param o the reference object with which to compare
+   * @return <code> true </code> if <code> o </code> equals <code>
+   * this, </code> <code> false </code> otherwise
+   */
+  public boolean equals(Object o) {
+    if (! (o instanceof Coordinates) ) {
+      return false;
+    }
+
+    Coordinates cc = (Coordinates) o;
+    // if the length or hashCodes differ, the objects are certainly not equal
+    if (m_coord.length != cc.m_coord.length || m_hashCode != cc.m_hashCode) {
+      return false;
+    }
+
+    for (int i = 0; i < m_coord.length; i++) {
+      if (m_coord[i] != cc.m_coord[i]) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /** 
+   * Checks if <code> this </code> is strictly smaller than <code> cc. </code>
+   * This means that for all indices i it holds that 
+   * <code> this.getValue(i) &lt;= cc.getValue(i) </code> and that there is
+   * at least one index i such that 
+   * <code> this.getValue(i) &ne; cc.getValue(i) </code>
+   * 
+   * @param cc the <code> Coordinates </code> that <code> this </code> is
+   * compared to
+   * @return <code> true </code> if <code> this </code> is strictly
+   * smaller than <code> cc, </code> <code> false </code> otherwise
+   * @throws IllegalArgumentException if the dimensions of both objects differ
+   */
+  public boolean strictlySmaller(Coordinates cc) throws IllegalArgumentException {
+    if (cc.m_coord.length != m_coord.length) { 
+      throw new IllegalArgumentException
+      ("Coordinates are not from the same space");
+    }
+
+    // Skip all equal values
+    int i = 0;
+    while(i < m_coord.length && cc.m_coord[i] == m_coord[i]) { 
+      i++;
+    }
+
+    if (i == m_coord.length) {
+      return false; // equality !
+    }
+
+    for (; i < m_coord.length; i++) {
+      if (m_coord[i] > cc.m_coord[i]) { 
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /** 
+   * Checks if <code> this </code> is smaller or equal than <code> cc. </code>
+   * This means that for all indices i it holds that 
+   * <code> this.getValue(i) &lt;= cc.getValue(i). </code>
+   * 
+   * @param cc the <code> Coordinates </code> that <code> this </code> is
+   * compared to
+   * @return <code> true </code> if <code> this </code> is
+   * smaller or equal than <code> cc, </code> <code> false </code> otherwise
+   * @throws IllegalArgumentException if the dimensions of both objects differ
+   */
+  public boolean smallerOrEqual(Coordinates cc) throws IllegalArgumentException {
+    if (cc.m_coord.length != m_coord.length) { 
+      throw new IllegalArgumentException
+      ("Coordinates are not from the same space");
+    }
+    for (int i = 0; i < m_coord.length; i++) {
+      if (m_coord[i] > cc.m_coord[i]) { 
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Gets the hash code value for this object.
+   *
+   * @return the requested hash code
+   */
+  public int hashCode() {
+    return m_hashCode;
+  }
+
+  /** 
+   * Gets the dimension of the data space, this is the number of attributes,
+   * exluding the class attribute.
+   * 
+   * @return the dimension of the data space this object resides in
+   */
+  public int dimension() {
+    return m_coord.length;
+  }
+
+  /**
+   * Get a string representation of this object.
+   *
+   * @return the requested string representation
+   */
+  public String toString() {
+    String s = "(";
+    for (int i = 0; i < m_coord.length - 1; i++) {
+      s += m_coord[i] + ",";
+    }
+    return s + m_coord[m_coord.length - 1] + ")";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/CumulativeDiscreteDistribution.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/CumulativeDiscreteDistribution.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/CumulativeDiscreteDistribution.java	(revision 29)
@@ -0,0 +1,255 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CumulativeDiscreteDistribution.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.estimators.DiscreteEstimator;
+
+import java.io.Serializable;
+
+/**
+ * Represents a discrete cumulative probability distribution 
+ * over a totally ordered discrete set.  The elements of this set
+ * are numbered consecutively starting from 0. 
+ *<p>
+ * In this implementation object of type 
+ * <code> CumulativeDiscreteDistribution </code> are immutable.
+ * </p>
+ * <p> 
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class CumulativeDiscreteDistribution
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2959806903004453176L;
+
+  /** 
+   * small tolerance to account for rounding errors when working
+   * with doubles
+   */
+  private static final double TOLERANCE = Utils.SMALL;
+
+  /** The cumulative probabilities */
+  private double[] m_cdf;
+
+  /** 
+   * Create a discrete cumulative probability distribution based on a 
+   * <code> DiscreteEstimator. </code>
+   *
+   * @param e the <code> DiscreteEstimator </code> on which the 
+   * cumulative probability distribution will be based
+   */
+  public CumulativeDiscreteDistribution(DiscreteEstimator e) {
+    m_cdf = new double[e.getNumSymbols()];
+
+    if (m_cdf.length != 0) {
+      m_cdf[0] = e.getProbability(0);
+    }
+    for (int i = 1; i < m_cdf.length; i++) {
+      m_cdf[i] = m_cdf[i - 1] + e.getProbability(i);
+    }
+  }
+
+  /** 
+   * Create a <code> CumulativeDiscreteDistribution </code> based on a 
+   * <code> DiscreteDistribution. </code>
+   *
+   * @param d the <code> DiscreteDistribution </code> on which the
+   * cumulative probability distribution will be based
+   */
+  public CumulativeDiscreteDistribution(DiscreteDistribution d) {
+    m_cdf = new double[d.getNumSymbols()];
+
+    if (m_cdf.length != 0) {
+      m_cdf[0] = d.getProbability(0);
+    }
+    for (int i = 1; i < m_cdf.length; i++) {
+      m_cdf[i] = m_cdf[i - 1] + d.getProbability(i);
+    }
+  }
+
+  /**
+   * Create a <code> CumulativeDiscreteDistribution </code> based on an 
+   * array of doubles.  The array <code> cdf </code> is copied, so
+   * the caller can reuse the same array.
+   *
+   * @param cdf an array that represents a valid discrete cumulative 
+   * probability distribution 
+   * @throws IllegalArgumentException if the array doesn't 
+   * represent a valid cumulative discrete distribution function
+   */
+  public CumulativeDiscreteDistribution(double[] cdf) throws IllegalArgumentException {
+    if (!validCumulativeDistribution(cdf)) {
+      throw new IllegalArgumentException
+      ("Not a cumulative probability distribution");
+    }
+    m_cdf = new double[cdf.length];
+    System.arraycopy(cdf, 0, m_cdf, 0, cdf.length);
+  }
+
+  /** 
+   * Get the number of elements over which the cumulative
+   * probability distribution is defined.
+   * 
+   * @return the number of elements over which the cumulative 
+   * probability distribution is defined.
+   */
+  public int getNumSymbols() {
+    return  (m_cdf != null) ? m_cdf.length : 0; 
+  }
+
+  /**
+   * Get the probability of finding an element 
+   * smaller or equal than <code> index. </code>
+   * 
+   * @param index the required index
+   * @return the probability of finding an element &lt;= index 
+   */
+  public double getCumulativeProbability(int index) {
+    return m_cdf[index];
+  }
+
+  /** 
+   * Get an array representation of the cumulative probability
+   * distribution.
+   * 
+   * @return an array of doubles representing the cumulative
+   * probability distribution
+   */
+  public double[] toArray() {
+    double cdf[] = new double[m_cdf.length];
+    System.arraycopy(m_cdf, 0, cdf, 0, cdf.length);
+    return cdf;
+  }
+
+  /**
+   * Returns if <code> this </code> is dominated by <code> cdf. </code> 
+   * This means that we check if, for all indices <code> i </code>, it
+   * holds that <code> this.getProbability(i) &gt;= cdf.getProbability(i).
+   * </code>
+   * 
+   * @param cdf the <code> CumulativeDiscreteDistribution </code>
+   * <code> this </code> is compared to
+   * @return <code> true </code> if <code> this </code> is dominated by 
+   * <code> cdf </code>, <code> false </code> otherwise
+   * @throws IllegalArgumentException if the two distributions don't
+   * have the same length
+   */
+  public boolean stochasticDominatedBy(CumulativeDiscreteDistribution cdf) throws IllegalArgumentException {
+    if (getNumSymbols() != cdf.getNumSymbols()) {
+      throw new IllegalArgumentException
+      ("Cumulative distributions are not defined over"
+	  + " the same number of symbols");
+    }
+    for (int i = 0; i < m_cdf.length; i++) {
+      if (m_cdf[i] < cdf.m_cdf[i]) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Indicates if the object <code> o </code> equals <code> this. </code>
+   * Note: for practical reasons I was forced to use a small tolerance
+   * whilst comparing the distributions, meaning that the transitivity
+   * property of <code> equals </code> is not guaranteed.
+   *
+   * @param o the reference object with which to compare
+   * @return <code> true </code> if <code> o </code> equals <code>
+   * this, </code> <code> false </code> otherwise
+   */
+  public boolean equals(Object o) {
+    if (!(o instanceof CumulativeDiscreteDistribution)) {
+      return false;
+    }
+    CumulativeDiscreteDistribution cdf = 
+      (CumulativeDiscreteDistribution) o;
+    if (m_cdf.length != cdf.getNumSymbols()) {
+      return false;
+    }
+    for (int i = 0; i < m_cdf.length; i++) {
+      if (Math.abs(m_cdf[i] - cdf.m_cdf[i]) > TOLERANCE) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /** 
+   * Get a string representation of the cumulative probability
+   * distribution.
+   * 
+   * @return a string representation of the distribution.
+   */
+  public String toString() {
+    // XXX MAYBE WE SHOULD USE STRINGBUFFER AND USE A FIXED
+    // NUMBER OF DECIMALS BEHIND THE COMMA
+    String s = "[" + getNumSymbols() + "]:";
+    for (int i = 0; i < getNumSymbols(); i++)
+      s += " " + getCumulativeProbability(i);
+    return s;
+  }
+
+  /** 
+   * Checks if the given array represents a valid cumulative 
+   * distribution.
+   *
+   * @param cdf an array holding the presumed cumulative distribution
+   * @return <code> true </code> if <code> cdf </code> represents
+   * a valid cumulative discrete distribution function, <code> false
+   * </code> otherwise
+   */
+  private static boolean validCumulativeDistribution(double[] cdf) {
+    if (cdf == null || cdf.length == 0 || 
+	Math.abs(cdf[cdf.length - 1] - 1.) > TOLERANCE || cdf[0] < 0) {
+      return false;
+    }
+    for (int i = 1; i < cdf.length; i++) {
+
+      // allow small tolerance for increasing check
+      if (cdf[i] < cdf[i - 1] - TOLERANCE) {   
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DiscreteDistribution.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DiscreteDistribution.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DiscreteDistribution.java	(revision 29)
@@ -0,0 +1,291 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DiscreteDistribution.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.estimators.DiscreteEstimator;
+
+import java.io.Serializable;
+
+/**
+ * This class represents a discrete probability distribution 
+ * over a finite number of values.
+ * <p>
+ * In the present implementation, objects of type 
+ * <code> DiscreteDistribution </code> are in fact immutable,
+ * so all one can do is create objects and retrieve information,
+ * such as median and mean, from them.
+ * </p>
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class DiscreteDistribution
+  implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1954630934425689828L;
+
+  /**
+   * small tolerance to account for rounding errors when working
+   * with doubles
+   */
+  private static final double TOLERANCE=Utils.SMALL;
+
+  /** the array of probabilities */
+  private double[] m_dd;
+
+  /** 
+   * Create a <code> DiscreteDistribution </code> based on a 
+   * <code> DiscreteEstimator. </code>
+   *
+   * @param e the <code> DiscreteEstimator </code>
+   */
+  public DiscreteDistribution(DiscreteEstimator e) {
+    m_dd = new double[e.getNumSymbols()];
+
+    for (int i = 0; i < m_dd.length; i++) {
+      m_dd[i] = e.getProbability(i);
+    }
+  }
+
+  /** 
+   * Create a <code> DiscreteDistribution </code> based on a 
+   * <code> CumulativeDiscreteDistribution. </code>
+   *
+   * @param cdf the <code> CumulativeDiscreteDistribution </code>
+   */
+  public DiscreteDistribution(CumulativeDiscreteDistribution cdf) {
+    m_dd = new double[cdf.getNumSymbols()];
+
+    if (m_dd.length != 0) {
+      m_dd[0] = cdf.getCumulativeProbability(0);
+    }
+    for (int i = 1; i < m_dd.length; i++) {
+      m_dd[i] = cdf.getCumulativeProbability(i) 
+      - cdf.getCumulativeProbability(i - 1);
+    }
+  }
+
+  /** 
+   * Create a <code> DiscreteDistribution </code> based on an 
+   * array of doubles.
+   *
+   * @param dd the array of doubles representing a valid 
+   * discrete distribution
+   * @throws IllegalArgumentException if <code> dd </code>
+   * does not represent a valid discrete distribution
+   */
+  public DiscreteDistribution(double[] dd) throws IllegalArgumentException {
+    if (!validDiscreteDistribution(dd)) {
+      throw new IllegalArgumentException
+      ("Not a valid discrete distribution");
+    }
+
+    m_dd = new double[dd.length];
+    System.arraycopy(dd,0,m_dd,0,dd.length);
+  }
+
+  /** 
+   * Get the number of elements over which the <code>
+   * DiscreteDistribution </code> is defined.
+   * 
+   * @return the number of elements over which the <code>
+   * DiscreteDistribution </code> is defined
+   */
+  public int getNumSymbols() {
+    return  (m_dd != null) ? m_dd.length : 0; 
+  }
+
+  /** 
+   * Get the probability of finding the element at
+   * a specified index.
+   * 
+   * @param index the index of the required element
+   * @return the probability of finding the specified element
+   */
+  public double getProbability(int index) {
+    return m_dd[index];
+  }
+
+  /** 
+   * Calculate the mean of the distribution.  The scores for
+   * calculating the mean start from 0 and have step size one,
+   * i.e. if there are n elements then the scores are 0,1,...,n-1.
+   * 
+   * @return the mean of the distribution
+   */
+  public double mean() {
+    double mean = 0;
+    for (int i = 1; i < m_dd.length; i++) {
+      mean += i * m_dd[i];
+    }
+    return mean;
+  }
+
+  /** 
+   * Calculate the median of the distribution.  This means
+   * the following: if there is a label m such that 
+   * P(x &lt;= m) &gt;= &frac12; and
+   * P(x &gt;= m) &gt;= &frac12;  then this label is returned.
+   * If there is no such label, an interpolation between the
+   * smallest label satisfying the first condition and the
+   * largest label satisfying the second condition is performed.
+   * The returned value is thus either an element label, or
+   * exactly between two element labels.
+   * 
+   * @return the median of the distribution. 
+   **/
+  public double median() {
+
+    /* cumulative probabilities starting from the left and
+     * right respectively 
+     */
+    double cl=m_dd[0];
+    double cr=m_dd[m_dd.length - 1]; // cumulative left and right
+
+    int i = 0;
+    while(cl < 0.5) {
+      cl += m_dd[++i]; // pre-increment
+    }
+
+    int j = m_dd.length - 1;
+    while(cr < 0.5) {
+      cr += m_dd[--j]; // pre-increment
+    }
+
+    return i == j ? i : ( (double) (i + j) ) / 2;
+  }
+
+  /** 
+   * Get a sorted array containing the indices of the elements with
+   * maximal probability.
+   *
+   * @return an array of class indices with maximal probability.
+   */
+  public int[] modes() {
+    int[] mm = new int[m_dd.length];
+    double max = m_dd[0];
+    int nr = 1; // number of relevant elements in mm
+    for (int i = 1; i < m_dd.length; i++) {
+      if (m_dd[i] > max + TOLERANCE) { 
+
+	// new maximum
+	max = m_dd[i];
+	mm[0] = i;
+	nr = 1;
+      }
+      else if (Math.abs(m_dd[i] - max) < TOLERANCE) {
+	mm[nr++] = i;
+      }
+    }
+
+    // trim to correct size
+    int[] modes = new int[nr];
+    System.arraycopy(mm, 0, modes, 0, nr);
+    return modes;
+  }
+
+  /**
+   * Convert the <code> DiscreteDistribution </code> to an
+   * array of doubles.
+   * 
+   * @return an array of doubles representing the
+   * <code> DiscreteDistribution </code>
+   */
+  public double[] toArray() {
+    double[] dd = new double[m_dd.length];
+    System.arraycopy(m_dd, 0, dd, 0, dd.length);
+    return dd;
+  }
+
+  /** 
+   * Get a string representation of the given <code>
+   * DiscreteDistribution. </code>
+   *
+   * @return a string representation of this object
+   */
+  public String toString() {
+
+    // XXX MAYBE WE SHOULD USE STRINGBUFFER AND FIXED WIDTH ...
+    String s = "[" + getNumSymbols() + "]:";
+    for (int i = 0; i < getNumSymbols(); i++) {
+      s += " " + getProbability(i);
+    }
+    return s;
+  }
+
+  /**
+   * Checks if <code> this </code> is dominated by <code> dd. </code> 
+
+   * @param dd the DiscreteDistribution to compare to
+   * @return <code> true </code> if <code> this </code> is dominated by 
+   * <code> dd </code>, <code> false </code> otherwise
+   * @throws IllegalArgumentException if the two distributions don't
+   * have the same length
+   */
+  public boolean stochasticDominatedBy(DiscreteDistribution dd) throws IllegalArgumentException {
+    return (new CumulativeDiscreteDistribution(this)).
+    stochasticDominatedBy
+    (new CumulativeDiscreteDistribution(dd));
+  }
+
+  /** 
+   * Checks if the given array of doubles represents a valid discrete 
+   * distribution.
+   * 
+   * @param dd an array holding the doubles
+   * @return <code> true </code> if <code> dd </code> is a valid discrete distribution, 
+   * <code> false </code> otherwise
+   */
+  private static boolean validDiscreteDistribution(double[] dd) {
+    if (dd == null || dd.length == 0) {
+      return false;
+    }
+
+    double sum = 0;
+    for (int i = 0; i < dd.length; i++) {
+      if (dd[i] < 0) {
+	return false;
+      }
+      sum += dd[i];
+    }
+    return !(Math.abs(sum - 1) > TOLERANCE); 
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DistributionUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DistributionUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/DistributionUtils.java	(revision 29)
@@ -0,0 +1,351 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DistributionUtils.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.estimators.DiscreteEstimator;
+
+import java.util.Arrays;
+
+/** 
+ * Class with some simple methods acting on 
+ * <code> CumulativeDiscreteDistribution. </code>
+ * All of the methods in this class are very easily implemented
+ * and the main use of this class is to gather all these methods
+ * in a single place.  It could be argued that some of the methods
+ * should be implemented in the class 
+ * <code> CumulativeDiscreteDistribution </code> itself.
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class DistributionUtils
+  implements RevisionHandler {
+
+  /**
+   * Constant indicating the maximal number of classes
+   * for which there is a minimal and maximal distribution
+   * present in the pool.
+   * One of the purposes of this class is to serve as a factory
+   * for minimal and maximal cumulative probability distributions.
+   * Since instances of <code> CumulativeDiscreteDistribution </code>
+   * are immutable, we can create them beforehand and reuse them 
+   * every time one is needed.  
+   */
+  private static final int MAX_CLASSES = 20;
+
+  /**
+   * Array filled with minimal cumulative discrete probability
+   * distributions.  This means that probability one is given to the
+   * first element.  This array serves as a pool for the method
+   * <code> getMinimalCumulativeDiscreteDistribution. </code>
+   */
+  private static final CumulativeDiscreteDistribution[] m_minimalDistributions;
+
+  /**
+   * Array filled with maximal cumulative discrete probability
+   * distributions. This means that probability one is given to the
+   * largest element.  This array serves as a pool for the method
+   * <code> getMaximalCumulativeDiscreteDistribution. </code>
+   */
+  private static final CumulativeDiscreteDistribution[] m_maximalDistributions;
+
+  // fill both static arrays with the correct distributions
+  static {
+    m_minimalDistributions = new CumulativeDiscreteDistribution[MAX_CLASSES + 1];
+    m_maximalDistributions = new CumulativeDiscreteDistribution[MAX_CLASSES + 1];
+
+    for (int i = 1; i <= MAX_CLASSES; i++) {
+      double[] dd = new double[i];
+      dd[dd.length - 1] = 1;
+      m_maximalDistributions[i] = new CumulativeDiscreteDistribution(dd);
+      Arrays.fill(dd,1);
+      m_minimalDistributions[i] = new CumulativeDiscreteDistribution(dd);
+    }
+  }
+
+  /** 
+   *  Compute a linear interpolation between the two given 
+   *  <code> CumulativeDiscreteDistribution. </code>
+   *  
+   *  @param cdf1 the first <code> CumulativeDiscreteDistribution </code>
+   *  @param cdf2 the second <code> CumulativeDiscreteDistribution </code>
+   *  @param s the interpolation parameter
+   *  @return (1 - s) &times; cdf1 + s &times; cdf2
+   *  @throws IllegalArgumentException if the two distributions
+   *  don't have the same size or if the parameter <code> s </code>
+   *  is not in the range [0,1]
+   */
+  public static CumulativeDiscreteDistribution interpolate(
+      CumulativeDiscreteDistribution cdf1,
+      CumulativeDiscreteDistribution cdf2, double s) throws IllegalArgumentException {
+
+    if (cdf1.getNumSymbols() != cdf2.getNumSymbols()) { 
+      throw new IllegalArgumentException
+      ("CumulativeDiscreteDistributions don't have " 
+	  + "the same size");
+    }
+
+    if (s < 0 || s > 1) {
+      throw new IllegalArgumentException
+      ("Parameter s exceeds bounds");
+    }
+
+    double[] res = new double[cdf1.getNumSymbols()];
+    for (int i = 0, n = cdf1.getNumSymbols(); i < n; i++) {
+      res[i] = (1 - s) * cdf1.getCumulativeProbability(i) +
+      s * cdf2.getCumulativeProbability(i);
+    }
+    return new CumulativeDiscreteDistribution(res);
+  }
+
+  /** 
+   *  Compute a linear interpolation between the two given 
+   *  <code> CumulativeDiscreteDistribution. </code>
+   *  
+   *  @param cdf1 the first <code> CumulativeDiscreteDistribution </code>
+   *  @param cdf2 the second <code> CumulativeDiscreteDistribution </code>
+   *  @param s the interpolation parameters, only the relevant number
+   *  of entries is used, so the array may be longer than the common
+   *  length of <code> cdf1 </code> and <code> cdf2 </code>
+   *  @return (1 - s) &times; cdf1 + s &times; cdf2, or more specifically
+   *  a distribution cd such that <code> 
+   *  cd.getCumulativeProbability(i) = 
+   *  (1-s[i]) &times; cdf1.getCumulativeProbability(i) + 
+   *  s[i] &times; cdf2.getCumulativeProbability(i) </code> 
+   *  @throws IllegalArgumentException if the two distributions
+   *  don't have the same size or if the array <code> s </code>
+   *  contains parameters not in the range <code> [0,1] </code>
+   */
+  public static CumulativeDiscreteDistribution interpolate(
+      CumulativeDiscreteDistribution cdf1,
+      CumulativeDiscreteDistribution cdf2, double[] s) throws IllegalArgumentException {
+
+    if (cdf1.getNumSymbols() != cdf2.getNumSymbols()) { 
+      throw new IllegalArgumentException
+      ("CumulativeDiscreteDistributions don't have " 
+	  + "the same size");
+    }
+
+    if (cdf1.getNumSymbols() > s.length) {
+      throw new IllegalArgumentException
+      ("Array with interpolation parameters is not "
+	  + " long enough");
+    }
+
+    double[] res = new double[cdf1.getNumSymbols()];
+    for (int i = 0, n = cdf1.getNumSymbols(); i < n; i++) {
+      if (s[i] < 0 || s[i] > 1) {
+	throw new IllegalArgumentException
+	("Interpolation parameter exceeds bounds");
+      }
+      res[i] = (1 - s[i]) * cdf1.getCumulativeProbability(i) +
+      s[i] * cdf2.getCumulativeProbability(i);
+    }
+    return new CumulativeDiscreteDistribution(res);
+  }
+
+  /** 
+   *  Compute a linear interpolation between the two given 
+   *  <code> DiscreteDistribution. </code>
+   *  
+   *  @param ddf1 the first <code> DiscreteDistribution </code>
+   *  @param ddf2 the second <code> DiscreteDistribution </code>
+   *  @param s the interpolation parameter
+   *  @return <code> (1 - s) &times; ddf1 + s &times; ddf2 </code>
+   *  @throws IllegalArgumentException if the two distributions
+   *  don't have the same size or if the parameter <code> s </code>
+   *  is not in the range [0,1]
+   */
+  public static DiscreteDistribution interpolate(
+      DiscreteDistribution ddf1,
+      DiscreteDistribution ddf2, double s) throws IllegalArgumentException {
+
+    if (ddf1.getNumSymbols() != ddf2.getNumSymbols()) { 
+      throw new IllegalArgumentException
+      ("DiscreteDistributions don't have " 
+	  + "the same size");
+    }
+
+    if (s < 0 || s > 1) {
+      throw new IllegalArgumentException
+      ("Parameter s exceeds bounds");
+    }
+
+    double[] res = new double[ddf1.getNumSymbols()];
+    for (int i = 0, n = ddf1.getNumSymbols(); i < n; i++) {
+      res[i] = (1 - s) * ddf1.getProbability(i) +
+      s * ddf2.getProbability(i);
+    }
+    return new DiscreteDistribution(res);
+  }
+
+  /**
+   * Create a new <code> CumulativeDiscreteDistribution </code>
+   * that is the minimum of the two given <code>
+   * CumulativeDiscreteDistribution. </code>
+   * Each component of the resulting probability distribution 
+   * is the minimum of the two corresponding components. <br/>
+   * Note: despite of its name, the returned cumulative probability
+   * distribution dominates both the arguments of this method.
+   *
+   * @param cdf1 first <code> CumulativeDiscreteDistribution </code>
+   * @param cdf2 second <code> CumulativeDiscreteDistribution </code>
+   * @return the minimum of the two distributions
+   * @throws IllegalArgumentException if the two distributions
+   * dont't have the same length
+   */
+  public static CumulativeDiscreteDistribution takeMin(
+      CumulativeDiscreteDistribution cdf1,
+      CumulativeDiscreteDistribution cdf2) throws IllegalArgumentException {
+    
+    if (cdf1.getNumSymbols() != cdf2.getNumSymbols() )
+      throw new IllegalArgumentException
+      ("Cumulative distributions don't have the same length");
+
+    double[] cdf = new double[cdf1.getNumSymbols()];
+    int n = cdf.length;
+    for (int i = 0; i < n; i++) { 
+      cdf[i] = Math.min(cdf1.getCumulativeProbability(i),
+	  cdf2.getCumulativeProbability(i));
+    }
+    return new CumulativeDiscreteDistribution(cdf);
+  }
+
+  /**
+   * Create a new <code> CumulativeDiscreteDistribution </code>
+   * that is the maximum of the two given <code>
+   * CumulativeDiscreteDistribution. </code>
+   * Each component of the resulting probability distribution 
+   * is the maximum of the two corresponding components.
+   * Note: despite of its name, the returned cumulative probability
+   * distribution is dominated by both the arguments of this method.
+   *
+   * @param cdf1 first <code> CumulativeDiscreteDistribution </code>
+   * @param cdf2 second <code> CumulativeDiscreteDistribution </code>
+   * @return the maximum of the two distributions
+   * @throws IllegalArgumentException if the two distributions
+   * dont't have the same length
+   */
+  public static CumulativeDiscreteDistribution takeMax(
+      CumulativeDiscreteDistribution cdf1,
+      CumulativeDiscreteDistribution cdf2) throws IllegalArgumentException {
+    
+    if (cdf1.getNumSymbols() != cdf2.getNumSymbols() )
+      throw new IllegalArgumentException
+      ("Cumulative distributions don't have the same length");
+
+    double[] cdf = new double[cdf1.getNumSymbols()];
+    int n = cdf.length;
+    for (int i = 0; i < n; i++) { 
+      cdf[i] = Math.max(cdf1.getCumulativeProbability(i),
+	  cdf2.getCumulativeProbability(i));
+    }
+    return new CumulativeDiscreteDistribution(cdf);
+  }
+
+  /**
+   * Converts a <code> DiscreteEstimator </code> to an array of 
+   * doubles.
+   *
+   * @param df the <code> DiscreteEstimator </code> to be converted
+   * @return an array of doubles representing the 
+   * <code> DiscreteEstimator </code>
+   */
+  public static double[] getDistributionArray(DiscreteEstimator df) {
+    double[] dfa = new double[df.getNumSymbols()];
+    for (int i = 0; i < dfa.length; i++) {
+      dfa[i] = df.getProbability(i);
+    }
+    return dfa;
+  }
+
+  /**
+   * Get the minimal <code> CumulativeDiscreteDistribution </code>
+   * over <code> numClasses </code> elements.  This means that
+   * a probability of one is assigned to the first element.
+   *
+   * @param numClasses the number of elements 
+   * @return the minimal <code> CumulativeDiscreteDistribution </code>
+   * over the requested number of elements
+   * @throws IllegalArgumentException if <code> numClasses </code> 
+   * is smaller or equal than 0
+   */
+  public static CumulativeDiscreteDistribution getMinimalCumulativeDiscreteDistribution(
+      int numClasses) throws IllegalArgumentException {
+    
+    if (numClasses <= 0) {
+      throw new IllegalArgumentException
+      ("Number of elements must be positive");
+    }
+    if (numClasses <= MAX_CLASSES) {
+      return m_minimalDistributions[numClasses];
+    }
+
+    double[] dd = new double[numClasses];
+    Arrays.fill(dd,1);
+    return new CumulativeDiscreteDistribution(dd);
+  }
+
+  /**
+   * Get the maximal <code> CumulativeDiscreteDistribution </code>
+   * over <code> numClasses </code> elements.  This means that
+   * a probability of one is assigned to the last class.
+   *
+   * @param numClasses the number of elements 
+   * @return the maximal <code> CumulativeDiscreteDistribution </code>
+   * over the requested number of elements
+   * @throws IllegalArgumentException if <code> numClasses </code> 
+   * is smaller or equal than 0
+   */
+  public static CumulativeDiscreteDistribution getMaximalCumulativeDiscreteDistribution(
+      int numClasses) throws IllegalArgumentException {
+    
+    if (numClasses <= 0) {
+      throw new IllegalArgumentException
+      ("Number of elements must be positive");
+    }
+    if (numClasses <= MAX_CLASSES) {
+      return m_maximalDistributions[numClasses];
+    }
+
+    double[] dd = new double[numClasses];
+    dd[dd.length - 1] = 1;
+    return new CumulativeDiscreteDistribution(dd);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/EnumerationIterator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/EnumerationIterator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/EnumerationIterator.java	(revision 29)
@@ -0,0 +1,100 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnumerationIterator.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/** 
+ * Implementation of a simple wrapper class for the <code> Enumeration </code>
+ * interface.
+ * This makes it possible to use an <code> Enumeration </code> as if
+ * it were an <code> Iterator. </code>
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class EnumerationIterator
+  implements Iterator, RevisionHandler {
+
+  private Enumeration e;
+
+  /**
+   * Construct an <code> EnumerationIterator </code> on basis of on
+   * <code> Enumeration. </code>
+   * 
+   * @param e the <code> Enumeration </code> on which the 
+   * <code> Iterator </code> will be based
+   */
+  public EnumerationIterator(Enumeration e) {
+    this.e = e;
+  }
+
+  /**
+   * Returns <code> true </code> if there are more elements in the iteration.
+   *
+   * @return <code> true </code> if there are more elements in the iteration,
+   * <code> false </code> otherwise
+   */
+  final public boolean hasNext() {
+    return e.hasMoreElements();
+  }
+
+  /**
+   * Returns the next element in the iteration.
+   *
+   * @return the next element in the iteration
+   * @throws NoSuchElementException if the requested element does  not exist
+   */
+  final public Object next() throws NoSuchElementException  {
+    return e.nextElement();
+  }
+
+  /**
+   * Since the iteration is based on an enumeration, removal of elements
+   * is not supported.
+   *
+   * @throws UnsupportedOperationException every time this method is invoked
+   */
+  final public void remove() {
+    throw new UnsupportedOperationException();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesComparator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesComparator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesComparator.java	(revision 29)
@@ -0,0 +1,108 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstancesComparator.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.Instance;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Comparator;
+
+/**
+ * Class to compare instances with respect to a given attribute, indicated
+ * by its index.  The ordering of the attribute values is determined
+ * by the internal values of WEKA.  There is also the possibility of
+ * reversing this order.
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class InstancesComparator
+  implements Comparator, RevisionHandler {
+
+  /** index of the attribute  */
+  private int m_Index;
+  
+  /** If 1 then the order is not reversed, when -1, the order is reversed */
+  private int m_Reverse = 1;
+
+  /**
+   * Construct an <code> InstancesComparator </code> that compares 
+   * the attributes with the given index.
+   *
+   * @param index the index on which to compare instances
+   */
+  public InstancesComparator(int index) {
+    m_Index = index;
+  }
+
+  /**
+   * Construct an <code> InstancesComparator </code> that compares 
+   * the attributes with the given index, with the possibility of
+   * reversing the order.
+   *
+   * @param index the index on which to compare instances
+   * @param reverse if <code> true </code> the order is reversed, if
+   * <code> false </code> the order is not reversed
+   */
+  public InstancesComparator(int index, boolean reverse) {
+    m_Index = index;
+    m_Reverse = (reverse == true) ? -1 : 1;
+  }
+
+  /**
+   * Compares two objects (instances) with respect to the attribute
+   * this comparator is constructed on.
+   *
+   * @param o1 the first object to be compared
+   * @param o2 the second object to be compared
+   * @return -1 if <code> o1 &lt; o2 </code> (wrt to the given attribute),
+   * 1 if <code> o1 &gt; o2 </code>, and 0 if <code> o1 </code> and
+   * <code> o2 </code> are equal (wrt to the given attribute)
+   */
+  public int compare(Object o1, Object o2) {
+    Instance i1 = (Instance) o1;
+    Instance i2 = (Instance) o2;
+
+    if (i1.value(m_Index) < i2.value(m_Index)) {
+      return -1 * m_Reverse;
+    } else if (i1.value(m_Index) > i2.value(m_Index)) {
+      return 1 * m_Reverse;
+    }
+    return 0;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesUtil.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesUtil.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/InstancesUtil.java	(revision 29)
@@ -0,0 +1,912 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstancesUtil.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.classifiers.Classifier;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.estimators.DiscreteEstimator;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * This class contains some methods for working with objects of 
+ * type <code> Instance </code> and <code> Instances, </code> not
+ * provided by there respective classes.
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5987 $
+ */
+public class InstancesUtil
+  implements RevisionHandler {
+
+  /**
+   * Compares two instances, ignoring the class attribute (if any)
+   *
+   * @param i1 the first instance
+   * @param i2 the second instance
+   * @return true if both instances are equal (ignoring the class
+   * attribute), false otherwise
+   */
+  public static boolean equalIgnoreClass(Instance i1, Instance i2) {
+    int n = i1.numAttributes();
+    int classIndex = i1.classIndex();
+    if (i2.numAttributes() != n || classIndex != i2.classIndex()) {
+      return false;
+    }
+    int i = 0;
+    while(i < n && (i == classIndex 
+	|| Utils.eq(i1.value(i), i2.value(i)))) {
+      i++;
+    }
+    return i == n;
+  }
+
+  /**
+   * Get the index of an instance in a set of instances, where 
+   * instances are compared ignoring the class attribute.
+   *
+   * @param instances the set of instances
+   * @param instance to instance to be found in the given set of instances
+   * @return the index of the first instance that equals the given instance
+   * (ignoring the class attribute), -1 if the instance was not found
+   */
+  public static int containsIgnoreClass(Instances instances, Instance instance) {
+    double[] dd = instance.toDoubleArray();
+    int classIndex = instances.classIndex();
+    int n = instances.numAttributes();
+    Iterator it = 
+      new EnumerationIterator(instances.enumerateInstances());
+    int index = 0;
+    while(it.hasNext()) {
+      Instance tmp = (Instance) it.next();
+      int i = 0;
+      while(i < n && 
+	  (i == classIndex || Utils.eq(dd[i], tmp.value(i)))) {
+	i++;
+      }
+      if (i == n) { 
+	return index;  // found it
+      }
+      index++;
+    }
+    return -1;
+  }
+
+  /**
+   * Find the next occurence of an instance, ignoring the class,
+   * for which the index in the dataset is at least <code> index. </code>
+   *
+   * @param instances the set of instances to be searched
+   * @param instance the instance to be found
+   * @param index the minimum index that might be returned
+   * @return the index of the first instance with index at 
+   * least <code> index </code> that equals the given instance
+   * (ignoring the class attribute), -1 if the instance was not found
+   */
+  public static int nextOccurenceIgnoreClass(Instances instances, Instance instance, int index) {
+    double[] dd = instance.toDoubleArray();
+    int classIndex = instances.classIndex();
+    int n = instances.numAttributes();
+    int numInstances = instances.numInstances();
+    int currentIndex = index;
+    while(currentIndex < numInstances) {
+      Instance tmp = instances.instance(currentIndex);
+      int i = 0;
+      while(i < n && 
+	  (i == classIndex || Utils.eq(dd[i], tmp.value(i)))) {
+	i++;
+      }
+      if (i == n) { 
+	return currentIndex;  // found it
+      }
+      currentIndex++;
+    }
+    return -1; // not present
+  }
+
+  /**
+   * Check if all instances have the same class value.
+   *
+   * @param instances the instances to be checked for homogeneity
+   * @return true if the instances have the same class value, false otherwise 
+   */
+  public static boolean isHomogeneous(Instances instances) {
+    Iterator it = new EnumerationIterator(instances.enumerateInstances());
+    if (it.hasNext()) {
+      double classValue = ((Instance) it.next()).classValue();
+      while(it.hasNext()) {
+	if (((Instance) it.next()).classValue() != classValue) {
+	  return false;
+	}
+      }
+    }
+    return true; // empty or all identical
+  }
+
+  /**
+   * Compares two instances in the data space, this is ignoring the class
+   * attribute.  An instance is strictly smaller than another instance
+   * if the same holds for the <code> Coordinates </code> based on
+   * these instances.
+   *
+   * @param i1 the first instance
+   * @param i2 the second instance
+   * @return <code> true </code> if the first instance is strictly smaller
+   * than the second instance, <code> false </code> otherwise
+   */
+  public static boolean strictlySmaller(Instance i1, Instance i2) {
+    // XXX implementation can be done faster
+    Coordinates c1 = new Coordinates(i1);
+    Coordinates c2 = new Coordinates(i2);
+
+    return c1.strictlySmaller(c2);
+  }
+
+  /**
+   * Compares two instances in the data space, this is, ignoring the class
+   * attribute.  An instance is smaller or equal than another instance
+   * if the same holds for the <code> Coordinates </code> based on
+   * these instances.
+   *
+   * @param i1 the first instance
+   * @param i2 the second instance
+   * @return <code> true </code> if the first instance is smaller or equal
+   * than the second instance, <code> false </code> otherwise
+   */
+  public static boolean smallerOrEqual(Instance i1,Instance i2) {
+    // XXX implementation can be done faster
+    Coordinates c1 = new Coordinates(i1);
+    Coordinates c2 = new Coordinates(i2);
+
+    return c1.smallerOrEqual(c2);
+  }
+
+  /**
+   * Checks if two instances are comparable in the data space, this is 
+   * ignoring the class attribute.  Two instances are comparable if the
+   * first is smaller or equal than the second, or the other way around.
+   *
+   * @param i1 the first instance
+   * @param i2 the second instance
+   * @return <code> true </code> if the given instances are comparable,
+   * <code> false </code> otherwise
+   * @throws IllegalArgumentException if the two instances don't have the
+   * same length
+   */
+  public static boolean comparable(Instance i1, Instance i2) throws IllegalArgumentException {
+    // XXX maybe we should think about using 'equalHeaders' of Instance
+    // to obtain a fool proof implementation
+    Coordinates c1 = new Coordinates(i1);
+    Coordinates c2 = new Coordinates(i2);
+
+    return c1.smallerOrEqual(c2) || c2.smallerOrEqual(c1);
+  }
+
+  /**
+   * Checks it two instances give rise to doubt.  There is doubt between
+   * two instances if their <code> Coordinates </code> are equal, but 
+   * their class value is different.
+   *
+   * @param i1 the first instance
+   * @param i2 the second instance
+   * @return <code> true </code> if there is doubt between the two 
+   * given instances, <code> false </code> otherwise
+   */
+  public static boolean doubt(Instance i1, Instance i2) {
+    // XXX use equalHeaders ?
+    if (i1.classValue() == i2.classValue()) {
+      return false;
+    }
+    Coordinates c1 = new Coordinates(i1);
+    Coordinates c2 = new Coordinates(i2);
+
+    return c1.equals(c2); 
+  }
+
+  /** 
+   *  Checks if two instances give rise to reversed preference.  
+   *  Two instances give rise to reversed preference in the data space,
+   *  if their <code> Coordinates </code> are comparable but different,
+   *  and their class values are not related in the same way.
+   *  
+   *  @param i1 the first instance
+   *  @param i2 the second instance
+   *  @return <code> true </code> if <code> i1 </code> and <code> i2 </code>
+   *  give rise to reversed preference, <code> false </code> otherwise
+   *  @throws IllegalArgumentException if the two instances don't have 
+   *  the same length
+   */
+  public static boolean reversedPreference(Instance i1, Instance i2) throws IllegalArgumentException {
+    // XXX should the implementation be made fool proof by use of 
+    // 'equalHeaders'?  It can also be speeded up I think.
+
+    if (i1.classValue() == i2.classValue()) {
+      return false;
+    }
+    Coordinates c1 = new Coordinates(i1);
+    Coordinates c2 = new Coordinates(i2);
+
+    if (i1.classValue() > i2.classValue() && c1.strictlySmaller(c2)) {
+      return true;
+    }
+    if (i2.classValue() > i1.classValue() && c2.strictlySmaller(c1)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /** 
+   *  Checks if the given data set is monotone.  We say that a data set
+   *  is monotone if it contains doubt nor reversed preferences.
+   *
+   *  @param instances the data set to be checked
+   *  @return <code> true </code> if the given data set if monotone,
+   *  <code> false </code> otherwise
+   */  
+  public static boolean isMonotone(Instances instances) {
+    int n = instances.numInstances();
+    for (int i = 0; i < n; i++) {
+      Instance i1 = instances.instance(i);
+      for (int j = i + 1; j < n; j++) {
+	if ( doubt(i1, instances.instance(j)) ||
+	    reversedPreference(i1, instances.instance(j))) {
+	  return false;
+	}
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Test if a set of instances is quasi monotone.  We say that a set
+   * of instances <code> S </code> is quasi monotone with respect to 
+   * a set of instances <code> D </code> iff 
+   * <code> [x,y] \cap D \neq \emptyset \implies class(x) \leq class(y).
+   * </code>  This implies that <code> D </code> itself is monotone.
+   *
+   * @param ground the instances playing the role of <code> D </code>
+   * @param other the instances playing the role of <code> S </code>
+   * @return true if the instances are quasi monotone, false otherwise
+   */
+  public static boolean isQuasiMonotone(Instances ground, Instances other) {
+    if (!isMonotone(ground)) {
+      return false;
+    }
+    Iterator it1 = new EnumerationIterator(ground.enumerateInstances());
+    while(it1.hasNext()) {
+      Instance inst1 = (Instance) it1.next();
+      Iterator it2 = new EnumerationIterator(other.enumerateInstances());
+      while(it2.hasNext()) {
+	Instance inst2 = (Instance) it2.next();
+	if (doubt(inst1, inst2) || reversedPreference(inst1, inst2)) {
+	  return false;
+	}
+      }
+    }
+    return true;
+  }
+
+  /** 
+   * Gather some statistics regarding reversed preferences. 
+   * 
+   * @param instances the instances to be examined
+   * @return array of length 3; position 0 indicates the number of 
+   *  couples that have reversed preference, position 1 the number of 
+   *  couples that are comparable, and position 2 the total
+   *  number of couples
+   * @see #reversedPreference(Instance, Instance)
+   */  
+  public static int[] nrOfReversedPreferences(Instances instances) {
+    int[] stats = new int[3];
+    int n = instances.numInstances();
+    stats[0] = 0;
+    stats[1] = 0;
+    // number of couples
+    stats[2] = n * (n - 1) / 2; 
+    for (int i = 0; i < n; i++) {
+      Instance i1 = instances.instance(i);
+      for (int j = i + 1; j < n; j++) {
+	Instance j1 = instances.instance(j);
+	if (comparable(i1, j1)) {
+	  stats[1]++;  // comparable
+	  if (reversedPreference(i1, j1)) {
+	    stats[0]++; // reversed preference
+	  }
+	}
+      }
+    }
+    return stats;
+  }
+
+  /**
+   * Find the number of stochastic reversed preferences in the dataset.
+   *
+   * @param instances the instances to be examined
+   * @return an array of integers containing at position
+   * <ul>
+   * <li> 0: number of different coordinates, this is the size of S_X </li> 
+   * <li> 1: number of couples showing reversed preference:<br> 
+   * <code> x &lt; y </code> and
+   *        <code> not (F_x leqstoch F_y) </code> </li>
+   * <li> 2: number of couples having<br>
+   *   <code> x &lt; y </code> and <code> F_y leqstoch F_x </code>
+   *                                  and <code> F_x neq F_y </code> </li>
+   * <li> 3: number of couples that are comparable <br>
+   *          <code> |\{ (x,y)\in S_X \times S_x | x &lt; y\}| </code> </li>
+   * <li> 4: number of couples in S_X </li>
+   * </ul>
+   * @throws IllegalArgumentException if there are no instances with
+   * a non-missing class value, or if the class is not set
+   */
+  public static int[] nrStochasticReversedPreference(Instances instances)
+    throws IllegalArgumentException {
+
+    if (instances.classIndex() < 0) {
+      throw new IllegalArgumentException("Class is not set");
+    }
+
+    // copy the dataset 
+    Instances data = new Instances(instances);
+
+    // new dataset where examples with missing class value are removed
+    data.deleteWithMissingClass();
+    if (data.numInstances() == 0) {
+      throw new IllegalArgumentException
+      ("No instances with a class value!");
+    }
+
+    // build the Map for the estimatedDistributions 
+    Map distributions = new HashMap(data.numInstances()/2);
+
+    // cycle through all instances 
+    Iterator i = 
+      new EnumerationIterator(instances.enumerateInstances());
+
+    while (i.hasNext()) { 
+      Instance instance = (Instance) i.next();
+      Coordinates c = new Coordinates(instance);
+
+      // get DiscreteEstimator from the map
+      DiscreteEstimator df = 
+	(DiscreteEstimator) distributions.get(c);
+
+      // if no DiscreteEstimator is present in the map, create one 
+      if (df == null) {
+	df = new DiscreteEstimator(instances.numClasses(), 0);
+      }
+      df.addValue(instance.classValue(),instance.weight()); // update
+      distributions.put(c,df); // put back in map
+    }
+
+
+    // build the map of cumulative distribution functions 
+    Map cumulativeDistributions = 
+      new HashMap(distributions.size());
+
+    // Cycle trough the map of discrete distributions, and create a new
+    // one containing cumulative discrete distributions
+    for (Iterator it=distributions.keySet().iterator();
+    it.hasNext();) {
+      Coordinates c = (Coordinates) it.next();
+      DiscreteEstimator df = 
+	(DiscreteEstimator) distributions.get(c);
+      cumulativeDistributions.put
+      (c, new CumulativeDiscreteDistribution(df));
+    }
+    int[] revPref = new int[5]; 
+    revPref[0] = cumulativeDistributions.size();
+    Iterator it = cumulativeDistributions.keySet().iterator();
+    while (it.hasNext()) {
+      Coordinates c1 = (Coordinates) it.next();
+      CumulativeDiscreteDistribution cdf1 = 
+	(CumulativeDiscreteDistribution) 
+	cumulativeDistributions.get(c1);
+      Iterator it2 = cumulativeDistributions.keySet().iterator();
+      while(it2.hasNext()) {
+	Coordinates c2 = (Coordinates) it2.next();
+	CumulativeDiscreteDistribution cdf2 = 
+	  (CumulativeDiscreteDistribution)
+	  cumulativeDistributions.get(c2);
+	if (c2.equals(c1)) {
+	  continue;
+	}
+
+	revPref[4]++;
+
+	if (c1.strictlySmaller(c2) == true) {
+	  revPref[3]++; //vergelijkbaar
+	  if (cdf1.stochasticDominatedBy(cdf2) == false ) {
+	    revPref[1]++;
+	    if (cdf2.stochasticDominatedBy(cdf1) == true) {
+	      revPref[2]++;
+	    }
+	  }
+	} 
+      }
+    }
+    revPref[4] /= 2; 
+    return revPref;
+  }
+
+  /**
+   * Counts the number of redundant pairs in the sense of OLM.
+   * Two instances are redundant if they are comparable and have the same
+   * class value.
+   * 
+   * @param instances the instances to be checked
+   * @return the number of redundant pairs in the given set of instances
+   */
+  public static int nrOfRedundant(Instances instances) {
+    int n = instances.numInstances();
+    int nrRedundant = 0;
+    for (int i = 0; i < n; i++) {
+      Instance i1 = instances.instance(i);
+      for (int j = i + 1; j < n; j++) {
+	Instance j1 = instances.instance(j);
+	if (j1.classValue() == i1.classValue() && comparable(i1, j1) ) {
+	  nrRedundant++; 
+	}
+      }
+    }
+
+    return nrRedundant;
+  }
+
+  /**
+   * Calulates the total loss over the <code> instances </code>, 
+   * using the trained <code> classifier </code> and the 
+   * specified <code> lossFunction. </code>  The instances 
+   * should not contain missing values in the class attribute.
+   *
+   * @param classifier the trained classifier to use
+   * @param instances the test instances
+   * @param lossFunction the loss function to use
+   * @return the total loss of all the instances using the given classifier and loss function
+   */
+  public static double totalLoss(Classifier classifier, Instances instances, 
+      NominalLossFunction lossFunction) {
+
+    double loss = 0;
+    int n = instances.numInstances();
+    for (int i = 0; i < n; i++) {
+      try {
+	loss += lossFunction.loss(instances.instance(i).classValue(), 
+	    classifier.classifyInstance(instances.instance(i)));
+      } catch (Exception e) { 
+	// what should we do here ?? 
+      }
+    }
+    return loss;
+  }
+
+
+  /**
+   * Classify a set of instances using a given classifier.  The class value
+   * of the instances are set.
+   *
+   * @param instances the instances to be classified
+   * @param classifier a built classifier 
+   * @throws Exception if one of the instances could no be classified
+   */
+  public static void classifyInstances(Instances instances, Classifier classifier)
+    throws Exception {
+    
+    Iterator it = new EnumerationIterator(instances.enumerateInstances());
+    while(it.hasNext()) {
+      Instance instance = (Instance) it.next();
+      instance.setClassValue(classifier.classifyInstance(instance));
+    }
+  }
+
+
+  /**
+   * Calculates the relation (poset) formed by the instances.
+   *
+   * @param instances the instances for which the poset is to be formed
+   * @return a <code> BooleanBitMatrix </code> for which position 
+   * <code> bm.get(i,j) == true </code> iff <code> 
+   * InstancesUtil.strictlySmaller(instances.instance(i), 
+   * instances.instance(j)) == true </code>
+   */
+  public static BooleanBitMatrix getBitMatrix(Instances instances) {
+    int numInstances = instances.numInstances();
+    BooleanBitMatrix bm = 
+      new BooleanBitMatrix(numInstances, numInstances);
+    for (int i = 0; i < numInstances; i++ ) {
+      Instance instance1 = instances.instance(i);
+      for (int j = 0; j < numInstances; j++) {
+	Instance instance2 = instances.instance(j);
+	if (InstancesUtil.strictlySmaller(instance1, instance2)) {
+	  bm.set(i, j); // arc from instance1 to instance2
+	}
+      }
+    }
+    return bm;
+  }
+
+  /**
+   * Calculatus the number of elements in the closed interval 
+   * <code> [low,up]. </code>  If the class index is set, then
+   * the class attribute does not play part in the calculations,
+   * this is we work in the data space.  The code also works with 
+   * numeric attributes, but is primarily intended for ordinal attributes.
+   *
+   * @param low the lower bound of the interval
+   * @param up the upper bound of the interval
+   * @return the size of the interval (in floating point format)
+   * @throws IllegalArgumentException if the given instances do not
+   * constitute an interval.
+   */
+  public static double numberInInterval(Instance low, Instance up)
+    throws IllegalArgumentException {
+    
+    Coordinates cLow = new Coordinates(low);
+    Coordinates cUp = new Coordinates(up);
+    if (cLow.smallerOrEqual(cUp) == false) {
+      throw new IllegalArgumentException
+      ("The given instances are not the bounds of an interval");
+    }
+    double number = 1;
+    int dim = cLow.dimension();
+    for (int i = 0; i < dim; i++) {
+      number *= (cUp.getValue(i) - cLow.getValue(i) + 1);
+    }
+    return number;
+  }
+
+
+  /**
+   * Calculatutes the number of vectors in the data space that are smaller 
+   * or equal than the given instance.
+   *
+   * @param instance the given instance
+   * @return the number of vectors in the data space smaller or equal 
+   * than the given instance
+   * @throws IllegalArgumentException if there are numeric attributes
+   */
+  public static double numberOfSmallerVectors(Instance instance) 
+    throws IllegalArgumentException {
+    
+    double[] values = InstancesUtil.toDataDouble(instance);
+    double nr = 1;
+
+    for (int i = 0; i < values.length; i++) {
+      if (instance.attribute(i).isNumeric()) {
+	throw new IllegalArgumentException
+	("Numeric attributes are not supported"); 
+      }
+      nr *= (values[i] + 1);
+    }
+
+    return nr;
+  }
+
+  /**
+   * Calculatutes the number of vectors in the data space that are 
+   * greater or equal than the given instance.
+   *
+   * @param instance the given instance
+   * @return the number of vectors in the data space greater of equal
+   * than the given instance
+   * @throws IllegalArgumentException if there are numeric attributes
+   */
+  public static double numberOfGreaterVectors(Instance instance) 
+    throws IllegalArgumentException {
+    
+    double[] values = InstancesUtil.toDataDouble(instance);
+    double nr = 1;
+
+    for (int i = 0; i < values.length; i++) {
+      if (instance.attribute(i).isNumeric()) {
+	throw new IllegalArgumentException
+	("Numeric attributes are not supported"); 
+      }
+      nr *= (instance.attribute(i).numValues() - values[i]);
+    }
+
+    return nr;
+  }
+
+  /**
+   * Write the instances in ARFF-format to the indicated 
+   * <code> BufferedWriter </code>.
+   * @param instances the instances to write
+   * @param file the <code> BufferedWriter </code> to write to
+   * @throws IOException if something goes wrong while writing the instances
+   */
+  public static void write(Instances instances, BufferedWriter file)
+    throws IOException{
+    
+    file.write(instances.toString()); // XXX can probably be done better 
+  }
+
+
+  /**
+   * Return a histogram of the values for the specified attribute.
+   *
+   * @param instances the instances
+   * @param attributeIndex the attribute to consider
+   * @return a <code> DiscreteEstimator </code> where the <code>i</code>th
+   * @throws IllegalArgumentException if the attribute at the specified 
+   * index is numeric
+   */
+  public static DiscreteEstimator countValues(Instances instances, int attributeIndex) 
+    throws IllegalArgumentException{
+    
+    int numValues = instances.attribute(attributeIndex).numValues();
+    if (numValues == 0) {
+      throw new IllegalArgumentException
+      ("Can't create histogram for numeric attribute");
+    }
+
+    DiscreteEstimator de = new DiscreteEstimator(numValues, false);
+    Iterator it = new EnumerationIterator(instances.enumerateInstances());
+    while (it.hasNext()) {
+      Instance instance = (Instance) it.next();
+      if (!instance.isMissing(attributeIndex)) {
+	de.addValue(instance.value(attributeIndex), instance.weight());
+      }
+    }
+    return de;
+  }
+
+  /**
+   * Create, without replacement, a random subsample of the given size 
+   * from the given instances.
+   *
+   * @param instances the instances to sample from
+   * @param size the requested size of the sample
+   * @param random the random generator to use
+   * @return a sample of the requested size, drawn from the given
+   * instances without replacement
+   * @throws IllegalArgumentException if the size exceeds the number
+   * of instances
+   */
+  public static Instances sampleWithoutReplacement(
+      Instances instances, int size, Random random) {
+    
+    if (size > instances.numInstances()) {
+      throw new IllegalArgumentException
+      ("Size of requested sample exceeds number of instances");
+    }
+
+    int numInstances = instances.numInstances();
+    int[] indices = new int[instances.numInstances()];
+    for (int i = 0; i < numInstances; i++) {
+      indices[i] = i;
+    }
+
+    Instances sample = new Instances(instances, size);
+    int index;
+    for (int i = 0; i < size; i++) {
+      index = random.nextInt(numInstances--);
+      sample.add(instances.instance(indices[index]));
+      swap(indices, index, numInstances);
+    }
+    return sample;
+  }
+
+  /**
+   * Swaps two elements of the given array.
+   *
+   * @param aa the array 
+   * @param i the index of the first element
+   * @param j the index of the second element
+   */
+  final private static void swap(int[] aa, int i, int j) {
+    int tmp = aa[i];
+    aa[i] = aa[j];
+    aa[j] = tmp;
+  }
+  /**
+   * Generates a random sample of instances.  Each attribute must be nominal, and the 
+   * class labels are not set.
+   * 
+   * @param headerInfo Instances whose header information is used to determine how the 
+   * set of returned instances will look
+   * @param numberOfExamples the desired size of the returned set
+   * @param random the random number generator to use
+   * @return a set of Instances containing the random sample.
+   * @throws IllegalArgumentException if numeric attributes are given
+   */
+  public static Instances generateRandomSample(
+      Instances headerInfo, int numberOfExamples, Random random) 
+    throws IllegalArgumentException {
+    
+    int n = headerInfo.numAttributes();
+    double[] info = new double[n];
+    int classIndex = headerInfo.classIndex();
+    for (int i = 0; i < n; i++) {
+      info[i] = headerInfo.attribute(i).numValues();
+      if (i != classIndex && info[i] == 0) {
+	throw new IllegalArgumentException
+	("Numeric attributes are currently not supported");
+      }
+    }
+    Instances sample = new Instances(headerInfo, numberOfExamples);
+    sample.setRelationName(headerInfo.relationName() + 
+	".random.sample.of." + numberOfExamples);
+    for (int i = 0; i < numberOfExamples; i++) {
+      sample.add(randomSample(info, classIndex, random));
+    }
+    return sample;
+  }
+  /**
+   * Generates a random instance.
+   * 
+   * @param info array that gives for each attribute the number of possible values
+   * @param classIndex the index of the class attribute
+   * @param random the random number generator used
+   * @return a random instance
+   */
+  private static Instance randomSample(double[] info, 
+      int classIndex, Random random) {
+    
+    double[] attValues = new double[info.length];
+    for (int i = 0; i < attValues.length; i++) {
+      if (i != classIndex) {
+	attValues[i] = random.nextInt( (int) info[i]); 
+      }
+    }
+    return new DenseInstance(1, attValues);
+  }
+
+
+
+  /**
+   * Returns an array containing the attribute values (in internal floating 
+   * point format) of the given instance in data space, this is, the class 
+   * attribute (if any) is removed.
+   *
+   * @param instance the instance to get the attribute values from
+   * @return array of doubles containing the attribute values
+   */
+  public static double[] toDataDouble(Instance instance) {
+    double[] vector = null;
+    int classIndex = instance.classIndex();
+    if(classIndex >= 0) {
+      vector = new double[instance.numAttributes() - 1];
+    } else {
+      vector = new double[instance.numAttributes()];
+    }
+    int index = 0;
+    for (int i = 0; i < instance.numAttributes(); i++) {
+      if(i != classIndex) {
+	vector[index++] = instance.value(i);
+      }
+    }
+    return vector;
+  }
+
+  /**
+   * Computes the minimal extension for a given instance.
+   *
+   * @param instances the set of instances 
+   * @param instance the instance for which the minimal extension is to be
+   * calculated
+   * @return the value of the minimal extension, in internal floating point
+   * format
+   */
+  public static double minimalExtension(Instances instances, Instance instance) {
+    return minimalExtension(instances, instance, 0);
+  }
+
+  /**
+   * Computes the minimal extension of a given instance, but the 
+   * minimal value returned is <code> minValue. </code>  This method
+   * may have its applications when the training set is divided into
+   * multiple Instances objects.
+   *
+   * @param instances the set of instances
+   * @param instance the instance for which the minimal extension is to 
+   * be calculated
+   * @param minValue a double indicating the minimal value that should 
+   * be returned
+   * @return the label of the minimal extension, in internal floating point format
+   */
+  public static double minimalExtension(
+      Instances instances, Instance instance, double minValue) {
+    
+    double value = minValue;
+
+    Iterator it = 
+      new EnumerationIterator(instances.enumerateInstances());
+    while(it.hasNext()) {
+      Instance tmp = (Instance) it.next();
+      if (tmp.classValue() > value 
+	  && InstancesUtil.smallerOrEqual(tmp, instance) ) {
+	value = tmp.classValue();
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Computes the maximal extension for a given instance.
+   *
+   * @param instances the set of instances 
+   * @param instance the instance for which the minimal extension is to be
+   * calculated
+   * @return the value of the minimal extension, in internal floating point
+   * format
+   */
+  public static double maximalExtension(Instances instances, Instance instance) {
+    return maximalExtension(instances, instance, instances.numClasses() - 1);
+  }
+
+  /**
+   * Computes the maximal extension of a given instance, but the 
+   * maximal value returned is <code> maxValue. </code>  This method
+   * may have its applications when the training set is divided into
+   * multiple Instances objects.
+   *
+   * @param instances the set of instances
+   * @param instance the instance for which the maximal extension is to 
+   * be calculated
+   * @param maxValue a double indicating the maximal value that should 
+   * be returned
+   * @return the value of the minimal extension, in internal floating point
+   * format
+   */
+  public static double maximalExtension(
+      Instances instances, Instance instance, double maxValue) {
+    
+    double value = maxValue;
+
+    Iterator it = 
+      new EnumerationIterator(instances.enumerateInstances());
+    while(it.hasNext()) {
+      Instance tmp = (Instance) it.next();
+      if (tmp.classValue() < value 
+	  && InstancesUtil.smallerOrEqual(instance, tmp) ) {
+	value = tmp.classValue();
+      }
+    }
+    return value;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/MultiDimensionalSort.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/MultiDimensionalSort.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/MultiDimensionalSort.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MultiDimensionalSort.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/** 
+ * Class for doing multidimensional sorting, using an array of
+ * <code> Comparator. </code>
+ * The goal is to sort an array topologically.  If <code> o1 </code>
+ * and <code> o2 </code> are two objects of the array <code> a, </code>
+ * and for all valid indices <code> i </code> in the array <code> c </code>
+ * if holds that <code> c[i].compare(o1,o2) &lt; 0 </code> then
+ * <code> o1 </code> comes before <code> o2 </code> in the sorted array.
+ * <p>
+ * A typical is the sorting of vectors in an n-dimensional space,
+ * where the ordering is determined by the product ordering.
+ * </p>
+ * <p>
+ * This implementation is part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class MultiDimensionalSort
+  implements RevisionHandler {
+
+  /** 
+   * Sort an array using different comparators.
+   *
+   * @param a the array to be sorted.  The sorted array is returned 
+   * in the array <code> a </code> itself.
+   * @param c an array holding the different comparators
+   */
+  public static void multiDimensionalSort (Object[] a, Comparator[] c) {
+    multiDimensionalSort(a, 0, a.length, c);
+  }
+
+  /** 
+   * Sort part of an array using different comparators.
+   * 
+   * @param a the array to be sorted, the indicated part of the array will
+   * be replaced by the sorted elements
+   * @param fromIndex index of the first element to be sorted (inclusive)
+   * @param toIndex index of the last element to be sorted (exclusive)
+   * @param c array holding the different comparators
+   * @throws IllegalArgumentException if <code> fromIndex &gt; toIndex </code>
+   */
+  public static void multiDimensionalSort(Object[] a, int fromIndex,
+      int toIndex, Comparator[] c) 
+    throws IllegalArgumentException {
+    
+    if (fromIndex > toIndex) {
+      throw new IllegalArgumentException
+      ("Illegal range: fromIndex can be at most toIndex");
+    }
+    multiDimensionalSort(a, fromIndex, toIndex, c, 0);
+  }
+
+  /**
+   * Do the actual sorting in a recursive way.
+   * 
+   * @param a the array to be sorted, the indicated part of the array will
+   * be replaced by the sorted elements
+   * @param fromIndex index of the first element to be sorted (inclusive)
+   * @param toIndex index of the last element to be sorted (exclusive)
+   * @param c array holding the different comparators
+   * @param depth the index of the comparator to use
+   */
+  private static void multiDimensionalSort(Object[] a, int fromIndex,
+      int toIndex, Comparator[] c, int depth) {
+
+    if (depth == c.length) {
+      return; // maximum depth reached
+    }
+
+    Comparator comp = c[depth]; // comparator to use
+    Arrays.sort(a, fromIndex, toIndex, comp); // sort part of the array 
+
+    // look for breakpoints in the array and sort recursively
+    int mark = fromIndex;
+    for (int i = fromIndex + 1; i < toIndex; i++) {
+      if (comp.compare(a[i - 1], a[i]) != 0) {
+	// a[i] is different from a[i-1], breakpoint detected
+	multiDimensionalSort(a, mark, i, c, depth + 1);
+	mark = i;
+      }
+    }
+    // sort the last part 
+    multiDimensionalSort(a, mark, toIndex, c, depth + 1);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/NominalLossFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/NominalLossFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/NominalLossFunction.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NominalLossFunction.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+/**
+ * Interface for incorporating different loss functions.
+ * <p>
+ * This interface contains only one method, namely <code> loss
+ * </code> that measures the error between an actual class
+ * value <code> actual </code> and a predicted value <code>
+ * predicted. </code>  It is understood that the return value
+ * of this method is always be positive and that it is zero
+ * if and only if the actual and the predicted value coincide.
+ * </p>
+ * <p>
+ * This implementation is done as part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public interface NominalLossFunction {
+
+  /**
+   * Calculate the loss between an actual and a predicted class value.
+   *
+   * @param actual the actual class value
+   * @param predicted the predicted class value
+   * @return a measure for the error of making the prediction
+   * <code> predicted </code> instead of <code> actual </code>
+   */
+  public double loss(double actual, double predicted);
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/OSDLCore.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/OSDLCore.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/OSDLCore.java	(revision 29)
@@ -0,0 +1,1746 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OSDLCore.java
+ *    Copyright (C) 2004 Stijn Lievens
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.estimators.DiscreteEstimator;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This class is an implementation of the Ordinal Stochastic Dominance Learner.<br/>
+ * Further information regarding the OSDL-algorithm can be found in:<br/>
+ * <br/>
+ * S. Lievens, B. De Baets, K. Cao-Van (2006). A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting. Annals of Operations Research..<br/>
+ * <br/>
+ * Kim Cao-Van (2003). Supervised ranking: from semantics to algorithms.<br/>
+ * <br/>
+ * Stijn Lievens (2004). Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken.<br/>
+ * <br/>
+ * For more information about supervised ranking, see<br/>
+ * <br/>
+ * http://users.ugent.be/~slievens/supervised_ranking.php
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Lievens2006,
+ *    author = {S. Lievens and B. De Baets and K. Cao-Van},
+ *    journal = {Annals of Operations Research},
+ *    title = {A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting},
+ *    year = {2006}
+ * }
+ * 
+ * &#64;phdthesis{Cao-Van2003,
+ *    author = {Kim Cao-Van},
+ *    school = {Ghent University},
+ *    title = {Supervised ranking: from semantics to algorithms},
+ *    year = {2003}
+ * }
+ * 
+ * &#64;mastersthesis{Lievens2004,
+ *    author = {Stijn Lievens},
+ *    school = {Ghent University},
+ *    title = {Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken},
+ *    year = {2004}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -C &lt;REG|WSUM|MAX|MED|RMED&gt;
+ *  Sets the classification type to be used.
+ *  (Default: MED)</pre>
+ * 
+ * <pre> -B
+ *  Use the balanced version of the Ordinal Stochastic Dominance Learner</pre>
+ * 
+ * <pre> -W
+ *  Use the weighted version of the Ordinal Stochastic Dominance Learner</pre>
+ * 
+ * <pre> -S &lt;value of interpolation parameter&gt;
+ *  Sets the value of the interpolation parameter (not with -W/T/P/L/U)
+ *  (default: 0.5).</pre>
+ * 
+ * <pre> -T
+ *  Tune the interpolation parameter (not with -W/S)
+ *  (default: off)</pre>
+ * 
+ * <pre> -L &lt;Lower bound for interpolation parameter&gt;
+ *  Lower bound for the interpolation parameter (not with -W/S)
+ *  (default: 0)</pre>
+ * 
+ * <pre> -U &lt;Upper bound for interpolation parameter&gt;
+ *  Upper bound for the interpolation parameter (not with -W/S)
+ *  (default: 1)</pre>
+ * 
+ * <pre> -P &lt;Number of parts&gt;
+ *  Determines the step size for tuning the interpolation
+ *  parameter, nl. (U-L)/P (not with -W/S)
+ *  (default: 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5987 $
+ */
+public abstract class OSDLCore
+  extends AbstractClassifier 
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -9209888846680062897L;
+
+  /**
+   * Constant indicating that the classification type is 
+   * regression (probabilistic weighted sum).
+   */
+  public static final int CT_REGRESSION = 0;
+
+  /**
+   * Constant indicating that the classification type is  
+   * the probabilistic weighted sum.
+   */
+  public static final int CT_WEIGHTED_SUM = 1;
+
+  /**
+   * Constant indicating that the classification type is  
+   * the mode of the distribution.
+   */
+  public static final int CT_MAXPROB = 2;
+
+  /** 
+   * Constant indicating that the classification type is  
+   * the median.
+   */
+  public static final int CT_MEDIAN = 3;
+
+  /** 
+   *  Constant indicating that the classification type is
+   *  the median, but not rounded to the nearest class.
+   */
+  public static final int CT_MEDIAN_REAL = 4;
+
+  /** the classification types */
+  public static final Tag[] TAGS_CLASSIFICATIONTYPES = {
+    new Tag(CT_REGRESSION, "REG", "Regression"),
+    new Tag(CT_WEIGHTED_SUM, "WSUM", "Weighted Sum"),
+    new Tag(CT_MAXPROB, "MAX", "Maximum probability"),
+    new Tag(CT_MEDIAN, "MED", "Median"),
+    new Tag(CT_MEDIAN_REAL, "RMED", "Median without rounding")
+  };
+
+  /**
+   * The classification type, by default set to CT_MEDIAN.
+   */
+  private int m_ctype = CT_MEDIAN;
+
+  /** 
+   * The training examples.
+   */
+  private Instances m_train;
+
+  /** 
+   * Collection of (Coordinates,DiscreteEstimator) pairs.
+   * This Map is build from the training examples.
+   * The DiscreteEstimator is over the classes.
+   * Each DiscreteEstimator indicates how many training examples
+   * there are with the specified classes.
+   */
+  private Map m_estimatedDistributions;
+
+
+  /** 
+   * Collection of (Coordinates,CumulativeDiscreteDistribution) pairs.
+   * This Map is build from the training examples, and more 
+   * specifically from the previous map.  
+   */
+  private Map m_estimatedCumulativeDistributions;
+
+
+  /** 
+   * The interpolationparameter s.  
+   * By default set to 1/2.
+   */
+  private double m_s = 0.5;
+
+  /** 
+   * Lower bound for the interpolationparameter s.
+   * Default value is 0.
+   */
+  private double m_sLower = 0.;
+
+  /** 
+   * Upper bound for the interpolationparameter s.
+   * Default value is 1.
+   */
+  private double m_sUpper = 1.0;
+
+  /** 
+   * The number of parts the interval [m_sLower,m_sUpper] is 
+   * divided in, while searching for the best parameter s.
+   * This thus determines the granularity of the search.
+   * m_sNrParts + 1 values of the interpolationparameter will
+   * be tested.
+   */
+  private int m_sNrParts = 10;
+
+  /** 
+   * Indicates whether the interpolationparameter is to be tuned 
+   * using leave-one-out cross validation.  <code> true </code> if
+   * this is the case (default is <code> false </code>).
+   */
+  private boolean m_tuneInterpolationParameter = false;
+
+  /**
+   * Indicates whether the current value of the interpolationparamter
+   * is valid.  More specifically if <code> 
+   * m_tuneInterpolationParameter == true </code>, and 
+   * <code> m_InterpolationParameter == false </code>, 
+   * this means that the current interpolation parameter is not valid.
+   * This parameter is only relevant if <code> m_tuneInterpolationParameter
+   * == true </code>.
+   *
+   * If <code> m_tuneInterpolationParameter </code> and <code>
+   * m_interpolationParameterValid </code> are both <code> true </code>,
+   * then <code> m_s </code> should always be between 
+   * <code> m_sLower </code> and <code> m_sUpper </code>. 
+   */
+  private boolean m_interpolationParameterValid = false;
+
+
+  /** 
+   * Constant to switch between balanced and unbalanced OSDL.
+   * <code> true </code> means that one chooses balanced OSDL
+   * (default: <code> false </code>).
+   */
+  private boolean m_balanced = false;
+
+  /** 
+   * Constant to choose the weighted variant of the OSDL algorithm.
+   */
+  private boolean m_weighted = false;
+
+  /**
+   * Coordinates representing the smallest element of the data space.
+   */
+  private Coordinates smallestElement;
+
+  /**
+   * Coordinates representing the biggest element of the data space.
+   */
+  private Coordinates biggestElement;
+
+  /**
+   * Returns a string describing the classifier.
+   * @return a description suitable for displaying in the 
+   * explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "This class is an implementation of the Ordinal Stochastic "
+    + "Dominance Learner.\n" 
+    + "Further information regarding the OSDL-algorithm can be found in:\n\n"
+    + getTechnicalInformation().toString() + "\n\n"
+    + "For more information about supervised ranking, see\n\n"
+    + "http://users.ugent.be/~slievens/supervised_ranking.php";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "S. Lievens and B. De Baets and K. Cao-Van");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting");
+    result.setValue(Field.JOURNAL, "Annals of Operations Research");
+
+    additional = result.add(Type.PHDTHESIS);
+    additional.setValue(Field.AUTHOR, "Kim Cao-Van");
+    additional.setValue(Field.YEAR, "2003");
+    additional.setValue(Field.TITLE, "Supervised ranking: from semantics to algorithms");
+    additional.setValue(Field.SCHOOL, "Ghent University");
+
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Stijn Lievens");
+    additional.setValue(Field.YEAR, "2004");
+    additional.setValue(Field.TITLE, "Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken");
+    additional.setValue(Field.SCHOOL, "Ghent University");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * Classifies a given instance using the current settings 
+   * of the classifier.
+   *
+   * @param instance the instance to be classified
+   * @throws Exception if for some reason no distribution
+   *         could be predicted
+   * @return the classification for the instance.  Depending on the
+   * settings of the classifier this is a double representing 
+   * a classlabel (internal WEKA format) or a real value in the sense
+   * of regression.
+   */
+  public double classifyInstance(Instance instance)
+    throws Exception { 
+    
+    try {
+      return classifyInstance(instance, m_s, m_ctype);
+    } catch (IllegalArgumentException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** 
+   * Classifies a given instance using the settings in the paramater
+   * list.  This doesn't change the internal settings of the classifier.
+   * In particular the interpolationparameter <code> m_s </code>
+   * and the classification type <code> m_ctype </code> are not changed.
+   *
+   * @param instance the instance to be classified
+   * @param s the value of the interpolationparameter to be used
+   * @param ctype the classification type to be used  
+   * @throws IllegalStateException for some reason no distribution
+   *         could be predicted
+   * @throws IllegalArgumentException if the interpolation parameter or the
+   *         classification type is not valid 
+   * @return the label assigned to the instance.  It is given in internal floating point format.
+   */
+  private double classifyInstance(Instance instance, double s, int ctype) 
+    throws IllegalArgumentException, IllegalStateException {
+    
+    if (s < 0 || s > 1) {
+      throw new IllegalArgumentException("Interpolation parameter is not valid " + s);
+    }
+
+    DiscreteDistribution dist = null;
+    if (!m_balanced) {
+      dist = distributionForInstance(instance, s);
+    } else {
+      dist = distributionForInstanceBalanced(instance, s);
+    }
+
+    if (dist == null) {
+      throw new IllegalStateException("Null distribution predicted");
+    }
+
+    double value = 0;
+    switch(ctype) {
+      case CT_REGRESSION:
+      case CT_WEIGHTED_SUM:
+	value = dist.mean();
+	if (ctype == CT_WEIGHTED_SUM) {
+	  value = Math.round(value);
+	}
+	break;
+
+      case CT_MAXPROB:
+	value = dist.modes()[0];
+	break;
+
+      case CT_MEDIAN:
+      case CT_MEDIAN_REAL:
+	value = dist.median();
+	if (ctype == CT_MEDIAN) {
+	  value = Math.round(value);
+	}
+	break;
+
+      default:
+	throw new IllegalArgumentException("Not a valid classification type!"); 
+    }
+    return value;
+  }
+
+  /**
+   * Calculates the class probabilities for the given test instance.
+   * Uses the current settings of the parameters if these are valid.
+   * If necessary it updates the interpolationparameter first, and hence 
+   * this may change the classifier.
+   *
+   * @param instance the instance to be classified
+   * @return an array of doubles representing the predicted 
+   * probability distribution over the class labels
+   */
+  public double[] distributionForInstance(Instance instance) {
+
+    if (m_tuneInterpolationParameter 
+	&& !m_interpolationParameterValid) {
+      tuneInterpolationParameter();
+    }
+
+    if (!m_balanced) {
+      return distributionForInstance(instance, m_s).toArray();
+    } 
+    // balanced variant
+    return distributionForInstanceBalanced(instance, m_s).toArray();
+  }
+
+  /**
+   * Calculates the cumulative class probabilities for the given test 
+   * instance. Uses the current settings of the parameters if these are 
+   * valid. If necessary it updates the interpolationparameter first, 
+   * and hence this may change the classifier.
+   *
+   * @param instance the instance to be classified
+   * @return an array of doubles representing the predicted 
+   * cumulative probability distribution over the class labels
+   */
+  public double[] cumulativeDistributionForInstance(Instance instance) {
+
+    if (m_tuneInterpolationParameter 
+	&& !m_interpolationParameterValid) {
+      tuneInterpolationParameter();
+    }
+
+    if (!m_balanced) {
+      return cumulativeDistributionForInstance(instance, m_s).toArray();
+    } 
+    return cumulativeDistributionForInstanceBalanced(instance, m_s).toArray();
+  }
+
+  /**
+   * Calculates the class probabilities for the given test instance.
+   * Uses the interpolation parameter from the parameterlist, and
+   * always performs the ordinary or weighted OSDL algorithm,
+   * according to the current settings of the classifier.
+   * This method doesn't change the classifier.  
+   *
+   * @param instance the instance to classify
+   * @param s value of the interpolationparameter to use
+   * @return the calculated distribution
+   */
+  private DiscreteDistribution distributionForInstance(Instance instance, double s) {
+    return new DiscreteDistribution(cumulativeDistributionForInstance(instance, s));
+  }
+
+  /**
+   * Calculates the class probabilities for the given test 
+   * instance. Uses the interpolationparameter from the parameterlist, and
+   * always performs the balanced OSDL algorithm.
+   * This method doesn't change the classifier.  
+   *
+   * @param instance the instance to classify
+   * @param s value of the interpolationparameter to use
+   * @return the calculated distribution
+   */
+  private DiscreteDistribution distributionForInstanceBalanced(
+      Instance instance, double s) {
+    
+    return new DiscreteDistribution(cumulativeDistributionForInstanceBalanced(instance,s));
+  }
+
+  /**
+   * Calculates the cumulative class probabilities for the given test 
+   * instance. Uses the interpolationparameter from the parameterlist, and
+   * always performs the ordinary or weighted OSDL algorithm,
+   * according to the current settings of the classifier.
+   * This method doesn't change the classifier.  
+   *
+   * @param instance the instance to classify
+   * @param s value of the interpolationparameter to use
+   * @return the calculated distribution
+   */
+  private CumulativeDiscreteDistribution cumulativeDistributionForInstance(
+      Instance instance, double s) {
+    
+    Coordinates xc = new Coordinates(instance);
+    int n = instance.numClasses();
+    int nrSmaller = 0; 
+    int nrGreater = 0;
+
+    if (!containsSmallestElement()) {
+      // corresponds to adding the minimal element to the data space
+      nrSmaller = 1; // avoid division by zero
+    }
+
+    if (!containsBiggestElement()) {
+      // corresponds to adding the maximal element to the data space
+      nrGreater = 1; // avoid division by zero	
+    }
+
+
+    // Create fMin and fMax 
+    CumulativeDiscreteDistribution fMin =
+      DistributionUtils.getMinimalCumulativeDiscreteDistribution(n);
+    CumulativeDiscreteDistribution fMax =
+      DistributionUtils.getMaximalCumulativeDiscreteDistribution(n);
+
+    // Cycle through all the map of cumulative distribution functions
+    for (Iterator i = m_estimatedCumulativeDistributions.keySet().iterator();
+    i.hasNext(); ) {
+      Coordinates yc = (Coordinates) i.next();
+      CumulativeDiscreteDistribution cdf = 
+	(CumulativeDiscreteDistribution) 
+	m_estimatedCumulativeDistributions.get(yc);
+
+      if (yc.equals(xc)) {
+	nrSmaller++;
+	fMin = DistributionUtils.takeMin(fMin,cdf);
+	nrGreater++;
+	fMax = DistributionUtils.takeMax(fMax,cdf);
+      } else if (yc.strictlySmaller(xc)) {
+	nrSmaller++;
+	fMin = DistributionUtils.takeMin(fMin,cdf);
+      } else if (xc.strictlySmaller(yc)) {
+	nrGreater++;
+	fMax = DistributionUtils.takeMax(fMax,cdf);
+      }
+    }
+
+    if (m_weighted) {
+      s = ( (double) nrSmaller) / (nrSmaller + nrGreater);
+      if (m_Debug) {
+	System.err.println("Weighted OSDL: interpolation parameter"
+	    + " is s = " + s);
+      }
+    }
+
+    // calculate s*fMin + (1-s)*fMax
+    return DistributionUtils.interpolate(fMin, fMax, 1 - s);
+  }
+
+  /**
+   * @return true if the learning examples contain an element for which 
+   * the coordinates are the minimal element of the data space, false 
+   * otherwise
+   */
+  private boolean containsSmallestElement() {
+    return m_estimatedCumulativeDistributions.containsKey(smallestElement);	
+  }
+
+  /**
+   * @return true if the learning examples contain an element for which 
+   * the coordinates are the maximal element of the data space, false 
+   * otherwise
+   */
+  private boolean containsBiggestElement() {
+    return m_estimatedCumulativeDistributions.containsKey(biggestElement);	
+  }
+
+
+  /**
+   * Calculates the cumulative class probabilities for the given test 
+   * instance. Uses the interpolationparameter from the parameterlist, and
+   * always performs the single or double balanced OSDL algorithm.
+   * This method doesn't change the classifier.  
+   *
+   * @param instance the instance to classify
+   * @param s value of the interpolationparameter to use
+   * @return the calculated distribution
+   */
+  private CumulativeDiscreteDistribution cumulativeDistributionForInstanceBalanced(
+      Instance instance, double s) {
+
+    Coordinates xc = new Coordinates(instance);
+    int n = instance.numClasses();
+
+    // n_m[i] represents the number of examples smaller or equal
+    // than xc and with a class label strictly greater than i
+    int[] n_m = new int[n];
+
+    // n_M[i] represents the number of examples greater or equal
+    // than xc and with a class label smaller or equal than i
+    int[] n_M = new int[n];
+
+    // Create fMin and fMax 
+    CumulativeDiscreteDistribution fMin =
+      DistributionUtils.getMinimalCumulativeDiscreteDistribution(n);
+    CumulativeDiscreteDistribution fMax =
+      DistributionUtils.getMaximalCumulativeDiscreteDistribution(n);
+
+    // Cycle through all the map of cumulative distribution functions
+    for (Iterator i = 
+      m_estimatedCumulativeDistributions.keySet().iterator();
+    i.hasNext(); ) {
+      Coordinates yc = (Coordinates) i.next();
+      CumulativeDiscreteDistribution cdf = 
+	(CumulativeDiscreteDistribution) 
+	m_estimatedCumulativeDistributions.get(yc);
+
+      if (yc.equals(xc)) {
+	// update n_m and n_M
+	DiscreteEstimator df = 
+	  (DiscreteEstimator) m_estimatedDistributions.get(yc);
+	updateN_m(n_m,df);
+	updateN_M(n_M,df);
+
+	fMin = DistributionUtils.takeMin(fMin,cdf);
+	fMax = DistributionUtils.takeMax(fMax,cdf);
+      } else if (yc.strictlySmaller(xc)) {
+	// update n_m 
+	DiscreteEstimator df = 
+	  (DiscreteEstimator) m_estimatedDistributions.get(yc);
+	updateN_m(n_m, df);
+	fMin = DistributionUtils.takeMin(fMin,cdf);
+      }
+      else if (xc.strictlySmaller(yc)) {
+	// update n_M
+	DiscreteEstimator df = 
+	  (DiscreteEstimator) m_estimatedDistributions.get(yc);
+	updateN_M(n_M, df);
+	fMax = DistributionUtils.takeMax(fMax,cdf);
+      }
+    }
+
+    double[] dd = new double[n];
+
+    // for each label decide what formula to use, either using
+    // n_m[i] and n_M[i] (if fMin[i]<fMax[i]) or using the
+    // interpolationparameter s or using the double balanced version
+    for (int i = 0; i < n; i++) {
+      double fmin = fMin.getCumulativeProbability(i);
+      double fmax = fMax.getCumulativeProbability(i);
+
+      if (m_weighted == true) { // double balanced version
+	if (fmin < fmax) { // reversed preference
+	  dd[i] =  (n_m[i] * fmin + n_M[i] * fmax) 
+	  / (n_m[i] + n_M[i]);
+	} else {
+	  if (n_m[i] + n_M[i] == 0) { // avoid division by zero
+	    dd[i] = s * fmin + (1 - s) * fmax;
+	  } else {
+	    dd[i] = (n_M[i] * fmin + n_m[i] * fmax) 
+	    / (n_m[i] + n_M[i]) ;
+	  }
+	}
+      } else {  // singly balanced version
+	dd[i] = (fmin < fmax) 
+	? (n_m[i] * fmin + n_M[i] * fmax) / (n_m[i] + n_M[i])
+	    : s * fmin + (1 - s) * fmax;
+      }
+    } try {
+      return new CumulativeDiscreteDistribution(dd);
+    } catch (IllegalArgumentException e) {
+      // this shouldn't happen.
+      System.err.println("We tried to create a cumulative "
+	  + "discrete distribution from the following array");
+      for (int i = 0; i < dd.length; i++) {
+	System.err.print(dd[i] + " ");
+      }
+      System.err.println();
+      throw new AssertionError(dd);
+    }
+  }
+
+
+  /**
+   * Update the array n_m using the given <code> DiscreteEstimator </code>.
+   * 
+   * @param n_m the array n_m that will be updated.
+   * @param de the <code> DiscreteEstimator </code> that gives the 
+   *        count over the different class labels.
+   */
+  private void updateN_m(int[] n_m, DiscreteEstimator de) {
+    int[] tmp = new int[n_m.length];
+
+    // all examples have a class labels strictly greater 
+    // than 0, except those that have class label 0.
+    tmp[0] = (int) de.getSumOfCounts() - (int) de.getCount(0);
+    n_m[0] += tmp[0];
+    for (int i = 1; i < n_m.length; i++) {
+
+      // the examples with a class label strictly greater
+      // than i are exactly those that have a class label strictly
+      // greater than i-1, except those that have class label i.
+      tmp[i] = tmp[i - 1] - (int) de.getCount(i);
+      n_m[i] += tmp[i];
+    }
+
+    if (n_m[n_m.length - 1] != 0) {
+      // this shouldn't happen
+      System.err.println("******** Problem with n_m in " 
+	  + m_train.relationName());
+      System.err.println("Last argument is non-zero, namely : " 
+	  + n_m[n_m.length - 1]);
+    }
+  }
+
+  /**
+   * Update the array n_M using the given <code> DiscreteEstimator </code>.
+   * 
+   * @param n_M the array n_M that will be updated.
+   * @param de the <code> DiscreteEstimator </code> that gives the 
+   *        count over the different class labels.
+   */
+  private void updateN_M(int[] n_M, DiscreteEstimator de) {
+    int n = n_M.length;
+    int[] tmp = new int[n];
+
+    // all examples have a class label smaller or equal
+    // than n-1 (which is the maximum class label)
+    tmp[n - 1] = (int) de.getSumOfCounts();
+    n_M[n - 1] += tmp[n - 1];
+    for (int i = n - 2; i >= 0; i--) {
+
+      // the examples with a class label smaller or equal 
+      // than i are exactly those that have a class label
+      // smaller or equal than i+1, except those that have 
+      // class label i+1.
+      tmp[i] = tmp[i + 1] - (int) de.getCount(i + 1);
+      n_M[i] += tmp[i];
+    }
+  }
+
+  /**
+   * Builds the classifier.
+   * This means that all relevant examples are stored into memory.
+   * If necessary the interpolation parameter is tuned.
+   *
+   * @param instances the instances to be used for building the classifier
+   * @throws Exception if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    getCapabilities().testWithFail(instances);
+
+    // copy the dataset 
+    m_train = new Instances(instances);
+
+    // new dataset in which examples with missing class value are removed
+    m_train.deleteWithMissingClass();
+
+    // build the Map for the estimatedDistributions 
+    m_estimatedDistributions = new HashMap(m_train.numInstances()/2);
+
+    // cycle through all instances 
+    for (Iterator it = 
+      new EnumerationIterator(instances.enumerateInstances()); 
+    it.hasNext();) {
+      Instance instance = (Instance) it.next();
+      Coordinates c = new Coordinates(instance);
+
+      // get DiscreteEstimator from the map
+      DiscreteEstimator df = 
+	(DiscreteEstimator) m_estimatedDistributions.get(c);
+
+      // if no DiscreteEstimator is present in the map, create one 
+      if (df == null) {
+	df = new DiscreteEstimator(instances.numClasses(),0);
+      }
+      df.addValue(instance.classValue(),instance.weight()); // update
+      m_estimatedDistributions.put(c,df); // put back in map
+    }
+
+
+    // build the map of cumulative distribution functions 
+    m_estimatedCumulativeDistributions = 
+      new HashMap(m_estimatedDistributions.size()/2);
+
+    // Cycle trough the map of discrete distributions, and create a new
+    // one containing cumulative discrete distributions
+    for (Iterator it=m_estimatedDistributions.keySet().iterator();
+    it.hasNext();) {
+      Coordinates c = (Coordinates) it.next();
+      DiscreteEstimator df = 
+	(DiscreteEstimator) m_estimatedDistributions.get(c);
+      m_estimatedCumulativeDistributions.put
+      (c, new CumulativeDiscreteDistribution(df));
+    }
+
+    // check if the interpolation parameter needs to be tuned
+    if (m_tuneInterpolationParameter && !m_interpolationParameterValid) {
+      tuneInterpolationParameter();
+    }
+
+    // fill in the smallest and biggest element (for use in the
+    // quasi monotone version of the algorithm)
+    double[] tmpAttValues = new double[instances.numAttributes()];
+    Instance instance = new DenseInstance(1, tmpAttValues);
+    instance.setDataset(instances);
+    smallestElement = new Coordinates(instance);
+    if (m_Debug) {
+      System.err.println("minimal element of data space = " 
+	  + smallestElement);
+    }
+    for (int i = 0; i < tmpAttValues.length; i++) {
+      tmpAttValues[i] = instances.attribute(i).numValues() - 1; 
+    }
+
+    instance = new DenseInstance(1, tmpAttValues);
+    instance.setDataset(instances);
+    biggestElement = new Coordinates(instance);
+    if (m_Debug) {
+      System.err.println("maximal element of data space = " 
+	  + biggestElement);
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String classificationTypeTipText() {
+    return "Sets the way in which a single label will be extracted "
+    + "from the estimated distribution.";
+  }
+
+  /**
+   * Sets the classification type.  Currently <code> ctype </code>
+   * must be one of:
+   * <ul>
+   * <li> <code> CT_REGRESSION </code> : use expectation value of
+   * distribution.  (Non-ordinal in nature).
+   * <li> <code> CT_WEIGHTED_SUM </code> : use expectation value of
+   * distribution rounded to nearest class label. (Non-ordinal in
+   * nature).
+   * <li> <code> CT_MAXPROB </code> : use the mode of the distribution.
+   * (May deliver non-monotone results).
+   * <li> <code> CT_MEDIAN </code> : use the median of the distribution
+   * (rounded to the nearest class label).
+   * <li> <code> CT_MEDIAN_REAL </code> : use the median of the distribution
+   * but not rounded to the nearest class label.
+   * </ul>
+   *
+   * @param value the classification type
+   */
+  public void setClassificationType(SelectedTag value) {
+    if (value.getTags() == TAGS_CLASSIFICATIONTYPES)
+      m_ctype = value.getSelectedTag().getID();
+  }
+
+  /** 
+   * Returns the classification type.
+   *
+   * @return the classification type
+   */
+  public SelectedTag getClassificationType() {
+    return new SelectedTag(m_ctype, TAGS_CLASSIFICATIONTYPES);
+  }
+
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String tuneInterpolationParameterTipText() {
+    return "Whether to tune the interpolation parameter based on the bounds.";
+  }
+  
+  /**
+   * Sets whether the interpolation parameter is to be tuned based on the
+   * bounds.
+   * 
+   * @param value if true the parameter is tuned
+   */
+  public void setTuneInterpolationParameter(boolean value) {
+    m_tuneInterpolationParameter = value;
+  }
+  
+  /**
+   * Returns whether the interpolation parameter is to be tuned based on the
+   * bounds.
+   * 
+   * @return true if the parameter is to be tuned
+   */
+  public boolean getTuneInterpolationParameter() {
+    return m_tuneInterpolationParameter;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String interpolationParameterLowerBoundTipText() {
+    return "Sets the lower bound for the interpolation parameter tuning (0 <= x < 1).";
+  }
+  
+  /**
+   * Sets the lower bound for the interpolation parameter tuning 
+   * (0 &lt;= x &lt; 1).
+   * 
+   * @param value the tne lower bound
+   * @throws IllegalArgumentException if bound is invalid
+   */
+  public void setInterpolationParameterLowerBound(double value) {
+    if ( (value < 0) || (value >= 1) || (value > getInterpolationParameterUpperBound()) )
+      throw new IllegalArgumentException("Illegal lower bound");
+    
+    m_sLower = value;
+    m_tuneInterpolationParameter = true;
+    m_interpolationParameterValid = false;
+  }
+  
+  /**
+   * Returns the lower bound for the interpolation parameter tuning
+   * (0 &lt;= x &lt; 1).
+   * 
+   * @return the lower bound
+   */
+  public double getInterpolationParameterLowerBound() {
+    return m_sLower;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String interpolationParameterUpperBoundTipText() {
+    return "Sets the upper bound for the interpolation parameter tuning (0 < x <= 1).";
+  }
+  
+  /**
+   * Sets the upper bound for the interpolation parameter tuning 
+   * (0 &lt; x &lt;= 1).
+   * 
+   * @param value the tne upper bound
+   * @throws IllegalArgumentException if bound is invalid
+   */
+  public void setInterpolationParameterUpperBound(double value) {
+    if ( (value <= 0) || (value > 1) || (value < getInterpolationParameterLowerBound()) )
+      throw new IllegalArgumentException("Illegal upper bound");
+    
+    m_sUpper = value;
+    m_tuneInterpolationParameter = true;
+    m_interpolationParameterValid = false;
+  }
+  
+  /**
+   * Returns the upper bound for the interpolation parameter tuning
+   * (0 &lt; x &lt;= 1).
+   * 
+   * @return the upper bound
+   */
+  public double getInterpolationParameterUpperBound() {
+    return m_sUpper;
+  }
+  
+  /**
+   * Sets the interpolation bounds for the interpolation parameter.
+   * When tuning the interpolation parameter only values in the interval
+   * <code> [sLow, sUp] </code> are considered.
+   * It is important to note that using this method immediately
+   * implies that the interpolation parameter is to be tuned.
+   *
+   * @param sLow lower bound for the interpolation parameter, 
+   * should not be smaller than 0 or greater than <code> sUp </code>
+   * @param sUp upper bound for the interpolation parameter,
+   * should not exceed 1 or be smaller than <code> sLow </code>
+   * @throws IllegalArgumentException if one of the above conditions 
+   * is not satisfied.
+   */
+  public void setInterpolationParameterBounds(double sLow, double sUp) 
+    throws IllegalArgumentException {
+    
+    if (sLow < 0. || sUp > 1. || sLow > sUp) 
+      throw new IllegalArgumentException("Illegal upper and lower bounds");
+    m_sLower = sLow;
+    m_sUpper = sUp;
+    m_tuneInterpolationParameter = true;
+    m_interpolationParameterValid = false;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String interpolationParameterTipText() {
+    return "Sets the value of the interpolation parameter s;"
+    + "Estimated distribution is s * f_min + (1 - s) *  f_max. ";
+  }
+
+  /**
+   * Sets the interpolation parameter.  This immediately means that
+   * the interpolation parameter is not to be tuned.
+   *
+   * @param s value for the interpolation parameter.
+   * @throws IllegalArgumentException if <code> s </code> is not in
+   * the range [0,1].
+   */
+  public void setInterpolationParameter(double s) 
+    throws IllegalArgumentException {
+    
+    if (0 > s || s > 1)
+      throw new IllegalArgumentException("Interpolationparameter exceeds bounds");
+    m_tuneInterpolationParameter = false;
+    m_interpolationParameterValid = false;
+    m_s = s;
+  }
+
+  /**
+   * Returns the current value of the interpolation parameter.
+   *
+   * @return the value of the interpolation parameter
+   */
+  public double getInterpolationParameter() {
+    return m_s;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String numberOfPartsForInterpolationParameterTipText() {
+    return "Sets the granularity for tuning the interpolation parameter; "
+    + "For instance if the value is 32 then 33 values for the "
+    + "interpolation are checked.";  
+  }
+
+  /**
+   * Sets the granularity for tuning the interpolation parameter.
+   * The interval between lower and upper bounds for the interpolation
+   * parameter is divided into <code> sParts </code> parts, i.e.
+   * <code> sParts + 1 </code> values will be checked when 
+   * <code> tuneInterpolationParameter </code> is invoked.
+   * This also means that the interpolation parameter is to
+   * be tuned.
+   * 
+   * @param sParts the number of parts
+   * @throws IllegalArgumentException if <code> sParts </code> is 
+   * smaller or equal than 0.
+   */
+  public void setNumberOfPartsForInterpolationParameter(int sParts) 
+    throws IllegalArgumentException {
+    
+    if (sParts <= 0)
+      throw new IllegalArgumentException("Number of parts is negative");
+
+    m_tuneInterpolationParameter = true;
+    if (m_sNrParts != sParts) {
+      m_interpolationParameterValid = false;
+      m_sNrParts = sParts;
+    }
+  }
+
+  /**
+   * Gets the granularity for tuning the interpolation parameter.
+   * 
+   * @return the number of parts in which the interval 
+   * <code> [s_low, s_up] </code> is to be split
+   */
+  public int getNumberOfPartsForInterpolationParameter() {
+    return m_sNrParts;
+  }
+
+  /**
+   * Returns a string suitable for displaying in the gui/experimenter.
+   * 
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String balancedTipText() {
+    return "If true, the balanced version of the OSDL-algorithm is used\n"
+    + "This means that distinction is made between the normal and "
+    + "reversed preference situation.";
+  }
+
+  /**
+   * If <code> balanced </code> is <code> true </code> then the balanced
+   * version of OSDL will be used, otherwise the ordinary version of 
+   * OSDL will be in effect.
+   *
+   * @param balanced if <code> true </code> then B-OSDL is used, otherwise
+   * it is OSDL
+   */
+  public void setBalanced(boolean balanced) {
+    m_balanced = balanced;
+  }
+
+  /** 
+   * Returns if the balanced version of OSDL is in effect.
+   *
+   * @return <code> true </code> if the balanced version is in effect,
+   * <code> false </code> otherwise
+   */
+  public boolean getBalanced() {
+    return m_balanced;
+  }
+
+  /** 
+   * Returns a string suitable for displaying in the gui/experimenter.
+   * 
+   * @return tip text for this property suitable for 
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightedTipText() {
+    return "If true, the weighted version of the OSDL-algorithm is used";
+  }
+
+  /**
+   * If <code> weighted </code> is <code> true </code> then the
+   * weighted version of the OSDL is used.
+   * Note: using the weighted (non-balanced) version only ensures the 
+   * quasi monotonicity of the results w.r.t. to training set.
+   *
+   * @param weighted <code> true </code> if the weighted version to be used,
+   * <code> false </code> otherwise
+   */
+  public void setWeighted(boolean weighted) {
+    m_weighted = weighted;
+  }
+
+  /**
+   * Returns if the weighted version is in effect.
+   *
+   * @return <code> true </code> if the weighted version is in effect,
+   * <code> false </code> otherwise.
+   */
+  public boolean getWeighted() {
+    return m_weighted;
+  }
+
+  /**
+   * Returns the current value of the lower bound for the interpolation 
+   * parameter.
+   *
+   * @return the current value of the lower bound for the interpolation
+   * parameter
+   */
+  public double getLowerBound() {
+    return m_sLower;
+  }
+
+  /**
+   * Returns the current value of the upper bound for the interpolation 
+   * parameter.
+   *
+   * @return the current value of the upper bound for the interpolation
+   * parameter
+   */
+  public double getUpperBound() {
+    return m_sUpper;
+  }
+
+  /**
+   * Returns the number of instances in the training set.
+   *
+   * @return the number of instances used for training
+   */
+  public int getNumInstances() {
+    return m_train.numInstances();
+  }
+
+  /** Tune the interpolation parameter using the current
+   *  settings of the classifier.
+   *  This also sets the interpolation parameter.
+   *  @return the value of the tuned interpolation parameter.
+   */
+  public double tuneInterpolationParameter() {
+    try {
+      return tuneInterpolationParameter(m_sLower, m_sUpper, m_sNrParts, m_ctype);
+    } catch (IllegalArgumentException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /**
+   *  Tunes the interpolation parameter using the given settings.
+   *  The parameters of the classifier are updated accordingly!
+   *  Marks the interpolation parameter as valid.
+   *  
+   *  @param sLow lower end point of interval of paramters to be examined
+   *  @param sUp upper end point of interval of paramters to be examined
+   *  @param sParts number of parts the interval is divided into.  This thus determines
+   *  the granularity of the search
+   *  @param ctype the classification type to use
+   *  @return the value of the tuned interpolation parameter
+   *  @throws IllegalArgumentException if the given parameter list is not
+   *  valid
+   */
+  public double tuneInterpolationParameter(double sLow, double sUp, int sParts, int ctype) 
+    throws IllegalArgumentException {
+    
+    setInterpolationParameterBounds(sLow, sUp);
+    setNumberOfPartsForInterpolationParameter(sParts);
+    setClassificationType(new SelectedTag(ctype, TAGS_CLASSIFICATIONTYPES));
+
+    m_s = crossValidate(sLow, sUp, sParts, ctype);
+    m_tuneInterpolationParameter = true;
+    m_interpolationParameterValid = true;
+    return m_s;
+  }
+
+  /** 
+   *  Tunes the interpolation parameter using the current settings
+   *  of the classifier.  This doesn't change the classifier, i.e.
+   *  none of the internal parameters is changed!
+   *
+   *  @return the tuned value of the interpolation parameter
+   *  @throws IllegalArgumentException if somehow the current settings of the 
+   *  classifier are illegal.
+   */
+  public double crossValidate() throws IllegalArgumentException {
+    return crossValidate(m_sLower, m_sUpper, m_sNrParts, m_ctype);
+  }
+
+  /**
+   *  Tune the interpolation parameter using leave-one-out
+   *  cross validation, the loss function used is the 1-0 loss
+   *  function.
+   *  <p>
+   *  The given settings are used, but the classifier is not
+   *  updated!.  Also, the interpolation parameter s is not 
+   *  set.
+   *  </p>
+   * 
+   *  @param sLow lower end point of interval of paramters to be examined
+   *  @param sUp upper end point of interval of paramters to be examined
+   *  @param sNrParts number of parts the interval is divided into.  This thus determines
+   *  the granularity of the search
+   *  @param ctype the classification type to use
+   *  @return the best value for the interpolation parameter
+   *  @throws IllegalArgumentException if the settings for the
+   *  interpolation parameter are not valid or if the classification 
+   *  type is not valid
+   */
+  public double crossValidate (double sLow, double sUp, int sNrParts, int ctype) 
+    throws IllegalArgumentException {
+
+    double[] performanceStats = new double[sNrParts + 1];
+    return crossValidate(sLow, sUp, sNrParts, ctype, 
+	performanceStats, new ZeroOneLossFunction());
+  }
+
+  /**
+   * Tune the interpolation parameter using leave-one-out
+   * cross validation.  The given parameters are used, but 
+   * the classifier is not changed, in particular, the interpolation
+   * parameter remains unchanged.
+   *
+   * @param sLow lower bound for interpolation parameter
+   * @param sUp upper bound for interpolation parameter
+   * @param sNrParts determines the granularity of the search
+   * @param ctype the classification type to use
+   * @param performanceStats array acting as output, and that will
+   * contain the total loss of the leave-one-out cross validation for
+   * each considered value of the interpolation parameter
+   * @param lossFunction the loss function to use
+   * @return the value of the interpolation parameter that is considered
+   * best
+   * @throws IllegalArgumentException the length of the array 
+   * <code> performanceStats </code> is not sufficient
+   * @throws IllegalArgumentException if the interpolation parameters 
+   * are not valid
+   * @throws IllegalArgumentException if the classification type is 
+   * not valid
+   */
+  public double crossValidate(double sLow, double sUp, int sNrParts, 
+      int ctype, double[] performanceStats, 
+      NominalLossFunction lossFunction) throws IllegalArgumentException {
+
+    if (performanceStats.length < sNrParts + 1) {
+      throw new IllegalArgumentException("Length of array is not sufficient");
+    }
+
+    if (!interpolationParametersValid(sLow, sUp, sNrParts)) {
+      throw new IllegalArgumentException("Interpolation parameters are not valid");
+    }
+
+    if (!classificationTypeValid(ctype)) {
+      throw new IllegalArgumentException("Not a valid classification type " + ctype);
+    }
+
+    Arrays.fill(performanceStats, 0, sNrParts + 1, 0);
+
+    // cycle through all instances
+    for (Iterator it = 
+      new EnumerationIterator(m_train.enumerateInstances());
+    it.hasNext(); ) {
+      Instance instance = (Instance) it.next();
+      double classValue = instance.classValue();
+      removeInstance(instance); 
+
+      double s = sLow;
+      double step = (sUp - sLow) / sNrParts; //step size
+      for (int i = 0; i <= sNrParts; i++, s += step) {
+	try {
+	  performanceStats[i] += 
+	    lossFunction.loss(classValue,
+		classifyInstance(instance, s, ctype));
+	} catch (Exception exception) {
+
+	  // XXX what should I do here, normally we shouldn't be here
+	  System.err.println(exception.getMessage());
+	  System.exit(1);
+	}
+      }
+
+      // XXX may be done more efficiently
+      addInstance(instance); // update
+    }
+
+    // select the 'best' value for s
+    // to this end, we sort the array with the leave-one-out
+    // performance statistics, and we choose the middle one
+    // off all those that score 'best'
+
+    // new code, august 2004
+    // new code, june 2005.  If performanceStats is longer than
+    // necessary, copy it first
+    double[] tmp = performanceStats;
+    if (performanceStats.length > sNrParts + 1) {
+      tmp = new double[sNrParts + 1];
+      System.arraycopy(performanceStats, 0, tmp, 0, tmp.length);
+    }
+    int[] sort = Utils.stableSort(tmp);
+    int minIndex = 0;
+    while (minIndex + 1 < tmp.length 
+	&& tmp[sort[minIndex + 1]] == tmp[sort[minIndex]]) {
+      minIndex++;
+    }
+    minIndex = sort[minIndex / 2];  // middle one 
+    // int minIndex = Utils.minIndex(performanceStats); // OLD code
+
+    return  sLow + minIndex * (sUp - sLow) / sNrParts;
+  }
+
+  /**
+   * Checks if <code> ctype </code> is a valid classification 
+   * type.
+   * @param ctype the int to be checked
+   * @return true if ctype is a valid classification type, false otherwise
+   */
+  private boolean classificationTypeValid(int ctype) {
+    return ctype == CT_REGRESSION || ctype == CT_WEIGHTED_SUM 
+    || ctype == CT_MAXPROB || ctype == CT_MEDIAN 
+    || ctype == CT_MEDIAN_REAL;
+  }
+
+  /**
+   * Checks if the given parameters are valid interpolation parameters.
+   * @param sLow lower bound for the interval
+   * @param sUp upper bound for the interval
+   * @param sNrParts the number of parts the interval has to be divided in
+   * @return true is the given parameters are valid interpolation parameters,
+   * false otherwise
+   */
+  private boolean interpolationParametersValid(double sLow, double sUp, int sNrParts) {
+    return sLow >= 0 && sUp <= 1 && sLow < sUp && sNrParts > 0
+    || sLow == sUp && sNrParts == 0; 
+    // special case included
+  }
+
+  /** 
+   * Remove an instance from the classifier.  Updates the hashmaps.
+   * @param instance the instance to be removed.  
+   */
+  private void removeInstance(Instance instance) {
+    Coordinates c = new Coordinates(instance);
+
+    // Remove instance temporarily from the Maps with the distributions
+    DiscreteEstimator df = 
+      (DiscreteEstimator) m_estimatedDistributions.get(c);
+
+    // remove from df
+    df.addValue(instance.classValue(),-instance.weight());
+
+    if (Math.abs(df.getSumOfCounts() - 0) < Utils.SMALL) {
+
+      /* There was apparently only one example with coordinates c
+       * in the training set, and now we removed it.
+       * Remove the key c from both maps. 
+       */
+      m_estimatedDistributions.remove(c);
+      m_estimatedCumulativeDistributions.remove(c);
+    }
+    else {
+
+      // update both maps
+      m_estimatedDistributions.put(c,df);
+      m_estimatedCumulativeDistributions.put
+      (c, new CumulativeDiscreteDistribution(df));
+    }
+  }
+
+  /**
+   * Update the classifier using the given instance.  Updates the hashmaps
+   * @param instance the instance to be added
+   */
+  private void addInstance(Instance instance) {
+
+    Coordinates c = new Coordinates(instance);
+
+    // Get DiscreteEstimator from the map
+    DiscreteEstimator df = 
+      (DiscreteEstimator) m_estimatedDistributions.get(c);
+
+    // If no DiscreteEstimator is present in the map, create one 
+    if (df == null) {
+      df = new DiscreteEstimator(instance.dataset().numClasses(),0);
+    }
+    df.addValue(instance.classValue(),instance.weight()); // update df
+    m_estimatedDistributions.put(c,df); // put back in map
+    m_estimatedCumulativeDistributions.put
+    (c, new CumulativeDiscreteDistribution(df));
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * For a list of available options, see <code> setOptions </code>.
+   *
+   * @return an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector options = new Vector();
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      options.addElement(enm.nextElement());
+
+    String description = 
+      "\tSets the classification type to be used.\n"
+      + "\t(Default: " + new SelectedTag(CT_MEDIAN, TAGS_CLASSIFICATIONTYPES) + ")";
+    String synopsis = "-C " + Tag.toOptionList(TAGS_CLASSIFICATIONTYPES);
+    String name = "C";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = "\tUse the balanced version of the "  
+      + "Ordinal Stochastic Dominance Learner";
+    synopsis = "-B";
+    name = "B";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = "\tUse the weighted version of the " 
+      + "Ordinal Stochastic Dominance Learner";
+    synopsis = "-W";
+    name = "W";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = 
+      "\tSets the value of the interpolation parameter (not with -W/T/P/L/U)\n" 
+      + "\t(default: 0.5).";
+    synopsis = "-S <value of interpolation parameter>";
+    name = "S";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = 
+      "\tTune the interpolation parameter (not with -W/S)\n" 
+      + "\t(default: off)";
+    synopsis = "-T";
+    name = "T";
+    options.addElement(new Option(description, name, 0, synopsis));
+
+    description = 
+      "\tLower bound for the interpolation parameter (not with -W/S)\n" 
+      + "\t(default: 0)";
+    synopsis = "-L <Lower bound for interpolation parameter>";
+    name="L";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = 
+      "\tUpper bound for the interpolation parameter (not with -W/S)\n" 
+      + "\t(default: 1)";
+    synopsis = "-U <Upper bound for interpolation parameter>";
+    name="U";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    description = 
+      "\tDetermines the step size for tuning the interpolation\n" 
+      + "\tparameter, nl. (U-L)/P (not with -W/S)\n"
+      + "\t(default: 10)";
+    synopsis = "-P <Number of parts>";
+    name="P";
+    options.addElement(new Option(description, name, 1, synopsis));
+
+    return options.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -C &lt;REG|WSUM|MAX|MED|RMED&gt;
+   *  Sets the classification type to be used.
+   *  (Default: MED)</pre>
+   * 
+   * <pre> -B
+   *  Use the balanced version of the Ordinal Stochastic Dominance Learner</pre>
+   * 
+   * <pre> -W
+   *  Use the weighted version of the Ordinal Stochastic Dominance Learner</pre>
+   * 
+   * <pre> -S &lt;value of interpolation parameter&gt;
+   *  Sets the value of the interpolation parameter (not with -W/T/P/L/U)
+   *  (default: 0.5).</pre>
+   * 
+   * <pre> -T
+   *  Tune the interpolation parameter (not with -W/S)
+   *  (default: off)</pre>
+   * 
+   * <pre> -L &lt;Lower bound for interpolation parameter&gt;
+   *  Lower bound for the interpolation parameter (not with -W/S)
+   *  (default: 0)</pre>
+   * 
+   * <pre> -U &lt;Upper bound for interpolation parameter&gt;
+   *  Upper bound for the interpolation parameter (not with -W/S)
+   *  (default: 1)</pre>
+   * 
+   * <pre> -P &lt;Number of parts&gt;
+   *  Determines the step size for tuning the interpolation
+   *  parameter, nl. (U-L)/P (not with -W/S)
+   *  (default: 10)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String args;
+
+    args = Utils.getOption('C',options);
+    if (args.length() != 0) 
+      setClassificationType(new SelectedTag(args, TAGS_CLASSIFICATIONTYPES));
+    else
+      setClassificationType(new SelectedTag(CT_MEDIAN, TAGS_CLASSIFICATIONTYPES));
+
+    setBalanced(Utils.getFlag('B',options));
+
+    if (Utils.getFlag('W', options)) {
+      m_weighted = true;
+      // ignore any T, S, P, L and U options
+      Utils.getOption('T', options);
+      Utils.getOption('S', options);
+      Utils.getOption('P', options);
+      Utils.getOption('L', options);
+      Utils.getOption('U', options);
+    } else {
+      m_tuneInterpolationParameter = Utils.getFlag('T', options);
+
+      if (!m_tuneInterpolationParameter) {
+	// ignore P, L, U
+	Utils.getOption('P', options);
+	Utils.getOption('L', options);
+	Utils.getOption('U', options);
+
+	// value of s 
+	args = Utils.getOption('S',options);
+	if (args.length() != 0)
+	  setInterpolationParameter(Double.parseDouble(args));
+	else
+	  setInterpolationParameter(0.5);
+      }
+      else {
+	// ignore S
+	Utils.getOption('S', options);
+	
+	args = Utils.getOption('L',options);
+	double l = m_sLower;
+	if (args.length() != 0)
+	  l = Double.parseDouble(args);
+	else
+	  l = 0.0;
+
+	args = Utils.getOption('U',options);
+	double u = m_sUpper;
+	if (args.length() != 0)
+	  u = Double.parseDouble(args);
+	else
+	  u = 1.0;
+
+	if (m_tuneInterpolationParameter)
+	  setInterpolationParameterBounds(l, u);
+
+	args = Utils.getOption('P',options);
+	if (args.length() != 0)
+	  setNumberOfPartsForInterpolationParameter(Integer.parseInt(args));
+	else
+	  setNumberOfPartsForInterpolationParameter(10);
+      }
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the OSDLCore classifier.
+   *
+   * @return an array of strings suitable for passing 
+   * to <code> setOptions </code>
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    // classification type
+    result.add("-C");
+    result.add("" + getClassificationType());
+
+    if (m_balanced)
+      result.add("-B");
+
+    if (m_weighted) {
+      result.add("-W");
+    }
+    else {
+      // interpolation parameter
+      if (!m_tuneInterpolationParameter) {
+        result.add("-S");
+        result.add(Double.toString(m_s));
+      }
+      else {
+        result.add("-T");
+        result.add("-L");
+        result.add(Double.toString(m_sLower));
+        result.add("-U");
+        result.add(Double.toString(m_sUpper));
+        result.add("-P");
+        result.add(Integer.toString(m_sNrParts));
+      }
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns a description of the classifier.
+   * Attention: if debugging is on, the description can be become
+   * very lengthy.
+   *
+   * @return a string containing the description
+   */
+  public String toString() {
+    StringBuffer sb = new StringBuffer();
+
+    // balanced or ordinary OSDL
+    if (m_balanced) {
+      sb.append("Balanced OSDL\n=============\n\n");
+    } else {
+      sb.append("Ordinary OSDL\n=============\n\n");
+    }
+
+    if (m_weighted) {
+      sb.append("Weighted variant\n");
+    }
+
+    // classification type used
+    sb.append("Classification type: " + getClassificationType() + "\n");
+
+    // parameter s 
+    if (!m_weighted) {
+      sb.append("Interpolation parameter: " + m_s + "\n");
+      if (m_tuneInterpolationParameter) {
+	sb.append("Bounds and stepsize: " + m_sLower + " " + m_sUpper + 
+	    " " + m_sNrParts + "\n");
+	if (!m_interpolationParameterValid) {
+	  sb.append("Interpolation parameter is not valid");
+	}
+      }
+    }
+
+
+    if(m_Debug) {
+
+      if (m_estimatedCumulativeDistributions != null) { 
+	/* 
+	 * Cycle through all the map of cumulative distribution functions
+	 * and print each cumulative distribution function
+	 */
+	for (Iterator i = 
+	  m_estimatedCumulativeDistributions.keySet().iterator();
+	i.hasNext(); ) {
+	  Coordinates yc = (Coordinates) i.next();
+	  CumulativeDiscreteDistribution cdf = 
+	    (CumulativeDiscreteDistribution) 
+	    m_estimatedCumulativeDistributions.get(yc);
+	  sb.append( "[" + yc.hashCode() + "] " + yc.toString() 
+	      + " --> " + cdf.toString() + "\n");
+	}
+      }
+    }
+    return sb.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/ZeroOneLossFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/ZeroOneLossFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/misc/monotone/ZeroOneLossFunction.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ZeroOneLossFunction.java
+ *    Copyright (C) 2004 Stijn Lievens
+ *
+ */
+
+package weka.classifiers.misc.monotone;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Class implementing the zero-one loss function, this is 
+ * an incorrect prediction always accounts for one unit loss.
+ *
+ * <p>
+ * This implementation is done as part of the master's thesis: "Studie
+ * en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd
+ * rangschikken", Stijn Lievens, Ghent University, 2004. 
+ * </p>
+ * 
+ * @author Stijn Lievens (stijn.lievens@ugent.be)
+ * @version $Revision: 5922 $
+ */
+public class ZeroOneLossFunction
+  implements NominalLossFunction, RevisionHandler {
+
+  /**
+   * Returns the zero-one loss function between two class values.
+   * 
+   * @param actual the actual class value
+   * @param predicted the predicted class value
+   * @return 1 if the actual and predicted value differ, 0 otherwise
+   */
+  public final double loss(double actual, double predicted) {
+    return actual == predicted ? 0 : 1;
+  }
+
+  /**
+   * Returns a string with the name of the loss function.
+   *
+   * @return a string with the name of the loss function
+   */
+  public String toString() {
+    return "ZeroOneLossFunction";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5922 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/GeneralRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/GeneralRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/GeneralRegression.java	(revision 29)
@@ -0,0 +1,1472 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GeneralRegression.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.pmml.*;
+
+/**
+ * Class implementing import of PMML General Regression model. Can be
+ * used as a Weka classifier for prediction (buildClassifier()
+ * raises an Exception).
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class GeneralRegression extends PMMLClassifier
+  implements Serializable {
+
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = 2583880411828388959L;
+
+  /**
+   * Enumerated type for the model type.
+   */
+  enum ModelType {
+
+    // same type of model
+    REGRESSION ("regression"), 
+      GENERALLINEAR ("generalLinear"), 
+      MULTINOMIALLOGISTIC ("multinomialLogistic"),
+      ORDINALMULTINOMIAL ("ordinalMultinomial"), 
+      GENERALIZEDLINEAR ("generalizedLinear");
+
+    private final String m_stringVal;
+    ModelType(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  // the model type
+  protected ModelType m_modelType = ModelType.REGRESSION;
+
+  // the model name (if defined)
+  protected String m_modelName;
+    
+  // the algorithm name (if defined)
+  protected String m_algorithmName;
+
+  // the function type (regression or classification)
+  protected int m_functionType = Regression.RegressionTable.REGRESSION;
+
+  /**
+   * Enumerated type for the cumulative link function
+   * (ordinal multinomial model type only).
+   */
+  enum CumulativeLinkFunction {
+    NONE ("none") {
+      double eval(double value, double offset) {
+        return Double.NaN; // no evaluation defined in this case!
+      }
+    },
+    LOGIT ("logit") {
+      double eval(double value, double offset) {
+        return 1.0 / (1.0 + Math.exp(-(value + offset)));
+      }
+    },
+    PROBIT ("probit") {
+      double eval(double value, double offset) {
+        return weka.core.matrix.Maths.pnorm(value + offset); 
+      }
+    },
+    CLOGLOG ("cloglog") {
+      double eval(double value, double offset) {
+        return 1.0 - Math.exp(-Math.exp(value + offset));
+      }
+    },
+    LOGLOG ("loglog") {
+      double eval(double value, double offset) {
+        return Math.exp(-Math.exp(-(value + offset))); 
+      }
+    },
+    CAUCHIT ("cauchit") {
+      double eval(double value, double offset) {
+        return 0.5 + (1.0 / Math.PI) * Math.atan(value + offset);
+      }
+    };
+
+    /**
+     * Evaluation function.
+     * 
+     * @param value the raw response value
+     * @param offset the offset to add to the raw value 
+     * @return the result of the link function
+     */
+    abstract double eval(double value, double offset);
+    
+    private final String m_stringVal;
+    
+    /**
+     * Constructor
+     * 
+     * @param name textual name for this enum
+     */
+    CumulativeLinkFunction(String name) {
+      m_stringVal = name;
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Enum#toString()
+     */
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  // cumulative link function (ordinal multinomial only)
+  protected CumulativeLinkFunction m_cumulativeLinkFunction 
+    = CumulativeLinkFunction.NONE;
+
+
+  /**
+   * Enumerated type for the link function (general linear and
+   * generalized linear model types only).
+   */
+  enum LinkFunction {
+    NONE ("none") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return Double.NaN; // no evaluation defined in this case!
+      }
+    },
+    CLOGLOG ("cloglog") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (1.0 - Math.exp(-Math.exp(value + offset))) * trials;
+      }
+    },
+    IDENTITY ("identity") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (value + offset) * trials;
+      }
+    },
+    LOG ("log") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return Math.exp(value + offset) * trials;
+      }
+    },
+    LOGC ("logc") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (1.0 - Math.exp(value + offset)) * trials;
+      }
+    },
+    LOGIT ("logit") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (1.0 / (1.0 + Math.exp(-(value + offset)))) * trials;
+      }
+    },
+    LOGLOG ("loglog") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return Math.exp(-Math.exp(-(value + offset))) * trials;
+      }
+    },
+    NEGBIN ("negbin") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (1.0 / (distParam * (Math.exp(-(value + offset)) - 1.0))) * trials;
+      }
+    },
+    ODDSPOWER ("oddspower") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (linkParam < 0.0 || linkParam > 0.0)
+        ? (1.0 / (1.0 + Math.pow(1.0 + linkParam * (value + offset), (-1.0 / linkParam)))) * trials
+        : (1.0 / (1.0 + Math.exp(-(value + offset)))) * trials;
+      }
+    },
+    POWER ("power") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return (linkParam < 0.0 || linkParam > 0.0)
+        ? Math.pow(value + offset, (1.0 / linkParam)) * trials
+            : Math.exp(value + offset) * trials;
+      }
+    },
+    PROBIT ("probit") {
+      double eval(double value, double offset, double trials,
+                  double distParam, double linkParam) {
+        return weka.core.matrix.Maths.pnorm(value + offset) * trials;
+      }
+    };
+
+    /**
+     * Evaluation function.
+     * 
+     * @param value the raw response value
+     * @param offset the offset to add to the raw value
+     * @param trials the trials value to multiply the result by
+     * @param distParam the distribution parameter (negbin only)
+     * @param linkParam the link parameter (power and oddspower only) 
+     * @return the result of the link function
+     */
+    abstract double eval(double value, double offset, double trials, 
+                         double distParam, double linkParam);
+    
+    private final String m_stringVal;
+    
+    /**
+     * Constructor.
+     * 
+     * @param name the textual name of this link function
+     */
+    LinkFunction(String name) {
+      m_stringVal = name;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Enum#toString()
+     */
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  // link function (generalLinear model type only)
+  protected LinkFunction m_linkFunction = LinkFunction.NONE;
+  protected double m_linkParameter = Double.NaN;
+  protected String m_trialsVariable;
+  protected double m_trialsValue = Double.NaN;
+
+  /**
+   * Enumerated type for the distribution (general linear
+   * and generalized linear model types only).
+   */
+  enum Distribution {
+    NONE ("none"),
+    NORMAL ("normal"),
+    BINOMIAL ("binomial"),
+    GAMMA ("gamma"),
+    INVGAUSSIAN ("igauss"),
+    NEGBINOMIAL ("negbin"),
+    POISSON ("poisson");
+
+    private final String m_stringVal;
+    Distribution(String name) {
+      m_stringVal = name;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Enum#toString()
+     */
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  // generalLinear and generalizedLinear model type only
+  protected Distribution m_distribution = Distribution.NORMAL;
+
+  // ancillary parameter value for the negative binomial distribution
+  protected double m_distParameter = Double.NaN;
+
+  // if present, this variable is used during scoring generalizedLinear/generalLinear or
+  // ordinalMultinomial models
+  protected String m_offsetVariable;
+
+  // if present, this variable is used during scoring generalizedLinear/generalLinear or
+  // ordinalMultinomial models. It works like a user-specified intercept.
+  // At most, only one of offsetVariable or offsetValue may be specified.
+  protected double m_offsetValue = Double.NaN;
+
+  /**
+   * Small inner class to hold the name of a parameter plus
+   * its optional descriptive label
+   */
+  static class Parameter implements Serializable {
+    // ESCA-JAVA0096:
+    /** For serialization */
+    // CHECK ME WITH serialver
+    private static final long serialVersionUID = 6502780192411755341L;
+
+    protected String m_name = null;
+    protected String m_label = null;
+  }
+
+  // List of model parameters
+  protected ArrayList<Parameter> m_parameterList = new ArrayList<Parameter>();
+
+  /**
+   * Small inner class to hold the name of a factor or covariate,
+   * plus the index of the attribute it corresponds to in the
+   * mining schema.
+   */
+  static class Predictor implements Serializable {
+    /** For serialization */
+    // CHECK ME WITH serialver
+    private static final long serialVersionUID = 6502780192411755341L;
+
+    protected String m_name = null;
+    protected int m_miningSchemaIndex = -1;
+    
+    public String toString() {
+      return m_name;
+    }
+  }
+  
+  // FactorList
+  protected ArrayList<Predictor> m_factorList = new ArrayList<Predictor>();
+
+  // CovariateList
+  protected ArrayList<Predictor> m_covariateList = new ArrayList<Predictor>();
+
+  /**
+   * Small inner class to hold details on a predictor-to-parameter
+   * correlation.
+   */
+  static class PPCell implements Serializable {
+    /** For serialization */
+    // CHECK ME WITH serialver
+    private static final long serialVersionUID = 6502780192411755341L;
+    
+    protected String m_predictorName = null;
+    protected String m_parameterName = null;
+
+    // either the exponent of a numeric attribute or the index of
+    // a discrete value
+    protected double m_value = 0;
+
+    // optional. The default is for all target categories to
+    // share the same PPMatrix.
+    // TO-DO: implement multiple PPMatrixes 
+    protected String m_targetCategory = null;
+    
+  }
+  
+  // PPMatrix (predictor-to-parameter matrix)
+  // rows = parameters, columns = predictors (attributes)
+  protected PPCell[][] m_ppMatrix;
+
+  /**
+   * Small inner class to hold a single entry in the 
+   * ParamMatrix (parameter matrix).
+   */
+  static class PCell implements Serializable {
+    
+    /** For serialization */
+    // CHECK ME WITH serialver
+    private static final long serialVersionUID = 6502780192411755341L;
+
+    // may be null for numeric target. May also be null if this coefficent
+    // applies to all target categories.
+    protected String m_targetCategory = null;
+    protected String m_parameterName = null;
+    // coefficient
+    protected double m_beta = 0.0;
+    // optional degrees of freedom
+    protected int m_df = -1;
+  }
+  
+  // ParamMatrix. rows = target categories (only one if target is numeric),
+  // columns = parameters (in order that they occur in the parameter list).
+  protected PCell[][] m_paramMatrix;
+
+  /**
+   * Constructs a GeneralRegression classifier.
+   * 
+   * @param model the Element that holds the model definition
+   * @param dataDictionary the data dictionary as a set of Instances
+   * @param miningSchema the mining schema
+   * @throws Exception if there is a problem constructing the general regression
+   * object from the PMML.
+   */
+  public GeneralRegression(Element model, Instances dataDictionary,
+                           MiningSchema miningSchema) throws Exception {
+
+    super(dataDictionary, miningSchema);
+ 
+    // get the model type
+    String mType = model.getAttribute("modelType");
+    boolean found = false;
+    for (ModelType m : ModelType.values()) {
+      if (m.toString().equals(mType)) {
+        m_modelType = m;
+        found = true;
+        break;
+      }      
+    }
+    if (!found) {
+      throw new Exception("[GeneralRegression] unknown model type: " + mType);
+    }
+
+    if (m_modelType == ModelType.ORDINALMULTINOMIAL) {
+      // get the cumulative link function
+      String cLink = model.getAttribute("cumulativeLink");
+      found = false;
+      for (CumulativeLinkFunction c : CumulativeLinkFunction.values()) {
+        if (c.toString().equals(cLink)) {
+          m_cumulativeLinkFunction = c;
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        throw new Exception("[GeneralRegression] cumulative link function " + cLink);
+      }
+    } else if (m_modelType == ModelType.GENERALIZEDLINEAR || 
+                m_modelType == ModelType.GENERALLINEAR) {
+      // get the link function
+      String link = model.getAttribute("linkFunction");
+      found = false;
+      for (LinkFunction l : LinkFunction.values()) {
+        if (l.toString().equals(link)) {
+          m_linkFunction = l;
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        throw new Exception("[GeneralRegression] unknown link function " + link);
+      }
+
+      // get the link parameter
+      String linkP = model.getAttribute("linkParameter");
+      if (linkP != null && linkP.length() > 0) {
+        try {
+          m_linkParameter = Double.parseDouble(linkP);
+        } catch (IllegalArgumentException ex) {
+          throw new Exception("[GeneralRegression] unable to parse the link parameter");
+        }
+      }
+
+      // get the trials variable
+      String trials = model.getAttribute("trialsVariable");
+      if (trials != null && trials.length() > 0) {
+        m_trialsVariable = trials;
+      }
+
+      // get the trials value
+      String trialsV = model.getAttribute("trialsValue");
+      if (trialsV != null && trialsV.length() > 0) {
+        try {
+          m_trialsValue = Double.parseDouble(trialsV);
+        } catch (IllegalArgumentException ex) {
+          throw new Exception("[GeneralRegression] unable to parse the trials value"); 
+        }
+      }
+    }
+  
+    String mName = model.getAttribute("modelName");
+    if (mName != null && mName.length() > 0) {
+      m_modelName = mName;
+    }
+
+    String fName = model.getAttribute("functionName");
+    if (fName.equals("classification")) {
+      m_functionType = Regression.RegressionTable.CLASSIFICATION;
+    }
+
+    String algName = model.getAttribute("algorithmName");
+    if (algName != null && algName.length() > 0) {
+      m_algorithmName = algName;
+    }
+
+    String distribution = model.getAttribute("distribution");
+    if (distribution != null && distribution.length() > 0) {
+      found = false;
+      for (Distribution d : Distribution.values()) {
+        if (d.toString().equals(distribution)) {
+          m_distribution = d;
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        throw new Exception("[GeneralRegression] unknown distribution type " + distribution);
+      }
+    }
+
+    String distP = model.getAttribute("distParameter");
+    if (distP != null && distP.length() > 0) {
+      try {
+        m_distParameter = Double.parseDouble(distP);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[GeneralRegression] unable to parse the distribution parameter");
+      }
+    }
+
+    String offsetV = model.getAttribute("offsetVariable");
+    if (offsetV != null && offsetV.length() > 0) {
+       m_offsetVariable = offsetV;
+    }
+
+    String offsetVal = model.getAttribute("offsetValue");
+    if (offsetVal != null && offsetVal.length() > 0) {
+      try {
+        m_offsetValue = Double.parseDouble(offsetVal);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[GeneralRegression] unable to parse the offset value");
+      }
+    }
+
+    // get the parameter list
+    readParameterList(model);
+    
+    // get the factors and covariates
+    readFactorsAndCovariates(model, "FactorList");
+    readFactorsAndCovariates(model, "CovariateList");
+
+    // read the PPMatrix
+    readPPMatrix(model);
+
+    // read the parameter estimates
+    readParamMatrix(model);
+  }
+
+  /**
+   * Read the list of parameters.
+   *
+   * @param model the Element that contains the model
+   * @throws Exception if there is some problem with extracting the
+   * parameters.
+   */
+  protected void readParameterList(Element model) throws Exception {
+    NodeList paramL = model.getElementsByTagName("ParameterList");
+
+    // should be just one parameter list
+    if (paramL.getLength() == 1) {
+      Node paramN = paramL.item(0);
+      if (paramN.getNodeType() == Node.ELEMENT_NODE) {
+        NodeList parameterList = ((Element)paramN).getElementsByTagName("Parameter");
+        for (int i = 0; i < parameterList.getLength(); i++) {
+          Node parameter = parameterList.item(i);
+          if (parameter.getNodeType() == Node.ELEMENT_NODE) {
+            Parameter p = new Parameter();
+            p.m_name = ((Element)parameter).getAttribute("name");
+            String label = ((Element)parameter).getAttribute("label");
+            if (label != null && label.length() > 0) {
+              p.m_label = label;
+            }
+            m_parameterList.add(p);
+          }
+        }
+      }
+    } else {
+      throw new Exception("[GeneralRegression] more than one parameter list!");
+    }
+  }
+
+  /**
+   * Read the lists of factors and covariates.
+   *
+   * @param model the Element that contains the model
+   * @param factorOrCovariate holds the String "FactorList" or
+   * "CovariateList"
+   * @throws Exception if there is a factor or covariate listed
+   * that isn't in the mining schema
+   */
+  protected void readFactorsAndCovariates(Element model, 
+                                          String factorOrCovariate) 
+    throws Exception {
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+
+    NodeList factorL = model.getElementsByTagName(factorOrCovariate);
+    if (factorL.getLength() == 1) { // should be 0 or 1 FactorList element
+      Node factor = factorL.item(0);
+      if (factor.getNodeType() == Node.ELEMENT_NODE) {
+        NodeList predL = ((Element)factor).getElementsByTagName("Predictor");
+        for (int i = 0; i < predL.getLength(); i++) {
+          Node pred = predL.item(i);
+          if (pred.getNodeType() == Node.ELEMENT_NODE) {
+            Predictor p = new Predictor();
+            p.m_name = ((Element)pred).getAttribute("name");
+            // find the index of this predictor in the mining schema
+            boolean found = false;
+            for (int j = 0; j < miningSchemaI.numAttributes(); j++) {
+              if (miningSchemaI.attribute(j).name().equals(p.m_name)) {
+                found = true;
+                p.m_miningSchemaIndex = j;
+                break;
+              }
+            }
+            if (found) {
+              if (factorOrCovariate.equals("FactorList")) {
+                m_factorList.add(p);
+              } else {
+                m_covariateList.add(p);
+              }
+            } else {
+              throw new Exception("[GeneralRegression] reading factors and covariates - "
+                                  + "unable to find predictor " +
+                                  p.m_name + " in the mining schema");
+            }
+          }
+        }
+      }
+    } else if (factorL.getLength() > 1){
+      throw new Exception("[GeneralRegression] more than one " + factorOrCovariate
+                          + "! ");
+    }
+  }
+
+  /**
+   * Read the PPMatrix from the xml. Does not handle multiple PPMatrixes yet.
+   *
+   * @param model the Element that contains the model
+   * @throws Exception if there is a problem parsing cell values.
+   */
+  protected void readPPMatrix(Element model) throws Exception {
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+    
+    NodeList matrixL = model.getElementsByTagName("PPMatrix");
+
+    // should be exactly one PPMatrix
+    if (matrixL.getLength() == 1) {
+      // allocate space for the matrix
+      // column that corresponds to the class will be empty (and will be missed out
+      // when printing the model).
+      m_ppMatrix = new PPCell[m_parameterList.size()][miningSchemaI.numAttributes()];
+
+      Node ppM = matrixL.item(0);
+      if (ppM.getNodeType() == Node.ELEMENT_NODE) {
+        NodeList cellL = ((Element)ppM).getElementsByTagName("PPCell");
+        for (int i = 0; i < cellL.getLength(); i++) {
+          Node cell = cellL.item(i);
+          if (cell.getNodeType() == Node.ELEMENT_NODE) {
+            String predictorName = ((Element)cell).getAttribute("predictorName");
+            String parameterName = ((Element)cell).getAttribute("parameterName");
+            String value = ((Element)cell).getAttribute("value");
+            double expOrIndex = -1;
+            int predictorIndex = -1;
+            int parameterIndex = -1;
+            for (int j = 0; j < m_parameterList.size(); j++) {
+              if (m_parameterList.get(j).m_name.equals(parameterName)) {
+                parameterIndex = j;
+                break;
+              }
+            }
+            if (parameterIndex == -1) {
+              throw new Exception("[GeneralRegression] unable to find parameter name "
+                                  + parameterName + " in parameter list");
+            }
+
+            Predictor p = getCovariate(predictorName);
+            if (p != null) {
+              try {
+                expOrIndex = Double.parseDouble(value);
+                predictorIndex = p.m_miningSchemaIndex;
+              } catch (IllegalArgumentException ex) {
+                throw new Exception("[GeneralRegression] unable to parse PPCell value: "
+                                    + value);
+              }
+            } else {
+              // try as a factor
+              p = getFactor(predictorName);
+              if (p != null) {
+                // An example pmml file from DMG seems to suggest that it
+                // is possible for a continuous variable in the mining schema
+                // to be treated as a factor, so we have to check for this
+                if (miningSchemaI.attribute(p.m_miningSchemaIndex).isNumeric()) {
+                  // parse this value as a double. It will be treated as a value
+                  // to match rather than an exponent since we are dealing with
+                  // a factor here
+                  try {
+                    expOrIndex = Double.parseDouble(value);
+                  } catch (IllegalArgumentException ex) {
+                    throw new Exception("[GeneralRegresion] unable to parse PPCell value: "
+                                        + value);
+                  }
+                } else {
+                  // it is a nominal attribute in the mining schema so find
+                  // the index that correponds to this value
+                  Attribute att = miningSchemaI.attribute(p.m_miningSchemaIndex); 
+                  expOrIndex = att.indexOfValue(value);
+                  if (expOrIndex == -1) {
+                    throw new Exception("[GeneralRegression] unable to find PPCell value "
+                                        + value + " in mining schema attribute "
+                                        + att.name());
+                  }
+                }
+              } else {
+                throw new Exception("[GeneralRegression] cant find predictor "
+                                    + predictorName + "in either the factors list "
+                                    + "or the covariates list");
+              }
+              predictorIndex = p.m_miningSchemaIndex;
+            }
+
+            // fill in cell value
+            PPCell ppc = new PPCell();
+            ppc.m_predictorName = predictorName; ppc.m_parameterName = parameterName;
+            ppc.m_value = expOrIndex;
+
+            // TO-DO: ppc.m_targetCategory (when handling for multiple PPMatrixes is implemented)
+            m_ppMatrix[parameterIndex][predictorIndex] = ppc;
+          }
+        }
+      }
+    } else {
+      throw new Exception("[GeneralRegression] more than one PPMatrix!");
+    }
+  }
+
+  private Predictor getCovariate(String predictorName) {
+    for (int i = 0; i < m_covariateList.size(); i++) {
+      if (predictorName.equals(m_covariateList.get(i).m_name)) {
+        return m_covariateList.get(i);
+      }
+    }
+    return null;
+  }
+
+  private Predictor getFactor(String predictorName) {
+    for (int i = 0; i < m_factorList.size(); i++) {
+      if (predictorName.equals(m_factorList.get(i).m_name)) {
+        return m_factorList.get(i);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Read the parameter matrix from the xml.
+   * 
+   * @param model Element that holds the model
+   * @throws Exception if a problem is encountered during extraction of
+   * the parameter matrix
+   */
+  private void readParamMatrix(Element model) throws Exception {
+
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+    Attribute classAtt = miningSchemaI.classAttribute();
+    // used when function type is classification but class attribute is numeric
+    // in the mining schema. We will assume that there is a Target specified in
+    // the pmml that defines the legal values for this class.
+    ArrayList<String> targetVals = null;
+
+    NodeList matrixL = model.getElementsByTagName("ParamMatrix");
+    if (matrixL.getLength() != 1) {
+      throw new Exception("[GeneralRegression] more than one ParamMatrix!");
+    }
+    Element matrix = (Element)matrixL.item(0);
+
+
+    // check for the case where the class in the mining schema is numeric,
+    // but this attribute is treated as discrete
+    if (m_functionType == Regression.RegressionTable.CLASSIFICATION &&
+        classAtt.isNumeric()) {
+      // try and convert the class attribute to nominal. For this to succeed
+      // there has to be a Target element defined in the PMML.
+      if (!m_miningSchema.hasTargetMetaData()) {
+        throw new Exception("[GeneralRegression] function type is classification and "
+                            + "class attribute in mining schema is numeric, however, "
+                            + "there is no Target element "
+                            + "specifying legal discrete values for the target!");
+
+      }
+
+      if (m_miningSchema.getTargetMetaData().getOptype() 
+          != TargetMetaInfo.Optype.CATEGORICAL) {
+        throw new Exception("[GeneralRegression] function type is classification and "
+                            + "class attribute in mining schema is numeric, however "
+                            + "Target element in PMML does not have optype categorical!");
+      }
+
+      // OK now get legal values
+      targetVals = m_miningSchema.getTargetMetaData().getValues();
+      if (targetVals.size() == 0) {
+        throw new Exception("[GeneralRegression] function type is classification and "
+                            + "class attribute in mining schema is numeric, however "
+                            + "Target element in PMML does not have any discrete values "
+                            + "defined!");
+      }
+
+      // Finally, convert the class in the mining schema to nominal
+      m_miningSchema.convertNumericAttToNominal(miningSchemaI.classIndex(), targetVals);
+    }
+    
+    // allocate space for the matrix 
+    m_paramMatrix = 
+        new PCell[(classAtt.isNumeric())
+                  ? 1
+                  : classAtt.numValues()][m_parameterList.size()];
+
+    NodeList pcellL = matrix.getElementsByTagName("PCell");
+    for (int i = 0; i < pcellL.getLength(); i++) {
+      // indicates that that this beta applies to all target categories
+      // or target is numeric
+      int targetCategoryIndex = -1;
+      int parameterIndex = -1;
+      Node pcell = pcellL.item(i);
+      if (pcell.getNodeType() == Node.ELEMENT_NODE) {
+        String paramName = ((Element)pcell).getAttribute("parameterName");
+        String targetCatName = ((Element)pcell).getAttribute("targetCategory");
+        String coefficient = ((Element)pcell).getAttribute("beta");
+        String df = ((Element)pcell).getAttribute("df");
+
+        for (int j = 0; j < m_parameterList.size(); j++) {
+          if (m_parameterList.get(j).m_name.equals(paramName)) {
+            parameterIndex = j;
+            // use the label if defined
+            if (m_parameterList.get(j).m_label != null) {
+              paramName = m_parameterList.get(j).m_label;
+            }
+            break;
+          }
+        }
+        if (parameterIndex == -1) {
+          throw new Exception("[GeneralRegression] unable to find parameter name "
+                              + paramName + " in parameter list");
+        }
+
+        if (targetCatName != null && targetCatName.length() > 0) {
+          if (classAtt.isNominal() || classAtt.isString()) {
+            targetCategoryIndex = classAtt.indexOfValue(targetCatName);
+          } else {
+            throw new Exception("[GeneralRegression] found a PCell with a named "
+                                + "target category: " + targetCatName
+                                + " but class attribute is numeric in "
+                                + "mining schema");
+          }
+        }
+
+        PCell p = new PCell();
+        if (targetCategoryIndex != -1) {
+          p.m_targetCategory = targetCatName;
+        }
+        p.m_parameterName = paramName;
+        try {
+          p.m_beta = Double.parseDouble(coefficient);
+        } catch (IllegalArgumentException ex) {
+          throw new Exception("[GeneralRegression] unable to parse beta value "
+                              + coefficient + " as a double from PCell");
+        }
+        if (df != null && df.length() > 0) {
+          try {
+            p.m_df = Integer.parseInt(df);
+          } catch (IllegalArgumentException ex) {
+            throw new Exception("[GeneralRegression] unable to parse df value "
+                              + df + " as an int from PCell");
+          }
+        }
+        
+        if (targetCategoryIndex != -1) {
+          m_paramMatrix[targetCategoryIndex][parameterIndex] = p;
+        } else {
+          // this PCell to all target categories (covers numeric class, in
+          // which case there will be only one row in the matrix anyway)
+          for (int j = 0; j < m_paramMatrix.length; j++) {
+            m_paramMatrix[j][parameterIndex] = p;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Return a textual description of this general regression.
+   * 
+   * @return a description of this general regression
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    temp.append("PMML version " + getPMMLVersion());
+    if (!getCreatorApplication().equals("?")) {
+      temp.append("\nApplication: " + getCreatorApplication());
+    }
+    temp.append("\nPMML Model: " + m_modelType);
+    temp.append("\n\n");
+    temp.append(m_miningSchema);
+
+    if (m_factorList.size() > 0) {
+      temp.append("Factors:\n");
+      for (Predictor p : m_factorList) {
+        temp.append("\t" + p + "\n");
+      }
+    }
+    temp.append("\n");
+    if (m_covariateList.size() > 0) {
+      temp.append("Covariates:\n");
+      for (Predictor p : m_covariateList) {
+        temp.append("\t" + p + "\n");
+      }
+    }
+    temp.append("\n");
+    
+    printPPMatrix(temp);
+    temp.append("\n");
+    printParameterMatrix(temp);
+    
+    // do the link function stuff
+    temp.append("\n");
+    
+    if (m_linkFunction != LinkFunction.NONE) {
+      temp.append("Link function: " + m_linkFunction);
+      if (m_offsetVariable != null) {
+        temp.append("\n\tOffset variable " + m_offsetVariable);
+      } else if (!Double.isNaN(m_offsetValue)) {
+        temp.append("\n\tOffset value " + m_offsetValue);
+      }
+      
+      if (m_trialsVariable != null) {
+        temp.append("\n\tTrials variable " + m_trialsVariable);
+      } else if (!Double.isNaN(m_trialsValue)) {
+        temp.append("\n\tTrials value " + m_trialsValue);
+      }
+      
+      if (m_distribution != Distribution.NONE) {
+        temp.append("\nDistribution: " + m_distribution);
+      }
+      
+      if (m_linkFunction == LinkFunction.NEGBIN &&
+          m_distribution == Distribution.NEGBINOMIAL &&
+          !Double.isNaN(m_distParameter)) {
+        temp.append("\n\tDistribution parameter " + m_distParameter);
+      }
+      
+      if (m_linkFunction == LinkFunction.POWER ||
+          m_linkFunction == LinkFunction.ODDSPOWER) {
+        if (!Double.isNaN(m_linkParameter)) {
+          temp.append("\n\nLink parameter " + m_linkParameter);
+        }
+      }
+    }
+    
+    if (m_cumulativeLinkFunction != CumulativeLinkFunction.NONE) {
+      temp.append("Cumulative link function: " + m_cumulativeLinkFunction);
+      
+      if (m_offsetVariable != null) {
+        temp.append("\n\tOffset variable " + m_offsetVariable);
+      } else if (!Double.isNaN(m_offsetValue)) {
+        temp.append("\n\tOffset value " + m_offsetValue);
+      }
+    }
+    temp.append("\n");
+    
+    return temp.toString();
+  }
+  
+  /**
+   * Format and print the PPMatrix to the supplied StringBuffer.
+   * 
+   * @param buff the StringBuffer to append to
+   */
+  protected void printPPMatrix(StringBuffer buff) {
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+    int maxAttWidth = 0;
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      Attribute a = miningSchemaI.attribute(i);
+      if (a.name().length() > maxAttWidth) {
+        maxAttWidth = a.name().length();
+      }
+    }
+
+    // check the width of the values
+    for (int i = 0; i < m_parameterList.size(); i++) {
+      for (int j = 0; j < miningSchemaI.numAttributes(); j++) {
+        if (m_ppMatrix[i][j] != null) {
+          double width = Math.log(Math.abs(m_ppMatrix[i][j].m_value)) /
+            Math.log(10.0);
+          if (width < 0) {
+            width = 1;
+          }
+          // decimal + # decimal places + 1
+          width += 2.0;
+          if ((int)width > maxAttWidth) {
+            maxAttWidth = (int)width;
+          }
+          if (miningSchemaI.attribute(j).isNominal() || 
+              miningSchemaI.attribute(j).isString()) {
+            // check the width of this value
+            String val = miningSchemaI.attribute(j).value((int)m_ppMatrix[i][j].m_value) + " ";
+            if (val.length() > maxAttWidth) {
+              maxAttWidth = val.length();
+            }
+          }
+        }
+      }
+    }
+
+    // get the max parameter width
+    int maxParamWidth = "Parameter  ".length();
+    for (Parameter p : m_parameterList) {
+      String temp = (p.m_label != null)
+        ? p.m_label + " "
+        : p.m_name + " ";
+
+      if (temp.length() > maxParamWidth) {
+        maxParamWidth = temp.length();
+      }
+    }
+
+    buff.append("Predictor-to-Parameter matrix:\n");
+    buff.append(PMMLUtils.pad("Predictor", " ", (maxParamWidth + (maxAttWidth * 2 + 2))
+                              - "Predictor".length(), true));
+    buff.append("\n" + PMMLUtils.pad("Parameter", " ", maxParamWidth - "Parameter".length(), false));
+    // attribute names
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      if (i != miningSchemaI.classIndex()) {
+        String attName = miningSchemaI.attribute(i).name();
+        buff.append(PMMLUtils.pad(attName, " ", maxAttWidth + 1 - attName.length(), true));
+      }
+    }
+    buff.append("\n");
+
+    for (int i = 0; i < m_parameterList.size(); i++) {
+      Parameter param = m_parameterList.get(i);
+      String paramS = (param.m_label != null)
+        ? param.m_label
+        : param.m_name;
+      buff.append(PMMLUtils.pad(paramS, " ", 
+                                maxParamWidth - paramS.length(), false));
+      for (int j = 0; j < miningSchemaI.numAttributes(); j++) {
+        if (j != miningSchemaI.classIndex()) {
+          PPCell p = m_ppMatrix[i][j];
+          String val = " ";
+          if (p != null) {
+            if (miningSchemaI.attribute(j).isNominal() ||
+                miningSchemaI.attribute(j).isString()) {
+              val = miningSchemaI.attribute(j).value((int)p.m_value);
+            } else {
+              val = "" + Utils.doubleToString(p.m_value, maxAttWidth, 4).trim();
+            }
+          }
+          buff.append(PMMLUtils.pad(val, " ", maxAttWidth + 1 - val.length(), true));
+        }
+      }
+      buff.append("\n");
+    }
+  }
+
+  /**
+   * Format and print the parameter matrix to the supplied StringBuffer.
+   * 
+   * @param buff the StringBuffer to append to
+   */
+  protected void printParameterMatrix(StringBuffer buff) {
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+
+    // get the maximum class value width (nominal)
+    int maxClassWidth = miningSchemaI.classAttribute().name().length();
+    if (miningSchemaI.classAttribute().isNominal()
+        || miningSchemaI.classAttribute().isString()) {
+      for (int i = 0; i < miningSchemaI.classAttribute().numValues(); i++) {
+        if (miningSchemaI.classAttribute().value(i).length() > maxClassWidth) {
+          maxClassWidth = miningSchemaI.classAttribute().value(i).length();
+        }
+      }
+    }
+
+    // get the maximum parameter name/label width
+    int maxParamWidth = 0;
+    for (int i = 0; i < m_parameterList.size(); i++) {
+      Parameter p = m_parameterList.get(i);
+      String val = (p.m_label != null)
+        ? p.m_label + " "
+        : p.m_name + " ";
+      if (val.length() > maxParamWidth) {
+        maxParamWidth = val.length();
+      }
+    }
+
+    // get the max beta value width
+    int maxBetaWidth = "Coeff.".length();
+    for (int i = 0; i < m_paramMatrix.length; i++) {
+      for (int j = 0; j < m_parameterList.size(); j++) {
+        PCell p = m_paramMatrix[i][j];
+        if (p != null) {
+          double width = Math.log(Math.abs(p.m_beta)) / Math.log(10);
+          if (width < 0) {
+            width = 1;
+          }
+          // decimal + # decimal places + 1
+          width += 7.0;
+          if ((int)width > maxBetaWidth) {
+            maxBetaWidth = (int)width;
+          }
+        }
+      }
+    }
+
+    buff.append("Parameter estimates:\n");
+    buff.append(PMMLUtils.pad(miningSchemaI.classAttribute().name(), " ", 
+                              maxClassWidth + maxParamWidth + 2 - 
+                              miningSchemaI.classAttribute().name().length(), false));
+    buff.append(PMMLUtils.pad("Coeff.", " ", maxBetaWidth + 1 - "Coeff.".length(), true));
+    buff.append(PMMLUtils.pad("df", " ", maxBetaWidth - "df".length(), true));
+    buff.append("\n");
+    for (int i = 0; i < m_paramMatrix.length; i++) {
+      // scan for non-null entry for this class value
+      boolean ok = false;
+      for (int j = 0; j < m_parameterList.size(); j++) {
+        if (m_paramMatrix[i][j] != null) {
+          ok = true;
+        }
+      }
+      if (!ok) {
+        continue;
+      }
+      // first the class value (if nominal)
+      String cVal = (miningSchemaI.classAttribute().isNominal() || 
+          miningSchemaI.classAttribute().isString())
+        ? miningSchemaI.classAttribute().value(i)
+        : " ";
+      buff.append(PMMLUtils.pad(cVal, " ", maxClassWidth - cVal.length(), false));     
+      buff.append("\n");
+      for (int j = 0; j < m_parameterList.size(); j++) {
+        PCell p = m_paramMatrix[i][j];
+        if (p != null) {
+          String label = p.m_parameterName;
+          buff.append(PMMLUtils.pad(label, " ", maxClassWidth + maxParamWidth + 2 -
+                                    label.length(), true));
+          String betaS = Utils.doubleToString(p.m_beta, maxBetaWidth, 4).trim();
+          buff.append(PMMLUtils.pad(betaS, " ", maxBetaWidth + 1 - betaS.length(), true));
+          String dfS = Utils.doubleToString(p.m_df, maxBetaWidth, 4).trim();
+          buff.append(PMMLUtils.pad(dfS, " ", maxBetaWidth - dfS.length(), true));
+          buff.append("\n");
+        }
+      }
+    }
+  }
+  
+  /**
+   * Construct the incoming parameter vector based on the values
+   * in the incoming test instance.
+   * 
+   * @param incomingInst the values of the incoming test instance
+   * @return the populated parameter vector ready to be multiplied against
+   * the vector of coefficients.
+   * @throws Exception if there is some problem whilst constructing the
+   * parameter vector
+   */
+  private double[] incomingParamVector(double[] incomingInst) throws Exception {
+    Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+    double[] incomingPV = new double[m_parameterList.size()];
+    
+    for (int i = 0; i < m_parameterList.size(); i++) {
+      //
+      // default is that this row represents the intercept.
+      // this will be the case if there are all null entries in this row
+      incomingPV[i] = 1.0;
+
+      // loop over the attributes (predictors)
+      for (int j = 0; j < miningSchemaI.numAttributes(); j++) {        
+        PPCell cellEntry = m_ppMatrix[i][j];
+        Predictor p = null;
+        if (cellEntry != null) {
+          if ((p = getFactor(cellEntry.m_predictorName)) != null) {
+            if ((int)incomingInst[p.m_miningSchemaIndex] == (int)cellEntry.m_value) {
+              incomingPV[i] *= 1.0; // we have a match
+            } else {
+              incomingPV[i] *= 0.0;
+            }
+          } else if ((p = getCovariate(cellEntry.m_predictorName)) != null) {
+              incomingPV[i] *= Math.pow(incomingInst[p.m_miningSchemaIndex], cellEntry.m_value);
+          } else {
+            throw new Exception("[GeneralRegression] can't find predictor "
+                + cellEntry.m_predictorName + " in either the list of factors or covariates");
+          }
+        }
+      }
+    }
+    
+    return incomingPV;
+  }
+
+  /**                                                                                                             
+   * Classifies the given test instance. The instance has to belong to a                                          
+   * dataset when it's being classified.                                                          
+   *                                                                                                              
+   * @param inst the instance to be classified                                                                
+   * @return the predicted most likely class for the instance or                                                  
+   * Utils.missingValue() if no prediction is made                                                             
+   * @exception Exception if an error occurred during the prediction                                              
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    if (!m_initialized) {
+      mapToMiningSchema(inst.dataset());
+    }
+    double[] preds = null;
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+      preds = new double[1];
+    } else {
+      preds = new double[m_miningSchema.getFieldsAsInstances().classAttribute().numValues()];
+    }
+    
+    // create an array of doubles that holds values from the incoming
+    // instance; in order of the fields in the mining schema. We will
+    // also handle missing values and outliers here.
+    double[] incoming = m_fieldsMap.instanceToSchema(inst, m_miningSchema);
+    
+    // In this implementation we will default to information in the Target element (default
+    // value for numeric prediction and prior probabilities for classification). If there is
+    // no Target element defined, then an Exception is thrown.
+
+    boolean hasMissing = false;
+    for (int i = 0; i < incoming.length; i++) {
+      if (i != m_miningSchema.getFieldsAsInstances().classIndex() && 
+          Double.isNaN(incoming[i])) {
+        hasMissing = true;
+        break;
+      }
+    }
+    
+    if (hasMissing) {
+      if (!m_miningSchema.hasTargetMetaData()) {
+        String message = "[GeneralRegression] WARNING: Instance to predict has missing value(s) but "
+          + "there is no missing value handling meta data and no "
+          + "prior probabilities/default value to fall back to. No "
+          + "prediction will be made (" 
+          + ((m_miningSchema.getFieldsAsInstances().classAttribute().isNominal()
+              || m_miningSchema.getFieldsAsInstances().classAttribute().isString())
+              ? "zero probabilities output)."
+              : "NaN output).");
+        if (m_log == null) {
+          System.err.println(message);
+        } else {
+          m_log.logMessage(message);
+        }
+        
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = Utils.missingValue();
+        }
+        return preds;
+      } else {
+        // use prior probablilities/default value
+        TargetMetaInfo targetData = m_miningSchema.getTargetMetaData();
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = targetData.getDefaultValue();
+        } else {
+          Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+          for (int i = 0; i < miningSchemaI.classAttribute().numValues(); i++) {
+            preds[i] = targetData.getPriorProbability(miningSchemaI.classAttribute().value(i));
+          }
+        }
+        return preds;
+      }
+    } else {
+      // construct input parameter vector here
+      double[] inputParamVector = incomingParamVector(incoming);
+      computeResponses(incoming, inputParamVector, preds);
+    }
+    
+    return preds;
+  }
+  
+  /**
+   * Compute the responses for the function given the parameter values corresponding
+   * to the current incoming instance.
+   * 
+   * @param incomingInst raw incoming instance values (after missing value
+   * replacement and outlier treatment)
+   * @param incomingParamVector incoming instance values mapped to parameters
+   * @param responses will contain the responses computed by the function
+   * @throws Exception if something goes wrong
+   */
+  private void computeResponses(double[] incomingInst, 
+                                double[] incomingParamVector,
+                                double[] responses) throws Exception {
+    for (int i = 0; i < responses.length; i++) {
+      for (int j = 0; j < m_parameterList.size(); j++) {
+        // a row of the parameter matrix should have all non-null entries
+        // except for the last class (in the case of classification) which
+        // should have just an intercept of 0. Need to handle the case where
+        // no intercept has been defined in the pmml file for the last class
+        PCell p = m_paramMatrix[i][j];
+        if (p == null) {
+          responses[i] += 0.0 * incomingParamVector[j];
+        } else {
+          responses[i] += incomingParamVector[j] * p.m_beta;
+        }
+      }
+    }
+    
+    switch(m_modelType) {
+    case MULTINOMIALLOGISTIC:
+      computeProbabilitiesMultinomialLogistic(responses);
+      break;
+    case REGRESSION:
+      // nothing to be done
+      break;
+    case GENERALLINEAR:
+    case GENERALIZEDLINEAR:
+      if (m_linkFunction != LinkFunction.NONE) {
+        computeResponseGeneralizedLinear(incomingInst, responses);
+      } else {
+        throw new Exception("[GeneralRegression] no link function specified!");
+      }
+      break;
+    case ORDINALMULTINOMIAL:
+      if (m_cumulativeLinkFunction != CumulativeLinkFunction.NONE) {
+        computeResponseOrdinalMultinomial(incomingInst, responses);
+      } else {
+        throw new Exception("[GeneralRegression] no cumulative link function specified!");
+      }
+      break;
+      default:
+        throw new Exception("[GeneralRegression] unknown model type");
+    }
+  }
+  
+  /**
+   * Computes probabilities for the multinomial logistic model type.
+   * 
+   * @param responses will hold the responses computed by the function.
+   */
+  private static void computeProbabilitiesMultinomialLogistic(double[] responses) {
+    double[] r = responses.clone();
+    for (int j = 0; j < r.length; j++) {
+      double sum = 0;
+      boolean overflow = false;
+      for (int k = 0; k < r.length; k++) {
+        if (r[k] - r[j] > 700) {
+          overflow = true;
+          break;
+        }
+        sum += Math.exp(r[k] - r[j]);
+      }
+      if (overflow) {
+        responses[j] = 0.0;
+      } else {
+        responses[j] = 1.0 / sum;
+      }
+    }
+  }
+  
+  /**
+   * Computes responses for the general linear and generalized linear model
+   * types.
+   * 
+   * @param incomingInst the raw incoming instance values (after missing value
+   * replacement and outlier treatment etc).
+   * @param responses will hold the responses computed by the function
+   * @throws Exception if a problem occurs. 
+   */
+  private void computeResponseGeneralizedLinear(double[] incomingInst, 
+                                                double[] responses) 
+    throws Exception {
+    double[] r = responses.clone();
+    
+    double offset = 0;
+    if (m_offsetVariable != null) {
+      Attribute offsetAtt = 
+        m_miningSchema.getFieldsAsInstances().attribute(m_offsetVariable);
+      if (offsetAtt == null) {
+        throw new Exception("[GeneralRegression] unable to find offset variable "
+            + m_offsetVariable + " in the mining schema!");
+      }
+      offset = incomingInst[offsetAtt.index()];
+    } else if (!Double.isNaN(m_offsetValue)) {
+      offset = m_offsetValue;
+    }
+    
+    double trials = 1;
+    if (m_trialsVariable != null) {
+      Attribute trialsAtt = m_miningSchema.getFieldsAsInstances().attribute(m_trialsVariable);
+      if (trialsAtt == null) {
+        throw new Exception("[GeneralRegression] unable to find trials variable "
+            + m_trialsVariable + " in the mining schema!");
+      }
+      trials = incomingInst[trialsAtt.index()];
+    } else if (!Double.isNaN(m_trialsValue)) {
+      trials = m_trialsValue;
+    }
+    
+    double distParam = 0;
+    if (m_linkFunction == LinkFunction.NEGBIN && 
+        m_distribution == Distribution.NEGBINOMIAL) {
+      if (Double.isNaN(m_distParameter)) {
+        throw new Exception("[GeneralRegression] no distribution parameter defined!");
+      }
+      distParam = m_distParameter;
+    }
+    
+    double linkParam = 0;
+    if (m_linkFunction == LinkFunction.POWER || 
+        m_linkFunction == LinkFunction.ODDSPOWER) {
+      if (Double.isNaN(m_linkParameter)) {
+        throw new Exception("[GeneralRegression] no link parameter defined!");
+      }
+      linkParam = m_linkParameter;
+    }
+   
+    for (int i = 0; i < r.length; i++) {
+      responses[i] = m_linkFunction.eval(r[i], offset, trials, distParam, linkParam);
+    }
+  }
+    
+  /**
+   * Computes responses for the ordinal multinomial model type.
+   * 
+   * @param incomingInst the raw incoming instance values (after missing value
+   * replacement and outlier treatment etc).
+   * @param responses will hold the responses computed by the function
+   * @throws Exception if a problem occurs. 
+   */
+  private void computeResponseOrdinalMultinomial(double[] incomingInst, 
+                                                  double[] responses) throws Exception {
+    
+    double[] r = responses.clone();
+    
+    double offset = 0;
+    if (m_offsetVariable != null) {
+      Attribute offsetAtt = 
+        m_miningSchema.getFieldsAsInstances().attribute(m_offsetVariable);
+      if (offsetAtt == null) {
+        throw new Exception("[GeneralRegression] unable to find offset variable "
+            + m_offsetVariable + " in the mining schema!");
+      }
+      offset = incomingInst[offsetAtt.index()];
+    } else if (!Double.isNaN(m_offsetValue)) {
+      offset = m_offsetValue;
+    }
+    
+    for (int i = 0; i < r.length; i++) {
+      if (i == 0) {
+        responses[i] = m_cumulativeLinkFunction.eval(r[i], offset);
+   
+      } else if (i == (r.length - 1)) {
+        responses[i] = 1.0 - responses[i - 1];
+      } else {
+        responses[i] = m_cumulativeLinkFunction.eval(r[i], offset) - responses[i - 1];
+      }
+    }
+  }
+
+  /* (non-Javadoc)
+   * @see weka.core.RevisionHandler#getRevision()
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/NeuralNetwork.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/NeuralNetwork.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/NeuralNetwork.java	(revision 29)
@@ -0,0 +1,936 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NeuralNetwork.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.pmml.*;
+
+/**
+ * Class implementing import of PMML Neural Network model. Can be used as a Weka
+ * classifier for prediction (buildClassifier() raises an Exception).
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class NeuralNetwork extends PMMLClassifier {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -4545904813133921249L;
+
+  /**
+   * Small inner class for a NeuralInput (essentially just
+   * wraps a DerivedField and adds an ID)
+   */
+  static class NeuralInput implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -1902233762824835563L;
+    
+    /** Field that this input refers to */
+    private DerivedFieldMetaInfo m_field;
+    
+    /** ID string */
+    private String m_ID = null;
+    
+    private String getID() {
+      return m_ID;
+    }
+    
+    protected NeuralInput(Element input, MiningSchema miningSchema) throws Exception {
+      m_ID = input.getAttribute("id");
+      
+      NodeList fL = input.getElementsByTagName("DerivedField");
+      if (fL.getLength() != 1) {
+        throw new Exception("[NeuralInput] expecting just one derived field!");
+      }
+      
+      Element dF = (Element)fL.item(0);
+      Instances allFields = miningSchema.getFieldsAsInstances();
+      ArrayList<Attribute> fieldDefs = new ArrayList<Attribute>();
+      for (int i = 0; i < allFields.numAttributes(); i++) {
+        fieldDefs.add(allFields.attribute(i));
+      }
+      m_field = new DerivedFieldMetaInfo(dF, fieldDefs, miningSchema.getTransformationDictionary());
+    }
+    
+    protected double getValue(double[] incoming) throws Exception {
+      return m_field.getDerivedValue(incoming);
+    }
+    
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      temp.append("Nueral input (" + getID() + ")\n");
+      temp.append(m_field);
+      
+      return temp.toString();
+    }
+  }
+  
+  /**
+   * Inner class representing a layer in the network.
+   */
+  class NeuralLayer implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -8386042001675763922L;
+
+    /** The number of neurons in this layer */
+    private int m_numNeurons = 0;
+    
+    /** Activation function (if defined, overrides one in NeuralNetwork) */
+    private ActivationFunction m_layerActivationFunction = null;
+    
+    /** Threshold (if defined overrides one in NeuralNetwork) */
+    private double m_layerThreshold = Double.NaN; 
+    
+    /** Width (if defined overrides one in NeuralNetwork) */
+    private double m_layerWidth = Double.NaN;
+    
+    /** Altitude (if defined overrides one in NeuralNetwork) */
+    private double m_layerAltitude = Double.NaN;
+    
+    /** Normalization (if defined overrides one in NeuralNetwork) */
+    private Normalization m_layerNormalization = null;
+    
+    /** The neurons at this hidden layer */
+    private Neuron[] m_layerNeurons = null;
+    
+    /** Stores the output of this layer (for given inputs) */
+    private HashMap<String, Double> m_layerOutput = new HashMap<String, Double>();
+    
+    protected NeuralLayer(Element layerE) {
+      
+      String activationFunction = layerE.getAttribute("activationFunction");
+      if (activationFunction != null && activationFunction.length() > 0) {
+        for (ActivationFunction a : ActivationFunction.values()) {
+          if (a.toString().equals(activationFunction)) {
+            m_layerActivationFunction = a;
+            break;
+          }
+        }
+      } else {
+        // use the network-level activation function
+        m_layerActivationFunction = m_activationFunction;
+      }
+      
+      String threshold = layerE.getAttribute("threshold");
+      if (threshold != null && threshold.length() > 0) {
+        m_layerThreshold = Double.parseDouble(threshold);
+      } else {
+        // use network-level threshold
+        m_layerThreshold = m_threshold;
+      }
+      
+      String width = layerE.getAttribute("width");
+      if (width != null && width.length() > 0) {
+        m_layerWidth = Double.parseDouble(width);
+      } else {
+        // use network-level width
+        m_layerWidth = m_width;
+      }
+      
+      String altitude = layerE.getAttribute("altitude");
+      if (altitude != null && altitude.length() > 0) {
+        m_layerAltitude = Double.parseDouble(altitude);
+      } else {
+        // use network-level altitude
+        m_layerAltitude = m_altitude;
+      }
+      
+      String normMethod = layerE.getAttribute("normalizationMethod");
+      if (normMethod != null && normMethod.length() > 0) {
+        for (Normalization n : Normalization.values()) {
+          if (n.toString().equals(normMethod)) {
+            m_layerNormalization = n;
+            break;
+          }
+        }
+      } else {
+        // use network-level normalization method
+        m_layerNormalization = m_normalizationMethod;
+      }
+      
+      NodeList neuronL = layerE.getElementsByTagName("Neuron");
+      m_numNeurons = neuronL.getLength();
+      m_layerNeurons = new Neuron[m_numNeurons];
+      for (int i = 0; i < neuronL.getLength(); i++) {
+        Node neuronN = neuronL.item(i);
+        if (neuronN.getNodeType() == Node.ELEMENT_NODE) {
+          m_layerNeurons[i] = new Neuron((Element)neuronN, this);
+        }
+      }
+    }
+    
+    protected ActivationFunction getActivationFunction() {
+      return m_layerActivationFunction;
+    }
+    
+    protected double getThreshold() {
+      return m_layerThreshold;
+    }
+    
+    protected double getWidth() {
+      return m_layerWidth;
+    }
+    
+    protected double getAltitude() {
+      return m_layerAltitude;
+    }
+    
+    protected Normalization getNormalization() {
+      return m_layerNormalization;
+    }
+    
+    /**
+     * Compute the output values for this layer.
+     * 
+     * @param incoming the incoming values
+     * @return the output values for this layer
+     * @throws Exception if there is a problem computing the outputs
+     */
+    protected HashMap<String, Double> computeOutput(HashMap<String, Double> incoming) 
+      throws Exception {
+      
+      m_layerOutput.clear();
+      
+      double normSum = 0;
+      for (int i = 0; i < m_layerNeurons.length; i++) {
+        double neuronOut = m_layerNeurons[i].getValue(incoming);
+        String neuronID = m_layerNeurons[i].getID();
+
+        if (m_layerNormalization == Normalization.SOFTMAX) {
+          normSum += Math.exp(neuronOut);
+        } else if (m_layerNormalization == Normalization.SIMPLEMAX) {
+          normSum += neuronOut;
+        }
+        //System.err.println("Inserting ID " + neuronID + " " + neuronOut);
+        m_layerOutput.put(neuronID, neuronOut);
+      }
+      
+      // apply the normalization (if necessary)
+      if (m_layerNormalization != Normalization.NONE) {
+        for (int i = 0; i < m_layerNeurons.length; i++) {
+          double val = m_layerOutput.get(m_layerNeurons[i].getID());
+//          System.err.println("Normalizing ID " + m_layerNeurons[i].getID() + " " + val);
+          if (m_layerNormalization == Normalization.SOFTMAX) {
+            val = Math.exp(val) / normSum;
+          } else {
+            val = (val / normSum);
+          }
+          m_layerOutput.put(m_layerNeurons[i].getID(), val);
+        }
+      }
+      return m_layerOutput;
+    }
+    
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      temp.append("activation: " + getActivationFunction() + "\n");
+      if (!Double.isNaN(getThreshold())) {
+        temp.append("threshold: " + getThreshold() + "\n");
+      }
+      if (!Double.isNaN(getWidth())) {
+        temp.append("width: " + getWidth() + "\n");
+      }
+      if (!Double.isNaN(getAltitude())) {
+        temp.append("altitude: " + getAltitude() + "\n");
+      }
+      temp.append("normalization: " + m_layerNormalization + "\n");
+      for (int i = 0; i < m_numNeurons; i++) {
+        temp.append(m_layerNeurons[i] + "\n");
+      }
+
+      return temp.toString();
+    }
+  }
+  
+  /**
+   * Inner class encapsulating a Neuron
+   */
+  static class Neuron implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -3817434025682603443L;
+
+    /** ID string */
+    private String m_ID = null;
+    
+    /** The layer we belong to (for accessing activation function, threshold etc.) */
+    private NeuralLayer m_layer;
+    
+    /** The bias */
+    private double m_bias = 0.0;
+    
+    /** The width (if defined overrides the one in NeuralLayer or NeuralNetwork) */
+    private double m_neuronWidth = Double.NaN;
+    
+    /** The altitude (if defined overrides the one in NeuralLayer or NeuralNetwork) */
+    private double m_neuronAltitude = Double.NaN;
+    
+    /** The IDs of the neurons/neural inputs that we are connected to */
+    private String[] m_connectionIDs = null;
+    
+    /** The weights corresponding to the connections */
+    private double[] m_weights = null;
+    
+    protected Neuron(Element neuronE, NeuralLayer layer) {
+      m_layer = layer;
+      
+      m_ID = neuronE.getAttribute("id");
+      
+      String bias = neuronE.getAttribute("bias");
+      if (bias != null && bias.length() > 0) {
+        m_bias = Double.parseDouble(bias);
+      }
+      
+      String width = neuronE.getAttribute("width");
+      if (width != null && width.length() > 0) {
+        m_neuronWidth = Double.parseDouble(width);
+      }
+      
+      String altitude = neuronE.getAttribute("altitude");
+      if (altitude != null && altitude.length() > 0) {
+        m_neuronAltitude = Double.parseDouble(altitude);
+      }
+      
+      // get the connection details
+      NodeList conL = neuronE.getElementsByTagName("Con");
+      m_connectionIDs = new String[conL.getLength()];
+      m_weights = new double[conL.getLength()];
+      for (int i = 0; i < conL.getLength(); i++) {
+        Node conN = conL.item(i);
+        if (conN.getNodeType() == Node.ELEMENT_NODE) {
+          Element conE = (Element)conN;
+          m_connectionIDs[i] = conE.getAttribute("from");
+          String weight = conE.getAttribute("weight");
+          m_weights[i] = Double.parseDouble(weight);
+        }
+      }
+    }
+    
+    protected String getID() {
+      return m_ID;
+    }    
+    
+    /**
+     * Compute the output of this Neuron.
+     * 
+     * @param incoming a Map of input values. The keys are the IDs
+     * of incoming connections (either neural inputs or neurons) and
+     * the values are the output values of the neural input/neuron in
+     * question.
+     * 
+     * @return the output of this neuron
+     * @throws Exception if any of our incoming connection IDs cannot be
+     * located in the Map
+     */
+    protected double getValue(HashMap<String, Double> incoming) throws Exception {
+      
+      double z = 0;
+      double result = Double.NaN;
+      
+      double width = (Double.isNaN(m_neuronWidth))
+        ? m_layer.getWidth()
+        : m_neuronWidth;
+
+      z = m_bias;
+      for (int i = 0; i < m_connectionIDs.length; i++) {
+        Double inVal = incoming.get(m_connectionIDs[i]);
+        if (inVal == null) {
+          throw new Exception("[Neuron] unable to find connection " 
+              + m_connectionIDs[i] + " in input Map!");
+        }
+
+        if (m_layer.getActivationFunction() != ActivationFunction.RADIALBASIS) {
+          // multiply with weight
+          double inV = inVal.doubleValue() * m_weights[i];
+          z += inV;
+        } else {
+          // Euclidean distance to the center (stored in m_weights)
+          double inV = Math.pow((inVal.doubleValue() - m_weights[i]), 2.0);
+          z += inV;
+        }
+      }
+      
+      // apply the width if necessary
+      if (m_layer.getActivationFunction() == ActivationFunction.RADIALBASIS) {
+        z /= (2.0 * (width * width));
+      }
+
+      double threshold = m_layer.getThreshold();
+      double altitude = (Double.isNaN(m_neuronAltitude))
+        ? m_layer.getAltitude()
+        : m_neuronAltitude;
+        
+      double fanIn = m_connectionIDs.length;        
+      result = m_layer.getActivationFunction().eval(z, threshold, altitude, fanIn);
+      
+      return result;
+    }
+    
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      temp.append("Nueron (" + m_ID + ") [bias:" + m_bias);
+      if (!Double.isNaN(m_neuronWidth)) {
+        temp.append(" width:" + m_neuronWidth);
+      }
+      if (!Double.isNaN(m_neuronAltitude)) {
+        temp.append(" altitude:" + m_neuronAltitude);
+      }
+      temp.append("]\n");
+      temp.append("  con. (ID:weight): ");
+      for (int i = 0; i < m_connectionIDs.length; i++) {
+        temp.append(m_connectionIDs[i] + ":" + Utils.doubleToString(m_weights[i], 2));
+        if ((i + 1) % 10 == 0 || i == m_connectionIDs.length - 1) {
+          temp.append("\n                    ");
+        } else {
+          temp.append(", ");
+        }
+      }
+      return temp.toString();
+    }
+  }
+  
+  static class NeuralOutputs implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -233611113950482952L;
+
+    /** The neurons we are mapping */
+    private String[] m_outputNeurons = null;
+    
+    /**
+     *  In the case of a nominal class, the index of the value
+     * being predicted by each output neuron
+     */
+    private int[] m_categoricalIndexes = null;
+    
+    /** The class attribute we are mapping to */
+    private Attribute m_classAttribute = null;
+    
+    /** Used when the class is numeric */
+    private NormContinuous m_regressionMapping = null;
+        
+    protected NeuralOutputs(Element outputs, MiningSchema miningSchema) throws Exception {
+      m_classAttribute = miningSchema.getMiningSchemaAsInstances().classAttribute();
+      
+      int vals = (m_classAttribute.isNumeric())
+        ? 1
+        : m_classAttribute.numValues();
+      
+      m_outputNeurons = new String[vals];
+      m_categoricalIndexes = new int[vals];
+      
+      NodeList outputL = outputs.getElementsByTagName("NeuralOutput");
+      if (outputL.getLength() != m_outputNeurons.length) {
+        throw new Exception("[NeuralOutputs] the number of neural outputs does not match "
+            + "the number expected!");
+      }
+      
+      for (int i = 0; i < outputL.getLength(); i++) {
+        Node outputN = outputL.item(i);
+        if (outputN.getNodeType() == Node.ELEMENT_NODE) {
+          Element outputE = (Element)outputN;
+          // get the ID for this output neuron
+          m_outputNeurons[i] = outputE.getAttribute("outputNeuron");
+          
+          if (m_classAttribute.isNumeric()) {
+            // get the single norm continuous
+            NodeList contL = outputE.getElementsByTagName("NormContinuous");
+            if (contL.getLength() != 1) {
+              throw new Exception("[NeuralOutputs] Should be exactly one norm continuous element "
+                  + "for numeric class!");
+            }
+            Node normContNode = contL.item(0);
+            String attName = ((Element)normContNode).getAttribute("field");
+            Attribute dummyTargetDef = new Attribute(attName);
+            ArrayList<Attribute> dummyFieldDefs = new ArrayList<Attribute>();
+            dummyFieldDefs.add(dummyTargetDef);
+            
+            m_regressionMapping = new NormContinuous((Element)normContNode, 
+                FieldMetaInfo.Optype.CONTINUOUS, dummyFieldDefs);
+            break;
+          } else {
+            // we just need to grab the categorical value (out of the NormDiscrete element)
+            // that this output neuron is associated with
+            NodeList discL = outputE.getElementsByTagName("NormDiscrete");
+            if (discL.getLength() != 1) {
+              throw new Exception("[NeuralOutputs] Should be only one norm discrete element "
+                  + "per derived field/neural output for a nominal class!");
+            }
+            Node normDiscNode = discL.item(0);
+            String attValue = ((Element)normDiscNode).getAttribute("value");
+            int index = m_classAttribute.indexOfValue(attValue);
+            if (index < 0) {
+              throw new Exception("[NeuralOutputs] Can't find specified target value "
+                  + attValue + " in class attribute " + m_classAttribute.name());
+            }
+            m_categoricalIndexes[i] = index;
+          }
+        }
+      }
+    }
+    
+    /**
+     * Compute the output. Either a probability distribution or a single
+     * value (regression).
+     * 
+     * @param incoming the values from the last hidden layer
+     * @param preds the array to fill with predicted values
+     * @throws Exception if there is a problem computing the output
+     */
+    protected void getOuput(HashMap<String, Double> incoming, double[] preds) throws Exception {
+      
+      if (preds.length != m_outputNeurons.length) {
+        throw new Exception("[NeuralOutputs] Incorrect number of predictions requested: "
+            + preds.length + "requested, " + m_outputNeurons.length + " expected");
+      }
+      for (int i = 0; i < m_outputNeurons.length; i++) {
+        Double neuronOut = incoming.get(m_outputNeurons[i]);
+        if (neuronOut == null) {
+          throw new Exception("[NeuralOutputs] Unable to find output neuron "
+              + m_outputNeurons[i] + " in the incoming HashMap!!");
+        }
+        if (m_classAttribute.isNumeric()) {
+          // will be only one output neuron anyway
+          preds[0] = neuronOut.doubleValue();
+          
+          preds[0] = m_regressionMapping.getResultInverse(preds);
+        } else {
+
+          // clip at zero
+          // preds[m_categoricalIndexes[i]] = (neuronOut < 0) ? 0.0 : neuronOut;
+          preds[m_categoricalIndexes[i]] = neuronOut;
+        }
+      }
+      
+      if (m_classAttribute.isNominal()) {
+        // check for negative values and adjust
+        double min = preds[Utils.minIndex(preds)];
+        if (min < 0) {
+          for (int i = 0; i < preds.length; i++) {
+            preds[i] -= min;
+          }
+        }
+        // do a simplemax normalization
+        Utils.normalize(preds);
+      }
+    }
+    
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      for (int i = 0; i < m_outputNeurons.length; i++) {
+        temp.append("Output neuron (" + m_outputNeurons[i] + ")\n");
+        temp.append("mapping:\n");
+        if (m_classAttribute.isNumeric()) {
+          temp.append(m_regressionMapping +"\n");
+        } else {
+          temp.append(m_classAttribute.name() + " = " 
+              + m_classAttribute.value(m_categoricalIndexes[i]) + "\n");
+        }
+      }
+      
+      return temp.toString();
+    }
+  }
+  
+  /**
+   * Enumerated type for the mining function
+   */
+  enum MiningFunction {
+    CLASSIFICATION,
+    REGRESSION;
+  }
+  
+  /** The mining function */
+  protected MiningFunction m_functionType = MiningFunction.CLASSIFICATION;
+  
+  /**
+   * Enumerated type for the activation function.
+   */
+  enum ActivationFunction {
+    THRESHOLD("threshold") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        if (z > threshold) {
+          return 1.0;
+        }
+        return 0.0;
+      }
+    },
+    LOGISTIC("logistic") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return 1.0 / (1.0 + Math.exp(-z));
+      }
+    },
+    TANH("tanh") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        double a = Math.exp( z );
+        double b = Math.exp( -z );
+        return ((a-b)/(a+b));
+        //return (1.0 - Math.exp(-2.0 * z)) / (1.0 + Math.exp(-2.0 * z));
+      }
+    },
+    IDENTITY("identity") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return z;
+      }
+    },
+    EXPONENTIAL("exponential") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return Math.exp(z);
+      }
+    },
+    RECIPROCAL("reciprocal") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return 1.0 / z;
+      }
+    },
+    SQUARE("square") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return  z * z;
+      }
+    },
+    GAUSS("gauss") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return Math.exp(-(z * z));
+      }
+    },
+    SINE("sine") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return Math.sin(z);
+      }
+    },
+    COSINE("cosine") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return Math.cos(z);
+      }
+    },
+    ELLICOT("ellicot") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return z / (1.0 + Math.abs(z));
+      }
+    },
+    ARCTAN("arctan") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return 2.0 * Math.atan(z) / Math.PI;
+      }
+    },
+    RADIALBASIS("radialBasis") {
+      double eval(double z, double threshold, double altitude, double fanIn) {
+        return Math.exp(fanIn * Math.log(altitude) - z);
+      }
+    };
+    
+    abstract double eval(double z, double threshold, double altitude, double fanIn);
+    
+    private final String m_stringVal;
+    
+    ActivationFunction(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** The activation function to use */
+  protected ActivationFunction m_activationFunction = ActivationFunction.ARCTAN;
+  
+  /**
+   * Enumerated type for the normalization method
+   */
+  enum Normalization {
+    NONE ("none"),
+    SIMPLEMAX ("simplemax"),
+    SOFTMAX ("softmax");
+    
+    private final String m_stringVal;
+    
+    Normalization(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+    
+  /** The normalization method */
+  protected Normalization m_normalizationMethod = Normalization.NONE;
+  
+  /** Threshold activation */
+  protected double m_threshold = 0.0; // default = 0
+  
+  /** Width for radial basis */
+  protected double m_width = Double.NaN; // no default
+  
+  /** Altitude for radial basis */
+  protected double m_altitude = 1.0; // default = 1
+  
+  /** The number of inputs to the network */
+  protected int m_numberOfInputs = 0;
+  
+  /** Number of hidden layers in the network */
+  protected int m_numberOfLayers = 0;
+  
+  /** The inputs to the network */
+  protected NeuralInput[] m_inputs = null;
+  
+  /** A map for storing network input values (computed from an incoming instance) */
+  protected HashMap<String, Double> m_inputMap = new HashMap<String, Double>();
+    
+  /** The hidden layers in the network */
+  protected NeuralLayer[] m_layers = null;
+  
+  /** The outputs of the network */
+  protected NeuralOutputs m_outputs = null;
+  
+  public NeuralNetwork(Element model, Instances dataDictionary,
+                       MiningSchema miningSchema) throws Exception {
+    
+    super(dataDictionary, miningSchema);
+    
+    String fn = model.getAttribute("functionName");
+    if (fn.equals("regression")) {
+      m_functionType = MiningFunction.REGRESSION;
+    }
+    
+    String act = model.getAttribute("activationFunction");
+    if (act == null || act.length() == 0) {
+      throw new Exception("[NeuralNetwork] no activation functon defined");
+    }
+    
+    // get the activation function
+    for (ActivationFunction a : ActivationFunction.values()) {
+      if (a.toString().equals(act)) {
+        m_activationFunction = a;
+        break;
+      }
+    }
+    
+    // get the normalization method (if specified)
+    String norm = model.getAttribute("normalizationMethod");
+    if (norm != null && norm.length() > 0) {
+      for (Normalization n : Normalization.values()) {
+        if (n.toString().equals(norm)) {
+          m_normalizationMethod = n;
+          break;
+        }
+      }
+    }
+    
+    String thresh = model.getAttribute("threshold");
+    if (thresh != null && thresh.length() > 0) {
+      m_threshold = Double.parseDouble(thresh);
+    }
+    String width = model.getAttribute("width");
+    if (width != null && width.length() > 0) {
+      m_width = Double.parseDouble(width);
+    }
+    String alt = model.getAttribute("altitude");
+    if (alt != null && alt.length() > 0) {
+      m_altitude = Double.parseDouble(alt);
+    }
+    
+    // get all the inputs
+    NodeList inputL = model.getElementsByTagName("NeuralInput");
+    m_numberOfInputs = inputL.getLength();
+    m_inputs = new NeuralInput[m_numberOfInputs];
+    for (int i = 0; i < m_numberOfInputs; i++) {
+      Node inputN = inputL.item(i);
+      if (inputN.getNodeType() == Node.ELEMENT_NODE) {
+        NeuralInput nI = new NeuralInput((Element)inputN, m_miningSchema);
+        m_inputs[i] = nI;
+      }
+    }
+    
+    // get the layers
+    NodeList layerL = model.getElementsByTagName("NeuralLayer");
+    m_numberOfLayers = layerL.getLength();
+    m_layers = new NeuralLayer[m_numberOfLayers];
+    for (int i = 0; i < m_numberOfLayers; i++) {
+      Node layerN = layerL.item(i);
+      if (layerN.getNodeType() == Node.ELEMENT_NODE) {
+        NeuralLayer nL = new NeuralLayer((Element)layerN);
+        m_layers[i] = nL;
+      }
+    }
+    
+    // get the outputs
+    NodeList outputL = model.getElementsByTagName("NeuralOutputs");
+    if (outputL.getLength() != 1) {
+      throw new Exception("[NeuralNetwork] Should be just one NeuralOutputs element defined!");
+    }
+    
+    m_outputs = new NeuralOutputs((Element)outputL.item(0), m_miningSchema);
+  }
+
+  /* (non-Javadoc)
+   * @see weka.core.RevisionHandler#getRevision()
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**                                                                                                             
+   * Classifies the given test instance. The instance has to belong to a                                          
+   * dataset when it's being classified.                                                          
+   *                                                                                                              
+   * @param inst the instance to be classified                                                                
+   * @return the predicted most likely class for the instance or                                                  
+   * Utils.missingValue() if no prediction is made                                                             
+   * @exception Exception if an error occurred during the prediction                                              
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    if (!m_initialized) {
+      mapToMiningSchema(inst.dataset());
+    }
+    double[] preds = null;
+    
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+      preds = new double[1];
+    } else {
+      preds = new double[m_miningSchema.getFieldsAsInstances().classAttribute().numValues()];
+    }
+    
+    double[] incoming = m_fieldsMap.instanceToSchema(inst, m_miningSchema);
+    
+    boolean hasMissing = false;
+    for (int i = 0; i < incoming.length; i++) {
+      if (i != m_miningSchema.getFieldsAsInstances().classIndex() && 
+          Double.isNaN(incoming[i])) {
+        hasMissing = true;
+        //System.err.println("Missing value for att : " + m_miningSchema.getFieldsAsInstances().attribute(i).name());
+        break;
+      }
+    }
+    
+    if (hasMissing) {
+      if (!m_miningSchema.hasTargetMetaData()) {
+        String message = "[NeuralNetwork] WARNING: Instance to predict has missing value(s) but "
+          + "there is no missing value handling meta data and no "
+          + "prior probabilities/default value to fall back to. No "
+          + "prediction will be made (" 
+          + ((m_miningSchema.getFieldsAsInstances().classAttribute().isNominal()
+              || m_miningSchema.getFieldsAsInstances().classAttribute().isString())
+              ? "zero probabilities output)."
+              : "NaN output).");
+        if (m_log == null) {
+          System.err.println(message);
+        } else {
+          m_log.logMessage(message);
+        }
+        
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = Utils.missingValue();
+        }
+        return preds;
+      } else {
+        // use prior probablilities/default value
+        TargetMetaInfo targetData = m_miningSchema.getTargetMetaData();
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = targetData.getDefaultValue();
+        } else {
+          Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+          for (int i = 0; i < miningSchemaI.classAttribute().numValues(); i++) {
+            preds[i] = targetData.getPriorProbability(miningSchemaI.classAttribute().value(i));
+          }
+        }
+        return preds;
+      }
+    } else {
+      
+      // construct the input to the network for this instance
+      m_inputMap.clear();
+      for (int i = 0; i < m_inputs.length; i++) {
+        double networkInVal = m_inputs[i].getValue(incoming);
+        String ID = m_inputs[i].getID();
+        m_inputMap.put(ID, networkInVal);
+      }
+      
+      // now compute the output of each layer
+      HashMap<String, Double> layerOut = m_layers[0].computeOutput(m_inputMap);
+      for (int i = 1; i < m_layers.length; i++) {
+        layerOut = m_layers[i].computeOutput(layerOut);
+      }
+      
+      // now do the output
+      m_outputs.getOuput(layerOut, preds);
+    }
+    
+    return preds;
+  }
+
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    
+    temp.append("PMML version " + getPMMLVersion());
+    if (!getCreatorApplication().equals("?")) {
+      temp.append("\nApplication: " + getCreatorApplication());
+    }
+    temp.append("\nPMML Model: Neural network");
+    temp.append("\n\n");
+    temp.append(m_miningSchema);
+    
+    temp.append("Inputs:\n");
+    for (int i = 0; i < m_inputs.length; i++) {
+      temp.append(m_inputs[i] + "\n");
+    }
+
+    for (int i = 0; i < m_layers.length; i++) {
+      temp.append("Layer: " + (i+1) + "\n");
+      temp.append(m_layers[i] + "\n");
+    }
+    
+    temp.append("Outputs:\n");
+    temp.append(m_outputs);
+    
+    return temp.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/PMMLClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/PMMLClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/PMMLClassifier.java	(revision 29)
@@ -0,0 +1,233 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PMMLClassifier.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instances;
+import weka.core.pmml.*;
+import weka.gui.Logger;
+
+/**
+ * Abstract base class for all PMML classifiers.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5928 $
+ */
+public abstract class PMMLClassifier extends AbstractClassifier
+  implements Serializable, PMMLModel {
+  
+  /** For serialization */
+  private static final long serialVersionUID = -5371600590320702971L;
+
+  /** PMML version */
+  protected String m_pmmlVersion = "?";
+  
+  /** Creator application */
+  protected String m_creatorApplication = "?";
+  
+  /** Logger */
+  protected Logger m_log = null;
+
+  /** The data dictionary */
+  protected Instances m_dataDictionary;
+
+  /** The fields and meta data used by the model */
+  protected MiningSchema m_miningSchema;
+
+  /** The mapping between mining schema fields and incoming instance
+      attributes */
+  protected transient MappingInfo m_fieldsMap;
+
+  /** Has the classifier been initialized (i.e. have we established
+      a mapping between the mining schema and the incoming instances)? */
+  protected transient boolean m_initialized = false;
+
+  /**
+   * Constructor.
+   *
+   * @param dataDictionary the data dictionary
+   * @param miningSchema the mining schema
+   */
+   PMMLClassifier(Instances dataDictionary,
+                        MiningSchema miningSchema) {
+    m_dataDictionary = dataDictionary;
+    m_miningSchema = miningSchema;
+  }
+
+  /**
+   * Set the version of PMML used for this model.
+   *
+   * @param doc the Document encapsulating the pmml
+   */
+  public void setPMMLVersion(Document doc) {
+    NodeList tempL = doc.getElementsByTagName("PMML");
+    Node pmml = tempL.item(0);
+    if (pmml.getNodeType() == Node.ELEMENT_NODE) {
+      String version = ((Element)pmml).getAttribute("version");
+      if (version.length() > 0) {
+        m_pmmlVersion = version;
+      }
+    }
+  }
+  
+  /**
+   * Set the name of the application (if specified) that created this
+   * model
+   * 
+   * @param doc the Document encapsulating the pmml
+   */
+  public void setCreatorApplication(Document doc) {
+    NodeList tempL = doc.getElementsByTagName("Header");
+    Node header = tempL.item(0);
+    if (header.getNodeType() == Node.ELEMENT_NODE) {
+      NodeList appL = ((Element)header).getElementsByTagName("Application");
+      if (appL.getLength() > 0) {
+        Node app = appL.item(0);
+        if (app.getNodeType() == Node.ELEMENT_NODE) {
+          String appName = ((Element)app).getAttribute("name");
+          if (appName != null && appName.length() > 0) {
+            String version = ((Element)app).getAttribute("version");
+            if (version != null && version.length() > 0) {
+              appName += " v. " + version;
+            }
+            m_creatorApplication = appName;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the data dictionary.
+   *
+   * @return the data dictionary
+   */
+  public Instances getDataDictionary() {
+    return m_dataDictionary;
+  }
+
+  /**
+   * Get the mining schema for this model.
+   *
+   * @return the mining schema
+   */
+  public MiningSchema getMiningSchema() {
+    return m_miningSchema;
+  }
+
+  /**
+   * Get the PMML version used for this model.
+   *
+   * @return the PMML version
+   */
+  public String getPMMLVersion() {
+    return m_pmmlVersion;
+  }
+  
+  /**
+   * Get the name of the application that created this model
+   * 
+   * @return the name of the creating application or null
+   * if not specified in the pmml.
+   */
+  public String getCreatorApplication() {
+    return m_creatorApplication;
+  }
+  
+  /**
+   * Set a logger to use.
+   * 
+   * @param log the logger to use
+   */
+  public void setLog(Logger log) {
+    m_log = log;
+  }
+  
+  /**
+   * Get the logger.
+   * 
+   * @return the logger (or null if none is being used)
+   */
+  public Logger getLog() {
+    return m_log;
+  }
+
+  /**
+   * Throw an exception - PMML models are pre-built.
+   *
+   * @param data the Instances to learn from
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    throw new Exception("[PMMLClassifier] PMML models are pre-built "
+                        + "and static!");
+  }
+  
+  /**
+   * Signal that a scoring run has been completed. Resets
+   * the initialized state to false so that a subsequent
+   * scoring run will trigger the mapping of the mining
+   * schema to incoming instances. If not called after a
+   * scoring run, then the classifier will assume that
+   * the current mapping is still valid.
+   */
+  public void done() {
+    m_initialized = false;
+    m_fieldsMap = null;
+  }
+
+  /**
+   * Map mining schema to incoming instances.
+   *
+   * @param dataSet the structure of the incoming Instances
+   * @throws Exception if something goes wrong
+   */
+  public void mapToMiningSchema(Instances dataSet) throws Exception {
+    if (m_fieldsMap == null) {
+      // PMMLUtils.mapToMiningSchema(dataSet, m_miningSchema);
+      m_fieldsMap = new MappingInfo(dataSet, m_miningSchema, m_log);
+      m_initialized = true;
+    }
+  }
+  
+  /**
+   * Get a textual description of the mapping between mining schema
+   * fields and incoming data fields.
+   * 
+   * @return a description of the fields mapping as a String or null if
+   * no mapping has been constructed yet.
+   */
+  public String getFieldsMappingString() {
+    if (!m_initialized) {
+      return null;
+    }
+    return m_fieldsMap.getFieldsMappingString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/Regression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/Regression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/Regression.java	(revision 29)
@@ -0,0 +1,840 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Regression.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.pmml.*;
+
+/**
+ * Class implementing import of PMML Regression model. Can be
+ * used as a Weka classifier for prediction (buildClassifier()
+ * raises an Exception).
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 6018 $
+ */
+public class Regression extends PMMLClassifier
+  implements Serializable {
+
+  /** For serialization */
+  private static final long serialVersionUID = -5551125528409488634L;
+
+  /**
+   * Inner class for encapsulating a regression table
+   */
+  static class RegressionTable implements Serializable {
+
+    /** For serialization */
+    private static final long serialVersionUID = -5259866093996338995L;
+
+    /**
+     * Abstract inner base class for different predictor types.
+     */
+    abstract static class Predictor implements Serializable {
+
+      /** For serialization */
+      private static final long serialVersionUID = 7043831847273383618L;
+      
+      /** Name of this predictor */
+      protected String m_name;
+      
+      /** 
+       * Index of the attribute in the mining schema that corresponds to this
+       * predictor
+       */
+      protected int m_miningSchemaAttIndex = -1;
+      
+      /** Coefficient for this predictor */
+      protected double m_coefficient = 1.0;
+      
+      /**
+       * Constructs a new Predictor.
+       * 
+       * @param predictor the <code>Element</code> encapsulating this predictor
+       * @param miningSchema the mining schema as an Instances object
+       * @throws Exception if there is a problem constructing this Predictor
+       */
+      protected Predictor(Element predictor, Instances miningSchema) throws Exception {
+        m_name = predictor.getAttribute("name");
+        for (int i = 0; i < miningSchema.numAttributes(); i++) {
+          Attribute temp = miningSchema.attribute(i);
+          if (temp.name().equals(m_name)) {
+            m_miningSchemaAttIndex = i;
+          }
+        }
+        
+        if (m_miningSchemaAttIndex == -1) {
+          throw new Exception("[Predictor] unable to find matching attribute for "
+                              + "predictor " + m_name);
+        }
+
+        String coeff = predictor.getAttribute("coefficient");
+        if (coeff.length() > 0) {
+          m_coefficient = Double.parseDouble(coeff);
+        }
+      }
+
+      /**
+       * Returns a textual description of this predictor applicable
+       * to all sub classes.
+       */
+      public String toString() {
+        return Utils.doubleToString(m_coefficient, 12, 4) + " * ";
+      }
+
+      /**
+       * Abstract add method. Adds this predictor into the sum for the
+       * current prediction.
+       * 
+       * @param preds the prediction computed so far. For regression, it is a
+       * single element array; for classification it is a multi-element array
+       * @param input the input instance's values
+       */
+      public abstract void add(double[] preds, double[] input);
+    }
+
+    /**
+     * Inner class for a numeric predictor
+     */
+    protected class NumericPredictor extends Predictor {
+      /**
+       * For serialization
+       */
+      private static final long serialVersionUID = -4335075205696648273L;
+      
+      /** The exponent*/
+      protected double m_exponent = 1.0;
+
+      /**
+       * Constructs a NumericPredictor.
+       * 
+       * @param predictor the <code>Element</code> holding the predictor
+       * @param miningSchema the mining schema as an Instances object
+       * @throws Exception if something goes wrong while constructing this
+       * predictor
+       */
+      protected NumericPredictor(Element predictor, 
+                              Instances miningSchema) throws Exception {
+        super(predictor, miningSchema);
+        
+        String exponent = predictor.getAttribute("exponent");
+        if (exponent.length() > 0) {
+          m_exponent = Double.parseDouble(exponent);
+        }
+      }
+
+      /**
+       * Return a textual description of this predictor.
+       */
+      public String toString() {
+        String output = super.toString();
+        output += m_name;
+        if (m_exponent > 1.0 || m_exponent < 1.0) {
+          output += "^" + Utils.doubleToString(m_exponent, 4);
+        }
+        return output;
+      }
+
+      /**
+       * Adds this predictor into the sum for the
+       * current prediction.
+       * 
+       * @param preds the prediction computed so far. For regression, it is a
+       * single element array; for classification it is a multi-element array
+       * @param input the input instance's values
+       */
+      public void add(double[] preds, double[] input) {
+        if (m_targetCategory == -1) {
+          preds[0] += m_coefficient * Math.pow(input[m_miningSchemaAttIndex], m_exponent);
+        } else {
+          preds[m_targetCategory] += 
+            m_coefficient * Math.pow(input[m_miningSchemaAttIndex], m_exponent);
+        }
+      }
+    }
+
+    /**
+     * Inner class encapsulating a categorical predictor.
+     */
+    protected class CategoricalPredictor extends Predictor {
+      
+      /**For serialization */
+      private static final long serialVersionUID = 3077920125549906819L;
+      
+      /** The attribute value for this predictor */
+      protected String m_valueName;
+      
+      /** The index of the attribute value for this predictor */
+      protected int m_valueIndex = -1;
+
+      /**
+       * Constructs a CategoricalPredictor.
+       * 
+       * @param predictor the <code>Element</code> containing the predictor
+       * @param miningSchema the mining schema as an Instances object
+       * @throws Exception if something goes wrong while constructing
+       * this predictor
+       */
+      protected CategoricalPredictor(Element predictor,
+                                  Instances miningSchema) throws Exception {
+        super(predictor, miningSchema);
+        
+        String valName = predictor.getAttribute("value");
+        if (valName.length() == 0) {
+          throw new Exception("[CategoricalPredictor] attribute value not specified!");
+        }
+        
+        m_valueName = valName;
+
+        Attribute att = miningSchema.attribute(m_miningSchemaAttIndex);
+        if (att.isString()) {
+          // means that there were no Value elements defined in the 
+          // data dictionary (and hence the mining schema).
+          // We add our value here.
+          att.addStringValue(m_valueName);
+        }
+        m_valueIndex = att.indexOfValue(m_valueName);
+        /*        for (int i = 0; i < att.numValues(); i++) {
+          if (att.value(i).equals(m_valueName)) {
+            m_valueIndex = i;
+          }
+          }*/
+
+        if (m_valueIndex == -1) {
+          throw new Exception("[CategoricalPredictor] unable to find value "
+                              + m_valueName + " in mining schema attribute "
+                              + att.name());
+        }
+      }
+
+      /**
+       * Return a textual description of this predictor.
+       */
+      public String toString() {
+        String output = super.toString();
+        output += m_name + "=" + m_valueName;
+        return output;
+      }
+
+      /**
+       * Adds this predictor into the sum for the
+       * current prediction.
+       * 
+       * @param preds the prediction computed so far. For regression, it is a
+       * single element array; for classification it is a multi-element array
+       * @param input the input instance's values
+       */
+      public void add(double[] preds, double[] input) {
+        
+        // if the value is equal to the one in the input then add the coefficient
+        if (m_valueIndex == (int)input[m_miningSchemaAttIndex]) {
+          if (m_targetCategory == -1) {
+            preds[0] += m_coefficient;
+          } else {
+            preds[m_targetCategory] += m_coefficient;
+          }
+        }
+      }
+    }
+
+    /**
+     * Inner class to handle PredictorTerms.
+     */
+    protected class PredictorTerm implements Serializable {
+
+      /** For serialization */
+      private static final long serialVersionUID = 5493100145890252757L;
+
+      /** The coefficient for this predictor term */
+      protected double m_coefficient = 1.0;
+
+      /** the indexes of the terms to be multiplied */
+      protected int[] m_indexes;
+
+      /** The names of the terms (attributes) to be multiplied */
+      protected String[] m_fieldNames;
+
+      /**
+       * Construct a new PredictorTerm.
+       * 
+       * @param predictorTerm the <code>Element</code> describing the predictor term
+       * @param miningSchema the mining schema as an Instances object
+       * @throws Exception if something goes wrong while constructing this
+       * predictor term
+       */
+      protected PredictorTerm(Element predictorTerm, 
+                              Instances miningSchema) throws Exception {
+
+        String coeff = predictorTerm.getAttribute("coefficient");
+        if (coeff != null && coeff.length() > 0) {
+          try {
+            m_coefficient = Double.parseDouble(coeff);
+          } catch (IllegalArgumentException ex) {
+            throw new Exception("[PredictorTerm] unable to parse coefficient");
+          }
+        }
+        
+        NodeList fields = predictorTerm.getElementsByTagName("FieldRef");
+        if (fields.getLength() > 0) {
+          m_indexes = new int[fields.getLength()];
+          m_fieldNames = new String[fields.getLength()];
+
+          for (int i = 0; i < fields.getLength(); i++) {
+            Node fieldRef = fields.item(i);
+            if (fieldRef.getNodeType() == Node.ELEMENT_NODE) {
+              String fieldName = ((Element)fieldRef).getAttribute("field");
+              if (fieldName != null && fieldName.length() > 0) {
+                boolean found = false;
+                // look for this field in the mining schema
+                for (int j = 0; j < miningSchema.numAttributes(); j++) {
+                  if (miningSchema.attribute(j).name().equals(fieldName)) {
+                    
+                    // all referenced fields MUST be numeric
+                    if (!miningSchema.attribute(j).isNumeric()) {
+                      throw new Exception("[PredictorTerm] field is not continuous: "
+                                          + fieldName);
+                    }
+                    found = true;
+                    m_indexes[i] = j;
+                    m_fieldNames[i] = fieldName;
+                    break;
+                  }
+                }
+                if (!found) {
+                  throw new Exception("[PredictorTerm] Unable to find field "
+                                      + fieldName + " in mining schema!");
+                }
+              }
+            }
+          }
+        }
+      }
+
+      /**
+       * Return a textual description of this predictor term.
+       */
+      public String toString() {
+        StringBuffer result = new StringBuffer();
+        result.append("(" + Utils.doubleToString(m_coefficient, 12, 4));
+        for (int i = 0; i < m_fieldNames.length; i++) {
+          result.append(" * " + m_fieldNames[i]);
+        }
+        result.append(")");
+        return result.toString();
+      }
+
+      /**
+       * Adds this predictor term into the sum for the
+       * current prediction.
+       * 
+       * @param preds the prediction computed so far. For regression, it is a
+       * single element array; for classification it is a multi-element array
+       * @param input the input instance's values
+       */
+      public void add(double[] preds, double[] input) {
+        int indx = 0;
+        if (m_targetCategory != -1) {
+          indx = m_targetCategory;
+        }
+
+        double result = m_coefficient;
+        for (int i = 0; i < m_indexes.length; i++) {
+          result *= input[m_indexes[i]];
+        }
+        preds[indx] += result;
+      }
+    }
+    
+    /** Constant for regression model type */
+    public static final int REGRESSION = 0;
+    
+    /** Constant for classification model type */
+    public static final int CLASSIFICATION = 1;
+
+    /** The type of function - regression or classification */
+    protected int m_functionType = REGRESSION;
+    
+    /** The mining schema */
+    protected MiningSchema m_miningSchema;
+        
+    /** The intercept */
+    protected double m_intercept = 0.0;
+    
+    /** classification only */
+    protected int m_targetCategory = -1;
+
+    /** Numeric and categorical predictors */
+    protected ArrayList<Predictor> m_predictors = 
+      new ArrayList<Predictor>();
+
+    /** Interaction terms */
+    protected ArrayList<PredictorTerm> m_predictorTerms =
+      new ArrayList<PredictorTerm>();
+
+    /**
+     * Return a textual description of this RegressionTable.
+     */
+    public String toString() {
+      Instances miningSchema = m_miningSchema.getFieldsAsInstances();
+      StringBuffer temp = new StringBuffer();
+      temp.append("Regression table:\n");
+      temp.append(miningSchema.classAttribute().name());
+      if (m_functionType == CLASSIFICATION) {
+        temp.append("=" + miningSchema.
+                    classAttribute().value(m_targetCategory));
+      }
+
+      temp.append(" =\n\n");
+      
+      // do the predictors
+      for (int i = 0; i < m_predictors.size(); i++) {
+        temp.append(m_predictors.get(i).toString() + " +\n");
+      }
+      
+      // do the predictor terms
+      for (int i = 0; i < m_predictorTerms.size(); i++) {
+        temp.append(m_predictorTerms.get(i).toString() + " +\n");
+      }
+
+      temp.append(Utils.doubleToString(m_intercept, 12, 4));
+      temp.append("\n\n");
+
+      return temp.toString();
+    }
+
+    /**
+     * Construct a regression table from an <code>Element</code>
+     *
+     * @param table the table to encapsulate
+     * @param functionType the type of function 
+     * (regression or classification)
+     * to use
+     * @param mSchema the mining schema
+     * @throws Exception if there is a problem while constructing
+     * this regression table
+     */
+    protected RegressionTable(Element table, 
+                           int functionType,
+                           MiningSchema mSchema) throws Exception {
+
+      m_miningSchema = mSchema;
+      m_functionType = functionType;
+
+      Instances miningSchema = m_miningSchema.getFieldsAsInstances();
+
+      // get the intercept
+      String intercept = table.getAttribute("intercept");
+      if (intercept.length() > 0) {
+        m_intercept = Double.parseDouble(intercept);
+      }
+
+      // get the target category (if classification)
+      if (m_functionType == CLASSIFICATION) {
+        // target category MUST be defined
+        String targetCat = table.getAttribute("targetCategory");
+        if (targetCat.length() > 0) {
+          Attribute classA = miningSchema.classAttribute();
+          for (int i = 0; i < classA.numValues(); i++) {
+            if (classA.value(i).equals(targetCat)) {
+              m_targetCategory = i;
+            }
+          }
+        } 
+        if (m_targetCategory == -1) {
+          throw new Exception("[RegressionTable] No target categories defined for classification");
+        }
+      }
+
+      // read all the numeric predictors
+      NodeList numericPs = table.getElementsByTagName("NumericPredictor");
+      for (int i = 0; i < numericPs.getLength(); i++) {
+        Node nP = numericPs.item(i);
+        if (nP.getNodeType() == Node.ELEMENT_NODE) {
+          NumericPredictor numP = new NumericPredictor((Element)nP, miningSchema);
+          m_predictors.add(numP);
+        }
+      }
+
+      // read all the categorical predictors
+      NodeList categoricalPs = table.getElementsByTagName("CategoricalPredictor");
+      for (int i = 0; i < categoricalPs.getLength(); i++) {
+        Node cP = categoricalPs.item(i);
+        if (cP.getNodeType() == Node.ELEMENT_NODE) {
+          CategoricalPredictor catP = new CategoricalPredictor((Element)cP, miningSchema);
+          m_predictors.add(catP);
+        }
+      }
+
+      // read all the PredictorTerms
+      NodeList predictorTerms = table.getElementsByTagName("PredictorTerm");
+      for (int i = 0; i < predictorTerms.getLength(); i++) {
+        Node pT = predictorTerms.item(i);
+        PredictorTerm predT = new PredictorTerm((Element)pT, miningSchema);
+        m_predictorTerms.add(predT);
+      }
+    }
+
+    public void predict(double[] preds, double[] input) {
+      if (m_targetCategory == -1) {
+        preds[0] = m_intercept;
+      } else {
+        preds[m_targetCategory] = m_intercept;
+      }
+      
+      // add the predictors
+      for (int i = 0; i < m_predictors.size(); i++) {
+        Predictor p = m_predictors.get(i);
+        p.add(preds, input);
+      }
+
+      // add the PredictorTerms
+      for (int i = 0; i < m_predictorTerms.size(); i++) {
+        PredictorTerm pt = m_predictorTerms.get(i);
+        pt.add(preds, input);
+      }
+    }
+  }
+
+  /** Description of the algorithm */
+  protected String m_algorithmName;
+
+  /** The regression tables for this regression */
+  protected RegressionTable[] m_regressionTables;
+
+  /**
+   * Enum for the normalization methods.
+   */
+  enum Normalization {
+    NONE, SIMPLEMAX, SOFTMAX, LOGIT, PROBIT, CLOGLOG,
+      EXP, LOGLOG, CAUCHIT}
+
+  /** The normalization to use */
+  protected Normalization m_normalizationMethod = Normalization.NONE;
+
+  /**
+   * Constructs a new PMML Regression.
+   * 
+   * @param model the <code>Element</code> containing the regression model
+   * @param dataDictionary the data dictionary as an Instances object
+   * @param miningSchema the mining schema
+   * @throws Exception if there is a problem constructing this Regression
+   */
+  public Regression(Element model, Instances dataDictionary,
+                    MiningSchema miningSchema) throws Exception {
+    super(dataDictionary, miningSchema);
+    
+    int functionType = RegressionTable.REGRESSION;
+
+    // determine function name first
+    String fName = model.getAttribute("functionName");
+    
+    if (fName.equals("regression")) {
+      functionType = RegressionTable.REGRESSION;
+    } else if (fName.equals("classification")) {
+      functionType = RegressionTable.CLASSIFICATION;
+    } else {
+      throw new Exception("[PMML Regression] Function name not defined in pmml!");
+    }
+
+    // do we have an algorithm name?
+    String algName = model.getAttribute("algorithmName");
+    if (algName != null && algName.length() > 0) {
+      m_algorithmName = algName;
+    }
+
+    // determine normalization method (if any)
+    m_normalizationMethod = determineNormalization(model);
+
+    setUpRegressionTables(model, functionType);
+
+    // convert any string attributes in the mining schema
+    //miningSchema.convertStringAttsToNominal();
+  }
+
+  /**
+   * Create all the RegressionTables for this model.
+   * 
+   * @param model the <code>Element</code> holding this regression model
+   * @param functionType the type of function (regression or
+   * classification)
+   * @throws Exception if there is a problem setting up the regression
+   * tables
+   */
+  private void setUpRegressionTables(Element model,
+                                     int functionType) throws Exception {
+    NodeList tableList = model.getElementsByTagName("RegressionTable");
+    
+    if (tableList.getLength() == 0) {
+      throw new Exception("[Regression] no regression tables defined!");
+    }
+
+    m_regressionTables = new RegressionTable[tableList.getLength()];
+    
+    for (int i = 0; i < tableList.getLength(); i++) {
+      Node table = tableList.item(i);
+      if (table.getNodeType() == Node.ELEMENT_NODE) {
+        RegressionTable tempRTable = 
+          new RegressionTable((Element)table, 
+                              functionType, 
+                              m_miningSchema);
+        m_regressionTables[i] = tempRTable;
+      }
+    }
+  }
+
+  /**
+   * Return the type of normalization used for this regression
+   * 
+   * @param model the <code>Element</code> holding the model
+   * @return the normalization used in this regression
+   */
+  private static Normalization determineNormalization(Element model) {
+    
+    Normalization normMethod = Normalization.NONE;
+
+    String normName = model.getAttribute("normalizationMethod");
+    if (normName.equals("simplemax")) {
+      normMethod = Normalization.SIMPLEMAX;
+    } else if (normName.equals("softmax")) {
+      normMethod = Normalization.SOFTMAX;
+    } else if (normName.equals("logit")) {
+      normMethod = Normalization.LOGIT;
+    } else if (normName.equals("probit")) {
+      normMethod = Normalization.PROBIT;
+    } else if (normName.equals("cloglog")) {
+      normMethod = Normalization.CLOGLOG;
+    } else if (normName.equals("exp")) {
+      normMethod = Normalization.EXP;
+    } else if (normName.equals("loglog")) {
+      normMethod = Normalization.LOGLOG;
+    } else if (normName.equals("cauchit")) {
+      normMethod = Normalization.CAUCHIT;
+    } 
+    return normMethod;
+  }
+
+  /**
+   * Return a textual description of this Regression model.
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    temp.append("PMML version " + getPMMLVersion());
+    if (!getCreatorApplication().equals("?")) {
+      temp.append("\nApplication: " + getCreatorApplication());
+    }
+    if (m_algorithmName != null) {
+      temp.append("\nPMML Model: " + m_algorithmName);
+    }
+    temp.append("\n\n");
+    temp.append(m_miningSchema);
+
+    for (RegressionTable table : m_regressionTables) {
+      temp.append(table);
+    }
+    
+    if (m_normalizationMethod != Normalization.NONE) {
+      temp.append("Normalization: " + m_normalizationMethod);
+    }
+    temp.append("\n");
+
+    return temp.toString();
+  }
+
+  /**                                                                                                             
+   * Classifies the given test instance. The instance has to belong to a                                          
+   * dataset when it's being classified.                                                          
+   *                                                                                                              
+   * @param inst the instance to be classified                                                                
+   * @return the predicted most likely class for the instance or                                                  
+   * Utils.missingValue() if no prediction is made                                                             
+   * @exception Exception if an error occurred during the prediction                                              
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    if (!m_initialized) {
+      mapToMiningSchema(inst.dataset());
+    }
+    double[] preds = null;
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+      preds = new double[1];
+    } else {
+      preds = new double[m_miningSchema.getFieldsAsInstances().classAttribute().numValues()];
+    }
+
+    // create an array of doubles that holds values from the incoming
+    // instance; in order of the fields in the mining schema. We will
+    // also handle missing values and outliers here.
+    //    System.err.println(inst);
+    double[] incoming = m_fieldsMap.instanceToSchema(inst, m_miningSchema);
+
+    // scan for missing values. If there are still missing values after instanceToSchema(),
+    // then missing value handling has been deferred to the PMML scheme. The specification
+    // (Regression PMML 3.2) seems to contradict itself with regards to classification and categorical
+    // variables. In one place it states that if a categorical variable is missing then 
+    // variable_name=value is 0 for any value. Further down in the document it states: "if
+    // one or more of the y_j cannot be evaluated because the value in one of the referenced
+    // fields is missing, then the following formulas (for computing p_j) do not apply. In
+    // that case the predictions are defined by the priorProbability values in the Target
+    // element".
+
+    // In this implementation we will default to information in the Target element (default
+    // value for numeric prediction and prior probabilities for classification). If there is
+    // no Target element defined, then an Exception is thrown.
+
+    boolean hasMissing = false;
+    for (int i = 0; i < incoming.length; i++) {
+      if (i != m_miningSchema.getFieldsAsInstances().classIndex() && 
+          Utils.isMissingValue(incoming[i])) {
+        hasMissing = true;
+        break;
+      }
+    }
+
+    if (hasMissing) {
+      if (!m_miningSchema.hasTargetMetaData()) {
+        String message = "[Regression] WARNING: Instance to predict has missing value(s) but "
+          + "there is no missing value handling meta data and no "
+          + "prior probabilities/default value to fall back to. No "
+          + "prediction will be made (" 
+          + ((m_miningSchema.getFieldsAsInstances().classAttribute().isNominal() ||
+              m_miningSchema.getFieldsAsInstances().classAttribute().isString())
+              ? "zero probabilities output)."
+              : "NaN output).");
+        if (m_log == null) {
+          System.err.println(message);
+        } else {
+          m_log.logMessage(message);
+        }
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = Utils.missingValue();
+        }
+        return preds;
+      } else {
+        // use prior probablilities/default value
+        TargetMetaInfo targetData = m_miningSchema.getTargetMetaData();
+        if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+          preds[0] = targetData.getDefaultValue();
+        } else {
+          Instances miningSchemaI = m_miningSchema.getFieldsAsInstances();
+          for (int i = 0; i < miningSchemaI.classAttribute().numValues(); i++) {
+            preds[i] = targetData.getPriorProbability(miningSchemaI.classAttribute().value(i));
+          }
+        }
+        return preds;
+      }
+    } else {
+      // loop through the RegressionTables
+      for (int i = 0; i < m_regressionTables.length; i++) {
+        m_regressionTables[i].predict(preds, incoming);
+      }
+ 
+      // Now apply the normalization
+      switch (m_normalizationMethod) {
+      case NONE:
+        // nothing to be done
+        break;
+      case SIMPLEMAX:
+        Utils.normalize(preds);
+        break;
+      case SOFTMAX:
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = Math.exp(preds[i]);
+        }
+        if (preds.length == 1) {
+          // hack for those models that do binary logistic regression as
+          // a numeric prediction model
+          preds[0] = preds[0] / (preds[0] + 1.0);
+        } else {
+          Utils.normalize(preds);
+        }
+        break;
+      case LOGIT:
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = 1.0 / (1.0 + Math.exp(-preds[i]));
+        }
+        Utils.normalize(preds);
+        break;
+      case PROBIT:
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = weka.core.matrix.Maths.pnorm(preds[i]);
+        }
+        Utils.normalize(preds);
+        break;
+      case CLOGLOG:
+        // note this is supposed to be illegal for regression
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = 1.0 - Math.exp(-Math.exp(-preds[i]));
+        }
+        Utils.normalize(preds);
+        break;
+      case EXP:
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = Math.exp(preds[i]);
+        }
+        Utils.normalize(preds);
+        break;
+      case LOGLOG:
+        // note this is supposed to be illegal for regression
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = Math.exp(-Math.exp(-preds[i]));
+        }
+        Utils.normalize(preds);
+        break;
+      case CAUCHIT:
+        for (int i = 0; i < preds.length; i++) {
+          preds[i] = 0.5 + (1.0 / Math.PI) * Math.atan(preds[i]);
+        }
+        Utils.normalize(preds);
+        break;
+      default:
+          throw new Exception("[Regression] unknown normalization method");
+      }
+
+      // If there is a Target defined, and this is a numeric prediction problem,
+      // then apply any min, max, rescaling etc.
+      if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()
+          && m_miningSchema.hasTargetMetaData()) {
+        TargetMetaInfo targetData = m_miningSchema.getTargetMetaData();
+        preds[0] = targetData.applyMinMaxRescaleCast(preds[0]);
+      }
+    }
+    
+    return preds;
+  }
+
+  /* (non-Javadoc)
+   * @see weka.core.RevisionHandler#getRevision()
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6018 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/RuleSetModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/RuleSetModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/RuleSetModel.java	(revision 29)
@@ -0,0 +1,824 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RuleSetModel.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.classifiers.pmml.consumer.TreeModel.MiningFunction;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.pmml.MiningSchema;
+
+/**
+ * Class implementing import of PMML RuleSetModel. Can be used as a Weka
+ * classifier for prediction only (buildClassifier() raises an Exception).
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class RuleSetModel extends PMMLClassifier {
+  
+  /** For serialization */
+  private static final long serialVersionUID = 1993161168811020547L;
+
+  /**
+   * Abstract inner base class for Rules
+   */
+  static abstract class Rule implements Serializable {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 6236231263477446102L;
+    
+    /** The predicate for this rule */
+    protected TreeModel.Predicate m_predicate;
+    
+    public Rule(Element ruleE, MiningSchema miningSchema) throws Exception {
+      // Set up the predicate
+      m_predicate = TreeModel.Predicate.getPredicate(ruleE, miningSchema);
+    }
+    
+    /**
+     * Collect the rule(s) that fire for the supplied incoming instance
+     * 
+     * @param input a vector of independent and derived independent variables
+     * @param ruleCollection the array list to add any firing rules into
+     */
+    public abstract void fires(double[] input, ArrayList<SimpleRule> ruleCollection);
+    
+    /**
+     * Get a textual description of this Rule
+     * 
+     * @param prefix prefix string (typically some number of spaces) to prepend
+     * @param indent the number of additional spaces to add to the prefix
+     * @return a description of this Rule as a String
+     */
+    public abstract String toString(String prefix, int indent);
+    
+  }
+  
+  /**
+   * Inner class for representing simple rules
+   */
+  static class SimpleRule extends Rule {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -2612893679476049682L;
+
+    /** The ID for the rule (optional) */
+    protected String m_ID;
+    
+    /** The predicted value when the rule fires (required) */
+    protected String m_scoreString;
+    
+    /** 
+     * The predicted value as a number (regression) or index (classification)
+     * when the rule fires (required)
+     */
+    protected double m_score = Utils.missingValue();
+    
+    /** The number of training/test instances on which the rule fired (optional) */
+    protected double m_recordCount = Utils.missingValue();
+    
+    /** 
+     * The number of training/test instances on which the rule fired and the
+     * prediction was correct (optional)
+     */
+    protected double m_nbCorrect = Utils.missingValue();
+    
+    /** The confidence of the rule (optional) */
+    protected double m_confidence = Utils.missingValue();
+    
+    /** The score distributions for this rule (if any) */
+    protected ArrayList<TreeModel.ScoreDistribution> m_scoreDistributions = 
+      new ArrayList<TreeModel.ScoreDistribution>();
+    
+    /**
+     *  The relative importance of the rule. May or may not be equal to the
+     * confidence (optional).
+     */
+    protected double m_weight = Utils.missingValue();
+    
+    public String toString(String prefix, int indent) {
+      StringBuffer temp = new StringBuffer();
+      
+      for (int i = 0; i < indent; i++) {
+        prefix += " ";
+      }
+      
+      temp.append(prefix + "Simple rule: " + m_predicate + "\n");
+      temp.append(prefix + " => " + m_scoreString + "\n");
+      if (!Utils.isMissingValue(m_recordCount)) {
+        temp.append(prefix + " recordCount: " + m_recordCount + "\n");
+      }
+      if (!Utils.isMissingValue(m_nbCorrect)) {
+        temp.append(prefix + "   nbCorrect: " + m_nbCorrect + "\n");
+      }
+      if (!Utils.isMissingValue(m_confidence)) {
+        temp.append(prefix + "  confidence: " + m_confidence + "\n");
+      }
+      if (!Utils.isMissingValue(m_weight)) {
+        temp.append(prefix + "      weight: " + m_weight + "\n");
+      }
+      
+      return temp.toString();
+    }
+    
+    public String toString() {
+      return toString("", 0);
+    }
+        
+    /**
+     * Constructor for a simple rule
+     * 
+     * @param ruleE the XML element holding the simple rule
+     * @param miningSchema the mining schema to use
+     * @throws Exception if something goes wrong
+     */
+    public SimpleRule(Element ruleE, MiningSchema miningSchema) throws Exception {
+      super(ruleE, miningSchema);
+      
+      String id = ruleE.getAttribute("id");
+      if (id != null && id.length() > 0) {
+        m_ID = id;
+      }
+      
+      m_scoreString = ruleE.getAttribute("score");
+      Attribute classAtt = miningSchema.getFieldsAsInstances().classAttribute(); 
+      if (classAtt.isNumeric()) {
+        m_score = Double.parseDouble(m_scoreString);
+      } else {
+        if (classAtt.indexOfValue(m_scoreString) < 0) {
+          throw new Exception("[SimpleRule] class value " + m_scoreString + 
+              "does not exist in class attribute " + classAtt.name());
+        }
+        m_score = classAtt.indexOfValue(m_scoreString);
+      }
+      
+      String recordCount = ruleE.getAttribute("recordCount");
+      if (recordCount != null && recordCount.length() > 0) {
+        m_recordCount = Double.parseDouble(recordCount);
+      }
+      
+      String nbCorrect = ruleE.getAttribute("nbCorrect");
+      if (nbCorrect != null && nbCorrect.length() > 0) {
+        m_nbCorrect = Double.parseDouble(nbCorrect);
+      }
+      
+      String confidence = ruleE.getAttribute("confidence");
+      if (confidence != null && confidence.length() > 0) {
+        m_confidence = Double.parseDouble(confidence);
+      }
+      
+      String weight = ruleE.getAttribute("weight");
+      if (weight != null && weight.length() > 0) {
+        m_weight = Double.parseDouble(weight);
+      }
+      
+      // get the ScoreDistributions (if any)
+      if (miningSchema.getFieldsAsInstances().classAttribute().isNominal()) {
+        // see if we have any ScoreDistribution entries
+        NodeList scoreChildren = ruleE.getChildNodes();
+                
+        for (int i = 0; i < scoreChildren.getLength(); i++) {
+          Node child = scoreChildren.item(i);
+          if (child.getNodeType() == Node.ELEMENT_NODE) {
+            String tagName = ((Element)child).getTagName();
+            if (tagName.equals("ScoreDistribution")) {
+              TreeModel.ScoreDistribution newDist = 
+                new TreeModel.ScoreDistribution((Element)child, 
+                  miningSchema, m_recordCount);
+              m_scoreDistributions.add(newDist);
+            }
+          }
+        }
+        
+        // check that we have as many score distribution elements as there
+        // are class labels in the data
+        if (m_scoreDistributions.size() > 0 && 
+            m_scoreDistributions.size() != 
+              miningSchema.getFieldsAsInstances().classAttribute().numValues()) {
+          throw new Exception("[SimpleRule] Number of score distribution elements is "
+              + " different than the number of class labels!");
+        }
+        
+        //backfit the confidence values (if necessary)
+        if (Utils.isMissingValue(m_recordCount)) {
+          double baseCount = 0;
+          for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+            baseCount += s.getRecordCount();
+          }
+          
+          for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+            s.deriveConfidenceValue(baseCount);
+          }
+        }
+      }
+    }
+    
+    /**
+     * Collect the rule(s) that fire for the supplied incoming instance
+     * 
+     * @param input a vector of independent and derived independent variables
+     * @param ruleCollection the array list to add any firing rules into
+     */
+    public void fires(double[] input, ArrayList<SimpleRule> ruleCollection) {
+      if (m_predicate.evaluate(input) == TreeModel.Predicate.Eval.TRUE) {
+        ruleCollection.add(this);
+      }      
+    }
+    
+    /**
+     * Score the incoming instance
+     * 
+     * @param instance a vector containing the incoming independent and
+     * derived independent variables
+     * @param classAtt the class attribute
+     * @param rsm the rule selection method (ignored by simple rules)
+     * @return a probability distribution over the class labels or
+     * the predicted value (in element zero of the array if the class is numeric)
+     * @throws Exception if something goes wrong
+     */
+    public double[] score(double[] instance, Attribute classAtt) 
+      throws Exception {
+      
+      double[] preds;
+      if (classAtt.isNumeric()) {
+        preds = new double[1];
+        preds[0] = m_score;
+      } else {
+        preds = new double[classAtt.numValues()];
+        if (m_scoreDistributions.size() > 0) {
+          for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+            preds[s.getClassLabelIndex()] = s.getConfidence();
+          }
+        } else if (!Utils.isMissingValue(m_confidence)) {
+          preds[classAtt.indexOfValue(m_scoreString)] = m_confidence;
+        } else {
+          preds[classAtt.indexOfValue(m_scoreString)] = 1.0;
+        }
+      }      
+      
+      return preds;
+    }
+    
+    /**
+     * Get the weight of the rule
+     * 
+     * @return the weight of the rule
+     */
+    public double getWeight() {
+      return m_weight;
+    }
+    
+    /**
+     * Get the ID of the rule
+     * 
+     * @return the ID of the rule
+     */
+    public String getID() {
+      return m_ID;
+    }
+    
+    /**
+     * Get the predicted value of this rule (either a number
+     * for regression problems or an index of a class label for
+     * classification problems)
+     * 
+     * @return the predicted value of this rule
+     */
+    public double getScore() {
+      return m_score;
+    }
+  }
+  
+  /**
+   * Inner class representing a compound rule
+   */
+  static class CompoundRule extends Rule {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -2853658811459970718L;
+    
+    /** The child rules of this compound rule */
+    ArrayList<Rule> m_childRules = new ArrayList<Rule>();
+    
+    public String toString(String prefix, int indent) {
+      StringBuffer temp = new StringBuffer();
+
+      for (int i = 0; i < indent; i++) {
+        prefix += " ";
+      }
+      
+      temp.append(prefix + "Compound rule: " + m_predicate + "\n");
+      
+      for (Rule r : m_childRules) {
+        temp.append(r.toString(prefix, indent + 1));
+      }
+
+      return temp.toString();
+    }
+    
+    public String toString() {
+      return toString("", 0);
+    }
+    
+    /**
+     * Constructor.
+     * 
+     * @param ruleE XML node holding the rule
+     * @param miningSchema the mining schema to use
+     * @throws Exception if something goes wrong
+     */
+    public CompoundRule(Element ruleE, MiningSchema miningSchema) throws Exception {
+      
+      // get the Predicate
+      super(ruleE, miningSchema);
+      
+      // get the nested rules
+      NodeList ruleChildren = ruleE.getChildNodes();
+      for (int i = 0; i < ruleChildren.getLength(); i++) {
+        Node child = ruleChildren.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("SimpleRule")) {
+            Rule childRule = new SimpleRule(((Element)child), miningSchema);
+            m_childRules.add(childRule);
+          } else if (tagName.equals("CompoundRule")) {
+            Rule childRule = new CompoundRule(((Element)child), miningSchema);
+            m_childRules.add(childRule);
+          }
+        }
+      }
+    }
+    
+    /**
+     * Collect the rule(s) that fire for the supplied incoming instance
+     * 
+     * @param input a vector of independent and derived independent variables
+     * @param ruleCollection the array list to add any firing rules into
+     */
+    public void fires(double[] input, ArrayList<SimpleRule> ruleCollection) {
+      
+      // evaluate our predicate first
+      if (m_predicate.evaluate(input) == TreeModel.Predicate.Eval.TRUE) {
+        // now check the child rules
+        for (Rule r : m_childRules) {
+          r.fires(input, ruleCollection);
+        }
+      }      
+    }
+  }
+  
+  /**
+   * Inner class representing a set of rules 
+   */
+  static class RuleSet implements Serializable {
+    
+    /** For serialization */
+    private static final long serialVersionUID = -8718126887943074376L;
+
+    enum RuleSelectionMethod {
+      WEIGHTEDSUM("weightedSum"),
+      WEIGHTEDMAX("weightedMax"),
+      FIRSTHIT("firstHit");
+      
+      private final String m_stringVal;
+      
+      RuleSelectionMethod(String name) {
+        m_stringVal = name;
+      }
+      
+      public String toString() {
+        return m_stringVal;
+      }
+    }
+    
+    /** 
+     * The number of training/test cases to which the ruleset was
+     * applied to generate support and confidence measures for individual
+     * rules (optional)
+     */
+    private double m_recordCount = Utils.missingValue();
+    
+    /** 
+     * The number of training/test cases for which the default
+     * score is correct (optional)
+     */
+    private double m_nbCorrect = Utils.missingValue();
+    
+    /** 
+     * The default value to predict when no rule in the
+     * ruleset fires (as a String; optional) 
+     * */
+    private String m_defaultScore;
+    
+    /** 
+     * The default value to predict (either a real value or an
+     * index) 
+     * */
+    private double m_defaultPrediction = Utils.missingValue();
+    
+    /** 
+     * The default distribution to predict when no rule in the
+     * ruleset fires (nominal class only, optional)
+     */
+    private ArrayList<TreeModel.ScoreDistribution> m_scoreDistributions =
+      new ArrayList<TreeModel.ScoreDistribution>();
+    
+    /**
+     * The default confidence value to return along with a score
+     * when no rules in the set fire (optional)
+     */
+    private double m_defaultConfidence = Utils.missingValue();
+    
+    /** The active rule selection method */
+    private RuleSelectionMethod m_currentMethod;
+    
+    /** The selection of rule selection methods allowed */
+    private ArrayList<RuleSelectionMethod> m_availableRuleSelectionMethods = 
+      new ArrayList<RuleSelectionMethod>();
+    
+    /** The rules contained in the rule set */
+    private ArrayList<Rule> m_rules = new ArrayList<Rule>();
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      temp.append("Rule selection method: " + m_currentMethod + "\n");
+      if (m_defaultScore != null) {
+        temp.append("Default prediction: " + m_defaultScore + "\n");
+        
+        if (!Utils.isMissingValue(m_recordCount)) {
+          temp.append("       recordCount: " + m_recordCount + "\n");
+        }
+        if (!Utils.isMissingValue(m_nbCorrect)) {
+          temp.append("         nbCorrect: " + m_nbCorrect + "\n");
+        }
+        if (!Utils.isMissingValue(m_defaultConfidence)) {
+          temp.append(" defaultConfidence: " + m_defaultConfidence + "\n");
+        }
+        
+        temp.append("\n");
+      }
+      
+      for (Rule r : m_rules) {
+        temp.append(r + "\n");
+      }
+      
+      return temp.toString();
+    }
+    
+    /**
+     * Constructor for a RuleSet.
+     * 
+     * @param ruleSetNode the XML node holding the RuleSet
+     * @param miningSchema the mining schema to use
+     * @throws Exception if something goes wrong
+     */
+    public RuleSet(Element ruleSetNode, MiningSchema miningSchema) 
+      throws Exception {
+      
+      String recordCount = ruleSetNode.getAttribute("recordCount");
+      if (recordCount != null && recordCount.length() > 0) {
+        m_recordCount = Double.parseDouble(recordCount);
+      }
+      
+      String nbCorrect = ruleSetNode.getAttribute("nbCorrect");
+      if (nbCorrect != null & nbCorrect.length() > 0) {
+        m_nbCorrect = Double.parseDouble(nbCorrect);
+      }
+      
+      String defaultScore = ruleSetNode.getAttribute("defaultScore");
+      if (defaultScore != null && defaultScore.length() > 0) {
+        m_defaultScore = defaultScore;
+        
+        Attribute classAtt = miningSchema.getFieldsAsInstances().classAttribute();
+        if (classAtt == null) {
+          throw new Exception("[RuleSet] class attribute not set!");
+        }
+        
+        if (classAtt.isNumeric()) {
+          m_defaultPrediction = Double.parseDouble(defaultScore);
+        } else {
+          if (classAtt.indexOfValue(defaultScore) < 0) {
+            throw new Exception("[RuleSet] class value " + defaultScore + 
+                " not found!");
+          }
+          m_defaultPrediction = classAtt.indexOfValue(defaultScore);
+        }
+      }
+      
+      String defaultConfidence = ruleSetNode.getAttribute("defaultConfidence");
+      if (defaultConfidence != null && defaultConfidence.length() > 0) {
+        m_defaultConfidence = Double.parseDouble(defaultConfidence);
+      }
+      
+      // get the rule selection methods
+      NodeList selectionNL = ruleSetNode.getElementsByTagName("RuleSelectionMethod");
+      for (int i = 0; i < selectionNL.getLength(); i++) {
+        Node selectN = selectionNL.item(i);
+        if (selectN.getNodeType() == Node.ELEMENT_NODE) {
+          Element sN = (Element)selectN;
+          String criterion = sN.getAttribute("criterion");
+          for (RuleSelectionMethod m : RuleSelectionMethod.values()) {
+            if (m.toString().equals(criterion)) {
+              m_availableRuleSelectionMethods.add(m);
+              if (i == 0) {
+                // set the default (first specified one)
+                m_currentMethod = m;
+              }
+            }
+          }
+        }
+      }
+      
+      if (miningSchema.getFieldsAsInstances().classAttribute().isNominal()) {
+        // see if we have any ScoreDistribution entries
+        NodeList scoreChildren = ruleSetNode.getChildNodes();
+        for (int i = 0; i < scoreChildren.getLength(); i++) {
+          Node child = scoreChildren.item(i);
+          if (child.getNodeType() == Node.ELEMENT_NODE) {
+            String tagName = ((Element)child).getTagName();
+            if (tagName.equals("ScoreDistribution")) {
+              TreeModel.ScoreDistribution newDist = 
+                new TreeModel.ScoreDistribution((Element)child, 
+                  miningSchema, m_recordCount);
+              m_scoreDistributions.add(newDist);
+            }
+          }
+        }
+        
+        //backfit the confidence values (if necessary)
+        if (Utils.isMissingValue(m_recordCount)) {
+          double baseCount = 0;
+          for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+            baseCount += s.getRecordCount();
+          }
+          
+          for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+            s.deriveConfidenceValue(baseCount);
+          }
+        }
+      }
+      
+      // Get the rules in this rule set
+      NodeList ruleChildren = ruleSetNode.getChildNodes();
+      for (int i = 0; i < ruleChildren.getLength(); i++) {
+        Node child = ruleChildren.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("SimpleRule")) {
+            Rule tempRule = new SimpleRule(((Element)child), miningSchema);
+            m_rules.add(tempRule);
+          } else if (tagName.equals("CompoundRule")) {
+            Rule tempRule = new CompoundRule(((Element)child), miningSchema);
+            m_rules.add(tempRule);
+          }
+        }
+      }
+    }
+    
+    /**
+     * Score an incoming instance by collecting all rules that fire.
+     * 
+     * @param instance a vector of incoming attribte and derived field values
+     * @param classAtt the class attribute
+     * @return a predicted probability distribution
+     * @throws Exception is something goes wrong
+     */
+    protected double[] score(double[] instance, Attribute classAtt)
+      throws Exception {
+      
+      double[] preds = null;
+      if (classAtt.isNumeric()) {
+        preds = new double[1];
+      } else {
+        preds = new double[classAtt.numValues()];
+      }
+      
+      // holds the rules that fire for this test case
+      ArrayList<SimpleRule> firingRules = new ArrayList<SimpleRule>();
+      
+      for (Rule r : m_rules) {
+        r.fires(instance, firingRules);
+      }
+      
+      if (firingRules.size() > 0) {
+        if (m_currentMethod == RuleSelectionMethod.FIRSTHIT) {
+          preds = firingRules.get(0).score(instance, classAtt);
+        } else if (m_currentMethod == RuleSelectionMethod.WEIGHTEDMAX) {
+          double wMax = Double.NEGATIVE_INFINITY;
+          SimpleRule best = null;
+          for (SimpleRule s : firingRules) {
+            if (Utils.isMissingValue(s.getWeight())) {
+              throw new Exception("[RuleSet] Scoring criterion is WEIGHTEDMAX, but " +
+              		"rule " + s.getID() + " does not have a weight defined!");
+            }
+            if (s.getWeight() > wMax) {
+              wMax = s.getWeight();
+              best = s;
+            }
+          }
+          if (best == null) {
+            throw new Exception("[RuleSet] Unable to determine the best rule under " +
+            		"the WEIGHTEDMAX criterion!");
+          }
+          preds = best.score(instance, classAtt);          
+        } else if (m_currentMethod == RuleSelectionMethod.WEIGHTEDSUM) {
+          double sumOfWeights = 0;
+          for (SimpleRule s : firingRules) {
+            if (Utils.isMissingValue(s.getWeight())) {
+              throw new Exception("[RuleSet] Scoring criterion is WEIGHTEDSUM, but " +
+                        "rule " + s.getID() + " does not have a weight defined!");
+            }            
+            if (classAtt.isNumeric()) {
+              sumOfWeights += s.getWeight();
+              preds[0] += (s.getScore() * s.getWeight());
+            } else {
+              preds[(int)s.getScore()] += s.getWeight();
+            }
+          }
+          if (classAtt.isNumeric()) {
+            if (sumOfWeights == 0) {
+              throw new Exception("[RuleSet] Sum of weights is zero!");
+            }
+            preds[0] /= sumOfWeights;
+          } else {
+            // array gets normalized in the distributionForInstance() method
+          }
+        }
+      } else {
+        // default prediction
+        if (classAtt.isNumeric()) {
+          preds[0] = m_defaultPrediction;
+        } else {
+          if (m_scoreDistributions.size() > 0) {
+            for (TreeModel.ScoreDistribution s : m_scoreDistributions) {
+              preds[s.getClassLabelIndex()] = s.getConfidence();
+            }
+          } else if (!Utils.isMissingValue(m_defaultConfidence)) {
+            preds[(int)m_defaultPrediction] = m_defaultConfidence;
+          } else {
+            preds[(int)m_defaultPrediction] = 1.0;
+          }
+        }
+      }
+      
+      return preds;
+    }
+  }
+  
+  /** The mining function */
+  protected MiningFunction m_functionType = MiningFunction.CLASSIFICATION;
+  
+  /** The model name (if defined) */
+  protected String m_modelName;
+  
+  /** The algorithm name (if defined) */
+  protected String m_algorithmName;
+  
+  /** The set of rules */
+  protected RuleSet m_ruleSet;
+
+  /**
+   * Constructor for a RuleSetModel
+   * 
+   * @param model the XML element encapsulating the RuleSetModel
+   * @param dataDictionary the data dictionary to use
+   * @param miningSchema the mining schema to use
+   * @throws Exception if something goes wrong
+   */
+  public RuleSetModel(Element model, Instances dataDictionary,
+      MiningSchema miningSchema) throws Exception {
+    
+    super(dataDictionary, miningSchema);
+    
+    if (!getPMMLVersion().equals("3.2")) {
+      // TODO: might have to throw an exception and only support 3.2
+    }
+    
+    String fn = model.getAttribute("functionName");
+    if (fn.equals("regression")) {
+      m_functionType = MiningFunction.REGRESSION;
+    }
+    
+    String modelName = model.getAttribute("modelName");
+    if (modelName != null && modelName.length() > 0) {
+      m_modelName = modelName;
+    }
+    
+    String algoName = model.getAttribute("algorithmName");
+    if (algoName != null && algoName.length() > 0) {
+      m_algorithmName = algoName;
+    }    
+    
+    NodeList ruleset = model.getElementsByTagName("RuleSet");
+    if (ruleset.getLength() == 1) {
+      Node ruleSetNode = ruleset.item(0);
+      if (ruleSetNode.getNodeType() == Node.ELEMENT_NODE) {
+        m_ruleSet = new RuleSet((Element)ruleSetNode, miningSchema);
+      }
+    } else {
+      throw new Exception ("[RuleSetModel] Should only have a single RuleSet!");
+    }
+  }
+  
+  /**                                                                                                             
+   * Classifies the given test instance. The instance has to belong to a                                          
+   * dataset when it's being classified.                                                          
+   *                                                                                                              
+   * @param inst the instance to be classified                                                                
+   * @return the predicted most likely class for the instance or                                                  
+   * Utils.missingValue() if no prediction is made                                                             
+   * @exception Exception if an error occurred during the prediction                                              
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    if (!m_initialized) {
+      mapToMiningSchema(inst.dataset());
+    }
+    double[] preds = null;
+    
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+      preds = new double[1];
+    } else {
+      preds = new double[m_miningSchema.getFieldsAsInstances().classAttribute().numValues()];
+    }
+    
+    double[] incoming = m_fieldsMap.instanceToSchema(inst, m_miningSchema);
+    
+    preds = m_ruleSet.score(incoming, 
+        m_miningSchema.getFieldsAsInstances().classAttribute());
+    
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNominal()) {
+      Utils.normalize(preds);
+    }
+    
+    return preds;
+  }
+  
+  /**
+   * Return a textual description of this model.
+   * 
+   * @return a textual description of this model
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    
+    temp.append("PMML version " + getPMMLVersion());
+    if (!getCreatorApplication().equals("?")) {
+      temp.append("\nApplication: " + getCreatorApplication());
+    }
+    temp.append("\nPMML Model: RuleSetModel");
+    temp.append("\n\n");
+    temp.append(m_miningSchema);
+    
+    if (m_algorithmName != null) {
+      temp.append("\nAlgorithm: " + m_algorithmName + "\n");
+    }
+    
+    temp.append(m_ruleSet);
+  
+    return temp.toString();
+  }
+
+  /**
+   * Get the revision string for this class
+   * 
+   * @return the revision string
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/TreeModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/TreeModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/pmml/consumer/TreeModel.java	(revision 29)
@@ -0,0 +1,1674 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TreeModel.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.pmml.*;
+
+/**
+ * Class implementing import of PMML TreeModel. Can be used as a Weka
+ * classifier for prediction (buildClassifier() raises and Exception).
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $;
+ */
+public class TreeModel extends PMMLClassifier implements Drawable {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -2065158088298753129L;
+
+  /**
+   * Inner class representing the ScoreDistribution element
+   */
+  static class ScoreDistribution implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -123506262094299933L;
+
+    /** The class label for this distribution element */
+    private String m_classLabel;
+    
+    /** The index of the class label */
+    private int m_classLabelIndex = -1;
+    
+    /** The count for this label */
+    private double m_recordCount;
+    
+    /** The optional confidence value */
+    private double m_confidence = Utils.missingValue();
+    
+    /**
+     * Construct a ScoreDistribution entry
+     * 
+     * @param scoreE the node containing the distribution
+     * @param miningSchema the mining schema
+     * @param baseCount the number of records at the node that owns this 
+     * distribution entry
+     * @throws Exception if something goes wrong
+     */
+    protected ScoreDistribution(Element scoreE, MiningSchema miningSchema, double baseCount) 
+      throws Exception {
+      // get the label
+      m_classLabel = scoreE.getAttribute("value");
+      Attribute classAtt = miningSchema.getFieldsAsInstances().classAttribute();
+      if (classAtt == null || classAtt.indexOfValue(m_classLabel) < 0) {
+        throw new Exception("[ScoreDistribution] class attribute not set or class value " +
+            m_classLabel + " not found!");
+      }
+      
+      m_classLabelIndex = classAtt.indexOfValue(m_classLabel);
+      
+      // get the frequency
+      String recordC = scoreE.getAttribute("recordCount");
+      m_recordCount = Double.parseDouble(recordC);
+      
+      // get the optional confidence
+      String confidence = scoreE.getAttribute("confidence");
+      if (confidence != null && confidence.length() > 0) {
+        m_confidence = Double.parseDouble(confidence);        
+      } else if (!Utils.isMissingValue(baseCount) && baseCount > 0) {
+        m_confidence = m_recordCount / baseCount;
+      }
+    }
+    
+    /**
+     * Backfit confidence value (does nothing if the confidence
+     * value is already set).
+     * 
+     * @param baseCount the total number of records (supplied either
+     * explicitly from the node that owns this distribution entry
+     * or most likely computed from summing the recordCounts of all
+     * the distribution entries in the distribution that owns this
+     * entry).
+     */
+    void deriveConfidenceValue(double baseCount) {
+      if (Utils.isMissingValue(m_confidence) && 
+          !Utils.isMissingValue(baseCount) && 
+          baseCount > 0) {
+        m_confidence = m_recordCount / baseCount;
+      }
+    }
+    
+    String getClassLabel() {
+      return m_classLabel;
+    }
+    
+    int getClassLabelIndex() {
+      return m_classLabelIndex;
+    }
+    
+    double getRecordCount() {
+      return m_recordCount;
+    }
+    
+    double getConfidence() {
+      return m_confidence;
+    }
+    
+    public String toString() {
+      return m_classLabel + ": " + m_recordCount 
+        + " (" + Utils.doubleToString(m_confidence, 2) + ") ";
+    }
+  }
+  
+  /**
+   * Base class for Predicates
+   */
+  static abstract class Predicate implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = 1035344165452733887L;
+
+    enum Eval {
+      TRUE,
+      FALSE,
+      UNKNOWN;
+    }
+    
+    /**
+     * Evaluate this predicate.
+     * 
+     * @param input the input vector of attribute and derived field values.
+     * 
+     * @return the evaluation status of this predicate.
+     */
+    abstract Eval evaluate(double[] input);
+    
+    protected String toString(int level, boolean cr) {
+      return toString(level);
+    }
+    
+    protected String toString(int level) {
+      StringBuffer text = new StringBuffer();
+      for (int j = 0; j < level; j++) {
+        text.append("|   ");
+      }
+      
+      return text.append(toString()).toString();
+    }
+    
+    static Eval booleanToEval(boolean missing, boolean result) {
+      if (missing) {
+        return Eval.UNKNOWN;
+      } else if (result) {
+        return Eval.TRUE;
+      } else {
+        return Eval.FALSE;
+      }
+    }
+    
+    /**
+     * Factory method to return the appropriate predicate for
+     * a given node in the tree.
+     * 
+     * @param nodeE the XML node encapsulating the tree node.
+     * @param miningSchema the mining schema in use
+     * @return a Predicate
+     * @throws Exception of something goes wrong.
+     */
+    static Predicate getPredicate(Element nodeE, 
+        MiningSchema miningSchema) throws Exception {
+      
+      Predicate result = null;
+      NodeList children = nodeE.getChildNodes();
+      for (int i = 0; i < children.getLength(); i++) {
+        Node child = children.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("True")) {
+            result = new True();
+            break;
+          } else if (tagName.equals("False")) {
+            result = new False();
+            break;
+          } else if (tagName.equals("SimplePredicate")) {
+            result = new SimplePredicate((Element)child, miningSchema);
+            break;
+          } else if (tagName.equals("CompoundPredicate")) {
+            result = new CompoundPredicate((Element)child, miningSchema);
+            break;
+          } else if (tagName.equals("SimpleSetPredicate")) {
+           result = new SimpleSetPredicate((Element)child, miningSchema);
+           break;
+          }
+        }
+      }
+      
+      if (result == null) {
+        throw new Exception("[Predicate] unknown or missing predicate type in node");
+      }
+      
+      return result;
+    }
+  }
+  
+  /**
+   * Simple True Predicate
+   */
+  static class True extends Predicate {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = 1817942234610531627L;
+
+    public Predicate.Eval evaluate(double[] input) {
+      return Predicate.Eval.TRUE;
+    }
+    
+    public String toString() {
+      return "True: ";
+    }
+  }
+  
+  /**
+   * Simple False Predicate
+   */
+  static class False extends Predicate {
+    
+    /**
+     * For serialization 
+     */
+    private static final long serialVersionUID = -3647261386442860365L;
+
+    public Predicate.Eval evaluate(double[] input) {
+      return Predicate.Eval.FALSE;
+    }
+    
+    public String toString() {
+      return "False: ";
+    }
+  }
+  
+  /**
+   * Class representing the SimplePredicate
+   */
+  static class SimplePredicate extends Predicate {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -6156684285069327400L;
+
+    enum Operator {
+      EQUAL("equal") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]), 
+              weka.core.Utils.eq(input[fieldIndex], value));
+        }
+        
+        String shortName() {
+          return "==";
+        }
+      },
+      NOTEQUAL("notEqual")
+       {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]), 
+              (input[fieldIndex] != value));
+        }
+        
+        String shortName() {
+          return "!=";
+        }
+      },
+      LESSTHAN("lessThan")  {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]),
+              (input[fieldIndex] < value));
+        }
+        
+        String shortName() {
+          return "<";
+        }
+      },
+      LESSOREQUAL("lessOrEqual") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]),
+              (input[fieldIndex] <= value));
+        }
+        
+        String shortName() {
+          return "<=";
+        }
+      },
+      GREATERTHAN("greaterThan") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]),
+              (input[fieldIndex] > value));
+        }
+        
+        String shortName() {
+          return ">";
+        }
+      },
+      GREATEROREQUAL("greaterOrEqual") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]),
+              (input[fieldIndex] >= value));
+        }
+        
+        String shortName() {
+          return ">=";
+        }
+      },
+      ISMISSING("isMissing") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(false,
+              Utils.isMissingValue(input[fieldIndex]));
+        }
+        
+        String shortName() {
+          return toString();
+        }
+      },
+      ISNOTMISSING("isNotMissing") {
+        Predicate.Eval evaluate(double[] input, double value, int fieldIndex) {
+          return Predicate.booleanToEval(false, !Utils.isMissingValue(input[fieldIndex]));
+        }
+        
+        String shortName() {
+          return toString();
+        }
+      };
+      
+      abstract Predicate.Eval evaluate(double[] input, double value, int fieldIndex);
+      abstract String shortName();
+      
+      private final String m_stringVal;
+      
+      Operator(String name) {
+        m_stringVal = name;
+      }
+            
+      public String toString() {
+        return m_stringVal;
+      }
+    }
+    
+    /** the field that we are comparing against */
+    int m_fieldIndex = -1;
+    
+    /** the name of the field */
+    String m_fieldName;
+    
+    /** true if the field is nominal */
+    boolean m_isNominal;
+    
+    /** the value as a string (if nominal) */
+    String m_nominalValue;
+    
+    /** the value to compare against (if nominal it holds the index of the value) */
+    double m_value;
+    
+    /** the operator to use */
+    Operator m_operator;
+        
+    public SimplePredicate(Element simpleP, 
+        MiningSchema miningSchema) throws Exception {
+      Instances totalStructure = miningSchema.getFieldsAsInstances();
+      
+      // get the field name and set up the index
+      String fieldS = simpleP.getAttribute("field");
+      Attribute att = totalStructure.attribute(fieldS);
+      if (att == null) {
+        throw new Exception("[SimplePredicate] unable to find field " + fieldS
+            + " in the incoming instance structure!");
+      }
+      
+      // find the index
+      int index = -1;
+      for (int i = 0; i < totalStructure.numAttributes(); i++) {
+        if (totalStructure.attribute(i).name().equals(fieldS)) {
+          index = i;
+          m_fieldName = totalStructure.attribute(i).name();
+          break;
+        }
+      }
+      m_fieldIndex = index;
+      if (att.isNominal()) {
+        m_isNominal = true;
+      }
+      
+      // get the operator
+      String oppS = simpleP.getAttribute("operator");
+      for (Operator o : Operator.values()) {
+        if (o.toString().equals(oppS)) {
+          m_operator = o;
+          break;
+        }
+      }
+      
+      if (m_operator != Operator.ISMISSING && m_operator != Operator.ISNOTMISSING) {
+        String valueS = simpleP.getAttribute("value");
+        if (att.isNumeric()) {
+          m_value = Double.parseDouble(valueS);
+        } else {
+          m_nominalValue = valueS;
+          m_value = att.indexOfValue(valueS);
+          if (m_value < 0) {
+            throw new Exception("[SimplePredicate] can't find value " + valueS + " in nominal " +
+                "attribute " + att.name());
+          }
+        }
+      }
+    }
+    
+    public Predicate.Eval evaluate(double[] input) {
+      return m_operator.evaluate(input, m_value, m_fieldIndex);
+    }
+        
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      temp.append(m_fieldName + " " + m_operator.shortName());
+      if (m_operator != Operator.ISMISSING && m_operator != Operator.ISNOTMISSING) {
+        temp.append(" " + ((m_isNominal) ? m_nominalValue : "" + m_value));
+      }
+      
+      return temp.toString();
+    }
+  }
+  
+  /**
+   * Class representing the CompoundPredicate
+   */
+  static class CompoundPredicate extends Predicate {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -3332091529764559077L;
+
+    enum BooleanOperator {
+      OR("or") {
+        Predicate.Eval evaluate(ArrayList<Predicate> constituents, double[] input) {
+          Predicate.Eval currentStatus = Predicate.Eval.FALSE;
+          for (Predicate p : constituents) {
+            Predicate.Eval temp = p.evaluate(input);
+            if (temp == Predicate.Eval.TRUE) {
+              currentStatus = temp;
+              break;
+            } else if (temp == Predicate.Eval.UNKNOWN) {
+              currentStatus = temp;
+            }            
+          }
+          return currentStatus;
+        }
+      },
+      AND("and") {
+        Predicate.Eval evaluate(ArrayList<Predicate> constituents, double[] input) {
+          Predicate.Eval currentStatus = Predicate.Eval.TRUE;
+          for (Predicate p : constituents) {
+            Predicate.Eval temp = p.evaluate(input);
+            if (temp == Predicate.Eval.FALSE) {
+              currentStatus = temp;
+              break;
+            } else if (temp == Predicate.Eval.UNKNOWN) {
+              currentStatus = temp;
+            }
+          }          
+          return currentStatus;
+        }
+      },
+      XOR("xor") {
+        Predicate.Eval evaluate(ArrayList<Predicate> constituents, double[] input) {
+          Predicate.Eval currentStatus = constituents.get(0).evaluate(input);
+          if (currentStatus != Predicate.Eval.UNKNOWN) {
+            for (int i = 1; i < constituents.size(); i++) {
+              Predicate.Eval temp = constituents.get(i).evaluate(input);
+              if (temp == Predicate.Eval.UNKNOWN) {
+                currentStatus = temp;
+                break;
+              } else {
+                if (currentStatus != temp) {
+                  currentStatus = Predicate.Eval.TRUE;
+                } else {
+                  currentStatus = Predicate.Eval.FALSE;
+                }
+              }
+            }
+          }
+          return currentStatus;
+        }
+      },
+      SURROGATE("surrogate") {
+        Predicate.Eval evaluate(ArrayList<Predicate> constituents, double[] input) {
+          Predicate.Eval currentStatus = constituents.get(0).evaluate(input);
+          
+          int i = 1;
+          while (currentStatus == Predicate.Eval.UNKNOWN) {
+            currentStatus = constituents.get(i).evaluate(input);            
+          }
+          
+          // return false if all our surrogates evaluate to unknown.
+          if (currentStatus == Predicate.Eval.UNKNOWN) {
+            currentStatus = Predicate.Eval.FALSE;
+          }
+          
+          return currentStatus;
+        }
+      };
+      
+      abstract Predicate.Eval evaluate(ArrayList<Predicate> constituents, double[] input);
+      
+      private final String m_stringVal;
+      
+      BooleanOperator(String name) {
+        m_stringVal = name;
+      }
+      
+      public String toString() {
+        return m_stringVal;
+      }
+    }
+    
+    /** the constituent Predicates */
+    ArrayList<Predicate> m_components = new ArrayList<Predicate>();
+    
+    /** the boolean operator */
+    BooleanOperator m_booleanOperator;
+        
+    public CompoundPredicate(Element compoundP, 
+        MiningSchema miningSchema) throws Exception {
+//      Instances totalStructure = miningSchema.getFieldsAsInstances();
+      
+      String booleanOpp = compoundP.getAttribute("booleanOperator");
+      for (BooleanOperator b : BooleanOperator.values()) {
+        if (b.toString().equals(booleanOpp)) {
+          m_booleanOperator = b;
+        }
+      }
+      
+      // now get all the encapsulated operators
+      NodeList children = compoundP.getChildNodes();
+      for (int i = 0; i < children.getLength(); i++) {
+        Node child = children.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("True")) {
+            m_components.add(new True());
+          } else if (tagName.equals("False")) {
+            m_components.add(new False());
+          } else if (tagName.equals("SimplePredicate")) {
+            m_components.add(new SimplePredicate((Element)child, miningSchema));
+          } else if (tagName.equals("CompoundPredicate")) {
+            m_components.add(new CompoundPredicate((Element)child, miningSchema));
+          } else {
+            m_components.add(new SimpleSetPredicate((Element)child, miningSchema));
+          }
+        }
+      }
+    }
+    
+    public Predicate.Eval evaluate(double[] input) {
+      return m_booleanOperator.evaluate(m_components, input);
+    }
+    
+    public String toString() {
+      return toString(0, false);
+    }
+    
+    public String toString(int level, boolean cr) {
+      StringBuffer text = new StringBuffer();
+      for (int j = 0; j < level; j++) {
+        text.append("|   ");
+      }
+      
+      text.append("Compound [" + m_booleanOperator.toString() + "]");
+      if (cr) {
+        text.append("\\n");
+      } else {
+        text.append("\n");
+      }
+      for (int i = 0; i < m_components.size(); i++) {
+        text.append(m_components.get(i).toString(level, cr).replace(":", ""));
+        if (i != m_components.size()-1) {
+          if (cr) {
+            text.append("\\n");
+          } else {
+            text.append("\n");
+          }
+        }
+      }
+      
+      return text.toString();
+    }
+  }
+  
+  /**
+   * Class representing the SimpleSetPredicate
+   */
+  static class SimpleSetPredicate extends Predicate {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -2711995401345708486L;
+
+    enum BooleanOperator {
+        IS_IN("isIn") {
+          Predicate.Eval evaluate(double[] input, int fieldIndex, 
+              Array set, Attribute nominalLookup) {            
+            if (set.getType() == Array.ArrayType.STRING) {
+              String value = "";
+              if (!Utils.isMissingValue(input[fieldIndex])) {
+                value = nominalLookup.value((int)input[fieldIndex]);
+              }
+              return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]), 
+                  set.contains(value));
+            } else if (set.getType() == Array.ArrayType.NUM ||
+                set.getType() == Array.ArrayType.REAL) {
+              return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]), 
+                set.contains(input[fieldIndex]));
+            }
+            return Predicate.booleanToEval(Utils.isMissingValue(input[fieldIndex]), 
+                set.contains((int)input[fieldIndex]));
+          }
+        },
+        IS_NOT_IN("isNotIn") {
+          Predicate.Eval evaluate(double[] input, int fieldIndex,
+              Array set, Attribute nominalLookup) {
+            Predicate.Eval result = IS_IN.evaluate(input, fieldIndex, set, nominalLookup);
+            if (result == Predicate.Eval.FALSE) {
+              result = Predicate.Eval.TRUE;
+            } else if (result == Predicate.Eval.TRUE) {
+              result = Predicate.Eval.FALSE;
+            }
+            
+            return result;
+          }
+        };
+        
+        abstract Predicate.Eval evaluate(double[] input, int fieldIndex, 
+            Array set, Attribute nominalLookup);
+        
+        private final String m_stringVal;
+        
+        BooleanOperator(String name) {
+          m_stringVal = name;
+        }
+        
+        public String toString() {
+          return m_stringVal;
+        }
+    }
+    
+    /** the field to reference */
+    int m_fieldIndex = -1;
+    
+    /** the name of the field */
+    String m_fieldName;
+    
+    /** is the referenced field nominal? */
+    boolean m_isNominal = false;
+    
+    /** the attribute to lookup nominal values from */
+    Attribute m_nominalLookup;
+    
+    /** the boolean operator */
+    BooleanOperator m_operator = BooleanOperator.IS_IN;
+    
+    /** the array holding the set of values */
+    Array m_set;
+        
+    public SimpleSetPredicate(Element setP, 
+        MiningSchema miningSchema) throws Exception {
+      Instances totalStructure = miningSchema.getFieldsAsInstances();
+      
+      // get the field name and set up the index
+      String fieldS = setP.getAttribute("field");
+      Attribute att = totalStructure.attribute(fieldS);
+      if (att == null) {
+        throw new Exception("[SimplePredicate] unable to find field " + fieldS
+            + " in the incoming instance structure!");
+      }
+      
+      // find the index
+      int index = -1;
+      for (int i = 0; i < totalStructure.numAttributes(); i++) {
+        if (totalStructure.attribute(i).name().equals(fieldS)) {
+          index = i;
+          m_fieldName = totalStructure.attribute(i).name();
+          break;
+        }
+      }
+      m_fieldIndex = index;
+      if (att.isNominal()) {
+        m_isNominal = true;
+        m_nominalLookup = att;
+      }
+  
+      // need to scan the children looking for an array type
+      NodeList children = setP.getChildNodes();
+      for (int i = 0; i < children.getLength(); i++) {
+        Node child = children.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          if (Array.isArray((Element)child)) {
+            // found the array
+            m_set = Array.create((Element)child);
+            break;
+          }
+        }
+      }
+
+      if (m_set == null) {
+        throw new Exception("[SimpleSetPredictate] couldn't find an " +
+        "array containing the set values!");
+      }
+      
+      // check array type against field type
+      if (m_set.getType() == Array.ArrayType.STRING &&
+          !m_isNominal) {
+        throw new Exception("[SimpleSetPredicate] referenced field " +
+            totalStructure.attribute(m_fieldIndex).name() + 
+            " is numeric but array type is string!");
+      } else if (m_set.getType() != Array.ArrayType.STRING && 
+          m_isNominal) {
+        throw new Exception("[SimpleSetPredicate] referenced field " +
+            totalStructure.attribute(m_fieldIndex).name() +
+            " is nominal but array type is numeric!");
+      }      
+    }
+    
+    public Predicate.Eval evaluate(double[] input) {
+      return m_operator.evaluate(input, m_fieldIndex, m_set, m_nominalLookup);
+    }
+    
+    public String toString() {
+      StringBuffer temp = new StringBuffer();
+      
+      temp.append(m_fieldName + " " + m_operator.toString() + " ");
+      temp.append(m_set.toString());
+      
+      return temp.toString();
+    }
+  }
+  
+  /**
+   * Class for handling a Node in the tree
+   */
+  class TreeNode implements Serializable {
+    // TODO: perhaps implement a class called Statistics that contains Partitions?
+        
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = 3011062274167063699L;
+
+    /** ID for this node */
+    private String m_ID = "" + this.hashCode();
+    
+    /** The score as a string */
+    private String m_scoreString;
+    
+    /** The index of this predicted value (if class is nominal) */
+    private int m_scoreIndex = -1;
+    
+    /** The score as a number (if target is numeric) */
+    private double m_scoreNumeric = Utils.missingValue();
+    
+    /** The record count at this node (if defined) */
+    private double m_recordCount = Utils.missingValue();
+    
+    /** The ID of the default child (if applicable) */
+    private String m_defaultChildID;
+    
+    /** Holds the node of the default child (if defined) */
+    private TreeNode m_defaultChild;
+    
+    /** The distribution for labels (classification) */
+    private ArrayList<ScoreDistribution> m_scoreDistributions = 
+      new ArrayList<ScoreDistribution>();
+    
+    /** The predicate for this node */
+    private Predicate m_predicate;
+    
+    /** The children of this node */
+    private ArrayList<TreeNode> m_childNodes = new ArrayList<TreeNode>();
+    
+    
+    protected TreeNode(Element nodeE, MiningSchema miningSchema) throws Exception {
+      Attribute classAtt = miningSchema.getFieldsAsInstances().classAttribute();
+      
+      // get the ID
+      String id = nodeE.getAttribute("id");
+      if (id != null && id.length() > 0) {
+        m_ID = id;
+      }
+      
+      // get the score for this node
+      String scoreS = nodeE.getAttribute("score");
+      if (scoreS != null && scoreS.length() > 0) {
+        m_scoreString = scoreS;
+        
+        // try to parse as a number in case we 
+        // are part of a regression tree
+        if (classAtt.isNumeric()) {
+          try {
+            m_scoreNumeric = Double.parseDouble(scoreS);
+          } catch (NumberFormatException ex) {
+            throw new Exception("[TreeNode] class is numeric but unable to parse score " 
+                + m_scoreString + " as a number!");
+          }
+        } else {
+          // store the index of this class value
+          m_scoreIndex = classAtt.indexOfValue(m_scoreString);
+          
+          if (m_scoreIndex < 0) {
+            throw new Exception("[TreeNode] can't find match for predicted value " 
+                + m_scoreString + " in class attribute!");
+          }
+        }
+      }
+      
+      // get the record count if defined
+      String recordC = nodeE.getAttribute("recordCount");
+      if (recordC != null && recordC.length() > 0) {
+        m_recordCount = Double.parseDouble(recordC);
+      }
+      
+      // get the default child (if applicable)
+      String defaultC = nodeE.getAttribute("defaultChild");
+      if (defaultC != null && defaultC.length() > 0) {
+        m_defaultChildID = defaultC;
+      }
+      
+      //TODO: Embedded model (once we support model composition)
+      
+      // Now get the ScoreDistributions (if any and mining function 
+      // is classification) at this level
+      if (m_functionType == MiningFunction.CLASSIFICATION) {
+        getScoreDistributions(nodeE, miningSchema);
+      }
+      
+      // Now get the Predicate
+      m_predicate = Predicate.getPredicate(nodeE, miningSchema);
+      
+      // Now get the child Node(s)
+      getChildNodes(nodeE, miningSchema);
+      
+      // If we have a default child specified, find it now
+      if (m_defaultChildID != null) {
+        for (TreeNode t : m_childNodes) {
+          if (t.getID().equals(m_defaultChildID)) {
+            m_defaultChild = t;
+            break;
+          }
+        }
+      }
+    }
+    
+    private void getChildNodes(Element nodeE, MiningSchema miningSchema) throws Exception {
+      NodeList children = nodeE.getChildNodes();
+      
+      for (int i = 0; i < children.getLength(); i++) {
+        Node child = children.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("Node")) {
+            TreeNode tempN = new TreeNode((Element)child, miningSchema);
+            m_childNodes.add(tempN);
+          }
+        }
+      }
+    }
+    
+    private void getScoreDistributions(Element nodeE, 
+        MiningSchema miningSchema) throws Exception {
+      
+      NodeList scoreChildren = nodeE.getChildNodes();
+      for (int i = 0; i < scoreChildren.getLength(); i++) {
+        Node child = scoreChildren.item(i);
+        if (child.getNodeType() == Node.ELEMENT_NODE) {
+          String tagName = ((Element)child).getTagName();
+          if (tagName.equals("ScoreDistribution")) {
+            ScoreDistribution newDist = new ScoreDistribution((Element)child, 
+                miningSchema, m_recordCount);
+            m_scoreDistributions.add(newDist);
+          }
+        }
+      }
+      
+      // backfit the confidence values
+      if (Utils.isMissingValue(m_recordCount)) {
+        double baseCount = 0;
+        for (ScoreDistribution s : m_scoreDistributions) {
+          baseCount += s.getRecordCount();
+        }
+        
+        for (ScoreDistribution s : m_scoreDistributions) {
+          s.deriveConfidenceValue(baseCount);
+        }
+      }
+    }
+        
+    /**
+     * Get the score value as a string.
+     * 
+     * @return the score value as a String.
+     */
+    protected String getScore() {
+      return m_scoreString;
+    }
+    
+    /**
+     * Get the score value as a number (regression trees only).
+     * 
+     * @return the score as a number
+     */
+    protected double getScoreNumeric() {
+      return m_scoreNumeric;
+    }
+    
+    /**
+     * Get the ID of this node.
+     * 
+     * @return the ID of this node.
+     */
+    protected String getID() {
+      return m_ID;
+    }
+    
+    /**
+     * Get the Predicate at this node.
+     * 
+     * @return the predicate at this node.
+     */
+    protected Predicate getPredicate() {
+      return m_predicate;
+    }
+    
+    /**
+     * Get the record count at this node.
+     * 
+     * @return the record count at this node.
+     */
+    protected double getRecordCount() {
+      return m_recordCount;
+    }
+    
+    protected void dumpGraph(StringBuffer text) throws Exception {
+      text.append("N" + m_ID + " ");
+      if (m_scoreString != null) {
+        text.append("[label=\"score=" + m_scoreString);
+      }
+      
+      if (m_scoreDistributions.size() > 0 && m_childNodes.size() == 0) {
+        text.append("\\n");
+        for (ScoreDistribution s : m_scoreDistributions) {
+          text.append(s + "\\n");
+        }
+      }
+      
+      text.append("\"");
+      
+      if (m_childNodes.size() == 0) {
+        text.append(" shape=box style=filled");
+        
+      }
+      
+      text.append("]\n");
+      
+      for (TreeNode c : m_childNodes) {
+        text.append("N" + m_ID +"->" + "N" + c.getID());
+        text.append(" [label=\"" + c.getPredicate().toString(0, true));
+        text.append("\"]\n");
+        c.dumpGraph(text);
+      }
+    }
+    
+    public String toString() {
+      StringBuffer text = new StringBuffer();
+      
+      // print out the root
+      dumpTree(0, text);
+
+      return text.toString();
+    }
+    
+    protected void dumpTree(int level, StringBuffer text) {
+      if (m_childNodes.size() > 0) {
+
+        for (int i = 0; i < m_childNodes.size(); i++) {
+          text.append("\n");
+          
+/*          for (int j = 0; j < level; j++) {
+            text.append("|   ");
+          } */
+          
+          // output the predicate for this child node
+          TreeNode child = m_childNodes.get(i);
+          text.append(child.getPredicate().toString(level, false));
+          
+          // process recursively
+          child.dumpTree(level + 1 , text);          
+        }
+      } else {
+        // leaf
+        text.append(": ");
+        if (!Utils.isMissingValue(m_scoreNumeric)) {
+          text.append(m_scoreNumeric);
+        } else {
+          text.append(m_scoreString + " ");
+          if (m_scoreDistributions.size() > 0) {
+            text.append("[");
+            for (ScoreDistribution s : m_scoreDistributions) {
+              text.append(s);
+            }
+            text.append("]");
+          } else {
+            text.append(m_scoreString);
+          }
+        }
+      }
+    }
+    
+    /**
+     * Score an incoming instance. Invokes a missing value handling strategy.
+     * 
+     * @param instance a vector of incoming attribute and derived field values.
+     * @param classAtt the class attribute
+     * @return a predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] score(double[] instance, Attribute classAtt) throws Exception {
+      double[] preds = null;
+      
+      if (classAtt.isNumeric()) {
+        preds = new double[1];
+      } else {
+        preds = new double[classAtt.numValues()];
+      }
+      
+      // leaf?
+      if (m_childNodes.size() == 0) {
+        doLeaf(classAtt, preds);
+      } else {
+        // process the children
+        switch (TreeModel.this.m_missingValueStrategy) {
+        case NONE:
+          preds = missingValueStrategyNone(instance, classAtt);
+          break;
+        case LASTPREDICTION:
+          preds = missingValueStrategyLastPrediction(instance, classAtt);
+          break;
+        case DEFAULTCHILD:
+          preds = missingValueStrategyDefaultChild(instance, classAtt);
+          break;
+        default:
+          throw new Exception("[TreeModel] not implemented!");
+        }
+      }
+      
+      return preds;
+    }
+    
+    /**
+     * Compute the predictions for a leaf.
+     * 
+     * @param classAtt the class attribute
+     * @param preds an array to hold the predicted probabilities.
+     * @throws Exception if something goes wrong.
+     */
+    protected void doLeaf(Attribute classAtt, double[] preds) throws Exception {
+      if (classAtt.isNumeric()) {
+        preds[0] = m_scoreNumeric;
+      } else {
+        if (m_scoreDistributions.size() == 0) {
+          preds[m_scoreIndex] = 1.0;
+        } else {
+          // collect confidences from the score distributions
+          for (ScoreDistribution s : m_scoreDistributions) {
+            preds[s.getClassLabelIndex()] = s.getConfidence();
+          }
+        }
+      }
+    }
+    
+    /**
+     * Evaluate on the basis of the no true child strategy.
+     * 
+     * @param classAtt the class attribute.
+     * @param preds an array to hold the predicted probabilities.
+     * @throws Exception if something goes wrong.
+     */
+    protected void doNoTrueChild(Attribute classAtt, double[] preds) 
+      throws Exception {
+      if (TreeModel.this.m_noTrueChildStrategy == 
+        NoTrueChildStrategy.RETURNNULLPREDICTION) {
+        for (int i = 0; i < classAtt.numValues(); i++) {
+          preds[i] = Utils.missingValue();
+        }
+      } else {
+        // return the predictions at this node
+        doLeaf(classAtt, preds);
+      }
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the weighted confidence
+     * missing value handling strategy.
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyWeightedConfidence(double[] instance,
+        Attribute classAtt) throws Exception {
+      
+      if (classAtt.isNumeric()) {
+        throw new Exception("[TreeNode] missing value strategy weighted confidence, "
+            + "but class is numeric!");
+      }
+      
+      double[] preds = null;
+      TreeNode trueNode = null;
+      boolean strategyInvoked = false;
+      int nodeCount = 0;
+      
+      // look at the evaluation of the child predicates
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          // note the first child to evaluate to true
+          if (trueNode == null) {
+            trueNode = c;
+          }
+          nodeCount++;
+        } else if (c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+          strategyInvoked = true;
+          nodeCount++;
+        }
+      }
+      
+      if (strategyInvoked) {
+        // we expect to combine nodeCount distributions
+        double[][] dists = new double[nodeCount][];
+        double[] weights = new double[nodeCount];
+        
+        // collect the distributions and weights
+        int count = 0;
+        for (TreeNode c : m_childNodes) {
+          if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE ||
+              c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+            
+            weights[count] = c.getRecordCount();
+            if (Utils.isMissingValue(weights[count])) {
+              throw new Exception("[TreeNode] weighted confidence missing value " +
+              		"strategy invoked, but no record count defined for node " +
+              		c.getID());
+            }            
+            dists[count++] = c.score(instance, classAtt);
+          }
+        }
+        
+        // do the combination
+        preds = new double[classAtt.numValues()];
+        for (int i = 0; i < classAtt.numValues(); i++) {
+          for (int j = 0; j < nodeCount; j++) {
+            preds[i] += ((weights[j] / m_recordCount) * dists[j][i]); 
+          }
+        }
+      } else {
+        if (trueNode != null) {
+          preds = trueNode.score(instance, classAtt);
+        } else {
+          doNoTrueChild(classAtt, preds);
+        }
+      }
+      
+      return preds;
+    }
+    
+    protected double[] freqCountsForAggNodesStrategy(double[] instance,
+        Attribute classAtt) throws Exception {
+    
+      double[] counts = new double[classAtt.numValues()];
+      
+      if (m_childNodes.size() > 0) {
+        // collect the counts
+        for (TreeNode c : m_childNodes) {
+          if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE ||
+              c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+
+            double[] temp = c.freqCountsForAggNodesStrategy(instance, classAtt);
+            for (int i = 0; i < classAtt.numValues(); i++) {
+              counts[i] += temp[i];
+            }
+          }
+        }
+      } else {
+        // process the score distributions
+        if (m_scoreDistributions.size() == 0) {
+          throw new Exception("[TreeModel] missing value strategy aggregate nodes:" +
+          		" no score distributions at leaf " + m_ID);
+        }
+        for (ScoreDistribution s : m_scoreDistributions) {
+          counts[s.getClassLabelIndex()] = s.getRecordCount();
+        }
+      }
+            
+      return counts;
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the aggregate nodes
+     * missing value handling strategy.
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyAggregateNodes(double[] instance,
+        Attribute classAtt) throws Exception {
+      
+      if (classAtt.isNumeric()) {
+        throw new Exception("[TreeNode] missing value strategy aggregate nodes, "
+            + "but class is numeric!");
+      }
+
+      double[] preds = null;
+      TreeNode trueNode = null;
+      boolean strategyInvoked = false;
+      int nodeCount = 0;
+      
+      // look at the evaluation of the child predicates
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          // note the first child to evaluate to true
+          if (trueNode == null) {
+            trueNode = c;
+          }
+          nodeCount++;
+        } else if (c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+          strategyInvoked = true;
+          nodeCount++;
+        }
+      }
+      
+      if (strategyInvoked) {
+        double[] aggregatedCounts = 
+          freqCountsForAggNodesStrategy(instance, classAtt);
+        
+        // normalize
+        Utils.normalize(aggregatedCounts);
+        preds = aggregatedCounts;
+      } else {
+        if (trueNode != null) {
+          preds = trueNode.score(instance, classAtt);
+        } else {
+          doNoTrueChild(classAtt, preds);
+        }
+      }
+      
+      return preds;             
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the default child
+     * missing value handling strategy.
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyDefaultChild(double[] instance, 
+        Attribute classAtt) throws Exception {
+      
+      double[] preds = null;
+      boolean strategyInvoked = false;
+      
+      // look for a child whose predicate evaluates to TRUE
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          preds = c.score(instance, classAtt);
+          break;
+        } else if (c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+          strategyInvoked = true;
+        }
+      }
+      
+      // no true child found
+      if (preds == null) {
+        if (!strategyInvoked) {
+          doNoTrueChild(classAtt, preds);
+        } else {
+          // do the strategy
+          
+          // NOTE: we don't actually implement the missing value penalty since
+          // we always return a full probability distribution.
+          if (m_defaultChild != null) {
+            preds = m_defaultChild.score(instance, classAtt);
+          } else {
+            throw new Exception("[TreeNode] missing value strategy is defaultChild, but " +
+            		"no default child has been specified in node " + m_ID);
+          }
+        }
+      }
+                  
+      return preds;
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the last prediction
+     * missing value handling strategy.
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyLastPrediction(double[] instance, 
+        Attribute classAtt) throws Exception {
+      
+      double[] preds = null;
+      boolean strategyInvoked = false;
+      
+      // look for a child whose predicate evaluates to TRUE
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          preds = c.score(instance, classAtt);
+          break;
+        } else if (c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+          strategyInvoked = true;
+        }
+      }
+      
+      // no true child found
+      if (preds == null) {
+        preds = new double[classAtt.numValues()];
+        if (!strategyInvoked) {
+          // no true child
+          doNoTrueChild(classAtt, preds);
+        } else {
+          // do the strategy
+          doLeaf(classAtt, preds);
+        }
+      }
+      
+      return preds;
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the null prediction
+     * missing value handling strategy.
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyNullPrediction(double[] instance,
+        Attribute classAtt) throws Exception {
+      
+      double[] preds = null;
+      boolean strategyInvoked = false;
+      
+      // look for a child whose predicate evaluates to TRUE
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          preds = c.score(instance, classAtt);
+          break;
+        } else if (c.getPredicate().evaluate(instance) == Predicate.Eval.UNKNOWN) {
+          strategyInvoked = true;
+        }
+      }
+      
+      // no true child found
+      if (preds == null) {
+        preds = new double[classAtt.numValues()];
+        if (!strategyInvoked) {
+          doNoTrueChild(classAtt, preds);
+        } else {
+          // do the strategy
+          for (int i = 0; i < classAtt.numValues(); i++) {
+            preds[i] = Utils.missingValue();
+          }
+        }
+      }
+      
+      return preds;
+    }
+    
+    /**
+     * Compute predictions and optionally invoke the "none"
+     * missing value handling strategy (invokes no true child).
+     * 
+     * @param instance the incoming vector of attribute and derived field values.
+     * @param classAtt the class attribute.
+     * @return the predicted probability distribution.
+     * @throws Exception if something goes wrong.
+     */
+    protected double[] missingValueStrategyNone(double[] instance, Attribute classAtt)
+      throws Exception {
+      
+      double[] preds = null;
+      
+      // look for a child whose predicate evaluates to TRUE
+      for (TreeNode c : m_childNodes) {
+        if (c.getPredicate().evaluate(instance) == Predicate.Eval.TRUE) {
+          preds = c.score(instance, classAtt);
+          break;
+        }
+      }
+      
+      if (preds == null) {
+        preds = new double[classAtt.numValues()];
+        
+        // no true child strategy
+        doNoTrueChild(classAtt, preds);
+      }
+      
+      return preds;
+    }
+  }
+  
+  /**
+   * Enumerated type for the mining function
+   */
+  enum MiningFunction {
+    CLASSIFICATION,
+    REGRESSION;
+  }
+  
+  enum MissingValueStrategy {
+    LASTPREDICTION("lastPrediction"),
+    NULLPREDICTION("nullPrediction"),
+    DEFAULTCHILD("defaultChild"),
+    WEIGHTEDCONFIDENCE("weightedConfidence"),
+    AGGREGATENODES("aggregateNodes"),
+    NONE("none");
+    
+    private final String m_stringVal;
+    
+    MissingValueStrategy(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  enum NoTrueChildStrategy {
+    RETURNNULLPREDICTION("returnNullPrediction"),
+    RETURNLASTPREDICTION("returnLastPrediction");
+    
+    private final String m_stringVal;
+    
+    NoTrueChildStrategy(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  enum SplitCharacteristic {
+    BINARYSPLIT("binarySplit"),
+    MULTISPLIT("multiSplit");
+  
+    private final String m_stringVal;
+    
+    SplitCharacteristic(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }  
+  }
+  
+  /** The mining function */
+  protected MiningFunction m_functionType = MiningFunction.CLASSIFICATION;
+  
+  /** The missing value strategy */
+  protected MissingValueStrategy m_missingValueStrategy = MissingValueStrategy.NONE;
+  
+  /** 
+   * The missing value penalty (if defined). 
+   * We don't actually make use of this since we always return 
+   * full probability distributions.
+   */
+  protected double m_missingValuePenalty = Utils.missingValue();
+  
+  /** The no true child strategy to use */
+  protected NoTrueChildStrategy m_noTrueChildStrategy = NoTrueChildStrategy.RETURNNULLPREDICTION;
+  
+  /** The splitting type */
+  protected SplitCharacteristic m_splitCharacteristic = SplitCharacteristic.MULTISPLIT;
+  
+  /** The root of the tree */
+  protected TreeNode m_root;
+  
+  public TreeModel(Element model, Instances dataDictionary, 
+      MiningSchema miningSchema) throws Exception {
+    
+    super(dataDictionary, miningSchema);
+    
+    if (!getPMMLVersion().equals("3.2")) {
+      // TODO: might have to throw an exception and only support 3.2
+    }
+    
+    String fn = model.getAttribute("functionName");
+    if (fn.equals("regression")) {
+      m_functionType = MiningFunction.REGRESSION;
+    }
+    
+    // get the missing value strategy (if any)
+    String missingVS = model.getAttribute("missingValueStrategy");
+    if (missingVS != null && missingVS.length() > 0) {
+      for (MissingValueStrategy m : MissingValueStrategy.values()) {
+        if (m.toString().equals(missingVS)) {
+          m_missingValueStrategy = m;
+          break;
+        }
+      }
+    }
+
+    // get the missing value penalty (if any)
+    String missingP = model.getAttribute("missingValuePenalty");
+    if (missingP != null && missingP.length() > 0) {
+      // try to parse as a number
+      try {
+        m_missingValuePenalty = Double.parseDouble(missingP);
+      } catch (NumberFormatException ex) {
+        System.err.println("[TreeModel] WARNING: " +
+          "couldn't parse supplied missingValuePenalty as a number");
+      }
+    }
+
+    String splitC = model.getAttribute("splitCharacteristic");
+
+    if (splitC != null && splitC.length() > 0) {
+      for (SplitCharacteristic s : SplitCharacteristic.values()) {
+        if (s.toString().equals(splitC)) {
+          m_splitCharacteristic = s;
+          break;
+        }
+      }
+    }
+    
+    // find the root node of the tree
+    NodeList children = model.getChildNodes();
+    for (int i = 0; i < children.getLength(); i++) {
+      Node child = children.item(i);
+      if (child.getNodeType() == Node.ELEMENT_NODE) {
+        String tagName = ((Element)child).getTagName();
+        if (tagName.equals("Node")) {
+          m_root = new TreeNode((Element)child, miningSchema);          
+          break;
+        }
+      }
+    }    
+  }
+  
+  /**                                                                                                             
+   * Classifies the given test instance. The instance has to belong to a                                          
+   * dataset when it's being classified.                                                          
+   *                                                                                                              
+   * @param inst the instance to be classified                                                                
+   * @return the predicted most likely class for the instance or                                                  
+   * Utils.missingValue() if no prediction is made                                                             
+   * @exception Exception if an error occurred during the prediction                                              
+   */
+  public double[] distributionForInstance(Instance inst) throws Exception {
+    if (!m_initialized) {
+      mapToMiningSchema(inst.dataset());
+    }
+    double[] preds = null;
+    
+    if (m_miningSchema.getFieldsAsInstances().classAttribute().isNumeric()) {
+      preds = new double[1];
+    } else {
+      preds = new double[m_miningSchema.getFieldsAsInstances().classAttribute().numValues()];
+    }
+    
+    double[] incoming = m_fieldsMap.instanceToSchema(inst, m_miningSchema);
+    
+    preds = m_root.score(incoming, m_miningSchema.getFieldsAsInstances().classAttribute());
+    
+   return preds; 
+  }
+  
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+
+    temp.append("PMML version " + getPMMLVersion());
+    if (!getCreatorApplication().equals("?")) {
+      temp.append("\nApplication: " + getCreatorApplication());
+    }
+    temp.append("\nPMML Model: TreeModel");
+    temp.append("\n\n");
+    temp.append(m_miningSchema);
+    
+    temp.append("Split-type: " + m_splitCharacteristic + "\n");
+    temp.append("No true child strategy: " + m_noTrueChildStrategy + "\n");
+    temp.append("Missing value strategy: " + m_missingValueStrategy + "\n");
+    
+    temp.append(m_root.toString());
+    
+    return temp.toString();
+  }
+  
+  public String graph() throws Exception {
+    StringBuffer text = new StringBuffer();
+    text.append("digraph PMMTree {\n");
+    
+    m_root.dumpGraph(text);
+    
+    text.append("}\n");
+    
+    return text.toString();
+  }
+
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  public int graphType() {
+    return Drawable.TREE;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/ConjunctiveRule.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/ConjunctiveRule.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/ConjunctiveRule.java	(revision 29)
@@ -0,0 +1,1694 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConjunctiveRule.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This class implements a single conjunctive rule learner that can predict for numeric and nominal class labels.<br/>
+ * <br/>
+ * A rule consists of antecedents "AND"ed together and the consequent (class value) for the classification/regression.  In this case, the consequent is the distribution of the available classes (or mean for a numeric value) in the dataset. If the test instance is not covered by this rule, then it's predicted using the default class distributions/value of the data not covered by the rule in the training data.This learner selects an antecedent by computing the Information Gain of each antecendent and prunes the generated rule using Reduced Error Prunning (REP) or simple pre-pruning based on the number of antecedents.<br/>
+ * <br/>
+ * For classification, the Information of one antecedent is the weighted average of the entropies of both the data covered and not covered by the rule.<br/>
+ * For regression, the Information is the weighted average of the mean-squared errors of both the data covered and not covered by the rule.<br/>
+ * <br/>
+ * In pruning, weighted average of the accuracy rates on the pruning data is used for classification while the weighted average of the mean-squared errors on the pruning data is used for regression.<br/>
+ * <br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for REP
+ *  One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -R
+ *  Set if NOT uses randomization
+ *  (default:use randomization)</pre>
+ * 
+ * <pre> -E
+ *  Set whether consider the exclusive
+ *  expressions for nominal attributes
+ *  (default false)</pre>
+ * 
+ * <pre> -M &lt;min. weights&gt;
+ *  Set the minimal weights of instances
+ *  within a split.
+ *  (default 2.0)</pre>
+ * 
+ * <pre> -P &lt;number of antecedents&gt;
+ *  Set number of antecedents for pre-pruning
+ *  if -1, then REP is used
+ *  (default -1)</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Set the seed of randomization
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin XU (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class ConjunctiveRule 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler{
+    
+  /** for serialization */
+  static final long serialVersionUID = -5938309903225087198L;
+  
+  /** The number of folds to split data into Grow and Prune for REP*/
+  private int m_Folds = 3;
+    
+  /** The class attribute of the data*/
+  private Attribute m_ClassAttribute;
+    
+  /** The vector of antecedents of this rule*/
+  protected FastVector m_Antds = null;
+    
+  /** The default rule distribution of the data not covered*/
+  protected double[] m_DefDstr = null;
+    
+  /** The consequent of this rule */
+  protected double[] m_Cnsqt = null;
+        
+  /** Number of classes in the training data */
+  private int m_NumClasses = 0;
+    
+  /** The seed to perform randomization */
+  private long m_Seed = 1;
+    
+  /** The Random object used for randomization */
+  private Random m_Random = null;
+    
+  /** The predicted classes recorded for each antecedent in the growing data */
+  private FastVector m_Targets;
+
+  /** Whether to use exlusive expressions for nominal attributes */
+  private boolean m_IsExclude = false;
+
+  /** The minimal number of instance weights within a split*/
+  private double m_MinNo = 2.0;
+    
+  /** The number of antecedents in pre-pruning */
+  private int m_NumAntds = -1;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "This class implements a single conjunctive rule learner that can predict "
+      + "for numeric and nominal class labels.\n\n"
+      + "A rule consists of antecedents \"AND\"ed together and the consequent (class value) "
+      + "for the classification/regression.  In this case, the consequent is the "
+      + "distribution of the available classes (or mean for a numeric value) in the dataset. " 
+      + "If the test instance is not covered by this rule, then it's predicted "
+      + "using the default class distributions/value of the data not covered by the "
+      + "rule in the training data."
+      + "This learner selects an antecedent by computing the Information Gain of each "
+      + "antecendent and prunes the generated rule using Reduced Error Prunning (REP) "
+      + "or simple pre-pruning based on the number of antecedents.\n\n"
+      + "For classification, the Information of one antecedent is the weighted average of "
+      + "the entropies of both the data covered and not covered by the rule.\n"
+      + "For regression, the Information is the weighted average of the mean-squared errors "
+      + "of both the data covered and not covered by the rule.\n\n"
+      + "In pruning, weighted average of the accuracy rates on the pruning data is used "
+      + "for classification while the weighted average of the mean-squared errors "
+      + "on the pruning data is used for regression.\n\n";
+  }
+
+  /** 
+   * The single antecedent in the rule, which is composed of an attribute and 
+   * the corresponding value.  There are two inherited classes, namely NumericAntd
+   * and NominalAntd in which the attributes are numeric and nominal respectively.
+   */
+  private abstract class Antd
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -8729076306737827571L;
+
+    /** The attribute of the antecedent */
+    protected Attribute att;
+	
+    /** The attribute value of the antecedent.  
+	For numeric attribute, value is either 0(1st bag) or 1(2nd bag) */
+    protected double value; 
+	
+    /** The maximum infoGain achieved by this antecedent test */
+    protected double maxInfoGain;
+	
+    /** The information of this antecedent test on the growing data */
+    protected double inform;
+	
+    /** The parameter related to the meanSquaredError of the data not covered 
+	by the previous antecedents when the class is numeric */
+    protected double uncoverWtSq, uncoverWtVl, uncoverSum;
+	
+    /** The parameters related to the data not covered by the previous
+	antecedents when the class is nominal */
+    protected double[] uncover;
+	
+    /** Constructor for nominal class */
+    public Antd(Attribute a, double[] unc){
+      att=a;
+      value=Double.NaN; 
+      maxInfoGain = 0;
+      inform = Double.NaN;
+      uncover = unc;	
+    }
+	
+    /** 
+     * Constructor for numeric class
+     */
+    public Antd(Attribute a, double uncoveredWtSq, 
+		double uncoveredWtVl, double uncoveredWts){
+      att=a;
+      value=Double.NaN; 
+      maxInfoGain = 0;
+      inform = Double.NaN;
+      uncoverWtSq = uncoveredWtSq;
+      uncoverWtVl = uncoveredWtVl;
+      uncoverSum = uncoveredWts;
+    }
+	
+    /* The abstract members for inheritance */
+    public abstract Instances[] splitData(Instances data, double defInfo);
+    public abstract boolean isCover(Instance inst);
+    public abstract String toString();
+	
+    /* Get functions of this antecedent */
+    public Attribute getAttr(){ return att; }
+    public double getAttrValue(){ return value; }
+    public double getMaxInfoGain(){ return maxInfoGain; }
+    public double getInfo(){ return inform;}
+	
+    /** 
+     * Function used to calculate the weighted mean squared error,
+     * i.e., sum[x-avg(x)]^2 based on the given elements of the formula:
+     * meanSquaredError = sum(Wi*Xi^2) - (sum(WiXi))^2/sum(Wi)
+     * 
+     * @param weightedSq sum(Wi*Xi^2)
+     * @param weightedValue sum(WiXi)
+     * @param sum sum of weights
+     * @return the weighted mean-squared error
+     */
+    protected double wtMeanSqErr(double weightedSq, double weightedValue, double sum){
+      if(Utils.smOrEq(sum, 1.0E-6))
+	return 0;	    
+      return (weightedSq - (weightedValue * weightedValue) / sum);
+    }
+	
+    /**
+     * Function used to calculate the entropy of given vector of values
+     * entropy = (1/sum)*{-sigma[i=1..P](Xi*log2(Xi)) + sum*log2(sum)}
+     * where P is the length of the vector
+     *
+     * @param value the given vector of values
+     * @param sum the sum of the given values.  It's provided just for efficiency.
+     * @return the entropy
+     */
+    protected double entropy(double[] value, double sum){	   
+      if(Utils.smOrEq(sum, 1.0E-6))
+	return 0;
+
+      double entropy = 0;	    
+      for(int i=0; i < value.length; i++){
+	if(!Utils.eq(value[i],0))
+	  entropy -= value[i] * Utils.log2(value[i]);
+      }
+      entropy += sum * Utils.log2(sum);
+      entropy /= sum;
+      return entropy;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+  /** 
+   * The antecedent with numeric attribute
+   */
+  private class NumericAntd 
+    extends Antd {
+    
+    /** for serialization */
+    static final long serialVersionUID = -7957266498918210436L;
+	
+    /** The split point for this numeric antecedent */
+    private double splitPoint;
+	
+    /** 
+     * Constructor for nominal class
+     */
+    public NumericAntd(Attribute a, double[] unc){ 
+      super(a, unc);
+      splitPoint = Double.NaN;
+    }    
+	
+    /** 
+     * Constructor for numeric class
+     */
+    public NumericAntd(Attribute a, double sq, double vl, double wts){ 
+      super(a, sq, vl, wts);
+      splitPoint = Double.NaN;
+    }
+	
+    /** 
+     * Get split point of this numeric antecedent
+     * 
+     * @return the split point
+     */
+    public double getSplitPoint(){ 
+      return splitPoint; 
+    }
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into two bags according 
+     * to the information gain of the numeric attribute value
+     * the data with missing values are stored in the last split.
+     * The maximum infoGain is also calculated.  
+     * 
+     * @param insts the data to be split
+     * @param defInfo the default information for data
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances insts, double defInfo){	
+      Instances data = new Instances(insts);
+      data.sort(att);
+      int total=data.numInstances();// Total number of instances without 
+      // missing value for att
+      maxInfoGain = 0;
+      value = 0;	
+	    
+      // Compute minimum number of Instances required in each split
+      double minSplit;
+      if(m_ClassAttribute.isNominal()){
+	minSplit =  0.1 * (data.sumOfWeights()) /
+	  ((double)m_ClassAttribute.numValues());
+	if (Utils.smOrEq(minSplit,m_MinNo)) 
+	  minSplit = m_MinNo;
+	else if (Utils.gr(minSplit,25)) 
+	  minSplit = 25;
+      }
+      else
+	minSplit = m_MinNo;
+ 
+      double[] fst=null, snd=null, missing=null;
+      if(m_ClassAttribute.isNominal()){
+	fst = new double[m_NumClasses];
+	snd = new double[m_NumClasses];
+	missing = new double[m_NumClasses];
+		
+	for(int v=0; v < m_NumClasses; v++)
+	  fst[v]=snd[v]=missing[v]=0.0;
+      }
+      double fstCover=0, sndCover=0, fstWtSq=0, sndWtSq=0, fstWtVl=0, sndWtVl=0;
+	    
+      int split=1;                  // Current split position
+      int prev=0;                   // Previous split position		    
+      int finalSplit=split;         // Final split position
+		    
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst = data.instance(x);
+	if(inst.isMissing(att)){
+	  total = x;
+	  break;
+	}
+		
+	sndCover += inst.weight();
+	if(m_ClassAttribute.isNominal()) // Nominal class
+	  snd[(int)inst.classValue()] += inst.weight();		
+	else{                            // Numeric class
+	  sndWtSq += inst.weight() * inst.classValue() * inst.classValue();
+	  sndWtVl += inst.weight() * inst.classValue();
+	}
+      }
+	    
+	    
+      // Enough Instances with known values?
+      if (Utils.sm(sndCover,(2*minSplit)))
+	return null;
+	    
+      double msingWtSq=0, msingWtVl=0;
+      Instances missingData = new Instances(data, 0);
+      for(int y=total; y < data.numInstances(); y++){	    
+	Instance inst = data.instance(y);
+	missingData.add(inst);
+	if(m_ClassAttribute.isNominal())
+	  missing[(int)inst.classValue()] += inst.weight();
+	else{ 
+	  msingWtSq += inst.weight() * inst.classValue() * inst.classValue();
+	  msingWtVl += inst.weight() * inst.classValue();
+	}			
+      }	    
+	    
+      if(total == 0) return null; // Data all missing for the attribute 	
+	    
+      splitPoint = data.instance(total-1).value(att);	
+	    
+      for(; split < total; split++){	
+	if(!Utils.eq(data.instance(split).value(att), // Can't split 
+		     data.instance(prev).value(att))){// within same value 	 
+		    
+	  // Move the split point
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    fstCover += inst.weight(); sndCover -= inst.weight();
+	    if(m_ClassAttribute.isNominal()){ // Nominal class
+	      fst[(int)inst.classValue()] += inst.weight();
+	      snd[(int)inst.classValue()] -= inst.weight();
+	    }   
+	    else{                             // Numeric class
+	      fstWtSq += inst.weight() * inst.classValue() * inst.classValue();
+	      fstWtVl += inst.weight() * inst.classValue();
+	      sndWtSq -= inst.weight() * inst.classValue() * inst.classValue();
+	      sndWtVl -= inst.weight() * inst.classValue();
+	    }
+	  }
+		    
+	  if(Utils.sm(fstCover, minSplit) || Utils.sm(sndCover, minSplit)){
+	    prev=split;  // Cannot split because either
+	    continue;    // split has not enough data
+	  }
+		    
+	  double fstEntp = 0, sndEntp = 0;
+		    
+	  if(m_ClassAttribute.isNominal()){
+	    fstEntp = entropy(fst, fstCover);
+	    sndEntp = entropy(snd, sndCover);
+	  }
+	  else{
+	    fstEntp = wtMeanSqErr(fstWtSq, fstWtVl, fstCover)/fstCover;
+	    sndEntp = wtMeanSqErr(sndWtSq, sndWtVl, sndCover)/sndCover;
+	  }
+		    
+	  /* Which bag has higher information gain? */
+	  boolean isFirst; 
+	  double fstInfoGain, sndInfoGain;
+	  double info, infoGain, fstInfo, sndInfo;
+	  if(m_ClassAttribute.isNominal()){
+	    double sum = data.sumOfWeights();
+	    double otherCover, whole = sum + Utils.sum(uncover), otherEntropy; 
+	    double[] other = null;
+			
+	    // InfoGain of first bag			
+	    other = new double[m_NumClasses];
+	    for(int z=0; z < m_NumClasses; z++)
+	      other[z] = uncover[z] + snd[z] + missing[z];   
+	    otherCover = whole - fstCover;			
+	    otherEntropy = entropy(other, otherCover);
+	    // Weighted average
+	    fstInfo = (fstEntp*fstCover + otherEntropy*otherCover)/whole;
+	    fstInfoGain = defInfo - fstInfo;
+			
+	    // InfoGain of second bag 			
+	    other = new double[m_NumClasses];
+	    for(int z=0; z < m_NumClasses; z++)
+	      other[z] = uncover[z] + fst[z] + missing[z]; 
+	    otherCover = whole - sndCover;			
+	    otherEntropy = entropy(other, otherCover);
+	    // Weighted average
+	    sndInfo = (sndEntp*sndCover + otherEntropy*otherCover)/whole;			    
+	    sndInfoGain = defInfo - sndInfo;			
+	  }
+	  else{
+	    double sum = data.sumOfWeights();
+	    double otherWtSq = (sndWtSq + msingWtSq + uncoverWtSq), 
+	      otherWtVl = (sndWtVl + msingWtVl + uncoverWtVl),
+	      otherCover = (sum - fstCover + uncoverSum);
+			
+	    fstInfo = Utils.eq(fstCover, 0) ? 0 : (fstEntp * fstCover);
+	    fstInfo += wtMeanSqErr(otherWtSq, otherWtVl, otherCover);
+	    fstInfoGain = defInfo - fstInfo;
+			
+	    otherWtSq = (fstWtSq + msingWtSq + uncoverWtSq); 
+	    otherWtVl = (fstWtVl + msingWtVl + uncoverWtVl);
+	    otherCover = sum - sndCover + uncoverSum;
+	    sndInfo = Utils.eq(sndCover, 0) ? 0 : (sndEntp * sndCover);
+	    sndInfo += wtMeanSqErr(otherWtSq, otherWtVl, otherCover);
+	    sndInfoGain = defInfo - sndInfo;
+	  }
+		    
+	  if(Utils.gr(fstInfoGain,sndInfoGain) || 
+	     (Utils.eq(fstInfoGain,sndInfoGain)&&(Utils.sm(fstEntp,sndEntp)))){ 
+	    isFirst = true;
+	    infoGain = fstInfoGain;
+	    info = fstInfo;
+	  }
+	  else{
+	    isFirst = false;
+	    infoGain = sndInfoGain;
+	    info = sndInfo;
+	  }
+		    
+	  boolean isUpdate = Utils.gr(infoGain, maxInfoGain);
+		    
+	  /* Check whether so far the max infoGain */
+	  if(isUpdate){
+	    splitPoint = ((data.instance(split).value(att)) + (data.instance(prev).value(att)))/2.0;
+	    value = ((isFirst) ? 0 : 1);
+	    inform = info;
+	    maxInfoGain = infoGain;
+	    finalSplit = split;			
+	  }
+	  prev=split;
+	}
+      }
+	    
+      /* Split the data */
+      Instances[] splitData = new Instances[3];
+      splitData[0] = new Instances(data, 0, finalSplit);
+      splitData[1] = new Instances(data, finalSplit, total-finalSplit);
+      splitData[2] = new Instances(missingData);
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered 
+     *         by this antecedent
+     */
+    public boolean isCover(Instance inst){
+      boolean isCover=false;
+      if(!inst.isMissing(att)){
+	if(Utils.eq(value, 0)){
+	  if(Utils.smOrEq(inst.value(att), splitPoint))
+	    isCover=true;
+	}
+	else if(Utils.gr(inst.value(att), splitPoint))
+	  isCover=true;
+      }
+      return isCover;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      String symbol = Utils.eq(value, 0.0) ? " <= " : " > ";
+      return (att.name() + symbol + Utils.doubleToString(splitPoint, 6));
+    }   
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+    
+  /** 
+   * The antecedent with nominal attribute
+   */
+  class NominalAntd 
+    extends Antd {
+	
+    /** for serialization */
+    static final long serialVersionUID = -5949864163376447424L;
+    
+    /* The parameters of infoGain calculated for each attribute value */
+    private double[][] stats;
+    private double[] coverage;
+    private boolean isIn;
+	
+    /** 
+     * Constructor for nominal class
+     */
+    public NominalAntd(Attribute a, double[] unc){ 
+      super(a, unc);
+      int bag = att.numValues();
+      stats = new double[bag][m_NumClasses];
+      coverage = new double[bag];
+      isIn = true;
+    }   
+	
+    /** 
+     * Constructor for numeric class
+     */
+    public NominalAntd(Attribute a, double sq, double vl, double wts){ 
+      super(a, sq, vl, wts);
+      int bag = att.numValues();	    
+      stats = null;
+      coverage = new double[bag];
+      isIn = true;
+    }
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into bags according 
+     * to the nominal attribute value
+     * the data with missing values are stored in the last bag.
+     * The infoGain for each bag is also calculated.  
+     * 
+     * @param data the data to be split
+     * @param defInfo the default information for data
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances data, double defInfo){
+      int bag = att.numValues();
+      Instances[] splitData = new Instances[bag+1];
+      double[] wSq = new double[bag];
+      double[] wVl = new double[bag];
+      double totalWS=0, totalWV=0, msingWS=0, msingWV=0, sum=data.sumOfWeights();
+      double[] all = new double[m_NumClasses];
+      double[] missing = new double[m_NumClasses];	   
+	    
+      for(int w=0; w < m_NumClasses; w++)
+	all[w] = missing[w] = 0;
+
+      for(int x=0; x<bag; x++){
+	coverage[x] = wSq[x] = wVl[x] = 0;
+	if(stats != null)
+	  for(int y=0; y < m_NumClasses; y++)
+	    stats[x][y] = 0;		
+	splitData[x] = new Instances(data, data.numInstances());
+      }
+      splitData[bag] = new Instances(data, data.numInstances());
+	    
+      // Record the statistics of data
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst=data.instance(x);
+	if(!inst.isMissing(att)){
+	  int v = (int)inst.value(att);
+	  splitData[v].add(inst);
+	  coverage[v] += inst.weight();
+	  if(m_ClassAttribute.isNominal()){ // Nominal class			
+	    stats[v][(int)inst.classValue()] += inst.weight();
+	    all[(int)inst.classValue()] += inst.weight();	    
+	  }
+	  else{                             // Numeric class
+	    wSq[v] += inst.weight() * inst.classValue() * inst.classValue();
+	    wVl[v] += inst.weight() * inst.classValue();
+	    totalWS += inst.weight() * inst.classValue() * inst.classValue();
+	    totalWV += inst.weight() * inst.classValue();
+	  }
+	}
+	else{
+	  splitData[bag].add(inst);
+	  if(m_ClassAttribute.isNominal()){ // Nominal class
+	    all[(int)inst.classValue()] += inst.weight();
+	    missing[(int)inst.classValue()] += inst.weight();
+	  }
+	  else{                            // Numeric class
+	    totalWS += inst.weight() * inst.classValue() * inst.classValue();
+	    totalWV += inst.weight() * inst.classValue();
+	    msingWS += inst.weight() * inst.classValue() * inst.classValue();
+	    msingWV += inst.weight() * inst.classValue();		 
+	  }
+	}
+      }
+	    
+      // The total weights of the whole grow data
+      double whole;
+      if(m_ClassAttribute.isNominal())
+	whole = sum + Utils.sum(uncover);
+      else
+	whole = sum + uncoverSum;
+	  
+      // Find the split  
+      double minEntrp=Double.MAX_VALUE;
+      maxInfoGain = 0;
+	    
+      // Check if >=2 splits have more than the minimal data
+      int count=0;
+      for(int x=0; x<bag; x++)
+	if(Utils.grOrEq(coverage[x], m_MinNo))		    
+	  ++count;
+	    
+      if(count < 2){ // Don't split
+	maxInfoGain = 0;
+	inform = defInfo;
+	value = Double.NaN;
+	return null;
+      }
+	    
+      for(int x=0; x<bag; x++){		
+	double t = coverage[x], entrp, infoGain;
+
+	if(Utils.sm(t, m_MinNo))
+	  continue;
+		
+	if(m_ClassAttribute.isNominal()){ // Nominal class	   
+	  double[] other = new double[m_NumClasses];
+	  for(int y=0; y < m_NumClasses; y++)
+	    other[y] = all[y] - stats[x][y] + uncover[y]; 
+	  double otherCover = whole - t;	
+		    
+	  // Entropies of data covered and uncovered 	
+	  entrp = entropy(stats[x], t);
+	  double uncEntp = entropy(other, otherCover);
+		    
+	  // Weighted average
+	  infoGain = defInfo - (entrp*t + uncEntp*otherCover)/whole;		   
+	}	
+	else{                             // Numeric class
+	  double weight = (whole - t);
+	  entrp = wtMeanSqErr(wSq[x], wVl[x], t)/t;
+	  infoGain = defInfo - (entrp * t) - 
+	    wtMeanSqErr((totalWS-wSq[x]+uncoverWtSq),
+			(totalWV-wVl[x]+uncoverWtVl), 
+			weight);		  
+	}   		
+		
+	// Test the exclusive expression
+	boolean isWithin =true;		
+	if(m_IsExclude){
+	  double infoGain2, entrp2;
+	  if(m_ClassAttribute.isNominal()){ // Nominal class	
+	    double[] other2 = new double[m_NumClasses];
+	    double[] notIn = new double[m_NumClasses];
+	    for(int y=0; y < m_NumClasses; y++){
+	      other2[y] = stats[x][y] + missing[y] + uncover[y];
+	      notIn[y] = all[y] - stats[x][y] - missing[y];
+	    } 
+			
+	    double msSum = Utils.sum(missing);
+	    double otherCover2 = t + msSum + Utils.sum(uncover);
+			
+	    entrp2 = entropy(notIn, (sum-t-msSum));
+	    double uncEntp2 = entropy(other2, otherCover2);
+	    infoGain2 = defInfo - 
+	      (entrp2*(sum-t-msSum) + uncEntp2*otherCover2)/whole;
+	  }
+	  else{                             // Numeric class
+	    double msWts = splitData[bag].sumOfWeights();
+	    double weight2 = t + uncoverSum + msWts;
+			
+	    entrp2 = wtMeanSqErr((totalWS-wSq[x]-msingWS),
+				 (totalWV-wVl[x]-msingWV),(sum-t-msWts))
+	      /(sum-t-msWts);
+	    infoGain2 = defInfo - entrp2 * (sum-t-msWts) -
+	      wtMeanSqErr((wSq[x]+uncoverWtSq+msingWS),
+			  (wVl[x]+uncoverWtVl+msingWV), 
+			  weight2);
+	  }
+		    
+	  // Use the exclusive expression?
+	  if (Utils.gr(infoGain2, infoGain) ||
+	      (Utils.eq(infoGain2, infoGain) && Utils.sm(entrp2, entrp))){
+	    infoGain = infoGain2;
+	    entrp = entrp2;
+	    isWithin =false;
+	  }
+	}
+		
+	// Test this split
+	if (Utils.gr(infoGain, maxInfoGain) ||
+	    (Utils.eq(infoGain, maxInfoGain) && Utils.sm(entrp, minEntrp))){
+	  value = (double)x;
+	  maxInfoGain = infoGain;
+	  inform = maxInfoGain - defInfo;
+	  minEntrp = entrp;
+	  isIn = isWithin;
+	}		
+      }
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered 
+     *         by this antecedent
+     */
+    public boolean isCover(Instance inst){	  
+      boolean isCover=false;
+      if(!inst.isMissing(att)){
+	if(isIn){
+	  if(Utils.eq(inst.value(att), value))
+	    isCover=true;
+	}
+	else if(!Utils.eq(inst.value(att), value))
+	  isCover=true;
+      }
+      return isCover;
+    }
+	
+    /**
+     * Whether the expression is "att = value" or att != value"
+     * for this nominal attribute.  True if in the former expression, 
+     * otherwise the latter
+     * 
+     * @return the boolean value
+     */
+    public boolean isIn(){	 
+      return isIn;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      String symbol = isIn ? " = " : " != ";	    
+      return (att.name() + symbol + att.value((int)value));
+    } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+  /**
+   * Returns an enumeration describing the available options
+   * Valid options are: <p>
+   *
+   * -N number <br>
+   * Set number of folds for REP. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   *
+   * -R <br>
+   * Set if NOT randomize the data before split to growing and 
+   * pruning data. If NOT set, the seed of randomization is 
+   * specified by the -S option. (Default: randomize) <p>
+   * 
+   * -S <br>
+   * Seed of randomization. (Default: 1)<p>
+   *
+   * -E <br>
+   * Set whether consider the exclusive expressions for nominal
+   * attribute split. (Default: false) <p>
+   *
+   * -M number <br>
+   * Set the minimal weights of instances within a split.
+   * (Default: 2) <p>
+   *
+   * -P number <br>
+   * Set the number of antecedents allowed in the rule if pre-pruning
+   * is used.  If this value is other than -1, then pre-pruning will be
+   * used, otherwise the rule uses REP. (Default: -1) <p>
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(6);
+	
+    newVector.addElement(new Option("\tSet number of folds for REP\n" +
+				    "\tOne fold is used as pruning set.\n" +
+				    "\t(default 3)","N", 1, "-N <number of folds>"));
+	
+    newVector.addElement(new Option("\tSet if NOT uses randomization\n" +
+				    "\t(default:use randomization)","R", 0, "-R"));
+
+    newVector.addElement(new Option("\tSet whether consider the exclusive\n" +
+				    "\texpressions for nominal attributes\n"+
+				    "\t(default false)","E", 0, "-E"));
+	
+    newVector.addElement(new Option("\tSet the minimal weights of instances\n" +
+				    "\twithin a split.\n" +
+				    "\t(default 2.0)","M", 1, "-M <min. weights>"));
+    
+    newVector.addElement(new Option("\tSet number of antecedents for pre-pruning\n" +
+				    "\tif -1, then REP is used\n" +
+				    "\t(default -1)","P", 1, "-P <number of antecedents>"));
+    
+    newVector.addElement(new Option("\tSet the seed of randomization\n" +
+				    "\t(default 1)","S", 1, "-S <seed>"));
+    
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for REP
+   *  One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -R
+   *  Set if NOT uses randomization
+   *  (default:use randomization)</pre>
+   * 
+   * <pre> -E
+   *  Set whether consider the exclusive
+   *  expressions for nominal attributes
+   *  (default false)</pre>
+   * 
+   * <pre> -M &lt;min. weights&gt;
+   *  Set the minimal weights of instances
+   *  within a split.
+   *  (default 2.0)</pre>
+   * 
+   * <pre> -P &lt;number of antecedents&gt;
+   *  Set number of antecedents for pre-pruning
+   *  if -1, then REP is used
+   *  (default -1)</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Set the seed of randomization
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+	
+    String numFoldsString = Utils.getOption('N', options);
+    if (numFoldsString.length() != 0) 
+      m_Folds = Integer.parseInt(numFoldsString);
+    else 
+      m_Folds = 3;
+
+    String minNoString = Utils.getOption('M', options);
+    if (minNoString.length() != 0) 
+      m_MinNo = Double.parseDouble(minNoString);
+    else 
+      m_MinNo = 2.0;
+	
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) 
+      m_Seed = Integer.parseInt(seedString);
+    else 
+      m_Seed = 1;
+	
+    String numAntdsString = Utils.getOption('P', options);
+    if (numAntdsString.length() != 0) 
+      m_NumAntds = Integer.parseInt(numAntdsString);
+    else 
+      m_NumAntds = -1;
+	
+    m_IsExclude = Utils.getFlag('E', options);	
+  }
+    
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+	
+    String [] options = new String [9];
+    int current = 0;
+    options[current++] = "-N"; options[current++] = "" + m_Folds;
+    options[current++] = "-M"; options[current++] = "" + m_MinNo;
+    options[current++] = "-P"; options[current++] = "" + m_NumAntds;
+    options[current++] = "-S"; options[current++] = "" + m_Seed;
+
+    if(m_IsExclude)
+      options[current++] = "-E";
+	
+    while (current < options.length) 
+      options[current++] = "";
+    return options;
+  }
+    
+  /** The access functions for parameters */
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTipText() {
+    return "Determines the amount of data used for pruning. One fold is used for "
+      + "pruning, the rest for growing the rules.";
+  }
+
+  /**
+   * the number of folds to use
+   * 
+   * @param folds the number of folds to use
+   */
+  public void setFolds(int folds) {  
+    m_Folds = folds; 
+  }
+  
+  /**
+   * returns the current number of folds
+   * 
+   * @return the number of folds
+   */
+  public int getFolds() { 
+    return m_Folds; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data.";
+  }
+
+  /**
+   * sets the seed for randomizing the data
+   * 
+   * @param s the seed value
+   */
+  public void setSeed(long s) { 
+    m_Seed = s;
+  }
+  
+  /**
+   * returns the current seed value for randomizing the data
+   * 
+   * @return the seed value
+   */
+  public long getSeed() { 
+    return m_Seed; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String exclusiveTipText() {
+    return "Set whether to consider exclusive expressions for nominal "
+      + "attribute splits.";
+  }
+
+  /**
+   * Returns whether exclusive expressions for nominal attributes splits are 
+   * considered
+   * 
+   * @return true if exclusive expressions for nominal attributes splits are
+   *         considered
+   */
+  public boolean getExclusive() { 
+    return m_IsExclude;
+  }
+  
+  /**
+   * Sets whether exclusive expressions for nominal attributes splits are 
+   * considered
+   * 
+   * @param e whether to consider exclusive expressions for nominal attribute
+   *          splits
+   */
+  public void setExclusive(boolean e) { 
+    m_IsExclude = e;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNoTipText() {
+    return "The minimum total weight of the instances in a rule.";
+  }
+
+  /**
+   * Sets the minimum total weight of the instances in a rule
+   * 
+   * @param m the minimum total weight of the instances in a rule
+   */
+  public void setMinNo(double m) {  
+    m_MinNo = m; 
+  }
+  
+  /**
+   * Gets the minimum total weight of the instances in a rule
+   * 
+   * @return the minimum total weight of the instances in a rule
+   */
+  public double getMinNo(){ 
+    return m_MinNo; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numAntdsTipText() {
+    return "Set the number of antecedents allowed in the rule if "
+      + "pre-pruning is used.  If this value is other than -1, then "
+      + "pre-pruning will be used, otherwise the rule uses reduced-error "
+      + "pruning.";
+  }
+
+  /**
+   * Sets the number of antecedants
+   * 
+   * @param n the number of antecedants
+   */
+  public void setNumAntds(int n) {  
+    m_NumAntds = n; 
+  }
+  
+  /**
+   * Gets the number of antecedants
+   * 
+   * @return the number of antecedants
+   */
+  public int getNumAntds(){ 
+    return m_NumAntds; 
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+    
+  /**
+   * Builds a single rule learner with REP dealing with nominal classes or
+   * numeric classes.
+   * For nominal classes, this rule learner predicts a distribution on
+   * the classes.
+   * For numeric classes, this learner predicts a single value.
+   *
+   * @param instances the training data
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    Instances data = new Instances(instances);
+    data.deleteWithMissingClass();
+    
+    if(data.numInstances() < m_Folds)
+      throw new Exception("Not enough data for REP.");
+
+    m_ClassAttribute = data.classAttribute();
+    if(m_ClassAttribute.isNominal())
+      m_NumClasses = m_ClassAttribute.numValues();
+    else
+      m_NumClasses = 1;
+	
+    m_Antds = new FastVector();
+    m_DefDstr = new double[m_NumClasses];
+    m_Cnsqt = new double[m_NumClasses];
+    m_Targets = new FastVector();	    
+    m_Random = new Random(m_Seed);
+    
+    if(m_NumAntds != -1){
+      grow(data);
+    }
+    else{
+
+      data.randomize(m_Random);
+
+      // Split data into Grow and Prune	   
+      data.stratify(m_Folds);
+	
+      Instances growData=data.trainCV(m_Folds, m_Folds-1, m_Random);
+      Instances pruneData=data.testCV(m_Folds, m_Folds-1);
+
+      grow(growData);      // Build this rule  
+      prune(pruneData);    // Prune this rule		  	  
+    }
+	
+    if(m_ClassAttribute.isNominal()){			   
+      Utils.normalize(m_Cnsqt);
+      if(Utils.gr(Utils.sum(m_DefDstr), 0))
+	Utils.normalize(m_DefDstr);
+    }	
+  }
+    
+  /**
+   * Computes class distribution for the given instance.
+   *
+   * @param instance the instance for which distribution is to be computed
+   * @return the class distribution for the given instance
+   * @throws Exception if given instance is null
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+      if(instance == null)
+	  throw new Exception("Testing instance is NULL!");
+	
+    if (isCover(instance))		
+      return m_Cnsqt;
+    else
+      return m_DefDstr;
+  }
+ 
+  /**
+   * Whether the instance covered by this rule
+   * 
+   * @param datum the instance in question
+   * @return the boolean value indicating whether the instance is covered by this rule
+   */
+  public boolean isCover(Instance datum){
+    boolean isCover=true;
+
+    for(int i=0; i<m_Antds.size(); i++){
+      Antd antd = (Antd)m_Antds.elementAt(i);
+      if(!antd.isCover(datum)){
+	isCover = false;
+	break;
+      }
+    }
+	
+    return isCover;
+  }        
+    
+  /**
+   * Whether this rule has antecedents, i.e. whether it is a default rule
+   * 
+   * @return the boolean value indicating whether the rule has antecedents
+   */
+  public boolean hasAntds(){
+    if (m_Antds == null)
+      return false;
+    else
+      return (m_Antds.size() > 0);
+  }      
+
+  /**
+   * Build one rule using the growing data
+   *
+   * @param data the growing data used to build the rule
+   */    
+  private void grow(Instances data){
+    Instances growData = new Instances(data);	
+    double defInfo;	
+    double whole = data.sumOfWeights();
+
+    if(m_NumAntds != 0){
+	
+      /* Class distribution for data both covered and not covered by one antecedent */
+      double[][] classDstr = new double[2][m_NumClasses];
+	    
+      /* Compute the default information of the growing data */
+      for(int j=0; j < m_NumClasses; j++){
+	classDstr[0][j] = 0;
+	classDstr[1][j] = 0;
+      }	
+      if(m_ClassAttribute.isNominal()){	    
+	for(int i=0; i < growData.numInstances(); i++){
+	  Instance datum = growData.instance(i);
+	  classDstr[0][(int)datum.classValue()] += datum.weight();
+	}
+	defInfo = ContingencyTables.entropy(classDstr[0]);    
+      }
+      else{
+	for(int i=0; i < growData.numInstances(); i++){
+	  Instance datum = growData.instance(i);
+	  classDstr[0][0] += datum.weight() * datum.classValue();
+	}
+		
+	// No need to be divided by the denomitor because
+	// it's always the same
+	double defMean = (classDstr[0][0] / whole);
+	defInfo = meanSquaredError(growData, defMean) * growData.sumOfWeights();    
+      }
+	    
+      // Store the default class distribution	
+      double[][] tmp = new double[2][m_NumClasses];
+      for(int y=0; y < m_NumClasses; y++){
+	if(m_ClassAttribute.isNominal()){	
+	  tmp[0][y] = classDstr[0][y];
+	  tmp[1][y] = classDstr[1][y];
+	}
+	else{
+	  tmp[0][y] = classDstr[0][y]/whole;
+	  tmp[1][y] = classDstr[1][y];
+	}
+      }
+      m_Targets.addElement(tmp); 
+	    
+      /* Keep the record of which attributes have already been used*/    
+      boolean[] used=new boolean[growData.numAttributes()];
+      for (int k=0; k<used.length; k++)
+	used[k]=false;
+      int numUnused=used.length;	
+      double maxInfoGain, uncoveredWtSq=0, uncoveredWtVl=0, uncoveredWts=0;	
+      boolean isContinue = true; // The stopping criterion of this rule	
+	    
+      while (isContinue){   
+	maxInfoGain = 0;       // We require that infoGain be positive
+		
+	/* Build a list of antecedents */
+	Antd oneAntd=null;
+	Instances coverData = null, uncoverData = null;
+	Enumeration enumAttr=growData.enumerateAttributes();	    
+	int index=-1;  
+		
+	/* Build one condition based on all attributes not used yet*/
+	while (enumAttr.hasMoreElements()){
+	  Attribute att= (Attribute)(enumAttr.nextElement());
+	  index++;
+		    
+	  Antd antd =null;	
+	  if(m_ClassAttribute.isNominal()){		    
+	    if(att.isNumeric())
+	      antd = new NumericAntd(att, classDstr[1]);
+	    else
+	      antd = new NominalAntd(att, classDstr[1]);
+	  }
+	  else
+	    if(att.isNumeric())
+	      antd = new NumericAntd(att, uncoveredWtSq, uncoveredWtVl, uncoveredWts);
+	    else
+	      antd = new NominalAntd(att, uncoveredWtSq, uncoveredWtVl, uncoveredWts); 
+		    
+	  if(!used[index]){
+	    /* Compute the best information gain for each attribute,
+	       it's stored in the antecedent formed by this attribute.
+	       This procedure returns the data covered by the antecedent*/
+	    Instances[] coveredData = computeInfoGain(growData, defInfo, antd);	 
+			
+	    if(coveredData != null){
+	      double infoGain = antd.getMaxInfoGain();			
+	      boolean isUpdate = Utils.gr(infoGain, maxInfoGain);
+			    
+	      if(isUpdate){
+		oneAntd=antd;
+		coverData = coveredData[0]; 
+		uncoverData = coveredData[1];  
+		maxInfoGain = infoGain;		    
+	      }
+	    }
+	  }
+	}
+		
+	if(oneAntd == null) 		
+	  break;	    
+		
+	//Numeric attributes can be used more than once
+	if(!oneAntd.getAttr().isNumeric()){ 
+	  used[oneAntd.getAttr().index()]=true;
+	  numUnused--;
+	}
+		
+	m_Antds.addElement(oneAntd);
+	growData = coverData;// Grow data size is shrinking 	    
+		
+	for(int x=0; x < uncoverData.numInstances(); x++){
+	  Instance datum = uncoverData.instance(x);
+	  if(m_ClassAttribute.isNumeric()){
+	    uncoveredWtSq += datum.weight() * datum.classValue() * datum.classValue();
+	    uncoveredWtVl += datum.weight() * datum.classValue();
+	    uncoveredWts += datum.weight();
+	    classDstr[0][0] -= datum.weight() * datum.classValue();
+	    classDstr[1][0] += datum.weight() * datum.classValue();
+	  }
+	  else{
+	    classDstr[0][(int)datum.classValue()] -= datum.weight();
+	    classDstr[1][(int)datum.classValue()] += datum.weight();
+	  }
+	}	       
+		
+	// Store class distribution of growing data
+	tmp = new double[2][m_NumClasses];
+	for(int y=0; y < m_NumClasses; y++){
+	  if(m_ClassAttribute.isNominal()){	
+	    tmp[0][y] = classDstr[0][y];
+	    tmp[1][y] = classDstr[1][y];
+	  }
+	  else{
+	    tmp[0][y] = classDstr[0][y]/(whole-uncoveredWts);
+	    tmp[1][y] = classDstr[1][y]/uncoveredWts;
+	  }
+	}
+	m_Targets.addElement(tmp);  
+		
+	defInfo = oneAntd.getInfo();
+	int numAntdsThreshold = (m_NumAntds == -1) ? Integer.MAX_VALUE : m_NumAntds;
+
+	if(Utils.eq(growData.sumOfWeights(), 0.0) || 
+	   (numUnused == 0) ||
+	   (m_Antds.size() >= numAntdsThreshold))
+	  isContinue = false;
+      }
+    }
+	
+    m_Cnsqt = ((double[][])(m_Targets.lastElement()))[0];
+    m_DefDstr = ((double[][])(m_Targets.lastElement()))[1];	
+  }
+    
+  /** 
+   * Compute the best information gain for the specified antecedent
+   *  
+   * @param instances the data based on which the infoGain is computed
+   * @param defInfo the default information of data
+   * @param antd the specific antecedent
+   * @return the data covered and not covered by the antecedent
+   */
+  private Instances[] computeInfoGain(Instances instances, double defInfo, Antd antd){	
+    Instances data = new Instances(instances);
+	
+    /* Split the data into bags.
+       The information gain of each bag is also calculated in this procedure */
+    Instances[] splitData = antd.splitData(data, defInfo); 
+    Instances[] coveredData = new Instances[2];
+	
+    /* Get the bag of data to be used for next antecedents */
+    Instances tmp1 = new Instances(data, 0);
+    Instances tmp2 = new Instances(data, 0);
+	
+    if(splitData == null)	    
+      return null;
+	
+    for(int x=0; x < (splitData.length-1); x++){
+      if(x == ((int)antd.getAttrValue()))
+	tmp1 = splitData[x];
+      else{		
+	for(int y=0; y < splitData[x].numInstances(); y++)
+	  tmp2.add(splitData[x].instance(y));		    
+      }		
+    }
+	
+    if(antd.getAttr().isNominal()){ // Nominal attributes
+      if(((NominalAntd)antd).isIn()){ // Inclusive expression
+	coveredData[0] = new Instances(tmp1);
+	coveredData[1] = new Instances(tmp2);
+      }
+      else{                           // Exclusive expression
+	coveredData[0] = new Instances(tmp2);
+	coveredData[1] = new Instances(tmp1);
+      }	
+    }
+    else{                           // Numeric attributes
+      coveredData[0] = new Instances(tmp1);
+      coveredData[1] = new Instances(tmp2);
+    }
+	
+    /* Add data with missing value */
+    for(int z=0; z<splitData[splitData.length-1].numInstances(); z++)
+      coveredData[1].add(splitData[splitData.length-1].instance(z));
+	
+    return coveredData;
+  }
+    
+  /**
+   * Prune the rule using the pruning data.
+   * The weighted average of accuracy rate/mean-squared error is 
+   * used to prune the rule.
+   *
+   * @param pruneData the pruning data used to prune the rule
+   */    
+  private void prune(Instances pruneData){
+    Instances data=new Instances(pruneData);
+    Instances otherData = new Instances(data, 0);
+    double total = data.sumOfWeights();
+	
+    /* The default accurate# and the the accuracy rate on pruning data */
+    double defAccu;
+    if(m_ClassAttribute.isNumeric())
+      defAccu = meanSquaredError(pruneData,
+				 ((double[][])m_Targets.firstElement())[0][0]);
+    else{
+      int predict = Utils.maxIndex(((double[][])m_Targets.firstElement())[0]);
+      defAccu = computeAccu(pruneData, predict)/total;
+    }
+	
+    int size=m_Antds.size();
+    if(size == 0){
+      m_Cnsqt = ((double[][])m_Targets.lastElement())[0];
+      m_DefDstr = ((double[][])m_Targets.lastElement())[1];
+      return; // Default rule before pruning
+    }
+	
+    double[] worthValue = new double[size];
+	
+    /* Calculate accuracy parameters for all the antecedents in this rule */
+    for(int x=0; x<size; x++){
+      Antd antd=(Antd)m_Antds.elementAt(x);
+      Instances newData = new Instances(data);
+      if(Utils.eq(newData.sumOfWeights(),0.0))
+	break;
+	    
+      data = new Instances(newData, newData.numInstances()); // Make data empty
+	    
+      for(int y=0; y<newData.numInstances(); y++){
+	Instance ins=newData.instance(y);
+	if(antd.isCover(ins))              // Covered by this antecedent
+	  data.add(ins);                 // Add to data for further 		
+	else
+	  otherData.add(ins);            // Not covered by this antecedent
+      }
+	    
+      double covered, other;	    
+      double[][] classes = 
+	(double[][])m_Targets.elementAt(x+1); // m_Targets has one more element
+      if(m_ClassAttribute.isNominal()){		
+	int coverClass = Utils.maxIndex(classes[0]),
+	  otherClass = Utils.maxIndex(classes[1]);
+		
+	covered = computeAccu(data, coverClass); 
+	other = computeAccu(otherData, otherClass);		
+      }
+      else{
+	double coverClass = classes[0][0],
+	  otherClass = classes[1][0];
+	covered = (data.sumOfWeights())*meanSquaredError(data, coverClass); 
+	other = (otherData.sumOfWeights())*meanSquaredError(otherData, otherClass);
+      }
+	    
+      worthValue[x] = (covered + other)/total;
+    }
+	
+    /* Prune the antecedents according to the accuracy parameters */
+    for(int z=(size-1); z > 0; z--){	
+      // Treatment to avoid precision problems
+      double valueDelta;
+      if(m_ClassAttribute.isNominal()){
+	if(Utils.sm(worthValue[z], 1.0))
+	  valueDelta = (worthValue[z] - worthValue[z-1]) / worthValue[z];
+	else
+	  valueDelta = worthValue[z] - worthValue[z-1];
+      }
+      else{
+	if(Utils.sm(worthValue[z], 1.0))
+	  valueDelta = (worthValue[z-1] - worthValue[z]) / worthValue[z];
+	else
+	  valueDelta = (worthValue[z-1] - worthValue[z]);
+      }
+	    
+      if(Utils.smOrEq(valueDelta, 0.0)){	
+	m_Antds.removeElementAt(z);
+	m_Targets.removeElementAt(z+1);
+      }
+      else  break;
+    }	
+	
+    // Check whether this rule is a default rule
+    if(m_Antds.size() == 1){
+      double valueDelta;
+      if(m_ClassAttribute.isNominal()){
+	if(Utils.sm(worthValue[0], 1.0))
+	  valueDelta = (worthValue[0] - defAccu) / worthValue[0];
+	else
+	  valueDelta = (worthValue[0] - defAccu);
+      }
+      else{
+	if(Utils.sm(worthValue[0], 1.0))
+	  valueDelta = (defAccu - worthValue[0]) / worthValue[0];
+	else
+	  valueDelta = (defAccu - worthValue[0]);
+      }
+	    
+      if(Utils.smOrEq(valueDelta, 0.0)){
+	m_Antds.removeAllElements();
+	m_Targets.removeElementAt(1);
+      }
+    }
+	
+    m_Cnsqt = ((double[][])(m_Targets.lastElement()))[0];
+    m_DefDstr = ((double[][])(m_Targets.lastElement()))[1];
+  }
+    
+  /**
+   * Private function to compute number of accurate instances
+   * based on the specified predicted class
+   * 
+   * @param data the data in question
+   * @param clas the predicted class
+   * @return the default accuracy number
+   */
+  private double computeAccu(Instances data, int clas){ 
+    double accu = 0;
+    for(int i=0; i<data.numInstances(); i++){
+      Instance inst = data.instance(i);
+      if((int)inst.classValue() == clas)
+	accu += inst.weight();
+    }
+    return accu;
+  }
+    
+
+  /**
+   * Private function to compute the squared error of
+   * the specified data and the specified mean
+   * 
+   * @param data the data in question
+   * @param mean the specified mean
+   * @return the default mean-squared error
+   */
+  private double meanSquaredError(Instances data, double mean){ 
+    if(Utils.eq(data.sumOfWeights(),0.0))
+      return 0;
+	
+    double mSqErr=0, sum = data.sumOfWeights();
+    for(int i=0; i < data.numInstances(); i++){
+      Instance datum = data.instance(i);
+      mSqErr += datum.weight()*
+	(datum.classValue() - mean)*
+	(datum.classValue() - mean);
+    }	 
+	
+    return (mSqErr / sum);
+  }
+     
+  /**
+   * Prints this rule with the specified class label
+   *
+   * @param att the string standing for attribute in the consequent of this rule
+   * @param cl the string standing for value in the consequent of this rule
+   * @return a textual description of this rule with the specified class label
+   */
+  public String toString(String att, String cl) {
+    StringBuffer text =  new StringBuffer();
+    if(m_Antds.size() > 0){
+      for(int j=0; j< (m_Antds.size()-1); j++)
+	text.append("(" + ((Antd)(m_Antds.elementAt(j))).toString()+ ") and ");
+      text.append("("+((Antd)(m_Antds.lastElement())).toString() + ")");
+    }
+    text.append(" => " + att + " = " + cl);
+	
+    return text.toString();
+  }
+    
+  /**
+   * Prints this rule
+   *
+   * @return a textual description of this rule
+   */
+  public String toString() {
+    String title = 
+      "\n\nSingle conjunctive rule learner:\n"+
+      "--------------------------------\n", body = null;
+    StringBuffer text =  new StringBuffer();
+    if(m_ClassAttribute != null){
+      if(m_ClassAttribute.isNominal()){
+	body = toString(m_ClassAttribute.name(), m_ClassAttribute.value(Utils.maxIndex(m_Cnsqt)));
+		
+	text.append("\n\nClass distributions:\nCovered by the rule:\n");
+	for(int k=0; k < m_Cnsqt.length; k++)
+	  text.append(m_ClassAttribute.value(k)+ "\t");
+	text.append('\n');
+	for(int l=0; l < m_Cnsqt.length; l++)
+	  text.append(Utils.doubleToString(m_Cnsqt[l], 6)+"\t");
+		
+	text.append("\n\nNot covered by the rule:\n");
+	for(int k=0; k < m_DefDstr.length; k++)
+	  text.append(m_ClassAttribute.value(k)+ "\t");
+	text.append('\n');
+	for(int l=0; l < m_DefDstr.length; l++)
+	  text.append(Utils.doubleToString(m_DefDstr[l], 6)+"\t");	    
+      }
+      else
+	body = toString(m_ClassAttribute.name(), Utils.doubleToString(m_Cnsqt[0], 6));
+    }
+    return (title + body + text.toString());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new ConjunctiveRule(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/DTNB.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/DTNB.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/DTNB.java	(revision 29)
@@ -0,0 +1,979 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DecisionTable.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.attributeSelection.ASEvaluation;
+import weka.attributeSelection.ASSearch;
+import weka.attributeSelection.SubsetEvaluator;
+import weka.classifiers.bayes.NaiveBayes;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.TechnicalInformation;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ *
+ <!-- globalinfo-start -->
+ * Class for building and using a decision table/naive bayes hybrid classifier. At each point in the search, the algorithm evaluates the merit of dividing the attributes into two disjoint subsets: one for the decision table, the other for naive Bayes. A forward selection search is used, where at each step, selected attributes are modeled by naive Bayes and the remainder by the decision table, and all attributes are modelled by the decision table initially. At each step, the algorithm also considers dropping an attribute entirely from the model.<br/>
+ * <br/>
+ * For more information, see: <br/>
+ * <br/>
+ * Mark Hall, Eibe Frank: Combining Naive Bayes and Decision Tables. In: Proceedings of the 21st Florida Artificial Intelligence Society Conference (FLAIRS), ???-???, 2008.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Hall2008,
+ *    author = {Mark Hall and Eibe Frank},
+ *    booktitle = {Proceedings of the 21st Florida Artificial Intelligence Society Conference (FLAIRS)},
+ *    pages = {???-???},
+ *    publisher = {AAAI press},
+ *    title = {Combining Naive Bayes and Decision Tables},
+ *    year = {2008}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Use cross validation to evaluate features.
+ *  Use number of folds = 1 for leave one out CV.
+ *  (Default = leave one out CV)</pre>
+ * 
+ * <pre> -E &lt;acc | rmse | mae | auc&gt;
+ *  Performance evaluation measure to use for selecting attributes.
+ *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+ * 
+ * <pre> -I
+ *  Use nearest neighbour instead of global table majority.</pre>
+ * 
+ * <pre> -R
+ *  Display decision table rules.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @author Eibe Frank (eibe{[at]}cs{[dot]}waikato{[dot]}ac{[dot]}nz)
+ *
+ * @version $Revision: 1.4 $
+ *
+ */
+public class DTNB extends DecisionTable {
+
+  /**
+   * The naive Bayes half of the hybrid
+   */
+  protected NaiveBayes m_NB;
+
+  /**
+   * The features used by naive Bayes
+   */
+  private int [] m_nbFeatures;
+
+  /**
+   * Percentage of the total number of features used by the decision table
+   */
+  private double m_percentUsedByDT;
+  
+  /**
+   * Percentage of the features features that were dropped entirely
+   */
+  private double m_percentDeleted;
+
+  static final long serialVersionUID = 2999557077765701326L;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  
+      "Class for building and using a decision table/naive bayes hybrid classifier. At each point "
+      + "in the search, the algorithm evaluates the merit of dividing the attributes into two disjoint "
+      + "subsets: one for the decision table, the other for naive Bayes. A forward selection search is "
+      + "used, where at each step, selected attributes are modeled by naive Bayes and the remainder "
+      + "by the decision table, and all attributes are modelled by the decision table initially. At each "
+      + "step, the algorithm also considers dropping an attribute entirely from the model.\n\n"
+      + "For more information, see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Mark Hall and Eibe Frank");
+    result.setValue(Field.TITLE, "Combining Naive Bayes and Decision Tables");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the 21st Florida Artificial Intelligence "
+                    + "Society Conference (FLAIRS)");
+    result.setValue(Field.YEAR, "2008");
+    result.setValue(Field.PAGES, "???-???");
+    result.setValue(Field.PUBLISHER, "AAAI press");
+
+    return result;
+  }
+
+  /**
+   * Calculates the accuracy on a test fold for internal cross validation
+   * of feature sets
+   *
+   * @param fold set of instances to be "left out" and classified
+   * @param fs currently selected feature set
+   * @return the accuracy for the fold
+   * @throws Exception if something goes wrong
+   */
+  double evaluateFoldCV(Instances fold, int [] fs) throws Exception {
+
+    int i;
+    int ruleCount = 0;
+    int numFold = fold.numInstances();
+    int numCl = m_theInstances.classAttribute().numValues();
+    double [][] class_distribs = new double [numFold][numCl];
+    double [] instA = new double [fs.length];
+    double [] normDist;
+    DecisionTableHashKey thekey;
+    double acc = 0.0;
+    int classI = m_theInstances.classIndex();
+    Instance inst;
+
+    if (m_classIsNominal) {
+      normDist = new double [numCl];
+    } else {
+      normDist = new double [2];
+    }
+
+    // first *remove* instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+      for (int j=0;j<fs.length;j++) {
+	if (fs[j] == classI) {
+	  instA[j] = Double.MAX_VALUE; // missing for the class
+	} else if (inst.isMissing(fs[j])) {
+	  instA[j] = Double.MAX_VALUE;
+	} else{
+	  instA[j] = inst.value(fs[j]);
+	}
+      }
+      thekey = new DecisionTableHashKey(instA);
+      if ((class_distribs[i] = (double [])m_entries.get(thekey)) == null) {
+	throw new Error("This should never happen!");
+      } else {
+	if (m_classIsNominal) {
+	  class_distribs[i][(int)inst.classValue()] -= inst.weight();
+	  inst.setWeight(-inst.weight());
+	  m_NB.updateClassifier(inst);
+	  inst.setWeight(-inst.weight());
+	} else {
+	  class_distribs[i][0] -= (inst.classValue() * inst.weight());
+	  class_distribs[i][1] -= inst.weight();
+	}
+	ruleCount++;
+      }
+      m_classPriorCounts[(int)inst.classValue()] -= 
+	inst.weight();	
+    }
+    double [] classPriors = m_classPriorCounts.clone();
+    Utils.normalize(classPriors);
+
+    // now classify instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+      System.arraycopy(class_distribs[i],0,normDist,0,normDist.length);
+      if (m_classIsNominal) {
+	boolean ok = false;
+	for (int j=0;j<normDist.length;j++) {
+	  if (Utils.gr(normDist[j],1.0)) {
+	    ok = true;
+	    break;
+	  }
+	}
+
+	if (!ok) { // majority class
+	  normDist = classPriors.clone();
+	} else {
+	  Utils.normalize(normDist);
+	}
+
+	double [] nbDist = m_NB.distributionForInstance(inst);
+
+	for (int l = 0; l < normDist.length; l++) {
+	  normDist[l] = (Math.log(normDist[l]) - Math.log(classPriors[l]));
+	  normDist[l] += Math.log(nbDist[l]);
+	}
+	normDist = Utils.logs2probs(normDist);
+	// Utils.normalize(normDist);
+
+	//	System.out.println(normDist[0] + " " + normDist[1] + " " + inst.classValue());
+
+	if (m_evaluationMeasure == EVAL_AUC) {
+	  m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, inst);
+	} else {
+	  m_evaluation.evaluateModelOnce(normDist, inst);
+	}
+	/*	} else {					
+	  normDist[(int)m_majority] = 1.0;
+	  if (m_evaluationMeasure == EVAL_AUC) {
+	    m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, inst);						
+	  } else {
+	    m_evaluation.evaluateModelOnce(normDist, inst);					
+	  }
+	} */
+      } else {
+	if (Utils.eq(normDist[1],0.0)) {
+	  double [] temp = new double[1];
+	  temp[0] = m_majority;
+	  m_evaluation.evaluateModelOnce(temp, inst);
+	} else {
+	  double [] temp = new double[1];
+	  temp[0] = normDist[0] / normDist[1];
+	  m_evaluation.evaluateModelOnce(temp, inst);
+	}
+      }
+    }
+
+    // now re-insert instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+
+      m_classPriorCounts[(int)inst.classValue()] += 
+	inst.weight();
+
+      if (m_classIsNominal) {
+	class_distribs[i][(int)inst.classValue()] += inst.weight();
+	m_NB.updateClassifier(inst);
+      } else {
+	class_distribs[i][0] += (inst.classValue() * inst.weight());
+	class_distribs[i][1] += inst.weight();
+      }
+    }
+    return acc;
+  }
+
+  /**
+   * Classifies an instance for internal leave one out cross validation
+   * of feature sets
+   *
+   * @param instance instance to be "left out" and classified
+   * @param instA feature values of the selected features for the instance
+   * @return the classification of the instance
+   * @throws Exception if something goes wrong
+   */
+  double evaluateInstanceLeaveOneOut(Instance instance, double [] instA)
+  throws Exception {
+
+    DecisionTableHashKey thekey;
+    double [] tempDist;
+    double [] normDist;
+
+    thekey = new DecisionTableHashKey(instA);
+
+    // if this one is not in the table
+    if ((tempDist = (double [])m_entries.get(thekey)) == null) {
+      throw new Error("This should never happen!");
+    } else {
+      normDist = new double [tempDist.length];
+      System.arraycopy(tempDist,0,normDist,0,tempDist.length);
+      normDist[(int)instance.classValue()] -= instance.weight();
+
+      // update the table
+      // first check to see if the class counts are all zero now
+      boolean ok = false;
+      for (int i=0;i<normDist.length;i++) {
+	if (Utils.gr(normDist[i],1.0)) {
+	  ok = true;
+	  break;
+	}
+      }
+
+      // downdate the class prior counts
+      m_classPriorCounts[(int)instance.classValue()] -= 
+	instance.weight(); 
+      double [] classPriors = m_classPriorCounts.clone();
+      Utils.normalize(classPriors);
+      if (!ok) { // majority class	
+	normDist = classPriors;
+      } else {
+	Utils.normalize(normDist);
+      }
+
+      m_classPriorCounts[(int)instance.classValue()] += 
+      instance.weight();
+
+      if (m_NB != null){
+	// downdate NaiveBayes
+
+	instance.setWeight(-instance.weight());
+	m_NB.updateClassifier(instance);
+	double [] nbDist = m_NB.distributionForInstance(instance);
+	instance.setWeight(-instance.weight());
+	m_NB.updateClassifier(instance);
+
+	for (int i = 0; i < normDist.length; i++) {
+	  normDist[i] = (Math.log(normDist[i]) - Math.log(classPriors[i]));
+	  normDist[i] += Math.log(nbDist[i]);
+	}
+	normDist = Utils.logs2probs(normDist);
+	// Utils.normalize(normDist);
+      }
+
+      if (m_evaluationMeasure == EVAL_AUC) {
+	m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, instance);						
+      } else {
+	m_evaluation.evaluateModelOnce(normDist, instance);
+      }
+      return Utils.maxIndex(normDist);
+    }
+  }
+
+  /**
+   * Sets up a dummy subset evaluator that basically just delegates
+   * evaluation to the estimatePerformance method in DecisionTable
+   */
+  protected void setUpEvaluator() throws Exception {
+    m_evaluator = new EvalWithDelete();
+    m_evaluator.buildEvaluator(m_theInstances);
+  }
+  
+  protected class EvalWithDelete extends ASEvaluation implements SubsetEvaluator {
+    
+    // holds the list of attributes that are no longer in the model at all
+    private BitSet m_deletedFromDTNB;
+    
+    public void buildEvaluator(Instances data) throws Exception {
+      m_NB = null;
+      m_deletedFromDTNB = new BitSet(data.numAttributes());
+      // System.err.println("Here");
+    }
+    
+   private int setUpForEval(BitSet subset) throws Exception {
+     
+     int fc = 0;
+     for (int jj = 0;jj < m_numAttributes; jj++) {
+	if (subset.get(jj)) {
+	  fc++;
+	}
+     }
+
+     //int [] nbFs = new int [fc];
+     //int count = 0;
+
+     for (int j = 0; j < m_numAttributes; j++) {
+	m_theInstances.attribute(j).setWeight(1.0); // reset weight
+	if (j != m_theInstances.classIndex()) {
+	  if (subset.get(j)) {
+	//    nbFs[count++] = j;
+	    m_theInstances.attribute(j).setWeight(0.0); // no influence for NB
+	  }
+	}
+     }
+     
+     // process delete set
+     for (int i = 0; i < m_numAttributes; i++) {
+	if (m_deletedFromDTNB.get(i)) {
+	   m_theInstances.attribute(i).setWeight(0.0); // no influence for NB
+	}
+     }
+     
+     if (m_NB == null) {
+	// construct naive bayes for the first time
+	m_NB = new NaiveBayes();
+	m_NB.buildClassifier(m_theInstances);
+     }
+     return fc;
+   }
+
+    public double evaluateSubset(BitSet subset) throws Exception {
+      int fc = setUpForEval(subset);
+      
+      return estimatePerformance(subset, fc);
+    }
+    
+    public double evaluateSubsetDelete(BitSet subset, int potentialDelete) throws Exception {
+      
+      int fc = setUpForEval(subset);
+      
+      // clear potentail delete for naive Bayes
+      m_theInstances.attribute(potentialDelete).setWeight(0.0);
+      //copy.clear(potentialDelete);
+      //fc--;
+      return estimatePerformance(subset, fc);
+    }
+    
+    public BitSet getDeletedList() {
+      return m_deletedFromDTNB;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+  }
+
+  protected ASSearch m_backwardWithDelete;
+
+  /**
+   * Inner class implementing a special forwards search that looks for a good
+   * split of attributes between naive Bayes and the decision table. It also
+   * considers dropping attributes entirely from the model.
+   */
+  protected class BackwardsWithDelete extends ASSearch {
+
+    public String globalInfo() {
+      return "Specialized search that performs a forward selection (naive Bayes)/"
+        + "backward elimination (decision table). Also considers dropping attributes "
+        + "entirely from the combined model.";
+    }
+
+    public String toString() {
+      return "";
+    }
+
+    public int [] search(ASEvaluation eval, Instances data)
+      	throws Exception {
+	int i;
+	double best_merit = -Double.MAX_VALUE;
+	double temp_best = 0, temp_merit = 0, temp_merit_delete = 0;
+	int temp_index=0;
+	BitSet temp_group;
+	BitSet best_group = null;
+
+	int numAttribs = data.numAttributes();
+
+	if (best_group == null) {
+	  best_group = new BitSet(numAttribs);
+	}
+
+	
+	int classIndex = data.classIndex();
+	for (i = 0; i < numAttribs; i++) {
+	  if (i != classIndex) {
+	    best_group.set(i);
+	  }
+	}
+
+	//System.err.println(best_group);
+	
+	// Evaluate the initial subset
+        //	best_merit = m_evaluator.evaluateSubset(best_group);
+        best_merit = ((SubsetEvaluator)eval).evaluateSubset(best_group);
+
+	//System.err.println(best_merit);
+
+	// main search loop
+	boolean done = false;
+	boolean addone = false;
+	boolean z;
+	boolean deleted = false;
+	while (!done) {
+	  temp_group = (BitSet)best_group.clone();
+	  temp_best = best_merit;
+	  
+	  done = true;
+	  addone = false;
+	  for (i = 0; i < numAttribs;i++) {
+	    z = ((i != classIndex) && (temp_group.get(i)));
+
+	    if (z) {
+	      // set/unset the bit
+	      temp_group.clear(i);
+
+              //	      temp_merit = m_evaluator.evaluateSubset(temp_group);
+	      temp_merit = ((SubsetEvaluator)eval).evaluateSubset(temp_group);
+              //	      temp_merit_delete = ((EvalWithDelete)m_evaluator).evaluateSubsetDelete(temp_group, i);
+	      temp_merit_delete = ((EvalWithDelete)eval).evaluateSubsetDelete(temp_group, i);
+	      boolean deleteBetter = false;
+	      //System.out.println("Merit: " + temp_merit + "\t" + "Delete merit: " + temp_merit_delete);
+	      if (temp_merit_delete >= temp_merit) {
+		temp_merit = temp_merit_delete;
+		deleteBetter = true;
+	      }
+	      
+	      z = (temp_merit >= temp_best);
+
+	      if (z) {
+		temp_best = temp_merit;
+		temp_index = i;
+		addone = true;
+		done = false;
+		if (deleteBetter) {
+		  deleted = true;
+		} else {
+		  deleted = false;
+		}
+	      }
+
+	      // unset this addition/deletion
+		temp_group.set(i);
+	    }
+	  }
+	  if (addone) {
+	    best_group.clear(temp_index);
+	    best_merit = temp_best;
+	    if (deleted) {
+              //	      ((EvalWithDelete)m_evaluator).getDeletedList().set(temp_index);
+	      ((EvalWithDelete)eval).getDeletedList().set(temp_index);
+	    }
+	    //System.err.println("----------------------");
+	    //System.err.println("Best subset: (dec table)" + best_group);
+	    //System.err.println("Best subset: (deleted)" + ((EvalWithDelete)m_evaluator).getDeletedList());
+	    //System.err.println(best_merit);
+	  }
+	}
+	return attributeList(best_group);
+      }
+      
+      /**
+       * converts a BitSet into a list of attribute indexes 
+       * @param group the BitSet to convert
+       * @return an array of attribute indexes
+       **/
+      protected int[] attributeList (BitSet group) {
+	int count = 0;
+	BitSet copy = (BitSet)group.clone();
+	
+	/* remove any that have been completely deleted from DTNB
+	BitSet deleted = ((EvalWithDelete)m_evaluator).getDeletedList();
+	for (int i = 0; i < m_numAttributes; i++) {
+	  if (deleted.get(i)) {
+	    copy.clear(i);
+	  }
+	} */
+	
+	// count how many were selected
+	for (int i = 0; i < m_numAttributes; i++) {
+	  if (copy.get(i)) {
+	    count++;
+	  }
+	}
+
+	int[] list = new int[count];
+	count = 0;
+
+	for (int i = 0; i < m_numAttributes; i++) {
+	  if (copy.get(i)) {
+	    list[count++] = i;
+	  }
+	}
+
+	return  list;
+      }
+      
+      /**
+       * Returns the revision string.
+       * 
+       * @return		the revision
+       */
+      public String getRevision() {
+        return RevisionUtils.extract("$Revision: 1.4 $");
+      }
+  }
+
+  private void setUpSearch() {
+    m_backwardWithDelete = new BackwardsWithDelete();
+  }
+  
+  /**
+   * Generates the classifier.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    m_saveMemory = false;
+
+    if (data.classAttribute().isNumeric()) {
+      throw new Exception("Can only handle nominal class!");
+    }
+
+    if (m_backwardWithDelete == null) {
+      setUpSearch();
+      m_search = m_backwardWithDelete;
+    }
+
+    /*    if (m_search != m_backwardWithDelete) {
+      m_search = m_backwardWithDelete;
+      } */
+    super.buildClassifier(data);
+
+    // new NB stuff
+
+    // delete the features used by the decision table (not the class!!)
+    for (int i = 0; i < m_theInstances.numAttributes(); i++) {
+      m_theInstances.attribute(i).setWeight(1.0); // reset all weights
+    }
+    // m_nbFeatures = new int [m_decisionFeatures.length - 1];
+     int count = 0;
+
+    for (int i = 0; i < m_decisionFeatures.length; i++) {
+      if (m_decisionFeatures[i] != m_theInstances.classIndex()) {
+	count++;
+//	m_nbFeatures[count++] = m_decisionFeatures[i];
+	m_theInstances.attribute(m_decisionFeatures[i]).setWeight(0.0); // No influence for NB
+      }
+    }
+    
+    double numDeleted = 0;
+    // remove any attributes that have been deleted completely from the DTNB
+    BitSet deleted = ((EvalWithDelete)m_evaluator).getDeletedList();
+    for (int i = 0; i < m_theInstances.numAttributes(); i++) {
+      if (deleted.get(i)) {
+	m_theInstances.attribute(i).setWeight(0.0);
+	// count--;
+	numDeleted++;
+	// System.err.println("Attribute "+i+" was eliminated completely");
+      }
+    }
+    
+    m_percentUsedByDT = (double)count / (m_theInstances.numAttributes() - 1);
+    m_percentDeleted = numDeleted / (m_theInstances.numAttributes() -1);
+
+    m_NB = new NaiveBayes();
+    m_NB.buildClassifier(m_theInstances);
+
+    m_dtInstances = new Instances(m_dtInstances, 0);
+    m_theInstances = new Instances(m_theInstances, 0);
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given 
+   * test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @exception Exception if distribution can't be computed
+   */
+  public double [] distributionForInstance(Instance instance)
+  throws Exception {
+
+    DecisionTableHashKey thekey;
+    double [] tempDist;
+    double [] normDist;
+
+    m_disTransform.input(instance);
+    m_disTransform.batchFinished();
+    instance = m_disTransform.output();
+
+    m_delTransform.input(instance);
+    m_delTransform.batchFinished();
+    Instance dtInstance = m_delTransform.output();
+
+    thekey = new DecisionTableHashKey(dtInstance, dtInstance.numAttributes(), false);
+
+    // if this one is not in the table
+    if ((tempDist = (double [])m_entries.get(thekey)) == null) {
+      if (m_useIBk) {
+	tempDist = m_ibk.distributionForInstance(dtInstance);
+      } else {  
+	// tempDist = new double [m_theInstances.classAttribute().numValues()];
+//	tempDist[(int)m_majority] = 1.0;
+	
+	tempDist = m_classPriors.clone();
+	// return tempDist; ??????
+      }
+    } else {
+      // normalise distribution
+      normDist = new double [tempDist.length];
+      System.arraycopy(tempDist,0,normDist,0,tempDist.length);
+      Utils.normalize(normDist);
+      tempDist = normDist;			
+    }
+
+    double [] nbDist = m_NB.distributionForInstance(instance);
+    for (int i = 0; i < nbDist.length; i++) {
+      tempDist[i] = (Math.log(tempDist[i]) - Math.log(m_classPriors[i]));
+      tempDist[i] += Math.log(nbDist[i]);
+
+      /*tempDist[i] *= nbDist[i];
+      tempDist[i] /= m_classPriors[i];*/
+    }
+    tempDist = Utils.logs2probs(tempDist);
+    Utils.normalize(tempDist);
+
+    return tempDist;
+  }
+
+  public String toString() {
+
+    String sS = super.toString();
+    if (m_displayRules && m_NB != null) {
+      sS += m_NB.toString();			
+    }
+    return sS;
+  }
+  
+  /**
+   * Returns the number of rules
+   * @return the number of rules
+   */
+  public double measurePercentAttsUsedByDT() {
+    return m_percentUsedByDT;
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(2);
+    newVector.addElement("measureNumRules");
+    newVector.addElement("measurePercentAttsUsedByDT");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else if (additionalMeasureName.compareToIgnoreCase("measurePercentAttsUsedByDT") == 0) {
+      return measurePercentAttsUsedByDT();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+	  + " not supported (DecisionTable)");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    result.disable(Capability.NUMERIC_CLASS);
+    result.disable(Capability.DATE_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Sets the search method to use
+   * 
+   * @param search
+   */
+  public void setSearch(ASSearch search) {
+    // Search method cannot be changed.
+    // Must be BackwardsWithDelete
+    return;
+  }
+
+  /**
+   * Gets the current search method
+   * 
+   * @return the search method used
+   */
+  public ASSearch getSearch() {
+    if (m_backwardWithDelete == null) {
+      setUpSearch();
+      //      setSearch(m_backwardWithDelete);
+      m_search = m_backwardWithDelete;
+    }
+    return m_search;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(7);
+
+    newVector.addElement(new Option(
+	"\tUse cross validation to evaluate features.\n" +
+	"\tUse number of folds = 1 for leave one out CV.\n" +
+	"\t(Default = leave one out CV)",
+	"X", 1, "-X <number of folds>"));
+
+    newVector.addElement(new Option(
+	"\tPerformance evaluation measure to use for selecting attributes.\n" +
+	"\t(Default = accuracy for discrete class and rmse for numeric class)",
+	"E", 1, "-E <acc | rmse | mae | auc>"));
+
+    newVector.addElement(new Option(
+	"\tUse nearest neighbour instead of global table majority.",
+	"I", 0, "-I"));
+
+    newVector.addElement(new Option(
+	"\tDisplay decision table rules.\n",
+	"R", 0, "-R")); 
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Use cross validation to evaluate features.
+   *  Use number of folds = 1 for leave one out CV.
+   *  (Default = leave one out CV)</pre>
+   * 
+   * <pre> -E &lt;acc | rmse | mae | auc&gt;
+   *  Performance evaluation measure to use for selecting attributes.
+   *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+   * 
+   * <pre> -I
+   *  Use nearest neighbour instead of global table majority.</pre>
+   * 
+   * <pre> -R
+   *  Display decision table rules.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String optionString;
+
+    resetOptions();
+
+    optionString = Utils.getOption('X',options);
+    if (optionString.length() != 0) {
+      setCrossVal(Integer.parseInt(optionString));
+    }
+
+    m_useIBk = Utils.getFlag('I',options);
+
+    m_displayRules = Utils.getFlag('R',options);
+
+    optionString = Utils.getOption('E', options);
+    if (optionString.length() != 0) {
+      if (optionString.equals("acc")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_ACCURACY, TAGS_EVALUATION));
+      } else if (optionString.equals("rmse")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_RMSE, TAGS_EVALUATION));
+      } else if (optionString.equals("mae")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_MAE, TAGS_EVALUATION));
+      } else if (optionString.equals("auc")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_AUC, TAGS_EVALUATION));
+      } else {
+	throw new IllegalArgumentException("Invalid evaluation measure");
+      }
+    }
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [9];
+    int current = 0;
+
+    options[current++] = "-X"; options[current++] = "" + getCrossVal();
+
+    if (m_evaluationMeasure != EVAL_DEFAULT) {
+      options[current++] = "-E";
+      switch (m_evaluationMeasure) {
+      case EVAL_ACCURACY:
+	options[current++] = "acc";
+	break;
+      case EVAL_RMSE:
+	options[current++] = "rmse";
+	break;
+      case EVAL_MAE:
+	options[current++] = "mae";
+	break;
+      case EVAL_AUC:
+	options[current++] = "auc";
+	break;
+      }
+    }
+    if (m_useIBk) {
+      options[current++] = "-I";
+    }
+    if (m_displayRules) {
+      options[current++] = "-R";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the command-line options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new DTNB(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTable.java	(revision 29)
@@ -0,0 +1,1411 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DecisionTable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.attributeSelection.ASSearch;
+import weka.attributeSelection.BestFirst;
+import weka.attributeSelection.SubsetEvaluator;
+import weka.attributeSelection.ASEvaluation;
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.lazy.IBk;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a simple decision table majority classifier.<br/>
+ * <br/>
+ * For more information see: <br/>
+ * <br/>
+ * Ron Kohavi: The Power of Decision Tables. In: 8th European Conference on Machine Learning, 174-189, 1995.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Kohavi1995,
+ *    author = {Ron Kohavi},
+ *    booktitle = {8th European Conference on Machine Learning},
+ *    pages = {174-189},
+ *    publisher = {Springer},
+ *    title = {The Power of Decision Tables},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;search method specification&gt;
+ *  Full class name of search method, followed
+ *  by its options.
+ *  eg: "weka.attributeSelection.BestFirst -D 1"
+ *  (default weka.attributeSelection.BestFirst)</pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  Use cross validation to evaluate features.
+ *  Use number of folds = 1 for leave one out CV.
+ *  (Default = leave one out CV)</pre>
+ * 
+ * <pre> -E &lt;acc | rmse | mae | auc&gt;
+ *  Performance evaluation measure to use for selecting attributes.
+ *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+ * 
+ * <pre> -I
+ *  Use nearest neighbour instead of global table majority.</pre>
+ * 
+ * <pre> -R
+ *  Display decision table rules.
+ * </pre>
+ * 
+ * <pre> 
+ * Options specific to search method weka.attributeSelection.BestFirst:
+ * </pre>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.</pre>
+ * 
+ * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+ *  Direction of search. (default = 1).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Number of non-improving nodes to
+ *  consider before terminating search.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Size of lookup cache for evaluated subsets.
+ *  Expressed as a multiple of the number of
+ *  attributes in the data set. (default = 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class DecisionTable 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, 
+             AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 2888557078165701326L;
+
+  /** The hashtable used to hold training instances */
+  protected Hashtable m_entries;
+
+  /** The class priors to use when there is no match in the table */
+  protected double [] m_classPriorCounts;
+  protected double [] m_classPriors;
+
+  /** Holds the final feature set */
+  protected int [] m_decisionFeatures;
+
+  /** Discretization filter */
+  protected Filter m_disTransform;
+
+  /** Filter used to remove columns discarded by feature selection */
+  protected Remove m_delTransform;
+
+  /** IB1 used to classify non matching instances rather than majority class */
+  protected IBk m_ibk;
+
+  /** Holds the original training instances */
+  protected Instances m_theInstances;
+
+  /** Holds the final feature selected set of instances */
+  protected Instances m_dtInstances;
+
+  /** The number of attributes in the dataset */
+  protected int m_numAttributes;
+
+  /** The number of instances in the dataset */
+  private int m_numInstances;
+
+  /** Class is nominal */
+  protected boolean m_classIsNominal;
+
+  /** Use the IBk classifier rather than majority class */
+  protected boolean m_useIBk;
+
+  /** Display Rules */
+  protected boolean m_displayRules;
+
+  /** Number of folds for cross validating feature sets */
+  private int m_CVFolds;
+
+  /** Random numbers for use in cross validation */
+  private Random m_rr;
+
+  /** Holds the majority class */
+  protected double m_majority;
+
+  /** The search method to use */
+  protected ASSearch m_search = new BestFirst();
+
+  /** Our own internal evaluator */
+  protected ASEvaluation m_evaluator;
+
+  /** The evaluation object used to evaluate subsets */
+  protected Evaluation m_evaluation;
+
+  /** default is accuracy for discrete class and RMSE for numeric class */
+  public static final int EVAL_DEFAULT = 1;
+  public static final int EVAL_ACCURACY = 2;
+  public static final int EVAL_RMSE = 3;
+  public static final int EVAL_MAE = 4;
+  public static final int EVAL_AUC = 5;
+
+  public static final Tag [] TAGS_EVALUATION = {
+    new Tag(EVAL_DEFAULT, "Default: accuracy (discrete class); RMSE (numeric class)"),
+    new Tag(EVAL_ACCURACY, "Accuracy (discrete class only"),
+    new Tag(EVAL_RMSE, "RMSE (of the class probabilities for discrete class)"),
+    new Tag(EVAL_MAE, "MAE (of the class probabilities for discrete class)"),
+    new Tag(EVAL_AUC, "AUC (area under the ROC curve - discrete class only)")
+  };
+
+  protected int m_evaluationMeasure = EVAL_DEFAULT;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  
+    "Class for building and using a simple decision table majority "
+    + "classifier.\n\n"
+    + "For more information see: \n\n"
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ron Kohavi");
+    result.setValue(Field.TITLE, "The Power of Decision Tables");
+    result.setValue(Field.BOOKTITLE, "8th European Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.PAGES, "174-189");
+    result.setValue(Field.PUBLISHER, "Springer");
+
+    return result;
+  }
+  
+  /**
+   * Inserts an instance into the hash table
+   *
+   * @param inst instance to be inserted
+   * @param instA to create the hash key from
+   * @throws Exception if the instance can't be inserted
+   */
+  private void insertIntoTable(Instance inst, double [] instA)
+  throws Exception {
+
+    double [] tempClassDist2;
+    double [] newDist;
+    DecisionTableHashKey thekey;
+
+    if (instA != null) {
+      thekey = new DecisionTableHashKey(instA);
+    } else {
+      thekey = new DecisionTableHashKey(inst, inst.numAttributes(), false);
+    }
+
+    // see if this one is already in the table
+    tempClassDist2 = (double []) m_entries.get(thekey);
+    if (tempClassDist2 == null) {
+      if (m_classIsNominal) {
+	newDist = new double [m_theInstances.classAttribute().numValues()];
+	
+	//Leplace estimation
+	for (int i = 0; i < m_theInstances.classAttribute().numValues(); i++) {
+	  newDist[i] = 1.0;
+	}
+	
+	newDist[(int)inst.classValue()] = inst.weight();
+
+	// add to the table
+	m_entries.put(thekey, newDist);
+      } else {
+	newDist = new double [2];
+	newDist[0] = inst.classValue() * inst.weight();
+	newDist[1] = inst.weight();
+
+	// add to the table
+	m_entries.put(thekey, newDist);
+      }
+    } else { 
+
+      // update the distribution for this instance
+      if (m_classIsNominal) {
+	tempClassDist2[(int)inst.classValue()]+=inst.weight();
+
+	// update the table
+	m_entries.put(thekey, tempClassDist2);
+      } else  {
+	tempClassDist2[0] += (inst.classValue() * inst.weight());
+	tempClassDist2[1] += inst.weight();
+
+	// update the table
+	m_entries.put(thekey, tempClassDist2);
+      }
+    }
+  }
+
+  /**
+   * Classifies an instance for internal leave one out cross validation
+   * of feature sets
+   *
+   * @param instance instance to be "left out" and classified
+   * @param instA feature values of the selected features for the instance
+   * @return the classification of the instance
+   * @throws Exception if something goes wrong
+   */
+  double evaluateInstanceLeaveOneOut(Instance instance, double [] instA)
+  throws Exception {
+
+    DecisionTableHashKey thekey;
+    double [] tempDist;
+    double [] normDist;
+
+    thekey = new DecisionTableHashKey(instA);
+    if (m_classIsNominal) {
+
+      // if this one is not in the table
+      if ((tempDist = (double [])m_entries.get(thekey)) == null) {
+	throw new Error("This should never happen!");
+      } else {
+	normDist = new double [tempDist.length];
+	System.arraycopy(tempDist,0,normDist,0,tempDist.length);
+	normDist[(int)instance.classValue()] -= instance.weight();
+
+	// update the table
+	// first check to see if the class counts are all zero now
+	boolean ok = false;
+	for (int i=0;i<normDist.length;i++) {
+	  if (Utils.gr(normDist[i],1.0)) {
+	    ok = true;
+	    break;
+	  }
+	}
+
+//	downdate the class prior counts
+	m_classPriorCounts[(int)instance.classValue()] -= 
+	  instance.weight();
+	double [] classPriors = m_classPriorCounts.clone();
+	Utils.normalize(classPriors);
+	if (!ok) { // majority class
+	  normDist = classPriors;
+	}
+
+	m_classPriorCounts[(int)instance.classValue()] += 
+	  instance.weight();
+
+	//if (ok) {
+	Utils.normalize(normDist);
+	if (m_evaluationMeasure == EVAL_AUC) {
+	  m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, instance);						
+	} else {
+	  m_evaluation.evaluateModelOnce(normDist, instance);
+	}
+	return Utils.maxIndex(normDist);
+	/*} else {
+	  normDist = new double [normDist.length];
+	  normDist[(int)m_majority] = 1.0;
+	  if (m_evaluationMeasure == EVAL_AUC) {
+	    m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, instance);						
+	  } else {
+	    m_evaluation.evaluateModelOnce(normDist, instance);
+	  }
+	  return m_majority;
+	} */
+      }
+      //      return Utils.maxIndex(tempDist);
+    } else {
+
+      // see if this one is already in the table
+      if ((tempDist = (double[])m_entries.get(thekey)) != null) {
+	normDist = new double [tempDist.length];
+	System.arraycopy(tempDist,0,normDist,0,tempDist.length);
+	normDist[0] -= (instance.classValue() * instance.weight());
+	normDist[1] -= instance.weight();
+	if (Utils.eq(normDist[1],0.0)) {
+	  double [] temp = new double[1];
+	  temp[0] = m_majority;
+	  m_evaluation.evaluateModelOnce(temp, instance);
+	  return m_majority;
+	} else {
+	  double [] temp = new double[1];
+	  temp[0] = normDist[0] / normDist[1];
+	  m_evaluation.evaluateModelOnce(temp, instance);
+	  return temp[0];
+	}
+      } else {
+	throw new Error("This should never happen!");
+      }
+    }
+
+    // shouldn't get here 
+    // return 0.0;
+  }
+
+  /**
+   * Calculates the accuracy on a test fold for internal cross validation
+   * of feature sets
+   *
+   * @param fold set of instances to be "left out" and classified
+   * @param fs currently selected feature set
+   * @return the accuracy for the fold
+   * @throws Exception if something goes wrong
+   */
+  double evaluateFoldCV(Instances fold, int [] fs) throws Exception {
+
+    int i;
+    int ruleCount = 0;
+    int numFold = fold.numInstances();
+    int numCl = m_theInstances.classAttribute().numValues();
+    double [][] class_distribs = new double [numFold][numCl];
+    double [] instA = new double [fs.length];
+    double [] normDist;
+    DecisionTableHashKey thekey;
+    double acc = 0.0;
+    int classI = m_theInstances.classIndex();
+    Instance inst;
+
+    if (m_classIsNominal) {
+      normDist = new double [numCl];
+    } else {
+      normDist = new double [2];
+    }
+
+    // first *remove* instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+      for (int j=0;j<fs.length;j++) {
+	if (fs[j] == classI) {
+	  instA[j] = Double.MAX_VALUE; // missing for the class
+	} else if (inst.isMissing(fs[j])) {
+	  instA[j] = Double.MAX_VALUE;
+	} else{
+	  instA[j] = inst.value(fs[j]);
+	}
+      }
+      thekey = new DecisionTableHashKey(instA);
+      if ((class_distribs[i] = (double [])m_entries.get(thekey)) == null) {
+	throw new Error("This should never happen!");
+      } else {
+	if (m_classIsNominal) {
+	  class_distribs[i][(int)inst.classValue()] -= inst.weight();
+	} else {
+	  class_distribs[i][0] -= (inst.classValue() * inst.weight());
+	  class_distribs[i][1] -= inst.weight();
+	}
+	ruleCount++;
+      }
+      m_classPriorCounts[(int)inst.classValue()] -= 
+	inst.weight();	
+    }
+    double [] classPriors = m_classPriorCounts.clone();
+    Utils.normalize(classPriors);
+
+    // now classify instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+      System.arraycopy(class_distribs[i],0,normDist,0,normDist.length);
+      if (m_classIsNominal) {
+	boolean ok = false;
+	for (int j=0;j<normDist.length;j++) {
+	  if (Utils.gr(normDist[j],1.0)) {
+	    ok = true;
+	    break;
+	  }
+	}
+
+	if (!ok) { // majority class
+	  normDist = classPriors.clone();
+	}
+
+//	if (ok) {
+	Utils.normalize(normDist);
+	if (m_evaluationMeasure == EVAL_AUC) {
+	  m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, inst);						
+	} else {
+	  m_evaluation.evaluateModelOnce(normDist, inst);
+	}
+	/*	} else {					
+	  normDist[(int)m_majority] = 1.0;
+	  if (m_evaluationMeasure == EVAL_AUC) {
+	    m_evaluation.evaluateModelOnceAndRecordPrediction(normDist, inst);						
+	  } else {
+	    m_evaluation.evaluateModelOnce(normDist, inst);					
+	  }
+	} */
+      } else {
+	if (Utils.eq(normDist[1],0.0)) {
+	  double [] temp = new double[1];
+	  temp[0] = m_majority;
+	  m_evaluation.evaluateModelOnce(temp, inst);
+	} else {
+	  double [] temp = new double[1];
+	  temp[0] = normDist[0] / normDist[1];
+	  m_evaluation.evaluateModelOnce(temp, inst);
+	}
+      }
+    }
+
+    // now re-insert instances
+    for (i=0;i<numFold;i++) {
+      inst = fold.instance(i);
+
+      m_classPriorCounts[(int)inst.classValue()] += 
+	inst.weight();
+
+      if (m_classIsNominal) {
+	class_distribs[i][(int)inst.classValue()] += inst.weight();
+      } else {
+	class_distribs[i][0] += (inst.classValue() * inst.weight());
+	class_distribs[i][1] += inst.weight();
+      }
+    }
+    return acc;
+  }
+
+
+  /**
+   * Evaluates a feature subset by cross validation
+   *
+   * @param feature_set the subset to be evaluated
+   * @param num_atts the number of attributes in the subset
+   * @return the estimated accuracy
+   * @throws Exception if subset can't be evaluated
+   */
+  protected double estimatePerformance(BitSet feature_set, int num_atts)
+  throws Exception {
+
+    m_evaluation = new Evaluation(m_theInstances);
+    int i;
+    int [] fs = new int [num_atts];
+
+    double [] instA = new double [num_atts];
+    int classI = m_theInstances.classIndex();
+
+    int index = 0;
+    for (i=0;i<m_numAttributes;i++) {
+      if (feature_set.get(i)) {
+	fs[index++] = i;
+      }
+    }
+
+    // create new hash table
+    m_entries = new Hashtable((int)(m_theInstances.numInstances() * 1.5));
+
+    // insert instances into the hash table
+    for (i=0;i<m_numInstances;i++) {
+
+      Instance inst = m_theInstances.instance(i);
+      for (int j=0;j<fs.length;j++) {
+	if (fs[j] == classI) {
+	  instA[j] = Double.MAX_VALUE; // missing for the class
+	} else if (inst.isMissing(fs[j])) {
+	  instA[j] = Double.MAX_VALUE;
+	} else {
+	  instA[j] = inst.value(fs[j]);
+	}
+      }
+      insertIntoTable(inst, instA);
+    }
+
+
+    if (m_CVFolds == 1) {
+
+      // calculate leave one out error
+      for (i=0;i<m_numInstances;i++) {
+	Instance inst = m_theInstances.instance(i);
+	for (int j=0;j<fs.length;j++) {
+	  if (fs[j] == classI) {
+	    instA[j] = Double.MAX_VALUE; // missing for the class
+	  } else if (inst.isMissing(fs[j])) {
+	    instA[j] = Double.MAX_VALUE;
+	  } else {
+	    instA[j] = inst.value(fs[j]);
+	  }
+	}
+	evaluateInstanceLeaveOneOut(inst, instA);				
+      }
+    } else {
+      m_theInstances.randomize(m_rr);
+      m_theInstances.stratify(m_CVFolds);
+
+      // calculate 10 fold cross validation error
+      for (i=0;i<m_CVFolds;i++) {
+	Instances insts = m_theInstances.testCV(m_CVFolds,i);
+	evaluateFoldCV(insts, fs);
+      }
+    }
+
+    switch (m_evaluationMeasure) {
+    case EVAL_DEFAULT:
+      if (m_classIsNominal) {
+	return m_evaluation.pctCorrect();
+      }
+      return -m_evaluation.rootMeanSquaredError();
+    case EVAL_ACCURACY:
+      return m_evaluation.pctCorrect();
+    case EVAL_RMSE:
+      return -m_evaluation.rootMeanSquaredError();
+    case EVAL_MAE:
+      return -m_evaluation.meanAbsoluteError();
+    case EVAL_AUC:
+      double [] classPriors = m_evaluation.getClassPriors();
+      Utils.normalize(classPriors);
+      double weightedAUC = 0;
+      for (i = 0; i < m_theInstances.classAttribute().numValues(); i++) {
+	double tempAUC = m_evaluation.areaUnderROC(i);
+	if (!Utils.isMissingValue(tempAUC)) {
+	  weightedAUC += (classPriors[i] * tempAUC);
+	} else {
+	  System.err.println("Undefined AUC!!");
+	}
+      }
+      return weightedAUC;
+    }
+    // shouldn't get here
+    return 0.0;
+  }
+
+  /**
+   * Returns a String representation of a feature subset
+   *
+   * @param sub BitSet representation of a subset
+   * @return String containing subset
+   */
+  private String printSub(BitSet sub) {
+
+    String s="";
+    for (int jj=0;jj<m_numAttributes;jj++) {
+      if (sub.get(jj)) {
+	s += " "+(jj+1);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Resets the options.
+   */
+  protected void resetOptions()  {
+
+    m_entries = null;
+    m_decisionFeatures = null;
+    m_useIBk = false;
+    m_CVFolds = 1;
+    m_displayRules = false;
+    m_evaluationMeasure = EVAL_DEFAULT;
+  }
+
+  /**
+   * Constructor for a DecisionTable
+   */
+  public DecisionTable() {
+
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(7);
+
+    newVector.addElement(new Option(
+	"\tFull class name of search method, followed\n"
+	+ "\tby its options.\n"
+	+ "\teg: \"weka.attributeSelection.BestFirst -D 1\"\n"
+	+ "\t(default weka.attributeSelection.BestFirst)",
+	"S", 1, "-S <search method specification>"));
+
+    newVector.addElement(new Option(
+	"\tUse cross validation to evaluate features.\n" +
+	"\tUse number of folds = 1 for leave one out CV.\n" +
+	"\t(Default = leave one out CV)",
+	"X", 1, "-X <number of folds>"));
+
+    newVector.addElement(new Option(
+	"\tPerformance evaluation measure to use for selecting attributes.\n" +
+	"\t(Default = accuracy for discrete class and rmse for numeric class)",
+	"E", 1, "-E <acc | rmse | mae | auc>"));
+
+    newVector.addElement(new Option(
+	"\tUse nearest neighbour instead of global table majority.",
+	"I", 0, "-I"));
+
+    newVector.addElement(new Option(
+	"\tDisplay decision table rules.\n",
+	"R", 0, "-R")); 
+
+    newVector.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to search method "
+	+ m_search.getClass().getName() + ":"));
+    Enumeration enu = ((OptionHandler)m_search).listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String crossValTipText() {
+    return "Sets the number of folds for cross validation (1 = leave one out).";
+  }
+
+  /**
+   * Sets the number of folds for cross validation (1 = leave one out)
+   *
+   * @param folds the number of folds
+   */
+  public void setCrossVal(int folds) {
+
+    m_CVFolds = folds;
+  }
+
+  /**
+   * Gets the number of folds for cross validation
+   *
+   * @return the number of cross validation folds
+   */
+  public int getCrossVal() {
+
+    return m_CVFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useIBkTipText() {
+    return "Sets whether IBk should be used instead of the majority class.";
+  }
+
+  /**
+   * Sets whether IBk should be used instead of the majority class
+   *
+   * @param ibk true if IBk is to be used
+   */
+  public void setUseIBk(boolean ibk) {
+
+    m_useIBk = ibk;
+  }
+
+  /**
+   * Gets whether IBk is being used instead of the majority class
+   *
+   * @return true if IBk is being used
+   */
+  public boolean getUseIBk() {
+
+    return m_useIBk;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String displayRulesTipText() {
+    return "Sets whether rules are to be printed.";
+  }
+
+  /**
+   * Sets whether rules are to be printed
+   *
+   * @param rules true if rules are to be printed
+   */
+  public void setDisplayRules(boolean rules) {
+
+    m_displayRules = rules;
+  }
+
+  /**
+   * Gets whether rules are being printed
+   *
+   * @return true if rules are being printed
+   */
+  public boolean getDisplayRules() {
+
+    return m_displayRules;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchTipText() {
+    return "The search method used to find good attribute combinations for the "
+    + "decision table.";
+  }
+  /**
+   * Sets the search method to use
+   * 
+   * @param search
+   */
+  public void setSearch(ASSearch search) {
+    m_search = search;
+  }
+
+  /**
+   * Gets the current search method
+   * 
+   * @return the search method used
+   */
+  public ASSearch getSearch() {
+    return m_search;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluationMeasureTipText() {
+    return "The measure used to evaluate the performance of attribute combinations "
+    + "used in the decision table.";
+  }
+  /**
+   * Gets the currently set performance evaluation measure used for selecting
+   * attributes for the decision table
+   * 
+   * @return the performance evaluation measure
+   */
+  public SelectedTag getEvaluationMeasure() {
+    return new SelectedTag(m_evaluationMeasure, TAGS_EVALUATION);
+  }
+
+  /**
+   * Sets the performance evaluation measure to use for selecting attributes
+   * for the decision table
+   * 
+   * @param newMethod the new performance evaluation metric to use
+   */
+  public void setEvaluationMeasure(SelectedTag newMethod) {
+    if (newMethod.getTags() == TAGS_EVALUATION) {
+      m_evaluationMeasure = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;search method specification&gt;
+   *  Full class name of search method, followed
+   *  by its options.
+   *  eg: "weka.attributeSelection.BestFirst -D 1"
+   *  (default weka.attributeSelection.BestFirst)</pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  Use cross validation to evaluate features.
+   *  Use number of folds = 1 for leave one out CV.
+   *  (Default = leave one out CV)</pre>
+   * 
+   * <pre> -E &lt;acc | rmse | mae | auc&gt;
+   *  Performance evaluation measure to use for selecting attributes.
+   *  (Default = accuracy for discrete class and rmse for numeric class)</pre>
+   * 
+   * <pre> -I
+   *  Use nearest neighbour instead of global table majority.</pre>
+   * 
+   * <pre> -R
+   *  Display decision table rules.
+   * </pre>
+   * 
+   * <pre> 
+   * Options specific to search method weka.attributeSelection.BestFirst:
+   * </pre>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.</pre>
+   * 
+   * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+   *  Direction of search. (default = 1).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Number of non-improving nodes to
+   *  consider before terminating search.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Size of lookup cache for evaluated subsets.
+   *  Expressed as a multiple of the number of
+   *  attributes in the data set. (default = 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String optionString;
+
+    resetOptions();
+
+    optionString = Utils.getOption('X',options);
+    if (optionString.length() != 0) {
+      m_CVFolds = Integer.parseInt(optionString);
+    }
+
+    m_useIBk = Utils.getFlag('I',options);
+
+    m_displayRules = Utils.getFlag('R',options);
+
+    optionString = Utils.getOption('E', options);
+    if (optionString.length() != 0) {
+      if (optionString.equals("acc")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_ACCURACY, TAGS_EVALUATION));
+      } else if (optionString.equals("rmse")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_RMSE, TAGS_EVALUATION));
+      } else if (optionString.equals("mae")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_MAE, TAGS_EVALUATION));
+      } else if (optionString.equals("auc")) {
+	setEvaluationMeasure(new SelectedTag(EVAL_AUC, TAGS_EVALUATION));
+      } else {
+	throw new IllegalArgumentException("Invalid evaluation measure");
+      }
+    }
+
+    String searchString = Utils.getOption('S', options);
+    if (searchString.length() == 0)
+      searchString = weka.attributeSelection.BestFirst.class.getName();
+    String [] searchSpec = Utils.splitOptions(searchString);
+    if (searchSpec.length == 0) {
+      throw new IllegalArgumentException("Invalid search specification string");
+    }
+    String searchName = searchSpec[0];
+    searchSpec[0] = "";
+    setSearch(ASSearch.forName(searchName, searchSpec));
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [9];
+    int current = 0;
+
+    options[current++] = "-X"; options[current++] = "" + m_CVFolds;
+
+    if (m_evaluationMeasure != EVAL_DEFAULT) {
+      options[current++] = "-E";
+      switch (m_evaluationMeasure) {
+      case EVAL_ACCURACY:
+	options[current++] = "acc";
+	break;
+      case EVAL_RMSE:
+	options[current++] = "rmse";
+	break;
+      case EVAL_MAE:
+	options[current++] = "mae";
+	break;
+      case EVAL_AUC:
+	options[current++] = "auc";
+	break;
+      }
+    }
+    if (m_useIBk) {
+      options[current++] = "-I";
+    }
+    if (m_displayRules) {
+      options[current++] = "-R";
+    }
+
+    options[current++] = "-S";
+    options[current++] = "" + getSearchSpec();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Gets the search specification string, which contains the class name of
+   * the search method and any options to it
+   *
+   * @return the search string.
+   */
+  protected String getSearchSpec() {
+
+    ASSearch s = getSearch();
+    if (s instanceof OptionHandler) {
+      return s.getClass().getName() + " "
+      + Utils.joinOptions(((OptionHandler)s).getOptions());
+    }
+    return s.getClass().getName();
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    if (m_evaluationMeasure != EVAL_ACCURACY && m_evaluationMeasure != EVAL_AUC) {
+      result.enable(Capability.NUMERIC_CLASS);
+      result.enable(Capability.DATE_CLASS);
+    }
+    
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+  
+  private class DummySubsetEvaluator extends ASEvaluation implements SubsetEvaluator {
+    /** for serialization */
+    private static final long serialVersionUID = 3927442457704974150L;
+      
+    public void buildEvaluator(Instances data) throws Exception {
+    }
+
+    public double evaluateSubset(BitSet subset) throws Exception {
+
+      int fc = 0;
+      for (int jj = 0;jj < m_numAttributes; jj++) {
+        if (subset.get(jj)) {
+          fc++;
+        }
+      }
+
+      return estimatePerformance(subset, fc);
+    }
+  }
+
+  /**
+   * Sets up a dummy subset evaluator that basically just delegates
+   * evaluation to the estimatePerformance method in DecisionTable
+   */
+  protected void setUpEvaluator() throws Exception {
+    m_evaluator = new DummySubsetEvaluator();
+  }
+
+  protected boolean m_saveMemory = true;
+  /**
+   * Generates the classifier.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    m_theInstances = new Instances(data);
+    m_theInstances.deleteWithMissingClass();
+
+    m_rr = new Random(1);
+
+    if (m_theInstances.classAttribute().isNominal())  {//	 Set up class priors
+      m_classPriorCounts = 
+	new double [data.classAttribute().numValues()];
+      Arrays.fill(m_classPriorCounts, 1.0);
+      for (int i = 0; i <data.numInstances(); i++) {
+	Instance curr = data.instance(i);
+	m_classPriorCounts[(int)curr.classValue()] += 
+	  curr.weight();
+      }
+      m_classPriors = m_classPriorCounts.clone();
+      Utils.normalize(m_classPriors);
+    }
+
+    setUpEvaluator();
+
+    if (m_theInstances.classAttribute().isNumeric()) {
+      m_disTransform = new weka.filters.unsupervised.attribute.Discretize();
+      m_classIsNominal = false;
+
+      // use binned discretisation if the class is numeric
+      ((weka.filters.unsupervised.attribute.Discretize)m_disTransform).
+      setBins(10);
+      ((weka.filters.unsupervised.attribute.Discretize)m_disTransform).
+      setInvertSelection(true);
+
+      // Discretize all attributes EXCEPT the class 
+      String rangeList = "";
+      rangeList+=(m_theInstances.classIndex()+1);
+      //System.out.println("The class col: "+m_theInstances.classIndex());
+
+      ((weka.filters.unsupervised.attribute.Discretize)m_disTransform).
+      setAttributeIndices(rangeList);
+    } else {
+      m_disTransform = new weka.filters.supervised.attribute.Discretize();
+      ((weka.filters.supervised.attribute.Discretize)m_disTransform).setUseBetterEncoding(true);
+      m_classIsNominal = true;
+    }
+
+    m_disTransform.setInputFormat(m_theInstances);
+    m_theInstances = Filter.useFilter(m_theInstances, m_disTransform);
+
+    m_numAttributes = m_theInstances.numAttributes();
+    m_numInstances = m_theInstances.numInstances();
+    m_majority = m_theInstances.meanOrMode(m_theInstances.classAttribute());
+
+    // Perform the search
+    int [] selected = m_search.search(m_evaluator, m_theInstances);
+
+    m_decisionFeatures = new int [selected.length+1];
+    System.arraycopy(selected, 0, m_decisionFeatures, 0, selected.length);
+    m_decisionFeatures[m_decisionFeatures.length-1] = m_theInstances.classIndex();
+
+    // reduce instances to selected features
+    m_delTransform = new Remove();
+    m_delTransform.setInvertSelection(true);
+
+    // set features to keep
+    m_delTransform.setAttributeIndicesArray(m_decisionFeatures); 
+    m_delTransform.setInputFormat(m_theInstances);
+    m_dtInstances = Filter.useFilter(m_theInstances, m_delTransform);
+
+    // reset the number of attributes
+    m_numAttributes = m_dtInstances.numAttributes();
+
+    // create hash table
+    m_entries = new Hashtable((int)(m_dtInstances.numInstances() * 1.5));
+
+    // insert instances into the hash table
+    for (int i = 0; i < m_numInstances; i++) {
+      Instance inst = m_dtInstances.instance(i);
+      insertIntoTable(inst, null);
+    }
+
+    // Replace the global table majority with nearest neighbour?
+    if (m_useIBk) {
+      m_ibk = new IBk();
+      m_ibk.buildClassifier(m_theInstances);
+    }
+
+    // Save memory
+    if (m_saveMemory) {
+      m_theInstances = new Instances(m_theInstances, 0);
+      m_dtInstances = new Instances(m_dtInstances, 0);
+    }
+    m_evaluation = null;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given 
+   * test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if distribution can't be computed
+   */
+  public double [] distributionForInstance(Instance instance)
+  throws Exception {
+
+    DecisionTableHashKey thekey;
+    double [] tempDist;
+    double [] normDist;
+
+    m_disTransform.input(instance);
+    m_disTransform.batchFinished();
+    instance = m_disTransform.output();
+
+    m_delTransform.input(instance);
+    m_delTransform.batchFinished();
+    instance = m_delTransform.output();
+
+    thekey = new DecisionTableHashKey(instance, instance.numAttributes(), false);
+
+    // if this one is not in the table
+    if ((tempDist = (double [])m_entries.get(thekey)) == null) {
+      if (m_useIBk) {
+	tempDist = m_ibk.distributionForInstance(instance);
+      } else {
+	if (!m_classIsNominal) {
+	  tempDist = new double[1];
+	  tempDist[0] = m_majority;
+	} else {
+	  tempDist = m_classPriors.clone();
+	  /*tempDist = new double [m_theInstances.classAttribute().numValues()];
+	  tempDist[(int)m_majority] = 1.0; */
+	}
+      }
+    } else {
+      if (!m_classIsNominal) {
+	normDist = new double[1];
+	normDist[0] = (tempDist[0] / tempDist[1]);
+	tempDist = normDist;
+      } else {
+
+	// normalise distribution
+	normDist = new double [tempDist.length];
+	System.arraycopy(tempDist,0,normDist,0,tempDist.length);
+	Utils.normalize(normDist);
+	tempDist = normDist;
+      }
+    }
+    return tempDist;
+  }
+
+  /**
+   * Returns a string description of the features selected
+   *
+   * @return a string of features
+   */
+  public String printFeatures() {
+
+    int i;
+    String s = "";
+
+    for (i=0;i<m_decisionFeatures.length;i++) {
+      if (i==0) {
+	s = ""+(m_decisionFeatures[i]+1);
+      } else {
+	s += ","+(m_decisionFeatures[i]+1);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Returns the number of rules
+   * @return the number of rules
+   */
+  public double measureNumRules() {
+    return m_entries.size();
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+	  + " not supported (DecisionTable)");
+    }
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+
+    if (m_entries == null) {
+      return "Decision Table: No model built yet.";
+    } else {
+      StringBuffer text = new StringBuffer();
+
+      text.append("Decision Table:"+
+	  "\n\nNumber of training instances: "+m_numInstances+
+	  "\nNumber of Rules : "+m_entries.size()+"\n");
+
+      if (m_useIBk) {
+	text.append("Non matches covered by IB1.\n");
+      } else {
+	text.append("Non matches covered by Majority class.\n");
+      }
+
+      text.append(m_search.toString());
+      /*text.append("Best first search for feature set,\nterminated after "+
+					m_maxStale+" non improving subsets.\n"); */
+
+      text.append("Evaluation (for feature selection): CV ");
+      if (m_CVFolds > 1) {
+	text.append("("+m_CVFolds+" fold) ");
+      } else {
+	text.append("(leave one out) ");
+      }
+      text.append("\nFeature set: "+printFeatures());
+
+      if (m_displayRules) {
+
+	// find out the max column width
+	int maxColWidth = 0;
+	for (int i=0;i<m_dtInstances.numAttributes();i++) {
+	  if (m_dtInstances.attribute(i).name().length() > maxColWidth) {
+	    maxColWidth = m_dtInstances.attribute(i).name().length();
+	  }
+
+	  if (m_classIsNominal || (i != m_dtInstances.classIndex())) {
+	    Enumeration e = m_dtInstances.attribute(i).enumerateValues();
+	    while (e.hasMoreElements()) {
+	      String ss = (String)e.nextElement();
+	      if (ss.length() > maxColWidth) {
+		maxColWidth = ss.length();
+	      }
+	    }
+	  }
+	}
+
+	text.append("\n\nRules:\n");
+	StringBuffer tm = new StringBuffer();
+	for (int i=0;i<m_dtInstances.numAttributes();i++) {
+	  if (m_dtInstances.classIndex() != i) {
+	    int d = maxColWidth - m_dtInstances.attribute(i).name().length();
+	    tm.append(m_dtInstances.attribute(i).name());
+	    for (int j=0;j<d+1;j++) {
+	      tm.append(" ");
+	    }
+	  }
+	}
+	tm.append(m_dtInstances.attribute(m_dtInstances.classIndex()).name()+"  ");
+
+	for (int i=0;i<tm.length()+10;i++) {
+	  text.append("=");
+	}
+	text.append("\n");
+	text.append(tm);
+	text.append("\n");
+	for (int i=0;i<tm.length()+10;i++) {
+	  text.append("=");
+	}
+	text.append("\n");
+
+	Enumeration e = m_entries.keys();
+	while (e.hasMoreElements()) {
+	  DecisionTableHashKey tt = (DecisionTableHashKey)e.nextElement();
+	  text.append(tt.toString(m_dtInstances,maxColWidth));
+	  double [] ClassDist = (double []) m_entries.get(tt);
+
+	  if (m_classIsNominal) {
+	    int m = Utils.maxIndex(ClassDist);
+	    try {
+	      text.append(m_dtInstances.classAttribute().value(m)+"\n");
+	    } catch (Exception ee) {
+	      System.out.println(ee.getMessage());
+	    }
+	  } else {
+	    text.append((ClassDist[0] / ClassDist[1])+"\n");
+	  }
+	}
+
+	for (int i=0;i<tm.length()+10;i++) {
+	  text.append("=");
+	}
+	text.append("\n");
+	text.append("\n");
+      }
+      return text.toString();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the command-line options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new DecisionTable(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTableHashKey.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTableHashKey.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/DecisionTableHashKey.java	(revision 29)
@@ -0,0 +1,207 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DecisionTableHashKey.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.classifiers.rules;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Class providing hash table keys for DecisionTable
+ */
+public class DecisionTableHashKey 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5674163500154964602L;
+
+  /** Array of attribute values for an instance */
+  private double [] attributes;
+
+  /** True for an index if the corresponding attribute value is missing. */
+  private boolean [] missing;
+
+  /** The key */
+  private int key;
+
+  /**
+   * Constructor for a hashKey
+   *
+   * @param t an instance from which to generate a key
+   * @param numAtts the number of attributes
+   * @param ignoreClass if true treat the class as a normal attribute
+   * @throws Exception if something goes wrong
+   */
+  public DecisionTableHashKey(Instance t, int numAtts, boolean ignoreClass) throws Exception {
+
+    int i;
+    int cindex = t.classIndex();
+
+    key = -999;
+    attributes = new double [numAtts];
+    missing = new boolean [numAtts];
+    for (i=0;i<numAtts;i++) {
+      if (i == cindex && !ignoreClass) {
+        missing[i] = true;
+      } else {
+        if ((missing[i] = t.isMissing(i)) == false) {
+          attributes[i] = t.value(i);
+        }
+      }
+    }
+  }
+
+  /**
+   * Convert a hash entry to a string
+   *
+   * @param t the set of instances
+   * @param maxColWidth width to make the fields
+   * @return string representation of the hash entry
+   */
+  public String toString(Instances t, int maxColWidth) {
+
+    int i;
+    int cindex = t.classIndex();
+    StringBuffer text = new StringBuffer();
+
+    for (i=0;i<attributes.length;i++) {
+      if (i != cindex) {
+        if (missing[i]) {
+          text.append("?");
+          for (int j=0;j<maxColWidth;j++) {
+            text.append(" ");
+          }
+        } else {
+          String ss = t.attribute(i).value((int)attributes[i]);
+          StringBuffer sb = new StringBuffer(ss);
+
+          for (int j=0;j < (maxColWidth-ss.length()+1); j++) {
+            sb.append(" ");
+          }
+          text.append(sb);
+        }
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Constructor for a hashKey
+   *
+   * @param t an array of feature values
+   */
+  public DecisionTableHashKey(double [] t) {
+
+    int i;
+    int l = t.length;
+
+    key = -999;
+    attributes = new double [l];
+    missing = new boolean [l];
+    for (i=0;i<l;i++) {
+      if (t[i] == Double.MAX_VALUE) {
+        missing[i] = true;
+      } else {
+        missing[i] = false;
+        attributes[i] = t[i];
+      }
+    }
+  }
+
+  /**
+   * Calculates a hash code
+   *
+   * @return the hash code as an integer
+   */
+  public int hashCode() {
+
+    int hv = 0;
+
+    if (key != -999)
+      return key;
+    for (int i=0;i<attributes.length;i++) {
+      if (missing[i]) {
+        hv += (i*13);
+      } else {
+        hv += (i * 5 * (attributes[i]+1));
+      }
+    }
+    if (key == -999) {
+      key = hv;
+    }
+    return hv;
+  }
+
+  /**
+   * Tests if two instances are equal
+   *
+   * @param b a key to compare with
+   * @return true if both objects are equal
+   */
+  public boolean equals(Object b) {
+
+    if ((b == null) || !(b.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    boolean ok = true;
+    boolean l;
+    if (b instanceof DecisionTableHashKey) {
+      DecisionTableHashKey n = (DecisionTableHashKey)b;
+      for (int i=0;i<attributes.length;i++) {
+        l = n.missing[i];
+        if (missing[i] || l) {
+          if ((missing[i] && !l) || (!missing[i] && l)) {
+            ok = false;
+            break;
+          }
+        } else {
+          if (attributes[i] != n.attributes[i]) {
+            ok = false;
+            break;
+          }
+        }
+      }
+    } else {
+      return false;
+    }
+    return ok;
+  }
+
+  /**
+   * Prints the hash code
+   */
+  public void print_hash_code() {
+    System.out.println("Hash val: "+hashCode());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/FURIA.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/FURIA.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/FURIA.java	(revision 29)
@@ -0,0 +1,2444 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FURIA.java
+ *    Copyright (C) 2008,2009 Jens Christian Huehn
+ *    
+ *    (based upon) JRip.java
+ *    Copyright (C) 2001 Xin Xu, Eibe Frank
+ */
+
+package weka.classifiers.rules;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+import weka.classifiers.AbstractClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Copyable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * FURIA: Fuzzy Unordered Rule Induction Algorithm<br/>
+ * <br/>
+ * Details please see:<br/>
+ * <br/>
+ * Jens Christian Huehn, Eyke Huellermeier (2009). FURIA: An Algorithm for Unordered Fuzzy Rule Induction. Data Mining and Knowledge Discovery..<br/>
+ * <br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Huehn2009,
+ *    author = {Jens Christian Huehn and Eyke Huellermeier},
+ *    journal = {Data Mining and Knowledge Discovery},
+ *    title = {FURIA: An Algorithm for Unordered Fuzzy Rule Induction},
+ *    year = {2009}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;number of folds&gt;
+ *  Set number of folds for REP
+ *  One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -N &lt;min. weights&gt;
+ *  Set the minimal weights of instances
+ *  within a split.
+ *  (default 2.0)</pre>
+ * 
+ * <pre> -O &lt;number of runs&gt;
+ *  Set the number of runs of
+ *  optimizations. (Default: 2)</pre>
+ * 
+ * <pre> -D
+ *  Set whether turn on the
+ *  debug mode (Default: false)</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  The seed of randomization
+ *  (Default: 1)</pre>
+ * 
+ * <pre> -E
+ *  Whether NOT check the error rate&gt;=0.5
+ *  in stopping criteria  (default: check)</pre>
+ * 
+ * <pre> -s
+ *  The action performed for uncovered instances.
+ *  (default: use stretching)</pre>
+ * 
+ * <pre> -p
+ *  The T-norm used as fuzzy AND-operator.
+ *  (default: Product T-norm)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Jens Christian H&uuml;hn (huehn@gmx.net)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5964 $
+ */
+public class FURIA 
+  extends AbstractClassifier 
+  implements OptionHandler, AdditionalMeasureProducer, WeightedInstancesHandler,
+             TechnicalInformationHandler {    
+
+  /** for serialization */
+  static final long serialVersionUID = -6589312996832147161L;
+
+  /** The limit of description length surplus in ruleset generation */
+  private static double MAX_DL_SURPLUS = 64.0;
+
+  /** The class attribute of the data*/
+  private Attribute m_Class; 
+
+  /** The ruleset */
+  private FastVector m_Ruleset;
+
+  /** The predicted class distribution */
+  private FastVector m_Distributions;
+
+  /** Runs of optimizations */
+  private int m_Optimizations = 2;
+
+  /** Random object used in this class */
+  private Random m_Random = null;
+
+  /** # of all the possible conditions in a rule */
+  private double m_Total = 0;
+
+  /** The seed to perform randomization */
+  private long m_Seed = 1;
+
+  /** The number of folds to split data into Grow and Prune for IREP */
+  private int m_Folds = 3;
+
+  /** The minimal number of instance weights within a split*/
+  private double m_MinNo = 2.0;
+
+  /** Whether in a debug mode */
+  private boolean m_Debug = false;
+
+  /** Whether check the error rate >= 0.5 in stopping criteria */
+  private boolean m_CheckErr = true;
+
+  /** The class distribution of the training data*/
+  private double[] aprioriDistribution;
+
+  /** The RuleStats for the ruleset of each class value */
+  private FastVector m_RulesetStats;
+
+
+  /** What to do if instance is uncovered */
+  private int m_uncovAction  = UNCOVACTION_STRETCH;
+
+  /** An uncovered instance is covered using rule stretching. */
+  private static final int UNCOVACTION_STRETCH     = 0;
+
+  /** An uncovered instance is classified according to the training data class distribution. */
+  private static final int UNCOVACTION_APRIORI     = 1;
+  
+  /** An uncovered instance is not classified at all. */
+  private static final int UNCOVACTION_REJECT      = 2;
+  
+  /** The tags explaining the uncovered action.  */
+  private static final Tag [] TAGS_UNCOVACTION = {
+    new Tag(UNCOVACTION_STRETCH, "Apply rule stretching (standard)"),
+    new Tag(UNCOVACTION_APRIORI, "Vote for the most frequent class"),
+    new Tag(UNCOVACTION_REJECT, "Reject the decision and abstain")
+  };
+
+
+  /** Whether using product T-norm (or else min T-norm) */
+  private int m_tNorm  = TNORM_PROD;
+
+  /** The Product T-Norm flag. */
+  private static final int TNORM_PROD     = 0;
+
+  /** The Minimum T-Norm flag. */
+  private static final int TNORM_MIN     = 1;  
+
+  /** The tags describing the T-norms */
+  private static final Tag [] TAGS_TNORM = {
+    new Tag(TNORM_PROD, "Product T-Norm (standard)"),
+    new Tag(TNORM_MIN, "Minimum T-Norm")
+  };
+
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "FURIA: Fuzzy Unordered Rule Induction Algorithm\n\n"
+    + "Details please see:\n\n"
+    + getTechnicalInformation().toString() + "\n\n"; 
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Jens Christian Huehn and Eyke Huellermeier");
+    result.setValue(Field.TITLE, "FURIA: An Algorithm for Unordered Fuzzy Rule Induction");
+    result.setValue(Field.YEAR, "2009");
+    result.setValue(Field.JOURNAL, "Data Mining and Knowledge Discovery");
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * Valid options are: <p>
+   *  
+   * -F number <br>
+   * The number of folds for reduced error pruning. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   * 
+   * -N number <br>
+   * The minimal weights of instances within a split.
+   * (Default: 2) <p>
+   *    
+   * -O number <br>
+   * Set the number of runs of optimizations. (Default: 2)<p>
+   *
+   * -D <br>
+   * Whether turn on the debug mode
+   *
+   * -S number <br>
+   * The seed of randomization used in FURIA.(Default: 1)<p>
+   *
+   * -E <br>
+   * Whether NOT check the error rate >= 0.5 in stopping criteria.
+   * (default: check)<p> 
+   *
+   * -s <br>
+   *  The action performed for uncovered instances.
+   *  (default: use rule stretching)<p>
+   *  
+   * -p <br>
+   *  The T-Norm used as fuzzy AND-operator.
+   *  (default: Product T-Norm)<p>
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(8);
+    newVector.addElement(new Option("\tSet number of folds for REP\n" +
+	"\tOne fold is used as pruning set.\n" +
+	"\t(default 3)","F", 1, "-F <number of folds>"));
+
+    newVector.addElement(new Option("\tSet the minimal weights of instances\n" +
+	"\twithin a split.\n" +
+	"\t(default 2.0)","N", 1, "-N <min. weights>"));
+
+    newVector.addElement(new Option("\tSet the number of runs of\n"+
+	"\toptimizations. (Default: 2)", "O",
+	1,"-O <number of runs>"));
+
+    newVector.addElement(new Option("\tSet whether turn on the\n"+
+	"\tdebug mode (Default: false)", "D",
+	0,"-D"));
+
+    newVector.addElement(new Option("\tThe seed of randomization\n"+
+	"\t(Default: 1)", "S",
+	1,"-S <seed>"));
+
+    newVector.addElement(new Option("\tWhether NOT check the error rate>=0.5\n"
+	+"\tin stopping criteria "
+	+"\t(default: check)", "E", 
+	0, "-E")); 
+
+    newVector.addElement(new Option("\tThe action performed for uncovered instances.\n"
+	+"\t(default: use stretching)", "s", 
+	1, "-s"));
+
+    newVector.addElement(new Option("\tThe T-norm used as fuzzy AND-operator.\n"
+	+"\t(default: Product T-norm)", "p", 
+	1, "-p"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;number of folds&gt;
+   *  Set number of folds for REP
+   *  One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -N &lt;min. weights&gt;
+   *  Set the minimal weights of instances
+   *  within a split.
+   *  (default 2.0)</pre>
+   * 
+   * <pre> -O &lt;number of runs&gt;
+   *  Set the number of runs of
+   *  optimizations. (Default: 2)</pre>
+   * 
+   * <pre> -D
+   *  Set whether turn on the
+   *  debug mode (Default: false)</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  The seed of randomization
+   *  (Default: 1)</pre>
+   * 
+   * <pre> -E
+   *  Whether NOT check the error rate&gt;=0.5
+   *  in stopping criteria  (default: check)</pre>
+   * 
+   * <pre> -s
+   *  The action performed for uncovered instances.
+   *  (default: use stretching)</pre>
+   * 
+   * <pre> -p
+   *  The T-norm used as fuzzy AND-operator.
+   *  (default: Product T-norm)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String numFoldsString = Utils.getOption('F', options);
+    if (numFoldsString.length() != 0) 
+      m_Folds = Integer.parseInt(numFoldsString);
+    else 
+      m_Folds = 3;   
+
+    String minNoString = Utils.getOption('N', options);
+    if (minNoString.length() != 0) 
+      m_MinNo = Double.parseDouble(minNoString);
+    else 
+      m_MinNo = 2.0;
+
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0)
+      m_Seed = Long.parseLong(seedString);
+    else 
+      m_Seed = 1;
+
+    String runString = Utils.getOption('O', options);
+    if (runString.length() != 0)
+      m_Optimizations = Integer.parseInt(runString);
+    else 
+      m_Optimizations = 2;
+
+    String tNormString = Utils.getOption('p', options);
+    if (tNormString.length() != 0)
+      m_tNorm = Integer.parseInt(tNormString);
+    else 
+      m_tNorm = TNORM_PROD;
+
+    String uncovActionString = Utils.getOption('s', options);
+    if (uncovActionString.length() != 0)
+      m_uncovAction = Integer.parseInt(uncovActionString);
+    else 
+      m_uncovAction = UNCOVACTION_STRETCH;
+
+    m_Debug = Utils.getFlag('D', options);
+
+    m_CheckErr = !Utils.getFlag('E', options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [14];
+    int current = 0;
+    options[current++] = "-F"; options[current++] = "" + m_Folds;
+    options[current++] = "-N"; options[current++] = "" + m_MinNo;
+    options[current++] = "-O"; options[current++] = "" + m_Optimizations;
+    options[current++] = "-S"; options[current++] = "" + m_Seed;
+    options[current++] = "-p"; options[current++] = "" + m_tNorm;
+    options[current++] = "-s"; options[current++] = "" + m_uncovAction;
+
+    if(m_Debug)
+      options[current++] = "-D";
+
+    if(!m_CheckErr)
+      options[current++] = "-E";
+
+    while(current < options.length)
+      options[current++] = "";
+
+    return options;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) 
+      return m_Ruleset.size();
+    else 
+      throw new IllegalArgumentException(additionalMeasureName+" not supported (FURIA)");
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTipText() {
+    return "Determines the amount of data used for pruning. One fold is used for "
+    + "pruning, the rest for growing the rules.";
+  }
+
+  /**
+   * Sets the number of folds to use
+   * 
+   * @param fold the number of folds
+   */
+  public void setFolds(int fold) { 
+    m_Folds = fold; 
+  }
+
+  /**
+   * Gets the number of folds
+   * 
+   * @return the number of folds
+   */
+  public int getFolds(){ 
+    return m_Folds; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNoTipText() {
+    return "The minimum total weight of the instances in a rule.";
+  }
+
+  /**
+   * Sets the minimum total weight of the instances in a rule
+   * 
+   * @param m the minimum total weight of the instances in a rule
+   */
+  public void setMinNo(double m) {
+    m_MinNo = m;
+  }
+
+  /**
+   * Gets the minimum total weight of the instances in a rule
+   * 
+   * @return the minimum total weight of the instances in a rule
+   */
+  public double getMinNo(){ 
+    return m_MinNo;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data.";
+  }
+
+  /**
+   * Sets the seed value to use in randomizing the data
+   * 
+   * @param s the new seed value
+   */
+  public void setSeed(long s) {
+    m_Seed = s;
+  }
+
+  /**
+   * Gets the current seed value to use in randomizing the data
+   * 
+   * @return the seed value
+   */
+  public long getSeed(){
+    return m_Seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String optimizationsTipText() {
+    return "The number of optimization runs.";
+  }
+
+  /**
+   * Sets the number of optimization runs
+   * 
+   * @param run the number of optimization runs
+   */
+  public void setOptimizations(int run) {
+    m_Optimizations = run;
+  }
+
+  /**
+   * Gets the the number of optimization runs
+   * 
+   * @return the number of optimization runs
+   */
+  public int getOptimizations() {
+    return m_Optimizations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Whether debug information is output to the console.";
+  }
+
+  /**
+   * Sets whether debug information is output to the console
+   * 
+   * @param d whether debug information is output to the console
+   */
+  public void setDebug(boolean d) {
+    m_Debug = d;
+  }
+
+  /**
+   * Gets whether debug information is output to the console
+   * 
+   * @return whether debug information is output to the console
+   */
+  public boolean getDebug(){
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String checkErrorRateTipText() {
+    return "Whether check for error rate >= 1/2 is included" +
+    " in stopping criterion.";
+  }
+
+  /**
+   * Sets whether to check for error rate is in stopping criterion
+   * 
+   * @param d whether to check for error rate is in stopping criterion
+   */
+  public void setCheckErrorRate(boolean d) { 
+    m_CheckErr = d;
+  }
+
+  /**
+   * Gets whether to check for error rate is in stopping criterion
+   * 
+   * @return true if checking for error rate is in stopping criterion
+   */
+  public boolean getCheckErrorRate(){ 
+    return m_CheckErr; 
+  } 
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String uncovActionTipText() {
+    return "Selet the action that is performed for uncovered instances.";
+  }
+
+  /**
+   * Gets the action that is performed for uncovered instances.
+   * It can be UNCOVACTION_STRETCH, UNCOVACTION_APRIORI or
+   * UNCOVACTION_REJECT.
+   * @return the current TNorm.
+   */
+  public SelectedTag getUncovAction() {
+    return new SelectedTag(m_uncovAction, TAGS_UNCOVACTION);
+  }
+
+  /**
+   * Sets the action that is performed for uncovered instances.   
+   * It can be UNCOVACTION_STRETCH, UNCOVACTION_APRIORI or
+   * UNCOVACTION_REJECT.
+   * @param newUncovAction the new action.
+   */
+  public void setUncovAction(SelectedTag newUncovAction) {
+    if (newUncovAction.getTags() == TAGS_UNCOVACTION) {
+      m_uncovAction = newUncovAction.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String TNormTipText() {
+    return "Choose the T-Norm that is used as fuzzy AND-operator.";
+  }
+
+  /**
+   * Gets the TNorm used. Will be either TNORM_PROD or TNORM_MIN.
+   *
+   * @return the current TNorm.
+   */
+  public SelectedTag getTNorm() {
+    return new SelectedTag(m_tNorm, TAGS_TNORM);
+  }
+
+  /**
+   * Sets the TNorm used. Will be either TNORM_PROD or TNORM_MIN.
+   *
+   * @param newTNorm the new TNorm.
+   */
+  public void setTNorm(SelectedTag newTNorm) {
+    if (newTNorm.getTags() == TAGS_TNORM) {
+      m_tNorm = newTNorm.getSelectedTag().getID();
+    }
+  }
+
+
+  /** 
+   * Get the ruleset generated by FURIA 
+   *
+   * @return the ruleset
+   */
+  public FastVector getRuleset(){ return m_Ruleset; }
+
+  /** 
+   * Get the statistics of the ruleset in the given position
+   *
+   * @param pos the position of the stats, assuming correct
+   * @return the statistics of the ruleset in the given position
+   */
+  public RuleStats getRuleStats(int pos) {
+    return (RuleStats)m_RulesetStats.elementAt(pos);
+  }
+
+  /** 
+   * The single antecedent in the rule, which is composed of an attribute and 
+   * the corresponding value.  There are two inherited classes, namely NumericAntd
+   * and NominalAntd in which the attributes are numeric and nominal respectively.
+   */    
+  protected abstract class Antd 
+  implements WeightedInstancesHandler, Copyable, Serializable {
+
+    /** The attribute of the antecedent */
+    public Attribute att;
+
+    /** The attribute value of the antecedent.  
+       For numeric attribute, value is either 0(1st bag) or 1(2nd bag) */
+    public double value; 
+
+    /** The maximum infoGain achieved by this antecedent test 
+     * in the growing data */
+    protected double maxInfoGain;
+
+    /** The accurate rate of this antecedent test on the growing data */
+    protected double accuRate;
+
+    /** The coverage of this antecedent in the growing data */
+    protected double cover;
+
+    /** The accurate data for this antecedent in the growing data */
+    protected double accu;
+
+
+    /** Confidence / weight of this rule for the rule stretching procedure that
+     * is returned when this is the last antecedent of the rule.  */
+    double weightOfTheRuleWhenItIsPrunedAfterThisAntecedent = 0;
+
+    /** Confidence / weight of this antecedent.  */
+    public double m_confidence = 0.0;
+
+    /** 
+     * Constructor
+     */
+    public Antd(Attribute a){
+      att=a;
+      value=Double.NaN; 
+      maxInfoGain = 0;
+      accuRate = Double.NaN;
+      cover = Double.NaN;
+      accu = Double.NaN;
+    }
+
+    /* The abstract members for inheritance */
+    public abstract Instances[] splitData(Instances data, double defAcRt, 
+	double cla);
+    public abstract double covers(Instance inst);
+    public abstract String toString();
+
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public abstract Object copy(); 
+
+    /* Get functions of this antecedent */
+    public Attribute getAttr(){ return att; }
+    public double getAttrValue(){ return value; }
+    public double getMaxInfoGain(){ return maxInfoGain; }
+    public double getAccuRate(){ return accuRate; } 
+    public double getAccu(){ return accu; } 
+    public double getCover(){ return cover; } 
+  }
+
+  /** 
+   * The antecedent with numeric attribute
+   */
+  public class 
+  NumericAntd extends Antd {
+
+    /** for serialization */
+    static final long serialVersionUID = 5699457269983735442L;
+
+    /** The split point for this numeric antecedent */
+    public double splitPoint;
+
+    /** The edge point for the fuzzy set of this numeric antecedent */
+    public double supportBound; 
+    
+    /** A flag determining whether this antecedent was successfully fuzzified yet*/
+    public boolean fuzzyYet = false;
+
+
+    /** 
+     * Constructor
+     */
+    public NumericAntd(Attribute a){ 
+      super(a);
+      splitPoint = Double.NaN;
+      supportBound = Double.NaN;
+    }    
+
+    /** 
+     * Get split point of this numeric antecedent
+     * 
+     * @return the split point of this numeric antecedent
+     */
+    public double getSplitPoint(){ 
+      return splitPoint;
+    }
+
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public Object copy(){      
+      NumericAntd na = new NumericAntd(getAttr());
+      na.m_confidence = m_confidence;
+      na.value = this.value;
+      na.splitPoint = this.splitPoint;
+      na.supportBound = this.supportBound;
+      na.fuzzyYet = this.fuzzyYet;
+      return na;
+    }
+
+
+
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into two bags according 
+     * to the information gain of the numeric attribute value
+     * The maximum infoGain is also calculated.  
+     * 
+     * @param insts the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances insts, double defAcRt, 
+	double cl){
+      Instances data = insts;
+      int total=data.numInstances();// Total number of instances without 
+      // missing value for att
+
+      int split=1;                  // Current split position
+      int prev=0;                   // Previous split position
+      int finalSplit=split;         // Final split position
+      maxInfoGain = 0;
+      value = 0;	
+
+      double fstCover=0, sndCover=0, fstAccu=0, sndAccu=0;
+
+      data.sort(att);
+      // Find the las instance without missing value 
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst = data.instance(x);
+	if(inst.isMissing(att)){
+	  total = x;
+	  break;
+	}
+
+	sndCover += inst.weight();
+	if(Utils.eq(inst.classValue(), cl))
+	  sndAccu += inst.weight();		
+      }	    
+
+      if(total == 0) return null; // Data all missing for the attribute
+      splitPoint = data.instance(total-1).value(att);	
+
+      for(; split <= total; split++){
+	if((split == total) ||
+	    (data.instance(split).value(att) > // Can't split within
+	    data.instance(prev).value(att))){ // same value	    
+
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    fstCover += inst.weight(); 
+	    if(Utils.eq(data.instance(y).classValue(), cl)){
+	      fstAccu += inst.weight();  // First bag positive# ++
+	    }	     		   
+	  }
+
+	  double fstAccuRate = (fstAccu+1.0)/(fstCover+1.0),
+	  sndAccuRate = (sndAccu+1.0)/(sndCover+1.0);
+
+	  /* Which bag has higher information gain? */
+	  boolean isFirst; 
+	  double fstInfoGain, sndInfoGain;
+	  double accRate, infoGain, coverage, accurate;
+
+	  fstInfoGain = 
+	    //Utils.eq(defAcRt, 1.0) ? 
+	    //fstAccu/(double)numConds : 
+	    fstAccu*(Utils.log2(fstAccuRate)-Utils.log2(defAcRt));
+
+	  sndInfoGain = 
+	    //Utils.eq(defAcRt, 1.0) ? 
+	    //sndAccu/(double)numConds : 
+	    sndAccu*(Utils.log2(sndAccuRate)-Utils.log2(defAcRt));
+
+	  if(fstInfoGain > sndInfoGain){
+	    isFirst = true;
+	    infoGain = fstInfoGain;
+	    accRate = fstAccuRate;
+	    accurate = fstAccu;
+	    coverage = fstCover;
+	  }
+	  else{
+	    isFirst = false;
+	    infoGain = sndInfoGain;
+	    accRate = sndAccuRate;
+	    accurate = sndAccu;
+	    coverage = sndCover;
+	  }
+
+	  /* Check whether so far the max infoGain */
+	  if(infoGain > maxInfoGain){
+	    splitPoint = data.instance(prev).value(att);
+	    value = (isFirst) ? 0 : 1;
+	    accuRate = accRate;
+	    accu = accurate;
+	    cover = coverage;
+	    maxInfoGain = infoGain;
+	    finalSplit = (isFirst) ? split : prev;
+	  }
+
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    sndCover -= inst.weight(); 
+	    if(Utils.eq(data.instance(y).classValue(), cl)){
+	      sndAccu -= inst.weight();  // Second bag positive# --
+	    }	     		   
+	  }		    
+	  prev=split;
+	}
+      }
+
+      /* Split the data */
+      Instances[] splitData = new Instances[2];
+      splitData[0] = new Instances(data, 0, finalSplit);
+      splitData[1] = new Instances(data, finalSplit, total-finalSplit);
+
+      return splitData;
+    }
+
+
+    /**
+     * The degree of coverage for the instance given that antecedent
+     * 
+     * @param inst the instance in question
+     * @return the numeric value indicating the membership of the instance 
+     *         for this antecedent
+     */
+    public double covers(Instance inst){
+      double isCover=0;
+      if(!inst.isMissing(att)){
+	if((int)value == 0){ // First bag
+	  if(inst.value(att) <= splitPoint)
+	    isCover=1;
+	  else if(fuzzyYet && (inst.value(att) > splitPoint) && (inst.value(att) < supportBound )) 
+	    isCover= 1-((inst.value(att) - splitPoint)/(supportBound-splitPoint));
+	}else{ 
+	  if(inst.value(att) >= splitPoint) // Second bag
+	    isCover=1;
+	  else if(fuzzyYet && inst.value(att) < splitPoint && (inst.value(att) > supportBound )) 
+	    isCover= 1-((splitPoint - inst.value(att)) /(splitPoint-supportBound));
+	}
+      }
+
+      return isCover;
+    }
+
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      if (value == 0){
+	if (fuzzyYet){
+	  return (att.name() + " in [-inf, -inf, " + Utils.doubleToString(splitPoint, 6) + ", " + Utils.doubleToString(supportBound, 6) + "]");
+	}
+	return (att.name() + " in [-inf, " + Utils.doubleToString(splitPoint, 6) + "]");
+      }else{
+	if (fuzzyYet){
+	  return (att.name() + " in [" + Utils.doubleToString(supportBound, 6) + ", " + Utils.doubleToString(splitPoint, 6) + ", inf, inf]");
+	}
+	return (att.name() + " in [" + Utils.doubleToString(splitPoint, 6) + ", inf]");
+      }
+
+    }
+
+  }
+
+
+  /** 
+   * The antecedent with nominal attribute
+   */
+  protected class NominalAntd 
+  extends Antd{
+
+    /** for serialization */
+    static final long serialVersionUID = -9102297038837585135L;
+
+    /* The parameters of infoGain calculated for each attribute value
+     * in the growing data */
+    private double[] accurate;
+    private double[] coverage;
+
+    /** 
+     * Constructor
+     */
+    public NominalAntd(Attribute a){ 
+      super(a);    
+      int bag = att.numValues();
+      accurate = new double[bag];
+      coverage = new double[bag];
+    }   
+
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public Object copy(){
+      Antd antec = new NominalAntd(getAttr());
+      antec.m_confidence = m_confidence;
+      antec.value = this.value;
+      return antec;	    
+    }
+
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into bags according 
+     * to the nominal attribute value
+     * The infoGain for each bag is also calculated.  
+     * 
+     * @param data the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances data, double defAcRt, 
+	double cl){
+      int bag = att.numValues();
+      Instances[] splitData = new Instances[bag];
+
+      for(int x=0; x<bag; x++){
+	splitData[x] = new Instances(data, data.numInstances());
+	accurate[x] = 0;
+	coverage[x] = 0;
+      }
+
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst=data.instance(x);
+	if(!inst.isMissing(att)){
+	  int v = (int)inst.value(att);
+	  splitData[v].add(inst);
+	  coverage[v] += inst.weight();
+	  if((int)inst.classValue() == (int)cl)
+	    accurate[v] += inst.weight();
+	}
+      }
+
+      for(int x=0; x<bag; x++){
+	double t = coverage[x]+1.0;
+	double p = accurate[x] + 1.0;		
+	double infoGain = 
+	  //Utils.eq(defAcRt, 1.0) ? 
+	  //accurate[x]/(double)numConds : 
+	  accurate[x]*(Utils.log2(p/t)-Utils.log2(defAcRt));
+
+	if(infoGain > maxInfoGain){
+	  maxInfoGain = infoGain;
+	  cover = coverage[x];
+	  accu = accurate[x];
+	  accuRate = p/t;
+	  value = (double)x;
+	}
+      }
+
+      return splitData;
+    }
+
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is
+     *         covered by this antecedent
+     */
+    public double covers(Instance inst){
+      double isCover=0;
+      if(!inst.isMissing(att)){
+	if((int)inst.value(att) == (int)value)
+	  isCover=1;	    
+      }
+      return isCover;
+    }
+
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      return (att.name() + " = " +att.value((int)value));
+    } 
+  }
+
+
+  /**
+   * This class implements a single rule that predicts specified class.  
+   *
+   * A rule consists of antecedents "AND"ed together and the consequent 
+   * (class value) for the classification.  
+   * In this class, the Information Gain (p*[log(p/t) - log(P/T)]) is used to
+   * select an antecedent and Reduced Error Prunning (REP) with the metric
+   * of accuracy rate p/(p+n) or (TP+TN)/(P+N) is used to prune the rule. 
+   */    
+  public class RipperRule 
+  extends Rule{
+
+    /** for serialization */
+    static final long serialVersionUID = -2410020717305262952L;
+
+    /** The internal representation of the class label to be predicted */
+    double m_Consequent = -1;	
+
+    /** The vector of antecedents of this rule*/
+    public FastVector m_Antds = null;
+
+    /** Constructor */
+    public RipperRule(){    
+      m_Antds = new FastVector();	
+    }
+
+    /**
+     * Sets the internal representation of the class label to be predicted
+     * 
+     * @param cl the internal representation of the class label to be predicted
+     */
+    public void setConsequent(double cl) {
+      m_Consequent = cl; 
+    }
+
+    /**
+     * Gets the internal representation of the class label to be predicted
+     * 
+     * @return the internal representation of the class label to be predicted
+     */
+    public double getConsequent() { 
+      return m_Consequent; 
+    }
+
+    /**
+     * Get a shallow copy of this rule
+     *
+     * @return the copy
+     */
+    public Object copy(){
+      RipperRule copy = new RipperRule();
+      copy.setConsequent(getConsequent());
+      copy.m_Antds = (FastVector)this.m_Antds.copyElements();
+      return copy;
+    }
+
+
+
+    /**
+     * The degree of coverage instance covered by this rule
+     * 
+     * @param datum the instance in question
+     * @return the degree to which the instance 
+     *         is covered by this rule
+     */
+    public double coverageDegree(Instance datum){
+      double coverage = 1;
+
+      for(int i=0; i<m_Antds.size(); i++){
+	Antd antd = (Antd)m_Antds.elementAt(i);
+	if(m_tNorm == TNORM_PROD){
+	  // Product T-Norm
+	  if (antd instanceof NumericAntd)
+	    coverage *= ((NumericAntd)antd).covers(datum);
+	  else
+	    coverage *= antd.covers(datum);
+	}else{
+	  // Min T-Norm
+	  if (antd instanceof NumericAntd)
+	    coverage = Math.min(coverage, ((NumericAntd)antd).covers(datum));
+	  else
+	    coverage = Math.min(coverage, antd.covers(datum));
+	}
+
+      }
+
+      return coverage;
+    } 
+
+    /**
+     * Whether the instance covered by this rule
+     * 
+     * @param datum the instance in question
+     * @return the boolean value indicating whether the instance 
+     *         is covered by this rule
+     */
+    public boolean covers(Instance datum){ 
+      if (coverageDegree(datum) == 0){
+	return false;
+      }else{
+	return true;
+      }
+    }       
+
+    /**
+     * Whether this rule has antecedents, i.e. whether it is a default rule
+     * 
+     * @return the boolean value indicating whether the rule has antecedents
+     */
+    public boolean hasAntds(){
+      if (m_Antds == null)
+	return false;
+      else
+	return (m_Antds.size() > 0);
+    }      
+
+    /** 
+     * the number of antecedents of the rule
+     *
+     * @return the size of this rule
+     */
+    public double size(){ return (double)m_Antds.size(); }		
+
+
+    /**
+     * Private function to compute default number of accurate instances
+     * in the specified data for the consequent of the rule
+     * 
+     * @param data the data in question
+     * @return the default accuracy number
+     */
+    private double computeDefAccu(Instances data){ 
+      double defAccu=0;
+      for(int i=0; i<data.numInstances(); i++){
+	Instance inst = data.instance(i);
+	if((int)inst.classValue() == (int)m_Consequent)
+	  defAccu += inst.weight();
+      }
+      return defAccu;
+    }
+
+
+    /**
+     * Build one rule using the growing data
+     *
+     * @param data the growing data used to build the rule
+     * @throws Exception if the consequent is not set yet
+     */    
+    public void grow(Instances data) throws Exception {
+      if(m_Consequent == -1)
+	throw new Exception(" Consequent not set yet.");
+
+      Instances growData = data;	         
+      double sumOfWeights = growData.sumOfWeights();
+      if(!Utils.gr(sumOfWeights, 0.0))
+	return;
+
+      /* Compute the default accurate rate of the growing data */
+      double defAccu = computeDefAccu(growData);
+      double defAcRt = (defAccu+1.0)/(sumOfWeights+1.0); 
+
+      /* Keep the record of which attributes have already been used*/    
+      boolean[] used=new boolean [growData.numAttributes()];
+      for (int k=0; k<used.length; k++)
+	used[k]=false;
+      int numUnused=used.length;
+
+      // If there are already antecedents existing
+      for(int j=0; j < m_Antds.size(); j++){
+	Antd antdj = (Antd)m_Antds.elementAt(j);
+	if(!antdj.getAttr().isNumeric()){ 
+	  used[antdj.getAttr().index()]=true;
+	  numUnused--;
+	} 
+      }	    
+
+      double maxInfoGain;	    
+      while (Utils.gr(growData.numInstances(), 0.0) && 
+	  (numUnused > 0) 
+	  && Utils.sm(defAcRt, 1.0)
+      ){   
+
+	// We require that infoGain be positive
+	/*if(numAntds == originalSize)
+	  maxInfoGain = 0.0; // At least one condition allowed
+	  else
+	  maxInfoGain = Utils.eq(defAcRt, 1.0) ? 
+	  defAccu/(double)numAntds : 0.0; */
+	maxInfoGain = 0.0; 
+
+	/* Build a list of antecedents */
+	Antd oneAntd=null;
+	Instances coverData = null;
+	Enumeration enumAttr=growData.enumerateAttributes();	      
+
+	/* Build one condition based on all attributes not used yet*/
+	while (enumAttr.hasMoreElements()){
+	  Attribute att= (Attribute)(enumAttr.nextElement());
+
+	  if(m_Debug)
+	    System.err.println("\nOne condition: size = " 
+		+ growData.sumOfWeights());
+
+	  Antd antd =null;	
+	  if(att.isNumeric())
+	    antd = new NumericAntd(att);
+	  else
+	    antd = new NominalAntd(att);
+
+	  if(!used[att.index()]){
+	    /* Compute the best information gain for each attribute,
+	       it's stored in the antecedent formed by this attribute.
+	       This procedure returns the data covered by the antecedent*/
+	    Instances coveredData = computeInfoGain(growData, defAcRt,
+		antd);
+	    if(coveredData != null){
+	      double infoGain = antd.getMaxInfoGain();      
+	      if(m_Debug)
+		System.err.println("Test of \'"+antd.toString()+
+		    "\': infoGain = "+
+		    infoGain + " | Accuracy = " +
+		    antd.getAccuRate()+
+		    "="+antd.getAccu()
+		    +"/"+antd.getCover()+
+		    " def. accuracy: "+defAcRt);
+
+	      if(infoGain > maxInfoGain){         
+		oneAntd=antd;
+		coverData = coveredData;  
+		maxInfoGain = infoGain;
+	      }		    
+	    }
+	  }
+	}
+
+	if(oneAntd == null) break; // Cannot find antds		
+	if(Utils.sm(oneAntd.getAccu(), m_MinNo)) break;// Too low coverage
+
+	//Numeric attributes can be used more than once
+	if(!oneAntd.getAttr().isNumeric()){ 
+	  used[oneAntd.getAttr().index()]=true;
+	  numUnused--;
+	}
+
+	m_Antds.addElement(oneAntd);
+
+
+	growData = coverData;// Grow data size is shrinking 	
+	defAcRt = oneAntd.getAccuRate();
+      }
+    }
+
+
+    /** 
+     * Compute the best information gain for the specified antecedent
+     *  
+     * @param instances the data based on which the infoGain is computed
+     * @param defAcRt the default accuracy rate of data
+     * @param antd the specific antecedent
+     * @return the data covered by the antecedent
+     */
+    private Instances computeInfoGain(Instances instances, double defAcRt, 
+	Antd antd){
+      Instances data = instances;
+
+      /* Split the data into bags.
+	 The information gain of each bag is also calculated in this procedure */
+      Instances[] splitData = antd.splitData(data, defAcRt, 
+	  m_Consequent); 
+
+      /* Get the bag of data to be used for next antecedents */
+      if(splitData != null)
+	return splitData[(int)antd.getAttrValue()];
+      else return null;
+    }
+
+    /**
+     * Prune all the possible final sequences of the rule using the 
+     * pruning data.  The measure used to prune the rule is based on
+     * flag given.
+     *
+     * @param pruneData the pruning data used to prune the rule
+     * @param useWhole flag to indicate whether use the error rate of
+     *                 the whole pruning data instead of the data covered
+     */    
+    public void prune(Instances pruneData, boolean useWhole){
+      Instances data = pruneData;
+
+      double total = data.sumOfWeights();
+      if(!Utils.gr(total, 0.0))
+	return;
+
+      /* The default accurate # and rate on pruning data */
+      double defAccu=computeDefAccu(data);
+
+      if(m_Debug)	
+	System.err.println("Pruning with " + defAccu + 
+	    " positive data out of " + total +
+	" instances");	
+
+      int size=m_Antds.size();
+      if(size == 0) return; // Default rule before pruning
+
+      double[] worthRt = new double[size];
+      double[] coverage = new double[size];
+      double[] worthValue = new double[size];
+      for(int w=0; w<size; w++){
+	worthRt[w]=coverage[w]=worthValue[w]=0.0;
+      }
+
+      /* Calculate accuracy parameters for all the antecedents in this rule */
+      double tn = 0.0; // True negative if useWhole
+      for(int x=0; x<size; x++){
+	Antd antd=(Antd)m_Antds.elementAt(x);
+	Instances newData = data;
+	data = new Instances(newData, 0); // Make data empty
+
+	for(int y=0; y<newData.numInstances(); y++){
+	  Instance ins=newData.instance(y);
+
+	  if(antd.covers(ins)>0){   // Covered by this antecedent
+	    coverage[x] += ins.weight();
+	    data.add(ins);                 // Add to data for further pruning
+	    if((int)ins.classValue() == (int)m_Consequent) // Accurate prediction
+	      worthValue[x] += ins.weight();
+	  }
+	  else if(useWhole){ // Not covered
+	    if((int)ins.classValue() != (int)m_Consequent)
+	      tn += ins.weight();
+	  }			
+	}
+
+	if(useWhole){
+	  worthValue[x] += tn;
+	  worthRt[x] = worthValue[x] / total;
+	}
+	else // Note if coverage is 0, accuracy is 0.5
+	  worthRt[x] = (worthValue[x]+1.0)/(coverage[x]+2.0);
+      }
+
+      double maxValue = (defAccu+1.0)/(total+2.0);
+      int maxIndex = -1;
+      for(int i=0; i<worthValue.length; i++){
+	if(m_Debug){
+	  double denom = useWhole ? total : coverage[i];
+	  System.err.println(i+"(useAccuray? "+!useWhole+"): "
+	      + worthRt[i] + 
+	      "="+worthValue[i]+
+	      "/"+denom);
+	}
+	if(worthRt[i] > maxValue){ // Prefer to the 
+	  maxValue = worthRt[i]; // shorter rule
+	  maxIndex = i;
+	}
+      }
+
+      if (maxIndex==-1) return;
+
+      /* Prune the antecedents according to the accuracy parameters */
+      for(int z=size-1;z>maxIndex;z--)
+	m_Antds.removeElementAt(z);       
+    }
+
+    /**
+     * Prints this rule
+     *
+     * @param classAttr the class attribute in the data
+     * @return a textual description of this rule
+     */
+    public String toString(Attribute classAttr) {
+      StringBuffer text =  new StringBuffer();
+      if(m_Antds.size() > 0){
+	for(int j=0; j< (m_Antds.size()-1); j++)
+	  text.append("(" + ((Antd)(m_Antds.elementAt(j))).toString()+ ") and ");
+	text.append("("+((Antd)(m_Antds.lastElement())).toString() + ")");
+      }
+      text.append(" => " + classAttr.name() +
+	  "=" + classAttr.value((int)m_Consequent));
+
+      return text.toString();
+    }
+
+    /**
+     * The fuzzification procedure
+     * @param data training data
+     * @param allWeightsAreOne flag whether all instances have weight 1. If this is the case branch-and-bound is possible for speed-up.
+     */
+    public void fuzzify(Instances data, boolean allWeightsAreOne){
+      // Determine whether there are numeric antecedents that can be fuzzified.
+      if (m_Antds == null) return;
+      int numNumericAntds = 0;
+      for (int i = 0; i < m_Antds.size(); i++){
+	if (m_Antds.elementAt(i) instanceof NumericAntd)
+	  numNumericAntds++;
+      }
+      if (numNumericAntds == 0)
+	return;
+
+      double maxPurity = Double.NEGATIVE_INFINITY;
+      boolean[] finishedAntecedents = new boolean[m_Antds.size()];
+      int numFinishedAntecedents = 0;
+
+      // Loop until all antecdents have been fuzzified
+      while (numFinishedAntecedents<m_Antds.size()){	
+	double maxPurityOfAllAntecedents = Double.NEGATIVE_INFINITY;
+	int bestAntecedentsIndex = -1;
+	double bestSupportBoundForAllAntecedents = Double.NaN;
+
+	Instances relevantData = new Instances(data,0);
+	for (int j = 0; j < m_Antds.size(); j++){
+	  if(finishedAntecedents[j]) continue; 
+
+	  relevantData = new Instances (data);
+	  /*
+	   * Remove instances which are not relevant, because they are not covered
+	   * by the _other_ antecedents.
+	   */
+	  for (int k = 0; k < m_Antds.size(); k++){
+	    if (k==j) continue;
+	    Antd exclusionAntd = ((Antd)m_Antds.elementAt(k));
+	    for (int y = 0; y < relevantData.numInstances(); y++){
+	      if (exclusionAntd.covers(relevantData.instance(y)) == 0){
+		relevantData.delete(y--);
+	      }
+	    }
+	  }
+
+	  // test whether this antecedent is numeric and whether there is data for making it fuzzy
+	  if (relevantData.attribute(((Antd)m_Antds.elementAt(j)).att.index()).isNumeric() && relevantData.numInstances()>0){
+	    // Get a working copy of this antecedent
+	    NumericAntd currentAntd = (NumericAntd) ((NumericAntd) m_Antds.elementAt(j)).copy();
+	    currentAntd.fuzzyYet=true;
+
+	    relevantData.deleteWithMissing(currentAntd.att.index());
+
+	    double sumOfWeights = relevantData.sumOfWeights();
+	    if(!Utils.gr(sumOfWeights, 0.0))
+	      return;
+
+	    relevantData.sort(currentAntd.att.index());
+
+	    double maxPurityForThisAntecedent = 0;
+	    double bestFoundSupportBound = Double.NaN;
+
+	    double lastAccu = 0;
+	    double lastCover = 0;
+	    // Test all possible edge points
+	    if (currentAntd.value == 0){
+	      for (int k = 1; k < relevantData.numInstances(); k++){
+		// break the loop if there is no gain (only works when all instances have weight 1)
+		if ((lastAccu+(relevantData.numInstances()-k-1))/(lastCover+(relevantData.numInstances()-k-1)) < maxPurityForThisAntecedent && allWeightsAreOne){
+		  break;
+		}
+
+		// Bag 1 
+		if (currentAntd.splitPoint < relevantData.instance(k).value(currentAntd.att.index())                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
+		    && relevantData.instance(k).value(currentAntd.att.index()) != relevantData.instance(k-1).value(currentAntd.att.index())){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
+		  currentAntd.supportBound = relevantData.instance(k).value(currentAntd.att.index());                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
+
+		  // Calculate the purity of this fuzzification
+		  double[] accuArray = new double[relevantData.numInstances()];                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
+		  double[] coverArray =  new double[relevantData.numInstances()];                       
+		  for (int i = 0; i < relevantData.numInstances(); i++){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
+		    coverArray[i] = relevantData.instance(i).weight();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
+		    double coverValue = currentAntd.covers(relevantData.instance(i));
+		    if (coverArray[i] >= coverValue*relevantData.instance(i).weight()){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+		      coverArray[i] = coverValue*relevantData.instance(i).weight();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
+		      if (relevantData.instance(i).classValue() == m_Consequent){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
+			accuArray[i] = coverValue*relevantData.instance(i).weight();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
+		      }                 
+		    }                   
+		  }                     
+
+		  // Test whether this fuzzification is the best one for this antecedent.
+		  // Keep it if this is the case.
+		  double purity = (Utils.sum(accuArray)) / (Utils.sum(coverArray));                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
+		  if (purity >= maxPurityForThisAntecedent){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+		    maxPurityForThisAntecedent =purity;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+		    bestFoundSupportBound =  currentAntd.supportBound;     
+		  }           
+		  lastAccu = Utils.sum(accuArray);
+		  lastCover = Utils.sum(coverArray);
+		}                           
+	      }                             
+	    }else{                          
+	      for (int k = relevantData.numInstances()-2; k >=0; k--){
+		// break the loop if there is no gain (only works when all instances have weight 1)
+		if ((lastAccu+(k))/(lastCover+(k)) < maxPurityForThisAntecedent && allWeightsAreOne){
+		  break;
+		}
+
+		//Bag 2                     
+		if (currentAntd.splitPoint > relevantData.instance(k).value(currentAntd.att.index())                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
+		    && relevantData.instance(k).value(currentAntd.att.index()) != relevantData.instance(k+1).value(currentAntd.att.index())){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
+		  currentAntd.supportBound = relevantData.instance(k).value(currentAntd.att.index());                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
+
+		  // Calculate the purity of this fuzzification
+		  double[] accuArray = new double[relevantData.numInstances()];
+		  double[] coverArray =  new double[relevantData.numInstances()];
+		  for (int i = 0; i < relevantData.numInstances(); i++){
+		    coverArray[i] = relevantData.instance(i).weight();
+		    double coverValue = currentAntd.covers(relevantData.instance(i));
+		    if (coverArray[i] >= coverValue*relevantData.instance(i).weight()){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+		      coverArray[i] = coverValue*relevantData.instance(i).weight();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
+		      if (relevantData.instance(i).classValue() == m_Consequent){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
+			accuArray[i] = coverValue*relevantData.instance(i).weight();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
+		      }                 
+		    }                   
+		  }                     
+
+		  // Test whether this fuzzification is the best one for this antecedent.
+		  // Keep it if this is the case.
+		  double purity = (Utils.sum(accuArray)) / (Utils.sum(coverArray));                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
+		  if (purity >= maxPurityForThisAntecedent){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+		    maxPurityForThisAntecedent =purity;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+		    bestFoundSupportBound =  currentAntd.supportBound;    
+		  }
+		  lastAccu = Utils.sum(accuArray);
+		  lastCover = Utils.sum(coverArray);
+		}                           
+	      }                             
+
+	    }             
+
+	    // Test whether the best fuzzification for this antecedent is the best one of all
+	    // antecedents considered so far.
+	    // Keep it if this is the case.
+	    if (maxPurityForThisAntecedent>maxPurityOfAllAntecedents){
+	      bestAntecedentsIndex = j;
+	      bestSupportBoundForAllAntecedents = bestFoundSupportBound;
+	      maxPurityOfAllAntecedents = maxPurityForThisAntecedent;
+	    }
+	  }else{
+	    // Deal with a nominal antecedent. 
+	    // Since there is no fuzzification it is already finished.
+	    finishedAntecedents[j] = true;
+	    numFinishedAntecedents++;
+	    continue;
+	  }
+	}
+
+	// Make the fuzzification step for the current antecedent real. 
+	if (maxPurity <= maxPurityOfAllAntecedents){
+	  if (Double.isNaN(bestSupportBoundForAllAntecedents)){
+	    ((NumericAntd)m_Antds.elementAt(bestAntecedentsIndex)).supportBound =  ((NumericAntd)m_Antds.elementAt(bestAntecedentsIndex)).splitPoint;
+	  }else{
+	    ((NumericAntd)m_Antds.elementAt(bestAntecedentsIndex)).supportBound = bestSupportBoundForAllAntecedents;
+	    ((NumericAntd)m_Antds.elementAt(bestAntecedentsIndex)).fuzzyYet = true;
+	  }  
+	  maxPurity = maxPurityOfAllAntecedents;
+	}
+	finishedAntecedents[bestAntecedentsIndex] = true;
+	numFinishedAntecedents++;
+      }
+
+    }
+
+    /**
+     * Calculation of the rule weights / confidences for all beginning rule stumps.
+     * @param data The training data
+     */
+    public void calculateConfidences(Instances data) {
+      RipperRule tempRule = (RipperRule) this.copy();
+
+      while(tempRule.hasAntds()){
+	double acc = 0;
+	double cov = 0;
+	for (int i = 0; i < data.numInstances(); i++){
+	  double membershipValue = tempRule.coverageDegree(data.instance(i)) * data.instance(i).weight();
+	  cov += membershipValue;
+	  if (m_Consequent == data.instance(i).classValue()){
+	    acc += membershipValue;
+	  }
+	}
+
+	// m-estimate
+	double m = 2.0;
+	((Antd)this.m_Antds.elementAt((int)tempRule.size()-1)).m_confidence = 
+	  (acc+m*(aprioriDistribution[(int)m_Consequent]/
+	      Utils.sum(aprioriDistribution))) / (cov+m);
+	tempRule.m_Antds.removeElementAt(tempRule.m_Antds.size()-1);
+      }
+    }
+
+
+    /**
+     * Get the rule confidence.
+     * @return rule confidence / weight
+     */
+    public double getConfidence(){
+      if (!hasAntds()) 
+	return Double.NaN;
+      return ((Antd)m_Antds.lastElement()).m_confidence;
+    }
+
+    /**
+     * 
+     */
+    public String getRevision() {
+      return "1.0";
+    }
+
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(m_Folds);
+
+    return result;
+  }
+
+  /**
+   * Builds the FURIA rule-based model 
+   *
+   * @param instances the training data
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {  
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    // Learn the apriori distribution for later
+    aprioriDistribution = new double[instances.classAttribute().numValues()];
+    boolean allWeightsAreOne = true;
+    for (int i = 0 ; i < instances.numInstances(); i++){
+      aprioriDistribution[(int)instances.instance(i).classValue()]+=instances.instance(i).weight();
+      if (allWeightsAreOne && instances.instance(i).weight() != 1.0){
+	allWeightsAreOne = false;
+	break;
+      }
+    }
+
+
+    m_Random = instances.getRandomNumberGenerator(m_Seed); 
+    m_Total = RuleStats.numAllConditions(instances);
+    if(m_Debug)
+      System.err.println("Number of all possible conditions = "+m_Total);
+
+    Instances data = new Instances(instances);
+
+    m_Class = data.classAttribute();	
+    m_Ruleset = new FastVector();
+    m_RulesetStats = new FastVector();
+    m_Distributions = new FastVector();
+
+
+    // Learn a rule set for each single class
+    oneClass:	
+      for(int y=0; y < data.numClasses(); y++){ // For each class	      
+
+	double classIndex = (double)y;
+	if(m_Debug){
+	  int ci = (int)classIndex;
+	  System.err.println("\n\nClass "+m_Class.value(ci)+"("+ci+"): "
+	      + aprioriDistribution[y] + "instances\n"+
+	  "=====================================\n");
+	}
+
+	if(Utils.eq(aprioriDistribution[y],0.0)) // No data for this class
+	  continue oneClass;		
+
+	// The expected FP/err is the proportion of the class    
+	double expFPRate = (aprioriDistribution[y] / Utils.sum(aprioriDistribution));
+
+
+	double classYWeights = 0, totalWeights = 0;
+	for(int j=0; j < data.numInstances(); j++){
+	  Instance datum = data.instance(j);
+	  totalWeights += datum.weight();
+	  if((int)datum.classValue() == y){
+	    classYWeights += datum.weight();
+	  }	          
+	}	
+
+	// DL of default rule, no theory DL, only data DL
+	double defDL;
+	if(classYWeights > 0)
+	  defDL = RuleStats.dataDL(expFPRate, 
+	      0.0,
+	      totalWeights,
+	      0.0,
+	      classYWeights);	    
+	else
+	  continue oneClass; // Subsumed by previous rules
+
+
+
+	if(Double.isNaN(defDL) || Double.isInfinite(defDL))
+	  throw new Exception("Should never happen: "+
+	  "defDL NaN or infinite!");
+	if(m_Debug)
+	  System.err.println("The default DL = "+defDL);
+
+	rulesetForOneClass(expFPRate, data, classIndex, defDL);
+      }
+
+    // Remove redundant antecedents
+    for(int z=0; z < m_Ruleset.size(); z++){
+      RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
+      for(int j = 0; j < rule.m_Antds.size(); j++){
+	Antd outerAntd = (Antd)rule.m_Antds.elementAt(j);
+	for (int k = j+1; k < rule.m_Antds.size(); k++){
+	  Antd innerAntd = (Antd)rule.m_Antds.elementAt(k);  
+	  if (outerAntd.att.index() == innerAntd.att.index() && outerAntd.value==innerAntd.value){
+	    rule.m_Antds.setElementAt(rule.m_Antds.elementAt(k), j);
+	    rule.m_Antds.removeElementAt(k--);
+	  }
+	}
+      }
+    } 
+
+
+    // Fuzzify all rules
+    for(int z=0; z < m_RulesetStats.size(); z++){
+      RuleStats oneClass = (RuleStats)m_RulesetStats.elementAt(z);
+      for(int xyz=0; xyz < oneClass.getRulesetSize(); xyz++){
+	RipperRule rule = (RipperRule)((FastVector)oneClass.getRuleset()).elementAt(xyz);
+
+	// do the fuzzification for all known antecedents 
+	rule.fuzzify(data, allWeightsAreOne);
+
+	double[] classDist = oneClass.getDistributions(xyz);
+	// Check for sum=0, because otherwise it does not work
+	if (Utils.sum(classDist)>0) Utils.normalize(classDist);
+	if(classDist != null)
+	  m_Distributions.addElement(classDist);
+      }	
+    }
+
+
+    // if there was some problem during fuzzification, set the support bound
+    // to the trivial fuzzification position
+    for(int z=0; z < m_Ruleset.size(); z++){
+      RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
+      for(int j = 0; j < rule.m_Antds.size(); j++){
+	Antd antd = (Antd)rule.m_Antds.elementAt(j);
+	if (antd instanceof NumericAntd) {
+	  NumericAntd numAntd = (NumericAntd) antd;
+
+
+	  if (!numAntd.fuzzyYet){
+	    for (int i = 0; i < data.numInstances(); i++){
+	      if ((numAntd.value == 1 && 
+		  numAntd.splitPoint > data.instance(i).value(numAntd.att.index()) &&
+		  (numAntd.supportBound < data.instance(i).value(numAntd.att.index()) ||
+		      !numAntd.fuzzyYet)
+	      )
+	      ||
+	      (numAntd.value == 0 && 
+		  numAntd.splitPoint < data.instance(i).value(numAntd.att.index()) &&
+		  (numAntd.supportBound > data.instance(i).value(numAntd.att.index()) ||
+		      !numAntd.fuzzyYet)
+	      )
+	      ){
+		numAntd.supportBound = data.instance(i).value(numAntd.att.index());
+		numAntd.fuzzyYet = true;
+	      }
+	    }
+
+	  }	  
+	}
+      }
+    }
+
+    //Determine confidences
+    for(int z=0; z < m_Ruleset.size(); z++){
+      RipperRule rule = (RipperRule)m_Ruleset.elementAt(z);
+      rule.calculateConfidences(data);
+    }
+  }
+
+  /**
+   * Classify the test instance with the rule learner and provide
+   * the class distributions 
+   *
+   * @param datum the instance to be classified
+   * @return the distribution
+   * @throws Exception 
+   */
+
+  public double[] distributionForInstance(Instance datum) throws Exception{ 
+    //test for multiple overlap of rules
+    double[] rulesCoveringForEachClass = new double[datum.numClasses()];
+    for(int i=0; i < m_Ruleset.size(); i++){
+      RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);
+
+      /* In case that one class does not contain any instances (e.g. in UCI-dataset glass), 
+       * a default rule assigns all instances to the other class. Such a rule may be ignored here.
+       */
+      if (!rule.hasAntds()) 
+	continue;
+
+
+      // Calculate the maximum degree of coverage
+      if(rule.covers(datum)){
+	rulesCoveringForEachClass[(int)rule.m_Consequent] += rule.coverageDegree(datum) * rule.getConfidence();
+      }
+
+    }
+
+
+    // If no rule covered the example, then maybe start the rule stretching
+    if (Utils.sum(rulesCoveringForEachClass)==0){
+
+      // If rule stretching is not allowed,  
+      // return either the apriori prediction
+      if (m_uncovAction == UNCOVACTION_APRIORI){
+	rulesCoveringForEachClass = aprioriDistribution;
+	if (Utils.sum(rulesCoveringForEachClass)>0)
+	  Utils.normalize(rulesCoveringForEachClass);
+	return rulesCoveringForEachClass;
+      }
+      // or abstain from that decision at all.
+      if (m_uncovAction == UNCOVACTION_REJECT)
+	return rulesCoveringForEachClass;
+
+      // Copy the ruleset as backup
+      FastVector origRuleset = (FastVector) m_Ruleset.copyElements();
+
+      // Find for every rule the first antecedent that does not
+      // cover the given instance. 
+      rulesCoveringForEachClass = new double[rulesCoveringForEachClass.length];
+      for(int i=0; i < m_Ruleset.size(); i++){
+	RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);
+	double numAntdsBefore = rule.m_Antds.size();
+
+	int firstAntdToDelete = Integer.MAX_VALUE;
+	for (int j = 0; j < rule.m_Antds.size(); j++){
+	  if (((Antd)rule.m_Antds.elementAt(j)).covers(datum)==0){
+	    firstAntdToDelete = j;
+	    break;
+	  }
+	}
+
+	// Prune antecedent such that it covers the instance
+	for (int j = firstAntdToDelete; j < rule.m_Antds.size(); j++){
+	  rule.m_Antds.removeElementAt(j--);
+	}
+	double numAntdsAfter = rule.m_Antds.size();
+
+	// Empty rules shall not vote here
+	if (!rule.hasAntds())
+	  continue;
+
+	// Calculate the maximum degree of coverage and weight the rule
+	// by its confidence and the fraction of antecedents left after
+	// rule stretching
+	double secondWeight = (numAntdsAfter+1)/(numAntdsBefore+2) ;
+	if (rule.getConfidence() *secondWeight*rule.coverageDegree(datum) >= rulesCoveringForEachClass[(int)rule.getConsequent()]){
+	  rulesCoveringForEachClass[(int)rule.getConsequent()] = rule.getConfidence()*secondWeight*rule.coverageDegree(datum);
+	}
+      }
+
+      // Reestablish original ruleset
+      m_Ruleset = origRuleset;
+    }
+
+    //check for conflicts
+    double[] maxClasses = new double[rulesCoveringForEachClass.length];
+    for (int i = 0; i < rulesCoveringForEachClass.length; i++){
+      if (rulesCoveringForEachClass[Utils.maxIndex(rulesCoveringForEachClass)] ==
+	rulesCoveringForEachClass[i] && rulesCoveringForEachClass[i]>0)
+	maxClasses[i] = 1;
+    }
+
+    //If there is a conflict, resolve it using the apriori distribution
+    if (Utils.sum(maxClasses)>0){
+      for (int i = 0; i < maxClasses.length; i++){
+	if (maxClasses[i] > 0 && aprioriDistribution[i] != rulesCoveringForEachClass[Utils.maxIndex(rulesCoveringForEachClass)])
+	  rulesCoveringForEachClass[i] -= 0.00001;
+      }
+    }
+
+    // If no stretched rule was able to cover the instance,
+    // then fall back to the apriori distribution
+    if (Utils.sum(rulesCoveringForEachClass)==0){
+      rulesCoveringForEachClass = aprioriDistribution;
+    }
+
+
+    if (Utils.sum(rulesCoveringForEachClass)>0)
+      Utils.normalize(rulesCoveringForEachClass);
+
+    return rulesCoveringForEachClass;
+
+  }
+
+
+  /** Build a ruleset for the given class according to the given data
+   *
+   * @param expFPRate the expected FP/(FP+FN) used in DL calculation
+   * @param data the given data
+   * @param classIndex the given class index
+   * @param defDL the default DL in the data
+   * @throws Exception if the ruleset can be built properly
+   */
+  protected Instances rulesetForOneClass(double expFPRate, 
+      Instances data, 
+      double classIndex,
+      double defDL)
+  throws Exception {
+
+    Instances newData = data, growData, pruneData;  	
+    boolean stop = false;
+    FastVector ruleset = new FastVector();		
+
+    double dl = defDL, minDL = defDL;
+    RuleStats rstats = null;
+    double[] rst;
+
+    // Check whether data have positive examples
+    boolean defHasPositive = true; // No longer used
+    boolean hasPositive = defHasPositive;
+
+    /********************** Building stage ***********************/	
+    if(m_Debug)
+      System.err.println("\n*** Building stage ***");
+
+
+    while((!stop) && hasPositive){ // Generate new rules until
+      // stopping criteria met
+      RipperRule oneRule;
+
+      oneRule = new RipperRule();
+      oneRule.setConsequent(classIndex);  // Must set first
+      if(m_Debug)
+	System.err.println("\nNo pruning: growing a rule ...");
+      oneRule.grow(newData);             // Build the rule
+      if(m_Debug)
+	System.err.println("No pruning: one rule found:\n"+
+	    oneRule.toString(m_Class));
+
+
+      // Compute the DL of this ruleset
+      if(rstats == null){ // First rule
+	rstats = new RuleStats();
+	rstats.setNumAllConds(m_Total);
+	rstats.setData(newData);
+      }
+
+      rstats.addAndUpdate(oneRule);		    
+      int last = rstats.getRuleset().size()-1; // Index of last rule
+      dl += rstats.relativeDL(last, expFPRate, m_CheckErr);
+
+      if(Double.isNaN(dl) || Double.isInfinite(dl))
+	throw new Exception("Should never happen: dl in "+
+	"building stage NaN or infinite!");
+      if(m_Debug)
+	System.err.println("Before optimization("+last+
+	    "): the dl = "+dl+" | best: "+minDL);
+
+      if(dl < minDL)
+	minDL = dl;  // The best dl so far	
+
+      rst = rstats.getSimpleStats(last);	    
+      if(m_Debug)
+	System.err.println("The rule covers: "+rst[0]+
+	    " | pos = " + rst[2] + 
+	    " | neg = " + rst[4]+
+	    "\nThe rule doesn't cover: "+rst[1]+
+	    " | pos = " + rst[5]);
+
+      stop = checkStop(rst, minDL, dl);
+
+      if(!stop){	  		
+	ruleset.addElement(oneRule);          // Accepted 
+	newData = rstats.getFiltered(last)[1];// Data not covered
+	hasPositive = Utils.gr(rst[5], 0.0);  // Positives remaining?
+	if(m_Debug)
+	  System.err.println("One rule added: has positive? "
+	      +hasPositive);
+      }
+      else{
+	if(m_Debug)
+	  System.err.println("Quit rule");
+	rstats.removeLast(); // Remove last to be re-used
+      }
+    }// while !stop
+
+
+    /******************** Optimization stage *******************/
+
+    RuleStats finalRulesetStat = null; 
+    for(int z=0; z < m_Optimizations; z++){
+      if(m_Debug)
+	System.err.println("\n*** Optimization: run #"
+	    +z+" ***");
+
+      newData = data;		    
+      finalRulesetStat = new RuleStats();
+      finalRulesetStat.setData(newData);
+      finalRulesetStat.setNumAllConds(m_Total);
+      int position=0;
+      stop = false;
+      boolean isResidual = false;	    
+      hasPositive = defHasPositive;		    
+      dl = minDL = defDL;
+
+      oneRule:    
+	while(!stop && hasPositive){			
+
+	  isResidual = (position>=ruleset.size()); // Cover residual positive examples  
+	  // Re-do shuffling and stratification    
+	  //newData.randomize(m_Random);	
+	  newData = RuleStats.stratify(newData, m_Folds, m_Random);
+	  Instances[] part = RuleStats.partition(newData, m_Folds);
+	  growData=part[0];
+	  pruneData=part[1];
+	  //growData=newData.trainCV(m_Folds, m_Folds-1);
+	  //pruneData=newData.testCV(m_Folds, m_Folds-1);	   
+	  RipperRule finalRule;
+
+	  if(m_Debug)
+	    System.err.println("\nRule #"+position +
+		"| isResidual?" + isResidual+
+		"| data size: "+newData.sumOfWeights());
+
+	  if(isResidual){
+	    RipperRule newRule = new RipperRule();   
+	    newRule.setConsequent(classIndex);
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+	      " a new rule ...");
+	    newRule.grow(newData);
+	    finalRule = newRule;
+	    if(m_Debug)
+	      System.err.println("\nNew rule found: "+
+		  newRule.toString(m_Class));
+	  }
+	  else{
+	    RipperRule oldRule = (RipperRule)ruleset.elementAt(position);
+	    boolean covers = false;
+	    // Test coverage of the next old rule
+	    for(int i=0; i<newData.numInstances(); i++)
+	      if(oldRule.covers(newData.instance(i))){
+		covers = true;
+		break;
+	      }
+
+	    if(!covers){// Null coverage, no variants can be generated
+	      finalRulesetStat.addAndUpdate(oldRule);
+	      position++;
+	      continue oneRule;
+	    }  
+
+	    // 2 variants 
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+	      " Replace ..."); 
+	    RipperRule replace = new RipperRule();   
+	    replace.setConsequent(classIndex);
+	    replace.grow(growData);
+
+	    // Remove the pruning data covered by the following
+	    // rules, then simply compute the error rate of the
+	    // current rule to prune it.  According to Ripper,
+	    // it's equivalent to computing the error of the 
+	    // whole ruleset -- is it true?
+	    pruneData = RuleStats.rmCoveredBySuccessives(pruneData,ruleset, position);      	
+	    replace.prune(pruneData, true);
+
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+	      " Revision ..."); 
+	    RipperRule revision = (RipperRule)oldRule.copy(); 
+
+	    // For revision, first rm the data covered by the old rule
+	    Instances newGrowData = new Instances(growData, 0);
+	    for(int b=0; b<growData.numInstances(); b++){
+	      Instance inst = growData.instance(b);
+	      if(revision.covers(inst))
+		newGrowData.add(inst);
+	    }
+	    revision.grow(newGrowData);	      
+	    revision.prune(pruneData, true);
+
+	    double[][] prevRuleStats = new double[position][6];
+	    for(int c=0; c < position; c++)
+	      prevRuleStats[c] = finalRulesetStat.getSimpleStats(c);
+
+	    // Now compare the relative DL of variants
+	    FastVector tempRules = (FastVector)ruleset.copyElements();
+	    tempRules.setElementAt(replace, position);
+
+	    RuleStats repStat = new RuleStats(data, tempRules);
+	    repStat.setNumAllConds(m_Total);
+	    repStat.countData(position, newData, prevRuleStats);
+	    //repStat.countData();
+	    rst = repStat.getSimpleStats(position);	    
+	    if(m_Debug)
+	      System.err.println("Replace rule covers: "+rst[0]+
+		  " | pos = " + rst[2] + 
+		  " | neg = " + rst[4]+
+		  "\nThe rule doesn't cover: "+rst[1]+
+		  " | pos = " + rst[5]);
+
+	    double repDL = repStat.relativeDL(position, expFPRate,
+		m_CheckErr);
+
+	    if(m_Debug)
+	      System.err.println("\nReplace: "+
+		  replace.toString(m_Class)
+		  +" |dl = "+repDL); 
+
+	    if(Double.isNaN(repDL) || Double.isInfinite(repDL))
+	      throw new Exception("Should never happen: repDL"+
+		  "in optmz. stage NaN or "+
+	      "infinite!");
+
+	    tempRules.setElementAt(revision, position);
+	    RuleStats revStat = new RuleStats(data, tempRules);
+	    revStat.setNumAllConds(m_Total);
+	    revStat.countData(position, newData, prevRuleStats);
+	    //revStat.countData();
+	    double revDL = revStat.relativeDL(position, expFPRate,
+		m_CheckErr);
+
+	    if(m_Debug)
+	      System.err.println("Revision: "
+		  + revision.toString(m_Class)
+		  +" |dl = "+revDL);
+
+	    if(Double.isNaN(revDL) || Double.isInfinite(revDL))
+	      throw new Exception("Should never happen: revDL"+
+		  "in optmz. stage NaN or "+
+	      "infinite!");
+
+	    rstats = new RuleStats(data, ruleset);
+	    rstats.setNumAllConds(m_Total);
+	    rstats.countData(position, newData, prevRuleStats);
+	    //rstats.countData();
+	    double oldDL = rstats.relativeDL(position, expFPRate,
+		m_CheckErr);
+
+	    if(Double.isNaN(oldDL) || Double.isInfinite(oldDL))
+	      throw new Exception("Should never happen: oldDL"+
+		  "in optmz. stage NaN or "+
+	      "infinite!");
+	    if(m_Debug)
+	      System.err.println("Old rule: "+
+		  oldRule.toString(m_Class)
+		  +" |dl = "+oldDL); 
+
+	    if(m_Debug)
+	      System.err.println("\nrepDL: "+repDL+ 
+		  "\nrevDL: "+revDL+
+		  "\noldDL: "+oldDL);
+
+	    if((oldDL <= revDL) && (oldDL <= repDL))
+	      finalRule = oldRule; // Old the best
+	    else if(revDL <= repDL)
+	      finalRule = revision; // Revision the best
+	    else
+	      finalRule = replace; // Replace the best  
+	  }		
+
+	  finalRulesetStat.addAndUpdate(finalRule);  	 
+	  rst = finalRulesetStat.getSimpleStats(position);
+
+	  if(isResidual){	
+	    dl += finalRulesetStat.relativeDL(position, 
+		expFPRate,
+		m_CheckErr);
+
+	    if(m_Debug)
+	      System.err.println("After optimization: the dl"
+		  +"="+dl+" | best: "+minDL);
+
+	    if(dl < minDL)
+	      minDL = dl;  // The best dl so far
+
+	    stop = checkStop(rst, minDL, dl);
+	    if(!stop)
+	      ruleset.addElement(finalRule); // Accepted 
+	    else{
+	      finalRulesetStat.removeLast(); // Remove last to be re-used
+	      position--;
+	    }
+	  }
+	  else
+	    ruleset.setElementAt(finalRule, position); // Accepted 
+
+	  if(m_Debug){
+	    System.err.println("The rule covers: "+rst[0]+
+		" | pos = " + rst[2] + 
+		" | neg = " + rst[4]+
+		"\nThe rule doesn't cover: "+rst[1]+
+		" | pos = " + rst[5]);		
+	    System.err.println("\nRuleset so far: ");
+	    for(int x=0; x<ruleset.size(); x++)
+	      System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
+	    System.err.println();
+	  }
+
+	  //Data not covered	
+	  if(finalRulesetStat.getRulesetSize() > 0)// If any rules	
+	    newData = finalRulesetStat.getFiltered(position)[1]; 
+	  hasPositive = Utils.gr(rst[5], 0.0); //Positives remaining? 
+	  position++;
+	} // while !stop && hasPositive
+
+      if(ruleset.size() > (position+1)){ // Hasn't gone through yet
+	for(int k=position+1; k<ruleset.size(); k++)
+	  finalRulesetStat.addAndUpdate((Rule)ruleset.elementAt(k));
+      }
+      if(m_Debug)
+	System.err.println("\nDeleting rules to decrease"+
+	" DL of the whole ruleset ..."); 
+      finalRulesetStat.reduceDL(expFPRate, m_CheckErr);
+
+      if(m_Debug){
+	int del = ruleset.size() -
+	finalRulesetStat.getRulesetSize(); 
+	System.err.println(del+" rules are deleted"+
+	" after DL reduction procedure");
+      }
+      ruleset = finalRulesetStat.getRuleset();
+      rstats = finalRulesetStat;	      	    
+
+    } // For each run of optimization
+
+    // Concatenate the ruleset for this class to the whole ruleset
+    if(m_Debug){
+      System.err.println("\nFinal ruleset: ");
+      for(int x=0; x<ruleset.size(); x++)
+	System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
+      System.err.println();
+    }
+
+
+    m_Ruleset.appendElements(ruleset);
+    m_RulesetStats.addElement(rstats);
+
+    return null;
+  }   
+
+  /**
+   * Check whether the stopping criterion meets
+   *
+   * @param rst the statistic of the ruleset
+   * @param minDL the min description length so far
+   * @param dl the current description length of the ruleset
+   * @return true if stop criterion meets, false otherwise
+   */
+  private boolean checkStop(double[] rst, double minDL, double dl){
+
+
+    if(dl > minDL+MAX_DL_SURPLUS){
+      if(m_Debug)
+	System.err.println("DL too large: "+dl+" | "+minDL);
+      return true;
+    }
+    else 
+      if(!Utils.gr(rst[2], 0.0)){// Covered positives
+	if(m_Debug)
+	  System.err.println("Too few positives.");
+	return true;
+      }	
+      else if((rst[4]/rst[0]) >= 0.5){// Err rate
+	if(m_CheckErr){
+	  if(m_Debug)
+	    System.err.println("Error too large: "+
+		rst[4] + "/" + rst[0]);
+	  return  true;
+	}
+	else
+	  return false;
+      }		
+      else{// Not stops
+	if(m_Debug)
+	  System.err.println("Continue.");
+	return  false;
+      }				
+  }
+
+  /**
+   * Prints the all the rules of the rule learner.
+   *
+   * @return a textual description of the classifier
+   */
+  public String toString() {
+    if (m_Ruleset == null) 
+      return "FURIA: No model built yet.";
+
+    StringBuffer sb = new StringBuffer("FURIA rules:\n"+
+    "===========\n\n"); 
+    for(int j=0; j<m_RulesetStats.size(); j++){
+      RuleStats rs = (RuleStats)m_RulesetStats.elementAt(j);
+      FastVector rules = rs.getRuleset();
+      for(int k=0; k<rules.size(); k++){
+	sb.append(((RipperRule)rules.elementAt(k)).toString(m_Class)
+	    + " (CF = " + Math.round(100.0*((RipperRule)rules.elementAt(k)).getConfidence())/100.0 +")\n");
+      }			    
+    }
+    if(m_Debug){
+      System.err.println("Inside m_Ruleset");
+      for(int i=0; i<m_Ruleset.size(); i++)
+	System.err.println(((RipperRule)m_Ruleset.elementAt(i)).toString(m_Class));
+    }
+    sb.append("\nNumber of Rules : " 
+	+ m_Ruleset.size() + "\n");
+
+
+
+
+    return sb.toString();
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   * @throws Exception 
+   */
+  public static void main(String[] args) throws Exception {
+    runClassifier(new FURIA(), args);
+  }
+
+  /**
+   * 
+   */
+  public String getRevision() {
+    return "$Revision: 5964 $";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/JRip.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/JRip.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/JRip.java	(revision 29)
@@ -0,0 +1,2061 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    JRip.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Copyable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.ClassOrder;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This class implements a propositional rule learner, Repeated Incremental Pruning to Produce Error Reduction (RIPPER), which was proposed by William W. Cohen as an optimized version of IREP. <br/>
+ * <br/>
+ * The algorithm is briefly described as follows: <br/>
+ * <br/>
+ * Initialize RS = {}, and for each class from the less prevalent one to the more frequent one, DO: <br/>
+ * <br/>
+ * 1. Building stage:<br/>
+ * Repeat 1.1 and 1.2 until the descrition length (DL) of the ruleset and examples is 64 bits greater than the smallest DL met so far, or there are no positive examples, or the error rate &gt;= 50%. <br/>
+ * <br/>
+ * 1.1. Grow phase:<br/>
+ * Grow one rule by greedily adding antecedents (or conditions) to the rule until the rule is perfect (i.e. 100% accurate).  The procedure tries every possible value of each attribute and selects the condition with highest information gain: p(log(p/t)-log(P/T)).<br/>
+ * <br/>
+ * 1.2. Prune phase:<br/>
+ * Incrementally prune each rule and allow the pruning of any final sequences of the antecedents;The pruning metric is (p-n)/(p+n) -- but it's actually 2p/(p+n) -1, so in this implementation we simply use p/(p+n) (actually (p+1)/(p+n+2), thus if p+n is 0, it's 0.5).<br/>
+ * <br/>
+ * 2. Optimization stage:<br/>
+ *  after generating the initial ruleset {Ri}, generate and prune two variants of each rule Ri from randomized data using procedure 1.1 and 1.2. But one variant is generated from an empty rule while the other is generated by greedily adding antecedents to the original rule. Moreover, the pruning metric used here is (TP+TN)/(P+N).Then the smallest possible DL for each variant and the original rule is computed.  The variant with the minimal DL is selected as the final representative of Ri in the ruleset.After all the rules in {Ri} have been examined and if there are still residual positives, more rules are generated based on the residual positives using Building Stage again. <br/>
+ * 3. Delete the rules from the ruleset that would increase the DL of the whole ruleset if it were in it. and add resultant ruleset to RS. <br/>
+ * ENDDO<br/>
+ * <br/>
+ * Note that there seem to be 2 bugs in the original ripper program that would affect the ruleset size and accuracy slightly.  This implementation avoids these bugs and thus is a little bit different from Cohen's original implementation. Even after fixing the bugs, since the order of classes with the same frequency is not defined in ripper, there still seems to be some trivial difference between this implementation and the original ripper, especially for audiology data in UCI repository, where there are lots of classes of few instances.<br/>
+ * <br/>
+ * Details please see:<br/>
+ * <br/>
+ * William W. Cohen: Fast Effective Rule Induction. In: Twelfth International Conference on Machine Learning, 115-123, 1995.<br/>
+ * <br/>
+ * PS.  We have compared this implementation with the original ripper implementation in aspects of accuracy, ruleset size and running time on both artificial data "ab+bcd+defg" and UCI datasets.  In all these aspects it seems to be quite comparable to the original ripper implementation.  However, we didn't consider memory consumption optimization in this implementation.<br/>
+ * <br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Cohen1995,
+ *    author = {William W. Cohen},
+ *    booktitle = {Twelfth International Conference on Machine Learning},
+ *    pages = {115-123},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Fast Effective Rule Induction},
+ *    year = {1995}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;number of folds&gt;
+ *  Set number of folds for REP
+ *  One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -N &lt;min. weights&gt;
+ *  Set the minimal weights of instances
+ *  within a split.
+ *  (default 2.0)</pre>
+ * 
+ * <pre> -O &lt;number of runs&gt;
+ *  Set the number of runs of
+ *  optimizations. (Default: 2)</pre>
+ * 
+ * <pre> -D
+ *  Set whether turn on the
+ *  debug mode (Default: false)</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  The seed of randomization
+ *  (Default: 1)</pre>
+ * 
+ * <pre> -E
+ *  Whether NOT check the error rate&gt;=0.5
+ *  in stopping criteria  (default: check)</pre>
+ * 
+ * <pre> -P
+ *  Whether NOT use pruning
+ *  (default: use pruning)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6041 $
+ */
+public class JRip 
+  extends AbstractClassifier 
+  implements AdditionalMeasureProducer, 
+	     WeightedInstancesHandler,
+	     TechnicalInformationHandler {    
+
+  /** for serialization */
+  static final long serialVersionUID = -6589312996832147161L;
+  
+  /** The limit of description length surplus in ruleset generation */
+  private static double MAX_DL_SURPLUS = 64.0;
+    
+  /** The class attribute of the data*/
+  private Attribute m_Class; 
+    
+  /** The ruleset */
+  private FastVector m_Ruleset;
+  
+  /** The predicted class distribution */
+  private FastVector m_Distributions;
+  
+  /** Runs of optimizations */
+  private int m_Optimizations = 2;
+    
+  /** Random object used in this class */
+  private Random m_Random = null;
+    
+  /** # of all the possible conditions in a rule */
+  private double m_Total = 0;
+
+  /** The seed to perform randomization */
+  private long m_Seed = 1;
+
+  /** The number of folds to split data into Grow and Prune for IREP */
+  private int m_Folds = 3;
+    
+  /** The minimal number of instance weights within a split*/
+  private double m_MinNo = 2.0;
+
+  /** Whether in a debug mode */
+  private boolean m_Debug = false;
+
+  /** Whether check the error rate >= 0.5 in stopping criteria */
+  private boolean m_CheckErr = true;
+
+  /** Whether use pruning, i.e. the data is clean or not */
+  private boolean m_UsePruning = true;
+
+  /** The filter used to randomize the class order */
+  private Filter m_Filter = null;
+
+  /** The RuleStats for the ruleset of each class value */
+  private FastVector m_RulesetStats;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "This class implements a propositional rule learner, Repeated Incremental "
+      + "Pruning to Produce Error Reduction (RIPPER), which was proposed by William "
+      + "W. Cohen as an optimized version of IREP. \n\n"
+      + "The algorithm is briefly described as follows: \n\n"
+      + "Initialize RS = {}, and for each class from the less prevalent one to "
+      + "the more frequent one, DO: \n\n"
+      + "1. Building stage:\nRepeat 1.1 and 1.2 until the descrition length (DL) "
+      + "of the ruleset and examples is 64 bits greater than the smallest DL "
+      + "met so far, or there are no positive examples, or the error rate >= 50%. "
+      + "\n\n"
+      + "1.1. Grow phase:\n"
+      + "Grow one rule by greedily adding antecedents (or conditions) to "
+      + "the rule until the rule is perfect (i.e. 100% accurate).  The "
+      + "procedure tries every possible value of each attribute and selects "
+      + "the condition with highest information gain: p(log(p/t)-log(P/T))."
+      + "\n\n"
+      + "1.2. Prune phase:\n"
+      + "Incrementally prune each rule and allow the pruning of any "
+      + "final sequences of the antecedents;"
+      + "The pruning metric is (p-n)/(p+n) -- but it's actually "
+      + "2p/(p+n) -1, so in this implementation we simply use p/(p+n) "
+      + "(actually (p+1)/(p+n+2), thus if p+n is 0, it's 0.5).\n\n"
+      + "2. Optimization stage:\n after generating the initial ruleset {Ri}, "
+      + "generate and prune two variants of each rule Ri from randomized data "
+      + "using procedure 1.1 and 1.2. But one variant is generated from an "
+      + "empty rule while the other is generated by greedily adding antecedents "
+      + "to the original rule. Moreover, the pruning metric used here is "
+      + "(TP+TN)/(P+N)."
+      + "Then the smallest possible DL for each variant and the original rule "
+      + "is computed.  The variant with the minimal DL is selected as the final "
+      + "representative of Ri in the ruleset."
+      + "After all the rules in {Ri} have been examined and if there are still "
+      + "residual positives, more rules are generated based on the residual "
+      + "positives using Building Stage again. \n"
+      + "3. Delete the rules from the ruleset that would increase the DL of the "
+      + "whole ruleset if it were in it. and add resultant ruleset to RS. \n"
+      + "ENDDO\n\n"
+      + "Note that there seem to be 2 bugs in the original ripper program that would "
+      + "affect the ruleset size and accuracy slightly.  This implementation avoids "
+      + "these bugs and thus is a little bit different from Cohen's original "
+      + "implementation. Even after fixing the bugs, since the order of classes with "
+      + "the same frequency is not defined in ripper, there still seems to be "
+      + "some trivial difference between this implementation and the original ripper, "
+      + "especially for audiology data in UCI repository, where there are lots of "
+      + "classes of few instances.\n\n"
+      + "Details please see:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "PS.  We have compared this implementation with the original ripper "
+      + "implementation in aspects of accuracy, ruleset size and running time "
+      + "on both artificial data \"ab+bcd+defg\" and UCI datasets.  In all these "
+      + "aspects it seems to be quite comparable to the original ripper "
+      + "implementation.  However, we didn't consider memory consumption "
+      + "optimization in this implementation.\n\n";    
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "William W. Cohen");
+    result.setValue(Field.TITLE, "Fast Effective Rule Induction");
+    result.setValue(Field.BOOKTITLE, "Twelfth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.PAGES, "115-123");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    return result;
+  }
+    
+  /**
+   * Returns an enumeration describing the available options
+   * Valid options are: <p>
+   *  
+   * -F number <br>
+   * The number of folds for reduced error pruning. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   * 
+   * -N number <br>
+   * The minimal weights of instances within a split.
+   * (Default: 2) <p>
+   *    
+   * -O number <br>
+   * Set the number of runs of optimizations. (Default: 2)<p>
+   *
+   * -D <br>
+   * Whether turn on the debug mode
+   *
+   * -S number <br>
+   * The seed of randomization used in Ripper.(Default: 1)<p>
+   *
+   * -E <br>
+   * Whether NOT check the error rate >= 0.5 in stopping criteria.
+   * (default: check)<p> 
+   *
+   * -P <br>
+   * Whether NOT use pruning. (default: use pruning)<p>
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option("\tSet number of folds for REP\n" +
+				    "\tOne fold is used as pruning set.\n" +
+				    "\t(default 3)","F", 1, "-F <number of folds>"));
+    newVector.addElement(new Option("\tSet the minimal weights of instances\n" +
+				    "\twithin a split.\n" +
+				    "\t(default 2.0)","N", 1, "-N <min. weights>"));		 
+    newVector.addElement(new Option("\tSet the number of runs of\n"+
+				    "\toptimizations. (Default: 2)", "O",
+				    1,"-O <number of runs>"));
+	
+    newVector.addElement(new Option("\tSet whether turn on the\n"+
+				    "\tdebug mode (Default: false)", "D",
+				    0,"-D"));
+	
+    newVector.addElement(new Option("\tThe seed of randomization\n"+
+				    "\t(Default: 1)", "S",
+				    1,"-S <seed>"));
+	
+    newVector.addElement(new Option("\tWhether NOT check the error rate>=0.5\n"
+				    +"\tin stopping criteria "
+				    +"\t(default: check)", "E", 
+				    0, "-E")); 
+	
+    newVector.addElement(new Option("\tWhether NOT use pruning\n"
+				    +"\t(default: use pruning)", "P", 
+				    0, "-P")); 
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;number of folds&gt;
+   *  Set number of folds for REP
+   *  One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -N &lt;min. weights&gt;
+   *  Set the minimal weights of instances
+   *  within a split.
+   *  (default 2.0)</pre>
+   * 
+   * <pre> -O &lt;number of runs&gt;
+   *  Set the number of runs of
+   *  optimizations. (Default: 2)</pre>
+   * 
+   * <pre> -D
+   *  Set whether turn on the
+   *  debug mode (Default: false)</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  The seed of randomization
+   *  (Default: 1)</pre>
+   * 
+   * <pre> -E
+   *  Whether NOT check the error rate&gt;=0.5
+   *  in stopping criteria  (default: check)</pre>
+   * 
+   * <pre> -P
+   *  Whether NOT use pruning
+   *  (default: use pruning)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String numFoldsString = Utils.getOption('F', options);
+    if (numFoldsString.length() != 0) 
+      m_Folds = Integer.parseInt(numFoldsString);
+    else 
+      m_Folds = 3;   
+	
+    String minNoString = Utils.getOption('N', options);
+    if (minNoString.length() != 0) 
+      m_MinNo = Double.parseDouble(minNoString);
+    else 
+      m_MinNo = 2.0;
+	
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0)
+      m_Seed = Long.parseLong(seedString);
+    else 
+      m_Seed = 1;
+
+    String runString = Utils.getOption('O', options);
+    if (runString.length() != 0)
+      m_Optimizations = Integer.parseInt(runString);
+    else 
+      m_Optimizations = 2;
+
+    m_Debug = Utils.getFlag('D', options);
+    m_CheckErr = !Utils.getFlag('E', options);
+    m_UsePruning = !Utils.getFlag('P', options);
+  }
+    
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [11];
+    int current = 0;
+    options[current++] = "-F"; options[current++] = "" + m_Folds;
+    options[current++] = "-N"; options[current++] = "" + m_MinNo;
+    options[current++] = "-O"; options[current++] = "" + m_Optimizations;
+    options[current++] = "-S"; options[current++] = "" + m_Seed;
+	
+    if(m_Debug)
+      options[current++] = "-D";
+
+    if(!m_CheckErr)
+      options[current++] = "-E";
+	
+    if(!m_UsePruning)
+      options[current++] = "-P";
+	
+    while(current < options.length)
+      options[current++] = "";
+	
+    return options;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+    
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) 
+      return m_Ruleset.size();
+    else 
+      throw new IllegalArgumentException(additionalMeasureName+" not supported (RIPPER)");
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTipText() {
+    return "Determines the amount of data used for pruning. One fold is used for "
+      + "pruning, the rest for growing the rules.";
+  }
+
+  /**
+   * Sets the number of folds to use
+   * 
+   * @param fold the number of folds
+   */
+  public void setFolds(int fold) { 
+    m_Folds = fold; 
+  }
+  
+  /**
+   * Gets the number of folds
+   * 
+   * @return the number of folds
+   */
+  public int getFolds(){ 
+    return m_Folds; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNoTipText() {
+    return "The minimum total weight of the instances in a rule.";
+  }
+
+  /**
+   * Sets the minimum total weight of the instances in a rule
+   * 
+   * @param m the minimum total weight of the instances in a rule
+   */
+  public void setMinNo(double m) {
+    m_MinNo = m;
+  }
+  
+  /**
+   * Gets the minimum total weight of the instances in a rule
+   * 
+   * @return the minimum total weight of the instances in a rule
+   */
+  public double getMinNo(){ 
+    return m_MinNo;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data.";
+  }
+
+  /**
+   * Sets the seed value to use in randomizing the data
+   * 
+   * @param s the new seed value
+   */
+  public void setSeed(long s) {
+    m_Seed = s;
+  }
+  
+  /**
+   * Gets the current seed value to use in randomizing the data
+   * 
+   * @return the seed value
+   */
+  public long getSeed(){
+    return m_Seed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String optimizationsTipText() {
+    return "The number of optimization runs.";
+  }
+
+  /**
+   * Sets the number of optimization runs
+   * 
+   * @param run the number of optimization runs
+   */
+  public void setOptimizations(int run) {
+    m_Optimizations = run;
+  }
+  
+  /**
+   * Gets the the number of optimization runs
+   * 
+   * @return the number of optimization runs
+   */
+  public int getOptimizations() {
+    return m_Optimizations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Whether debug information is output to the console.";
+  }
+
+  /**
+   * Sets whether debug information is output to the console
+   * 
+   * @param d whether debug information is output to the console
+   */
+  public void setDebug(boolean d) {
+    m_Debug = d;
+  }
+  
+  /**
+   * Gets whether debug information is output to the console
+   * 
+   * @return whether debug information is output to the console
+   */
+  public boolean getDebug(){
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String checkErrorRateTipText() {
+    return "Whether check for error rate >= 1/2 is included" +
+      " in stopping criterion.";
+  }
+
+  /**
+   * Sets whether to check for error rate is in stopping criterion
+   * 
+   * @param d whether to check for error rate is in stopping criterion
+   */
+  public void setCheckErrorRate(boolean d) { 
+    m_CheckErr = d;
+  }
+  
+  /**
+   * Gets whether to check for error rate is in stopping criterion
+   * 
+   * @return true if checking for error rate is in stopping criterion
+   */
+  public boolean getCheckErrorRate(){ 
+    return m_CheckErr; 
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String usePruningTipText() {
+    return "Whether pruning is performed.";
+  }
+
+  /**
+   * Sets whether pruning is performed
+   * 
+   * @param d Whether pruning is performed
+   */
+  public void setUsePruning(boolean d) { 
+    m_UsePruning = d;
+  }
+  
+  /**
+   * Gets whether pruning is performed
+   * 
+   * @return true if pruning is performed
+   */
+  public boolean getUsePruning(){ 
+    return m_UsePruning; 
+  }
+    
+  /** 
+   * Get the ruleset generated by Ripper 
+   *
+   * @return the ruleset
+   */
+  public FastVector getRuleset(){ return m_Ruleset; }
+
+  /** 
+   * Get the statistics of the ruleset in the given position
+   *
+   * @param pos the position of the stats, assuming correct
+   * @return the statistics of the ruleset in the given position
+   */
+  public RuleStats getRuleStats(int pos) {
+    return (RuleStats)m_RulesetStats.elementAt(pos);
+  }
+    
+  /** 
+   * The single antecedent in the rule, which is composed of an attribute and 
+   * the corresponding value.  There are two inherited classes, namely NumericAntd
+   * and NominalAntd in which the attributes are numeric and nominal respectively.
+   */    
+  private abstract class Antd 
+    implements WeightedInstancesHandler, Copyable, Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -8929754772994154334L;
+	
+    /** The attribute of the antecedent */
+    protected Attribute att;
+	
+    /** The attribute value of the antecedent.  
+       For numeric attribute, value is either 0(1st bag) or 1(2nd bag) */
+    protected double value; 
+	
+    /** The maximum infoGain achieved by this antecedent test 
+     * in the growing data */
+    protected double maxInfoGain;
+	
+    /** The accurate rate of this antecedent test on the growing data */
+    protected double accuRate;
+	
+    /** The coverage of this antecedent in the growing data */
+    protected double cover;
+	
+    /** The accurate data for this antecedent in the growing data */
+    protected double accu;
+	
+    /** 
+     * Constructor
+     */
+    public Antd(Attribute a){
+      att=a;
+      value=Double.NaN; 
+      maxInfoGain = 0;
+      accuRate = Double.NaN;
+      cover = Double.NaN;
+      accu = Double.NaN;
+    }
+	
+    /* The abstract members for inheritance */
+    public abstract Instances[] splitData(Instances data, double defAcRt, 
+					  double cla);
+    public abstract boolean covers(Instance inst);
+    public abstract String toString();
+	
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public abstract Object copy(); 
+	   
+    /* Get functions of this antecedent */
+    public Attribute getAttr(){ return att; }
+    public double getAttrValue(){ return value; }
+    public double getMaxInfoGain(){ return maxInfoGain; }
+    public double getAccuRate(){ return accuRate; } 
+    public double getAccu(){ return accu; } 
+    public double getCover(){ return cover; } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6041 $");
+    }
+  }
+    
+  /** 
+   * The antecedent with numeric attribute
+   */
+  private class 
+    NumericAntd extends Antd {
+    
+    /** for serialization */
+    static final long serialVersionUID = 5699457269983735442L;
+	
+    /** The split point for this numeric antecedent */
+    private double splitPoint;
+    
+    /** 
+     * Constructor
+     */
+    public NumericAntd(Attribute a){ 
+      super(a);
+      splitPoint = Double.NaN;
+    }    
+	
+    /** 
+     * Get split point of this numeric antecedent
+     * 
+     * @return the split point of this numeric antecedent
+     */
+    public double getSplitPoint(){ 
+      return splitPoint;
+    }
+	
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public Object copy(){ 
+      NumericAntd na = new NumericAntd(getAttr());
+      na.value = this.value;
+      na.splitPoint = this.splitPoint;
+      return na;
+    }
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into two bags according 
+     * to the information gain of the numeric attribute value
+     * The maximum infoGain is also calculated.  
+     * 
+     * @param insts the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances insts, double defAcRt, 
+				 double cl){
+	Instances data = insts;
+      int total=data.numInstances();// Total number of instances without 
+      // missing value for att
+	    
+      int split=1;                  // Current split position
+      int prev=0;                   // Previous split position
+      int finalSplit=split;         // Final split position
+      maxInfoGain = 0;
+      value = 0;	
+	    
+      double fstCover=0, sndCover=0, fstAccu=0, sndAccu=0;
+	    
+      data.sort(att);
+      // Find the las instance without missing value 
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst = data.instance(x);
+	if(inst.isMissing(att)){
+	  total = x;
+	  break;
+	}
+		
+	sndCover += inst.weight();
+	if(Utils.eq(inst.classValue(), cl))
+	  sndAccu += inst.weight();		
+      }	    
+
+      if(total == 0) return null; // Data all missing for the attribute
+      splitPoint = data.instance(total-1).value(att);	
+	    
+      for(; split <= total; split++){
+	if((split == total) ||
+	   (data.instance(split).value(att) > // Can't split within
+	    data.instance(prev).value(att))){ // same value	    
+		    
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    fstCover += inst.weight(); 
+	    if(Utils.eq(data.instance(y).classValue(), cl)){
+	      fstAccu += inst.weight();  // First bag positive# ++
+	    }	     		   
+	  }
+		    
+	  double fstAccuRate = (fstAccu+1.0)/(fstCover+1.0),
+	    sndAccuRate = (sndAccu+1.0)/(sndCover+1.0);
+		    
+	  /* Which bag has higher information gain? */
+	  boolean isFirst; 
+	  double fstInfoGain, sndInfoGain;
+	  double accRate, infoGain, coverage, accurate;
+		    
+	  fstInfoGain = 
+	    //Utils.eq(defAcRt, 1.0) ? 
+	    //fstAccu/(double)numConds : 
+	    fstAccu*(Utils.log2(fstAccuRate)-Utils.log2(defAcRt));
+		    
+	  sndInfoGain = 
+	    //Utils.eq(defAcRt, 1.0) ? 
+	    //sndAccu/(double)numConds : 
+	    sndAccu*(Utils.log2(sndAccuRate)-Utils.log2(defAcRt));
+		    
+	  if(fstInfoGain > sndInfoGain){
+	    isFirst = true;
+	    infoGain = fstInfoGain;
+	    accRate = fstAccuRate;
+	    accurate = fstAccu;
+	    coverage = fstCover;
+	  }
+	  else{
+	    isFirst = false;
+	    infoGain = sndInfoGain;
+	    accRate = sndAccuRate;
+	    accurate = sndAccu;
+	    coverage = sndCover;
+	  }
+		    
+	  /* Check whether so far the max infoGain */
+	  if(infoGain > maxInfoGain){
+	    splitPoint = data.instance(prev).value(att);
+	    value = (isFirst) ? 0 : 1;
+	    accuRate = accRate;
+	    accu = accurate;
+	    cover = coverage;
+	    maxInfoGain = infoGain;
+	    finalSplit = (isFirst) ? split : prev;
+	  }
+		    
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    sndCover -= inst.weight(); 
+	    if(Utils.eq(data.instance(y).classValue(), cl)){
+	      sndAccu -= inst.weight();  // Second bag positive# --
+	    }	     		   
+	  }		    
+	  prev=split;
+	}
+      }
+	    
+      /* Split the data */
+      Instances[] splitData = new Instances[2];
+      splitData[0] = new Instances(data, 0, finalSplit);
+      splitData[1] = new Instances(data, finalSplit, total-finalSplit);
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered
+     *         by this antecedent
+     */
+    public boolean covers(Instance inst){
+      boolean isCover=true;
+      if(!inst.isMissing(att)){
+	if((int)value == 0){ // First bag
+	  if(inst.value(att) > splitPoint)
+	    isCover=false;
+	}
+	else if(inst.value(att) < splitPoint) // Second bag
+	  isCover=false;
+      }
+      else
+	isCover = false;
+	    
+      return isCover;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      String symbol = ((int)value == 0) ? " <= " : " >= ";
+      return (att.name() + symbol + Utils.doubleToString(splitPoint, 6));
+    }   
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6041 $");
+    }
+  }
+    
+    
+  /** 
+   * The antecedent with nominal attribute
+   */
+  private class NominalAntd 
+    extends Antd {
+	
+    /** for serialization */
+    static final long serialVersionUID = -9102297038837585135L;
+    
+    /* The parameters of infoGain calculated for each attribute value
+     * in the growing data */
+    private double[] accurate;
+    private double[] coverage;
+	
+    /** 
+     * Constructor
+     */
+    public NominalAntd(Attribute a){ 
+      super(a);    
+      int bag = att.numValues();
+      accurate = new double[bag];
+      coverage = new double[bag];
+    }   
+
+    /** 
+     * Implements Copyable
+     * 
+     * @return a copy of this object
+     */
+    public Object copy(){
+      Antd antec = new NominalAntd(getAttr());
+      antec.value = this.value;
+      return antec;	    
+    }
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into bags according 
+     * to the nominal attribute value
+     * The infoGain for each bag is also calculated.  
+     * 
+     * @param data the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances data, double defAcRt, 
+				 double cl){
+      int bag = att.numValues();
+      Instances[] splitData = new Instances[bag];
+	    
+      for(int x=0; x<bag; x++){
+	splitData[x] = new Instances(data, data.numInstances());
+	accurate[x] = 0;
+	coverage[x] = 0;
+      }
+	    
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst=data.instance(x);
+	if(!inst.isMissing(att)){
+	  int v = (int)inst.value(att);
+	  splitData[v].add(inst);
+	  coverage[v] += inst.weight();
+	  if((int)inst.classValue() == (int)cl)
+	    accurate[v] += inst.weight();
+	}
+      }
+	    
+      for(int x=0; x<bag; x++){
+	double t = coverage[x]+1.0;
+	double p = accurate[x] + 1.0;		
+	double infoGain = 
+	  //Utils.eq(defAcRt, 1.0) ? 
+	  //accurate[x]/(double)numConds : 
+	  accurate[x]*(Utils.log2(p/t)-Utils.log2(defAcRt));
+		
+	if(infoGain > maxInfoGain){
+	  maxInfoGain = infoGain;
+	  cover = coverage[x];
+	  accu = accurate[x];
+	  accuRate = p/t;
+	  value = (double)x;
+	}
+      }
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is
+     *         covered by this antecedent
+     */
+    public boolean covers(Instance inst){
+      boolean isCover=false;
+      if(!inst.isMissing(att)){
+	if((int)inst.value(att) == (int)value)
+	  isCover=true;	    
+      }
+      return isCover;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      return (att.name() + " = " +att.value((int)value));
+    } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6041 $");
+    }
+  }
+
+    
+  /**
+   * This class implements a single rule that predicts specified class.  
+   *
+   * A rule consists of antecedents "AND"ed together and the consequent 
+   * (class value) for the classification.  
+   * In this class, the Information Gain (p*[log(p/t) - log(P/T)]) is used to
+   * select an antecedent and Reduced Error Prunning (REP) with the metric
+   * of accuracy rate p/(p+n) or (TP+TN)/(P+N) is used to prune the rule. 
+   */    
+  protected class RipperRule 
+    extends Rule {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2410020717305262952L;
+	
+    /** The internal representation of the class label to be predicted */
+    private double m_Consequent = -1;	
+		
+    /** The vector of antecedents of this rule*/
+    protected FastVector m_Antds = null;	
+	
+    /** Constructor */
+    public RipperRule(){    
+      m_Antds = new FastVector();	
+    }
+	
+    /**
+     * Sets the internal representation of the class label to be predicted
+     * 
+     * @param cl the internal representation of the class label to be predicted
+     */
+    public void setConsequent(double cl) {
+      m_Consequent = cl; 
+    }
+    
+    /**
+     * Gets the internal representation of the class label to be predicted
+     * 
+     * @return the internal representation of the class label to be predicted
+     */
+    public double getConsequent() { 
+      return m_Consequent; 
+    }
+	
+    /**
+     * Get a shallow copy of this rule
+     *
+     * @return the copy
+     */
+    public Object copy(){
+      RipperRule copy = new RipperRule();
+      copy.setConsequent(getConsequent());
+      copy.m_Antds = (FastVector)this.m_Antds.copyElements();
+      return copy;
+    }
+	
+    /**
+     * Whether the instance covered by this rule
+     * 
+     * @param datum the instance in question
+     * @return the boolean value indicating whether the instance 
+     *         is covered by this rule
+     */
+    public boolean covers(Instance datum){
+      boolean isCover=true;
+	    
+      for(int i=0; i<m_Antds.size(); i++){
+	Antd antd = (Antd)m_Antds.elementAt(i);
+	if(!antd.covers(datum)){
+	  isCover = false;
+	  break;
+	}
+      }
+	    
+      return isCover;
+    }        
+	
+    /**
+     * Whether this rule has antecedents, i.e. whether it is a default rule
+     * 
+     * @return the boolean value indicating whether the rule has antecedents
+     */
+    public boolean hasAntds(){
+      if (m_Antds == null)
+	return false;
+      else
+	return (m_Antds.size() > 0);
+    }      
+	
+    /** 
+     * the number of antecedents of the rule
+     *
+     * @return the size of this rule
+     */
+    public double size(){ return (double)m_Antds.size(); }		
+
+	
+    /**
+     * Private function to compute default number of accurate instances
+     * in the specified data for the consequent of the rule
+     * 
+     * @param data the data in question
+     * @return the default accuracy number
+     */
+    private double computeDefAccu(Instances data){ 
+      double defAccu=0;
+      for(int i=0; i<data.numInstances(); i++){
+	Instance inst = data.instance(i);
+	if((int)inst.classValue() == (int)m_Consequent)
+	  defAccu += inst.weight();
+      }
+      return defAccu;
+    }
+	
+	
+    /**
+     * Build one rule using the growing data
+     *
+     * @param data the growing data used to build the rule
+     * @throws Exception if the consequent is not set yet
+     */    
+    public void grow(Instances data) throws Exception {
+      if(m_Consequent == -1)
+        throw new Exception(" Consequent not set yet.");
+
+      Instances growData = data;	         
+      double sumOfWeights = growData.sumOfWeights();
+      if(!Utils.gr(sumOfWeights, 0.0))
+        return;
+
+      /* Compute the default accurate rate of the growing data */
+      double defAccu = computeDefAccu(growData);
+      double defAcRt = (defAccu+1.0)/(sumOfWeights+1.0); 
+
+      /* Keep the record of which attributes have already been used*/    
+      boolean[] used=new boolean [growData.numAttributes()];
+      for (int k=0; k<used.length; k++)
+        used[k]=false;
+      int numUnused=used.length;
+
+      // If there are already antecedents existing
+      for(int j=0; j < m_Antds.size(); j++){
+        Antd antdj = (Antd)m_Antds.elementAt(j);
+        if(!antdj.getAttr().isNumeric()){ 
+          used[antdj.getAttr().index()]=true;
+          numUnused--;
+        } 
+      }	    
+
+      double maxInfoGain;	    
+      while (Utils.gr(growData.numInstances(), 0.0) && 
+          (numUnused > 0) 
+          && Utils.sm(defAcRt, 1.0)
+          ){   
+
+        // We require that infoGain be positive
+        /*if(numAntds == originalSize)
+          maxInfoGain = 0.0; // At least one condition allowed
+          else
+          maxInfoGain = Utils.eq(defAcRt, 1.0) ? 
+          defAccu/(double)numAntds : 0.0; */
+        maxInfoGain = 0.0; 
+
+        /* Build a list of antecedents */
+        Antd oneAntd=null;
+        Instances coverData = null;
+        Enumeration enumAttr=growData.enumerateAttributes();	      
+
+        /* Build one condition based on all attributes not used yet*/
+        while (enumAttr.hasMoreElements()){
+          Attribute att= (Attribute)(enumAttr.nextElement());
+
+          if(m_Debug)
+            System.err.println("\nOne condition: size = " 
+                + growData.sumOfWeights());
+
+          Antd antd =null;	
+          if(att.isNumeric())
+            antd = new NumericAntd(att);
+          else
+            antd = new NominalAntd(att);
+
+          if(!used[att.index()]){
+            /* Compute the best information gain for each attribute,
+               it's stored in the antecedent formed by this attribute.
+               This procedure returns the data covered by the antecedent*/
+            Instances coveredData = computeInfoGain(growData, defAcRt,
+                antd);
+            if(coveredData != null){
+              double infoGain = antd.getMaxInfoGain();      
+              if(m_Debug)
+                System.err.println("Test of \'"+antd.toString()+
+                    "\': infoGain = "+
+                    infoGain + " | Accuracy = " +
+                    antd.getAccuRate()+
+                    "="+antd.getAccu()
+                    +"/"+antd.getCover()+
+                    " def. accuracy: "+defAcRt);
+
+              if(infoGain > maxInfoGain){         
+                oneAntd=antd;
+                coverData = coveredData;  
+                maxInfoGain = infoGain;
+              }		    
+            }
+          }
+        }
+
+        if(oneAntd == null) break; // Cannot find antds		
+        if(Utils.sm(oneAntd.getAccu(), m_MinNo)) break;// Too low coverage
+
+        //Numeric attributes can be used more than once
+        if(!oneAntd.getAttr().isNumeric()){ 
+          used[oneAntd.getAttr().index()]=true;
+          numUnused--;
+        }
+
+        m_Antds.addElement(oneAntd);
+        growData = coverData;// Grow data size is shrinking 	
+        defAcRt = oneAntd.getAccuRate();
+          }
+    }
+	
+	
+    /** 
+     * Compute the best information gain for the specified antecedent
+     *  
+     * @param instances the data based on which the infoGain is computed
+     * @param defAcRt the default accuracy rate of data
+     * @param antd the specific antecedent
+     * @param numConds the number of antecedents in the rule so far
+     * @return the data covered by the antecedent
+     */
+    private Instances computeInfoGain(Instances instances, double defAcRt, 
+				      Antd antd){
+	Instances data = instances;
+	
+      /* Split the data into bags.
+	 The information gain of each bag is also calculated in this procedure */
+      Instances[] splitData = antd.splitData(data, defAcRt, 
+					     m_Consequent); 
+	    
+      /* Get the bag of data to be used for next antecedents */
+      if(splitData != null)
+	return splitData[(int)antd.getAttrValue()];
+      else return null;
+    }
+	
+    /**
+     * Prune all the possible final sequences of the rule using the 
+     * pruning data.  The measure used to prune the rule is based on
+     * flag given.
+     *
+     * @param pruneData the pruning data used to prune the rule
+     * @param useWhole flag to indicate whether use the error rate of
+     *                 the whole pruning data instead of the data covered
+     */    
+    public void prune(Instances pruneData, boolean useWhole){
+	Instances data = pruneData;
+	
+      double total = data.sumOfWeights();
+      if(!Utils.gr(total, 0.0))
+	return;
+	
+      /* The default accurate # and rate on pruning data */
+      double defAccu=computeDefAccu(data);
+	    
+      if(m_Debug)	
+	System.err.println("Pruning with " + defAccu + 
+			   " positive data out of " + total +
+			   " instances");	
+	    
+      int size=m_Antds.size();
+      if(size == 0) return; // Default rule before pruning
+	    
+      double[] worthRt = new double[size];
+      double[] coverage = new double[size];
+      double[] worthValue = new double[size];
+      for(int w=0; w<size; w++){
+	worthRt[w]=coverage[w]=worthValue[w]=0.0;
+      }
+	    
+      /* Calculate accuracy parameters for all the antecedents in this rule */
+      double tn = 0.0; // True negative if useWhole
+      for(int x=0; x<size; x++){
+	Antd antd=(Antd)m_Antds.elementAt(x);
+	Instances newData = data;
+	data = new Instances(newData, 0); // Make data empty
+		
+	for(int y=0; y<newData.numInstances(); y++){
+	  Instance ins=newData.instance(y);
+		    
+	  if(antd.covers(ins)){   // Covered by this antecedent
+	    coverage[x] += ins.weight();
+	    data.add(ins);                 // Add to data for further pruning
+	    if((int)ins.classValue() == (int)m_Consequent) // Accurate prediction
+	      worthValue[x] += ins.weight();
+	  }
+	  else if(useWhole){ // Not covered
+	    if((int)ins.classValue() != (int)m_Consequent)
+	      tn += ins.weight();
+	  }			
+	}
+		
+	if(useWhole){
+	  worthValue[x] += tn;
+	  worthRt[x] = worthValue[x] / total;
+	}
+	else // Note if coverage is 0, accuracy is 0.5
+	  worthRt[x] = (worthValue[x]+1.0)/(coverage[x]+2.0);
+      }
+	    
+      double maxValue = (defAccu+1.0)/(total+2.0);
+      int maxIndex = -1;
+      for(int i=0; i<worthValue.length; i++){
+	if(m_Debug){
+	  double denom = useWhole ? total : coverage[i];
+	  System.err.println(i+"(useAccuray? "+!useWhole+"): "
+			     + worthRt[i] + 
+			     "="+worthValue[i]+
+			     "/"+denom);
+	}
+	if(worthRt[i] > maxValue){ // Prefer to the 
+	  maxValue = worthRt[i]; // shorter rule
+	  maxIndex = i;
+	}
+      }
+	    
+      /* Prune the antecedents according to the accuracy parameters */
+      for(int z=size-1;z>maxIndex;z--)
+	m_Antds.removeElementAt(z);       
+    }
+	
+    /**
+     * Prints this rule
+     *
+     * @param classAttr the class attribute in the data
+     * @return a textual description of this rule
+     */
+    public String toString(Attribute classAttr) {
+      StringBuffer text =  new StringBuffer();
+      if(m_Antds.size() > 0){
+	for(int j=0; j< (m_Antds.size()-1); j++)
+	  text.append("(" + ((Antd)(m_Antds.elementAt(j))).toString()+ ") and ");
+	text.append("("+((Antd)(m_Antds.lastElement())).toString() + ")");
+      }
+      text.append(" => " + classAttr.name() +
+		  "=" + classAttr.value((int)m_Consequent));
+	    
+      return text.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6041 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+   
+    // instances
+    result.setMinimumNumberInstances(m_Folds);
+    
+    return result;
+  }
+    
+  /**
+   * Builds Ripper in the order of class frequencies.  For each class
+   * it's built in two stages: building and optimization 
+   *
+   * @param instances the training data
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    m_Random = instances.getRandomNumberGenerator(m_Seed); 
+    m_Total = RuleStats.numAllConditions(instances);
+    if(m_Debug)
+      System.err.println("Number of all possible conditions = "+m_Total);
+    
+    Instances data = null;
+    m_Filter = new ClassOrder();
+    ((ClassOrder)m_Filter).setSeed(m_Random.nextInt());	
+    ((ClassOrder)m_Filter).setClassOrder(ClassOrder.FREQ_ASCEND);
+    m_Filter.setInputFormat(instances);
+    data = Filter.useFilter(instances, m_Filter);
+	
+    if(data == null)
+      throw new Exception(" Unable to randomize the class orders.");
+    
+    m_Class = data.classAttribute();	
+    m_Ruleset = new FastVector();
+    m_RulesetStats = new FastVector();
+    m_Distributions = new FastVector();
+
+    // Sort by classes frequency
+    double[] orderedClasses = ((ClassOrder)m_Filter).getClassCounts();
+    if(m_Debug){
+      System.err.println("Sorted classes:");
+      for(int x=0; x < m_Class.numValues(); x++)
+	System.err.println(x+": "+m_Class.value(x) + " has " +
+			   orderedClasses[x] + " instances.");
+    }
+    // Iterate from less prevalent class to more frequent one
+  oneClass:	
+    for(int y=0; y < data.numClasses()-1; y++){ // For each class	      
+	    
+      double classIndex = (double)y;
+      if(m_Debug){
+	int ci = (int)classIndex;
+	System.err.println("\n\nClass "+m_Class.value(ci)+"("+ci+"): "
+			   + orderedClasses[y] + "instances\n"+
+			   "=====================================\n");
+      }
+		
+      if(Utils.eq(orderedClasses[y],0.0)) // No data for this class
+	continue oneClass;		
+	    
+      // The expected FP/err is the proportion of the class
+      double all = 0;
+      for(int i=y; i<orderedClasses.length; i++)
+	all += orderedClasses[i];
+      double expFPRate = orderedClasses[y] / all;	    
+		
+      double classYWeights = 0, totalWeights = 0;
+      for(int j=0; j < data.numInstances(); j++){
+	  Instance datum = data.instance(j);
+	  totalWeights += datum.weight();
+	  if((int)datum.classValue() == y){
+	      classYWeights += datum.weight();
+	  }	          
+      }	
+          
+      // DL of default rule, no theory DL, only data DL
+      double defDL;
+      if(classYWeights > 0)
+	  defDL = RuleStats.dataDL(expFPRate, 
+				   0.0,
+				   totalWeights,
+				   0.0,
+				   classYWeights);	    
+      else
+	  continue oneClass; // Subsumed by previous rules
+
+      if(Double.isNaN(defDL) || Double.isInfinite(defDL))
+	throw new Exception("Should never happen: "+
+			    "defDL NaN or infinite!");
+      if(m_Debug)
+	System.err.println("The default DL = "+defDL);
+	    
+      data = rulesetForOneClass(expFPRate, data, classIndex, defDL);
+    }
+
+    // Set the default rule
+    RipperRule defRule = new RipperRule();
+    defRule.setConsequent((double)(data.numClasses()-1));
+    m_Ruleset.addElement(defRule);
+	
+    RuleStats defRuleStat = new RuleStats();
+    defRuleStat.setData(data);
+    defRuleStat.setNumAllConds(m_Total);
+    defRuleStat.addAndUpdate(defRule);
+    m_RulesetStats.addElement(defRuleStat);
+
+    for(int z=0; z < m_RulesetStats.size(); z++){
+	RuleStats oneClass = (RuleStats)m_RulesetStats.elementAt(z);
+	for(int xyz=0; xyz < oneClass.getRulesetSize(); xyz++){
+	    double[] classDist = oneClass.getDistributions(xyz);
+	    Utils.normalize(classDist);
+	    if(classDist != null)
+		m_Distributions.addElement(((ClassOrder)m_Filter).distributionsByOriginalIndex(classDist));
+	}	
+    }    
+
+    // free up memory
+    for (int i = 0; i < m_RulesetStats.size(); i++)
+      ((RuleStats) m_RulesetStats.elementAt(i)).cleanUp();
+  }
+    
+  /**
+   * Classify the test instance with the rule learner and provide
+   * the class distributions 
+   *
+   * @param datum the instance to be classified
+   * @return the distribution
+   */
+    public double[] distributionForInstance(Instance datum){
+	try{
+	    for(int i=0; i < m_Ruleset.size(); i++){
+		RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);
+		if(rule.covers(datum))
+		    return (double[])m_Distributions.elementAt(i); 
+	    }
+	}catch(Exception e){
+	    System.err.println(e.getMessage());
+	    e.printStackTrace();
+	}
+	
+	System.err.println("Should never happen!");
+	return new double[datum.classAttribute().numValues()];
+    }
+
+  /** Build a ruleset for the given class according to the given data
+   *
+   * @param expFPRate the expected FP/(FP+FN) used in DL calculation
+   * @param data the given data
+   * @param classIndex the given class index
+   * @param defDL the default DL in the data
+   * @throws Exception if the ruleset can be built properly
+   */
+  protected Instances rulesetForOneClass(double expFPRate, 
+					 Instances data, 
+					 double classIndex,
+					 double defDL)
+    throws Exception {
+	
+    Instances newData = data, growData, pruneData;  	
+    boolean stop = false;
+    FastVector ruleset = new FastVector();		
+	
+    double dl = defDL, minDL = defDL;
+    RuleStats rstats = null;
+    double[] rst;
+	
+    // Check whether data have positive examples
+    boolean defHasPositive = true; // No longer used
+    boolean hasPositive = defHasPositive;
+	
+    /********************** Building stage ***********************/	
+    if(m_Debug)
+      System.err.println("\n*** Building stage ***");
+    
+    while((!stop) && hasPositive){ // Generate new rules until
+      // stopping criteria met
+      RipperRule oneRule;
+      if(m_UsePruning){		
+	/* Split data into Grow and Prune*/
+
+	// We should have stratified the data, but ripper seems
+	// to have a bug that makes it not to do so.  In order
+	// to simulate it more precisely, we do the same thing.
+	//newData.randomize(m_Random);	
+	newData = RuleStats.stratify(newData, m_Folds, m_Random);		
+	Instances[] part = RuleStats.partition(newData, m_Folds);
+	growData=part[0];
+	pruneData=part[1];
+	//growData=newData.trainCV(m_Folds, m_Folds-1);
+	//pruneData=newData.testCV(m_Folds, m_Folds-1);	
+		
+	oneRule = new RipperRule();
+	oneRule.setConsequent(classIndex);  // Must set first
+		
+	if(m_Debug)
+	  System.err.println("\nGrowing a rule ...");  
+	oneRule.grow(growData);             // Build the rule
+	if(m_Debug)
+	  System.err.println("One rule found before pruning:"+
+			     oneRule.toString(m_Class));
+		
+	if(m_Debug)
+	  System.err.println("\nPruning the rule ...");  
+	oneRule.prune(pruneData, false);    // Prune the rule
+	if(m_Debug)
+	  System.err.println("One rule found after pruning:"+
+			     oneRule.toString(m_Class));
+      }
+      else{
+	oneRule = new RipperRule();
+	oneRule.setConsequent(classIndex);  // Must set first
+	if(m_Debug)
+	  System.err.println("\nNo pruning: growing a rule ...");
+	oneRule.grow(newData);             // Build the rule
+	if(m_Debug)
+	  System.err.println("No pruning: one rule found:\n"+
+			     oneRule.toString(m_Class));
+      }
+	    
+      // Compute the DL of this ruleset
+      if(rstats == null){ // First rule
+	rstats = new RuleStats();
+	rstats.setNumAllConds(m_Total);
+	rstats.setData(newData);
+      }
+	    
+      rstats.addAndUpdate(oneRule);		    
+      int last = rstats.getRuleset().size()-1; // Index of last rule
+      dl += rstats.relativeDL(last, expFPRate, m_CheckErr);
+	    
+      if(Double.isNaN(dl) || Double.isInfinite(dl))
+	throw new Exception("Should never happen: dl in "+
+			    "building stage NaN or infinite!");
+      if(m_Debug)
+	System.err.println("Before optimization("+last+
+			   "): the dl = "+dl+" | best: "+minDL);
+	    
+      if(dl < minDL)
+	minDL = dl;  // The best dl so far	
+	    
+      rst = rstats.getSimpleStats(last);	    
+      if(m_Debug)
+	System.err.println("The rule covers: "+rst[0]+
+			   " | pos = " + rst[2] + 
+			   " | neg = " + rst[4]+
+			   "\nThe rule doesn't cover: "+rst[1]+
+			   " | pos = " + rst[5]);
+	    
+      stop = checkStop(rst, minDL, dl);
+	    
+      if(!stop){	  		
+	ruleset.addElement(oneRule);          // Accepted 
+	newData = rstats.getFiltered(last)[1];// Data not covered
+	hasPositive = Utils.gr(rst[5], 0.0);  // Positives remaining?
+	if(m_Debug)
+	  System.err.println("One rule added: has positive? "
+			     +hasPositive);
+      }
+      else{
+	if(m_Debug)
+	  System.err.println("Quit rule");
+	rstats.removeLast(); // Remove last to be re-used
+      }
+    }// while !stop	
+	
+    /******************** Optimization stage *******************/
+    RuleStats finalRulesetStat = null;
+    if(m_UsePruning){	 
+      for(int z=0; z < m_Optimizations; z++){
+	if(m_Debug)
+	  System.err.println("\n*** Optimization: run #"
+			     +z+" ***");
+		
+	newData = data;		    
+	finalRulesetStat = new RuleStats();
+	finalRulesetStat.setData(newData);
+	finalRulesetStat.setNumAllConds(m_Total);
+	int position=0;
+	stop = false;
+	boolean isResidual = false;	    
+	hasPositive = defHasPositive;		    
+	dl = minDL = defDL;
+		
+      oneRule:    
+	while(!stop && hasPositive){			
+		    
+	  isResidual = (position>=ruleset.size()); // Cover residual positive examples  
+	  // Re-do shuffling and stratification    
+	  //newData.randomize(m_Random);	
+	  newData = RuleStats.stratify(newData, m_Folds, m_Random);
+	  Instances[] part = RuleStats.partition(newData, m_Folds);
+	  growData=part[0];
+	  pruneData=part[1];
+	  //growData=newData.trainCV(m_Folds, m_Folds-1);
+	  //pruneData=newData.testCV(m_Folds, m_Folds-1);	   
+	  RipperRule finalRule;
+		    
+	  if(m_Debug)
+	    System.err.println("\nRule #"+position +
+			       "| isResidual?" + isResidual+
+			       "| data size: "+newData.sumOfWeights());
+		    
+	  if(isResidual){
+	    RipperRule newRule = new RipperRule();   
+	    newRule.setConsequent(classIndex);
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+				 " a new rule ..."); 
+	    newRule.grow(growData);
+	    newRule.prune(pruneData, false);
+	    finalRule = newRule;
+	    if(m_Debug)
+	      System.err.println("\nNew rule found: "+
+				 newRule.toString(m_Class));
+	  }
+	  else{
+	    RipperRule oldRule = (RipperRule)ruleset.elementAt(position);
+	    boolean covers = false;
+	    // Test coverage of the next old rule
+	    for(int i=0; i<newData.numInstances(); i++)
+	      if(oldRule.covers(newData.instance(i))){
+		covers = true;
+		break;
+	      }
+			
+	    if(!covers){// Null coverage, no variants can be generated
+	      finalRulesetStat.addAndUpdate(oldRule);
+	      position++;
+	      continue oneRule;
+	    }  
+			
+	    // 2 variants 
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+				 " Replace ..."); 
+	    RipperRule replace = new RipperRule();   
+	    replace.setConsequent(classIndex);
+	    replace.grow(growData);
+			
+	    // Remove the pruning data covered by the following
+	    // rules, then simply compute the error rate of the
+	    // current rule to prune it.  According to Ripper,
+	    // it's equivalent to computing the error of the 
+	    // whole ruleset -- is it true?
+	    pruneData = RuleStats.rmCoveredBySuccessives(pruneData,ruleset, position);      	
+	    replace.prune(pruneData, true);
+			
+	    if(m_Debug)
+	      System.err.println("\nGrowing and pruning"+
+				 " Revision ..."); 
+	    RipperRule revision = (RipperRule)oldRule.copy(); 
+			
+	    // For revision, first rm the data covered by the old rule
+	    Instances newGrowData = new Instances(growData, 0);
+	    for(int b=0; b<growData.numInstances(); b++){
+	      Instance inst = growData.instance(b);
+	      if(revision.covers(inst))
+		newGrowData.add(inst);
+	    }
+	    revision.grow(newGrowData);	      
+	    revision.prune(pruneData, true);
+			
+	    double[][] prevRuleStats = new double[position][6];
+	    for(int c=0; c < position; c++)
+		prevRuleStats[c] = finalRulesetStat.getSimpleStats(c);
+
+	    // Now compare the relative DL of variants
+	    FastVector tempRules = (FastVector)ruleset.copyElements();
+	    tempRules.setElementAt(replace, position);
+			
+	    RuleStats repStat = new RuleStats(data, tempRules);
+	    repStat.setNumAllConds(m_Total);
+	    repStat.countData(position, newData, prevRuleStats);
+	    //repStat.countData();
+	    rst = repStat.getSimpleStats(position);	    
+	    if(m_Debug)
+	      System.err.println("Replace rule covers: "+rst[0]+
+				 " | pos = " + rst[2] + 
+				 " | neg = " + rst[4]+
+				 "\nThe rule doesn't cover: "+rst[1]+
+				 " | pos = " + rst[5]);
+			
+	    double repDL = repStat.relativeDL(position, expFPRate,
+					      m_CheckErr);
+	    if(m_Debug)
+	      System.err.println("\nReplace: "+
+				 replace.toString(m_Class)
+				 +" |dl = "+repDL); 
+			
+	    if(Double.isNaN(repDL) || Double.isInfinite(repDL))
+	      throw new Exception("Should never happen: repDL"+
+				  "in optmz. stage NaN or "+
+				  "infinite!");
+			
+	    tempRules.setElementAt(revision, position);
+	    RuleStats revStat = new RuleStats(data, tempRules);
+	    revStat.setNumAllConds(m_Total);
+	    revStat.countData(position, newData, prevRuleStats);
+	    //revStat.countData();
+	    double revDL = revStat.relativeDL(position, expFPRate,
+					      m_CheckErr);
+			
+	    if(m_Debug)
+	      System.err.println("Revision: "
+				 + revision.toString(m_Class)
+				 +" |dl = "+revDL);
+			
+	    if(Double.isNaN(revDL) || Double.isInfinite(revDL))
+	      throw new Exception("Should never happen: revDL"+
+				  "in optmz. stage NaN or "+
+				  "infinite!");
+			
+	    rstats = new RuleStats(data, ruleset);
+	    rstats.setNumAllConds(m_Total);
+	    rstats.countData(position, newData, prevRuleStats);
+	    //rstats.countData();
+	    double oldDL = rstats.relativeDL(position, expFPRate,
+					     m_CheckErr);
+			
+	    if(Double.isNaN(oldDL) || Double.isInfinite(oldDL))
+	      throw new Exception("Should never happen: oldDL"+
+				  "in optmz. stage NaN or "+
+				  "infinite!");
+	    if(m_Debug)
+	      System.err.println("Old rule: "+
+				 oldRule.toString(m_Class)
+				 +" |dl = "+oldDL); 
+			
+	    if(m_Debug)
+	      System.err.println("\nrepDL: "+repDL+ 
+				 "\nrevDL: "+revDL+
+				 "\noldDL: "+oldDL);
+			
+	    if((oldDL <= revDL) && (oldDL <= repDL))
+	      finalRule = oldRule; // Old the best
+	    else if(revDL <= repDL)
+	      finalRule = revision; // Revision the best
+	    else
+	      finalRule = replace; // Replace the best  
+	  }		
+		    
+	  finalRulesetStat.addAndUpdate(finalRule);  	 
+	  rst = finalRulesetStat.getSimpleStats(position);
+		    
+	  if(isResidual){
+			
+	    dl += finalRulesetStat.relativeDL(position, 
+					      expFPRate,
+					      m_CheckErr);
+	    if(m_Debug)
+	      System.err.println("After optimization: the dl"
+				 +"="+dl+" | best: "+minDL);
+			
+	    if(dl < minDL)
+	      minDL = dl;  // The best dl so far
+			
+	    stop = checkStop(rst, minDL, dl);
+	    if(!stop)
+	      ruleset.addElement(finalRule); // Accepted 
+	    else{
+	      finalRulesetStat.removeLast(); // Remove last to be re-used
+	      position--;
+	    }
+	  }
+	  else
+	    ruleset.setElementAt(finalRule, position); // Accepted 
+
+	  if(m_Debug){
+	    System.err.println("The rule covers: "+rst[0]+
+			       " | pos = " + rst[2] + 
+			       " | neg = " + rst[4]+
+			       "\nThe rule doesn't cover: "+rst[1]+
+			       " | pos = " + rst[5]);		
+	    System.err.println("\nRuleset so far: ");
+	    for(int x=0; x<ruleset.size(); x++)
+	      System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
+	    System.err.println();
+	  }
+		    
+	  //Data not covered	
+	  if(finalRulesetStat.getRulesetSize() > 0)// If any rules	
+	    newData = finalRulesetStat.getFiltered(position)[1]; 
+	  hasPositive = Utils.gr(rst[5], 0.0); //Positives remaining? 
+	  position++;
+	} // while !stop && hasPositive
+		
+	if(ruleset.size() > (position+1)){ // Hasn't gone through yet
+	  for(int k=position+1; k<ruleset.size(); k++)
+	    finalRulesetStat.addAndUpdate((Rule)ruleset.elementAt(k));
+	}
+	if(m_Debug)
+	  System.err.println("\nDeleting rules to decrease"+
+			     " DL of the whole ruleset ..."); 
+	finalRulesetStat.reduceDL(expFPRate, m_CheckErr);
+	if(m_Debug){
+	  int del = ruleset.size() -
+	    finalRulesetStat.getRulesetSize(); 
+	  System.err.println(del+" rules are deleted"+
+			     " after DL reduction procedure");
+	}
+	ruleset = finalRulesetStat.getRuleset();
+	rstats = finalRulesetStat;	      	    
+		
+      } // For each run of optimization
+    } // if pruning is used
+	
+    // Concatenate the ruleset for this class to the whole ruleset
+    if(m_Debug){
+      System.err.println("\nFinal ruleset: ");
+      for(int x=0; x<ruleset.size(); x++)
+	System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
+      System.err.println();
+    }
+	
+    m_Ruleset.appendElements(ruleset);
+    m_RulesetStats.addElement(rstats);
+	
+    if(ruleset.size() > 0)// If any rules for this class
+      return rstats.getFiltered(ruleset.size()-1)[1]; // Data not 
+    else                                                // covered
+      return data; 
+  }   
+    
+  /**
+   * Check whether the stopping criterion meets
+   *
+   * @param rst the statistic of the ruleset
+   * @param minDL the min description length so far
+   * @param dl the current description length of the ruleset
+   * @return true if stop criterion meets, false otherwise
+   */
+  private boolean checkStop(double[] rst, double minDL, double dl){
+	
+    if(dl > minDL+MAX_DL_SURPLUS){
+      if(m_Debug)
+	System.err.println("DL too large: "+dl+" | "+minDL);
+      return true;
+    }
+    else if(!Utils.gr(rst[2], 0.0)){// Covered positives
+      if(m_Debug)
+	System.err.println("Too few positives.");
+      return true;
+    }	
+    else if((rst[4]/rst[0]) >= 0.5){// Err rate
+      if(m_CheckErr){
+	if(m_Debug)
+	  System.err.println("Error too large: "+
+			     rst[4] + "/" + rst[0]);
+	return  true;
+      }
+      else
+	return false;
+    }		
+    else{// Not stops
+      if(m_Debug)
+	System.err.println("Continue.");
+      return  false;
+    }				
+  }
+ 
+  /**
+   * Prints the all the rules of the rule learner.
+   *
+   * @return a textual description of the classifier
+   */
+  public String toString() {
+    if (m_Ruleset == null) 
+      return "JRIP: No model built yet.";
+	
+    StringBuffer sb = new StringBuffer("JRIP rules:\n"+
+				       "===========\n\n"); 
+    for(int j=0; j<m_RulesetStats.size(); j++){
+      RuleStats rs = (RuleStats)m_RulesetStats.elementAt(j);
+      FastVector rules = rs.getRuleset();
+      for(int k=0; k<rules.size(); k++){
+	double[] simStats = rs.getSimpleStats(k);
+	sb.append(((RipperRule)rules.elementAt(k)).toString(m_Class)
+		  + " ("+simStats[0]+"/"+simStats[4]+")\n");
+      }			    
+    }
+    if(m_Debug){
+      System.err.println("Inside m_Ruleset");
+      for(int i=0; i<m_Ruleset.size(); i++)
+	System.err.println(((RipperRule)m_Ruleset.elementAt(i)).toString(m_Class));
+    }
+    sb.append("\nNumber of Rules : " 
+	      + m_Ruleset.size() + "\n");
+    return sb.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6041 $");
+  }
+    
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new JRip(), args);
+  } 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/M5Rules.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/M5Rules.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/M5Rules.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    M5Rules.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.trees.m5.M5Base;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates a decision list for regression problems using separate-and-conquer. In each iteration it builds a model tree using M5 and makes the "best" leaf into a rule.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Geoffrey Holmes, Mark Hall, Eibe Frank: Generating Rule Sets from Model Trees. In: Twelfth Australian Joint Conference on Artificial Intelligence, 1-12, 1999.<br/>
+ * <br/>
+ * Ross J. Quinlan: Learning with Continuous Classes. In: 5th Australian Joint Conference on Artificial Intelligence, Singapore, 343-348, 1992.<br/>
+ * <br/>
+ * Y. Wang, I. H. Witten: Induction of model trees for predicting continuous classes. In: Poster papers of the 9th European Conference on Machine Learning, 1997.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Holmes1999,
+ *    author = {Geoffrey Holmes and Mark Hall and Eibe Frank},
+ *    booktitle = {Twelfth Australian Joint Conference on Artificial Intelligence},
+ *    pages = {1-12},
+ *    publisher = {Springer},
+ *    title = {Generating Rule Sets from Model Trees},
+ *    year = {1999}
+ * }
+ * 
+ * &#64;inproceedings{Quinlan1992,
+ *    address = {Singapore},
+ *    author = {Ross J. Quinlan},
+ *    booktitle = {5th Australian Joint Conference on Artificial Intelligence},
+ *    pages = {343-348},
+ *    publisher = {World Scientific},
+ *    title = {Learning with Continuous Classes},
+ *    year = {1992}
+ * }
+ * 
+ * &#64;inproceedings{Wang1997,
+ *    author = {Y. Wang and I. H. Witten},
+ *    booktitle = {Poster papers of the 9th European Conference on Machine Learning},
+ *    publisher = {Springer},
+ *    title = {Induction of model trees for predicting continuous classes},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Use unpruned tree/rules</pre>
+ * 
+ * <pre> -U
+ *  Use unsmoothed predictions</pre>
+ * 
+ * <pre> -R
+ *  Build regression tree/rule rather than a model tree/rule</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf
+ *  (default 4)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.11 $
+ */
+public class M5Rules 
+  extends M5Base
+  implements TechnicalInformationHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -1746114858746563180L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Generates a decision list for regression problems using " 
+      + "separate-and-conquer. In each iteration it builds a "
+      + "model tree using M5 and makes the \"best\" "
+      + "leaf into a rule.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Constructor
+   */
+  public M5Rules() {
+    super();
+    setGenerateRules(true);
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Geoffrey Holmes and Mark Hall and Eibe Frank");
+    result.setValue(Field.TITLE, "Generating Rule Sets from Model Trees");
+    result.setValue(Field.BOOKTITLE, "Twelfth Australian Joint Conference on Artificial Intelligence");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.PAGES, "1-12");
+    result.setValue(Field.PUBLISHER, "Springer");
+    
+    result.add(super.getTechnicalInformation());
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.11 $");
+  }
+
+  /**
+   * Main method by which this class can be tested
+   * 
+   * @param args an array of options
+   */
+  public static void main(String[] args) {
+    runClassifier(new M5Rules(), args);
+  } 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/NNge.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/NNge.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/NNge.java	(revision 29)
@@ -0,0 +1,1727 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NNge.java
+ *    Copyright (C) 2002 Brent Martin
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.UpdateableClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Nearest-neighbor-like algorithm using non-nested generalized exemplars (which are hyperrectangles that can be viewed as if-then rules). For more information, see <br/>
+ * <br/>
+ * Brent Martin (1995). Instance-Based learning: Nearest Neighbor With Generalization. Hamilton, New Zealand.<br/>
+ * <br/>
+ * Sylvain Roy (2002). Nearest Neighbor With Generalization. Christchurch, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;mastersthesis{Martin1995,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Brent Martin},
+ *    school = {University of Waikato},
+ *    title = {Instance-Based learning: Nearest Neighbor With Generalization},
+ *    year = {1995}
+ * }
+ * 
+ * &#64;unpublished{Roy2002,
+ *    address = {Christchurch, New Zealand},
+ *    author = {Sylvain Roy},
+ *    school = {University of Canterbury},
+ *    title = {Nearest Neighbor With Generalization},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -G &lt;value&gt;
+ *  Number of attempts of generalisation.
+ * </pre>
+ * 
+ * <pre> -I &lt;value&gt;
+ *  Number of folder for computing the mutual information.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Brent Martin (bim20@cosc.canterbury.ac.nz)
+ * @author Sylvain Roy (sro33@student.canterbury.ac.nz)
+ * @version $Revision: 5956 $
+ */
+public class NNge 
+  extends AbstractClassifier 
+  implements UpdateableClassifier, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4084742275553788972L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Nearest-neighbor-like algorithm using non-nested generalized exemplars "
+      + "(which are hyperrectangles that can be viewed as if-then rules). For more "
+      + "information, see \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.MASTERSTHESIS);
+    result.setValue(Field.AUTHOR, "Brent Martin");
+    result.setValue(Field.YEAR, "1995");
+    result.setValue(Field.TITLE, "Instance-Based learning: Nearest Neighbor With Generalization");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+    
+    additional = result.add(Type.UNPUBLISHED);
+    additional.setValue(Field.AUTHOR, "Sylvain Roy");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.TITLE, "Nearest Neighbor With Generalization");
+    additional.setValue(Field.SCHOOL, "University of Canterbury");
+    additional.setValue(Field.ADDRESS, "Christchurch, New Zealand");
+    
+    return result;
+  }
+
+  /**
+   * Implements Exemplar as used by NNge : parallel axis hyperrectangle.
+   */
+  private class Exemplar 
+    extends Instances {
+    
+    /** for serialization */
+    static final long serialVersionUID = 3960180128928697216L;
+    
+    /** List of all the Exemplar */
+    private Exemplar previous = null;
+    private Exemplar next = null;
+	
+    /** List of all the Exemplar with the same class */
+    private Exemplar previousWithClass = null;
+    private Exemplar nextWithClass = null;
+
+    /** The NNge which owns this Exemplar */
+    private NNge m_NNge;
+
+    /** class of the Exemplar */
+    private double m_ClassValue;
+
+    /** Number of correct prediction for this examplar */
+    private int m_PositiveCount = 1;
+    
+    /** Number of incorrect prediction for this examplar */
+    private int m_NegativeCount = 0;
+
+    /** The max borders of the rectangle for numeric attributes */
+    private double[] m_MaxBorder;
+	                     
+    /** The min borders of the rectangle for numeric attributes */
+    private double[] m_MinBorder;
+	                     
+    /** The ranges of the hyperrectangle for nominal attributes */
+    private boolean[][] m_Range;
+	                     
+    /** the arrays used by preGeneralise */
+    private double[] m_PreMaxBorder = null;
+    private double[] m_PreMinBorder = null;
+    private boolean[][] m_PreRange = null;
+    private Instance m_PreInst = null;
+
+
+    /**
+     * Build a new empty Exemplar
+     *
+     * @param nnge the classifier which owns this Exemplar
+     * @param inst the instances from which the header information is to be taken
+     * @param size the capacity of the Exemplar
+     * @param classV the class of the Exemplar
+     */
+    private Exemplar (NNge nnge, Instances inst, int size, double classV){
+
+      super(inst, size);
+      m_NNge = nnge;
+      m_ClassValue = classV;
+      m_MinBorder = new double[numAttributes()];
+      m_MaxBorder = new double[numAttributes()];
+      m_Range = new boolean[numAttributes()][];
+      for(int i = 0; i < numAttributes(); i++){
+	if(attribute(i).isNumeric()){
+	  m_MinBorder[i] = Double.POSITIVE_INFINITY;
+	  m_MaxBorder[i] = Double.NEGATIVE_INFINITY;
+	  m_Range[i] = null;
+	} else {
+	  m_MinBorder[i] = Double.NaN;
+	  m_MaxBorder[i] = Double.NaN;
+	  m_Range[i] = new boolean[attribute(i).numValues() + 1];
+	  for(int j = 0; j < attribute(i).numValues() + 1; j++){
+	    m_Range[i][j] = false;
+	  }
+	}
+      }
+    }
+
+
+    /**
+     * Generalise the Exemplar with inst
+     *
+     * @param inst the new example used for the generalisation
+     * @throws Exception if either the class of inst is not equal to the class of the Exemplar or inst misses a value.
+     */
+    private void generalise(Instance inst) throws Exception {
+
+      if(m_ClassValue != inst.classValue())
+	throw new Exception("Exemplar.generalise : Incompatible instance's class.");
+
+      add(inst);
+
+      /* extends each range in order to cover inst */
+      for(int i = 0; i < numAttributes(); i++){
+	 
+	if(inst.isMissing(i))
+	  throw new Exception("Exemplar.generalise : Generalisation with missing feature impossible.");
+
+	if(i == classIndex())
+	  continue;
+	    
+	if(attribute(i).isNumeric()){
+	  if(m_MaxBorder[i] < inst.value(i)) 
+	    m_MaxBorder[i] = inst.value(i);
+	  if(inst.value(i) < m_MinBorder[i]) 
+	    m_MinBorder[i] = inst.value(i);  
+		
+	} else {
+	  m_Range[i][(int) inst.value(i)] = true;
+	}
+      }
+    } 
+
+
+    /**
+     * pre-generalise the Exemplar with inst
+     * i.e. the boundaries of the Exemplar include inst but the Exemplar still doesn't 'own' inst.
+     * To be complete, the generalisation must be validated with validateGeneralisation.
+     * the generalisation can be canceled with cancelGeneralisation.
+     * @param inst the new example used for the generalisation
+     * @throws Exception if either the class of inst is not equal to the class of the Exemplar or inst misses a value.
+     */
+    private void preGeneralise(Instance inst) throws Exception {
+	
+      if(m_ClassValue != inst.classValue())
+	throw new Exception("Exemplar.preGeneralise : Incompatible instance's class.");
+
+      m_PreInst = inst;
+
+      /* save the current state */
+      m_PreRange = new boolean[numAttributes()][];
+      m_PreMinBorder = new double[numAttributes()];
+      m_PreMaxBorder = new double[numAttributes()];
+      for(int i = 0; i < numAttributes(); i++){
+	if(attribute(i).isNumeric()){
+	  m_PreMinBorder[i] = m_MinBorder[i];
+	  m_PreMaxBorder[i] = m_MaxBorder[i];
+	} else {
+	  m_PreRange[i] = new boolean[attribute(i).numValues() + 1];
+	  for(int j = 0; j < attribute(i).numValues() + 1; j++){
+	    m_PreRange[i][j] = m_Range[i][j];
+	  }
+	}
+      }
+
+      /* perform the pre-generalisation */
+      for(int i = 0; i < numAttributes(); i++){
+	if(inst.isMissing(i))
+	  throw new Exception("Exemplar.preGeneralise : Generalisation with missing feature impossible.");
+	if(i == classIndex())
+	  continue;
+	if(attribute(i).isNumeric()){
+	  if(m_MaxBorder[i] < inst.value(i)) 
+	    m_MaxBorder[i] = inst.value(i);
+	  if(inst.value(i) < m_MinBorder[i]) 
+	    m_MinBorder[i] = inst.value(i);  
+	} else {
+	  m_Range[i][(int) inst.value(i)] = true;
+	}
+      }
+    }
+
+
+    /**
+     * Validates a generalisation started with preGeneralise.
+     * Watch out, preGeneralise must have been called before.
+     *
+     * @throws Exception is thrown if preGeneralise hasn't been called before
+     */
+    private void validateGeneralisation() throws Exception {
+      if(m_PreInst == null){
+	throw new Exception("Exemplar.validateGeneralisation : validateGeneralisation called without previous call to preGeneralise!");
+      }
+      add(m_PreInst);
+      m_PreRange = null;
+      m_PreMinBorder = null;
+      m_PreMaxBorder = null;
+    }
+
+    
+    /**
+     * Cancels a generalisation started with preGeneralise.
+     * Watch out, preGeneralise must have been called before.
+     *
+     * @throws Exception is thrown if preGeneralise hasn't been called before
+     */
+    private void cancelGeneralisation() throws Exception {
+      if(m_PreInst == null){
+	throw new Exception("Exemplar.cancelGeneralisation : cancelGeneralisation called without previous call to preGeneralise!");
+      }
+      m_PreInst = null;
+      m_Range = m_PreRange;
+      m_MinBorder = m_PreMinBorder;
+      m_MaxBorder = m_PreMaxBorder;
+      m_PreRange = null;
+      m_PreMinBorder = null;
+      m_PreMaxBorder = null;
+    }
+
+
+    /**
+     * return true if inst is held by this Exemplar, false otherwise
+     *
+     * @param inst an Instance
+     * @return true if inst is held by this hyperrectangle, false otherwise
+     */
+    private boolean holds(Instance inst) {
+	
+      if(numInstances() == 0)
+	return false;
+
+      for(int i = 0; i < numAttributes(); i++){
+	if(i != classIndex() && !holds(i, inst.value(i)))
+	  return false;
+      }
+      return true;
+    }
+
+
+    /**
+     * return true if value is inside the Exemplar along the attrIndex attribute.
+     *
+     * @param attrIndex the index of an attribute 
+     * @param value a value along the attrIndexth attribute
+     * @return true if value is inside the Exemplar along the attrIndex attribute.
+     */
+    private boolean holds(int attrIndex, double value) {
+	
+      if (numAttributes() == 0)
+	return false;
+	
+      if(attribute(attrIndex).isNumeric())
+	return(m_MinBorder[attrIndex] <= value && value <= m_MaxBorder[attrIndex]);
+      else
+	return m_Range[attrIndex][(int) value];
+    }
+
+
+    /**
+     * Check if the Examplar overlaps ex
+     *
+     * @param ex an Exemplar
+     * @return true if ex is overlapped by the Exemplar
+     * @throws Exception
+     */
+    private boolean overlaps(Exemplar ex) {
+
+      if(ex.isEmpty() || isEmpty())
+	return false;
+
+      for (int i = 0; i < numAttributes(); i++){
+	    
+	if(i == classIndex()){
+	  continue;
+	}
+	if (attribute(i).isNumeric() && 
+	    (ex.m_MaxBorder[i] < m_MinBorder[i] || ex.m_MinBorder[i] > m_MaxBorder[i])){
+	  return false;
+	}
+	if (attribute(i).isNominal()) {
+	  boolean in = false;
+	  for (int j = 0; j < attribute(i).numValues() + 1; j++){
+	    if(m_Range[i][j] && ex.m_Range[i][j]){
+	      in = true;
+	      break;
+	    }
+	  }
+	  if(!in) return false;
+	}
+      }
+      return true;
+    }
+
+
+    /** 
+     * Compute the distance between the projection of inst and this Exemplar along the attribute attrIndex.
+     * If inst misses its value along the attribute, the function returns 0.
+     *
+     * @param inst an instance
+     * @param attrIndex the index of the attribute 
+     * @return the distance between the projection of inst and this Exemplar along the attribute attrIndex.
+     */
+    private double attrDistance(Instance inst, int attrIndex) {
+
+      if(inst.isMissing(attrIndex))
+	return 0;
+
+      /* numeric attribute */
+      if(attribute(attrIndex).isNumeric()){
+
+	double norm = m_NNge.m_MaxArray[attrIndex] - m_NNge.m_MinArray[attrIndex];
+	if(norm <= 0)
+	  norm = 1;
+
+	if (m_MaxBorder[attrIndex] < inst.value(attrIndex)) {
+	  return (inst.value(attrIndex) - m_MaxBorder[attrIndex]) / norm;
+	} else if (inst.value(attrIndex) < m_MinBorder[attrIndex]) {
+	  return (m_MinBorder[attrIndex] - inst.value(attrIndex)) / norm;
+	} else {
+	  return 0;
+	}
+
+	/* nominal attribute */
+      } else {
+	if(holds(attrIndex, inst.value(attrIndex))){
+	  return 0;
+	} else {
+	  return 1;
+	}
+      }
+    }
+
+
+    /**
+     * Returns the square of the distance between inst and the Exemplar. 
+     * 
+     * @param inst an instance
+     * @return the squared distance between inst and the Exemplar.
+     */
+    private double squaredDistance(Instance inst) {
+	
+      double sum = 0, term;
+      int numNotMissingAttr = 0;
+      for(int i = 0; i < inst.numAttributes(); i++){
+	    
+	if(i == classIndex())
+	  continue;
+	    
+	term = m_NNge.attrWeight(i) * attrDistance(inst, i);
+	term = term * term;
+	sum += term;
+
+	if (!inst.isMissing(i))
+	  numNotMissingAttr++;
+
+      }
+	
+      if(numNotMissingAttr == 0){
+	return 0;
+      } else {
+	return sum / (double) (numNotMissingAttr * numNotMissingAttr);
+      }
+    }
+
+
+    /**
+     * Return the weight of the Examplar
+     *
+     * @return the weight of the Examplar.
+     */
+    private double weight(){
+      return ((double) (m_PositiveCount + m_NegativeCount)) / ((double) m_PositiveCount);
+    }
+
+
+    /**
+     * Return the class of the Exemplar
+     *
+     * @return the class of this exemplar as a double (weka format)
+     */
+    private double classValue(){
+      return m_ClassValue;
+    }
+
+
+    /**
+     * Returns the value of the inf border of the Exemplar. 
+     *
+     * @param attrIndex the index of the attribute
+     * @return the value of the inf border for this attribute
+     * @throws Exception is thrown either if the attribute is nominal or if the Exemplar is empty
+     */
+    private double getMinBorder(int attrIndex) throws Exception {
+      if(!attribute(attrIndex).isNumeric())
+	throw new Exception("Exception.getMinBorder : not numeric attribute !");
+      if(numInstances() == 0)
+	throw new Exception("Exception.getMinBorder : empty Exemplar !");
+      return m_MinBorder[attrIndex];
+    }
+
+
+    /**
+     * Returns the value of the sup border of the hyperrectangle
+     * Returns NaN if the HyperRectangle doesn't have any border for this attribute 
+     *
+     * @param attrIndex the index of the attribute
+     * @return the value of the sup border for this attribute
+     * @throws Exception is thrown either if the attribute is nominal or if the Exemplar is empty
+     */
+    private double getMaxBorder(int attrIndex) throws Exception {
+      if(!attribute(attrIndex).isNumeric())
+	throw new Exception("Exception.getMaxBorder : not numeric attribute !");
+      if(numInstances() == 0)
+	throw new Exception("Exception.getMaxBorder : empty Exemplar !");
+      return m_MaxBorder[attrIndex];
+    }
+
+
+    /**
+     * Returns the number of positive classifications
+     *
+     * @return the number of positive classifications
+     */
+    private int getPositiveCount(){
+      return m_PositiveCount;
+    }
+
+
+    /**
+     * Returns the number of negative classifications
+     *
+     * @return the number of negative classifications
+     */
+    private int getNegativeCount(){
+      return m_NegativeCount;
+    }
+
+
+    /**
+     * Set the number of positive classifications
+     *
+     * @param value an integer value (greater than 0 is wise...)
+     */
+    private void setPositiveCount(int value) {
+      m_PositiveCount = value;
+    }
+
+
+    /**
+     * Set the number of negative classifications
+     *
+     * @param value an integer value
+     */
+    private void setNegativeCount(int value) {
+      m_NegativeCount = value;
+    }
+
+
+    /**
+     * Increment the number of positive Classifications
+     */
+    private void incrPositiveCount(){
+      m_PositiveCount++;
+    }
+
+
+    /**
+     * Increment the number of negative Classifications
+     */
+    private void incrNegativeCount(){
+      m_NegativeCount++;
+    }
+
+
+    /**
+     * Returns true if the Exemplar is empty (i.e. doesn't yield any Instance)
+     *
+     * @return true if the Exemplar is empty, false otherwise
+     */
+    public boolean isEmpty(){
+      return (numInstances() == 0);
+    }
+
+    
+    /**
+     * Returns a description of this Exemplar
+     *
+     * @return A string that describes this Exemplar
+     */
+    private String toString2(){
+      String s;
+      Enumeration enu = null;
+      s = "Exemplar[";
+      if (numInstances() == 0) {
+	return s + "Empty]";
+      }
+      s += "{";
+      enu = enumerateInstances();
+      while(enu.hasMoreElements()){
+	s = s + "<" + enu.nextElement().toString() + "> ";
+      }
+      s = s.substring(0, s.length()-1);
+      s = s + "} {" + toRules() + "} p=" + m_PositiveCount + " n=" + m_NegativeCount + "]";
+      return s;
+    }
+
+
+    /**
+     * Returns a string of the rules induced by this examplar
+     *
+     * @return a string of the rules induced by this examplar
+     */
+    private String toRules(){
+
+      if (numInstances() == 0)
+	return "No Rules (Empty Exemplar)";
+
+      String s = "", sep = "";
+	
+      for(int i = 0; i < numAttributes(); i++){
+	    
+	if(i == classIndex())
+	  continue;
+	    
+	if(attribute(i).isNumeric()){
+	  if(m_MaxBorder[i] != m_MinBorder[i]){
+	    s += sep + m_MinBorder[i] + "<=" + attribute(i).name() + "<=" + m_MaxBorder[i];
+	  } else {
+	    s += sep + attribute(i).name() + "=" + m_MaxBorder[i];
+	  }
+	  sep = " ^ ";
+	    
+	} else {
+	  s += sep + attribute(i).name() + " in {";
+	  String virg = "";
+	  for(int j = 0; j < attribute(i).numValues() + 1; j++){
+	    if(m_Range[i][j]){
+	      s+= virg;
+	      if(j == attribute(i).numValues())
+		s += "?";
+	      else
+		s += attribute(i).value(j);
+	      virg = ",";
+	    }
+	  }
+	  s+="}";
+	  sep = " ^ ";
+	}	    
+      }
+      s += "  ("+numInstances() +")";
+      return s;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5956 $");
+    }
+  }
+
+
+
+  /** An empty instances to keep the headers, the classIndex, etc... */
+  private Instances m_Train;
+
+  /** The list of Exemplars */
+  private Exemplar m_Exemplars;
+
+  /** The lists of Exemplars by class */
+  private Exemplar m_ExemplarsByClass[];
+
+  /** The minimum values for numeric attributes. */
+  double [] m_MinArray;
+
+  /** The maximum values for numeric attributes. */
+  double [] m_MaxArray;
+
+  /** The number of try for generalisation */
+  private int m_NumAttemptsOfGene = 5;
+
+  /** The number of folder for the Mutual Information */
+  private int m_NumFoldersMI = 5;
+
+  /** Values to use for missing value */
+  private double [] m_MissingVector;
+
+  /** MUTUAL INFORMATION'S DATAS */
+  /* numeric attributes */
+  private int [][][] m_MI_NumAttrClassInter;
+  private int [][] m_MI_NumAttrInter;
+  private double [] m_MI_MaxArray;
+  private double [] m_MI_MinArray;
+  /* nominal attributes */
+  private int [][][] m_MI_NumAttrClassValue;
+  private int [][] m_MI_NumAttrValue;
+  /* both */
+  private int [] m_MI_NumClass;
+  private int m_MI_NumInst;
+  private double [] m_MI;
+
+
+
+  /** MAIN FUNCTIONS OF THE CLASSIFIER */
+
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates a classifier. Must initialize all fields of the classifier
+   * that are not being set via options (ie. multiple calls of buildClassifier
+   * must always lead to the same result). Must not change the dataset
+   * in any way.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the classifier has not been 
+   * generated successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    /* initialize the classifier */
+
+    m_Train = new Instances(data, 0);
+    m_Exemplars = null;
+    m_ExemplarsByClass = new Exemplar[m_Train.numClasses()];
+    for(int i = 0; i < m_Train.numClasses(); i++){
+      m_ExemplarsByClass[i] = null;
+    }
+    m_MaxArray = new double[m_Train.numAttributes()];
+    m_MinArray = new double[m_Train.numAttributes()];
+    for(int i = 0; i < m_Train.numAttributes(); i++){
+      m_MinArray[i] = Double.POSITIVE_INFINITY;
+      m_MaxArray[i] = Double.NEGATIVE_INFINITY;
+    }
+
+    m_MI_MinArray = new double [data.numAttributes()];
+    m_MI_MaxArray = new double [data.numAttributes()];
+    m_MI_NumAttrClassInter = new int[data.numAttributes()][][];
+    m_MI_NumAttrInter = new int[data.numAttributes()][];
+    m_MI_NumAttrClassValue = new int[data.numAttributes()][][];
+    m_MI_NumAttrValue = new int[data.numAttributes()][];
+    m_MI_NumClass = new int[data.numClasses()];
+    m_MI = new double[data.numAttributes()];
+    m_MI_NumInst = 0;
+    for(int cclass = 0; cclass < data.numClasses(); cclass++)
+      m_MI_NumClass[cclass] = 0;
+    for (int attrIndex = 0; attrIndex < data.numAttributes(); attrIndex++) {
+	    
+      if(attrIndex == data.classIndex())
+	continue;
+	    
+      m_MI_MaxArray[attrIndex] = m_MI_MinArray[attrIndex] = Double.NaN;
+      m_MI[attrIndex] = Double.NaN;
+	    
+      if(data.attribute(attrIndex).isNumeric()){
+	m_MI_NumAttrInter[attrIndex] = new int[m_NumFoldersMI];
+	for(int inter = 0; inter < m_NumFoldersMI; inter++){
+	  m_MI_NumAttrInter[attrIndex][inter] = 0;
+	}
+      } else {
+	m_MI_NumAttrValue[attrIndex] = new int[data.attribute(attrIndex).numValues() + 1];
+	for(int attrValue = 0; attrValue < data.attribute(attrIndex).numValues() + 1; attrValue++){
+	  m_MI_NumAttrValue[attrIndex][attrValue] = 0;
+	}
+      }
+	    
+      m_MI_NumAttrClassInter[attrIndex] = new int[data.numClasses()][];
+      m_MI_NumAttrClassValue[attrIndex] = new int[data.numClasses()][];
+
+      for(int cclass = 0; cclass < data.numClasses(); cclass++){
+	if(data.attribute(attrIndex).isNumeric()){
+	  m_MI_NumAttrClassInter[attrIndex][cclass] = new int[m_NumFoldersMI];
+	  for(int inter = 0; inter < m_NumFoldersMI; inter++){
+	    m_MI_NumAttrClassInter[attrIndex][cclass][inter] = 0;
+	  }
+	} else if(data.attribute(attrIndex).isNominal()){
+	  m_MI_NumAttrClassValue[attrIndex][cclass] = new int[data.attribute(attrIndex).numValues() + 1];		
+	  for(int attrValue = 0; attrValue < data.attribute(attrIndex).numValues() + 1; attrValue++){
+	    m_MI_NumAttrClassValue[attrIndex][cclass][attrValue] = 0;
+	  }
+	}
+      }
+    }
+    m_MissingVector = new double[data.numAttributes()];
+    for(int i = 0; i < data.numAttributes(); i++){
+      if(i == data.classIndex()){
+	m_MissingVector[i] = Double.NaN;
+      } else {
+	m_MissingVector[i] = data.attribute(i).numValues();
+      }
+    }
+
+    /* update the classifier with data */
+    Enumeration enu = data.enumerateInstances();
+    while(enu.hasMoreElements()){
+      update((Instance) enu.nextElement());
+    }	
+  }
+
+    
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be classified
+   * @return index of the predicted class as a double
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    /* check the instance */
+    if (m_Train.equalHeaders(instance.dataset()) == false){
+      throw new Exception("NNge.classifyInstance : Incompatible instance types !\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    }
+	
+    Exemplar matched = nearestExemplar(instance); 
+    if(matched == null){
+      throw new Exception("NNge.classifyInstance : NNge hasn't been trained !");
+    }
+    return matched.classValue();
+  }
+
+
+  /**
+   * Updates the classifier using the given instance.
+   *
+   * @param instance the instance to include
+   * @throws Exception if instance could not be incorporated
+   * successfully
+   */
+  public void updateClassifier(Instance instance) throws Exception {
+
+    if (m_Train.equalHeaders(instance.dataset()) == false) {
+      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
+    }	
+    update(instance);	
+  }
+
+
+
+  /** HIGH LEVEL SUB-FUNCTIONS */
+    
+
+
+  /**
+   * Performs the update of the classifier
+   *
+   * @param instance the new instance
+   * @throws Exception if the update fails
+   */
+  private void update(Instance instance) throws Exception {
+
+    if (instance.classIsMissing()) {
+      return;
+    }
+
+    instance.replaceMissingValues(m_MissingVector);
+    m_Train.add(instance);
+
+    /* Update the minimum and maximum for all the attributes */
+    updateMinMax(instance);
+
+    /* update the mutual information datas */
+    updateMI(instance);
+
+    /* Nearest Exemplar */
+    Exemplar nearest = nearestExemplar(instance);
+	
+    /* Adjust */
+    if(nearest == null){
+      Exemplar newEx = new Exemplar(this, m_Train, 10, instance.classValue());
+      newEx.generalise(instance);
+      initWeight(newEx);
+      addExemplar(newEx);
+      return;
+    }
+    adjust(instance, nearest);
+
+    /* Generalise */
+    generalise(instance);
+  }
+
+
+  /**
+   * Returns the nearest Exemplar
+   *
+   * @param inst an Instance
+   * @return the nearest Exemplar to inst, null if no exemplar are found.
+   */
+  private Exemplar nearestExemplar(Instance inst){
+
+    if (m_Exemplars == null)
+      return null;
+    Exemplar cur = m_Exemplars, nearest = m_Exemplars;
+    double dist, smallestDist = cur.squaredDistance(inst);
+    while (cur.next != null){
+      cur = cur.next;
+      dist = cur.squaredDistance(inst);
+      if (dist < smallestDist){
+	smallestDist = dist;
+	nearest = cur;
+      }
+    }
+    return nearest;
+  }
+
+    
+  /**
+   * Returns the nearest Exemplar with class c
+   *
+   * @param inst an Instance
+   * @param c the class of the Exemplar to return
+   * @return the nearest Exemplar to inst with class c, null if no exemplar with class c are found.
+   */
+  private Exemplar nearestExemplar(Instance inst, double c){
+
+    if (m_ExemplarsByClass[(int) c] == null)
+      return null;
+    Exemplar cur = m_ExemplarsByClass[(int) c], nearest = m_ExemplarsByClass[(int) c];
+    double dist, smallestDist = cur.squaredDistance(inst);
+    while (cur.nextWithClass != null){
+      cur = cur.nextWithClass;
+      dist = cur.squaredDistance(inst);
+      if (dist < smallestDist){
+	smallestDist = dist;
+	nearest = cur;
+      }
+    }
+    return nearest;
+  }
+
+    
+  /**
+   * Generalise an Exemplar (not necessarily predictedExemplar) to match instance.
+   * predictedExemplar must be in NNge's lists
+   *
+   * @param newInst the new instance
+   * @throws Exception in case of inconsitent situation
+   */
+  private void generalise(Instance newInst) throws Exception {
+
+    Exemplar first = m_ExemplarsByClass[(int) newInst.classValue()];
+    int n = 0;
+
+    /* try to generalise with the n first exemplars */
+    while(n < m_NumAttemptsOfGene && first != null){
+	    
+      /* find the nearest one starting from first */
+      Exemplar closest = first, cur = first;
+      double smallestDist = first.squaredDistance(newInst), dist;
+      while(cur.nextWithClass != null){
+	cur = cur.nextWithClass;
+	dist = cur.squaredDistance(newInst);
+	if(dist < smallestDist){
+	  smallestDist = dist;
+	  closest = cur;
+	}
+      }
+
+      /* remove the Examplar from NNge's lists */
+      if(closest == first)
+	first = first.nextWithClass;
+      removeExemplar(closest); 
+
+      /* try to generalise */
+      closest.preGeneralise(newInst);
+      if(!detectOverlapping(closest)){
+	closest.validateGeneralisation();
+	addExemplar(closest);
+	return;
+      }
+
+      /* it didn't work, put ungeneralised exemplar on the top of the lists */
+      closest.cancelGeneralisation();
+      addExemplar(closest);			
+
+      n++;
+    }
+
+    /* generalisation failled : add newInst as a new Examplar */
+    Exemplar newEx = new Exemplar(this, m_Train, 5, newInst.classValue());
+    newEx.generalise(newInst);
+    initWeight(newEx);
+    addExemplar(newEx);
+  }
+
+
+  /**
+   * Adjust the NNge.
+   *
+   * @param newInst the instance to classify
+   * @param predictedExemplar the Exemplar that matches newInst
+   * @throws Exception in case of inconsistent situation
+   */
+  private void adjust(Instance newInst, Exemplar predictedExemplar) throws Exception {
+
+    /* correct prediction */
+    if(newInst.classValue() == predictedExemplar.classValue()){
+      predictedExemplar.incrPositiveCount();
+      /* incorrect prediction */
+    } else {
+      predictedExemplar.incrNegativeCount();
+
+      /* new instance falls inside */
+      if(predictedExemplar.holds(newInst)){
+	prune(predictedExemplar, newInst);
+      }
+    }    
+  }
+
+
+  /**
+   * Prunes an Exemplar that matches an Instance
+   *
+   * @param predictedExemplar an Exemplar
+   * @param newInst an Instance matched by predictedExemplar
+   * @throws Exception in case of inconsistent situation. (shouldn't happen.)
+   */
+  private void prune(Exemplar predictedExemplar, Instance newInst) throws Exception {
+
+    /* remove the Exemplar */
+    removeExemplar(predictedExemplar);
+
+    /* look for the best nominal feature and the best numeric feature to cut */
+    int numAttr = -1, nomAttr = -1;
+    double smallestDelta = Double.POSITIVE_INFINITY, delta;
+    int biggest_N_Nom = -1, biggest_N_Num = -1, n, m;
+    for(int i = 0; i < m_Train.numAttributes(); i++){
+
+      if(i == m_Train.classIndex())
+	continue;
+
+      /* numeric attribute */
+      if(m_Train.attribute(i).isNumeric()){
+
+	/* compute the distance 'delta' to the closest boundary */
+	double norm = m_MaxArray[i] - m_MinArray[i];
+	if(norm != 0){
+	  delta = Math.min((predictedExemplar.getMaxBorder(i) - newInst.value(i)), 
+			   (newInst.value(i) - predictedExemplar.getMinBorder(i))) / norm;
+	} else {
+	  delta = Double.POSITIVE_INFINITY;
+	}
+
+	/* compute the size of the biggest Exemplar which would be created */
+	n = m = 0;
+	Enumeration enu = predictedExemplar.enumerateInstances();
+	while(enu.hasMoreElements()){
+	  Instance ins = (Instance) enu.nextElement();
+	  if(ins.value(i) < newInst.value(i))
+	    n++;
+	  else if(ins.value(i) > newInst.value(i))
+	    m++;
+	}
+	n = Math.max(n, m);
+
+	if(delta < smallestDelta){
+	  smallestDelta = delta;
+	  biggest_N_Num = n;
+	  numAttr = i;
+	} else if(delta == smallestDelta && n > biggest_N_Num){
+	  biggest_N_Num = n;
+	  numAttr = i;
+	}
+
+	/* nominal attribute */
+      } else {
+
+	/* compute the size of the Exemplar which would be created */
+	Enumeration enu = predictedExemplar.enumerateInstances();
+	n = 0;
+	while(enu.hasMoreElements()){
+	  if(((Instance) enu.nextElement()).value(i) != newInst.value(i))
+	    n++;
+	}
+	if(n > biggest_N_Nom){
+	  biggest_N_Nom = n;
+	  nomAttr = i;
+	} 
+      }
+    }
+
+    /* selection of the feature to cut between the best nominal and the best numeric */
+    int attrToCut;
+    if(numAttr == -1 && nomAttr == -1){
+      attrToCut = 0;
+    } else if (numAttr == -1){
+      attrToCut = nomAttr;
+    } else if(nomAttr == -1){
+      attrToCut = numAttr;
+    } else {
+      if(biggest_N_Nom > biggest_N_Num)
+	attrToCut = nomAttr;
+      else
+	attrToCut = numAttr;
+    }
+
+    /* split the Exemplar */
+    Instance curInst;
+    Exemplar a, b;
+    a = new Exemplar(this, m_Train, 10, predictedExemplar.classValue());
+    b = new Exemplar(this, m_Train, 10, predictedExemplar.classValue());
+    LinkedList leftAlone = new LinkedList();
+    Enumeration enu = predictedExemplar.enumerateInstances();
+    if(m_Train.attribute(attrToCut).isNumeric()){
+      while(enu.hasMoreElements()){
+	curInst = (Instance) enu.nextElement();
+	if(curInst.value(attrToCut) > newInst.value(attrToCut)){
+	  a.generalise(curInst);
+	} else if (curInst.value(attrToCut) < newInst.value(attrToCut)){
+	  b.generalise(curInst);
+	} else if (notEqualFeatures(curInst, newInst)) {
+	  leftAlone.add(curInst);
+	}
+      }
+    } else {
+      while(enu.hasMoreElements()){
+	curInst = (Instance) enu.nextElement();
+	if(curInst.value(attrToCut) != newInst.value(attrToCut)){
+	  a.generalise(curInst);
+	} else if (notEqualFeatures(curInst, newInst)){
+	  leftAlone.add(curInst);
+	}
+      }
+    }
+	
+    /* treat the left alone Instances */
+    while(leftAlone.size() != 0){
+
+      Instance alone = (Instance) leftAlone.removeFirst();
+      a.preGeneralise(alone);
+      if(!a.holds(newInst)){
+	a.validateGeneralisation();
+	continue;
+      }
+      a.cancelGeneralisation();
+      b.preGeneralise(alone);
+      if(!b.holds(newInst)){
+	b.validateGeneralisation();
+	continue;
+      }
+      b.cancelGeneralisation();
+      Exemplar exem = new Exemplar(this, m_Train, 3, alone.classValue());
+      exem.generalise(alone);
+      initWeight(exem);
+      addExemplar(exem);
+    }
+
+    /* add (or not) the new Exemplars */
+    if(a.numInstances() != 0){
+      initWeight(a);
+      addExemplar(a);
+    }
+    if(b.numInstances() != 0){
+      initWeight(b);
+      addExemplar(b);	    
+    }
+  }
+
+
+  /**
+   * Returns true if the instance don't have the same feature values
+   * 
+   * @param inst1 an instance
+   * @param inst2 an instance
+   * @return true if the instance don't have the same feature values
+   */
+  private boolean notEqualFeatures(Instance inst1, Instance inst2) {
+
+    for(int i = 0; i < m_Train.numAttributes(); i++){
+      if(i == m_Train.classIndex())
+	continue;
+      if(inst1.value(i) != inst2.value(i))
+	return true;
+    }
+    return false;
+  }
+
+
+  /**
+   * Returns true if ex overlaps any of the Exemplars in NNge's lists
+   *
+   * @param ex an Exemplars
+   * @return true if ex overlaps any of the Exemplars in NNge's lists
+   */
+  private boolean detectOverlapping(Exemplar ex){
+    Exemplar cur = m_Exemplars;
+    while(cur != null){
+      if(ex.overlaps(cur)){
+	return true;
+      }
+      cur = cur.next;
+    }
+    return false;
+  }
+
+
+  /**
+   * Updates the minimum, maximum, sum, sumSquare values for all the attributes 
+   * 
+   * @param instance the new instance
+   */
+  private void updateMinMax(Instance instance){
+
+    for (int j = 0; j < m_Train.numAttributes(); j++) {
+      if(m_Train.classIndex() == j || m_Train.attribute(j).isNominal())
+	continue;
+      if (instance.value(j) < m_MinArray[j]) 
+	m_MinArray[j] = instance.value(j);
+      if (instance.value(j) > m_MaxArray[j])
+	m_MaxArray[j] = instance.value(j);
+    }    
+  }
+
+
+  /**
+   * Updates the data for computing the mutual information
+   *
+   * MUST be called AFTER adding inst in m_Train 
+   *
+   * @param inst the new instance
+   * @throws Exception is thrown if an inconsistent situation is met
+   */
+  private void updateMI(Instance inst) throws Exception {
+
+    if(m_NumFoldersMI < 1){
+      throw new Exception("NNge.updateMI : incorrect number of folders ! Option I must be greater than 1.");
+    }
+
+    m_MI_NumClass[(int) inst.classValue()]++;
+    m_MI_NumInst++;
+
+    /* for each attribute */
+    for(int attrIndex = 0; attrIndex < m_Train.numAttributes(); attrIndex++){
+
+      /* which is the class attribute */
+      if(m_Train.classIndex() == attrIndex)
+	continue;
+
+      /* which is a numeric attribute */
+      else if(m_Train.attribute(attrIndex).isNumeric()){
+		
+	/* if max-min have to be updated */
+	if(Double.isNaN(m_MI_MaxArray[attrIndex]) ||
+	   Double.isNaN(m_MI_MinArray[attrIndex]) ||
+	   m_MI_MaxArray[attrIndex] < inst.value(attrIndex) || 
+	   inst.value(attrIndex) < m_MI_MinArray[attrIndex]){
+
+	  /* then update them */
+	  if(Double.isNaN(m_MI_MaxArray[attrIndex])) m_MI_MaxArray[attrIndex] = inst.value(attrIndex);
+	  if(Double.isNaN(m_MI_MinArray[attrIndex])) m_MI_MinArray[attrIndex] = inst.value(attrIndex);
+	  if(m_MI_MaxArray[attrIndex] < inst.value(attrIndex)) m_MI_MaxArray[attrIndex] = inst.value(attrIndex);
+	  if(m_MI_MinArray[attrIndex] > inst.value(attrIndex)) m_MI_MinArray[attrIndex] = inst.value(attrIndex);
+		    
+	  /* and re-compute everything from scratch... (just for this attribute) */
+	  double delta = (m_MI_MaxArray[attrIndex] - m_MI_MinArray[attrIndex]) / (double) m_NumFoldersMI;
+
+	  /* for each interval */
+	  for(int inter = 0; inter < m_NumFoldersMI; inter++){
+
+	    m_MI_NumAttrInter[attrIndex][inter] = 0;
+
+	    /* for each class */
+	    for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
+			    
+	      m_MI_NumAttrClassInter[attrIndex][cclass][inter] = 0;
+
+	      /* count */
+	      Enumeration enu = m_Train.enumerateInstances();
+	      while(enu.hasMoreElements()){
+		Instance cur = (Instance) enu.nextElement();
+		if(( (m_MI_MinArray[attrIndex] + inter * delta) <= cur.value(attrIndex)       ) &&
+		   ( cur.value(attrIndex) <= (m_MI_MinArray[attrIndex] + (inter + 1) * delta) ) &&
+		   ( cur.classValue() == cclass ) ){
+		  m_MI_NumAttrInter[attrIndex][inter]++;
+		  m_MI_NumAttrClassInter[attrIndex][cclass][inter]++;
+		}
+	      }
+	    }
+	  }
+		
+	  /* max-min don't have to be updated */
+	} else {
+
+	  /* still have to incr the card of the correct interval */
+	  double delta = (m_MI_MaxArray[attrIndex] - m_MI_MinArray[attrIndex]) / (double) m_NumFoldersMI;
+		    
+	  /* for each interval */
+	  for(int inter = 0; inter < m_NumFoldersMI; inter++){
+	    /* which contains inst*/
+	    if(( (m_MI_MinArray[attrIndex] + inter * delta) <= inst.value(attrIndex)       ) &&
+	       ( inst.value(attrIndex) <= (m_MI_MinArray[attrIndex] + (inter + 1) * delta) )){
+	      m_MI_NumAttrInter[attrIndex][inter]++;
+	      m_MI_NumAttrClassInter[attrIndex][(int) inst.classValue()][inter]++;
+	    }
+	  }
+	}
+		
+	/* update the mutual information of this attribute... */
+	m_MI[attrIndex] = 0;
+		
+	/* for each interval, for each class */
+	for(int inter = 0; inter < m_NumFoldersMI; inter++){
+	  for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
+	    double pXY = ((double) m_MI_NumAttrClassInter[attrIndex][cclass][inter]) / ((double) m_MI_NumInst);
+	    double pX = ((double) m_MI_NumClass[cclass]) / ((double) m_MI_NumInst);
+	    double pY = ((double) m_MI_NumAttrInter[attrIndex][inter]) / ((double) m_MI_NumInst);
+
+	    if(pXY != 0)
+	      m_MI[attrIndex] += pXY * Utils.log2(pXY / (pX * pY));
+	  }
+	}
+		
+	/* which is a nominal attribute */
+      } else if (m_Train.attribute(attrIndex).isNominal()){
+		
+	/*incr the card of the correct 'values' */
+	m_MI_NumAttrValue[attrIndex][(int) inst.value(attrIndex)]++;
+	m_MI_NumAttrClassValue[attrIndex][(int) inst.classValue()][(int) inst.value(attrIndex)]++;
+		
+	/* update the mutual information of this attribute... */
+	m_MI[attrIndex] = 0;
+		
+	/* for each nominal value, for each class */
+	for(int attrValue = 0; attrValue < m_Train.attribute(attrIndex).numValues() + 1; attrValue++){
+	  for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
+	    double pXY = ((double) m_MI_NumAttrClassValue[attrIndex][cclass][attrValue]) / ((double) m_MI_NumInst);
+	    double pX = ((double) m_MI_NumClass[cclass]) / ((double) m_MI_NumInst);
+	    double pY = ((double) m_MI_NumAttrValue[attrIndex][attrValue]) / ((double) m_MI_NumInst);
+	    if(pXY != 0)
+	      m_MI[attrIndex] += pXY * Utils.log2(pXY / (pX * pY));
+	  }
+	}
+
+	/* not a nominal attribute, not a numeric attribute */
+      } else {
+	throw new Exception("NNge.updateMI : Cannot deal with 'string attribute'.");
+      }
+    }	
+  }
+
+
+  /**
+   * Init the weight of ex
+   * Watch out ! ex shouldn't be in NNge's lists when initialized
+   *
+   * @param ex the Exemplar to initialise
+   */
+  private void initWeight(Exemplar ex) {	
+    int pos = 0, neg = 0, n = 0;
+    Exemplar cur = m_Exemplars;
+    if (cur == null){
+      ex.setPositiveCount(1);
+      ex.setNegativeCount(0);
+      return;
+    }
+    while(cur != null){
+      pos += cur.getPositiveCount();
+      neg += cur.getNegativeCount();
+      n++;
+      cur = cur.next;
+    }
+    ex.setPositiveCount(pos / n);
+    ex.setNegativeCount(neg / n);
+  }
+
+
+  /**
+   * Adds an Exemplar in NNge's lists
+   * Ensure that the exemplar is not already in a list : the links would be broken...
+   *
+   * @param ex a new Exemplar to add
+   */
+  private void addExemplar(Exemplar ex) {
+	
+    /* add ex at the top of the general list */
+    ex.next = m_Exemplars;
+    if(m_Exemplars != null)
+      m_Exemplars.previous = ex;
+    ex.previous = null;
+    m_Exemplars = ex;
+
+    /* add ex at the top of the corresponding class list */
+    ex.nextWithClass = m_ExemplarsByClass[(int) ex.classValue()];
+    if(m_ExemplarsByClass[(int) ex.classValue()] != null)
+      m_ExemplarsByClass[(int) ex.classValue()].previousWithClass = ex;
+    ex.previousWithClass = null;
+    m_ExemplarsByClass[(int) ex.classValue()] = ex;
+  }
+
+
+  /**
+   * Removes an Exemplar from NNge's lists
+   * Ensure that the Exemplar is actually in NNge's lists. 
+   *   Likely to do something wrong if this condition is not respected.
+   * Due to the list implementation, the Exemplar can appear only once in the lists : 
+   *   once removed, the exemplar is not in the lists anymore.
+   *
+   * @param ex a new Exemplar to add
+   */
+  private void removeExemplar(Exemplar ex){
+
+    /* remove from the general list */
+    if(m_Exemplars == ex){
+      m_Exemplars = ex.next;
+      if(m_Exemplars != null)
+	m_Exemplars.previous = null;
+	
+    } else {
+      ex.previous.next = ex.next;
+      if(ex.next != null){
+	ex.next.previous = ex.previous;
+      }
+    }
+    ex.next = ex.previous = null;
+
+    /* remove from the class list */
+    if(m_ExemplarsByClass[(int) ex.classValue()] == ex){
+      m_ExemplarsByClass[(int) ex.classValue()] = ex.nextWithClass;
+      if(m_ExemplarsByClass[(int) ex.classValue()] != null)
+	m_ExemplarsByClass[(int) ex.classValue()].previousWithClass = null;
+	
+    } else {
+      ex.previousWithClass.nextWithClass = ex.nextWithClass;
+      if(ex.nextWithClass != null){
+	ex.nextWithClass.previousWithClass = ex.previousWithClass;
+      }
+    }
+    ex.nextWithClass = ex.previousWithClass = null;
+  }
+
+
+  /**
+   * returns the weight of indexth attribute
+   *
+   * @param index attribute's index
+   * @return the weight of indexth attribute
+   */
+  private double attrWeight (int index) {
+    return m_MI[index];
+  }
+
+    
+  /**
+   * Returns a description of this classifier.
+   *
+   * @return a description of this classifier as a string.
+   */
+  public String toString(){
+
+    String s;
+    Exemplar cur = m_Exemplars;
+    int i;	
+
+   if (m_MinArray == null) {
+      return "No classifier built";
+    }
+     int[] nbHypClass = new int[m_Train.numClasses()];
+    int[] nbSingleClass = new int[m_Train.numClasses()];
+    for(i = 0; i<nbHypClass.length; i++){
+      nbHypClass[i] = 0;
+      nbSingleClass[i] = 0;
+    }
+    int nbHyp = 0, nbSingle = 0;
+
+    s = "\nNNGE classifier\n\nRules generated :\n";
+
+    while(cur != null){
+      s += "\tclass " + m_Train.attribute(m_Train.classIndex()).value((int) cur.classValue()) + " IF : ";
+      s += cur.toRules() + "\n";
+      nbHyp++;
+      nbHypClass[(int) cur.classValue()]++;	    
+      if (cur.numInstances() == 1){
+	nbSingle++;
+	nbSingleClass[(int) cur.classValue()]++;
+      }
+      cur = cur.next;
+    }
+    s += "\nStat :\n";
+    for(i = 0; i<nbHypClass.length; i++){
+      s += "\tclass " + m_Train.attribute(m_Train.classIndex()).value(i) + 
+	" : " + Integer.toString(nbHypClass[i]) + " exemplar(s) including " + 
+	Integer.toString(nbHypClass[i] - nbSingleClass[i]) + " Hyperrectangle(s) and " +
+	Integer.toString(nbSingleClass[i]) + " Single(s).\n";
+    }
+    s += "\n\tTotal : " + Integer.toString(nbHyp) + " exemplars(s) including " + 
+      Integer.toString(nbHyp - nbSingle) + " Hyperrectangle(s) and " +
+      Integer.toString(nbSingle) + " Single(s).\n";
+	
+    s += "\n";
+	
+    s += "\tFeature weights : ";
+
+    String space = "[";
+    for(int ii = 0; ii < m_Train.numAttributes(); ii++){
+      if(ii != m_Train.classIndex()){
+	s += space + Double.toString(attrWeight(ii));
+	space = " ";
+      }
+    }
+    s += "]";
+    s += "\n\n";
+    return s;
+  }
+
+
+
+  /** OPTION HANDLER FUNCTION */
+    
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return an enumeration of all available options.
+   */
+  public Enumeration listOptions(){
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+				    "\tNumber of attempts of generalisation.\n",
+				    "G", 
+				    1, 
+				    "-G <value>"));
+    newVector.addElement(new Option(
+				    "\tNumber of folder for computing the mutual information.\n",
+				    "I", 
+				    1, 
+				    "-I <value>"));
+
+    return newVector.elements();
+  }
+
+    
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible). <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -G &lt;value&gt;
+   *  Number of attempts of generalisation.
+   * </pre>
+   * 
+   * <pre> -I &lt;value&gt;
+   *  Number of folder for computing the mutual information.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String str;
+
+    /* Number max of attempts of generalisation */
+    str = Utils.getOption('G', options);
+    if(str.length() != 0){
+      m_NumAttemptsOfGene = Integer.parseInt(str);
+      if(m_NumAttemptsOfGene < 1)
+	throw new Exception("NNge.setOptions : G option's value must be greater than 1.");
+    } else {
+      m_NumAttemptsOfGene = 5;
+    }
+
+    /* Number of folder for computing the mutual information */
+    str = Utils.getOption('I', options);
+    if(str.length() != 0){
+      m_NumFoldersMI = Integer.parseInt(str);
+      if(m_NumFoldersMI < 1)
+	throw new Exception("NNge.setOptions : I option's value must be greater than 1.");
+    } else {
+      m_NumFoldersMI = 5;
+    }
+  }
+
+    
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions(){
+
+    String[] options = new String[5];
+    int current = 0;
+
+    options[current++] = "-G"; options[current++] = "" + m_NumAttemptsOfGene;
+    options[current++] = "-I"; options[current++] = "" + m_NumFoldersMI;
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numAttemptsOfGeneOptionTipText() {
+    return "Sets the number of attempts for generalization.";
+  }
+
+  /**
+   * Gets the number of attempts for generalisation.
+   *
+   * @return the value of the option G
+   */
+  public int getNumAttemptsOfGeneOption() {
+    return m_NumAttemptsOfGene;
+  }
+
+
+  /**
+   * Sets the number of attempts for generalisation.
+   *
+   * @param newIntParameter the new value.
+   */
+  public void setNumAttemptsOfGeneOption(int newIntParameter) {
+    m_NumAttemptsOfGene = newIntParameter;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldersMIOptionTipText() {
+    return "Sets the number of folder for mutual information.";
+  }
+
+  /**
+   * Gets the number of folder for mutual information.
+   *
+   * @return the value of the option I
+   */
+  public int getNumFoldersMIOption() {
+    return m_NumFoldersMI;
+  }
+
+  /**
+   * Sets the number of folder for mutual information.
+   *
+   * @param newIntParameter the new value.
+   */
+  public void setNumFoldersMIOption(int newIntParameter) {
+    m_NumFoldersMI = newIntParameter;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5956 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line arguments for evaluation
+   * (see Evaluation).
+   */
+  public static void main(String [] argv) {
+    runClassifier(new NNge(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/OLM.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/OLM.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/OLM.java	(revision 29)
@@ -0,0 +1,851 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OLM.java
+ *    Copyright (C) 2009 TriDat Tran
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import java.io.*;
+import java.util.*;
+import weka.core.*;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ * 
+ <!-- globalinfo-start -->
+ * This class is an implementation of the Ordinal Learning Method (OLM).<br/>
+ * Further information regarding the algorithm and variants can be found in:<br/>
+ * <br/>
+ * Arie Ben-David (1992). Automatic Generation of Symbolic Multiattribute Ordinal Knowledge-Based DSSs: methodology and Applications. Decision Sciences. 23:1357-1372.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Ben-David1992,
+ *    author = {Arie Ben-David},
+ *    journal = {Decision Sciences},
+ *    pages = {1357-1372},
+ *    title = {Automatic Generation of Symbolic Multiattribute Ordinal Knowledge-Based DSSs: methodology and Applications},
+ *    volume = {23},
+ *    year = {1992}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;integer&gt;
+ *  The resolution mode. Valid values are:
+ *  0 for conservative resolution, 1 for random resolution, 2 for average, and 3 for no resolution. (default 0).</pre>
+ * 
+ * <pre> -C &lt;integer&gt;
+ *  The classification mode. Valid values are:
+ *  0 for conservative classification, 1 for nearest neighbour classification. (default 0).</pre>
+ * 
+ * <pre> -U &lt;size&gt;
+ *  SSet maximum size of rule base
+ *  (default: -U &lt;number of examples&gt;)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author TriDat Tran
+ * @version $Revision: 5928 $ 
+ */
+public class OLM extends AbstractClassifier 
+implements OptionHandler, TechnicalInformationHandler {
+
+  /**
+   * For serialization 
+   */
+  private static final long serialVersionUID = -381974207649598344L;
+
+  //protected Instance ist;
+  protected int printR;
+  protected int numExamples;
+
+
+  /* The conflict resolution modes */
+  public static final int RESOLUTION_NONE = 3;
+  public static final int RESOLUTION_AVERAGE = 2;
+  public static final int RESOLUTION_RANDOM = 1;
+  public static final int RESOLUTION_CONSERVATIVE = 0;
+  public static final Tag [] TAGS_RESOLUTION = {
+    new Tag(RESOLUTION_NONE, "No conflict resolution"),
+    new Tag(RESOLUTION_AVERAGE, "Resolution using average"),
+    new Tag(RESOLUTION_RANDOM, "Random resolution"),
+    new Tag(RESOLUTION_CONSERVATIVE, "Conservative resolution")
+  };
+
+  /** The conflict resolution mode */
+  protected int m_resolutionMode = RESOLUTION_CONSERVATIVE;
+
+  /* The classification modes */
+  public static final int CLASSIFICATION_CONSERVATIVE = 1;
+  public static final int CLASSIFICATION_NEARESTNEIGHBOUR = 0;
+  public static final Tag[] TAGS_CLASSIFICATION = {
+    new Tag(CLASSIFICATION_NEARESTNEIGHBOUR, "Nearest neighbour classification"),
+    new Tag(CLASSIFICATION_CONSERVATIVE, "Conservative classification")
+  };
+
+  /** The classification mode */
+  protected int m_classificationMode = CLASSIFICATION_CONSERVATIVE;
+
+  protected int upperBaseLimit = -1;
+  protected int randSeed = 0;
+  protected Random rand = new Random(0);
+
+  protected boolean print_msg = false;
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(1);
+
+    return result;
+  }
+
+  /**
+   * Returns a string describing the classifier.
+   * @return a description suitable for displaying in the 
+   * explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "This class is an implementation of the Ordinal Learning "
+    + "Method (OLM).\n" 
+    + "Further information regarding the algorithm and variants "
+    + "can be found in:\n\n"
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+    TechnicalInformation        additional;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Arie Ben-David");
+    result.setValue(Field.YEAR, "1992");
+    result.setValue(Field.TITLE, "Automatic Generation of Symbolic Multiattribute Ordinal Knowledge-Based DSSs: methodology and Applications");
+    result.setValue(Field.JOURNAL, "Decision Sciences");
+    result.setValue(Field.PAGES, "1357-1372");
+    result.setValue(Field.VOLUME, "23");
+
+    return result;
+  }
+
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param inst the instance to be classified
+   * @return the classification
+   */
+  public double classifyInstance(Instance inst) {
+    return olmrules.classify(inst);
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * Valid options are: 
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+        "\tThe resolution mode. Valid values are:\n" +
+        "\t0 for conservative resolution, 1 for random resolution," +
+        "\t2 for average, and 3 for no resolution. (default 0).",
+        "R", 1, "-R <integer>"));
+
+    newVector.addElement(new Option(
+        "\tThe classification mode. Valid values are:\n" +
+        "\t0 for conservative classification, 1 for nearest neighbour classification." +
+        " (default 0).",
+        "C", 1, "-C <integer>"));
+
+    newVector.addElement(new Option("\tSSet maximum size of rule base\n" +
+        "\t(default: -U <number of examples>)","U", 1, "-U <size>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;integer&gt;
+   *  The resolution mode. Valid values are:
+   *  0 for conservative resolution, 1 for random resolution, 2 for average, and 3 for no resolution. (default 0).</pre>
+   * 
+   * <pre> -C &lt;integer&gt;
+   *  The classification mode. Valid values are:
+   *  0 for conservative classification, 1 for nearest neighbour classification. (default 0).</pre>
+   * 
+   * <pre> -U &lt;size&gt;
+   *  SSet maximum size of rule base
+   *  (default: -U &lt;number of examples&gt;)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String resolutionMode = Utils.getOption('R', options);
+    if (resolutionMode.length() > 0) {
+      setResolutionMode(new SelectedTag(Integer.parseInt(resolutionMode), 
+          TAGS_RESOLUTION));
+    }
+
+    String classificationMode = Utils.getOption('C', options);
+    if (classificationMode.length() > 0) {
+      setClassificationMode(new SelectedTag(Integer.parseInt(classificationMode), 
+          TAGS_CLASSIFICATION));
+    }
+
+    String upperBase = Utils.getOption('U', options);
+    if (upperBase.length() != 0) 
+      upperBaseLimit = Integer.parseInt(upperBase); 
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String [] options = new String [6];
+    int current = 0;
+
+    if(upperBaseLimit == -1) upperBaseLimit = numExamples;
+
+    options[current++] = "-R"; options[current++] = "" + m_resolutionMode;
+    options[current++] = "-C"; options[current++] = "" + m_classificationMode;
+    options[current++] = "-U"; options[current++] = "" + upperBaseLimit;
+
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String resolutionModeTipText() {
+    return "The resolution mode to use.";
+  }
+
+  /**
+   * Sets the resolution mode.
+   *
+   * @param newMethod the new evaluation mode.
+   */
+  public void setResolutionMode(SelectedTag newMethod) {
+
+    if (newMethod.getTags() == TAGS_RESOLUTION) {
+      m_resolutionMode = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the resolution mode.
+   *
+   * @return the evaluation mode.
+   */
+  public SelectedTag getResolutionMode() {
+
+    return new SelectedTag(m_resolutionMode, TAGS_RESOLUTION);
+  }
+
+  /**
+   * Sets the classification mode.
+   * 
+   * @param newMethod the new classification mode.
+   */
+  public void setClassificationMode(SelectedTag newMethod) {
+    m_classificationMode = newMethod.getSelectedTag().getID();
+  }
+
+  /**
+   * Gets the classification mode.
+   * 
+   * @return the classiciation mode
+   */
+  public SelectedTag getClassificationMode() {
+    return new SelectedTag(m_classificationMode, TAGS_CLASSIFICATION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classificationModeTipText() {
+    return "The classification mode to use.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ruleSizeTipText() {
+    return "Set the rule base size\n" +
+    "0 - unlimited\n";
+  }
+
+  public int getRuleSize(){ return upperBaseLimit;}
+  public void setRuleSize(int s){ upperBaseLimit = s;}
+
+  /**
+   * Class to store CISE (Consistent and Irredundant Set of Examples) rules 
+   */
+  private class OLMRules implements Serializable{
+    private Vector rules;
+
+    /**
+     * Constructor
+     */
+    public OLMRules()
+    {
+      rules = new Vector();
+    }
+
+    public int distance(Instance inst1, Instance inst2)
+    {
+      double values1[] = inst1.toDoubleArray();
+      double values2[] = inst2.toDoubleArray();
+      int classindex = inst1.classIndex();
+      int numAtt = inst1.numAttributes();
+      int dist = 0;
+
+      for(int i=0; i < numAtt; i++)
+      {
+        if(i != classindex)
+          dist += Math.abs(values1[i] - values2[i]);
+      }
+
+      return dist; 
+    }
+
+    public Instance averageRule(Instance inst1, Instance inst2)
+    {
+      Instance inst = inst1;
+      double values1[] = inst1.toDoubleArray();
+      double values2[] = inst2.toDoubleArray();
+      int classindex = inst1.classIndex();
+      int numAtt = inst1.numAttributes();
+
+      for(int i=0; i < numAtt; i++)
+      {
+        inst.setValue(i,Math.round((values1[i] + values2[i])/2));
+      } 
+
+      return inst;
+    }
+
+    public void printRules()
+    {
+      Instance inst;
+      for(int i=0; i < rules.size(); i++)
+      {
+        inst = (Instance)rules.elementAt(i);
+        System.out.print(i+": ");
+        System.out.println(inst.toString());
+      }
+    }
+    /**
+     * Checks if the input (non-class) attributes in inst1 is greater
+     * than in inst2.
+     *
+     * @param inst1 Instance1
+     * @param inst2 Instance2
+     */
+    private boolean isGreaterInput(Instance inst1, Instance inst2)
+    {
+      double values1[] = inst1.toDoubleArray();
+      double values2[] = inst2.toDoubleArray();
+      int classindex = inst1.classIndex();
+      int numAtt = inst1.numAttributes();
+
+      for(int i=0; i < numAtt; i++)
+      {
+        if(i!= classindex && values1[i] < values2[i]) 
+          return false;
+      }
+      return true;
+    }
+
+    private boolean isEqualInput(Instance inst1, Instance inst2)
+    {
+      double values1[] = inst1.toDoubleArray();
+      double values2[] = inst2.toDoubleArray();
+      int classindex = inst1.classIndex();
+      int numAtt = inst1.numAttributes();
+
+      for(int i=0; i < numAtt; i++)
+      {
+        if(i!= classindex && values1[i] != values2[i]) 
+          return false;
+      }
+      return true;
+    }
+
+    private boolean isGreaterOutput(Instance inst1, Instance inst2)
+    {
+      return (inst1.toDoubleArray())[inst1.classIndex()] > 
+      (inst2.toDoubleArray())[inst2.classIndex()];
+    }
+
+    private boolean isEqualOutput(Instance inst1, Instance inst2)
+    {
+      return (inst1.toDoubleArray())[inst1.classIndex()] == 
+        (inst2.toDoubleArray())[inst2.classIndex()];
+    }
+
+    private void fillMissing(Instance inst)
+    {
+      ;
+    }
+
+    public void addRule(Instance inst)
+    {
+      // add new rule?
+      boolean addr = true;
+      boolean b = false;
+      int classindex = inst.classIndex();
+      // Fill in missing values.
+      fillMissing(inst);
+      // Compare E with each rule in CISE
+      for(int i=0; i < rules.size(); i++)
+      {
+        b = false;
+        // Checks of Redudancies.
+        if(isEqualOutput(inst, (Instance)rules.elementAt(i))) 
+        {
+          // Is E redundant : i.e EI(1) > EI(2) and EO(1) = EO(2)
+          if(isGreaterInput(inst, (Instance)rules.elementAt(i)))
+          {
+            // E is redundant w.r.t rule i, we discard E
+            addr = false;
+            if(print_msg)
+              System.out.println(inst.toString() + " is (1) redundant wrt " +
+                  ((Instance)rules.elementAt(i)).toString());
+            continue;
+          }
+          else if(isGreaterInput((Instance)rules.elementAt(i), inst))
+          {
+            if(print_msg)
+              System.out.println(((Instance)rules.elementAt(i)).toString() + 
+                  " is (2) redundant wrt " + inst.toString());
+            // rule i is redundant w.r.t E, discard rule i
+            rules.removeElementAt(i);
+            i--;
+            continue;
+          }
+        }
+
+        // is E inconsistent and has a higher output?
+        if(isGreaterInput((Instance)rules.elementAt(i), inst) && 
+            !isGreaterOutput((Instance)rules.elementAt(i), inst))
+        {
+
+          // Conservative
+          if (m_resolutionMode == RESOLUTION_CONSERVATIVE)
+          {
+            // discard E
+            addr = false;
+          }
+          // Random
+          if (m_resolutionMode == RESOLUTION_RANDOM)
+          {
+            // select random rule to keep
+            if(rand.nextBoolean()) 
+            {
+              addr = addr || true;
+              rules.removeElementAt(i);
+              i--;
+            }
+            else
+              addr = false;
+          }
+          // No Conflict Resolution, ignore new rule
+          if (m_resolutionMode == RESOLUTION_NONE)
+          {
+            addr = false; 
+          }
+          // Average
+          if (m_resolutionMode == RESOLUTION_AVERAGE)
+          {
+            // create 'average rule'
+            if(print_msg)
+              System.out.print(inst.toString() + " - " +
+                  ((Instance)rules.elementAt(i)).toString());
+            inst = averageRule(inst, (Instance)rules.elementAt(i));
+            System.out.println(" : Average : " + inst.toString());
+            // Remove current rule
+            rules.removeElementAt(i);
+            // test average rule
+            addr = true;
+            i = 0;
+          }
+          continue;
+        }
+        // is E inconsistent and has a lower output?
+        if(isGreaterInput(inst, (Instance)rules.elementAt(i)) && 
+            !isGreaterOutput(inst, (Instance)rules.elementAt(i)))
+        {
+          // Conservative
+          if (m_resolutionMode == RESOLUTION_CONSERVATIVE)
+          {
+            // discard rule i
+            if(print_msg)
+              System.out.println("Discard rule "+
+                  ((Instance)rules.elementAt(i)).toString());
+            b = true;
+            rules.removeElementAt(i);
+            i--;
+          }
+          // Random
+          if (m_resolutionMode == RESOLUTION_RANDOM)
+          {
+            // select random rule to keep
+            if(rand.nextBoolean()) 
+            {
+              addr = addr || true;
+              rules.removeElementAt(i);
+              i--;
+            }
+            else
+              addr = false;
+          }
+          // No Conflict Resolution, ignore new rule
+          if (m_resolutionMode == RESOLUTION_NONE)
+          {
+            addr = false; 
+          }
+          // Average
+          if (m_resolutionMode == RESOLUTION_AVERAGE)
+          {
+            // create 'average rule'
+            if(print_msg)
+              System.out.print(inst.toString() + " - " +
+                  ((Instance)rules.elementAt(i)).toString());
+            inst = averageRule(inst, (Instance)rules.elementAt(i));
+            if(print_msg)
+              System.out.println(" : Average : " + inst.toString());
+            // Remove current rule
+            rules.removeElementAt(i);
+            // test average rule
+            addr = true;
+            i = 0;
+          }
+          continue;
+        }
+        // check if the rule is inconsistent
+        if(isEqualInput(inst,(Instance)rules.elementAt(i)))
+        {
+          if(isGreaterOutput(inst,(Instance)rules.elementAt(i)))
+          {
+            // Conservative
+            if (m_resolutionMode == RESOLUTION_CONSERVATIVE)                
+            {
+              // discard E
+              addr = false;
+            }
+            // random
+            if (m_resolutionMode == RESOLUTION_RANDOM)
+            {
+              // select random rule to keep
+              if(rand.nextBoolean()) 
+              {
+                addr = addr || true;
+                rules.removeElementAt(i);
+                i--;
+              }
+              else
+                addr = false;
+            }
+            // No Conflict Resolution, ignore new rule
+            if (m_resolutionMode == RESOLUTION_NONE)
+            {
+              addr = false; 
+            }
+            // Average
+            if (m_resolutionMode == RESOLUTION_AVERAGE)
+            {
+              // create 'average rule'
+              if(print_msg)
+                System.out.print(inst.toString() + " - " +
+                    ((Instance)rules.elementAt(i)).toString());
+              inst = averageRule(inst, (Instance)rules.elementAt(i));
+              if(print_msg)
+                System.out.println(" : 2Average : " + inst.toString());
+              // Remove current rule
+              rules.removeElementAt(i);
+              // test average rule
+              addr = true;
+              i = 0;
+            }
+            continue;
+          }
+          else if(isGreaterOutput((Instance)rules.elementAt(i),inst)) 
+          {
+
+            // Conservative
+            if (m_resolutionMode == RESOLUTION_CONSERVATIVE)                
+            {
+              //discard rule i
+              rules.removeElementAt(i);
+              i--;
+            }
+            //random
+            if (m_resolutionMode == RESOLUTION_RANDOM)
+            {
+              // select random rule to keep
+              if(rand.nextBoolean()) 
+              {
+                addr = addr || true;
+                rules.removeElementAt(i);
+                i--;
+              }
+              else
+                addr = false;
+            }
+            // No Conflict Resolution, ignore new rule
+            if (m_resolutionMode == RESOLUTION_NONE)
+            {
+              addr = false; 
+            }
+            // Average
+            if (m_resolutionMode == RESOLUTION_AVERAGE)
+            {
+              // create 'average rule'
+              if(print_msg)
+                System.out.print(inst.toString() + " - " +
+                    ((Instance)rules.elementAt(i)).toString());
+              inst = averageRule(inst, (Instance)rules.elementAt(i));
+              if(print_msg)
+                System.out.println(" : Average : " + inst.toString());
+              // Remove current rule
+              rules.removeElementAt(i);
+              // test average rule
+              addr = true;
+              i = 0;
+            }
+            continue;
+          }
+        }
+      }
+
+      if(b) System.out.println("broke out of loop totally!!");
+      // insert the new rule if it has not been discarded, based on 
+      // output order (decreasing order)
+      // System.out.println("Adding Rule");
+      int i = 0;
+      double output = inst.toDoubleArray()[classindex];
+
+      // Check Rule Base Limit
+      if(addr && ( upperBaseLimit <= 0 || upperBaseLimit > rules.size()))
+      {
+        while(i < rules.size() && 
+            (((Instance)rules.elementAt(i)).toDoubleArray())
+            [classindex] > output) i++;
+
+        if(i == rules.size())
+          rules.addElement(inst);
+        else if(i == 0)
+          rules.insertElementAt(inst, 0);
+        else
+          rules.insertElementAt(inst, i); 
+      }
+      return;
+    }
+
+    public double classify(Instance inst)
+    {
+      Instance tInst;
+
+      // fill in missing values
+      fillMissing(inst);
+
+      // Conservative
+      if (m_classificationMode == CLASSIFICATION_CONSERVATIVE)
+      {
+        for(int i=0; i < rules.size(); i++)
+        {
+          tInst = (Instance)rules.elementAt(i);
+          if(isGreaterInput(inst, tInst))
+          {
+            return (tInst.toDoubleArray())[inst.classIndex()];
+          }
+        }
+
+        return (((Instance)rules.lastElement()).toDoubleArray())
+        [inst.classIndex()];
+      }
+      // Nearest Neightbour
+      int cDist = -1;
+      int elem = -1;
+      if (m_classificationMode == CLASSIFICATION_NEARESTNEIGHBOUR)
+      {
+        for(int i=0; i < rules.size(); i++)
+        {
+          tInst = (Instance)rules.elementAt(i);
+          if(cDist == -1 || (distance(inst, tInst) < cDist))
+          {
+            cDist = distance(inst, tInst);
+            elem = i;
+          }
+          if(print_msg)
+            System.out.println(((Instance)rules.elementAt(i)).toString() +
+                " - " +
+                inst.toString() +
+                ": Distance is " + distance(inst,tInst));
+        }
+        if(print_msg)
+          System.out.println(((Instance)rules.elementAt(elem)).toString() +
+              " is closest to " +
+              inst.toString());
+
+        return (((Instance)rules.elementAt(elem)).toDoubleArray())
+        [inst.classIndex()];
+      }
+
+      return 0;
+    }
+  }
+
+  private OLMRules olmrules;
+  /**
+   * Generates the classifier.
+   *
+   * @param data the data to be used
+   * @exception Exception if the classifier can't built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception 
+  {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    data = new Instances(data);
+    numExamples = data.numInstances();
+    Enumeration e = data.enumerateInstances();
+
+    // Checks on data not implemented.
+
+    // reset random generator to produce the same results each time
+    rand = new Random(0);
+    // Options
+    if(print_msg)
+      System.out.println("Resolution mode: " + m_resolutionMode);
+    if(print_msg)
+      System.out.println("Classification: " + m_classificationMode);
+    if(print_msg)
+      System.out.println("Rule size: " + upperBaseLimit);
+
+    // initialize rules set.
+    olmrules = new OLMRules();
+    int i = 0;
+    // fill in rules.
+    if(print_msg)
+      System.out.println("Printing Rule Process");
+    while(e.hasMoreElements())
+    {
+      Instance ins = (Instance)e.nextElement();
+      if(print_msg)
+        System.out.println("Trying to add (" +
+            ins.toString() + ") Rule");
+      olmrules.addRule(ins); 
+      if(print_msg)
+        System.out.println("Result:");
+      if(print_msg)
+        olmrules.printRules();
+      i++;
+
+      // System.out.println("Added rule " + i);
+    }
+    //System.out.println("Rule set built!!");
+
+    // print rule set:
+
+  }
+
+  /**
+   * Prints a description of the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {   
+    return "OLM";
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class
+   */
+  public static void main(String[] args) {
+
+    runClassifier(new OLM(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/OneR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/OneR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/OneR.java	(revision 29)
@@ -0,0 +1,697 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OneR.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a 1R classifier; in other words, uses the minimum-error attribute for prediction, discretizing numeric attributes. For more information, see:<br/>
+ * <br/>
+ * R.C. Holte (1993). Very simple classification rules perform well on most commonly used datasets. Machine Learning. 11:63-91.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Holte1993,
+ *    author = {R.C. Holte},
+ *    journal = {Machine Learning},
+ *    pages = {63-91},
+ *    title = {Very simple classification rules perform well on most commonly used datasets},
+ *    volume = {11},
+ *    year = {1993}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;minimum bucket size&gt;
+ *  The minimum number of objects in a bucket (default: 6).</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Ian H. Witten (ihw@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+*/
+public class OneR 
+  extends AbstractClassifier 
+  implements TechnicalInformationHandler, Sourcable {
+    
+  /** for serialization */
+  static final long serialVersionUID = -2459427002147861445L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Class for building and using a 1R classifier; in other words, uses "
+      + "the minimum-error attribute for prediction, discretizing numeric "
+      + "attributes. For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "R.C. Holte");
+    result.setValue(Field.YEAR, "1993");
+    result.setValue(Field.TITLE, "Very simple classification rules perform well on most commonly used datasets");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "11");
+    result.setValue(Field.PAGES, "63-91");
+    
+    return result;
+  }
+
+  /**
+   * Class for storing store a 1R rule.
+   */
+  private class OneRRule 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 1152814630957092281L;
+
+    /** The class attribute. */
+    private Attribute m_class;
+
+    /** The number of instances used for building the rule. */
+    private int m_numInst;
+
+    /** Attribute to test */
+    private Attribute m_attr; 
+
+    /** Training set examples this rule gets right */
+    private int m_correct; 
+
+    /** Predicted class for each value of attr */
+    private int[] m_classifications; 
+
+    /** Predicted class for missing values */
+    private int m_missingValueClass = -1; 
+
+    /** Breakpoints (numeric attributes only) */
+    private double[] m_breakpoints; 
+  
+    /**
+     * Constructor for nominal attribute.
+     * 
+     * @param data the data to work with
+     * @param attribute the attribute to use
+     * @throws Exception if something goes wrong
+     */
+    public OneRRule(Instances data, Attribute attribute) throws Exception {
+
+      m_class = data.classAttribute();
+      m_numInst = data.numInstances();
+      m_attr = attribute;
+      m_correct = 0;
+      m_classifications = new int[m_attr.numValues()];
+    }
+
+    /**
+     * Constructor for numeric attribute.
+     * 
+     * @param data the data to work with
+     * @param attribute the attribute to use
+     * @param nBreaks the break point
+     * @throws Exception if something goes wrong
+     */
+    public OneRRule(Instances data, Attribute attribute, int nBreaks) throws Exception {
+
+      m_class = data.classAttribute();
+      m_numInst = data.numInstances();
+      m_attr = attribute;
+      m_correct = 0;
+      m_classifications = new int[nBreaks];
+      m_breakpoints = new double[nBreaks - 1]; // last breakpoint is infinity
+    }
+    
+    /**
+     * Returns a description of the rule.
+     * 
+     * @return a string representation of the rule
+     */
+    public String toString() {
+
+      try {
+	StringBuffer text = new StringBuffer();
+	text.append(m_attr.name() + ":\n");
+	for (int v = 0; v < m_classifications.length; v++) {
+	  text.append("\t");
+	  if (m_attr.isNominal()) {
+	    text.append(m_attr.value(v));
+	  } else if (v < m_breakpoints.length) {
+	    text.append("< " + m_breakpoints[v]);
+	  } else if (v > 0) {
+	    text.append(">= " + m_breakpoints[v - 1]);
+	  } else {
+	    text.append("not ?");
+	  }
+	  text.append("\t-> " + m_class.value(m_classifications[v]) + "\n");
+	}
+	if (m_missingValueClass != -1) {
+	  text.append("\t?\t-> " + m_class.value(m_missingValueClass) + "\n");
+	}
+	text.append("(" + m_correct + "/" + m_numInst + " instances correct)\n");
+	return text.toString();
+      } catch (Exception e) {
+	return "Can't print OneR classifier!";
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+  
+  /** A 1-R rule */
+  private OneRRule m_rule;
+
+  /** The minimum bucket size */
+  private int m_minBucketSize = 6;
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+    
+  /**
+   * Classifies a given instance.
+   *
+   * @param inst the instance to be classified
+   * @return the classification of the instance
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.classifyInstance(inst);
+    }
+    
+    int v = 0;
+    if (inst.isMissing(m_rule.m_attr)) {
+      if (m_rule.m_missingValueClass != -1) {
+	return m_rule.m_missingValueClass;
+      } else {
+	return 0;  // missing values occur in test but not training set    
+      }
+    }
+    if (m_rule.m_attr.isNominal()) {
+      v = (int) inst.value(m_rule.m_attr);
+    } else {
+      while (v < m_rule.m_breakpoints.length &&
+	     inst.value(m_rule.m_attr) >= m_rule.m_breakpoints[v]) {
+	v++;
+      }
+    }
+    return m_rule.m_classifications[v];
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances the instances to be used for building the classifier
+   * @throws Exception if the classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) 
+    throws Exception {
+    
+    boolean noRule = true;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    Instances data = new Instances(instances);
+    data.deleteWithMissingClass();
+
+    // only class? -> build ZeroR model
+    if (data.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(data);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    // for each attribute ...
+    Enumeration enu = instances.enumerateAttributes();
+    while (enu.hasMoreElements()) {
+      try {
+	OneRRule r = newRule((Attribute) enu.nextElement(), data);
+
+	// if this attribute is the best so far, replace the rule
+	if (noRule || r.m_correct > m_rule.m_correct) {
+	  m_rule = r;
+	}
+	noRule = false;
+      } catch (Exception ex) {
+      }
+    }
+    
+    if (noRule)
+      throw new WekaException("No attributes found to work with!");
+  }
+
+  /**
+   * Create a rule branching on this attribute.
+   *
+   * @param attr the attribute to branch on
+   * @param data the data to be used for creating the rule
+   * @return the generated rule
+   * @throws Exception if the rule can't be built successfully
+   */
+  public OneRRule newRule(Attribute attr, Instances data) throws Exception {
+
+    OneRRule r;
+
+    // ... create array to hold the missing value counts
+    int[] missingValueCounts =
+      new int [data.classAttribute().numValues()];
+    
+    if (attr.isNominal()) {
+      r = newNominalRule(attr, data, missingValueCounts);
+    } else {
+      r = newNumericRule(attr, data, missingValueCounts);
+    }
+    r.m_missingValueClass = Utils.maxIndex(missingValueCounts);
+    if (missingValueCounts[r.m_missingValueClass] == 0) {
+      r.m_missingValueClass = -1; // signal for no missing value class
+    } else {
+      r.m_correct += missingValueCounts[r.m_missingValueClass];
+    }
+    return r;
+  }
+
+  /**
+   * Create a rule branching on this nominal attribute.
+   *
+   * @param attr the attribute to branch on
+   * @param data the data to be used for creating the rule
+   * @param missingValueCounts to be filled in
+   * @return the generated rule
+   * @throws Exception if the rule can't be built successfully
+   */
+  public OneRRule newNominalRule(Attribute attr, Instances data,
+                                 int[] missingValueCounts) throws Exception {
+
+    // ... create arrays to hold the counts
+    int[][] counts = new int [attr.numValues()]
+                             [data.classAttribute().numValues()];
+      
+    // ... calculate the counts
+    Enumeration enu = data.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      Instance i = (Instance) enu.nextElement();
+      if (i.isMissing(attr)) {
+	missingValueCounts[(int) i.classValue()]++; 
+      } else {
+	counts[(int) i.value(attr)][(int) i.classValue()]++;
+      }
+    }
+
+    OneRRule r = new OneRRule(data, attr); // create a new rule
+    for (int value = 0; value < attr.numValues(); value++) {
+      int best = Utils.maxIndex(counts[value]);
+      r.m_classifications[value] = best;
+      r.m_correct += counts[value][best];
+    }
+    return r;
+  }
+
+  /**
+   * Create a rule branching on this numeric attribute
+   *
+   * @param attr the attribute to branch on
+   * @param data the data to be used for creating the rule
+   * @param missingValueCounts to be filled in
+   * @return the generated rule
+   * @throws Exception if the rule can't be built successfully
+   */
+  public OneRRule newNumericRule(Attribute attr, Instances data,
+                             int[] missingValueCounts) throws Exception {
+
+
+    // ... can't be more than numInstances buckets
+    int [] classifications = new int[data.numInstances()];
+    double [] breakpoints = new double[data.numInstances()];
+
+    // create array to hold the counts
+    int [] counts = new int[data.classAttribute().numValues()];
+    int correct = 0;
+    int lastInstance = data.numInstances();
+
+    // missing values get sorted to the end of the instances
+    data.sort(attr);
+    while (lastInstance > 0 && 
+           data.instance(lastInstance-1).isMissing(attr)) {
+      lastInstance--;
+      missingValueCounts[(int) data.instance(lastInstance).
+                         classValue()]++; 
+    }
+    int i = 0; 
+    int cl = 0; // index of next bucket to create
+    int it;
+    while (i < lastInstance) { // start a new bucket
+      for (int j = 0; j < counts.length; j++) counts[j] = 0;
+      do { // fill it until it has enough of the majority class
+        it = (int) data.instance(i++).classValue();
+        counts[it]++;
+      } while (counts[it] < m_minBucketSize && i < lastInstance);
+
+      // while class remains the same, keep on filling
+      while (i < lastInstance && 
+             (int) data.instance(i).classValue() == it) { 
+        counts[it]++; 
+        i++;
+      }
+      while (i < lastInstance && // keep on while attr value is the same
+             (data.instance(i - 1).value(attr) 
+	      == data.instance(i).value(attr))) {
+        counts[(int) data.instance(i++).classValue()]++;
+      }
+      for (int j = 0; j < counts.length; j++) {
+        if (counts[j] > counts[it]) { 
+	  it = j;
+	}
+      }
+      if (cl > 0) { // can we coalesce with previous class?
+        if (counts[classifications[cl - 1]] == counts[it]) {
+          it = classifications[cl - 1];
+	}
+        if (it == classifications[cl - 1]) {
+	  cl--; // yes!
+	}
+      }
+      correct += counts[it];
+      classifications[cl] = it;
+      if (i < lastInstance) {
+        breakpoints[cl] = (data.instance(i - 1).value(attr)
+			   + data.instance(i).value(attr)) / 2;
+      }
+      cl++;
+    }
+    if (cl == 0) {
+      throw new Exception("Only missing values in the training data!");
+    }
+    OneRRule r = new OneRRule(data, attr, cl); // new rule with cl branches
+    r.m_correct = correct;
+    for (int v = 0; v < cl; v++) {
+      r.m_classifications[v] = classifications[v];
+      if (v < cl-1) {
+	r.m_breakpoints[v] = breakpoints[v];
+      }
+    }
+
+    return r;
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    String string = "\tThe minimum number of objects in a bucket (default: 6).";
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(string, "B", 1, 
+				    "-B <minimum bucket size>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;minimum bucket size&gt;
+   *  The minimum number of objects in a bucket (default: 6).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String bucketSizeString = Utils.getOption('B', options);
+    if (bucketSizeString.length() != 0) {
+      m_minBucketSize = Integer.parseInt(bucketSizeString);
+    } else {
+      m_minBucketSize = 6;
+    }
+  }
+
+  /**
+   * Gets the current settings of the OneR classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [2];
+    int current = 0;
+
+    options[current++] = "-B"; options[current++] = "" + m_minBucketSize;
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string that describes the classifier as source. The
+   * classifier will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain a method with the signature:
+   * <pre><code>
+   * public static double classify(Object[] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className the name that should be given to the source class.
+   * @return the object source described by a string
+   * @throws Exception if the souce can't be computed
+   */
+  public String toSource(String className) throws Exception {
+    StringBuffer        result;
+    int                 i;
+    
+    result = new StringBuffer();
+    
+    if (m_ZeroR != null) {
+      result.append(((ZeroR) m_ZeroR).toSource(className));
+    }
+    else {
+      result.append("class " + className + " {\n");
+      result.append("  public static double classify(Object[] i) {\n");
+      result.append("    // chosen attribute: " + m_rule.m_attr.name() + " (" + m_rule.m_attr.index() + ")\n");
+      result.append("\n");
+      // missing values
+      result.append("    // missing value?\n");
+      result.append("    if (i[" + m_rule.m_attr.index() + "] == null)\n");
+      if (m_rule.m_missingValueClass != -1)
+        result.append("      return Double.NaN;\n");
+      else
+        result.append("      return 0;\n");
+      result.append("\n");
+      
+      // actual prediction
+      result.append("    // prediction\n");
+      result.append("    double v = 0;\n");
+      result.append("    double[] classifications = new double[]{" + Utils.arrayToString(m_rule.m_classifications) + "};");
+      result.append(" // ");
+      for (i = 0; i < m_rule.m_classifications.length; i++) {
+        if (i > 0)
+          result.append(", ");
+        result.append(m_rule.m_class.value(m_rule.m_classifications[i]));
+      }
+      result.append("\n");
+      if (m_rule.m_attr.isNominal()) {
+        for (i = 0; i < m_rule.m_attr.numValues(); i++) {
+          result.append("    ");
+          if (i > 0)
+            result.append("else ");
+          result.append("if (((String) i[" + m_rule.m_attr.index() + "]).equals(\"" + m_rule.m_attr.value(i) + "\"))\n");
+          result.append("      v = " + i + "; // " + m_rule.m_class.value(m_rule.m_classifications[i]) + "\n");
+        }
+      }
+      else {
+        result.append("    double[] breakpoints = new double[]{" + Utils.arrayToString(m_rule.m_breakpoints) + "};\n");
+        result.append("    while (v < breakpoints.length && \n");
+        result.append("           ((Double) i[" + m_rule.m_attr.index() + "]) >= breakpoints[(int) v]) {\n");
+        result.append("      v++;\n");
+        result.append("    }\n");
+      }
+      result.append("    return classifications[(int) v];\n");
+      
+      result.append("  }\n");
+      result.append("}\n");
+    }
+    
+    return result.toString();
+  }
+
+  /**
+   * Returns a description of the classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_rule == null) {
+      return "OneR: No model built yet.";
+    }
+    return m_rule.toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minBucketSizeTipText() {
+    return "The minimum bucket size used for discretizing numeric "
+      + "attributes.";
+  }
+  
+  /**
+   * Get the value of minBucketSize.
+   * @return Value of minBucketSize.
+   */
+  public int getMinBucketSize() {
+    
+    return m_minBucketSize;
+  }
+  
+  /**
+   * Set the value of minBucketSize.
+   * @param v  Value to assign to minBucketSize.
+   */
+  public void setMinBucketSize(int v) {
+    
+    m_minBucketSize = v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class
+   * 
+   * @param argv the commandline options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new OneR(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/PART.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/PART.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/PART.java	(revision 29)
@@ -0,0 +1,764 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PART.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.rules.part.MakeDecList;
+import weka.classifiers.trees.j48.BinC45ModelSelection;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating a PART decision list. Uses separate-and-conquer. Builds a partial C4.5 decision tree in each iteration and makes the "best" leaf into a rule.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Eibe Frank, Ian H. Witten: Generating Accurate Rule Sets Without Global Optimization. In: Fifteenth International Conference on Machine Learning, 144-151, 1998.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Frank1998,
+ *    author = {Eibe Frank and Ian H. Witten},
+ *    booktitle = {Fifteenth International Conference on Machine Learning},
+ *    editor = {J. Shavlik},
+ *    pages = {144-151},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Generating Accurate Rule Sets Without Global Optimization},
+ *    year = {1998},
+ *    PS = {http://www.cs.waikato.ac.nz/\~eibe/pubs/ML98-57.ps.gz}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of objects&gt;
+ *  Set minimum number of objects per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -U
+ *  Generate unpruned decision list.</pre>
+ * 
+ * <pre> -J
+ *  Do not use MDL correction for info gain on numeric attributes.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6089 $
+ */
+public class PART 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, Summarizable, 
+             AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8121455039782598361L;
+  
+  /** The decision list */
+  private MakeDecList m_root;
+
+  /** Confidence level */
+  private float m_CF = 0.25f;
+
+  /** Minimum number of objects */
+  private int m_minNumObj = 2;
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection = true;         
+
+  /** Use reduced error pruning? */
+  private boolean m_reducedErrorPruning = false;
+
+  /** Number of folds for reduced error pruning. */
+  private int m_numFolds = 3;
+
+  /** Binary splits on nominal attributes? */
+  private boolean m_binarySplits = false;
+  
+  /** Generate unpruned list? */
+  private boolean m_unpruned = false;
+
+  /** The seed for random number generation. */
+  private int m_Seed = 1;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for generating a PART decision list. Uses "
+      + "separate-and-conquer. Builds a partial C4.5 decision tree "
+      + "in each iteration and makes the \"best\" leaf into a rule.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Eibe Frank and Ian H. Witten");
+    result.setValue(Field.TITLE, "Generating Accurate Rule Sets Without Global Optimization");
+    result.setValue(Field.BOOKTITLE, "Fifteenth International Conference on Machine Learning");
+    result.setValue(Field.EDITOR, "J. Shavlik");
+    result.setValue(Field.YEAR, "1998");
+    result.setValue(Field.PAGES, "144-151");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.PS, "http://www.cs.waikato.ac.nz/~eibe/pubs/ML98-57.ps.gz");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+
+    if (m_unpruned) 
+      result = new MakeDecList(null, m_minNumObj).getCapabilities();
+    else if (m_reducedErrorPruning) 
+      result = new MakeDecList(null, m_numFolds, m_minNumObj, m_Seed).getCapabilities();
+    else
+      result = new MakeDecList(null, m_CF, m_minNumObj).getCapabilities();
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances the data to train with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) 
+       throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    ModelSelection modSelection;	 
+
+    if (m_binarySplits)
+      modSelection = new BinC45ModelSelection(m_minNumObj, instances, m_useMDLcorrection);
+    else
+      modSelection = new C45ModelSelection(m_minNumObj, instances, m_useMDLcorrection);
+    if (m_unpruned) 
+      m_root = new MakeDecList(modSelection, m_minNumObj);
+    else if (m_reducedErrorPruning) 
+      m_root = new MakeDecList(modSelection, m_numFolds, m_minNumObj, m_Seed);
+    else
+      m_root = new MakeDecList(modSelection, m_CF, m_minNumObj);
+    m_root.buildClassifier(instances);
+    if (m_binarySplits) {
+      ((BinC45ModelSelection)modSelection).cleanup();
+    } else {
+      ((C45ModelSelection)modSelection).cleanup();
+    }
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) 
+       throws Exception {
+
+    return m_root.classifyInstance(instance);
+  }
+
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to get the distribution for
+   * @return the class probabilities
+   * @throws Exception if the distribution can't be computed successfully
+   */
+  public final double [] distributionForInstance(Instance instance) 
+       throws Exception {
+
+    return m_root.distributionForInstance(instance);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * Valid options are: <p>
+   *
+   * -C confidence <br>
+   * Set confidence threshold for pruning. (Default: 0.25) <p>
+   *
+   * -M number <br>
+   * Set minimum number of instances per leaf. (Default: 2) <p>
+   *
+   * -R <br>
+   * Use reduced error pruning. <p>
+   *
+   * -N number <br>
+   * Set number of folds for reduced error pruning. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   *
+   * -B <br>
+   * Use binary splits for nominal attributes. <p>
+   *
+   * -U <br>
+   * Generate unpruned decision list. <p>
+   *
+   * -Q <br>
+   * The seed for reduced-error pruning. <p>
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(8);
+
+    newVector.
+	addElement(new Option("\tSet confidence threshold for pruning.\n" +
+			      "\t(default 0.25)",
+			      "C", 1, "-C <pruning confidence>"));
+    newVector.
+	addElement(new Option("\tSet minimum number of objects per leaf.\n" +
+			      "\t(default 2)",
+			      "M", 1, "-M <minimum number of objects>"));
+    newVector.
+	addElement(new Option("\tUse reduced error pruning.",
+			      "R", 0, "-R"));
+    newVector.
+	addElement(new Option("\tSet number of folds for reduced error\n" +
+			      "\tpruning. One fold is used as pruning set.\n" +
+			      "\t(default 3)",
+			      "N", 1, "-N <number of folds>"));
+    newVector.
+	addElement(new Option("\tUse binary splits only.",
+			      "B", 0, "-B"));
+    newVector.
+	addElement(new Option("\tGenerate unpruned decision list.",
+			      "U", 0, "-U"));
+    newVector.
+      addElement(new Option("\tDo not use MDL correction for info gain on numeric attributes.",
+                            "J", 0, "-J"));
+    newVector.
+      addElement(new Option("\tSeed for random data shuffling (default 1).",
+			    "Q", 1, "-Q <seed>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of objects&gt;
+   *  Set minimum number of objects per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -U
+   *  Generate unpruned decision list.</pre>
+   * 
+   * <pre> -J
+   *  Do not use MDL correction for info gain on numeric attributes.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    // Pruning options
+    m_unpruned = Utils.getFlag('U', options);
+    m_reducedErrorPruning = Utils.getFlag('R', options);
+    m_binarySplits = Utils.getFlag('B', options);
+    m_useMDLcorrection = !Utils.getFlag('J', options);
+    String confidenceString = Utils.getOption('C', options);
+    if (confidenceString.length() != 0) {
+      if (m_reducedErrorPruning) {
+	throw new Exception("Setting CF doesn't make sense " +
+			    "for reduced error pruning.");
+      } else {
+	m_CF = (new Float(confidenceString)).floatValue();
+	if ((m_CF <= 0) || (m_CF >= 1)) {
+	  throw new Exception("CF has to be greater than zero and smaller than one!");
+	} 
+      }
+    } else {
+      m_CF = 0.25f;
+    }
+    String numFoldsString = Utils.getOption('N', options);
+    if (numFoldsString.length() != 0) {
+      if (!m_reducedErrorPruning) {
+	throw new Exception("Setting the number of folds" +
+			    " does only make sense for" +
+			    " reduced error pruning.");
+      } else {
+	m_numFolds = Integer.parseInt(numFoldsString);
+      }
+    } else {
+      m_numFolds = 3;
+    }
+
+    // Other options
+    String minNumString = Utils.getOption('M', options);
+    if (minNumString.length() != 0) {
+      m_minNumObj = Integer.parseInt(minNumString);
+    } else {
+      m_minNumObj = 2;
+    }
+    String seedString = Utils.getOption('Q', options);
+    if (seedString.length() != 0) {
+      m_Seed = Integer.parseInt(seedString);
+    } else {
+      m_Seed = 1;
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [12];
+    int current = 0;
+
+    if (m_unpruned) {
+      options[current++] = "-U";
+    }
+    if (m_reducedErrorPruning) {
+      options[current++] = "-R";
+    }
+    if (m_binarySplits) {
+      options[current++] = "-B";
+    }
+    options[current++] = "-M"; options[current++] = "" + m_minNumObj;
+    if (!m_reducedErrorPruning) {
+      options[current++] = "-C"; options[current++] = "" + m_CF;
+    }
+    if (m_reducedErrorPruning) {
+      options[current++] = "-N"; options[current++] = "" + m_numFolds;
+    }
+    options[current++] = "-Q"; options[current++] = "" + m_Seed;
+    if (!m_useMDLcorrection) {
+      options[current++] = "-J";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a description of the classifier
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_root == null) {
+      return "No classifier built";
+    }
+    return "PART decision list\n------------------\n\n" + m_root.toString();
+  }
+  
+  /**
+   * Returns a superconcise version of the model
+   * 
+   * @return a concise version of the model
+   */
+  public String toSummaryString() {
+
+    return "Number of rules: " + m_root.numRules() + "\n";
+  }
+  
+  /**
+   * Return the number of rules.
+   * @return the number of rules
+   */
+  public double measureNumRules() {
+    return m_root.numRules();
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (PART)");
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String confidenceFactorTipText() {
+    return "The confidence factor used for pruning (smaller values incur "
+      + "more pruning).";
+  }
+
+  /**
+   * Get the value of CF.
+   *
+   * @return Value of CF.
+   */
+  public float getConfidenceFactor() {
+    
+    return m_CF;
+  }
+  
+  /**
+   * Set the value of CF.
+   *
+   * @param v  Value to assign to CF.
+   */
+  public void setConfidenceFactor(float v) {
+    
+    m_CF = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumObjTipText() {
+    return "The minimum number of instances per rule.";
+  }
+
+  /**
+   * Get the value of minNumObj.
+   *
+   * @return Value of minNumObj.
+   */
+  public int getMinNumObj() {
+    
+    return m_minNumObj;
+  }
+  
+  /**
+   * Set the value of minNumObj.
+   *
+   * @param v  Value to assign to minNumObj.
+   */
+  public void setMinNumObj(int v) {
+    
+    m_minNumObj = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String reducedErrorPruningTipText() {
+    return "Whether reduced-error pruning is used instead of C.4.5 pruning.";
+  }
+
+  /**
+   * Get the value of reducedErrorPruning.
+   *
+   * @return Value of reducedErrorPruning.
+   */
+  public boolean getReducedErrorPruning() {
+    
+    return m_reducedErrorPruning;
+  }
+  
+  /**
+   * Set the value of reducedErrorPruning.
+   *
+   * @param v  Value to assign to reducedErrorPruning.
+   */
+  public void setReducedErrorPruning(boolean v) {
+    
+    m_reducedErrorPruning = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String unprunedTipText() {
+    return "Whether pruning is performed.";
+  }
+
+  /**
+   * Get the value of unpruned.
+   *
+   * @return Value of unpruned.
+   */
+  public boolean getUnpruned() {
+    
+    return m_unpruned;
+  }
+  
+  /**
+   * Set the value of unpruned.
+   *
+   * @param newunpruned Value to assign to unpruned.
+   */
+  public void setUnpruned(boolean newunpruned) {
+    
+    m_unpruned = newunpruned;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useMDLcorrectionTipText() {
+    return "Whether MDL correction is used when finding splits on numeric attributes.";
+  }
+
+  /**
+   * Get the value of useMDLcorrection.
+   *
+   * @return Value of useMDLcorrection.
+   */
+  public boolean getUseMDLcorrection() {
+    
+    return m_useMDLcorrection;
+  }
+  
+  /**
+   * Set the value of useMDLcorrection.
+   *
+   * @param newuseMDLcorrection Value to assign to useMDLcorrection.
+   */
+  public void setUseMDLcorrection(boolean newuseMDLcorrection) {
+    
+    m_useMDLcorrection = newuseMDLcorrection;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Determines the amount of data used for reduced-error pruning. "
+      + " One fold is used for pruning, the rest for growing the rules.";
+  }
+
+  /**
+   * Get the value of numFolds.
+   *
+   * @return Value of numFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_numFolds;
+  }
+  
+  /**
+   * Set the value of numFolds.
+   *
+   * @param v  Value to assign to numFolds.
+   */
+  public void setNumFolds(int v) {
+    
+    m_numFolds = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data " +
+      "when reduced-error pruning is used.";
+  }
+
+  /**
+   * Get the value of Seed.
+   *
+   * @return Value of Seed.
+   */
+  public int getSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the value of Seed.
+   *
+   * @param newSeed Value to assign to Seed.
+   */
+  public void setSeed(int newSeed) {
+    
+    m_Seed = newSeed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binarySplitsTipText() {
+    return "Whether to use binary splits on nominal attributes when "
+      + "building the partial trees.";
+  }
+  
+  /**
+   * Get the value of binarySplits.
+   *
+   * @return Value of binarySplits.
+   */
+  public boolean getBinarySplits() {
+    
+    return m_binarySplits;
+  }
+  
+  /**
+   * Set the value of binarySplits.
+   *
+   * @param v  Value to assign to binarySplits.
+   */
+  public void setBinarySplits(boolean v) {
+    
+    m_binarySplits = v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6089 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv command line options 
+   */
+  public static void main(String [] argv){
+    runClassifier(new PART(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/Prism.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/Prism.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/Prism.java	(revision 29)
@@ -0,0 +1,559 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Prism.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a PRISM rule set for classification. Can only deal with nominal attributes. Can't deal with missing values. Doesn't do any pruning.<br/>
+ * <br/>
+ * For more information, see <br/>
+ * <br/>
+ * J. Cendrowska (1987). PRISM: An algorithm for inducing modular rules. International Journal of Man-Machine Studies. 27(4):349-370.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Cendrowska1987,
+ *    author = {J. Cendrowska},
+ *    journal = {International Journal of Man-Machine Studies},
+ *    number = {4},
+ *    pages = {349-370},
+ *    title = {PRISM: An algorithm for inducing modular rules},
+ *    volume = {27},
+ *    year = {1987}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Ian H. Witten (ihw@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+*/
+public class Prism 
+  extends AbstractClassifier
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 1310258880025902106L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for building and using a PRISM rule set for classification. "
+      + "Can only deal with nominal attributes. Can't deal with missing values. "
+      + "Doesn't do any pruning.\n\n"
+      + "For more information, see \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "J. Cendrowska");
+    result.setValue(Field.YEAR, "1987");
+    result.setValue(Field.TITLE, "PRISM: An algorithm for inducing modular rules");
+    result.setValue(Field.JOURNAL, "International Journal of Man-Machine Studies");
+    result.setValue(Field.VOLUME, "27");
+    result.setValue(Field.NUMBER, "4");
+    result.setValue(Field.PAGES, "349-370");
+    
+    return result;
+  }
+
+  /**
+   * Class for storing a PRISM ruleset, i.e. a list of rules
+   */
+  private class PrismRule 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 4248784350656508583L;
+    
+    /** The classification */
+    private int m_classification;
+
+    /** The instance */
+    private Instances m_instances;
+
+    /** First test of this rule */
+    private Test m_test; 
+
+    /** Number of errors made by this rule (will end up 0) */
+    private int m_errors; 
+
+    /** The next rule in the list */
+    private PrismRule m_next;
+
+    /**
+     * Constructor that takes instances and the classification.
+     *
+     * @param data the instances
+     * @param cl the class
+     * @exception Exception if something goes wrong
+     */
+    public PrismRule(Instances data, int cl) throws Exception {
+
+      m_instances = data;
+      m_classification = cl;
+      m_test = null;
+      m_next = null;
+      m_errors = 0;
+      Enumeration enu = data.enumerateInstances();
+      while (enu.hasMoreElements()) {
+        if ((int) ((Instance) enu.nextElement()).classValue() != cl) {
+	  m_errors++;
+	}
+      }
+      m_instances = new Instances(m_instances, 0);
+    }  
+
+    /**
+     * Returns the result assigned by this rule to a given instance.
+     *
+     * @param inst the instance to be classified
+     * @return the classification
+     */
+    public int resultRule(Instance inst) {
+
+      if (m_test == null || m_test.satisfies(inst)) {
+	return m_classification;
+      } else {
+	return -1;
+      }
+    }
+
+    /**
+     * Returns the result assigned by these rules to a given instance.
+     *
+     * @param inst the instance to be classified
+     * @return the classification
+     */
+    public int resultRules(Instance inst) {
+
+      if (resultRule(inst) != -1) {
+	return m_classification;
+      } else if (m_next != null) {
+	return m_next.resultRules(inst);
+      } else {
+	return -1;
+      }
+    }
+
+    /**
+     * Returns the set of instances that are covered by this rule.
+     *
+     * @param data the instances to be checked
+     * @return the instances covered
+     */
+    public Instances coveredBy(Instances data) {
+
+      Instances r = new Instances(data, data.numInstances());
+      Enumeration enu = data.enumerateInstances();
+      while (enu.hasMoreElements()) {
+	Instance i = (Instance) enu.nextElement();
+	if (resultRule(i) != -1) {
+	  r.add(i);
+	}
+      }
+      r.compactify();
+      return r;
+    }
+
+    /**
+     * Returns the set of instances that are not covered by this rule.
+     *
+     * @param data the instances to be checked
+     * @return the instances not covered
+     */
+    public Instances notCoveredBy(Instances data) {
+
+      Instances r = new Instances(data, data.numInstances());
+      Enumeration enu = data.enumerateInstances();
+      while (enu.hasMoreElements()) {
+	Instance i = (Instance) enu.nextElement();
+	if (resultRule(i) == -1) {
+	  r.add(i);
+	}
+      }
+      r.compactify();
+      return r;
+    }
+
+    /**
+     * Prints the set of rules.
+     *
+     * @return a description of the rules as a string
+     */
+    public String toString() {
+
+      try {
+	StringBuffer text = new StringBuffer();
+	if (m_test != null) {
+	  text.append("If ");
+	  for (Test t = m_test; t != null; t = t.m_next) {
+	    if (t.m_attr == -1) {
+	      text.append("?");
+	    } else {
+	      text.append(m_instances.attribute(t.m_attr).name() + " = " +
+			  m_instances.attribute(t.m_attr).value(t.m_val));
+	    }
+	    if (t.m_next != null) {
+	      text.append("\n   and ");
+	    }
+	  }
+	  text.append(" then ");
+	}
+	text.append(m_instances.classAttribute().value(m_classification) + "\n");
+	if (m_next != null) {
+	  text.append(m_next.toString());
+	}
+	return text.toString();
+      } catch (Exception e) {
+	return "Can't print Prism classifier!";
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+  
+  /**
+   * Class for storing a list of attribute-value tests
+   */
+  private class Test 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -8925333011350280799L;
+
+    /** Attribute to test */
+    private int m_attr = -1; 
+
+    /** The attribute's value */
+    private int m_val; 
+
+    /** The next test in the rule */
+    private Test m_next = null; 
+
+    /**
+     * Returns whether a given instance satisfies this test.
+     *
+     * @param inst the instance to be tested
+     * @return true if the instance satisfies the test
+     */
+    private boolean satisfies(Instance inst) {
+
+      if ((int) inst.value(m_attr) == m_val) {
+        if (m_next == null) {
+	  return true;
+	} else {
+	  return m_next.satisfies(inst);
+	}
+      }
+      return false;    
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /** The first rule in the list of rules */
+  private PrismRule m_rules;
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param inst the instance to be classified
+   * @return the classification
+   */
+  public double classifyInstance(Instance inst) {
+
+    int result = m_rules.resultRules(inst);
+    if (result == -1) {
+      return Utils.missingValue();
+    } else {
+      return (double)result;
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param data the data to be used
+   * @exception Exception if the classifier can't built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    int cl; // possible value of theClass
+    Instances E, ruleE;
+    PrismRule rule = null;
+    Test test = null, oldTest = null;
+    int bestCorrect, bestCovers, attUsed;
+    Enumeration enumAtt;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    for (cl = 0; cl < data.numClasses(); cl++) { // for each class cl
+      E = data; // initialize E to the instance set
+      while (contains(E, cl)) { // while E contains examples in class cl
+        rule = addRule(rule, new PrismRule(E, cl)); // make a new rule
+        ruleE = E; // examples covered by this rule
+        while (rule.m_errors != 0) { // until the rule is perfect
+          test = new Test(); // make a new test
+          bestCorrect = bestCovers = attUsed = 0;
+
+          // for every attribute not mentioned in the rule
+          enumAtt = ruleE.enumerateAttributes();
+          while (enumAtt.hasMoreElements()) {
+            Attribute attr = (Attribute) enumAtt.nextElement();
+            if (isMentionedIn(attr, rule.m_test)) {
+	      attUsed++; 
+	      continue;
+	    }
+            int M = attr.numValues();
+            int[] covers = new int [M];
+            int[] correct = new int [M];
+            for (int j = 0; j < M; j++) {
+	      covers[j] = correct[j] = 0;
+	    }
+
+            // ... calculate the counts for this class
+            Enumeration enu = ruleE.enumerateInstances();
+            while (enu.hasMoreElements()) {
+              Instance i = (Instance) enu.nextElement();
+              covers[(int) i.value(attr)]++;
+              if ((int) i.classValue() == cl) {
+                correct[(int) i.value(attr)]++;
+	      }
+            }
+
+            // ... for each value of this attribute, see if this test is better
+            for (int val = 0; val < M; val ++) {
+              int diff = correct[val] * bestCovers - bestCorrect * covers[val];
+
+              // this is a ratio test, correct/covers vs best correct/covers
+              if (test.m_attr == -1
+                  || diff > 0 || (diff == 0 && correct[val] > bestCorrect)) {
+
+                // update the rule to use this test
+                bestCorrect = correct[val];
+                bestCovers = covers[val];
+                test.m_attr = attr.index();
+                test.m_val = val;
+                rule.m_errors = bestCovers - bestCorrect;
+              }
+            }
+          }
+	  if (test.m_attr == -1) { // Couldn't find any sensible test
+	    break;
+	  }
+	  oldTest = addTest(rule, oldTest, test);
+	  ruleE = rule.coveredBy(ruleE);
+	  if (attUsed == (data.numAttributes() - 1)) { // Used all attributes.
+	    break;
+	  }
+        }
+        E = rule.notCoveredBy(E);
+      }
+    }
+  }
+
+  /**
+   * Add a rule to the ruleset.
+   *
+   * @param lastRule the last rule in the rule set
+   * @param newRule the rule to be added
+   * @return the new last rule in the rule set
+   */
+  private PrismRule addRule(PrismRule lastRule, PrismRule newRule) {
+
+    if (lastRule == null) {
+      m_rules = newRule;
+    } else {
+      lastRule.m_next = newRule;
+    }
+    return newRule;
+  }
+
+  /**
+   * Add a test to this rule.
+   *
+   * @param rule the rule to which test is to be added
+   * @param lastTest the rule's last test
+   * @param newTest the test to be added
+   * @return the new last test of the rule
+   */
+  private Test addTest(PrismRule rule, Test lastTest, Test newTest) {
+
+    if (rule.m_test == null) {
+      rule.m_test = newTest;
+    } else {
+      lastTest.m_next = newTest;
+    }
+    return newTest;
+  }
+
+  /**
+   * Does E contain any examples in the class C?
+   *
+   * @param E the instances to be checked
+   * @param C the class
+   * @return true if there are any instances of class C
+   * @throws Exception if something goes wrong
+   */
+  private static boolean contains(Instances E, int C) throws Exception {
+
+    Enumeration enu = E.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      if ((int) ((Instance) enu.nextElement()).classValue() == C) {
+	return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Is this attribute mentioned in the rule?
+   *
+   * @param attr the attribute to be checked for
+   * @param t test contained by rule
+   * @return true if the attribute is mentioned in the rule
+   */
+  private static boolean isMentionedIn(Attribute attr, Test t) {
+
+    if (t == null) { 
+      return false;
+    }
+    if (t.m_attr == attr.index()) {
+      return true;
+    }
+    return isMentionedIn(attr, t.m_next);
+  }    
+
+  /**
+   * Prints a description of the classifier.
+   *
+   * @return a description of the classifier as a string
+   */
+  public String toString() {
+
+    if (m_rules == null) {
+      return "Prism: No model built yet.";
+    }
+    return "Prism rules\n----------\n" + m_rules.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String[] args) {
+    runClassifier(new Prism(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/Ridor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/Ridor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/Ridor.java	(revision 29)
@@ -0,0 +1,1654 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Ridor.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.UnsupportedClassTypeException;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * An implementation of a RIpple-DOwn Rule learner.<br/>
+ * <br/>
+ * It generates a default rule first and then the exceptions for the default rule with the least (weighted) error rate.  Then it generates the "best" exceptions for each exception and iterates until pure.  Thus it performs a tree-like expansion of exceptions.The exceptions are a set of rules that predict classes other than the default. IREP is used to generate the exceptions.<br/>
+ * <br/>
+ * For more information about Ripple-Down Rules, see:<br/>
+ * <br/>
+ * Brian R. Gaines, Paul Compton (1995). Induction of Ripple-Down Rules Applied to Modeling Large Databases. J. Intell. Inf. Syst.. 5(3):211-228.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * There are five inner classes defined in this class. <br>
+ * The first is Ridor_node, which implements one node in the Ridor tree.  It's basically
+ * composed of a default class and a set of exception rules to the default class.<br>
+ * The second inner class is RidorRule, which implements a single exception rule 
+ * using REP.<br>
+ * The last three inner classes are only used in RidorRule.  They are Antd, NumericAntd 
+ * and NominalAntd, which all implement a single antecedent in the RidorRule. <br>
+ * The Antd class is an abstract class, which has two subclasses, NumericAntd and 
+ * NominalAntd, to implement the corresponding abstract functions.  These two subclasses
+ * implement the functions related to a antecedent with a nominal attribute and a numeric 
+ * attribute respectively.<p>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;number of folds&gt;
+ *  Set number of folds for IREP
+ *  One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -S &lt;number of shuffles&gt;
+ *  Set number of shuffles to randomize
+ *  the data in order to get better rule.
+ *  (default 10)</pre>
+ * 
+ * <pre> -A
+ *  Set flag of whether use the error rate 
+ *  of all the data to select the default class
+ *  in each step. If not set, the learner will only use the error rate in the pruning data</pre>
+ * 
+ * <pre> -M
+ *   Set flag of whether use the majority class as
+ *  the default class in each step instead of 
+ *  choosing default class based on the error rate
+ *  (if the flag is not set)</pre>
+ * 
+ * <pre> -N &lt;min. weights&gt;
+ *  Set the minimal weights of instances
+ *  within a split.
+ *  (default 2.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin XU (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class Ridor 
+  extends AbstractClassifier
+  implements AdditionalMeasureProducer, WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -7261533075088314436L;
+  
+  /** The number of folds to split data into Grow and Prune for IREP */
+  private int m_Folds = 3;
+    
+  /** The number of shuffles performed on the data for randomization */
+  private int m_Shuffle = 1;
+
+  /** Random object for randomization */
+  private Random m_Random = null;
+    
+  /** The seed to perform randomization */
+  private int m_Seed = 1;
+
+  /** Whether use error rate on all the data */
+  private boolean m_IsAllErr = false;
+
+  /** Whether use majority class as default class */
+  private boolean m_IsMajority = false;
+    
+  /** The root of Ridor */
+  private Ridor_node m_Root = null;
+    
+  /** The class attribute of the data */
+  private Attribute m_Class;
+
+  /** Statistics of the data */
+  private double m_Cover, m_Err;
+
+  /** The minimal number of instance weights within a split*/
+  private double m_MinNo = 2.0;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An implementation of a RIpple-DOwn Rule learner.\n\n" 
+      + "It generates a default rule first and then the exceptions for the default rule "
+      + "with the least (weighted) error rate.  Then it generates the \"best\" exceptions for "
+      + "each exception and iterates until pure.  Thus it performs a tree-like expansion of "
+      + "exceptions."
+      + "The exceptions are a set of rules that predict classes other than the default. "
+      + "IREP is used to generate the exceptions.\n\n"
+      + "For more information about Ripple-Down Rules, see:\n\n";
+  }
+    
+  /** 
+   * Private class implementing the single node of Ridor. 
+   * It consists of a default class label, a set of exceptions to the default rule
+   * and the exceptions to each exception
+   */
+  private class Ridor_node 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -581370560157467677L;
+	
+    /** The default class label */
+    private double defClass = Double.NaN;
+	
+    /** The set of exceptions of the default rule. 
+	Each element also has its own exceptions and the consequent of each rule 
+	is determined by its exceptions */
+    private RidorRule[] rules = null;
+	
+    /** The exceptions of the exception rules */
+    private Ridor_node[] excepts = null; 
+
+    /** The level of this node */
+    private int level;
+
+    /**
+     * Gets the default class label
+     *
+     * @return the default class label
+     */
+    public double getDefClass() { 
+      return defClass; 
+    }
+    
+    /**
+     * Gets the set of exceptions
+     * 
+     * @return the set of exceptions
+     */
+    public RidorRule[] getRules() { 
+      return rules; 
+    }
+    
+    /**
+     * Gets the exceptions of the exceptions rules
+     * 
+     * @return the exceptions of the exceptions rules
+     */
+    public Ridor_node[] getExcepts() { 
+      return excepts; 
+    }
+
+    /**
+     * Builds a ripple-down manner rule learner.
+     *
+     * @param dataByClass the divided data by their class label. The real class
+     * labels of the instances are all set to 0
+     * @param lvl the level of the parent node
+     * @throws Exception if ruleset of this node cannot be built
+     */
+    public void findRules(Instances[] dataByClass, int lvl) throws Exception {
+      Vector finalRules = null;
+      int clas = -1;
+      double[] isPure = new double[dataByClass.length];
+      int numMajority = 0;
+	    
+      level = lvl + 1;
+	    
+      for(int h=0; h < dataByClass.length; h++){
+	isPure[h] = dataByClass[h].sumOfWeights();
+	if(Utils.grOrEq(isPure[h], m_Folds))
+	  numMajority++;  // Count how many class labels have enough instances
+      }
+	    
+      if(numMajority <= 1){	                   // The data is pure or not enough
+	defClass = (double)Utils.maxIndex(isPure);
+	return;
+      }
+      double total = Utils.sum(isPure);	 
+	    
+      if(m_IsMajority){
+	defClass = (double)Utils.maxIndex(isPure);
+	Instances data = new Instances(dataByClass[(int)defClass]);
+	int index = data.classIndex();
+		
+	for(int j=0; j<data.numInstances(); j++)
+	  data.instance(j).setClassValue(1);       // Set one class as default
+		
+	for(int k=0; k < dataByClass.length; k++)    // Merge into one dataset
+	  if(k != (int)defClass){
+	    if(data.numInstances() >= dataByClass[k].numInstances())
+	      data = append(data, dataByClass[k]);
+	    else data = append(dataByClass[k], data);
+	  }
+		
+	data.setClassIndex(index);           // Position new class label
+		
+	double classCount = total - isPure[(int)defClass];
+	finalRules = new Vector();
+	buildRuleset(data, classCount, finalRules);
+	if(finalRules.size() == 0)           // No good rules built
+	  return;
+      }
+      else{
+	double maxAcRt = isPure[Utils.maxIndex(isPure)] / total;
+		
+	// Find default class
+	for(int i=0; i < dataByClass.length; i++){
+	  if(isPure[i] >= m_Folds){
+	    Instances data = new Instances(dataByClass[i]);
+	    int index = data.classIndex();
+			
+	    for(int j=0; j<data.numInstances(); j++)
+	      data.instance(j).setClassValue(1);       // Set one class as default
+			
+	    for(int k=0; k < dataByClass.length; k++)    // Merge into one dataset
+	      if(k != i){
+		if(data.numInstances() >= dataByClass[k].numInstances())
+		  data = append(data, dataByClass[k]);
+		else data = append(dataByClass[k], data);
+	      }
+			
+	    data.setClassIndex(index);           // Position new class label 
+			
+	    /* Build a set of rules */
+	    double classCount = data.sumOfWeights() - isPure[i];
+	    Vector ruleset = new Vector();
+	    double wAcRt = buildRuleset(data, classCount, ruleset); 
+			
+	    if(Utils.gr(wAcRt, maxAcRt)){
+	      finalRules = ruleset;
+	      maxAcRt = wAcRt;
+	      clas = i;
+	    }
+	  }
+	}
+		
+	if(finalRules == null){ // No good rules found, set majority class as default
+	  defClass = (double)Utils.maxIndex(isPure);
+	  return;
+	}
+		
+	defClass = (double)clas;
+      }
+			
+      /* Store the exception rules and default class in this node */
+      int size = finalRules.size();
+      rules = new RidorRule[size];
+      excepts = new Ridor_node[size];
+      for(int l=0; l < size; l++)
+	rules[l] = (RidorRule)finalRules.elementAt(l);
+	    
+      /* Build exceptions for each exception rule */
+      Instances[] uncovered = dataByClass; 
+      if(level == 1)  // The error of default rule
+	m_Err = total - uncovered[(int)defClass].sumOfWeights();			
+
+      uncovered[(int)defClass] = new Instances(uncovered[(int)defClass], 0);    
+	    
+      for(int m=0; m < size; m++){
+	/* The data covered by this rule, they are also deducted from the original data */
+	Instances[][] dvdData = divide(rules[m], uncovered);
+	Instances[] covered = dvdData[0];    // Data covered by the rule
+	//uncovered = dvdData[1];            // Data not covered by the rule
+	excepts[m] = new Ridor_node();
+	excepts[m].findRules(covered, level);// Find exceptions on the covered data
+      }
+    }
+
+    /**	
+     * Private function to build a rule set and return the weighted avg of accuracy
+     * rate of rules in the set.
+     *
+     * @param insts the data used to build ruleset
+     * @param classCount the counts of the instances with the predicted class but not
+     *                   yet covered by the ruleset
+     * @param ruleset the ruleset to be built
+     * @return the weighted accuracy rate of the ruleset
+     * @throws Exception if the rules cannot be built properly
+     */
+    private double buildRuleset(Instances insts, double classCount, Vector ruleset) 
+      throws Exception {	    
+      Instances data = new Instances(insts);
+      double wAcRt = 0;  // The weighted accuracy rate of this ruleset
+      double total = data.sumOfWeights();
+	    
+      while( classCount >= m_Folds ){      // Data is not pure
+	RidorRule bestRule = null;
+	double bestWorthRate= -1;        // The best worth achieved by
+	double bestWorth = -1;           // randomization of the data
+		
+	RidorRule rule = new RidorRule();                                
+	rule.setPredictedClass(0);       // Predict the classes other than default
+		
+	for(int j = 0; j < m_Shuffle; j++){
+	  if(m_Shuffle > 1)
+	    data.randomize(m_Random);
+		    
+	  rule.buildClassifier(data);
+		    
+	  double wr, w; // Worth rate and worth
+	  if(m_IsAllErr){
+	    wr = (rule.getWorth()+rule.getAccuG()) / 
+	      (rule.getCoverP()+rule.getCoverG());
+	    w = rule.getWorth() + rule.getAccuG();
+	  }
+	  else{
+	    wr = rule.getWorthRate();
+	    w = rule.getWorth(); 
+	  }
+		    
+	  if(Utils.gr(wr, bestWorthRate) ||
+	     (Utils.eq(wr, bestWorthRate) && Utils.gr(w, bestWorth))){
+	    bestRule = rule;
+	    bestWorthRate = wr;
+	    bestWorth = w;
+	  }
+	}
+		
+	if (bestRule == null)
+	  throw new Exception("Something wrong here inside findRule()!");
+		
+	if(Utils.sm(bestWorthRate, 0.5) || (!bestRule.hasAntds()))
+	  break;                       // No more good rules generated
+		
+	Instances newData = new Instances(data); 
+	data = new Instances(newData, 0);// Empty the data
+	classCount = 0;
+	double cover = 0;                // Coverage of this rule on whole data
+		
+	for(int l=0; l<newData.numInstances(); l++){
+	  Instance datum = newData.instance(l);
+	  if(!bestRule.isCover(datum)){// Data not covered by the previous rule
+	    data.add(datum);
+	    if(Utils.eq(datum.classValue(), 0)) 
+	      classCount += datum.weight(); // The predicted class in the data
+	  }
+	  else cover += datum.weight();
+	}			
+		
+	wAcRt += computeWeightedAcRt(bestWorthRate, cover, total);
+	ruleset.addElement(bestRule);			
+      }  
+	    
+      /* The weighted def. accuracy */
+      double wDefAcRt = (data.sumOfWeights()-classCount) / total;		    
+      wAcRt += wDefAcRt;
+	    
+      return wAcRt;
+    }
+	
+    /**
+     * Private function to combine two data
+     *
+     * @param data1 the data to which data2 is appended 
+     * @param data2 the data to be appended to data1
+     * @return the merged data
+     */
+    private Instances append(Instances data1, Instances data2){
+      Instances data = new Instances(data1);
+      for(int i=0; i<data2.numInstances(); i++)
+	data.add(data2.instance(i));
+	    
+      return data;
+    }
+	
+    /**
+     * Compute the weighted average of accuracy rate of a certain rule
+     * Each rule is weighted by its coverage proportion in the whole data.  
+     * So the accuracy rate of one ruleset is actually 
+     * 
+     * (worth rate) * (coverage proportion)
+     *
+     *                               coverage of the rule on the whole data
+     * where coverage proportion = -----------------------------------------
+     *                              the whole data size fed into the ruleset
+     *
+     * @param worthRt the worth rate
+     * @param cover the coverage of the rule on the whole data
+     * @param total the total data size fed into the ruleset
+     * @return the weighted accuracy rate of this rule
+     */
+    private double computeWeightedAcRt(double worthRt, double cover, double total){
+	  
+      return (worthRt * (cover/total));	
+    }
+	
+    /**
+     * Builds an array of data according to their true class label
+     * Each bag of data is filtered through the rule specified and
+     * is totally covered by this rule.  
+     * Both the data covered and uncovered by the rule will be returned
+     * by the procedure.  
+     *
+     * @param rule the rule covering the data
+     * @param dataByClass the array of data to be covered by the rule
+     * @return the arrays of data both covered and not covered by the rule
+     */
+    private Instances[][] divide(RidorRule rule, Instances[] dataByClass){
+      int len = dataByClass.length;
+      Instances[][] dataBags = new Instances[2][len];
+	    
+      for(int i=0; i < len; i++){
+	Instances[] dvdData = rule.coveredByRule(dataByClass[i]);
+	dataBags[0][i] = dvdData[0];     // Covered by the rule
+	dataBags[1][i] = dvdData[1];     // Not covered by the rule
+      }
+	    
+      return dataBags;
+    }
+    /**
+     * The size of the certain node of Ridor, i.e. the 
+     * number of rules generated within and below this node
+     *
+     * @return the size of this node
+     */
+    public int size(){
+      int size = 0;
+      if(rules != null){
+	for(int i=0; i < rules.length; i++)
+	  size += excepts[i].size(); // The children's size
+	size += rules.length;          // This node's size
+      }
+      return size;
+    }
+	
+    /**
+     * Prints the all the rules of one node of Ridor.
+     *
+     * @return a textual description of one node of Ridor
+     */
+    public String toString(){
+      StringBuffer text =  new StringBuffer();
+	    
+      if(level == 1)
+	text.append(m_Class.name() + " = " + m_Class.value((int)getDefClass())+
+		    "  ("+m_Cover+"/"+m_Err+")\n");
+      if(rules != null){
+	for(int i=0; i < rules.length; i++){
+	  for(int j=0; j < level; j++)
+	    text.append("         ");
+	  String cl = m_Class.value((int)(excepts[i].getDefClass()));
+	  text.append("  Except " + 
+		      rules[i].toString(m_Class.name(), cl)+
+		      "\n" + excepts[i].toString());
+	}
+      }
+	    
+      return text.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }    
+
+  /**
+   * This class implements a single rule that predicts the 2-class distribution.  
+   *
+   * A rule consists of antecedents "AND"ed together and the consequent (class value) 
+   * for the classification.  In this case, the consequent is the distribution of
+   * the available classes (always 2 classes) in the dataset.  
+   * In this class, the Information Gain (p*[log(p/t) - log(P/T)]) is used to select 
+   * an antecedent and Reduced Error Prunning (REP) is used to prune the rule. 
+   *
+   */
+  private class RidorRule 
+    implements WeightedInstancesHandler, Serializable, RevisionHandler {
+	
+    /** for serialization */
+    static final long serialVersionUID = 4375199423973848157L;
+    
+    /** The internal representation of the class label to be predicted*/
+    private double m_Class = -1;	
+	
+    /** The class attribute of the data*/
+    private Attribute m_ClassAttribute;
+	
+    /** The vector of antecedents of this rule*/
+    protected FastVector m_Antds = null;
+	
+    /** The worth rate of this rule, in this case, accuracy rate in the pruning data*/
+    private double m_WorthRate = 0;
+	
+    /** The worth value of this rule, in this case, accurate # in pruning data*/
+    private double m_Worth = 0;
+	
+    /** The sum of weights of the data covered by this rule in the pruning data */
+    private double m_CoverP = 0;   
+	
+    /** The accurate and covered data of this rule in the growing data */
+    private double m_CoverG = 0, m_AccuG = 0;   	
+  
+    /** The access functions for parameters */
+    public void setPredictedClass(double cl){  m_Class = cl; }
+    public double getPredictedClass(){ return m_Class; }
+	
+    /**
+     * Builds a single rule learner with REP dealing with 2 classes.
+     * This rule learner always tries to predict the class with label 
+     * m_Class.
+     *
+     * @param instances the training data
+     * @throws Exception if classifier can't be built successfully
+     */
+    public void buildClassifier(Instances instances) throws Exception {
+      m_ClassAttribute = instances.classAttribute();
+      if (!m_ClassAttribute.isNominal()) 
+	throw new UnsupportedClassTypeException(" Only nominal class, please.");
+      if(instances.numClasses() != 2)
+	throw new Exception(" Only 2 classes, please.");
+	    
+      Instances data = new Instances(instances);
+      if(Utils.eq(data.sumOfWeights(),0))
+	throw new Exception(" No training data.");
+	    
+      data.deleteWithMissingClass();
+      if(Utils.eq(data.sumOfWeights(),0))
+	throw new Exception(" The class labels of all the training data are missing.");	
+	    
+      if(data.numInstances() < m_Folds)
+	throw new Exception(" Not enough data for REP.");
+	    
+      m_Antds = new FastVector();	
+	    
+      /* Split data into Grow and Prune*/
+      m_Random = new Random(m_Seed);
+      data.randomize(m_Random);
+      data.stratify(m_Folds);
+      Instances growData=data.trainCV(m_Folds, m_Folds-1, m_Random);
+      Instances pruneData=data.testCV(m_Folds, m_Folds-1);
+	    
+      grow(growData);      // Build this rule
+	    
+      prune(pruneData);    // Prune this rule
+    }
+	
+    /**
+     * Find all the instances in the dataset covered by this rule.
+     * The instances not covered will also be deducted from the the original data
+     * and returned by this procedure.
+     * 
+     * @param insts the dataset to be covered by this rule.
+     * @return the instances covered and not covered by this rule
+     */
+    public Instances[] coveredByRule(Instances insts){
+      Instances[] data = new Instances[2];
+      data[0] = new Instances(insts, insts.numInstances());
+      data[1] = new Instances(insts, insts.numInstances());
+	    
+      for(int i=0; i<insts.numInstances(); i++){
+	Instance datum = insts.instance(i);
+	if(isCover(datum))
+	  data[0].add(datum);        // Covered by this rule
+	else
+	  data[1].add(datum);        // Not covered by this rule
+      }
+	    
+      return data;
+    }
+	
+    /**
+     * Whether the instance covered by this rule
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered by this rule
+     */
+    public boolean isCover(Instance datum){
+      boolean isCover=true;
+	    
+      for(int i=0; i<m_Antds.size(); i++){
+	Antd antd = (Antd)m_Antds.elementAt(i);
+	if(!antd.isCover(datum)){
+	  isCover = false;
+	  break;
+	}
+      }
+	    
+      return isCover;
+    }        
+	
+    /**
+     * Whether this rule has antecedents, i.e. whether it is a default rule
+     * 
+     * @return the boolean value indicating whether the rule has antecedents
+     */
+    public boolean hasAntds(){
+      if (m_Antds == null)
+	return false;
+      else
+	return (m_Antds.size() > 0);
+    }      
+	
+    /**
+     * Build one rule using the growing data
+     *
+     * @param data the growing data used to build the rule
+     */    
+    private void grow(Instances data){
+      Instances growData = new Instances(data);
+	    
+      m_AccuG = computeDefAccu(growData);
+      m_CoverG = growData.sumOfWeights();
+      /* Compute the default accurate rate of the growing data */
+      double defAcRt= m_AccuG / m_CoverG; 
+	    
+      /* Keep the record of which attributes have already been used*/    
+      boolean[] used=new boolean [growData.numAttributes()];
+      for (int k=0; k<used.length; k++)
+	used[k]=false;
+      int numUnused=used.length;
+	    
+      double maxInfoGain;
+      boolean isContinue = true; // The stopping criterion of this rule
+	    
+      while (isContinue){   
+	maxInfoGain = 0;       // We require that infoGain be positive
+		
+	/* Build a list of antecedents */
+	Antd oneAntd=null;
+	Instances coverData = null;
+	Enumeration enumAttr=growData.enumerateAttributes();	    
+	int index=-1;  
+		
+	/* Build one condition based on all attributes not used yet*/
+	while (enumAttr.hasMoreElements()){
+	  Attribute att= (Attribute)(enumAttr.nextElement());
+	  index++;
+		    
+	  Antd antd =null;	
+	  if(att.isNumeric())
+	    antd = new NumericAntd(att);
+	  else
+	    antd = new NominalAntd(att);
+		    
+	  if(!used[index]){
+	    /* Compute the best information gain for each attribute,
+	       it's stored in the antecedent formed by this attribute.
+	       This procedure returns the data covered by the antecedent*/
+	    Instances coveredData = computeInfoGain(growData, defAcRt, antd);
+	    if(coveredData != null){
+	      double infoGain = antd.getMaxInfoGain();			
+	      if(Utils.gr(infoGain, maxInfoGain)){
+		oneAntd=antd;
+		coverData = coveredData;  
+		maxInfoGain = infoGain;
+	      }		    
+	    }
+	  }
+	}
+		
+	if(oneAntd == null)	 return;
+		
+	//Numeric attributes can be used more than once
+	if(!oneAntd.getAttr().isNumeric()){ 
+	  used[oneAntd.getAttr().index()]=true;
+	  numUnused--;
+	}
+		
+	m_Antds.addElement((Object)oneAntd);
+	growData = coverData;// Grow data size is shrinking 
+		
+	defAcRt = oneAntd.getAccuRate();
+		
+	/* Stop if no more data, rule perfect, no more attributes */
+	if(Utils.eq(growData.sumOfWeights(), 0.0) || Utils.eq(defAcRt, 1.0) || (numUnused == 0))
+	  isContinue = false;
+      }
+    }
+	
+    /** 
+     * Compute the best information gain for the specified antecedent
+     *  
+     * @param data the data based on which the infoGain is computed
+     * @param defAcRt the default accuracy rate of data
+     * @param antd the specific antecedent
+     * @return the data covered by the antecedent
+     */
+    private Instances computeInfoGain(Instances instances, double defAcRt, Antd antd){
+      Instances data = new Instances(instances);
+	    
+      /* Split the data into bags.
+	 The information gain of each bag is also calculated in this procedure */
+      Instances[] splitData = antd.splitData(data, defAcRt, m_Class); 
+	    
+      /* Get the bag of data to be used for next antecedents */
+      if(splitData != null)
+	return splitData[(int)antd.getAttrValue()];
+      else return null;
+    }
+	
+    /**
+     * Prune the rule using the pruning data and update the worth parameters for this rule
+     * The accuracy rate is used to prune the rule.
+     *
+     * @param pruneData the pruning data used to prune the rule
+     */    
+    private void prune(Instances pruneData){
+      Instances data=new Instances(pruneData);
+	    
+      double total = data.sumOfWeights();
+	    
+      /* The default accurate# and the the accuracy rate on pruning data */
+      double defAccu=0, defAccuRate=0;
+	    
+      int size=m_Antds.size();
+      if(size == 0) return; // Default rule before pruning
+	    
+      double[] worthRt = new double[size];
+      double[] coverage = new double[size];
+      double[] worthValue = new double[size];
+      for(int w=0; w<size; w++){
+	worthRt[w]=coverage[w]=worthValue[w]=0.0;
+      }
+	    
+      /* Calculate accuracy parameters for all the antecedents in this rule */
+      for(int x=0; x<size; x++){
+	Antd antd=(Antd)m_Antds.elementAt(x);
+	Attribute attr= antd.getAttr();
+	Instances newData = new Instances(data);
+	data = new Instances(newData, newData.numInstances()); // Make data empty
+		
+	for(int y=0; y<newData.numInstances(); y++){
+	  Instance ins=newData.instance(y);
+	  if(!ins.isMissing(attr)){              // Attribute not missing
+	    if(antd.isCover(ins)){             // Covered by this antecedent
+	      coverage[x] += ins.weight();
+	      data.add(ins);                 // Add to data for further pruning
+	      if(Utils.eq(ins.classValue(), m_Class)) // Accurate prediction
+		worthValue[x] += ins.weight();
+	    }
+	  }
+	}
+		
+	if(coverage[x] != 0)  
+	  worthRt[x] = worthValue[x]/coverage[x];
+      }
+	    
+      /* Prune the antecedents according to the accuracy parameters */
+      for(int z=(size-1); z > 0; z--)
+	if(Utils.sm(worthRt[z], worthRt[z-1]))
+	  m_Antds.removeElementAt(z);
+	else  break;
+	    
+      /* Check whether this rule is a default rule */
+      if(m_Antds.size() == 1){
+	defAccu = computeDefAccu(pruneData);
+	defAccuRate = defAccu/total;                // Compute def. accuracy
+	if(Utils.sm(worthRt[0], defAccuRate)){      // Becomes a default rule
+	  m_Antds.removeAllElements();
+	}
+      }   
+	    
+      /* Update the worth parameters of this rule*/
+      int antdsSize = m_Antds.size();
+      if(antdsSize != 0){                          // Not a default rule
+	m_Worth = worthValue[antdsSize-1];       // WorthValues of the last antecedent
+	m_WorthRate = worthRt[antdsSize-1];
+	m_CoverP = coverage[antdsSize-1];
+	Antd last = (Antd)m_Antds.lastElement();
+	m_CoverG = last.getCover();
+	m_AccuG = last.getAccu();
+      }
+      else{                                        // Default rule    
+	m_Worth = defAccu;                       // Default WorthValues
+	m_WorthRate = defAccuRate;
+	m_CoverP = total;
+      }
+    }
+	
+    /**
+     * Private function to compute default number of accurate instances
+     * in the specified data for m_Class
+     * 
+     * @param data the data in question
+     * @return the default accuracy number
+     */
+    private double computeDefAccu(Instances data){ 
+      double defAccu=0;
+      for(int i=0; i<data.numInstances(); i++){
+	Instance inst = data.instance(i);
+	if(Utils.eq(inst.classValue(), m_Class))
+	  defAccu += inst.weight();
+      }
+      return defAccu;
+    }
+	
+    /** The following are get functions after prune() has set the value of worthRate and worth*/
+    public double getWorthRate(){ return m_WorthRate; }
+    public double getWorth(){ return m_Worth; }
+    public double getCoverP(){ return m_CoverP; }
+    public double getCoverG(){ return m_CoverG; }
+    public double getAccuG(){ return m_AccuG; }
+
+    /**
+     * Prints this rule with the specified class label
+     *
+     * @param att the string standing for attribute in the consequent of this rule
+     * @param cl the string standing for value in the consequent of this rule
+     * @return a textual description of this rule with the specified class label
+     */
+    public String toString(String att, String cl) {
+      StringBuffer text =  new StringBuffer();
+      if(m_Antds.size() > 0){
+	for(int j=0; j< (m_Antds.size()-1); j++)
+	  text.append("(" + ((Antd)(m_Antds.elementAt(j))).toString()+ ") and ");
+	text.append("("+((Antd)(m_Antds.lastElement())).toString() + ")");
+      }
+      text.append(" => " + att + " = " + cl);
+      text.append("  ("+m_CoverG+"/"+(m_CoverG - m_AccuG)+") ["+
+		  m_CoverP+"/"+(m_CoverP - m_Worth)+"]");
+      return text.toString();
+    }
+	
+    /**
+     * Prints this rule
+     *
+     * @return a textual description of this rule
+     */
+    public String toString() {
+      return toString(m_ClassAttribute.name(), m_ClassAttribute.value((int)m_Class));
+    }        
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+    
+  /** 
+   * The single antecedent in the rule, which is composed of an attribute and 
+   * the corresponding value.  There are two inherited classes, namely NumericAntd
+   * and NominalAntd in which the attributes are numeric and nominal respectively.
+   */
+  private abstract class Antd 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 5317379013858933369L;
+    
+    /** The attribute of the antecedent */
+    protected Attribute att;
+	
+    /** The attribute value of the antecedent.  
+       For numeric attribute, value is either 0(1st bag) or 1(2nd bag) */
+    protected double value; 
+	
+    /** The maximum infoGain achieved by this antecedent test */
+    protected double maxInfoGain;
+	
+    /** The accurate rate of this antecedent test on the growing data */
+    protected double accuRate;
+	
+    /** The coverage of this antecedent */
+    protected double cover;
+	
+    /** The accurate data for this antecedent */
+    protected double accu;
+	
+    /** Constructor*/
+    public Antd(Attribute a){
+      att=a;
+      value=Double.NaN; 
+      maxInfoGain = 0;
+      accuRate = Double.NaN;
+      cover = Double.NaN;
+      accu = Double.NaN;
+    }
+	
+    /* The abstract members for inheritance */
+    public abstract Instances[] splitData(Instances data, double defAcRt, double cla);
+    public abstract boolean isCover(Instance inst);
+    public abstract String toString();
+	
+    /* Get functions of this antecedent */
+    public Attribute getAttr(){ return att; }
+    public double getAttrValue(){ return value; }
+    public double getMaxInfoGain(){ return maxInfoGain; }
+    public double getAccuRate(){ return accuRate; } 
+    public double getAccu(){ return accu; } 
+    public double getCover(){ return cover; } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+  /** 
+   * The antecedent with numeric attribute
+   */
+  private class NumericAntd 
+    extends Antd {
+    
+    /** for serialization */
+    static final long serialVersionUID = 1968761518014492214L;
+	
+    /** The split point for this numeric antecedent */
+    private double splitPoint;
+	
+    /** Constructor*/
+    public NumericAntd(Attribute a){ 
+      super(a);
+      splitPoint = Double.NaN;
+    }    
+	
+    /** Get split point of this numeric antecedent */
+    public double getSplitPoint(){ return splitPoint; }
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into two bags according 
+     * to the information gain of the numeric attribute value
+     * The maximum infoGain is also calculated.  
+     * 
+     * @param insts the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances insts, double defAcRt, double cl){
+      Instances data = new Instances(insts);
+      data.sort(att);
+      int total=data.numInstances();// Total number of instances without 
+      // missing value for att
+	    
+      int split=1;                  // Current split position
+      int prev=0;                   // Previous split position
+      int finalSplit=split;         // Final split position
+      maxInfoGain = 0;
+      value = 0;	
+
+      // Compute minimum number of Instances required in each split
+      double minSplit =  0.1 * (data.sumOfWeights()) / 2.0;
+      if (Utils.smOrEq(minSplit,m_MinNo)) 
+	minSplit = m_MinNo;
+      else if (Utils.gr(minSplit,25)) 
+	minSplit = 25;	    
+	    
+      double fstCover=0, sndCover=0, fstAccu=0, sndAccu=0;
+	    
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst = data.instance(x);
+	if(inst.isMissing(att)){
+	  total = x;
+	  break;
+	}
+		
+	sndCover += inst.weight();
+	if(Utils.eq(inst.classValue(), cl))
+	  sndAccu += inst.weight();
+      }
+	    
+      // Enough Instances with known values?
+      if (Utils.sm(sndCover,(2*minSplit)))
+	return null;
+	    
+      if(total == 0) return null; // Data all missing for the attribute 	
+      splitPoint = data.instance(total-1).value(att);	
+	    
+      for(; split < total; split++){
+	if(!Utils.eq(data.instance(split).value(att), 
+		     data.instance(prev).value(att))){ // Can't split within same value
+		    
+	  for(int y=prev; y<split; y++){
+	    Instance inst = data.instance(y);
+	    fstCover += inst.weight(); sndCover -= inst.weight(); 
+	    if(Utils.eq(data.instance(y).classValue(), cl)){
+	      fstAccu += inst.weight();  // First bag positive# ++
+	      sndAccu -= inst.weight();  // Second bag positive# --
+	    }	     		   
+	  }
+		    
+	  if(Utils.sm(fstCover, minSplit) || Utils.sm(sndCover, minSplit)){
+	    prev=split;  // Cannot split because either
+	    continue;    // split has not enough data
+	  }
+		    
+	  double fstAccuRate = 0, sndAccuRate = 0;
+	  if(!Utils.eq(fstCover,0))
+	    fstAccuRate = fstAccu/fstCover;		
+	  if(!Utils.eq(sndCover,0))
+	    sndAccuRate = sndAccu/sndCover;
+		    
+	  /* Which bag has higher information gain? */
+	  boolean isFirst; 
+	  double fstInfoGain, sndInfoGain;
+	  double accRate, infoGain, coverage, accurate;
+		    
+	  fstInfoGain = Utils.eq(fstAccuRate, 0) ? 
+	    0 : (fstAccu*(Utils.log2(fstAccuRate) - Utils.log2(defAcRt)));
+	  sndInfoGain = Utils.eq(sndAccuRate, 0) ? 
+	    0 : (sndAccu*(Utils.log2(sndAccuRate) - Utils.log2(defAcRt)));
+	  if(Utils.gr(fstInfoGain,sndInfoGain) || 
+	     (Utils.eq(fstInfoGain,sndInfoGain)&&(Utils.grOrEq(fstAccuRate,sndAccuRate)))){
+	    isFirst = true;
+	    infoGain = fstInfoGain;
+	    accRate = fstAccuRate;
+	    accurate = fstAccu;
+	    coverage = fstCover;
+	  }
+	  else{
+	    isFirst = false;
+	    infoGain = sndInfoGain;
+	    accRate = sndAccuRate;
+	    accurate = sndAccu;
+	    coverage = sndCover;
+	  }
+		    
+	  boolean isUpdate = Utils.gr(infoGain, maxInfoGain);
+		    
+	  /* Check whether so far the max infoGain */
+	  if(isUpdate){
+	    splitPoint = (data.instance(split).value(att) + 
+			  data.instance(prev).value(att))/2;
+	    value = ((isFirst) ? 0 : 1);
+	    accuRate = accRate;
+	    accu = accurate;
+	    cover = coverage;
+	    maxInfoGain = infoGain;
+	    finalSplit = split;
+	  }
+	  prev=split;
+	}
+      }
+	    
+      /* Split the data */
+      Instances[] splitData = new Instances[2];
+      splitData[0] = new Instances(data, 0, finalSplit);
+      splitData[1] = new Instances(data, finalSplit, total-finalSplit);
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered 
+     *         by this antecedent
+     */
+    public boolean isCover(Instance inst){
+      boolean isCover=false;
+      if(!inst.isMissing(att)){
+	if(Utils.eq(value, 0)){
+	  if(Utils.smOrEq(inst.value(att), splitPoint))
+	    isCover=true;
+	}
+	else if(Utils.gr(inst.value(att), splitPoint))
+	  isCover=true;
+      }
+      return isCover;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      String symbol = Utils.eq(value, 0.0) ? " <= " : " > ";
+      return (att.name() + symbol + Utils.doubleToString(splitPoint, 6));
+    }   
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+    
+    
+  /** 
+   * The antecedent with nominal attribute
+   */
+  private class NominalAntd 
+    extends Antd {
+    
+    /** for serialization */
+    static final long serialVersionUID = -256386137196078004L;
+	
+    /* The parameters of infoGain calculated for each attribute value */
+    private double[] accurate;
+    private double[] coverage;
+    private double[] infoGain;
+	
+    /** Constructor*/
+    public NominalAntd(Attribute a){ 
+      super(a);
+      int bag = att.numValues();
+      accurate = new double[bag];
+      coverage = new double[bag];
+      infoGain = new double[bag];
+    }   
+	
+    /**
+     * Implements the splitData function.  
+     * This procedure is to split the data into bags according 
+     * to the nominal attribute value
+     * The infoGain for each bag is also calculated.  
+     * 
+     * @param data the data to be split
+     * @param defAcRt the default accuracy rate for data
+     * @param cl the class label to be predicted
+     * @return the array of data after split
+     */
+    public Instances[] splitData(Instances data, double defAcRt, double cl){
+      int bag = att.numValues();
+      Instances[] splitData = new Instances[bag];
+	    
+      for(int x=0; x<bag; x++){
+	accurate[x] = coverage[x] = infoGain[x] = 0;
+	splitData[x] = new Instances(data, data.numInstances());
+      }
+	    
+      for(int x=0; x<data.numInstances(); x++){
+	Instance inst=data.instance(x);
+	if(!inst.isMissing(att)){
+	  int v = (int)inst.value(att);
+	  splitData[v].add(inst);
+	  coverage[v] += inst.weight();
+	  if(Utils.eq(inst.classValue(), cl))
+	    accurate[v] += inst.weight();
+	}
+      }
+	    
+      // Check if >=2 splits have more than the minimal data
+      int count=0; 
+      for(int x=0; x<bag; x++){
+	double t = coverage[x];
+	if(Utils.grOrEq(t, m_MinNo)){
+	  double p = accurate[x];		
+		    
+	  if(!Utils.eq(t, 0.0))
+	    infoGain[x] = p *((Utils.log2(p/t)) - (Utils.log2(defAcRt)));
+	  ++count;
+	}
+      }
+	        
+      if(count < 2) // Don't split
+	return null;
+	    
+      value = (double)Utils.maxIndex(infoGain);
+	    
+      cover = coverage[(int)value];
+      accu = accurate[(int)value];
+	    
+      if(!Utils.eq(cover,0))
+	accuRate = accu / cover;
+      else accuRate = 0;
+	    
+      maxInfoGain = infoGain [(int)value];
+	    
+      return splitData;
+    }
+	
+    /**
+     * Whether the instance is covered by this antecedent
+     * 
+     * @param inst the instance in question
+     * @return the boolean value indicating whether the instance is covered 
+     *         by this antecedent
+     */
+    public boolean isCover(Instance inst){
+      boolean isCover=false;
+      if(!inst.isMissing(att)){
+	if(Utils.eq(inst.value(att), value))
+	  isCover=true;	    
+      }
+      return isCover;
+    }
+	
+    /**
+     * Prints this antecedent
+     *
+     * @return a textual description of this antecedent
+     */
+    public String toString() {
+      return (att.name() + " = " +att.value((int)value));
+    } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds a ripple-down manner rule learner.
+   *
+   * @param instances the training data
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    Instances data = new Instances(instances);
+    data.deleteWithMissingClass();
+    
+    int numCl = data.numClasses();
+    m_Root = new Ridor_node();
+    m_Class = instances.classAttribute();     // The original class label
+	
+    int index = data.classIndex();
+    m_Cover = data.sumOfWeights();
+    
+    m_Random = new Random(m_Seed);
+	
+    /* Create a binary attribute */
+    FastVector binary_values = new FastVector(2);
+    binary_values.addElement("otherClasses");
+    binary_values.addElement("defClass");
+    Attribute attr = new Attribute ("newClass", binary_values);
+    data.insertAttributeAt(attr, index);	
+    data.setClassIndex(index);                 // The new class label
+
+    /* Partition the data into bags according to their original class values */
+    Instances[] dataByClass = new Instances[numCl];
+    for(int i=0; i < numCl; i++)
+      dataByClass[i] = new Instances(data, data.numInstances()); // Empty bags
+    for(int i=0; i < data.numInstances(); i++){ // Partitioning
+      Instance inst = data.instance(i);
+      inst.setClassValue(0);           // Set new class vaue to be 0
+      dataByClass[(int)inst.value(index+1)].add(inst); 
+    }	
+	
+    for(int i=0; i < numCl; i++)    
+      dataByClass[i].deleteAttributeAt(index+1);   // Delete original class
+	
+    m_Root.findRules(dataByClass, 0);
+    
+  }
+    
+  /**
+   * Classify the test instance with the rule learner 
+   *
+   * @param datum the instance to be classified
+   * @return the classification
+   */
+  public double classifyInstance(Instance datum){
+    return classify(m_Root, datum);
+  }
+    
+  /**
+   * Classify the test instance with one node of Ridor 
+   *
+   * @param node the node of Ridor to classify the test instance
+   * @param datum the instance to be classified
+   * @return the classification
+   */
+  private double classify(Ridor_node node, Instance datum){
+    double classValue = node.getDefClass();
+    RidorRule[] rules = node.getRules();
+
+    if(rules != null){
+      Ridor_node[] excepts = node.getExcepts();	
+      for(int i=0; i < excepts.length; i++){
+	if(rules[i].isCover(datum)){
+	  classValue = classify(excepts[i], datum);
+	  break;
+	}
+      }
+    }
+	
+    return classValue;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * Valid options are: <p>
+   *
+   * -F number <br>
+   * Set number of folds for reduced error pruning. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   *
+   * -S number <br>
+   * Set number of shuffles for randomization. (Default: 10) <p>
+   * 
+   * -A <br>
+   * Set flag of whether use the error rate of all the data to select
+   * the default class in each step. If not set, the learner will only use
+   * the error rate in the pruning data <p>
+   *
+   * -M <br>
+   * Set flag of whether use the majority class as the default class
+   * in each step instead of choosing default class based on the error rate
+   * (if the flag is not set) <p>  
+   * 
+   * -N number <br>
+   * Set the minimal weights of instances within a split.
+   * (Default: 2) <p>
+   *    
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(5);
+	
+    newVector.addElement(new Option("\tSet number of folds for IREP\n" +
+				    "\tOne fold is used as pruning set.\n" +
+				    "\t(default 3)","F", 1, "-F <number of folds>"));
+    newVector.addElement(new Option("\tSet number of shuffles to randomize\n" +
+				    "\tthe data in order to get better rule.\n" +
+				    "\t(default 1)","S", 1, "-S <number of shuffles>"));
+    newVector.addElement(new Option("\tSet flag of whether use the error rate \n"+
+				    "\tof all the data to select the default class\n"+
+				    "\tin each step. If not set, the learner will only use"+
+				    "\tthe error rate in the pruning data","A", 0, "-A"));
+    newVector.addElement(new Option("\t Set flag of whether use the majority class as\n"+
+				    "\tthe default class in each step instead of \n"+
+				    "\tchoosing default class based on the error rate\n"+
+				    "\t(if the flag is not set)","M", 0, "-M"));
+    newVector.addElement(new Option("\tSet the minimal weights of instances\n" +
+				    "\twithin a split.\n" +
+				    "\t(default 2.0)","N", 1, "-N <min. weights>"));		
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;number of folds&gt;
+   *  Set number of folds for IREP
+   *  One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -S &lt;number of shuffles&gt;
+   *  Set number of shuffles to randomize
+   *  the data in order to get better rule.
+   *  (default 10)</pre>
+   * 
+   * <pre> -A
+   *  Set flag of whether use the error rate 
+   *  of all the data to select the default class
+   *  in each step. If not set, the learner will only use the error rate in the pruning data</pre>
+   * 
+   * <pre> -M
+   *   Set flag of whether use the majority class as
+   *  the default class in each step instead of 
+   *  choosing default class based on the error rate
+   *  (if the flag is not set)</pre>
+   * 
+   * <pre> -N &lt;min. weights&gt;
+   *  Set the minimal weights of instances
+   *  within a split.
+   *  (default 2.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+	
+    String numFoldsString = Utils.getOption('F', options);
+    if (numFoldsString.length() != 0) 
+      m_Folds = Integer.parseInt(numFoldsString);
+    else 
+      m_Folds = 3;
+	
+    String numShuffleString = Utils.getOption('S', options);
+    if (numShuffleString.length() != 0) 
+      m_Shuffle = Integer.parseInt(numShuffleString);
+    else 
+      m_Shuffle = 1;
+
+    String seedString = Utils.getOption('s', options);
+    if (seedString.length() != 0) 
+      m_Seed = Integer.parseInt(seedString);
+    else 
+      m_Seed = 1;
+	
+    String minNoString = Utils.getOption('N', options);
+    if (minNoString.length() != 0) 
+      m_MinNo = Double.parseDouble(minNoString);
+    else 
+      m_MinNo = 2.0;
+	
+    m_IsAllErr = Utils.getFlag('A', options);
+    m_IsMajority = Utils.getFlag('M', options);
+  }
+    
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+	
+    String [] options = new String [8];
+    int current = 0;
+    options[current++] = "-F"; options[current++] = "" + m_Folds;
+    options[current++] = "-S"; options[current++] = "" + m_Shuffle;
+    options[current++] = "-N"; options[current++] = "" + m_MinNo;
+	
+    if(m_IsAllErr)
+      options[current++] = "-A";
+    if(m_IsMajority)
+      options[current++] = "-M";	
+    while (current < options.length) 
+      options[current++] = "";
+    return options;
+  }
+    
+  /** Set and get members for parameters */
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldsTipText() {
+    return "Determines the amount of data used for pruning. One fold is used for "
+      + "pruning, the rest for growing the rules.";
+  }
+
+  public void setFolds(int fold){ m_Folds = fold; }
+  public int getFolds(){ return m_Folds; }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String shuffleTipText() {
+    return "Determines how often the data is shuffled before a rule "
+      + "is chosen. If > 1, a rule is learned multiple times and the "
+      + "most accurate rule is chosen.";
+  }
+
+  public void setShuffle(int sh){ m_Shuffle = sh; }
+  public int getShuffle(){ return m_Shuffle; }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data.";
+  }
+
+  public void setSeed(int s){ m_Seed = s; }
+  public int getSeed(){ return m_Seed; }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String wholeDataErrTipText() {
+    return "Whether worth of rule is computed based on all the data "
+      + "or just based on data covered by rule.";
+  }
+
+  public void setWholeDataErr(boolean a){ m_IsAllErr = a; }
+  public boolean getWholeDataErr(){ return m_IsAllErr; }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String majorityClassTipText() {
+    return "Whether the majority class is used as default.";
+  }
+  public void setMajorityClass(boolean m){ m_IsMajority = m; }
+  public boolean getMajorityClass(){ return m_IsMajority; }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNoTipText() {
+    return "The minimum total weight of the instances in a rule.";
+  }
+
+  public void setMinNo(double m){  m_MinNo = m; }
+  public double getMinNo(){ return m_MinNo; }
+    
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+    
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) 
+      return numRules();
+    else 
+      throw new IllegalArgumentException(additionalMeasureName+" not supported (Ripple down rule learner)");
+  }  
+    
+  /**
+   * Measure the number of rules in total in the model
+   *
+   * @return the number of rules
+   */  
+  private double numRules(){
+    int size = 0;
+    if(m_Root != null)
+      size = m_Root.size();
+	
+    return (double)(size+1); // Add the default rule
+  }
+   
+  /**
+   * Prints the all the rules of the rule learner.
+   *
+   * @return a textual description of the classifier
+   */
+  public String toString() {
+    if (m_Root == null) 
+      return "RIpple DOwn Rule Learner(Ridor): No model built yet.";
+	
+    return ("RIpple DOwn Rule Learner(Ridor) rules\n"+
+	    "--------------------------------------\n\n" + 
+	    m_Root.toString() +
+	    "\nTotal number of rules (incl. the default rule): " + (int)numRules());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+    
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {	
+    runClassifier(new Ridor(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/Rule.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/Rule.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/Rule.java	(revision 29)
@@ -0,0 +1,89 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Rule.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.core.Copyable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.WeightedInstancesHandler;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class of generic rule
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public abstract class Rule 
+    implements WeightedInstancesHandler, Copyable, Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 8815687740470471229L;
+    
+    /**
+     * Get a shallow copy of this rule
+     *
+     * @return the copy
+     */
+    public Object copy(){ return this;}
+    
+    /**
+     * Whether the instance covered by this rule
+     * 
+     * @param datum the instance in question
+     * @return the boolean value indicating whether the instance 
+     *         is covered by this rule
+     */
+    public abstract boolean covers(Instance datum);
+
+    /**
+     * Build this rule
+     *
+     * @param data the data used to build the rule
+     * @exception Exception if rule cannot be built
+     */    
+    public abstract void grow(Instances data) throws Exception;    
+
+    /**
+     * Whether this rule has antecedents, i.e. whether it is a default rule
+     * 
+     * @return the boolean value indicating whether the rule has antecedents
+     */
+    public abstract boolean hasAntds();   
+
+    /** 
+     * Get the consequent of this rule, i.e. the predicted class 
+     * 
+     * @return the consequent
+     */
+    public abstract double getConsequent(); 
+
+    /** 
+     * The size of the rule.  Could be number of antecedents in the case
+     * of conjunctive rule
+     *
+     * @return the size of the rule
+     */
+    public abstract double size(); 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/RuleStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/RuleStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/RuleStats.java	(revision 29)
@@ -0,0 +1,919 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RuleStats.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+
+/**
+ * This class implements the statistics functions used in the 
+ * propositional rule learner, from the simpler ones like count of
+ * true/false positive/negatives, filter data based on the ruleset, etc.
+ * to the more sophisticated ones such as MDL calculation and rule
+ * variants generation for each rule in the ruleset. <p>
+ *
+ * Obviously the statistics functions listed above need the specific
+ * data and the specific ruleset, which are given in order to instantiate
+ * an object of this class. <p>
+ *  
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 4608 $
+ */
+public class RuleStats 
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5708153367675298624L;
+
+  /** The data on which the stats calculation is based */
+  private Instances m_Data;
+
+  /** The specific ruleset in question */
+  private FastVector m_Ruleset;
+
+  /** The simple stats of each rule */
+  private FastVector m_SimpleStats;
+
+  /** The set of instances filtered by the ruleset */
+  private FastVector m_Filtered;
+    
+  /** The total number of possible conditions that could
+   *  appear in a rule */
+  private double m_Total;
+ 
+  /** The redundancy factor in theory description length */	
+  private static double REDUNDANCY_FACTOR = 0.5;
+    
+  /** The theory weight in the MDL calculation */
+  private double MDL_THEORY_WEIGHT = 1.0;
+
+    /** The class distributions predicted by each rule */
+    private FastVector m_Distributions;
+
+  /** Default constructor */
+  public RuleStats(){
+    m_Data = null;
+    m_Ruleset = null;
+    m_SimpleStats = null;
+    m_Filtered = null;
+    m_Distributions = null;
+    m_Total = -1;
+  }
+
+    
+  /** 
+   * Constructor that provides ruleset and data 
+   *
+   * @param data the data
+   * @param rules the ruleset
+   */
+  public RuleStats(Instances data, FastVector rules){
+    this();
+    m_Data = data;
+    m_Ruleset = rules;	
+  }
+
+  /**
+   * Frees up memory after classifier has been built.
+   */
+  public void cleanUp() {
+    m_Data     = null;
+    m_Filtered = null;
+  }
+
+  /**
+   * Set the number of all conditions that could appear 
+   * in a rule in this RuleStats object, if the number set
+   * is smaller than 0 (typically -1), then it calcualtes
+   * based on the data store 
+   *
+   * @param total the set number
+   */
+  public void setNumAllConds(double total){
+    if(total < 0)
+      m_Total = numAllConditions(m_Data);    
+    else
+      m_Total = total;
+  }
+    
+  /** 
+   * Set the data of the stats, overwriting the old one if any 
+   *
+   * @param data the data to be set
+   */
+  public void setData(Instances data){
+    m_Data = data;
+  }
+    
+  /** 
+   * Get the data of the stats 
+   *
+   * @return the data 
+   */
+  public Instances getData(){
+    return m_Data;
+  }
+
+    
+  /** 
+   * Set the ruleset of the stats, overwriting the old one if any 
+   *      
+   * @param rules the set of rules to be set
+   */
+  public void setRuleset(FastVector rules){
+    m_Ruleset = rules;
+  }
+
+    
+  /** 
+   * Get the ruleset of the stats 
+   *      
+   * @return the set of rules 
+   */
+  public FastVector getRuleset(){
+    return m_Ruleset;
+  }
+
+  /** 
+   * Get the size of the ruleset in the stats 
+   *      
+   * @return the size of ruleset 
+   */
+  public int getRulesetSize(){
+    return m_Ruleset.size();
+  }
+  
+  /** 
+   * Get the simple stats of one rule, including 6 parameters:
+   * 0: coverage; 1:uncoverage; 2: true positive; 3: true negatives;
+   * 4: false positives; 5: false negatives
+   *
+   * @param index the index of the rule
+   * @return the stats
+   */
+  public double[] getSimpleStats(int index){
+    if((m_SimpleStats != null) && (index < m_SimpleStats.size()))
+      return (double[])m_SimpleStats.elementAt(index);
+	
+    return null;
+  }    
+
+
+  /**
+   * Get the data after filtering the given rule
+   *
+   * @param index the index of the rule
+   * @return the data covered and uncovered by the rule
+   */
+  public Instances[] getFiltered(int index){
+	
+    if((m_Filtered != null) && (index < m_Filtered.size()))
+      return (Instances[])m_Filtered.elementAt(index);
+	
+    return null;
+  }    
+
+    /**
+     * Get the class distribution predicted by the rule in
+     * given position
+     *
+     * @param index the position index of the rule
+     * @return the class distributions
+     */
+    public double[] getDistributions(int index){
+	
+	if((m_Distributions != null) && (index < m_Distributions.size()))
+	    return (double[])m_Distributions.elementAt(index);
+	
+	return null;
+    }    
+    
+  /**
+   * Set the weight of theory in MDL calcualtion
+   *
+   * @param weight the weight to be set
+   */
+  public void setMDLTheoryWeight(double weight){
+    MDL_THEORY_WEIGHT = weight;
+  } 
+
+  /**
+   * Compute the number of all possible conditions that could 
+   * appear in a rule of a given data.  For nominal attributes,
+   * it's the number of values that could appear; for numeric 
+   * attributes, it's the number of values * 2, i.e. <= and >=
+   * are counted as different possible conditions.
+   *
+   * @param data the given data
+   * @return number of all conditions of the data
+   */
+  public static double numAllConditions(Instances data){
+    double total = 0;
+    Enumeration attEnum = data.enumerateAttributes();	
+    while(attEnum.hasMoreElements()){
+      Attribute att= (Attribute)attEnum.nextElement();
+      if(att.isNominal())
+	total += (double)att.numValues();
+      else
+	total += 2.0 * (double)data.numDistinctValues(att);	
+    }
+    return total;
+  }
+
+
+  /**
+   * Filter the data according to the ruleset and compute the basic
+   * stats: coverage/uncoverage, true/false positive/negatives of 
+   * each rule
+   */
+  public void countData(){
+    if((m_Filtered != null) ||
+       (m_Ruleset == null)  ||
+       (m_Data == null))
+      return;
+	
+    int size = m_Ruleset.size();
+    m_Filtered = new FastVector(size);
+    m_SimpleStats = new FastVector(size);
+    m_Distributions = new FastVector(size);
+    Instances data = new Instances(m_Data);
+	
+    for(int i=0; i < size; i++){
+      double[] stats = new double[6];  // 6 statistics parameters
+      double[] classCounts = new double[m_Data.classAttribute().numValues()];
+      Instances[] filtered = computeSimpleStats(i, data, stats, classCounts);
+      m_Filtered.addElement(filtered);
+      m_SimpleStats.addElement(stats);
+      m_Distributions.addElement(classCounts);
+      data = filtered[1];  // Data not covered
+    }	
+  }
+    
+    /**
+     * Count data from the position index in the ruleset
+     * assuming that given data are not covered by the rules
+     * in position 0...(index-1), and the statistics of these
+     * rules are provided.<br>
+     * This procedure is typically useful when a temporary 
+     * object of RuleStats is constructed in order to efficiently
+     * calculate the relative DL of rule in position index, 
+     * thus all other stuff is not needed.
+     *
+     * @param index the given position
+     * @param uncovered the data not covered by rules before index
+     * @param prevRuleStats the provided stats of previous rules
+     */
+    public void countData(int index, Instances uncovered, 
+			  double[][] prevRuleStats){
+	if((m_Filtered != null) ||
+	   (m_Ruleset == null))
+	    return;
+	
+	int size = m_Ruleset.size();
+	m_Filtered = new FastVector(size);
+	m_SimpleStats = new FastVector(size);
+	Instances[] data = new Instances[2];
+	data[1] = uncovered;
+	
+	for(int i=0; i < index; i++){
+	    m_SimpleStats.addElement(prevRuleStats[i]);
+	    if(i+1 == index)
+		m_Filtered.addElement(data);
+	    else
+		m_Filtered.addElement(new Object()); // Stuff sth.
+	}
+	
+	for(int j=index; j < size; j++){
+	    double[] stats = new double[6];  // 6 statistics parameters
+	    Instances[] filtered = computeSimpleStats(j, data[1], stats, null);
+	    m_Filtered.addElement(filtered);
+	    m_SimpleStats.addElement(stats);
+	    data = filtered;  // Data not covered
+	}	
+    }
+    
+  /**
+   * Find all the instances in the dataset covered/not covered by 
+   * the rule in given index, and the correponding simple statistics
+   * and predicted class distributions are stored in the given double array,
+   * which can be obtained by getSimpleStats() and getDistributions().<br>
+   * 
+   * @param index the given index, assuming correct
+   * @param insts the dataset to be covered by the rule
+   * @param stats the given double array to hold stats, side-effected
+   * @param dist the given array to hold class distributions, side-effected
+   *             if null, the distribution is not necessary 
+   * @return the instances covered and not covered by the rule
+   */
+  private Instances[] computeSimpleStats(int index, Instances insts, 
+					 double[] stats, double[] dist){
+    Rule rule = (Rule)m_Ruleset.elementAt(index);
+	
+    Instances[] data = new Instances[2];
+    data[0] = new Instances(insts, insts.numInstances());
+    data[1] = new Instances(insts, insts.numInstances());
+
+    for(int i=0; i<insts.numInstances(); i++){
+      Instance datum = insts.instance(i);
+      double weight = datum.weight();
+      if(rule.covers(datum)){
+	data[0].add(datum);        // Covered by this rule
+	stats[0] += weight;        // Coverage
+	if((int)datum.classValue() == (int)rule.getConsequent())
+	  stats[2] += weight;    // True positives
+	else
+	  stats[4] += weight;    // False positives
+	if(dist != null)
+	    dist[(int)datum.classValue()] += weight;
+      }
+      else{
+	data[1].add(datum);        // Not covered by this rule
+	stats[1] += weight; 
+	if((int)datum.classValue() != (int)rule.getConsequent())
+	  stats[3] += weight;    // True negatives
+	else
+	  stats[5] += weight;    // False negatives	    
+      }
+    }
+    
+    return data;
+  }
+    
+    
+  /** 
+   * Add a rule to the ruleset and update the stats
+   *
+   * @param lastRule the rule to be added
+   */
+  public void addAndUpdate(Rule lastRule){
+    if(m_Ruleset == null)
+      m_Ruleset = new FastVector();
+    m_Ruleset.addElement(lastRule);
+  
+    Instances data = (m_Filtered == null) ?
+      m_Data : ((Instances[])m_Filtered.lastElement())[1];
+    double[] stats = new double[6];
+    double[] classCounts = new double[m_Data.classAttribute().numValues()];
+    Instances[] filtered = 
+	computeSimpleStats(m_Ruleset.size()-1, data, stats, classCounts);
+    
+    if(m_Filtered == null)
+	m_Filtered = new FastVector();	
+    m_Filtered.addElement(filtered);
+
+    if(m_SimpleStats == null)
+      m_SimpleStats = new FastVector();	
+    m_SimpleStats.addElement(stats);
+
+    if(m_Distributions == null)
+	m_Distributions = new FastVector();
+    m_Distributions.addElement(classCounts);
+  }
+    
+    
+  /**
+   * Subset description length: <br>
+   * S(t,k,p) = -k*log2(p)-(n-k)log2(1-p)
+   *
+   * Details see Quilan: "MDL and categorical theories (Continued)",ML95
+   *
+   * @param t the number of elements in a known set
+   * @param k the number of elements in a subset
+   * @param p the expected proportion of subset known by recipient
+   * @return the subset description length
+   */
+  public static double subsetDL(double t, double k, double p){
+    double rt = Utils.gr(p, 0.0) ? (- k*Utils.log2(p)) : 0.0;
+    rt -= (t-k)*Utils.log2(1-p);
+    return rt;
+  }
+    
+    
+  /** 
+   * The description length of the theory for a given rule.  Computed as:<br>
+   *                 0.5* [||k||+ S(t, k, k/t)]<br>
+   * where k is the number of antecedents of the rule; t is the total
+   * possible antecedents that could appear in a rule; ||K|| is the 
+   * universal prior for k , log2*(k) and S(t,k,p) = -k*log2(p)-(n-k)log2(1-p)
+   * is the subset encoding length.<p>
+   *
+   * Details see Quilan: "MDL and categorical theories (Continued)",ML95
+   *
+   * @param index the index of the given rule (assuming correct)
+   * @return the theory DL, weighted if weight != 1.0
+   */
+  public double theoryDL(int index){
+	
+    double k = ((Rule)m_Ruleset.elementAt(index)).size();
+	
+    if(k == 0)
+      return 0.0;
+	
+    double tdl = Utils.log2(k);               	    
+    if(k > 1)                           // Approximation
+      tdl += 2.0 * Utils.log2(tdl);   // of log2 star	
+    tdl += subsetDL(m_Total, k, k/m_Total);
+    //System.out.println("!!!theory: "+MDL_THEORY_WEIGHT * REDUNDANCY_FACTOR * tdl);
+    return MDL_THEORY_WEIGHT * REDUNDANCY_FACTOR * tdl;
+  }
+     
+
+  /** 
+   * The description length of data given the parameters of the data
+   * based on the ruleset. <p>
+   * Details see Quinlan: "MDL and categorical theories (Continued)",ML95<p>
+   *
+   * @param expFPOverErr expected FP/(FP+FN)
+   * @param cover coverage
+   * @param uncover uncoverage
+   * @param fp False Positive
+   * @param fn False Negative
+   * @return the description length
+   */
+  public static double dataDL(double expFPOverErr, double cover, 
+			      double uncover, double fp, double fn){
+    double totalBits = Utils.log2(cover+uncover+1.0); // how many data?
+    double coverBits, uncoverBits; // What's the error?
+    double expErr;                 // Expected FP or FN
+
+    if(Utils.gr(cover, uncover)){
+      expErr = expFPOverErr*(fp+fn);
+      coverBits = subsetDL(cover, fp, expErr/cover);
+      uncoverBits = Utils.gr(uncover, 0.0) ? 
+	subsetDL(uncover, fn, fn/uncover) : 0.0;	    
+    }
+    else{
+      expErr = (1.0-expFPOverErr)*(fp+fn);
+      coverBits = Utils.gr(cover, 0.0) ? 
+	subsetDL(cover, fp, fp/cover) : 0.0;
+      uncoverBits = subsetDL(uncover, fn, expErr/uncover);
+    }
+	
+    /*
+      System.err.println("!!!cover: " + cover + "|uncover" + uncover +
+      "|coverBits: "+coverBits+"|uncBits: "+ uncoverBits+
+      "|FPRate: "+expFPOverErr + "|expErr: "+expErr+
+      "|fp: "+fp+"|fn: "+fn+"|total: "+totalBits);
+    */
+    return (totalBits + coverBits + uncoverBits);
+  }
+
+    
+  /**
+   * Calculate the potential to decrease DL of the ruleset,
+   * i.e. the possible DL that could be decreased by deleting
+   * the rule whose index and simple statstics are given.  
+   * If there's no potentials (i.e. smOrEq 0 && error rate < 0.5),
+   * it returns NaN. <p>
+   *
+   * The way this procedure does is copied from original RIPPER
+   * implementation and is quite bizzare because it 
+   * does not update the following rules' stats recursively 
+   * any more when testing each rule, which means it assumes
+   * after deletion no data covered by the following rules (or
+   * regards the deleted rule as the last rule).  Reasonable 
+   * assumption?<p>
+   *
+   * @param index the index of the rule in m_Ruleset to be deleted
+   * @param expFPOverErr expected FP/(FP+FN)
+   * @param rulesetStat the simple statistics of the ruleset, updated
+   *                    if the rule should be deleted
+   * @param ruleStat the simple statistics of the rule to be deleted
+   * @param checkErr whether check if error rate >= 0.5
+   * @return the potential DL that could be decreased
+   */
+  public double potential(int index, double expFPOverErr, 
+			  double[] rulesetStat, double[] ruleStat,
+			  boolean checkErr){
+    //System.out.println("!!!inside potential: ");
+    // Restore the stats if deleted
+    double pcov = rulesetStat[0] - ruleStat[0];
+    double puncov = rulesetStat[1] + ruleStat[0];
+    double pfp = rulesetStat[4] - ruleStat[4];
+    double pfn = rulesetStat[5] + ruleStat[2];
+
+    double dataDLWith = dataDL(expFPOverErr, rulesetStat[0], 
+			       rulesetStat[1], rulesetStat[4], 
+			       rulesetStat[5]);
+    double theoryDLWith = theoryDL(index);
+    double dataDLWithout = dataDL(expFPOverErr, pcov, puncov, pfp, pfn);
+
+    double potential = dataDLWith + theoryDLWith - dataDLWithout;
+    double err = ruleStat[4] / ruleStat[0];
+    /*System.out.println("!!!"+dataDLWith +" | "+ 
+      theoryDLWith + " | " 
+      +dataDLWithout+"|"+ruleStat[4] + " / " + ruleStat[0]);
+    */
+    boolean overErr = Utils.grOrEq(err, 0.5);
+    if(!checkErr)
+      overErr = false;
+	
+    if(Utils.grOrEq(potential, 0.0) || overErr){ 
+      // If deleted, update ruleset stats.  Other stats do not matter
+      rulesetStat[0] = pcov;
+      rulesetStat[1] = puncov;
+      rulesetStat[4] = pfp;
+      rulesetStat[5] = pfn;
+      return potential;
+    }
+    else
+      return Double.NaN;
+  }
+    
+    
+  /**
+   * Compute the minimal data description length of the ruleset
+   * if the rule in the given position is deleted.<br>
+   * The min_data_DL_if_deleted = data_DL_if_deleted - potential
+   *
+   * @param index the index of the rule in question
+   * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
+   * @param checkErr whether check if error rate >= 0.5
+   * @return the minDataDL
+   */
+  public double minDataDLIfDeleted(int index, double expFPRate,
+				   boolean checkErr){
+    //System.out.println("!!!Enter without: ");
+    double[] rulesetStat = new double[6]; // Stats of ruleset if deleted
+    int more = m_Ruleset.size() - 1 - index; // How many rules after?
+    FastVector indexPlus = new FastVector(more); // Their stats
+	
+    // 0...(index-1) are OK	
+    for(int j=0; j<index; j++){
+      // Covered stats are cumulative
+      rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
+      rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
+      rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
+    }
+	
+    // Recount data from index+1
+    Instances data = (index == 0) ?  
+      m_Data : ((Instances[])m_Filtered.elementAt(index-1))[1];	
+    //System.out.println("!!!without: " + data.sumOfWeights());
+
+    for(int j=(index+1); j<m_Ruleset.size(); j++){
+      double[] stats = new double[6];
+      Instances[] split = computeSimpleStats(j, data, stats, null);
+      indexPlus.addElement(stats);
+      rulesetStat[0] += stats[0];
+      rulesetStat[2] += stats[2];
+      rulesetStat[4] += stats[4];	   
+      data = split[1];
+    }
+    // Uncovered stats are those of the last rule
+    if(more > 0){
+      rulesetStat[1] = ((double[])indexPlus.lastElement())[1];
+      rulesetStat[3] = ((double[])indexPlus.lastElement())[3];
+      rulesetStat[5] = ((double[])indexPlus.lastElement())[5];
+    }
+    else if(index > 0){
+      rulesetStat[1] = 
+	((double[])m_SimpleStats.elementAt(index-1))[1];
+      rulesetStat[3] =
+	((double[])m_SimpleStats.elementAt(index-1))[3];
+      rulesetStat[5] = 
+	((double[])m_SimpleStats.elementAt(index-1))[5];
+    }	
+    else{ // Null coverage
+      rulesetStat[1] = ((double[])m_SimpleStats.elementAt(0))[0] +
+	((double[])m_SimpleStats.elementAt(0))[1];
+      rulesetStat[3] = ((double[])m_SimpleStats.elementAt(0))[3] +
+	((double[])m_SimpleStats.elementAt(0))[4];
+      rulesetStat[5] = ((double[])m_SimpleStats.elementAt(0))[2] +
+	((double[])m_SimpleStats.elementAt(0))[5];	    
+    }
+	
+    // Potential 
+    double potential = 0;
+    for(int k=index+1; k<m_Ruleset.size(); k++){
+      double[] ruleStat = (double[])indexPlus.elementAt(k-index-1);
+      double ifDeleted = potential(k, expFPRate, rulesetStat, 
+				   ruleStat, checkErr);
+      if(!Double.isNaN(ifDeleted))
+	potential += ifDeleted;
+    }
+
+    // Data DL of the ruleset without the rule
+    // Note that ruleset stats has already been updated to reflect 
+    // deletion if any potential		
+    double dataDLWithout = dataDL(expFPRate, rulesetStat[0], 
+				  rulesetStat[1], rulesetStat[4], 
+				  rulesetStat[5]);
+    //System.out.println("!!!without: "+dataDLWithout + " |potential: "+
+    //		   potential);
+    // Why subtract potential again?  To reflect change of theory DL??
+    return (dataDLWithout - potential);
+  }    
+    
+    
+  /**
+   * Compute the minimal data description length of the ruleset
+   * if the rule in the given position is NOT deleted.<br>
+   * The min_data_DL_if_n_deleted = data_DL_if_n_deleted - potential
+   *
+   * @param index the index of the rule in question
+   * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
+   * @param checkErr whether check if error rate >= 0.5
+   * @return the minDataDL
+   */
+  public double minDataDLIfExists(int index, double expFPRate,
+				  boolean checkErr){
+    //	System.out.println("!!!Enter with: ");
+    double[] rulesetStat = new double[6]; // Stats of ruleset if rule exists
+    for(int j=0; j<m_SimpleStats.size(); j++){
+      // Covered stats are cumulative
+      rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
+      rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
+      rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
+      if(j == m_SimpleStats.size()-1){ // Last rule
+	rulesetStat[1] = ((double[])m_SimpleStats.elementAt(j))[1];
+	rulesetStat[3] = ((double[])m_SimpleStats.elementAt(j))[3];
+	rulesetStat[5] = ((double[])m_SimpleStats.elementAt(j))[5];
+      }	    
+    }
+	
+    // Potential 
+    double potential = 0;
+    for(int k=index+1; k<m_SimpleStats.size(); k++){
+      double[] ruleStat = (double[])getSimpleStats(k);
+      double ifDeleted = potential(k, expFPRate, rulesetStat, 
+				   ruleStat, checkErr);
+      if(!Double.isNaN(ifDeleted))
+	potential += ifDeleted;
+    }
+	
+    // Data DL of the ruleset without the rule
+    // Note that ruleset stats has already been updated to reflect deletion
+    // if any potential	
+    double dataDLWith = dataDL(expFPRate, rulesetStat[0], 
+			       rulesetStat[1], rulesetStat[4], 
+			       rulesetStat[5]);	
+    //System.out.println("!!!with: "+dataDLWith + " |potential: "+
+    //		   potential);
+    return (dataDLWith - potential);
+  }
+    
+    
+  /**
+   * The description length (DL) of the ruleset relative to if the
+   * rule in the given position is deleted, which is obtained by: <br>
+   * MDL if the rule exists - MDL if the rule does not exist <br>
+   * Note the minimal possible DL of the ruleset is calculated(i.e. some
+   * other rules may also be deleted) instead of the DL of the current
+   * ruleset.<p>
+   *
+   * @param index the given position of the rule in question 
+   *              (assuming correct)
+   * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
+   * @param checkErr whether check if error rate >= 0.5
+   * @return the relative DL
+   */
+  public double relativeDL(int index, double expFPRate, boolean checkErr){ 
+		 
+    return (minDataDLIfExists(index, expFPRate, checkErr) 
+	    + theoryDL(index) - 
+	    minDataDLIfDeleted(index, expFPRate, checkErr));
+  }  
+    
+    
+  /**
+   * Try to reduce the DL of the ruleset by testing removing the rules
+   * one by one in reverse order and update all the stats
+   * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
+   * @param checkErr whether check if error rate >= 0.5
+   */
+  public void reduceDL(double expFPRate, boolean checkErr){
+	
+    boolean needUpdate = false;
+    double[] rulesetStat = new double[6];
+    for(int j=0; j<m_SimpleStats.size(); j++){
+      // Covered stats are cumulative
+      rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
+      rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
+      rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
+      if(j == m_SimpleStats.size()-1){ // Last rule
+	rulesetStat[1] = ((double[])m_SimpleStats.elementAt(j))[1];
+	rulesetStat[3] = ((double[])m_SimpleStats.elementAt(j))[3];
+	rulesetStat[5] = ((double[])m_SimpleStats.elementAt(j))[5];
+      }	    
+    }
+	
+    // Potential 
+    for(int k=m_SimpleStats.size()-1; k>=0; k--){
+	    	
+      double[] ruleStat = (double[])m_SimpleStats.elementAt(k);
+
+      // rulesetStat updated
+      double ifDeleted = potential(k, expFPRate, rulesetStat, 
+				   ruleStat, checkErr);
+      if(!Double.isNaN(ifDeleted)){  
+	/*System.err.println("!!!deleted ("+k+"): save "+ifDeleted
+	  +" | "+rulesetStat[0]
+	  +" | "+rulesetStat[1]
+	  +" | "+rulesetStat[4]
+	  +" | "+rulesetStat[5]);
+	*/
+	
+	if(k == (m_SimpleStats.size()-1))
+	    removeLast();
+	else{
+	    m_Ruleset.removeElementAt(k);
+	    needUpdate = true;
+	}
+      }
+    }
+	
+    if(needUpdate){
+      m_Filtered = null;
+      m_SimpleStats = null;
+      countData();
+    }
+  }
+    
+  /**
+   * Remove the last rule in the ruleset as well as it's stats.
+   * It might be useful when the last rule was added for testing
+   * purpose and then the test failed
+   */
+  public void removeLast(){
+    int last = m_Ruleset.size()-1;
+    m_Ruleset.removeElementAt(last);
+    m_Filtered.removeElementAt(last);
+    m_SimpleStats.removeElementAt(last);	
+    if(m_Distributions != null)
+	m_Distributions.removeElementAt(last);
+  }
+
+  /**
+   * Static utility function to count the data covered by the 
+   * rules after the given index in the given rules, and then
+   * remove them.  It returns the data not covered by the
+   * successive rules.
+   *
+   * @param data the data to be processed
+   * @param rules the ruleset
+   * @param index the given index
+   * @return the data after processing
+   */
+  public static Instances rmCoveredBySuccessives(Instances data, FastVector rules, int index){
+    Instances rt = new Instances(data, 0);
+
+    for(int i=0; i < data.numInstances(); i++){
+      Instance datum = data.instance(i);
+      boolean covered = false;	    
+	    
+      for(int j=index+1; j<rules.size();j++){
+	Rule rule = (Rule)rules.elementAt(j);
+	if(rule.covers(datum)){
+	  covered = true;
+	  break;
+	}
+      }
+
+      if(!covered)
+	rt.add(datum);
+    }	
+    return rt;
+  } 
+    
+  /** 
+   * Stratify the given data into the given number of bags based on the class
+   * values.  It differs from the <code>Instances.stratify(int fold)</code>
+   * that before stratification it sorts the instances according to the 
+   * class order in the header file.  It assumes no missing values in the class.
+   * 
+   * @param data the given data
+   * @param folds the given number of folds
+   * @param rand the random object used to randomize the instances
+   * @return the stratified instances
+   */
+  public static final Instances stratify(Instances data, int folds, Random rand){
+    if(!data.classAttribute().isNominal())
+      return data;
+	
+    Instances result = new Instances(data, 0);
+    Instances[] bagsByClasses = new Instances[data.numClasses()];
+	
+    for(int i=0; i < bagsByClasses.length; i++)
+      bagsByClasses[i] = new Instances(data, 0);
+	
+    // Sort by class	
+    for(int j=0; j < data.numInstances(); j++){
+      Instance datum = data.instance(j);
+      bagsByClasses[(int)datum.classValue()].add(datum);
+    }
+	
+    // Randomize each class
+    for(int j=0; j < bagsByClasses.length; j++)
+      bagsByClasses[j].randomize(rand);
+	
+    for(int k=0; k < folds; k++){
+      int offset = k, bag = 0;
+    oneFold:
+      while (true){	
+	while(offset >= bagsByClasses[bag].numInstances()){
+	  offset -= bagsByClasses[bag].numInstances();
+	  if (++bag >= bagsByClasses.length)// Next bag
+	    break oneFold;      	   
+	}	
+	    
+	result.add(bagsByClasses[bag].instance(offset));
+	offset += folds;				
+      }
+    }
+	
+    return result;
+  }
+
+  /** 
+   * Compute the combined DL of the ruleset in this class, i.e. theory 
+   * DL and data DL.  Note this procedure computes the combined DL
+   * according to the current status of the ruleset in this class
+   * 
+   * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
+   * @param predicted the default classification if ruleset covers null
+   * @return the combined class
+   */
+  public double combinedDL(double expFPRate, double predicted){
+    double rt = 0;
+    
+    if(getRulesetSize() > 0) {
+      double[] stats = (double[])m_SimpleStats.lastElement();
+      for(int j=getRulesetSize()-2; j >= 0; j--){
+	stats[0] += getSimpleStats(j)[0];
+	stats[2] += getSimpleStats(j)[2];
+	stats[4] += getSimpleStats(j)[4];
+      }
+      rt += dataDL(expFPRate, stats[0], stats[1], 
+		   stats[4], stats[5]);     // Data DL      
+    }
+    else{ // Null coverage ruleset
+      double fn = 0.0;
+      for(int j=0; j < m_Data.numInstances(); j++)
+	if((int)m_Data.instance(j).classValue() == (int)predicted)
+	  fn += m_Data.instance(j).weight();
+      rt += dataDL(expFPRate, 0.0, m_Data.sumOfWeights(), 0.0, fn);	
+    }     
+    
+    for(int i=0; i<getRulesetSize(); i++) // Theory DL
+      rt += theoryDL(i);     
+    
+    return rt;
+  }
+  
+  /** 
+   * Patition the data into 2, first of which has (numFolds-1)/numFolds of
+   * the data and the second has 1/numFolds of the data
+   *
+   * 
+   * @param data the given data
+   * @param numFolds the given number of folds
+   * @return the patitioned instances
+   */
+  public static final Instances[] partition(Instances data, int numFolds){
+    Instances[] rt = new Instances[2];
+    int splits = data.numInstances() * (numFolds - 1) / numFolds;
+    
+    rt[0] = new Instances(data, 0, splits);
+    rt[1] = new Instances(data, splits, data.numInstances()-splits);
+    
+    return rt;
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4608 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/ZeroR.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/ZeroR.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/ZeroR.java	(revision 29)
@@ -0,0 +1,260 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ZeroR.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a 0-R classifier. Predicts the mean (for a numeric class) or the mode (for a nominal class).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class ZeroR 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = 48055541465867954L;
+  
+  /** The class value 0R predicts. */
+  private double m_ClassValue;
+
+  /** The number of instances in each class (null if class numeric). */
+  private double [] m_Counts;
+  
+  /** The class attribute. */
+  private Attribute m_Class;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for building and using a 0-R classifier. Predicts the mean " 
+      + "(for a numeric class) or the mode (for a nominal class).";	    
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    double sumOfWeights = 0;
+
+    m_Class = instances.classAttribute();
+    m_ClassValue = 0;
+    switch (instances.classAttribute().type()) {
+      case Attribute.NUMERIC:
+        m_Counts = null;
+        break;
+      case Attribute.NOMINAL:
+        m_Counts = new double [instances.numClasses()];
+        for (int i = 0; i < m_Counts.length; i++) {
+          m_Counts[i] = 1;
+        }
+        sumOfWeights = instances.numClasses();
+        break;
+    }
+    Enumeration enu = instances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      Instance instance = (Instance) enu.nextElement();
+      if (!instance.classIsMissing()) {
+	if (instances.classAttribute().isNominal()) {
+	  m_Counts[(int)instance.classValue()] += instance.weight();
+	} else {
+	  m_ClassValue += instance.weight() * instance.classValue();
+	}
+	sumOfWeights += instance.weight();
+      }
+    }
+    if (instances.classAttribute().isNumeric()) {
+      if (Utils.gr(sumOfWeights, 0)) {
+	m_ClassValue /= sumOfWeights;
+      }
+    } else {
+      m_ClassValue = Utils.maxIndex(m_Counts);
+      Utils.normalize(m_Counts, sumOfWeights);
+    }
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be classified
+   * @return index of the predicted class
+   */
+  public double classifyInstance(Instance instance) {
+
+    return m_ClassValue;
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if class is numeric
+   */
+  public double [] distributionForInstance(Instance instance) 
+       throws Exception {
+	 
+    if (m_Counts == null) {
+      double[] result = new double[1];
+      result[0] = m_ClassValue;
+      return result;
+    } else {
+      return (double []) m_Counts.clone();
+    }
+  }
+
+  /**
+   * Returns a string that describes the classifier as source. The
+   * classifier will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain a method with the signature:
+   * <pre><code>
+   * public static double classify(Object[] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className the name that should be given to the source class.
+   * @return the object source described by a string
+   * @throws Exception if the souce can't be computed
+   */
+  public String toSource(String className) throws Exception {
+    StringBuffer        result;
+    
+    result = new StringBuffer();
+    
+    result.append("class " + className + " {\n");
+    result.append("  public static double classify(Object[] i) {\n");
+    if (m_Counts != null)
+      result.append("    // always predicts label '" + m_Class.value((int) m_ClassValue) + "'\n");
+    result.append("    return " + m_ClassValue + ";\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+ 
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString() {
+
+    if (m_Class ==  null) {
+      return "ZeroR: No model built yet.";
+    }
+    if (m_Counts == null) {
+      return "ZeroR predicts class value: " + m_ClassValue;
+    } else {
+      return "ZeroR predicts class value: " + m_Class.value((int) m_ClassValue);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new ZeroR(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/part/C45PruneableDecList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/part/C45PruneableDecList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/part/C45PruneableDecList.java	(revision 29)
@@ -0,0 +1,202 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45PruneableDecList.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules.part;
+
+import weka.classifiers.trees.j48.Distribution;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.classifiers.trees.j48.Stats;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for handling a partial tree structure pruned using C4.5's
+ * pruning heuristic.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public class C45PruneableDecList
+  extends ClassifierDecList{
+
+  /** for serialization */
+  private static final long serialVersionUID = -2757684345218324559L;
+    
+  /** CF */
+  private double CF = 0.25;
+  
+  /**
+   * Constructor for pruneable tree structure. Stores reference
+   * to associated training data at each node.
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param cf the confidence factor for pruning
+   * @param minNum the minimum number of objects in a leaf
+   * @exception Exception if something goes wrong
+   */
+  public C45PruneableDecList(ModelSelection toSelectLocModel, 
+			     double cf, int minNum) 
+       throws Exception {
+			       
+    super(toSelectLocModel, minNum);
+    
+    CF = cf;
+  }
+ 
+  /**
+   * Builds the partial tree without hold out set.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildDecList(Instances data, boolean leaf) throws Exception {
+    
+    Instances [] localInstances,localPruneInstances;
+    int index,ind;
+    int i,j;
+    double sumOfWeights;
+    NoSplit noSplit;
+    
+    m_train = null;
+    m_test = null;
+    m_isLeaf = false;
+    m_isEmpty = false;
+    m_sons = null;
+    indeX = 0;
+    sumOfWeights = data.sumOfWeights();
+    noSplit = new NoSplit (new Distribution((Instances)data));
+    if (leaf)
+      m_localModel = noSplit;
+    else
+      m_localModel = m_toSelectModel.selectModel(data);
+    if (m_localModel.numSubsets() > 1) {
+      localInstances = m_localModel.split(data);
+      data = null;
+      m_sons = new ClassifierDecList [m_localModel.numSubsets()];
+      i = 0;
+      do {
+	i++;
+	ind = chooseIndex();
+	if (ind == -1) {
+	  for (j = 0; j < m_sons.length; j++) 
+	    if (m_sons[j] == null)
+	      m_sons[j] = getNewDecList(localInstances[j],true);
+	  if (i < 2) {
+	    m_localModel = noSplit;
+	    m_isLeaf = true;
+	    m_sons = null;
+	    if (Utils.eq(sumOfWeights,0))
+	      m_isEmpty = true;
+	    return;
+	  }
+	  ind = 0;
+	  break;
+	} else 
+	  m_sons[ind] = getNewDecList(localInstances[ind],false);
+      } while ((i < m_sons.length) && (m_sons[ind].m_isLeaf));
+      
+      // Check if all successors are leaves
+      for (j = 0; j < m_sons.length; j++) 
+	if ((m_sons[j] == null) || (!m_sons[j].m_isLeaf))
+	  break;
+      if (j == m_sons.length) {
+	pruneEnd();
+	if (!m_isLeaf) 
+	  indeX = chooseLastIndex();
+      }else 
+	indeX = chooseLastIndex();
+    }else{
+      m_isLeaf = true;
+      if (Utils.eq(sumOfWeights, 0))
+	m_isEmpty = true;
+    }
+  }
+ 
+  /**
+   * Returns a newly created tree.
+   *
+   * @exception Exception if something goes wrong
+   */
+  protected ClassifierDecList getNewDecList(Instances data, boolean leaf) 
+       throws Exception {
+	 
+    C45PruneableDecList newDecList = 
+      new C45PruneableDecList(m_toSelectModel,CF, m_minNumObj);
+    
+    newDecList.buildDecList((Instances)data, leaf);
+    
+    return newDecList;
+  }
+
+  /**
+   * Prunes the end of the rule.
+   */
+  protected void pruneEnd() {
+    
+    double errorsLeaf, errorsTree;
+    
+    errorsTree = getEstimatedErrorsForTree();
+    errorsLeaf = getEstimatedErrorsForLeaf();
+    if (Utils.smOrEq(errorsLeaf,errorsTree+0.1)) { // +0.1 as in C4.5
+      m_isLeaf = true;
+      m_sons = null;
+      m_localModel = new NoSplit(localModel().distribution());
+    }
+  }
+  
+  /**
+   * Computes estimated errors for tree.
+   */
+  private double getEstimatedErrorsForTree() {
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForLeaf();
+    else {
+      double error = 0;
+      for (int i = 0; i < m_sons.length; i++) 
+	if (!Utils.eq(son(i).localModel().distribution().total(),0))
+	  error += ((C45PruneableDecList)son(i)).getEstimatedErrorsForTree();
+      return error;
+    }
+  }
+  
+  /**
+   * Computes estimated errors for leaf.
+   */
+  public double getEstimatedErrorsForLeaf() {
+  
+    double errors = localModel().distribution().numIncorrect();
+
+    return errors+Stats.addErrs(localModel().distribution().total(),
+				errors,(float)CF);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/part/ClassifierDecList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/part/ClassifierDecList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/part/ClassifierDecList.java	(revision 29)
@@ -0,0 +1,441 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierDecList.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules.part;
+
+import weka.classifiers.trees.j48.ClassifierSplitModel;
+import weka.classifiers.trees.j48.Distribution;
+import weka.classifiers.trees.j48.EntropySplitCrit;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * Class for handling a rule (partial tree) for a decision list.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.13 $
+ */
+public class ClassifierDecList
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7284358349711992497L;
+
+  /** Minimum number of objects */
+  protected int m_minNumObj;
+ 
+  /** To compute the entropy. */
+  protected static EntropySplitCrit m_splitCrit = new EntropySplitCrit();
+
+  /** The model selection method. */
+  protected ModelSelection m_toSelectModel;   
+
+  /** Local model at node. */  
+  protected ClassifierSplitModel m_localModel; 
+
+  /** References to sons. */
+  protected ClassifierDecList [] m_sons;       
+  
+  /** True if node is leaf. */
+  protected boolean m_isLeaf;   
+
+  /** True if node is empty. */
+  protected boolean m_isEmpty;                 
+
+  /** The training instances. */
+  protected Instances m_train;                 
+
+  /** The pruning instances. */ 
+  protected Distribution m_test;               
+
+  /** Which son to expand? */  
+  protected int indeX;         
+
+  /**
+   * Constructor - just calls constructor of class DecList.
+   */
+  public ClassifierDecList(ModelSelection toSelectLocModel, int minNum){
+
+    m_toSelectModel = toSelectLocModel;
+    m_minNumObj = minNum;
+   }
+  
+  /**
+   * Method for building a pruned partial tree.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildRule(Instances data) throws Exception {
+    
+    buildDecList(data, false);
+
+    cleanup(new Instances(data, 0));
+  }
+ 
+  /**
+   * Builds the partial tree without hold out set.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildDecList(Instances data, boolean leaf) throws Exception {
+    
+    Instances [] localInstances,localPruneInstances;
+    int index,ind;
+    int i,j;
+    double sumOfWeights;
+    NoSplit noSplit;
+    
+    m_train = null;
+    m_test = null;
+    m_isLeaf = false;
+    m_isEmpty = false;
+    m_sons = null;
+    indeX = 0;
+    sumOfWeights = data.sumOfWeights();
+    noSplit = new NoSplit (new Distribution((Instances)data));
+    if (leaf)
+      m_localModel = noSplit;
+    else
+      m_localModel = m_toSelectModel.selectModel(data);
+    if (m_localModel.numSubsets() > 1) {
+      localInstances = m_localModel.split(data);
+      data = null;
+      m_sons = new ClassifierDecList [m_localModel.numSubsets()];
+      i = 0;
+      do {
+	i++;
+	ind = chooseIndex();
+	if (ind == -1) {
+	  for (j = 0; j < m_sons.length; j++) 
+	    if (m_sons[j] == null)
+	      m_sons[j] = getNewDecList(localInstances[j],true);
+	  if (i < 2) {
+	    m_localModel = noSplit;
+	    m_isLeaf = true;
+	    m_sons = null;
+	    if (Utils.eq(sumOfWeights,0))
+	      m_isEmpty = true;
+	    return;
+	  }
+	  ind = 0;
+	  break;
+	} else 
+	  m_sons[ind] = getNewDecList(localInstances[ind],false);
+      } while ((i < m_sons.length) && (m_sons[ind].m_isLeaf));
+      
+      // Choose rule
+      indeX = chooseLastIndex();
+    }else{
+      m_isLeaf = true;
+      if (Utils.eq(sumOfWeights, 0))
+	m_isEmpty = true;
+    }
+  }
+
+  /** 
+   * Classifies an instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double classifyInstance(Instance instance)
+       throws Exception {
+
+    double maxProb = -1;
+    double currentProb;
+    int maxIndex = 0;
+    int j;
+
+    for (j = 0; j < instance.numClasses();
+	 j++){
+      currentProb = getProbs(j,instance,1);
+      if (Utils.gr(currentProb,maxProb)){
+	maxIndex = j;
+	maxProb = currentProb;
+      }
+    }
+    if (Utils.eq(maxProb,0))
+      return -1.0;
+    else
+      return (double)maxIndex;
+  }
+
+  /** 
+   * Returns class probabilities for a weighted instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final double [] distributionForInstance(Instance instance) 
+       throws Exception {
+		
+
+    double [] doubles =
+      new double[instance.numClasses()];
+
+    for (int i = 0; i < doubles.length; i++)
+      doubles[i] = getProbs(i,instance,1);
+    
+    return doubles;
+  }
+  
+  /**
+   * Returns the weight a rule assigns to an instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double weight(Instance instance) throws Exception {
+
+    int subset;
+
+    if (m_isLeaf)
+      return 1;
+    subset = m_localModel.whichSubset(instance);
+    if (subset == -1)
+      return (m_localModel.weights(instance))[indeX]*
+	m_sons[indeX].weight(instance);
+    if (subset == indeX)
+      return m_sons[indeX].weight(instance);
+    return 0;
+  }
+
+  /**
+   * Cleanup in order to save memory.
+   */
+  public final void cleanup(Instances justHeaderInfo) {
+
+    m_train = justHeaderInfo;
+    m_test = null;
+    if (!m_isLeaf)
+      for (int i = 0; i < m_sons.length; i++)
+	if (m_sons[i] != null)
+	  m_sons[i].cleanup(justHeaderInfo);
+  }
+
+  /**
+   * Prints rules.
+   */
+  public String toString(){
+
+    try {
+      StringBuffer text;
+      
+      text = new StringBuffer();
+      if (m_isLeaf){
+	text.append(": ");
+	text.append(m_localModel.dumpLabel(0,m_train)+"\n");
+      }else{
+      dumpDecList(text);
+      //dumpTree(0,text);
+      }
+      return text.toString();
+    } catch (Exception e) {
+      return "Can't print rule.";
+    }
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @exception Exception if something goes wrong
+   */
+  protected ClassifierDecList getNewDecList(Instances train, boolean leaf) 
+    throws Exception {
+	 
+    ClassifierDecList newDecList = new ClassifierDecList(m_toSelectModel,
+							 m_minNumObj);
+    newDecList.buildDecList(train,leaf);
+    
+    return newDecList;
+  }
+ 
+  /**
+   * Method for choosing a subset to expand.
+   */
+  public final int chooseIndex() {
+    
+    int minIndex = -1;
+    double estimated, min = Double.MAX_VALUE;
+    int i, j;
+
+    for (i = 0; i < m_sons.length; i++)
+      if (son(i) == null) {
+	if (Utils.sm(localModel().distribution().perBag(i),
+		     (double)m_minNumObj))
+	  estimated = Double.MAX_VALUE;
+	else{
+	  estimated = 0;
+	  for (j = 0; j < localModel().distribution().numClasses(); j++) 
+	    estimated -= m_splitCrit.logFunc(localModel().distribution().
+				     perClassPerBag(i,j));
+	  estimated += m_splitCrit.logFunc(localModel().distribution().
+				   perBag(i));
+	  estimated /= localModel().distribution().perBag(i);
+	}
+	if (Utils.smOrEq(estimated,0))
+	  return i;
+	if (Utils.sm(estimated,min)) {
+	  min = estimated;
+	  minIndex = i;
+	}
+      }
+
+    return minIndex;
+  }
+  
+  /**
+   * Choose last index (ie. choose rule).
+   */
+  public final int chooseLastIndex() {
+    
+    int minIndex = 0;
+    double estimated, min = Double.MAX_VALUE;
+    
+    if (!m_isLeaf) 
+      for (int i = 0; i < m_sons.length; i++)
+	if (son(i) != null) {
+	  if (Utils.grOrEq(localModel().distribution().perBag(i),
+			   (double)m_minNumObj)) {
+	    estimated = son(i).getSizeOfBranch();
+	    if (Utils.sm(estimated,min)) {
+	      min = estimated;
+	      minIndex = i;
+	    }
+	  }
+	}
+
+    return minIndex;
+  }
+ 
+  /**
+   * Returns the number of instances covered by a branch
+   */
+  protected double getSizeOfBranch() {
+    
+    if (m_isLeaf) {
+      return -localModel().distribution().total();
+    } else
+      return son(indeX).getSizeOfBranch();
+  }
+
+  /**
+   * Help method for printing tree structure.
+   */
+  private void dumpDecList(StringBuffer text) throws Exception {
+    
+    text.append(m_localModel.leftSide(m_train));
+    text.append(m_localModel.rightSide(indeX, m_train));
+    if (m_sons[indeX].m_isLeaf){
+      text.append(": ");
+      text.append(m_localModel.dumpLabel(indeX,m_train)+"\n");
+    }else{
+      text.append(" AND\n");
+      m_sons[indeX].dumpDecList(text);
+    }
+  }
+
+  /**
+   * Dumps the partial tree (only used for debugging)
+   *
+   * @exception Exception Exception if something goes wrong
+   */
+  private void dumpTree(int depth,StringBuffer text)
+       throws Exception {
+    
+    int i,j;
+    
+    for (i=0;i<m_sons.length;i++){
+      text.append("\n");;
+      for (j=0;j<depth;j++)
+	text.append("|   ");
+      text.append(m_localModel.leftSide(m_train));
+      text.append(m_localModel.rightSide(i, m_train));
+      if (m_sons[i] == null)
+	text.append("null");
+      else if (m_sons[i].m_isLeaf){
+	text.append(": ");
+	text.append(m_localModel.dumpLabel(i,m_train));
+      }else
+	m_sons[i].dumpTree(depth+1,text);
+    }
+  }
+
+  /**
+   * Help method for computing class probabilities of 
+   * a given instance.
+   *
+   * @exception Exception Exception if something goes wrong
+   */
+  private double getProbs(int classIndex,Instance instance,
+			  double weight) throws Exception {
+    
+    double [] weights;
+    int treeIndex;
+
+    if (m_isLeaf) {
+      return weight * localModel().classProb(classIndex, instance, -1);
+    } else {
+      treeIndex = localModel().whichSubset(instance);
+      if (treeIndex == -1) {
+	weights = localModel().weights(instance);
+	return son(indeX).getProbs(classIndex, instance, 
+				   weights[indeX] * weight);
+      }else{
+	if (treeIndex == indeX) {
+	  return son(indeX).getProbs(classIndex, instance, weight);
+	} else {
+	  return 0;
+	}
+      }
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  protected ClassifierSplitModel localModel(){
+    
+    return (ClassifierSplitModel)m_localModel;
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  protected ClassifierDecList son(int index){
+    
+    return m_sons[index];
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.13 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/part/MakeDecList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/part/MakeDecList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/part/MakeDecList.java	(revision 29)
@@ -0,0 +1,308 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MakeDecList.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules.part;
+
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for handling a decision list.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5483 $
+ */
+public class MakeDecList
+  implements Serializable, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1427481323245079123L;
+
+  /** Vector storing the rules. */
+  private Vector theRules;
+
+  /** The confidence for C45-type pruning. */
+  private double CF = 0.25f;
+
+  /** Minimum number of objects */
+  private int minNumObj;
+
+  /** The model selection method. */
+  private ModelSelection toSelectModeL;
+
+  /** How many subsets of equal size? One used for pruning, the rest for training. */
+  private int numSetS = 3;
+
+  /** Use reduced error pruning? */
+  private boolean reducedErrorPruning = false;
+
+  /** Generated unpruned list? */
+  private boolean unpruned = false;
+
+  /** The seed for random number generation. */
+  private int m_seed = 1;
+
+  /**
+   * Constructor for unpruned dec list.
+   */
+  public MakeDecList(ModelSelection toSelectLocModel,
+		     int minNum){
+
+    toSelectModeL = toSelectLocModel;
+    reducedErrorPruning = false;
+    unpruned = true;
+    minNumObj = minNum;
+  }
+
+  /**
+   * Constructor for dec list pruned using C4.5 pruning.
+   */
+  public MakeDecList(ModelSelection toSelectLocModel, double cf,
+		     int minNum){
+
+    toSelectModeL = toSelectLocModel;
+    CF = cf;
+    reducedErrorPruning = false;
+    unpruned = false;
+    minNumObj = minNum;
+  }
+
+  /**
+   * Constructor for dec list pruned using hold-out pruning.
+   */
+  public MakeDecList(ModelSelection toSelectLocModel, int num,
+		     int minNum, int seed){
+
+    toSelectModeL = toSelectLocModel;
+    numSetS = num;
+    reducedErrorPruning = true;
+    unpruned = false;
+    minNumObj = minNum;
+    m_seed = seed;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds dec list.
+   *
+   * @exception Exception if dec list can't be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    ClassifierDecList currentRule;
+    double currentWeight;
+    Instances oldGrowData, newGrowData, oldPruneData,
+      newPruneData;
+    int numRules = 0;
+    
+    theRules = new Vector();
+    if ((reducedErrorPruning) && !(unpruned)){ 
+      Random random = new Random(m_seed);
+      data.randomize(random);
+      data.stratify(numSetS);
+      oldGrowData = data.trainCV(numSetS, numSetS - 1, random);
+      oldPruneData = data.testCV(numSetS, numSetS - 1);
+    } else {
+      oldGrowData = data;
+      oldPruneData = null;
+    }
+
+    while (Utils.gr(oldGrowData.numInstances(),0)){
+
+      // Create rule
+      if (unpruned) {
+	currentRule = new ClassifierDecList(toSelectModeL,
+					    minNumObj);
+	((ClassifierDecList)currentRule).buildRule(oldGrowData);
+      } else if (reducedErrorPruning) {
+	currentRule = new PruneableDecList(toSelectModeL,
+					   minNumObj);
+	((PruneableDecList)currentRule).buildRule(oldGrowData, 
+						  oldPruneData);
+      } else {
+	currentRule = new C45PruneableDecList(toSelectModeL, CF,
+					      minNumObj);
+	((C45PruneableDecList)currentRule).buildRule(oldGrowData);
+      }
+      numRules++;
+
+      // Remove instances from growing data
+      newGrowData = new Instances(oldGrowData,
+				  oldGrowData.numInstances());
+      Enumeration enu = oldGrowData.enumerateInstances();
+      while (enu.hasMoreElements()) {
+	Instance instance = (Instance) enu.nextElement();
+	currentWeight = currentRule.weight(instance);
+	if (Utils.sm(currentWeight,1)) {
+	  instance.setWeight(instance.weight()*(1-currentWeight));
+	  newGrowData.add(instance);
+	}
+      }
+      newGrowData.compactify();
+      oldGrowData = newGrowData;
+      
+      // Remove instances from pruning data
+      if ((reducedErrorPruning) && !(unpruned)) {
+	newPruneData = new Instances(oldPruneData,
+					     oldPruneData.numInstances());
+	enu = oldPruneData.enumerateInstances();
+	while (enu.hasMoreElements()) {
+	  Instance instance = (Instance) enu.nextElement();
+	  currentWeight = currentRule.weight(instance);
+	  if (Utils.sm(currentWeight,1)) {
+	    instance.setWeight(instance.weight()*(1-currentWeight));
+	    newPruneData.add(instance);
+	  }
+	}
+	newPruneData.compactify();
+	oldPruneData = newPruneData;
+      }
+      theRules.addElement(currentRule);
+    }
+  }
+
+  /**
+   * Outputs the classifier into a string.
+   */
+  public String toString(){
+
+    StringBuffer text = new StringBuffer();
+
+    for (int i=0;i<theRules.size();i++)
+      text.append((ClassifierDecList)theRules.elementAt(i)+"\n");
+    text.append("Number of Rules  : \t"+theRules.size()+"\n");
+
+    return text.toString();
+  }
+
+  /** 
+   * Classifies an instance.
+   *
+   * @exception Exception if instance can't be classified
+   */
+  public double classifyInstance(Instance instance) 
+       throws Exception {
+
+    double maxProb = -1;
+    double [] sumProbs;
+    int maxIndex = 0;
+
+    sumProbs = distributionForInstance(instance);
+    for (int j = 0; j < sumProbs.length; j++) {
+      if (Utils.gr(sumProbs[j],maxProb)){
+	maxIndex = j;
+	maxProb = sumProbs[j];
+      }
+    }
+
+    return (double)maxIndex;
+  }
+
+  /** 
+   * Returns the class distribution for an instance.
+   *
+   * @exception Exception if distribution can't be computed
+   */
+  public double[] distributionForInstance(Instance instance) 
+       throws Exception {
+
+    double [] currentProbs = null;
+    double [] sumProbs;
+    double currentWeight, weight = 1;
+    int i,j;
+	
+    // Get probabilities.
+    sumProbs = new double [instance.numClasses()];
+    i = 0;
+    while (Utils.gr(weight,0)){
+      currentWeight = 
+	((ClassifierDecList)theRules.elementAt(i)).weight(instance);
+      if (Utils.gr(currentWeight,0)) {
+	currentProbs = ((ClassifierDecList)theRules.elementAt(i)).
+	  distributionForInstance(instance);
+	for (j = 0; j < sumProbs.length; j++)
+	  sumProbs[j] += weight*currentProbs[j];
+	weight = weight*(1-currentWeight);
+      }
+      i++;
+    }
+
+    return sumProbs;
+  }
+
+  /**
+   * Outputs the number of rules in the classifier.
+   */
+  public int numRules(){
+
+    return theRules.size();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5483 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/rules/part/PruneableDecList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/rules/part/PruneableDecList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/rules/part/PruneableDecList.java	(revision 29)
@@ -0,0 +1,216 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PruneableDecList.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.rules.part;
+
+import weka.classifiers.trees.j48.Distribution;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for handling a partial tree structure that
+ * can be pruned using a pruning set.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public class PruneableDecList
+  extends ClassifierDecList {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7228103346297172921L;
+  
+  /**
+   * Constructor for pruneable partial tree structure. 
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param minNum minimum number of objects in leaf
+   */
+  public PruneableDecList(ModelSelection toSelectLocModel,
+			  int minNum) {
+			       
+    super(toSelectLocModel, minNum);
+  }
+  
+  /**
+   * Method for building a pruned partial tree.
+   *
+   * @throws Exception if tree can't be built successfully
+   */
+  public void buildRule(Instances train,
+			Instances test) throws Exception { 
+    
+    buildDecList(train, test, false);
+
+    cleanup(new Instances(train, 0));
+  }
+
+  /**
+   * Builds the partial tree with hold out set
+   *
+   * @throws Exception if something goes wrong
+   */
+  public void buildDecList(Instances train, Instances test, 
+			   boolean leaf) throws Exception {
+    
+    Instances [] localTrain,localTest;
+    int index,ind;
+    int i,j;
+    double sumOfWeights;
+    NoSplit noSplit;
+    
+    m_train = null;
+    m_isLeaf = false;
+    m_isEmpty = false;
+    m_sons = null;
+    indeX = 0;
+    sumOfWeights = train.sumOfWeights();
+    noSplit = new NoSplit (new Distribution((Instances)train));
+    if (leaf)
+      m_localModel = noSplit;
+    else
+      m_localModel = m_toSelectModel.selectModel(train, test);
+    m_test = new Distribution(test, m_localModel);
+    if (m_localModel.numSubsets() > 1) {
+      localTrain = m_localModel.split(train);
+      localTest = m_localModel.split(test);
+      train = null;
+      test = null;
+      m_sons = new ClassifierDecList [m_localModel.numSubsets()];
+      i = 0;
+      do {
+	i++;
+	ind = chooseIndex();
+	if (ind == -1) {
+	  for (j = 0; j < m_sons.length; j++) 
+	    if (m_sons[j] == null)
+	      m_sons[j] = getNewDecList(localTrain[j],localTest[j],true);
+	  if (i < 2) {
+	    m_localModel = noSplit;
+	    m_isLeaf = true;
+	    m_sons = null;
+	    if (Utils.eq(sumOfWeights,0))
+	      m_isEmpty = true;
+	    return;
+	  }
+	  ind = 0;
+	  break;
+	} else 
+	  m_sons[ind] = getNewDecList(localTrain[ind],localTest[ind],false);
+      } while ((i < m_sons.length) && (m_sons[ind].m_isLeaf));
+      
+      // Check if all successors are leaves
+      for (j = 0; j < m_sons.length; j++) 
+	if ((m_sons[j] == null) || (!m_sons[j].m_isLeaf))
+	  break;
+      if (j == m_sons.length) {
+	pruneEnd();
+	if (!m_isLeaf) 
+	  indeX = chooseLastIndex();
+      }else 
+	indeX = chooseLastIndex();
+    }else{
+      m_isLeaf = true;
+      if (Utils.eq(sumOfWeights, 0))
+	m_isEmpty = true;
+    }
+  }
+  
+  /**
+   * Returns a newly created tree.
+   *
+   * @param train train data
+   * @param test test data
+   * @param leaf
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierDecList getNewDecList(Instances train, Instances test, 
+					    boolean leaf) throws Exception {
+	 
+    PruneableDecList newDecList = 
+      new PruneableDecList(m_toSelectModel, m_minNumObj);
+    
+    newDecList.buildDecList((Instances)train, test, leaf);
+    
+    return newDecList;
+  }
+
+  /**
+   * Prunes the end of the rule.
+   */
+  protected void pruneEnd() throws Exception {
+    
+    double errorsLeaf, errorsTree;
+    
+    errorsTree = errorsForTree();
+    errorsLeaf = errorsForLeaf();
+    if (Utils.smOrEq(errorsLeaf,errorsTree)){ 
+      m_isLeaf = true;
+      m_sons = null;
+      m_localModel = new NoSplit(localModel().distribution());
+    }
+  }
+
+  /**
+   * Computes error estimate for tree.
+   */
+  private double errorsForTree() throws Exception {
+
+    Distribution test;
+
+    if (m_isLeaf)
+      return errorsForLeaf();
+    else {
+      double error = 0;
+      for (int i = 0; i < m_sons.length; i++) 
+	if (Utils.eq(son(i).localModel().distribution().total(),0)) {
+	  error += m_test.perBag(i)-
+	    m_test.perClassPerBag(i,localModel().distribution().
+				maxClass());
+	} else
+	  error += ((PruneableDecList)son(i)).errorsForTree();
+
+      return error;
+    }
+  }
+
+  /**
+   * Computes estimated errors for leaf.
+   */
+  private double errorsForLeaf() throws Exception {
+
+    return m_test.total()-
+	    m_test.perClass(localModel().distribution().maxClass());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/scripting/GroovyClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/scripting/GroovyClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/scripting/GroovyClassifier.java	(revision 29)
@@ -0,0 +1,361 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GroovyClassifier.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.scripting;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.OptionHandler;
+import weka.core.scripting.Groovy;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A wrapper class for Groovy code. Even though the classifier is serializable, the trained classifier cannot be stored persistently. I.e., one cannot store a model file and re-load it at a later point in time again to make predictions.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -G &lt;filename&gt;
+ *  The Groovy module to load (full path)
+ *  Options after '--' will be passed on to the Groovy module.</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after "--" will be passed on to the Groovy module.
+ * <p/>
+ * In order to use <a href="http://groovy.codehaus.org/" target="_blank">Groovy</a>, 
+ * the jar containing all the classes must be present in the CLASSPATH. 
+ * This jar is normally found in the <i>embeddable</i>
+ * sub-directory of the Groovy installation.
+ * <p/>
+ * Tested with Groovy 1.5.7.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see Groovy
+ */
+public class GroovyClassifier 
+  extends AbstractClassifier {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -9078371491735496175L;
+  
+  /** the Groovy module. */
+  protected File m_GroovyModule = new File(System.getProperty("user.dir"));
+
+  /** the options for the Groovy module. */
+  protected String[] m_GroovyOptions = new String[0];
+
+  /** the loaded Groovy object. */
+  transient protected Classifier m_GroovyObject = null;
+
+  /**
+   * default constructor.
+   */
+  public GroovyClassifier() {
+    super();
+  }
+  
+  /**
+   * Returns a string describing classifier.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A wrapper class for Groovy code. Even though the classifier is "
+      + "serializable, the trained classifier cannot be stored persistently. "
+      + "I.e., one cannot store a model file and re-load it at a later point "
+      + "in time again to make predictions.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tThe Groovy module to load (full path)\n"
+	+ "\tOptions after '--' will be passed on to the Groovy module.",
+	"G", 1, "-G <filename>"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String		tmpStr;
+
+    m_GroovyOptions = new String[0];
+
+    tmpStr = Utils.getOption('G', options);
+    if (tmpStr.length() != 0)
+      setGroovyModule(new File(tmpStr));
+    else
+      setGroovyModule(new File(System.getProperty("user.dir")));
+
+    setGroovyOptions(Utils.joinOptions(Utils.partitionOptions(options).clone()));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return 		an array of strings suitable for passing to 
+   * 			setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    result.add("-G");
+    result.add("" + getGroovyModule().getAbsolutePath());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (m_GroovyOptions.length > 0) {
+      options = m_GroovyOptions;
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+	result.add(options[i]);
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String GroovyModuleTipText() {
+    return "The Groovy module to load and execute.";
+  }
+
+  /**
+   * Sets the Groovy module.
+   *
+   * @param value 	the Groovy module
+   */
+  public void setGroovyModule(File value) {
+    m_GroovyModule = value;
+    initGroovyObject();
+  }
+
+  /**
+   * Gets the Groovy module.
+   *
+   * @return 		the Groovy module
+   */
+  public File getGroovyModule() {
+    return m_GroovyModule;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String GroovyOptionsTipText() {
+    return "The options for the Groovy module.";
+  }
+
+  /**
+   * Sets the Groovy module options.
+   *
+   * @param value 	the options
+   */
+  public void setGroovyOptions(String value) {
+    try {
+      m_GroovyOptions = Utils.splitOptions(value).clone();
+      initGroovyObject();
+    }
+    catch (Exception e) {
+      m_GroovyOptions = new String[0];
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Gets the Groovy module options.
+   *
+   * @return 		the options
+   */
+  public String getGroovyOptions() {
+    return Utils.joinOptions(m_GroovyOptions);
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+
+    if (m_GroovyObject == null) {
+      result = new Capabilities(this);
+      result.disableAll();
+    } else {
+      result = m_GroovyObject.getCapabilities();
+    }
+
+    result.enableAllAttributeDependencies();
+    result.enableAllClassDependencies();
+
+    return result;
+  }
+
+  /**
+   * tries to initialize the groovy object and set its options.
+   */
+  protected void initGroovyObject() {
+    try {
+      if (m_GroovyModule.isFile())
+	m_GroovyObject = (Classifier) Groovy.newInstance(m_GroovyModule, Classifier.class);
+      else
+	m_GroovyObject = null;
+      
+      if (m_GroovyObject != null)
+	((OptionHandler)m_GroovyObject).setOptions(m_GroovyOptions.clone());
+    }
+    catch (Exception e) {
+      m_GroovyObject = null;
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances 	set of instances serving as training data 
+   * @throws Exception	if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    if (!Groovy.isPresent())
+      throw new Exception("Groovy classes not in CLASSPATH!");
+
+    // try loading the module
+    initGroovyObject();
+
+    // build the model
+    if (m_GroovyObject != null)
+      m_GroovyObject.buildClassifier(instances);
+    else
+      System.err.println("buildClassifier: No Groovy object present!");
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance	the instance to be classified
+   * @return 		index of the predicted class
+   * @throws Exception 	if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    if (m_GroovyObject != null)
+      return m_GroovyObject.classifyInstance(instance);
+    else
+      return Utils.missingValue();
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance	the instance to be classified
+   * @return 		predicted class probability distribution
+   * @throws Exception	if class is numeric
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    if (m_GroovyObject != null)
+      return m_GroovyObject.distributionForInstance(instance);
+    else
+      return new double[instance.numClasses()];
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return 		a description of the classifier as a string.
+   */
+  public String toString() {
+    if (m_GroovyObject != null)
+      return m_GroovyObject.toString();
+    else
+      return "No Groovy module loaded.";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	the options
+   */
+  public static void main(String [] args) {
+    runClassifier(new GroovyClassifier(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/scripting/JythonClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/scripting/JythonClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/scripting/JythonClassifier.java	(revision 29)
@@ -0,0 +1,423 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    JythonClassifier.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.scripting;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.OptionHandler;
+import weka.core.scripting.Jython;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A wrapper class for Jython code. Even though the classifier is serializable, the trained classifier cannot be stored persistently. I.e., one cannot store a model file and re-load it at a later point in time again to make predictions.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -J &lt;filename&gt;
+ *  The Jython module to load (full path)
+ *  Options after '--' will be passed on to the Jython module.</pre>
+ * 
+ * <pre> -P &lt;directory[,directory,...]&gt;
+ *  The paths to add to 'sys.path' (comma-separated list).</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after "--" will be passed onto the Jython module.
+ * <p/>
+ * Partially based on <a href="http://wiki.python.org/jython/JythonMonthly/Articles/September2006/1" target="_blank">this</a>
+ * code.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class JythonClassifier 
+  extends AbstractClassifier {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -9078371491735496175L;
+  
+  /** the Jython module. */
+  protected File m_JythonModule = new File(System.getProperty("user.dir"));
+
+  /** the options for the Jython module. */
+  protected String[] m_JythonOptions = new String[0];
+
+  /** additional paths for the Jython module (will be added to "sys.path"). */
+  protected File[] m_JythonPaths = new File[0];
+
+  /** the loaded Jython object. */
+  transient protected Classifier m_JythonObject = null;
+
+  /**
+   * default constructor.
+   */
+  public JythonClassifier() {
+    super();
+  }
+  
+  /**
+   * Returns a string describing classifier.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A wrapper class for Jython code. Even though the classifier is "
+      + "serializable, the trained classifier cannot be stored persistently. "
+      + "I.e., one cannot store a model file and re-load it at a later point "
+      + "in time again to make predictions.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tThe Jython module to load (full path)\n"
+	+ "\tOptions after '--' will be passed on to the Jython module.",
+	"J", 1, "-J <filename>"));
+
+    result.addElement(new Option(
+	"\tThe paths to add to 'sys.path' (comma-separated list).",
+	"P", 1, "-P <directory[,directory,...]>"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String		tmpStr;
+
+    m_JythonOptions = new String[0];
+
+    setJythonPaths(Utils.getOption('P', options));
+
+    tmpStr = Utils.getOption('J', options);
+    if (tmpStr.length() != 0)
+      setJythonModule(new File(tmpStr));
+    else
+      setJythonModule(new File(System.getProperty("user.dir")));
+
+    setJythonOptions(Utils.joinOptions(Utils.partitionOptions(options).clone()));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return 		an array of strings suitable for passing to 
+   * 			setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+
+    result = new Vector<String>();
+
+    if (getJythonPaths().length() > 0) {
+      result.add("-P");
+      result.add("" + getJythonPaths());
+    }
+
+    result.add("-J");
+    result.add("" + getJythonModule().getAbsolutePath());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (m_JythonOptions.length > 0) {
+      options = m_JythonOptions;
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+	result.add(options[i]);
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String jythonModuleTipText() {
+    return "The Jython module to load and execute.";
+  }
+
+  /**
+   * Sets the Jython module.
+   *
+   * @param value 	the Jython module
+   */
+  public void setJythonModule(File value) {
+    m_JythonModule = value;
+    initJythonObject();
+  }
+
+  /**
+   * Gets the Jython module.
+   *
+   * @return 		the Jython module
+   */
+  public File getJythonModule() {
+    return m_JythonModule;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String jythonOptionsTipText() {
+    return "The options for the Jython module.";
+  }
+
+  /**
+   * Sets the Jython module options.
+   *
+   * @param value 	the options
+   */
+  public void setJythonOptions(String value) {
+    try {
+      m_JythonOptions = Utils.splitOptions(value).clone();
+      initJythonObject();
+    }
+    catch (Exception e) {
+      m_JythonOptions = new String[0];
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Gets the Jython module options.
+   *
+   * @return 		the options
+   */
+  public String getJythonOptions() {
+    return Utils.joinOptions(m_JythonOptions);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String jythonPathsTipText() {
+    return "Comma-separated list of additional paths that get added to 'sys.path'.";
+  }
+
+  /**
+   * Sets the additional Jython paths.
+   *
+   * @param value 	the paths (comma-separated list)
+   */
+  public void setJythonPaths(String value) {
+    String[]	paths;
+    int		i;
+
+    if (value.length() == 0) {
+      m_JythonPaths = new File[0];
+    }
+    else {
+      paths = value.split(",");
+      m_JythonPaths = new File[paths.length];
+      for (i = 0; i < m_JythonPaths.length; i++)
+	m_JythonPaths[i] = new File(paths[i]);
+    }
+  }
+
+  /**
+   * Gets the additional Jython paths.
+   *
+   * @return 		the paths
+   */
+  public String getJythonPaths() {
+    String	result;
+    int		i;
+
+    result = "";
+
+    for (i = 0; i < m_JythonPaths.length; i++) {
+      if (i > 0)
+	result += ",";
+      result += m_JythonPaths[i].getAbsolutePath();
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+
+    if (m_JythonObject == null) {
+      result = new Capabilities(this);
+      result.disableAll();
+    } else {
+      result = m_JythonObject.getCapabilities();
+    }
+
+    result.enableAllAttributeDependencies();
+    result.enableAllClassDependencies();
+
+    return result;
+  }
+
+  /**
+   * tries to initialize the python object and set its options.
+   */
+  protected void initJythonObject() {
+    try {
+      if (m_JythonModule.isFile())
+	m_JythonObject = (Classifier) Jython.newInstance(m_JythonModule, Classifier.class, m_JythonPaths);
+      else
+	m_JythonObject = null;
+      
+      if (m_JythonObject != null)
+	((OptionHandler)m_JythonObject).setOptions(m_JythonOptions.clone());
+    }
+    catch (Exception e) {
+      m_JythonObject = null;
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances 	set of instances serving as training data 
+   * @throws Exception	if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    if (!Jython.isPresent())
+      throw new Exception("Jython classes not in CLASSPATH!");
+
+    // try loading the module
+    initJythonObject();
+
+    // build the model
+    if (m_JythonObject != null)
+      m_JythonObject.buildClassifier(instances);
+    else
+      System.err.println("buildClassifier: No Jython object present!");
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance	the instance to be classified
+   * @return 		index of the predicted class
+   * @throws Exception 	if an error occurred during the prediction
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    if (m_JythonObject != null)
+      return m_JythonObject.classifyInstance(instance);
+    else
+      return Utils.missingValue();
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance	the instance to be classified
+   * @return 		predicted class probability distribution
+   * @throws Exception	if class is numeric
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    if (m_JythonObject != null)
+      return m_JythonObject.distributionForInstance(instance);
+    else
+      return new double[instance.numClasses()];
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return 		a description of the classifier as a string.
+   */
+  public String toString() {
+    if (m_JythonObject != null)
+      return m_JythonObject.toString();
+    else
+      return "No Jython module loaded.";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	the options
+   */
+  public static void main(String [] args) {
+    runClassifier(new JythonClassifier(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/ADTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/ADTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/ADTree.java	(revision 29)
@@ -0,0 +1,1499 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ADTree.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.IterativeClassifier;
+import weka.classifiers.trees.adtree.PredictionNode;
+import weka.classifiers.trees.adtree.ReferenceInstances;
+import weka.classifiers.trees.adtree.Splitter;
+import weka.classifiers.trees.adtree.TwoWayNominalSplit;
+import weka.classifiers.trees.adtree.TwoWayNumericSplit;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SerializedObject;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating an alternating decision tree. The basic algorithm is based on:<br/>
+ * <br/>
+ * Freund, Y., Mason, L.: The alternating decision tree learning algorithm. In: Proceeding of the Sixteenth International Conference on Machine Learning, Bled, Slovenia, 124-133, 1999.<br/>
+ * <br/>
+ * This version currently only supports two-class problems. The number of boosting iterations needs to be manually tuned to suit the dataset and the desired complexity/accuracy tradeoff. Induction of the trees has been optimized, and heuristic search methods have been introduced to speed learning.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Freund1999,
+ *    address = {Bled, Slovenia},
+ *    author = {Freund, Y. and Mason, L.},
+ *    booktitle = {Proceeding of the Sixteenth International Conference on Machine Learning},
+ *    pages = {124-133},
+ *    title = {The alternating decision tree learning algorithm},
+ *    year = {1999}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;number of boosting iterations&gt;
+ *  Number of boosting iterations.
+ *  (Default = 10)</pre>
+ * 
+ * <pre> -E &lt;-3|-2|-1|&gt;=0&gt;
+ *  Expand nodes: -3(all), -2(weight), -1(z_pure), &gt;=0 seed for random walk
+ *  (Default = -3)</pre>
+ * 
+ * <pre> -D
+ *  Save the instance data with the model</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class ADTree
+  extends AbstractClassifier 
+  implements OptionHandler, Drawable, AdditionalMeasureProducer,
+             WeightedInstancesHandler, IterativeClassifier, 
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -1532264837167690683L;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for generating an alternating decision tree. The basic "
+      + "algorithm is based on:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "This version currently only supports two-class problems. The number of boosting "
+      + "iterations needs to be manually tuned to suit the dataset and the desired "
+      + "complexity/accuracy tradeoff. Induction of the trees has been optimized, and heuristic "
+      + "search methods have been introduced to speed learning.";
+  }
+
+  /** search mode: Expand all paths */
+  public static final int SEARCHPATH_ALL = 0;
+  /** search mode: Expand the heaviest path */
+  public static final int SEARCHPATH_HEAVIEST = 1;
+  /** search mode: Expand the best z-pure path */
+  public static final int SEARCHPATH_ZPURE = 2;
+  /** search mode: Expand a random path */
+  public static final int SEARCHPATH_RANDOM = 3;
+  /** The search modes */
+  public static final Tag [] TAGS_SEARCHPATH = {
+    new Tag(SEARCHPATH_ALL, "Expand all paths"),
+    new Tag(SEARCHPATH_HEAVIEST, "Expand the heaviest path"),
+    new Tag(SEARCHPATH_ZPURE, "Expand the best z-pure path"),
+    new Tag(SEARCHPATH_RANDOM, "Expand a random path")
+  };
+
+  /** The instances used to train the tree */
+  protected Instances m_trainInstances;
+
+  /** The root of the tree */
+  protected PredictionNode m_root = null;
+
+  /** The random number generator - used for the random search heuristic */
+  protected Random m_random = null; 
+
+  /** The number of the last splitter added to the tree */
+  protected int m_lastAddedSplitNum = 0;
+
+  /** An array containing the inidices to the numeric attributes in the data */
+  protected int[] m_numericAttIndices;
+
+  /** An array containing the inidices to the nominal attributes in the data */
+  protected int[] m_nominalAttIndices;
+
+  /** The total weight of the instances - used to speed Z calculations */
+  protected double m_trainTotalWeight;
+
+  /** The training instances with positive class - referencing the training dataset */
+  protected ReferenceInstances m_posTrainInstances;
+
+  /** The training instances with negative class - referencing the training dataset */
+  protected ReferenceInstances m_negTrainInstances;
+
+  /** The best node to insert under, as found so far by the latest search */
+  protected PredictionNode m_search_bestInsertionNode;
+
+  /** The best splitter to insert, as found so far by the latest search */
+  protected Splitter m_search_bestSplitter;
+
+  /** The smallest Z value found so far by the latest search */
+  protected double m_search_smallestZ;
+
+  /** The positive instances that apply to the best path found so far */
+  protected Instances m_search_bestPathPosInstances;
+
+  /** The negative instances that apply to the best path found so far */
+  protected Instances m_search_bestPathNegInstances;
+
+  /** Statistics - the number of prediction nodes investigated during search */
+  protected int m_nodesExpanded = 0;
+
+  /** Statistics - the number of instances processed during search */
+  protected int m_examplesCounted = 0;
+
+  /** Option - the number of boosting iterations o perform */
+  protected int m_boostingIterations = 10;
+
+  /** Option - the search mode */
+  protected int m_searchPath = 0;
+
+  /** Option - the seed to use for a random search */
+  protected int m_randomSeed = 0; 
+
+  /** Option - whether the tree should remember the instance data */
+  protected boolean m_saveInstanceData = false; 
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Freund, Y. and Mason, L.");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.TITLE, "The alternating decision tree learning algorithm");
+    result.setValue(Field.BOOKTITLE, "Proceeding of the Sixteenth International Conference on Machine Learning");
+    result.setValue(Field.ADDRESS, "Bled, Slovenia");
+    result.setValue(Field.PAGES, "124-133");
+    
+    return result;
+  }
+
+  /**
+   * Sets up the tree ready to be trained, using two-class optimized method.
+   *
+   * @param instances the instances to train the tree with
+   * @exception Exception if training data is unsuitable
+   */
+  public void initClassifier(Instances instances) throws Exception {
+
+    // clear stats
+    m_nodesExpanded = 0;
+    m_examplesCounted = 0;
+    m_lastAddedSplitNum = 0;
+
+    // prepare the random generator
+    m_random = new Random(m_randomSeed);
+
+    // create training set
+    m_trainInstances = new Instances(instances);
+
+    // create positive/negative subsets
+    m_posTrainInstances = new ReferenceInstances(m_trainInstances,
+						 m_trainInstances.numInstances());
+    m_negTrainInstances = new ReferenceInstances(m_trainInstances,
+						 m_trainInstances.numInstances());
+    for (Enumeration e = m_trainInstances.enumerateInstances(); e.hasMoreElements(); ) {
+      Instance inst = (Instance) e.nextElement();
+      if ((int) inst.classValue() == 0)
+	m_negTrainInstances.addReference(inst); // belongs in negative class
+      else
+	m_posTrainInstances.addReference(inst); // belongs in positive class
+    }
+    m_posTrainInstances.compactify();
+    m_negTrainInstances.compactify();
+
+    // create the root prediction node
+    double rootPredictionValue = calcPredictionValue(m_posTrainInstances,
+						     m_negTrainInstances);
+    m_root = new PredictionNode(rootPredictionValue);
+
+    // pre-adjust weights
+    updateWeights(m_posTrainInstances, m_negTrainInstances, rootPredictionValue);
+    
+    // pre-calculate what we can
+    generateAttributeIndicesSingle();
+  }
+
+  /**
+   * Performs one iteration.
+   * 
+   * @param iteration the index of the current iteration (0-based)
+   * @exception Exception if this iteration fails 
+   */  
+  public void next(int iteration) throws Exception {
+
+    boost();
+  }
+
+  /**
+   * Performs a single boosting iteration, using two-class optimized method.
+   * Will add a new splitter node and two prediction nodes to the tree
+   * (unless merging takes place).
+   *
+   * @exception Exception if try to boost without setting up tree first or there are no 
+   * instances to train with
+   */
+  public void boost() throws Exception {
+
+    if (m_trainInstances == null || m_trainInstances.numInstances() == 0)
+      throw new Exception("Trying to boost with no training data");
+
+    // perform the search
+    searchForBestTestSingle();
+
+    if (m_search_bestSplitter == null) return; // handle empty instances
+
+    // create the new nodes for the tree, updating the weights
+    for (int i=0; i<2; i++) {
+      Instances posInstances =
+	m_search_bestSplitter.instancesDownBranch(i, m_search_bestPathPosInstances);
+      Instances negInstances =
+	m_search_bestSplitter.instancesDownBranch(i, m_search_bestPathNegInstances);
+      double predictionValue = calcPredictionValue(posInstances, negInstances);
+      PredictionNode newPredictor = new PredictionNode(predictionValue);
+      updateWeights(posInstances, negInstances, predictionValue);
+      m_search_bestSplitter.setChildForBranch(i, newPredictor);
+    }
+
+    // insert the new nodes
+    m_search_bestInsertionNode.addChild((Splitter) m_search_bestSplitter, this);
+
+    // free memory
+    m_search_bestPathPosInstances = null;
+    m_search_bestPathNegInstances = null;
+    m_search_bestSplitter = null;
+  }
+
+  /**
+   * Generates the m_nominalAttIndices and m_numericAttIndices arrays to index
+   * the respective attribute types in the training data.
+   *
+   */
+  private void generateAttributeIndicesSingle() {
+
+    // insert indices into vectors
+    FastVector nominalIndices = new FastVector();
+    FastVector numericIndices = new FastVector();
+
+    for (int i=0; i<m_trainInstances.numAttributes(); i++) {
+      if (i != m_trainInstances.classIndex()) {
+	if (m_trainInstances.attribute(i).isNumeric())
+	  numericIndices.addElement(new Integer(i));
+	else
+	  nominalIndices.addElement(new Integer(i));
+      }
+    }
+
+    // create nominal array
+    m_nominalAttIndices = new int[nominalIndices.size()];
+    for (int i=0; i<nominalIndices.size(); i++)
+      m_nominalAttIndices[i] = ((Integer)nominalIndices.elementAt(i)).intValue();
+    
+    // create numeric array
+    m_numericAttIndices = new int[numericIndices.size()];
+    for (int i=0; i<numericIndices.size(); i++)
+      m_numericAttIndices[i] = ((Integer)numericIndices.elementAt(i)).intValue();
+  }
+
+  /**
+   * Performs a search for the best test (splitter) to add to the tree, by aiming to
+   * minimize the Z value.
+   *
+   * @exception Exception if search fails
+   */
+  private void searchForBestTestSingle() throws Exception {
+
+    // keep track of total weight for efficient wRemainder calculations
+    m_trainTotalWeight = m_trainInstances.sumOfWeights();
+    
+    m_search_smallestZ = Double.POSITIVE_INFINITY;
+    searchForBestTestSingle(m_root, m_posTrainInstances, m_negTrainInstances);
+  }
+
+  /**
+   * Recursive function that carries out search for the best test (splitter) to add to
+   * this part of the tree, by aiming to minimize the Z value. Performs Z-pure cutoff to
+   * reduce search space.
+   *
+   * @param currentNode the root of the subtree to be searched, and the current node 
+   * being considered as parent of a new split
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void searchForBestTestSingle(PredictionNode currentNode,
+				       Instances posInstances, Instances negInstances)
+    throws Exception {
+
+    // don't investigate pure or empty nodes any further
+    if (posInstances.numInstances() == 0 || negInstances.numInstances() == 0) return;
+
+    // do z-pure cutoff
+    if (calcZpure(posInstances, negInstances) >= m_search_smallestZ) return;
+
+    // keep stats
+    m_nodesExpanded++;
+    m_examplesCounted += posInstances.numInstances() + negInstances.numInstances();
+
+    // evaluate static splitters (nominal)
+    for (int i=0; i<m_nominalAttIndices.length; i++)
+      evaluateNominalSplitSingle(m_nominalAttIndices[i], currentNode,
+				 posInstances, negInstances);
+
+    // evaluate dynamic splitters (numeric)
+    if (m_numericAttIndices.length > 0) {
+
+      // merge the two sets of instances into one
+      Instances allInstances = new Instances(posInstances);
+      for (Enumeration e = negInstances.enumerateInstances(); e.hasMoreElements(); )
+	allInstances.add((Instance) e.nextElement());
+    
+      // use method of finding the optimal Z split-point
+      for (int i=0; i<m_numericAttIndices.length; i++)
+	evaluateNumericSplitSingle(m_numericAttIndices[i], currentNode,
+				   posInstances, negInstances, allInstances);
+    }
+
+    if (currentNode.getChildren().size() == 0) return;
+
+    // keep searching
+    switch (m_searchPath) {
+    case SEARCHPATH_ALL:
+      goDownAllPathsSingle(currentNode, posInstances, negInstances);
+      break;
+    case SEARCHPATH_HEAVIEST: 
+      goDownHeaviestPathSingle(currentNode, posInstances, negInstances);
+      break;
+    case SEARCHPATH_ZPURE: 
+      goDownZpurePathSingle(currentNode, posInstances, negInstances);
+      break;
+    case SEARCHPATH_RANDOM: 
+      goDownRandomPathSingle(currentNode, posInstances, negInstances);
+      break;
+    }
+  }
+
+  /**
+   * Continues single (two-class optimized) search by investigating every node in the
+   * subtree under currentNode.
+   *
+   * @param currentNode the root of the subtree to be searched
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void goDownAllPathsSingle(PredictionNode currentNode,
+				    Instances posInstances, Instances negInstances)
+    throws Exception {
+
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      for (int i=0; i<split.getNumOfBranches(); i++)
+	searchForBestTestSingle(split.getChildForBranch(i),
+				split.instancesDownBranch(i, posInstances),
+				split.instancesDownBranch(i, negInstances));
+    }
+  }
+
+  /**
+   * Continues single (two-class optimized) search by investigating only the path
+   * with the most heavily weighted instances.
+   *
+   * @param currentNode the root of the subtree to be searched
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void goDownHeaviestPathSingle(PredictionNode currentNode,
+					Instances posInstances, Instances negInstances)
+    throws Exception {
+
+    Splitter heaviestSplit = null;
+    int heaviestBranch = 0;
+    double largestWeight = 0.0;
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      for (int i=0; i<split.getNumOfBranches(); i++) {
+	double weight =
+	  split.instancesDownBranch(i, posInstances).sumOfWeights() +
+	  split.instancesDownBranch(i, negInstances).sumOfWeights();
+	if (weight > largestWeight) {
+	  heaviestSplit = split;
+	  heaviestBranch = i;
+	  largestWeight = weight;
+	}
+      }
+    }
+    if (heaviestSplit != null)
+      searchForBestTestSingle(heaviestSplit.getChildForBranch(heaviestBranch),
+			      heaviestSplit.instancesDownBranch(heaviestBranch,
+								posInstances),
+			      heaviestSplit.instancesDownBranch(heaviestBranch,
+								negInstances));
+  }
+
+  /**
+   * Continues single (two-class optimized) search by investigating only the path
+   * with the best Z-pure value at each branch.
+   *
+   * @param currentNode the root of the subtree to be searched
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void goDownZpurePathSingle(PredictionNode currentNode,
+				     Instances posInstances, Instances negInstances)
+    throws Exception {
+
+    double lowestZpure = m_search_smallestZ; // do z-pure cutoff
+    PredictionNode bestPath = null;
+    Instances bestPosSplit = null, bestNegSplit = null;
+
+    // search for branch with lowest Z-pure
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      for (int i=0; i<split.getNumOfBranches(); i++) {
+	Instances posSplit = split.instancesDownBranch(i, posInstances);
+	Instances negSplit = split.instancesDownBranch(i, negInstances);
+	double newZpure = calcZpure(posSplit, negSplit);
+	if (newZpure < lowestZpure) {
+	  lowestZpure = newZpure;
+	  bestPath = split.getChildForBranch(i);
+	  bestPosSplit = posSplit;
+	  bestNegSplit = negSplit;
+	}
+      }
+    }
+
+    if (bestPath != null)
+      searchForBestTestSingle(bestPath, bestPosSplit, bestNegSplit);
+  }
+
+  /**
+   * Continues single (two-class optimized) search by investigating a random path.
+   *
+   * @param currentNode the root of the subtree to be searched
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void goDownRandomPathSingle(PredictionNode currentNode,
+				      Instances posInstances, Instances negInstances)
+    throws Exception {
+
+    FastVector children = currentNode.getChildren();
+    Splitter split = (Splitter) children.elementAt(getRandom(children.size()));
+    int branch = getRandom(split.getNumOfBranches());
+    searchForBestTestSingle(split.getChildForBranch(branch),
+			    split.instancesDownBranch(branch, posInstances),
+			    split.instancesDownBranch(branch, negInstances));
+  }
+
+  /**
+   * Investigates the option of introducing a nominal split under currentNode. If it
+   * finds a split that has a Z-value lower than has already been found it will
+   * update the search information to record this as the best option so far. 
+   *
+   * @param attIndex index of a nominal attribute to create a split from
+   * @param currentNode the parent under which a split is to be considered
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   */
+  private void evaluateNominalSplitSingle(int attIndex, PredictionNode currentNode,
+					  Instances posInstances, Instances negInstances)
+  {
+    
+    double[] indexAndZ = findLowestZNominalSplit(posInstances, negInstances, attIndex);
+
+    if (indexAndZ[1] < m_search_smallestZ) {
+      m_search_smallestZ = indexAndZ[1];
+      m_search_bestInsertionNode = currentNode;
+      m_search_bestSplitter = new TwoWayNominalSplit(attIndex, (int) indexAndZ[0]);
+      m_search_bestPathPosInstances = posInstances;
+      m_search_bestPathNegInstances = negInstances;
+    }
+  }
+
+  /**
+   * Investigates the option of introducing a two-way numeric split under currentNode.
+   * If it finds a split that has a Z-value lower than has already been found it will
+   * update the search information to record this as the best option so far. 
+   *
+   * @param attIndex index of a numeric attribute to create a split from
+   * @param currentNode the parent under which a split is to be considered
+   * @param posInstances the positive-class instances that apply at this node
+   * @param negInstances the negative-class instances that apply at this node
+   * @param allInstances all of the instances the apply at this node (pos+neg combined)
+   * @throws Exception in case of an error
+   */
+  private void evaluateNumericSplitSingle(int attIndex, PredictionNode currentNode,
+					  Instances posInstances, Instances negInstances,
+					  Instances allInstances)
+    throws Exception {
+    
+    double[] splitAndZ = findLowestZNumericSplit(allInstances, attIndex);
+
+    if (splitAndZ[1] < m_search_smallestZ) {
+      m_search_smallestZ = splitAndZ[1];
+      m_search_bestInsertionNode = currentNode;
+      m_search_bestSplitter = new TwoWayNumericSplit(attIndex, splitAndZ[0]);
+      m_search_bestPathPosInstances = posInstances;
+      m_search_bestPathNegInstances = negInstances;
+    }
+  }
+
+  /**
+   * Calculates the prediction value used for a particular set of instances.
+   *
+   * @param posInstances the positive-class instances
+   * @param negInstances the negative-class instances
+   * @return the prediction value
+   */
+  private double calcPredictionValue(Instances posInstances, Instances negInstances) {
+    
+    return 0.5 * Math.log( (posInstances.sumOfWeights() + 1.0)
+			  / (negInstances.sumOfWeights() + 1.0) );
+  }
+
+  /**
+   * Calculates the Z-pure value for a particular set of instances.
+   *
+   * @param posInstances the positive-class instances
+   * @param negInstances the negative-class instances
+   * @return the Z-pure value
+   */
+  private double calcZpure(Instances posInstances, Instances negInstances) {
+    
+    double posWeight = posInstances.sumOfWeights();
+    double negWeight = negInstances.sumOfWeights();
+    return (2.0 * (Math.sqrt(posWeight+1.0) + Math.sqrt(negWeight+1.0))) + 
+      (m_trainTotalWeight - (posWeight + negWeight));
+  }
+
+  /**
+   * Updates the weights of instances that are influenced by a new prediction value.
+   *
+   * @param posInstances positive-class instances to which the prediction value applies
+   * @param negInstances negative-class instances to which the prediction value applies
+   * @param predictionValue the new prediction value
+   */
+  private void updateWeights(Instances posInstances, Instances negInstances,
+			     double predictionValue) {
+    
+    // do positives
+    double weightMultiplier = Math.pow(Math.E, -predictionValue);
+    for (Enumeration e = posInstances.enumerateInstances(); e.hasMoreElements(); ) {
+      Instance inst = (Instance) e.nextElement();
+      inst.setWeight(inst.weight() * weightMultiplier);
+    }
+    // do negatives
+    weightMultiplier = Math.pow(Math.E, predictionValue);
+    for (Enumeration e = negInstances.enumerateInstances(); e.hasMoreElements(); ) {
+      Instance inst = (Instance) e.nextElement();
+      inst.setWeight(inst.weight() * weightMultiplier);
+    }
+  }
+
+  /**
+   * Finds the nominal attribute value to split on that results in the lowest Z-value.
+   *
+   * @param posInstances the positive-class instances to split
+   * @param negInstances the negative-class instances to split
+   * @param attIndex the index of the nominal attribute to find a split for
+   * @return a double array, index[0] contains the value to split on, index[1] contains
+   * the Z-value of the split
+   */
+  private double[] findLowestZNominalSplit(Instances posInstances, Instances negInstances,
+					   int attIndex)
+  {
+    
+    double lowestZ = Double.MAX_VALUE;
+    int bestIndex = 0;
+
+    // set up arrays
+    double[] posWeights = attributeValueWeights(posInstances, attIndex);
+    double[] negWeights = attributeValueWeights(negInstances, attIndex);
+    double posWeight = Utils.sum(posWeights);
+    double negWeight = Utils.sum(negWeights);
+
+    int maxIndex = posWeights.length;
+    if (maxIndex == 2) maxIndex = 1; // avoid repeating due to 2-way symmetry
+
+    for (int i = 0; i < maxIndex; i++) {
+      // calculate Z
+      double w1 = posWeights[i] + 1.0;
+      double w2 = negWeights[i] + 1.0;
+      double w3 = posWeight - w1 + 2.0;
+      double w4 = negWeight - w2 + 2.0;
+      double wRemainder = m_trainTotalWeight + 4.0 - (w1 + w2 + w3 + w4);
+      double newZ = (2.0 * (Math.sqrt(w1 * w2) + Math.sqrt(w3 * w4))) + wRemainder;
+
+      // record best option
+      if (newZ < lowestZ) { 
+	lowestZ = newZ;
+	bestIndex = i;
+      }
+    }
+
+    // return result
+    double[] indexAndZ = new double[2];
+    indexAndZ[0] = (double) bestIndex;
+    indexAndZ[1] = lowestZ;
+    return indexAndZ; 
+  }
+
+  /**
+   * Simultanously sum the weights of all attribute values for all instances.
+   *
+   * @param instances the instances to get the weights from 
+   * @param attIndex index of the attribute to be evaluated
+   * @return a double array containing the weight of each attribute value
+   */    
+  private double[] attributeValueWeights(Instances instances, int attIndex)
+  {
+    
+    double[] weights = new double[instances.attribute(attIndex).numValues()];
+    for(int i = 0; i < weights.length; i++) weights[i] = 0.0;
+
+    for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+      Instance inst = (Instance) e.nextElement();
+      if (!inst.isMissing(attIndex)) weights[(int)inst.value(attIndex)] += inst.weight();
+    }
+    return weights;
+  }
+
+  /**
+   * Finds the numeric split-point that results in the lowest Z-value.
+   *
+   * @param instances the instances to find a split for
+   * @param attIndex the index of the numeric attribute to find a split for
+   * @return a double array, index[0] contains the split-point, index[1] contains the
+   * Z-value of the split
+   * @throws Exception in case of an error
+   */
+  private double[] findLowestZNumericSplit(Instances instances, int attIndex)
+    throws Exception {
+    
+    double splitPoint = 0.0;
+    double bestVal = Double.MAX_VALUE, currVal, currCutPoint;
+    int numMissing = 0;
+    double[][] distribution = new double[3][instances.numClasses()];   
+
+    // compute counts for all the values
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Instance inst = instances.instance(i);
+      if (!inst.isMissing(attIndex)) {
+	distribution[1][(int)inst.classValue()] += inst.weight();
+      } else {
+	distribution[2][(int)inst.classValue()] += inst.weight();
+	numMissing++;
+      }
+    }
+
+    // sort instances
+    instances.sort(attIndex);
+    
+    // make split counts for each possible split and evaluate
+    for (int i = 0; i < instances.numInstances() - (numMissing + 1); i++) {
+      Instance inst = instances.instance(i);
+      Instance instPlusOne = instances.instance(i + 1);
+      distribution[0][(int)inst.classValue()] += inst.weight();
+      distribution[1][(int)inst.classValue()] -= inst.weight();
+      if (Utils.sm(inst.value(attIndex), instPlusOne.value(attIndex))) {
+	currCutPoint = (inst.value(attIndex) + instPlusOne.value(attIndex)) / 2.0;
+	currVal = conditionedZOnRows(distribution);
+	if (currVal < bestVal) {
+	  splitPoint = currCutPoint;
+	  bestVal = currVal;
+	}
+      }
+    }
+	
+    double[] splitAndZ = new double[2];
+    splitAndZ[0] = splitPoint;
+    splitAndZ[1] = bestVal;
+    return splitAndZ;
+  }
+
+  /**
+   * Calculates the Z-value from the rows of a weight distribution array.
+   *
+   * @param distribution the weight distribution
+   * @return the Z-value
+   */
+  private double conditionedZOnRows(double [][] distribution) {
+    
+    double w1 = distribution[0][0] + 1.0;
+    double w2 = distribution[0][1] + 1.0;
+    double w3 = distribution[1][0] + 1.0; 
+    double w4 = distribution[1][1] + 1.0;
+    double wRemainder = m_trainTotalWeight + 4.0 - (w1 + w2 + w3 + w4);
+    return (2.0 * (Math.sqrt(w1 * w2) + Math.sqrt(w3 * w4))) + wRemainder;
+  }
+
+  /**
+   * Returns the class probability distribution for an instance.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution the tree generates for the instance
+   */
+  public double[] distributionForInstance(Instance instance) {
+    
+    double predVal = predictionValueForInstance(instance, m_root, 0.0);
+    
+    double[] distribution = new double[2];
+    distribution[0] = 1.0 / (1.0 + Math.pow(Math.E, predVal));
+    distribution[1] = 1.0 / (1.0 + Math.pow(Math.E, -predVal));
+
+    return distribution;
+  }
+
+  /**
+   * Returns the class prediction value (vote) for an instance.
+   *
+   * @param inst the instance
+   * @param currentNode the root of the tree to get the values from
+   * @param currentValue the current value before adding the value contained in the
+   * subtree
+   * @return the class prediction value (vote)
+   */
+  protected double predictionValueForInstance(Instance inst, PredictionNode currentNode,
+					    double currentValue) {
+    
+    currentValue += currentNode.getValue();
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      int branch = split.branchInstanceGoesDown(inst);
+      if (branch >= 0)
+	currentValue = predictionValueForInstance(inst, split.getChildForBranch(branch),
+						  currentValue);
+    }
+    return currentValue;
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a string containing a description of the classifier
+   */
+  public String toString() {
+    
+    if (m_root == null)
+      return ("ADTree not built yet");
+    else {
+      return ("Alternating decision tree:\n\n" + toString(m_root, 1) +
+	      "\nLegend: " + legend() +
+	      "\nTree size (total number of nodes): " + numOfAllNodes(m_root) + 
+	      "\nLeaves (number of predictor nodes): " + numOfPredictionNodes(m_root)
+	      );
+    }
+  }
+
+  /**
+   * Traverses the tree, forming a string that describes it.
+   *
+   * @param currentNode the current node under investigation
+   * @param level the current level in the tree
+   * @return the string describing the subtree
+   */      
+  protected String toString(PredictionNode currentNode, int level) {
+    
+    StringBuffer text = new StringBuffer();
+    
+    text.append(": " + Utils.doubleToString(currentNode.getValue(),3));
+    
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+	    
+      for (int j=0; j<split.getNumOfBranches(); j++) {
+	PredictionNode child = split.getChildForBranch(j);
+	if (child != null) {
+	  text.append("\n");
+	  for (int k = 0; k < level; k++) {
+	    text.append("|  ");
+	  }
+	  text.append("(" + split.orderAdded + ")");
+	  text.append(split.attributeString(m_trainInstances) + " "
+		      + split.comparisonString(j, m_trainInstances));
+	  text.append(toString(child, level + 1));
+	}
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph of the tree in dotty format
+   * @exception Exception if something goes wrong
+   */
+  public String graph() throws Exception {
+    
+    StringBuffer text = new StringBuffer();
+    text.append("digraph ADTree {\n");
+    graphTraverse(m_root, text, 0, 0, m_trainInstances);
+    return text.toString() +"}\n";
+  }
+
+  /**
+   * Traverses the tree, graphing each node.
+   *
+   * @param currentNode the currentNode under investigation
+   * @param text the string built so far
+   * @param splitOrder the order the parent splitter was added to the tree
+   * @param predOrder the order this predictor was added to the split
+   * @param instances the data to work on
+   * @exception Exception if something goes wrong
+   */       
+  protected void graphTraverse(PredictionNode currentNode, StringBuffer text,
+			       int splitOrder, int predOrder, Instances instances)
+    throws Exception {
+    
+    text.append("S" + splitOrder + "P" + predOrder + " [label=\"");
+    text.append(Utils.doubleToString(currentNode.getValue(),3));
+    if (splitOrder == 0) // show legend in root
+      text.append(" (" + legend() + ")");
+    text.append("\" shape=box style=filled");
+    if (instances.numInstances() > 0) text.append(" data=\n" + instances + "\n,\n");
+    text.append("]\n");
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      text.append("S" + splitOrder + "P" + predOrder + "->" + "S" + split.orderAdded +
+		  " [style=dotted]\n");
+      text.append("S" + split.orderAdded + " [label=\"" + split.orderAdded + ": " +
+		  split.attributeString(m_trainInstances) + "\"]\n");
+
+      for (int i=0; i<split.getNumOfBranches(); i++) {
+	PredictionNode child = split.getChildForBranch(i);
+	if (child != null) {
+	  text.append("S" + split.orderAdded + "->" + "S" + split.orderAdded + "P" + i +
+		      " [label=\"" + split.comparisonString(i, m_trainInstances) + "\"]\n");
+	  graphTraverse(child, text, split.orderAdded, i,
+			split.instancesDownBranch(i, instances));
+	}
+      }
+    }  
+  }
+
+  /**
+   * Returns the legend of the tree, describing how results are to be interpreted.
+   *
+   * @return a string containing the legend of the classifier
+   */
+  public String legend() {
+    
+    Attribute classAttribute = null;
+    if (m_trainInstances == null) return "";
+    try {classAttribute = m_trainInstances.classAttribute();} catch (Exception x){};
+    return ("-ve = " + classAttribute.value(0) +
+	    ", +ve = " + classAttribute.value(1));
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numOfBoostingIterationsTipText() {
+
+    return "Sets the number of boosting iterations to perform. You will need to manually "
+      + "tune this parameter to suit the dataset and the desired complexity/accuracy "
+      + "tradeoff. More boosting iterations will result in larger (potentially more "
+      + " accurate) trees, but will make learning slower. Each iteration will add 3 nodes "
+      + "(1 split + 2 prediction) to the tree unless merging occurs.";
+  }
+
+  /**
+   * Gets the number of boosting iterations.
+   *
+   * @return the number of boosting iterations
+   */
+  public int getNumOfBoostingIterations() {
+    
+    return m_boostingIterations;
+  }
+
+  /**
+   * Sets the number of boosting iterations.
+   *
+   * @param b the number of boosting iterations to use
+   */
+  public void setNumOfBoostingIterations(int b) {
+    
+    m_boostingIterations = b; 
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchPathTipText() {
+
+    return "Sets the type of search to perform when building the tree. The default option"
+      + " (Expand all paths) will do an exhaustive search. The other search methods are"
+      + " heuristic, so they are not guaranteed to find an optimal solution but they are"
+      + " much faster. Expand the heaviest path: searches the path with the most heavily"
+      + " weighted instances. Expand the best z-pure path: searches the path determined"
+      + " by the best z-pure estimate. Expand a random path: the fastest method, simply"
+      + " searches down a single random path on each iteration.";
+  }
+
+  /**
+   * Gets the method of searching the tree for a new insertion. Will be one of
+   * SEARCHPATH_ALL, SEARCHPATH_HEAVIEST, SEARCHPATH_ZPURE, SEARCHPATH_RANDOM.
+   *
+   * @return the tree searching mode
+   */
+  public SelectedTag getSearchPath() {
+
+    return new SelectedTag(m_searchPath, TAGS_SEARCHPATH);
+  }
+  
+  /**
+   * Sets the method of searching the tree for a new insertion. Will be one of
+   * SEARCHPATH_ALL, SEARCHPATH_HEAVIEST, SEARCHPATH_ZPURE, SEARCHPATH_RANDOM.
+   *
+   * @param newMethod the new tree searching mode
+   */
+  public void setSearchPath(SelectedTag newMethod) {
+    
+    if (newMethod.getTags() == TAGS_SEARCHPATH) {
+      m_searchPath = newMethod.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+
+    return "Sets the random seed to use for a random search.";
+  }
+
+  /**
+   * Gets random seed for a random walk.
+   *
+   * @return the random seed
+   */
+  public int getRandomSeed() {
+    
+    return m_randomSeed;
+  }
+
+  /**
+   * Sets random seed for a random walk.
+   *
+   * @param seed the random seed
+   */
+  public void setRandomSeed(int seed) {
+    
+    // the actual random object is created when the tree is initialized
+    m_randomSeed = seed; 
+  }  
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String saveInstanceDataTipText() {
+
+    return "Sets whether the tree is to save instance data - the model will take up more"
+      + " memory if it does. If enabled you will be able to visualize the instances at"
+      + " the prediction nodes when visualizing the tree.";
+  }
+
+  /**
+   * Gets whether the tree is to save instance data.
+   *
+   * @return the random seed
+   */
+  public boolean getSaveInstanceData() {
+    
+    return m_saveInstanceData;
+  }
+
+  /**
+   * Sets whether the tree is to save instance data.
+   * 
+   * @param v true then the tree saves instance data
+   */
+  public void setSaveInstanceData(boolean v) {
+    
+    m_saveInstanceData = v;
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(3);
+    newVector.addElement(new Option(
+				    "\tNumber of boosting iterations.\n"
+				    +"\t(Default = 10)",
+				    "B", 1,"-B <number of boosting iterations>"));
+    newVector.addElement(new Option(
+				    "\tExpand nodes: -3(all), -2(weight), -1(z_pure), "
+				    +">=0 seed for random walk\n"
+				    +"\t(Default = -3)",
+				    "E", 1,"-E <-3|-2|-1|>=0>"));
+    newVector.addElement(new Option(
+				    "\tSave the instance data with the model",
+				    "D", 0,"-D"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -B num <br>
+   * Set the number of boosting iterations
+   * (default 10) <p>
+   *
+   * -E num <br>
+   * Set the nodes to expand: -3(all), -2(weight), -1(z_pure), >=0 seed for random walk
+   * (default -3) <p>
+   *
+   * -D <br>
+   * Save the instance data with the model <p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String bString = Utils.getOption('B', options);
+    if (bString.length() != 0) setNumOfBoostingIterations(Integer.parseInt(bString));
+
+    String eString = Utils.getOption('E', options);
+    if (eString.length() != 0) {
+      int value = Integer.parseInt(eString);
+      if (value >= 0) {
+	setSearchPath(new SelectedTag(SEARCHPATH_RANDOM, TAGS_SEARCHPATH));
+	setRandomSeed(value);
+      } else setSearchPath(new SelectedTag(value + 3, TAGS_SEARCHPATH));
+    }
+
+    setSaveInstanceData(Utils.getFlag('D', options));
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of ADTree.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    
+    String[] options = new String[6];
+    int current = 0;
+    options[current++] = "-B"; options[current++] = "" + getNumOfBoostingIterations();
+    options[current++] = "-E"; options[current++] = "" +
+				 (m_searchPath == SEARCHPATH_RANDOM ?
+				  m_randomSeed : m_searchPath - 3);
+    if (getSaveInstanceData()) options[current++] = "-D";
+    while (current < options.length) options[current++] = "";
+    return options;
+  }
+
+  /**
+   * Calls measure function for tree size - the total number of nodes.
+   *
+   * @return the tree size
+   */
+  public double measureTreeSize() {
+    
+    return numOfAllNodes(m_root);
+  }
+
+  /**
+   * Calls measure function for leaf size - the number of prediction nodes.
+   *
+   * @return the leaf size
+   */
+  public double measureNumLeaves() {
+    
+    return numOfPredictionNodes(m_root);
+  }
+
+  /**
+   * Calls measure function for prediction leaf size - the number of 
+   * prediction nodes without children.
+   *
+   * @return the leaf size
+   */
+  public double measureNumPredictionLeaves() {
+    
+    return numOfPredictionLeafNodes(m_root);
+  }
+
+  /**
+   * Returns the number of nodes expanded.
+   *
+   * @return the number of nodes expanded during search
+   */
+  public double measureNodesExpanded() {
+    
+    return m_nodesExpanded;
+  }
+
+  /**
+   * Returns the number of examples "counted".
+   *
+   * @return the number of nodes processed during search
+   */
+
+  public double measureExamplesProcessed() {
+    
+    return m_examplesCounted;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names.
+   *
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    
+    Vector newVector = new Vector(4);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureNumPredictionLeaves");
+    newVector.addElement("measureNodesExpanded");
+    newVector.addElement("measureExamplesProcessed");
+    return newVector.elements();
+  }
+ 
+  /**
+   * Returns the value of the named measure.
+   *
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @exception IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    
+    if (additionalMeasureName.equalsIgnoreCase("measureTreeSize")) {
+      return measureTreeSize();
+    }
+    else if (additionalMeasureName.equalsIgnoreCase("measureNumLeaves")) {
+      return measureNumLeaves();
+    }
+    else if (additionalMeasureName.equalsIgnoreCase("measureNumPredictionLeaves")) {
+      return measureNumPredictionLeaves();
+    }
+    else if (additionalMeasureName.equalsIgnoreCase("measureNodesExpanded")) {
+      return measureNodesExpanded();
+    }
+    else if (additionalMeasureName.equalsIgnoreCase("measureExamplesProcessed")) {
+      return measureExamplesProcessed();
+    }
+    else {throw new IllegalArgumentException(additionalMeasureName 
+			      + " not supported (ADTree)");
+    }
+  }
+
+  /**
+   * Returns the total number of nodes in a tree.
+   *
+   * @param root the root of the tree being measured
+   * @return tree size in number of splitter + prediction nodes
+   */       
+  protected int numOfAllNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root != null) {
+      numSoFar++;
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	numSoFar++;
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfAllNodes(split.getChildForBranch(i));
+      }
+    }
+    return numSoFar;
+  }
+
+  /**
+   * Returns the number of prediction nodes in a tree.
+   *
+   * @param root the root of the tree being measured
+   * @return tree size in number of prediction nodes
+   */       
+  protected int numOfPredictionNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root != null) {
+      numSoFar++;
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfPredictionNodes(split.getChildForBranch(i));
+      }
+    }
+    return numSoFar;
+  }
+
+  /**
+   * Returns the number of leaf nodes in a tree - prediction nodes without
+   * children.
+   *
+   * @param root the root of the tree being measured
+   * @return tree leaf size in number of prediction nodes
+   */       
+  protected int numOfPredictionLeafNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root.getChildren().size() > 0) {
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfPredictionLeafNodes(split.getChildForBranch(i));
+      }
+    } else numSoFar = 1;
+    return numSoFar;
+  }
+
+  /**
+   * Gets the next random value.
+   *
+   * @param max the maximum value (+1) to be returned
+   * @return the next random value (between 0 and max-1)
+   */
+  protected int getRandom(int max) {
+    
+    return m_random.nextInt(max);
+  }
+
+  /**
+   * Returns the next number in the order that splitter nodes have been added to
+   * the tree, and records that a new splitter has been added.
+   *
+   * @return the next number in the order
+   */
+  public int nextSplitAddedOrder() {
+
+    return ++m_lastAddedSplitNum;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds a classifier for a set of instances.
+   *
+   * @param instances the instances to train the classifier with
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+
+    // set up the tree
+    initClassifier(instances);
+
+    // build the tree
+    for (int T = 0; T < m_boostingIterations; T++) boost();
+
+    // clean up if desired
+    if (!m_saveInstanceData) done();
+  }
+
+  /**
+   * Frees memory that is no longer needed for a final model - will no longer be able
+   * to increment the classifier after calling this.
+   *
+   */
+  public void done() {
+
+    m_trainInstances = new Instances(m_trainInstances, 0);
+    m_random = null; 
+    m_numericAttIndices = null;
+    m_nominalAttIndices = null;
+    m_posTrainInstances = null;
+    m_negTrainInstances = null;
+  }
+
+  /**
+   * Creates a clone that is identical to the current tree, but is independent.
+   * Deep copies the essential elements such as the tree nodes, and the instances
+   * (because the weights change.) Reference copies several elements such as the
+   * potential splitter sets, assuming that such elements should never differ between
+   * clones.
+   *
+   * @return the clone
+   */
+  public Object clone() {
+    
+    ADTree clone = new ADTree();
+
+    if (m_root != null) { // check for initialization first
+      clone.m_root = (PredictionNode) m_root.clone(); // deep copy the tree
+
+      clone.m_trainInstances = new Instances(m_trainInstances); // copy training instances
+      
+      // deep copy the random object
+      if (m_random != null) { 
+	SerializedObject randomSerial = null;
+	try {
+	  randomSerial = new SerializedObject(m_random);
+	} catch (Exception ignored) {} // we know that Random is serializable
+	clone.m_random = (Random) randomSerial.getObject();
+      }
+
+      clone.m_lastAddedSplitNum = m_lastAddedSplitNum;
+      clone.m_numericAttIndices = m_numericAttIndices;
+      clone.m_nominalAttIndices = m_nominalAttIndices;
+      clone.m_trainTotalWeight = m_trainTotalWeight;
+
+      // reconstruct pos/negTrainInstances references
+      if (m_posTrainInstances != null) { 
+	clone.m_posTrainInstances =
+	  new ReferenceInstances(m_trainInstances, m_posTrainInstances.numInstances());
+	clone.m_negTrainInstances =
+	  new ReferenceInstances(m_trainInstances, m_negTrainInstances.numInstances());
+	for (Enumeration e = clone.m_trainInstances.enumerateInstances();
+	     e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  try { // ignore classValue() exception
+	    if ((int) inst.classValue() == 0)
+	      clone.m_negTrainInstances.addReference(inst); // belongs in negative class
+	    else
+	      clone.m_posTrainInstances.addReference(inst); // belongs in positive class
+	  } catch (Exception ignored) {} 
+	}
+      }
+    }
+    clone.m_nodesExpanded = m_nodesExpanded;
+    clone.m_examplesCounted = m_examplesCounted;
+    clone.m_boostingIterations = m_boostingIterations;
+    clone.m_searchPath = m_searchPath;
+    clone.m_randomSeed = m_randomSeed;
+
+    return clone;
+  }
+
+  /**
+   * Merges two trees together. Modifies the tree being acted on, leaving tree passed
+   * as a parameter untouched (cloned). Does not check to see whether training instances
+   * are compatible - strange things could occur if they are not.
+   *
+   * @param mergeWith the tree to merge with
+   * @exception Exception if merge could not be performed
+   */
+  public void merge(ADTree mergeWith) throws Exception {
+    
+    if (m_root == null || mergeWith.m_root == null)
+      throw new Exception("Trying to merge an uninitialized tree");
+    m_root.merge(mergeWith.m_root, this);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new ADTree(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/BFTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/BFTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/BFTree.java	(revision 29)
@@ -0,0 +1,2592 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BFTree.java
+ * Copyright (C) 2007 Haijian Shi
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.Matrix;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building a best-first decision tree classifier. This class uses binary split for both nominal and numeric attributes. For missing values, the method of 'fractional' instances is used.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Haijian Shi (2007). Best-first decision tree learning. Hamilton, NZ.<br/>
+ * <br/>
+ * Jerome Friedman, Trevor Hastie, Robert Tibshirani (2000). Additive logistic regression : A statistical view of boosting. Annals of statistics. 28(2):337-407.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;mastersthesis{Shi2007,
+ *    address = {Hamilton, NZ},
+ *    author = {Haijian Shi},
+ *    note = {COMP594},
+ *    school = {University of Waikato},
+ *    title = {Best-first decision tree learning},
+ *    year = {2007}
+ * }
+ * 
+ * &#64;article{Friedman2000,
+ *    author = {Jerome Friedman and Trevor Hastie and Robert Tibshirani},
+ *    journal = {Annals of statistics},
+ *    number = {2},
+ *    pages = {337-407},
+ *    title = {Additive logistic regression : A statistical view of boosting},
+ *    volume = {28},
+ *    year = {2000},
+ *    ISSN = {0090-5364}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -P &lt;UNPRUNED|POSTPRUNED|PREPRUNED&gt;
+ *  The pruning strategy.
+ *  (default: POSTPRUNED)</pre>
+ * 
+ * <pre> -M &lt;min no&gt;
+ *  The minimal number of instances at the terminal nodes.
+ *  (default 2)</pre>
+ * 
+ * <pre> -N &lt;num folds&gt;
+ *  The number of folds used in the pruning.
+ *  (default 5)</pre>
+ * 
+ * <pre> -H
+ *  Don't use heuristic search for nominal attributes in multi-class
+ *  problem (default yes).
+ * </pre>
+ * 
+ * <pre> -G
+ *  Don't use Gini index for splitting (default yes),
+ *  if not information is used.</pre>
+ * 
+ * <pre> -R
+ *  Don't use error rate in internal cross-validation (default yes), 
+ *  but root mean squared error.</pre>
+ * 
+ * <pre> -A
+ *  Use the 1 SE rule to make pruning decision.
+ *  (default no).</pre>
+ * 
+ * <pre> -C
+ *  Percentage of training data size (0-1]
+ *  (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Haijian Shi (hs69@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class BFTree
+  extends RandomizableClassifier
+  implements AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** For serialization.	 */
+  private static final long serialVersionUID = -7035607375962528217L;
+
+  /** pruning strategy: un-pruned */
+  public static final int PRUNING_UNPRUNED = 0;
+  /** pruning strategy: post-pruning */
+  public static final int PRUNING_POSTPRUNING = 1;
+  /** pruning strategy: pre-pruning */
+  public static final int PRUNING_PREPRUNING = 2;
+  /** pruning strategy */
+  public static final Tag[] TAGS_PRUNING = {
+    new Tag(PRUNING_UNPRUNED, "unpruned", "Un-pruned"),
+    new Tag(PRUNING_POSTPRUNING, "postpruned", "Post-pruning"),
+    new Tag(PRUNING_PREPRUNING, "prepruned", "Pre-pruning")
+  };
+  
+  /** the pruning strategy */
+  protected int m_PruningStrategy = PRUNING_POSTPRUNING;
+
+  /** Successor nodes. */
+  protected BFTree[] m_Successors;
+
+  /** Attribute used for splitting. */
+  protected Attribute m_Attribute;
+
+  /** Split point (for numeric attributes). */
+  protected double m_SplitValue;
+
+  /** Split subset (for nominal attributes). */
+  protected String m_SplitString;
+
+  /** Class value for a node. */
+  protected double m_ClassValue;
+
+  /** Class attribute of a dataset. */
+  protected Attribute m_ClassAttribute;
+
+  /** Minimum number of instances at leaf nodes. */
+  protected int m_minNumObj = 2;
+
+  /** Number of folds for the pruning. */
+  protected int m_numFoldsPruning = 5;
+
+  /** If the ndoe is leaf node. */
+  protected boolean m_isLeaf;
+
+  /** Number of expansions. */
+  protected static int m_Expansion;
+
+  /** Fixed number of expansions (if no pruning method is used, its value is -1. Otherwise,
+   *  its value is gotten from internal cross-validation).   */
+  protected int m_FixedExpansion = -1;
+
+  /** If use huristic search for binary split (default true). Note even if its value is true, it is only
+   * used when the number of values of a nominal attribute is larger than 4. */
+  protected boolean m_Heuristic = true;
+
+  /** If use Gini index as the splitting criterion - default (if not, information is used). */
+  protected boolean m_UseGini = true;
+
+  /** If use error rate in internal cross-validation to fix the number of expansions - default
+   *  (if not, root mean squared error is used). */
+  protected boolean m_UseErrorRate = true;
+
+  /** If use the 1SE rule to make the decision. */
+  protected boolean m_UseOneSE = false;
+
+  /** Class distributions.  */
+  protected double[] m_Distribution;
+
+  /** Branch proportions. */
+  protected double[] m_Props;
+
+  /** Sorted indices. */
+  protected int[][] m_SortedIndices;
+
+  /** Sorted weights. */
+  protected double[][] m_Weights;
+
+  /** Distributions of each attribute for two successor nodes. */
+  protected double[][][] m_Dists;
+
+  /** Class probabilities. */
+  protected double[] m_ClassProbs;
+
+  /** Total weights. */
+  protected double m_TotalWeight;
+
+  /** The training data size (0-1). Default 1. */
+  protected double m_SizePer = 1;
+
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return  
+        "Class for building a best-first decision tree classifier. "
+      + "This class uses binary split for both nominal and numeric attributes. "
+      + "For missing values, the method of 'fractional' instances is used.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.MASTERSTHESIS);
+    result.setValue(Field.AUTHOR, "Haijian Shi");
+    result.setValue(Field.YEAR, "2007");
+    result.setValue(Field.TITLE, "Best-first decision tree learning");
+    result.setValue(Field.SCHOOL, "University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, NZ");
+    result.setValue(Field.NOTE, "COMP594");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "Jerome Friedman and Trevor Hastie and Robert Tibshirani");
+    additional.setValue(Field.YEAR, "2000");
+    additional.setValue(Field.TITLE, "Additive logistic regression : A statistical view of boosting");
+    additional.setValue(Field.JOURNAL, "Annals of statistics");
+    additional.setValue(Field.VOLUME, "28");
+    additional.setValue(Field.NUMBER, "2");
+    additional.setValue(Field.PAGES, "337-407");
+    additional.setValue(Field.ISSN, "0090-5364");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   * 
+   * @return 		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Method for building a BestFirst decision tree classifier.
+   *
+   * @param data 	set of instances serving as training data
+   * @throws Exception 	if decision tree cannot be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    getCapabilities().testWithFail(data);
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    // build an unpruned tree
+    if (m_PruningStrategy == PRUNING_UNPRUNED) {
+
+      // calculate sorted indices, weights and initial class probabilities
+      int[][] sortedIndices = new int[data.numAttributes()][0];
+      double[][] weights = new double[data.numAttributes()][0];
+      double[] classProbs = new double[data.numClasses()];
+      double totalWeight = computeSortedInfo(data,sortedIndices, weights,classProbs);
+
+      // Compute information of the best split for this node (include split attribute,
+      // split value and gini gain (or information gain)). At the same time, compute
+      // variables dists, props and totalSubsetWeights.
+      double[][][] dists = new double[data.numAttributes()][2][data.numClasses()];
+      double[][] props = new double[data.numAttributes()][2];
+      double[][] totalSubsetWeights = new double[data.numAttributes()][2];
+      FastVector nodeInfo = computeSplitInfo(this, data, sortedIndices, weights, dists,
+	  props, totalSubsetWeights, m_Heuristic, m_UseGini);
+
+      // add the node (with all split info) into BestFirstElements
+      FastVector BestFirstElements = new FastVector();
+      BestFirstElements.addElement(nodeInfo);
+
+      // Make the best-first decision tree.
+      int attIndex = ((Attribute)nodeInfo.elementAt(1)).index();
+      m_Expansion = 0;
+      makeTree(BestFirstElements, data, sortedIndices, weights, dists, classProbs,
+	  totalWeight, props[attIndex] ,m_minNumObj, m_Heuristic, m_UseGini, m_FixedExpansion);
+
+      return;
+    }
+
+    // the following code is for pre-pruning and post-pruning methods
+
+    // Compute train data, test data, sorted indices, sorted weights, total weights,
+    // class probabilities, class distributions, branch proportions and total subset
+    // weights for root nodes of each fold for prepruning and postpruning.
+    int expansion = 0;
+
+    Random random = new Random(m_Seed);
+    Instances cvData = new Instances(data);
+    cvData.randomize(random);
+    cvData = new Instances(cvData,0,(int)(cvData.numInstances()*m_SizePer)-1);
+    cvData.stratify(m_numFoldsPruning);
+
+    Instances[] train = new Instances[m_numFoldsPruning];
+    Instances[] test = new Instances[m_numFoldsPruning];
+    FastVector[] parallelBFElements = new FastVector [m_numFoldsPruning];
+    BFTree[] m_roots = new BFTree[m_numFoldsPruning];
+
+    int[][][] sortedIndices = new int[m_numFoldsPruning][data.numAttributes()][0];
+    double[][][] weights = new double[m_numFoldsPruning][data.numAttributes()][0];
+    double[][] classProbs = new double[m_numFoldsPruning][data.numClasses()];
+    double[] totalWeight = new double[m_numFoldsPruning];
+
+    double[][][][] dists =
+      new double[m_numFoldsPruning][data.numAttributes()][2][data.numClasses()];
+    double[][][] props =
+      new double[m_numFoldsPruning][data.numAttributes()][2];
+    double[][][] totalSubsetWeights =
+      new double[m_numFoldsPruning][data.numAttributes()][2];
+    FastVector[] nodeInfo = new FastVector[m_numFoldsPruning];
+
+    for (int i = 0; i < m_numFoldsPruning; i++) {
+      train[i] = cvData.trainCV(m_numFoldsPruning, i);
+      test[i] = cvData.testCV(m_numFoldsPruning, i);
+      parallelBFElements[i] = new FastVector();
+      m_roots[i] = new BFTree();
+
+      // calculate sorted indices, weights, initial class counts and total weights for each training data
+      totalWeight[i] = computeSortedInfo(train[i],sortedIndices[i], weights[i],
+	  classProbs[i]);
+
+      // compute information of the best split for this node (include split attribute,
+      // split value and gini gain (or information gain)) in this fold
+      nodeInfo[i] = computeSplitInfo(m_roots[i], train[i], sortedIndices[i],
+	  weights[i], dists[i], props[i], totalSubsetWeights[i], m_Heuristic, m_UseGini);
+
+      // compute information for root nodes
+
+      int attIndex = ((Attribute)nodeInfo[i].elementAt(1)).index();
+
+      m_roots[i].m_SortedIndices = new int[sortedIndices[i].length][0];
+      m_roots[i].m_Weights = new double[weights[i].length][0];
+      m_roots[i].m_Dists = new double[dists[i].length][0][0];
+      m_roots[i].m_ClassProbs = new double[classProbs[i].length];
+      m_roots[i].m_Distribution = new double[classProbs[i].length];
+      m_roots[i].m_Props = new double[2];
+
+      for (int j=0; j<m_roots[i].m_SortedIndices.length; j++) {
+	m_roots[i].m_SortedIndices[j] = sortedIndices[i][j];
+	m_roots[i].m_Weights[j] = weights[i][j];
+	m_roots[i].m_Dists[j] = dists[i][j];
+      }
+
+      System.arraycopy(classProbs[i], 0, m_roots[i].m_ClassProbs, 0,
+	  classProbs[i].length);
+      if (Utils.sum(m_roots[i].m_ClassProbs)!=0)
+	Utils.normalize(m_roots[i].m_ClassProbs);
+
+      System.arraycopy(classProbs[i], 0, m_roots[i].m_Distribution, 0,
+	  classProbs[i].length);
+      System.arraycopy(props[i][attIndex], 0, m_roots[i].m_Props, 0,
+	  props[i][attIndex].length);
+
+      m_roots[i].m_TotalWeight = totalWeight[i];
+
+      parallelBFElements[i].addElement(nodeInfo[i]);
+    }
+
+    // build a pre-pruned tree
+    if (m_PruningStrategy == PRUNING_PREPRUNING) {
+
+      double previousError = Double.MAX_VALUE;
+      double currentError = previousError;
+      double minError = Double.MAX_VALUE;
+      int minExpansion = 0;
+      FastVector errorList = new FastVector();
+      while(true) {
+	// compute average error
+	double expansionError = 0;
+	int count = 0;
+
+	for (int i=0; i<m_numFoldsPruning; i++) {
+	  Evaluation eval;
+
+	  // calculate error rate if only root node
+	  if (expansion==0) {
+	    m_roots[i].m_isLeaf = true;
+	    eval = new Evaluation(test[i]);
+	    eval.evaluateModel(m_roots[i], test[i]);
+	    if (m_UseErrorRate) expansionError += eval.errorRate();
+	    else expansionError += eval.rootMeanSquaredError();
+	    count ++;
+	  }
+
+	  // make tree - expand one node at a time
+	  else {
+	    if (m_roots[i] == null) continue; // if the tree cannot be expanded, go to next fold
+	    m_roots[i].m_isLeaf = false;
+	    BFTree nodeToSplit = (BFTree)
+	    (((FastVector)(parallelBFElements[i].elementAt(0))).elementAt(0));
+	    if (!m_roots[i].makeTree(parallelBFElements[i], m_roots[i], train[i],
+		nodeToSplit.m_SortedIndices, nodeToSplit.m_Weights,
+		nodeToSplit.m_Dists, nodeToSplit.m_ClassProbs,
+		nodeToSplit.m_TotalWeight, nodeToSplit.m_Props, m_minNumObj,
+		m_Heuristic, m_UseGini)) {
+	      m_roots[i] = null; // cannot be expanded
+	      continue;
+	    }
+	    eval = new Evaluation(test[i]);
+	    eval.evaluateModel(m_roots[i], test[i]);
+	    if (m_UseErrorRate) expansionError += eval.errorRate();
+	    else expansionError += eval.rootMeanSquaredError();
+	    count ++;
+	  }
+	}
+
+	// no tree can be expanded any more
+	if (count==0) break;
+
+	expansionError /=count;
+	errorList.addElement(new Double(expansionError));
+	currentError = expansionError;
+
+	if (!m_UseOneSE) {
+	  if (currentError>previousError)
+	    break;
+	}
+
+	else {
+	  if (expansionError < minError) {
+	    minError = expansionError;
+	    minExpansion = expansion;
+	  }
+
+	  if (currentError>previousError) {
+	    double oneSE = Math.sqrt(minError*(1-minError)/
+		data.numInstances());
+	    if (currentError > minError + oneSE) {
+	      break;
+	    }
+	  }
+	}
+
+	expansion ++;
+	previousError = currentError;
+      }
+
+      if (!m_UseOneSE) expansion = expansion - 1;
+      else {
+	double oneSE = Math.sqrt(minError*(1-minError)/data.numInstances());
+	for (int i=0; i<errorList.size(); i++) {
+	  double error = ((Double)(errorList.elementAt(i))).doubleValue();
+	  if (error<=minError + oneSE) { // && counts[i]>=m_numFoldsPruning/2) {
+	    expansion = i;
+	    break;
+	  }
+	}
+      }
+    }
+
+    // build a postpruned tree
+    else {
+      FastVector[] modelError = new FastVector[m_numFoldsPruning];
+
+      // calculate error of each expansion for each fold
+      for (int i = 0; i < m_numFoldsPruning; i++) {
+	modelError[i] = new FastVector();
+
+	m_roots[i].m_isLeaf = true;
+	Evaluation eval = new Evaluation(test[i]);
+	eval.evaluateModel(m_roots[i], test[i]);
+	double error;
+	if (m_UseErrorRate) error = eval.errorRate();
+	else error = eval.rootMeanSquaredError();
+	modelError[i].addElement(new Double(error));
+
+	m_roots[i].m_isLeaf = false;
+	BFTree nodeToSplit = (BFTree)
+	(((FastVector)(parallelBFElements[i].elementAt(0))).elementAt(0));
+
+	m_roots[i].makeTree(parallelBFElements[i], m_roots[i], train[i], test[i],
+	    modelError[i],nodeToSplit.m_SortedIndices, nodeToSplit.m_Weights,
+	    nodeToSplit.m_Dists, nodeToSplit.m_ClassProbs,
+	    nodeToSplit.m_TotalWeight, nodeToSplit.m_Props, m_minNumObj,
+	    m_Heuristic, m_UseGini, m_UseErrorRate);
+	m_roots[i] = null;
+      }
+
+      // find the expansion with minimal error rate
+      double minError = Double.MAX_VALUE;
+
+      int maxExpansion = modelError[0].size();
+      for (int i=1; i<modelError.length; i++) {
+	if (modelError[i].size()>maxExpansion)
+	  maxExpansion = modelError[i].size();
+      }
+
+      double[] error = new double[maxExpansion];
+      int[] counts = new int[maxExpansion];
+      for (int i=0; i<maxExpansion; i++) {
+	counts[i] = 0;
+	error[i] = 0;
+	for (int j=0; j<m_numFoldsPruning; j++) {
+	  if (i<modelError[j].size()) {
+	    error[i] += ((Double)modelError[j].elementAt(i)).doubleValue();
+	    counts[i]++;
+	  }
+	}
+	error[i] = error[i]/counts[i]; //average error for each expansion
+
+	if (error[i]<minError) {// && counts[i]>=m_numFoldsPruning/2) {
+	  minError = error[i];
+	  expansion = i;
+	}
+      }
+
+      // the 1 SE rule choosen
+      if (m_UseOneSE) {
+	double oneSE = Math.sqrt(minError*(1-minError)/
+	    data.numInstances());
+	for (int i=0; i<maxExpansion; i++) {
+	  if (error[i]<=minError + oneSE) { // && counts[i]>=m_numFoldsPruning/2) {
+	    expansion = i;
+	    break;
+	  }
+	}
+      }
+    }
+
+    // make tree on all data based on the expansion caculated
+    // from cross-validation
+
+    // calculate sorted indices, weights and initial class counts
+    int[][] prune_sortedIndices = new int[data.numAttributes()][0];
+    double[][] prune_weights = new double[data.numAttributes()][0];
+    double[] prune_classProbs = new double[data.numClasses()];
+    double prune_totalWeight = computeSortedInfo(data, prune_sortedIndices,
+	prune_weights, prune_classProbs);
+
+    // compute information of the best split for this node (include split attribute,
+    // split value and gini gain)
+    double[][][] prune_dists = new double[data.numAttributes()][2][data.numClasses()];
+    double[][] prune_props = new double[data.numAttributes()][2];
+    double[][] prune_totalSubsetWeights = new double[data.numAttributes()][2];
+    FastVector prune_nodeInfo = computeSplitInfo(this, data, prune_sortedIndices,
+	prune_weights, prune_dists, prune_props, prune_totalSubsetWeights, m_Heuristic,m_UseGini);
+
+    // add the root node (with its split info) to BestFirstElements
+    FastVector BestFirstElements = new FastVector();
+    BestFirstElements.addElement(prune_nodeInfo);
+
+    int attIndex = ((Attribute)prune_nodeInfo.elementAt(1)).index();
+    m_Expansion = 0;
+    makeTree(BestFirstElements, data, prune_sortedIndices, prune_weights, prune_dists,
+	prune_classProbs, prune_totalWeight, prune_props[attIndex] ,m_minNumObj,
+	m_Heuristic, m_UseGini, expansion);
+  }
+
+  /**
+   * Recursively build a best-first decision tree.
+   * Method for building a Best-First tree for a given number of expansions.
+   * preExpasion is -1 means that no expansion is specified (just for a
+   * tree without any pruning method). Pre-pruning and post-pruning methods also
+   * use this method to build the final tree on all training data based on the
+   * expansion calculated from internal cross-validation.
+   *
+   * @param BestFirstElements 	list to store BFTree nodes
+   * @param data 		training data
+   * @param sortedIndices 	sorted indices of the instances
+   * @param weights 		weights of the instances
+   * @param dists 		class distributions for each attribute
+   * @param classProbs 		class probabilities of this node
+   * @param totalWeight 	total weight of this node (note if the node 
+   * 				can not split, this value is not calculated.)
+   * @param branchProps 	proportions of two subbranches
+   * @param minNumObj 		minimal number of instances at leaf nodes
+   * @param useHeuristic 	if use heuristic search for nominal attributes 
+   * 				in multi-class problem
+   * @param useGini 		if use Gini index as splitting criterion
+   * @param preExpansion 	the number of expansions the tree to be expanded
+   * @throws Exception 		if something goes wrong
+   */
+  protected void makeTree(FastVector BestFirstElements,Instances data,
+      int[][] sortedIndices, double[][] weights, double[][][] dists,
+      double[] classProbs, double totalWeight, double[] branchProps,
+      int minNumObj, boolean useHeuristic, boolean useGini, int preExpansion)
+  	throws Exception {
+
+    if (BestFirstElements.size()==0) return;
+
+    ///////////////////////////////////////////////////////////////////////
+    // All information about the node to split (the first BestFirst object in
+    // BestFirstElements)
+    FastVector firstElement = (FastVector)BestFirstElements.elementAt(0);
+
+    // split attribute
+    Attribute att = (Attribute)firstElement.elementAt(1);
+
+    // info of split value or split string
+    double splitValue = Double.NaN;
+    String splitStr = null;
+    if (att.isNumeric())
+      splitValue = ((Double)firstElement.elementAt(2)).doubleValue();
+    else {
+      splitStr=((String)firstElement.elementAt(2)).toString();
+    }
+
+    // the best gini gain or information gain of this node
+    double gain = ((Double)firstElement.elementAt(3)).doubleValue();
+    ///////////////////////////////////////////////////////////////////////
+
+    if (m_ClassProbs==null) {
+      m_SortedIndices = new int[sortedIndices.length][0];
+      m_Weights = new double[weights.length][0];
+      m_Dists = new double[dists.length][0][0];
+      m_ClassProbs = new double[classProbs.length];
+      m_Distribution = new double[classProbs.length];
+      m_Props = new double[2];
+
+      for (int i=0; i<m_SortedIndices.length; i++) {
+	m_SortedIndices[i] = sortedIndices[i];
+	m_Weights[i] = weights[i];
+	m_Dists[i] = dists[i];
+      }
+
+      System.arraycopy(classProbs, 0, m_ClassProbs, 0, classProbs.length);
+      System.arraycopy(classProbs, 0, m_Distribution, 0, classProbs.length);
+      System.arraycopy(branchProps, 0, m_Props, 0, m_Props.length);
+      m_TotalWeight = totalWeight;
+      if (Utils.sum(m_ClassProbs)!=0) Utils.normalize(m_ClassProbs);
+    }
+
+    // If no enough data or this node can not be split, find next node to split.
+    if (totalWeight < 2*minNumObj || branchProps[0]==0
+	|| branchProps[1]==0) {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+
+      makeLeaf(data);
+      if (BestFirstElements.size()!=0) {
+	FastVector nextSplitElement = (FastVector)BestFirstElements.elementAt(0);
+	BFTree nextSplitNode = (BFTree)nextSplitElement.elementAt(0);
+	nextSplitNode.makeTree(BestFirstElements,data,
+	    nextSplitNode.m_SortedIndices, nextSplitNode.m_Weights,
+	    nextSplitNode.m_Dists,
+	    nextSplitNode.m_ClassProbs, nextSplitNode.m_TotalWeight,
+	    nextSplitNode.m_Props, minNumObj, useHeuristic, useGini, preExpansion);
+      }
+      return;
+    }
+
+    // If gini gain or information gain is 0, make all nodes in the BestFirstElements leaf nodes
+    // because these nodes are sorted descendingly according to gini gain or information gain.
+    // (namely, gini gain or information gain of all nodes in BestFirstEelements is 0).
+    if (gain==0 || preExpansion==m_Expansion) {
+      for (int i=0; i<BestFirstElements.size(); i++) {
+	FastVector element = (FastVector)BestFirstElements.elementAt(i);
+	BFTree node = (BFTree)element.elementAt(0);
+	node.makeLeaf(data);
+      }
+      BestFirstElements.removeAllElements();
+    }
+
+    // gain is not 0
+    else {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+
+      m_Attribute = att;
+      if (m_Attribute.isNumeric()) m_SplitValue = splitValue;
+      else m_SplitString = splitStr;
+
+      int[][][] subsetIndices = new int[2][data.numAttributes()][0];
+      double[][][] subsetWeights = new double[2][data.numAttributes()][0];
+
+      splitData(subsetIndices, subsetWeights, m_Attribute, m_SplitValue,
+	  m_SplitString, sortedIndices, weights, data);
+
+      // If split will generate node(s) which has total weights less than m_minNumObj,
+      // do not split.
+      int attIndex = att.index();
+      if (subsetIndices[0][attIndex].length<minNumObj ||
+	  subsetIndices[1][attIndex].length<minNumObj) {
+	makeLeaf(data);
+      }
+
+      // split the node
+      else {
+	m_isLeaf = false;
+	m_Attribute = att;
+
+	// if expansion is specified (if pruning method used)
+	if (    (m_PruningStrategy == PRUNING_PREPRUNING) 
+	     || (m_PruningStrategy == PRUNING_POSTPRUNING)
+	     || (preExpansion != -1)) 
+	  m_Expansion++;
+
+	makeSuccessors(BestFirstElements,data,subsetIndices,subsetWeights,dists,
+	    att,useHeuristic, useGini);
+      }
+
+      // choose next node to split
+      if (BestFirstElements.size()!=0) {
+	FastVector nextSplitElement = (FastVector)BestFirstElements.elementAt(0);
+	BFTree nextSplitNode = (BFTree)nextSplitElement.elementAt(0);
+	nextSplitNode.makeTree(BestFirstElements,data,
+	    nextSplitNode.m_SortedIndices, nextSplitNode.m_Weights,
+	    nextSplitNode.m_Dists,
+	    nextSplitNode.m_ClassProbs, nextSplitNode.m_TotalWeight,
+	    nextSplitNode.m_Props, minNumObj, useHeuristic, useGini, preExpansion);
+      }
+
+    }
+  }
+
+  /**
+   * This method is to find the number of expansions based on internal 
+   * cross-validation for just pre-pruning. It expands the first BestFirst 
+   * node in the BestFirstElements if it is expansible, otherwise it looks 
+   * for next exapansible node. If it finds a node is expansibel, expand the 
+   * node, then return true. (note it just expands one node at a time).
+   *
+   * @param BestFirstElements 	list to store BFTree nodes
+   * @param root 		root node of tree in each fold
+   * @param train 		training data
+   * @param sortedIndices 	sorted indices of the instances
+   * @param weights 		weights of the instances
+   * @param dists 		class distributions for each attribute
+   * @param classProbs 		class probabilities of this node
+   * @param totalWeight 	total weight of this node (note if the node 
+   * 				can not split, this value is not calculated.)
+   * @param branchProps 	proportions of two subbranches
+   * @param minNumObj 	minimal number of instances at leaf nodes
+   * @param useHeuristic 	if use heuristic search for nominal attributes 
+   * 				in multi-class problem
+   * @param useGini 		if use Gini index as splitting criterion
+   * @return true 		if expand successfully, otherwise return false 
+   * 				(all nodes in BestFirstElements cannot be 
+   * 				expanded).
+   * @throws Exception 		if something goes wrong
+   */
+  protected boolean makeTree(FastVector BestFirstElements, BFTree root,
+      Instances train, int[][] sortedIndices, double[][] weights,
+      double[][][] dists, double[] classProbs, double totalWeight,
+      double[] branchProps, int minNumObj, boolean useHeuristic, boolean useGini)
+  throws Exception {
+
+    if (BestFirstElements.size()==0) return false;
+
+    ///////////////////////////////////////////////////////////////////////
+    // All information about the node to split (first BestFirst object in
+    // BestFirstElements)
+    FastVector firstElement = (FastVector)BestFirstElements.elementAt(0);
+
+    // node to split
+    BFTree nodeToSplit = (BFTree)firstElement.elementAt(0);
+
+    // split attribute
+    Attribute att = (Attribute)firstElement.elementAt(1);
+
+    // info of split value or split string
+    double splitValue = Double.NaN;
+    String splitStr = null;
+    if (att.isNumeric())
+      splitValue = ((Double)firstElement.elementAt(2)).doubleValue();
+    else {
+      splitStr=((String)firstElement.elementAt(2)).toString();
+    }
+
+    // the best gini gain or information gain of this node
+    double gain = ((Double)firstElement.elementAt(3)).doubleValue();
+    ///////////////////////////////////////////////////////////////////////
+
+    // If no enough data to split for this node or this node can not be split find next node to split.
+    if (totalWeight < 2*minNumObj || branchProps[0]==0
+	|| branchProps[1]==0) {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+      nodeToSplit.makeLeaf(train);
+      BFTree nextNode = (BFTree)
+      ((FastVector)BestFirstElements.elementAt(0)).elementAt(0);
+      return root.makeTree(BestFirstElements, root, train,
+	  nextNode.m_SortedIndices, nextNode.m_Weights, nextNode.m_Dists,
+	  nextNode.m_ClassProbs, nextNode.m_TotalWeight,
+	  nextNode.m_Props, minNumObj, useHeuristic, useGini);
+    }
+
+    // If gini gain or information is 0, make all nodes in the BestFirstElements leaf nodes
+    // because these node sorted descendingly according to gini gain or information gain.
+    // (namely, gini gain or information gain of all nodes in BestFirstEelements is 0).
+    if (gain==0) {
+      for (int i=0; i<BestFirstElements.size(); i++) {
+	FastVector element = (FastVector)BestFirstElements.elementAt(i);
+	BFTree node = (BFTree)element.elementAt(0);
+	node.makeLeaf(train);
+      }
+      BestFirstElements.removeAllElements();
+      return false;
+    }
+
+    else {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+      nodeToSplit.m_Attribute = att;
+      if (att.isNumeric()) nodeToSplit.m_SplitValue = splitValue;
+      else nodeToSplit.m_SplitString = splitStr;
+
+      int[][][] subsetIndices = new int[2][train.numAttributes()][0];
+      double[][][] subsetWeights = new double[2][train.numAttributes()][0];
+
+      splitData(subsetIndices, subsetWeights, nodeToSplit.m_Attribute,
+	  nodeToSplit.m_SplitValue, nodeToSplit.m_SplitString,
+	  nodeToSplit.m_SortedIndices, nodeToSplit.m_Weights, train);
+
+      // if split will generate node(s) which has total weights less than m_minNumObj,
+      // do not split
+      int attIndex = att.index();
+      if (subsetIndices[0][attIndex].length<minNumObj ||
+	  subsetIndices[1][attIndex].length<minNumObj) {
+
+	nodeToSplit.makeLeaf(train);
+	BFTree nextNode = (BFTree)
+	((FastVector)BestFirstElements.elementAt(0)).elementAt(0);
+	return root.makeTree(BestFirstElements, root, train,
+	    nextNode.m_SortedIndices, nextNode.m_Weights, nextNode.m_Dists,
+	    nextNode.m_ClassProbs, nextNode.m_TotalWeight,
+	    nextNode.m_Props, minNumObj, useHeuristic, useGini);
+      }
+
+      // split the node
+      else {
+	nodeToSplit.m_isLeaf = false;
+	nodeToSplit.m_Attribute = att;
+
+	nodeToSplit.makeSuccessors(BestFirstElements,train,subsetIndices,
+	    subsetWeights,dists, nodeToSplit.m_Attribute,useHeuristic,useGini);
+
+	for (int i=0; i<2; i++){
+	  nodeToSplit.m_Successors[i].makeLeaf(train);
+	}
+
+	return true;
+      }
+    }
+  }
+
+  /**
+   * This method is to find the number of expansions based on internal 
+   * cross-validation for just post-pruning. It expands the first BestFirst 
+   * node in the BestFirstElements until no node can be split. When building 
+   * the tree, stroe error for each temporary tree, namely for each expansion.
+   *
+   * @param BestFirstElements 	list to store BFTree nodes
+   * @param root 		root node of tree in each fold
+   * @param train 		training data in each fold
+   * @param test 		test data in each fold
+   * @param modelError 		list to store error for each expansion in 
+   * 				each fold
+   * @param sortedIndices 	sorted indices of the instances
+   * @param weights 		weights of the instances
+   * @param dists 		class distributions for each attribute
+   * @param classProbs 		class probabilities of this node
+   * @param totalWeight 	total weight of this node (note if the node 
+   * 				can not split, this value is not calculated.)
+   * @param branchProps 	proportions of two subbranches
+   * @param minNumObj 		minimal number of instances at leaf nodes
+   * @param useHeuristic 	if use heuristic search for nominal attributes 
+   * 				in multi-class problem
+   * @param useGini 		if use Gini index as splitting criterion
+   * @param useErrorRate 	if use error rate in internal cross-validation
+   * @throws Exception 		if something goes wrong
+   */
+  protected void makeTree(FastVector BestFirstElements, BFTree root,
+      Instances train, Instances test, FastVector modelError, int[][] sortedIndices,
+      double[][] weights, double[][][] dists, double[] classProbs, double totalWeight,
+      double[] branchProps, int minNumObj, boolean useHeuristic, boolean useGini, boolean useErrorRate)
+  throws Exception {
+
+    if (BestFirstElements.size()==0) return;
+
+    ///////////////////////////////////////////////////////////////////////
+    // All information about the node to split (first BestFirst object in
+    // BestFirstElements)
+    FastVector firstElement = (FastVector)BestFirstElements.elementAt(0);
+
+    // node to split
+    //BFTree nodeToSplit = (BFTree)firstElement.elementAt(0);
+
+    // split attribute
+    Attribute att = (Attribute)firstElement.elementAt(1);
+
+    // info of split value or split string
+    double splitValue = Double.NaN;
+    String splitStr = null;
+    if (att.isNumeric())
+      splitValue = ((Double)firstElement.elementAt(2)).doubleValue();
+    else {
+      splitStr=((String)firstElement.elementAt(2)).toString();
+    }
+
+    // the best gini gain or information of this node
+    double gain = ((Double)firstElement.elementAt(3)).doubleValue();
+    ///////////////////////////////////////////////////////////////////////
+
+    if (totalWeight < 2*minNumObj || branchProps[0]==0
+	|| branchProps[1]==0) {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+      makeLeaf(train);
+      if (BestFirstElements.size() == 0) {
+        return;
+      }
+
+      BFTree nextSplitNode = (BFTree)
+      ((FastVector)BestFirstElements.elementAt(0)).elementAt(0);
+      nextSplitNode.makeTree(BestFirstElements, root, train, test, modelError,
+	  nextSplitNode.m_SortedIndices, nextSplitNode.m_Weights,
+	  nextSplitNode.m_Dists, nextSplitNode.m_ClassProbs,
+	  nextSplitNode.m_TotalWeight, nextSplitNode.m_Props, minNumObj,
+	  useHeuristic, useGini, useErrorRate);
+      return;
+
+    }
+
+    // If gini gain or information gain is 0, make all nodes in the BestFirstElements leaf nodes
+    // because these node sorted descendingly according to gini gain or information gain.
+    // (namely, gini gain or information gain of all nodes in BestFirstEelements is 0).
+    if (gain==0) {
+      for (int i=0; i<BestFirstElements.size(); i++) {
+	FastVector element = (FastVector)BestFirstElements.elementAt(i);
+	BFTree node = (BFTree)element.elementAt(0);
+	node.makeLeaf(train);
+      }
+      BestFirstElements.removeAllElements();
+    }
+
+    // gini gain or information gain is not 0
+    else {
+      // remove the first element
+      BestFirstElements.removeElementAt(0);
+      m_Attribute = att;
+      if (att.isNumeric()) m_SplitValue = splitValue;
+      else m_SplitString = splitStr;
+
+      int[][][] subsetIndices = new int[2][train.numAttributes()][0];
+      double[][][] subsetWeights = new double[2][train.numAttributes()][0];
+
+      splitData(subsetIndices, subsetWeights, m_Attribute,
+	  m_SplitValue, m_SplitString,
+	  sortedIndices, weights, train);
+
+      // if split will generate node(s) which has total weights less than m_minNumObj,
+      // do not split
+      int attIndex = att.index();
+      if (subsetIndices[0][attIndex].length<minNumObj ||
+	  subsetIndices[1][attIndex].length<minNumObj) {
+	makeLeaf(train);
+      }
+
+      // split the node and cauculate error rate of this temporary tree
+      else {
+	m_isLeaf = false;
+	m_Attribute = att;
+
+	makeSuccessors(BestFirstElements,train,subsetIndices,
+	    subsetWeights,dists, m_Attribute, useHeuristic, useGini);
+	for (int i=0; i<2; i++){
+	  m_Successors[i].makeLeaf(train);
+	}
+
+	Evaluation eval = new Evaluation(test);
+	eval.evaluateModel(root, test);
+	double error;
+	if (useErrorRate) error = eval.errorRate();
+	else error = eval.rootMeanSquaredError();
+	modelError.addElement(new Double(error));
+      }
+
+      if (BestFirstElements.size()!=0) {
+	FastVector nextSplitElement = (FastVector)BestFirstElements.elementAt(0);
+	BFTree nextSplitNode = (BFTree)nextSplitElement.elementAt(0);
+	nextSplitNode.makeTree(BestFirstElements, root, train, test, modelError,
+	    nextSplitNode.m_SortedIndices, nextSplitNode.m_Weights,
+	    nextSplitNode.m_Dists, nextSplitNode.m_ClassProbs,
+	    nextSplitNode.m_TotalWeight, nextSplitNode.m_Props, minNumObj,
+	    useHeuristic, useGini,useErrorRate);
+      }
+    }
+  }
+
+
+  /**
+   * Generate successor nodes for a node and put them into BestFirstElements 
+   * according to gini gain or information gain in a descending order.
+   *
+   * @param BestFirstElements 	list to store BestFirst nodes
+   * @param data 		training instance
+   * @param subsetSortedIndices	sorted indices of instances of successor nodes
+   * @param subsetWeights 	weights of instances of successor nodes
+   * @param dists 		class distributions of successor nodes
+   * @param att 		attribute used to split the node
+   * @param useHeuristic 	if use heuristic search for nominal attributes in multi-class problem
+   * @param useGini 		if use Gini index as splitting criterion
+   * @throws Exception 		if something goes wrong 
+   */
+  protected void makeSuccessors(FastVector BestFirstElements,Instances data,
+      int[][][] subsetSortedIndices, double[][][] subsetWeights,
+      double[][][] dists,
+      Attribute att, boolean useHeuristic, boolean useGini) throws Exception {
+
+    m_Successors = new BFTree[2];
+
+    for (int i=0; i<2; i++) {
+      m_Successors[i] = new BFTree();
+      m_Successors[i].m_isLeaf = true;
+
+      // class probability and distribution for this successor node
+      m_Successors[i].m_ClassProbs = new double[data.numClasses()];
+      m_Successors[i].m_Distribution = new double[data.numClasses()];
+      System.arraycopy(dists[att.index()][i], 0, m_Successors[i].m_ClassProbs,
+	  0,m_Successors[i].m_ClassProbs.length);
+      System.arraycopy(dists[att.index()][i], 0, m_Successors[i].m_Distribution,
+	  0,m_Successors[i].m_Distribution.length);
+      if (Utils.sum(m_Successors[i].m_ClassProbs)!=0)
+	Utils.normalize(m_Successors[i].m_ClassProbs);
+
+      // split information for this successor node
+      double[][] props = new double[data.numAttributes()][2];
+      double[][][] subDists = new double[data.numAttributes()][2][data.numClasses()];
+      double[][] totalSubsetWeights = new double[data.numAttributes()][2];
+      FastVector splitInfo = m_Successors[i].computeSplitInfo(m_Successors[i], data,
+	  subsetSortedIndices[i], subsetWeights[i], subDists, props,
+	  totalSubsetWeights, useHeuristic, useGini);
+
+      // branch proportion for this successor node
+      int splitIndex = ((Attribute)splitInfo.elementAt(1)).index();
+      m_Successors[i].m_Props = new double[2];
+      System.arraycopy(props[splitIndex], 0, m_Successors[i].m_Props, 0,
+	  m_Successors[i].m_Props.length);
+
+      // sorted indices and weights of each attribute for this successor node
+      m_Successors[i].m_SortedIndices = new int[data.numAttributes()][0];
+      m_Successors[i].m_Weights = new double[data.numAttributes()][0];
+      for (int j=0; j<m_Successors[i].m_SortedIndices.length; j++) {
+	m_Successors[i].m_SortedIndices[j] = subsetSortedIndices[i][j];
+	m_Successors[i].m_Weights[j] = subsetWeights[i][j];
+      }
+
+      // distribution of each attribute for this successor node
+      m_Successors[i].m_Dists = new double[data.numAttributes()][2][data.numClasses()];
+      for (int j=0; j<subDists.length; j++) {
+	m_Successors[i].m_Dists[j] = subDists[j];
+      }
+
+      // total weights for this successor node. 
+      m_Successors[i].m_TotalWeight = Utils.sum(totalSubsetWeights[splitIndex]);
+
+      // insert this successor node into BestFirstElements according to gini gain or information gain
+      //  descendingly
+      if (BestFirstElements.size()==0) {
+	BestFirstElements.addElement(splitInfo);
+      } else {
+	double gGain = ((Double)(splitInfo.elementAt(3))).doubleValue();
+	int vectorSize = BestFirstElements.size();
+	FastVector lastNode = (FastVector)BestFirstElements.elementAt(vectorSize-1);
+
+	// If gini gain is less than that of last node in FastVector
+	if (gGain<((Double)(lastNode.elementAt(3))).doubleValue()) {
+	  BestFirstElements.insertElementAt(splitInfo, vectorSize);
+	} else {
+	  for (int j=0; j<vectorSize; j++) {
+	    FastVector node = (FastVector)BestFirstElements.elementAt(j);
+	    double nodeGain = ((Double)(node.elementAt(3))).doubleValue();
+	    if (gGain>=nodeGain) {
+	      BestFirstElements.insertElementAt(splitInfo, j);
+	      break;
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Compute sorted indices, weights and class probabilities for a given 
+   * dataset. Return total weights of the data at the node.
+   * 
+   * @param data 		training data
+   * @param sortedIndices 	sorted indices of instances at the node
+   * @param weights 		weights of instances at the node
+   * @param classProbs 		class probabilities at the node
+   * @return 			total weights of instances at the node
+   * @throws Exception 		if something goes wrong
+   */
+  protected double computeSortedInfo(Instances data, int[][] sortedIndices, double[][] weights,
+      double[] classProbs) throws Exception {
+
+    // Create array of sorted indices and weights
+    double[] vals = new double[data.numInstances()];
+    for (int j = 0; j < data.numAttributes(); j++) {
+      if (j==data.classIndex()) continue;
+      weights[j] = new double[data.numInstances()];
+
+      if (data.attribute(j).isNominal()) {
+
+	// Handling nominal attributes. Putting indices of
+	// instances with missing values at the end.
+	sortedIndices[j] = new int[data.numInstances()];
+	int count = 0;
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  if (!inst.isMissing(j)) {
+	    sortedIndices[j][count] = i;
+	    weights[j][count] = inst.weight();
+	    count++;
+	  }
+	}
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  if (inst.isMissing(j)) {
+	    sortedIndices[j][count] = i;
+	    weights[j][count] = inst.weight();
+	    count++;
+	  }
+	}
+      } else {
+
+	// Sorted indices are computed for numeric attributes
+	// missing values instances are put to end (through Utils.sort() method)
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  vals[i] = inst.value(j);
+	}
+	sortedIndices[j] = Utils.sort(vals);
+	for (int i = 0; i < data.numInstances(); i++) {
+	  weights[j][i] = data.instance(sortedIndices[j][i]).weight();
+	}
+      }
+    }
+
+    // Compute initial class counts and total weight
+    double totalWeight = 0;
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance inst = data.instance(i);
+      classProbs[(int)inst.classValue()] += inst.weight();
+      totalWeight += inst.weight();
+    }
+
+    return totalWeight;
+  }
+
+  /**
+   * Compute the best splitting attribute, split point or subset and the best
+   * gini gain or iformation gain for a given dataset.
+   *
+   * @param node 		node to be split
+   * @param data 		training data
+   * @param sortedIndices 	sorted indices of the instances
+   * @param weights 		weights of the instances
+   * @param dists 		class distributions for each attribute
+   * @param props 		proportions of two branches
+   * @param totalSubsetWeights 	total weight of two subsets
+   * @param useHeuristic 	if use heuristic search for nominal attributes 
+   * 				in multi-class problem
+   * @param useGini 		if use Gini index as splitting criterion
+   * @return 			split information about the node
+   * @throws Exception 		if something is wrong
+   */
+  protected FastVector computeSplitInfo(BFTree node, Instances data, int[][] sortedIndices,
+      double[][] weights, double[][][] dists, double[][] props,
+      double[][] totalSubsetWeights, boolean useHeuristic, boolean useGini) throws Exception {
+
+    double[] splits = new double[data.numAttributes()];
+    String[] splitString = new String[data.numAttributes()];
+    double[] gains = new double[data.numAttributes()];
+
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i==data.classIndex()) continue;
+      Attribute att = data.attribute(i);
+      if (att.isNumeric()) {
+	// numeric attribute
+	splits[i] = numericDistribution(props, dists, att, sortedIndices[i],
+	    weights[i], totalSubsetWeights, gains, data, useGini);
+      } else {
+	// nominal attribute
+	splitString[i] = nominalDistribution(props, dists, att, sortedIndices[i],
+	    weights[i], totalSubsetWeights, gains, data, useHeuristic, useGini);
+      }
+    }
+
+    int index = Utils.maxIndex(gains);
+    double mBestGain = gains[index];
+
+    Attribute att = data.attribute(index);
+    double mValue =Double.NaN;
+    String mString = null;
+    if (att.isNumeric())  mValue= splits[index];
+    else {
+      mString = splitString[index];
+      if (mString==null) mString = "";
+    }
+
+    // split information
+    FastVector splitInfo = new FastVector();
+    splitInfo.addElement(node);
+    splitInfo.addElement(att);
+    if (att.isNumeric()) splitInfo.addElement(new Double(mValue));
+    else splitInfo.addElement(mString);
+    splitInfo.addElement(new Double(mBestGain));
+
+    return splitInfo;
+  }
+
+  /**
+   * Compute distributions, proportions and total weights of two successor nodes for 
+   * a given numeric attribute.
+   *
+   * @param props 		proportions of each two branches for each attribute
+   * @param dists 		class distributions of two branches for each attribute
+   * @param att 		numeric att split on
+   * @param sortedIndices 	sorted indices of instances for the attirubte
+   * @param weights 		weights of instances for the attirbute
+   * @param subsetWeights 	total weight of two branches split based on the attribute
+   * @param gains 		Gini gains or information gains for each attribute 
+   * @param data 		training instances
+   * @param useGini 		if use Gini index as splitting criterion
+   * @return 			Gini gain or information gain for the given attribute
+   * @throws Exception 		if something goes wrong
+   */
+  protected double numericDistribution(double[][] props, double[][][] dists,
+      Attribute att, int[] sortedIndices, double[] weights, double[][] subsetWeights,
+      double[] gains, Instances data, boolean useGini)
+  throws Exception {
+
+    double splitPoint = Double.NaN;
+    double[][] dist = null;
+    int numClasses = data.numClasses();
+    int i; // differ instances with or without missing values
+
+    double[][] currDist = new double[2][numClasses];
+    dist = new double[2][numClasses];
+
+    // Move all instances without missing values into second subset
+    double[] parentDist = new double[numClasses];
+    int missingStart = 0;
+    for (int j = 0; j < sortedIndices.length; j++) {
+      Instance inst = data.instance(sortedIndices[j]);
+      if (!inst.isMissing(att)) {
+	missingStart ++;
+	currDist[1][(int)inst.classValue()] += weights[j];
+      }
+      parentDist[(int)inst.classValue()] += weights[j];
+    }
+    System.arraycopy(currDist[1], 0, dist[1], 0, dist[1].length);
+
+    // Try all possible split points
+    double currSplit = data.instance(sortedIndices[0]).value(att);
+    double currGain;
+    double bestGain = -Double.MAX_VALUE;
+
+    for (i = 0; i < sortedIndices.length; i++) {
+      Instance inst = data.instance(sortedIndices[i]);
+      if (inst.isMissing(att)) {
+	break;
+      }
+      if (inst.value(att) > currSplit) {
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int k=0; k<2; k++) {
+	  //tempDist[k] = currDist[k];
+	  System.arraycopy(currDist[k], 0, tempDist[k], 0, tempDist[k].length);
+	}
+
+	double[] tempProps = new double[2];
+	for (int k=0; k<2; k++) {
+	  tempProps[k] = Utils.sum(tempDist[k]);
+	}
+
+	if (Utils.sum(tempProps) !=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int index = missingStart;
+	while (index < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[index]);
+	  for (int j = 0; j < 2; j++) {
+	    tempDist[j][(int)insta.classValue()] += tempProps[j] * weights[index];
+	  }
+	  index++;
+	}
+
+	if (useGini) currGain = computeGiniGain(parentDist,tempDist);
+	else currGain = computeInfoGain(parentDist,tempDist);
+
+	if (currGain > bestGain) {
+	  bestGain = currGain;
+	  // clean split point
+	  splitPoint = Math.rint((inst.value(att) + currSplit)/2.0*100000)/100000.0;
+	  for (int j = 0; j < currDist.length; j++) {
+	    System.arraycopy(tempDist[j], 0, dist[j], 0,
+		dist[j].length);
+	  }
+	}
+      }
+      currSplit = inst.value(att);
+      currDist[0][(int)inst.classValue()] += weights[i];
+      currDist[1][(int)inst.classValue()] -= weights[i];
+    }
+
+    // Compute weights
+    int attIndex = att.index();
+    props[attIndex] = new double[2];
+    for (int k = 0; k < 2; k++) {
+      props[attIndex][k] = Utils.sum(dist[k]);
+    }
+    if (Utils.sum(props[attIndex]) != 0) Utils.normalize(props[attIndex]);
+
+    // Compute subset weights
+    subsetWeights[attIndex] = new double[2];
+    for (int j = 0; j < 2; j++) {
+      subsetWeights[attIndex][j] += Utils.sum(dist[j]);
+    }
+
+    // clean gain
+    gains[attIndex] = Math.rint(bestGain*10000000)/10000000.0;
+    dists[attIndex] = dist;
+    return splitPoint;
+  }
+
+  /**
+   * Compute distributions, proportions and total weights of two successor 
+   * nodes for a given nominal attribute.
+   *
+   * @param props 		proportions of each two branches for each attribute
+   * @param dists 		class distributions of two branches for each attribute
+   * @param att 		numeric att split on
+   * @param sortedIndices 	sorted indices of instances for the attirubte
+   * @param weights 		weights of instances for the attirbute
+   * @param subsetWeights 	total weight of two branches split based on the attribute
+   * @param gains 		Gini gains for each attribute 
+   * @param data 		training instances
+   * @param useHeuristic 	if use heuristic search
+   * @param useGini 		if use Gini index as splitting criterion
+   * @return 			Gini gain for the given attribute
+   * @throws Exception 		if something goes wrong
+   */
+  protected String nominalDistribution(double[][] props, double[][][] dists,
+      Attribute att, int[] sortedIndices, double[] weights, double[][] subsetWeights,
+      double[] gains, Instances data, boolean useHeuristic, boolean useGini)
+  throws Exception {
+
+    String[] values = new String[att.numValues()];
+    int numCat = values.length; // number of values of the attribute
+    int numClasses = data.numClasses();
+
+    String bestSplitString = "";
+    double bestGain = -Double.MAX_VALUE;
+
+    // class frequency for each value
+    int[] classFreq = new int[numCat];
+    for (int j=0; j<numCat; j++) classFreq[j] = 0;
+
+    double[] parentDist = new double[numClasses];
+    double[][] currDist = new double[2][numClasses];
+    double[][] dist = new double[2][numClasses];
+    int missingStart = 0;
+
+    for (int i = 0; i < sortedIndices.length; i++) {
+      Instance inst = data.instance(sortedIndices[i]);
+      if (!inst.isMissing(att)) {
+	missingStart++;
+	classFreq[(int)inst.value(att)] ++;
+      }
+      parentDist[(int)inst.classValue()] += weights[i];
+    }
+
+    // count the number of values that class frequency is not 0
+    int nonEmpty = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]!=0) nonEmpty ++;
+    }
+
+    // attribute values which class frequency is not 0
+    String[] nonEmptyValues = new String[nonEmpty];
+    int nonEmptyIndex = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]!=0) {
+	nonEmptyValues[nonEmptyIndex] = att.value(j);
+	nonEmptyIndex ++;
+      }
+    }
+
+    // attribute values which class frequency is 0
+    int empty = numCat - nonEmpty;
+    String[] emptyValues = new String[empty];
+    int emptyIndex = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]==0) {
+	emptyValues[emptyIndex] = att.value(j);
+	emptyIndex ++;
+      }
+    }
+
+    if (nonEmpty<=1) {
+      gains[att.index()] = 0;
+      return "";
+    }
+
+    // for tow-class probloms
+    if (data.numClasses()==2) {
+
+      //// Firstly, for attribute values which class frequency is not zero
+
+      // probability of class 0 for each attribute value
+      double[] pClass0 = new double[nonEmpty];
+      // class distribution for each attribute value
+      double[][] valDist = new double[nonEmpty][2];
+
+      for (int j=0; j<nonEmpty; j++) {
+	for (int k=0; k<2; k++) {
+	  valDist[j][k] = 0;
+	}
+      }
+
+      for (int i = 0; i < sortedIndices.length; i++) {
+	Instance inst = data.instance(sortedIndices[i]);
+	if (inst.isMissing(att)) {
+	  break;
+	}
+
+	for (int j=0; j<nonEmpty; j++) {
+	  if (att.value((int)inst.value(att)).compareTo(nonEmptyValues[j])==0) {
+	    valDist[j][(int)inst.classValue()] += inst.weight();
+	    break;
+	  }
+	}
+      }
+
+      for (int j=0; j<nonEmpty; j++) {
+	double distSum = Utils.sum(valDist[j]);
+	if (distSum==0) pClass0[j]=0;
+	else pClass0[j] = valDist[j][0]/distSum;
+      }
+
+      // sort category according to the probability of class 0.0
+      String[] sortedValues = new String[nonEmpty];
+      for (int j=0; j<nonEmpty; j++) {
+	sortedValues[j] = nonEmptyValues[Utils.minIndex(pClass0)];
+	pClass0[Utils.minIndex(pClass0)] = Double.MAX_VALUE;
+      }
+
+      // Find a subset of attribute values that maximize impurity decrease
+
+      // for the attribute values that class frequency is not 0
+      String tempStr = "";
+
+      for (int j=0; j<nonEmpty-1; j++) {
+	currDist = new double[2][numClasses];
+	if (tempStr=="") tempStr="(" + sortedValues[j] + ")";
+	else tempStr += "|"+ "(" + sortedValues[j] + ")";
+	//System.out.println(sortedValues[j]);
+	for (int i=0; i<sortedIndices.length;i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf
+	      ("(" + att.value((int)inst.value(att)) + ")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[i];
+	  } else currDist[1][(int)inst.classValue()] += weights[i];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int kk=0; kk<2; kk++) {
+	  tempDist[kk] = currDist[kk];
+	}
+
+	double[] tempProps = new double[2];
+	for (int kk=0; kk<2; kk++) {
+	  tempProps[kk] = Utils.sum(tempDist[kk]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int mstart = missingStart;
+	while (mstart < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[mstart]);
+	  for (int jj = 0; jj < 2; jj++) {
+	    tempDist[jj][(int)insta.classValue()] += tempProps[jj] * weights[mstart];
+	  }
+	  mstart++;
+	}
+
+	double currGain;
+	if (useGini) currGain = computeGiniGain(parentDist,tempDist);
+	else currGain = computeInfoGain(parentDist,tempDist);
+
+	if (currGain>bestGain) {
+	  bestGain = currGain;
+	  bestSplitString = tempStr;
+	  for (int jj = 0; jj < 2; jj++) {
+	    System.arraycopy(tempDist[jj], 0, dist[jj], 0,
+		dist[jj].length);
+	  }
+	}
+      }
+    }
+
+    // multi-class problems (exhaustive search)
+    else if (!useHeuristic || nonEmpty<=4) {
+      //else if (!useHeuristic || nonEmpty==2) {
+
+      // Firstly, for attribute values which class frequency is not zero
+      for (int i=0; i<(int)Math.pow(2,nonEmpty-1); i++) {
+	String tempStr="";
+	currDist = new double[2][numClasses];
+	int mod;
+	int bit10 = i;
+	for (int j=nonEmpty-1; j>=0; j--) {
+	  mod = bit10%2; // convert from 10bit to 2bit
+	  if (mod==1) {
+	    if (tempStr=="") tempStr = "("+nonEmptyValues[j]+")";
+	    else tempStr += "|" + "("+nonEmptyValues[j]+")";
+	  }
+	  bit10 = bit10/2;
+	}
+	for (int j=0; j<sortedIndices.length;j++) {
+	  Instance inst = data.instance(sortedIndices[j]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf("("+att.value((int)inst.value(att))+")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[j];
+	  } else currDist[1][(int)inst.classValue()] += weights[j];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int k=0; k<2; k++) {
+	  tempDist[k] = currDist[k];
+	}
+
+	double[] tempProps = new double[2];
+	for (int k=0; k<2; k++) {
+	  tempProps[k] = Utils.sum(tempDist[k]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int index = missingStart;
+	while (index < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[index]);
+	  for (int j = 0; j < 2; j++) {
+	    tempDist[j][(int)insta.classValue()] += tempProps[j] * weights[index];
+	  }
+	  index++;
+	}
+
+	double currGain;
+	if (useGini) currGain = computeGiniGain(parentDist,tempDist);
+	else currGain = computeInfoGain(parentDist,tempDist);
+
+	if (currGain>bestGain) {
+	  bestGain = currGain;
+	  bestSplitString = tempStr;
+	  for (int j = 0; j < 2; j++) {
+	    //dist[jj] = new double[currDist[jj].length];
+	    System.arraycopy(tempDist[j], 0, dist[j], 0,
+		dist[j].length);
+	  }
+	}
+      }
+    }
+
+    // huristic method to solve multi-classes problems
+    else {
+      // Firstly, for attribute values which class frequency is not zero
+      int n = nonEmpty;
+      int k = data.numClasses();  // number of classes of the data
+      double[][] P = new double[n][k];      // class probability matrix
+      int[] numInstancesValue = new int[n]; // number of instances for an attribute value
+      double[] meanClass = new double[k];   // vector of mean class probability
+      int numInstances = data.numInstances(); // total number of instances
+
+      // initialize the vector of mean class probability
+      for (int j=0; j<meanClass.length; j++) meanClass[j]=0;
+
+      for (int j=0; j<numInstances; j++) {
+	Instance inst = (Instance)data.instance(j);
+	int valueIndex = 0; // attribute value index in nonEmptyValues
+	for (int i=0; i<nonEmpty; i++) {
+	  if (att.value((int)inst.value(att)).compareToIgnoreCase(nonEmptyValues[i])==0){
+	    valueIndex = i;
+	    break;
+	  }
+	}
+	P[valueIndex][(int)inst.classValue()]++;
+	numInstancesValue[valueIndex]++;
+	meanClass[(int)inst.classValue()]++;
+      }
+
+      // calculate the class probability matrix
+      for (int i=0; i<P.length; i++) {
+	for (int j=0; j<P[0].length; j++) {
+	  if (numInstancesValue[i]==0) P[i][j]=0;
+	  else P[i][j]/=numInstancesValue[i];
+	}
+      }
+
+      //calculate the vector of mean class probability
+      for (int i=0; i<meanClass.length; i++) {
+	meanClass[i]/=numInstances;
+      }
+
+      // calculate the covariance matrix
+      double[][] covariance = new double[k][k];
+      for (int i1=0; i1<k; i1++) {
+	for (int i2=0; i2<k; i2++) {
+	  double element = 0;
+	  for (int j=0; j<n; j++) {
+	    element += (P[j][i2]-meanClass[i2])*(P[j][i1]-meanClass[i1])
+	    *numInstancesValue[j];
+	  }
+	  covariance[i1][i2] = element;
+	}
+      }
+
+      Matrix matrix = new Matrix(covariance);
+      weka.core.matrix.EigenvalueDecomposition eigen =
+	new weka.core.matrix.EigenvalueDecomposition(matrix);
+      double[] eigenValues = eigen.getRealEigenvalues();
+
+      // find index of the largest eigenvalue
+      int index=0;
+      double largest = eigenValues[0];
+      for (int i=1; i<eigenValues.length; i++) {
+	if (eigenValues[i]>largest) {
+	  index=i;
+	  largest = eigenValues[i];
+	}
+      }
+
+      // calculate the first principle component
+      double[] FPC = new double[k];
+      Matrix eigenVector = eigen.getV();
+      double[][] vectorArray = eigenVector.getArray();
+      for (int i=0; i<FPC.length; i++) {
+	FPC[i] = vectorArray[i][index];
+      }
+
+      // calculate the first principle component scores
+      double[] Sa = new double[n];
+      for (int i=0; i<Sa.length; i++) {
+	Sa[i]=0;
+	for (int j=0; j<k; j++) {
+	  Sa[i] += FPC[j]*P[i][j];
+	}
+      }
+
+      // sort category according to Sa(s)
+      double[] pCopy = new double[n];
+      System.arraycopy(Sa,0,pCopy,0,n);
+      String[] sortedValues = new String[n];
+      Arrays.sort(Sa);
+
+      for (int j=0; j<n; j++) {
+	sortedValues[j] = nonEmptyValues[Utils.minIndex(pCopy)];
+	pCopy[Utils.minIndex(pCopy)] = Double.MAX_VALUE;
+      }
+
+      // for the attribute values that class frequency is not 0
+      String tempStr = "";
+
+      for (int j=0; j<nonEmpty-1; j++) {
+	currDist = new double[2][numClasses];
+	if (tempStr=="") tempStr="(" + sortedValues[j] + ")";
+	else tempStr += "|"+ "(" + sortedValues[j] + ")";
+	for (int i=0; i<sortedIndices.length;i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf
+	      ("(" + att.value((int)inst.value(att)) + ")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[i];
+	  } else currDist[1][(int)inst.classValue()] += weights[i];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int kk=0; kk<2; kk++) {
+	  tempDist[kk] = currDist[kk];
+	}
+
+	double[] tempProps = new double[2];
+	for (int kk=0; kk<2; kk++) {
+	  tempProps[kk] = Utils.sum(tempDist[kk]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int mstart = missingStart;
+	while (mstart < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[mstart]);
+	  for (int jj = 0; jj < 2; jj++) {
+	    tempDist[jj][(int)insta.classValue()] += tempProps[jj] * weights[mstart];
+	  }
+	  mstart++;
+	}
+
+	double currGain;
+	if (useGini) currGain = computeGiniGain(parentDist,tempDist);
+	else currGain = computeInfoGain(parentDist,tempDist);
+
+	if (currGain>bestGain) {
+	  bestGain = currGain;
+	  bestSplitString = tempStr;
+	  for (int jj = 0; jj < 2; jj++) {
+	    //dist[jj] = new double[currDist[jj].length];
+	    System.arraycopy(tempDist[jj], 0, dist[jj], 0,
+		dist[jj].length);
+	  }
+	}
+      }
+    }
+
+    // Compute weights
+    int attIndex = att.index();
+    props[attIndex] = new double[2];
+    for (int k = 0; k < 2; k++) {
+      props[attIndex][k] = Utils.sum(dist[k]);
+    }
+    if (!(Utils.sum(props[attIndex]) > 0)) {
+      for (int k = 0; k < props[attIndex].length; k++) {
+	props[attIndex][k] = 1.0 / (double)props[attIndex].length;
+      }
+    } else {
+      Utils.normalize(props[attIndex]);
+    }
+
+    // Compute subset weights
+    subsetWeights[attIndex] = new double[2];
+    for (int j = 0; j < 2; j++) {
+      subsetWeights[attIndex][j] += Utils.sum(dist[j]);
+    }
+
+    // Then, for the attribute values that class frequency is 0, split it into the
+    // most frequent branch
+    for (int j=0; j<empty; j++) {
+      if (props[attIndex][0]>=props[attIndex][1]) {
+	if (bestSplitString=="") bestSplitString = "(" + emptyValues[j] + ")";
+	else bestSplitString += "|" + "(" + emptyValues[j] + ")";
+      }
+    }
+
+    // clean gain
+    gains[attIndex] = Math.rint(bestGain*10000000)/10000000.0;
+
+    dists[attIndex] = dist;
+    return bestSplitString;
+  }
+
+
+  /**
+   * Split data into two subsets and store sorted indices and weights for two
+   * successor nodes.
+   *
+   * @param subsetIndices 	sorted indecis of instances for each attribute for two successor node
+   * @param subsetWeights 	weights of instances for each attribute for two successor node
+   * @param att 		attribute the split based on
+   * @param splitPoint 		split point the split based on if att is numeric
+   * @param splitStr 		split subset the split based on if att is nominal
+   * @param sortedIndices 	sorted indices of the instances to be split
+   * @param weights 		weights of the instances to bes split
+   * @param data 		training data
+   * @throws Exception 		if something goes wrong  
+   */
+  protected void splitData(int[][][] subsetIndices, double[][][] subsetWeights,
+      Attribute att, double splitPoint, String splitStr, int[][] sortedIndices,
+      double[][] weights, Instances data) throws Exception {
+
+    int j;
+    // For each attribute
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i==data.classIndex()) continue;
+      int[] num = new int[2];
+      for (int k = 0; k < 2; k++) {
+	subsetIndices[k][i] = new int[sortedIndices[i].length];
+	subsetWeights[k][i] = new double[weights[i].length];
+      }
+
+      for (j = 0; j < sortedIndices[i].length; j++) {
+	Instance inst = data.instance(sortedIndices[i][j]);
+	if (inst.isMissing(att)) {
+	  // Split instance up
+	  for (int k = 0; k < 2; k++) {
+	    if (m_Props[k] > 0) {
+	      subsetIndices[k][i][num[k]] = sortedIndices[i][j];
+	      subsetWeights[k][i][num[k]] = m_Props[k] * weights[i][j];
+	      num[k]++;
+	    }
+	  }
+	} else {
+	  int subset;
+	  if (att.isNumeric())  {
+	    subset = (inst.value(att) < splitPoint) ? 0 : 1;
+	  } else { // nominal attribute
+	    if (splitStr.indexOf
+		("(" + att.value((int)inst.value(att.index()))+")")!=-1) {
+	      subset = 0;
+	    } else subset = 1;
+	  }
+	  subsetIndices[subset][i][num[subset]] = sortedIndices[i][j];
+	  subsetWeights[subset][i][num[subset]] = weights[i][j];
+	  num[subset]++;
+	}
+      }
+
+      // Trim arrays
+      for (int k = 0; k < 2; k++) {
+	int[] copy = new int[num[k]];
+	System.arraycopy(subsetIndices[k][i], 0, copy, 0, num[k]);
+	subsetIndices[k][i] = copy;
+	double[] copyWeights = new double[num[k]];
+	System.arraycopy(subsetWeights[k][i], 0 ,copyWeights, 0, num[k]);
+	subsetWeights[k][i] = copyWeights;
+      }
+    }
+  }
+
+
+  /**
+   * Compute and return gini gain for given distributions of a node and its 
+   * successor nodes.
+   * 
+   * @param parentDist 	class distributions of parent node
+   * @param childDist 	class distributions of successor nodes
+   * @return 		Gini gain computed
+   */
+  protected double computeGiniGain(double[] parentDist, double[][] childDist) {
+    double totalWeight = Utils.sum(parentDist);
+    if (totalWeight==0) return 0;
+
+    double leftWeight = Utils.sum(childDist[0]);
+    double rightWeight = Utils.sum(childDist[1]);
+
+    double parentGini = computeGini(parentDist, totalWeight);
+    double leftGini = computeGini(childDist[0],leftWeight);
+    double rightGini = computeGini(childDist[1], rightWeight);
+
+    return parentGini - leftWeight/totalWeight*leftGini -
+    rightWeight/totalWeight*rightGini;
+  }
+
+  /**
+   * Compute and return gini index for a given distribution of a node.
+   * 
+   * @param dist 	class distributions
+   * @param total 	class distributions
+   * @return 		Gini index of the class distributions
+   */
+  protected double computeGini(double[] dist, double total) {
+    if (total==0) return 0;
+    double val = 0;
+    for (int i=0; i<dist.length; i++) {
+      val += (dist[i]/total)*(dist[i]/total);
+    }
+    return 1- val;
+  }
+
+  /**
+   * Compute and return information gain for given distributions of a node 
+   * and its successor nodes.
+   * 
+   * @param parentDist 	class distributions of parent node
+   * @param childDist 	class distributions of successor nodes
+   * @return 		information gain computed
+   */
+  protected double computeInfoGain(double[] parentDist, double[][] childDist) {
+    double totalWeight = Utils.sum(parentDist);
+    if (totalWeight==0) return 0;
+
+    double leftWeight = Utils.sum(childDist[0]);
+    double rightWeight = Utils.sum(childDist[1]);
+
+    double parentInfo = computeEntropy(parentDist, totalWeight);
+    double leftInfo = computeEntropy(childDist[0],leftWeight);
+    double rightInfo = computeEntropy(childDist[1], rightWeight);
+
+    return parentInfo - leftWeight/totalWeight*leftInfo -
+    rightWeight/totalWeight*rightInfo;
+  }
+
+  /**
+   * Compute and return entropy for a given distribution of a node.
+   * 
+   * @param dist 	class distributions
+   * @param total 	class distributions
+   * @return 		entropy of the class distributions
+   */
+  protected double computeEntropy(double[] dist, double total) {
+    if (total==0) return 0;
+    double entropy = 0;
+    for (int i=0; i<dist.length; i++) {
+      if (dist[i]!=0) entropy -= dist[i]/total * Utils.log2(dist[i]/total);
+    }
+    return entropy;
+  }
+
+  /**
+   * Make the node leaf node.
+   * 
+   * @param data 	training data
+   */
+  protected void makeLeaf(Instances data) {
+    m_Attribute = null;
+    m_isLeaf = true;
+    m_ClassValue=Utils.maxIndex(m_ClassProbs);
+    m_ClassAttribute = data.classAttribute();
+  }
+
+  /**
+   * Computes class probabilities for instance using the decision tree.
+   *
+   * @param instance 	the instance for which class probabilities is to be computed
+   * @return 		the class probabilities for the given instance
+   * @throws Exception 	if something goes wrong
+   */
+  public double[] distributionForInstance(Instance instance)
+  throws Exception {
+    if (!m_isLeaf) {
+      // value of split attribute is missing
+      if (instance.isMissing(m_Attribute)) {
+	double[] returnedDist = new double[m_ClassProbs.length];
+
+	for (int i = 0; i < m_Successors.length; i++) {
+	  double[] help =
+	    m_Successors[i].distributionForInstance(instance);
+	  if (help != null) {
+	    for (int j = 0; j < help.length; j++) {
+	      returnedDist[j] += m_Props[i] * help[j];
+	    }
+	  }
+	}
+	return returnedDist;
+      }
+
+      // split attribute is nonimal
+      else if (m_Attribute.isNominal()) {
+	if (m_SplitString.indexOf("(" +
+	    m_Attribute.value((int)instance.value(m_Attribute)) + ")")!=-1)
+	  return  m_Successors[0].distributionForInstance(instance);
+	else return  m_Successors[1].distributionForInstance(instance);
+      }
+
+      // split attribute is numeric
+      else {
+	if (instance.value(m_Attribute) < m_SplitValue)
+	  return m_Successors[0].distributionForInstance(instance);
+	else
+	  return m_Successors[1].distributionForInstance(instance);
+      }
+    }
+
+    // leaf node
+    else return m_ClassProbs;
+  }
+
+  /**
+   * Prints the decision tree using the protected toString method from below.
+   * 
+   * @return 		a textual description of the classifier
+   */
+  public String toString() {
+    if ((m_Distribution == null) && (m_Successors == null)) {
+      return "Best-First: No model built yet.";
+    }
+    return "Best-First Decision Tree\n" + toString(0)+"\n\n"
+    +"Size of the Tree: "+numNodes()+"\n\n"
+    +"Number of Leaf Nodes: "+numLeaves();
+  }
+
+  /**
+   * Outputs a tree at a certain level.
+   * 
+   * @param level 	the level at which the tree is to be printed
+   * @return 		a tree at a certain level.
+   */
+  protected String toString(int level) {
+    StringBuffer text = new StringBuffer();
+    // if leaf nodes
+    if (m_Attribute == null) {
+      if (Utils.isMissingValue(m_ClassValue)) {
+	text.append(": null");
+      } else {
+	double correctNum = Math.rint(m_Distribution[Utils.maxIndex(m_Distribution)]*100)/
+	100.0;
+	double wrongNum = Math.rint((Utils.sum(m_Distribution) -
+	    m_Distribution[Utils.maxIndex(m_Distribution)])*100)/100.0;
+	String str = "("  + correctNum + "/" + wrongNum + ")";
+	text.append(": " + m_ClassAttribute.value((int) m_ClassValue)+ str);
+      }
+    } else {
+      for (int j = 0; j < 2; j++) {
+	text.append("\n");
+	for (int i = 0; i < level; i++) {
+	  text.append("|  ");
+	}
+	if (j==0) {
+	  if (m_Attribute.isNumeric())
+	    text.append(m_Attribute.name() + " < " + m_SplitValue);
+	  else
+	    text.append(m_Attribute.name() + "=" + m_SplitString);
+	} else {
+	  if (m_Attribute.isNumeric())
+	    text.append(m_Attribute.name() + " >= " + m_SplitValue);
+	  else
+	    text.append(m_Attribute.name() + "!=" + m_SplitString);
+	}
+	text.append(m_Successors[j].toString(level + 1));
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Compute size of the tree.
+   * 
+   * @return 		size of the tree
+   */
+  public int numNodes() {
+    if (m_isLeaf) {
+      return 1;
+    } else {
+      int size =1;
+      for (int i=0;i<m_Successors.length;i++) {
+	size+=m_Successors[i].numNodes();
+      }
+      return size;
+    }
+  }
+
+  /**
+   * Compute number of leaf nodes.
+   * 
+   * @return 		number of leaf nodes
+   */
+  public int numLeaves() {
+    if (m_isLeaf) return 1;
+    else {
+      int size=0;
+      for (int i=0;i<m_Successors.length;i++) {
+	size+=m_Successors[i].numLeaves();
+      }
+      return size;
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration describing the available options.
+   */
+  public Enumeration listOptions() {
+    Vector 		result;
+    Enumeration		en;
+    
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe pruning strategy.\n"
+	+ "\t(default: " + new SelectedTag(PRUNING_POSTPRUNING, TAGS_PRUNING) + ")",
+	"P", 1, "-P " + Tag.toOptionList(TAGS_PRUNING)));
+
+    result.addElement(new Option(
+	"\tThe minimal number of instances at the terminal nodes.\n" 
+	+ "\t(default 2)",
+	"M", 1, "-M <min no>"));
+    
+    result.addElement(new Option(
+	"\tThe number of folds used in the pruning.\n"
+	+ "\t(default 5)",
+	"N", 5, "-N <num folds>"));
+    
+    result.addElement(new Option(
+	"\tDon't use heuristic search for nominal attributes in multi-class\n"
+	+ "\tproblem (default yes).\n",
+	"H", 0, "-H"));
+    
+    result.addElement(new Option(
+	"\tDon't use Gini index for splitting (default yes),\n"
+	+ "\tif not information is used.", 
+	"G", 0, "-G"));
+    
+    result.addElement(new Option(
+	"\tDon't use error rate in internal cross-validation (default yes), \n"
+	+ "\tbut root mean squared error.", 
+	"R", 0, "-R"));
+    
+    result.addElement(new Option(
+	"\tUse the 1 SE rule to make pruning decision.\n"
+	+ "\t(default no).", 
+	"A", 0, "-A"));
+    
+    result.addElement(new Option(
+	"\tPercentage of training data size (0-1]\n"
+	+ "\t(default 1).", 
+	"C", 0, "-C"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -P &lt;UNPRUNED|POSTPRUNED|PREPRUNED&gt;
+   *  The pruning strategy.
+   *  (default: POSTPRUNED)</pre>
+   * 
+   * <pre> -M &lt;min no&gt;
+   *  The minimal number of instances at the terminal nodes.
+   *  (default 2)</pre>
+   * 
+   * <pre> -N &lt;num folds&gt;
+   *  The number of folds used in the pruning.
+   *  (default 5)</pre>
+   * 
+   * <pre> -H
+   *  Don't use heuristic search for nominal attributes in multi-class
+   *  problem (default yes).
+   * </pre>
+   * 
+   * <pre> -G
+   *  Don't use Gini index for splitting (default yes),
+   *  if not information is used.</pre>
+   * 
+   * <pre> -R
+   *  Don't use error rate in internal cross-validation (default yes), 
+   *  but root mean squared error.</pre>
+   * 
+   * <pre> -A
+   *  Use the 1 SE rule to make pruning decision.
+   *  (default no).</pre>
+   * 
+   * <pre> -C
+   *  Percentage of training data size (0-1]
+   *  (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String 	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0) 
+      setMinNumObj(Integer.parseInt(tmpStr));
+    else
+      setMinNumObj(2);
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNumFoldsPruning(Integer.parseInt(tmpStr));
+    else
+      setNumFoldsPruning(5);
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length()!=0)
+      setSizePer(Double.parseDouble(tmpStr));
+    else
+      setSizePer(1);
+
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setPruningStrategy(new SelectedTag(tmpStr, TAGS_PRUNING));
+    else
+      setPruningStrategy(new SelectedTag(PRUNING_POSTPRUNING, TAGS_PRUNING));
+
+    setHeuristic(!Utils.getFlag('H',options));
+
+    setUseGini(!Utils.getFlag('G',options));
+    
+    setUseErrorRate(!Utils.getFlag('R',options));
+    
+    setUseOneSE(Utils.getFlag('A',options));
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   * 
+   * @return 		the current settings of the Classifier
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-M");
+    result.add("" + getMinNumObj());
+
+    result.add("-N");
+    result.add("" + getNumFoldsPruning());
+
+    if (!getHeuristic())
+      result.add("-H");
+
+    if (!getUseGini())
+      result.add("-G");
+
+    if (!getUseErrorRate())
+      result.add("-R");
+
+    if (getUseOneSE())
+      result.add("-A");
+
+    result.add("-C");
+    result.add("" + getSizePer());
+
+    result.add("-P");
+    result.add("" + getPruningStrategy());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Return an enumeration of the measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector result = new Vector();
+    
+    result.addElement("measureTreeSize");
+    
+    return result.elements();
+  }
+
+  /**
+   * Return number of tree size.
+   * 
+   * @return 		number of tree size
+   */
+  public double measureTreeSize() {
+    return numNodes();
+  }
+
+  /**
+   * Returns the value of the named measure
+   *
+   * @param additionalMeasureName 	the name of the measure to query for its value
+   * @return 				the value of the named measure
+   * @throws IllegalArgumentException 	if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName
+	  + " not supported (Best-First)");
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String pruningStrategyTipText() {
+    return "Sets the pruning strategy.";
+  }
+
+  /**
+   * Sets the pruning strategy. 
+   *
+   * @param value 	the strategy
+   */
+  public void setPruningStrategy(SelectedTag value) {
+    if (value.getTags() == TAGS_PRUNING) {
+      m_PruningStrategy = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the pruning strategy. 
+   *
+   * @return 		the current strategy.
+   */
+  public SelectedTag getPruningStrategy() {
+    return new SelectedTag(m_PruningStrategy, TAGS_PRUNING);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String minNumObjTipText() {
+    return "Set minimal number of instances at the terminal nodes.";
+  }
+
+  /**
+   * Set minimal number of instances at the terminal nodes.
+   * 
+   * @param value 	minimal number of instances at the terminal nodes
+   */
+  public void setMinNumObj(int value) {
+    m_minNumObj = value;
+  }
+
+  /**
+   * Get minimal number of instances at the terminal nodes.
+   * 
+   * @return 		minimal number of instances at the terminal nodes
+   */
+  public int getMinNumObj() {
+    return m_minNumObj;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numFoldsPruningTipText() {
+    return "Number of folds in internal cross-validation.";
+  }
+
+  /**
+   * Set number of folds in internal cross-validation.
+   * 
+   * @param value 	the number of folds
+   */
+  public void setNumFoldsPruning(int value) {
+    m_numFoldsPruning = value;
+  }
+
+  /**
+   * Set number of folds in internal cross-validation.
+   * 
+   * @return 		number of folds in internal cross-validation
+   */
+  public int getNumFoldsPruning() {
+    return m_numFoldsPruning;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String heuristicTipText() {
+    return "If heuristic search is used for binary split for nominal attributes.";
+  }
+
+  /**
+   * Set if use heuristic search for nominal attributes in multi-class problems.
+   * 
+   * @param value 	if use heuristic search for nominal attributes in 
+   * 			multi-class problems
+   */
+  public void setHeuristic(boolean value) {
+    m_Heuristic = value;
+  }
+
+  /**
+   * Get if use heuristic search for nominal attributes in multi-class problems.
+   * 
+   * @return 		if use heuristic search for nominal attributes in 
+   * 			multi-class problems
+   */
+  public boolean getHeuristic() {
+    return m_Heuristic;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String useGiniTipText() {
+    return "If true the Gini index is used for splitting criterion, otherwise the information is used.";
+  }
+
+  /**
+   * Set if use Gini index as splitting criterion.
+   * 
+   * @param value 	if use Gini index splitting criterion
+   */
+  public void setUseGini(boolean value) {
+    m_UseGini = value;
+  }
+
+  /**
+   * Get if use Gini index as splitting criterion.
+   * 
+   * @return 		if use Gini index as splitting criterion
+   */
+  public boolean getUseGini() {
+    return m_UseGini;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String useErrorRateTipText() {
+    return "If error rate is used as error estimate. if not, root mean squared error is used.";
+  }
+
+  /**
+   * Set if use error rate in internal cross-validation.
+   * 
+   * @param value 	if use error rate in internal cross-validation
+   */
+  public void setUseErrorRate(boolean value) {
+    m_UseErrorRate = value;
+  }
+
+  /**
+   * Get if use error rate in internal cross-validation.
+   * 
+   * @return 		if use error rate in internal cross-validation.
+   */
+  public boolean getUseErrorRate() {
+    return m_UseErrorRate;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String useOneSETipText() {
+    return "Use the 1SE rule to make pruning decision.";
+  }
+
+  /**
+   * Set if use the 1SE rule to choose final model.
+   * 
+   * @param value 	if use the 1SE rule to choose final model
+   */
+  public void setUseOneSE(boolean value) {
+    m_UseOneSE = value;
+  }
+
+  /**
+   * Get if use the 1SE rule to choose final model.
+   * 
+   * @return 		if use the 1SE rule to choose final model
+   */
+  public boolean getUseOneSE() {
+    return m_UseOneSE;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String sizePerTipText() {
+    return "The percentage of the training set size (0-1, 0 not included).";
+  }
+
+  /**
+   * Set training set size.
+   * 
+   * @param value 	training set size
+   */
+  public void setSizePer(double value) {
+    if ((value <= 0) || (value > 1))
+      System.err.println(
+	  "The percentage of the training set size must be in range 0 to 1 "
+	  + "(0 not included) - ignored!");
+    else
+      m_SizePer = value;
+  }
+
+  /**
+   * Get training set size.
+   * 
+   * @return 		training set size
+   */
+  public double getSizePer() {
+    return m_SizePer;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {
+    runClassifier(new BFTree(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/DecisionStump.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/DecisionStump.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/DecisionStump.java	(revision 29)
@@ -0,0 +1,772 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DecisionStump.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for building and using a decision stump. Usually used in conjunction with a boosting algorithm. Does regression (based on mean-squared error) or classification (based on entropy). Missing is treated as a separate value.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * Typical usage: <p>
+ * <code>java weka.classifiers.meta.LogitBoost -I 100 -W weka.classifiers.trees.DecisionStump 
+ * -t training_data </code><p>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class DecisionStump 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = 1618384535950391L;
+  
+  /** The attribute used for classification. */
+  private int m_AttIndex;
+
+  /** The split point (index respectively). */
+  private double m_SplitPoint;
+
+  /** The distribution of class values or the means in each subset. */
+  private double[][] m_Distribution;
+
+  /** The instances used for training. */
+  private Instances m_Instances;
+
+  /** a ZeroR model in case no model can be built from the data */
+  private Classifier m_ZeroR;
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for building and using a decision stump. Usually used in "
+      + "conjunction with a boosting algorithm. Does regression (based on "
+      + "mean-squared error) or classification (based on entropy). Missing "
+      + "is treated as a separate value.";
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances set of instances serving as training data 
+   * @throws Exception if the classifier has not been generated successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    double bestVal = Double.MAX_VALUE, currVal;
+    double bestPoint = -Double.MAX_VALUE;
+    int bestAtt = -1, numClasses;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(instances);
+
+    // remove instances with missing class
+    instances = new Instances(instances);
+    instances.deleteWithMissingClass();
+    
+    // only class? -> build ZeroR model
+    if (instances.numAttributes() == 1) {
+      System.err.println(
+	  "Cannot build model (only class attribute present in data!), "
+	  + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(instances);
+      return;
+    }
+    else {
+      m_ZeroR = null;
+    }
+    
+    double[][] bestDist = new double[3][instances.numClasses()];
+
+    m_Instances = new Instances(instances);
+
+    if (m_Instances.classAttribute().isNominal()) {
+      numClasses = m_Instances.numClasses();
+    } else {
+      numClasses = 1;
+    }
+
+    // For each attribute
+    boolean first = true;
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      if (i != m_Instances.classIndex()) {
+
+	// Reserve space for distribution.
+	m_Distribution = new double[3][numClasses];
+
+	// Compute value of criterion for best split on attribute
+	if (m_Instances.attribute(i).isNominal()) {
+	  currVal = findSplitNominal(i);
+	} else {
+	  currVal = findSplitNumeric(i);
+	}
+	if ((first) || (currVal < bestVal)) {
+	  bestVal = currVal;
+	  bestAtt = i;
+	  bestPoint = m_SplitPoint;
+	  for (int j = 0; j < 3; j++) {
+	    System.arraycopy(m_Distribution[j], 0, bestDist[j], 0, 
+			     numClasses);
+	  }
+	}
+	
+	// First attribute has been investigated
+	first = false;
+      }
+    }
+    
+    // Set attribute, split point and distribution.
+    m_AttIndex = bestAtt;
+    m_SplitPoint = bestPoint;
+    m_Distribution = bestDist;
+    if (m_Instances.classAttribute().isNominal()) {
+      for (int i = 0; i < m_Distribution.length; i++) {
+	double sumCounts = Utils.sum(m_Distribution[i]);
+	if (sumCounts == 0) { // This means there were only missing attribute values
+	  System.arraycopy(m_Distribution[2], 0, m_Distribution[i], 0, 
+			   m_Distribution[2].length);
+	  Utils.normalize(m_Distribution[i]);
+	} else {
+	  Utils.normalize(m_Distribution[i], sumCounts); 
+	}
+      }
+    }
+    
+    // Save memory
+    m_Instances = new Instances(m_Instances, 0);
+  }
+
+  /**
+   * Calculates the class membership probabilities for the given test instance.
+   *
+   * @param instance the instance to be classified
+   * @return predicted class probability distribution
+   * @throws Exception if distribution can't be computed
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+    
+    return m_Distribution[whichSubset(instance)];
+  }
+
+  /**
+   * Returns the decision tree as Java source code.
+   *
+   * @param className the classname of the generated code
+   * @return the tree as Java source code
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) throws Exception {
+
+    StringBuffer text = new StringBuffer("class ");
+    Attribute c = m_Instances.classAttribute();
+    text.append(className)
+      .append(" {\n"
+	      +"  public static double classify(Object[] i) {\n");
+    text.append("    /* " + m_Instances.attribute(m_AttIndex).name() + " */\n");
+    text.append("    if (i[").append(m_AttIndex);
+    text.append("] == null) { return ");
+    text.append(sourceClass(c, m_Distribution[2])).append(";");
+    if (m_Instances.attribute(m_AttIndex).isNominal()) {
+      text.append(" } else if (((String)i[").append(m_AttIndex);
+      text.append("]).equals(\"");
+      text.append(m_Instances.attribute(m_AttIndex).value((int)m_SplitPoint));
+      text.append("\")");
+    } else {
+      text.append(" } else if (((Double)i[").append(m_AttIndex);
+      text.append("]).doubleValue() <= ").append(m_SplitPoint);
+    }
+    text.append(") { return ");
+    text.append(sourceClass(c, m_Distribution[0])).append(";");
+    text.append(" } else { return ");
+    text.append(sourceClass(c, m_Distribution[1])).append(";");
+    text.append(" }\n  }\n}\n");
+    return text.toString();
+  }
+
+  /**
+   * Returns the value as string out of the given distribution
+   * 
+   * @param c the attribute to get the value for
+   * @param dist the distribution to extract the value
+   * @return the value
+   */
+  private String sourceClass(Attribute c, double []dist) {
+
+    if (c.isNominal()) {
+      return Integer.toString(Utils.maxIndex(dist));
+    } else {
+      return Double.toString(dist[0]);
+    }
+  }
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier as a string.
+   */
+  public String toString(){
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
+      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+    
+    if (m_Instances == null) {
+      return "Decision Stump: No model built yet.";
+    }
+    try {
+      StringBuffer text = new StringBuffer();
+      
+      text.append("Decision Stump\n\n");
+      text.append("Classifications\n\n");
+      Attribute att = m_Instances.attribute(m_AttIndex);
+      if (att.isNominal()) {
+	text.append(att.name() + " = " + att.value((int)m_SplitPoint) + 
+		    " : ");
+	text.append(printClass(m_Distribution[0]));
+	text.append(att.name() + " != " + att.value((int)m_SplitPoint) + 
+		    " : ");
+	text.append(printClass(m_Distribution[1]));
+      } else {
+	text.append(att.name() + " <= " + m_SplitPoint + " : ");
+	text.append(printClass(m_Distribution[0]));
+	text.append(att.name() + " > " + m_SplitPoint + " : ");
+	text.append(printClass(m_Distribution[1]));
+      }
+      text.append(att.name() + " is missing : ");
+      text.append(printClass(m_Distribution[2]));
+
+      if (m_Instances.classAttribute().isNominal()) {
+	text.append("\nClass distributions\n\n");
+	if (att.isNominal()) {
+	  text.append(att.name() + " = " + att.value((int)m_SplitPoint) + 
+		      "\n");
+	  text.append(printDist(m_Distribution[0]));
+	  text.append(att.name() + " != " + att.value((int)m_SplitPoint) + 
+		      "\n");
+	  text.append(printDist(m_Distribution[1]));
+	} else {
+	  text.append(att.name() + " <= " + m_SplitPoint + "\n");
+	  text.append(printDist(m_Distribution[0]));
+	  text.append(att.name() + " > " + m_SplitPoint + "\n");
+	  text.append(printDist(m_Distribution[1]));
+	}
+	text.append(att.name() + " is missing\n");
+	text.append(printDist(m_Distribution[2]));
+      }
+
+      return text.toString();
+    } catch (Exception e) {
+      return "Can't print decision stump classifier!";
+    }
+  }
+
+  /** 
+   * Prints a class distribution.
+   *
+   * @param dist the class distribution to print
+   * @return the distribution as a string
+   * @throws Exception if distribution can't be printed
+   */
+  private String printDist(double[] dist) throws Exception {
+
+    StringBuffer text = new StringBuffer();
+    
+    if (m_Instances.classAttribute().isNominal()) {
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+	text.append(m_Instances.classAttribute().value(i) + "\t");
+      }
+      text.append("\n");
+      for (int i = 0; i < m_Instances.numClasses(); i++) {
+	text.append(dist[i] + "\t");
+      }
+      text.append("\n");
+    }
+    
+    return text.toString();
+  }
+
+  /** 
+   * Prints a classification.
+   *
+   * @param dist the class distribution
+   * @return the classificationn as a string
+   * @throws Exception if the classification can't be printed
+   */
+  private String printClass(double[] dist) throws Exception {
+
+    StringBuffer text = new StringBuffer();
+    
+    if (m_Instances.classAttribute().isNominal()) {
+      text.append(m_Instances.classAttribute().value(Utils.maxIndex(dist)));
+    } else {
+      text.append(dist[0]);
+    }
+    
+    return text.toString() + "\n";
+  }
+
+  /**
+   * Finds best split for nominal attribute and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNominal(int index) throws Exception {
+
+    if (m_Instances.classAttribute().isNominal()) {
+      return findSplitNominalNominal(index);
+    } else {
+      return findSplitNominalNumeric(index);
+    }
+  }
+
+  /**
+   * Finds best split for nominal attribute and nominal class
+   * and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNominalNominal(int index) throws Exception {
+
+    double bestVal = Double.MAX_VALUE, currVal;
+    double[][] counts = new double[m_Instances.attribute(index).numValues() 
+				  + 1][m_Instances.numClasses()];
+    double[] sumCounts = new double[m_Instances.numClasses()];
+    double[][] bestDist = new double[3][m_Instances.numClasses()];
+    int numMissing = 0;
+
+    // Compute counts for all the values
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      Instance inst = m_Instances.instance(i);
+      if (inst.isMissing(index)) {
+	numMissing++;
+	counts[m_Instances.attribute(index).numValues()]
+	  [(int)inst.classValue()] += inst.weight();
+      } else {
+	counts[(int)inst.value(index)][(int)inst.classValue()] += inst
+	  .weight();
+      }
+    }
+
+    // Compute sum of counts
+    for (int i = 0; i < m_Instances.attribute(index).numValues(); i++) {
+      for (int j = 0; j < m_Instances.numClasses(); j++) {
+	sumCounts[j] += counts[i][j];
+      }
+    }
+    
+    // Make split counts for each possible split and evaluate
+    System.arraycopy(counts[m_Instances.attribute(index).numValues()], 0,
+		     m_Distribution[2], 0, m_Instances.numClasses());
+    for (int i = 0; i < m_Instances.attribute(index).numValues(); i++) {
+      for (int j = 0; j < m_Instances.numClasses(); j++) {
+	m_Distribution[0][j] = counts[i][j];
+	m_Distribution[1][j] = sumCounts[j] - counts[i][j];
+      }
+      currVal = ContingencyTables.entropyConditionedOnRows(m_Distribution);
+      if (currVal < bestVal) {
+	bestVal = currVal;
+	m_SplitPoint = (double)i;
+	for (int j = 0; j < 3; j++) {
+	  System.arraycopy(m_Distribution[j], 0, bestDist[j], 0, 
+			   m_Instances.numClasses());
+	}
+      }
+    }
+
+    // No missing values in training data.
+    if (numMissing == 0) {
+      System.arraycopy(sumCounts, 0, bestDist[2], 0, 
+		       m_Instances.numClasses());
+    }
+   
+    m_Distribution = bestDist;
+    return bestVal;
+  }
+
+  /**
+   * Finds best split for nominal attribute and numeric class
+   * and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNominalNumeric(int index) throws Exception {
+
+    double bestVal = Double.MAX_VALUE, currVal;
+    double[] sumsSquaresPerValue = 
+      new double[m_Instances.attribute(index).numValues()], 
+      sumsPerValue = new double[m_Instances.attribute(index).numValues()], 
+      weightsPerValue = new double[m_Instances.attribute(index).numValues()];
+    double totalSumSquaresW = 0, totalSumW = 0, totalSumOfWeightsW = 0,
+      totalSumOfWeights = 0, totalSum = 0;
+    double[] sumsSquares = new double[3], sumOfWeights = new double[3];
+    double[][] bestDist = new double[3][1];
+
+    // Compute counts for all the values
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      Instance inst = m_Instances.instance(i);
+      if (inst.isMissing(index)) {
+	m_Distribution[2][0] += inst.classValue() * inst.weight();
+	sumsSquares[2] += inst.classValue() * inst.classValue() 
+	  * inst.weight();
+	sumOfWeights[2] += inst.weight();
+      } else {
+	weightsPerValue[(int)inst.value(index)] += inst.weight();
+	sumsPerValue[(int)inst.value(index)] += inst.classValue() 
+	  * inst.weight();
+	sumsSquaresPerValue[(int)inst.value(index)] += 
+	  inst.classValue() * inst.classValue() * inst.weight();
+      }
+      totalSumOfWeights += inst.weight();
+      totalSum += inst.classValue() * inst.weight();
+    }
+
+    // Check if the total weight is zero
+    if (totalSumOfWeights <= 0) {
+      return bestVal;
+    }
+
+    // Compute sum of counts without missing ones
+    for (int i = 0; i < m_Instances.attribute(index).numValues(); i++) {
+      totalSumOfWeightsW += weightsPerValue[i];
+      totalSumSquaresW += sumsSquaresPerValue[i];
+      totalSumW += sumsPerValue[i];
+    }
+    
+    // Make split counts for each possible split and evaluate
+    for (int i = 0; i < m_Instances.attribute(index).numValues(); i++) {
+      
+      m_Distribution[0][0] = sumsPerValue[i];
+      sumsSquares[0] = sumsSquaresPerValue[i];
+      sumOfWeights[0] = weightsPerValue[i];
+      m_Distribution[1][0] = totalSumW - sumsPerValue[i];
+      sumsSquares[1] = totalSumSquaresW - sumsSquaresPerValue[i];
+      sumOfWeights[1] = totalSumOfWeightsW - weightsPerValue[i];
+
+      currVal = variance(m_Distribution, sumsSquares, sumOfWeights);
+      
+      if (currVal < bestVal) {
+	bestVal = currVal;
+	m_SplitPoint = (double)i;
+	for (int j = 0; j < 3; j++) {
+	  if (sumOfWeights[j] > 0) {
+	    bestDist[j][0] = m_Distribution[j][0] / sumOfWeights[j];
+	  } else {
+	    bestDist[j][0] = totalSum / totalSumOfWeights;
+	  }
+	}
+      }
+    }
+
+    m_Distribution = bestDist;
+    return bestVal;
+  }
+
+  /**
+   * Finds best split for numeric attribute and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNumeric(int index) throws Exception {
+
+    if (m_Instances.classAttribute().isNominal()) {
+      return findSplitNumericNominal(index);
+    } else {
+      return findSplitNumericNumeric(index);
+    }
+  }
+
+  /**
+   * Finds best split for numeric attribute and nominal class
+   * and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNumericNominal(int index) throws Exception {
+
+    double bestVal = Double.MAX_VALUE, currVal, currCutPoint;
+    int numMissing = 0;
+    double[] sum = new double[m_Instances.numClasses()];
+    double[][] bestDist = new double[3][m_Instances.numClasses()];
+
+    // Compute counts for all the values
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      Instance inst = m_Instances.instance(i);
+      if (!inst.isMissing(index)) {
+	m_Distribution[1][(int)inst.classValue()] += inst.weight();
+      } else {
+	m_Distribution[2][(int)inst.classValue()] += inst.weight();
+	numMissing++;
+      }
+    }
+    System.arraycopy(m_Distribution[1], 0, sum, 0, m_Instances.numClasses());
+
+    // Save current distribution as best distribution
+    for (int j = 0; j < 3; j++) {
+      System.arraycopy(m_Distribution[j], 0, bestDist[j], 0, 
+		       m_Instances.numClasses());
+    }
+
+    // Sort instances
+    m_Instances.sort(index);
+    
+    // Make split counts for each possible split and evaluate
+    for (int i = 0; i < m_Instances.numInstances() - (numMissing + 1); i++) {
+      Instance inst = m_Instances.instance(i);
+      Instance instPlusOne = m_Instances.instance(i + 1);
+      m_Distribution[0][(int)inst.classValue()] += inst.weight();
+      m_Distribution[1][(int)inst.classValue()] -= inst.weight();
+      if (inst.value(index) < instPlusOne.value(index)) {
+	currCutPoint = (inst.value(index) + instPlusOne.value(index)) / 2.0;
+	currVal = ContingencyTables.entropyConditionedOnRows(m_Distribution);
+	if (currVal < bestVal) {
+	  m_SplitPoint = currCutPoint;
+	  bestVal = currVal;
+	  for (int j = 0; j < 3; j++) {
+	    System.arraycopy(m_Distribution[j], 0, bestDist[j], 0, 
+			     m_Instances.numClasses());
+	  }
+	}
+      }
+    }
+
+    // No missing values in training data.
+    if (numMissing == 0) {
+      System.arraycopy(sum, 0, bestDist[2], 0, m_Instances.numClasses());
+    }
+ 
+    m_Distribution = bestDist;
+    return bestVal;
+  }
+
+  /**
+   * Finds best split for numeric attribute and numeric class
+   * and returns value.
+   *
+   * @param index attribute index
+   * @return value of criterion for the best split
+   * @throws Exception if something goes wrong
+   */
+  private double findSplitNumericNumeric(int index) throws Exception {
+
+    double bestVal = Double.MAX_VALUE, currVal, currCutPoint;
+    int numMissing = 0;
+    double[] sumsSquares = new double[3], sumOfWeights = new double[3];
+    double[][] bestDist = new double[3][1];
+    double totalSum = 0, totalSumOfWeights = 0;
+
+    // Compute counts for all the values
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      Instance inst = m_Instances.instance(i);
+      if (!inst.isMissing(index)) {
+	m_Distribution[1][0] += inst.classValue() * inst.weight();
+	sumsSquares[1] += inst.classValue() * inst.classValue() 
+	  * inst.weight();
+	sumOfWeights[1] += inst.weight();
+      } else {
+	m_Distribution[2][0] += inst.classValue() * inst.weight();
+	sumsSquares[2] += inst.classValue() * inst.classValue() 
+	  * inst.weight();
+	sumOfWeights[2] += inst.weight();
+	numMissing++;
+      }
+      totalSumOfWeights += inst.weight();
+      totalSum += inst.classValue() * inst.weight();
+    }
+
+    // Check if the total weight is zero
+    if (totalSumOfWeights <= 0) {
+      return bestVal;
+    }
+
+    // Sort instances
+    m_Instances.sort(index);
+    
+    // Make split counts for each possible split and evaluate
+    for (int i = 0; i < m_Instances.numInstances() - (numMissing + 1); i++) {
+      Instance inst = m_Instances.instance(i);
+      Instance instPlusOne = m_Instances.instance(i + 1);
+      m_Distribution[0][0] += inst.classValue() * inst.weight();
+      sumsSquares[0] += inst.classValue() * inst.classValue() * inst.weight();
+      sumOfWeights[0] += inst.weight();
+      m_Distribution[1][0] -= inst.classValue() * inst.weight();
+      sumsSquares[1] -= inst.classValue() * inst.classValue() * inst.weight();
+      sumOfWeights[1] -= inst.weight();
+      if (inst.value(index) < instPlusOne.value(index)) {
+	currCutPoint = (inst.value(index) + instPlusOne.value(index)) / 2.0;
+	currVal = variance(m_Distribution, sumsSquares, sumOfWeights);
+	if (currVal < bestVal) {
+	  m_SplitPoint = currCutPoint;
+	  bestVal = currVal;
+	  for (int j = 0; j < 3; j++) {
+	    if (sumOfWeights[j] > 0) {
+	      bestDist[j][0] = m_Distribution[j][0] / sumOfWeights[j];
+	    } else {
+	      bestDist[j][0] = totalSum / totalSumOfWeights;
+	    }
+	  }
+	}
+      }
+    }
+
+    m_Distribution = bestDist;
+    return bestVal;
+  }
+
+  /**
+   * Computes variance for subsets.
+   * 
+   * @param s
+   * @param sS
+   * @param sumOfWeights
+   * @return the variance
+   */
+  private double variance(double[][] s,double[] sS,double[] sumOfWeights) {
+
+    double var = 0;
+
+    for (int i = 0; i < s.length; i++) {
+      if (sumOfWeights[i] > 0) {
+	var += sS[i] - ((s[i][0] * s[i][0]) / (double) sumOfWeights[i]);
+      }
+    }
+    
+    return var;
+  }
+
+  /**
+   * Returns the subset an instance falls into.
+   * 
+   * @param instance the instance to check
+   * @return the subset the instance falls into
+   * @throws Exception if something goes wrong
+   */
+  private int whichSubset(Instance instance) throws Exception {
+
+    if (instance.isMissing(m_AttIndex)) {
+      return 2;
+    } else if (instance.attribute(m_AttIndex).isNominal()) {
+      if ((int)instance.value(m_AttIndex) == m_SplitPoint) {
+	return 0;
+      } else {
+	return 1;
+      }
+    } else {
+      if (instance.value(m_AttIndex) <= m_SplitPoint) {
+	return 0;
+      } else {
+	return 1;
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+ 
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClassifier(new DecisionStump(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/FT.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/FT.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/FT.java	(revision 29)
@@ -0,0 +1,801 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FT.java
+ *    Copyright (C) 2007 University of Porto, Porto, Portugal
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.trees.ft.FTInnerNode;
+import weka.classifiers.trees.ft.FTLeavesNode;
+import weka.classifiers.trees.ft.FTNode;
+import weka.classifiers.trees.ft.FTtree;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Classifier for building 'Functional trees', which are classification trees  that could have logistic regression functions at the inner nodes and/or leaves. The algorithm can deal with binary and multi-class target variables, numeric and nominal attributes and missing values.<br/>
+ * <br/>
+ * For more information see: <br/>
+ * <br/>
+ * Joao Gama (2004). Functional Trees.<br/>
+ * <br/>
+ * Niels Landwehr, Mark Hall, Eibe Frank (2005). Logistic Model Trees.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Gama2004,
+ *    author = {Joao Gama},
+ *    booktitle = {Machine Learning},
+ *    number = {3},
+ *    pages = {219-250},
+ *    title = {Functional Trees},
+ *    volume = {55},
+ *    year = {2004}
+ * }
+ * 
+ * &#64;article{Landwehr2005,
+ *    author = {Niels Landwehr and Mark Hall and Eibe Frank},
+ *    booktitle = {Machine Learning},
+ *    number = {1-2},
+ *    pages = {161-205},
+ *    title = {Logistic Model Trees},
+ *    volume = {95},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B
+ *  Binary splits (convert nominal attributes to binary ones) </pre>
+ * 
+ * <pre> -P
+ *  Use error on probabilities instead of misclassification error for stopping criterion of LogitBoost.</pre>
+ * 
+ * <pre> -I &lt;numIterations&gt;
+ *  Set fixed number of iterations for LogitBoost (instead of using cross-validation)</pre>
+ * 
+ * <pre> -F &lt;modelType&gt;
+ *  Set Funtional Tree type to be generate:  0 for FT, 1 for FTLeaves and 2 for FTInner</pre>
+ * 
+ * <pre> -M &lt;numInstances&gt;
+ *  Set minimum number of instances at which a node can be split (default 15)</pre>
+ * 
+ * <pre> -W &lt;beta&gt;
+ *  Set beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.</pre>
+ * 
+ * <pre> -A
+ *  The AIC is used to choose the best iteration.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Jo\~{a}o Gama
+ * @author Carlos Ferreira  
+ * @version $Revision: 5928 $
+ */
+public class FT 
+  extends AbstractClassifier 
+  implements OptionHandler, AdditionalMeasureProducer, Drawable,
+             TechnicalInformationHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -1113212459618105000L;
+  
+  /** Filter to replace missing values*/
+  protected ReplaceMissingValues m_replaceMissing;
+  
+  /** Filter to replace nominal attributes*/
+  protected NominalToBinary m_nominalToBinary;
+    
+  /** root of the logistic model tree*/
+  protected FTtree m_tree;
+  
+  /** convert nominal attributes to binary ?*/
+  protected boolean m_convertNominal;
+  
+  /**use error on probabilties instead of misclassification for stopping criterion of LogitBoost?*/
+  protected boolean m_errorOnProbabilities;
+    
+  /**minimum number of instances at which a node is considered for splitting*/
+  protected int m_minNumInstances;
+
+  /**if non-zero, use fixed number of iterations for LogitBoost*/
+  protected int m_numBoostingIterations;
+  
+  /**Model Type, value: 0 is FT, 1 is FTLeaves, 2  is FTInner*/
+  protected int m_modelType;
+    
+  /**Threshold for trimming weights. Instances with a weight lower than this (as a percentage
+   * of total weights) are not included in the regression fit.
+   **/
+  protected double m_weightTrimBeta;
+  
+  /** If true, the AIC is used to choose the best LogitBoost iteration*/
+  protected boolean m_useAIC ;
+
+  /** model types */
+  public static final int MODEL_FT = 0;
+  public static final int MODEL_FTLeaves = 1;
+  public static final int MODEL_FTInner = 2;
+
+  /** possible model types. */
+  public static final Tag [] TAGS_MODEL = {
+    new Tag(MODEL_FT, "FT"),
+    new Tag(MODEL_FTLeaves, "FTLeaves"),
+    new Tag(MODEL_FTInner, "FTInner")
+  };
+
+  
+  /**
+   * Creates an instance of FT with standard options
+   */
+  public FT() {
+    m_numBoostingIterations=15;
+    m_minNumInstances = 15;
+    m_weightTrimBeta = 0;
+    m_useAIC = false;
+    m_modelType=0;
+  }    
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier.
+   *
+   * @param data the data to train with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception{
+	
+      
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances filteredData = new Instances(data);
+    filteredData.deleteWithMissingClass();
+    
+    //replace missing values
+    m_replaceMissing = new ReplaceMissingValues();
+    m_replaceMissing.setInputFormat(filteredData);	
+    filteredData = Filter.useFilter(filteredData, m_replaceMissing);
+    
+    //possibly convert nominal attributes globally
+    if (m_convertNominal) {	    
+      m_nominalToBinary = new NominalToBinary();
+      m_nominalToBinary.setInputFormat(filteredData);	
+      filteredData = Filter.useFilter(filteredData, m_nominalToBinary);
+    }
+	
+    int minNumInstances = 2;  
+    
+    
+    //create a FT  tree root
+    if (m_modelType==0)
+      m_tree = new FTNode( m_errorOnProbabilities, m_numBoostingIterations, m_minNumInstances, 
+                           m_weightTrimBeta, m_useAIC);
+                       
+    //create a FTLeaves  tree root
+    if (m_modelType==1){ 
+      m_tree = new FTLeavesNode(m_errorOnProbabilities, m_numBoostingIterations, m_minNumInstances, 
+                                m_weightTrimBeta, m_useAIC);
+    }
+    //create a FTInner  tree root
+    if (m_modelType==2)
+      m_tree = new FTInnerNode(m_errorOnProbabilities, m_numBoostingIterations, m_minNumInstances, 
+                               m_weightTrimBeta, m_useAIC);
+        
+    //build tree
+    m_tree.buildClassifier(filteredData);
+    // prune tree
+    m_tree.prune();
+    m_tree.assignIDs(0);
+    m_tree.cleanup();         
+  }
+  
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to compute the distribution for
+   * @return the class probabilities
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+     
+    //replace missing values
+    m_replaceMissing.input(instance);
+    instance = m_replaceMissing.output();
+    
+    //possibly convert nominal attributes
+    if (m_convertNominal) {
+      m_nominalToBinary.input(instance);
+      instance = m_nominalToBinary.output();
+    }
+    return m_tree.distributionForInstance(instance);
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    double maxProb = -1;
+    int maxIndex = 0;
+   
+    //classify by maximum probability
+    double[] probs = distributionForInstance(instance);       
+    for (int j = 0; j < instance.numClasses(); j++) {
+      if (Utils.gr(probs[j], maxProb)) {
+	maxIndex = j;
+	maxProb = probs[j];
+      }
+    }     
+    return (double)maxIndex;      
+  }    
+     
+  /**
+   * Returns a description of the classifier.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+    if (m_tree!=null) {
+      if (m_modelType==0)
+        return "FT tree \n------------------\n" + m_tree.toString();
+      else { 
+        if (m_modelType==1)
+          return "FT Leaves tree \n------------------\n" + m_tree.toString();
+        else  
+          return "FT Inner tree \n------------------\n" + m_tree.toString();
+      }
+    }else{
+      return "No tree built";
+    }
+  }    
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(8);
+    
+    newVector.addElement(new Option("\tBinary splits (convert nominal attributes to binary ones) ",
+                                    "B", 0, "-B"));
+    
+    newVector.addElement(new Option("\tUse error on probabilities instead of misclassification error "+
+                                    "for stopping criterion of LogitBoost.",
+                                    "P", 0, "-P"));
+    
+    newVector.addElement(new Option("\tSet fixed number of iterations for LogitBoost (instead of using "+
+                                    "cross-validation)",
+                                    "I",1,"-I <numIterations>"));
+    
+    newVector.addElement(new Option("\tSet Funtional Tree type to be generate: "+
+                                    " 0 for FT, 1 for FTLeaves and 2 for FTInner",
+                                    "F",1,"-F <modelType>"));
+    
+    newVector.addElement(new Option("\tSet minimum number of instances at which a node can be split (default 15)",
+                                    "M",1,"-M <numInstances>"));
+    
+    newVector.addElement(new Option("\tSet beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.",
+                                    "W",1,"-W <beta>"));
+    
+    newVector.addElement(new Option("\tThe AIC is used to choose the best iteration.",
+                                    "A", 0, "-A"));
+    
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B
+   *  Binary splits (convert nominal attributes to binary ones) </pre>
+   * 
+   * <pre> -P
+   *  Use error on probabilities instead of misclassification error for stopping criterion of LogitBoost.</pre>
+   * 
+   * <pre> -I &lt;numIterations&gt;
+   *  Set fixed number of iterations for LogitBoost (instead of using cross-validation)</pre>
+   * 
+   * <pre> -F &lt;modelType&gt;
+   *  Set Funtional Tree type to be generate:  0 for FT, 1 for FTLeaves and 2 for FTInner</pre>
+   * 
+   * <pre> -M &lt;numInstances&gt;
+   *  Set minimum number of instances at which a node can be split (default 15)</pre>
+   * 
+   * <pre> -W &lt;beta&gt;
+   *  Set beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.</pre>
+   * 
+   * <pre> -A
+   *  The AIC is used to choose the best iteration.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setBinSplit(Utils.getFlag('B', options));
+    setErrorOnProbabilities(Utils.getFlag('P', options));
+
+    String optionString = Utils.getOption('I', options);
+    if (optionString.length() != 0) {
+      setNumBoostingIterations((new Integer(optionString)).intValue());
+    }
+
+    optionString = Utils.getOption('F', options);
+    if (optionString.length() != 0) {
+      setModelType(new SelectedTag(Integer.parseInt(optionString), TAGS_MODEL));
+      // setModelType((new Integer(optionString)).intValue());
+    }
+    
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMinNumInstances((new Integer(optionString)).intValue());
+    }
+
+    optionString = Utils.getOption('W', options);
+    if (optionString.length() != 0) {
+      setWeightTrimBeta((new Double(optionString)).doubleValue());
+    }
+    
+    setUseAIC(Utils.getFlag('A', options));        
+    
+    Utils.checkForRemainingOptions(options);
+	
+  } 
+    
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    String[] options = new String[11];
+    int current = 0;
+
+    if (getBinSplit()) {
+      options[current++] = "-B";
+    } 
+    
+    if (getErrorOnProbabilities()) {
+      options[current++] = "-P";
+    }
+    
+    options[current++] = "-I"; 
+    options[current++] = ""+getNumBoostingIterations();
+    
+    options[current++] = "-F"; 
+    //    options[current++] = ""+getModelType();
+    options[current++] = ""+getModelType().getSelectedTag().getID();
+
+    options[current++] = "-M"; 
+    options[current++] = ""+getMinNumInstances();
+        
+    options[current++] = "-W";
+    options[current++] = ""+getWeightTrimBeta();
+    
+    if (getUseAIC()) {
+      options[current++] = "-A";
+    }
+    
+    while (current < options.length) {
+      options[current++] = "";
+    } 
+    return options;
+  } 
+
+  
+  /**
+   * Get the value of weightTrimBeta.
+   */
+  public double getWeightTrimBeta(){
+    return m_weightTrimBeta;
+  }
+  
+  /**
+   * Get the value of useAIC.
+   *
+   * @return Value of useAIC.
+   */
+  public boolean getUseAIC(){
+    return m_useAIC;
+  }
+ 
+  
+  /**
+   * Set the value of weightTrimBeta.
+   */
+  public void setWeightTrimBeta(double n){
+    m_weightTrimBeta = n;
+  }
+  
+  /**
+   * Set the value of useAIC.
+   *
+   * @param c Value to assign to useAIC.
+   */
+  public void setUseAIC(boolean c){
+    m_useAIC = c;
+  }
+  
+  /**
+   * Get the value of binarySplits.
+   *
+   * @return Value of binarySplits.
+   */
+  public boolean getBinSplit(){
+    return m_convertNominal;
+  }
+
+  /**
+   * Get the value of errorOnProbabilities.
+   *
+   * @return Value of errorOnProbabilities.
+   */
+  public boolean getErrorOnProbabilities(){
+    return m_errorOnProbabilities;
+  }
+  
+  /**
+   * Get the value of numBoostingIterations.
+   *
+   * @return Value of numBoostingIterations.
+   */
+  public int getNumBoostingIterations(){
+    return m_numBoostingIterations;
+  }
+  
+  /**
+   * Get the type of functional tree model being used.
+   *
+   * @return the type of functional tree model.
+   */
+  public SelectedTag getModelType() {
+    return new SelectedTag(m_modelType, TAGS_MODEL);
+  } 
+
+  /**
+   * Set the Functional Tree type.
+   *
+   * @param newMethod Value corresponding to tree type.
+   */
+  public void setModelType(SelectedTag newMethod){
+    if (newMethod.getTags() == TAGS_MODEL) {
+      int c = newMethod.getSelectedTag().getID();
+      if (c==0 || c==1 || c==2) {
+        m_modelType = c;
+      } else  {
+        throw new IllegalArgumentException("Wrong model type, -F value should be: 0, for FT, 1, " +
+                                           "for FTLeaves, and 2, for FTInner "); 
+      }
+    }
+  }
+  
+  /**
+   * Get the value of minNumInstances.
+   *
+   * @return Value of minNumInstances.
+   */
+  public int getMinNumInstances(){
+    return m_minNumInstances;
+  }
+    
+  /**
+   * Set the value of binarySplits.
+   *
+   * @param c Value to assign to binarySplits.
+   */
+  public void setBinSplit(boolean c){
+    m_convertNominal=c;
+  }
+
+  /**
+   * Set the value of errorOnProbabilities.
+   *
+   * @param c Value to assign to errorOnProbabilities.
+   */
+  public void setErrorOnProbabilities(boolean c){
+    m_errorOnProbabilities = c;
+  }
+  
+  /**
+   * Set the value of numBoostingIterations.
+   *
+   * @param c Value to assign to numBoostingIterations.
+   */
+  public void setNumBoostingIterations(int c){
+    m_numBoostingIterations = c;
+  }
+   
+  /**
+   * Set the value of minNumInstances.
+   *
+   * @param c Value to assign to minNumInstances.
+   */
+  public void setMinNumInstances(int c){
+    m_minNumInstances = c;
+  }
+    
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+    return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph describing the tree
+   * @throws Exception if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    return m_tree.graph();
+  }
+
+  /**
+   * Returns the size of the tree
+   * @return the size of the tree
+   */
+  public int measureTreeSize(){
+    return m_tree.numNodes();
+  }
+    
+  /**
+   * Returns the number of leaves in the tree
+   * @return the number of leaves in the tree
+   */
+  public int measureNumLeaves(){
+    return m_tree.numLeaves();
+  }
+     
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(2);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+	
+    return newVector.elements();
+  }
+    
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+					 + " not supported (FT)");
+    }
+  }    
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Classifier for building 'Functional trees', which are classification trees  that could have "
+      +"logistic regression functions at the inner nodes and/or leaves. The algorithm can deal with " 
+      +"binary and multi-class target variables, numeric and nominal attributes and missing values.\n\n"
+      +"For more information see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+      
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Joao Gama");
+    result.setValue(Field.TITLE, "Functional Trees");
+    result.setValue(Field.BOOKTITLE, "Machine Learning");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.VOLUME, "55");
+    result.setValue(Field.PAGES, "219-250");
+    result.setValue(Field.NUMBER, "3");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "Niels Landwehr and Mark Hall and Eibe Frank");
+    additional.setValue(Field.TITLE, "Logistic Model Trees");
+    additional.setValue(Field.BOOKTITLE, "Machine Learning");
+    additional.setValue(Field.YEAR, "2005");
+    additional.setValue(Field.VOLUME, "95");
+    additional.setValue(Field.PAGES, "161-205");
+    additional.setValue(Field.NUMBER, "1-2");
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String modelTypeTipText() {
+    return "The type of FT model. 0, for FT, 1, " +
+      "for FTLeaves, and 2, for FTInner";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binSplitTipText() {
+    return "Convert all nominal attributes to binary ones before building the tree. "
+      +"This means that all splits in the final tree will be binary.";
+    
+  } 
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String errorOnProbabilitiesTipText() {
+    return "Minimize error on probabilities instead of misclassification error when cross-validating the number "
+      +"of LogitBoost iterations. When set, the number of LogitBoost iterations is chosen that minimizes "
+      +"the root mean squared error instead of the misclassification error.";	   
+  } 
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numBoostingIterationsTipText() {
+    return "Set a fixed number of iterations for LogitBoost. If >= 0, this sets a fixed number of LogitBoost "
+      +"iterations that is used everywhere in the tree. If < 0, the number is cross-validated.";
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumInstancesTipText() {
+    return "Set the minimum number of instances at which a node is considered for splitting. "
+      +"The default value is 15.";
+  } 
+      
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightTrimBetaTipText() {
+    return "Set the beta value used for weight trimming in LogitBoost. "
+      +"Only instances carrying (1 - beta)% of the weight from previous iteration "
+      +"are used in the next iteration. Set to 0 for no weight trimming. "
+      +"The default value is 0.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useAICTipText() {
+    return "The AIC is used to determine when to stop LogitBoost iterations. "
+      +"The default is not to use AIC.";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+  
+  /**
+   * Main method for testing this class
+   *
+   * @param argv the commandline options 
+   */
+  public static void main (String [] argv) {	
+    runClassifier(new FT(), argv);
+  } 
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/Id3.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/Id3.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/Id3.java	(revision 29)
@@ -0,0 +1,496 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Id3.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.NoSupportForMissingValuesException;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for constructing an unpruned decision tree based on the ID3 algorithm. Can only deal with nominal attributes. No missing values allowed. Empty leaves may result in unclassified instances. For more information see: <br/>
+ * <br/>
+ * R. Quinlan (1986). Induction of decision trees. Machine Learning. 1(1):81-106.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Quinlan1986,
+ *    author = {R. Quinlan},
+ *    journal = {Machine Learning},
+ *    number = {1},
+ *    pages = {81-106},
+ *    title = {Induction of decision trees},
+ *    volume = {1},
+ *    year = {1986}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class Id3 
+  extends AbstractClassifier 
+  implements TechnicalInformationHandler, Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = -2693678647096322561L;
+  
+  /** The node's successors. */ 
+  private Id3[] m_Successors;
+
+  /** Attribute used for splitting. */
+  private Attribute m_Attribute;
+
+  /** Class value if node is leaf. */
+  private double m_ClassValue;
+
+  /** Class distribution if node is leaf. */
+  private double[] m_Distribution;
+
+  /** Class attribute of dataset. */
+  private Attribute m_ClassAttribute;
+
+  /**
+   * Returns a string describing the classifier.
+   * @return a description suitable for the GUI.
+   */
+  public String globalInfo() {
+
+    return  "Class for constructing an unpruned decision tree based on the ID3 "
+      + "algorithm. Can only deal with nominal attributes. No missing values "
+      + "allowed. Empty leaves may result in unclassified instances. For more "
+      + "information see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "R. Quinlan");
+    result.setValue(Field.YEAR, "1986");
+    result.setValue(Field.TITLE, "Induction of decision trees");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "1");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "81-106");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Builds Id3 decision tree classifier.
+   *
+   * @param data the training data
+   * @exception Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    makeTree(data);
+  }
+
+  /**
+   * Method for building an Id3 tree.
+   *
+   * @param data the training data
+   * @exception Exception if decision tree can't be built successfully
+   */
+  private void makeTree(Instances data) throws Exception {
+
+    // Check if no instances have reached this node.
+    if (data.numInstances() == 0) {
+      m_Attribute = null;
+      m_ClassValue = Utils.missingValue();
+      m_Distribution = new double[data.numClasses()];
+      return;
+    }
+
+    // Compute attribute with maximum information gain.
+    double[] infoGains = new double[data.numAttributes()];
+    Enumeration attEnum = data.enumerateAttributes();
+    while (attEnum.hasMoreElements()) {
+      Attribute att = (Attribute) attEnum.nextElement();
+      infoGains[att.index()] = computeInfoGain(data, att);
+    }
+    m_Attribute = data.attribute(Utils.maxIndex(infoGains));
+    
+    // Make leaf if information gain is zero. 
+    // Otherwise create successors.
+    if (Utils.eq(infoGains[m_Attribute.index()], 0)) {
+      m_Attribute = null;
+      m_Distribution = new double[data.numClasses()];
+      Enumeration instEnum = data.enumerateInstances();
+      while (instEnum.hasMoreElements()) {
+        Instance inst = (Instance) instEnum.nextElement();
+        m_Distribution[(int) inst.classValue()]++;
+      }
+      Utils.normalize(m_Distribution);
+      m_ClassValue = Utils.maxIndex(m_Distribution);
+      m_ClassAttribute = data.classAttribute();
+    } else {
+      Instances[] splitData = splitData(data, m_Attribute);
+      m_Successors = new Id3[m_Attribute.numValues()];
+      for (int j = 0; j < m_Attribute.numValues(); j++) {
+        m_Successors[j] = new Id3();
+        m_Successors[j].makeTree(splitData[j]);
+      }
+    }
+  }
+
+  /**
+   * Classifies a given test instance using the decision tree.
+   *
+   * @param instance the instance to be classified
+   * @return the classification
+   * @throws NoSupportForMissingValuesException if instance has missing values
+   */
+  public double classifyInstance(Instance instance) 
+    throws NoSupportForMissingValuesException {
+
+    if (instance.hasMissingValue()) {
+      throw new NoSupportForMissingValuesException("Id3: no missing values, "
+                                                   + "please.");
+    }
+    if (m_Attribute == null) {
+      return m_ClassValue;
+    } else {
+      return m_Successors[(int) instance.value(m_Attribute)].
+        classifyInstance(instance);
+    }
+  }
+
+  /**
+   * Computes class distribution for instance using decision tree.
+   *
+   * @param instance the instance for which distribution is to be computed
+   * @return the class distribution for the given instance
+   * @throws NoSupportForMissingValuesException if instance has missing values
+   */
+  public double[] distributionForInstance(Instance instance) 
+    throws NoSupportForMissingValuesException {
+
+    if (instance.hasMissingValue()) {
+      throw new NoSupportForMissingValuesException("Id3: no missing values, "
+                                                   + "please.");
+    }
+    if (m_Attribute == null) {
+      return m_Distribution;
+    } else { 
+      return m_Successors[(int) instance.value(m_Attribute)].
+        distributionForInstance(instance);
+    }
+  }
+
+  /**
+   * Prints the decision tree using the private toString method from below.
+   *
+   * @return a textual description of the classifier
+   */
+  public String toString() {
+
+    if ((m_Distribution == null) && (m_Successors == null)) {
+      return "Id3: No model built yet.";
+    }
+    return "Id3\n\n" + toString(0);
+  }
+
+  /**
+   * Computes information gain for an attribute.
+   *
+   * @param data the data for which info gain is to be computed
+   * @param att the attribute
+   * @return the information gain for the given attribute and data
+   * @throws Exception if computation fails
+   */
+  private double computeInfoGain(Instances data, Attribute att) 
+    throws Exception {
+
+    double infoGain = computeEntropy(data);
+    Instances[] splitData = splitData(data, att);
+    for (int j = 0; j < att.numValues(); j++) {
+      if (splitData[j].numInstances() > 0) {
+        infoGain -= ((double) splitData[j].numInstances() /
+                     (double) data.numInstances()) *
+          computeEntropy(splitData[j]);
+      }
+    }
+    return infoGain;
+  }
+
+  /**
+   * Computes the entropy of a dataset.
+   * 
+   * @param data the data for which entropy is to be computed
+   * @return the entropy of the data's class distribution
+   * @throws Exception if computation fails
+   */
+  private double computeEntropy(Instances data) throws Exception {
+
+    double [] classCounts = new double[data.numClasses()];
+    Enumeration instEnum = data.enumerateInstances();
+    while (instEnum.hasMoreElements()) {
+      Instance inst = (Instance) instEnum.nextElement();
+      classCounts[(int) inst.classValue()]++;
+    }
+    double entropy = 0;
+    for (int j = 0; j < data.numClasses(); j++) {
+      if (classCounts[j] > 0) {
+        entropy -= classCounts[j] * Utils.log2(classCounts[j]);
+      }
+    }
+    entropy /= (double) data.numInstances();
+    return entropy + Utils.log2(data.numInstances());
+  }
+
+  /**
+   * Splits a dataset according to the values of a nominal attribute.
+   *
+   * @param data the data which is to be split
+   * @param att the attribute to be used for splitting
+   * @return the sets of instances produced by the split
+   */
+  private Instances[] splitData(Instances data, Attribute att) {
+
+    Instances[] splitData = new Instances[att.numValues()];
+    for (int j = 0; j < att.numValues(); j++) {
+      splitData[j] = new Instances(data, data.numInstances());
+    }
+    Enumeration instEnum = data.enumerateInstances();
+    while (instEnum.hasMoreElements()) {
+      Instance inst = (Instance) instEnum.nextElement();
+      splitData[(int) inst.value(att)].add(inst);
+    }
+    for (int i = 0; i < splitData.length; i++) {
+      splitData[i].compactify();
+    }
+    return splitData;
+  }
+
+  /**
+   * Outputs a tree at a certain level.
+   *
+   * @param level the level at which the tree is to be printed
+   * @return the tree as string at the given level
+   */
+  private String toString(int level) {
+
+    StringBuffer text = new StringBuffer();
+    
+    if (m_Attribute == null) {
+      if (Utils.isMissingValue(m_ClassValue)) {
+        text.append(": null");
+      } else {
+        text.append(": " + m_ClassAttribute.value((int) m_ClassValue));
+      } 
+    } else {
+      for (int j = 0; j < m_Attribute.numValues(); j++) {
+        text.append("\n");
+        for (int i = 0; i < level; i++) {
+          text.append("|  ");
+        }
+        text.append(m_Attribute.name() + " = " + m_Attribute.value(j));
+        text.append(m_Successors[j].toString(level + 1));
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Adds this tree recursively to the buffer.
+   * 
+   * @param id          the unqiue id for the method
+   * @param buffer      the buffer to add the source code to
+   * @return            the last ID being used
+   * @throws Exception  if something goes wrong
+   */
+  protected int toSource(int id, StringBuffer buffer) throws Exception {
+    int                 result;
+    int                 i;
+    int                 newID;
+    StringBuffer[]      subBuffers;
+    
+    buffer.append("\n");
+    buffer.append("  protected static double node" + id + "(Object[] i) {\n");
+    
+    // leaf?
+    if (m_Attribute == null) {
+      result = id;
+      if (Double.isNaN(m_ClassValue))
+        buffer.append("    return Double.NaN;");
+      else
+        buffer.append("    return " + m_ClassValue + ";");
+      if (m_ClassAttribute != null)
+        buffer.append(" // " + m_ClassAttribute.value((int) m_ClassValue));
+      buffer.append("\n");
+      buffer.append("  }\n");
+    }
+    else {
+      buffer.append("    // " + m_Attribute.name() + "\n");
+      
+      // subtree calls
+      subBuffers = new StringBuffer[m_Attribute.numValues()];
+      newID      = id;
+      for (i = 0; i < m_Attribute.numValues(); i++) {
+        newID++;
+
+        buffer.append("    ");
+        if (i > 0)
+          buffer.append("else ");
+        buffer.append("if (((String) i[" + m_Attribute.index() + "]).equals(\"" + m_Attribute.value(i) + "\"))\n");
+        buffer.append("      return node" + newID + "(i);\n");
+
+        subBuffers[i] = new StringBuffer();
+        newID         = m_Successors[i].toSource(newID, subBuffers[i]);
+      }
+      buffer.append("    else\n");
+      buffer.append("      throw new IllegalArgumentException(\"Value '\" + i[" + m_Attribute.index() + "] + \"' is not allowed!\");\n");
+      buffer.append("  }\n");
+
+      // output subtree code
+      for (i = 0; i < m_Attribute.numValues(); i++) {
+        buffer.append(subBuffers[i].toString());
+      }
+      subBuffers = null;
+      
+      result = newID;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns a string that describes the classifier as source. The
+   * classifier will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain a method with the signature:
+   * <pre><code>
+   * public static double classify(Object[] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty. <br/>
+   * Note: works only if class attribute is the last attribute in the dataset.
+   *
+   * @param className the name that should be given to the source class.
+   * @return the object source described by a string
+   * @throws Exception if the souce can't be computed
+   */
+  public String toSource(String className) throws Exception {
+    StringBuffer        result;
+    int                 id;
+    
+    result = new StringBuffer();
+
+    result.append("class " + className + " {\n");
+    result.append("  public static double classify(Object[] i) {\n");
+    id = 0;
+    result.append("    return node" + id + "(i);\n");
+    result.append("  }\n");
+    toSource(id, result);
+    result.append("}\n");
+
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {
+    runClassifier(new Id3(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/J48.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/J48.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/J48.java	(revision 29)
@@ -0,0 +1,1042 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    J48.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.classifiers.trees.j48.BinC45ModelSelection;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.C45PruneableClassifierTree;
+import weka.classifiers.trees.j48.ClassifierTree;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.PruneableClassifierTree;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Matchable;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating a pruned or unpruned C4.5 decision tree. For more information, see<br/>
+ * <br/>
+ * Ross Quinlan (1993). C4.5: Programs for Machine Learning. Morgan Kaufmann Publishers, San Mateo, CA.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Quinlan1993,
+ *    address = {San Mateo, CA},
+ *    author = {Ross Quinlan},
+ *    publisher = {Morgan Kaufmann Publishers},
+ *    title = {C4.5: Programs for Machine Learning},
+ *    year = {1993}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -O
+ *  Do not collapse tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -R
+ *  Use reduced error pruning.</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Set number of folds for reduced error
+ *  pruning. One fold is used as pruning set.
+ *  (default 3)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.</pre>
+ * 
+ * <pre> -J
+ *  Do not use MDL correction for info gain on numeric attributes.</pre>
+ * 
+ * <pre> -Q &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6088 $
+ */
+public class J48 
+  extends AbstractClassifier 
+  implements OptionHandler, Drawable, Matchable, Sourcable, 
+             WeightedInstancesHandler, Summarizable, AdditionalMeasureProducer, 
+             TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -217733168393644444L;
+
+  /** The decision tree */
+  protected ClassifierTree m_root;
+  
+  /** Unpruned tree? */
+  private boolean m_unpruned = false;
+
+  /** Collapse tree? */
+  private boolean m_collapseTree = true;
+
+  /** Confidence level */
+  private float m_CF = 0.25f;
+
+  /** Minimum number of instances */
+  private int m_minNumObj = 2;
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection = true;         
+
+  /** Determines whether probabilities are smoothed using
+      Laplace correction when predictions are generated */
+  private boolean m_useLaplace = false;
+
+  /** Use reduced error pruning? */
+  private boolean m_reducedErrorPruning = false;
+
+  /** Number of folds for reduced error pruning. */
+  private int m_numFolds = 3;
+
+  /** Binary splits on nominal attributes? */
+  private boolean m_binarySplits = false;
+
+  /** Subtree raising to be performed? */
+  private boolean m_subtreeRaising = true;
+
+  /** Cleanup after the tree has been built. */
+  private boolean m_noCleanup = false;
+
+  /** Random number seed for reduced-error pruning. */
+  private int m_Seed = 1;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for generating a pruned or unpruned C4.5 decision tree. For more "
+      + "information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Ross Quinlan");
+    result.setValue(Field.YEAR, "1993");
+    result.setValue(Field.TITLE, "C4.5: Programs for Machine Learning");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+    result.setValue(Field.ADDRESS, "San Mateo, CA");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+    
+    try {
+      if (!m_reducedErrorPruning)
+        result = new C45PruneableClassifierTree(null, !m_unpruned, m_CF, m_subtreeRaising, !m_noCleanup, m_collapseTree).getCapabilities();
+      else
+        result = new PruneableClassifierTree(null, !m_unpruned, m_numFolds, !m_noCleanup, m_Seed).getCapabilities();
+    }
+    catch (Exception e) {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+    
+    result.setOwner(this);
+    
+    return result;
+  }
+  
+  /**
+   * Generates the classifier.
+   *
+   * @param instances the data to train the classifier with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) 
+       throws Exception {
+
+    ModelSelection modSelection;	 
+
+    if (m_binarySplits)
+      modSelection = new BinC45ModelSelection(m_minNumObj, instances, m_useMDLcorrection);
+    else
+      modSelection = new C45ModelSelection(m_minNumObj, instances, m_useMDLcorrection);
+    if (!m_reducedErrorPruning)
+      m_root = new C45PruneableClassifierTree(modSelection, !m_unpruned, m_CF,
+                                              m_subtreeRaising, !m_noCleanup, m_collapseTree);
+    else
+      m_root = new PruneableClassifierTree(modSelection, !m_unpruned, m_numFolds,
+					   !m_noCleanup, m_Seed);
+    m_root.buildClassifier(instances);
+    if (m_binarySplits) {
+      ((BinC45ModelSelection)modSelection).cleanup();
+    } else {
+      ((C45ModelSelection)modSelection).cleanup();
+    }
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification for the instance
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    return m_root.classifyInstance(instance);
+  }
+
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to calculate the class probabilities for
+   * @return the class probabilities
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public final double [] distributionForInstance(Instance instance) 
+       throws Exception {
+
+    return m_root.distributionForInstance(instance, m_useLaplace);
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph describing the tree
+   * @throws Exception if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    return m_root.graph();
+  }
+
+  /**
+   * Returns tree in prefix order.
+   *
+   * @return the tree in prefix order
+   * @throws Exception if something goes wrong
+   */
+  public String prefix() throws Exception {
+    
+    return m_root.prefix();
+  }
+
+
+  /**
+   * Returns tree as an if-then statement.
+   *
+   * @param className the name of the Java class 
+   * @return the tree as a Java if-then type statement
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) throws Exception {
+
+    StringBuffer [] source = m_root.toSource(className);
+    return 
+    "class " + className + " {\n\n"
+    +"  public static double classify(Object[] i)\n"
+    +"    throws Exception {\n\n"
+    +"    double p = Double.NaN;\n"
+    + source[0]  // Assignment code
+    +"    return p;\n"
+    +"  }\n"
+    + source[1]  // Support code
+    +"}\n";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * Valid options are: <p>
+   *
+   * -U <br>
+   * Use unpruned tree.<p>
+   *
+   * -C confidence <br>
+   * Set confidence threshold for pruning. (Default: 0.25) <p>
+   *
+   * -M number <br>
+   * Set minimum number of instances per leaf. (Default: 2) <p>
+   *
+   * -R <br>
+   * Use reduced error pruning. No subtree raising is performed. <p>
+   *
+   * -N number <br>
+   * Set number of folds for reduced error pruning. One fold is
+   * used as the pruning set. (Default: 3) <p>
+   *
+   * -B <br>
+   * Use binary splits for nominal attributes. <p>
+   *
+   * -S <br>
+   * Don't perform subtree raising. <p>
+   *
+   * -L <br>
+   * Do not clean up after the tree has been built.
+   *
+   * -A <br>
+   * If set, Laplace smoothing is used for predicted probabilites. <p>
+   *
+   * -Q <br>
+   * The seed for reduced-error pruning. <p>
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(12);
+
+    newVector.
+	addElement(new Option("\tUse unpruned tree.",
+			      "U", 0, "-U"));
+    newVector.
+	addElement(new Option("\tDo not collapse tree.",
+			      "O", 0, "-O"));
+    newVector.
+	addElement(new Option("\tSet confidence threshold for pruning.\n" +
+			      "\t(default 0.25)",
+			      "C", 1, "-C <pruning confidence>"));
+    newVector.
+	addElement(new Option("\tSet minimum number of instances per leaf.\n" +
+			      "\t(default 2)",
+			      "M", 1, "-M <minimum number of instances>"));
+    newVector.
+	addElement(new Option("\tUse reduced error pruning.",
+			      "R", 0, "-R"));
+    newVector.
+	addElement(new Option("\tSet number of folds for reduced error\n" +
+			      "\tpruning. One fold is used as pruning set.\n" +
+			      "\t(default 3)",
+			      "N", 1, "-N <number of folds>"));
+    newVector.
+	addElement(new Option("\tUse binary splits only.",
+			      "B", 0, "-B"));
+    newVector.
+        addElement(new Option("\tDon't perform subtree raising.",
+			      "S", 0, "-S"));
+    newVector.
+        addElement(new Option("\tDo not clean up after the tree has been built.",
+			      "L", 0, "-L"));
+    newVector.
+      addElement(new Option("\tLaplace smoothing for predicted probabilities.",
+                            "A", 0, "-A"));
+    newVector.
+      addElement(new Option("\tDo not use MDL correction for info gain on numeric attributes.",
+                            "J", 0, "-J"));
+    newVector.
+      addElement(new Option("\tSeed for random data shuffling (default 1).",
+			    "Q", 1, "-Q <seed>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -O
+   *  Do not collapse tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -R
+   *  Use reduced error pruning.</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Set number of folds for reduced error
+   *  pruning. One fold is used as pruning set.
+   *  (default 3)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.</pre>
+   * 
+   * <pre> -J
+   *  Do not use MDL correction for info gain on numeric attributes.</pre>
+   * 
+   * <pre> -Q &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    // Other options
+    String minNumString = Utils.getOption('M', options);
+    if (minNumString.length() != 0) {
+      m_minNumObj = Integer.parseInt(minNumString);
+    } else {
+      m_minNumObj = 2;
+    }
+    m_binarySplits = Utils.getFlag('B', options);
+    m_useLaplace = Utils.getFlag('A', options);
+    m_useMDLcorrection = !Utils.getFlag('J', options);
+
+    // Pruning options
+    m_unpruned = Utils.getFlag('U', options);
+    m_collapseTree = !Utils.getFlag('O', options);
+    m_subtreeRaising = !Utils.getFlag('S', options);
+    m_noCleanup = Utils.getFlag('L', options);
+    if ((m_unpruned) && (!m_subtreeRaising)) {
+      throw new Exception("Subtree raising doesn't need to be unset for unpruned tree!");
+    }
+    m_reducedErrorPruning = Utils.getFlag('R', options);
+    if ((m_unpruned) && (m_reducedErrorPruning)) {
+      throw new Exception("Unpruned tree and reduced error pruning can't be selected " +
+			  "simultaneously!");
+    }
+    String confidenceString = Utils.getOption('C', options);
+    if (confidenceString.length() != 0) {
+      if (m_reducedErrorPruning) {
+	throw new Exception("Setting the confidence doesn't make sense " +
+			    "for reduced error pruning.");
+      } else if (m_unpruned) {
+	throw new Exception("Doesn't make sense to change confidence for unpruned "
+			    +"tree!");
+      } else {
+	m_CF = (new Float(confidenceString)).floatValue();
+	if ((m_CF <= 0) || (m_CF >= 1)) {
+	  throw new Exception("Confidence has to be greater than zero and smaller " +
+			      "than one!");
+	}
+      }
+    } else {
+      m_CF = 0.25f;
+    }
+    String numFoldsString = Utils.getOption('N', options);
+    if (numFoldsString.length() != 0) {
+      if (!m_reducedErrorPruning) {
+	throw new Exception("Setting the number of folds" +
+			    " doesn't make sense if" +
+			    " reduced error pruning is not selected.");
+      } else {
+	m_numFolds = Integer.parseInt(numFoldsString);
+      }
+    } else {
+      m_numFolds = 3;
+    }
+    String seedString = Utils.getOption('Q', options);
+    if (seedString.length() != 0) {
+      m_Seed = Integer.parseInt(seedString);
+    } else {
+      m_Seed = 1;
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [16];
+    int current = 0;
+
+    if (m_noCleanup) {
+      options[current++] = "-L";
+    }
+    if (!m_collapseTree) {
+      options[current++] = "-O";
+    }
+    if (m_unpruned) {
+      options[current++] = "-U";
+    } else {
+      if (!m_subtreeRaising) {
+	options[current++] = "-S";
+      }
+      if (m_reducedErrorPruning) {
+	options[current++] = "-R";
+	options[current++] = "-N"; options[current++] = "" + m_numFolds;
+	options[current++] = "-Q"; options[current++] = "" + m_Seed;
+      } else {
+	options[current++] = "-C"; options[current++] = "" + m_CF;
+      }
+    }
+    if (m_binarySplits) {
+      options[current++] = "-B";
+    }
+    options[current++] = "-M"; options[current++] = "" + m_minNumObj;
+    if (m_useLaplace) {
+      options[current++] = "-A";
+    }
+    if (!m_useMDLcorrection) {
+      options[current++] = "-J";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data " +
+      "when reduced-error pruning is used.";
+  }
+
+  /**
+   * Get the value of Seed.
+   *
+   * @return Value of Seed.
+   */
+  public int getSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the value of Seed.
+   *
+   * @param newSeed Value to assign to Seed.
+   */
+  public void setSeed(int newSeed) {
+    
+    m_Seed = newSeed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useLaplaceTipText() {
+    return "Whether counts at leaves are smoothed based on Laplace.";
+  }
+
+  /**
+   * Get the value of useLaplace.
+   *
+   * @return Value of useLaplace.
+   */
+  public boolean getUseLaplace() {
+    
+    return m_useLaplace;
+  }
+  
+  /**
+   * Set the value of useLaplace.
+   *
+   * @param newuseLaplace Value to assign to useLaplace.
+   */
+  public void setUseLaplace(boolean newuseLaplace) {
+    
+    m_useLaplace = newuseLaplace;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useMDLcorrectionTipText() {
+    return "Whether MDL correction is used when finding splits on numeric attributes.";
+  }
+
+  /**
+   * Get the value of useMDLcorrection.
+   *
+   * @return Value of useMDLcorrection.
+   */
+  public boolean getUseMDLcorrection() {
+    
+    return m_useMDLcorrection;
+  }
+  
+  /**
+   * Set the value of useMDLcorrection.
+   *
+   * @param newuseMDLcorrection Value to assign to useMDLcorrection.
+   */
+  public void setUseMDLcorrection(boolean newuseMDLcorrection) {
+    
+    m_useMDLcorrection = newuseMDLcorrection;
+  }
+  
+  /**
+   * Returns a description of the classifier.
+   * 
+   * @return a description of the classifier
+   */
+  public String toString() {
+
+    if (m_root == null) {
+      return "No classifier built";
+    }
+    if (m_unpruned)
+      return "J48 unpruned tree\n------------------\n" + m_root.toString();
+    else
+      return "J48 pruned tree\n------------------\n" + m_root.toString();
+  }
+
+  /**
+   * Returns a superconcise version of the model
+   * 
+   * @return a summary of the model
+   */
+  public String toSummaryString() {
+
+    return "Number of leaves: " + m_root.numLeaves() + "\n"
+         + "Size of the tree: " + m_root.numNodes() + "\n";
+  }
+
+  /**
+   * Returns the size of the tree
+   * @return the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_root.numNodes();
+  }
+
+  /**
+   * Returns the number of leaves
+   * @return the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_root.numLeaves();
+  }
+
+  /**
+   * Returns the number of rules (same as number of leaves)
+   * @return the number of rules
+   */
+  public double measureNumRules() {
+    return m_root.numLeaves();
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(3);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (j48)");
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String unprunedTipText() {
+    return "Whether pruning is performed.";
+  }
+
+  /**
+   * Get the value of unpruned.
+   *
+   * @return Value of unpruned.
+   */
+  public boolean getUnpruned() {
+    
+    return m_unpruned;
+  }
+  
+  /**
+   * Set the value of unpruned. Turns reduced-error pruning
+   * off if set.
+   * @param v  Value to assign to unpruned.
+   */
+  public void setUnpruned(boolean v) {
+
+    if (v) {
+      m_reducedErrorPruning = false;
+    }
+    m_unpruned = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String collapseTreeTipText() {
+    return "Whether parts are removed that do not reduce training error.";
+  }
+
+  /**
+   * Get the value of collapseTree.
+   *
+   * @return Value of collapseTree.
+   */
+  public boolean getCollapseTree() {
+    
+    return m_collapseTree;
+  }
+  
+  /**
+   * Set the value of collapseTree.
+   * @param v  Value to assign to collapseTree.
+   */
+  public void setCollapseTree(boolean v) {
+
+    m_collapseTree = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String confidenceFactorTipText() {
+    return "The confidence factor used for pruning (smaller values incur "
+      + "more pruning).";
+  }
+  
+  /**
+   * Get the value of CF.
+   *
+   * @return Value of CF.
+   */
+  public float getConfidenceFactor() {
+    
+    return m_CF;
+  }
+  
+  /**
+   * Set the value of CF.
+   *
+   * @param v  Value to assign to CF.
+   */
+  public void setConfidenceFactor(float v) {
+    
+    m_CF = v;
+  }
+   
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumObjTipText() {
+    return "The minimum number of instances per leaf.";
+  }
+
+  /**
+   * Get the value of minNumObj.
+   *
+   * @return Value of minNumObj.
+   */
+  public int getMinNumObj() {
+    
+    return m_minNumObj;
+  }
+  
+  /**
+   * Set the value of minNumObj.
+   *
+   * @param v  Value to assign to minNumObj.
+   */
+  public void setMinNumObj(int v) {
+    
+    m_minNumObj = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String reducedErrorPruningTipText() {
+    return "Whether reduced-error pruning is used instead of C.4.5 pruning.";
+  }
+ 
+  /**
+   * Get the value of reducedErrorPruning. 
+   *
+   * @return Value of reducedErrorPruning.
+   */
+  public boolean getReducedErrorPruning() {
+    
+    return m_reducedErrorPruning;
+  }
+  
+  /**
+   * Set the value of reducedErrorPruning. Turns
+   * unpruned trees off if set.
+   *
+   * @param v  Value to assign to reducedErrorPruning.
+   */
+  public void setReducedErrorPruning(boolean v) {
+    
+    if (v) {
+      m_unpruned = false;
+    }
+    m_reducedErrorPruning = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Determines the amount of data used for reduced-error pruning. "
+      + " One fold is used for pruning, the rest for growing the tree.";
+  }
+
+  /**
+   * Get the value of numFolds.
+   *
+   * @return Value of numFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_numFolds;
+  }
+  
+  /**
+   * Set the value of numFolds.
+   *
+   * @param v  Value to assign to numFolds.
+   */
+  public void setNumFolds(int v) {
+    
+    m_numFolds = v;
+  }
+ 
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binarySplitsTipText() {
+    return "Whether to use binary splits on nominal attributes when "
+      + "building the trees.";
+  }
+  
+  /**
+   * Get the value of binarySplits.
+   *
+   * @return Value of binarySplits.
+   */
+  public boolean getBinarySplits() {
+    
+    return m_binarySplits;
+  }
+  
+  /**
+   * Set the value of binarySplits.
+   *
+   * @param v  Value to assign to binarySplits.
+   */
+  public void setBinarySplits(boolean v) {
+    
+    m_binarySplits = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String subtreeRaisingTipText() {
+    return "Whether to consider the subtree raising operation when pruning.";
+  }
+ 
+  /**
+   * Get the value of subtreeRaising.
+   *
+   * @return Value of subtreeRaising.
+   */
+  public boolean getSubtreeRaising() {
+    
+    return m_subtreeRaising;
+  }
+  
+  /**
+   * Set the value of subtreeRaising.
+   *
+   * @param v  Value to assign to subtreeRaising.
+   */
+  public void setSubtreeRaising(boolean v) {
+    
+    m_subtreeRaising = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String saveInstanceDataTipText() {
+    return "Whether to save the training data for visualization.";
+  }
+
+  /**
+   * Check whether instance data is to be saved.
+   *
+   * @return true if instance data is saved
+   */
+  public boolean getSaveInstanceData() {
+    
+    return m_noCleanup;
+  }
+  
+  /**
+   * Set whether instance data is to be saved.
+   * @param v true if instance data is to be saved
+   */
+  public void setSaveInstanceData(boolean v) {
+    
+    m_noCleanup = v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+ 
+  /**
+   * Main method for testing this class
+   *
+   * @param argv the commandline options
+   */
+  public static void main(String [] argv){
+    runClassifier(new J48(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/J48graft.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/J48graft.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/J48graft.java	(revision 29)
@@ -0,0 +1,818 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    J48graft.java
+ *    Copyright (C) 2007 Geoff Webb & Janice Boughton
+ *    (adapted from code written by Eibe Frank).
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.classifiers.trees.j48.BinC45ModelSelection;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.C45PruneableClassifierTreeG;
+import weka.classifiers.trees.j48.ClassifierTree;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Matchable;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating a grafted (pruned or unpruned) C4.5 decision tree. For more information, see<br/>
+ * <br/>
+ * Geoff Webb: Decision Tree Grafting From the All-Tests-But-One Partition. In: , San Francisco, CA, 1999.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Webb1999,
+ *    address = {San Francisco, CA},
+ *    author = {Geoff Webb},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {Decision Tree Grafting From the All-Tests-But-One Partition},
+ *    year = {1999}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -U
+ *  Use unpruned tree.</pre>
+ * 
+ * <pre> -C &lt;pruning confidence&gt;
+ *  Set confidence threshold for pruning.
+ *  (default 0.25)</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.
+ *  (default 2)</pre>
+ * 
+ * <pre> -B
+ *  Use binary splits only.</pre>
+ * 
+ * <pre> -S
+ *  Don't perform subtree raising.</pre>
+ * 
+ * <pre> -L
+ *  Do not clean up after the tree has been built.</pre>
+ * 
+ * <pre> -A
+ *  Laplace smoothing for predicted probabilities.  (note: this option only affects initial tree; grafting process always uses laplace).</pre>
+ * 
+ * <pre> -E
+ *  Relabel when grafting.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Janice Boughton (jrbought@csse.monash.edu.au)
+ *  (based on J48.java written by Eibe Frank)
+ * @version $Revision: 6088 $
+ */
+public class J48graft 
+  extends AbstractClassifier
+  implements OptionHandler, Drawable, Matchable, Sourcable, 
+             WeightedInstancesHandler, Summarizable,
+             AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8823716098042427799L;
+
+  /** The decision tree */
+  private ClassifierTree m_root;
+
+  /** Unpruned tree? */
+  private boolean m_unpruned = false;
+
+  /** Confidence level */
+  private float m_CF = 0.25f;
+
+  /** Minimum number of instances */
+  private int m_minNumObj = 2;
+
+  /** Determines whether probabilities are smoothed using
+      Laplace correction when predictions are generated */
+  private boolean m_useLaplace = false;
+
+  /** Number of folds for reduced error pruning. */
+  private int m_numFolds = 3;
+
+  /** Binary splits on nominal attributes? */
+  private boolean m_binarySplits = false;
+
+  /** Subtree raising to be performed? */
+  private boolean m_subtreeRaising = true;
+
+  /** Cleanup after the tree has been built. */
+  private boolean m_noCleanup = false;
+
+  /** relabel instances when grafting */
+  private boolean m_relabel = false;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return  "Class for generating a grafted (pruned or unpruned) C4.5 "
+      + "decision tree. For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Geoff Webb");
+    result.setValue(Field.YEAR, "1999");
+    result.setValue(Field.TITLE, "Decision Tree Grafting From the All-Tests-But-One Partition");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    result.setValue(Field.ADDRESS, "San Francisco, CA");
+
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities      result;
+
+    try {
+     result = new C45PruneableClassifierTreeG(null, !m_unpruned, m_CF, m_subtreeRaising, m_relabel, !m_noCleanup).getCapabilities();
+    }
+    catch (Exception e) {
+      result = new Capabilities(this);
+      result.disableAll();
+    }
+
+    result.setOwner(this);
+
+    return result;
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances the data to train the classifier with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances)
+       throws Exception {
+
+    ModelSelection modSelection;
+
+    if (m_binarySplits)
+      modSelection = new BinC45ModelSelection(m_minNumObj, instances, true);
+    else
+      modSelection = new C45ModelSelection(m_minNumObj, instances, true);
+      m_root = new C45PruneableClassifierTreeG(modSelection, 
+                              !m_unpruned, m_CF, m_subtreeRaising, 
+                               m_relabel, !m_noCleanup);
+    m_root.buildClassifier(instances);
+
+    if (m_binarySplits) {
+      ((BinC45ModelSelection)modSelection).cleanup();
+    } else {
+      ((C45ModelSelection)modSelection).cleanup();
+    }
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification for the instance
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    return m_root.classifyInstance(instance);
+  }
+
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to calculate the class probabilities for
+   * @return the class probabilities
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public final double [] distributionForInstance(Instance instance) 
+       throws Exception {
+
+    return m_root.distributionForInstance(instance, m_useLaplace);
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph describing the tree
+   * @throws Exception if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    return m_root.graph();
+  }
+
+  /**
+   * Returns tree in prefix order.
+   *
+   * @return the tree in prefix order
+   * @throws Exception if something goes wrong
+   */
+  public String prefix() throws Exception {
+    
+    return m_root.prefix();
+  }
+
+
+  /**
+   * Returns tree as an if-then statement.
+   *
+   * @param className the name of the Java class
+   * @return the tree as a Java if-then type statement
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) throws Exception {
+
+    StringBuffer [] source = m_root.toSource(className);
+    return 
+    "class " + className + " {\n\n"
+    +"  public static double classify(Object [] i)\n"
+    +"    throws Exception {\n\n"
+    +"    double p = Double.NaN;\n"
+    + source[0]  // Assignment code
+    +"    return p;\n"
+    +"  }\n"
+    + source[1]  // Support code
+    +"}\n";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * Valid options are: <p>
+   *
+   * -U <br>
+   * Use unpruned tree.<p>
+   *
+   * -C confidence <br>
+   * Set confidence threshold for pruning. (Default: 0.25) <p>
+   *
+   * -M number <br>
+   * Set minimum number of instances per leaf. (Default: 2) <p>
+   *
+   * -B <br>
+   * Use binary splits for nominal attributes. <p>
+   *
+   * -S <br>
+   * Don't perform subtree raising. <p>
+   *
+   * -L <br>
+   * Do not clean up after the tree has been built.
+   *
+   * -A <br>
+   * If set, Laplace smoothing is used for predicted probabilites. 
+   *  (note: this option only affects initial tree; grafting process always uses laplace). <p>
+   *
+   * -E <br>
+   * Allow relabelling when grafting. <p>
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(9);
+
+    newVector.
+       addElement(new Option("\tUse unpruned tree.",
+			      "U", 0, "-U"));
+    newVector.
+       addElement(new Option("\tSet confidence threshold for pruning.\n" +
+                             "\t(default 0.25)",
+			     "C", 1, "-C <pruning confidence>"));
+    newVector.
+       addElement(new Option("\tSet minimum number of instances per leaf.\n" +
+			      "\t(default 2)",
+			      "M", 1, "-M <minimum number of instances>"));
+    newVector.
+       addElement(new Option("\tUse binary splits only.",
+			      "B", 0, "-B"));
+    newVector.
+       addElement(new Option("\tDon't perform subtree raising.",
+			      "S", 0, "-S"));
+    newVector.
+       addElement(new Option("\tDo not clean up after the tree has been built.",
+			      "L", 0, "-L"));
+    newVector.
+       addElement(new Option("\tLaplace smoothing for predicted probabilities.  (note: this option only affects initial tree; grafting process always uses laplace).",
+ 
+			      "A", 0, "-A"));
+    newVector.
+       addElement(new Option("\tRelabel when grafting.",
+                             "E", 0, "-E"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -U
+   *  Use unpruned tree.</pre>
+   * 
+   * <pre> -C &lt;pruning confidence&gt;
+   *  Set confidence threshold for pruning.
+   *  (default 0.25)</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.
+   *  (default 2)</pre>
+   * 
+   * <pre> -B
+   *  Use binary splits only.</pre>
+   * 
+   * <pre> -S
+   *  Don't perform subtree raising.</pre>
+   * 
+   * <pre> -L
+   *  Do not clean up after the tree has been built.</pre>
+   * 
+   * <pre> -A
+   *  Laplace smoothing for predicted probabilities.  (note: this option only affects initial tree; grafting process always uses laplace).</pre>
+   * 
+   * <pre> -E
+   *  Relabel when grafting.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+ 
+    // Other options
+    String minNumString = Utils.getOption('M', options);
+    if (minNumString.length() != 0) {
+      m_minNumObj = Integer.parseInt(minNumString);
+    } else {
+      m_minNumObj = 2;
+    }
+    m_binarySplits = Utils.getFlag('B', options);
+    m_useLaplace = Utils.getFlag('A', options);
+
+    // Pruning options
+    m_unpruned = Utils.getFlag('U', options);
+    m_subtreeRaising = !Utils.getFlag('S', options);
+    m_noCleanup = Utils.getFlag('L', options);
+		if ((m_unpruned) && (!m_subtreeRaising)) {
+      throw new Exception("Subtree raising doesn't need to be unset for unpruned tree!");
+    }
+    m_relabel = Utils.getFlag('E', options);
+    String confidenceString = Utils.getOption('C', options);
+    if (confidenceString.length() != 0) {
+      if (m_unpruned) {
+	throw new Exception("Doesn't make sense to change confidence for unpruned "
+			    +"tree!");
+      } else {
+	m_CF = (new Float(confidenceString)).floatValue();
+	if ((m_CF <= 0) || (m_CF >= 1)) {
+	  throw new Exception("Confidence has to be greater than zero and smaller " +
+			      "than one!");
+	}
+      }
+    } else {
+      m_CF = 0.25f;
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [10];
+    int current = 0;
+
+    if (m_noCleanup) {
+      options[current++] = "-L";
+    }
+    if (m_unpruned) {
+      options[current++] = "-U";
+    } else {
+      if (!m_subtreeRaising) {
+	options[current++] = "-S";
+      }
+      options[current++] = "-C"; options[current++] = "" + m_CF;
+    }
+    if (m_binarySplits) {
+      options[current++] = "-B";
+    }
+    options[current++] = "-M"; options[current++] = "" + m_minNumObj;
+    if (m_useLaplace) {
+      options[current++] = "-A";
+    }
+
+    if(m_relabel) {
+       options[current++] = "-E";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useLaplaceTipText() {
+    return "Whether counts at leaves are smoothed based on Laplace.";
+  }
+
+  /**
+   * Get the value of useLaplace.
+   *
+   * @return Value of useLaplace.
+   */
+  public boolean getUseLaplace() {
+    
+    return m_useLaplace;
+  }
+  
+  /**
+   * Set the value of useLaplace.
+   *
+   * @param newuseLaplace Value to assign to useLaplace.
+   */
+  public void setUseLaplace(boolean newuseLaplace) {
+    
+    m_useLaplace = newuseLaplace;
+  }
+  
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a description of the classifier
+   */
+  public String toString() {
+
+    if (m_root == null) {
+      return "No classifier built";
+    }
+    if (m_unpruned)
+      return "J48graft unpruned tree\n------------------\n" + m_root.toString();
+    else
+      return "J48graft pruned tree\n------------------\n" + m_root.toString();
+  }
+
+  /**
+   * Returns a superconcise version of the model
+   *
+   * @return a summary of the model
+   */
+  public String toSummaryString() {
+
+    return "Number of leaves: " + m_root.numLeaves() + "\n"
+         + "Size of the tree: " + m_root.numNodes() + "\n";
+  }
+
+  /**
+   * Returns the size of the tree
+   * @return the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_root.numNodes();
+  }
+
+  /**
+   * Returns the number of leaves
+   * @return the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_root.numLeaves();
+  }
+
+  /**
+   * Returns the number of rules (same as number of leaves)
+   * @return the number of rules
+   */
+  public double measureNumRules() {
+    return m_root.numLeaves();
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(3);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareTo("measureNumRules") == 0) {
+      return measureNumRules();
+    } else if (additionalMeasureName.compareTo("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareTo("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName
+			  + " not supported (j48)");
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String unprunedTipText() {
+    return "Whether pruning is performed.";
+  }
+
+  /**
+   * Get the value of unpruned.
+   *
+   * @return Value of unpruned.
+   */
+  public boolean getUnpruned() {
+
+    return m_unpruned;
+  }
+
+  /**
+   * Set the value of unpruned.
+   * @param v  Value to assign to unpruned.
+   */
+  public void setUnpruned(boolean v) {
+
+    m_unpruned = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String relabelTipText() {
+    return "Whether relabelling is allowed during grafting.";
+  }
+
+  /**
+   * Get the value of relabelling
+   *
+   * @return Value of relabelling.
+   */
+  public boolean getRelabel() {
+
+    return m_relabel;
+  }
+
+  /**
+   * Set the value of relabelling. 
+   *
+   * @param v  Value to assign to relabelling flag.
+   */
+  public void setRelabel(boolean v) {
+    m_relabel = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String confidenceFactorTipText() {
+    return "The confidence factor used for pruning (smaller values incur "
+      + "more pruning).";
+  }
+
+  /**
+   * Get the value of CF.
+   *
+   * @return Value of CF.
+   */
+  public float getConfidenceFactor() {
+    
+    return m_CF;
+  }
+  
+  /**
+   * Set the value of CF.
+   *
+   * @param v  Value to assign to CF.
+   */
+  public void setConfidenceFactor(float v) {
+    
+    m_CF = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumObjTipText() {
+    return "The minimum number of instances per leaf.";
+  }
+
+  /**
+   * Get the value of minNumObj.
+   *
+   * @return Value of minNumObj.
+   */
+  public int getMinNumObj() {
+    
+    return m_minNumObj;
+  }
+  
+  /**
+   * Set the value of minNumObj.
+   *
+   * @param v  Value to assign to minNumObj.
+   */
+  public void setMinNumObj(int v) {
+    
+    m_minNumObj = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binarySplitsTipText() {
+    return "Whether to use binary splits on nominal attributes when "
+      + "building the trees.";
+  }
+  
+  /**
+   * Get the value of binarySplits.
+   *
+   * @return Value of binarySplits.
+   */
+  public boolean getBinarySplits() {
+
+    return m_binarySplits;
+  }
+
+  /**
+   * Set the value of binarySplits.
+   *
+   * @param v  Value to assign to binarySplits.
+   */
+  public void setBinarySplits(boolean v) {
+    
+    m_binarySplits = v;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String subtreeRaisingTipText() {
+    return "Whether to consider the subtree raising operation when pruning.";
+  }
+ 
+  /**
+   * Get the value of subtreeRaising.
+   *
+   * @return Value of subtreeRaising.
+   */
+  public boolean getSubtreeRaising() {
+    
+    return m_subtreeRaising;
+  }
+  
+  /**
+   * Set the value of subtreeRaising.
+   *
+   * @param v  Value to assign to subtreeRaising.
+   */
+  public void setSubtreeRaising(boolean v) {
+    
+    m_subtreeRaising = v;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String saveInstanceDataTipText() {
+    return "Whether to save the training data for visualization.";
+  }
+
+  /**
+   * Check whether instance data is to be saved.
+   *
+   * @return true if instance data is saved
+   */
+  public boolean getSaveInstanceData() {
+    
+    return m_noCleanup;
+  }
+  
+  /**
+   * Set whether instance data is to be saved.
+   * @param v true if instance data is to be saved
+   */
+  public void setSaveInstanceData(boolean v) {
+
+    m_noCleanup = v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+ 
+  /**
+   * Main method for testing this class
+   *
+   * @param argv the commandline options
+   */
+  public static void main(String [] argv){
+    runClassifier(new J48graft(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/LADTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/LADTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/LADTree.java	(revision 29)
@@ -0,0 +1,1505 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LADTree.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.*;
+import weka.core.Capabilities;
+import weka.core.Capabilities.Capability;
+import weka.core.*;
+import weka.classifiers.trees.adtree.ReferenceInstances;
+import java.util.*;
+import java.io.*;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating a multi-class alternating decision tree using the LogitBoost strategy. For more info, see<br/>
+ * <br/>
+ * Geoffrey Holmes, Bernhard Pfahringer, Richard Kirkby, Eibe Frank, Mark Hall: Multiclass alternating decision trees. In: ECML, 161-172, 2001.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Holmes2001,
+ *    author = {Geoffrey Holmes and Bernhard Pfahringer and Richard Kirkby and Eibe Frank and Mark Hall},
+ *    booktitle = {ECML},
+ *    pages = {161-172},
+ *    publisher = {Springer},
+ *    title = {Multiclass alternating decision trees},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;number of boosting iterations&gt;
+ *  Number of boosting iterations.
+ *  (Default = 10)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby
+ * @version $Revision: 6035 $
+*/
+
+public class LADTree
+  extends AbstractClassifier implements Drawable,
+                                AdditionalMeasureProducer,
+                                TechnicalInformationHandler {
+
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -4940716114518300302L;
+
+  // Constant from LogitBoost
+  protected double Z_MAX = 4;
+
+  // Number of classes
+  protected int m_numOfClasses;
+
+  // Instances as reference instances
+  protected ReferenceInstances m_trainInstances;
+
+  // Root of the tree
+  protected PredictionNode m_root = null; 
+
+  // To keep track of the order in which splits are added
+  protected int m_lastAddedSplitNum = 0;
+
+  // Indices for numeric attributes
+  protected int[] m_numericAttIndices;
+
+  // Variables to keep track of best options
+  protected double m_search_smallestLeastSquares;
+  protected PredictionNode m_search_bestInsertionNode;
+  protected Splitter m_search_bestSplitter;
+  protected Instances m_search_bestPathInstances;
+
+  // A collection of splitter nodes
+  protected FastVector m_staticPotentialSplitters2way;
+
+  // statistics
+  protected int m_nodesExpanded = 0;
+  protected int m_examplesCounted = 0;
+
+  // options
+  protected int m_boostingIterations = 10;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Class for generating a multi-class alternating decision tree using " +
+      "the LogitBoost strategy. For more info, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+        
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Geoffrey Holmes and Bernhard Pfahringer and Richard Kirkby and Eibe Frank and Mark Hall");
+    result.setValue(Field.TITLE, "Multiclass alternating decision trees");
+    result.setValue(Field.BOOKTITLE, "ECML");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "161-172");
+    result.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /** helper classes ********************************************************************/
+
+  protected class LADInstance extends DenseInstance {
+    public double[] fVector;
+    public double[] wVector;
+    public double[] pVector;
+    public double[] zVector;
+    public LADInstance(Instance instance) {
+    
+      super(instance);
+      
+      setDataset(instance.dataset()); // preserve dataset
+
+      // set up vectors
+      fVector = new double[m_numOfClasses];
+      wVector = new double[m_numOfClasses];
+      pVector = new double[m_numOfClasses];
+      zVector = new double[m_numOfClasses];
+
+      // set initial probabilities
+      double initProb = 1.0 / ((double) m_numOfClasses);
+      for (int i=0; i<m_numOfClasses; i++) {
+	pVector[i] = initProb;
+      }
+      updateZVector();
+      updateWVector();
+    }
+    public void updateWeights(double[] fVectorIncrement) {
+      for (int i=0; i<fVector.length; i++) {
+	fVector[i] += fVectorIncrement[i];
+      }
+      updateVectors(fVector);
+    }
+    public void updateVectors(double[] newFVector) {
+      updatePVector(newFVector);
+      updateZVector();
+      updateWVector();
+    }
+    public void updatePVector(double[] newFVector) {
+      double max = newFVector[Utils.maxIndex(newFVector)];
+      for (int i=0; i<pVector.length; i++) {
+	pVector[i] = Math.exp(newFVector[i] - max);
+      }
+      Utils.normalize(pVector);
+    }
+    public void updateWVector() {
+      for (int i=0; i<wVector.length; i++) {
+	wVector[i] = (yVector(i) - pVector[i]) / zVector[i];
+      }
+    }
+    public void updateZVector() {
+
+      for (int i=0; i<zVector.length; i++) {
+	if (yVector(i) == 1) {
+	  zVector[i] = 1.0 / pVector[i];
+	  if (zVector[i] > Z_MAX) { // threshold
+	    zVector[i] = Z_MAX;
+	  }
+	} else {
+	  zVector[i] = -1.0 / (1.0 - pVector[i]);
+	  if (zVector[i] < -Z_MAX) { // threshold
+	    zVector[i] = -Z_MAX;
+	  }
+	}
+      }
+    }
+    public double yVector(int index) {
+      return (index == (int) classValue() ? 1.0 : 0.0); 
+    }
+    public Object copy() {
+      LADInstance copy = new LADInstance((Instance) super.copy());
+      System.arraycopy(fVector, 0, copy.fVector, 0, fVector.length);
+      System.arraycopy(wVector, 0, copy.wVector, 0, wVector.length);
+      System.arraycopy(pVector, 0, copy.pVector, 0, pVector.length);
+      System.arraycopy(zVector, 0, copy.zVector, 0, zVector.length);
+      return copy;
+    }
+    public String toString() {
+
+      StringBuffer text = new StringBuffer();
+      text.append(" * F(");
+      for (int i=0; i<fVector.length; i++) {
+	text.append(Utils.doubleToString(fVector[i], 3));
+	if (i<fVector.length-1) text.append(",");
+      }
+      text.append(") P(");
+      for (int i=0; i<pVector.length; i++) {
+	text.append(Utils.doubleToString(pVector[i], 3));
+	if (i<pVector.length-1) text.append(",");
+      }
+      text.append(") W(");
+      for (int i=0; i<wVector.length; i++) {
+	text.append(Utils.doubleToString(wVector[i], 3));
+	if (i<wVector.length-1) text.append(",");
+      }
+      text.append(")");
+      return super.toString() + text.toString();
+
+    }
+  }
+
+  protected class PredictionNode implements Serializable, Cloneable{
+    private double[] values;
+    private FastVector children; // any number of splitter nodes
+    
+    public PredictionNode(double[] newValues) {
+      values = new double[m_numOfClasses];
+      setValues(newValues);
+      children = new FastVector();
+    }
+    public void setValues(double[] newValues) {
+      System.arraycopy(newValues, 0, values, 0, m_numOfClasses);
+    }
+    public double[] getValues() {
+      return values;
+    }
+    public FastVector getChildren() { return children; }
+    public Enumeration children() { return children.elements(); }
+    public void addChild(Splitter newChild) { // merges, adds a clone (deep copy)
+      Splitter oldEqual = null;
+      for (Enumeration e = children(); e.hasMoreElements(); ) {
+	Splitter split = (Splitter) e.nextElement();
+	if (newChild.equalTo(split)) { oldEqual = split; break; }
+      }
+      if (oldEqual == null) {
+	Splitter addChild = (Splitter) newChild.clone();
+	addChild.orderAdded = ++m_lastAddedSplitNum;
+	children.addElement(addChild);
+      }
+      else { // do a merge
+	for (int i=0; i<newChild.getNumOfBranches(); i++) {
+	  PredictionNode oldPred = oldEqual.getChildForBranch(i);
+	  PredictionNode newPred = newChild.getChildForBranch(i);
+	  if (oldPred != null && newPred != null)
+	    oldPred.merge(newPred);
+	}
+      }
+    }
+    public Object clone() { // does a deep copy (recurses through tree)
+      PredictionNode clone = new PredictionNode(values);
+      // should actually clone once merges are enabled!
+      for (Enumeration e = children.elements(); e.hasMoreElements(); )
+	clone.children.addElement((Splitter)((Splitter) e.nextElement()).clone());
+      return clone;
+    }
+    public void merge(PredictionNode merger) {
+      // need to merge linear models here somehow
+      for (int i=0; i<m_numOfClasses; i++) values[i] += merger.values[i];
+      for (Enumeration e = merger.children(); e.hasMoreElements(); ) {
+	addChild((Splitter)e.nextElement());
+      }
+    }
+  }
+
+  /** splitter classes ******************************************************************/
+
+  protected abstract class Splitter implements Serializable, Cloneable {
+      protected int attIndex;
+    public int orderAdded;
+    public abstract int getNumOfBranches();
+    public abstract int branchInstanceGoesDown(Instance i);
+    public abstract Instances instancesDownBranch(int branch, Instances sourceInstances);
+    public abstract String attributeString();
+    public abstract String comparisonString(int branchNum);
+    public abstract boolean equalTo(Splitter compare);
+    public abstract void setChildForBranch(int branchNum, PredictionNode childPredictor);
+    public abstract PredictionNode getChildForBranch(int branchNum);
+    public abstract Object clone();
+  }
+
+  protected class TwoWayNominalSplit extends Splitter {
+      //private int attIndex;
+    private int trueSplitValue;
+    private PredictionNode[] children;
+    public TwoWayNominalSplit(int _attIndex, int _trueSplitValue) {
+      attIndex = _attIndex; trueSplitValue = _trueSplitValue;
+      children = new PredictionNode[2];
+    }
+    public int getNumOfBranches() { return 2; }
+    public int branchInstanceGoesDown(Instance inst) {
+      if (inst.isMissing(attIndex)) return -1;
+      else if (inst.value(attIndex) == trueSplitValue) return 0;
+      else return 1;
+    }
+    public Instances instancesDownBranch(int branch, Instances instances) {
+      ReferenceInstances filteredInstances = new ReferenceInstances(instances, 1);
+      if (branch == -1) {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (inst.isMissing(attIndex)) filteredInstances.addReference(inst);
+	}
+      } else if (branch == 0) {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (!inst.isMissing(attIndex) && inst.value(attIndex) == trueSplitValue)
+	    filteredInstances.addReference(inst);
+	}
+      } else {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (!inst.isMissing(attIndex) && inst.value(attIndex) != trueSplitValue)
+	    filteredInstances.addReference(inst);
+	}
+      }
+      return filteredInstances;
+    }
+    public String attributeString() {
+      return m_trainInstances.attribute(attIndex).name();
+    }
+    public String comparisonString(int branchNum) {
+      Attribute att = m_trainInstances.attribute(attIndex);
+      if (att.numValues() != 2) 
+	return ((branchNum == 0 ? "= " : "!= ") + att.value(trueSplitValue));
+      else return ("= " + (branchNum == 0 ?
+			   att.value(trueSplitValue) :
+			   att.value(trueSplitValue == 0 ? 1 : 0)));
+    }
+    public boolean equalTo(Splitter compare) {
+      if (compare instanceof TwoWayNominalSplit) { // test object type
+	TwoWayNominalSplit compareSame = (TwoWayNominalSplit) compare;
+	return (attIndex == compareSame.attIndex &&
+		trueSplitValue == compareSame.trueSplitValue);
+      } else return false;
+    }
+    public void setChildForBranch(int branchNum, PredictionNode childPredictor) {
+      children[branchNum] = childPredictor;
+    }
+    public PredictionNode getChildForBranch(int branchNum) {
+      return children[branchNum];
+    }
+    public Object clone() { // deep copy
+      TwoWayNominalSplit clone = new TwoWayNominalSplit(attIndex, trueSplitValue);
+      if (children[0] != null)
+	clone.setChildForBranch(0, (PredictionNode) children[0].clone());
+      if (children[1] != null)
+	clone.setChildForBranch(1, (PredictionNode) children[1].clone());
+      return clone;
+    }
+  }
+
+  protected class TwoWayNumericSplit extends Splitter implements Cloneable {
+      //private int attIndex;
+    private double splitPoint;
+    private PredictionNode[] children;
+    public TwoWayNumericSplit(int _attIndex, double _splitPoint) {
+      attIndex = _attIndex;
+      splitPoint = _splitPoint;
+      children = new PredictionNode[2];
+    }
+    public TwoWayNumericSplit(int _attIndex, Instances instances) throws Exception {
+      attIndex = _attIndex;
+      splitPoint = findSplit(instances, attIndex);
+      children = new PredictionNode[2];
+    }
+    public int getNumOfBranches() { return 2; }
+    public int branchInstanceGoesDown(Instance inst) {
+      if (inst.isMissing(attIndex)) return -1;
+      else if (inst.value(attIndex) < splitPoint) return 0;
+      else return 1;
+    }
+    public Instances instancesDownBranch(int branch, Instances instances) {
+      ReferenceInstances filteredInstances = new ReferenceInstances(instances, 1);
+      if (branch == -1) {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (inst.isMissing(attIndex)) filteredInstances.addReference(inst);
+	}
+      } else if (branch == 0) {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (!inst.isMissing(attIndex) && inst.value(attIndex) < splitPoint)
+	    filteredInstances.addReference(inst);
+	}
+      } else {
+	for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	  Instance inst = (Instance) e.nextElement();
+	  if (!inst.isMissing(attIndex) && inst.value(attIndex) >= splitPoint)
+	    filteredInstances.addReference(inst);
+	}
+      }
+      return filteredInstances;
+    }
+    public String attributeString() {
+      return m_trainInstances.attribute(attIndex).name();
+    }
+    public String comparisonString(int branchNum) {
+      return ((branchNum == 0 ? "< " : ">= ") + Utils.doubleToString(splitPoint, 3));
+    }
+    public boolean equalTo(Splitter compare) {
+      if (compare instanceof TwoWayNumericSplit) { // test object type
+	TwoWayNumericSplit compareSame = (TwoWayNumericSplit) compare;
+	return (attIndex == compareSame.attIndex &&
+		splitPoint == compareSame.splitPoint);
+      } else return false;
+    }
+    public void setChildForBranch(int branchNum, PredictionNode childPredictor) {
+      children[branchNum] = childPredictor;
+    }
+    public PredictionNode getChildForBranch(int branchNum) {
+      return children[branchNum];
+    }
+    public Object clone() { // deep copy
+      TwoWayNumericSplit clone = new TwoWayNumericSplit(attIndex, splitPoint);
+      if (children[0] != null)
+	clone.setChildForBranch(0, (PredictionNode) children[0].clone());
+      if (children[1] != null)
+	clone.setChildForBranch(1, (PredictionNode) children[1].clone());
+      return clone;
+    }
+    private double findSplit(Instances instances, int index) throws Exception {
+      double splitPoint = 0;
+      double bestVal = Double.MAX_VALUE, currVal, currCutPoint;
+      int numMissing = 0;
+      double[][] distribution = new double[3][instances.numClasses()];   
+
+      // Compute counts for all the values
+      for (int i = 0; i < instances.numInstances(); i++) {
+	Instance inst = instances.instance(i);
+	if (!inst.isMissing(index)) {
+	  distribution[1][(int)inst.classValue()] ++;
+	} else {
+	  distribution[2][(int)inst.classValue()] ++;
+	  numMissing++;
+	}
+      }
+      
+      // Sort instances
+      instances.sort(index);
+      
+      // Make split counts for each possible split and evaluate
+      for (int i = 0; i < instances.numInstances() - (numMissing + 1); i++) {
+	Instance inst = instances.instance(i);
+	Instance instPlusOne = instances.instance(i + 1);
+	distribution[0][(int)inst.classValue()] += inst.weight();
+	distribution[1][(int)inst.classValue()] -= inst.weight();
+	if (Utils.sm(inst.value(index), instPlusOne.value(index))) {
+	  currCutPoint = (inst.value(index) + instPlusOne.value(index)) / 2.0;
+	  currVal = ContingencyTables.entropyConditionedOnRows(distribution);
+	  if (Utils.sm(currVal, bestVal)) {
+	    splitPoint = currCutPoint;
+	    bestVal = currVal;
+	  }
+	}
+      }
+
+      return splitPoint;
+    }
+  }
+
+  /**
+   * Sets up the tree ready to be trained.
+   *
+   * @param instances the instances to train the tree with
+   * @exception Exception if training data is unsuitable
+   */
+  public void initClassifier(Instances instances) throws Exception {
+
+    // clear stats
+    m_nodesExpanded = 0;
+    m_examplesCounted = 0;
+    m_lastAddedSplitNum = 0;
+
+    m_numOfClasses = instances.numClasses();
+
+    // make sure training data is suitable
+    if (instances.checkForStringAttributes()) {
+      throw new Exception("Can't handle string attributes!");
+    }
+    if (!instances.classAttribute().isNominal()) {
+      throw new Exception("Class must be nominal!");
+    }
+
+    // create training set (use LADInstance class)
+    m_trainInstances =
+      new ReferenceInstances(instances, instances.numInstances());
+    for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+      Instance inst = (Instance) e.nextElement();
+      if (!inst.classIsMissing()) {
+	LADInstance adtInst = new LADInstance(inst);
+	m_trainInstances.addReference(adtInst);
+	adtInst.setDataset(m_trainInstances);
+      }
+    }
+
+    // create the root prediction node
+    m_root = new PredictionNode(new double[m_numOfClasses]);
+    
+    // pre-calculate what we can
+    generateStaticPotentialSplittersAndNumericIndices();
+  }
+
+    public void next(int iteration) throws Exception {
+	boost();
+    }
+
+    public void done() throws Exception {}
+
+  /**
+   * Performs a single boosting iteration.
+   * Will add a new splitter node and two prediction nodes to the tree
+   * (unless merging takes place).
+   *
+   * @exception Exception if try to boost without setting up tree first
+   */
+  private void boost() throws Exception {
+
+    if (m_trainInstances == null)
+      throw new Exception("Trying to boost with no training data");
+
+    // perform the search
+    searchForBestTest();
+
+    if (m_Debug) {
+      System.out.println("Best split found: "
+			 + m_search_bestSplitter.getNumOfBranches() + "-way split on "
+			 + m_search_bestSplitter.attributeString()
+			 //+ "\nsmallestLeastSquares = " + m_search_smallestLeastSquares);
+			 + "\nBestGain = " + m_search_smallestLeastSquares);
+    }
+
+    if (m_search_bestSplitter == null) return; // handle empty instances
+
+    // create the new nodes for the tree, updating the weights
+    for (int i=0; i<m_search_bestSplitter.getNumOfBranches(); i++) {
+      Instances applicableInstances =
+	m_search_bestSplitter.instancesDownBranch(i, m_search_bestPathInstances);
+      double[] predictionValues = calcPredictionValues(applicableInstances);
+      PredictionNode newPredictor = new PredictionNode(predictionValues);
+      updateWeights(applicableInstances, predictionValues);
+      m_search_bestSplitter.setChildForBranch(i, newPredictor);
+    }
+
+    // insert the new nodes
+    m_search_bestInsertionNode.addChild((Splitter) m_search_bestSplitter);
+
+    if (m_Debug) {
+      System.out.println("Tree is now:\n" + toString(m_root, 1) + "\n");
+      //System.out.println("Instances are now:\n" + m_trainInstances + "\n");
+    }
+
+    // free memory
+    m_search_bestPathInstances = null;
+  }
+
+  private void updateWeights(Instances instances, double[] newPredictionValues) {
+
+    for (int i=0; i<instances.numInstances(); i++)
+      ((LADInstance) instances.instance(i)).updateWeights(newPredictionValues);
+  }
+
+  /**
+   * Generates the m_staticPotentialSplitters2way 
+   * vector to contain all possible nominal splits, and the m_numericAttIndices array to
+   * index the numeric attributes in the training data.
+   *
+   */
+  private void generateStaticPotentialSplittersAndNumericIndices() {
+    
+    m_staticPotentialSplitters2way = new FastVector();
+    FastVector numericIndices = new FastVector();
+
+    for (int i=0; i<m_trainInstances.numAttributes(); i++) {
+      if (i == m_trainInstances.classIndex()) continue;
+      if (m_trainInstances.attribute(i).isNumeric())
+	numericIndices.addElement(new Integer(i));
+      else {
+	int numValues = m_trainInstances.attribute(i).numValues();
+	if (numValues == 2) // avoid redundancy due to 2-way symmetry
+	  m_staticPotentialSplitters2way.addElement(new TwoWayNominalSplit(i, 0));
+	else for (int j=0; j<numValues; j++)
+	  m_staticPotentialSplitters2way.addElement(new TwoWayNominalSplit(i, j));
+      }
+    }
+
+    m_numericAttIndices = new int[numericIndices.size()];
+    for (int i=0; i<numericIndices.size(); i++)
+      m_numericAttIndices[i] = ((Integer)numericIndices.elementAt(i)).intValue();
+  }
+
+  /**
+   * Performs a search for the best test (splitter) to add to the tree, by looking
+   * for the largest weight change.
+   *
+   * @exception Exception if search fails
+   */
+  private void searchForBestTest() throws Exception {
+    
+    if (m_Debug) {
+      System.out.println("Searching for best split...");
+    }
+
+    m_search_smallestLeastSquares = 0.0; //Double.POSITIVE_INFINITY;
+    searchForBestTest(m_root, m_trainInstances);
+  }
+
+  /**
+   * Recursive function that carries out search for the best test (splitter) to add to
+   * this part of the tree, by looking for the largest weight change. Will try 2-way
+   * and/or multi-way splits depending on the current state.
+   *
+   * @param currentNode the root of the subtree to be searched, and the current node 
+   * being considered as parent of a new split
+   * @param instances the instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void searchForBestTest(PredictionNode currentNode, Instances instances)
+    throws Exception
+  {
+
+    // keep stats
+    m_nodesExpanded++;
+    m_examplesCounted += instances.numInstances();
+      
+    // evaluate static splitters (nominal)
+    for (Enumeration e = m_staticPotentialSplitters2way.elements();
+         e.hasMoreElements(); ) {
+      evaluateSplitter((Splitter) e.nextElement(), currentNode, instances);
+    }
+
+    if (m_Debug) {
+	//System.out.println("Instances considered are: " + instances);
+    }
+
+    // evaluate dynamic splitters (numeric)
+    for (int i=0; i<m_numericAttIndices.length; i++) {
+      evaluateNumericSplit(currentNode, instances, m_numericAttIndices[i]);
+    }
+
+    if (currentNode.getChildren().size() == 0) return;
+
+    // keep searching
+    goDownAllPaths(currentNode, instances);
+  }
+
+  /**
+   * Continues general multi-class search by investigating every node in the
+   * subtree under currentNode.
+   *
+   * @param currentNode the root of the subtree to be searched
+   * @param instances the instances that apply at this node
+   * @exception Exception if search fails
+   */
+  private void goDownAllPaths(PredictionNode currentNode, Instances instances)
+    throws Exception
+  {
+    
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      for (int i=0; i<split.getNumOfBranches(); i++)
+	searchForBestTest(split.getChildForBranch(i),
+			  split.instancesDownBranch(i, instances));
+    }
+  }
+
+  /**
+   * Investigates the option of introducing a split under currentNode. If the 
+   * split creates a weight change that is larger than has already been found it will
+   * update the search information to record this as the best option so far. 
+   *
+   * @param split the splitter node to evaluate
+   * @param currentNode the parent under which the split is to be considered
+   * @param instances the instances that apply at this node
+   * @exception Exception if something goes wrong 
+   */
+  private void evaluateSplitter(Splitter split, PredictionNode currentNode,
+				Instances instances)
+    throws Exception
+  {
+    
+    double leastSquares = leastSquaresNonMissing(instances,split.attIndex);
+
+    for (int i=0; i<split.getNumOfBranches(); i++)
+      leastSquares -= leastSquares(split.instancesDownBranch(i, instances));
+
+    if (m_Debug) {
+      //System.out.println("Instances considered are: " + instances);
+      System.out.print(split.getNumOfBranches() + "-way split on " + split.attributeString()
+		       + " has leastSquares value of "
+		       + Utils.doubleToString(leastSquares,3));
+    }
+
+    if (leastSquares > m_search_smallestLeastSquares) {
+      if (m_Debug) {
+	System.out.print(" (best so far)");
+      }
+      m_search_smallestLeastSquares = leastSquares;
+      m_search_bestInsertionNode = currentNode;
+      m_search_bestSplitter = split;
+      m_search_bestPathInstances = instances;
+    }
+    if (m_Debug) {
+      System.out.print("\n");
+    }
+  }
+
+  private void evaluateNumericSplit(PredictionNode currentNode,
+				    Instances instances, int attIndex)
+  {
+  
+    double[] splitAndLS = findNumericSplitpointAndLS(instances, attIndex);
+    double gain = leastSquaresNonMissing(instances,attIndex) - splitAndLS[1];
+ 
+   if (m_Debug) {
+     //System.out.println("Instances considered are: " + instances);
+     System.out.print("Numeric split on " + instances.attribute(attIndex).name()
+		      + " has leastSquares value of " 
+		      //+ Utils.doubleToString(splitAndLS[1],3));
+		      + Utils.doubleToString(gain,3));
+    }
+
+   if (gain > m_search_smallestLeastSquares) {
+      if (m_Debug) {
+	System.out.print(" (best so far)");
+      }
+      m_search_smallestLeastSquares = gain; //splitAndLS[1];
+      m_search_bestInsertionNode = currentNode;
+      m_search_bestSplitter = new TwoWayNumericSplit(attIndex, splitAndLS[0]);;
+      m_search_bestPathInstances = instances;
+    }
+    if (m_Debug) {
+      System.out.print("\n");
+    }
+  }
+
+  private double[] findNumericSplitpointAndLS(Instances instances, int attIndex) {
+
+      double allLS = leastSquares(instances);
+
+    // all instances in right subset
+    double[] term1L = new double[m_numOfClasses];
+    double[] term2L = new double[m_numOfClasses];
+    double[] term3L = new double[m_numOfClasses];
+    double[] meanNumL = new double[m_numOfClasses];
+    double[] meanDenL = new double[m_numOfClasses];
+
+    double[] term1R = new double[m_numOfClasses];
+    double[] term2R = new double[m_numOfClasses];
+    double[] term3R = new double[m_numOfClasses];
+    double[] meanNumR = new double[m_numOfClasses];
+    double[] meanDenR = new double[m_numOfClasses];
+
+    double temp1, temp2, temp3;
+
+    double[] classMeans = new double[m_numOfClasses];
+    double[] classTotals = new double[m_numOfClasses];
+
+    // fill up RHS
+    for (int j=0; j<m_numOfClasses; j++) { 
+      for (int i=0; i<instances.numInstances(); i++) {
+	LADInstance inst = (LADInstance) instances.instance(i);
+	temp1 = inst.wVector[j] * inst.zVector[j];
+	term1R[j] += temp1 * inst.zVector[j];
+	term2R[j] += temp1;
+	term3R[j] += inst.wVector[j];
+	meanNumR[j] += inst.wVector[j] * inst.zVector[j];
+      }
+    }
+
+    //leastSquares = term1 - (2.0 * u * term2) + (u * u * term3);
+
+    double leastSquares;
+    boolean newSplit;
+    double smallestLeastSquares = Double.POSITIVE_INFINITY;
+    double bestSplit = 0.0;
+    double meanL, meanR;
+
+    instances.sort(attIndex);
+
+    for (int i=0; i<instances.numInstances()-1; i++) {// shift inst from right to left
+      if (instances.instance(i+1).isMissing(attIndex)) break;
+      if (instances.instance(i+1).value(attIndex) > instances.instance(i).value(attIndex))
+	newSplit = true;
+      else newSplit = false;
+      LADInstance inst = (LADInstance) instances.instance(i);
+      leastSquares = 0.0;
+      for (int j=0; j<m_numOfClasses; j++) {   
+	temp1 = inst.wVector[j] * inst.zVector[j];
+	temp2 = temp1 * inst.zVector[j];
+	temp3 = inst.wVector[j] * inst.zVector[j];
+	term1L[j] += temp2;
+	term2L[j] += temp1;
+	term3L[j] += inst.wVector[j];
+	term1R[j] -= temp2;
+	term2R[j] -= temp1;
+	term3R[j] -= inst.wVector[j];
+	meanNumL[j] += temp3;
+	meanNumR[j] -= temp3;
+	if (newSplit) {
+	  meanL = meanNumL[j] / term3L[j];
+	  meanR = meanNumR[j] / term3R[j];
+	  leastSquares += term1L[j] - (2.0 * meanL * term2L[j])
+	    + (meanL * meanL * term3L[j]);
+	  leastSquares += term1R[j] - (2.0 * meanR * term2R[j])
+	    + (meanR * meanR * term3R[j]);
+	}
+      }
+      if (m_Debug && newSplit)
+      System.out.println(attIndex + "/" + 
+			 ((instances.instance(i).value(attIndex) +
+			   instances.instance(i+1).value(attIndex)) / 2.0) +
+			 " = " + (allLS - leastSquares));
+
+      if (newSplit && leastSquares < smallestLeastSquares) {
+	bestSplit = (instances.instance(i).value(attIndex) +
+		     instances.instance(i+1).value(attIndex)) / 2.0;
+	smallestLeastSquares = leastSquares;
+      }
+    }
+    double[] result = new double[2];
+    result[0] = bestSplit;
+    result[1] = smallestLeastSquares > 0 ? smallestLeastSquares : 0;
+    return result;
+  }
+
+  private double leastSquares(Instances instances) {
+
+    double numerator=0, denominator=0, w, t;
+    double[] classMeans = new double[m_numOfClasses];
+    double[] classTotals = new double[m_numOfClasses];
+
+    for (int i=0; i<instances.numInstances(); i++) {
+      LADInstance inst = (LADInstance) instances.instance(i);
+      for (int j=0; j<m_numOfClasses; j++) {
+	classMeans[j] += inst.zVector[j] * inst.wVector[j];
+	classTotals[j] += inst.wVector[j];
+      }
+    }
+
+    double numInstances = (double) instances.numInstances();
+    for (int j=0; j<m_numOfClasses; j++) {
+      if (classTotals[j] != 0) classMeans[j] /= classTotals[j];
+    }
+
+    for (int i=0; i<instances.numInstances(); i++) 
+      for (int j=0; j<m_numOfClasses; j++) {
+	LADInstance inst = (LADInstance) instances.instance(i);
+	w = inst.wVector[j];
+	t = inst.zVector[j] - classMeans[j];
+	numerator += w * (t * t);
+	denominator += w;
+      }
+    //System.out.println(numerator + " / " + denominator);
+    return numerator > 0 ? numerator : 0;//  / denominator;
+  }
+
+
+  private double leastSquaresNonMissing(Instances instances, int attIndex) {
+
+    double numerator=0, denominator=0, w, t;
+    double[] classMeans = new double[m_numOfClasses];
+    double[] classTotals = new double[m_numOfClasses];
+
+    for (int i=0; i<instances.numInstances(); i++) {
+      LADInstance inst = (LADInstance) instances.instance(i);
+      for (int j=0; j<m_numOfClasses; j++) {
+	  classMeans[j] += inst.zVector[j] * inst.wVector[j];
+	  classTotals[j] += inst.wVector[j];
+      }
+    }
+
+    double numInstances = (double) instances.numInstances();
+    for (int j=0; j<m_numOfClasses; j++) {
+      if (classTotals[j] != 0) classMeans[j] /= classTotals[j];
+    }
+
+    for (int i=0; i<instances.numInstances(); i++) 
+      for (int j=0; j<m_numOfClasses; j++) {
+	LADInstance inst = (LADInstance) instances.instance(i);
+	if(!inst.isMissing(attIndex)) {
+	    w = inst.wVector[j];
+	    t = inst.zVector[j] - classMeans[j];
+	    numerator += w * (t * t);
+	    denominator += w;
+	}
+      }
+    //System.out.println(numerator + " / " + denominator);
+    return numerator > 0 ? numerator : 0;//  / denominator;
+  }
+
+  private double[] calcPredictionValues(Instances instances) {
+
+    double[] classMeans = new double[m_numOfClasses];
+    double meansSum = 0;
+    double multiplier = ((double) (m_numOfClasses-1)) / ((double) (m_numOfClasses));
+
+    double[] classTotals = new double[m_numOfClasses];
+
+    for (int i=0; i<instances.numInstances(); i++) {
+      LADInstance inst = (LADInstance) instances.instance(i);
+      for (int j=0; j<m_numOfClasses; j++) {
+	classMeans[j] += inst.zVector[j] * inst.wVector[j];
+	classTotals[j] += inst.wVector[j];
+      }
+    }
+    double numInstances = (double) instances.numInstances();
+    for (int j=0; j<m_numOfClasses; j++) {
+      if (classTotals[j] != 0) classMeans[j] /= classTotals[j];
+      meansSum += classMeans[j];
+    }
+    meansSum /= m_numOfClasses;
+
+    for (int j=0; j<m_numOfClasses; j++) {
+      classMeans[j] = multiplier * (classMeans[j] - meansSum);
+    }
+    return classMeans;
+  }
+
+  /**
+   * Returns the class probability distribution for an instance.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution the tree generates for the instance
+   */
+  public double[] distributionForInstance(Instance instance) {
+    
+    double[] predValues = new double[m_numOfClasses];
+    for (int i=0; i<m_numOfClasses; i++) predValues[i] = 0.0;
+    double[] distribution = predictionValuesForInstance(instance, m_root, predValues);
+    double max = distribution[Utils.maxIndex(distribution)];
+    for (int i=0; i<m_numOfClasses; i++) {
+      distribution[i] = Math.exp(distribution[i] - max);
+    }
+    double sum = Utils.sum(distribution);
+    if (sum > 0.0) Utils.normalize(distribution, sum);
+    return distribution;
+  }
+
+  /**
+   * Returns the class prediction values (votes) for an instance.
+   *
+   * @param inst the instance
+   * @param currentNode the root of the tree to get the values from
+   * @param currentValues the current values before adding the values contained in the
+   * subtree
+   * @return the class prediction values (votes)
+   */
+  private double[] predictionValuesForInstance(Instance inst, PredictionNode currentNode,
+					       double[] currentValues) {
+    
+    double[] predValues = currentNode.getValues();
+    for (int i=0; i<m_numOfClasses; i++) currentValues[i] += predValues[i];
+    //for (int i=0; i<m_numOfClasses; i++) currentValues[i] = predValues[i];
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      int branch = split.branchInstanceGoesDown(inst);
+      if (branch >= 0)
+	currentValues = predictionValuesForInstance(inst, split.getChildForBranch(branch),
+						    currentValues);
+    }
+    return currentValues;
+  }
+
+
+
+  /** model output functions ************************************************************/
+
+  /**
+   * Returns a description of the classifier.
+   *
+   * @return a string containing a description of the classifier
+   */
+  public String toString() {
+    
+    String className = getClass().getName();
+    if (m_root == null)
+      return (className +" not built yet");
+    else {
+      return (className + ":\n\n" + toString(m_root, 1) +
+	      "\nLegend: " + legend() +
+	      "\n#Tree size (total): " +
+	      numOfAllNodes(m_root) + 
+	      "\n#Tree size (number of predictor nodes): " +
+	      numOfPredictionNodes(m_root) + 
+	      "\n#Leaves (number of predictor nodes): " +
+	      numOfLeafNodes(m_root) + 
+	      "\n#Expanded nodes: " +
+	      m_nodesExpanded +
+	      "\n#Processed examples: " +
+	      m_examplesCounted + 
+	      "\n#Ratio e/n: " + 
+	      ((double)m_examplesCounted/(double)m_nodesExpanded)
+	      );
+    }
+  }
+
+  /**
+   * Traverses the tree, forming a string that describes it.
+   *
+   * @param currentNode the current node under investigation
+   * @param level the current level in the tree
+   * @return the string describing the subtree
+   */      
+  private String toString(PredictionNode currentNode, int level) {
+    
+    StringBuffer text = new StringBuffer();
+    
+    text.append(": ");
+    double[] predValues = currentNode.getValues();
+    for (int i=0; i<m_numOfClasses; i++) {
+      text.append(Utils.doubleToString(predValues[i],3));
+      if (i<m_numOfClasses-1) text.append(",");
+    }
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+	    
+      for (int j=0; j<split.getNumOfBranches(); j++) {
+	PredictionNode child = split.getChildForBranch(j);
+	if (child != null) {
+	  text.append("\n");
+	  for (int k = 0; k < level; k++) {
+	    text.append("|  ");
+	  }
+	  text.append("(" + split.orderAdded + ")");
+	  text.append(split.attributeString() + " " + split.comparisonString(j));
+	  text.append(toString(child, level + 1));
+	}
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph of the tree in dotty format
+   * @exception Exception if something goes wrong
+   */
+  public String graph() throws Exception {
+    
+    StringBuffer text = new StringBuffer();
+    text.append("digraph ADTree {\n");
+    //text.append("center=true\nsize=\"8.27,11.69\"\n");
+    graphTraverse(m_root, text, 0, 0);
+    return text.toString() +"}\n";
+  }
+
+
+  /**
+   * Traverses the tree, graphing each node.
+   *
+   * @param currentNode the currentNode under investigation
+   * @param text the string built so far
+   * @param splitOrder the order the parent splitter was added to the tree
+   * @param predOrder the order this predictor was added to the split
+   * @exception Exception if something goes wrong
+   */       
+  protected void graphTraverse(PredictionNode currentNode, StringBuffer text,
+			       int splitOrder, int predOrder)
+    throws Exception
+  {
+    
+    text.append("S" + splitOrder + "P" + predOrder + " [label=\"");
+    double[] predValues = currentNode.getValues();
+    for (int i=0; i<m_numOfClasses; i++) {
+      text.append(Utils.doubleToString(predValues[i],3));
+      if (i<m_numOfClasses-1) text.append(",");
+    }
+    if (splitOrder == 0) // show legend in root
+      text.append(" (" + legend() + ")");
+    text.append("\" shape=box style=filled]\n");
+    for (Enumeration e = currentNode.children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      text.append("S" + splitOrder + "P" + predOrder + "->" + "S" + split.orderAdded +
+		  " [style=dotted]\n");
+      text.append("S" + split.orderAdded + " [label=\"" + split.orderAdded + ": " +
+		  split.attributeString() + "\"]\n");
+
+      for (int i=0; i<split.getNumOfBranches(); i++) {
+	PredictionNode child = split.getChildForBranch(i);
+	if (child != null) {
+	  text.append("S" + split.orderAdded + "->" + "S" + split.orderAdded + "P" + i +
+		      " [label=\"" + split.comparisonString(i) + "\"]\n");
+	  graphTraverse(child, text, split.orderAdded, i);
+	}
+      }
+    }  
+  }
+
+  /**
+   * Returns the legend of the tree, describing how results are to be interpreted.
+   *
+   * @return a string containing the legend of the classifier
+   */
+  public String legend() {
+    
+    Attribute classAttribute = null;
+    if (m_trainInstances == null) return "";
+    try {classAttribute = m_trainInstances.classAttribute();} catch (Exception x){};
+    if (m_numOfClasses == 1) {
+      return ("-ve = " + classAttribute.value(0)
+	      + ", +ve = " + classAttribute.value(1));
+    } else {
+      StringBuffer text = new StringBuffer();
+      for (int i=0; i<m_numOfClasses; i++) {
+	if (i>0) text.append(", ");
+	text.append(classAttribute.value(i));
+      }
+      return text.toString();
+    }
+  }
+
+
+
+  /** option handling  ******************************************************************/
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numOfBoostingIterationsTipText() {
+
+    return "The number of boosting iterations to use, which determines the size of the tree.";
+  }
+
+  /**
+   * Gets the number of boosting iterations.
+   *
+   * @return the number of boosting iterations
+   */
+  public int getNumOfBoostingIterations() {
+    
+    return m_boostingIterations;
+  }
+
+  /**
+   * Sets the number of boosting iterations.
+   *
+   * @param b the number of boosting iterations to use
+   */
+  public void setNumOfBoostingIterations(int b) {
+    
+    m_boostingIterations = b; 
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(1);
+    newVector.addElement(new Option(
+				    "\tNumber of boosting iterations.\n"
+				    +"\t(Default = 10)",
+				    "B", 1,"-B <number of boosting iterations>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -B num <br>
+   * Set the number of boosting iterations
+   * (default 10) <p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String bString = Utils.getOption('B', options);
+    if (bString.length() != 0) setNumOfBoostingIterations(Integer.parseInt(bString));
+
+    super.setOptions(options);
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of ADTree.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    
+    String[] options = new String[2  + super.getOptions().length];
+
+    int current = 0;
+    options[current++] = "-B"; options[current++] = "" + getNumOfBoostingIterations();
+
+    System.arraycopy(super.getOptions(), 0, options, current, super.getOptions().length);
+
+    while (current < options.length) options[current++] = "";
+    return options;
+  }
+
+
+
+  /** additional measures ***************************************************************/
+
+  /**
+   * Calls measure function for tree size.
+   *
+   * @return the tree size
+   */
+  public double measureTreeSize() {
+    
+    return numOfAllNodes(m_root);
+  }
+
+  /**
+   * Calls measure function for leaf size.
+   *
+   * @return the leaf size
+   */
+  public double measureNumLeaves() {
+    
+    return numOfPredictionNodes(m_root);
+  }
+
+  /**
+   * Calls measure function for leaf size.
+   *
+   * @return the leaf size
+   */
+  public double measureNumPredictionLeaves() {
+    
+    return numOfLeafNodes(m_root);
+  }
+
+  /**
+   * Returns the number of nodes expanded.
+   *
+   * @return the number of nodes expanded during search
+   */
+  public double measureNodesExpanded() {
+    
+    return m_nodesExpanded;
+  }
+
+  /**
+   * Returns the number of examples "counted".
+   *
+   * @return the number of nodes processed during search
+   */
+  public double measureExamplesCounted() {
+    
+    return m_examplesCounted;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names.
+   *
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    
+    Vector newVector = new Vector(5);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureNumPredictionLeaves");
+    newVector.addElement("measureNodesExpanded");
+    newVector.addElement("measureExamplesCounted");
+    return newVector.elements();
+  }
+ 
+  /**
+   * Returns the value of the named measure.
+   *
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @exception IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    
+    if (additionalMeasureName.equals("measureTreeSize")) {
+      return measureTreeSize();
+    }
+    else if (additionalMeasureName.equals("measureNodesExpanded")) {
+      return measureNodesExpanded();
+    }
+    else if (additionalMeasureName.equals("measureNumLeaves")) {
+      return measureNumLeaves();
+    }
+    else if (additionalMeasureName.equals("measureNumPredictionLeaves")) {
+      return measureNumPredictionLeaves();
+    }
+    else if (additionalMeasureName.equals("measureExamplesCounted")) {
+      return measureExamplesCounted();
+    }
+    else {throw new IllegalArgumentException(additionalMeasureName 
+			      + " not supported (ADTree)");
+    }
+  }
+
+  /**
+   * Returns the number of prediction nodes in a tree.
+   *
+   * @param root the root of the tree being measured
+   * @return tree size in number of prediction nodes
+   */       
+  protected int numOfPredictionNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root != null) {
+      numSoFar++;
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfPredictionNodes(split.getChildForBranch(i));
+      }
+    }
+    return numSoFar;
+  }
+
+  /**
+   * Returns the number of leaf nodes in a tree.
+   *
+   * @param root the root of the tree being measured
+   * @return tree leaf size in number of prediction nodes
+   */       
+  protected int numOfLeafNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root.getChildren().size() > 0) {
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfLeafNodes(split.getChildForBranch(i));
+      }
+    } else numSoFar = 1;
+    return numSoFar;
+  }
+
+  /**
+   * Returns the total number of nodes in a tree.
+   *
+   * @param root the root of the tree being measured
+   * @return tree size in number of splitter + prediction nodes
+   */       
+  protected int numOfAllNodes(PredictionNode root) {
+    
+    int numSoFar = 0;
+    if (root != null) {
+      numSoFar++;
+      for (Enumeration e = root.children(); e.hasMoreElements(); ) {
+	numSoFar++;
+	Splitter split = (Splitter) e.nextElement();
+	for (int i=0; i<split.getNumOfBranches(); i++)
+	    numSoFar += numOfAllNodes(split.getChildForBranch(i));
+      }
+    }
+    return numSoFar;
+  }
+  
+  /** main functions ********************************************************************/
+
+  /**
+   * Builds a classifier for a set of instances.
+   *
+   * @param instances the instances to train the classifier with
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+
+    // set up the tree
+    initClassifier(instances);
+
+    // build the tree
+    for (int T = 0; T < m_boostingIterations; T++) {
+	boost();    
+    }
+  }
+
+    public int predictiveError(Instances test) {
+	int error = 0;
+	for(int i = test.numInstances()-1; i>=0; i--) {
+	    Instance inst = test.instance(i);
+	    try {
+		if (classifyInstance(inst) != inst.classValue())
+		    error++;
+	    } catch (Exception e) { error++;}
+	}
+	return error;
+    }
+
+  /**
+   * Merges two trees together. Modifies the tree being acted on, leaving tree passed
+   * as a parameter untouched (cloned). Does not check to see whether training instances
+   * are compatible - strange things could occur if they are not.
+   *
+   * @param mergeWith the tree to merge with
+   * @exception Exception if merge could not be performed
+   */
+  public void merge(LADTree mergeWith) throws Exception {
+    
+    if (m_root == null || mergeWith.m_root == null)
+      throw new Exception("Trying to merge an uninitialized tree");
+    if (m_numOfClasses != mergeWith.m_numOfClasses)
+      throw new Exception("Trees not suitable for merge - "
+			  + "different sized prediction nodes");
+    m_root.merge(mergeWith.m_root);
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6035 $");
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {    
+    runClassifier(new LADTree(), argv);
+  }
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/LMT.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/LMT.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/LMT.java	(revision 29)
@@ -0,0 +1,804 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LMT.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.lmt.LMTNode;
+import weka.classifiers.trees.lmt.ResidualModelSelection;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Classifier for building 'logistic model trees', which are classification trees with logistic regression functions at the leaves. The algorithm can deal with binary and multi-class target variables, numeric and nominal attributes and missing values.<br/>
+ * <br/>
+ * For more information see: <br/>
+ * <br/>
+ * Niels Landwehr, Mark Hall, Eibe Frank (2005). Logistic Model Trees. Machine Learning. 95(1-2):161-205.<br/>
+ * <br/>
+ * Marc Sumner, Eibe Frank, Mark Hall: Speeding up Logistic Model Tree Induction. In: 9th European Conference on Principles and Practice of Knowledge Discovery in Databases, 675-683, 2005.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Landwehr2005,
+ *    author = {Niels Landwehr and Mark Hall and Eibe Frank},
+ *    journal = {Machine Learning},
+ *    number = {1-2},
+ *    pages = {161-205},
+ *    title = {Logistic Model Trees},
+ *    volume = {95},
+ *    year = {2005}
+ * }
+ * 
+ * &#64;inproceedings{Sumner2005,
+ *    author = {Marc Sumner and Eibe Frank and Mark Hall},
+ *    booktitle = {9th European Conference on Principles and Practice of Knowledge Discovery in Databases},
+ *    pages = {675-683},
+ *    publisher = {Springer},
+ *    title = {Speeding up Logistic Model Tree Induction},
+ *    year = {2005}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B
+ *  Binary splits (convert nominal attributes to binary ones)</pre>
+ * 
+ * <pre> -R
+ *  Split on residuals instead of class values</pre>
+ * 
+ * <pre> -C
+ *  Use cross-validation for boosting at all nodes (i.e., disable heuristic)</pre>
+ * 
+ * <pre> -P
+ *  Use error on probabilities instead of misclassification error for stopping criterion of LogitBoost.</pre>
+ * 
+ * <pre> -I &lt;numIterations&gt;
+ *  Set fixed number of iterations for LogitBoost (instead of using cross-validation)</pre>
+ * 
+ * <pre> -M &lt;numInstances&gt;
+ *  Set minimum number of instances at which a node can be split (default 15)</pre>
+ * 
+ * <pre> -W &lt;beta&gt;
+ *  Set beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.</pre>
+ * 
+ * <pre> -A
+ *  The AIC is used to choose the best iteration.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Niels Landwehr 
+ * @author Marc Sumner 
+ * @version $Revision: 6088 $
+ */
+public class LMT 
+  extends AbstractClassifier 
+  implements OptionHandler, AdditionalMeasureProducer, Drawable,
+             TechnicalInformationHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -1113212459618104943L;
+  
+  /** Filter to replace missing values*/
+  protected ReplaceMissingValues m_replaceMissing;
+    
+  /** Filter to replace nominal attributes*/
+  protected NominalToBinary m_nominalToBinary;
+    
+  /** root of the logistic model tree*/
+  protected LMTNode m_tree;
+    
+  /** use heuristic that determines the number of LogitBoost iterations only once in the beginning?*/
+  protected boolean m_fastRegression;
+
+  /** convert nominal attributes to binary ?*/
+  protected boolean m_convertNominal;
+
+  /** split on residuals?*/
+  protected boolean m_splitOnResiduals;
+    
+  /**use error on probabilties instead of misclassification for stopping criterion of LogitBoost?*/
+  protected boolean m_errorOnProbabilities;
+
+  /**minimum number of instances at which a node is considered for splitting*/
+  protected int m_minNumInstances;
+
+  /**if non-zero, use fixed number of iterations for LogitBoost*/
+  protected int m_numBoostingIterations;
+    
+  /**Threshold for trimming weights. Instances with a weight lower than this (as a percentage
+   * of total weights) are not included in the regression fit.
+   **/
+  protected double m_weightTrimBeta;
+  
+  /** If true, the AIC is used to choose the best LogitBoost iteration*/
+  private boolean m_useAIC = false;
+  
+  /**
+   * Creates an instance of LMT with standard options
+   */
+  public LMT() {
+    m_fastRegression = true;
+    m_numBoostingIterations = -1;
+    m_minNumInstances = 15;
+    m_weightTrimBeta = 0;
+    m_useAIC = false;
+  }    
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds the classifier.
+   *
+   * @param data the data to train with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances data) throws Exception{
+	
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    Instances filteredData = new Instances(data);
+    filteredData.deleteWithMissingClass();
+    
+    //replace missing values
+    m_replaceMissing = new ReplaceMissingValues();
+    m_replaceMissing.setInputFormat(filteredData);	
+    filteredData = Filter.useFilter(filteredData, m_replaceMissing);	
+	
+    //possibly convert nominal attributes globally
+    if (m_convertNominal) {	    
+      m_nominalToBinary = new NominalToBinary();
+      m_nominalToBinary.setInputFormat(filteredData);	
+      filteredData = Filter.useFilter(filteredData, m_nominalToBinary);
+    }
+
+    int minNumInstances = 2;
+	
+    //create ModelSelection object, either for splits on the residuals or for splits on the class value 
+    ModelSelection modSelection;	
+    if (m_splitOnResiduals) {
+      modSelection = new ResidualModelSelection(minNumInstances);
+    } else {
+      modSelection = new C45ModelSelection(minNumInstances, filteredData, true);
+    }
+	
+    //create tree root
+    m_tree = new LMTNode(modSelection, m_numBoostingIterations, m_fastRegression, 
+			 m_errorOnProbabilities, m_minNumInstances, m_weightTrimBeta, m_useAIC);
+    //build tree
+    m_tree.buildClassifier(filteredData);
+
+    if (modSelection instanceof C45ModelSelection) ((C45ModelSelection)modSelection).cleanup();
+  }
+
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to compute the distribution for
+   * @return the class probabilities
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public double [] distributionForInstance(Instance instance) throws Exception {
+	
+    //replace missing values
+    m_replaceMissing.input(instance);
+    instance = m_replaceMissing.output();	
+	
+    //possibly convert nominal attributes
+    if (m_convertNominal) {
+      m_nominalToBinary.input(instance);
+      instance = m_nominalToBinary.output();
+    }
+	
+    return m_tree.distributionForInstance(instance);
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    double maxProb = -1;
+    int maxIndex = 0;
+      
+    //classify by maximum probability
+    double[] probs = distributionForInstance(instance);       
+    for (int j = 0; j < instance.numClasses(); j++) {
+      if (Utils.gr(probs[j], maxProb)) {
+	maxIndex = j;
+	maxProb = probs[j];
+      }
+    }     
+    return (double)maxIndex;      
+  }    
+     
+  /**
+   * Returns a description of the classifier.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+    if (m_tree!=null) {
+      return "Logistic model tree \n------------------\n" + m_tree.toString();
+    } else {
+      return "No tree build";
+    }
+  }    
+    
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(8);
+    
+    newVector.addElement(new Option("\tBinary splits (convert nominal attributes to binary ones)",
+                                    "B", 0, "-B"));
+    
+    newVector.addElement(new Option("\tSplit on residuals instead of class values",
+                                    "R", 0, "-R"));
+    
+    newVector.addElement(new Option("\tUse cross-validation for boosting at all nodes (i.e., disable heuristic)",
+                                    "C", 0, "-C"));
+    
+    newVector.addElement(new Option("\tUse error on probabilities instead of misclassification error "+
+                                    "for stopping criterion of LogitBoost.",
+                                    "P", 0, "-P"));
+    
+    newVector.addElement(new Option("\tSet fixed number of iterations for LogitBoost (instead of using "+
+                                    "cross-validation)",
+                                    "I",1,"-I <numIterations>"));
+    
+    newVector.addElement(new Option("\tSet minimum number of instances at which a node can be split (default 15)",
+                                    "M",1,"-M <numInstances>"));
+    
+    newVector.addElement(new Option("\tSet beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.",
+                                    "W",1,"-W <beta>"));
+    
+    newVector.addElement(new Option("\tThe AIC is used to choose the best iteration.",
+                                    "A", 0, "-A"));
+    
+    return newVector.elements();
+  }
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B
+   *  Binary splits (convert nominal attributes to binary ones)</pre>
+   * 
+   * <pre> -R
+   *  Split on residuals instead of class values</pre>
+   * 
+   * <pre> -C
+   *  Use cross-validation for boosting at all nodes (i.e., disable heuristic)</pre>
+   * 
+   * <pre> -P
+   *  Use error on probabilities instead of misclassification error for stopping criterion of LogitBoost.</pre>
+   * 
+   * <pre> -I &lt;numIterations&gt;
+   *  Set fixed number of iterations for LogitBoost (instead of using cross-validation)</pre>
+   * 
+   * <pre> -M &lt;numInstances&gt;
+   *  Set minimum number of instances at which a node can be split (default 15)</pre>
+   * 
+   * <pre> -W &lt;beta&gt;
+   *  Set beta for weight trimming for LogitBoost. Set to 0 (default) for no weight trimming.</pre>
+   * 
+   * <pre> -A
+   *  The AIC is used to choose the best iteration.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setConvertNominal(Utils.getFlag('B', options));
+    setSplitOnResiduals(Utils.getFlag('R', options));
+    setFastRegression(!Utils.getFlag('C', options));
+    setErrorOnProbabilities(Utils.getFlag('P', options));
+
+    String optionString = Utils.getOption('I', options);
+    if (optionString.length() != 0) {
+      setNumBoostingIterations((new Integer(optionString)).intValue());
+    }
+	
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMinNumInstances((new Integer(optionString)).intValue());
+    }
+
+    optionString = Utils.getOption('W', options);
+    if (optionString.length() != 0) {
+      setWeightTrimBeta((new Double(optionString)).doubleValue());
+    }
+    
+    setUseAIC(Utils.getFlag('A', options));        
+    
+    Utils.checkForRemainingOptions(options);
+	
+  } 
+    
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    String[] options = new String[11];
+    int current = 0;
+
+    if (getConvertNominal()) {
+      options[current++] = "-B";
+    } 
+
+    if (getSplitOnResiduals()) {
+      options[current++] = "-R";
+    }
+
+    if (!getFastRegression()) {
+      options[current++] = "-C";
+    } 
+	
+    if (getErrorOnProbabilities()) {
+      options[current++] = "-P";
+    } 
+	
+    options[current++] = "-I"; 
+    options[current++] = ""+getNumBoostingIterations();
+
+    options[current++] = "-M"; 
+    options[current++] = ""+getMinNumInstances();
+        
+    options[current++] = "-W";
+    options[current++] = ""+getWeightTrimBeta();
+    
+    if (getUseAIC()) {
+      options[current++] = "-A";
+    }
+    
+    while (current < options.length) {
+      options[current++] = "";
+    } 
+    return options;
+  } 
+
+  /**
+   * Get the value of weightTrimBeta.
+   */
+  public double getWeightTrimBeta(){
+    return m_weightTrimBeta;
+  }
+  
+  /**
+   * Get the value of useAIC.
+   *
+   * @return Value of useAIC.
+   */
+  public boolean getUseAIC(){
+    return m_useAIC;
+  }
+    
+  /**
+   * Set the value of weightTrimBeta.
+   */
+  public void setWeightTrimBeta(double n){
+    m_weightTrimBeta = n;
+  }
+  
+  /**
+   * Set the value of useAIC.
+   *
+   * @param c Value to assign to useAIC.
+   */
+  public void setUseAIC(boolean c){
+    m_useAIC = c;
+  }
+  
+  /**
+   * Get the value of convertNominal.
+   *
+   * @return Value of convertNominal.
+   */
+  public boolean getConvertNominal(){
+    return m_convertNominal;
+  }
+
+  /**
+   * Get the value of splitOnResiduals.
+   *
+   * @return Value of splitOnResiduals.
+   */
+  public boolean getSplitOnResiduals(){
+    return m_splitOnResiduals;
+  }
+
+  /**
+   * Get the value of fastRegression.
+   *
+   * @return Value of fastRegression.
+   */
+  public boolean getFastRegression(){
+    return m_fastRegression;
+  }
+    
+  /**
+   * Get the value of errorOnProbabilities.
+   *
+   * @return Value of errorOnProbabilities.
+   */
+  public boolean getErrorOnProbabilities(){
+    return m_errorOnProbabilities;
+  }
+
+  /**
+   * Get the value of numBoostingIterations.
+   *
+   * @return Value of numBoostingIterations.
+   */
+  public int getNumBoostingIterations(){
+    return m_numBoostingIterations;
+  }
+    
+  /**
+   * Get the value of minNumInstances.
+   *
+   * @return Value of minNumInstances.
+   */
+  public int getMinNumInstances(){
+    return m_minNumInstances;
+  }
+    
+  /**
+   * Set the value of convertNominal.
+   *
+   * @param c Value to assign to convertNominal.
+   */
+  public void setConvertNominal(boolean c){
+    m_convertNominal = c;
+  }
+
+  /**
+   * Set the value of splitOnResiduals.
+   *
+   * @param c Value to assign to splitOnResiduals.
+   */
+  public void setSplitOnResiduals(boolean c){
+    m_splitOnResiduals = c;
+  }
+
+  /**
+   * Set the value of fastRegression.
+   *
+   * @param c Value to assign to fastRegression.
+   */
+  public void setFastRegression(boolean c){
+    m_fastRegression = c;
+  }
+
+  /**
+   * Set the value of errorOnProbabilities.
+   *
+   * @param c Value to assign to errorOnProbabilities.
+   */
+  public void setErrorOnProbabilities(boolean c){
+    m_errorOnProbabilities = c;
+  }
+
+  /**
+   * Set the value of numBoostingIterations.
+   *
+   * @param c Value to assign to numBoostingIterations.
+   */
+  public void setNumBoostingIterations(int c){
+    m_numBoostingIterations = c;
+  } 
+
+  /**
+   * Set the value of minNumInstances.
+   *
+   * @param c Value to assign to minNumInstances.
+   */
+  public void setMinNumInstances(int c){
+    m_minNumInstances = c;
+  }
+    
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+    return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph describing the tree
+   * @throws Exception if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    return m_tree.graph();
+  }
+
+  /**
+   * Returns the size of the tree
+   * @return the size of the tree
+   */
+  public int measureTreeSize(){
+    return m_tree.numNodes();
+  }
+    
+  /**
+   * Returns the number of leaves in the tree
+   * @return the number of leaves in the tree
+   */
+  public int measureNumLeaves(){
+    return m_tree.numLeaves();
+  }
+     
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(2);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+	
+    return newVector.elements();
+  }
+    
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+					 + " not supported (LMT)");
+    }
+  }    
+    
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Classifier for building 'logistic model trees', which are classification trees with "
+      +"logistic regression functions at the leaves. The algorithm can deal with binary and multi-class "
+      +"target variables, numeric and nominal attributes and missing values.\n\n"
+      +"For more information see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+      
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Niels Landwehr and Mark Hall and Eibe Frank");
+    result.setValue(Field.TITLE, "Logistic Model Trees");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.YEAR, "2005");
+    result.setValue(Field.VOLUME, "95");
+    result.setValue(Field.PAGES, "161-205");
+    result.setValue(Field.NUMBER, "1-2");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Marc Sumner and Eibe Frank and Mark Hall");
+    additional.setValue(Field.TITLE, "Speeding up Logistic Model Tree Induction");
+    additional.setValue(Field.BOOKTITLE, "9th European Conference on Principles and Practice of Knowledge Discovery in Databases");
+    additional.setValue(Field.YEAR, "2005");
+    additional.setValue(Field.PAGES, "675-683");
+    additional.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String convertNominalTipText() {
+    return "Convert all nominal attributes to binary ones before building the tree. "
+      +"This means that all splits in the final tree will be binary.";
+  }
+    
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String splitOnResidualsTipText() {
+    return "Set splitting criterion based on the residuals of LogitBoost. "
+      +"There are two possible splitting criteria for LMT: the default is to use the C4.5 "
+      +"splitting criterion that uses information gain on the class variable. The other splitting "
+      +"criterion tries to improve the purity in the residuals produces when fitting the logistic "
+      +"regression functions. The choice of the splitting criterion does not usually affect classification "
+      +"accuracy much, but can produce different trees.";
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String fastRegressionTipText() {
+    return "Use heuristic that avoids cross-validating the number of Logit-Boost iterations at every node. "
+      +"When fitting the logistic regression functions at a node, LMT has to determine the number of LogitBoost "
+      +"iterations to run. Originally, this number was cross-validated at every node in the tree. "
+      +"To save time, this heuristic cross-validates the number only once and then uses that number at every "
+      +"node in the tree. Usually this does not decrease accuracy but improves runtime considerably.";
+  }  
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String errorOnProbabilitiesTipText() {
+    return "Minimize error on probabilities instead of misclassification error when cross-validating the number "
+      +"of LogitBoost iterations. When set, the number of LogitBoost iterations is chosen that minimizes "
+      +"the root mean squared error instead of the misclassification error.";	   
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numBoostingIterationsTipText() {
+    return "Set a fixed number of iterations for LogitBoost. If >= 0, this sets a fixed number of LogitBoost "
+      +"iterations that is used everywhere in the tree. If < 0, the number is cross-validated.";
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumInstancesTipText() {
+    return "Set the minimum number of instances at which a node is considered for splitting. "
+      +"The default value is 15.";
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightTrimBetaTipText() {
+    return "Set the beta value used for weight trimming in LogitBoost. "
+    +"Only instances carrying (1 - beta)% of the weight from previous iteration "
+    +"are used in the next iteration. Set to 0 for no weight trimming. "
+    +"The default value is 0.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useAICTipText() {
+    return "The AIC is used to determine when to stop LogitBoost iterations. "
+    +"The default is not to use AIC.";
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param argv the commandline options 
+   */
+  public static void main (String [] argv) {	
+    runClassifier(new LMT(), argv);
+  }  
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/M5P.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/M5P.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/M5P.java	(revision 29)
@@ -0,0 +1,261 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    M5P.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.trees.m5.M5Base;
+import weka.classifiers.trees.m5.Rule;
+import weka.core.Drawable;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * M5Base. Implements base routines for generating M5 Model trees and rules<br/>
+ * The original algorithm M5 was invented by R. Quinlan and Yong Wang made improvements.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Ross J. Quinlan: Learning with Continuous Classes. In: 5th Australian Joint Conference on Artificial Intelligence, Singapore, 343-348, 1992.<br/>
+ * <br/>
+ * Y. Wang, I. H. Witten: Induction of model trees for predicting continuous classes. In: Poster papers of the 9th European Conference on Machine Learning, 1997.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Quinlan1992,
+ *    address = {Singapore},
+ *    author = {Ross J. Quinlan},
+ *    booktitle = {5th Australian Joint Conference on Artificial Intelligence},
+ *    pages = {343-348},
+ *    publisher = {World Scientific},
+ *    title = {Learning with Continuous Classes},
+ *    year = {1992}
+ * }
+ * 
+ * &#64;inproceedings{Wang1997,
+ *    author = {Y. Wang and I. H. Witten},
+ *    booktitle = {Poster papers of the 9th European Conference on Machine Learning},
+ *    publisher = {Springer},
+ *    title = {Induction of model trees for predicting continuous classes},
+ *    year = {1997}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Use unpruned tree/rules</pre>
+ * 
+ * <pre> -U
+ *  Use unsmoothed predictions</pre>
+ * 
+ * <pre> -R
+ *  Build regression tree/rule rather than a model tree/rule</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf
+ *  (default 4)</pre>
+ * 
+ * <pre> -L
+ *  Save instances at the nodes in
+ *  the tree (for visualization purposes)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.10 $
+ */
+public class M5P 
+  extends M5Base 
+  implements Drawable {
+
+  /** for serialization */
+  static final long serialVersionUID = -6118439039768244417L;
+  
+  /**
+   * Creates a new <code>M5P</code> instance.
+   */
+  public M5P() {
+    super();
+    setGenerateRules(false);
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Return a dot style String describing the tree.
+   *
+   * @return a <code>String</code> value
+   * @throws Exception if an error occurs
+   */
+  public String graph() throws Exception {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("digraph M5Tree {\n");
+    Rule temp = (Rule)m_ruleSet.elementAt(0);
+    temp.topOfTree().graph(text);
+    text.append("}\n");
+    return text.toString();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String saveInstancesTipText() {
+    return 
+        "Whether to save instance data at each node in the tree for "
+      + "visualization purposes.";
+  }
+
+  /**
+   * Set whether to save instance data at each node in the
+   * tree for visualization purposes
+   *
+   * @param save a <code>boolean</code> value
+   */
+  public void setSaveInstances(boolean save) {
+    m_saveInstances = save;
+  }
+
+  /**
+   * Get whether instance data is being save.
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean getSaveInstances() {
+    return m_saveInstances;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * 
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Enumeration superOpts = super.listOptions();
+    
+    Vector newVector = new Vector();
+    while (superOpts.hasMoreElements()) {
+      newVector.addElement((Option)superOpts.nextElement());
+    }
+
+    newVector.addElement(new Option("\tSave instances at the nodes in\n"
+				    +"\tthe tree (for visualization purposes)",
+				    "L", 0, "-L"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Use unpruned tree/rules</pre>
+   * 
+   * <pre> -U
+   *  Use unsmoothed predictions</pre>
+   * 
+   * <pre> -R
+   *  Build regression tree/rule rather than a model tree/rule</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf
+   *  (default 4)</pre>
+   * 
+   * <pre> -L
+   *  Save instances at the nodes in
+   *  the tree (for visualization purposes)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setSaveInstances(Utils.getFlag('L', options));
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String[] superOpts = super.getOptions();
+    String [] options = new String [superOpts.length+1];
+    int current = superOpts.length;
+    for (int i = 0; i < current; i++) {
+      options[i] = superOpts[i];
+    }
+    
+    if (getSaveInstances()) {
+      options[current++] = "-L";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+
+  /**
+   * Main method by which this class can be tested
+   * 
+   * @param args an array of options
+   */
+  public static void main(String[] args) {
+    runClassifier(new M5P(), args);
+  } 
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/NBTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/NBTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/NBTree.java	(revision 29)
@@ -0,0 +1,292 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NBTree.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.trees.j48.NBTreeClassifierTree;
+import weka.classifiers.trees.j48.NBTreeModelSelection;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for generating a decision tree with naive Bayes classifiers at the leaves.<br/>
+ * <br/>
+ * For more information, see<br/>
+ * <br/>
+ * Ron Kohavi: Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid. In: Second International Conference on Knoledge Discovery and Data Mining, 202-207, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Kohavi1996,
+ *    author = {Ron Kohavi},
+ *    booktitle = {Second International Conference on Knoledge Discovery and Data Mining},
+ *    pages = {202-207},
+ *    title = {Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall
+ * @version $Revision: 5928 $
+ */
+public class NBTree 
+  extends AbstractClassifier 
+  implements WeightedInstancesHandler, Drawable, Summarizable,
+	     AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -4716005707058256086L;
+
+  /** Minimum number of instances */
+  private int m_minNumObj = 30;
+
+  /** The root of the tree */
+  private NBTreeClassifierTree m_root;
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Class for generating a decision tree with naive Bayes classifiers at "
+      + "the leaves.\n\n"
+      + "For more information, see\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ron Kohavi");
+    result.setValue(Field.TITLE, "Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid");
+    result.setValue(Field.BOOKTITLE, "Second International Conference on Knoledge Discovery and Data Mining");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "202-207");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    return new NBTreeClassifierTree(null).getCapabilities();
+  }
+
+  /**
+   * Generates the classifier.
+   *
+   * @param instances the data to train with
+   * @throws Exception if classifier can't be built successfully
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    
+    NBTreeModelSelection modSelection = 
+      new NBTreeModelSelection(m_minNumObj, instances);
+
+    m_root = new NBTreeClassifierTree(modSelection);
+    m_root.buildClassifier(instances);
+  }
+
+  /**
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification
+   * @throws Exception if instance can't be classified successfully
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+
+    return m_root.classifyInstance(instance);
+  }
+
+  /** 
+   * Returns class probabilities for an instance.
+   *
+   * @param instance the instance to get the distribution for
+   * @return the class probabilities
+   * @throws Exception if distribution can't be computed successfully
+   */
+  public final double[] distributionForInstance(Instance instance) 
+       throws Exception {
+
+    return m_root.distributionForInstance(instance, false);
+  }
+
+  /**
+   * Returns a description of the classifier.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    if (m_root == null) {
+      return "No classifier built";
+    }
+    return "NBTree\n------------------\n" + m_root.toString();
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @return the graph describing the tree
+   * @throws Exception if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    return m_root.graph();
+  }
+
+  /**
+   * Returns a superconcise version of the model
+   * 
+   * @return a description of the model
+   */
+  public String toSummaryString() {
+
+    return "Number of leaves: " + m_root.numLeaves() + "\n"
+         + "Size of the tree: " + m_root.numNodes() + "\n";
+  }
+
+  /**
+   * Returns the size of the tree
+   * @return the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_root.numNodes();
+  }
+
+  /**
+   * Returns the number of leaves
+   * @return the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_root.numLeaves();
+  }
+
+  /**
+   * Returns the number of rules (same as number of leaves)
+   * @return the number of rules
+   */
+  public double measureNumRules() {
+    return m_root.numLeaves();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (j48)");
+    }
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(3);
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param argv the commandline options
+   */
+  public static void main(String[] argv){
+    runClassifier(new NBTree(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/REPTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/REPTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/REPTree.java	(revision 29)
@@ -0,0 +1,1972 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    REPTree.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Sourcable;
+import weka.classifiers.rules.ZeroR;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Fast decision tree learner. Builds a decision/regression tree using information gain/variance and prunes it using reduced-error pruning (with backfitting).  Only sorts values for numeric attributes once. Missing values are dealt with by splitting the corresponding instances into pieces (i.e. as in C4.5).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf (default 2).</pre>
+ * 
+ * <pre> -V &lt;minimum variance for split&gt;
+ *  Set minimum numeric class variance proportion
+ *  of train variance for split (default 1e-3).</pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Number of folds for reduced error pruning (default 3).</pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Seed for random data shuffling (default 1).</pre>
+ * 
+ * <pre> -P
+ *  No pruning.</pre>
+ * 
+ * <pre> -L
+ *  Maximum tree depth (default -1, no maximum)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $ 
+ */
+public class REPTree 
+  extends AbstractClassifier 
+  implements OptionHandler, WeightedInstancesHandler, Drawable, 
+	     AdditionalMeasureProducer, Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = -8562443428621539458L;
+  
+  /** ZeroR model that is used if no attributes are present. */
+  protected ZeroR m_zeroR;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Fast decision tree learner. Builds a decision/regression tree using "
+      + "information gain/variance and prunes it using reduced-error pruning "
+      + "(with backfitting).  Only sorts values for numeric attributes "
+      + "once. Missing values are dealt with by splitting the corresponding "
+      + "instances into pieces (i.e. as in C4.5).";
+  }
+
+  /** An inner class for building and storing the tree structure */
+  protected class Tree 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -1635481717888437935L;
+    
+    /** The header information (for printing the tree). */
+    protected Instances m_Info = null;
+
+    /** The subtrees of this tree. */ 
+    protected Tree[] m_Successors;
+    
+    /** The attribute to split on. */
+    protected int m_Attribute = -1;
+
+    /** The split point. */
+    protected double m_SplitPoint = Double.NaN;
+    
+    /** The proportions of training instances going down each branch. */
+    protected double[] m_Prop = null;
+
+    /** Class probabilities from the training data in the nominal case. 
+	Holds the mean in the numeric case. */
+    protected double[] m_ClassProbs = null;
+    
+    /** The (unnormalized) class distribution in the nominal
+	case. Holds the sum of squared errors and the weight 
+	in the numeric case. */
+    protected double[] m_Distribution = null;
+    
+    /** Class distribution of hold-out set at node in the nominal case. 
+	Straight sum of weights in the numeric case (i.e. array has
+	only one element. */
+    protected double[] m_HoldOutDist = null;
+    
+    /** The hold-out error of the node. The number of miss-classified
+	instances in the nominal case, the sum of squared errors in the 
+	numeric case. */
+    protected double m_HoldOutError = 0;
+  
+    /**
+     * Computes class distribution of an instance using the tree.
+     * 
+     * @param instance the instance to compute the distribution for
+     * @return the distribution
+     * @throws Exception if computation fails
+     */
+    protected double[] distributionForInstance(Instance instance) 
+      throws Exception {
+
+      double[] returnedDist = null;
+      
+      if (m_Attribute > -1) {
+	
+	// Node is not a leaf
+	if (instance.isMissing(m_Attribute)) {
+	  
+	  // Value is missing
+	  returnedDist = new double[m_Info.numClasses()];
+
+	  // Split instance up
+	  for (int i = 0; i < m_Successors.length; i++) {
+	    double[] help = 
+	      m_Successors[i].distributionForInstance(instance);
+	    if (help != null) {
+	      for (int j = 0; j < help.length; j++) {
+		returnedDist[j] += m_Prop[i] * help[j];
+	      }
+	    }
+	  }
+	} else if (m_Info.attribute(m_Attribute).isNominal()) {
+	  
+	  // For nominal attributes
+	  returnedDist =  m_Successors[(int)instance.value(m_Attribute)].
+	    distributionForInstance(instance);
+	} else {
+	  
+	  // For numeric attributes
+	  if (instance.value(m_Attribute) < m_SplitPoint) {
+	    returnedDist = 
+	      m_Successors[0].distributionForInstance(instance);
+	  } else {
+	    returnedDist = 
+	      m_Successors[1].distributionForInstance(instance);
+	  }
+	}
+      }
+      if ((m_Attribute == -1) || (returnedDist == null)) {
+	
+	// Node is a leaf or successor is empty
+	return m_ClassProbs;
+      } else {
+	return returnedDist;
+      }
+    }
+
+   /**
+    * Returns a string containing java source code equivalent to the test
+    * made at this node. The instance being tested is called "i". This
+    * routine assumes to be called in the order of branching, enabling us to
+    * set the >= condition test (the last one) of a numeric splitpoint 
+    * to just "true" (because being there in the flow implies that the 
+    * previous less-than test failed).
+    *
+    * @param index index of the value tested
+    * @return a value of type 'String'
+    */
+    public final String sourceExpression(int index) {
+      
+      StringBuffer expr = null;
+      if (index < 0) {
+        return "i[" + m_Attribute + "] == null";
+      }
+      if (m_Info.attribute(m_Attribute).isNominal()) {
+        expr = new StringBuffer("i[");
+	expr.append(m_Attribute).append("]");
+	expr.append(".equals(\"").append(m_Info.attribute(m_Attribute)
+		.value(index)).append("\")");
+      } else {
+        expr = new StringBuffer("");
+	if (index == 0) {
+	  expr.append("((Double)i[")
+	    .append(m_Attribute).append("]).doubleValue() < ")
+	    .append(m_SplitPoint);
+	} else {
+	  expr.append("true");
+	}
+      }
+      return expr.toString();
+    }
+
+   /**
+    * Returns source code for the tree as if-then statements. The 
+    * class is assigned to variable "p", and assumes the tested 
+    * instance is named "i". The results are returned as two stringbuffers: 
+    * a section of code for assignment of the class, and a section of
+    * code containing support code (eg: other support methods).
+    * <p/>
+    * TODO: If the outputted source code encounters a missing value
+    * for the evaluated attribute, it stops branching and uses the 
+    * class distribution of the current node to decide the return value. 
+    * This is unlike the behaviour of distributionForInstance(). 
+    *
+    * @param className the classname that this static classifier has
+    * @param parent parent node of the current node 
+    * @return an array containing two stringbuffers, the first string containing
+    * assignment code, and the second containing source for support code.
+    * @throws Exception if something goes wrong
+    */
+    public StringBuffer [] toSource(String className, Tree parent) 
+      throws Exception {
+    
+      StringBuffer [] result = new StringBuffer[2];
+      double[] currentProbs;
+
+      if(m_ClassProbs == null)
+        currentProbs = parent.m_ClassProbs;
+      else
+        currentProbs = m_ClassProbs;
+
+      long printID = nextID();
+
+      // Is this a leaf?
+      if (m_Attribute == -1) {
+        result[0] = new StringBuffer("	p = ");
+ 	if(m_Info.classAttribute().isNumeric())
+	  result[0].append(currentProbs[0]);
+	else {
+	  result[0].append(Utils.maxIndex(currentProbs));
+	}
+	result[0].append(";\n");
+	result[1] = new StringBuffer("");
+      } else {
+	StringBuffer text = new StringBuffer("");
+	StringBuffer atEnd = new StringBuffer("");
+
+	text.append("  static double N")
+	  .append(Integer.toHexString(this.hashCode()) + printID)
+	  .append("(Object []i) {\n")
+	  .append("    double p = Double.NaN;\n");
+
+        text.append("    /* " + m_Info.attribute(m_Attribute).name() + " */\n");
+	// Missing attribute?
+	text.append("    if (" + this.sourceExpression(-1) + ") {\n")
+	  .append("      p = ");
+	if(m_Info.classAttribute().isNumeric())
+	  text.append(currentProbs[0] + ";\n");
+	else
+	  text.append(Utils.maxIndex(currentProbs) + ";\n");
+	text.append("    } ");
+	
+	// Branching of the tree
+	for (int i=0;i<m_Successors.length; i++) {
+          text.append("else if (" + this.sourceExpression(i) + ") {\n");
+	  // Is the successor a leaf?
+	  if(m_Successors[i].m_Attribute == -1) {
+	    double[] successorProbs = m_Successors[i].m_ClassProbs;
+	    if(successorProbs == null)
+	      successorProbs = m_ClassProbs;
+	    text.append("      p = ");
+	    if(m_Info.classAttribute().isNumeric()) {
+	      text.append(successorProbs[0] + ";\n");
+	    } else {
+	      text.append(Utils.maxIndex(successorProbs) + ";\n");
+	    }
+	  } else {
+	    StringBuffer [] sub = m_Successors[i].toSource(className, this);
+	    text.append("" + sub[0]);
+            atEnd.append("" + sub[1]);
+	  }
+	  text.append("    } ");
+	  if (i == m_Successors.length - 1) {
+	    text.append("\n");
+	  }
+        }
+
+        text.append("    return p;\n  }\n");
+
+        result[0] = new StringBuffer("    p = " + className + ".N");
+        result[0].append(Integer.toHexString(this.hashCode()) + printID)
+          .append("(i);\n");
+        result[1] = text.append("" + atEnd);
+      }
+      return result;
+    }
+
+	
+    /**
+     * Outputs one node for graph.
+     * 
+     * @param text the buffer to append the output to
+     * @param num the current node id
+     * @param parent the parent of the nodes
+     * @return the next node id
+     * @throws Exception if something goes wrong
+     */
+    protected int toGraph(StringBuffer text, int num,
+			Tree parent) throws Exception {
+      
+      num++;
+      if (m_Attribute == -1) {
+	text.append("N" + Integer.toHexString(Tree.this.hashCode()) +
+		    " [label=\"" + num + leafString(parent) +"\"" +
+		    "shape=box]\n");
+      } else {
+	text.append("N" + Integer.toHexString(Tree.this.hashCode()) +
+		    " [label=\"" + num + ": " + 
+		    m_Info.attribute(m_Attribute).name() + 
+		    "\"]\n");
+	for (int i = 0; i < m_Successors.length; i++) {
+	  text.append("N" + Integer.toHexString(Tree.this.hashCode()) 
+		      + "->" + 
+		      "N" + 
+		      Integer.toHexString(m_Successors[i].hashCode())  +
+		      " [label=\"");
+	  if (m_Info.attribute(m_Attribute).isNumeric()) {
+	    if (i == 0) {
+	      text.append(" < " +
+			  Utils.doubleToString(m_SplitPoint, 2));
+	    } else {
+	      text.append(" >= " +
+			  Utils.doubleToString(m_SplitPoint, 2));
+	    }
+	  } else {
+	    text.append(" = " + m_Info.attribute(m_Attribute).value(i));
+	  }
+	  text.append("\"]\n");
+	  num = m_Successors[i].toGraph(text, num, this);
+	}
+      }
+      
+      return num;
+    }
+
+    /**
+     * Outputs description of a leaf node.
+     * 
+     * @param parent the parent of the node
+     * @return the description of the node
+     * @throws Exception if generation fails
+     */
+    protected String leafString(Tree parent) throws Exception {
+    
+      if (m_Info.classAttribute().isNumeric()) {
+	double classMean;
+	if (m_ClassProbs == null) {
+	  classMean = parent.m_ClassProbs[0];
+	} else {
+	  classMean = m_ClassProbs[0];
+	}
+	StringBuffer buffer = new StringBuffer();
+	buffer.append(" : " + Utils.doubleToString(classMean, 2));
+	double avgError = 0;
+	if (m_Distribution[1] > 0) {
+	  avgError = m_Distribution[0] / m_Distribution[1];
+	}
+	buffer.append(" (" +
+		      Utils.doubleToString(m_Distribution[1], 2) + "/" +
+		      Utils.doubleToString(avgError, 2) 
+		      + ")");
+	avgError = 0;
+	if (m_HoldOutDist[0] > 0) {
+	  avgError = m_HoldOutError / m_HoldOutDist[0];
+	}
+	buffer.append(" [" +
+		      Utils.doubleToString(m_HoldOutDist[0], 2) + "/" +
+		      Utils.doubleToString(avgError, 2) 
+		      + "]");
+	return buffer.toString();
+      } else { 
+	int maxIndex;
+	if (m_ClassProbs == null) {
+	  maxIndex = Utils.maxIndex(parent.m_ClassProbs);
+	} else {
+	  maxIndex = Utils.maxIndex(m_ClassProbs);
+	}
+	return " : " + m_Info.classAttribute().value(maxIndex) + 
+	  " (" + Utils.doubleToString(Utils.sum(m_Distribution), 2) + 
+	  "/" + 
+	  Utils.doubleToString((Utils.sum(m_Distribution) - 
+				m_Distribution[maxIndex]), 2) + ")" +
+	  " [" + Utils.doubleToString(Utils.sum(m_HoldOutDist), 2) + "/" + 
+	  Utils.doubleToString((Utils.sum(m_HoldOutDist) - 
+				m_HoldOutDist[maxIndex]), 2) + "]";
+      }
+    }
+  
+    /**
+     * Recursively outputs the tree.
+     * 
+     * @param level the current level
+     * @param parent the current parent
+     * @return the generated substree
+     */
+    protected String toString(int level, Tree parent) {
+
+      try {
+	StringBuffer text = new StringBuffer();
+      
+	if (m_Attribute == -1) {
+	
+	  // Output leaf info
+	  return leafString(parent);
+	} else if (m_Info.attribute(m_Attribute).isNominal()) {
+	
+	  // For nominal attributes
+	  for (int i = 0; i < m_Successors.length; i++) {
+	    text.append("\n");
+	    for (int j = 0; j < level; j++) {
+	      text.append("|   ");
+	    }
+	    text.append(m_Info.attribute(m_Attribute).name() + " = " +
+			m_Info.attribute(m_Attribute).value(i));
+	    text.append(m_Successors[i].toString(level + 1, this));
+	  }
+	} else {
+	
+	  // For numeric attributes
+	  text.append("\n");
+	  for (int j = 0; j < level; j++) {
+	    text.append("|   ");
+	  }
+	  text.append(m_Info.attribute(m_Attribute).name() + " < " +
+		      Utils.doubleToString(m_SplitPoint, 2));
+	  text.append(m_Successors[0].toString(level + 1, this));
+	  text.append("\n");
+	  for (int j = 0; j < level; j++) {
+	    text.append("|   ");
+	  }
+	  text.append(m_Info.attribute(m_Attribute).name() + " >= " +
+		      Utils.doubleToString(m_SplitPoint, 2));
+	  text.append(m_Successors[1].toString(level + 1, this));
+	}
+      
+	return text.toString();
+      } catch (Exception e) {
+	e.printStackTrace();
+	return "Decision tree: tree can't be printed";
+      }
+    }     
+
+    /**
+     * Recursively generates a tree.
+     * 
+     * @param sortedIndices the sorted indices of the instances
+     * @param weights the weights of the instances
+     * @param data the data to work with
+     * @param totalWeight
+     * @param classProbs the class probabilities
+     * @param header the header of the data
+     * @param minNum the minimum number of instances in a leaf
+     * @param minVariance
+     * @param depth the current depth of the tree
+     * @param maxDepth the maximum allowed depth of the tree
+     * @throws Exception if generation fails
+     */
+    protected void buildTree(int[][] sortedIndices, double[][] weights,
+			     Instances data, double totalWeight, 
+			     double[] classProbs, Instances header,
+			     double minNum, double minVariance,
+			     int depth, int maxDepth) 
+      throws Exception {
+      
+      // Store structure of dataset, set minimum number of instances
+      // and make space for potential info from pruning data
+      m_Info = header;
+      m_HoldOutDist = new double[data.numClasses()];
+	
+      // Make leaf if there are no training instances
+      int helpIndex = 0;
+      if (data.classIndex() == 0) {
+	helpIndex = 1;
+      }
+      if (sortedIndices[helpIndex].length == 0) {
+	if (data.classAttribute().isNumeric()) {
+	  m_Distribution = new double[2];
+	} else {
+	  m_Distribution = new double[data.numClasses()];
+	}
+	m_ClassProbs = null;
+	return;
+      }
+      
+      double priorVar = 0;
+      if (data.classAttribute().isNumeric()) {
+
+	// Compute prior variance
+	double totalSum = 0, totalSumSquared = 0, totalSumOfWeights = 0; 
+	for (int i = 0; i < sortedIndices[helpIndex].length; i++) {
+	  Instance inst = data.instance(sortedIndices[helpIndex][i]);
+	  totalSum += inst.classValue() * weights[helpIndex][i];
+	  totalSumSquared += 
+	    inst.classValue() * inst.classValue() * weights[helpIndex][i];
+	  totalSumOfWeights += weights[helpIndex][i];
+	}
+	priorVar = singleVariance(totalSum, totalSumSquared, 
+				  totalSumOfWeights);
+      }
+
+      // Check if node doesn't contain enough instances, is pure
+      // or the maximum tree depth is reached
+      m_ClassProbs = new double[classProbs.length];
+      System.arraycopy(classProbs, 0, m_ClassProbs, 0, classProbs.length);
+      if ((totalWeight < (2 * minNum)) ||
+
+	  // Nominal case
+	  (data.classAttribute().isNominal() &&
+	   Utils.eq(m_ClassProbs[Utils.maxIndex(m_ClassProbs)],
+		    Utils.sum(m_ClassProbs))) ||
+
+	  // Numeric case
+	  (data.classAttribute().isNumeric() && 
+	   ((priorVar / totalWeight) < minVariance)) ||
+
+	  // Check tree depth
+	  ((m_MaxDepth >= 0) && (depth >= maxDepth))) {
+
+	// Make leaf
+	m_Attribute = -1;
+	if (data.classAttribute().isNominal()) {
+
+	  // Nominal case
+	  m_Distribution = new double[m_ClassProbs.length];
+	  for (int i = 0; i < m_ClassProbs.length; i++) {
+	    m_Distribution[i] = m_ClassProbs[i];
+	  }
+	  Utils.normalize(m_ClassProbs);
+	} else {
+
+	  // Numeric case
+	  m_Distribution = new double[2];
+	  m_Distribution[0] = priorVar;
+	  m_Distribution[1] = totalWeight;
+	}
+	return;
+      }
+
+      // Compute class distributions and value of splitting
+      // criterion for each attribute
+      double[] vals = new double[data.numAttributes()];
+      double[][][] dists = new double[data.numAttributes()][0][0];
+      double[][] props = new double[data.numAttributes()][0];
+      double[][] totalSubsetWeights = new double[data.numAttributes()][0];
+      double[] splits = new double[data.numAttributes()];
+      if (data.classAttribute().isNominal()) { 
+
+	// Nominal case
+	for (int i = 0; i < data.numAttributes(); i++) {
+	  if (i != data.classIndex()) {
+	    splits[i] = distribution(props, dists, i, sortedIndices[i], 
+				     weights[i], totalSubsetWeights, data);
+	    vals[i] = gain(dists[i], priorVal(dists[i]));
+	  }
+	}
+      } else {
+
+	// Numeric case
+	for (int i = 0; i < data.numAttributes(); i++) {
+	  if (i != data.classIndex()) {
+	    splits[i] = 
+	      numericDistribution(props, dists, i, sortedIndices[i], 
+				  weights[i], totalSubsetWeights, data, 
+				  vals);
+	  }
+	}
+      }
+
+      // Find best attribute
+      m_Attribute = Utils.maxIndex(vals);
+      int numAttVals = dists[m_Attribute].length;
+
+      // Check if there are at least two subsets with
+      // required minimum number of instances
+      int count = 0;
+      for (int i = 0; i < numAttVals; i++) {
+	if (totalSubsetWeights[m_Attribute][i] >= minNum) {
+	  count++;
+	}
+	if (count > 1) {
+	  break;
+	}
+      }
+
+      // Any useful split found?
+      if ((vals[m_Attribute] > 0) && (count > 1)) {
+
+	// Build subtrees
+	m_SplitPoint = splits[m_Attribute];
+	m_Prop = props[m_Attribute];
+	int[][][] subsetIndices = 
+	  new int[numAttVals][data.numAttributes()][0];
+	double[][][] subsetWeights = 
+	  new double[numAttVals][data.numAttributes()][0];
+	splitData(subsetIndices, subsetWeights, m_Attribute, m_SplitPoint, 
+		  sortedIndices, weights, data);
+	m_Successors = new Tree[numAttVals];
+	for (int i = 0; i < numAttVals; i++) {
+	  m_Successors[i] = new Tree();
+	  m_Successors[i].
+	    buildTree(subsetIndices[i], subsetWeights[i], 
+		      data, totalSubsetWeights[m_Attribute][i],
+		      dists[m_Attribute][i], header, minNum, 
+		      minVariance, depth + 1, maxDepth);
+	}
+      } else {
+      
+	// Make leaf
+	m_Attribute = -1;
+      }
+
+      // Normalize class counts
+      if (data.classAttribute().isNominal()) {
+	m_Distribution = new double[m_ClassProbs.length];
+	for (int i = 0; i < m_ClassProbs.length; i++) {
+	    m_Distribution[i] = m_ClassProbs[i];
+	}
+	Utils.normalize(m_ClassProbs);
+      } else {
+	m_Distribution = new double[2];
+	m_Distribution[0] = priorVar;
+	m_Distribution[1] = totalWeight;
+      }
+    }
+
+    /**
+     * Computes size of the tree.
+     * 
+     * @return the number of nodes
+     */
+    protected int numNodes() {
+    
+      if (m_Attribute == -1) {
+	return 1;
+      } else {
+	int size = 1;
+	for (int i = 0; i < m_Successors.length; i++) {
+	  size += m_Successors[i].numNodes();
+	}
+	return size;
+      }
+    }
+
+    /**
+     * Splits instances into subsets.
+     * 
+     * @param subsetIndices the sorted indices in the subset
+     * @param subsetWeights the weights of the subset
+     * @param att the attribute index
+     * @param splitPoint the split point for numeric attributes
+     * @param sortedIndices the sorted indices of the whole set
+     * @param weights the weights of the whole set
+     * @param data the data to work with
+     * @throws Exception if something goes wrong
+     */
+    protected void splitData(int[][][] subsetIndices, 
+			     double[][][] subsetWeights,
+			     int att, double splitPoint, 
+			     int[][] sortedIndices, double[][] weights, 
+			     Instances data) throws Exception {
+    
+      int j;
+      int[] num;
+   
+      // For each attribute
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (i != data.classIndex()) {
+	  if (data.attribute(att).isNominal()) {
+
+	    // For nominal attributes
+	    num = new int[data.attribute(att).numValues()];
+	    for (int k = 0; k < num.length; k++) {
+	      subsetIndices[k][i] = new int[sortedIndices[i].length];
+	      subsetWeights[k][i] = new double[sortedIndices[i].length];
+	    }
+	    for (j = 0; j < sortedIndices[i].length; j++) {
+	      Instance inst = data.instance(sortedIndices[i][j]);
+	      if (inst.isMissing(att)) {
+
+		// Split instance up
+		for (int k = 0; k < num.length; k++) {
+		  if (m_Prop[k] > 0) {
+		    subsetIndices[k][i][num[k]] = sortedIndices[i][j];
+		    subsetWeights[k][i][num[k]] = 
+		      m_Prop[k] * weights[i][j];
+		    num[k]++;
+		  }
+		}
+	      } else {
+		int subset = (int)inst.value(att);
+		subsetIndices[subset][i][num[subset]] = 
+		  sortedIndices[i][j];
+		subsetWeights[subset][i][num[subset]] = weights[i][j];
+		num[subset]++;
+	      }
+	    }
+	  } else {
+
+	    // For numeric attributes
+	    num = new int[2];
+	    for (int k = 0; k < 2; k++) {
+	      subsetIndices[k][i] = new int[sortedIndices[i].length];
+	      subsetWeights[k][i] = new double[weights[i].length];
+	    }
+	    for (j = 0; j < sortedIndices[i].length; j++) {
+	      Instance inst = data.instance(sortedIndices[i][j]);
+	      if (inst.isMissing(att)) {
+
+		// Split instance up
+		for (int k = 0; k < num.length; k++) {
+		  if (m_Prop[k] > 0) {
+		    subsetIndices[k][i][num[k]] = sortedIndices[i][j];
+		    subsetWeights[k][i][num[k]] = 
+		      m_Prop[k] * weights[i][j];
+		    num[k]++;
+		  }
+		}
+	      } else {
+		int subset = (inst.value(att) < splitPoint) ? 0 : 1;
+		subsetIndices[subset][i][num[subset]] = 
+		  sortedIndices[i][j];
+		subsetWeights[subset][i][num[subset]] = weights[i][j];
+		num[subset]++;
+	      } 
+	    }
+	  }
+	
+	  // Trim arrays
+	  for (int k = 0; k < num.length; k++) {
+	    int[] copy = new int[num[k]];
+	    System.arraycopy(subsetIndices[k][i], 0, copy, 0, num[k]);
+	    subsetIndices[k][i] = copy;
+	    double[] copyWeights = new double[num[k]];
+	    System.arraycopy(subsetWeights[k][i], 0,
+			     copyWeights, 0, num[k]);
+	    subsetWeights[k][i] = copyWeights;
+	  }
+	}
+      }
+    }
+
+    /**
+     * Computes class distribution for an attribute.
+     * 
+     * @param props
+     * @param dists
+     * @param att the attribute index
+     * @param sortedIndices the sorted indices of the instances
+     * @param weights the weights of the instances
+     * @param subsetWeights the weights of the subset
+     * @param data the data to work with
+     * @return the split point
+     * @throws Exception if computation fails
+     */
+    protected double distribution(double[][] props,
+				  double[][][] dists, int att, 
+				  int[] sortedIndices,
+				  double[] weights, 
+				  double[][] subsetWeights, 
+				  Instances data) 
+      throws Exception {
+
+      double splitPoint = Double.NaN;
+      Attribute attribute = data.attribute(att);
+      double[][] dist = null;
+      int i;
+
+      if (attribute.isNominal()) {
+
+	// For nominal attributes
+	dist = new double[attribute.numValues()][data.numClasses()];
+	for (i = 0; i < sortedIndices.length; i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  dist[(int)inst.value(att)][(int)inst.classValue()] += weights[i];
+	}
+      } else {
+
+	// For numeric attributes
+	double[][] currDist = new double[2][data.numClasses()];
+	dist = new double[2][data.numClasses()];
+
+	// Move all instances into second subset
+	for (int j = 0; j < sortedIndices.length; j++) {
+	  Instance inst = data.instance(sortedIndices[j]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  currDist[1][(int)inst.classValue()] += weights[j];
+	}
+	double priorVal = priorVal(currDist);
+	System.arraycopy(currDist[1], 0, dist[1], 0, dist[1].length);
+
+	// Try all possible split points
+	double currSplit = data.instance(sortedIndices[0]).value(att);
+	double currVal, bestVal = -Double.MAX_VALUE;
+	for (i = 0; i < sortedIndices.length; i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  if (inst.value(att) > currSplit) {
+	    currVal = gain(currDist, priorVal);
+	    if (currVal > bestVal) {
+	      bestVal = currVal;
+	      splitPoint = (inst.value(att) + currSplit) / 2.0;
+	      for (int j = 0; j < currDist.length; j++) {
+		System.arraycopy(currDist[j], 0, dist[j], 0, 
+				 dist[j].length);
+	      }
+	    } 
+	  } 
+	  currSplit = inst.value(att);
+	  currDist[0][(int)inst.classValue()] += weights[i];
+	  currDist[1][(int)inst.classValue()] -= weights[i];
+	}
+      }
+
+      // Compute weights
+      props[att] = new double[dist.length];
+      for (int k = 0; k < props[att].length; k++) {
+	props[att][k] = Utils.sum(dist[k]);
+      }
+      if (!(Utils.sum(props[att]) > 0)) {
+	for (int k = 0; k < props[att].length; k++) {
+	  props[att][k] = 1.0 / (double)props[att].length;
+	}
+      } else {
+	Utils.normalize(props[att]);
+      }
+    
+      // Distribute counts
+      while (i < sortedIndices.length) {
+	Instance inst = data.instance(sortedIndices[i]);
+	for (int j = 0; j < dist.length; j++) {
+	  dist[j][(int)inst.classValue()] += props[att][j] * weights[i];
+	}
+	i++;
+      }
+
+      // Compute subset weights
+      subsetWeights[att] = new double[dist.length];
+      for (int j = 0; j < dist.length; j++) {
+	subsetWeights[att][j] += Utils.sum(dist[j]);
+      }
+
+      // Return distribution and split point
+      dists[att] = dist;
+      return splitPoint;
+    }      
+
+    /**
+     * Computes class distribution for an attribute.
+     * 
+     * @param props
+     * @param dists
+     * @param att the attribute index
+     * @param sortedIndices the sorted indices of the instances
+     * @param weights the weights of the instances
+     * @param subsetWeights the weights of the subset
+     * @param data the data to work with
+     * @param vals
+     * @return the split point
+     * @throws Exception if computation fails
+     */
+    protected double numericDistribution(double[][] props, 
+					 double[][][] dists, int att, 
+					 int[] sortedIndices,
+					 double[] weights, 
+					 double[][] subsetWeights, 
+					 Instances data,
+					 double[] vals) 
+      throws Exception {
+
+      double splitPoint = Double.NaN;
+      Attribute attribute = data.attribute(att);
+      double[][] dist = null;
+      double[] sums = null;
+      double[] sumSquared = null;
+      double[] sumOfWeights = null;
+      double totalSum = 0, totalSumSquared = 0, totalSumOfWeights = 0;
+
+      int i;
+
+      if (attribute.isNominal()) {
+
+	// For nominal attributes
+	sums = new double[attribute.numValues()];
+        sumSquared = new double[attribute.numValues()];
+	sumOfWeights = new double[attribute.numValues()];
+	int attVal;
+	for (i = 0; i < sortedIndices.length; i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  attVal = (int)inst.value(att);
+	  sums[attVal] += inst.classValue() * weights[i];
+	  sumSquared[attVal] += 
+	    inst.classValue() * inst.classValue() * weights[i];
+	  sumOfWeights[attVal] += weights[i];
+	}
+	totalSum = Utils.sum(sums);
+	totalSumSquared = Utils.sum(sumSquared);
+	totalSumOfWeights = Utils.sum(sumOfWeights);
+      } else {
+
+	// For numeric attributes
+	sums = new double[2];
+        sumSquared = new double[2];
+	sumOfWeights = new double[2];
+	double[] currSums = new double[2];
+        double[] currSumSquared = new double[2];
+	double[] currSumOfWeights = new double[2];
+
+	// Move all instances into second subset
+	for (int j = 0; j < sortedIndices.length; j++) {
+	  Instance inst = data.instance(sortedIndices[j]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  currSums[1] += inst.classValue() * weights[j];
+	  currSumSquared[1] += 
+	    inst.classValue() * inst.classValue() * weights[j];
+	  currSumOfWeights[1] += weights[j];
+	  
+	}
+	totalSum = currSums[1];
+	totalSumSquared = currSumSquared[1];
+	totalSumOfWeights = currSumOfWeights[1];
+	
+	sums[1] = currSums[1];
+	sumSquared[1] = currSumSquared[1];
+	sumOfWeights[1] = currSumOfWeights[1];
+
+	// Try all possible split points
+	double currSplit = data.instance(sortedIndices[0]).value(att);
+	double currVal, bestVal = Double.MAX_VALUE;
+	for (i = 0; i < sortedIndices.length; i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+	  if (inst.value(att) > currSplit) {
+	    currVal = variance(currSums, currSumSquared, currSumOfWeights);
+	    if (currVal < bestVal) {
+	      bestVal = currVal;
+	      splitPoint = (inst.value(att) + currSplit) / 2.0;
+	      for (int j = 0; j < 2; j++) {
+		sums[j] = currSums[j];
+		sumSquared[j] = currSumSquared[j];
+		sumOfWeights[j] = currSumOfWeights[j];
+	      }
+	    } 
+	  } 
+
+	  currSplit = inst.value(att);
+
+	  double classVal = inst.classValue() * weights[i];
+	  double classValSquared = inst.classValue() * classVal;
+
+	  currSums[0] += classVal;
+	  currSumSquared[0] += classValSquared;
+	  currSumOfWeights[0] += weights[i];
+
+	  currSums[1] -= classVal;
+	  currSumSquared[1] -= classValSquared;
+	  currSumOfWeights[1] -= weights[i];
+	}
+      }
+
+      // Compute weights
+      props[att] = new double[sums.length];
+      for (int k = 0; k < props[att].length; k++) {
+	props[att][k] = sumOfWeights[k];
+      }
+      if (!(Utils.sum(props[att]) > 0)) {
+	for (int k = 0; k < props[att].length; k++) {
+	  props[att][k] = 1.0 / (double)props[att].length;
+	}
+      } else {
+	Utils.normalize(props[att]);
+      }
+    
+	
+      // Distribute counts for missing values
+      while (i < sortedIndices.length) {
+	Instance inst = data.instance(sortedIndices[i]);
+	for (int j = 0; j < sums.length; j++) {
+	  sums[j] += props[att][j] * inst.classValue() * weights[i];
+	  sumSquared[j] += props[att][j] * inst.classValue() * 
+	    inst.classValue() * weights[i];
+	  sumOfWeights[j] += props[att][j] * weights[i];
+	}
+	totalSum += inst.classValue() * weights[i];
+	totalSumSquared += 
+	  inst.classValue() * inst.classValue() * weights[i]; 
+	totalSumOfWeights += weights[i];
+	i++;
+      }
+
+      // Compute final distribution
+      dist = new double[sums.length][data.numClasses()];
+      for (int j = 0; j < sums.length; j++) {
+	if (sumOfWeights[j] > 0) {
+	  dist[j][0] = sums[j] / sumOfWeights[j];
+	} else {
+	  dist[j][0] = totalSum / totalSumOfWeights;
+	}
+      }
+      
+      // Compute variance gain
+      double priorVar =
+	singleVariance(totalSum, totalSumSquared, totalSumOfWeights);
+      double var = variance(sums, sumSquared, sumOfWeights);
+      double gain = priorVar - var;
+      
+      // Return distribution and split point
+      subsetWeights[att] = sumOfWeights;
+      dists[att] = dist;
+      vals[att] = gain;
+      return splitPoint;
+    }      
+
+    /**
+     * Computes variance for subsets.
+     * 
+     * @param s
+     * @param sS
+     * @param sumOfWeights
+     * @return the variance
+     */
+    protected double variance(double[] s, double[] sS, 
+			    double[] sumOfWeights) {
+      
+      double var = 0;
+      
+      for (int i = 0; i < s.length; i++) {
+	if (sumOfWeights[i] > 0) {
+	  var += singleVariance(s[i], sS[i], sumOfWeights[i]);
+	}
+      }
+      
+      return var;
+    }
+    
+    /** 
+     * Computes the variance for a single set
+     * 
+     * @param s
+     * @param sS
+     * @param weight the weight
+     * @return the variance
+     */
+    protected double singleVariance(double s, double sS, double weight) {
+      
+      return sS - ((s * s) / weight);
+    }
+
+    /**
+     * Computes value of splitting criterion before split.
+     * 
+     * @param dist
+     * @return the splitting criterion
+     */
+    protected double priorVal(double[][] dist) {
+
+      return ContingencyTables.entropyOverColumns(dist);
+    }
+
+    /**
+     * Computes value of splitting criterion after split.
+     * 
+     * @param dist
+     * @param priorVal the splitting criterion
+     * @return the gain after splitting
+     */
+    protected double gain(double[][] dist, double priorVal) {
+
+      return priorVal - ContingencyTables.entropyConditionedOnRows(dist);
+    }
+
+    /**
+     * Prunes the tree using the hold-out data (bottom-up).
+     * 
+     * @return the error
+     * @throws Exception if pruning fails for some reason
+     */
+    protected double reducedErrorPrune() throws Exception {
+
+      // Is node leaf ? 
+      if (m_Attribute == -1) {
+	return m_HoldOutError;
+      }
+
+      // Prune all sub trees
+      double errorTree = 0;
+      for (int i = 0; i < m_Successors.length; i++) {
+	errorTree += m_Successors[i].reducedErrorPrune();
+      }
+
+      // Replace sub tree with leaf if error doesn't get worse
+      if (errorTree >= m_HoldOutError) {
+	m_Attribute = -1;
+	m_Successors = null;
+	return m_HoldOutError;
+      } else {
+	return errorTree;
+      }
+    }
+
+    /**
+     * Inserts hold-out set into tree.
+     * 
+     * @param data the data to insert
+     * @throws Exception if something goes wrong
+     */
+    protected void insertHoldOutSet(Instances data) throws Exception {
+
+      for (int i = 0; i < data.numInstances(); i++) {
+	insertHoldOutInstance(data.instance(i), data.instance(i).weight(),
+			      this);
+      }
+    }
+
+    /**
+     * Inserts an instance from the hold-out set into the tree.
+     * 
+     * @param inst the instance to insert
+     * @param weight the weight of the instance
+     * @param parent the parent of the node
+     * @throws Exception if insertion fails
+     */
+    protected void insertHoldOutInstance(Instance inst, double weight, 
+					 Tree parent) throws Exception {
+      
+      // Insert instance into hold-out class distribution
+      if (inst.classAttribute().isNominal()) {
+	
+	// Nominal case
+	m_HoldOutDist[(int)inst.classValue()] += weight;
+	int predictedClass = 0;
+	if (m_ClassProbs == null) {
+	  predictedClass = Utils.maxIndex(parent.m_ClassProbs);
+	} else {
+	  predictedClass = Utils.maxIndex(m_ClassProbs);
+	}
+	if (predictedClass != (int)inst.classValue()) {
+	  m_HoldOutError += weight;
+	}
+      } else {
+	
+	// Numeric case
+	m_HoldOutDist[0] += weight;
+	double diff = 0;
+	if (m_ClassProbs == null) {
+	  diff = parent.m_ClassProbs[0] - inst.classValue();
+	} else {
+	  diff =  m_ClassProbs[0] - inst.classValue();
+	}
+	m_HoldOutError += diff * diff * weight;
+      }	
+      
+      // The process is recursive
+      if (m_Attribute != -1) {
+	
+	// If node is not a leaf
+	if (inst.isMissing(m_Attribute)) {
+	  
+	  // Distribute instance
+	  for (int i = 0; i < m_Successors.length; i++) {
+	    if (m_Prop[i] > 0) {
+	      m_Successors[i].insertHoldOutInstance(inst, weight * 
+						    m_Prop[i], this);
+	    }
+	  }
+	} else {
+	  
+	  if (m_Info.attribute(m_Attribute).isNominal()) {
+	    
+	    // Treat nominal attributes
+	    m_Successors[(int)inst.value(m_Attribute)].
+	      insertHoldOutInstance(inst, weight, this);
+	  } else {
+	    
+	    // Treat numeric attributes
+	    if (inst.value(m_Attribute) < m_SplitPoint) {
+	      m_Successors[0].insertHoldOutInstance(inst, weight, this);
+	    } else {
+	      m_Successors[1].insertHoldOutInstance(inst, weight, this);
+	    }
+	  }
+	}
+      }
+    }
+  
+    /**
+     * Inserts hold-out set into tree.
+     * 
+     * @param data the data to insert
+     * @throws Exception if insertion fails
+     */
+    protected void backfitHoldOutSet(Instances data) throws Exception {
+      
+      for (int i = 0; i < data.numInstances(); i++) {
+	backfitHoldOutInstance(data.instance(i), data.instance(i).weight(),
+			       this);
+      }
+    }
+    
+    /**
+     * Inserts an instance from the hold-out set into the tree.
+     * 
+     * @param inst the instance to insert
+     * @param weight the weight of the instance
+     * @param parent the parent node
+     * @throws Exception if insertion fails
+     */
+    protected void backfitHoldOutInstance(Instance inst, double weight, 
+					  Tree parent) throws Exception {
+      
+      // Insert instance into hold-out class distribution
+      if (inst.classAttribute().isNominal()) {
+	
+	// Nominal case
+	if (m_ClassProbs == null) {
+	  m_ClassProbs = new double[inst.numClasses()];
+	}
+	System.arraycopy(m_Distribution, 0, m_ClassProbs, 0, inst.numClasses());
+	m_ClassProbs[(int)inst.classValue()] += weight;
+	Utils.normalize(m_ClassProbs);
+      } else {
+	
+	// Numeric case
+	if (m_ClassProbs == null) {
+	  m_ClassProbs = new double[1];
+	}
+	m_ClassProbs[0] *= m_Distribution[1];
+	m_ClassProbs[0] += weight * inst.classValue();
+	m_ClassProbs[0] /= (m_Distribution[1] + weight);
+      }	
+      
+      // The process is recursive
+      if (m_Attribute != -1) {
+	
+	// If node is not a leaf
+	if (inst.isMissing(m_Attribute)) {
+	  
+	  // Distribute instance
+	  for (int i = 0; i < m_Successors.length; i++) {
+	    if (m_Prop[i] > 0) {
+	      m_Successors[i].backfitHoldOutInstance(inst, weight * 
+						     m_Prop[i], this);
+	    }
+	  }
+	} else {
+	  
+	  if (m_Info.attribute(m_Attribute).isNominal()) {
+	    
+	    // Treat nominal attributes
+	    m_Successors[(int)inst.value(m_Attribute)].
+	      backfitHoldOutInstance(inst, weight, this);
+	  } else {
+	    
+	    // Treat numeric attributes
+	    if (inst.value(m_Attribute) < m_SplitPoint) {
+	      m_Successors[0].backfitHoldOutInstance(inst, weight, this);
+	    } else {
+	      m_Successors[1].backfitHoldOutInstance(inst, weight, this);
+	    }
+	  }
+	}
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+  }
+
+  /** The Tree object */
+  protected Tree m_Tree = null;
+    
+  /** Number of folds for reduced error pruning. */
+  protected int m_NumFolds = 3;
+    
+  /** Seed for random data shuffling. */
+  protected int m_Seed = 1;
+    
+  /** Don't prune */
+  protected boolean m_NoPruning = false;
+
+  /** The minimum number of instances per leaf. */
+  protected double m_MinNum = 2;
+
+  /** The minimum proportion of the total variance (over all the data)
+      required for split. */
+  protected double m_MinVarianceProp = 1e-3;
+
+  /** Upper bound on the tree depth */
+  protected int m_MaxDepth = -1;
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String noPruningTipText() {
+    return "Whether pruning is performed.";
+  }
+  
+  /**
+   * Get the value of NoPruning.
+   *
+   * @return Value of NoPruning.
+   */
+  public boolean getNoPruning() {
+    
+    return m_NoPruning;
+  }
+  
+  /**
+   * Set the value of NoPruning.
+   *
+   * @param newNoPruning Value to assign to NoPruning.
+   */
+  public void setNoPruning(boolean newNoPruning) {
+    
+    m_NoPruning = newNoPruning;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minNumTipText() {
+    return "The minimum total weight of the instances in a leaf.";
+  }
+
+  /**
+   * Get the value of MinNum.
+   *
+   * @return Value of MinNum.
+   */
+  public double getMinNum() {
+    
+    return m_MinNum;
+  }
+  
+  /**
+   * Set the value of MinNum.
+   *
+   * @param newMinNum Value to assign to MinNum.
+   */
+  public void setMinNum(double newMinNum) {
+    
+    m_MinNum = newMinNum;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minVariancePropTipText() {
+    return "The minimum proportion of the variance on all the data " +
+      "that needs to be present at a node in order for splitting to " +
+      "be performed in regression trees.";
+  }
+
+  /**
+   * Get the value of MinVarianceProp.
+   *
+   * @return Value of MinVarianceProp.
+   */
+  public double getMinVarianceProp() {
+    
+    return m_MinVarianceProp;
+  }
+  
+  /**
+   * Set the value of MinVarianceProp.
+   *
+   * @param newMinVarianceProp Value to assign to MinVarianceProp.
+   */
+  public void setMinVarianceProp(double newMinVarianceProp) {
+    
+    m_MinVarianceProp = newMinVarianceProp;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed used for randomizing the data.";
+  }
+
+  /**
+   * Get the value of Seed.
+   *
+   * @return Value of Seed.
+   */
+  public int getSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the value of Seed.
+   *
+   * @param newSeed Value to assign to Seed.
+   */
+  public void setSeed(int newSeed) {
+    
+    m_Seed = newSeed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Determines the amount of data used for pruning. One fold is used for "
+      + "pruning, the rest for growing the rules.";
+  }
+  
+  /**
+   * Get the value of NumFolds.
+   *
+   * @return Value of NumFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_NumFolds;
+  }
+  
+  /**
+   * Set the value of NumFolds.
+   *
+   * @param newNumFolds Value to assign to NumFolds.
+   */
+  public void setNumFolds(int newNumFolds) {
+    
+    m_NumFolds = newNumFolds;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxDepthTipText() {
+    return "The maximum tree depth (-1 for no restriction).";
+  }
+
+  /**
+   * Get the value of MaxDepth.
+   *
+   * @return Value of MaxDepth.
+   */
+  public int getMaxDepth() {
+    
+    return m_MaxDepth;
+  }
+  
+  /**
+   * Set the value of MaxDepth.
+   *
+   * @param newMaxDepth Value to assign to MaxDepth.
+   */
+  public void setMaxDepth(int newMaxDepth) {
+    
+    m_MaxDepth = newMaxDepth;
+  }
+  
+  /**
+   * Lists the command-line options for this classifier.
+   * 
+   * @return an enumeration over all commandline options
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(5);
+
+    newVector.
+      addElement(new Option("\tSet minimum number of instances per leaf " +
+			    "(default 2).",
+			    "M", 1, "-M <minimum number of instances>"));
+    newVector.
+      addElement(new Option("\tSet minimum numeric class variance proportion\n" +
+			    "\tof train variance for split (default 1e-3).",
+			    "V", 1, "-V <minimum variance for split>"));
+    newVector.
+      addElement(new Option("\tNumber of folds for reduced error pruning " +
+			    "(default 3).",
+			    "N", 1, "-N <number of folds>"));
+    newVector.
+      addElement(new Option("\tSeed for random data shuffling (default 1).",
+			    "S", 1, "-S <seed>"));
+    newVector.
+      addElement(new Option("\tNo pruning.",
+			    "P", 0, "-P"));
+    newVector.
+      addElement(new Option("\tMaximum tree depth (default -1, no maximum)",
+			    "L", 1, "-L"));
+
+    return newVector.elements();
+  } 
+
+  /**
+   * Gets options from this classifier.
+   * 
+   * @return the options for the current setup
+   */
+  public String[] getOptions() {
+    
+    String [] options = new String [12];
+    int current = 0;
+    options[current++] = "-M"; 
+    options[current++] = "" + (int)getMinNum();
+    options[current++] = "-V"; 
+    options[current++] = "" + getMinVarianceProp();
+    options[current++] = "-N"; 
+    options[current++] = "" + getNumFolds();
+    options[current++] = "-S"; 
+    options[current++] = "" + getSeed();
+    options[current++] = "-L"; 
+    options[current++] = "" + getMaxDepth();
+    if (getNoPruning()) {
+      options[current++] = "-P";
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf (default 2).</pre>
+   * 
+   * <pre> -V &lt;minimum variance for split&gt;
+   *  Set minimum numeric class variance proportion
+   *  of train variance for split (default 1e-3).</pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Number of folds for reduced error pruning (default 3).</pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Seed for random data shuffling (default 1).</pre>
+   * 
+   * <pre> -P
+   *  No pruning.</pre>
+   * 
+   * <pre> -L
+   *  Maximum tree depth (default -1, no maximum)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String minNumString = Utils.getOption('M', options);
+    if (minNumString.length() != 0) {
+      m_MinNum = (double)Integer.parseInt(minNumString);
+    } else {
+      m_MinNum = 2;
+    }
+    String minVarString = Utils.getOption('V', options);
+    if (minVarString.length() != 0) {
+      m_MinVarianceProp = Double.parseDouble(minVarString);
+    } else {
+      m_MinVarianceProp = 1e-3;
+    }
+    String numFoldsString = Utils.getOption('N', options);
+    if (numFoldsString.length() != 0) {
+      m_NumFolds = Integer.parseInt(numFoldsString);
+    } else {
+      m_NumFolds = 3;
+    }
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      m_Seed = Integer.parseInt(seedString);
+    } else {
+      m_Seed = 1;
+    }
+    m_NoPruning = Utils.getFlag('P', options);
+    String depthString = Utils.getOption('L', options);
+    if (depthString.length() != 0) {
+      m_MaxDepth = Integer.parseInt(depthString);
+    } else {
+      m_MaxDepth = -1;
+    }
+    Utils.checkForRemainingOptions(options);
+  }
+  
+  /**
+   * Computes size of the tree.
+   * 
+   * @return the number of nodes
+   */
+  public int numNodes() {
+
+    return m_Tree.numNodes();
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names.
+   *
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureTreeSize");
+    return newVector.elements();
+  }
+ 
+  /**
+   * Returns the value of the named measure.
+   *
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    
+    if (additionalMeasureName.equalsIgnoreCase("measureTreeSize")) {
+      return (double) numNodes();
+    }
+    else {throw new IllegalArgumentException(additionalMeasureName 
+			      + " not supported (REPTree)");
+    }
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Builds classifier.
+   * 
+   * @param data the data to train with
+   * @throws Exception if building fails
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    Random random = new Random(m_Seed);
+
+    m_zeroR = null;
+    if (data.numAttributes() == 1) {
+      m_zeroR = new ZeroR();
+      m_zeroR.buildClassifier(data);
+      return;
+    }
+
+    // Randomize and stratify
+    data.randomize(random);
+    if (data.classAttribute().isNominal()) {
+      data.stratify(m_NumFolds);
+    }
+
+    // Split data into training and pruning set
+    Instances train = null;
+    Instances prune = null;
+    if (!m_NoPruning) {
+      train = data.trainCV(m_NumFolds, 0, random);
+      prune = data.testCV(m_NumFolds, 0);
+    } else {
+      train = data;
+    }
+
+    // Create array of sorted indices and weights
+    int[][] sortedIndices = new int[train.numAttributes()][0];
+    double[][] weights = new double[train.numAttributes()][0];
+    double[] vals = new double[train.numInstances()];
+    for (int j = 0; j < train.numAttributes(); j++) {
+      if (j != train.classIndex()) {
+	weights[j] = new double[train.numInstances()];
+	if (train.attribute(j).isNominal()) {
+
+	  // Handling nominal attributes. Putting indices of
+	  // instances with missing values at the end.
+	  sortedIndices[j] = new int[train.numInstances()];
+	  int count = 0;
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    Instance inst = train.instance(i);
+	    if (!inst.isMissing(j)) {
+	      sortedIndices[j][count] = i;
+	      weights[j][count] = inst.weight();
+	      count++;
+	    }
+	  }
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    Instance inst = train.instance(i);
+	    if (inst.isMissing(j)) {
+	      sortedIndices[j][count] = i;
+	      weights[j][count] = inst.weight();
+	      count++;
+	    }
+	  }
+	} else {
+
+	  // Sorted indices are computed for numeric attributes
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    Instance inst = train.instance(i);
+	    vals[i] = inst.value(j);
+	  }
+	  sortedIndices[j] = Utils.sort(vals);
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    weights[j][i] = train.instance(sortedIndices[j][i]).weight();
+	  }
+	}
+      }
+    }
+
+    // Compute initial class counts
+    double[] classProbs = new double[train.numClasses()];
+    double totalWeight = 0, totalSumSquared = 0;
+    for (int i = 0; i < train.numInstances(); i++) {
+      Instance inst = train.instance(i);
+      if (data.classAttribute().isNominal()) {
+	classProbs[(int)inst.classValue()] += inst.weight();
+	totalWeight += inst.weight();
+      } else {
+	classProbs[0] += inst.classValue() * inst.weight();
+	totalSumSquared += inst.classValue() * inst.classValue() * inst.weight();
+	totalWeight += inst.weight();
+      }
+    }
+    m_Tree = new Tree();
+    double trainVariance = 0;
+    if (data.classAttribute().isNumeric()) {
+      trainVariance = m_Tree.
+	singleVariance(classProbs[0], totalSumSquared, totalWeight) / totalWeight;
+      classProbs[0] /= totalWeight;
+    }
+
+    // Build tree
+    m_Tree.buildTree(sortedIndices, weights, train, totalWeight, classProbs,
+		     new Instances(train, 0), m_MinNum, m_MinVarianceProp * 
+		     trainVariance, 0, m_MaxDepth);
+    
+    // Insert pruning data and perform reduced error pruning
+    if (!m_NoPruning) {
+      m_Tree.insertHoldOutSet(prune);
+      m_Tree.reducedErrorPrune();
+      m_Tree.backfitHoldOutSet(prune);
+    }
+  }
+
+  /**
+   * Computes class distribution of an instance using the tree.
+   * 
+   * @param instance the instance to compute the distribution for
+   * @return the computed class probabilities
+   * @throws Exception if computation fails
+   */
+  public double[] distributionForInstance(Instance instance) 
+    throws Exception {
+      
+      if (m_zeroR != null) {
+	return m_zeroR.distributionForInstance(instance);
+      } else {
+	return m_Tree.distributionForInstance(instance);
+      }
+  }
+
+
+  /** 
+   * For getting a unique ID when outputting the tree source
+   * (hashcode isn't guaranteed unique) 
+   */
+  private static long PRINTED_NODES = 0;
+
+  /**
+   * Gets the next unique node ID.
+   *
+   * @return the next unique node ID.
+   */
+  protected static long nextID() {
+
+    return PRINTED_NODES ++;
+  }
+
+  /**
+   * resets the counter for the nodes
+   */
+  protected static void resetID() {
+    PRINTED_NODES = 0;
+  }
+
+  /**
+   * Returns the tree as if-then statements.
+   *
+   * @param className the name for the generated class
+   * @return the tree as a Java if-then type statement
+   * @throws Exception if something goes wrong
+   */
+  public String toSource(String className) 
+    throws Exception {
+     
+    if (m_Tree == null) {
+      throw new Exception("REPTree: No model built yet.");
+    } 
+    StringBuffer [] source = m_Tree.toSource(className, m_Tree);
+    return
+    "class " + className + " {\n\n"
+    +"  public static double classify(Object [] i)\n"
+    +"    throws Exception {\n\n"
+    +"    double p = Double.NaN;\n"
+    + source[0]  // Assignment code
+    +"    return p;\n"
+    +"  }\n"
+    + source[1]  // Support code
+    +"}\n";
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Outputs the decision tree as a graph
+   * 
+   * @return the tree as a graph
+   * @throws Exception if generation fails
+   */
+  public String graph() throws Exception {
+
+    if (m_Tree == null) {
+      throw new Exception("REPTree: No model built yet.");
+    } 
+    StringBuffer resultBuff = new StringBuffer();
+    m_Tree.toGraph(resultBuff, 0, null);
+    String result = "digraph Tree {\n" + "edge [style=bold]\n" + resultBuff.toString()
+      + "\n}\n";
+    return result;
+  }
+  
+  /**
+   * Outputs the decision tree.
+   * 
+   * @return a string representation of the classifier 
+   */
+  public String toString() {
+
+    if (m_zeroR != null) {
+      return "No attributes other than class. Using ZeroR.\n\n" + m_zeroR.toString();
+    }
+    if ((m_Tree == null)) {
+      return "REPTree: No model built yet.";
+    } 
+    return     
+      "\nREPTree\n============\n" + m_Tree.toString(0, null) + "\n" +
+      "\nSize of the tree : " + numNodes();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for this class.
+   * 
+   * @param argv the commandline options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new REPTree(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomForest.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomForest.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomForest.java	(revision 29)
@@ -0,0 +1,538 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomForest.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.meta.Bagging;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for constructing a forest of random trees.<br/>
+ * <br/>
+ * For more information see: <br/>
+ * <br/>
+ * Leo Breiman (2001). Random Forests. Machine Learning. 45(1):5-32.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Breiman2001,
+ *    author = {Leo Breiman},
+ *    journal = {Machine Learning},
+ *    number = {1},
+ *    pages = {5-32},
+ *    title = {Random Forests},
+ *    volume = {45},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;number of trees&gt;
+ *  Number of trees to build.</pre>
+ * 
+ * <pre> -K &lt;number of features&gt;
+ *  Number of features to consider (&lt;1=int(logM+1)).</pre>
+ * 
+ * <pre> -S
+ *  Seed for random number generator.
+ *  (default 1)</pre>
+ * 
+ * <pre> -depth &lt;num&gt;
+ *  The maximum depth of the trees, 0 for unlimited.
+ *  (default 0)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RandomForest 
+  extends AbstractClassifier 
+  implements OptionHandler, Randomizable, WeightedInstancesHandler, 
+             AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4216839470751428698L;
+  
+  /** Number of trees in forest. */
+  protected int m_numTrees = 10;
+
+  /** Number of features to consider in random feature selection.
+      If less than 1 will use int(logM+1) ) */
+  protected int m_numFeatures = 0;
+
+  /** The random seed. */
+  protected int m_randomSeed = 1;  
+
+  /** Final number of features that were considered in last build. */
+  protected int m_KValue = 0;
+
+  /** The bagger. */
+  protected Bagging m_bagger = null;
+  
+  /** The maximum depth of the trees (0 = unlimited) */
+  protected int m_MaxDepth = 0;
+
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  
+        "Class for constructing a forest of random trees.\n\n"
+      + "For more information see: \n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Leo Breiman");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.TITLE, "Random Forests");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "45");
+    result.setValue(Field.NUMBER, "1");
+    result.setValue(Field.PAGES, "5-32");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numTreesTipText() {
+    return "The number of trees to be generated.";
+  }
+
+  /**
+   * Get the value of numTrees.
+   *
+   * @return Value of numTrees.
+   */
+  public int getNumTrees() {
+    
+    return m_numTrees;
+  }
+  
+  /**
+   * Set the value of numTrees.
+   *
+   * @param newNumTrees Value to assign to numTrees.
+   */
+  public void setNumTrees(int newNumTrees) {
+    
+    m_numTrees = newNumTrees;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFeaturesTipText() {
+    return "The number of attributes to be used in random selection (see RandomTree).";
+  }
+
+  /**
+   * Get the number of features used in random selection.
+   *
+   * @return Value of numFeatures.
+   */
+  public int getNumFeatures() {
+    
+    return m_numFeatures;
+  }
+  
+  /**
+   * Set the number of features to use in random selection.
+   *
+   * @param newNumFeatures Value to assign to numFeatures.
+   */
+  public void setNumFeatures(int newNumFeatures) {
+    
+    m_numFeatures = newNumFeatures;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed 
+   */
+  public void setSeed(int seed) {
+
+    m_randomSeed = seed;
+  }
+  
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_randomSeed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxDepthTipText() {
+    return "The maximum depth of the trees, 0 for unlimited.";
+  }
+
+  /**
+   * Get the maximum depth of trh tree, 0 for unlimited.
+   *
+   * @return 		the maximum depth.
+   */
+  public int getMaxDepth() {
+    return m_MaxDepth;
+  }
+  
+  /**
+   * Set the maximum depth of the tree, 0 for unlimited.
+   *
+   * @param value 	the maximum depth.
+   */
+  public void setMaxDepth(int value) {
+    m_MaxDepth = value;
+  }
+
+  /**
+   * Gets the out of bag error that was calculated as the classifier was built.
+   *
+   * @return the out of bag error
+   */
+  public double measureOutOfBagError() {
+    
+    if (m_bagger != null) {
+      return m_bagger.measureOutOfBagError();
+    } else return Double.NaN;
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names.
+   *
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureOutOfBagError");
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   *
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    
+    if (additionalMeasureName.equalsIgnoreCase("measureOutOfBagError")) {
+      return measureOutOfBagError();
+    }
+    else {throw new IllegalArgumentException(additionalMeasureName 
+					     + " not supported (RandomForest)");
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+	"\tNumber of trees to build.",
+	"I", 1, "-I <number of trees>"));
+    
+    newVector.addElement(new Option(
+	"\tNumber of features to consider (<1=int(logM+1)).",
+	"K", 1, "-K <number of features>"));
+    
+    newVector.addElement(new Option(
+	"\tSeed for random number generator.\n"
+	+ "\t(default 1)",
+	"S", 1, "-S"));
+
+    newVector.addElement(new Option(
+	"\tThe maximum depth of the trees, 0 for unlimited.\n"
+	+ "\t(default 0)",
+	"depth", 1, "-depth <num>"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Gets the current settings of the forest.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+    
+    result.add("-I");
+    result.add("" + getNumTrees());
+    
+    result.add("-K");
+    result.add("" + getNumFeatures());
+    
+    result.add("-S");
+    result.add("" + getSeed());
+    
+    if (getMaxDepth() > 0) {
+      result.add("-depth");
+      result.add("" + getMaxDepth());
+    }
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I &lt;number of trees&gt;
+   *  Number of trees to build.</pre>
+   * 
+   * <pre> -K &lt;number of features&gt;
+   *  Number of features to consider (&lt;1=int(logM+1)).</pre>
+   * 
+   * <pre> -S
+   *  Seed for random number generator.
+   *  (default 1)</pre>
+   * 
+   * <pre> -depth &lt;num&gt;
+   *  The maximum depth of the trees, 0 for unlimited.
+   *  (default 0)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception{
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('I', options);
+    if (tmpStr.length() != 0) {
+      m_numTrees = Integer.parseInt(tmpStr);
+    } else {
+      m_numTrees = 10;
+    }
+    
+    tmpStr = Utils.getOption('K', options);
+    if (tmpStr.length() != 0) {
+      m_numFeatures = Integer.parseInt(tmpStr);
+    } else {
+      m_numFeatures = 0;
+    }
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0) {
+      setSeed(Integer.parseInt(tmpStr));
+    } else {
+      setSeed(1);
+    }
+    
+    tmpStr = Utils.getOption("depth", options);
+    if (tmpStr.length() != 0) {
+      setMaxDepth(Integer.parseInt(tmpStr));
+    } else {
+      setMaxDepth(0);
+    }
+    
+    super.setOptions(options);
+    
+    Utils.checkForRemainingOptions(options);
+  }  
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    return new RandomTree().getCapabilities();
+  }
+
+  /**
+   * Builds a classifier for a set of instances.
+   *
+   * @param data the instances to train the classifier with
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    m_bagger = new Bagging();
+    RandomTree rTree = new RandomTree();
+
+    // set up the random tree options
+    m_KValue = m_numFeatures;
+    if (m_KValue < 1) m_KValue = (int) Utils.log2(data.numAttributes())+1;
+    rTree.setKValue(m_KValue);
+    rTree.setMaxDepth(getMaxDepth());
+
+    // set up the bagger and build the forest
+    m_bagger.setClassifier(rTree);
+    m_bagger.setSeed(m_randomSeed);
+    m_bagger.setNumIterations(m_numTrees);
+    m_bagger.setCalcOutOfBag(true);
+    m_bagger.buildClassifier(data);
+  }
+
+  /**
+   * Returns the class probability distribution for an instance.
+   *
+   * @param instance the instance to be classified
+   * @return the distribution the forest generates for the instance
+   * @throws Exception if computation fails
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    return m_bagger.distributionForInstance(instance);
+  }
+
+  /**
+   * Outputs a description of this classifier.
+   *
+   * @return a string containing a description of the classifier
+   */
+  public String toString() {
+
+    if (m_bagger == null) 
+      return "Random forest not built yet";
+    else 
+      return "Random forest of " + m_numTrees
+	   + " trees, each constructed while considering "
+	   + m_KValue + " random feature" + (m_KValue==1 ? "" : "s") + ".\n"
+	   + "Out of bag error: "
+	   + Utils.doubleToString(m_bagger.measureOutOfBagError(), 4) + "\n"
+	   + (getMaxDepth() > 0 ? ("Max. depth of trees: " + getMaxDepth() + "\n") : (""))
+	   + "\n";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String[] argv) {
+    runClassifier(new RandomForest(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/RandomTree.java	(revision 29)
@@ -0,0 +1,1447 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomTree.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * <!-- globalinfo-start -->
+ * Class for constructing a tree that considers K randomly  chosen attributes at each node. Performs no pruning. Also has an option to allow estimation of class probabilities based on a hold-out set (backfitting).
+ * <p/>
+ * <!-- globalinfo-end -->
+ * 
+ * <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -K &lt;number of attributes&gt;
+ *  Number of attributes to randomly investigate
+ *  (&lt;0 = int(log_2(#attributes)+1)).</pre>
+ * 
+ * <pre> -M &lt;minimum number of instances&gt;
+ *  Set minimum number of instances per leaf.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Seed for random number generator.
+ *  (default 1)</pre>
+ * 
+ * <pre> -depth &lt;num&gt;
+ *  The maximum depth of the tree, 0 for unlimited.
+ *  (default 0)</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Number of folds for backfitting (default 0, no backfitting).</pre>
+ * 
+ * <pre> -U
+ *  Allow unclassified instances.</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RandomTree extends AbstractClassifier implements OptionHandler,
+WeightedInstancesHandler, Randomizable, Drawable {
+
+  /** for serialization */
+  static final long serialVersionUID = 8934314652175299374L;
+
+  /** The subtrees appended to this tree. */
+  protected RandomTree[] m_Successors;
+
+  /** The attribute to split on. */
+  protected int m_Attribute = -1;
+
+  /** The split point. */
+  protected double m_SplitPoint = Double.NaN;
+
+  /** The header information. */
+  protected Instances m_Info = null;
+
+  /** The proportions of training instances going down each branch. */
+  protected double[] m_Prop = null;
+
+  /** Class probabilities from the training data. */
+  protected double[] m_ClassDistribution = null;
+
+  /** Minimum number of instances for leaf. */
+  protected double m_MinNum = 1.0;
+
+  /** The number of attributes considered for a split. */
+  protected int m_KValue = 0;
+
+  /** The random seed to use. */
+  protected int m_randomSeed = 1;
+
+  /** The maximum depth of the tree (0 = unlimited) */
+  protected int m_MaxDepth = 0;
+
+  /** Determines how much data is used for backfitting */
+  protected int m_NumFolds = 0;
+
+  /** Whether unclassified instances are allowed */
+  protected boolean m_AllowUnclassifiedInstances = false;
+
+  /** a ZeroR model in case no model can be built from the data */
+  protected Classifier m_ZeroR;
+
+  /**
+   * Returns a string describing classifier
+   * 
+   * @return a description suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Class for constructing a tree that considers K randomly "
+    + " chosen attributes at each node. Performs no pruning. Also has"
+    + " an option to allow estimation of class probabilities based on"
+    + " a hold-out set (backfitting).";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String minNumTipText() {
+    return "The minimum total weight of the instances in a leaf.";
+  }
+
+  /**
+   * Get the value of MinNum.
+   * 
+   * @return Value of MinNum.
+   */
+  public double getMinNum() {
+
+    return m_MinNum;
+  }
+
+  /**
+   * Set the value of MinNum.
+   * 
+   * @param newMinNum
+   *            Value to assign to MinNum.
+   */
+  public void setMinNum(double newMinNum) {
+
+    m_MinNum = newMinNum;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String KValueTipText() {
+    return "Sets the number of randomly chosen attributes. If 0, log_2(number_of_attributes) + 1 is used.";
+  }
+
+  /**
+   * Get the value of K.
+   * 
+   * @return Value of K.
+   */
+  public int getKValue() {
+
+    return m_KValue;
+  }
+
+  /**
+   * Set the value of K.
+   * 
+   * @param k
+   *            Value to assign to K.
+   */
+  public void setKValue(int k) {
+
+    m_KValue = k;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed used for selecting attributes.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   * 
+   * @param seed
+   *            the seed
+   */
+  public void setSeed(int seed) {
+
+    m_randomSeed = seed;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   * 
+   * @return the seed for the random number generation
+   */
+  public int getSeed() {
+
+    return m_randomSeed;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for displaying in the
+   *         explorer/experimenter gui
+   */
+  public String maxDepthTipText() {
+    return "The maximum depth of the tree, 0 for unlimited.";
+  }
+
+  /**
+   * Get the maximum depth of trh tree, 0 for unlimited.
+   * 
+   * @return the maximum depth.
+   */
+  public int getMaxDepth() {
+    return m_MaxDepth;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Determines the amount of data used for backfitting. One fold is used for "
+      + "backfitting, the rest for growing the tree. (Default: 0, no backfitting)";
+  }
+  
+  /**
+   * Get the value of NumFolds.
+   *
+   * @return Value of NumFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_NumFolds;
+  }
+  
+  /**
+   * Set the value of NumFolds.
+   *
+   * @param newNumFolds Value to assign to NumFolds.
+   */
+  public void setNumFolds(int newNumFolds) {
+    
+    m_NumFolds = newNumFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String allowUnclassifiedInstancesTipText() {
+    return "Whether to allow unclassified instances.";
+  }
+  
+  /**
+   * Get the value of NumFolds.
+   *
+   * @return Value of NumFolds.
+   */
+  public boolean getAllowUnclassifiedInstances() {
+    
+    return m_AllowUnclassifiedInstances;
+  }
+  
+  /**
+   * Set the value of AllowUnclassifiedInstances.
+   *
+   * @param newAllowUnclassifiedInstances Value to assign to AllowUnclassifiedInstances.
+   */
+  public void setAllowUnclassifiedInstances(boolean newAllowUnclassifiedInstances) {
+    
+    m_AllowUnclassifiedInstances = newAllowUnclassifiedInstances;
+  }
+
+  /**
+   * Set the maximum depth of the tree, 0 for unlimited.
+   * 
+   * @param value
+   *            the maximum depth.
+   */
+  public void setMaxDepth(int value) {
+    m_MaxDepth = value;
+  }
+
+  /**
+   * Lists the command-line options for this classifier.
+   * 
+   * @return an enumeration over all possible options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+        "\tNumber of attributes to randomly investigate\n"
+        + "\t(<0 = int(log_2(#attributes)+1)).", "K", 1,
+    "-K <number of attributes>"));
+
+    newVector.addElement(new Option(
+        "\tSet minimum number of instances per leaf.", "M", 1,
+    "-M <minimum number of instances>"));
+
+    newVector.addElement(new Option("\tSeed for random number generator.\n"
+        + "\t(default 1)", "S", 1, "-S <num>"));
+
+    newVector.addElement(new Option(
+        "\tThe maximum depth of the tree, 0 for unlimited.\n"
+        + "\t(default 0)", "depth", 1, "-depth <num>"));
+
+    newVector.
+      addElement(new Option("\tNumber of folds for backfitting " +
+			    "(default 0, no backfitting).",
+                            "N", 1, "-N <num>"));
+    newVector.
+      addElement(new Option("\tAllow unclassified instances.",
+			    "U", 0, "-U"));
+
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    return newVector.elements();
+  }
+
+  /**
+   * Gets options from this classifier.
+   * 
+   * @return the options for the current setup
+   */
+  public String[] getOptions() {
+    Vector result;
+    String[] options;
+    int i;
+
+    result = new Vector();
+
+    result.add("-K");
+    result.add("" + getKValue());
+
+    result.add("-M");
+    result.add("" + getMinNum());
+
+    result.add("-S");
+    result.add("" + getSeed());
+
+    if (getMaxDepth() > 0) {
+      result.add("-depth");
+      result.add("" + getMaxDepth());
+    }
+
+    if (getNumFolds() > 0) {
+      result.add("-N"); 
+      result.add("" + getNumFolds());
+    }
+
+    if (getAllowUnclassifiedInstances()) {
+      result.add("-U");
+    }
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Parses a given list of options.
+   * <p/>
+   * 
+   * <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -K &lt;number of attributes&gt;
+   *  Number of attributes to randomly investigate
+   *  (&lt;0 = int(log_2(#attributes)+1)).</pre>
+   * 
+   * <pre> -M &lt;minimum number of instances&gt;
+   *  Set minimum number of instances per leaf.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Seed for random number generator.
+   *  (default 1)</pre>
+   * 
+   * <pre> -depth &lt;num&gt;
+   *  The maximum depth of the tree, 0 for unlimited.
+   *  (default 0)</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Number of folds for backfitting (default 0, no backfitting).</pre>
+   * 
+   * <pre> -U
+   *  Allow unclassified instances.</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <!-- options-end -->
+   * 
+   * @param options
+   *            the list of options as an array of strings
+   * @throws Exception
+   *             if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String tmpStr;
+
+    tmpStr = Utils.getOption('K', options);
+    if (tmpStr.length() != 0) {
+      m_KValue = Integer.parseInt(tmpStr);
+    } else {
+      m_KValue = 0;
+    }
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0) {
+      m_MinNum = Double.parseDouble(tmpStr);
+    } else {
+      m_MinNum = 1;
+    }
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0) {
+      setSeed(Integer.parseInt(tmpStr));
+    } else {
+      setSeed(1);
+    }
+
+    tmpStr = Utils.getOption("depth", options);
+    if (tmpStr.length() != 0) {
+      setMaxDepth(Integer.parseInt(tmpStr));
+    } else {
+      setMaxDepth(0);
+    }
+    String numFoldsString = Utils.getOption('N', options);
+    if (numFoldsString.length() != 0) {
+      m_NumFolds = Integer.parseInt(numFoldsString);
+    } else {
+      m_NumFolds = 0;
+    }
+
+    setAllowUnclassifiedInstances(Utils.getFlag('U', options));
+
+    super.setOptions(options);
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   * 
+   * @return the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Builds classifier.
+   * 
+   * @param data
+   *            the data to train with
+   * @throws Exception
+   *             if something goes wrong or the data doesn't fit
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // Make sure K value is in range
+    if (m_KValue > data.numAttributes() - 1)
+      m_KValue = data.numAttributes() - 1;
+    if (m_KValue < 1)
+      m_KValue = (int) Utils.log2(data.numAttributes()) + 1;
+
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    // only class? -> build ZeroR model
+    if (data.numAttributes() == 1) {
+      System.err
+      .println("Cannot build model (only class attribute present in data!), "
+          + "using ZeroR model instead!");
+      m_ZeroR = new weka.classifiers.rules.ZeroR();
+      m_ZeroR.buildClassifier(data);
+      return;
+    } else {
+      m_ZeroR = null;
+    }
+
+    // Figure out appropriate datasets
+    Instances train = null;
+    Instances backfit = null;
+    Random rand = data.getRandomNumberGenerator(m_randomSeed);
+    if (m_NumFolds <= 0) {
+      train = data;
+    } else {
+      data.randomize(rand);
+      data.stratify(m_NumFolds);
+      train = data.trainCV(m_NumFolds, 1, rand);
+      backfit = data.testCV(m_NumFolds, 1);
+    }
+
+    // Create the attribute indices window
+    int[] attIndicesWindow = new int[data.numAttributes() - 1];
+    int j = 0;
+    for (int i = 0; i < attIndicesWindow.length; i++) {
+      if (j == data.classIndex())
+        j++; // do not include the class
+      attIndicesWindow[i] = j++;
+    }
+
+    // Compute initial class counts
+    double[] classProbs = new double[train.numClasses()];
+    for (int i = 0; i < train.numInstances(); i++) {
+      Instance inst = train.instance(i);
+      classProbs[(int) inst.classValue()] += inst.weight();
+    }
+
+    // Build tree 
+    buildTree(train, classProbs, new Instances(data, 0), m_MinNum, m_Debug, attIndicesWindow, 
+              rand, 0, getAllowUnclassifiedInstances());
+      
+    // Backfit if required
+    if (backfit != null) {
+      backfitData(backfit);
+    }
+  }
+
+  /**
+   * Backfits the given data into the tree.
+   */
+  public void backfitData(Instances data) throws Exception {
+
+    // Compute initial class counts
+    double[] classProbs = new double[data.numClasses()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance inst = data.instance(i);
+      classProbs[(int) inst.classValue()] += inst.weight();
+    }
+
+    // Fit data into tree
+    backfitData(data, classProbs);
+  }
+
+  /**
+   * Computes class distribution of an instance using the decision tree.
+   * 
+   * @param instance
+   *            the instance to compute the distribution for
+   * @return the computed class distribution
+   * @throws Exception
+   *             if computation fails
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+
+    // default model?
+    if (m_ZeroR != null) {
+      return m_ZeroR.distributionForInstance(instance);
+    }
+
+    double[] returnedDist = null;
+
+    if (m_Attribute > -1) {
+
+      // Node is not a leaf
+      if (instance.isMissing(m_Attribute)) {
+
+        // Value is missing
+        returnedDist = new double[m_Info.numClasses()];
+
+        // Split instance up
+        for (int i = 0; i < m_Successors.length; i++) {
+          double[] help = m_Successors[i]
+                                       .distributionForInstance(instance);
+          if (help != null) {
+            for (int j = 0; j < help.length; j++) {
+              returnedDist[j] += m_Prop[i] * help[j];
+            }
+          }
+        }
+      } else if (m_Info.attribute(m_Attribute).isNominal()) {
+
+        // For nominal attributes
+        returnedDist = m_Successors[(int) instance.value(m_Attribute)]
+                                    .distributionForInstance(instance);
+      } else {
+
+        // For numeric attributes
+        if (instance.value(m_Attribute) < m_SplitPoint) {
+          returnedDist = m_Successors[0]
+                                      .distributionForInstance(instance);
+        } else {
+          returnedDist = m_Successors[1]
+                                      .distributionForInstance(instance);
+        }
+      }
+    }
+
+
+    // Node is a leaf or successor is empty?
+    if ((m_Attribute == -1) || (returnedDist == null)) {
+ 
+      // Is node empty?
+      if (m_ClassDistribution == null) {
+        if (getAllowUnclassifiedInstances()) {
+          return new double[m_Info.numClasses()];
+        } else {
+          return null;
+        }
+      }
+
+      // Else return normalized distribution
+      double[] normalizedDistribution = (double[]) m_ClassDistribution.clone();
+      Utils.normalize(normalizedDistribution);
+      return normalizedDistribution;
+    } else {
+      return returnedDist;
+    }
+  }
+
+  /**
+   * Outputs the decision tree as a graph
+   * 
+   * @return the tree as a graph
+   */
+  public String toGraph() {
+
+    try {
+      StringBuffer resultBuff = new StringBuffer();
+      toGraph(resultBuff, 0);
+      String result = "digraph Tree {\n" + "edge [style=bold]\n"
+      + resultBuff.toString() + "\n}\n";
+      return result;
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  /**
+   * Outputs one node for graph.
+   * 
+   * @param text
+   *            the buffer to append the output to
+   * @param num
+   *            unique node id
+   * @return the next node id
+   * @throws Exception
+   *             if generation fails
+   */
+  public int toGraph(StringBuffer text, int num) throws Exception {
+
+    int maxIndex = Utils.maxIndex(m_ClassDistribution);
+    String classValue = m_Info.classAttribute().value(maxIndex);
+
+    num++;
+    if (m_Attribute == -1) {
+      text.append("N" + Integer.toHexString(hashCode()) + " [label=\""
+          + num + ": " + classValue + "\"" + "shape=box]\n");
+    } else {
+      text.append("N" + Integer.toHexString(hashCode()) + " [label=\""
+          + num + ": " + classValue + "\"]\n");
+      for (int i = 0; i < m_Successors.length; i++) {
+        text.append("N" + Integer.toHexString(hashCode()) + "->" + "N"
+            + Integer.toHexString(m_Successors[i].hashCode())
+            + " [label=\"" + m_Info.attribute(m_Attribute).name());
+        if (m_Info.attribute(m_Attribute).isNumeric()) {
+          if (i == 0) {
+            text.append(" < "
+                + Utils.doubleToString(m_SplitPoint, 2));
+          } else {
+            text.append(" >= "
+                + Utils.doubleToString(m_SplitPoint, 2));
+          }
+        } else {
+          text.append(" = " + m_Info.attribute(m_Attribute).value(i));
+        }
+        text.append("\"]\n");
+        num = m_Successors[i].toGraph(text, num);
+      }
+    }
+
+    return num;
+  }
+
+  /**
+   * Outputs the decision tree.
+   * 
+   * @return a string representation of the classifier
+   */
+  public String toString() {
+
+    // only ZeroR model?
+    if (m_ZeroR != null) {
+      StringBuffer buf = new StringBuffer();
+      buf
+      .append(this.getClass().getName().replaceAll(".*\\.", "")
+          + "\n");
+      buf.append(this.getClass().getName().replaceAll(".*\\.", "")
+          .replaceAll(".", "=")
+          + "\n\n");
+      buf
+      .append("Warning: No model could be built, hence ZeroR model is used:\n\n");
+      buf.append(m_ZeroR.toString());
+      return buf.toString();
+    }
+
+    if (m_Successors == null) {
+      return "RandomTree: no model has been built yet.";
+    } else {
+      return "\nRandomTree\n==========\n"
+      + toString(0)
+      + "\n"
+      + "\nSize of the tree : "
+      + numNodes()
+      + (getMaxDepth() > 0 ? ("\nMax depth of tree: " + getMaxDepth())
+          : (""));
+    }
+  }
+
+  /**
+   * Outputs a leaf.
+   * 
+   * @return the leaf as string
+   * @throws Exception
+   *             if generation fails
+   */
+  protected String leafString() throws Exception {
+
+    double sum = 0, maxCount = 0;
+    int maxIndex = 0;
+    if (m_ClassDistribution != null) {
+      sum = Utils.sum(m_ClassDistribution);
+      maxIndex = Utils.maxIndex(m_ClassDistribution);
+      maxCount = m_ClassDistribution[maxIndex];
+    } 
+    return " : "
+    + m_Info.classAttribute().value(maxIndex)
+    + " ("
+    + Utils.doubleToString(sum, 2)
+    + "/"
+    + Utils.doubleToString(sum - maxCount, 2) + ")";
+  }
+
+  /**
+   * Recursively outputs the tree.
+   * 
+   * @param level
+   *            the current level of the tree
+   * @return the generated subtree
+   */
+  protected String toString(int level) {
+
+    try {
+      StringBuffer text = new StringBuffer();
+
+      if (m_Attribute == -1) {
+
+        // Output leaf info
+        return leafString();
+      } else if (m_Info.attribute(m_Attribute).isNominal()) {
+
+        // For nominal attributes
+        for (int i = 0; i < m_Successors.length; i++) {
+          text.append("\n");
+          for (int j = 0; j < level; j++) {
+            text.append("|   ");
+          }
+          text.append(m_Info.attribute(m_Attribute).name() + " = "
+              + m_Info.attribute(m_Attribute).value(i));
+          text.append(m_Successors[i].toString(level + 1));
+        }
+      } else {
+
+        // For numeric attributes
+        text.append("\n");
+        for (int j = 0; j < level; j++) {
+          text.append("|   ");
+        }
+        text.append(m_Info.attribute(m_Attribute).name() + " < "
+            + Utils.doubleToString(m_SplitPoint, 2));
+        text.append(m_Successors[0].toString(level + 1));
+        text.append("\n");
+        for (int j = 0; j < level; j++) {
+          text.append("|   ");
+        }
+        text.append(m_Info.attribute(m_Attribute).name() + " >= "
+            + Utils.doubleToString(m_SplitPoint, 2));
+        text.append(m_Successors[1].toString(level + 1));
+      }
+
+      return text.toString();
+    } catch (Exception e) {
+      e.printStackTrace();
+      return "RandomTree: tree can't be printed";
+    }
+  }
+
+  /**
+   * Recursively backfits data into the tree.
+   * 
+   * @param data
+   *            the data to work with
+   * @param classProbs
+   *            the class distribution
+   * @throws Exception
+   *             if generation fails
+   */
+  protected void backfitData(Instances data, double[] classProbs) throws Exception {
+
+    // Make leaf if there are no training instances
+    if (data.numInstances() == 0) {
+      m_Attribute = -1;
+      m_ClassDistribution = null;
+      m_Prop = null;
+      return;
+    }
+
+    // Check if node doesn't contain enough instances or is pure
+    // or maximum depth reached
+    m_ClassDistribution = (double[]) classProbs.clone();
+
+    /*    if (Utils.sum(m_ClassDistribution) < 2 * m_MinNum
+        || Utils.eq(m_ClassDistribution[Utils.maxIndex(m_ClassDistribution)], Utils
+                    .sum(m_ClassDistribution))) {
+      
+      // Make leaf
+      m_Attribute = -1;
+      m_Prop = null;
+      return;
+      }*/
+
+    // Are we at an inner node
+    if (m_Attribute > -1) {
+      
+      // Compute new weights for subsets based on backfit data
+      m_Prop = new double[m_Successors.length];
+      for (int i = 0; i < data.numInstances(); i++) {
+        Instance inst = data.instance(i);
+        if (!inst.isMissing(m_Attribute)) {
+          if (data.attribute(m_Attribute).isNominal()) {
+            m_Prop[(int)inst.value(m_Attribute)] += inst.weight();
+          } else {
+            m_Prop[(inst.value(m_Attribute) < m_SplitPoint) ? 0 : 1] += inst.weight();
+          }
+        }
+      }
+
+      // If we only have missing values we can make this node into a leaf
+      if (Utils.sum(m_Prop) <= 0) {
+        m_Attribute = -1;
+        m_Prop = null;
+        return;
+      }
+
+      // Otherwise normalize the proportions
+      Utils.normalize(m_Prop);
+
+      // Split data
+      Instances[] subsets = splitData(data);
+      
+      // Go through subsets
+      for (int i = 0; i < subsets.length; i++) {
+        
+        // Compute distribution for current subset
+        double[] dist = new double[data.numClasses()];
+        for (int j = 0; j < subsets[i].numInstances(); j++) {
+          dist[(int)subsets[i].instance(j).classValue()] += subsets[i].instance(j).weight();
+        }
+        
+        // Backfit subset
+        m_Successors[i].backfitData(subsets[i], dist);
+      }
+
+      // If unclassified instances are allowed, we don't need to store the class distribution
+      if (getAllowUnclassifiedInstances()) {
+        m_ClassDistribution = null;
+        return;
+      }
+
+      // Otherwise, if all successors are non-empty, we don't need to store the class distribution
+      boolean emptySuccessor = false;
+      for (int i = 0; i < subsets.length; i++) {
+        if (m_Successors[i].m_ClassDistribution == null) {
+          emptySuccessor = true;
+          return;
+        }
+      }
+      m_ClassDistribution = null;
+      
+      // If we have a least two non-empty successors, we should keep this tree
+      /*      int nonEmptySuccessors = 0;
+      for (int i = 0; i < subsets.length; i++) {
+        if (m_Successors[i].m_ClassDistribution != null) {
+          nonEmptySuccessors++;
+          if (nonEmptySuccessors > 1) {
+            return;
+          }
+        }
+      }
+      
+      // Otherwise, this node is a leaf or should become a leaf
+      m_Successors = null;
+      m_Attribute = -1;
+      m_Prop = null;
+      return;*/
+    }
+  }
+
+  /**
+   * Recursively generates a tree.
+   * 
+   * @param data
+   *            the data to work with
+   * @param classProbs
+   *            the class distribution
+   * @param header
+   *            the header of the data
+   * @param minNum
+   *            the minimum number of instances per leaf
+   * @param debug
+   *            whether debugging is on
+   * @param attIndicesWindow
+   *            the attribute window to choose attributes from
+   * @param random
+   *            random number generator for choosing random attributes
+   * @param depth
+   *            the current depth
+   * @param determineStructure
+   *            whether to determine structure
+   * @throws Exception
+   *             if generation fails
+   */
+  protected void buildTree(Instances data, double[] classProbs, Instances header,
+                           double minNum, boolean debug, int[] attIndicesWindow,
+                           Random random, int depth, boolean allow) throws Exception {
+
+    // Store structure of dataset, set minimum number of instances
+    m_Info = header;
+    m_Debug = debug;
+    m_MinNum = minNum;
+    m_AllowUnclassifiedInstances = allow;
+
+    // Make leaf if there are no training instances
+    if (data.numInstances() == 0) {
+      m_Attribute = -1;
+      m_ClassDistribution = null;
+      m_Prop = null;
+      return;
+    }
+
+    // Check if node doesn't contain enough instances or is pure
+    // or maximum depth reached
+    m_ClassDistribution = (double[]) classProbs.clone();
+
+    if (Utils.sum(m_ClassDistribution) < 2 * m_MinNum
+        || Utils.eq(m_ClassDistribution[Utils.maxIndex(m_ClassDistribution)], Utils
+            .sum(m_ClassDistribution))
+            || ((getMaxDepth() > 0) && (depth >= getMaxDepth()))) {
+      // Make leaf
+      m_Attribute = -1;
+      m_Prop = null;
+      return;
+    }
+
+    // Compute class distributions and value of splitting
+    // criterion for each attribute
+    double[] vals = new double[data.numAttributes()];
+    double[][][] dists = new double[data.numAttributes()][0][0];
+    double[][] props = new double[data.numAttributes()][0];
+    double[] splits = new double[data.numAttributes()];
+    
+    // Investigate K random attributes
+    int attIndex = 0;
+    int windowSize = attIndicesWindow.length;
+    int k = m_KValue;
+    boolean gainFound = false;
+    while ((windowSize > 0) && (k-- > 0 || !gainFound)) {
+      
+      int chosenIndex = random.nextInt(windowSize);
+      attIndex = attIndicesWindow[chosenIndex];
+      
+      // shift chosen attIndex out of window
+      attIndicesWindow[chosenIndex] = attIndicesWindow[windowSize - 1];
+      attIndicesWindow[windowSize - 1] = attIndex;
+      windowSize--;
+      
+      splits[attIndex] = distribution(props, dists, attIndex, data);
+      vals[attIndex] = gain(dists[attIndex], priorVal(dists[attIndex]));
+      
+      if (Utils.gr(vals[attIndex], 0))
+        gainFound = true;
+    }
+      
+    // Find best attribute
+    m_Attribute = Utils.maxIndex(vals);
+    double[][] distribution = dists[m_Attribute];
+
+    // Any useful split found? 
+    if (Utils.gr(vals[m_Attribute], 0)) {
+
+      // Build subtrees
+      m_SplitPoint = splits[m_Attribute];
+      m_Prop = props[m_Attribute];
+      Instances[] subsets = splitData(data);
+      m_Successors = new RandomTree[distribution.length];
+      for (int i = 0; i < distribution.length; i++) {
+        m_Successors[i] = new RandomTree();
+        m_Successors[i].setKValue(m_KValue);
+        m_Successors[i].setMaxDepth(getMaxDepth());
+        m_Successors[i].buildTree(subsets[i], distribution[i], header, m_MinNum, m_Debug,
+                                  attIndicesWindow, random, depth + 1, allow);
+      }
+
+      // If all successors are non-empty, we don't need to store the class distribution
+      boolean emptySuccessor = false;
+      for (int i = 0; i < subsets.length; i++) {
+        if (m_Successors[i].m_ClassDistribution == null) {
+          emptySuccessor = true;
+          break;
+        }
+      }
+      if (!emptySuccessor) {
+        m_ClassDistribution = null;
+      }
+    } else {
+
+      // Make leaf
+      m_Attribute = -1;
+    }
+  }
+
+  /**
+   * Computes size of the tree.
+   * 
+   * @return the number of nodes
+   */
+  public int numNodes() {
+
+    if (m_Attribute == -1) {
+      return 1;
+    } else {
+      int size = 1;
+      for (int i = 0; i < m_Successors.length; i++) {
+        size += m_Successors[i].numNodes();
+      }
+      return size;
+    }
+  }
+
+  /**
+   * Splits instances into subsets based on the given split.
+   * 
+   * @param data
+   *            the data to work with
+   * @return  the subsets of instances
+   * @throws Exception
+   *             if something goes wrong
+   */
+  protected Instances[] splitData(Instances data) throws Exception {
+
+    // Allocate array of Instances objects
+    Instances[] subsets = new Instances[m_Prop.length];
+    for (int i = 0; i < m_Prop.length; i++) {
+      subsets[i] = new Instances(data, data.numInstances());
+    }
+
+    // Go through the data
+    for (int i = 0; i < data.numInstances(); i++) {
+
+      // Get instance
+      Instance inst = data.instance(i);
+
+      // Does the instance have a missing value?
+      if (inst.isMissing(m_Attribute)) {
+        
+        // Split instance up
+        for (int k = 0; k < m_Prop.length; k++) {
+          if (m_Prop[k] > 0) {
+            Instance copy = (Instance)inst.copy();
+            copy.setWeight(m_Prop[k] * inst.weight());
+            subsets[k].add(copy);
+          }
+        }
+
+        // Proceed to next instance
+        continue;
+      }
+
+      // Do we have a nominal attribute?
+      if (data.attribute(m_Attribute).isNominal()) {
+        subsets[(int)inst.value(m_Attribute)].add(inst);
+
+        // Proceed to next instance
+        continue;
+      }
+
+      // Do we have a numeric attribute?
+      if (data.attribute(m_Attribute).isNumeric()) {
+        subsets[(inst.value(m_Attribute) < m_SplitPoint) ? 0 : 1].add(inst);
+
+        // Proceed to next instance
+        continue;
+      }
+      
+      // Else throw an exception
+      throw new IllegalArgumentException("Unknown attribute type");
+    }
+
+    // Save memory
+    for (int i = 0; i < m_Prop.length; i++) {
+      subsets[i].compactify();
+    }
+
+    // Return the subsets
+    return subsets;
+  }
+
+  /**
+   * Computes class distribution for an attribute.
+   * 
+   * @param props
+   * @param dists
+   * @param att
+   *            the attribute index
+   * @param data
+   *            the data to work with
+   * @throws Exception
+   *             if something goes wrong
+   */
+  protected double distribution(double[][] props, double[][][] dists, int att, Instances data)
+  throws Exception {
+
+    double splitPoint = Double.NaN;
+    Attribute attribute = data.attribute(att);
+    double[][] dist = null;
+    int indexOfFirstMissingValue = -1;
+
+    if (attribute.isNominal()) {
+
+      // For nominal attributes
+      dist = new double[attribute.numValues()][data.numClasses()];
+      for (int i = 0; i < data.numInstances(); i++) {
+        Instance inst = data.instance(i);
+        if (inst.isMissing(att)) {
+
+          // Skip missing values at this stage
+          if (indexOfFirstMissingValue < 0) {
+            indexOfFirstMissingValue = i;
+          }
+          continue;
+        }
+        dist[(int) inst.value(att)][(int) inst.classValue()] += inst.weight();
+      }
+    } else {
+
+      // For numeric attributes
+      double[][] currDist = new double[2][data.numClasses()];
+      dist = new double[2][data.numClasses()];
+
+      // Sort data
+      data.sort(att);
+
+      // Move all instances into second subset
+      for (int j = 0; j < data.numInstances(); j++) {
+        Instance inst = data.instance(j);
+        if (inst.isMissing(att)) {
+
+          // Can stop as soon as we hit a missing value
+          indexOfFirstMissingValue = j;
+          break;
+        }
+        currDist[1][(int) inst.classValue()] += inst.weight();
+      }
+
+      // Value before splitting
+      double priorVal = priorVal(currDist);
+
+      // Save initial distribution
+      for (int j = 0; j < currDist.length; j++) {
+        System.arraycopy(currDist[j], 0, dist[j], 0, dist[j].length);
+      }
+
+      // Try all possible split points
+      double currSplit = data.instance(0).value(att);
+      double currVal, bestVal = -Double.MAX_VALUE;
+      for (int i = 0; i < data.numInstances(); i++) {
+        Instance inst = data.instance(i);
+        if (inst.isMissing(att)) {
+
+          // Can stop as soon as we hit a missing value
+          break;
+        }
+
+        // Can we place a sensible split point here?
+        if (inst.value(att) > currSplit) {
+
+          // Compute gain for split point
+          currVal = gain(currDist, priorVal);
+
+          // Is the current split point the best point so far?
+          if (currVal > bestVal) {
+
+            // Store value of current point
+            bestVal = currVal;
+
+            // Save split point
+            splitPoint = (inst.value(att) + currSplit) / 2.0;
+
+            // Save distribution
+            for (int j = 0; j < currDist.length; j++) {
+              System.arraycopy(currDist[j], 0, dist[j], 0, dist[j].length);
+            }
+          }
+        }
+        currSplit = inst.value(att);
+
+        // Shift over the weight
+        currDist[0][(int) inst.classValue()] += inst.weight();
+        currDist[1][(int) inst.classValue()] -= inst.weight();
+      }
+    }
+
+    // Compute weights for subsets
+    props[att] = new double[dist.length];
+    for (int k = 0; k < props[att].length; k++) {
+      props[att][k] = Utils.sum(dist[k]);
+    }
+    if (Utils.eq(Utils.sum(props[att]), 0)) {
+      for (int k = 0; k < props[att].length; k++) {
+        props[att][k] = 1.0 / (double) props[att].length;
+      }
+    } else {
+      Utils.normalize(props[att]);
+    }
+
+    // Any instances with missing values ?
+    if (indexOfFirstMissingValue > -1) {
+
+      // Distribute weights for instances with missing values
+      for (int i = indexOfFirstMissingValue; i < data.numInstances(); i++) {
+        Instance inst = data.instance(i);
+        if (attribute.isNominal()) {
+
+          // Need to check if attribute value is missing
+          if (inst.isMissing(att)) {
+            for (int j = 0; j < dist.length; j++) {
+              dist[j][(int) inst.classValue()] += props[att][j] * inst.weight();
+            }
+          }
+        } else {
+
+          // Can be sure that value is missing, so no test required
+          for (int j = 0; j < dist.length; j++) {
+            dist[j][(int) inst.classValue()] += props[att][j] * inst.weight();
+          }
+        }
+      }
+    }
+
+    // Return distribution and split point
+    dists[att] = dist;
+    return splitPoint;
+  }
+
+  /**
+   * Computes value of splitting criterion before split.
+   * 
+   * @param dist
+   *            the distributions
+   * @return the splitting criterion
+   */
+  protected double priorVal(double[][] dist) {
+
+    return ContingencyTables.entropyOverColumns(dist);
+  }
+
+  /**
+   * Computes value of splitting criterion after split.
+   * 
+   * @param dist
+   *            the distributions
+   * @param priorVal
+   *            the splitting criterion
+   * @return the gain after the split
+   */
+  protected double gain(double[][] dist, double priorVal) {
+
+    return priorVal - ContingencyTables.entropyConditionedOnRows(dist);
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for this class.
+   * 
+   * @param argv
+   *            the commandline parameters
+   */
+  public static void main(String[] argv) {
+    runClassifier(new RandomTree(), argv);
+  }
+
+  /**
+   * Returns graph describing the tree.
+   * 
+   * @return the graph describing the tree
+   * @throws Exception
+   *             if graph can't be computed
+   */
+  public String graph() throws Exception {
+
+    if (m_Successors == null) {
+      throw new Exception("RandomTree: No model built yet.");
+    }
+    StringBuffer resultBuff = new StringBuffer();
+    toGraph(resultBuff, 0, null);
+    String result = "digraph RandomTree {\n" + "edge [style=bold]\n"
+    + resultBuff.toString() + "\n}\n";
+    return result;
+  }
+
+  /**
+   * Returns the type of graph this classifier represents.
+   * 
+   * @return Drawable.TREE
+   */
+  public int graphType() {
+    return Drawable.TREE;
+  }
+
+  /**
+   * Outputs one node for graph.
+   * 
+   * @param text
+   *            the buffer to append the output to
+   * @param num
+   *            the current node id
+   * @param parent
+   *            the parent of the nodes
+   * @return the next node id
+   * @throws Exception
+   *             if something goes wrong
+   */
+  protected int toGraph(StringBuffer text, int num, RandomTree parent)
+  throws Exception {
+
+    num++;
+    if (m_Attribute == -1) {
+      text.append("N" + Integer.toHexString(RandomTree.this.hashCode())
+          + " [label=\"" + num + leafString() + "\""
+          + " shape=box]\n");
+
+    } else {
+      text.append("N" + Integer.toHexString(RandomTree.this.hashCode())
+          + " [label=\"" + num + ": "
+          + m_Info.attribute(m_Attribute).name() + "\"]\n");
+      for (int i = 0; i < m_Successors.length; i++) {
+        text.append("N"
+            + Integer.toHexString(RandomTree.this.hashCode())
+            + "->" + "N"
+            + Integer.toHexString(m_Successors[i].hashCode())
+            + " [label=\"");
+        if (m_Info.attribute(m_Attribute).isNumeric()) {
+          if (i == 0) {
+            text.append(" < "
+                + Utils.doubleToString(m_SplitPoint, 2));
+          } else {
+            text.append(" >= "
+                + Utils.doubleToString(m_SplitPoint, 2));
+          }
+        } else {
+          text.append(" = " + m_Info.attribute(m_Attribute).value(i));
+        }
+        text.append("\"]\n");
+        num = m_Successors[i].toGraph(text, num, this);
+      }
+    }
+
+    return num;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/SimpleCart.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/SimpleCart.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/SimpleCart.java	(revision 29)
@@ -0,0 +1,2022 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleCart.java
+ * Copyright (C) 2007 Haijian Shi
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Evaluation;
+import weka.classifiers.RandomizableClassifier;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.Matrix;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing minimal cost-complexity pruning.<br/>
+ * Note when dealing with missing values, use "fractional instances" method instead of surrogate split method.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Leo Breiman, Jerome H. Friedman, Richard A. Olshen, Charles J. Stone (1984). Classification and Regression Trees. Wadsworth International Group, Belmont, California.
+ * <p/>
+ <!-- globalinfo-end -->	
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Breiman1984,
+ *    address = {Belmont, California},
+ *    author = {Leo Breiman and Jerome H. Friedman and Richard A. Olshen and Charles J. Stone},
+ *    publisher = {Wadsworth International Group},
+ *    title = {Classification and Regression Trees},
+ *    year = {1984}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -M &lt;min no&gt;
+ *  The minimal number of instances at the terminal nodes.
+ *  (default 2)</pre>
+ * 
+ * <pre> -N &lt;num folds&gt;
+ *  The number of folds used in the minimal cost-complexity pruning.
+ *  (default 5)</pre>
+ * 
+ * <pre> -U
+ *  Don't use the minimal cost-complexity pruning.
+ *  (default yes).</pre>
+ * 
+ * <pre> -H
+ *  Don't use the heuristic method for binary split.
+ *  (default true).</pre>
+ * 
+ * <pre> -A
+ *  Use 1 SE rule to make pruning decision.
+ *  (default no).</pre>
+ * 
+ * <pre> -C
+ *  Percentage of training data size (0-1].
+ *  (default 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Haijian Shi (hs69@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class SimpleCart
+  extends RandomizableClassifier
+  implements AdditionalMeasureProducer, TechnicalInformationHandler {
+
+  /** For serialization.	 */
+  private static final long serialVersionUID = 4154189200352566053L;
+
+  /** Training data.  */
+  protected Instances m_train;
+
+  /** Successor nodes. */
+  protected SimpleCart[] m_Successors;
+
+  /** Attribute used to split data. */
+  protected Attribute m_Attribute;
+
+  /** Split point for a numeric attribute. */
+  protected double m_SplitValue;
+
+  /** Split subset used to split data for nominal attributes. */
+  protected String m_SplitString;
+
+  /** Class value if the node is leaf. */
+  protected double m_ClassValue;
+
+  /** Class attriubte of data. */
+  protected Attribute m_ClassAttribute;
+
+  /** Minimum number of instances in at the terminal nodes. */
+  protected double m_minNumObj = 2;
+
+  /** Number of folds for minimal cost-complexity pruning. */
+  protected int m_numFoldsPruning = 5;
+
+  /** Alpha-value (for pruning) at the node. */
+  protected double m_Alpha;
+
+  /** Number of training examples misclassified by the model (subtree rooted). */
+  protected double m_numIncorrectModel;
+
+  /** Number of training examples misclassified by the model (subtree not rooted). */
+  protected double m_numIncorrectTree;
+
+  /** Indicate if the node is a leaf node. */
+  protected boolean m_isLeaf;
+
+  /** If use minimal cost-compexity pruning. */
+  protected boolean m_Prune = true;
+
+  /** Total number of instances used to build the classifier. */
+  protected int m_totalTrainInstances;
+
+  /** Proportion for each branch. */
+  protected double[] m_Props;
+
+  /** Class probabilities. */
+  protected double[] m_ClassProbs = null;
+
+  /** Distributions of leaf node (or temporary leaf node in minimal cost-complexity pruning) */
+  protected double[] m_Distribution;
+
+  /** If use huristic search for nominal attributes in multi-class problems (default true). */
+  protected boolean m_Heuristic = true;
+
+  /** If use the 1SE rule to make final decision tree. */
+  protected boolean m_UseOneSE = false;
+
+  /** Training data size. */
+  protected double m_SizePer = 1;
+
+  /**
+   * Return a description suitable for displaying in the explorer/experimenter.
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter
+   */
+  public String globalInfo() {
+    return  
+        "Class implementing minimal cost-complexity pruning.\n"
+      + "Note when dealing with missing values, use \"fractional "
+      + "instances\" method instead of surrogate split method.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Leo Breiman and Jerome H. Friedman and Richard A. Olshen and Charles J. Stone");
+    result.setValue(Field.YEAR, "1984");
+    result.setValue(Field.TITLE, "Classification and Regression Trees");
+    result.setValue(Field.PUBLISHER, "Wadsworth International Group");
+    result.setValue(Field.ADDRESS, "Belmont, California");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   * 
+   * @return 		the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Build the classifier.
+   * 
+   * @param data 	the training instances
+   * @throws Exception 	if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    getCapabilities().testWithFail(data);
+    data = new Instances(data);        
+    data.deleteWithMissingClass();
+
+    // unpruned CART decision tree
+    if (!m_Prune) {
+
+      // calculate sorted indices and weights, and compute initial class counts.
+      int[][] sortedIndices = new int[data.numAttributes()][0];
+      double[][] weights = new double[data.numAttributes()][0];
+      double[] classProbs = new double[data.numClasses()];
+      double totalWeight = computeSortedInfo(data,sortedIndices, weights,classProbs);
+
+      makeTree(data, data.numInstances(),sortedIndices,weights,classProbs,
+	  totalWeight,m_minNumObj, m_Heuristic);
+      return;
+    }
+
+    Random random = new Random(m_Seed);
+    Instances cvData = new Instances(data);
+    cvData.randomize(random);
+    cvData = new Instances(cvData,0,(int)(cvData.numInstances()*m_SizePer)-1);
+    cvData.stratify(m_numFoldsPruning);
+
+    double[][] alphas = new double[m_numFoldsPruning][];
+    double[][] errors = new double[m_numFoldsPruning][];
+
+    // calculate errors and alphas for each fold
+    for (int i = 0; i < m_numFoldsPruning; i++) {
+
+      //for every fold, grow tree on training set and fix error on test set.
+      Instances train = cvData.trainCV(m_numFoldsPruning, i);
+      Instances test = cvData.testCV(m_numFoldsPruning, i);
+
+      // calculate sorted indices and weights, and compute initial class counts for each fold
+      int[][] sortedIndices = new int[train.numAttributes()][0];
+      double[][] weights = new double[train.numAttributes()][0];
+      double[] classProbs = new double[train.numClasses()];
+      double totalWeight = computeSortedInfo(train,sortedIndices, weights,classProbs);
+
+      makeTree(train, train.numInstances(),sortedIndices,weights,classProbs,
+	  totalWeight,m_minNumObj, m_Heuristic);
+
+      int numNodes = numInnerNodes();
+      alphas[i] = new double[numNodes + 2];
+      errors[i] = new double[numNodes + 2];
+
+      // prune back and log alpha-values and errors on test set
+      prune(alphas[i], errors[i], test);
+    }
+
+    // calculate sorted indices and weights, and compute initial class counts on all training instances
+    int[][] sortedIndices = new int[data.numAttributes()][0];
+    double[][] weights = new double[data.numAttributes()][0];
+    double[] classProbs = new double[data.numClasses()];
+    double totalWeight = computeSortedInfo(data,sortedIndices, weights,classProbs);
+
+    //build tree using all the data
+    makeTree(data, data.numInstances(),sortedIndices,weights,classProbs,
+	totalWeight,m_minNumObj, m_Heuristic);
+
+    int numNodes = numInnerNodes();
+
+    double[] treeAlphas = new double[numNodes + 2];
+
+    // prune back and log alpha-values
+    int iterations = prune(treeAlphas, null, null);
+
+    double[] treeErrors = new double[numNodes + 2];
+
+    // for each pruned subtree, find the cross-validated error
+    for (int i = 0; i <= iterations; i++){
+      //compute midpoint alphas
+      double alpha = Math.sqrt(treeAlphas[i] * treeAlphas[i+1]);
+      double error = 0;
+      for (int k = 0; k < m_numFoldsPruning; k++) {
+	int l = 0;
+	while (alphas[k][l] <= alpha) l++;
+	error += errors[k][l - 1];
+      }
+      treeErrors[i] = error/m_numFoldsPruning;
+    }
+
+    // find best alpha
+    int best = -1;
+    double bestError = Double.MAX_VALUE;
+    for (int i = iterations; i >= 0; i--) {
+      if (treeErrors[i] < bestError) {
+	bestError = treeErrors[i];
+	best = i;
+      }
+    }
+
+    // 1 SE rule to choose expansion
+    if (m_UseOneSE) {
+      double oneSE = Math.sqrt(bestError*(1-bestError)/(data.numInstances()));
+      for (int i = iterations; i >= 0; i--) {
+	if (treeErrors[i] <= bestError+oneSE) {
+	  best = i;
+	  break;
+	}
+      }
+    }
+
+    double bestAlpha = Math.sqrt(treeAlphas[best] * treeAlphas[best + 1]);
+
+    //"unprune" final tree (faster than regrowing it)
+    unprune();
+    prune(bestAlpha);        
+  }
+
+  /**
+   * Make binary decision tree recursively.
+   * 
+   * @param data 		the training instances
+   * @param totalInstances 	total number of instances
+   * @param sortedIndices 	sorted indices of the instances
+   * @param weights 		weights of the instances
+   * @param classProbs 		class probabilities
+   * @param totalWeight 	total weight of instances
+   * @param minNumObj 		minimal number of instances at leaf nodes
+   * @param useHeuristic 	if use heuristic search for nominal attributes in multi-class problem
+   * @throws Exception 		if something goes wrong
+   */
+  protected void makeTree(Instances data, int totalInstances, int[][] sortedIndices,
+      double[][] weights, double[] classProbs, double totalWeight, double minNumObj,
+      boolean useHeuristic) throws Exception{
+
+    // if no instances have reached this node (normally won't happen)
+    if (totalWeight == 0){
+      m_Attribute = null;
+      m_ClassValue = Utils.missingValue();
+      m_Distribution = new double[data.numClasses()];
+      return;
+    }
+
+    m_totalTrainInstances = totalInstances;
+    m_isLeaf = true;
+
+    m_ClassProbs = new double[classProbs.length];
+    m_Distribution = new double[classProbs.length];
+    System.arraycopy(classProbs, 0, m_ClassProbs, 0, classProbs.length);
+    System.arraycopy(classProbs, 0, m_Distribution, 0, classProbs.length);
+    if (Utils.sum(m_ClassProbs)!=0) Utils.normalize(m_ClassProbs);
+
+    // Compute class distributions and value of splitting
+    // criterion for each attribute
+    double[][][] dists = new double[data.numAttributes()][0][0];
+    double[][] props = new double[data.numAttributes()][0];
+    double[][] totalSubsetWeights = new double[data.numAttributes()][2];
+    double[] splits = new double[data.numAttributes()];
+    String[] splitString = new String[data.numAttributes()];
+    double[] giniGains = new double[data.numAttributes()];
+
+    // for each attribute find split information
+    for (int i = 0; i < data.numAttributes(); i++) {
+      Attribute att = data.attribute(i);
+      if (i==data.classIndex()) continue;
+      if (att.isNumeric()) {
+	// numeric attribute
+	splits[i] = numericDistribution(props, dists, att, sortedIndices[i],
+	    weights[i], totalSubsetWeights, giniGains, data);
+      } else {
+	// nominal attribute
+	splitString[i] = nominalDistribution(props, dists, att, sortedIndices[i],
+	    weights[i], totalSubsetWeights, giniGains, data, useHeuristic);
+      }
+    }
+
+    // Find best attribute (split with maximum Gini gain)
+    int attIndex = Utils.maxIndex(giniGains);
+    m_Attribute = data.attribute(attIndex);
+
+    m_train = new Instances(data, sortedIndices[attIndex].length);
+    for (int i=0; i<sortedIndices[attIndex].length; i++) {
+      Instance inst = data.instance(sortedIndices[attIndex][i]);
+      Instance instCopy = (Instance)inst.copy();
+      instCopy.setWeight(weights[attIndex][i]);
+      m_train.add(instCopy);
+    }
+
+    // Check if node does not contain enough instances, or if it can not be split,
+    // or if it is pure. If does, make leaf.
+    if (totalWeight < 2 * minNumObj || giniGains[attIndex]==0 ||
+	props[attIndex][0]==0 || props[attIndex][1]==0) {
+      makeLeaf(data);
+    }
+    
+    else {            
+      m_Props = props[attIndex];
+      int[][][] subsetIndices = new int[2][data.numAttributes()][0];
+      double[][][] subsetWeights = new double[2][data.numAttributes()][0];
+
+      // numeric split
+      if (m_Attribute.isNumeric()) m_SplitValue = splits[attIndex];
+
+      // nominal split
+      else m_SplitString = splitString[attIndex];
+
+      splitData(subsetIndices, subsetWeights, m_Attribute, m_SplitValue,
+	  m_SplitString, sortedIndices, weights, data);
+      
+      // If split of the node results in a node with less than minimal number of isntances, 
+      // make the node leaf node.
+      if (subsetIndices[0][attIndex].length<minNumObj ||
+	  subsetIndices[1][attIndex].length<minNumObj) {
+	makeLeaf(data);
+	return;
+      }
+
+      // Otherwise, split the node.
+      m_isLeaf = false;
+      m_Successors = new SimpleCart[2];
+      for (int i = 0; i < 2; i++) {
+	m_Successors[i] = new SimpleCart();
+	m_Successors[i].makeTree(data, m_totalTrainInstances, subsetIndices[i],
+	    subsetWeights[i],dists[attIndex][i], totalSubsetWeights[attIndex][i],
+	    minNumObj, useHeuristic);
+      }
+    }
+  }
+
+  /**
+   * Prunes the original tree using the CART pruning scheme, given a 
+   * cost-complexity parameter alpha.
+   * 
+   * @param alpha 	the cost-complexity parameter
+   * @throws Exception 	if something goes wrong
+   */
+  public void prune(double alpha) throws Exception {
+
+    Vector nodeList;
+
+    // determine training error of pruned subtrees (both with and without replacing a subtree),
+    // and calculate alpha-values from them
+    modelErrors();
+    treeErrors();
+    calculateAlphas();
+
+    // get list of all inner nodes in the tree
+    nodeList = getInnerNodes();
+
+    boolean prune = (nodeList.size() > 0);
+    double preAlpha = Double.MAX_VALUE;
+    while (prune) {
+
+      // select node with minimum alpha
+      SimpleCart nodeToPrune = nodeToPrune(nodeList);
+
+      // want to prune if its alpha is smaller than alpha
+      if (nodeToPrune.m_Alpha > alpha) {
+	break;
+      }
+
+      nodeToPrune.makeLeaf(nodeToPrune.m_train);
+
+      // normally would not happen
+      if (nodeToPrune.m_Alpha==preAlpha) {
+	nodeToPrune.makeLeaf(nodeToPrune.m_train);
+	treeErrors();
+	calculateAlphas();
+	nodeList = getInnerNodes();
+	prune = (nodeList.size() > 0);
+	continue;
+      }
+      preAlpha = nodeToPrune.m_Alpha;
+
+      //update tree errors and alphas
+      treeErrors();
+      calculateAlphas();
+
+      nodeList = getInnerNodes();
+      prune = (nodeList.size() > 0);
+    }
+  }
+
+  /**
+   * Method for performing one fold in the cross-validation of minimal 
+   * cost-complexity pruning. Generates a sequence of alpha-values with error 
+   * estimates for the corresponding (partially pruned) trees, given the test 
+   * set of that fold.
+   *
+   * @param alphas 	array to hold the generated alpha-values
+   * @param errors 	array to hold the corresponding error estimates
+   * @param test 	test set of that fold (to obtain error estimates)
+   * @return 		the iteration of the pruning
+   * @throws Exception 	if something goes wrong
+   */
+  public int prune(double[] alphas, double[] errors, Instances test) 
+    throws Exception {
+
+    Vector nodeList;
+
+    // determine training error of subtrees (both with and without replacing a subtree), 
+    // and calculate alpha-values from them
+    modelErrors();
+    treeErrors();
+    calculateAlphas();
+
+    // get list of all inner nodes in the tree
+    nodeList = getInnerNodes();
+
+    boolean prune = (nodeList.size() > 0);
+
+    //alpha_0 is always zero (unpruned tree)
+    alphas[0] = 0;
+
+    Evaluation eval;
+
+    // error of unpruned tree
+    if (errors != null) {
+      eval = new Evaluation(test);
+      eval.evaluateModel(this, test);
+      errors[0] = eval.errorRate();
+    }
+
+    int iteration = 0;
+    double preAlpha = Double.MAX_VALUE;
+    while (prune) {
+
+      iteration++;
+
+      // get node with minimum alpha
+      SimpleCart nodeToPrune = nodeToPrune(nodeList);
+
+      // do not set m_sons null, want to unprune
+      nodeToPrune.m_isLeaf = true;
+
+      // normally would not happen
+      if (nodeToPrune.m_Alpha==preAlpha) {
+	iteration--;
+	treeErrors();
+	calculateAlphas();
+	nodeList = getInnerNodes();
+	prune = (nodeList.size() > 0);
+	continue;
+      }
+
+      // get alpha-value of node
+      alphas[iteration] = nodeToPrune.m_Alpha;
+
+      // log error
+      if (errors != null) {
+	eval = new Evaluation(test);
+	eval.evaluateModel(this, test);
+	errors[iteration] = eval.errorRate();
+      }
+      preAlpha = nodeToPrune.m_Alpha;
+
+      //update errors/alphas
+      treeErrors();
+      calculateAlphas();
+
+      nodeList = getInnerNodes();
+      prune = (nodeList.size() > 0);
+    }
+
+    //set last alpha 1 to indicate end
+    alphas[iteration + 1] = 1.0;
+    return iteration;
+  }
+
+  /**
+   * Method to "unprune" the CART tree. Sets all leaf-fields to false.
+   * Faster than re-growing the tree because CART do not have to be fit again.
+   */
+  protected void unprune() {
+    if (m_Successors != null) {
+      m_isLeaf = false;
+      for (int i = 0; i < m_Successors.length; i++) m_Successors[i].unprune();
+    }
+  }
+
+  /**
+   * Compute distributions, proportions and total weights of two successor 
+   * nodes for a given numeric attribute.
+   * 
+   * @param props 		proportions of each two branches for each attribute
+   * @param dists 		class distributions of two branches for each attribute
+   * @param att 		numeric att split on
+   * @param sortedIndices 	sorted indices of instances for the attirubte
+   * @param weights 		weights of instances for the attirbute
+   * @param subsetWeights 	total weight of two branches split based on the attribute
+   * @param giniGains 		Gini gains for each attribute 
+   * @param data 		training instances
+   * @return 			Gini gain the given numeric attribute
+   * @throws Exception 		if something goes wrong
+   */
+  protected double numericDistribution(double[][] props, double[][][] dists,
+      Attribute att, int[] sortedIndices, double[] weights, double[][] subsetWeights,
+      double[] giniGains, Instances data)
+    throws Exception {
+
+    double splitPoint = Double.NaN;
+    double[][] dist = null;
+    int numClasses = data.numClasses();
+    int i; // differ instances with or without missing values
+
+    double[][] currDist = new double[2][numClasses];
+    dist = new double[2][numClasses];
+
+    // Move all instances without missing values into second subset
+    double[] parentDist = new double[numClasses];
+    int missingStart = 0;
+    for (int j = 0; j < sortedIndices.length; j++) {
+      Instance inst = data.instance(sortedIndices[j]);
+      if (!inst.isMissing(att)) {
+	missingStart ++;
+	currDist[1][(int)inst.classValue()] += weights[j];
+      }
+      parentDist[(int)inst.classValue()] += weights[j];
+    }
+    System.arraycopy(currDist[1], 0, dist[1], 0, dist[1].length);
+
+    // Try all possible split points
+    double currSplit = data.instance(sortedIndices[0]).value(att);
+    double currGiniGain;
+    double bestGiniGain = -Double.MAX_VALUE;
+
+    for (i = 0; i < sortedIndices.length; i++) {
+      Instance inst = data.instance(sortedIndices[i]);
+      if (inst.isMissing(att)) {
+	break;
+      }
+      if (inst.value(att) > currSplit) {
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int k=0; k<2; k++) {
+	  //tempDist[k] = currDist[k];
+	  System.arraycopy(currDist[k], 0, tempDist[k], 0, tempDist[k].length);
+	}
+
+	double[] tempProps = new double[2];
+	for (int k=0; k<2; k++) {
+	  tempProps[k] = Utils.sum(tempDist[k]);
+	}
+
+	if (Utils.sum(tempProps) !=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int index = missingStart;
+	while (index < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[index]);
+	  for (int j = 0; j < 2; j++) {
+	    tempDist[j][(int)insta.classValue()] += tempProps[j] * weights[index];
+	  }
+	  index++;
+	}
+
+	currGiniGain = computeGiniGain(parentDist,tempDist);
+
+	if (currGiniGain > bestGiniGain) {
+	  bestGiniGain = currGiniGain;
+
+	  // clean split point
+//	  splitPoint = Math.rint((inst.value(att) + currSplit)/2.0*100000)/100000.0;
+	  splitPoint = (inst.value(att) + currSplit) / 2.0;
+
+	  for (int j = 0; j < currDist.length; j++) {
+	    System.arraycopy(tempDist[j], 0, dist[j], 0,
+		dist[j].length);
+	  }
+	}
+      }
+      currSplit = inst.value(att);
+      currDist[0][(int)inst.classValue()] += weights[i];
+      currDist[1][(int)inst.classValue()] -= weights[i];
+    }
+
+    // Compute weights
+    int attIndex = att.index();
+    props[attIndex] = new double[2];
+    for (int k = 0; k < 2; k++) {
+      props[attIndex][k] = Utils.sum(dist[k]);
+    }
+    if (Utils.sum(props[attIndex]) != 0) Utils.normalize(props[attIndex]);
+
+    // Compute subset weights
+    subsetWeights[attIndex] = new double[2];
+    for (int j = 0; j < 2; j++) {
+      subsetWeights[attIndex][j] += Utils.sum(dist[j]);
+    }
+
+    // clean Gini gain
+    //giniGains[attIndex] = Math.rint(bestGiniGain*10000000)/10000000.0;
+    giniGains[attIndex] = bestGiniGain;
+    dists[attIndex] = dist;
+
+    return splitPoint;
+  }
+
+  /**
+   * Compute distributions, proportions and total weights of two successor 
+   * nodes for a given nominal attribute.
+   * 
+   * @param props 		proportions of each two branches for each attribute
+   * @param dists 		class distributions of two branches for each attribute
+   * @param att 		numeric att split on
+   * @param sortedIndices 	sorted indices of instances for the attirubte
+   * @param weights 		weights of instances for the attirbute
+   * @param subsetWeights 	total weight of two branches split based on the attribute
+   * @param giniGains 		Gini gains for each attribute 
+   * @param data 		training instances
+   * @param useHeuristic 	if use heuristic search
+   * @return 			Gini gain for the given nominal attribute
+   * @throws Exception 		if something goes wrong
+   */
+  protected String nominalDistribution(double[][] props, double[][][] dists,
+      Attribute att, int[] sortedIndices, double[] weights, double[][] subsetWeights,
+      double[] giniGains, Instances data, boolean useHeuristic)
+    throws Exception {
+
+    String[] values = new String[att.numValues()];
+    int numCat = values.length; // number of values of the attribute
+    int numClasses = data.numClasses();
+
+    String bestSplitString = "";
+    double bestGiniGain = -Double.MAX_VALUE;
+
+    // class frequency for each value
+    int[] classFreq = new int[numCat];
+    for (int j=0; j<numCat; j++) classFreq[j] = 0;
+
+    double[] parentDist = new double[numClasses];
+    double[][] currDist = new double[2][numClasses];
+    double[][] dist = new double[2][numClasses];
+    int missingStart = 0;
+
+    for (int i = 0; i < sortedIndices.length; i++) {
+      Instance inst = data.instance(sortedIndices[i]);
+      if (!inst.isMissing(att)) {
+	missingStart++;
+	classFreq[(int)inst.value(att)] ++;
+      }
+      parentDist[(int)inst.classValue()] += weights[i];
+    }
+
+    // count the number of values that class frequency is not 0
+    int nonEmpty = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]!=0) nonEmpty ++;
+    }
+
+    // attribute values that class frequency is not 0
+    String[] nonEmptyValues = new String[nonEmpty];
+    int nonEmptyIndex = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]!=0) {
+	nonEmptyValues[nonEmptyIndex] = att.value(j);
+	nonEmptyIndex ++;
+      }
+    }
+
+    // attribute values that class frequency is 0
+    int empty = numCat - nonEmpty;
+    String[] emptyValues = new String[empty];
+    int emptyIndex = 0;
+    for (int j=0; j<numCat; j++) {
+      if (classFreq[j]==0) {
+	emptyValues[emptyIndex] = att.value(j);
+	emptyIndex ++;
+      }
+    }
+
+    if (nonEmpty<=1) {
+      giniGains[att.index()] = 0;
+      return "";
+    }
+
+    // for tow-class probloms
+    if (data.numClasses()==2) {
+
+      //// Firstly, for attribute values which class frequency is not zero
+
+      // probability of class 0 for each attribute value
+      double[] pClass0 = new double[nonEmpty];
+      // class distribution for each attribute value
+      double[][] valDist = new double[nonEmpty][2];
+
+      for (int j=0; j<nonEmpty; j++) {
+	for (int k=0; k<2; k++) {
+	  valDist[j][k] = 0;
+	}
+      }
+
+      for (int i = 0; i < sortedIndices.length; i++) {
+	Instance inst = data.instance(sortedIndices[i]);
+	if (inst.isMissing(att)) {
+	  break;
+	}
+
+	for (int j=0; j<nonEmpty; j++) {
+	  if (att.value((int)inst.value(att)).compareTo(nonEmptyValues[j])==0) {
+	    valDist[j][(int)inst.classValue()] += inst.weight();
+	    break;
+	  }
+	}
+      }
+
+      for (int j=0; j<nonEmpty; j++) {
+	double distSum = Utils.sum(valDist[j]);
+	if (distSum==0) pClass0[j]=0;
+	else pClass0[j] = valDist[j][0]/distSum;
+      }
+
+      // sort category according to the probability of the first class
+      String[] sortedValues = new String[nonEmpty];
+      for (int j=0; j<nonEmpty; j++) {
+	sortedValues[j] = nonEmptyValues[Utils.minIndex(pClass0)];
+	pClass0[Utils.minIndex(pClass0)] = Double.MAX_VALUE;
+      }
+
+      // Find a subset of attribute values that maximize Gini decrease
+
+      // for the attribute values that class frequency is not 0
+      String tempStr = "";
+
+      for (int j=0; j<nonEmpty-1; j++) {
+	currDist = new double[2][numClasses];
+	if (tempStr=="") tempStr="(" + sortedValues[j] + ")";
+	else tempStr += "|"+ "(" + sortedValues[j] + ")";
+	for (int i=0; i<sortedIndices.length;i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf
+	      ("(" + att.value((int)inst.value(att)) + ")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[i];
+	  } else currDist[1][(int)inst.classValue()] += weights[i];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int kk=0; kk<2; kk++) {
+	  tempDist[kk] = currDist[kk];
+	}
+
+	double[] tempProps = new double[2];
+	for (int kk=0; kk<2; kk++) {
+	  tempProps[kk] = Utils.sum(tempDist[kk]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int mstart = missingStart;
+	while (mstart < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[mstart]);
+	  for (int jj = 0; jj < 2; jj++) {
+	    tempDist[jj][(int)insta.classValue()] += tempProps[jj] * weights[mstart];
+	  }
+	  mstart++;
+	}
+
+	double currGiniGain = computeGiniGain(parentDist,tempDist);
+
+	if (currGiniGain>bestGiniGain) {
+	  bestGiniGain = currGiniGain;
+	  bestSplitString = tempStr;
+	  for (int jj = 0; jj < 2; jj++) {
+	    //dist[jj] = new double[currDist[jj].length];
+	    System.arraycopy(tempDist[jj], 0, dist[jj], 0,
+		dist[jj].length);
+	  }
+	}
+      }
+    }
+
+    // multi-class problems - exhaustive search
+    else if (!useHeuristic || nonEmpty<=4) {
+
+      // Firstly, for attribute values which class frequency is not zero
+      for (int i=0; i<(int)Math.pow(2,nonEmpty-1); i++) {
+	String tempStr="";
+	currDist = new double[2][numClasses];
+	int mod;
+	int bit10 = i;
+	for (int j=nonEmpty-1; j>=0; j--) {
+	  mod = bit10%2; // convert from 10bit to 2bit
+	  if (mod==1) {
+	    if (tempStr=="") tempStr = "("+nonEmptyValues[j]+")";
+	    else tempStr += "|" + "("+nonEmptyValues[j]+")";
+	  }
+	  bit10 = bit10/2;
+	}
+	for (int j=0; j<sortedIndices.length;j++) {
+	  Instance inst = data.instance(sortedIndices[j]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf("("+att.value((int)inst.value(att))+")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[j];
+	  } else currDist[1][(int)inst.classValue()] += weights[j];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int k=0; k<2; k++) {
+	  tempDist[k] = currDist[k];
+	}
+
+	double[] tempProps = new double[2];
+	for (int k=0; k<2; k++) {
+	  tempProps[k] = Utils.sum(tempDist[k]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int index = missingStart;
+	while (index < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[index]);
+	  for (int j = 0; j < 2; j++) {
+	    tempDist[j][(int)insta.classValue()] += tempProps[j] * weights[index];
+	  }
+	  index++;
+	}
+
+	double currGiniGain = computeGiniGain(parentDist,tempDist);
+
+	if (currGiniGain>bestGiniGain) {
+	  bestGiniGain = currGiniGain;
+	  bestSplitString = tempStr;
+	  for (int j = 0; j < 2; j++) {
+	    //dist[jj] = new double[currDist[jj].length];
+	    System.arraycopy(tempDist[j], 0, dist[j], 0,
+		dist[j].length);
+	  }
+	}
+      }
+    }
+
+    // huristic search to solve multi-classes problems
+    else {
+      // Firstly, for attribute values which class frequency is not zero
+      int n = nonEmpty;
+      int k = data.numClasses();  // number of classes of the data
+      double[][] P = new double[n][k];      // class probability matrix
+      int[] numInstancesValue = new int[n]; // number of instances for an attribute value
+      double[] meanClass = new double[k];   // vector of mean class probability
+      int numInstances = data.numInstances(); // total number of instances
+
+      // initialize the vector of mean class probability
+      for (int j=0; j<meanClass.length; j++) meanClass[j]=0;
+
+      for (int j=0; j<numInstances; j++) {
+	Instance inst = (Instance)data.instance(j);
+	int valueIndex = 0; // attribute value index in nonEmptyValues
+	for (int i=0; i<nonEmpty; i++) {
+	  if (att.value((int)inst.value(att)).compareToIgnoreCase(nonEmptyValues[i])==0){
+	    valueIndex = i;
+	    break;
+	  }
+	}
+	P[valueIndex][(int)inst.classValue()]++;
+	numInstancesValue[valueIndex]++;
+	meanClass[(int)inst.classValue()]++;
+      }
+
+      // calculate the class probability matrix
+      for (int i=0; i<P.length; i++) {
+	for (int j=0; j<P[0].length; j++) {
+	  if (numInstancesValue[i]==0) P[i][j]=0;
+	  else P[i][j]/=numInstancesValue[i];
+	}
+      }
+
+      //calculate the vector of mean class probability
+      for (int i=0; i<meanClass.length; i++) {
+	meanClass[i]/=numInstances;
+      }
+
+      // calculate the covariance matrix
+      double[][] covariance = new double[k][k];
+      for (int i1=0; i1<k; i1++) {
+	for (int i2=0; i2<k; i2++) {
+	  double element = 0;
+	  for (int j=0; j<n; j++) {
+	    element += (P[j][i2]-meanClass[i2])*(P[j][i1]-meanClass[i1])
+	    *numInstancesValue[j];
+	  }
+	  covariance[i1][i2] = element;
+	}
+      }
+
+      Matrix matrix = new Matrix(covariance);
+      weka.core.matrix.EigenvalueDecomposition eigen =
+	new weka.core.matrix.EigenvalueDecomposition(matrix);
+      double[] eigenValues = eigen.getRealEigenvalues();
+
+      // find index of the largest eigenvalue
+      int index=0;
+      double largest = eigenValues[0];
+      for (int i=1; i<eigenValues.length; i++) {
+	if (eigenValues[i]>largest) {
+	  index=i;
+	  largest = eigenValues[i];
+	}
+      }
+
+      // calculate the first principle component
+      double[] FPC = new double[k];
+      Matrix eigenVector = eigen.getV();
+      double[][] vectorArray = eigenVector.getArray();
+      for (int i=0; i<FPC.length; i++) {
+	FPC[i] = vectorArray[i][index];
+      }
+
+      // calculate the first principle component scores
+      //System.out.println("the first principle component scores: ");
+      double[] Sa = new double[n];
+      for (int i=0; i<Sa.length; i++) {
+	Sa[i]=0;
+	for (int j=0; j<k; j++) {
+	  Sa[i] += FPC[j]*P[i][j];
+	}
+      }
+
+      // sort category according to Sa(s)
+      double[] pCopy = new double[n];
+      System.arraycopy(Sa,0,pCopy,0,n);
+      String[] sortedValues = new String[n];
+      Arrays.sort(Sa);
+
+      for (int j=0; j<n; j++) {
+	sortedValues[j] = nonEmptyValues[Utils.minIndex(pCopy)];
+	pCopy[Utils.minIndex(pCopy)] = Double.MAX_VALUE;
+      }
+
+      // for the attribute values that class frequency is not 0
+      String tempStr = "";
+
+      for (int j=0; j<nonEmpty-1; j++) {
+	currDist = new double[2][numClasses];
+	if (tempStr=="") tempStr="(" + sortedValues[j] + ")";
+	else tempStr += "|"+ "(" + sortedValues[j] + ")";
+	for (int i=0; i<sortedIndices.length;i++) {
+	  Instance inst = data.instance(sortedIndices[i]);
+	  if (inst.isMissing(att)) {
+	    break;
+	  }
+
+	  if (tempStr.indexOf
+	      ("(" + att.value((int)inst.value(att)) + ")")!=-1) {
+	    currDist[0][(int)inst.classValue()] += weights[i];
+	  } else currDist[1][(int)inst.classValue()] += weights[i];
+	}
+
+	double[][] tempDist = new double[2][numClasses];
+	for (int kk=0; kk<2; kk++) {
+	  tempDist[kk] = currDist[kk];
+	}
+
+	double[] tempProps = new double[2];
+	for (int kk=0; kk<2; kk++) {
+	  tempProps[kk] = Utils.sum(tempDist[kk]);
+	}
+
+	if (Utils.sum(tempProps)!=0) Utils.normalize(tempProps);
+
+	// split missing values
+	int mstart = missingStart;
+	while (mstart < sortedIndices.length) {
+	  Instance insta = data.instance(sortedIndices[mstart]);
+	  for (int jj = 0; jj < 2; jj++) {
+	    tempDist[jj][(int)insta.classValue()] += tempProps[jj] * weights[mstart];
+	  }
+	  mstart++;
+	}
+
+	double currGiniGain = computeGiniGain(parentDist,tempDist);
+
+	if (currGiniGain>bestGiniGain) {
+	  bestGiniGain = currGiniGain;
+	  bestSplitString = tempStr;
+	  for (int jj = 0; jj < 2; jj++) {
+	    //dist[jj] = new double[currDist[jj].length];
+	    System.arraycopy(tempDist[jj], 0, dist[jj], 0,
+		dist[jj].length);
+	  }
+	}
+      }
+    }
+
+    // Compute weights
+    int attIndex = att.index();        
+    props[attIndex] = new double[2];
+    for (int k = 0; k < 2; k++) {
+      props[attIndex][k] = Utils.sum(dist[k]);
+    }
+
+    if (!(Utils.sum(props[attIndex]) > 0)) {
+      for (int k = 0; k < props[attIndex].length; k++) {
+	props[attIndex][k] = 1.0 / (double)props[attIndex].length;
+      }
+    } else {
+      Utils.normalize(props[attIndex]);
+    }
+
+
+    // Compute subset weights
+    subsetWeights[attIndex] = new double[2];
+    for (int j = 0; j < 2; j++) {
+      subsetWeights[attIndex][j] += Utils.sum(dist[j]);
+    }
+
+    // Then, for the attribute values that class frequency is 0, split it into the
+    // most frequent branch
+    for (int j=0; j<empty; j++) {
+      if (props[attIndex][0]>=props[attIndex][1]) {
+	if (bestSplitString=="") bestSplitString = "(" + emptyValues[j] + ")";
+	else bestSplitString += "|" + "(" + emptyValues[j] + ")";
+      }
+    }
+
+    // clean Gini gain for the attribute
+    //giniGains[attIndex] = Math.rint(bestGiniGain*10000000)/10000000.0;
+    giniGains[attIndex] = bestGiniGain;
+
+    dists[attIndex] = dist;
+    return bestSplitString;
+  }
+
+
+  /**
+   * Split data into two subsets and store sorted indices and weights for two
+   * successor nodes.
+   * 
+   * @param subsetIndices 	sorted indecis of instances for each attribute 
+   * 				for two successor node
+   * @param subsetWeights 	weights of instances for each attribute for 
+   * 				two successor node
+   * @param att 		attribute the split based on
+   * @param splitPoint 		split point the split based on if att is numeric
+   * @param splitStr 		split subset the split based on if att is nominal
+   * @param sortedIndices 	sorted indices of the instances to be split
+   * @param weights 		weights of the instances to bes split
+   * @param data 		training data
+   * @throws Exception 		if something goes wrong  
+   */
+  protected void splitData(int[][][] subsetIndices, double[][][] subsetWeights,
+      Attribute att, double splitPoint, String splitStr, int[][] sortedIndices,
+      double[][] weights, Instances data) throws Exception {
+
+    int j;
+    // For each attribute
+    for (int i = 0; i < data.numAttributes(); i++) {
+      if (i==data.classIndex()) continue;
+      int[] num = new int[2];
+      for (int k = 0; k < 2; k++) {
+	subsetIndices[k][i] = new int[sortedIndices[i].length];
+	subsetWeights[k][i] = new double[weights[i].length];
+      }
+
+      for (j = 0; j < sortedIndices[i].length; j++) {
+	Instance inst = data.instance(sortedIndices[i][j]);
+	if (inst.isMissing(att)) {
+	  // Split instance up
+	  for (int k = 0; k < 2; k++) {
+	    if (m_Props[k] > 0) {
+	      subsetIndices[k][i][num[k]] = sortedIndices[i][j];
+	      subsetWeights[k][i][num[k]] = m_Props[k] * weights[i][j];
+	      num[k]++;
+	    }
+	  }
+	} else {
+	  int subset;
+	  if (att.isNumeric())  {
+	    subset = (inst.value(att) < splitPoint) ? 0 : 1;
+	  } else { // nominal attribute
+	    if (splitStr.indexOf
+		("(" + att.value((int)inst.value(att.index()))+")")!=-1) {
+	      subset = 0;
+	    } else subset = 1;
+	  }
+	  subsetIndices[subset][i][num[subset]] = sortedIndices[i][j];
+	  subsetWeights[subset][i][num[subset]] = weights[i][j];
+	  num[subset]++;
+	}
+      }
+
+      // Trim arrays
+      for (int k = 0; k < 2; k++) {
+	int[] copy = new int[num[k]];
+	System.arraycopy(subsetIndices[k][i], 0, copy, 0, num[k]);
+	subsetIndices[k][i] = copy;
+	double[] copyWeights = new double[num[k]];
+	System.arraycopy(subsetWeights[k][i], 0 ,copyWeights, 0, num[k]);
+	subsetWeights[k][i] = copyWeights;
+      }
+    }
+  }
+
+  /**
+   * Updates the numIncorrectModel field for all nodes when subtree (to be 
+   * pruned) is rooted. This is needed for calculating the alpha-values.
+   * 
+   * @throws Exception 	if something goes wrong
+   */
+  public void modelErrors() throws Exception{
+    Evaluation eval = new Evaluation(m_train);
+
+    if (!m_isLeaf) {
+      m_isLeaf = true; //temporarily make leaf
+
+      // calculate distribution for evaluation
+      eval.evaluateModel(this, m_train);
+      m_numIncorrectModel = eval.incorrect();
+
+      m_isLeaf = false;
+
+      for (int i = 0; i < m_Successors.length; i++)
+	m_Successors[i].modelErrors();
+
+    } else {
+      eval.evaluateModel(this, m_train);
+      m_numIncorrectModel = eval.incorrect();
+    }       
+  }
+
+  /**
+   * Updates the numIncorrectTree field for all nodes. This is needed for
+   * calculating the alpha-values.
+   * 
+   * @throws Exception 	if something goes wrong
+   */
+  public void treeErrors() throws Exception {
+    if (m_isLeaf) {
+      m_numIncorrectTree = m_numIncorrectModel;
+    } else {
+      m_numIncorrectTree = 0;
+      for (int i = 0; i < m_Successors.length; i++) {
+	m_Successors[i].treeErrors();
+	m_numIncorrectTree += m_Successors[i].m_numIncorrectTree;
+      }
+    }
+  }
+
+  /**
+   * Updates the alpha field for all nodes.
+   * 
+   * @throws Exception 	if something goes wrong
+   */
+  public void calculateAlphas() throws Exception {
+
+    if (!m_isLeaf) {
+      double errorDiff = m_numIncorrectModel - m_numIncorrectTree;
+      if (errorDiff <=0) {
+	//split increases training error (should not normally happen).
+	//prune it instantly.
+	makeLeaf(m_train);
+	m_Alpha = Double.MAX_VALUE;
+      } else {
+	//compute alpha
+	errorDiff /= m_totalTrainInstances;
+	m_Alpha = errorDiff / (double)(numLeaves() - 1);
+	long alphaLong = Math.round(m_Alpha*Math.pow(10,10));
+	m_Alpha = (double)alphaLong/Math.pow(10,10);
+	for (int i = 0; i < m_Successors.length; i++) {
+	  m_Successors[i].calculateAlphas();
+	}
+      }
+    } else {
+      //alpha = infinite for leaves (do not want to prune)
+      m_Alpha = Double.MAX_VALUE;
+    }
+  }
+
+  /**
+   * Find the node with minimal alpha value. If two nodes have the same alpha, 
+   * choose the one with more leave nodes.
+   * 
+   * @param nodeList 	list of inner nodes
+   * @return 		the node to be pruned
+   */
+  protected SimpleCart nodeToPrune(Vector nodeList) {
+    if (nodeList.size()==0) return null;
+    if (nodeList.size()==1) return (SimpleCart)nodeList.elementAt(0);
+    SimpleCart returnNode = (SimpleCart)nodeList.elementAt(0);
+    double baseAlpha = returnNode.m_Alpha;
+    for (int i=1; i<nodeList.size(); i++) {
+      SimpleCart node = (SimpleCart)nodeList.elementAt(i);
+      if (node.m_Alpha < baseAlpha) {
+	baseAlpha = node.m_Alpha;
+	returnNode = node;
+      } else if (node.m_Alpha == baseAlpha) { // break tie
+	if (node.numLeaves()>returnNode.numLeaves()) {
+	  returnNode = node;
+	}
+      }
+    }
+    return returnNode;
+  }
+
+  /**
+   * Compute sorted indices, weights and class probabilities for a given 
+   * dataset. Return total weights of the data at the node.
+   * 
+   * @param data 		training data
+   * @param sortedIndices 	sorted indices of instances at the node
+   * @param weights 		weights of instances at the node
+   * @param classProbs 		class probabilities at the node
+   * @return total 		weights of instances at the node
+   * @throws Exception 		if something goes wrong
+   */
+  protected double computeSortedInfo(Instances data, int[][] sortedIndices, double[][] weights,
+      double[] classProbs) throws Exception {
+
+    // Create array of sorted indices and weights
+    double[] vals = new double[data.numInstances()];
+    for (int j = 0; j < data.numAttributes(); j++) {
+      if (j==data.classIndex()) continue;
+      weights[j] = new double[data.numInstances()];
+
+      if (data.attribute(j).isNominal()) {
+
+	// Handling nominal attributes. Putting indices of
+	// instances with missing values at the end.
+	sortedIndices[j] = new int[data.numInstances()];
+	int count = 0;
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  if (!inst.isMissing(j)) {
+	    sortedIndices[j][count] = i;
+	    weights[j][count] = inst.weight();
+	    count++;
+	  }
+	}
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  if (inst.isMissing(j)) {
+	    sortedIndices[j][count] = i;
+	    weights[j][count] = inst.weight();
+	    count++;
+	  }
+	}
+      } else {
+
+	// Sorted indices are computed for numeric attributes
+	// missing values instances are put to end 
+	for (int i = 0; i < data.numInstances(); i++) {
+	  Instance inst = data.instance(i);
+	  vals[i] = inst.value(j);
+	}
+	sortedIndices[j] = Utils.sort(vals);
+	for (int i = 0; i < data.numInstances(); i++) {
+	  weights[j][i] = data.instance(sortedIndices[j][i]).weight();
+	}
+      }
+    }
+
+    // Compute initial class counts
+    double totalWeight = 0;
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance inst = data.instance(i);
+      classProbs[(int)inst.classValue()] += inst.weight();
+      totalWeight += inst.weight();
+    }
+
+    return totalWeight;
+  }
+
+  /**
+   * Compute and return gini gain for given distributions of a node and its 
+   * successor nodes.
+   * 
+   * @param parentDist 	class distributions of parent node
+   * @param childDist 	class distributions of successor nodes
+   * @return 		Gini gain computed
+   */
+  protected double computeGiniGain(double[] parentDist, double[][] childDist) {
+    double totalWeight = Utils.sum(parentDist);
+    if (totalWeight==0) return 0;
+
+    double leftWeight = Utils.sum(childDist[0]);
+    double rightWeight = Utils.sum(childDist[1]);
+
+    double parentGini = computeGini(parentDist, totalWeight);
+    double leftGini = computeGini(childDist[0],leftWeight);
+    double rightGini = computeGini(childDist[1], rightWeight);
+
+    return parentGini - leftWeight/totalWeight*leftGini -
+    rightWeight/totalWeight*rightGini;
+  }
+
+  /**
+   * Compute and return gini index for a given distribution of a node.
+   * 
+   * @param dist 	class distributions
+   * @param total 	class distributions
+   * @return 		Gini index of the class distributions
+   */
+  protected double computeGini(double[] dist, double total) {
+    if (total==0) return 0;
+    double val = 0;
+    for (int i=0; i<dist.length; i++) {
+      val += (dist[i]/total)*(dist[i]/total);
+    }
+    return 1- val;
+  }
+
+  /**
+   * Computes class probabilities for instance using the decision tree.
+   * 
+   * @param instance 	the instance for which class probabilities is to be computed
+   * @return 		the class probabilities for the given instance
+   * @throws Exception 	if something goes wrong
+   */
+  public double[] distributionForInstance(Instance instance)
+  throws Exception {
+    if (!m_isLeaf) {
+      // value of split attribute is missing
+      if (instance.isMissing(m_Attribute)) {
+	double[] returnedDist = new double[m_ClassProbs.length];
+
+	for (int i = 0; i < m_Successors.length; i++) {
+	  double[] help =
+	    m_Successors[i].distributionForInstance(instance);
+	  if (help != null) {
+	    for (int j = 0; j < help.length; j++) {
+	      returnedDist[j] += m_Props[i] * help[j];
+	    }
+	  }
+	}
+	return returnedDist;
+      }
+
+      // split attribute is nonimal
+      else if (m_Attribute.isNominal()) {
+	if (m_SplitString.indexOf("(" +
+	    m_Attribute.value((int)instance.value(m_Attribute)) + ")")!=-1)
+	  return  m_Successors[0].distributionForInstance(instance);
+	else return  m_Successors[1].distributionForInstance(instance);
+      }
+
+      // split attribute is numeric
+      else {
+	if (instance.value(m_Attribute) < m_SplitValue)
+	  return m_Successors[0].distributionForInstance(instance);
+	else
+	  return m_Successors[1].distributionForInstance(instance);
+      }
+    }
+
+    // leaf node
+    else return m_ClassProbs;
+  }
+
+  /**
+   * Make the node leaf node.
+   * 
+   * @param data 	trainging data
+   */
+  protected void makeLeaf(Instances data) {
+    m_Attribute = null;
+    m_isLeaf = true;
+    m_ClassValue=Utils.maxIndex(m_ClassProbs);
+    m_ClassAttribute = data.classAttribute();
+  }
+
+  /**
+   * Prints the decision tree using the protected toString method from below.
+   * 
+   * @return 		a textual description of the classifier
+   */
+  public String toString() {
+    if ((m_ClassProbs == null) && (m_Successors == null)) {
+      return "CART Tree: No model built yet.";
+    }
+
+    return "CART Decision Tree\n" + toString(0)+"\n\n"
+    +"Number of Leaf Nodes: "+numLeaves()+"\n\n" +
+    "Size of the Tree: "+numNodes();
+  }
+
+  /**
+   * Outputs a tree at a certain level.
+   * 
+   * @param level 	the level at which the tree is to be printed
+   * @return 		a tree at a certain level
+   */
+  protected String toString(int level) {
+
+    StringBuffer text = new StringBuffer();
+    // if leaf nodes
+    if (m_Attribute == null) {
+      if (Utils.isMissingValue(m_ClassValue)) {
+	text.append(": null");
+      } else {
+	double correctNum = (int)(m_Distribution[Utils.maxIndex(m_Distribution)]*100)/
+	100.0;
+	double wrongNum = (int)((Utils.sum(m_Distribution) -
+	    m_Distribution[Utils.maxIndex(m_Distribution)])*100)/100.0;
+	String str = "("  + correctNum + "/" + wrongNum + ")";
+	text.append(": " + m_ClassAttribute.value((int) m_ClassValue)+ str);
+      }
+    } else {
+      for (int j = 0; j < 2; j++) {
+	text.append("\n");
+	for (int i = 0; i < level; i++) {
+	  text.append("|  ");
+	}
+	if (j==0) {
+	  if (m_Attribute.isNumeric())
+	    text.append(m_Attribute.name() + " < " + m_SplitValue);
+	  else
+	    text.append(m_Attribute.name() + "=" + m_SplitString);
+	} else {
+	  if (m_Attribute.isNumeric())
+	    text.append(m_Attribute.name() + " >= " + m_SplitValue);
+	  else
+	    text.append(m_Attribute.name() + "!=" + m_SplitString);
+	}
+	text.append(m_Successors[j].toString(level + 1));
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Compute size of the tree.
+   * 
+   * @return 		size of the tree
+   */
+  public int numNodes() {
+    if (m_isLeaf) {
+      return 1;
+    } else {
+      int size =1;
+      for (int i=0;i<m_Successors.length;i++) {
+	size+=m_Successors[i].numNodes();
+      }
+      return size;
+    }
+  }
+
+  /**
+   * Method to count the number of inner nodes in the tree.
+   * 
+   * @return 		the number of inner nodes
+   */
+  public int numInnerNodes(){
+    if (m_Attribute==null) return 0;
+    int numNodes = 1;
+    for (int i = 0; i < m_Successors.length; i++)
+      numNodes += m_Successors[i].numInnerNodes();
+    return numNodes;
+  }
+
+  /**
+   * Return a list of all inner nodes in the tree.
+   * 
+   * @return 		the list of all inner nodes
+   */
+  protected Vector getInnerNodes(){
+    Vector nodeList = new Vector();
+    fillInnerNodes(nodeList);
+    return nodeList;
+  }
+
+  /**
+   * Fills a list with all inner nodes in the tree.
+   * 
+   * @param nodeList 	the list to be filled
+   */
+  protected void fillInnerNodes(Vector nodeList) {
+    if (!m_isLeaf) {
+      nodeList.add(this);
+      for (int i = 0; i < m_Successors.length; i++)
+	m_Successors[i].fillInnerNodes(nodeList);
+    }
+  }
+
+  /**
+   * Compute number of leaf nodes.
+   * 
+   * @return 		number of leaf nodes
+   */
+  public int numLeaves() {
+    if (m_isLeaf) return 1;
+    else {
+      int size=0;
+      for (int i=0;i<m_Successors.length;i++) {
+	size+=m_Successors[i].numLeaves();
+      }
+      return size;
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector 	result;
+    Enumeration	en;
+    
+    result = new Vector();
+    
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe minimal number of instances at the terminal nodes.\n" 
+	+ "\t(default 2)",
+	"M", 1, "-M <min no>"));
+    
+    result.addElement(new Option(
+	"\tThe number of folds used in the minimal cost-complexity pruning.\n"
+	+ "\t(default 5)",
+	"N", 1, "-N <num folds>"));
+    
+    result.addElement(new Option(
+	"\tDon't use the minimal cost-complexity pruning.\n"
+	+ "\t(default yes).",
+	"U", 0, "-U"));
+    
+    result.addElement(new Option(
+	"\tDon't use the heuristic method for binary split.\n"
+	+ "\t(default true).",
+	"H", 0, "-H"));
+    
+    result.addElement(new Option(
+	"\tUse 1 SE rule to make pruning decision.\n"
+	+ "\t(default no).",
+	"A", 0, "-A"));
+    
+    result.addElement(new Option(
+	"\tPercentage of training data size (0-1].\n" 
+	+ "\t(default 1).",
+	"C", 1, "-C"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -M &lt;min no&gt;
+   *  The minimal number of instances at the terminal nodes.
+   *  (default 2)</pre>
+   * 
+   * <pre> -N &lt;num folds&gt;
+   *  The number of folds used in the minimal cost-complexity pruning.
+   *  (default 5)</pre>
+   * 
+   * <pre> -U
+   *  Don't use the minimal cost-complexity pruning.
+   *  (default yes).</pre>
+   * 
+   * <pre> -H
+   *  Don't use the heuristic method for binary split.
+   *  (default true).</pre>
+   * 
+   * <pre> -A
+   *  Use 1 SE rule to make pruning decision.
+   *  (default no).</pre>
+   * 
+   * <pre> -C
+   *  Percentage of training data size (0-1].
+   *  (default 1).</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an options is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setMinNumObj(Double.parseDouble(tmpStr));
+    else
+      setMinNumObj(2);
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length()!=0)
+      setNumFoldsPruning(Integer.parseInt(tmpStr));
+    else
+      setNumFoldsPruning(5);
+
+    setUsePrune(!Utils.getFlag('U',options));
+    setHeuristic(!Utils.getFlag('H',options));
+    setUseOneSE(Utils.getFlag('A',options));
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length()!=0)
+      setSizePer(Double.parseDouble(tmpStr));
+    else
+      setSizePer(1);
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   * 
+   * @return 		the current setting of the classifier
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-M");
+    result.add("" + getMinNumObj());
+    
+    result.add("-N");
+    result.add("" + getNumFoldsPruning());
+    
+    if (!getUsePrune())
+      result.add("-U");
+    
+    if (!getHeuristic())
+      result.add("-H");
+    
+    if (getUseOneSE())
+      result.add("-A");
+    
+    result.add("-C");
+    result.add("" + getSizePer());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Return an enumeration of the measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector result = new Vector();
+    
+    result.addElement("measureTreeSize");
+    
+    return result.elements();
+  }
+
+  /**
+   * Return number of tree size.
+   * 
+   * @return 		number of tree size
+   */
+  public double measureTreeSize() {
+    return numNodes();
+  }
+
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName 	the name of the measure to query for its value
+   * @return 				the value of the named measure
+   * @throws IllegalArgumentException 	if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName
+	  + " not supported (Cart pruning)");
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String minNumObjTipText() {
+    return "The minimal number of observations at the terminal nodes (default 2).";
+  }
+
+  /**
+   * Set minimal number of instances at the terminal nodes.
+   * 
+   * @param value 	minimal number of instances at the terminal nodes
+   */
+  public void setMinNumObj(double value) {
+    m_minNumObj = value;
+  }
+
+  /**
+   * Get minimal number of instances at the terminal nodes.
+   * 
+   * @return 		minimal number of instances at the terminal nodes
+   */
+  public double getMinNumObj() {
+    return m_minNumObj;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numFoldsPruningTipText() {
+    return "The number of folds in the internal cross-validation (default 5).";
+  }
+
+  /** 
+   * Set number of folds in internal cross-validation.
+   * 
+   * @param value 	number of folds in internal cross-validation.
+   */
+  public void setNumFoldsPruning(int value) {
+    m_numFoldsPruning = value;
+  }
+
+  /**
+   * Set number of folds in internal cross-validation.
+   * 
+   * @return 		number of folds in internal cross-validation.
+   */
+  public int getNumFoldsPruning() {
+    return m_numFoldsPruning;
+  }
+
+  /**
+   * Return the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for displaying in 
+   * 			the explorer/experimenter gui.
+   */
+  public String usePruneTipText() {
+    return "Use minimal cost-complexity pruning (default yes).";
+  }
+
+  /** 
+   * Set if use minimal cost-complexity pruning.
+   * 
+   * @param value 	if use minimal cost-complexity pruning
+   */
+  public void setUsePrune(boolean value) {
+    m_Prune = value;
+  }
+
+  /** 
+   * Get if use minimal cost-complexity pruning.
+   * 
+   * @return 		if use minimal cost-complexity pruning
+   */
+  public boolean getUsePrune() {
+    return m_Prune;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String heuristicTipText() {
+    return 
+        "If heuristic search is used for binary split for nominal attributes "
+      + "in multi-class problems (default yes).";
+  }
+
+  /**
+   * Set if use heuristic search for nominal attributes in multi-class problems.
+   * 
+   * @param value 	if use heuristic search for nominal attributes in 
+   * 			multi-class problems
+   */
+  public void setHeuristic(boolean value) {
+    m_Heuristic = value;
+  }
+
+  /** 
+   * Get if use heuristic search for nominal attributes in multi-class problems.
+   * 
+   * @return 		if use heuristic search for nominal attributes in 
+   * 			multi-class problems
+   */
+  public boolean getHeuristic() {return m_Heuristic;}
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String useOneSETipText() {
+    return "Use the 1SE rule to make pruning decisoin.";
+  }
+
+  /** 
+   * Set if use the 1SE rule to choose final model.
+   * 
+   * @param value 	if use the 1SE rule to choose final model
+   */
+  public void setUseOneSE(boolean value) {
+    m_UseOneSE = value;
+  }
+
+  /**
+   * Get if use the 1SE rule to choose final model.
+   * 
+   * @return 		if use the 1SE rule to choose final model
+   */
+  public boolean getUseOneSE() {
+    return m_UseOneSE;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui.
+   */
+  public String sizePerTipText() {
+    return "The percentage of the training set size (0-1, 0 not included).";
+  }
+
+  /** 
+   * Set training set size.
+   * 
+   * @param value 	training set size
+   */  
+  public void setSizePer(double value) {
+    if ((value <= 0) || (value > 1))
+      System.err.println(
+	  "The percentage of the training set size must be in range 0 to 1 "
+	  + "(0 not included) - ignored!");
+    else
+      m_SizePer = value;
+  }
+
+  /**
+   * Get training set size.
+   * 
+   * @return 		training set size
+   */
+  public double getSizePer() {
+    return m_SizePer;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   * @param args the options for the classifier
+   */
+  public static void main(String[] args) {
+    runClassifier(new SimpleCart(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/UserClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/UserClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/UserClassifier.java	(revision 29)
@@ -0,0 +1,1509 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UserClassifier.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.LinearRegression;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertyDialog;
+import weka.gui.treevisualizer.PlaceNode1;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeDisplayEvent;
+import weka.gui.treevisualizer.TreeDisplayListener;
+import weka.gui.treevisualizer.TreeVisualizer;
+import weka.gui.visualize.VisualizePanel;
+import weka.gui.visualize.VisualizePanelEvent;
+import weka.gui.visualize.VisualizePanelListener;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.Serializable;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JTabbedPane;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Interactively classify through visual means. You are Presented with a scatter graph of the data against two user selectable attributes, as well as a view of the decision tree. You can create binary splits by creating polygons around data plotted on the scatter graph, as well as by allowing another classifier to take over at points in the decision tree should you see fit.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Malcolm Ware, Eibe Frank, Geoffrey Holmes, Mark Hall, Ian H. Witten (2001). Interactive machine learning: letting users build classifiers. Int. J. Hum.-Comput. Stud.. 55(3):281-292.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Ware2001,
+ *    author = {Malcolm Ware and Eibe Frank and Geoffrey Holmes and Mark Hall and Ian H. Witten},
+ *    journal = {Int. J. Hum.-Comput. Stud.},
+ *    number = {3},
+ *    pages = {281-292},
+ *    title = {Interactive machine learning: letting users build classifiers},
+ *    volume = {55},
+ *    year = {2001},
+ *    PS = {http://www.cs.waikato.ac.nz/\~ml/publications/2000/00MW-etal-Interactive-ML.ps}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class UserClassifier 
+  extends AbstractClassifier 
+  implements Drawable, TreeDisplayListener, VisualizePanelListener,
+             TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 6483901103562809843L;
+
+  /** I am not sure if these are strictly adhered to in visualizepanel
+   * so I am making them private to avoid confusion, (note that they will
+   * be correct in this class, VLINE and HLINE aren't used).
+   */
+  private static final int LEAF = 0;
+  private static final int RECTANGLE = 1;
+  private static final int POLYGON = 2;
+  private static final int POLYLINE = 3;
+  private static final int VLINE = 5;
+  private static final int HLINE =6;
+  
+
+  /** The tree display panel. */
+  private transient TreeVisualizer m_tView = null;
+  /** The instances display. */
+  private transient VisualizePanel m_iView = null;
+  /** Two references to the structure of the decision tree. */
+  private TreeClass m_top, m_focus;
+  /** The next number that can be used as a unique id for a node. */
+  private int m_nextId;
+  /** The tabbed window for the tree and instances view. */
+  private transient JTabbedPane m_reps;
+  /** The window. */
+  private transient JFrame m_mainWin;
+  /** The status of whether there is a decision tree ready or not. */
+  private boolean m_built=false;
+  /** A list of other m_classifiers. */
+  private GenericObjectEditor m_classifiers;
+  /** A window for selecting other classifiers. */
+  private PropertyDialog m_propertyDialog;
+
+  /** Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain command line options (see setOptions)
+   */
+  public static void main(String [] argv) {
+    runClassifier(new UserClassifier(), argv);
+  }
+
+  /**
+   * @return a string that represents this objects tree.
+   */
+  public String toString() {
+    if (!m_built) {
+
+      return "Tree Not Built";
+    }
+    StringBuffer text = new StringBuffer();
+    try {
+      m_top.toString(0, text);
+      
+      m_top.objectStrings(text);
+
+    } catch(Exception e) {
+      System.out.println("error: " + e.getMessage());
+    }
+    
+    return text.toString();
+  }
+
+  /**
+   * Receives user choices from the tree view, and then deals with these 
+   * choices. 
+   * @param e The choice. 
+   */
+  public void userCommand(TreeDisplayEvent e) {
+    
+    if (m_propertyDialog != null) {
+      m_propertyDialog.dispose();
+      m_propertyDialog = null;
+    }
+    try {
+      if (m_iView == null || m_tView == null) {
+	//throw exception
+      }
+      if (e.getCommand() == TreeDisplayEvent.NO_COMMAND) {
+	//do nothing
+      }
+      else if (e.getCommand() == TreeDisplayEvent.ADD_CHILDREN) {
+	//highlight the particular node and reset the vis panel
+	if (m_top == null) {
+	  //this shouldn't happen , someone elses code would 
+	  //have to have added a trigger to this listener.
+	  System.out.println("Error : Received event from a TreeDisplayer"
+			     + " that is unknown to the classifier.");
+	}
+	else {
+	  m_tView.setHighlight(e.getID());
+	  /*if (m_iView == null)
+	    {
+	    m_iView = new VisualizePanel(this);
+	    m_iView.setSize(400, 300);
+	    }*/
+	  m_focus = m_top.getNode(e.getID());
+	  m_iView.setInstances(m_focus.m_training);
+	  if (m_focus.m_attrib1 >= 0) {
+	    m_iView.setXIndex(m_focus.m_attrib1);
+	  }
+	  if (m_focus.m_attrib2 >= 0) {
+	    m_iView.setYIndex(m_focus.m_attrib2);
+	  }
+	  m_iView.setColourIndex(m_focus.m_training.classIndex());
+	  if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
+	       elementAt(0)).intValue() != LEAF) {
+	    m_iView.setShapes(m_focus.m_ranges);
+	  }
+	  //m_iView.setSIndex(2);
+	}
+      }
+      else if (e.getCommand() == TreeDisplayEvent.REMOVE_CHILDREN) {
+	/*if (m_iView == null)
+	  {
+	  m_iView = new VisualizePanel(this);
+	  m_iView.setSize(400, 300);
+	  }*/
+	m_focus = m_top.getNode(e.getID());
+	m_iView.setInstances(m_focus.m_training);
+	if (m_focus.m_attrib1 >= 0) {
+	  m_iView.setXIndex(m_focus.m_attrib1);
+	}
+	if (m_focus.m_attrib2 >= 0) {
+	  m_iView.setYIndex(m_focus.m_attrib2);
+	}
+	m_iView.setColourIndex(m_focus.m_training.classIndex());
+	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
+	     elementAt(0)).intValue() != LEAF) {
+	  m_iView.setShapes(m_focus.m_ranges);
+	}
+	//m_iView.setSIndex(2);
+	//now to remove all the stuff
+	m_focus.m_set1 = null;
+	m_focus.m_set2 = null;
+	m_focus.setInfo(m_focus.m_attrib1, m_focus.m_attrib2, null);
+	//tree_frame.getContentPane().removeAll();
+	m_tView = new TreeVisualizer(this, graph(), new PlaceNode2());
+	//tree_frame.getContentPane().add(m_tView);
+	m_reps.setComponentAt(0, m_tView);
+	//tree_frame.getContentPane().doLayout();
+	m_tView.setHighlight(m_focus.m_identity);
+      }
+      else if (e.getCommand() == TreeDisplayEvent.CLASSIFY_CHILD) {
+	/*if (m_iView == null)
+	  {
+	  m_iView = new VisualizePanel(this);
+	  m_iView.setSize(400, 300);
+	  }*/
+	m_focus = m_top.getNode(e.getID());
+	m_iView.setInstances(m_focus.m_training);
+	if (m_focus.m_attrib1 >= 0) {
+	  m_iView.setXIndex(m_focus.m_attrib1);
+	}
+	if (m_focus.m_attrib2 >= 0) {
+	  m_iView.setYIndex(m_focus.m_attrib2);
+	}
+	m_iView.setColourIndex(m_focus.m_training.classIndex());
+	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
+	     elementAt(0)).intValue() != LEAF) {
+	  m_iView.setShapes(m_focus.m_ranges);
+	}
+	
+	Classifier classifierAtNode = m_focus.getClassifier();
+        if (classifierAtNode != null) {
+          m_classifiers.setValue(classifierAtNode);
+        }
+	m_propertyDialog = new PropertyDialog((Frame) null, m_classifiers, 
+					      m_mainWin.getLocationOnScreen().x,
+					      m_mainWin.getLocationOnScreen().y);
+	m_propertyDialog.setVisible(true);
+	
+	//note property dialog may change all the time
+	//but the generic editor which has the listeners does not
+	//so at the construction of the editor is when I am going to add
+	//the listeners.
+	
+	
+	
+	//focus.setClassifier(new IB1());
+	//tree_frame.getContentPane().removeAll();
+	//////m_tView = new Displayer(this, graph(), new PlaceNode2());
+	//tree_frame.getContentPane().add(m_tView);
+	//tree_frame.getContentPane().doLayout();
+	/////////////reps.setComponentAt(0, m_tView);
+	m_tView.setHighlight(m_focus.m_identity);
+      }
+      /*else if (e.getCommand() == e.SEND_INSTANCES) {
+	TreeClass source = m_top.getNode(e.getID());
+	m_iView.setExtInstances(source.m_training);
+	}*/
+      else if (e.getCommand() == TreeDisplayEvent.ACCEPT) {
+	
+	int well = JOptionPane.showConfirmDialog(m_mainWin, 
+						 "Are You Sure...\n"
+						 + "Click Yes To Accept The"
+						 + " Tree" 
+						 + "\n Click No To Return",
+						 "Accept Tree", 
+						 JOptionPane.YES_NO_OPTION);
+	
+	if (well == 0) {
+	  m_mainWin.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+	  m_mainWin.dispose();
+	  blocker(false);  //release the thread waiting at blocker to 
+	  //continue.
+	}
+	
+      }
+    } catch(Exception er) {
+      System.out.println("Error : " + er);
+      System.out.println("Part of user input so had to catch here");
+      er.printStackTrace();
+    }
+  }
+
+  /**
+   * This receives shapes from the data view. 
+   * It then enters these shapes into the decision tree structure. 
+   * @param e Contains the shapes, and other info.
+   */
+  public void userDataEvent(VisualizePanelEvent e) {
+    
+    if (m_propertyDialog != null) {
+      m_propertyDialog.dispose();
+      m_propertyDialog = null;
+    }
+    
+    try {
+      if (m_focus != null) {
+	
+
+	double wdom = e.getInstances1().numInstances() 
+	  + e.getInstances2().numInstances();
+	if (wdom == 0) {
+	  wdom = 1;
+	}
+	
+	TreeClass tmp = m_focus;
+	m_focus.m_set1 = new TreeClass(null, e.getAttribute1(), 
+				       e.getAttribute2(), m_nextId, 
+				       e.getInstances1().numInstances() / wdom,
+ 				       e.getInstances1(), m_focus);
+	
+	m_focus.m_set2 = new TreeClass(null, e.getAttribute1(), 
+				       e.getAttribute2(), m_nextId, 
+				       e.getInstances2().numInstances() / wdom,
+				       e.getInstances2(), m_focus); 
+	//this needs the other instance
+	
+	
+	//tree_frame.getContentPane().removeAll();  
+	m_focus.setInfo(e.getAttribute1(), e.getAttribute2(), e.getValues());
+	//System.out.println(graph());
+	m_tView = new TreeVisualizer(this, graph(), new PlaceNode2());
+	//tree_frame.getContentPane().add(m_tView);
+	//tree_frame.getContentPane().doLayout();
+	m_reps.setComponentAt(0, m_tView);
+	
+	m_focus = m_focus.m_set2;
+	m_tView.setHighlight(m_focus.m_identity);
+	m_iView.setInstances(m_focus.m_training);
+	if (tmp.m_attrib1 >= 0) {
+	  m_iView.setXIndex(tmp.m_attrib1);
+	}
+	if (tmp.m_attrib2 >= 0) {
+	  m_iView.setYIndex(tmp.m_attrib2);
+	}
+	m_iView.setColourIndex(m_focus.m_training.classIndex());
+	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
+	     elementAt(0)).intValue() != LEAF) {
+	  m_iView.setShapes(m_focus.m_ranges);
+	}
+	//m_iView.setSIndex(2);
+      }
+      else {
+	System.out.println("Somehow the focus is null");
+      }
+    } catch(Exception er) {
+      System.out.println("Error : " + er);
+      System.out.println("Part of user input so had to catch here");
+      //er.printStackTrace();
+    }
+    
+  }
+  
+  /** 
+   * Constructor
+   */
+  public UserClassifier() {
+    //do nothing here except set alot of variables to default values
+    m_top = null;
+    m_tView = null;
+    m_iView = null;
+    m_nextId = 0; 
+    
+  }
+  
+ /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * @return A string formatted with a dotty representation of the decision
+   * tree.
+   * @throws Exception if String can't be built properly.
+   */
+  public String graph() throws Exception {
+    //create a dotty rep of the tree from here
+    StringBuffer text = new StringBuffer();
+    text.append("digraph UserClassifierTree {\n" +
+		"node [fontsize=10]\n" +
+		"edge [fontsize=10 style=bold]\n");
+    
+    m_top.toDotty(text);
+    return text.toString() +"}\n";
+    
+    
+  }
+  
+  /**
+   * A function used to stop the code that called buildclassifier
+   * from continuing on before the user has finished the decision tree.
+   * @param tf True to stop the thread, False to release the thread that is
+   * waiting there (if one).
+   */
+  private synchronized void blocker(boolean tf) {
+    if (tf) {
+      try {
+	wait();
+      } catch(InterruptedException e) {
+      }
+    }
+    else {
+      notifyAll();
+    }
+    
+    //System.out.println("out");
+  }
+
+  /**
+   * This will return a string describing the classifier.
+   * @return The string.
+   */
+  public String globalInfo() {
+
+    return "Interactively classify through visual means."
+      + " You are Presented with a scatter graph of the data against two user"
+      + " selectable attributes, as well as a view of the decision tree."
+      + " You can create binary splits by creating polygons around data"
+      + " plotted on the scatter graph, as well as by allowing another"
+      + " classifier to take over at points in the decision tree should you"
+      + " see fit.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Malcolm Ware and Eibe Frank and Geoffrey Holmes and Mark Hall and Ian H. Witten");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.TITLE, "Interactive machine learning: letting users build classifiers");
+    result.setValue(Field.JOURNAL, "Int. J. Hum.-Comput. Stud.");
+    result.setValue(Field.VOLUME, "55");
+    result.setValue(Field.NUMBER, "3");
+    result.setValue(Field.PAGES, "281-292");
+    result.setValue(Field.PS, "http://www.cs.waikato.ac.nz/~ml/publications/2000/00MW-etal-Interactive-ML.ps");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Call this function to build a decision tree for the training
+   * data provided.
+   * @param i The training data.
+   * @throws Exception if can't build classification properly.
+   */
+  public void buildClassifier(Instances i) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(i);
+
+    // remove instances with missing class
+    i = new Instances(i);
+    i.deleteWithMissingClass();
+    
+    //construct a visualizer
+    //construct a tree displayer and feed both then do nothing
+    //note that I will display at the bottom of each split how many 
+    //fall into each catagory
+    
+    m_classifiers = new GenericObjectEditor(true);
+    m_classifiers.setClassType(Classifier.class);
+    m_classifiers.setValue(new weka.classifiers.rules.ZeroR());
+    
+    ((GenericObjectEditor.GOEPanel)m_classifiers.getCustomEditor())
+      .addOkListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    //I want to use the focus variable but to trust it I need
+	    //to kill the window if anything gets changed by either
+	    //editor
+	    try {
+	      m_focus.m_set1 = null;
+	      m_focus.m_set2 = null;
+	      m_focus.setInfo(m_focus.m_attrib1, m_focus.m_attrib2, null);
+	      m_focus.setClassifier((Classifier)m_classifiers.getValue());
+              /*	      m_classifiers = new GenericObjectEditor();
+	      m_classifiers.setClassType(Classifier.class);
+	      m_classifiers.setValue(new weka.classifiers.rules.ZeroR());
+	      ((GenericObjectEditor.GOEPanel)m_classifiers.getCustomEditor())
+              .addOkListener(this); */
+	      m_tView = new TreeVisualizer(UserClassifier.this, graph(), 
+				     new PlaceNode2());
+	      m_tView.setHighlight(m_focus.m_identity);
+	      m_reps.setComponentAt(0, m_tView);
+	      m_iView.setShapes(null);
+	    } catch(Exception er) {
+	      System.out.println("Error : " + er);
+	      System.out.println("Part of user input so had to catch here");
+              JOptionPane.showMessageDialog(
+                         null,
+                         "Unable to use " + m_focus.getClassifier().getClass().getName() 
+                         + " at this node.\n"
+                         + "This exception was produced:\n"
+                         + er.toString(),
+                         "UserClassifier",
+                         JOptionPane.ERROR_MESSAGE);
+	    }
+	  }
+	});
+    
+    m_built = false;
+    m_mainWin = new JFrame();
+    
+    m_mainWin.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  int well = JOptionPane.showConfirmDialog(m_mainWin, 
+						   "Are You Sure...\n"
+						   + "Click Yes To Accept"
+						   + " The Tree" 
+						   + "\n Click No To Return",
+						   "Accept Tree", 
+						   JOptionPane.YES_NO_OPTION);
+	  
+	  if (well == 0) {
+	    m_mainWin.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+	    blocker(false);
+	    
+	  }
+	  else {
+	    m_mainWin.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+	  }
+	}
+      });
+    
+    m_reps = new JTabbedPane();
+    m_mainWin.getContentPane().add(m_reps);
+    
+    //make a backup of the instances so that any changes don't go past here.
+    Instances te = new Instances(i, i.numInstances());
+    for (int noa = 0; noa < i.numInstances(); noa++) {
+      te.add(i.instance(noa));
+    }
+    
+    te.deleteWithMissingClass(); //remove all instances with a missing class
+    //from training
+    
+    m_top = new TreeClass(null, 0, 0, m_nextId, 1, te, null);
+    m_focus = m_top;
+    //System.out.println(graph());
+    m_tView = new TreeVisualizer(this, graph(), new PlaceNode1());
+    
+    m_reps.add("Tree Visualizer", m_tView);
+    //tree_frame = new JFrame();
+    //tree_frame.getContentPane().add(m_tView);
+    //tree_frame.setSize(800,600);
+    //tree_frame.setVisible(true);
+    
+    m_tView.setHighlight(m_top.m_identity);
+    m_iView = new VisualizePanel(this);
+    //m_iView.setSize(400, 300);
+    m_iView.setInstances(m_top.m_training);
+    m_iView.setColourIndex(te.classIndex());
+    //vis_frame = new JFrame();
+    //vis_frame.getContentPane().add(m_iView);
+    //vis_frame.setSize(400, 300);
+    //vis_frame.setVisible(true);
+    m_reps.add("Data Visualizer", m_iView);
+    m_mainWin.setSize(560, 420);
+    m_mainWin.setVisible(true);
+    blocker(true);          //a call so that the main thread of 
+    //execution has to wait for the all clear message from the user.
+    
+    //so that it can be garbage 
+    if (m_propertyDialog != null) {
+      m_propertyDialog.dispose();
+      m_propertyDialog = null;
+    }
+    
+    //collected
+    m_classifiers = null;
+    m_built = true;
+  }
+
+  /**
+   * Call this function to get a double array filled with the probability
+   * of how likely each class type is the class of the instance.
+   * @param i The instance to classify.
+   * @return A double array filled with the probalities of each class type.
+   * @throws Exception if can't classify instance.
+   */
+  public double[] distributionForInstance(Instance i) throws Exception {
+
+    if (!m_built) {
+      return null;
+    }
+    
+    double[] res = m_top.calcClassType(i);
+    if (m_top.m_training.classAttribute().isNumeric()) {
+      return res;
+    }
+
+    double most_likely = 0, highest = -1;
+    double count = 0;
+    for (int noa = 0; noa < m_top.m_training.numClasses(); noa++) {
+      count += res[noa];
+      if (res[noa] > highest) {
+	most_likely = noa;
+	highest = res[noa];
+      }
+    }
+    
+    if (count <= 0) {
+      //not sure how this happened.
+      return null;
+    }
+
+    for (int noa = 0; noa < m_top.m_training.numClasses(); noa++) {
+      res[noa] = res[noa] / count;
+    }
+    //System.out.println("ret");
+    
+    return res;
+  }
+  
+  /**
+   * Inner class used to represent the actual decision tree structure and data.
+   */
+  private class TreeClass 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 595663560871347434L;
+    
+    /**
+     * This contains the info for the coords of the shape converted 
+     * to attrib coords, 
+     * for polygon the first attrib is the number of points, 
+     * This is not more object oriented because that would 
+     * be over kill.
+     */
+    public FastVector m_ranges;
+
+    /** the first attribute */
+    public int m_attrib1;
+    
+    /** the second attribute */
+    public int m_attrib2;
+    
+    public TreeClass m_set1;
+    public TreeClass m_set2;
+
+    /** the parent */
+    public TreeClass m_parent;
+
+    /** A string to uniquely identify this node. */
+    public String m_identity;
+    
+    /** the weight of this node */
+    public double m_weight;
+    
+    /** the training instances for this node */
+    public Instances m_training;
+    
+    /** Used instead of the standard leaf if one exists. */
+    public Classifier m_classObject;
+
+    /** Used on the instances while classifying if one exists. */
+    public Filter m_filter;
+    
+    /**
+     * Constructs a TreeClass node  with all the important information.
+     * @param r A FastVector containing the shapes, null if it's a leaf node.
+     * @param a1 The first attribute.
+     * @param a2 The second attribute.
+     * @param id The unique id number for this node.
+     * @param w The weight of this node.
+     * @param i The instances that make it to this node from the training data.
+     * @param p the parent
+     * @throws Exception if can't use 'i' properly.
+     */
+    public TreeClass(FastVector r, int a1, int a2, int id, double w, 
+		     Instances i, TreeClass p) throws Exception {
+      m_set1 = null;
+      m_set2 = null;
+      m_ranges = r;
+      m_classObject = null;
+      m_filter = null;
+      m_training = i;
+      m_attrib1 = a1;
+      m_attrib2 = a2;
+      m_identity = "N" + String.valueOf(id);
+      m_weight = w;
+      m_parent = p;
+      m_nextId++;
+      if (m_ranges == null) {
+	
+	setLeaf();
+	//this will fill the ranges array with the 
+	//number of times each class type occurs for the instances.
+	/*m_ranges = new FastVector(1);
+	  m_ranges.addElement(new FastVector(i.numClasses() + 1));
+	  FastVector tmp = (FastVector)m_ranges.elementAt(0);
+	  tmp.addElement(new Double(0));
+	  for (int noa = 0; noa < i.numClasses(); noa++) {
+	  tmp.addElement(new Double(0));
+	  }
+	  for (int noa = 0; noa < i.numInstances(); noa++) {
+	  tmp.setElementAt(new Double(((Double)tmp.elementAt
+	  ((int)i.instance(noa).
+	  classValue() + 1)).doubleValue() + 
+	  i.instance(noa).weight()),
+	  (int)i.instance(noa).classValue() + 1);  
+	  //this gets the current class value and alters it and replaces it
+	  }*/
+      }     
+    }
+    
+    /**
+     * Call this to set an alternate classifier For this node.
+     * @param c The alternative classifier to use.
+     * @throws Exception if alternate classifier can't build classification.
+     */
+    public void setClassifier(Classifier c) throws Exception {
+      m_classObject = c;
+      m_classObject.buildClassifier(m_training);
+    }
+
+    /**
+     * Get the alternate classifier at this node. Returns null if there is
+     * no classifier.
+     *
+     * @return the alternate classifier at this node, or null if there is none.
+     */
+    public Classifier getClassifier() {
+      return m_classObject;
+    }
+    
+    /**
+     * Call this to set this node with different information to what
+     * it was created with.
+     * @param at1 The first attribute.
+     * @param at2 The second attribute.
+     * @param ar The shapes at this node, null if leaf node, or 
+     * alternate classifier.
+     * @throws Exception if leaf node and cant't create leaf info.
+     */
+    public void setInfo(int at1, int at2, FastVector ar) throws Exception {
+      m_classObject = null;
+      m_filter = null;
+      m_attrib1 = at1;
+      m_attrib2 = at2;
+      m_ranges = ar;
+      
+      //FastVector tmp;
+      if (m_ranges == null) {
+	setLeaf();
+	/*
+	//this will fill the ranges array with the number of times 
+	//each class type occurs for the instances.
+	  if (m_training != null) {
+	    m_ranges = new FastVector(1);
+	    m_ranges.addElement(new FastVector(m_training.numClasses() + 1));
+	    tmp = (FastVector)m_ranges.elementAt(0);
+	    tmp.addElement(new Double(0));
+	    for (int noa = 0; noa < m_training.numClasses(); noa++) {
+	      tmp.addElement(new Double(0));
+	    }
+	    for (int noa = 0; noa < m_training.numInstances(); noa++) {
+	      tmp.setElementAt(new Double(((Double)tmp.elementAt
+					   ((int)m_training.instance(noa).
+					    classValue() + 1)).doubleValue() + 
+					  m_training.instance(noa).weight()), 
+			       (int)m_training.instance(noa).classValue() + 1);
+	      //this gets the current class val and alters it and replaces it
+	      }
+	      }*/
+      }
+    }
+    
+    /**
+     * This sets up the informtion about this node such as the s.d or the
+     * number of each class.
+     * @throws Exception if problem with training instances.
+     */
+    private void setLeaf() throws Exception {
+      //this will fill the ranges array with the number of times 
+      //each class type occurs for the instances.
+      //System.out.println("ihere");
+      if (m_training != null ) {
+	
+	if (m_training.classAttribute().isNominal()) {
+	  FastVector tmp;
+	  
+	  //System.out.println("ehlpe");
+	  m_ranges = new FastVector(1);
+	  m_ranges.addElement(new FastVector(m_training.numClasses() + 1));
+	  tmp = (FastVector)m_ranges.elementAt(0);
+	  tmp.addElement(new Double(0));
+	  for (int noa = 0; noa < m_training.numClasses(); noa++) {
+	    tmp.addElement(new Double(0));
+	  }
+	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
+	    tmp.setElementAt(new Double(((Double)tmp.elementAt
+					 ((int)m_training.instance(noa).
+					  classValue() + 1)).doubleValue() + 
+					m_training.instance(noa).weight()), 
+			     (int)m_training.instance(noa).classValue() + 1);
+	    //this gets the current class val and alters it and replaces it
+	  }
+	}
+	else {
+	  //then calc the standard deviation.
+	  m_ranges = new FastVector(1);
+	  double t1 = 0;
+	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
+	    t1 += m_training.instance(noa).classValue();
+	  }
+	  
+	  if (m_training.numInstances() != 0) {
+	    t1 /= m_training.numInstances();
+	  }
+	  double t2 = 0;
+	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
+	    t2 += Math.pow(m_training.instance(noa).classValue() - t1, 2);
+	  }
+	  FastVector tmp;
+	  if (m_training.numInstances() != 0) {
+	    t1 = Math.sqrt(t2 / m_training.numInstances());
+	    m_ranges.addElement(new FastVector(2));
+	    tmp = (FastVector)m_ranges.elementAt(0);
+	    tmp.addElement(new Double(0));
+	    tmp.addElement(new Double(t1));
+	  }
+	  else {
+	    m_ranges.addElement(new FastVector(2));
+	    tmp = (FastVector)m_ranges.elementAt(0);
+	    tmp.addElement(new Double(0));
+	    tmp.addElement(new Double(Double.NaN));
+	  }
+	}
+      }
+    }
+
+    /**
+     * This will recursively go through the tree and return inside the 
+     * array the weightings of each of the class types
+     * for this instance. Note that this function returns an otherwise 
+     * unreferenced double array so there are no worry's about
+     * making changes.
+     *
+     * @param i The instance to test
+     * @return A double array containing the results.
+     * @throws Exception if can't use instance i properly.
+     */
+    public double[] calcClassType(Instance i) throws Exception {
+      //note that it will be the same calcs for both numeric and nominal
+      //attrib types.
+      //note the weightings for returning stuff will need to be modified 
+      //to work properly but will do for now.
+      double x = 0, y = 0;
+      if (m_attrib1 >= 0) {
+	x = i.value(m_attrib1);
+      }
+      if (m_attrib2 >= 0) {
+	y = i.value(m_attrib2);
+      }
+      double[] rt;
+      if (m_training.classAttribute().isNominal()) {
+	rt = new double[m_training.numClasses()];
+      }
+      else {
+	rt = new double[1];
+      }
+
+      FastVector tmp;
+      if (m_classObject != null) {
+	//then use the classifier.
+	if (m_training.classAttribute().isNominal()) {
+	  rt[(int)m_classObject.classifyInstance(i)] = 1;
+	}
+	else {
+	  if (m_filter != null) {
+	    m_filter.input(i);
+	    rt[0] = m_classObject.classifyInstance(m_filter.output());
+	  }
+	  else {
+	    rt[0] = m_classObject.classifyInstance(i);
+	  }
+	}
+	//System.out.println("j48");
+	return rt;
+      }
+      else if (((Double)((FastVector)m_ranges.elementAt(0)).
+		elementAt(0)).intValue() == LEAF) {
+	//System.out.println("leaf");
+	//then this is a leaf
+	//rt = new double[m_training.numClasses()];
+	
+	if (m_training.classAttribute().isNumeric()) {
+	 
+	  setLinear();
+	  m_filter.input(i);
+	  rt[0] = m_classObject.classifyInstance(m_filter.output());
+	  return rt;
+	}
+	
+	int totaler = 0;
+	tmp = (FastVector)m_ranges.elementAt(0);
+	for (int noa = 0; noa < m_training.numClasses();noa++) {
+	  rt[noa] = ((Double)tmp.elementAt(noa + 1)).doubleValue();
+	  totaler += rt[noa];
+	}
+	for (int noa = 0; noa < m_training.numClasses(); noa++) {
+	  rt[noa] = rt[noa] / totaler;
+	}
+	return rt;
+      }
+      
+      for (int noa = 0; noa < m_ranges.size(); noa++) {
+	
+	tmp = (FastVector)m_ranges.elementAt(noa);
+	
+	if (((Double)tmp.elementAt(0)).intValue() 
+	    == VLINE && !Utils.isMissingValue(x)) {
+	  
+	}
+	else if (((Double)tmp.elementAt(0)).intValue() 
+		 == HLINE && !Utils.isMissingValue(y)) {
+	  
+	}
+	else if (Utils.isMissingValue(x) || Utils.isMissingValue(y)) {
+	  //System.out.println("miss");
+	  //then go down both branches using their weights
+	  rt = m_set1.calcClassType(i);
+	  double[] tem = m_set2.calcClassType(i);
+	  if (m_training.classAttribute().isNominal()) {
+	    for (int nob = 0; nob < m_training.numClasses(); nob++) {
+	      rt[nob] *= m_set1.m_weight;
+	      rt[nob] += tem[nob] * m_set2.m_weight;
+	    }
+	  }
+	  else {
+	    rt[0] *= m_set1.m_weight;
+	    rt[0] += tem[0] * m_set2.m_weight;
+	  }
+	  return rt;
+	}
+	else if (((Double)tmp.elementAt(0)).intValue() == RECTANGLE) {
+	  //System.out.println("RECT");
+	  if (x >= ((Double)tmp.elementAt(1)).doubleValue() && 
+	      x <= ((Double)tmp.elementAt(3)).doubleValue() && 
+	      y <= ((Double)tmp.elementAt(2)).doubleValue() && 
+	      y >= ((Double)tmp.elementAt(4)).doubleValue()) {
+	    //then falls inside the rectangle
+	    //System.out.println("true");
+	    rt = m_set1.calcClassType(i);
+	    return rt;
+	  }
+	  
+	}
+	else if (((Double)tmp.elementAt(0)).intValue() == POLYGON) {
+	  if (inPoly(tmp, x, y)) {
+	    rt = m_set1.calcClassType(i);
+	    return rt;
+	  }
+	}
+	else if (((Double)tmp.elementAt(0)).intValue() == POLYLINE) {
+	  if (inPolyline(tmp, x, y)) {
+	    rt = m_set1.calcClassType(i);
+	    return rt;
+	  }
+	}
+      }
+      //is outside the split
+      if (m_set2 != null) {
+	rt = m_set2.calcClassType(i);
+      }
+      return rt;
+    }
+    
+    /**
+     * This function gets called to set the node to use a linear regression
+     * and attribute filter.
+     * @throws Exception If can't set a default linear egression model.
+     */
+    private void setLinear() throws Exception {
+      //then set default behaviour for node.
+      //set linear regression combined with attribute filter
+      
+      //find the attributes used for splitting.
+      boolean[] attributeList = new boolean[m_training.numAttributes()];
+      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
+	attributeList[noa] = false;
+      }
+      
+      TreeClass temp = this;
+      attributeList[m_training.classIndex()] = true;
+      while (temp != null) {
+	attributeList[temp.m_attrib1] = true;
+	attributeList[temp.m_attrib2] = true;
+	temp = temp.m_parent;
+      }
+      int classind = 0;
+      
+      
+      //find the new class index
+      for (int noa = 0; noa < m_training.classIndex(); noa++) {
+	if (attributeList[noa]) {
+	  classind++;
+	}
+      }
+      //count how many attribs were used
+      int count = 0;
+      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
+	if (attributeList[noa]) {
+	  count++;
+	}
+      }
+      
+      //fill an int array with the numbers of those attribs
+      int[] attributeList2 = new int[count];
+      count = 0;
+      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
+	if (attributeList[noa]) {
+	  attributeList2[count] = noa;
+	  count++;
+	}
+      }
+      
+      m_filter = new Remove();
+      ((Remove)m_filter).setInvertSelection(true);
+      ((Remove)m_filter).setAttributeIndicesArray(attributeList2);
+      m_filter.setInputFormat(m_training);
+      
+      Instances temp2 = Filter.useFilter(m_training, m_filter);
+      temp2.setClassIndex(classind);
+      m_classObject = new LinearRegression();
+      m_classObject.buildClassifier(temp2);
+    }
+    
+    /**
+     * Call to find out if an instance is in a polyline.
+     * @param ob The polyline to check.
+     * @param x The value of attribute1 to check.
+     * @param y The value of attribute2 to check.
+     * @return True if inside, false if not.
+     */
+    private boolean inPolyline(FastVector ob, double x, double y) {
+      //this works similar to the inPoly below except that
+      //the first and last lines are treated as extending infinite 
+      //in one direction and 
+      //then infinitly in the x dirction their is a line that will 
+      //normaly be infinite but
+      //can be finite in one or both directions
+      
+      int countx = 0;
+      double vecx, vecy;
+      double change;
+      double x1, y1, x2, y2;
+      
+      for (int noa = 1; noa < ob.size() - 4; noa+= 2) {
+	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
+	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
+	x1 = ((Double)ob.elementAt(noa)).doubleValue();
+	x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
+	vecy = y2 - y1;
+	vecx = x2 - x1;
+	if (noa == 1 && noa == ob.size() - 6) {
+	  //then do special test first and last edge
+	  if (vecy != 0) {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersection
+	      countx++;
+	    }
+	  }
+	}
+	else if (noa == 1) {
+	  if ((y < y2 && vecy > 0) || (y > y2 && vecy < 0)) {
+	    //now just determine intersection or not
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersection on horiz
+	      countx++;
+	    }
+	  }
+	}
+	else if (noa == ob.size() - 6) {
+	  //then do special test on last edge
+	  if ((y <= y1 && vecy < 0) || (y >= y1 && vecy > 0)) {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      countx++;
+	    }
+	  }
+	  
+	}
+	else if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
+	  //then continue tests.
+	  if (vecy == 0) {
+	    //then lines are parallel stop tests in 
+	    //ofcourse it should never make it this far
+	  }
+	  else {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersects on horiz
+	      countx++;
+	    }
+	  }
+	}
+	
+      }
+      
+      //now check for intersection with the infinity line
+      y1 = ((Double)ob.elementAt(ob.size() - 2)).doubleValue();
+      y2 = ((Double)ob.elementAt(ob.size() - 1)).doubleValue();
+      
+      if (y1 > y2) {
+	//then normal line
+	if (y1 >= y && y > y2) {
+	  countx++;
+	}
+      }
+      else {
+	//then the line segment is inverted
+	if (y1 >= y || y > y2) {
+	  countx++;
+	}
+      }
+      
+      if ((countx % 2) == 1) {
+	return true;
+      }
+      else {
+	return false;
+      }
+    }
+    
+    /** 
+     * Call this to determine if an instance is in a polygon.
+     * @param ob The polygon.
+     * @param x The value of attribute 1.
+     * @param y The value of attribute 2.
+     * @return True if in polygon, false if not.
+     */
+    private boolean inPoly(FastVector ob, double x, double y) {
+      int count = 0;
+      double vecx, vecy;
+      double change;
+      double x1, y1, x2, y2;
+      for (int noa = 1; noa < ob.size() - 2; noa += 2) {
+	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
+	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
+	if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
+	  //then continue tests.
+	  vecy = y2 - y1;
+	  if (vecy == 0) {
+	    //then lines are parallel stop tests for this line
+	  }
+	  else {
+	    x1 = ((Double)ob.elementAt(noa)).doubleValue();
+	    x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
+	    vecx = x2 - x1;
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then add to count as an intersected line
+	      count++;
+	    }
+	  }
+	  
+	}
+      }
+      if ((count % 2) == 1) {
+	//then lies inside polygon
+	//System.out.println("in");
+	return true;
+      }
+      else {
+	//System.out.println("out");
+	return false;
+      }
+      //System.out.println("WHAT?!?!?!?!!?!??!?!");
+      //return false;
+    }
+
+    /**
+     * Goes through the tree structure recursively and returns the node that
+     * has the id.
+     * @param id The node to find.
+     * @return The node that matches the id.
+     */
+    public TreeClass getNode(String id) {
+      //returns the treeclass object with the particular ident
+      if (id.equals(m_identity)) {
+	return this;
+      }
+      
+      if (m_set1 != null) {
+	TreeClass tmp = m_set1.getNode(id);
+	if (tmp != null) {
+	  return tmp;
+	}
+      }
+      if (m_set2 != null) {
+	TreeClass tmp = m_set2.getNode(id);
+	if (tmp != null) {
+	  return tmp;
+	}
+      }
+      return null;
+    }
+    
+    /**
+     * Returns a string containing a bit of information about this node, in 
+     * alternate form.
+     * @param s The string buffer to fill.
+     * @throws Exception if can't create label.
+     */
+    public void getAlternateLabel(StringBuffer s) throws Exception {
+      
+      //StringBuffer s = new StringBuffer();
+      
+      FastVector tmp = (FastVector)m_ranges.elementAt(0);
+      
+      if (m_classObject != null && m_training.classAttribute().isNominal()) {
+	s.append("Classified by " + m_classObject.getClass().getName());
+      }
+      else if (((Double)tmp.elementAt(0)).intValue() == LEAF) {
+	if (m_training.classAttribute().isNominal()) {
+	  double high = -1000;
+	  int num = 0;
+	  double count = 0;
+	  for (int noa = 0; noa < m_training.classAttribute().numValues();
+	       noa++) {
+	    if (((Double)tmp.elementAt(noa + 1)).doubleValue() > high) {
+	      high = ((Double)tmp.elementAt(noa + 1)).doubleValue();
+	      num  = noa + 1;
+	    }
+	    count += ((Double)tmp.elementAt(noa + 1)).doubleValue();
+	  }
+	  s.append(m_training.classAttribute().value(num-1) + "(" + count);
+	  if (count > high) {
+	    s.append("/" + (count - high));
+	  }
+	  s.append(")");
+	}
+	else {
+	  if (m_classObject == null 
+	      && ((Double)tmp.elementAt(0)).intValue() == LEAF) {
+	    setLinear();
+	  }
+	  s.append("Standard Deviation = " 
+		   + Utils.doubleToString(((Double)tmp.elementAt(1))
+					  .doubleValue(), 6));
+	  
+	}
+      }
+      else {
+	s.append("Split on ");
+	s.append(m_training.attribute(m_attrib1).name() + " AND ");
+	s.append(m_training.attribute(m_attrib2).name());
+	
+	
+      }
+      
+      //return s.toString();
+    }
+    
+    /**
+     * Returns a string containing a bit of information about this node.
+     * @param s The stringbuffer to fill.
+     * @throws Exception if can't create label.
+     */
+    public void getLabel(StringBuffer s) throws Exception {
+      //for now just return identity
+      //StringBuffer s = new StringBuffer();
+      
+      FastVector tmp = (FastVector)m_ranges.elementAt(0);
+      
+      
+      if (m_classObject != null && m_training.classAttribute().isNominal()) {
+	s.append("Classified by\\n" + m_classObject.getClass().getName());
+      }
+      else if (((Double)tmp.elementAt(0)).intValue() == LEAF) {
+	
+	if (m_training.classAttribute().isNominal()) {
+	  boolean first = true;
+	  for (int noa = 0; noa < m_training.classAttribute().numValues(); 
+	       noa++) {
+	    if (((Double)tmp.elementAt(noa + 1)).doubleValue() > 0) {
+	      if (first)
+		{
+		  s.append("[" + m_training.classAttribute().value(noa));
+		  first = false;
+		}
+	      else
+		{
+		  s.append("\\n[" + m_training.classAttribute().value(noa));
+		}
+	      s.append(", " + ((Double)tmp.elementAt(noa + 1)).doubleValue() 
+		       + "]");
+	    }      
+	  }
+	}
+	else {
+	  if (m_classObject == null 
+	      && ((Double)tmp.elementAt(0)).intValue() == LEAF) {
+	    setLinear();
+	  }
+	  s.append("Standard Deviation = " 
+		   + Utils.doubleToString(((Double)tmp.elementAt(1))
+		   .doubleValue(), 6));
+	}
+      }
+      else {
+	s.append("Split on\\n");
+	s.append(m_training.attribute(m_attrib1).name() + " AND\\n");
+	s.append(m_training.attribute(m_attrib2).name());
+      }
+      //return s.toString();
+    }
+
+    /**
+     * Converts The tree structure to a dotty string.
+     * @param t The stringbuffer to fill with the dotty structure.
+     * @throws Exception if can't convert structure to dotty.
+     */
+    public void toDotty(StringBuffer t) throws Exception {
+      //this will recursively create all the dotty info for the structure
+      t.append(m_identity + " [label=\"");
+      getLabel(t);
+      t.append("\" ");
+      //System.out.println(((Double)((FastVector)ranges.elementAt(0)).
+      //elementAt(0)).intValue() + " A num ");
+      if (((Double)((FastVector)m_ranges.elementAt(0)).elementAt(0)).intValue()
+	  == LEAF) {
+	t.append("shape=box ");
+      }
+      else {
+	t.append("shape=ellipse ");
+      }
+      t.append("style=filled color=gray95]\n");
+      
+      if (m_set1 != null) {
+	t.append(m_identity + "->");
+	t.append(m_set1.m_identity + " [label=\"True\"]\n");//the edge for 
+	//the left
+	m_set1.toDotty(t);
+      }
+      if (m_set2 != null) {
+	t.append(m_identity + "->");
+	t.append(m_set2.m_identity + " [label=\"False\"]\n"); //the edge for 
+	//the 
+	//right
+	m_set2.toDotty(t);
+      }
+      
+    }
+    
+    /**
+     * This will append the class Object in the tree to the string buffer.
+     * @param t The stringbuffer.
+     */
+    public void objectStrings(StringBuffer t) {
+      
+      if (m_classObject != null) {
+	t.append("\n\n" + m_identity +" {\n" + m_classObject.toString()+"\n}");
+      }
+      if (m_set1 != null) {
+	m_set1.objectStrings(t);
+      }
+      if (m_set2 != null) {
+	m_set2.objectStrings(t);
+      }
+    }
+    
+    /**
+     * Converts the tree structure to a string. for people to read.
+     * @param l How deep this node is in the tree.
+     * @param t The stringbuffer to fill with the string.
+     * @throws Exception if can't convert th string.
+     */
+    public void toString(int l, StringBuffer t) throws Exception {
+      
+      if (((Double)((FastVector)m_ranges.elementAt(0)).elementAt(0)).intValue()
+	  == LEAF) {
+	t.append(": " + m_identity + " ");
+	getAlternateLabel(t);
+      }
+      if (m_set1 != null) {
+	t.append("\n");
+	for (int noa = 0; noa < l; noa++) {
+	  t.append("|   ");
+	  
+	}
+	getAlternateLabel(t);
+	t.append(" (In Set)");
+	m_set1.toString(l+1, t);
+      }
+      if (m_set2 != null) {
+	t.append("\n");
+	for (int noa = 0; noa < l; noa++) {
+	  t.append("|   ");
+	}
+	getAlternateLabel(t);
+	t.append(" (Not in Set)");
+	m_set2.toString(l+1, t);
+      }
+      //return t.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/PredictionNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/PredictionNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/PredictionNode.java	(revision 29)
@@ -0,0 +1,185 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PredictionNode.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.adtree;
+
+import weka.classifiers.trees.ADTree;
+import weka.core.FastVector;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+/**
+ * Class representing a prediction node in an alternating tree.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public final class PredictionNode
+  implements Serializable, Cloneable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6018958856358698814L;
+
+  /** The prediction value stored in this node */
+  private double value;
+
+  /** The children of this node - any number of splitter nodes */
+  private FastVector children;
+  
+  /**
+   * Creates a new prediction node.
+   *
+   * @param newValue the value that the node should store
+   */
+  public PredictionNode(double newValue) {
+
+    value = newValue;
+    children = new FastVector();  
+  }
+
+  /**
+   * Sets the prediction value of the node.
+   *
+   * @param newValue the value that the node should store
+   */
+  public final void setValue(double newValue) {
+
+    value = newValue;
+  }
+
+  /**
+   * Gets the prediction value of the node.
+   *
+   * @return the value stored in the node
+   */
+  public final double getValue() {
+
+    return value;
+  }
+
+  /**
+   * Gets the children of this node.
+   *
+   * @return a FastVector containing child Splitter object references
+   */ 
+  public final FastVector getChildren() {
+
+    return children;
+  }
+
+  /**
+   * Enumerates the children of this node.
+   *
+   * @return an enumeration of child Splitter object references
+   */ 
+  public final Enumeration children() {
+
+    return children.elements();
+  }
+  
+  /**
+   * Adds a child to this node. If possible will merge, and will perform a deep copy
+   * of the child tree.
+   *
+   * @param newChild the new child to add (will be cloned)
+   * @param addingTo the tree that this node belongs to
+   */
+  public final void addChild(Splitter newChild, ADTree addingTo) {
+
+    // search for an equivalent child
+    Splitter oldEqual = null;
+    for (Enumeration e = children(); e.hasMoreElements(); ) {
+      Splitter split = (Splitter) e.nextElement();
+      if (newChild.equalTo(split)) { oldEqual = split; break; }
+    }
+    if (oldEqual == null) { // didn't find one so just add
+      Splitter addChild = (Splitter) newChild.clone();
+      setOrderAddedSubtree(addChild, addingTo);
+      children.addElement(addChild);
+    }
+    else { // found one, so do a merge
+      for (int i=0; i<newChild.getNumOfBranches(); i++) {
+	PredictionNode oldPred = oldEqual.getChildForBranch(i);
+	PredictionNode newPred = newChild.getChildForBranch(i);
+	if (oldPred != null && newPred != null)
+	  oldPred.merge(newPred, addingTo);
+      }
+    }
+  }
+
+  /**
+   * Clones this node. Performs a deep copy, recursing through the tree.
+   *
+   * @return a clone
+   */ 
+  public final Object clone() {
+
+    PredictionNode clone = new PredictionNode(value);
+    for (Enumeration e = children.elements(); e.hasMoreElements(); )
+      clone.children.addElement((Splitter)((Splitter) e.nextElement()).clone());
+    return clone;
+  }
+
+  /**
+   * Merges this node with another.
+   *
+   * @param merger the node that is merging with this node - will not be affected,
+   * will instead be cloned
+   * @param mergingTo the tree that this node belongs to 
+   */ 
+  public final void merge(PredictionNode merger, ADTree mergingTo) {
+
+    value += merger.value;
+    for (Enumeration e = merger.children(); e.hasMoreElements(); ) {
+      addChild((Splitter)e.nextElement(), mergingTo);
+    }
+  }
+
+  /**
+   * Sets the order added values of the subtree rooted at this splitter node.
+   *
+   * @param addChild the root of the subtree
+   * @param addingTo the tree that this node will belong to
+   */
+  private final void setOrderAddedSubtree(Splitter addChild, ADTree addingTo) {
+
+    addChild.orderAdded = addingTo.nextSplitAddedOrder();
+    for (int i=0; i<addChild.getNumOfBranches(); i++) {
+      PredictionNode node = addChild.getChildForBranch(i);
+      if (node != null)
+	for (Enumeration e = node.children(); e.hasMoreElements(); )
+	  setOrderAddedSubtree((Splitter) e.nextElement(), addingTo);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/ReferenceInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/ReferenceInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/ReferenceInstances.java	(revision 29)
@@ -0,0 +1,75 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ReferenceInstances.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.adtree;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * Simple class that extends the Instances class making it possible to create
+ * subsets of instances that reference their source set. Is used by ADTree to
+ * make reweighting of instances easy to manage.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class ReferenceInstances
+  extends Instances {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8022666381920252997L;
+
+  /**
+   * Creates an empty set of instances.
+   *
+   * @param dataset the instances to get the header information from
+   * @param capacity the initial storage capacity of the set
+   */
+  public ReferenceInstances(Instances dataset, int capacity) {
+
+    super(dataset, capacity);
+  }
+
+  /**
+   * Adds one instance reference to the end of the set. 
+   * Does not copy instance before it is added. Increases the
+   * size of the dataset if it is not large enough. Does not
+   * check if the instance is compatible with the dataset.
+   *
+   * @param instance the instance to be added
+   */
+  public final void addReference(Instance instance) {
+
+    m_Instances.add(instance);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/Splitter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/Splitter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/Splitter.java	(revision 29)
@@ -0,0 +1,122 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Splitter.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.adtree;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class representing a splitter node in an alternating tree.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public abstract class Splitter
+  implements Serializable, Cloneable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8190449848490055L;
+
+  /** The number this node was in the order of nodes added to the tree */
+  public int orderAdded;
+
+  /**
+   * Gets the number of branches of the split.
+   *
+   * @return the number of branches
+   */
+  public abstract int getNumOfBranches();
+
+  /**
+   * Gets the index of the branch that an instance applies to. Returns -1 if no branches
+   * apply.
+   *
+   * @param i the instance
+   * @return the branch index
+   */
+  public abstract int branchInstanceGoesDown(Instance i);
+
+  /**
+   * Gets the subset of instances that apply to a particluar branch of the split. If the
+   * branch index is -1, the subset will consist of those instances that don't apply to
+   * any branch.
+   *
+   * @param branch the index of the branch
+   * @param sourceInstances the instances from which to find the subset 
+   * @return the set of instances that apply
+   */
+  public abstract ReferenceInstances instancesDownBranch(int branch, Instances sourceInstances);
+
+  /**
+   * Gets the string describing the attributes the split depends on.
+   * i.e. the left hand side of the description of the split.
+   *
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the attributes
+   */
+  public abstract String attributeString(Instances dataset);
+
+  /**
+   * Gets the string describing the comparision the split depends on for a particular
+   * branch. i.e. the right hand side of the description of the split.
+   *
+   * @param branchNum the branch of the split
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the comparison
+   */
+  public abstract String comparisonString(int branchNum, Instances dataset);
+
+  /**
+   * Tests whether two splitters are equivalent.
+   *
+   * @param compare the splitter to compare with
+   * @return whether or not they match
+   */
+  public abstract boolean equalTo(Splitter compare);
+
+  /**
+   * Sets the child for a branch of the split.
+   *
+   * @param branchNum the branch to set the child for
+   * @param childPredictor the new child
+   */
+  public abstract void setChildForBranch(int branchNum, PredictionNode childPredictor);
+
+  /**
+   * Gets the child for a branch of the split.
+   *
+   * @param branchNum the branch to get the child for
+   * @return the child
+   */
+  public abstract PredictionNode getChildForBranch(int branchNum);
+
+  /**
+   * Clones this node. Performs a deep copy, recursing through the tree.
+   *
+   * @return a clone
+   */
+  public abstract Object clone();
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNominalSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNominalSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNominalSplit.java	(revision 29)
@@ -0,0 +1,214 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TwoWayNominalSplit.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.adtree;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.util.Enumeration;
+
+/**
+ * Class representing a two-way split on a nominal attribute, of the form:
+ * either 'is some_value' or 'is not some_value'.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public class TwoWayNominalSplit
+  extends Splitter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4598366190152721355L;
+
+  /** The index of the attribute the split depends on */
+  private int attIndex;
+
+  /** The attribute value that is compared against */
+  private int trueSplitValue;
+
+  /** The children of this split */
+  private PredictionNode[] children;
+
+  /**
+   * Creates a new two-way nominal splitter.
+   *
+   * @param _attIndex the index of the attribute this split depeneds on
+   * @param _trueSplitValue the attribute value that the splitter splits on
+   */
+  public TwoWayNominalSplit(int _attIndex, int _trueSplitValue) {
+
+    attIndex = _attIndex; trueSplitValue = _trueSplitValue;
+    children = new PredictionNode[2];
+  }
+
+  /**
+   * Gets the number of branches of the split.
+   *
+   * @return the number of branches (always = 2)
+   */
+  public int getNumOfBranches() { 
+  
+    return 2;
+  }
+
+  /**
+   * Gets the index of the branch that an instance applies to. Returns -1 if no branches
+   * apply.
+   *
+   * @param inst the instance
+   * @return the branch index
+   */
+  public int branchInstanceGoesDown(Instance inst) {
+    
+    if (inst.isMissing(attIndex)) return -1;
+    else if (inst.value(attIndex) == trueSplitValue) return 0;
+    else return 1;
+  }
+
+  /**
+   * Gets the subset of instances that apply to a particluar branch of the split. If the
+   * branch index is -1, the subset will consist of those instances that don't apply to
+   * any branch.
+   *
+   * @param branch the index of the branch
+   * @param instances the instances from which to find the subset 
+   * @return the set of instances that apply
+   */
+  public ReferenceInstances instancesDownBranch(int branch, Instances instances) {
+    
+    ReferenceInstances filteredInstances = new ReferenceInstances(instances, 1);
+    if (branch == -1) {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (inst.isMissing(attIndex)) filteredInstances.addReference(inst);
+      }
+    } else if (branch == 0) {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (!inst.isMissing(attIndex) && inst.value(attIndex) == trueSplitValue)
+	  filteredInstances.addReference(inst);
+      }
+    } else {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (!inst.isMissing(attIndex) && inst.value(attIndex) != trueSplitValue)
+	  filteredInstances.addReference(inst);
+      }
+    }
+    return filteredInstances;
+  }
+
+  /**
+   * Gets the string describing the attributes the split depends on.
+   * i.e. the left hand side of the description of the split.
+   *
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the attributes
+   */  
+  public String attributeString(Instances dataset) {
+    
+    return dataset.attribute(attIndex).name();
+  }
+
+  /**
+   * Gets the string describing the comparision the split depends on for a particular
+   * branch. i.e. the right hand side of the description of the split.
+   *
+   * @param branchNum the branch of the split
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the comparison
+   */
+  public String comparisonString(int branchNum, Instances dataset) {
+
+    Attribute att = dataset.attribute(attIndex);
+    if (att.numValues() != 2) 
+      return ((branchNum == 0 ? "= " : "!= ") + att.value(trueSplitValue));
+    else return ("= " + (branchNum == 0 ?
+			 att.value(trueSplitValue) :
+			 att.value(trueSplitValue == 0 ? 1 : 0)));
+  }
+
+  /**
+   * Tests whether two splitters are equivalent.
+   *
+   * @param compare the splitter to compare with
+   * @return whether or not they match
+   */
+  public boolean equalTo(Splitter compare) {
+
+    if (compare instanceof TwoWayNominalSplit) { // test object type
+      TwoWayNominalSplit compareSame = (TwoWayNominalSplit) compare;
+      return (attIndex == compareSame.attIndex &&
+	      trueSplitValue == compareSame.trueSplitValue);
+    } else return false;
+  }
+
+  /**
+   * Sets the child for a branch of the split.
+   *
+   * @param branchNum the branch to set the child for
+   * @param childPredictor the new child
+   */
+  public void setChildForBranch(int branchNum, PredictionNode childPredictor) {
+
+    children[branchNum] = childPredictor;
+  }
+
+  /**
+   * Gets the child for a branch of the split.
+   *
+   * @param branchNum the branch to get the child for
+   * @return the child
+   */
+  public PredictionNode getChildForBranch(int branchNum) {
+
+    return children[branchNum];
+  }
+
+  /**
+   * Clones this node. Performs a deep copy, recursing through the tree.
+   *
+   * @return a clone
+   */
+  public Object clone() {
+
+    TwoWayNominalSplit clone = new TwoWayNominalSplit(attIndex, trueSplitValue);
+    clone.orderAdded = orderAdded;
+    if (children[0] != null)
+      clone.setChildForBranch(0, (PredictionNode) children[0].clone());
+    if (children[1] != null)
+      clone.setChildForBranch(1, (PredictionNode) children[1].clone());
+    return clone;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNumericSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNumericSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/adtree/TwoWayNumericSplit.java	(revision 29)
@@ -0,0 +1,210 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TwoWayNumericSplit.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.adtree;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class representing a two-way split on a numeric attribute, of the form:
+ * either 'is < some_value' or 'is >= some_value'.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public class TwoWayNumericSplit
+  extends Splitter {
+
+  /** for serialization */
+  private static final long serialVersionUID = 449769177903158283L;
+
+  /** The index of the attribute the split depends on */
+  private int attIndex;
+
+  /** The attribute value that is compared against */
+  private double splitPoint;
+
+  /** The children of this split */
+  private PredictionNode[] children;
+
+  /**
+   * Creates a new two-way numeric splitter.
+   *
+   * @param _attIndex the index of the attribute this split depeneds on
+   * @param _splitPoint the attribute value that the splitter splits on
+   */
+  public TwoWayNumericSplit(int _attIndex, double _splitPoint) {
+
+    attIndex = _attIndex;
+    splitPoint = _splitPoint;
+    children = new PredictionNode[2];
+  }
+  
+  /**
+   * Gets the number of branches of the split.
+   *
+   * @return the number of branches (always = 2)
+   */
+  public int getNumOfBranches() { 
+    
+    return 2;
+  }
+
+  /**
+   * Gets the index of the branch that an instance applies to. Returns -1 if no branches
+   * apply.
+   *
+   * @param inst the instance
+   * @return the branch index
+   */
+  public int branchInstanceGoesDown(Instance inst) {
+    
+    if (inst.isMissing(attIndex)) return -1;
+    else if (inst.value(attIndex) < splitPoint) return 0;
+    else return 1;
+  }
+
+  /**
+   * Gets the subset of instances that apply to a particluar branch of the split. If the
+   * branch index is -1, the subset will consist of those instances that don't apply to
+   * any branch.
+   *
+   * @param branch the index of the branch
+   * @param instances the instances from which to find the subset 
+   * @return the set of instances that apply
+   */
+  public ReferenceInstances instancesDownBranch(int branch, Instances instances) {
+    
+    ReferenceInstances filteredInstances = new ReferenceInstances(instances, 1);
+    if (branch == -1) {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (inst.isMissing(attIndex)) filteredInstances.addReference(inst);
+      }
+    } else if (branch == 0) {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (!inst.isMissing(attIndex) && inst.value(attIndex) < splitPoint)
+	  filteredInstances.addReference(inst);
+      }
+    } else {
+      for (Enumeration e = instances.enumerateInstances(); e.hasMoreElements(); ) {
+	Instance inst = (Instance) e.nextElement();
+	if (!inst.isMissing(attIndex) && inst.value(attIndex) >= splitPoint)
+	  filteredInstances.addReference(inst);
+      }
+    }
+    return filteredInstances;
+  }
+  
+  /**
+   * Gets the string describing the attributes the split depends on.
+   * i.e. the left hand side of the description of the split.
+   *
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the attributes
+   */  
+  public String attributeString(Instances dataset) {
+  
+    return dataset.attribute(attIndex).name();
+  }
+
+  /**
+   * Gets the string describing the comparision the split depends on for a particular
+   * branch. i.e. the right hand side of the description of the split.
+   *
+   * @param branchNum the branch of the split
+   * @param dataset the dataset that the split is based on
+   * @return a string describing the comparison
+   */
+  public String comparisonString(int branchNum, Instances dataset) {
+    
+    return ((branchNum == 0 ? "< " : ">= ") + Utils.doubleToString(splitPoint, 3));
+  }
+
+  /**
+   * Tests whether two splitters are equivalent.
+   *
+   * @param compare the splitter to compare with
+   * @return whether or not they match
+   */
+  public boolean equalTo(Splitter compare) {
+    
+    if (compare instanceof TwoWayNumericSplit) { // test object type
+      TwoWayNumericSplit compareSame = (TwoWayNumericSplit) compare;
+      return (attIndex == compareSame.attIndex &&
+	      splitPoint == compareSame.splitPoint);
+    } else return false;
+  }
+
+  /**
+   * Sets the child for a branch of the split.
+   *
+   * @param branchNum the branch to set the child for
+   * @param childPredictor the new child
+   */
+  public void setChildForBranch(int branchNum, PredictionNode childPredictor) {
+    
+    children[branchNum] = childPredictor;
+  }
+
+  /**
+   * Gets the child for a branch of the split.
+   *
+   * @param branchNum the branch to get the child for
+   * @return the child
+   */
+  public PredictionNode getChildForBranch(int branchNum) {
+    
+    return children[branchNum];
+  }
+
+  /**
+   * Clones this node. Performs a deep copy, recursing through the tree.
+   *
+   * @return a clone
+   */
+  public Object clone() {
+    
+    TwoWayNumericSplit clone = new TwoWayNumericSplit(attIndex, splitPoint);
+    clone.orderAdded = orderAdded;
+    if (children[0] != null)
+      clone.setChildForBranch(0, (PredictionNode) children[0].clone());
+    if (children[1] != null)
+      clone.setChildForBranch(1, (PredictionNode) children[1].clone());
+    return clone;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.6 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTInnerNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTInnerNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTInnerNode.java	(revision 29)
@@ -0,0 +1,295 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FTInnerNode.java
+ *    Copyright (C) 2007 University of Porto, Porto, Portugal
+ *
+ */
+
+package weka.classifiers.trees.ft;
+
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.C45Split;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for Functional Inner tree structure. 
+ * 
+ * @author Jo\~{a}o Gama
+ * @author Carlos Ferreira
+ *
+ * @version $Revision: 6088 $
+ */
+public class FTInnerNode 
+  extends FTtree {   
+     
+  /** for serialization. */
+  private static final long serialVersionUID = -1125334488640233181L;
+
+  /**
+   * Constructor for Functional Inner tree node. 
+   *
+   * @param errorOnProbabilities Use error on probabilities for stopping criterion of LogitBoost?
+   * @param numBoostingIterations sets the numBoostingIterations parameter
+   * @param minNumInstances minimum number of instances at which a node is considered for splitting
+   */
+  public FTInnerNode(boolean errorOnProbabilities,int numBoostingIterations,
+                     int minNumInstances, double weightTrimBeta, boolean useAIC) {
+    m_errorOnProbabilities = errorOnProbabilities;
+    m_fixedNumIterations = numBoostingIterations;      
+    m_minNumInstances = minNumInstances;
+    m_maxIterations = 200;
+    setWeightTrimBeta(weightTrimBeta);
+    setUseAIC(useAIC);
+  }         
+    
+  /**
+   * Method for building a Functional Inner tree (only called for the root node).
+   * Grows an initial Functional Tree.
+   *
+   * @param data the data to train with
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception{
+	
+    // add new attributes to original dataset
+    data= insertNewAttr(data); 
+	
+    //build tree using all the data
+    buildTree(data, null, data.numInstances(), 0);
+	
+  }
+
+  /**
+   * Method for building the tree structure.
+   * Builds a logistic model, splits the node and recursively builds tree for child nodes.
+   * @param data the training data passed on to this node
+   * @param higherRegressions An array of regression functions produced by LogitBoost at higher 
+   * levels in the tree. They represent a logistic regression model that is refined locally 
+   * at this node.
+   * @param totalInstanceWeight the total number of training examples
+   * @param higherNumParameters effective number of parameters in the logistic regression model built
+   * in parent nodes
+   * @throws Exception if something goes wrong
+   */
+  public void buildTree(Instances data, SimpleLinearRegression[][] higherRegressions, 
+                        double totalInstanceWeight, double higherNumParameters) throws Exception{
+
+    //save some stuff
+    m_totalInstanceWeight = totalInstanceWeight;
+    m_train = new Instances(data);
+	
+    m_train= removeExtAttributes( m_train);
+        
+    m_isLeaf = true;
+    m_sons = null;
+	
+    m_numInstances = m_train.numInstances();
+    m_numClasses = m_train.numClasses();				
+	
+    //init 
+    m_numericData = getNumericData(m_train);		  
+    m_numericDataHeader = new Instances(m_numericData, 0);
+	
+    m_regressions = initRegressions();
+    m_numRegressions = 0;
+	
+    if (higherRegressions != null) m_higherRegressions = higherRegressions;
+    else m_higherRegressions = new SimpleLinearRegression[m_numClasses][0];	
+
+    m_numHigherRegressions = m_higherRegressions[0].length;	
+        
+    m_numParameters = higherNumParameters;
+        
+    //build logistic model
+    if (m_numInstances >= m_numFoldsBoosting) {
+      if (m_fixedNumIterations > 0){
+        performBoosting(m_fixedNumIterations);
+      } else if (getUseAIC()) {
+        performBoostingInfCriterion();
+      } else {
+        performBoostingCV();
+      }
+    }
+        
+    m_numParameters += m_numRegressions;
+	
+    //only keep the simple regression functions that correspond to the selected number of LogitBoost iterations
+    m_regressions = selectRegressions(m_regressions);
+         
+    boolean grow;
+       
+    //Compute logistic probs
+    double[][] FsConst;
+    double[] probsConst;
+    int j;
+    FsConst = getFs(m_numericData);
+        
+    for (j = 0; j < data.numInstances(); j++)
+      {
+        probsConst=probs(FsConst[j]);
+        // Computes constructor error
+        if (data.instance(j).classValue()!=getConstError(probsConst)) m_constError=m_constError +1;
+        for (int i = 0; i<data.classAttribute().numValues() ; i++)
+          data.instance(j).setValue(i,probsConst[i]);
+      }
+        
+    // needed by dynamic data
+    m_modelSelection=new  C45ModelSelection(m_minNumInstances, data, true);
+    m_localModel = m_modelSelection.selectModel(data);
+    //split node if more than minNumInstances...
+    if (m_numInstances > m_minNumInstances) {
+      grow = (m_localModel.numSubsets() > 1);
+    } else {
+      grow = false;
+    }
+          
+    // logitboost uses distribution for instance
+    m_hasConstr=false;
+    m_train=data;
+    if (grow) {	
+      //create and build children of node
+      m_isLeaf = false;	    	    
+      Instances[] localInstances = m_localModel.split(data);
+      // If split attribute is a extended attribute, the node has a constructor
+      if (((C45Split)m_localModel).attIndex() >=0 && ((C45Split)m_localModel).attIndex()< data.classAttribute().numValues()) 
+        m_hasConstr=true;
+               
+      m_sons = new FTInnerNode[m_localModel.numSubsets()];
+      for (int i = 0; i < m_sons.length; i++) {
+        m_sons[i] = new FTInnerNode (m_errorOnProbabilities, m_fixedNumIterations, 
+                                     m_minNumInstances,getWeightTrimBeta(), getUseAIC());
+        m_sons[i].buildTree(localInstances[i],
+                            mergeArrays(m_regressions, m_higherRegressions), m_totalInstanceWeight, m_numParameters);		
+        localInstances[i] = null;
+      }	    
+    } 
+    else{
+      m_leafclass=m_localModel.distribution().maxClass();
+    }
+  }
+
+    
+    
+  /**
+   * Prunes a tree using C4.5 pruning procedure.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double prune() throws Exception {
+
+    double errorsLeaf;
+    double errorsTree;
+    double errorsConstModel;
+    double treeError=0;
+    int i;
+    double probBranch;
+
+    // Compute error if this Tree would be leaf without contructor
+    errorsLeaf = getEstimatedErrorsForDistribution(m_localModel.distribution());
+    if (m_isLeaf ) { 
+      return  errorsLeaf;
+    } else {
+      //Computes da error of the constructor model
+      errorsConstModel = getEtimateConstModel(m_localModel.distribution());
+      errorsTree=0;
+      for (i = 0; i < m_sons.length; i++) {
+        probBranch = m_localModel.distribution().perBag(i) /
+          m_localModel.distribution().total();
+        errorsTree += probBranch* m_sons[i].prune();
+      }
+         
+      // Decide if leaf is best choice.
+      if (Utils.smOrEq(errorsLeaf, errorsTree)) {
+        // Free son Trees
+        m_sons = null;
+        m_isLeaf = true;
+        m_hasConstr=false;
+        m_leafclass=m_localModel.distribution().maxClass();
+        // Get NoSplit Model for node.
+        m_localModel = new NoSplit(m_localModel.distribution());
+        treeError=errorsLeaf;
+
+      } else{
+        treeError=errorsTree;
+      }
+    }
+    return  treeError;
+  }
+
+  /**
+   * Returns the class probabilities for an instance given by the Functional tree.
+   * @param instance the instance
+   * @return the array of probabilities
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[] probs;
+                                               
+    //also needed for logitboost
+    if (m_isLeaf && m_hasConstr) { //leaf
+      //leaf: use majoraty class or constructor model
+      probs = modelDistributionForInstance(instance);
+    } else {
+      if (m_isLeaf && !m_hasConstr)
+        {
+          probs=new double[instance.numClasses()];
+          probs[m_leafclass]=(double)1;  
+        }else{
+               
+        probs = modelDistributionForInstance(instance);
+        //Built auxiliary split instance    
+        Instance instanceSplit=new DenseInstance(instance.numAttributes()+instance.numClasses());
+           
+        instanceSplit.setDataset(instance.dataset());
+     
+        for(int i=0; i< instance.numClasses();i++)
+          {
+            instanceSplit.dataset().insertAttributeAt( new Attribute("N"+ (instance.numClasses()-i)), 0);
+            instanceSplit.setValue(i,probs[i]);
+          }
+        for(int i=0; i< instance.numAttributes();i++)
+          instanceSplit.setValue(i+instance.numClasses(),instance.value(i));
+          
+           
+           
+        int branch = m_localModel.whichSubset(instanceSplit); //split
+        for(int i=0; i< instance.numClasses();i++)
+          instanceSplit.dataset().deleteAttributeAt(0);
+            
+        //probs = m_sons[branch].distributionForInstance(instance);
+        probs = m_sons[branch].distributionForInstance(instance);
+      }
+    }
+    return probs;	
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTLeavesNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTLeavesNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTLeavesNode.java	(revision 29)
@@ -0,0 +1,271 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FTLeavesNode.java
+ *    Copyright (C) 2007 University of Porto, Porto, Portugal
+ *
+ */
+
+package weka.classifiers.trees.ft;
+
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for Functional Leaves tree version. 
+ * 
+ * @author Jo\~{a}o Gama
+ * @author Carlos Ferreira
+ *
+ * @version $Revision: 6088 $
+ */
+public class FTLeavesNode 
+  extends FTtree {   
+
+  /** for serialization. */
+  private static final long serialVersionUID = 950601378326259315L;
+
+  /**
+   * Constructor for Functional Leaves tree node. 
+   *
+   * @param errorOnProbabilities Use error on probabilities for stopping criterion of LogitBoost?
+   * @param numBoostingIterations sets the numBoostingIterations parameter
+   * @param minNumInstances minimum number of instances at which a node is considered for splitting
+   */
+  public FTLeavesNode( boolean errorOnProbabilities, int numBoostingIterations, int minNumInstances,
+                       double weightTrimBeta, boolean useAIC) {
+    m_errorOnProbabilities = errorOnProbabilities;
+    m_fixedNumIterations = numBoostingIterations;      
+    m_minNumInstances = minNumInstances;
+    m_maxIterations = 200;
+    setWeightTrimBeta(weightTrimBeta);
+    setUseAIC(useAIC);
+  }         
+    
+  /**
+   * Method for building a Functional Leaves tree (only called for the root node).
+   * Grows an initial Functional Tree.
+   *
+   * @param data the data to train with
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception{
+	
+    buildTree(data, null, data.numInstances(), 0);
+	
+  }
+
+  /**
+   * Method for building the tree structure.
+   * Builds a logistic model, splits the node and recursively builds tree for child nodes.
+   * @param data the training data passed on to this node
+   * @param higherRegressions An array of regression functions produced by LogitBoost at higher 
+   * levels in the tree. They represent a logistic regression model that is refined locally 
+   * at this node.
+   * @param totalInstanceWeight the total number of training examples
+   * @param higherNumParameters effective number of parameters in the logistic regression model built
+   * in parent nodes
+   * @throws Exception if something goes wrong
+   */
+  public void buildTree(Instances data, SimpleLinearRegression[][] higherRegressions, 
+                        double totalInstanceWeight, double higherNumParameters) throws Exception{
+
+    //save some stuff
+    m_totalInstanceWeight = totalInstanceWeight;
+    m_train = new Instances(data);
+	
+        
+    m_isLeaf = true;
+    m_sons = null;
+	
+    m_numInstances = m_train.numInstances();
+    m_numClasses = m_train.numClasses();				
+	
+    //init 
+    m_numericData = getNumericData(m_train);		  
+    m_numericDataHeader = new Instances(m_numericData, 0);
+	
+    m_regressions = initRegressions();
+    m_numRegressions = 0;
+	
+    if (higherRegressions != null) m_higherRegressions = higherRegressions;
+    else m_higherRegressions = new SimpleLinearRegression[m_numClasses][0];	
+
+    m_numHigherRegressions = m_higherRegressions[0].length;	
+        
+    m_numParameters = higherNumParameters;
+        
+    //build logistic model
+    if (m_numInstances >= m_numFoldsBoosting) {
+      if (m_fixedNumIterations > 0){
+        performBoosting(m_fixedNumIterations);
+      } else if (getUseAIC()) {
+        performBoostingInfCriterion();
+      } else {
+        performBoostingCV();
+      }
+    }
+        
+    m_numParameters += m_numRegressions;
+	
+    //only keep the simple regression functions that correspond to the selected number of LogitBoost iterations
+    m_regressions = selectRegressions(m_regressions);
+        
+    boolean grow;
+       
+    //Compute logistic probs
+    double[][] FsConst;
+    double[] probsConst;
+    int j;
+    FsConst = getFs(m_numericData);
+        
+    for (j = 0; j < data.numInstances(); j++)
+      {
+        probsConst=probs(FsConst[j]);
+        // Computes constructor error
+        if (data.instance(j).classValue()!=getConstError(probsConst)) m_constError=m_constError +1;
+      }
+        
+    //to choose split point on the node data
+    m_modelSelection=new  C45ModelSelection(m_minNumInstances, data, true);
+    m_localModel = m_modelSelection.selectModel(data);
+       
+    //split node if more than minNumInstances...
+    if (m_numInstances > m_minNumInstances) {
+      grow = (m_localModel.numSubsets() > 1);
+    } else {
+      grow = false;
+    }
+        
+    // logitboost uses distribution for instance
+    m_hasConstr=false;
+    if (grow) {	
+      //create and build children of node
+      m_isLeaf = false;
+      Instances[] localInstances = m_localModel.split(data);
+      m_sons = new FTLeavesNode[m_localModel.numSubsets()];
+      
+      for (int i = 0; i < m_sons.length; i++) {
+        m_sons[i] = new FTLeavesNode(m_errorOnProbabilities, m_fixedNumIterations, 
+                                     m_minNumInstances,getWeightTrimBeta(), getUseAIC());
+        m_sons[i].buildTree(localInstances[i],
+                            mergeArrays(m_regressions, m_higherRegressions), m_totalInstanceWeight, m_numParameters);		
+        localInstances[i] = null;
+      }	    
+    } 
+    else{
+      m_leafclass=m_localModel.distribution().maxClass();
+
+    }
+  }
+    
+  /**
+   * Prunes a tree using C4.5 pruning procedure.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double prune() throws Exception {
+
+    double errorsLeaf;
+    double errorsTree;
+    double errorsConstModel;
+    double treeError=0;
+    int i;
+    double probBranch;
+
+    // Compute error if this Tree would be leaf without contructor
+    errorsLeaf = getEstimatedErrorsForDistribution(m_localModel.distribution());
+    if (m_isLeaf ) { 
+      return  errorsLeaf;
+    } else {
+      //Computes da error of the constructor model
+      errorsConstModel = getEtimateConstModel(m_localModel.distribution());
+      errorsTree=0;
+      for (i = 0; i < m_sons.length; i++) {
+        probBranch = m_localModel.distribution().perBag(i) /
+          m_localModel.distribution().total();
+        errorsTree += probBranch* m_sons[i].prune();
+      }
+      // Decide if leaf is best choice.
+
+      if (Utils.smOrEq(errorsLeaf, errorsTree) && Utils.smOrEq(errorsLeaf, errorsConstModel)) {
+        // Free son Trees
+        m_sons = null;
+        m_isLeaf = true;
+        m_hasConstr=false;
+        m_leafclass=m_localModel.distribution().maxClass();
+        // Get NoSplit Model for node.
+        m_localModel = new NoSplit(m_localModel.distribution());
+        treeError=errorsLeaf;
+
+      }else{
+        // Decide if Constructor is best choice.
+        if (Utils.smOrEq(errorsConstModel, errorsTree)) {
+          // Free son Trees
+          m_sons = null;
+          m_isLeaf = true;
+          m_hasConstr =true;
+          // Get NoSplit Model for node.
+          m_localModel = new NoSplit(m_localModel.distribution());
+          treeError=errorsConstModel;
+        } else
+          treeError=errorsTree;
+      }
+    }
+    return  treeError;
+  }
+
+  /**
+   * Returns the class probabilities for an instance given by the Functional Leaves tree.
+   * @param instance the instance
+   * @return the array of probabilities
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[] probs;
+       
+    if (m_isLeaf && m_hasConstr) { //leaf
+      //leaf: use majoraty class or constructor model
+      probs = modelDistributionForInstance(instance);
+    } else {
+      if (m_isLeaf && !m_hasConstr)
+        {
+          probs=new double[instance.numClasses()];
+          probs[m_leafclass]=(double)1;   
+        }else{
+                              
+        int branch = m_localModel.whichSubset(instance); //split
+        probs = m_sons[branch].distributionForInstance(instance);
+      }
+    }
+    return probs;
+	
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTNode.java	(revision 29)
@@ -0,0 +1,306 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FTNode.java
+ *    Copyright (C) 2007 University of Porto, Porto, Portugal
+ *
+ */
+
+package weka.classifiers.trees.ft;
+
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.classifiers.trees.j48.C45ModelSelection;
+import weka.classifiers.trees.j48.C45Split;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for Functional tree structure. 
+ *
+ * @author Jo\~{a}o Gama
+ * @author Carlos Ferreira
+ *
+ * @version $Revision: 6088 $
+ */
+public class FTNode 
+  extends FTtree {   
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2317688685139295063L;
+
+  /**
+   * Constructor for Functional tree node. 
+   *
+   * @param errorOnProbabilities Use error on probabilities for stopping criterion of LogitBoost?
+   * @param numBoostingIterations sets the numBoostingIterations parameter
+   * @param minNumInstances minimum number of instances at which a node is considered for splitting
+   *
+   */
+  public FTNode( boolean errorOnProbabilities, int numBoostingIterations, 
+                 int minNumInstances, double weightTrimBeta, boolean useAIC) {
+    m_errorOnProbabilities = errorOnProbabilities;
+    m_fixedNumIterations = numBoostingIterations;      
+    m_minNumInstances = minNumInstances;
+    m_maxIterations = 200;
+    setWeightTrimBeta(weightTrimBeta);
+    setUseAIC(useAIC);
+  }         
+    
+  /**
+   * Method for building a Functional tree (only called for the root node).
+   * Grows an initial Functional Tree.
+   *
+   * @param data the data to train with
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception{
+	
+    // Insert new attributes
+    data= insertNewAttr(data); 
+         
+    //build tree using all the data
+    buildTree(data, null, data.numInstances(), 0);
+	
+  }
+
+  /**
+   * Method for building the tree structure.
+   * Builds a logistic model, splits the node and recursively builds tree for child nodes.
+   * @param data the training data passed on to this node
+   * @param higherRegressions An array of regression functions produced by LogitBoost at higher 
+   * levels in the tree. They represent a logistic regression model that is refined locally 
+   * at this node.
+   * @param totalInstanceWeight the total number of training examples
+   * @param higherNumParameters effective number of parameters in the logistic regression model built
+   * in parent nodes
+   * @throws Exception if something goes wrong
+   */
+  public void buildTree(Instances data, SimpleLinearRegression[][] higherRegressions, 
+                        double totalInstanceWeight, double higherNumParameters) throws Exception{
+
+    //save some stuff
+    m_totalInstanceWeight = totalInstanceWeight;
+    m_train = new Instances(data);
+    m_train= removeExtAttributes( m_train);
+        
+    m_isLeaf = true;
+    m_sons = null;
+	
+    m_numInstances = m_train.numInstances();
+    m_numClasses = m_train.numClasses();				
+	
+    //init 
+    m_numericData = getNumericData(m_train);		  
+    m_numericDataHeader = new Instances(m_numericData, 0);
+	
+    m_regressions = initRegressions();
+    m_numRegressions = 0;
+	
+    if (higherRegressions != null) m_higherRegressions = higherRegressions;
+    else m_higherRegressions = new SimpleLinearRegression[m_numClasses][0];	
+
+    m_numHigherRegressions = m_higherRegressions[0].length;	
+        
+    m_numParameters = higherNumParameters;
+        
+    //build logistic model
+    if (m_numInstances >= m_numFoldsBoosting) {
+      if (m_fixedNumIterations > 0){
+        performBoosting(m_fixedNumIterations);
+      } else if (getUseAIC()) {
+        performBoostingInfCriterion();
+      } else {
+        performBoostingCV();
+      }
+    }
+        
+    m_numParameters += m_numRegressions;
+	
+    //only keep the simple regression functions that correspond to the selected number of LogitBoost iterations
+    m_regressions = selectRegressions(m_regressions);
+         
+    boolean grow;
+       
+    //Compute logistic probs
+    double[][] FsConst;
+    double[] probsConst;
+    int j;
+    FsConst = getFs(m_numericData);
+        
+    for (j = 0; j < data.numInstances(); j++)
+      {
+        probsConst=probs(FsConst[j]);
+        // auxiliary to compute constructor error
+        if (data.instance(j).classValue()!=getConstError(probsConst)) m_constError=m_constError +1;
+        for (int i = 0; i<data.classAttribute().numValues() ; i++)
+          data.instance(j).setValue(i,probsConst[i]);
+      } 
+  
+        
+    //needed by dynamic data
+    m_modelSelection=new  C45ModelSelection(m_minNumInstances, data, true);
+      
+    m_localModel = m_modelSelection.selectModel(data);
+       
+    //split node if more than minNumInstances...
+    if (m_numInstances > m_minNumInstances) {
+      grow = (m_localModel.numSubsets() > 1);
+    } else {
+      grow = false;
+    }
+        
+    // logitboost uses distribution for instance
+    m_hasConstr=false;
+    m_train=data;
+    if (grow) {	
+      //create and build children of node
+      m_isLeaf = false;
+      Instances[] localInstances = m_localModel.split(data);
+      // deletes extended attributes
+      if (((C45Split)m_localModel).attIndex() >=0 && ((C45Split)m_localModel).attIndex()< data.classAttribute().numValues()) 
+        m_hasConstr=true;                         
+               
+      m_sons = new FTNode[m_localModel.numSubsets()];
+      for (int i = 0; i < m_sons.length; i++) {
+        m_sons[i] = new FTNode(m_errorOnProbabilities,m_fixedNumIterations, 
+                               m_minNumInstances,getWeightTrimBeta(), getUseAIC());
+        m_sons[i].buildTree(localInstances[i],
+                            mergeArrays(m_regressions, m_higherRegressions), m_totalInstanceWeight, m_numParameters);		
+        localInstances[i] = null;
+      }	    
+    } 
+    else{
+      m_leafclass=m_localModel.distribution().maxClass();
+    }
+  }
+
+  /**
+   * Method for prunning a tree using C4.5 pruning procedure.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double prune() throws Exception {
+
+    double errorsLeaf;
+    double errorsTree;
+    double errorsConstModel;
+    double treeError=0;
+    int i;
+    double probBranch;
+
+    // Compute error if this Tree would be leaf without contructor
+    errorsLeaf = getEstimatedErrorsForDistribution(m_localModel.distribution());
+    if (m_isLeaf ) { 
+      return  errorsLeaf;
+    } else {
+      //Computes da error of the constructor model
+      errorsConstModel = getEtimateConstModel(m_localModel.distribution());
+      errorsTree=0;
+      for (i = 0; i < m_sons.length; i++) {
+        probBranch = m_localModel.distribution().perBag(i) /
+          m_localModel.distribution().total();
+        errorsTree += probBranch* m_sons[i].prune();
+      }
+      // Decide if leaf is best choice.
+
+      if (Utils.smOrEq(errorsLeaf, errorsTree) && Utils.smOrEq(errorsLeaf, errorsConstModel)) {
+        // Free son Trees
+        m_sons = null;
+        m_isLeaf = true;
+        m_hasConstr=false;
+        m_leafclass=m_localModel.distribution().maxClass();
+        // Get NoSplit Model for node.
+        m_localModel = new NoSplit(m_localModel.distribution());
+        treeError=errorsLeaf;
+
+      }else{
+        // Decide if Constructor is best choice.
+        if (Utils.smOrEq(errorsConstModel, errorsTree)) {
+          // Free son Trees
+          m_sons = null;
+          m_isLeaf = true;
+          m_hasConstr =true;
+          // Get NoSplit Model for node.
+          m_localModel = new NoSplit(m_localModel.distribution());
+          treeError=errorsConstModel;
+        } else
+          treeError=errorsTree;
+      }
+    }
+    return  treeError;
+  }
+ 
+  /**
+   * Returns the class probabilities for an instance given by the Functional Tree.
+   * @param instance the instance
+   * @return the array of probabilities
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    double[] probs;
+
+    if (m_isLeaf && m_hasConstr) { //leaf
+      //leaf: use majoraty class or constructor model
+      probs = modelDistributionForInstance(instance);
+    } else { 
+      if (m_isLeaf && !m_hasConstr)
+        {
+          probs=new double[instance.numClasses()];
+          probs[m_leafclass]=(double)1;
+        }else{
+               
+        probs = modelDistributionForInstance(instance);
+        //Built auxiliary split instance    
+        Instance instanceSplit=new DenseInstance(instance.numAttributes()+instance.numClasses());
+        instanceSplit.setDataset(instance.dataset());
+           
+        // Inserts attribute and their value
+        for(int i=0; i< instance.numClasses();i++)
+          {
+            instanceSplit.dataset().insertAttributeAt( new Attribute("N"+ (instance.numClasses()-i)), 0);
+            instanceSplit.setValue(i,probs[i]);
+          }
+        for(int i=0; i< instance.numAttributes();i++)
+          instanceSplit.setValue(i+instance.numClasses(),instance.value(i));
+           
+        //chooses best branch           
+        int branch = m_localModel.whichSubset(instanceSplit); //split
+           
+        //delete added attributes
+        for(int i=0; i< instance.numClasses();i++)
+          instanceSplit.dataset().deleteAttributeAt(0);
+            
+        probs = m_sons[branch].distributionForInstance(instance);
+      }
+    }
+    return probs;
+	
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTtree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTtree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/ft/FTtree.java	(revision 29)
@@ -0,0 +1,704 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FTNode.java
+ *    Copyright (C) 2007 University of Porto, Porto, Portugal
+ *
+ */
+
+package weka.classifiers.trees.ft;
+
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.classifiers.trees.j48.BinC45ModelSelection;
+import weka.classifiers.trees.j48.BinC45Split;
+import weka.classifiers.trees.j48.C45Split;
+import weka.classifiers.trees.j48.ClassifierSplitModel;
+import weka.classifiers.trees.j48.Distribution;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.Stats;
+import weka.classifiers.trees.lmt.LogisticBase;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+
+import java.util.Vector;
+
+/**
+ * Abstract class for Functional tree structure. 
+ * 
+ * @author Jo\~{a}o Gama
+ * @author Carlos Ferreira
+ *
+ * @version $Revision: 4899 $
+ */
+public abstract class FTtree 
+  extends LogisticBase {   
+  
+  /** for serialization */
+  static final long serialVersionUID = 1862737145870398755L;
+    
+  /** Total number of training instances. */
+  protected double m_totalInstanceWeight;
+    
+  /** Node id*/
+  protected int m_id;
+    
+  /** ID of logistic model at leaf*/
+  protected int m_leafModelNum;
+ 
+  /**minimum number of instances at which a node is considered for splitting*/
+  protected int m_minNumInstances;
+
+  /**ModelSelection object (for splitting)*/
+  protected ModelSelection m_modelSelection;     
+
+  /**Filter to convert nominal attributes to binary*/
+  protected NominalToBinary m_nominalToBinary;  
+   
+  /**Simple regression functions fit by LogitBoost at higher levels in the tree*/
+  protected SimpleLinearRegression[][] m_higherRegressions;
+    
+  /**Number of simple regression functions fit by LogitBoost at higher levels in the tree*/
+  protected int m_numHigherRegressions = 0;
+    
+  /**Number of instances at the node*/
+  protected int m_numInstances;   
+
+  /**The ClassifierSplitModel (for splitting)*/
+  protected ClassifierSplitModel m_localModel; 
+    
+  /**Auxiliary copy ClassifierSplitModel (for splitting)*/
+  protected ClassifierSplitModel m_auxLocalModel; 
+ 
+  /**Array of children of the node*/
+  protected FTtree[] m_sons; 
+   
+  /** Stores leaf class value */ 
+  protected int m_leafclass;
+    
+  /**True if node is leaf*/
+  protected boolean m_isLeaf;
+    
+  /**True if node has or splits on constructor */
+  protected boolean m_hasConstr=true;
+    
+  /** Constructor error */
+  protected double  m_constError=0;
+    
+  /** Confidence level */
+  protected float m_CF = 0.10f;  
+                       
+  /**
+   * Method for building a Functional Tree (only called for the root node).
+   * Grows an initial Functional Tree.
+   *
+   * @param data the data to train with
+   * @throws Exception if something goes wrong
+   */
+  public abstract void buildClassifier(Instances data) throws Exception;
+
+  /**
+   * Abstract method for building the tree structure.
+   * Builds a logistic model, splits the node and recursively builds tree for child nodes.
+   * @param data the training data passed on to this node
+   * @param higherRegressions An array of regression functions produced by LogitBoost at higher 
+   * levels in the tree. They represent a logistic regression model that is refined locally 
+   * at this node.
+   * @param totalInstanceWeight the total number of training examples
+   * @param higherNumParameters effective number of parameters in the logistic regression model built
+   * in parent nodes
+   * @throws Exception if something goes wrong
+   */
+  public abstract void buildTree(Instances data, SimpleLinearRegression[][] higherRegressions, 
+                                 double totalInstanceWeight, double higherNumParameters) throws Exception;
+    
+  /**
+   * Abstract Method that prunes a tree using C4.5 pruning procedure.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public abstract double prune() throws Exception; 
+ 
+  /** Inserts new attributes in current dataset or instance 
+   *
+   * @exception Exception if something goes wrong
+   */
+  protected Instances insertNewAttr(Instances data) throws Exception{
+    
+    int i;
+    for (i=0; i<data.classAttribute().numValues(); i++)
+      {
+        data.insertAttributeAt( new Attribute("N"+ i), i); 
+      }
+    return data;
+  }
+
+  /** Removes extended attributes in current dataset or instance 
+   *
+   * @exception Exception if something goes wrong
+   */
+  protected Instances removeExtAttributes(Instances  data) throws Exception{
+    
+    for (int i=0; i< data.classAttribute().numValues(); i++)
+      {
+        data.deleteAttributeAt(0);
+      }
+    return data;
+  }
+
+  /**
+   * Computes estimated errors for tree.
+   */
+  protected double getEstimatedErrors(){
+
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(m_localModel.distribution());
+    else{
+      for (i=0;i<m_sons.length;i++)
+        errors = errors+ m_sons[i].getEstimatedErrors();
+
+      return errors;
+    }
+  }
+
+  /**
+   * Computes estimated errors for one branch.
+   *
+   * @exception Exception if something goes wrong
+   */
+  protected double getEstimatedErrorsForBranch(Instances data)
+    throws Exception {
+
+    Instances [] localInstances;
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(new Distribution(data));
+    else{
+      Distribution savedDist = m_localModel.distribution();
+      m_localModel.resetDistribution(data);
+      localInstances = (Instances[])m_localModel.split(data);
+      //m_localModel.m_distribution=savedDist;
+      for (i=0;i<m_sons.length;i++)
+        errors = errors+
+          m_sons[i].getEstimatedErrorsForBranch(localInstances[i]);
+      return errors;
+    }
+  }
+
+  /**
+   * Computes estimated errors for leaf.
+   */
+  protected double getEstimatedErrorsForDistribution(Distribution
+                                                     theDistribution){
+    double numInc;
+    double numTotal;
+    if (Utils.eq(theDistribution.total(),0))
+      return 0;
+    else// stats.addErrs returns p - numberofincorrect.=p
+      {
+        numInc=theDistribution.numIncorrect();
+        numTotal=theDistribution.total();
+        return ((Stats.addErrs(numTotal, numInc,m_CF)) + numInc)/numTotal;
+      }
+
+  }
+
+  /**
+   * Computes estimated errors for Constructor Model.
+   */
+  protected double getEtimateConstModel(Distribution theDistribution){
+    double numInc;
+    double numTotal;
+    if (Utils.eq(theDistribution.total(),0))
+      return 0;
+    else// stats.addErrs returns p - numberofincorrect.=p
+      {
+        numTotal=theDistribution.total();
+        return ((Stats.addErrs(numTotal,m_constError,m_CF)) + m_constError)/numTotal;
+      }
+  }
+    
+
+  /**
+   * Method to count the number of inner nodes in the tree
+   * @return the number of inner nodes
+   */
+  public int getNumInnerNodes(){
+    if (m_isLeaf) return 0;
+    int numNodes = 1;
+    for (int i = 0; i < m_sons.length; i++) numNodes += m_sons[i].getNumInnerNodes();
+    return numNodes;
+  }
+
+  /**
+   * Returns the number of leaves in the tree.
+   * Leaves are only counted if their logistic model has changed compared to the one of the parent node.
+   * @return the number of leaves
+   */
+  public int getNumLeaves(){
+    int numLeaves;
+    if (!m_isLeaf) {
+      numLeaves = 0;
+      int numEmptyLeaves = 0;
+      for (int i = 0; i < m_sons.length; i++) {
+        numLeaves += m_sons[i].getNumLeaves();
+        if (m_sons[i].m_isLeaf && !m_sons[i].hasModels()) numEmptyLeaves++;
+      }
+      if (numEmptyLeaves > 1) {
+        numLeaves -= (numEmptyLeaves - 1);
+      }
+    } else {
+      numLeaves = 1;
+    }	   
+    return numLeaves;	
+  }
+
+
+     
+  /**
+   * Merges two arrays of regression functions into one
+   * @param a1 one array
+   * @param a2 the other array
+   *
+   * @return an array that contains all entries from both input arrays
+   */
+  protected SimpleLinearRegression[][] mergeArrays(SimpleLinearRegression[][] a1,	
+                                                   SimpleLinearRegression[][] a2){
+    int numModels1 = a1[0].length;
+    int numModels2 = a2[0].length;		
+	
+    SimpleLinearRegression[][] result =
+      new SimpleLinearRegression[m_numClasses][numModels1 + numModels2];
+	
+    for (int i = 0; i < m_numClasses; i++)
+      for (int j = 0; j < numModels1; j++) {
+        result[i][j]  = a1[i][j];
+      }
+    for (int i = 0; i < m_numClasses; i++)
+      for (int j = 0; j < numModels2; j++) result[i][j+numModels1] = a2[i][j];
+    return result;
+  }
+
+  /**
+   * Return a list of all inner nodes in the tree
+   * @return the list of nodes
+   */
+  public Vector getNodes(){
+    Vector nodeList = new Vector();
+    getNodes(nodeList);
+    return nodeList;
+  }
+
+  /**
+   * Fills a list with all inner nodes in the tree
+   * 
+   * @param nodeList the list to be filled
+   */
+  public void getNodes(Vector nodeList) {
+    if (!m_isLeaf) {
+      nodeList.add(this);
+      for (int i = 0; i < m_sons.length; i++) m_sons[i].getNodes(nodeList);
+    }	
+  }
+    
+  /**
+   * Returns a numeric version of a set of instances.
+   * All nominal attributes are replaced by binary ones, and the class variable is replaced
+   * by a pseudo-class variable that is used by LogitBoost.
+   */
+  protected Instances getNumericData(Instances train) throws Exception{
+	
+    Instances filteredData = new Instances(train);	
+    m_nominalToBinary = new NominalToBinary();			
+    m_nominalToBinary.setInputFormat(filteredData);
+    filteredData = Filter.useFilter(filteredData, m_nominalToBinary);	
+
+    return super.getNumericData(filteredData);
+  }
+
+  /**
+   * Computes the F-values of LogitBoost for an instance from the current logistic model at the node
+   * Note that this also takes into account the (partial) logistic model fit at higher levels in 
+   * the tree.
+   * @param instance the instance
+   * @return the array of F-values 
+   */
+  protected double[] getFs(Instance instance) throws Exception{
+	
+    double [] pred = new double [m_numClasses];
+	
+    //Need to take into account partial model fit at higher levels in the tree (m_higherRegressions) 
+    //and the part of the model fit at this node (m_regressions).
+
+    //Fs from m_regressions (use method of LogisticBase)
+    double [] instanceFs = super.getFs(instance);		
+
+    //Fs from m_higherRegressions
+    for (int i = 0; i < m_numHigherRegressions; i++) {
+      double predSum = 0;
+      for (int j = 0; j < m_numClasses; j++) {
+        pred[j] = m_higherRegressions[j][i].classifyInstance(instance);
+        predSum += pred[j];
+      }
+      predSum /= m_numClasses;
+      for (int j = 0; j < m_numClasses; j++) {
+        instanceFs[j] += (pred[j] - predSum) * (m_numClasses - 1) 
+          / m_numClasses;
+      }
+    }
+    return instanceFs; 
+  }
+     
+  /**
+   *
+   * @param probsConst
+   */
+  public int getConstError(double[] probsConst)
+  {
+    return Utils.maxIndex(probsConst);
+  }
+    
+  /**
+   *Returns true if the logistic regression model at this node has changed compared to the
+   *one at the parent node.
+   *@return whether it has changed
+   */
+  public boolean hasModels() {
+    return (m_numRegressions > 0);
+  }
+
+  /**
+   * Returns the class probabilities for an instance according to the logistic model at the node.
+   * @param instance the instance
+   * @return the array of probabilities
+   */
+  public double[] modelDistributionForInstance(Instance instance) throws Exception {
+	
+    //make copy and convert nominal attributes
+    instance = (Instance)instance.copy();		
+    m_nominalToBinary.input(instance);
+    instance = m_nominalToBinary.output();	
+	
+    //set numeric pseudo-class
+    instance.setDataset(m_numericDataHeader);		
+	
+    return probs(getFs(instance));
+  }
+
+  /**
+   * Returns the class probabilities for an instance given by the Functional tree.
+   * @param instance the instance
+   * @return the array of probabilities
+   */
+  public abstract double[] distributionForInstance(Instance instance) throws Exception;
+  
+  
+    
+  /**
+   * Returns a description of the Functional tree (tree structure and logistic models)
+   * @return describing string
+   */
+  public String toString(){	
+    //assign numbers to logistic regression functions at leaves
+    assignLeafModelNumbers(0);	
+    try{
+      StringBuffer text = new StringBuffer();
+	    
+      if (m_isLeaf && !m_hasConstr) {
+        text.append(": ");
+        text.append("Class"+"="+ m_leafclass);
+        //text.append("FT_"+m_leafModelNum+":"+getModelParameters());
+      } else {
+                
+        if (m_isLeaf && m_hasConstr) {
+          text.append(": ");
+          text.append("FT_"+m_leafModelNum+":"+getModelParameters());
+                    
+        } else {
+          dumpTree(0,text);  
+        }	    	    
+      }
+      text.append("\n\nNumber of Leaves  : \t"+numLeaves()+"\n");
+      text.append("\nSize of the Tree : \t"+numNodes()+"\n");	
+	        
+      //This prints logistic models after the tree, comment out if only tree should be printed
+      text.append(modelsToString());
+      return text.toString();
+    } catch (Exception e){
+      return "Can't print logistic model tree";
+    }
+  }
+    
+  /**
+   * Returns the number of leaves (normal count).
+   * @return the number of leaves
+   */
+  public int numLeaves() {	
+    if (m_isLeaf) return 1;	
+    int numLeaves = 0;
+    for (int i = 0; i < m_sons.length; i++) numLeaves += m_sons[i].numLeaves();
+    return numLeaves;
+  }
+    
+  /**
+   * Returns the number of nodes.
+   * @return the number of nodes
+   */
+  public int numNodes() {
+    if (m_isLeaf) return 1;	
+    int numNodes = 1;
+    for (int i = 0; i < m_sons.length; i++) numNodes += m_sons[i].numNodes();
+    return numNodes;
+  }
+   
+  /**
+   * Returns a string describing the number of LogitBoost iterations performed at this node, the total number
+   * of LogitBoost iterations performed (including iterations at higher levels in the tree), and the number
+   * of training examples at this node.
+   * @return the describing string
+   */
+  public String getModelParameters(){
+	
+    StringBuffer text = new StringBuffer();
+    int numModels = m_numRegressions+m_numHigherRegressions;
+    text.append(m_numRegressions+"/"+numModels+" ("+m_numInstances+")");
+    return text.toString();
+  }
+       
+  /**
+   * Help method for printing tree structure.
+   *
+   * @throws Exception if something goes wrong
+   */
+  protected void dumpTree(int depth,StringBuffer text) 
+    throws Exception {
+	
+    for (int i = 0; i < m_sons.length; i++) {
+      text.append("\n");
+      for (int j = 0; j < depth; j++)
+        text.append("|   ");
+      if(m_hasConstr)
+        text.append(m_localModel.leftSide(m_train)+ "#" + m_id);
+      else 
+        text.append(m_localModel.leftSide(m_train)); 
+      text.append(m_localModel.rightSide(i, m_train) );
+      if (m_sons[i].m_isLeaf && m_sons[i].m_hasConstr ) {
+        text.append(": ");
+        text.append("FT_"+m_sons[i].m_leafModelNum+":"+m_sons[i].getModelParameters());
+      }else {                
+        if(m_sons[i].m_isLeaf && !m_sons[i].m_hasConstr)
+          {
+            text.append(": ");
+            text.append("Class"+"="+ m_sons[i].m_leafclass);  
+          }
+        else{
+            
+          m_sons[i].dumpTree(depth+1,text);
+        }
+      }
+    }
+  }
+
+  /**
+   * Assigns unique IDs to all nodes in the tree
+   */
+  public int assignIDs(int lastID) {
+	
+    int currLastID = lastID + 1;
+	
+    m_id = currLastID;
+    if (m_sons != null) {
+      for (int i = 0; i < m_sons.length; i++) {
+        currLastID = m_sons[i].assignIDs(currLastID);
+      }
+    }
+    return currLastID;
+  }
+    
+  /**
+   * Assigns numbers to the logistic regression models at the leaves of the tree
+   */
+  public int assignLeafModelNumbers(int leafCounter) {
+    if (!m_isLeaf) {
+      m_leafModelNum = 0;
+      for (int i = 0; i < m_sons.length; i++){
+        leafCounter = m_sons[i].assignLeafModelNumbers(leafCounter);
+      }
+    } else {
+      leafCounter++;
+      m_leafModelNum = leafCounter;
+    } 
+    return leafCounter;
+  }
+
+  /**
+   * Returns an array containing the coefficients of the logistic regression function at this node.
+   * @return the array of coefficients, first dimension is the class, second the attribute. 
+   */
+  protected double[][] getCoefficients(){
+       
+    //Need to take into account partial model fit at higher levels in the tree (m_higherRegressions) 
+    //and the part of the model fit at this node (m_regressions).
+	
+    //get coefficients from m_regressions: use method of LogisticBase
+    double[][] coefficients = super.getCoefficients();
+    //get coefficients from m_higherRegressions:
+    double constFactor = (double)(m_numClasses - 1) / (double)m_numClasses; // (J - 1)/J
+    for (int j = 0; j < m_numClasses; j++) {
+      for (int i = 0; i < m_numHigherRegressions; i++) {		
+        double slope = m_higherRegressions[j][i].getSlope();
+        double intercept = m_higherRegressions[j][i].getIntercept();
+        int attribute = m_higherRegressions[j][i].getAttributeIndex();
+        coefficients[j][0] += constFactor * intercept;
+        coefficients[j][attribute + 1] += constFactor * slope;
+      }
+    }
+
+    return coefficients;
+  }
+    
+  /**
+   * Returns a string describing the logistic regression function at the node.
+   */
+  public String modelsToString(){
+	
+    StringBuffer text = new StringBuffer();
+    if (m_isLeaf && m_hasConstr) {
+      text.append("FT_"+m_leafModelNum+":"+super.toString());
+            
+    }else{
+      if (!m_isLeaf && m_hasConstr) {
+        if (m_modelSelection instanceof BinC45ModelSelection){
+          text.append("FT_N"+((BinC45Split)m_localModel).attIndex()+"#"+m_id +":"+super.toString()); 
+        }else{
+          text.append("FT_N"+((C45Split)m_localModel).attIndex()+"#"+m_id +":"+super.toString());
+        }
+        for (int i = 0; i < m_sons.length; i++) { 
+          text.append("\n"+ m_sons[i].modelsToString());
+        }
+      }else{
+        if (!m_isLeaf && !m_hasConstr) 
+          {
+            for (int i = 0; i < m_sons.length; i++) { 
+              text.append("\n"+ m_sons[i].modelsToString());
+            }
+          }else{
+          if (m_isLeaf && !m_hasConstr)
+            {
+              text.append("");
+            }
+        }
+                
+      }
+    }
+        
+    return text.toString();
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @throws Exception if something goes wrong
+   */
+  public String graph() throws Exception {
+	
+    StringBuffer text = new StringBuffer();
+	
+    assignIDs(-1);
+    assignLeafModelNumbers(0);
+    text.append("digraph FTree {\n");
+    if (m_isLeaf && m_hasConstr) {
+      text.append("N" + m_id + " [label=\"FT_"+m_leafModelNum+":"+getModelParameters()+"\" " + 
+                  "shape=box style=filled");
+      text.append("]\n");
+    }else{
+      if (m_isLeaf && !m_hasConstr){
+        text.append("N" + m_id + " [label=\"Class="+m_leafclass+ "\" " + 
+                    "shape=box style=filled");
+        text.append("]\n");
+             
+      }else {
+        text.append("N" + m_id 
+                    + " [label=\"" + 
+                    m_localModel.leftSide(m_train) + "\" ");
+        text.append("]\n");
+        graphTree(text);
+      }
+    }
+    return text.toString() +"}\n";
+  }
+
+  /**
+   * Helper function for graph description of tree
+   *
+   * @throws Exception if something goes wrong
+   */
+  protected void graphTree(StringBuffer text) throws Exception {
+	
+    for (int i = 0; i < m_sons.length; i++) {
+      text.append("N" + m_id  
+                  + "->" + 
+                  "N" + m_sons[i].m_id +
+                  " [label=\"" + m_localModel.rightSide(i,m_train).trim() + 
+                  "\"]\n");
+      if (m_sons[i].m_isLeaf && m_sons[i].m_hasConstr) {
+        text.append("N" +m_sons[i].m_id + " [label=\"FT_"+m_sons[i].m_leafModelNum+":"+
+                    m_sons[i].getModelParameters()+"\" " + "shape=box style=filled");
+        text.append("]\n");
+      } else { 
+        if (m_sons[i].m_isLeaf && !m_sons[i].m_hasConstr) {
+          text.append("N" +m_sons[i].m_id + " [label=\"Class="+m_sons[i].m_leafclass+"\" " + "shape=box style=filled");
+          text.append("]\n");
+        }else{
+          text.append("N" + m_sons[i].m_id +
+                      " [label=\""+m_sons[i].m_localModel.leftSide(m_train) + 
+                      "\" ");
+          text.append("]\n");
+          m_sons[i].graphTree(text);
+        }
+      }
+    } 
+  }  
+
+  /**
+   * Cleanup in order to save memory.
+   */
+  public void cleanup() {
+    super.cleanup();
+    if (!m_isLeaf) {
+      for (int i = 0; i < m_sons.length; i++) m_sons[i].cleanup();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4899 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45ModelSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45ModelSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45ModelSelection.java	(revision 29)
@@ -0,0 +1,200 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BinC45ModelSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class for selecting a C4.5-like binary (!) split for a given dataset.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6073 $
+ */
+public class BinC45ModelSelection
+  extends ModelSelection {
+
+  /** for serialization */
+  private static final long serialVersionUID = 179170923545122001L;
+
+  /** Minimum number of instances in interval. */
+  private int m_minNoObj;               
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection;         
+
+  /** The FULL training dataset. */
+  private Instances m_allData; 
+
+  /**
+   * Initializes the split selection method with the given parameters.
+   *
+   * @param minNoObj minimum number of instances that have to occur in
+   * at least two subsets induced by split
+   * @param allData FULL training dataset (necessary for selection of
+   * split points).  
+   * @param useMDLcorrection whether to use MDL adjustement when
+   * finding splits on numeric attributes
+   */
+  public BinC45ModelSelection(int minNoObj,Instances allData,
+                             boolean useMDLcorrection){
+    m_minNoObj = minNoObj;
+    m_allData = allData;
+    m_useMDLcorrection = useMDLcorrection;
+  }
+
+  /**
+   * Sets reference to training data to null.
+   */
+  public void cleanup() {
+
+    m_allData = null;
+  }
+
+  /**
+   * Selects C4.5-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances data){
+
+    double minResult;
+    double currentResult;
+    BinC45Split [] currentModel;
+    BinC45Split bestModel = null;
+    NoSplit noSplitModel = null;
+    double averageInfoGain = 0;
+    int validModels = 0;
+    boolean multiVal = true;
+    Distribution checkDistribution;
+    double sumOfWeights;
+    int i;
+    
+    try{
+
+      // Check if all Instances belong to one class or if not
+      // enough Instances to split.
+      checkDistribution = new Distribution(data);
+      noSplitModel = new NoSplit(checkDistribution);
+      if (Utils.sm(checkDistribution.total(),2*m_minNoObj) ||
+	  Utils.eq(checkDistribution.total(),
+		   checkDistribution.perClass(checkDistribution.maxClass())))
+	return noSplitModel;
+
+      // Check if all attributes are nominal and have a 
+      // lot of values.
+      Enumeration enu = data.enumerateAttributes();
+      while (enu.hasMoreElements()) {
+	Attribute attribute = (Attribute) enu.nextElement();
+	if ((attribute.isNumeric()) ||
+	    (Utils.sm((double)attribute.numValues(),
+		      (0.3*(double)m_allData.numInstances())))){
+	  multiVal = false;
+	  break;
+	}
+      }
+      currentModel = new BinC45Split[data.numAttributes()];
+      sumOfWeights = data.sumOfWeights();
+
+      // For each attribute.
+      for (i = 0; i < data.numAttributes(); i++){
+	
+	// Apart from class attribute.
+	if (i != (data).classIndex()){
+	  
+	  // Get models for current attribute.
+	  currentModel[i] = new BinC45Split(i,m_minNoObj,sumOfWeights,m_useMDLcorrection);
+	  currentModel[i].buildClassifier(data);
+	  
+	  // Check if useful split for current attribute
+	  // exists and check for enumerated attributes with 
+	  // a lot of values.
+	  if (currentModel[i].checkModel())
+	    if ((data.attribute(i).isNumeric()) ||
+		(multiVal || Utils.sm((double)data.attribute(i).numValues(),
+				      (0.3*(double)m_allData.numInstances())))){
+	      averageInfoGain = averageInfoGain+currentModel[i].infoGain();
+	      validModels++;
+	    }
+	}else
+	  currentModel[i] = null;
+      }
+      
+      // Check if any useful split was found.
+      if (validModels == 0)
+	return noSplitModel;
+      averageInfoGain = averageInfoGain/(double)validModels;
+
+      // Find "best" attribute to split on.
+      minResult = 0;
+      for (i=0;i<data.numAttributes();i++){
+	if ((i != (data).classIndex()) &&
+	    (currentModel[i].checkModel()))
+	  
+	  // Use 1E-3 here to get a closer approximation to the original
+	  // implementation.
+	  if ((currentModel[i].infoGain() >= (averageInfoGain-1E-3)) &&
+	      Utils.gr(currentModel[i].gainRatio(),minResult)){ 
+	    bestModel = currentModel[i];
+	    minResult = currentModel[i].gainRatio();
+	  }
+      }
+      
+      // Check if useful split was found.
+      if (Utils.eq(minResult,0))
+	return noSplitModel;
+
+      // Add all Instances with unknown values for the corresponding
+      // attribute to the distribution for the model, so that
+      // the complete distribution is stored with the model. 
+      bestModel.distribution().
+	addInstWithUnknown(data,bestModel.attIndex());
+      
+      // Set the split point analogue to C45 if attribute numeric.
+      bestModel.setSplitPoint(m_allData);
+      return bestModel;
+    }catch(Exception e){
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Selects C4.5-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances train, Instances test) {
+
+    return selectModel(train);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6073 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45Split.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45Split.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/BinC45Split.java	(revision 29)
@@ -0,0 +1,507 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BinC45Split.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class implementing a binary C4.5-like split on an attribute.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6073 $
+ */
+public class BinC45Split
+  extends ClassifierSplitModel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1278776919563022474L;
+
+  /** Attribute to split on. */
+  private int m_attIndex;        
+
+  /** Minimum number of objects in a split.   */ 
+  private int m_minNoObj;         
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection;         
+
+  /** Value of split point. */
+  private double m_splitPoint;  
+
+  /** InfoGain of split. */
+  private double m_infoGain; 
+
+  /** GainRatio of split.  */
+  private double m_gainRatio;  
+
+  /** The sum of the weights of the instances. */
+  private double m_sumOfWeights;  
+
+  /** Static reference to splitting criterion. */
+  private static InfoGainSplitCrit m_infoGainCrit = new InfoGainSplitCrit();
+
+  /** Static reference to splitting criterion. */
+  private static GainRatioSplitCrit m_gainRatioCrit = new GainRatioSplitCrit();
+
+  /**
+   * Initializes the split model.
+   */
+  public BinC45Split(int attIndex,int minNoObj,double sumOfWeights,
+                     boolean useMDLcorrection) {
+
+    // Get index of attribute to split on.
+    m_attIndex = attIndex;
+        
+    // Set minimum number of objects.
+    m_minNoObj = minNoObj;
+
+    // Set sum of weights;
+    m_sumOfWeights = sumOfWeights;
+
+    // Whether to use the MDL correction for numeric attributes
+    m_useMDLcorrection = useMDLcorrection;
+  }
+
+  /**
+   * Creates a C4.5-type split on the given data.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances trainInstances)
+       throws Exception {
+
+    // Initialize the remaining instance variables.
+    m_numSubsets = 0;
+    m_splitPoint = Double.MAX_VALUE;
+    m_infoGain = 0;
+    m_gainRatio = 0;
+
+    // Different treatment for enumerated and numeric
+    // attributes.
+    if (trainInstances.attribute(m_attIndex).isNominal()){
+      handleEnumeratedAttribute(trainInstances);
+    }else{
+      trainInstances.sort(trainInstances.attribute(m_attIndex));
+      handleNumericAttribute(trainInstances);
+    }
+  }    
+
+  /**
+   * Returns index of attribute for which split was generated.
+   */
+  public final int attIndex(){
+
+    return m_attIndex;
+  }
+  
+  /**
+   * Returns the split point (numeric attribute only).
+   * 
+   * @return the split point used for a test on a numeric attribute
+   */
+  public double splitPoint() {
+    return m_splitPoint;
+  }
+  
+  /**
+   * Returns (C4.5-type) gain ratio for the generated split.
+   */
+  public final double gainRatio(){
+    return m_gainRatio;
+  }
+
+  /**
+   * Gets class probability for instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final double classProb(int classIndex,Instance instance,
+				int theSubset) throws Exception {
+
+    if (theSubset <= -1) {
+      double [] weights = weights(instance);
+      if (weights == null) {
+	return m_distribution.prob(classIndex);
+      } else {
+	double prob = 0;
+	for (int i = 0; i < weights.length; i++) {
+	  prob += weights[i] * m_distribution.prob(classIndex, i);
+	}
+	return prob;
+      }
+    } else {
+      if (Utils.gr(m_distribution.perBag(theSubset), 0)) {
+	return m_distribution.prob(classIndex, theSubset);
+      } else {
+	return m_distribution.prob(classIndex);
+      }
+    }
+  }
+ 
+  /**
+   * Creates split on enumerated attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleEnumeratedAttribute(Instances trainInstances)
+       throws Exception {
+    
+    Distribution newDistribution,secondDistribution;
+    int numAttValues;
+    double currIG,currGR;
+    Instance instance;
+    int i;
+
+    numAttValues = trainInstances.attribute(m_attIndex).numValues();
+    newDistribution = new Distribution(numAttValues,
+				       trainInstances.numClasses());
+    
+    // Only Instances with known values are relevant.
+    Enumeration enu = trainInstances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (!instance.isMissing(m_attIndex))
+	newDistribution.add((int)instance.value(m_attIndex),instance);
+    }
+    m_distribution = newDistribution;
+
+    // For all values
+    for (i = 0; i < numAttValues; i++){
+
+      if (Utils.grOrEq(newDistribution.perBag(i),m_minNoObj)){
+	secondDistribution = new Distribution(newDistribution,i);
+	
+	// Check if minimum number of Instances in the two
+	// subsets.
+	if (secondDistribution.check(m_minNoObj)){
+	  m_numSubsets = 2;
+	  currIG = m_infoGainCrit.splitCritValue(secondDistribution,
+					       m_sumOfWeights);
+	  currGR = m_gainRatioCrit.splitCritValue(secondDistribution,
+						m_sumOfWeights,
+						currIG);
+	  if ((i == 0) || Utils.gr(currGR,m_gainRatio)){
+	    m_gainRatio = currGR;
+	    m_infoGain = currIG;
+	    m_splitPoint = (double)i;
+	    m_distribution = secondDistribution;
+	  }
+	}
+      }
+    }
+  }
+  
+  /**
+   * Creates split on numeric attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleNumericAttribute(Instances trainInstances)
+       throws Exception {
+  
+    int firstMiss;
+    int next = 1;
+    int last = 0;
+    int index = 0;
+    int splitIndex = -1;
+    double currentInfoGain;
+    double defaultEnt;
+    double minSplit;
+    Instance instance;
+    int i;
+
+    // Current attribute is a numeric attribute.
+    m_distribution = new Distribution(2,trainInstances.numClasses());
+    
+    // Only Instances with known values are relevant.
+    Enumeration enu = trainInstances.enumerateInstances();
+    i = 0;
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (instance.isMissing(m_attIndex))
+	break;
+      m_distribution.add(1,instance);
+      i++;
+    }
+    firstMiss = i;
+
+    // Compute minimum number of Instances required in each
+    // subset.
+    minSplit =  0.1*(m_distribution.total())/
+      ((double)trainInstances.numClasses());
+    if (Utils.smOrEq(minSplit,m_minNoObj)) 
+      minSplit = m_minNoObj;
+    else
+      if (Utils.gr(minSplit,25)) 
+	minSplit = 25;
+
+    // Enough Instances with known values?
+    if (Utils.sm((double)firstMiss,2*minSplit))
+      return;
+    
+    // Compute values of criteria for all possible split
+    // indices.
+    defaultEnt = m_infoGainCrit.oldEnt(m_distribution);
+    while (next < firstMiss){
+	  
+      if (trainInstances.instance(next-1).value(m_attIndex)+1e-5 < 
+	  trainInstances.instance(next).value(m_attIndex)){ 
+	
+	// Move class values for all Instances up to next 
+	// possible split point.
+	m_distribution.shiftRange(1,0,trainInstances,last,next);
+	
+	// Check if enough Instances in each subset and compute
+	// values for criteria.
+	if (Utils.grOrEq(m_distribution.perBag(0),minSplit) && 
+	    Utils.grOrEq(m_distribution.perBag(1),minSplit)){
+	  currentInfoGain = m_infoGainCrit.
+	    splitCritValue(m_distribution,m_sumOfWeights,
+			   defaultEnt);
+	  if (Utils.gr(currentInfoGain,m_infoGain)){
+	    m_infoGain = currentInfoGain;
+	    splitIndex = next-1;
+	  }
+	  index++;
+	}
+	last = next;
+      }
+      next++;
+    }
+    
+    // Was there any useful split?
+    if (index == 0)
+      return;
+    
+    // Compute modified information gain for best split.
+    if (m_useMDLcorrection) {
+      m_infoGain = m_infoGain-(Utils.log2(index)/m_sumOfWeights);
+    }
+    if (Utils.smOrEq(m_infoGain,0))
+      return;
+    
+    // Set instance variables' values to values for
+    // best split.
+    m_numSubsets = 2;
+    m_splitPoint = 
+      (trainInstances.instance(splitIndex+1).value(m_attIndex)+
+       trainInstances.instance(splitIndex).value(m_attIndex))/2;
+
+    // In case we have a numerical precision problem we need to choose the
+    // smaller value
+    if (m_splitPoint == trainInstances.instance(splitIndex + 1).value(m_attIndex)) {
+      m_splitPoint = trainInstances.instance(splitIndex).value(m_attIndex);
+    }
+
+    // Restore distributioN for best split.
+    m_distribution = new Distribution(2,trainInstances.numClasses());
+    m_distribution.addRange(0,trainInstances,0,splitIndex+1);
+    m_distribution.addRange(1,trainInstances,splitIndex+1,firstMiss);
+
+    // Compute modified gain ratio for best split.
+    m_gainRatio = m_gainRatioCrit.
+      splitCritValue(m_distribution,m_sumOfWeights,
+		     m_infoGain);
+  }
+
+  /**
+   * Returns (C4.5-type) information gain for the generated split.
+   */
+  public final double infoGain(){
+
+    return m_infoGain;
+  }
+
+  /**
+   * Prints left side of condition.
+   * 
+   * @param data the data to get the attribute name from.
+   * @return the attribute name
+   */
+  public final String leftSide(Instances data){
+
+    return data.attribute(m_attIndex).name();
+  }
+
+  /**
+   * Prints the condition satisfied by instances in a subset.
+   *
+   * @param index of subset and training set.
+   */
+  public final String rightSide(int index,Instances data){
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    if (data.attribute(m_attIndex).isNominal()){
+      if (index == 0)
+	text.append(" = "+
+		    data.attribute(m_attIndex).value((int)m_splitPoint));
+      else
+	text.append(" != "+
+		    data.attribute(m_attIndex).value((int)m_splitPoint));
+    }else
+      if (index == 0)
+	text.append(" <= "+m_splitPoint);
+      else
+	text.append(" > "+m_splitPoint);
+    
+    return text.toString();
+  }
+
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+
+    StringBuffer expr = null;
+    if (index < 0) {
+      return "i[" + m_attIndex + "] == null";
+    }
+    if (data.attribute(m_attIndex).isNominal()) {
+      if (index == 0) {
+	expr = new StringBuffer("i[");
+      } else {
+	expr = new StringBuffer("!i[");
+      }
+      expr.append(m_attIndex).append("]");
+      expr.append(".equals(\"").append(data.attribute(m_attIndex)
+				     .value((int)m_splitPoint)).append("\")");
+    } else {
+      expr = new StringBuffer("((Double) i[");
+      expr.append(m_attIndex).append("])");
+      if (index == 0) {
+	expr.append(".doubleValue() <= ").append(m_splitPoint);
+      } else {
+	expr.append(".doubleValue() > ").append(m_splitPoint);
+      }
+    }
+    return expr.toString();
+  }  
+
+  /**
+   * Sets split point to greatest value in given data smaller or equal to
+   * old split point.
+   * (C4.5 does this for some strange reason).
+   */
+  public final void setSplitPoint(Instances allInstances){
+    
+    double newSplitPoint = -Double.MAX_VALUE;
+    double tempValue;
+    Instance instance;
+    
+    if ((!allInstances.attribute(m_attIndex).isNominal()) &&
+	(m_numSubsets > 1)){
+      Enumeration enu = allInstances.enumerateInstances();
+      while (enu.hasMoreElements()) {
+	instance = (Instance) enu.nextElement();
+	if (!instance.isMissing(m_attIndex)){
+	  tempValue = instance.value(m_attIndex);
+	  if (Utils.gr(tempValue,newSplitPoint) && 
+	      Utils.smOrEq(tempValue,m_splitPoint))
+	    newSplitPoint = tempValue;
+	}
+      }
+      m_splitPoint = newSplitPoint;
+    }
+  }
+  
+  /**
+   * Sets distribution associated with model.
+   */
+  public void resetDistribution(Instances data) throws Exception {
+
+    Instances insts = new Instances(data, data.numInstances());
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (whichSubset(data.instance(i)) > -1) {
+	insts.add(data.instance(i));
+      }
+    }
+    Distribution newD = new Distribution(insts, this);
+    newD.addInstWithUnknown(data, m_attIndex);
+    m_distribution = newD;
+  }
+
+  /**
+   * Returns weights if instance is assigned to more than one subset.
+   * Returns null if instance is only assigned to one subset.
+   */
+  public final double [] weights(Instance instance){
+    
+    double [] weights;
+    int i;
+    
+    if (instance.isMissing(m_attIndex)){
+      weights = new double [m_numSubsets];
+      for (i=0;i<m_numSubsets;i++)
+	weights [i] = m_distribution.perBag(i)/m_distribution.total();
+      return weights;
+    }else{
+      return null;
+    }
+  }
+  
+  /**
+   * Returns index of subset instance is assigned to.
+   * Returns -1 if instance is assigned to more than one subset.
+   *
+   * @exception Exception if something goes wrong
+   */
+
+  public final int whichSubset(Instance instance) throws Exception {
+    
+    if (instance.isMissing(m_attIndex))
+      return -1;
+    else{
+      if (instance.attribute(m_attIndex).isNominal()){
+	if ((int)m_splitPoint == (int)instance.value(m_attIndex))
+	  return 0;
+	else
+	  return 1;
+      }else
+	if (Utils.smOrEq(instance.value(m_attIndex),m_splitPoint))
+	  return 0;
+	else
+	  return 1;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6073 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45ModelSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45ModelSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45ModelSelection.java	(revision 29)
@@ -0,0 +1,210 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45ModelSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class for selecting a C4.5-type split for a given dataset.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6073 $
+ */
+public class C45ModelSelection
+  extends ModelSelection {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3372204862440821989L;
+
+  /** Minimum number of objects in interval. */
+  private int m_minNoObj;               
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection;         
+
+  /** All the training data */
+  private Instances m_allData; // 
+
+  /**
+   * Initializes the split selection method with the given parameters.
+   *
+   * @param minNoObj minimum number of instances that have to occur in at least two
+   * subsets induced by split
+   * @param allData FULL training dataset (necessary for
+   * selection of split points).
+   * @param useMDLcorrection whether to use MDL adjustement when
+   * finding splits on numeric attributes
+   */
+  public C45ModelSelection(int minNoObj, Instances allData,
+                             boolean useMDLcorrection) {
+    m_minNoObj = minNoObj;
+    m_allData = allData;
+    m_useMDLcorrection = useMDLcorrection;
+  }
+
+  /**
+   * Sets reference to training data to null.
+   */
+  public void cleanup() {
+
+    m_allData = null;
+  }
+
+  /**
+   * Selects C4.5-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances data){
+
+    double minResult;
+    double currentResult;
+    C45Split [] currentModel;
+    C45Split bestModel = null;
+    NoSplit noSplitModel = null;
+    double averageInfoGain = 0;
+    int validModels = 0;
+    boolean multiVal = true;
+    Distribution checkDistribution;
+    Attribute attribute;
+    double sumOfWeights;
+    int i;
+    
+    try{
+
+      // Check if all Instances belong to one class or if not
+      // enough Instances to split.
+      checkDistribution = new Distribution(data);
+      noSplitModel = new NoSplit(checkDistribution);
+      if (Utils.sm(checkDistribution.total(),2*m_minNoObj) ||
+	  Utils.eq(checkDistribution.total(),
+		   checkDistribution.perClass(checkDistribution.maxClass())))
+	return noSplitModel;
+
+      // Check if all attributes are nominal and have a 
+      // lot of values.
+      if (m_allData != null) {
+	Enumeration enu = data.enumerateAttributes();
+	while (enu.hasMoreElements()) {
+	  attribute = (Attribute) enu.nextElement();
+	  if ((attribute.isNumeric()) ||
+	      (Utils.sm((double)attribute.numValues(),
+			(0.3*(double)m_allData.numInstances())))){
+	    multiVal = false;
+	    break;
+	  }
+	}
+      } 
+
+      currentModel = new C45Split[data.numAttributes()];
+      sumOfWeights = data.sumOfWeights();
+
+      // For each attribute.
+      for (i = 0; i < data.numAttributes(); i++){
+	
+	// Apart from class attribute.
+	if (i != (data).classIndex()){
+	  
+	  // Get models for current attribute.
+	  currentModel[i] = new C45Split(i,m_minNoObj,sumOfWeights,m_useMDLcorrection);
+	  currentModel[i].buildClassifier(data);
+	  
+	  // Check if useful split for current attribute
+	  // exists and check for enumerated attributes with 
+	  // a lot of values.
+	  if (currentModel[i].checkModel())
+	    if (m_allData != null) {
+	      if ((data.attribute(i).isNumeric()) ||
+		  (multiVal || Utils.sm((double)data.attribute(i).numValues(),
+					(0.3*(double)m_allData.numInstances())))){
+		averageInfoGain = averageInfoGain+currentModel[i].infoGain();
+		validModels++;
+	      } 
+	    } else {
+	      averageInfoGain = averageInfoGain+currentModel[i].infoGain();
+	      validModels++;
+	    }
+	}else
+	  currentModel[i] = null;
+      }
+      
+      // Check if any useful split was found.
+      if (validModels == 0)
+	return noSplitModel;
+      averageInfoGain = averageInfoGain/(double)validModels;
+
+      // Find "best" attribute to split on.
+      minResult = 0;
+      for (i=0;i<data.numAttributes();i++){
+	if ((i != (data).classIndex()) &&
+	    (currentModel[i].checkModel()))
+	  
+	  // Use 1E-3 here to get a closer approximation to the original
+	  // implementation.
+	  if ((currentModel[i].infoGain() >= (averageInfoGain-1E-3)) &&
+	      Utils.gr(currentModel[i].gainRatio(),minResult)){ 
+	    bestModel = currentModel[i];
+	    minResult = currentModel[i].gainRatio();
+	  } 
+      }
+
+      // Check if useful split was found.
+      if (Utils.eq(minResult,0))
+	return noSplitModel;
+      
+      // Add all Instances with unknown values for the corresponding
+      // attribute to the distribution for the model, so that
+      // the complete distribution is stored with the model. 
+      bestModel.distribution().
+	  addInstWithUnknown(data,bestModel.attIndex());
+      
+      // Set the split point analogue to C45 if attribute numeric.
+      if (m_allData != null)
+	bestModel.setSplitPoint(m_allData);
+      return bestModel;
+    }catch(Exception e){
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Selects C4.5-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances train, Instances test) {
+
+    return selectModel(train);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6073 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTree.java	(revision 29)
@@ -0,0 +1,380 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45PruneableClassifierTree.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+/**
+ * Class for handling a tree structure that can
+ * be pruned using C4.5 procedures.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6073 $
+ */
+
+public class C45PruneableClassifierTree 
+  extends ClassifierTree {
+
+  /** for serialization */
+  static final long serialVersionUID = -4813820170260388194L;
+  
+  /** True if the tree is to be pruned. */
+  boolean m_pruneTheTree = false;
+  
+  /** True if the tree is to be collapsed. */
+  boolean m_collapseTheTree = false;
+
+  /** The confidence factor for pruning. */
+  float m_CF = 0.25f;
+
+  /** Is subtree raising to be performed? */
+  boolean m_subtreeRaising = true;
+
+  /** Cleanup after the tree has been built. */
+  boolean m_cleanup = true;
+
+  /**
+   * Constructor for pruneable tree structure. Stores reference
+   * to associated training data at each node.
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param pruneTree true if the tree is to be pruned
+   * @param cf the confidence factor for pruning
+   * @param raiseTree
+   * @param cleanup
+   * @throws Exception if something goes wrong
+   */
+  public C45PruneableClassifierTree(ModelSelection toSelectLocModel,
+				    boolean pruneTree,float cf,
+				    boolean raiseTree,
+				    boolean cleanup,
+                                    boolean collapseTree)
+       throws Exception {
+
+    super(toSelectLocModel);
+
+    m_pruneTheTree = pruneTree;
+    m_CF = cf;
+    m_subtreeRaising = raiseTree;
+    m_cleanup = cleanup;
+    m_collapseTheTree = collapseTree;
+  }
+
+  /**
+   * Returns default capabilities of the classifier tree.
+   *
+   * @return      the capabilities of this classifier tree
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Method for building a pruneable classifier tree.
+   *
+   * @param data the data for building the tree
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier tree handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+   buildTree(data, m_subtreeRaising);
+   if (m_collapseTheTree) {
+     collapse();
+   }
+   if (m_pruneTheTree) {
+     prune();
+   }
+   if (m_cleanup) {
+     cleanup(new Instances(data, 0));
+   }
+  }
+
+  /**
+   * Collapses a tree to a node if training error doesn't increase.
+   */
+  public final void collapse(){
+
+    double errorsOfSubtree;
+    double errorsOfTree;
+    int i;
+
+    if (!m_isLeaf){
+      errorsOfSubtree = getTrainingErrors();
+      errorsOfTree = localModel().distribution().numIncorrect();
+      if (errorsOfSubtree >= errorsOfTree-1E-3){
+
+	// Free adjacent trees
+	m_sons = null;
+	m_isLeaf = true;
+			
+	// Get NoSplit Model for tree.
+	m_localModel = new NoSplit(localModel().distribution());
+      }else
+	for (i=0;i<m_sons.length;i++)
+	  son(i).collapse();
+    }
+  }
+
+  /**
+   * Prunes a tree using C4.5's pruning procedure.
+   *
+   * @throws Exception if something goes wrong
+   */
+  public void prune() throws Exception {
+
+    double errorsLargestBranch;
+    double errorsLeaf;
+    double errorsTree;
+    int indexOfLargestBranch;
+    C45PruneableClassifierTree largestBranch;
+    int i;
+
+    if (!m_isLeaf){
+
+      // Prune all subtrees.
+      for (i=0;i<m_sons.length;i++)
+	son(i).prune();
+
+      // Compute error for largest branch
+      indexOfLargestBranch = localModel().distribution().maxBag();
+      if (m_subtreeRaising) {
+	errorsLargestBranch = son(indexOfLargestBranch).
+	  getEstimatedErrorsForBranch((Instances)m_train);
+      } else {
+	errorsLargestBranch = Double.MAX_VALUE;
+      }
+
+      // Compute error if this Tree would be leaf
+      errorsLeaf = 
+	getEstimatedErrorsForDistribution(localModel().distribution());
+
+      // Compute error for the whole subtree
+      errorsTree = getEstimatedErrors();
+
+      // Decide if leaf is best choice.
+      if (Utils.smOrEq(errorsLeaf,errorsTree+0.1) &&
+	  Utils.smOrEq(errorsLeaf,errorsLargestBranch+0.1)){
+
+	// Free son Trees
+	m_sons = null;
+	m_isLeaf = true;
+		
+	// Get NoSplit Model for node.
+	m_localModel = new NoSplit(localModel().distribution());
+	return;
+      }
+
+      // Decide if largest branch is better choice
+      // than whole subtree.
+      if (Utils.smOrEq(errorsLargestBranch,errorsTree+0.1)){
+	largestBranch = son(indexOfLargestBranch);
+	m_sons = largestBranch.m_sons;
+	m_localModel = largestBranch.localModel();
+	m_isLeaf = largestBranch.m_isLeaf;
+	newDistribution(m_train);
+	prune();
+      }
+    }
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param data the data to work with
+   * @return the new tree
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances data) throws Exception {
+    
+    C45PruneableClassifierTree newTree = 
+      new C45PruneableClassifierTree(m_toSelectModel, m_pruneTheTree, m_CF,
+				     m_subtreeRaising, m_cleanup, m_collapseTheTree);
+    newTree.buildTree((Instances)data, m_subtreeRaising);
+
+    return newTree;
+  }
+
+  /**
+   * Computes estimated errors for tree.
+   * 
+   * @return the estimated errors
+   */
+  private double getEstimatedErrors(){
+
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(localModel().distribution());
+    else{
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+son(i).getEstimatedErrors();
+      return errors;
+    }
+  }
+  
+  /**
+   * Computes estimated errors for one branch.
+   *
+   * @param data the data to work with
+   * @return the estimated errors
+   * @throws Exception if something goes wrong
+   */
+  private double getEstimatedErrorsForBranch(Instances data) 
+       throws Exception {
+
+    Instances [] localInstances;
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(new Distribution(data));
+    else{
+      Distribution savedDist = localModel().m_distribution;
+      localModel().resetDistribution(data);
+      localInstances = (Instances[])localModel().split(data);
+      localModel().m_distribution = savedDist;
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+
+	  son(i).getEstimatedErrorsForBranch(localInstances[i]);
+      return errors;
+    }
+  }
+
+  /**
+   * Computes estimated errors for leaf.
+   * 
+   * @param theDistribution the distribution to use
+   * @return the estimated errors
+   */
+  private double getEstimatedErrorsForDistribution(Distribution 
+						   theDistribution){
+
+    if (Utils.eq(theDistribution.total(),0))
+      return 0;
+    else
+      return theDistribution.numIncorrect()+
+	Stats.addErrs(theDistribution.total(),
+		      theDistribution.numIncorrect(),m_CF);
+  }
+
+  /**
+   * Computes errors of tree on training data.
+   * 
+   * @return the training errors
+   */
+  private double getTrainingErrors(){
+
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return localModel().distribution().numIncorrect();
+    else{
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+son(i).getTrainingErrors();
+      return errors;
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   * 
+   * @return the local split model
+   */
+  private ClassifierSplitModel localModel(){
+    
+    return (ClassifierSplitModel)m_localModel;
+  }
+
+  /**
+   * Computes new distributions of instances for nodes
+   * in tree.
+   *
+   * @param data the data to compute the distributions for
+   * @throws Exception if something goes wrong
+   */
+  private void newDistribution(Instances data) throws Exception {
+
+    Instances [] localInstances;
+
+    localModel().resetDistribution(data);
+    m_train = data;
+    if (!m_isLeaf){
+      localInstances = 
+	(Instances [])localModel().split(data);
+      for (int i = 0; i < m_sons.length; i++)
+	son(i).newDistribution(localInstances[i]);
+    } else {
+
+      // Check whether there are some instances at the leaf now!
+      if (!Utils.eq(data.sumOfWeights(), 0)) {
+	m_isEmpty = false;
+      }
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private C45PruneableClassifierTree son(int index){
+
+    return (C45PruneableClassifierTree)m_sons[index];
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6073 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTreeG.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTreeG.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45PruneableClassifierTreeG.java	(revision 29)
@@ -0,0 +1,1229 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45PruneableClassifierTreeG.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *    Copyright (C) 2007 Geoff Webb & Janice Boughton
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Instance;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Class for handling a tree structure that can
+ * be pruned using C4.5 procedures and have nodes grafted on.
+ *
+ * @author Janice Boughton (based on code by Eibe Frank)
+ * @version $Revision: 5532 $
+ */
+
+public class C45PruneableClassifierTreeG extends ClassifierTree{
+
+  /** for serialization */
+  static final long serialVersionUID = 66981207374331964L;
+
+  /** True if the tree is to be pruned. */
+  boolean m_pruneTheTree = false;
+
+  /** The confidence factor for pruning. */
+  float m_CF = 0.25f;
+
+  /** Is subtree raising to be performed? */
+  boolean m_subtreeRaising = true;
+
+  /** Cleanup after the tree has been built. */
+  boolean m_cleanup = true;
+
+  /** flag for using relabelling when grafting */
+  boolean m_relabel = false;
+
+  /** binomial probability critical value */
+  double m_BiProbCrit = 1.64;
+
+  boolean m_Debug = false;
+
+  /**
+   * Constructor for pruneable tree structure. Stores reference
+   * to associated training data at each node.
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param pruneTree true if the tree is to be pruned
+   * @param cf the confidence factor for pruning
+   * @param raiseTree
+   * @param cleanup
+   * @throws Exception if something goes wrong
+   */
+  public C45PruneableClassifierTreeG(ModelSelection toSelectLocModel,
+				    boolean pruneTree,float cf,
+				    boolean raiseTree,
+				    boolean relabel, boolean cleanup)
+       throws Exception {
+
+    super(toSelectLocModel);
+
+    m_pruneTheTree = pruneTree;
+    m_CF = cf;
+    m_subtreeRaising = raiseTree;
+    m_cleanup = cleanup;
+    m_relabel = relabel;
+  }
+
+
+  /**
+   * Returns default capabilities of the classifier tree.
+   *
+   * @return      the capabilities of this classifier tree
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+
+    return result;
+  }
+
+  /**
+   * Constructor for pruneable tree structure. Used to create new nodes
+   * in the tree during grafting.
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param data the dta used to produce split model
+   * @param gs the split model
+   * @param prune true if the tree is to be pruned
+   * @param cf the confidence factor for pruning
+   * @param raise
+   * @param isLeaf if this node is a leaf or not
+   * @param relabel whether relabeling occured
+   * @param cleanup
+   * @throws Exception if something goes wrong
+   */
+  public C45PruneableClassifierTreeG(ModelSelection toSelectLocModel, 
+                                    Instances data, ClassifierSplitModel gs, 
+                                    boolean prune, float cf, boolean raise,
+                                    boolean isLeaf, boolean relabel, 
+                                    boolean cleanup) {
+
+    super(toSelectLocModel);
+    m_relabel = relabel;
+    m_cleanup = cleanup;
+    m_localModel = gs;
+    m_train = data;
+    m_test = null;
+    m_isLeaf = isLeaf;
+    if(gs.distribution().total() > 0)
+       m_isEmpty = false;
+    else
+       m_isEmpty = true;
+
+    m_pruneTheTree = prune;
+    m_CF = cf;
+    m_subtreeRaising = raise;
+  }
+
+  /**
+   * Method for building a pruneable classifier tree.
+   *
+   * @param data the data for building the tree
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier tree handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+
+    buildTree(data, m_subtreeRaising);
+    collapse();
+    if (m_pruneTheTree) {
+      prune();
+    }
+    doGrafting(data);
+    if (m_cleanup) {
+      cleanup(new Instances(data, 0));
+    }
+  }
+
+
+  /**
+   * Collapses a tree to a node if training error doesn't increase.
+   */
+  public final void collapse(){
+
+    double errorsOfSubtree;
+    double errorsOfTree;
+    int i;
+
+    if (!m_isLeaf){
+      errorsOfSubtree = getTrainingErrors();
+      errorsOfTree = localModel().distribution().numIncorrect();
+      if (errorsOfSubtree >= errorsOfTree-1E-3){
+
+	// Free adjacent trees
+	m_sons = null;
+	m_isLeaf = true;
+			
+	// Get NoSplit Model for tree.
+	m_localModel = new NoSplit(localModel().distribution());
+      }else
+	for (i=0;i<m_sons.length;i++)
+	  son(i).collapse();
+    }
+  }
+
+  /**
+   * Prunes a tree using C4.5's pruning procedure.
+   *
+   * @throws Exception if something goes wrong
+   */
+  public void prune() throws Exception {
+
+    double errorsLargestBranch;
+    double errorsLeaf;
+    double errorsTree;
+    int indexOfLargestBranch;
+    C45PruneableClassifierTreeG largestBranch;
+    int i;
+
+    if (!m_isLeaf){
+
+      // Prune all subtrees.
+      for (i=0;i<m_sons.length;i++)
+	son(i).prune();
+
+      // Compute error for largest branch
+      indexOfLargestBranch = localModel().distribution().maxBag();
+      if (m_subtreeRaising) {
+	errorsLargestBranch = son(indexOfLargestBranch).
+	  getEstimatedErrorsForBranch((Instances)m_train);
+      } else {
+	errorsLargestBranch = Double.MAX_VALUE;
+      }
+
+      // Compute error if this Tree would be leaf
+      errorsLeaf = 
+	getEstimatedErrorsForDistribution(localModel().distribution());
+
+      // Compute error for the whole subtree
+      errorsTree = getEstimatedErrors();
+
+      // Decide if leaf is best choice.
+      if (Utils.smOrEq(errorsLeaf,errorsTree+0.1) &&
+	  Utils.smOrEq(errorsLeaf,errorsLargestBranch+0.1)){
+
+	// Free son Trees
+	m_sons = null;
+	m_isLeaf = true;
+		
+	// Get NoSplit Model for node.
+	m_localModel = new NoSplit(localModel().distribution());
+	return;
+      }
+
+      // Decide if largest branch is better choice
+      // than whole subtree.
+      if (Utils.smOrEq(errorsLargestBranch,errorsTree+0.1)){
+	largestBranch = son(indexOfLargestBranch);
+	m_sons = largestBranch.m_sons;
+	m_localModel = largestBranch.localModel();
+	m_isLeaf = largestBranch.m_isLeaf;
+	newDistribution(m_train);
+	prune();
+      }
+    }
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param data the data to work with
+   * @return the new tree
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances data) throws Exception {
+    
+    C45PruneableClassifierTreeG newTree = 
+      new C45PruneableClassifierTreeG(m_toSelectModel, m_pruneTheTree, m_CF,
+	     m_subtreeRaising, m_relabel, m_cleanup);
+	// ATBOP Modification     // m_subtreeRaising, m_cleanup);
+
+    newTree.buildTree((Instances)data, m_subtreeRaising);
+
+    return newTree;
+  }
+
+  /**
+   * Computes estimated errors for tree.
+   *
+   * @return the estimated errors
+   */
+  private double getEstimatedErrors(){
+
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(localModel().distribution());
+    else{
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+son(i).getEstimatedErrors();
+      return errors;
+    }
+  }
+  
+  /**
+   * Computes estimated errors for one branch.
+   *
+   * @param data the data to work with
+   * @return the estimated errors
+   * @throws Exception if something goes wrong
+   */
+  private double getEstimatedErrorsForBranch(Instances data) 
+       throws Exception {
+
+    Instances [] localInstances;
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return getEstimatedErrorsForDistribution(new Distribution(data));
+    else{
+      Distribution savedDist = localModel().m_distribution;
+      localModel().resetDistribution(data);
+      localInstances = (Instances[])localModel().split(data);
+      localModel().m_distribution = savedDist;
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+
+	  son(i).getEstimatedErrorsForBranch(localInstances[i]);
+      return errors;
+    }
+  }
+
+  /**
+   * Computes estimated errors for leaf.
+   *
+   * @param theDistribution the distribution to use
+   * @return the estimated errors
+   */
+  private double getEstimatedErrorsForDistribution(Distribution 
+						   theDistribution){
+
+    if (Utils.eq(theDistribution.total(),0))
+      return 0;
+    else
+      return theDistribution.numIncorrect()+
+	Stats.addErrs(theDistribution.total(),
+		      theDistribution.numIncorrect(),m_CF);
+  }
+
+  /**
+   * Computes errors of tree on training data.
+   *
+   * @return the training errors
+   */
+  private double getTrainingErrors(){
+
+    double errors = 0;
+    int i;
+
+    if (m_isLeaf)
+      return localModel().distribution().numIncorrect();
+    else{
+      for (i=0;i<m_sons.length;i++)
+	errors = errors+son(i).getTrainingErrors();
+      return errors;
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   *
+   * @return the local split model
+   */
+  private ClassifierSplitModel localModel(){
+    
+    return (ClassifierSplitModel)m_localModel;
+  }
+
+  /**
+   * Computes new distributions of instances for nodes
+   * in tree.
+   *
+   * @param data the data to compute the distributions for
+   * @throws Exception if something goes wrong
+   */
+  private void newDistribution(Instances data) throws Exception {
+
+    Instances [] localInstances;
+
+    localModel().resetDistribution(data);
+    m_train = data;
+    if (!m_isLeaf){
+      localInstances = 
+	(Instances [])localModel().split(data);
+      for (int i = 0; i < m_sons.length; i++)
+	son(i).newDistribution(localInstances[i]);
+    } else {
+
+      // Check whether there are some instances at the leaf now!
+      if (!Utils.eq(data.sumOfWeights(), 0)) {
+	m_isEmpty = false;
+      }
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private C45PruneableClassifierTreeG son(int index){
+    return (C45PruneableClassifierTreeG)m_sons[index];
+  }
+
+
+  /**
+   * Initializes variables for grafting.
+   * sets up limits array (for numeric attributes) and calls 
+   * the recursive function traverseTree.
+   *
+   * @param data the data for the tree
+   * @throws Exception if anything goes wrong
+   */
+  public void doGrafting(Instances data) throws Exception {
+
+    // 2d array for the limits
+    double [][] limits = new double[data.numAttributes()][2];
+    // 2nd dimension: index 0 == lower limit, index 1 == upper limit
+    // initialise to no limit
+    for(int i = 0; i < data.numAttributes(); i++) {
+       limits[i][0] = Double.NEGATIVE_INFINITY;
+       limits[i][1] = Double.POSITIVE_INFINITY;
+    }
+
+    // use an index instead of creating new Insances objects all the time
+    // instanceIndex[0] == array for weights at leaf
+    // instanceIndex[1] == array for weights in atbop
+    double [][] instanceIndex = new double[2][data.numInstances()];
+    // initialize the weight for each instance
+    for(int x = 0; x < data.numInstances(); x++) {
+        instanceIndex[0][x] = 1;
+        instanceIndex[1][x] = 1;  // leaf instances are in atbop
+    }
+
+    // first call to graft
+    traverseTree(data, instanceIndex, limits, this, 0, -1);
+  }
+
+
+  /**
+   * recursive function.
+   * if this node is a leaf then calls findGraft, otherwise sorts 
+   * the two sets of instances (tracked in iindex array) and calls
+   * sortInstances for each of the child nodes (which then calls
+   * this method).
+   *
+   * @param fulldata all instances
+   * @param iindex array the tracks the weight of each instance in
+   *        the atbop and at the leaf (0.0 if not present)
+   * @param limits array specifying current upper/lower limits for numeric atts
+   * @param parent the node immediately before the current one
+   * @param pL laplace for node, as calculated by parent (in case leaf is empty)
+   * @param nodeClass class of node, determined by parent (in case leaf empty)
+   */
+  private void traverseTree(Instances fulldata, double [][] iindex, 
+     double[][] limits, C45PruneableClassifierTreeG parent, 
+     double pL, int nodeClass) throws Exception {
+    
+    if(m_isLeaf) {
+
+       findGraft(fulldata, iindex, limits, 
+                 (ClassifierTree)parent, pL, nodeClass);
+
+    } else {
+
+       // traverse each branch
+       for(int i = 0; i < localModel().numSubsets(); i++) {
+
+          double [][] newiindex = new double[2][fulldata.numInstances()];
+          for(int x = 0; x < 2; x++)
+             System.arraycopy(iindex[x], 0, newiindex[x], 0, iindex[x].length);
+          sortInstances(fulldata, newiindex, limits, i);
+       }
+    }
+  }
+
+  /**
+   * sorts/deletes instances into/from node and atbop according to 
+   * the test for subset, then calls traverseTree for subset's node.
+   *
+   * @param fulldata all instances
+   * @param iindex array the tracks the weight of each instance in
+   *        the atbop and at the leaf (0.0 if not present)
+   * @param limits array specifying current upper/lower limits for numeric atts
+   * @param subset the subset for which to sort instances into inode & iatbop
+   */
+  private void sortInstances(Instances fulldata, double [][] iindex, 
+                   double [][] limits, int subset) throws Exception {
+
+    C45Split test = (C45Split)localModel();
+
+    // update the instances index for subset
+    double knownCases = 0;
+    double thisSubsetCount = 0;
+    for(int x = 0; x < iindex[0].length; x++) {
+       if(iindex[0][x] == 0 && iindex[1][x] == 0) // skip "discarded" instances
+          continue;
+       if(!fulldata.instance(x).isMissing(test.attIndex())) {
+          knownCases += iindex[0][x];
+          if(test.whichSubset(fulldata.instance(x)) != subset) {
+             if(iindex[0][x] > 0) {
+                // move to atbop, delete from leaf
+                iindex[1][x] = iindex[0][x];
+                iindex[0][x] = 0;
+             } else {
+                if(iindex[1][x] > 0) {
+                   // instance is now "discarded"
+                   iindex[1][x] = 0;
+                }
+             }
+          } else {
+             thisSubsetCount += iindex[0][x];
+          }
+       }
+    }
+
+    // work out proportions of weight for missing values for leaf and atbop
+    double lprop = (knownCases == 0) ? (1.0 / (double)test.numSubsets()) 
+                                : (thisSubsetCount / (double)knownCases);
+
+    // add in the instances that have missing value for attIndex 
+    for(int x = 0; x < iindex[0].length; x++) {
+       if(iindex[0][x] == 0 && iindex[1][x] == 0)
+          continue;     // skip "discarded" instances
+       if(fulldata.instance(x).isMissing(test.attIndex())) {
+          iindex[1][x] -= (iindex[1][x] - iindex[0][x]) * (1-lprop);
+          iindex[0][x] *= lprop;
+       }
+    }
+
+    int nodeClass = localModel().distribution().maxClass(subset);
+    double pL = (localModel().distribution().perClass(nodeClass) + 1.0)
+               / (localModel().distribution().total() + 2.0);
+
+    // call traerseTree method for the child node
+    son(subset).traverseTree(fulldata, iindex,
+          test.minsAndMaxs(fulldata, limits, subset), this, pL, nodeClass);
+  }
+
+  /**
+   * finds new nodes that improve accuracy and grafts them onto the tree
+   *
+   * @param fulldata the instances in whole trainset
+   * @param iindex records num tests each instance has failed up to this node
+   * @param limits the upper/lower limits for numeric attributes
+   * @param parent the node immediately before the current one
+   * @param pLaplace laplace for leaf, calculated by parent (in case leaf empty)
+   * @param pLeafClass class of leaf, determined by parent (in case leaf empty)
+   */
+  private void findGraft(Instances fulldata, double [][] iindex, 
+   double [][] limits, ClassifierTree parent, double pLaplace, 
+   int pLeafClass) throws Exception {
+
+    // get the class for this leaf
+    int leafClass = (m_isEmpty)
+                       ? pLeafClass
+                       :  localModel().distribution().maxClass();
+
+    // get the laplace value for this leaf
+    double leafLaplace = (m_isEmpty)
+                            ? pLaplace
+                            : laplaceLeaf(leafClass);
+
+    // sort the instances into those at the leaf, those in atbop, and discarded
+    Instances l = new Instances(fulldata, fulldata.numInstances());
+    Instances n = new Instances(fulldata, fulldata.numInstances());
+    int lcount = 0;
+    int acount = 0;
+    for(int x = 0; x < fulldata.numInstances(); x++) {
+       if(iindex[0][x] <= 0 && iindex[1][x] <= 0)
+          continue;
+       if(iindex[0][x] != 0) {
+          l.add(fulldata.instance(x));
+          l.instance(lcount).setWeight(iindex[0][x]);
+          // move instance's weight in iindex to same index as in l
+          iindex[0][lcount++] = iindex[0][x];
+       }
+       if(iindex[1][x] > 0) {
+          n.add(fulldata.instance(x));
+          n.instance(acount).setWeight(iindex[1][x]);
+          // move instance's weight in iindex to same index as in n
+          iindex[1][acount++] = iindex[1][x];
+       }
+    }
+
+    boolean graftPossible = false;
+    double [] classDist = new double[n.numClasses()];
+    for(int x = 0; x < n.numInstances(); x++) {
+       if(iindex[1][x] > 0 && !n.instance(x).classIsMissing())
+          classDist[(int)n.instance(x).classValue()] += iindex[1][x];
+    }
+
+    for(int cVal = 0; cVal < n.numClasses(); cVal++) {
+       double theLaplace = (classDist[cVal] + 1.0) / (classDist[cVal] + 2.0);
+       if(cVal != leafClass && (theLaplace > leafLaplace) && 
+        (biprob(classDist[cVal], classDist[cVal], leafLaplace)
+         > m_BiProbCrit)) {
+          graftPossible = true;
+          break;
+       }
+    }
+
+    if(!graftPossible) {
+       return;
+    }
+
+    // 1. Initialize to {} a set of tuples t containing potential tests
+    ArrayList t = new ArrayList();
+
+    // go through each attribute
+    for(int a = 0; a < n.numAttributes(); a++) {
+       if(a == n.classIndex())
+          continue;   // skip the class
+
+       // sort instances in atbop by $a
+       int [] sorted = sortByAttribute(n, a);
+
+       // 2. For each continuous attribute $a:
+       if(n.attribute(a).isNumeric()) {
+
+          // find min and max values for this attribute at the leaf
+          boolean prohibited = false;
+          double minLeaf = Double.POSITIVE_INFINITY;
+          double maxLeaf = Double.NEGATIVE_INFINITY;
+          for(int i = 0; i < l.numInstances(); i++) {
+             if(l.instance(i).isMissing(a)) {
+                if(l.instance(i).classValue() == leafClass) {
+                   prohibited = true;
+                   break;
+                }
+             }
+             double value = l.instance(i).value(a);
+             if(!m_relabel || l.instance(i).classValue() == leafClass) {
+                if(value < minLeaf)
+                   minLeaf = value;
+                if(value > maxLeaf)
+                   maxLeaf = value;
+             }
+          }
+          if(prohibited) {
+             continue;
+	  }
+
+          // (a) find values of
+          //    $n: instances in atbop (already have that, actually)
+          //    $v: a value for $a that exists for a case in the atbop, where
+          //       $v is < the min value for $a for a case at the leaf which
+          //       has the class $c, and $v is > the lowerlimit of $a at
+          //       the leaf.
+          //       (note: error in original paper stated that $v must be
+          //       smaller OR EQUAL TO the min value).
+          //    $k: $k is a class
+          //  that maximize L' = Laplace({$x: $x contained in cases($n)
+          //    & value($a,$x) <= $v & value($a,$x) > lowerlim($l,$a)}, $k).
+          double minBestClass = Double.NaN;
+          double minBestLaplace = leafLaplace;
+          double minBestVal = Double.NaN;
+          double minBestPos = Double.NaN;
+          double minBestTotal = Double.NaN;
+          double [][] minBestCounts = null;
+          double [][] counts = new double[2][n.numClasses()];
+          for(int x = 0; x < n.numInstances(); x++) {
+             if(n.instance(sorted[x]).isMissing(a))
+                break;   // missing are sorted to end: no more valid vals
+
+             double theval = n.instance(sorted[x]).value(a);
+             if(m_Debug)
+                System.out.println("\t " + theval);
+
+             if(theval <= limits[a][0]) {
+                if(m_Debug)
+                   System.out.println("\t  <= lowerlim: continuing...");
+                continue;
+             }
+             // note: error in paper would have this read "theVal > minLeaf)
+             if(theval >= minLeaf) {
+                if(m_Debug)
+                   System.out.println("\t  >= minLeaf; breaking...");
+                break;
+             }
+             counts[0][(int)n.instance(sorted[x]).classValue()]
+                += iindex[1][sorted[x]];
+
+             if(x != n.numInstances() - 1) {
+                int z = x + 1;
+                while(z < n.numInstances()
+                 && n.instance(sorted[z]).value(a) == theval) {
+                   z++; x++;
+                   counts[0][(int)n.instance(sorted[x]).classValue()] 
+                    += iindex[1][sorted[x]];
+                }
+             }
+
+             // work out the best laplace/class (for <= theval)
+             double total = Utils.sum(counts[0]);
+             for(int c = 0; c < n.numClasses(); c++) {
+                double temp = (counts[0][c]+1.0)/(total+2.0);
+                if(temp > minBestLaplace) {
+                   minBestPos = counts[0][c];
+                   minBestTotal = total;
+                   minBestLaplace = temp;
+                   minBestClass = c;
+                   minBestCounts = copyCounts(counts);
+
+                   minBestVal = (x == n.numInstances()-1) 
+                      ? theval
+                      : ((theval + n.instance(sorted[x+1]).value(a)) / 2.0);
+                }
+             }
+          }
+
+          // (b) add to t tuple <n,a,v,k,L',"<=">
+          if(!Double.isNaN(minBestVal)
+             && biprob(minBestPos, minBestTotal, leafLaplace) > m_BiProbCrit) {
+             GraftSplit gsplit = null;
+             try {
+                gsplit = new GraftSplit(a, minBestVal, 0,
+                                        leafClass, minBestCounts);
+             } catch (Exception e) {
+                System.err.println("graftsplit error: "+e.getMessage());
+                System.exit(1);
+             }
+             t.add(gsplit);
+	  }
+          // free space
+          minBestCounts = null;
+
+          // (c) find values of
+          //    n: instances in atbop (already have that, actually)
+          //    $v: a value for $a that exists for a case in the atbop, where
+          //       $v is > the max value for $a for a case at the leaf which
+          //       has the class $c, and $v is <= the upperlimit of $a at
+          //       the leaf.
+          //    k: k is a class
+          //   that maximize L' = Laplace({x: x contained in cases(n)
+          //       & value(a,x) > v & value(a,x) <= upperlim(l,a)}, k).
+          double maxBestClass = -1;
+          double maxBestLaplace = leafLaplace;
+          double maxBestVal = Double.NaN;
+          double maxBestPos = Double.NaN;
+          double maxBestTotal = Double.NaN;
+          double [][] maxBestCounts = null;
+          for(int c = 0; c < n.numClasses(); c++) {  // zero the counts
+             counts[0][c] = 0;
+             counts[1][c] = 0;  // shouldn't need to do this ...
+          }
+
+          // check smallest val for a in atbop is < upper limit
+          if(n.numInstances() >= 1
+           && n.instance(sorted[0]).value(a) < limits[a][1]) {
+             for(int x = n.numInstances() - 1; x >= 0; x--) {
+                if(n.instance(sorted[x]).isMissing(a))
+                   continue;
+
+                double theval = n.instance(sorted[x]).value(a);
+                if(m_Debug)
+                   System.out.println("\t " + theval);
+
+                if(theval > limits[a][1]) {
+                   if(m_Debug)
+                      System.out.println("\t  >= upperlim; continuing...");
+                   continue;
+                }
+                if(theval <= maxLeaf) {
+                   if(m_Debug)
+                      System.out.println("\t  < maxLeaf; breaking...");
+                   break;
+                }
+
+                // increment counts
+                counts[1][(int)n.instance(sorted[x]).classValue()] 
+                   += iindex[1][sorted[x]];
+
+                if(x != 0 && !n.instance(sorted[x-1]).isMissing(a)) {
+                   int z = x - 1;
+                   while(z >= 0 && n.instance(sorted[z]).value(a) == theval) {
+                      z--; x--;
+                      counts[1][(int)n.instance(sorted[x]).classValue()]
+                         += iindex[1][sorted[x]];
+                   }
+                }
+
+                // work out best laplace for > theval
+                double total = Utils.sum(counts[1]);
+                for(int c = 0; c < n.numClasses(); c++) {
+                   double temp = (counts[1][c]+1.0)/(total+2.0);
+                   if(temp > maxBestLaplace ) {
+                      maxBestPos = counts[1][c];
+                      maxBestTotal = total;
+                      maxBestLaplace = temp;
+                      maxBestClass = c;
+                      maxBestCounts = copyCounts(counts);
+                      maxBestVal = (x == 0) 
+                        ? theval
+                        : ((theval + n.instance(sorted[x-1]).value(a)) / 2.0);
+                   }
+                }
+             }
+
+             // (d) add to t tuple <n,a,v,k,L',">">
+             if(!Double.isNaN(maxBestVal)
+               && biprob(maxBestPos,maxBestTotal,leafLaplace) > m_BiProbCrit) {
+                GraftSplit gsplit = null;
+                try {
+                   gsplit = new GraftSplit(a, maxBestVal, 1,
+                      leafClass, maxBestCounts);
+                } catch (Exception e) {
+                   System.err.println("graftsplit error:" + e.getMessage());
+                   System.exit(1);
+                }
+                t.add(gsplit);
+             }
+          }
+       } else {    // must be a nominal attribute
+
+          // 3. for each discrete attribute a for which there is no
+          //    test at an ancestor of l
+
+          // skip if this attribute has already been used
+          if(limits[a][1] == 1) {
+             continue;
+          }
+
+          boolean [] prohibit = new boolean[l.attribute(a).numValues()];
+          for(int aval = 0; aval < n.attribute(a).numValues(); aval++) {
+             for(int x = 0; x < l.numInstances(); x++) {
+                if((l.instance(x).isMissing(a)
+                    || l.instance(x).value(a) == aval) 
+                 && (!m_relabel || (l.instance(x).classValue() == leafClass))) {
+                   prohibit[aval] = true;
+                   break;
+                }
+             }
+          }
+
+          // (a) find values of
+          //       $n: instances in atbop (already have that, actually)
+          //       $v: $v is a value for $a
+          //       $k: $k is a class
+          //     that maximize L' = Laplace({$x: $x contained in cases($n)
+          //           & value($a,$x) = $v}, $k).
+          double bestVal = Double.NaN;
+          double bestClass = Double.NaN;
+          double bestLaplace = leafLaplace;
+          double [][] bestCounts = null;
+          double [][] counts = new double[2][n.numClasses()];
+
+          for(int x = 0; x < n.numInstances(); x++) {
+             if(n.instance(sorted[x]).isMissing(a))
+                continue;
+
+             // zero the counts
+             for(int c = 0; c < n.numClasses(); c++)
+                counts[0][c] = 0;
+
+             double theval = n.instance(sorted[x]).value(a);
+             counts[0][(int)n.instance(sorted[x]).classValue()] 
+               += iindex[1][sorted[x]];
+
+             if(x != n.numInstances() - 1) {
+                int z = x + 1;
+                while(z < n.numInstances() 
+                 && n.instance(sorted[z]).value(a) == theval) {
+                   z++; x++;
+                   counts[0][(int)n.instance(sorted[x]).classValue()]
+                      += iindex[1][sorted[x]];
+                }
+             }
+
+             if(!prohibit[(int)theval]) {
+                // work out best laplace for > theval
+                double total = Utils.sum(counts[0]);
+                bestLaplace = leafLaplace;
+                bestClass = Double.NaN;
+                for(int c = 0; c < n.numClasses(); c++) {
+                   double temp = (counts[0][c]+1.0)/(total+2.0);
+                   if(temp > bestLaplace
+                    && biprob(counts[0][c],total,leafLaplace) > m_BiProbCrit) {
+                      bestLaplace = temp;
+                      bestClass = c;
+                      bestVal = theval;
+                      bestCounts = copyCounts(counts);
+                   }
+                }
+		// add to graft list
+                if(!Double.isNaN(bestClass)) {
+                   GraftSplit gsplit = null;
+                   try {
+                      gsplit = new GraftSplit(a, bestVal, 2,
+                         leafClass, bestCounts);
+                   } catch (Exception e) {
+                     System.err.println("graftsplit error: "+e.getMessage());
+                     System.exit(1);
+                   }
+                   t.add(gsplit);
+                }
+             }
+          }
+          // (b) add to t tuple <n,a,v,k,L',"=">
+          // done this already
+       }
+    }
+
+    // 4. remove from t all tuples <n,a,v,c,L,x> such that L <=
+    //    Laplace(cases(l),c) or prob(x,n,Laplace(cases(l),c) <= 0.05
+    //      -- checked this constraint prior to adding a tuple --
+
+    // *** step six done before step five for efficiency ***
+    // 6. for each <n,a,v,k,L,x> in t ordered on L from highest to lowest
+    // order the tuples from highest to lowest laplace
+    // (this actually orders lowest to highest)
+    Collections.sort(t);
+
+    // 5. remove from t all tuples <n,a,v,c,L,x> such that there is
+    //    no tuple <n',a',v',k',L',x'> such that k' != c & L' < L.
+    for(int x = 0; x < t.size(); x++) {
+       GraftSplit gs = (GraftSplit)t.get(x);
+       if(gs.maxClassForSubsetOfInterest() != leafClass) {
+          break; // reached a graft with class != leafClass, so stop deleting
+       } else {
+          t.remove(x);
+          x--;
+       }
+    }
+
+    // if no potential grafts were found, do nothing and return
+    if(t.size() < 1) {
+       return;
+    }
+
+    // create the distributions for each graft
+    for(int x = t.size()-1; x >= 0; x--) {
+       GraftSplit gs = (GraftSplit)t.get(x);
+       try {
+          gs.buildClassifier(l);
+          gs.deleteGraftedCases(l); // so they don't go down the other branch
+       } catch (Exception e) {
+          System.err.println("graftsplit build error: " + e.getMessage());
+       }
+    }
+
+    // add this stuff to the tree
+    ((C45PruneableClassifierTreeG)parent).setDescendents(t, this);
+  }
+
+  /**
+   * sorts the int array in ascending order by attribute indexed 
+   * by a in dataset data.  
+   * @param the data the indices represent
+   * @param the index of the attribute to sort by
+   * @return array of sorted indicies
+   */
+  private int [] sortByAttribute(Instances data, int a) {
+
+    double [] attList = data.attributeToDoubleArray(a);
+    int [] temp = Utils.sort(attList);
+    return temp;
+  }
+
+  /**
+   * deep copy the 2d array of counts
+   *
+   * @param src the array to copy
+   * @return a copy of src
+   */
+  private double [][] copyCounts(double [][] src) {
+
+    double [][] newArr = new double[src.length][0];
+    for(int x = 0; x < src.length; x++) {
+       newArr[x] = new double[src[x].length];
+       for(int y = 0; y < src[x].length; y++) {
+          newArr[x][y] = src[x][y];
+       }
+    }
+    return newArr;
+  }
+  
+
+  /**
+   * Help method for computing class probabilities of
+   * a given instance.
+   *
+   * @throws Exception if something goes wrong
+   */
+  private double getProbsLaplace(int classIndex, Instance instance, double weight)
+       throws Exception {
+
+    double [] weights;
+    double prob = 0;
+    int treeIndex;
+    int i,j;
+
+    if (m_isLeaf) {
+       return weight * localModel().classProbLaplace(classIndex, instance, -1);
+    } else {
+       treeIndex = localModel().whichSubset(instance);
+
+       if (treeIndex == -1) {
+          weights = localModel().weights(instance);
+          for (i = 0; i < m_sons.length; i++) {
+             if (!son(i).m_isEmpty) {
+                if (!son(i).m_isLeaf) {
+                   prob += son(i).getProbsLaplace(classIndex, instance,
+                                                  weights[i] * weight);
+                } else {
+                   prob += weight * weights[i] *
+                     localModel().classProbLaplace(classIndex, instance, i);
+                }
+             }
+          }
+          return prob;
+       } else {
+
+          if (son(treeIndex).m_isLeaf) {
+             return weight * localModel().classProbLaplace(classIndex, instance,
+                                                           treeIndex);
+          } else {
+             return son(treeIndex).getProbsLaplace(classIndex,instance,weight);
+          }
+       }
+    }
+  }
+
+
+  /**
+   * Help method for computing class probabilities of
+   * a given instance.
+   *
+   * @throws Exception if something goes wrong
+   */
+  private double getProbs(int classIndex, Instance instance, double weight)
+      throws Exception {
+
+    double [] weights;
+    double prob = 0;
+    int treeIndex;
+    int i,j;
+
+    if (m_isLeaf) {
+       return weight * localModel().classProb(classIndex, instance, -1);
+    } else {
+       treeIndex = localModel().whichSubset(instance);
+       if (treeIndex == -1) {
+          weights = localModel().weights(instance);
+          for (i = 0; i < m_sons.length; i++) {
+             if (!son(i).m_isEmpty) {
+                prob += son(i).getProbs(classIndex, instance,
+                                 weights[i] * weight);
+             }
+          }
+          return prob;
+       } else {
+
+          if (son(treeIndex).m_isEmpty) {
+             return weight * localModel().classProb(classIndex, instance,
+                                                    treeIndex);
+          } else {
+             return son(treeIndex).getProbs(classIndex, instance, weight);
+          }
+       }
+    }
+  }
+
+
+
+  /**
+   * add the grafted nodes at originalLeaf's position in tree.
+   * a recursive function that terminates when t is empty.
+   * 
+   * @param t the list of nodes to graft
+   * @param originalLeaf the leaf that the grafts are replacing
+   */
+  public void setDescendents(ArrayList t, 
+                             C45PruneableClassifierTreeG originalLeaf) {
+
+    Instances headerInfo = new Instances(m_train, 0);
+
+    boolean end = false;
+    ClassifierSplitModel splitmod = null;
+    C45PruneableClassifierTreeG newNode;
+    if(t.size() > 0) {
+       splitmod = (ClassifierSplitModel)t.remove(t.size() - 1);
+       newNode = new C45PruneableClassifierTreeG(m_toSelectModel, headerInfo,
+                           splitmod, m_pruneTheTree, m_CF, m_subtreeRaising,
+                           false, m_relabel, m_cleanup);
+    } else {
+       // get the leaf for one of newNode's children
+       NoSplit kLeaf = ((GraftSplit)localModel()).getOtherLeaf();
+       newNode = 
+             new C45PruneableClassifierTreeG(m_toSelectModel, headerInfo,
+                           kLeaf, m_pruneTheTree, m_CF, m_subtreeRaising,
+                           true, m_relabel, m_cleanup);
+       end = true;
+    }
+
+    // behave differently for parent of original leaf, since we don't
+    // want to destroy any of its other branches
+    if(m_sons != null) {
+       for(int x = 0; x < m_sons.length; x++) {
+          if(son(x).equals(originalLeaf)) {
+             m_sons[x] = newNode;  // replace originalLeaf with newNode
+          }
+       }
+    } else {
+
+       // allocate space for the children
+       m_sons = new C45PruneableClassifierTreeG[localModel().numSubsets()];
+ 
+       // get the leaf for one of newNode's children
+       NoSplit kLeaf = ((GraftSplit)localModel()).getLeaf();
+       C45PruneableClassifierTreeG kNode = 
+                 new C45PruneableClassifierTreeG(m_toSelectModel, headerInfo,
+                               kLeaf, m_pruneTheTree, m_CF, m_subtreeRaising,
+                               true, m_relabel, m_cleanup);
+ 
+       // figure where to put the new node
+       if(((GraftSplit)localModel()).subsetOfInterest() == 0) {
+          m_sons[0] = kNode;
+          m_sons[1] = newNode;
+       } else {
+          m_sons[0] = newNode;
+          m_sons[1] = kNode;
+       }
+    }
+    if(!end)
+       ((C45PruneableClassifierTreeG)newNode).setDescendents
+                  (t, (C45PruneableClassifierTreeG)originalLeaf);
+  }
+
+
+  /**
+   *  class prob with laplace correction (assumes binary class)
+   */
+  private double laplaceLeaf(double classIndex) {
+    double l =  (localModel().distribution().perClass((int)classIndex) + 1.0)
+               / (localModel().distribution().total() + 2.0);
+    return l;
+  }
+
+
+  /**
+   * Significance test
+   * @param x 
+   * @param n
+   * @param r
+   * @return returns the probability of obtaining x or MORE out of n
+   * if r proportion of n are positive.
+   *
+   * z for normal estimation of binomial probability of obtaining x 
+   * or more out of n, if r proportion of n are positive
+   */
+  public double biprob(double x, double n, double r) throws Exception {
+
+    return ((((x) - 0.5) - (n) * (r)) / Math.sqrt((n) * (r) * (1.0 - (r))));
+  }
+
+  /**
+   * Prints tree structure.
+   */
+  public String toString() {
+
+    try {
+       StringBuffer text = new StringBuffer();
+
+       if(m_isLeaf) {
+          text.append(": ");
+          if(m_localModel instanceof GraftSplit)
+             text.append(((GraftSplit)m_localModel).dumpLabelG(0,m_train));
+          else
+             text.append(m_localModel.dumpLabel(0,m_train));
+       } else
+          dumpTree(0,text);
+       text.append("\n\nNumber of Leaves  : \t"+numLeaves()+"\n");
+       text.append("\nSize of the tree : \t"+numNodes()+"\n");
+
+       return text.toString();
+    } catch (Exception e) {
+       return "Can't print classification tree.";
+    }
+  }
+
+  /**
+   * Help method for printing tree structure.
+   *
+   * @throws Exception if something goes wrong
+   */
+  protected void dumpTree(int depth,StringBuffer text) throws Exception {
+
+    int i,j;
+
+    for(i=0;i<m_sons.length;i++) {
+       text.append("\n");;
+       for(j=0;j<depth;j++)
+          text.append("|   ");
+       text.append(m_localModel.leftSide(m_train));
+       text.append(m_localModel.rightSide(i, m_train));
+       if(m_sons[i].m_isLeaf) {
+          text.append(": ");
+          if(m_localModel instanceof GraftSplit)
+             text.append(((GraftSplit)m_localModel).dumpLabelG(i,m_train));
+          else
+             text.append(m_localModel.dumpLabel(i,m_train));
+       } else
+          ((C45PruneableClassifierTreeG)m_sons[i]).dumpTree(depth+1,text);
+     }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5532 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45Split.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45Split.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/C45Split.java	(revision 29)
@@ -0,0 +1,516 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45Split.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class implementing a C4.5-type split on an attribute.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6073 $
+ */
+public class C45Split
+  extends ClassifierSplitModel{
+
+  /** for serialization */
+  private static final long serialVersionUID = 3064079330067903161L;
+
+  /** Desired number of branches. */
+  private int m_complexityIndex;  
+
+  /** Attribute to split on. */
+  private int m_attIndex;         
+
+  /** Minimum number of objects in a split.   */
+  private int m_minNoObj;         
+
+  /** Use MDL correction? */
+  private boolean m_useMDLcorrection;         
+
+  /** Value of split point. */
+  private double m_splitPoint;   
+
+  /** InfoGain of split. */ 
+  private double m_infoGain; 
+
+  /** GainRatio of split.  */
+  private double m_gainRatio;  
+
+  /** The sum of the weights of the instances. */
+  private double m_sumOfWeights;  
+
+  /** Number of split points. */
+  private int m_index;            
+
+  /** Static reference to splitting criterion. */
+  private static InfoGainSplitCrit infoGainCrit = new InfoGainSplitCrit();
+
+  /** Static reference to splitting criterion. */
+  private static GainRatioSplitCrit gainRatioCrit = new GainRatioSplitCrit();
+
+  /**
+   * Initializes the split model.
+   */
+  public C45Split(int attIndex,int minNoObj, double sumOfWeights,
+                  boolean useMDLcorrection) {
+
+    // Get index of attribute to split on.
+    m_attIndex = attIndex;
+        
+    // Set minimum number of objects.
+    m_minNoObj = minNoObj;
+
+    // Set the sum of the weights
+    m_sumOfWeights = sumOfWeights;
+
+    // Whether to use the MDL correction for numeric attributes
+    m_useMDLcorrection = useMDLcorrection;
+  }
+
+  /**
+   * Creates a C4.5-type split on the given data. Assumes that none of
+   * the class values is missing.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances trainInstances) 
+       throws Exception {
+
+    // Initialize the remaining instance variables.
+    m_numSubsets = 0;
+    m_splitPoint = Double.MAX_VALUE;
+    m_infoGain = 0;
+    m_gainRatio = 0;
+
+    // Different treatment for enumerated and numeric
+    // attributes.
+    if (trainInstances.attribute(m_attIndex).isNominal()) {
+      m_complexityIndex = trainInstances.attribute(m_attIndex).numValues();
+      m_index = m_complexityIndex;
+      handleEnumeratedAttribute(trainInstances);
+    }else{
+      m_complexityIndex = 2;
+      m_index = 0;
+      trainInstances.sort(trainInstances.attribute(m_attIndex));
+      handleNumericAttribute(trainInstances);
+    }
+  }    
+
+  /**
+   * Returns index of attribute for which split was generated.
+   */
+  public final int attIndex() {
+
+    return m_attIndex;
+  }
+  
+  /**
+   * Returns the split point (numeric attribute only).
+   * 
+   * @return the split point used for a test on a numeric attribute
+   */
+  public double splitPoint() {
+    return m_splitPoint;
+  }
+
+  /**
+   * Gets class probability for instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final double classProb(int classIndex,Instance instance,
+				int theSubset) throws Exception {
+
+    if (theSubset <= -1) {
+      double [] weights = weights(instance);
+      if (weights == null) {
+	return m_distribution.prob(classIndex);
+      } else {
+	double prob = 0;
+	for (int i = 0; i < weights.length; i++) {
+	  prob += weights[i] * m_distribution.prob(classIndex, i);
+	}
+	return prob;
+      }
+    } else {
+      if (Utils.gr(m_distribution.perBag(theSubset), 0)) {
+	return m_distribution.prob(classIndex, theSubset);
+      } else {
+	return m_distribution.prob(classIndex);
+      }
+    }
+  }
+ 
+  /**
+   * Returns coding cost for split (used in rule learner).
+   */
+  public final double codingCost() {
+
+    return Utils.log2(m_index);
+  }
+ 
+  /**
+   * Returns (C4.5-type) gain ratio for the generated split.
+   */
+  public final double gainRatio() {
+    return m_gainRatio;
+  }
+
+  /**
+   * Creates split on enumerated attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleEnumeratedAttribute(Instances trainInstances)
+       throws Exception {
+    
+    Instance instance;
+
+    m_distribution = new Distribution(m_complexityIndex,
+			      trainInstances.numClasses());
+    
+    // Only Instances with known values are relevant.
+    Enumeration enu = trainInstances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (!instance.isMissing(m_attIndex))
+	m_distribution.add((int)instance.value(m_attIndex),instance);
+    }
+    
+    // Check if minimum number of Instances in at least two
+    // subsets.
+    if (m_distribution.check(m_minNoObj)) {
+      m_numSubsets = m_complexityIndex;
+      m_infoGain = infoGainCrit.
+	splitCritValue(m_distribution,m_sumOfWeights);
+      m_gainRatio = 
+	gainRatioCrit.splitCritValue(m_distribution,m_sumOfWeights,
+				     m_infoGain);
+    }
+  }
+  
+  /**
+   * Creates split on numeric attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleNumericAttribute(Instances trainInstances)
+       throws Exception {
+  
+    int firstMiss;
+    int next = 1;
+    int last = 0;
+    int splitIndex = -1;
+    double currentInfoGain;
+    double defaultEnt;
+    double minSplit;
+    Instance instance;
+    int i;
+
+    // Current attribute is a numeric attribute.
+    m_distribution = new Distribution(2,trainInstances.numClasses());
+    
+    // Only Instances with known values are relevant.
+    Enumeration enu = trainInstances.enumerateInstances();
+    i = 0;
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (instance.isMissing(m_attIndex))
+	break;
+      m_distribution.add(1,instance);
+      i++;
+    }
+    firstMiss = i;
+	
+    // Compute minimum number of Instances required in each
+    // subset.
+    minSplit =  0.1*(m_distribution.total())/
+      ((double)trainInstances.numClasses());
+    if (Utils.smOrEq(minSplit,m_minNoObj)) 
+      minSplit = m_minNoObj;
+    else
+      if (Utils.gr(minSplit,25)) 
+	minSplit = 25;
+	
+    // Enough Instances with known values?
+    if (Utils.sm((double)firstMiss,2*minSplit))
+      return;
+    
+    // Compute values of criteria for all possible split
+    // indices.
+    defaultEnt = infoGainCrit.oldEnt(m_distribution);
+    while (next < firstMiss) {
+	  
+      if (trainInstances.instance(next-1).value(m_attIndex)+1e-5 < 
+	  trainInstances.instance(next).value(m_attIndex)) { 
+	
+	// Move class values for all Instances up to next 
+	// possible split point.
+	m_distribution.shiftRange(1,0,trainInstances,last,next);
+	
+	// Check if enough Instances in each subset and compute
+	// values for criteria.
+	if (Utils.grOrEq(m_distribution.perBag(0),minSplit) &&
+	    Utils.grOrEq(m_distribution.perBag(1),minSplit)) {
+	  currentInfoGain = infoGainCrit.
+	    splitCritValue(m_distribution,m_sumOfWeights,
+			   defaultEnt);
+	  if (Utils.gr(currentInfoGain,m_infoGain)) {
+	    m_infoGain = currentInfoGain;
+	    splitIndex = next-1;
+	  }
+	  m_index++;
+	}
+	last = next;
+      }
+      next++;
+    }
+    
+    // Was there any useful split?
+    if (m_index == 0)
+      return;
+    
+    // Compute modified information gain for best split.
+    if (m_useMDLcorrection) {
+      m_infoGain = m_infoGain-(Utils.log2(m_index)/m_sumOfWeights);
+    }
+    if (Utils.smOrEq(m_infoGain,0))
+      return;
+    
+    // Set instance variables' values to values for
+    // best split.
+    m_numSubsets = 2;
+    m_splitPoint = 
+      (trainInstances.instance(splitIndex+1).value(m_attIndex)+
+       trainInstances.instance(splitIndex).value(m_attIndex))/2;
+
+    // In case we have a numerical precision problem we need to choose the
+    // smaller value
+    if (m_splitPoint == trainInstances.instance(splitIndex + 1).value(m_attIndex)) {
+      m_splitPoint = trainInstances.instance(splitIndex).value(m_attIndex);
+    }
+
+    // Restore distributioN for best split.
+    m_distribution = new Distribution(2,trainInstances.numClasses());
+    m_distribution.addRange(0,trainInstances,0,splitIndex+1);
+    m_distribution.addRange(1,trainInstances,splitIndex+1,firstMiss);
+
+    // Compute modified gain ratio for best split.
+    m_gainRatio = gainRatioCrit.
+      splitCritValue(m_distribution,m_sumOfWeights,
+		     m_infoGain);
+  }
+
+  /**
+   * Returns (C4.5-type) information gain for the generated split.
+   */
+  public final double infoGain() {
+
+    return m_infoGain;
+  }
+
+  /**
+   * Prints left side of condition..
+   *
+   * @param data training set.
+   */
+  public final String leftSide(Instances data) {
+
+    return data.attribute(m_attIndex).name();
+  }
+
+  /**
+   * Prints the condition satisfied by instances in a subset.
+   *
+   * @param index of subset 
+   * @param data training set.
+   */
+  public final String rightSide(int index,Instances data) {
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    if (data.attribute(m_attIndex).isNominal())
+      text.append(" = "+
+		  data.attribute(m_attIndex).value(index));
+    else
+      if (index == 0)
+	text.append(" <= "+
+		    Utils.doubleToString(m_splitPoint,6));
+      else
+	text.append(" > "+
+		    Utils.doubleToString(m_splitPoint,6));
+    return text.toString();
+  }
+  
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+
+    StringBuffer expr = null;
+    if (index < 0) {
+      return "i[" + m_attIndex + "] == null";
+    }
+    if (data.attribute(m_attIndex).isNominal()) {
+      expr = new StringBuffer("i[");
+      expr.append(m_attIndex).append("]");
+      expr.append(".equals(\"").append(data.attribute(m_attIndex)
+				     .value(index)).append("\")");
+    } else {
+      expr = new StringBuffer("((Double) i[");
+      expr.append(m_attIndex).append("])");
+      if (index == 0) {
+	expr.append(".doubleValue() <= ").append(m_splitPoint);
+      } else {
+	expr.append(".doubleValue() > ").append(m_splitPoint);
+      }
+    }
+    return expr.toString();
+  }  
+
+  /**
+   * Sets split point to greatest value in given data smaller or equal to
+   * old split point.
+   * (C4.5 does this for some strange reason).
+   */
+  public final void setSplitPoint(Instances allInstances) {
+    
+    double newSplitPoint = -Double.MAX_VALUE;
+    double tempValue;
+    Instance instance;
+    
+    if ((allInstances.attribute(m_attIndex).isNumeric()) &&
+	(m_numSubsets > 1)) {
+      Enumeration enu = allInstances.enumerateInstances();
+      while (enu.hasMoreElements()) {
+	instance = (Instance) enu.nextElement();
+	if (!instance.isMissing(m_attIndex)) {
+	  tempValue = instance.value(m_attIndex);
+	  if (Utils.gr(tempValue,newSplitPoint) && 
+	      Utils.smOrEq(tempValue,m_splitPoint))
+	    newSplitPoint = tempValue;
+	}
+      }
+      m_splitPoint = newSplitPoint;
+    }
+  }
+  
+  /**
+   * Returns the minsAndMaxs of the index.th subset.
+   */
+  public final double [][] minsAndMaxs(Instances data, double [][] minsAndMaxs,
+				       int index) {
+
+    double [][] newMinsAndMaxs = new double[data.numAttributes()][2];
+    
+    for (int i = 0; i < data.numAttributes(); i++) {
+      newMinsAndMaxs[i][0] = minsAndMaxs[i][0];
+      newMinsAndMaxs[i][1] = minsAndMaxs[i][1];
+      if (i == m_attIndex)
+	if (data.attribute(m_attIndex).isNominal())
+	  newMinsAndMaxs[m_attIndex][1] = 1;
+	else
+	  newMinsAndMaxs[m_attIndex][1-index] = m_splitPoint;
+    }
+
+    return newMinsAndMaxs;
+  }
+  
+  /**
+   * Sets distribution associated with model.
+   */
+  public void resetDistribution(Instances data) throws Exception {
+    
+    Instances insts = new Instances(data, data.numInstances());
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (whichSubset(data.instance(i)) > -1) {
+	insts.add(data.instance(i));
+      }
+    }
+    Distribution newD = new Distribution(insts, this);
+    newD.addInstWithUnknown(data, m_attIndex);
+    m_distribution = newD;
+  }
+
+  /**
+   * Returns weights if instance is assigned to more than one subset.
+   * Returns null if instance is only assigned to one subset.
+   */
+  public final double [] weights(Instance instance) {
+    
+    double [] weights;
+    int i;
+    
+    if (instance.isMissing(m_attIndex)) {
+      weights = new double [m_numSubsets];
+      for (i=0;i<m_numSubsets;i++)
+	weights [i] = m_distribution.perBag(i)/m_distribution.total();
+      return weights;
+    }else{
+      return null;
+    }
+  }
+  
+  /**
+   * Returns index of subset instance is assigned to.
+   * Returns -1 if instance is assigned to more than one subset.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final int whichSubset(Instance instance) 
+       throws Exception {
+    
+    if (instance.isMissing(m_attIndex))
+      return -1;
+    else{
+      if (instance.attribute(m_attIndex).isNominal())
+	return (int)instance.value(m_attIndex);
+      else
+	if (Utils.smOrEq(instance.value(m_attIndex),m_splitPoint))
+	  return 0;
+	else
+	  return 1;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6073 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierSplitModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierSplitModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierSplitModel.java	(revision 29)
@@ -0,0 +1,293 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierSplitModel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/** 
+ * Abstract class for classification models that can be used 
+ * recursively to split the data.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public abstract class ClassifierSplitModel
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4280730118393457457L;
+
+  /** Distribution of class values. */  
+  protected Distribution m_distribution;  
+
+  /** Number of created subsets. */
+  protected int m_numSubsets;         
+
+  /**
+   * Allows to clone a model (shallow copy).
+   */
+  public Object clone() {
+
+    Object clone = null;
+    
+    try {
+      clone = super.clone();
+    } catch (CloneNotSupportedException e) {
+    } 
+    return clone;
+  }
+
+  /**
+   * Builds the classifier split model for the given set of instances.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public abstract void buildClassifier(Instances instances) throws Exception;
+  
+  /**
+   * Checks if generated model is valid.
+   */
+  public final boolean checkModel() {
+    
+    if (m_numSubsets > 0)
+      return true;
+    else
+      return false;
+  }
+  
+  /**
+   * Classifies a given instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final double classifyInstance(Instance instance)
+       throws Exception {
+    
+    int theSubset;
+    
+    theSubset = whichSubset(instance);
+    if (theSubset > -1)
+      return (double)m_distribution.maxClass(theSubset);
+    else
+      return (double)m_distribution.maxClass();
+  }
+
+  /**
+   * Gets class probability for instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double classProb(int classIndex, Instance instance, int theSubset) 
+       throws Exception {
+
+    if (theSubset > -1) {
+      return m_distribution.prob(classIndex,theSubset);
+    } else {
+      double [] weights = weights(instance);
+      if (weights == null) {
+	return m_distribution.prob(classIndex);
+      } else {
+	double prob = 0;
+	for (int i = 0; i < weights.length; i++) {
+	  prob += weights[i] * m_distribution.prob(classIndex, i);
+	}
+	return prob;
+      }
+    }
+  }
+
+  /**
+   * Gets class probability for instance.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public double classProbLaplace(int classIndex, Instance instance,
+				 int theSubset) throws Exception {
+
+    if (theSubset > -1) {
+      return m_distribution.laplaceProb(classIndex, theSubset);
+    } else {
+      double [] weights = weights(instance);
+      if (weights == null) {
+	return m_distribution.laplaceProb(classIndex);
+      } else {
+	double prob = 0;
+	for (int i = 0; i < weights.length; i++) {
+	  prob += weights[i] * m_distribution.laplaceProb(classIndex, i);
+	}
+	return prob;
+      }
+    }
+  }
+
+  /**
+   * Returns coding costs of model. Returns 0 if not overwritten.
+   */
+  public double codingCost() {
+
+    return 0;
+  }
+
+  /**
+   * Returns the distribution of class values induced by the model.
+   */
+  public final Distribution distribution() {
+
+    return m_distribution;
+  }
+
+  /**
+   * Prints left side of condition satisfied by instances.
+   *
+   * @param data the data.
+   */
+  public abstract String leftSide(Instances data);
+
+  /**
+   * Prints left side of condition satisfied by instances in subset index.
+   */
+  public abstract String rightSide(int index,Instances data);
+
+  /**
+   * Prints label for subset index of instances (eg class).
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final String dumpLabel(int index,Instances data) throws Exception {
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    text.append(((Instances)data).classAttribute().
+		value(m_distribution.maxClass(index)));
+    text.append(" ("+Utils.roundDouble(m_distribution.perBag(index),2));
+    if (Utils.gr(m_distribution.numIncorrect(index),0))
+      text.append("/"+Utils.roundDouble(m_distribution.numIncorrect(index),2));
+    text.append(")");
+
+    return text.toString();
+  }
+  
+  public final String sourceClass(int index, Instances data) throws Exception {
+
+    System.err.println("sourceClass");
+    return (new StringBuffer(m_distribution.maxClass(index))).toString();
+  }
+
+  public abstract String sourceExpression(int index, Instances data);
+
+  /**
+   * Prints the split model.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final String dumpModel(Instances data) throws Exception {
+
+    StringBuffer text;
+    int i;
+
+    text = new StringBuffer();
+    for (i=0;i<m_numSubsets;i++) {
+      text.append(leftSide(data)+rightSide(i,data)+": ");
+      text.append(dumpLabel(i,data)+"\n");
+    }
+    return text.toString();
+  }
+ 
+  /**
+   * Returns the number of created subsets for the split.
+   */
+  public final int numSubsets() {
+
+    return m_numSubsets;
+  }
+  
+  /**
+   * Sets distribution associated with model.
+   */
+  public void resetDistribution(Instances data) throws Exception {
+
+    m_distribution = new Distribution(data, this);
+  }
+
+  /**
+   * Splits the given set of instances into subsets.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final Instances [] split(Instances data) 
+       throws Exception { 
+
+    Instances [] instances = new Instances [m_numSubsets];
+    double [] weights;
+    double newWeight;
+    Instance instance;
+    int subset, i, j;
+
+    for (j=0;j<m_numSubsets;j++)
+      instances[j] = new Instances((Instances)data,
+					    data.numInstances());
+    for (i = 0; i < data.numInstances(); i++) {
+      instance = ((Instances) data).instance(i);
+      weights = weights(instance);
+      subset = whichSubset(instance);
+      if (subset > -1)
+	instances[subset].add(instance);
+      else
+	for (j = 0; j < m_numSubsets; j++)
+	  if (Utils.gr(weights[j],0)) {
+	    newWeight = weights[j]*instance.weight();
+	    instances[j].add(instance);
+	    instances[j].lastInstance().setWeight(newWeight);
+	  }
+    }
+    for (j = 0; j < m_numSubsets; j++)
+      instances[j].compactify();
+    
+    return instances;
+  }
+
+  /**
+   * Returns weights if instance is assigned to more than one subset.
+   * Returns null if instance is only assigned to one subset.
+   */
+  public abstract double [] weights(Instance instance);
+  
+  /**
+   * Returns index of subset instance is assigned to.
+   * Returns -1 if instance is assigned to more than one subset.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public abstract int whichSubset(Instance instance) throws Exception;
+}
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ClassifierTree.java	(revision 29)
@@ -0,0 +1,713 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierTree.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * Class for handling a tree structure used for
+ * classification.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5530 $
+ */
+public class ClassifierTree 
+  implements Drawable, Serializable, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8722249377542734193L;
+  
+  /** The model selection method. */  
+  protected ModelSelection m_toSelectModel;     
+
+  /** Local model at node. */
+  protected ClassifierSplitModel m_localModel;  
+
+  /** References to sons. */
+  protected ClassifierTree [] m_sons;           
+
+  /** True if node is leaf. */
+  protected boolean m_isLeaf;                   
+
+  /** True if node is empty. */
+  protected boolean m_isEmpty;                  
+
+  /** The training instances. */
+  protected Instances m_train;                  
+
+  /** The pruning instances. */
+  protected Distribution m_test;     
+
+  /** The id for the node. */
+  protected int m_id;
+
+  /** 
+   * For getting a unique ID when outputting the tree (hashcode isn't
+   * guaranteed unique) 
+   */
+  private static long PRINTED_NODES = 0;
+
+  /**
+   * Gets the next unique node ID.
+   *
+   * @return the next unique node ID.
+   */
+  protected static long nextID() {
+
+    return PRINTED_NODES ++;
+  }
+
+  /**
+   * Resets the unique node ID counter (e.g.
+   * between repeated separate print types)
+   */
+  protected static void resetID() {
+
+    PRINTED_NODES = 0;
+  }
+
+  /**
+   * Constructor. 
+   */
+  public ClassifierTree(ModelSelection toSelectLocModel) {
+    
+    m_toSelectModel = toSelectLocModel;
+  }
+
+  /**
+   * Returns default capabilities of the classifier tree.
+   *
+   * @return      the capabilities of this classifier tree
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.enableAll();
+    
+    return result;
+  }
+
+  /**
+   * Method for building a classifier tree.
+   *
+   * @param data the data to build the tree from
+   * @throws Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // can classifier tree handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    buildTree(data, false);
+  }
+
+  /**
+   * Builds the tree structure.
+   *
+   * @param data the data for which the tree structure is to be
+   * generated.
+   * @param keepData is training data to be kept?
+   * @throws Exception if something goes wrong
+   */
+  public void buildTree(Instances data, boolean keepData) throws Exception {
+    
+    Instances [] localInstances;
+
+    if (keepData) {
+      m_train = data;
+    }
+    m_test = null;
+    m_isLeaf = false;
+    m_isEmpty = false;
+    m_sons = null;
+    m_localModel = m_toSelectModel.selectModel(data);
+    if (m_localModel.numSubsets() > 1) {
+      localInstances = m_localModel.split(data);
+      data = null;
+      m_sons = new ClassifierTree [m_localModel.numSubsets()];
+      for (int i = 0; i < m_sons.length; i++) {
+	m_sons[i] = getNewTree(localInstances[i]);
+	localInstances[i] = null;
+      }
+    }else{
+      m_isLeaf = true;
+      if (Utils.eq(data.sumOfWeights(), 0))
+	m_isEmpty = true;
+      data = null;
+    }
+  }
+
+  /**
+   * Builds the tree structure with hold out set
+   *
+   * @param train the data for which the tree structure is to be
+   * generated.
+   * @param test the test data for potential pruning
+   * @param keepData is training Data to be kept?
+   * @throws Exception if something goes wrong
+   */
+  public void buildTree(Instances train, Instances test, boolean keepData)
+       throws Exception {
+    
+    Instances [] localTrain, localTest;
+    int i;
+    
+    if (keepData) {
+      m_train = train;
+    }
+    m_isLeaf = false;
+    m_isEmpty = false;
+    m_sons = null;
+    m_localModel = m_toSelectModel.selectModel(train, test);
+    m_test = new Distribution(test, m_localModel);
+    if (m_localModel.numSubsets() > 1) {
+      localTrain = m_localModel.split(train);
+      localTest = m_localModel.split(test);
+      train = test = null;
+      m_sons = new ClassifierTree [m_localModel.numSubsets()];
+      for (i=0;i<m_sons.length;i++) {
+	m_sons[i] = getNewTree(localTrain[i], localTest[i]);
+	localTrain[i] = null;
+	localTest[i] = null;
+      }
+    }else{
+      m_isLeaf = true;
+      if (Utils.eq(train.sumOfWeights(), 0))
+	m_isEmpty = true;
+      train = test = null;
+    }
+  }
+
+  /** 
+   * Classifies an instance.
+   *
+   * @param instance the instance to classify
+   * @return the classification
+   * @throws Exception if something goes wrong
+   */
+  public double classifyInstance(Instance instance) 
+    throws Exception {
+
+    double maxProb = -1;
+    double currentProb;
+    int maxIndex = 0;
+    int j;
+
+    for (j = 0; j < instance.numClasses(); j++) {
+      currentProb = getProbs(j, instance, 1);
+      if (Utils.gr(currentProb,maxProb)) {
+	maxIndex = j;
+	maxProb = currentProb;
+      }
+    }
+
+    return (double)maxIndex;
+  }
+
+  /**
+   * Cleanup in order to save memory.
+   * 
+   * @param justHeaderInfo
+   */
+  public final void cleanup(Instances justHeaderInfo) {
+
+    m_train = justHeaderInfo;
+    m_test = null;
+    if (!m_isLeaf)
+      for (int i = 0; i < m_sons.length; i++)
+	m_sons[i].cleanup(justHeaderInfo);
+  }
+
+  /** 
+   * Returns class probabilities for a weighted instance.
+   *
+   * @param instance the instance to get the distribution for
+   * @param useLaplace whether to use laplace or not
+   * @return the distribution
+   * @throws Exception if something goes wrong
+   */
+  public final double [] distributionForInstance(Instance instance,
+						 boolean useLaplace) 
+       throws Exception {
+
+    double [] doubles = new double[instance.numClasses()];
+
+    for (int i = 0; i < doubles.length; i++) {
+      if (!useLaplace) {
+	doubles[i] = getProbs(i, instance, 1);
+      } else {
+	doubles[i] = getProbsLaplace(i, instance, 1);
+      }
+    }
+
+    return doubles;
+  }
+
+  /**
+   * Assigns a uniqe id to every node in the tree.
+   * 
+   * @param lastID the last ID that was assign
+   * @return the new current ID
+   */
+  public int assignIDs(int lastID) {
+
+    int currLastID = lastID + 1;
+
+    m_id = currLastID;
+    if (m_sons != null) {
+      for (int i = 0; i < m_sons.length; i++) {
+	currLastID = m_sons[i].assignIDs(currLastID);
+      }
+    }
+    return currLastID;
+  }
+
+  /**
+   *  Returns the type of graph this classifier
+   *  represents.
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @throws Exception if something goes wrong
+   * @return the tree as graph
+   */
+  public String graph() throws Exception {
+
+    StringBuffer text = new StringBuffer();
+
+    assignIDs(-1);
+    text.append("digraph J48Tree {\n");
+    if (m_isLeaf) {
+      text.append("N" + m_id 
+		  + " [label=\"" + 
+		  m_localModel.dumpLabel(0,m_train) + "\" " + 
+		  "shape=box style=filled ");
+      if (m_train != null && m_train.numInstances() > 0) {
+	text.append("data =\n" + m_train + "\n");
+	text.append(",\n");
+
+      }
+      text.append("]\n");
+    }else {
+      text.append("N" + m_id 
+		  + " [label=\"" + 
+		  m_localModel.leftSide(m_train) + "\" ");
+      if (m_train != null && m_train.numInstances() > 0) {
+	text.append("data =\n" + m_train + "\n");
+	text.append(",\n");
+     }
+      text.append("]\n");
+      graphTree(text);
+    }
+    
+    return text.toString() +"}\n";
+  }
+
+  /**
+   * Returns tree in prefix order.
+   *
+   * @throws Exception if something goes wrong
+   * @return the prefix order
+   */
+  public String prefix() throws Exception {
+    
+    StringBuffer text;
+
+    text = new StringBuffer();
+    if (m_isLeaf) {
+      text.append("["+m_localModel.dumpLabel(0,m_train)+"]");
+    }else {
+      prefixTree(text);
+    }
+    
+    return text.toString();
+  }
+
+  /**
+   * Returns source code for the tree as an if-then statement. The 
+   * class is assigned to variable "p", and assumes the tested 
+   * instance is named "i". The results are returned as two stringbuffers: 
+   * a section of code for assignment of the class, and a section of
+   * code containing support code (eg: other support methods).
+   *
+   * @param className the classname that this static classifier has
+   * @return an array containing two stringbuffers, the first string containing
+   * assignment code, and the second containing source for support code.
+   * @throws Exception if something goes wrong
+   */
+  public StringBuffer [] toSource(String className) throws Exception {
+    
+    StringBuffer [] result = new StringBuffer [2];
+    if (m_isLeaf) {
+      result[0] = new StringBuffer("    p = " 
+	+ m_localModel.distribution().maxClass(0) + ";\n");
+      result[1] = new StringBuffer("");
+    } else {
+      StringBuffer text = new StringBuffer();
+      StringBuffer atEnd = new StringBuffer();
+
+      long printID = ClassifierTree.nextID();
+
+      text.append("  static double N") 
+	.append(Integer.toHexString(m_localModel.hashCode()) + printID)
+	.append("(Object []i) {\n")
+	.append("    double p = Double.NaN;\n");
+
+      text.append("    if (")
+	.append(m_localModel.sourceExpression(-1, m_train))
+	.append(") {\n");
+      text.append("      p = ")
+	.append(m_localModel.distribution().maxClass(0))
+	.append(";\n");
+      text.append("    } ");
+      for (int i = 0; i < m_sons.length; i++) {
+	text.append("else if (" + m_localModel.sourceExpression(i, m_train) 
+		    + ") {\n");
+	if (m_sons[i].m_isLeaf) {
+	  text.append("      p = " 
+		      + m_localModel.distribution().maxClass(i) + ";\n");
+	} else {
+	  StringBuffer [] sub = m_sons[i].toSource(className);
+	  text.append(sub[0]);
+	  atEnd.append(sub[1]);
+	}
+	text.append("    } ");
+	if (i == m_sons.length - 1) {
+	  text.append('\n');
+	}
+      }
+
+      text.append("    return p;\n  }\n");
+
+      result[0] = new StringBuffer("    p = " + className + ".N");
+      result[0].append(Integer.toHexString(m_localModel.hashCode()) +  printID)
+	.append("(i);\n");
+      result[1] = text.append(atEnd);
+    }
+    return result;
+  }
+
+  /**
+   * Returns number of leaves in tree structure.
+   * 
+   * @return the number of leaves
+   */
+  public int numLeaves() {
+    
+    int num = 0;
+    int i;
+    
+    if (m_isLeaf)
+      return 1;
+    else
+      for (i=0;i<m_sons.length;i++)
+	num = num+m_sons[i].numLeaves();
+        
+    return num;
+  }
+
+  /**
+   * Returns number of nodes in tree structure.
+   * 
+   * @return the number of nodes
+   */
+  public int numNodes() {
+    
+    int no = 1;
+    int i;
+    
+    if (!m_isLeaf)
+      for (i=0;i<m_sons.length;i++)
+	no = no+m_sons[i].numNodes();
+    
+    return no;
+  }
+
+  /**
+   * Prints tree structure.
+   * 
+   * @return the tree structure
+   */
+  public String toString() {
+
+    try {
+      StringBuffer text = new StringBuffer();
+      
+      if (m_isLeaf) {
+	text.append(": ");
+	text.append(m_localModel.dumpLabel(0,m_train));
+      }else
+	dumpTree(0,text);
+      text.append("\n\nNumber of Leaves  : \t"+numLeaves()+"\n");
+      text.append("\nSize of the tree : \t"+numNodes()+"\n");
+ 
+      return text.toString();
+    } catch (Exception e) {
+      return "Can't print classification tree.";
+    }
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param data the training data
+   * @return the generated tree
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances data) throws Exception {
+	 
+    ClassifierTree newTree = new ClassifierTree(m_toSelectModel);
+    newTree.buildTree(data, false);
+    
+    return newTree;
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param train the training data
+   * @param test the pruning data.
+   * @return the generated tree
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances train, Instances test) 
+       throws Exception {
+	 
+    ClassifierTree newTree = new ClassifierTree(m_toSelectModel);
+    newTree.buildTree(train, test, false);
+    
+    return newTree;
+  }
+
+  /**
+   * Help method for printing tree structure.
+   *
+   * @param depth the current depth
+   * @param text for outputting the structure
+   * @throws Exception if something goes wrong
+   */
+  private void dumpTree(int depth, StringBuffer text) 
+       throws Exception {
+    
+    int i,j;
+    
+    for (i=0;i<m_sons.length;i++) {
+      text.append("\n");;
+      for (j=0;j<depth;j++)
+	text.append("|   ");
+      text.append(m_localModel.leftSide(m_train));
+      text.append(m_localModel.rightSide(i, m_train));
+      if (m_sons[i].m_isLeaf) {
+	text.append(": ");
+	text.append(m_localModel.dumpLabel(i,m_train));
+      }else
+	m_sons[i].dumpTree(depth+1,text);
+    }
+  }
+
+  /**
+   * Help method for printing tree structure as a graph.
+   *
+   * @param text for outputting the tree
+   * @throws Exception if something goes wrong
+   */
+  private void graphTree(StringBuffer text) throws Exception {
+    
+    for (int i = 0; i < m_sons.length; i++) {
+      text.append("N" + m_id  
+		  + "->" + 
+		  "N" + m_sons[i].m_id +
+		  " [label=\"" + m_localModel.rightSide(i,m_train).trim() + 
+		  "\"]\n");
+      if (m_sons[i].m_isLeaf) {
+	text.append("N" + m_sons[i].m_id +
+		    " [label=\""+m_localModel.dumpLabel(i,m_train)+"\" "+ 
+		    "shape=box style=filled ");
+	if (m_train != null && m_train.numInstances() > 0) {
+	  text.append("data =\n" + m_sons[i].m_train + "\n");
+	  text.append(",\n");
+	}
+	text.append("]\n");
+      } else {
+	text.append("N" + m_sons[i].m_id +
+		    " [label=\""+m_sons[i].m_localModel.leftSide(m_train) + 
+		    "\" ");
+	if (m_train != null && m_train.numInstances() > 0) {
+	  text.append("data =\n" + m_sons[i].m_train + "\n");
+	  text.append(",\n");
+	}
+	text.append("]\n");
+	m_sons[i].graphTree(text);
+      }
+    }
+  }
+
+  /**
+   * Prints the tree in prefix form
+   * 
+   * @param text the buffer to output the prefix form to
+   * @throws Exception if something goes wrong
+   */
+  private void prefixTree(StringBuffer text) throws Exception {
+
+    text.append("[");
+    text.append(m_localModel.leftSide(m_train)+":");
+    for (int i = 0; i < m_sons.length; i++) {
+      if (i > 0) {
+	text.append(",\n");
+      }
+      text.append(m_localModel.rightSide(i, m_train));
+    }
+    for (int i = 0; i < m_sons.length; i++) {
+      if (m_sons[i].m_isLeaf) {
+	text.append("[");
+	text.append(m_localModel.dumpLabel(i,m_train));
+	text.append("]");
+      } else {
+	m_sons[i].prefixTree(text);
+      }
+    }
+    text.append("]");
+  }
+
+  /**
+   * Help method for computing class probabilities of 
+   * a given instance.
+   *
+   * @param classIndex the class index
+   * @param instance the instance to compute the probabilities for
+   * @param weight the weight to use
+   * @return the laplace probs
+   * @throws Exception if something goes wrong
+   */
+  private double getProbsLaplace(int classIndex, Instance instance, double weight) 
+    throws Exception {
+    
+    double prob = 0;
+    
+    if (m_isLeaf) {
+      return weight * localModel().classProbLaplace(classIndex, instance, -1);
+    } else {
+      int treeIndex = localModel().whichSubset(instance);
+      if (treeIndex == -1) {
+	double[] weights = localModel().weights(instance);
+	for (int i = 0; i < m_sons.length; i++) {
+	  if (!son(i).m_isEmpty) {
+        prob += son(i).getProbsLaplace(classIndex, instance, 
+					     weights[i] * weight);
+	  }
+	}
+	return prob;
+      } else {
+	if (son(treeIndex).m_isEmpty) {
+	  return weight * localModel().classProbLaplace(classIndex, instance, 
+							treeIndex);
+	} else {
+	  return son(treeIndex).getProbsLaplace(classIndex, instance, weight);
+	}
+      }
+    }
+  }
+
+  /**
+   * Help method for computing class probabilities of 
+   * a given instance.
+   * 
+   * @param classIndex the class index
+   * @param instance the instance to compute the probabilities for
+   * @param weight the weight to use
+   * @return the probs
+   * @throws Exception if something goes wrong
+   */
+  private double getProbs(int classIndex, Instance instance, double weight) 
+    throws Exception {
+    
+    double prob = 0;
+    
+    if (m_isLeaf) {
+      return weight * localModel().classProb(classIndex, instance, -1);
+    } else {
+      int treeIndex = localModel().whichSubset(instance);
+      if (treeIndex == -1) {
+	double[] weights = localModel().weights(instance);
+	for (int i = 0; i < m_sons.length; i++) {
+	  if (!son(i).m_isEmpty) {
+	    prob += son(i).getProbs(classIndex, instance, 
+				    weights[i] * weight);
+	  }
+	}
+	return prob;
+      } else {
+	if (son(treeIndex).m_isEmpty) {
+	  return weight * localModel().classProb(classIndex, instance, 
+						 treeIndex);
+	} else {
+	  return son(treeIndex).getProbs(classIndex, instance, weight);
+	}
+      }
+    }
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private ClassifierSplitModel localModel() {
+    
+    return (ClassifierSplitModel)m_localModel;
+  }
+  
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private ClassifierTree son(int index) {
+    
+    return (ClassifierTree)m_sons[index];
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5530 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Distribution.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Distribution.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Distribution.java	(revision 29)
@@ -0,0 +1,750 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Distribution.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+/**
+ * Class for handling a distribution of class values.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.12 $
+ */
+public class Distribution
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8526859638230806576L;
+
+  /** Weight of instances per class per bag. */
+  private double m_perClassPerBag[][]; 
+
+  /** Weight of instances per bag. */
+  private double m_perBag[];           
+
+  /** Weight of instances per class. */
+  private double m_perClass[];         
+
+  /** Total weight of instances. */
+  private double totaL;            
+
+  /**
+   * Creates and initializes a new distribution.
+   */
+  public Distribution(int numBags,int numClasses) {
+
+    int i;
+
+    m_perClassPerBag = new double [numBags][0];
+    m_perBag = new double [numBags];
+    m_perClass = new double [numClasses];
+    for (i=0;i<numBags;i++)
+      m_perClassPerBag[i] = new double [numClasses];
+    totaL = 0;
+  }
+
+  /**
+   * Creates and initializes a new distribution using the given
+   * array. WARNING: it just copies a reference to this array.
+   */
+  public Distribution(double [][] table) {
+
+    int i, j;
+
+    m_perClassPerBag = table;
+    m_perBag = new double [table.length];
+    m_perClass = new double [table[0].length];
+    for (i = 0; i < table.length; i++) 
+      for (j  = 0; j < table[i].length; j++) {
+	m_perBag[i] += table[i][j];
+	m_perClass[j] += table[i][j];
+	totaL += table[i][j];
+      }
+  }
+
+  /**
+   * Creates a distribution with only one bag according
+   * to instances in source.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public Distribution(Instances source) throws Exception {
+    
+    m_perClassPerBag = new double [1][0];
+    m_perBag = new double [1];
+    totaL = 0;
+    m_perClass = new double [source.numClasses()];
+    m_perClassPerBag[0] = new double [source.numClasses()];
+    Enumeration enu = source.enumerateInstances();
+    while (enu.hasMoreElements())
+      add(0,(Instance) enu.nextElement());
+  }
+
+  /**
+   * Creates a distribution according to given instances and
+   * split model.
+   *
+   * @exception Exception if something goes wrong
+   */
+
+  public Distribution(Instances source, 
+		      ClassifierSplitModel modelToUse)
+       throws Exception {
+
+    int index;
+    Instance instance;
+    double[] weights;
+
+    m_perClassPerBag = new double [modelToUse.numSubsets()][0];
+    m_perBag = new double [modelToUse.numSubsets()];
+    totaL = 0;
+    m_perClass = new double [source.numClasses()];
+    for (int i = 0; i < modelToUse.numSubsets(); i++)
+      m_perClassPerBag[i] = new double [source.numClasses()];
+    Enumeration enu = source.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      index = modelToUse.whichSubset(instance);
+      if (index != -1)
+	add(index, instance);
+      else {
+	weights = modelToUse.weights(instance);
+	addWeights(instance, weights);
+      }
+    }
+  }
+
+  /**
+   * Creates distribution with only one bag by merging all
+   * bags of given distribution.
+   */
+  public Distribution(Distribution toMerge) {
+
+    totaL = toMerge.totaL;
+    m_perClass = new double [toMerge.numClasses()];
+    System.arraycopy(toMerge.m_perClass,0,m_perClass,0,toMerge.numClasses());
+    m_perClassPerBag = new double [1] [0];
+    m_perClassPerBag[0] = new double [toMerge.numClasses()];
+    System.arraycopy(toMerge.m_perClass,0,m_perClassPerBag[0],0,
+		     toMerge.numClasses());
+    m_perBag = new double [1];
+    m_perBag[0] = totaL;
+  }
+
+  /**
+   * Creates distribution with two bags by merging all bags apart of
+   * the indicated one.
+   */
+  public Distribution(Distribution toMerge, int index) {
+
+    int i;
+
+    totaL = toMerge.totaL;
+    m_perClass = new double [toMerge.numClasses()];
+    System.arraycopy(toMerge.m_perClass,0,m_perClass,0,toMerge.numClasses());
+    m_perClassPerBag = new double [2] [0];
+    m_perClassPerBag[0] = new double [toMerge.numClasses()];
+    System.arraycopy(toMerge.m_perClassPerBag[index],0,m_perClassPerBag[0],0,
+		     toMerge.numClasses());
+    m_perClassPerBag[1] = new double [toMerge.numClasses()];
+    for (i=0;i<toMerge.numClasses();i++)
+      m_perClassPerBag[1][i] = toMerge.m_perClass[i]-m_perClassPerBag[0][i];
+    m_perBag = new double [2];
+    m_perBag[0] = toMerge.m_perBag[index];
+    m_perBag[1] = totaL-m_perBag[0];
+  }
+  
+  /**
+   * Returns number of non-empty bags of distribution.
+   */
+  public final int actualNumBags() {
+    
+    int returnValue = 0;
+    int i;
+
+    for (i=0;i<m_perBag.length;i++)
+      if (Utils.gr(m_perBag[i],0))
+	returnValue++;
+    
+    return returnValue;
+  }
+
+  /**
+   * Returns number of classes actually occuring in distribution.
+   */
+  public final int actualNumClasses() {
+
+    int returnValue = 0;
+    int i;
+
+    for (i=0;i<m_perClass.length;i++)
+      if (Utils.gr(m_perClass[i],0))
+	returnValue++;
+    
+    return returnValue;
+  }
+
+  /**
+   * Returns number of classes actually occuring in given bag.
+   */
+  public final int actualNumClasses(int bagIndex) {
+
+    int returnValue = 0;
+    int i;
+
+    for (i=0;i<m_perClass.length;i++)
+      if (Utils.gr(m_perClassPerBag[bagIndex][i],0))
+	returnValue++;
+    
+    return returnValue;
+  }
+
+  /**
+   * Adds given instance to given bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void add(int bagIndex,Instance instance) 
+       throws Exception {
+    
+    int classIndex;
+    double weight;
+
+    classIndex = (int)instance.classValue();
+    weight = instance.weight();
+    m_perClassPerBag[bagIndex][classIndex] = 
+      m_perClassPerBag[bagIndex][classIndex]+weight;
+    m_perBag[bagIndex] = m_perBag[bagIndex]+weight;
+    m_perClass[classIndex] = m_perClass[classIndex]+weight;
+    totaL = totaL+weight;
+  }
+
+  /**
+   * Subtracts given instance from given bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void sub(int bagIndex,Instance instance) 
+       throws Exception {
+    
+    int classIndex;
+    double weight;
+
+    classIndex = (int)instance.classValue();
+    weight = instance.weight();
+    m_perClassPerBag[bagIndex][classIndex] = 
+      m_perClassPerBag[bagIndex][classIndex]-weight;
+    m_perBag[bagIndex] = m_perBag[bagIndex]-weight;
+    m_perClass[classIndex] = m_perClass[classIndex]-weight;
+    totaL = totaL-weight;
+  }
+
+  /**
+   * Adds counts to given bag.
+   */
+  public final void add(int bagIndex, double[] counts) {
+    
+    double sum = Utils.sum(counts);
+
+    for (int i = 0; i < counts.length; i++)
+      m_perClassPerBag[bagIndex][i] += counts[i];
+    m_perBag[bagIndex] = m_perBag[bagIndex]+sum;
+    for (int i = 0; i < counts.length; i++)
+      m_perClass[i] = m_perClass[i]+counts[i];
+    totaL = totaL+sum;
+  }
+
+  /**
+   * Adds all instances with unknown values for given attribute, weighted
+   * according to frequency of instances in each bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void addInstWithUnknown(Instances source,
+				       int attIndex)
+       throws Exception {
+
+    double [] probs;
+    double weight,newWeight;
+    int classIndex;
+    Instance instance;
+    int j;
+
+    probs = new double [m_perBag.length];
+    for (j=0;j<m_perBag.length;j++) {
+      if (Utils.eq(totaL, 0)) {
+	probs[j] = 1.0 / probs.length;
+      } else {
+	probs[j] = m_perBag[j]/totaL;
+      }
+    }
+    Enumeration enu = source.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (instance.isMissing(attIndex)) {
+	classIndex = (int)instance.classValue();
+	weight = instance.weight();
+	m_perClass[classIndex] = m_perClass[classIndex]+weight;
+	totaL = totaL+weight;
+	for (j = 0; j < m_perBag.length; j++) {
+	  newWeight = probs[j]*weight;
+	  m_perClassPerBag[j][classIndex] = m_perClassPerBag[j][classIndex]+
+	    newWeight;
+	  m_perBag[j] = m_perBag[j]+newWeight;
+	}
+      }
+    }
+  }
+
+  /**
+   * Adds all instances in given range to given bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void addRange(int bagIndex,Instances source,
+			     int startIndex, int lastPlusOne)
+       throws Exception {
+
+    double sumOfWeights = 0;
+    int classIndex;
+    Instance instance;
+    int i;
+
+    for (i = startIndex; i < lastPlusOne; i++) {
+      instance = (Instance) source.instance(i);
+      classIndex = (int)instance.classValue();
+      sumOfWeights = sumOfWeights+instance.weight();
+      m_perClassPerBag[bagIndex][classIndex] += instance.weight();
+      m_perClass[classIndex] += instance.weight();
+    }
+    m_perBag[bagIndex] += sumOfWeights;
+    totaL += sumOfWeights;
+  }
+
+  /**
+   * Adds given instance to all bags weighting it according to given weights.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void addWeights(Instance instance, 
+			       double [] weights)
+       throws Exception {
+
+    int classIndex;
+    int i;
+
+    classIndex = (int)instance.classValue();
+    for (i=0;i<m_perBag.length;i++) {
+      double weight = instance.weight() * weights[i];
+      m_perClassPerBag[i][classIndex] = m_perClassPerBag[i][classIndex] + weight;
+      m_perBag[i] = m_perBag[i] + weight;
+      m_perClass[classIndex] = m_perClass[classIndex] + weight;
+      totaL = totaL + weight;
+    }
+  }
+
+  /**
+   * Checks if at least two bags contain a minimum number of instances.
+   */
+  public final boolean check(double minNoObj) {
+
+    int counter = 0;
+    int i;
+
+    for (i=0;i<m_perBag.length;i++)
+      if (Utils.grOrEq(m_perBag[i],minNoObj))
+	counter++;
+    if (counter > 1)
+      return true;
+    else
+      return false;
+  }
+
+  /**
+   * Clones distribution (Deep copy of distribution).
+   */
+  public final Object clone() {
+
+    int i,j;
+
+    Distribution newDistribution = new Distribution (m_perBag.length,
+						     m_perClass.length);
+    for (i=0;i<m_perBag.length;i++) {
+      newDistribution.m_perBag[i] = m_perBag[i];
+      for (j=0;j<m_perClass.length;j++)
+	newDistribution.m_perClassPerBag[i][j] = m_perClassPerBag[i][j];
+    }
+    for (j=0;j<m_perClass.length;j++)
+      newDistribution.m_perClass[j] = m_perClass[j];
+    newDistribution.totaL = totaL;
+  
+    return newDistribution;
+  }
+
+  /**
+   * Deletes given instance from given bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void del(int bagIndex,Instance instance) 
+       throws Exception {
+
+    int classIndex;
+    double weight;
+
+    classIndex = (int)instance.classValue();
+    weight = instance.weight();
+    m_perClassPerBag[bagIndex][classIndex] = 
+      m_perClassPerBag[bagIndex][classIndex]-weight;
+    m_perBag[bagIndex] = m_perBag[bagIndex]-weight;
+    m_perClass[classIndex] = m_perClass[classIndex]-weight;
+    totaL = totaL-weight;
+  }
+
+  /**
+   * Deletes all instances in given range from given bag.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void delRange(int bagIndex,Instances source,
+			     int startIndex, int lastPlusOne)
+       throws Exception {
+
+    double sumOfWeights = 0;
+    int classIndex;
+    Instance instance;
+    int i;
+
+    for (i = startIndex; i < lastPlusOne; i++) {
+      instance = (Instance) source.instance(i);
+      classIndex = (int)instance.classValue();
+      sumOfWeights = sumOfWeights+instance.weight();
+      m_perClassPerBag[bagIndex][classIndex] -= instance.weight();
+      m_perClass[classIndex] -= instance.weight();
+    }
+    m_perBag[bagIndex] -= sumOfWeights;
+    totaL -= sumOfWeights;
+  }
+
+  /**
+   * Prints distribution.
+   */
+  
+  public final String dumpDistribution() {
+
+    StringBuffer text;
+    int i,j;
+
+    text = new StringBuffer();
+    for (i=0;i<m_perBag.length;i++) {
+      text.append("Bag num "+i+"\n");
+      for (j=0;j<m_perClass.length;j++)
+	text.append("Class num "+j+" "+m_perClassPerBag[i][j]+"\n");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Sets all counts to zero.
+   */
+  public final void initialize() {
+
+    for (int i = 0; i < m_perClass.length; i++) 
+      m_perClass[i] = 0;
+    for (int i = 0; i < m_perBag.length; i++)
+      m_perBag[i] = 0;
+    for (int i = 0; i < m_perBag.length; i++)
+      for (int j = 0; j < m_perClass.length; j++)
+	m_perClassPerBag[i][j] = 0;
+    totaL = 0;
+  }
+
+  /**
+   * Returns matrix with distribution of class values.
+   */
+  public final double[][] matrix() {
+
+    return m_perClassPerBag;
+  }
+  
+  /**
+   * Returns index of bag containing maximum number of instances.
+   */
+  public final int maxBag() {
+
+    double max;
+    int maxIndex;
+    int i;
+    
+    max = 0;
+    maxIndex = -1;
+    for (i=0;i<m_perBag.length;i++)
+      if (Utils.grOrEq(m_perBag[i],max)) {
+	max = m_perBag[i];
+	maxIndex = i;
+      }
+    return maxIndex;
+  }
+
+  /**
+   * Returns class with highest frequency over all bags.
+   */
+  public final int maxClass() {
+
+    double maxCount = 0;
+    int maxIndex = 0;
+    int i;
+
+    for (i=0;i<m_perClass.length;i++)
+      if (Utils.gr(m_perClass[i],maxCount)) {
+	maxCount = m_perClass[i];
+	maxIndex = i;
+      }
+
+    return maxIndex;
+  }
+
+  /**
+   * Returns class with highest frequency for given bag.
+   */
+  public final int maxClass(int index) {
+
+    double maxCount = 0;
+    int maxIndex = 0;
+    int i;
+
+    if (Utils.gr(m_perBag[index],0)) {
+      for (i=0;i<m_perClass.length;i++)
+	if (Utils.gr(m_perClassPerBag[index][i],maxCount)) {
+	  maxCount = m_perClassPerBag[index][i];
+	  maxIndex = i;
+	}
+      return maxIndex;
+    }else
+      return maxClass();
+  }
+
+  /**
+   * Returns number of bags.
+   */
+  public final int numBags() {
+    
+    return m_perBag.length;
+  }
+
+  /**
+   * Returns number of classes.
+   */
+  public final int numClasses() {
+
+    return m_perClass.length;
+  }
+
+  /**
+   * Returns perClass(maxClass()).
+   */
+  public final double numCorrect() {
+
+    return m_perClass[maxClass()];
+  }
+
+  /**
+   * Returns perClassPerBag(index,maxClass(index)).
+   */
+  public final double numCorrect(int index) {
+
+    return m_perClassPerBag[index][maxClass(index)];
+  }
+
+  /**
+   * Returns total-numCorrect().
+   */
+  public final double numIncorrect() {
+
+    return totaL-numCorrect();
+  }
+
+  /**
+   * Returns perBag(index)-numCorrect(index).
+   */
+  public final double numIncorrect(int index) {
+
+    return m_perBag[index]-numCorrect(index);
+  }
+
+  /**
+   * Returns number of (possibly fractional) instances of given class in 
+   * given bag.
+   */
+  public final double perClassPerBag(int bagIndex, int classIndex) {
+
+    return m_perClassPerBag[bagIndex][classIndex];
+  }
+
+  /**
+   * Returns number of (possibly fractional) instances in given bag.
+   */
+  public final double perBag(int bagIndex) {
+
+    return m_perBag[bagIndex];
+  }
+
+  /**
+   * Returns number of (possibly fractional) instances of given class.
+   */
+  public final double perClass(int classIndex) {
+
+    return m_perClass[classIndex];
+  }
+
+  /**
+   * Returns relative frequency of class over all bags with
+   * Laplace correction.
+   */
+  public final double laplaceProb(int classIndex) {
+
+    return (m_perClass[classIndex] + 1) / 
+      (totaL + (double) m_perClass.length);
+  }
+
+  /**
+   * Returns relative frequency of class for given bag.
+   */
+  public final double laplaceProb(int classIndex, int intIndex) {
+
+	  if (Utils.gr(m_perBag[intIndex],0))
+		return (m_perClassPerBag[intIndex][classIndex] + 1.0) /
+	           (m_perBag[intIndex] + (double) m_perClass.length);
+	  else
+	    return laplaceProb(classIndex);
+	  
+  }
+
+  /**
+   * Returns relative frequency of class over all bags.
+   */
+  public final double prob(int classIndex) {
+
+    if (!Utils.eq(totaL, 0)) {
+      return m_perClass[classIndex]/totaL;
+    } else {
+      return 0;
+    }
+  }
+
+  /**
+   * Returns relative frequency of class for given bag.
+   */
+  public final double prob(int classIndex,int intIndex) {
+
+    if (Utils.gr(m_perBag[intIndex],0))
+      return m_perClassPerBag[intIndex][classIndex]/m_perBag[intIndex];
+    else
+      return prob(classIndex);
+  }
+
+  /** 
+   * Subtracts the given distribution from this one. The results
+   * has only one bag.
+   */
+  public final Distribution subtract(Distribution toSubstract) {
+
+    Distribution newDist = new Distribution(1,m_perClass.length);
+
+    newDist.m_perBag[0] = totaL-toSubstract.totaL;
+    newDist.totaL = newDist.m_perBag[0];
+    for (int i = 0; i < m_perClass.length; i++) {
+      newDist.m_perClassPerBag[0][i] = m_perClass[i] - toSubstract.m_perClass[i];
+      newDist.m_perClass[i] = newDist.m_perClassPerBag[0][i];
+    }
+    return newDist;
+  }
+
+  /**
+   * Returns total number of (possibly fractional) instances.
+   */
+  public final double total() {
+
+    return totaL;
+  }
+
+  /**
+   * Shifts given instance from one bag to another one.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void shift(int from,int to,Instance instance) 
+       throws Exception {
+    
+    int classIndex;
+    double weight;
+
+    classIndex = (int)instance.classValue();
+    weight = instance.weight();
+    m_perClassPerBag[from][classIndex] -= weight;
+    m_perClassPerBag[to][classIndex] += weight;
+    m_perBag[from] -= weight;
+    m_perBag[to] += weight;
+  }
+
+  /**
+   * Shifts all instances in given range from one bag to another one.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final void shiftRange(int from,int to,Instances source,
+			       int startIndex,int lastPlusOne) 
+       throws Exception {
+    
+    int classIndex;
+    double weight;
+    Instance instance;
+    int i;
+
+    for (i = startIndex; i < lastPlusOne; i++) {
+      instance = (Instance) source.instance(i);
+      classIndex = (int)instance.classValue();
+      weight = instance.weight();
+      m_perClassPerBag[from][classIndex] -= weight;
+      m_perClassPerBag[to][classIndex] += weight;
+      m_perBag[from] -= weight;
+      m_perBag[to] += weight;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.12 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropyBasedSplitCrit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropyBasedSplitCrit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropyBasedSplitCrit.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EntropyBasedSplitCrit.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+/**
+ * "Abstract" class for computing splitting criteria
+ * based on the entropy of a class distribution.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public abstract class EntropyBasedSplitCrit
+  extends SplitCriterion {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2618691439791653056L;
+
+  /** The log of 2. */
+  protected static double log2 = Math.log(2);
+
+  /**
+   * Help method for computing entropy.
+   */
+  public final double logFunc(double num) {
+
+    // Constant hard coded for efficiency reasons
+    if (num < 1e-6)
+      return 0;
+    else
+      return num*Math.log(num)/log2;
+  }
+
+  /**
+   * Computes entropy of distribution before splitting.
+   */
+  public final double oldEnt(Distribution bags) {
+
+    double returnValue = 0;
+    int j;
+
+    for (j=0;j<bags.numClasses();j++)
+      returnValue = returnValue+logFunc(bags.perClass(j));
+    return logFunc(bags.total())-returnValue; 
+  }
+
+  /**
+   * Computes entropy of distribution after splitting.
+   */
+  public final double newEnt(Distribution bags) {
+    
+    double returnValue = 0;
+    int i,j;
+
+    for (i=0;i<bags.numBags();i++){
+      for (j=0;j<bags.numClasses();j++)
+	returnValue = returnValue+logFunc(bags.perClassPerBag(i,j));
+      returnValue = returnValue-logFunc(bags.perBag(i));
+    }
+    return -returnValue;
+  }
+
+  /**
+   * Computes entropy after splitting without considering the
+   * class values.
+   */
+  public final double splitEnt(Distribution bags) {
+
+    double returnValue = 0;
+    int i;
+
+    for (i=0;i<bags.numBags();i++)
+      returnValue = returnValue+logFunc(bags.perBag(i));
+    return logFunc(bags.total())-returnValue;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropySplitCrit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropySplitCrit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/EntropySplitCrit.java	(revision 29)
@@ -0,0 +1,83 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EntropySplitCrit.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for computing the entropy for a given distribution.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public final class EntropySplitCrit
+  extends EntropyBasedSplitCrit {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5986252682266803935L;
+
+  /**
+   * Computes entropy for given distribution.
+   */
+  public final double splitCritValue(Distribution bags) {
+    
+    return newEnt(bags);
+  }
+
+  /**
+   * Computes entropy of test distribution with respect to training distribution.
+   */
+  public final double splitCritValue(Distribution train, Distribution test) {
+
+    double result = 0;
+    int numClasses = 0;
+    int i, j;
+    
+    // Find out relevant number of classes
+    for (j = 0; j < test.numClasses(); j++)
+      if (Utils.gr(train.perClass(j), 0) || Utils.gr(test.perClass(j), 0))
+	numClasses++;
+
+    // Compute entropy of test data with respect to training data
+    for (i = 0; i < test.numBags(); i++)
+      if (Utils.gr(test.perBag(i),0)) {
+	for (j = 0; j < test.numClasses(); j++)
+	  if (Utils.gr(test.perClassPerBag(i, j), 0))
+	    result -= test.perClassPerBag(i, j)*
+	      Math.log(train.perClassPerBag(i, j) + 1);
+	result += test.perBag(i) * Math.log(train.perBag(i) + numClasses);
+      }
+  
+    return result / log2;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GainRatioSplitCrit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GainRatioSplitCrit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GainRatioSplitCrit.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GainRatioSplitCrit.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for computing the gain ratio for a given distribution.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public final class GainRatioSplitCrit
+  extends EntropyBasedSplitCrit{
+
+  /** for serialization */
+  private static final long serialVersionUID = -433336694718670930L;
+
+  /**
+   * This method is a straightforward implementation of the gain
+   * ratio criterion for the given distribution.
+   */
+  public final double splitCritValue(Distribution bags) {
+
+    double numerator;
+    double denumerator;
+    
+    numerator = oldEnt(bags)-newEnt(bags);
+
+    // Splits with no gain are useless.
+    if (Utils.eq(numerator,0))
+      return Double.MAX_VALUE;
+    denumerator = splitEnt(bags);
+    
+    // Test if split is trivial.
+    if (Utils.eq(denumerator,0))
+      return Double.MAX_VALUE;
+    
+    //  We take the reciprocal value because we want to minimize the
+    // splitting criterion's value.
+    return denumerator/numerator;
+  }
+
+  /**
+   * This method computes the gain ratio in the same way C4.5 does.
+   *
+   * @param bags the distribution
+   * @param totalnoInst the weight of ALL instances
+   * @param numerator the info gain
+   */
+  public final double splitCritValue(Distribution bags, double totalnoInst,
+				     double numerator){
+    
+    double denumerator;
+    double noUnknown;
+    double unknownRate;
+    int i;
+    
+    // Compute split info.
+    denumerator = splitEnt(bags,totalnoInst);
+        
+    // Test if split is trivial.
+    if (Utils.eq(denumerator,0))
+      return 0;  
+    denumerator = denumerator/totalnoInst;
+
+    return numerator/denumerator;
+  }
+  
+  /**
+   * Help method for computing the split entropy.
+   */
+  private final double splitEnt(Distribution bags,double totalnoInst){
+    
+    double returnValue = 0;
+    double noUnknown;
+    int i;
+    
+    noUnknown = totalnoInst-bags.total();
+    if (Utils.gr(bags.total(),0)){
+      for (i=0;i<bags.numBags();i++)
+	returnValue = returnValue-logFunc(bags.perBag(i));
+      returnValue = returnValue-logFunc(noUnknown);
+      returnValue = returnValue+logFunc(totalnoInst);
+    }
+    return returnValue;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GraftSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GraftSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/GraftSplit.java	(revision 29)
@@ -0,0 +1,554 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *  GraftSplit.java
+ *  Copyright (C) 2007 Geoff Webb & Janice Boughton
+ *  a split object for nodes added to a tree during grafting.
+ *  (used in classifier J48g).
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.*;
+
+/**
+ * Class implementing a split for nodes added to a tree during grafting.
+ *
+ * @author Janice Boughton (jrbought@infotech.monash.edu.au)
+ * @version $Revision 1.0 $
+ */
+public class GraftSplit
+  extends ClassifierSplitModel
+  implements Comparable {
+
+  /** for serialzation. */
+  private static final long serialVersionUID = 722773260393182051L;
+
+  /** the distribution for graft values, from cases in atbop */
+  private Distribution m_graftdistro;
+	
+  /** the attribute we are splitting on */
+  private int m_attIndex;
+
+  /** value of split point (if numeric attribute) */
+  private double m_splitPoint;
+
+  /** dominant class of the subset specified by m_testType */
+  private int m_maxClass;
+
+  /** dominant class of the subset not specified by m_testType */
+  private int m_otherLeafMaxClass;
+
+  /** laplace value of the subset specified by m_testType for m_maxClass */
+  private double m_laplace;
+
+  /** leaf for the subset specified by m_testType */
+  private Distribution m_leafdistro;
+
+  /** 
+   * type of test:
+   * 0: <= test
+   * 1: > test
+   * 2: = test
+   * 3: != test
+   */
+  private int m_testType;
+
+
+  /**
+   * constructor
+   *
+   * @param a the attribute to split on
+   * @param v the value of a where split occurs
+   * @param t the test type (0 is <=, 1 is >, 2 is =, 3 is !)
+   * @param c the class to label the leaf node pointed to by test as.
+   * @param l the laplace value (needed when sorting GraftSplits)
+   */
+  public GraftSplit(int a, double v, int t, double c, double l) {
+
+    m_attIndex = a;
+    m_splitPoint = v;
+    m_testType = t;
+    m_maxClass = (int)c;
+    m_laplace = l;
+  }
+
+
+  /**
+   * constructor
+   *
+   * @param a the attribute to split on
+   * @param v the value of a where split occurs
+   * @param t the test type (0 is <=, 1 is >, 2 is =, 3 is !=)
+   * @param oC the class to label the leaf node not pointed to by test as.
+   * @param counts the distribution for this split
+   */
+  public GraftSplit(int a, double v, int t, double oC, double [][] counts)
+                                                           throws Exception {
+    m_attIndex = a;
+    m_splitPoint = v;
+    m_testType = t;
+    m_otherLeafMaxClass = (int)oC;
+
+    // only deal with binary cuts (<= and >; = and !=)
+    m_numSubsets = 2;
+
+    // which subset are we looking at for the graft?
+    int subset = subsetOfInterest();  // this is the subset for m_leaf
+
+    // create graft distribution, based on counts
+    m_distribution = new Distribution(counts);
+
+    // create a distribution object for m_leaf
+    double [][] lcounts = new double[1][m_distribution.numClasses()];
+    for(int c = 0; c < lcounts[0].length; c++) {
+       lcounts[0][c] = counts[subset][c];
+    }
+    m_leafdistro = new Distribution(lcounts);
+
+    // set the max class
+    m_maxClass = m_distribution.maxClass(subset);
+ 
+    // set the laplace value (assumes binary class) for subset of interest
+    m_laplace = (m_distribution.perClassPerBag(subset, m_maxClass) + 1.0) 
+               / (m_distribution.perBag(subset) + 2.0);
+  }
+
+
+  /**
+   * deletes the cases in data that belong to leaf pointed to by
+   * the test (i.e. the subset of interest).  this is useful so
+   * the instances belonging to that leaf aren't passed down the
+   * other branch.
+   *
+   * @param data the instances to delete from
+   */
+  public void deleteGraftedCases(Instances data) {
+
+    int subOfInterest = subsetOfInterest();
+    for(int x = 0; x < data.numInstances(); x++) {
+       if(whichSubset(data.instance(x)) == subOfInterest) {
+          data.delete(x--);
+       }
+    }
+  }
+
+
+  /**
+   * builds m_graftdistro using the passed data
+   *
+   * @param data the instances to use when creating the distribution
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    // distribution for the graft, not counting cases in atbop, only orig leaf
+    m_graftdistro = new Distribution(2, data.numClasses());
+ 
+    // which subset are we looking at for the graft?
+    int subset = subsetOfInterest();  // this is the subset for m_leaf
+
+    double thisNodeCount = 0;
+    double knownCases = 0;
+    boolean allKnown = true;
+    // populate distribution
+    for(int x = 0; x < data.numInstances(); x++) {
+       Instance instance = data.instance(x);
+       if(instance.isMissing(m_attIndex)) {
+          allKnown = false;
+          continue;
+       }
+       knownCases += instance.weight();
+       int subst = whichSubset(instance);
+       if(subst == -1)
+          continue;
+       m_graftdistro.add(subst, instance);
+       if(subst == subset) {  // instance belongs at m_leaf
+          thisNodeCount += instance.weight();
+       }
+    }
+    double factor = (knownCases == 0) ? (1.0 / (double)2.0)
+                                      : (thisNodeCount / knownCases);
+    if(!allKnown) {
+       for(int x = 0; x < data.numInstances(); x++) {
+          if(data.instance(x).isMissing(m_attIndex)) {
+             Instance instance = data.instance(x);
+             int subst = whichSubset(instance);
+             if(subst == -1)
+                continue;
+             instance.setWeight(instance.weight() * factor);
+             m_graftdistro.add(subst, instance);
+          }
+       }
+    }
+
+    // if there are no cases at the leaf, make sure the desired
+    // class is chosen, by setting counts to 0.01
+    if(m_graftdistro.perBag(subset) == 0) {
+       double [] counts = new double[data.numClasses()];
+       counts[m_maxClass] = 0.01;
+       m_graftdistro.add(subset, counts);
+    }
+    if(m_graftdistro.perBag((subset == 0) ? 1 : 0) == 0) {
+       double [] counts = new double[data.numClasses()];
+       counts[(int)m_otherLeafMaxClass] = 0.01;
+       m_graftdistro.add((subset == 0) ? 1 : 0, counts);
+    }
+  }
+
+
+  /**
+   * @return the NoSplit object for the leaf pointed to by m_testType branch
+   */
+  public NoSplit getLeaf() {
+    return new NoSplit(m_leafdistro);
+  }
+
+
+  /**
+   * @return the NoSplit object for the leaf not pointed to by m_testType branch
+   */
+  public NoSplit getOtherLeaf() {
+
+    // the bag (subset) that isn't pointed to by m_testType branch
+    int bag = (subsetOfInterest() == 0) ? 1 : 0;
+
+    double [][] counts = new double[1][m_graftdistro.numClasses()];
+    double totals = 0;
+    for(int c = 0; c < counts[0].length; c++) {
+       counts[0][c] = m_graftdistro.perClassPerBag(bag, c);
+       totals += counts[0][c];
+    }
+    // if empty, make sure proper class gets chosen
+    if(totals == 0) {
+       counts[0][m_otherLeafMaxClass] += 0.01;
+    }
+    return new NoSplit(new Distribution(counts));
+  }
+
+
+  /**
+   * Prints label for subset index of instances (eg class).
+   *
+   * @param index the bag to dump label for
+   * @param data to get attribute names and such
+   * @return the label as a string
+   * @exception Exception if something goes wrong
+   */
+  public final String dumpLabelG(int index, Instances data) throws Exception {
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    text.append(((Instances)data).classAttribute().
+       value((index==subsetOfInterest()) ? m_maxClass : m_otherLeafMaxClass));
+    text.append(" ("+Utils.roundDouble(m_graftdistro.perBag(index),1));
+    if(Utils.gr(m_graftdistro.numIncorrect(index),0))
+       text.append("/"
+        +Utils.roundDouble(m_graftdistro.numIncorrect(index),2));
+
+    // show the graft values, only if this is subsetOfInterest()
+    if(index == subsetOfInterest()) {
+       text.append("|"+Utils.roundDouble(m_distribution.perBag(index),2));
+       if(Utils.gr(m_distribution.numIncorrect(index),0))
+          text.append("/"
+             +Utils.roundDouble(m_distribution.numIncorrect(index),2));
+    }
+    text.append(")");
+    return text.toString();
+  }
+
+
+  /**
+   * @return the subset that is specified by the test type
+   */
+  public int subsetOfInterest() {
+    if(m_testType == 2)
+       return 0;
+    if(m_testType == 3)
+       return 1;
+    return m_testType;
+  }
+
+
+  /**
+   * @return the number of positive cases in the subset of interest
+   */
+  public double positivesForSubsetOfInterest() {
+    return (m_distribution.perClassPerBag(subsetOfInterest(), m_maxClass));
+  }
+
+
+  /**
+   * @param subset the subset to get the positives for
+   * @return the number of positive cases in the specified subset
+   */
+  public double positives(int subset) {
+    return (m_distribution.perClassPerBag(subset, 
+                                    m_distribution.maxClass(subset)));
+  }
+
+
+  /**
+   * @return the number of instances in the subset of interest
+   */
+  public double totalForSubsetOfInterest() {
+    return (m_distribution.perBag(subsetOfInterest()));
+  }
+
+  
+  /**
+   * @param subset the index of the bag to get the total for
+   * @return the number of instances in the subset
+   */
+  public double totalForSubset(int subset) {
+    return (m_distribution.perBag(subset));
+  }
+
+
+  /**
+   * Prints left side of condition satisfied by instances.
+   *
+   * @param data the data.
+   */
+  public String leftSide(Instances data) {
+    return data.attribute(m_attIndex).name();
+  }
+
+
+  /**
+   * @return the index of the attribute to split on
+   */ 
+  public int attribute() {
+    return m_attIndex;
+  }
+
+
+  /**
+   * Prints condition satisfied by instances in subset index.
+   */
+  public final String rightSide(int index, Instances data) {
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    if(data.attribute(m_attIndex).isNominal())
+       if(index == 0)
+          text.append(" = "+
+                      data.attribute(m_attIndex).value((int)m_splitPoint));
+       else
+          text.append(" != "+
+                      data.attribute(m_attIndex).value((int)m_splitPoint));
+    else
+       if(index == 0)
+          text.append(" <= "+
+                      Utils.doubleToString(m_splitPoint,6));
+       else
+          text.append(" > "+
+                      Utils.doubleToString(m_splitPoint,6));
+    return text.toString();
+  }
+
+
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+
+    StringBuffer expr = null;
+    if(index < 0) {
+       return "i[" + m_attIndex + "] == null";
+    }
+    if(data.attribute(m_attIndex).isNominal()) {
+       if(index == 0)
+          expr = new StringBuffer("i[");
+       else
+          expr = new StringBuffer("!i[");
+       expr.append(m_attIndex).append("]");
+       expr.append(".equals(\"").append(data.attribute(m_attIndex)
+                                      .value((int)m_splitPoint)).append("\")");
+    } else {
+       expr = new StringBuffer("((Double) i[");
+       expr.append(m_attIndex).append("])");
+       if(index == 0) {
+          expr.append(".doubleValue() <= ").append(m_splitPoint);
+       } else {
+          expr.append(".doubleValue() > ").append(m_splitPoint);
+       }
+    }
+    return expr.toString();
+  }
+
+
+  /**
+   * @param instance the instance to produce the weights for
+   * @return a double array of weights, null if only belongs to one subset
+   */
+  public double [] weights(Instance instance) {
+
+    double [] weights;
+    int i;
+
+    if(instance.isMissing(m_attIndex)) {
+       weights = new double [m_numSubsets];
+       for(i=0;i<m_numSubsets;i++) {
+          weights [i] = m_graftdistro.perBag(i)/m_graftdistro.total();
+       }
+       return weights;
+    } else {
+       return null;
+    }
+  }
+
+
+  /**
+   * @param instance the instance for which to determine the subset
+   * @return an int indicating the subset this instance belongs to
+   */
+  public int whichSubset(Instance instance) {
+
+    if(instance.isMissing(m_attIndex))
+       return -1;
+
+    if(instance.attribute(m_attIndex).isNominal()) {
+       // in the case of nominal, m_splitPoint is the = value, all else is !=
+       if(instance.value(m_attIndex) == m_splitPoint)
+          return 0;
+       else
+          return 1;
+    } else {
+       if(Utils.smOrEq(instance.value(m_attIndex), m_splitPoint))
+          return 0;
+       else
+          return 1;
+    }
+  }
+
+
+  /**
+   * @return the value of the split point
+   */
+  public double splitPoint() {
+    return m_splitPoint;
+  }
+
+  /**
+   * @return the dominate class for the subset of interest
+   */
+  public int maxClassForSubsetOfInterest() {
+    return m_maxClass;
+  }
+
+  /**
+   * @return the laplace value for maxClass of subset of interest
+   */
+  public double laplaceForSubsetOfInterest() {
+    return m_laplace;
+  }
+
+  /**
+   * returns the test type
+   * @return value of testtype
+   */
+  public int testType() {
+    return m_testType;
+  }
+
+  /**
+   * method needed for sorting a collection of GraftSplits by laplace value
+   * @param g the graft split to compare to this one
+   * @return -1, 0, or 1 if this GraftSplit laplace is <, = or > than that of g
+   */
+  public int compareTo(Object g) {
+
+    if(m_laplace > ((GraftSplit)g).laplaceForSubsetOfInterest())
+       return 1;
+    if(m_laplace < ((GraftSplit)g).laplaceForSubsetOfInterest())
+       return -1;
+    return 0;
+  }
+
+  /**
+   * returns the probability for instance for the specified class
+   * @param classIndex the index of the class
+   * @param instance the instance to get the probability for
+   * @param theSubset the subset
+   */
+  public final double classProb(int classIndex, Instance instance, 
+		            int theSubset) throws Exception {
+
+    if (theSubset <= -1) {
+       double [] weights = weights(instance);
+       if (weights == null) {
+          return m_distribution.prob(classIndex);
+       } else {
+          double prob = 0;
+          for (int i = 0; i < weights.length; i++) {
+             prob += weights[i] * m_distribution.prob(classIndex, i);
+          }
+          return prob;
+       }
+    } else {
+       if (Utils.gr(m_distribution.perBag(theSubset), 0)) {
+          return m_distribution.prob(classIndex, theSubset);
+       } else {
+          return m_distribution.prob(classIndex);
+       }
+    }
+  }
+
+
+  /**
+   * method for returning information about this GraftSplit
+   * @param data instances for determining names of attributes and values
+   * @return a string showing this GraftSplit's information
+   */
+  public String toString(Instances data) {
+
+    String theTest;
+    if(m_testType == 0)
+       theTest = " <= ";
+    else if(m_testType == 1)
+       theTest = " > ";
+    else if(m_testType == 2)
+       theTest = " = ";
+    else
+       theTest = " != ";
+
+    if(data.attribute(m_attIndex).isNominal())
+       theTest += data.attribute(m_attIndex).value((int)m_splitPoint);
+    else
+       theTest += Double.toString(m_splitPoint);
+
+    return data.attribute(m_attIndex).name() + theTest
+           + " (" + Double.toString(m_laplace) + ") --> " 
+           + data.attribute(data.classIndex()).value(m_maxClass);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.2 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/InfoGainSplitCrit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/InfoGainSplitCrit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/InfoGainSplitCrit.java	(revision 29)
@@ -0,0 +1,122 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InfoGainSplitCrit.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for computing the information gain for a given distribution.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public final class InfoGainSplitCrit
+  extends EntropyBasedSplitCrit{
+
+  /** for serialization */
+  private static final long serialVersionUID = 4892105020180728499L;
+
+  /**
+   * This method is a straightforward implementation of the information
+   * gain criterion for the given distribution.
+   */
+  public final double splitCritValue(Distribution bags) {
+
+    double numerator;
+        
+    numerator = oldEnt(bags)-newEnt(bags);
+
+    // Splits with no gain are useless.
+    if (Utils.eq(numerator,0))
+      return Double.MAX_VALUE;
+        
+    // We take the reciprocal value because we want to minimize the
+    // splitting criterion's value.
+    return bags.total()/numerator;
+  }
+
+  /**
+   * This method computes the information gain in the same way 
+   * C4.5 does.
+   *
+   * @param bags the distribution
+   * @param totalNoInst weight of ALL instances (including the
+   * ones with missing values).
+   */
+  public final double splitCritValue(Distribution bags, double totalNoInst) {
+    
+    double numerator;
+    double noUnknown;
+    double unknownRate;
+    int i;
+    
+    noUnknown = totalNoInst-bags.total();
+    unknownRate = noUnknown/totalNoInst;
+    numerator = (oldEnt(bags)-newEnt(bags));
+    numerator = (1-unknownRate)*numerator;
+    
+    // Splits with no gain are useless.
+    if (Utils.eq(numerator,0))
+      return 0;
+    
+    return numerator/bags.total();
+  }
+
+  /**
+   * This method computes the information gain in the same way 
+   * C4.5 does.
+   *
+   * @param bags the distribution
+   * @param totalNoInst weight of ALL instances 
+   * @param oldEnt entropy with respect to "no-split"-model.
+   */
+  public final double splitCritValue(Distribution bags,double totalNoInst,
+                                     double oldEnt) {
+    
+    double numerator;
+    double noUnknown;
+    double unknownRate;
+    int i;
+    
+    noUnknown = totalNoInst-bags.total();
+    unknownRate = noUnknown/totalNoInst;
+    numerator = (oldEnt-newEnt(bags));
+    numerator = (1-unknownRate)*numerator;
+    
+    // Splits with no gain are useless.
+    if (Utils.eq(numerator,0))
+      return 0;
+    
+    return numerator/bags.total();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ModelSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ModelSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/ModelSelection.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class for model selection criteria.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public abstract class ModelSelection
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4850147125096133642L;
+
+  /**
+   * Selects a model for the given dataset.
+   *
+   * @exception Exception if model can't be selected
+   */
+  public abstract ClassifierSplitModel selectModel(Instances data) throws Exception;
+
+  /**
+   * Selects a model for the given train data using the given test data
+   *
+   * @exception Exception if model can't be selected
+   */
+  public ClassifierSplitModel selectModel(Instances train, Instances test) 
+       throws Exception {
+
+    throw new Exception("Model selection method not implemented");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeClassifierTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeClassifierTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeClassifierTree.java	(revision 29)
@@ -0,0 +1,280 @@
+/*
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NBTreeClassifierTree.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+/**
+ * Class for handling a naive bayes tree structure used for
+ * classification.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5534 $
+ */
+public class NBTreeClassifierTree
+  extends ClassifierTree {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4472639447877404786L;
+
+  public NBTreeClassifierTree(ModelSelection toSelectLocModel) {
+    super(toSelectLocModel);
+  }
+
+  /**
+   * Returns default capabilities of the classifier tree.
+   *
+   * @return      the capabilities of this classifier tree
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Method for building a naive bayes classifier tree
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances data) throws Exception {
+   super.buildClassifier(data);
+   cleanup(new Instances(data, 0));
+   assignIDs(-1);
+  }
+
+  /**
+   * Assigns a uniqe id to every node in the tree.
+   *
+  public int assignIDs(int lastID) {
+
+    int currLastID = lastID + 1;
+
+    m_id = currLastID;
+    if (m_sons != null) {
+      for (int i = 0; i < m_sons.length; i++) {
+	currLastID = m_sons[i].assignIDs(currLastID);
+      }
+    }
+    return currLastID;
+    } */
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param data the training data
+   * @exception Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances data) throws Exception {
+	 
+    ClassifierTree newTree = new NBTreeClassifierTree(m_toSelectModel);
+    newTree.buildTree(data, false);
+    
+    return newTree;
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param train the training data
+   * @param test the pruning data.
+   * @exception Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances train, Instances test) 
+       throws Exception {
+	 
+    ClassifierTree newTree = new NBTreeClassifierTree(m_toSelectModel);
+    newTree.buildTree(train, test, false);
+    
+    return newTree;
+  }
+
+  /**
+   * Print the models at the leaves
+   *
+   * @return textual description of the leaf models
+   */
+  public String printLeafModels() {
+    StringBuffer text = new StringBuffer();
+
+    if (m_isLeaf) {
+      text.append("\nLeaf number: " + m_id+" ");
+      text.append(m_localModel.toString());
+      text.append("\n");
+    } else {
+       for (int i=0;i<m_sons.length;i++) {
+	 text.append(((NBTreeClassifierTree)m_sons[i]).printLeafModels());
+       }
+    } 
+    return text.toString();
+  }
+
+  /**
+   * Prints tree structure.
+   */
+  public String toString() {
+
+    try {
+      StringBuffer text = new StringBuffer();
+      
+      if (m_isLeaf) {
+	text.append(": NB");
+	text.append(m_id);
+      }else
+	dumpTreeNB(0,text);
+
+      text.append("\n"+printLeafModels());
+      text.append("\n\nNumber of Leaves  : \t"+numLeaves()+"\n");
+      text.append("\nSize of the tree : \t"+numNodes()+"\n");
+ 
+      return text.toString();
+    } catch (Exception e) {
+      e.printStackTrace();
+      return "Can't print nb tree.";
+    }
+  }
+
+  /**
+   * Help method for printing tree structure.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void dumpTreeNB(int depth,StringBuffer text) 
+       throws Exception {
+    
+    int i,j;
+    
+    for (i=0;i<m_sons.length;i++) {
+      text.append("\n");;
+      for (j=0;j<depth;j++)
+	text.append("|   ");
+      text.append(m_localModel.leftSide(m_train));
+      text.append(m_localModel.rightSide(i, m_train));
+      if (m_sons[i].m_isLeaf) {
+	text.append(": NB ");
+	text.append(m_sons[i].m_id);
+      }else
+	((NBTreeClassifierTree)m_sons[i]).dumpTreeNB(depth+1,text);
+    }
+  }
+
+  /**
+   * Returns graph describing the tree.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public String graph() throws Exception {
+
+    StringBuffer text = new StringBuffer();
+
+    text.append("digraph J48Tree {\n");
+    if (m_isLeaf) {
+      text.append("N" + m_id 
+		  + " [label=\"" + 
+		  "NB model" + "\" " + 
+		  "shape=box style=filled ");
+      if (m_train != null && m_train.numInstances() > 0) {
+	text.append("data =\n" + m_train + "\n");
+	text.append(",\n");
+
+      }
+      text.append("]\n");
+    }else {
+      text.append("N" + m_id 
+		  + " [label=\"" + 
+		  m_localModel.leftSide(m_train) + "\" ");
+      if (m_train != null && m_train.numInstances() > 0) {
+	text.append("data =\n" + m_train + "\n");
+	text.append(",\n");
+     }
+      text.append("]\n");
+      graphTree(text);
+    }
+    
+    return text.toString() +"}\n";
+  }
+
+  /**
+   * Help method for printing tree structure as a graph.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void graphTree(StringBuffer text) throws Exception {
+    
+    for (int i = 0; i < m_sons.length; i++) {
+      text.append("N" + m_id  
+		  + "->" + 
+		  "N" + m_sons[i].m_id +
+		  " [label=\"" + m_localModel.rightSide(i,m_train).trim() + 
+		  "\"]\n");
+      if (m_sons[i].m_isLeaf) {
+	text.append("N" + m_sons[i].m_id +
+		    " [label=\""+"NB Model"+"\" "+ 
+		    "shape=box style=filled ");
+	if (m_train != null && m_train.numInstances() > 0) {
+	  text.append("data =\n" + m_sons[i].m_train + "\n");
+	  text.append(",\n");
+	}
+	text.append("]\n");
+      } else {
+	text.append("N" + m_sons[i].m_id +
+		    " [label=\""+m_sons[i].m_localModel.leftSide(m_train) + 
+		    "\" ");
+	if (m_train != null && m_train.numInstances() > 0) {
+	  text.append("data =\n" + m_sons[i].m_train + "\n");
+	  text.append(",\n");
+	}
+	text.append("]\n");
+	((NBTreeClassifierTree)m_sons[i]).graphTree(text);
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5534 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeModelSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeModelSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeModelSelection.java	(revision 29)
@@ -0,0 +1,210 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NBTreeModelSelection.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+
+/**
+ * Class for selecting a NB tree split.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class NBTreeModelSelection
+  extends ModelSelection {
+
+  /** for serialization */
+  private static final long serialVersionUID = 990097748931976704L;
+
+  /** Minimum number of objects in interval. */
+  private int m_minNoObj;               
+
+  /** All the training data */
+  private Instances m_allData; // 
+
+  /**
+   * Initializes the split selection method with the given parameters.
+   *
+   * @param minNoObj minimum number of instances that have to occur in at least two
+   * subsets induced by split
+   * @param allData FULL training dataset (necessary for
+   * selection of split points).
+   */
+  public NBTreeModelSelection(int minNoObj, Instances allData) {
+    m_minNoObj = minNoObj;
+    m_allData = allData;
+  }
+
+  /**
+   * Sets reference to training data to null.
+   */
+  public void cleanup() {
+
+    m_allData = null;
+  }
+
+  /**
+   * Selects NBTree-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances data){
+
+    double globalErrors = 0;
+
+    double minResult;
+    double currentResult;
+    NBTreeSplit [] currentModel;
+    NBTreeSplit bestModel = null;
+    NBTreeNoSplit noSplitModel = null;
+    int validModels = 0;
+    boolean multiVal = true;
+    Distribution checkDistribution;
+    Attribute attribute;
+    double sumOfWeights;
+    int i;
+    
+    try{
+      // build the global model at this node
+      noSplitModel = new NBTreeNoSplit();
+      noSplitModel.buildClassifier(data);
+      if (data.numInstances() < 5) {
+	return noSplitModel;
+      }
+
+      // evaluate it
+      globalErrors = noSplitModel.getErrors();
+      if (globalErrors == 0) {
+	return noSplitModel;
+      }
+
+      // Check if all Instances belong to one class or if not
+      // enough Instances to split.
+      checkDistribution = new Distribution(data);
+      if (Utils.sm(checkDistribution.total(), m_minNoObj) ||
+	  Utils.eq(checkDistribution.total(),
+		   checkDistribution.perClass(checkDistribution.maxClass()))) {
+	return noSplitModel;
+      }
+
+      // Check if all attributes are nominal and have a 
+      // lot of values.
+      if (m_allData != null) {
+	Enumeration enu = data.enumerateAttributes();
+	while (enu.hasMoreElements()) {
+	  attribute = (Attribute) enu.nextElement();
+	  if ((attribute.isNumeric()) ||
+	      (Utils.sm((double)attribute.numValues(),
+			(0.3*(double)m_allData.numInstances())))){
+	    multiVal = false;
+	    break;
+	  }
+	}
+      }
+
+      currentModel = new NBTreeSplit[data.numAttributes()];
+      sumOfWeights = data.sumOfWeights();
+
+      // For each attribute.
+      for (i = 0; i < data.numAttributes(); i++){
+	
+	// Apart from class attribute.
+	if (i != (data).classIndex()){
+	  
+	  // Get models for current attribute.
+	  currentModel[i] = new NBTreeSplit(i,m_minNoObj,sumOfWeights);
+	  currentModel[i].setGlobalModel(noSplitModel);
+	  currentModel[i].buildClassifier(data);
+	  
+	  // Check if useful split for current attribute
+	  // exists and check for enumerated attributes with 
+	  // a lot of values.
+	  if (currentModel[i].checkModel()){
+	    validModels++;
+	  }
+	} else {
+	  currentModel[i] = null;
+	}
+      }
+      
+      // Check if any useful split was found.
+      if (validModels == 0) {
+	return noSplitModel;
+      }
+      
+     // Find "best" attribute to split on.
+      minResult = globalErrors;
+      for (i=0;i<data.numAttributes();i++){
+	if ((i != (data).classIndex()) &&
+	    (currentModel[i].checkModel())) {
+	  /*  System.err.println("Errors for "+data.attribute(i).name()+" "+
+	      currentModel[i].getErrors()); */
+	  if (currentModel[i].getErrors() < minResult) {
+	    bestModel = currentModel[i];
+	    minResult = currentModel[i].getErrors();
+	  }
+	}
+      }
+      //      System.exit(1);
+      // Check if useful split was found.
+      
+
+      if (((globalErrors - minResult) / globalErrors) < 0.05) {
+	return noSplitModel;
+      }
+      
+      /*      if (bestModel == null) {
+	System.err.println("This shouldn't happen! glob : "+globalErrors+
+			   " minRes : "+minResult);
+	System.exit(1);
+	} */
+      // Set the global model for the best split
+      //      bestModel.setGlobalModel(noSplitModel);
+
+      return bestModel;
+    }catch(Exception e){
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Selects NBTree-type split for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances train, Instances test) {
+
+    return selectModel(train);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeNoSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeNoSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeNoSplit.java	(revision 29)
@@ -0,0 +1,217 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NBTreeNoSplit.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.bayes.NaiveBayesUpdateable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.Random;
+
+/**
+ * Class implementing a "no-split"-split (leaf node) for naive bayes
+ * trees.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public final class NBTreeNoSplit
+  extends ClassifierSplitModel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7824804381545259618L;
+
+  /** the naive bayes classifier */
+  private NaiveBayesUpdateable m_nb;
+
+  /** the discretizer used */
+  private Discretize m_disc;
+
+  /** errors on the training data at this node */
+  private double m_errors;
+
+  public NBTreeNoSplit() {
+    m_numSubsets = 1;
+  }
+
+  /**
+   * Build the no-split node
+   *
+   * @param instances an <code>Instances</code> value
+   * @exception Exception if an error occurs
+   */
+  public final void buildClassifier(Instances instances) throws Exception {
+    m_nb = new NaiveBayesUpdateable();
+    m_disc = new Discretize();
+    m_disc.setInputFormat(instances);
+    Instances temp = Filter.useFilter(instances, m_disc);
+    m_nb.buildClassifier(temp);
+    if (temp.numInstances() >= 5) {
+      m_errors = crossValidate(m_nb, temp, new Random(1));
+    }
+    m_numSubsets = 1;
+  }
+
+  /**
+   * Return the errors made by the naive bayes model at this node
+   *
+   * @return the number of errors made
+   */
+  public double getErrors() {
+    return m_errors;
+  }
+
+  /**
+   * Return the discretizer used at this node
+   *
+   * @return a <code>Discretize</code> value
+   */
+  public Discretize getDiscretizer() {
+    return m_disc;
+  }
+
+  /**
+   * Get the naive bayes model at this node
+   *
+   * @return a <code>NaiveBayesUpdateable</code> value
+   */
+  public NaiveBayesUpdateable getNaiveBayesModel() {
+    return m_nb;
+  }
+
+  /**
+   * Always returns 0 because only there is only one subset.
+   */
+  public final int whichSubset(Instance instance){
+    
+    return 0;
+  }
+
+  /**
+   * Always returns null because there is only one subset.
+   */
+  public final double [] weights(Instance instance){
+
+    return null;
+  }
+  
+  /**
+   * Does nothing because no condition has to be satisfied.
+   */
+  public final String leftSide(Instances instances){
+
+    return "";
+  }
+  
+  /**
+   * Does nothing because no condition has to be satisfied.
+   */
+  public final String rightSide(int index, Instances instances){
+
+    return "";
+  }
+
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+
+    return "true";  // or should this be false??
+  }
+
+  /**
+   * Return the probability for a class value
+   *
+   * @param classIndex the index of the class value
+   * @param instance the instance to generate a probability for
+   * @param theSubset the subset to consider
+   * @return a probability
+   * @exception Exception if an error occurs
+   */
+  public double classProb(int classIndex, Instance instance, int theSubset) 
+    throws Exception {
+    m_disc.input(instance);
+    Instance temp = m_disc.output();
+    return m_nb.distributionForInstance(temp)[classIndex];
+  }
+
+  /**
+   * Return a textual description of the node
+   *
+   * @return a <code>String</code> value
+   */
+  public String toString() {
+    return m_nb.toString();
+  }
+
+  /**
+   * Utility method for fast 5-fold cross validation of a naive bayes
+   * model
+   *
+   * @param fullModel a <code>NaiveBayesUpdateable</code> value
+   * @param trainingSet an <code>Instances</code> value
+   * @param r a <code>Random</code> value
+   * @return a <code>double</code> value
+   * @exception Exception if an error occurs
+   */
+  public static double crossValidate(NaiveBayesUpdateable fullModel,
+			       Instances trainingSet,
+			       Random r) throws Exception {
+    // make some copies for fast evaluation of 5-fold xval
+    Classifier [] copies = AbstractClassifier.makeCopies(fullModel, 5);
+    Evaluation eval = new Evaluation(trainingSet);
+    // make some splits
+    for (int j = 0; j < 5; j++) {
+      Instances test = trainingSet.testCV(5, j);
+      // unlearn these test instances
+      for (int k = 0; k < test.numInstances(); k++) {
+	test.instance(k).setWeight(-test.instance(k).weight());
+	((NaiveBayesUpdateable)copies[j]).updateClassifier(test.instance(k));
+	// reset the weight back to its original value
+	test.instance(k).setWeight(-test.instance(k).weight());
+      }
+      eval.evaluateModel(copies[j], test);
+    }
+    return eval.incorrect();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NBTreeSplit.java	(revision 29)
@@ -0,0 +1,412 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NBTreeSplit.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.classifiers.bayes.NaiveBayesUpdateable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.Discretize;
+
+import java.util.Random;
+
+/**
+ * Class implementing a NBTree split on an attribute.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 6088 $
+ */
+public class NBTreeSplit
+  extends ClassifierSplitModel{
+
+  /** for serialization */
+  private static final long serialVersionUID = 8922627123884975070L;
+
+  /** Desired number of branches. */
+  private int m_complexityIndex;  
+
+  /** Attribute to split on. */
+  private int m_attIndex;         
+
+  /** Minimum number of objects in a split.   */
+  private int m_minNoObj;         
+
+  /** Value of split point. */
+  private double m_splitPoint;   
+
+  /** The sum of the weights of the instances. */
+  private double m_sumOfWeights;  
+
+  /** The weight of the instances incorrectly classified by the 
+      naive bayes models arising from this split*/
+  private double m_errors;
+
+  private C45Split m_c45S;
+
+  /** The global naive bayes model for this node */
+  NBTreeNoSplit m_globalNB;
+
+  /**
+   * Initializes the split model.
+   */
+  public NBTreeSplit(int attIndex, int minNoObj, double sumOfWeights) {
+    
+    // Get index of attribute to split on.
+    m_attIndex = attIndex;
+        
+    // Set minimum number of objects.
+    m_minNoObj = minNoObj;
+
+    // Set the sum of the weights
+    m_sumOfWeights = sumOfWeights;
+    
+  }
+
+  /**
+   * Creates a NBTree-type split on the given data. Assumes that none of
+   * the class values is missing.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void buildClassifier(Instances trainInstances) 
+       throws Exception {
+
+    // Initialize the remaining instance variables.
+    m_numSubsets = 0;
+    m_splitPoint = Double.MAX_VALUE;
+    m_errors = 0;
+    if (m_globalNB != null) {
+      m_errors = m_globalNB.getErrors();
+    }
+
+    // Different treatment for enumerated and numeric
+    // attributes.
+    if (trainInstances.attribute(m_attIndex).isNominal()) {
+      m_complexityIndex = trainInstances.attribute(m_attIndex).numValues();
+      handleEnumeratedAttribute(trainInstances);
+    }else{
+      m_complexityIndex = 2;
+      trainInstances.sort(trainInstances.attribute(m_attIndex));
+      handleNumericAttribute(trainInstances);
+    }
+  }
+
+  /**
+   * Returns index of attribute for which split was generated.
+   */
+  public final int attIndex() {
+    
+    return m_attIndex;
+  }
+
+  /**
+   * Creates split on enumerated attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleEnumeratedAttribute(Instances trainInstances)
+       throws Exception {
+
+    m_c45S = new C45Split(m_attIndex, 2, m_sumOfWeights, true);
+    m_c45S.buildClassifier(trainInstances);
+    if (m_c45S.numSubsets() == 0) {
+      return;
+    }
+    m_errors = 0;
+    Instance instance;
+
+    Instances [] trainingSets = new Instances [m_complexityIndex];
+    for (int i = 0; i < m_complexityIndex; i++) {
+      trainingSets[i] = new Instances(trainInstances, 0);
+    }
+    /*    m_distribution = new Distribution(m_complexityIndex,
+	  trainInstances.numClasses()); */
+    int subset;
+    for (int i = 0; i < trainInstances.numInstances(); i++) {
+      instance = trainInstances.instance(i);
+      subset = m_c45S.whichSubset(instance);
+      if (subset > -1) {
+	trainingSets[subset].add((Instance)instance.copy());
+      } else {
+	double [] weights = m_c45S.weights(instance);
+	for (int j = 0; j < m_complexityIndex; j++) {
+	  try {
+	    Instance temp = (Instance) instance.copy();
+	    if (weights.length == m_complexityIndex) {
+	      temp.setWeight(temp.weight() * weights[j]);
+	    } else {
+	      temp.setWeight(temp.weight() / m_complexityIndex);
+	    }
+	    trainingSets[j].add(temp);
+	  } catch (Exception ex) {
+	    ex.printStackTrace();
+	    System.err.println("*** "+m_complexityIndex);
+	    System.err.println(weights.length);
+	    System.exit(1);
+	  }
+	}
+      }
+    }
+
+    /*    // compute weights (weights of instances per subset
+    m_weights = new double [m_complexityIndex];
+    for (int i = 0; i < m_complexityIndex; i++) {
+      m_weights[i] = trainingSets[i].sumOfWeights();
+    }
+    Utils.normalize(m_weights); */
+
+    /*
+    // Only Instances with known values are relevant.
+    Enumeration enu = trainInstances.enumerateInstances();
+    while (enu.hasMoreElements()) {
+      instance = (Instance) enu.nextElement();
+      if (!instance.isMissing(m_attIndex)) {
+	//	m_distribution.add((int)instance.value(m_attIndex),instance);
+	trainingSets[(int)instances.value(m_attIndex)].add(instance);
+      } else {
+	// add these to the error count
+	m_errors += instance.weight();
+      }
+      } */
+
+    Random r = new Random(1);
+    int minNumCount = 0;
+    for (int i = 0; i < m_complexityIndex; i++) {
+      if (trainingSets[i].numInstances() >= 5) {
+	minNumCount++;
+	// Discretize the sets
+	Discretize disc = new Discretize();
+	disc.setInputFormat(trainingSets[i]);
+	trainingSets[i] = Filter.useFilter(trainingSets[i], disc);
+
+	trainingSets[i].randomize(r);
+	trainingSets[i].stratify(5);
+	NaiveBayesUpdateable fullModel = new NaiveBayesUpdateable();
+	fullModel.buildClassifier(trainingSets[i]);
+
+	// add the errors for this branch of the split
+	m_errors += NBTreeNoSplit.crossValidate(fullModel, trainingSets[i], r);
+      } else {
+	// if fewer than min obj then just count them as errors
+	for (int j = 0; j < trainingSets[i].numInstances(); j++) {
+	  m_errors += trainingSets[i].instance(j).weight();
+	}
+      }
+    }
+    
+    // Check if there are at least five instances in at least two of the subsets
+    // subsets.
+    if (minNumCount > 1) {
+      m_numSubsets = m_complexityIndex;
+    }
+  }
+
+  /**
+   * Creates split on numeric attribute.
+   *
+   * @exception Exception if something goes wrong
+   */
+  private void handleNumericAttribute(Instances trainInstances)
+       throws Exception {
+
+    m_c45S = new C45Split(m_attIndex, 2, m_sumOfWeights, true);
+    m_c45S.buildClassifier(trainInstances);
+    if (m_c45S.numSubsets() == 0) {
+      return;
+    }
+    m_errors = 0;
+
+    Instances [] trainingSets = new Instances [m_complexityIndex];
+    trainingSets[0] = new Instances(trainInstances, 0);
+    trainingSets[1] = new Instances(trainInstances, 0);
+    int subset = -1;
+    
+    // populate the subsets
+    for (int i = 0; i < trainInstances.numInstances(); i++) {
+      Instance instance = trainInstances.instance(i);
+      subset = m_c45S.whichSubset(instance);
+      if (subset != -1) {
+	trainingSets[subset].add((Instance)instance.copy());
+      } else {
+	double [] weights = m_c45S.weights(instance);
+	for (int j = 0; j < m_complexityIndex; j++) {
+	  Instance temp = (Instance)instance.copy();
+	  if (weights.length == m_complexityIndex) {
+	    temp.setWeight(temp.weight() * weights[j]);
+	  } else {
+	    temp.setWeight(temp.weight() / m_complexityIndex);
+	  }
+	  trainingSets[j].add(temp); 
+	}
+      }
+    }
+    
+    /*    // compute weights (weights of instances per subset
+    m_weights = new double [m_complexityIndex];
+    for (int i = 0; i < m_complexityIndex; i++) {
+      m_weights[i] = trainingSets[i].sumOfWeights();
+    }
+    Utils.normalize(m_weights); */
+
+    Random r = new Random(1);
+    int minNumCount = 0;
+    for (int i = 0; i < m_complexityIndex; i++) {
+      if (trainingSets[i].numInstances() > 5) {
+	minNumCount++;
+	// Discretize the sets
+		Discretize disc = new Discretize();
+	disc.setInputFormat(trainingSets[i]);
+	trainingSets[i] = Filter.useFilter(trainingSets[i], disc);
+
+	trainingSets[i].randomize(r);
+	trainingSets[i].stratify(5);
+	NaiveBayesUpdateable fullModel = new NaiveBayesUpdateable();
+	fullModel.buildClassifier(trainingSets[i]);
+
+	// add the errors for this branch of the split
+	m_errors += NBTreeNoSplit.crossValidate(fullModel, trainingSets[i], r);
+      } else {
+	for (int j = 0; j < trainingSets[i].numInstances(); j++) {
+	  m_errors += trainingSets[i].instance(j).weight();
+	}
+      }
+    }
+    
+    // Check if minimum number of Instances in at least two
+    // subsets.
+    if (minNumCount > 1) {
+      m_numSubsets = m_complexityIndex;
+    }
+  }
+
+  /**
+   * Returns index of subset instance is assigned to.
+   * Returns -1 if instance is assigned to more than one subset.
+   *
+   * @exception Exception if something goes wrong
+   */
+  public final int whichSubset(Instance instance) 
+    throws Exception {
+    
+    return m_c45S.whichSubset(instance);
+  }
+
+  /**
+   * Returns weights if instance is assigned to more than one subset.
+   * Returns null if instance is only assigned to one subset.
+   */
+  public final double [] weights(Instance instance) {
+    return m_c45S.weights(instance);
+    //     return m_weights;
+  }
+
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+    return m_c45S.sourceExpression(index, data);
+  }
+
+  /**
+   * Prints the condition satisfied by instances in a subset.
+   *
+   * @param index of subset 
+   * @param data training set.
+   */
+  public final String rightSide(int index,Instances data) {
+    return m_c45S.rightSide(index, data);
+  }
+
+  /**
+   * Prints left side of condition..
+   *
+   * @param data training set.
+   */
+  public final String leftSide(Instances data) {
+
+    return m_c45S.leftSide(data);
+  }
+
+  /**
+   * Return the probability for a class value
+   *
+   * @param classIndex the index of the class value
+   * @param instance the instance to generate a probability for
+   * @param theSubset the subset to consider
+   * @return a probability
+   * @exception Exception if an error occurs
+   */
+  public double classProb(int classIndex, Instance instance, int theSubset) 
+    throws Exception {
+
+    // use the global naive bayes model
+    if (theSubset > -1) {
+      return m_globalNB.classProb(classIndex, instance, theSubset);
+    } else {
+      throw new Exception("This shouldn't happen!!!");
+    }
+  }
+
+  /**
+   * Return the global naive bayes model for this node
+   *
+   * @return a <code>NBTreeNoSplit</code> value
+   */
+  public NBTreeNoSplit getGlobalModel() {
+    return m_globalNB;
+  }
+
+  /**
+   * Set the global naive bayes model for this node
+   *
+   * @param global a <code>NBTreeNoSplit</code> value
+   */
+  public void setGlobalModel(NBTreeNoSplit global) {
+    m_globalNB = global;
+  }
+
+  /**
+   * Return the errors made by the naive bayes models arising
+   * from this split.
+   *
+   * @return a <code>double</code> value
+   */
+  public double getErrors() {
+    return m_errors;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6088 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NoSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NoSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/NoSplit.java	(revision 29)
@@ -0,0 +1,115 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NoSplit.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * Class implementing a "no-split"-split.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public final class NoSplit
+  extends ClassifierSplitModel{
+
+  /** for serialization */
+  private static final long serialVersionUID = -1292620749331337546L;
+
+  /**
+   * Creates "no-split"-split for given distribution.
+   */
+  public NoSplit(Distribution distribution){
+    
+    m_distribution = new Distribution(distribution);
+    m_numSubsets = 1;
+  }
+  
+  /**
+   * Creates a "no-split"-split for a given set of instances.
+   *
+   * @exception Exception if split can't be built successfully
+   */
+  public final void buildClassifier(Instances instances) 
+       throws Exception {
+
+    m_distribution = new Distribution(instances);
+    m_numSubsets = 1;
+  }
+
+  /**
+   * Always returns 0 because only there is only one subset.
+   */
+  public final int whichSubset(Instance instance){
+    
+    return 0;
+  }
+
+  /**
+   * Always returns null because there is only one subset.
+   */
+  public final double [] weights(Instance instance){
+
+    return null;
+  }
+  
+  /**
+   * Does nothing because no condition has to be satisfied.
+   */
+  public final String leftSide(Instances instances){
+
+    return "";
+  }
+  
+  /**
+   * Does nothing because no condition has to be satisfied.
+   */
+  public final String rightSide(int index, Instances instances){
+
+    return "";
+  }
+
+  /**
+   * Returns a string containing java source code equivalent to the test
+   * made at this node. The instance being tested is called "i".
+   *
+   * @param index index of the nominal value tested
+   * @param data the data containing instance structure info
+   * @return a value of type 'String'
+   */
+  public final String sourceExpression(int index, Instances data) {
+
+    return "true";  // or should this be false??
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/PruneableClassifierTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/PruneableClassifierTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/PruneableClassifierTree.java	(revision 29)
@@ -0,0 +1,240 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PruneableClassifierTree.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Random;
+
+/**
+ * Class for handling a tree structure that can
+ * be pruned using a pruning set. 
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5533 $
+ */
+public class PruneableClassifierTree 
+  extends ClassifierTree {
+  
+  /** for serialization */
+  static final long serialVersionUID = -555775736857600201L;
+
+  /** True if the tree is to be pruned. */
+  private boolean pruneTheTree = false;
+
+  /** How many subsets of equal size? One used for pruning, the rest for training. */
+  private int numSets = 3;
+
+  /** Cleanup after the tree has been built. */
+  private boolean m_cleanup = true;
+
+  /** The random number seed. */
+  private int m_seed = 1;
+
+  /**
+   * Constructor for pruneable tree structure. Stores reference
+   * to associated training data at each node.
+   *
+   * @param toSelectLocModel selection method for local splitting model
+   * @param pruneTree true if the tree is to be pruned
+   * @param num number of subsets of equal size
+   * @param cleanup
+   * @param seed the seed value to use
+   * @throws Exception if something goes wrong
+   */
+  public PruneableClassifierTree(ModelSelection toSelectLocModel,
+				 boolean pruneTree, int num, boolean cleanup,
+				 int seed)
+       throws Exception {
+
+    super(toSelectLocModel);
+
+    pruneTheTree = pruneTree;
+    numSets = num;
+    m_cleanup = cleanup;
+    m_seed = seed;
+  }
+
+  /**
+   * Returns default capabilities of the classifier tree.
+   *
+   * @return      the capabilities of this classifier tree
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    // instances
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Method for building a pruneable classifier tree.
+   *
+   * @param data the data to build the tree from 
+   * @throws Exception if tree can't be built successfully
+   */
+  public void buildClassifier(Instances data) 
+       throws Exception {
+
+    // can classifier tree handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+   Random random = new Random(m_seed);
+   data.stratify(numSets);
+   buildTree(data.trainCV(numSets, numSets - 1, random),
+	     data.testCV(numSets, numSets - 1), false);
+   if (pruneTheTree) {
+     prune();
+   }
+   if (m_cleanup) {
+     cleanup(new Instances(data, 0));
+   }
+  }
+
+  /**
+   * Prunes a tree.
+   *
+   * @throws Exception if tree can't be pruned successfully
+   */
+  public void prune() throws Exception {
+  
+    if (!m_isLeaf) {
+      
+      // Prune all subtrees.
+      for (int i = 0; i < m_sons.length; i++)
+	son(i).prune();
+      
+      // Decide if leaf is best choice.
+      if (Utils.smOrEq(errorsForLeaf(),errorsForTree())) {
+	
+	// Free son Trees
+	m_sons = null;
+	m_isLeaf = true;
+	
+	// Get NoSplit Model for node.
+	m_localModel = new NoSplit(localModel().distribution());
+      }
+    }
+  }
+
+  /**
+   * Returns a newly created tree.
+   *
+   * @param train the training data
+   * @param test the test data
+   * @return the generated tree
+   * @throws Exception if something goes wrong
+   */
+  protected ClassifierTree getNewTree(Instances train, Instances test) 
+       throws Exception {
+
+    PruneableClassifierTree newTree = 
+      new PruneableClassifierTree(m_toSelectModel, pruneTheTree, numSets, m_cleanup,
+				  m_seed);
+    newTree.buildTree(train, test, false);
+    return newTree;
+  }
+
+  /**
+   * Computes estimated errors for tree.
+   *
+   * @return the estimated errors
+   * @throws Exception if error estimate can't be computed
+   */
+  private double errorsForTree() throws Exception {
+
+    double errors = 0;
+
+    if (m_isLeaf)
+      return errorsForLeaf();
+    else{
+      for (int i = 0; i < m_sons.length; i++)
+	if (Utils.eq(localModel().distribution().perBag(i), 0)) {
+	  errors += m_test.perBag(i)-
+	    m_test.perClassPerBag(i,localModel().distribution().
+				maxClass());
+	} else
+	  errors += son(i).errorsForTree();
+
+      return errors;
+    }
+  }
+
+  /**
+   * Computes estimated errors for leaf.
+   *
+   * @return the estimated errors
+   * @throws Exception if error estimate can't be computed
+   */
+  private double errorsForLeaf() throws Exception {
+
+    return m_test.total()-
+      m_test.perClass(localModel().distribution().maxClass());
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private ClassifierSplitModel localModel() {
+    
+    return (ClassifierSplitModel)m_localModel;
+  }
+
+  /**
+   * Method just exists to make program easier to read.
+   */
+  private PruneableClassifierTree son(int index) {
+
+    return (PruneableClassifierTree)m_sons[index];
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5533 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/SplitCriterion.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/SplitCriterion.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/SplitCriterion.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SplitCriterion.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class for computing splitting criteria
+ * with respect to distributions of class values.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public abstract class SplitCriterion
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5490996638027101259L;
+
+  /**
+   * Computes result of splitting criterion for given distribution.
+   *
+   * @return value of splitting criterion. 0 by default
+   */
+  public double splitCritValue(Distribution bags){
+
+    return 0;
+  }
+
+  /**
+   * Computes result of splitting criterion for given training and
+   * test distributions.
+   *
+   * @return value of splitting criterion. 0 by default
+   */
+  public double splitCritValue(Distribution train, Distribution test){
+
+    return 0;
+  }
+
+  /**
+   * Computes result of splitting criterion for given training and
+   * test distributions and given number of classes.
+   *
+   * @return value of splitting criterion. 0 by default
+   */
+  public double splitCritValue(Distribution train, Distribution test,
+			       int noClassesDefault){
+
+    return 0;
+  }
+
+  /**
+   * Computes result of splitting criterion for given training and
+   * test distributions and given default distribution.
+   *
+   * @return value of splitting criterion. 0 by default
+   */
+  public double splitCritValue(Distribution train, Distribution test,
+			       Distribution defC){
+
+    return 0;
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Stats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Stats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/j48/Stats.java	(revision 29)
@@ -0,0 +1,102 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Stats.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.j48;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+
+/**
+ * Class implementing a statistical routine needed by J48 to
+ * compute its error estimate.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public class Stats
+  implements RevisionHandler {
+
+  /**
+   * Computes estimated extra error for given total number of instances
+   * and error using normal approximation to binomial distribution
+   * (and continuity correction).
+   *
+   * @param N number of instances
+   * @param e observed error
+   * @param CF confidence value
+   */
+  public static double addErrs(double N, double e, float CF){
+
+    // Ignore stupid values for CF
+    if (CF > 0.5) {
+      System.err.println("WARNING: confidence value for pruning " +
+			 " too high. Error estimate not modified.");
+      return 0;
+    }
+
+    // Check for extreme cases at the low end because the
+    // normal approximation won't work
+    if (e < 1) {
+
+      // Base case (i.e. e == 0) from documenta Geigy Scientific
+      // Tables, 6th edition, page 185
+      double base = N * (1 - Math.pow(CF, 1 / N)); 
+      if (e == 0) {
+	return base; 
+      }
+    
+      // Use linear interpolation between 0 and 1 like C4.5 does
+      return base + e * (addErrs(N, 1, CF) - base);
+    }
+    
+    // Use linear interpolation at the high end (i.e. between N - 0.5
+    // and N) because of the continuity correction
+    if (e + 0.5 >= N) {
+
+      // Make sure that we never return anything smaller than zero
+      return Math.max(N - e, 0);
+    }
+
+    // Get z-score corresponding to CF
+    double z = Statistics.normalInverse(1 - CF);
+
+    // Compute upper limit of confidence interval
+    double  f = (e + 0.5) / N;
+    double r = (f + (z * z) / (2 * N) +
+		z * Math.sqrt((f / N) - 
+			      (f * f / N) + 
+			      (z * z / (4 * N * N)))) /
+      (1 + (z * z) / N);
+
+    return (r * N) - e;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LMTNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LMTNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LMTNode.java	(revision 29)
@@ -0,0 +1,959 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LMTNode.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.lmt;
+
+import weka.classifiers.Evaluation;
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.classifiers.trees.j48.ClassifierSplitModel;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Vector;
+
+/** 
+ * Auxiliary class for list of LMTNodes
+ */
+class CompareNode 
+    implements Comparator, RevisionHandler {
+
+    /**
+     * Compares its two arguments for order.
+     * 
+     * @param o1 first object
+     * @param o2 second object
+     * @return a negative integer, zero, or a positive integer as the first 
+     *         argument is less than, equal to, or greater than the second.
+     */
+    public int compare(Object o1, Object o2) {		
+	if ( ((LMTNode)o1).m_alpha < ((LMTNode)o2).m_alpha) return -1;
+	if ( ((LMTNode)o1).m_alpha > ((LMTNode)o2).m_alpha) return 1;
+	return 0;	
+    }        
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.8 $");
+    }
+}
+
+/**
+ * Class for logistic model tree structure. 
+ * 
+ * 
+ * @author Niels Landwehr 
+ * @author Marc Sumner 
+ * @version $Revision: 1.8 $
+ */
+public class LMTNode 
+    extends LogisticBase {
+  
+    /** for serialization */
+    static final long serialVersionUID = 1862737145870398755L;
+    
+    /** Total number of training instances. */
+    protected double m_totalInstanceWeight;
+    
+    /** Node id*/
+    protected int m_id;
+    
+    /** ID of logistic model at leaf*/
+    protected int m_leafModelNum;
+ 
+    /** Alpha-value (for pruning) at the node*/
+    public double m_alpha;
+    
+    /** Weighted number of training examples currently misclassified by the logistic model at the node*/ 
+    public double m_numIncorrectModel;
+
+    /** Weighted number of training examples currently misclassified by the subtree rooted at the node*/
+    public double m_numIncorrectTree;
+
+    /**minimum number of instances at which a node is considered for splitting*/
+    protected int m_minNumInstances;
+    
+    /**ModelSelection object (for splitting)*/
+    protected ModelSelection m_modelSelection;     
+
+    /**Filter to convert nominal attributes to binary*/
+    protected NominalToBinary m_nominalToBinary;  
+   
+    /**Simple regression functions fit by LogitBoost at higher levels in the tree*/
+    protected SimpleLinearRegression[][] m_higherRegressions;
+    
+    /**Number of simple regression functions fit by LogitBoost at higher levels in the tree*/
+    protected int m_numHigherRegressions = 0;
+    
+    /**Number of folds for CART pruning*/
+    protected static int m_numFoldsPruning = 5;
+
+    /**Use heuristic that determines the number of LogitBoost iterations only once in the beginning? */
+    protected boolean m_fastRegression;
+    
+    /**Number of instances at the node*/
+    protected int m_numInstances;    
+
+    /**The ClassifierSplitModel (for splitting)*/
+    protected ClassifierSplitModel m_localModel; 
+ 
+    /**Array of children of the node*/
+    protected LMTNode[] m_sons;           
+
+    /**True if node is leaf*/
+    protected boolean m_isLeaf;                   
+
+    /**
+     * Constructor for logistic model tree node. 
+     *
+     * @param modelSelection selection method for local splitting model
+     * @param numBoostingIterations sets the numBoostingIterations parameter
+     * @param fastRegression sets the fastRegression parameter
+     * @param errorOnProbabilities Use error on probabilities for stopping criterion of LogitBoost?
+     * @param minNumInstances minimum number of instances at which a node is considered for splitting
+     */
+    public LMTNode(ModelSelection modelSelection, int numBoostingIterations, 
+		   boolean fastRegression, 
+                   boolean errorOnProbabilities, int minNumInstances,
+                   double weightTrimBeta, boolean useAIC) {
+	m_modelSelection = modelSelection;
+	m_fixedNumIterations = numBoostingIterations;      
+	m_fastRegression = fastRegression;
+	m_errorOnProbabilities = errorOnProbabilities;
+	m_minNumInstances = minNumInstances;
+	m_maxIterations = 200;
+        setWeightTrimBeta(weightTrimBeta);
+        setUseAIC(useAIC);
+    }         
+    
+    /**
+     * Method for building a logistic model tree (only called for the root node).
+     * Grows an initial logistic model tree and prunes it back using the CART pruning scheme.
+     *
+     * @param data the data to train with
+     * @throws Exception if something goes wrong
+     */
+    public void buildClassifier(Instances data) throws Exception{
+	
+	//heuristic to avoid cross-validating the number of LogitBoost iterations
+	//at every node: build standalone logistic model and take its optimum number
+	//of iteration everywhere in the tree.
+	if (m_fastRegression && (m_fixedNumIterations < 0)) m_fixedNumIterations = tryLogistic(data);
+	
+	//Need to cross-validate alpha-parameter for CART-pruning
+	Instances cvData = new Instances(data);
+	cvData.stratify(m_numFoldsPruning);
+	
+	double[][] alphas = new double[m_numFoldsPruning][];
+	double[][] errors = new double[m_numFoldsPruning][];
+	
+	for (int i = 0; i < m_numFoldsPruning; i++) {
+	    //for every fold, grow tree on training set...
+	    Instances train = cvData.trainCV(m_numFoldsPruning, i);
+	    Instances test = cvData.testCV(m_numFoldsPruning, i);
+	    
+	    buildTree(train, null, train.numInstances() , 0);	
+	    
+	    int numNodes = getNumInnerNodes();	   
+	    alphas[i] = new double[numNodes + 2];
+	    errors[i] = new double[numNodes + 2];
+	    
+	    //... then prune back and log alpha-values and errors on test set
+	    prune(alphas[i], errors[i], test);	    	   
+	}
+	
+	//build tree using all the data
+	buildTree(data, null, data.numInstances(), 0);
+	int numNodes = getNumInnerNodes();
+
+	double[] treeAlphas = new double[numNodes + 2];	
+	
+	//prune back and log alpha-values     
+	int iterations = prune(treeAlphas, null, null);
+	
+	double[] treeErrors = new double[numNodes + 2];
+	
+	for (int i = 0; i <= iterations; i++){
+	    //compute midpoint alphas
+	    double alpha = Math.sqrt(treeAlphas[i] * treeAlphas[i+1]);
+	    double error = 0;
+	    
+	    //compute error estimate for final trees from the midpoint-alphas and the error estimates gotten in 
+	    //the cross-validation
+	    for (int k = 0; k < m_numFoldsPruning; k++) {
+		int l = 0;
+		while (alphas[k][l] <= alpha) l++;
+		error += errors[k][l - 1];
+	    }
+
+	    treeErrors[i] = error;	    	  	   
+	}
+	
+	//find best alpha 
+	int best = -1;
+	double bestError = Double.MAX_VALUE;
+	for (int i = iterations; i >= 0; i--) {
+	    if (treeErrors[i] < bestError) {
+		bestError = treeErrors[i];
+		best = i;
+	    }	    
+	}
+
+	double bestAlpha = Math.sqrt(treeAlphas[best] * treeAlphas[best + 1]);      	
+	
+	//"unprune" final tree (faster than regrowing it)
+	unprune();
+
+	//CART-prune it with best alpha
+	prune(bestAlpha);    	 		
+	cleanup();	
+    }
+
+    /**
+     * Method for building the tree structure.
+     * Builds a logistic model, splits the node and recursively builds tree for child nodes.
+     * @param data the training data passed on to this node
+     * @param higherRegressions An array of regression functions produced by LogitBoost at higher 
+     * levels in the tree. They represent a logistic regression model that is refined locally 
+     * at this node.
+     * @param totalInstanceWeight the total number of training examples
+     * @param higherNumParameters effective number of parameters in the logistic regression model built
+     * in parent nodes
+     * @throws Exception if something goes wrong
+     */
+    public void buildTree(Instances data, SimpleLinearRegression[][] higherRegressions, 
+			  double totalInstanceWeight, double higherNumParameters) throws Exception{
+
+	//save some stuff
+	m_totalInstanceWeight = totalInstanceWeight;
+	m_train = new Instances(data);
+	
+	m_isLeaf = true;
+	m_sons = null;
+	
+	m_numInstances = m_train.numInstances();
+	m_numClasses = m_train.numClasses();				
+	
+	//init 
+	m_numericData = getNumericData(m_train);		  
+	m_numericDataHeader = new Instances(m_numericData, 0);
+	
+	m_regressions = initRegressions();
+	m_numRegressions = 0;
+	
+	if (higherRegressions != null) m_higherRegressions = higherRegressions;
+	else m_higherRegressions = new SimpleLinearRegression[m_numClasses][0];	
+
+	m_numHigherRegressions = m_higherRegressions[0].length;	
+        
+        m_numParameters = higherNumParameters;
+        
+        //build logistic model
+        if (m_numInstances >= m_numFoldsBoosting) {
+            if (m_fixedNumIterations > 0){
+                performBoosting(m_fixedNumIterations);
+            } else if (getUseAIC()) {
+                performBoostingInfCriterion();
+            } else {
+                performBoostingCV();
+            }
+        }
+        
+        m_numParameters += m_numRegressions;
+	
+	//only keep the simple regression functions that correspond to the selected number of LogitBoost iterations
+	m_regressions = selectRegressions(m_regressions);
+
+	boolean grow;
+	//split node if more than minNumInstances...
+	if (m_numInstances > m_minNumInstances) {
+	    //split node: either splitting on class value (a la C4.5) or splitting on residuals
+	    if (m_modelSelection instanceof ResidualModelSelection) {	
+		//need ps/Ys/Zs/weights
+		double[][] probs = getProbs(getFs(m_numericData));
+		double[][] trainYs = getYs(m_train);
+		double[][] dataZs = getZs(probs, trainYs);
+		double[][] dataWs = getWs(probs, trainYs);
+		m_localModel = ((ResidualModelSelection)m_modelSelection).selectModel(m_train, dataZs, dataWs);	
+	    } else {
+		m_localModel = m_modelSelection.selectModel(m_train);	
+	    }
+	    //... and valid split found
+	    grow = (m_localModel.numSubsets() > 1);
+	} else {
+	    grow = false;
+	}
+	
+	if (grow) {	
+	    //create and build children of node
+	    m_isLeaf = false;	    	    
+	    Instances[] localInstances = m_localModel.split(m_train);	    
+	    m_sons = new LMTNode[m_localModel.numSubsets()];
+	    for (int i = 0; i < m_sons.length; i++) {
+		m_sons[i] = new LMTNode(m_modelSelection, m_fixedNumIterations, 
+					 m_fastRegression,  
+					 m_errorOnProbabilities,m_minNumInstances,
+                                        getWeightTrimBeta(), getUseAIC());
+		//the "higherRegressions" (partial logistic model fit at higher levels in the tree) passed
+		//on to the children are the "higherRegressions" at this node plus the regressions added
+		//at this node (m_regressions).
+		m_sons[i].buildTree(localInstances[i],
+				  mergeArrays(m_regressions, m_higherRegressions), m_totalInstanceWeight, m_numParameters);		
+		localInstances[i] = null;
+	    }	    
+	} 
+    }
+
+    /** 
+     * Prunes a logistic model tree using the CART pruning scheme, given a 
+     * cost-complexity parameter alpha.
+     * 
+     * @param alpha the cost-complexity measure  
+     * @throws Exception if something goes wrong
+     */
+    public void prune(double alpha) throws Exception {
+	
+	Vector nodeList; 	
+	CompareNode comparator = new CompareNode();	
+	
+	//determine training error of logistic models and subtrees, and calculate alpha-values from them
+	modelErrors();
+	treeErrors();
+	calculateAlphas();
+	
+	//get list of all inner nodes in the tree
+	nodeList = getNodes();
+       		
+	boolean prune = (nodeList.size() > 0);
+	
+	while (prune) {
+	    
+	    //select node with minimum alpha
+	    LMTNode nodeToPrune = (LMTNode)Collections.min(nodeList,comparator);
+	    
+	    //want to prune if its alpha is smaller than alpha
+	    if (nodeToPrune.m_alpha > alpha) break; 
+	    
+	    nodeToPrune.m_isLeaf = true;
+	    nodeToPrune.m_sons = null;
+	    
+	    //update tree errors and alphas
+	    treeErrors();
+	    calculateAlphas();
+
+	    nodeList = getNodes();
+	    prune = (nodeList.size() > 0);   	  
+	}  
+    }
+
+    /**
+     * Method for performing one fold in the cross-validation of the cost-complexity parameter.
+     * Generates a sequence of alpha-values with error estimates for the corresponding (partially pruned)
+     * trees, given the test set of that fold.
+     * @param alphas array to hold the generated alpha-values
+     * @param errors array to hold the corresponding error estimates
+     * @param test test set of that fold (to obtain error estimates)
+     * @throws Exception if something goes wrong
+     */
+    public int prune(double[] alphas, double[] errors, Instances test) throws Exception {
+	
+	Vector nodeList; 
+	
+	CompareNode comparator = new CompareNode();	
+
+	//determine training error of logistic models and subtrees, and calculate alpha-values from them
+	modelErrors();
+	treeErrors();
+	calculateAlphas();
+
+	//get list of all inner nodes in the tree
+	nodeList = getNodes();
+       
+	boolean prune = (nodeList.size() > 0);           		
+
+	//alpha_0 is always zero (unpruned tree)
+	alphas[0] = 0;
+
+	Evaluation eval;
+
+	//error of unpruned tree
+	if (errors != null) {
+	    eval = new Evaluation(test);
+	    eval.evaluateModel(this, test);
+	    errors[0] = eval.errorRate(); 
+	}	
+       
+	int iteration = 0;
+	while (prune) {
+
+	    iteration++;
+	    
+	    //get node with minimum alpha
+	    LMTNode nodeToPrune = (LMTNode)Collections.min(nodeList,comparator);
+
+	    nodeToPrune.m_isLeaf = true;
+	    //Do not set m_sons null, want to unprune
+	    
+	    //get alpha-value of node
+	    alphas[iteration] = nodeToPrune.m_alpha;
+ 	    
+	    //log error
+	    if (errors != null) {
+		eval = new Evaluation(test);
+		eval.evaluateModel(this, test);
+		errors[iteration] = eval.errorRate(); 
+	    }
+
+	    //update errors/alphas
+	    treeErrors();
+	    calculateAlphas();
+
+	    nodeList = getNodes();	   
+	    prune = (nodeList.size() > 0);   	   
+	} 
+	
+	//set last alpha 1 to indicate end
+	alphas[iteration + 1] = 1.0;	
+	return iteration;
+    }
+
+
+    /**
+     *Method to "unprune" a logistic model tree.
+     *Sets all leaf-fields to false.
+     *Faster than re-growing the tree because the logistic models do not have to be fit again. 
+     */
+    protected void unprune() {
+	if (m_sons != null) {
+	    m_isLeaf = false;
+	    for (int i = 0; i < m_sons.length; i++) m_sons[i].unprune();
+	}
+    }
+
+    /**
+     *Determines the optimum number of LogitBoost iterations to perform by building a standalone logistic 
+     *regression function on the training data. Used for the heuristic that avoids cross-validating this
+     *number again at every node.
+     *@param data training instances for the logistic model
+     *@throws Exception if something goes wrong
+     */
+    protected int tryLogistic(Instances data) throws Exception{
+	
+	//convert nominal attributes
+	Instances filteredData = new Instances(data);	
+	NominalToBinary nominalToBinary = new NominalToBinary();			
+	nominalToBinary.setInputFormat(filteredData);
+	filteredData = Filter.useFilter(filteredData, nominalToBinary);	
+	
+	LogisticBase logistic = new LogisticBase(0,true,m_errorOnProbabilities);
+	
+	//limit LogitBoost to 200 iterations (speed)
+	logistic.setMaxIterations(200);
+        logistic.setWeightTrimBeta(getWeightTrimBeta()); // Not in Marc's code. Added by Eibe.
+        logistic.setUseAIC(getUseAIC());
+	logistic.buildClassifier(filteredData);
+	
+	//return best number of iterations
+	return logistic.getNumRegressions(); 
+    }
+
+    /**
+     * Method to count the number of inner nodes in the tree
+     * @return the number of inner nodes
+     */
+    public int getNumInnerNodes(){
+	if (m_isLeaf) return 0;
+	int numNodes = 1;
+	for (int i = 0; i < m_sons.length; i++) numNodes += m_sons[i].getNumInnerNodes();
+	return numNodes;
+    }
+
+    /**
+     * Returns the number of leaves in the tree.
+     * Leaves are only counted if their logistic model has changed compared to the one of the parent node.
+     * @return the number of leaves
+     */
+     public int getNumLeaves(){
+	int numLeaves;
+	if (!m_isLeaf) {
+	    numLeaves = 0;
+	    int numEmptyLeaves = 0;
+	    for (int i = 0; i < m_sons.length; i++) {
+		numLeaves += m_sons[i].getNumLeaves();
+		if (m_sons[i].m_isLeaf && !m_sons[i].hasModels()) numEmptyLeaves++;
+	    }
+	    if (numEmptyLeaves > 1) {
+		numLeaves -= (numEmptyLeaves - 1);
+	    }
+	} else {
+	    numLeaves = 1;
+	}	   
+	return numLeaves;	
+    }
+
+    /**
+     *Updates the numIncorrectModel field for all nodes. This is needed for calculating the alpha-values. 
+     */
+    public void modelErrors() throws Exception{
+		
+	Evaluation eval = new Evaluation(m_train);
+		
+	if (!m_isLeaf) {
+	    m_isLeaf = true;
+	    eval.evaluateModel(this, m_train);
+	    m_isLeaf = false;
+	    m_numIncorrectModel = eval.incorrect();
+	    for (int i = 0; i < m_sons.length; i++) m_sons[i].modelErrors();
+	} else {
+	    eval.evaluateModel(this, m_train);
+	    m_numIncorrectModel = eval.incorrect();
+	}
+    }
+    
+    /**
+     *Updates the numIncorrectTree field for all nodes. This is needed for calculating the alpha-values. 
+     */
+    public void treeErrors(){
+	if (m_isLeaf) {
+	    m_numIncorrectTree = m_numIncorrectModel;
+	} else {
+	    m_numIncorrectTree = 0;
+	    for (int i = 0; i < m_sons.length; i++) {
+		m_sons[i].treeErrors();
+		m_numIncorrectTree += m_sons[i].m_numIncorrectTree;
+	    }	 
+	}	
+    }
+
+    /**
+     *Updates the alpha field for all nodes.
+     */
+    public void calculateAlphas() throws Exception {		
+		
+	if (!m_isLeaf) {	
+	    double errorDiff = m_numIncorrectModel - m_numIncorrectTree;	    	    
+	    
+	    if (errorDiff <= 0) {
+		//split increases training error (should not normally happen).
+		//prune it instantly.
+		m_isLeaf = true;
+		m_sons = null;
+		m_alpha = Double.MAX_VALUE;		
+	    } else {
+		//compute alpha
+		errorDiff /= m_totalInstanceWeight;		
+		m_alpha = errorDiff / (double)(getNumLeaves() - 1);
+		
+		for (int i = 0; i < m_sons.length; i++) m_sons[i].calculateAlphas();
+	    }
+	} else {	    
+	    //alpha = infinite for leaves (do not want to prune)
+	    m_alpha = Double.MAX_VALUE;
+	}
+    }
+    
+    /**
+     * Merges two arrays of regression functions into one
+     * @param a1 one array
+     * @param a2 the other array
+     *
+     * @return an array that contains all entries from both input arrays
+     */
+    protected SimpleLinearRegression[][] mergeArrays(SimpleLinearRegression[][] a1,	
+							   SimpleLinearRegression[][] a2){
+	int numModels1 = a1[0].length;
+	int numModels2 = a2[0].length;		
+	
+	SimpleLinearRegression[][] result =
+	    new SimpleLinearRegression[m_numClasses][numModels1 + numModels2];
+	
+	for (int i = 0; i < m_numClasses; i++)
+	    for (int j = 0; j < numModels1; j++) {
+		result[i][j]  = a1[i][j];
+	    }
+	for (int i = 0; i < m_numClasses; i++)
+	    for (int j = 0; j < numModels2; j++) result[i][j+numModels1] = a2[i][j];
+	return result;
+    }
+
+    /**
+     * Return a list of all inner nodes in the tree
+     * @return the list of nodes
+     */
+    public Vector getNodes(){
+	Vector nodeList = new Vector();
+	getNodes(nodeList);
+	return nodeList;
+    }
+
+    /**
+     * Fills a list with all inner nodes in the tree
+     * 
+     * @param nodeList the list to be filled
+     */
+    public void getNodes(Vector nodeList) {
+	if (!m_isLeaf) {
+	    nodeList.add(this);
+	    for (int i = 0; i < m_sons.length; i++) m_sons[i].getNodes(nodeList);
+	}	
+    }
+    
+    /**
+     * Returns a numeric version of a set of instances.
+     * All nominal attributes are replaced by binary ones, and the class variable is replaced
+     * by a pseudo-class variable that is used by LogitBoost.
+     */
+    protected Instances getNumericData(Instances train) throws Exception{
+	
+	Instances filteredData = new Instances(train);	
+	m_nominalToBinary = new NominalToBinary();			
+	m_nominalToBinary.setInputFormat(filteredData);
+	filteredData = Filter.useFilter(filteredData, m_nominalToBinary);	
+
+	return super.getNumericData(filteredData);
+    }
+
+    /**
+     * Computes the F-values of LogitBoost for an instance from the current logistic model at the node
+     * Note that this also takes into account the (partial) logistic model fit at higher levels in 
+     * the tree.
+     * @param instance the instance
+     * @return the array of F-values 
+     */
+    protected double[] getFs(Instance instance) throws Exception{
+	
+	double [] pred = new double [m_numClasses];
+	
+	//Need to take into account partial model fit at higher levels in the tree (m_higherRegressions) 
+	//and the part of the model fit at this node (m_regressions).
+
+	//Fs from m_regressions (use method of LogisticBase)
+	double [] instanceFs = super.getFs(instance);		
+
+	//Fs from m_higherRegressions
+	for (int i = 0; i < m_numHigherRegressions; i++) {
+	    double predSum = 0;
+	    for (int j = 0; j < m_numClasses; j++) {
+		pred[j] = m_higherRegressions[j][i].classifyInstance(instance);
+		predSum += pred[j];
+	    }
+	    predSum /= m_numClasses;
+	    for (int j = 0; j < m_numClasses; j++) {
+		instanceFs[j] += (pred[j] - predSum) * (m_numClasses - 1) 
+		    / m_numClasses;
+	    }
+	}
+	return instanceFs; 
+    }
+    
+    /**
+     *Returns true if the logistic regression model at this node has changed compared to the
+     *one at the parent node.
+     *@return whether it has changed
+     */
+    public boolean hasModels() {
+	return (m_numRegressions > 0);
+    }
+
+    /**
+     * Returns the class probabilities for an instance according to the logistic model at the node.
+     * @param instance the instance
+     * @return the array of probabilities
+     */
+    public double[] modelDistributionForInstance(Instance instance) throws Exception {
+	
+	//make copy and convert nominal attributes
+	instance = (Instance)instance.copy();		
+	m_nominalToBinary.input(instance);
+	instance = m_nominalToBinary.output();	
+	
+	//saet numeric pseudo-class
+	instance.setDataset(m_numericDataHeader);		
+	
+	return probs(getFs(instance));
+    }
+
+    /**
+     * Returns the class probabilities for an instance given by the logistic model tree.
+     * @param instance the instance
+     * @return the array of probabilities
+     */
+    public double[] distributionForInstance(Instance instance) throws Exception {
+	
+	double[] probs;
+	
+	if (m_isLeaf) {	    
+	    //leaf: use logistic model
+	    probs = modelDistributionForInstance(instance);
+	} else {
+	    //sort into appropiate child node
+	    int branch = m_localModel.whichSubset(instance);
+	    probs = m_sons[branch].distributionForInstance(instance);
+	}  			
+	return probs;
+    }
+
+    /**
+     * Returns the number of leaves (normal count).
+     * @return the number of leaves
+     */
+    public int numLeaves() {	
+	if (m_isLeaf) return 1;	
+	int numLeaves = 0;
+	for (int i = 0; i < m_sons.length; i++) numLeaves += m_sons[i].numLeaves();
+   	return numLeaves;
+    }
+    
+    /**
+     * Returns the number of nodes.
+     * @return the number of nodes
+     */
+    public int numNodes() {
+	if (m_isLeaf) return 1;	
+	int numNodes = 1;
+	for (int i = 0; i < m_sons.length; i++) numNodes += m_sons[i].numNodes();
+   	return numNodes;
+    }
+
+    /**
+     * Returns a description of the logistic model tree (tree structure and logistic models)
+     * @return describing string
+     */
+    public String toString(){	
+	//assign numbers to logistic regression functions at leaves
+	assignLeafModelNumbers(0);	
+	try{
+	    StringBuffer text = new StringBuffer();
+	    
+	    if (m_isLeaf) {
+		text.append(": ");
+		text.append("LM_"+m_leafModelNum+":"+getModelParameters());
+	    } else {
+		dumpTree(0,text);	    	    
+	    }
+	    text.append("\n\nNumber of Leaves  : \t"+numLeaves()+"\n");
+	    text.append("\nSize of the Tree : \t"+numNodes()+"\n");	
+	        
+	    //This prints logistic models after the tree, comment out if only tree should be printed
+	    text.append(modelsToString());
+	    return text.toString();
+	} catch (Exception e){
+	    return "Can't print logistic model tree";
+	}
+	
+        
+    }
+
+    /**
+     * Returns a string describing the number of LogitBoost iterations performed at this node, the total number
+     * of LogitBoost iterations performed (including iterations at higher levels in the tree), and the number
+     * of training examples at this node.
+     * @return the describing string
+     */
+    public String getModelParameters(){
+	
+	StringBuffer text = new StringBuffer();
+	int numModels = m_numRegressions+m_numHigherRegressions;
+	text.append(m_numRegressions+"/"+numModels+" ("+m_numInstances+")");
+	return text.toString();
+    }
+    
+   
+    /**
+     * Help method for printing tree structure.
+     *
+     * @throws Exception if something goes wrong
+     */
+    protected void dumpTree(int depth,StringBuffer text) 
+	throws Exception {
+	
+	for (int i = 0; i < m_sons.length; i++) {
+	    text.append("\n");
+	    for (int j = 0; j < depth; j++)
+		text.append("|   ");
+	    text.append(m_localModel.leftSide(m_train));
+	    text.append(m_localModel.rightSide(i, m_train));
+	    if (m_sons[i].m_isLeaf) {
+		text.append(": ");
+		text.append("LM_"+m_sons[i].m_leafModelNum+":"+m_sons[i].getModelParameters());
+	    }else
+		m_sons[i].dumpTree(depth+1,text);
+	}
+    }
+
+    /**
+     * Assigns unique IDs to all nodes in the tree
+     */
+    public int assignIDs(int lastID) {
+	
+	int currLastID = lastID + 1;
+	
+	m_id = currLastID;
+	if (m_sons != null) {
+	    for (int i = 0; i < m_sons.length; i++) {
+		currLastID = m_sons[i].assignIDs(currLastID);
+	    }
+	}
+	return currLastID;
+    }
+    
+    /**
+     * Assigns numbers to the logistic regression models at the leaves of the tree
+     */
+    public int assignLeafModelNumbers(int leafCounter) {
+	if (!m_isLeaf) {
+	    m_leafModelNum = 0;
+	    for (int i = 0; i < m_sons.length; i++){
+		leafCounter = m_sons[i].assignLeafModelNumbers(leafCounter);
+	    }
+	} else {
+	    leafCounter++;
+	    m_leafModelNum = leafCounter;
+	} 
+	return leafCounter;
+    }
+
+    /**
+     * Returns an array containing the coefficients of the logistic regression function at this node.
+     * @return the array of coefficients, first dimension is the class, second the attribute. 
+     */
+    protected double[][] getCoefficients(){
+       
+	//Need to take into account partial model fit at higher levels in the tree (m_higherRegressions) 
+	//and the part of the model fit at this node (m_regressions).
+	
+	//get coefficients from m_regressions: use method of LogisticBase
+	double[][] coefficients = super.getCoefficients();
+	//get coefficients from m_higherRegressions:
+        double constFactor = (double)(m_numClasses - 1) / (double)m_numClasses; // (J - 1)/J
+	for (int j = 0; j < m_numClasses; j++) {
+	    for (int i = 0; i < m_numHigherRegressions; i++) {		
+		double slope = m_higherRegressions[j][i].getSlope();
+		double intercept = m_higherRegressions[j][i].getIntercept();
+		int attribute = m_higherRegressions[j][i].getAttributeIndex();
+		coefficients[j][0] += constFactor * intercept;
+		coefficients[j][attribute + 1] += constFactor * slope;
+	    }
+	}
+
+	return coefficients;
+    }
+    
+    /**
+     * Returns a string describing the logistic regression function at the node.
+     */
+    public String modelsToString(){
+	
+	StringBuffer text = new StringBuffer();
+	if (m_isLeaf) {
+	    text.append("LM_"+m_leafModelNum+":"+super.toString());
+	} else {
+	    for (int i = 0; i < m_sons.length; i++) {
+		text.append("\n"+m_sons[i].modelsToString());
+	    }
+	}
+	return text.toString();	    
+    }
+
+    /**
+     * Returns graph describing the tree.
+     *
+     * @throws Exception if something goes wrong
+     */
+    public String graph() throws Exception {
+	
+	StringBuffer text = new StringBuffer();
+	
+	assignIDs(-1);
+	assignLeafModelNumbers(0);
+	text.append("digraph LMTree {\n");
+	if (m_isLeaf) {
+	    text.append("N" + m_id + " [label=\"LM_"+m_leafModelNum+":"+getModelParameters()+"\" " + 
+			"shape=box style=filled");
+	    text.append("]\n");
+	}else {
+	    text.append("N" + m_id 
+			+ " [label=\"" + 
+			m_localModel.leftSide(m_train) + "\" ");
+	    text.append("]\n");
+	    graphTree(text);
+	}
+    
+	return text.toString() +"}\n";
+    }
+
+    /**
+     * Helper function for graph description of tree
+     *
+     * @throws Exception if something goes wrong
+     */
+    private void graphTree(StringBuffer text) throws Exception {
+	
+	for (int i = 0; i < m_sons.length; i++) {
+	    text.append("N" + m_id  
+			+ "->" + 
+			"N" + m_sons[i].m_id +
+			" [label=\"" + m_localModel.rightSide(i,m_train).trim() + 
+			"\"]\n");
+	    if (m_sons[i].m_isLeaf) {
+		text.append("N" +m_sons[i].m_id + " [label=\"LM_"+m_sons[i].m_leafModelNum+":"+
+			    m_sons[i].getModelParameters()+"\" " + "shape=box style=filled");
+		text.append("]\n");
+	    } else {
+		text.append("N" + m_sons[i].m_id +
+			    " [label=\""+m_sons[i].m_localModel.leftSide(m_train) + 
+			    "\" ");
+		text.append("]\n");
+		m_sons[i].graphTree(text);
+	    }
+	}
+    } 
+    
+    /**
+     * Cleanup in order to save memory.
+     */
+    public void cleanup() {
+	super.cleanup();
+	if (!m_isLeaf) {
+	    for (int i = 0; i < m_sons.length; i++) m_sons[i].cleanup();
+	}
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.8 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LogisticBase.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LogisticBase.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/LogisticBase.java	(revision 29)
@@ -0,0 +1,1039 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LogisticBase.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.lmt;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.functions.SimpleLinearRegression;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+/**
+ * Base/helper class for building logistic regression models with the LogitBoost algorithm.
+ * Used for building logistic model trees (weka.classifiers.trees.lmt.LMT)
+ * and standalone logistic regression (weka.classifiers.functions.SimpleLogistic).
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Niels Landwehr
+ * @author Marc Sumner
+ * @version $Revision: 5928 $
+ */
+public class LogisticBase 
+    extends AbstractClassifier 
+    implements WeightedInstancesHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 168765678097825064L;
+  
+    /** Header-only version of the numeric version of the training data*/
+    protected Instances m_numericDataHeader;
+    /** 
+     * Numeric version of the training data. Original class is replaced by a numeric pseudo-class.
+     */
+    protected Instances m_numericData;
+    
+    /** Training data */
+    protected Instances m_train;
+    
+    /** Use cross-validation to determine best number of LogitBoost iterations ?*/
+    protected boolean m_useCrossValidation;
+
+    /**Use error on probabilities for stopping criterion of LogitBoost? */
+    protected boolean m_errorOnProbabilities;
+
+    /**Use fixed number of iterations for LogitBoost? (if negative, cross-validate number of iterations)*/
+    protected int m_fixedNumIterations;
+    
+    /**Use heuristic to stop performing LogitBoost iterations earlier?
+     * If enabled, LogitBoost is stopped if the current (local) minimum of the error on a test set as 
+     * a function of the number of iterations has not changed for m_heuristicStop iterations.
+     */
+    protected int m_heuristicStop = 50;
+ 
+    /**The number of LogitBoost iterations performed.*/
+    protected int m_numRegressions = 0;
+    
+    /**The maximum number of LogitBoost iterations*/
+    protected int m_maxIterations;
+    
+    /**The number of different classes*/
+    protected int m_numClasses;
+
+    /**Array holding the simple regression functions fit by LogitBoost*/
+    protected SimpleLinearRegression[][] m_regressions;
+        
+    /**Number of folds for cross-validating number of LogitBoost iterations*/
+    protected static int m_numFoldsBoosting = 5;
+
+    /**Threshold on the Z-value for LogitBoost*/
+    protected static final double Z_MAX = 3;
+    
+    /** If true, the AIC is used to choose the best iteration*/
+    private boolean m_useAIC = false;
+    
+    /** Effective number of parameters used for AIC / BIC automatic stopping */
+    protected double m_numParameters = 0;
+    
+    /**Threshold for trimming weights. Instances with a weight lower than this (as a percentage
+     * of total weights) are not included in the regression fit.
+     **/
+    protected double m_weightTrimBeta = 0;
+
+    /**
+     * Constructor that creates LogisticBase object with standard options.
+     */
+    public LogisticBase(){
+	m_fixedNumIterations = -1;
+	m_useCrossValidation = true;
+	m_errorOnProbabilities = false;	
+	m_maxIterations = 500;
+        m_useAIC = false;
+        m_numParameters = 0;
+    }
+    
+    /**
+     * Constructor to create LogisticBase object.
+     * @param numBoostingIterations fixed number of iterations for LogitBoost (if negative, use cross-validation or
+     * stopping criterion on the training data).
+     * @param useCrossValidation cross-validate number of LogitBoost iterations (if false, use stopping 
+     * criterion on the training data).
+     * @param errorOnProbabilities if true, use error on probabilities 
+     * instead of misclassification for stopping criterion of LogitBoost
+     */
+    public LogisticBase(int numBoostingIterations, boolean useCrossValidation, boolean errorOnProbabilities){
+	m_fixedNumIterations = numBoostingIterations;
+	m_useCrossValidation = useCrossValidation;
+	m_errorOnProbabilities = errorOnProbabilities;	
+	m_maxIterations = 500;
+        m_useAIC = false;
+        m_numParameters = 0;
+    }    
+
+    /**
+     * Builds the logistic regression model usiing LogitBoost.
+     * 
+     * @param data the training data
+     * @throws Exception if something goes wrong
+     */
+    public void buildClassifier(Instances data) throws Exception {			
+
+	m_train = new Instances(data);
+	
+	m_numClasses = m_train.numClasses();
+	
+	//init the array of simple regression functions
+	m_regressions = initRegressions();
+	m_numRegressions = 0;
+
+	//get numeric version of the training data (class variable replaced  by numeric pseudo-class)
+	m_numericData = getNumericData(m_train);	
+	
+	//save header info
+	m_numericDataHeader = new Instances(m_numericData, 0);
+	
+	
+	if (m_fixedNumIterations > 0) {
+	    //run LogitBoost for fixed number of iterations
+	    performBoosting(m_fixedNumIterations);
+	} else if (m_useAIC) { // Marc had this after the test for m_useCrossValidation. Changed by Eibe.
+            //run LogitBoost using information criterion for stopping
+            performBoostingInfCriterion();
+        } else if (m_useCrossValidation) {
+	    //cross-validate number of LogitBoost iterations
+	    performBoostingCV();
+	} else {
+	    //run LogitBoost with number of iterations that minimizes error on the training set
+	    performBoosting();
+	}	
+	
+	//only keep the simple regression functions that correspond to the selected number of LogitBoost iterations
+	m_regressions = selectRegressions(m_regressions);	
+    }   
+
+    /**
+     * Runs LogitBoost, determining the best number of iterations by cross-validation.
+     * 
+     * @throws Exception if something goes wrong
+     */
+    protected void performBoostingCV() throws Exception{			
+	
+	//completed iteration keeps track of the number of iterations that have been
+	//performed in every fold (some might stop earlier than others). 
+	//Best iteration is selected only from these.
+	int completedIterations = m_maxIterations;
+	
+	Instances allData = new Instances(m_train);
+	
+	allData.stratify(m_numFoldsBoosting);	      
+
+	double[] error = new double[m_maxIterations + 1];	
+	
+	for (int i = 0; i < m_numFoldsBoosting; i++) {
+	    //split into training/test data in fold
+	    Instances train = allData.trainCV(m_numFoldsBoosting,i);
+	    Instances test = allData.testCV(m_numFoldsBoosting,i);
+
+	    //initialize LogitBoost
+	    m_numRegressions = 0;
+	    m_regressions = initRegressions();
+
+	    //run LogitBoost iterations
+	    int iterations = performBoosting(train,test,error,completedIterations);	    
+	    if (iterations < completedIterations) completedIterations = iterations;	    
+	}
+	
+	//determine iteration with minimum error over the folds
+	int bestIteration = getBestIteration(error,completedIterations);
+	
+	//rebuild model on all of the training data
+	m_numRegressions = 0;
+	performBoosting(bestIteration);
+    }    
+    
+    /**
+     * Runs LogitBoost, determining the best number of iterations by an information criterion (currently AIC).
+     */
+    protected void performBoostingInfCriterion() throws Exception{
+        
+        double criterion = 0.0;
+        double bestCriterion = Double.MAX_VALUE;
+        int bestIteration = 0;
+        int noMin = 0;
+        
+        // Variable to keep track of criterion values (AIC)
+        double criterionValue = Double.MAX_VALUE;
+        
+        // initialize Ys/Fs/ps
+        double[][] trainYs = getYs(m_train);
+        double[][] trainFs = getFs(m_numericData);
+        double[][] probs = getProbs(trainFs);
+        
+        // Array with true/false if the attribute is included in the model or not
+        boolean[][] attributes = new boolean[m_numClasses][m_numericDataHeader.numAttributes()];
+        
+        int iteration = 0;
+        while (iteration < m_maxIterations) {
+            
+            //perform single LogitBoost iteration
+            boolean foundAttribute = performIteration(iteration, trainYs, trainFs, probs, m_numericData);
+            if (foundAttribute) {
+                iteration++;
+                m_numRegressions = iteration;
+            } else {
+                //could not fit simple linear regression: stop LogitBoost
+                break;
+            }
+            
+            double numberOfAttributes = m_numParameters + iteration;
+            
+            // Fill criterion array values
+            criterionValue = 2.0 * negativeLogLikelihood(trainYs, probs) +
+              2.0 * numberOfAttributes;
+
+            //heuristic: stop LogitBoost if the current minimum has not changed for <m_heuristicStop> iterations
+            if (noMin > m_heuristicStop) break;
+            if (criterionValue < bestCriterion) {
+                bestCriterion = criterionValue;
+                bestIteration = iteration;
+                noMin = 0;
+            } else {
+                noMin++;
+            }
+        }
+
+        m_numRegressions = 0;
+        performBoosting(bestIteration);
+    }
+
+    /**
+     * Runs LogitBoost on a training set and monitors the error on a test set.
+     * Used for running one fold when cross-validating the number of LogitBoost iterations.
+     * @param train the training set
+     * @param test the test set
+     * @param error array to hold the logged error values
+     * @param maxIterations the maximum number of LogitBoost iterations to run
+     * @return the number of completed LogitBoost iterations (can be smaller than maxIterations 
+     * if the heuristic for early stopping is active or there is a problem while fitting the regressions 
+     * in LogitBoost).
+     * @throws Exception if something goes wrong
+     */
+    protected int performBoosting(Instances train, Instances test, 
+				  double[] error, int maxIterations) throws Exception{
+	
+	//get numeric version of the (sub)set of training instances
+	Instances numericTrain = getNumericData(train);		
+
+	//initialize Ys/Fs/ps 
+	double[][] trainYs = getYs(train);
+	double[][] trainFs = getFs(numericTrain);		
+	double[][] probs = getProbs(trainFs);	
+
+	int iteration = 0;
+
+ 	int noMin = 0;
+	double lastMin = Double.MAX_VALUE;	
+	
+	if (m_errorOnProbabilities) error[0] += getMeanAbsoluteError(test);
+	else error[0] += getErrorRate(test);
+	
+	while (iteration < maxIterations) {
+	  
+	    //perform single LogitBoost iteration
+	    boolean foundAttribute = performIteration(iteration, trainYs, trainFs, probs, numericTrain);
+	    if (foundAttribute) {
+		iteration++;
+		m_numRegressions = iteration;
+	    } else {
+		//could not fit simple linear regression: stop LogitBoost
+		break;
+	    }
+	    
+	    if (m_errorOnProbabilities) error[iteration] += getMeanAbsoluteError(test);
+	    else error[iteration] += getErrorRate(test);
+	  
+	    //heuristic: stop LogitBoost if the current minimum has not changed for <m_heuristicStop> iterations
+	    if (noMin > m_heuristicStop) break;
+	    if (error[iteration] < lastMin) {
+		lastMin = error[iteration];
+		noMin = 0;
+	    } else {
+		noMin++;
+	    }	    	      	    
+	}
+
+	return iteration;
+    }
+
+    /**
+     * Runs LogitBoost with a fixed number of iterations.
+     * @param numIterations the number of iterations to run
+     * @throws Exception if something goes wrong
+     */
+    protected void performBoosting(int numIterations) throws Exception{
+
+	//initialize Ys/Fs/ps 
+	double[][] trainYs = getYs(m_train);
+	double[][] trainFs = getFs(m_numericData);		
+	double[][] probs = getProbs(trainFs);
+	
+	int iteration = 0;
+
+	//run iterations
+	while (iteration < numIterations) {
+	    boolean foundAttribute = performIteration(iteration, trainYs, trainFs, probs, m_numericData);
+	    if (foundAttribute) iteration++;
+	    else break;
+	}
+	
+	m_numRegressions = iteration;
+    }
+    
+    /**
+     * Runs LogitBoost using the stopping criterion on the training set.
+     * The number of iterations is used that gives the lowest error on the training set, either misclassification
+     * or error on probabilities (depending on the errorOnProbabilities option).
+     * @throws Exception if something goes wrong
+     */
+    protected void performBoosting() throws Exception{
+	
+	//initialize Ys/Fs/ps
+	double[][] trainYs = getYs(m_train);
+	double[][] trainFs = getFs(m_numericData);		
+	double[][] probs = getProbs(trainFs);	
+
+	int iteration = 0;
+
+	double[] trainErrors = new double[m_maxIterations+1];
+	trainErrors[0] = getErrorRate(m_train);
+	
+	int noMin = 0;
+	double lastMin = Double.MAX_VALUE;
+	
+	while (iteration < m_maxIterations) {
+	    boolean foundAttribute = performIteration(iteration, trainYs, trainFs, probs, m_numericData);
+	    if (foundAttribute) {
+		iteration++;
+		m_numRegressions = iteration;
+	    } else {		
+		//could not fit simple regression
+		break;
+	    }
+	    
+	    trainErrors[iteration] = getErrorRate(m_train);	    
+	 
+	    //heuristic: stop LogitBoost if the current minimum has not changed for <m_heuristicStop> iterations
+	    if (noMin > m_heuristicStop) break;	    
+	    if (trainErrors[iteration] < lastMin) {
+		lastMin = trainErrors[iteration];
+		noMin = 0;
+	    } else {
+		noMin++;
+	    }
+	}
+	
+	//find iteration with best error
+        m_numRegressions = getBestIteration(trainErrors, iteration);	
+    }
+
+    /**
+     * Returns the misclassification error of the current model on a set of instances.
+     * @param data the set of instances
+     * @return the error rate
+     * @throws Exception if something goes wrong
+     */
+    protected double getErrorRate(Instances data) throws Exception {
+	Evaluation eval = new Evaluation(data);
+	eval.evaluateModel(this,data);
+	return eval.errorRate();
+    }
+
+    /**
+     * Returns the error of the probability estimates for the current model on a set of instances.
+     * @param data the set of instances
+     * @return the error
+     * @throws Exception if something goes wrong
+     */
+    protected double getMeanAbsoluteError(Instances data) throws Exception {
+	Evaluation eval = new Evaluation(data);
+	eval.evaluateModel(this,data);
+	return eval.meanAbsoluteError();
+    }
+
+    /**
+     * Helper function to find the minimum in an array of error values.
+     * 
+     * @param errors an array containing errors
+     * @param maxIteration the maximum of iterations
+     * @return the minimum
+     */
+    protected int getBestIteration(double[] errors, int maxIteration) {
+	double bestError = errors[0];
+	int bestIteration = 0;
+	for (int i = 1; i <= maxIteration; i++) {	    
+	    if (errors[i] < bestError) {
+		bestError = errors[i];
+		bestIteration = i;		
+	    }
+	} 
+	return bestIteration;
+    }
+
+    /**
+     * Performs a single iteration of LogitBoost, and updates the model accordingly.
+     * A simple regression function is fit to the response and added to the m_regressions array.
+     * @param iteration the current iteration 
+     * @param trainYs the y-values (see description of LogitBoost) for the model trained so far
+     * @param trainFs the F-values (see description of LogitBoost) for the model trained so far
+     * @param probs the p-values (see description of LogitBoost) for the model trained so far
+     * @param trainNumeric numeric version of the training data
+     * @return returns true if iteration performed successfully, false if no simple regression function 
+     * could be fitted.
+     * @throws Exception if something goes wrong
+     */
+    protected boolean performIteration(int iteration, 
+				       double[][] trainYs,
+				       double[][] trainFs,
+				       double[][] probs,
+				       Instances trainNumeric) throws Exception {
+	
+	for (int j = 0; j < m_numClasses; j++) {
+            // Keep track of sum of weights
+            double[] weights = new double[trainNumeric.numInstances()];
+            double weightSum = 0.0;
+	    
+	    //make copy of data (need to save the weights) 
+	    Instances boostData = new Instances(trainNumeric);
+	    
+	    for (int i = 0; i < trainNumeric.numInstances(); i++) {
+		
+		//compute response and weight
+		double p = probs[i][j];
+		double actual = trainYs[i][j];
+		double z = getZ(actual, p);
+		double w = (actual - p) / z;
+		
+		//set values for instance 
+		Instance current = boostData.instance(i);
+		current.setValue(boostData.classIndex(), z);
+		current.setWeight(current.weight() * w);				
+                
+                weights[i] = current.weight();
+                weightSum += current.weight();
+	    }
+            
+            Instances instancesCopy = new Instances(boostData);
+            
+            if (weightSum > 0) {
+                // Only the (1-beta)th quantile of instances are sent to the base classifier
+                if (m_weightTrimBeta > 0) {
+                    double weightPercentage = 0.0;
+                    int[] weightsOrder = new int[trainNumeric.numInstances()];
+                    weightsOrder = Utils.sort(weights);
+                    instancesCopy.delete();
+                    
+                    
+                    for (int i = weightsOrder.length-1; (i >= 0) && (weightPercentage < (1-m_weightTrimBeta)); i--) {
+                        instancesCopy.add(boostData.instance(weightsOrder[i]));
+                        weightPercentage += (weights[weightsOrder[i]] / weightSum);
+                        
+                    }
+                }
+                
+                //Scale the weights
+                weightSum = instancesCopy.sumOfWeights();
+                for (int i = 0; i < instancesCopy.numInstances(); i++) {
+                    Instance current = instancesCopy.instance(i);
+                    current.setWeight(current.weight() * (double)instancesCopy.numInstances() / weightSum);
+                }
+            }
+	    
+	    //fit simple regression function
+	    m_regressions[j][iteration].buildClassifier(instancesCopy);
+	    
+	    boolean foundAttribute = m_regressions[j][iteration].foundUsefulAttribute();
+	    if (!foundAttribute) {
+		//could not fit simple regression function
+		return false;
+	    }
+	    
+	}
+	
+	// Evaluate / increment trainFs from the classifier
+	for (int i = 0; i < trainFs.length; i++) {
+	    double [] pred = new double [m_numClasses];
+	    double predSum = 0;
+	    for (int j = 0; j < m_numClasses; j++) {
+		pred[j] = m_regressions[j][iteration]
+		    .classifyInstance(trainNumeric.instance(i));
+		predSum += pred[j];
+	    }
+	    predSum /= m_numClasses;
+	    for (int j = 0; j < m_numClasses; j++) {
+		trainFs[i][j] += (pred[j] - predSum) * (m_numClasses - 1) 
+		    / m_numClasses;
+	    }
+	}
+	
+	// Compute the current probability estimates
+	for (int i = 0; i < trainYs.length; i++) {
+	    probs[i] = probs(trainFs[i]);
+	}
+	return true;
+    }    
+
+    /**
+     * Helper function to initialize m_regressions.
+     * 
+     * @return the generated classifiers
+     */
+    protected SimpleLinearRegression[][] initRegressions(){
+	SimpleLinearRegression[][] classifiers =   
+	    new SimpleLinearRegression[m_numClasses][m_maxIterations];
+	for (int j = 0; j < m_numClasses; j++) {
+	    for (int i = 0; i < m_maxIterations; i++) {
+		classifiers[j][i] = new SimpleLinearRegression();
+		classifiers[j][i].setSuppressErrorMessage(true);
+	    }
+	}
+	return classifiers;
+    }
+
+    /**
+     * Converts training data to numeric version. The class variable is replaced by a pseudo-class 
+     * used by LogitBoost.
+     * 
+     * @param data the data to convert
+     * @return the converted data
+     * @throws Exception if something goes wrong
+     */
+    protected Instances getNumericData(Instances data) throws Exception{
+	Instances numericData = new Instances(data);
+	
+	int classIndex = numericData.classIndex();
+	numericData.setClassIndex(-1);
+	numericData.deleteAttributeAt(classIndex);
+	numericData.insertAttributeAt(new Attribute("'pseudo class'"), classIndex);
+	numericData.setClassIndex(classIndex);
+	return numericData;
+    }
+    
+    /**
+     * Helper function for cutting back m_regressions to the set of classifiers 
+     * (corresponsing to the number of LogitBoost iterations) that gave the 
+     * smallest error.
+     * 
+     * @param classifiers the original set of classifiers
+     * @return the cut back set of classifiers
+     */
+    protected SimpleLinearRegression[][] selectRegressions(SimpleLinearRegression[][] classifiers){
+	SimpleLinearRegression[][] goodClassifiers = 
+	    new SimpleLinearRegression[m_numClasses][m_numRegressions];
+	
+	for (int j = 0; j < m_numClasses; j++) {
+	    for (int i = 0; i < m_numRegressions; i++) {
+		goodClassifiers[j][i] = classifiers[j][i];
+	    }
+	}
+	return goodClassifiers;
+    }		
+    
+    /**
+     * Computes the LogitBoost response variable from y/p values 
+     * (actual/estimated class probabilities).
+     * 
+     * @param actual the actual class probability
+     * @param p the estimated class probability
+     * @return the LogitBoost response
+     */
+    protected double getZ(double actual, double p) {
+	double z;
+	if (actual == 1) {
+	    z = 1.0 / p;
+	    if (z > Z_MAX) { // threshold
+		z = Z_MAX;
+	    }
+	} else {
+	    z = -1.0 / (1.0 - p);
+	    if (z < -Z_MAX) { // threshold
+		z = -Z_MAX;
+	    }
+	}
+	return z;
+    }
+    
+    /**
+     * Computes the LogitBoost response for an array of y/p values 
+     * (actual/estimated class probabilities).
+     * 
+     * @param dataYs the actual class probabilities
+     * @param probs the estimated class probabilities
+     * @return the LogitBoost response
+     */
+    protected double[][] getZs(double[][] probs, double[][] dataYs) {
+	
+	double[][] dataZs = new double[probs.length][m_numClasses];
+	for (int j = 0; j < m_numClasses; j++) 
+	    for (int i = 0; i < probs.length; i++) dataZs[i][j] = getZ(dataYs[i][j], probs[i][j]);
+	return dataZs;
+    }
+    
+    /**
+     * Computes the LogitBoost weights from an array of y/p values 
+     * (actual/estimated class probabilities).
+     * 
+     * @param dataYs the actual class probabilities
+     * @param probs the estimated class probabilities
+     * @return the LogitBoost weights
+     */
+    protected double[][] getWs(double[][] probs, double[][] dataYs) {
+	
+	double[][] dataWs = new double[probs.length][m_numClasses];
+	for (int j = 0; j < m_numClasses; j++) 
+	    for (int i = 0; i < probs.length; i++){
+	    double z = getZ(dataYs[i][j], probs[i][j]);
+	    dataWs[i][j] = (dataYs[i][j] - probs[i][j]) / z;
+	    }
+	return dataWs;
+    }
+
+    /**
+     * Computes the p-values (probabilities for the classes) from the F-values 
+     * of the logistic model.
+     * 
+     * @param Fs the F-values
+     * @return the p-values
+     */
+    protected double[] probs(double[] Fs) {
+	
+	double maxF = -Double.MAX_VALUE;
+	for (int i = 0; i < Fs.length; i++) {
+	    if (Fs[i] > maxF) {
+		maxF = Fs[i];
+	    }
+	}   
+	double sum = 0;
+	double[] probs = new double[Fs.length];
+	for (int i = 0; i < Fs.length; i++) {
+	    probs[i] = Math.exp(Fs[i] - maxF);   
+	    sum += probs[i];
+	}
+	
+	Utils.normalize(probs, sum);
+	return probs;
+    }
+
+    /**
+     * Computes the Y-values (actual class probabilities) for a set of instances.
+     * 
+     * @param data the data to compute the Y-values from
+     * @return the Y-values
+     */
+    protected double[][] getYs(Instances data){
+	
+	double [][] dataYs = new double [data.numInstances()][m_numClasses];
+	for (int j = 0; j < m_numClasses; j++) {
+	    for (int k = 0; k < data.numInstances(); k++) {
+		dataYs[k][j] = (data.instance(k).classValue() == j) ? 
+		    1.0: 0.0;
+	    }
+	}
+	return dataYs;
+    }
+
+    /**
+     * Computes the F-values for a single instance.
+     * 
+     * @param instance the instance to compute the F-values for
+     * @return the F-values
+     * @throws Exception if something goes wrong
+     */
+    protected double[] getFs(Instance instance) throws Exception{
+	
+	double [] pred = new double [m_numClasses];
+	double [] instanceFs = new double [m_numClasses]; 
+	
+	//add up the predictions from the simple regression functions
+	for (int i = 0; i < m_numRegressions; i++) {
+	    double predSum = 0;
+	    for (int j = 0; j < m_numClasses; j++) {
+		pred[j] = m_regressions[j][i].classifyInstance(instance);
+		predSum += pred[j];
+	    }
+	    predSum /= m_numClasses;
+	    for (int j = 0; j < m_numClasses; j++) {
+		instanceFs[j] += (pred[j] - predSum) * (m_numClasses - 1) 
+		    / m_numClasses;
+	    }
+	}	
+	
+	return instanceFs; 
+    } 
+    
+    /**
+     * Computes the F-values for a set of instances.
+     * 
+     * @param data the data to work on
+     * @return the F-values
+     * @throws Exception if something goes wrong
+     */
+    protected double[][] getFs(Instances data) throws Exception{
+	
+	double[][] dataFs = new double[data.numInstances()][];
+       
+	for (int k = 0; k < data.numInstances(); k++) {
+	    dataFs[k] = getFs(data.instance(k));
+	}
+	
+	return dataFs;	
+    }   
+
+    /**
+     * Computes the p-values (probabilities for the different classes) from 
+     * the F-values for a set of instances.
+     * 
+     * @param dataFs the F-values
+     * @return the p-values
+     */
+    protected double[][] getProbs(double[][] dataFs){
+	
+	int numInstances = dataFs.length;
+	double[][] probs = new double[numInstances][];
+	
+	for (int k = 0; k < numInstances; k++) {       
+	    probs[k] = probs(dataFs[k]);
+	}
+	return probs;
+    }
+    
+    /**
+     * Returns the negative loglikelihood of the Y-values (actual class probabilities) given the 
+     * p-values (current probability estimates).
+     * 
+     * @param dataYs the Y-values
+     * @param probs the p-values
+     * @return the likelihood
+     */
+    protected double negativeLogLikelihood(double[][] dataYs, double[][] probs) {
+	
+	double logLikelihood = 0;
+	for (int i = 0; i < dataYs.length; i++) {
+	    for (int j = 0; j < m_numClasses; j++) {
+		if (dataYs[i][j] == 1.0) {
+		    logLikelihood -= Math.log(probs[i][j]);
+		}
+	    }
+	}
+	return logLikelihood;// / (double)dataYs.length;
+    }
+
+    /**
+     * Returns an array of the indices of the attributes used in the logistic model.
+     * The first dimension is the class, the second dimension holds a list of attribute indices.
+     * Attribute indices start at zero.
+     * @return the array of attribute indices
+     */
+    public int[][] getUsedAttributes(){
+	
+	int[][] usedAttributes = new int[m_numClasses][];
+	
+	//first extract coefficients
+	double[][] coefficients = getCoefficients();
+	
+	for (int j = 0; j < m_numClasses; j++){
+	    
+	    //boolean array indicating if attribute used
+	    boolean[] attributes = new boolean[m_numericDataHeader.numAttributes()];
+	    for (int i = 0; i < attributes.length; i++) {
+		//attribute used if coefficient > 0
+		if (!Utils.eq(coefficients[j][i + 1],0)) attributes[i] = true;
+	    }
+	    	    
+	    int numAttributes = 0;
+	    for (int i = 0; i < m_numericDataHeader.numAttributes(); i++) if (attributes[i]) numAttributes++;
+	    
+	    //"collect" all attributes into array of indices
+	    int[] usedAttributesClass = new int[numAttributes];
+	    int count = 0;
+	    for (int i = 0; i < m_numericDataHeader.numAttributes(); i++) {
+		if (attributes[i]) {
+		usedAttributesClass[count] = i;
+		count++;
+		} 
+	    }
+	    
+	    usedAttributes[j] = usedAttributesClass;
+	}
+	
+	return usedAttributes;
+    }
+
+    /**
+     * The number of LogitBoost iterations performed (= the number of simple 
+     * regression functions fit).
+     * 
+     * @return the number of LogitBoost iterations performed 
+     */
+    public int getNumRegressions() {
+	return m_numRegressions;
+    }
+    
+    /**
+     * Get the value of weightTrimBeta.
+     *
+     * @return Value of weightTrimBeta.
+     */
+    public double getWeightTrimBeta(){
+        return m_weightTrimBeta;
+    }
+    
+    /**
+     * Get the value of useAIC.
+     *
+     * @return Value of useAIC.
+     */
+    public boolean getUseAIC(){
+        return m_useAIC;
+    }
+
+    /**
+     * Sets the parameter "maxIterations".
+     * 
+     * @param maxIterations the maximum iterations
+     */
+    public void setMaxIterations(int maxIterations) {
+	m_maxIterations = maxIterations;
+    }
+    
+    /**
+     * Sets the option "heuristicStop".
+     * 
+     * @param heuristicStop the heuristic stop to use
+     */
+    public void setHeuristicStop(int heuristicStop){
+	m_heuristicStop = heuristicStop;
+    }
+    
+    /**
+     * Sets the option "weightTrimBeta".
+     */
+    public void setWeightTrimBeta(double w){
+        m_weightTrimBeta = w;
+    }
+    
+    /**
+     * Set the value of useAIC.
+     *
+     * @param c Value to assign to useAIC.
+     */
+    public void setUseAIC(boolean c){
+        m_useAIC = c;
+    }
+
+    /**
+     * Returns the maxIterations parameter.
+     * 
+     * @return the maximum iteration
+     */
+    public int getMaxIterations(){
+	return m_maxIterations;
+    }
+        
+    /**
+     * Returns an array holding the coefficients of the logistic model.
+     * First dimension is the class, the second one holds a list of coefficients.
+     * At position zero, the constant term of the model is stored, then, the coefficients for
+     * the attributes in ascending order.
+     * @return the array of coefficients
+     */
+    protected double[][] getCoefficients(){
+	double[][] coefficients = new double[m_numClasses][m_numericDataHeader.numAttributes() + 1];
+	for (int j = 0; j < m_numClasses; j++) {
+	    //go through simple regression functions and add their coefficient to the coefficient of
+	    //the attribute they are built on.
+	    for (int i = 0; i < m_numRegressions; i++) {
+		
+		double slope = m_regressions[j][i].getSlope();
+		double intercept = m_regressions[j][i].getIntercept();
+		int attribute = m_regressions[j][i].getAttributeIndex();
+		
+		coefficients[j][0] += intercept;
+		coefficients[j][attribute + 1] += slope;
+	    }
+	}
+        
+        // Need to multiply all coefficients by (J-1) / J
+        for (int j = 0; j < coefficients.length; j++) {
+          for (int i = 0; i < coefficients[0].length; i++) {
+            coefficients[j][i] *= (double)(m_numClasses - 1) / (double)m_numClasses;
+          }
+        }
+
+	return coefficients;
+    }
+
+    /**
+     * Returns the fraction of all attributes in the data that are used in the 
+     * logistic model (in percent). 
+     * An attribute is used in the model if it is used in any of the models for 
+     * the different classes.
+     * 
+     * @return the fraction of all attributes that are used
+     */
+    public double percentAttributesUsed(){	
+	boolean[] attributes = new boolean[m_numericDataHeader.numAttributes()];
+	
+	double[][] coefficients = getCoefficients();
+	for (int j = 0; j < m_numClasses; j++){
+	    for (int i = 1; i < m_numericDataHeader.numAttributes() + 1; i++) {
+		//attribute used if it is used in any class, note coefficients are shifted by one (because
+		//of constant term).
+		if (!Utils.eq(coefficients[j][i],0)) attributes[i - 1] = true;
+	    }
+	}
+	
+	//count number of used attributes (without the class attribute)
+	double count = 0;
+	for (int i = 0; i < attributes.length; i++) if (attributes[i]) count++;
+	return count / (double)(m_numericDataHeader.numAttributes() - 1) * 100.0;
+    }
+    
+    /**
+     * Returns a description of the logistic model (i.e., attributes and 
+     * coefficients).
+     * 
+     * @return the description of the model
+     */
+    public String toString(){
+	
+	StringBuffer s = new StringBuffer();	
+
+	//get used attributes
+	int[][] attributes = getUsedAttributes();
+	
+	//get coefficients
+	double[][] coefficients = getCoefficients();
+	
+	for (int j = 0; j < m_numClasses; j++) {
+	    s.append("\nClass "+j+" :\n");
+	    //constant term
+	    s.append(Utils.doubleToString(coefficients[j][0],4,2)+" + \n");
+	    for (int i = 0; i < attributes[j].length; i++) {		
+		//attribute/coefficient pairs
+		s.append("["+m_numericDataHeader.attribute(attributes[j][i]).name()+"]");
+		s.append(" * " + Utils.doubleToString(coefficients[j][attributes[j][i]+1],4,2));
+		if (i != attributes[j].length - 1) s.append(" +");
+		s.append("\n");	    
+	    }
+	}	
+	return new String(s);
+    }
+
+    /** 
+     * Returns class probabilities for an instance.
+     *
+     * @param instance the instance to compute the distribution for
+     * @return the class probabilities
+     * @throws Exception if distribution can't be computed successfully
+     */
+    public double[] distributionForInstance(Instance instance) throws Exception {
+	
+	instance = (Instance)instance.copy();	
+
+	//set to numeric pseudo-class
+      	instance.setDataset(m_numericDataHeader);		
+	
+	//calculate probs via Fs
+	return probs(getFs(instance));
+    }
+
+    /**
+     * Cleanup in order to save memory.
+     */
+    public void cleanup() {
+	//save just header info
+	m_train = new Instances(m_train,0);
+	m_numericData = null;	
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5928 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualModelSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualModelSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualModelSelection.java	(revision 29)
@@ -0,0 +1,131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResidualModelSelection.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.lmt;
+
+import weka.classifiers.trees.j48.ClassifierSplitModel;
+import weka.classifiers.trees.j48.Distribution;
+import weka.classifiers.trees.j48.ModelSelection;
+import weka.classifiers.trees.j48.NoSplit;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+/**
+ * Helper class for logistic model trees (weka.classifiers.trees.lmt.LMT) to implement the 
+ * splitting criterion based on residuals.
+ * 
+ * @author Niels Landwehr
+ * @version $Revision: 1.4 $
+ */
+public class ResidualModelSelection
+  extends ModelSelection {
+
+  /** for serialization */
+  private static final long serialVersionUID = -293098783159385148L;
+
+  /** Minimum number of instances for leaves*/
+  protected int m_minNumInstances;
+
+  /** Minimum information gain for split*/
+  protected double m_minInfoGain;    
+
+  /**
+   * Constructor to create ResidualModelSelection object. 
+   * @param minNumInstances minimum number of instances for leaves
+   */
+  public ResidualModelSelection(int minNumInstances) {
+    m_minNumInstances = minNumInstances;
+    m_minInfoGain = 1.0E-4;
+  }
+
+  /**Method not in use*/
+  public void cleanup() {
+    //method not in use
+  }
+
+  /**
+   * Selects split based on residuals for the given dataset.
+   */
+  public final ClassifierSplitModel selectModel(Instances data, 
+      double[][] dataZs, double[][] dataWs) throws Exception{
+
+    int numAttributes = data.numAttributes();
+
+    if (numAttributes < 2) throw new Exception("Can't select Model without non-class attribute");
+    if (data.numInstances() < m_minNumInstances) return new NoSplit(new Distribution(data));
+
+
+    double bestGain = -Double.MAX_VALUE;
+    int bestAttribute = -1;
+
+    //try split on every attribute
+    for (int i = 0; i < numAttributes; i++) {
+      if (i != data.classIndex()) {
+
+	//build split
+	ResidualSplit split = new ResidualSplit(i);	    
+	split.buildClassifier(data, dataZs, dataWs);
+
+	if (split.checkModel(m_minNumInstances)){
+
+	  //evaluate split 
+	  double gain = split.entropyGain();	
+	  if (gain > bestGain) {
+	    bestGain = gain;
+	    bestAttribute = i;
+	  }
+	}
+      }    	    
+    }     
+
+    if (bestGain >= m_minInfoGain){
+      //return best split
+      ResidualSplit split = new ResidualSplit(bestAttribute);
+      split.buildClassifier(data, dataZs, dataWs);	
+      return split;	    
+    } else {	    
+      //could not find any split with enough information gain
+      return new NoSplit(new Distribution(data));	    
+    }
+  }
+
+  /**Method not in use*/
+  public final ClassifierSplitModel selectModel(Instances train) {
+    //method not in use
+    return null;
+  }
+
+  /**Method not in use*/
+  public final ClassifierSplitModel selectModel(Instances train, Instances test) {
+    //method not in use
+    return null;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualSplit.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualSplit.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/lmt/ResidualSplit.java	(revision 29)
@@ -0,0 +1,319 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResidualSplit.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.lmt;
+
+import weka.classifiers.trees.j48.ClassifierSplitModel;
+import weka.classifiers.trees.j48.Distribution;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Helper class for logistic model trees (weka.classifiers.trees.lmt.LMT) to implement the 
+ * splitting criterion based on residuals of the LogitBoost algorithm.
+ * 
+ * @author Niels Landwehr
+ * @version $Revision: 1.4 $
+ */
+public class ResidualSplit
+  extends ClassifierSplitModel{
+
+  /** for serialization */
+  private static final long serialVersionUID = -5055883734183713525L;
+  
+  /**The attribute selected for the split*/
+  protected Attribute m_attribute;
+
+  /**The index of the attribute selected for the split*/
+  protected int m_attIndex;
+
+  /**Number of instances in the set*/
+  protected int m_numInstances;
+
+  /**Number of classed*/
+  protected int m_numClasses;
+
+  /**The set of instances*/
+  protected Instances m_data;
+
+  /**The Z-values (LogitBoost response) for the set of instances*/
+  protected double[][] m_dataZs;
+
+  /**The LogitBoost-weights for the set of instances*/
+  protected double[][] m_dataWs; 
+
+  /**The split point (for numeric attributes)*/
+  protected double m_splitPoint;
+
+  /**
+   *Creates a split object
+   *@param attIndex the index of the attribute to split on 
+   */    
+  public ResidualSplit(int attIndex) {	
+    m_attIndex = attIndex;              
+  }
+
+  /**
+   * Builds the split.
+   * Needs the Z/W values of LogitBoost for the set of instances.
+   */
+  public void buildClassifier(Instances data, double[][] dataZs, double[][] dataWs) 
+    throws Exception {
+
+    m_numClasses = data.numClasses();	
+    m_numInstances = data.numInstances();
+    if (m_numInstances == 0) throw new Exception("Can't build split on 0 instances");
+
+    //save data/Zs/Ws
+    m_data = data;
+    m_dataZs = dataZs;
+    m_dataWs = dataWs;
+    m_attribute = data.attribute(m_attIndex);
+
+    //determine number of subsets and split point for numeric attributes
+    if (m_attribute.isNominal()) {
+      m_splitPoint = 0.0;
+      m_numSubsets = m_attribute.numValues();
+    } else {
+      getSplitPoint();
+      m_numSubsets = 2;
+    }
+    //create distribution for data
+    m_distribution = new Distribution(data, this);	
+  }
+
+
+  /**
+   * Selects split point for numeric attribute.
+   */
+  protected boolean getSplitPoint() throws Exception{
+
+    //compute possible split points
+    double[] splitPoints = new double[m_numInstances];
+    int numSplitPoints = 0;
+
+    Instances sortedData = new Instances(m_data);
+    sortedData.sort(sortedData.attribute(m_attIndex));
+
+    double last, current;
+
+    last = sortedData.instance(0).value(m_attIndex);	
+
+    for (int i = 0; i < m_numInstances - 1; i++) {
+      current = sortedData.instance(i+1).value(m_attIndex);	
+      if (!Utils.eq(current, last)){
+	splitPoints[numSplitPoints++] = (last + current) / 2.0;
+      }
+      last = current;
+    }
+
+    //compute entropy for all split points
+    double[] entropyGain = new double[numSplitPoints];
+
+    for (int i = 0; i < numSplitPoints; i++) {
+      m_splitPoint = splitPoints[i];
+      entropyGain[i] = entropyGain();
+    }
+
+    //get best entropy gain
+    int bestSplit = -1;
+    double bestGain = -Double.MAX_VALUE;
+
+    for (int i = 0; i < numSplitPoints; i++) {
+      if (entropyGain[i] > bestGain) {
+	bestGain = entropyGain[i];
+	bestSplit = i;
+      }
+    }
+
+    if (bestSplit < 0) return false;
+
+    m_splitPoint = splitPoints[bestSplit];	
+    return true;
+  }
+
+  /**
+   * Computes entropy gain for current split.
+   */
+  public double entropyGain() throws Exception{
+
+    int numSubsets;
+    if (m_attribute.isNominal()) {
+      numSubsets = m_attribute.numValues();
+    } else {
+      numSubsets = 2;
+    }
+
+    double[][][] splitDataZs = new double[numSubsets][][];
+    double[][][] splitDataWs = new double[numSubsets][][];
+
+    //determine size of the subsets
+    int[] subsetSize = new int[numSubsets];
+    for (int i = 0; i < m_numInstances; i++) {
+      int subset = whichSubset(m_data.instance(i));
+      if (subset < 0) throw new Exception("ResidualSplit: no support for splits on missing values");
+      subsetSize[subset]++;
+    }
+
+    for (int i = 0; i < numSubsets; i++) {
+      splitDataZs[i] = new double[subsetSize[i]][];
+      splitDataWs[i] = new double[subsetSize[i]][];
+    }
+
+
+    int[] subsetCount = new int[numSubsets];
+
+    //sort Zs/Ws into subsets
+    for (int i = 0; i < m_numInstances; i++) {
+      int subset = whichSubset(m_data.instance(i));
+      splitDataZs[subset][subsetCount[subset]] = m_dataZs[i];
+      splitDataWs[subset][subsetCount[subset]] = m_dataWs[i];
+      subsetCount[subset]++;
+    }
+
+    //calculate entropy gain
+    double entropyOrig = entropy(m_dataZs, m_dataWs);
+
+    double entropySplit = 0.0;
+
+    for (int i = 0; i < numSubsets; i++) {
+      entropySplit += entropy(splitDataZs[i], splitDataWs[i]);
+    }
+
+    return entropyOrig - entropySplit;
+  }
+
+  /**
+   * Helper function to compute entropy from Z/W values.
+   */
+  protected double entropy(double[][] dataZs, double[][] dataWs){
+    //method returns entropy * sumOfWeights
+    double entropy = 0.0;
+    int numInstances = dataZs.length;
+
+    for (int j = 0; j < m_numClasses; j++) {
+
+      //compute mean for class
+      double m = 0.0;
+      double sum = 0.0;
+      for (int i = 0; i < numInstances; i++) {
+	m += dataZs[i][j] * dataWs[i][j];
+	sum += dataWs[i][j];
+      }
+      m /= sum;
+
+      //sum up entropy for class
+      for (int i = 0; i < numInstances; i++) {
+	entropy += dataWs[i][j] * Math.pow(dataZs[i][j] - m,2);
+      }
+
+    }
+
+    return entropy;
+  }
+
+  /**
+   * Checks if there are at least 2 subsets that contain >= minNumInstances.
+   */
+  public boolean checkModel(int minNumInstances){
+    //checks if there are at least 2 subsets that contain >= minNumInstances
+    int count = 0;
+    for (int i = 0; i < m_distribution.numBags(); i++) {
+      if (m_distribution.perBag(i) >= minNumInstances) count++; 
+    }
+    return (count >= 2);
+  }
+
+  /**
+   * Returns name of splitting attribute (left side of condition).
+   */
+  public final String leftSide(Instances data) {
+
+    return data.attribute(m_attIndex).name();
+  }
+
+  /**
+   * Prints the condition satisfied by instances in a subset.
+   */
+  public final String rightSide(int index,Instances data) {
+
+    StringBuffer text;
+
+    text = new StringBuffer();
+    if (data.attribute(m_attIndex).isNominal())
+      text.append(" = "+
+	  data.attribute(m_attIndex).value(index));
+    else
+      if (index == 0)
+	text.append(" <= "+
+	    Utils.doubleToString(m_splitPoint,6));
+      else
+	text.append(" > "+
+	    Utils.doubleToString(m_splitPoint,6));
+    return text.toString();
+  }
+
+  public final int whichSubset(Instance instance) 
+  throws Exception {
+
+    if (instance.isMissing(m_attIndex))
+      return -1;
+    else{
+      if (instance.attribute(m_attIndex).isNominal())
+	return (int)instance.value(m_attIndex);
+      else
+	if (Utils.smOrEq(instance.value(m_attIndex),m_splitPoint))
+	  return 0;
+	else
+	  return 1;
+    }
+  }    
+
+  /** Method not in use*/
+  public void buildClassifier(Instances data) {
+    //method not in use
+  }
+
+  /**Method not in use*/
+  public final double [] weights(Instance instance){
+    //method not in use
+    return null;
+  } 
+
+  /**Method not in use*/
+  public final String sourceExpression(int index, Instances data) {
+    //method not in use
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/CorrelationSplitInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/CorrelationSplitInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/CorrelationSplitInfo.java	(revision 29)
@@ -0,0 +1,253 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CorrelationSplitInfo.java
+ * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.experiment.PairedStats;
+
+import java.io.Serializable;
+
+/**
+ * Finds split points using correlation.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public final class CorrelationSplitInfo
+  implements Cloneable, Serializable, SplitEvaluate, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4212734895125452770L;
+
+  /**
+   * the first instance
+   */
+  private int    m_first;
+
+  /**
+   * the last instance
+   */
+  private int    m_last;
+  private int    m_position;
+
+  /**
+   * the maximum impurity reduction
+   */
+  private double m_maxImpurity;
+
+  /**
+   * the attribute being tested
+   */
+  private int    m_splitAttr;
+
+  /**
+   * the best value on which to split
+   */
+  private double m_splitValue;
+
+  /**
+   * the number of instances
+   */
+  private int    m_number;
+
+  /**
+   * Constructs an object which contains the split information
+   *
+   * @param low the index of the first instance
+   * @param high the index of the last instance
+   * @param attr an attribute
+   */
+  public CorrelationSplitInfo(int low, int high, int attr) {
+    initialize(low, high, attr);
+  }
+
+  /**
+   * Makes a copy of this CorrelationSplitInfo object
+   */
+  public final SplitEvaluate copy() throws Exception {
+    CorrelationSplitInfo s = (CorrelationSplitInfo) this.clone();
+
+    return s;
+  } 
+
+  /**
+   * Resets the object of split information
+   *
+   * @param low the index of the first instance
+   * @param high the index of the last instance
+   * @param attr the attribute
+   */
+  public final void initialize(int low, int high, int attr) {
+    m_number = high - low + 1;
+    m_first = low;
+    m_last = high;
+    m_position = -1;
+    m_maxImpurity = -Double.MAX_VALUE;
+    m_splitAttr = attr;
+    m_splitValue = 0.0;
+  } 
+
+  /**
+   * Finds the best splitting point for an attribute in the instances
+   *
+   * @param attr the splitting attribute
+   * @param inst the instances
+   * @exception Exception if something goes wrong
+   */
+  public final void attrSplit(int attr, Instances inst) throws Exception {
+    int		i;
+    int		len;
+    int		part;
+    int		low = 0;
+    int		high = inst.numInstances() - 1;
+    PairedStats full = new PairedStats(0.01);
+    PairedStats leftSubset = new PairedStats(0.01);
+    PairedStats rightSubset = new PairedStats(0.01);
+    int		classIndex = inst.classIndex();
+    double      leftCorr, rightCorr;
+    double      leftVar, rightVar, allVar;
+    double      order = 2.0;
+
+    initialize(low, high, attr);
+
+    if (m_number < 4) {
+      return;
+    } 
+
+    len = ((high - low + 1) < 5) ? 1 : (high - low + 1) / 5;
+    m_position = low;
+    part = low + len - 1;
+
+    // prime the subsets
+    for (i = low; i < len; i++) {
+      full.add(inst.instance(i).value(attr), 
+	       inst.instance(i).value(classIndex));
+      leftSubset.add(inst.instance(i).value(attr), 
+		     inst.instance(i).value(classIndex));
+    } 
+
+    for (i = len; i < inst.numInstances(); i++) {
+      full.add(inst.instance(i).value(attr), 
+	       inst.instance(i).value(classIndex));
+      rightSubset.add(inst.instance(i).value(attr), 
+		      inst.instance(i).value(classIndex));
+    } 
+
+    full.calculateDerived();
+
+    allVar = (full.yStats.stdDev * full.yStats.stdDev);
+    allVar = Math.abs(allVar);
+    allVar = Math.pow(allVar, (1.0 / order));
+
+    for (i = low + len; i < high - len - 1; i++) {
+      rightSubset.subtract(inst.instance(i).value(attr), 
+			   inst.instance(i).value(classIndex));
+      leftSubset.add(inst.instance(i).value(attr), 
+		     inst.instance(i).value(classIndex));
+
+      if (!Utils.eq(inst.instance(i + 1).value(attr), 
+		    inst.instance(i).value(attr))) {
+	leftSubset.calculateDerived();
+	rightSubset.calculateDerived();
+
+	leftCorr = Math.abs(leftSubset.correlation);
+	rightCorr = Math.abs(rightSubset.correlation);
+	leftVar = (leftSubset.yStats.stdDev * leftSubset.yStats.stdDev);
+	leftVar = Math.abs(leftVar);
+	leftVar = Math.pow(leftVar, (1.0 / order));
+	rightVar = (rightSubset.yStats.stdDev * rightSubset.yStats.stdDev);
+	rightVar = Math.abs(rightVar);
+	rightVar = Math.pow(rightVar, (1.0 / order));
+
+	double score = allVar - ((leftSubset.count / full.count) * leftVar) 
+		       - ((rightSubset.count / full.count) * rightVar);
+
+	// score /= allVar;
+	leftCorr = (leftSubset.count / full.count) * leftCorr;
+	rightCorr = (rightSubset.count / full.count) * rightCorr;
+
+	double c_score = (leftCorr + rightCorr) - Math.abs(full.correlation);
+
+	// c_score += score;
+	if (!Utils.eq(score, 0.0)) {
+	  if (score > m_maxImpurity) {
+	    m_maxImpurity = score;
+	    m_splitValue = 
+	      (inst.instance(i).value(attr) + inst.instance(i + 1)
+	      .value(attr)) * 0.5;
+	    m_position = i;
+	  } 
+	} 
+      } 
+    } 
+  } 
+
+  /**
+   * Returns the impurity of this split
+   *
+   * @return the impurity of this split
+   */
+  public double maxImpurity() {
+    return m_maxImpurity;
+  } 
+
+  /**
+   * Returns the attribute used in this split
+   *
+   * @return the attribute used in this split
+   */
+  public int splitAttr() {
+    return m_splitAttr;
+  } 
+
+  /**
+   * Returns the position of the split in the sorted values. -1 indicates that
+   * a split could not be found.
+   *
+   * @return an <code>int</code> value
+   */
+  public int position() {
+    return m_position;
+  } 
+
+  /**
+   * Returns the split value
+   *
+   * @return the split value
+   */
+  public double splitValue() {
+    return m_splitValue;
+  } 
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Impurity.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Impurity.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Impurity.java	(revision 29)
@@ -0,0 +1,186 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Impurity.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Class for handling the impurity values when spliting the instances
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public final class Impurity
+  implements RevisionHandler {
+  
+  double n;                   // number of total instances 
+  int attr;                   // splitting attribute 
+  double nl;                  // number of instances in the left group 
+  double nr;                  // number of instances in the right group 
+  double sl;                  // sum of the left group  
+  double sr;                  // sum of the right group 
+  double s2l;                 // squared sum of the left group 
+  double s2r;                 // squared sum of the right group 
+  double sdl;                 // standard deviation of the left group 
+  double sdr;                 // standard deviation of the right group 
+  double vl;                  // variance of the left group 
+  double vr;                  // variance of the right group 
+  double sd;                  // overall standard deviation 
+  double va;                  // overall variance 
+
+  double impurity;            // impurity value;
+  int order;                  // order = 1, variance; order = 2, standard deviation; order = 3, the cubic root of the variance;  
+                              // order = k, the k-th order root of the variance
+
+  /**
+   * Constructs an Impurity object containing the impurity values of partitioning the instances using an attribute
+   * @param partition the index of the last instance in the left subset
+   * @param attribute the attribute used in partitioning
+   * @param inst instances
+   * @param k the order of the impurity; =1, the variance; =2, the stardard deviation; =k, the k-th order root of the variance
+   */
+  public Impurity(int partition,int attribute,Instances inst,int k){
+
+    Values values = new Values(0,inst.numInstances()-1,inst.classIndex(),inst);
+    attr = attribute;
+    n   = inst.numInstances();
+    sd  = values.sd; 
+    va  = values.va;
+
+    values = new Values(0,partition,inst.classIndex(),inst);
+    nl  = partition + 1;
+    sl  = values.sum;
+    s2l = values.sqrSum;
+
+    values = new Values(partition+1,inst.numInstances()-1,inst.classIndex(),inst);
+    nr  = inst.numInstances() - partition -1;
+    sr  = values.sum;
+    s2r = values.sqrSum;
+
+    order = k;
+    this.incremental(0,0);
+  }
+
+  /**
+   * Converts an Impurity object to a string
+   * @return the converted string
+   */
+  public final String  toString() {
+    
+    StringBuffer text = new StringBuffer();
+    
+    text.append("Print impurity values:\n");
+    text.append("    Number of total instances:\t" + n + "\n");
+    text.append("    Splitting attribute:\t\t" + attr + "\n");
+    text.append("    Number of the instances in the left:\t" + nl + "\n");
+    text.append("    Number of the instances in the right:\t" + nr + "\n");
+    text.append("    Sum of the left:\t\t\t" + sl + "\n");
+    text.append("    Sum of the right:\t\t\t" + sr + "\n");
+    text.append("    Squared sum of the left:\t\t" + s2l + "\n"); 
+    text.append("    Squared sum of the right:\t\t" + s2r + "\n");
+    text.append("    Standard deviation of the left:\t" + sdl + "\n");
+    text.append("    Standard deviation of the right:\t" + sdr + "\n");
+    text.append("    Variance of the left:\t\t" + vr + "\n");
+    text.append("    Variance of the right:\t\t" + vr + "\n");
+    text.append("    Overall standard deviation:\t\t" + sd + "\n");
+    text.append("    Overall variance:\t\t\t" + va + "\n");
+    text.append("    Impurity (order " + order + "):\t\t" + impurity + "\n");
+
+    return text.toString();
+  }
+  
+  /**
+   * Incrementally computes the impurirty values
+   * @param value the incremental value 
+   * @param type if type=1, value will be added to the left subset; type=-1, to the right subset; type=0, initializes
+   */
+  public final void  incremental(double value,int type){
+    double y=0.,yl=0.,yr=0.;
+
+    switch(type){
+    case 1:
+      nl += 1;
+      nr -= 1;
+      sl += value;
+      sr -= value;
+      s2l += value*value;
+      s2r -= value*value;
+      break;
+    case -1:
+      nl -= 1;
+      nr += 1;
+      sl -= value;
+      sr += value;
+      s2l -= value*value;
+      s2r += value*value;
+      break;
+    case 0:
+      break;
+    default: System.err.println("wrong type in Impurity.incremental().");
+    }
+
+    if(nl<=0.0){
+      vl=0.0;
+      sdl=0.0;
+    }
+    else {
+      vl = (nl*s2l-sl*sl)/((double)nl*((double)nl));
+      vl = Math.abs(vl);
+      sdl = Math.sqrt(vl);
+    }
+    if(nr<=0.0){
+      vr=0.0;
+      sdr=0.0;
+    }
+    else {
+      vr = (nr*s2r-sr*sr)/((double)nr*((double)nr));
+      vr = Math.abs(vr);
+      sdr = Math.sqrt(vr);
+    }
+
+    if(order <= 0)System.err.println("Impurity order less than zero in Impurity.incremental()");
+    else if(order == 1) {
+      y = va; yl = vl; yr = vr;
+    } else {
+      y  = Math.pow(va,1./order);
+      yl = Math.pow(vl,1./order);
+      yr = Math.pow(vr,1./order);
+    }
+
+    if(nl<=0.0 || nr<=0.0)
+      impurity = 0.0;
+    else { 
+      impurity = y - ((double)nl/(double)n)*yl - ((double)nr/(double)n)*yr;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/M5Base.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/M5Base.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/M5Base.java	(revision 29)
@@ -0,0 +1,640 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    M5Base.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.functions.LinearRegression;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.supervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.RemoveUseless;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * M5Base. Implements base routines
+ * for generating M5 Model trees and rules. <p>
+ * 
+ * The original algorithm M5 was invented by Quinlan: <br/>
+ * 
+ * Quinlan J. R. (1992). Learning with continuous classes. Proceedings of
+ * the Australian Joint Conference on Artificial Intelligence. 343--348.
+ * World Scientific, Singapore. <p/>
+ * 
+ * Yong Wang made improvements and created M5': <br/>
+ * 
+ * Wang, Y and Witten, I. H. (1997). Induction of model trees for
+ * predicting continuous classes. Proceedings of the poster papers of the
+ * European Conference on Machine Learning. University of Economics,
+ * Faculty of Informatics and Statistics, Prague. <p/>
+ *
+ * Valid options are:<p>
+ * 
+ * -U <br>
+ * Use unsmoothed predictions. <p>
+ *
+ * -R <br>
+ * Build regression tree/rule rather than model tree/rule
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public abstract class M5Base 
+  extends AbstractClassifier 
+  implements AdditionalMeasureProducer,
+	     TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4022221950191647679L;
+
+  /**
+   * the instances covered by the tree/rules
+   */
+  private Instances m_instances;
+
+  /**
+   * the rule set
+   */
+  protected FastVector m_ruleSet;
+
+  /**
+   * generate a decision list instead of a single tree.
+   */
+  private boolean m_generateRules;
+
+  /**
+   * use unsmoothed predictions
+   */
+  private boolean m_unsmoothedPredictions;
+
+  /**
+   * filter to fill in missing values
+   */
+  private ReplaceMissingValues m_replaceMissing;
+
+  /**
+   * filter to convert nominal attributes to binary
+   */
+  private NominalToBinary m_nominalToBinary;
+  
+  /**
+   * for removing useless attributes 
+   */
+  private RemoveUseless m_removeUseless;
+
+  /**
+   * Save instances at each node in an M5 tree for visualization purposes.
+   */
+  protected boolean m_saveInstances = false;
+
+  /**
+   * Make a regression tree/rule instead of a model tree/rule
+   */
+  protected boolean m_regressionTree;
+
+  /**
+   * Do not prune tree/rules
+   */
+  protected boolean m_useUnpruned = false;
+
+  /**
+   * The minimum number of instances to allow at a leaf node
+   */
+  protected double m_minNumInstances = 4;
+
+  /**
+   * Constructor
+   */
+  public M5Base() {
+    m_generateRules = false;
+    m_unsmoothedPredictions = false;
+    m_useUnpruned = false;
+    m_minNumInstances = 4;
+  }
+
+  /**
+   * returns information about the classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "M5Base. Implements base routines for generating M5 Model trees and " 
+      + "rules\n"
+      + "The original algorithm M5 was invented by R. Quinlan and Yong Wang "
+      + "made improvements.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ross J. Quinlan");
+    result.setValue(Field.TITLE, "Learning with Continuous Classes");
+    result.setValue(Field.BOOKTITLE, "5th Australian Joint Conference on Artificial Intelligence");
+    result.setValue(Field.YEAR, "1992");
+    result.setValue(Field.PAGES, "343-348");
+    result.setValue(Field.PUBLISHER, "World Scientific");
+    result.setValue(Field.ADDRESS, "Singapore");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Y. Wang and I. H. Witten");
+    additional.setValue(Field.TITLE, "Induction of model trees for predicting continuous classes");
+    additional.setValue(Field.BOOKTITLE, "Poster papers of the 9th European Conference on Machine Learning");
+    additional.setValue(Field.YEAR, "1997");
+    additional.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   * 
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option("\tUse unpruned tree/rules", 
+				    "N", 0, "-N"));
+
+    newVector.addElement(new Option("\tUse unsmoothed predictions", 
+				    "U", 0, "-U"));
+
+    newVector.addElement(new Option("\tBuild regression tree/rule rather "
+				    +"than a model tree/rule", 
+				    "R", 0, "-R"));
+
+    newVector.addElement(new Option("\tSet minimum number of instances "
+				    +"per leaf\n\t(default 4)",
+				    "M",1,"-M <minimum number of instances>"));
+    return newVector.elements();
+  } 
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   * Valid options are:<p>
+   * 
+   * -U <br>
+   * Use unsmoothed predictions. <p>
+   *
+   * -R <br>
+   * Build a regression tree rather than a model tree. <p>
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setUnpruned(Utils.getFlag('N', options));
+    setUseUnsmoothed(Utils.getFlag('U', options));
+    setBuildRegressionTree(Utils.getFlag('R', options));
+    String optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMinNumInstances((new Double(optionString)).doubleValue());
+    }
+    Utils.checkForRemainingOptions(options);
+  } 
+
+  /**
+   * Gets the current settings of the classifier.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    String[] options = new String[5];
+    int current = 0;
+
+    if (getUnpruned()) {
+      options[current++] = "-N";
+    }
+
+    if (getUseUnsmoothed()) {
+      options[current++] = "-U";
+    } 
+
+    if (getBuildRegressionTree()) {
+      options[current++] = "-R";
+    }
+
+    options[current++] = "-M"; 
+    options[current++] = ""+getMinNumInstances();
+
+    while (current < options.length) {
+      options[current++] = "";
+    } 
+    return options;
+  } 
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String unprunedTipText() {
+    return "Whether unpruned tree/rules are to be generated.";
+  }
+
+  /**
+   * Use unpruned tree/rules
+   *
+   * @param unpruned true if unpruned tree/rules are to be generated
+   */
+  public void setUnpruned(boolean unpruned) {
+    m_useUnpruned = unpruned;
+  }
+
+  /**
+   * Get whether unpruned tree/rules are being generated
+   *
+   * @return true if unpruned tree/rules are to be generated
+   */
+  public boolean getUnpruned() {
+    return m_useUnpruned;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String generateRulesTipText() {
+    return "Whether to generate rules (decision list) rather than a tree.";
+  }
+
+  /**
+   * Generate rules (decision list) rather than a tree
+   * 
+   * @param u true if rules are to be generated
+   */
+  protected void setGenerateRules(boolean u) {
+    m_generateRules = u;
+  } 
+
+  /**
+   * get whether rules are being generated rather than a tree
+   * 
+   * @return true if rules are to be generated
+   */
+  protected boolean getGenerateRules() {
+    return m_generateRules;
+  } 
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useUnsmoothedTipText() {
+    return "Whether to use unsmoothed predictions.";
+  }
+
+  /**
+   * Use unsmoothed predictions
+   * 
+   * @param s true if unsmoothed predictions are to be used
+   */
+  public void setUseUnsmoothed(boolean s) {
+    m_unsmoothedPredictions = s;
+  } 
+
+  /**
+   * Get whether or not smoothing is being used
+   * 
+   * @return true if unsmoothed predictions are to be used
+   */
+  public boolean getUseUnsmoothed() {
+    return m_unsmoothedPredictions;
+  } 
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String buildRegressionTreeTipText() {
+    return "Whether to generate a regression tree/rule instead of a model tree/rule.";
+  }
+
+  /**
+   * Get the value of regressionTree.
+   *
+   * @return Value of regressionTree.
+   */
+  public boolean getBuildRegressionTree() {
+    
+    return m_regressionTree;
+  }
+  
+  /**
+   * Set the value of regressionTree.
+   *
+   * @param newregressionTree Value to assign to regressionTree.
+   */
+  public void setBuildRegressionTree(boolean newregressionTree) {
+    
+    m_regressionTree = newregressionTree;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String minNumInstancesTipText() {
+    return "The minimum number of instances to allow at a leaf node.";
+  }
+
+  /**
+   * Set the minimum number of instances to allow at a leaf node
+   *
+   * @param minNum the minimum number of instances
+   */
+  public void setMinNumInstances(double minNum) {
+    m_minNumInstances = minNum;
+  }
+
+  /**
+   * Get the minimum number of instances to allow at a leaf node
+   *
+   * @return a <code>double</code> value
+   */
+  public double getMinNumInstances() {
+    return m_minNumInstances;
+  }
+
+  /**
+   * Returns default capabilities of the classifier, i.e., of LinearRegression.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    return new LinearRegression().getCapabilities();
+  }
+
+  /**
+   * Generates the classifier.
+   * 
+   * @param data set of instances serving as training data
+   * @throws Exception if the classifier has not been generated
+   * successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    // can classifier handle the data?
+    getCapabilities().testWithFail(data);
+
+    // remove instances with missing class
+    data = new Instances(data);
+    data.deleteWithMissingClass();
+    
+    m_instances = new Instances(data);
+
+    m_replaceMissing = new ReplaceMissingValues();
+    m_replaceMissing.setInputFormat(m_instances);
+    m_instances = Filter.useFilter(m_instances, m_replaceMissing);
+
+    m_nominalToBinary = new NominalToBinary();
+    m_nominalToBinary.setInputFormat(m_instances);
+    m_instances = Filter.useFilter(m_instances, m_nominalToBinary);
+
+    m_removeUseless = new RemoveUseless();
+    m_removeUseless.setInputFormat(m_instances);
+    m_instances = Filter.useFilter(m_instances, m_removeUseless);
+    
+    m_instances.randomize(new Random(1));
+
+    m_ruleSet = new FastVector();
+
+    Rule tempRule;
+
+    if (m_generateRules) {
+      Instances tempInst = m_instances;
+     
+      do {
+	tempRule = new Rule();
+	tempRule.setSmoothing(!m_unsmoothedPredictions);
+	tempRule.setRegressionTree(m_regressionTree);
+	tempRule.setUnpruned(m_useUnpruned);
+	tempRule.setSaveInstances(false);
+	tempRule.setMinNumInstances(m_minNumInstances);
+	tempRule.buildClassifier(tempInst);
+	m_ruleSet.addElement(tempRule);
+	//	System.err.println("Built rule : "+tempRule.toString());
+	tempInst = tempRule.notCoveredInstances();
+      } while (tempInst.numInstances() > 0);
+    } else {
+      // just build a single tree
+      tempRule = new Rule();
+
+      tempRule.setUseTree(true);
+      //      tempRule.setGrowFullTree(true);
+      tempRule.setSmoothing(!m_unsmoothedPredictions);
+      tempRule.setSaveInstances(m_saveInstances);
+      tempRule.setRegressionTree(m_regressionTree);
+      tempRule.setUnpruned(m_useUnpruned);
+      tempRule.setMinNumInstances(m_minNumInstances);
+
+      Instances temp_train;
+
+      temp_train = m_instances;
+
+      tempRule.buildClassifier(temp_train);
+
+      m_ruleSet.addElement(tempRule);      
+
+      // save space
+      m_instances = new Instances(m_instances, 0);
+      //      System.err.print(tempRule.m_topOfTree.treeToString(0));
+    } 
+  } 
+
+  /**
+   * Calculates a prediction for an instance using a set of rules
+   * or an M5 model tree
+   * 
+   * @param inst the instance whos class value is to be predicted
+   * @return the prediction
+   * @throws Exception if a prediction can't be made.
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+    Rule   temp;
+    double prediction = 0;
+    boolean success = false;
+
+    m_replaceMissing.input(inst);
+    inst = m_replaceMissing.output();
+    m_nominalToBinary.input(inst);
+    inst = m_nominalToBinary.output();
+    m_removeUseless.input(inst);
+    inst = m_removeUseless.output();
+
+    if (m_ruleSet == null) {
+      throw new Exception("Classifier has not been built yet!");
+    } 
+
+    if (!m_generateRules) {
+      temp = (Rule) m_ruleSet.elementAt(0);
+      return temp.classifyInstance(inst);
+    } 
+
+    boolean cont;
+    int     i;
+
+    for (i = 0; i < m_ruleSet.size(); i++) {
+      cont = false;
+      temp = (Rule) m_ruleSet.elementAt(i);
+
+      try {
+	prediction = temp.classifyInstance(inst);
+	success = true;
+      } catch (Exception e) {
+	cont = true;
+      } 
+
+      if (!cont) {
+	break;
+      } 
+    } 
+
+    if (!success) {
+      System.out.println("Error in predicting (DecList)");
+    } 
+    return prediction;
+  } 
+
+  /**
+   * Returns a description of the classifier
+   * 
+   * @return a description of the classifier as a String
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    Rule	 temp;
+
+    if (m_ruleSet == null) {
+      return "Classifier hasn't been built yet!";
+    } 
+
+    if (m_generateRules) {
+      text.append("M5 "
+		  + ((m_useUnpruned == true)
+		     ? "unpruned "
+		     : "pruned ")
+		  + ((m_regressionTree == true) 
+		     ?  "regression "
+		     : "model ")
+		  + "rules ");
+
+      if (!m_unsmoothedPredictions) {
+	text.append("\n(using smoothed linear models) ");
+      }
+
+      text.append(":\n");
+
+      text.append("Number of Rules : " + m_ruleSet.size() + "\n\n");
+
+      for (int j = 0; j < m_ruleSet.size(); j++) {
+	temp = (Rule) m_ruleSet.elementAt(j);
+
+	text.append("Rule: " + (j + 1) + "\n");
+	text.append(temp.toString());
+      } 
+    } else {
+      temp = (Rule) m_ruleSet.elementAt(0);
+      text.append(temp.toString());
+    } 
+    return text.toString();
+  } 
+
+  /**
+   * Returns an enumeration of the additional measure names
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector(1);
+    newVector.addElement("measureNumRules");
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws Exception if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) 
+    {
+    if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) {
+      return measureNumRules();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+					 + " not supported (M5)");
+    }
+  }
+
+  /**
+   * return the number of rules
+   * @return the number of rules (same as # linear models &
+   * # leaves in the tree)
+   */
+  public double measureNumRules() {
+    if (m_generateRules) {
+      return m_ruleSet.size();
+    }
+    return ((Rule)m_ruleSet.elementAt(0)).m_topOfTree.numberOfLinearModels();
+  }
+
+  public RuleNode getM5RootNode() {
+    Rule temp = (Rule) m_ruleSet.elementAt(0);
+    return temp.getM5RootNode();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/PreConstructedLinearModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/PreConstructedLinearModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/PreConstructedLinearModel.java	(revision 29)
@@ -0,0 +1,179 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RuleNode.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * This class encapsulates a linear regression function. It is a classifier
+ * but does not learn the function itself, instead it is constructed with
+ * coefficients and intercept obtained elsewhere. The buildClassifier method
+ * must still be called however as this stores a copy of the training data's
+ * header for use in printing the model to the console.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class PreConstructedLinearModel 
+  extends AbstractClassifier 
+  implements Serializable {
+  
+  /** for serialization */
+  static final long serialVersionUID = 2030974097051713247L;
+  
+  /** The coefficients */
+  private double [] m_coefficients;
+
+  /** The intercept */
+  private double m_intercept;
+
+  /** Holds the instances header for printing the model */
+  private Instances m_instancesHeader;
+
+  /** number of coefficients in the model */
+  private int m_numParameters;
+
+  /**
+   * Constructor
+   *
+   * @param coeffs an array of coefficients
+   * @param intercept the intercept
+   */
+  public PreConstructedLinearModel(double [] coeffs, double intercept) {
+    m_coefficients = coeffs;
+    m_intercept = intercept;
+    int count = 0;
+    for (int i = 0; i < coeffs.length; i++) {
+      if (coeffs[i] != 0) {
+	count++;
+      }
+    }
+    m_numParameters = count;
+  }
+
+  /**
+   * Builds the classifier. In this case all that is done is that a
+   * copy of the training instances header is saved.
+   *
+   * @param instances an <code>Instances</code> value
+   * @exception Exception if an error occurs
+   */
+  public void buildClassifier(Instances instances) throws Exception {
+    m_instancesHeader = new Instances(instances, 0);
+  }
+
+  /**
+   * Predicts the class of the supplied instance using the linear model.
+   *
+   * @param inst the instance to make a prediction for
+   * @return the prediction
+   * @exception Exception if an error occurs
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+    double result = 0;
+
+    //    System.out.println(inst);
+    for (int i = 0; i < m_coefficients.length; i++) {
+      if (i != inst.classIndex() && !inst.isMissing(i)) {
+	//	System.out.println(inst.value(i)+" "+m_coefficients[i]);
+	result += m_coefficients[i] * inst.value(i);
+      }
+    }
+    
+    result += m_intercept;
+    return result;
+  }
+  
+  /**
+   * Return the number of parameters (coefficients) in the linear model
+   *
+   * @return the number of parameters
+   */
+  public int numParameters() {
+    return m_numParameters;
+  }
+  
+  /**
+   * Return the array of coefficients
+   *
+   * @return the coefficients
+   */
+  public double [] coefficients() {
+    return m_coefficients;
+  }
+
+  /**
+   * Return the intercept
+   *
+   * @return the intercept
+   */
+  public double intercept() {
+    return m_intercept;
+  }
+
+  /**
+   * Returns a textual description of this linear model
+   *
+   * @return String containing a description of this linear model
+   */
+  public String toString() {
+    StringBuffer b = new StringBuffer();
+    b.append("\n"+m_instancesHeader.classAttribute().name() + " = ");
+    boolean first = true;
+    for (int i = 0; i < m_coefficients.length; i++) {
+      if (m_coefficients[i] != 0.0) {
+	double c = m_coefficients[i];
+	if (first) {
+	  b.append("\n\t" + Utils.doubleToString(c, 12, 4).trim() + " * " 
+		   + m_instancesHeader.attribute(i).name() + " ");
+	  first = false;
+	} else {
+	  b.append("\n\t" + ((m_coefficients[i] < 0) ? 
+			   "- " + Utils.doubleToString(Math.abs(c), 12, 4).trim() : "+ "
+		   + Utils.doubleToString(Math.abs(c), 12, 4).trim()) + " * "
+		   + m_instancesHeader.attribute(i).name() + " ");
+	}
+      }
+    }
+    
+    b.append("\n\t" + ((m_intercept < 0) ? "- " : "+ ")
+	     + Utils.doubleToString(Math.abs(m_intercept), 12, 4).trim());
+    return b.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Rule.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Rule.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Rule.java	(revision 29)
@@ -0,0 +1,639 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Rule.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * Generates a single m5 tree or rule
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.15 $
+ */
+public class Rule
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4458627451682483204L;
+
+  protected static int LEFT = 0;
+  protected static int RIGHT = 1;
+
+  /**
+   * the instances covered by this rule
+   */
+  private Instances m_instances;
+
+  /**
+   * the class index
+   */
+  private int m_classIndex;
+
+  /**
+   * the number of attributes
+   */
+  private int m_numAttributes;
+
+  /**
+   * the number of instances in the dataset
+   */
+  private int m_numInstances;
+
+  /**
+   * the indexes of the attributes used to split on for this rule
+   */
+  private int[] m_splitAtts;
+
+  /**
+   * the corresponding values of the split points
+   */
+  private double[] m_splitVals;
+
+  /**
+   * the corresponding internal nodes. Used for smoothing rules.
+   */
+  private RuleNode[] m_internalNodes;
+
+  /**
+   * the corresponding relational operators (0 = "<=", 1 = ">")
+   */
+  private int[] m_relOps;
+
+  /**
+   * the leaf encapsulating the linear model for this rule
+   */
+  private RuleNode m_ruleModel;
+
+  /**
+   * the top of the m5 tree for this rule
+   */
+  protected RuleNode m_topOfTree;
+
+  /**
+   * the standard deviation of the class for all the instances
+   */
+  private double m_globalStdDev;
+
+  /**
+   * the absolute deviation of the class for all the instances
+   */
+  private double m_globalAbsDev;
+
+  /**
+   * the instances covered by this rule
+   */
+  private Instances m_covered;
+
+  /**
+   * the number of instances covered by this rule
+   */
+  private int m_numCovered;
+
+  /**
+   * the instances not covered by this rule
+   */
+  private Instances m_notCovered;
+
+  /**
+   * use a pruned m5 tree rather than make a rule
+   */
+  private boolean m_useTree;
+
+  /**
+   * use the original m5 smoothing procedure
+   */
+  private boolean m_smoothPredictions;
+
+  /**
+   * Save instances at each node in an M5 tree for visualization purposes.
+   */
+  private boolean m_saveInstances;
+
+  /**
+   * Make a regression tree instead of a model tree
+   */
+  private boolean m_regressionTree;
+
+  /**
+   * Build unpruned tree/rule
+   */
+  private boolean m_useUnpruned;
+
+  /**
+   * The minimum number of instances to allow at a leaf node
+   */
+  private double m_minNumInstances;
+
+  /**
+   * Constructor declaration
+   *
+   */
+  public Rule() {
+    m_useTree = false;
+    m_smoothPredictions = false;
+    m_useUnpruned = false;
+    m_minNumInstances = 4;
+  }
+
+  /**
+   * Generates a single rule or m5 model tree.
+   * 
+   * @param data set of instances serving as training data
+   * @exception Exception if the rule has not been generated
+   * successfully
+   */
+  public void buildClassifier(Instances data) throws Exception {
+    m_instances = null;
+    m_topOfTree = null;
+    m_covered = null;
+    m_notCovered = null;
+    m_ruleModel = null;
+    m_splitAtts = null;
+    m_splitVals = null;
+    m_relOps = null;
+    m_internalNodes = null;
+    m_instances = data;
+    m_classIndex = m_instances.classIndex();
+    m_numAttributes = m_instances.numAttributes();
+    m_numInstances = m_instances.numInstances();
+
+    // first calculate global deviation of class attribute
+    m_globalStdDev = Rule.stdDev(m_classIndex, m_instances);
+    m_globalAbsDev = Rule.absDev(m_classIndex, m_instances);
+
+    m_topOfTree = new RuleNode(m_globalStdDev, m_globalAbsDev, null);
+    m_topOfTree.setSaveInstances(m_saveInstances);
+    m_topOfTree.setRegressionTree(m_regressionTree);
+    m_topOfTree.setMinNumInstances(m_minNumInstances);
+    m_topOfTree.buildClassifier(m_instances);
+
+
+    if (!m_useUnpruned) {
+      m_topOfTree.prune();
+    } else {
+      m_topOfTree.installLinearModels();
+    }
+
+    if (m_smoothPredictions) {
+      m_topOfTree.installSmoothedModels();
+    }
+    //m_topOfTree.printAllModels();
+    m_topOfTree.numLeaves(0);
+
+    if (!m_useTree) {     
+      makeRule();
+      // save space
+      //      m_topOfTree = null;
+    }
+
+    // save space
+    m_instances = new Instances(m_instances, 0);
+    
+  } 
+
+  /**
+   * Calculates a prediction for an instance using this rule
+   * or M5 model tree
+   * 
+   * @param instance the instance whos class value is to be predicted
+   * @return the prediction
+   * @exception Exception if a prediction can't be made.
+   */
+  public double classifyInstance(Instance instance) throws Exception {
+    if (m_useTree) {
+      return m_topOfTree.classifyInstance(instance);
+    } 
+
+    // does the instance pass the rule's conditions?
+    if (m_splitAtts.length > 0) {
+      for (int i = 0; i < m_relOps.length; i++) {
+	if (m_relOps[i] == LEFT)    // left
+	 {
+	  if (instance.value(m_splitAtts[i]) > m_splitVals[i]) {
+	    throw new Exception("Rule does not classify instance");
+	  } 
+	} else {
+	  if (instance.value(m_splitAtts[i]) <= m_splitVals[i]) {
+	    throw new Exception("Rule does not classify instance");
+	  } 
+	} 
+      } 
+    } 
+
+    // the linear model's prediction for this rule
+    return m_ruleModel.classifyInstance(instance);
+  } 
+
+  /**
+   * Returns the top of the tree.
+   */
+  public RuleNode topOfTree() {
+
+    return m_topOfTree;
+  }
+
+  /**
+   * Make the single best rule from a pruned m5 model tree
+   * 
+   * @exception Exception if something goes wrong.
+   */
+  private void makeRule() throws Exception {
+    RuleNode[] best_leaf = new RuleNode[1];
+    double[]   best_cov = new double[1];
+    RuleNode   temp;
+
+    m_notCovered = new Instances(m_instances, 0);
+    m_covered = new Instances(m_instances, 0);
+    best_cov[0] = -1;
+    best_leaf[0] = null;
+
+    m_topOfTree.findBestLeaf(best_cov, best_leaf);
+
+    temp = best_leaf[0];
+
+    if (temp == null) {
+      throw new Exception("Unable to generate rule!");
+    } 
+
+    // save the linear model for this rule
+    m_ruleModel = temp;
+
+    int count = 0;
+
+    while (temp.parentNode() != null) {
+      count++;
+      temp = temp.parentNode();
+    } 
+
+    temp = best_leaf[0];
+    m_relOps = new int[count];
+    m_splitAtts = new int[count];
+    m_splitVals = new double[count];
+    if (m_smoothPredictions) {
+      m_internalNodes = new RuleNode[count];
+    }
+
+    // trace back to the root
+    int i = 0;
+
+    while (temp.parentNode() != null) {
+      m_splitAtts[i] = temp.parentNode().splitAtt();
+      m_splitVals[i] = temp.parentNode().splitVal();
+
+      if (temp.parentNode().leftNode() == temp) {
+	m_relOps[i] = LEFT;
+	//	temp.parentNode().m_right = null;
+      } else {
+	m_relOps[i] = RIGHT;
+	//	temp.parentNode().m_left = null;
+      }
+
+      if (m_smoothPredictions) {
+	m_internalNodes[i] = temp.parentNode();
+      }
+
+      temp = temp.parentNode();
+      i++;
+    } 
+
+    // now assemble the covered and uncovered instances
+    boolean ok;
+
+    for (i = 0; i < m_numInstances; i++) {
+      ok = true;
+
+      for (int j = 0; j < m_relOps.length; j++) {
+	if (m_relOps[j] == LEFT)
+	 {
+	  if (m_instances.instance(i).value(m_splitAtts[j]) 
+		  > m_splitVals[j]) {
+	    m_notCovered.add(m_instances.instance(i));
+	    ok = false;
+	    break;
+	  } 
+	} else {
+	  if (m_instances.instance(i).value(m_splitAtts[j]) 
+		  <= m_splitVals[j]) {
+	    m_notCovered.add(m_instances.instance(i));
+	    ok = false;
+	    break;
+	  } 
+	} 
+      } 
+
+      if (ok) {
+	m_numCovered++;
+	//	m_covered.add(m_instances.instance(i));
+      } 
+    } 
+  } 
+
+  /**
+   * Return a description of the m5 tree or rule
+   * 
+   * @return a description of the m5 tree or rule as a String
+   */
+  public String toString() {
+    if (m_useTree) {
+      return treeToString();
+    } else {
+      return ruleToString();
+    } 
+  } 
+
+  /**
+   * Return a description of the m5 tree
+   * 
+   * @return a description of the m5 tree as a String
+   */
+  private String treeToString() {
+    StringBuffer text = new StringBuffer();
+
+    if (m_topOfTree == null) {
+      return "Tree/Rule has not been built yet!";
+    } 
+
+    text.append("M5 "
+		+ ((m_useUnpruned)
+		   ? "unpruned "
+		   : "pruned ")
+		+ ((m_regressionTree) 
+		   ? "regression "
+		   : "model ")
+		+"tree:\n");
+
+    if (m_smoothPredictions == true) {
+      text.append("(using smoothed linear models)\n");
+    } 
+
+    text.append(m_topOfTree.treeToString(0));
+    text.append(m_topOfTree.printLeafModels());
+    text.append("\nNumber of Rules : " + m_topOfTree.numberOfLinearModels());
+
+    return text.toString();
+  } 
+
+  /**
+   * Return a description of the rule
+   * 
+   * @return a description of the rule as a String
+   */
+  private String ruleToString() {
+    StringBuffer text = new StringBuffer();
+
+    if (m_splitAtts.length > 0) {
+      text.append("IF\n");
+
+      for (int i = m_splitAtts.length - 1; i >= 0; i--) {
+	text.append("\t" + m_covered.attribute(m_splitAtts[i]).name() + " ");
+
+	if (m_relOps[i] == 0) {
+	  text.append("<= ");
+	} else {
+	  text.append("> ");
+	} 
+
+	text.append(Utils.doubleToString(m_splitVals[i], 1, 3) + "\n");
+      } 
+
+      text.append("THEN\n");
+    } 
+
+    if (m_ruleModel != null) {
+      try {
+	text.append(m_ruleModel.printNodeLinearModel());
+	text.append(" [" + m_numCovered/*m_covered.numInstances()*/);
+
+	if (m_globalAbsDev > 0.0) {
+	  text.append("/"+Utils.doubleToString((100 * 
+						   m_ruleModel.
+						   rootMeanSquaredError() / 
+						   m_globalStdDev), 1, 3) 
+		      + "%]\n\n");
+	} else {
+	  text.append("]\n\n");
+	} 
+      } catch (Exception e) {
+	return "Can't print rule";
+      } 
+    } 
+    
+    //    System.out.println(m_instances);
+    return text.toString();
+  } 
+
+  /**
+   * Use unpruned tree/rules
+   *
+   * @param unpruned true if unpruned tree/rules are to be generated
+   */
+  public void setUnpruned(boolean unpruned) {
+    m_useUnpruned = unpruned;
+  }
+
+  /**
+   * Get whether unpruned tree/rules are being generated
+   *
+   * @return true if unpruned tree/rules are to be generated
+   */
+  public boolean getUnpruned() {
+    return m_useUnpruned;
+  }
+
+  /**
+   * Use an m5 tree rather than generate rules
+   * 
+   * @param u true if m5 tree is to be used
+   */
+  public void setUseTree(boolean u) {
+    m_useTree = u;
+  } 
+
+  /**
+   * get whether an m5 tree is being used rather than rules
+   * 
+   * @return true if an m5 tree is being used.
+   */
+  public boolean getUseTree() {
+    return m_useTree;
+  } 
+
+  /**
+   * Smooth predictions
+   * 
+   * @param s true if smoothing is to be used
+   */
+  public void setSmoothing(boolean s) {
+    m_smoothPredictions = s;
+  } 
+
+  /**
+   * Get whether or not smoothing has been turned on
+   * 
+   * @return true if smoothing is being used
+   */
+  public boolean getSmoothing() {
+    return m_smoothPredictions;
+  } 
+
+  /**
+   * Get the instances not covered by this rule
+   * 
+   * @return the instances not covered
+   */
+  public Instances notCoveredInstances() {
+    return m_notCovered;
+  } 
+
+//    /**
+//     * Get the instances covered by this rule
+//     * 
+//     * @return the instances covered by this rule
+//     */
+//    public Instances coveredInstances() {
+//      return m_covered;
+//    } 
+
+  /**
+   * Returns the standard deviation value of the supplied attribute index.
+   *
+   * @param attr an attribute index
+   * @param inst the instances
+   * @return the standard deviation value
+   */
+  protected static final double stdDev(int attr, Instances inst) {
+    int i,count=0;
+    double sd,va,sum=0.0,sqrSum=0.0,value;
+    
+    for(i = 0; i <= inst.numInstances() - 1; i++) {
+      count++;
+      value = inst.instance(i).value(attr);
+      sum +=  value;
+      sqrSum += value * value;
+    }
+    
+    if(count > 1) {
+      va = (sqrSum - sum * sum / count) / count;
+      va = Math.abs(va);
+      sd = Math.sqrt(va);
+    } else {
+      sd = 0.0;
+    }
+
+    return sd;
+  }
+
+  /**
+   * Returns the absolute deviation value of the supplied attribute index.
+   *
+   * @param attr an attribute index
+   * @param inst the instances
+   * @return the absolute deviation value
+   */
+  protected static final double absDev(int attr, Instances inst) {
+    int i;
+    double average=0.0,absdiff=0.0,absDev;
+    
+    for(i = 0; i <= inst.numInstances()-1; i++) {
+      average  += inst.instance(i).value(attr);
+    }
+    if(inst.numInstances() > 1) {
+      average /= (double)inst.numInstances();
+      for(i=0; i <= inst.numInstances()-1; i++) {
+	absdiff += Math.abs(inst.instance(i).value(attr) - average);
+      }
+      absDev = absdiff / (double)inst.numInstances();
+    } else {
+      absDev = 0.0;
+    }
+   
+    return absDev;
+  }
+
+  /**
+   * Sets whether instances at each node in an M5 tree should be saved
+   * for visualization purposes. Default is to save memory.
+   *
+   * @param save a <code>boolean</code> value
+   */
+  protected void setSaveInstances(boolean save) {
+    m_saveInstances = save;
+  }
+
+  /**
+   * Get the value of regressionTree.
+   *
+   * @return Value of regressionTree.
+   */
+  public boolean getRegressionTree() {
+    
+    return m_regressionTree;
+  }
+  
+  /**
+   * Set the value of regressionTree.
+   *
+   * @param newregressionTree Value to assign to regressionTree.
+   */
+  public void setRegressionTree(boolean newregressionTree) {
+    
+    m_regressionTree = newregressionTree;
+  }
+
+  /**
+   * Set the minumum number of instances to allow at a leaf node
+   *
+   * @param minNum the minimum number of instances
+   */
+  public void setMinNumInstances(double minNum) {
+    m_minNumInstances = minNum;
+  }
+
+  /**
+   * Get the minimum number of instances to allow at a leaf node
+   *
+   * @return a <code>double</code> value
+   */
+  public double getMinNumInstances() {
+    return m_minNumInstances;
+  }
+
+  public RuleNode getM5RootNode() {
+    return m_topOfTree;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.15 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/RuleNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/RuleNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/RuleNode.java	(revision 29)
@@ -0,0 +1,1109 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RuleNode.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.functions.LinearRegression;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+/**
+ * Constructs a node for use in an m5 tree or rule
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RuleNode 
+  extends AbstractClassifier {
+
+  /** for serialization */
+  static final long serialVersionUID = 1979807611124337144L;
+  
+  /**
+   * instances reaching this node
+   */
+  private Instances	   m_instances;
+
+  /**
+   * the class index
+   */
+  private int		   m_classIndex;
+
+  /**
+   * the number of instances reaching this node
+   */
+  protected int		   m_numInstances;
+
+  /**
+   * the number of attributes
+   */
+  private int		   m_numAttributes;
+
+  /**
+   * Node is a leaf
+   */
+  private boolean	   m_isLeaf;
+
+  /**
+   * attribute this node splits on
+   */
+  private int		   m_splitAtt;
+
+  /**
+   * the value of the split attribute
+   */
+  private double	   m_splitValue;
+
+  /**
+   * the linear model at this node
+   */
+  private PreConstructedLinearModel m_nodeModel;
+
+  /**
+   * the number of paramters in the chosen model for this node---either
+   * the subtree model or the linear model.
+   * The constant term is counted as a paramter---this is for pruning
+   * purposes
+   */
+  public int		   m_numParameters;
+
+  /**
+   * the mean squared error of the model at this node (either linear or
+   * subtree)
+   */
+  private double	   m_rootMeanSquaredError;
+
+  /**
+   * left child node
+   */
+  protected RuleNode	   m_left;
+
+  /**
+   * right child node
+   */
+  protected RuleNode	   m_right;
+
+  /**
+   * the parent of this node
+   */
+  private RuleNode	   m_parent;
+
+  /**
+   * a node will not be split if it contains less then m_splitNum instances
+   */
+  private double	   m_splitNum = 4;
+
+  /**
+   * a node will not be split if its class standard deviation is less
+   * than 5% of the class standard deviation of all the instances
+   */
+  private double	   m_devFraction = 0.05;
+  private double	   m_pruningMultiplier = 2;
+
+  /**
+   * the number assigned to the linear model if this node is a leaf.
+   * = 0 if this node is not a leaf
+   */
+  private int		   m_leafModelNum;
+
+  /**
+   * a node will not be split if the class deviation of its
+   * instances is less than m_devFraction of the deviation of the
+   * global class
+   */
+  private double	   m_globalDeviation;
+
+  /**
+   * the absolute deviation of the global class
+   */
+  private double	   m_globalAbsDeviation;
+
+  /**
+   * Indices of the attributes to be used in generating a linear model
+   * at this node
+   */
+  private int [] m_indices;
+    
+  /**
+   * Constant used in original m5 smoothing calculation
+   */
+  private static final double	   SMOOTHING_CONSTANT = 15.0;
+
+  /**
+   * Node id.
+   */
+  private int m_id;
+
+  /**
+   * Save the instances at each node (for visualizing in the
+   * Explorer's treevisualizer.
+   */
+  private boolean m_saveInstances = false;
+
+  /**
+   * Make a regression tree instead of a model tree
+   */
+  private boolean m_regressionTree;
+
+  /**
+   * Creates a new <code>RuleNode</code> instance.
+   *
+   * @param globalDev the global standard deviation of the class
+   * @param globalAbsDev the global absolute deviation of the class
+   * @param parent the parent of this node
+   */
+  public RuleNode(double globalDev, double globalAbsDev, RuleNode parent) {
+    m_nodeModel = null;
+    m_right = null;
+    m_left = null;
+    m_parent = parent;
+    m_globalDeviation = globalDev;
+    m_globalAbsDeviation = globalAbsDev;
+  }
+
+    
+  /**
+   * Build this node (find an attribute and split point)
+   *
+   * @param data the instances on which to build this node
+   * @throws Exception if an error occurs
+   */
+  public void buildClassifier(Instances data) throws Exception {
+
+    m_rootMeanSquaredError = Double.MAX_VALUE;
+    //    m_instances = new Instances(data);
+    m_instances = data;
+    m_classIndex = m_instances.classIndex();
+    m_numInstances = m_instances.numInstances();
+    m_numAttributes = m_instances.numAttributes();
+    m_nodeModel = null;
+    m_right = null;
+    m_left = null;
+
+    if ((m_numInstances < m_splitNum) 
+	|| (Rule.stdDev(m_classIndex, m_instances) 
+	    < (m_globalDeviation * m_devFraction))) {
+      m_isLeaf = true;
+    } else {
+      m_isLeaf = false;
+    } 
+
+    split();
+  } 
+ 
+  /**
+   * Classify an instance using this node. Recursively calls classifyInstance
+   * on child nodes.
+   *
+   * @param inst the instance to classify
+   * @return the prediction for this instance
+   * @throws Exception if an error occurs
+   */
+  public double classifyInstance(Instance inst) throws Exception {
+    if (m_isLeaf) {
+      if (m_nodeModel == null) {
+	throw new Exception("Classifier has not been built correctly.");
+      } 
+
+      return m_nodeModel.classifyInstance(inst);
+    }
+
+    if (inst.value(m_splitAtt) <= m_splitValue) {
+      return m_left.classifyInstance(inst);
+    } else {
+      return m_right.classifyInstance(inst);
+    } 
+  } 
+
+  /**
+   * Applies the m5 smoothing procedure to a prediction
+   *
+   * @param n number of instances in selected child of this node
+   * @param pred the prediction so far
+   * @param supportPred the prediction of the linear model at this node
+   * @return the current prediction smoothed with the prediction of the
+   * linear model at this node
+   * @throws Exception if an error occurs
+   */
+  protected static double smoothingOriginal(double n, double pred, 
+					    double supportPred) 
+    throws Exception {
+    double   smoothed;
+
+    smoothed = 
+      ((n * pred) + (SMOOTHING_CONSTANT * supportPred)) /
+      (n + SMOOTHING_CONSTANT);
+
+    return smoothed;
+  } 
+
+
+  /**
+   * Finds an attribute and split point for this node
+   *
+   * @throws Exception if an error occurs
+   */
+  public void split() throws Exception {
+    int		  i;
+    Instances     leftSubset, rightSubset;
+    SplitEvaluate bestSplit, currentSplit;
+    boolean[]     attsBelow;
+
+    if (!m_isLeaf) {
+     
+      bestSplit = new YongSplitInfo(0, m_numInstances - 1, -1);
+      currentSplit = new YongSplitInfo(0, m_numInstances - 1, -1);
+
+      // find the best attribute to split on
+      for (i = 0; i < m_numAttributes; i++) {
+	if (i != m_classIndex) {
+
+	  // sort the instances by this attribute
+	  m_instances.sort(i);
+	  currentSplit.attrSplit(i, m_instances);
+
+	  if ((Math.abs(currentSplit.maxImpurity() - 
+			bestSplit.maxImpurity()) > 1.e-6) 
+	      && (currentSplit.maxImpurity() 
+		  > bestSplit.maxImpurity() + 1.e-6)) {
+	    bestSplit = currentSplit.copy();
+	  } 
+	} 
+      } 
+
+      // cant find a good split or split point?
+      if (bestSplit.splitAttr() < 0 || bestSplit.position() < 1 
+	  || bestSplit.position() > m_numInstances - 1) {
+	m_isLeaf = true;
+      } else {
+	m_splitAtt = bestSplit.splitAttr();
+	m_splitValue = bestSplit.splitValue();
+	leftSubset = new Instances(m_instances, m_numInstances);
+	rightSubset = new Instances(m_instances, m_numInstances);
+
+	for (i = 0; i < m_numInstances; i++) {
+	  if (m_instances.instance(i).value(m_splitAtt) <= m_splitValue) {
+	    leftSubset.add(m_instances.instance(i));
+	  } else {
+	    rightSubset.add(m_instances.instance(i));
+	  } 
+	} 
+
+	leftSubset.compactify();
+	rightSubset.compactify();
+
+	// build left and right nodes
+	m_left = new RuleNode(m_globalDeviation, m_globalAbsDeviation, this);
+	m_left.setMinNumInstances(m_splitNum);
+	m_left.setRegressionTree(m_regressionTree);
+	m_left.setSaveInstances(m_saveInstances);
+	m_left.buildClassifier(leftSubset);
+
+	m_right = new RuleNode(m_globalDeviation, m_globalAbsDeviation, this);
+	m_right.setMinNumInstances(m_splitNum);
+	m_right.setRegressionTree(m_regressionTree);
+	m_right.setSaveInstances(m_saveInstances);
+	m_right.buildClassifier(rightSubset);
+
+	// now find out what attributes are tested in the left and right
+	// subtrees and use them to learn a linear model for this node
+	if (!m_regressionTree) {
+	  attsBelow = attsTestedBelow();
+	  attsBelow[m_classIndex] = true;
+	  int count = 0, j;
+
+	  for (j = 0; j < m_numAttributes; j++) {
+	    if (attsBelow[j]) {
+	      count++;
+	    } 
+	  } 
+	  
+	  int[] indices = new int[count];
+
+	  count = 0;
+	  
+	  for (j = 0; j < m_numAttributes; j++) {
+	    if (attsBelow[j] && (j != m_classIndex)) {
+	      indices[count++] = j;
+	    } 
+	  } 
+	  
+	  indices[count] = m_classIndex;
+	  m_indices = indices;
+	} else {
+	  m_indices = new int [1];
+	  m_indices[0] = m_classIndex;
+	  m_numParameters = 1;
+	}
+      } 
+    } 
+
+    if (m_isLeaf) {
+      int [] indices = new int [1];
+      indices[0] = m_classIndex;
+      m_indices = indices;
+      m_numParameters = 1;
+     
+      // need to evaluate the model here if want correct stats for unpruned
+      // tree
+    } 
+  } 
+
+  /**
+   * Build a linear model for this node using those attributes
+   * specified in indices.
+   *
+   * @param indices an array of attribute indices to include in the linear
+   * model
+   * @throws Exception if something goes wrong
+   */
+  private void buildLinearModel(int [] indices) throws Exception {
+    // copy the training instances and remove all but the tested
+    // attributes
+    Instances reducedInst = new Instances(m_instances);
+    Remove attributeFilter = new Remove();
+    
+    attributeFilter.setInvertSelection(true);
+    attributeFilter.setAttributeIndicesArray(indices);
+    attributeFilter.setInputFormat(reducedInst);
+
+    reducedInst = Filter.useFilter(reducedInst, attributeFilter);
+    
+    // build a linear regression for the training data using the
+    // tested attributes
+    LinearRegression temp = new LinearRegression();
+    temp.buildClassifier(reducedInst);
+
+    double [] lmCoeffs = temp.coefficients();
+    double [] coeffs = new double [m_instances.numAttributes()];
+
+    for (int i = 0; i < lmCoeffs.length - 1; i++) {
+      if (indices[i] != m_classIndex) {
+	coeffs[indices[i]] = lmCoeffs[i];
+      }
+    }
+    m_nodeModel = new PreConstructedLinearModel(coeffs, lmCoeffs[lmCoeffs.length - 1]);
+    m_nodeModel.buildClassifier(m_instances);
+  }
+
+  /**
+   * Returns an array containing the indexes of attributes used in tests
+   * above this node
+   *
+   * @return an array of attribute indexes
+   */
+  private boolean[] attsTestedAbove() {
+    boolean[] atts = new boolean[m_numAttributes];
+    boolean[] attsAbove = null;
+
+    if (m_parent != null) {
+      attsAbove = m_parent.attsTestedAbove();
+    } 
+
+    if (attsAbove != null) {
+      for (int i = 0; i < m_numAttributes; i++) {
+	atts[i] = attsAbove[i];
+      } 
+    } 
+
+    atts[m_splitAtt] = true;
+    return atts;
+  } 
+
+  /**
+   * Returns an array containing the indexes of attributes used in tests
+   * below this node
+   *
+   * @return an array of attribute indexes
+   */
+  private boolean[] attsTestedBelow() {
+    boolean[] attsBelow = new boolean[m_numAttributes];
+    boolean[] attsBelowLeft = null;
+    boolean[] attsBelowRight = null;
+
+    if (m_right != null) {
+      attsBelowRight = m_right.attsTestedBelow();
+    } 
+
+    if (m_left != null) {
+      attsBelowLeft = m_left.attsTestedBelow();
+    } 
+
+    for (int i = 0; i < m_numAttributes; i++) {
+      if (attsBelowLeft != null) {
+	attsBelow[i] = (attsBelow[i] || attsBelowLeft[i]);
+      } 
+
+      if (attsBelowRight != null) {
+	attsBelow[i] = (attsBelow[i] || attsBelowRight[i]);
+      } 
+    } 
+
+    if (!m_isLeaf) {
+      attsBelow[m_splitAtt] = true;
+    } 
+    return attsBelow;
+  } 
+
+  /**
+   * Sets the leaves' numbers
+   * @param leafCounter the number of leaves counted
+   * @return the number of the total leaves under the node
+   */
+  public int numLeaves(int leafCounter) {
+
+    if (!m_isLeaf) {
+      // node
+      m_leafModelNum = 0;
+
+      if (m_left != null) {
+	leafCounter = m_left.numLeaves(leafCounter);
+      } 
+
+      if (m_right != null) {
+	leafCounter = m_right.numLeaves(leafCounter);
+      } 
+    } else {
+      // leaf
+      leafCounter++;
+      m_leafModelNum = leafCounter;
+    } 
+    return leafCounter;
+  } 
+
+  /**
+   * print the linear model at this node
+   * 
+   * @return the linear model
+   */
+  public String toString() {
+    return printNodeLinearModel();
+  } 
+
+  /**
+   * print the linear model at this node
+   * 
+   * @return the linear model at this node
+   */
+  public String printNodeLinearModel() {
+    return m_nodeModel.toString();
+  } 
+
+  /**
+   * print all leaf models
+   * 
+   * @return the leaf models
+   */
+  public String printLeafModels() {
+    StringBuffer text = new StringBuffer();
+
+    if (m_isLeaf) {
+      text.append("\nLM num: " + m_leafModelNum);
+      text.append(m_nodeModel.toString());
+      text.append("\n");
+    } else {
+      text.append(m_left.printLeafModels());
+      text.append(m_right.printLeafModels());
+    } 
+    return text.toString();
+  } 
+
+  /**
+   * Returns a description of this node (debugging purposes)
+   *
+   * @return a string describing this node
+   */
+  public String nodeToString() {
+    StringBuffer text = new StringBuffer();
+
+    System.out.println("In to string");
+    text.append("Node:\n\tnum inst: " + m_numInstances);
+
+    if (m_isLeaf) {
+      text.append("\n\tleaf");
+    } else {
+      text.append("\tnode");
+    }
+
+    text.append("\n\tSplit att: " + m_instances.attribute(m_splitAtt).name());
+    text.append("\n\tSplit val: " + Utils.doubleToString(m_splitValue, 1, 3));
+    text.append("\n\tLM num: " + m_leafModelNum);
+    text.append("\n\tLinear model\n" + m_nodeModel.toString());
+    text.append("\n\n");
+
+    if (m_left != null) {
+      text.append(m_left.nodeToString());
+    } 
+
+    if (m_right != null) {
+      text.append(m_right.nodeToString());
+    } 
+
+    return text.toString();
+  } 
+
+  /**
+   * Recursively builds a textual description of the tree
+   *
+   * @param level the level of this node
+   * @return string describing the tree
+   */
+  public String treeToString(int level) {
+    int		 i;
+    StringBuffer text = new StringBuffer();
+
+    if (!m_isLeaf) {
+      text.append("\n");
+
+      for (i = 1; i <= level; i++) {
+	text.append("|   ");
+      } 
+
+      if (m_instances.attribute(m_splitAtt).name().charAt(0) != '[') {
+	text.append(m_instances.attribute(m_splitAtt).name() + " <= " 
+		    + Utils.doubleToString(m_splitValue, 1, 3) + " : ");
+      } else {
+	text.append(m_instances.attribute(m_splitAtt).name() + " false : ");
+      } 
+
+      if (m_left != null) {
+	text.append(m_left.treeToString(level + 1));
+      } else {
+	text.append("NULL\n");
+      }
+
+      for (i = 1; i <= level; i++) {
+	text.append("|   ");
+      } 
+
+      if (m_instances.attribute(m_splitAtt).name().charAt(0) != '[') {
+	text.append(m_instances.attribute(m_splitAtt).name() + " >  " 
+		    + Utils.doubleToString(m_splitValue, 1, 3) + " : ");
+      } else {
+	text.append(m_instances.attribute(m_splitAtt).name() + " true : ");
+      } 
+
+      if (m_right != null) {
+	text.append(m_right.treeToString(level + 1));
+      } else {
+	text.append("NULL\n");
+      }
+    } else {
+      text.append("LM" + m_leafModelNum);
+
+      if (m_globalDeviation > 0.0) {
+	text
+	  .append(" (" + m_numInstances + "/" 
+		  + Utils.doubleToString((100.0 * m_rootMeanSquaredError /
+					     m_globalDeviation), 1, 3) 
+		  + "%)\n");
+      } else {
+	text.append(" (" + m_numInstances + ")\n");
+      } 
+    } 
+    return text.toString();
+  } 
+
+  /**
+   * Traverses the tree and installs linear models at each node.
+   * This method must be called if pruning is not to be performed.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void installLinearModels() throws Exception {
+    Evaluation nodeModelEval;
+    if (m_isLeaf) {
+      buildLinearModel(m_indices);
+    } else {
+      if (m_left != null) {
+	m_left.installLinearModels();
+      }
+
+      if (m_right != null) {
+	m_right.installLinearModels();
+      }
+      buildLinearModel(m_indices);
+    }
+    nodeModelEval = new Evaluation(m_instances);
+    nodeModelEval.evaluateModel(m_nodeModel, m_instances);
+    m_rootMeanSquaredError = nodeModelEval.rootMeanSquaredError();
+    // save space
+    if (!m_saveInstances) {
+      m_instances = new Instances(m_instances, 0);
+    }
+  }
+
+  /**
+   * 
+   * @throws Exception
+   */
+  public void installSmoothedModels() throws Exception {
+
+    if (m_isLeaf) {
+      double [] coefficients = new double [m_numAttributes];
+      double intercept;
+      double  [] coeffsUsedByLinearModel = m_nodeModel.coefficients();
+      RuleNode current = this;
+      
+      // prime array with leaf node coefficients
+      for (int i = 0; i < coeffsUsedByLinearModel.length; i++) {
+	if (i != m_classIndex) {
+	  coefficients[i] = coeffsUsedByLinearModel[i];
+	}
+      }
+      // intercept
+      intercept = m_nodeModel.intercept();
+
+      do {
+	if (current.m_parent != null) {
+	  double n = current.m_numInstances;
+	  // contribution of the model below
+	  for (int i = 0; i < coefficients.length; i++) {
+	    coefficients[i] = ((coefficients[i] * n) / (n + SMOOTHING_CONSTANT));
+	  }
+	  intercept =  ((intercept * n) / (n + SMOOTHING_CONSTANT));
+
+	  // contribution of this model
+	  coeffsUsedByLinearModel = current.m_parent.getModel().coefficients();
+	  for (int i = 0; i < coeffsUsedByLinearModel.length; i++) {
+	    if (i != m_classIndex) {
+	      // smooth in these coefficients (at this node)
+	      coefficients[i] += 
+		((SMOOTHING_CONSTANT * coeffsUsedByLinearModel[i]) /
+		 (n + SMOOTHING_CONSTANT));
+	    }
+	  }
+	  // smooth in the intercept
+	  intercept += 
+	    ((SMOOTHING_CONSTANT * 
+	      current.m_parent.getModel().intercept()) /
+	     (n + SMOOTHING_CONSTANT));
+	  current = current.m_parent;
+	}
+      } while (current.m_parent != null);
+      m_nodeModel = 
+	new PreConstructedLinearModel(coefficients, intercept);
+      m_nodeModel.buildClassifier(m_instances);
+    }
+    if (m_left != null) {
+      m_left.installSmoothedModels();
+    }
+    if (m_right != null) {
+      m_right.installSmoothedModels();
+    }
+  }
+    
+  /**
+   * Recursively prune the tree
+   *
+   * @throws Exception if an error occurs
+   */
+  public void prune() throws Exception {
+    Evaluation nodeModelEval = null;
+
+    if (m_isLeaf) {
+      buildLinearModel(m_indices);
+      nodeModelEval = new Evaluation(m_instances);
+
+      // count the constant term as a paramter for a leaf
+      // Evaluate the model
+      nodeModelEval.evaluateModel(m_nodeModel, m_instances);
+
+      m_rootMeanSquaredError = nodeModelEval.rootMeanSquaredError();
+    } else {
+
+      // Prune the left and right subtrees
+      if (m_left != null) {
+	m_left.prune();
+      } 
+
+      if (m_right != null) {
+	m_right.prune();	
+      } 
+      
+      buildLinearModel(m_indices);
+      nodeModelEval = new Evaluation(m_instances);
+
+      double rmsModel;
+      double adjustedErrorModel;
+
+      nodeModelEval.evaluateModel(m_nodeModel, m_instances);
+
+      rmsModel = nodeModelEval.rootMeanSquaredError();
+      adjustedErrorModel = rmsModel 
+	* pruningFactor(m_numInstances, 
+			m_nodeModel.numParameters() + 1);
+
+      // Evaluate this node (ie its left and right subtrees)
+      Evaluation nodeEval = new Evaluation(m_instances);
+      double     rmsSubTree;
+      double     adjustedErrorNode;
+      int	 l_params = 0, r_params = 0;
+
+      nodeEval.evaluateModel(this, m_instances);
+
+      rmsSubTree = nodeEval.rootMeanSquaredError();
+
+      if (m_left != null) {
+	l_params = m_left.numParameters();
+      } 
+
+      if (m_right != null) {
+	r_params = m_right.numParameters();
+      } 
+
+      adjustedErrorNode = rmsSubTree 
+	* pruningFactor(m_numInstances, 
+			(l_params + r_params + 1));
+
+      if ((adjustedErrorModel <= adjustedErrorNode) 
+	  || (adjustedErrorModel < (m_globalDeviation * 0.00001))) {
+
+	// Choose linear model for this node rather than subtree model
+	m_isLeaf = true;
+	m_right = null;
+	m_left = null;
+	m_numParameters = m_nodeModel.numParameters() + 1;
+	m_rootMeanSquaredError = rmsModel;
+      } else {
+	m_numParameters = (l_params + r_params + 1);
+	m_rootMeanSquaredError = rmsSubTree;
+      } 
+    }
+    // save space
+    if (!m_saveInstances) {
+      m_instances = new Instances(m_instances, 0);
+    }
+  } 
+
+
+  /**
+   * Compute the pruning factor
+   *
+   * @param num_instances number of instances
+   * @param num_params number of parameters in the model
+   * @return the pruning factor
+   */
+  private double pruningFactor(int num_instances, int num_params) {
+    if (num_instances <= num_params) {
+      return 10.0;    // Caution says Yong in his code
+    } 
+
+    return ((double) (num_instances + m_pruningMultiplier * num_params) 
+	    / (double) (num_instances - num_params));
+  } 
+
+  /**
+   * Find the leaf with greatest coverage
+   *
+   * @param maxCoverage the greatest coverage found so far
+   * @param bestLeaf the leaf with the greatest coverage
+   */
+  public void findBestLeaf(double[] maxCoverage, RuleNode[] bestLeaf) {
+    if (!m_isLeaf) {
+      if (m_left != null) {
+	m_left.findBestLeaf(maxCoverage, bestLeaf);
+      } 
+
+      if (m_right != null) {
+	m_right.findBestLeaf(maxCoverage, bestLeaf);
+      } 
+    } else {
+      if (m_numInstances > maxCoverage[0]) {
+	maxCoverage[0] = m_numInstances;
+	bestLeaf[0] = this;
+      } 
+    } 
+  } 
+
+  /**
+   * Return a list containing all the leaves in the tree
+   *
+   * @param v a single element array containing a vector of leaves
+   */
+  public void returnLeaves(FastVector[] v) {
+    if (m_isLeaf) {
+      v[0].addElement(this);
+    } else {
+      if (m_left != null) {
+	m_left.returnLeaves(v);
+      } 
+
+      if (m_right != null) {
+	m_right.returnLeaves(v);
+      } 
+    } 
+  } 
+
+  /**
+   * Get the parent of this node
+   *
+   * @return the parent of this node
+   */
+  public RuleNode parentNode() {
+    return m_parent;
+  } 
+
+  /**
+   * Get the left child of this node
+   *
+   * @return the left child of this node
+   */
+  public RuleNode leftNode() {
+    return m_left;
+  } 
+
+  /**
+   * Get the right child of this node
+   *
+   * @return the right child of this node
+   */
+  public RuleNode rightNode() {
+    return m_right;
+  } 
+
+  /**
+   * Get the index of the splitting attribute for this node
+   *
+   * @return the index of the splitting attribute
+   */
+  public int splitAtt() {
+    return m_splitAtt;
+  } 
+
+  /**
+   * Get the split point for this node
+   *
+   * @return the split point for this node
+   */
+  public double splitVal() {
+    return m_splitValue;
+  } 
+
+  /**
+   * Get the number of linear models in the tree
+   *
+   * @return the number of linear models
+   */
+  public int numberOfLinearModels() {
+    if (m_isLeaf) {
+      return 1;
+    } else {
+      return m_left.numberOfLinearModels() + m_right.numberOfLinearModels();
+    } 
+  } 
+
+  /**
+   * Return true if this node is a leaf
+   *
+   * @return true if this node is a leaf
+   */
+  public boolean isLeaf() {
+    return m_isLeaf;
+  } 
+
+  /**
+   * Get the root mean squared error at this node
+   *
+   * @return the root mean squared error
+   */
+  protected double rootMeanSquaredError() {
+    return m_rootMeanSquaredError;
+  } 
+
+  /**
+   * Get the linear model at this node
+   *
+   * @return the linear model at this node
+   */
+  public PreConstructedLinearModel getModel() {
+    return m_nodeModel;
+  }
+
+  /**
+   * Return the number of instances that reach this node.
+   *
+   * @return the number of instances at this node.
+   */
+  public int getNumInstances() {
+    return m_numInstances;
+  }
+
+  /**
+   * Get the number of parameters in the model at this node
+   *
+   * @return the number of parameters in the model at this node
+   */
+  private int numParameters() {
+    return m_numParameters;
+  } 
+
+  /**
+   * Get the value of regressionTree.
+   *
+   * @return Value of regressionTree.
+   */
+  public boolean getRegressionTree() {
+    
+    return m_regressionTree;
+  }
+
+  /**
+   * Set the minumum number of instances to allow at a leaf node
+   *
+   * @param minNum the minimum number of instances
+   */
+  public void setMinNumInstances(double minNum) {
+    m_splitNum = minNum;
+  }
+
+  /**
+   * Get the minimum number of instances to allow at a leaf node
+   *
+   * @return a <code>double</code> value
+   */
+  public double getMinNumInstances() {
+    return m_splitNum;
+  }
+  
+  /**
+   * Set the value of regressionTree.
+   *
+   * @param newregressionTree Value to assign to regressionTree.
+   */
+  public void setRegressionTree(boolean newregressionTree) {
+    
+    m_regressionTree = newregressionTree;
+  }
+							  
+  /**
+   * Print all the linear models at the learf (debugging purposes)
+   */
+  public void printAllModels() {
+    if (m_isLeaf) {
+      System.out.println(m_nodeModel.toString());
+    } else {
+      System.out.println(m_nodeModel.toString());
+      m_left.printAllModels();
+      m_right.printAllModels();
+    } 
+  } 
+
+  /**
+   * Assigns a unique identifier to each node in the tree
+   *
+   * @param lastID last id number used
+   * @return ID after processing child nodes
+   */
+  protected int assignIDs(int lastID) {
+    int currLastID = lastID + 1;
+    m_id = currLastID;
+
+    if (m_left != null) {
+      currLastID = m_left.assignIDs(currLastID);
+    }
+
+    if (m_right != null) {
+      currLastID = m_right.assignIDs(currLastID);
+    }
+    return currLastID;
+  }
+
+  /**
+   * Assign a unique identifier to each node in the tree and then
+   * calls graphTree
+   *
+   * @param text a <code>StringBuffer</code> value
+   */
+  public void graph(StringBuffer text) {
+    assignIDs(-1);
+    graphTree(text);
+  }
+
+  /**
+   * Return a dotty style string describing the tree
+   *
+   * @param text a <code>StringBuffer</code> value
+   */
+  protected void graphTree(StringBuffer text) {
+    text.append("N" + m_id
+		+ (m_isLeaf 
+		   ? " [label=\"LM " + m_leafModelNum
+		   : " [label=\"" + m_instances.attribute(m_splitAtt).name())
+		+ (m_isLeaf
+		 ? " (" + ((m_globalDeviation > 0.0) 
+			  ?  m_numInstances + "/" 
+			     + Utils.doubleToString((100.0 * 
+						     m_rootMeanSquaredError /
+						     m_globalDeviation), 
+						    1, 3) 
+			     + "%)"
+			   : m_numInstances + ")")
+		    + "\" shape=box style=filled "
+		   : "\"")
+		+ (m_saveInstances
+		   ? "data=\n" + m_instances + "\n,\n"
+		   : "")
+		+ "]\n");
+		
+    if (m_left != null) {
+      text.append("N" + m_id + "->" + "N" + m_left.m_id + " [label=\"<="
+		  + Utils.doubleToString(m_splitValue, 1, 3)
+		  + "\"]\n");
+      m_left.graphTree(text);
+    }
+     
+    if (m_right != null) {
+      text.append("N" + m_id + "->" + "N" + m_right.m_id + " [label=\">"
+		  + Utils.doubleToString(m_splitValue, 1, 3)
+		  + "\"]\n");
+      m_right.graphTree(text);
+    }
+  }
+
+  /**
+   * Set whether to save instances for visualization purposes.
+   * Default is to save memory.
+   *
+   * @param save a <code>boolean</code> value
+   */
+  protected void setSaveInstances(boolean save) {
+    m_saveInstances = save;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/SplitEvaluate.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/SplitEvaluate.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/SplitEvaluate.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SplitEvaluate.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *    
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instances;
+
+/**
+ * Interface for objects that determine a split point on an attribute
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.3 $
+ */
+public interface SplitEvaluate {
+  
+  /**
+   * makes a copy of the SplitEvaluate object
+   * @return a copy of the object
+   */
+  SplitEvaluate copy () throws Exception;
+
+  /** 
+   * Finds the best splitting point for an attribute in the instances
+   * @param attr the splitting attribute
+   * @param inst the instances
+   * @exception Exception if something goes wrong
+   */
+   void attrSplit (int attr, Instances inst) throws Exception;
+
+  /**
+   * Returns the impurity of this split
+   *
+   * @return the impurity of this split
+   */
+   double maxImpurity();
+
+  /**
+   * Returns the position of the split in the sorted values. -1 indicates that
+   * a split could not be found.
+   *
+   * @return an <code>int</code> value
+   */
+   int position();
+  
+  /**
+   * Returns the attribute used in this split
+   *
+   * @return the attribute used in this split
+   */
+   int splitAttr();
+
+  /**
+   * Returns the split value
+   *
+   * @return the split value
+   */
+   double splitValue();
+
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Values.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Values.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/Values.java	(revision 29)
@@ -0,0 +1,115 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Values.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * Stores some statistics.
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public final class Values
+  implements RevisionHandler {
+  
+  int  numInstances;        // number of the instances
+  int  missingInstances;    // number of the instances with missing values 
+  int  first;               // index of the first instance
+  int  last;                // index of the last instance
+  int  attr;                // attribute
+  double  sum;              // sum of the instances for attribute
+  double  sqrSum;           // squared sum of the instances for attribute
+  double  va;               // variance
+  double  sd;               // standard deviation
+  
+
+  /**
+   * Constructs an object which stores some statistics of the instances such 
+   *      as sum, squared sum, variance, standard deviation
+   * @param low the index of the first instance
+   * @param high the index of the last instance
+   * @param attribute the attribute
+   * @param inst the instances
+   */
+
+  public Values(int low,int high,int attribute,Instances inst){
+    int i,count=0;
+    double value;
+
+    numInstances = high-low+1;
+    missingInstances = 0;
+    first = low;
+    last = high;
+    attr = attribute;
+    sum=0.0;
+    sqrSum=0.0;
+    for(i=first;i<=last;i++){
+      if(inst.instance(i).isMissing(attr)==false){
+	count++;
+	value = inst.instance(i).value(attr);
+	sum += value;
+	sqrSum += value * value;
+      }
+      
+      if(count >1){
+	va = (sqrSum - sum * sum/count)/count;
+	va = Math.abs(va);
+	sd = Math.sqrt(va);
+      }
+      else {va = 0.0;  sd = 0.0;}      
+    }
+  }
+
+  /**
+   * Converts the stats to a string
+   * @return the converted string
+   */
+  public final String  toString(){
+    
+    StringBuffer text = new StringBuffer();
+
+    text.append("Print statistic values of instances (" + first + "-" + last + 
+		"\n");
+    text.append("    Number of instances:\t" + numInstances + "\n");
+    text.append("    NUmber of instances with unknowns:\t" + missingInstances +
+		"\n");
+    text.append("    Attribute:\t\t\t:" + attr + "\n");
+    text.append("    Sum:\t\t\t" + sum + "\n");
+    text.append("    Squared sum:\t\t" + sqrSum + "\n");
+    text.append("    Stanard Deviation:\t\t" + sd + "\n");
+
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/YongSplitInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/YongSplitInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/trees/m5/YongSplitInfo.java	(revision 29)
@@ -0,0 +1,212 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    YongSplitInfo.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.classifiers.trees.m5;
+
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * Stores split information.
+ *
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public final class YongSplitInfo
+  implements Cloneable, Serializable, SplitEvaluate, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1864267581079767881L;
+
+  private int  number;         // number of total instances
+  private int  first;          // first instance index
+  private int  last;           // last instance index
+  private int  position;       // position of maximum impurity reduction
+  private double  maxImpurity; // maximum impurity reduction
+  private double  leftAve;     // left average class value
+  private double rightAve;     // right average class value
+  private int  splitAttr;      // spliting attribute 
+  private double  splitValue;  // splitting value
+
+  /**
+   * Constructs an object which contains the split information
+   * @param low the index of the first instance
+   * @param high the index of the last instance
+   * @param attr an attribute
+   */
+  public YongSplitInfo(int low,int high,int attr) {
+    number = high-low+1;
+    first = low;
+    last = high;
+    position = -1;
+    maxImpurity = -1.e20;
+    splitAttr = attr;      // attr < 0 is an empty object 
+    splitValue = 0.0;
+    Utils.SMALL = 1e-10;
+  }
+
+  /**
+   * Makes a copy of this SplitInfo object
+   */ 
+  public final SplitEvaluate copy () throws Exception {
+
+    YongSplitInfo s = (YongSplitInfo)this.clone();
+    
+    return s;
+  }
+
+  /**
+   * Resets the object of split information
+   * @param low the index of the first instance
+   * @param high the index of the last instance
+   * @param attr the attribute
+   */
+  public final void  initialize(int low,int high,int attr){
+      
+    number = high-low+1;
+    first = low;
+    last = high;
+    position = -1;
+    maxImpurity = -1.e20;
+    splitAttr = attr;
+    splitValue = 0.0;
+  }
+
+  /**
+   * Converts the spliting information to string
+   * @param inst the instances
+   */
+  public final String  toString(Instances inst){
+
+    StringBuffer text =  new StringBuffer();
+
+    text.append("Print SplitInfo:\n");
+    text.append("    Instances:\t\t" + number + " (" + first + "-" + 
+		position + "," + (position+1) + "-" + last + ")\n");
+    text.append("    Maximum Impurity Reduction:\t" + 
+		Utils.doubleToString(maxImpurity,1,4) + "\n");
+    text.append("    Left average:\t" + leftAve + "\n");
+    text.append("    Right average:\t" + rightAve + "\n");
+    if(maxImpurity>0.0)
+      text.append("    Splitting function:\t" + 
+		  inst.attribute(splitAttr).name() + " = " 
+		  + splitValue + "\n");
+    else text.append("    Splitting function:\tnull\n");
+    
+    return text.toString();
+  }
+  
+  /** 
+   * Finds the best splitting point for an attribute in the instances
+   * @param attr the splitting attribute
+   * @param inst the instances
+   * @exception Exception if something goes wrong
+   */
+  public final void  attrSplit(int attr,Instances inst) throws Exception {
+    int i,len,count,part;
+    Impurity imp;
+    
+    int low = 0;
+    int high = inst.numInstances()-1;
+    this.initialize(low,high,attr);
+    if(number < 4) {
+      return;
+    }
+    
+    len = ((high-low+1)<5) ? 1 : (high-low+1) / 5; 
+    
+    position = low;
+    
+    part = low + len - 1;
+    imp = new Impurity(part,attr,inst,5);
+    
+    count=0;
+    for(i=low+len;i<=high-len-1;i++) {
+      
+      imp.incremental(inst.instance(i).classValue(),1);
+      
+      if(Utils.eq(inst.instance(i+1).value(attr),
+		  inst.instance(i).value(attr)) == false) {
+	count = i;
+	if(imp.impurity > maxImpurity){
+	  maxImpurity = imp.impurity;
+	  splitValue = (inst.instance(i).value(attr) +
+			inst.instance(i+1).value(attr)) * 0.5;
+	  leftAve = imp.sl / imp.nl; 
+	  rightAve = imp.sr / imp.nr; 
+	  position=i;
+	}
+      } 
+    }
+  }
+
+  /**
+   * Returns the impurity of this split
+   *
+   * @return the impurity of this split
+   */
+  public double maxImpurity () {
+    return maxImpurity;
+  }
+
+  /**
+   * Returns the attribute used in this split
+   *
+   * @return the attribute used in this split
+   */
+  public int splitAttr () {
+    return splitAttr;
+  }
+
+  /**
+   * Returns the position of the split in the sorted values. -1 indicates that
+   * a split could not be found.
+   *
+   * @return an <code>int</code> value
+   */
+  public int position () {
+    return position;
+  }
+
+  /**
+   * Returns the split value
+   *
+   * @return the split value
+   */
+  public double splitValue () {
+    return splitValue;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/classifiers/xml/XMLClassifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/classifiers/xml/XMLClassifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/classifiers/xml/XMLClassifier.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLClassifier.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.xml;
+
+import weka.core.RevisionUtils;
+import weka.core.xml.XMLBasicSerialization;
+
+/**
+ * This class serializes and deserializes a Classifier instance to and
+ * fro XML.<br>
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.6 $ 
+ */
+public class XMLClassifier
+   extends XMLBasicSerialization {
+
+   /**
+    * initializes the serialization
+    * 
+    * @throws Exception if initialization fails
+    */
+   public XMLClassifier() throws Exception {
+      super();
+   }
+
+   /**
+    * generates internally a new XML document and clears also the IgnoreList and
+    * the mappings for the Read/Write-Methods
+    */
+   public void clear() throws Exception {
+      super.clear();
+
+      // allow
+      m_Properties.addAllowed(weka.classifiers.Classifier.class, "debug");
+      m_Properties.addAllowed(weka.classifiers.Classifier.class, "options");
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 1.6 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/AbstractClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/AbstractClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/AbstractClusterer.java	(revision 29)
@@ -0,0 +1,216 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractClusterer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+
+/** 
+ * Abstract clusterer.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5487 $
+ */
+public abstract class AbstractClusterer
+  implements Clusterer, Cloneable, Serializable, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6099962589663877632L;
+
+  // ===============
+  // Public methods.
+  // ===============
+ 
+  /**
+   * Generates a clusterer. Has to initialize all fields of the clusterer
+   * that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @exception Exception if the clusterer has not been 
+   * generated successfully
+   */
+  public abstract void buildClusterer(Instances data) throws Exception;
+
+  /**
+   * Classifies a given instance. Either this or distributionForInstance()
+   * needs to be implemented by subclasses.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an integer
+   * @exception Exception if instance could not be clustered
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+
+    double [] dist = distributionForInstance(instance);
+
+    if (dist == null) {
+      throw new Exception("Null distribution predicted");
+    }
+
+    if (Utils.sum(dist) <= 0) {
+      throw new Exception("Unable to cluster instance");
+    }
+    return Utils.maxIndex(dist);
+  }
+
+  /**
+   * Predicts the cluster memberships for a given instance.  Either
+   * this or clusterInstance() needs to be implemented by subclasses.
+   *
+   * @param instance the instance to be assigned a cluster.
+   * @return an array containing the estimated membership 
+   * probabilities of the test instance in each cluster (this 
+   * should sum to at most 1)
+   * @exception Exception if distribution could not be 
+   * computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) 
+    throws Exception {
+
+    double[] d = new double[numberOfClusters()];
+
+    d[clusterInstance(instance)] = 1.0;
+    
+    return d;
+  }
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @exception Exception if number of clusters could not be returned
+   * successfully
+   */
+  public abstract int numberOfClusters() throws Exception;
+
+  /**
+   * Creates a new instance of a clusterer given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * clusterer implements OptionHandler and the options parameter is
+   * non-null, the clusterer will have it's options set.
+   *
+   * @param clustererName the fully qualified class name of the clusterer
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created search object, ready for use.
+   * @exception Exception if the clusterer class name is invalid, or the 
+   * options supplied are not acceptable to the clusterer.
+   */
+  public static Clusterer forName(String clustererName,
+				  String [] options) throws Exception {
+    return (Clusterer)Utils.forName(Clusterer.class,
+				    clustererName,
+				    options);
+  }
+
+  /**
+   * Creates a deep copy of the given clusterer using serialization.
+   *
+   * @param model the clusterer to copy
+   * @return a deep copy of the clusterer
+   * @exception Exception if an error occurs
+   */
+  public static Clusterer makeCopy(Clusterer model) throws Exception {
+    return (Clusterer) new SerializedObject(model).getObject();
+  }
+
+  /**
+   * Creates copies of the current clusterer. Note that this method
+   * now uses Serialization to perform a deep copy, so the Clusterer
+   * object must be fully Serializable. Any currently built model will
+   * now be copied as well.
+   *
+   * @param model an example clusterer to copy
+   * @param num the number of clusterer copies to create.
+   * @return an array of clusterers.
+   * @exception Exception if an error occurs 
+   */
+  public static Clusterer [] makeCopies(Clusterer model,
+					int num) throws Exception {
+     if (model == null) {
+      throw new Exception("No model clusterer set");
+    }
+    Clusterer [] clusterers = new Clusterer [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < clusterers.length; i++) {
+      clusterers[i] = (Clusterer) so.getObject();
+    }
+    return clusterers;
+  }
+
+  /** 
+   * Returns the Capabilities of this clusterer. Derived classifiers have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    
+    result = new Capabilities(this);
+    result.enableAll();
+//    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5487 $");
+  }
+  
+  /**
+   * runs the clusterer instance with the given options.
+   * 
+   * @param clusterer		the clusterer to run
+   * @param options	the commandline options
+   */
+  protected static void runClusterer(Clusterer clusterer, String[] options) {
+    try {
+      System.out.println(ClusterEvaluation.evaluateClusterer(clusterer, options));
+    } 
+    catch (Exception e) {
+      if (    (e.getMessage() == null)
+	   || (    (e.getMessage() != null)
+	        && (e.getMessage().indexOf("General options") == -1) ) )
+	e.printStackTrace();
+      else
+	System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/AbstractDensityBasedClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/AbstractDensityBasedClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/AbstractDensityBasedClusterer.java	(revision 29)
@@ -0,0 +1,147 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractDensityBasedClusterer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Instance;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+
+/** 
+ * Abstract clustering model that produces (for each test instance)
+ * an estimate of the membership in each cluster 
+ * (ie. a probability distribution).
+ *
+ * @author   Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version  $Revision: 1.1 $
+ */
+public abstract class AbstractDensityBasedClusterer
+  extends AbstractClusterer implements DensityBasedClusterer {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -5950728041704213845L;
+
+  // ===============
+  // Public methods.
+  // ===============
+
+  /**
+   * Returns the prior probability of each cluster.
+   *
+   * @return the prior probability for each cluster
+   * @exception Exception if priors could not be 
+   * returned successfully
+   */
+  public abstract double[] clusterPriors() 
+    throws Exception;
+
+  /**
+   * Computes the log of the conditional density (per cluster) for a given instance.
+   * 
+   * @param instance the instance to compute the density for
+   * @return an array containing the estimated densities
+   * @exception Exception if the density could not be computed
+   * successfully
+   */
+  public abstract double[] logDensityPerClusterForInstance(Instance instance) 
+    throws Exception;
+
+  /**
+   * Computes the density for a given instance.
+   * 
+   * @param instance the instance to compute the density for
+   * @return the density.
+   * @exception Exception if the density could not be computed successfully
+   */
+  public double logDensityForInstance(Instance instance) throws Exception {
+
+    double[] a = logJointDensitiesForInstance(instance);
+    double max = a[Utils.maxIndex(a)];
+    double sum = 0.0;
+
+    for(int i = 0; i < a.length; i++) {
+      sum += Math.exp(a[i] - max);
+    }
+
+    return max + Math.log(sum);
+  }
+
+  /**
+   * Returns the cluster probability distribution for an instance.
+   *
+   * @param instance the instance to be clustered
+   * @return the probability distribution
+   * @throws Exception if computation fails
+   */  
+  public double[] distributionForInstance(Instance instance) throws Exception {
+    
+    return Utils.logs2probs(logJointDensitiesForInstance(instance));
+  }
+
+  /** 
+   * Returns the logs of the joint densities for a given instance.
+   *
+   * @param inst the instance 
+   * @return the array of values
+   * @exception Exception if values could not be computed
+   */
+  public double[] logJointDensitiesForInstance(Instance inst)
+    throws Exception {
+
+    double[] weights = logDensityPerClusterForInstance(inst);
+    double[] priors = clusterPriors();
+
+    for (int i = 0; i < weights.length; i++) {
+      if (priors[i] > 0) {
+	weights[i] += Math.log(priors[i]);
+      } else {
+	throw new IllegalArgumentException("Cluster empty!");
+      }
+    }
+    return weights;
+  }
+
+  /**
+   * Creates copies of the current clusterer. Note that this method
+   * now uses Serialization to perform a deep copy, so the Clusterer
+   * object must be fully Serializable. Any currently built model will
+   * now be copied as well.
+   *
+   * @param model an example clusterer to copy
+   * @param num the number of clusterer copies to create.
+   * @return an array of clusterers.
+   * @exception Exception if an error occurs 
+   */
+  public static DensityBasedClusterer [] makeCopies(DensityBasedClusterer model,
+						    int num) throws Exception {
+     if (model == null) {
+      throw new Exception("No model clusterer set");
+    }
+    DensityBasedClusterer [] clusterers = new DensityBasedClusterer [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < clusterers.length; i++) {
+      clusterers[i] = (DensityBasedClusterer) so.getObject();
+    }
+    return clusterers;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/CLOPE.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/CLOPE.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/CLOPE.java	(revision 29)
@@ -0,0 +1,635 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2008
+ *    & Alexander Smirnov (austellus@gmail.com)
+ */
+package weka.clusterers;
+
+import java.io.Serializable;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DecimalFormat;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+/**
+<!-- globalinfo-start -->
+* Yiling Yang, Xudong Guan, Jinyuan You: CLOPE: a fast and effective clustering algorithm for transactional data. In: Proceedings of the eighth ACM SIGKDD international conference on Knowledge discovery and data mining, 682-687, 2002.
+* <p/>
+<!-- globalinfo-end -->
+ *
+<!-- technical-bibtex-start -->
+* BibTeX:
+* <pre>
+* &#64;inproceedings{Yang2002,
+*    author = {Yiling Yang and Xudong Guan and Jinyuan You},
+*    booktitle = {Proceedings of the eighth ACM SIGKDD international conference on Knowledge discovery and data mining},
+*    pages = {682-687},
+*    publisher = {ACM  New York, NY, USA},
+*    title = {CLOPE: a fast and effective clustering algorithm for transactional data},
+*    year = {2002}
+* }
+* </pre>
+* <p/>
+<!-- technical-bibtex-end -->
+ *
+<!-- options-start -->
+* Valid options are: <p/>
+* 
+* <pre> -R &lt;num&gt;
+*  Repulsion
+*  (default 2.6)</pre>
+* 
+<!-- options-end -->
+ *
+ * @author Alexander Smirnov (austellus@gmail.com)
+ * @version $Revision: 5488 $
+ */
+public class CLOPE
+  extends AbstractClusterer
+  implements OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -567567567567588L;
+
+  /**
+   * Inner class for cluster of CLOPE.
+   *
+   * @see Serializable
+   */
+  private class CLOPECluster implements Serializable {
+
+    /**
+     * Number of transactions
+     */
+    public int N = 0; //number of transactions
+    
+    /**
+     * Number of distinct items (or width)
+     */
+    public int W = 0;
+    
+    /**
+     * Size of cluster
+     */
+    public int S = 0;
+    
+    /**
+     * Hash of <item, occurrence> pairs
+     */
+    public HashMap occ = new HashMap();
+
+    /**
+     *  Add item to cluster
+     */
+    public void AddItem(String Item) {
+      int count;
+      if (!this.occ.containsKey(Item)) {
+	this.occ.put(Item, 1);
+      } else {
+	count = (Integer) this.occ.get(Item);
+	count++;
+	this.occ.remove(Item);
+	this.occ.put(Item, count);
+      }
+      this.S++;
+    }
+
+    public void AddItem(Integer Item) {
+      int count;
+      if (!this.occ.containsKey(Item)) {
+	this.occ.put(Item, 1);
+      } else {
+	count = (Integer) this.occ.get(Item);
+	count++;
+	this.occ.remove(Item);
+	this.occ.put(Item, count);
+      }
+      this.S++;
+    }
+
+    /**
+     *  Delete item from cluster
+     */
+     public void DeleteItem(String Item) {
+      int count;
+
+      count = (Integer) this.occ.get(Item);
+
+      if (count == 1) {
+	this.occ.remove(Item);
+
+      } else {
+	count--;
+	this.occ.remove(Item);
+	this.occ.put(Item, count);
+      }
+      this.S--;
+     }
+
+     public void DeleteItem(Integer Item) {
+       int count;
+
+       count = (Integer) this.occ.get(Item);
+
+       if (count == 1) {
+	 this.occ.remove(Item);
+
+       } else {
+	 count--;
+	 this.occ.remove(Item);
+	 this.occ.put(Item, count);
+       }
+       this.S--;
+     }
+
+     /**
+      * Calculate Delta
+      */
+      public double DeltaAdd(Instance inst, double r) {
+	//System.out.println("DeltaAdd");
+	int S_new;
+	int W_new;
+	double profit;
+	double profit_new;
+	double deltaprofit;
+	S_new = 0;
+	W_new = occ.size();
+
+	if (inst instanceof SparseInstance) {
+	  //System.out.println("DeltaAddSparceInstance");
+	  for (int i = 0; i < inst.numValues(); i++) {
+	    S_new++;
+
+	    if ((Integer) this.occ.get(inst.index(i)) == null) {
+	      W_new++;
+	    }
+	  }
+	} else {
+	  for (int i = 0; i < inst.numAttributes(); i++) {
+	    if (!inst.isMissing(i)) {
+	      S_new++;
+	      if ((Integer) this.occ.get(i + inst.toString(i)) == null) {
+		W_new++;
+	      }
+	    }
+	  }
+	}
+	S_new += S;
+
+
+	if (N == 0) {
+	  deltaprofit = S_new / Math.pow(W_new, r);
+	} else {
+	  profit = S * N / Math.pow(W, r);
+	  profit_new = S_new * (N + 1) / Math.pow(W_new, r);
+	  deltaprofit = profit_new - profit;
+	}
+	return deltaprofit;
+      }
+
+      /**
+       * Add instance to cluster
+       */
+      public void AddInstance(Instance inst) {
+	if (inst instanceof SparseInstance) {
+	  //  System.out.println("AddSparceInstance");
+	  for (int i = 0; i < inst.numValues(); i++) {
+	    AddItem(inst.index(i));
+	    //  for(int i=0;i<inst.numAttributes();int++){
+	    // AddItem(inst.index(i)+inst.value(i));
+	  }
+	} else {
+	  for (int i = 0; i < inst.numAttributes(); i++) {
+
+	    if (!inst.isMissing(i)) {
+
+	      AddItem(i + inst.toString(i));
+	    }
+	  }
+	}
+	this.W = this.occ.size();
+	this.N++;
+      }
+
+      /**
+       * Delete instance from cluster
+       */
+      public void DeleteInstance(Instance inst) {
+	if (inst instanceof SparseInstance) {
+	  //   System.out.println("DeleteSparceInstance");
+	  for (int i = 0; i < inst.numValues(); i++) {
+	    DeleteItem(inst.index(i));
+	  }
+	} else {
+	  for (int i = 0; i <= inst.numAttributes() - 1; i++) {
+
+	    if (!inst.isMissing(i)) {
+	      DeleteItem(i + inst.toString(i));
+	    }
+	  }
+	}
+	this.W = this.occ.size();
+	this.N--;
+      }
+  }
+  /**
+   * Array of clusters
+   */
+  public ArrayList<CLOPECluster> clusters = new ArrayList<CLOPECluster>();
+  
+  /**
+   * Specifies the repulsion default
+   */
+  protected double m_RepulsionDefault = 2.6;
+  
+  /**
+   * Specifies the repulsion
+   */
+  protected double m_Repulsion = m_RepulsionDefault;
+  
+  /**
+   * Number of clusters 
+   */
+  protected int m_numberOfClusters = -1;
+  
+  /**
+   * Counter for the processed instances
+   */
+  protected int m_processed_InstanceID;
+  
+  /**
+   * Number of instances
+   */
+  protected int m_numberOfInstances;
+  
+  /**
+   * 
+   */
+  protected ArrayList<Integer> m_clusterAssignments = new ArrayList();
+  
+  /** 
+   * whether the number of clusters was already determined
+   */
+  protected boolean m_numberOfClustersDetermined = false;
+
+  public int numberOfClusters() {
+    determineNumberOfClusters();
+    return m_numberOfClusters;
+  }
+
+  protected void determineNumberOfClusters() {
+
+    m_numberOfClusters = clusters.size();
+
+    m_numberOfClustersDetermined = true;
+  }
+
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    result.addElement(new Option(
+	"\tRepulsion\n" + "\t(default " + m_RepulsionDefault + ")",
+	"R", 1, "-R <num>"));
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+    <!-- options-start -->
+    * Valid options are: <p/>
+    * 
+    * <pre> -R &lt;num&gt;
+    *  Repulsion
+    *  (default 2.6)</pre>
+    * 
+    <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String tmpStr;
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0) {
+      setRepulsion(Double.parseDouble(tmpStr));
+    } else {
+      setRepulsion(m_RepulsionDefault);
+    }
+  }
+
+  /**
+   * Gets the current settings of CLOPE
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    Vector result;
+
+    result = new Vector();
+
+    result.add("-R");
+    result.add("" + getRepulsion());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String repulsionTipText() {
+    return "Repulsion to be used.";
+  }
+
+  /**
+   * set the repulsion
+   *
+   * @param value the repulsion
+   * @throws Exception if number of clusters is negative
+   */
+  public void setRepulsion(double value) {
+    m_Repulsion = value;
+  }
+
+  /**
+   * gets the repulsion
+   *
+   * @return the repulsion
+   */
+  public double getRepulsion() {
+    return m_Repulsion;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    // result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Generate Clustering via CLOPE
+   * @param data The instances that need to be clustered
+   * @throws java.lang.Exception If clustering was not successful
+   */
+  public void buildClusterer(Instances data) throws Exception {
+    clusters.clear();
+    m_processed_InstanceID = 0;
+    m_clusterAssignments.clear();
+    m_numberOfInstances = data.numInstances();
+    boolean moved;
+    //Phase 1 
+    for (int i = 0; i < data.numInstances(); i++) {
+      int clusterid = AddInstanceToBestCluster(data.instance(i));
+      m_clusterAssignments.add(clusterid);
+
+    }
+    //Phase 2
+    do {
+      moved = false;
+      for (int i = 0; i < data.numInstances(); i++) {
+	m_processed_InstanceID = i;
+	int clusterid = MoveInstanceToBestCluster(data.instance(i));
+	if (clusterid != m_clusterAssignments.get(i)) {
+	  moved = true;
+	  m_clusterAssignments.set(i, clusterid);
+	}
+      }
+    } while (!moved);
+    m_processed_InstanceID = 0;
+  }
+
+  /**
+   * the default constructor
+   */
+  public CLOPE() {
+    super();
+  }
+
+  /**
+   * Add instance to best cluster
+   */
+  public int AddInstanceToBestCluster(Instance inst) {
+
+    double delta;
+    double deltamax;
+    int clustermax = -1;
+    if (clusters.size() > 0) {
+      int tempS = 0;
+      int tempW = 0;
+      if (inst instanceof SparseInstance) {
+	for (int i = 0; i < inst.numValues(); i++) {
+	  tempS++;
+	  tempW++;
+	}
+      } else {
+	for (int i = 0; i < inst.numAttributes(); i++) {
+	  if (!inst.isMissing(i)) {
+	    tempS++;
+	    tempW++;
+	  }
+	}
+      }
+
+      deltamax = tempS / Math.pow(tempW, m_Repulsion);
+
+      for (int i = 0; i < clusters.size(); i++) {
+	CLOPECluster tempcluster = clusters.get(i);
+	delta = tempcluster.DeltaAdd(inst, m_Repulsion);
+	//  System.out.println("delta " + delta);
+	if (delta > deltamax) {
+	  deltamax = delta;
+	  clustermax = i;
+	}
+      }
+    } else {
+      CLOPECluster newcluster = new CLOPECluster();
+      clusters.add(newcluster);
+      newcluster.AddInstance(inst);
+      return clusters.size() - 1;
+    }
+
+    if (clustermax == -1) {
+      CLOPECluster newcluster = new CLOPECluster();
+      clusters.add(newcluster);
+      newcluster.AddInstance(inst);
+      return clusters.size() - 1;
+    }
+    clusters.get(clustermax).AddInstance(inst);
+    return clustermax;
+  }
+
+  /**
+   * Move instance to best cluster
+   */
+  public int MoveInstanceToBestCluster(Instance inst) {
+
+    clusters.get(m_clusterAssignments.get(m_processed_InstanceID)).DeleteInstance(inst);
+    m_clusterAssignments.set(m_processed_InstanceID, -1);
+    double delta;
+    double deltamax;
+    int clustermax = -1;
+    int tempS = 0;
+    int tempW = 0;
+
+    if (inst instanceof SparseInstance) {
+      for (int i = 0; i < inst.numValues(); i++) {
+	tempS++;
+	tempW++;
+      }
+    } else {
+      for (int i = 0; i < inst.numAttributes(); i++) {
+	if (!inst.isMissing(i)) {
+	  tempS++;
+	  tempW++;
+	}
+      }
+    }
+
+    deltamax = tempS / Math.pow(tempW, m_Repulsion);
+    for (int i = 0; i < clusters.size(); i++) {
+      CLOPECluster tempcluster = clusters.get(i);
+      delta = tempcluster.DeltaAdd(inst, m_Repulsion);
+      // System.out.println("delta " + delta);
+      if (delta > deltamax) {
+	deltamax = delta;
+	clustermax = i;
+      }
+    }
+    if (clustermax == -1) {
+      CLOPECluster newcluster = new CLOPECluster();
+      clusters.add(newcluster);
+      newcluster.AddInstance(inst);
+      return clusters.size() - 1;
+    }
+    clusters.get(clustermax).AddInstance(inst);
+    return clustermax;
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance The instance to be assigned to a cluster
+   * @return int The number of the assigned cluster as an integer
+   * @throws java.lang.Exception If instance could not be clustered
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    if (m_processed_InstanceID >= m_numberOfInstances) {
+      m_processed_InstanceID = 0;
+    }
+    int i = m_clusterAssignments.get(m_processed_InstanceID);
+    m_processed_InstanceID++;
+    return i;
+  }
+
+  /**
+   * return a string describing this clusterer
+   *
+   * @return a description of the clusterer as a string
+   */
+  public String toString() {
+    StringBuffer stringBuffer = new StringBuffer();
+    stringBuffer.append("CLOPE clustering results\n" +
+    "========================================================================================\n\n");
+    stringBuffer.append("Clustered instances: " + m_clusterAssignments.size() + "\n");
+    return stringBuffer.toString() + "\n";
+  }
+
+  /**
+   * Returns a string describing this DataMining-Algorithm
+   * @return String Information for the gui-explorer
+   */
+  public String globalInfo() {
+    return getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   *
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Yiling Yang and Xudong Guan and Jinyuan You");
+    result.setValue(Field.TITLE, "CLOPE: a fast and effective clustering algorithm for transactional data");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the eighth ACM SIGKDD international conference on Knowledge discovery and data mining");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.PAGES, "682-687");
+    result.setValue(Field.PUBLISHER, "ACM  New York, NY, USA");
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5488 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments: <p>
+   * -t training file [-R repulsion]
+   */
+  public static void main(String[] argv) {
+    runClusterer(new CLOPE(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/CheckClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/CheckClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/CheckClusterer.java	(revision 29)
@@ -0,0 +1,1352 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckClusterer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.CheckScheme;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with 
+ * clusterers. If you implement a clusterer using the WEKA.libraries,
+ * you should run the checks on it to ensure robustness and correct
+ * operation. Passing all the tests of this object does not mean
+ * bugs in the clusterer don't exist, but this will help find some
+ * common ones. <p/>
+ * 
+ * Typical usage: <p/>
+ * <code>java weka.clusterers.CheckClusterer -W clusterer_name 
+ * -- clusterer_options </code><p/>
+ * 
+ * CheckClusterer reports on the following:
+ * <ul>
+ *    <li> Clusterer abilities 
+ *      <ul>
+ *         <li> Possible command line options to the clusterer </li>
+ *         <li> Whether the clusterer can predict nominal, numeric, string, 
+ *              date or relational class attributes.</li>
+ *         <li> Whether the clusterer can handle numeric predictor attributes </li>
+ *         <li> Whether the clusterer can handle nominal predictor attributes </li>
+ *         <li> Whether the clusterer can handle string predictor attributes </li>
+ *         <li> Whether the clusterer can handle date predictor attributes </li>
+ *         <li> Whether the clusterer can handle relational predictor attributes </li>
+ *         <li> Whether the clusterer can handle multi-instance data </li>
+ *         <li> Whether the clusterer can handle missing predictor values </li>
+ *         <li> Whether the clusterer can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning 
+ *      <ul>
+ *         <li> Correct initialisation during buildClusterer (i.e. no result
+ *              changes when buildClusterer called repeatedly) </li>
+ *         <li> Whether the clusterer alters the data pased to it 
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases 
+ *      <ul>
+ *         <li> building clusterer with zero training instances </li>
+ *         <li> all but one predictor attribute values missing </li>
+ *         <li> all predictor attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckClusterer with the debug option set will output the 
+ * training dataset for any failed tests.<p/>
+ *
+ * The <code>weka.clusterers.AbstractClustererTest</code> uses this
+ * class to test all the clusterers. Any changes here, have to be 
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ * 
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 2).</pre>
+ * 
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 1).</pre>
+ * 
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 1).</pre>
+ * 
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 1).</pre>
+ * 
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 1).</pre>
+ * 
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 1).</pre>
+ * 
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ * 
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ * 
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ * 
+ * <pre> -W
+ *  Full name of the clusterer analyzed.
+ *  eg: weka.clusterers.SimpleKMeans
+ *  (default weka.clusterers.SimpleKMeans)</pre>
+ * 
+ * <pre> 
+ * Options specific to clusterer weka.clusterers.SimpleKMeans:
+ * </pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -V
+ *  Display std. deviations for centroids.
+ * </pre>
+ * 
+ * <pre> -M
+ *  Replace missing values with mean/mode.
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated clusterer.<p/>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.11 $
+ * @see TestInstances
+ */
+public class CheckClusterer 
+  extends CheckScheme {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+  
+  /*** The clusterer to be examined */
+  protected Clusterer m_Clusterer = new SimpleKMeans();
+  
+  /**
+   * default constructor
+   */
+  public CheckClusterer() {
+    super();
+    
+    setNumInstances(40);
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    result.addElement(new Option(
+        "\tFull name of the clusterer analyzed.\n"
+        +"\teg: weka.clusterers.SimpleKMeans\n"
+        + "\t(default weka.clusterers.SimpleKMeans)",
+        "W", 1, "-W"));
+    
+    if ((m_Clusterer != null) 
+        && (m_Clusterer instanceof OptionHandler)) {
+      result.addElement(new Option("", "", 0, 
+          "\nOptions specific to clusterer "
+          + m_Clusterer.getClass().getName()
+          + ":"));
+      Enumeration enu = ((OptionHandler)m_Clusterer).listOptions();
+      while (enu.hasMoreElements())
+        result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   * 
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 2).</pre>
+   * 
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 1).</pre>
+   * 
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 1).</pre>
+   * 
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 1).</pre>
+   * 
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 1).</pre>
+   * 
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 1).</pre>
+   * 
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   * 
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   * 
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   * 
+   * <pre> -W
+   *  Full name of the clusterer analyzed.
+   *  eg: weka.clusterers.SimpleKMeans
+   *  (default weka.clusterers.SimpleKMeans)</pre>
+   * 
+   * <pre> 
+   * Options specific to clusterer weka.clusterers.SimpleKMeans:
+   * </pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -V
+   *  Display std. deviations for centroids.
+   * </pre>
+   * 
+   * <pre> -M
+   *  Replace missing values with mean/mode.
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    tmpStr = Utils.getOption('N', options);
+    
+    super.setOptions(options);
+    
+    if (tmpStr.length() != 0)
+      setNumInstances(Integer.parseInt(tmpStr));
+    else
+      setNumInstances(40);
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.clusterers.SimpleKMeans.class.getName();
+    setClusterer(
+	(Clusterer) forName(
+	    "weka.clusterers", 
+	    Clusterer.class, 
+	    tmpStr, 
+	    Utils.partitionOptions(options)));
+  }
+  
+  /**
+   * Gets the current settings of the CheckClusterer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getClusterer() != null) {
+      result.add("-W");
+      result.add(getClusterer().getClass().getName());
+    }
+    
+    if ((m_Clusterer != null) && (m_Clusterer instanceof OptionHandler))
+      options = ((OptionHandler) m_Clusterer).getOptions();
+    else
+      options = new String[0];
+    
+    if (options.length > 0) {
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+    
+    if (getClusterer() == null) {
+      println("\n=== No clusterer set ===");
+      return;
+    }
+    println("\n=== Check on Clusterer: "
+        + getClusterer().getClass().getName()
+        + " ===\n");
+    
+    // Start tests
+    println("--> Checking for interfaces");
+    canTakeOptions();
+    boolean updateable = updateableClusterer()[0];
+    boolean weightedInstancesHandler = weightedInstancesHandler()[0];
+    boolean multiInstanceHandler = multiInstanceHandler()[0];
+    println("--> Clusterer tests");
+    declaresSerialVersionUID();
+    runTests(weightedInstancesHandler, multiInstanceHandler, updateable);
+  }
+  
+  /**
+   * Set the clusterer for testing. 
+   *
+   * @param newClusterer the Clusterer to use.
+   */
+  public void setClusterer(Clusterer newClusterer) {
+    m_Clusterer = newClusterer;
+  }
+  
+  /**
+   * Get the clusterer used as the clusterer
+   *
+   * @return the clusterer used as the clusterer
+   */
+  public Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+  
+  /**
+   * Run a battery of tests
+   *
+   * @param weighted true if the clusterer says it handles weights
+   * @param multiInstance true if the clusterer is a multi-instance clusterer
+   * @param updateable true if the classifier is updateable
+   */
+  protected void runTests(boolean weighted, boolean multiInstance, boolean updateable) {
+    
+    boolean PNom = canPredict(true,  false, false, false, false, multiInstance)[0];
+    boolean PNum = canPredict(false, true,  false, false, false, multiInstance)[0];
+    boolean PStr = canPredict(false, false, true,  false, false, multiInstance)[0];
+    boolean PDat = canPredict(false, false, false, true,  false, multiInstance)[0];
+    boolean PRel;
+    if (!multiInstance)
+      PRel = canPredict(false, false, false, false,  true, multiInstance)[0];
+    else
+      PRel = false;
+
+    if (PNom || PNum || PStr || PDat || PRel) {
+      if (weighted)
+        instanceWeights(PNom, PNum, PStr, PDat, PRel, multiInstance);
+      
+      canHandleZeroTraining(PNom, PNum, PStr, PDat, PRel, multiInstance);
+      boolean handleMissingPredictors = canHandleMissing(PNom, PNum, PStr, PDat, PRel, 
+          multiInstance, true, 20)[0];
+      if (handleMissingPredictors)
+        canHandleMissing(PNom, PNum, PStr, PDat, PRel, multiInstance, true, 100);
+      
+      correctBuildInitialisation(PNom, PNum, PStr, PDat, PRel, multiInstance);
+      datasetIntegrity(PNom, PNum, PStr, PDat, PRel, multiInstance, handleMissingPredictors);
+      if (updateable)
+        updatingEquality(PNom, PNum, PStr, PDat, PRel, multiInstance);
+    }
+  }
+  
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the clusterer can take options
+   */
+  protected boolean[] canTakeOptions() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("options...");
+    if (m_Clusterer instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler)m_Clusterer).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n" 
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme can build models incrementally.
+   *
+   * @return index 0 is true if the clusterer can train incrementally
+   */
+  protected boolean[] updateableClusterer() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("updateable clusterer...");
+    if (m_Clusterer instanceof UpdateableClusterer) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the clusterer handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("weighted instances clusterer...");
+    if (m_Clusterer instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme handles multi-instance data.
+   * 
+   * @return true if the clusterer handles multi-instance data
+   */
+  protected boolean[] multiInstanceHandler() {
+    boolean[] result = new boolean[2];
+    
+    print("multi-instance clusterer...");
+    if (m_Clusterer instanceof MultiInstanceCapabilitiesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * tests for a serialVersionUID. Fails in case the scheme doesn't declare
+   * a UID.
+   *
+   * @return index 0 is true if the scheme declares a UID
+   */
+  protected boolean[] declaresSerialVersionUID() {
+    boolean[] result = new boolean[2];
+    
+    print("serialVersionUID...");
+    
+    result[0] = !SerializationHelper.needsUID(m_Clusterer.getClass());
+    
+    if (result[0])
+      println("yes");
+    else
+      println("no");
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic prediction of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canPredict(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance) {
+    
+    print("basic predict");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("unary");
+    accepts.addElement("binary");
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("multi-instance");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false;
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        missingLevel, predictorMissing, 
+        numTrain, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance) {
+    
+    print("handle zero training instances");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, missingLevel = 0;
+    boolean predictorMissing = false;
+    
+    return runBasicTest(
+              nominalPredictor, numericPredictor, stringPredictor, 
+              datePredictor, relationalPredictor, 
+              multiInstance,
+              missingLevel, predictorMissing,
+              numTrain, 
+              accepts);
+  }
+  
+  /**
+   * Checks whether the scheme correctly initialises models when 
+   * buildClusterer is called. This test calls buildClusterer with
+   * one training dataset. buildClusterer is then called on a training set 
+   * with different structure, and then again with the original training set. 
+   * If the equals method of the ClusterEvaluation class returns 
+   * false, this is noted as incorrect build initialisation.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] correctBuildInitialisation(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance) {
+
+    boolean[] result = new boolean[2];
+    
+    print("correct initialisation during buildClusterer");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false;
+    
+    Instances train1 = null;
+    Instances train2 = null;
+    Clusterer clusterer = null;
+    ClusterEvaluation evaluation1A = null;
+    ClusterEvaluation evaluation1B = null;
+    ClusterEvaluation evaluation2 = null;
+    boolean built = false;
+    int stage = 0;
+    try {
+      
+      // Make two train sets with different numbers of attributes
+      train1 = makeTestDataset(42, numTrain, 
+                               nominalPredictor    ? getNumNominal()    : 0,
+                               numericPredictor    ? getNumNumeric()    : 0, 
+                               stringPredictor     ? getNumString()     : 0, 
+                               datePredictor       ? getNumDate()       : 0, 
+                               relationalPredictor ? getNumRelational() : 0, 
+                               multiInstance);
+      train2 = makeTestDataset(84, numTrain, 
+                               nominalPredictor    ? getNumNominal() + 1 : 0,
+                               numericPredictor    ? getNumNumeric() + 1 : 0, 
+                               stringPredictor     ? getNumString()      : 0, 
+                               datePredictor       ? getNumDate()        : 0, 
+                               relationalPredictor ? getNumRelational()  : 0, 
+                               multiInstance);
+      if (nominalPredictor && !multiInstance) {
+        train1.deleteAttributeAt(0);
+        train2.deleteAttributeAt(0);
+      }
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, predictorMissing);
+        addMissing(train2, missingLevel, predictorMissing);
+      }
+      
+      clusterer = AbstractClusterer.makeCopies(getClusterer(), 1)[0];
+      evaluation1A = new ClusterEvaluation();
+      evaluation1B = new ClusterEvaluation();
+      evaluation2 = new ClusterEvaluation();
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      stage = 0;
+      clusterer.buildClusterer(train1);
+      built = true;
+      evaluation1A.setClusterer(clusterer);
+      evaluation1A.evaluateClusterer(train1);
+      
+      stage = 1;
+      built = false;
+      clusterer.buildClusterer(train2);
+      built = true;
+      evaluation2.setClusterer(clusterer);
+      evaluation2.evaluateClusterer(train2);
+      
+      stage = 2;
+      built = false;
+      clusterer.buildClusterer(train1);
+      built = true;
+      evaluation1B.setClusterer(clusterer);
+      evaluation1B.evaluateClusterer(train1);
+      
+      stage = 3;
+      if (!evaluation1A.equals(evaluation1B)) {
+        if (m_Debug) {
+          println("\n=== Full report ===\n");
+          println("First buildClusterer()");
+          println(evaluation1A.clusterResultsToString() + "\n\n");
+          println("Second buildClusterer()");
+          println(evaluation1B.clusterResultsToString() + "\n\n");
+        }
+        throw new Exception("Results differ between buildClusterer calls");
+      }
+      println("yes");
+      result[0] = true;
+      
+      if (false && m_Debug) {
+        println("\n=== Full report ===\n");
+        println("First buildClusterer()");
+        println(evaluation1A.clusterResultsToString() + "\n\n");
+        println("Second buildClusterer()");
+        println(evaluation1B.clusterResultsToString() + "\n\n");
+      }
+    } 
+    catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;	  
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      boolean predictorMissing,
+      int missingLevel) {
+    
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (predictorMissing) {
+      print(" predictor");
+    }
+    print(" values");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    int numTrain = getNumInstances();
+    
+    return runBasicTest(nominalPredictor, numericPredictor, stringPredictor, 
+        datePredictor, relationalPredictor, 
+        multiInstance,
+        missingLevel, predictorMissing,
+        numTrain, 
+        accepts);
+  }
+  
+  /**
+   * Checks whether the clusterer can handle instance weights.
+   * This test compares the clusterer performance on two datasets
+   * that are identical except for the training weights. If the 
+   * results change, then the clusterer must be using the weights. It
+   * may be possible to get a false positive from this test if the 
+   * weight changes aren't significant enough to induce a change
+   * in clusterer performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance) {
+    
+    print("clusterer uses instance weights");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    int numTrain = 2*getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Clusterer [] clusterers = null;
+    ClusterEvaluation evaluationB = null;
+    ClusterEvaluation evaluationI = null;
+    boolean built = false;
+    boolean evalFail = false;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal() + 1 : 0,
+                              numericPredictor    ? getNumNumeric() + 1 : 0, 
+                              stringPredictor     ? getNumString()      : 0, 
+                              datePredictor       ? getNumDate()        : 0, 
+                              relationalPredictor ? getNumRelational()  : 0, 
+                              multiInstance);
+      if (nominalPredictor && !multiInstance)
+        train.deleteAttributeAt(0);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing);
+      clusterers = AbstractClusterer.makeCopies(getClusterer(), 2);
+      evaluationB = new ClusterEvaluation();
+      evaluationI = new ClusterEvaluation();
+      clusterers[0].buildClusterer(train);
+      evaluationB.setClusterer(clusterers[0]);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      
+      // Now modify instance weights and re-built/test
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      clusterers[1].buildClusterer(train);
+      built = true;
+      evaluationI.setClusterer(clusterers[1]);
+      if (evaluationB.equals(evaluationI)) {
+        //	println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println("\nboth methods\n");
+          println(evaluationB.clusterResultsToString());
+        } else {
+          print("Problem during");
+          if (built) {
+            print(" testing");
+          } else {
+            print(" training");
+          }
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1) 
+              + "    " + train.instance(i).weight());
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * training. If the scheme needs to modify the training
+   * data it should take a copy of the training data. Currently checks
+   * for changes to header structure, number of instances, order of
+   * instances, instance weights.
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param predictorMissing true if we know the clusterer can handle
+   * (at least) moderate missing predictor values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      boolean predictorMissing) {
+    
+    print("clusterer doesn't alter original datasets");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    int numTrain = getNumInstances(), missingLevel = 20;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Clusterer clusterer = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0, 
+                              datePredictor       ? getNumDate()       : 0, 
+                              relationalPredictor ? getNumRelational() : 0, 
+                              multiInstance);
+      if (nominalPredictor && !multiInstance)
+        train.deleteAttributeAt(0);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing);
+      clusterer = AbstractClusterer.makeCopies(getClusterer(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      Instances trainCopy = new Instances(train);
+      clusterer.buildClusterer(trainCopy);
+      compareDatasets(train, trainCopy);
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during training");
+        println(": " + ex.getMessage() + "\n");
+        println("Here is the dataset:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether an updateable scheme produces the same model when
+   * trained incrementally as when batch trained. The model itself
+   * cannot be compared, so we compare the evaluation on test data
+   * for both models. It is possible to get a false positive on this
+   * test (likelihood depends on the classifier).
+   *
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] updatingEquality(
+      boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor, 
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance) {
+    
+    print("incremental training produces the same results"
+        + " as batch training");
+    printAttributeSummary(
+        nominalPredictor, numericPredictor, stringPredictor, datePredictor, relationalPredictor, multiInstance);
+    print("...");
+    int numTrain = getNumInstances(), missingLevel = 0;
+    boolean predictorMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Clusterer[] clusterers = null;
+    ClusterEvaluation evaluationB = null;
+    ClusterEvaluation evaluationI = null;
+    boolean built = false;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0, 
+                              datePredictor       ? getNumDate()       : 0, 
+                              relationalPredictor ? getNumRelational() : 0, 
+                              multiInstance);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing, classMissing);
+      clusterers = AbstractClusterer.makeCopies(getClusterer(), 2);
+      evaluationB = new ClusterEvaluation();
+      evaluationI = new ClusterEvaluation();
+      clusterers[0].buildClusterer(train);
+      evaluationB.setClusterer(clusterers[0]);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      clusterers[1].buildClusterer(new Instances(train, 0));
+      for (int i = 0; i < train.numInstances(); i++) {
+        ((UpdateableClusterer)clusterers[1]).updateClusterer(
+            train.instance(i));
+      }
+      built = true;
+      evaluationI.setClusterer(clusterers[1]);
+      if (!evaluationB.equals(evaluationI)) {
+        println("no");
+        result[0] = false;
+        
+        if (m_Debug) {
+          println("\n=== Full Report ===");
+          println("Results differ between batch and "
+              + "incrementally built models.\n"
+              + "Depending on the classifier, this may be OK");
+          println("Here are the results:\n");
+          println("\nbatch built results\n" + evaluationB.clusterResultsToString());
+          println("\nincrementally built results\n" + evaluationI.clusterResultsToString());
+          println("Here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+        }
+      }
+      else {
+        println("yes");
+        result[0] = true;
+      }
+    } catch (Exception ex) {
+      result[0] = false;
+      
+      print("Problem during");
+      if (built)
+        print(" testing");
+      else
+        print(" training");
+      println(": " + ex.getMessage() + "\n");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param nominalPredictor if true use nominal predictor attributes
+   * @param numericPredictor if true use numeric predictor attributes
+   * @param stringPredictor if true use string predictor attributes
+   * @param datePredictor if true use date predictor attributes
+   * @param relationalPredictor if true use relational predictor attributes
+   * @param multiInstance whether multi-instance is needed
+   * @param missingLevel the percentage of missing values
+   * @param predictorMissing true if the missing values may be in 
+   * the predictors
+   * @param numTrain the number of instances in the training set
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(boolean nominalPredictor,
+      boolean numericPredictor, 
+      boolean stringPredictor,
+      boolean datePredictor,
+      boolean relationalPredictor,
+      boolean multiInstance,
+      int missingLevel,
+      boolean predictorMissing,
+      int numTrain,
+      FastVector accepts) {
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Clusterer clusterer = null;
+    try {
+      train = makeTestDataset(42, numTrain, 
+                              nominalPredictor    ? getNumNominal()    : 0,
+                              numericPredictor    ? getNumNumeric()    : 0, 
+                              stringPredictor     ? getNumString()     : 0,
+                              datePredictor       ? getNumDate()       : 0,
+                              relationalPredictor ? getNumRelational() : 0,
+                              multiInstance);
+      if (nominalPredictor && !multiInstance)
+        train.deleteAttributeAt(0);
+      if (missingLevel > 0)
+        addMissing(train, missingLevel, predictorMissing);
+      clusterer = AbstractClusterer.makeCopies(getClusterer(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      clusterer.buildClusterer(train);
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg = ex.getMessage().toLowerCase();
+      for (int i = 0; i < accepts.size(); i++) {
+        if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+          acceptable = true;
+        }
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during training");
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here is the dataset:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Add missing values to a dataset.
+   *
+   * @param data the instances to add missing values to
+   * @param level the level of missing values to add (if positive, this
+   * is the probability that a value will be set to missing, if negative
+   * all but one value will be set to missing (not yet implemented))
+   * @param predictorMissing if true, predictor attributes will be modified
+   */
+  protected void addMissing(Instances data, int level, boolean predictorMissing) {
+    
+    Random random = new Random(1);
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance current = data.instance(i);
+      for (int j = 0; j < data.numAttributes(); j++) {
+        if (predictorMissing) {
+          if (Math.abs(random.nextInt()) % 100 < level)
+            current.setMissing(j);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Make a simple set of instances with variable position of the class 
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numNominal the number of nominal attributes
+   * @param numNumeric the number of numeric attributes
+   * @param numString the number of string attributes
+   * @param numDate the number of date attributes
+   * @param numRelational the number of relational attributes
+   * @param multiInstance whether the dataset should a multi-instance dataset
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+                                      int numNominal, int numNumeric, 
+                                      int numString, int numDate,
+                                      int numRelational,
+                                      boolean multiInstance)
+  throws Exception {
+    
+    TestInstances dataset = new TestInstances();
+    
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal(numNominal);
+    dataset.setNumNumeric(numNumeric);
+    dataset.setNumString(numString);
+    dataset.setNumDate(numDate);
+    dataset.setNumRelational(numRelational);
+    dataset.setClassIndex(TestInstances.NO_CLASS);
+    dataset.setMultiInstance(multiInstance);
+    
+    return dataset.generate();
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param nominalPredictor true if nominal predictor attributes are present
+   * @param numericPredictor true if numeric predictor attributes are present
+   * @param stringPredictor true if string predictor attributes are present
+   * @param datePredictor true if date predictor attributes are present
+   * @param relationalPredictor true if relational predictor attributes are present
+   * @param multiInstance whether multi-instance is needed
+   */
+  protected void printAttributeSummary(boolean nominalPredictor, 
+                                       boolean numericPredictor, 
+                                       boolean stringPredictor, 
+                                       boolean datePredictor, 
+                                       boolean relationalPredictor, 
+                                       boolean multiInstance) {
+    
+    String str = "";
+
+    if (numericPredictor)
+      str += "numeric";
+    
+    if (nominalPredictor) {
+      if (str.length() > 0)
+        str += " & ";
+      str += "nominal";
+    }
+    
+    if (stringPredictor) {
+      if (str.length() > 0)
+        str += " & ";
+      str += "string";
+    }
+    
+    if (datePredictor) {
+      if (str.length() > 0)
+        str += " & ";
+      str += "date";
+    }
+    
+    if (relationalPredictor) {
+      if (str.length() > 0)
+        str += " & ";
+      str += "relational";
+    }
+    
+    str = " (" + str + " predictors)";
+    
+    print(str);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.11 $");
+  }
+  
+  /**
+   * Test method for this class
+   * 
+   * @param args the commandline options
+   */
+  public static void main(String [] args) {
+    runCheck(new CheckClusterer(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/ClusterEvaluation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/ClusterEvaluation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/ClusterEvaluation.java	(revision 29)
@@ -0,0 +1,1271 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClusterEvaluation.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package  weka.clusterers;
+
+import weka.core.Drawable;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for evaluating clustering models.<p/>
+ *
+ * Valid options are: <p/>
+ *
+ * -t name of the training file <br/>
+ * Specify the training file. <p/>
+ *
+ * -T name of the test file <br/>
+ * Specify the test file to apply clusterer to. <p/>
+ *
+ * -d name of file to save clustering model to <br/>
+ * Specify output file. <p/>
+ *
+ * -l name of file to load clustering model from <br/>
+ * Specifiy input file. <p/>
+ *
+ * -p attribute range <br/>
+ * Output predictions. Predictions are for the training file if only the
+ * training file is specified, otherwise they are for the test file. The range
+ * specifies attribute values to be output with the predictions.
+ * Use '-p 0' for none. <p/>
+ *
+ * -x num folds <br/>
+ * Set the number of folds for a cross validation of the training data.
+ * Cross validation can only be done for distribution clusterers and will
+ * be performed if the test file is missing. <p/>
+ * 
+ * -s num <br/>
+ * Sets the seed for randomizing the data for cross-validation. <p/>
+ *
+ * -c class <br/>
+ * Set the class attribute. If set, then class based evaluation of clustering
+ * is performed. <p/>
+ * 
+ * -g name of graph file <br/>
+ * Outputs the graph representation of the clusterer to the file. Only for
+ * clusterer that implemented the <code>weka.core.Drawable</code> interface.
+ * <p/>
+ *
+ * @author   Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version  $Revision: 6021 $
+ * @see	     weka.core.Drawable
+ */
+public class ClusterEvaluation 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -830188327319128005L;
+  
+  /** the clusterer */
+  private Clusterer m_Clusterer;
+
+  /** holds a string describing the results of clustering the training data */
+  private StringBuffer m_clusteringResults;
+
+  /** holds the number of clusters found by the clusterer */
+  private int m_numClusters;
+
+  /** holds the assigments of instances to clusters for a particular testing
+      dataset */
+  private double[] m_clusterAssignments;
+
+  /** holds the average log likelihood for a particular testing dataset
+     if the clusterer is a DensityBasedClusterer */
+  private double m_logL;
+
+  /** will hold the mapping of classes to clusters (for class based 
+      evaluation) */
+  private int[] m_classToCluster = null;
+
+  /**
+   * set the clusterer
+   * @param clusterer the clusterer to use
+   */
+  public void setClusterer(Clusterer clusterer) {
+    m_Clusterer = clusterer;
+  }
+
+  /**
+   * return the results of clustering.
+   * @return a string detailing the results of clustering a data set
+   */
+  public String clusterResultsToString() {
+    return m_clusteringResults.toString();
+  }
+
+  /**
+   * Return the number of clusters found for the most recent call to
+   * evaluateClusterer
+   * @return the number of clusters found
+   */
+  public int getNumClusters() {
+    return m_numClusters;
+  }
+
+  /**
+   * Return an array of cluster assignments corresponding to the most
+   * recent set of instances clustered.
+   * @return an array of cluster assignments
+   */
+  public double[] getClusterAssignments() {
+    return m_clusterAssignments;
+  }
+
+  /**
+   * Return the array (ordered by cluster number) of minimum error class to
+   * cluster mappings
+   * @return an array of class to cluster mappings
+   */
+  public int[] getClassesToClusters() {
+    return m_classToCluster;
+  }
+
+  /**
+   * Return the log likelihood corresponding to the most recent
+   * set of instances clustered.
+   *
+   * @return a <code>double</code> value
+   */
+  public double getLogLikelihood() {
+    return m_logL;
+  }
+
+  /**
+   * Constructor. Sets defaults for each member variable. Default Clusterer
+   * is EM.
+   */
+  public ClusterEvaluation () {
+    setClusterer(new SimpleKMeans());
+    m_clusteringResults = new StringBuffer();
+    m_clusterAssignments = null;
+  }
+
+  /**
+   * Evaluate the clusterer on a set of instances. Calculates clustering
+   * statistics and stores cluster assigments for the instances in
+   * m_clusterAssignments
+   * 
+   * @param test the set of instances to cluster
+   * @throws Exception if something goes wrong
+   */
+  public void evaluateClusterer(Instances test) throws Exception {
+    evaluateClusterer(test, "");
+  }
+
+  /**
+   * Evaluate the clusterer on a set of instances. Calculates clustering
+   * statistics and stores cluster assigments for the instances in
+   * m_clusterAssignments
+   * 
+   * @param test the set of instances to cluster
+   * @param testFileName the name of the test file for incremental testing, 
+   * if "" or null then not used
+   * @throws Exception if something goes wrong
+   */
+  public void evaluateClusterer(Instances test, String testFileName) throws Exception {
+    int i = 0;
+    int cnum;
+    double loglk = 0.0;
+    int cc = m_Clusterer.numberOfClusters();
+    m_numClusters = cc;
+    double[] instanceStats = new double[cc];
+    Instances testRaw = null;
+    boolean hasClass = (test.classIndex() >= 0);
+    int unclusteredInstances = 0;
+    Vector<Double> clusterAssignments = new Vector<Double>();
+    Filter filter = null;
+    DataSource source = null;
+    Instance inst;
+
+    if (testFileName == null)
+      testFileName = "";
+    
+    // load data
+    if (testFileName.length() != 0)
+      source = new DataSource(testFileName);
+    else
+      source = new DataSource(test);
+    testRaw = source.getStructure(test.classIndex());
+    
+    // If class is set then do class based evaluation as well
+    if (hasClass) {
+      if (testRaw.classAttribute().isNumeric())
+	throw new Exception("ClusterEvaluation: Class must be nominal!");
+
+      filter = new Remove();
+      ((Remove) filter).setAttributeIndices("" + (testRaw.classIndex() + 1));
+      ((Remove) filter).setInvertSelection(false);
+      filter.setInputFormat(testRaw);
+    }
+    
+    i = 0;
+    while (source.hasMoreElements(testRaw)) {
+      // next instance
+      inst = source.nextElement(testRaw);
+      if (filter != null) {
+	filter.input(inst);
+	filter.batchFinished();
+	inst = filter.output();
+      }
+      
+      cnum = -1;
+      try {
+	if (m_Clusterer instanceof DensityBasedClusterer) {
+	  loglk += ((DensityBasedClusterer)m_Clusterer).
+	    logDensityForInstance(inst);
+	  cnum = m_Clusterer.clusterInstance(inst); 
+	  clusterAssignments.add((double) cnum);
+	}
+	else {
+	  cnum = m_Clusterer.clusterInstance(inst);
+	  clusterAssignments.add((double) cnum);
+	}
+      }
+      catch (Exception e) {
+	clusterAssignments.add(-1.0);
+	unclusteredInstances++;
+      }
+      
+      if (cnum != -1) {
+	instanceStats[cnum]++;
+      }
+    }
+    
+    double sum = Utils.sum(instanceStats);
+    loglk /= sum;
+    m_logL = loglk;
+    m_clusterAssignments = new double [clusterAssignments.size()];
+    for (i = 0; i < clusterAssignments.size(); i++) {
+      m_clusterAssignments[i] = clusterAssignments.get(i);
+    }
+    int numInstFieldWidth = (int)((Math.log(clusterAssignments.size())/Math.log(10))+1);
+    
+    m_clusteringResults.append(m_Clusterer.toString());
+    m_clusteringResults.append("Clustered Instances\n\n");
+    int clustFieldWidth = (int)((Math.log(cc)/Math.log(10))+1);
+    for (i = 0; i < cc; i++) {
+      if (instanceStats[i] > 0)
+	m_clusteringResults.append(Utils.doubleToString((double)i, 
+							clustFieldWidth, 0) 
+				   + "      " 
+				   + Utils.doubleToString(instanceStats[i],
+							  numInstFieldWidth, 0) 
+				   + " (" 
+				   + Utils.doubleToString((instanceStats[i] / 
+							   sum * 100.0)
+							  , 3, 0) + "%)\n");
+    }
+    
+    if (unclusteredInstances > 0)
+      m_clusteringResults.append("\nUnclustered instances : "
+				 +unclusteredInstances);
+
+    if (m_Clusterer instanceof DensityBasedClusterer)
+      m_clusteringResults.append("\n\nLog likelihood: " 
+				 + Utils.doubleToString(loglk, 1, 5) 
+				 + "\n");       
+    
+    if (hasClass) {
+      evaluateClustersWithRespectToClass(test, testFileName);
+    }
+  }
+
+  /**
+   * Evaluates cluster assignments with respect to actual class labels.
+   * Assumes that m_Clusterer has been trained and tested on 
+   * inst (minus the class).
+   * 
+   * @param inst the instances (including class) to evaluate with respect to
+   * @param fileName the name of the test file for incremental testing, 
+   * if "" or null then not used
+   * @throws Exception if something goes wrong
+   */
+  private void evaluateClustersWithRespectToClass(Instances inst, String fileName)
+    throws Exception {
+    
+    
+    
+    int numClasses = inst.classAttribute().numValues();
+    int[][] counts = new int [m_numClusters][numClasses];
+    int[] clusterTotals = new int[m_numClusters];
+    double[] best = new double[m_numClusters+1];
+    double[] current = new double[m_numClusters+1];
+    DataSource source = null;
+    Instances instances = null;
+    Instance instance = null;
+    int i;
+    int numInstances;
+        
+
+    if (fileName == null)
+      fileName = "";
+    
+    if (fileName.length() != 0) {
+      source = new DataSource(fileName);
+    }
+    else
+      source = new DataSource(inst);
+    instances = source.getStructure(inst.classIndex());
+
+    i = 0;
+    while (source.hasMoreElements(instances)) {
+      instance = source.nextElement(instances);
+      if (m_clusterAssignments[i] >= 0) {
+        counts[(int)m_clusterAssignments[i]][(int)instance.classValue()]++;
+        clusterTotals[(int)m_clusterAssignments[i]]++;        
+      }
+      i++;
+    }
+    numInstances = i;
+   
+    best[m_numClusters] = Double.MAX_VALUE;
+    mapClasses(m_numClusters, 0, counts, clusterTotals, current, best, 0);
+
+    m_clusteringResults.append("\n\nClass attribute: "
+			+inst.classAttribute().name()
+			+"\n");
+    m_clusteringResults.append("Classes to Clusters:\n");
+    String matrixString = toMatrixString(counts, clusterTotals, new Instances(inst, 0));
+    m_clusteringResults.append(matrixString).append("\n");
+
+    int Cwidth = 1 + (int)(Math.log(m_numClusters) / Math.log(10));
+    // add the minimum error assignment
+    for (i = 0; i < m_numClusters; i++) {
+      if (clusterTotals[i] > 0) {
+	m_clusteringResults.append("Cluster "
+				   +Utils.doubleToString((double)i,Cwidth,0));
+	m_clusteringResults.append(" <-- ");
+	
+	if (best[i] < 0) {
+	  m_clusteringResults.append("No class\n");
+	} else {
+	  m_clusteringResults.
+	    append(inst.classAttribute().value((int)best[i])).append("\n");
+	}
+      }
+    }
+    m_clusteringResults.append("\nIncorrectly clustered instances :\t"
+			       +best[m_numClusters]+"\t"
+			       +(Utils.doubleToString((best[m_numClusters] / 
+						       numInstances * 
+						       100.0), 8, 4))
+			       +" %\n");
+
+    // copy the class assignments
+    m_classToCluster = new int [m_numClusters];
+    for (i = 0; i < m_numClusters; i++) {
+      m_classToCluster[i] = (int)best[i];
+    }
+  }
+
+  /**
+   * Returns a "confusion" style matrix of classes to clusters assignments
+   * @param counts the counts of classes for each cluster
+   * @param clusterTotals total number of examples in each cluster
+   * @param inst the training instances (with class)
+   * @return the "confusion" style matrix as string
+   * @throws Exception if matrix can't be generated
+   */
+  private String toMatrixString(int[][] counts, int[] clusterTotals,
+				Instances inst) 
+    throws Exception {
+    StringBuffer ms = new StringBuffer();
+
+    int maxval = 0;
+    for (int i = 0; i < m_numClusters; i++) {
+      for (int j = 0; j < counts[i].length; j++) {
+	if (counts[i][j] > maxval) {
+	  maxval = counts[i][j];
+	}
+      }
+    }
+
+    int Cwidth = 1 + Math.max((int)(Math.log(maxval) / Math.log(10)),
+			      (int)(Math.log(m_numClusters) / Math.log(10)));
+
+    ms.append("\n");
+    
+    for (int i = 0; i < m_numClusters; i++) {
+      if (clusterTotals[i] > 0) {
+	ms.append(" ").append(Utils.doubleToString((double)i, Cwidth, 0));
+      }
+    }
+    ms.append("  <-- assigned to cluster\n");
+    
+    for (int i = 0; i< counts[0].length; i++) {
+
+      for (int j = 0; j < m_numClusters; j++) {
+	if (clusterTotals[j] > 0) {
+	  ms.append(" ").append(Utils.doubleToString((double)counts[j][i], 
+						     Cwidth, 0));
+	}
+      }
+      ms.append(" | ").append(inst.classAttribute().value(i)).append("\n");
+    }
+
+    return ms.toString();
+  }
+
+  /**
+   * Finds the minimum error mapping of classes to clusters. Recursively
+   * considers all possible class to cluster assignments.
+   * 
+   * @param numClusters the number of clusters
+   * @param lev the cluster being processed
+   * @param counts the counts of classes in clusters
+   * @param clusterTotals the total number of examples in each cluster
+   * @param current the current path through the class to cluster assignment
+   * tree
+   * @param best the best assignment path seen
+   * @param error accumulates the error for a particular path
+   */
+  public static void mapClasses(int numClusters, int lev, int[][] counts, int[] clusterTotals,
+			  double[] current, double[] best, int error) {
+    // leaf
+    if (lev == numClusters) {
+      if (error < best[numClusters]) {
+	best[numClusters] = error;
+	for (int i = 0; i < numClusters; i++) {
+	  best[i] = current[i];
+	}
+      }
+    } else {
+      // empty cluster -- ignore
+      if (clusterTotals[lev] == 0) {
+	current[lev] = -1; // cluster ignored
+	mapClasses(numClusters, lev+1, counts, clusterTotals, current, best,
+		   error);
+      } else {
+	// first try no class assignment to this cluster
+	current[lev] = -1; // cluster assigned no class (ie all errors)
+	mapClasses(numClusters, lev+1, counts, clusterTotals, current, best,
+		   error+clusterTotals[lev]);
+	// now loop through the classes in this cluster
+	for (int i = 0; i < counts[0].length; i++) {
+	  if (counts[lev][i] > 0) {
+	    boolean ok = true;
+	    // check to see if this class has already been assigned
+	    for (int j = 0; j < lev; j++) {
+	      if ((int)current[j] == i) {
+		ok = false;
+		break;
+	      }
+	    }
+	    if (ok) {
+	      current[lev] = i;
+	      mapClasses(numClusters, lev+1, counts, clusterTotals, current, best, 
+			 (error + (clusterTotals[lev] - counts[lev][i])));
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Evaluates a clusterer with the options given in an array of
+   * strings. It takes the string indicated by "-t" as training file, the
+   * string indicated by "-T" as test file.
+   * If the test file is missing, a stratified ten-fold
+   * cross-validation is performed (distribution clusterers only).
+   * Using "-x" you can change the number of
+   * folds to be used, and using "-s" the random seed.
+   * If the "-p" option is present it outputs the classification for
+   * each test instance. If you provide the name of an object file using
+   * "-l", a clusterer will be loaded from the given file. If you provide the
+   * name of an object file using "-d", the clusterer built from the
+   * training data will be saved to the given file.
+   *
+   * @param clusterer machine learning clusterer
+   * @param options the array of string containing the options
+   * @throws Exception if model could not be evaluated successfully
+   * @return a string describing the results 
+   */
+  public static String evaluateClusterer(Clusterer clusterer, String[] options)
+    throws Exception {
+    
+    int seed = 1, folds = 10;
+    boolean doXval = false;
+    Instances train = null;
+    Random random;
+    String trainFileName, testFileName, seedString, foldsString;
+    String objectInputFileName, objectOutputFileName, attributeRangeString;
+    String graphFileName;
+    String[] savedOptions = null;
+    boolean printClusterAssignments = false;
+    Range attributesToOutput = null;
+    StringBuffer text = new StringBuffer();
+    int theClass = -1; // class based evaluation of clustering
+    boolean updateable = (clusterer instanceof UpdateableClusterer);
+    DataSource source = null;
+    Instance inst;
+
+    if (Utils.getFlag('h', options) || Utils.getFlag("help", options)) {
+      
+      // global info requested as well?
+      boolean globalInfo = Utils.getFlag("synopsis", options) ||
+        Utils.getFlag("info", options);
+      
+      throw  new Exception("Help requested." 
+          + makeOptionString(clusterer, globalInfo));
+    }
+    
+    try {
+      // Get basic options (options the same for all clusterers
+      //printClusterAssignments = Utils.getFlag('p', options);
+      objectInputFileName = Utils.getOption('l', options);
+      objectOutputFileName = Utils.getOption('d', options);
+      trainFileName = Utils.getOption('t', options);
+      testFileName = Utils.getOption('T', options);
+      graphFileName = Utils.getOption('g', options);
+
+      // Check -p option
+      try {
+	attributeRangeString = Utils.getOption('p', options);
+      }
+      catch (Exception e) {
+	throw new Exception(e.getMessage() + "\nNOTE: the -p option has changed. " +
+			    "It now expects a parameter specifying a range of attributes " +
+			    "to list with the predictions. Use '-p 0' for none.");
+      }
+      if (attributeRangeString.length() != 0) {
+	printClusterAssignments = true;
+	if (!attributeRangeString.equals("0")) 
+	  attributesToOutput = new Range(attributeRangeString);
+      }
+
+      if (trainFileName.length() == 0) {
+        if (objectInputFileName.length() == 0) {
+          throw  new Exception("No training file and no object " 
+			       + "input file given.");
+        }
+
+        if (testFileName.length() == 0) {
+          throw  new Exception("No training file and no test file given.");
+        }
+      }
+      else {
+	if ((objectInputFileName.length() != 0) 
+	    && (printClusterAssignments == false)) {
+	  throw  new Exception("Can't use both train and model file " 
+			       + "unless -p specified.");
+	}
+      }
+
+      seedString = Utils.getOption('s', options);
+
+      if (seedString.length() != 0) {
+	seed = Integer.parseInt(seedString);
+      }
+
+      foldsString = Utils.getOption('x', options);
+
+      if (foldsString.length() != 0) {
+	folds = Integer.parseInt(foldsString);
+	doXval = true;
+      }
+    }
+    catch (Exception e) {
+      throw  new Exception('\n' + e.getMessage() 
+			   + makeOptionString(clusterer, false));
+    }
+
+    try {
+      if (trainFileName.length() != 0) {
+	source = new DataSource(trainFileName);
+	train  = source.getStructure();
+
+	String classString = Utils.getOption('c',options);
+	if (classString.length() != 0) {
+	  if (classString.compareTo("last") == 0)
+	    theClass = train.numAttributes();
+	  else if (classString.compareTo("first") == 0)
+	    theClass = 1;
+	  else
+	    theClass = Integer.parseInt(classString);
+
+	  if (theClass != -1) {
+	    if (doXval || testFileName.length() != 0)
+	      throw new Exception("Can only do class based evaluation on the "
+		  +"training data");
+
+	    if (objectInputFileName.length() != 0)
+	      throw new Exception("Can't load a clusterer and do class based "
+		  +"evaluation");
+
+	    if (objectOutputFileName.length() != 0)
+	      throw new Exception(
+		  "Can't do class based evaluation and save clusterer");
+	  }
+	}
+	else {
+	  // if the dataset defines a class attribute, use it
+	  if (train.classIndex() != -1) {
+	    theClass = train.classIndex() + 1;
+	    System.err.println(
+		"Note: using class attribute from dataset, i.e., attribute #" 
+		+ theClass);
+	  }
+	}
+
+	if (theClass != -1) {
+	  if (theClass < 1 || theClass > train.numAttributes())
+	    throw new Exception("Class is out of range!");
+
+	  if (!train.attribute(theClass - 1).isNominal())
+	    throw new Exception("Class must be nominal!");
+	  
+	  train.setClassIndex(theClass - 1);
+	}
+      }
+    }
+    catch (Exception e) {
+      throw  new Exception("ClusterEvaluation: " + e.getMessage() + '.');
+    }
+
+    // Save options
+    if (options != null) {
+      savedOptions = new String[options.length];
+      System.arraycopy(options, 0, savedOptions, 0, options.length);
+    }
+
+    if (objectInputFileName.length() != 0)
+      Utils.checkForRemainingOptions(options);
+
+    // Set options for clusterer
+    if (clusterer instanceof OptionHandler)
+      ((OptionHandler)clusterer).setOptions(options);
+
+    Utils.checkForRemainingOptions(options);
+
+    Instances trainHeader = train;
+    if (objectInputFileName.length() != 0) {
+      // Load the clusterer from file
+      //      clusterer = (Clusterer) SerializationHelper.read(objectInputFileName);
+      java.io.ObjectInputStream ois = 
+        new java.io.ObjectInputStream(
+        new java.io.BufferedInputStream(
+        new java.io.FileInputStream(objectInputFileName)));
+      clusterer = (Clusterer) ois.readObject();
+      // try and get the training header
+      try {
+        trainHeader = (Instances) ois.readObject();
+      } catch (Exception ex) {
+        // don't moan if we cant
+      }
+    }
+    else {
+      // Build the clusterer if no object file provided
+      if (theClass == -1) {
+	if (updateable) {
+	  clusterer.buildClusterer(source.getStructure());
+	  while (source.hasMoreElements(train)) {
+	    inst = source.nextElement(train);
+	    ((UpdateableClusterer) clusterer).updateClusterer(inst);
+	  }
+	  ((UpdateableClusterer) clusterer).updateFinished();
+	}
+	else {
+	  clusterer.buildClusterer(source.getDataSet());
+	}
+      }
+      else {
+	Remove removeClass = new Remove();
+	removeClass.setAttributeIndices("" + theClass);
+	removeClass.setInvertSelection(false);
+	removeClass.setInputFormat(train);
+	if (updateable) {
+	  Instances clusterTrain = Filter.useFilter(train, removeClass);
+	  clusterer.buildClusterer(clusterTrain);
+          trainHeader = clusterTrain;
+	  while (source.hasMoreElements(train)) {
+	    inst = source.nextElement(train);
+	    removeClass.input(inst);
+	    removeClass.batchFinished();
+	    Instance clusterTrainInst = removeClass.output();
+	    ((UpdateableClusterer) clusterer).updateClusterer(clusterTrainInst);
+	  }
+	  ((UpdateableClusterer) clusterer).updateFinished();
+	}
+	else {
+	  Instances clusterTrain = Filter.useFilter(source.getDataSet(), removeClass);
+	  clusterer.buildClusterer(clusterTrain);
+          trainHeader = clusterTrain;
+	}
+	ClusterEvaluation ce = new ClusterEvaluation();
+	ce.setClusterer(clusterer);
+	ce.evaluateClusterer(train, trainFileName);
+	
+	return "\n\n=== Clustering stats for training data ===\n\n" +
+	  ce.clusterResultsToString();
+      }
+    }
+
+    /* Output cluster predictions only (for the test data if specified,
+       otherwise for the training data */
+    if (printClusterAssignments) {
+      return printClusterings(clusterer, trainFileName, testFileName, attributesToOutput);
+    }
+
+    text.append(clusterer.toString());
+    text.append("\n\n=== Clustering stats for training data ===\n\n" 
+		+ printClusterStats(clusterer, trainFileName));
+
+    if (testFileName.length() != 0) {
+      // check header compatibility
+      DataSource test = new DataSource(testFileName);
+      Instances testStructure = test.getStructure();
+      if (!trainHeader.equalHeaders(testStructure)) {
+        throw new Exception("Training and testing data are not compatible\n" + trainHeader.equalHeadersMsg(testStructure));
+      }
+
+      text.append("\n\n=== Clustering stats for testing data ===\n\n" 
+		  + printClusterStats(clusterer, testFileName));
+    }
+
+    if ((clusterer instanceof DensityBasedClusterer) && 
+	(doXval == true) && 
+	(testFileName.length() == 0) && 
+	(objectInputFileName.length() == 0)) {
+      // cross validate the log likelihood on the training data
+      random = new Random(seed);
+      random.setSeed(seed);
+      train = source.getDataSet();
+      train.randomize(random);
+      text.append(
+	  crossValidateModel(
+	      clusterer.getClass().getName(), train, folds, savedOptions, random));
+    }
+
+    // Save the clusterer if an object output file is provided
+    if (objectOutputFileName.length() != 0) {
+      //SerializationHelper.write(objectOutputFileName, clusterer);
+      saveClusterer(objectOutputFileName, clusterer, trainHeader);
+    }
+
+    // If classifier is drawable output string describing graph
+    if ((clusterer instanceof Drawable) && (graphFileName.length() != 0)) {
+      BufferedWriter writer = new BufferedWriter(new FileWriter(graphFileName));
+      writer.write(((Drawable) clusterer).graph());
+      writer.newLine();
+      writer.flush();
+      writer.close();
+    }
+    
+    return  text.toString();
+  }
+
+  private static void saveClusterer(String fileName, 
+                             Clusterer clusterer, 
+                             Instances header) throws Exception {
+    java.io.ObjectOutputStream oos = 
+      new java.io.ObjectOutputStream(
+      new java.io.BufferedOutputStream(
+      new java.io.FileOutputStream(fileName)));
+
+    oos.writeObject(clusterer);
+    if (header != null) {
+      oos.writeObject(header);
+    }
+    oos.flush();
+    oos.close();
+  }
+
+  /**
+   * Perform a cross-validation for DensityBasedClusterer on a set of instances.
+   *
+   * @param clusterer the clusterer to use
+   * @param data the training data
+   * @param numFolds number of folds of cross validation to perform
+   * @param random random number seed for cross-validation
+   * @return the cross-validated log-likelihood
+   * @throws Exception if an error occurs
+   */
+  public static double crossValidateModel(DensityBasedClusterer clusterer,
+					  Instances data,
+					  int numFolds,
+					  Random random) throws Exception {
+    Instances train, test;
+    double foldAv = 0;;
+    data = new Instances(data);
+    data.randomize(random);
+    //    double sumOW = 0;
+    for (int i = 0; i < numFolds; i++) {
+      // Build and test clusterer
+      train = data.trainCV(numFolds, i, random);
+
+      clusterer.buildClusterer(train);
+
+      test = data.testCV(numFolds, i);
+      
+      for (int j = 0; j < test.numInstances(); j++) {
+	try {
+	  foldAv += ((DensityBasedClusterer)clusterer).
+	    logDensityForInstance(test.instance(j));
+	  //	  sumOW += test.instance(j).weight();
+	  //	double temp = Utils.sum(tempDist);
+	} catch (Exception ex) {
+	  // unclustered instances
+	}
+      }
+    }
+   
+    //    return foldAv / sumOW;
+    return foldAv / data.numInstances();
+  }
+
+  /**
+   * Performs a cross-validation 
+   * for a DensityBasedClusterer clusterer on a set of instances.
+   *
+   * @param clustererString a string naming the class of the clusterer
+   * @param data the data on which the cross-validation is to be 
+   * performed 
+   * @param numFolds the number of folds for the cross-validation
+   * @param options the options to the clusterer
+   * @param random a random number generator
+   * @return a string containing the cross validated log likelihood
+   * @throws Exception if a clusterer could not be generated 
+   */
+  public static String crossValidateModel (String clustererString, 
+					   Instances data, 
+					   int numFolds, 
+					   String[] options,
+					   Random random)
+    throws Exception {
+    Clusterer clusterer = null;
+    String[] savedOptions = null;
+    double CvAv = 0.0;
+    StringBuffer CvString = new StringBuffer();
+
+    if (options != null) {
+      savedOptions = new String[options.length];
+    }
+
+    data = new Instances(data);
+
+    // create clusterer
+    try {
+      clusterer = (Clusterer)Class.forName(clustererString).newInstance();
+    }
+    catch (Exception e) {
+      throw  new Exception("Can't find class with name " 
+			   + clustererString + '.');
+    }
+
+    if (!(clusterer instanceof DensityBasedClusterer)) {
+      throw  new Exception(clustererString 
+			   + " must be a distrinbution " 
+			   + "clusterer.");
+    }
+
+    // Save options
+    if (options != null) {
+      System.arraycopy(options, 0, savedOptions, 0, options.length);
+    }
+
+    // Parse options
+    if (clusterer instanceof OptionHandler) {
+      try {
+	((OptionHandler)clusterer).setOptions(savedOptions);
+	Utils.checkForRemainingOptions(savedOptions);
+      }
+      catch (Exception e) {
+	throw  new Exception("Can't parse given options in " 
+			     + "cross-validation!");
+      }
+    }
+    CvAv = crossValidateModel((DensityBasedClusterer)clusterer, data, numFolds, random);
+
+    CvString.append("\n" + numFolds 
+		    + " fold CV Log Likelihood: " 
+		    + Utils.doubleToString(CvAv, 6, 4) 
+		    + "\n");
+    return  CvString.toString();
+  }
+
+
+  // ===============
+  // Private methods
+  // ===============
+  /**
+   * Print the cluster statistics for either the training
+   * or the testing data.
+   *
+   * @param clusterer the clusterer to use for generating statistics.
+   * @param fileName the file to load
+   * @return a string containing cluster statistics.
+   * @throws Exception if statistics can't be generated.
+   */
+  private static String printClusterStats (Clusterer clusterer, 
+					   String fileName)
+    throws Exception {
+    StringBuffer text = new StringBuffer();
+    int i = 0;
+    int cnum;
+    double loglk = 0.0;
+    int cc = clusterer.numberOfClusters();
+    double[] instanceStats = new double[cc];
+    int unclusteredInstances = 0;
+
+    if (fileName.length() != 0) {
+      DataSource source = new DataSource(fileName);
+      Instances structure = source.getStructure();
+      Instance inst;
+      while (source.hasMoreElements(structure)) {
+	inst = source.nextElement(structure);
+	try {
+	  cnum = clusterer.clusterInstance(inst);
+
+	  if (clusterer instanceof DensityBasedClusterer) {
+	    loglk += ((DensityBasedClusterer)clusterer).
+	      logDensityForInstance(inst);
+	    //	    temp = Utils.sum(dist);
+	  }
+	  instanceStats[cnum]++;
+	}
+	catch (Exception e) {
+	  unclusteredInstances++;
+	}
+	i++;
+      }
+
+      /*
+      // count the actual number of used clusters
+      int count = 0;
+      for (i = 0; i < cc; i++) {
+	if (instanceStats[i] > 0) {
+	  count++;
+	}
+      }
+      if (count > 0) {
+	double[] tempStats = new double [count];
+	count=0;
+	for (i=0;i<cc;i++) {
+	  if (instanceStats[i] > 0) {
+	    tempStats[count++] = instanceStats[i];
+	}
+	}
+	instanceStats = tempStats;
+	cc = instanceStats.length;
+	} */
+
+      int clustFieldWidth = (int)((Math.log(cc)/Math.log(10))+1);
+      int numInstFieldWidth = (int)((Math.log(i)/Math.log(10))+1);
+      double sum = Utils.sum(instanceStats);
+      loglk /= sum;
+      text.append("Clustered Instances\n");
+
+      for (i = 0; i < cc; i++) {
+	if (instanceStats[i] > 0) {
+	  text.append(Utils.doubleToString((double)i, 
+					   clustFieldWidth, 0) 
+		      + "      " 
+		      + Utils.doubleToString(instanceStats[i], 
+					     numInstFieldWidth, 0) 
+		      + " (" 
+		    + Utils.doubleToString((instanceStats[i]/sum*100.0)
+					   , 3, 0) + "%)\n");
+	}
+      }
+      if (unclusteredInstances > 0) {
+	text.append("\nUnclustered Instances : "+unclusteredInstances);
+      }
+
+      if (clusterer instanceof DensityBasedClusterer) {
+	text.append("\n\nLog likelihood: " 
+		    + Utils.doubleToString(loglk, 1, 5) 
+		    + "\n");
+      }
+    }
+
+    return text.toString();
+  }
+
+
+  /**
+   * Print the cluster assignments for either the training
+   * or the testing data.
+   *
+   * @param clusterer the clusterer to use for cluster assignments
+   * @param trainFileName the train file
+   * @param testFileName an optional test file
+   * @param attributesToOutput the attributes to print
+   * @return a string containing the instance indexes and cluster assigns.
+   * @throws Exception if cluster assignments can't be printed
+   */
+  private static String printClusterings (Clusterer clusterer, String trainFileName,
+					  String testFileName, Range attributesToOutput)
+    throws Exception {
+
+    StringBuffer text = new StringBuffer();
+    int i = 0;
+    int cnum;
+    DataSource source = null;
+    Instance inst;
+    Instances structure;
+    
+    if (testFileName.length() != 0)
+      source = new DataSource(testFileName);
+    else
+      source = new DataSource(trainFileName);
+    
+    structure = source.getStructure();
+    while (source.hasMoreElements(structure)) {
+      inst = source.nextElement(structure);
+      try {
+	cnum = clusterer.clusterInstance(inst);
+	
+	text.append(i + " " + cnum + " "
+	    + attributeValuesString(inst, attributesToOutput) + "\n");
+      }
+      catch (Exception e) {
+	/*	  throw  new Exception('\n' + "Unable to cluster instance\n" 
+	 + e.getMessage()); */
+	text.append(i + " Unclustered "
+	    + attributeValuesString(inst, attributesToOutput) + "\n");
+      }
+      i++;
+    }
+    
+    return text.toString();
+  }
+
+  /**
+   * Builds a string listing the attribute values in a specified range of indices,
+   * separated by commas and enclosed in brackets.
+   *
+   * @param instance the instance to print the values from
+   * @param attRange the range of the attributes to list
+   * @return a string listing values of the attributes in the range
+   */
+  private static String attributeValuesString(Instance instance, Range attRange) {
+    StringBuffer text = new StringBuffer();
+    if (attRange != null) {
+      boolean firstOutput = true;
+      attRange.setUpper(instance.numAttributes() - 1);
+      for (int i=0; i<instance.numAttributes(); i++)
+	if (attRange.isInRange(i)) {
+	  if (firstOutput) text.append("(");
+	  else text.append(",");
+	  text.append(instance.toString(i));
+	  firstOutput = false;
+	}
+      if (!firstOutput) text.append(")");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Make up the help string giving all the command line options
+   *
+   * @param clusterer the clusterer to include options for
+   * @return a string detailing the valid command line options
+   */
+  private static String makeOptionString (Clusterer clusterer,
+                                          boolean globalInfo) {
+    StringBuffer optionsText = new StringBuffer("");
+    // General options
+    optionsText.append("\n\nGeneral options:\n\n");
+    optionsText.append("-h or -help\n");
+    optionsText.append("\tOutput help information.\n");
+    optionsText.append("-synopsis or -info\n");
+    optionsText.append("\tOutput synopsis for clusterer (use in conjunction "
+        + " with -h)\n");
+    optionsText.append("-t <name of training file>\n");
+    optionsText.append("\tSets training file.\n");
+    optionsText.append("-T <name of test file>\n");
+    optionsText.append("\tSets test file.\n");
+    optionsText.append("-l <name of input file>\n");
+    optionsText.append("\tSets model input file.\n");
+    optionsText.append("-d <name of output file>\n");
+    optionsText.append("\tSets model output file.\n");
+    optionsText.append("-p <attribute range>\n");
+    optionsText.append("\tOutput predictions. Predictions are for " 
+		       + "training file" 
+		       + "\n\tif only training file is specified," 
+		       + "\n\totherwise predictions are for the test file."
+		       + "\n\tThe range specifies attribute values to be output"
+		       + "\n\twith the predictions. Use '-p 0' for none.\n");
+    optionsText.append("-x <number of folds>\n");
+    optionsText.append("\tOnly Distribution Clusterers can be cross validated.\n");
+    optionsText.append("-s <random number seed>\n");
+    optionsText.append("\tSets the seed for randomizing the data in cross-validation\n");
+    optionsText.append("-c <class index>\n");
+    optionsText.append("\tSet class attribute. If supplied, class is ignored");
+    optionsText.append("\n\tduring clustering but is used in a classes to");
+    optionsText.append("\n\tclusters evaluation.\n");
+    if (clusterer instanceof Drawable) {
+      optionsText.append("-g <name of graph file>\n");
+      optionsText.append("\tOutputs the graph representation of the clusterer to the file.\n");
+    }
+
+    // Get scheme-specific options
+    if (clusterer instanceof OptionHandler) {
+      optionsText.append("\nOptions specific to " 
+			 + clusterer.getClass().getName() + ":\n\n");
+      Enumeration enu = ((OptionHandler)clusterer).listOptions();
+
+      while (enu.hasMoreElements()) {
+	Option option = (Option)enu.nextElement();
+	optionsText.append(option.synopsis() + '\n');
+	optionsText.append(option.description() + "\n");
+      }
+    }
+    
+    // Get global information (if available)
+    if (globalInfo) {
+      try {
+        String gi = getGlobalInfo(clusterer);
+        optionsText.append(gi);
+      } catch (Exception ex) {
+        // quietly ignore
+      }
+    }
+
+    return  optionsText.toString();
+  }
+  
+  /**
+   * Return the global info (if it exists) for the supplied clusterer
+   * 
+   * @param clusterer the clusterer to get the global info for
+   * @return the global info (synopsis) for the clusterer
+   * @throws Exception if there is a problem reflecting on the clusterer
+   */
+  protected static String getGlobalInfo(Clusterer clusterer) throws Exception {
+    BeanInfo bi = Introspector.getBeanInfo(clusterer.getClass());
+    MethodDescriptor[] methods;
+    methods = bi.getMethodDescriptors();
+    Object[] args = {};
+    String result = "\nSynopsis for " + clusterer.getClass().getName()
+      + ":\n\n";
+    
+    for (int i = 0; i < methods.length; i++) {
+      String name = methods[i].getDisplayName();
+      Method meth = methods[i].getMethod();
+      if (name.equals("globalInfo")) {
+        String globalInfo = (String)(meth.invoke(clusterer, args));
+        result += globalInfo;
+        break;
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tests whether the current evaluation object is equal to another
+   * evaluation object
+   *
+   * @param obj the object to compare against
+   * @return true if the two objects are equal
+   */
+  public boolean equals(Object obj) {
+    if ((obj == null) || !(obj.getClass().equals(this.getClass())))
+      return false;
+    
+    ClusterEvaluation cmp = (ClusterEvaluation) obj;
+    
+    if ((m_classToCluster != null) != (cmp.m_classToCluster != null)) return false;
+    if (m_classToCluster != null) {
+      for (int i = 0; i < m_classToCluster.length; i++) {
+        if (m_classToCluster[i] != cmp.m_classToCluster[i])
+  	return false;
+      }
+    }
+    
+    if ((m_clusterAssignments != null) != (cmp.m_clusterAssignments != null)) return false;
+    if (m_clusterAssignments != null) {
+      for (int i = 0; i < m_clusterAssignments.length; i++) {
+        if (m_clusterAssignments[i] != cmp.m_clusterAssignments[i])
+  	return false;
+      }
+    }
+
+    if (Double.isNaN(m_logL) != Double.isNaN(cmp.m_logL)) return false;
+    if (!Double.isNaN(m_logL)) {
+      if (m_logL != cmp.m_logL) return false;
+    }
+    
+    if (m_numClusters != cmp.m_numClusters) return false;
+    
+    // TODO: better comparison? via members?
+    String clusteringResults1 = m_clusteringResults.toString().replaceAll("Elapsed time.*", "");
+    String clusteringResults2 = cmp.m_clusteringResults.toString().replaceAll("Elapsed time.*", "");
+    if (!clusteringResults1.equals(clusteringResults2)) return false;
+    
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6021 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args the options
+   */
+  public static void main (String[] args) {
+    try {
+      if (args.length == 0) {
+	throw  new Exception("The first argument must be the name of a " 
+			     + "clusterer");
+      }
+
+      String ClustererString = args[0];
+      args[0] = "";
+      Clusterer newClusterer = AbstractClusterer.forName(ClustererString, null);
+      System.out.println(evaluateClusterer(newClusterer, args));
+    }
+    catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/Clusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/Clusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/Clusterer.java	(revision 29)
@@ -0,0 +1,90 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Clusterer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+
+/**
+ * Interface for clusterers. Clients will typically extend either
+ * AbstractClusterer or AbstractDensityBasedClusterer.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public interface Clusterer {
+
+  /**
+   * Generates a clusterer. Has to initialize all fields of the clusterer
+   * that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @exception Exception if the clusterer has not been 
+   * generated successfully
+   */
+  void buildClusterer(Instances data) throws Exception;
+
+  /**
+   * Classifies a given instance. Either this or distributionForInstance()
+   * needs to be implemented by subclasses.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an integer
+   * @exception Exception if instance could not be clustered
+   * successfully
+   */
+  int clusterInstance(Instance instance) throws Exception;
+
+  /**
+   * Predicts the cluster memberships for a given instance.  Either
+   * this or clusterInstance() needs to be implemented by subclasses.
+   *
+   * @param instance the instance to be assigned a cluster.
+   * @return an array containing the estimated membership 
+   * probabilities of the test instance in each cluster (this 
+   * should sum to at most 1)
+   * @exception Exception if distribution could not be 
+   * computed successfully 
+   */
+  public double[] distributionForInstance(Instance instance) throws Exception;
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @exception Exception if number of clusters could not be returned
+   * successfully
+   */
+  int numberOfClusters() throws Exception;
+
+  /** 
+   * Returns the Capabilities of this clusterer. Derived classifiers have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities();
+  
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/Cobweb.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/Cobweb.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/Cobweb.java	(revision 29)
@@ -0,0 +1,1258 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Cobweb.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.experiment.Stats;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Add;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the Cobweb and Classit clustering algorithms.<br/>
+ * <br/>
+ * Note: the application of node operators (merging, splitting etc.) in terms of ordering and priority differs (and is somewhat ambiguous) between the original Cobweb and Classit papers. This algorithm always compares the best host, adding a new leaf, merging the two best hosts, and splitting the best host when considering where to place a new instance.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * D. Fisher (1987). Knowledge acquisition via incremental conceptual clustering. Machine Learning. 2(2):139-172.<br/>
+ * <br/>
+ * J. H. Gennari, P. Langley, D. Fisher (1990). Models of incremental concept formation. Artificial Intelligence. 40:11-61.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Fisher1987,
+ *    author = {D. Fisher},
+ *    journal = {Machine Learning},
+ *    number = {2},
+ *    pages = {139-172},
+ *    title = {Knowledge acquisition via incremental conceptual clustering},
+ *    volume = {2},
+ *    year = {1987}
+ * }
+ * 
+ * &#64;article{Gennari1990,
+ *    author = {J. H. Gennari and P. Langley and D. Fisher},
+ *    journal = {Artificial Intelligence},
+ *    pages = {11-61},
+ *    title = {Models of incremental concept formation},
+ *    volume = {40},
+ *    year = {1990}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;acuity&gt;
+ *  Acuity.
+ *  (default=1.0)</pre>
+ * 
+ * <pre> -C &lt;cutoff&gt;
+ *  Cutoff.
+ *  (default=0.002)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 42)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5488 $
+ * @see RandomizableClusterer
+ * @see Drawable
+ */
+public class Cobweb 
+  extends RandomizableClusterer
+  implements Drawable, TechnicalInformationHandler, UpdateableClusterer {
+
+  /** for serialization */
+  static final long serialVersionUID = 928406656495092318L;
+  
+  /**
+   * Inner class handling node operations for Cobweb.
+   *
+   * @see Serializable
+   */
+  private class CNode 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 3452097436933325631L;    
+    /**
+     * Within cluster attribute statistics
+     */
+    private AttributeStats[] m_attStats;
+
+    /**
+     * Number of attributes
+     */
+    private int m_numAttributes;
+    
+    /**
+     * Instances at this node
+     */
+    protected Instances m_clusterInstances = null;
+
+    /**
+     * Children of this node
+     */
+    private FastVector m_children = null;
+
+    /**
+     * Total instances at this node
+     */
+    private double m_totalInstances = 0.0;
+
+    /**
+     * Cluster number of this node
+     */
+    private int m_clusterNum = -1;
+
+    /**
+     * Creates an empty <code>CNode</code> instance.
+     *
+     * @param numAttributes the number of attributes in the data
+     */
+    public CNode(int numAttributes) {      
+      m_numAttributes = numAttributes;
+    }
+
+    /**
+     * Creates a new leaf <code>CNode</code> instance.
+     *
+     * @param numAttributes the number of attributes in the data
+     * @param leafInstance the instance to store at this leaf
+     */
+    public CNode(int numAttributes, Instance leafInstance) {
+      this(numAttributes);
+      if (m_clusterInstances == null) {
+	m_clusterInstances = new Instances(leafInstance.dataset(), 1);
+      }
+      m_clusterInstances.add(leafInstance);
+      updateStats(leafInstance, false);
+    }
+    
+    /**
+     * Adds an instance to this cluster.
+     *
+     * @param newInstance the instance to add
+     * @throws Exception if an error occurs
+     */
+    protected void addInstance(Instance newInstance) throws Exception {
+      // Add the instance to this cluster
+
+      if (m_clusterInstances == null) {
+	m_clusterInstances = new Instances(newInstance.dataset(), 1);
+	m_clusterInstances.add(newInstance);
+	updateStats(newInstance, false);
+	return;
+      } else if (m_children == null) {
+	/* we are a leaf, so make our existing instance(s) into a child
+	   and then add the new instance as a child */
+	m_children = new FastVector();
+	CNode tempSubCluster = new CNode(m_numAttributes, 
+					 m_clusterInstances.instance(0)); 
+
+	//	System.out.println("Dumping "+m_clusterInstances.numInstances());
+	for (int i = 1; i < m_clusterInstances.numInstances(); i++) {
+	  tempSubCluster.m_clusterInstances.
+	    add(m_clusterInstances.instance(i));
+	  tempSubCluster.updateStats(m_clusterInstances.instance(i), false);
+	}
+	m_children = new FastVector();
+	m_children.addElement(tempSubCluster);
+	m_children.addElement(new CNode(m_numAttributes, newInstance));
+	
+	m_clusterInstances.add(newInstance);
+	updateStats(newInstance, false);
+
+	// here is where we check against cutoff (also check cutoff
+	// in findHost)
+	if (categoryUtility() < m_cutoff) {
+	  //	  System.out.println("Cutting (leaf add) ");
+	  m_children = null;
+	}
+	return;
+      }
+      
+      // otherwise, find the best host for this instance
+      CNode bestHost = findHost(newInstance, false);
+      if (bestHost != null) {	
+	// now add to the best host
+	bestHost.addInstance(newInstance);
+      }
+    }
+
+    /**
+     * Temporarily adds a new instance to each of this nodes children
+     * in turn and computes the category utility.
+     *
+     * @param newInstance the new instance to evaluate
+     * @return an array of category utility values---the result of considering
+     * each child in turn as a host for the new instance
+     * @throws Exception if an error occurs
+     */
+    private double[] cuScoresForChildren(Instance newInstance) 
+      throws Exception {
+      // look for a host in existing children
+      double[] categoryUtils = new double [m_children.size()];
+      
+      // look for a home for this instance in the existing children
+      for (int i = 0; i < m_children.size(); i++) {
+	CNode temp = (CNode) m_children.elementAt(i);
+	// tentitively add the new instance to this child
+	temp.updateStats(newInstance, false);
+	categoryUtils[i] = categoryUtility();
+	
+	// remove the new instance from this child
+	temp.updateStats(newInstance, true);
+      }
+      return categoryUtils;
+    }
+
+    private double cuScoreForBestTwoMerged(CNode merged, 
+					   CNode a, CNode b,
+					   Instance newInstance) 
+      throws Exception {
+
+      double mergedCU = -Double.MAX_VALUE;
+      // consider merging the best and second
+      // best.
+      merged.m_clusterInstances = new Instances(m_clusterInstances, 1);
+      
+      merged.addChildNode(a);
+      merged.addChildNode(b);
+      merged.updateStats(newInstance, false); // add new instance to stats
+      // remove the best and second best nodes
+      m_children.removeElementAt(m_children.indexOf(a));
+      m_children.removeElementAt(m_children.indexOf(b));	
+      m_children.addElement(merged);
+      mergedCU = categoryUtility();
+      // restore the status quo
+      merged.updateStats(newInstance, true);
+      m_children.removeElementAt(m_children.indexOf(merged));
+      m_children.addElement(a);
+      m_children.addElement(b);
+      return mergedCU;
+    }
+
+    /**
+     * Finds a host for the new instance in this nodes children. Also
+     * considers merging the two best hosts and splitting the best host.
+     *
+     * @param newInstance the instance to find a host for
+     * @param structureFrozen true if the instance is not to be added to
+     * the tree and instead the best potential host is to be returned
+     * @return the best host
+     * @throws Exception if an error occurs
+     */
+    private CNode findHost(Instance newInstance, 
+			   boolean structureFrozen) throws Exception {
+
+      if (!structureFrozen) {
+	updateStats(newInstance, false);
+      }
+      
+      // look for a host in existing children and also consider as a new leaf
+      double[] categoryUtils = cuScoresForChildren(newInstance);
+      
+      // make a temporary new leaf for this instance and get CU
+      CNode newLeaf = new CNode(m_numAttributes, newInstance);
+      m_children.addElement(newLeaf);
+      double bestHostCU = categoryUtility();
+      CNode finalBestHost = newLeaf;
+      
+      // remove new leaf when seaching for best and second best nodes to
+      // consider for merging and splitting
+      m_children.removeElementAt(m_children.size()-1);
+
+      // now determine the best host (and the second best)
+      int best = 0;
+      int secondBest = 0;
+      for (int i = 0; i < categoryUtils.length; i++) {
+	if (categoryUtils[i] > categoryUtils[secondBest]) {
+	  if (categoryUtils[i] > categoryUtils[best]) {
+	    secondBest = best;
+	    best = i;
+	  } else {
+	    secondBest = i;
+	  }
+	} 
+      }
+      
+      CNode a = (CNode) m_children.elementAt(best);
+      CNode b = (CNode) m_children.elementAt(secondBest);
+      if (categoryUtils[best] > bestHostCU) {
+	bestHostCU = categoryUtils[best];
+	finalBestHost = a;
+	//	System.out.println("Node is best");
+      }
+
+      if (structureFrozen) {
+	if (finalBestHost == newLeaf) {
+	  return null; // *this* node is the best host
+	} else {
+	  return finalBestHost;
+	}
+      }
+
+      double mergedCU = -Double.MAX_VALUE;
+      CNode merged = new CNode(m_numAttributes);
+      if (a != b) {
+	mergedCU = cuScoreForBestTwoMerged(merged, a, b, newInstance);
+
+	if (mergedCU > bestHostCU) {
+	  bestHostCU = mergedCU;
+	  finalBestHost = merged;
+	}
+      }
+
+      // Consider splitting the best
+      double splitCU = -Double.MAX_VALUE;
+      double splitBestChildCU = -Double.MAX_VALUE;
+      double splitPlusNewLeafCU = -Double.MAX_VALUE;
+      double splitPlusMergeBestTwoCU = -Double.MAX_VALUE;
+      if (a.m_children != null) {
+	FastVector tempChildren = new FastVector();
+
+	for (int i = 0; i < m_children.size(); i++) {
+	  CNode existingChild = (CNode)m_children.elementAt(i);
+	  if (existingChild != a) {
+	    tempChildren.addElement(existingChild);
+	  }
+	}
+	for (int i = 0; i < a.m_children.size(); i++) {
+	  CNode promotedChild = (CNode)a.m_children.elementAt(i);
+	  tempChildren.addElement(promotedChild);
+	}
+	// also add the new leaf
+	tempChildren.addElement(newLeaf);
+
+	FastVector saveStatusQuo = m_children;
+	m_children = tempChildren;
+	splitPlusNewLeafCU = categoryUtility(); // split + new leaf
+	// remove the new leaf
+	tempChildren.removeElementAt(tempChildren.size()-1);
+	// now look for best and second best
+	categoryUtils = cuScoresForChildren(newInstance);
+
+	// now determine the best host (and the second best)
+	best = 0;
+	secondBest = 0;
+	for (int i = 0; i < categoryUtils.length; i++) {
+	  if (categoryUtils[i] > categoryUtils[secondBest]) {
+	    if (categoryUtils[i] > categoryUtils[best]) {
+	      secondBest = best;
+	      best = i;
+	    } else {
+	      secondBest = i;
+	    }
+	  } 
+	}
+	CNode sa = (CNode) m_children.elementAt(best);
+	CNode sb = (CNode) m_children.elementAt(secondBest);
+	splitBestChildCU = categoryUtils[best];
+
+	// now merge best and second best
+	CNode mergedSplitChildren = new CNode(m_numAttributes);
+	if (sa != sb) {
+	  splitPlusMergeBestTwoCU = 
+	    cuScoreForBestTwoMerged(mergedSplitChildren, sa, sb, newInstance);
+	}
+	splitCU = (splitBestChildCU > splitPlusNewLeafCU) ?
+	  splitBestChildCU : splitPlusNewLeafCU;
+	splitCU = (splitCU > splitPlusMergeBestTwoCU) ? 
+	  splitCU : splitPlusMergeBestTwoCU;
+
+	if (splitCU > bestHostCU) {
+	  bestHostCU = splitCU;
+	  finalBestHost = this;
+	  //	  tempChildren.removeElementAt(tempChildren.size()-1);
+	} else {
+	  // restore the status quo
+	  m_children = saveStatusQuo;
+	}
+      }
+
+      if (finalBestHost != this) {
+	// can commit the instance to the set of instances at this node
+	m_clusterInstances.add(newInstance);
+      } else {
+	m_numberSplits++;
+      }
+
+      if (finalBestHost == merged) {
+	m_numberMerges++;
+	m_children.removeElementAt(m_children.indexOf(a));
+	m_children.removeElementAt(m_children.indexOf(b));	
+	m_children.addElement(merged);
+      }
+
+      if (finalBestHost == newLeaf) {
+	finalBestHost = new CNode(m_numAttributes);
+	m_children.addElement(finalBestHost);
+      }
+
+      if (bestHostCU < m_cutoff) {
+	if (finalBestHost == this) {
+	  // splitting was the best, but since we are cutting all children
+	  // recursion is aborted and we still need to add the instance
+	  // to the set of instances at this node
+	  m_clusterInstances.add(newInstance);
+	}
+	m_children = null;
+	finalBestHost = null;
+      }
+
+      if (finalBestHost == this) {
+	// splitting is still the best, so downdate the stats as 
+	// we'll be recursively calling on this node
+	updateStats(newInstance, true);
+      }
+
+      return finalBestHost;
+    }
+    
+    /**
+     * Adds the supplied node as a child of this node. All of the child's
+     * instances are added to this nodes instances
+     *
+     * @param child the child to add
+     */
+    protected void addChildNode(CNode child) {
+      for (int i = 0; i < child.m_clusterInstances.numInstances(); i++) {
+	Instance temp = child.m_clusterInstances.instance(i);
+	m_clusterInstances.add(temp);
+	updateStats(temp, false);
+      }
+
+      if (m_children == null) {
+	m_children = new FastVector();
+      }
+      m_children.addElement(child);
+    }
+
+    /**
+     * Computes the utility of all children with respect to this node
+     *
+     * @return the category utility of the children with respect to this node.
+     * @throws Exception if there are no children
+     */
+    protected double categoryUtility() throws Exception {
+      
+      if (m_children == null) {
+	throw new Exception("categoryUtility: No children!");
+      }
+
+      double totalCU = 0;
+     
+      for (int i = 0; i < m_children.size(); i++) {
+	CNode child = (CNode) m_children.elementAt(i);
+	totalCU += categoryUtilityChild(child);
+      }
+
+      totalCU /= (double)m_children.size();
+      return totalCU;
+    }
+
+    /**
+     * Computes the utility of a single child with respect to this node
+     *
+     * @param child the child for which to compute the utility
+     * @return the utility of the child with respect to this node
+     * @throws Exception if something goes wrong
+     */
+    protected double categoryUtilityChild(CNode child) throws Exception {
+      
+      double sum = 0;
+      for (int i = 0; i < m_numAttributes; i++) {
+	if (m_clusterInstances.attribute(i).isNominal()) {
+	  for (int j = 0; 
+	       j < m_clusterInstances.attribute(i).numValues(); j++) {
+	    double x = child.getProbability(i, j);
+	    double y = getProbability(i, j);
+	    sum += (x * x) - (y * y);
+	  }
+	} else {
+	  // numeric attribute
+	  sum += ((m_normal / child.getStandardDev(i)) - 
+		  (m_normal / getStandardDev(i)));
+	  
+	}
+      }
+      return (child.m_totalInstances / m_totalInstances) * sum;
+    }
+
+    /**
+     * Returns the probability of a value of a nominal attribute in this node
+     *
+     * @param attIndex the index of the attribute
+     * @param valueIndex the index of the value of the attribute
+     * @return the probability
+     * @throws Exception if the requested attribute is not nominal
+     */
+    protected double getProbability(int attIndex, int valueIndex) 
+      throws Exception {
+      
+      if (!m_clusterInstances.attribute(attIndex).isNominal()) {
+	throw new Exception("getProbability: attribute is not nominal");
+      }
+
+      if (m_attStats[attIndex].totalCount <= 0) {
+	return 0;
+      }
+
+      return (double) m_attStats[attIndex].nominalCounts[valueIndex] / 
+	(double) m_attStats[attIndex].totalCount;
+    }
+
+    /**
+     * Returns the standard deviation of a numeric attribute
+     *
+     * @param attIndex the index of the attribute
+     * @return the standard deviation
+     * @throws Exception if an error occurs
+     */
+    protected double getStandardDev(int attIndex) throws Exception {
+      if (!m_clusterInstances.attribute(attIndex).isNumeric()) {
+	throw new Exception("getStandardDev: attribute is not numeric");
+      }
+
+      m_attStats[attIndex].numericStats.calculateDerived();
+      double stdDev = m_attStats[attIndex].numericStats.stdDev;
+      if (Double.isNaN(stdDev) || Double.isInfinite(stdDev)) {
+	return m_acuity;
+      }
+
+      return Math.max(m_acuity, stdDev);
+    }
+
+    /**
+     * Update attribute stats using the supplied instance. 
+     *
+     * @param updateInstance the instance for updating
+     * @param delete true if the values of the supplied instance are
+     * to be removed from the statistics
+     */
+    protected void updateStats(Instance updateInstance, 
+			       boolean delete) {
+
+      if (m_attStats == null) {
+	m_attStats = new AttributeStats[m_numAttributes];
+	for (int i = 0; i < m_numAttributes; i++) {
+	  m_attStats[i] = new AttributeStats();
+	  if (m_clusterInstances.attribute(i).isNominal()) {
+	    m_attStats[i].nominalCounts = 
+	      new int [m_clusterInstances.attribute(i).numValues()];
+	  } else {
+	    m_attStats[i].numericStats = new Stats();
+	  }
+	}
+      }
+      for (int i = 0; i < m_numAttributes; i++) {
+	if (!updateInstance.isMissing(i)) {
+	  double value = updateInstance.value(i);
+	  if (m_clusterInstances.attribute(i).isNominal()) {
+	    m_attStats[i].nominalCounts[(int)value] += (delete) ? 
+	      (-1.0 * updateInstance.weight()) : 
+	      updateInstance.weight();
+	    m_attStats[i].totalCount += (delete) ?
+	      (-1.0 * updateInstance.weight()) :
+	      updateInstance.weight();
+	  } else {
+	    if (delete) {
+	      m_attStats[i].numericStats.subtract(value, 
+						  updateInstance.weight());
+	    } else {
+	      m_attStats[i].numericStats.add(value, updateInstance.weight());
+	    }
+	  }
+	}
+      }
+      m_totalInstances += (delete) 
+	? (-1.0 * updateInstance.weight()) 
+	: (updateInstance.weight());
+    }
+
+    /**
+     * Recursively assigns numbers to the nodes in the tree.
+     *
+     * @param cl_num an <code>int[]</code> value
+     * @throws Exception if an error occurs
+     */
+    private void assignClusterNums(int[] cl_num) throws Exception {
+      if (m_children != null && m_children.size() < 2) {
+	throw new Exception("assignClusterNums: tree not built correctly!");
+      }
+      
+      m_clusterNum = cl_num[0];
+      cl_num[0]++;
+      if (m_children != null) {
+	for (int i = 0; i < m_children.size(); i++) {
+	  CNode child = (CNode) m_children.elementAt(i);
+	  child.assignClusterNums(cl_num);
+	}
+      }
+    }
+
+    /**
+     * Recursively build a string representation of the Cobweb tree
+     *
+     * @param depth depth of this node in the tree
+     * @param text holds the string representation
+     */
+    protected void dumpTree(int depth, StringBuffer text) {
+
+      if (depth == 0)
+	determineNumberOfClusters();
+      
+      if (m_children == null) {
+	text.append("\n");
+	for (int j = 0; j < depth; j++) {
+	  text.append("|   ");
+	}
+	text.append("leaf "+m_clusterNum+" ["
+		    +m_clusterInstances.numInstances()+"]");
+      } else {
+	for (int i = 0; i < m_children.size(); i++) {
+	  text.append("\n");
+	  for (int j = 0; j < depth; j++) {
+	    text.append("|   ");
+	  }
+	  text.append("node "+m_clusterNum+" ["
+		      +m_clusterInstances.numInstances()
+		      +"]");
+	  ((CNode) m_children.elementAt(i)).dumpTree(depth+1, text);
+	}
+      }
+    }
+
+    /**
+     * Returns the instances at this node as a string. Appends the cluster
+     * number of the child that each instance belongs to.
+     *
+     * @return a <code>String</code> value
+     * @throws Exception if an error occurs
+     */
+    protected String dumpData() throws Exception {
+      if (m_children == null) {
+	return m_clusterInstances.toString();
+      }
+
+      // construct instances string with cluster numbers attached
+      CNode tempNode = new CNode(m_numAttributes);
+      tempNode.m_clusterInstances = new Instances(m_clusterInstances, 1);
+      for (int i = 0; i < m_children.size(); i++) {
+	tempNode.addChildNode((CNode)m_children.elementAt(i));
+      }
+      Instances tempInst = tempNode.m_clusterInstances;
+      tempNode = null;
+
+      Add af = new Add();
+      af.setAttributeName("Cluster");
+      String labels = "";
+      for (int i = 0; i < m_children.size(); i++) {
+	CNode temp = (CNode)m_children.elementAt(i);
+	labels += ("C"+temp.m_clusterNum);
+	if (i < m_children.size()-1) {
+	  labels+=",";
+	}
+      }
+      af.setNominalLabels(labels);
+      af.setInputFormat(tempInst);
+      tempInst = Filter.useFilter(tempInst, af);
+      tempInst.setRelationName("Cluster "+m_clusterNum);
+      
+      int z = 0;
+      for (int i = 0; i < m_children.size(); i++) {
+	CNode temp = (CNode)m_children.elementAt(i);
+	for (int j = 0; j < temp.m_clusterInstances.numInstances(); j++) {
+	  tempInst.instance(z).setValue(m_numAttributes, (double)i);
+	  z++;
+	}
+      }
+      return tempInst.toString();
+    }
+
+    /**
+     * Recursively generate the graph string for the Cobweb tree.
+     *
+     * @param text holds the graph string
+     * @throws Exception if generation fails
+     */
+    protected void graphTree(StringBuffer text) throws Exception {
+      
+      text.append("N"+m_clusterNum
+		  + " [label=\""+((m_children == null) 
+				  ? "leaf " : "node ")
+		  +m_clusterNum+" "
+		  +" ("+m_clusterInstances.numInstances()
+		  +")\" "
+		  +((m_children == null) 
+		    ? "shape=box style=filled " : "")
+		  +(m_saveInstances 
+		    ? "data =\n"+dumpData() +"\n,\n"
+		    : "")
+		  + "]\n");
+      if (m_children != null) {
+	for (int i = 0; i < m_children.size(); i++) {
+	  CNode temp = (CNode)m_children.elementAt(i);
+	  text.append("N"+m_clusterNum
+		      +"->"
+		      +"N" + temp.m_clusterNum
+		      + "\n");
+	}
+
+	for (int i = 0; i < m_children.size(); i++) {
+	  CNode temp = (CNode)m_children.elementAt(i);
+	  temp.graphTree(text);
+	}
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5488 $");
+    }
+  }
+
+  /**
+   * Normal constant.
+   */
+  protected static final double m_normal = 1.0/(2 * Math.sqrt(Math.PI));
+
+  /**
+   * Acuity (minimum standard deviation).
+   */
+  protected double m_acuity = 1.0;
+
+  /**
+   * Cutoff (minimum category utility).
+   */
+  protected double m_cutoff = 0.01 * Cobweb.m_normal;
+
+  /**
+   * Holds the root of the Cobweb tree.
+   */
+  protected CNode m_cobwebTree = null;
+
+  /**
+   * Number of clusters (nodes in the tree). Must never be queried directly, 
+   * only via the method numberOfClusters(). Otherwise it's not guaranteed that 
+   * it contains the correct value.
+   * 
+   * @see #numberOfClusters()
+   * @see #m_numberOfClustersDetermined
+   */
+  protected int m_numberOfClusters = -1;
+  
+  /** whether the number of clusters was already determined */
+  protected boolean m_numberOfClustersDetermined = false;
+  
+  /** the number of splits that happened */
+  protected int m_numberSplits;
+  
+  /** the number of merges that happened */
+  protected int m_numberMerges;
+
+  /**
+   * Output instances in graph representation of Cobweb tree (Allows
+   * instances at nodes in the tree to be visualized in the Explorer).
+   */
+  protected boolean m_saveInstances = false;
+
+  /**
+   * default constructor
+   */
+  public Cobweb() {
+    super();
+    
+    m_SeedDefault = 42;
+    setSeed(m_SeedDefault);
+  }
+  
+  /**
+   * Returns a string describing this clusterer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the Cobweb and Classit clustering algorithms.\n\n"
+      + "Note: the application of node operators (merging, splitting etc.) in "
+      + "terms of ordering and priority differs (and is somewhat ambiguous) "
+      + "between the original Cobweb and Classit papers. This algorithm always "
+      + "compares the best host, adding a new leaf, merging the two best hosts, "
+      + "and splitting the best host when considering where to place a new "
+      + "instance.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "D. Fisher");
+    result.setValue(Field.YEAR, "1987");
+    result.setValue(Field.TITLE, "Knowledge acquisition via incremental conceptual clustering");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.VOLUME, "2");
+    result.setValue(Field.NUMBER, "2");
+    result.setValue(Field.PAGES, "139-172");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "J. H. Gennari and P. Langley and D. Fisher");
+    additional.setValue(Field.YEAR, "1990");
+    additional.setValue(Field.TITLE, "Models of incremental concept formation");
+    additional.setValue(Field.JOURNAL, "Artificial Intelligence");
+    additional.setValue(Field.VOLUME, "40");
+    additional.setValue(Field.PAGES, "11-61");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // other
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Builds the clusterer.
+   *
+   * @param data the training instances.
+   * @throws Exception if something goes wrong.
+   */
+  public void buildClusterer(Instances data) throws Exception {
+    m_numberOfClusters = -1;
+    m_cobwebTree = null;
+    m_numberSplits = 0;
+    m_numberMerges = 0;
+
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+
+    // randomize the instances
+    data = new Instances(data);
+    data.randomize(new Random(getSeed()));
+
+    for (int i = 0; i < data.numInstances(); i++) {
+      updateClusterer(data.instance(i));
+    }
+    
+    updateFinished();
+  }
+
+  /**
+   * Singals the end of the updating.
+   */
+  public void updateFinished() {
+    determineNumberOfClusters();
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an interger
+   * if the class is enumerated, otherwise the predicted value
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    CNode host = m_cobwebTree;
+    CNode temp = null;
+    
+    determineNumberOfClusters();
+    
+    do {
+      if (host.m_children == null) {
+	temp = null;
+	break;
+      }
+
+      host.updateStats(instance, false);
+      temp = host.findHost(instance, true);
+      host.updateStats(instance, true);
+      
+      if (temp != null) {
+	host = temp;
+      }
+    } while (temp != null);
+    
+    return host.m_clusterNum;
+  }
+
+  /**
+   * determines the number of clusters if necessary
+   * 
+   * @see #m_numberOfClusters
+   * @see #m_numberOfClustersDetermined
+   */
+  protected void determineNumberOfClusters() {
+    if (    !m_numberOfClustersDetermined 
+	 && (m_cobwebTree != null) ) {
+      int[] numClusts = new int [1];
+      numClusts[0] = 0;
+      try {
+	m_cobwebTree.assignClusterNums(numClusts);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	numClusts[0] = 0;
+      }
+      m_numberOfClusters = numClusts[0];
+
+      m_numberOfClustersDetermined = true;
+    }
+  }
+  
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters
+   */
+  public int numberOfClusters() {
+    determineNumberOfClusters();
+    return m_numberOfClusters;
+  }
+
+  /**
+   * Adds an instance to the clusterer.
+   *
+   * @param newInstance the instance to be added
+   * @throws Exception 	if something goes wrong
+   */
+  public void updateClusterer(Instance newInstance) throws Exception {
+    m_numberOfClustersDetermined = false;
+    
+    if (m_cobwebTree == null) {
+      m_cobwebTree = new CNode(newInstance.numAttributes(), newInstance);
+    } else {
+      m_cobwebTree.addInstance(newInstance);
+    }
+  }
+  
+  /**
+   * Adds an instance to the Cobweb tree.
+   *
+   * @param newInstance the instance to be added
+   * @throws Exception if something goes wrong
+   * @deprecated updateClusterer(Instance) should be used instead
+   * @see #updateClusterer(Instance)
+   */
+  public void addInstance(Instance newInstance) throws Exception {
+    updateClusterer(newInstance);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tAcuity.\n"
+	+"\t(default=1.0)",
+	"A", 1,"-A <acuity>"));
+    
+    result.addElement(new Option(
+	"\tCutoff.\n"
+	+"\t(default=0.002)",
+	"C", 1,"-C <cutoff>"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -A &lt;acuity&gt;
+   *  Acuity.
+   *  (default=1.0)</pre>
+   * 
+   * <pre> -C &lt;cutoff&gt;
+   *  Cutoff.
+   *  (default=0.002)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 42)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString;
+
+    optionString = Utils.getOption('A', options); 
+    if (optionString.length() != 0) {
+      Double temp = new Double(optionString);
+      setAcuity(temp.doubleValue());
+    }
+    else {
+      m_acuity = 1.0;
+    }
+    optionString = Utils.getOption('C', options); 
+    if (optionString.length() != 0) {
+      Double temp = new Double(optionString);
+      setCutoff(temp.doubleValue());
+    }
+    else {
+      m_cutoff = 0.01 * Cobweb.m_normal;
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String acuityTipText() {
+    return "set the minimum standard deviation for numeric attributes";
+  }
+
+  /**
+   * set the acuity.
+   * @param a the acuity value
+   */
+  public void setAcuity(double a) {
+    m_acuity = a;
+  }
+
+  /**
+   * get the acuity value
+   * @return the acuity
+   */
+  public double getAcuity() {
+    return m_acuity;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String cutoffTipText() {
+    return "set the category utility threshold by which to prune nodes";
+  }
+
+  /**
+   * set the cutoff
+   * @param c the cutof
+   */
+  public void setCutoff(double c) {
+    m_cutoff = c;
+  }
+
+  /**
+   * get the cutoff
+   * @return the cutoff
+   */
+  public double getCutoff() {
+    return m_cutoff;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String saveInstanceDataTipText() {
+    return "save instance information for visualization purposes";
+  }
+
+  /**
+   * Get the value of saveInstances.
+   *
+   * @return Value of saveInstances.
+   */
+  public boolean getSaveInstanceData() {
+    
+    return m_saveInstances;
+  }
+  
+  /**
+   * Set the value of saveInstances.
+   *
+   * @param newsaveInstances Value to assign to saveInstances.
+   */
+  public void setSaveInstanceData(boolean newsaveInstances) {
+    
+    m_saveInstances = newsaveInstances;
+  }
+
+  /**
+   * Gets the current settings of Cobweb.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    int       		i;
+    Vector<String>    	result;
+    String[]  		options;
+
+    result = new Vector<String>();
+
+    result.add("-A"); 
+    result.add("" + m_acuity);
+    result.add("-C"); 
+    result.add("" + m_cutoff);
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns a description of the clusterer as a string.
+   *
+   * @return a string describing the clusterer.
+   */
+  public String toString() { 
+    StringBuffer text = new StringBuffer();
+    if (m_cobwebTree == null) {
+      return "Cobweb hasn't been built yet!";
+    }
+    else {
+      m_cobwebTree.dumpTree(0, text); 
+      return "Number of merges: "
+	+ m_numberMerges+"\nNumber of splits: "
+	+ m_numberSplits+"\nNumber of clusters: "
+	+ numberOfClusters() +"\n"+text.toString()+"\n\n";
+     
+    }
+  }
+    
+  /**
+   *  Returns the type of graphs this class
+   *  represents
+   *  @return Drawable.TREE
+   */   
+  public int graphType() {
+      return Drawable.TREE;
+  }
+
+  /**
+   * Generates the graph string of the Cobweb tree
+   *
+   * @return a <code>String</code> value
+   * @throws Exception if an error occurs
+   */
+  public String graph() throws Exception {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("digraph CobwebTree {\n");
+    m_cobwebTree.graphTree(text);
+    text.append("}\n");
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5488 $");
+  }
+
+  /** 
+   * Main method.
+   * 
+   * @param argv the commandline options
+   */
+  public static void main(String[] argv) {
+    runClusterer(new Cobweb(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/DBScan.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/DBScan.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/DBScan.java	(revision 29)
@@ -0,0 +1,640 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.clusterers.forOPTICSAndDBScan.Databases.Database;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DecimalFormat;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Martin Ester, Hans-Peter Kriegel, Joerg Sander, Xiaowei Xu: A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise. In: Second International Conference on Knowledge Discovery and Data Mining, 226-231, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Ester1996,
+ *    author = {Martin Ester and Hans-Peter Kriegel and Joerg Sander and Xiaowei Xu},
+ *    booktitle = {Second International Conference on Knowledge Discovery and Data Mining},
+ *    editor = {Evangelos Simoudis and Jiawei Han and Usama M. Fayyad},
+ *    pages = {226-231},
+ *    publisher = {AAAI Press},
+ *    title = {A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;double&gt;
+ *  epsilon (default = 0.9)</pre>
+ * 
+ * <pre> -M &lt;int&gt;
+ *  minPoints (default = 6)</pre>
+ * 
+ * <pre> -I &lt;String&gt;
+ *  index (database) used for DBScan (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)</pre>
+ * 
+ * <pre> -D &lt;String&gt;
+ *  distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 5488 $
+ */
+public class DBScan 
+    extends AbstractClusterer 
+    implements OptionHandler, TechnicalInformationHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -1666498248451219728L;
+  
+    /**
+     * Specifies the radius for a range-query
+     */
+    private double epsilon = 0.9;
+
+    /**
+     * Specifies the density (the range-query must contain at least minPoints DataObjects)
+     */
+    private int minPoints = 6;
+
+    /**
+     * Replace missing values in training instances
+     */
+    private ReplaceMissingValues replaceMissingValues_Filter;
+
+    /**
+     * Holds the number of clusters generated
+     */
+    private int numberOfGeneratedClusters;
+
+    /**
+     * Holds the distance-type that is used
+     * (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)
+     */
+    private String database_distanceType = "weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject";
+
+    /**
+     * Holds the type of the used database
+     * (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)
+     */
+    private String database_Type = "weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase";
+
+    /**
+     * The database that is used for DBScan
+     */
+    private Database database;
+
+    /**
+     * Holds the current clusterID
+     */
+    private int clusterID;
+
+    /**
+     * Counter for the processed instances
+     */
+    private int processed_InstanceID;
+
+    /**
+     * Holds the time-value (seconds) for the duration of the clustering-process
+     */
+    private double elapsedTime;
+
+    /**
+     * Returns default capabilities of the clusterer.
+     *
+     * @return      the capabilities of this clusterer
+     */
+    public Capabilities getCapabilities() {
+      Capabilities result = super.getCapabilities();
+      result.disableAll();
+      result.enable(Capability.NO_CLASS);
+
+      // attributes
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+      result.enable(Capability.NUMERIC_ATTRIBUTES);
+      result.enable(Capability.DATE_ATTRIBUTES);
+      result.enable(Capability.MISSING_VALUES);
+
+      return result;
+    }
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Generate Clustering via DBScan
+     * @param instances The instances that need to be clustered
+     * @throws java.lang.Exception If clustering was not successful
+     */
+    public void buildClusterer(Instances instances) throws Exception {
+        // can clusterer handle the data?
+        getCapabilities().testWithFail(instances);
+
+        long time_1 = System.currentTimeMillis();
+
+        processed_InstanceID = 0;
+        numberOfGeneratedClusters = 0;
+        clusterID = 0;
+
+        replaceMissingValues_Filter = new ReplaceMissingValues();
+        replaceMissingValues_Filter.setInputFormat(instances);
+        Instances filteredInstances = Filter.useFilter(instances, replaceMissingValues_Filter);
+
+        database = databaseForName(getDatabase_Type(), filteredInstances);
+        for (int i = 0; i < database.getInstances().numInstances(); i++) {
+            DataObject dataObject = dataObjectForName(getDatabase_distanceType(),
+                    database.getInstances().instance(i),
+                    Integer.toString(i),
+                    database);
+            database.insert(dataObject);
+        }
+        database.setMinMaxValues();
+
+        Iterator iterator = database.dataObjectIterator();
+        while (iterator.hasNext()) {
+            DataObject dataObject = (DataObject) iterator.next();
+            if (dataObject.getClusterLabel() == DataObject.UNCLASSIFIED) {
+                if (expandCluster(dataObject)) {
+                    clusterID++;
+                    numberOfGeneratedClusters++;
+                }
+            }
+        }
+
+        long time_2 = System.currentTimeMillis();
+        elapsedTime = (double) (time_2 - time_1) / 1000.0;
+    }
+
+    /**
+     * Assigns this dataObject to a cluster or remains it as NOISE
+     * @param dataObject The DataObject that needs to be assigned
+     * @return true, if the DataObject could be assigned, else false
+     */
+    private boolean expandCluster(DataObject dataObject) {
+        List seedList = database.epsilonRangeQuery(getEpsilon(), dataObject);
+        /** dataObject is NO coreObject */
+        if (seedList.size() < getMinPoints()) {
+            dataObject.setClusterLabel(DataObject.NOISE);
+            return false;
+        }
+
+        /** dataObject is coreObject */
+        for (int i = 0; i < seedList.size(); i++) {
+            DataObject seedListDataObject = (DataObject) seedList.get(i);
+            /** label this seedListDataObject with the current clusterID, because it is in epsilon-range */
+            seedListDataObject.setClusterLabel(clusterID);
+            if (seedListDataObject.equals(dataObject)) {
+                seedList.remove(i);
+                i--;
+            }
+        }
+
+        /** Iterate the seedList of the startDataObject */
+        for (int j = 0; j < seedList.size(); j++) {
+            DataObject seedListDataObject = (DataObject) seedList.get(j);
+            List seedListDataObject_Neighbourhood = database.epsilonRangeQuery(getEpsilon(), seedListDataObject);
+
+            /** seedListDataObject is coreObject */
+            if (seedListDataObject_Neighbourhood.size() >= getMinPoints()) {
+                for (int i = 0; i < seedListDataObject_Neighbourhood.size(); i++) {
+                    DataObject p = (DataObject) seedListDataObject_Neighbourhood.get(i);
+                    if (p.getClusterLabel() == DataObject.UNCLASSIFIED || p.getClusterLabel() == DataObject.NOISE) {
+                        if (p.getClusterLabel() == DataObject.UNCLASSIFIED) {
+                            seedList.add(p);
+                        }
+                        p.setClusterLabel(clusterID);
+                    }
+                }
+            }
+            seedList.remove(j);
+            j--;
+        }
+
+        return true;
+    }
+
+    /**
+     * Classifies a given instance.
+     *
+     * @param instance The instance to be assigned to a cluster
+     * @return int The number of the assigned cluster as an integer
+     * @throws java.lang.Exception If instance could not be clustered
+     * successfully
+     */
+    public int clusterInstance(Instance instance) throws Exception {
+        if (processed_InstanceID >= database.size()) processed_InstanceID = 0;
+        int cnum = (database.getDataObject(Integer.toString(processed_InstanceID++))).getClusterLabel();
+        if (cnum == DataObject.NOISE)
+            throw new Exception();
+        else
+            return cnum;
+    }
+
+    /**
+     * Returns the number of clusters.
+     *
+     * @return int The number of clusters generated for a training dataset.
+     * @throws java.lang.Exception if number of clusters could not be returned
+     * successfully
+     */
+    public int numberOfClusters() throws Exception {
+        return numberOfGeneratedClusters;
+    }
+
+    /**
+     * Returns an enumeration of all the available options..
+     *
+     * @return Enumeration An enumeration of all available options.
+     */
+    public Enumeration listOptions() {
+        Vector vector = new Vector();
+
+        vector.addElement(
+                new Option("\tepsilon (default = 0.9)",
+                        "E",
+                        1,
+                        "-E <double>"));
+        vector.addElement(
+                new Option("\tminPoints (default = 6)",
+                        "M",
+                        1,
+                        "-M <int>"));
+        vector.addElement(
+                new Option("\tindex (database) used for DBScan (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)",
+                        "I",
+                        1,
+                        "-I <String>"));
+        vector.addElement(
+                new Option("\tdistance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)",
+                        "D",
+                        1,
+                        "-D <String>"));
+        return vector.elements();
+    }
+
+    /**
+     * Sets the OptionHandler's options using the given list. All options
+     * will be set (or reset) during this call (i.e. incremental setting
+     * of options is not possible). <p/>
+     *
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -E &lt;double&gt;
+     *  epsilon (default = 0.9)</pre>
+     * 
+     * <pre> -M &lt;int&gt;
+     *  minPoints (default = 6)</pre>
+     * 
+     * <pre> -I &lt;String&gt;
+     *  index (database) used for DBScan (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)</pre>
+     * 
+     * <pre> -D &lt;String&gt;
+     *  distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)</pre>
+     * 
+     <!-- options-end -->
+     *
+     * @param options The list of options as an array of strings
+     * @throws java.lang.Exception If an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        String optionString = Utils.getOption('E', options);
+        if (optionString.length() != 0) {
+            setEpsilon(Double.parseDouble(optionString));
+        }
+
+        optionString = Utils.getOption('M', options);
+        if (optionString.length() != 0) {
+            setMinPoints(Integer.parseInt(optionString));
+        }
+
+        optionString = Utils.getOption('I', options);
+        if (optionString.length() != 0) {
+            setDatabase_Type(optionString);
+        }
+
+        optionString = Utils.getOption('D', options);
+        if (optionString.length() != 0) {
+            setDatabase_distanceType(optionString);
+        }
+    }
+
+    /**
+     * Gets the current option settings for the OptionHandler.
+     *
+     * @return String[] The list of current option settings as an array of strings
+     */
+    public String[] getOptions() {
+        String[] options = new String[8];
+        int current = 0;
+
+        options[current++] = "-E";
+        options[current++] = "" + getEpsilon();
+        options[current++] = "-M";
+        options[current++] = "" + getMinPoints();
+        options[current++] = "-I";
+        options[current++] = "" + getDatabase_Type();
+        options[current++] = "-D";
+        options[current++] = "" + getDatabase_distanceType();
+
+        return options;
+    }
+
+    /**
+     * Returns a new Class-Instance of the specified database
+     * @param database_Type String of the specified database
+     * @param instances Instances that were delivered from WEKA
+     * @return Database New constructed Database
+     */
+    public Database databaseForName(String database_Type, Instances instances) {
+        Object o = null;
+
+        Constructor co = null;
+        try {
+            co = (Class.forName(database_Type)).getConstructor(new Class[]{Instances.class});
+            o = co.newInstance(new Object[]{instances});
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+
+        return (Database) o;
+    }
+
+    /**
+     * Returns a new Class-Instance of the specified database
+     * @param database_distanceType String of the specified distance-type
+     * @param instance The original instance that needs to hold by this DataObject
+     * @param key Key for this DataObject
+     * @param database Link to the database
+     * @return DataObject New constructed DataObject
+     */
+    public DataObject dataObjectForName(String database_distanceType, Instance instance, String key, Database database) {
+        Object o = null;
+
+        Constructor co = null;
+        try {
+            co = (Class.forName(database_distanceType)).
+                    getConstructor(new Class[]{Instance.class, String.class, Database.class});
+            o = co.newInstance(new Object[]{instance, key, database});
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+
+        return (DataObject) o;
+    }
+
+    /**
+     * Sets a new value for minPoints
+     * @param minPoints MinPoints
+     */
+    public void setMinPoints(int minPoints) {
+        this.minPoints = minPoints;
+    }
+
+    /**
+     * Sets a new value for epsilon
+     * @param epsilon Epsilon
+     */
+    public void setEpsilon(double epsilon) {
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Returns the value of epsilon
+     * @return double Epsilon
+     */
+    public double getEpsilon() {
+        return epsilon;
+    }
+
+    /**
+     * Returns the value of minPoints
+     * @return int MinPoints
+     */
+    public int getMinPoints() {
+        return minPoints;
+    }
+
+    /**
+     * Returns the distance-type
+     * @return String Distance-type
+     */
+    public String getDatabase_distanceType() {
+        return database_distanceType;
+    }
+
+    /**
+     * Returns the type of the used index (database)
+     * @return String Index-type
+     */
+    public String getDatabase_Type() {
+        return database_Type;
+    }
+
+    /**
+     * Sets a new distance-type
+     * @param database_distanceType The new distance-type
+     */
+    public void setDatabase_distanceType(String database_distanceType) {
+        this.database_distanceType = database_distanceType;
+    }
+
+    /**
+     * Sets a new database-type
+     * @param database_Type The new database-type
+     */
+    public void setDatabase_Type(String database_Type) {
+        this.database_Type = database_Type;
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String epsilonTipText() {
+        return "radius of the epsilon-range-queries";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String minPointsTipText() {
+        return "minimun number of DataObjects required in an epsilon-range-query";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String database_TypeTipText() {
+        return "used database";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String database_distanceTypeTipText() {
+        return "used distance-type";
+    }
+
+    /**
+     * Returns a string describing this DataMining-Algorithm
+     * @return String Information for the gui-explorer
+     */
+    public String globalInfo() {
+        return getTechnicalInformation().toString();
+    }
+
+    /**
+     * Returns an instance of a TechnicalInformation object, containing 
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     * 
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      
+      result = new TechnicalInformation(Type.INPROCEEDINGS);
+      result.setValue(Field.AUTHOR, "Martin Ester and Hans-Peter Kriegel and Joerg Sander and Xiaowei Xu");
+      result.setValue(Field.TITLE, "A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise");
+      result.setValue(Field.BOOKTITLE, "Second International Conference on Knowledge Discovery and Data Mining");
+      result.setValue(Field.EDITOR, "Evangelos Simoudis and Jiawei Han and Usama M. Fayyad");
+      result.setValue(Field.YEAR, "1996");
+      result.setValue(Field.PAGES, "226-231");
+      result.setValue(Field.PUBLISHER, "AAAI Press");
+      
+      return result;
+    }
+
+    /**
+     * Returns a description of the clusterer
+     * 
+     * @return a string representation of the clusterer
+     */
+    public String toString() {
+        StringBuffer stringBuffer = new StringBuffer();
+        stringBuffer.append("DBScan clustering results\n" +
+                "========================================================================================\n\n");
+        stringBuffer.append("Clustered DataObjects: " + database.size() + "\n");
+        stringBuffer.append("Number of attributes: " + database.getInstances().numAttributes() + "\n");
+        stringBuffer.append("Epsilon: " + getEpsilon() + "; minPoints: " + getMinPoints() + "\n");
+        stringBuffer.append("Index: " + getDatabase_Type() + "\n");
+        stringBuffer.append("Distance-type: " + getDatabase_distanceType() + "\n");
+        stringBuffer.append("Number of generated clusters: " + numberOfGeneratedClusters + "\n");
+        DecimalFormat decimalFormat = new DecimalFormat(".##");
+        stringBuffer.append("Elapsed time: " + decimalFormat.format(elapsedTime) + "\n\n");
+
+        for (int i = 0; i < database.size(); i++) {
+            DataObject dataObject = database.getDataObject(Integer.toString(i));
+            stringBuffer.append("(" + Utils.doubleToString(Double.parseDouble(dataObject.getKey()),
+                    (Integer.toString(database.size()).length()), 0) + ".) "
+                    + Utils.padRight(dataObject.toString(), 69) + "  -->  " +
+                    ((dataObject.getClusterLabel() == DataObject.NOISE) ?
+                    "NOISE\n" : dataObject.getClusterLabel() + "\n"));
+        }
+        return stringBuffer.toString() + "\n";
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5488 $");
+    }
+
+    /**
+     * Main Method for testing DBScan
+     * @param args Valid parameters are: 'E' epsilon (default = 0.9); 'M' minPoints (default = 6);
+     *                                   'I' index-type (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase);
+     *                                   'D' distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject);
+     */
+    public static void main(String[] args) {
+        runClusterer(new DBScan(), args);
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/DensityBasedClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/DensityBasedClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/DensityBasedClusterer.java	(revision 29)
@@ -0,0 +1,73 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DensityBasedClusterer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Instance;
+
+/**
+ * Interface for clusterers that can estimate the density for a given instance.
+ * Implementations will typically extend AbstractDensityBasedClusterer.
+ *
+ * @author   Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version  $Revision: 5987 $
+ */
+public interface DensityBasedClusterer extends Clusterer {
+
+  /**
+   * Returns the prior probability of each cluster.
+   *
+   * @return the prior probability for each cluster
+   * @exception Exception if priors could not be 
+   * returned successfully
+   */
+  double[] clusterPriors() throws Exception;
+
+  /**
+   * Computes the log of the conditional density (per cluster) for a given instance.
+   * 
+   * @param instance the instance to compute the density for
+   * @return an array containing the estimated densities
+   * @exception Exception if the density could not be computed
+   * successfully
+   */
+  double[] logDensityPerClusterForInstance(Instance instance) throws Exception;
+
+  /**
+   * Computes the density for a given instance.
+   * 
+   * @param instance the instance to compute the density for
+   * @return the density.
+   * @exception Exception if the density could not be computed successfully
+   */
+  double logDensityForInstance(Instance instance) throws Exception;
+
+  /** 
+   * Returns the logs of the joint densities for a given instance.
+   *
+   * @param inst the instance 
+   * @return the array of values
+   * @exception Exception if values could not be computed
+   */
+  double[] logJointDensitiesForInstance(Instance inst) throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/EM.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/EM.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/EM.java	(revision 29)
@@ -0,0 +1,1454 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EM.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.estimators.DiscreteEstimator;
+import weka.estimators.Estimator;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Simple EM (expectation maximisation) class.<br/>
+ * <br/>
+ * EM assigns a probability distribution to each instance which indicates the probability of it belonging to each of the clusters. EM can decide how many clusters to create by cross validation, or you may specify apriori how many clusters to generate.<br/>
+ * <br/>
+ * The cross validation performed to determine the number of clusters is done in the following steps:<br/>
+ * 1. the number of clusters is set to 1<br/>
+ * 2. the training set is split randomly into 10 folds.<br/>
+ * 3. EM is performed 10 times using the 10 folds the usual CV way.<br/>
+ * 4. the loglikelihood is averaged over all 10 results.<br/>
+ * 5. if loglikelihood has increased the number of clusters is increased by 1 and the program continues at step 2. <br/>
+ * <br/>
+ * The number of folds is fixed to 10, as long as the number of instances in the training set is not smaller 10. If this is the case the number of folds is set equal to the number of instances.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters. If omitted or -1 specified, then 
+ *  cross validation is used to select the number of clusters.</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  max iterations.
+ * (default 100)</pre>
+ * 
+ * <pre> -V
+ *  verbose.</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  minimum allowable standard deviation for normal density
+ *  computation
+ *  (default 1e-6)</pre>
+ * 
+ * <pre> -O
+ *  Display model in old format (good when there are many clusters)
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 100)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 1.44 $
+ */
+public class EM
+  extends RandomizableDensityBasedClusterer
+  implements NumberOfClustersRequestable, WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8348181483812829475L;
+  
+  /** hold the discrete estimators for each cluster */
+  private Estimator m_model[][];
+
+  /** hold the normal estimators for each cluster */
+  private double m_modelNormal[][][];
+
+  /** default minimum standard deviation */
+  private double m_minStdDev = 1e-6;
+
+  private double [] m_minStdDevPerAtt;
+
+  /** hold the weights of each instance for each cluster */
+  private double m_weights[][];
+
+  /** the prior probabilities for clusters */
+  private double m_priors[];
+
+  /** the loglikelihood of the data */
+  private double m_loglikely;
+
+  /** training instances */
+  private Instances m_theInstances = null;
+
+  /** number of clusters selected by the user or cross validation */
+  private int m_num_clusters;
+
+  /** the initial number of clusters requested by the user--- -1 if
+      xval is to be used to find the number of clusters */
+  private int m_initialNumClusters;
+
+  /** number of attributes */
+  private int m_num_attribs;
+
+  /** number of training instances */
+  private int m_num_instances;
+
+  /** maximum iterations to perform */
+  private int m_max_iterations;
+
+  /** attribute min values */
+  private double [] m_minValues;
+
+  /** attribute max values */
+  private double [] m_maxValues;
+
+  /** random number generator */
+  private Random m_rr;
+
+  /** Verbose? */
+  private boolean m_verbose;
+
+ /** globally replace missing values */
+  private ReplaceMissingValues m_replaceMissing;
+
+  /** display model output in old-style format */
+  private boolean m_displayModelInOldFormat;
+
+  /**
+   * Returns a string describing this clusterer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Simple EM (expectation maximisation) class.\n\n"
+      + "EM assigns a probability distribution to each instance which "
+      + "indicates the probability of it belonging to each of the clusters. "
+      + "EM can decide how many clusters to create by cross validation, or you "
+      + "may specify apriori how many clusters to generate.\n\n"
+      + "The cross validation performed to determine the number of clusters "
+      + "is done in the following steps:\n"
+      + "1. the number of clusters is set to 1\n"
+      + "2. the training set is split randomly into 10 folds.\n"
+      + "3. EM is performed 10 times using the 10 folds the usual CV way.\n"
+      + "4. the loglikelihood is averaged over all 10 results.\n"
+      + "5. if loglikelihood has increased the number of clusters is increased "
+      + "by 1 and the program continues at step 2. \n\n"
+      + "The number of folds is fixed to 10, as long as the number of "
+      + "instances in the training set is not smaller 10. If this is the case "
+      + "the number of folds is set equal to the number of instances.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions () {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tnumber of clusters. If omitted or -1 specified, then \n"
+	+ "\tcross validation is used to select the number of clusters.", 
+	"N", 1, "-N <num>"));
+
+    result.addElement(new Option(
+	"\tmax iterations."
+	+ "\n(default 100)", 
+	"I", 1, "-I <num>"));
+    
+    result.addElement(new Option(
+	"\tverbose.",
+	"V", 0, "-V"));
+    
+    result.addElement(new Option(
+	"\tminimum allowable standard deviation for normal density\n"
+	+ "\tcomputation\n"
+	+ "\t(default 1e-6)",
+	"M",1,"-M <num>"));
+
+    result.addElement(
+              new Option("\tDisplay model in old format (good when there are "
+                         + "many clusters)\n",
+                         "O", 0, "-O"));
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    return  result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters. If omitted or -1 specified, then 
+   *  cross validation is used to select the number of clusters.</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  max iterations.
+   * (default 100)</pre>
+   * 
+   * <pre> -V
+   *  verbose.</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  minimum allowable standard deviation for normal density
+   *  computation
+   *  (default 1e-6)</pre>
+   * 
+   * <pre> -O
+   *  Display model in old format (good when there are many clusters)
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 100)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+    resetOptions();
+    setDebug(Utils.getFlag('V', options));
+    String optionString = Utils.getOption('I', options);
+
+    if (optionString.length() != 0) {
+      setMaxIterations(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumClusters(Integer.parseInt(optionString));
+    }
+
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMinStdDev((new Double(optionString)).doubleValue());
+    }
+
+    setDisplayModelInOldFormat(Utils.getFlag('O', options));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String displayModelInOldFormatTipText() {
+    return "Use old format for model output. The old format is "
+      + "better when there are many clusters. The new format "
+      + "is better when there are fewer clusters and many attributes.";
+  }
+
+  /**
+   * Set whether to display model output in the old, original
+   * format.
+   *
+   * @param d true if model ouput is to be shown in the old format
+   */
+  public void setDisplayModelInOldFormat(boolean d) {
+    m_displayModelInOldFormat = d;
+  }
+
+  /**
+   * Get whether to display model output in the old, original
+   * format.
+   *
+   * @return true if model ouput is to be shown in the old format
+   */
+  public boolean getDisplayModelInOldFormat() {
+    return m_displayModelInOldFormat;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minStdDevTipText() {
+    return "set minimum allowable standard deviation";
+  }
+
+  /**
+   * Set the minimum value for standard deviation when calculating
+   * normal density. Reducing this value can help prevent arithmetic
+   * overflow resulting from multiplying large densities (arising from small
+   * standard deviations) when there are many singleton or near singleton
+   * values.
+   * @param m minimum value for standard deviation
+   */
+  public void setMinStdDev(double m) {
+    m_minStdDev = m;
+  }
+
+  public void setMinStdDevPerAtt(double [] m) {
+    m_minStdDevPerAtt = m;
+  }
+
+  /**
+   * Get the minimum allowable standard deviation.
+   * @return the minumum allowable standard deviation
+   */
+  public double getMinStdDev() {
+    return m_minStdDev;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numClustersTipText() {
+    return "set number of clusters. -1 to select number of clusters "
+      +"automatically by cross validation.";
+  }
+
+  /**
+   * Set the number of clusters (-1 to select by CV).
+   *
+   * @param n the number of clusters
+   * @throws Exception if n is 0
+   */
+  public void setNumClusters (int n)
+    throws Exception {
+    
+    if (n == 0) {
+      throw  new Exception("Number of clusters must be > 0. (or -1 to " 
+			   + "select by cross validation).");
+    }
+
+    if (n < 0) {
+      m_num_clusters = -1;
+      m_initialNumClusters = -1;
+    }
+    else {
+      m_num_clusters = n;
+      m_initialNumClusters = n;
+    }
+  }
+
+
+  /**
+   * Get the number of clusters
+   *
+   * @return the number of clusters.
+   */
+  public int getNumClusters () {
+    return  m_initialNumClusters;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+    return "maximum number of iterations";
+  }
+
+  /**
+   * Set the maximum number of iterations to perform
+   *
+   * @param i the number of iterations
+   * @throws Exception if i is less than 1
+   */
+  public void setMaxIterations (int i)
+    throws Exception {
+    if (i < 1) {
+      throw  new Exception("Maximum number of iterations must be > 0!");
+    }
+
+    m_max_iterations = i;
+  }
+
+
+  /**
+   * Get the maximum number of iterations
+   *
+   * @return the number of iterations
+   */
+  public int getMaxIterations () {
+    return  m_max_iterations;
+  }
+
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "If set to true, clusterer may output additional info to " +
+      "the console.";
+  }
+
+
+  /**
+   * Set debug mode - verbose output
+   *
+   * @param v true for verbose output
+   */
+  public void setDebug (boolean v) {
+    m_verbose = v;
+  }
+
+
+  /**
+   * Get debug mode
+   *
+   * @return true if debug mode is set
+   */
+  public boolean getDebug () {
+    return  m_verbose;
+  }
+
+
+  /**
+   * Gets the current settings of EM.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    result.add("-I");
+    result.add("" + m_max_iterations);
+    result.add("-N");
+    result.add("" + getNumClusters());
+    result.add("-M");
+    result.add("" + getMinStdDev());
+    if (m_displayModelInOldFormat) {
+      result.add("-O");
+    }
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Initialise estimators and storage.
+   *
+   * @param inst the instances
+   * @throws Exception if initialization fails
+   **/
+  private void EM_Init (Instances inst)
+    throws Exception {
+    int i, j, k;
+
+    // run k means 10 times and choose best solution
+    SimpleKMeans bestK = null;
+    double bestSqE = Double.MAX_VALUE;
+    for (i = 0; i < 10; i++) {
+      SimpleKMeans sk = new SimpleKMeans();
+      sk.setSeed(m_rr.nextInt());
+      sk.setNumClusters(m_num_clusters);
+      sk.setDisplayStdDevs(true);
+      sk.buildClusterer(inst);
+      if (sk.getSquaredError() < bestSqE) {
+	bestSqE = sk.getSquaredError();
+	bestK = sk;
+      }
+    }
+    
+    // initialize with best k-means solution
+    m_num_clusters = bestK.numberOfClusters();
+    m_weights = new double[inst.numInstances()][m_num_clusters];
+    m_model = new DiscreteEstimator[m_num_clusters][m_num_attribs];
+    m_modelNormal = new double[m_num_clusters][m_num_attribs][3];
+    m_priors = new double[m_num_clusters];
+    Instances centers = bestK.getClusterCentroids();
+    Instances stdD = bestK.getClusterStandardDevs();
+    int [][][] nominalCounts = bestK.getClusterNominalCounts();
+    int [] clusterSizes = bestK.getClusterSizes();
+
+    for (i = 0; i < m_num_clusters; i++) {
+      Instance center = centers.instance(i);
+      for (j = 0; j < m_num_attribs; j++) {
+	if (inst.attribute(j).isNominal()) {
+	  m_model[i][j] = new DiscreteEstimator(m_theInstances.
+						attribute(j).numValues()
+						, true);
+	  for (k = 0; k < inst.attribute(j).numValues(); k++) {
+	    m_model[i][j].addValue(k, nominalCounts[i][j][k]);
+	  }
+	} else {
+	  double minStdD = (m_minStdDevPerAtt != null)
+	    ? m_minStdDevPerAtt[j]
+	    : m_minStdDev;
+	  double mean = (center.isMissing(j))
+	    ? inst.meanOrMode(j)
+	    : center.value(j);
+	  m_modelNormal[i][j][0] = mean;
+	  double stdv = (stdD.instance(i).isMissing(j))
+	    ? ((m_maxValues[j] - m_minValues[j]) / (2 * m_num_clusters))
+	    : stdD.instance(i).value(j);
+	  if (stdv < minStdD) {
+	    stdv = inst.attributeStats(j).numericStats.stdDev;
+            if (Double.isInfinite(stdv)) {
+              stdv = minStdD;
+            }
+	    if (stdv < minStdD) {
+	      stdv = minStdD;
+	    }
+	  }
+	  if (stdv <= 0) {
+	    stdv = m_minStdDev;
+	  }
+
+	  m_modelNormal[i][j][1] = stdv;
+	  m_modelNormal[i][j][2] = 1.0;
+	}
+      } 
+    }    
+    
+    
+    for (j = 0; j < m_num_clusters; j++) {
+      //      m_priors[j] += 1.0;
+      m_priors[j] = clusterSizes[j];
+    }
+    Utils.normalize(m_priors);
+  }
+
+
+  /**
+   * calculate prior probabilites for the clusters
+   *
+   * @param inst the instances
+   * @throws Exception if priors can't be calculated
+   **/
+  private void estimate_priors (Instances inst)
+    throws Exception {
+
+    for (int i = 0; i < m_num_clusters; i++) {
+      m_priors[i] = 0.0;
+    }
+
+    for (int i = 0; i < inst.numInstances(); i++) {
+      for (int j = 0; j < m_num_clusters; j++) {
+        m_priors[j] += inst.instance(i).weight() * m_weights[i][j];
+      }
+    }
+
+    Utils.normalize(m_priors);
+  }
+
+
+  /** Constant for normal distribution. */
+  private static double m_normConst = Math.log(Math.sqrt(2*Math.PI));
+
+  /**
+   * Density function of normal distribution.
+   * @param x input value
+   * @param mean mean of distribution
+   * @param stdDev standard deviation of distribution
+   * @return the density
+   */
+  private double logNormalDens (double x, double mean, double stdDev) {
+
+    double diff = x - mean;
+    //    System.err.println("x: "+x+" mean: "+mean+" diff: "+diff+" stdv: "+stdDev);
+    //    System.err.println("diff*diff/(2*stdv*stdv): "+ (diff * diff / (2 * stdDev * stdDev)));
+    
+    return - (diff * diff / (2 * stdDev * stdDev))  - m_normConst - Math.log(stdDev);
+  }
+
+  /**
+   * New probability estimators for an iteration
+   */
+  private void new_estimators () {
+    for (int i = 0; i < m_num_clusters; i++) {
+      for (int j = 0; j < m_num_attribs; j++) {
+        if (m_theInstances.attribute(j).isNominal()) {
+          m_model[i][j] = new DiscreteEstimator(m_theInstances.
+						attribute(j).numValues()
+						, true);
+        }
+        else {
+          m_modelNormal[i][j][0] = m_modelNormal[i][j][1] = 
+	    m_modelNormal[i][j][2] = 0.0;
+        }
+      }
+    }
+  }
+
+
+  /**
+   * The M step of the EM algorithm.
+   * @param inst the training instances
+   * @throws Exception if something goes wrong
+   */
+  private void M (Instances inst)
+    throws Exception {
+
+    int i, j, l;
+
+    new_estimators();
+
+    for (i = 0; i < m_num_clusters; i++) {
+      for (j = 0; j < m_num_attribs; j++) {
+        for (l = 0; l < inst.numInstances(); l++) {
+	  Instance in = inst.instance(l);
+          if (!in.isMissing(j)) {
+            if (inst.attribute(j).isNominal()) {
+              m_model[i][j].addValue(in.value(j), 
+				     in.weight() * m_weights[l][i]);
+            }
+            else {
+              m_modelNormal[i][j][0] += (in.value(j) * in.weight() *
+					 m_weights[l][i]);
+              m_modelNormal[i][j][2] += in.weight() * m_weights[l][i];
+              m_modelNormal[i][j][1] += (in.value(j) * 
+					 in.value(j) * in.weight() * m_weights[l][i]);
+            }
+          }
+        }
+      }
+    }
+    
+    // calcualte mean and std deviation for numeric attributes
+    for (j = 0; j < m_num_attribs; j++) {
+      if (!inst.attribute(j).isNominal()) {
+        for (i = 0; i < m_num_clusters; i++) {
+          if (m_modelNormal[i][j][2] <= 0) {
+            m_modelNormal[i][j][1] = Double.MAX_VALUE;
+	    //	    m_modelNormal[i][j][0] = 0;
+	    m_modelNormal[i][j][0] = m_minStdDev;
+          } else {
+	      
+	    // variance
+	    m_modelNormal[i][j][1] = (m_modelNormal[i][j][1] - 
+				      (m_modelNormal[i][j][0] * 
+				       m_modelNormal[i][j][0] / 
+				       m_modelNormal[i][j][2])) / 
+	      (m_modelNormal[i][j][2]);
+	    
+	    if (m_modelNormal[i][j][1] < 0) {
+	      m_modelNormal[i][j][1] = 0;
+	    }
+	    
+	    // std dev      
+	    double minStdD = (m_minStdDevPerAtt != null)
+	    ? m_minStdDevPerAtt[j]
+	    : m_minStdDev;
+
+	    m_modelNormal[i][j][1] = Math.sqrt(m_modelNormal[i][j][1]);              
+
+	    if ((m_modelNormal[i][j][1] <= minStdD)) {
+	      m_modelNormal[i][j][1] = inst.attributeStats(j).numericStats.stdDev;
+	      if ((m_modelNormal[i][j][1] <= minStdD)) {
+		m_modelNormal[i][j][1] = minStdD;
+	      }
+	    }
+	    if ((m_modelNormal[i][j][1] <= 0)) {
+	      m_modelNormal[i][j][1] = m_minStdDev;
+	    }
+            if (Double.isInfinite(m_modelNormal[i][j][1])) {
+              m_modelNormal[i][j][1] = m_minStdDev;
+            }
+	    
+	    // mean
+	    m_modelNormal[i][j][0] /= m_modelNormal[i][j][2];
+	  }
+        }
+      }
+    }
+  }
+
+  /**
+   * The E step of the EM algorithm. Estimate cluster membership 
+   * probabilities.
+   *
+   * @param inst the training instances
+   * @param change_weights whether to change the weights
+   * @return the average log likelihood
+   * @throws Exception if computation fails
+   */
+  private double E (Instances inst, boolean change_weights)
+    throws Exception {
+
+    double loglk = 0.0, sOW = 0.0;
+
+    for (int l = 0; l < inst.numInstances(); l++) {
+
+      Instance in = inst.instance(l);
+
+      loglk += in.weight() * logDensityForInstance(in);
+      sOW += in.weight();
+
+      if (change_weights) {
+	m_weights[l] = distributionForInstance(in);
+      }
+    }
+    
+    // reestimate priors
+    if (change_weights) {
+      estimate_priors(inst);
+    }
+    return  loglk / sOW;
+  }
+  
+  
+  /**
+   * Constructor.
+   *
+   **/
+  public EM () {
+    super();
+    
+    m_SeedDefault = 100;
+    resetOptions();
+  }
+
+
+  /**
+   * Reset to default options
+   */
+  protected void resetOptions () {
+    m_minStdDev = 1e-6;
+    m_max_iterations = 100;
+    m_Seed = m_SeedDefault;
+    m_num_clusters = -1;
+    m_initialNumClusters = -1;
+    m_verbose = false;
+  }
+
+  /**
+   * Return the normal distributions for the cluster models
+   *
+   * @return a <code>double[][][]</code> value
+   */
+  public double [][][] getClusterModelsNumericAtts() {
+    return m_modelNormal;
+  }
+
+  /**
+   * Return the priors for the clusters
+   *
+   * @return a <code>double[]</code> value
+   */
+  public double [] getClusterPriors() {
+    return m_priors;
+  }
+
+  /**
+   * Outputs the generated clusters into a string.
+   * 
+   * @return the clusterer in string representation
+   */
+  public String toString() {
+    if (m_displayModelInOldFormat) {
+      return toStringOriginal();
+    }
+
+    if (m_priors == null) {
+      return "No clusterer built yet!";
+    }
+    StringBuffer temp = new StringBuffer();
+    temp.append("\nEM\n==\n");
+    if (m_initialNumClusters == -1) {
+      temp.append("\nNumber of clusters selected by cross validation: "
+		  +m_num_clusters+"\n");
+    } else {
+      temp.append("\nNumber of clusters: " + m_num_clusters + "\n");
+    }
+
+    int maxWidth = 0;
+    int maxAttWidth = 0;
+    boolean containsKernel = false;
+    
+    // set up max widths
+    // attributes
+    for (int i = 0; i < m_num_attribs; i++) {
+      Attribute a = m_theInstances.attribute(i);
+      if (a.name().length() > maxAttWidth) {
+        maxAttWidth = m_theInstances.attribute(i).name().length();
+      }
+      if (a.isNominal()) {
+        // check values
+        for (int j = 0; j < a.numValues(); j++) {
+          String val = a.value(j) + "  ";
+          if (val.length() > maxAttWidth) {
+            maxAttWidth = val.length();
+          }
+        }
+      }
+    }
+
+    for (int i = 0; i < m_num_clusters; i++) {
+      for (int j = 0; j < m_num_attribs; j++) {
+        if (m_theInstances.attribute(j).isNumeric()) {
+          // check mean and std. dev. against maxWidth
+          double mean = Math.log(Math.abs(m_modelNormal[i][j][0])) / Math.log(10.0);
+          double stdD = Math.log(Math.abs(m_modelNormal[i][j][1])) / Math.log(10.0);
+          double width = (mean > stdD)
+            ? mean
+            : stdD;
+          if (width < 0) {
+            width = 1;
+          }
+          // decimal + # decimal places + 1
+          width += 6.0;
+          if ((int)width > maxWidth) {
+            maxWidth = (int)width;
+          }
+        } else {
+          // nominal distributions
+          DiscreteEstimator d = (DiscreteEstimator)m_model[i][j];
+          for (int k = 0; k < d.getNumSymbols(); k++) {
+            String size = Utils.doubleToString(d.getCount(k), maxWidth, 4).trim();
+            if (size.length() > maxWidth) {
+              maxWidth = size.length();
+            }
+          }
+          int sum = 
+            Utils.doubleToString(d.getSumOfCounts(), maxWidth, 4).trim().length();
+          if (sum > maxWidth) {
+            maxWidth = sum;
+          }
+        }
+      }
+    }
+
+    if (maxAttWidth < "Attribute".length()) {
+      maxAttWidth = "Attribute".length();
+    }    
+    
+    maxAttWidth += 2;
+
+    temp.append("\n\n");
+    temp.append(pad("Cluster", " ", 
+                    (maxAttWidth + maxWidth + 1) - "Cluster".length(), 
+                    true));
+    
+    temp.append("\n");
+    temp.append(pad("Attribute", " ", maxAttWidth - "Attribute".length(), false));
+
+    // cluster #'s
+    for (int i = 0; i < m_num_clusters; i++) {
+      String classL = "" + i;
+      temp.append(pad(classL, " ", maxWidth + 1 - classL.length(), true));
+    }
+    temp.append("\n");
+
+    // cluster priors
+    temp.append(pad("", " ", maxAttWidth, true));
+    for (int i = 0; i < m_num_clusters; i++) {
+      String priorP = Utils.doubleToString(m_priors[i], maxWidth, 2).trim();
+      priorP = "(" + priorP + ")";
+      temp.append(pad(priorP, " ", maxWidth + 1 - priorP.length(), true));
+    }
+
+    temp.append("\n");
+    temp.append(pad("", "=", maxAttWidth + 
+                    (maxWidth * m_num_clusters)
+                    + m_num_clusters + 1, true));
+    temp.append("\n");
+
+    for (int i = 0; i < m_num_attribs; i++) {
+      String attName = m_theInstances.attribute(i).name();
+      temp.append(attName + "\n");
+
+      if (m_theInstances.attribute(i).isNumeric()) {
+        String meanL = "  mean";
+        temp.append(pad(meanL, " ", maxAttWidth + 1 - meanL.length(), false));
+        for (int j = 0; j < m_num_clusters; j++) {
+          // means
+          String mean = 
+            Utils.doubleToString(m_modelNormal[j][i][0], maxWidth, 4).trim();
+          temp.append(pad(mean, " ", maxWidth + 1 - mean.length(), true));
+        }
+        temp.append("\n");            
+        // now do std deviations
+        String stdDevL = "  std. dev.";
+        temp.append(pad(stdDevL, " ", maxAttWidth + 1 - stdDevL.length(), false));
+        for (int j = 0; j < m_num_clusters; j++) {
+          String stdDev = 
+            Utils.doubleToString(m_modelNormal[j][i][1], maxWidth, 4).trim();
+          temp.append(pad(stdDev, " ", maxWidth + 1 - stdDev.length(), true));
+        }
+        temp.append("\n\n");
+      } else {
+        Attribute a = m_theInstances.attribute(i);
+        for (int j = 0; j < a.numValues(); j++) {
+          String val = "  " + a.value(j);
+          temp.append(pad(val, " ", maxAttWidth + 1 - val.length(), false));
+          for (int k = 0; k < m_num_clusters; k++) {
+            DiscreteEstimator d = (DiscreteEstimator)m_model[k][i];
+            String count = Utils.doubleToString(d.getCount(j), maxWidth, 4).trim();
+            temp.append(pad(count, " ", maxWidth + 1 - count.length(), true));
+          }
+          temp.append("\n");
+        }
+        // do the totals
+        String total = "  [total]";
+        temp.append(pad(total, " ", maxAttWidth + 1 - total.length(), false));
+        for (int k = 0; k < m_num_clusters; k++) {
+          DiscreteEstimator d = (DiscreteEstimator)m_model[k][i];
+          String count = 
+            Utils.doubleToString(d.getSumOfCounts(), maxWidth, 4).trim();
+            temp.append(pad(count, " ", maxWidth + 1 - count.length(), true));
+        }
+        temp.append("\n");        
+      }
+    }
+
+    return temp.toString();
+  }
+
+  private String pad(String source, String padChar, 
+                     int length, boolean leftPad) {
+    StringBuffer temp = new StringBuffer();
+
+    if (leftPad) {
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+      temp.append(source);
+    } else {
+      temp.append(source);
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+    }
+    return temp.toString();
+  }
+
+  /**
+   * Outputs the generated clusters into a string.
+   * 
+   * @return the clusterer in string representation
+   */
+  protected String toStringOriginal () {
+    if (m_priors == null) {
+      return "No clusterer built yet!";
+    }
+    StringBuffer temp = new StringBuffer();
+    temp.append("\nEM\n==\n");
+    if (m_initialNumClusters == -1) {
+      temp.append("\nNumber of clusters selected by cross validation: "
+		  +m_num_clusters+"\n");
+    } else {
+      temp.append("\nNumber of clusters: " + m_num_clusters + "\n");
+    }
+
+    for (int j = 0; j < m_num_clusters; j++) {
+      temp.append("\nCluster: " + j + " Prior probability: " 
+		  + Utils.doubleToString(m_priors[j], 4) + "\n\n");
+
+      for (int i = 0; i < m_num_attribs; i++) {
+        temp.append("Attribute: " + m_theInstances.attribute(i).name() + "\n");
+
+        if (m_theInstances.attribute(i).isNominal()) {
+          if (m_model[j][i] != null) {
+            temp.append(m_model[j][i].toString());
+          }
+        }
+        else {
+          temp.append("Normal Distribution. Mean = " 
+		      + Utils.doubleToString(m_modelNormal[j][i][0], 4) 
+		      + " StdDev = " 
+		      + Utils.doubleToString(m_modelNormal[j][i][1], 4) 
+		      + "\n");
+        }
+      }
+    }
+
+    return  temp.toString();
+  }
+
+
+  /**
+   * verbose output for debugging
+   * @param inst the training instances
+   */
+  private void EM_Report (Instances inst) {
+    int i, j, l, m;
+    System.out.println("======================================");
+
+    for (j = 0; j < m_num_clusters; j++) {
+      for (i = 0; i < m_num_attribs; i++) {
+	System.out.println("Clust: " + j + " att: " + i + "\n");
+
+	if (m_theInstances.attribute(i).isNominal()) {
+	  if (m_model[j][i] != null) {
+	    System.out.println(m_model[j][i].toString());
+	  }
+	}
+	else {
+	  System.out.println("Normal Distribution. Mean = " 
+			     + Utils.doubleToString(m_modelNormal[j][i][0]
+						    , 8, 4) 
+			     + " StandardDev = " 
+			     + Utils.doubleToString(m_modelNormal[j][i][1]
+						    , 8, 4) 
+			     + " WeightSum = " 
+			     + Utils.doubleToString(m_modelNormal[j][i][2]
+						    , 8, 4));
+	}
+      }
+    }
+    
+    for (l = 0; l < inst.numInstances(); l++) {
+      m = Utils.maxIndex(m_weights[l]);
+      System.out.print("Inst " + Utils.doubleToString((double)l, 5, 0) 
+		       + " Class " + m + "\t");
+      for (j = 0; j < m_num_clusters; j++) {
+	System.out.print(Utils.doubleToString(m_weights[l][j], 7, 5) + "  ");
+      }
+      System.out.println();
+    }
+  }
+
+
+  /**
+   * estimate the number of clusters by cross validation on the training
+   * data.
+   *
+   * @throws Exception if something goes wrong
+   */
+  private void CVClusters ()
+    throws Exception {
+    double CVLogLikely = -Double.MAX_VALUE;
+    double templl, tll;
+    boolean CVincreased = true;
+    m_num_clusters = 1;
+    int num_clusters = m_num_clusters;
+    int i;
+    Random cvr;
+    Instances trainCopy;
+    int numFolds = (m_theInstances.numInstances() < 10) 
+      ? m_theInstances.numInstances() 
+      : 10;
+
+    boolean ok = true;
+    int seed = getSeed();
+    int restartCount = 0;
+    CLUSTER_SEARCH: while (CVincreased) {
+      // theInstances.stratify(10);
+        
+      CVincreased = false;
+      cvr = new Random(getSeed());
+      trainCopy = new Instances(m_theInstances);
+      trainCopy.randomize(cvr);
+      templl = 0.0;
+      for (i = 0; i < numFolds; i++) {
+	Instances cvTrain = trainCopy.trainCV(numFolds, i, cvr);
+	if (num_clusters > cvTrain.numInstances()) {
+	  break CLUSTER_SEARCH;
+	}
+	Instances cvTest = trainCopy.testCV(numFolds, i);
+	m_rr = new Random(seed);
+        for (int z=0; z<10; z++) m_rr.nextDouble();
+	m_num_clusters = num_clusters;
+	EM_Init(cvTrain);
+	try {
+	  iterate(cvTrain, false);
+	} catch (Exception ex) {
+	  // catch any problems - i.e. empty clusters occuring
+	  ex.printStackTrace();
+          //          System.err.println("Restarting after CV training failure ("+num_clusters+" clusters");
+          seed++;
+          restartCount++;
+          ok = false;
+          if (restartCount > 5) {
+            break CLUSTER_SEARCH;
+          }
+	  break;
+	}
+        try {
+          tll = E(cvTest, false);
+        } catch (Exception ex) {
+          // catch any problems - i.e. empty clusters occuring
+          //          ex.printStackTrace();
+          ex.printStackTrace();
+          //          System.err.println("Restarting after CV testing failure ("+num_clusters+" clusters");
+          //          throw new Exception(ex); 
+          seed++;
+          restartCount++;
+          ok = false;
+          if (restartCount > 5) {
+            break CLUSTER_SEARCH;
+          }
+          break;
+        }
+
+	if (m_verbose) {
+	  System.out.println("# clust: " + num_clusters + " Fold: " + i 
+			     + " Loglikely: " + tll);
+	}
+	templl += tll;
+      }
+
+      if (ok) {
+        restartCount = 0;
+        seed = getSeed();
+        templl /= (double)numFolds;
+        
+        if (m_verbose) {
+          System.out.println("===================================" 
+                             + "==============\n# clust: " 
+                             + num_clusters 
+                             + " Mean Loglikely: " 
+                             + templl 
+                             + "\n================================" 
+                             + "=================");
+        }
+        
+        if (templl > CVLogLikely) {
+          CVLogLikely = templl;
+          CVincreased = true;
+          num_clusters++;
+        }
+      }
+    }
+
+    if (m_verbose) {
+      System.out.println("Number of clusters: " + (num_clusters - 1));
+    }
+
+    m_num_clusters = num_clusters - 1;
+  }
+
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @throws Exception if number of clusters could not be returned
+   * successfully
+   */
+  public int numberOfClusters ()
+    throws Exception {
+    if (m_num_clusters == -1) {
+      throw  new Exception("Haven't generated any clusters!");
+    }
+
+    return  m_num_clusters;
+  }
+
+ /**
+  * Updates the minimum and maximum values for all the attributes
+  * based on a new instance.
+  *
+  * @param instance the new instance
+  */
+  private void updateMinMax(Instance instance) {
+    
+    for (int j = 0; j < m_theInstances.numAttributes(); j++) {
+      if (!instance.isMissing(j)) {
+	if (Double.isNaN(m_minValues[j])) {
+	  m_minValues[j] = instance.value(j);
+	  m_maxValues[j] = instance.value(j);
+	} else {
+	  if (instance.value(j) < m_minValues[j]) {
+	    m_minValues[j] = instance.value(j);
+	  } else {
+	    if (instance.value(j) > m_maxValues[j]) {
+	      m_maxValues[j] = instance.value(j);
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Returns default capabilities of the clusterer (i.e., the ones of 
+   * SimpleKMeans).
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new SimpleKMeans().getCapabilities();
+    result.setOwner(this);
+    return result;
+  }
+  
+  /**
+   * Generates a clusterer. Has to initialize all fields of the clusterer
+   * that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the clusterer has not been 
+   * generated successfully
+   */
+  public void buildClusterer (Instances data)
+    throws Exception {
+    
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+
+    m_replaceMissing = new ReplaceMissingValues();
+    Instances instances = new Instances(data);
+    instances.setClassIndex(-1);
+    m_replaceMissing.setInputFormat(instances);
+    data = weka.filters.Filter.useFilter(instances, m_replaceMissing);
+    instances = null;
+    
+    m_theInstances = data;
+
+    // calculate min and max values for attributes
+    m_minValues = new double [m_theInstances.numAttributes()];
+    m_maxValues = new double [m_theInstances.numAttributes()];
+    for (int i = 0; i < m_theInstances.numAttributes(); i++) {
+      m_minValues[i] = m_maxValues[i] = Double.NaN;
+    }
+    for (int i = 0; i < m_theInstances.numInstances(); i++) {
+      updateMinMax(m_theInstances.instance(i));
+    }
+
+    doEM();
+    
+    // save memory
+    m_theInstances = new Instances(m_theInstances,0);
+  }
+
+  /**
+   * Returns the cluster priors.
+   * 
+   * @return the cluster priors
+   */
+  public double[] clusterPriors() {
+
+    double[] n = new double[m_priors.length];
+  
+    System.arraycopy(m_priors, 0, n, 0, n.length);
+    return n;
+  }
+
+  /**
+   * Computes the log of the conditional density (per cluster) for a given instance.
+   * 
+   * @param inst the instance to compute the density for
+   * @return an array containing the estimated densities
+   * @throws Exception if the density could not be computed
+   * successfully
+   */
+  public double[] logDensityPerClusterForInstance(Instance inst) throws Exception {
+
+    int i, j;
+    double logprob;
+    double[] wghts = new double[m_num_clusters];
+    
+    m_replaceMissing.input(inst);
+    inst = m_replaceMissing.output();
+
+    for (i = 0; i < m_num_clusters; i++) {
+      //      System.err.println("Cluster : "+i);
+      logprob = 0.0;
+
+      for (j = 0; j < m_num_attribs; j++) {
+	if (!inst.isMissing(j)) {
+	  if (inst.attribute(j).isNominal()) {
+	    logprob += Math.log(m_model[i][j].getProbability(inst.value(j)));
+	  }
+	  else { // numeric attribute
+	    logprob += logNormalDens(inst.value(j), 
+				     m_modelNormal[i][j][0], 
+				     m_modelNormal[i][j][1]);
+	    /*	    System.err.println(logNormalDens(inst.value(j), 
+				     m_modelNormal[i][j][0], 
+				     m_modelNormal[i][j][1]) + " "); */
+	  }
+	}
+      }
+      //      System.err.println("");
+
+      wghts[i] = logprob;
+    }
+    return  wghts;
+  }
+
+
+  /**
+   * Perform the EM algorithm
+   * 
+   * @throws Exception if something goes wrong
+   */
+  private void doEM ()
+    throws Exception {
+    
+    if (m_verbose) {
+      System.out.println("Seed: " + getSeed());
+    }
+
+    m_rr = new Random(getSeed());
+
+    // throw away numbers to avoid problem of similar initial numbers
+    // from a similar seed
+    for (int i=0; i<10; i++) m_rr.nextDouble();
+
+    m_num_instances = m_theInstances.numInstances();
+    m_num_attribs = m_theInstances.numAttributes();
+
+    if (m_verbose) {
+      System.out.println("Number of instances: " 
+			 + m_num_instances 
+			 + "\nNumber of atts: " 
+			 + m_num_attribs 
+			 + "\n");
+    }
+
+    // setDefaultStdDevs(theInstances);
+    // cross validate to determine number of clusters?
+    if (m_initialNumClusters == -1) {
+      if (m_theInstances.numInstances() > 9) {
+	CVClusters();
+	m_rr = new Random(getSeed());
+	for (int i=0; i<10; i++) m_rr.nextDouble();
+      } else {
+	m_num_clusters = 1;
+      }
+    }
+
+    // fit full training set
+    EM_Init(m_theInstances);
+    m_loglikely = iterate(m_theInstances, m_verbose);
+  }
+
+
+  /**
+   * iterates the E and M steps until the log likelihood of the data
+   * converges.
+   *
+   * @param inst the training instances.
+   * @param report be verbose.
+   * @return the log likelihood of the data
+   * @throws Exception if something goes wrong
+   */
+  private double iterate (Instances inst, boolean report)
+    throws Exception {
+    
+    int i;
+    double llkold = 0.0;
+    double llk = 0.0;
+
+    if (report) {
+      EM_Report(inst);
+    }
+
+    boolean ok = false;
+    int seed = getSeed();
+    int restartCount = 0;
+    while (!ok) {
+      try {
+        for (i = 0; i < m_max_iterations; i++) {
+          llkold = llk;
+          llk = E(inst, true);
+          
+          if (report) {
+            System.out.println("Loglikely: " + llk);
+          }
+          
+          if (i > 0) {
+            if ((llk - llkold) < 1e-6) {
+              break;
+            }
+          }
+          M(inst);
+        }
+        ok = true;
+      } catch (Exception ex) {
+        //        System.err.println("Restarting after training failure");
+        ex.printStackTrace();
+        seed++;
+        restartCount++;
+        m_rr = new Random(seed);
+        for (int z = 0; z < 10; z++) {
+          m_rr.nextDouble(); m_rr.nextInt();
+        }
+        if (restartCount > 5) {
+          //          System.err.println("Reducing the number of clusters");
+          m_num_clusters--;
+          restartCount = 0;
+        }
+        EM_Init(m_theInstances);
+      }
+    }
+      
+    if (report) {
+      EM_Report(inst);
+    }
+
+    return  llk;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.44 $");
+  }
+
+  // ============
+  // Test method.
+  // ============
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments: <p>
+   * -t training file [-T test file] [-N number of clusters] [-S random seed]
+   */
+  public static void main (String[] argv) {
+    runClusterer(new EM(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/FarthestFirst.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/FarthestFirst.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/FarthestFirst.java	(revision 29)
@@ -0,0 +1,621 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FarthestFirst.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.clusterers;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Cluster data using the FarthestFirst algorithm.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Hochbaum, Shmoys (1985). A best possible heuristic for the k-center problem. Mathematics of Operations Research. 10(2):180-184.<br/>
+ * <br/>
+ * Sanjoy Dasgupta: Performance Guarantees for Hierarchical Clustering. In: 15th Annual Conference on Computational Learning Theory, 351-363, 2002.<br/>
+ * <br/>
+ * Notes:<br/>
+ * - works as a fast simple approximate clusterer<br/>
+ * - modelled after SimpleKMeans, might be a useful initializer for it
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Hochbaum1985,
+ *    author = {Hochbaum and Shmoys},
+ *    journal = {Mathematics of Operations Research},
+ *    number = {2},
+ *    pages = {180-184},
+ *    title = {A best possible heuristic for the k-center problem},
+ *    volume = {10},
+ *    year = {1985}
+ * }
+ * 
+ * &#64;inproceedings{Dasgupta2002,
+ *    author = {Sanjoy Dasgupta},
+ *    booktitle = {15th Annual Conference on Computational Learning Theory},
+ *    pages = {351-363},
+ *    publisher = {Springer},
+ *    title = {Performance Guarantees for Hierarchical Clustering},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters. (default = 2).</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Bernhard Pfahringer (bernhard@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ * @see RandomizableClusterer
+ */
+public class FarthestFirst 
+  extends RandomizableClusterer 
+  implements TechnicalInformationHandler {
+
+  //Todo: rewrite to be fully incremental
+  //      cleanup, like deleting m_instances 
+
+  /** for serialization */
+  static final long serialVersionUID = 7499838100631329509L;
+  
+  /**
+   * training instances, not necessary to keep, 
+   * could be replaced by m_ClusterCentroids where needed for header info
+   */
+  protected Instances m_instances;
+
+  /**
+   * replace missing values in training instances
+   */
+  protected ReplaceMissingValues m_ReplaceMissingFilter;
+
+  /**
+   * number of clusters to generate
+   */
+  protected int m_NumClusters = 2;
+
+  /**
+   * holds the cluster centroids
+   */
+  protected Instances m_ClusterCentroids;
+
+  /**
+   * attribute min values
+   */
+  private double [] m_Min;
+  
+  /**
+   * attribute max values
+   */
+  private double [] m_Max;
+
+  /**
+   * Returns a string describing this clusterer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Cluster data using the FarthestFirst algorithm.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString() + "\n\n"
+      + "Notes:\n"
+      + "- works as a fast simple approximate clusterer\n"
+      + "- modelled after SimpleKMeans, might be a useful initializer for it";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Hochbaum and Shmoys");
+    result.setValue(Field.YEAR, "1985");
+    result.setValue(Field.TITLE, "A best possible heuristic for the k-center problem");
+    result.setValue(Field.JOURNAL, "Mathematics of Operations Research");
+    result.setValue(Field.VOLUME, "10");
+    result.setValue(Field.NUMBER, "2");
+    result.setValue(Field.PAGES, "180-184");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Sanjoy Dasgupta");
+    additional.setValue(Field.TITLE, "Performance Guarantees for Hierarchical Clustering");
+    additional.setValue(Field.BOOKTITLE, "15th Annual Conference on Computational Learning Theory");
+    additional.setValue(Field.YEAR, "2002");
+    additional.setValue(Field.PAGES, "351-363");
+    additional.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Generates a clusterer. Has to initialize all fields of the clusterer
+   * that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the clusterer has not been 
+   * generated successfully
+   */
+  public void buildClusterer(Instances data) throws Exception {
+
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+
+    //long start = System.currentTimeMillis();
+
+    m_ReplaceMissingFilter = new ReplaceMissingValues();
+    m_ReplaceMissingFilter.setInputFormat(data);
+    m_instances = Filter.useFilter(data, m_ReplaceMissingFilter);
+
+    initMinMax(m_instances);
+
+    m_ClusterCentroids = new Instances(m_instances, m_NumClusters);
+
+    int n = m_instances.numInstances();
+    Random r = new Random(getSeed());
+    boolean[] selected = new boolean[n];
+    double[] minDistance = new double[n];
+
+    for(int i = 0; i<n; i++) minDistance[i] = Double.MAX_VALUE;
+
+    int firstI = r.nextInt(n);
+    m_ClusterCentroids.add(m_instances.instance(firstI));
+    selected[firstI] = true;
+
+    updateMinDistance(minDistance,selected,m_instances,m_instances.instance(firstI));
+
+    if (m_NumClusters > n) m_NumClusters = n;
+
+    for(int i = 1; i < m_NumClusters; i++) {
+      int nextI =  farthestAway(minDistance, selected);
+      m_ClusterCentroids.add(m_instances.instance(nextI));
+      selected[nextI] = true;
+      updateMinDistance(minDistance,selected,m_instances,m_instances.instance(nextI));
+    }
+
+    m_instances = new Instances(m_instances,0);
+    //long end = System.currentTimeMillis();
+    //System.out.println("Clustering Time = " + (end-start));
+  }
+
+
+  protected void updateMinDistance(double[] minDistance, boolean[] selected, 
+				   Instances data, Instance center) {
+    for(int i = 0; i<selected.length; i++) 
+      if (!selected[i]) {
+	double d = distance(center,data.instance(i));
+	if (d<minDistance[i]) 
+	  minDistance[i] = d;
+      }
+  }
+
+  protected int farthestAway(double[] minDistance, boolean[] selected) {
+    double maxDistance = -1.0;
+    int maxI = -1;
+    for(int i = 0; i<selected.length; i++) 
+      if (!selected[i]) 
+	if (maxDistance < minDistance[i]) {
+	  maxDistance = minDistance[i];
+	  maxI = i;
+	}
+    return maxI;
+  }
+
+  protected void initMinMax(Instances data) {
+    m_Min = new double [data.numAttributes()];
+    m_Max = new double [data.numAttributes()];
+    for (int i = 0; i < data.numAttributes(); i++) {
+      m_Min[i] = m_Max[i] = Double.NaN;
+    }
+
+    for (int i = 0; i < data.numInstances(); i++) {
+      updateMinMax(data.instance(i));
+    }
+  }
+
+
+  /**
+   * Updates the minimum and maximum values for all the attributes
+   * based on a new instance.
+   *
+   * @param instance the new instance
+   */
+  private void updateMinMax(Instance instance) {  
+
+    for (int j = 0;j < instance.numAttributes(); j++) {
+      if (Double.isNaN(m_Min[j])) {
+	m_Min[j] = instance.value(j);
+	m_Max[j] = instance.value(j);
+      } else {
+	if (instance.value(j) < m_Min[j]) {
+	  m_Min[j] = instance.value(j);
+	} else {
+	  if (instance.value(j) > m_Max[j]) {
+	    m_Max[j] = instance.value(j);
+	  }
+	}
+      }
+    }
+  }
+
+
+  /**
+   * clusters an instance that has been through the filters
+   *
+   * @param instance the instance to assign a cluster to
+   * @return a cluster number
+   */
+  protected int clusterProcessedInstance(Instance instance) {
+    double minDist = Double.MAX_VALUE;
+    int bestCluster = 0;
+    for (int i = 0; i < m_NumClusters; i++) {
+      double dist = distance(instance, m_ClusterCentroids.instance(i));
+      if (dist < minDist) {
+	minDist = dist;
+	bestCluster = i;
+      }
+    }
+    return bestCluster;
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an integer
+   * if the class is enumerated, otherwise the predicted value
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    m_ReplaceMissingFilter.input(instance);
+    m_ReplaceMissingFilter.batchFinished();
+    Instance inst = m_ReplaceMissingFilter.output();
+
+    return clusterProcessedInstance(inst);
+  }
+
+  /**
+   * Calculates the distance between two instances
+   *
+   * @param first the first instance
+   * @param second the second instance
+   * @return the distance between the two given instances, between 0 and 1
+   */          
+  protected double distance(Instance first, Instance second) {  
+
+    double distance = 0;
+    int firstI, secondI;
+
+    for (int p1 = 0, p2 = 0; 
+	 p1 < first.numValues() || p2 < second.numValues();) {
+      if (p1 >= first.numValues()) {
+	firstI = m_instances.numAttributes();
+      } else {
+	firstI = first.index(p1); 
+      }
+      if (p2 >= second.numValues()) {
+	secondI = m_instances.numAttributes();
+      } else {
+	secondI = second.index(p2);
+      }
+      if (firstI == m_instances.classIndex()) {
+	p1++; continue;
+      } 
+      if (secondI == m_instances.classIndex()) {
+	p2++; continue;
+      } 
+      double diff;
+      if (firstI == secondI) {
+	diff = difference(firstI, 
+			  first.valueSparse(p1),
+			  second.valueSparse(p2));
+	p1++; p2++;
+      } else if (firstI > secondI) {
+	diff = difference(secondI, 
+			  0, second.valueSparse(p2));
+	p2++;
+      } else {
+	diff = difference(firstI, 
+			  first.valueSparse(p1), 0);
+	p1++;
+      }
+      distance += diff * diff;
+    }
+    
+    return Math.sqrt(distance / m_instances.numAttributes());
+  }
+
+  /**
+   * Computes the difference between two given attribute
+   * values.
+   */
+  protected double difference(int index, double val1, double val2) {
+
+    switch (m_instances.attribute(index).type()) {
+    case Attribute.NOMINAL:
+      
+      // If attribute is nominal
+      if (Utils.isMissingValue(val1) || 
+	  Utils.isMissingValue(val2) ||
+	  ((int)val1 != (int)val2)) {
+	return 1;
+      } else {
+	return 0;
+      }
+    case Attribute.NUMERIC:
+
+      // If attribute is numeric
+      if (Utils.isMissingValue(val1) || 
+	  Utils.isMissingValue(val2)) {
+	if (Utils.isMissingValue(val1) && 
+	    Utils.isMissingValue(val2)) {
+	  return 1;
+	} else {
+	  double diff;
+	  if (Utils.isMissingValue(val2)) {
+	    diff = norm(val1, index);
+	  } else {
+	    diff = norm(val2, index);
+	  }
+	  if (diff < 0.5) {
+	    diff = 1.0 - diff;
+	  }
+	  return diff;
+	}
+      } else {
+	return norm(val1, index) - norm(val2, index);
+      }
+    default:
+      return 0;
+    }
+  }
+
+  /**
+   * Normalizes a given value of a numeric attribute.
+   *
+   * @param x the value to be normalized
+   * @param i the attribute's index
+   * @return the normalized value
+   */
+  protected double norm(double x, int i) {
+
+    if (Double.isNaN(m_Min[i]) || Utils.eq(m_Max[i],m_Min[i])) {
+      return 0;
+    } else {
+      return (x - m_Min[i]) / (m_Max[i] - m_Min[i]);
+    }
+  }
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @throws Exception if number of clusters could not be returned
+   * successfully
+   */
+  public int numberOfClusters() throws Exception {
+    return m_NumClusters;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions () {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tnumber of clusters. (default = 2).", 
+	"N", 1, "-N <num>"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    return  result.elements();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numClustersTipText() {
+    return "set number of clusters";
+  }
+
+  /**
+   * set the number of clusters to generate
+   *
+   * @param n the number of clusters to generate
+   * @throws Exception if number of clusters is negative
+   */
+  public void setNumClusters(int n) throws Exception {
+    if (n < 0) {
+      throw new Exception("Number of clusters must be > 0");
+    }
+    m_NumClusters = n;
+  }
+
+  /**
+   * gets the number of clusters to generate
+   *
+   * @return the number of clusters to generate
+   */
+  public int getNumClusters() {
+    return m_NumClusters;
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters. (default = 2).</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+
+    String optionString = Utils.getOption('N', options);
+
+    if (optionString.length() != 0) {
+      setNumClusters(Integer.parseInt(optionString));
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of FarthestFirst
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    result.add("-N");
+    result.add("" + getNumClusters());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * return a string describing this clusterer
+   *
+   * @return a description of the clusterer as a string
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+
+    temp.append("\n FarthestFirst\n==============\n");
+
+    temp.append("\nCluster centroids:\n");
+    for (int i = 0; i < m_NumClusters; i++) {
+      temp.append("\nCluster "+i+"\n\t");
+      for (int j = 0; j < m_ClusterCentroids.numAttributes(); j++) {
+	if (m_ClusterCentroids.attribute(j).isNominal()) {
+	  temp.append(" "+m_ClusterCentroids.attribute(j).
+		      value((int)m_ClusterCentroids.instance(i).value(j)));
+	} else {
+	  temp.append(" "+m_ClusterCentroids.instance(i).value(j));
+	}
+      }
+    }
+    temp.append("\n\n");
+    return temp.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments: <p>
+   * -t training file [-N number of clusters]
+   */
+  public static void main (String[] argv) {
+    runClusterer(new FarthestFirst(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/FilteredClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/FilteredClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/FilteredClusterer.java	(revision 29)
@@ -0,0 +1,401 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * FilteredClusterer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for running an arbitrary clusterer on data that has been passed through an arbitrary filter. Like the clusterer, the structure of the filter is based exclusively on the training data and test instances will be processed by the filter without changing their structure.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  Full class name of filter to use, followed
+ *  by filter options.
+ *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"
+ * (default: weka.filters.AllFilter)</pre>
+ * 
+ * <pre> -W
+ *  Full name of base clusterer.
+ *  (default: weka.clusterers.SimpleKMeans)</pre>
+ * 
+ * <pre> 
+ * Options specific to clusterer weka.clusterers.SimpleKMeans:
+ * </pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -V
+ *  Display std. deviations for centroids.
+ * </pre>
+ * 
+ * <pre> -M
+ *  Replace missing values with mean/mode.
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Based on code from the FilteredClassifier by Len Trigg.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5488 $
+ * @see weka.classifiers.meta.FilteredClassifier
+ */
+public class FilteredClusterer
+  extends SingleClustererEnhancer {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1420005943163412943L;
+  
+  /** The filter. */
+  protected Filter m_Filter;
+
+  /** The instance structure of the filtered instances. */
+  protected Instances m_FilteredInstances;
+
+  /**
+   * Default constructor.
+   */
+  public FilteredClusterer() {
+    m_Clusterer = new SimpleKMeans();
+    m_Filter    = new weka.filters.AllFilter();
+  }
+
+  /**
+   * Returns a string describing this clusterer.
+   * 
+   * @return 		a description of the clusterer suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Class for running an arbitrary clusterer on data that has been passed "
+      + "through an arbitrary filter. Like the clusterer, the structure of the filter "
+      + "is based exclusively on the training data and test instances will be processed "
+      + "by the filter without changing their structure.";
+  }
+
+  /**
+   * String describing default filter.
+   * 
+   * @return 		the default filter classname
+   */
+  protected String defaultFilterString() {
+    return weka.filters.AllFilter.class.getName();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tFull class name of filter to use, followed\n"
+	+ "\tby filter options.\n"
+	+ "\teg: \"weka.filters.unsupervised.attribute.Remove -V -R 1,2\"\n"
+	+ "(default: " + defaultFilterString() + ")",
+	"F", 1, "-F <filter specification>"));
+
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  Full class name of filter to use, followed
+   *  by filter options.
+   *  eg: "weka.filters.unsupervised.attribute.Remove -V -R 1,2"
+   * (default: weka.filters.AllFilter)</pre>
+   * 
+   * <pre> -W
+   *  Full name of base clusterer.
+   *  (default: weka.clusterers.SimpleKMeans)</pre>
+   * 
+   * <pre> 
+   * Options specific to clusterer weka.clusterers.SimpleKMeans:
+   * </pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -V
+   *  Display std. deviations for centroids.
+   * </pre>
+   * 
+   * <pre> -M
+   *  Replace missing values with mean/mode.
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() > 0) {
+      tmpOptions = Utils.splitOptions(tmpStr);
+      if (tmpOptions.length == 0)
+	throw new IllegalArgumentException("Invalid filter specification string");
+      tmpStr = tmpOptions[0];
+      tmpOptions[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
+    } 
+    else {
+      setFilter(new weka.filters.AllFilter());
+    }
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the clusterer.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    String[]	options;
+    int		i;
+    
+    result = new Vector();
+    
+    result.add("-F");
+    result.add(getFilterSpec());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The filter to be used.";
+  }
+
+  /**
+   * Sets the filter.
+   *
+   * @param filter 	the filter with all options set.
+   */
+  public void setFilter(Filter filter) {
+    m_Filter = filter;
+    
+    if (m_Filter instanceof SupervisedFilter)
+      System.out.println(
+	  "WARNING: you are using a supervised filter, which will leak "
+	  + "information about the class attribute!");
+  }
+
+  /**
+   * Gets the filter used.
+   *
+   * @return 		the filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+  
+  /**
+   * Gets the filter specification string, which contains the class name of
+   * the filter and any options to the filter.
+   *
+   * @return 		the filter string.
+   */
+  protected String getFilterSpec() {
+    String	result;
+    Filter 	filter;
+    
+    filter = getFilter();
+    result = filter.getClass().getName();
+    
+    if (filter instanceof OptionHandler)
+      result += " " + Utils.joinOptions(((OptionHandler) filter).getOptions());
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return		the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getFilter() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+      result.enable(Capability.NO_CLASS);
+    } else {
+      result = getFilter().getCapabilities();
+    }
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Build the clusterer on the filtered data.
+   *
+   * @param data 	the training data
+   * @throws Exception 	if the clusterer could not be built successfully
+   */
+  public void buildClusterer(Instances data) throws Exception {
+    if (m_Clusterer == null)
+      throw new Exception("No base clusterer has been set!");
+
+    // remove instances with missing class
+    if (data.classIndex() > -1) {
+      data = new Instances(data);
+      data.deleteWithMissingClass();
+    }
+    
+    m_Filter.setInputFormat(data);  // filter capabilities are checked here
+    data = Filter.useFilter(data, m_Filter);
+
+    // can clusterer handle the data?
+    getClusterer().getCapabilities().testWithFail(data);
+
+    m_FilteredInstances = data.stringFreeStructure();
+    m_Clusterer.buildClusterer(data);
+  }
+
+  /**
+   * Classifies a given instance after filtering.
+   *
+   * @param instance 	the instance to be classified
+   * @return 		the class distribution for the given instance
+   * @throws Exception 	if instance could not be classified
+   * 			successfully
+   */
+  public double[] distributionForInstance(Instance instance)
+    throws Exception {
+
+    if (m_Filter.numPendingOutput() > 0)
+      throw new Exception("Filter output queue not empty!");
+    
+    if (!m_Filter.input(instance))
+      throw new Exception(
+	  "Filter didn't make the test instance immediately available!");
+    
+    m_Filter.batchFinished();
+    Instance newInstance = m_Filter.output();
+
+    return m_Clusterer.distributionForInstance(newInstance);
+  }
+
+  /**
+   * Output a representation of this clusterer.
+   * 
+   * @return 		a representation of this clusterer
+   */
+  public String toString() {
+    String 	result;
+    
+    if (m_FilteredInstances == null)
+      result = "FilteredClusterer: No model built yet.";
+    else
+      result = "FilteredClusterer using "
+	+ getClustererSpec()
+	+ " on data filtered through "
+	+ getFilterSpec()
+	+ "\n\nFiltered Header\n"
+	+ m_FilteredInstances.toString()
+	+ "\n\nClusterer Model\n"
+	+ m_Clusterer.toString();
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5488 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	the commandline options, use "-h" for help
+   */
+  public static void main(String [] args) {
+    runClusterer(new FilteredClusterer(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/HierarchicalClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/HierarchicalClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/HierarchicalClusterer.java	(revision 29)
@@ -0,0 +1,1121 @@
+/*
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/*
+ * HierarchicalClusterer.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+*/
+/**
+ <!-- globalinfo-start -->
+ * Hierarchical clustering class.
+ * Implements a number of classic hierarchical clustering methods.
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  number of clusters
+ * </pre>
+ * 
+ * 
+ * <pre> -L
+ *  Link type (Single, Complete, Average, Mean, Centroid, Ward, Adjusted complete, Neighbor Joining)
+ *  [SINGLE|COMPLETE|AVERAGE|MEAN|CENTROID|WARD|ADJCOMLPETE|NEIGHBOR_JOINING]
+ * </pre>
+ * 
+ * <pre> -A
+ * Distance function to use. (default: weka.core.EuclideanDistance)
+ * </pre>
+ *
+ * <pre> -P
+ * Print hierarchy in Newick format, which can be used for display in other programs.
+ * </pre>
+ *  
+ * <pre> -D
+ * If set, classifier is run in debug mode and may output additional info to the console.
+ * </pre>
+ * 
+ * <pre> -B
+ * \If set, distance is interpreted as branch length, otherwise it is node height.
+ * </pre>
+ * 
+ *<!-- options-end -->
+ *
+ * 
+ * @author Remco Bouckaert (rrb@xm.co.nz, remco@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 6042 $
+ */
+
+package weka.clusterers;
+
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.PriorityQueue;
+import java.util.Vector;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.DistanceFunction;
+import weka.core.Drawable;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+public class HierarchicalClusterer extends AbstractClusterer implements OptionHandler, CapabilitiesHandler, Drawable {
+	private static final long serialVersionUID = 1L;
+
+	/** Whether the classifier is run in debug mode. */
+	protected boolean m_bDebug = false;
+
+	/** Whether the distance represent node height (if false) or branch length (if true). */
+	protected boolean m_bDistanceIsBranchLength = false;
+
+	/** training data **/
+	Instances m_instances;
+
+	/** number of clusters desired in clustering **/
+	int m_nNumClusters = 2;
+	public void setNumClusters(int nClusters) {m_nNumClusters = Math.max(1,nClusters);}
+	public int getNumClusters() {return m_nNumClusters;}
+	
+	/** distance function used for comparing members of a cluster **/
+	protected DistanceFunction m_DistanceFunction = new EuclideanDistance();
+	public DistanceFunction getDistanceFunction() {return m_DistanceFunction;}
+  	public void setDistanceFunction(DistanceFunction distanceFunction) {m_DistanceFunction = distanceFunction;}
+
+	/** used for priority queue for efficient retrieval of pair of clusters to merge**/
+	class Tuple {
+		public Tuple(double d, int i, int j, int nSize1, int nSize2) {
+			m_fDist = d;
+			m_iCluster1 = i;
+			m_iCluster2 = j;
+			m_nClusterSize1 = nSize1;
+			m_nClusterSize2 = nSize2;
+		}
+		double m_fDist;
+		int m_iCluster1;
+		int m_iCluster2;
+		int m_nClusterSize1;
+		int m_nClusterSize2;
+	}
+	/** comparator used by priority queue**/
+	class TupleComparator implements Comparator<Tuple> {
+		public int compare(Tuple o1, Tuple o2) {
+			if (o1.m_fDist < o2.m_fDist) {
+				return -1;
+			} else if (o1.m_fDist == o2.m_fDist) {
+				return 0;
+			}
+			return 1;
+		}
+	}
+
+	/** the various link types */
+	final static int SINGLE = 0;
+	final static int COMPLETE = 1;
+	final static int AVERAGE = 2;
+	final static int MEAN = 3;
+	final static int CENTROID = 4;
+	final static int WARD = 5;
+	final static int ADJCOMLPETE = 6;
+	final static int NEIGHBOR_JOINING = 7;
+	public static final Tag[] TAGS_LINK_TYPE = {
+	  new Tag(SINGLE, "SINGLE"),
+	  new Tag(COMPLETE, "COMPLETE"),
+	  new Tag(AVERAGE, "AVERAGE"),
+	  new Tag(MEAN, "MEAN"),
+	  new Tag(CENTROID, "CENTROID"),
+	  new Tag(WARD, "WARD"),
+	  new Tag(ADJCOMLPETE,"ADJCOMLPETE"),
+	  new Tag(NEIGHBOR_JOINING,"NEIGHBOR_JOINING")
+	};
+
+	/**
+	 * Holds the Link type used calculate distance between clusters
+	 */
+	int m_nLinkType = SINGLE;
+	
+	boolean m_bPrintNewick = true;;
+	public boolean getPrintNewick() {return m_bPrintNewick;}
+	public void setPrintNewick(boolean bPrintNewick) {m_bPrintNewick = bPrintNewick;}
+	
+	public void setLinkType(SelectedTag newLinkType) {
+		if (newLinkType.getTags() == TAGS_LINK_TYPE) {
+			m_nLinkType = newLinkType.getSelectedTag().getID();
+		}
+	}
+
+	public SelectedTag getLinkType() {
+		return new SelectedTag(m_nLinkType, TAGS_LINK_TYPE);
+	}
+
+	/** class representing node in cluster hierarchy **/
+	class Node implements Serializable {
+		Node m_left;
+		Node m_right;
+		Node m_parent;
+		int m_iLeftInstance;
+		int m_iRightInstance;
+		double m_fLeftLength = 0;
+		double m_fRightLength = 0;
+		double m_fHeight = 0;
+		public String toString(int attIndex) {
+			DecimalFormat myFormatter = new DecimalFormat("#.#####");
+
+			if (m_left == null) {
+				if (m_right == null) {
+					return "(" + m_instances.instance(m_iLeftInstance).stringValue(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+					             m_instances.instance(m_iRightInstance).stringValue(attIndex) +":" + myFormatter.format(m_fRightLength) + ")";
+				} else {
+					return "(" + m_instances.instance(m_iLeftInstance).stringValue(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+						m_right.toString(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				}
+			} else {
+				if (m_right == null) {
+					return "(" + m_left.toString(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+					             m_instances.instance(m_iRightInstance).stringValue(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				} else {
+					return "(" + m_left.toString(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +m_right.toString(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				}
+			}
+		}
+		public String toString2(int attIndex) {
+			DecimalFormat myFormatter = new DecimalFormat("#.#####");
+
+			if (m_left == null) {
+				if (m_right == null) {
+					return "(" + m_instances.instance(m_iLeftInstance).value(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+					             m_instances.instance(m_iRightInstance).value(attIndex) +":" + myFormatter.format(m_fRightLength) + ")";
+				} else {
+					return "(" + m_instances.instance(m_iLeftInstance).value(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+						m_right.toString2(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				}
+			} else {
+				if (m_right == null) {
+					return "(" + m_left.toString2(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +
+					             m_instances.instance(m_iRightInstance).value(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				} else {
+					return "(" + m_left.toString2(attIndex) + ":" + myFormatter.format(m_fLeftLength) + "," +m_right.toString2(attIndex) + ":" + myFormatter.format(m_fRightLength) + ")";
+				}
+			}
+		}
+		void setHeight(double fHeight1, double fHeight2) {
+			m_fHeight = fHeight1;
+			if (m_left == null) {
+				m_fLeftLength = fHeight1;
+			} else {
+				m_fLeftLength = fHeight1 - m_left.m_fHeight;
+			}
+			if (m_right == null) {
+				m_fRightLength = fHeight2;
+			} else {
+				m_fRightLength = fHeight2 - m_right.m_fHeight;
+			}
+		}
+		void setLength(double fLength1, double fLength2) {
+			m_fLeftLength = fLength1;
+			m_fRightLength = fLength2;
+			m_fHeight = fLength1;
+			if (m_left != null) {
+				m_fHeight += m_left.m_fHeight;
+			}
+		}
+	}
+	Node [] m_clusters;
+	int [] m_nClusterNr;
+	
+	
+	@Override
+	public void buildClusterer(Instances data) throws Exception {
+//		/System.err.println("Method " + m_nLinkType);
+		m_instances = data;
+		int nInstances = m_instances.numInstances();
+		if (nInstances == 0) {
+			return;
+		}
+		m_DistanceFunction.setInstances(m_instances);
+		// use array of integer vectors to store cluster indices,
+		// starting with one cluster per instance
+		Vector<Integer> [] nClusterID = new Vector[data.numInstances()];
+		for (int i = 0; i < data.numInstances(); i++) {
+			nClusterID[i] = new Vector<Integer>();
+			nClusterID[i].add(i);
+		}
+		// calculate distance matrix
+		int nClusters = data.numInstances();
+		
+		// used for keeping track of hierarchy
+		Node [] clusterNodes = new Node[nInstances];
+		if (m_nLinkType == NEIGHBOR_JOINING) {
+			neighborJoining(nClusters, nClusterID, clusterNodes);
+		} else {
+			doLinkClustering(nClusters, nClusterID, clusterNodes);
+		}
+		
+		// move all clusters in m_nClusterID array
+		// & collect hierarchy
+		int iCurrent = 0;
+		m_clusters = new Node[m_nNumClusters];
+		m_nClusterNr = new int[nInstances];
+		for (int i = 0; i < nInstances; i++) {
+			if (nClusterID[i].size() > 0) {
+				for (int j = 0; j < nClusterID[i].size(); j++) {
+					m_nClusterNr[nClusterID[i].elementAt(j)] = iCurrent;
+				}
+				m_clusters[iCurrent] = clusterNodes[i];
+				iCurrent++;
+			}
+		}
+		
+	} // buildClusterer
+
+	/** use neighbor joining algorithm for clustering
+	 * This is roughly based on the RapidNJ simple implementation and runs at O(n^3)
+	 * More efficient implementations exist, see RapidNJ (or my GPU implementation :-))
+	 * @param nClusters
+	 * @param nClusterID
+	 * @param clusterNodes
+	 */
+	void neighborJoining(int nClusters, Vector<Integer>[] nClusterID, Node [] clusterNodes) {
+		int n = m_instances.numInstances();
+
+		double [][] fDist = new double[nClusters][nClusters];
+		for (int i = 0; i < nClusters; i++) {
+			fDist[i][i] = 0;
+			for (int j = i+1; j < nClusters; j++) {
+				fDist[i][j] = getDistance0(nClusterID[i], nClusterID[j]);
+				fDist[j][i] = fDist[i][j];
+			}
+		}
+		
+		double [] fSeparationSums = new double [n];
+		double [] fSeparations = new double [n];
+	    int [] nNextActive = new int[n];
+
+		//calculate initial separation rows
+		for(int i = 0; i < n; i++){
+		    double fSum = 0;
+		    for(int j = 0; j < n; j++){
+		        fSum += fDist[i][j];
+		    }
+		    fSeparationSums[i] = fSum;
+		    fSeparations[i] = fSum / (nClusters - 2);
+		    nNextActive[i] = i +1;
+		}
+
+		while (nClusters > 2) {
+			// find minimum
+			int iMin1 = -1;
+			int iMin2 = -1;
+			double fMin = Double.MAX_VALUE;
+			if (m_bDebug) {
+				for (int i = 0; i < n; i++) {
+					if(nClusterID[i].size() > 0){
+						double [] fRow = fDist[i];
+						double fSep1 = fSeparations[i];
+						for(int j = 0; j < n; j++){
+				            if(nClusterID[j].size() > 0 && i != j){
+				            	double fSep2 = fSeparations[j];
+				            	double fVal = fRow[j] - fSep1 - fSep2;
+	
+								if(fVal < fMin){
+									// new minimum
+									iMin1 = i;
+									iMin2 = j;
+									fMin = fVal;
+								}
+				            }
+						}
+					}
+				}
+			} else {
+				int i = 0;
+				while (i < n) {
+					double fSep1 = fSeparations[i];
+					double [] fRow = fDist[i];
+					int j = nNextActive[i];
+					while (j < n) {
+		            	double fSep2 = fSeparations[j];
+		            	double fVal = fRow[j] - fSep1 - fSep2;
+						if(fVal < fMin){
+							// new minimum
+							iMin1 = i;
+							iMin2 = j;
+							fMin = fVal;
+						}
+						j = nNextActive[j];
+					}
+					i = nNextActive[i];
+				}		
+			}
+			// record distance
+			double fMinDistance = fDist[iMin1][iMin2];
+			nClusters--;
+			double fSep1 = fSeparations[iMin1];
+			double fSep2 = fSeparations[iMin2];
+			double fDist1 = (0.5 * fMinDistance) + (0.5 * (fSep1 - fSep2));
+			double fDist2 = (0.5 * fMinDistance) + (0.5 * (fSep2 - fSep1));
+			if (nClusters > 2) {
+				// update separations  & distance
+				double fNewSeparationSum = 0;
+				double fMutualDistance = fDist[iMin1][iMin2];
+				double[] fRow1 = fDist[iMin1];
+				double[] fRow2 = fDist[iMin2];
+				for(int i = 0; i < n; i++) {
+				    if(i == iMin1 || i == iMin2 || nClusterID[i].size() == 0) {
+				        fRow1[i] = 0;
+				    } else {
+				        double fVal1 = fRow1[i];
+				        double fVal2 = fRow2[i];
+				        double fDistance = (fVal1 + fVal2 - fMutualDistance) / 2.0;
+				        fNewSeparationSum += fDistance;
+				        // update the separationsum of cluster i.
+				        fSeparationSums[i] += (fDistance - fVal1 - fVal2);
+				        fSeparations[i] = fSeparationSums[i] / (nClusters -2);
+				        fRow1[i] = fDistance;
+				        fDist[i][iMin1] = fDistance;
+				    }
+				}
+				fSeparationSums[iMin1] = fNewSeparationSum;
+				fSeparations[iMin1] = fNewSeparationSum / (nClusters - 2);
+				fSeparationSums[iMin2] = 0;
+				merge(iMin1, iMin2, fDist1, fDist2, nClusterID, clusterNodes);
+				int iPrev = iMin2;
+				// since iMin1 < iMin2 we havenActiveRows[0] >= 0, so the next loop should be save
+				while (nClusterID[iPrev].size() == 0) {
+					iPrev--;
+				}
+				nNextActive[iPrev] = nNextActive[iMin2];
+			} else {
+				merge(iMin1, iMin2, fDist1, fDist2, nClusterID, clusterNodes);
+				break;
+			}
+		}
+
+		for (int i = 0; i < n; i++) {
+			if (nClusterID[i].size() > 0) {
+				for (int j = i+1; j < n; j++) {
+					if (nClusterID[j].size() > 0) {
+						double fDist1 = fDist[i][j];
+						if(nClusterID[i].size() == 1) {
+							merge(i,j,fDist1,0,nClusterID, clusterNodes);
+						} else if (nClusterID[j].size() == 1) {
+							merge(i,j,0,fDist1,nClusterID, clusterNodes);
+						} else {
+							merge(i,j,fDist1/2.0,fDist1/2.0,nClusterID, clusterNodes);
+						}
+						break;
+					}
+				}
+			}
+		}
+	} // neighborJoining
+	
+	/** Perform clustering using a link method
+	 * This implementation uses a priority queue resulting in a O(n^2 log(n)) algorithm
+	 * @param nClusters number of clusters
+	 * @param nClusterID 
+	 * @param clusterNodes 
+	 */
+	void doLinkClustering(int nClusters, Vector<Integer>[] nClusterID, Node [] clusterNodes) {
+		int nInstances = m_instances.numInstances();
+		PriorityQueue<Tuple> queue = new PriorityQueue<Tuple>(nClusters*nClusters/2, new TupleComparator());
+		double [][] fDistance0 = new double[nClusters][nClusters];
+		double [][] fClusterDistance = null;
+		if (m_bDebug) {
+			fClusterDistance = new double[nClusters][nClusters];
+		}
+		for (int i = 0; i < nClusters; i++) {
+			fDistance0[i][i] = 0;
+			for (int j = i+1; j < nClusters; j++) {
+				fDistance0[i][j] = getDistance0(nClusterID[i], nClusterID[j]);
+				fDistance0[j][i] = fDistance0[i][j];
+				queue.add(new Tuple(fDistance0[i][j], i, j, 1, 1));
+				if (m_bDebug) {
+					fClusterDistance[i][j] = fDistance0[i][j];
+					fClusterDistance[j][i] = fDistance0[i][j];
+				}
+			}
+		}
+		while (nClusters > m_nNumClusters) {
+			int iMin1 = -1;
+			int iMin2 = -1;
+			// find closest two clusters
+			if (m_bDebug) {
+				/* simple but inefficient implementation */
+				double fMinDistance = Double.MAX_VALUE;
+				for (int i = 0; i < nInstances; i++) {
+					if (nClusterID[i].size()>0) {
+						for (int j = i+1; j < nInstances; j++) {
+							if (nClusterID[j].size()>0) {
+								double fDist = fClusterDistance[i][j];
+								if (fDist < fMinDistance) {
+									fMinDistance = fDist;
+									iMin1 = i;
+									iMin2 = j;
+								}
+							}
+						}
+					}
+				}
+				merge(iMin1, iMin2, fMinDistance, fMinDistance, nClusterID, clusterNodes);
+			} else {
+				// use priority queue to find next best pair to cluster
+				Tuple t;
+				do {
+					t = queue.poll();
+				} while (t!=null && (nClusterID[t.m_iCluster1].size() != t.m_nClusterSize1 || nClusterID[t.m_iCluster2].size() != t.m_nClusterSize2));
+				iMin1 = t.m_iCluster1;
+				iMin2 = t.m_iCluster2;
+				merge(iMin1, iMin2, t.m_fDist, t.m_fDist, nClusterID, clusterNodes);
+			}
+			// merge  clusters
+			
+			// update distances & queue
+			for (int i = 0; i < nInstances; i++) {
+				if (i != iMin1 && nClusterID[i].size()!=0) {
+					int i1 = Math.min(iMin1,i);
+					int i2 = Math.max(iMin1,i);
+					double fDistance = getDistance(fDistance0, nClusterID[i1], nClusterID[i2]);
+					if (m_bDebug) {
+						fClusterDistance[i1][i2] = fDistance;
+						fClusterDistance[i2][i1] = fDistance;
+					}
+					queue.add(new Tuple(fDistance, i1, i2, nClusterID[i1].size(), nClusterID[i2].size()));
+				}
+			}
+			
+			nClusters--;
+		}
+	} // doLinkClustering
+	
+	void merge(int iMin1, int iMin2, double fDist1, double fDist2, Vector<Integer>[] nClusterID, Node [] clusterNodes) {
+		if (m_bDebug) {
+			System.err.println("Merging " + iMin1 + " " + iMin2 + " " + fDist1 + " " + fDist2);
+		}
+		if (iMin1 > iMin2) {
+			int h = iMin1; iMin1 = iMin2; iMin2 = h;
+			double f = fDist1; fDist1 = fDist2; fDist2 = f;
+		}
+		nClusterID[iMin1].addAll(nClusterID[iMin2]);
+		nClusterID[iMin2].removeAllElements();
+		
+		// track hierarchy
+		Node node = new Node();
+		if (clusterNodes[iMin1] == null) {
+			node.m_iLeftInstance = iMin1;
+		} else {
+			node.m_left = clusterNodes[iMin1];
+			clusterNodes[iMin1].m_parent = node;
+		}
+		if (clusterNodes[iMin2] == null) {
+			node.m_iRightInstance = iMin2;
+		} else {
+			node.m_right = clusterNodes[iMin2];
+			clusterNodes[iMin2].m_parent = node;
+		}
+		if (m_bDistanceIsBranchLength) {
+			node.setLength(fDist1, fDist2);
+		} else {
+			node.setHeight(fDist1, fDist2);
+		}
+		clusterNodes[iMin1] = node;
+	} // merge
+	
+	/** calculate distance the first time when setting up the distance matrix **/
+	double getDistance0(Vector<Integer> cluster1, Vector<Integer> cluster2) {
+		double fBestDist = Double.MAX_VALUE;
+		switch (m_nLinkType) {
+		case SINGLE:
+		case NEIGHBOR_JOINING:
+		case CENTROID:
+		case COMPLETE:
+		case ADJCOMLPETE:
+		case AVERAGE:
+		case MEAN:
+			// set up two instances for distance function
+			Instance instance1 = (Instance) m_instances.instance(cluster1.elementAt(0)).copy();
+			Instance instance2 = (Instance) m_instances.instance(cluster2.elementAt(0)).copy();
+			fBestDist = m_DistanceFunction.distance(instance1, instance2);
+			break;
+		case WARD:
+			{
+				// finds the distance of the change in caused by merging the cluster.
+				// The information of a cluster is calculated as the error sum of squares of the
+				// centroids of the cluster and its members.
+				double ESS1 = calcESS(cluster1);
+				double ESS2 = calcESS(cluster2);
+				Vector<Integer> merged = new Vector<Integer>();
+				merged.addAll(cluster1);
+				merged.addAll(cluster2);
+				double ESS = calcESS(merged);
+				fBestDist = ESS * merged.size() - ESS1 * cluster1.size() - ESS2 * cluster2.size();
+			}
+			break;
+		}
+		return fBestDist;
+	} // getDistance0
+
+	/** calculate the distance between two clusters 
+	 * @param cluster1 list of indices of instances in the first cluster
+	 * @param cluster2 dito for second cluster
+	 * @return distance between clusters based on link type
+	 */
+	double getDistance(double [][] fDistance, Vector<Integer> cluster1, Vector<Integer> cluster2) {
+		double fBestDist = Double.MAX_VALUE;
+		switch (m_nLinkType) {
+		case SINGLE:
+			// find single link distance aka minimum link, which is the closest distance between
+			// any item in cluster1 and any item in cluster2
+			fBestDist = Double.MAX_VALUE;
+			for (int i = 0; i < cluster1.size(); i++) {
+				int i1 = cluster1.elementAt(i);
+				for (int j = 0; j < cluster2.size(); j++) {
+					int i2  = cluster2.elementAt(j);
+					double fDist = fDistance[i1][i2];
+					if (fBestDist > fDist) {
+						fBestDist = fDist;
+					}
+				}
+			}
+			break;
+		case COMPLETE:
+		case ADJCOMLPETE:
+			// find complete link distance aka maximum link, which is the largest distance between
+			// any item in cluster1 and any item in cluster2
+			fBestDist = 0;
+			for (int i = 0; i < cluster1.size(); i++) {
+				int i1 = cluster1.elementAt(i);
+				for (int j = 0; j < cluster2.size(); j++) {
+					int i2 = cluster2.elementAt(j);
+					double fDist = fDistance[i1][i2];
+					if (fBestDist < fDist) {
+						fBestDist = fDist;
+					}
+				}
+			}
+			if (m_nLinkType == COMPLETE) {
+				break;
+			}
+			// calculate adjustment, which is the largest within cluster distance
+			double fMaxDist = 0;
+			for (int i = 0; i < cluster1.size(); i++) {
+				int i1 = cluster1.elementAt(i);
+				for (int j = i+1; j < cluster1.size(); j++) {
+					int i2 = cluster1.elementAt(j);
+					double fDist = fDistance[i1][i2];
+					if (fMaxDist < fDist) {
+						fMaxDist = fDist;
+					}
+				}
+			}
+			for (int i = 0; i < cluster2.size(); i++) {
+				int i1 = cluster2.elementAt(i);
+				for (int j = i+1; j < cluster2.size(); j++) {
+					int i2 = cluster2.elementAt(j);
+					double fDist = fDistance[i1][i2];
+					if (fMaxDist < fDist) {
+						fMaxDist = fDist;
+					}
+				}
+			}
+			fBestDist -= fMaxDist;
+			break;
+		case AVERAGE:
+			// finds average distance between the elements of the two clusters
+			fBestDist = 0;
+			for (int i = 0; i < cluster1.size(); i++) {
+				int i1 = cluster1.elementAt(i);
+				for (int j = 0; j < cluster2.size(); j++) {
+					int i2 = cluster2.elementAt(j);
+					fBestDist += fDistance[i1][i2];
+				}
+			}
+			fBestDist /= (cluster1.size() * cluster2.size());
+			break;
+		case MEAN: 
+			{
+				// calculates the mean distance of a merged cluster (akak Group-average agglomerative clustering)
+				Vector<Integer> merged = new Vector<Integer>();
+				merged.addAll(cluster1);
+				merged.addAll(cluster2);
+				fBestDist = 0;
+				for (int i = 0; i < merged.size(); i++) {
+					int i1 = merged.elementAt(i);
+					for (int j = i+1; j < merged.size(); j++) {
+						int i2 = merged.elementAt(j);
+						fBestDist += fDistance[i1][i2];
+					}
+				}
+				int n = merged.size();
+				fBestDist /= (n*(n-1.0)/2.0);
+			}
+			break;
+		case CENTROID:
+			// finds the distance of the centroids of the clusters
+			double [] fValues1 = new double[m_instances.numAttributes()];
+			for (int i = 0; i < cluster1.size(); i++) {
+				Instance instance = m_instances.instance(cluster1.elementAt(i));
+				for (int j = 0; j < m_instances.numAttributes(); j++) {
+					fValues1[j] += instance.value(j);
+				}
+			}
+			double [] fValues2 = new double[m_instances.numAttributes()];
+			for (int i = 0; i < cluster2.size(); i++) {
+				Instance instance = m_instances.instance(cluster2.elementAt(i));
+				for (int j = 0; j < m_instances.numAttributes(); j++) {
+					fValues2[j] += instance.value(j);
+				}
+			}
+			for (int j = 0; j < m_instances.numAttributes(); j++) {
+				fValues1[j] /= cluster1.size();
+				fValues2[j] /= cluster2.size();
+			}
+			// set up two instances for distance function
+			Instance instance1 = (Instance) m_instances.instance(0).copy();
+			Instance instance2 = (Instance) m_instances.instance(0).copy();
+			for (int j = 0; j < m_instances.numAttributes(); j++) {
+				instance1.setValue(j, fValues1[j]);
+				instance2.setValue(j, fValues2[j]);
+			}
+			fBestDist = m_DistanceFunction.distance(instance1, instance2);
+			break;
+		case WARD:
+			{
+				// finds the distance of the change in caused by merging the cluster.
+				// The information of a cluster is calculated as the error sum of squares of the
+				// centroids of the cluster and its members.
+				double ESS1 = calcESS(cluster1);
+				double ESS2 = calcESS(cluster2);
+				Vector<Integer> merged = new Vector<Integer>();
+				merged.addAll(cluster1);
+				merged.addAll(cluster2);
+				double ESS = calcESS(merged);
+				fBestDist = ESS * merged.size() - ESS1 * cluster1.size() - ESS2 * cluster2.size();
+			}
+			break;
+		}
+		return fBestDist;
+	} // getDistance
+
+	/** calculated error sum-of-squares for instances wrt centroid **/
+	double calcESS(Vector<Integer> cluster) {
+		double [] fValues1 = new double[m_instances.numAttributes()];
+		for (int i = 0; i < cluster.size(); i++) {
+			Instance instance = m_instances.instance(cluster.elementAt(i));
+			for (int j = 0; j < m_instances.numAttributes(); j++) {
+				fValues1[j] += instance.value(j);
+			}
+		}
+		for (int j = 0; j < m_instances.numAttributes(); j++) {
+			fValues1[j] /= cluster.size();
+		}
+		// set up two instances for distance function
+		Instance centroid = (Instance) m_instances.instance(cluster.elementAt(0)).copy();
+		for (int j = 0; j < m_instances.numAttributes(); j++) {
+			centroid.setValue(j, fValues1[j]);
+		}
+		double fESS = 0;
+		for (int i = 0; i < cluster.size(); i++) {
+			Instance instance = m_instances.instance(cluster.elementAt(i));
+			fESS += m_DistanceFunction.distance(centroid, instance);
+		}
+		return fESS / cluster.size(); 
+	} // calcESS
+	
+	@Override
+	/** instances are assigned a cluster by finding the instance in the training data 
+	 * with the closest distance to the instance to be clustered. The cluster index of
+	 * the training data point is taken as the cluster index.
+	 */
+	public int clusterInstance(Instance instance) throws Exception {
+		if (m_instances.numInstances() == 0) {
+			return 0;
+		}
+		double fBestDist = Double.MAX_VALUE;
+		int iBestInstance = -1;
+		for (int i = 0; i < m_instances.numInstances(); i++) {
+			double fDist = m_DistanceFunction.distance(instance, m_instances.instance(i));
+			if (fDist < fBestDist) {
+				fBestDist = fDist;
+				iBestInstance = i;
+			}
+		}
+		return m_nClusterNr[iBestInstance];
+	}
+
+	@Override
+	/** create distribution with all clusters having zero probability, except the
+	 * cluster the instance is assigned to.
+	 */
+	public double[] distributionForInstance(Instance instance) throws Exception {
+		if (numberOfClusters() == 0) {
+			double [] p = new double[1];
+			p[0] = 1;
+			return p;
+		}
+		double [] p = new double[numberOfClusters()];
+		p[clusterInstance(instance)] = 1.0;
+		return p;
+	}
+
+	@Override
+	public Capabilities getCapabilities() {
+	    Capabilities result = new Capabilities(this);
+	    result.disableAll();
+	    result.enable(Capability.NO_CLASS);
+
+	    // attributes
+	    result.enable(Capability.NOMINAL_ATTRIBUTES);
+	    result.enable(Capability.NUMERIC_ATTRIBUTES);
+	    result.enable(Capability.DATE_ATTRIBUTES);
+	    result.enable(Capability.MISSING_VALUES);
+	    result.enable(Capability.STRING_ATTRIBUTES);
+
+	    // other
+	    result.setMinimumNumberInstances(0);
+	    return result;
+	}
+
+	@Override
+	public int numberOfClusters() throws Exception {
+		return Math.min(m_nNumClusters, m_instances.numInstances());
+	}
+
+	  /**
+	   * Returns an enumeration describing the available options.
+	   *
+	   * @return an enumeration of all the available options.
+	   */
+	  public Enumeration listOptions() {
+
+	    Vector newVector = new Vector(8);
+	    newVector.addElement(new Option(
+	  	      "\tIf set, classifier is run in debug mode and\n"
+	  	      + "\tmay output additional info to the console",
+	  	      "D", 0, "-D"));
+	    newVector.addElement(new Option(
+		  	      "\tIf set, distance is interpreted as branch length\n"
+		  	      + "\totherwise it is node height.",
+		  	      "B", 0, "-B"));
+
+	    newVector.addElement(new Option(
+	            "\tnumber of clusters",
+	  	      "N", 1,"-N <Nr Of Clusters>"));
+	    newVector.addElement(new Option(
+	            "\tFlag to indicate the cluster should be printed in Newick format.",
+	  	      "P", 0,"-P"));
+		newVector.addElement(
+				new Option(
+					"Link type (Single, Complete, Average, Mean, Centroid, Ward, Adjusted complete, Neighbor joining)", "L", 1,
+					"-L [SINGLE|COMPLETE|AVERAGE|MEAN|CENTROID|WARD|ADJCOMLPETE|NEIGHBOR_JOINING]"));
+	    newVector.add(new Option(
+	    		"\tDistance function to use.\n"
+	    		+ "\t(default: weka.core.EuclideanDistance)",
+	    		"A", 1,"-A <classname and options>"));
+	    return newVector.elements();
+	  }
+
+	  /**
+	   * Parses a given list of options. <p/>
+	   *
+	   <!-- options-start -->
+	   * Valid options are: <p/>
+	   * 
+	   <!-- options-end -->
+	   *
+	   * @param options the list of options as an array of strings
+	   * @throws Exception if an option is not supported
+	   */
+	  public void setOptions(String[] options) throws Exception {
+		    m_bPrintNewick = Utils.getFlag('P', options);
+
+		    String optionString = Utils.getOption('N', options); 
+		    if (optionString.length() != 0) {
+		      Integer temp = new Integer(optionString);
+		      setNumClusters(temp);
+		    }
+		    else {
+		      setNumClusters(2);
+		    }
+	    
+        setDebug(Utils.getFlag('D', options));
+        setDistanceIsBranchLength(Utils.getFlag('B', options));
+
+	    String sLinkType = Utils.getOption('L', options);
+
+
+		if (sLinkType.compareTo("SINGLE") == 0) {setLinkType(new SelectedTag(SINGLE, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("COMPLETE") == 0) {setLinkType(new SelectedTag(COMPLETE, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("AVERAGE") == 0) {setLinkType(new SelectedTag(AVERAGE, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("MEAN") == 0) {setLinkType(new SelectedTag(MEAN, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("CENTROID") == 0) {setLinkType(new SelectedTag(CENTROID, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("WARD") == 0) {setLinkType(new SelectedTag(WARD, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("ADJCOMLPETE") == 0) {setLinkType(new SelectedTag(ADJCOMLPETE, TAGS_LINK_TYPE));}
+		if (sLinkType.compareTo("NEIGHBOR_JOINING") == 0) {setLinkType(new SelectedTag(NEIGHBOR_JOINING, TAGS_LINK_TYPE));}
+		
+	    String nnSearchClass = Utils.getOption('A', options);
+	    if(nnSearchClass.length() != 0) {
+	      String nnSearchClassSpec[] = Utils.splitOptions(nnSearchClass);
+	      if(nnSearchClassSpec.length == 0) { 
+	        throw new Exception("Invalid DistanceFunction specification string."); 
+	      }
+	      String className = nnSearchClassSpec[0];
+	      nnSearchClassSpec[0] = "";
+
+	      setDistanceFunction( (DistanceFunction)
+	                            Utils.forName( DistanceFunction.class, 
+	                                           className, nnSearchClassSpec) );
+	    }
+	    else {
+	      setDistanceFunction(new EuclideanDistance());
+	    }
+	    
+	    Utils.checkForRemainingOptions(options);
+	  }
+
+	  /**
+	   * Gets the current settings of the clusterer.
+	   *
+	   * @return an array of strings suitable for passing to setOptions()
+	   */
+	  public String [] getOptions() {
+
+	    String [] options = new String [14];
+	    int current = 0;
+
+	    options[current++] = "-N";
+	    options[current++] = "" + getNumClusters();
+	    
+	    options[current++] = "-L";
+		switch (m_nLinkType) {
+			case (SINGLE) :options[current++] = "SINGLE";break;
+			case (COMPLETE) :options[current++] = "COMPLETE";break;
+			case (AVERAGE) :options[current++] = "AVERAGE";break;
+			case (MEAN) :options[current++] = "MEAN";break;
+			case (CENTROID) :options[current++] = "CENTROID";break;
+			case (WARD) :options[current++] = "WARD";break;
+			case (ADJCOMLPETE) :options[current++] = "ADJCOMLPETE";break;
+			case (NEIGHBOR_JOINING) :options[current++] = "NEIGHBOR_JOINING";break;
+		}
+		if (m_bPrintNewick) {
+			options[current++] = "-P";
+		}
+	    if (getDebug()) {
+	        options[current++] = "-D";
+	    }
+        if (getDistanceIsBranchLength()) {
+	        options[current++] = "-B";
+        }
+	    
+		options[current++] = "-A";
+		options[current++] = (m_DistanceFunction.getClass().getName() + " " +
+	                   Utils.joinOptions(m_DistanceFunction.getOptions())).trim();
+	    
+	    while (current < options.length) {
+	      options[current++] = "";
+	    }
+	    
+	    return options;
+	  }
+	  public String toString() {
+		  StringBuffer buf = new StringBuffer();
+		  int attIndex = m_instances.classIndex();
+		  if (attIndex < 0) {
+			  // try find a string, or last attribute otherwise
+			  attIndex = 0;
+			  while (attIndex < m_instances.numAttributes()-1) {
+				  if (m_instances.attribute(attIndex).isString()) {
+					  break;
+				  }
+				  attIndex++;
+			  }
+		  }
+		  try {
+			if (m_bPrintNewick && (numberOfClusters() > 0)) {
+				  for (int i = 0; i < m_clusters.length; i++) {
+					  if (m_clusters[i] != null) {
+						  buf.append("Cluster " + i + "\n");
+						  if (m_instances.attribute(attIndex).isString()) {
+							  buf.append(m_clusters[i].toString(attIndex));
+						  } else {
+							  buf.append(m_clusters[i].toString2(attIndex));
+						  }
+						  buf.append("\n\n");
+					  }
+				  }
+			  }
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		  return buf.toString();
+	  }
+	  /**
+	   * Set debugging mode.
+	   *
+	   * @param debug true if debug output should be printed
+	   */
+	  public void setDebug(boolean debug) {
+
+	    m_bDebug = debug;
+	  }
+
+	  /**
+	   * Get whether debugging is turned on.
+	   *
+	   * @return true if debugging output is on
+	   */
+	  public boolean getDebug() {
+
+	    return m_bDebug;
+	  }
+
+	  public boolean getDistanceIsBranchLength() {return m_bDistanceIsBranchLength;}
+
+	  public void setDistanceIsBranchLength(boolean bDistanceIsHeight) {m_bDistanceIsBranchLength = bDistanceIsHeight;}
+
+	  public String distanceIsHeightTipText() {
+		  return "If set to false, the distance between clusters is interpreted " +
+		  "as the height of the node linking the clusters. This is appropriate for " +
+		  "example for single link clustering. However, for neighbor joining, the " +
+		  "distance is better interpreted as branch length. Set this flag to " +
+		  "get the latter interpretation.";
+	  }
+	  /**
+	   * Returns the tip text for this property
+	   * @return tip text for this property suitable for
+	   * displaying in the explorer/experimenter gui
+	   */
+	  public String debugTipText() {
+	    return "If set to true, classifier may output additional info to " +
+	      "the console.";
+	  }
+	  /**
+	   * @return a string to describe the NumClusters
+	   */
+	  public String numClustersTipText() {
+	    return "Sets the number of clusters. " +
+	    "If a single hierarchy is desired, set this to 1.";
+	  }
+
+	  /**
+	   * @return a string to describe the print Newick flag
+	   */
+	  public String printNewickTipText() {
+	    return "Flag to indicate whether the cluster should be print in Newick format." +
+	    " This can be useful for display in other programs. However, for large datasets" +
+	    " a lot of text may be produced, which may not be a nuisance when the Newick format" +
+	    " is not required";
+	  }
+
+	  /**
+	   * @return a string to describe the distance function
+	   */
+	  public String distanceFunctionTipText() {
+	    return "Sets the distance function, which measures the distance between two individual. " +
+	    "instances (or possibly the distance between an instance and the centroid of a cluster" +
+	    "depending on the Link type).";
+	  }
+
+	  /**
+	   * @return a string to describe the Link type
+	   */
+	  public String linkTypeTipText() {
+	    return "Sets the method used to measure the distance between two clusters.\n" +
+	    "SINGLE:\n" +
+	    " find single link distance aka minimum link, which is the closest distance between" +
+	    " any item in cluster1 and any item in cluster2\n" +
+	    "COMPLETE:\n" +
+	    " find complete link distance aka maximum link, which is the largest distance between" +
+	    " any item in cluster1 and any item in cluster2\n" +
+	    "ADJCOMLPETE:\n" +
+	    " as COMPLETE, but with adjustment, which is the largest within cluster distance\n" +
+	    "AVERAGE:\n" +
+	    " finds average distance between the elements of the two clusters\n" +
+	    "MEAN: \n" +
+	    " calculates the mean distance of a merged cluster (akak Group-average agglomerative clustering)\n" +
+	    "CENTROID:\n" +
+	    " finds the distance of the centroids of the clusters\n" +
+	    "WARD:\n" +
+	    " finds the distance of the change in caused by merging the cluster." +
+	    " The information of a cluster is calculated as the error sum of squares of the" +
+	    " centroids of the cluster and its members.\n" +
+	    "NEIGHBOR_JOINING\n" +
+	    " use neighbor joining algorithm."
+	    ;
+	  }
+
+	  /**
+	   * This will return a string describing the clusterer.
+	   * @return The string.
+	   */
+	  public String globalInfo() {
+	    return 
+	    "Hierarchical clustering class.\n" +
+	    "Implements a number of classic agglomorative (i.e. bottom up) hierarchical clustering methods" +
+	    "based on .";
+	  }
+	  
+	  public static void main(String [] argv) {
+		    runClusterer(new HierarchicalClusterer(), argv);
+		  }
+	@Override
+	public String graph() throws Exception {
+		if (numberOfClusters() == 0) {
+			  return "Newick:(no,clusters)";
+		}
+		  int attIndex = m_instances.classIndex();
+		  if (attIndex < 0) {
+			  // try find a string, or last attribute otherwise
+			  attIndex = 0;
+			  while (attIndex < m_instances.numAttributes()-1) {
+				  if (m_instances.attribute(attIndex).isString()) {
+					  break;
+				  }
+				  attIndex++;
+			  }
+		  }
+		  String sNewick = null;
+		  if (m_instances.attribute(attIndex).isString()) {
+			  sNewick = m_clusters[0].toString(attIndex);
+		  } else {
+			  sNewick = m_clusters[0].toString2(attIndex);
+		  }
+		  return "Newick:" + sNewick;
+	}
+	@Override
+	public int graphType() {
+		return Drawable.Newick;
+	}
+	  /**
+	   * Returns the revision string.
+	   * 
+	   * @return		the revision
+	   */
+	  public String getRevision() {
+	    return RevisionUtils.extract("$Revision: 6042 $");
+	  }
+} // class HierarchicalClusterer
Index: branches/MetisMQI/src/main/java/weka/clusterers/MakeDensityBasedClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/MakeDensityBasedClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/MakeDensityBasedClusterer.java	(revision 29)
@@ -0,0 +1,601 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MakeDensityBasedClusterer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.estimators.DiscreteEstimator;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class for wrapping a Clusterer to make it return a distribution and density. Fits normal distributions and discrete distributions within each cluster produced by the wrapped clusterer. Supports the NumberOfClustersRequestable interface only if the wrapped Clusterer does.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  minimum allowable standard deviation for normal density computation 
+ *  (default 1e-6)</pre>
+ * 
+ * <pre> -W &lt;clusterer name&gt;
+ *  Clusterer to wrap.
+ *  (default weka.clusterers.SimpleKMeans)</pre>
+ * 
+ * <pre> 
+ * Options specific to clusterer weka.clusterers.SimpleKMeans:
+ * </pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -V
+ *  Display std. deviations for centroids.
+ * </pre>
+ * 
+ * <pre> -M
+ *  Replace missing values with mean/mode.
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * Options after "--" are passed on to the base clusterer.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5488 $
+ */
+public class MakeDensityBasedClusterer 
+  extends AbstractDensityBasedClusterer
+  implements NumberOfClustersRequestable, 
+	     OptionHandler, 
+	     WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -5643302427972186631L;
+  
+  /** holds training instances header information */
+  private Instances m_theInstances;
+  /** prior probabilities for the fitted clusters */
+  private double [] m_priors;
+  /** normal distributions fitted to each numeric attribute in each cluster */
+  private double [][][] m_modelNormal;
+  /** discrete distributions fitted to each discrete attribute in each cluster */
+  private DiscreteEstimator [][] m_model;
+  /** default minimum standard deviation */
+  private double m_minStdDev = 1e-6;
+  /** The clusterer being wrapped */
+  private Clusterer m_wrappedClusterer = new weka.clusterers.SimpleKMeans();
+  /** globally replace missing values */
+  private ReplaceMissingValues m_replaceMissing;
+
+  /**
+   * Default constructor.
+   * 
+   */  
+  public MakeDensityBasedClusterer() {
+    super();
+  }
+   
+  /**
+   * Contructs a MakeDensityBasedClusterer wrapping a given Clusterer.
+   * 
+   * @param toWrap the clusterer to wrap around
+   */    
+  public MakeDensityBasedClusterer(Clusterer toWrap) {
+
+    setClusterer(toWrap);
+  }
+  
+  /**
+   * Returns a string describing classifier
+   * @return a description suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class for wrapping a Clusterer to make it return a distribution "
+      + "and density. Fits normal distributions and discrete distributions "
+      + "within each cluster produced by the wrapped clusterer. Supports the "
+      + "NumberOfClustersRequestable interface only if the wrapped Clusterer "
+      + "does.";
+  }
+
+  /**
+   * String describing default clusterer.
+   * 
+   * @return 		the default clusterer classname
+   */
+  protected String defaultClustererString() {
+    return SimpleKMeans.class.getName();
+  }
+
+  /**
+   * Set the number of clusters to generate.
+   *
+   * @param n the number of clusters to generate
+   * @throws Exception if the wrapped clusterer has not been set, or if
+   * the wrapped clusterer does not implement this facility.
+   */
+  public void setNumClusters(int n) throws Exception {
+    if (m_wrappedClusterer == null) {
+      throw new Exception("Can't set the number of clusters to generate - "
+			  +"no clusterer has been set yet.");
+    }
+    if (!(m_wrappedClusterer instanceof NumberOfClustersRequestable)) {
+      throw new Exception("Can't set the number of clusters to generate - "
+			  +"wrapped clusterer does not support this facility.");
+    }
+
+    ((NumberOfClustersRequestable)m_wrappedClusterer).setNumClusters(n);
+  }
+
+  /**
+   * Returns default capabilities of the clusterer (i.e., of the wrapper
+   * clusterer).
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    if (m_wrappedClusterer != null) {
+      return m_wrappedClusterer.getCapabilities();
+    }
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Builds a clusterer for a set of instances.
+   *
+   * @param data the instances to train the clusterer with
+   * @throws Exception if the clusterer hasn't been set or something goes wrong
+   */  
+  public void buildClusterer(Instances data) throws Exception {
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+
+    m_replaceMissing = new ReplaceMissingValues();
+    m_replaceMissing.setInputFormat(data);
+    data = weka.filters.Filter.useFilter(data, m_replaceMissing);
+
+    m_theInstances = new Instances(data, 0);
+    if (m_wrappedClusterer == null) {
+      throw new Exception("No clusterer has been set");
+    }
+    m_wrappedClusterer.buildClusterer(data);
+    m_model = 
+       new DiscreteEstimator[m_wrappedClusterer.numberOfClusters()][data.numAttributes()];
+    m_modelNormal = 
+      new double[m_wrappedClusterer.numberOfClusters()][data.numAttributes()][2];
+    double[][] weights =  new double[m_wrappedClusterer.numberOfClusters()][data.numAttributes()];
+    m_priors = new double[m_wrappedClusterer.numberOfClusters()]; 
+     for (int i = 0; i < m_wrappedClusterer.numberOfClusters(); i++) {
+       m_priors[i] = 1.0; // laplace correction
+       for (int j = 0; j < data.numAttributes(); j++) {
+	 if (data.attribute(j).isNominal()) {
+	   m_model[i][j] = new DiscreteEstimator(data.attribute(j).numValues(),
+						 true);
+	 }
+       }
+     }
+     
+     Instance inst = null;
+
+     // Compute mean, etc.
+     int[] clusterIndex = new int[data.numInstances()];
+     for (int i = 0; i < data.numInstances(); i++) {
+       inst = data.instance(i);
+       int cluster = m_wrappedClusterer.clusterInstance(inst);
+       m_priors[cluster] += inst.weight();
+       for (int j = 0; j < data.numAttributes(); j++) {
+	 if (!inst.isMissing(j)) {
+	   if (data.attribute(j).isNominal()) {
+	     m_model[cluster][j].addValue(inst.value(j),inst.weight());
+	   } else {
+	     m_modelNormal[cluster][j][0] += inst.weight() * inst.value(j);
+	     weights[cluster][j] += inst.weight();
+	   }
+	 }
+       }
+       clusterIndex[i] = cluster;
+     }
+
+     for (int j = 0; j < data.numAttributes(); j++) {
+       if (data.attribute(j).isNumeric()) {
+	 for (int i = 0; i < m_wrappedClusterer.numberOfClusters(); i++) {	   
+	   if (weights[i][j] > 0) {
+	     m_modelNormal[i][j][0] /= weights[i][j];
+	   }
+	 }
+       }
+     }
+
+     // Compute standard deviations
+     for (int i = 0; i < data.numInstances(); i++) {
+       inst = data.instance(i);
+       for (int j = 0; j < data.numAttributes(); j++) {
+	 if (!inst.isMissing(j)) {
+	   if (data.attribute(j).isNumeric()) {
+	     double diff = m_modelNormal[clusterIndex[i]][j][0] - inst.value(j);
+	     m_modelNormal[clusterIndex[i]][j][1] += inst.weight() * diff * diff;
+	   }
+	 }
+       }
+     }
+
+     for (int j = 0; j < data.numAttributes(); j++) {
+       if (data.attribute(j).isNumeric()) {
+	 for (int i = 0; i < m_wrappedClusterer.numberOfClusters(); i++) {	   
+	   if (weights[i][j] > 0) {
+	     m_modelNormal[i][j][1] = 
+	       Math.sqrt(m_modelNormal[i][j][1] / weights[i][j]);
+	   } else if (weights[i][j] <= 0) {
+	     m_modelNormal[i][j][1] = Double.MAX_VALUE;
+	   }
+	   if (m_modelNormal[i][j][1] <= m_minStdDev) {
+	     m_modelNormal[i][j][1] = data.attributeStats(j).numericStats.stdDev;
+	     if (m_modelNormal[i][j][1] <= m_minStdDev) {
+	       m_modelNormal[i][j][1] = m_minStdDev;
+	     }
+	   }
+	 }
+       }
+     }
+     
+     Utils.normalize(m_priors);
+  }
+
+  /**
+   * Returns the cluster priors.
+   * 
+   * @return the cluster priors
+   */
+  public double[] clusterPriors() {
+
+    double[] n = new double[m_priors.length];
+  
+    System.arraycopy(m_priors, 0, n, 0, n.length);
+    return n;
+  }
+
+  /**
+   * Computes the log of the conditional density (per cluster) for a given instance.
+   * 
+   * @param inst the instance to compute the density for
+   * @return an array containing the estimated densities
+   * @throws Exception if the density could not be computed
+   * successfully
+   */
+  public double[] logDensityPerClusterForInstance(Instance inst) throws Exception {
+
+    int i, j;
+    double logprob;
+    double[] wghts = new double[m_wrappedClusterer.numberOfClusters()];
+    
+    m_replaceMissing.input(inst);
+    inst = m_replaceMissing.output();
+
+    for (i = 0; i < m_wrappedClusterer.numberOfClusters(); i++) {
+      logprob = 0;
+      for (j = 0; j < inst.numAttributes(); j++) {
+	if (!inst.isMissing(j)) {
+	  if (inst.attribute(j).isNominal()) {
+	    logprob += Math.log(m_model[i][j].getProbability(inst.value(j)));
+	  } else { // numeric attribute
+	    logprob += logNormalDens(inst.value(j), 
+				     m_modelNormal[i][j][0], 
+				     m_modelNormal[i][j][1]);
+	  }
+	}
+      }
+      wghts[i] = logprob;
+    }
+    return  wghts;
+  }
+
+  /** Constant for normal distribution. */
+  private static double m_normConst = 0.5 * Math.log(2 * Math.PI);
+
+  /**
+   * Density function of normal distribution.
+   * @param x input value
+   * @param mean mean of distribution
+   * @param stdDev standard deviation of distribution
+   * @return the density
+   */
+  private double logNormalDens (double x, double mean, double stdDev) {
+
+    double diff = x - mean;
+    
+    return - (diff * diff / (2 * stdDev * stdDev))  - m_normConst - Math.log(stdDev);
+  }
+  
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @throws Exception if number of clusters could not be returned successfully
+   */
+  public int numberOfClusters() throws Exception {
+
+    return m_wrappedClusterer.numberOfClusters();
+  }
+
+  /**
+   * Returns a description of the clusterer.
+   *
+   * @return a string containing a description of the clusterer
+   */
+  public String toString() {
+    if (m_priors == null) {
+      return "No clusterer built yet!";
+    }
+
+    StringBuffer text = new StringBuffer();
+    text.append("MakeDensityBasedClusterer: \n\nWrapped clusterer: " 
+		+ m_wrappedClusterer.toString());
+
+    text.append("\nFitted estimators (with ML estimates of variance):\n");
+    
+    for (int j = 0; j < m_priors.length; j++) {
+      text.append("\nCluster: " + j + " Prior probability: " 
+		  + Utils.doubleToString(m_priors[j], 4) + "\n\n");
+      
+      for (int i = 0; i < m_model[0].length; i++) {
+        text.append("Attribute: " + m_theInstances.attribute(i).name() + "\n");
+	
+        if (m_theInstances.attribute(i).isNominal()) {
+          if (m_model[j][i] != null) {
+            text.append(m_model[j][i].toString());
+          }
+        }
+        else {
+          text.append("Normal Distribution. Mean = " 
+		      + Utils.doubleToString(m_modelNormal[j][i][0], 4) 
+		      + " StdDev = " 
+		      + Utils.doubleToString(m_modelNormal[j][i][1], 4) 
+		      + "\n");
+        }
+      }
+    }
+
+    return  text.toString();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clustererTipText() {
+    return "the clusterer to wrap";
+  }
+
+  /**
+   * Sets the clusterer to wrap.
+   *
+   * @param toWrap the clusterer
+   */
+  public void setClusterer(Clusterer toWrap) {
+
+    m_wrappedClusterer = toWrap;
+  }
+
+  /**
+   * Gets the clusterer being wrapped.
+   *
+   * @return the clusterer
+   */
+  public Clusterer getClusterer() {
+
+    return m_wrappedClusterer;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minStdDevTipText() {
+    return "set minimum allowable standard deviation";
+  }
+
+  /**
+   * Set the minimum value for standard deviation when calculating
+   * normal density. Reducing this value can help prevent arithmetic
+   * overflow resulting from multiplying large densities (arising from small
+   * standard deviations) when there are many singleton or near singleton
+   * values.
+   * @param m minimum value for standard deviation
+   */
+  public void setMinStdDev(double m) {
+    m_minStdDev = m;
+  }
+
+  /**
+   * Get the minimum allowable standard deviation.
+   * @return the minumum allowable standard deviation
+   */
+  public double getMinStdDev() {
+    return m_minStdDev;
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tminimum allowable standard deviation for normal density computation "
+	+"\n\t(default 1e-6)"
+	,"M",1,"-M <num>"));
+	
+    result.addElement(new Option(
+	"\tClusterer to wrap.\n"
+	+ "\t(default " + defaultClustererString() + ")",
+	"W", 1,"-W <clusterer name>"));
+
+    if ((m_wrappedClusterer != null) &&
+	(m_wrappedClusterer instanceof OptionHandler)) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to clusterer "
+	  + m_wrappedClusterer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_wrappedClusterer).listOptions();
+      while (enu.hasMoreElements()) {
+	result.addElement(enu.nextElement());
+      }
+    }
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  minimum allowable standard deviation for normal density computation 
+   *  (default 1e-6)</pre>
+   * 
+   * <pre> -W &lt;clusterer name&gt;
+   *  Clusterer to wrap.
+   *  (default weka.clusterers.SimpleKMeans)</pre>
+   * 
+   * <pre> 
+   * Options specific to clusterer weka.clusterers.SimpleKMeans:
+   * </pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -V
+   *  Display std. deviations for centroids.
+   * </pre>
+   * 
+   * <pre> -M
+   *  Replace missing values with mean/mode.
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0)
+      setMinStdDev((new Double(optionString)).doubleValue());
+    else
+      setMinStdDev(1e-6);
+     
+    String wString = Utils.getOption('W', options);
+    if (wString.length() == 0)
+      wString = defaultClustererString();
+    setClusterer(AbstractClusterer.forName(wString, Utils.partitionOptions(options)));
+  }
+
+  /**
+   * Gets the current settings of the clusterer.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+
+    String [] clustererOptions = new String [0];
+    if ((m_wrappedClusterer != null) &&
+	(m_wrappedClusterer instanceof OptionHandler)) {
+      clustererOptions = ((OptionHandler)m_wrappedClusterer).getOptions();
+    }
+    String [] options = new String [clustererOptions.length + 5];
+    int current = 0;
+
+    options[current++] = "-M";
+    options[current++] = ""+getMinStdDev();
+
+    if (getClusterer() != null) {
+      options[current++] = "-W";
+      options[current++] = getClusterer().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(clustererOptions, 0, options, current, 
+		     clustererOptions.length);
+    current += clustererOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5488 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv the options
+   */
+  public static void main(String [] argv) {
+    runClusterer(new MakeDensityBasedClusterer(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/MetisMQIClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/MetisMQIClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/MetisMQIClusterer.java	(revision 29)
@@ -0,0 +1,244 @@
+package weka.clusterers;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import weka.clusterers.forMetisMQI.GraphAlgorithms;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import weka.clusterers.forMetisMQI.util.Configuration;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+public class MetisMQIClusterer extends AbstractClusterer implements
+		OptionHandler {
+
+	/**
+	 * It maps each cluster with an integer id.
+	 */
+	private Map<Set<Node>, Integer> clustersMap = null;
+
+	/**
+	 * Holds the cluster membership for each node.
+	 */
+	private Map<Node, Integer> nodeMap = null;
+	
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public void buildClusterer(Instances data) throws Exception {
+		getCapabilities().testWithFail(data);
+		UndirectedGraph g = new UndirectedGraph();
+		g.loadFromInstance(data);
+		Set<Set<Node>> clusters = GraphAlgorithms.metisMqi(g);
+		setNumClusters(clusters.size());
+		int i = 0;
+		Iterator<Set<Node>> clusterIterator = clusters.iterator();
+		clustersMap = new HashMap<Set<Node>, Integer>();
+		nodeMap = new HashMap<Node, Integer>();
+		while (clusterIterator.hasNext()) {
+			Set<Node> cluster = clusterIterator.next();
+			clustersMap.put(cluster, i);
+			Iterator<Node> nodeIterator = cluster.iterator();
+			while (nodeIterator.hasNext()) {
+				Node n = nodeIterator.next();
+				if (nodeMap.get(n) == null) {
+					nodeMap.put(n, i);
+				}
+			}
+			i++;
+		}
+	}
+
+	@Override
+	public int clusterInstance(Instance instance) throws Exception {
+		Attribute from = instance.dataset().attribute("from");
+		Attribute to = instance.dataset().attribute("to");
+		Instance edge = instance;
+		Node node1 = new Node(Integer.toString(((int) Math.round(edge
+				.value(from)))));
+		Node node2 = new Node(Integer.toString(((int) Math
+				.round(edge.value(to)))));
+		if (nodeMap.get(node1) == nodeMap.get(node2))
+			return nodeMap.get(node1);
+		else
+			throw new Exception();
+	}
+
+	/**
+	 * Parses a given list of options.
+	 * <p/>
+	 * 
+	 * <!-- options-start --> Valid options are:
+	 * <p/>
+	 * 
+	 * <pre>
+	 * -N &lt;num&gt;
+	 *  number of clusters.
+	 *  (default 2).
+	 * </pre>
+	 * 
+	 * <pre>
+	 * -S
+	 *  Maximum size of the finer graph during the coarsening phase.
+	 * </pre>
+	 * 
+	 * <!-- options-end -->
+	 * 
+	 * @param options
+	 *            the list of options as an array of strings
+	 * @throws Exception
+	 *             if an option is not supported
+	 */
+	@Override
+	public void setOptions(String[] options) throws Exception {
+		String optionString = Utils.getOption('N', options);
+		if (optionString.length() != 0) {
+			setNumClusters(Integer.parseInt(optionString));
+		}
+		optionString = Utils.getOption('S', options);
+		if (optionString.length() != 0) {
+			setSizeFinerGraph(Integer.parseInt(optionString));
+		}
+		optionString = Utils.getOption('R', options);
+		setRandomBisection(Boolean.parseBoolean(optionString));
+		optionString = Utils.getOption('V', options);
+		if (optionString.length() != 0) {
+			setVerboseLevel(Integer.parseInt(optionString));
+		}
+		optionString = Utils.getOption('o', options);
+		if (optionString.length() != 0) {
+			setOutputFile(optionString);
+		}
+	}
+
+	private void setOutputFile(String outputFile) {
+		Configuration.instance().setOutputFile(outputFile);
+	}
+	
+	private String getOutputFile() {
+		return Configuration.instance().getOutputFile();
+	}
+
+	private void setVerboseLevel(int verboseLevel) {
+		Configuration.instance().setVerboseLevel(verboseLevel);
+	}
+
+	private void setRandomBisection(boolean b) {
+		Configuration.instance().setRandomBisection(b);
+	}
+
+	/**
+	 * Gets the current settings of MetisMQIClusterer
+	 * 
+	 * @return an array of strings suitable for passing to setOptions()
+	 */
+	@SuppressWarnings("unchecked")
+	@Override
+	public String[] getOptions() {
+		Vector result;
+		result = new Vector();
+		result.add("-N");
+		result.add("" + getNumClusters());
+		result.add("-S");
+		result.add("" + getSizeFinerGraph());
+		result.add("-R");
+		result.add("" + getRandomBisection());
+		result.add("-V");
+		result.add("" + getVerboseLevel());
+		result.add("-o");
+		result.add("" + getOutputFile());
+		return (String[]) result.toArray(new String[result.size()]);
+	}
+
+	private boolean getRandomBisection() {
+		return Configuration.instance().isRandomBisection();
+	}
+
+	private int getVerboseLevel() {
+		return Configuration.instance().getVerboseLevel();
+	}
+
+	private int getSizeFinerGraph() {
+		return Configuration.instance().getSizeFinerGraph();
+	}
+
+	private int getNumClusters() {
+		return Configuration.instance().getNumberOfClusters();
+	}
+
+	/**
+	 * Returns an enumeration describing the available options.
+	 * 
+	 * @return an enumeration of all the available options.
+	 */
+	@SuppressWarnings("unchecked")
+	@Override
+	public Enumeration listOptions() {
+		Vector result = new Vector();
+		result.addElement(new Option("\tnumber of clusters.\n"
+				+ "\t(default 2).", "N", 1, "-N <num>"));
+		result.addElement(new Option("\tsize of finer graph.\n"
+				+ "\t(default 10).", "S", 1, "-S <num>"));
+		result.addElement(new Option("\trandom bisection.\n"
+				+ "\t(default false).", "R", 1, "-V <boolean>"));
+		result.addElement(new Option("\tverbosity of graphical output.\n"
+				+ "\t(default 1).", "V", 1, "-V <num>"));
+		result.addElement(new Option("\tpath of the output file.\n"
+				, "o", 1, "-o <path>"));
+		return result.elements();
+	}
+
+	private void setSizeFinerGraph(int size) {
+		Configuration.instance().setSizeFinerGraph(size);
+	}
+
+	private void setNumClusters(int n) {
+		Configuration.instance().setNumberOfClusters(n);
+	}
+
+	@Override
+	public double[] distributionForInstance(Instance instance) throws Exception {
+		double[] d = new double[numberOfClusters()];
+		d[clusterInstance(instance)] = 1.0;
+		return d;
+	}
+
+	@Override
+	public Capabilities getCapabilities() {
+		Capabilities result = super.getCapabilities();
+		result.enable(Capability.NUMERIC_ATTRIBUTES);
+		result.enable(Capability.NO_CLASS);
+		return result;
+	}
+
+	@Override
+	public int numberOfClusters() throws Exception {
+		return Configuration.instance().getNumberOfClusters();
+	}
+
+	/**
+	 * Main method for executing this clusterer.
+	 * 
+	 * @param args
+	 *            the options, use "-h" to display options
+	 */
+	public static void main(String[] args) {
+		AbstractClusterer.runClusterer(new MetisMQIClusterer(), args);
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/NumberOfClustersRequestable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/NumberOfClustersRequestable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/NumberOfClustersRequestable.java	(revision 29)
@@ -0,0 +1,43 @@
+/*    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumberOfClustersRequestable.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+/**
+ * Interface to a clusterer that can generate a requested number of
+ * clusters
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.2 $
+ */
+public interface NumberOfClustersRequestable {
+  
+  /**
+   * Set the number of clusters to generate
+   *
+   * @param numClusters the number of clusters to generate
+   * @exception Exception if the requested number of 
+   * clusters in inapropriate
+   */
+  void setNumClusters(int numClusters) throws Exception;
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/OPTICS.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/OPTICS.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/OPTICS.java	(revision 29)
@@ -0,0 +1,907 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de) 
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.clusterers.forOPTICSAndDBScan.Databases.Database;
+import weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.OPTICS_Visualizer;
+import weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.SERObject;
+import weka.clusterers.forOPTICSAndDBScan.Utils.EpsilonRange_ListElement;
+import weka.clusterers.forOPTICSAndDBScan.Utils.UpdateQueue;
+import weka.clusterers.forOPTICSAndDBScan.Utils.UpdateQueueElement;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.text.DecimalFormat;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Mihael Ankerst, Markus M. Breunig, Hans-Peter Kriegel, Joerg Sander: OPTICS: Ordering Points To Identify the Clustering Structure. In: ACM SIGMOD International Conference on Management of Data, 49-60, 1999.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Ankerst1999,
+ *    author = {Mihael Ankerst and Markus M. Breunig and Hans-Peter Kriegel and Joerg Sander},
+ *    booktitle = {ACM SIGMOD International Conference on Management of Data},
+ *    pages = {49-60},
+ *    publisher = {ACM Press},
+ *    title = {OPTICS: Ordering Points To Identify the Clustering Structure},
+ *    year = {1999}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;double&gt;
+ *  epsilon (default = 0.9)</pre>
+ * 
+ * <pre> -M &lt;int&gt;
+ *  minPoints (default = 6)</pre>
+ * 
+ * <pre> -I &lt;String&gt;
+ *  index (database) used for OPTICS (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)</pre>
+ * 
+ * <pre> -D &lt;String&gt;
+ *  distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)</pre>
+ * 
+ * <pre> -F
+ *  write results to OPTICS_#TimeStamp#.TXT - File</pre>
+ * 
+ * <pre> -no-gui
+ *  suppress the display of the GUI after building the clusterer</pre>
+ * 
+ * <pre> -db-output &lt;file&gt;
+ *  The file to save the generated database to. If a directory
+ *  is provided, the database doesn't get saved.
+ *  The generated file can be viewed with the OPTICS Visualizer:
+ *    java weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.OPTICS_Visualizer [file.ser]
+ *  (default: .)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 5488 $
+ */
+public class OPTICS 
+    extends AbstractClusterer 
+    implements OptionHandler, TechnicalInformationHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 274552680222105221L;
+  
+    /**
+     * Specifies the radius for a range-query
+     */
+    private double epsilon = 0.9;
+
+    /**
+     * Specifies the density (the range-query must contain at least minPoints DataObjects)
+     */
+    private int minPoints = 6;
+
+    /**
+     * Replace missing values in training instances
+     */
+    private ReplaceMissingValues replaceMissingValues_Filter;
+
+    /**
+     * Holds the number of clusters generated
+     */
+    private int numberOfGeneratedClusters;
+
+    /**
+     * Holds the distance-type that is used
+     * (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)
+     */
+    private String database_distanceType = "weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject";
+
+    /**
+     * Holds the type of the used database
+     * (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)
+     */
+    private String database_Type = "weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase";
+
+    /**
+     * The database that is used for OPTICS
+     */
+    private Database database;
+
+    /**
+     * Holds the time-value (seconds) for the duration of the clustering-process
+     */
+    private double elapsedTime;
+
+    /**
+     * Flag that indicates if the results are written to a file or not
+     */
+    private boolean writeOPTICSresults = false;
+
+    /**
+     * Holds the ClusterOrder (dataObjects with their r_dist and c_dist) for the GUI
+     */
+    private FastVector resultVector;
+
+    /** whether to display the GUI after building the clusterer or not. */
+    private boolean showGUI = true;
+    
+    /** the file to save the generated database object to. */
+    private File databaseOutput = new File(".");
+    
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns default capabilities of the clusterer.
+     *
+     * @return      the capabilities of this clusterer
+     */
+    public Capabilities getCapabilities() {
+      Capabilities result = super.getCapabilities();
+      result.disableAll();
+      result.enable(Capability.NO_CLASS);
+
+      // attributes
+      result.enable(Capability.NOMINAL_ATTRIBUTES);
+      result.enable(Capability.NUMERIC_ATTRIBUTES);
+      result.enable(Capability.DATE_ATTRIBUTES);
+      result.enable(Capability.MISSING_VALUES);
+
+      return result;
+    }
+
+    /**
+     * Generate Clustering via OPTICS
+     * @param instances The instances that need to be clustered
+     * @throws java.lang.Exception If clustering was not successful
+     */
+    public void buildClusterer(Instances instances) throws Exception {
+        // can clusterer handle the data?
+        getCapabilities().testWithFail(instances);
+
+        resultVector = new FastVector();
+        long time_1 = System.currentTimeMillis();
+
+        numberOfGeneratedClusters = 0;
+
+        replaceMissingValues_Filter = new ReplaceMissingValues();
+        replaceMissingValues_Filter.setInputFormat(instances);
+        Instances filteredInstances = Filter.useFilter(instances, replaceMissingValues_Filter);
+
+        database = databaseForName(getDatabase_Type(), filteredInstances);
+        for (int i = 0; i < database.getInstances().numInstances(); i++) {
+            DataObject dataObject = dataObjectForName(getDatabase_distanceType(),
+                    database.getInstances().instance(i),
+                    Integer.toString(i),
+                    database);
+            database.insert(dataObject);
+        }
+        database.setMinMaxValues();
+
+        UpdateQueue seeds = new UpdateQueue();
+
+        /** OPTICS-Begin */
+        Iterator iterator = database.dataObjectIterator();
+        while (iterator.hasNext()) {
+            DataObject dataObject = (DataObject) iterator.next();
+            if (!dataObject.isProcessed()) {
+                expandClusterOrder(dataObject, seeds);
+            }
+        }
+
+        long time_2 = System.currentTimeMillis();
+        elapsedTime = (double) (time_2 - time_1) / 1000.0;
+
+        if (writeOPTICSresults) {
+            String fileName = "";
+            GregorianCalendar gregorianCalendar = new GregorianCalendar();
+            String timeStamp = gregorianCalendar.get(Calendar.DAY_OF_MONTH) + "-" +
+                    (gregorianCalendar.get(Calendar.MONTH) + 1) +
+                    "-" + gregorianCalendar.get(Calendar.YEAR) +
+                    "--" + gregorianCalendar.get(Calendar.HOUR_OF_DAY) +
+                    "-" + gregorianCalendar.get(Calendar.MINUTE) +
+                    "-" + gregorianCalendar.get(Calendar.SECOND);
+            fileName = "OPTICS_" + timeStamp + ".TXT";
+
+            FileWriter fileWriter = new FileWriter(fileName);
+            BufferedWriter bufferedOPTICSWriter = new BufferedWriter(fileWriter);
+            for (int i = 0; i < resultVector.size(); i++) {
+                bufferedOPTICSWriter.write(format_dataObject((DataObject) resultVector.elementAt(i)));
+            }
+            bufferedOPTICSWriter.flush();
+            bufferedOPTICSWriter.close();
+        }
+
+        // explicit file provided to write the generated database to?
+        if (!databaseOutput.isDirectory()) {
+          try {
+            FileOutputStream fos = new FileOutputStream(databaseOutput);
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            oos.writeObject(getSERObject());
+            oos.flush();
+            oos.close();
+            fos.close();
+          }
+          catch (Exception e) {
+            System.err.println(
+        	"Error writing generated database to file '" + getDatabaseOutput() + "': " 
+        	+ e);
+            e.printStackTrace();
+          }
+        }
+        
+        if (showGUI)
+          new OPTICS_Visualizer(getSERObject(), "OPTICS Visualizer - Main Window");
+    }
+
+    /**
+     * Expands the ClusterOrder for this dataObject
+     * @param dataObject Start-DataObject
+     * @param seeds SeedList that stores dataObjects with reachability-distances
+     */
+    private void expandClusterOrder(DataObject dataObject, UpdateQueue seeds) {
+        List list = database.coreDistance(getMinPoints(), getEpsilon(), dataObject);
+        List epsilonRange_List = (List) list.get(1);
+        dataObject.setReachabilityDistance(DataObject.UNDEFINED);
+        dataObject.setCoreDistance(((Double) list.get(2)).doubleValue());
+        dataObject.setProcessed(true);
+
+        resultVector.addElement(dataObject);
+
+        if (dataObject.getCoreDistance() != DataObject.UNDEFINED) {
+            update(seeds, epsilonRange_List, dataObject);
+            while (seeds.hasNext()) {
+                UpdateQueueElement updateQueueElement = seeds.next();
+                DataObject currentDataObject = (DataObject) updateQueueElement.getObject();
+                currentDataObject.setReachabilityDistance(updateQueueElement.getPriority());
+                List list_1 = database.coreDistance(getMinPoints(), getEpsilon(), currentDataObject);
+                List epsilonRange_List_1 = (List) list_1.get(1);
+                currentDataObject.setCoreDistance(((Double) list_1.get(2)).doubleValue());
+                currentDataObject.setProcessed(true);
+
+                resultVector.addElement(currentDataObject);
+
+                if (currentDataObject.getCoreDistance() != DataObject.UNDEFINED) {
+                    update(seeds, epsilonRange_List_1, currentDataObject);
+                }
+            }
+        }
+    }
+
+    /**
+     * Wraps the dataObject into a String, that contains the dataObject's key, the dataObject itself,
+     * the coreDistance and its reachabilityDistance in a formatted manner.
+     * @param dataObject The dataObject that is wrapped into a formatted string.
+     * @return String Formatted string
+     */
+    private String format_dataObject(DataObject dataObject) {
+        StringBuffer stringBuffer = new StringBuffer();
+
+        stringBuffer.append("(" + Utils.doubleToString(Double.parseDouble(dataObject.getKey()),
+                (Integer.toString(database.size()).length()), 0) + ".) "
+                + Utils.padRight(dataObject.toString(), 40) + "  -->  c_dist: " +
+
+                ((dataObject.getCoreDistance() == DataObject.UNDEFINED) ?
+                Utils.padRight("UNDEFINED", 12) :
+                Utils.padRight(Utils.doubleToString(dataObject.getCoreDistance(), 2, 3), 12)) +
+
+                " r_dist: " +
+                ((dataObject.getReachabilityDistance() == DataObject.UNDEFINED) ?
+                Utils.padRight("UNDEFINED", 12) :
+                Utils.doubleToString(dataObject.getReachabilityDistance(), 2, 3)) + "\n");
+
+        return stringBuffer.toString();
+    }
+
+    /**
+     * Updates reachability-distances in the Seeds-List
+     * @param seeds UpdateQueue that holds DataObjects with their corresponding reachability-distances
+     * @param epsilonRange_list List of DataObjects that were found in epsilon-range of centralObject
+     * @param centralObject
+     */
+    private void update(UpdateQueue seeds, List epsilonRange_list, DataObject centralObject) {
+        double coreDistance = centralObject.getCoreDistance();
+        double new_r_dist = DataObject.UNDEFINED;
+
+        for (int i = 0; i < epsilonRange_list.size(); i++) {
+            EpsilonRange_ListElement listElement = (EpsilonRange_ListElement) epsilonRange_list.get(i);
+            DataObject neighbourhood_object = listElement.getDataObject();
+            if (!neighbourhood_object.isProcessed()) {
+                new_r_dist = Math.max(coreDistance, listElement.getDistance());
+                seeds.add(new_r_dist, neighbourhood_object, neighbourhood_object.getKey());
+            }
+        }
+    }
+
+    /**
+     * Classifies a given instance.
+     *
+     * @param instance The instance to be assigned to a cluster
+     * @return int The number of the assigned cluster as an integer
+     * @throws java.lang.Exception If instance could not be clustered
+     * successfully
+     */
+    public int clusterInstance(Instance instance) throws Exception {
+        throw new Exception();
+    }
+
+    /**
+     * Returns the number of clusters.
+     *
+     * @return int The number of clusters generated for a training dataset.
+     * @throws java.lang.Exception If number of clusters could not be returned
+     * successfully
+     */
+    public int numberOfClusters() throws Exception {
+        return numberOfGeneratedClusters;
+    }
+
+    /**
+     * Returns an enumeration of all the available options.
+     *
+     * @return Enumeration An enumeration of all available options.
+     */
+    public Enumeration listOptions() {
+        Vector vector = new Vector();
+
+        vector.addElement(
+            new Option(
+        	"\tepsilon (default = 0.9)",
+        	"E", 1, "-E <double>"));
+        
+        vector.addElement(
+            new Option("\tminPoints (default = 6)",
+        	"M", 1, "-M <int>"));
+        
+        vector.addElement(
+            new Option(
+        	"\tindex (database) used for OPTICS (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)",
+        	"I", 1, "-I <String>"));
+        
+        vector.addElement(
+            new Option(
+        	"\tdistance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)",
+        	"D", 1, "-D <String>"));
+        
+        vector.addElement(
+            new Option(
+        	"\twrite results to OPTICS_#TimeStamp#.TXT - File",
+        	"F", 0, "-F"));
+        
+        vector.addElement(
+            new Option(
+        	"\tsuppress the display of the GUI after building the clusterer",
+        	"no-gui", 0, "-no-gui"));
+        
+        vector.addElement(
+            new Option(
+        	"\tThe file to save the generated database to. If a directory\n"
+        	+ "\tis provided, the database doesn't get saved.\n"
+        	+ "\tThe generated file can be viewed with the OPTICS Visualizer:\n"
+        	+ "\t  java " + OPTICS_Visualizer.class.getName() + " [file.ser]\n"
+        	+ "\t(default: .)",
+        	"db-output", 1, "-db-output <file>"));
+        
+        return vector.elements();
+    }
+
+    /**
+     * Sets the OptionHandler's options using the given list. All options
+     * will be set (or reset) during this call (i.e. incremental setting
+     * of options is not possible). <p/>
+     * 
+     <!-- options-start -->
+     * Valid options are: <p/>
+     * 
+     * <pre> -E &lt;double&gt;
+     *  epsilon (default = 0.9)</pre>
+     * 
+     * <pre> -M &lt;int&gt;
+     *  minPoints (default = 6)</pre>
+     * 
+     * <pre> -I &lt;String&gt;
+     *  index (database) used for OPTICS (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase)</pre>
+     * 
+     * <pre> -D &lt;String&gt;
+     *  distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject)</pre>
+     * 
+     * <pre> -F
+     *  write results to OPTICS_#TimeStamp#.TXT - File</pre>
+     * 
+     * <pre> -no-gui
+     *  suppress the display of the GUI after building the clusterer</pre>
+     * 
+     * <pre> -db-output &lt;file&gt;
+     *  The file to save the generated database to. If a directory
+     *  is provided, the database doesn't get saved.
+     *  The generated file can be viewed with the OPTICS Visualizer:
+     *    java weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.OPTICS_Visualizer [file.ser]
+     *  (default: .)</pre>
+     * 
+     <!-- options-end -->
+     *
+     * @param options The list of options as an array of strings
+     * @throws java.lang.Exception If an option is not supported
+     */
+    public void setOptions(String[] options) throws Exception {
+        String optionString = Utils.getOption('E', options);
+        if (optionString.length() != 0)
+            setEpsilon(Double.parseDouble(optionString));
+        else
+            setEpsilon(0.9);
+
+        optionString = Utils.getOption('M', options);
+        if (optionString.length() != 0)
+            setMinPoints(Integer.parseInt(optionString));
+        else
+            setMinPoints(6);
+
+        optionString = Utils.getOption('I', options);
+        if (optionString.length() != 0)
+            setDatabase_Type(optionString);
+        else
+            setDatabase_Type(weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase.class.getName());
+
+        optionString = Utils.getOption('D', options);
+        if (optionString.length() != 0)
+            setDatabase_distanceType(optionString);
+        else
+            setDatabase_distanceType(weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject.class.getName());
+
+        setWriteOPTICSresults(Utils.getFlag('F', options));
+
+        setShowGUI(!Utils.getFlag("no-gui", options));
+
+        optionString = Utils.getOption("db-output", options);
+        if (optionString.length() != 0)
+            setDatabaseOutput(new File(optionString));
+        else
+            setDatabaseOutput(new File("."));
+    }
+
+    /**
+     * Gets the current option settings for the OptionHandler.
+     *
+     * @return String[] The list of current option settings as an array of strings
+     */
+    public String[] getOptions() {
+        Vector<String>	result;
+        
+        result = new Vector<String>();
+        
+        result.add("-E");
+        result.add("" + getEpsilon());
+        
+        result.add("-M");
+        result.add("" + getMinPoints());
+        
+        result.add("-I");
+        result.add("" + getDatabase_Type());
+        
+        result.add("-D");
+        result.add("" + getDatabase_distanceType());
+
+        if (getWriteOPTICSresults())
+          result.add("-F");
+
+        if (!getShowGUI())
+          result.add("-no-gui");
+        
+        result.add("-db-output");
+        result.add("" + getDatabaseOutput());
+        
+        return result.toArray(new String[result.size()]);
+    }
+
+    /**
+     * Returns a new Class-Instance of the specified database
+     * @param database_Type String of the specified database
+     * @param instances Instances that were delivered from WEKA
+     * @return Database New constructed Database
+     */
+    public Database databaseForName(String database_Type, Instances instances) {
+        Object o = null;
+
+        Constructor co = null;
+        try {
+            co = (Class.forName(database_Type)).getConstructor(new Class[]{Instances.class});
+            o = co.newInstance(new Object[]{instances});
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+
+        return (Database) o;
+    }
+
+    /**
+     * Returns a new Class-Instance of the specified database
+     * @param database_distanceType String of the specified distance-type
+     * @param instance The original instance that needs to hold by this DataObject
+     * @param key Key for this DataObject
+     * @param database Link to the database
+     * @return DataObject New constructed DataObject
+     */
+    public DataObject dataObjectForName(String database_distanceType, Instance instance, String key, Database database) {
+        Object o = null;
+
+        Constructor co = null;
+        try {
+            co = (Class.forName(database_distanceType)).
+                    getConstructor(new Class[]{Instance.class, String.class, Database.class});
+            o = co.newInstance(new Object[]{instance, key, database});
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (SecurityException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+
+        return (DataObject) o;
+    }
+
+    /**
+     * Sets a new value for minPoints
+     * @param minPoints MinPoints
+     */
+    public void setMinPoints(int minPoints) {
+        this.minPoints = minPoints;
+    }
+
+    /**
+     * Sets a new value for epsilon
+     * @param epsilon Epsilon
+     */
+    public void setEpsilon(double epsilon) {
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Returns the value of epsilon
+     * @return double Epsilon
+     */
+    public double getEpsilon() {
+        return epsilon;
+    }
+
+    /**
+     * Returns the value of minPoints
+     * @return int MinPoints
+     */
+    public int getMinPoints() {
+        return minPoints;
+    }
+
+    /**
+     * Returns the distance-type
+     * @return String Distance-type
+     */
+    public String getDatabase_distanceType() {
+        return database_distanceType;
+    }
+
+    /**
+     * Returns the type of the used index (database)
+     * @return String Index-type
+     */
+    public String getDatabase_Type() {
+        return database_Type;
+    }
+
+    /**
+     * Sets a new distance-type
+     * @param database_distanceType The new distance-type
+     */
+    public void setDatabase_distanceType(String database_distanceType) {
+        this.database_distanceType = database_distanceType;
+    }
+
+    /**
+     * Sets a new database-type
+     * @param database_Type The new database-type
+     */
+    public void setDatabase_Type(String database_Type) {
+        this.database_Type = database_Type;
+    }
+
+    /**
+     * Returns the flag for writing actions
+     * @return writeOPTICSresults (flag)
+     */
+    public boolean getWriteOPTICSresults() {
+        return writeOPTICSresults;
+    }
+
+    /**
+     * Sets the flag for writing actions
+     * @param writeOPTICSresults Results are written to a file if the flag is set
+     */
+    public void setWriteOPTICSresults(boolean writeOPTICSresults) {
+        this.writeOPTICSresults = writeOPTICSresults;
+    }
+
+    /**
+     * Returns the flag for showing the OPTICS visualizer GUI.
+     * 
+     * @return 		true if the GUI is displayed
+     */
+    public boolean getShowGUI() {
+        return showGUI;
+    }
+
+    /**
+     * Sets the flag for displaying the GUI.
+     * 
+     * @param value 	if true, then the OPTICS visualizer GUI will be 
+     * 			displayed after building the clusterer
+     */
+    public void setShowGUI(boolean value) {
+        showGUI = value;
+    }
+
+    /**
+     * Returns the file to save the database to - if directory, database is not
+     * saved.
+     * 
+     * @return 		the file to save the database to a directory if saving
+     * 			is ignored
+     */
+    public File getDatabaseOutput() {
+        return databaseOutput;
+    }
+
+    /**
+     * Sets the the file to save the generated database to. If a directory
+     * is provided, the datbase doesn't get saved.
+     * 
+     * @param value 	the file to save the database to or a directory if
+     * 			saving is to be ignored
+     */
+    public void setDatabaseOutput(File value) {
+        databaseOutput = value;
+    }
+
+    /**
+     * Returns the resultVector
+     * @return resultVector
+     */
+    public FastVector getResultVector() {
+        return resultVector;
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String epsilonTipText() {
+        return "radius of the epsilon-range-queries";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String minPointsTipText() {
+        return "minimun number of DataObjects required in an epsilon-range-query";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String database_TypeTipText() {
+        return "used database";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String database_distanceTypeTipText() {
+        return "used distance-type";
+    }
+
+    /**
+     * Returns the tip text for this property
+     * @return tip text for this property suitable for
+     * displaying in the explorer/experimenter gui
+     */
+    public String writeOPTICSresultsTipText() {
+        return "if the -F option is set, the results are written to OPTICS_#TimeStamp#.TXT";
+    }
+
+    /**
+     * Returns the tip text for this property.
+     * 
+     * @return 		tip text for this property suitable for
+     * 			displaying in the explorer/experimenter gui
+     */
+    public String showGUITipText() {
+        return "Defines whether the OPTICS Visualizer is displayed after the clusterer has been built or not.";
+    }
+
+    /**
+     * Returns the tip text for this property.
+     * 
+     * @return 		tip text for this property suitable for
+     * 			displaying in the explorer/experimenter gui
+     */
+    public String databaseOutputTipText() {
+        return 
+            "The optional output file for the generated database object - can "
+          + "be viewed with the OPTICS Visualizer.\n"
+          + "java " + OPTICS_Visualizer.class.getName() + " [file.ser]";
+    }
+
+    /**
+     * Returns a string describing this DataMining-Algorithm
+     * @return String Information for the gui-explorer
+     */
+    public String globalInfo() {
+        return getTechnicalInformation().toString();
+    }
+
+    /**
+     * Returns an instance of a TechnicalInformation object, containing 
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     * 
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      
+      result = new TechnicalInformation(Type.INPROCEEDINGS);
+      result.setValue(Field.AUTHOR, "Mihael Ankerst and Markus M. Breunig and Hans-Peter Kriegel and Joerg Sander");
+      result.setValue(Field.TITLE, "OPTICS: Ordering Points To Identify the Clustering Structure");
+      result.setValue(Field.BOOKTITLE, "ACM SIGMOD International Conference on Management of Data");
+      result.setValue(Field.YEAR, "1999");
+      result.setValue(Field.PAGES, "49-60");
+      result.setValue(Field.PUBLISHER, "ACM Press");
+      
+      return result;
+    }
+
+    /**
+     * Returns the internal database
+     * 
+     * @return the internal database
+     */
+    public SERObject getSERObject() {
+        SERObject serObject = new SERObject(resultVector,
+                database.size(),
+                database.getInstances().numAttributes(),
+                getEpsilon(),
+                getMinPoints(),
+                writeOPTICSresults,
+                getDatabase_Type(),
+                getDatabase_distanceType(),
+                numberOfGeneratedClusters,
+                Utils.doubleToString(elapsedTime, 3, 3));
+        return serObject;
+    }
+
+    /**
+     * Returns a description of the clusterer
+     * 
+     * @return the clusterer as string
+     */
+    public String toString() {
+        StringBuffer stringBuffer = new StringBuffer();
+        stringBuffer.append("OPTICS clustering results\n" +
+                "============================================================================================\n\n");
+        stringBuffer.append("Clustered DataObjects: " + database.size() + "\n");
+        stringBuffer.append("Number of attributes: " + database.getInstances().numAttributes() + "\n");
+        stringBuffer.append("Epsilon: " + getEpsilon() + "; minPoints: " + getMinPoints() + "\n");
+        stringBuffer.append("Write results to file: " + (writeOPTICSresults ? "yes" : "no") + "\n");
+        stringBuffer.append("Index: " + getDatabase_Type() + "\n");
+        stringBuffer.append("Distance-type: " + getDatabase_distanceType() + "\n");
+        stringBuffer.append("Number of generated clusters: " + numberOfGeneratedClusters + "\n");
+        DecimalFormat decimalFormat = new DecimalFormat(".##");
+        stringBuffer.append("Elapsed time: " + decimalFormat.format(elapsedTime) + "\n\n");
+
+        for (int i = 0; i < resultVector.size(); i++) {
+            stringBuffer.append(format_dataObject((DataObject) resultVector.elementAt(i)));
+        }
+        return stringBuffer.toString() + "\n";
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5488 $");
+    }
+
+    /**
+     * Main Method for testing OPTICS
+     * @param args Valid parameters are: 'E' epsilon (default = 0.9); 'M' minPoints (default = 6);
+     *                                   'I' index-type (default = weka.clusterers.forOPTICSAndDBScan.Databases.SequentialDatabase);
+     *                                   'D' distance-type (default = weka.clusterers.forOPTICSAndDBScan.DataObjects.EuclidianDataObject);
+     *                                   'F' write results to OPTICS_#TimeStamp#.TXT - File
+     */
+    public static void main(String[] args) {
+        runClusterer(new OPTICS(), args);
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/RandomizableClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/RandomizableClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/RandomizableClusterer.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableClusterer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * clusterers.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public abstract class RandomizableClusterer
+  extends AbstractClusterer
+  implements OptionHandler, Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4819590778152242745L;
+  
+  /** the default seed value */
+  protected int m_SeedDefault = 1;
+  
+  /** The random number seed. */
+  protected int m_Seed = m_SeedDefault;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tRandom number seed.\n"
+	+ "\t(default " + m_SeedDefault + ")",
+	"S", 1, "-S <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(m_SeedDefault);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.add("-S");
+    result.add("" + getSeed());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param value 	the seed to use
+   */
+  public void setSeed(int value) {
+    m_Seed = value;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return 		the seed for the random number generation
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/RandomizableDensityBasedClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/RandomizableDensityBasedClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/RandomizableDensityBasedClusterer.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableDensityBasedClusterer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * clusterers.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public abstract class RandomizableDensityBasedClusterer
+  extends AbstractDensityBasedClusterer
+  implements OptionHandler, Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5325270357918932849L;
+  
+  /** the default seed value */
+  protected int m_SeedDefault = 1;
+  
+  /** The random number seed. */
+  protected int m_Seed = m_SeedDefault;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tRandom number seed.\n"
+	+ "\t(default " + m_SeedDefault + ")",
+	"S", 1, "-S <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(m_SeedDefault);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.add("-S");
+    result.add("" + getSeed());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param value 	the seed to use
+   */
+  public void setSeed(int value) {
+    m_Seed = value;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return 		the seed for the random number generation
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/RandomizableSingleClustererEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/RandomizableSingleClustererEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/RandomizableSingleClustererEnhancer.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomizableSingleClustererEnhancer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract utility class for handling settings common to randomizable
+ * clusterers.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public abstract class RandomizableSingleClustererEnhancer
+  extends AbstractClusterer
+  implements OptionHandler, Randomizable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -644847037106316249L;
+  
+  /** the default seed value */
+  protected int m_SeedDefault = 1;
+  
+  /** The random number seed. */
+  protected int m_Seed = m_SeedDefault;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tRandom number seed.\n"
+	+ "\t(default " + m_SeedDefault + ")",
+	"S", 1, "-S <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(m_SeedDefault);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.add("-S");
+    result.add("" + getSeed());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random number seed to be used.";
+  }
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param value 	the seed to use
+   */
+  public void setSeed(int value) {
+    m_Seed = value;
+  }
+
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return 		the seed for the random number generation
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/SimpleKMeans.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/SimpleKMeans.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/SimpleKMeans.java	(revision 29)
@@ -0,0 +1,1267 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleKMeans.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.clusterers;
+
+import weka.classifiers.rules.DecisionTableHashKey;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.ManhattanDistance;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Cluster data using the k means algorithm
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -V
+ *  Display std. deviations for centroids.
+ * </pre>
+ * 
+ * <pre> -M
+ *  Replace missing values with mean/mode.
+ * </pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ * <pre> -A &lt;classname and options&gt;
+ *  Distance function to be used for instance comparison
+ *  (default weka.core.EuclidianDistance)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  Maximum number of iterations. </pre>
+ * 
+ * <pre> -O 
+ *  Preserve order of instances. </pre>
+ * 
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ * @see RandomizableClusterer
+ */
+public class SimpleKMeans 
+  extends RandomizableClusterer 
+  implements NumberOfClustersRequestable, WeightedInstancesHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3235809600124455376L;
+  
+  /**
+   * replace missing values in training instances
+   */
+  private ReplaceMissingValues m_ReplaceMissingFilter;
+
+  /**
+   * number of clusters to generate
+   */
+  private int m_NumClusters = 2;
+
+  /**
+   * holds the cluster centroids
+   */
+  private Instances m_ClusterCentroids;
+
+  /**
+   * Holds the standard deviations of the numeric attributes in each cluster
+   */
+  private Instances m_ClusterStdDevs;
+
+  
+  /**
+   * For each cluster, holds the frequency counts for the values of each 
+   * nominal attribute
+   */
+  private int [][][] m_ClusterNominalCounts;
+  private int[][] m_ClusterMissingCounts;
+  
+  /**
+   * Stats on the full data set for comparison purposes
+   * In case the attribute is numeric the value is the mean if is 
+   * being used the Euclidian distance or the median if Manhattan distance
+   * and if the attribute is nominal then it's mode is saved
+   */
+  private double[] m_FullMeansOrMediansOrModes;
+  private double[] m_FullStdDevs;
+  private int[][] m_FullNominalCounts;
+  private int[] m_FullMissingCounts;
+
+  /**
+   * Display standard deviations for numeric atts
+   */
+  private boolean m_displayStdDevs;
+
+  /**
+   * Replace missing values globally?
+   */
+  private boolean m_dontReplaceMissing = false;
+
+  /**
+   * The number of instances in each cluster
+   */
+  private int [] m_ClusterSizes;
+
+  /**
+   * Maximum number of iterations to be executed
+   */
+  private int m_MaxIterations = 500;
+
+  /**
+   * Keep track of the number of iterations completed before convergence
+   */
+  private int m_Iterations = 0;
+
+  /**
+   * Holds the squared errors for all clusters
+   */
+  private double [] m_squaredErrors;
+
+  /** the distance function used. */
+  protected DistanceFunction m_DistanceFunction = new EuclideanDistance();
+
+  /**
+   * Preserve order of instances 
+   */
+  private boolean m_PreserveOrder = false;
+	
+  /**
+   * Assignments obtained
+   */
+  protected int[] m_Assignments = null;
+	
+  /**
+   * the default constructor
+   */
+  public SimpleKMeans() {
+    super();
+    
+    m_SeedDefault = 10;
+    setSeed(m_SeedDefault);
+  }
+  
+  /**
+   * Returns a string describing this clusterer
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Cluster data using the k means algorithm. Can use either "
+      + "the Euclidean distance (default) or the Manhattan distance."
+      + " If the Manhattan distance is used, then centroids are computed "
+      + "as the component-wise median rather than mean.";
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Generates a clusterer. Has to initialize all fields of the clusterer
+   * that are not being set via options.
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the clusterer has not been 
+   * generated successfully
+   */
+  public void buildClusterer(Instances data) throws Exception {
+
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+
+    m_Iterations = 0;
+
+    m_ReplaceMissingFilter = new ReplaceMissingValues();
+    Instances instances = new Instances(data);
+				
+    instances.setClassIndex(-1);
+    if (!m_dontReplaceMissing) {
+      m_ReplaceMissingFilter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_ReplaceMissingFilter);
+    }
+
+    m_FullMissingCounts = new int[instances.numAttributes()];
+    if (m_displayStdDevs) {
+      m_FullStdDevs = new double[instances.numAttributes()];
+    }
+    m_FullNominalCounts = new int[instances.numAttributes()][0];
+		
+    m_FullMeansOrMediansOrModes = moveCentroid(0, instances, false);
+    for (int i = 0; i < instances.numAttributes(); i++) {
+      m_FullMissingCounts[i] = instances.attributeStats(i).missingCount;
+      if (instances.attribute(i).isNumeric()) {
+        if (m_displayStdDevs) {
+          m_FullStdDevs[i] = Math.sqrt(instances.variance(i));
+        }
+        if (m_FullMissingCounts[i] == instances.numInstances()) {
+          m_FullMeansOrMediansOrModes[i] = Double.NaN; // mark missing as mean
+        }
+      } else {
+        m_FullNominalCounts[i] = instances.attributeStats(i).nominalCounts;
+        if (m_FullMissingCounts[i] 
+            > m_FullNominalCounts[i][Utils.maxIndex(m_FullNominalCounts[i])]) {
+          m_FullMeansOrMediansOrModes[i] = -1; // mark missing as most common value
+        }
+      }
+    }
+
+    m_ClusterCentroids = new Instances(instances, m_NumClusters);
+    int[] clusterAssignments = new int [instances.numInstances()];
+
+    if(m_PreserveOrder)
+      m_Assignments = clusterAssignments;
+		
+    m_DistanceFunction.setInstances(instances);
+    
+    Random RandomO = new Random(getSeed());
+    int instIndex;
+    HashMap initC = new HashMap();
+    DecisionTableHashKey hk = null;
+
+    Instances initInstances = null;
+    if(m_PreserveOrder)
+      initInstances = new Instances(instances);
+    else
+      initInstances = instances;
+		
+    for (int j = initInstances.numInstances() - 1; j >= 0; j--) {
+      instIndex = RandomO.nextInt(j+1);
+      hk = new DecisionTableHashKey(initInstances.instance(instIndex),
+                                    initInstances.numAttributes(), true);
+      if (!initC.containsKey(hk)) {
+        m_ClusterCentroids.add(initInstances.instance(instIndex));
+	initC.put(hk, null);
+      }
+      initInstances.swap(j, instIndex);
+      
+      if (m_ClusterCentroids.numInstances() == m_NumClusters) {
+	break;
+      }
+    }
+
+    m_NumClusters = m_ClusterCentroids.numInstances();
+    
+    //removing reference
+    initInstances = null;
+		
+    int i;
+    boolean converged = false;
+    int emptyClusterCount;
+    Instances [] tempI = new Instances[m_NumClusters];
+    m_squaredErrors = new double [m_NumClusters];
+    m_ClusterNominalCounts = new int [m_NumClusters][instances.numAttributes()][0];
+    m_ClusterMissingCounts = new int[m_NumClusters][instances.numAttributes()];
+    while (!converged) {
+      emptyClusterCount = 0;
+      m_Iterations++;
+      converged = true;
+      for (i = 0; i < instances.numInstances(); i++) {
+	Instance toCluster = instances.instance(i);
+	int newC = clusterProcessedInstance(toCluster, true);
+	if (newC != clusterAssignments[i]) {
+	  converged = false;
+	}
+	clusterAssignments[i] = newC;
+      }
+      
+      // update centroids
+      m_ClusterCentroids = new Instances(instances, m_NumClusters);
+      for (i = 0; i < m_NumClusters; i++) {
+	tempI[i] = new Instances(instances, 0);
+      }
+      for (i = 0; i < instances.numInstances(); i++) {
+	tempI[clusterAssignments[i]].add(instances.instance(i));
+      }
+      for (i = 0; i < m_NumClusters; i++) {
+	if (tempI[i].numInstances() == 0) {
+	  // empty cluster
+	  emptyClusterCount++;
+	} else {
+          moveCentroid( i, tempI[i], true  );					
+	}
+      }
+
+      if (emptyClusterCount > 0) {
+	m_NumClusters -= emptyClusterCount;
+        if (converged) {
+          Instances[] t = new Instances[m_NumClusters];
+          int index = 0;
+          for (int k = 0; k < tempI.length; k++) {
+            if (tempI[k].numInstances() > 0) {
+              t[index++] = tempI[k];
+            }
+          }
+          tempI = t;
+        } else {
+          tempI = new Instances[m_NumClusters];
+        }
+      }
+			
+      if(m_Iterations == m_MaxIterations)
+        converged = true;
+			
+      if (!converged) {
+	m_squaredErrors = new double [m_NumClusters];
+	m_ClusterNominalCounts = new int [m_NumClusters][instances.numAttributes()][0];
+      }
+    }
+		
+    if (m_displayStdDevs) {
+      m_ClusterStdDevs = new Instances(instances, m_NumClusters);
+    }
+    m_ClusterSizes = new int [m_NumClusters];
+    for (i = 0; i < m_NumClusters; i++) {
+      if (m_displayStdDevs) {
+        double [] vals2 = new double[instances.numAttributes()];
+        for (int j = 0; j < instances.numAttributes(); j++) {
+          if (instances.attribute(j).isNumeric()) {
+            vals2[j] = Math.sqrt(tempI[i].variance(j));
+          } else {
+            vals2[j] = Utils.missingValue();
+          }	
+        }    
+        m_ClusterStdDevs.add(new DenseInstance(1.0, vals2));
+      }
+      m_ClusterSizes[i] = tempI[i].numInstances();
+    }
+  }
+
+  /**
+   * Move the centroid to it's new coordinates. Generate the centroid coordinates based 
+   * on it's  members (objects assigned to the cluster of the centroid) and the distance 
+   * function being used.
+   * @param centroidIndex index of the centroid which the coordinates will be computed
+   * @param members the objects that are assigned to the cluster of this centroid
+   * @param updateClusterInfo if the method is supposed to update the m_Cluster arrays
+   * @return the centroid coordinates
+   */
+  protected double[] moveCentroid(int centroidIndex, Instances members, boolean updateClusterInfo){
+    double [] vals = new double[members.numAttributes()];
+		
+    //used only for Manhattan Distance
+    Instances sortedMembers = null;
+    int middle = 0;
+    boolean dataIsEven = false;
+		
+    if(m_DistanceFunction instanceof ManhattanDistance){
+      middle = (members.numInstances()-1)/2;
+      dataIsEven = ((members.numInstances()%2)==0);
+      if(m_PreserveOrder){
+        sortedMembers = members;
+      }else{
+        sortedMembers = new Instances(members);
+      }
+    }
+		
+    for (int j = 0; j < members.numAttributes(); j++) {						
+			
+      //in case of Euclidian distance the centroid is the mean point
+      //in case of Manhattan distance the centroid is the median point
+      //in both cases, if the attribute is nominal, the centroid is the mode
+      if(m_DistanceFunction instanceof EuclideanDistance ||
+         members.attribute(j).isNominal())
+        {													
+          vals[j] = members.meanOrMode(j);
+        }else if(m_DistanceFunction instanceof ManhattanDistance){
+        //singleton special case
+        if(members.numInstances() == 1){
+          vals[j] = members.instance(0).value(j);
+        }else{
+          sortedMembers.kthSmallestValue(j, middle+1);
+          vals[j] = sortedMembers.instance(middle).value(j);
+          if( dataIsEven ){						
+            sortedMembers.kthSmallestValue(j, middle+2);						
+            vals[j] = (vals[j]+sortedMembers.instance(middle+1).value(j))/2;
+          }
+        }
+      }	
+			
+      if(updateClusterInfo){
+        m_ClusterMissingCounts[centroidIndex][j] = members.attributeStats(j).missingCount;
+        m_ClusterNominalCounts[centroidIndex][j] = members.attributeStats(j).nominalCounts;
+        if (members.attribute(j).isNominal()) {
+          if (m_ClusterMissingCounts[centroidIndex][j] >  
+              m_ClusterNominalCounts[centroidIndex][j][Utils.maxIndex(m_ClusterNominalCounts[centroidIndex][j])]) 
+            {
+              vals[j] = Utils.missingValue(); // mark mode as missing
+            }
+        } else {
+          if (m_ClusterMissingCounts[centroidIndex][j] == members.numInstances()) {
+            vals[j] = Utils.missingValue(); // mark mean as missing
+          }
+        }
+      }
+    }
+    if(updateClusterInfo)
+      m_ClusterCentroids.add(new DenseInstance(1.0, vals));
+    return vals;
+  }
+	
+  /**
+   * clusters an instance that has been through the filters
+   *
+   * @param instance the instance to assign a cluster to
+   * @param updateErrors if true, update the within clusters sum of errors
+   * @return a cluster number
+   */
+  private int clusterProcessedInstance(Instance instance, boolean updateErrors) {
+    double minDist = Integer.MAX_VALUE;
+    int bestCluster = 0;
+    for (int i = 0; i < m_NumClusters; i++) {
+      double dist = m_DistanceFunction.distance(instance, m_ClusterCentroids.instance(i));
+      if (dist < minDist) {
+	minDist = dist;
+	bestCluster = i;
+      }
+    }
+    if (updateErrors) {
+      if(m_DistanceFunction instanceof EuclideanDistance){
+        //Euclidean distance to Squared Euclidean distance
+        minDist *= minDist;
+      }
+      m_squaredErrors[bestCluster] += minDist;
+    }
+    return bestCluster;
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an interger
+   * if the class is enumerated, otherwise the predicted value
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    Instance inst = null;
+    if (!m_dontReplaceMissing) {
+      m_ReplaceMissingFilter.input(instance);
+      m_ReplaceMissingFilter.batchFinished();
+      inst = m_ReplaceMissingFilter.output();
+    } else {
+      inst = instance;
+    }
+
+    return clusterProcessedInstance(inst, false);
+  }
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   * @throws Exception if number of clusters could not be returned
+   * successfully
+   */
+  public int numberOfClusters() throws Exception {
+    return m_NumClusters;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions () {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+                                 "\tnumber of clusters.\n"
+                                 + "\t(default 2).", 
+                                 "N", 1, "-N <num>"));
+    result.addElement(new Option(
+                                 "\tDisplay std. deviations for centroids.\n", 
+                                 "V", 0, "-V"));
+    result.addElement(new Option(
+                                 "\tReplace missing values with mean/mode.\n", 
+                                 "M", 0, "-M"));
+
+    result.add(new Option(
+                          "\tDistance function to use.\n"
+                          + "\t(default: weka.core.EuclideanDistance)",
+                          "A", 1,"-A <classname and options>"));
+		
+    result.add(new Option(
+                          "\tMaximum number of iterations.\n",
+                          "I",1,"-I <num>"));
+
+    result.addElement(new Option(
+                                 "\tPreserve order of instances.\n", 
+                                 "O", 0, "-O"));
+		
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    return  result.elements();
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numClustersTipText() {
+    return "set number of clusters";
+  }
+
+  /**
+   * set the number of clusters to generate
+   *
+   * @param n the number of clusters to generate
+   * @throws Exception if number of clusters is negative
+   */
+  public void setNumClusters(int n) throws Exception {
+    if (n <= 0) {
+      throw new Exception("Number of clusters must be > 0");
+    }
+    m_NumClusters = n;
+  }
+
+  /**
+   * gets the number of clusters to generate
+   *
+   * @return the number of clusters to generate
+   */
+  public int getNumClusters() {
+    return m_NumClusters;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+    return "set maximum number of iterations";
+  }
+
+  /**
+   * set the maximum number of iterations to be executed
+   *
+   * @param n the maximum number of iterations
+   * @throws Exception if maximum number of iteration is smaller than 1
+   */
+  public void setMaxIterations(int n) throws Exception {
+    if (n <= 0) {
+      throw new Exception("Maximum number of iterations must be > 0");
+    }
+    m_MaxIterations = n;
+  }
+
+  /**
+   * gets the number of maximum iterations to be executed
+   *
+   * @return the number of clusters to generate
+   */
+  public int getMaxIterations() {
+    return m_MaxIterations;
+  }
+	
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String displayStdDevsTipText() {
+    return "Display std deviations of numeric attributes "
+      + "and counts of nominal attributes.";
+  }
+
+  /**
+   * Sets whether standard deviations and nominal count
+   * Should be displayed in the clustering output
+   *
+   * @param stdD true if std. devs and counts should be 
+   * displayed
+   */
+  public void setDisplayStdDevs(boolean stdD) {
+    m_displayStdDevs = stdD;
+  }
+
+  /**
+   * Gets whether standard deviations and nominal count
+   * Should be displayed in the clustering output
+   *
+   * @return true if std. devs and counts should be 
+   * displayed
+   */
+  public boolean getDisplayStdDevs() {
+    return m_displayStdDevs;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String dontReplaceMissingValuesTipText() {
+    return "Replace missing values globally with mean/mode.";
+  }
+
+  /**
+   * Sets whether missing values are to be replaced
+   *
+   * @param r true if missing values are to be
+   * replaced
+   */
+  public void setDontReplaceMissingValues(boolean r) {
+    m_dontReplaceMissing = r;
+  }
+
+  /**
+   * Gets whether missing values are to be replaced
+   *
+   * @return true if missing values are to be
+   * replaced
+   */
+  public boolean getDontReplaceMissingValues() {
+    return m_dontReplaceMissing;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String distanceFunctionTipText() {
+    return "The distance function to use for instances comparison " +
+      "(default: weka.core.EuclideanDistance). ";
+  }
+
+  /**
+   * returns the distance function currently in use.
+   * 
+   * @return the distance function
+   */
+  public DistanceFunction getDistanceFunction() {
+    return m_DistanceFunction;
+  }
+
+  /**
+   * sets the distance function to use for instance comparison.
+   * 
+   * @param df the new distance function to use
+   * @throws Exception if instances cannot be processed
+   */
+  public void setDistanceFunction(DistanceFunction df) throws Exception {
+    if(!(df instanceof EuclideanDistance) && 
+       !(df instanceof ManhattanDistance))
+      {
+        throw new Exception("SimpleKMeans currently only supports the Euclidean and Manhattan distances.");
+      }
+    m_DistanceFunction = df;
+  }	
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String preserveInstancesOrderTipText() {
+    return "Preserve order of instances.";
+  }
+
+  /**
+   * Sets whether order of instances must be preserved
+   *
+   * @param r true if missing values are to be
+   * replaced
+   */
+  public void setPreserveInstancesOrder(boolean r) {
+    m_PreserveOrder = r;
+  }
+
+  /**
+   * Gets whether order of instances must be preserved
+   *
+   * @return true if missing values are to be
+   * replaced
+   */
+  public boolean getPreserveInstancesOrder() {
+    return m_PreserveOrder;
+  }
+	
+	
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -V
+   *  Display std. deviations for centroids.
+   * </pre>
+   * 
+   * <pre> -M
+   *  Replace missing values with mean/mode.
+   * </pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   * <pre> -A &lt;classname and options&gt;
+   *  Distance function to be used for instance comparison
+   *  (default weka.core.EuclidianDistance)</pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  Maximum number of iterations. </pre>
+   *  
+   * <pre> -O
+   *  Preserve order of instances.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+
+    m_displayStdDevs = Utils.getFlag("V", options);
+    m_dontReplaceMissing = Utils.getFlag("M", options);
+
+    String optionString = Utils.getOption('N', options);
+
+    if (optionString.length() != 0) {
+      setNumClusters(Integer.parseInt(optionString));
+    }
+    
+    optionString = Utils.getOption("I", options);
+    if (optionString.length() != 0) {
+      setMaxIterations(Integer.parseInt(optionString));
+    }
+		
+    String distFunctionClass = Utils.getOption('A', options);
+    if(distFunctionClass.length() != 0) {
+      String distFunctionClassSpec[] = Utils.splitOptions(distFunctionClass);
+      if(distFunctionClassSpec.length == 0) { 
+        throw new Exception("Invalid DistanceFunction specification string."); 
+      }
+      String className = distFunctionClassSpec[0];
+      distFunctionClassSpec[0] = "";
+
+      setDistanceFunction( (DistanceFunction)
+                           Utils.forName( DistanceFunction.class, 
+                                          className, distFunctionClassSpec) );
+    }
+    else {
+      setDistanceFunction(new EuclideanDistance());
+    }
+		
+    m_PreserveOrder = Utils.getFlag("O", options);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of SimpleKMeans
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    if (m_displayStdDevs) {
+      result.add("-V");
+    }
+
+    if (m_dontReplaceMissing) {
+      result.add("-M");
+    }
+
+    result.add("-N");
+    result.add("" + getNumClusters());
+
+    result.add("-A");
+    result.add((m_DistanceFunction.getClass().getName() + " " +
+                Utils.joinOptions(m_DistanceFunction.getOptions())).trim());
+		
+    result.add("-I");
+    result.add(""+ getMaxIterations());
+
+    if(m_PreserveOrder){
+      result.add("-O");
+    }
+		
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * return a string describing this clusterer
+   *
+   * @return a description of the clusterer as a string
+   */
+  public String toString() {
+    if (m_ClusterCentroids == null) {
+      return "No clusterer built yet!";
+    }
+
+    int maxWidth = 0;
+    int maxAttWidth = 0;
+    boolean containsNumeric = false;
+    for (int i = 0; i < m_NumClusters; i++) {
+      for (int j = 0 ;j < m_ClusterCentroids.numAttributes(); j++) {
+        if (m_ClusterCentroids.attribute(j).name().length() > maxAttWidth) {
+          maxAttWidth = m_ClusterCentroids.attribute(j).name().length();
+        }
+	if (m_ClusterCentroids.attribute(j).isNumeric()) {
+          containsNumeric = true;
+	  double width = Math.log(Math.abs(m_ClusterCentroids.instance(i).value(j))) /
+	    Math.log(10.0);
+          //          System.err.println(m_ClusterCentroids.instance(i).value(j)+" "+width);
+          if (width < 0) {
+            width = 1;
+          }
+          // decimal + # decimal places + 1
+	  width += 6.0;
+	  if ((int)width > maxWidth) {
+	    maxWidth = (int)width;
+	  }
+	}
+      }
+    }
+
+    for (int i = 0; i < m_ClusterCentroids.numAttributes(); i++) {
+      if (m_ClusterCentroids.attribute(i).isNominal()) {
+        Attribute a = m_ClusterCentroids.attribute(i);
+        for (int j = 0; j < m_ClusterCentroids.numInstances(); j++) {
+          String val = a.value((int)m_ClusterCentroids.instance(j).value(i));
+          if (val.length() > maxWidth) {
+            maxWidth = val.length();
+          }
+        }
+        for (int j = 0; j < a.numValues(); j++) {
+          String val = a.value(j) + " ";
+          if (val.length() > maxAttWidth) {
+            maxAttWidth = val.length();
+          }
+        }
+      }
+    }
+
+    if (m_displayStdDevs) {
+      // check for maximum width of maximum frequency count
+      for (int i = 0; i < m_ClusterCentroids.numAttributes(); i++) {
+        if (m_ClusterCentroids.attribute(i).isNominal()) {
+          int maxV = Utils.maxIndex(m_FullNominalCounts[i]);
+          /*          int percent = (int)((double)m_FullNominalCounts[i][maxV] /
+                      Utils.sum(m_ClusterSizes) * 100.0); */
+          int percent = 6; // max percent width (100%)
+          String nomV = "" + m_FullNominalCounts[i][maxV];
+          //            + " (" + percent + "%)";
+          if (nomV.length() + percent > maxWidth) {
+            maxWidth = nomV.length() + 1;
+          }
+        }
+      }
+    }
+
+    // check for size of cluster sizes
+    for (int i = 0; i < m_ClusterSizes.length; i++) {
+      String size = "(" + m_ClusterSizes[i] + ")";
+      if (size.length() > maxWidth) {
+        maxWidth = size.length();
+      }
+    }
+    
+    if (m_displayStdDevs && maxAttWidth < "missing".length()) {
+      maxAttWidth = "missing".length();
+    }
+    
+    String plusMinus = "+/-";
+    maxAttWidth += 2;
+    if (m_displayStdDevs && containsNumeric) {
+      maxWidth += plusMinus.length();
+    }
+    if (maxAttWidth < "Attribute".length() + 2) {
+      maxAttWidth = "Attribute".length() + 2;
+    }
+
+    if (maxWidth < "Full Data".length()) {
+      maxWidth = "Full Data".length() + 1;
+    }
+
+    if (maxWidth < "missing".length()) {
+      maxWidth = "missing".length() + 1;
+    }
+
+
+    
+    StringBuffer temp = new StringBuffer();
+    //    String naString = "N/A";
+
+    
+    /*    for (int i = 0; i < maxWidth+2; i++) {
+          naString += " ";
+          } */
+    temp.append("\nkMeans\n======\n");
+    temp.append("\nNumber of iterations: " + m_Iterations+"\n");
+		
+    if(m_DistanceFunction instanceof EuclideanDistance){
+      temp.append("Within cluster sum of squared errors: " + Utils.sum(m_squaredErrors));
+    }else{
+      temp.append("Sum of within cluster distances: " + Utils.sum(m_squaredErrors));
+    }
+		
+		
+    if (!m_dontReplaceMissing) {
+      temp.append("\nMissing values globally replaced with mean/mode");
+    }
+
+    temp.append("\n\nCluster centroids:\n");
+    temp.append(pad("Cluster#", " ", (maxAttWidth + (maxWidth * 2 + 2)) - "Cluster#".length(), true));
+
+    temp.append("\n");
+    temp.append(pad("Attribute", " ", maxAttWidth - "Attribute".length(), false));
+
+    
+    temp.append(pad("Full Data", " ", maxWidth + 1 - "Full Data".length(), true));
+
+    // cluster numbers
+    for (int i = 0; i < m_NumClusters; i++) {
+      String clustNum = "" + i;
+      temp.append(pad(clustNum, " ", maxWidth + 1 - clustNum.length(), true));
+    }
+    temp.append("\n");
+
+    // cluster sizes
+    String cSize = "(" + Utils.sum(m_ClusterSizes) + ")";
+    temp.append(pad(cSize, " ", maxAttWidth + maxWidth + 1 - cSize.length(), true));
+    for (int i = 0; i < m_NumClusters; i++) {
+      cSize = "(" + m_ClusterSizes[i] + ")";
+      temp.append(pad(cSize, " ",maxWidth + 1 - cSize.length(), true));
+    }
+    temp.append("\n");
+
+    temp.append(pad("", "=", maxAttWidth + 
+                    (maxWidth * (m_ClusterCentroids.numInstances()+1) 
+                     + m_ClusterCentroids.numInstances() + 1), true));
+    temp.append("\n");
+
+    for (int i = 0; i < m_ClusterCentroids.numAttributes(); i++) {
+      String attName = m_ClusterCentroids.attribute(i).name();
+      temp.append(attName);
+      for (int j = 0; j < maxAttWidth - attName.length(); j++) {
+        temp.append(" ");
+      }
+
+      String strVal;
+      String valMeanMode;
+      // full data
+      if (m_ClusterCentroids.attribute(i).isNominal()) {
+        if (m_FullMeansOrMediansOrModes[i] == -1) { // missing
+          valMeanMode = pad("missing", " ", maxWidth + 1 - "missing".length(), true);
+        } else {
+          valMeanMode = 
+            pad((strVal = m_ClusterCentroids.attribute(i).value((int)m_FullMeansOrMediansOrModes[i])),
+                " ", maxWidth + 1 - strVal.length(), true);
+        }
+      } else {
+        if (Double.isNaN(m_FullMeansOrMediansOrModes[i])) {
+          valMeanMode = pad("missing", " ", maxWidth + 1 - "missing".length(), true);
+        } else {
+          valMeanMode =  pad((strVal = Utils.doubleToString(m_FullMeansOrMediansOrModes[i],
+                                                            maxWidth,4).trim()), 
+                             " ", maxWidth + 1 - strVal.length(), true);
+        }
+      }
+      temp.append(valMeanMode);
+
+      for (int j = 0; j < m_NumClusters; j++) {
+        if (m_ClusterCentroids.attribute(i).isNominal()) {
+          if (m_ClusterCentroids.instance(j).isMissing(i)) {
+            valMeanMode = pad("missing", " ", maxWidth + 1 - "missing".length(), true);
+          } else {
+            valMeanMode = 
+              pad((strVal = m_ClusterCentroids.attribute(i).value((int)m_ClusterCentroids.instance(j).value(i))),
+                  " ", maxWidth + 1 - strVal.length(), true);
+          }
+        } else {
+          if (m_ClusterCentroids.instance(j).isMissing(i)) {
+            valMeanMode = pad("missing", " ", maxWidth + 1 - "missing".length(), true);
+          } else {
+            valMeanMode = pad((strVal = Utils.doubleToString(m_ClusterCentroids.instance(j).value(i),
+                                                             maxWidth,4).trim()), 
+                              " ", maxWidth + 1 - strVal.length(), true);
+          }
+        }
+        temp.append(valMeanMode);
+      }
+      temp.append("\n");
+
+      if (m_displayStdDevs) {
+        // Std devs/max nominal
+        String stdDevVal = "";
+
+        if (m_ClusterCentroids.attribute(i).isNominal()) {
+          // Do the values of the nominal attribute
+          Attribute a = m_ClusterCentroids.attribute(i);
+          for (int j = 0; j < a.numValues(); j++) {
+            // full data
+            String val = "  " + a.value(j);
+            temp.append(pad(val, " ", maxAttWidth + 1 - val.length(), false));
+            int count = m_FullNominalCounts[i][j];
+            int percent = (int)((double)m_FullNominalCounts[i][j] /
+                                Utils.sum(m_ClusterSizes) * 100.0);
+            String percentS = "" + percent + "%)";
+            percentS = pad(percentS, " ", 5 - percentS.length(), true);
+            stdDevVal = "" + count + " (" + percentS;
+            stdDevVal = 
+              pad(stdDevVal, " ", maxWidth + 1 - stdDevVal.length(), true);
+            temp.append(stdDevVal);
+
+            // Clusters
+            for (int k = 0; k < m_NumClusters; k++) {
+              count = m_ClusterNominalCounts[k][i][j];
+              percent = (int)((double)m_ClusterNominalCounts[k][i][j] /
+                              m_ClusterSizes[k] * 100.0);
+              percentS = "" + percent + "%)";
+              percentS = pad(percentS, " ", 5 - percentS.length(), true);
+              stdDevVal = "" + count + " (" + percentS;
+              stdDevVal = 
+                pad(stdDevVal, " ", maxWidth + 1 - stdDevVal.length(), true);
+              temp.append(stdDevVal);
+            }
+            temp.append("\n");
+          }
+          // missing (if any)
+          if (m_FullMissingCounts[i] > 0) {
+            // Full data
+            temp.append(pad("  missing", " ", maxAttWidth + 1 - "  missing".length(), false));
+            int count = m_FullMissingCounts[i];
+            int percent = (int)((double)m_FullMissingCounts[i] /
+                                Utils.sum(m_ClusterSizes) * 100.0);
+            String percentS = "" + percent + "%)";
+            percentS = pad(percentS, " ", 5 - percentS.length(), true);
+            stdDevVal = "" + count + " (" + percentS;
+            stdDevVal = 
+              pad(stdDevVal, " ", maxWidth + 1 - stdDevVal.length(), true);
+            temp.append(stdDevVal);
+           
+            // Clusters
+            for (int k = 0; k < m_NumClusters; k++) {
+              count = m_ClusterMissingCounts[k][i];
+              percent = (int)((double)m_ClusterMissingCounts[k][i] /
+                              m_ClusterSizes[k] * 100.0);
+              percentS = "" + percent + "%)";
+              percentS = pad(percentS, " ", 5 - percentS.length(), true);
+              stdDevVal = "" + count + " (" + percentS;
+              stdDevVal = 
+                pad(stdDevVal, " ", maxWidth + 1 - stdDevVal.length(), true);
+              temp.append(stdDevVal);
+            }
+
+            temp.append("\n");
+          }
+
+          temp.append("\n");
+        } else {
+          // Full data
+          if (Double.isNaN(m_FullMeansOrMediansOrModes[i])) {
+            stdDevVal = pad("--", " ", maxAttWidth + maxWidth + 1 - 2, true);
+          } else {
+            stdDevVal = pad((strVal = plusMinus 
+                             + Utils.doubleToString(m_FullStdDevs[i],
+                                                    maxWidth,4).trim()), 
+                            " ", maxWidth + maxAttWidth + 1 - strVal.length(), true);
+          }
+          temp.append(stdDevVal);
+
+          // Clusters
+          for (int j = 0; j < m_NumClusters; j++) {
+            if (m_ClusterCentroids.instance(j).isMissing(i)) {
+              stdDevVal = pad("--", " ", maxWidth + 1 - 2, true);
+            } else {
+              stdDevVal = 
+                pad((strVal = plusMinus 
+                     + Utils.doubleToString(m_ClusterStdDevs.instance(j).value(i),
+                                            maxWidth,4).trim()), 
+                    " ", maxWidth + 1 - strVal.length(), true);
+            }
+            temp.append(stdDevVal);
+          }
+          temp.append("\n\n");
+        }
+      }
+    }
+
+    temp.append("\n\n");
+    return temp.toString();
+  }
+
+  private String pad(String source, String padChar, 
+                     int length, boolean leftPad) {
+    StringBuffer temp = new StringBuffer();
+
+    if (leftPad) {
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+      temp.append(source);
+    } else {
+      temp.append(source);
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+    }
+    return temp.toString();
+  }
+
+  /**
+   * Gets the the cluster centroids
+   * 
+   * @return		the cluster centroids
+   */
+  public Instances getClusterCentroids() {
+    return m_ClusterCentroids;
+  }
+
+  /**
+   * Gets the standard deviations of the numeric attributes in each cluster
+   * 
+   * @return		the standard deviations of the numeric attributes 
+   * 			in each cluster
+   */
+  public Instances getClusterStandardDevs() {
+    return m_ClusterStdDevs;
+  }
+
+  /**
+   * Returns for each cluster the frequency counts for the values of each 
+   * nominal attribute
+   * 
+   * @return		the counts
+   */
+  public int [][][] getClusterNominalCounts() {
+    return m_ClusterNominalCounts;
+  }
+
+  /**
+   * Gets the squared error for all clusters
+   * 
+   * @return		the squared error
+   */
+  public double getSquaredError() {
+    return Utils.sum(m_squaredErrors);
+  }
+
+  /**
+   * Gets the number of instances in each cluster
+   * 
+   * @return		The number of instances in each cluster
+   */
+  public int [] getClusterSizes() {
+    return m_ClusterSizes;
+  }
+  
+  /**
+   * Gets the assignments for each instance
+   * @return Array of indexes of the centroid assigned to each instance
+   * @throws Exception if order of instances wasn't preserved or no assignments were made
+   */
+  public int [] getAssignments() throws Exception{
+    if(!m_PreserveOrder){
+      throw new Exception("The assignments are only available when order of instances is preserved (-O)");
+    }
+    if(m_Assignments == null){
+      throw new Exception("No assignments made.");
+    }
+    return m_Assignments;
+  }
+	
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain the following arguments: <p>
+   * -t training file [-N number of clusters]
+   */
+  public static void main (String[] argv) {
+    runClusterer(new SimpleKMeans(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/clusterers/SingleClustererEnhancer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/SingleClustererEnhancer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/SingleClustererEnhancer.java	(revision 29)
@@ -0,0 +1,212 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SingleClustererEnhancer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Meta-clusterer for enhancing a base clusterer.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public abstract class SingleClustererEnhancer
+  extends AbstractClusterer
+  implements OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4893928362926428671L;
+
+  /** the clusterer */
+  protected Clusterer m_Clusterer = new SimpleKMeans();
+
+  /**
+   * String describing default clusterer.
+   * 
+   * @return 		the default clusterer classname
+   */
+  protected String defaultClustererString() {
+    return SimpleKMeans.class.getName();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tFull name of base clusterer.\n"
+	+ "\t(default: " + defaultClustererString() +")",
+	"W", 1, "-W"));
+
+    if (m_Clusterer instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to clusterer "
+	  + m_Clusterer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler) m_Clusterer).listOptions();
+      while (enu.hasMoreElements()) {
+	result.addElement(enu.nextElement());
+      }
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) { 
+      // This is just to set the classifier in case the option 
+      // parsing fails.
+      setClusterer(AbstractClusterer.forName(tmpStr, null));
+      setClusterer(AbstractClusterer.forName(tmpStr, Utils.partitionOptions(options)));
+    } 
+    else {
+      // This is just to set the classifier in case the option 
+      // parsing fails.
+      setClusterer(AbstractClusterer.forName(defaultClustererString(), null));
+      setClusterer(AbstractClusterer.forName(defaultClustererString(), Utils.partitionOptions(options)));
+    }
+  }
+
+  /**
+   * Gets the current settings of the clusterer.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    String[]	options;
+    int		i;
+    
+    result = new Vector();
+
+    result.add("-W");
+    result.add(getClusterer().getClass().getName());
+    
+    if (getClusterer() instanceof OptionHandler) {
+      result.add("--");
+      options = ((OptionHandler) getClusterer()).getOptions();
+      for (i = 0; i < options.length; i++)
+	result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String clustererTipText() {
+    return "The base clusterer to be used.";
+  }
+
+  /**
+   * Set the base clusterer.
+   *
+   * @param value 	the classifier to use.
+   */
+  public void setClusterer(Clusterer value) {
+    m_Clusterer = value;
+  }
+
+  /**
+   * Get the clusterer used as the base clusterer.
+   *
+   * @return 		the base clusterer
+   */
+  public Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+  
+  /**
+   * Gets the clusterer specification string, which contains the class name of
+   * the clusterer and any options to the clusterer
+   *
+   * @return 		the clusterer string
+   */
+  protected String getClustererSpec() {
+    String	result;
+    Clusterer 	clusterer;
+    
+    clusterer = getClusterer();
+    result    = clusterer.getClass().getName();
+    
+    if (clusterer instanceof OptionHandler)
+      result += " " + Utils.joinOptions(((OptionHandler) clusterer).getOptions());
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return		the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getClusterer() == null)
+      result = super.getCapabilities();
+    else
+      result = getClusterer().getCapabilities();
+    
+    // set dependencies
+    for (Capability cap: Capability.values())
+      result.enableDependency(cap);
+    
+    return result;
+  }
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return 		the number of clusters generated for a training dataset.
+   * @throws Exception 	if number of clusters could not be returned
+   * 			successfully
+   */
+  public int numberOfClusters() throws Exception {
+    return m_Clusterer.numberOfClusters();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/UpdateableClusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/UpdateableClusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/UpdateableClusterer.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * UpdateableClusterer.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.core.Instance;
+
+/**
+ * Interface to incremental cluster models that can learn using one instance 
+ * at a time.
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public interface UpdateableClusterer {
+
+  /**
+   * Adds an instance to the clusterer.
+   *
+   * @param newInstance the instance to be added
+   * @throws Exception 	if something goes wrong
+   */
+  public void updateClusterer(Instance newInstance) throws Exception;
+
+  /**
+   * Singals the end of the updating.
+   */
+  public void updateFinished();
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/XMeans.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/XMeans.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/XMeans.java	(revision 29)
@@ -0,0 +1,2410 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    XMeans.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.AlgVector;
+import weka.core.Capabilities;
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.neighboursearch.KDTree;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Cluster data using the X-means algorithm.<br/>
+ * <br/>
+ * X-Means is K-Means extended by an Improve-Structure part In this part of the algorithm the centers are attempted to be split in its region. The decision between the children of each center and itself is done comparing the BIC-values of the two structures.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Dan Pelleg, Andrew W. Moore: X-means: Extending K-means with Efficient Estimation of the Number of Clusters. In: Seventeenth International Conference on Machine Learning, 727-734, 2000.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Pelleg2000,
+ *    author = {Dan Pelleg and Andrew W. Moore},
+ *    booktitle = {Seventeenth International Conference on Machine Learning},
+ *    pages = {727-734},
+ *    publisher = {Morgan Kaufmann},
+ *    title = {X-means: Extending K-means with Efficient Estimation of the Number of Clusters},
+ *    year = {2000}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  maximum number of overall iterations
+ *  (default 1).</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  maximum number of iterations in the kMeans loop in
+ *  the Improve-Parameter part 
+ *  (default 1000).</pre>
+ * 
+ * <pre> -J &lt;num&gt;
+ *  maximum number of iterations in the kMeans loop
+ *  for the splitted centroids in the Improve-Structure part 
+ *  (default 1000).</pre>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  minimum number of clusters
+ *  (default 2).</pre>
+ * 
+ * <pre> -H &lt;num&gt;
+ *  maximum number of clusters
+ *  (default 4).</pre>
+ * 
+ * <pre> -B &lt;value&gt;
+ *  distance value for binary attributes
+ *  (default 1.0).</pre>
+ * 
+ * <pre> -use-kdtree
+ *  Uses the KDTree internally
+ *  (default no).</pre>
+ * 
+ * <pre> -K &lt;KDTree class specification&gt;
+ *  Full class name of KDTree class to use, followed
+ *  by scheme options.
+ *  eg: "weka.core.neighboursearch.kdtrees.KDTree -P"
+ *  (default no KDTree class used).</pre>
+ * 
+ * <pre> -C &lt;value&gt;
+ *  cutoff factor, takes the given percentage of the splitted 
+ *  centroids if none of the children win
+ *  (default 0.0).</pre>
+ * 
+ * <pre> -D &lt;distance function class specification&gt;
+ *  Full class name of Distance function class to use, followed
+ *  by scheme options.
+ *  (default weka.core.EuclideanDistance).</pre>
+ * 
+ * <pre> -N &lt;file name&gt;
+ *  file to read starting centers from (ARFF format).</pre>
+ * 
+ * <pre> -O &lt;file name&gt;
+ *  file to write centers to (ARFF format).</pre>
+ * 
+ * <pre> -U &lt;int&gt;
+ *  The debug level.
+ *  (default 0)</pre>
+ * 
+ * <pre> -Y &lt;file name&gt;
+ *  The debug vectors file.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 10)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5488 $
+ * @see RandomizableClusterer
+ */
+public class XMeans 
+  extends RandomizableClusterer
+  implements TechnicalInformationHandler {
+
+  /*
+   * major TODOS:
+   *
+   * make BIC-Score replaceable by other scores
+   */
+
+  /** for serialization. */
+  private static final long serialVersionUID = -7941793078404132616L;
+  
+  /** training instances. */
+  protected Instances m_Instances = null;
+
+  /** model information, should increase readability. */
+  protected Instances m_Model = null;
+  
+  /** replace missing values in training instances. */
+  protected ReplaceMissingValues m_ReplaceMissingFilter;
+
+  /**
+   * Distance value between true and false of binary attributes and 
+   * "same" and "different" of nominal attributes (default = 1.0).
+   */
+  protected double m_BinValue = 1.0;
+
+  /** BIC-Score of the current model. */
+  protected double m_Bic = Double.MIN_VALUE;
+
+  /** Distortion.  */
+  protected double[] m_Mle = null;
+
+  /** maximum overall iterations. */
+  protected int m_MaxIterations = 1;
+
+  /**
+   * maximum iterations to perform Kmeans part 
+   * if negative, iterations are not checked.
+   */
+  protected int m_MaxKMeans = 1000;
+
+  /** see above, but for kMeans of splitted clusters.
+   */
+  protected int m_MaxKMeansForChildren = 1000;
+
+  /** The actual number of clusters. */
+  protected int m_NumClusters = 2;
+
+  /** min number of clusters to generate. */
+  protected int m_MinNumClusters = 2;
+
+  /** max number of clusters to generate. */
+  protected int m_MaxNumClusters = 4;
+
+  /** the distance function used. */
+  protected DistanceFunction m_DistanceF = new EuclideanDistance();
+
+  /** cluster centers. */
+  protected Instances m_ClusterCenters;
+
+  /** file name of the output file for the cluster centers. */
+  protected File m_InputCenterFile = new File(System.getProperty("user.dir"));
+
+  /* --> DebugVectors - USED FOR DEBUGGING */
+  /** input file for the random vectors --> USED FOR DEBUGGING. */
+  protected Reader m_DebugVectorsInput = null;
+  /** the index for the current debug vector. */
+  protected int m_DebugVectorsIndex = 0;
+  /** all the debug vectors. */
+  protected Instances m_DebugVectors = null;
+
+  /** file name of the input file for the random vectors. */
+  protected File m_DebugVectorsFile = new File(System.getProperty("user.dir"));
+
+  /** input file for the cluster centers. */
+  protected Reader m_CenterInput = null;
+    
+  /** file name of the output file for the cluster centers. */
+  protected File m_OutputCenterFile = new File(System.getProperty("user.dir"));
+  
+  /** output file for the cluster centers. */
+  protected PrintWriter m_CenterOutput = null;
+    
+  /**
+   * temporary variable holding cluster assignments while iterating.
+   */
+  protected int[] m_ClusterAssignments;
+
+  /** cutoff factor - percentage of splits done in Improve-Structure part
+     only relevant, if all children lost. */ 
+  protected double m_CutOffFactor = 0.5;
+
+  /** Index in ranges for LOW. */
+  public static int R_LOW = 0;
+  /** Index in ranges for HIGH. */
+  public static int R_HIGH = 1;
+  /** Index in ranges for WIDTH. */
+  public static int R_WIDTH = 2;
+
+  /**
+   * KDTrees class if KDTrees are used.
+   */
+  protected KDTree m_KDTree = new KDTree();
+  
+  /** whether to use the KDTree (the KDTree is only initialized to be 
+   * configurable from the GUI). */
+  protected boolean m_UseKDTree = false;
+
+  /** counts iterations done in main loop. */
+  protected int m_IterationCount = 0;
+
+  /** counter to say how often kMeans was stopped by loop counter. */
+  protected int m_KMeansStopped = 0;
+
+  /** Number of splits prepared. */
+  protected int m_NumSplits = 0;
+
+  /** Number of splits accepted (including cutoff factor decisions). */
+  protected int m_NumSplitsDone = 0;
+
+  /** Number of splits accepted just because of cutoff factor. */
+  protected int m_NumSplitsStillDone = 0;
+
+  /**
+   * level of debug output, 0 is no output.
+   */
+  protected int m_DebugLevel = 0;
+  
+  /** print the centers. */
+  public static int D_PRINTCENTERS = 1;
+  /** follows the splitting of the centers. */
+  public static int D_FOLLOWSPLIT = 2;
+  /** have a closer look at converge children. */
+  public static int D_CONVCHCLOSER = 3;
+  /** check on random vectors. */
+  public static int D_RANDOMVECTOR = 4;
+  /** check on kdtree. */
+  public static int D_KDTREE = 5;
+  /** follow iterations. */
+  public static int D_ITERCOUNT = 6;
+  /** functions were maybe misused.  */
+  public static int D_METH_MISUSE = 80; 
+  /** for current debug.  */
+  public static int D_CURR = 88;
+  /** general debugging. */
+  public static int D_GENERAL = 99;
+
+  /** Flag: I'm debugging. */
+  public boolean m_CurrDebugFlag = true;
+
+  /**
+   * the default constructor.
+   */
+  public XMeans() {
+    super();
+    
+    m_SeedDefault = 10;
+    setSeed(m_SeedDefault);
+  }
+  
+  /**
+   * Returns a string describing this clusterer.
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Cluster data using the X-means algorithm.\n\n" 
+      + "X-Means is K-Means extended by an Improve-Structure part In this "
+      + "part of the algorithm the centers are attempted to be split in "
+      + "its region. The decision between the children of each center and "
+      + "itself is done comparing the BIC-values of the two structures.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Dan Pelleg and Andrew W. Moore");
+    result.setValue(Field.TITLE, "X-means: Extending K-means with Efficient Estimation of the Number of Clusters");
+    result.setValue(Field.BOOKTITLE, "Seventeenth International Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.PAGES, "727-734");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann");
+    
+    return result;
+  }
+
+  /**
+   * Returns default capabilities of the clusterer.
+   *
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    return result;
+  }
+ 
+  /**
+   * Generates the X-Means clusterer. 
+   *
+   * @param data set of instances serving as training data 
+   * @throws Exception if the clusterer has not been 
+   * generated successfully
+   */
+  public void buildClusterer(Instances data) throws Exception {
+
+    // can clusterer handle the data?
+    getCapabilities().testWithFail(data);
+    
+    if (m_MinNumClusters > m_MaxNumClusters) {
+      throw new Exception("XMeans: min number of clusters "
+          + "can't be greater than max number of clusters!");
+    }
+
+    m_NumSplits = 0;
+    m_NumSplitsDone = 0;
+    m_NumSplitsStillDone = 0;
+
+    // replace missing values
+    m_ReplaceMissingFilter = new ReplaceMissingValues();
+    m_ReplaceMissingFilter.setInputFormat(data);
+    m_Instances = Filter.useFilter(data, m_ReplaceMissingFilter);
+    
+    // initialize random function
+    Random random0 = new Random(m_Seed);
+
+    // num of clusters to start with
+    m_NumClusters =  m_MinNumClusters;
+
+    // set distance function to default
+    if (m_DistanceF == null) {
+      m_DistanceF = new EuclideanDistance();
+    }
+
+    m_DistanceF.setInstances(m_Instances);
+    checkInstances();
+
+    if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile())
+      initDebugVectorsInput();
+
+    // make list of indexes for m_Instances
+    int[] allInstList = new int[m_Instances.numInstances()]; 
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      allInstList[i] = i;
+    }
+    
+    // set model used (just for convenience)
+    m_Model = new Instances(m_Instances, 0);
+
+    // produce the starting centers
+    if (m_CenterInput != null) {
+      // read centers from file
+      m_ClusterCenters = new Instances(m_CenterInput);
+      m_NumClusters = m_ClusterCenters.numInstances();
+    }
+    else
+      // makes the first centers randomly
+      m_ClusterCenters = makeCentersRandomly(random0,
+					     m_Instances, m_NumClusters);
+    PFD(D_FOLLOWSPLIT, "\n*** Starting centers ");
+    for (int k = 0; k < m_ClusterCenters.numInstances(); k++) {
+      PFD(D_FOLLOWSPLIT, "Center " + k + ": " + m_ClusterCenters.instance(k));
+    }
+
+    PrCentersFD(D_PRINTCENTERS);
+
+    boolean finished = false;
+    Instances children; 
+
+    // builds up a KDTree
+    if (m_UseKDTree)
+      m_KDTree.setInstances(m_Instances);
+  
+    // loop counter of main loop
+    m_IterationCount = 0;
+
+    /**
+     * "finished" does get true as soon as:
+     * 1. number of clusters gets >= m_MaxClusters, 
+     * 2. in the last round, none of the centers have been split
+     * 
+     * if number of clusters is already >= m_MaxClusters 
+     * part 1 (= Improve-Params) is done at least once.
+     */
+    while (!finished &&
+           !stopIteration(m_IterationCount, m_MaxIterations)) {
+      
+      /* ====================================================================
+       * 1. Improve-Params                  
+       *    conventional K-means
+       */
+
+
+      PFD(D_FOLLOWSPLIT, "\nBeginning of main loop - centers:");
+      PrCentersFD(D_FOLLOWSPLIT);
+
+      PFD(D_ITERCOUNT, "\n*** 1. Improve-Params " + m_IterationCount + 
+	  ". time");
+      m_IterationCount++;
+
+      // prepare to converge
+      boolean converged = false;
+
+      // initialize assignments to -1
+      m_ClusterAssignments = initAssignments(m_Instances.numInstances());
+      // stores a list of indexes of instances belonging to each center
+      int[][] instOfCent = new int[m_ClusterCenters.numInstances()][];
+
+      // KMeans loop counter
+      int kMeansIteration = 0;
+
+      // converge in conventional K-means ----------------------------------
+      PFD(D_FOLLOWSPLIT, "\nConverge in K-Means:");
+      while (!converged && 
+	     !stopKMeansIteration(kMeansIteration, m_MaxKMeans)) {
+	
+	kMeansIteration++;
+	converged = true;
+	
+        // assign instances to centers -------------------------------------
+        converged = assignToCenters(m_UseKDTree ? m_KDTree : null,
+				    m_ClusterCenters, 
+				    instOfCent,
+				    allInstList, 
+				    m_ClusterAssignments,
+				    kMeansIteration);
+	
+	PFD(D_FOLLOWSPLIT, "\nMain loop - Assign - centers:");
+	PrCentersFD(D_FOLLOWSPLIT);
+	// compute new centers = centers of mass of points
+        converged = recomputeCenters(m_ClusterCenters, // clusters
+				     instOfCent,       // their instances
+				     m_Model);         // model information
+      PFD(D_FOLLOWSPLIT, "\nMain loop - Recompute - centers:");
+      PrCentersFD(D_FOLLOWSPLIT);
+      }
+      PFD(D_FOLLOWSPLIT, "");
+      PFD(D_FOLLOWSPLIT, "End of Part: 1. Improve-Params - conventional K-means");
+
+      /** =====================================================================
+       * 2. Improve-Structur
+       */
+
+      // BIC before split distortioning the centres
+      m_Mle = distortion(instOfCent, m_ClusterCenters);
+      m_Bic = calculateBIC(instOfCent, m_ClusterCenters, m_Mle);
+      PFD(D_FOLLOWSPLIT, "m_Bic " + m_Bic);
+
+      int currNumCent = m_ClusterCenters.numInstances();
+      Instances splitCenters = new Instances(m_ClusterCenters, 
+					     currNumCent * 2);
+      
+      // store BIC values of parent and children
+      double[] pbic = new double [currNumCent];
+      double[] cbic = new double [currNumCent];
+            
+      // split each center
+      for (int i = 0; i < currNumCent 
+	   // this could help to optimize the algorithm
+	   //	     && currNumCent + numSplits <= m_MaxNumClusters
+           ; 
+	   i++) {
+	
+	PFD(D_FOLLOWSPLIT, "\nsplit center " + i +
+		      " " + m_ClusterCenters.instance(i));
+	Instance currCenter = m_ClusterCenters.instance(i);
+	int[] currInstList = instOfCent[i];
+	int currNumInst = instOfCent[i].length;
+	
+	// not enough instances; than continue with next
+	if (currNumInst <= 2) {
+	  pbic[i] = Double.MAX_VALUE;
+	  cbic[i] = 0.0;
+	  // add center itself as dummy
+	  splitCenters.add(currCenter);
+	  splitCenters.add(currCenter);
+	  continue;
+	}
+	
+	// split centers  ----------------------------------------------
+	double variance = m_Mle[i] / (double)currNumInst;
+	children = splitCenter(random0, currCenter, variance, m_Model);
+	
+	// initialize assignments to -1
+	int[] oneCentAssignments = initAssignments(currNumInst);
+	int[][] instOfChCent = new int [2][]; // todo maybe split didn't work
+	
+	// converge the children  --------------------------------------
+	converged = false;
+	int kMeansForChildrenIteration = 0;
+	PFD(D_FOLLOWSPLIT, "\nConverge, K-Means for children: " + i);
+	while (!converged && 
+          !stopKMeansIteration(kMeansForChildrenIteration, 
+			       m_MaxKMeansForChildren)) {
+	  kMeansForChildrenIteration++;
+	  
+	  converged =
+	    assignToCenters(children, instOfChCent,
+			    currInstList, oneCentAssignments);
+
+	  if (!converged) {       
+	    recomputeCentersFast(children, instOfChCent, m_Model);
+	  }
+	} 
+
+	// store new centers for later decision if they are taken
+	splitCenters.add(children.instance(0));
+	splitCenters.add(children.instance(1));
+
+	PFD(D_FOLLOWSPLIT, "\nconverged cildren ");
+	PFD(D_FOLLOWSPLIT, " " + children.instance(0));
+	PFD(D_FOLLOWSPLIT, " " + children.instance(1));
+
+	// compare parent and children model by their BIC-value
+	pbic[i] = calculateBIC(currInstList, currCenter,  m_Mle[i], m_Model);
+	double[] chMLE = distortion(instOfChCent, children);
+	cbic[i] = calculateBIC(instOfChCent, children, chMLE);
+
+      } // end of loop over clusters
+
+      // decide which one to split and make new list of cluster centers
+      Instances newClusterCenters = null;
+      newClusterCenters = newCentersAfterSplit(pbic, cbic, m_CutOffFactor,
+                                                 splitCenters);
+      /**
+       * Compare with before Improve-Structure
+       */
+      int newNumClusters = newClusterCenters.numInstances();
+      if (newNumClusters != m_NumClusters) {
+	
+	PFD(D_FOLLOWSPLIT, "Compare with non-split");
+
+	// initialize assignments to -1
+	int[] newClusterAssignments = 
+	  initAssignments(m_Instances.numInstances());
+	
+	// stores a list of indexes of instances belonging to each center
+	int[][] newInstOfCent = new int[newClusterCenters.numInstances()][];
+	
+	// assign instances to centers -------------------------------------
+	converged = assignToCenters(m_UseKDTree ? m_KDTree : null,
+				    newClusterCenters, 
+				    newInstOfCent,
+				    allInstList, 
+				    newClusterAssignments,
+				    m_IterationCount);
+	
+	double[] newMle = distortion(newInstOfCent, newClusterCenters);
+	double newBic = calculateBIC(newInstOfCent, newClusterCenters, newMle);
+	PFD(D_FOLLOWSPLIT, "newBic " + newBic);
+	if (newBic > m_Bic) {
+          PFD(D_FOLLOWSPLIT, "*** decide for new clusters");
+	  m_Bic = newBic;
+	  m_ClusterCenters = newClusterCenters;
+	  m_ClusterAssignments = newClusterAssignments;
+	} else {
+          PFD(D_FOLLOWSPLIT, "*** keep old clusters");
+        }
+      }
+
+      newNumClusters = m_ClusterCenters.numInstances();
+      // decide if finished: max num cluster reached 
+      // or last centers where not split at all 
+      if ((newNumClusters >= m_MaxNumClusters) 
+	  || (newNumClusters == m_NumClusters)) {
+	finished = true;
+      }
+      m_NumClusters = newNumClusters;
+    }
+  }
+
+  /**
+   * Checks for nominal attributes in the dataset.
+   * Class attribute is ignored.
+   * @param data the data to check
+   * @return false if no nominal attributes are present
+   */
+  public boolean checkForNominalAttributes(Instances data) {
+
+    int i = 0;
+    while (i < data.numAttributes()) {
+      if ((i != data.classIndex()) && data.attribute(i++).isNominal()) {
+	return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Set array of int, used to store assignments, to -1.
+   * @param ass integer array used for storing assignments
+   * @return integer array used for storing assignments
+   */
+  protected int[] initAssignments(int[] ass) {
+    for (int i = 0; i < ass.length; i++)
+      ass[i] = -1;
+    return ass;
+  }    
+ 
+  /**
+   * Creates and initializes integer array, used to store assignments.
+   * @param numInstances length of array used for assignments
+   * @return integer array used for storing assignments
+   */
+  protected int[] initAssignments(int numInstances) {
+    int[] ass = new int[numInstances];
+    for (int i = 0; i < numInstances; i++)
+      ass[i] = -1;
+    return ass;
+  }    
+  
+  /**
+   * Creates and initializes boolean array.
+   * @param len length of new array
+   * @return the new array
+   */
+  boolean[] initBoolArray(int len) {
+    boolean[] boolArray = new boolean [len];
+    for (int i = 0; i < len; i++) {
+      boolArray[i] = false;
+    }
+    return boolArray;
+  }
+
+  /**
+   * Returns new center list.
+   *
+   * The following steps 1. and 2. both take care that the number of centers
+   * does not exceed maxCenters.
+   *
+   * 1. Compare BIC values of parent and children and takes the one as
+   * new centers which do win (= BIC-value is smaller).
+   *
+   * 2. If in 1. none of the children are chosen 
+   *    && and cutoff factor is > 0
+   * cutoff factor is taken as the percentage of "best" centers that are
+   * still taken.
+   * @param pbic array of parents BIC-values
+   * @param cbic array of childrens BIC-values
+   * @param cutoffFactor cutoff factor 
+   * @param splitCenters all children 
+   * @return the new centers
+   */
+  protected Instances newCentersAfterSplit(double[] pbic, 
+					 double[] cbic,
+					 double cutoffFactor,
+					 Instances splitCenters) {
+
+    // store if split won
+    boolean splitPerCutoff = false;
+    boolean takeSomeAway = false;
+    boolean[] splitWon = initBoolArray(m_ClusterCenters.numInstances());
+    int numToSplit = 0;
+    Instances newCenters = null;
+    
+    // how many would be split, because the children have a better bic value
+    for (int i = 0; i < cbic.length; i++) {
+      if (cbic[i] > pbic[i]) {
+	// decide for splitting ----------------------------------------
+	splitWon[i] = true; numToSplit++;
+	PFD(D_FOLLOWSPLIT, "Center " + i + " decide for children");
+      }
+      else {
+	// decide for parents and finished stays true  -----------------
+	PFD(D_FOLLOWSPLIT, "Center " + i + " decide for parent");
+      }
+    }
+
+    // no splits yet so split per cutoff factor
+    if ((numToSplit == 0) && (cutoffFactor > 0)) {
+      splitPerCutoff = true;
+      
+      // how many to split per cutoff factor
+      numToSplit = (int) 
+        ((double) m_ClusterCenters.numInstances() * m_CutOffFactor); 
+    }
+
+    // prepare indexes of values in ascending order  
+    double[] diff = new double [m_NumClusters];
+    for (int j = 0; j < diff.length; j++) {
+      diff[j] = pbic[j] - cbic[j];
+    }    
+    int[] sortOrder = Utils.sort(diff);
+    
+    // check if maxNumClusters would be exceeded
+    int possibleToSplit = m_MaxNumClusters - m_NumClusters; 
+
+    if (possibleToSplit > numToSplit) {
+      // still enough possible, do the whole amount
+      possibleToSplit = numToSplit;
+    }
+    else
+      takeSomeAway = true;
+
+    // prepare for splitting the one that are supposed to be split
+    if (splitPerCutoff) {
+      for (int j = 0; (j < possibleToSplit) && (cbic[sortOrder[j]] > 0.0);
+	   j++) {
+	splitWon[sortOrder[j]] = true;
+      }
+      m_NumSplitsStillDone += possibleToSplit;
+    } 
+    else {
+      // take some splits away if max number of clusters would be exceeded
+      if (takeSomeAway) {
+	int count = 0;
+	int j = 0;
+	for (;j < splitWon.length && count < possibleToSplit; j++){
+	  if (splitWon[sortOrder[j]] == true) count++;
+	}
+	
+	while (j < splitWon.length) {
+	  splitWon[sortOrder[j]] = false;
+	  j++;
+	}
+      }
+    }
+   
+    // finally split
+    if (possibleToSplit > 0) 
+      newCenters = newCentersAfterSplit(splitWon, splitCenters);
+    else
+      newCenters = m_ClusterCenters;
+    return newCenters;
+  }
+
+  /**
+   * Returns new centers. Depending on splitWon: if true takes children, if
+   * false takes parent = current center.
+   * 
+   * @param splitWon
+   *          array of boolean to indicate to take split or not
+   * @param splitCenters
+   *          list of splitted centers
+   * @return the new centers
+   */
+  protected Instances newCentersAfterSplit(boolean[] splitWon,
+      Instances splitCenters) {
+    Instances newCenters = new Instances(splitCenters, 0);
+
+    int sIndex = 0;
+    for (int i = 0; i < splitWon.length; i++) {
+      if (splitWon[i]) {
+        m_NumSplitsDone++;
+        newCenters.add(splitCenters.instance(sIndex++));
+        newCenters.add(splitCenters.instance(sIndex++));
+      } else {
+        sIndex++;
+        sIndex++;
+        newCenters.add(m_ClusterCenters.instance(i));
+      }
+    }
+    return newCenters;
+  }
+
+  /**
+   * Controls that counter does not exceed max iteration value. Special function
+   * for kmeans iterations.
+   * 
+   * @param iterationCount
+   *          current value of counter
+   * @param max
+   *          maximum value for counter
+   * @return true if iteration should be stopped
+   */ 
+  protected boolean stopKMeansIteration(int iterationCount, int max) {
+    boolean stopIterate = false;
+    if (max >= 0) 
+      stopIterate = (iterationCount >= max);
+    if (stopIterate) 
+      m_KMeansStopped++;
+    return stopIterate;
+  }
+
+  /**
+   * Checks if iterationCount has to be checked and if yes
+   * (this means max is > 0) compares it with max.
+   * 
+   * @param iterationCount the current iteration count
+   * @param max the maximum number of iterations
+   * @return true if maximum has been reached
+   */ 
+  protected boolean stopIteration(int iterationCount, int max) {
+    boolean stopIterate = false;
+    if (max >= 0) 
+      stopIterate = (iterationCount >= max);
+    return stopIterate;
+  }
+
+  /**
+   * Recompute the new centers. New cluster center is center of mass of its 
+   * instances. Returns true if cluster stays the same.
+   * @param centers the input and output centers
+   * @param instOfCent the instances to the centers 
+   * @param model data model information
+   * @return true if converged.
+   */
+   protected boolean recomputeCenters(Instances centers,          
+				   int[][] instOfCent, 
+				   Instances model) {
+    boolean converged = true;
+    
+    for (int i = 0; i < centers.numInstances(); i++) {
+      double val;
+      for (int j = 0; j < model.numAttributes(); j++) {
+	val = meanOrMode(m_Instances, instOfCent[i], j);
+
+	for (int k = 0; k < instOfCent[i].length; k++)
+
+	if (converged && m_ClusterCenters.instance(i).value(j) != val) 
+	  converged = false;
+	if (!converged)
+	  m_ClusterCenters.instance(i).setValue(j, val);
+      }
+    }
+    return converged;
+  }
+
+  /**
+   * Recompute the new centers - 2nd version 
+   * Same as recomputeCenters, but does not check if center stays the same.
+   * 
+   * @param centers the input center and output centers
+   * @param instOfCentIndexes the indexes of the instances to the centers 
+   * @param model data model information
+   */
+  protected void recomputeCentersFast(Instances centers,          
+				    int[][] instOfCentIndexes, 
+				    Instances model   
+				    ) {
+    for (int i = 0; i < centers.numInstances(); i++) {
+      double val;
+      for (int j = 0; j < model.numAttributes(); j++) {
+	val = meanOrMode(m_Instances, instOfCentIndexes[i], j);
+	centers.instance(i).setValue(j, val);
+      }
+    }
+  }
+
+  /**
+   * Computes Mean Or Mode of one attribute on a subset of m_Instances. 
+   * The subset is defined by an index list.
+   * @param instances all instances
+   * @param instList the indexes of the instances the mean is computed from
+   * @param attIndex the index of the attribute
+   * @return mean value
+   */
+  protected double meanOrMode(Instances instances, 
+			    int[] instList, 
+			    int attIndex) {
+    double result, found;
+    int[] counts;
+    int numInst = instList.length;
+
+    if (instances.attribute(attIndex).isNumeric()) {
+      result = found = 0;
+      for (int j = 0; j < numInst; j++) {
+	Instance currInst = instances.instance(instList[j]);
+	if (!currInst.isMissing(attIndex)) {
+	  found += currInst.weight();
+	  result += currInst.weight() * 
+	    currInst.value(attIndex);
+	}
+      }
+      if (Utils.eq(found, 0)) {
+	return 0;
+      } else {
+	return result / found;
+      }
+    } else if (instances.attribute(attIndex).isNominal()) {
+      counts = new int[instances.attribute(attIndex).numValues()];
+      for (int j = 0; j < numInst; j++) {
+	Instance currInst = instances.instance(instList[j]);
+	if (!currInst.isMissing(attIndex)) {
+	  counts[(int) currInst.value(attIndex)] += currInst.weight();
+	}
+      }
+      return (double)Utils.maxIndex(counts);
+    } else {
+      return 0;
+    }
+  }
+
+ 
+  /**
+   * Assigns instances to centers.
+   *
+   * @param tree KDTree on all instances
+   * @param centers all the input centers
+   * @param instOfCent the instances to each center
+   * @param allInstList list of all instances
+   * @param assignments assignments of instances to centers
+   * @param iterationCount the number of iteration 
+   * @return true if converged
+   * @throws Exception is something goes wrong
+   */
+  protected boolean assignToCenters(KDTree tree,
+                                  Instances centers, 
+				  int[][] instOfCent, 
+                                  int[] allInstList,
+				  int[] assignments,
+                                  int iterationCount) throws Exception {
+    
+    boolean converged = true;
+    if (tree != null) {
+      // using KDTree structure for assigning
+      converged = assignToCenters(tree,
+				  centers, 
+				  instOfCent,
+				  assignments,
+				  iterationCount);
+    } else {
+      converged = assignToCenters(centers, 
+				  instOfCent,
+				  allInstList, 
+				  assignments);
+    }
+    return converged;
+  }
+
+  /**
+   * Assign instances to centers using KDtree.
+   * First part of conventionell K-Means, returns true if new assignment
+   * is the same as the last one.
+   *
+   * @param kdtree KDTree on all instances
+   * @param centers all the input centers
+   * @param instOfCent the instances to each center
+   * @param assignments assignments of instances to centers
+   * @param iterationCount the number of iteration 
+   * @return true if converged
+   * @throws Exception in case instances are not assigned to cluster
+   */
+  protected boolean assignToCenters(KDTree kdtree,
+				  Instances centers, 
+				  int[][] instOfCent, 
+				  int[] assignments,
+                                  int iterationCount) throws Exception {
+
+    int numCent = centers.numInstances();
+    int numInst = m_Instances.numInstances(); 
+    int[] oldAssignments = new int[numInst];
+    
+    // WARNING:  assignments is "input/output-parameter"
+    // should not be null
+    if (assignments == null) {
+      assignments = new int[numInst];
+      for (int i = 0; i < numInst; i++) {
+	assignments[0] = -1;
+      }
+    }
+    
+    // WARNING:  instOfCent is "input/output-parameter"
+    // should not be null
+    if (instOfCent == null) {
+      instOfCent = new int [numCent][];
+    }
+    
+    // save old assignments
+    for (int i = 0; i < assignments.length; i++) {
+      oldAssignments[i] = assignments[i];
+    }
+    
+    // use tree to get new assignments
+    kdtree.centerInstances(centers, assignments,
+			   Math.pow(.8, iterationCount));	
+    boolean converged = true;
+  
+    // compare with previous assignment
+    for (int i = 0; converged && (i < assignments.length); i++) {
+      converged = (oldAssignments[i] == assignments[i]);
+      if (assignments[i] == -1) 
+	throw new Exception("Instance " + i + 
+			    " has not been assigned to cluster.");
+    }
+
+    if (!converged) {
+      int[] numInstOfCent = new int[numCent];
+      for (int i = 0; i < numCent; i++)
+	numInstOfCent[i] = 0;
+
+      // count num of assignments per center
+      for (int i = 0; i < numInst; i++)
+	numInstOfCent[assignments[i]]++;
+      
+      // prepare instancelists per center
+      for (int i = 0; i < numCent; i++){
+	instOfCent[i] = new int[numInstOfCent[i]];
+      }
+      // write instance lists per center
+      for (int i = 0; i < numCent; i++) {
+	int index = -1;   
+	for (int j = 0; j < numInstOfCent[i]; j++) {
+	  index = nextAssignedOne(i, index, assignments);
+	  instOfCent[i][j] = index;
+	}
+      }
+    }
+  
+    return converged;
+  }
+
+  /**
+   * Assign instances to centers.
+   * Part of conventionell K-Means, returns true if new assignment
+   * is the same as the last one.
+   *
+   * @param centers all the input centers
+   * @param instOfCent the instances to each center
+   * @param allInstList list of all indexes
+   * @param assignments assignments of instances to centers
+   * @return true if converged
+   * @throws Exception if something goes wrong
+   */
+  protected boolean assignToCenters(Instances centers, 
+				  int[][] instOfCent,
+				  int[] allInstList,
+				  int[] assignments) 
+    throws Exception {
+    
+    // todo: undecided situations
+    boolean converged = true; // true if new assignment is the same 
+                              // as the old one
+
+    int numInst = allInstList.length; 
+    int numCent = centers.numInstances();
+    int[] numInstOfCent = new int [numCent];
+    for (int i = 0; i < numCent; i++) numInstOfCent[i] = 0;
+
+    // WARNING:  assignments is "input/output-parameter"
+    // should not be null
+    if (assignments == null) {
+      assignments = new int[numInst];
+      for (int i = 0; i < numInst; i++) {
+	assignments[i] = -1;
+      }
+    }
+
+    // WARNING: instOfCent is "input/output-parameter"
+    // should not be null
+    if (instOfCent == null) {
+      instOfCent = new int [numCent][];
+    }
+
+    // set assignments
+    for (int i = 0; i < numInst; i++) {
+      Instance inst = m_Instances.instance(allInstList[i]);
+      int newC = clusterProcessedInstance(inst, centers);
+      
+      if (converged && newC != assignments[i]) {
+	converged = false;
+      }
+
+      numInstOfCent[newC]++; 
+      if (!converged)
+	assignments[i] = newC;
+    }
+
+    // the following is only done
+    // if assignments are not the same, because too much effort
+    if (!converged) {
+      PFD(D_FOLLOWSPLIT, "assignToCenters -> it has NOT converged");
+      for (int i = 0; i < numCent; i++) {
+	instOfCent[i] = new int [numInstOfCent[i]];
+      }
+
+      for (int i = 0; i < numCent; i++) {
+	int index = -1;   
+	for (int j = 0; j < numInstOfCent[i]; j++) {
+	  index = nextAssignedOne(i, index, assignments);
+	  instOfCent[i][j] = allInstList[index];
+	}
+      }
+    }
+    else
+      PFD(D_FOLLOWSPLIT, "assignToCenters -> it has converged");
+
+    return converged;
+  }
+
+  /**
+   * Searches along the assignment array for the next entry of the center 
+   * in question.
+   * @param cent index of the center 
+   * @param lastIndex index to start searching
+   * @param assignments assignments
+   * @return index of the instance the center cent is assigned to
+   */
+  protected int nextAssignedOne(int cent, int lastIndex, 
+			      int[] assignments) {
+    int len = assignments.length;
+    int index = lastIndex + 1;
+    while (index < len) {
+      if (assignments[index] == cent) {
+	return (index);
+      }
+      index++;
+    }
+    return (-1);
+  }
+
+  /**
+   * Split centers in their region. Generates random vector of 
+   * length = variance and
+   * adds and substractsx to cluster vector to get two new clusters.
+   * 
+   * @param random random function
+   * @param center the center that is split here
+   * @param variance variance of the cluster 
+   * @param model data model valid
+   * @return a pair of new centers
+   * @throws Exception something in AlgVector goes wrong
+   */
+  protected Instances splitCenter(Random random,
+			        Instance center,
+			        double variance,
+			        Instances model) throws Exception {
+    m_NumSplits++;
+    AlgVector r = null;
+    Instances children = new Instances(model, 2);
+
+    if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile()) {
+      Instance nextVector = getNextDebugVectorsInstance(model);
+      PFD(D_RANDOMVECTOR, "Random Vector from File " + nextVector);
+      r = new AlgVector(nextVector);
+    }
+    else {
+      // random vector of length = variance
+      r = new AlgVector(model, random);
+    }
+    r.changeLength(Math.pow(variance, 0.5));
+    PFD(D_RANDOMVECTOR, "random vector *variance "+ r);
+    
+    // add random vector to center
+    AlgVector c = new AlgVector(center);
+    AlgVector c2 = (AlgVector) c.clone();
+    c = c.add(r);
+    Instance newCenter = c.getAsInstance(model, random);
+    children.add(newCenter);
+    PFD(D_FOLLOWSPLIT, "first child "+ newCenter);
+    
+    // substract random vector to center
+    c2 = c2.substract(r);
+    newCenter = c2.getAsInstance(model, random);
+    children.add(newCenter);
+    PFD(D_FOLLOWSPLIT, "second child "+ newCenter);
+
+    return children;
+  }
+
+  /**
+   * Split centers in their region.
+   * (*Alternative version of splitCenter()*) 
+   * 
+   * @param random the random number generator
+   * @param instances of the region
+   * @param model the model for the centers 
+   * (should be the same as that of instances)
+   * @return a pair of new centers
+   */
+  protected Instances splitCenters(Random random,
+				 Instances instances,
+				 Instances model) {
+    Instances children = new Instances(model, 2);
+    int instIndex = Math.abs(random.nextInt()) % instances.numInstances();
+    children.add(instances.instance(instIndex));
+    int instIndex2 = instIndex;
+    int count = 0;
+    while ((instIndex2 == instIndex) && count < 10) {
+      count++;
+      instIndex2 = Math.abs(random.nextInt()) % instances.numInstances();
+    }
+    children.add(instances.instance(instIndex2));
+    
+    return children;
+  }
+
+  /**
+   * Generates new centers randomly. Used for starting centers.
+   *
+   * @param random0 random number generator
+   * @param model data model of the instances
+   * @param numClusters number of clusters
+   * @return new centers
+   */
+  protected Instances makeCentersRandomly(Random random0,
+					Instances model,
+					int numClusters) {
+    Instances clusterCenters = new Instances(model, numClusters);
+    m_NumClusters = numClusters;
+
+    // makes the new centers randomly
+    for (int i = 0; i < numClusters; i++) {
+      int instIndex = Math.abs(random0.nextInt()) % m_Instances.numInstances();
+      clusterCenters.add(m_Instances.instance(instIndex));
+    }
+    return clusterCenters;
+  }
+
+  /**
+   * Returns the BIC-value for the given center and instances.
+   * @param instList The indices of the instances that belong to the center
+   * @param center the center.
+   * @param mle maximum likelihood
+   * @param model the data model
+   * @return the BIC value 
+   */   
+  protected double calculateBIC(int[] instList, Instance center,
+			      double mle, Instances model) {
+    int[][] w1 = new int[1][instList.length];
+    for (int i = 0; i < instList.length; i++) {
+      w1[0][i] = instList[i];
+    }
+    double[] m = {mle};
+    Instances w2 = new Instances(model, 1);
+    w2.add(center);
+    return calculateBIC(w1, w2, m);
+    }
+  
+  /**
+   * Calculates the BIC for the given set of centers and instances.
+   * @param instOfCent The instances that belong to their respective centers
+   * @param centers the centers
+   * @param mle maximum likelihood
+   * @return The BIC for the input.
+   */
+  protected double calculateBIC(int[][] instOfCent, Instances centers,
+			      double[] mle) {
+    double loglike = 0.0;
+    int numInstTotal = 0;
+    int numCenters = centers.numInstances();
+    int numDimensions = centers.numAttributes();
+    int numParameters = (numCenters - 1) + //probabilities
+      numCenters * numDimensions + //means
+      numCenters; // variance params
+    for (int i = 0; i < centers.numInstances(); i++) {
+      loglike += logLikelihoodEstimate(instOfCent[i].length, centers.instance(i),
+				       mle[i], centers.numInstances() * 2);
+      numInstTotal += instOfCent[i].length;
+    }
+    /* diff
+       thats how we did it
+    loglike -= ((centers.numAttributes() + 1.0) * centers.numInstances() * 1)
+      * Math.log(count);
+      */
+    loglike -= numInstTotal * Math.log(numInstTotal);
+    //System.out.println ("numInstTotal " + numInstTotal +
+    //                    "calculateBIC res " + loglike);
+    loglike -= (numParameters / 2.0) * Math.log(numInstTotal);
+    //System.out.println ("numParam " +
+    //                     + numParameters +
+    //			" calculateBIC res " + loglike);
+    return loglike;
+  }
+  
+  /**
+   * Calculates the log-likelihood of the data for the given model, taken
+   * at the maximum likelihood point.
+   *
+   * @param numInst number of instances that belong to the center
+   * @param center the center
+   * @param distortion distortion 
+   * @param numCent number of centers 
+   * @return the likelihood estimate
+   */
+  protected double logLikelihoodEstimate(int numInst, 
+				       Instance center, 
+				       double distortion, 
+				       int numCent) {
+    // R(n) num of instances of the center -> numInst
+    // K num of centers -> not used
+    //
+    //todo take the diff comments away
+    double loglike = 0;
+    /* if is new */
+    if (numInst > 1) {
+      /* diff variance is new */
+      //
+      // distortion = Sum over instances x of the center(x-center)
+      // different to paper; sum should be squared
+      //
+      // (Sum of distances to center) / R(n) - 1.0
+      // different to paper; should be R(n)-K
+      double variance =  distortion / (numInst - 1.0); 
+  
+      //
+      //  -R(n)/2 * log(pi*2)
+      //
+      double p1 = - (numInst / 2.0) * Math.log(Math.PI * 2.0);
+      /* diff
+	 thats how we had it
+	 double p2 = -((ni * center.numAttributes()) / 2) * distortion;
+      */
+      //
+      // -(R(n)*M)/2 * log(variance) 
+      //
+      double p2 = - (numInst * center.numAttributes()) / 2 * Math.log(variance);
+      
+      /* diff
+	 thats how we had it, the difference is a bug in x-means
+	 double p3 = - (numInst - numCent) / 2;
+      */
+      //
+      // -(R(n)-1)/2
+      //
+      double p3 = - (numInst - 1.0) / 2.0;
+      
+      //
+      // R(n)*log(R(n))
+      //
+      double p4 = numInst * Math.log(numInst);
+      
+      /* diff x-means doesn't have this part 
+	 double p5 = - numInst * Math.log(numInstTotal);
+      */
+      
+      /*
+	loglike = -(ni / 2) * Math.log(Math.PI * 2) 
+	- (ni * center.numAttributes()) / 2.0) * logdistortion
+	- (ni - k) / 2.0 
+	+ ni * Math.log(ni) 
+	- ni * Math.log(r);
+      */
+      loglike = p1 + p2 + p3 + p4; // diff + p5;
+      //the log(r) is something that can be reused.
+      //as is the log(2 PI), these could provide extra speed up later on.
+      //since distortion is so expensive to compute, I only do that once.
+    }
+    return loglike;
+  }
+  
+  /**
+   * Calculates the maximum likelihood estimate for the variance.
+   * @param instOfCent indices of instances to each center
+   * @param centers the centers
+   * @return the list of distortions distortion.
+   */
+  protected double[] distortion(int[][] instOfCent, Instances centers) {
+    double[] distortion = new double[centers.numInstances()];
+    for (int i = 0; i < centers.numInstances(); i++) {
+      distortion[i] = 0.0;
+      for (int j = 0; j < instOfCent[i].length; j++) {
+        distortion[i] += m_DistanceF.distance(m_Instances
+            .instance(instOfCent[i][j]), centers.instance(i));
+      }
+    }
+    /*
+     * diff not done in x-means res *= 1.0 / (count - centers.numInstances());
+     */
+    return distortion;
+  }
+  
+  /**
+   * Clusters an instance.
+   * 
+   * @param instance
+   *          the instance to assign a cluster to.
+   * @param centers
+   *          the centers to cluster the instance to.
+   * @return a cluster index.
+   */
+  protected int clusterProcessedInstance(Instance instance, Instances centers) {
+    
+    double minDist = Integer.MAX_VALUE;
+    int bestCluster = 0;
+    for (int i = 0; i < centers.numInstances(); i++) {
+      double dist = m_DistanceF.distance(instance, centers.instance(i));
+
+      if (dist < minDist) {
+        minDist = dist;
+        bestCluster = i;
+      }
+    }
+    ;
+    return bestCluster;
+  }
+  
+  /**
+   * Clusters an instance that has been through the filters.
+   * 
+   * @param instance
+   *          the instance to assign a cluster to
+   * @return a cluster number
+   */
+  protected int clusterProcessedInstance(Instance instance) {
+    double minDist = Integer.MAX_VALUE;
+    int bestCluster = 0;
+    for (int i = 0; i < m_NumClusters; i++) {
+      double dist = m_DistanceF
+          .distance(instance, m_ClusterCenters.instance(i));
+      if (dist < minDist) {
+        minDist = dist;
+        bestCluster = i;
+      }
+    }
+    return bestCluster;
+  }
+
+  /**
+   * Classifies a given instance.
+   *
+   * @param instance the instance to be assigned to a cluster
+   * @return the number of the assigned cluster as an integer
+   * if the class is enumerated, otherwise the predicted value
+   * @throws Exception if instance could not be classified
+   * successfully
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    m_ReplaceMissingFilter.input(instance);
+    Instance inst = m_ReplaceMissingFilter.output();
+
+    return clusterProcessedInstance(inst);
+  }
+
+
+  /**
+   * Returns the number of clusters.
+   *
+   * @return the number of clusters generated for a training dataset.
+   */
+  public int numberOfClusters() {
+    return m_NumClusters;
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options. 
+   * @return an enumeration of all the available options
+   **/
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tmaximum number of overall iterations\n"
+	+ "\t(default 1).", 
+	"I", 1, "-I <num>"));
+    
+    result.addElement(new Option(
+	"\tmaximum number of iterations in the kMeans loop in\n"
+	+ "\tthe Improve-Parameter part \n"
+	+ "\t(default 1000).", 
+	"M", 1, "-M <num>"));
+    
+    result.addElement(new Option(
+	"\tmaximum number of iterations in the kMeans loop\n"
+	+ "\tfor the splitted centroids in the Improve-Structure part \n"
+	+ "\t(default 1000).",
+	"J", 1, "-J <num>"));
+    
+    result.addElement(new Option(
+	"\tminimum number of clusters\n"
+	+ "\t(default 2).", 
+	"L", 1, "-L <num>"));
+    
+    result.addElement(new Option(
+	"\tmaximum number of clusters\n"
+	+ "\t(default 4).",
+	"H", 1, "-H <num>"));
+    
+    result.addElement(new Option(
+	"\tdistance value for binary attributes\n"
+	+ "\t(default 1.0).",
+	"B", 1, "-B <value>"));
+    
+    result.addElement(new Option(
+	"\tUses the KDTree internally\n"
+	+ "\t(default no).",
+	"use-kdtree", 0, "-use-kdtree"));
+    
+    result.addElement(new Option(
+	"\tFull class name of KDTree class to use, followed\n"
+	+ "\tby scheme options.\n"
+	+ "\teg: \"weka.core.neighboursearch.kdtrees.KDTree -P\"\n"
+	+ "\t(default no KDTree class used).",
+	"K", 1, "-K <KDTree class specification>"));
+    
+    result.addElement(new Option(
+	"\tcutoff factor, takes the given percentage of the splitted \n"
+	+ "\tcentroids if none of the children win\n"
+	+ "\t(default 0.0).",
+	"C", 1, "-C <value>"));
+    
+    result.addElement(new Option(
+	"\tFull class name of Distance function class to use, followed\n"
+	+ "\tby scheme options.\n" +
+	"\t(default weka.core.EuclideanDistance).",
+	"D", 1, "-D <distance function class specification>"));
+    
+    result.addElement(new Option(
+	"\tfile to read starting centers from (ARFF format).",
+	"N", 1, "-N <file name>"));
+    
+    result.addElement(new Option(
+	"\tfile to write centers to (ARFF format).",
+	"O", 1, "-O <file name>"));
+    
+    result.addElement(new Option(
+	"\tThe debug level.\n"
+	+ "\t(default 0)",
+	"U", 1, "-U <int>"));
+    
+    result.addElement(new Option(
+	"\tThe debug vectors file.",
+	"Y", 1, "-Y <file name>"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+    
+    return result.elements();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String minNumClustersTipText() {
+    return "set minimum number of clusters";
+  }
+
+  /**
+   * Sets the minimum number of clusters to generate.
+   *
+   * @param n the minimum number of clusters to generate
+   */
+  public void setMinNumClusters(int n) {
+    m_MinNumClusters = n;
+  }
+
+  /**
+   * Gets the minimum number of clusters to generate.
+   * @return the minimum number of clusters to generate
+   */
+  public int getMinNumClusters() {
+    return m_MinNumClusters;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String maxNumClustersTipText() {
+    return "set maximum number of clusters";
+  }
+
+  /**
+   * Sets the maximum number of clusters to generate.
+   * @param n the maximum number of clusters to generate
+   */
+  public void setMaxNumClusters(int n) {
+    if (n >= m_MinNumClusters) {
+      m_MaxNumClusters = n;
+    }
+  }
+  
+  /**
+   * Gets the maximum number of clusters to generate.
+   * @return the maximum number of clusters to generate
+   */
+  public int getMaxNumClusters() {
+    return m_MaxNumClusters;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String maxIterationsTipText() {
+    return "the maximum number of iterations to perform";
+  }
+
+  /**
+   * Sets the maximum number of iterations to perform.
+   * @param i the number of iterations
+   * @throws Exception if i is less than 1
+   */
+  public void setMaxIterations(int i) throws Exception {
+    if (i < 0) 
+      throw new Exception("Only positive values for iteration number" +
+                           " allowed (Option I)."); 
+    m_MaxIterations = i;
+  }
+
+  /**
+   * Gets the maximum number of iterations.
+   * @return the number of iterations
+   */
+  public int getMaxIterations() {
+    return  m_MaxIterations;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String maxKMeansTipText() {
+    return "the maximum number of iterations to perform in KMeans";
+  }
+
+  /**
+   * Set the maximum number of iterations to perform in KMeans.
+   * @param i the number of iterations
+   */
+  public void setMaxKMeans(int i) {
+    m_MaxKMeans = i;
+    m_MaxKMeansForChildren = i;
+  }
+
+  /**
+   * Gets the maximum number of iterations in KMeans.
+   * @return the number of iterations
+   */
+  public int getMaxKMeans() {
+    return  m_MaxKMeans;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String maxKMeansForChildrenTipText() {
+    return "the maximum number of iterations KMeans that is performed on the child centers";
+  }
+
+  /**
+   * Sets the maximum number of iterations KMeans that is performed 
+   * on the child centers.
+   * @param i the number of iterations
+   */
+  public void setMaxKMeansForChildren(int i) {
+    m_MaxKMeansForChildren = i;
+  }
+
+  /**
+   * Gets the maximum number of iterations in KMeans.
+   * @return the number of iterations
+   */
+  public int getMaxKMeansForChildren() {
+    return  m_MaxKMeansForChildren;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property 
+   */
+  public String cutOffFactorTipText() {
+    return "the cut-off factor to use";
+  }
+
+  /**
+   * Sets a new cutoff factor.
+   * @param i the new cutoff factor
+   */
+  public void setCutOffFactor(double i) {
+    m_CutOffFactor = i;
+  }
+
+  /**
+   * Gets the cutoff factor.
+   * @return the cutoff factor
+   */
+  public double getCutOffFactor() {
+    return  m_CutOffFactor;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binValueTipText() {
+    return "Set the value that represents true in the new attributes.";
+  }
+  
+  /**
+   * Gets value that represents true in a new numeric attribute.
+   * (False is always represented by 0.0.)
+   * @return the value that represents true in a new numeric attribute
+   */
+  public double getBinValue() {
+    return m_BinValue;
+  }
+
+  /**
+   * Sets the distance value between true and false of binary attributes.
+   * and  "same" and "different" of nominal attributes    
+   * @param value the distance
+   */
+  public void setBinValue(double value) {
+    m_BinValue = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String distanceFTipText() {
+    return "The distance function to use.";
+  }
+
+  /**
+   * gets the "binary" distance value.
+   * @param distanceF the distance function with all options set
+   */
+  public void setDistanceF(DistanceFunction distanceF) {
+    m_DistanceF = distanceF;
+  }
+
+  /**
+   * Gets the distance function.
+   * @return the distance function
+   */
+  public DistanceFunction getDistanceF() {
+    return m_DistanceF;
+  }
+
+  /**
+   * Gets the distance function specification string, which contains the 
+   * class name of the distance function class and any options to it.
+   *
+   * @return the distance function specification string
+   */
+  protected String getDistanceFSpec() {
+    
+    DistanceFunction d = getDistanceF();
+    if (d instanceof OptionHandler) {
+      return d.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler) d).getOptions());
+    }
+    return d.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String debugVectorsFileTipText() {
+    return "The file containing the debug vectors (only for debugging!).";
+  }
+  
+  /**
+   * Sets the file that has the random vectors stored.
+   * Only used for debugging reasons.
+   * @param value the file to read the random vectors from
+   */
+  public void setDebugVectorsFile(File value) {
+    m_DebugVectorsFile = value;
+  }
+
+  /**
+   * Gets the file name for a file that has the random vectors stored.
+   * Only used for debugging purposes.
+   * @return the file to read the vectors from
+   */
+  public File getDebugVectorsFile() {
+    return m_DebugVectorsFile;
+  }
+  
+  /**
+   * Initialises the debug vector input.
+   * @throws Exception if there is error 
+   * opening the debug input file.
+   */
+  public void initDebugVectorsInput() throws Exception {
+    m_DebugVectorsInput = 
+      new BufferedReader(new FileReader(m_DebugVectorsFile));
+    m_DebugVectors = new Instances(m_DebugVectorsInput);
+    m_DebugVectorsIndex = 0;
+  }
+
+  /**
+   * Read an instance from debug vectors file.
+   * @param model the data model for the instance.
+   * @throws Exception if there are no debug vector 
+   * in m_DebugVectors.
+   * @return the next debug vector.
+   */
+  public Instance getNextDebugVectorsInstance(Instances model) 
+    throws Exception {
+    if (m_DebugVectorsIndex >= m_DebugVectors.numInstances())
+      throw new Exception("no more prefabricated Vectors");
+    Instance nex = m_DebugVectors.instance(m_DebugVectorsIndex);
+    nex.setDataset(model);
+    m_DebugVectorsIndex++;
+    return nex;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String inputCenterFileTipText() {
+    return "The file to read the list of centers from.";
+  }
+
+  /**
+   * Sets the file to read the list of centers from.
+   *
+   * @param value the file to read centers from
+   */
+  public void setInputCenterFile(File value) {
+    m_InputCenterFile = value;
+  }
+  
+  /**
+   * Gets the file to read the list of centers from.
+   *
+   * @return the file to read the centers from
+   */
+  public File getInputCenterFile() {
+    return m_InputCenterFile;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputCenterFileTipText() {
+    return "The file to write the list of centers to.";
+  }
+    
+  /**
+   * Sets file to write the list of centers to. 
+   *
+   * @param value file to write centers to
+   */
+  public void setOutputCenterFile(File value) {
+    m_OutputCenterFile = value;
+  }
+
+  /**
+   * Gets the file to write the list of centers to. 
+   * 
+   * @return filename of the file to write centers to
+   */
+  public File getOutputCenterFile() {
+    return m_OutputCenterFile;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String KDTreeTipText() {
+    return "The KDTree to use.";
+  }
+    
+  /**
+   * Sets the KDTree class.
+   * @param k a KDTree object with all options set
+   */
+  public void setKDTree(KDTree k) {
+    m_KDTree = k;
+  }
+
+  /**
+   * Gets the KDTree class.
+   * 
+   * @return the configured KDTree
+   */
+  public KDTree getKDTree() {
+    return m_KDTree;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useKDTreeTipText() {
+    return "Whether to use the KDTree.";
+  }
+    
+  /**
+   * Sets whether to use the KDTree or not.
+   * 
+   * @param value	if true the KDTree is used
+   */
+  public void setUseKDTree(boolean value) {
+    m_UseKDTree = value;
+  }
+
+  /**
+   * Gets whether the KDTree is used or not.
+   * 
+   * @return 		true if KDTrees are used
+   */
+  public boolean getUseKDTree() {
+    return m_UseKDTree;
+  }
+
+  /**
+   * Gets the KDTree specification string, which contains the class name of
+   * the KDTree class and any options to the KDTree.
+   *
+   * @return the KDTree string.
+   */
+  protected String getKDTreeSpec() {
+    
+    KDTree c = getKDTree();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String debugLevelTipText() {
+    return "The debug level to use.";
+  }
+
+  /**
+   * Sets the debug level.
+   * debug level = 0, means no output
+   * @param d debuglevel
+   */
+  public void setDebugLevel(int d) {
+    m_DebugLevel = d;
+  }
+
+  /**
+   * Gets the debug level.
+   * @return debug level
+   */
+  public int getDebugLevel() {
+    return m_DebugLevel;
+  }
+
+  /**
+   * Checks the instances.
+   * No checks in this KDTree but it calls the check of the distance function.
+   */
+  protected void checkInstances () {
+    
+   // m_DistanceF.checkInstances();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  maximum number of overall iterations
+   *  (default 1).</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  maximum number of iterations in the kMeans loop in
+   *  the Improve-Parameter part 
+   *  (default 1000).</pre>
+   * 
+   * <pre> -J &lt;num&gt;
+   *  maximum number of iterations in the kMeans loop
+   *  for the splitted centroids in the Improve-Structure part 
+   *  (default 1000).</pre>
+   * 
+   * <pre> -L &lt;num&gt;
+   *  minimum number of clusters
+   *  (default 2).</pre>
+   * 
+   * <pre> -H &lt;num&gt;
+   *  maximum number of clusters
+   *  (default 4).</pre>
+   * 
+   * <pre> -B &lt;value&gt;
+   *  distance value for binary attributes
+   *  (default 1.0).</pre>
+   * 
+   * <pre> -use-kdtree
+   *  Uses the KDTree internally
+   *  (default no).</pre>
+   * 
+   * <pre> -K &lt;KDTree class specification&gt;
+   *  Full class name of KDTree class to use, followed
+   *  by scheme options.
+   *  eg: "weka.core.neighboursearch.kdtrees.KDTree -P"
+   *  (default no KDTree class used).</pre>
+   * 
+   * <pre> -C &lt;value&gt;
+   *  cutoff factor, takes the given percentage of the splitted 
+   *  centroids if none of the children win
+   *  (default 0.0).</pre>
+   * 
+   * <pre> -D &lt;distance function class specification&gt;
+   *  Full class name of Distance function class to use, followed
+   *  by scheme options.
+   *  (default weka.core.EuclideanDistance).</pre>
+   * 
+   * <pre> -N &lt;file name&gt;
+   *  file to read starting centers from (ARFF format).</pre>
+   * 
+   * <pre> -O &lt;file name&gt;
+   *  file to write centers to (ARFF format).</pre>
+   * 
+   * <pre> -U &lt;int&gt;
+   *  The debug level.
+   *  (default 0)</pre>
+   * 
+   * <pre> -Y &lt;file name&gt;
+   *  The debug vectors file.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 10)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {
+    
+    String 	optionString;
+    String 	funcString;
+
+    optionString = Utils.getOption('I', options);
+    if (optionString.length() != 0)
+      setMaxIterations(Integer.parseInt(optionString));
+    else
+      setMaxIterations(1);
+    
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0)
+      setMaxKMeans(Integer.parseInt(optionString));
+    else
+      setMaxKMeans(1000);
+    
+    optionString = Utils.getOption('J', options);
+    if (optionString.length() != 0)
+      setMaxKMeansForChildren(Integer.parseInt(optionString));
+    else
+      setMaxKMeansForChildren(1000);
+      
+    optionString = Utils.getOption('L', options);
+    if (optionString.length() != 0)
+      setMinNumClusters(Integer.parseInt(optionString));
+    else
+      setMinNumClusters(2);
+      
+    optionString = Utils.getOption('H', options);
+    if (optionString.length() != 0)
+      setMaxNumClusters(Integer.parseInt(optionString));
+    else
+      setMaxNumClusters(4);
+    
+    optionString = Utils.getOption('B', options);
+    if (optionString.length() != 0)
+      setBinValue(Double.parseDouble(optionString));
+    else
+      setBinValue(1.0);
+
+    setUseKDTree(Utils.getFlag("use-kdtree", options));
+    
+    if (getUseKDTree()) {
+      funcString = Utils.getOption('K', options);
+      if (funcString.length() != 0) {
+	String[] funcSpec = Utils.splitOptions(funcString);
+	if (funcSpec.length == 0) {
+	  throw new Exception("Invalid function specification string");
+	}
+	String funcName = funcSpec[0];
+	funcSpec[0] = "";
+	setKDTree((KDTree) Utils.forName(KDTree.class, funcName, funcSpec));
+      }
+      else {
+	setKDTree(new KDTree());
+      }
+    }
+    else {
+      setKDTree(new KDTree());
+    }
+
+    optionString = Utils.getOption('C', options);
+    if (optionString.length() != 0)
+      setCutOffFactor(Double.parseDouble(optionString));
+    else
+      setCutOffFactor(0.0);
+    
+    funcString = Utils.getOption('D', options);
+    if (funcString.length() != 0) {
+      String[] funcSpec = Utils.splitOptions(funcString);
+      if (funcSpec.length == 0) {
+	throw new Exception("Invalid function specification string");
+      }
+      String funcName = funcSpec[0];
+      funcSpec[0] = "";
+      setDistanceF((DistanceFunction) Utils.forName(DistanceFunction.class,
+						    funcName, funcSpec));
+    }
+    else {
+      setDistanceF(new EuclideanDistance());
+    }
+
+    optionString  = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setInputCenterFile(new File(optionString));
+      m_CenterInput = 
+	new BufferedReader(new FileReader(optionString));
+    }
+    else {
+      setInputCenterFile(new File(System.getProperty("user.dir")));
+      m_CenterInput = null;
+    }
+
+    optionString  = Utils.getOption('O', options);
+    if (optionString.length() != 0) {
+      setOutputCenterFile(new File(optionString));
+      m_CenterOutput = new PrintWriter(new FileOutputStream(optionString));
+    }
+    else {
+      setOutputCenterFile(new File(System.getProperty("user.dir")));
+      m_CenterOutput = null;
+    }
+
+    optionString = Utils.getOption('U', options);
+    int debugLevel = 0;
+    if (optionString.length() != 0) {
+      try {
+	debugLevel = Integer.parseInt(optionString);
+      } catch (NumberFormatException e) {
+	throw new Exception(optionString +
+                            "is an illegal value for option -U"); 
+      }
+    }
+    setDebugLevel(debugLevel);
+
+    optionString  = Utils.getOption('Y', options);
+    if (optionString.length() != 0) {
+      setDebugVectorsFile(new File(optionString));
+    }
+    else {
+      setDebugVectorsFile(new File(System.getProperty("user.dir")));
+      m_DebugVectorsInput = null;
+      m_DebugVectors      = null;
+    }
+    
+    super.setOptions(options);
+  }
+  
+  /**
+   * Gets the current settings of SimpleKMeans.
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       	i;
+    Vector    	result;
+    String[]  	options;
+
+    result = new Vector();
+
+    result.add("-I");
+    result.add("" + getMaxIterations());
+    
+    result.add("-M");
+    result.add("" + getMaxKMeans());
+    
+    result.add("-J");
+    result.add("" + getMaxKMeansForChildren());
+    
+    result.add("-L");
+    result.add("" + getMinNumClusters());
+    
+    result.add("-H");
+    result.add("" + getMaxNumClusters());
+    
+    result.add("-B");
+    result.add("" + getBinValue());
+    
+    if (getUseKDTree()) {
+      result.add("-use-kdtree");
+      result.add("-K");
+      result.add("" + getKDTreeSpec());
+    }
+    
+    result.add("-C");
+    result.add("" + getCutOffFactor());
+    
+    if (getDistanceF() != null) {
+      result.add("-D");
+      result.add("" + getDistanceFSpec());
+    }
+    
+    if (getInputCenterFile().exists() && getInputCenterFile().isFile()) {
+      result.add("-N");
+      result.add("" + getInputCenterFile());
+    }
+    
+    if (getOutputCenterFile().exists() && getOutputCenterFile().isFile()) {
+      result.add("-O");
+      result.add("" + getOutputCenterFile());
+    }
+    
+    int dL = getDebugLevel();
+    if (dL > 0) {
+      result.add("-U");
+      result.add("" + getDebugLevel());
+    }
+
+    if (getDebugVectorsFile().exists() && getDebugVectorsFile().isFile()) {
+      result.add("-Y");
+      result.add("" + getDebugVectorsFile());
+    }
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Return a string describing this clusterer.
+   * @return a description of the clusterer as a string
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+
+    temp.append("\nXMeans\n======\n");
+
+    temp.append("Requested iterations            : " + m_MaxIterations + "\n");
+    temp.append("Iterations performed            : " + m_IterationCount+ "\n");
+    if (m_KMeansStopped > 0) {
+      temp.append("kMeans did not converge\n");
+      temp.append("  but was stopped by max-loops " 
+	  + m_KMeansStopped + " times (max kMeans-iter)\n");
+    }
+    temp.append("Splits prepared                 : " + m_NumSplits + "\n");
+    temp.append("Splits performed                : " + m_NumSplitsDone + "\n");
+    temp.append("Cutoff factor                   : " + m_CutOffFactor + "\n");
+    double perc;
+    if (m_NumSplitsDone > 0)
+      perc = (((double)m_NumSplitsStillDone)/((double) m_NumSplitsDone))
+             * 100.0;
+    else
+      perc = 0.0;
+    temp.append("Percentage of splits accepted \n" +
+                "by cutoff factor                : " 
+		+ Utils.doubleToString(perc,2) + " %\n");
+    temp.append("------\n");
+
+    temp.append("Cutoff factor                   : " + m_CutOffFactor + "\n");
+    temp.append("------\n");
+    temp.append("\nCluster centers                 : " + m_NumClusters + " centers\n");
+    for (int i = 0; i < m_NumClusters; i++) {
+      temp.append("\nCluster "+i+"\n           ");
+      for (int j = 0; j < m_ClusterCenters.numAttributes(); j++) {
+	if (m_ClusterCenters.attribute(j).isNominal()) {
+	  temp.append(" "+m_ClusterCenters.attribute(j).
+		      value((int)m_ClusterCenters.instance(i).value(j)));
+	} else {
+	  temp.append(" "+m_ClusterCenters.instance(i).value(j));
+	}
+      }
+    }
+    if (m_Mle != null)
+      temp.append("\n\nDistortion: " + 
+		  Utils.doubleToString(Utils.sum(m_Mle),6) + "\n");
+    temp.append("BIC-Value : " + Utils.doubleToString(m_Bic,6) + "\n");
+    return temp.toString();
+  }
+
+  /**
+   * Print centers for debug.
+   * @param debugLevel level that gives according messages
+   */
+  protected void PrCentersFD(int debugLevel) {
+    if (debugLevel == m_DebugLevel) {
+      for (int i = 0; i < m_ClusterCenters.numInstances(); i++) {
+	System.out.println(m_ClusterCenters.instance(i));
+      }
+    }
+  }
+
+  /**
+   * Tests on debug status.
+   * @param debugLevel level that gives according messages
+   * @return true if debug level is set
+   */
+  protected boolean TFD(int debugLevel) {
+    return (debugLevel == m_DebugLevel);
+  }
+
+  /**
+   * Does debug printouts.
+   * @param debugLevel level that gives according messages
+   * @param output string that is printed
+   */
+  protected void PFD(int debugLevel, String output) {
+    if (debugLevel == m_DebugLevel)
+      System.out.println(output);
+  }
+  /**
+   * Does debug printouts.
+   * @param output string that is printed
+   */
+  protected void PFD_CURR(String output) {
+    if (m_CurrDebugFlag)
+      System.out.println(output);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5488 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   * @param argv should contain options 
+   */
+  public static void main(String[] argv) {
+    runClusterer(new XMeans(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Coarse.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Coarse.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Coarse.java	(revision 29)
@@ -0,0 +1,197 @@
+package weka.clusterers.forMetisMQI;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import weka.clusterers.forMetisMQI.graph.Edge;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import weka.clusterers.forMetisMQI.util.CoarserGraphElement;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+public class Coarse {
+	
+	private static boolean debug = false;
+	private static PrintStream debugStream = System.err;
+	private static int finerSize = 5;
+
+	/**
+	 * Return true if the vertex v is matched
+	 * 
+	 * @param g graph
+	 * @param v
+	 *            the index of the vertex
+	 * @return true if the vertex v is matched, false o.w.
+	 */
+	private static boolean isMatched(Map<Node,Node> match, Node v) {
+		if(match.containsKey(v))
+			return !match.get(v).equals(v);
+		else
+			return false;
+	}
+
+	private static void RMMatch(UndirectedGraph g, Map<Node,Node> match, Map<Node,Node> map) {
+		int labelCounter = 0;
+		Iterator<Node> nodeIterator = g.vtxsPermutation().iterator();
+		while (nodeIterator.hasNext()) {
+			Node u = nodeIterator.next();
+			if (debug)
+				debugStream.println("Visiting node " + u + " Matched = " + isMatched(match,u));
+			if (!isMatched(match,u)) {
+				boolean foundMatch = false;
+				Node matchedNode = null;
+				Iterator<Node> iterator = g.getNeighborsPermutation(u).iterator();
+				while(iterator.hasNext() && !foundMatch){
+					Node v = iterator.next();
+					if (!isMatched(match,v)) {
+						matchedNode = v;
+						foundMatch = true;
+						if (debug)
+							debugStream.println("Found a match with node "
+									+ matchedNode);
+					}
+				}
+				if (debug && !foundMatch)
+					debugStream.println("There aren't unmatched neighbors.");
+				Node newNode = new Node(Integer.toString(labelCounter));
+				if(foundMatch) {
+					match.put(u, matchedNode);
+					match.put(matchedNode, u);
+					map.put(u, newNode);
+					map.put(matchedNode, newNode);
+					if(debug) debugStream.println("Contracting node " + u + " with " + matchedNode + ". Node id: " + getMappedNode(map, u));
+				} else {
+					match.put(u, u);
+					map.put(u, newNode);
+					if(debug) debugStream.println("Node " + u + " with " + " new node id: " + getMappedNode(map, u));
+				}
+				labelCounter++;
+			}
+		}
+	}
+	
+	private static Node getMatchedNode(Map<Node,Node> match, Node u) {
+		return match.get(u);
+	}
+	
+	private static Node getMappedNode(Map<Node,Node> map, Node u) {
+		return map.get(u);
+	}
+	
+	/**
+	 * Return a new contracted graph.
+	 */
+	private static UndirectedGraph contract(UndirectedGraph g, Map<Node,Node> match, Map<Node,Node> map) {
+		UndirectedGraph output = new UndirectedGraph();
+		Iterator<Node> iterator = g.getVertices().iterator();
+		int i = 0;
+		while(iterator.hasNext()) {
+			Node u = iterator.next();
+			output.addVertex(getMappedNode(map,u));
+			
+			Set<Node> neighbors = new HashSet<Node>();
+			Iterator<Node> it = g.getNeighbors(u).iterator();
+			while(it.hasNext())
+				neighbors.add(getMappedNode(map, it.next()));
+			it = g.getNeighbors(getMatchedNode(match, u)).iterator();
+			while(it.hasNext())
+				neighbors.add(getMappedNode(map, it.next()));
+			neighbors.remove(getMappedNode(map,u));
+			it = neighbors.iterator();
+			while(it.hasNext()) {
+				Node v = it.next();
+				output.addVertex(v);
+				output.addEdge(new Edge(Integer.toString(i),0,0),getMappedNode(map,u), v);
+				i++;
+			}
+		}
+		
+		//calcolo dei pesi del nuovo grafo: per ogni arco (u,v) && u < v.
+		//w(map(u),map(v)) += w(u,v).
+		
+		Iterator<Edge> edgeIterator = g.getEdges().iterator();
+		while(edgeIterator.hasNext()) {
+			Edge oldEdge = edgeIterator.next();
+			Pair<Node> srcDst = g.getEndpoints(oldEdge);
+			Node src = srcDst.getFirst();
+			Node dst = srcDst.getSecond();
+			Node srcMapped = getMappedNode(map, src);
+			Node dstMapped = getMappedNode(map, dst);
+			if(!srcMapped.equals(dstMapped) && output.containsEdge(srcMapped, dstMapped)) {
+				Edge newEdge = output.findEdge(srcMapped, dstMapped);
+				newEdge.setWeight(newEdge.getWeight() + oldEdge.getWeight());
+			}
+		}
+		iterator = g.getVertices().iterator();
+		Set<Node> nodesComplete = new HashSet<Node>();
+		while(iterator.hasNext()) {
+			Node u = iterator.next();
+			if(isMatched(match,u)) {
+				Node v = getMatchedNode(match,u);
+				if(!nodesComplete.contains(u)) {
+					getMappedNode(map,u).setVwgt(u.getVwgt() + v.getVwgt());
+					getMappedNode(map,u).setCewgt(u.getCewgt() + v.getCewgt() + g.findEdge(u, v).getWeight());
+					nodesComplete.add(u);
+					nodesComplete.add(v);
+				}
+			} else {
+				getMappedNode(map,u).setVwgt(u.getVwgt());
+				getMappedNode(map,u).setCewgt(u.getCewgt());
+			}
+		}
+		return output;
+	}
+
+	/**
+	 * Performs the first stage of the METIS algorithm, using RM.
+	 */
+	private static CoarserGraphElement coarseOneStep(UndirectedGraph g) {
+		UndirectedGraph projected = g;
+		UndirectedGraph contracted = null;
+		Map<Node,Node> match = new HashMap<Node,Node>();
+		Map<Node,Node> map = new HashMap<Node,Node>();
+		RMMatch(g,match,map);
+		contracted = contract(g,match,map);
+		return new CoarserGraphElement(contracted, projected, match, map);
+	}
+
+	
+	/**
+	 * Performs at least one contraction of the given the graph, and goes on until the graph
+	 * is under the desidered size (see setFinerSize()).
+	 * @param g
+	 * @return the stack of contracted graphs
+	 */
+	public static Stack<CoarserGraphElement> coarse(UndirectedGraph g) {
+		Stack<CoarserGraphElement> stack = new Stack<CoarserGraphElement>();
+		CoarserGraphElement e = null;
+		UndirectedGraph curr = g;
+		int i = 0;
+		
+	    do {
+	    	if(debug)
+	    		debugStream.println("--------CONTRACTION-nr" + i +"------------------------------");
+	    	e = coarseOneStep(curr);
+	    	stack.push(e);
+	    	curr = e.getContracted();
+	    	if(debug) {
+	    		debugStream.println("-----------EXPANDED----------------------------------");
+	    		debugStream.println(e.getProjected().toString());
+	    		debugStream.println("-----------CONTRACTED--------------------------------");
+	    		debugStream.println(e.getContracted().toString());
+	    	}
+	    	i++;
+	    } while(e.getProjected().getVertexCount() > e.getContracted().getVertexCount() && e.getContracted().getVertexCount() > finerSize);
+	    return stack;
+	}
+
+	public static void setFinerSize(int i) {
+		finerSize = i;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/GraphAlgorithms.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/GraphAlgorithms.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/GraphAlgorithms.java	(revision 29)
@@ -0,0 +1,187 @@
+package weka.clusterers.forMetisMQI;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Stack;
+
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+
+import weka.clusterers.forMetisMQI.graph.Bisection;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.Subgraph;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import weka.clusterers.forMetisMQI.util.CoarserGraphElement;
+import weka.clusterers.forMetisMQI.util.Configuration;
+import weka.clusterers.forMetisMQI.util.GraphsFrame;
+import weka.clusterers.forMetisMQI.util.Random;
+import weka.clusterers.forMetisMQI.util.Util;
+
+public class GraphAlgorithms {
+
+	
+	static public Bisection KLRefinement(Bisection b) {
+		int remainingNumberOfSwap = 50;
+		Bisection partition = b;
+		Bisection result = partition;
+		int bestEdgeCut = partition.edgeCut();
+		Node u = partition.getCandidate();
+		while (u != null && remainingNumberOfSwap > 0) {
+			partition.swap(u);
+			if (partition.edgeCut() <= bestEdgeCut) {
+				bestEdgeCut = partition.edgeCut();
+				result = partition.clone();
+				remainingNumberOfSwap = 50;
+			} else
+				remainingNumberOfSwap--;
+			u = partition.getCandidate();
+		}
+		return result;
+	}
+	
+	
+	/**
+	 * Given an undirected graph, performs the Kernighan-Li algorithm to find a bisection and
+	 * then returns it.
+	 * @param g
+	 * @return
+	 */
+	static public Bisection KL(UndirectedGraph g) {
+		int remainingNumberOfSwap = 50;
+		Bisection partition = new Bisection(g);
+		Bisection result = partition;
+		int bestEdgeCut = partition.edgeCut();
+		Node u = partition.getCandidate();
+		while (u != null && remainingNumberOfSwap > 0) {
+			partition.swap(u);
+			if (partition.edgeCut() <= bestEdgeCut) {
+				bestEdgeCut = partition.edgeCut();
+				result = partition.clone();
+				remainingNumberOfSwap = 50;
+			} else
+				remainingNumberOfSwap--;
+			u = partition.getCandidate();
+		}
+		return result;
+	}
+	
+	static public Bisection metis(UndirectedGraph g, int sizeFinerGraph) {
+		Coarse.setFinerSize(sizeFinerGraph);
+		Stack<CoarserGraphElement> stack = Coarse.coarse(g);
+		Bisection partition = null;
+		if (stack.size() > 0) {
+			partition = KL(stack.peek().getContracted());
+			if(Configuration.instance().getVerboseLevel() > 1) {
+				GraphsFrame.instance().addPanel(Util.panelContractedGraph(stack.peek()));
+				GraphsFrame.instance().addPanel(Util.panelContractedGraph(stack.peek(),partition.getSmallerSubgraph().createInducedSubgraph().getVertices()));
+			}
+			partition = Uncoarse.uncoarse(stack, partition);
+		}
+		return partition;
+	}
+
+	/**
+	 * Given an UndirectedGraph, runs metis+mqi for <code>numberOfCluster</code> times and
+	 * returns a set of clusters. With the third parameter you can control the maximum size of the finer
+	 * graph during the coarsening phase.  
+	 * @param g
+	 * @param numberOfCluster
+	 * @param sizeFinerGraph
+	 */
+	static public Set<Set<Node>> metisMqi(UndirectedGraph g) {
+		int numberOfCluster = Configuration.instance().getNumberOfClusters();
+		boolean randomBisection = Configuration.instance().isRandomBisection();
+		int sizeFinerGraph = Configuration.instance().getSizeFinerGraph();
+		int verboseLevel = Configuration.instance().getVerboseLevel();
+		GraphsFrame gf = GraphsFrame.instance();
+		
+		Element rootXML = new Element("run");
+		Document logXML = new Document(rootXML);
+		rootXML.setAttribute("seed", Long.toString(Random.instance().getSeed()));
+		Element graphXML = new Element("graph");
+		graphXML.addContent(new Element("vertex").setText(Integer.toString(g.getVertexCount())));
+		graphXML.addContent(new Element("edge").setText(Integer.toString(g.getEdgeCount())));
+		rootXML.addContent(graphXML);
+		Element clustersXML = new Element("clusters");
+		rootXML.addContent(clustersXML);
+		
+		System.out.println("Seed: " + Random.instance().getSeed());
+		System.out.println("Vertex count: " + g.getVertexCount());
+		System.out.println("Edges count: " + g.getEdgeCount());
+		Set<Set<Node>> clusters = new HashSet<Set<Node>>();
+		UndirectedGraph gclone = g.clone();
+		
+		for (int i = 0; i < numberOfCluster; i++) {
+			Bisection bisection = null;
+			if(verboseLevel > 1)
+				gf.addPanel(Util.panelGraph(g.clone()));
+			if(!randomBisection)
+				bisection = metis(g,sizeFinerGraph);
+			else
+				bisection = new Bisection(g);
+			
+			if(verboseLevel > 1)
+				gf.addPanel(Util.panelCluster(g.clone(),bisection.getSmallerSubgraph().createInducedSubgraph().getVertices()));
+			
+			System.out.print("Initial partition: ");
+			System.out.print("V1: " + bisection.getSubgraph().getVertexCount());
+			System.out.print(" V2: " + bisection.getComplement().getVertexCount());
+			System.out.println(" EC: " + bisection.edgeCut() * 0.5);
+			System.out.println("Conductance: " + 
+					((double)bisection.edgeCut() / 2) / Math.min(bisection.getSubgraph().totalDegree(),bisection.getComplement().totalDegree()));
+			Set<Node> cluster = MQI.mqi(bisection,true);
+			
+			UndirectedGraph clusterGraph = new Subgraph(gclone,cluster).createInducedSubgraph();
+			
+//			System.out.println(cluster);
+			Bisection mqiBisection = new Bisection(new Subgraph(g,cluster));
+			System.out.println("Refined partition (MQI)");
+			double newConductance = ((double)mqiBisection.edgeCut() / 2) / Math.min(mqiBisection.getSubgraph().totalDegree(),mqiBisection.getComplement().totalDegree());
+			System.out.print("Cluster "+ i + ":  V=" + clusterGraph.getVertexCount() + ", E=" + clusterGraph.getEdgeCount()+", ");
+			System.out.println("Cond: " + newConductance);
+			
+			
+			mqiBisection = new Bisection(new Subgraph(gclone,cluster));
+			newConductance = ((double)mqiBisection.edgeCut() / 2) / Math.min(mqiBisection.getSubgraph().totalDegree(),mqiBisection.getComplement().totalDegree());
+			System.out.println("Cluster conductance: " + newConductance);
+			
+			
+			Element clusterXML = new Element("cluster");
+			clusterXML.setAttribute("id", Integer.toString(i));
+			clusterXML.addContent(new Element("vertex").setText(Integer.toString(clusterGraph.getVertexCount())));
+			clusterXML.addContent(new Element("edge").setText(Integer.toString(clusterGraph.getEdgeCount())));
+			clusterXML.addContent(new Element("conductance").setText(Double.toString(newConductance)));
+			clustersXML.addContent(clusterXML);
+			
+			clusters.add(cluster);
+			
+			System.out.println();
+			Iterator<Node> clustersNode = cluster.iterator();
+			while(clustersNode.hasNext()){
+				g.removeVertex(clustersNode.next());
+			}
+		}
+		
+		
+		XMLOutputter xmlOutputter = new XMLOutputter();
+		xmlOutputter.setFormat(Format.getPrettyFormat());
+		try {
+			xmlOutputter.output(logXML, new FileOutputStream(new File(Configuration.instance().getOutputFile())));
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		
+		if(verboseLevel > 0) {
+			gf.addPanel(Util.panelClusters(gclone, clusters));
+			gf.setVisible(true);
+		}
+		return clusters;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/MQI.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/MQI.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/MQI.java	(revision 29)
@@ -0,0 +1,226 @@
+package weka.clusterers.forMetisMQI;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.commons.collections15.Factory;
+import org.apache.commons.collections15.Transformer;
+
+import weka.clusterers.forMetisMQI.graph.Bisection;
+import weka.clusterers.forMetisMQI.graph.Edge;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.Subgraph;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import edu.uci.ics.jung.algorithms.flows.EdmondsKarpMaxFlow;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import weka.clusterers.forMetisMQI.util.Configuration;
+import weka.clusterers.forMetisMQI.util.GraphsFrame;
+import weka.clusterers.forMetisMQI.util.Util;
+
+public class MQI {
+
+	static int i = -1;
+
+	static private Set<Node> DFSReversed(Node currentNode,
+			DirectedGraph<Node, Edge> g, Map<Edge, Number> edgeFlowMap,
+			Set<Node> marked) {
+		Collection<Edge> inEdges = g.getInEdges(currentNode);
+		Set<Node> result = new HashSet<Node>();
+		result.add(currentNode);
+		Iterator<Edge> inEdgesIterator = inEdges.iterator();
+		while (inEdgesIterator.hasNext()) {
+			Edge edge = inEdgesIterator.next();
+			Node src = g.getSource(edge);
+			Edge reverseEdge = g.findEdge(src, currentNode);
+			if (reverseEdge != null && !marked.contains(src)) {
+				int flow = (Integer) edgeFlowMap.get(reverseEdge);
+				int capacity = reverseEdge.getCapacity();
+				if (flow < capacity) {
+					marked.add(src);
+					result.addAll(DFSReversed(src, g, edgeFlowMap, marked));
+				}
+			}
+		}
+		return result;
+	}
+
+	static private Set<Node> BFSReversed(Node sink,
+			DirectedGraph<Node, Edge> g, Map<Edge, Number> edgeFlowMap) {
+		Set<Node> result = new HashSet<Node>();
+		Set<Node> visitedNodes = new HashSet<Node>();
+		Stack<Node> nodesToVisit = new Stack<Node>();
+		result.add(sink);
+		nodesToVisit.push(sink);
+		while (!nodesToVisit.empty()) {
+			Node currentNode = nodesToVisit.pop();
+			visitedNodes.add(currentNode);
+			Collection<Edge> inEdges = g.getInEdges(currentNode);
+			Iterator<Edge> inEdgesIterator = inEdges.iterator();
+			while (inEdgesIterator.hasNext()) {
+				Edge edge = inEdgesIterator.next();
+				Node src = g.getSource(edge);
+				Edge reverseEdge = g.findEdge(src, currentNode);
+				if (reverseEdge != null) {
+					int flow = (Integer) edgeFlowMap.get(reverseEdge);
+					int capacity = reverseEdge.getCapacity();
+					if (flow < capacity) {
+						if (!nodesToVisit.contains(src)
+								&& !visitedNodes.contains(src)) {
+							nodesToVisit.push(src);
+						}
+						result.add(src);
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	static private DirectedGraph<Node, Edge> prepareDirectedGraph(
+			Subgraph A, Node source, Node sink, boolean forConductance) {
+		Bisection bisection = new Bisection(A);
+		Subgraph B = bisection.getComplement();
+		int a = 0;
+		if (!forConductance)
+			a = A.getVertexCount();
+		else {
+//			a = Math.min(B.totalDegree(),A.totalDegree());
+			a = A.totalDegree();
+		}
+		int c = bisection.edgeCut() / 2;
+
+		DirectedGraph<Node, Edge> g = new DirectedSparseGraph<Node, Edge>();
+		Iterator<Node> nodes = A.iterator();
+		while (nodes.hasNext()) {
+			Node u = nodes.next();
+			g.addVertex(u);
+		}
+		nodes = A.iterator();
+		int id = 0;
+		while (nodes.hasNext()) {
+			Node u = nodes.next();
+			Iterator<Node> neighbors = A.getNeighbors(u).iterator();
+			while (neighbors.hasNext()) {
+				Node v = neighbors.next();
+				g.addEdge(new Edge(Integer.toString(id), A.getWeight(u, v), a),
+						u, v);
+				id++;
+			}
+		}
+
+		g.addVertex(source);
+		g.addVertex(sink);
+
+		// build the edges from source to each node of A which previously was
+		// connected
+		// with a node of B.
+		nodes = B.iterator();
+		while (nodes.hasNext()) {
+			Node u = nodes.next();
+			Iterator<Node> neighbors = B.getGraph().getNeighbors(u).iterator();
+			while (neighbors.hasNext()) {
+				Node v = neighbors.next();
+				if (A.contains(v)) {
+					Edge e = g.findEdge(source, v);
+					if (e != null) {
+						e.setCapacity(e.getCapacity() + a);
+					} else {
+						g.addEdge(new Edge(Integer.toString(id), 1, a), source,
+								v);
+						id++;
+					}
+				}
+			}
+		}
+
+		nodes = A.iterator();
+		while (nodes.hasNext()) {
+			Node u = nodes.next();
+			if(forConductance)
+				g.addEdge(new Edge(Integer.toString(id), 1, c * bisection.getGraph().degree(u)), u, sink);
+			else
+				g.addEdge(new Edge(Integer.toString(id), 1, c), u, sink);
+			id++;
+		}
+		return g;
+	}
+
+	/**
+	 * Given a partion of a graph, execute the Max-Flow Quotient-cut Improvement
+	 * algorithm, to find an improved cut and then returns the cluster which
+	 * yields the best quotient cut.
+	 * 
+	 * @param partition
+	 * @return
+	 */
+	static public Set<Node> mqi(Bisection partition, boolean forConductance) {
+//		System.out.println("INITIAL BISECTION: " + partition.toString());
+		UndirectedGraph startingGraph = partition.getGraph();
+		boolean finished = false;
+		Bisection bisection = partition;
+		Set<Node> cluster = new HashSet<Node>(partition.getSmallerSubgraph()
+				.createInducedSubgraph().getVertices());
+//		System.out.println("IMPROVING SUBGRAPH: " + cluster);
+		int maxFlowThreshold = Integer.MAX_VALUE;
+		while (!finished) {
+			Node source = new Node("$$$$S");
+			Node sink = new Node("$$$$T");
+			Subgraph A = new Subgraph(startingGraph,cluster);
+			DirectedGraph<Node, Edge> directedGraph = prepareDirectedGraph(A, source, sink, true);
+			Transformer<Edge, Number> capTransformer = new Transformer<Edge, Number>() {
+				public Double transform(Edge e) {
+					return (double) e.getCapacity();
+				}
+			};
+			Map<Edge, Number> edgeFlowMap = new HashMap<Edge, Number>();
+			i = -1;
+			// This Factory produces new edges for use by the algorithm
+			Factory<Edge> edgeFactory = new Factory<Edge>() {
+				public Edge create() {
+					i++;
+					return new Edge("$$$$" + Integer.toString(i), 1, 1);
+				}
+			};
+			EdmondsKarpMaxFlow<Node, Edge> alg = new EdmondsKarpMaxFlow<Node, Edge>(
+					directedGraph, source, sink, capTransformer, edgeFlowMap,
+					edgeFactory);
+
+			if (!forConductance)
+				maxFlowThreshold = A.getVertexCount()
+						* bisection.edgeCut() / 2;
+			else {
+//				maxFlowThreshold = Math.min(bisection.getLargerSubgraph().totalDegree(), bisection.getSmallerSubgraph().totalDegree());
+				maxFlowThreshold = A.totalDegree();
+				maxFlowThreshold = maxFlowThreshold
+						* (bisection.edgeCut() / 2);
+			}
+			System.out.print(".");
+			alg.evaluate();
+			System.out.print(".");
+			if(Configuration.instance().getVerboseLevel() > 1)
+				GraphsFrame.instance().addPanel(Util.panelFlowGraph(directedGraph, edgeFlowMap));
+			System.out.println("MAX FLOW: " + alg.getMaxFlow() + " THRESHOLD: "
+					+ maxFlowThreshold);
+			if (alg.getMaxFlow() < maxFlowThreshold) {
+				Set<Node> dfsResult = DFSReversed(sink, directedGraph,
+						edgeFlowMap, new HashSet<Node>());
+				dfsResult.remove(sink);
+				cluster = dfsResult;
+				bisection = new Bisection(new Subgraph(
+					bisection.getGraph(), cluster));
+//				System.out.println("REFINED BISECTION: " + bisection.toString());
+				if(Configuration.instance().getVerboseLevel() > 1)
+					GraphsFrame.instance().addPanel(Util.panelCluster(bisection.getGraph().clone(), cluster));
+			} else
+				finished = true;
+		}
+		return cluster;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Uncoarse.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Uncoarse.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/Uncoarse.java	(revision 29)
@@ -0,0 +1,53 @@
+package weka.clusterers.forMetisMQI;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+import weka.clusterers.forMetisMQI.graph.Bisection;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.Subgraph;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import weka.clusterers.forMetisMQI.util.CoarserGraphElement;
+
+public class Uncoarse {
+	
+	private static boolean projectedBelongs(Node u, Bisection partition, CoarserGraphElement cge) {
+		Subgraph s = partition.getSubgraph();
+		Node mappedNode = cge.getMap().get(u);
+		return s.contains(mappedNode);
+	}
+	
+	/**
+	 * Given the projected graph and the partition of the coarser graph, it builds
+	 * the projected partition.
+	 * @param partition
+	 * @param cge
+	 */
+	private static Bisection uncoarseOneStep(Bisection partition, CoarserGraphElement cge) {
+		UndirectedGraph projected = cge.getProjected();
+		Subgraph part = new Subgraph(projected);
+		Iterator<Node> projectedIterator = projected.getVertices().iterator();
+		while(projectedIterator.hasNext()) {
+			Node u = projectedIterator.next();
+			if(projectedBelongs(u,partition,cge))
+				part.addVertex(u);
+		}
+		return new Bisection(part);
+	}
+	
+	/**
+	 * Given a bisection of the finer graph and the stack of graphs which yielded the finer graph,
+	 * it returns the bisection projected into the coarser graph.
+	 * @param stack
+	 * @param partition
+	 * @return
+	 */
+	public static Bisection uncoarse(Stack<CoarserGraphElement> stack, Bisection partition) {
+		while(stack.size() > 0) {
+			CoarserGraphElement element = stack.pop();
+			partition = uncoarseOneStep(partition,element);
+			partition = GraphAlgorithms.KLRefinement(partition);
+		}
+		return partition;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Bisection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Bisection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Bisection.java	(revision 29)
@@ -0,0 +1,159 @@
+package weka.clusterers.forMetisMQI.graph;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import weka.clusterers.forMetisMQI.util.Random;
+
+
+
+public class Bisection {
+	
+	private Subgraph a = null;
+	
+	private Subgraph b = null;
+	
+	private Set<Node> marked = null;
+	
+	private UndirectedGraph g = null;
+
+	private Bisection() {
+	}
+	
+	/**
+	 * Initialize the bisection with a given subgraph.
+	 * @param s
+	 */
+	public Bisection(Subgraph s) {
+		g  = s.getGraph();
+		a = s;
+		b = new Subgraph(g);
+		Iterator<Node> graphIterator =  g.getVertices().iterator();
+		while(graphIterator.hasNext()) {
+			Node u = graphIterator.next();
+			if(!s.contains(u))
+				b.addVertex(u);
+		}
+		marked = new HashSet<Node>();
+	}
+	
+	/**
+	 * Creates a bisection choosing randomly the nodes for each subgraph.
+	 * @param g
+	 */
+	public Bisection(UndirectedGraph g){
+		double limitingProbabilities = 0.1;
+		this.g = g;
+		a = new Subgraph(g);
+		b = new Subgraph(g);
+		Iterator<Node> graph = g.vtxsPermutation().iterator();
+		while(graph.hasNext()) {
+			Node u = graph.next();
+			if(Random.instance().nextDouble() < limitingProbabilities)
+				a.addVertex(u);
+			else
+				b.addVertex(u);
+		}
+		marked = new HashSet<Node>();
+	}
+	
+	public UndirectedGraph getGraph() {
+		return g;
+	}
+	
+	/**
+	 * Returns the node marked as candidate for swapping or <code>null</code> if there aren't node available
+	 * for swapping.
+	 * @return
+	 */
+	public Node getCandidate() {
+		Node u;
+		if(a.getVertexCount() > b.getVertexCount()) {
+			u = a.getCandidate(marked);
+			if(u == null)
+				u = b.getCandidate(marked);
+		} else {
+			u = b.getCandidate(marked);
+			if(u == null)
+				u = a.getCandidate(marked);
+		}
+		if(u != null) {
+			marked.add(u);
+		}
+		return u;
+	}
+	
+	public void swap(Node u) {
+		Subgraph from = fromSubgraph(u);
+		Subgraph to = toSubgraph(u);
+		from.removeVertex(u);
+		to.addVertex(u);
+	}
+	
+	private Subgraph fromSubgraph(Node u) {
+		Subgraph ret = null;
+		if(a.contains(u))
+			ret = a;
+		if(b.contains(u))
+			ret = b;
+		return ret;
+	}
+	
+	private Subgraph toSubgraph(Node u) {
+		Subgraph ret = null;
+		if(!a.contains(u))
+			ret = a;
+		if(!b.contains(u))
+			ret = b;
+		return ret;
+	}
+
+	public int edgeCut() {
+		int acc = a.getExternalDegree() + b.getExternalDegree();
+		return acc;
+	}
+	
+	public Subgraph getSubgraph() {
+		return a;
+	}
+	
+	public Subgraph getComplement() {
+		return b;
+	}
+	
+	public Bisection clone(){
+		Bisection clone = new Bisection();
+		clone.a = (Subgraph) a.clone();
+		clone.b = (Subgraph) b.clone();
+		clone.g = g.clone();
+		clone.marked = new HashSet<Node>();
+		return clone;
+	}
+	
+	@Override
+	public String toString(){
+		String out = a.toString();
+		out = out + "\n";
+		out = out + b.toString();
+		return out;
+	}
+	
+	public Subgraph getLargerSubgraph() {
+		if(a.getVertexCount() < b.getVertexCount())
+			return b;
+		else
+			return a;
+	}
+	
+	/**
+	 * Returns the smaller subgraph of this bisection, null otherwise.
+	 * @return
+	 */
+	public Subgraph getSmallerSubgraph() {
+		if(a.getVertexCount() < b.getVertexCount())
+			return a;
+		else
+			return b;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Edge.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Edge.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Edge.java	(revision 29)
@@ -0,0 +1,65 @@
+package weka.clusterers.forMetisMQI.graph;
+
+public class Edge {
+	
+	private String id;
+	
+	private int weight;
+	
+	private int capacity;
+	
+	public Edge(String id, int weight, int capacity) {
+		this.id = id;
+		this.weight = weight;
+		this.capacity = capacity;
+	}
+	
+	@Override
+	public int hashCode() {
+		int hash = 1;
+		hash = hash * 31 + id.hashCode();
+		return hash;
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		boolean result = (o instanceof Edge);
+		if(result) {
+			Edge e = (Edge) o;
+			result = result && (e.getId().equals(id));
+		}
+		return result;
+	}
+
+	public String getId() {
+		return id;
+	}
+	
+	public int getWeight() {
+		return weight;
+	}
+	
+	public int getCapacity() {
+		return capacity;
+	}
+	
+	@Override
+	public String toString() {
+		return Integer.toString(capacity);
+	}
+
+	public void setWeight(int weight) {
+		this.weight = weight;
+	}
+
+	public void setCapacity(int capacity) {
+		this.capacity = capacity;
+	}
+	
+	@Override
+	public Edge clone(){
+		Edge e = new Edge(id, weight, capacity);
+		return e;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Node.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Node.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Node.java	(revision 29)
@@ -0,0 +1,63 @@
+package weka.clusterers.forMetisMQI.graph;
+
+
+public class Node {
+
+	private String id;
+
+	/** The weight of the node */
+	private int vwgt;
+	/** The weight of the edges that have been contracted to create the node */
+	private int cewgt;
+	public Node(String id) {
+		this.id = id;
+		this.vwgt = 1;
+		this.cewgt = 0;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return (o instanceof Node) && (((Node) o).getId().equals(id));
+	}
+
+	@Override
+	public int hashCode() {
+		int hash = 1;
+		hash = hash * 31 + id.hashCode();
+		return hash;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	@Override
+	public String toString() {
+		return id; //+ " Cewgt: " + cewgt;
+	}
+
+	public int getVwgt() {
+		return vwgt;
+	}
+
+	public void setVwgt(int vwgt) {
+		this.vwgt = vwgt;
+	}
+
+	public int getCewgt() {
+		return cewgt;
+	}
+
+	public void setCewgt(int cewgt) {
+		this.cewgt = cewgt;
+	}
+
+	@Override
+	public Node clone() {
+		Node n = new Node(id);
+		n.cewgt = cewgt;
+		n.vwgt = vwgt;
+		return n;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Subgraph.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Subgraph.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/Subgraph.java	(revision 29)
@@ -0,0 +1,272 @@
+package weka.clusterers.forMetisMQI.graph;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.Map.Entry;
+
+import edu.uci.ics.jung.algorithms.filters.FilterUtils;
+
+
+public class Subgraph {
+	
+	private UndirectedGraph g = null;
+	
+	private Set<Node> nodes = null;
+	
+	private HashMap<Node,Integer> ID = null;
+	private HashMap<Node,Integer> ED = null;
+	private HashMap<Integer,List<Node>> bucketGain = null;
+	private SortedSet<Integer> gainSet = null;
+	private boolean recomputeGain = true;
+	
+	public Subgraph(UndirectedGraph g){
+		this.g = g;
+		nodes = new HashSet<Node>();
+		ID = new HashMap<Node,Integer>();
+		ED = new HashMap<Node,Integer>();
+		bucketGain = new HashMap<Integer, List<Node>>();
+		gainSet = new TreeSet<Integer>();
+	}
+	
+	public Subgraph(UndirectedGraph g, Set<Node> nodes) {
+		this.g = g;
+		this.nodes = new HashSet<Node>();
+		this.ID = new HashMap<Node,Integer>();
+		this.ED = new HashMap<Node,Integer>();
+		this.bucketGain = new HashMap<Integer, List<Node>>();
+		this.gainSet = new TreeSet<Integer>();
+		Iterator<Node> nodesIterator = nodes.iterator();
+		while(nodesIterator.hasNext()) {
+			addVertex(nodesIterator.next());
+		}
+	}
+	
+	public UndirectedGraph getGraph() {
+		return g;
+	}
+	
+	/**
+	 * Adds to the subgraph the node u iff u belongs to the graph.
+	 * @param u
+	 */
+	public void addVertex(Node u) {
+		if(g.containsVertex(u)) {
+			nodes.add(u);
+			ID.put(u, 0);
+			ED.put(u, 0);
+			recomputeGain = true;
+		}
+	}
+	
+	private void computeDegree() {
+		Iterator<Node> subgraphIterator = nodes.iterator();
+		while(subgraphIterator.hasNext()) {
+			Node u = subgraphIterator.next();
+			Iterator<Node> nborsIterator = g.getNeighbors(u).iterator();
+			int newID = 0;
+			int newED = 0;
+			while(nborsIterator.hasNext()) {
+				Node v = nborsIterator.next();
+				if(contains(v))
+					newID = newID + g.findEdge(u, v).getWeight();
+				else
+					newED = newED + g.findEdge(u, v).getWeight();
+			}
+			ID.put(u, newID);
+			ED.put(u, newED);
+		}
+	}
+	
+	private void computeGain() {
+		bucketGain.clear();
+		gainSet.clear();
+		ID.clear();
+		ED.clear();
+		computeDegree();
+		Iterator<Node> subgraphIterator = nodes.iterator();
+		while(subgraphIterator.hasNext()) {
+			Node u = subgraphIterator.next();
+			int gainU = ED.get(u) - ID.get(u);
+			if(!bucketGain.containsKey(gainU)) {
+				bucketGain.put(gainU, new ArrayList<Node>());
+			}
+			bucketGain.get(gainU).add(u);
+			gainSet.add(gainU);
+		}
+		recomputeGain = false;
+	}
+	
+	public Node getCandidate() {
+		return getCandidate(new HashSet<Node>());
+	}
+	
+	/**
+	 * 
+	 * @param marked 
+	 * @return the candidate node for swap or <code>null</code> if there aren't available nodes.
+	 */
+	public Node getCandidate(Set<Node> marked) {
+		if(recomputeGain)
+			computeGain();
+		Node candidate = null;
+		Iterator<Integer> iterator = ((TreeSet<Integer>)gainSet).descendingIterator();
+		while(iterator.hasNext() && (candidate == null)) {
+			int gain = iterator.next();
+			Iterator<Node> nodes = bucketGain.get(gain).iterator();
+			while(nodes.hasNext() && (candidate == null)) {
+				Node u = nodes.next();
+				if(!marked.contains(u))
+					candidate = u;
+			}
+		}
+		return candidate;
+	}
+	
+	public int gain(Node u){
+		if(recomputeGain)
+			computeGain();
+		return ED.get(u) - ID.get(u);
+	}
+	
+	public void removeVertex(Node u) {
+		if(recomputeGain)
+			computeGain();
+		int gainU = gain(u);
+		nodes.remove(u);
+		ID.remove(u);
+		ED.remove(u);
+		List<Node> l = bucketGain.get(gainU);
+		l.remove(l.indexOf(u));
+		if(l.size() == 0) {
+			bucketGain.remove(gainU);
+			gainSet.remove(gainU);
+		}
+		recomputeGain = true;
+	}
+	
+	public int getVertexCount() {
+		return nodes.size();
+	}
+	
+	public Edge findEdge(Node v1, Node v2) {
+		Edge e = null;
+		if(contains(v1) && contains(v2))
+			e = g.findEdge(v1, v2);
+		return e;
+	}
+	
+	public int getWeight(Node u, Node v) {
+		int ret = -1;
+		if(containsEdge(u,v))
+			ret = g.findEdge(u, v).getWeight();
+		return ret;
+	}
+	
+	public Iterator<Node> iterator() {
+		return nodes.iterator();
+	}
+	
+	public boolean containsEdge(Node u, Node v) {
+		return contains(u) && contains(v) && g.containsEdge(u, v);
+	}
+	
+	public boolean contains(Node u) {
+		return nodes.contains(u);
+	}
+	
+	public List<Node> getNeighbors(Node u) {
+		List<Node> neighbors = new ArrayList<Node>();
+		Iterator<Node> iterator = g.getNeighbors(u).iterator(); 
+		while(iterator.hasNext()) {
+			Node v = iterator.next();
+			if(contains(v) && containsEdge(u, v))
+				neighbors.add(v);
+		}
+		return neighbors;
+	}
+	
+	public int getExternalDegree() {
+		if(recomputeGain)
+			computeGain();
+		int acc = 0;
+		Iterator<Integer> it = ED.values().iterator();
+		while(it.hasNext())
+			acc = acc + it.next();
+		return acc;
+	}
+	
+	@Override
+	public String toString() {
+		if(getVertexCount() == 0)
+			return "[]";
+		StringBuffer out =new StringBuffer("[");
+		Iterator<Node> it = nodes.iterator();
+		while(it.hasNext()) {
+			Node u = it.next();
+			out.append(u.toString() + ",");
+		}
+		out.setLength(out.length() - 1);
+		out.append("]");
+		return out.toString();
+	}
+	
+	public List<Node> getBoundary() {
+		if(recomputeGain)
+			computeGain();
+		Iterator<Entry<Node,Integer>> EDIterator = ED.entrySet().iterator();
+		List<Node> boundary = new ArrayList<Node>();
+		while(EDIterator.hasNext()) {
+			Entry<Node,Integer> entry = EDIterator.next();
+			if(entry.getValue() > 0)
+				boundary.add(entry.getKey());
+		}
+		return boundary;
+	}
+	
+	private Subgraph() {
+		
+	}
+	
+	@Override
+	public Subgraph clone() {
+		Subgraph clone = new Subgraph();
+		clone.g = g.clone();
+		
+		clone.nodes = new HashSet<Node>();
+		Iterator<Node> graphIterator =clone.g.getVertices().iterator();
+		while(graphIterator.hasNext()) {
+			Node u = graphIterator.next();
+			if(contains(u))
+				clone.nodes.add(u);
+		}
+		
+		clone.bucketGain = new HashMap<Integer, List<Node>>();
+		clone.gainSet = new TreeSet<Integer>();
+		clone.ID = new HashMap<Node, Integer>();
+		clone.ED = new HashMap<Node, Integer>();
+		
+		clone.computeGain();
+		
+		return clone;
+	}
+	
+	public UndirectedGraph createInducedSubgraph() {
+		return FilterUtils.createInducedSubgraph(nodes,g);
+	}
+	
+	public int totalDegree() {
+		int result = 0;
+		Iterator<Node> nodeIterator = iterator();
+		while(nodeIterator.hasNext()) {
+			int degree = g.degree(nodeIterator.next());
+			result += degree;
+		}
+		return result;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/UndirectedGraph.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/UndirectedGraph.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/graph/UndirectedGraph.java	(revision 29)
@@ -0,0 +1,151 @@
+package weka.clusterers.forMetisMQI.graph;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeMap;
+
+import weka.clusterers.forMetisMQI.util.Random;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public class UndirectedGraph extends UndirectedSparseGraph<Node, Edge> {
+	
+	private static final long serialVersionUID = 1L;
+
+	public List<Node> vtxsPermutation() {
+		Random r = Random.instance(); 
+		List<Node> perm = new ArrayList<Node>();
+		Iterator<Node> vtxsIterator = getVertices().iterator();
+		while(vtxsIterator.hasNext()) {
+			Node node = vtxsIterator.next();
+			perm.add(node);
+		}
+		for (int i = 0; i < perm.size(); i++) {
+			int k = r.nextInt(perm.size());
+			Node swap = perm.get(k);
+			perm.set(k, perm.get(i));
+			perm.set(i, swap);
+		}
+		return perm;
+	}
+	
+	public boolean containsEdge(Node v1, Node v2) {
+		return (findEdge(v1, v2) != null);
+	}
+	
+	public Node findVertex(Node v1) {
+		Iterator<Node> graphIterator = getVertices().iterator();
+		while(graphIterator.hasNext()) {
+			Node v2 = graphIterator.next();
+			if(v1.equals(v2)) return v2;
+		}
+		return null;
+	}
+	
+	public Collection<Node> getNeighborsPermutation(Node n) {
+		Random r = Random.instance();
+		ArrayList<Node> perm = new ArrayList<Node>();
+		Iterator<Node> vtxsIterator = getNeighbors(n).iterator();
+		while(vtxsIterator.hasNext()) {
+			Node node = vtxsIterator.next();
+			perm.add(node);
+		}
+		for (int i = 0; i < perm.size(); i++) {
+			int k = r.nextInt(perm.size());
+			Node swap = perm.get(k);
+			perm.set(k, perm.get(i));
+			perm.set(i, swap);
+		}
+		return perm;
+	}
+	
+	public void loadFromInstance(Instances data) {
+		Iterator<Instance> dataIterator = data.iterator();
+		Attribute from = data.attribute("from");
+		Attribute to = data.attribute("to");
+		if (from == null || to == null)
+			throw new RuntimeException(
+					"Unsupported data: check the list of attributes (from and to are needed).");
+		int edgeId = 0;
+		while (dataIterator.hasNext()) {
+			Instance edge = dataIterator.next();
+			Node node1 = new Node(edge.stringValue(from));
+			Node node2 = new Node(edge.stringValue(to));
+			addVertex(node1);
+			addVertex(node2);
+			addEdge(new Edge(Integer.toString(edgeId),1,1),node1,node2);
+			edgeId++;
+		}
+	}
+	
+	@Override
+	public UndirectedGraph clone() {
+		UndirectedGraph g = new UndirectedGraph();
+		Iterator<Node> nodesIterator = getVertices().iterator();
+		while(nodesIterator.hasNext()) {
+			g.addVertex(nodesIterator.next().clone());
+		}
+		Iterator<Edge> edgesIterator = getEdges().iterator();
+		while(edgesIterator.hasNext()) {
+			Edge e = edgesIterator.next();
+			g.addEdge(e.clone(), getEndpoints(e));
+		}
+		return g;
+	}
+	
+	public int getAdjcncyWeight(Node v1){
+		Iterator<Node> nbsIterator = getNeighbors(v1).iterator();
+		int adjcncyWgt = 0;
+		while(nbsIterator.hasNext()) {
+			Node v2 = nbsIterator.next();
+			Edge edge = findEdge(v1, v2);
+			adjcncyWgt += edge.getWeight();
+		}
+		return adjcncyWgt;
+	}
+	
+	public void printForMetis(PrintStream output) {
+		TreeMap<Integer,Node> map = new TreeMap<Integer,Node>();
+		HashMap<Node,Integer> reverseMap = new HashMap<Node,Integer>();
+		Iterator<Node> nodesIterator = getVertices().iterator();
+		int id = 1;
+		while(nodesIterator.hasNext()) {
+			Node node = nodesIterator.next();
+			map.put(id, node);
+			reverseMap.put(node,id);
+			id++;
+		}
+		output.println(getVertexCount() + " "+ getEdgeCount());
+		Iterator<Integer> mappedIterator = map.keySet().iterator();
+		while(mappedIterator.hasNext()) {
+			id = mappedIterator.next();
+			Iterator<Node> neighbors = getNeighbors(map.get(id)).iterator();
+			while(neighbors.hasNext()){
+				output.print(reverseMap.get(neighbors.next()));
+				if(neighbors.hasNext()) output.print(" ");
+			}
+			output.println();
+		}
+	}
+	
+	public String toString() {
+		StringBuffer sb = new StringBuffer("Vertices:");
+    	for(Node v : getVertices()) {
+    		sb.append(v+ " Adjw: "+ getAdjcncyWeight(v) + ",");
+    	}
+    	sb.setLength(sb.length()-1);
+    	sb.append("\nEdges:");
+    	for(Edge e : getEdges()) {
+    		Pair<Node> ep = getEndpoints(e);
+    		sb.append(e+"["+ep.getFirst()+","+ep.getSecond()+"] ");
+    	}
+        return sb.toString();
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/CoarserGraphElement.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/CoarserGraphElement.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/CoarserGraphElement.java	(revision 29)
@@ -0,0 +1,38 @@
+package weka.clusterers.forMetisMQI.util;
+
+import java.util.Map;
+
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+
+public class CoarserGraphElement {
+	
+	private UndirectedGraph contracted;
+	private UndirectedGraph projected;
+	private Map<Node,Node> match;
+	private Map<Node,Node> map;
+	
+	public CoarserGraphElement(UndirectedGraph contracted, UndirectedGraph projected, Map<Node,Node> match, Map<Node,Node> map) {
+		this.contracted = contracted;
+		this.projected = projected;
+		this.match = match;
+		this.map = map;
+	}
+	
+	public Map<Node,Node> getMatch() {
+		return match;
+	}
+	
+	public Map<Node,Node> getMap() {
+		return map;
+	}
+	
+	public UndirectedGraph getContracted() {
+		return contracted;
+	}
+	
+	public UndirectedGraph getProjected() {
+		return projected;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Configuration.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Configuration.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Configuration.java	(revision 29)
@@ -0,0 +1,69 @@
+package weka.clusterers.forMetisMQI.util;
+
+public class Configuration {
+	
+	private boolean randomBisection = false;
+	
+	private int verboseLevel = 1;
+	
+	private int sizeFinerGraph = 100;
+	
+	private int numberOfClusters = 2;
+	
+	private String outputFile = null;
+	
+	public String getOutputFile() {
+		return outputFile;
+	}
+
+	public void setOutputFile(String outputFile) {
+		this.outputFile = outputFile;
+	}
+
+	private static Configuration instance = null;
+	
+	private Configuration() {
+		
+	}
+	
+	public static Configuration instance() {
+		if(instance == null)
+			instance = new Configuration();
+		return instance;
+	}
+
+	public boolean isRandomBisection() {
+		return randomBisection;
+	}
+
+	public void setRandomBisection(boolean randomBisection) {
+		this.randomBisection = randomBisection;
+	}
+
+	public int getVerboseLevel() {
+		return verboseLevel;
+	}
+
+	public void setVerboseLevel(int verboseLevel) {
+		this.verboseLevel = verboseLevel;
+	}
+
+	public int getSizeFinerGraph() {
+		return sizeFinerGraph;
+	}
+
+	public void setSizeFinerGraph(int sizeFinerGraph) {
+		this.sizeFinerGraph = sizeFinerGraph;
+	}
+
+	public int getNumberOfClusters() {
+		return numberOfClusters;
+	}
+
+	public void setNumberOfClusters(int numberOfClusters) {
+		this.numberOfClusters = numberOfClusters;
+	}
+	
+	
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/ContractedVertexTransformer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/ContractedVertexTransformer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/ContractedVertexTransformer.java	(revision 29)
@@ -0,0 +1,43 @@
+package weka.clusterers.forMetisMQI.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.apache.commons.collections15.Transformer;
+
+import weka.clusterers.forMetisMQI.graph.Node;
+
+public class ContractedVertexTransformer implements Transformer<Node, String> {
+	Map<Node,Set<Node>> mapContracted = null;
+
+	public ContractedVertexTransformer(CoarserGraphElement cge) {
+		mapContracted = new HashMap<Node,Set<Node>>();
+		Iterator<Entry<Node,Node>> entryIterator = cge.getMap().entrySet().iterator();
+		while(entryIterator.hasNext()) {
+			Entry<Node, Node> entry = entryIterator.next();
+			Node prevNode = entry.getKey();
+			Node mappedNode = entry.getValue();
+			if(!mapContracted.containsKey(mappedNode))
+				mapContracted.put(mappedNode, new HashSet<Node>());
+			mapContracted.get(mappedNode).add(prevNode);
+		}
+	}
+	
+	public String transform(Node n) {
+		StringBuffer buffer = new StringBuffer();
+		Iterator<Node> contracted = mapContracted.get(n).iterator();
+		buffer.append("[");
+		while(contracted.hasNext()) {
+			buffer.append(contracted.next().getId() + ", ");
+		}
+		if(buffer.length() > 1)
+			buffer.setLength(buffer.length() - 2);
+		buffer.append("]");
+		return buffer.toString();
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/GraphsFrame.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/GraphsFrame.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/GraphsFrame.java	(revision 29)
@@ -0,0 +1,79 @@
+package weka.clusterers.forMetisMQI.util;
+
+import java.awt.CardLayout;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+public class GraphsFrame extends JFrame {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	private static GraphsFrame instance = null;
+
+	private int numberOfPanels = 0;
+
+	private JPanel graphPanel = null;
+	private JPanel buttonPanel = null;
+	private JButton nextButton = null;
+	private JButton prevButton = null;
+	
+	public static GraphsFrame instance() {
+		if(instance == null) {
+			instance = new GraphsFrame("Graphs Frame");
+		}
+		return instance;
+	}
+	
+	private GraphsFrame() {
+		
+	}
+
+	private GraphsFrame(String name) {
+		super(name);
+		setBounds(new Rectangle(800, 700));
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+
+		graphPanel = new JPanel();
+		graphPanel.setName("graphPanel");
+		graphPanel.setBounds(0,40,800, 660);
+		graphPanel.setLayout(new CardLayout());
+		getContentPane().add(graphPanel);
+
+		buttonPanel = new JPanel();
+		nextButton = new JButton(">>");
+
+		nextButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				CardLayout c = (CardLayout) graphPanel.getLayout();
+				c.next(graphPanel);
+			}
+		});
+		prevButton = new JButton("<<");
+		prevButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				CardLayout c = (CardLayout) graphPanel.getLayout();
+				c.previous(graphPanel);
+			}
+		});
+		buttonPanel.setBounds(0, 0, 800, 40);
+		buttonPanel.add(prevButton);
+		buttonPanel.add(nextButton);
+		getContentPane().add(buttonPanel);
+	}
+
+	public void addPanel(JPanel panel) {
+		graphPanel.add(panel, Integer.toString(numberOfPanels));
+		numberOfPanels++;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Random.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Random.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Random.java	(revision 29)
@@ -0,0 +1,31 @@
+package weka.clusterers.forMetisMQI.util;
+
+public class Random extends java.util.Random {
+
+	private static final long serialVersionUID = 1L;
+	
+	private static Random instance = null;
+	
+	private long seed = 1234567890;
+	
+	/**
+	 * Return an instance of a random generator with a random seed.
+	 * @return
+	 */
+	public static Random instance() {
+		if(instance == null) {
+			instance = new Random(/*new java.util.Random().nextLong()*/1234567890);
+		}
+		return instance;
+	}
+	
+	public long getSeed() {
+		return seed;
+	}
+	
+	private Random(long seed) {
+		super(seed);
+		this.seed = seed;
+	}
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Util.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Util.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forMetisMQI/util/Util.java	(revision 29)
@@ -0,0 +1,181 @@
+package weka.clusterers.forMetisMQI.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.swing.JPanel;
+
+import org.apache.commons.collections15.Transformer;
+
+import weka.clusterers.forMetisMQI.graph.Edge;
+import weka.clusterers.forMetisMQI.graph.Node;
+import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+
+public class Util {
+	
+	public static JPanel panelCluster(Graph<Node, Edge> g, Collection<Node> cluster) {
+		Set<Node> setNodes = new HashSet<Node>();
+		setNodes.addAll(cluster);
+		Set<Set<Node>> clusters = new HashSet<Set<Node>>();
+		clusters.add(setNodes);
+		return panelClusters(g, clusters);
+	}
+	
+	public static JPanel panelClusters(Graph<Node, Edge> g, Set<Set<Node>> clusters) {
+		return panelClusters(g,clusters,new ToStringLabeller<Node>());
+	}
+	
+	public static JPanel panelClusters(Graph<Node, Edge> g, Set<Set<Node>> clusters, Transformer<Node,String> transformer) {
+		Layout<Node, Edge> layout = new KKLayout<Node, Edge>(g);
+		layout.setSize(new Dimension(800, 600)); // sets the initial size of the space
+		// The BasicVisualizationServer<V,E> is parameterized by the edge types
+		BasicVisualizationServer<Node, Edge> vv = new BasicVisualizationServer<Node, Edge>(
+				layout);
+
+		class VertexPaintTransformer implements Transformer<Node, Paint> {
+			Set<Set<Node>> clusters = null;
+			Map<Set<Node>, Color> clustersColor = null;
+
+			public Set<Node> getCluster(Node node) {
+				Iterator<Set<Node>> clusterIterator = clusters.iterator();
+				while (clusterIterator.hasNext()) {
+					Set<Node> cluster = clusterIterator.next();
+					if (cluster.contains(node))
+						return cluster;
+				}
+				return null;
+			}
+
+			public VertexPaintTransformer(Set<Set<Node>> clusters) {
+				this.clusters = clusters;
+				clustersColor = new HashMap<Set<Node>, Color>(clusters.size());
+				Iterator<Set<Node>> clusterIterator = clusters.iterator();
+				while (clusterIterator.hasNext()) {
+					Set<Node> cluster = clusterIterator.next();
+					clustersColor.put(cluster, new Color(Random.instance()
+							.nextInt(256), Random.instance().nextInt(256),
+							Random.instance().nextInt(256)));
+				}
+			}
+
+			public Paint transform(Node i) {
+				Set<Node> cluster = getCluster(i);
+				if (cluster == null)
+					return Color.RED;
+				else
+					return clustersColor.get(getCluster(i));
+			}
+		}
+
+		Transformer<Node, Paint> vertexPaint = new VertexPaintTransformer(
+				clusters);
+		vv.setPreferredSize(new Dimension(800, 600)); // Sets the viewing area
+														// size
+		vv.getRenderContext().setVertexLabelTransformer(transformer);
+		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaint);
+		return vv;
+	}
+	
+	public static JPanel panelGraph(Graph<Node, Edge> g){
+		Layout<Node, Edge> layout = new FRLayout<Node, Edge>(g);
+		layout.setSize(new Dimension(800,600)); // sets the initial size of the space
+		// The BasicVisualizationServer<V,E> is parameterized by the edge types
+		BasicVisualizationServer<Node,Edge> vv =
+		new BasicVisualizationServer<Node,Edge>(layout);
+		vv.setPreferredSize(new Dimension(800,600)); //Sets the viewing area size
+		vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Node>());
+		vv.getRenderContext().setEdgeLabelTransformer(
+				new Transformer<Edge, String>() {
+					public String transform(Edge e) {
+						return "";
+					}
+				});
+		return vv;
+	}
+	
+	public static JPanel panelFlowGraph(Graph<Node, Edge> g, Map<Edge, Number> edgeFlowMap){
+		class EdgeTransformer implements Transformer<Edge,String> {
+			Map<Edge,Number> edgeFlowMap = null;
+			public String transform(Edge edge){
+				return edgeFlowMap.get(edge) + "/" + edge.getCapacity();
+			}
+			public EdgeTransformer(Map<Edge,Number> edgeFlowMap) {
+				this.edgeFlowMap = edgeFlowMap;
+			}
+		}
+		Layout<Node, Edge> layout = new FRLayout<Node, Edge>(g);
+		layout.setSize(new Dimension(800,600)); // sets the initial size of the space
+		// The BasicVisualizationServer<V,E> is parameterized by the edge types
+		BasicVisualizationServer<Node,Edge> vv =
+		new BasicVisualizationServer<Node,Edge>(layout);
+		vv.setPreferredSize(new Dimension(800,600)); //Sets the viewing area size
+		vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Node>());
+		vv.getRenderContext().setEdgeLabelTransformer(new EdgeTransformer(edgeFlowMap));
+		return vv;
+	}
+	
+	static public JPanel panelContractedGraph(CoarserGraphElement cge, Collection<Node> cluster) {
+		Set<Node> setNodes = new HashSet<Node>();
+		setNodes.addAll(cluster);
+		Set<Set<Node>> clusters = new HashSet<Set<Node>>();
+		clusters.add(setNodes);
+		return panelClusters(cge.getContracted(), clusters, new ContractedVertexTransformer(cge));
+	}
+	
+	static public JPanel panelContractedGraph(CoarserGraphElement cge) {
+		Layout<Node, Edge> layout = new KKLayout<Node, Edge>(cge.getContracted());
+		layout.setSize(new Dimension(800,600)); // sets the initial size of the space
+		// The BasicVisualizationServer<V,E> is parameterized by the edge types
+		BasicVisualizationServer<Node,Edge> vv =
+		new BasicVisualizationServer<Node,Edge>(layout);
+		vv.setPreferredSize(new Dimension(800,600)); //Sets the viewing area size
+		vv.getRenderContext().setVertexLabelTransformer(new ContractedVertexTransformer(cge));
+		return vv;
+	}
+
+	
+	/**
+	 * Generates a small graph with 100 nodes and two big components.
+	 * For testing purpose.
+	 * @return the generated graph
+	 */
+	public UndirectedGraph generateGraph(){
+		UndirectedGraph g = new UndirectedGraph();
+		for (int i = 0; i < 50; i++) {
+			g.addVertex(new Node(Integer.toString(i)));
+		}
+		for (int j = 0; j < 120; j++) {
+			g.addEdge(new Edge(Integer.toString(j), 1, 1), new Node(Integer
+					.toString(Random.instance().nextInt(50))), new Node(Integer
+					.toString(Random.instance().nextInt(50))));
+		}
+		for (int i = 50; i < 100; i++) {
+			g.addVertex(new Node(Integer.toString(i)));
+		}
+		for (int j = 120; j < 240; j++) {
+			g.addEdge(new Edge(Integer.toString(j), 1, 1), new Node(Integer
+					.toString(50 + Random.instance().nextInt(50))), new Node(
+					Integer.toString(50 + Random.instance().nextInt(50))));
+		}
+		for (int j = 240; j < 250; j++) {
+			g.addEdge(new Edge(Integer.toString(j), 1, 1), new Node(Integer
+					.toString(50 + Random.instance().nextInt(50))), new Node(
+					Integer.toString(Random.instance().nextInt(50))));
+		}
+		return g;
+	}
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/DataObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/DataObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/DataObject.java	(revision 29)
@@ -0,0 +1,129 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.DataObjects;
+
+import weka.core.Instance;
+
+/**
+ * <p>
+ * DataObject.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 19, 2004 <br/>
+ * Time: 5:48:59 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.2 $
+ */
+public interface DataObject {
+
+    static final int UNCLASSIFIED = -1;
+    static final int NOISE = Integer.MIN_VALUE;
+    static final double UNDEFINED = Integer.MAX_VALUE;
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Compares two DataObjects in respect to their attribute-values
+     * @param dataObject The DataObject, that is compared with this.dataObject
+     * @return Returns true, if the DataObjects correspond in each value, else returns false
+     */
+    boolean equals(DataObject dataObject);
+
+    /**
+     * Calculates the distance between dataObject and this.dataObject
+     * @param dataObject The DataObject, that is used for distance-calculation with this.dataObject
+     * @return double-value The distance between dataObject and this.dataObject
+     */
+    double distance(DataObject dataObject);
+
+    /**
+     * Returns the original instance
+     * @return originalInstance
+     */
+    Instance getInstance();
+
+    /**
+     * Returns the key for this DataObject
+     * @return key
+     */
+    String getKey();
+
+    /**
+     * Sets the key for this DataObject
+     * @param key The key is represented as string
+     */
+    void setKey(String key);
+
+    /**
+     * Sets the clusterID (cluster), to which this DataObject belongs to
+     * @param clusterID Number of the Cluster
+     */
+    void setClusterLabel(int clusterID);
+
+    /**
+     * Returns the clusterID, to which this DataObject belongs to
+     * @return clusterID
+     */
+    int getClusterLabel();
+
+    /**
+     * Marks this dataObject as processed
+     * @param processed True, if the DataObject has been already processed, false else
+     */
+    void setProcessed(boolean processed);
+
+    /**
+     * Gives information about the status of a dataObject
+     * @return True, if this dataObject has been processed, else false
+     */
+    boolean isProcessed();
+
+    /**
+     * Sets a new coreDistance for this dataObject
+     * @param c_dist coreDistance
+     */
+    void setCoreDistance(double c_dist);
+
+    /**
+     * Returns the coreDistance for this dataObject
+     * @return coreDistance
+     */
+    double getCoreDistance();
+
+    /**
+     * Sets a new reachability-distance for this dataObject
+     */
+    void setReachabilityDistance(double r_dist);
+
+    /**
+     * Returns the reachabilityDistance for this dataObject
+     */
+    double getReachabilityDistance();
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/EuclidianDataObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/EuclidianDataObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/EuclidianDataObject.java	(revision 29)
@@ -0,0 +1,301 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.DataObjects;
+
+import weka.clusterers.forOPTICSAndDBScan.Databases.Database;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * EuclidianDataObject.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 19, 2004 <br/>
+ * Time: 5:50:22 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 5987 $
+ */
+public class EuclidianDataObject
+    implements DataObject, Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4408119914898291075L;
+
+    /**
+     * Holds the original instance
+     */
+    private Instance instance;
+
+    /**
+     * Holds the (unique) key that is associated with this DataObject
+     */
+    private String key;
+
+    /**
+     * Holds the ID of the cluster, to which this DataObject is assigned
+     */
+    private int clusterID;
+
+    /**
+     * Holds the status for this DataObject (true, if it has been processed, else false)
+     */
+    private boolean processed;
+
+    /**
+     * Holds the coreDistance for this DataObject
+     */
+    private double c_dist;
+
+    /**
+     * Holds the reachabilityDistance for this DataObject
+     */
+    private double r_dist;
+
+    /**
+     * Holds the database, that is the keeper of this DataObject
+     */
+    private Database database;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Constructs a new DataObject. The original instance is kept as instance-variable
+     * @param originalInstance the original instance
+     */
+    public EuclidianDataObject(Instance originalInstance, String key, Database database) {
+        this.database = database;
+        this.key = key;
+        instance = originalInstance;
+        clusterID = DataObject.UNCLASSIFIED;
+        processed = false;
+        c_dist = DataObject.UNDEFINED;
+        r_dist = DataObject.UNDEFINED;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Compares two DataObjects in respect to their attribute-values
+     * @param dataObject The DataObject, that is compared with this.dataObject
+     * @return Returns true, if the DataObjects correspond in each value, else returns false
+     */
+    public boolean equals(DataObject dataObject) {
+        if (this == dataObject) return true;
+        if (!(dataObject instanceof EuclidianDataObject)) return false;
+
+        final EuclidianDataObject euclidianDataObject = (EuclidianDataObject) dataObject;
+
+        if (getInstance().equalHeaders(euclidianDataObject.getInstance())) {
+            for (int i = 0; i < getInstance().numValues(); i++) {
+                double i_value_Instance_1 = getInstance().valueSparse(i);
+                double i_value_Instance_2 = euclidianDataObject.getInstance().valueSparse(i);
+
+                if (i_value_Instance_1 != i_value_Instance_2) return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Calculates the euclidian-distance between dataObject and this.dataObject
+     * @param dataObject The DataObject, that is used for distance-calculation with this.dataObject
+     * @return double-value The euclidian-distance between dataObject and this.dataObject
+     *                      NaN, if the computation could not be performed
+     */
+    public double distance(DataObject dataObject) {
+        double dist = 0.0;
+
+        if (!(dataObject instanceof EuclidianDataObject)) return Double.NaN;
+
+        if (getInstance().equalHeaders(dataObject.getInstance())) {
+            for (int i = 0; i < getInstance().numValues(); i++) {
+                double cDistance = computeDistance(getInstance().index(i),
+                        getInstance().valueSparse(i),
+                        dataObject.getInstance().valueSparse(i));
+                dist += Math.pow(cDistance, 2.0);
+            }
+            return Math.sqrt(dist);
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Performs euclidian-distance-calculation between two given values
+     * @param index of the attribute within the DataObject's instance
+     * @param v value_1
+     * @param v1 value_2
+     * @return double norm-distance between value_1 and value_2
+     */
+    private double computeDistance(int index, double v, double v1) {
+        switch (getInstance().attribute(index).type()) {
+            case Attribute.NOMINAL:
+                return (Utils.isMissingValue(v) || Utils.isMissingValue(v1)
+                        || ((int) v != (int) v1)) ? 1 : 0;
+
+            case Attribute.NUMERIC:
+                if (Utils.isMissingValue(v) || Utils.isMissingValue(v1)) {
+                    if (Utils.isMissingValue(v) && Utils.isMissingValue(v1))
+                        return 1;
+                    else {
+                        return (Utils.isMissingValue(v)) ? norm(v1, index)
+                                : norm(v, index);
+                    }
+                } else
+                    return norm(v, index) - norm(v1, index);
+
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Normalizes a given value of a numeric attribute.
+     *
+     * @param x the value to be normalized
+     * @param i the attribute's index
+     */
+    private double norm(double x, int i) {
+        if (Double.isNaN(database.getAttributeMinValues()[i])
+                || Utils.eq(database.getAttributeMaxValues()[i], database.getAttributeMinValues()[i])) {
+            return 0;
+        } else {
+            return (x - database.getAttributeMinValues()[i]) /
+                    (database.getAttributeMaxValues()[i] - database.getAttributeMinValues()[i]);
+        }
+    }
+
+    /**
+     * Returns the original instance
+     * @return originalInstance
+     */
+    public Instance getInstance() {
+        return instance;
+    }
+
+    /**
+     * Returns the key for this DataObject
+     * @return key
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Sets the key for this DataObject
+     * @param key The key is represented as string
+     */
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    /**
+     * Sets the clusterID (cluster), to which this DataObject belongs to
+     * @param clusterID Number of the Cluster
+     */
+    public void setClusterLabel(int clusterID) {
+        this.clusterID = clusterID;
+    }
+
+    /**
+     * Returns the clusterID, to which this DataObject belongs to
+     * @return clusterID
+     */
+    public int getClusterLabel() {
+        return clusterID;
+    }
+
+    /**
+     * Marks this dataObject as processed
+     * @param processed True, if the DataObject has been already processed, false else
+     */
+    public void setProcessed(boolean processed) {
+        this.processed = processed;
+    }
+
+    /**
+     * Gives information about the status of a dataObject
+     * @return True, if this dataObject has been processed, else false
+     */
+    public boolean isProcessed() {
+        return processed;
+    }
+
+    /**
+     * Sets a new coreDistance for this dataObject
+     * @param c_dist coreDistance
+     */
+    public void setCoreDistance(double c_dist) {
+        this.c_dist = c_dist;
+    }
+
+    /**
+     * Returns the coreDistance for this dataObject
+     * @return coreDistance
+     */
+    public double getCoreDistance() {
+        return c_dist;
+    }
+
+    /**
+     * Sets a new reachability-distance for this dataObject
+     */
+    public void setReachabilityDistance(double r_dist) {
+        this.r_dist = r_dist;
+    }
+
+    /**
+     * Returns the reachabilityDistance for this dataObject
+     */
+    public double getReachabilityDistance() {
+        return r_dist;
+    }
+
+    public String toString() {
+        return instance.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/ManhattanDataObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/ManhattanDataObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/DataObjects/ManhattanDataObject.java	(revision 29)
@@ -0,0 +1,301 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004  
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.DataObjects;
+
+import weka.clusterers.forOPTICSAndDBScan.Databases.Database;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * ManhattanDataObject.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 19, 2004 <br/>
+ * Time: 5:50:22 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 5987 $
+ */
+public class ManhattanDataObject
+    implements DataObject, Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -3417720553766544582L;
+
+    /**
+     * Holds the original instance
+     */
+    private Instance instance;
+
+    /**
+     * Holds the (unique) key that is associated with this DataObject
+     */
+    private String key;
+
+    /**
+     * Holds the ID of the cluster, to which this DataObject is assigned
+     */
+    private int clusterID;
+
+    /**
+     * Holds the status for this DataObject (true, if it has been processed, else false)
+     */
+    private boolean processed;
+
+    /**
+     * Holds the coreDistance for this DataObject
+     */
+    private double c_dist;
+
+    /**
+     * Holds the reachabilityDistance for this DataObject
+     */
+    private double r_dist;
+
+    /**
+     * Holds the database, that is the keeper of this DataObject
+     */
+    private Database database;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Constructs a new DataObject. The original instance is kept as instance-variable
+     * @param originalInstance the original instance
+     */
+    public ManhattanDataObject(Instance originalInstance, String key, Database database) {
+        this.database = database;
+        this.key = key;
+        instance = originalInstance;
+        clusterID = DataObject.UNCLASSIFIED;
+        processed = false;
+        c_dist = DataObject.UNDEFINED;
+        r_dist = DataObject.UNDEFINED;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Compares two DataObjects in respect to their attribute-values
+     * @param dataObject The DataObject, that is compared with this.dataObject
+     * @return Returns true, if the DataObjects correspond in each value, else returns false
+     */
+    public boolean equals(DataObject dataObject) {
+        if (this == dataObject) return true;
+        if (!(dataObject instanceof ManhattanDataObject)) return false;
+
+        final ManhattanDataObject manhattanDataObject = (ManhattanDataObject) dataObject;
+
+        if (getInstance().equalHeaders(manhattanDataObject.getInstance())) {
+            for (int i = 0; i < getInstance().numValues(); i++) {
+                double i_value_Instance_1 = getInstance().valueSparse(i);
+                double i_value_Instance_2 = manhattanDataObject.getInstance().valueSparse(i);
+
+                if (i_value_Instance_1 != i_value_Instance_2) return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Calculates the manhattan-distance between dataObject and this.dataObject
+     * @param dataObject The DataObject, that is used for distance-calculation with this.dataObject
+     * @return double-value The manhattan-distance between dataObject and this.dataObject
+     *                      NaN, if the computation could not be performed
+     */
+    public double distance(DataObject dataObject) {
+        double dist = 0.0;
+
+        if (!(dataObject instanceof ManhattanDataObject)) return Double.NaN;
+
+        if (getInstance().equalHeaders(dataObject.getInstance())) {
+            for (int i = 0; i < getInstance().numValues(); i++) {
+                double cDistance = computeDistance(getInstance().index(i),
+                        getInstance().valueSparse(i),
+                        dataObject.getInstance().valueSparse(i));
+                dist += Math.abs(cDistance);
+            }
+            return dist;
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Performs manhattan-distance-calculation between two given values
+     * @param index of the attribute within the DataObject's instance
+     * @param v value_1
+     * @param v1 value_2
+     * @return double norm-distance between value_1 and value_2
+     */
+    private double computeDistance(int index, double v, double v1) {
+        switch (getInstance().attribute(index).type()) {
+            case Attribute.NOMINAL:
+                return (Utils.isMissingValue(v) || Utils.isMissingValue(v1)
+                        || ((int) v != (int) v1)) ? 1 : 0;
+
+            case Attribute.NUMERIC:
+                if (Utils.isMissingValue(v) || Utils.isMissingValue(v1)) {
+                    if (Utils.isMissingValue(v) && Utils.isMissingValue(v1))
+                        return 1;
+                    else {
+                        return (Utils.isMissingValue(v)) ? norm(v1, index)
+                                : norm(v, index);
+                    }
+                } else
+                    return norm(v, index) - norm(v1, index);
+
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Normalizes a given value of a numeric attribute.
+     *
+     * @param x the value to be normalized
+     * @param i the attribute's index
+     */
+    private double norm(double x, int i) {
+        if (Double.isNaN(database.getAttributeMinValues()[i])
+                || Utils.eq(database.getAttributeMaxValues()[i], database.getAttributeMinValues()[i])) {
+            return 0;
+        } else {
+            return (x - database.getAttributeMinValues()[i]) /
+                    (database.getAttributeMaxValues()[i] - database.getAttributeMinValues()[i]);
+        }
+    }
+
+    /**
+     * Returns the original instance
+     * @return originalInstance
+     */
+    public Instance getInstance() {
+        return instance;
+    }
+
+    /**
+     * Returns the key for this DataObject
+     * @return key
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Sets the key for this DataObject
+     * @param key The key is represented as string
+     */
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    /**
+     * Sets the clusterID (cluster), to which this DataObject belongs to
+     * @param clusterID Number of the Cluster
+     */
+    public void setClusterLabel(int clusterID) {
+        this.clusterID = clusterID;
+    }
+
+    /**
+     * Returns the clusterID, to which this DataObject belongs to
+     * @return clusterID
+     */
+    public int getClusterLabel() {
+        return clusterID;
+    }
+
+    /**
+     * Marks this dataObject as processed
+     * @param processed True, if the DataObject has been already processed, false else
+     */
+    public void setProcessed(boolean processed) {
+        this.processed = processed;
+    }
+
+    /**
+     * Gives information about the status of a dataObject
+     * @return True, if this dataObject has been processed, else false
+     */
+    public boolean isProcessed() {
+        return processed;
+    }
+
+    /**
+     * Sets a new coreDistance for this dataObject
+     * @param c_dist coreDistance
+     */
+    public void setCoreDistance(double c_dist) {
+        this.c_dist = c_dist;
+    }
+
+    /**
+     * Returns the coreDistance for this dataObject
+     * @return coreDistance
+     */
+    public double getCoreDistance() {
+        return c_dist;
+    }
+
+    /**
+     * Sets a new reachability-distance for this dataObject
+     */
+    public void setReachabilityDistance(double r_dist) {
+        this.r_dist = r_dist;
+    }
+
+    /**
+     * Returns the reachabilityDistance for this dataObject
+     */
+    public double getReachabilityDistance() {
+        return r_dist;
+    }
+
+    public String toString() {
+        return instance.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/Database.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/Database.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/Database.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Databases;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.core.Instances;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <p>
+ * Database.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 20, 2004 <br/>
+ * Time: 1:03:43 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.2 $
+ */
+public interface Database {
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Select a dataObject from the database
+     * @param key The key that is associated with the dataObject
+     * @return dataObject
+     */
+    DataObject getDataObject(String key);
+
+    /**
+     * Returns the size of the database (the number of dataObjects in the database)
+     * @return size
+     */
+    int size();
+
+    /**
+     * Returns an iterator over all the keys
+     * @return iterator
+     */
+    Iterator keyIterator();
+
+    /**
+     * Returns an iterator over all the dataObjects in the database
+     * @return iterator
+     */
+    Iterator dataObjectIterator();
+
+    /**
+     * Tests if the database contains the dataObject_Query
+     * @param dataObject_Query The query-object
+     * @return true if the database contains dataObject_Query, else false
+     */
+    boolean contains(DataObject dataObject_Query);
+
+    /**
+     * Inserts a new dataObject into the database
+     * @param dataObject
+     */
+    void insert(DataObject dataObject);
+
+    /**
+     * Returns the original instances delivered from WEKA
+     * @return instances
+     */
+    Instances getInstances();
+
+    /**
+     * Sets the minimum and maximum values for each attribute in different arrays
+     * by walking through every DataObject of the database
+     */
+    void setMinMaxValues();
+
+    /**
+     * Returns the array of minimum-values for each attribute
+     * @return attributeMinValues
+     */
+    double[] getAttributeMinValues();
+
+    /**
+     * Returns the array of maximum-values for each attribute
+     * @return attributeMaxValues
+     */
+    double[] getAttributeMaxValues();
+
+    /**
+     * Performs an epsilon range query for this dataObject
+     * @param epsilon Specifies the range for the query
+     * @param queryDataObject The dataObject that is used as query-object for epsilon range query
+     * @return List with all the DataObjects that are within the specified range
+     */
+    List epsilonRangeQuery(double epsilon, DataObject queryDataObject);
+
+    /**
+     * Emits the k next-neighbours and performs an epsilon-range-query at the parallel.
+     * The returned list contains two elements:
+     * At index=0 --> list with all k next-neighbours;
+     * At index=1 --> list with all dataObjects within epsilon;
+     * @param k number of next neighbours
+     * @param epsilon Specifies the range for the query
+     * @param dataObject the start object
+     * @return list with the k-next neighbours (PriorityQueueElements) and a list
+     *         with candidates from the epsilon-range-query (EpsilonRange_ListElements)
+     */
+    List k_nextNeighbourQuery(int k, double epsilon, DataObject dataObject);
+
+    /**
+     * Calculates the coreDistance for the specified DataObject.
+     * The returned list contains three elements:
+     * At index=0 --> list with all k next-neighbours;
+     * At index=1 --> list with all dataObjects within epsilon;
+     * At index=2 --> coreDistance as Double-value
+     * @param minPoints minPoints-many neighbours within epsilon must be found to have a non-undefined coreDistance
+     * @param epsilon Specifies the range for the query
+     * @param dataObject Calculate coreDistance for this dataObject
+     * @return list with the k-next neighbours (PriorityQueueElements) and a list
+     *         with candidates from the epsilon-range-query (EpsilonRange_ListElements) and
+     *         the double-value for the calculated coreDistance
+     */
+    List coreDistance(int minPoints, double epsilon, DataObject dataObject);
+
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/SequentialDatabase.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/SequentialDatabase.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Databases/SequentialDatabase.java	(revision 29)
@@ -0,0 +1,312 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Databases;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.clusterers.forOPTICSAndDBScan.Utils.EpsilonRange_ListElement;
+import weka.clusterers.forOPTICSAndDBScan.Utils.PriorityQueue;
+import weka.clusterers.forOPTICSAndDBScan.Utils.PriorityQueueElement;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * <p>
+ * SequentialDatabase.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 20, 2004 <br/>
+ * Time: 1:23:38 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.4 $
+ */
+public class SequentialDatabase
+    implements Database, Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 787245523118665778L;
+
+    /**
+     * Internal, sorted Treemap for storing all the DataObjects
+     */
+    private TreeMap treeMap;
+
+    /**
+     * Holds the original instances delivered from WEKA
+     */
+    private Instances instances;
+
+    /**
+     * Holds the minimum value for each attribute
+     */
+    private double[] attributeMinValues;
+
+    /**
+     * Holds the maximum value for each attribute
+     */
+    private double[] attributeMaxValues;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Constructs a new sequential database and holds the original instances
+     * @param instances
+     */
+    public SequentialDatabase(Instances instances) {
+        this.instances = instances;
+        treeMap = new TreeMap();
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Select a dataObject from the database
+     * @param key The key that is associated with the dataObject
+     * @return dataObject
+     */
+    public DataObject getDataObject(String key) {
+        return (DataObject) treeMap.get(key);
+    }
+
+    /**
+     * Sets the minimum and maximum values for each attribute in different arrays
+     * by walking through every DataObject of the database
+     */
+    public void setMinMaxValues() {
+        attributeMinValues = new double[getInstances().numAttributes()];
+        attributeMaxValues = new double[getInstances().numAttributes()];
+
+        //Init
+        for (int i = 0; i < getInstances().numAttributes(); i++) {
+            attributeMinValues[i] = attributeMaxValues[i] = Double.NaN;
+        }
+
+        Iterator iterator = dataObjectIterator();
+        while (iterator.hasNext()) {
+            DataObject dataObject = (DataObject) iterator.next();
+            for (int j = 0; j < getInstances().numAttributes(); j++) {
+                if (Double.isNaN(attributeMinValues[j])) {
+                    attributeMinValues[j] = dataObject.getInstance().value(j);
+                    attributeMaxValues[j] = dataObject.getInstance().value(j);
+                } else {
+                    if (dataObject.getInstance().value(j) < attributeMinValues[j])
+                        attributeMinValues[j] = dataObject.getInstance().value(j);
+                    if (dataObject.getInstance().value(j) > attributeMaxValues[j])
+                        attributeMaxValues[j] = dataObject.getInstance().value(j);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the array of minimum-values for each attribute
+     * @return attributeMinValues
+     */
+    public double[] getAttributeMinValues() {
+        return attributeMinValues;
+    }
+
+    /**
+     * Returns the array of maximum-values for each attribute
+     * @return attributeMaxValues
+     */
+    public double[] getAttributeMaxValues() {
+        return attributeMaxValues;
+    }
+
+    /**
+     * Performs an epsilon range query for this dataObject
+     * @param epsilon Specifies the range for the query
+     * @param queryDataObject The dataObject that is used as query-object for epsilon range query
+     * @return List with all the DataObjects that are within the specified range
+     */
+    public List epsilonRangeQuery(double epsilon, DataObject queryDataObject) {
+        ArrayList epsilonRange_List = new ArrayList();
+        Iterator iterator = dataObjectIterator();
+        while (iterator.hasNext()) {
+            DataObject dataObject = (DataObject) iterator.next();
+            double distance = queryDataObject.distance(dataObject);
+            if (distance < epsilon) {
+                epsilonRange_List.add(dataObject);
+            }
+        }
+
+        return epsilonRange_List;
+    }
+
+    /**
+     * Emits the k next-neighbours and performs an epsilon-range-query at the parallel.
+     * The returned list contains two elements:
+     * At index=0 --> list with all k next-neighbours;
+     * At index=1 --> list with all dataObjects within epsilon;
+     * @param k number of next neighbours
+     * @param epsilon Specifies the range for the query
+     * @param dataObject the start object
+     * @return list with the k-next neighbours (PriorityQueueElements) and a list
+     *         with candidates from the epsilon-range-query (EpsilonRange_ListElements)
+     */
+    public List k_nextNeighbourQuery(int k, double epsilon, DataObject dataObject) {
+        Iterator iterator = dataObjectIterator();
+
+        List return_List = new ArrayList();
+        List nextNeighbours_List = new ArrayList();
+        List epsilonRange_List = new ArrayList();
+
+        PriorityQueue priorityQueue = new PriorityQueue();
+
+        while (iterator.hasNext()) {
+            DataObject next_dataObject = (DataObject) iterator.next();
+            double dist = dataObject.distance(next_dataObject);
+
+            if (dist <= epsilon) epsilonRange_List.add(new EpsilonRange_ListElement(dist, next_dataObject));
+
+            if (priorityQueue.size() < k) {
+                priorityQueue.add(dist, next_dataObject);
+            } else {
+                if (dist < priorityQueue.getPriority(0)) {
+                    priorityQueue.next(); //removes the highest distance
+                    priorityQueue.add(dist, next_dataObject);
+                }
+            }
+        }
+
+        while (priorityQueue.hasNext()) {
+            nextNeighbours_List.add(0, priorityQueue.next());
+        }
+
+        return_List.add(nextNeighbours_List);
+        return_List.add(epsilonRange_List);
+        return return_List;
+    }
+
+    /**
+     * Calculates the coreDistance for the specified DataObject.
+     * The returned list contains three elements:
+     * At index=0 --> list with all k next-neighbours;
+     * At index=1 --> list with all dataObjects within epsilon;
+     * At index=2 --> coreDistance as Double-value
+     * @param minPoints minPoints-many neighbours within epsilon must be found to have a non-undefined coreDistance
+     * @param epsilon Specifies the range for the query
+     * @param dataObject Calculate coreDistance for this dataObject
+     * @return list with the k-next neighbours (PriorityQueueElements) and a list
+     *         with candidates from the epsilon-range-query (EpsilonRange_ListElements) and
+     *         the double-value for the calculated coreDistance
+     */
+    public List coreDistance(int minPoints, double epsilon, DataObject dataObject) {
+        List list = k_nextNeighbourQuery(minPoints, epsilon, dataObject);
+
+        if (((List) list.get(1)).size() < minPoints) {
+            list.add(new Double(DataObject.UNDEFINED));
+            return list;
+        } else {
+            List nextNeighbours_List = (List) list.get(0);
+            PriorityQueueElement priorityQueueElement =
+                    (PriorityQueueElement) nextNeighbours_List.get(nextNeighbours_List.size() - 1);
+            if (priorityQueueElement.getPriority() <= epsilon) {
+                list.add(new Double(priorityQueueElement.getPriority()));
+                return list;
+            } else {
+                list.add(new Double(DataObject.UNDEFINED));
+                return list;
+            }
+        }
+    }
+
+    /**
+     * Returns the size of the database (the number of dataObjects in the database)
+     * @return size
+     */
+    public int size() {
+        return treeMap.size();
+    }
+
+    /**
+     * Returns an iterator over all the keys
+     * @return iterator
+     */
+    public Iterator keyIterator() {
+        return treeMap.keySet().iterator();
+    }
+
+    /**
+     * Returns an iterator over all the dataObjects in the database
+     * @return iterator
+     */
+    public Iterator dataObjectIterator() {
+        return treeMap.values().iterator();
+    }
+
+    /**
+     * Tests if the database contains the dataObject_Query
+     * @param dataObject_Query The query-object
+     * @return true if the database contains dataObject_Query, else false
+     */
+    public boolean contains(DataObject dataObject_Query) {
+        Iterator iterator = dataObjectIterator();
+        while (iterator.hasNext()) {
+            DataObject dataObject = (DataObject) iterator.next();
+            if (dataObject.equals(dataObject_Query)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Inserts a new dataObject into the database
+     * @param dataObject
+     */
+    public void insert(DataObject dataObject) {
+        treeMap.put(dataObject.getKey(), dataObject);
+    }
+
+    /**
+     * Returns the original instances delivered from WEKA
+     * @return instances
+     */
+    public Instances getInstances() {
+        return instances;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/GraphPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/GraphPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/GraphPanel.java	(revision 29)
@@ -0,0 +1,360 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.core.FastVector;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+
+import javax.swing.JComponent;
+
+/**
+ * <p>
+ * GraphPanel.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
+ * Date: Sep 16, 2004 <br/>
+ * Time: 10:28:19 AM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.4 $
+ */
+public class GraphPanel
+    extends JComponent
+    implements RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 7917937528738361470L;
+
+    /**
+     * Holds the clustering results
+     */
+    private FastVector resultVector;
+
+    /**
+     * Holds the value that is multiplied with the original values of core- and reachability
+     * distances in order to get better graphical views
+     */
+    private int verticalAdjustment;
+
+    /**
+     * Specifies the color for displaying core-distances
+     */
+    private Color coreDistanceColor;
+
+    /**
+     * Specifies the color for displaying reachability-distances
+     */
+    private Color reachabilityDistanceColor;
+
+    /**
+     * Specifies the width for displaying the distances
+     */
+    private int widthSlider;
+
+    /**
+     * Holds the flag for showCoreDistances
+     */
+    private boolean showCoreDistances;
+
+    /**
+     * Holds the flag for showrRechabilityDistances
+     */
+    private boolean showReachabilityDistances;
+
+    /**
+     * Holds the index of the last toolTip
+     */
+    private int recentIndex = -1;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    public GraphPanel(FastVector resultVector,
+                      int verticalAdjustment,
+                      boolean showCoreDistances,
+                      boolean showReachbilityDistances) {
+        this.resultVector = resultVector;
+        this.verticalAdjustment = verticalAdjustment;
+        coreDistanceColor = new Color(100, 100, 100);
+        reachabilityDistanceColor = Color.orange;
+        widthSlider = 5;
+        this.showCoreDistances = showCoreDistances;
+        this.showReachabilityDistances = showReachbilityDistances;
+
+        addMouseMotionListener(new MouseHandler());
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Draws the OPTICS Plot
+     * @param g
+     */
+    protected void paintComponent(Graphics g) {
+        if (isOpaque()) {
+            Dimension size = getSize();
+            g.setColor(getBackground());
+            g.fillRect(0, 0, size.width, size.height);
+        }
+
+        int stepSize = 0;
+        int cDist = 0;
+        int rDist = 0;
+
+        for (int vectorIndex = 0; vectorIndex < resultVector.size(); vectorIndex++) {
+            double coreDistance = ((DataObject) resultVector.elementAt(vectorIndex)).getCoreDistance();
+            double reachDistance = ((DataObject) resultVector.elementAt(vectorIndex)).getReachabilityDistance();
+
+            if (coreDistance == DataObject.UNDEFINED)
+                cDist = getHeight();
+            else
+                cDist = (int) (coreDistance * verticalAdjustment);
+
+            if (reachDistance == DataObject.UNDEFINED)
+                rDist = getHeight();
+            else
+                rDist = (int) (reachDistance * verticalAdjustment);
+
+            int x = vectorIndex + stepSize;
+
+            if (isShowCoreDistances()) {
+                /**
+                 * Draw coreDistance
+                 */
+                g.setColor(coreDistanceColor);
+                g.fillRect(x, getHeight() - cDist, widthSlider, cDist);
+            }
+
+            if (isShowReachabilityDistances()) {
+                int sizer = widthSlider;
+                if (!isShowCoreDistances()) sizer = 0;
+                /**
+                 * Draw reachabilityDistance
+                 */
+                g.setColor(reachabilityDistanceColor);
+                g.fillRect(x + sizer, getHeight() - rDist, widthSlider, rDist);
+            }
+
+            if (isShowCoreDistances() && isShowReachabilityDistances()) {
+                stepSize += (widthSlider * 2);
+            } else
+                stepSize += widthSlider;
+        }
+    }
+
+    /**
+     * Sets a new resultVector
+     * @param resultVector
+     */
+    public void setResultVector(FastVector resultVector) {
+        this.resultVector = resultVector;
+    }
+
+    /**
+     * Displays a toolTip for the selected DataObject
+     * @param toolTip
+     */
+    public void setNewToolTip(String toolTip) {
+        setToolTipText(toolTip);
+    }
+
+    /**
+     * Adjusts the size of this panel in respect of the shown content
+     * @param serObject SERObject that contains the OPTICS clustering results
+     */
+    public void adjustSize(SERObject serObject) {
+        int i = 0;
+        if (isShowCoreDistances() && isShowReachabilityDistances())
+            i = 10;
+        else if ((isShowCoreDistances() && !isShowReachabilityDistances()) ||
+                !isShowCoreDistances() && isShowReachabilityDistances())
+            i = 5;
+        setSize(new Dimension((i * serObject.getDatabaseSize()) +
+                serObject.getDatabaseSize(), getHeight()));
+        setPreferredSize(new Dimension((i * serObject.getDatabaseSize()) +
+                serObject.getDatabaseSize(), getHeight()));
+    }
+
+    /**
+     * Returns the flag for showCoreDistances
+     * @return True or false
+     */
+    public boolean isShowCoreDistances() {
+        return showCoreDistances;
+    }
+
+    /**
+     * Sets the flag for showCoreDistances
+     * @param showCoreDistances
+     */
+    public void setShowCoreDistances(boolean showCoreDistances) {
+        this.showCoreDistances = showCoreDistances;
+    }
+
+    /**
+     * Returns the flag for showReachabilityDistances
+     * @return True or false
+     */
+    public boolean isShowReachabilityDistances() {
+        return showReachabilityDistances;
+    }
+
+    /**
+     * Sets the flag for showReachabilityDistances
+     * @param showReachabilityDistances
+     */
+    public void setShowReachabilityDistances(boolean showReachabilityDistances) {
+        this.showReachabilityDistances = showReachabilityDistances;
+    }
+
+    /**
+     * Sets a new value for the vertical verticalAdjustment
+     * @param verticalAdjustment
+     */
+    public void setVerticalAdjustment(int verticalAdjustment) {
+        this.verticalAdjustment = verticalAdjustment;
+    }
+
+    /**
+     * Sets a new color for the coreDistance
+     * @param coreDistanceColor
+     */
+    public void setCoreDistanceColor(Color coreDistanceColor) {
+        this.coreDistanceColor = coreDistanceColor;
+        repaint();
+    }
+
+    /**
+     * Sets a new color for the reachabilityDistance
+     * @param reachabilityDistanceColor
+     */
+    public void setReachabilityDistanceColor(Color reachabilityDistanceColor) {
+        this.reachabilityDistanceColor = reachabilityDistanceColor;
+        repaint();
+    }
+
+    // *****************************************************************************************************************
+    // inner classes
+    // *****************************************************************************************************************
+
+    private class MouseHandler
+         extends MouseMotionAdapter
+         implements RevisionHandler {
+      
+        /**
+         * Invoked when the mouse button has been moved on a component
+         * (with no buttons no down).
+         */
+        public void mouseMoved(MouseEvent e) {
+            showToolTip(e.getX());
+        }
+
+        /**
+         * Shows a toolTip with the dataObjects parameters (c-dist, r-dist, key, attributes . . .)
+         * @param x MouseCoordinate X
+         * @return boolean
+         */
+        private boolean showToolTip(int x) {
+            int i = 0;
+            if (isShowCoreDistances() && isShowReachabilityDistances())
+                i = 11;
+            else if ((isShowCoreDistances() && !isShowReachabilityDistances()) ||
+                    !isShowCoreDistances() && isShowReachabilityDistances() ||
+                    !isShowCoreDistances() && !isShowReachabilityDistances())
+                i = 6;
+            if ((x / i) == recentIndex)
+                return false;
+            else
+                recentIndex = x / i;
+            DataObject dataObject = null;
+            try {
+                dataObject = (DataObject) resultVector.elementAt(recentIndex);
+            } catch (Exception e) {
+            }
+            if (dataObject != null) {
+                if (!isShowCoreDistances() && !isShowReachabilityDistances()) {
+                    setNewToolTip("<html><body><b>Please select a distance" +
+                            "</b></body></html>"
+                    );
+                } else
+                    setNewToolTip("<html><body><table>" +
+                            "<tr><td>DataObject:</td><td>" + dataObject + "</td></tr>" +
+                            "<tr><td>Key:</td><td>" + dataObject.getKey() + "</td></tr>" +
+                            "<tr><td>" +
+                            (isShowCoreDistances() ? "<b>" : "") + "Core-Distance:" +
+                            (isShowCoreDistances() ? "</b>" : "") +
+                            "</td><td>" +
+                            (isShowCoreDistances() ? "<b>" : "") +
+                            ((dataObject.getCoreDistance() == DataObject.UNDEFINED) ? "UNDEFINED" :
+                            Utils.doubleToString(dataObject.getCoreDistance(), 3, 5)) +
+                            (isShowCoreDistances() ? "</b>" : "") +
+                            "</td></tr>" +
+                            "<tr><td>" +
+                            (isShowReachabilityDistances() ? "<b>" : "") + "Reachability-Distance:" +
+                            (isShowReachabilityDistances() ? "</b>" : "") +
+                            "</td><td>" +
+                            (isShowReachabilityDistances() ? "<b>" : "") +
+                            ((dataObject.getReachabilityDistance() == DataObject.UNDEFINED) ? "UNDEFINED" :
+                            Utils.doubleToString(dataObject.getReachabilityDistance(), 3, 5)) +
+                            (isShowReachabilityDistances() ? "</b>" : "") +
+                            "</td></tr>" +
+                            "</table></body></html>"
+                    );
+            }
+            return true;
+        }
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 1.4 $");
+        }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/OPTICS_Visualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/OPTICS_Visualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/OPTICS_Visualizer.java	(revision 29)
@@ -0,0 +1,1011 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;
+
+import weka.core.FastVector;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.gui.LookAndFeel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Event;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JToolBar;
+import javax.swing.KeyStroke;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableColumn;
+
+/**
+ * Start the OPTICS Visualizer from command-line: <br/>
+ * <code>java weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI.OPTICS_Visualizer [file.ser]</code>
+ * <br/>
+ * 
+ * <p>
+ * OPTICS_Visualizer.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
+ * Date: Sep 12, 2004 <br/>
+ * Time: 8:01:13 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 4791 $
+ */
+public class OPTICS_Visualizer
+    implements RevisionHandler {
+
+    /**
+     * Holds the OPTICS clustering results
+     */
+    private SERObject serObject;
+
+    /**
+     * Main Window of the OPTICS-Visualizer
+     */
+    private JFrame frame;
+
+    /**
+     * Statistic-frame
+     */
+    private JFrame statisticsFrame;
+
+    /**
+     * Help-frame
+     */
+    private JFrame helpFrame;
+
+    /**
+     * Listener for menu- and toolBar actions
+     */
+    private FrameListener frameListener;
+
+    /**
+     * Holds the toolBar and its components
+     */
+    private JToolBar toolBar;
+    private JButton toolBarButton_open;
+    private JButton toolBarButton_save;
+    private JButton toolBarButton_parameters;
+    private JButton toolBarButton_help;
+    private JButton toolBarButton_about;
+
+    /**
+     * Holds the default-menu and its components
+     */
+    private JMenuBar defaultMenuBar;
+    private JMenuItem open;
+    private JMenuItem save;
+    private JMenuItem exit;
+    private JMenuItem parameters;
+    private JMenuItem help;
+    private JMenuItem about;
+
+    /**
+     * Holds the tabbedPane and its components
+     */
+    private JTabbedPane tabbedPane;
+    private JTable resultVectorTable;
+    private GraphPanel graphPanel;
+    private JScrollPane graphPanelScrollPane;
+
+    /**
+     * Holds the settingsPanel and its components
+     */
+    private JPanel settingsPanel;
+    private JCheckBox showCoreDistances;
+    private JCheckBox showReachabilityDistances;
+    private int verValue = 30;
+    private JSlider verticalSlider;
+    private JButton coreDistanceColorButton;
+    private JButton reachDistanceColorButton;
+    private JButton graphBackgroundColorButton;
+    private JButton resetColorButton;
+
+    /**
+     * FileChooser for saving- and open-actions
+     */
+    private JFileChooser jFileChooser;
+    private String lastPath;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    public OPTICS_Visualizer(SERObject serObject, String title) {
+        this.serObject = serObject;
+
+        LookAndFeel.setLookAndFeel();
+    
+        frame = new JFrame(title);
+
+        frame.addWindowListener(new WindowAdapter() {
+            /**
+             * Invoked when a window is in the process of being closed.
+             * The close operation can be overridden at this point.
+             */
+            public void windowClosing(WindowEvent e) {
+                frame.dispose();
+            }
+        });
+
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.setSize(new Dimension(800, 600));
+        Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
+        Rectangle windowRectangle = frame.getBounds();
+        frame.setLocation((screenDimension.width - windowRectangle.width) / 2,
+                (screenDimension.height - windowRectangle.height) / 2);
+
+        frameListener = new FrameListener();
+        jFileChooser = new JFileChooser();
+        jFileChooser.setFileFilter(new SERFileFilter("ser", "Java Serialized Object File (*.ser)"));
+
+        createGUI();
+        frame.setVisible(true);
+        frame.toFront();
+
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Constructs the main-layout for the OPTICS-Visualizer
+     */
+    private void createGUI() {
+        setMenuBar(constructDefaultMenuBar());
+
+        frame.getContentPane().add(createToolBar(), BorderLayout.NORTH);
+        frame.getContentPane().add(createTabbedPane(), BorderLayout.CENTER);
+        frame.getContentPane().add(createSettingsPanel(), BorderLayout.SOUTH);
+        disableSettingsPanel();
+    }
+
+    /**
+     * Creates the settings-panel
+     * @return Settings-panel
+     */
+    private JComponent createSettingsPanel() {
+        settingsPanel = new JPanel(new GridBagLayout());
+
+        SettingsPanelListener panelListener = new SettingsPanelListener();
+
+        JPanel setPanelLeft = new JPanel(new GridBagLayout());
+        setPanelLeft.setBorder(BorderFactory.createTitledBorder(" General Settings "));
+
+        JPanel checkBoxesPanel = new JPanel(new GridLayout(1, 2));
+        showCoreDistances = new JCheckBox("Show Core-Distances");
+        showCoreDistances.setSelected(true);
+        showReachabilityDistances = new JCheckBox("Show Reachability-Distances");
+        showReachabilityDistances.setSelected(true);
+        showCoreDistances.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent e) {
+                if (e.getStateChange() == ItemEvent.SELECTED) {
+                    graphPanel.setShowCoreDistances(true);
+                    graphPanel.adjustSize(serObject);
+                    graphPanel.repaint();
+                } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+                    graphPanel.setShowCoreDistances(false);
+                    graphPanel.adjustSize(serObject);
+                    graphPanel.repaint();
+                }
+            }
+        });
+        showReachabilityDistances.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent e) {
+                if (e.getStateChange() == ItemEvent.SELECTED) {
+                    graphPanel.setShowReachabilityDistances(true);
+                    graphPanel.adjustSize(serObject);
+                    graphPanel.repaint();
+                } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+                    graphPanel.setShowReachabilityDistances(false);
+                    graphPanel.adjustSize(serObject);
+                    graphPanel.repaint();
+                }
+            }
+        });
+
+        checkBoxesPanel.add(showCoreDistances);
+        checkBoxesPanel.add(showReachabilityDistances);
+
+        JPanel verticalAdPanel = new JPanel(new BorderLayout());
+        final JLabel verValueLabel = new JLabel("Vertical Adjustment: " + verValue);
+        verticalAdPanel.add(verValueLabel, BorderLayout.NORTH);
+        verticalSlider = new JSlider(JSlider.HORIZONTAL, 0, frame.getHeight(), verValue);
+        verticalSlider.setMajorTickSpacing(100);
+        verticalSlider.setMinorTickSpacing(10);
+        verticalSlider.setPaintTicks(true);
+        verticalSlider.setPaintLabels(true);
+        verticalSlider.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                if (!verticalSlider.getValueIsAdjusting()) {
+                    verValue = verticalSlider.getValue();
+                    verValueLabel.setText("Vertical Adjustment: " + verValue);
+                    graphPanel.setVerticalAdjustment(verValue);
+                    graphPanel.repaint();
+                }
+            }
+        });
+        verticalAdPanel.add(verticalSlider, BorderLayout.CENTER);
+
+        setPanelLeft.add(checkBoxesPanel,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 5, 5), 0, 0));
+        setPanelLeft.add(verticalAdPanel,
+                new GridBagConstraints(0, 1, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 5, 5), 0, 0));
+
+        settingsPanel.add(setPanelLeft,
+                new GridBagConstraints(0, 0, 1, 1, 3, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 5, 0), 0, 0));
+
+        JPanel setPanelRight = new JPanel(new GridBagLayout());
+        setPanelRight.setBorder(BorderFactory.createTitledBorder(" Colors "));
+
+        JPanel colorsPanel = new JPanel(new GridLayout(4, 2, 10, 10));
+
+        colorsPanel.add(new JLabel("Core-Distance: "));
+        coreDistanceColorButton = new JButton();
+        coreDistanceColorButton.setBackground(new Color(100, 100, 100));
+        coreDistanceColorButton.addActionListener(panelListener);
+        colorsPanel.add(coreDistanceColorButton);
+
+        colorsPanel.add(new JLabel("Reachability-Distance: "));
+        reachDistanceColorButton = new JButton();
+        reachDistanceColorButton.setBackground(Color.orange);
+        reachDistanceColorButton.addActionListener(panelListener);
+        colorsPanel.add(reachDistanceColorButton);
+
+        colorsPanel.add(new JLabel("Graph Background: "));
+        graphBackgroundColorButton = new JButton();
+        graphBackgroundColorButton.setBackground(new Color(255, 255, 179));
+        graphBackgroundColorButton.addActionListener(panelListener);
+        colorsPanel.add(graphBackgroundColorButton);
+
+        colorsPanel.add(new JLabel());
+        resetColorButton = new JButton("Reset");
+        resetColorButton.addActionListener(panelListener);
+        colorsPanel.add(resetColorButton);
+
+        setPanelRight.add(colorsPanel,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 5, 5), 0, 0));
+
+        settingsPanel.add(setPanelRight,
+                new GridBagConstraints(1, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 5, 5), 0, 0));
+
+        return settingsPanel;
+    }
+
+    /**
+     * Disables all components from the settingsPanel
+     */
+    private void disableSettingsPanel() {
+
+        verticalSlider.setEnabled(false);
+        coreDistanceColorButton.setEnabled(false);
+        reachDistanceColorButton.setEnabled(false);
+        graphBackgroundColorButton.setEnabled(false);
+        resetColorButton.setEnabled(false);
+        settingsPanel.setVisible(false);
+    }
+
+    /**
+     * Enables all components from the settingsPanel
+     */
+    private void enableSettingsPanel() {
+        verticalSlider.setEnabled(true);
+        coreDistanceColorButton.setEnabled(true);
+        reachDistanceColorButton.setEnabled(true);
+        graphBackgroundColorButton.setEnabled(true);
+        resetColorButton.setEnabled(true);
+        settingsPanel.setVisible(true);
+    }
+
+    /**
+     * Creates the TabbedPane
+     * @return TabbedPane
+     */
+    private JComponent createTabbedPane() {
+        tabbedPane = new JTabbedPane();
+        tabbedPane.addTab("Table", new ImageIcon(Toolkit.getDefaultToolkit().
+            getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Table16.gif"))),
+            clusteringResultsTable(),
+        "Show table of DataObjects, Core- and Reachability-Distances");
+        if (serObject != null)
+          tabbedPane.addTab("Graph - Epsilon: " + serObject.getEpsilon() + ", MinPoints: " + serObject.getMinPoints()
+              , new ImageIcon(Toolkit.getDefaultToolkit().
+        	  getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Graph16.gif"))),
+        	  graphPanel(),
+          "Show Plot of Core- and Reachability-Distances");
+        else
+          tabbedPane.addTab(
+              "Graph - Epsilon: --, MinPoints: --", 
+              new ImageIcon(
+        	  Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Graph16.gif"))),
+        	  graphPanel(),
+          "Show Plot of Core- and Reachability-Distances");
+
+        tabbedPane.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                int c = tabbedPane.getSelectedIndex();
+                if (c == 0)
+                    disableSettingsPanel();
+                else
+                    enableSettingsPanel();
+            }
+        });
+
+        return tabbedPane;
+    }
+
+    /**
+     * Creates the ToolBar
+     * @return ToolBar
+     */
+    private JComponent createToolBar() {
+        toolBar = new JToolBar();
+        toolBar.setName("OPTICS Visualizer ToolBar");
+        toolBar.setFloatable(false);
+        toolBarButton_open = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Open16.gif"))));
+        toolBarButton_open.setToolTipText("Open OPTICS-Session");
+        toolBarButton_open.addActionListener(frameListener);
+        toolBar.add(toolBarButton_open);
+
+        toolBarButton_save = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Save16.gif"))));
+        toolBarButton_save.setToolTipText("Save OPTICS-Session");
+        toolBarButton_save.addActionListener(frameListener);
+        toolBar.add(toolBarButton_save);
+        toolBar.addSeparator(new Dimension(10, 25));
+
+        toolBarButton_parameters = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Parameters16.gif"))));
+        toolBarButton_parameters.setToolTipText("Show epsilon, MinPoints...");
+        toolBarButton_parameters.addActionListener(frameListener);
+        toolBar.add(toolBarButton_parameters);
+
+        toolBar.addSeparator(new Dimension(10, 25));
+
+        toolBarButton_help = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Help16.gif"))));
+        toolBarButton_help.setToolTipText("Help topics");
+        toolBarButton_help.addActionListener(frameListener);
+        toolBar.add(toolBarButton_help);
+
+        toolBarButton_about = new JButton(new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Information16.gif"))));
+        toolBarButton_about.setToolTipText("About");
+        toolBarButton_about.addActionListener(frameListener);
+        toolBar.add(toolBarButton_about);
+
+        return toolBar;
+    }
+
+    /**
+     * Creates the OPTICS clustering results table
+     * @return Table
+     */
+    private JComponent clusteringResultsTable() {
+        resultVectorTable = new JTable();
+        String[] resultVectorTableColumnNames = {"Key",
+                                                 "DataObject",
+                                                 "Core-Distance",
+                                                 "Reachability-Distance"};
+
+        DefaultTableColumnModel resultVectorTableColumnModel = new DefaultTableColumnModel();
+        for (int i = 0; i < resultVectorTableColumnNames.length; i++) {
+            TableColumn tc = new TableColumn(i);
+            tc.setHeaderValue(resultVectorTableColumnNames[i]);
+            resultVectorTableColumnModel.addColumn(tc);
+        }
+
+        ResultVectorTableModel resultVectorTableModel;
+        if (serObject != null)
+          resultVectorTableModel = new ResultVectorTableModel(serObject.getResultVector());
+        else
+          resultVectorTableModel = new ResultVectorTableModel(null);
+        resultVectorTable = new JTable(resultVectorTableModel, resultVectorTableColumnModel);
+        resultVectorTable.getColumnModel().getColumn(0).setPreferredWidth(70);
+        resultVectorTable.getColumnModel().getColumn(1).setPreferredWidth(400);
+        resultVectorTable.getColumnModel().getColumn(2).setPreferredWidth(150);
+        resultVectorTable.getColumnModel().getColumn(3).setPreferredWidth(150);
+        resultVectorTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+
+        JScrollPane resultVectorTableScrollPane = new JScrollPane(resultVectorTable,
+                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+        return resultVectorTableScrollPane;
+    }
+
+    /**
+     * Creates the OPTICS Plot
+     * @return JComponent with the PLOT
+     */
+    private JComponent graphPanel() {
+
+        if (serObject == null) {
+          graphPanel = new GraphPanel(new FastVector(), verValue, true, true);
+        }
+        else {
+          graphPanel = new GraphPanel(serObject.getResultVector(), verValue, true, true);
+          graphPanel.setPreferredSize(new Dimension((10 * serObject.getDatabaseSize()) +
+              serObject.getDatabaseSize(), graphPanel.getHeight()));
+        }
+        graphPanel.setBackground(new Color(255, 255, 179));
+        graphPanel.setOpaque(true);
+
+        graphPanelScrollPane = new JScrollPane(graphPanel,
+                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+        return (graphPanelScrollPane);
+    }
+
+    /**
+     * Constructs the default MenuBar
+     * @return MenuBar
+     */
+    private JMenuBar constructDefaultMenuBar() {
+        defaultMenuBar = new JMenuBar();
+
+        JMenu fileMenu = new JMenu("File");
+        fileMenu.setMnemonic('F');
+        open = new JMenuItem("Open...", new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Open16.gif"))));
+        open.setMnemonic('O');
+        open.setAccelerator(KeyStroke.getKeyStroke('O', Event.CTRL_MASK));
+        open.addActionListener(frameListener);
+        fileMenu.add(open);
+
+        save = new JMenuItem("Save...", new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Save16.gif"))));
+        save.setMnemonic('S');
+        save.setAccelerator(KeyStroke.getKeyStroke('S', Event.CTRL_MASK));
+        save.addActionListener(frameListener);
+        fileMenu.add(save);
+
+        fileMenu.addSeparator();
+
+        exit = new JMenuItem("Exit", 'X');
+        exit.addActionListener(frameListener);
+        fileMenu.add(exit);
+
+        defaultMenuBar.add(fileMenu);
+
+        JMenu toolsMenu = new JMenu("View");
+        toolsMenu.setMnemonic('V');
+        parameters = new JMenuItem("Parameters...", new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Parameters16.gif"))));
+        parameters.setMnemonic('P');
+        parameters.setAccelerator(KeyStroke.getKeyStroke('P', Event.CTRL_MASK));
+        parameters.addActionListener(frameListener);
+        toolsMenu.add(parameters);
+
+        defaultMenuBar.add(toolsMenu);
+
+        JMenu miscMenu = new JMenu("Help");
+        miscMenu.setMnemonic('H');
+        help = new JMenuItem("Help Topics", new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Help16.gif"))));
+        help.setMnemonic('H');
+        help.setAccelerator(KeyStroke.getKeyStroke('H', Event.CTRL_MASK));
+        help.addActionListener(frameListener);
+        miscMenu.add(help);
+
+        about = new JMenuItem("About...", new ImageIcon(Toolkit.getDefaultToolkit().
+                getImage(ClassLoader.getSystemResource("weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/Graphics/Information16.gif"))));
+        about.setMnemonic('A');
+        about.setAccelerator(KeyStroke.getKeyStroke('A', Event.CTRL_MASK));
+        about.addActionListener(frameListener);
+        miscMenu.add(about);
+        defaultMenuBar.add(miscMenu);
+
+        return defaultMenuBar;
+    }
+
+    /**
+     * Sets a MenuBar for the this frame
+     * @param menuBar New MenuBar
+     */
+    private void setMenuBar(JMenuBar menuBar) {
+        frame.setJMenuBar(menuBar);
+    }
+
+    /**
+     * Shows a little frame with statistic information about the OPTICS-results
+     */
+    private void loadStatisticsFrame() {
+        statisticsFrame = new JFrame("Parameters");
+        statisticsFrame.getContentPane().setLayout(new BorderLayout());
+
+        JPanel statPanel_Labels = new JPanel(new GridBagLayout());
+        JPanel statPanel_Labels_Left = new JPanel(new GridLayout(9, 1));
+        JPanel statPanel_Labels_Right = new JPanel(new GridLayout(9, 1));
+
+        statPanel_Labels_Left.add(new JLabel("Number of clustered DataObjects: "));
+        statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject.getDatabaseSize())));
+        statPanel_Labels_Left.add(new JLabel("Number of attributes: "));
+        statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject.getNumberOfAttributes())));
+        statPanel_Labels_Left.add(new JLabel("Epsilon: "));
+        statPanel_Labels_Right.add(new JLabel(Double.toString(serObject.getEpsilon())));
+        statPanel_Labels_Left.add(new JLabel("MinPoints: "));
+        statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject.getMinPoints())));
+        statPanel_Labels_Left.add(new JLabel("Write results to file: "));
+        statPanel_Labels_Right.add(new JLabel(serObject.isOpticsOutputs() ? "yes" : "no"));
+        statPanel_Labels_Left.add(new JLabel("Index: "));
+        statPanel_Labels_Right.add(new JLabel(serObject.getDatabase_Type()));
+        statPanel_Labels_Left.add(new JLabel("Distance-Type: "));
+        statPanel_Labels_Right.add(new JLabel(serObject.getDatabase_distanceType()));
+        statPanel_Labels_Left.add(new JLabel("Number of generated clusters: "));
+        statPanel_Labels_Right.add(new JLabel(Integer.toString(serObject.getNumberOfGeneratedClusters())));
+        statPanel_Labels_Left.add(new JLabel("Elapsed-time: "));
+        statPanel_Labels_Right.add(new JLabel(serObject.getElapsedTime()));
+        statPanel_Labels.setBorder(BorderFactory.createTitledBorder(" OPTICS parameters "));
+
+        statPanel_Labels.add(statPanel_Labels_Left,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(0, 5, 2, 0), 0, 0));
+
+        statPanel_Labels.add(statPanel_Labels_Right,
+                new GridBagConstraints(1, 0, 1, 1, 3, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(0, 5, 2, 5), 0, 0));
+
+        statisticsFrame.getContentPane().add(statPanel_Labels, BorderLayout.CENTER);
+
+        statisticsFrame.addWindowListener(new WindowAdapter() {
+            /**
+             * Invoked when a window is in the process of being closed.
+             * The close operation can be overridden at this point.
+             */
+            public void windowClosing(WindowEvent e) {
+                statisticsFrame.dispose();
+            }
+        });
+
+        JPanel okButtonPanel = new JPanel(new GridBagLayout());
+
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (e.getActionCommand().equals("OK")) {
+                    statisticsFrame.dispose();
+                }
+            }
+        });
+        okButtonPanel.add(okButton,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.NONE,
+                        new Insets(5, 0, 5, 0), 0, 0));
+
+        statisticsFrame.getContentPane().add(okButtonPanel, BorderLayout.SOUTH);
+        statisticsFrame.setSize(new Dimension(500, 300));
+        Rectangle frameDimension = frame.getBounds();
+        Point p = frame.getLocation();
+        Rectangle statisticsFrameDimension = statisticsFrame.getBounds();
+        statisticsFrame.setLocation(((frameDimension.width - statisticsFrameDimension.width) / 2) + (int) p.getX(),
+                ((frameDimension.height - statisticsFrameDimension.height) / 2) + (int) p.getY());
+        statisticsFrame.setVisible(true);
+        statisticsFrame.toFront();
+    }
+
+    /**
+     * Shows a little frame with information about handling the OPTICS Visualizer
+     */
+    private void loadHelpFrame() {
+        helpFrame = new JFrame("Help Topics");
+        helpFrame.getContentPane().setLayout(new BorderLayout());
+
+        JPanel helpPanel = new JPanel(new GridBagLayout());
+        JTextArea helpTextArea = new JTextArea();
+        helpTextArea.setEditable(false);
+        helpTextArea.append(
+                "OPTICS Visualizer Help\n"
+                + "===========================================================\n\n"
+                + "Open\n"
+                + " - Open OPTICS-Session\n"
+                + "   [Ctrl-O], File | Open\n\n"
+                + "Save\n"
+                + " - Save OPTICS-Session\n"
+                + "   [Ctrl-S], File | Save\n\n"
+                + "Exit\n"
+                + " - Exit OPTICS Visualizer\n"
+                + "   [Alt-F4], File | Exit\n\n"
+                + "Parameters\n"
+                + " - Show epsilon, MinPoints...\n"
+                + "   [Ctrl-P], View | Parameters\n\n"
+                + "Help Topics\n"
+                + " - Show this frame\n"
+                + "   [Ctrl-H], Help | Help Topics\n\n"
+                + "About\n"
+                + " - Copyright-Information\n"
+                + "   [Ctrl-A], Help | About\n\n\n"
+                + "Table-Pane:\n"
+                + "-----------------------------------------------------------\n"
+                + "The table represents the calculated clustering-order.\n"
+                + "To save the table please select File | Save from the\n"
+                + "menubar. Restart OPTICS with the -F option to obtain\n"
+                + "an ASCII-formatted file of the clustering-order.\n\n"
+                + "Graph-Pane:\n"
+                + "-----------------------------------------------------------\n"
+                + "The graph draws the plot of core- and reachability-\n"
+                + "distances. By (de-)activating core- and reachability-\n"
+                + "distances in the 'General Settings'-Panel you can\n"
+                + "influence the visualization in detail. Simply use the\n"
+                + "'Vertical Adjustment'-Slider to emphasize the plot of\n"
+                + "distances. The 'Colors'-Panel lets you define different\n"
+                + "colors of the graph background, core- and reachability-\n"
+                + "distances. Click the 'Reset'-Button to restore the\n"
+                + "defaults.\n"
+        );
+        final JScrollPane helpTextAreaScrollPane = new JScrollPane(helpTextArea,
+                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+        helpTextAreaScrollPane.setBorder(BorderFactory.createEtchedBorder());
+        helpPanel.add(helpTextAreaScrollPane,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.BOTH,
+                        new Insets(5, 5, 7, 5), 0, 0));
+
+        helpFrame.getContentPane().add(helpPanel, BorderLayout.CENTER);
+
+        helpFrame.addWindowListener(new WindowAdapter() {
+            /**
+             * Invoked when a window is in the process of being closed.
+             * The close operation can be overridden at this point.
+             */
+            public void windowClosing(WindowEvent e) {
+                helpFrame.dispose();
+            }
+
+            /**
+             * Invoked when a window has been opened.
+             */
+            public void windowOpened(WindowEvent e) {
+                helpTextAreaScrollPane.getVerticalScrollBar().setValue(0);
+            }
+        });
+
+        JPanel closeButtonPanel = new JPanel(new GridBagLayout());
+
+        JButton closeButton = new JButton("Close");
+        closeButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (e.getActionCommand().equals("Close")) {
+                    helpFrame.dispose();
+                }
+            }
+        });
+        closeButtonPanel.add(closeButton,
+                new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                        GridBagConstraints.CENTER,
+                        GridBagConstraints.NONE,
+                        new Insets(0, 0, 5, 0), 0, 0));
+
+        helpFrame.getContentPane().add(closeButtonPanel, BorderLayout.SOUTH);
+        helpFrame.setSize(new Dimension(480, 400));
+        Rectangle frameDimension = frame.getBounds();
+        Point p = frame.getLocation();
+        Rectangle helpFrameDimension = helpFrame.getBounds();
+        helpFrame.setLocation(((frameDimension.width - helpFrameDimension.width) / 2) + (int) p.getX(),
+                ((frameDimension.height - helpFrameDimension.height) / 2) + (int) p.getY());
+        helpFrame.setVisible(true);
+        helpFrame.toFront();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4791 $");
+    }
+    
+    /**
+     * Displays the GUI. If an optics file is provided as first parameter,
+     * this will be loaded and displayed automatically.
+     * 
+     * @param args		the commandline parameters
+     * @throws Exception	if something goes wrong
+     */
+    public static void main(String[] args) throws Exception {
+      SERObject serObject = null;
+      if (args.length == 1) {
+	System.out.println("Attempting to load: " + args[0]);
+	ObjectInputStream is = null;
+	try {
+	  FileInputStream fs = new FileInputStream(args[0]);
+	  is = new ObjectInputStream(fs);
+	  serObject = (SERObject) is.readObject();
+	}
+	catch (Exception e) {
+	  serObject = null;
+	  JOptionPane.showMessageDialog(
+	      null,
+	      "Error loading file:\n" + e,
+	      "Error", JOptionPane.ERROR_MESSAGE);
+	}
+	finally {
+	  try {
+	    is.close();
+	  }
+	  catch (Exception e) {
+	    // ignored
+	  }
+	}
+      }
+      
+      // open GUI
+      new OPTICS_Visualizer(serObject, "OPTICS Visualizer - Main Window");
+    }
+
+    // *****************************************************************************************************************
+    // inner classes
+    // *****************************************************************************************************************
+
+    private class FrameListener
+        implements ActionListener, RevisionHandler {
+      
+        /**
+         * Invoked when an action occurs.
+         */
+        public void actionPerformed(ActionEvent e) {
+            if (e.getSource() == parameters || e.getSource() == toolBarButton_parameters) {
+                loadStatisticsFrame();
+            }
+
+            if (e.getSource() == about || e.getSource() == toolBarButton_about) {
+                JOptionPane.showMessageDialog(frame,
+                        "OPTICS Visualizer\n$ Rev 1.4 $\n\nCopyright (C) 2004 " +
+                        "Rainer Holzmann, Zhanna Melnikova-Albrecht",
+                        "About", JOptionPane.INFORMATION_MESSAGE);
+            }
+
+            if (e.getSource() == help || e.getSource() == toolBarButton_help) {
+                loadHelpFrame();
+            }
+
+            if (e.getSource() == exit) {
+                frame.dispose();
+            }
+
+            if (e.getSource() == open || e.getSource() == toolBarButton_open) {
+                jFileChooser.setDialogTitle("Open OPTICS-Session");
+                if (lastPath == null) {
+                    lastPath = System.getProperty("user.dir");
+                }
+                jFileChooser.setCurrentDirectory(new File(lastPath));
+                int ret = jFileChooser.showOpenDialog(frame);
+                SERObject serObject_1 = null;
+                if (ret == JFileChooser.APPROVE_OPTION) {
+                    File f = jFileChooser.getSelectedFile();
+                    try {
+                        FileInputStream fs = new FileInputStream(f.getAbsolutePath());
+                        ObjectInputStream is = new ObjectInputStream(fs);
+                        serObject_1 = (SERObject) is.readObject();
+                        is.close();
+                    } catch (FileNotFoundException e1) {
+                        JOptionPane.showMessageDialog(frame,
+                                "File not found.",
+                                "Error", JOptionPane.ERROR_MESSAGE);
+                    } catch (ClassNotFoundException e1) {
+                        JOptionPane.showMessageDialog(frame,
+                                "OPTICS-Session could not be read.",
+                                "Error", JOptionPane.ERROR_MESSAGE);
+                    } catch (IOException e1) {
+                        JOptionPane.showMessageDialog(frame,
+                                "This file does not contain a valid OPTICS-Session.",
+                                "Error", JOptionPane.ERROR_MESSAGE);
+                    }
+                    if (serObject_1 != null) {
+                        int ret_1 = JOptionPane.showConfirmDialog(frame,
+                                "Open OPTICS-Session in a new window?",
+                                "Open", 1);
+                        switch (ret_1) {
+                            case 0:
+                                new OPTICS_Visualizer(serObject_1, "OPTICS Visualizer - " + f.getName());
+                                break;
+                            case 1:
+                                serObject = serObject_1;
+                                resultVectorTable.setModel(new ResultVectorTableModel(serObject.getResultVector()));
+                                tabbedPane.setTitleAt(1,
+                                        "Graph - Epsilon: " + serObject.getEpsilon() +
+                                        ", MinPoints: " + serObject.getMinPoints());
+                                graphPanel.setResultVector(serObject.getResultVector());
+                                graphPanel.adjustSize(serObject);
+                                graphPanel.repaint();
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+            }
+
+            if (e.getSource() == save || e.getSource() == toolBarButton_save) {
+                jFileChooser.setDialogTitle("Save OPTICS-Session");
+
+                GregorianCalendar gregorianCalendar = new GregorianCalendar();
+                String timeStamp = gregorianCalendar.get(Calendar.DAY_OF_MONTH) + "-" +
+                        (gregorianCalendar.get(Calendar.MONTH) + 1) +
+                        "-" + gregorianCalendar.get(Calendar.YEAR) +
+                        "--" + gregorianCalendar.get(Calendar.HOUR_OF_DAY) +
+                        "-" + gregorianCalendar.get(Calendar.MINUTE) +
+                        "-" + gregorianCalendar.get(Calendar.SECOND);
+                String filename = "OPTICS_" + timeStamp + ".ser";
+
+                File file = new File(filename);
+                jFileChooser.setSelectedFile(file);
+                if (lastPath == null) {
+                    lastPath = System.getProperty("user.dir");
+                }
+                jFileChooser.setCurrentDirectory(new File(lastPath));
+
+                int ret = jFileChooser.showSaveDialog(frame);
+                if (ret == JFileChooser.APPROVE_OPTION) {
+                    file = jFileChooser.getSelectedFile();
+                    try {
+                        FileOutputStream fs = new FileOutputStream(file.getAbsolutePath());
+                        ObjectOutputStream os = new ObjectOutputStream(fs);
+                        os.writeObject(serObject);
+                        os.flush();
+                        os.close();
+                    } catch (IOException e1) {
+                        JOptionPane.showMessageDialog(frame,
+                                "OPTICS-Session could not be saved.",
+                                "Error", JOptionPane.ERROR_MESSAGE);
+                    }
+                }
+            }
+        }
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 4791 $");
+        }
+    }
+
+    private class SettingsPanelListener
+        implements ActionListener, RevisionHandler {
+      
+        /**
+         * Invoked when an action occurs.
+         */
+        public void actionPerformed(ActionEvent e) {
+            if (e.getSource() == coreDistanceColorButton) {
+                Color c = getSelectedColor("Select 'Core-Distance' color");
+                if (c != null) {
+                    coreDistanceColorButton.setBackground(c);
+                    graphPanel.setCoreDistanceColor(c);
+                }
+            }
+            if (e.getSource() == reachDistanceColorButton) {
+                Color c = getSelectedColor("Select 'Reachability-Distance' color");
+                if (c != null) {
+                    reachDistanceColorButton.setBackground(c);
+                    graphPanel.setReachabilityDistanceColor(c);
+                }
+            }
+            if (e.getSource() == graphBackgroundColorButton) {
+                Color c = getSelectedColor("Select 'Graph Background' color");
+                if (c != null) {
+                    graphBackgroundColorButton.setBackground(c);
+                    graphPanel.setBackground(c);
+                }
+            }
+            if (e.getSource() == resetColorButton) {
+                coreDistanceColorButton.setBackground(new Color(100, 100, 100));
+                graphPanel.setCoreDistanceColor(new Color(100, 100, 100));
+                reachDistanceColorButton.setBackground(Color.orange);
+                graphPanel.setReachabilityDistanceColor(Color.orange);
+                graphBackgroundColorButton.setBackground(new Color(255, 255, 179));
+                graphPanel.setBackground(new Color(255, 255, 179));
+                graphPanel.repaint();
+            }
+        }
+
+        private Color getSelectedColor(String title) {
+            Color c = JColorChooser.showDialog(frame, title, Color.black);
+            return c;
+        }
+        
+        /**
+         * Returns the revision string.
+         * 
+         * @return		the revision
+         */
+        public String getRevision() {
+          return RevisionUtils.extract("$Revision: 4791 $");
+        }
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/ResultVectorTableModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/ResultVectorTableModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/ResultVectorTableModel.java	(revision 29)
@@ -0,0 +1,134 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.core.FastVector;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * <p>
+ * ResultVectorTableModel.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
+ * Date: Sep 12, 2004 <br/>
+ * Time: 9:23:31 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.4 $
+ */
+public class ResultVectorTableModel
+    extends AbstractTableModel
+    implements RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -7732711470435549210L;
+
+    /**
+     * Holds the ClusterOrder (dataObjects with their r_dist and c_dist) for the GUI
+     */
+    private FastVector resultVector;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     *  Constructs a default <code>DefaultTableModel</code>
+     *  which is a table of zero columns and zero rows.
+     */
+    public ResultVectorTableModel(FastVector resultVector) {
+        this.resultVector = resultVector;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns the number of rows of this model.
+     * The number of rows is the number of dataObjects stored in the resultVector
+     * @return the number of rows of this model
+     */
+    public int getRowCount() {
+        if (resultVector == null)
+            return 0;
+        else
+            return resultVector.size();
+    }
+
+    /**
+     * Returns the number of columns of this model.
+     * The number of columns is 4 (dataObject.key, dataobject, c_dist, r_dist)
+     * @return int The number of columns of this model
+     */
+    public int getColumnCount() {
+        if (resultVector == null)
+            return 0;
+
+        return 4;
+    }
+
+    /**
+     * Returns the value for the JTable for a given position.
+     * @param row The row of the value
+     * @param column The column of the value
+     * @return value
+     * */
+    public Object getValueAt(int row, int column) {
+        DataObject dataObject = (DataObject) resultVector.elementAt(row);
+
+        switch (column) {
+            case 0:
+                return dataObject.getKey();
+            case 1:
+                return dataObject;
+            case 2:
+                return ((dataObject.getCoreDistance() == DataObject.UNDEFINED) ?
+                        "UNDEFINED" :
+                        Utils.doubleToString(dataObject.getCoreDistance(), 3, 5));
+            case 3:
+                return ((dataObject.getReachabilityDistance() == DataObject.UNDEFINED) ?
+                        "UNDEFINED" :
+                        Utils.doubleToString(dataObject.getReachabilityDistance(), 3, 5));
+            default:
+                return "";
+        }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERFileFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERFileFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERFileFilter.java	(revision 29)
@@ -0,0 +1,109 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * <p>
+ * SERFileFilter.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
+ * Date: Sep 15, 2004 <br/>
+ * Time: 6:54:56 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class SERFileFilter
+    extends FileFilter
+    implements RevisionHandler {
+
+    /**
+     * Holds the extension of the FileFilter
+     */
+    private String extension;
+
+    /**
+     * Holds the description for this File-Type
+     */
+    private String description;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    public SERFileFilter(String extension, String description) {
+        this.extension = extension;
+        this.description = description;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Whether the given file is accepted by this filter.
+     */
+    public boolean accept(File f) {
+        if (f != null) {
+            if (f.isDirectory()) {
+                return true;
+            }
+
+            String filename = f.getName();
+            int i = filename.lastIndexOf('.');
+            if (i > 0 && i < filename.length() - 1) {
+                extension = filename.substring(i + 1).toLowerCase();
+            }
+            if (extension.equals("ser")) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * The description of this filter.
+     * @see javax.swing.filechooser.FileView#getName
+     */
+    public String getDescription() {
+        return description;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/OPTICS_GUI/SERObject.java	(revision 29)
@@ -0,0 +1,180 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.OPTICS_GUI;
+
+import weka.core.FastVector;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * SERObject.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht <br/>
+ * Date: Sep 15, 2004 <br/>
+ * Time: 9:43:00 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.4 $
+ */
+public class SERObject
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -6022057864970639151L;
+  
+    private FastVector resultVector;
+    private int databaseSize;
+    private int numberOfAttributes;
+    private double epsilon;
+    private int minPoints;
+    private boolean opticsOutputs;
+    private String database_Type;
+    private String database_distanceType;
+    private int numberOfGeneratedClusters;
+    private String elapsedTime;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    public SERObject(FastVector resultVector,
+                     int databaseSize,
+                     int numberOfAttributes,
+                     double epsilon,
+                     int minPoints,
+                     boolean opticsOutputs,
+                     String database_Type,
+                     String database_distanceType,
+                     int numberOfGeneratedClusters,
+                     String elapsedTime) {
+        this.resultVector = resultVector;
+        this.databaseSize = databaseSize;
+        this.numberOfAttributes = numberOfAttributes;
+        this.epsilon = epsilon;
+        this.minPoints = minPoints;
+        this.opticsOutputs = opticsOutputs;
+        this.database_Type = database_Type;
+        this.database_distanceType = database_distanceType;
+        this.numberOfGeneratedClusters = numberOfGeneratedClusters;
+        this.elapsedTime = elapsedTime;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns the resultVector
+     * @return FastVector resultVector
+     */
+    public FastVector getResultVector() {
+        return resultVector;
+    }
+
+    /**
+     * Returns the database's size
+     * @return int databaseSize
+     */
+    public int getDatabaseSize() {
+        return databaseSize;
+    }
+
+    /**
+     * Returns the number of Attributes of the specified database
+     * @return int numberOfAttributes
+     */
+    public int getNumberOfAttributes() {
+        return numberOfAttributes;
+    }
+
+    /**
+     * Returns the value of epsilon
+     * @return double epsilon
+     */
+    public double getEpsilon() {
+        return epsilon;
+    }
+
+    /**
+     * Returns the number of minPoints
+     * @return int minPoints
+     */
+    public int getMinPoints() {
+        return minPoints;
+    }
+
+    /**
+     * Returns the flag for writing actions
+     * @return True if the outputs are to write to a file, else false
+     */
+    public boolean isOpticsOutputs() {
+        return opticsOutputs;
+    }
+
+    /**
+     * Returns the type of the used index (database)
+     * @return String Index-type
+     */
+    public String getDatabase_Type() {
+        return database_Type;
+    }
+
+    /**
+     * Returns the distance-type
+     * @return String Distance-type
+     */
+    public String getDatabase_distanceType() {
+        return database_distanceType;
+    }
+
+    /**
+     * Returns the number of generated clusters
+     * @return int numberOfGeneratedClusters
+     */
+    public int getNumberOfGeneratedClusters() {
+        return numberOfGeneratedClusters;
+    }
+
+    /**
+     * Returns the elapsed-time
+     * @return String elapsedTime
+     */
+    public String getElapsedTime() {
+        return elapsedTime + " sec";
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.4 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/EpsilonRange_ListElement.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/EpsilonRange_ListElement.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/EpsilonRange_ListElement.java	(revision 29)
@@ -0,0 +1,106 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Utils;
+
+import weka.clusterers.forOPTICSAndDBScan.DataObjects.DataObject;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * <p>
+ * EpsilonRange_ListElement.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Sep 7, 2004 <br/>
+ * Time: 2:12:34 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class EpsilonRange_ListElement
+    implements RevisionHandler {
+
+    /**
+     * Holds the dataObject
+     */
+    private DataObject dataObject;
+
+    /**
+     * Holds the distance that was calculated for this dataObject
+     */
+    private double distance;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Constructs a new Element that is stored in the ArrayList which is
+     * built in the k_nextNeighbourQuery-method from a specified database.
+     * This structure is chosen to deliver not only the DataObjects that
+     * are within the epsilon-range but also deliver the distances that
+     * were calculated. This reduces the amount of distance-calculations
+     * within some data-mining-algorithms.
+     * @param distance The calculated distance for this dataObject
+     * @param dataObject A dataObject that is within the epsilon-range
+     */
+    public EpsilonRange_ListElement(double distance, DataObject dataObject) {
+        this.distance = distance;
+        this.dataObject = dataObject;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns the distance that was calulcated for this dataObject
+     * (The distance between this dataObject and the dataObject for which an epsilon-range-query
+     * was performed.)
+     * @return distance
+     */
+    public double getDistance() {
+        return distance;
+    }
+
+    /**
+     * Returns this dataObject
+     * @return dataObject
+     */
+    public DataObject getDataObject() {
+        return dataObject;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueue.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueue.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueue.java	(revision 29)
@@ -0,0 +1,164 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Utils;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.ArrayList;
+
+/**
+ * <p>
+ * PriorityQueue.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 27, 2004 <br/>
+ * Time: 5:36:35 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class PriorityQueue
+    implements RevisionHandler {
+
+    /**
+     * Used to store the binary heap
+     */
+    private ArrayList queue;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Creates a new PriorityQueue backed on a binary heap. The queue is
+     * dynamically growing and shrinking and it is descending, that is: the highest
+     * priority is always in the root.
+     */
+    public PriorityQueue() {
+        queue = new ArrayList();
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Adds a new Object to the queue
+     * @param priority The priority associated with the object
+     * @param o
+     */
+    public void add(double priority, Object o) {
+        queue.add(new PriorityQueueElement(priority, o));
+        heapValueUpwards();
+    }
+
+    /**
+     * Returns the priority for the object at the specified index
+     * @param index the index of the object
+     * @return priority
+     */
+    public double getPriority(int index) {
+        return ((PriorityQueueElement) queue.get(index)).getPriority();
+    }
+
+    /**
+     * Restores the heap after inserting a new object
+     */
+    private void heapValueUpwards() {
+        int a = size();
+        int c = a / 2;
+
+        PriorityQueueElement recentlyInsertedElement = (PriorityQueueElement) queue.get(a - 1);
+
+        while (c > 0 && getPriority(c - 1) < recentlyInsertedElement.getPriority()) {
+            queue.set(a - 1, queue.get(c - 1));       //shift parent-node down
+            a = c;                                    //(c <= 0) => no parent-node remains
+            c = a / 2;
+        }
+        queue.set(a - 1, recentlyInsertedElement);
+    }
+
+    /**
+     * Restores the heap after removing the next element
+     */
+    private void heapValueDownwards() {
+        int a = 1;
+        int c = 2 * a;           //descendant
+
+        PriorityQueueElement priorityQueueElement = (PriorityQueueElement) queue.get(a - 1);
+
+        if (c < size() && (getPriority(c) > getPriority(c - 1))) c++;
+
+        while (c <= size() && getPriority(c - 1) > priorityQueueElement.getPriority()) {
+            queue.set(a - 1, queue.get(c - 1));
+            a = c;
+            c = 2 * a;
+            if (c < size() && (getPriority(c) > getPriority(c - 1))) c++;
+        }
+        queue.set(a - 1, priorityQueueElement);
+    }
+
+    /**
+     * Returns the queue's size
+     * @return size
+     */
+    public int size() {
+        return queue.size();
+    }
+
+    /**
+     * Tests, if the queue has some more elements left
+     * @return true, if there are any elements left, else false
+     */
+    public boolean hasNext() {
+        return !(size() == 0);
+    }
+
+    /**
+     * Returns the element with the highest priority
+     * @return next element
+     */
+    public PriorityQueueElement next() {
+        PriorityQueueElement next = (PriorityQueueElement) queue.get(0);
+        queue.set(0, queue.get(size() - 1));
+        queue.remove(size() - 1);
+        if (hasNext()) {
+            heapValueDownwards();
+        }
+        return next;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueueElement.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueueElement.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/PriorityQueueElement.java	(revision 29)
@@ -0,0 +1,92 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Utils;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * <p>
+ * PriorityQueueElement.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 31, 2004 <br/>
+ * Time: 6:43:18 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class PriorityQueueElement
+    implements RevisionHandler {
+
+    /**
+     * Holds the priority for the object (in this case: the distance)
+     */
+    private double priority;
+
+    /**
+     * Holds the original object
+     */
+    private Object o;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+    public PriorityQueueElement(double priority, Object o) {
+        this.priority = priority;
+        this.o = o;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns the priority for this object
+     * @return priority
+     */
+    public double getPriority() {
+        return priority;
+    }
+
+    /**
+     * Returns the object
+     * @return
+     */
+    public Object getObject() {
+        return o;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueue.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueue.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueue.java	(revision 29)
@@ -0,0 +1,187 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Utils;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.ArrayList;
+import java.util.TreeMap;
+
+/**
+ * <p>
+ * UpdateQueue.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 27, 2004 <br/>
+ * Time: 5:36:35 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class UpdateQueue
+    implements RevisionHandler {
+
+    /**
+     * Used to store the binary heap
+     */
+    private ArrayList queue;
+
+    /**
+     * Used to get efficient access to the stored Objects
+     */
+    private TreeMap objectPositionsInHeap;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+
+    /**
+     * Creates a new PriorityQueue (backed on a binary heap) with the ability to efficiently
+     * update the priority of the stored objects in the heap. The ascending (!) queue is
+     * dynamically growing and shrinking.
+     */
+    public UpdateQueue() {
+        queue = new ArrayList();
+        objectPositionsInHeap = new TreeMap();
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Adds a new Object to the queue
+     * @param priority The priority associated with the object (in this case: the reachability-distance)
+     * @param objectKey The key for this object
+     * @param o
+     */
+    public void add(double priority, Object o, String objectKey) {
+        int objectPosition = 0;
+
+        if (objectPositionsInHeap.containsKey(objectKey)) {
+            objectPosition = ((Integer) objectPositionsInHeap.get(objectKey)).intValue();
+            if (((UpdateQueueElement) queue.get(objectPosition)).getPriority() <= priority) return;
+            queue.set(objectPosition++, new UpdateQueueElement(priority, o, objectKey));
+        } else {
+            queue.add(new UpdateQueueElement(priority, o, objectKey));
+            objectPosition = size();
+        }
+        heapValueUpwards(objectPosition);
+    }
+
+    /**
+     * Returns the priority for the object at the specified index
+     * @param index the index of the object
+     * @return priority
+     */
+    public double getPriority(int index) {
+        return ((UpdateQueueElement) queue.get(index)).getPriority();
+    }
+
+    /**
+     * Restores the heap after inserting a new object
+     */
+    private void heapValueUpwards(int pos) {
+        int a = pos;
+        int c = a / 2;
+
+        UpdateQueueElement recentlyInsertedElement = (UpdateQueueElement) queue.get(a - 1);
+
+        /** ascending order! */
+        while (c > 0 && getPriority(c - 1) > recentlyInsertedElement.getPriority()) {
+            queue.set(a - 1, queue.get(c - 1));       //shift parent-node down
+            objectPositionsInHeap.put(((UpdateQueueElement) queue.get(a - 1)).getObjectKey(), new Integer(a - 1));
+            a = c;                                    //(c <= 0) => no parent-node remains
+            c = a / 2;
+        }
+        queue.set(a - 1, recentlyInsertedElement);
+        objectPositionsInHeap.put(((UpdateQueueElement) queue.get(a - 1)).getObjectKey(), new Integer(a - 1));
+    }
+
+    /**
+     * Restores the heap after removing the next element
+     */
+    private void heapValueDownwards() {
+        int a = 1;
+        int c = 2 * a;           //descendant
+
+        UpdateQueueElement updateQueueElement = (UpdateQueueElement) queue.get(a - 1);
+
+        if (c < size() && (getPriority(c) < getPriority(c - 1))) c++;
+
+        while (c <= size() && getPriority(c - 1) < updateQueueElement.getPriority()) {
+            queue.set(a - 1, queue.get(c - 1));
+            objectPositionsInHeap.put(((UpdateQueueElement) queue.get(a - 1)).getObjectKey(), new Integer(a - 1));
+            a = c;
+            c = 2 * a;
+            if (c < size() && (getPriority(c) < getPriority(c - 1))) c++;
+        }
+        queue.set(a - 1, updateQueueElement);
+        objectPositionsInHeap.put(((UpdateQueueElement) queue.get(a - 1)).getObjectKey(), new Integer(a - 1));
+    }
+
+    /**
+     * Returns the queue's size
+     * @return size
+     */
+    public int size() {
+        return queue.size();
+    }
+
+    /**
+     * Tests, if the queue has some more elements left
+     * @return true, if there are any elements left, else false
+     */
+    public boolean hasNext() {
+        return !(queue.size() == 0);
+    }
+
+    /**
+     * Returns the element with the lowest priority
+     * @return next element
+     */
+    public UpdateQueueElement next() {
+        UpdateQueueElement next = (UpdateQueueElement) queue.get(0);
+        queue.set(0, queue.get(size() - 1));
+        queue.remove(size() - 1);
+        objectPositionsInHeap.remove(next.getObjectKey());
+        if (hasNext()) {
+            heapValueDownwards();
+        }
+        return next;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueueElement.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueueElement.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/forOPTICSAndDBScan/Utils/UpdateQueueElement.java	(revision 29)
@@ -0,0 +1,106 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyright (C) 2004
+ *    & Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ *    & Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ *    & Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ */
+
+package weka.clusterers.forOPTICSAndDBScan.Utils;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+/**
+ * <p>
+ * UpdateQueueElement.java <br/>
+ * Authors: Rainer Holzmann, Zhanna Melnikova-Albrecht, Matthias Schubert <br/>
+ * Date: Aug 31, 2004 <br/>
+ * Time: 6:43:18 PM <br/>
+ * $ Revision 1.4 $ <br/>
+ * </p>
+ *
+ * @author Matthias Schubert (schubert@dbs.ifi.lmu.de)
+ * @author Zhanna Melnikova-Albrecht (melnikov@cip.ifi.lmu.de)
+ * @author Rainer Holzmann (holzmann@cip.ifi.lmu.de)
+ * @version $Revision: 1.3 $
+ */
+public class UpdateQueueElement
+    implements RevisionHandler {
+
+    /**
+     * Holds the priority for the object (in this case: the reachability-distance)
+     */
+    private double priority;
+
+    /**
+     * Holds the original object
+     */
+    private Object o;
+
+    /**
+     * Holds the key for this object
+     */
+    private String objectKey;
+
+    // *****************************************************************************************************************
+    // constructors
+    // *****************************************************************************************************************
+    public UpdateQueueElement(double priority, Object o, String objectKey) {
+        this.priority = priority;
+        this.o = o;
+        this.objectKey = objectKey;
+    }
+
+    // *****************************************************************************************************************
+    // methods
+    // *****************************************************************************************************************
+
+    /**
+     * Returns the priority for this object
+     * @return priority
+     */
+    public double getPriority() {
+        return priority;
+    }
+
+    /**
+     * Returns the object
+     * @return
+     */
+    public Object getObject() {
+        return o;
+    }
+
+    /**
+     * Returns the key
+     * @return objectKey
+     */
+    public String getObjectKey() {
+        return objectKey;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 1.3 $");
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/clusterers/sIB.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/clusterers/sIB.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/clusterers/sIB.java	(revision 29)
@@ -0,0 +1,1244 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    sIB.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.clusterers;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.Matrix;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Cluster data using the sequential information bottleneck algorithm.<br/>
+ * <br/>
+ * Note: only hard clustering scheme is supported. sIB assign for each instance the cluster that have the minimum cost/distance to the instance. The trade-off beta is set to infinite so 1/beta is zero.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Noam Slonim, Nir Friedman, Naftali Tishby: Unsupervised document classification using sequential information maximization. In: Proceedings of the 25th International ACM SIGIR Conference on Research and Development in Information Retrieval, 129-136, 2002.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Slonim2002,
+ *    author = {Noam Slonim and Nir Friedman and Naftali Tishby},
+ *    booktitle = {Proceedings of the 25th International ACM SIGIR Conference on Research and Development in Information Retrieval},
+ *    pages = {129-136},
+ *    title = {Unsupervised document classification using sequential information maximization},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  maximum number of iterations
+ *  (default 100).</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  minimum number of changes in a single iteration
+ *  (default 0).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  number of clusters.
+ *  (default 2).</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  number of restarts.
+ *  (default 5).</pre>
+ * 
+ * <pre> -U
+ *  set not to normalize the data
+ *  (default true).</pre>
+ * 
+ * <pre> -V
+ *  set to output debug info
+ *  (default false).</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Random number seed.
+ *  (default 1)</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Noam Slonim
+ * @author <a href="mailto:lh92@cs.waikato.ac.nz">Anna Huang</a> 
+ * @version $Revision: 5987 $
+ */
+public class sIB
+  extends RandomizableClusterer
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long    serialVersionUID = -8652125897352654213L;
+  
+  /**
+   * Inner class handling status of the input data
+   * 
+   * @see Serializable
+   */
+  private class Input
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2464453171263384037L;
+    
+    /** Prior probability of each instance */
+    private double[] Px;
+    
+    /** Prior probability of each attribute */
+    private double[] Py;
+    
+    /** Joint distribution of attribute and instance */
+    private Matrix Pyx;
+    
+    /** P[y|x] */
+    private Matrix Py_x;
+    
+    /** Mutual information between the instances and the attributes */
+    private double Ixy;
+    
+    /** Entropy of the attributes */ 
+    private double Hy;
+    
+    /** Entropy of the instances */
+    private double Hx;
+    
+    /** Sum values of the dataset */
+    private double sumVals;
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+  
+  /**
+   * Internal class handling the whole partition
+   * 
+   * @see Serializable
+   */
+  private class Partition
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 4957194978951259946L;
+    
+    /** Cluster assignment for each instance */
+    private int[]    Pt_x;
+    
+    /** Prior probability of each cluster */
+    private double[] Pt;
+    
+    /** sIB equation score, to evaluate the quality of the partition */
+    private double   L;
+    
+    /** Number of changes during the generation of this partition */
+    private int      counter;
+    
+    /** Attribute probablities for each cluster */
+    private Matrix   Py_t;
+
+    /** 
+     * Create a new empty <code>Partition</code> instance.
+     */
+    public Partition() {
+      Pt_x = new int[m_numInstances];
+      for (int i = 0; i < m_numInstances; i++) {
+	Pt_x[i] = -1;
+      }
+      Pt = new double[m_numCluster];
+      Py_t = new Matrix(m_numAttributes, m_numCluster);
+      counter = 0;
+    }
+
+    /**
+     * Find all the instances that have been assigned to cluster i
+     * @param i index of the cluster
+     * @return an arraylist of the instance ids that have been assigned to cluster i
+     */
+    private ArrayList<Integer> find(int i) {
+      ArrayList<Integer> indices = new ArrayList<Integer>();
+      for (int x = 0; x < Pt_x.length; x++) {
+	if (Pt_x[x] == i) {
+	  indices.add(x);
+	}
+      }
+      return indices;
+    }
+
+    /**
+     * Find the size of the cluster i
+     * @param i index of the cluster
+     * @return the size of cluster i
+     */
+    private int size(int i) {
+      int count = 0;
+      for (int x = 0; x < Pt_x.length; x++) {
+	if (Pt_x[x] == i) {
+	  count++;
+	}
+      }
+      return count;
+    }
+
+    /**
+     * Copy the current partition into T
+     * @param T the target partition object
+     */
+    private void copy(Partition T) {
+      if (T == null) {
+	T = new Partition();
+      }
+      System.arraycopy(Pt_x, 0, T.Pt_x, 0, Pt_x.length);
+      System.arraycopy(Pt, 0, T.Pt, 0, Pt.length);
+      T.L = L;
+      T.counter = counter;
+
+      double[][] mArray = Py_t.getArray();
+      double[][] tgtArray = T.Py_t.getArray();
+      for (int i = 0; i < mArray.length; i++) {
+	System.arraycopy(mArray[i], 0, tgtArray[i], 0, mArray[0].length);
+      }
+    }
+
+    /**
+     * Output the current partition
+     * @param insts
+     * @return a string that describes the partition
+     */
+    public String toString() {
+      StringBuffer text = new StringBuffer();
+      text.append("score (L) : " + Utils.doubleToString(L, 4) + "\n");
+      text.append("number of changes : " + counter +"\n");
+      for (int i = 0; i < m_numCluster; i++) {
+	text.append("\nCluster "+i+"\n");
+	text.append("size : "+size(i)+"\n");
+	text.append("prior prob : "+Utils.doubleToString(Pt[i], 4)+"\n");
+      }
+      return text.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /** Training data */
+  private Instances m_data;
+
+  /** Number of clusters */
+  private int m_numCluster = 2;
+
+  /** Number of restarts */
+  private int m_numRestarts = 5;
+  
+  /** Verbose? */
+  private boolean m_verbose = false;
+
+  /** Uniform prior probability of the documents */
+  private boolean m_uniformPrior = true;
+
+  /** Max number of iterations during each restart */
+  private int m_maxLoop = 100;
+
+  /** Minimum number of changes */
+  private int m_minChange = 0;
+
+  /** Globally replace missing values */
+  private ReplaceMissingValues m_replaceMissing;
+
+  /** Number of instances */
+  private int m_numInstances;
+
+  /** Number of attributes */
+  private int m_numAttributes;
+  
+  /** Randomly generate initial partition */
+  private Random random;
+  
+  /** Holds the best partition built */
+  private Partition bestT;
+  
+  /** Holds the statistics about the input dataset */
+  private Input input;
+  
+  /**
+   * Generates a clusterer. 
+   *
+   * @param data the training instances 
+   * @throws Exception if something goes wrong
+   */
+  public void buildClusterer(Instances data) throws Exception {
+    // can clusterer handle the data ?
+    getCapabilities().testWithFail(data);
+
+    m_replaceMissing = new ReplaceMissingValues();
+    Instances instances = new Instances(data);
+    instances.setClassIndex(-1);
+    m_replaceMissing.setInputFormat(instances);
+    data = weka.filters.Filter.useFilter(instances, m_replaceMissing);
+    instances = null;
+    
+    // initialize all fields that are not being set via options
+    m_data = data;
+    m_numInstances = m_data.numInstances();
+    m_numAttributes = m_data.numAttributes();
+    random = new Random(getSeed()); 
+    
+    // initialize the statistics of the input training data
+    input = sIB_ProcessInput();
+    
+    // object to hold the best partition
+    bestT = new Partition();
+
+    // the real clustering
+    double bestL = Double.NEGATIVE_INFINITY;
+    for (int k = 0; k < m_numRestarts; k++) {   
+      if(m_verbose) {
+	System.out.format("restart number %s...\n", k);
+      }
+      
+      // initialize the partition and optimize it
+      Partition tmpT = sIB_InitT(input);      
+      tmpT = sIB_OptimizeT(tmpT, input);
+
+      // if a better partition is found, save it
+      if (tmpT.L > bestL) {
+	tmpT.copy(bestT);
+	bestL = bestT.L;
+      }
+      
+      if(m_verbose) {
+	System.out.println("\nPartition status : ");
+	System.out.println("------------------");
+	System.out.println(tmpT.toString()+"\n");
+      }
+    }
+    
+    if(m_verbose){
+      System.out.println("\nBest Partition");
+      System.out.println("===============");      
+      System.out.println(bestT.toString());
+    }
+    
+    // save memory
+    m_data = new Instances(m_data, 0);
+  }
+  
+  /**
+   * Cluster a given instance, this is the method defined in Clusterer
+   * interface do nothing but just return the cluster assigned to it
+   */
+  public int clusterInstance(Instance instance) throws Exception {
+    double prior = (double) 1 / input.sumVals;
+    double[] distances = new double[m_numCluster]; 
+    for(int i = 0; i < m_numCluster; i++){
+      double Pnew = bestT.Pt[i] + prior;
+      double pi1 = prior / Pnew;
+      double pi2 = bestT.Pt[i] / Pnew;
+      distances[i] = Pnew * JS(instance, i, pi1, pi2);
+    }
+    return Utils.minIndex(distances);
+  }
+
+  /**
+   * Process the input and compute the statistics of the training data
+   * @return an Input object which holds the statistics about the training data
+   */
+  private Input sIB_ProcessInput() {
+    double valSum = 0.0;
+    for (int i = 0; i < m_numInstances; i++) {
+      valSum = 0.0;
+      for (int v = 0; v < m_data.instance(i).numValues(); v++) {
+	valSum += m_data.instance(i).valueSparse(v);
+      }
+      if (valSum <= 0) {
+	if(m_verbose){
+	  System.out.format("Instance %s sum of value = %s <= 0, removed.\n", i, valSum);
+	}
+	m_data.delete(i);
+	m_numInstances--;
+      }
+    }
+
+    // get the term-document matrix
+    Input input = new Input();
+    input.Py_x = getTransposedNormedMatrix(m_data);    
+    if (m_uniformPrior) {
+      input.Pyx = input.Py_x.copy();  
+      normalizePrior(m_data);
+    } 
+    else {
+      input.Pyx = getTransposedMatrix(m_data);
+    }
+    input.sumVals = getTotalSum(m_data);    
+    input.Pyx.timesEquals((double) 1 / input.sumVals);
+
+    // prior probability of documents, ie. sum the columns from the Pyx matrix
+    input.Px = new double[m_numInstances];
+    for (int i = 0; i < m_numInstances; i++) {
+      for (int j = 0; j < m_numAttributes; j++) {
+	input.Px[i] += input.Pyx.get(j, i);
+      }           
+    }
+
+    // prior probability of terms, ie. sum the rows from the Pyx matrix
+    input.Py = new double[m_numAttributes];
+    for (int i = 0; i < input.Pyx.getRowDimension(); i++) {
+      for (int j = 0; j < input.Pyx.getColumnDimension(); j++) {
+	input.Py[i] += input.Pyx.get(i, j);
+      }
+    }
+    
+    MI(input.Pyx, input);
+    return input;
+  }
+
+  /**
+   * Initialize the partition
+   * @param input object holding the statistics of the training data
+   * @return the initialized partition
+   */
+  private Partition sIB_InitT(Input input) {
+    Partition T = new Partition();
+    int avgSize = (int) Math.ceil((double) m_numInstances / m_numCluster);    
+    
+    ArrayList<Integer> permInstsIdx = new ArrayList<Integer>();
+    ArrayList<Integer> unassigned = new ArrayList<Integer>();
+    for (int i = 0; i < m_numInstances; i++) {
+      unassigned.add(i);
+    }
+    while (unassigned.size() != 0) {
+      int t = random.nextInt(unassigned.size());
+      permInstsIdx.add(unassigned.get(t));
+      unassigned.remove(t);
+    }
+
+    for (int i = 0; i < m_numCluster; i++) {
+      int r2 = avgSize > permInstsIdx.size() ? permInstsIdx.size() : avgSize;
+      for (int j = 0; j < r2; j++) {
+	T.Pt_x[permInstsIdx.get(j)] = i;
+      }      
+      for (int j = 0; j < r2; j++) {	
+	permInstsIdx.remove(0);
+      }
+    }
+    
+    // initialize the prior prob of each cluster, and the probability 
+    // for each attribute within the cluster
+    for (int i = 0; i < m_numCluster; i++) {
+      ArrayList<Integer> indices = T.find(i);
+      for (int j = 0; j < indices.size(); j++) {
+	T.Pt[i] += input.Px[indices.get(j)];
+      }
+      double[][] mArray = input.Pyx.getArray();
+      for (int j = 0; j < m_numAttributes; j++) {
+	double sum = 0.0;
+	for (int k = 0; k < indices.size(); k++) {
+	  sum += mArray[j][indices.get(k)];
+	}
+	sum /= T.Pt[i];
+	T.Py_t.set(j, i, sum);
+      }
+    }
+    
+    if(m_verbose) {
+      System.out.println("Initializing...");
+    }
+    return T;
+  }
+
+  /**
+   * Optimize the partition
+   * @param tmpT partition to be optimized
+   * @param input object describing the statistics of the training dataset
+   * @return the optimized partition
+   */
+  private Partition sIB_OptimizeT(Partition tmpT, Input input) {
+    boolean done = false;
+    int change = 0, loopCounter = 0;
+    if(m_verbose) {
+      System.out.println("Optimizing...");
+      System.out.println("-------------");
+    }
+    while (!done) {
+      change = 0;
+      for (int i = 0; i < m_numInstances; i++) {
+	int old_t = tmpT.Pt_x[i];
+	// If the current cluster only has one instance left, leave it.
+	if (tmpT.size(old_t) == 1) {
+	  if(m_verbose){
+	    System.out.format("cluster %s has only 1 doc remain\n", old_t);
+	  }
+	  continue;
+	}
+	// draw the instance out from its previous cluster
+	reduce_x(i, old_t, tmpT, input);
+	
+	// re-cluster the instance
+	int new_t = clusterInstance(i, input, tmpT);	
+	if (new_t != old_t) {	  
+	  change++;
+	  updateAssignment(i, new_t, tmpT, input.Px[i], input.Py_x);
+	}
+      }
+      
+      tmpT.counter += change;
+      if(m_verbose){
+	System.out.format("iteration %s , changes : %s\n", loopCounter, change);
+      }
+      done = checkConvergence(change, loopCounter);
+      loopCounter++;
+    }
+
+    // compute the sIB score
+    tmpT.L = sIB_local_MI(tmpT.Py_t, tmpT.Pt);
+    if(m_verbose){
+      System.out.format("score (L) : %s \n", Utils.doubleToString(tmpT.L, 4));
+    }
+    return tmpT;
+  }
+
+  /**
+   * Draw a instance out from a cluster. 
+   * @param instIdx index of the instance to be drawn out
+   * @param t index of the cluster which the instance previously belong to
+   * @param T the current working partition
+   * @param input the input statistics
+   */
+  private void reduce_x(int instIdx, int t, Partition T, Input input) {
+    // Update the prior probability of the cluster
+    ArrayList<Integer> indices = T.find(t);
+    double sum = 0.0;
+    for (int i = 0; i < indices.size(); i++) {
+      if (indices.get(i) == instIdx)
+	continue;      
+      sum += input.Px[indices.get(i)];
+    }
+    T.Pt[t] = sum;
+    
+    if (T.Pt[t] < 0) {
+      System.out.format("Warning: probability < 0 (%s)\n", T.Pt[t]);
+      T.Pt[t] = 0;
+    }
+    
+    // Update prob of each attribute in the cluster    
+    double[][] mArray = input.Pyx.getArray();
+    for (int i = 0; i < m_numAttributes; i++) {
+      sum = 0.0;
+      for (int j = 0; j < indices.size(); j++) {
+	if (indices.get(j) == instIdx)
+	  continue;
+	sum += mArray[i][indices.get(j)];
+      }
+      T.Py_t.set(i, t, sum / T.Pt[t]);
+    }    
+  }
+
+  /**
+   * Put an instance into a new cluster and update.
+   * @param instIdx instance to be updated
+   * @param newt index of the new cluster this instance has been assigned to
+   * @param T the current working partition
+   * @param Px an array of prior probabilities of the instances
+   */
+  private void updateAssignment(int instIdx, int newt, Partition T, double Px, Matrix Py_x) {    
+    T.Pt_x[instIdx] = newt;
+    
+    // update probability of attributes in the cluster 
+    double mass = Px + T.Pt[newt];
+    double pi1 = Px / mass;
+    double pi2 = T.Pt[newt] / mass;
+    for (int i = 0; i < m_numAttributes; i++) {
+      T.Py_t.set(i, newt, pi1 * Py_x.get(i, instIdx) + pi2 * T.Py_t.get(i, newt));
+    }
+
+    T.Pt[newt] = mass;
+  }
+  
+  /**
+   * Check whether the current iteration is converged
+   * @param change number of changes in current iteration
+   * @param loops number of iterations done
+   * @return true if the iteration is converged, false otherwise
+   */
+  private boolean checkConvergence(int change, int loops) {
+    if (change <= m_minChange || loops >= m_maxLoop) {
+      if(m_verbose){
+	System.out.format("\nsIB converged after %s iterations with %s changes\n", loops,
+	  change);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Cluster an instance into the nearest cluster. 
+   * @param instIdx Index of the instance to be clustered
+   * @param input Object which describe the statistics of the training dataset
+   * @param T Partition
+   * @return index of the cluster that has the minimum distance to the instance
+   */
+  private int clusterInstance(int instIdx, Input input, Partition T) {
+    double[] distances = new double[m_numCluster];
+    for (int i = 0; i < m_numCluster; i++) {
+      double Pnew = input.Px[instIdx] + T.Pt[i];
+      double pi1 = input.Px[instIdx] / Pnew;
+      double pi2 = T.Pt[i] / Pnew;
+      distances[i] = Pnew * JS(instIdx, input, T, i, pi1, pi2);
+    }
+    return Utils.minIndex(distances);    
+  }
+
+  /**
+   * Compute the JS divergence between an instance and a cluster, used for training data
+   * @param instIdx index of the instance
+   * @param input statistics of the input data
+   * @param T the whole partition
+   * @param t index of the cluster 
+   * @param pi1
+   * @param pi2
+   * @return the JS divergence
+   */
+  private double JS(int instIdx, Input input, Partition T, int t, double pi1, double pi2) {
+    if (Math.min(pi1, pi2) <= 0) {
+      System.out.format("Warning: zero or negative weights in JS calculation! (pi1 %s, pi2 %s)\n", pi1, pi2);
+      return 0;
+    }
+    Instance inst = m_data.instance(instIdx);
+    double kl1 = 0.0, kl2 = 0.0, tmp = 0.0;    
+    for (int i = 0; i < inst.numValues(); i++) {
+      tmp = input.Py_x.get(inst.index(i), instIdx);      
+      if(tmp != 0) {
+	kl1 += tmp * Math.log(tmp / (tmp * pi1 + pi2 * T.Py_t.get(inst.index(i), t)));
+      }
+    }
+    for (int i = 0; i < m_numAttributes; i++) {
+      if ((tmp = T.Py_t.get(i, t)) != 0) {
+	kl2 += tmp * Math.log(tmp / (input.Py_x.get(i, instIdx) * pi1 + pi2 * tmp));
+      }
+    }    
+    return pi1 * kl1 + pi2 * kl2;
+  }
+  
+  /**
+   * Compute the JS divergence between an instance and a cluster, used for test data
+   * @param inst instance to be clustered
+   * @param t index of the cluster
+   * @param pi1
+   * @param pi2
+   * @return the JS divergence
+   */
+  private double JS(Instance inst, int t, double pi1, double pi2) {
+    if (Math.min(pi1, pi2) <= 0) {
+      System.out.format("Warning: zero or negative weights in JS calculation! (pi1 %s, pi2 %s)\n", pi1, pi2);
+      return 0;
+    }
+    double sum = Utils.sum(inst.toDoubleArray());
+    double kl1 = 0.0, kl2 = 0.0, tmp = 0.0;    
+    for (int i = 0; i < inst.numValues(); i++) {
+      tmp = inst.valueSparse(i) / sum;      
+      if(tmp != 0) {
+	kl1 += tmp * Math.log(tmp / (tmp * pi1 + pi2 * bestT.Py_t.get(inst.index(i), t)));
+      }
+    }
+    for (int i = 0; i < m_numAttributes; i++) {
+      if ((tmp = bestT.Py_t.get(i, t)) != 0) {
+	kl2 += tmp * Math.log(tmp / (inst.value(i) * pi1  / sum + pi2 * tmp));
+      }
+    }    
+    return pi1 * kl1 + pi2 * kl2;
+  }
+  
+  /**
+   * Compute the sIB score
+   * @param m a term-cluster matrix, with m[i, j] is the probability of term i given cluster j  
+   * @param Pt an array of cluster prior probabilities
+   * @return the sIB score which indicates the quality of the partition
+   */
+  private double sIB_local_MI(Matrix m, double[] Pt) {
+    double Hy = 0.0, Ht = 0.0;
+    for (int i = 0; i < Pt.length; i++) {
+      Ht += Pt[i] * Math.log(Pt[i]);
+    }
+    Ht = -Ht;
+    
+    for (int i = 0; i < m_numAttributes; i++) {
+      double Py = 0.0;
+      for (int j = 0; j < m_numCluster; j++) {
+	Py += m.get(i, j) * Pt[j];	
+      }     
+      if(Py == 0) continue;
+      Hy += Py * Math.log(Py);
+    }
+    Hy = -Hy;
+    
+    double Hyt = 0.0, tmp = 0.0;
+    for (int i = 0; i < m.getRowDimension(); i++) {
+      for (int j = 0; j < m.getColumnDimension(); j++) {
+	if ((tmp = m.get(i, j)) == 0 || Pt[j] == 0) {
+	  continue;
+	}
+	tmp *= Pt[j];
+	Hyt += tmp * Math.log(tmp);
+      }
+    }
+    return Hy + Ht + Hyt;
+  }
+  
+  /**
+   * Get the sum of value of the dataset
+   * @param data set of instances to handle
+   * @return sum of all the attribute values for all the instances in the dataset
+   */
+  private double getTotalSum(Instances data) {
+    double sum = 0.0;
+    for (int i = 0; i < data.numInstances(); i++) {
+      for (int v = 0; v < data.instance(i).numValues(); v++) {
+	sum += data.instance(i).valueSparse(v);
+      }
+    }
+    return sum;
+  }
+
+  /**
+   * Transpose the document-term matrix to term-document matrix
+   * @param data instances with document-term info
+   * @return a term-document matrix transposed from the input dataset
+   */
+  private Matrix getTransposedMatrix(Instances data) {
+    double[][] temp = new double[data.numAttributes()][data.numInstances()];
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance inst = data.instance(i);
+      for (int v = 0; v < inst.numValues(); v++) {
+	temp[inst.index(v)][i] = inst.valueSparse(v);
+      }
+    }
+    Matrix My_x = new Matrix(temp);
+    return My_x;
+  }
+
+  /**
+   * Normalize the document vectors
+   * @param data instances to be normalized
+   */
+  private void normalizePrior(Instances data) {
+    for (int i = 0; i < data.numInstances(); i++) {
+      normalizeInstance(data.instance(i));
+    }
+  }
+  
+  /**
+   * Normalize the instance
+   * @param inst instance to be normalized
+   * @return a new Instance with normalized values
+   */
+  private Instance normalizeInstance(Instance inst) {
+    double[] vals = inst.toDoubleArray();
+    double sum = Utils.sum(vals);
+    for(int i = 0; i < vals.length; i++) {
+      vals[i] /= sum;
+    }
+    return new DenseInstance(inst.weight(), vals);
+  }
+  
+  private Matrix getTransposedNormedMatrix(Instances data) {
+    Matrix matrix = new Matrix(data.numAttributes(), data.numInstances());
+    for(int i = 0; i < data.numInstances(); i++){
+      double[] vals = data.instance(i).toDoubleArray();
+      double sum = Utils.sum(vals);
+      for (int v = 0; v < vals.length; v++) {
+	vals[v] /= sum;
+	matrix.set(v, i, vals[v]);
+      }      
+    }
+    return matrix;
+  }
+  
+  /**
+   * Compute the MI between instances and attributes
+   * @param m the term-document matrix
+   * @param input object that describes the statistics about the training data
+   */
+  private void MI(Matrix m, Input input){    
+    int minDimSize = m.getColumnDimension() < m.getRowDimension() ? m.getColumnDimension() : m.getRowDimension();
+    if(minDimSize < 2){
+      System.err.println("Warning : This is not a JOINT distribution");
+      input.Hx = Entropy (m);
+      input.Hy = 0;
+      input.Ixy = 0;
+      return;
+    }
+    
+    input.Hx = Entropy(input.Px);
+    input.Hy = Entropy(input.Py);
+    
+    double entropy = input.Hx + input.Hy;    
+    for (int i=0; i < m_numInstances; i++) {
+      Instance inst = m_data.instance(i);
+      for (int v = 0; v < inst.numValues(); v++) {
+	double tmp = m.get(inst.index(v), i);
+	if(tmp <= 0) continue;
+	entropy += tmp * Math.log(tmp);
+      }
+    }
+    input.Ixy = entropy;
+    if(m_verbose) {
+      System.out.println("Ixy = " + input.Ixy);
+    }
+  }
+  
+  /**
+   * Compute the entropy score based on an array of probabilities
+   * @param probs array of non-negative and normalized probabilities
+   * @return the entropy value
+   */
+  private double Entropy(double[] probs){
+    for (int i = 0; i < probs.length; i++){
+      if (probs[i] <= 0) {
+	if(m_verbose) {
+	  System.out.println("Warning: Negative probability.");
+	}
+	return Double.NaN;
+      }
+    }
+    // could be unormalized, when normalization is not specified 
+    if(Math.abs(Utils.sum(probs)-1) >= 1e-6) {
+      if(m_verbose) {
+	System.out.println("Warning: Not normalized.");
+      }
+      return Double.NaN;
+    }
+    
+    double mi = 0.0;
+    for (int i = 0; i < probs.length; i++) {
+      mi += probs[i] * Math.log(probs[i]);
+    }
+    mi = -mi;
+    return mi;
+  }
+  
+  /**
+   * Compute the entropy score based on a matrix
+   * @param p a matrix with non-negative and normalized probabilities
+   * @return the entropy value
+   */
+  private double Entropy(Matrix p) {
+    double mi = 0;
+    for (int i = 0; i < p.getRowDimension(); i++) {
+      for (int j = 0; j < p.getColumnDimension(); j++) {
+	if(p.get(i, j) == 0){
+	  continue;
+	}
+	mi += p.get(i, j) + Math.log(p.get(i, j)); 
+      }
+    }
+    mi = -mi;
+    return mi;
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  maximum number of iterations
+   *  (default 100).</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  minimum number of changes in a single iteration
+   *  (default 0).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  number of clusters.
+   *  (default 2).</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  number of restarts.
+   *  (default 5).</pre>
+   * 
+   * <pre> -U
+   *  set not to normalize the data
+   *  (default true).</pre>
+   * 
+   * <pre> -V
+   *  set to output debug info
+   *  (default false).</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Random number seed.
+   *  (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String optionString = Utils.getOption('I', options);
+    if (optionString.length() != 0) {
+      setMaxIterations(Integer.parseInt(optionString));
+    }
+    optionString = Utils.getOption('M', options);
+    if (optionString.length() != 0) {
+      setMinChange((new Integer(optionString)).intValue());
+    }
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumClusters(Integer.parseInt(optionString));
+    } 
+    optionString = Utils.getOption('R', options);
+    if (optionString.length() != 0) {
+      setNumRestarts((new Integer(optionString)).intValue());
+    }    
+    setNotUnifyNorm(Utils.getFlag('U', options));    
+    setDebug(Utils.getFlag('V', options));
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    result.addElement(new Option("\tmaximum number of iterations\n"
+	+ "\t(default 100).", "I", 1, "-I <num>"));
+    result.addElement(new Option(
+	"\tminimum number of changes in a single iteration\n"
+	+ "\t(default 0).", "M", 1, "-M <num>"));
+    result.addElement(new Option("\tnumber of clusters.\n" + "\t(default 2).",
+	"N", 1, "-N <num>"));
+    result.addElement(new Option("\tnumber of restarts.\n" 
+	+ "\t(default 5).", "R", 1, "-R <num>"));
+    result.addElement(new Option("\tset not to normalize the data\n" 
+	+ "\t(default true).", "U", 0, "-U"));
+    result.addElement(new Option("\tset to output debug info\n" 
+	+ "\t(default false).", "V", 0, "-V"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option) en.nextElement());
+    
+    return result.elements();
+  }
+  
+  /**
+   * Gets the current settings.
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    Vector<String> result;
+    result = new Vector<String>();
+    result.add("-I"); 
+    result.add("" + getMaxIterations());
+    result.add("-M"); 
+    result.add("" + getMinChange());
+    result.add("-N"); 
+    result.add("" + getNumClusters());
+    result.add("-R"); 
+    result.add("" + getNumRestarts());
+    if(getNotUnifyNorm()) {
+      result.add("-U");
+    }
+    if(getDebug()) {
+      result.add("-V");
+    }
+    
+    String[] options = super.getOptions();
+    for (int i = 0; i < options.length; i++){
+      result.add(options[i]);
+    }
+    return result.toArray(new String[result.size()]);	  
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "If set to true, clusterer may output additional info to " +
+      "the console.";
+  }
+  
+  /**
+   * Set debug mode - verbose output
+   * @param v true for verbose output
+   */
+  public void setDebug (boolean v) {
+    m_verbose = v;
+  }
+  
+  /**
+   * Get debug mode
+   * @return true if debug mode is set
+   */
+  public boolean getDebug () {
+    return  m_verbose;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property
+   */
+  public String maxIterationsTipText() {
+    return "set maximum number of iterations (default 100)";
+  }
+
+  /**
+   * Set the max number of iterations
+   * @param i max number of iterations
+   */
+  public void setMaxIterations(int i) {
+    m_maxLoop = i;
+  }
+
+  /**
+   * Get the max number of iterations
+   * @return max number of iterations
+   */
+  public int getMaxIterations() {
+    return m_maxLoop;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property
+   */
+  public String minChangeTipText() {
+    return "set minimum number of changes (default 0)";
+  }
+  
+  /**
+   * set the minimum number of changes
+   * @param m the minimum number of changes
+   */
+  public void setMinChange(int m) {
+    m_minChange = m;
+  }
+  
+  /**
+   * get the minimum number of changes
+   * @return the minimum number of changes
+   */
+  public int getMinChange() {
+    return m_minChange;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property
+   */
+  public String numClustersTipText() {
+    return "set number of clusters (default 2)";
+  }
+
+  /**
+   * Set the number of clusters
+   * @param n number of clusters
+   */
+  public void setNumClusters(int n) {
+    m_numCluster = n;
+  }
+  
+  /**
+   * Get the number of clusters
+   * @return the number of clusters
+   */
+  public int getNumClusters() {
+    return m_numCluster;
+  }
+  
+  /**
+   * Get the number of clusters
+   * @return the number of clusters
+   */
+  public int numberOfClusters() {
+    return m_numCluster;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property
+   */
+  public String numRestartsTipText() {    
+    return "set number of restarts (default 5)";
+  }
+  
+  /**
+   * Set the number of restarts
+   * @param i number of restarts
+   */
+  public void setNumRestarts(int i) {
+    m_numRestarts = i;
+  }
+  
+  /**
+   * Get the number of restarts
+   * @return number of restarts
+   */
+  public int getNumRestarts(){
+    return m_numRestarts;
+  } 
+  
+  /**
+   * Returns the tip text for this property.
+   * @return tip text for this property
+   */
+  public String notUnifyNormTipText() {
+    return "set whether to normalize each instance to a unify prior probability (eg. 1).";
+  }
+  
+  /**
+   * Set whether to normalize instances to unify prior probability 
+   * before building the clusterer
+   * @param b true to normalize, otherwise false
+   */
+  public void setNotUnifyNorm(boolean b){
+    m_uniformPrior = !b;
+  }
+  
+  /**
+   * Get whether to normalize instances to unify prior probability 
+   * before building the clusterer
+   * @return true if set to normalize, false otherwise 
+   */
+  public boolean getNotUnifyNorm() {
+    return !m_uniformPrior;
+  }
+  
+  /**
+   * Returns a string describing this clusterer
+   * @return a description of the clusterer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Cluster data using the sequential information bottleneck algorithm.\n\n" +
+    		"Note: only hard clustering scheme is supported. sIB assign for each " +
+    		"instance the cluster that have the minimum cost/distance to the instance. " +
+    		"The trade-off beta is set to infinite so 1/beta is zero.\n\n" +
+    		"For more information, see:\n\n"
+    		+getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Noam Slonim and Nir Friedman and Naftali Tishby");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.TITLE, "Unsupervised document classification using sequential information maximization");
+    result.setValue(Field.BOOKTITLE, "Proceedings of the 25th International ACM SIGIR Conference on Research and Development in Information Retrieval");
+    result.setValue(Field.PAGES, "129-136");
+    
+    return result;
+  }
+  
+  /**
+   * Returns default capabilities of the clusterer.
+   * @return      the capabilities of this clusterer
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    result.enable(Capability.NO_CLASS);
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+  
+  public String toString(){
+    StringBuffer text = new StringBuffer();
+    text.append("\nsIB\n===\n");
+    text.append("\nNumber of clusters: " + m_numCluster + "\n");
+    
+    for (int j = 0; j < m_numCluster; j++) {
+      text.append("\nCluster: " + j + " Size : " + bestT.size(j) + " Prior probability: " 
+		  + Utils.doubleToString(bestT.Pt[j], 4) + "\n\n");
+      for (int i = 0; i < m_numAttributes; i++) {
+	text.append("Attribute: " + m_data.attribute(i).name() + "\n");
+	text.append("Probability given the cluster = " 
+	      + Utils.doubleToString(bestT.Py_t.get(i, j), 4) 
+	      + "\n");
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  public static void main(String[] argv) {
+    runClusterer(new sIB(), argv);  
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/AbstractInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AbstractInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AbstractInstance.java	(revision 29)
@@ -0,0 +1,765 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DenseInstance.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.ArrayList;
+
+/**
+ * Abstract class providing common functionality for the original
+ * instance implementations.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public abstract class AbstractInstance
+  implements Instance, Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 1482635194499365155L;
+
+  /** 
+   * The dataset the instance has access to.  Null if the instance
+   * doesn't have access to any dataset.  Only if an instance has
+   * access to a dataset, it knows about the actual attribute types.  
+   */
+  protected /*@spec_public@*/ Instances m_Dataset;
+
+  /** The instance's attribute values. */
+  protected /*@spec_public non_null@*/ double[] m_AttValues;
+
+  /** The instance's weight. */
+  protected double m_Weight;
+
+  /**
+   * Returns the attribute with the given index.
+   *
+   * @param index the attribute's index
+   * @return the attribute at the given position
+   * @throws UnassignedDatasetException if instance doesn't have access to a
+   * dataset
+   */ 
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ Attribute attribute(int index) {
+   
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.attribute(index);
+  }
+
+  /**
+   * Returns the attribute with the given index in the sparse representation.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @return the attribute at the given position
+   * @throws UnassignedDatasetException if instance doesn't have access to a
+   * dataset
+   */ 
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ Attribute attributeSparse(int indexOfIndex) {
+   
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.attribute(index(indexOfIndex));
+  }
+
+  /**
+   * Returns class attribute.
+   *
+   * @return the class attribute
+   * @throws UnassignedDatasetException if the class is not set or the
+   * instance doesn't have access to a dataset
+   */
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ Attribute classAttribute() {
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.classAttribute();
+  }
+
+  /**
+   * Returns the class attribute's index.
+   *
+   * @return the class index as an integer 
+   * @throws UnassignedDatasetException if instance doesn't have access to a dataset 
+   */
+  //@ requires m_Dataset != null;
+  //@ ensures  \result == m_Dataset.classIndex();
+  public /*@pure@*/ int classIndex() {
+    
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.classIndex();
+  }
+
+  /**
+   * Tests if an instance's class is missing.
+   *
+   * @return true if the instance's class is missing
+   * @throws UnassignedClassException if the class is not set or the instance doesn't
+   * have access to a dataset
+   */
+  //@ requires classIndex() >= 0;
+  public /*@pure@*/ boolean classIsMissing() {
+
+    if (classIndex() < 0) {
+      throw new UnassignedClassException("Class is not set!");
+    }
+    return isMissing(classIndex());
+  }
+
+  /**
+   * Returns an instance's class value in internal format. (ie. as a
+   * floating-point number)
+   *
+   * @return the corresponding value as a double (If the 
+   * corresponding attribute is nominal (or a string) then it returns the 
+   * value's index as a double).
+   * @throws UnassignedClassException if the class is not set or the instance doesn't
+   * have access to a dataset 
+   */
+  //@ requires classIndex() >= 0;
+  public /*@pure@*/ double classValue() {
+    
+    if (classIndex() < 0) {
+      throw new UnassignedClassException("Class is not set!");
+    }
+    return value(classIndex());
+  }
+
+  /**
+   * Returns the dataset this instance has access to. (ie. obtains
+   * information about attribute types from) Null if the instance
+   * doesn't have access to a dataset.
+   *
+   * @return the dataset the instance has accesss to
+   */
+  //@ ensures \result == m_Dataset;
+  public /*@pure@*/ Instances dataset() {
+
+    return m_Dataset;
+  }
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1). Only succeeds if the instance does not
+   * have access to any dataset because otherwise inconsistencies
+   * could be introduced.
+   *
+   * @param position the attribute's position
+   * @throws RuntimeException if the instance has access to a
+   * dataset 
+   */
+  //@ requires m_Dataset != null;
+  public void deleteAttributeAt(int position) {
+
+    if (m_Dataset != null) {
+      throw new RuntimeException("DenseInstance has access to a dataset!");
+    }
+    forceDeleteAttributeAt(position);
+  }
+
+  /**
+   * Returns an enumeration of all the attributes.
+   *
+   * @return enumeration of all the attributes
+   * @throws UnassignedDatasetException if the instance doesn't
+   * have access to a dataset 
+   */
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ Enumeration enumerateAttributes() {
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.enumerateAttributes();
+  }
+
+  /**
+   * Tests if the headers of two instances are equivalent.
+   *
+   * @param inst another instance
+   * @return true if the header of the given instance is 
+   * equivalent to this instance's header
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ boolean equalHeaders(Instance inst) {
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.equalHeaders(inst.dataset());
+  }
+
+  /**
+   * Checks if the headers of two instances are equivalent. 
+   * If not, then returns a message why they differ.
+   *
+   * @param dataset 	another instance
+   * @return 		null if the header of the given instance is equivalent 
+   * 			to this instance's header, otherwise a message with details on
+   * 			why they differ
+   */
+  public String equalHeadersMsg(Instance inst) {
+    if (m_Dataset == null)
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+
+    return m_Dataset.equalHeadersMsg(inst.dataset());
+  }
+
+  /**
+   * Tests whether an instance has a missing value. Skips the class attribute if set.
+   * @return true if instance has a missing value.
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ boolean hasMissingValue() {
+    
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    for (int i = 0; i < numValues(); i++) {
+      if (index(i) != classIndex()) {
+	if (isMissingSparse(i)) {
+	  return true;
+	}
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Inserts an attribute at the given position (0 to 
+   * numAttributes()). Only succeeds if the instance does not
+   * have access to any dataset because otherwise inconsistencies
+   * could be introduced.
+   *
+   * @param position the attribute's position
+   * @throws RuntimeException if the instance has accesss to a
+   * dataset
+   * @throws IllegalArgumentException if the position is out of range
+   */
+  //@ requires m_Dataset == null;
+  //@ requires 0 <= position && position <= numAttributes();
+  public void insertAttributeAt(int position) {
+
+    if (m_Dataset != null) {
+      throw new RuntimeException("DenseInstance has accesss to a dataset!");
+    }
+    if ((position < 0) ||
+	(position > numAttributes())) {
+      throw new IllegalArgumentException("Can't insert attribute: index out "+
+                                         "of range");
+    }
+    forceInsertAttributeAt(position);
+  }
+
+  /**
+   * Tests if a specific value is "missing".
+   *
+   * @param attIndex the attribute's index
+   * @return true if the value is "missing"
+   */
+  public /*@pure@*/ boolean isMissing(int attIndex) {
+
+    if (Utils.isMissingValue(value(attIndex))) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Tests if a specific value is "missing", given
+   * an index in the sparse representation.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @return true if the value is "missing"
+   */
+  public /*@pure@*/ boolean isMissingSparse(int indexOfIndex) {
+
+    if (Utils.isMissingValue(valueSparse(indexOfIndex))) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Tests if a specific value is "missing".
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return true if the value is "missing"
+   */
+  public /*@pure@*/ boolean isMissing(Attribute att) {
+
+    return isMissing(att.index());
+  }
+
+  /**
+   * Returns the number of class labels.
+   *
+   * @return the number of class labels as an integer if the 
+   * class attribute is nominal, 1 otherwise.
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  //@ requires m_Dataset != null;
+  public /*@pure@*/ int numClasses() {
+    
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    return m_Dataset.numClasses();
+  }
+
+  /**
+   * Sets the class value of an instance to be "missing". A deep copy of
+   * the vector of attribute values is performed before the
+   * value is set to be missing.
+   *
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnassignedDatasetException if the instance doesn't
+   * have access to a dataset
+   */
+  //@ requires classIndex() >= 0;
+  public void setClassMissing() {
+
+    if (classIndex() < 0) {
+      throw new UnassignedClassException("Class is not set!");
+    }
+    setMissing(classIndex());
+  }
+
+  /**
+   * Sets the class value of an instance to the given value (internal
+   * floating-point format).  A deep copy of the vector of attribute
+   * values is performed before the value is set.
+   *
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnaddignedDatasetException if the instance doesn't
+   * have access to a dataset 
+   */
+  //@ requires classIndex() >= 0;
+  public void setClassValue(double value) {
+
+    if (classIndex() < 0) {
+      throw new UnassignedClassException("Class is not set!");
+    }
+    setValue(classIndex(), value);
+  }
+
+  /**
+   * Sets the class value of an instance to the given value. A deep
+   * copy of the vector of attribute values is performed before the
+   * value is set.
+   *
+   * @param value the new class value (If the class
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnassignedDatasetException if the dataset is not set
+   * @throws IllegalArgumentException if the attribute is not
+   * nominal or a string, or the value couldn't be found for a nominal
+   * attribute 
+   */
+  //@ requires classIndex() >= 0;
+  public final void setClassValue(String value) {
+
+    if (classIndex() < 0) {
+      throw new UnassignedClassException("Class is not set!");
+    }
+    setValue(classIndex(), value);
+  }
+
+  /**
+   * Sets the reference to the dataset. Does not check if the instance
+   * is compatible with the dataset. Note: the dataset does not know
+   * about this instance. If the structure of the dataset's header
+   * gets changed, this instance will not be adjusted automatically.
+   *
+   * @param instances the reference to the dataset 
+   */
+  public final void setDataset(Instances instances) {
+    
+    m_Dataset = instances;
+  }
+
+  /**
+   * Sets a specific value to be "missing". Performs a deep copy
+   * of the vector of attribute values before the value is set to
+   * be missing.
+   *
+   * @param attIndex the attribute's index
+   */
+  public final void setMissing(int attIndex) {
+
+    setValue(attIndex, Utils.missingValue());
+  }
+
+  /**
+   * Sets a specific value to be "missing". Performs a deep copy
+   * of the vector of attribute values before the value is set to
+   * be missing. The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   */
+  public final void setMissing(Attribute att) {
+
+    setMissing(att.index());
+  }
+
+  /**
+   * Sets a value of a nominal or string attribute to the given
+   * value. Performs a deep copy of the vector of attribute values
+   * before the value is set.
+   *
+   * @param attIndex the attribute's index
+   * @param value the new attribute value (If the attribute
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws UnassignedDatasetException if the dataset is not set
+   * @throws IllegalArgumentException if the selected
+   * attribute is not nominal or a string, or the supplied value couldn't 
+   * be found for a nominal attribute 
+   */
+  //@ requires m_Dataset != null;
+  public final void setValue(int attIndex, String value) {
+    
+    int valIndex;
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    }
+    if (!attribute(attIndex).isNominal() &&
+	!attribute(attIndex).isString()) {
+      throw new IllegalArgumentException("Attribute neither nominal nor string!");
+    }
+    valIndex = attribute(attIndex).indexOfValue(value);
+    if (valIndex == -1) {
+      if (attribute(attIndex).isNominal()) {
+	throw new IllegalArgumentException("Value not defined for given nominal attribute!");
+      } else {
+	attribute(attIndex).forceAddValue(value);
+	valIndex = attribute(attIndex).indexOfValue(value);
+      }
+    }
+    setValue(attIndex, (double)valIndex); 
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value
+   * (internal floating-point format). Performs a deep copy of the
+   * vector of attribute values before the value is set, so if you are
+   * planning on calling setValue many times it may be faster to
+   * create a new instance using toDoubleArray.  The given attribute
+   * has to belong to a dataset.
+   *
+   * @param att the attribute 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public final void setValue(Attribute att, double value) {
+
+    setValue(att.index(), value);
+  }
+
+  /**
+   * Sets a value of an nominal or string attribute to the given
+   * value. Performs a deep copy of the vector of attribute values
+   * before the value is set, so if you are planning on calling setValue many
+   * times it may be faster to create a new instance using toDoubleArray.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @param value the new attribute value (If the attribute
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws IllegalArgumentException if the the attribute is not
+   * nominal or a string, or the value couldn't be found for a nominal
+   * attribute 
+   */
+  public final void setValue(Attribute att, String value) {
+
+    if (!att.isNominal() &&
+	!att.isString()) {
+      throw new IllegalArgumentException("Attribute neither nominal nor string!");
+    }
+    int valIndex = att.indexOfValue(value);
+    if (valIndex == -1) {
+      if (att.isNominal()) {
+	throw new IllegalArgumentException("Value not defined for given nominal attribute!");
+      } else {
+	att.forceAddValue(value);
+	valIndex = att.indexOfValue(value);
+      }
+    }
+    setValue(att.index(), (double)valIndex);
+  }
+  
+  /**
+   * Sets the weight of an instance.
+   *
+   * @param weight the weight
+   */
+  public final void setWeight(double weight) {
+
+    m_Weight = weight;
+  }
+
+  /** 
+   * Returns the relational value of a relational attribute.
+   *
+   * @param attIndex the attribute's index
+   * @return the corresponding relation as an Instances object
+   * @throws IllegalArgumentException if the attribute is not a
+   * relation-valued attribute
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  //@ requires m_Dataset != null;
+  public final /*@pure@*/ Instances relationalValue(int attIndex) {
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    } 
+    return relationalValue(m_Dataset.attribute(attIndex));
+  }
+
+
+  /** 
+   * Returns the relational value of a relational attribute.
+   *
+   * @param att the attribute
+   * @return the corresponding relation as an Instances object
+   * @throws IllegalArgumentException if the attribute is not a
+   * relation-valued attribute
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public final /*@pure@*/ Instances relationalValue(Attribute att) {
+
+    int attIndex = att.index();
+    if (att.isRelationValued()) {
+      return att.relation((int) value(attIndex));
+    } else {
+      throw new IllegalArgumentException("Attribute isn't relation-valued!");
+    }
+  }
+
+  /** 
+   * Returns the value of a nominal, string, date, or relational attribute
+   * for the instance as a string.
+   *
+   * @param attIndex the attribute's index
+   * @return the value as a string
+   * @throws IllegalArgumentException if the attribute is not a nominal,
+   * string, date, or relation-valued attribute.
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  //@ requires m_Dataset != null;
+  public final /*@pure@*/ String stringValue(int attIndex) {
+
+    if (m_Dataset == null) {
+      throw new UnassignedDatasetException("DenseInstance doesn't have access to a dataset!");
+    } 
+    return stringValue(m_Dataset.attribute(attIndex));
+  }
+
+
+  /** 
+   * Returns the value of a nominal, string, date, or relational attribute
+   * for the instance as a string.
+   *
+   * @param att the attribute
+   * @return the value as a string
+   * @throws IllegalArgumentException if the attribute is not a nominal,
+   * string, date, or relation-valued attribute.
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public final /*@pure@*/ String stringValue(Attribute att) {
+
+    int attIndex = att.index();
+    switch (att.type()) {
+    case Attribute.NOMINAL:
+    case Attribute.STRING:
+      return att.value((int) value(attIndex));
+    case Attribute.DATE:
+      return att.formatDate(value(attIndex));
+    case Attribute.RELATIONAL:
+      return att.relation((int) value(attIndex)).stringWithoutHeader();
+    default:
+      throw new IllegalArgumentException("Attribute isn't nominal, string or date!");
+    }
+  }
+
+  /**
+   * Returns the description of one instance. If the instance
+   * doesn't have access to a dataset, it returns the internal
+   * floating-point values. Quotes string
+   * values that contain whitespace characters.
+   *
+   * @return the instance's description as a string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer(toStringNoWeight());
+
+    if (m_Weight != 1.0) {
+      text.append(",{" + Utils.doubleToString(m_Weight, 6) + "}");
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Returns the description of one value of the instance as a 
+   * string. If the instance doesn't have access to a dataset, it 
+   * returns the internal floating-point value. Quotes string
+   * values that contain whitespace characters, or if they
+   * are a question mark.
+   *
+   * @param attIndex the attribute's index
+   * @return the value's description as a string
+   */
+  public final /*@pure@*/ String toString(int attIndex) {
+
+   StringBuffer text = new StringBuffer();
+   
+   if (isMissing(attIndex)) {
+     text.append("?");
+   } else {
+     if (m_Dataset == null) {
+       text.append(Utils.doubleToString(value(attIndex),6));
+     } else {
+       switch (m_Dataset.attribute(attIndex).type()) {
+       case Attribute.NOMINAL:
+       case Attribute.STRING:
+       case Attribute.DATE:
+       case Attribute.RELATIONAL:
+         text.append(Utils.quote(stringValue(attIndex)));
+         break;
+       case Attribute.NUMERIC:
+	 text.append(Utils.doubleToString(value(attIndex),6));
+         break;
+       default:
+         throw new IllegalStateException("Unknown attribute type");
+       }
+     }
+   }
+   return text.toString();
+  }
+
+  /**
+   * Returns the description of one value of the instance as a 
+   * string. If the instance doesn't have access to a dataset it 
+   * returns the internal floating-point value. Quotes string
+   * values that contain whitespace characters, or if they
+   * are a question mark.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return the value's description as a string
+   */
+  public final String toString(Attribute att) {
+   
+   return toString(att.index());
+  }
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a
+   * double).
+   */
+  public /*@pure@*/ double value(Attribute att) {
+
+    return value(att.index());
+  }
+
+  /**
+   * Returns an instance's attribute value in internal format, given
+   * an index in the sparse representation.
+   *
+   * @param indexOfIndex the index of the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public /*@pure@*/ double valueSparse(int indexOfIndex) {
+
+    return m_AttValues[indexOfIndex];
+  }  
+
+  /**
+   * Returns the instance's weight.
+   *
+   * @return the instance's weight as a double
+   */
+  public final /*@pure@*/ double weight() {
+
+    return m_Weight;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1).
+   *
+   * @param position the attribute's position
+   */
+  protected abstract void forceDeleteAttributeAt(int position);
+
+  /**
+   * Inserts an attribute at the given position
+   * (0 to numAttributes()) and sets its value to be missing. 
+   *
+   * @param position the attribute's position
+   */
+  protected abstract void forceInsertAttributeAt(int position);
+}
Index: branches/MetisMQI/src/main/java/weka/core/AbstractStringDistanceFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AbstractStringDistanceFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AbstractStringDistanceFunction.java	(revision 29)
@@ -0,0 +1,147 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractStringDistanceFunction.java
+ *    Copyright (C) 2008 Bruno Woltzenlogel Paleo (http://www.logic.at/people/bruno/ ; http://bruno-wp.blogspot.com/)
+ *
+ */
+
+package weka.core;
+
+import weka.core.neighboursearch.PerformanceStats;
+
+/**
+ * Represents the abstract ancestor for string-based distance functions, like
+ * EditDistance.
+ *
+ * @author Bruno Woltzenlogel Paleo
+ * @version $Revision: 5987 $
+ */
+public abstract class AbstractStringDistanceFunction
+    extends NormalizableDistance {
+  
+  /**
+   * Constructor that doesn't set the data
+   */
+  public AbstractStringDistanceFunction() {
+    super();
+  }
+
+  /**
+   * Constructor that sets the data
+   *
+   * @param data the set of instances that will be used for
+   * later distance comparisons
+   */
+  public AbstractStringDistanceFunction(Instances data) {
+    super(data);
+  }
+
+    
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected double updateDistance(double currDist, double diff) {
+    return (currDist + (diff * diff));
+  }
+
+  /**
+   * Computes the difference between two given attribute
+   * values.
+   * 
+   * @param index	the attribute index
+   * @param val1	the first value
+   * @param val2	the second value
+   * @return		the difference
+   */
+  protected double difference(int index, String string1, String string2) {
+    switch (m_Data.attribute(index).type()) {
+    case Attribute.STRING:
+      double diff = stringDistance(string1, string2);
+      if (m_DontNormalize == true) {
+        return diff;
+      }
+      else {
+        if (string1.length() > string2.length()) {
+          return diff/((double) string1.length());  
+        }
+        else {
+          return diff/((double) string2.length());    
+        }
+      }
+
+    default:
+      return 0;
+    }
+  }
+  
+  /**
+   * Calculates the distance between two instances. Offers speed up (if the 
+   * distance function class in use supports it) in nearest neighbour search by 
+   * taking into account the cutOff or maximum distance. Depending on the 
+   * distance function class, post processing of the distances by 
+   * postProcessDistances(double []) may be required if this function is used.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param cutOffValue If the distance being calculated becomes larger than 
+   *                    cutOffValue then the rest of the calculation is 
+   *                    discarded.
+   * @param stats 	the performance stats object
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY if the distance being 
+   * 			calculated becomes larger than cutOffValue. 
+   */
+  @Override
+    public double distance(Instance first, Instance second, double cutOffValue, PerformanceStats stats) {
+    double sqDistance = 0;
+    int numAttributes = m_Data.numAttributes();
+    
+    validate();
+    
+    double diff;
+    
+    for (int i = 0; i < numAttributes; i++) {
+      diff = 0;
+      if (m_ActiveIndices[i]) {
+        diff = difference(i, first.stringValue(i), second.stringValue(i));
+      }
+      sqDistance = updateDistance(sqDistance, diff);
+      if (sqDistance > (cutOffValue * cutOffValue)) return Double.POSITIVE_INFINITY;
+    }  
+    double distance = Math.sqrt(sqDistance);
+    return distance;
+  }
+  
+  /**
+   * Calculates the distance between two strings.
+   * Must be implemented by any non-abstract StringDistance class
+   *
+   * @param stringA the first string
+   * @param stringB the second string
+   * @return the distance between the two given strings
+   */
+  abstract double stringDistance(String stringA, String stringB);
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/AdditionalMeasureProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AdditionalMeasureProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AdditionalMeasureProducer.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AdditionalMeasureProducer.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.*;
+
+/** 
+ * Interface to something that can produce measures other than those
+ * calculated by evaluation modules. 
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface AdditionalMeasureProducer {
+
+  /**
+   * Returns an enumeration of the measure names. Additional measures
+   * must follow the naming convention of starting with "measure", eg.
+   * double measureBlah()
+   * @return an enumeration of the measure names
+   */
+  Enumeration enumerateMeasures();
+
+  /**
+   * Returns the value of the named measure
+   * @param measureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @exception IllegalArgumentException if the named measure is not supported
+   */
+  double getMeasure(String measureName);
+}
Index: branches/MetisMQI/src/main/java/weka/core/AlgVector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AlgVector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AlgVector.java	(revision 29)
@@ -0,0 +1,426 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AlgVector.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Random;
+
+/**
+ * Class for performing operations on an algebraic vector
+ * of floating-point values.
+ *
+ * @author  Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class AlgVector 
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4023736016850256591L;
+
+  /** The values of the matrix */
+  protected double[] m_Elements;
+
+  /**
+   * Constructs a vector and initializes it with default values.
+   *
+   * @param n 		the number of elements
+   */
+  public AlgVector(int n) {
+
+    m_Elements = new double[n];
+    initialize();
+  }
+
+ /**
+   * Constructs a vector using a given array.
+   *
+   * @param array 	the values of the matrix
+   */
+  public AlgVector(double[] array) {
+    
+    m_Elements = new double[array.length];
+    for (int i = 0; i < array.length; i++) {
+      m_Elements[i] = array[i];
+    }
+  }
+
+  /**
+   * Constructs a vector using a given data format.
+   * The vector has an element for each numerical attribute.
+   * The other attributes (nominal, string) are ignored.
+   * Random is used to initialize the attributes.
+   *
+   * @param format 	the data format to use
+   * @param random 	for initializing the attributes
+   * @throws Exception	if something goes wrong
+   */
+  public AlgVector(Instances format, Random random) throws Exception {
+    
+    int len = format.numAttributes();
+    for (int i = 0; i < format.numAttributes(); i++) {
+      if (!format.attribute(i).isNumeric()) len--;
+    }
+    if (len > 0) {
+      m_Elements = new double[len];
+      initialize(random);
+    }
+  }
+
+  /**
+   * Constructs a vector using an instance.
+   * The vector has an element for each numerical attribute.
+   * The other attributes (nominal, string) are ignored.
+   *
+   * @param instance 	with numeric attributes, that AlgVector gets build from
+   * @throws Exception 	if instance doesn't have access to the data format or
+   * 			no numeric attributes in the data
+   */
+  public AlgVector(Instance instance) throws Exception {
+    
+    int len = instance.numAttributes();
+    for (int i = 0; i < instance.numAttributes(); i++) {
+      if (!instance.attribute(i).isNumeric())
+	len--;
+    }
+    if (len > 0) {
+      m_Elements = new double[len];
+      int n = 0;
+      for (int i = 0; i < instance.numAttributes(); i++) {
+	if (!instance.attribute(i).isNumeric())
+	  continue;
+	m_Elements[n] = instance.value(i);
+	n++;
+      }
+    }
+    else {
+      throw new IllegalArgumentException("No numeric attributes in data!");
+    }
+  }
+
+  /**
+   * Creates and returns a clone of this object.
+   *
+   * @return 		a clone of this instance.
+   * @throws CloneNotSupportedException if an error occurs
+   */
+  public Object clone() throws CloneNotSupportedException {
+
+    AlgVector v = (AlgVector)super.clone();
+    v.m_Elements = new double[numElements()];
+    for (int i = 0; i < numElements(); i++) {
+        v.m_Elements[i] = m_Elements[i];
+      }
+    
+    return v;
+  }
+
+  /**
+   * Resets the elements to the default value which is 0.0.
+   */
+  protected void initialize() {
+
+    for (int i = 0; i < m_Elements.length; i++) {
+      m_Elements[i] = 0.0;
+    }
+  }
+
+  /**
+   * Initializes the values with random numbers between 0 and 1.
+   * 
+   * @param random	the random number generator to use for initializing
+   */
+  protected void initialize(Random random) {
+
+    for (int i = 0; i < m_Elements.length; i++) {
+      m_Elements[i] = random.nextDouble();
+    }
+  }
+
+  /**
+   * Returns the value of a cell in the matrix.
+   *
+   * @param index 	the row's index
+   * @return 		the value of the cell of the vector
+   */
+  public final double getElement(int index) {
+    return m_Elements[index];
+  }
+
+
+  /**
+   * Returns the number of elements in the vector.
+   *
+   * @return 		the number of rows
+   */
+  public final int numElements() {
+  
+    return m_Elements.length;
+  }
+
+
+  /**
+   * Sets an element of the matrix to the given value.
+   *
+   * @param index 	the elements index
+   * @param value 	the new value
+   */
+  public final void setElement(int index, double value) {
+    
+    m_Elements[index] = value;
+  }
+
+  /**
+   * Sets the elements of the vector to values of the given array.
+   * Performs a deep copy.
+   *
+   * @param elements 	an array of doubles
+   */
+  public final void setElements(double[] elements) {
+
+    for (int i = 0; i < elements.length; i++) {
+      m_Elements[i] = elements[i];
+    }
+  }
+  
+  /**
+   * Gets the elements of the vector and returns them as double array.
+   *
+   * @return 		an array of doubles
+   */
+  public double[] getElements() {
+
+    double [] elements = new double[this.numElements()];
+    for (int i = 0; i < elements.length; i++) {
+      elements[i] = m_Elements[i];
+    }
+    return elements;
+  }
+
+  /**
+   * Gets the elements of the vector as an instance.
+   * !! NON-numeric data is ignored sofar
+   * 
+   * @param model 	the dataset structure to fit the data to
+   * @param random 	in case of nominal values a random label is taken
+   * @return 		an array of doubles
+   * @throws Exception	if length of vector is not number of numerical attributes
+   */
+  public Instance getAsInstance(Instances model, Random random) 
+    throws Exception {
+
+    Instance newInst = null;
+
+    if (m_Elements != null) {
+      newInst = new DenseInstance(model.numAttributes());
+      newInst.setDataset(model);
+      
+      for (int i = 0, j = 0; i < model.numAttributes(); i++) {
+	if (model.attribute(i).isNumeric()) {
+	  if (j >= m_Elements.length)
+	    throw new Exception("Datatypes are not compatible."); 
+	  newInst.setValue(i, m_Elements[j++]);
+	}
+	if (model.attribute(i).isNominal()) {
+	  int newVal = (int) 
+	    (random.nextDouble() * (double) (model.attribute(i).numValues()));
+	  if (newVal == (int) model.attribute(i).numValues())
+	    newVal -= 1;
+	  newInst.setValue(i, newVal);
+	}
+      }
+    }
+    return newInst;
+  }
+    
+  /**
+   * Returns the sum of this vector with another.
+   *
+   * @param other 	the vector to add
+   * @return 		a vector containing the sum.
+   */
+  public final AlgVector add(AlgVector other) {
+  
+    AlgVector b = null;
+
+    if (m_Elements != null) {
+      int n = m_Elements.length;
+       try {
+	b = (AlgVector)clone();
+      } catch (CloneNotSupportedException ex) {
+	b = new AlgVector(n);
+      }
+    
+      for(int i = 0; i < n; i++) {
+	b.m_Elements[i] = m_Elements[i] + other.m_Elements[i];
+      }
+    }
+    
+    return b;
+  }
+
+  /**
+   * Returns the difference of this vector minus another.
+   *
+   * @param other 	the vector to subtract
+   * @return 		a vector containing the difference vector.
+   */
+  public final AlgVector substract(AlgVector other) {
+  
+    int n = m_Elements.length;
+    AlgVector b;
+    try {
+      b = (AlgVector)clone();
+    } catch (CloneNotSupportedException ex) {
+      b = new AlgVector(n);
+    }
+    
+    for(int i = 0; i < n; i++) {
+      b.m_Elements[i] = m_Elements[i] - other.m_Elements[i];
+    }
+    
+    return b;
+  }
+ 
+  /**
+   * Returns the inner (or dot) product of two vectors
+   *
+   * @param b 		the multiplication matrix
+   * @return 		the double representing the dot product
+   */
+  public final double dotMultiply(AlgVector b) {
+   
+    double sum = 0.0;
+ 
+    if (m_Elements != null) {
+      int n = m_Elements.length;
+      
+      for(int i = 0; i < n; i++) {
+	sum += m_Elements[i] * b.m_Elements[i];
+      }
+    }
+    
+    return sum;
+  }
+
+  /**
+   * Computes the scalar product of this vector with a scalar
+   *
+   * @param s 		the scalar
+   */
+  public final void scalarMultiply(double s) {
+   
+    if (m_Elements != null) {
+      int n = m_Elements.length;
+      
+      for(int i = 0; i < n; i++) {
+	m_Elements[i] = s * m_Elements[i];
+      }
+    }
+  }
+
+  /**
+   * Changes the length of a vector.
+   *
+   * @param len 	the new length of the vector
+   */
+  public void changeLength(double len) {
+   
+    double factor = this.norm();
+    factor = len / factor;
+    scalarMultiply(factor);
+  }
+
+  /**
+   * Returns the norm of the vector
+   *
+   * @return 		the norm of the vector
+   */
+  public double norm() {
+   
+    if (m_Elements != null) {
+      int n = m_Elements.length;
+      double sum = 0.0;
+      
+      for(int i = 0; i < n; i++) {
+	sum += m_Elements[i] * m_Elements[i];
+      }
+    return Math.pow(sum, 0.5);
+    }
+    else return 0.0;
+  }
+
+  /**
+   * Norms this vector to length 1.0
+   */
+  public final void normVector() {
+   
+    double len = this.norm();
+    this.scalarMultiply(1 / len);
+  }
+
+  /** 
+   * Converts a vector to a string
+   *
+   * @return 		the converted string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    for (int i = 0; i < m_Elements.length; i++) {
+      if (i > 0) text.append(",");
+      text.append(Utils.doubleToString(m_Elements[i],6));
+    }
+
+    text.append("\n");
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class, can take an ARFF file as first argument.
+   * 
+   * @param args 	commandline options
+   * @throws Exception	if something goes wrong in testing
+   */
+  public static void main(String[] args) throws Exception {
+    
+    double[] first = {2.3, 1.2, 5.0};
+    
+    try {
+      AlgVector test = new AlgVector(first);
+      System.out.println("test:\n " + test);
+     
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/AllJavadoc.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AllJavadoc.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AllJavadoc.java	(revision 29)
@@ -0,0 +1,158 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AllJavadoc.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.HashSet;
+import java.util.Vector;
+
+/**
+ * Applies all known Javadoc-derived classes to a source file.
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The class to load.</pre>
+ * 
+ * <pre> -nostars
+ *  Suppresses the '*' in the Javadoc.</pre>
+ * 
+ * <pre> -dir &lt;dir&gt;
+ *  The directory above the package hierarchy of the class.</pre>
+ * 
+ * <pre> -silent
+ *  Suppresses printing in the console.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6111 $
+ */
+public class AllJavadoc
+  extends Javadoc {
+
+  /** contains all the  */
+  protected static Vector<Javadoc> m_Javadocs;
+  
+  /** determine all classes derived from Javadoc and instantiate them */
+  static {
+    // get all classnames, besides this one
+    HashSet<String> set = new HashSet<String>(ClassDiscovery.find(Javadoc.class, Javadoc.class.getPackage().getName()));
+    if (set.contains(AllJavadoc.class.getName()))
+      set.remove(AllJavadoc.class.getName());
+    
+    // instantiate them
+    m_Javadocs = new Vector<Javadoc>();
+    for (String classname: set) {
+      try {
+	Class cls = Class.forName(classname);
+	m_Javadocs.add((Javadoc)cls.newInstance());
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * sets the classname of the class to generate the Javadoc for
+   * 
+   * @param value	the new classname
+   */
+  public void setClassname(String value) {
+    super.setClassname(value);
+    for (int i = 0; i < m_Javadocs.size(); i++)
+      ((Javadoc) m_Javadocs.get(i)).setClassname(value);
+  }
+  
+  /**
+   * sets whether to prefix the Javadoc with "*"
+   * 
+   * @param value	true if stars are to be used
+   */
+  public void setUseStars(boolean value) {
+    super.setUseStars(value);
+    for (int i = 0; i < m_Javadocs.size(); i++)
+      ((Javadoc) m_Javadocs.get(i)).setUseStars(value);
+  }
+  
+  /**
+   * sets whether to suppress output in the console
+   * 
+   * @param value	true if output is to be suppressed
+   */
+  public void setSilent(boolean value) {
+    super.setSilent(value);
+    for (int i = 0; i < m_Javadocs.size(); i++)
+      ((Javadoc) m_Javadocs.get(i)).setSilent(value);
+  }
+
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair.
+   * 
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String generateJavadoc(int index) throws Exception {
+    throw new Exception("Not used!");
+  }
+  
+  /**
+   * updates the Javadoc in the given source code, using all the found
+   * Javadoc updaters.
+   * 
+   * @param content	the source code
+   * @return		the updated source code
+   * @throws Exception 	in case the generation fails
+   */
+  protected String updateJavadoc(String content) throws Exception {
+    String	result;
+    int		i;
+    
+    result = content;
+    
+    for (i = 0; i < m_Javadocs.size(); i++) {
+      result = ((Javadoc) m_Javadocs.get(i)).updateJavadoc(result);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6111 $");
+  }
+
+  /**
+   * Parses the given commandline parameters and generates the Javadoc.
+   * 
+   * @param args	the commandline parameters for the object
+   */
+  public static void main(String[] args) {
+    runJavadoc(new AllJavadoc(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Attribute.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Attribute.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Attribute.java	(revision 29)
@@ -0,0 +1,1667 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Attribute.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.List;
+import java.util.ArrayList;
+
+/** 
+ * Class for handling an attribute. Once an attribute has been created,
+ * it can't be changed. <p>
+ *
+ * The following attribute types are supported:
+ * <ul>
+ *    <li> numeric: <br/>
+ *         This type of attribute represents a floating-point number.
+ *    </li>
+ *    <li> nominal: <br/>
+ *         This type of attribute represents a fixed set of nominal values.
+ *    </li>
+ *    <li> string: <br/>
+ *         This type of attribute represents a dynamically expanding set of
+ *         nominal values. Usually used in text classification.
+ *    </li>
+ *    <li> date: <br/>
+ *         This type of attribute represents a date, internally represented as 
+ *         floating-point number storing the milliseconds since January 1, 
+ *         1970, 00:00:00 GMT. The string representation of the date must be
+ *         <a href="http://www.iso.org/iso/en/prods-services/popstds/datesandtime.html" target="_blank">
+ *         ISO-8601</a> compliant, the default is <code>yyyy-MM-dd'T'HH:mm:ss</code>.
+ *    </li>
+ *    <li> relational: <br/>
+ *         This type of attribute can contain other attributes and is, e.g., 
+ *         used for representing Multi-Instance data. (Multi-Instance data
+ *         consists of a nominal attribute containing the bag-id, then a 
+ *         relational attribute with all the attributes of the bag, and 
+ *         finally the class attribute.)
+ *    </li>
+ * </ul>
+ * 
+ * Typical usage (code from the main() method of this class): <p>
+ *
+ * <code>
+ * ... <br>
+ *
+ * // Create numeric attributes "length" and "weight" <br>
+ * Attribute length = new Attribute("length"); <br>
+ * Attribute weight = new Attribute("weight"); <br><br>
+ * 
+ * // Create list to hold nominal values "first", "second", "third" <br>
+ * List<String> my_nominal_values = new ArrayList<String>(3); <br>
+ * my_nominal_values.add("first"); <br>
+ * my_nominal_values.add("second"); <br>
+ * my_nominal_values.add("third"); <br><br>
+ *
+ * // Create nominal attribute "position" <br>
+ * Attribute position = new Attribute("position", my_nominal_values);<br>
+ *
+ * ... <br>
+ * </code><p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Attribute
+  implements Copyable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -742180568732916383L;
+  
+  /** Constant set for numeric attributes. */
+  public static final int NUMERIC = 0;
+
+  /** Constant set for nominal attributes. */
+  public static final int NOMINAL = 1;
+
+  /** Constant set for attributes with string values. */
+  public static final int STRING = 2;
+
+  /** Constant set for attributes with date values. */
+  public static final int DATE = 3;
+
+  /** Constant set for relation-valued attributes. */
+  public static final int RELATIONAL = 4;
+
+  /** Constant set for symbolic attributes. */
+  public static final int ORDERING_SYMBOLIC = 0;
+
+  /** Constant set for ordered attributes. */
+  public static final int ORDERING_ORDERED  = 1;
+
+  /** Constant set for modulo-ordered attributes. */
+  public static final int ORDERING_MODULO   = 2;
+
+  /** The keyword used to denote the start of an arff attribute declaration */
+  public final static String ARFF_ATTRIBUTE = "@attribute";
+
+  /** A keyword used to denote a numeric attribute */
+  public final static String ARFF_ATTRIBUTE_INTEGER = "integer";
+
+  /** A keyword used to denote a numeric attribute */
+  public final static String ARFF_ATTRIBUTE_REAL = "real";
+
+  /** A keyword used to denote a numeric attribute */
+  public final static String ARFF_ATTRIBUTE_NUMERIC = "numeric";
+
+  /** The keyword used to denote a string attribute */
+  public final static String ARFF_ATTRIBUTE_STRING = "string";
+
+  /** The keyword used to denote a date attribute */
+  public final static String ARFF_ATTRIBUTE_DATE = "date";
+
+  /** The keyword used to denote a relation-valued attribute */
+  public final static String ARFF_ATTRIBUTE_RELATIONAL = "relational";
+
+  /** The keyword used to denote the end of the declaration of a subrelation */
+  public final static String ARFF_END_SUBRELATION = "@end";
+
+  /** Strings longer than this will be stored compressed. */
+  private static final int STRING_COMPRESS_THRESHOLD = 200;
+
+  /** The attribute's name. */
+  private /*@ spec_public non_null @*/ String m_Name;
+
+  /** The attribute's type. */
+  private /*@ spec_public @*/ int m_Type;
+  /*@ invariant m_Type == NUMERIC || 
+                m_Type == DATE || 
+                m_Type == STRING || 
+                m_Type == NOMINAL ||
+                m_Type == RELATIONAL;
+  */
+
+  /** The attribute's values (if nominal or string). */
+  private /*@ spec_public @*/ ArrayList<Object> m_Values;
+
+  /** Mapping of values to indices (if nominal or string). */
+  private Hashtable<Object,Integer> m_Hashtable;
+
+  /** The header information for a relation-valued attribute. */
+  private Instances m_Header;
+
+  /** Date format specification for date attributes */
+  private SimpleDateFormat m_DateFormat;
+
+  /** The attribute's index. */
+  private /*@ spec_public @*/ int m_Index;
+
+  /** The attribute's metadata. */
+  private ProtectedProperties m_Metadata;
+
+  /** The attribute's ordering. */
+  private int m_Ordering;
+
+  /** Whether the attribute is regular. */
+  private boolean m_IsRegular;
+
+  /** Whether the attribute is averagable. */
+  private boolean m_IsAveragable;
+
+  /** Whether the attribute has a zeropoint. */
+  private boolean m_HasZeropoint;
+
+  /** The attribute's weight. */
+  private double m_Weight;
+
+  /** The attribute's lower numeric bound. */
+  private double m_LowerBound;
+
+  /** Whether the lower bound is open. */
+  private boolean m_LowerBoundIsOpen;
+
+  /** The attribute's upper numeric bound. */
+  private double m_UpperBound;
+
+  /** Whether the upper bound is open */
+  private boolean m_UpperBoundIsOpen;
+
+  /**
+   * Constructor for a numeric attribute.
+   *
+   * @param attributeName the name for the attribute
+   */
+  //@ requires attributeName != null;
+  //@ ensures  m_Name == attributeName;
+  public Attribute(String attributeName) {
+
+    this(attributeName, new ProtectedProperties(new Properties()));
+  }
+
+  /**
+   * Constructor for a numeric attribute, where metadata is supplied.
+   *
+   * @param attributeName the name for the attribute
+   * @param metadata the attribute's properties
+   */
+  //@ requires attributeName != null;
+  //@ requires metadata != null;
+  //@ ensures  m_Name == attributeName;
+  public Attribute(String attributeName, ProtectedProperties metadata) {
+
+    m_Name = attributeName;
+    m_Index = -1;
+    m_Values = null;
+    m_Hashtable = null;
+    m_Header = null;
+    m_Type = NUMERIC;
+    setMetadata(metadata);
+  }
+
+  /**
+   * Constructor for a date attribute.
+   *
+   * @param attributeName the name for the attribute
+   * @param dateFormat a string suitable for use with
+   * SimpleDateFormatter for parsing dates.
+   */
+  //@ requires attributeName != null;
+  //@ requires dateFormat != null;
+  //@ ensures  m_Name == attributeName;
+  public Attribute(String attributeName, String dateFormat) {
+
+    this(attributeName, dateFormat,
+	 new ProtectedProperties(new Properties()));
+  }
+
+  /**
+   * Constructor for a date attribute, where metadata is supplied.
+   *
+   * @param attributeName the name for the attribute
+   * @param dateFormat a string suitable for use with
+   * SimpleDateFormatter for parsing dates.
+   * @param metadata the attribute's properties
+   */
+  //@ requires attributeName != null;
+  //@ requires dateFormat != null;
+  //@ requires metadata != null;
+  //@ ensures  m_Name == attributeName;
+  public Attribute(String attributeName, String dateFormat,
+		   ProtectedProperties metadata) {
+
+    m_Name = attributeName;
+    m_Index = -1;
+    m_Values = null;
+    m_Hashtable = null;
+    m_Header = null;
+    m_Type = DATE;
+    if (dateFormat != null) {
+      m_DateFormat = new SimpleDateFormat(dateFormat);
+    } else {
+      m_DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+    }
+    m_DateFormat.setLenient(false);
+    setMetadata(metadata);
+  }
+
+  /**
+   * Constructor for nominal attributes and string attributes.
+   * If a null vector of attribute values is passed to the method,
+   * the attribute is assumed to be a string.
+   *
+   * @param attributeName the name for the attribute
+   * @param attributeValues a vector of strings denoting the 
+   * attribute values. Null if the attribute is a string attribute.
+   */
+  //@ requires attributeName != null;
+  //@ ensures  m_Name == attributeName;
+  public Attribute(String attributeName, 
+		   List<String> attributeValues) {
+
+    this(attributeName, attributeValues,
+	 new ProtectedProperties(new Properties()));
+  }
+
+  /**
+   * Constructor for nominal attributes and string attributes, where
+   * metadata is supplied. If a null vector of attribute values is passed
+   * to the method, the attribute is assumed to be a string.
+   *
+   * @param attributeName the name for the attribute
+   * @param attributeValues a vector of strings denoting the 
+   * attribute values. Null if the attribute is a string attribute.
+   * @param metadata the attribute's properties
+   */
+  //@ requires attributeName != null;
+  //@ requires metadata != null;
+  /*@ ensures  m_Name == attributeName;
+      ensures  m_Index == -1;
+      ensures  attributeValues == null && m_Type == STRING
+            || attributeValues != null && m_Type == NOMINAL 
+                  && m_Values.size() == attributeValues.size();
+      signals (IllegalArgumentException ex) 
+                 (* if duplicate strings in attributeValues *);
+  */
+  public Attribute(String attributeName, 
+		   List<String> attributeValues,
+		   ProtectedProperties metadata) {
+
+    m_Name = attributeName;
+    m_Index = -1;
+    if (attributeValues == null) {
+      m_Values = new ArrayList<Object>();
+      m_Hashtable = new Hashtable<Object,Integer>();
+      m_Header = null;
+      m_Type = STRING;
+    } else {
+      m_Values = new ArrayList<Object>(attributeValues.size());
+      m_Hashtable = new Hashtable<Object,Integer>(attributeValues.size());
+      m_Header = null;
+      for (int i = 0; i < attributeValues.size(); i++) {
+	Object store = attributeValues.get(i);
+	if (((String)store).length() > STRING_COMPRESS_THRESHOLD) {
+	  try {
+	    store = new SerializedObject(attributeValues.get(i), true);
+	  } catch (Exception ex) {
+	    System.err.println("Couldn't compress nominal attribute value -"
+			       + " storing uncompressed.");
+	  }
+	}
+	if (m_Hashtable.containsKey(store)) {
+	  throw new IllegalArgumentException("A nominal attribute (" +
+					     attributeName + ") cannot"
+					     + " have duplicate labels (" + store + ").");
+	}
+	m_Values.add(store);
+	m_Hashtable.put(store, new Integer(i));
+      }
+      m_Type = NOMINAL;
+    }
+    setMetadata(metadata);
+  }
+
+  /**
+   * Constructor for relation-valued attributes.
+   *
+   * @param attributeName the name for the attribute
+   * @param header an Instances object specifying the header of the relation.
+   */
+  public Attribute(String attributeName, Instances header) {
+
+    this(attributeName, header,
+	 new ProtectedProperties(new Properties()));
+  }
+
+  /**
+   * Constructor for relation-valued attributes.
+   *
+   * @param attributeName the name for the attribute
+   * @param header an Instances object specifying the header of the relation.
+   * @param metadata the attribute's properties
+   */
+  public Attribute(String attributeName, 
+		   Instances header,
+		   ProtectedProperties metadata) {
+
+    if (header.numInstances() > 0) {
+      throw new IllegalArgumentException("Header for relation-valued " +
+                                         "attribute should not contain " +
+                                         "any instances");
+    }
+    m_Name = attributeName;
+    m_Index = -1;
+    m_Values = new ArrayList<Object>();
+    m_Hashtable = new Hashtable<Object,Integer>();
+    m_Header = header;
+    m_Type = RELATIONAL;
+    setMetadata(metadata);
+  }
+
+  /**
+   * Produces a shallow copy of this attribute.
+   *
+   * @return a copy of this attribute with the same index
+   */
+  //@ also ensures \result instanceof Attribute;
+  public /*@ pure non_null @*/ Object copy() {
+
+    Attribute copy = new Attribute(m_Name);
+
+    copy.m_Index = m_Index;
+    copy.m_Type = m_Type;
+    copy.m_Values = m_Values;
+    copy.m_Hashtable = m_Hashtable;
+    copy.m_DateFormat = m_DateFormat;
+    copy.m_Header = m_Header;
+    copy.setMetadata(m_Metadata);
+ 
+    return copy;
+  }
+
+  /**
+   * Returns an enumeration of all the attribute's values if the
+   * attribute is nominal, string, or relation-valued, null otherwise.
+   *
+   * @return enumeration of all the attribute's values
+   */
+  public final /*@ pure @*/ Enumeration enumerateValues() {
+
+    if (isNominal() || isString()) {
+      final Enumeration ee = new WekaEnumeration(m_Values);
+      return new Enumeration () {
+          public boolean hasMoreElements() {
+            return ee.hasMoreElements();
+          }
+          public Object nextElement() {
+            Object oo = ee.nextElement();
+            if (oo instanceof SerializedObject) {
+              return ((SerializedObject)oo).getObject();
+            } else {
+              return oo;
+            }
+          }
+        };
+    }
+    return null;
+  }
+
+  /**
+   * Tests if given attribute is equal to this attribute.
+   *
+   * @param other the Object to be compared to this attribute
+   * @return true if the given attribute is equal to this attribute
+   */
+  public final /*@ pure @*/ boolean equals(Object other) {
+    return (equalsMsg(other) == null);
+  }
+
+  /**
+   * Tests if given attribute is equal to this attribute. If they're not
+   * the same a message detailing why they differ will be returned, otherwise
+   * null.
+   *
+   * @param other 	the Object to be compared to this attribute
+   * @return 		null if the given attribute is equal to this attribute
+   */
+  public final String equalsMsg(Object other) {
+    if (other == null)
+      return "Comparing with null object";
+    
+    if (!(other.getClass().equals(this.getClass())))
+      return "Object has wrong class";
+    
+    Attribute att = (Attribute) other;
+    if (!m_Name.equals(att.m_Name))
+      return "Names differ: " + m_Name + " != " + att.m_Name;
+
+    if (isNominal() && att.isNominal()) {
+      if (m_Values.size() != att.m_Values.size())
+        return "Different number of labels: " + m_Values.size() + " != " + att.m_Values.size();
+      
+      for (int i = 0; i < m_Values.size(); i++) {
+        if (!m_Values.get(i).equals(att.m_Values.get(i)))
+          return "Labels differ at position " + (i+1) + ": " + m_Values.get(i) + " != " + att.m_Values.get(i);
+      }
+      
+      return null;
+    } 
+    
+    if (isRelationValued() && att.isRelationValued())
+      return m_Header.equalHeadersMsg(att.m_Header);
+    
+    if ((type() != att.type()))
+      return "Types differ: " + typeToString(this) + " != " + typeToString(att);
+    
+    return null;
+  }
+  
+  /**
+   * Returns a string representation of the attribute type.
+   * 
+   * @param att		the attribute to return the type string for
+   * @return		the string representation of the attribute type
+   */
+  public static String typeToString(Attribute att) {
+    return typeToString(att.type());
+  }
+  
+  /**
+   * Returns a string representation of the attribute type.
+   * 
+   * @param type	the type of the attribute
+   * @return		the string representation of the attribute type
+   */
+  public static String typeToString(int type) {
+    String	result;
+    
+    switch(type) {
+      case NUMERIC:
+	result = "numeric";
+	break;
+	
+      case NOMINAL:
+	result = "nominal";
+	break;
+	
+      case STRING:
+	result = "string";
+	break;
+	
+      case DATE:
+	result = "date";
+	break;
+	
+      case RELATIONAL:
+	result = "relational";
+	break;
+	
+      default:
+	result = "unknown(" + type + ")";
+    }
+    
+    return result;
+  }
+
+  /**
+   * Returns the index of this attribute.
+   *
+   * @return the index of this attribute
+   */
+  //@ ensures \result == m_Index;
+  public final /*@ pure @*/ int index() {
+
+    return m_Index;
+  }
+
+  /**
+   * Returns the index of a given attribute value. (The index of
+   * the first occurence of this value.)
+   *
+   * @param value the value for which the index is to be returned
+   * @return the index of the given attribute value if attribute
+   * is nominal or a string, -1 if it is not or the value 
+   * can't be found
+   */
+  public final int indexOfValue(String value) {
+
+    if (!isNominal() && !isString())
+      return -1;
+    Object store = value;
+    if (value.length() > STRING_COMPRESS_THRESHOLD) {
+      try {
+        store = new SerializedObject(value, true);
+      } catch (Exception ex) {
+        System.err.println("Couldn't compress string attribute value -"
+                           + " searching uncompressed.");
+      }
+    }
+    Integer val = (Integer)m_Hashtable.get(store);
+    if (val == null) return -1;
+    else return val.intValue();
+  }
+
+  /**
+   * Test if the attribute is nominal.
+   *
+   * @return true if the attribute is nominal
+   */
+  //@ ensures \result <==> (m_Type == NOMINAL);
+  public final /*@ pure @*/ boolean isNominal() {
+
+    return (m_Type == NOMINAL);
+  }
+
+  /**
+   * Tests if the attribute is numeric.
+   *
+   * @return true if the attribute is numeric
+   */
+  //@ ensures \result <==> ((m_Type == NUMERIC) || (m_Type == DATE));
+  public final /*@ pure @*/ boolean isNumeric() {
+
+    return ((m_Type == NUMERIC) || (m_Type == DATE));
+  }
+
+  /**
+   * Tests if the attribute is relation valued.
+   *
+   * @return true if the attribute is relation valued
+   */
+  //@ ensures \result <==> (m_Type == RELATIONAL);
+  public final /*@ pure @*/ boolean isRelationValued() {
+
+    return (m_Type == RELATIONAL);
+  }
+
+  /**
+   * Tests if the attribute is a string.
+   *
+   * @return true if the attribute is a string
+   */
+  //@ ensures \result <==> (m_Type == STRING);
+  public final /*@ pure @*/ boolean isString() {
+
+    return (m_Type == STRING);
+  }
+
+  /**
+   * Tests if the attribute is a date type.
+   *
+   * @return true if the attribute is a date type
+   */
+  //@ ensures \result <==> (m_Type == DATE);
+  public final /*@ pure @*/ boolean isDate() {
+
+    return (m_Type == DATE);
+  }
+
+  /**
+   * Returns the attribute's name.
+   *
+   * @return the attribute's name as a string
+   */
+  //@ ensures \result == m_Name;
+  public final /*@ pure @*/ String name() {
+
+    return m_Name;
+  }
+  
+  /**
+   * Returns the number of attribute values. Returns 0 for 
+   * attributes that are not either nominal, string, or
+   * relation-valued.
+   *
+   * @return the number of attribute values
+   */
+  public final /*@ pure @*/ int numValues() {
+
+    if (!isNominal() && !isString() && !isRelationValued()) {
+      return 0;
+    } else {
+      return m_Values.size();
+    }
+  }
+
+  /**
+   * Returns a description of this attribute in ARFF format. Quotes
+   * strings if they contain whitespace characters, or if they
+   * are a question mark.
+   *
+   * @return a description of this attribute as a string
+   */
+  public final String toString() {
+    
+    StringBuffer text = new StringBuffer();
+    
+    text.append(ARFF_ATTRIBUTE).append(" ").append(Utils.quote(m_Name)).append(" ");
+    switch (m_Type) {
+    case NOMINAL:
+      text.append('{');
+      Enumeration enu = enumerateValues();
+      while (enu.hasMoreElements()) {
+	text.append(Utils.quote((String) enu.nextElement()));
+	if (enu.hasMoreElements())
+	  text.append(',');
+      }
+      text.append('}');
+      break;
+    case NUMERIC:
+      text.append(ARFF_ATTRIBUTE_NUMERIC);
+      break;
+    case STRING:
+      text.append(ARFF_ATTRIBUTE_STRING);
+      break;
+    case DATE:
+      text.append(ARFF_ATTRIBUTE_DATE).append(" ").append(Utils.quote(m_DateFormat.toPattern()));
+      break;
+    case RELATIONAL:
+      text.append(ARFF_ATTRIBUTE_RELATIONAL).append("\n");
+      Enumeration enm = m_Header.enumerateAttributes();
+      while (enm.hasMoreElements()) {
+        text.append(enm.nextElement()).append("\n");
+      }
+      text.append(ARFF_END_SUBRELATION).append(" ").append(Utils.quote(m_Name));
+      break;
+    default:
+      text.append("UNKNOWN");
+      break;
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns the attribute's type as an integer.
+   *
+   * @return the attribute's type.
+   */
+  //@ ensures \result == m_Type;
+  public final /*@ pure @*/ int type() {
+
+    return m_Type;
+  }
+  
+  /**
+   * Returns the Date format pattern in case this attribute is of type DATE,
+   * otherwise an empty string.
+   * 
+   * @return the date format pattern
+   * @see SimpleDateFormat
+   */
+  public final String getDateFormat() {
+    if (isDate())
+      return m_DateFormat.toPattern();
+    else
+      return "";
+  }
+
+  /**
+   * Returns a value of a nominal or string attribute.  Returns an
+   * empty string if the attribute is neither a string nor a nominal
+   * attribute.
+   *
+   * @param valIndex the value's index
+   * @return the attribute's value as a string
+   */
+  public final /*@ non_null pure @*/ String value(int valIndex) {
+    
+    if (!isNominal() && !isString()) {
+      return "";
+    } else {
+      Object val = m_Values.get(valIndex);
+      
+      // If we're storing strings compressed, uncompress it.
+      if (val instanceof SerializedObject) {
+        val = ((SerializedObject)val).getObject();
+      }
+      return (String) val;
+    }
+  }
+
+  /**
+   * Returns the header info for a relation-valued attribute,
+   * null if the attribute is not relation-valued.
+   *
+   * @return the attribute's value as an Instances object
+   */
+  public final /*@ non_null pure @*/ Instances relation() {
+    
+    if (!isRelationValued()) {
+      return null;
+    } else {
+      return m_Header;
+    }
+  }
+
+  /**
+   * Returns a value of a relation-valued attribute. Returns
+   * null if the attribute is not relation-valued.
+   *
+   * @param valIndex the value's index
+   * @return the attribute's value as an Instances object
+   */
+  public final /*@ non_null pure @*/ Instances relation(int valIndex) {
+    
+    if (!isRelationValued()) {
+      return null;
+    } else {
+      return (Instances) m_Values.get(valIndex);
+    }
+  }
+
+  /**
+   * Constructor for a numeric attribute with a particular index.
+   *
+   * @param attributeName the name for the attribute
+   * @param index the attribute's index
+   */
+  //@ requires attributeName != null;
+  //@ requires index >= 0;
+  //@ ensures  m_Name == attributeName;
+  //@ ensures  m_Index == index;
+  public Attribute(String attributeName, int index) {
+
+    this(attributeName);
+    m_Index = index;
+  }
+
+  /**
+   * Constructor for date attributes with a particular index.
+   *
+   * @param attributeName the name for the attribute
+   * @param dateFormat a string suitable for use with
+   * SimpleDateFormatter for parsing dates.  Null for a default format
+   * string.
+   * @param index the attribute's index
+   */
+  //@ requires attributeName != null;
+  //@ requires index >= 0;
+  //@ ensures  m_Name == attributeName;
+  //@ ensures  m_Index == index;
+  public Attribute(String attributeName, String dateFormat, 
+	    int index) {
+
+    this(attributeName, dateFormat);
+    m_Index = index;
+  }
+
+  /**
+   * Constructor for nominal attributes and string attributes with
+   * a particular index.
+   * If a null vector of attribute values is passed to the method,
+   * the attribute is assumed to be a string.
+   *
+   * @param attributeName the name for the attribute
+   * @param attributeValues a vector of strings denoting the attribute values.
+   * Null if the attribute is a string attribute.
+   * @param index the attribute's index
+   */
+  //@ requires attributeName != null;
+  //@ requires index >= 0;
+  //@ ensures  m_Name == attributeName;
+  //@ ensures  m_Index == index;
+  public Attribute(String attributeName, List<String> attributeValues, 
+	    int index) {
+
+    this(attributeName, attributeValues);
+    m_Index = index;
+  }
+
+  /**
+   * Constructor for a relation-valued attribute with a particular index.
+   *
+   * @param attributeName the name for the attribute
+   * @param header the header information for this attribute
+   * @param index the attribute's index
+   */
+  //@ requires attributeName != null;
+  //@ requires index >= 0;
+  //@ ensures  m_Name == attributeName;
+  //@ ensures  m_Index == index;
+  public Attribute(String attributeName, Instances header,
+	    int index) {
+
+    this(attributeName, header);
+    m_Index = index;
+  }
+
+  /**
+   * Adds a string value to the list of valid strings for attributes
+   * of type STRING and returns the index of the string.
+   *
+   * @param value The string value to add
+   * @return the index assigned to the string, or -1 if the attribute is not
+   * of type Attribute.STRING 
+   */
+  /*@ requires value != null;
+      ensures  isString() && 0 <= \result && \result < m_Values.size() ||
+             ! isString() && \result == -1;
+  */
+  public int addStringValue(String value) {
+
+    if (!isString()) {
+      return -1;
+    }
+    Object store = value;
+
+    if (value.length() > STRING_COMPRESS_THRESHOLD) {
+      try {
+        store = new SerializedObject(value, true);
+      } catch (Exception ex) {
+        System.err.println("Couldn't compress string attribute value -"
+                           + " storing uncompressed.");
+      }
+    }
+    Integer index = (Integer)m_Hashtable.get(store);
+    if (index != null) {
+      return index.intValue();
+    } else {
+      int intIndex = m_Values.size();
+      m_Values.add(store);
+      m_Hashtable.put(store, new Integer(intIndex));
+      return intIndex;
+    }
+  }
+
+  /**
+   * Adds a string value to the list of valid strings for attributes
+   * of type STRING and returns the index of the string. This method is
+   * more efficient than addStringValue(String) for long strings.
+   *
+   * @param src The Attribute containing the string value to add.
+   * @param index the index of the string value in the source attribute.
+   * @return the index assigned to the string, or -1 if the attribute is not
+   * of type Attribute.STRING 
+   */
+  /*@ requires src != null;
+      requires 0 <= index && index < src.m_Values.size();
+      ensures  isString() && 0 <= \result && \result < m_Values.size() ||
+             ! isString() && \result == -1;
+  */
+  public int addStringValue(Attribute src, int index) {
+
+    if (!isString()) {
+      return -1;
+    }
+    Object store = src.m_Values.get(index);
+    Integer oldIndex = (Integer)m_Hashtable.get(store);
+    if (oldIndex != null) {
+      return oldIndex.intValue();
+    } else {
+      int intIndex = m_Values.size();
+      m_Values.add(store);
+      m_Hashtable.put(store, new Integer(intIndex));
+      return intIndex;
+    }
+  }
+
+  /**
+   * Adds a relation to a relation-valued attribute.
+   *
+   * @param value The value to add
+   * @return the index assigned to the value, or -1 if the attribute is not
+   * of type Attribute.RELATIONAL 
+   */
+  public int addRelation(Instances value) {
+
+    if (!isRelationValued()) {
+      return -1;
+    }
+    if (!m_Header.equalHeaders(value)) {
+      throw new IllegalArgumentException("Incompatible value for " +
+                                         "relation-valued attribute.\n" + 
+                                         m_Header.equalHeadersMsg(value));
+    }
+    Integer index = (Integer)m_Hashtable.get(value);
+    if (index != null) {
+      return index.intValue();
+    } else {
+      int intIndex = m_Values.size();
+      m_Values.add(value);
+      m_Hashtable.put(value, new Integer(intIndex));
+      return intIndex;
+    }
+  }
+
+  /**
+   * Adds an attribute value. Creates a fresh list of attribute
+   * values before adding it.
+   *
+   * @param value the attribute value
+   */
+  final void addValue(String value) {
+
+    m_Values = Utils.cast(m_Values.clone());
+    m_Hashtable = Utils.cast(m_Hashtable.clone());
+    forceAddValue(value);
+  }
+
+  /**
+   * Produces a shallow copy of this attribute with a new name.
+   *
+   * @param newName the name of the new attribute
+   * @return a copy of this attribute with the same index
+   */
+  //@ requires newName != null;
+  //@ ensures \result.m_Name  == newName;
+  //@ ensures \result.m_Index == m_Index;
+  //@ ensures \result.m_Type  == m_Type;
+  public final /*@ pure non_null @*/ Attribute copy(String newName) {
+
+    Attribute copy = new Attribute(newName);
+
+    copy.m_Index = m_Index;
+    copy.m_DateFormat = m_DateFormat;
+    copy.m_Type = m_Type;
+    copy.m_Values = m_Values;
+    copy.m_Hashtable = m_Hashtable;
+    copy.m_Header = m_Header;
+    copy.setMetadata(m_Metadata);
+ 
+    return copy;
+  }
+
+  /**
+   * Removes a value of a nominal, string, or relation-valued
+   * attribute. Creates a fresh list of attribute values before
+   * removing it.
+   *
+   * @param index the value's index
+   * @throws IllegalArgumentException if the attribute is not 
+   * of the correct type
+   */
+  //@ requires isNominal() || isString() || isRelationValued();
+  //@ requires 0 <= index && index < m_Values.size();
+  final void delete(int index) {
+    
+    if (!isNominal() && !isString() && !isRelationValued()) 
+      throw new IllegalArgumentException("Can only remove value of " +
+                                         "nominal, string or relation-" +
+                                         " valued attribute!");
+    else {
+      m_Values = Utils.cast(m_Values.clone());
+      m_Values.remove(index);
+      if (!isRelationValued()) {
+        Hashtable<Object,Integer> hash = new Hashtable<Object,Integer>(m_Hashtable.size());
+        Enumeration enu = m_Hashtable.keys();
+        while (enu.hasMoreElements()) {
+          Object string = enu.nextElement();
+          Integer valIndexObject = (Integer)m_Hashtable.get(string);
+          int valIndex = valIndexObject.intValue();
+          if (valIndex > index) {
+            hash.put(string, new Integer(valIndex - 1));
+          } else if (valIndex < index) {
+            hash.put(string, valIndexObject);
+          }
+        }
+        m_Hashtable = hash;
+      }
+    }
+  }
+
+  /**
+   * Adds an attribute value.
+   *
+   * @param value the attribute value
+   */
+  //@ requires value != null;
+  //@ ensures  m_Values.size() == \old(m_Values.size()) + 1;
+  final void forceAddValue(String value) {
+
+    Object store = value;
+    if (value.length() > STRING_COMPRESS_THRESHOLD) {
+      try {
+        store = new SerializedObject(value, true);
+      } catch (Exception ex) {
+        System.err.println("Couldn't compress string attribute value -"
+                           + " storing uncompressed.");
+      }
+    }
+    m_Values.add(store);
+    m_Hashtable.put(store, new Integer(m_Values.size() - 1));
+  }
+
+  /**
+   * Sets the index of this attribute.
+   *
+   * @param index the index of this attribute
+   */
+  //@ requires 0 <= index;
+  //@ assignable m_Index;
+  //@ ensures m_Index == index;
+  final void setIndex(int index) {
+
+    m_Index = index;
+  }
+
+  /**
+   * Sets a value of a nominal attribute or string attribute.
+   * Creates a fresh list of attribute values before it is set.
+   *
+   * @param index the value's index
+   * @param string the value
+   * @throws IllegalArgumentException if the attribute is not nominal or 
+   * string.
+   */
+  //@ requires string != null;
+  //@ requires isNominal() || isString();
+  //@ requires 0 <= index && index < m_Values.size();
+  final void setValue(int index, String string) {
+    
+    switch (m_Type) {
+    case NOMINAL:
+    case STRING:
+      m_Values = Utils.cast(m_Values.clone());
+      m_Hashtable = Utils.cast(m_Hashtable.clone());
+      Object store = string;
+      if (string.length() > STRING_COMPRESS_THRESHOLD) {
+        try {
+          store = new SerializedObject(string, true);
+        } catch (Exception ex) {
+          System.err.println("Couldn't compress string attribute value -"
+                             + " storing uncompressed.");
+        }
+      }
+      m_Hashtable.remove(m_Values.get(index));
+      m_Values.set(index, store);
+      m_Hashtable.put(store, new Integer(index));
+      break;
+    default:
+      throw new IllegalArgumentException("Can only set values for nominal"
+                                         + " or string attributes!");
+    }
+  }
+
+  /**
+   * Sets a value of a relation-valued attribute.
+   * Creates a fresh list of attribute values before it is set.
+   *
+   * @param index the value's index
+   * @param data the value
+   * @throws IllegalArgumentException if the attribute is not 
+   * relation-valued.
+   */
+  final void setValue(int index, Instances data) {
+    
+    if (isRelationValued()) { 
+      if (!data.equalHeaders(m_Header)) {
+        throw new IllegalArgumentException("Can't set relational value. " +
+                                           "Headers not compatible.\n" +
+                                           data.equalHeadersMsg(m_Header));
+      }
+      m_Values = Utils.cast(m_Values.clone());
+      m_Values.set(index, data);
+    } else {
+      throw new IllegalArgumentException("Can only set value for"
+                                         + " relation-valued attributes!");
+    }
+  }
+
+  /**
+   * Returns the given amount of milliseconds formatted according to the
+   * current Date format.
+   * 
+   * @param date 	the date, represented in milliseconds since 
+   * 			January 1, 1970, 00:00:00 GMT, to return as string
+   * @return 		the formatted date
+   */
+  //@ requires isDate();
+  public /*@pure@*/ String formatDate(double date) {
+    switch (m_Type) {
+    case DATE:
+      return m_DateFormat.format(new Date((long)date));
+    default:
+      throw new IllegalArgumentException("Can only format date values for date"
+                                         + " attributes!");
+    }
+  }
+
+  /**
+   * Parses the given String as Date, according to the current format and
+   * returns the corresponding amount of milliseconds.
+   * 
+   * @param string the date to parse
+   * @return the date in milliseconds since January 1, 1970, 00:00:00 GMT
+   * @throws ParseException if parsing fails
+   */
+  //@ requires isDate();
+  //@ requires string != null;
+  public double parseDate(String string) throws ParseException {
+    switch (m_Type) {
+    case DATE:
+      long time = m_DateFormat.parse(string).getTime();
+      // TODO put in a safety check here if we can't store the value in a double.
+      return (double)time;
+    default:
+      throw new IllegalArgumentException("Can only parse date values for date"
+                                         + " attributes!");
+    }
+  }
+
+  /**
+   * Returns the properties supplied for this attribute.
+   *
+   * @return metadata for this attribute
+   */  
+  public final /*@ pure @*/ ProtectedProperties getMetadata() {
+
+    return m_Metadata;
+  }
+
+  /**
+   * Returns the ordering of the attribute. One of the following:
+   * 
+   * ORDERING_SYMBOLIC - attribute values should be treated as symbols.
+   * ORDERING_ORDERED  - attribute values have a global ordering.
+   * ORDERING_MODULO   - attribute values have an ordering which wraps.
+   *
+   * @return the ordering type of the attribute
+   */
+  public final /*@ pure @*/ int ordering() {
+
+    return m_Ordering;
+  }
+
+  /**
+   * Returns whether the attribute values are equally spaced.
+   *
+   * @return whether the attribute is regular or not
+   */
+  public final /*@ pure @*/ boolean isRegular() {
+
+    return m_IsRegular;
+  }
+
+  /**
+   * Returns whether the attribute can be averaged meaningfully.
+   *
+   * @return whether the attribute can be averaged or not
+   */
+  public final /*@ pure @*/ boolean isAveragable() {
+
+    return m_IsAveragable;
+  }
+
+  /**
+   * Returns whether the attribute has a zeropoint and may be
+   * added meaningfully.
+   *
+   * @return whether the attribute has a zeropoint or not
+   */
+  public final /*@ pure @*/ boolean hasZeropoint() {
+
+    return m_HasZeropoint;
+  }
+
+  /**
+   * Returns the attribute's weight.
+   *
+   * @return the attribute's weight as a double
+   */
+  public final /*@ pure @*/ double weight() {
+
+    return m_Weight;
+  }
+
+  /**
+   * Sets the new attribute's weight
+   * 
+   * @param value	the new weight
+   */
+  public void setWeight(double value) {
+    Properties	props;
+    Enumeration names;
+    String	name;
+    
+    m_Weight = value;
+
+    // generate new metadata object
+    props = new Properties();
+    names = m_Metadata.propertyNames();
+    while (names.hasMoreElements()) {
+      name = (String) names.nextElement();
+      if (!name.equals("weight"))
+	props.setProperty(name, m_Metadata.getProperty(name));
+    }
+    props.setProperty("weight", "" + m_Weight);
+    m_Metadata = new ProtectedProperties(props);
+  }
+  
+  /**
+   * Returns the lower bound of a numeric attribute.
+   *
+   * @return the lower bound of the specified numeric range
+   */
+  public final /*@ pure @*/ double getLowerNumericBound() {
+
+    return m_LowerBound;
+  }
+
+  /**
+   * Returns whether the lower numeric bound of the attribute is open.
+   *
+   * @return whether the lower numeric bound is open or not (closed)
+   */
+  public final /*@ pure @*/ boolean lowerNumericBoundIsOpen() {
+
+    return m_LowerBoundIsOpen;
+  }
+
+  /**
+   * Returns the upper bound of a numeric attribute.
+   *
+   * @return the upper bound of the specified numeric range
+   */
+  public final /*@ pure @*/ double getUpperNumericBound() {
+
+    return m_UpperBound;
+  }
+
+  /**
+   * Returns whether the upper numeric bound of the attribute is open.
+   *
+   * @return whether the upper numeric bound is open or not (closed)
+   */
+  public final /*@ pure @*/ boolean upperNumericBoundIsOpen() {
+
+    return m_UpperBoundIsOpen;
+  }
+
+  /**
+   * Determines whether a value lies within the bounds of the attribute.
+   *
+   * @param value the value to check
+   * @return whether the value is in range
+   */
+  public final /*@ pure @*/ boolean isInRange(double value) {
+
+    // dates and missing values are a special case 
+    if (m_Type == DATE || Utils.isMissingValue(value)) return true;
+    if (m_Type != NUMERIC) {
+      // do label range check
+      int intVal = (int) value;
+      if (intVal < 0 || intVal >= m_Hashtable.size()) return false;
+    } else {
+      // do numeric bounds check
+      if (m_LowerBoundIsOpen) {
+	if (value <= m_LowerBound) return false;
+      } else {
+	if (value < m_LowerBound) return false;
+      }
+      if (m_UpperBoundIsOpen) {
+	if (value >= m_UpperBound) return false;
+      } else {
+	if (value > m_UpperBound) return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Sets the metadata for the attribute. Processes the strings stored in the
+   * metadata of the attribute so that the properties can be set up for the
+   * easy-access metadata methods. Any strings sought that are omitted will
+   * cause default values to be set.
+   * 
+   * The following properties are recognised:
+   * ordering, averageable, zeropoint, regular, weight, and range.
+   *
+   * All other properties can be queried and handled appropriately by classes
+   * calling the getMetadata() method.
+   *
+   * @param metadata the metadata
+   * @throws IllegalArgumentException if the properties are not consistent
+   */
+  //@ requires metadata != null;
+  private void setMetadata(ProtectedProperties metadata) {
+    
+    m_Metadata = metadata;
+
+    if (m_Type == DATE) {
+      m_Ordering = ORDERING_ORDERED;
+      m_IsRegular = true;
+      m_IsAveragable = false;
+      m_HasZeropoint = false;
+    } else {
+
+      // get ordering
+      String orderString = m_Metadata.getProperty("ordering","");
+      
+      // numeric ordered attributes are averagable and zeropoint by default
+      String def;
+      if (m_Type == NUMERIC
+	  && orderString.compareTo("modulo") != 0
+	  && orderString.compareTo("symbolic") != 0)
+	def = "true";
+      else def = "false";
+      
+      // determine boolean states
+      m_IsAveragable =
+	(m_Metadata.getProperty("averageable",def).compareTo("true") == 0);
+      m_HasZeropoint =
+	(m_Metadata.getProperty("zeropoint",def).compareTo("true") == 0);
+      // averagable or zeropoint implies regular
+      if (m_IsAveragable || m_HasZeropoint) def = "true";
+      m_IsRegular =
+	(m_Metadata.getProperty("regular",def).compareTo("true") == 0);
+      
+      // determine ordering
+      if (orderString.compareTo("symbolic") == 0)
+	m_Ordering = ORDERING_SYMBOLIC;
+      else if (orderString.compareTo("ordered") == 0)
+	m_Ordering = ORDERING_ORDERED;
+      else if (orderString.compareTo("modulo") == 0)
+	m_Ordering = ORDERING_MODULO;
+      else {
+	if (m_Type == NUMERIC || m_IsAveragable || m_HasZeropoint)
+	  m_Ordering = ORDERING_ORDERED;
+	else m_Ordering = ORDERING_SYMBOLIC;
+      }
+    }
+
+    // consistency checks
+    if (m_IsAveragable && !m_IsRegular)
+      throw new IllegalArgumentException("An averagable attribute must be"
+					 + " regular");
+    if (m_HasZeropoint && !m_IsRegular)
+      throw new IllegalArgumentException("A zeropoint attribute must be"
+					 + " regular");
+    if (m_IsRegular && m_Ordering == ORDERING_SYMBOLIC)
+      throw new IllegalArgumentException("A symbolic attribute cannot be"
+					 + " regular");
+    if (m_IsAveragable && m_Ordering != ORDERING_ORDERED)
+      throw new IllegalArgumentException("An averagable attribute must be"
+					 + " ordered");
+    if (m_HasZeropoint && m_Ordering != ORDERING_ORDERED)
+      throw new IllegalArgumentException("A zeropoint attribute must be"
+					 + " ordered");
+
+    // determine weight
+    m_Weight = 1.0;
+    String weightString = m_Metadata.getProperty("weight");
+    if (weightString != null) {
+      try{
+	m_Weight = Double.valueOf(weightString).doubleValue();
+      } catch (NumberFormatException e) {
+	// Check if value is really a number
+	throw new IllegalArgumentException("Not a valid attribute weight: '" 
+					   + weightString + "'");
+      }
+    }
+
+    // determine numeric range
+    if (m_Type == NUMERIC) setNumericRange(m_Metadata.getProperty("range"));
+  }
+
+  /**
+   * Sets the numeric range based on a string. If the string is null the range
+   * will default to [-inf,+inf]. A square brace represents a closed interval, a
+   * curved brace represents an open interval, and 'inf' represents infinity.
+   * Examples of valid range strings: "[-inf,20)","(-13.5,-5.2)","(5,inf]"
+   *
+   * @param rangeString the string to parse as the attribute's numeric range
+   * @throws IllegalArgumentException if the range is not valid
+   */
+  //@ requires rangeString != null;
+  private void setNumericRange(String rangeString)
+  {
+    // set defaults
+    m_LowerBound = Double.NEGATIVE_INFINITY;
+    m_LowerBoundIsOpen = false;
+    m_UpperBound = Double.POSITIVE_INFINITY;
+    m_UpperBoundIsOpen = false;
+
+    if (rangeString == null) return;
+
+    // set up a tokenzier to parse the string
+    StreamTokenizer tokenizer =
+      new StreamTokenizer(new StringReader(rangeString));
+    tokenizer.resetSyntax();         
+    tokenizer.whitespaceChars(0, ' ');    
+    tokenizer.wordChars(' '+1,'\u00FF');
+    tokenizer.ordinaryChar('[');
+    tokenizer.ordinaryChar('(');
+    tokenizer.ordinaryChar(',');
+    tokenizer.ordinaryChar(']');
+    tokenizer.ordinaryChar(')');
+
+    try {
+
+      // get opening brace
+      tokenizer.nextToken();
+    
+      if (tokenizer.ttype == '[') m_LowerBoundIsOpen = false;
+      else if (tokenizer.ttype == '(') m_LowerBoundIsOpen = true;
+      else throw new IllegalArgumentException("Expected opening brace on range,"
+					      + " found: "
+					      + tokenizer.toString());
+
+      // get lower bound
+      tokenizer.nextToken();
+      if (tokenizer.ttype != tokenizer.TT_WORD)
+	throw new IllegalArgumentException("Expected lower bound in range,"
+					   + " found: "
+					   + tokenizer.toString());
+      if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
+	m_LowerBound = Double.NEGATIVE_INFINITY;
+      else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
+	m_LowerBound = Double.POSITIVE_INFINITY;
+      else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
+	m_LowerBound = Double.NEGATIVE_INFINITY;
+      else try {
+	m_LowerBound = Double.valueOf(tokenizer.sval).doubleValue();
+      } catch (NumberFormatException e) {
+	throw new IllegalArgumentException("Expected lower bound in range,"
+					   + " found: '" + tokenizer.sval + "'");
+      }
+
+      // get separating comma
+      if (tokenizer.nextToken() != ',')
+	throw new IllegalArgumentException("Expected comma in range,"
+					   + " found: "
+					   + tokenizer.toString());
+
+      // get upper bound
+      tokenizer.nextToken();
+      if (tokenizer.ttype != tokenizer.TT_WORD)
+	throw new IllegalArgumentException("Expected upper bound in range,"
+					   + " found: "
+					   + tokenizer.toString());
+      if (tokenizer.sval.compareToIgnoreCase("-inf") == 0)
+	m_UpperBound = Double.NEGATIVE_INFINITY;
+      else if (tokenizer.sval.compareToIgnoreCase("+inf") == 0)
+	m_UpperBound = Double.POSITIVE_INFINITY;
+      else if (tokenizer.sval.compareToIgnoreCase("inf") == 0)
+	m_UpperBound = Double.POSITIVE_INFINITY;
+      else try {
+	m_UpperBound = Double.valueOf(tokenizer.sval).doubleValue();
+      } catch (NumberFormatException e) {
+	throw new IllegalArgumentException("Expected upper bound in range,"
+					   + " found: '" + tokenizer.sval + "'");
+      }
+
+      // get closing brace
+      tokenizer.nextToken();
+    
+      if (tokenizer.ttype == ']') m_UpperBoundIsOpen = false;
+      else if (tokenizer.ttype == ')') m_UpperBoundIsOpen = true;
+      else throw new IllegalArgumentException("Expected closing brace on range,"
+					      + " found: "
+					      + tokenizer.toString());
+
+      // check for rubbish on end
+      if (tokenizer.nextToken() != tokenizer.TT_EOF)
+	throw new IllegalArgumentException("Expected end of range string,"
+					   + " found: "
+					   + tokenizer.toString());
+
+    } catch (IOException e) {
+      throw new IllegalArgumentException("IOException reading attribute range"
+					 + " string: " + e.getMessage());
+    }
+
+    if (m_UpperBound < m_LowerBound)
+      throw new IllegalArgumentException("Upper bound (" + m_UpperBound
+					 + ") on numeric range is"
+					 + " less than lower bound ("
+					 + m_LowerBound + ")!");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Simple main method for testing this class.
+   * 
+   * @param ops the commandline options
+   */
+  //@ requires ops != null;
+  //@ requires \nonnullelements(ops);
+  public static void main(String[] ops) {
+
+    try {
+      
+      // Create numeric attributes "length" and "weight"
+      Attribute length = new Attribute("length");
+      Attribute weight = new Attribute("weight");
+
+      // Create date attribute "date"
+      Attribute date = new Attribute("date", "yyyy-MM-dd HH:mm:ss");
+
+      System.out.println(date);
+      double dd = date.parseDate("2001-04-04 14:13:55");
+      System.out.println("Test date = " + dd);
+      System.out.println(date.formatDate(dd));
+
+      dd = new Date().getTime();
+      System.out.println("Date now = " + dd);
+      System.out.println(date.formatDate(dd));
+      
+      // Create vector to hold nominal values "first", "second", "third" 
+      List<String> my_nominal_values = new ArrayList<String>(3); 
+      my_nominal_values.add("first"); 
+      my_nominal_values.add("second"); 
+      my_nominal_values.add("third"); 
+      
+      // Create nominal attribute "position" 
+      Attribute position = new Attribute("position", my_nominal_values);
+
+      // Print the name of "position"
+      System.out.println("Name of \"position\": " + position.name());
+
+      // Print the values of "position"
+      Enumeration attValues = position.enumerateValues();
+      while (attValues.hasMoreElements()) {
+	String string = (String)attValues.nextElement();
+	System.out.println("Value of \"position\": " + string);
+      }
+
+      // Shallow copy attribute "position"
+      Attribute copy = (Attribute) position.copy();
+
+      // Test if attributes are the same
+      System.out.println("Copy is the same as original: " + copy.equals(position));
+
+      // Print index of attribute "weight" (should be unset: -1)
+      System.out.println("Index of attribute \"weight\" (should be -1): " + 
+			 weight.index());
+
+      // Print index of value "first" of attribute "position"
+      System.out.println("Index of value \"first\" of \"position\" (should be 0): " +
+			 position.indexOfValue("first"));
+
+      // Tests type of attribute "position"
+      System.out.println("\"position\" is numeric: " + position.isNumeric());
+      System.out.println("\"position\" is nominal: " + position.isNominal());
+      System.out.println("\"position\" is string: " + position.isString());
+
+      // Prints name of attribute "position"
+      System.out.println("Name of \"position\": " + position.name());
+    
+      // Prints number of values of attribute "position"
+      System.out.println("Number of values for \"position\": " + position.numValues());
+
+      // Prints the values (againg)
+      for (int i = 0; i < position.numValues(); i++) {
+	System.out.println("Value " + i + ": " + position.value(i));
+      }
+
+      // Prints the attribute "position" in ARFF format
+      System.out.println(position);
+
+      // Checks type of attribute "position" using constants
+      switch (position.type()) {
+      case Attribute.NUMERIC:
+	System.out.println("\"position\" is numeric");
+	break;
+      case Attribute.NOMINAL:
+	System.out.println("\"position\" is nominal");
+	break;
+      case Attribute.STRING:
+	System.out.println("\"position\" is string");
+	break;
+      case Attribute.DATE:
+	System.out.println("\"position\" is date");
+	break;
+      case Attribute.RELATIONAL:
+	System.out.println("\"position\" is relation-valued");
+	break;
+      default:
+	System.out.println("\"position\" has unknown type");
+      }
+
+      ArrayList<Attribute> atts = new ArrayList<Attribute>(1);
+      atts.add(position);
+      Instances relation = new Instances("Test", atts, 0);
+      Attribute relationValuedAtt = new Attribute("test", relation);
+      System.out.println(relationValuedAtt);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+  
Index: branches/MetisMQI/src/main/java/weka/core/AttributeExpression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AttributeExpression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AttributeExpression.java	(revision 29)
@@ -0,0 +1,588 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeExpression.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Stack;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * A general purpose class for parsing mathematical expressions
+ * involving attribute values. Values can be provided in an array
+ * or in an Instance. Values are accessed in the expression by
+ * prefixing their index (starting at 1) with the character 'a'.
+ *
+ * <pre> Example expression: a1^2*a5/log(a7*4.0) </pre>
+ *
+ * Supported opperators: +, -, *, /, ^, log, abs, cos, exp, sqrt,
+ * floor, ceil, rint, tan, sin, (, ).
+ *
+ * @author Mark Hall
+ * @version $Revision: 5988 $
+ */
+public class AttributeExpression
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 402130123261736245L;
+  
+  /**
+   * Interface implemented by operators and operants.
+   */
+  private interface ExpressionComponent {};
+
+  /**
+   * Inner class handling an attribute index as an operand
+   */
+  private class AttributeOperand  
+    implements ExpressionComponent, Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -7674280127286031105L;
+
+    /** the index of the attribute */
+    protected int m_attributeIndex;
+
+    /** true if the value of the attribute are to be multiplied by -1 */
+    protected boolean m_negative;
+
+    /**
+     * Constructor
+     * 
+     * @param operand
+     * @param sign
+     * @throws Exception
+     */
+    public AttributeOperand(String operand, boolean sign) throws Exception {
+      // strip the leading 'a' and set the index
+      m_attributeIndex = (Integer.parseInt(operand.substring(1)))-1;
+      m_negative = sign;
+    }
+
+    /**
+     * Return a string describing this object
+     * @return a string descibing the attribute operand
+     */
+    public String toString() {
+      String result = "";
+      if (m_negative) {
+	result += '-';
+      }
+      return result+"a"+(m_attributeIndex+1);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5988 $");
+    }
+  }
+
+  /**
+   * Inner class for storing numeric constant opperands
+   */
+  private class NumericOperand 
+    implements ExpressionComponent, Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = 9037007836243662859L;
+
+    /** numeric constant */
+    protected double m_numericConst;
+
+    /**
+     * Constructor
+     * 
+     * @param operand
+     * @param sign
+     * @throws Exception
+     */
+    public NumericOperand(String operand, boolean sign) throws Exception {
+      m_numericConst = Double.valueOf(operand).doubleValue();
+      if (sign) {
+	m_numericConst *= -1.0;
+      }
+    }
+    
+    /**
+     * Return a string describing this object
+     * @return a string descibing the numeric operand
+     */
+    public String toString() {
+      return ""+m_numericConst;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5988 $");
+    }
+  }
+
+  /**
+   * Inner class for storing operators
+   */
+  private class Operator 
+    implements ExpressionComponent, Serializable, RevisionHandler {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2760353522666004638L;
+
+    /** the operator */
+    protected char m_operator;
+
+    /**
+     * Constructor
+     * 
+     * @param opp the operator
+     */
+    public Operator(char opp) {
+      if (!isOperator(opp)) {
+	throw new IllegalArgumentException("Unrecognized operator:" + opp);
+      }
+      m_operator = opp;
+    }
+
+    /**
+     * Apply this operator to the supplied arguments
+     * @param first the first argument
+     * @param second the second argument
+     * @return the result
+     */
+    protected double applyOperator(double first, double second) {
+      switch (m_operator) {
+	case '+' :
+	  return (first+second);
+	case '-' :
+	  return (first-second);
+	case '*' :
+	  return (first*second);
+	case '/' :
+	  return (first/second);
+	case '^' :
+	  return Math.pow(first,second);
+      }
+      return Double.NaN;
+    }
+
+    /**
+     * Apply this operator (function) to the supplied argument
+     * @param value the argument
+     * @return the result
+     */
+    protected double applyFunction(double value) {
+      switch (m_operator) {
+	case 'l' :
+	  return Math.log(value);
+	case 'b' :
+	  return Math.abs(value);
+	case 'c' :
+	  return Math.cos(value);
+	case 'e' :
+	  return Math.exp(value);
+	case 's' :
+	  return Math.sqrt(value);
+	case 'f' :
+	  return Math.floor(value);
+	case 'h' :
+	  return Math.ceil(value);
+	case 'r' :
+	  return Math.rint(value);
+	case 't' :
+	  return Math.tan(value);
+	case 'n' :
+	  return Math.sin(value);
+      }
+      return Double.NaN;
+    }
+
+    /**
+     * Return a string describing this object
+     * @return a string descibing the operator
+     */
+    public String toString() {
+      return ""+m_operator;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5988 $");
+    }
+  }
+
+  /** Operator stack */
+  private Stack<String> m_operatorStack = new Stack<String>();
+
+  /** Supported operators. l = log, b = abs, c = cos, e = exp, s = sqrt, 
+      f = floor, h = ceil, r = rint, t = tan, n = sin */
+  private static final String OPERATORS = "+-*/()^lbcesfhrtn";
+  /** Unary functions. l = log, b = abs, c = cos, e = exp, s = sqrt, 
+      f = floor, h = ceil, r = rint, t = tan, n = sin */
+  private static final String UNARY_FUNCTIONS = "lbcesfhrtn";
+
+  /** Holds the original infix expression */
+  private String m_originalInfix;
+  
+  /** Holds the expression in postfix form */
+  private Vector<ExpressionComponent> m_postFixExpVector;
+
+  /** True if the next numeric constant or attribute index is negative */
+  private boolean m_signMod = false;
+
+  /** Holds the previous token */
+  private String m_previousTok = "";
+
+    /**
+   * Handles the processing of an infix operand to postfix
+   * @param tok the infix operand
+   * @throws Exception if there is difficulty parsing the operand
+   */
+  private void handleOperand(String tok) throws Exception {
+    // if it contains an 'a' then its an attribute index
+    if (tok.indexOf('a') != -1) {
+      m_postFixExpVector.addElement(new AttributeOperand(tok,m_signMod));
+    } else {
+      try {
+	// should be a numeric constant
+	m_postFixExpVector.addElement(new NumericOperand(tok, m_signMod));
+      } catch (NumberFormatException ne) {
+	throw new Exception("Trouble parsing numeric constant");
+      }
+    }
+    m_signMod = false;
+  }
+
+  /**
+   * Handles the processing of an infix operator to postfix
+   * @param tok the infix operator
+   * @throws Exception if there is difficulty parsing the operator
+   */
+  private void handleOperator(String tok) throws Exception {
+    boolean push = true;
+
+    char tokchar = tok.charAt(0);
+    if (tokchar == ')') {
+      String popop = " ";
+      do {
+	popop = (String)(m_operatorStack.pop());
+	if (popop.charAt(0) != '(') {
+	  m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
+	}
+      } while (popop.charAt(0) != '(');
+    } else {
+      int infixToc = infixPriority(tok.charAt(0));
+      while (!m_operatorStack.empty() && 
+	     stackPriority(((String)(m_operatorStack.peek())).charAt(0)) 
+	     >= infixToc) {
+	
+	// try an catch double operators and see if the current one can
+	// be interpreted as the sign of an upcoming number
+	if (m_previousTok.length() == 1 && 
+	    isOperator(m_previousTok.charAt(0)) &&
+	    m_previousTok.charAt(0) != ')') {
+	  if (tok.charAt(0) == '-') {
+	    m_signMod = true;
+	  } else {
+	    m_signMod = false;
+	  }
+	  push = false;
+	  break;
+	} else {
+	  String popop = (String)(m_operatorStack.pop());
+	  m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
+	}
+      }
+      if (m_postFixExpVector.size() == 0) {
+	if (tok.charAt(0) == '-') {
+	  m_signMod = true;
+	  push = false;
+	}
+      }
+
+      if (push) {
+	m_operatorStack.push(tok);
+      }
+    }
+  }
+
+  /**
+   * Converts a string containing a mathematical expression in infix form
+   * to postfix form. The result is stored in the vector m_postfixExpVector
+   *
+   * @param infixExp the infix expression to convert
+   * @throws Exception if something goes wrong during the conversion
+   */
+  public void convertInfixToPostfix(String infixExp) throws Exception {
+    m_originalInfix = infixExp;
+
+    infixExp = Utils.removeSubstring(infixExp, " ");
+    infixExp = Utils.replaceSubstring(infixExp,"log","l");
+    infixExp = Utils.replaceSubstring(infixExp,"abs","b");
+    infixExp = Utils.replaceSubstring(infixExp,"cos","c");
+    infixExp = Utils.replaceSubstring(infixExp,"exp","e");
+    infixExp = Utils.replaceSubstring(infixExp,"sqrt","s");
+    infixExp = Utils.replaceSubstring(infixExp,"floor","f");
+    infixExp = Utils.replaceSubstring(infixExp,"ceil","h");
+    infixExp = Utils.replaceSubstring(infixExp,"rint","r");
+    infixExp = Utils.replaceSubstring(infixExp,"tan","t");
+    infixExp = Utils.replaceSubstring(infixExp,"sin","n");
+
+    StringTokenizer tokenizer = new StringTokenizer(infixExp, OPERATORS, true);
+    m_postFixExpVector = new Vector<ExpressionComponent>();
+
+    while (tokenizer.hasMoreTokens()) {
+      String tok = tokenizer.nextToken();
+      
+      if (tok.length() > 1) {
+	handleOperand(tok);
+      } else {
+	// probably an operator, but could be a single char operand
+	if (isOperator(tok.charAt(0))) {
+	  handleOperator(tok);
+	} else {
+	  // should be a numeric constant
+	  handleOperand(tok);
+	}
+      }
+      m_previousTok = tok;
+    }
+    while (!m_operatorStack.empty()) {
+      String popop = (String)(m_operatorStack.pop());
+      if (popop.charAt(0) == '(' || popop.charAt(0) == ')') {
+	throw new Exception("Mis-matched parenthesis!");
+      }
+      m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
+    }
+  }
+
+  /**
+   * Evaluate the expression using the supplied Instance.
+   * Assumes that the infix expression has been converted to 
+   * postfix and stored in m_postFixExpVector
+   *
+   * @param instance the Instance containing values to apply
+   * the expression to
+   * @throws Exception if something goes wrong
+   */
+  public double evaluateExpression(Instance instance)
+    throws Exception {
+    double [] vals = new double [instance.numAttributes()+1];
+    for(int i = 0; i < instance.numAttributes(); i++) {
+      if (instance.isMissing(i)) {
+	vals[i] = Utils.missingValue();
+      } else {
+	vals[i] = instance.value(i);
+      }
+    }
+    
+    evaluateExpression(vals);
+    return vals[vals.length - 1];
+  }
+
+  /**
+   * Evaluate the expression using the supplied array of attribute values.
+   * The result is stored in the last element of the array. Assumes that
+   * the infix expression has been converted to postfix and stored in
+   * m_postFixExpVector
+   * @param vals the values to apply the expression to
+   * @throws Exception if something goes wrong
+   */
+  public void evaluateExpression(double [] vals) throws Exception {
+    Stack<Double> operands = new Stack<Double>();
+
+    for (int i=0;i<m_postFixExpVector.size();i++) {
+      Object nextob = m_postFixExpVector.elementAt(i);
+      if (nextob instanceof NumericOperand) {
+	operands.push(new Double(((NumericOperand)nextob).m_numericConst));
+      } else if (nextob instanceof AttributeOperand) {
+	double value = vals[((AttributeOperand)nextob).m_attributeIndex];
+	/*if (Utils.isMissingValue(value)) {
+	  vals[vals.length-1] = Utils.missingValue();
+	  break;
+	}*/
+	if (((AttributeOperand)nextob).m_negative) {
+	  value = -value;
+	}
+	operands.push(new Double(value));
+      } else if (nextob instanceof Operator) {
+	char op = ((Operator)nextob).m_operator;
+	if (isUnaryFunction(op)) {
+	  double operand = ((Double)operands.pop()).doubleValue();
+	  double result = ((Operator)nextob).applyFunction(operand);
+	  operands.push(new Double(result));
+	} else {
+	  double second = ((Double)operands.pop()).doubleValue();
+	  double first = ((Double)operands.pop()).doubleValue();
+	  double result = ((Operator)nextob).applyOperator(first,second);
+	  operands.push(new Double(result));
+	}
+      } else {
+	throw new Exception("Unknown object in postfix vector!");
+      }
+    }
+
+    if (operands.size() != 1) {
+      throw new Exception("Problem applying function");
+    }
+
+    Double result = ((Double)operands.pop());
+    if (result.isNaN() || result.isInfinite()) {
+      vals[vals.length-1] = Utils.missingValue();
+    } else {
+      vals[vals.length-1] = result.doubleValue();
+    }
+  }
+
+  /**
+   * Returns true if a token is an operator
+   * @param tok the token to check
+   * @return true if the supplied token is an operator
+   */
+  private boolean isOperator(char tok) {
+    if (OPERATORS.indexOf(tok) == -1) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns true if a token is a unary function
+   * @param tok the token to check
+   * @return true if the supplied token is a unary function
+   */
+  private boolean isUnaryFunction(char tok) {
+    if (UNARY_FUNCTIONS.indexOf(tok) == -1) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Return the infix priority of an operator
+   * @param opp the operator
+   * @return the infix priority
+   */
+  private int infixPriority(char opp) {
+    switch (opp) {
+      case 'l' : 
+      case 'b' :
+      case 'c' :
+      case 'e' :
+      case 's' :
+      case 'f' :
+      case 'h' :
+      case 'r' :
+      case 't' :
+      case 'n' :
+	return 3;
+      case '^' :
+	return 2;
+      case '*' : 
+	return 2;
+      case '/' : 
+	return 2;
+      case '+' :
+	return 1;
+      case '-' :
+	return 1;
+      case '(' :
+	return 4;
+      case ')' :
+	return 0;
+      default :
+	throw new IllegalArgumentException("Unrecognized operator:" + opp);
+    }
+  }
+
+  /**
+   * Return the stack priority of an operator
+   * @param opp the operator
+   * @return the stack priority
+   */
+  private int stackPriority(char opp) {
+     switch (opp) {
+       case 'l' :
+       case 'b' :
+       case 'c' :
+       case 'e' :
+       case 's' :
+       case 'f' :
+       case 'h' :
+       case 'r' :
+       case 't' :
+       case 'n' :
+	 return 3;
+       case '^' :
+	 return 2;
+       case '*' : 
+	 return 2;
+       case '/' : 
+	 return 2;
+       case '+' :
+	 return 1;
+       case '-' :
+	 return 1;
+       case '(' :
+	 return 0;
+       case ')' :
+	 return -1;
+       default :
+	 throw new IllegalArgumentException("Unrecognized operator:" + opp);
+    }
+  }
+
+  /**
+   * Return the postfix expression
+   *
+   * @return the postfix expression as a String
+   */
+  public String getPostFixExpression() {
+    return m_postFixExpVector.toString();
+  }
+
+  public String toString() {
+    return m_originalInfix;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5988 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/AttributeLocator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AttributeLocator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AttributeLocator.java	(revision 29)
@@ -0,0 +1,336 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * StringLocator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Vector;
+
+/**
+ * This class locates and records the indices of a certain type of attributes, 
+ * recursively in case of Relational attributes.
+ * 
+ * @author fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Attribute#RELATIONAL
+ */
+public class AttributeLocator 
+  implements Serializable, Comparable<AttributeLocator>, RevisionHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -2932848827681070345L;
+
+  /** the attribute indices that may be inspected */
+  protected int[] m_AllowedIndices = null;
+  
+  /** contains the attribute locations, either true or false Boolean objects */
+  protected Vector<Boolean> m_Attributes = null;
+  
+  /** contains the locator locations, either null or a AttributeLocator reference */
+  protected Vector<AttributeLocator> m_Locators = null;
+
+  /** the type of the attribute */
+  protected int m_Type = -1;
+  
+  /** the referenced data */
+  protected Instances m_Data = null;
+
+  /** the indices */
+  protected int[] m_Indices = null;
+
+  /** the indices of locator objects */
+  protected int[] m_LocatorIndices = null;
+  
+  /**
+   * Initializes the AttributeLocator with the given data for the specified
+   * type of attribute. Checks all attributes.
+   * 
+   * @param data	the data to work on
+   * @param type	the type of attribute to locate
+   */
+  public AttributeLocator(Instances data, int type) {
+    this(data, type, 0, data.numAttributes() - 1);
+  }
+  
+  /**
+   * Initializes the AttributeLocator with the given data for the specified
+   * type of attribute. Checks only the given range.
+   * 
+   * @param data	the data to work on
+   * @param type	the type of attribute to locate
+   * @param fromIndex	the first index to inspect (including)
+   * @param toIndex	the last index to check (including)
+   */
+  public AttributeLocator(Instances data, int type, int fromIndex, int toIndex) {
+    super();
+
+    int[] indices = new int[toIndex - fromIndex + 1];
+    for (int i = 0; i < indices.length; i++)
+      indices[i] = fromIndex + i;
+    
+    initialize(data, type, indices);
+  }
+  
+  /**
+   * initializes the AttributeLocator with the given data for the specified
+   * type of attribute. Checks only the given attribute indices.
+   * 
+   * @param data	the data to work on
+   * @param type	the type of attribute to locate
+   * @param indices	the attribute indices to check
+   */
+  public AttributeLocator(Instances data, int type, int[] indices) {
+    super();
+
+    initialize(data, type, indices);
+  }
+  
+  /**
+   * initializes the AttributeLocator
+   * 
+   * @param data	the data to base the search for attributes on
+   * @param type	the type of attribute to look for
+   * @param indices	the indices that are allowed to check
+   */
+  protected void initialize(Instances data, int type, int[] indices) {
+    m_Data = new Instances(data, 0);
+    m_Type = type;
+    
+    m_AllowedIndices = new int[indices.length];
+    System.arraycopy(indices, 0, m_AllowedIndices, 0, indices.length);
+    
+    locate();
+
+    m_Indices        = find(true);
+    m_LocatorIndices = find(false);
+  }
+  
+  /**
+   * returns the type of attribute that is located
+   * 
+   * @return		the type of attribute
+   */
+  public int getType() {
+    return m_Type;
+  }
+  
+  /**
+   * returns the indices that are allowed to check for the attribute type
+   * 
+   * @return 		the indices that are checked for the attribute type
+   */
+  public int[] getAllowedIndices() {
+    return m_AllowedIndices;
+  }
+  
+  /**
+   * sets up the structure
+   */
+  protected void locate() {
+    int         i;
+    
+    m_Attributes = new Vector<Boolean>();
+    m_Locators   = new Vector<AttributeLocator>();
+    
+    for (i = 0; i < m_AllowedIndices.length; i++) {
+      if (m_Data.attribute(m_AllowedIndices[i]).type() == Attribute.RELATIONAL)
+	m_Locators.add(new AttributeLocator(m_Data.attribute(m_AllowedIndices[i]).relation(), getType()));
+      else
+	m_Locators.add(null);
+      
+      if (m_Data.attribute(m_AllowedIndices[i]).type() == getType())
+        m_Attributes.add(new Boolean(true));
+      else
+        m_Attributes.add(new Boolean(false));
+    }
+  }
+  
+  /**
+   * returns the underlying data
+   * 
+   * @return      the underlying Instances object
+   */
+  public Instances getData() {
+    return m_Data;
+  }
+  
+  /**
+   * returns the indices of the searched-for attributes (if TRUE) or the indices
+   * of AttributeLocator objects (if FALSE)
+   * 
+   * @param findAtts      if true the indices of attributes are located,
+   *                      otherwise the ones of AttributeLocator objects
+   * @return              the indices of the attributes or the AttributeLocator objects
+   */
+  protected int[] find(boolean findAtts) {
+    int		i;
+    int[]	result;
+    Vector<Integer>	indices;
+
+    // determine locations
+    indices = new Vector<Integer>();
+    if (findAtts) {
+      for (i = 0; i < m_Attributes.size(); i++) {
+	if (((Boolean) m_Attributes.get(i)).booleanValue())
+	  indices.add(new Integer(i));
+      }
+    }
+    else {
+      for (i = 0; i < m_Locators.size(); i++) {
+	if (m_Locators.get(i) != null)
+	  indices.add(new Integer(i));
+      }
+    }
+    
+    // fill array
+    result = new int[indices.size()];
+    for (i = 0; i < indices.size(); i++)
+      result[i] = ((Integer) indices.get(i)).intValue();
+    
+    return result;
+  }
+
+  /**
+   * returns actual index in the Instances object.
+   * 
+   * @param index	the index in the m_AllowedIndices array
+   * @return		the actual index in the instances object
+   */
+  public int getActualIndex(int index) {
+    return m_AllowedIndices[index];
+  }
+  
+  /**
+   * Returns the indices of the attributes. These indices are referring
+   * to the m_AllowedIndices array, not the actual indices in the Instances
+   * object.
+   * 
+   * @return	the indices of the attributes
+   * @see	#getActualIndex(int)
+   */
+  public int[] getAttributeIndices() {
+    return m_Indices;
+  }
+  
+  /**
+   * Returns the indices of the AttributeLocator objects.  These indices are 
+   * referring to the m_AllowedIndices array, not the actual indices in the 
+   * Instances object.
+   * 
+   * @return	the indices of the AttributeLocator objects
+   * @see	#getActualIndex(int)
+   */
+  public int[] getLocatorIndices() {
+    return m_LocatorIndices;
+  }
+  
+  /**
+   * Returns the AttributeLocator at the given index. This index refers to
+   * the index of the m_AllowedIndices array, not the actual Instances object.
+   * 
+   * @param index   the index of the locator to retrieve
+   * @return        the AttributeLocator at the given index
+   */
+  public AttributeLocator getLocator(int index) {
+    return (AttributeLocator) m_Locators.get(index);
+  }
+  
+  /**
+   * Compares this object with the specified object for order. Returns a 
+   * negative integer, zero, or a positive integer as this object is less 
+   * than, equal to, or greater than the specified object. Only type and
+   * indices are checked.
+   * 
+   * @param o		the object to compare with
+   * @return		-1 if less than, 0 if equal, +1 if greater than the 
+   * 			given object
+   */
+  public int compareTo(AttributeLocator o) {
+    int		result;
+    int		i;
+    
+    result = 0;
+    
+    // 1. check type
+    if (this.getType() < o.getType()) {
+      result = -1;
+    }
+    else if (this.getType() > o.getType()) {
+      result = 1;
+    }
+    else {
+      // 2. check indices
+      if (this.getAllowedIndices().length < o.getAllowedIndices().length) {
+	result = -1;
+      }
+      else if (this.getAllowedIndices().length > o.getAllowedIndices().length) {
+	result = 1;
+      }
+      else {
+	for (i = 0; i < this.getAllowedIndices().length; i++) {
+	  if (this.getAllowedIndices()[i] < o.getAllowedIndices()[i]) {
+	    result = -1;
+	    break;
+	  }
+	  else if (this.getAllowedIndices()[i] > o.getAllowedIndices()[i]) {
+	    result = 1;
+	    break;
+	  }
+	  else {
+	    result = 0;
+	  }
+	}
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Indicates whether some other object is "equal to" this one. Only type
+   * and indices are checked.
+   * 
+   * @param o		the AttributeLocator to check for equality
+   * @return		true if the AttributeLocators have the same type and 
+   * 			indices
+   */
+  public boolean equals(Object o) {
+    return (compareTo((AttributeLocator) o) == 0);
+  }
+  
+  /**
+   * returns a string representation of this object
+   * 
+   * @return 		a string representation
+   */
+  public String toString() {
+    return m_Attributes.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/AttributeStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/AttributeStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/AttributeStats.java	(revision 29)
@@ -0,0 +1,157 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeStats.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+
+/**
+ * A Utility class that contains summary information on an
+ * the values that appear in a dataset for a particular attribute.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 5296 $
+ */
+public class AttributeStats
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4434688832743939380L;
+  
+  /** The number of int-like values */
+  public int intCount = 0;
+  
+  /** The number of real-like values (i.e. have a fractional part) */
+  public int realCount = 0;
+  
+  /** The number of missing values */
+  public int missingCount = 0;
+  
+  /** The number of distinct values */
+  public int distinctCount = 0;
+  
+  /** The number of values that only appear once */
+  public int uniqueCount = 0;
+  
+  /** The total number of values (i.e. number of instances) */
+  public int totalCount = 0;
+  
+  /** Stats on numeric value distributions */
+  // perhaps Stats should be moved from weka.experiment to weka.core
+  public weka.experiment.Stats numericStats;
+  
+  /** Counts of each nominal value */
+  public int [] nominalCounts;
+  
+  /** Weight mass for each nominal value */
+  public double[] nominalWeights;
+    
+  /**
+   * Updates the counters for one more observed distinct value.
+   *
+   * @param value the value that has just been seen
+   * @param count the number of times the value appeared
+   * @param weight the weight mass of the value
+   */
+  protected void addDistinct(double value, int count, double weight) {
+    
+    if (count > 0) {
+      if (count == 1) {
+	uniqueCount++;
+	}
+      if (Utils.eq(value, (double)((int)value))) {
+	intCount += count;
+      } else {
+	realCount += count;
+      }
+      if (nominalCounts != null) {
+	nominalCounts[(int)value] = count;
+	nominalWeights[(int)value] = weight;
+      }
+      if (numericStats != null) {
+	  //numericStats.add(value, count);
+          numericStats.add(value, weight);
+	  numericStats.calculateDerived();
+      }
+    }
+    distinctCount++;
+  }
+
+  /**
+   * Returns a human readable representation of this AttributeStats instance.
+   *
+   * @return a String represtinging these AttributeStats.
+   */
+  public String toString() {
+
+    StringBuffer sb = new StringBuffer();
+    sb.append(Utils.padLeft("Type", 4)).append(Utils.padLeft("Nom", 5));
+    sb.append(Utils.padLeft("Int", 5)).append(Utils.padLeft("Real", 5));
+    sb.append(Utils.padLeft("Missing", 12));
+    sb.append(Utils.padLeft("Unique", 12));
+    sb.append(Utils.padLeft("Dist", 6));
+    if (nominalCounts != null) {
+      sb.append(' ');
+      for (int i = 0; i < nominalCounts.length; i++) {
+        sb.append(Utils.padLeft("C[" + i + "]", 5));
+      }
+    }
+    sb.append('\n');
+
+    long percent;
+    percent = Math.round(100.0 * intCount / totalCount);
+    if (nominalCounts != null) {
+      sb.append(Utils.padLeft("Nom", 4)).append(' ');
+      sb.append(Utils.padLeft("" + percent, 3)).append("% ");
+      sb.append(Utils.padLeft("" + 0, 3)).append("% ");
+    } else {
+      sb.append(Utils.padLeft("Num", 4)).append(' ');
+      sb.append(Utils.padLeft("" + 0, 3)).append("% ");
+      sb.append(Utils.padLeft("" + percent, 3)).append("% ");
+    }
+    percent = Math.round(100.0 * realCount / totalCount);
+    sb.append(Utils.padLeft("" + percent, 3)).append("% ");
+    sb.append(Utils.padLeft("" + missingCount, 5)).append(" /");
+    percent = Math.round(100.0 * missingCount / totalCount);
+    sb.append(Utils.padLeft("" + percent, 3)).append("% ");
+    sb.append(Utils.padLeft("" + uniqueCount, 5)).append(" /");
+    percent = Math.round(100.0 * uniqueCount / totalCount);
+    sb.append(Utils.padLeft("" + percent, 3)).append("% ");
+    sb.append(Utils.padLeft("" + distinctCount, 5)).append(' ');
+    if (nominalCounts != null) {
+      for (int i = 0; i < nominalCounts.length; i++) {
+        sb.append(Utils.padLeft("" + nominalCounts[i], 5));
+      }
+    }
+    sb.append('\n');
+    return sb.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5296 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/BinarySparseInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/BinarySparseInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/BinarySparseInstance.java	(revision 29)
@@ -0,0 +1,605 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BinarySparseInstance.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.ArrayList;
+
+/**
+ * Class for storing a binary-data-only instance as a sparse vector. A
+ * sparse instance only requires storage for those attribute values
+ * that are non-zero.  Since the objective is to reduce storage
+ * requirements for datasets with large numbers of default values,
+ * this also includes nominal attributes -- the first nominal value
+ * (i.e. that which has index 0) will not require explicit storage, so
+ * rearrange your nominal attribute value orderings if
+ * necessary. Missing values are not supported, and will be treated as 
+ * 1 (true).
+ *
+ * @version $Revision: 5987 $
+ */
+public class BinarySparseInstance
+  extends SparseInstance {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5297388762342528737L;
+
+  /**
+   * Constructor that generates a sparse instance from the given
+   * instance. Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param instance the instance from which the attribute values
+   * and the weight are to be copied
+   */
+  public BinarySparseInstance(Instance instance) {
+    
+    m_Weight = instance.weight();
+    m_Dataset = null;
+    m_NumAttributes = instance.numAttributes();
+    if (instance instanceof SparseInstance) {
+      m_AttValues = null;
+      m_Indices = ((SparseInstance)instance).m_Indices;
+    } else {
+      int[] tempIndices = new int[instance.numAttributes()];
+      int vals = 0;
+      for (int i = 0; i < instance.numAttributes(); i++) {
+	if (instance.value(i) != 0) {
+	  tempIndices[vals] = i;
+	  vals++;
+	}
+      }
+      m_AttValues = null;
+      m_Indices = new int[vals];
+      System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
+    }
+  }
+  
+  /**
+   * Constructor that copies the info from the given instance. 
+   * Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param instance the instance from which the attribute
+   * info is to be copied 
+   */
+  public BinarySparseInstance(SparseInstance instance) {
+    
+    m_AttValues = null;
+    m_Indices = instance.m_Indices;
+    m_Weight = instance.m_Weight;
+    m_NumAttributes = instance.m_NumAttributes;
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor that generates a sparse instance from the given
+   * parameters. Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param weight the instance's weight
+   * @param attValues a vector of attribute values 
+   */
+  public BinarySparseInstance(double weight, double[] attValues) {
+    
+    m_Weight = weight;
+    m_Dataset = null;
+    m_NumAttributes = attValues.length;
+    int[] tempIndices = new int[m_NumAttributes];
+    int vals = 0;
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (attValues[i] != 0) {
+	tempIndices[vals] = i;
+	vals++;
+      }
+    }
+    m_AttValues = null;
+    m_Indices = new int[vals];
+    System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
+  }
+  
+  /**
+   * Constructor that inititalizes instance variable with given
+   * values. Reference to the dataset is set to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   *
+   * @param weight the instance's weight
+   * @param indices the indices of the given values in the full vector
+   * @param maxNumValues the maximium number of values that can be stored
+   */
+  public BinarySparseInstance(double weight,
+                              int[] indices, int maxNumValues) {
+    
+    m_AttValues = null;
+    m_Indices = indices;
+    m_Weight = weight;
+    m_NumAttributes = maxNumValues;
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor of an instance that sets weight to one, all values to
+   * 1, and the reference to the dataset to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   *
+   * @param numAttributes the size of the instance 
+   */
+  public BinarySparseInstance(int numAttributes) {
+    
+    m_AttValues = null;
+    m_NumAttributes = numAttributes;
+    m_Indices = new int[numAttributes];
+    for (int i = 0; i < m_Indices.length; i++) {
+      m_Indices[i] = i;
+    }
+    m_Weight = 1;
+    m_Dataset = null;
+  }
+
+  /**
+   * Produces a shallow copy of this instance. The copy doesn't have
+   * access to a dataset.
+   *
+   * @return the shallow copy
+   */
+  public Object copy() {
+
+    return new BinarySparseInstance(this);
+  }
+
+  /**
+   * Merges this instance with the given instance and returns
+   * the result. Dataset is set to null.
+   *
+   * @param inst the instance to be merged with this one
+   * @return the merged instances
+   */
+  public Instance mergeInstance(Instance inst) {
+
+    int [] indices = new int [numValues() + inst.numValues()];
+
+    int m = 0;
+    for (int j = 0; j < numValues(); j++) {
+      indices[m++] = index(j);
+    }
+    for (int j = 0; j < inst.numValues(); j++) {
+      if (inst.valueSparse(j) != 0) {
+        indices[m++] = numAttributes() + inst.index(j);
+      }
+    }
+
+    if (m != indices.length) {
+      // Need to truncate
+      int [] newInd = new int [m];
+      System.arraycopy(indices, 0, newInd, 0, m);
+      indices = newInd;
+    }
+    return new BinarySparseInstance(1.0, indices, numAttributes() +
+                                    inst.numAttributes());
+  }
+
+  /** 
+   * Does nothing, since we don't support missing values.
+   *
+   * @param array containing the means and modes
+   */
+  public void replaceMissingValues(double[] array) {
+	 
+    // Does nothing, since we don't store missing values.
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param attIndex the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValue(int attIndex, double value) {
+
+    int index = locateIndex(attIndex);
+    
+    if ((index >= 0) && (m_Indices[index] == attIndex)) {
+      if (value == 0) {
+	int[] tempIndices = new int[m_Indices.length - 1];
+	System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+	System.arraycopy(m_Indices, index + 1, tempIndices, index, 
+			 m_Indices.length - index - 1);
+	m_Indices = tempIndices;
+      }
+    } else {
+      if (value != 0) {
+	int[] tempIndices = new int[m_Indices.length + 1];
+	System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+	tempIndices[index + 1] = attIndex;
+	System.arraycopy(m_Indices, index + 1, tempIndices, index + 2, 
+			 m_Indices.length - index - 1);
+	m_Indices = tempIndices;
+      }
+    }
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValueSparse(int indexOfIndex, double value) {
+
+    if (value == 0) {
+      int[] tempIndices = new int[m_Indices.length - 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, indexOfIndex);
+      System.arraycopy(m_Indices, indexOfIndex + 1, tempIndices, indexOfIndex, 
+		       m_Indices.length - indexOfIndex - 1);
+      m_Indices = tempIndices;
+    }
+  }
+
+  /**
+   * Returns the values of each attribute as an array of doubles.
+   *
+   * @return an array containing all the instance attribute values
+   */
+  public double[] toDoubleArray() {
+
+    double[] newValues = new double[m_NumAttributes];
+    for (int i = 0; i < m_Indices.length; i++) {
+      newValues[m_Indices[i]] = 1.0;
+    }
+    return newValues;
+  }
+
+  /**
+   * Returns the description of one instance in sparse format. 
+   * If the instance doesn't have access to a dataset, it returns the 
+   * internal floating-point values. Quotes string values that contain 
+   * whitespace characters.
+   *
+   * @return the instance's description as a string
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+    
+    text.append('{');
+    for (int i = 0; i < m_Indices.length; i++) {
+      if (i > 0) {
+        text.append(",");
+      }
+      if (m_Dataset == null) {
+        text.append(m_Indices[i] + " 1");
+      } else {
+        if (m_Dataset.attribute(m_Indices[i]).isNominal() || 
+            m_Dataset.attribute(m_Indices[i]).isString()) {
+          text.append(m_Indices[i] + " " +
+                      Utils.quote(m_Dataset.attribute(m_Indices[i]).
+                                  value(1)));
+        } else {
+          text.append(m_Indices[i] + " 1");
+        }
+      }
+    }
+    text.append('}');
+    if (m_Weight != 1.0) {
+      text.append(",{" + Utils.doubleToString(m_Weight, 6) + "}");
+    }
+    return text.toString();
+  }
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   *
+   * @param attIndex the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public double value(int attIndex) {
+
+    int index = locateIndex(attIndex);
+    if ((index >= 0) && (m_Indices[index] == attIndex)) {
+      return 1.0;
+    } else {
+      return 0.0;
+    }
+  }  
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   * Does exactly the same thing as value() if applied to an Instance.
+   *
+   * @param indexOfIndex the index of the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public final double valueSparse(int indexOfIndex) {
+
+    int index = m_Indices[indexOfIndex]; // Throws if out of bounds
+    return 1;
+  }  
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1).
+   *
+   * @param position the attribute's position
+   */
+  protected void forceDeleteAttributeAt(int position) {
+
+    int index = locateIndex(position);
+
+    m_NumAttributes--;
+    if ((index >= 0) && (m_Indices[index] == position)) {
+      int[] tempIndices = new int[m_Indices.length - 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+      for (int i = index; i < m_Indices.length - 1; i++) {
+	tempIndices[i] = m_Indices[i + 1] - 1;
+      }
+      m_Indices = tempIndices;
+    } else {
+      int[] tempIndices = new int[m_Indices.length];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+      for (int i = index + 1; i < m_Indices.length - 1; i++) {
+	tempIndices[i] = m_Indices[i] - 1;
+      }
+      m_Indices = tempIndices;
+    }
+  }
+
+  /**
+   * Inserts an attribute at the given position
+   * (0 to numAttributes()) and sets its value to 1. 
+   *
+   * @param position the attribute's position
+   */
+  protected void forceInsertAttributeAt(int position)  {
+
+    int index = locateIndex(position);
+
+    m_NumAttributes++;
+    if ((index >= 0) && (m_Indices[index] == position)) {
+      int[] tempIndices = new int[m_Indices.length + 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+      tempIndices[index] = position;
+      for (int i = index; i < m_Indices.length; i++) {
+	tempIndices[i + 1] = m_Indices[i] + 1;
+      }
+      m_Indices = tempIndices;
+    } else {
+      int[] tempIndices = new int[m_Indices.length + 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+      tempIndices[index + 1] = position;
+      for (int i = index + 1; i < m_Indices.length; i++) {
+	tempIndices[i + 1] = m_Indices[i] + 1;
+      }
+      m_Indices = tempIndices;
+    }
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param options	the command line options - ignored
+   */
+  public static void main(String[] options) {
+
+    try {
+
+      // Create numeric attributes "length" and "weight"
+      Attribute length = new Attribute("length");
+      Attribute weight = new Attribute("weight");
+      
+      // Create vector to hold nominal values "first", "second", "third" 
+      ArrayList<String> my_nominal_values = new ArrayList<String>(3); 
+      my_nominal_values.add("first"); 
+      my_nominal_values.add("second"); 
+      
+      // Create nominal attribute "position" 
+      Attribute position = new Attribute("position", my_nominal_values);
+      
+      // Create vector of the above attributes 
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>(3);
+      attributes.add(length);
+      attributes.add(weight);
+      attributes.add(position);
+      
+      // Create the empty dataset "race" with above attributes
+      Instances race = new Instances("race", attributes, 0);
+      
+      // Make position the class attribute
+      race.setClassIndex(position.index());
+      
+      // Create empty instance with three attribute values
+      BinarySparseInstance inst = new BinarySparseInstance(3);
+      
+      // Set instance's values for the attributes "length", "weight", and "position"
+      inst.setValue(length, 5.3);
+      inst.setValue(weight, 300);
+      inst.setValue(position, "first");
+      
+      // Set instance's dataset to be the dataset "race"
+      inst.setDataset(race);
+      
+      // Print the instance
+      System.out.println("The instance: " + inst);
+      
+      // Print the first attribute
+      System.out.println("First attribute: " + inst.attribute(0));
+      
+      // Print the class attribute
+      System.out.println("Class attribute: " + inst.classAttribute());
+      
+      // Print the class index
+      System.out.println("Class index: " + inst.classIndex());
+      
+      // Say if class is missing
+      System.out.println("Class is missing: " + inst.classIsMissing());
+      
+      // Print the instance's class value in internal format
+      System.out.println("Class value (internal format): " + inst.classValue());
+      
+      // Print a shallow copy of this instance
+      SparseInstance copy = (SparseInstance) inst.copy();
+      System.out.println("Shallow copy: " + copy);
+      
+      // Set dataset for shallow copy
+      copy.setDataset(inst.dataset());
+      System.out.println("Shallow copy with dataset set: " + copy);
+
+      // Print out all values in internal format
+      System.out.print("All stored values in internal format: ");
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Set all values to zero
+      System.out.print("All values set to zero: ");
+      while (inst.numValues() > 0) {
+	inst.setValueSparse(0, 0);
+      }
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Set all values to one
+      System.out.print("All values set to one: ");
+      for (int i = 0; i < inst.numAttributes(); i++) {
+	inst.setValue(i, 1);
+      }
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Unset dataset for copy, delete first attribute, and insert it again
+      copy.setDataset(null);
+      copy.deleteAttributeAt(0);
+      copy.insertAttributeAt(0);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with first attribute deleted and inserted: " + copy); 
+
+      // Same for second attribute
+      copy.setDataset(null);
+      copy.deleteAttributeAt(1);
+      copy.insertAttributeAt(1);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with second attribute deleted and inserted: " + copy); 
+
+      // Same for last attribute
+      copy.setDataset(null);
+      copy.deleteAttributeAt(2);
+      copy.insertAttributeAt(2);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with third attribute deleted and inserted: " + copy); 
+      
+      // Enumerate attributes (leaving out the class attribute)
+      System.out.println("Enumerating attributes (leaving out class):");
+      Enumeration enu = inst.enumerateAttributes();
+      while (enu.hasMoreElements()) {
+	Attribute att = (Attribute) enu.nextElement();
+	System.out.println(att);
+      }
+      
+      // Headers are equivalent?
+      System.out.println("Header of original and copy equivalent: " +
+			 inst.equalHeaders(copy));
+
+      // Test for missing values
+      System.out.println("Length of copy missing: " + copy.isMissing(length));
+      System.out.println("Weight of copy missing: " + copy.isMissing(weight.index()));
+      System.out.println("Length of copy missing: " + 
+			 Utils.isMissingValue(copy.value(length)));
+
+      // Prints number of attributes and classes
+      System.out.println("Number of attributes: " + copy.numAttributes());
+      System.out.println("Number of classes: " + copy.numClasses());
+
+      // Replace missing values
+      double[] meansAndModes = {2, 3, 0};
+      copy.replaceMissingValues(meansAndModes);
+      System.out.println("Copy with missing value replaced: " + copy);
+
+      // Setting and getting values and weights
+      copy.setClassMissing();
+      System.out.println("Copy with missing class: " + copy);
+      copy.setClassValue(0);
+      System.out.println("Copy with class value set to first value: " + copy);
+      copy.setClassValue("second");
+      System.out.println("Copy with class value set to \"second\": " + copy);
+      copy.setMissing(1);
+      System.out.println("Copy with second attribute set to be missing: " + copy);
+      copy.setMissing(length);
+      System.out.println("Copy with length set to be missing: " + copy);
+      copy.setValue(0, 0);
+      System.out.println("Copy with first attribute set to 0: " + copy);
+      copy.setValue(weight, 1);
+      System.out.println("Copy with weight attribute set to 1: " + copy);
+      copy.setValue(position, "second");
+      System.out.println("Copy with position set to \"second\": " + copy);
+      copy.setValue(2, "first");
+      System.out.println("Copy with last attribute set to \"first\": " + copy);
+      System.out.println("Current weight of instance copy: " + copy.weight());
+      copy.setWeight(2);
+      System.out.println("Current weight of instance copy (set to 2): " + copy.weight());
+      System.out.println("Last value of copy: " + copy.toString(2));
+      System.out.println("Value of position for copy: " + copy.toString(position));
+      System.out.println("Last value of copy (internal format): " + copy.value(2));
+      System.out.println("Value of position for copy (internal format): " + 
+			 copy.value(position));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Capabilities.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Capabilities.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Capabilities.java	(revision 29)
@@ -0,0 +1,1636 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Capabilities.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * A class that describes the capabilites (e.g., handling certain types of
+ * attributes, missing values, types of classes, etc.) of a specific
+ * classifier. By default, the classifier is capable of nothing. This
+ * ensures that new features have to be enabled explicitly. <p/>
+ * 
+ * A common code fragment for making use of the capabilities in a classifier 
+ * would be this:
+ * <pre>
+ * public void <b>buildClassifier</b>(Instances instances) throws Exception {
+ *   // can the classifier handle the data?
+ *   getCapabilities().<b>testWithFail(instances)</b>;
+ *   ...
+ *   // possible deletion of instances with missing class labels, etc.
+ * </pre>
+ * For only testing a single attribute, use this:
+ * <pre>
+ *   ...
+ *   Attribute att = instances.attribute(0);
+ *   getCapabilities().<b>testWithFail(att)</b>;
+ *   ...
+ * </pre>
+ * Or for testing the class attribute (uses the capabilities that are 
+ * especially for the class):
+ * <pre>
+ *   ...
+ *   Attribute att = instances.classAttribute();
+ *   getCapabilities().<b>testWithFail(att, <i>true</i>)</b>;
+ *   ...
+ * </pre>
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Capabilities 
+  implements Cloneable, Serializable, RevisionHandler {
+  
+  /** serialversion UID */
+  static final long serialVersionUID = -5478590032325567849L;  
+
+  /** the properties file for managing the tests */
+  public final static String PROPERTIES_FILE = "weka/core/Capabilities.props";
+
+  /** the actual properties */
+  protected static Properties PROPERTIES;
+  
+  /** defines an attribute type */
+  private final static int ATTRIBUTE = 1;
+  
+  /** defines a class type */
+  private final static int CLASS = 2;
+  
+  /** defines an attribute capability */
+  private final static int ATTRIBUTE_CAPABILITY = 4;
+  
+  /** defines a class capability */
+  private final static int CLASS_CAPABILITY = 8;
+  
+  /** defines a other capability */
+  private final static int OTHER_CAPABILITY = 16;
+
+  /** enumeration of all capabilities */
+  public enum Capability {
+    // attributes
+    /** can handle nominal attributes */
+    NOMINAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Nominal attributes"),
+    /** can handle binary attributes */
+    BINARY_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Binary attributes"),
+    /** can handle unary attributes */
+    UNARY_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Unary attributes"),
+    /** can handle empty nominal attributes */
+    EMPTY_NOMINAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Empty nominal attributes"),
+    /** can handle numeric attributes */
+    NUMERIC_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Numeric attributes"),
+    /** can handle date attributes */
+    DATE_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Date attributes"),
+    /** can handle string attributes */
+    STRING_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "String attributes"),
+    /** can handle relational attributes */
+    RELATIONAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Relational attributes"),
+    /** can handle missing values in attributes */
+    MISSING_VALUES(ATTRIBUTE_CAPABILITY, "Missing values"),
+    // class
+    /** can handle data without class attribute, eg clusterers */
+    NO_CLASS(CLASS_CAPABILITY, "No class"),
+    /** can handle nominal classes */
+    NOMINAL_CLASS(CLASS + CLASS_CAPABILITY, "Nominal class"),
+    /** can handle binary classes */
+    BINARY_CLASS(CLASS + CLASS_CAPABILITY, "Binary class"),
+    /** can handle unary classes */
+    UNARY_CLASS(CLASS + CLASS_CAPABILITY, "Unary class"),
+    /** can handle empty nominal classes */
+    EMPTY_NOMINAL_CLASS(CLASS + CLASS_CAPABILITY, "Empty nominal class"),
+    /** can handle numeric classes */
+    NUMERIC_CLASS(CLASS + CLASS_CAPABILITY, "Numeric class"),
+    /** can handle date classes */
+    DATE_CLASS(CLASS + CLASS_CAPABILITY, "Date class"),
+    /** can handle string classes */
+    STRING_CLASS(CLASS + CLASS_CAPABILITY, "String class"),
+    /** can handle relational classes */
+    RELATIONAL_CLASS(CLASS + CLASS_CAPABILITY, "Relational class"),
+    /** can handle missing values in class attribute */
+    MISSING_CLASS_VALUES(CLASS_CAPABILITY, "Missing class values"),
+    // other
+    /** can handle multi-instance data */
+    ONLY_MULTIINSTANCE(OTHER_CAPABILITY, "Only multi-Instance data");
+
+    /** the flags for the capabilities */
+    private int m_Flags = 0;
+    
+    /** the display string */
+    private String m_Display;
+    
+    /**
+     * initializes the capability with the given flags
+     * 
+     * @param flags	"meta-data" for the capability
+     * @param display	the display string (must be unique!)
+     */
+    private Capability(int flags, String display) {
+      m_Flags   = flags;
+      m_Display = display;
+    }
+    
+    /**
+     * returns true if the capability is an attribute
+     * 
+     * @return true if the capability is an attribute
+     */
+    public boolean isAttribute() {
+      return ((m_Flags & ATTRIBUTE) == ATTRIBUTE);
+    }
+    
+    /**
+     * returns true if the capability is a class
+     * 
+     * @return true if the capability is a class
+     */
+    public boolean isClass() {
+      return ((m_Flags & CLASS) == CLASS);
+    }
+    
+    /**
+     * returns true if the capability is an attribute capability
+     * 
+     * @return true if the capability is an attribute capability
+     */
+    public boolean isAttributeCapability() {
+      return ((m_Flags & ATTRIBUTE_CAPABILITY) == ATTRIBUTE_CAPABILITY);
+    }
+    
+    /**
+     * returns true if the capability is a class capability
+     * 
+     * @return true if the capability is a class capability
+     */
+    public boolean isOtherCapability() {
+      return ((m_Flags & OTHER_CAPABILITY) == OTHER_CAPABILITY);
+    }
+    
+    /**
+     * returns true if the capability is a other capability
+     * 
+     * @return true if the capability is a other capability
+     */
+    public boolean isClassCapability() {
+      return ((m_Flags & CLASS_CAPABILITY) == CLASS_CAPABILITY);
+    }
+    
+    /**
+     * returns the display string of the capability
+     * 
+     * @return the display string
+     */
+    public String toString() {
+      return m_Display;
+    }
+  };
+
+  /** the object that owns this capabilities instance */
+  protected CapabilitiesHandler m_Owner;
+  
+  /** the hashset for storing the active capabilities */
+  protected HashSet<Capability> m_Capabilities;
+  
+  /** the hashset for storing dependent capabilities, eg for meta-classifiers */
+  protected HashSet<Capability> m_Dependencies;
+  
+  /** the reason why the test failed, used to throw an exception */
+  protected Exception m_FailReason = null;
+
+  /** the minimum number of instances in a dataset */
+  protected int m_MinimumNumberInstances = 1;
+
+  /** whether to perform any tests at all */
+  protected boolean m_Test;
+
+  /** whether to perform data based tests */
+  protected boolean m_InstancesTest;
+
+  /** whether to perform attribute based tests */
+  protected boolean m_AttributeTest;
+
+  /** whether to test for missing values */
+  protected boolean m_MissingValuesTest;
+
+  /** whether to test for missing class values */
+  protected boolean m_MissingClassValuesTest;
+
+  /** whether to test for minimum number of instances */
+  protected boolean m_MinimumNumberInstancesTest;
+  
+  /**
+   * initializes the capabilities for the given owner
+   * 
+   * @param owner       the object that produced this Capabilities instance
+   */
+  public Capabilities(CapabilitiesHandler owner) {
+    super();
+
+    setOwner(owner);
+    m_Capabilities = new HashSet<Capability>();
+    m_Dependencies = new HashSet<Capability>();
+
+    // load properties
+    if (PROPERTIES == null) {
+      try {
+        PROPERTIES = Utils.readProperties(PROPERTIES_FILE);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	PROPERTIES = new Properties();
+      }
+    }
+    
+    m_Test                       = Boolean.parseBoolean(PROPERTIES.getProperty("Test", "true"));
+    m_InstancesTest              = Boolean.parseBoolean(PROPERTIES.getProperty("InstancesTest", "true")) && m_Test;
+    m_AttributeTest              = Boolean.parseBoolean(PROPERTIES.getProperty("AttributeTest", "true")) && m_Test;
+    m_MissingValuesTest          = Boolean.parseBoolean(PROPERTIES.getProperty("MissingValuesTest", "true")) && m_Test;
+    m_MissingClassValuesTest     = Boolean.parseBoolean(PROPERTIES.getProperty("MissingClassValuesTest", "true")) && m_Test;
+    m_MinimumNumberInstancesTest = Boolean.parseBoolean(PROPERTIES.getProperty("MinimumNumberInstancesTest", "true")) && m_Test;
+  }
+  
+  /**
+   * Creates and returns a copy of this object.
+   * 
+   * @return	a clone of this object
+   */
+  public Object clone() {
+    Capabilities    result;
+
+    result = new Capabilities(m_Owner);
+    result.assign(this);
+
+    return result;
+  }
+  
+  /**
+   * retrieves the data from the given Capabilities object
+   * 
+   * @param c	  the capabilities object to initialize with
+   */
+  public void assign(Capabilities c) {
+    for (Capability cap: Capability.values()) {
+      // capability
+      if (c.handles(cap))
+        enable(cap);
+      else
+	disable(cap);
+      // dependency
+      if (c.hasDependency(cap))
+        enableDependency(cap);
+      else
+	disableDependency(cap);
+    }
+
+    setMinimumNumberInstances(c.getMinimumNumberInstances());
+  }
+
+  /**
+   * performs an AND conjunction with the capabilities of the given 
+   * Capabilities object and updates itself
+   *
+   * @param c     the capabilities to AND with
+   */
+  public void and(Capabilities c) {
+    for (Capability cap: Capability.values()) {
+      // capability
+      if (handles(cap) && c.handles(cap))
+        m_Capabilities.add(cap);
+      else
+        m_Capabilities.remove(cap);
+      // dependency
+      if (hasDependency(cap) && c.hasDependency(cap))
+        m_Dependencies.add(cap);
+      else
+        m_Dependencies.remove(cap);
+    }
+    
+    // minimum number of instances that both handlers need at least to work
+    if (c.getMinimumNumberInstances() > getMinimumNumberInstances())
+      setMinimumNumberInstances(c.getMinimumNumberInstances());
+  }
+
+  /**
+   * performs an OR conjunction with the capabilities of the given 
+   * Capabilities object and updates itself
+   *
+   * @param c     the capabilities to OR with
+   */
+  public void or(Capabilities c) {
+    for (Capability cap: Capability.values()) {
+      // capability
+      if (handles(cap) || c.handles(cap))
+        m_Capabilities.add(cap);
+      else
+        m_Capabilities.remove(cap);
+      // dependency
+      if (hasDependency(cap) || c.hasDependency(cap))
+        m_Dependencies.add(cap);
+      else
+        m_Dependencies.remove(cap);
+    }
+    
+    if (c.getMinimumNumberInstances() < getMinimumNumberInstances())
+      setMinimumNumberInstances(c.getMinimumNumberInstances());
+  }
+  
+  /**
+   * Returns true if the currently set capabilities support at least all of
+   * the capabiliites of the given Capabilities object (checks only the enum!)
+   * 
+   * @param c	the capabilities to support at least
+   * @return	true if all the requested capabilities are supported
+   */
+  public boolean supports(Capabilities c) {
+    boolean	result;
+    
+    result = true;
+    
+    for (Capability cap: Capability.values()) {
+      if (c.handles(cap) && !handles(cap)) {
+	result = false;
+	break;
+      }
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns true if the currently set capabilities support (or have a 
+   * dependency) at least all of the capabilities of the given Capabilities 
+   * object (checks only the enum!)
+   * 
+   * @param c	the capabilities (or dependencies) to support at least
+   * @return	true if all the requested capabilities are supported (or at 
+   * 		least have a dependency)
+   */
+  public boolean supportsMaybe(Capabilities c) {
+    boolean	result;
+    
+    result = true;
+    
+    for (Capability cap: Capability.values()) {
+      if (c.handles(cap) && !(handles(cap) || hasDependency(cap))) {
+	result = false;
+	break;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * sets the owner of this capabilities object
+   * 
+   * @param value       the new owner
+   */
+  public void setOwner(CapabilitiesHandler value) {
+    m_Owner = value;
+  }
+  
+  /**
+   * returns the owner of this capabilities object
+   * 
+   * @return            the current owner of this capabilites object
+   */
+  public CapabilitiesHandler getOwner() {
+    return m_Owner;
+  }
+
+  /**
+   * sets the minimum number of instances that have to be in the dataset
+   * 
+   * @param value       the minimum number of instances
+   */
+  public void setMinimumNumberInstances(int value) {
+    if (value >= 0)
+      m_MinimumNumberInstances = value;
+  }
+  
+  /**
+   * returns the minimum number of instances that have to be in the dataset
+   * 
+   * @return            the minimum number of instances
+   */
+  public int getMinimumNumberInstances() {
+    return m_MinimumNumberInstances;
+  }
+  
+  /**
+   * Returns an Iterator over the stored capabilities
+   * 
+   * @return iterator over the current capabilities
+   */
+  public Iterator capabilities() {
+    return m_Capabilities.iterator();
+  }
+  
+  /**
+   * Returns an Iterator over the stored dependencies
+   * 
+   * @return iterator over the current dependencies
+   */
+  public Iterator dependencies() {
+    return m_Dependencies.iterator();
+  }
+  
+  /**
+   * enables the given capability. 
+   * Enabling NOMINAL_ATTRIBUTES also enables BINARY_ATTRIBUTES, 
+   * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. 
+   * Enabling BINARY_ATTRIBUTES also enables UNARY_ATTRIBUTES and 
+   * EMPTY_NOMINAL_ATTRIBUTES. 
+   * Enabling UNARY_ATTRIBUTES also enables EMPTY_NOMINAL_ATTRIBUTES.
+   * But NOMINAL_CLASS only enables BINARY_CLASS, since normal schemes in Weka
+   * don't work with datasets that have only 1 class label (or none).
+   *
+   * @param c     the capability to enable
+   */
+  public void enable(Capability c) {
+    // attributes
+    if (c == Capability.NOMINAL_ATTRIBUTES) {
+      enable(Capability.BINARY_ATTRIBUTES);
+    }
+    else if (c == Capability.BINARY_ATTRIBUTES) {
+      enable(Capability.UNARY_ATTRIBUTES);
+    }
+    else if (c == Capability.UNARY_ATTRIBUTES) {
+      enable(Capability.EMPTY_NOMINAL_ATTRIBUTES);
+    }
+    // class
+    else if (c == Capability.NOMINAL_CLASS) {
+      enable(Capability.BINARY_CLASS);
+    }
+
+    m_Capabilities.add(c);
+  }
+  
+  /**
+   * enables the dependency flag for the given capability
+   * Enabling NOMINAL_ATTRIBUTES also enables BINARY_ATTRIBUTES, 
+   * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. 
+   * Enabling BINARY_ATTRIBUTES also enables UNARY_ATTRIBUTES and 
+   * EMPTY_NOMINAL_ATTRIBUTES. 
+   * Enabling UNARY_ATTRIBUTES also enables EMPTY_NOMINAL_ATTRIBUTES.
+   * But NOMINAL_CLASS only enables BINARY_CLASS, since normal schemes in Weka
+   * don't work with datasets that have only 1 class label (or none).
+   *
+   * @param c     the capability to enable the dependency flag for
+   */
+  public void enableDependency(Capability c) {
+    // attributes
+    if (c == Capability.NOMINAL_ATTRIBUTES) {
+      enableDependency(Capability.BINARY_ATTRIBUTES);
+    }
+    else if (c == Capability.BINARY_ATTRIBUTES) {
+      enableDependency(Capability.UNARY_ATTRIBUTES);
+    }
+    else if (c == Capability.UNARY_ATTRIBUTES) {
+      enableDependency(Capability.EMPTY_NOMINAL_ATTRIBUTES);
+    }
+    // class
+    else if (c == Capability.NOMINAL_CLASS) {
+      enableDependency(Capability.BINARY_CLASS);
+    }
+
+    m_Dependencies.add(c);
+  }
+  
+  /**
+   * enables all class types
+   * 
+   * @see #disableAllClasses()
+   * @see #getClassCapabilities()
+   */
+  public void enableAllClasses() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isClass())
+	enable(cap);
+    }
+  }
+  
+  /**
+   * enables all class type dependencies
+   * 
+   * @see #disableAllClassDependencies()
+   * @see #getClassCapabilities()
+   */
+  public void enableAllClassDependencies() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isClass())
+	enableDependency(cap);
+    }
+  }
+  
+  /**
+   * enables all attribute types
+   * 
+   * @see #disableAllAttributes()
+   * @see #getAttributeCapabilities()
+   */
+  public void enableAllAttributes() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isAttribute())
+	enable(cap);
+    }
+  }
+  
+  /**
+   * enables all attribute type dependencies
+   * 
+   * @see #disableAllAttributeDependencies()
+   * @see #getAttributeCapabilities()
+   */
+  public void enableAllAttributeDependencies() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isAttribute())
+	enableDependency(cap);
+    }
+  }
+  
+  /**
+   * enables all attribute and class types (including dependencies)
+   */
+  public void enableAll() {
+    enableAllAttributes();
+    enableAllAttributeDependencies();
+    enableAllClasses();
+    enableAllClassDependencies();
+  }
+
+  /**
+   * disables the given capability
+   * Disabling NOMINAL_ATTRIBUTES also disables BINARY_ATTRIBUTES, 
+   * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. 
+   * Disabling BINARY_ATTRIBUTES also disables UNARY_ATTRIBUTES and 
+   * EMPTY_NOMINAL_ATTRIBUTES. 
+   * Disabling UNARY_ATTRIBUTES also disables EMPTY_NOMINAL_ATTRIBUTES.
+   * The same hierarchy applies to the class capabilities.
+   *
+   * @param c     the capability to disable
+   */
+  public void disable(Capability c) {
+    // attributes
+    if (c == Capability.NOMINAL_ATTRIBUTES) {
+      disable(Capability.BINARY_ATTRIBUTES);
+    }
+    else if (c == Capability.BINARY_ATTRIBUTES) {
+      disable(Capability.UNARY_ATTRIBUTES);
+    }
+    else if (c == Capability.UNARY_ATTRIBUTES) {
+      disable(Capability.EMPTY_NOMINAL_ATTRIBUTES);
+    }
+    // class
+    else if (c == Capability.NOMINAL_CLASS) {
+      disable(Capability.BINARY_CLASS);
+    }
+    else if (c == Capability.BINARY_CLASS) {
+      disable(Capability.UNARY_CLASS);
+    }
+    else if (c == Capability.UNARY_CLASS) {
+      disable(Capability.EMPTY_NOMINAL_CLASS);
+    }
+
+    m_Capabilities.remove(c);
+  }
+
+  /**
+   * disables the dependency of the given capability
+   * Disabling NOMINAL_ATTRIBUTES also disables BINARY_ATTRIBUTES, 
+   * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. 
+   * Disabling BINARY_ATTRIBUTES also disables UNARY_ATTRIBUTES and 
+   * EMPTY_NOMINAL_ATTRIBUTES. 
+   * Disabling UNARY_ATTRIBUTES also disables EMPTY_NOMINAL_ATTRIBUTES.
+   * The same hierarchy applies to the class capabilities.
+   *
+   * @param c     the capability to disable the dependency flag for
+   */
+  public void disableDependency(Capability c) {
+    // attributes
+    if (c == Capability.NOMINAL_ATTRIBUTES) {
+      disableDependency(Capability.BINARY_ATTRIBUTES);
+    }
+    else if (c == Capability.BINARY_ATTRIBUTES) {
+      disableDependency(Capability.UNARY_ATTRIBUTES);
+    }
+    else if (c == Capability.UNARY_ATTRIBUTES) {
+      disableDependency(Capability.EMPTY_NOMINAL_ATTRIBUTES);
+    }
+    // class
+    else if (c == Capability.NOMINAL_CLASS) {
+      disableDependency(Capability.BINARY_CLASS);
+    }
+    else if (c == Capability.BINARY_CLASS) {
+      disableDependency(Capability.UNARY_CLASS);
+    }
+    else if (c == Capability.UNARY_CLASS) {
+      disableDependency(Capability.EMPTY_NOMINAL_CLASS);
+    }
+
+    m_Dependencies.remove(c);
+  }
+  
+  /**
+   * disables all class types
+   * 
+   * @see #enableAllClasses()
+   * @see #getClassCapabilities()
+   */
+  public void disableAllClasses() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isClass())
+	disable(cap);
+    }
+  }
+  
+  /**
+   * disables all class type dependencies
+   * 
+   * @see #enableAllClassDependencies()
+   * @see #getClassCapabilities()
+   */
+  public void disableAllClassDependencies() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isClass())
+	disableDependency(cap);
+    }
+  }
+  
+  /**
+   * disables all attribute types
+   * 
+   * @see #enableAllAttributes()
+   * @see #getAttributeCapabilities()
+   */
+  public void disableAllAttributes() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isAttribute())
+	disable(cap);
+    }
+  }
+  
+  /**
+   * disables all attribute type dependencies
+   * 
+   * @see #enableAllAttributeDependencies()
+   * @see #getAttributeCapabilities()
+   */
+  public void disableAllAttributeDependencies() {
+    for (Capability cap: Capability.values()) {
+      if (cap.isAttribute())
+	disableDependency(cap);
+    }
+  }
+  
+  /**
+   * disables all attribute and class types (including dependencies)
+   */
+  public void disableAll() {
+    disableAllAttributes();
+    disableAllAttributeDependencies();
+    disableAllClasses();
+    disableAllClassDependencies();
+  }
+  
+  /**
+   * returns all class capabilities
+   * 
+   * @return		all capabilities regarding the class
+   * @see #enableAllClasses()
+   * @see #disableAllClasses()
+   */
+  public Capabilities getClassCapabilities() {
+    Capabilities	result;
+    
+    result = new Capabilities(getOwner());
+    
+    for (Capability cap: Capability.values()) {
+      if (cap.isClassCapability()) {
+	if (handles(cap))
+	  result.m_Capabilities.add(cap);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns all attribute capabilities
+   * 
+   * @return		all capabilities regarding attributes
+   * @see #enableAllAttributes()
+   * @see #disableAllAttributes()
+   */
+  public Capabilities getAttributeCapabilities() {
+    Capabilities	result;
+    
+    result = new Capabilities(getOwner());
+    
+    for (Capability cap: Capability.values()) {
+      if (cap.isAttributeCapability()) {
+	if (handles(cap))
+	  result.m_Capabilities.add(cap);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns all other capabilities, besides class and attribute related ones
+   * 
+   * @return		all other capabilities, besides class and attribute 
+   * 			related ones
+   */
+  public Capabilities getOtherCapabilities() {
+    Capabilities	result;
+    
+    result = new Capabilities(getOwner());
+    
+    for (Capability cap: Capability.values()) {
+      if (cap.isOtherCapability()) {
+	if (handles(cap))
+	  result.m_Capabilities.add(cap);
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns true if the classifier handler has the specified capability
+   *
+   * @param c     the capability to test
+   * @return      true if the classifier handler has the capability
+   */
+  public boolean handles(Capability c) {
+    return m_Capabilities.contains(c);
+  }
+
+  /**
+   * returns true if the classifier handler has a dependency for the specified 
+   * capability
+   *
+   * @param c     the capability to test
+   * @return      true if the classifier handler has a dependency for the 
+   *               capability
+   */
+  public boolean hasDependency(Capability c) {
+    return m_Dependencies.contains(c);
+  }
+  
+  /**
+   * Checks whether there are any dependencies at all
+   * 
+   * @return true if there is at least one dependency for a capability
+   */
+  public boolean hasDependencies() {
+    return (m_Dependencies.size() > 0);
+  }
+
+  /**
+   * returns the reason why the tests failed, is null if tests succeeded
+   * 
+   * @return		the reason why the tests failed
+   */
+  public Exception getFailReason() {
+    return m_FailReason;
+  }
+  
+  /**
+   * Generates the message for, e.g., an exception. Adds the classname before the
+   * actual message and returns that string.
+   * 
+   * @param msg		the actual content of the message, e.g., exception
+   * @return		the new message
+   */
+  protected String createMessage(String msg) {
+    String	result;
+    
+    result = "";
+    
+    if (getOwner() != null)
+      result = getOwner().getClass().getName();
+    else
+      result = "<anonymous>";
+      
+    result += ": " + msg;
+    
+    return result;
+  }
+  
+  /**
+   * Test the given attribute, whether it can be processed by the handler,
+   * given its capabilities. The method assumes that the specified attribute
+   * is not the class attribute.
+   * 
+   * @param att		the attribute to test
+   * @return		true if all the tests succeeded
+   * @see		#test(Attribute, boolean)
+   */
+  public boolean test(Attribute att) {
+    return test(att, false);
+  }
+  
+  /**
+   * Test the given attribute, whether it can be processed by the handler,
+   * given its capabilities.
+   * 
+   * @param att		the attribute to test
+   * @param isClass	whether this attribute is the class attribute
+   * @return		true if all the tests succeeded
+   * @see		#m_AttributeTest
+   */
+  public boolean test(Attribute att, boolean isClass) {
+    boolean		result;
+    Capability		cap;
+    Capability		capBinary;
+    Capability		capUnary;
+    Capability		capEmpty;
+    String		errorStr;
+    
+    result = true;
+    
+    // shall we test the data?
+    if (!m_AttributeTest)
+      return result;
+
+    // for exception
+    if (isClass)
+      errorStr  = "class";
+    else
+      errorStr  = "attributes";
+    
+    switch (att.type()) {
+      case Attribute.NOMINAL:
+	if (isClass) {
+	  cap       = Capability.NOMINAL_CLASS;
+	  capBinary = Capability.BINARY_CLASS;
+	  capUnary  = Capability.UNARY_CLASS;
+	  capEmpty  = Capability.EMPTY_NOMINAL_CLASS;
+	}
+	else {
+	  cap       = Capability.NOMINAL_ATTRIBUTES;
+	  capBinary = Capability.BINARY_ATTRIBUTES;
+	  capUnary  = Capability.UNARY_ATTRIBUTES;
+	  capEmpty  = Capability.EMPTY_NOMINAL_ATTRIBUTES;
+	}
+	
+        if (handles(cap) && (att.numValues() > 2))
+          break;
+        else if (handles(capBinary) && (att.numValues() == 2))
+          break;
+        else if (handles(capUnary) && (att.numValues() == 1))
+          break;
+        else if (handles(capEmpty) && (att.numValues() == 0))
+          break;
+
+        if (att.numValues() == 0) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+              createMessage("Cannot handle empty nominal " + errorStr + "!"));
+          result = false;
+        }
+        if (att.numValues() == 1) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+              createMessage("Cannot handle unary " + errorStr + "!"));
+          result = false;
+        }
+        else if (att.numValues() == 2) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+              createMessage("Cannot handle binary " + errorStr + "!"));
+          result = false;
+        }
+        else {
+          m_FailReason = new UnsupportedAttributeTypeException(
+              createMessage("Cannot handle multi-valued nominal " + errorStr + "!"));
+          result = false;
+        }
+        break;
+
+      case Attribute.NUMERIC:
+	if (isClass)
+	  cap = Capability.NUMERIC_CLASS;
+	else
+	  cap = Capability.NUMERIC_ATTRIBUTES;
+	
+        if (!handles(cap)) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+                              createMessage("Cannot handle numeric " + errorStr + "!"));
+          result = false;
+        }
+        break;
+
+      case Attribute.DATE:
+	if (isClass)
+	  cap = Capability.DATE_CLASS;
+	else
+	  cap = Capability.DATE_ATTRIBUTES;
+	
+        if (!handles(cap)) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+                              createMessage("Cannot handle date " + errorStr + "!"));
+          result = false;
+        }
+        break;
+
+      case Attribute.STRING:
+	if (isClass)
+	  cap = Capability.STRING_CLASS;
+	else
+	  cap = Capability.STRING_ATTRIBUTES;
+	
+        if (!handles(cap)) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+                              createMessage("Cannot handle string " + errorStr + "!"));
+          result = false;
+        }
+        break;
+
+      case Attribute.RELATIONAL:
+	if (isClass)
+	  cap = Capability.RELATIONAL_CLASS;
+	else
+	  cap = Capability.RELATIONAL_ATTRIBUTES;
+	
+        if (!handles(cap)) {
+          m_FailReason = new UnsupportedAttributeTypeException(
+                              createMessage("Cannot handle relational " + errorStr + "!"));
+          result = false;
+        }
+        // attributes in the relation of this attribute must be tested
+        // separately with a different Capabilites object
+        break;
+
+      default:
+        m_FailReason = new UnsupportedAttributeTypeException(
+                            createMessage("Cannot handle unknown attribute type '" 
+                                        + att.type() + "'!"));
+        result = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Tests the given data, whether it can be processed by the handler,
+   * given its capabilities. Classifiers implementing the 
+   * <code>MultiInstanceCapabilitiesHandler</code> interface are checked 
+   * automatically for their multi-instance Capabilities (if no bags, then
+   * only the bag-structure, otherwise only the first bag).
+   *
+   * @param data 	the data to test
+   * @return		true if all the tests succeeded
+   * @see 		#test(Instances, int, int)
+   */
+  public boolean test(Instances data) {
+    return test(data, 0, data.numAttributes() - 1);
+  }
+  
+  /**
+   * Tests a certain range of attributes of the given data, whether it can be 
+   * processed by the handler, given its capabilities. Classifiers 
+   * implementing the <code>MultiInstanceCapabilitiesHandler</code> interface 
+   * are checked automatically for their multi-instance Capabilities (if no 
+   * bags, then only the bag-structure, otherwise only the first bag).
+   *
+   * @param data 	the data to test
+   * @param fromIndex	the range of attributes - start (incl.)
+   * @param toIndex	the range of attributes - end (incl.)
+   * @return		true if all the tests succeeded
+   * @see 		MultiInstanceCapabilitiesHandler
+   * @see 		#m_InstancesTest
+   * @see		#m_MissingValuesTest
+   * @see		#m_MissingClassValuesTest
+   * @see		#m_MinimumNumberInstancesTest
+   */
+  public boolean test(Instances data, int fromIndex, int toIndex) {
+    int         	i;
+    int         	n;
+    int			m;
+    Attribute   	att;
+    Instance    	inst;
+    boolean		testClass;
+    Capabilities	cap;
+    boolean		missing;
+    Iterator		iter;
+    
+    // shall we test the data?
+    if (!m_InstancesTest)
+      return true;
+    
+    // no Capabilities? -> warning
+    if (    (m_Capabilities.size() == 0) 
+	 || ((m_Capabilities.size() == 1) && handles(Capability.NO_CLASS)) )
+      System.err.println(createMessage("No capabilities set!"));
+    
+    // any attributes?
+    if (toIndex - fromIndex < 0) {
+      m_FailReason = new WekaException(
+                          createMessage("No attributes!"));
+      return false;
+    }
+
+    // do wee need to test the class attribute, i.e., is the class attribute
+    // within the range of attributes?
+    testClass =    (data.classIndex() > -1) 
+    		&& (data.classIndex() >= fromIndex)
+    		&& (data.classIndex() <= toIndex);
+    
+    // attributes
+    for (i = fromIndex; i <= toIndex; i++) {
+      att = data.attribute(i);
+      
+      // class is handled separately
+      if (i == data.classIndex())
+        continue;
+      
+      // check attribute types
+      if (!test(att))
+	return false;
+    }
+
+    // class
+    if (!handles(Capability.NO_CLASS) && (data.classIndex() == -1)) {
+      m_FailReason = new UnassignedClassException(
+	  createMessage("Class attribute not set!"));
+      return false;
+    }
+      
+    // special case: no class attribute can be handled
+    if (handles(Capability.NO_CLASS) && (data.classIndex() > -1)) {
+      cap  = getClassCapabilities();
+      cap.disable(Capability.NO_CLASS);
+      iter = cap.capabilities();
+      if (!iter.hasNext()) {
+	m_FailReason = new WekaException(
+	    createMessage("Cannot handle any class attribute!"));
+	return false;
+      }
+    }
+      
+    if (testClass && !handles(Capability.NO_CLASS)) {
+      att = data.classAttribute();
+      if (!test(att, true))
+	return false;
+
+      // special handling of RELATIONAL class
+      // TODO: store additional Capabilities for this case
+      
+      // missing class labels
+      if (m_MissingClassValuesTest) {
+	if (!handles(Capability.MISSING_CLASS_VALUES)) {
+	  for (i = 0; i < data.numInstances(); i++) {
+	    if (data.instance(i).classIsMissing()) {
+	      m_FailReason = new WekaException(
+		  createMessage("Cannot handle missing class values!"));
+	      return false;
+	    }
+	  }
+	}
+	else {
+	  if (m_MinimumNumberInstancesTest) {
+	    int hasClass = 0;
+	    
+	    for (i = 0; i < data.numInstances(); i++) {
+	      if (!data.instance(i).classIsMissing())
+		hasClass++;
+	    }
+	    
+	    // not enough instances with class labels?
+	    if (hasClass < getMinimumNumberInstances()) {
+	      m_FailReason = new WekaException(
+		  createMessage("Not enough training instances with class labels (required: " 
+		      + getMinimumNumberInstances() 
+		      + ", provided: " 
+		      + hasClass + ")!"));
+	      return false;
+	    }
+	  }
+	}
+      }
+    }
+
+    // missing values
+    if (m_MissingValuesTest) {
+      if (!handles(Capability.MISSING_VALUES)) {
+	missing = false;
+	for (i = 0; i < data.numInstances(); i++) {
+	  inst = data.instance(i);
+	  
+	  if (inst instanceof SparseInstance) {
+	    for (m = 0; m < inst.numValues(); m++) {
+	      n = inst.index(m);
+	      
+	      // out of scope?
+	      if (n < fromIndex)
+		continue;
+	      if (n > toIndex)
+		break;
+
+	      // skip class
+	      if (n == inst.classIndex())
+		continue;
+	      
+
+	      if (inst.isMissing(n)) {
+		missing = true;
+		break;
+	      }
+	    }
+	  }
+	  else {
+	    for (n = fromIndex; n <= toIndex; n++) {
+	      // skip class
+	      if (n == inst.classIndex())
+		continue;
+
+	      if (inst.isMissing(n)) {
+		missing = true;
+		break;
+	      }
+	    }
+	  }
+	  
+	  if (missing) {
+	    m_FailReason = new NoSupportForMissingValuesException(
+		createMessage("Cannot handle missing values!"));
+	    return false;
+	  }
+	}
+      }
+    }
+    
+    // instances
+    if (m_MinimumNumberInstancesTest) {
+      if (data.numInstances() < getMinimumNumberInstances()) {
+	m_FailReason = new WekaException(
+	    createMessage("Not enough training instances (required: " 
+		+ getMinimumNumberInstances() 
+		+ ", provided: " 
+		+ data.numInstances() + ")!"));
+	return false;
+      }
+    }
+
+    // Multi-Instance? -> check structure (regardless of attribute range!)
+    if (handles(Capability.ONLY_MULTIINSTANCE)) {
+      // number of attributes?
+      if (data.numAttributes() != 3) {
+        m_FailReason = new WekaException(
+                            createMessage("Incorrect Multi-Instance format, must be 'bag-id, bag, class'!"));
+        return false;
+      }
+      
+      // type of attributes and position of class?
+      if (    !data.attribute(0).isNominal() 
+           || !data.attribute(1).isRelationValued() 
+           || (data.classIndex() != data.numAttributes() - 1) ) {
+        m_FailReason = new WekaException(
+            createMessage("Incorrect Multi-Instance format, must be 'NOMINAL att, RELATIONAL att, CLASS att'!"));
+        return false;
+      }
+
+      // check data immediately
+      if (getOwner() instanceof MultiInstanceCapabilitiesHandler) {
+	MultiInstanceCapabilitiesHandler handler = (MultiInstanceCapabilitiesHandler) getOwner();
+	cap = handler.getMultiInstanceCapabilities();
+	boolean result;
+	if (data.numInstances() > 0)
+	  result = cap.test(data.attribute(1).relation(0));
+	else
+	  result = cap.test(data.attribute(1).relation());
+	
+	if (!result) {
+	  m_FailReason = cap.m_FailReason;
+	  return false;
+	}
+      }
+    }
+    
+    // passed all tests!
+    return true;
+  }
+
+  /**
+   * tests the given attribute by calling the test(Attribute,boolean) method 
+   * and throws an exception if the test fails. The method assumes that the
+   * specified attribute is not the class attribute.
+   *
+   * @param att        	the attribute to test
+   * @throws Exception  in case the attribute doesn't pass the tests
+   * @see 		#test(Attribute,boolean)
+   */
+  public void testWithFail(Attribute att) throws Exception {
+    test(att, false);
+  }
+
+  /**
+   * tests the given attribute by calling the test(Attribute,boolean) method 
+   * and throws an exception if the test fails.
+   *
+   * @param att        	the attribute to test
+   * @param isClass	whether this attribute is the class attribute
+   * @throws Exception  in case the attribute doesn't pass the tests
+   * @see 		#test(Attribute,boolean)
+   */
+  public void testWithFail(Attribute att, boolean isClass) throws Exception {
+    if (!test(att, isClass))
+      throw m_FailReason;
+  }
+
+  /**
+   * tests the given data by calling the test(Instances,int,int) method and 
+   * throws an exception if the test fails.
+   *
+   * @param data        the data to test
+   * @param fromIndex	the range of attributes - start (incl.)
+   * @param toIndex	the range of attributes - end (incl.)
+   * @throws Exception  in case the data doesn't pass the tests
+   * @see 		#test(Instances,int,int)
+   */
+  public void testWithFail(Instances data, int fromIndex, int toIndex) throws Exception {
+    if (!test(data, fromIndex, toIndex))
+      throw m_FailReason;
+  }
+
+  /**
+   * tests the given data by calling the test(Instances) method and throws 
+   * an exception if the test fails.
+   *
+   * @param data        the data to test
+   * @throws Exception  in case the data doesn't pass the tests
+   * @see 		#test(Instances)
+   */
+  public void testWithFail(Instances data) throws Exception {
+    if (!test(data))
+      throw m_FailReason;
+  }
+  
+  /**
+   * returns a string representation of the capabilities
+   * 
+   * @return 	a string representation of this object
+   */
+  public String toString() {
+    Vector<Capability>		sorted;
+    StringBuffer	result;
+    
+    result = new StringBuffer();
+
+    // capabilities
+    sorted = new Vector<Capability>(m_Capabilities);
+    Collections.sort(sorted);
+    result.append("Capabilities: " + sorted.toString() + "\n");
+
+    // dependencies
+    sorted = new Vector<Capability>(m_Dependencies);
+    Collections.sort(sorted);
+    result.append("Dependencies: " + sorted.toString() + "\n");
+    
+    // other stuff
+    result.append("min # Instance: " + getMinimumNumberInstances() + "\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * turns the capabilities object into source code. The returned source code
+   * is a block that creates a Capabilities object named 'objectname' and
+   * enables all the capabilities of this Capabilities object.
+   * 
+   * @param objectname	the name of the Capabilities object being instantiated
+   * @return		the generated source code
+   */
+  public String toSource(String objectname) {
+    return toSource(objectname, 0);
+  }
+    
+  /**
+   * turns the capabilities object into source code. The returned source code
+   * is a block that creates a Capabilities object named 'objectname' and
+   * enables all the capabilities of this Capabilities object.
+   * 
+   * @param objectname	the name of the Capabilities object being instantiated
+   * @param indent	the number of blanks to indent
+   * @return		the generated source code
+   */
+  public String toSource(String objectname, int indent) {
+    StringBuffer	result;
+    String		capsName;
+    String		capName;
+    String		indentStr;
+    int			i;
+    
+    result = new StringBuffer();
+
+    capsName = Capabilities.class.getName();
+    capName  = Capabilities.Capability.class.getName().replaceAll("\\$", ".");
+    
+    indentStr = "";
+    for (i = 0; i < indent; i++)
+      indentStr += " ";
+    
+    // object name
+    result.append(indentStr + capsName + " " + objectname + " = new " + capsName + "(this);\n");
+    
+    // capabilities
+    result.append("\n");
+    for (Capability cap: Capability.values()) {
+      // capability
+      if (handles(cap))
+        result.append(
+            indentStr + objectname + ".enable(" + capName + "." + cap.name() + ");\n");
+      // dependency
+      if (hasDependency(cap))
+        result.append(
+            indentStr + objectname + ".enableDependency(" + capName + "." + cap.name() + ");\n");
+    }
+
+    // other
+    result.append("\n");
+    result.append(
+	indentStr + objectname + ".setMinimumNumberInstances(" 
+	+ getMinimumNumberInstances() + ");\n");
+
+    result.append("\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * returns a Capabilities object specific for this data. The multi-instance
+   * capability is not checked as well as the minimum number of instances
+   * is not set.
+   * 
+   * @param data	the data to base the capabilities on
+   * @return		a data-specific capabilities object
+   * @throws Exception	in case an error occurrs, e.g., an unknown attribute 
+   * 			type
+   */
+  public static Capabilities forInstances(Instances data) throws Exception {
+    return forInstances(data, false);
+  }
+  
+  /**
+   * returns a Capabilities object specific for this data. The minimum number 
+   * of instances is not set, the check for multi-instance data is optional.
+   * 
+   * @param data	the data to base the capabilities on
+   * @param multi	if true then the structure is checked, too
+   * @return		a data-specific capabilities object
+   * @throws Exception	in case an error occurrs, e.g., an unknown attribute 
+   * 			type
+   */
+  public static Capabilities forInstances(Instances data, boolean multi) throws Exception {
+    Capabilities	result;
+    Capabilities	multiInstance;
+    int			i;
+    int			n;
+    int			m;
+    Instance		inst;
+    boolean		missing;
+    
+    result = new Capabilities(null);
+    
+    // class
+    if (data.classIndex() == -1) {
+      result.enable(Capability.NO_CLASS);
+    }
+    else {
+      switch (data.classAttribute().type()) {
+	case Attribute.NOMINAL:
+	  if (data.classAttribute().numValues() == 1)
+	    result.enable(Capability.UNARY_CLASS);
+	  else if (data.classAttribute().numValues() == 2)
+	    result.enable(Capability.BINARY_CLASS);
+	  else
+	    result.enable(Capability.NOMINAL_CLASS);
+	  break;
+	  
+	case Attribute.NUMERIC:
+	  result.enable(Capability.NUMERIC_CLASS);
+	  break;
+	  
+	case Attribute.STRING:
+	  result.enable(Capability.STRING_CLASS);
+	  break;
+	  
+	case Attribute.DATE:
+	  result.enable(Capability.DATE_CLASS);
+	  break;
+	  
+	case Attribute.RELATIONAL:
+	  result.enable(Capability.RELATIONAL_CLASS);
+	  break;
+	  
+	default:
+	  throw new UnsupportedAttributeTypeException(
+	      "Unknown class attribute type '" + data.classAttribute() + "'!");
+      }
+      
+      // missing class values
+      for (i = 0; i < data.numInstances(); i++) {
+	if (data.instance(i).classIsMissing()) {
+	  result.enable(Capability.MISSING_CLASS_VALUES);
+	  break;
+	}
+      }
+    }
+    
+    // attributes
+    for (i = 0; i < data.numAttributes(); i++) {
+      // skip class
+      if (i == data.classIndex())
+	continue;
+
+      switch (data.attribute(i).type()) {
+	case Attribute.NOMINAL:
+	  result.enable(Capability.UNARY_ATTRIBUTES);
+	  if (data.attribute(i).numValues() == 2)
+	    result.enable(Capability.BINARY_ATTRIBUTES);
+	  else if (data.attribute(i).numValues() > 2)
+	    result.enable(Capability.NOMINAL_ATTRIBUTES);
+	  break;
+
+	case Attribute.NUMERIC:
+	  result.enable(Capability.NUMERIC_ATTRIBUTES);
+	  break;
+		
+	case Attribute.DATE:
+	  result.enable(Capability.DATE_ATTRIBUTES);
+	  break;
+
+	case Attribute.STRING:
+	  result.enable(Capability.STRING_ATTRIBUTES);
+	  break;
+	  
+	case Attribute.RELATIONAL:
+	  result.enable(Capability.RELATIONAL_ATTRIBUTES);
+	  break;
+	  
+	default:
+	  throw new UnsupportedAttributeTypeException(
+	      "Unknown attribute type '" + data.attribute(i).type() + "'!");
+      }
+    }
+    
+    // missing values
+    missing = false;
+    for (i = 0; i < data.numInstances(); i++) {
+      inst = data.instance(i);
+
+      if (inst instanceof SparseInstance) {
+	for (m = 0; m < inst.numValues(); m++) {
+	  n = inst.index(m);
+
+	  // skip class
+	  if (n == inst.classIndex())
+	    continue;
+
+	  if (inst.isMissing(n)) {
+	    missing = true;
+	    break;
+	  }
+	}
+      }
+      else {
+	for (n = 0; n < data.numAttributes(); n++) {
+	  // skip class
+	  if (n == inst.classIndex())
+	    continue;
+
+	  if (inst.isMissing(n)) {
+	    missing = true;
+	    break;
+	  }
+	}
+      }
+
+      if (missing) {
+	result.enable(Capability.MISSING_VALUES);
+	break;
+      }
+    }
+
+    // multi-instance data?
+    if (multi) {
+      if (    (data.numAttributes() == 3)
+	   && (data.attribute(0).isNominal())		// bag-id
+	   && (data.attribute(1).isRelationValued()) 	// bag
+	   && (data.classIndex() == data.numAttributes() - 1) ) {
+	multiInstance = new Capabilities(null);
+	multiInstance.or(result.getClassCapabilities());
+	multiInstance.enable(Capability.NOMINAL_ATTRIBUTES);
+	multiInstance.enable(Capability.RELATIONAL_ATTRIBUTES);
+	multiInstance.enable(Capability.ONLY_MULTIINSTANCE);
+	result.assign(multiInstance);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * loads the given dataset and prints the Capabilities necessary to 
+   * process it. <p/>
+   * 
+   * Valid parameters: <p/>
+   * 
+   * -file filename <br/>
+   *  the file to load
+   *  
+   * -c index
+   *  the explicit index of the class attribute (default: none)
+   * 
+   * @param args	the commandline arguments
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    String 		tmpStr;
+    String		filename;
+    DataSource 		source;
+    Instances 		data;
+    int 		classIndex;
+    Capabilities 	cap;
+    Iterator		iter;
+
+    if (args.length == 0) {
+      System.out.println(
+	  "\nUsage: " + Capabilities.class.getName() 
+	  + " -file <dataset> [-c <class index>]\n");
+      return;
+    }
+    
+    // get parameters
+    tmpStr = Utils.getOption("file", args);
+    if (tmpStr.length() == 0)
+      throw new Exception("No file provided with option '-file'!");
+    else
+      filename = tmpStr;
+
+    tmpStr = Utils.getOption("c", args);
+    if (tmpStr.length() != 0) {
+      if (tmpStr.equals("first"))
+	classIndex = 0;
+      else if (tmpStr.equals("last"))
+	classIndex = -2;  // last
+      else
+	classIndex = Integer.parseInt(tmpStr) - 1;
+    }
+    else {
+      classIndex = -3;  // not set
+    }
+    
+    // load data
+    source = new DataSource(filename);
+    if (classIndex == -3)
+      data = source.getDataSet();
+    else if (classIndex == -2)
+      data = source.getDataSet(source.getStructure().numAttributes() - 1);
+    else
+      data = source.getDataSet(classIndex);
+
+    // determine and print capabilities
+    cap = forInstances(data);
+    System.out.println("File: " + filename);
+    System.out.println("Class index: " + ((data.classIndex() == -1) ? "not set" : "" + (data.classIndex() + 1)));
+    System.out.println("Capabilities:");
+    iter = cap.capabilities();
+    while (iter.hasNext())
+      System.out.println("- " + iter.next());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Capabilities.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Capabilities.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Capabilities.props	(revision 29)
@@ -0,0 +1,28 @@
+# The processing of Capabilities is managed with the properties stored
+# in this file.
+#
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5953 $
+
+# are Capabilities tests enabled at all?
+Test=true
+
+# enable tests that are based on the data 
+# see weka.core.Capabilities.test(Instances,int,int)
+InstancesTest=true
+
+# enable tests that work only on the type of attribute, not the data
+# see weka.core.Capabilities.test(Attribute,boolean)
+AttributeTest=true
+
+# test for missing values
+# see weka.core.Capabilities.test(Instances,int,int)
+MissingValuesTest=true
+
+# test for missing class values
+# see weka.core.Capabilities.test(Instances,int,int)
+MissingClassValuesTest=true
+
+# test for minimum number of instances
+# see weka.core.Capabilities.test(Instances,int,int)
+MinimumNumberInstancesTest=true
Index: branches/MetisMQI/src/main/java/weka/core/CapabilitiesHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/CapabilitiesHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/CapabilitiesHandler.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CapabilitiesHandler.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+/**
+ * Classes implementing this interface return their capabilities in regards
+ * to datasets.
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see     Capabilities
+ */
+public interface CapabilitiesHandler {
+
+  /**
+   * Returns the capabilities of this object.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities();
+}
Index: branches/MetisMQI/src/main/java/weka/core/ChebyshevDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ChebyshevDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ChebyshevDistance.java	(revision 29)
@@ -0,0 +1,156 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ChebyshevDistance.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements the Chebyshev distance. The distance between two vectors is the greatest of their differences along any coordinate dimension.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Wikipedia. Chebyshev distance. URL http://en.wikipedia.org/wiki/Chebyshev_distance.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{missing_id,
+ *    author = {Wikipedia},
+ *    title = {Chebyshev distance},
+ *    URL = {http://en.wikipedia.org/wiki/Chebyshev_distance}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns off the normalization of attribute 
+ *  values in distance calculation.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to used in the calculation of the 
+ *  distance. 'first' and 'last' are valid indices.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indices.</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class ChebyshevDistance
+  extends NormalizableDistance
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -7739904999895461429L;
+
+  /**
+   * Constructs an Chebyshev Distance object, Instances must be still set.
+   */
+  public ChebyshevDistance() {
+    super();
+  }
+
+  /**
+   * Constructs an Chebyshev Distance object and automatically initializes the
+   * ranges.
+   * 
+   * @param data 	the instances the distance function should work on
+   */
+  public ChebyshevDistance(Instances data) {
+    super(data);
+  }
+
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implements the Chebyshev distance. The distance between two vectors "
+      + "is the greatest of their differences along any coordinate dimension.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Wikipedia");
+    result.setValue(Field.TITLE, "Chebyshev distance");
+    result.setValue(Field.URL, "http://en.wikipedia.org/wiki/Chebyshev_distance");
+
+    return result;
+  }
+  
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected double updateDistance(double currDist, double diff) {
+    double	result;
+    
+    result = currDist;
+    
+    diff = Math.abs(diff);
+    if (diff > result)
+      result = diff;
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Check.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Check.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Check.java	(revision 29)
@@ -0,0 +1,241 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckScheme.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract general class for testing in Weka.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class Check
+  implements OptionHandler, RevisionHandler {
+  
+  /** Debugging mode, gives extra output if true */
+  protected boolean m_Debug = false;
+  
+  /** Silent mode, for no output at all to stdout */
+  protected boolean m_Silent = false;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tTurn on debugging output.",
+        "D", 0, "-D"));
+    
+    result.addElement(new Option(
+        "\tSilent mode - prints nothing to stdout.",
+        "S", 0, "-S"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag('D', options));
+
+    setSilent(Utils.getFlag('S', options));
+  }
+  
+  /**
+   * Gets the current settings of the CheckClassifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    
+    result = new Vector<String>();
+    
+    if (getDebug())
+      result.add("-D");
+    
+    if (getSilent())
+      result.add("-S");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Tries to instantiate a new instance of the given class and checks whether
+   * it is an instance of the specified class. For convenience one can also
+   * specify a classname prefix (e.g., "weka.classifiers") to avoid long 
+   * classnames and then instantiate it with the shortened classname (e.g.,
+   * "trees.J48").
+   * 
+   * @param prefix	the classname prefix (without trailing dot)
+   * @param cls		the class to check whether the generated object is an
+   * 			instance of
+   * @param classname	the classname to instantiate
+   * @param options	optional options for the object
+   * @return		the configured object
+   * @throws Exception	if instantiation fails
+   */
+  protected Object forName(String prefix, Class cls, String classname, 
+      String[] options) throws Exception {
+
+    Object	result;
+    
+    result = null;
+
+    try {
+      result = Utils.forName(cls, classname, options);
+    }
+    catch (Exception e) {
+      // shall we try with prefix?
+      if (e.getMessage().toLowerCase().indexOf("can't find") > -1) {
+	try {
+	  result = Utils.forName(cls, prefix + "." + classname, options);
+	}
+	catch (Exception ex) {
+	  if (e.getMessage().toLowerCase().indexOf("can't find") > -1) {
+	    throw new Exception(
+		"Can't find class called '" + classname 
+		+ "' or '" + prefix + "." + classname + "'!");
+	  }
+	  else {
+	    throw new Exception(ex);
+	  }
+	}
+      }
+      else {
+	throw new Exception(e);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public abstract void doTests();
+  
+  /**
+   * Set debugging mode
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+    // disable silent mode, if necessary
+    if (getDebug())
+      setSilent(false);
+  }
+  
+  /**
+   * Get whether debugging is turned on
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Set slient mode, i.e., no output at all to stdout
+   *
+   * @param value whether silent mode is active or not
+   */
+  public void setSilent(boolean value) {
+    m_Silent = value;
+  }
+  
+  /**
+   * Get whether silent mode is turned on
+   *
+   * @return true if silent mode is on
+   */
+  public boolean getSilent() {
+    return m_Silent;
+  }
+  
+  /**
+   * prints the given message to stdout, if not silent mode
+   * 
+   * @param msg         the text to print to stdout
+   */
+  protected void print(Object msg) {
+    if (!getSilent())
+      System.out.print(msg);
+  }
+  
+  /**
+   * prints the given message (+ LF) to stdout, if not silent mode
+   * 
+   * @param msg         the message to println to stdout
+   */
+  protected void println(Object msg) {
+    print(msg + "\n");
+  }
+  
+  /**
+   * prints a LF to stdout, if not silent mode
+   */
+  protected void println() {
+    print("\n");
+  }
+  
+  /**
+   * runs the CheckScheme with the given options
+   * 
+   * @param check	the checkscheme to setup and run
+   * @param options	the commandline parameters to use
+   */
+  protected static void runCheck(Check check, String[] options) {
+    try {
+      try {
+        check.setOptions(options);
+        Utils.checkForRemainingOptions(options);
+      }
+      catch (Exception ex) {
+        String result = ex.getMessage() + "\n\n" + check.getClass().getName().replaceAll(".*\\.", "") + " Options:\n\n";
+        Enumeration enm = check.listOptions();
+        while (enm.hasMoreElements()) {
+          Option option = (Option) enm.nextElement();
+          result += option.synopsis() + "\n" + option.description() + "\n";
+        }
+        throw new Exception(result);
+      }
+      
+      check.doTests();
+    }
+    catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/CheckGOE.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/CheckGOE.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/CheckGOE.java	(revision 29)
@@ -0,0 +1,394 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckGOE.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ * Simple command line checking of classes that are editable in the GOE.<p/>
+ *
+ * Usage: <p/>
+ * <code>
+ *     CheckGOE -W classname -- test options
+ * </code> <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -ignored &lt;comma-separated list of properties&gt;
+ *  Skipped properties.
+ *  (default: capabilities,options)</pre>
+ * 
+ * <pre> -W
+ *  Full name of the class analysed.
+ *  eg: weka.classifiers.rules.ZeroR
+ *  (default weka.classifiers.rules.ZeroR)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class CheckGOE
+  extends Check {
+
+  /** the object to test */
+  protected Object m_Object = new weka.classifiers.rules.ZeroR();
+  
+  /** whether the tests were successful */
+  protected boolean m_Success;
+  
+  /** properties that are skipped in the checkToolTips method 
+   * @see #checkToolTips() */
+  protected HashSet<String> m_IgnoredProperties = new HashSet<String>();
+  
+  /**
+   * default constructor
+   */
+  public CheckGOE() {
+    super();
+    
+    // set default options
+    try {
+      setOptions(new String[0]);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(new Option(
+        "\tSkipped properties.\n"
+	+ "\t(default: capabilities,options)",
+        "ignored", 1, "-ignored <comma-separated list of properties>"));
+    
+    result.addElement(new Option(
+        "\tFull name of the class analysed.\n"
+        +"\teg: weka.classifiers.rules.ZeroR\n"
+        + "\t(default weka.classifiers.rules.ZeroR)",
+        "W", 1, "-W"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -ignored &lt;comma-separated list of properties&gt;
+   *  Skipped properties.
+   *  (default: capabilities,options)</pre>
+   * 
+   * <pre> -W
+   *  Full name of the class analysed.
+   *  eg: weka.classifiers.rules.ZeroR
+   *  (default weka.classifiers.rules.ZeroR)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.classifiers.rules.ZeroR.class.getName();
+    setObject(Utils.forName(Object.class, tmpStr, null));
+    
+    tmpStr = Utils.getOption("ignored", options);
+    if (tmpStr.length() == 0)
+      tmpStr = "capabilities,options";
+    setIgnoredProperties(tmpStr);
+  }
+  
+  /**
+   * Gets the current settings of the object.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-ignored");
+    result.add(getIgnoredProperties());
+    
+    if (getObject() != null) {
+      result.add("-W");
+      result.add(getObject().getClass().getName());
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Set the object to work on.. 
+   *
+   * @param value 	the object to use.
+   */
+  public void setObject(Object value) {
+    m_Object = value;
+  }
+  
+  /**
+   * Get the object used in the tests.
+   *
+   * @return 		the object used in the tests.
+   */
+  public Object getObject() {
+    return m_Object;
+  }
+
+  /**
+   * Sets the properties to ignore in checkToolTips(). Comma-separated list.
+   * 
+   * @param value	the list of properties
+   * @see 		#checkToolTips()
+   */
+  public void setIgnoredProperties(String value) {
+    String[] 	props;
+    int		i;
+    
+    m_IgnoredProperties.clear();
+    props = value.split(",");
+    for (i = 0; i < props.length; i++)
+      m_IgnoredProperties.add(props[i]);
+  }
+
+  /**
+   * Get the ignored properties used in checkToolTips() as comma-separated 
+   * list (sorted).
+   * 
+   * @return		the ignored properties
+   * @see		#checkToolTips()
+   */
+  public String getIgnoredProperties() {
+    String		result;
+    Vector<String>	list;
+    Iterator		iter;
+    int			i;
+    
+    list = new Vector<String>();
+    iter = m_IgnoredProperties.iterator();
+    while (iter.hasNext())
+      list.add((String) iter.next());
+    
+    // sort
+    if (list.size() > 1)
+      Collections.sort(list);
+    
+    result = "";
+    for (i = 0; i < list.size(); i++) {
+      if (i > 0)
+	result += ",";
+      result += list.get(i);
+    }
+     
+    return result;
+  }
+  
+  /**
+   * returns the success of the tests
+   * 
+   * @return		true if the tests were successful
+   */
+  public boolean getSuccess() {
+    return m_Success;
+  }
+
+  /**
+   * checks whether the object declares a globalInfo method.
+   * 
+   * @return 		true if the test was passed
+   */
+  public boolean checkGlobalInfo() {
+    boolean 	result;
+    Class<?>	cls;
+    
+    print("Global info...");
+    
+    result = true;
+    cls    = getObject().getClass();
+    
+    // test for globalInfo method
+    try {
+      cls.getMethod("globalInfo", (Class[]) null);
+    }
+    catch (Exception e) {
+      result = false;
+    }
+
+    if (result)
+      println("yes");
+    else
+      println("no");
+
+    return result;
+  }
+
+  /**
+   * checks whether the object declares tip text method for all its
+   * properties.
+   * 
+   * @return 		true if the test was passed
+   */
+  public boolean checkToolTips() {
+    boolean 			result;
+    Class<?>			cls;
+    BeanInfo			info;
+    PropertyDescriptor[]	desc;
+    int				i;
+    Vector<String>		missing;
+    String			suffix;
+    
+    print("Tool tips...");
+    
+    result = true;
+    suffix = "TipText";
+    cls    = getObject().getClass();
+    
+    // get properties
+    try {
+      info = Introspector.getBeanInfo(cls, Object.class);
+      desc = info.getPropertyDescriptors();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      desc = null;
+    }
+
+    // test for TipText methods
+    if (desc != null) {
+      missing = new Vector<String>();
+      
+      for (i = 0; i < desc.length; i++) {
+	// skip property?
+	if (m_IgnoredProperties.contains(desc[i].getName()))
+	  continue;
+	if ((desc[i].getReadMethod() == null) || (desc[i].getWriteMethod() == null))
+	  continue;
+	  
+	try {
+	  cls.getMethod(desc[i].getName() + suffix, (Class[]) null);
+	}
+	catch (Exception e) {
+	  result = false;
+	  missing.add(desc[i].getName() + suffix);
+	}
+      }
+      
+      if (result)
+	println("yes");
+      else
+	println("no (missing: " + missing + ")");
+
+    }
+    else {
+      println("maybe");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs some diagnostic tests on the object. Output is
+   * printed to System.out (if not silent).
+   */
+  public void doTests() {
+    println("Object: " + m_Object.getClass().getName() + "\n");
+
+    println("--> Tests");
+
+    m_Success = checkGlobalInfo();
+
+    if (m_Success)
+      m_Success = checkToolTips();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /** 
+   * Main method for using the CheckGOE.
+   *
+   * @param args 	the options to the CheckGOE
+   */
+  public static void main(String[] args) {
+    CheckGOE check = new CheckGOE();
+    runCheck(check, args);
+    if (check.getSuccess())
+      System.exit(0);
+    else
+      System.exit(1);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/CheckOptionHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/CheckOptionHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/CheckOptionHandler.java	(revision 29)
@@ -0,0 +1,600 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckOptionHandler.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Simple command line checking of classes that implement OptionHandler.<p/>
+ *
+ * Usage: <p/>
+ * <code>
+ *     CheckOptionHandler -W optionHandlerClassName -- test options
+ * </code> <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -W
+ *  Full name of the OptionHandler analysed.
+ *  eg: weka.classifiers.rules.ZeroR
+ *  (default weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> 
+ * Options specific to option handler weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are used as user options in testing the
+ * OptionHandler
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class CheckOptionHandler
+  extends Check {
+
+  /** the optionhandler to test */
+  protected OptionHandler m_OptionHandler = new weka.classifiers.rules.ZeroR();
+
+  /** the user-supplied options */
+  protected String[] m_UserOptions = new String[0];
+  
+  /** whether the tests were successful */
+  protected boolean m_Success;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(new Option(
+        "\tFull name of the OptionHandler analysed.\n"
+        +"\teg: weka.classifiers.rules.ZeroR\n"
+        + "\t(default weka.classifiers.rules.ZeroR)",
+        "W", 1, "-W"));
+    
+    if (m_OptionHandler != null) {
+      result.addElement(new Option(
+	  "", "", 0, 
+	  "\nOptions specific to option handler " 
+	  + m_OptionHandler.getClass().getName() + ":"));
+      
+      Enumeration enm = m_OptionHandler.listOptions();
+      while (enm.hasMoreElements())
+        result.addElement((Option)enm.nextElement());
+    }
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -W
+   *  Full name of the OptionHandler analysed.
+   *  eg: weka.classifiers.rules.ZeroR
+   *  (default weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> 
+   * Options specific to option handler weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      tmpStr = weka.classifiers.rules.ZeroR.class.getName();
+    setUserOptions(Utils.partitionOptions(options));
+    setOptionHandler(
+	(OptionHandler) Utils.forName(
+	    OptionHandler.class, tmpStr, null));
+  }
+  
+  /**
+   * Gets the current settings of the CheckClassifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getOptionHandler() != null) {
+      result.add("-W");
+      result.add(getOptionHandler().getClass().getName());
+    }
+    
+    if (m_OptionHandler != null) {
+      options = m_OptionHandler.getOptions();
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Set the OptionHandler to work on.. 
+   *
+   * @param value the OptionHandler to use.
+   */
+  public void setOptionHandler(OptionHandler value) {
+    m_OptionHandler = value;
+  }
+  
+  /**
+   * Get the OptionHandler used in the tests.
+   *
+   * @return the OptionHandler used in the tests.
+   */
+  public OptionHandler getOptionHandler() {
+    return m_OptionHandler;
+  }
+
+  /**
+   * Sets the user-supplied options (creates a copy)
+   * 
+   * @param value	the user-supplied options to use
+   */
+  public void setUserOptions(String[] value) {
+    m_UserOptions = getCopy(value);
+  }
+  
+  /**
+   * Gets the current user-supplied options (creates a copy)
+   * 
+   * @return		the user-supplied options
+   */
+  public String[] getUserOptions() {
+    return getCopy(m_UserOptions);
+  }
+  
+  /**
+   * returns the success of the tests
+   * 
+   * @return		true if the tests were successful
+   */
+  public boolean getSuccess() {
+    return m_Success;
+  }
+  
+  /**
+   * Prints the given options to a string.
+   *
+   * @param options the options to be joined
+   * @return the options as one long string
+   */
+  protected String printOptions(String[] options) {
+    if (options == null) {
+      return("<null>");
+    } else {
+      return Utils.joinOptions(options);
+    }
+  }
+
+  /**
+   * Compares the two given sets of options.
+   *
+   * @param options1 the first set of options
+   * @param options2 the second set of options
+   * @throws Exception if the two sets of options differ
+   */
+  protected void compareOptions(String[] options1, String[] options2) 
+    throws Exception {
+
+    if (options1 == null) {
+      throw new Exception("first set of options is null!");
+    }
+    if (options2 == null) {
+      throw new Exception("second set of options is null!");
+    }
+    if (options1.length != options2.length) {
+      throw new Exception("problem found!\n"
+			    + "First set: " + printOptions(options1) + '\n'
+			    + "Second set: " + printOptions(options2) + '\n'
+			    + "options differ in length");
+    }
+    for (int i = 0; i < options1.length; i++) {
+      if (!options1[i].equals(options2[i])) {
+	
+	throw new Exception("problem found!\n"
+			    + "\tFirst set: " + printOptions(options1) + '\n'
+			    + "\tSecond set: " + printOptions(options2) + '\n'
+			    + '\t' + options1[i] + " != " + options2[i]);
+      }
+    }
+  }
+
+  /**
+   * creates a copy of the given options
+   * 
+   * @param options	the options to copy
+   * @return		the copy
+   */
+  protected String[] getCopy(String[] options) {
+    String[]	result;
+    
+    result = new String[options.length];
+    System.arraycopy(options, 0, result, 0, options.length);
+    
+    return result;
+  }
+  
+  /**
+   * returns a new instance of the OptionHandler's class
+   * 
+   * @return		a new instance
+   */
+  protected OptionHandler getDefaultHandler() {
+    OptionHandler	result;
+    
+    try {
+      result = (OptionHandler) m_OptionHandler.getClass().newInstance();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns the default options the default OptionHandler will return
+   * 
+   * @return		the default options
+   */
+  protected String[] getDefaultOptions() {
+    String[]		result;
+    OptionHandler	o;
+    
+    o = getDefaultHandler();
+    if (o == null) {
+      println("WARNING: couldn't create default handler, cannot use default options!");
+      result = new String[0];
+    }
+    else {
+      result = o.getOptions();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the listOptions method works
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkListOptions() {
+    boolean	result;
+    
+    print("ListOptions...");
+    
+    try {
+      Enumeration enu = getOptionHandler().listOptions();
+      if (getDebug() && enu.hasMoreElements())
+	println("");
+      while (enu.hasMoreElements()) {
+	Option option = (Option) enu.nextElement();
+	if (getDebug()) {
+	  println(option.synopsis());
+	  println(option.description());
+	}
+      }
+
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the user-supplied options can be processed at all
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkSetOptions() {
+    boolean	result;
+    
+    print("SetOptions...");
+    
+    try {
+      getDefaultHandler().setOptions(getUserOptions());
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the default options can be processed completely
+   * or some invalid options are returned by the getOptions() method.
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkDefaultOptions() {
+    boolean	result;
+    String[]	options;
+    
+    print("Default options...");
+
+    options = getDefaultOptions();
+    
+    try {
+      getDefaultHandler().setOptions(options);
+      Utils.checkForRemainingOptions(options);
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the user-supplied options can be processed completely
+   * or some "left-over" options remain
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkRemainingOptions() {
+    boolean	result;
+    String[]	options;
+    
+    print("Remaining options...");
+
+    options = getUserOptions();
+    
+    try {
+      getDefaultHandler().setOptions(options);
+      if (getDebug())
+	println("\n  remaining: " + printOptions(options));
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the user-supplied options stay the same after settting,
+   * getting and re-setting again
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkCanonicalUserOptions() {
+    boolean		result;
+    OptionHandler	handler;
+    String[] 		userOptions;
+    String[] 		userOptionsCheck;
+    
+    print("Canonical user options...");
+
+    try {
+      handler = getDefaultHandler();
+      handler.setOptions(getUserOptions());
+      if (getDebug())
+	print("\n  Getting canonical user options: ");
+      userOptions = handler.getOptions();
+      if (getDebug())
+	println(printOptions(userOptions));
+      if (getDebug())
+	println("  Setting canonical user options");
+      handler.setOptions((String[])userOptions.clone());
+      if (getDebug())
+	println("  Checking canonical user options");
+      userOptionsCheck = handler.getOptions();
+      compareOptions(userOptions, userOptionsCheck);
+
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+
+  /**
+   * checks whether the optionhandler can be re-setted again to default
+   * options after the user-supplied options have been set.
+   * 
+   * @return index 0 is true if the test was passed, index 1 is always false
+   */
+  public boolean checkResettingOptions() {
+    boolean		result;
+    String[]		defaultOptions;
+    String[] 		defaultOptionsCheck;
+    OptionHandler	handler;
+
+    print("Resetting options...");
+    
+    try {
+      if (getDebug())
+	println("\n  Setting user options");
+      handler = getDefaultHandler();
+      handler.setOptions(getUserOptions());
+      defaultOptions = getDefaultOptions();
+      if (getDebug())
+	println("  Resetting to default options");
+      handler.setOptions(getCopy(defaultOptions));
+      if (getDebug())
+	println("  Checking default options match previous default");
+      defaultOptionsCheck = handler.getOptions();
+      compareOptions(defaultOptions, defaultOptionsCheck);
+      
+      println("yes");
+      result = true;
+    }
+    catch (Exception e) {
+      println("no");
+      result = false;
+
+      if (getDebug())
+	println(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs some diagnostic tests on an optionhandler object. Output is
+   * printed to System.out (if not silent).
+   */
+  public void doTests() {
+    println("OptionHandler: " + m_OptionHandler.getClass().getName() + "\n");
+
+    if (getDebug()) {
+      println("--> Info");
+      print("Default options: ");
+      println(printOptions(getDefaultOptions()));
+      print("User options: ");
+      println(printOptions(getUserOptions()));
+    }
+
+    println("--> Tests");
+    m_Success = checkListOptions();
+
+    if (m_Success)
+      m_Success = checkSetOptions();
+   
+    if (m_Success)
+      m_Success = checkDefaultOptions();
+    
+    if (m_Success)
+      m_Success = checkRemainingOptions();
+
+    if (m_Success)
+      m_Success = checkCanonicalUserOptions();
+
+    if (m_Success)
+      m_Success = checkResettingOptions();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /** 
+   * Main method for using the CheckOptionHandler.
+   *
+   * @param args 	the options to the CheckOptionHandler
+   */
+  public static void main(String[] args) {
+    CheckOptionHandler check = new CheckOptionHandler();
+    runCheck(check, args);
+    if (check.getSuccess())
+      System.exit(0);
+    else
+      System.exit(1);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/CheckScheme.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/CheckScheme.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/CheckScheme.java	(revision 29)
@@ -0,0 +1,617 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckScheme.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Abstract general class for testing schemes in Weka. Derived classes are
+ * also used for JUnit tests.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see TestInstances
+ */
+public abstract class CheckScheme
+  extends Check {
+  
+  /** a class for postprocessing the test-data */
+  public static class PostProcessor
+    implements RevisionHandler {
+    
+    /**
+     * Provides a hook for derived classes to further modify the data. Currently,
+     * the data is just passed through.
+     * 
+     * @param data	the data to process
+     * @return		the processed data
+     */
+    public Instances process(Instances data) {
+      return data;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /** The number of instances in the datasets */
+  protected int m_NumInstances = 20;
+  
+  /** the number of nominal attributes */
+  protected int m_NumNominal = 2;
+  
+  /** the number of numeric attributes */
+  protected int m_NumNumeric = 1;
+  
+  /** the number of string attributes */
+  protected int m_NumString = 1;
+  
+  /** the number of date attributes */
+  protected int m_NumDate = 1;
+  
+  /** the number of relational attributes */
+  protected int m_NumRelational = 1;
+  
+  /** the number of instances in relational attributes (applies also for bags
+   * in multi-instance) */
+  protected int m_NumInstancesRelational = 10;
+  
+  /** for generating String attributes/classes */
+  protected String[] m_Words = TestInstances.DEFAULT_WORDS;
+  
+  /** for generating String attributes/classes */
+  protected String m_WordSeparators = TestInstances.DEFAULT_SEPARATORS;
+  
+  /** for post-processing the data even further */
+  protected PostProcessor m_PostProcessor = null;
+  
+  /** whether classpath problems occurred */
+  protected boolean m_ClasspathProblems = false;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(new Option(
+        "\tThe number of instances in the datasets (default 20).",
+        "N", 1, "-N <num>"));
+
+    result.addElement(new Option(
+        "\tThe number of nominal attributes (default 2).",
+        "nominal", 1, "-nominal <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of values for nominal attributes (default 1).",
+        "nominal-values", 1, "-nominal-values <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of numeric attributes (default 1).",
+        "numeric", 1, "-numeric <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of string attributes (default 1).",
+        "string", 1, "-string <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of date attributes (default 1).",
+        "date", 1, "-date <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of relational attributes (default 1).",
+        "relational", 1, "-relational <num>"));
+    
+    result.addElement(new Option(
+        "\tThe number of instances in relational/bag attributes (default 10).",
+        "num-instances-relational", 1, "-num-instances-relational <num>"));
+    
+    result.addElement(new Option(
+        "\tThe words to use in string attributes.",
+        "words", 1, "-words <comma-separated-list>"));
+    
+    result.addElement(new Option(
+        "\tThe word separators to use in string attributes.",
+        "word-separators", 1, "-word-separators <chars>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNumInstances(Integer.parseInt(tmpStr));
+    else
+      setNumInstances(20);
+    
+    tmpStr = Utils.getOption("nominal", options);
+    if (tmpStr.length() != 0)
+      setNumNominal(Integer.parseInt(tmpStr));
+    else
+      setNumNominal(2);
+    
+    tmpStr = Utils.getOption("numeric", options);
+    if (tmpStr.length() != 0)
+      setNumNumeric(Integer.parseInt(tmpStr));
+    else
+      setNumNumeric(1);
+    
+    tmpStr = Utils.getOption("string", options);
+    if (tmpStr.length() != 0)
+      setNumString(Integer.parseInt(tmpStr));
+    else
+      setNumString(1);
+    
+    tmpStr = Utils.getOption("date", options);
+    if (tmpStr.length() != 0)
+      setNumDate(Integer.parseInt(tmpStr));
+    else
+      setNumDate(1);
+    
+    tmpStr = Utils.getOption("relational", options);
+    if (tmpStr.length() != 0)
+      setNumRelational(Integer.parseInt(tmpStr));
+    else
+      setNumRelational(1);
+    
+    tmpStr = Utils.getOption("num-instances-relational", options);
+    if (tmpStr.length() != 0)
+      setNumInstancesRelational(Integer.parseInt(tmpStr));
+    else
+      setNumInstancesRelational(10);
+    
+    tmpStr = Utils.getOption("words", options);
+    if (tmpStr.length() != 0)
+      setWords(tmpStr);
+    else
+      setWords(new TestInstances().getWords());
+    
+    if (Utils.getOptionPos("word-separators", options) > -1) {
+      tmpStr = Utils.getOption("word-separators", options);
+      setWordSeparators(tmpStr);
+    }
+    else {
+      setWordSeparators(TestInstances.DEFAULT_SEPARATORS);
+    }
+  }
+  
+  /**
+   * Gets the current settings of the CheckClassifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-N");
+    result.add("" + getNumInstances());
+    
+    result.add("-nominal");
+    result.add("" + getNumNominal());
+    
+    result.add("-numeric");
+    result.add("" + getNumNumeric());
+    
+    result.add("-string");
+    result.add("" + getNumString());
+    
+    result.add("-date");
+    result.add("" + getNumDate());
+    
+    result.add("-relational");
+    result.add("" + getNumRelational());
+    
+    result.add("-words");
+    result.add("" + getWords());
+    
+    result.add("-word-separators");
+    result.add("" + getWordSeparators());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets the PostProcessor to use
+   * 
+   * @param value	the new PostProcessor
+   * @see #m_PostProcessor
+   */
+  public void setPostProcessor(PostProcessor value) {
+    m_PostProcessor = value;
+  }
+  
+  /**
+   * returns the current PostProcessor, can be null
+   * 
+   * @return		the current PostProcessor
+   */
+  public PostProcessor getPostProcessor() {
+    return m_PostProcessor;
+  }
+  
+  /**
+   * returns TRUE if the classifier returned a "not in classpath" Exception
+   * 
+   * @return	true if CLASSPATH problems occurred
+   */
+  public boolean hasClasspathProblems() {
+    return m_ClasspathProblems;
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public abstract void doTests();
+  
+  /**
+   * Sets the number of instances to use in the datasets (some classifiers
+   * might require more instances).
+   *
+   * @param value the number of instances to use
+   */
+  public void setNumInstances(int value) {
+    m_NumInstances = value;
+  }
+  
+  /**
+   * Gets the current number of instances to use for the datasets.
+   *
+   * @return the number of instances
+   */
+  public int getNumInstances() {
+    return m_NumInstances;
+  }
+  
+  /**
+   * sets the number of nominal attributes
+   * 
+   * @param value	the number of nominal attributes
+   */
+  public void setNumNominal(int value) {
+    m_NumNominal = value;
+  }
+  
+  /**
+   * returns the current number of nominal attributes
+   * 
+   * @return 		the number of nominal attributes
+   */
+  public int getNumNominal() {
+    return m_NumNominal;
+  }
+  
+  /**
+   * sets the number of numeric attributes
+   * 
+   * @param value 	the number of numeric attributes
+   */
+  public void setNumNumeric(int value) {
+    m_NumNumeric = value;
+  }
+  
+  /**
+   * returns the current number of numeric attributes
+   * 
+   * @return 		the number of numeric attributes
+   */
+  public int getNumNumeric() {
+    return m_NumNumeric;
+  }
+  
+  /**
+   * sets the number of string attributes
+   * 
+   * @param value 	the number of string attributes
+   */
+  public void setNumString(int value) {
+    m_NumString = value;
+  }
+  
+  /**
+   * returns the current number of string attributes
+   * 
+   * @return 		the number of string attributes
+   */
+  public int getNumString() {
+    return m_NumString;
+  }
+  
+  /**
+   * sets the number of data attributes
+   * 
+   * @param value	the number of date attributes
+   */
+  public void setNumDate(int value) {
+    m_NumDate = value;
+  }
+  
+  /**
+   * returns the current number of date attributes
+   * 
+   * @return		the number of date attributes
+   */
+  public int getNumDate() {
+    return m_NumDate;
+  }
+  
+  /**
+   * sets the number of relational attributes
+   * 
+   * @param value	the number of relational attributes
+   */
+  public void setNumRelational(int value) {
+    m_NumRelational = value;
+  }
+  
+  /**
+   * returns the current number of relational attributes
+   * 
+   * @return		the number of relational attributes
+   */
+  public int getNumRelational() {
+    return m_NumRelational;
+  }
+  
+  /**
+   * sets the number of instances in relational/bag attributes to produce
+   * 
+   * @param value	the number of instances
+   */
+  public void setNumInstancesRelational(int value) {
+    m_NumInstancesRelational = value;
+  }
+  
+  /**
+   * returns the current number of instances in relational/bag attributes to produce
+   * 
+   * @return		the number of instances
+   */
+  public int getNumInstancesRelational() {
+    return m_NumInstancesRelational;
+  }
+
+  /**
+   * turns the comma-separated list into an array
+   * 
+   * @param value	the list to process
+   * @return		the list as array
+   */
+  protected static String[] listToArray(String value) {
+    StringTokenizer	tok;
+    Vector<String>		list;
+    
+    list = new Vector<String>();
+    tok = new StringTokenizer(value, ",");
+    while (tok.hasMoreTokens())
+      list.add(tok.nextToken());
+    
+    return (String[]) list.toArray(new String[list.size()]);
+  }
+  
+  /**
+   * turns the array into a comma-separated list
+   * 
+   * @param value	the array to process
+   * @return		the array as list
+   */
+  protected static String arrayToList(String[] value) {
+    String	result;
+    int		i;
+    
+    result = "";
+    
+    for (i = 0; i < value.length; i++) {
+      if (i > 0)
+	result += ",";
+      result += value[i];
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns a string representation of the attribute type
+   * 
+   * @param type	the attribute type to get a string rerpresentation for
+   * @return		the string representation
+   */
+  public static String attributeTypeToString(int type) {
+    String	result;
+    
+    switch (type) {
+      case Attribute.NUMERIC:
+	result = "numeric";
+	break;
+	
+      case Attribute.NOMINAL:
+	result = "nominal";
+	break;
+	
+      case Attribute.STRING:
+	result = "string";
+	break;
+	
+      case Attribute.DATE:
+	result = "date";
+	break;
+	
+      case Attribute.RELATIONAL:
+	result = "relational";
+	break;
+
+      default:
+	result = "???";
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Sets the comma-separated list of words to use for generating strings. The
+   * list must contain at least 2 words, otherwise an exception will be thrown.
+   * 
+   * @param value			the list of words
+   * @throws IllegalArgumentException	if not at least 2 words are provided
+   */
+  public void setWords(String value) {
+    if (listToArray(value).length < 2)
+      throw new IllegalArgumentException("At least 2 words must be provided!");
+    
+    m_Words = listToArray(value);
+  }
+  
+  /**
+   * returns the words used for assembling strings in a comma-separated list.
+   * 
+   * @return		the words as comma-separated list
+   */
+  public String getWords() {
+    return arrayToList(m_Words);
+  }
+
+  /**
+   * sets the word separators (chars) to use for assembling strings.
+   * 
+   * @param value	the characters to use as separators
+   */
+  public void setWordSeparators(String value) {
+    m_WordSeparators = value;
+  }
+  
+  /**
+   * returns the word separators (chars) to use for assembling strings.
+   * 
+   * @return		the current separators
+   */
+  public String getWordSeparators() {
+    return m_WordSeparators;
+  }
+  
+  /**
+   * Compare two datasets to see if they differ.
+   *
+   * @param data1 one set of instances
+   * @param data2 the other set of instances
+   * @throws Exception if the datasets differ
+   */
+  protected void compareDatasets(Instances data1, Instances data2)
+    throws Exception {
+    
+    if (!data2.equalHeaders(data1)) {
+      throw new Exception("header has been modified\n" + data2.equalHeadersMsg(data1));
+    }
+    if (!(data2.numInstances() == data1.numInstances())) {
+      throw new Exception("number of instances has changed");
+    }
+    for (int i = 0; i < data2.numInstances(); i++) {
+      Instance orig = data1.instance(i);
+      Instance copy = data2.instance(i);
+      for (int j = 0; j < orig.numAttributes(); j++) {
+        if (orig.isMissing(j)) {
+          if (!copy.isMissing(j)) {
+            throw new Exception("instances have changed");
+          }
+        } else if (orig.value(j) != copy.value(j)) {
+          throw new Exception("instances have changed");
+        }
+        if (orig.weight() != copy.weight()) {
+          throw new Exception("instance weights have changed");
+        }	  
+      }
+    }
+  }
+  
+  /**
+   * Add missing values to a dataset.
+   *
+   * @param data the instances to add missing values to
+   * @param level the level of missing values to add (if positive, this
+   * is the probability that a value will be set to missing, if negative
+   * all but one value will be set to missing (not yet implemented))
+   * @param predictorMissing if true, predictor attributes will be modified
+   * @param classMissing if true, the class attribute will be modified
+   */
+  protected void addMissing(Instances data, int level,
+      boolean predictorMissing, boolean classMissing) {
+    
+    int classIndex = data.classIndex();
+    Random random = new Random(1);
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance current = data.instance(i);
+      for (int j = 0; j < data.numAttributes(); j++) {
+        if (((j == classIndex) && classMissing) ||
+            ((j != classIndex) && predictorMissing)) {
+          if (Math.abs(random.nextInt()) % 100 < level)
+            current.setMissing(j);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. 
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   * @see #m_PostProcessor
+   */
+  protected Instances process(Instances data) {
+    if (getPostProcessor() == null)
+      return data;
+    else
+      return getPostProcessor().process(data);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ClassDiscovery.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ClassDiscovery.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ClassDiscovery.java	(revision 29)
@@ -0,0 +1,787 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClassDiscovery.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.File;
+import java.lang.reflect.Modifier;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * This class is used for discovering classes that implement a certain
+ * interface or a derived from a certain class.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6074 $
+ * @see StringCompare
+ */
+public class ClassDiscovery
+  implements RevisionHandler {
+
+  /** whether to output some debug information. */
+  public final static boolean VERBOSE = false;
+  
+  /** for caching queries (classname+packagename &lt;-&gt; Vector with classnames). */
+  protected static Hashtable<String,Vector<String>> m_Cache;
+  
+  /** notify if VERBOSE is still on */
+  static {
+    if (VERBOSE)
+      System.err.println(ClassDiscovery.class.getName() + ": VERBOSE ON");
+  }
+  
+  /**
+   * Checks whether the "otherclass" is a subclass of the given "superclass".
+   * 
+   * @param superclass      the superclass to check against
+   * @param otherclass      this class is checked whether it is a subclass
+   *                        of the the superclass
+   * @return                TRUE if "otherclass" is a true subclass
+   */
+  public static boolean isSubclass(String superclass, String otherclass) {
+    try {
+      return isSubclass(Class.forName(superclass), Class.forName(otherclass));
+    }
+    catch (Exception e) {
+      return false;
+    }
+  }
+  
+  /**
+   * Checks whether the "otherclass" is a subclass of the given "superclass".
+   * 
+   * @param superclass      the superclass to check against
+   * @param otherclass      this class is checked whether it is a subclass
+   *                        of the the superclass
+   * @return                TRUE if "otherclass" is a true subclass
+   */
+  public static boolean isSubclass(Class superclass, Class otherclass) {
+    Class       currentclass;
+    boolean     result;
+    
+    result       = false;
+    currentclass = otherclass;
+    do {
+      result = currentclass.equals(superclass);
+      
+      // topmost class reached?
+      if (currentclass.equals(Object.class))
+        break;
+      
+      if (!result)
+        currentclass = currentclass.getSuperclass(); 
+    } 
+    while (!result);
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the given class implements the given interface.
+   * 
+   * @param intf      the interface to look for in the given class
+   * @param cls       the class to check for the interface
+   * @return          TRUE if the class contains the interface 
+   */
+  public static boolean hasInterface(String intf, String cls) {
+    try {
+      return hasInterface(Class.forName(intf), Class.forName(cls));
+    }
+    catch (Exception e) {
+      return false;
+    }
+  }
+  
+  /**
+   * Checks whether the given class implements the given interface.
+   * 
+   * @param intf      the interface to look for in the given class
+   * @param cls       the class to check for the interface
+   * @return          TRUE if the class contains the interface 
+   */
+  public static boolean hasInterface(Class intf, Class cls) {
+    Class[]       intfs;
+    int           i;
+    boolean       result;
+    Class         currentclass;
+    
+    result       = false;
+    currentclass = cls;
+    do {
+      // check all the interfaces, this class implements
+      intfs = currentclass.getInterfaces();
+      for (i = 0; i < intfs.length; i++) {
+        if (intfs[i].equals(intf)) {
+          result = true;
+          break;
+        }
+      }
+
+      // get parent class
+      if (!result) {
+        currentclass = currentclass.getSuperclass();
+        
+        // topmost class reached or no superclass?
+        if ( (currentclass == null) || (currentclass.equals(Object.class)) )
+          break;
+      }
+    } 
+    while (!result);
+      
+    return result;
+  }
+  
+  /**
+   * If the given package can be found in this part of the classpath then 
+   * an URL object is returned, otherwise <code>null</code>.
+   * 
+   * @param classpathPart     the part of the classpath to look for the package
+   * @param pkgname           the package to look for
+   * @return                  if found, the url as string, otherwise null
+   */
+  protected static URL getURL(String classpathPart, String pkgname) {
+    String              urlStr;
+    URL                 result;
+    File                classpathFile;
+    File                file;
+    JarFile             jarfile;
+    Enumeration         enm;
+    String              pkgnameTmp;
+    
+    result = null;
+    urlStr = null;
+
+    try {
+      classpathFile = new File(classpathPart);
+      
+      // directory or jar?
+      if (classpathFile.isDirectory()) {
+        // does the package exist in this directory?
+        file = new File(classpathPart + pkgname);
+        if (file.exists())
+          urlStr = "file:" + classpathPart + pkgname;
+      }
+      else {
+        // is package actually included in jar?
+        jarfile    = new JarFile(classpathPart);
+        enm        = jarfile.entries();
+        pkgnameTmp = pkgname.substring(1);   // remove the leading "/"
+        while (enm.hasMoreElements()) {
+          if (enm.nextElement().toString().startsWith(pkgnameTmp)) {
+            urlStr = "jar:file:" + classpathPart + "!" + pkgname;
+            break;
+          }
+        }
+      }
+    }
+    catch (Exception e) {
+      // ignore
+    }
+    
+    // try to generate URL from url string
+    if (urlStr != null) {
+      try {
+        result = new URL(urlStr);
+      }
+      catch (Exception e) {
+        System.err.println(
+            "Trying to create URL from '" + urlStr 
+            + "' generates this exception:\n" + e);
+        result = null;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks the given packages for classes that inherited from the given class,
+   * in case it's a class, or implement this class, in case it's an interface.
+   *
+   * @param classname       the class/interface to look for
+   * @param pkgnames        the packages to search in
+   * @return                a list with all the found classnames
+   */
+  public static Vector<String> find(String classname, String[] pkgnames) {
+    Vector<String>      result;
+    Class       cls;
+
+    result = new Vector<String>();
+
+    try {
+      cls    = Class.forName(classname);
+      result = find(cls, pkgnames);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks the given package for classes that inherited from the given class,
+   * in case it's a class, or implement this class, in case it's an interface.
+   *
+   * @param classname       the class/interface to look for
+   * @param pkgname         the package to search in
+   * @return                a list with all the found classnames
+   */
+  public static Vector<String> find(String classname, String pkgname) {
+    Vector<String>      result;
+    Class       cls;
+
+    result = new Vector<String>();
+
+    try {
+      cls    = Class.forName(classname);
+      result = find(cls, pkgname);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks the given packages for classes that inherited from the given class,
+   * in case it's a class, or implement this class, in case it's an interface.
+   *
+   * @param cls             the class/interface to look for
+   * @param pkgnames        the packages to search in
+   * @return                a list with all the found classnames
+   */
+  public static Vector<String> find(Class cls, String[] pkgnames) {
+    Vector<String>	result;
+    int		i;
+    HashSet<String>	names;
+
+    result = new Vector<String>();
+
+    names = new HashSet<String>();
+    for (i = 0; i < pkgnames.length; i++)
+      names.addAll(find(cls, pkgnames[i]));
+
+    // sort result
+    result.addAll(names);
+    Collections.sort(result, new StringCompare());
+
+    return result;
+  }
+
+  /**
+   * Checks the given package for classes that inherited from the given class,
+   * in case it's a class, or implement this class, in case it's an interface.
+   *
+   * @param cls             the class/interface to look for
+   * @param pkgname         the package to search in
+   * @return                a list with all the found classnames
+   */
+  public static Vector<String> find(Class cls, String pkgname) {
+    Vector<String>                result;
+    StringTokenizer       tok;
+    String                part;
+    String                pkgpath;
+    File                  dir;
+    File[]                files;
+    URL                   url;
+    int                   i;
+    Class                 clsNew;
+    String                classname;
+    JarFile               jar;
+    JarEntry              entry;
+    Enumeration           enm;
+
+
+    // already cached?
+    result = getCache(cls, pkgname);
+    
+    if (result == null) {
+      result = new Vector<String>();
+
+      if (VERBOSE)
+	System.out.println(
+	    "Searching for '" + cls.getName() + "' in '" + pkgname + "':");
+
+      // turn package into path
+      pkgpath = pkgname.replaceAll("\\.", "/");
+
+      // check all parts of the classpath, to include additional classes from
+      // "parallel" directories/jars, not just the first occurence
+      /*tok = new StringTokenizer(
+	  System.getProperty("java.class.path"), 
+	  System.getProperty("path.separator")); */
+      
+      ClassloaderUtil clu = new ClassloaderUtil();
+      URLClassLoader sysLoader = (URLClassLoader)clu.getClass().getClassLoader();
+      URL[] cl_urls = sysLoader.getURLs();
+
+      //while (tok.hasMoreTokens()) {
+      for (i = 0; i < cl_urls.length; i++) {
+	//part = tok.nextToken();
+        part = cl_urls[i].toString();
+        if (part.startsWith("file:")) {
+          part.replace(" ", "%20");
+          try {
+            File temp = new File(new java.net.URI(part));
+            part = temp.getAbsolutePath();
+          } catch (URISyntaxException e) {            
+            e.printStackTrace();
+          }
+        }
+	if (VERBOSE)
+	  System.out.println("Classpath-part: " + part);
+
+	// does package exist in this part of the classpath?
+	url = getURL(part, "/" + pkgpath);
+	if (VERBOSE) {
+	  if (url == null)
+	    System.out.println("   " + pkgpath + " NOT FOUND");
+	  else
+	    System.out.println("   " + pkgpath + " FOUND");
+	}
+	if (url == null)
+	  continue;
+
+	// find classes
+	dir = new File(part + "/" + pkgpath);
+	if (dir.exists()) {
+	  files = dir.listFiles();
+	  for (int j = 0; j < files.length; j++) {
+	    // only class files
+	    if (    (!files[j].isFile()) 
+		|| (!files[j].getName().endsWith(".class")) )
+	      continue;
+
+	    try {
+	      classname =   pkgname + "." 
+	      + files[j].getName().replaceAll(".*/", "")
+	      .replaceAll("\\.class", "");
+	      result.add(classname);
+	    }
+	    catch (Exception e) {
+	      e.printStackTrace();
+	    }
+	  }
+	}
+	else {
+	  try {
+	    jar = new JarFile(part);
+	    enm = jar.entries();
+	    while (enm.hasMoreElements()) {
+	      entry = (JarEntry) enm.nextElement();
+
+	      // only class files
+	      if (    (entry.isDirectory())
+		  || (!entry.getName().endsWith(".class")) )
+		continue;
+
+	      classname = entry.getName().replaceAll("\\.class", "");
+
+	      // only classes in the particular package
+	      if (!classname.startsWith(pkgpath))
+		continue;
+
+	      // no sub-package
+	      if (classname.substring(pkgpath.length() + 1).indexOf("/") > -1)
+		continue;
+
+	      result.add(classname.replaceAll("/", "."));
+	    }
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	  }
+	}
+      }
+
+      // check classes
+      i = 0;
+      while (i < result.size()) {
+	try {
+	  clsNew = Class.forName((String) result.get(i));
+
+	  // no abstract classes
+	  if (Modifier.isAbstract(clsNew.getModifiers()))
+	    result.remove(i);
+	  // must implement interface
+	  else if ( (cls.isInterface()) && (!hasInterface(cls, clsNew)) )
+	    result.remove(i);
+	  // must be derived from class
+	  else if ( (!cls.isInterface()) && (!isSubclass(cls, clsNew)) )
+	    result.remove(i);
+	  else
+	    i++;
+	}
+	catch (Throwable e) {
+	  System.err.println("Checking class: " + result.get(i));
+	  e.printStackTrace();
+	  result.remove(i);
+	}
+      }
+
+      // sort result
+      Collections.sort(result, new StringCompare());
+
+      // add to cache
+      addCache(cls, pkgname, result);
+    }
+
+    return result;
+  }
+
+  /**
+   * adds all the sub-directories recursively to the list.
+   * 
+   * @param prefix	the path prefix
+   * @param dir		the directory to look in for sub-dirs
+   * @param list	the current list of sub-dirs
+   * @return		the new list of sub-dirs
+   */
+  protected static HashSet<String> getSubDirectories(String prefix, File dir, HashSet<String> list) {
+    File[]	files;
+    int		i;
+    String 	newPrefix;
+    
+    // add directory to the list
+    if (prefix == null)
+      newPrefix = "";
+    else if (prefix.length() == 0)
+      newPrefix = dir.getName();
+    else
+      newPrefix = prefix + "." + dir.getName();
+
+    if (newPrefix.length() != 0)
+      list.add(newPrefix);
+    
+    // search for sub-directories
+    files = dir.listFiles();
+    if (files != null) {
+      for (i = 0; i < files.length; i++) {
+	if (files[i].isDirectory())
+	  list = getSubDirectories(newPrefix, files[i], list);
+      }
+    }
+      
+    return list;
+  }
+  
+  /**
+   * Lists all packages it can find in the classpath.
+   *
+   * @return                a list with all the found packages
+   */
+  public static Vector<String> findPackages() {
+    Vector<String>		result;
+    StringTokenizer	tok;
+    String		part;
+    File		file;
+    JarFile		jar;
+    JarEntry		entry;
+    Enumeration<JarEntry>		enm;
+    HashSet<String>		set;
+
+    result = new Vector<String>();
+    set    = new HashSet<String>();
+    
+    // check all parts of the classpath, to include additional classes from
+    // "parallel" directories/jars, not just the first occurence
+    tok = new StringTokenizer(
+        System.getProperty("java.class.path"), 
+        System.getProperty("path.separator"));
+
+    while (tok.hasMoreTokens()) {
+      part = tok.nextToken();
+      if (VERBOSE)
+        System.out.println("Classpath-part: " + part);
+      
+      // find classes
+      file = new File(part);
+      if (file.isDirectory()) {
+	set = getSubDirectories(null, file, set);
+      }
+      else if (file.exists()) {
+        try {
+          jar = new JarFile(part);
+          enm = jar.entries();
+          while (enm.hasMoreElements()) {
+            entry = (JarEntry) enm.nextElement();
+            
+            // only directories
+            if (entry.isDirectory())
+              set.add(entry.getName().replaceAll("/", ".").replaceAll("\\.$", ""));
+          }
+        }
+        catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    }
+
+    // sort result
+    set.remove("META-INF");
+    result.addAll(set);
+    Collections.sort(result, new StringCompare());
+
+    return result;
+  }
+
+  /**
+   * initializes the cache for the classnames.
+   */
+  protected static void initCache() {
+    if (m_Cache == null)
+      m_Cache = new Hashtable<String,Vector<String>>();
+  }
+  
+  /**
+   * adds the list of classnames to the cache.
+   * 
+   * @param cls		the class to cache the classnames for
+   * @param pkgname	the package name the classes were found in
+   * @param classnames	the list of classnames to cache
+   */
+  protected static void addCache(Class cls, String pkgname, Vector<String> classnames) {
+    initCache();
+    m_Cache.put(cls.getName() + "-" + pkgname, classnames);
+  }
+  
+  /**
+   * returns the list of classnames associated with this class and package, if
+   * available, otherwise null.
+   * 
+   * @param cls		the class to get the classnames for
+   * @param pkgname	the package name for the classes 
+   * @return		the classnames if found, otherwise null
+   */
+  protected static Vector<String> getCache(Class cls, String pkgname) {
+    initCache();
+    return m_Cache.get(cls.getName() + "-" + pkgname);
+  }
+  
+  /**
+   * clears the cache for class/classnames relation.
+   */
+  public static void clearCache() {
+    initCache();
+    m_Cache.clear();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6074 $");
+  }
+
+  /**
+   * Possible calls:
+   * <ul>
+   *    <li>
+   *      weka.core.ClassDiscovery &lt;packages&gt;<br/>
+   *      Prints all the packages in the current classpath
+   *    </li>
+   *    <li>
+   *      weka.core.ClassDiscovery &lt;classname&gt; &lt;packagename(s)&gt;<br/>
+   *      Prints the classes it found.
+   *    </li>
+   * </ul>
+   * 
+   * @param args	the commandline arguments
+   */
+  public static void main(String[] args) {
+    Vector<String>      	list;
+    Vector<String> 		packages;
+    int         	i;
+    StringTokenizer	tok;
+    
+    if ((args.length == 1) && (args[0].equals("packages"))) {
+      list = findPackages();
+      for (i = 0; i < list.size(); i++)
+	System.out.println(list.get(i));
+    }
+    else if (args.length == 2) {
+      // packages
+      packages = new Vector<String>();
+      tok = new StringTokenizer(args[1], ",");
+      while (tok.hasMoreTokens())
+        packages.add(tok.nextToken());
+      
+      // search
+      list = ClassDiscovery.find(
+  		args[0], 
+  		(String[]) packages.toArray(new String[packages.size()]));
+
+      // print result, if any
+      System.out.println(
+          "Searching for '" + args[0] + "' in '" + args[1] + "':\n" 
+          + "  " + list.size() + " found.");
+      for (i = 0; i < list.size(); i++)
+        System.out.println("  " + (i+1) + ". " + list.get(i));
+    }
+    else {
+      System.out.println("\nUsage:");
+      System.out.println(
+	  ClassDiscovery.class.getName() + " packages");
+      System.out.println("\tlists all packages in the classpath");
+      System.out.println(
+	  ClassDiscovery.class.getName() + " <classname> <packagename(s)>");
+      System.out.println("\tlists classes derived from/implementing 'classname' that");
+      System.out.println("\tcan be found in 'packagename(s)' (comma-separated list");
+      System.out.println();
+      System.exit(1);
+    }
+  }
+  
+  /**
+   * compares two strings. The following order is used:<br/>
+   * <ul>
+   *    <li>case insensitive</li>
+   *    <li>german umlauts (&auml; , &ouml; etc.) or other non-ASCII letters
+   *    are treated as special chars</li>
+   *    <li>special chars &lt; numbers &lt; letters</li>
+   * </ul>
+   */
+  public static class StringCompare 
+    implements Comparator, RevisionHandler {
+
+    /**
+     * appends blanks to the string if its shorter than <code>len</code>.
+     * 
+     * @param s		the string to pad
+     * @param len	the minimum length for the string to have
+     * @return		the padded string
+     */
+    private String fillUp(String s, int len) {
+      while (s.length() < len)
+        s += " ";
+      return s;
+    }
+    
+    /**
+     * returns the group of the character: 0=special char, 1=number, 2=letter.
+     * 
+     * @param c		the character to check
+     * @return		the group
+     */
+    private int charGroup(char c) {
+      int         result;
+      
+      result = 0;
+      
+      if ( (c >= 'a') && (c <= 'z') )
+        result = 2;
+      else if ( (c >= '0') && (c <= '9') )
+        result = 1;
+      
+      return result;
+    }
+    
+    /**
+     * Compares its two arguments for order.
+     * 
+     * @param o1	the first object
+     * @param o2	the second object
+     * @return		-1 if o1&lt;o2, 0 if o1=o2 and 1 if o1&;gt;o2
+     */    
+    public int compare(Object o1, Object o2) {
+      String        s1;
+      String        s2;
+      int           i;
+      int           result;
+      int           v1;
+      int           v2;
+      
+      result = 0;   // they're equal
+      
+      // get lower case string
+      s1 = o1.toString().toLowerCase();
+      s2 = o2.toString().toLowerCase();
+      
+      // same length
+      s1 = fillUp(s1, s2.length());
+      s2 = fillUp(s2, s1.length());
+      
+      for (i = 0; i < s1.length(); i++) {
+        // same char?
+        if (s1.charAt(i) == s2.charAt(i)) {
+          result = 0;
+        }
+        else {
+          v1 = charGroup(s1.charAt(i));
+          v2 = charGroup(s2.charAt(i));
+          
+          // different type (special, number, letter)?
+          if (v1 != v2) {
+            if (v1 < v2)
+              result = -1;
+            else
+              result = 1;
+          }
+          else {
+            if (s1.charAt(i) < s2.charAt(i))
+              result = -1;
+            else
+              result = 1;
+          }
+          
+          break;
+        }
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Indicates whether some other object is "equal to" this Comparator. 
+     * 
+     * @param obj	the object to compare with this Comparator
+     * @return		true if the object is a StringCompare object as well
+     */
+    public boolean equals(Object obj) {
+      return (obj instanceof StringCompare);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6074 $");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ClassloaderUtil.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ClassloaderUtil.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ClassloaderUtil.java	(revision 29)
@@ -0,0 +1,97 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassloaderUtil.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * Utility class that can add jar files to the classpath dynamically.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version  $Revision: 5953 $
+ */
+public class ClassloaderUtil
+  implements RevisionHandler {
+
+  // Parameters
+  private static final Class[] parameters = new Class[]{URL.class};
+
+  /**
+   * Add file to CLASSPATH
+   * @param s File name
+   * @throws IOException if something goes wrong when adding a file
+   */
+  public static void addFile(String s) throws IOException {
+    File f = new File(s);
+    addFile(f);
+  }
+
+  /**
+   * Add file to CLASSPATH
+   * @param f  File object
+   * @throws IOException if something goes wrong when adding a file
+   */
+  public static void addFile(File f) throws IOException {
+    addURL(f.toURL());
+  }
+
+  /**
+   * Add URL to CLASSPATH
+   * @param u URL
+   * @throws IOException if something goes wrong when adding a url
+   */
+  public static void addURL(URL u) throws IOException {
+    ClassloaderUtil clu = new ClassloaderUtil();
+    //        URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+    URLClassLoader sysLoader = (URLClassLoader) clu.getClass().getClassLoader();
+    URL urls[] = sysLoader.getURLs();
+    for (int i = 0; i < urls.length; i++) {
+      if (urls[i].toString().toLowerCase().equals(u.toString().toLowerCase())) {
+        System.err.println("URL " + u + " is already in the CLASSPATH");
+        return;
+      }
+    }
+    Class<?> sysclass = URLClassLoader.class;
+    try {
+      Method method = sysclass.getDeclaredMethod("addURL", parameters);
+      method.setAccessible(true);
+      method.invoke(sysLoader, new Object[]{u});
+    } catch (Throwable t) {
+      t.printStackTrace();
+      throw new IOException("Error, could not add URL to system classloader");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ContingencyTables.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ContingencyTables.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ContingencyTables.java	(revision 29)
@@ -0,0 +1,657 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ContingencyTables.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Class implementing some statistical routines for contingency tables.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class ContingencyTables
+  implements RevisionHandler {
+
+  /** The natural logarithm of 2 */
+  private static double log2 = Math.log(2);
+
+  /**
+   * Returns chi-squared probability for a given matrix.
+   *
+   * @param matrix the contigency table
+   * @param yates is Yates' correction to be used?
+   * @return the chi-squared probability
+   */
+
+  public static double chiSquared(double [][] matrix, boolean yates) {
+
+    int df = (matrix.length - 1) * (matrix[0].length - 1);
+
+    return Statistics.chiSquaredProbability(chiVal(matrix, yates), df);
+  }
+
+  /**
+   * Computes chi-squared statistic for a contingency table.
+   *
+   * @param matrix the contigency table
+   * @param useYates is Yates' correction to be used?
+   * @return the value of the chi-squared statistic
+   */
+  public static double chiVal(double [][] matrix, boolean useYates) {
+    
+    int df, nrows, ncols, row, col;
+    double[] rtotal, ctotal;
+    double expect = 0, chival = 0, n = 0;
+    boolean yates = true;
+    
+    nrows = matrix.length;
+    ncols = matrix[0].length;
+    rtotal = new double [nrows];
+    ctotal = new double [ncols];
+    for (row = 0; row < nrows; row++) {
+      for (col = 0; col < ncols; col++) {
+	rtotal[row] += matrix[row][col];
+	ctotal[col] += matrix[row][col];
+	n += matrix[row][col];
+      }
+    }
+    df = (nrows - 1)*(ncols - 1);
+    if ((df > 1) || (!useYates)) {
+      yates = false;
+    } else if (df <= 0) {
+      return 0;
+    }
+    chival = 0.0;
+    for (row = 0; row < nrows; row++) {
+      if (Utils.gr(rtotal[row], 0)) {
+	for (col = 0; col < ncols; col++) {
+	  if (Utils.gr(ctotal[col], 0)) {
+	    expect = (ctotal[col] * rtotal[row]) / n;
+	    chival += chiCell (matrix[row][col], expect, yates);
+	  }
+	}
+      }
+    }
+    return chival;
+  }
+
+  /**
+   * Tests if Cochran's criterion is fullfilled for the given
+   * contingency table. Rows and columns with all zeros are not considered
+   * relevant.
+   *
+   * @param matrix the contigency table to be tested
+   * @return true if contingency table is ok, false if not
+   */
+  public static boolean cochransCriterion(double[][] matrix) {
+
+    double[] rtotal, ctotal;
+    double n = 0, expect, smallfreq = 5;
+    int smallcount = 0, nonZeroRows = 0, nonZeroColumns = 0, nrows, ncols, 
+      row, col;
+
+    nrows = matrix.length;
+    ncols = matrix[0].length;
+
+    rtotal = new double [nrows];
+    ctotal = new double [ncols];
+    for (row = 0; row < nrows; row++) {
+      for (col = 0; col < ncols; col++) {
+	rtotal[row] += matrix[row][col];
+	ctotal[col] += matrix[row][col];
+	n += matrix[row][col];
+      }
+    }
+    for (row = 0; row < nrows; row++) {
+      if (Utils.gr(rtotal[row], 0)) {
+	nonZeroRows++;
+      }
+    }
+    for (col = 0; col < ncols; col++) {
+      if (Utils.gr(ctotal[col], 0)) {
+	nonZeroColumns++;
+      }
+    }
+    for (row = 0; row < nrows; row++) {
+      if (Utils.gr(rtotal[row], 0)) {
+	for (col = 0; col < ncols; col++) {
+	  if (Utils.gr(ctotal[col], 0)) {
+	    expect = (ctotal[col] * rtotal[row]) / n;
+	    if (Utils.sm(expect, smallfreq)) {
+	      if (Utils.sm(expect, 1)) {
+		return false;
+	      } else {
+		smallcount++;
+		if (smallcount > (nonZeroRows * nonZeroColumns) / smallfreq) {
+		  return false;
+		}
+	      }
+	    }
+	  }
+	}
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Computes Cramer's V for a contingency table.
+   *
+   * @param matrix the contingency table
+   * @return Cramer's V
+   */
+  public static double CramersV(double [][] matrix) {
+
+    int row, col, nrows,ncols, min;
+    double n = 0;
+    
+    nrows = matrix.length;
+    ncols = matrix[0].length;
+    for (row = 0; row < nrows; row++) {
+      for (col = 0; col < ncols; col++) {
+	n += matrix[row][col];
+      }
+    }
+    min = nrows < ncols ? nrows-1 : ncols-1;
+    if ((min == 0) || Utils.eq(n, 0))
+      return 0;
+    return Math.sqrt(chiVal(matrix, false) / (n * (double)min)); 
+  } 
+
+  /**
+   * Computes the entropy of the given array.
+   *
+   * @param array the array
+   * @return the entropy
+   */
+  public static double entropy(double[] array) {
+
+    double returnValue = 0, sum = 0;
+
+    for (int i = 0; i < array.length; i++) {
+      returnValue -= lnFunc(array[i]);
+      sum += array[i];
+    }
+    if (Utils.eq(sum, 0)) {
+      return 0;
+    } else {
+      return (returnValue + lnFunc(sum)) / (sum * log2);
+    }
+  }
+
+  /**
+   * Computes conditional entropy of the rows given
+   * the columns.
+   *
+   * @param matrix the contingency table
+   * @return the conditional entropy of the rows given the columns
+   */
+  public static double entropyConditionedOnColumns(double[][] matrix) {
+    
+    double returnValue = 0, sumForColumn, total = 0;
+
+    for (int j = 0; j < matrix[0].length; j++) {
+      sumForColumn = 0;
+      for (int i = 0; i < matrix.length; i++) {
+	returnValue = returnValue + lnFunc(matrix[i][j]);
+	sumForColumn += matrix[i][j];
+      }
+      returnValue = returnValue - lnFunc(sumForColumn);
+      total += sumForColumn;
+    }
+    if (Utils.eq(total, 0)) {
+      return 0;
+    }
+    return -returnValue / (total * log2);
+  }
+
+  /**
+   * Computes conditional entropy of the columns given
+   * the rows.
+   *
+   * @param matrix the contingency table
+   * @return the conditional entropy of the columns given the rows
+   */
+  public static double entropyConditionedOnRows(double[][] matrix) {
+    
+    double returnValue = 0, sumForRow, total = 0;
+
+    for (int i = 0; i < matrix.length; i++) {
+      sumForRow = 0;
+      for (int j = 0; j < matrix[0].length; j++) {
+	returnValue = returnValue + lnFunc(matrix[i][j]);
+	sumForRow += matrix[i][j];
+      }
+      returnValue = returnValue - lnFunc(sumForRow);
+      total += sumForRow;
+    }
+    if (Utils.eq(total, 0)) {
+      return 0;
+    }
+    return -returnValue / (total * log2);
+  }
+
+  /**
+   * Computes conditional entropy of the columns given the rows
+   * of the test matrix with respect to the train matrix. Uses a
+   * Laplace prior. Does NOT normalize the entropy.
+   *
+   * @param train the train matrix 
+   * @param test the test matrix
+   * @param numClasses the number of symbols for Laplace
+   * @return the entropy
+   */
+  public static double entropyConditionedOnRows(double[][] train, 
+						double[][] test,
+						double numClasses) {
+    
+    double returnValue = 0, trainSumForRow, testSumForRow, testSum = 0;
+
+    for (int i = 0; i < test.length; i++) {
+      trainSumForRow = 0;
+      testSumForRow = 0;
+      for (int j = 0; j < test[0].length; j++) {
+	returnValue -= test[i][j] * Math.log(train[i][j] + 1);
+	trainSumForRow += train[i][j];
+	testSumForRow += test[i][j];
+      }
+      testSum = testSumForRow;
+      returnValue += testSumForRow * Math.log(trainSumForRow + 
+					     numClasses);
+    }
+    return returnValue / (testSum * log2);
+  }
+
+  /**
+   * Computes the rows' entropy for the given contingency table.
+   *
+   * @param matrix the contingency table
+   * @return the rows' entropy
+   */
+  public static double entropyOverRows(double[][] matrix) {
+    
+    double returnValue = 0, sumForRow, total = 0;
+
+    for (int i = 0; i < matrix.length; i++) {
+      sumForRow = 0;
+      for (int j = 0; j < matrix[0].length; j++) {
+	sumForRow += matrix[i][j];
+      }
+      returnValue = returnValue - lnFunc(sumForRow);
+      total += sumForRow;
+    }
+    if (Utils.eq(total, 0)) {
+      return 0;
+    }
+    return (returnValue + lnFunc(total)) / (total * log2);
+  }
+
+  /**
+   * Computes the columns' entropy for the given contingency table.
+   *
+   * @param matrix the contingency table
+   * @return the columns' entropy
+   */
+  public static double entropyOverColumns(double[][] matrix){
+    
+    double returnValue = 0, sumForColumn, total = 0;
+
+    for (int j = 0; j < matrix[0].length; j++){
+      sumForColumn = 0;
+      for (int i = 0; i < matrix.length; i++) {
+	sumForColumn += matrix[i][j];
+      }
+      returnValue = returnValue - lnFunc(sumForColumn);
+      total += sumForColumn;
+    }
+    if (Utils.eq(total, 0)) {
+      return 0;
+    }
+    return (returnValue + lnFunc(total)) / (total * log2);
+  }
+
+  /**
+   * Computes gain ratio for contingency table (split on rows).
+   * Returns Double.MAX_VALUE if the split entropy is 0.
+   *
+   * @param matrix the contingency table
+   * @return the gain ratio
+   */
+  public static double gainRatio(double[][] matrix){
+    
+    double preSplit = 0, postSplit = 0, splitEnt = 0,
+      sumForRow, sumForColumn, total = 0, infoGain;
+
+    // Compute entropy before split
+    for (int i = 0; i < matrix[0].length; i++) {
+      sumForColumn = 0;
+      for (int j = 0; j < matrix.length; j++) 
+	sumForColumn += matrix[j][i];
+      preSplit += lnFunc(sumForColumn);
+      total += sumForColumn;
+    }
+    preSplit -= lnFunc(total);
+
+    // Compute entropy after split and split entropy
+    for (int i = 0; i < matrix.length; i++) {
+      sumForRow = 0;
+      for (int j = 0; j < matrix[0].length; j++) {
+	postSplit += lnFunc(matrix[i][j]);
+	sumForRow += matrix[i][j];
+      }
+      splitEnt += lnFunc(sumForRow);
+    }
+    postSplit -= splitEnt;
+    splitEnt -= lnFunc(total);
+
+    infoGain = preSplit - postSplit;
+    if (Utils.eq(splitEnt, 0))
+      return 0;
+    return infoGain / splitEnt;
+  }
+
+  /**
+   * Returns negative base 2 logarithm of multiple hypergeometric
+   * probability for a contingency table.
+   *
+   * @param matrix the contingency table
+   * @return the log of the hypergeometric probability of the contingency table 
+   */
+  public static double log2MultipleHypergeometric(double[][] matrix) {
+
+    double sum = 0, sumForRow, sumForColumn, total = 0;
+
+    for (int i = 0; i < matrix.length; i++) {
+      sumForRow = 0;
+      for (int j = 0; j < matrix[i].length; j++) {
+	sumForRow += matrix[i][j];
+      }
+      sum += SpecialFunctions.lnFactorial(sumForRow);
+      total += sumForRow;
+    }
+    for (int j = 0; j < matrix[0].length; j++) {
+      sumForColumn = 0;
+      for (int i = 0; i < matrix.length; i++) {
+	sumForColumn += matrix [i][j];
+      }
+      sum += SpecialFunctions.lnFactorial(sumForColumn);
+    }
+    for (int i = 0; i < matrix.length; i++) {
+      for (int j = 0; j < matrix[i].length; j++) {
+	sum -= SpecialFunctions.lnFactorial(matrix[i][j]);
+      }
+    }
+    sum -= SpecialFunctions.lnFactorial(total);
+    return -sum / log2;
+  }
+
+  /**
+   * Reduces a matrix by deleting all zero rows and columns.
+   *
+   * @param matrix the matrix to be reduced
+   * @return the matrix with all zero rows and columns deleted
+   */
+  public static double[][] reduceMatrix(double[][] matrix) {
+
+    int row, col, currCol, currRow, nrows, ncols, 
+      nonZeroRows = 0, nonZeroColumns = 0;
+    double[] rtotal, ctotal;
+    double[][] newMatrix;
+
+    nrows = matrix.length;
+    ncols = matrix[0].length;
+    rtotal = new double [nrows];
+    ctotal = new double [ncols];
+    for (row = 0; row < nrows; row++) {
+      for (col = 0; col < ncols; col++) {
+	rtotal[row] += matrix[row][col];
+	ctotal[col] += matrix[row][col];
+      }
+    }
+    for (row = 0; row < nrows; row++) {
+      if (Utils.gr(rtotal[row],0)) {
+	nonZeroRows++;
+      }
+    }
+    for (col = 0; col < ncols; col++) {
+      if (Utils.gr(ctotal[col],0)) {
+	nonZeroColumns++;
+      }
+    }
+    newMatrix = new double[nonZeroRows][nonZeroColumns];
+    currRow = 0;
+    for (row = 0; row < nrows; row++) {
+      if (Utils.gr(rtotal[row],0)) {
+	currCol = 0;
+	for (col = 0; col < ncols; col++) {
+	  if (Utils.gr(ctotal[col],0)) {
+	    newMatrix[currRow][currCol] = matrix[row][col];
+	    currCol++;
+	  }
+	}
+	currRow++;
+      }
+    }
+    return newMatrix;
+  }
+    
+   /**
+    * Calculates the symmetrical uncertainty for base 2.
+    *
+    * @param matrix the contingency table
+    * @return the calculated symmetrical uncertainty
+    *
+    */
+  public static double symmetricalUncertainty(double matrix[][]) {
+
+    double sumForColumn, sumForRow, total = 0, columnEntropy = 0, 
+      rowEntropy = 0, entropyConditionedOnRows = 0, infoGain = 0;
+
+    // Compute entropy for columns
+    for (int i = 0; i < matrix[0].length; i++) {
+      sumForColumn = 0;
+      for (int j = 0; j < matrix.length; j++) {
+	sumForColumn += matrix[j][i];
+      }
+      columnEntropy += lnFunc(sumForColumn);
+      total += sumForColumn;
+    }
+    columnEntropy -= lnFunc(total);
+
+    // Compute entropy for rows and conditional entropy
+    for (int i = 0; i < matrix.length; i++) {
+      sumForRow = 0;
+      for (int j = 0; j < matrix[0].length; j++) { 
+	sumForRow += matrix[i][j];
+	entropyConditionedOnRows += lnFunc(matrix[i][j]);
+      }
+      rowEntropy += lnFunc(sumForRow);
+    }
+    entropyConditionedOnRows -= rowEntropy;
+    rowEntropy -= lnFunc(total);
+    infoGain = columnEntropy - entropyConditionedOnRows;
+    if (Utils.eq(columnEntropy, 0) || Utils.eq(rowEntropy, 0))
+      return 0;
+    return 2.0 * (infoGain / (columnEntropy + rowEntropy));
+  }
+
+  /**
+   * Computes Goodman and Kruskal's tau-value for a contingency table.
+   *
+   * @param matrix the contingency table
+   * @return Goodman and Kruskal's tau-value
+   */
+  public static double tauVal(double[][] matrix) {
+    
+    int nrows, ncols, row, col;
+    double [] ctotal;
+    double maxcol = 0, max, maxtotal = 0, n = 0;
+    
+    nrows = matrix.length;
+    ncols = matrix[0].length;
+    ctotal = new double [ncols];
+    for (row = 0; row < nrows; row++) {
+      max = 0;
+      for (col = 0; col < ncols; col++) {
+	if (Utils.gr(matrix[row][col], max)) 
+	  max = matrix[row][col];
+	ctotal[col] += matrix[row][col];
+	n += matrix[row][col];
+      }
+      maxtotal += max;
+    }
+    if (Utils.eq(n, 0)) {
+      return 0;
+    }
+    maxcol = ctotal[Utils.maxIndex(ctotal)];
+    return (maxtotal - maxcol)/(n - maxcol);
+  }
+
+  /**
+   * Help method for computing entropy.
+   */
+  private static double lnFunc(double num){
+    
+    // Constant hard coded for efficiency reasons
+    if (num < 1e-6) {
+      return 0;
+    } else {
+      return num * Math.log(num);
+    }
+  }
+
+  /**
+   * Computes chi-value for one cell in a contingency table.
+   *
+   * @param freq the observed frequency in the cell
+   * @param expected the expected frequency in the cell
+   * @return the chi-value for that cell; 0 if the expected value is
+   * too close to zero 
+   */
+  private static double chiCell(double freq, double expected, 
+                                boolean yates){
+
+    // Cell in empty row and column?
+    if (Utils.smOrEq(expected, 0)) {
+      return 0;
+    }
+
+    // Compute difference between observed and expected value
+    double diff = Math.abs(freq - expected);
+    if (yates) {
+
+      // Apply Yates' correction if wanted
+      diff -= 0.5;
+
+      // The difference should never be negative
+      if (diff < 0) {
+        diff = 0;
+      }
+    }
+
+    // Return chi-value for the cell
+    return (diff * diff / expected);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] ops) {
+
+    double[] firstRow = {10, 5, 20};
+    double[] secondRow = {2, 10, 6};
+    double[] thirdRow = {5, 10, 10};
+    double[][] matrix = new double[3][0];
+
+    matrix[0] = firstRow; matrix[1] = secondRow; matrix[2] = thirdRow;
+    for (int i = 0; i < matrix.length; i++) {
+      for (int j = 0; j < matrix[i].length; j++) {
+	System.out.print(matrix[i][j] + " ");
+      }
+      System.out.println();
+    }
+    System.out.println("Chi-squared probability: " +
+		       ContingencyTables.chiSquared(matrix, false));
+    System.out.println("Chi-squared value: " +
+		       ContingencyTables.chiVal(matrix, false));
+    System.out.println("Cochran's criterion fullfilled: " +
+		       ContingencyTables.cochransCriterion(matrix));
+    System.out.println("Cramer's V: " +
+		       ContingencyTables.CramersV(matrix));
+    System.out.println("Entropy of first row: " +
+		       ContingencyTables.entropy(firstRow));
+    System.out.println("Entropy conditioned on columns: " +
+		       ContingencyTables.entropyConditionedOnColumns(matrix));
+    System.out.println("Entropy conditioned on rows: " +
+		       ContingencyTables.entropyConditionedOnRows(matrix));
+    System.out.println("Entropy conditioned on rows (with Laplace): " +
+		       ContingencyTables.entropyConditionedOnRows(matrix, matrix, 3));
+    System.out.println("Entropy of rows: " +
+		       ContingencyTables.entropyOverRows(matrix));
+    System.out.println("Entropy of columns: " +
+		       ContingencyTables.entropyOverColumns(matrix));
+    System.out.println("Gain ratio: " +
+		       ContingencyTables.gainRatio(matrix));
+    System.out.println("Negative log2 of multiple hypergeometric probability: " +
+		       ContingencyTables.log2MultipleHypergeometric(matrix));
+    System.out.println("Symmetrical uncertainty: " +
+		       ContingencyTables.symmetricalUncertainty(matrix));
+    System.out.println("Tau value: " +
+		       ContingencyTables.tauVal(matrix));
+    double[][] newMatrix = new double[3][3];
+    newMatrix[0][0] = 1; newMatrix[0][1] = 0; newMatrix[0][2] = 1;
+    newMatrix[1][0] = 0; newMatrix[1][1] = 0; newMatrix[1][2] = 0;
+    newMatrix[2][0] = 1; newMatrix[2][1] = 0; newMatrix[2][2] = 1;
+    System.out.println("Matrix with empty row and column: ");
+    for (int i = 0; i < newMatrix.length; i++) {
+      for (int j = 0; j < newMatrix[i].length; j++) {
+	System.out.print(newMatrix[i][j] + " ");
+      }
+      System.out.println();
+    }
+    System.out.println("Reduced matrix: ");
+    newMatrix = ContingencyTables.reduceMatrix(newMatrix);
+    for (int i = 0; i < newMatrix.length; i++) {
+      for (int j = 0; j < newMatrix[i].length; j++) {
+	System.out.print(newMatrix[i][j] + " ");
+      }
+      System.out.println();
+    }
+  }
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/Copyable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Copyable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Copyable.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Interface implemented by classes that can produce "shallow" copies
+ * of their objects. (As opposed to clone(), which is supposed to
+ * produce a "deep" copy.)
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Copyable {
+
+  /**
+   * This method produces a shallow copy of an object.
+   * It does the same as the clone() method in Object, which also produces
+   * a shallow copy.
+   */
+  Object copy();
+}
Index: branches/MetisMQI/src/main/java/weka/core/Copyright.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Copyright.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Copyright.java	(revision 29)
@@ -0,0 +1,109 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.Calendar;
+import java.util.Properties;
+
+/**
+ * A class for providing centralized Copyright information.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Copyright {
+  
+  /** the copyright file */
+  public final static String PROPERTY_FILE = "weka/core/Copyright.props";
+  
+  /** Contains the properties */
+  protected static Properties PROPERTIES;
+
+  static {
+    PROPERTIES = new Properties();
+    
+    try {
+      //      PROPERTIES.load(ClassLoader.getSystemResourceAsStream(PROPERTY_FILE));
+      PROPERTIES.
+        load((new Copyright()).getClass().getClassLoader().getResourceAsStream(PROPERTY_FILE));
+    }
+    catch (Exception e) {
+      System.err.println(
+	  "Could not read configuration file for the copyright "
+	  + "information - using default.");
+    }
+  }
+  
+  /**
+   * returns the start year of the copyright
+   * 
+   * @return		the start year
+   */
+  public static String getFromYear() {
+    return PROPERTIES.getProperty("FromYear", "1999");
+  }
+  
+  /**
+   * returns the end year of the copyright (i.e., current year)
+   * 
+   * @return		the end/current year
+   */
+  public static String getToYear() {
+    return PROPERTIES.getProperty("ToYear", "" + Calendar.getInstance().get(Calendar.YEAR));
+  }
+  
+  /**
+   * returns the entity owning the copyright
+   * 
+   * @return		the owner
+   */
+  public static String getOwner() {
+    return PROPERTIES.getProperty("Owner", "The University of Waikato");
+  }
+  
+  /**
+   * returns the address of the owner
+   * 
+   * @return		the address
+   */
+  public static String getAddress() {
+    return PROPERTIES.getProperty("Address", "Hamilton, New Zealand");
+  }
+  
+  /**
+   * returns the URL of the owner
+   * 
+   * @return		the URL
+   */
+  public static String getURL() {
+    return PROPERTIES.getProperty("URL", "http://www.cs.waikato.ac.nz/~ml/");
+  }
+  
+  /**
+   * Only for testing
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    System.out.println(PROPERTIES);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Copyright.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Copyright.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Copyright.props	(revision 29)
@@ -0,0 +1,11 @@
+# central copyright information
+#
+# Author:  FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 6205 $
+
+FromYear=1999
+ToYear=2010
+Owner=The University of Waikato
+Address=Hamilton, New Zealand
+URL=http://www.cs.waikato.ac.nz/~ml/weka/
+
Index: branches/MetisMQI/src/main/java/weka/core/Debug.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Debug.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Debug.java	(revision 29)
@@ -0,0 +1,1658 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Debug.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * A helper class for debug output, logging, clocking, etc.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Debug
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 66171861743328020L;
+  
+  /** the log level All */
+  public final static Level ALL = Level.ALL;
+  /** the log level Vonfig */
+  public final static Level CONFIG = Level.CONFIG;
+  /** the log level Fine */
+  public final static Level FINE = Level.FINE;
+  /** the log level Finer */
+  public final static Level FINER = Level.FINER;
+  /** the log level Finest */
+  public final static Level FINEST = Level.FINEST;
+  /** the log level Info */
+  public final static Level INFO = Level.INFO;
+  /** the log level Off - i.e., no logging */
+  public final static Level OFF = Level.OFF;
+  /** the log level Severe */
+  public final static Level SEVERE = Level.SEVERE;
+  /** the log level Warning */
+  public final static Level WARNING = Level.WARNING;
+
+  /** whether logging is enabled */
+  protected boolean m_Enabled = true;
+  
+  /** for logging */
+  protected Log m_Log;
+  
+  /** for clocking */
+  protected Clock m_Clock = new Clock();
+  
+  /**
+   * A little helper class for clocking and outputting times. It measures the
+   * CPU time if possible, otherwise it's just based on the system time. In 
+   * case one just wants to measure time (e.g., database queries don't take up
+   * much CPU time, but still might take a long time to finish), then one can
+   * disable the use of CPU time as well.
+   *
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $ 
+   * @see ThreadMXBean#isThreadCpuTimeEnabled()
+   */
+  public static class Clock 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 4622161807307942201L;
+    
+    /** the output format in milli-seconds */
+    public final static int FORMAT_MILLISECONDS = 0;
+    
+    /** the output format in seconds, with fraction of msecs */
+    public final static int FORMAT_SECONDS = 1;
+    
+    /** the output format in hours:minutes:seconds, with fraction of msecs */
+    public final static int FORMAT_HHMMSS = 2;
+    
+    /** the output formats */
+    public static final Tag[] TAGS_FORMAT = {
+      new Tag(FORMAT_MILLISECONDS, "milli-seconds"),
+      new Tag(FORMAT_SECONDS, "seconds"),
+      new Tag(FORMAT_HHMMSS, "hh:mm:ss")
+    };
+    
+    /** the format of the output */
+    public int m_OutputFormat = FORMAT_SECONDS;
+    
+    /** the start time */
+    protected long m_Start;
+    
+    /** the end time */
+    protected long m_Stop;
+    
+    /** whether the time is still clocked */
+    protected boolean m_Running;
+    
+    /** the thread ID */
+    protected long m_ThreadID;
+    
+    /** whether the system can measure the CPU time */
+    protected boolean m_CanMeasureCpuTime;
+    
+    /** whether to use the CPU time (by default TRUE) */
+    protected boolean m_UseCpuTime;
+    
+    /** the thread monitor, if the system can measure the CPU time */
+    protected transient ThreadMXBean m_ThreadMonitor;
+    
+    /**
+     * automatically starts the clock with FORMAT_SECONDS format and CPU
+     * time if available
+     * 
+     * @see		#m_OutputFormat
+     */
+    public Clock() {
+      this(true);
+    }
+    
+    /**
+     * automatically starts the clock with the given output format and CPU
+     * time if available
+     * 
+     * @param format	the output format
+     * @see		#m_OutputFormat
+     */
+    public Clock(int format) {
+      this(true, format);
+    }
+    
+    /**
+     * starts the clock depending on <code>start</code> immediately with the
+     * FORMAT_SECONDS output format and CPU time if available
+     * 
+     * @param start	whether to start the clock immediately
+     * @see		#m_OutputFormat
+     */
+    public Clock(boolean start) {
+      this(start, FORMAT_SECONDS);
+    }
+    
+    /**
+     * starts the clock depending on <code>start</code> immediately, using
+     * CPU time if available
+     * 
+     * @param start	whether to start the clock immediately
+     * @param format	the format
+     * @see		#m_OutputFormat
+     */
+    public Clock(boolean start, int format) {
+      m_Running    = false;
+      m_Start      = 0;
+      m_Stop       = 0;
+      m_UseCpuTime = true;
+      setOutputFormat(format);
+
+      if (start)
+	start();
+    }
+    
+    /**
+     * initializes the clocking, ensure to get the correct thread ID.
+     */
+    protected void init() {
+      m_ThreadMonitor = null;
+      m_ThreadMonitor = getThreadMonitor();
+
+      // can we measure cpu time?
+      m_CanMeasureCpuTime = m_ThreadMonitor.isThreadCpuTimeSupported();
+    }
+    
+    /**
+     * whether the measurement is based on the msecs returned from the System
+     * class or on the more accurate CPU time. Also depends on whether the 
+     * usage of the CPU time was disabled or enabled.
+     * 
+     * @return		true if the more accurate CPU time of the thread is 
+     * 			used and the use of CPU time hasn't been disabled
+     * @see System#currentTimeMillis()
+     * @see ThreadMXBean#isThreadCpuTimeEnabled()
+     * @see #getUseCpuTime()
+     */
+    public boolean isCpuTime() {
+      return m_UseCpuTime && m_CanMeasureCpuTime;
+    }
+
+    /**
+     * enables/disables the use of CPU time (if measurement of CPU time is 
+     * available). The actual use of CPU time still depends on whether the 
+     * system supports it. Resets the current timer, if running.
+     * 
+     * @param value	if true the CPU time is used (if possible)
+     */
+    public void setUseCpuTime(boolean value) {
+      m_UseCpuTime = value;
+      
+      // we have to re-initialize the start time, otherwise we get bogus
+      // results
+      if (m_Running) {
+	stop();
+	start();
+      }
+    }
+
+    /**
+     * returns whether the use of CPU is time is enabled/disabled (regardless
+     * whether the system supports it or not)
+     * 
+     * @return		true the CPU time is used (if possible)
+     */
+    public boolean getUseCpuTime() {
+      return m_UseCpuTime;
+    }
+    
+    /**
+     * Returns a new thread monitor if the current one is null (e.g., due to
+     * serialization) or the currently set one. The thread ID is also updated
+     * if necessary.
+     * 
+     * @return		the thread monitor to use
+     */
+    protected ThreadMXBean getThreadMonitor() {
+      if (m_ThreadMonitor == null) {
+	m_ThreadMonitor = ManagementFactory.getThreadMXBean();
+	if (!m_ThreadMonitor.isThreadCpuTimeEnabled())
+	  m_ThreadMonitor.setThreadCpuTimeEnabled(true);
+	m_ThreadID = Thread.currentThread().getId();
+      }
+      
+      return m_ThreadMonitor;
+    }
+    
+    /**
+     * returns the current time in msec
+     * 
+     * @return		the current time
+     */
+    protected long getCurrentTime() {
+      long	result;
+      
+      if (isCpuTime())
+	result = getThreadMonitor().getThreadUserTime(m_ThreadID) / 1000000;
+      else
+	result = System.currentTimeMillis();
+      
+      return result;
+    }
+    
+    /**
+     * saves the current system time (or CPU time) in msec as start time
+     * 
+     * @see       #m_Start
+     */
+    public void start() {
+      // make sure that we get the right thread ID!
+      init();
+
+      m_Start   = getCurrentTime();
+      m_Stop    = m_Start;
+      m_Running = true;
+    }
+    
+    /**
+     * saves the current system (or CPU time) in msec as stop time
+     * 
+     * @see       #m_Stop
+     */
+    public void stop() {
+      m_Stop    = getCurrentTime();
+      m_Running = false;
+    }
+    
+    /**
+     * returns the start time
+     * 
+     * @return	the start time
+     */
+    public long getStart() {
+      return m_Start;
+    }
+    
+    /**
+     * returns the stop time or, if still running, the current time
+     * 
+     * @return 	the stop time
+     */
+    public long getStop() {
+      long	result;
+      
+      if (isRunning())
+	result = getCurrentTime();
+      else
+	result = m_Stop;
+      
+      return result;
+    }
+    
+    /**
+     * whether the time is still being clocked
+     * 
+     * @return		true if the time is still being clocked
+     */
+    public boolean isRunning() {
+      return m_Running;
+    }
+    
+    /**
+     * sets the format of the output
+     * 
+     * @param value       the format of the output
+     * @see               #m_OutputFormat
+     */
+    public void setOutputFormat(int value) {
+      if (value == FORMAT_MILLISECONDS)
+	m_OutputFormat = value;
+      else if (value == FORMAT_SECONDS)
+	m_OutputFormat = value;
+      else if (value == FORMAT_HHMMSS)
+	m_OutputFormat = value;
+      else
+	System.out.println("Format '" + value + "' is not recognized!");
+    }
+    
+    /**
+     * returns the output format
+     * 
+     * @return		the output format
+     * @see		#m_OutputFormat
+     */
+    public int getOutputFormat() {
+      return m_OutputFormat;
+    }
+    
+    /**
+     * returns the elapsed time, getStop() - getStart(), as string
+     * 
+     * @return	the elapsed time as string
+     * @see       #getStart()
+     * @see       #getStop()
+     */
+    public String toString() {
+      String    result;
+      long      elapsed;
+      long      hours;
+      long      mins;
+      long      secs;
+      long      msecs;
+      
+      result  = "";
+      elapsed = getStop() - getStart();
+      
+      switch (getOutputFormat()) {
+	case FORMAT_HHMMSS:
+	  hours   = elapsed / (3600 * 1000);
+	  elapsed = elapsed % (3600 * 1000);
+	  mins    = elapsed / (60 * 1000);
+	  elapsed = elapsed % (60 * 1000);
+	  secs    = elapsed / 1000;
+	  msecs   = elapsed % 1000;
+	  
+	  if (hours > 0)
+	    result += "" + hours + ":";
+	  
+	  if (mins < 10)
+	    result += "0" + mins + ":";
+	  else
+	    result += ""  + mins + ":";
+	  
+	  if (secs < 10)
+	    result += "0" + secs + ".";
+	  else
+	    result += "" + secs + ".";
+	  
+	  result += Utils.doubleToString(
+	      (double) msecs / (double) 1000, 3).replaceAll(".*\\.", "");
+	  break;
+	  
+	case FORMAT_SECONDS:
+	  result = Utils.doubleToString((double) elapsed / (double) 1000, 3) + "s";
+	  break;
+	  
+	case FORMAT_MILLISECONDS:
+	  result = "" + elapsed + "ms";
+	  break;
+	  
+	default:
+	  result = "<unknown time format>";
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A class that can be used for timestamps in files, The toString() method
+   * simply returns the associated Date object in a timestamp format. For
+   * formatting options, see java.text.SimpleDateFormat.
+   *
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $ 
+   * @see SimpleDateFormat
+   */
+  public static class Timestamp
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -6099868388466922753L;
+
+    /** the default format */
+    public final static String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    
+    /** the actual date */
+    protected Date m_Stamp;
+    
+    /** the format of the timestamp */
+    protected String m_Format;
+    
+    /** handles the format of the output */
+    protected SimpleDateFormat m_Formatter;
+    
+    /**
+     * creates a timestamp with the current date and time and the default
+     * format.
+     */
+    public Timestamp() {
+      this(DEFAULT_FORMAT);
+    }
+    
+    /**
+     * creates a timestamp with the current date and time and the specified
+     * format.
+     * 
+     * @param format	the format of the timestamp
+     * @see		SimpleDateFormat
+     */
+    public Timestamp(String format) {
+      this(new Date(), format);
+    }
+    
+    /**
+     * creates a timestamp with the given date and the default format.
+     * 
+     * @param stamp	the associated date/time for the timestamp
+     */
+    public Timestamp(Date stamp) {
+      this(stamp, DEFAULT_FORMAT);
+    }
+    
+    /**
+     * creates a timestamp with the given date and format.
+     * 
+     * @param stamp	the associated date/time for the timestamp
+     * @param format	the format of the timestamp
+     * @see		SimpleDateFormat
+     */
+    public Timestamp(Date stamp, String format) {
+      super();
+      
+      m_Stamp = stamp;
+      setFormat(format);
+    }
+    
+    /**
+     * sets the format for the timestamp
+     * 
+     * @param value	the format string
+     * @see		SimpleDateFormat
+     */
+    public void setFormat(String value) {
+      try {
+	m_Formatter = new SimpleDateFormat(value);
+	m_Format    = value;
+      }
+      catch (Exception e) {
+	m_Formatter = new SimpleDateFormat(DEFAULT_FORMAT);
+	m_Format    = DEFAULT_FORMAT;
+      }
+    }
+    
+    /**
+     * returns the current timestamp format
+     * 
+     * @return		the current format
+     */
+    public String getFormat() {
+      return m_Format;
+    }
+    
+    /**
+     * returns the associated date/time
+     * 
+     * @return		the timestamp value
+     */
+    public Date getStamp() {
+      return m_Stamp;
+    }
+    
+    /**
+     * returns the timestamp as string in the specified format
+     * 
+     * @return		the timestamp as string
+     */
+    public String toString() {
+      return m_Formatter.format(getStamp());
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A little, simple helper class for logging stuff. Uses simple file access
+   * and not the java.util.logging stuff (see Log for that). Uses the 
+   * writeToFile methods of the Debug class.
+   * 
+   * @see Debug.Log
+   * @see Debug#writeToFile(String, String)
+   * @see Debug#writeToFile(String, String, boolean)
+   */
+  public static class SimpleLog 
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -2671928223819510830L;
+    
+    /** the file to write to (if null then only stdout is used) */
+    protected String m_Filename = null;
+    
+    /**
+     * default constructor, uses only stdout
+     */
+    public SimpleLog() {
+      this(null);
+    }
+    
+    /**
+     * Creates a logger that writes into the specified file. Appends to the 
+     * file by default.
+     * 
+     * @param filename	the file to write to, if null then only stdout is used
+     */
+    public SimpleLog(String filename) {
+      this(filename, true);
+    }
+    
+    /**
+     * Creates a logger that writes into the specified file. Appends to the 
+     * file by default.
+     * 
+     * @param filename	the file to write to, if null then only stdout is used
+     * @param append	if false, the file will be deleted first
+     */
+    public SimpleLog(String filename, boolean append) {
+      super();
+      
+      m_Filename = filename;
+      
+      Debug.writeToFile(m_Filename, "--> Log started", append);
+    }
+    
+    /**
+     * returns the filename of the log, can be null
+     * 
+     * @return		the filename of the log
+     */
+    public String getFilename() {
+      return m_Filename;
+    }
+    
+    /**
+     * logs the given message to the file
+     * 
+     * @param message	the message to log
+     */
+    public void log(String message) {
+      String	log;
+      
+      log = new Timestamp() + " " + message;
+      
+      if (getFilename() != null)
+	Debug.writeToFile(getFilename(), log);
+
+      System.out.println(log);
+    }
+    
+    /**
+     * a convenience method for dumping the current system info in the 
+     * log file
+     * 
+     * @see SystemInfo
+     */
+    public void logSystemInfo() {
+      log("SystemInfo:\n" + new SystemInfo().toString());
+    }
+    
+    /**
+     * returns a string representation of the logger
+     * 
+     * @return		a string representation of the logger
+     */
+    public String toString() {
+      String	result;
+      
+      result =   "Filename: " + getFilename();
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A helper class for logging stuff. Uses the java.util.logging
+   * package. If this approach seems an "overkill" (it can create quite a few 
+   * log files if used in different threads), one can use the 
+   * Debug.SimpleLog class.
+   *
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $ 
+   * @see Debug.SimpleLog
+   */
+  public static class Log
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = 1458435732111675823L;
+
+    /** the actual logger, if null only stdout is used */
+    protected transient Logger m_Logger = null;
+    
+    /** the filename, if any */
+    protected String m_Filename = null;
+    
+    /** the size of the file (in bytes) */
+    protected int m_Size;
+    
+    /** the number of files for rotating the logs */
+    protected int m_NumFiles;
+
+    /** whether the initialization of the logger failed */
+    protected boolean m_LoggerInitFailed = false;
+    
+    /**
+     * default constructor, uses only stdout
+     */
+    public Log() {
+      this(null);
+    }
+    
+    /**
+     * creates a logger that logs into the specified file, if null then only
+     * stdout is used. It uses 1,000,000 bytes for file size and 1 file.
+     * 
+     * @param filename	the file to log into
+     */
+    public Log(String filename) {
+      this(filename, 1000000, 1);
+    }
+    
+    /**
+     * creates a logger that logs into the specified file, if null then only
+     * stdout is used.
+     * 
+     * @param filename	the file to log into
+     * @param size	the size of the files in bytes
+     * @param numFiles	the number of files for rotating
+     */
+    public Log(String filename, int size, int numFiles) {
+      m_Filename = filename;
+      m_Size     = size;
+      m_NumFiles = numFiles;
+    }
+    
+    /**
+     * initializes and returns the logger if necessary (e.g., due to 
+     * serialization).
+     * 
+     * @return		the logger, can be null, e.g., if no filename provided
+     */
+    protected Logger getLogger() {
+      if ( (m_Logger == null) && (!m_LoggerInitFailed) ) {
+	if (m_Filename != null) {
+	  m_Logger = Logger.getLogger(m_Filename);
+	  Handler fh = null;
+	  try{	     
+	    fh = new FileHandler(m_Filename, m_Size, m_NumFiles);
+	    fh.setFormatter(new SimpleFormatter());
+	    m_Logger.addHandler(fh);      
+	    m_LoggerInitFailed = false;
+	  }
+	  catch(Exception e) {
+	    System.out.println("Cannot init fileHandler for logger:" + e.toString());
+	    m_Logger = null;
+	    m_LoggerInitFailed = true;
+	  }  
+	}
+      }
+      
+      return m_Logger;
+    }
+    
+    /**
+     * turns the string representing a level, e.g., "FINE" or "ALL" into
+     * the corresponding level (case-insensitive). The default is ALL.
+     * 
+     * @param level	the string to return a level for
+     * @return		the corresponding level or the default
+     */
+    public static Level stringToLevel(String level) {
+      Level	result;
+      
+      if (level.equalsIgnoreCase("ALL"))
+        result = ALL;
+      else if (level.equalsIgnoreCase("CONFIG"))
+        result = CONFIG;
+      else if (level.equalsIgnoreCase("FINE"))
+        result = FINE;
+      else if (level.equalsIgnoreCase("FINER"))
+        result = FINER;
+      else if (level.equalsIgnoreCase("FINEST"))
+        result = FINEST;
+      else if (level.equalsIgnoreCase("INFO"))
+        result = INFO;
+      else if (level.equalsIgnoreCase("OFF"))
+        result = OFF;
+      else if (level.equalsIgnoreCase("SEVERE"))
+        result = SEVERE;
+      else if (level.equalsIgnoreCase("WARNING"))
+        result = WARNING;
+      else
+        result = ALL;
+      
+      return result;
+    }
+    
+    /**
+     * returns the filename of the log, can be null
+     * 
+     * @return		the filename of the log
+     */
+    public String getFilename() {
+      return m_Filename;
+    }
+    
+    /**
+     * returns the size of the files
+     * 
+     * @return		the size of a file
+     */
+    public int getSize() {
+      return m_Size;
+    }
+    
+    /**
+     * returns the number of files being used
+     * 
+     * @return		the number of files
+     */
+    public int getNumFiles() {
+      return m_NumFiles;
+    }
+
+    /**
+     * logs the given message
+     * 
+     * @param level	the level of severity
+     * @param message	the message to log
+     */
+    public void log(Level level, String message) {
+      log(level, "", message);
+    }
+    
+    /**
+     * prints the given message with the specified level
+     * 
+     * @param level	the level of logging
+     * @param sourceclass	the class that logs the message
+     * @param message	the message to print
+     */
+    public void log(Level level, String sourceclass, String message) {
+      log(level, sourceclass, "", message);
+    }
+    
+    /**
+     * prints the given message with the specified level
+     * 
+     * @param level		the level of logging
+     * @param sourceclass		the class that logs the message
+     * @param sourcemethod	the method that logs the message
+     * @param message		the message to print
+     */
+    public void log(Level level, String sourceclass, String sourcemethod, String message) {
+      Logger	logger;
+      
+      logger = getLogger();
+      
+      if (logger != null)
+        logger.logp(level, sourceclass, sourcemethod, message);
+      else
+	System.out.println(message);
+    }
+    
+    /**
+     * a convenience method for dumping the current system info in the 
+     * log file
+     * 
+     * @see SystemInfo
+     */
+    public void logSystemInfo() {
+      log(INFO, "SystemInfo:\n" + new SystemInfo().toString());
+    }
+    
+    /**
+     * returns a string representation of the logger
+     * 
+     * @return		a string representation of the logger
+     */
+    public String toString() {
+      String	result;
+      
+      result =   "Filename: " + getFilename() + ", "
+      	       + "Size: " + getSize() + ", "
+      	       + "# Files: " + getNumFiles();
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+
+  /**
+   * This extended Random class enables one to print the generated random
+   * numbers etc., before they are returned. It can either use stdout (default)
+   * for outputting the logging information or a Log object (level is then 
+   * INFO).
+   *
+   * @author  FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class Random
+    extends java.util.Random
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = 1256846887618333956L;
+
+    /** whether to output debug information */
+    protected boolean m_Debug = false;
+
+    /** the unique ID for this number generator */
+    protected long m_ID;
+    
+    /** for keeping track of unique IDs */
+    protected static long m_CurrentID;
+    
+    /** the log to use for outputting the data, otherwise just stdout */
+    protected Log m_Log = null;
+    
+    /**
+     * Creates a new random number generator. With no debugging.
+     */
+    public Random() {
+      this(false);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     * With no debugging
+     * 
+     * @param seed	the seed value
+     */
+    public Random(long seed) {
+      this(seed, false);
+    }
+
+    /**
+     * Creates a new random number generator. With optional debugging.
+     * 
+     * @param debug	if true, debugging output is enabled
+     */
+    public Random(boolean debug) {
+      super();
+      setDebug(debug);
+      m_ID = nextID();
+      if (getDebug())
+        printStackTrace();
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     * With optional debugging
+     * 
+     * @param seed	the seed value
+     * @param debug	if true, debugging output is enabled
+     */
+    public Random(long seed, boolean debug) {
+      super(seed);
+      setDebug(debug);
+      m_ID = nextID();
+      if (getDebug())
+        printStackTrace();
+    }
+
+    /**
+     * sets whether to print the generated random values or not
+     * 
+     * @param value	if true debugging output is enabled
+     */
+    public void setDebug(boolean value) {
+      m_Debug = value;
+    }
+
+    /**
+     * returns whether to print the generated random values or not
+     * 
+     * @return		true if debugging output is enabled
+     */
+    public boolean getDebug() {
+      return m_Debug;
+    }
+    
+    /**
+     * the log to use, if it is null then stdout is used
+     * 
+     * @param value	the log to use
+     */
+    public void setLog(Log value) {
+      m_Log = value;
+    }
+    
+    /**
+     * the currently used log, if null then stdout is used for outputting
+     * the debugging information
+     * 
+     * @return		the log, can be null
+     */
+    public Log getLog() {
+      return m_Log;
+    }
+
+    /**
+     * returns the next unique ID for a number generator
+     * 
+     * @return		the next unique ID
+     */
+    protected static long nextID() {
+      m_CurrentID++;
+      
+      return m_CurrentID;
+    }
+
+    /**
+     * returns the unique ID of this number generator
+     * 
+     * @return		the unique ID of this number generator
+     */
+    public long getID() {
+      return m_ID;
+    }
+
+    /**
+     * prints the given message only if m_Debug is TRUE
+     * 
+     * @param msg	the message to print
+     * @see 		#m_Debug
+     */
+    protected void println(String msg) {
+      if (getDebug()) {
+	if (getLog() != null)
+	  getLog().log(Level.INFO, m_ID + ": " + msg);
+	else
+	  System.out.println(m_ID + ": " + msg);
+      }
+    }
+
+    /**
+     * prints the current stacktrace
+     */
+    public void printStackTrace() {
+      Throwable		t;
+      StringWriter	writer;
+
+      writer = new StringWriter();
+      
+      // generate stacktrace
+      t = new Throwable();
+      t.fillInStackTrace();
+      t.printStackTrace(new PrintWriter(writer));
+
+      println(writer.toString());
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed boolean value from
+     * this random number generator's sequence.
+     * 
+     * @return		random boolean
+     */
+    public boolean nextBoolean() {
+      boolean result = super.nextBoolean();
+      println("nextBoolean=" + result);
+      return result;
+    }
+
+    /**
+     * Generates random bytes and places them into a user-supplied byte array.
+     * 
+     * @param bytes	array to fill with random bytes
+     */
+    public void nextBytes(byte[] bytes) {
+      super.nextBytes(bytes);
+      println("nextBytes=" + Utils.arrayToString(bytes));
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed double value between
+     * 0.0 and 1.0 from this random number generator's sequence.
+     * 
+     * @return		random double
+     */
+    public double nextDouble() {
+      double result = super.nextDouble();
+      println("nextDouble=" + result);
+      return result;
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed float  value between
+     * 0.0 and 1.0 from this random number generator's sequence.
+     * 
+     * @return		random float
+     */
+    public float nextFloat() {
+      float result = super.nextFloat();
+      println("nextFloat=" + result);
+      return result;
+    }
+
+    /**
+     * Returns the next pseudorandom, Gaussian ("normally") distributed double
+     * value with mean 0.0 and standard deviation 1.0 from this random number
+     * generator's sequence.
+     * 
+     * @return		random double, gaussian distributed
+     */
+    public double nextGaussian() {
+      double result = super.nextGaussian();
+      println("nextGaussian=" + result);
+      return result;
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed int  value from this
+     * random number generator's sequence.
+     * 
+     * @return		random int
+     */
+    public int nextInt() {
+      int result = super.nextInt();
+      println("nextInt=" + result);
+      return result;
+    }
+
+    /**
+     * Returns a pseudorandom, uniformly distributed int value between 0
+     * (inclusive) and the specified value (exclusive), drawn from this random
+     * number generator's sequence.
+     * 
+     * @param n		the upper limit (exclusive)
+     * @return		random int
+     */
+    public int nextInt(int n) {
+      int result = super.nextInt(n);
+      println("nextInt(" + n + ")=" + result);
+      return result;
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed long  value from this
+     * random number generator's sequence.
+     * 
+     * @return		random long
+     */
+    public long nextLong() {
+      long result = super.nextLong();
+      println("nextLong=" + result);
+      return result;
+    }
+
+    /**
+     * Sets the seed of this random number generator using a single long seed.
+     * 
+     * @param seed	the seed value
+     */
+    public void setSeed(long seed) {
+      super.setSeed(seed);
+      println("setSeed(" + seed + ")");
+    }
+
+    /**
+     * returns a string representation of this number generator
+     * 
+     * @return		a string representation
+     */
+    public String toString() {
+      return this.getClass().getName() + ": " + getID();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  /**
+   * contains debug methods
+   *
+   * @author Gabi Schmidberger (gabi at cs dot waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class DBO 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -5245628124742606784L;  
+
+    /** enables/disables output of debug information */
+    public boolean m_verboseOn = false;
+
+    /** range of outputtyp */
+    public Range m_outputTypes = new Range();
+
+    /** 
+     * Set the verbose on flag on
+     */
+    public void setVerboseOn() {
+      m_verboseOn = true;
+    }
+
+    /** 
+     * Initialize ranges, upper limit must be set
+     * 
+     * @param upper upper limit
+     */
+    public void initializeRanges(int upper) {
+      m_outputTypes.setUpper(upper);
+    }
+
+    /**
+     * Return true if the outputtype is set
+     * 
+     * @param num value that is reserved for a specific outputtype
+     * @return return true if the output type is set
+     */
+    public boolean outputTypeSet(int num) {
+      return (m_outputTypes.isInRange(num));
+    }
+
+     /**
+     * Return true if the debug level is set
+     * same method as outpuTypeSet but better name
+     * 
+     * @param num value that is reserved for a specific outputtype
+     * @return return true if the debug level is set
+     */
+    public boolean dl(int num) {
+      return (outputTypeSet(num));
+    }
+
+   /**
+     * Switches the outputs on that are requested from the option O
+     * 
+     * @param list list of integers, all are used for an output type
+     */
+    public void setOutputTypes(String list) {
+      if (list.length() > 0) {
+        m_verboseOn = true; 
+
+        m_outputTypes.setRanges(list);
+        m_outputTypes.setUpper(30);
+      }
+    }
+
+    /**
+     * Gets the current output type selection
+     *
+     * @return a string containing a comma separated list of ranges
+     */
+    public String getOutputTypes() {
+      return m_outputTypes.getRanges();
+    }
+
+    /**
+     * prints out text + endofline if verbose is on.
+     * helps to make debug output commands more visible in text
+     * 
+     * @param text the text to print
+     */
+    public void dpln(String text) {
+      if (m_verboseOn) {
+        System.out.println(text);
+      }
+    } 
+
+    /**
+     * prints out text + endofline but only if parameter debug type is set.
+     * helps to make debug output commands more visible in text
+     *
+     * @param debugType the type of the output
+     * @param text the text to print
+     */
+    public void dpln(int debugType, String text) {
+      if (outputTypeSet(debugType)) {
+        System.out.println(text);
+      }
+    } 
+
+     /**
+     * prints out text  if verbose is on.
+     * helps to make debug output commands more visible in text
+     * 
+     * @param text the text to print
+     */
+    public void dp(String text) {
+      if (m_verboseOn) {
+        System.out.print(text);
+      }
+    } 
+
+   /**
+     * prints out text but only if debug level is set.
+     * helps to make debug output commands more visible in text
+     *
+     * @param debugType the type of the output
+     * @param text the text to print
+     */
+    public void dp(int debugType, String text) {
+     if (outputTypeSet(debugType)) {
+        System.out.print(text);
+      }
+    } 
+
+    /**
+     * prints out text + endofline.
+     * helps to make debug output commands more visible in text
+     * 
+     * @param text the text to print
+     */
+    public static void pln(String text) {
+      System.out.println(text);
+    } 
+
+    /**
+     * prints out text.
+     * helps to make debug output commands more visible in text
+     * 
+     * @param text the text to print
+     */
+    public static void p (String text) {
+      System.out.print(text);
+    } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * default constructor, prints only to stdout
+   */
+  public Debug() {
+    this(null);
+  }
+  
+  /**
+   * logs the output to the specified file (and stdout). Size is 1,000,000 bytes 
+   * and 1 file.
+   * 
+   * @param filename	the name of the log
+   */
+  public Debug(String filename) {
+    this(filename, 1000000, 1);
+  }
+  
+  /**
+   * logs the output
+   * 
+   * @param filename	the name of the log
+   * @param size	the size of the files in bytes
+   * @param numFiles	the number of files for rotating
+   */
+  public Debug(String filename, int size, int numFiles) {
+    super();
+    
+    m_Log = newLog(filename, size, numFiles);
+  }
+  
+  /**
+   * turns the string representing a level, e.g., "FINE" or "ALL" into
+   * the corresponding level (case-insensitive). The default is ALL.
+   * 
+   * @param level	the string to return a level for
+   * @return		the corresponding level or the default
+   */
+  public static Level stringToLevel(String level) {
+    return Log.stringToLevel(level);
+  }
+  
+  /**
+   * returns a new Log instance
+   * 
+   * @param filename	the name of the log
+   * @param size	the size of the files in bytes
+   * @param numFiles	the number of files for rotating
+   * @return		the log instance
+   */
+  public static Log newLog(String filename, int size, int numFiles) {
+    return new Log(filename, size, numFiles);
+  }
+  
+  /**
+   * prints the given message with level INFO
+   * 
+   * @param message	the message to print
+   */
+  public void log(String message) {
+    log(INFO, message);
+  }
+  
+  /**
+   * prints the given message with the specified level and an empty sourceclass
+   * 
+   * @param level	the level of logging
+   * @param message	the message to print
+   */
+  public void log(Level level, String message) {
+    log(level, "", message);
+  }
+  
+  /**
+   * prints the given message with the specified level
+   * 
+   * @param level	the level of logging
+   * @param sourceclass	the class that logs the message
+   * @param message	the message to print
+   */
+  public void log(Level level, String sourceclass, String message) {
+    log(level, sourceclass, "", message);
+  }
+  
+  /**
+   * prints the given message with the specified level
+   * 
+   * @param level		the level of logging
+   * @param sourceclass		the class that logs the message
+   * @param sourcemethod	the method that logs the message
+   * @param message		the message to print
+   */
+  public void log(Level level, String sourceclass, String sourcemethod, String message) {
+    if (getEnabled())
+      m_Log.log(level, sourceclass, sourcemethod, message);
+  }
+  
+  /**
+   * sets whether the logging is enabled or not
+   * 
+   * @param value	if true logging will be enabled
+   */
+  public void setEnabled(boolean value) {
+    m_Enabled = value;
+  }
+  
+  /**
+   * returns whether the logging is enabled
+   * 
+   * @return		true if the logging is enabled
+   */
+  public boolean getEnabled() {
+    return m_Enabled;
+  }
+  
+  /**
+   * returns a new instance of a clock
+   * 
+   * @return		a new instance of a Clock
+   */
+  public static Clock newClock() {
+    return new Clock();
+  }
+  
+  /**
+   * returns the instance of the Clock that is internally used
+   * 
+   * @return		the clock that's being used
+   */
+  public Clock getClock() {
+    return m_Clock;
+  }
+  
+  /**
+   * starts the clock
+   */
+  public void startClock() {
+    m_Clock.start();
+  }
+  
+  /**
+   * stops the clock and prints the message associated with the time, but only
+   * if the logging is enabled.
+   * 
+   * @param message	the message to print
+   * @see		#getEnabled()
+   */
+  public void stopClock(String message) {
+    log(message + ": " + m_Clock);
+  }
+  
+  /**
+   * returns a default debug random object, with no particular seed and 
+   * debugging enabled.
+   * 
+   * @return		a new instance of a Random object
+   */
+  public static java.util.Random newRandom() {
+    return new Random(true);
+  }
+  
+  /**
+   * returns a debug random object with the specified seed and debugging 
+   * enabled.
+   * 
+   * @param seed	the seed value
+   * @return		a new instance of a Random object
+   */
+  public static java.util.Random newRandom(int seed) {
+    return new Random(seed, true);
+  }
+
+  /**
+   * returns a default timestamp for the current date/time
+   * 
+   * @return		a new timestamp
+   */
+  public static Timestamp newTimestamp() {
+    return new Timestamp();
+  }
+  
+  /**
+   * returns the system temp directory
+   * 
+   * @return		the temp directory
+   */
+  public static String getTempDir() {
+    return System.getProperty("java.io.tmpdir");
+  }
+  
+  /**
+   * returns the home directory of the user
+   * 
+   * @return		the user's home directory
+   */
+  public static String getHomeDir() {
+    return System.getProperty("user.home");
+  }
+  
+  /**
+   * returns the current working directory of the user
+   * 
+   * @return		the user's current working directory
+   */
+  public static String getCurrentDir() {
+    return System.getProperty("user.dir");
+  }
+  
+  /**
+   * Writes the given object to the specified file. The string representation
+   * of the object is appended to the file.
+   * 
+   * @param filename	the file to write to
+   * @param obj		the object to write to the file
+   * @return		true if writing was successful
+   */
+  public static boolean writeToFile(String filename, Object obj) {
+    return writeToFile(filename, obj, true);
+  }
+  
+  /**
+   * Writes the given message to the specified file. The message is appended 
+   * to the file.
+   * 
+   * @param filename	the file to write to
+   * @param message	the message to write
+   * @return		true if writing was successful
+   */
+  public static boolean writeToFile(String filename, String message) {
+    return writeToFile(filename, message, true);
+  }
+  
+  /**
+   * Writes the given object to the specified file. The string representation 
+   * of the object is either appended or replaces the current content of the 
+   * file.
+   * 
+   * @param filename	the file to write to
+   * @param obj		the object to write to the file
+   * @param append	whether to append the message or not
+   * @return		true if writing was successful
+   */
+  public static boolean writeToFile(String filename, Object obj, boolean append) {
+    return writeToFile(filename, obj.toString(), append);
+  }
+  
+  /**
+   * Writes the given message to the specified file. The message is either 
+   * appended or replaces the current content of the file.
+   * 
+   * @param filename	the file to write to
+   * @param message	the message to write
+   * @param append	whether to append the message or not
+   * @return		true if writing was successful
+   */
+  public static boolean writeToFile(String filename, String message, boolean append) {
+    boolean		result;
+    BufferedWriter	writer;
+    
+    try {
+      writer = new BufferedWriter(new FileWriter(filename, append));
+      writer.write(message);
+      writer.newLine();
+      writer.flush();
+      writer.close();
+      result = true;
+    }
+    catch (Exception e) {
+      result = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * writes the serialized object to the speicified file
+   * 
+   * @param filename	the file to serialize the object to
+   * @param o		the object to serialize
+   * @return		true if writing was successful
+   */
+  public static boolean saveToFile(String filename, Object o) {
+    boolean 	result;
+    
+    if (SerializationHelper.isSerializable(o.getClass())) {
+      try {
+	SerializationHelper.write(filename, o);
+	result = true;
+      }
+      catch (Exception e) {
+	result = false;
+      }
+    }
+    else {
+      result = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * deserializes the content of the file and returns it, null if an error
+   * occurred.
+   * 
+   * @param filename	the name of the file to deserialize
+   * @return		the deserialized content, null if problem occurred
+   */
+  public static Object loadFromFile(String filename) {
+    Object	result;
+    
+    try {
+      result = SerializationHelper.read(filename);
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/DenseInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/DenseInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/DenseInstance.java	(revision 29)
@@ -0,0 +1,503 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DenseInstance.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.ArrayList;
+
+/**
+ * Class for handling an instance. All values (numeric, date, nominal, string
+ * or relational) are internally stored as floating-point numbers. If an
+ * attribute is nominal (or a string or relational), the stored value is the
+ * index of the corresponding nominal (or string or relational) value in the
+ * attribute's definition. We have chosen this approach in favor of a more
+ * elegant object-oriented approach because it is much faster. <p>
+ *
+ * Typical usage (code from the main() method of this class): <p>
+ *
+ * <code>
+ * ... <br>
+ *      
+ * // Create empty instance with three attribute values <br>
+ * Instance inst = new DenseInstance(3); <br><br>
+ *     
+ * // Set instance's values for the attributes "length", "weight", and "position"<br>
+ * inst.setValue(length, 5.3); <br>
+ * inst.setValue(weight, 300); <br>
+ * inst.setValue(position, "first"); <br><br>
+ *   
+ * // Set instance's dataset to be the dataset "race" <br>
+ * inst.setDataset(race); <br><br>
+ *   
+ * // Print the instance <br>
+ * System.out.println("The instance: " + inst); <br>
+ *
+ * ... <br>
+ * </code><p>
+ *
+ * All methods that change an instance's attribute values are safe,
+ * ie. a change of an instance's attribute values does not affect any
+ * other instances. All methods that change an instance's attribute
+ * values clone the attribute value vector before it is changed. If
+ * your application heavily modifies instance values, it may be faster
+ * to create a new instance from scratch.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class DenseInstance extends AbstractInstance {
+  
+  /** for serialization */
+  static final long serialVersionUID = 1482635194499365122L;
+
+  /**
+   * Constructor that copies the attribute values and the weight from
+   * the given instance. It does NOT perform a deep copy of the
+   * attribute values if the instance provided is also of type
+   * DenseInstance (it simply copies the reference to the array of
+   * values), otherwise it does. Reference to the dataset is set to
+   * null.  (ie. the instance doesn't have access to information about
+   * the attribute types)
+   *
+   * @param instance the instance from which the attribute
+   * values and the weight are to be copied 
+   */
+  //@ ensures m_Dataset == null;
+  public DenseInstance(/*@non_null@*/ Instance instance) {
+      
+    if (instance instanceof DenseInstance) {
+      m_AttValues = ((DenseInstance)instance).m_AttValues;
+    } else {
+      m_AttValues = instance.toDoubleArray();
+    }
+    m_Weight = instance.weight();
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor that inititalizes instance variable with given
+   * values. Reference to the dataset is set to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   *
+   * @param weight the instance's weight
+   * @param attValues a vector of attribute values 
+   */
+  //@ ensures m_Dataset == null;
+  public DenseInstance(double weight,  /*@non_null@*/ double[]attValues){
+    
+    m_AttValues = attValues;
+    m_Weight = weight;
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor of an instance that sets weight to one, all values to
+   * be missing, and the reference to the dataset to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   *
+   * @param numAttributes the size of the instance 
+   */
+  //@ requires numAttributes > 0;    // Or maybe == 0 is okay too?
+  //@ ensures m_Dataset == null;
+  public DenseInstance(int numAttributes) {
+    
+    m_AttValues = new double[numAttributes];
+    for (int i = 0; i < m_AttValues.length; i++) {
+      m_AttValues[i] = Utils.missingValue();
+    }
+    m_Weight = 1;
+    m_Dataset = null;
+  }
+
+  /**
+   * Produces a shallow copy of this instance. The copy has
+   * access to the same dataset. (if you want to make a copy
+   * that doesn't have access to the dataset, use 
+   * <code>new DenseInstance(instance)</code>
+   *
+   * @return the shallow copy
+   */
+  //@ also ensures \result != null;
+  //@ also ensures \result instanceof DenseInstance;
+  //@ also ensures ((DenseInstance)\result).m_Dataset == m_Dataset;
+  public /*@pure@*/ Object copy() {
+
+    DenseInstance result = new DenseInstance(this);
+    result.m_Dataset = m_Dataset;
+    return result;
+  }
+
+  /**
+   * Returns the index of the attribute stored at the given position.
+   * Just returns the given value.
+   *
+   * @param position the position 
+   * @return the index of the attribute stored at the given position
+   */
+  public /*@pure@*/ int index(int position) {
+
+    return position;
+  }
+
+  /**
+   * Merges this instance with the given instance and returns
+   * the result. Dataset is set to null. The returned instance
+   * is of the same type as this instance.
+   *
+   * @param inst the instance to be merged with this one
+   * @return the merged instances
+   */
+  public Instance mergeInstance(Instance inst) {
+
+    int m = 0;
+    double [] newVals = new double[numAttributes() + inst.numAttributes()];
+    for (int j = 0; j < numAttributes(); j++, m++) {
+      newVals[m] = value(j);
+    }
+    for (int j = 0; j < inst.numAttributes(); j++, m++) {
+      newVals[m] = inst.value(j);
+    }
+    return new DenseInstance(1.0, newVals);
+  }
+
+  /**
+   * Returns the number of attributes.
+   *
+   * @return the number of attributes as an integer
+   */
+  //@ ensures \result == m_AttValues.length;
+  public /*@pure@*/ int numAttributes() {
+
+    return m_AttValues.length;
+  }
+
+  /**
+   * Returns the number of values present. Always the same as numAttributes().
+   *
+   * @return the number of values
+   */
+  //@ ensures \result == m_AttValues.length;
+  public /*@pure@*/ int numValues() {
+
+    return m_AttValues.length;
+  }
+
+  /** 
+   * Replaces all missing values in the instance with the
+   * values contained in the given array. A deep copy of
+   * the vector of attribute values is performed before the
+   * values are replaced.
+   *
+   * @param array containing the means and modes
+   * @throws IllegalArgumentException if numbers of attributes are unequal
+   */
+  public void replaceMissingValues(double[] array) {
+	 
+    if ((array == null) || 
+	(array.length != m_AttValues.length)) {
+      throw new IllegalArgumentException("Unequal number of attributes!");
+    }
+    freshAttributeVector();
+    for (int i = 0; i < m_AttValues.length; i++) {
+      if (isMissing(i)) {
+	m_AttValues[i] = array[i];
+      }
+    }
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param attIndex the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValue(int attIndex, double value) {
+    
+    freshAttributeVector();
+    m_AttValues[attIndex] = value;
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   * Does exactly the same thing as setValue().
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValueSparse(int indexOfIndex, double value) {
+    
+    freshAttributeVector();
+    m_AttValues[indexOfIndex] = value;
+  }
+
+  /**
+   * Returns the values of each attribute as an array of doubles.
+   *
+   * @return an array containing all the instance attribute values
+   */
+  public double[] toDoubleArray() {
+
+    double[] newValues = new double[m_AttValues.length];
+    System.arraycopy(m_AttValues, 0, newValues, 0, 
+		     m_AttValues.length);
+    return newValues;
+  }
+
+  /**
+   * Returns the description of one instance (without weight
+   * appended). If the instance
+   * doesn't have access to a dataset, it returns the internal
+   * floating-point values. Quotes string
+   * values that contain whitespace characters.
+   *
+   * This method is used by getRandomNumberGenerator() in
+   * Instances.java in order to maintain backwards compatibility
+   * with weka 3.4.
+   *
+   * @return the instance's description as a string
+   */
+  public String toStringNoWeight() {
+    StringBuffer text = new StringBuffer();
+    
+    for (int i = 0; i < m_AttValues.length; i++) {
+      if (i > 0) text.append(",");
+      text.append(toString(i));
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   *
+   * @param attIndex the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public /*@pure@*/ double value(int attIndex) {
+
+    return m_AttValues[attIndex];
+  }
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1).
+   *
+   * @param position the attribute's position
+   */
+  protected void forceDeleteAttributeAt(int position) {
+
+    double[] newValues = new double[m_AttValues.length - 1];
+
+    System.arraycopy(m_AttValues, 0, newValues, 0, position);
+    if (position < m_AttValues.length - 1) {
+      System.arraycopy(m_AttValues, position + 1, 
+		       newValues, position, 
+		       m_AttValues.length - (position + 1));
+    }
+    m_AttValues = newValues;
+  }
+
+  /**
+   * Inserts an attribute at the given position
+   * (0 to numAttributes()) and sets its value to be missing. 
+   *
+   * @param position the attribute's position
+   */
+  protected void forceInsertAttributeAt(int position)  {
+
+    double[] newValues = new double[m_AttValues.length + 1];
+
+    System.arraycopy(m_AttValues, 0, newValues, 0, position);
+    newValues[position] = Utils.missingValue();
+    System.arraycopy(m_AttValues, position, newValues, 
+		     position + 1, m_AttValues.length - position);
+    m_AttValues = newValues;
+  }
+
+  /**
+   * Clones the attribute vector of the instance and
+   * overwrites it with the clone.
+   */
+  private void freshAttributeVector() {
+
+    m_AttValues = toDoubleArray();
+  }
+
+  /**
+   * Main method for testing this class.
+   * 
+   * @param options the commandline options - ignored
+   */
+  //@ requires options != null;
+  public static void main(String[] options) {
+
+    try {
+
+      // Create numeric attributes "length" and "weight"
+      Attribute length = new Attribute("length");
+      Attribute weight = new Attribute("weight");
+      
+      // Create vector to hold nominal values "first", "second", "third" 
+      ArrayList<String> my_nominal_values = new ArrayList<String>(3); 
+      my_nominal_values.add("first"); 
+      my_nominal_values.add("second"); 
+      my_nominal_values.add("third"); 
+      
+      // Create nominal attribute "position" 
+      Attribute position = new Attribute("position", my_nominal_values);
+      
+      // Create vector of the above attributes 
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>(3);
+      attributes.add(length);
+      attributes.add(weight);
+      attributes.add(position);
+      
+      // Create the empty dataset "race" with above attributes
+      Instances race = new Instances("race", attributes, 0);
+      
+      // Make position the class attribute
+      race.setClassIndex(position.index());
+      
+      // Create empty instance with three attribute values
+      Instance inst = new DenseInstance(3);
+      
+      // Set instance's values for the attributes "length", "weight", and "position"
+      inst.setValue(length, 5.3);
+      inst.setValue(weight, 300);
+      inst.setValue(position, "first");
+      
+      // Set instance's dataset to be the dataset "race"
+      inst.setDataset(race);
+      
+      // Print the instance
+      System.out.println("The instance: " + inst);
+      
+      // Print the first attribute
+      System.out.println("First attribute: " + inst.attribute(0));
+      
+      // Print the class attribute
+      System.out.println("Class attribute: " + inst.classAttribute());
+      
+      // Print the class index
+      System.out.println("Class index: " + inst.classIndex());
+      
+      // Say if class is missing
+      System.out.println("Class is missing: " + inst.classIsMissing());
+      
+      // Print the instance's class value in internal format
+      System.out.println("Class value (internal format): " + inst.classValue());
+      
+      // Print a shallow copy of this instance
+      Instance copy = (Instance) inst.copy();
+      System.out.println("Shallow copy: " + copy);
+      
+      // Set dataset for shallow copy
+      copy.setDataset(inst.dataset());
+      System.out.println("Shallow copy with dataset set: " + copy);
+      
+      // Unset dataset for copy, delete first attribute, and insert it again
+      copy.setDataset(null);
+      copy.deleteAttributeAt(0);
+      copy.insertAttributeAt(0);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with first attribute deleted and inserted: " + copy); 
+      
+      // Enumerate attributes (leaving out the class attribute)
+      System.out.println("Enumerating attributes (leaving out class):");
+      Enumeration enu = inst.enumerateAttributes();
+      while (enu.hasMoreElements()) {
+	Attribute att = (Attribute) enu.nextElement();
+	System.out.println(att);
+      }
+      
+      // Headers are equivalent?
+      System.out.println("Header of original and copy equivalent: " +
+			 inst.equalHeaders(copy));
+
+      // Test for missing values
+      System.out.println("Length of copy missing: " + copy.isMissing(length));
+      System.out.println("Weight of copy missing: " + copy.isMissing(weight.index()));
+      System.out.println("Length of copy missing: " + 
+			 Utils.isMissingValue(copy.value(length)));
+
+      // Prints number of attributes and classes
+      System.out.println("Number of attributes: " + copy.numAttributes());
+      System.out.println("Number of classes: " + copy.numClasses());
+
+      // Replace missing values
+      double[] meansAndModes = {2, 3, 0};
+      copy.replaceMissingValues(meansAndModes);
+      System.out.println("Copy with missing value replaced: " + copy);
+
+      // Setting and getting values and weights
+      copy.setClassMissing();
+      System.out.println("Copy with missing class: " + copy);
+      copy.setClassValue(0);
+      System.out.println("Copy with class value set to first value: " + copy);
+      copy.setClassValue("third");
+      System.out.println("Copy with class value set to \"third\": " + copy);
+      copy.setMissing(1);
+      System.out.println("Copy with second attribute set to be missing: " + copy);
+      copy.setMissing(length);
+      System.out.println("Copy with length set to be missing: " + copy);
+      copy.setValue(0, 0);
+      System.out.println("Copy with first attribute set to 0: " + copy);
+      copy.setValue(weight, 1);
+      System.out.println("Copy with weight attribute set to 1: " + copy);
+      copy.setValue(position, "second");
+      System.out.println("Copy with position set to \"second\": " + copy);
+      copy.setValue(2, "first");
+      System.out.println("Copy with last attribute set to \"first\": " + copy);
+      System.out.println("Current weight of instance copy: " + copy.weight());
+      copy.setWeight(2);
+      System.out.println("Current weight of instance copy (set to 2): " + copy.weight());
+      System.out.println("Last value of copy: " + copy.toString(2));
+      System.out.println("Value of position for copy: " + copy.toString(position));
+      System.out.println("Last value of copy (internal format): " + copy.value(2));
+      System.out.println("Value of position for copy (internal format): " + 
+			 copy.value(position));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/DistanceFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/DistanceFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/DistanceFunction.java	(revision 29)
@@ -0,0 +1,159 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DistanceFunction.java
+ *    Copyright (C) 1999-2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.neighboursearch.PerformanceStats;
+
+/**
+ * Interface for any class that can compute and return distances between two
+ * instances.
+ *
+ * @author  Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $ 
+ */
+public interface DistanceFunction extends OptionHandler {
+
+  /**
+   * Sets the instances.
+   * 
+   * @param insts 	the instances to use
+   */
+  public void setInstances(Instances insts);
+
+  /**
+   * returns the instances currently set.
+   * 
+   * @return 		the current instances
+   */
+  public Instances getInstances();
+
+  /**
+   * Sets the range of attributes to use in the calculation of the distance.
+   * The indices start from 1, 'first' and 'last' are valid as well. 
+   * E.g.: first-3,5,6-last
+   * 
+   * @param value	the new attribute index range
+   */
+  public void setAttributeIndices(String value);
+  
+  /**
+   * Gets the range of attributes used in the calculation of the distance.
+   * 
+   * @return		the attribute index range
+   */
+  public String getAttributeIndices();
+  
+  /**
+   * Sets whether the matching sense of attribute indices is inverted or not.
+   * 
+   * @param value	if true the matching sense is inverted
+   */
+  public void setInvertSelection(boolean value);
+  
+  /**
+   * Gets whether the matching sense of attribute indices is inverted or not.
+   * 
+   * @return		true if the matching sense is inverted
+   */
+  public boolean getInvertSelection();
+
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @return 		the distance between the two given instances
+   */
+  public double distance(Instance first, Instance second);
+
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param stats 	the performance stats object
+   * @return 		the distance between the two given instances
+   * @throws Exception 	if calculation fails
+   */
+  public double distance(Instance first, Instance second, PerformanceStats stats) 
+      throws Exception;
+
+  /**
+   * Calculates the distance between two instances. Offers speed up (if the 
+   * distance function class in use supports it) in nearest neighbour search by 
+   * taking into account the cutOff or maximum distance. Depending on the 
+   * distance function class, post processing of the distances by 
+   * postProcessDistances(double []) may be required if this function is used.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param cutOffValue If the distance being calculated becomes larger than 
+   *                    cutOffValue then the rest of the calculation is 
+   *                    discarded.
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY if the distance being 
+   * 			calculated becomes larger than cutOffValue. 
+   */
+  public double distance(Instance first, Instance second, double cutOffValue);
+
+  /**
+   * Calculates the distance between two instances. Offers speed up (if the 
+   * distance function class in use supports it) in nearest neighbour search by 
+   * taking into account the cutOff or maximum distance. Depending on the 
+   * distance function class, post processing of the distances by 
+   * postProcessDistances(double []) may be required if this function is used.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param cutOffValue If the distance being calculated becomes larger than 
+   *                    cutOffValue then the rest of the calculation is 
+   *                    discarded.
+   * @param stats 	the performance stats object
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY if the distance being 
+   * 			calculated becomes larger than cutOffValue. 
+   */
+  public double distance(Instance first, Instance second, 
+      double cutOffValue, PerformanceStats stats);
+
+  /**
+   * Does post processing of the distances (if necessary) returned by
+   * distance(distance(Instance first, Instance second, double cutOffValue). It
+   * may be necessary, depending on the distance function, to do post processing
+   * to set the distances on the correct scale. Some distance function classes
+   * may not return correct distances using the cutOffValue distance function to 
+   * minimize the inaccuracies resulting from floating point comparison and 
+   * manipulation.
+   * 
+   * @param distances	the distances to post-process
+   */
+  public void postProcessDistances(double distances[]);
+
+  /**
+   * Update the distance function (if necessary) for the newly added instance.
+   * 
+   * @param ins		the instance to add
+   */
+  public void update(Instance ins);
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/Drawable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Drawable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Drawable.java	(revision 29)
@@ -0,0 +1,61 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Drawable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Interface to something that can be drawn as a graph.
+ *
+ * @author Ashraf M. Kibriya(amk14@cs.waikato.ac.nz), Eibe Frank(eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5961 $
+ */
+public interface Drawable {
+
+  int NOT_DRAWABLE = 0, TREE = 1, BayesNet = 2, Newick = 3;
+
+  /**
+   * Returns the type of graph representing
+   * the object.
+   *
+   * @return the type of graph representing the object
+   */
+  int graphType();
+
+  /**
+   * Returns a string that describes a graph representing
+   * the object. The string should be in XMLBIF ver.
+   * 0.3 format if the graph is a BayesNet, otherwise
+   * it should be in dotty format.
+   *
+   * @return the graph described by a string
+   * @exception Exception if the graph can't be computed
+   */
+  String graph() throws Exception;
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/EditDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/EditDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/EditDistance.java	(revision 29)
@@ -0,0 +1,103 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractStringDistanceFunction.java
+ *    Copyright (C) 2008 Bruno Woltzenlogel Paleo (http://www.logic.at/people/bruno/ ; http://bruno-wp.blogspot.com/)
+ *
+ */
+
+package weka.core;
+
+/**
+ * Computes the Levenshtein edit distance between two strings.
+ *
+ * @author Bruno Woltzenlogel Paleo
+ * @version $Revision: 5987 $
+ */
+public class EditDistance
+    extends AbstractStringDistanceFunction {
+
+  public EditDistance() {
+  }
+
+  public EditDistance(Instances data) {
+    super(data);
+  }
+
+  /**
+   * Calculates the distance (Levenshtein Edit Distance) between two strings
+   *
+   * @param stringA the first string
+   * @param stringB the second string
+   * @return the distance between the two given strings
+   */
+  double stringDistance(String stringA, String stringB) {
+    int lengthA = stringA.length();
+    int lengthB = stringB.length();
+
+    double[][] distanceMatrix = new double[lengthA + 1][lengthB + 1];
+
+    for (int i = 0; i <= lengthA; i++) {
+      distanceMatrix[i][0] = i;
+    }
+
+    for (int j = 1; j <= lengthB; j++) {
+      distanceMatrix[0][j] = j;
+    }
+
+    for (int i = 1; i <= lengthA; i++) {
+      for (int j = 1; j <= lengthB; j++) {
+        if (stringA.charAt(i - 1) == stringB.charAt(j - 1)) {
+          distanceMatrix[i][j] = distanceMatrix[i - 1][j - 1];
+        }
+        else {
+          distanceMatrix[i][j] = 1 + Math.min(distanceMatrix[i - 1][j],
+                                              Math.min(distanceMatrix[i][j - 1],
+                                                       distanceMatrix[i - 1][j - 1]));
+        }
+      }
+    }
+    return distanceMatrix[lengthA][lengthB];
+  }
+
+    
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+      "Implementing Levenshtein distance function.\n\n"
+      + "One object defines not one distance but the data model in which "
+      + "the distances between objects of that data model can be computed.\n\n"
+      + "Attention: For efficiency reasons the use of consistency checks "
+      + "(like are the data models of the two instances exactly the same), "
+      + "is low.\n\n"
+      + "For more information, see: http://en.wikipedia.org/wiki/Levenshtein_distance\n\n";
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Environment.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Environment.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Environment.java	(revision 29)
@@ -0,0 +1,197 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Environment.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * This class encapsulates a map of all environment and java system properties.
+ * There are methods for adding and removing variables as well as a method for
+ * replacing key names (enclosed by ${}) with their associated value in Strings.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class Environment implements RevisionHandler {
+  
+  private static Environment m_systemWide = new Environment();
+  
+  // Map to hold all the system environment variables + java properties
+  private Map<String,String> m_envVars = new TreeMap<String,String>();
+  
+  public Environment() {
+    // get the env variables first
+    Map<String,String> env = System.getenv();
+    Set<String> keys = env.keySet();
+    Iterator<String> i = keys.iterator();
+    while (i.hasNext()) {
+      String kv = i.next();
+      String value = env.get(kv);
+      m_envVars.put(kv, value);
+    }
+
+    // get the java properties
+    Properties jvmProps = System.getProperties();
+    Enumeration pKeys = jvmProps.propertyNames();
+    while (pKeys.hasMoreElements()) {
+      String kv = (String)pKeys.nextElement();
+      String value = jvmProps.getProperty(kv);
+      m_envVars.put(kv, value);
+    }
+    m_envVars.put("weka.version", Version.VERSION);
+  }
+  
+  /**
+   * Get the singleton system-wide (visible to every
+   * class in the running VM) set of environment
+   * variables.
+   * 
+   * @return the system-wide set of environment variables.
+   */
+  public static Environment getSystemWide() {
+    return m_systemWide;
+  }
+  
+  /**
+   * Tests for the presence of environment variables.
+   * 
+   * @param source the string to test
+   * @return true if the argument contains one or more environment
+   * variables
+   */
+  public static boolean containsEnvVariables(String source) {
+    return (source.indexOf("${") >= 0);
+  }
+
+  /**
+   * Substitute a variable names for their values in the given string.
+   * 
+   * @param source the source string to replace variables in
+   * @return a String with all variable names replaced with their values
+   * @throws Exception if an unknown variable name is encountered
+   */
+  public String substitute(String source) throws Exception {
+    // Grab each variable out of the string
+    int index = source.indexOf("${");
+
+    while (index >= 0) {
+      index += 2;
+      int endIndex = source.indexOf('}');
+      if (endIndex >= 0 && endIndex > index +1) {
+        String key = source.substring(index, endIndex);
+
+        // look this sucker up
+        String replace = m_envVars.get(key);
+        if (replace != null) {
+          String toReplace = "${" + key + "}";
+          source = source.replace(toReplace, replace);
+        } else {
+          throw new Exception("[Environment] Variable " 
+                              + key + " doesn't seem to be set.");
+        }
+      } else {
+        break;
+      }
+      index = source.indexOf("${");
+    }
+    return source;
+  }
+
+  /**
+   * Add a variable to the internal map.
+   *
+   * @param key the name of the variable
+   * @param value its value
+   */
+  public void addVariable(String key, String value) {
+    m_envVars.put(key, value);
+  }
+
+  /**
+   * Remove a named variable from the map.
+   *
+   * @param key the name of the varaible to remove.
+   */
+  public void removeVariable(String key) {
+    m_envVars.remove(key);
+  }
+  
+  /**
+   * Get the names of the variables (keys) stored in the 
+   * internal map.
+   * 
+   * @return a Set of variable names (keys)
+   */
+  public Set<String> getVariableNames() {
+    return m_envVars.keySet();
+  }
+
+  /**
+   * Get the value for a particular variable.
+   *
+   * @param key the name of the variable to get
+   * @return the associated value or null if this variable
+   * is not in the internal map
+   */
+  public String getVariableValue(String key) {
+    return m_envVars.get(key);
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param args a list of strings to replace variables in 
+   * (e.g. "\${os.name} "\${java.version}")
+   */
+  public static void main(String[] args) {
+    Environment t = new Environment();
+    //    String test = "Here is a string with the variable ${java.version} and ${os.name} in it";
+
+    if (args.length == 0) {
+      System.err.println("Usage: java weka.core.Environment <string> <string> ...");
+    } else {
+      try {
+        for (int i = 0; i < args.length; i++) {
+          String newS = t.substitute(args[i]);
+          System.out.println("Original string:\n" + args[i] +"\n\nNew string:\n" + newS);
+        }
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/EnvironmentHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/EnvironmentHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/EnvironmentHandler.java	(revision 29)
@@ -0,0 +1,46 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * EnvironmentHandler.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.core.Environment;
+
+/**
+ * Interface for something that can utilize environment
+ * variables. NOTE: since environment variables should
+ * be transient, the implementer needs to be careful
+ * of state after de-serialization. Default system-wide
+ * environment variables can be got via a call to
+ * <code>weka.core.Environment.getSystemWide()</code>
+ * 
+ * @author mhall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public interface EnvironmentHandler {
+  
+  /**
+   * Set environment variables to use.
+   * 
+   * @param env the environment variables to
+   * use
+   */
+  void setEnvironment(Environment env);
+}
Index: branches/MetisMQI/src/main/java/weka/core/EuclideanDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/EuclideanDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/EuclideanDistance.java	(revision 29)
@@ -0,0 +1,274 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EuclideanDistance.java
+ *    Copyright (C) 1999-2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.neighboursearch.PerformanceStats;
+
+/**
+ <!-- globalinfo-start -->
+ * Implementing Euclidean distance (or similarity) function.<br/>
+ * <br/>
+ * One object defines not one distance but the data model in which the distances between objects of that data model can be computed.<br/>
+ * <br/>
+ * Attention: For efficiency reasons the use of consistency checks (like are the data models of the two instances exactly the same), is low.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Wikipedia. Euclidean distance. URL http://en.wikipedia.org/wiki/Euclidean_distance.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{missing_id,
+ *    author = {Wikipedia},
+ *    title = {Euclidean distance},
+ *    URL = {http://en.wikipedia.org/wiki/Euclidean_distance}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns off the normalization of attribute 
+ *  values in distance calculation.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to used in the calculation of the 
+ *  distance. 'first' and 'last' are valid indices.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indices.</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class EuclideanDistance
+  extends NormalizableDistance
+  implements Cloneable, TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1068606253458807903L;
+
+  /**
+   * Constructs an Euclidean Distance object, Instances must be still set.
+   */
+  public EuclideanDistance() {
+    super();
+  }
+
+  /**
+   * Constructs an Euclidean Distance object and automatically initializes the
+   * ranges.
+   * 
+   * @param data 	the instances the distance function should work on
+   */
+  public EuclideanDistance(Instances data) {
+    super(data);
+  }
+
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implementing Euclidean distance (or similarity) function.\n\n"
+      + "One object defines not one distance but the data model in which "
+      + "the distances between objects of that data model can be computed.\n\n"
+      + "Attention: For efficiency reasons the use of consistency checks "
+      + "(like are the data models of the two instances exactly the same), "
+      + "is low.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Wikipedia");
+    result.setValue(Field.TITLE, "Euclidean distance");
+    result.setValue(Field.URL, "http://en.wikipedia.org/wiki/Euclidean_distance");
+
+    return result;
+  }
+  
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @return 		the distance between the two given instances
+   */
+  public double distance(Instance first, Instance second) {
+    return Math.sqrt(distance(first, second, Double.POSITIVE_INFINITY));
+  }
+  
+  /**
+   * Calculates the distance (or similarity) between two instances. Need to
+   * pass this returned distance later on to postprocess method to set it on
+   * correct scale. <br/>
+   * P.S.: Please don't mix the use of this function with
+   * distance(Instance first, Instance second), as that already does post
+   * processing. Please consider passing Double.POSITIVE_INFINITY as the cutOffValue to
+   * this function and then later on do the post processing on all the
+   * distances.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param stats 	the structure for storing performance statistics.
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY.
+   */
+  public double distance(Instance first, Instance second, PerformanceStats stats) { //debug method pls remove after use
+    return Math.sqrt(distance(first, second, Double.POSITIVE_INFINITY, stats));
+  }
+  
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected double updateDistance(double currDist, double diff) {
+    double	result;
+    
+    result  = currDist;
+    result += diff * diff;
+    
+    return result;
+  }
+  
+  /**
+   * Does post processing of the distances (if necessary) returned by
+   * distance(distance(Instance first, Instance second, double cutOffValue). It
+   * is necessary to do so to get the correct distances if
+   * distance(distance(Instance first, Instance second, double cutOffValue) is
+   * used. This is because that function actually returns the squared distance
+   * to avoid inaccuracies arising from floating point comparison.
+   * 
+   * @param distances	the distances to post-process
+   */
+  public void postProcessDistances(double distances[]) {
+    for(int i = 0; i < distances.length; i++) {
+      distances[i] = Math.sqrt(distances[i]);
+    }
+  }
+  
+  /**
+   * Returns the squared difference of two values of an attribute.
+   * 
+   * @param index	the attribute index
+   * @param val1	the first value
+   * @param val2	the second value
+   * @return		the squared difference
+   */
+  public double sqDifference(int index, double val1, double val2) {
+    double val = difference(index, val1, val2);
+    return val*val;
+  }
+  
+  /**
+   * Returns value in the middle of the two parameter values.
+   * 
+   * @param ranges 	the ranges to this dimension
+   * @return 		the middle value
+   */
+  public double getMiddle(double[] ranges) {
+
+    double middle = ranges[R_MIN] + ranges[R_WIDTH] * 0.5;
+    return middle;
+  }
+  
+  /**
+   * Returns the index of the closest point to the current instance.
+   * Index is index in Instances object that is the second parameter.
+   *
+   * @param instance 	the instance to assign a cluster to
+   * @param allPoints 	all points
+   * @param pointList 	the list of points
+   * @return 		the index of the closest point
+   * @throws Exception	if something goes wrong
+   */
+  public int closestPoint(Instance instance, Instances allPoints,
+      			  int[] pointList) throws Exception {
+    double minDist = Integer.MAX_VALUE;
+    int bestPoint = 0;
+    for (int i = 0; i < pointList.length; i++) {
+      double dist = distance(instance, allPoints.instance(pointList[i]), Double.POSITIVE_INFINITY);
+      if (dist < minDist) {
+        minDist = dist;
+        bestPoint = i;
+      }
+    }
+    return pointList[bestPoint];
+  }
+  
+  /**
+   * Returns true if the value of the given dimension is smaller or equal the
+   * value to be compared with.
+   * 
+   * @param instance 	the instance where the value should be taken of
+   * @param dim 	the dimension of the value
+   * @param value 	the value to compare with
+   * @return 		true if value of instance is smaller or equal value
+   */
+  public boolean valueIsSmallerEqual(Instance instance, int dim,
+      				     double value) {  //This stays
+    return instance.value(dim) <= value;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/FastVector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/FastVector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/FastVector.java	(revision 29)
@@ -0,0 +1,217 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FastVector.java
+ *    Copyright (C) 1999, 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Collection;
+import java.util.ArrayList;
+
+/**
+ * Simple extension of ArrayList. Exists for legacy reasons.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+@Deprecated 
+public class FastVector<E> extends ArrayList<E> implements Copyable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2173635135622930169L;
+
+  /**
+   * Constructs an empty vector with initial
+   * capacity zero.
+   */
+  public FastVector() {
+    super();
+  }
+
+  /**
+   * Constructs a vector with the given capacity.
+   *
+   * @param capacity the vector's initial capacity
+   */
+  public FastVector(int capacity) {
+    super(capacity);
+  }
+
+  /**
+   * Adds an element to this vector. Increases its
+   * capacity if its not large enough.
+   *
+   * @param element the element to add
+   */
+  public final void addElement(E element) {
+    add(element);
+  }
+
+  /**
+   * Produces a shallow copy of this vector.
+   *
+   * @return the new vector
+   */
+  public final FastVector<E> copy() {
+    return Utils.cast(clone());
+  }
+
+  /**
+   * Clones the vector and shallow copies all its elements.
+   * The elements have to implement the Copyable interface.
+   * 
+   * @return the new vector
+   */
+  public final FastVector<E> copyElements() {
+
+    FastVector<E> copy = copy();
+    for (int i = 0; i < size(); i++) {
+      copy.set(i, Utils.<E>cast(((Copyable)get(i)).copy()));
+    }
+    return copy;
+  }
+
+  /**
+   * Returns the element at the given position.
+   *
+   * @param index the element's index
+   * @return the element with the given index
+   */
+  public final E elementAt(int index) {
+    return get(index);
+  }
+
+  /**
+   * Returns an enumeration of this vector.
+   *
+   * @return an enumeration of this vector
+   */
+  public final Enumeration elements() {
+    return new WekaEnumeration(this);
+  }
+
+  /**
+   * Returns an enumeration of this vector, skipping the
+   * element with the given index.
+   *
+   * @param index the element to skip
+   * @return an enumeration of this vector
+   */
+  public final Enumeration elements(int index) {
+    return new WekaEnumeration(this, index);
+  }
+
+  /**
+   * Returns the first element of the vector.
+   *
+   * @return the first element of the vector
+   */
+  public final  E firstElement() {
+    return get(0);
+  }
+
+  /**
+   * Inserts an element at the given position.
+   *
+   * @param element the element to be inserted
+   * @param index the element's index
+   */
+  public final void insertElementAt(E element, int index) {
+    add(index, element);
+  }
+
+  /**
+   * Returns the last element of the vector.
+   *
+   * @return the last element of the vector
+   */
+  public final E lastElement() {
+    return get(size() - 1);
+  }
+
+  /**
+   * Deletes an element from this vector.
+   *
+   * @param index the index of the element to be deleted
+   */
+  public final void removeElementAt(int index) {
+    remove(index);
+  }
+
+  /**
+   * Removes all components from this vector and sets its 
+   * size to zero. 
+   */
+  public final void removeAllElements() {
+    clear();
+  }
+
+  /**
+   * Appends all elements of the supplied vector to this vector.
+   *
+   * @param toAppend the FastVector containing elements to append.
+   */
+  public final void appendElements(Collection<? extends E> toAppend) {
+    addAll(toAppend);
+  }
+
+  /**
+   * Sets the vector's capacity to the given value.
+   *
+   * @param capacity the new capacity
+   */
+  public final void setCapacity(int capacity) {
+    ensureCapacity(capacity);
+  }
+
+  /**
+   * Sets the element at the given index.
+   *
+   * @param element the element to be put into the vector
+   * @param index the index at which the element is to be placed
+   */
+  public final void setElementAt(E element, int index) {
+    set(index, element);
+  }
+
+  /**
+   * Swaps two elements in the vector.
+   *
+   * @param first index of the first element
+   * @param second index of the second element
+   */
+  public final void swap(int first, int second) {
+    
+    E in = get(first);
+    set(first, get(second));
+    set(second, in);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/FindWithCapabilities.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/FindWithCapabilities.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/FindWithCapabilities.java	(revision 29)
@@ -0,0 +1,988 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * FindWithCapabilities.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.core.Capabilities.Capability;
+import weka.gui.GenericPropertiesCreator;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Locates all classes with certain capabilities. One should keep in mind, 
+ * that works only with the default capabilities of a scheme and doesn't
+ * take dependencies into account. E.g., a meta-classifier that could have
+ * a base classifier handling numeric classes, but by default uses one with
+ * a nominal class, will never show up in a search for schemes that handle
+ * numeric classes.<p/>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> All class and attribute options can be prefixed with 'not',
+ * e.g., '-not-numeric-class'. This makes sure that the returned
+ * schemes 'cannot' handle numeric classes.
+ * </pre>
+ * 
+ * <pre> -num-instances &lt;num&gt;
+ *  The minimum number of instances (default 1).</pre>
+ * 
+ * <pre> -unary-class
+ *  Must handle unray classes.</pre>
+ * 
+ * <pre> -binary-class
+ *  Must handle binary classes.</pre>
+ * 
+ * <pre> -nominal-class
+ *  Must handle nominal classes.</pre>
+ * 
+ * <pre> -numeric-class
+ *  Must handle numeric classes.</pre>
+ * 
+ * <pre> -string-class
+ *  Must handle string classes.</pre>
+ * 
+ * <pre> -date-class
+ *  Must handle date classes.</pre>
+ * 
+ * <pre> -relational-class
+ *  Must handle relational classes.</pre>
+ * 
+ * <pre> -missing-class-values
+ *  Must handle missing class values.</pre>
+ * 
+ * <pre> -no-class
+ *  Doesn't need a class.</pre>
+ * 
+ * <pre> -unary-atts
+ *  Must handle unary attributes.</pre>
+ * 
+ * <pre> -binary-atts
+ *  Must handle binary attributes.</pre>
+ * 
+ * <pre> -nominal-atts
+ *  Must handle nominal attributes.</pre>
+ * 
+ * <pre> -numeric-atts
+ *  Must handle numeric attributes.</pre>
+ * 
+ * <pre> -string-atts
+ *  Must handle string attributes.</pre>
+ * 
+ * <pre> -date-atts
+ *  Must handle date attributes.</pre>
+ * 
+ * <pre> -relational-atts
+ *  Must handle relational attributes.</pre>
+ * 
+ * <pre> -missing-att-values
+ *  Must handle missing attribute values.</pre>
+ * 
+ * <pre> -only-multiinstance
+ *  Must handle multi-instance data.</pre>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The Capabilities handler to base the handling on.
+ *  The other parameters can be used to override the ones
+ *  determined from the handler. Additional parameters for
+ *  handler can be passed on after the '--'.
+ *  Either '-W' or '-t' can be used.</pre>
+ * 
+ * <pre> -t &lt;file&gt;
+ *  The dataset to base the capabilities on.
+ *  The other parameters can be used to override the ones
+ *  determined from the handler.
+ *  Either '-t' or '-W' can be used.</pre>
+ * 
+ * <pre> -c &lt;num&gt;
+ *  The index of the class attribute, -1 for none.
+ *  'first' and 'last' are also valid.
+ *  Only in conjunction with option '-t'.</pre>
+ * 
+ * <pre> -superclass
+ *  Superclass to look for in the packages.
+ * </pre>
+ * 
+ * <pre> -packages
+ *  Comma-separated list of packages to search in.</pre>
+ * 
+ * <pre> -generic
+ *  Retrieves the package list from the GenericPropertiesCreator
+ *  for the given superclass. (overrides -packages &lt;list&gt;).</pre>
+ * 
+ * <pre> -misses
+ *  Also prints the classname that didn't match the criteria.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Capabilities
+ * @see Capabilities.Capability
+ * @see GenericPropertiesCreator
+ */
+public class FindWithCapabilities 
+implements OptionHandler, CapabilitiesHandler, RevisionHandler {
+
+  /** the capabilities to look for. */
+  protected Capabilities m_Capabilities = new Capabilities(this);
+
+  /** the capabilities to look for to "not have". */
+  protected Capabilities m_NotCapabilities = new Capabilities(this);
+
+  /** the packages to search in. */
+  protected Vector<String> m_Packages = new Vector<String>();
+
+  /** a capabilities handler to retrieve the capabilities from. */
+  protected CapabilitiesHandler m_Handler = null;
+
+  /** a file the capabilities can be based on. */
+  protected String m_Filename = "";
+
+  /** the class index, in case the capabilities are based on a file. */
+  protected SingleIndex m_ClassIndex = new SingleIndex();
+
+  /** the superclass from the GenericPropertiesCreator to retrieve the packages from. */
+  protected String m_Superclass = "";
+
+  /** whether to use the GenericPropertiesCreator with the superclass. */
+  protected boolean m_GenericPropertiesCreator = false;
+
+  /** the classes that matched. */
+  protected Vector<String> m_Matches = new Vector<String>();
+
+  /** the class that didn't match. */
+  protected Vector<String> m_Misses = new Vector<String>();
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+
+    result.addElement(new Option(
+	"", "", 0, 
+	"All class and attribute options can be prefixed with 'not',\n"
+	+ "e.g., '-not-numeric-class'. This makes sure that the returned\n"
+	+ "schemes 'cannot' handle numeric classes."));
+
+    result.addElement(new Option(
+	"\tThe minimum number of instances (default 1).",
+	"num-instances", 1, "-num-instances <num>"));
+
+    result.addElement(new Option(
+	"\tMust handle unray classes.",
+	"unary-class", 0, "-unary-class"));
+
+    result.addElement(new Option(
+	"\tMust handle binary classes.",
+	"binary-class", 0, "-binary-class"));
+
+    result.addElement(new Option(
+	"\tMust handle nominal classes.",
+	"nominal-class", 0, "-nominal-class"));
+
+    result.addElement(new Option(
+	"\tMust handle numeric classes.",
+	"numeric-class", 0, "-numeric-class"));
+
+    result.addElement(new Option(
+	"\tMust handle string classes.",
+	"string-class", 0, "-string-class"));
+
+    result.addElement(new Option(
+	"\tMust handle date classes.",
+	"date-class", 0, "-date-class"));
+
+    result.addElement(new Option(
+	"\tMust handle relational classes.",
+	"relational-class", 0, "-relational-class"));
+
+    result.addElement(new Option(
+	"\tMust handle missing class values.",
+	"missing-class-values", 0, "-missing-class-values"));
+
+    result.addElement(new Option(
+	"\tDoesn't need a class.",
+	"no-class", 0, "-no-class"));
+
+    result.addElement(new Option(
+	"\tMust handle unary attributes.",
+	"unary-atts", 0, "-unary-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle binary attributes.",
+	"binary-atts", 0, "-binary-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle nominal attributes.",
+	"nominal-atts", 0, "-nominal-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle numeric attributes.",
+	"numeric-atts", 0, "-numeric-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle string attributes.",
+	"string-atts", 0, "-string-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle date attributes.",
+	"date-atts", 0, "-date-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle relational attributes.",
+	"relational-atts", 0, "-relational-atts"));
+
+    result.addElement(new Option(
+	"\tMust handle missing attribute values.",
+	"missing-att-values", 0, "-missing-att-values"));
+
+    result.addElement(new Option(
+	"\tMust handle multi-instance data.",
+	"only-multiinstance", 0, "-only-multiinstance"));
+
+    result.addElement(new Option(
+	"\tThe Capabilities handler to base the handling on.\n"
+	+ "\tThe other parameters can be used to override the ones\n"
+	+ "\tdetermined from the handler. Additional parameters for\n"
+	+ "\thandler can be passed on after the '--'.\n"
+	+ "\tEither '-W' or '-t' can be used.",
+	"W", 1, "-W <classname>"));
+
+    result.addElement(new Option(
+	"\tThe dataset to base the capabilities on.\n"
+	+ "\tThe other parameters can be used to override the ones\n"
+	+ "\tdetermined from the handler.\n"
+	+ "\tEither '-t' or '-W' can be used.",
+	"t", 1, "-t <file>"));
+
+    result.addElement(new Option(
+	"\tThe index of the class attribute, -1 for none.\n"
+	+ "\t'first' and 'last' are also valid.\n"
+	+ "\tOnly in conjunction with option '-t'.",
+	"c", 1, "-c <num>"));
+
+    result.addElement(new Option(
+	"\tSuperclass to look for in the packages.\n",
+	"superclass", 1, "-superclass"));
+
+    result.addElement(new Option(
+	"\tComma-separated list of packages to search in.",
+	"packages", 1, "-packages"));
+
+    result.addElement(new Option(
+	"\tRetrieves the package list from the GenericPropertiesCreator\n"
+	+ "\tfor the given superclass. (overrides -packages <list>).",
+	"generic", 1, "-generic"));
+
+    result.addElement(new Option(
+	"\tAlso prints the classname that didn't match the criteria.",
+	"misses", 0, "-misses"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      		tmpStr;
+    Class			cls;
+    CapabilitiesHandler		handler;
+    boolean			initialized;
+    StringTokenizer		tok;
+    GenericPropertiesCreator	creator;
+    Properties			props;
+
+    m_Capabilities = new Capabilities(this);
+    initialized    = false;
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() != 0) {
+      cls = Class.forName(tmpStr);
+      if (ClassDiscovery.hasInterface(CapabilitiesHandler.class, cls)) {
+	initialized = true;
+	handler = (CapabilitiesHandler) cls.newInstance();
+	if (handler instanceof OptionHandler)
+	  ((OptionHandler) handler).setOptions(Utils.partitionOptions(options));
+	setHandler(handler);
+      }
+      else {
+	throw new IllegalArgumentException("Class '" + tmpStr + "' is not a CapabilitiesHandler!");
+      }
+    }
+    else {
+      tmpStr = Utils.getOption('c', options);
+      if (tmpStr.length() != 0)
+	setClassIndex(tmpStr);
+      else
+	setClassIndex("last");
+
+      tmpStr = Utils.getOption('t', options);
+      setFilename(tmpStr);
+    }
+
+    tmpStr = Utils.getOption("num-instances", options);
+    if (tmpStr.length() != 0)
+      m_Capabilities.setMinimumNumberInstances(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      m_Capabilities.setMinimumNumberInstances(1);
+
+    // allowed
+    if (Utils.getFlag("no-class", options))
+      enable(Capability.NO_CLASS);
+    // not allowed
+    if (Utils.getFlag("not-no-class", options))
+      enableNot(Capability.NO_CLASS);
+
+    if (!m_Capabilities.handles(Capability.NO_CLASS)) {
+      // allowed
+      if (Utils.getFlag("nominal-class", options)) {
+	enable(Capability.NOMINAL_CLASS);
+	disable(Capability.BINARY_CLASS);
+      }
+      if (Utils.getFlag("binary-class", options)) {
+	enable(Capability.BINARY_CLASS);
+	disable(Capability.UNARY_CLASS);
+      }
+      if (Utils.getFlag("unary-class", options))
+	enable(Capability.UNARY_CLASS);
+      if (Utils.getFlag("numeric-class", options))
+	enable(Capability.NUMERIC_CLASS);
+      if (Utils.getFlag("string-class", options))
+	enable(Capability.STRING_CLASS);
+      if (Utils.getFlag("date-class", options))
+	enable(Capability.DATE_CLASS);
+      if (Utils.getFlag("relational-class", options))
+	enable(Capability.RELATIONAL_CLASS);
+      if (Utils.getFlag("missing-class-values", options))
+	enable(Capability.MISSING_CLASS_VALUES);
+    }
+    // not allowed
+    if (Utils.getFlag("not-nominal-class", options)) {
+      enableNot(Capability.NOMINAL_CLASS);
+      disableNot(Capability.BINARY_CLASS);
+    }
+    if (Utils.getFlag("not-binary-class", options)) {
+      enableNot(Capability.BINARY_CLASS);
+      disableNot(Capability.UNARY_CLASS);
+    }
+    if (Utils.getFlag("not-unary-class", options))
+      enableNot(Capability.UNARY_CLASS);
+    if (Utils.getFlag("not-numeric-class", options))
+      enableNot(Capability.NUMERIC_CLASS);
+    if (Utils.getFlag("not-string-class", options))
+      enableNot(Capability.STRING_CLASS);
+    if (Utils.getFlag("not-date-class", options))
+      enableNot(Capability.DATE_CLASS);
+    if (Utils.getFlag("not-relational-class", options))
+      enableNot(Capability.RELATIONAL_CLASS);
+    if (Utils.getFlag("not-relational-class", options))
+      enableNot(Capability.RELATIONAL_CLASS);
+    if (Utils.getFlag("not-missing-class-values", options))
+      enableNot(Capability.MISSING_CLASS_VALUES);
+
+    // allowed
+    if (Utils.getFlag("nominal-atts", options)) {
+      enable(Capability.NOMINAL_ATTRIBUTES);
+      disable(Capability.BINARY_ATTRIBUTES);
+    }
+    if (Utils.getFlag("binary-atts", options)) {
+      enable(Capability.BINARY_ATTRIBUTES);
+      disable(Capability.UNARY_ATTRIBUTES);
+    }
+    if (Utils.getFlag("unary-atts", options))
+      enable(Capability.UNARY_ATTRIBUTES);
+    if (Utils.getFlag("numeric-atts", options))
+      enable(Capability.NUMERIC_ATTRIBUTES);
+    if (Utils.getFlag("string-atts", options))
+      enable(Capability.STRING_ATTRIBUTES);
+    if (Utils.getFlag("date-atts", options))
+      enable(Capability.DATE_ATTRIBUTES);
+    if (Utils.getFlag("relational-atts", options))
+      enable(Capability.RELATIONAL_ATTRIBUTES);
+    if (Utils.getFlag("missing-att-values", options))
+      enable(Capability.MISSING_VALUES);
+    // not allowed
+    if (Utils.getFlag("not-nominal-atts", options)) {
+      enableNot(Capability.NOMINAL_ATTRIBUTES);
+      disableNot(Capability.BINARY_ATTRIBUTES);
+    }
+    if (Utils.getFlag("not-binary-atts", options)) {
+      enableNot(Capability.BINARY_ATTRIBUTES);
+      disableNot(Capability.UNARY_ATTRIBUTES);
+    }
+    if (Utils.getFlag("not-unary-atts", options))
+      enableNot(Capability.UNARY_ATTRIBUTES);
+    if (Utils.getFlag("not-numeric-atts", options))
+      enableNot(Capability.NUMERIC_ATTRIBUTES);
+    if (Utils.getFlag("not-string-atts", options))
+      enableNot(Capability.STRING_ATTRIBUTES);
+    if (Utils.getFlag("not-date-atts", options))
+      enableNot(Capability.DATE_ATTRIBUTES);
+    if (Utils.getFlag("not-relational-atts", options))
+      enableNot(Capability.RELATIONAL_ATTRIBUTES);
+    if (Utils.getFlag("not-missing-att-values", options))
+      enableNot(Capability.MISSING_VALUES);
+
+    if (Utils.getFlag("only-multiinstance", options))
+      enable(Capability.ONLY_MULTIINSTANCE);
+
+    tmpStr = Utils.getOption("superclass", options);
+    if (tmpStr.length() != 0)
+      m_Superclass = tmpStr;
+    else
+      throw new IllegalArgumentException("A superclass has to be specified!");
+
+    tmpStr = Utils.getOption("packages", options);
+    if (tmpStr.length() != 0) {
+      tok        = new StringTokenizer(tmpStr, ",");
+      m_Packages = new Vector<String>();
+      while (tok.hasMoreTokens())
+	m_Packages.add(tok.nextToken());
+    }
+
+    if (Utils.getFlag("generic", options)) {
+      creator    = new GenericPropertiesCreator();
+      creator.execute(false);
+      props	 = creator.getInputProperties();
+      tok        = new StringTokenizer(props.getProperty(m_Superclass), ",");
+      m_Packages = new Vector<String>();
+      while (tok.hasMoreTokens())
+	m_Packages.add(tok.nextToken());
+    }
+  }
+
+  /**
+   * Gets the current settings of this object.
+   * 
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String> 	result;
+    String[]	options;
+    int		i;
+
+    result = new Vector<String>();
+
+    result.add("-num-instances");
+    result.add("" + m_Capabilities.getMinimumNumberInstances());
+
+    if (isEnabled(Capability.NO_CLASS)) {
+      result.add("-no-class");
+    }
+    else {
+      if (isEnabled(Capability.UNARY_CLASS))
+	result.add("-unary-class");
+      if (isEnabled(Capability.BINARY_CLASS))
+	result.add("-binary-class");
+      if (isEnabled(Capability.NOMINAL_CLASS))
+	result.add("-nominal-class");
+      if (isEnabled(Capability.NUMERIC_CLASS))
+	result.add("-numeric-class");
+      if (isEnabled(Capability.STRING_CLASS))
+	result.add("-string-class");
+      if (isEnabled(Capability.DATE_CLASS))
+	result.add("-date-class");
+      if (isEnabled(Capability.RELATIONAL_CLASS))
+	result.add("-relational-class");
+      if (isEnabled(Capability.MISSING_CLASS_VALUES))
+	result.add("-missing-class-values");
+    }
+
+    if (isEnabled(Capability.UNARY_ATTRIBUTES))
+      result.add("-unary-atts");
+    if (isEnabled(Capability.BINARY_ATTRIBUTES))
+      result.add("-binary-atts");
+    if (isEnabled(Capability.NOMINAL_ATTRIBUTES))
+      result.add("-nominal-atts");
+    if (isEnabled(Capability.NUMERIC_ATTRIBUTES))
+      result.add("-numeric-atts");
+    if (isEnabled(Capability.STRING_ATTRIBUTES))
+      result.add("-string-atts");
+    if (isEnabled(Capability.DATE_ATTRIBUTES))
+      result.add("-date-atts");
+    if (isEnabled(Capability.RELATIONAL_ATTRIBUTES))
+      result.add("-relational-atts");
+    if (isEnabled(Capability.MISSING_VALUES))
+      result.add("-missing-att-values");
+
+    // not allowed
+    if (isEnabledNot(Capability.NO_CLASS))
+      result.add("-not-no-class");
+    if (isEnabledNot(Capability.UNARY_CLASS))
+      result.add("-not-unary-class");
+    if (isEnabledNot(Capability.BINARY_CLASS))
+      result.add("-not-binary-class");
+    if (isEnabledNot(Capability.NOMINAL_CLASS))
+      result.add("-not-nominal-class");
+    if (isEnabledNot(Capability.NUMERIC_CLASS))
+      result.add("-not-numeric-class");
+    if (isEnabledNot(Capability.STRING_CLASS))
+      result.add("-not-string-class");
+    if (isEnabledNot(Capability.DATE_CLASS))
+      result.add("-not-date-class");
+    if (isEnabledNot(Capability.RELATIONAL_CLASS))
+      result.add("-not-relational-class");
+    if (isEnabledNot(Capability.MISSING_CLASS_VALUES))
+      result.add("-not-missing-class-values");
+
+    if (isEnabledNot(Capability.UNARY_ATTRIBUTES))
+      result.add("-not-unary-atts");
+    if (isEnabledNot(Capability.BINARY_ATTRIBUTES))
+      result.add("-not-binary-atts");
+    if (isEnabledNot(Capability.NOMINAL_ATTRIBUTES))
+      result.add("-not-nominal-atts");
+    if (isEnabledNot(Capability.NUMERIC_ATTRIBUTES))
+      result.add("-not-numeric-atts");
+    if (isEnabledNot(Capability.STRING_ATTRIBUTES))
+      result.add("-not-string-atts");
+    if (isEnabledNot(Capability.DATE_ATTRIBUTES))
+      result.add("-not-date-atts");
+    if (isEnabledNot(Capability.RELATIONAL_ATTRIBUTES))
+      result.add("-not-relational-atts");
+    if (isEnabledNot(Capability.MISSING_VALUES))
+      result.add("-not-missing-att-values");
+
+    if (isEnabled(Capability.ONLY_MULTIINSTANCE))
+      result.add("-only-multi-instance");
+
+    if (getHandler() != null) {
+      result.add("-W");
+      result.add(getHandler().getClass().getName());
+      if (getHandler() instanceof OptionHandler) {
+	result.add("--");
+	options = ((OptionHandler) getHandler()).getOptions();
+	for (i = 0; i < options.length; i++)
+	  result.add(options[i]);
+      }
+    }
+    else if (getFilename().length() != 0) {
+      result.add("-t");
+      result.add(getFilename());
+      result.add("-c");
+      result.add(m_ClassIndex.getSingleIndex());
+    }
+
+    if (m_Superclass.length() != 0) {
+      result.add("-superclass");
+      result.add(m_Superclass);
+    }
+    else {
+      result.add("-packages");
+      result.add(m_Packages.toString().replaceAll("\\[", "").replaceAll("\\]", ""));
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * sets the Capabilities handler to generate the data for.
+   * 
+   * @param value	the handler
+   */
+  public void setHandler(CapabilitiesHandler value) {
+    m_Handler = value;
+    setCapabilities(m_Handler.getCapabilities());
+  }
+
+  /**
+   * returns the current set CapabilitiesHandler to generate the dataset
+   * for, can be null.
+   * 
+   * @return		the handler
+   */
+  public CapabilitiesHandler getHandler() {
+    return m_Handler;
+  }
+
+  /**
+   * Sets the dataset filename to base the capabilities on. It immediately
+   * loads the dataset and retrieves the capabilities from it.
+   * 
+   * @param value	the filename of the dataset
+   */
+  public void setFilename(String value) {
+    Instances		insts;
+
+    m_Filename = value;
+
+    if (m_Filename.length() != 0) {
+      try {
+	insts  = new Instances(new BufferedReader(new FileReader(m_Filename)));
+	m_ClassIndex.setUpper(insts.numAttributes());
+	insts.setClassIndex(Integer.parseInt(getClassIndex()) - 1);
+
+	setCapabilities(Capabilities.forInstances(insts));
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * returns the current filename for the dataset to base the capabilities on.
+   * 
+   * @return		the filename of the dataset
+   */
+  public String getFilename() {
+    return m_Filename;
+  }
+
+  /**
+   * sets the class index, -1 for none, first and last are also valid.
+   * 
+   * @param value	the class index
+   */
+  public void setClassIndex(String value) {
+    if (value.equals("-1"))
+      m_ClassIndex = null;
+    else
+      m_ClassIndex = new SingleIndex(value);
+  }
+
+  /**
+   * returns the current current class index, -1 if no class attribute.
+   * 
+   * @return		the class index
+   */
+  public String getClassIndex() {
+    if (m_ClassIndex == null)
+      return "-1";
+    else
+      return "" + m_ClassIndex.getIndex();
+  }
+
+  /**
+   * enables the given capability.
+   * 
+   * @param c		the capability to enable
+   */
+  public void enable(Capability c) {
+    m_Capabilities.enable(c);
+  }
+
+  /**
+   * whether the given capability is enabled.
+   * 
+   * @param c		the capability to enable
+   * @return		true if the capability is enabled
+   */
+  public boolean isEnabled(Capability c) {
+    return m_Capabilities.handles(c);
+  }
+
+  /**
+   * disables the given capability.
+   * 
+   * @param c		the capability to disable
+   */
+  public void disable(Capability c) {
+    m_Capabilities.disable(c);
+  }
+
+  /**
+   * enables the given "not to have" capability.
+   * 
+   * @param c		the capability to enable
+   */
+  public void enableNot(Capability c) {
+    m_NotCapabilities.enable(c);
+  }
+
+  /**
+   * whether the given "not to have" capability is enabled.
+   * 
+   * @param c		the capability to enable
+   * @return		true if the capability is enabled
+   */
+  public boolean isEnabledNot(Capability c) {
+    return m_NotCapabilities.handles(c);
+  }
+
+  /**
+   * disables the given "not to have" capability.
+   * 
+   * @param c		the capability to disable
+   */
+  public void disableNot(Capability c) {
+    m_NotCapabilities.disable(c);
+  }
+
+  /**
+   * returns true if the given capability can be handled.
+   * 
+   * @param c		the capability to check
+   * @return		true if the capability can be handled
+   */
+  public boolean handles(Capability c) {
+    return m_Capabilities.handles(c);
+  }
+
+  /**
+   * The capabilities to search for.
+   *
+   * @return            the capabilities to search for
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    return m_Capabilities;
+  }
+
+  /**
+   * Uses the given Capabilities for the search.
+   * 
+   * @param c		the capabilities to use for the search
+   */
+  public void setCapabilities(Capabilities c) {
+    m_Capabilities = (Capabilities) c.clone();
+  }
+
+  /**
+   * The "not to have" capabilities to search for.
+   *
+   * @return            the capabilities to search for
+   * @see               Capabilities
+   */
+  public Capabilities getNotCapabilities() {
+    return m_NotCapabilities;
+  }
+
+  /**
+   * Uses the given "not to have" Capabilities for the search.
+   * 
+   * @param c		the capabilities to use for the search
+   */
+  public void setNotCapabilities(Capabilities c) {
+    m_NotCapabilities = (Capabilities) c.clone();
+  }
+
+  /**
+   * returns the matches from the last find call.
+   * 
+   * @return		the matching classname from the last find run
+   */
+  public Vector<String> getMatches() {
+    return m_Matches;
+  }
+
+  /**
+   * returns the misses from the last find call.
+   * 
+   * @return		the classnames that didn't match from the last find run
+   */
+  public Vector<String> getMisses() {
+    return m_Misses;
+  }
+
+  /**
+   * returns a list with all the classnames that fit the criteria.
+   * 
+   * @return		contains all classnames that fit the criteria
+   */
+  public Vector<String> find() {
+    Vector<String>		list;
+    int			i;
+    Class		cls;
+    Object		obj;
+    CapabilitiesHandler	handler;
+    boolean		fits;
+    Capabilities	caps;
+
+    m_Matches = new Vector<String>();
+    m_Misses  = new Vector<String>();
+
+    list = ClassDiscovery.find(m_Superclass, (String[]) m_Packages.toArray(new String[m_Packages.size()]));
+    for (i = 0; i < list.size(); i++) {
+      try {
+	cls = Class.forName((String) list.get(i));
+	obj = cls.newInstance();
+
+	// exclude itself
+	if (cls == this.getClass())
+	  continue;
+
+	// really a CapabilitiesHandler?
+	if (!(obj instanceof CapabilitiesHandler))
+	  continue;
+
+	// check capabilities enumeration
+	handler = (CapabilitiesHandler) obj;
+	caps    = handler.getCapabilities();
+	fits    = true;
+	for (Capability cap: Capability.values()) {
+	  if (m_Capabilities.handles(cap)) {
+	    if (!(caps.handles(cap))) {
+	      fits = false;
+	      break;
+	    }
+	  }
+	}
+	if (!fits) {
+	  m_Misses.add(list.get(i));
+	  continue;
+	}
+
+	// check "not" list
+	for (Capability cap: Capability.values()) {
+	  if (m_NotCapabilities.handles(cap)) {
+	    if ((caps.handles(cap))) {
+	      fits = false;
+	      break;
+	    }
+	  }
+	}
+	if (!fits) {
+	  m_Misses.add(list.get(i));
+	  continue;
+	}
+
+	// other stuff
+	if (caps.getMinimumNumberInstances() > m_Capabilities.getMinimumNumberInstances()) {
+	  m_Misses.add(list.get(i));
+	  continue;
+	}
+
+	// matches all criteria!
+	m_Matches.add(list.get(i));
+      }
+      catch (Exception e) {
+	// ignore
+      }
+    }
+
+    return m_Matches;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Executes the location of classes with parameters from the commandline.
+   * 
+   * @param args	the commandline parameters
+   */
+  public static void main(String[] args) {
+    FindWithCapabilities 	find;
+    Vector<String> 			list;
+    String			result;
+    int				i;
+    boolean			printMisses;
+    Iterator			iter;
+    boolean			first;
+
+    printMisses = false;
+
+    try {
+      find = new FindWithCapabilities();
+
+      try {
+	printMisses = Utils.getFlag("misses", args);
+	find.setOptions(args);
+	Utils.checkForRemainingOptions(args);
+      } 
+      catch (Exception ex) {
+	result = ex.getMessage() + "\n\n" + find.getClass().getName().replaceAll(".*\\.", "") + " Options:\n\n";
+	Enumeration enm = find.listOptions();
+	while (enm.hasMoreElements()) {
+	  Option option = (Option) enm.nextElement();
+	  result += option.synopsis() + "\n" + option.description() + "\n";
+	}
+	throw new Exception(result);
+      }
+
+      System.out.println("\nSearching for the following Capabilities:");
+      // allowed
+      System.out.print("- allowed: ");
+      iter  = find.getCapabilities().capabilities();
+      first = true;
+      while (iter.hasNext()) {
+	if (!first)
+	  System.out.print(", ");
+	first = false;
+	System.out.print(iter.next());
+      }
+      System.out.println();
+      // not allowed
+      System.out.print("- not allowed: ");
+      iter  = find.getNotCapabilities().capabilities();
+      first = true;
+      if (iter.hasNext()) {
+	while (iter.hasNext()) {
+	  if (!first)
+	    System.out.print(", ");
+	  first = false;
+	  System.out.print(iter.next());
+	}
+	System.out.println();
+      }
+      else {
+	System.out.println("-");
+      }
+
+      find.find();
+
+      // matches
+      list = find.getMatches();
+      if (list.size() == 1)
+	System.out.println("\nFound " + list.size() + " class that matched the criteria:\n");
+      else
+	System.out.println("\nFound " + list.size() + " classes that matched the criteria:\n");
+      for (i = 0; i < list.size(); i++)
+	System.out.println(list.get(i));
+
+      // misses
+      if (printMisses) {
+	list = find.getMisses();
+	if (list.size() == 1)
+	  System.out.println("\nFound " + list.size() + " class that didn't match the criteria:\n");
+	else
+	  System.out.println("\nFound " + list.size() + " classes that didn't match the criteria:\n");
+	for (i = 0; i < list.size(); i++)
+	  System.out.println(list.get(i));
+      }
+      
+      System.out.println();
+    } 
+    catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/GlobalInfoJavadoc.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/GlobalInfoJavadoc.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/GlobalInfoJavadoc.java	(revision 29)
@@ -0,0 +1,133 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GlobalInfoJavadoc.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.lang.reflect.Method;
+
+/**
+ * Generates Javadoc comments from the class's globalInfo method. Can 
+ * automatically update the comments if they're surrounded by
+ * the GLOBALINFO_STARTTAG and GLOBALINFO_ENDTAG (the indention is determined via
+ * the GLOBALINFO_STARTTAG). <p/>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The class to load.</pre>
+ * 
+ * <pre> -nostars
+ *  Suppresses the '*' in the Javadoc.</pre>
+ * 
+ * <pre> -dir &lt;dir&gt;
+ *  The directory above the package hierarchy of the class.</pre>
+ * 
+ * <pre> -silent
+ *  Suppresses printing in the console.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see #GLOBALINFO_METHOD
+ * @see #GLOBALINFO_STARTTAG
+ * @see #GLOBALINFO_ENDTAG
+ */
+public class GlobalInfoJavadoc 
+  extends Javadoc {
+  
+  /** the globalInfo method name */
+  public final static String GLOBALINFO_METHOD = "globalInfo";
+  
+  /** the start comment tag for inserting the generated Javadoc */
+  public final static String GLOBALINFO_STARTTAG = "<!-- globalinfo-start -->";
+  
+  /** the end comment tag for inserting the generated Javadoc */
+  public final static String GLOBALINFO_ENDTAG = "<!-- globalinfo-end -->";
+  
+  /**
+   * default constructor 
+   */
+  public GlobalInfoJavadoc() {
+    super();
+    
+    m_StartTag    = new String[1];
+    m_EndTag      = new String[1];
+    m_StartTag[0] = GLOBALINFO_STARTTAG;
+    m_EndTag[0]   = GLOBALINFO_ENDTAG;
+  }
+  
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair.
+   * 
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String generateJavadoc(int index) throws Exception {
+    String		result;
+    Method		method;
+    
+    result = "";
+    
+    if (index == 0) {
+      if (!canInstantiateClass())
+	return result;
+	    
+      try {
+	method = getInstance().getClass().getMethod(GLOBALINFO_METHOD, (Class[]) null);
+      }
+      catch (Exception e) {
+	// no method "globalInfo"
+	return result;
+      }
+      
+      // retrieve global info
+      result = toHTML((String) method.invoke(getInstance(), (Object[]) null));
+      result = result.trim() + "\n<p/>\n";
+      
+      // stars?
+      if (getUseStars()) 
+	result = indent(result, 1, "* ");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Parses the given commandline parameters and generates the Javadoc.
+   * 
+   * @param args	the commandline parameters for the object
+   */
+  public static void main(String[] args) {
+    runJavadoc(new GlobalInfoJavadoc(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Instance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Instance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Instance.java	(revision 29)
@@ -0,0 +1,570 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Instance.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.ArrayList;
+
+/**
+ * Interface representing an instance. All values (numeric, date,
+ * nominal, string or relational) are internally stored as
+ * floating-point numbers in the original concrete class
+ * implementations (now called DenseInstance.java and
+ * SparseInstance.java), and the methods in this interface reflect
+ * this. If an attribute is nominal (or a string or relational), the
+ * stored value is the index of the corresponding nominal (or string
+ * or relational) value in the attribute's definition. We have chosen
+ * this approach in favor of a more elegant object-oriented approach
+ * because it is much faster. <p>
+ *
+ * Typical usage (code from the main() method of this class): <p>
+ *
+ * <code>
+ * ... <br>
+ *      
+ * // Create empty instance with three attribute values <br>
+ * Instance inst = new DenseInstance(3); <br><br>
+ *     
+ * // Set instance's values for the attributes "length", "weight", and "position"<br>
+ * inst.setValue(length, 5.3); <br>
+ * inst.setValue(weight, 300); <br>
+ * inst.setValue(position, "first"); <br><br>
+ *   
+ * // Set instance's dataset to be the dataset "race" <br>
+ * inst.setDataset(race); <br><br>
+ *   
+ * // Print the instance <br>
+ * System.out.println("The instance: " + inst); <br>
+ *
+ * ... <br>
+ * </code><p>
+ *
+ * All methods that change an instance's attribute values must be
+ * safe, ie. a change of an instance's attribute values must not
+ * affect any other instances.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public interface Instance extends Copyable {
+
+  /**
+   * Returns the attribute with the given index.
+   *
+   * @param index the attribute's index
+   * @return the attribute at the given position
+   * @throws UnassignedDatasetException if instance doesn't have access to a
+   * dataset
+   */ 
+  public Attribute attribute(int index);
+
+  /**
+   * Returns the attribute with the given index in the sparse representation.
+   * Same as attribute(int) for a DenseInstance.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @return the attribute at the given position
+   * @throws UnassignedDatasetException if instance doesn't have access to a
+   * dataset
+   */ 
+  public Attribute attributeSparse(int indexOfIndex);
+
+  /**
+   * Returns class attribute.
+   *
+   * @return the class attribute
+   * @throws UnassignedDatasetException if the class is not set or the
+   * instance doesn't have access to a dataset
+   */
+  public Attribute classAttribute();
+
+  /**
+   * Returns the class attribute's index.
+   *
+   * @return the class index as an integer 
+   * @throws UnassignedDatasetException if instance doesn't have access to a dataset 
+   */
+  public int classIndex();
+
+  /**
+   * Tests if an instance's class is missing.
+   *
+   * @return true if the instance's class is missing
+   * @throws UnassignedClassException if the class is not set or the instance doesn't
+   * have access to a dataset
+   */
+  public boolean classIsMissing();
+
+  /**
+   * Returns an instance's class value as a floating-point number.
+   *
+   * @return the corresponding value as a double (If the 
+   * corresponding attribute is nominal (or a string) then it returns the 
+   * value's index as a double).
+   * @throws UnassignedClassException if the class is not set or the instance doesn't
+   * have access to a dataset 
+   */
+  public double classValue();
+
+  /**
+   * Returns the dataset this instance has access to. (ie. obtains
+   * information about attribute types from) Null if the instance
+   * doesn't have access to a dataset.
+   *
+   * @return the dataset the instance has accesss to
+   */
+  public Instances dataset();
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1). Only succeeds if the instance does not
+   * have access to any dataset because otherwise inconsistencies
+   * could be introduced.
+   *
+   * @param position the attribute's position
+   * @throws RuntimeException if the instance has access to a
+   * dataset 
+   */
+  public void deleteAttributeAt(int position);
+
+  /**
+   * Returns an enumeration of all the attributes.
+   *
+   * @return enumeration of all the attributes
+   * @throws UnassignedDatasetException if the instance doesn't
+   * have access to a dataset 
+   */
+  public Enumeration enumerateAttributes();
+
+  /**
+   * Tests if the headers of two instances are equivalent.
+   *
+   * @param inst another instance
+   * @return true if the header of the given instance is 
+   * equivalent to this instance's header
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  public boolean equalHeaders(Instance inst);
+
+  /**
+   * Checks if the headers of two instances are equivalent. 
+   * If not, then returns a message why they differ.
+   *
+   * @param dataset 	another instance
+   * @return 		null if the header of the given instance is equivalent 
+   * 			to this instance's header, otherwise a message with details on
+   * 			why they differ
+   */
+  public String equalHeadersMsg(Instance inst);
+
+  /**
+   * Tests whether an instance has a missing value. Skips the class attribute if set.
+   * @return true if instance has a missing value.
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  public boolean hasMissingValue();
+
+  /**
+   * Returns the index of the attribute stored at the given position in the sparse
+   * representation. Identify function for an instance of type DenseInstance.
+   *
+   * @param position the position 
+   * @return the index of the attribute stored at the given position
+   */
+  public int index(int position);
+
+  /**
+   * Inserts an attribute at the given position (0 to 
+   * numAttributes()). Only succeeds if the instance does not
+   * have access to any dataset because otherwise inconsistencies
+   * could be introduced.
+   *
+   * @param position the attribute's position
+   * @throws RuntimeException if the instance has accesss to a
+   * dataset
+   * @throws IllegalArgumentException if the position is out of range
+   */
+  public void insertAttributeAt(int position);
+
+  /**
+   * Tests if a specific value is "missing".
+   *
+   * @param attIndex the attribute's index
+   * @return true if the value is "missing"
+   */
+  public boolean isMissing(int attIndex);
+
+  /**
+   * Tests if a specific value is "missing" in the sparse
+   * representation. Samse as isMissing(int) for a DenseInstance.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @return true if the value is "missing"
+   */
+  public boolean isMissingSparse(int indexOfIndex);
+
+  /**
+   * Tests if a specific value is "missing".
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return true if the value is "missing"
+   */
+  public boolean isMissing(Attribute att);
+
+  /**
+   * Merges this instance with the given instance and returns
+   * the result. Dataset is set to null. The returned instance
+   * is of the same type as this instance.
+   *
+   * @param inst the instance to be merged with this one
+   * @return the merged instances
+   */
+  public Instance mergeInstance(Instance inst);
+
+  /**
+   * Returns the number of attributes.
+   *
+   * @return the number of attributes as an integer
+   */
+  public int numAttributes();
+
+  /**
+   * Returns the number of class labels.
+   *
+   * @return the number of class labels as an integer if the 
+   * class attribute is nominal, 1 otherwise.
+   * @throws UnassignedDatasetException if instance doesn't have access to any
+   * dataset
+   */
+  public int numClasses();
+
+  /**
+   * Returns the number of values present in a sparse representation.
+   *
+   * @return the number of values
+   */
+  public int numValues();
+
+  /** 
+   * Replaces all missing values in the instance with the
+   * values contained in the given array. A deep copy of
+   * the vector of attribute values is performed before the
+   * values are replaced.
+   *
+   * @param array containing the means and modes
+   * @throws IllegalArgumentException if numbers of attributes are unequal
+   */
+  public void replaceMissingValues(double[] array);
+
+  /**
+   * Sets the class value of an instance to be "missing". A deep copy of
+   * the vector of attribute values is performed before the
+   * value is set to be missing.
+   *
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnassignedDatasetException if the instance doesn't
+   * have access to a dataset
+   */
+  public void setClassMissing();
+
+  /**
+   * Sets the class value of an instance to the given value (internal
+   * floating-point format).  A deep copy of the vector of attribute
+   * values is performed before the value is set.
+   *
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnaddignedDatasetException if the instance doesn't
+   * have access to a dataset 
+   */
+  public void setClassValue(double value);
+
+  /**
+   * Sets the class value of an instance to the given value. A deep
+   * copy of the vector of attribute values is performed before the
+   * value is set.
+   *
+   * @param value the new class value (If the class
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws UnassignedClassException if the class is not set
+   * @throws UnassignedDatasetException if the dataset is not set
+   * @throws IllegalArgumentException if the attribute is not
+   * nominal or a string, or the value couldn't be found for a nominal
+   * attribute 
+   */
+  public void setClassValue(String value);
+
+  /**
+   * Sets the reference to the dataset. Does not check if the instance
+   * is compatible with the dataset. Note: the dataset does not know
+   * about this instance. If the structure of the dataset's header
+   * gets changed, this instance will not be adjusted automatically.
+   *
+   * @param instances the reference to the dataset 
+   */
+  public void setDataset(Instances instances);
+
+  /**
+   * Sets a specific value to be "missing". Performs a deep copy
+   * of the vector of attribute values before the value is set to
+   * be missing.
+   *
+   * @param attIndex the attribute's index
+   */
+  public void setMissing(int attIndex);
+
+  /**
+   * Sets a specific value to be "missing". Performs a deep copy
+   * of the vector of attribute values before the value is set to
+   * be missing. The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   */
+  public void setMissing(Attribute att);
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param attIndex the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValue(int attIndex, double value);
+
+  /**
+   * Sets a specific value in the instance to the given value
+   * (internal floating-point format), given an index into the sparse
+   * representation. Performs a deep copy of the vector of attribute
+   * values before the value is set. Same as setValue(int, double)
+   * for a DenseInstance.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValueSparse(int indexOfIndex, double value);
+
+  /**
+   * Sets a value of a nominal or string attribute to the given
+   * value. Performs a deep copy of the vector of attribute values
+   * before the value is set.
+   *
+   * @param attIndex the attribute's index
+   * @param value the new attribute value (If the attribute
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws UnassignedDatasetException if the dataset is not set
+   * @throws IllegalArgumentException if the selected
+   * attribute is not nominal or a string, or the supplied value couldn't 
+   * be found for a nominal attribute 
+   */
+  public void setValue(int attIndex, String value);
+
+  /**
+   * Sets a specific value in the instance to the given value
+   * (internal floating-point format). Performs a deep copy of the
+   * vector of attribute values before the value is set, so if you are
+   * planning on calling setValue many times it may be faster to
+   * create a new instance using toDoubleArray.  The given attribute
+   * has to belong to a dataset.
+   *
+   * @param att the attribute 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValue(Attribute att, double value);
+
+  /**
+   * Sets a value of an nominal or string attribute to the given
+   * value. Performs a deep copy of the vector of attribute values
+   * before the value is set, so if you are planning on calling setValue many
+   * times it may be faster to create a new instance using toDoubleArray.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @param value the new attribute value (If the attribute
+   * is a string attribute and the value can't be found,
+   * the value is added to the attribute).
+   * @throws IllegalArgumentException if the the attribute is not
+   * nominal or a string, or the value couldn't be found for a nominal
+   * attribute 
+   */
+  public void setValue(Attribute att, String value);
+  
+  /**
+   * Sets the weight of an instance.
+   *
+   * @param weight the weight
+   */
+  public void setWeight(double weight);
+
+  /** 
+   * Returns the relational value of a relational attribute.
+   *
+   * @param attIndex the attribute's index
+   * @return the corresponding relation as an Instances object
+   * @throws IllegalArgumentException if the attribute is not a
+   * relation-valued attribute
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public Instances relationalValue(int attIndex);
+
+
+  /** 
+   * Returns the relational value of a relational attribute.
+   *
+   * @param att the attribute
+   * @return the corresponding relation as an Instances object
+   * @throws IllegalArgumentException if the attribute is not a
+   * relation-valued attribute
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public Instances relationalValue(Attribute att);
+
+  /** 
+   * Returns the value of a nominal, string, date, or relational attribute
+   * for the instance as a string.
+   *
+   * @param attIndex the attribute's index
+   * @return the value as a string
+   * @throws IllegalArgumentException if the attribute is not a nominal,
+   * string, date, or relation-valued attribute.
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public String stringValue(int attIndex);
+
+  /** 
+   * Returns the value of a nominal, string, date, or relational attribute
+   * for the instance as a string.
+   *
+   * @param att the attribute
+   * @return the value as a string
+   * @throws IllegalArgumentException if the attribute is not a nominal,
+   * string, date, or relation-valued attribute.
+   * @throws UnassignedDatasetException if the instance doesn't belong
+   * to a dataset.
+   */
+  public String stringValue(Attribute att);
+
+  /**
+   * Returns the values of each attribute as an array of doubles.
+   *
+   * @return an array containing all the instance attribute values
+   */
+  public double[] toDoubleArray();
+
+  /**
+   * Returns the description of one instance (without weight
+   * appended). If the instance
+   * doesn't have access to a dataset, it returns the internal
+   * floating-point values. Quotes string
+   * values that contain whitespace characters.
+   *
+   * This method is used by getRandomNumberGenerator() in
+   * Instances.java in order to maintain backwards compatibility
+   * with weka 3.4.
+   *
+   * @return the instance's description as a string
+   */
+  public String toStringNoWeight();
+
+  /**
+   * Returns the description of one value of the instance as a 
+   * string. If the instance doesn't have access to a dataset, it 
+   * returns the internal floating-point value. Quotes string
+   * values that contain whitespace characters, or if they
+   * are a question mark.
+   *
+   * @param attIndex the attribute's index
+   * @return the value's description as a string
+   */
+  public String toString(int attIndex);
+
+  /**
+   * Returns the description of one value of the instance as a 
+   * string. If the instance doesn't have access to a dataset it 
+   * returns the internal floating-point value. Quotes string
+   * values that contain whitespace characters, or if they
+   * are a question mark.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return the value's description as a string
+   */
+  public String toString(Attribute att);
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   *
+   * @param attIndex the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public double value(int attIndex);
+
+  /**
+   * Returns an instance's attribute value in internal format, given
+   * an index in the sparse representation. Same as value(int) for
+   * a DenseInstance.
+   *
+   * @param indexOfIndex the index of the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public double valueSparse(int indexOfIndex);
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   * The given attribute has to belong to a dataset.
+   *
+   * @param att the attribute
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a
+   * double).
+   */
+  public double value(Attribute att);
+
+  /**
+   * Returns the instance's weight.
+   *
+   * @return the instance's weight as a double
+   */
+  public double weight();
+}
Index: branches/MetisMQI/src/main/java/weka/core/InstanceComparator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/InstanceComparator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/InstanceComparator.java	(revision 29)
@@ -0,0 +1,184 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InstanceComparator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A comparator for the Instance class. it can be used with or without the
+ * class label. Missing values are sorted at the beginning.<br>
+ * Can be used as comparator in the sorting and binary search algorithms of
+ * <code>Arrays</code> and <code>Collections</code>.
+ *
+ * @see     Instance
+ * @author  FracPete (fracpete at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see     java.util.Arrays
+ * @see     java.util.Collections
+ */
+public class InstanceComparator
+  implements Comparator<Instance>, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6589278678230949683L;
+  
+  /** whether to include the class in the comparison */
+  protected boolean m_IncludeClass;
+  
+  /**
+   * initializes the comparator and includes the class in the comparison 
+   */
+  public InstanceComparator() {
+    this(true);
+  }
+  
+  /**
+   * initializes the comparator  
+   */
+  public InstanceComparator(boolean includeClass) {
+    super();
+    setIncludeClass(includeClass);
+  }
+  
+  /**
+   * sets whether the class should be included (= TRUE) in the comparison
+   * 
+   * @param includeClass        whether to include the class in the comparison 
+   */
+  public void setIncludeClass(boolean includeClass) {
+    m_IncludeClass = includeClass;
+  }
+  
+  /**
+   * returns TRUE if the class is included in the comparison
+   */
+  public boolean getIncludeClass() {
+    return m_IncludeClass;
+  }
+
+  /**
+   * compares the two instances, returns -1 if o1 is smaller than o2, 0
+   * if equal and +1 if greater. The method assumes that both instance objects
+   * have the same attributes, they don't have to belong to the same dataset.
+   * 
+   * @param o1        the first instance to compare
+   * @param o2        the second instance to compare
+   * @return          returns -1 if o1 is smaller than o2, 0 if equal and +1 
+   *                  if greater
+   */
+  public int compare(Instance o1, Instance o2) {
+    int         result;
+    Instance    inst1;
+    Instance    inst2;
+    int         classindex;
+    int         i;
+    
+    inst1 = (Instance) o1;
+    inst2 = (Instance) o2;
+    
+    // get class index
+    if (inst1.classIndex() == -1)
+      classindex = inst1.numAttributes() - 1;
+    else
+      classindex = inst1.classIndex();
+
+    result = 0;
+    for (i = 0; i < inst1.numAttributes(); i++) {
+      // exclude class?
+      if (!getIncludeClass() && (i == classindex))
+        continue;
+   
+      // comparing attribute values
+      // 1. special handling if missing value (NaN) is involved:
+      if (inst1.isMissing(i) || inst2.isMissing(i)) {
+        if (inst1.isMissing(i) && inst2.isMissing(i)) {
+          continue;
+        }
+        else {
+          if (inst1.isMissing(i))
+            result = -1;
+          else
+            result = 1;
+          break;
+        }
+      }
+      // 2. regular values:
+      else {
+        if (Utils.eq(inst1.value(i), inst2.value(i))) { 
+          continue;
+        }
+        else {
+          if (inst1.value(i) < inst2.value(i))
+            result = -1;
+          else
+            result = 1;
+          break;
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * for testing only. takes an ARFF-filename as first argument to perform
+   * some tests. 
+   */
+  public static void main(String[] args) throws Exception {
+    Instances       inst;
+    Comparator<Instance>      comp;
+    
+    if (args.length == 0)
+      return;
+    
+    // read instances
+    inst = new Instances(new BufferedReader(new FileReader(args[0])));
+    inst.setClassIndex(inst.numAttributes() - 1);
+    
+    // compare incl. class
+    comp = new InstanceComparator();
+    System.out.println("\nIncluding the class");
+    System.out.println("comparing 1. instance with 1.: " + comp.compare(inst.instance(0), inst.instance(0)));
+    System.out.println("comparing 1. instance with 2.: " + comp.compare(inst.instance(0), inst.instance(1)));
+    System.out.println("comparing 2. instance with 1.: " + comp.compare(inst.instance(1), inst.instance(0)));
+        
+    // compare excl. class
+    comp = new InstanceComparator(false);
+    System.out.println("\nExcluding the class");
+    System.out.println("comparing 1. instance with 1.: " + comp.compare(inst.instance(0), inst.instance(0)));
+    System.out.println("comparing 1. instance with 2.: " + comp.compare(inst.instance(0), inst.instance(1)));
+    System.out.println("comparing 2. instance with 1.: " + comp.compare(inst.instance(1), inst.instance(0)));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Instances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Instances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Instances.java	(revision 29)
@@ -0,0 +1,2211 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Instances.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.converters.ArffLoader.ArffReader;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.List;
+import java.util.AbstractList;
+import java.util.ArrayList;
+
+/**
+ * Class for handling an ordered set of weighted instances. <p>
+ *
+ * Typical usage: <p>
+ * <pre>
+ * import weka.core.converters.ConverterUtils.DataSource;
+ * ...
+ * 
+ * // Read all the instances in the file (ARFF, CSV, XRFF, ...)
+ * DataSource source = new DataSource(filename);
+ * Instances instances = source.getDataSet();
+ *
+ * // Make the last attribute be the class
+ * instances.setClassIndex(instances.numAttributes() - 1);
+ * 
+ * // Print header and instances.
+ * System.out.println("\nDataset:\n");
+ * System.out.println(instances);
+ * 
+ * ...
+ * </pre><p>
+ *
+ * All methods that change a set of instances are safe, ie. a change
+ * of a set of instances does not affect any other sets of
+ * instances. All methods that change a datasets's attribute
+ * information clone the dataset before it is changed.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $ 
+ */
+public class Instances extends AbstractList<Instance>
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -19412345060742748L;
+  
+  /** The filename extension that should be used for arff files */
+  public final static String FILE_EXTENSION = ".arff";
+
+  /** The filename extension that should be used for bin. serialized instances files */
+  public final static String SERIALIZED_OBJ_FILE_EXTENSION = ".bsi";
+
+  /** The keyword used to denote the start of an arff header */
+  public final static String ARFF_RELATION = "@relation";
+
+  /** The keyword used to denote the start of the arff data section */
+  public final static String ARFF_DATA = "@data";
+
+  /** The dataset's name. */
+  protected /*@spec_public non_null@*/ String m_RelationName;         
+
+  /** The attribute information. */
+  protected /*@spec_public non_null@*/ ArrayList<Attribute> m_Attributes;
+  /*  public invariant (\forall int i; 0 <= i && i < m_Attributes.size(); 
+                    m_Attributes.get(i) != null);
+  */
+
+  /** The instances. */
+  protected /*@spec_public non_null@*/ ArrayList<Instance> m_Instances;
+
+  /** The class attribute's index */
+  protected int m_ClassIndex;
+  //@ protected invariant classIndex() == m_ClassIndex;
+
+  /** The lines read so far in case of incremental loading. Since the 
+   * StreamTokenizer will be re-initialized with every instance that is read,
+   * we have to keep track of the number of lines read so far. 
+   * @see #readInstance(Reader) */
+  protected int m_Lines = 0;
+  
+  /**
+   * Reads an ARFF file from a reader, and assigns a weight of
+   * one to each instance. Lets the index of the class 
+   * attribute be undefined (negative).
+   *
+   * @param reader the reader
+   * @throws IOException if the ARFF file is not read 
+   * successfully
+   */
+  public Instances(/*@non_null@*/Reader reader) throws IOException {
+    ArffReader arff = new ArffReader(reader);
+    Instances dataset = arff.getData();
+    initialize(dataset, dataset.numInstances());
+    dataset.copyInstances(0, this, dataset.numInstances());
+    compactify();
+  }
+ 
+  /**
+   * Reads the header of an ARFF file from a reader and 
+   * reserves space for the given number of instances. Lets
+   * the class index be undefined (negative).
+   *
+   * @param reader the reader
+   * @param capacity the capacity
+   * @throws IllegalArgumentException if the header is not read successfully
+   * or the capacity is negative.
+   * @throws IOException if there is a problem with the reader.
+   * @deprecated instead of using this method in conjunction with the
+   * <code>readInstance(Reader)</code> method, one should use the 
+   * <code>ArffLoader</code> or <code>DataSource</code> class instead.
+   * @see weka.core.converters.ArffLoader
+   * @see weka.core.converters.ConverterUtils.DataSource
+   */
+  //@ requires capacity >= 0;
+  //@ ensures classIndex() == -1;
+  @Deprecated public Instances(/*@non_null@*/Reader reader, int capacity)
+    throws IOException {
+
+    ArffReader arff = new ArffReader(reader, 0);
+    Instances header = arff.getStructure();
+    initialize(header, capacity);
+    m_Lines = arff.getLineNo();
+  }
+
+  /**
+   * Constructor copying all instances and references to
+   * the header information from the given set of instances.
+   *
+   * @param dataset the set to be copied
+   */
+  public Instances(/*@non_null@*/Instances dataset) {
+
+    this(dataset, dataset.numInstances());
+
+    dataset.copyInstances(0, this, dataset.numInstances());
+  }
+
+  /**
+   * Constructor creating an empty set of instances. Copies references
+   * to the header information from the given set of instances. Sets
+   * the capacity of the set of instances to 0 if its negative.
+   *
+   * @param dataset the instances from which the header 
+   * information is to be taken
+   * @param capacity the capacity of the new dataset 
+   */
+  public Instances(/*@non_null@*/Instances dataset, int capacity) {
+    initialize(dataset, capacity);
+  }
+
+  /**
+   * initializes with the header information of the given dataset and sets
+   * the capacity of the set of instances.
+   * 
+   * @param dataset the dataset to use as template
+   * @param capacity the number of rows to reserve
+   */
+  protected void initialize(Instances dataset, int capacity) {
+    if (capacity < 0)
+      capacity = 0;
+    
+    // Strings only have to be "shallow" copied because
+    // they can't be modified.
+    m_ClassIndex   = dataset.m_ClassIndex;
+    m_RelationName = dataset.m_RelationName;
+    m_Attributes   = dataset.m_Attributes;
+    m_Instances    = new ArrayList<Instance>(capacity);
+  }
+  
+  /**
+   * Creates a new set of instances by copying a 
+   * subset of another set.
+   *
+   * @param source the set of instances from which a subset 
+   * is to be created
+   * @param first the index of the first instance to be copied
+   * @param toCopy the number of instances to be copied
+   * @throws IllegalArgumentException if first and toCopy are out of range
+   */
+  //@ requires 0 <= first;
+  //@ requires 0 <= toCopy;
+  //@ requires first + toCopy <= source.numInstances();
+  public Instances(/*@non_null@*/Instances source, int first, int toCopy) {
+    
+    this(source, toCopy);
+
+    if ((first < 0) || ((first + toCopy) > source.numInstances())) {
+      throw new IllegalArgumentException("Parameters first and/or toCopy out "+
+                                         "of range");
+    }
+    source.copyInstances(first, this, toCopy);
+  }
+
+  /**
+   * Creates an empty set of instances. Uses the given
+   * attribute information. Sets the capacity of the set of 
+   * instances to 0 if its negative. Given attribute information
+   * must not be changed after this constructor has been used.
+   *
+   * @param name the name of the relation
+   * @param attInfo the attribute information
+   * @param capacity the capacity of the set
+   */
+  public Instances(/*@non_null@*/String name, 
+		   /*@non_null@*/ArrayList<Attribute> attInfo, int capacity) {
+
+    m_RelationName = name;
+    m_ClassIndex = -1;
+    m_Attributes = attInfo;
+    for (int i = 0; i < numAttributes(); i++) {
+      attribute(i).setIndex(i);
+    }
+    m_Instances = new ArrayList<Instance>(capacity);
+  }
+
+  /**
+   * Create a copy of the structure if the data has string or
+   * relational attributes, "cleanses" string types (i.e. doesn't
+   * contain references to the strings seen in the past) and all
+   * relational attributes.
+   *
+   * @return a copy of the instance structure.
+   */
+  public Instances stringFreeStructure() {
+
+    ArrayList<Attribute> newAtts = new ArrayList<Attribute>();
+    for (int i = 0 ; i < m_Attributes.size(); i++) {
+      Attribute att = (Attribute)m_Attributes.get(i);
+      if (att.type() == Attribute.STRING) {
+        newAtts.add(new Attribute(att.name(), (List<String>)null, i));
+      } else if (att.type() == Attribute.RELATIONAL) {
+        newAtts.add(new Attribute(att.name(), new Instances(att.relation(), 0), i));
+      }
+    }
+    if (newAtts.size() == 0) {
+      return new Instances(this, 0);
+    }
+    ArrayList<Attribute> atts = Utils.cast(m_Attributes.clone());
+    for (int i = 0; i < newAtts.size(); i++) {
+      atts.set(((Attribute)newAtts.get(i)).index(), newAtts.get(i));
+    }
+    Instances result = new Instances(this, 0);
+    result.m_Attributes = atts;
+    return result;
+  }
+
+  /**
+   * Adds one instance to the end of the set. 
+   * Shallow copies instance before it is added. Increases the
+   * size of the dataset if it is not large enough. Does not
+   * check if the instance is compatible with the dataset.
+   * Note: String or relational values are not transferred.
+   *
+   * @param instance the instance to be added
+   */
+  public boolean add(/*@non_null@*/ Instance instance) {
+
+    Instance newInstance = (Instance)instance.copy();
+
+    newInstance.setDataset(this);
+    m_Instances.add(newInstance);
+
+    return true;
+  }
+
+  /**
+   * Adds one instance to the end of the set. 
+   * Shallow copies instance before it is added. Increases the
+   * size of the dataset if it is not large enough. Does not
+   * check if the instance is compatible with the dataset.
+   * Note: String or relational values are not transferred.
+   *
+   * @param index position where instance is to be inserted
+   * @param instance the instance to be added
+   */
+  //@ requires 0 <= index;
+  //@ requires index < m_Instances.size();
+  public void add(int index, /*@non_null@*/ Instance instance) {
+
+    Instance newInstance = (Instance)instance.copy();
+
+    newInstance.setDataset(this);
+    m_Instances.add(index, newInstance);
+  }
+
+  /**
+   * Returns an attribute.
+   *
+   * @param index the attribute's index (index starts with 0)
+   * @return the attribute at the given position
+   */
+  //@ requires 0 <= index;
+  //@ requires index < m_Attributes.size();
+  //@ ensures \result != null;
+  public /*@pure@*/ Attribute attribute(int index) {
+    
+    return (Attribute) m_Attributes.get(index);
+  }
+
+  /**
+   * Returns an attribute given its name. If there is more than
+   * one attribute with the same name, it returns the first one.
+   * Returns null if the attribute can't be found.
+   *
+   * @param name the attribute's name
+   * @return the attribute with the given name, null if the
+   * attribute can't be found
+   */ 
+  public /*@pure@*/ Attribute attribute(String name) {
+    
+    for (int i = 0; i < numAttributes(); i++) {
+      if (attribute(i).name().equals(name)) {
+	return attribute(i);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Checks for attributes of the given type in the dataset
+   *
+   * @param attType  the attribute type to look for
+   * @return         true if attributes of the given type are present
+   */
+  public boolean checkForAttributeType(int attType) {
+    
+    int i = 0;
+    
+    while (i < m_Attributes.size()) {
+      if (attribute(i++).type() == attType) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Checks for string attributes in the dataset
+   *
+   * @return true if string attributes are present, false otherwise
+   */
+  public /*@pure@*/ boolean checkForStringAttributes() {
+    return checkForAttributeType(Attribute.STRING);
+  }
+
+  /**
+   * Checks if the given instance is compatible
+   * with this dataset. Only looks at the size of
+   * the instance and the ranges of the values for 
+   * nominal and string attributes.
+   *
+   * @param instance the instance to check
+   * @return true if the instance is compatible with the dataset 
+   */
+  public /*@pure@*/ boolean checkInstance(Instance instance) {
+
+    if (instance.numAttributes() != numAttributes()) {
+      return false;
+    }
+    for (int i = 0; i < numAttributes(); i++) {
+      if (instance.isMissing(i)) {
+	continue;
+      } else if (attribute(i).isNominal() ||
+		 attribute(i).isString()) {
+	if (!(Utils.eq(instance.value(i),
+		       (double)(int)instance.value(i)))) {
+	  return false;
+	} else if (Utils.sm(instance.value(i), 0) ||
+		   Utils.gr(instance.value(i),
+			    attribute(i).numValues())) {
+	  return false;
+	}
+      }
+    }
+    return true;
+  }
+	
+  /**
+   * Returns the class attribute.
+   *
+   * @return the class attribute
+   * @throws UnassignedClassException if the class is not set
+   */
+  //@ requires classIndex() >= 0;
+  public /*@pure@*/ Attribute classAttribute() {
+
+    if (m_ClassIndex < 0) {
+      throw new UnassignedClassException("Class index is negative (not set)!");
+    }
+    return attribute(m_ClassIndex);
+  }
+
+  /**
+   * Returns the class attribute's index. Returns negative number
+   * if it's undefined.
+   *
+   * @return the class index as an integer
+   */
+  // ensures \result == m_ClassIndex;
+  public /*@pure@*/ int classIndex() {
+    
+    return m_ClassIndex;
+  }
+ 
+  /**
+   * Compactifies the set of instances. Decreases the capacity of
+   * the set so that it matches the number of instances in the set.
+   */
+  public void compactify() {
+
+    m_Instances.trimToSize();
+  }
+
+  /**
+   * Removes all instances from the set.
+   */
+  public void delete() {
+    
+    m_Instances = new ArrayList<Instance>();
+  }
+
+  /**
+   * Removes an instance at the given position from the set.
+   *
+   * @param index the instance's position (index starts with 0)
+   */
+  //@ requires 0 <= index && index < numInstances();
+  public void delete(int index) {
+    
+    m_Instances.remove(index);
+  }
+
+  /**
+   * Deletes an attribute at the given position 
+   * (0 to numAttributes() - 1). A deep copy of the attribute
+   * information is performed before the attribute is deleted.
+   *
+   * @param position the attribute's position (position starts with 0)
+   * @throws IllegalArgumentException if the given index is out of range 
+   *            or the class attribute is being deleted
+   */
+  //@ requires 0 <= position && position < numAttributes();
+  //@ requires position != classIndex();
+  public void deleteAttributeAt(int position) {
+	 
+    if ((position < 0) || (position >= m_Attributes.size())) {
+      throw new IllegalArgumentException("Index out of range");
+    }
+    if (position == m_ClassIndex) {
+      throw new IllegalArgumentException("Can't delete class attribute");
+    }
+    freshAttributeInfo();
+    if (m_ClassIndex > position) {
+      m_ClassIndex--;
+    }
+    m_Attributes.remove(position);
+    for (int i = position; i < m_Attributes.size(); i++) {
+      Attribute current = (Attribute)m_Attributes.get(i);
+      current.setIndex(current.index() - 1);
+    }
+    for (int i = 0; i < numInstances(); i++) {
+      instance(i).setDataset(null);
+      instance(i).deleteAttributeAt(position); 
+      instance(i).setDataset(this);
+    }
+  }
+
+  /**
+   * Deletes all attributes of the given type in the dataset. A deep copy of 
+   * the attribute information is performed before an attribute is deleted.
+   *
+   * @param attType the attribute type to delete
+   * @throws IllegalArgumentException if attribute couldn't be 
+   * successfully deleted (probably because it is the class attribute).
+   */
+  public void deleteAttributeType(int attType) {
+    int i = 0;
+    while (i < m_Attributes.size()) {
+      if (attribute(i).type() == attType) {
+        deleteAttributeAt(i);
+      } else {
+        i++;
+      }
+    }
+  }
+
+  /**
+   * Deletes all string attributes in the dataset. A deep copy of the attribute
+   * information is performed before an attribute is deleted.
+   *
+   * @throws IllegalArgumentException if string attribute couldn't be 
+   * successfully deleted (probably because it is the class attribute).
+   * @see #deleteAttributeType(int)
+   */
+  public void deleteStringAttributes() {
+    deleteAttributeType(Attribute.STRING);
+  }
+
+  /**
+   * Removes all instances with missing values for a particular
+   * attribute from the dataset.
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   */
+  //@ requires 0 <= attIndex && attIndex < numAttributes();
+  public void deleteWithMissing(int attIndex) {
+
+    ArrayList<Instance> newInstances = new ArrayList<Instance>(numInstances());
+
+    for (int i = 0; i < numInstances(); i++) {
+      if (!instance(i).isMissing(attIndex)) {
+	newInstances.add(instance(i));
+      }
+    }
+    m_Instances = newInstances;
+  }
+
+  /**
+   * Removes all instances with missing values for a particular
+   * attribute from the dataset.
+   *
+   * @param att the attribute
+   */
+  public void deleteWithMissing(/*@non_null@*/ Attribute att) {
+
+    deleteWithMissing(att.index());
+  }
+
+  /**
+   * Removes all instances with a missing class value
+   * from the dataset.
+   *
+   * @throws UnassignedClassException if class is not set
+   */
+  public void deleteWithMissingClass() {
+
+    if (m_ClassIndex < 0) {
+      throw new UnassignedClassException("Class index is negative (not set)!");
+    }
+    deleteWithMissing(m_ClassIndex);
+  }
+
+  /**
+   * Returns an enumeration of all the attributes.
+   *
+   * @return enumeration of all the attributes.
+   */
+  public /*@non_null pure@*/ Enumeration enumerateAttributes() {
+
+    return new WekaEnumeration(m_Attributes, m_ClassIndex);
+  }
+
+  /**
+   * Returns an enumeration of all instances in the dataset.
+   *
+   * @return enumeration of all instances in the dataset
+   */
+  public /*@non_null pure@*/ Enumeration enumerateInstances() {
+
+    return new WekaEnumeration(m_Instances);
+  }
+
+  /**
+   * Checks if two headers are equivalent. If not, then returns a message why
+   * they differ.
+   *
+   * @param dataset 	another dataset
+   * @return 		null if the header of the given dataset is equivalent 
+   * 			to this header, otherwise a message with details on
+   * 			why they differ
+   */
+  public String equalHeadersMsg(Instances dataset) {
+    // Check class and all attributes
+    if (m_ClassIndex != dataset.m_ClassIndex)
+      return "Class index differ: " + (m_ClassIndex+1) + " != " + (dataset.m_ClassIndex+1);
+
+    if (m_Attributes.size() != dataset.m_Attributes.size())
+      return "Different number of attributes: " + m_Attributes.size() + " != " + dataset.m_Attributes.size();
+    
+    for (int i = 0; i < m_Attributes.size(); i++) {
+      String msg = attribute(i).equalsMsg(dataset.attribute(i));
+      if (msg != null)
+	return "Attributes differ at position " + (i+1) + ":\n" + msg;
+    }
+    
+    return null;
+  }
+
+  /**
+   * Checks if two headers are equivalent.
+   *
+   * @param dataset another dataset
+   * @return true if the header of the given dataset is equivalent 
+   * to this header
+   */
+  public /*@pure@*/ boolean equalHeaders(Instances dataset){
+    return (equalHeadersMsg(dataset) == null);
+  }
+ 
+  /**
+   * Returns the first instance in the set.
+   *
+   * @return the first instance in the set
+   */
+  //@ requires numInstances() > 0;
+  public /*@non_null pure@*/ Instance firstInstance() {
+    
+    return (Instance)m_Instances.get(0);
+  }
+
+  /**
+   * Returns a random number generator. The initial seed of the random
+   * number generator depends on the given seed and the hash code of
+   * a string representation of a instances chosen based on the given
+   * seed. 
+   *
+   * @param seed the given seed
+   * @return the random number generator
+   */
+  public Random getRandomNumberGenerator(long seed) {
+
+    Random r = new Random(seed);
+    r.setSeed(instance(r.nextInt(numInstances())).toStringNoWeight().hashCode() + seed);
+    return r;
+  }
+ 
+  /**
+   * Inserts an attribute at the given position (0 to 
+   * numAttributes()) and sets all values to be missing.
+   * Shallow copies the attribute before it is inserted, and performs
+   * a deep copy of the existing attribute information.
+   *
+   * @param att the attribute to be inserted
+   * @param position the attribute's position (position starts with 0)
+   * @throws IllegalArgumentException if the given index is out of range
+   */
+  //@ requires 0 <= position;
+  //@ requires position <= numAttributes();
+  public void insertAttributeAt(/*@non_null@*/ Attribute att, int position) {
+	 
+    if ((position < 0) ||
+	(position > m_Attributes.size())) {
+      throw new IllegalArgumentException("Index out of range");
+    }
+    att = (Attribute)att.copy();
+    freshAttributeInfo();
+    att.setIndex(position);
+    m_Attributes.add(position, att);
+    for (int i = position + 1; i < m_Attributes.size(); i++) {
+      Attribute current = (Attribute)m_Attributes.get(i);
+      current.setIndex(current.index() + 1);
+    }
+    for (int i = 0; i < numInstances(); i++) {
+      instance(i).setDataset(null);
+      instance(i).insertAttributeAt(position);
+      instance(i).setDataset(this);
+    }
+    if (m_ClassIndex >= position) {
+      m_ClassIndex++;
+    }
+  }
+
+  /**
+   * Returns the instance at the given position.
+   *
+   * @param index the instance's index (index starts with 0)
+   * @return the instance at the given position
+   */
+  //@ requires 0 <= index;
+  //@ requires index < numInstances();
+  public /*@non_null pure@*/ Instance instance(int index) {
+
+    return m_Instances.get(index);
+  }
+
+  /**
+   * Returns the instance at the given position.
+   *
+   * @param index the instance's index (index starts with 0)
+   * @return the instance at the given position
+   */
+  //@ requires 0 <= index;
+  //@ requires index < numInstances();
+  public /*@non_null pure@*/ Instance get(int index) {
+
+    return m_Instances.get(index);
+  }
+
+  /**
+   * Returns the kth-smallest attribute value of a numeric attribute.
+   * Note that calling this method will change the order of the data!
+   *
+   * @param att the Attribute object
+   * @param k the value of k
+   * @return the kth-smallest value
+   */
+  public double kthSmallestValue(Attribute att, int k) {
+
+    return kthSmallestValue(att.index(), k);
+  }
+
+  /**
+   * Returns the kth-smallest attribute value of a numeric attribute.
+   * Note that calling this method will change the order of the data!
+   * The number of non-missing values in the data must be as least
+   * as last as k for this to work.
+   *
+   * @param attIndex the attribute's index
+   * @param k the value of k
+   * @return the kth-smallest value
+   */
+  public double kthSmallestValue(int attIndex, int k) {
+    
+    if (!attribute(attIndex).isNumeric()) {
+      throw new IllegalArgumentException("Instances: attribute must be numeric to compute kth-smallest value.");
+    }
+
+    int i,j;
+
+    // move all instances with missing values to end
+    j = numInstances() - 1;
+    i = 0;
+    while (i <= j) {
+      if (instance(j).isMissing(attIndex)) {
+	j--;
+      } else {
+	if (instance(i).isMissing(attIndex)) {
+	  swap(i,j);
+	  j--;
+	}
+	i++;
+      }
+    }
+
+    if ((k < 1) || (k > j+1)) {
+      throw new IllegalArgumentException("Instances: value for k for computing kth-smallest value too large.");
+    }
+
+    return instance(select(attIndex, 0, j, k)).value(attIndex);
+  }
+
+  /**
+   * Returns the last instance in the set.
+   *
+   * @return the last instance in the set
+   */
+  //@ requires numInstances() > 0;
+  public /*@non_null pure@*/ Instance lastInstance() {
+    
+    return (Instance)m_Instances.get(m_Instances.size() - 1);
+  }
+
+  /**
+   * Returns the mean (mode) for a numeric (nominal) attribute as
+   * a floating-point value. Returns 0 if the attribute is neither nominal nor 
+   * numeric. If all values are missing it returns zero.
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   * @return the mean or the mode
+   */
+  public /*@pure@*/ double meanOrMode(int attIndex) {
+
+    double result, found;
+    int [] counts;
+
+    if (attribute(attIndex).isNumeric()) {
+      result = found = 0;
+      for (int j = 0; j < numInstances(); j++) {
+	if (!instance(j).isMissing(attIndex)) {
+	  found += instance(j).weight();
+	  result += instance(j).weight()*instance(j).value(attIndex);
+	}
+      }
+      if (found <= 0) {
+	return 0;
+      } else {
+	return result / found;
+      }
+    } else if (attribute(attIndex).isNominal()) {
+      counts = new int[attribute(attIndex).numValues()];
+      for (int j = 0; j < numInstances(); j++) {
+	if (!instance(j).isMissing(attIndex)) {
+	  counts[(int) instance(j).value(attIndex)] += instance(j).weight();
+	}
+      }
+      return (double)Utils.maxIndex(counts);
+    } else {
+      return 0;
+    }
+  }
+
+  /**
+   * Returns the mean (mode) for a numeric (nominal) attribute as a
+   * floating-point value.  Returns 0 if the attribute is neither
+   * nominal nor numeric.  If all values are missing it returns zero.
+   *
+   * @param att the attribute
+   * @return the mean or the mode 
+   */
+  public /*@pure@*/ double meanOrMode(Attribute att) {
+
+    return meanOrMode(att.index());
+  }
+
+  /**
+   * Returns the number of attributes.
+   *
+   * @return the number of attributes as an integer
+   */
+  //@ ensures \result == m_Attributes.size();
+  public /*@pure@*/ int numAttributes() {
+
+    return m_Attributes.size();
+  }
+
+  /**
+   * Returns the number of class labels.
+   *
+   * @return the number of class labels as an integer if the class 
+   * attribute is nominal, 1 otherwise.
+   * @throws UnassignedClassException if the class is not set
+   */
+  //@ requires classIndex() >= 0;
+  public /*@pure@*/ int numClasses() {
+    
+    if (m_ClassIndex < 0) {
+      throw new UnassignedClassException("Class index is negative (not set)!");
+    }
+    if (!classAttribute().isNominal()) {
+      return 1;
+    } else {
+      return classAttribute().numValues();
+    }
+  }
+
+  /**
+   * Returns the number of distinct values of a given attribute.
+   * Returns the number of instances if the attribute is a
+   * string attribute. The value 'missing' is not counted.
+   *
+   * @param attIndex the attribute (index starts with 0)
+   * @return the number of distinct values of a given attribute
+   */
+  //@ requires 0 <= attIndex;
+  //@ requires attIndex < numAttributes();
+  public /*@pure@*/ int numDistinctValues(int attIndex) {
+
+    if (attribute(attIndex).isNumeric()) {
+      double [] attVals = attributeToDoubleArray(attIndex);
+      int [] sorted = Utils.sort(attVals);
+      double prev = 0;
+      int counter = 0;
+      for (int i = 0; i < sorted.length; i++) {
+	Instance current = instance(sorted[i]);
+	if (current.isMissing(attIndex)) {
+	  break;
+	}
+	if ((i == 0) || 
+	    (current.value(attIndex) > prev)) {
+	  prev = current.value(attIndex);
+	  counter++;
+	}
+      }
+      return counter;
+    } else {
+      return attribute(attIndex).numValues();
+    }
+  }
+
+  /**
+   * Returns the number of distinct values of a given attribute.
+   * Returns the number of instances if the attribute is a
+   * string attribute. The value 'missing' is not counted.
+   *
+   * @param att the attribute
+   * @return the number of distinct values of a given attribute
+   */
+  public /*@pure@*/ int numDistinctValues(/*@non_null@*/Attribute att) {
+
+    return numDistinctValues(att.index());
+  }
+  
+  /**
+   * Returns the number of instances in the dataset.
+   *
+   * @return the number of instances in the dataset as an integer
+   */
+  //@ ensures \result == m_Instances.size();
+  public /*@pure@*/ int numInstances() {
+
+    return m_Instances.size();
+  }
+  
+  /**
+   * Returns the number of instances in the dataset.
+   *
+   * @return the number of instances in the dataset as an integer
+   */
+  //@ ensures \result == m_Instances.size();
+  public /*@pure@*/ int size() {
+
+    return m_Instances.size();
+  }
+
+  /**
+   * Shuffles the instances in the set so that they are ordered 
+   * randomly.
+   *
+   * @param random a random number generator
+   */
+  public void randomize(Random random) {
+
+    for (int j = numInstances() - 1; j > 0; j--)
+      swap(j, random.nextInt(j+1));
+  }
+  
+  /**
+   * Reads a single instance from the reader and appends it
+   * to the dataset.  Automatically expands the dataset if it
+   * is not large enough to hold the instance. This method does
+   * not check for carriage return at the end of the line.
+   *
+   * @param reader the reader 
+   * @return false if end of file has been reached
+   * @throws IOException if the information is not read 
+   * successfully
+   * @deprecated instead of using this method in conjunction with the
+   * <code>readInstance(Reader)</code> method, one should use the 
+   * <code>ArffLoader</code> or <code>DataSource</code> class instead.
+   * @see weka.core.converters.ArffLoader
+   * @see weka.core.converters.ConverterUtils.DataSource
+   */ 
+  @Deprecated public boolean readInstance(Reader reader) throws IOException {
+
+    ArffReader arff = new ArffReader(reader, this, m_Lines, 1);
+    Instance inst = arff.readInstance(arff.getData(), false);
+    m_Lines = arff.getLineNo();
+    if (inst != null) {
+      add(inst);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }    
+
+  /**
+   * Returns the relation's name.
+   *
+   * @return the relation's name as a string
+   */
+  //@ ensures \result == m_RelationName;
+  public /*@pure@*/ String relationName() {
+
+    return m_RelationName;
+  }
+
+  /**
+   * Removes the instance at the given position.
+   *
+   * @param index the instance's index (index starts with 0)
+   * @return the instance at the given position
+   */
+  //@ requires 0 <= index;
+  //@ requires index < numInstances();
+  public Instance remove(int index) {
+
+    return m_Instances.remove(index);
+  }
+
+  /**
+   * Renames an attribute. This change only affects this
+   * dataset.
+   *
+   * @param att the attribute's index (index starts with 0)
+   * @param name the new name
+   */
+  public void renameAttribute(int att, String name) {
+
+    Attribute newAtt = attribute(att).copy(name);
+    ArrayList<Attribute> newVec = new ArrayList<Attribute>(numAttributes());
+
+    for (int i = 0; i < numAttributes(); i++) {
+      if (i == att) {
+	newVec.add(newAtt);
+      } else {
+	newVec.add(attribute(i));
+      }
+    }
+    m_Attributes = newVec;
+  }
+
+  /**
+   * Renames an attribute. This change only affects this
+   * dataset.
+   *
+   * @param att the attribute
+   * @param name the new name
+   */
+  public void renameAttribute(Attribute att, String name) {
+
+    renameAttribute(att.index(), name);
+  }
+
+  /**
+   * Renames the value of a nominal (or string) attribute value. This
+   * change only affects this dataset.
+   *
+   * @param att the attribute's index (index starts with 0)
+   * @param val the value's index (index starts with 0)
+   * @param name the new name 
+   */
+  public void renameAttributeValue(int att, int val, String name) {
+
+    Attribute newAtt = (Attribute)attribute(att).copy();
+    ArrayList<Attribute> newVec = new ArrayList<Attribute>(numAttributes());
+
+    newAtt.setValue(val, name);
+    for (int i = 0; i < numAttributes(); i++) {
+      if (i == att) {
+	newVec.add(newAtt);
+      } else {
+	newVec.add(attribute(i));
+      }
+    }
+    m_Attributes = newVec;
+  }
+
+  /**
+   * Renames the value of a nominal (or string) attribute value. This
+   * change only affects this dataset.
+   *
+   * @param att the attribute
+   * @param val the value
+   * @param name the new name
+   */
+  public void renameAttributeValue(Attribute att, String val, 
+                                         String name) {
+
+    int v = att.indexOfValue(val);
+    if (v == -1) throw new IllegalArgumentException(val + " not found");
+    renameAttributeValue(att.index(), v, name);
+  }
+
+  /**
+   * Creates a new dataset of the same size using random sampling
+   * with replacement.
+   *
+   * @param random a random number generator
+   * @return the new dataset
+   */
+  public Instances resample(Random random) {
+
+    Instances newData = new Instances(this, numInstances());
+    while (newData.numInstances() < numInstances()) {
+      newData.add(instance(random.nextInt(numInstances())));
+    }
+    return newData;
+  }
+
+  /**
+   * Creates a new dataset of the same size using random sampling
+   * with replacement according to the current instance weights. The
+   * weights of the instances in the new dataset are set to one.
+   *
+   * @param random a random number generator
+   * @return the new dataset
+   */
+  public Instances resampleWithWeights(Random random) {
+
+    double [] weights = new double[numInstances()];
+    for (int i = 0; i < weights.length; i++) {
+      weights[i] = instance(i).weight();
+    }
+    return resampleWithWeights(random, weights);
+  }
+
+
+  /**
+   * Creates a new dataset of the same size using random sampling
+   * with replacement according to the given weight vector. The
+   * weights of the instances in the new dataset are set to one.
+   * The length of the weight vector has to be the same as the
+   * number of instances in the dataset, and all weights have to
+   * be positive.
+   *
+   * @param random a random number generator
+   * @param weights the weight vector
+   * @return the new dataset
+   * @throws IllegalArgumentException if the weights array is of the wrong
+   * length or contains negative weights.
+   */
+  public Instances resampleWithWeights(Random random, 
+					     double[] weights) {
+
+    if (weights.length != numInstances()) {
+      throw new IllegalArgumentException("weights.length != numInstances.");
+    }
+    Instances newData = new Instances(this, numInstances());
+    if (numInstances() == 0) {
+      return newData;
+    }
+    double[] probabilities = new double[numInstances()];
+    double sumProbs = 0, sumOfWeights = Utils.sum(weights);
+    for (int i = 0; i < numInstances(); i++) {
+      sumProbs += random.nextDouble();
+      probabilities[i] = sumProbs;
+    }
+    Utils.normalize(probabilities, sumProbs / sumOfWeights);
+
+    // Make sure that rounding errors don't mess things up
+    probabilities[numInstances() - 1] = sumOfWeights;
+    int k = 0; int l = 0;
+    sumProbs = 0;
+    while ((k < numInstances() && (l < numInstances()))) {
+      if (weights[l] < 0) {
+	throw new IllegalArgumentException("Weights have to be positive.");
+      }
+      sumProbs += weights[l];
+      while ((k < numInstances()) &&
+	     (probabilities[k] <= sumProbs)) { 
+	newData.add(instance(l));
+	newData.instance(k).setWeight(1);
+	k++;
+      }
+      l++;
+    }
+    return newData;
+  }
+
+  /**
+   * Replaces the instance at the given position.
+   * Shallow copies instance before it is added. Does not
+   * check if the instance is compatible with the dataset.
+   * Note: String or relational values are not transferred.
+   *
+   * @param index position where instance is to be inserted
+   * @param instance the instance to be inserted
+   * @return the instance previously at that position
+   */
+  //@ requires 0 <= index;
+  //@ requires index < m_Instances.size();
+  public Instance set(int index, /*@non_null@*/ Instance instance) {
+
+    Instance newInstance = (Instance)instance.copy();
+    Instance oldInstance = m_Instances.get(index);
+
+    newInstance.setDataset(this);
+    m_Instances.set(index, newInstance);
+
+    return oldInstance;
+  }
+
+  /** 
+   * Sets the class attribute.
+   *
+   * @param att attribute to be the class
+   */
+  public void setClass(Attribute att) {
+
+    m_ClassIndex = att.index();
+  }
+
+  /** 
+   * Sets the class index of the set.
+   * If the class index is negative there is assumed to be no class.
+   * (ie. it is undefined)
+   *
+   * @param classIndex the new class index (index starts with 0)
+   * @throws IllegalArgumentException if the class index is too big or < 0
+   */
+  public void setClassIndex(int classIndex) {
+
+    if (classIndex >= numAttributes()) {
+      throw new IllegalArgumentException("Invalid class index: " + classIndex);
+    }
+    m_ClassIndex = classIndex;
+  }
+
+  /**
+   * Sets the relation's name.
+   *
+   * @param newName the new relation name.
+   */
+  public void setRelationName(/*@non_null@*/String newName) {
+    
+    m_RelationName = newName;
+  }
+
+  /**
+   * Sorts the instances based on an attribute. For numeric attributes, 
+   * instances are sorted in ascending order. For nominal attributes, 
+   * instances are sorted based on the attribute label ordering 
+   * specified in the header. Instances with missing values for the 
+   * attribute are placed at the end of the dataset.
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   */
+  public void sort(int attIndex) {
+
+    int i,j;
+
+    // move all instances with missing values to end
+    j = numInstances() - 1;
+    i = 0;
+    while (i <= j) {
+      if (instance(j).isMissing(attIndex)) {
+	j--;
+      } else {
+	if (instance(i).isMissing(attIndex)) {
+	  swap(i,j);
+	  j--;
+	}
+	i++;
+      }
+    }
+    quickSort(attIndex, 0, j);
+  }
+
+  /**
+   * Sorts the instances based on an attribute. For numeric attributes, 
+   * instances are sorted into ascending order. For nominal attributes, 
+   * instances are sorted based on the attribute label ordering 
+   * specified in the header. Instances with missing values for the 
+   * attribute are placed at the end of the dataset.
+   *
+   * @param att the attribute
+   */
+  public void sort(Attribute att) {
+
+    sort(att.index());
+  }
+
+  /**
+   * Stratifies a set of instances according to its class values 
+   * if the class attribute is nominal (so that afterwards a 
+   * stratified cross-validation can be performed).
+   *
+   * @param numFolds the number of folds in the cross-validation
+   * @throws UnassignedClassException if the class is not set
+   */
+  public void stratify(int numFolds) {
+    
+    if (numFolds <= 1) {
+      throw new IllegalArgumentException("Number of folds must be greater than 1");
+    }
+    if (m_ClassIndex < 0) {
+      throw new UnassignedClassException("Class index is negative (not set)!");
+    }
+    if (classAttribute().isNominal()) {
+
+      // sort by class
+      int index = 1;
+      while (index < numInstances()) {
+	Instance instance1 = instance(index - 1);
+	for (int j = index; j < numInstances(); j++) {
+	  Instance instance2 = instance(j);
+	  if ((instance1.classValue() == instance2.classValue()) ||
+	      (instance1.classIsMissing() && 
+	       instance2.classIsMissing())) {
+	    swap(index,j);
+	    index++;
+	  }
+	}
+	index++;
+      }
+      stratStep(numFolds);
+    }
+  }
+ 
+  /**
+   * Computes the sum of all the instances' weights.
+   *
+   * @return the sum of all the instances' weights as a double
+   */
+  public /*@pure@*/ double sumOfWeights() {
+    
+    double sum = 0;
+
+    for (int i = 0; i < numInstances(); i++) {
+      sum += instance(i).weight();
+    }
+    return sum;
+  }
+
+  /**
+   * Creates the test set for one fold of a cross-validation on 
+   * the dataset.
+   *
+   * @param numFolds the number of folds in the cross-validation. Must
+   * be greater than 1.
+   * @param numFold 0 for the first fold, 1 for the second, ...
+   * @return the test set as a set of weighted instances
+   * @throws IllegalArgumentException if the number of folds is less than 2
+   * or greater than the number of instances.
+   */
+  //@ requires 2 <= numFolds && numFolds < numInstances();
+  //@ requires 0 <= numFold && numFold < numFolds;
+  public Instances testCV(int numFolds, int numFold) {
+
+    int numInstForFold, first, offset;
+    Instances test;
+    
+    if (numFolds < 2) {
+      throw new IllegalArgumentException("Number of folds must be at least 2!");
+    }
+    if (numFolds > numInstances()) {
+      throw new IllegalArgumentException("Can't have more folds than instances!");
+    }
+    numInstForFold = numInstances() / numFolds;
+    if (numFold < numInstances() % numFolds){
+      numInstForFold++;
+      offset = numFold;
+    }else
+      offset = numInstances() % numFolds;
+    test = new Instances(this, numInstForFold);
+    first = numFold * (numInstances() / numFolds) + offset;
+    copyInstances(first, test, numInstForFold);
+    return test;
+  }
+ 
+  /**
+   * Returns the dataset as a string in ARFF format. Strings
+   * are quoted if they contain whitespace characters, or if they
+   * are a question mark.
+   *
+   * @return the dataset in ARFF format as a string
+   */
+  public String toString() {
+    
+    StringBuffer text = new StringBuffer();
+    
+    text.append(ARFF_RELATION).append(" ").
+      append(Utils.quote(m_RelationName)).append("\n\n");
+    for (int i = 0; i < numAttributes(); i++) {
+      text.append(attribute(i)).append("\n");
+    }
+    text.append("\n").append(ARFF_DATA).append("\n");
+
+    text.append(stringWithoutHeader());
+    return text.toString();
+  }
+
+  /**
+   * Returns the instances in the dataset as a string in ARFF format. Strings
+   * are quoted if they contain whitespace characters, or if they
+   * are a question mark.
+   *
+   * @return the dataset in ARFF format as a string
+   */
+  protected String stringWithoutHeader() {
+    
+    StringBuffer text = new StringBuffer();
+
+    for (int i = 0; i < numInstances(); i++) {
+      text.append(instance(i));
+      if (i < numInstances() - 1) {
+	text.append('\n');
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Creates the training set for one fold of a cross-validation 
+   * on the dataset. 
+   *
+   * @param numFolds the number of folds in the cross-validation. Must
+   * be greater than 1.
+   * @param numFold 0 for the first fold, 1 for the second, ...
+   * @return the training set 
+   * @throws IllegalArgumentException if the number of folds is less than 2
+   * or greater than the number of instances.
+   */
+  //@ requires 2 <= numFolds && numFolds < numInstances();
+  //@ requires 0 <= numFold && numFold < numFolds;
+  public Instances trainCV(int numFolds, int numFold) {
+
+    int numInstForFold, first, offset;
+    Instances train;
+ 
+    if (numFolds < 2) {
+      throw new IllegalArgumentException("Number of folds must be at least 2!");
+    }
+    if (numFolds > numInstances()) {
+      throw new IllegalArgumentException("Can't have more folds than instances!");
+    }
+    numInstForFold = numInstances() / numFolds;
+    if (numFold < numInstances() % numFolds) {
+      numInstForFold++;
+      offset = numFold;
+    }else
+      offset = numInstances() % numFolds;
+    train = new Instances(this, numInstances() - numInstForFold);
+    first = numFold * (numInstances() / numFolds) + offset;
+    copyInstances(0, train, first);
+    copyInstances(first + numInstForFold, train,
+		  numInstances() - first - numInstForFold);
+
+    return train;
+  }
+
+  /**
+   * Creates the training set for one fold of a cross-validation 
+   * on the dataset. The data is subsequently randomized based
+   * on the given random number generator.
+   *
+   * @param numFolds the number of folds in the cross-validation. Must
+   * be greater than 1.
+   * @param numFold 0 for the first fold, 1 for the second, ...
+   * @param random the random number generator
+   * @return the training set 
+   * @throws IllegalArgumentException if the number of folds is less than 2
+   * or greater than the number of instances.
+   */
+  //@ requires 2 <= numFolds && numFolds < numInstances();
+  //@ requires 0 <= numFold && numFold < numFolds;
+  public Instances trainCV(int numFolds, int numFold, Random random) {
+
+    Instances train = trainCV(numFolds, numFold);
+    train.randomize(random);
+    return train;
+  }
+
+  /**
+   * Computes the variance for a numeric attribute.
+   *
+   * @param attIndex the numeric attribute (index starts with 0)
+   * @return the variance if the attribute is numeric
+   * @throws IllegalArgumentException if the attribute is not numeric
+   */
+  public /*@pure@*/ double variance(int attIndex) {
+  
+    double sum = 0, sumSquared = 0, sumOfWeights = 0;
+
+    if (!attribute(attIndex).isNumeric()) {
+      throw new IllegalArgumentException("Can't compute variance because attribute is " +
+			  "not numeric!");
+    }
+    for (int i = 0; i < numInstances(); i++) {
+      if (!instance(i).isMissing(attIndex)) {
+	sum += instance(i).weight() * 
+	  instance(i).value(attIndex);
+	sumSquared += instance(i).weight() * 
+	  instance(i).value(attIndex) *
+	  instance(i).value(attIndex);
+	sumOfWeights += instance(i).weight();
+      }
+    }
+    if (sumOfWeights <= 1) {
+      return 0;
+    }
+    double result = (sumSquared - (sum * sum / sumOfWeights)) / 
+      (sumOfWeights - 1);
+
+    // We don't like negative variance
+    if (result < 0) {
+      return 0;
+    } else {
+      return result;
+    }
+  }
+
+  /**
+   * Computes the variance for a numeric attribute.
+   *
+   * @param att the numeric attribute
+   * @return the variance if the attribute is numeric
+   * @throws IllegalArgumentException if the attribute is not numeric
+   */
+  public /*@pure@*/ double variance(Attribute att) {
+    
+    return variance(att.index());
+  }
+  
+  /**
+   * Calculates summary statistics on the values that appear in this
+   * set of instances for a specified attribute.
+   *
+   * @param index the index of the attribute to summarize (index starts with 0)
+   * @return an AttributeStats object with it's fields calculated.
+   */
+  //@ requires 0 <= index && index < numAttributes();
+  public AttributeStats attributeStats(int index) {
+
+    AttributeStats result = new AttributeStats();
+    if (attribute(index).isNominal()) {
+      result.nominalCounts = new int [attribute(index).numValues()];
+      result.nominalWeights = new double[attribute(index).numValues()];
+    }
+    if (attribute(index).isNumeric()) {
+      result.numericStats = new weka.experiment.Stats();
+    }
+    result.totalCount = numInstances();
+
+    double [] attVals = attributeToDoubleArray(index);
+    int [] sorted = Utils.sort(attVals);
+    int currentCount = 0;
+    double currentWeight = 0;
+    double prev = Double.NaN;
+    for (int j = 0; j < numInstances(); j++) {
+      Instance current = instance(sorted[j]);
+      if (current.isMissing(index)) {
+	result.missingCount = numInstances() - j;
+	break;
+      }
+      if (current.value(index) == prev) {
+	currentCount++;
+	currentWeight += current.weight();
+      } else {
+	result.addDistinct(prev, currentCount, currentWeight);
+	currentCount = 1;
+	currentWeight = current.weight();
+	prev = current.value(index);
+      }
+    }
+    result.addDistinct(prev, currentCount, currentWeight);
+    result.distinctCount--; // So we don't count "missing" as a value 
+    return result;
+  }
+  
+  /**
+   * Gets the value of all instances in this dataset for a particular
+   * attribute. Useful in conjunction with Utils.sort to allow iterating
+   * through the dataset in sorted order for some attribute.
+   *
+   * @param index the index of the attribute.
+   * @return an array containing the value of the desired attribute for
+   * each instance in the dataset. 
+   */
+  //@ requires 0 <= index && index < numAttributes();
+  public /*@pure@*/ double [] attributeToDoubleArray(int index) {
+
+    double [] result = new double[numInstances()];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = instance(i).value(index);
+    }
+    return result;
+  }
+
+  /**
+   * Generates a string summarizing the set of instances. Gives a breakdown
+   * for each attribute indicating the number of missing/discrete/unique
+   * values and other information.
+   *
+   * @return a string summarizing the dataset
+   */
+  public String toSummaryString() {
+
+    StringBuffer result = new StringBuffer();
+    result.append("Relation Name:  ").append(relationName()).append('\n');
+    result.append("Num Instances:  ").append(numInstances()).append('\n');
+    result.append("Num Attributes: ").append(numAttributes()).append('\n');
+    result.append('\n');
+
+    result.append(Utils.padLeft("", 5)).append(Utils.padRight("Name", 25));
+    result.append(Utils.padLeft("Type", 5)).append(Utils.padLeft("Nom", 5));
+    result.append(Utils.padLeft("Int", 5)).append(Utils.padLeft("Real", 5));
+    result.append(Utils.padLeft("Missing", 12));
+    result.append(Utils.padLeft("Unique", 12));
+    result.append(Utils.padLeft("Dist", 6)).append('\n');
+    for (int i = 0; i < numAttributes(); i++) {
+      Attribute a = attribute(i);
+      AttributeStats as = attributeStats(i);
+      result.append(Utils.padLeft("" + (i + 1), 4)).append(' ');
+      result.append(Utils.padRight(a.name(), 25)).append(' ');
+      long percent;
+      switch (a.type()) {
+      case Attribute.NOMINAL:
+	result.append(Utils.padLeft("Nom", 4)).append(' ');
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      case Attribute.NUMERIC:
+	result.append(Utils.padLeft("Num", 4)).append(' ');
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      case Attribute.DATE:
+	result.append(Utils.padLeft("Dat", 4)).append(' ');
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      case Attribute.STRING:
+	result.append(Utils.padLeft("Str", 4)).append(' ');
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      case Attribute.RELATIONAL:
+	result.append(Utils.padLeft("Rel", 4)).append(' ');
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      default:
+	result.append(Utils.padLeft("???", 4)).append(' ');
+	result.append(Utils.padLeft("" + 0, 3)).append("% ");
+	percent = Math.round(100.0 * as.intCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	percent = Math.round(100.0 * as.realCount / as.totalCount);
+	result.append(Utils.padLeft("" + percent, 3)).append("% ");
+	break;
+      }
+      result.append(Utils.padLeft("" + as.missingCount, 5)).append(" /");
+      percent = Math.round(100.0 * as.missingCount / as.totalCount);
+      result.append(Utils.padLeft("" + percent, 3)).append("% ");
+      result.append(Utils.padLeft("" + as.uniqueCount, 5)).append(" /");
+      percent = Math.round(100.0 * as.uniqueCount / as.totalCount);
+      result.append(Utils.padLeft("" + percent, 3)).append("% ");
+      result.append(Utils.padLeft("" + as.distinctCount, 5)).append(' ');
+      result.append('\n');
+    }
+    return result.toString();
+  }
+
+  /**
+   * Copies instances from one set to the end of another 
+   * one.
+   *
+   * @param from the position of the first instance to be copied
+   * @param dest the destination for the instances
+   * @param num the number of instances to be copied
+   */
+  //@ requires 0 <= from && from <= numInstances() - num;
+  //@ requires 0 <= num;
+  protected void copyInstances(int from, /*@non_null@*/ Instances dest, int num) {
+    
+    for (int i = 0; i < num; i++) {
+      dest.add(instance(from + i));
+    }
+  }
+  
+  /**
+   * Replaces the attribute information by a clone of
+   * itself.
+   */
+  protected void freshAttributeInfo() {
+
+    ArrayList<Attribute> newList = new ArrayList<Attribute>(m_Attributes.size());
+    for (Attribute att : m_Attributes) {
+      newList.add((Attribute)att.copy());
+    }
+    m_Attributes = newList;
+  }
+ 
+  /**
+   * Returns string including all instances, their weights and
+   * their indices in the original dataset.
+   *
+   * @return description of instance and its weight as a string
+   */
+  protected /*@pure@*/ String instancesAndWeights(){
+
+    StringBuffer text = new StringBuffer();
+
+    for (int i = 0; i < numInstances(); i++) {
+      text.append(instance(i) + " " + instance(i).weight());
+      if (i < numInstances() - 1) {
+	text.append("\n");
+      }
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   * @param l the first index of the subset (index starts with 0)
+   * @param r the last index of the subset (index starts with 0)
+   *
+   * @return the index of the middle element
+   */
+  //@ requires 0 <= attIndex && attIndex < numAttributes();
+  //@ requires 0 <= left && left <= right && right < numInstances();
+  protected int partition(int attIndex, int l, int r) {
+    
+    double pivot = instance((l + r) / 2).value(attIndex);
+
+    while (l < r) {
+      while ((instance(l).value(attIndex) < pivot) && (l < r)) {
+        l++;
+      }
+      while ((instance(r).value(attIndex) > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        swap(l, r);
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (instance(r).value(attIndex) > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+  
+  /**
+   * Implements quicksort according to Manber's "Introduction to
+   * Algorithms".
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   * @param left the first index of the subset to be sorted (index starts with 0)
+   * @param right the last index of the subset to be sorted (index starts with 0)
+   */
+  //@ requires 0 <= attIndex && attIndex < numAttributes();
+  //@ requires 0 <= first && first <= right && right < numInstances();
+  protected void quickSort(int attIndex, int left, int right) {
+
+    if (left < right) {
+      int middle = partition(attIndex, left, right);
+      quickSort(attIndex, left, middle);
+      quickSort(attIndex, middle + 1, right);
+    }
+  }
+  
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param attIndex the attribute's index (index starts with 0)
+   * @param left the first index of the subset (index starts with 0)
+   * @param right the last index of the subset (index starts with 0)
+   * @param k the value of k
+   *
+   * @return the index of the kth-smallest element
+   */
+  //@ requires 0 <= attIndex && attIndex < numAttributes();
+  //@ requires 0 <= first && first <= right && right < numInstances();
+  protected int select(int attIndex, int left, int right, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(attIndex, left, right);
+      if ((middle - left + 1) >= k) {
+        return select(attIndex, left, middle, k);
+      } else {
+        return select(attIndex, middle + 1, right, k - (middle - left + 1));
+      }
+    }
+  }
+
+  /**
+   * Help function needed for stratification of set.
+   *
+   * @param numFolds the number of folds for the stratification
+   */
+  protected void stratStep (int numFolds){
+    
+    ArrayList<Instance> newVec = new ArrayList<Instance>(m_Instances.size());
+    int start = 0, j;
+
+    // create stratified batch
+    while (newVec.size() < numInstances()) {
+      j = start;
+      while (j < numInstances()) {
+	newVec.add(instance(j));
+	j = j + numFolds;
+      }
+      start++;
+    }
+    m_Instances = newVec;
+  }
+  
+  /**
+   * Swaps two instances in the set.
+   *
+   * @param i the first instance's index (index starts with 0)
+   * @param j the second instance's index (index starts with 0)
+   */
+  //@ requires 0 <= i && i < numInstances();
+  //@ requires 0 <= j && j < numInstances();
+  public void swap(int i, int j){
+    
+    Instance in = m_Instances.get(i);
+    m_Instances.set(i, m_Instances.get(j));
+    m_Instances.set(j, in);
+  }
+
+  /**
+   * Merges two sets of Instances together. The resulting set will have
+   * all the attributes of the first set plus all the attributes of the 
+   * second set. The number of instances in both sets must be the same.
+   *
+   * @param first the first set of Instances
+   * @param second the second set of Instances
+   * @return the merged set of Instances
+   * @throws IllegalArgumentException if the datasets are not the same size
+   */
+  public static Instances mergeInstances(Instances first, Instances second) {
+
+    if (first.numInstances() != second.numInstances()) {
+      throw new IllegalArgumentException("Instance sets must be of the same size");
+    }
+
+    // Create the vector of merged attributes
+    ArrayList<Attribute> newAttributes = new ArrayList<Attribute>();
+    for (int i = 0; i < first.numAttributes(); i++) {
+      newAttributes.add(first.attribute(i));
+    }
+    for (int i = 0; i < second.numAttributes(); i++) {
+      newAttributes.add(second.attribute(i));
+    }
+    
+    // Create the set of Instances
+    Instances merged = new Instances(first.relationName() + '_'
+				     + second.relationName(), 
+				     newAttributes, 
+				     first.numInstances());
+    // Merge each instance
+    for (int i = 0; i < first.numInstances(); i++) {
+      merged.add(first.instance(i).mergeInstance(second.instance(i)));
+    }
+    return merged;
+  }
+
+  /**
+   * Method for testing this class.
+   *
+   * @param argv should contain one element: the name of an ARFF file
+   */
+  //@ requires argv != null;
+  //@ requires argv.length == 1;
+  //@ requires argv[0] != null;
+  public static void test(String [] argv) {
+
+    Instances instances, secondInstances, train, test, empty;
+    Random random = new Random(2);
+    Reader reader;
+    int start, num;
+    ArrayList<Attribute> testAtts;
+    ArrayList<String> testVals;
+    int i,j;
+    
+    try{
+      if (argv.length > 1) {
+	throw (new Exception("Usage: Instances [<filename>]"));
+      }
+      
+      // Creating set of instances from scratch
+      testVals = new ArrayList<String>(2);
+      testVals.add("first_value");
+      testVals.add("second_value");
+      testAtts = new ArrayList<Attribute>(2);
+      testAtts.add(new Attribute("nominal_attribute", testVals));
+      testAtts.add(new Attribute("numeric_attribute"));
+      instances = new Instances("test_set", testAtts, 10);
+      instances.add(new DenseInstance(instances.numAttributes()));
+      instances.add(new DenseInstance(instances.numAttributes()));
+      instances.add(new DenseInstance(instances.numAttributes()));
+      instances.setClassIndex(0);
+      System.out.println("\nSet of instances created from scratch:\n");
+      System.out.println(instances);
+      
+      if (argv.length == 1) {
+	String filename = argv[0];
+	reader = new FileReader(filename);
+	
+	// Read first five instances and print them
+	System.out.println("\nFirst five instances from file:\n");
+	instances = new Instances(reader, 1);
+	instances.setClassIndex(instances.numAttributes() - 1);
+	i = 0;
+	while ((i < 5) && (instances.readInstance(reader))) {
+	  i++;
+	}
+	System.out.println(instances);
+
+	// Read all the instances in the file
+	reader = new FileReader(filename);
+	instances = new Instances(reader);
+
+	// Make the last attribute be the class 
+	instances.setClassIndex(instances.numAttributes() - 1);
+	
+	// Print header and instances.
+	System.out.println("\nDataset:\n");
+	System.out.println(instances);
+	System.out.println("\nClass index: "+instances.classIndex());
+      }
+      
+      // Test basic methods based on class index.
+      System.out.println("\nClass name: "+instances.classAttribute().name());
+      System.out.println("\nClass index: "+instances.classIndex());
+      System.out.println("\nClass is nominal: " +
+			 instances.classAttribute().isNominal());
+      System.out.println("\nClass is numeric: " +
+			 instances.classAttribute().isNumeric());
+      System.out.println("\nClasses:\n");
+      for (i = 0; i < instances.numClasses(); i++) {
+	System.out.println(instances.classAttribute().value(i));
+      }
+      System.out.println("\nClass values and labels of instances:\n");
+      for (i = 0; i < instances.numInstances(); i++) {
+	Instance inst = instances.instance(i);
+	System.out.print(inst.classValue() + "\t");
+	System.out.print(inst.toString(inst.classIndex()));
+	if (instances.instance(i).classIsMissing()) {
+	  System.out.println("\tis missing");
+	} else {
+	  System.out.println();
+	}
+      }
+      
+      // Create random weights.
+      System.out.println("\nCreating random weights for instances.");
+      for (i = 0; i < instances.numInstances(); i++) {
+	instances.instance(i).setWeight(random.nextDouble()); 
+      }
+      
+      // Print all instances and their weights (and the sum of weights).
+      System.out.println("\nInstances and their weights:\n");
+      System.out.println(instances.instancesAndWeights());
+      System.out.print("\nSum of weights: ");
+      System.out.println(instances.sumOfWeights());
+      
+      // Insert an attribute
+      secondInstances = new Instances(instances);
+      Attribute testAtt = new Attribute("Inserted");
+      secondInstances.insertAttributeAt(testAtt, 0);
+      System.out.println("\nSet with inserted attribute:\n");
+      System.out.println(secondInstances);
+      System.out.println("\nClass name: "
+			 + secondInstances.classAttribute().name());
+      
+      // Delete the attribute
+      secondInstances.deleteAttributeAt(0);
+      System.out.println("\nSet with attribute deleted:\n");
+      System.out.println(secondInstances);
+      System.out.println("\nClass name: "
+			 + secondInstances.classAttribute().name());
+      
+      // Test if headers are equal
+      System.out.println("\nHeaders equal: "+
+			 instances.equalHeaders(secondInstances) + "\n");
+      
+      // Print data in internal format.
+      System.out.println("\nData (internal values):\n");
+      for (i = 0; i < instances.numInstances(); i++) {
+	for (j = 0; j < instances.numAttributes(); j++) {
+	  if (instances.instance(i).isMissing(j)) {
+	    System.out.print("? ");
+	  } else {
+	    System.out.print(instances.instance(i).value(j) + " ");
+	  }
+	}
+	System.out.println();
+      }
+      
+      // Just print header
+      System.out.println("\nEmpty dataset:\n");
+      empty = new Instances(instances, 0);
+      System.out.println(empty);
+      System.out.println("\nClass name: "+empty.classAttribute().name());
+
+      // Create copy and rename an attribute and a value (if possible)
+      if (empty.classAttribute().isNominal()) {
+	Instances copy = new Instances(empty, 0);
+	copy.renameAttribute(copy.classAttribute(), "new_name");
+	copy.renameAttributeValue(copy.classAttribute(), 
+				  copy.classAttribute().value(0), 
+				  "new_val_name");
+	System.out.println("\nDataset with names changed:\n" + copy);
+	System.out.println("\nOriginal dataset:\n" + empty);
+      }
+
+      // Create and prints subset of instances.
+      start = instances.numInstances() / 4;
+      num = instances.numInstances() / 2;
+      System.out.print("\nSubset of dataset: ");
+      System.out.println(num + " instances from " + (start + 1) 
+			 + ". instance");
+      secondInstances = new Instances(instances, start, num);
+      System.out.println("\nClass name: "
+			 + secondInstances.classAttribute().name());
+
+      // Print all instances and their weights (and the sum of weights).
+      System.out.println("\nInstances and their weights:\n");
+      System.out.println(secondInstances.instancesAndWeights());
+      System.out.print("\nSum of weights: ");
+      System.out.println(secondInstances.sumOfWeights());
+      
+      // Create and print training and test sets for 3-fold
+      // cross-validation.
+      System.out.println("\nTrain and test folds for 3-fold CV:");
+      if (instances.classAttribute().isNominal()) {
+	instances.stratify(3);
+      }
+      for (j = 0; j < 3; j++) {
+        train = instances.trainCV(3,j, new Random(1));
+	test = instances.testCV(3,j);
+                      
+	// Print all instances and their weights (and the sum of weights).
+	System.out.println("\nTrain: ");
+	System.out.println("\nInstances and their weights:\n");
+	System.out.println(train.instancesAndWeights());
+	System.out.print("\nSum of weights: ");
+	System.out.println(train.sumOfWeights());
+	System.out.println("\nClass name: "+train.classAttribute().name());
+	System.out.println("\nTest: ");
+	System.out.println("\nInstances and their weights:\n");
+	System.out.println(test.instancesAndWeights());
+	System.out.print("\nSum of weights: ");
+	System.out.println(test.sumOfWeights());
+	System.out.println("\nClass name: "+test.classAttribute().name());
+      }
+
+      // Randomize instances and print them.
+      System.out.println("\nRandomized dataset:");
+      instances.randomize(random);
+      
+      // Print all instances and their weights (and the sum of weights).
+      System.out.println("\nInstances and their weights:\n");
+      System.out.println(instances.instancesAndWeights());
+      System.out.print("\nSum of weights: ");
+      System.out.println(instances.sumOfWeights());
+
+      // Sort instances according to first attribute and
+      // print them.
+      System.out.print("\nInstances sorted according to first attribute:\n ");
+      instances.sort(0);
+        
+      // Print all instances and their weights (and the sum of weights).
+      System.out.println("\nInstances and their weights:\n");
+      System.out.println(instances.instancesAndWeights());
+      System.out.print("\nSum of weights: ");
+      System.out.println(instances.sumOfWeights());
+    } catch (Exception e) {
+      e.printStackTrace(); 
+    }
+  }
+
+  /**
+   * Main method for this class. The following calls are possible:
+   * <ul>
+   *   <li>
+   *     <code>weka.core.Instances</code> help<br/>
+   *     prints a short list of possible commands.
+   *   </li>
+   *   <li>
+   *     <code>weka.core.Instances</code> &lt;filename&gt;<br/>
+   *     prints a summary of a set of instances.
+   *   </li>
+   *   <li>
+   *     <code>weka.core.Instances</code> merge &lt;filename1&gt; &lt;filename2&gt;<br/>
+   *     merges the two datasets (must have same number of instances) and
+   *     outputs the results on stdout.
+   *   </li>
+   *   <li>
+   *     <code>weka.core.Instances</code> append &lt;filename1&gt; &lt;filename2&gt;<br/>
+   *     appends the second dataset to the first one (must have same headers) and
+   *     outputs the results on stdout.
+   *   </li>
+   *   <li>
+   *     <code>weka.core.Instances</code> headers &lt;filename1&gt; &lt;filename2&gt;<br/>
+   *     Compares the headers of the two datasets and prints whether they match
+   *     or not.
+   *   </li>
+   *   <li>
+   *     <code>weka.core.Instances</code> randomize &lt;seed&gt; &lt;filename&gt;<br/>
+   *     randomizes the dataset with the given seed and outputs the result on stdout.
+   *   </li>
+   * </ul>
+   *
+   * @param args 	the commandline parameters
+   */
+  public static void main(String[] args) {
+
+    try {
+      Instances i;
+      // read from stdin and print statistics
+      if (args.length == 0) {
+	DataSource source = new DataSource(System.in);
+	i = source.getDataSet();
+	System.out.println(i.toSummaryString());
+      }
+      // read file and print statistics
+      else if ((args.length == 1) && (!args[0].equals("-h")) && (!args[0].equals("help"))) {
+	DataSource source = new DataSource(args[0]);
+	i = source.getDataSet();
+	System.out.println(i.toSummaryString());
+      }
+      // read two files, merge them and print result to stdout
+      else if ((args.length == 3) && (args[0].toLowerCase().equals("merge"))) {
+	DataSource source1 = new DataSource(args[1]);
+	DataSource source2 = new DataSource(args[2]);
+	i = Instances.mergeInstances(source1.getDataSet(), source2.getDataSet());
+	System.out.println(i);
+      }
+      // read two files, append them and print result to stdout
+      else if ((args.length == 3) && (args[0].toLowerCase().equals("append"))) {
+	DataSource source1 = new DataSource(args[1]);
+	DataSource source2 = new DataSource(args[2]);
+	String msg = source1.getStructure().equalHeadersMsg(source2.getStructure());
+	if (msg != null)
+	  throw new Exception("The two datasets have different headers:\n" + msg);
+	Instances structure = source1.getStructure();
+	System.out.println(source1.getStructure());
+	while (source1.hasMoreElements(structure))
+	  System.out.println(source1.nextElement(structure));
+	structure = source2.getStructure();
+	while (source2.hasMoreElements(structure))
+	  System.out.println(source2.nextElement(structure));
+      }
+      // read two files and compare their headers
+      else if ((args.length == 3) && (args[0].toLowerCase().equals("headers"))) {
+	DataSource source1 = new DataSource(args[1]);
+	DataSource source2 = new DataSource(args[2]);
+	String msg = source1.getStructure().equalHeadersMsg(source2.getStructure());
+	if (msg == null)
+	  System.out.println("Headers match");
+	else
+	  System.out.println("Headers don't match:\n" + msg);
+      }
+      // read file and seed value, randomize data and print result to stdout
+      else if ((args.length == 3) && (args[0].toLowerCase().equals("randomize"))) {
+	DataSource source = new DataSource(args[2]);
+	i = source.getDataSet();
+	i.randomize(new Random(Integer.parseInt(args[1])));
+	System.out.println(i);
+      }
+      // wrong parameters or help
+      else {
+	System.err.println(
+	    "\nUsage:\n"
+	    // help
+	    + "\tweka.core.Instances help\n"
+	    + "\t\tPrints this help\n"
+	    // stats
+	    + "\tweka.core.Instances <filename>\n"
+	    + "\t\tOutputs dataset statistics\n"
+	    // merge
+	    + "\tweka.core.Instances merge <filename1> <filename2>\n"
+	    + "\t\tMerges the datasets (must have same number of rows).\n"
+	    + "\t\tGenerated dataset gets output on stdout.\n"
+	    // append
+	    + "\tweka.core.Instances append <filename1> <filename2>\n"
+	    + "\t\tAppends the second dataset to the first (must have same number of attributes).\n"
+	    + "\t\tGenerated dataset gets output on stdout.\n"
+	    // headers
+	    + "\tweka.core.Instances headers <filename1> <filename2>\n"
+	    + "\t\tCompares the structure of the two datasets and outputs whether they\n"
+	    + "\t\tdiffer or not.\n"
+	    // randomize
+	    + "\tweka.core.Instances randomize <seed> <filename>\n"
+	    + "\t\tRandomizes the dataset and outputs it on stdout.\n"
+	);
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Javadoc.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Javadoc.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Javadoc.java	(revision 29)
@@ -0,0 +1,579 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Javadoc.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Abstract superclass for classes that generate Javadoc comments and replace
+ * the content between certain comment tags.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class Javadoc 
+  implements OptionHandler, RevisionHandler {
+
+  /** the start tag */
+  protected String[] m_StartTag = null;
+
+  /** the end tag */
+  protected String[] m_EndTag = null;
+  
+  /** the classname */
+  protected String m_Classname = Javadoc.class.getName();
+  
+  /** whether to include the stars in the Javadoc */
+  protected boolean m_UseStars = true;
+
+  /** the directory above the class to update */
+  protected String m_Dir = "";
+  
+  /** whether to suppress error messages (no printout in the console) */
+  protected boolean m_Silent = false;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+
+    result.addElement(new Option(
+        "\tThe class to load.",
+        "W", 1, "-W <classname>"));
+    
+    result.addElement(new Option(
+        "\tSuppresses the '*' in the Javadoc.",
+        "nostars", 0, "-nostars"));
+    
+    result.addElement(new Option(
+        "\tThe directory above the package hierarchy of the class.",
+        "dir", 1, "-dir <dir>"));
+    
+    result.addElement(new Option(
+        "\tSuppresses printing in the console.",
+        "silent", 0, "-silent"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      		tmpStr;
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0)
+      setClassname(tmpStr);
+    else
+      setClassname(this.getClass().getName());
+
+    setUseStars(!Utils.getFlag("nostars", options));
+
+    setDir(Utils.getOption("dir", options));
+
+    setSilent(Utils.getFlag("silent", options));
+  }
+  
+  /**
+   * Gets the current settings of this object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String> 	result;
+
+    result = new Vector<String>();
+    
+    result.add("-W");
+    result.add(getClassname());
+    
+    if (!getUseStars())
+      result.add("-nostars");
+    
+    if (getDir().length() != 0) {
+      result.add("-dir");
+      result.add(getDir());
+    }
+    
+    if (getSilent())
+      result.add("-silent");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets the classname of the class to generate the Javadoc for
+   * 
+   * @param value	the new classname
+   */
+  public void setClassname(String value) {
+    m_Classname = value;
+  }
+  
+  /**
+   * returns the current classname
+   * 
+   * @return	the current classname
+   */
+  public String getClassname() {
+    return m_Classname;
+  }
+  
+  /**
+   * sets whether to prefix the Javadoc with "*"
+   * 
+   * @param value	true if stars are used
+   */
+  public void setUseStars(boolean value) {
+    m_UseStars = value;
+  }
+  
+  /**
+   * whether the Javadoc is prefixed with "*"
+   * 
+   * @return 		whether stars are used
+   */
+  public boolean getUseStars() {
+    return m_UseStars;
+  }
+  
+  /**
+   * sets the dir containing the file that is to be updated. It is the dir
+   * above the package hierarchy of the class.
+   * 
+   * @param value	the directory containing the classes
+   */
+  public void setDir(String value) {
+    m_Dir = value;
+  }
+  
+  /**
+   * returns the current dir containing the class to update. It is the dir
+   * above the package name of the class.
+   * 
+   * @return		the  current directory
+   */
+  public String getDir() {
+    return m_Dir;
+  }
+  
+  /**
+   * sets whether to suppress output in the console
+   * 
+   * @param value	true if output is to be suppressed
+   */
+  public void setSilent(boolean value) {
+    m_Silent = value;
+  }
+  
+  /**
+   * whether output in the console is suppressed
+   * 
+   * @return 		true if output is suppressed
+   */
+  public boolean getSilent() {
+    return m_Silent;
+  }
+  
+  /**
+   * prints the given object to System.err
+   * 
+   * @param o		the object to print
+   */
+  protected void println(Object o) {
+    if (!getSilent())
+      System.err.println(o.toString());
+  }
+
+  /**
+   * returns true if the class can be instantiated, i.e., has a default
+   * constructor.
+   * 
+   * @return true if the class can be instantiated
+   */
+  protected boolean canInstantiateClass() {
+    boolean	result;
+    Class	cls;
+
+    result = true;
+    cls    = null;
+
+    try {
+      cls = Class.forName(getClassname());
+    }
+    catch (Exception e) {
+      result = false;
+      println("Cannot instantiate '" + getClassname() + "'! Class in CLASSPATH?");
+    }
+
+    if (result) {
+      try {
+	cls.newInstance();
+      }
+      catch (Exception e) {
+	result = false;
+	println("Cannot instantiate '" + getClassname() + "'! Missing default constructor?");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns a new instance of the class
+   * 
+   * @return a new instance of the class
+   */
+  protected Object getInstance() {
+    Object	result;
+    Class	cls;
+
+    result = null;
+    
+    try {
+      cls    = Class.forName(getClassname());
+      result = cls.newInstance();
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * converts the given String into HTML, i.e., replacing some char entities
+   * with HTML entities.
+   * 
+   * @param s		the string to convert
+   * @return		the HTML conform string
+   */
+  protected String toHTML(String s) {
+    String	result;
+    
+    result = s;
+    
+    result = result.replaceAll("&", "&amp;");
+    result = result.replaceAll("<", "&lt;");
+    result = result.replaceAll(">", "&gt;");
+    result = result.replaceAll("@", "&#64;");
+    result = result.replaceAll("\n", "<br/>\n");
+    
+    return result;
+  }
+
+  /**
+   * indents the given string by a given number of indention strings
+   * 
+   * @param content	the string to indent
+   * @param count	the number of times to indent one line
+   * @param indentStr	the indention string
+   * @return		the indented content
+   */
+  protected String indent(String content, int count, String indentStr) {
+    String		result;
+    StringTokenizer	tok;
+    int			i;
+    
+    tok = new StringTokenizer(content, "\n", true);
+    result = "";
+    while (tok.hasMoreTokens()) {
+      if (result.endsWith("\n") || (result.length() == 0)) {
+	for (i = 0; i < count; i++)
+	  result += indentStr;
+      }
+      result += tok.nextToken();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair.
+   * 
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected abstract String generateJavadoc(int index) throws Exception;
+  
+  /**
+   * generates and returns the Javadoc
+   * 
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String generateJavadoc() throws Exception {
+    String	result;
+    int		i;
+    
+    result = "";
+    
+    for (i = 0; i < m_StartTag.length; i++) {
+      if (i > 0)
+	result += "\n\n";
+      result += generateJavadoc(i).trim();
+    }
+    
+    return result;
+  }
+
+  /**
+   * determines the base string of the given indention string, whether it's
+   * either only spaces (one space will be retured) or mixed mode (tabs and 
+   * spaces, in that case the same string will be returned)
+   * 
+   * @param str		the string to analyze
+   * @return 		the indention string
+   */
+  protected String getIndentionString(String str) {
+    String	result;
+    
+    // only spaces?
+    if (str.replaceAll(" ", "").length() == 0)
+      result = " ";
+    // only tabs?
+    else if (str.replaceAll("\t", "").length() == 0)
+      result = "\t";
+    else
+      result = str;
+      
+    return result;
+  }
+  
+  /**
+   * determines the number of indention strings that have to be inserted to
+   * generated the given indention string.
+   * 
+   * @param str 	the string to analyze
+   * @return		the number of base indention strings to insert
+   */
+  protected int getIndentionLength(String str) {
+    int		result;
+    
+    // only spaces?
+    if (str.replaceAll(" ", "").length() == 0)
+      result = str.length();
+    // only tabs?
+    else if (str.replaceAll("\t", "").length() == 0)
+      result = str.length();
+    else
+      result = 1;
+    
+    return result;
+  }
+  
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair
+   * 
+   * @param content	the current source code
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String updateJavadoc(String content, int index) throws Exception {
+    StringBuffer	resultBuf;
+    int			indentionLen;
+    String		indentionStr;
+    String		part;
+    String		tmpStr;
+
+    // start and end tag?
+    if (    (content.indexOf(m_StartTag[index]) == -1)
+	   || (content.indexOf(m_EndTag[index]) == -1) ) {
+	println(
+	    "No start and/or end tags found: " 
+	    + m_StartTag[index] + "/" + m_EndTag[index]);
+	return content;
+    }
+
+    // replace option-tags
+    resultBuf = new StringBuffer();
+    while (content.length() > 0) {
+      if (content.indexOf(m_StartTag[index]) > -1) {
+	part = content.substring(0, content.indexOf(m_StartTag[index]));
+	// is it a Java constant? -> skip
+	if (part.endsWith("\"")) {
+	  resultBuf.append(part);
+	  resultBuf.append(m_StartTag[index]);
+	  content = content.substring(part.length() + m_StartTag[index].length());
+	}
+	else {
+	  tmpStr       = part.substring(part.lastIndexOf("\n") + 1);
+	  indentionLen = getIndentionLength(tmpStr);
+	  indentionStr = getIndentionString(tmpStr);
+	  part         = part.substring(0, part.lastIndexOf("\n") + 1);
+	  resultBuf.append(part);
+	  resultBuf.append(indent(m_StartTag[index], indentionLen, indentionStr) + "\n");
+	  resultBuf.append(indent(generateJavadoc(index), indentionLen, indentionStr));
+	  resultBuf.append(indent(m_EndTag[index], indentionLen, indentionStr));
+	  content = content.substring(content.indexOf(m_EndTag[index]));
+	  content = content.substring(m_EndTag[index].length());
+	}
+      }
+      else {
+	resultBuf.append(content);
+	content = "";
+      }
+    }
+    
+    return resultBuf.toString().trim();
+  }
+  
+  /**
+   * updates the Javadoc in the given source code.
+   * 
+   * @param content	the source code
+   * @return		the updated source code
+   * @throws Exception 	in case the generation fails
+   */
+  protected String updateJavadoc(String content) throws Exception {
+    String	result;
+    int		i;
+    
+    result = content;
+    
+    for (i = 0; i < m_StartTag.length; i++) {
+      result = updateJavadoc(result, i);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * generates the Javadoc and returns it applied to the source file if one
+   * was provided, otherwise an empty string.
+   * 
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  public String updateJavadoc() throws Exception {
+    StringBuffer	contentBuf;
+    BufferedReader	reader;
+    String		line;
+    String		result;
+    File		file;
+    
+    result = "";
+    
+    // non-existing?
+    file = new File(getDir() + "/" + getClassname().replaceAll("\\.", "/") + ".java");
+    if (!file.exists()) {
+      println("File '" + file.getAbsolutePath() + "' doesn't exist!");
+      return result;
+    }
+    
+    try {
+      // load file
+      reader     = new BufferedReader(new FileReader(file));
+      contentBuf = new StringBuffer();
+      while ((line = reader.readLine()) != null) {
+	contentBuf.append(line + "\n");
+      }
+      reader.close();
+      result = updateJavadoc(contentBuf.toString());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return result.trim();
+  }
+  
+  /**
+   * generates either the plain Javadoc (if no filename specified) or the
+   * updated file (if a filename is specified). The start and end tag for
+   * the global info have to be specified in the file in the latter case.
+   * 
+   * @return 		either the plain Javadoc or the modified file
+   * @throws Exception 	in case the generation fails
+   */
+  public String generate() throws Exception {
+    if (getDir().length() == 0)
+      return generateJavadoc();
+    else
+      return updateJavadoc();
+  }
+  
+  /**
+   * generates a string to print as help on the console
+   * 
+   * @return 	the generated help
+   */
+  public String generateHelp() {
+    String 		result;
+    Enumeration 	enm;
+    Option 		option;
+    
+    result = getClass().getName().replaceAll(".*\\.", "") + " Options:\n\n";
+    enm = listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      result += option.synopsis() + "\n" + option.description() + "\n";
+    }
+    
+    return result;
+  }
+  
+  /**
+   * runs the javadoc producer with the given commandline options
+   * 
+   * @param javadoc	the javadoc producer to execute
+   * @param options	the commandline options
+   */
+  protected static void runJavadoc(Javadoc javadoc, String[] options) {
+    try {
+      try {
+	if (Utils.getFlag('h', options))
+	  throw new Exception("Help requested");
+
+	javadoc.setOptions(options);
+        Utils.checkForRemainingOptions(options);
+
+        // directory is necessary!
+        if (javadoc.getDir().length() == 0)
+          throw new Exception("No directory provided!");
+      } 
+      catch (Exception ex) {
+        String result = "\n" + ex.getMessage() + "\n\n" + javadoc.generateHelp();
+        throw new Exception(result);
+      }
+
+      System.out.println(javadoc.generate() + "\n");
+    } 
+    catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ListOptions.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ListOptions.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ListOptions.java	(revision 29)
@@ -0,0 +1,186 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ListOptions.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Lists the options of an OptionHandler
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class ListOptions
+  implements OptionHandler, RevisionHandler {
+  
+  /** the classname */
+  protected String m_Classname = ListOptions.class.getName();
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+
+    result.addElement(new Option(
+        "\tThe class to load.",
+        "W", 1, "-W <classname>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      		tmpStr;
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0)
+      setClassname(tmpStr);
+    else
+      setClassname(this.getClass().getName());
+  }
+  
+  /**
+   * Gets the current settings of this object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String> 	result;
+
+    result = new Vector<String>();
+    
+    result.add("-W");
+    result.add(getClassname());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets the classname of the class to generate the Javadoc for
+   * 
+   * @param value	the new classname
+   */
+  public void setClassname(String value) {
+    m_Classname = value;
+  }
+  
+  /**
+   * returns the current classname
+   * 
+   * @return	the current classname
+   */
+  public String getClassname() {
+    return m_Classname;
+  }
+  
+  /**
+   * generates a string to print as help on the console
+   * 
+   * @return 	the generated help
+   */
+  public String generateHelp() {
+    String 		result;
+    Enumeration 	enm;
+    Option 		option;
+    
+    result = getClass().getName().replaceAll(".*\\.", "") + " Options:\n\n";
+    enm = listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      result += option.synopsis() + "\n" + option.description() + "\n";
+    }
+    
+    return result;
+  }
+  
+  /**
+   * generates the options string.
+   * 
+   * @return 		the options string
+   * @throws Exception 	in case the generation fails
+   */
+  public String generate() throws Exception {
+    StringBuffer 	result;
+    OptionHandler	handler;
+    Enumeration 	enm;
+    Option 		option;
+    
+    result = new StringBuffer();
+    
+    handler = (OptionHandler) Class.forName(getClassname()).newInstance();
+    
+    enm = ((OptionHandler) handler).listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      result.append(option.synopsis() + '\n');
+      result.append(option.description() + "\n");
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * runs the javadoc producer with the given commandline options
+   * 
+   * @param options	the commandline options
+   */
+  public static void main(String[] options) {
+    ListOptions list = new ListOptions();
+    
+    try {
+      try {
+	if (Utils.getFlag('h', options))
+	  throw new Exception("Help requested");
+
+	list.setOptions(options);
+        Utils.checkForRemainingOptions(options);
+      } 
+      catch (Exception ex) {
+        String result = "\n" + ex.getMessage() + "\n\n" + list.generateHelp();
+        throw new Exception(result);
+      }
+
+      System.out.println("\n" + list.generate());
+    } 
+    catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ManhattanDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ManhattanDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ManhattanDistance.java	(revision 29)
@@ -0,0 +1,154 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ManhattanDistance.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements the Manhattan distance (or Taxicab geometry). The distance between two points is the sum of the (absolute) differences of their coordinates.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Wikipedia. Taxicab geometry. URL http://en.wikipedia.org/wiki/Taxicab_geometry.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{missing_id,
+ *    author = {Wikipedia},
+ *    title = {Taxicab geometry},
+ *    URL = {http://en.wikipedia.org/wiki/Taxicab_geometry}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns off the normalization of attribute 
+ *  values in distance calculation.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to used in the calculation of the 
+ *  distance. 'first' and 'last' are valid indices.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indices.</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class ManhattanDistance
+  extends NormalizableDistance
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 6783782554224000243L;
+
+  /**
+   * Constructs an Manhattan Distance object, Instances must be still set.
+   */
+  public ManhattanDistance() {
+    super();
+  }
+
+  /**
+   * Constructs an Manhattan Distance object and automatically initializes the
+   * ranges.
+   * 
+   * @param data 	the instances the distance function should work on
+   */
+  public ManhattanDistance(Instances data) {
+    super(data);
+  }
+
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implements the Manhattan distance (or Taxicab geometry). The distance "
+      + "between two points is the sum of the (absolute) differences of their "
+      + "coordinates.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Wikipedia");
+    result.setValue(Field.TITLE, "Taxicab geometry");
+    result.setValue(Field.URL, "http://en.wikipedia.org/wiki/Taxicab_geometry");
+
+    return result;
+  }
+  
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected double updateDistance(double currDist, double diff) {
+    double	result;
+    
+    result  = currDist;
+    result += Math.abs(diff);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Matchable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Matchable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Matchable.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Matchable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Interface to something that can be matched with tree matching
+ * algorithms.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Matchable {
+
+  /**
+   * Returns a string that describes a tree representing
+   * the object in prefix order.
+   *
+   * @return the tree described as a string
+   * @exception Exception if the tree can't be computed
+   */
+  String prefix() throws Exception;
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/MathematicalExpression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/MathematicalExpression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/MathematicalExpression.java	(revision 29)
@@ -0,0 +1,144 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MathematicalExpression.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.core.mathematicalexpression.Parser;
+import weka.core.mathematicalexpression.Scanner;
+import java_cup.runtime.DefaultSymbolFactory;
+import java_cup.runtime.SymbolFactory;
+
+import java.io.ByteArrayInputStream;
+import java.util.HashMap;
+
+/** 
+ * Class for evaluating a string adhering the following grammar:<br/>
+ * 
+ * <pre>
+ * expr_list ::= expr_list expr_part | expr_part ;
+ * expr_part ::= expr ;
+ * expr      ::=   NUMBER
+ *               | ( expr )
+ *               | opexpr
+ *               | varexpr
+ *               | funcexpr
+ *               ;
+ * 
+ * opexpr    ::=   expr + expr
+ *               | expr - expr
+ *               | expr * expr
+ *               | expr / expr
+ *               ;
+ * 
+ * varexpr  ::=  VARIABLE ;
+ * 
+ * funcexpr ::=    abs ( expr )
+ *               | sqrt ( expr )
+ *               | log ( expr )
+ *               | exp ( expr )
+ *               | sin ( expr )
+ *               | cos ( expr )
+ *               | tan ( expr )
+ *               | rint ( expr )
+ *               | floor ( expr )
+ *               | pow ( expr , expr )
+ *               | ceil ( expr )
+ *               | ifelse ( boolexpr , expr (if true) , expr (if false) )
+ *               ;
+ * 
+ * boolexpr ::=    BOOLEAN
+ *               | true
+ *               | false
+ *               | expr &lt; expr
+ *               | expr &lt;= expr
+ *               | expr &gt; expr
+ *               | expr &gt;= expr
+ *               | expr = expr
+ *               | ( boolexpr )
+ *               | ! boolexpr
+ *               | boolexpr & boolexpr
+ *               | boolexpr | boolexpr
+ *               ;
+ * </pre>
+ *
+ * Code example 1:
+ * <pre>
+ * String expr = "pow(BASE,EXPONENT)*MULT";
+ * HashMap symbols = new HashMap();
+ * symbols.put("BASE", new Double(2));
+ * symbols.put("EXPONENT", new Double(9));
+ * symbols.put("MULT", new Double(0.1));
+ * double result = MathematicalExpression.evaluate(expr, symbols);
+ * System.out.println(expr + " and " + symbols + " = " + result);
+ * </pre>
+ * 
+ * Code Example 2 (uses the "ifelse" construct):
+ * <pre>
+ * String expr = "ifelse(I<0,pow(BASE,I*0.5),pow(BASE,I))";
+ * MathematicalExpression.TreeNode tree = MathematicalExpression.parse(expr);
+ * HashMap symbols = new HashMap();
+ * symbols.put("BASE", new Double(2));
+ * for (int i = -10; i <= 10; i++) {
+ *   symbols.put("I", new Double(i));
+ *   double result = MathematicalExpression.evaluate(expr, symbols);
+ *   System.out.println(expr + " and " + symbols + " = " + result);
+ * }
+ * </pre>
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+public class MathematicalExpression
+  implements RevisionHandler {
+  
+  /**
+   * Parses and evaluates the given expression.
+   * Returns the result of the mathematical expression, based on the given 
+   * values of the symbols.
+   * 
+   * @param expr	the expression to evaluate
+   * @param symbols	the symbol/value mapping
+   * @return		the evaluated result
+   * @throws Exception	if something goes wrong
+   */
+  public static double evaluate(String expr, HashMap symbols) throws Exception {
+    SymbolFactory 		sf;
+    ByteArrayInputStream 	parserInput;
+    Parser 			parser;
+    
+    sf          = new DefaultSymbolFactory();
+    parserInput = new ByteArrayInputStream(expr.getBytes());
+    parser      = new Parser(new Scanner(parserInput, sf), sf);
+    parser.setSymbols(symbols);
+    parser.parse();
+    
+    return parser.getResult();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4939 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Matrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Matrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Matrix.java	(revision 29)
@@ -0,0 +1,549 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Matrix.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+
+/**
+ * Class for performing operations on a matrix of floating-point values.
+ * <p/>
+ * Deprecated: Uses internally the code of the sub-package 
+ * <code>weka.core.matrix</code> - only for backwards compatibility.
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author Yong Wang (yongwang@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (eibe@cs.waikato.ac.nz)
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @deprecated Use <code>weka.core.matrix.Matrix</code> instead - only for
+ * backwards compatibility. 
+ */
+public class Matrix 
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3604757095849145838L;
+
+  /**
+   * The actual matrix */
+  protected weka.core.matrix.Matrix m_Matrix = null;
+
+  /**
+   * Constructs a matrix and initializes it with default values.
+   *
+   * @param nr the number of rows
+   * @param nc the number of columns
+   */
+  public Matrix(int nr, int nc) {
+    m_Matrix = new weka.core.matrix.Matrix(nr, nc);
+  }
+
+  /**
+   * Constructs a matrix using a given array.
+   *
+   * @param array the values of the matrix
+   */
+  public Matrix(double[][] array) throws Exception {
+    m_Matrix = new weka.core.matrix.Matrix(array);
+  }
+
+  /**
+   * Reads a matrix from a reader. The first line in the file should
+   * contain the number of rows and columns. Subsequent lines
+   * contain elements of the matrix.
+   *
+   * @param r the reader containing the matrix
+   * @throws Exception if an error occurs
+   */
+  public Matrix(Reader r) throws Exception {
+    m_Matrix = new weka.core.matrix.Matrix(r);
+  }
+
+  /**
+   * Creates and returns a clone of this object.
+   *
+   * @return a clone of this instance.
+   * @throws Exception if an error occurs
+   */
+  public Object clone() {
+    try {
+      return new Matrix(m_Matrix.getArrayCopy());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  /**
+   * Writes out a matrix.
+   *
+   * @param w the output Writer
+   * @throws Exception if an error occurs
+   */
+  public void write(Writer w) throws Exception {
+    m_Matrix.write(w);
+  }
+
+  /**
+   * returns the internal matrix
+   * @see #m_Matrix
+   */
+  protected weka.core.matrix.Matrix getMatrix() {
+    return m_Matrix;
+  }
+
+  /**
+   * Returns the value of a cell in the matrix.
+   *
+   * @param rowIndex the row's index
+   * @param columnIndex the column's index
+   * @return the value of the cell of the matrix
+   */
+  public final double getElement(int rowIndex, int columnIndex) {
+    return m_Matrix.get(rowIndex, columnIndex);
+  }
+
+  /**
+   * Add a value to an element.
+   *
+   * @param rowIndex the row's index.
+   * @param columnIndex the column's index.
+   * @param value the value to add.
+   */
+  public final void addElement(int rowIndex, int columnIndex, double value) {
+    m_Matrix.set(
+        rowIndex, columnIndex, m_Matrix.get(rowIndex, columnIndex) + value);
+  }
+
+  /**
+   * Returns the number of rows in the matrix.
+   *
+   * @return the number of rows
+   */
+  public final int numRows() {
+    return m_Matrix.getRowDimension();
+  }
+
+  /**
+   * Returns the number of columns in the matrix.
+   *
+   * @return the number of columns
+   */
+  public final int numColumns() {
+    return m_Matrix.getColumnDimension();
+  }
+
+  /**
+   * Sets an element of the matrix to the given value.
+   *
+   * @param rowIndex the row's index
+   * @param columnIndex the column's index
+   * @param value the value
+   */
+  public final void setElement(int rowIndex, int columnIndex, double value) {
+    m_Matrix.set(rowIndex, columnIndex, value);
+  }
+
+  /**
+   * Sets a row of the matrix to the given row. Performs a deep copy.
+   *
+   * @param index the row's index
+   * @param newRow an array of doubles
+   */
+  public final void setRow(int index, double[] newRow) {
+    for (int i = 0; i < newRow.length; i++)
+      m_Matrix.set(index, i, newRow[i]);
+  }
+  
+  /**
+   * Gets a row of the matrix and returns it as double array.
+   *
+   * @param index the row's index
+   * @return an array of doubles
+   */
+  public double[] getRow(int index) {
+    double[] newRow = new double[this.numColumns()];
+    for (int i = 0; i < newRow.length; i++)
+      newRow[i] = getElement(index, i);
+
+    return newRow;
+  }
+
+  /**
+   * Gets a column of the matrix and returns it as a double array.
+   *
+   * @param index the column's index
+   * @return an array of doubles
+   */
+  public double[] getColumn(int index) {
+    double[] newColumn = new double[this.numRows()];
+    for (int i = 0; i < newColumn.length; i++)
+      newColumn[i] = getElement(i, index);
+
+    return newColumn;
+  }
+
+  /**
+   * Sets a column of the matrix to the given column. Performs a deep copy.
+   *
+   * @param index the column's index
+   * @param newColumn an array of doubles
+   */
+  public final void setColumn(int index, double[] newColumn) {
+    for (int i = 0; i < numRows(); i++)
+      m_Matrix.set(i, index, newColumn[i]);
+  }
+
+  /** 
+   * Converts a matrix to a string
+   *
+   * @return the converted string
+   */
+  public String toString() {
+    return m_Matrix.toString();
+  } 
+    
+  /**
+   * Returns the sum of this matrix with another.
+   *
+   * @return a matrix containing the sum.
+   */
+  public final Matrix add(Matrix other) {
+    try {
+      return new Matrix(m_Matrix.plus(other.getMatrix()).getArrayCopy());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+  
+  /**
+   * Returns the transpose of a matrix.
+   *
+   * @return the transposition of this instance.
+   */
+  public final Matrix transpose() {
+    try {
+      return new Matrix(m_Matrix.transpose().getArrayCopy());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+  
+  /**
+   * Returns true if the matrix is symmetric.
+   *
+   * @return boolean true if matrix is symmetric.
+   */
+  public boolean isSymmetric() {
+    return m_Matrix.isSymmetric();
+  }
+
+  /**
+   * Returns the multiplication of two matrices
+   *
+   * @param b the multiplication matrix
+   * @return the product matrix
+   */
+  public final Matrix multiply(Matrix b) {
+    try {
+      return new Matrix(getMatrix().times(b.getMatrix()).getArrayCopy());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  /**
+   * Performs a (ridged) linear regression.
+   *
+   * @param y the dependent variable vector
+   * @param ridge the ridge parameter
+   * @return the coefficients 
+   * @throws IllegalArgumentException if not successful
+   */
+  public final double[] regression(Matrix y, double ridge) {
+    return getMatrix().regression(y.getMatrix(), ridge).getCoefficients();
+  }
+
+  /**
+   * Performs a weighted (ridged) linear regression. 
+   *
+   * @param y the dependent variable vector
+   * @param w the array of data point weights
+   * @param ridge the ridge parameter
+   * @return the coefficients 
+   * @throws IllegalArgumentException if the wrong number of weights were
+   * provided.
+   */
+  public final double[] regression(Matrix y, double [] w, double ridge) {
+    return getMatrix().regression(y.getMatrix(), w, ridge).getCoefficients();
+  }
+
+  /**
+   * Returns the L part of the matrix.
+   * This does only make sense after LU decomposition.
+   *
+   * @return matrix with the L part of the matrix; 
+   * @see #LUDecomposition()
+   */
+  public Matrix getL() throws Exception {
+    int nr = numRows();    // num of rows
+    int nc = numColumns(); // num of columns
+    double[][] ld = new double[nr][nc];
+
+    for (int i = 0; i < nr; i++) {
+      for (int j = 0; (j < i) && (j < nc); j++) {
+        ld[i][j] = getElement(i, j);
+      }
+      if (i < nc) ld[i][i] = 1;
+    }
+    Matrix l = new Matrix(ld);
+    return l;
+  }
+
+  /**
+   * Returns the U part of the matrix.
+   * This does only make sense after LU decomposition.
+   *
+   * @return matrix with the U part of a matrix; 
+   * @see #LUDecomposition()
+   */
+  public Matrix getU() throws Exception {
+    int nr = numRows();    // num of rows
+    int nc = numColumns(); // num of columns
+    double[][] ud = new double[nr][nc];
+
+    for (int i = 0; i < nr; i++) {
+      for (int j = i; j < nc ; j++) {
+        ud[i][j] = getElement(i, j);
+      }
+    }
+    Matrix u = new Matrix(ud);
+    return u;
+  }
+  
+  /**
+   * Performs a LUDecomposition on the matrix.
+   * It changes the matrix into its LU decomposition.
+   *
+   * @return the indices of the row permutation
+   */
+  public int[] LUDecomposition() throws Exception {
+    // decompose
+    weka.core.matrix.LUDecomposition lu = m_Matrix.lu();
+
+    // singular? old class throws Exception!
+    if (!lu.isNonsingular())
+    	throw new Exception("Matrix is singular");
+
+    weka.core.matrix.Matrix u = lu.getU();
+    weka.core.matrix.Matrix l = lu.getL();
+
+    // modify internal matrix
+    int nr = numRows();
+    int nc = numColumns();
+    for (int i = 0; i < nr; i++) {
+      for (int j = 0; j < nc; j++) {
+        if (j < i)
+          setElement(i, j, l.get(i, j));
+        else
+          setElement(i, j, u.get(i, j));
+      }
+    }
+
+    u = null;
+    l = null;
+    
+    return lu.getPivot();
+  }
+  
+  /**
+   * Solve A*X = B using backward substitution.
+   * A is current object (this). Note that this matrix will be changed! 
+   * B parameter bb.
+   * X returned in parameter bb.
+   *
+   * @param bb first vector B in above equation then X in same equation.
+   */
+  public void solve(double[] bb) throws Exception {
+    // solve
+    weka.core.matrix.Matrix x = m_Matrix.solve(
+                                    new weka.core.matrix.Matrix(bb, bb.length));
+    
+    // move X into bb
+    int nr = x.getRowDimension();
+    for (int i = 0; i < nr; i++)
+      bb[i] = x.get(i, 0);
+  }
+
+  /**
+   * Performs Eigenvalue Decomposition using Householder QR Factorization
+   *
+   * Matrix must be symmetrical.
+   * Eigenvectors are return in parameter V, as columns of the 2D array.
+   * (Real parts of) Eigenvalues are returned in parameter d.
+   *
+   * @param V double array in which the eigenvectors are returned 
+   * @param d array in which the eigenvalues are returned
+   * @throws Exception if matrix is not symmetric
+   */
+  public void eigenvalueDecomposition(double[][] V, double[] d) 
+    throws Exception {
+
+    // old class only worked with symmetric matrices!
+    if (!this.isSymmetric())
+      throw new Exception("EigenvalueDecomposition: Matrix must be symmetric.");
+    
+    // perform eigenvalue decomposition
+    weka.core.matrix.EigenvalueDecomposition eig = m_Matrix.eig();
+    weka.core.matrix.Matrix v = eig.getV();
+    double[] d2 = eig.getRealEigenvalues();
+    
+    // transfer data
+    int nr = numRows();
+    int nc = numColumns();
+    for (int i = 0; i < nr; i++)
+      for (int j = 0; j < nc; j++)
+        V[i][j] = v.get(i, j);
+
+    for (int i = 0; i < d2.length; i++)
+      d[i] = d2[i];
+  } 
+
+  /**
+   * Returns sqrt(a^2 + b^2) without under/overflow.
+   *   
+   * @param a length of one side of rectangular triangle
+   * @param b length of other side of rectangular triangle
+   * @return lenght of third side
+   */
+  protected static double hypot(double a, double b) {
+    return weka.core.matrix.Maths.hypot(a, b);
+  }
+
+  /**
+   * converts the Matrix into a single line Matlab string: matrix is enclosed 
+   * by parentheses, rows are separated by semicolon and single cells by
+   * blanks, e.g., [1 2; 3 4].
+   * @return      the matrix in Matlab single line format
+   */
+  public String toMatlab() {
+    return getMatrix().toMatlab();
+  }
+
+  /**
+   * creates a matrix from the given Matlab string.
+   * @param matlab  the matrix in matlab format
+   * @return        the matrix represented by the given string
+   * @see           #toMatlab()
+   */
+  public static Matrix parseMatlab(String matlab) throws Exception {
+    return new Matrix(weka.core.matrix.Matrix.parseMatlab(matlab).getArray());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] ops) {
+
+    double[] first = {2.3, 1.2, 5};
+    double[] second = {5.2, 1.4, 9};
+    double[] response = {4, 7, 8};
+    double[] weights = {1, 2, 3};
+
+    try {
+      // test eigenvaluedecomposition
+      double[][] m = {{1, 2, 3}, {2, 5, 6},{3, 6, 9}};
+      Matrix M = new Matrix(m);
+      int n = M.numRows();
+      double[][] V = new double[n][n];
+      double[] d = new double[n];
+      double[] e = new double[n];
+      M.eigenvalueDecomposition(V, d);
+      Matrix v = new Matrix(V);
+      // M.testEigen(v, d, );
+      // end of test-eigenvaluedecomposition
+      
+      Matrix a = new Matrix(2, 3);
+      Matrix b = new Matrix(3, 2);
+      System.out.println("Number of columns for a: " + a.numColumns());
+      System.out.println("Number of rows for a: " + a.numRows());
+      a.setRow(0, first);
+      a.setRow(1, second);
+      b.setColumn(0, first);
+      b.setColumn(1, second);
+      System.out.println("a:\n " + a);
+      System.out.println("b:\n " + b);
+      System.out.println("a (0, 0): " + a.getElement(0, 0));
+      System.out.println("a transposed:\n " + a.transpose());
+      System.out.println("a * b:\n " + a.multiply(b));
+      Matrix r = new Matrix(3, 1);
+      r.setColumn(0, response);
+      System.out.println("r:\n " + r);
+      System.out.println("Coefficients of regression of b on r: ");
+      double[] coefficients = b.regression(r, 1.0e-8);
+      for (int i = 0; i < coefficients.length; i++) {
+	System.out.print(coefficients[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Weights: ");
+      for (int i = 0; i < weights.length; i++) {
+	System.out.print(weights[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Coefficients of weighted regression of b on r: ");
+      coefficients = b.regression(r, weights, 1.0e-8);
+      for (int i = 0; i < coefficients.length; i++) {
+	System.out.print(coefficients[i] + " ");
+      }
+      System.out.println();
+      a.setElement(0, 0, 6);
+      System.out.println("a with (0, 0) set to 6:\n " + a);
+      a.write(new java.io.FileWriter("main.matrix"));
+      System.out.println("wrote matrix to \"main.matrix\"\n" + a);
+      a = new Matrix(new java.io.FileReader("main.matrix"));
+      System.out.println("read matrix from \"main.matrix\"\n" + a);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/Memory.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Memory.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Memory.java	(revision 29)
@@ -0,0 +1,247 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Memory.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.core;
+
+import javax.swing.JOptionPane;
+
+/**
+ * A little helper class for Memory management. Very crude, since JDK 1.4 
+ * doesn't offer real Memory Management.<p/>
+ * The memory management can be disabled by using the setEnabled(boolean)
+ * method.
+ *
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 5953 $
+ * @see       #setEnabled(boolean)
+ */
+public class Memory
+  implements RevisionHandler {
+  
+  /** whether memory management is enabled */
+  protected static boolean m_Enabled = true;
+  
+  /** whether a GUI is present */
+  protected boolean m_UseGUI = false;
+
+  /** the initial size of the JVM */
+  protected static long m_Initial = Runtime.getRuntime().totalMemory();
+
+  /** the total memory that is used */
+  protected long m_Total;
+
+  /** the maximum amount of memory that can be used */
+  protected long m_Max;
+
+  /** the current runtime variable  */
+  protected Runtime m_Runtime;
+
+  /**
+   * initializes the memory management without GUI support
+   */
+  public Memory() {
+    this(false);
+  }
+
+  /**
+   * initializes the memory management
+   * @param useGUI      whether a GUI is present
+   */
+  public Memory(boolean useGUI) {
+    m_UseGUI  = useGUI;
+    m_Runtime = Runtime.getRuntime();
+    m_Max     = m_Runtime.maxMemory();
+    m_Total   = m_Runtime.totalMemory();
+  }
+
+  /**
+   * returns whether the memory management is enabled
+   * 
+   * @return		true if enabled
+   */
+  public boolean isEnabled() {
+    return m_Enabled;
+  }
+
+  /**
+   * sets whether the memory management is enabled
+   * 
+   * @param value	true if the management should be enabled
+   */
+  public void setEnabled(boolean value) {
+    m_Enabled = value;
+  }
+
+  /**
+   * whether to display a dialog in case of a problem (= TRUE) or just print
+   * on stderr (= FALSE)
+   * 
+   * @return		true if the GUI is used
+   */
+  public boolean getUseGUI() {
+    return m_UseGUI;
+  }
+
+  /**
+   * returns the initial size of the JVM
+   * 
+   * @return		the initial size in bytes
+   */
+  public long getInitial() {
+    return m_Initial;
+  }
+
+  /**
+   * returns the current memory consumption
+   * 
+   * @return		the current size in bytes
+   */
+  public long getCurrent() {
+    m_Runtime = Runtime.getRuntime();
+    m_Total   = m_Runtime.totalMemory();
+
+    return m_Total;
+  }
+
+  /**
+   * returns the maximum amount of memory that can be assigned
+   * 
+   * @return		the maximum size in bytes
+   */
+  public long getMax() {
+    return m_Max;
+  }
+
+  /**
+   * checks if there's still enough memory left. if ENABLED is true, then
+   * false is returned always
+   * 
+   * @return		true if out of memory (only if management enabled, 
+   * 			otherwise always false)
+   */
+  public boolean isOutOfMemory() {
+    if (isEnabled())
+      return ((getMax() - getCurrent()) < (getInitial() + 200000));
+    else
+      return false;
+  }
+
+  /**
+   * returns the amount of bytes as MB
+   * 
+   * @return		the MB amount
+   */
+  public static double toMegaByte(long bytes) {
+    return (bytes / (double) (1024 * 1024));
+  }
+
+  /**
+   * prints an error message if OutOfMemory (and if GUI is present a dialog),
+   * otherwise nothing happens. isOutOfMemory() has to be called beforehand,
+   * since it sets all the memory parameters.
+   * @see #isOutOfMemory()
+   * @see #m_Enabled
+   */
+  public void showOutOfMemory() {
+    if (!isEnabled())
+      return;
+      
+    System.gc();
+
+    String msg =   "Not enough memory. Please load a smaller "  
+                 + "dataset or use larger heap size.\n"
+                 + "- initial JVM size:   " 
+                 + Utils.doubleToString(toMegaByte(m_Initial), 1) + "MB\n"
+                 + "- total memory used:  " 
+                 + Utils.doubleToString(toMegaByte(m_Total), 1) + "MB\n"
+                 + "- max. memory avail.: " 
+                 + Utils.doubleToString(toMegaByte(m_Max), 1) + "MB\n"
+                 + "\n"
+                 + "Note:\n"
+                 + "The Java heap size can be specified with the -Xmx option.\n"
+                 + "E.g., to use 128MB as heap size, the command line looks like this:\n"
+                 + "   java -Xmx128m -classpath ...\n"
+                 + "This does NOT work in the SimpleCLI, the java command refers\n"
+                 + "to the one with which Weka is started.";
+    
+    System.err.println(msg);
+    
+    if (getUseGUI())
+      JOptionPane.showMessageDialog(
+          null, msg, "OutOfMemory", JOptionPane.WARNING_MESSAGE);
+  }
+
+  /**
+   * stops all the current threads, to make a restart possible
+   */
+  public void stopThreads() {
+    int           i;
+    Thread[]      thGroup;
+    Thread        t;
+
+    thGroup = new Thread[Thread.activeCount()];
+    Thread.enumerate(thGroup);
+
+    for (i = 0; i < thGroup.length; i++) {
+      t = thGroup[i];
+      if (t != null) {
+        if (t != Thread.currentThread()) {
+          if (t.getName().startsWith("Thread"))
+            t.stop();
+          else if (t.getName().startsWith("AWT-EventQueue"))
+            t.stop();
+        }
+      }
+    }
+
+    thGroup = null;
+
+    System.gc();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * prints only some statistics
+   *
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args) {
+    Memory mem = new Memory();
+    System.out.println(
+        "Initial memory: "
+        + Utils.doubleToString(Memory.toMegaByte(mem.getInitial()), 1) + "MB" 
+        + " (" + mem.getInitial() + ")");
+    System.out.println(
+        "Max memory: "
+        + Utils.doubleToString(Memory.toMegaByte(mem.getMax()), 1) + "MB"
+        + " (" + mem.getMax() + ")");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/MinkowskiDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/MinkowskiDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/MinkowskiDistance.java	(revision 29)
@@ -0,0 +1,277 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MinkowskiDistance.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.neighboursearch.PerformanceStats;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Implementing Minkowski distance (or similarity) function.<br/>
+ * <br/>
+ * One object defines not one distance but the data model in which the distances between objects of that data model can be computed.<br/>
+ * <br/>
+ * Attention: For efficiency reasons the use of consistency checks (like are the data models of the two instances exactly the same), is low.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Wikipedia. Minkowski distance. URL http://en.wikipedia.org/wiki/Minkowski_distance.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{missing_id,
+ *    author = {Wikipedia},
+ *    title = {Minkowski distance},
+ *    URL = {http://en.wikipedia.org/wiki/Minkowski_distance}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;order&gt;
+ *  The order 'p'. With '1' being the Manhattan distance and '2'
+ *  the Euclidean distance.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -D
+ *  Turns off the normalization of attribute 
+ *  values in distance calculation.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to used in the calculation of the 
+ *  distance. 'first' and 'last' are valid indices.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indices.</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class MinkowskiDistance
+  extends NormalizableDistance
+  implements Cloneable, TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -7446019339455453893L;
+  
+  /** the order of the minkowski distance. */
+  protected double m_Order = 2;
+  
+  /**
+   * Constructs an Minkowski Distance object, Instances must be still set.
+   */
+  public MinkowskiDistance() {
+    super();
+  }
+
+  /**
+   * Constructs an Minkowski Distance object and automatically initializes the
+   * ranges.
+   * 
+   * @param data 	the instances the distance function should work on
+   */
+  public MinkowskiDistance(Instances data) {
+    super(data);
+  }
+
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Implementing Minkowski distance (or similarity) function.\n\n"
+      + "One object defines not one distance but the data model in which "
+      + "the distances between objects of that data model can be computed.\n\n"
+      + "Attention: For efficiency reasons the use of consistency checks "
+      + "(like are the data models of the two instances exactly the same), "
+      + "is low.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Wikipedia");
+    result.setValue(Field.TITLE, "Minkowski distance");
+    result.setValue(Field.URL, "http://en.wikipedia.org/wiki/Minkowski_distance");
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tThe order 'p'. With '1' being the Manhattan distance and '2'\n"
+	+ "\tthe Euclidean distance.\n"
+        + "\t(default: 2)",
+        "P", 1, "-P <order>"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+      
+    return result.elements();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String orderTipText() {
+    return 
+        "The order of the Minkowski distance ('1' is Manhattan distance and "
+      + "'2' the Euclidean distance).";
+  }
+
+  /**
+   * Sets the order.
+   * 
+   * @param value	the new order
+   */
+  public void setOrder(double value) {
+    if (m_Order != 0.0) {
+      m_Order = value;
+      invalidate();
+    }
+    else {
+      System.err.println("Order cannot be zero!");
+    }
+  }
+  
+  /**
+   * Gets the order.
+   * 
+   * @return		the order
+   */
+  public double getOrder() {
+    return m_Order;
+  }   
+  
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @return 		the distance between the two given instances
+   */
+  public double distance(Instance first, Instance second) {
+    return Math.pow(distance(first, second, Double.POSITIVE_INFINITY), 1/m_Order);
+  }
+  
+  /**
+   * Calculates the distance (or similarity) between two instances. Need to
+   * pass this returned distance later on to postprocess method to set it on
+   * correct scale. <br/>
+   * P.S.: Please don't mix the use of this function with
+   * distance(Instance first, Instance second), as that already does post
+   * processing. Please consider passing Double.POSITIVE_INFINITY as the cutOffValue to
+   * this function and then later on do the post processing on all the
+   * distances.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param stats 	the structure for storing performance statistics.
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY.
+   */
+  public double distance(Instance first, Instance second, PerformanceStats stats) { //debug method pls remove after use
+    return Math.pow(distance(first, second, Double.POSITIVE_INFINITY, stats), 1/m_Order);
+  }
+  
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected double updateDistance(double currDist, double diff) {
+    double	result;
+    
+    result  = currDist;
+    result += Math.pow(Math.abs(diff), m_Order);
+    
+    return result;
+  }
+  
+  /**
+   * Does post processing of the distances (if necessary) returned by
+   * distance(distance(Instance first, Instance second, double cutOffValue). It
+   * is necessary to do so to get the correct distances if
+   * distance(distance(Instance first, Instance second, double cutOffValue) is
+   * used. This is because that function actually returns the squared distance
+   * to avoid inaccuracies arising from floating point comparison.
+   * 
+   * @param distances	the distances to post-process
+   */
+  public void postProcessDistances(double distances[]) {
+    for(int i = 0; i < distances.length; i++) {
+      distances[i] = Math.pow(distances[i], 1/m_Order);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 0$");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/MultiInstanceCapabilitiesHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/MultiInstanceCapabilitiesHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/MultiInstanceCapabilitiesHandler.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MultiInstanceClassifier.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+/**
+ * Multi-Instance classifiers can specify an additional Capabilities object
+ * for the data in the relational attribute, since the format of multi-instance
+ * data is fixed to "bag/NOMINAL,data/RELATIONAL,class".
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public interface MultiInstanceCapabilitiesHandler
+  extends CapabilitiesHandler {
+
+  /**
+   * Returns the capabilities of this multi-instance classifier for the
+   * relational data (i.e., the bags).
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities();
+}
Index: branches/MetisMQI/src/main/java/weka/core/NoSupportForMissingValuesException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/NoSupportForMissingValuesException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/NoSupportForMissingValuesException.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NoSupportForMissingValuesException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Exception that is raised by an object that is unable to process 
+ * data with missing values.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class NoSupportForMissingValuesException
+  extends WekaException {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5161175307725893973L;
+
+  /**
+   * Creates a new NoSupportForMissingValuesException with no message.
+   *
+   */
+  public NoSupportForMissingValuesException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new NoSupportForMissingValuesException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public NoSupportForMissingValuesException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/NormalizableDistance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/NormalizableDistance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/NormalizableDistance.java	(revision 29)
@@ -0,0 +1,816 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NormalizableDistance.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.neighboursearch.PerformanceStats;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Represents the abstract ancestor for normalizable distance functions, like
+ * Euclidean or Manhattan distance.
+ *
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz) -- original code from weka.core.EuclideanDistance
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz) -- original code from weka.core.EuclideanDistance
+ * @version $Revision: 5987 $
+ */
+public abstract class NormalizableDistance
+  implements DistanceFunction, OptionHandler, Serializable, RevisionHandler {
+  
+  /** Index in ranges for MIN. */
+  public static final int R_MIN = 0;
+
+  /** Index in ranges for MAX. */
+  
+  public static final int R_MAX = 1;
+  
+  /** Index in ranges for WIDTH. */
+  public static final int R_WIDTH = 2;
+
+  /** the instances used internally. */
+  protected Instances m_Data = null;
+
+  /** True if normalization is turned off (default false).*/
+  protected boolean m_DontNormalize = false;
+  
+  /** The range of the attributes. */
+  protected double[][] m_Ranges;
+
+  /** The range of attributes to use for calculating the distance. */
+  protected Range m_AttributeIndices = new Range("first-last");
+
+  /** The boolean flags, whether an attribute will be used or not. */
+  protected boolean[] m_ActiveIndices;
+  
+  /** Whether all the necessary preparations have been done. */
+  protected boolean m_Validated;
+
+  /**
+   * Invalidates the distance function, Instances must be still set.
+   */
+  public NormalizableDistance() {
+    invalidate();
+  }
+
+  /**
+   * Initializes the distance function and automatically initializes the
+   * ranges.
+   * 
+   * @param data 	the instances the distance function should work on
+   */
+  public NormalizableDistance(Instances data) {
+    setInstances(data);
+  }
+  
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public abstract String globalInfo();
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.add(new Option(
+	"\tTurns off the normalization of attribute \n"
+	+ "\tvalues in distance calculation.",
+	"D", 0, "-D"));
+    
+    result.addElement(new Option(
+	"\tSpecifies list of columns to used in the calculation of the \n"
+	+ "\tdistance. 'first' and 'last' are valid indices.\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+
+    result.addElement(new Option(
+	"\tInvert matching sense of column indices.",
+	"V", 0, "-V"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Gets the current settings. Returns empty array.
+   *
+   * @return 		an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+
+    if (getDontNormalize())
+      result.add("-D");
+    
+    result.add("-R");
+    result.add(getAttributeIndices());
+    
+    if (getInvertSelection())
+      result.add("-V");
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Parses a given list of options.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    setDontNormalize(Utils.getFlag('D', options));
+    
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices("first-last");
+
+    setInvertSelection(Utils.getFlag('V', options));
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String dontNormalizeTipText() {
+    return "Whether if the normalization of attributes should be turned off " +
+           "for distance calculation (Default: false i.e. attribute values " +
+           "are normalized). ";
+  }
+  
+  /** 
+   * Sets whether if the attribute values are to be normalized in distance
+   * calculation.
+   * 
+   * @param dontNormalize	if true the values are not normalized
+   */
+  public void setDontNormalize(boolean dontNormalize) {
+    m_DontNormalize = dontNormalize;
+    invalidate();
+  }
+  
+  /**
+   * Gets whether if the attribute values are to be normazlied in distance
+   * calculation. (default false i.e. attribute values are normalized.)
+   * 
+   * @return		false if values get normalized
+   */
+  public boolean getDontNormalize() {
+    return m_DontNormalize;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return 
+        "Specify range of attributes to act on. "
+      + "This is a comma separated list of attribute indices, with "
+      + "\"first\" and \"last\" valid values. Specify an inclusive "
+      + "range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Sets the range of attributes to use in the calculation of the distance.
+   * The indices start from 1, 'first' and 'last' are valid as well. 
+   * E.g.: first-3,5,6-last
+   * 
+   * @param value	the new attribute index range
+   */
+  public void setAttributeIndices(String value) {
+    m_AttributeIndices.setRanges(value);
+    invalidate();
+  }
+  
+  /**
+   * Gets the range of attributes used in the calculation of the distance.
+   * 
+   * @return		the attribute index range
+   */
+  public String getAttributeIndices() {
+    return m_AttributeIndices.getRanges();
+  }   
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return 
+        "Set attribute selection mode. If false, only selected "
+      + "attributes in the range will be used in the distance calculation; if "
+      + "true, only non-selected attributes will be used for the calculation.";
+  }
+  
+  /**
+   * Sets whether the matching sense of attribute indices is inverted or not.
+   * 
+   * @param value	if true the matching sense is inverted
+   */
+  public void setInvertSelection(boolean value) {
+    m_AttributeIndices.setInvert(value);
+    invalidate();
+  }
+  
+  /**
+   * Gets whether the matching sense of attribute indices is inverted or not.
+   * 
+   * @return		true if the matching sense is inverted
+   */
+  public boolean getInvertSelection() {
+    return m_AttributeIndices.getInvert();
+  }
+  
+  /**
+   * invalidates all initializations.
+   */
+  protected void invalidate() {
+    m_Validated = false;
+  }
+  
+  /**
+   * performs the initializations if necessary.
+   */
+  protected void validate() {
+    if (!m_Validated) {
+      initialize();
+      m_Validated = true;
+    }
+  }
+  
+  /**
+   * initializes the ranges and the attributes being used.
+   */
+  protected void initialize() {
+    initializeAttributeIndices();
+    initializeRanges();
+  }
+
+  /**
+   * initializes the attribute indices.
+   */
+  protected void initializeAttributeIndices() {
+    m_AttributeIndices.setUpper(m_Data.numAttributes() - 1);
+    m_ActiveIndices = new boolean[m_Data.numAttributes()];
+    for (int i = 0; i < m_ActiveIndices.length; i++)
+      m_ActiveIndices[i] = m_AttributeIndices.isInRange(i);
+  }
+
+  /**
+   * Sets the instances.
+   * 
+   * @param insts 	the instances to use
+   */
+  public void setInstances(Instances insts) {
+    m_Data = insts;
+    invalidate();
+  }
+
+  /**
+   * returns the instances currently set.
+   * 
+   * @return 		the current instances
+   */
+  public Instances getInstances() {
+    return m_Data;
+  }
+
+  /**
+   * Does nothing, derived classes may override it though.
+   * 
+   * @param distances	the distances to post-process
+   */
+  public void postProcessDistances(double[] distances) {
+  }
+
+  /**
+   * Update the distance function (if necessary) for the newly added instance.
+   * 
+   * @param ins		the instance to add
+   */
+  public void update(Instance ins) {
+    validate();
+    
+    m_Ranges = updateRanges(ins, m_Ranges);
+  }
+
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @return 		the distance between the two given instances
+   */
+  public double distance(Instance first, Instance second) {
+    return distance(first, second, null);
+  }
+
+  /**
+   * Calculates the distance between two instances.
+   * 
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param stats 	the performance stats object
+   * @return 		the distance between the two given instances
+   */
+  public double distance(Instance first, Instance second, PerformanceStats stats) {
+    return distance(first, second, Double.POSITIVE_INFINITY, stats);
+  }
+
+  /**
+   * Calculates the distance between two instances. Offers speed up (if the 
+   * distance function class in use supports it) in nearest neighbour search by 
+   * taking into account the cutOff or maximum distance. Depending on the 
+   * distance function class, post processing of the distances by 
+   * postProcessDistances(double []) may be required if this function is used.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param cutOffValue If the distance being calculated becomes larger than 
+   *                    cutOffValue then the rest of the calculation is 
+   *                    discarded.
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY if the distance being 
+   * 			calculated becomes larger than cutOffValue. 
+   */
+  public double distance(Instance first, Instance second, double cutOffValue) {
+    return distance(first, second, cutOffValue, null);
+  }
+
+  /**
+   * Calculates the distance between two instances. Offers speed up (if the 
+   * distance function class in use supports it) in nearest neighbour search by 
+   * taking into account the cutOff or maximum distance. Depending on the 
+   * distance function class, post processing of the distances by 
+   * postProcessDistances(double []) may be required if this function is used.
+   *
+   * @param first 	the first instance
+   * @param second 	the second instance
+   * @param cutOffValue If the distance being calculated becomes larger than 
+   *                    cutOffValue then the rest of the calculation is 
+   *                    discarded.
+   * @param stats 	the performance stats object
+   * @return 		the distance between the two given instances or 
+   * 			Double.POSITIVE_INFINITY if the distance being 
+   * 			calculated becomes larger than cutOffValue. 
+   */
+  public double distance(Instance first, Instance second, double cutOffValue, PerformanceStats stats) {
+    double distance = 0;
+    int firstI, secondI;
+    int firstNumValues = first.numValues();
+    int secondNumValues = second.numValues();
+    int numAttributes = m_Data.numAttributes();
+    int classIndex = m_Data.classIndex();
+    
+    validate();
+    
+    for (int p1 = 0, p2 = 0; p1 < firstNumValues || p2 < secondNumValues; ) {
+      if (p1 >= firstNumValues)
+	firstI = numAttributes;
+      else
+	firstI = first.index(p1); 
+
+      if (p2 >= secondNumValues)
+	secondI = numAttributes;
+      else
+	secondI = second.index(p2);
+
+      if (firstI == classIndex) {
+	p1++; 
+	continue;
+      }
+      if ((firstI < numAttributes) && !m_ActiveIndices[firstI]) {
+	p1++; 
+	continue;
+      }
+       
+      if (secondI == classIndex) {
+	p2++; 
+	continue;
+      }
+      if ((secondI < numAttributes) && !m_ActiveIndices[secondI]) {
+	p2++;
+	continue;
+      }
+       
+      double diff;
+      
+      if (firstI == secondI) {
+	diff = difference(firstI,
+	    		  first.valueSparse(p1),
+	    		  second.valueSparse(p2));
+	p1++;
+	p2++;
+      }
+      else if (firstI > secondI) {
+	diff = difference(secondI, 
+	    		  0, second.valueSparse(p2));
+	p2++;
+      }
+      else {
+	diff = difference(firstI, 
+	    		  first.valueSparse(p1), 0);
+	p1++;
+      }
+      if (stats != null)
+	stats.incrCoordCount();
+      
+      distance = updateDistance(distance, diff);
+      if (distance > cutOffValue)
+        return Double.POSITIVE_INFINITY;
+    }
+
+    return distance;
+  }
+  
+  /**
+   * Updates the current distance calculated so far with the new difference
+   * between two attributes. The difference between the attributes was 
+   * calculated with the difference(int,double,double) method.
+   * 
+   * @param currDist	the current distance calculated so far
+   * @param diff	the difference between two new attributes
+   * @return		the update distance
+   * @see		#difference(int, double, double)
+   */
+  protected abstract double updateDistance(double currDist, double diff);
+  
+  /**
+   * Normalizes a given value of a numeric attribute.
+   *
+   * @param x 		the value to be normalized
+   * @param i 		the attribute's index
+   * @return		the normalized value
+   */
+  protected double norm(double x, int i) {
+    if (Double.isNaN(m_Ranges[i][R_MIN]) || (m_Ranges[i][R_MAX] == m_Ranges[i][R_MIN]))
+      return 0;
+    else
+      return (x - m_Ranges[i][R_MIN]) / (m_Ranges[i][R_WIDTH]);
+  }
+
+  /**
+   * Computes the difference between two given attribute
+   * values.
+   * 
+   * @param index	the attribute index
+   * @param val1	the first value
+   * @param val2	the second value
+   * @return		the difference
+   */
+  protected double difference(int index, double val1, double val2) {
+    switch (m_Data.attribute(index).type()) {
+      case Attribute.NOMINAL:
+        if (Utils.isMissingValue(val1) ||
+           Utils.isMissingValue(val2) ||
+           ((int) val1 != (int) val2)) {
+          return 1;
+        }
+        else {
+          return 0;
+        }
+        
+      case Attribute.NUMERIC:
+        if (Utils.isMissingValue(val1) ||
+           Utils.isMissingValue(val2)) {
+          if (Utils.isMissingValue(val1) &&
+             Utils.isMissingValue(val2)) {
+            if (!m_DontNormalize)  //We are doing normalization
+              return 1;
+            else
+              return (m_Ranges[index][R_MAX] - m_Ranges[index][R_MIN]);
+          }
+          else {
+            double diff;
+            if (Utils.isMissingValue(val2)) {
+              diff = (!m_DontNormalize) ? norm(val1, index) : val1;
+            }
+            else {
+              diff = (!m_DontNormalize) ? norm(val2, index) : val2;
+            }
+            if (!m_DontNormalize && diff < 0.5) {
+              diff = 1.0 - diff;
+            }
+            else if (m_DontNormalize) {
+              if ((m_Ranges[index][R_MAX]-diff) > (diff-m_Ranges[index][R_MIN]))
+                return m_Ranges[index][R_MAX]-diff;
+              else
+                return diff-m_Ranges[index][R_MIN];
+            }
+            return diff;
+          }
+        }
+        else {
+          return (!m_DontNormalize) ? 
+              	 (norm(val1, index) - norm(val2, index)) :
+              	 (val1 - val2);
+        }
+        
+      default:
+        return 0;
+    }
+  }
+  
+  /**
+   * Initializes the ranges using all instances of the dataset.
+   * Sets m_Ranges.
+   * 
+   * @return 		the ranges
+   */
+  public double[][] initializeRanges() {
+    if (m_Data == null) {
+      m_Ranges = null;
+      return m_Ranges;
+    }
+    
+    int numAtt = m_Data.numAttributes();
+    double[][] ranges = new double [numAtt][3];
+    
+    if (m_Data.numInstances() <= 0) {
+      initializeRangesEmpty(numAtt, ranges);
+      m_Ranges = ranges;
+      return m_Ranges;
+    }
+    else {
+      // initialize ranges using the first instance
+      updateRangesFirst(m_Data.instance(0), numAtt, ranges);
+    }
+    
+    // update ranges, starting from the second
+    for (int i = 1; i < m_Data.numInstances(); i++)
+      updateRanges(m_Data.instance(i), numAtt, ranges);
+
+    m_Ranges = ranges;
+    
+    return m_Ranges;
+  }
+  
+  /**
+   * Used to initialize the ranges. For this the values of the first
+   * instance is used to save time.
+   * Sets low and high to the values of the first instance and
+   * width to zero.
+   * 
+   * @param instance 	the new instance
+   * @param numAtt 	number of attributes in the model
+   * @param ranges 	low, high and width values for all attributes
+   */
+  public void updateRangesFirst(Instance instance, int numAtt, double[][] ranges) {
+    for (int j = 0; j < numAtt; j++) {
+      if (!instance.isMissing(j)) {
+        ranges[j][R_MIN] = instance.value(j);
+        ranges[j][R_MAX] = instance.value(j);
+        ranges[j][R_WIDTH] = 0.0;
+      }
+      else { // if value was missing
+        ranges[j][R_MIN] = Double.POSITIVE_INFINITY;
+        ranges[j][R_MAX] = -Double.POSITIVE_INFINITY;
+        ranges[j][R_WIDTH] = Double.POSITIVE_INFINITY;
+      }
+    }
+  }
+  
+  /**
+   * Updates the minimum and maximum and width values for all the attributes
+   * based on a new instance.
+   * 
+   * @param instance 	the new instance
+   * @param numAtt 	number of attributes in the model
+   * @param ranges 	low, high and width values for all attributes
+   */
+  public void updateRanges(Instance instance, int numAtt, double[][] ranges) {
+    // updateRangesFirst must have been called on ranges
+    for (int j = 0; j < numAtt; j++) {
+      double value = instance.value(j);
+      if (!instance.isMissing(j)) {
+        if (value < ranges[j][R_MIN]) {
+          ranges[j][R_MIN] = value;
+          ranges[j][R_WIDTH] = ranges[j][R_MAX] - ranges[j][R_MIN];
+          if (value > ranges[j][R_MAX]) { //if this is the first value that is
+            ranges[j][R_MAX] = value;    //not missing. The,0
+            ranges[j][R_WIDTH] = ranges[j][R_MAX] - ranges[j][R_MIN];
+          }
+        }
+        else {
+          if (value > ranges[j][R_MAX]) {
+            ranges[j][R_MAX] = value;
+            ranges[j][R_WIDTH] = ranges[j][R_MAX] - ranges[j][R_MIN];
+          }
+        }
+      }
+    }
+  }
+  
+  /**
+   * Used to initialize the ranges.
+   * 
+   * @param numAtt 	number of attributes in the model
+   * @param ranges 	low, high and width values for all attributes
+   */
+  public void initializeRangesEmpty(int numAtt, double[][] ranges) {
+    for (int j = 0; j < numAtt; j++) {
+      ranges[j][R_MIN] = Double.POSITIVE_INFINITY;
+      ranges[j][R_MAX] = -Double.POSITIVE_INFINITY;
+      ranges[j][R_WIDTH] = Double.POSITIVE_INFINITY;
+    }
+  }
+  
+  /**
+   * Updates the ranges given a new instance.
+   * 
+   * @param instance 	the new instance
+   * @param ranges 	low, high and width values for all attributes
+   * @return		the updated ranges
+   */
+  public double[][] updateRanges(Instance instance, double[][] ranges) {
+    // updateRangesFirst must have been called on ranges
+    for (int j = 0; j < ranges.length; j++) {
+      double value = instance.value(j);
+      if (!instance.isMissing(j)) {
+        if (value < ranges[j][R_MIN]) {
+          ranges[j][R_MIN] = value;
+          ranges[j][R_WIDTH] = ranges[j][R_MAX] - ranges[j][R_MIN];
+        } else {
+          if (instance.value(j) > ranges[j][R_MAX]) {
+            ranges[j][R_MAX] = value;
+            ranges[j][R_WIDTH] = ranges[j][R_MAX] - ranges[j][R_MIN];
+          }
+        }
+      }
+    }
+    
+    return ranges;
+  }
+  
+  /**
+   * Initializes the ranges of a subset of the instances of this dataset.
+   * Therefore m_Ranges is not set.
+   * 
+   * @param instList 	list of indexes of the subset
+   * @return 		the ranges
+   * @throws Exception	if something goes wrong
+   */
+  public double[][] initializeRanges(int[] instList) throws Exception {
+    if (m_Data == null)
+      throw new Exception("No instances supplied.");
+    
+    int numAtt = m_Data.numAttributes();
+    double[][] ranges = new double [numAtt][3];
+    
+    if (m_Data.numInstances() <= 0) {
+      initializeRangesEmpty(numAtt, ranges);
+      return ranges;
+    }
+    else {
+      // initialize ranges using the first instance
+      updateRangesFirst(m_Data.instance(instList[0]), numAtt, ranges);
+      // update ranges, starting from the second
+      for (int i = 1; i < instList.length; i++) {
+        updateRanges(m_Data.instance(instList[i]), numAtt, ranges);
+      }
+    }
+    return ranges;
+  }
+
+  /**
+   * Initializes the ranges of a subset of the instances of this dataset.
+   * Therefore m_Ranges is not set.
+   * The caller of this method should ensure that the supplied start and end 
+   * indices are valid (start &lt;= end, end&lt;instList.length etc) and
+   * correct.
+   *
+   * @param instList 	list of indexes of the instances
+   * @param startIdx 	start index of the subset of instances in the indices array
+   * @param endIdx 	end index of the subset of instances in the indices array
+   * @return 		the ranges
+   * @throws Exception	if something goes wrong
+   */
+  public double[][] initializeRanges(int[] instList, int startIdx, int endIdx) throws Exception {
+    if (m_Data == null)
+      throw new Exception("No instances supplied.");
+    
+    int numAtt = m_Data.numAttributes();
+    double[][] ranges = new double [numAtt][3];
+    
+    if (m_Data.numInstances() <= 0) {
+      initializeRangesEmpty(numAtt, ranges);
+      return ranges;
+    }
+    else {
+      // initialize ranges using the first instance
+      updateRangesFirst(m_Data.instance(instList[startIdx]), numAtt, ranges);
+      // update ranges, starting from the second
+      for (int i = startIdx+1; i <= endIdx; i++) {
+        updateRanges(m_Data.instance(instList[i]), numAtt, ranges);
+      }
+    }
+    
+    return ranges;
+  }
+  
+  /**
+   * Update the ranges if a new instance comes.
+   * 
+   * @param instance 	the new instance
+   */
+  public void updateRanges(Instance instance) {
+    validate();
+    
+    m_Ranges = updateRanges(instance, m_Ranges);
+  }
+  
+  /**
+   * Test if an instance is within the given ranges.
+   * 
+   * @param instance 	the instance
+   * @param ranges 	the ranges the instance is tested to be in
+   * @return true 	if instance is within the ranges
+   */
+  public boolean inRanges(Instance instance, double[][] ranges) {
+    boolean isIn = true;
+    
+    // updateRangesFirst must have been called on ranges
+    for (int j = 0; isIn && (j < ranges.length); j++) {
+      if (!instance.isMissing(j)) {
+        double value = instance.value(j);
+        isIn = value <= ranges[j][R_MAX];
+        if (isIn) isIn = value >= ranges[j][R_MIN];
+      }
+    }
+    
+    return isIn;
+  }
+  
+  /**
+   * Check if ranges are set.
+   * 
+   * @return 		true if ranges are set
+   */
+  public boolean rangesSet() {
+    return (m_Ranges != null);
+  }
+  
+  /**
+   * Method to get the ranges.
+   * 
+   * @return 		the ranges
+   * @throws Exception	if no randes are set yet
+   */
+  public double[][] getRanges() throws Exception {
+    validate();
+    
+    if (m_Ranges == null)
+      throw new Exception("Ranges not yet set.");
+    
+    return m_Ranges;
+  }
+  
+  /**
+   * Returns an empty string.
+   * 
+   * @return		an empty string
+   */
+  public String toString() {
+    return "";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Optimization.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Optimization.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Optimization.java	(revision 29)
@@ -0,0 +1,1394 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Optimization.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ * Implementation of Active-sets method with BFGS update to solve optimization
+ * problem with only bounds constraints in multi-dimensions.  In this
+ * implementation we consider both the lower and higher bound constraints. <p/>
+ *
+ * Here is the sketch of our searching strategy, and the detailed description
+ * of the algorithm can be found in the Appendix of Xin Xu's MSc thesis:<p/>
+ * Initialize everything, incl. initial value, direction, etc.<p/>
+ * LOOP (main algorithm):<br/>
+ * 
+ * 1. Perform the line search using the directions for free variables<br/>
+ * 1.1  Check all the bounds that are not "active" (i.e. binding variables)
+ *      and compute the feasible step length to the bound for each of them<br/>
+ * 1.2  Pick up the least feasible step length, say \alpha, and set it as 
+ *      the upper bound of the current step length, i.e.
+ *      0&lt;\lambda&lt;=\alpha<br/>
+ * 1.3  Search for any possible step length&lt;=\alpha that can result the 
+ *      "sufficient function decrease" (\alpha condition) AND "positive
+ *      definite inverse Hessian" (\beta condition), if possible, using
+ *      SAFEGUARDED polynomial interpolation.  This step length is "safe" and
+ *      thus is used to compute the next value of the free variables .<br/>
+ * 1.4  Fix the variable(s) that are newly bound to its constraint(s).<p/>     
+ *
+ * 2. Check whether there is convergence of all variables or their gradients.
+ *    If there is, check the possibilities to release any current bindings of
+ *    the fixed variables to their bounds based on the "reliable" second-order
+ *    Lagarange multipliers if available.  If it's available and negative for
+ *    one variable, then release it.  If not available, use first-order
+ *    Lagarange multiplier to test release.  If there is any released
+ *    variables, STOP the loop.  Otherwise update the inverse of Hessian matrix
+ *    and gradient for the newly released variables and CONTINUE LOOP.<p/>
+ *
+ * 3. Use BFGS formula to update the inverse of Hessian matrix.  Note the 
+ *    already-fixed variables must have zeros in the corresponding entries
+ *    in the inverse Hessian.<p/>  
+ *
+ * 4. Compute the new (newton) search direction d=H^{-1}*g, where H^{-1} is the 
+ *    inverse Hessian and g is the Jacobian.  Note that again, the already-
+ *    fixed variables will have zero direction.<p/>
+ *
+ * ENDLOOP<p/>
+ *
+ * A typical usage of this class is to create your own subclass of this class
+ * and provide the objective function and gradients as follows:<p/>
+ * <pre>
+ * class MyOpt extends Optimization {
+ *   // Provide the objective function
+ *   protected double objectiveFunction(double[] x) {
+ *       // How to calculate your objective function...
+ *       // ...
+ *   }
+ *
+ *   // Provide the first derivatives
+ *   protected double[] evaluateGradient(double[] x) {
+ *       // How to calculate the gradient of the objective function...
+ *       // ...
+ *   }
+ *
+ *   // If possible, provide the index^{th} row of the Hessian matrix
+ *   protected double[] evaluateHessian(double[] x, int index) {
+ *      // How to calculate the index^th variable's second derivative
+ *      // ... 
+ *   }
+ * }
+ * </pre>
+ *
+ * When it's the time to use it, in some routine(s) of other class...
+ * <pre>
+ * MyOpt opt = new MyOpt();
+ * 
+ * // Set up initial variable values and bound constraints
+ * double[] x = new double[numVariables];
+ * // Lower and upper bounds: 1st row is lower bounds, 2nd is upper
+ * double[] constraints = new double[2][numVariables];
+ * ...
+ *
+ * // Find the minimum, 200 iterations as default
+ * x = opt.findArgmin(x, constraints); 
+ * while(x == null){  // 200 iterations are not enough
+ *    x = opt.getVarbValues();  // Try another 200 iterations
+ *    x = opt.findArgmin(x, constraints);
+ * }
+ *
+ * // The minimal function value
+ * double minFunction = opt.getMinFunction();
+ * ...
+ * </pre>
+ * 
+ * It is recommended that Hessian values be provided so that the second-order
+ * Lagrangian multiplier estimate can be calcluated.  However, if it is not
+ * provided, there is no need to override the <code>evaluateHessian()</code>
+ * function.<p/>
+ *
+ * REFERENCES (see also the <code>getTechnicalInformation()</code> method):<br/>
+ * The whole model algorithm is adapted from Chapter 5 and other related
+ * chapters in Gill, Murray and Wright(1981) "Practical Optimization", Academic
+ * Press.  and Gill and Murray(1976) "Minimization Subject to Bounds on the
+ * Variables", NPL Report NAC72, while Chong and Zak(1996) "An Introduction to
+ * Optimization", John Wiley &amp; Sons, Inc. provides us a brief but helpful
+ * introduction to the method. <p/>
+ *
+ * Dennis and Schnabel(1983) "Numerical Methods for Unconstrained Optimization
+ * and Nonlinear Equations", Prentice-Hall Inc. and Press et al.(1992) "Numeric
+ * Recipe in C", Second Edition, Cambridge University Press. are consulted for
+ * the polynomial interpolation used in the line search implementation.  <p/>
+ *
+ * The Hessian modification in BFGS update uses Cholesky factorization and two
+ * rank-one modifications:<br/>
+ * Bk+1 = Bk + (Gk*Gk')/(Gk'Dk) + (dGk*(dGk)'))/[alpha*(dGk)'*Dk]. <br/>
+ * where Gk is the gradient vector, Dk is the direction vector and alpha is the
+ * step rate. <br/>
+ * This method is due to Gill, Golub, Murray and Saunders(1974) ``Methods for
+ * Modifying Matrix Factorizations'', Mathematics of Computation, Vol.28,
+ * No.126, pp 505-535. <p/>
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $ 
+ * @see #getTechnicalInformation()
+ */
+public abstract class Optimization
+    implements TechnicalInformationHandler, RevisionHandler {
+    
+    protected double m_ALF = 1.0e-4;
+
+    protected double m_BETA = 0.9;    
+
+    protected double m_TOLX = 1.0e-6;
+   
+    protected double m_STPMX = 100.0;
+    
+    protected int m_MAXITS = 200;
+    
+    protected static boolean m_Debug = false;
+    
+    /** function value */
+    protected double m_f;    
+ 
+    /** G'*p */
+    private double m_Slope;
+    
+    /** Test if zero step in lnsrch */
+    private boolean m_IsZeroStep = false;
+    
+    /** Used when iteration overflow occurs */
+    private double[] m_X;
+    
+    /** Compute machine precision */
+    protected static double m_Epsilon, m_Zero; 
+    static {
+	m_Epsilon=1.0;
+	while(1.0+m_Epsilon > 1.0){
+	    m_Epsilon /= 2.0;	    
+	}
+	m_Epsilon *= 2.0;
+	m_Zero = Math.sqrt(m_Epsilon);
+	if (m_Debug)
+	    System.err.print("Machine precision is "+m_Epsilon+
+			     " and zero set to "+m_Zero);
+    }
+    
+    /**
+     * Returns an instance of a TechnicalInformation object, containing 
+     * detailed information about the technical background of this class,
+     * e.g., paper reference or book this class is based on.
+     * 
+     * @return the technical information about this class
+     */
+    public TechnicalInformation getTechnicalInformation() {
+      TechnicalInformation 	result;
+      TechnicalInformation 	additional;
+      
+      result = new TechnicalInformation(Type.MASTERSTHESIS);
+      result.setValue(Field.AUTHOR, "Xin Xu");
+      result.setValue(Field.YEAR, "2003");
+      result.setValue(Field.TITLE, "Statistical learning in multiple instance problem");
+      result.setValue(Field.SCHOOL, "University of Waikato");
+      result.setValue(Field.ADDRESS, "Hamilton, NZ");
+      result.setValue(Field.NOTE, "0657.594");
+
+      additional = result.add(Type.BOOK);
+      additional.setValue(Field.AUTHOR, "P. E. Gill and W. Murray and M. H. Wright");
+      additional.setValue(Field.YEAR, "1981");
+      additional.setValue(Field.TITLE, "Practical Optimization");
+      additional.setValue(Field.PUBLISHER, "Academic Press");
+      additional.setValue(Field.ADDRESS, "London and New York");
+      
+      additional = result.add(Type.TECHREPORT);
+      additional.setValue(Field.AUTHOR, "P. E. Gill and W. Murray");
+      additional.setValue(Field.YEAR, "1976");
+      additional.setValue(Field.TITLE, "Minimization subject to bounds on the variables");
+      additional.setValue(Field.INSTITUTION, "National Physical Laboratory");
+      additional.setValue(Field.NUMBER, "NAC 72");
+      
+      additional = result.add(Type.BOOK);
+      additional.setValue(Field.AUTHOR, "E. K. P. Chong and S. H. Zak");
+      additional.setValue(Field.YEAR, "1996");
+      additional.setValue(Field.TITLE, "An Introduction to Optimization");
+      additional.setValue(Field.PUBLISHER, "John Wiley and Sons");
+      additional.setValue(Field.ADDRESS, "New York");
+      
+      additional = result.add(Type.BOOK);
+      additional.setValue(Field.AUTHOR, "J. E. Dennis and R. B. Schnabel");
+      additional.setValue(Field.YEAR, "1983");
+      additional.setValue(Field.TITLE, "Numerical Methods for Unconstrained Optimization and Nonlinear Equations");
+      additional.setValue(Field.PUBLISHER, "Prentice-Hall");
+      
+      additional = result.add(Type.BOOK);
+      additional.setValue(Field.AUTHOR, "W. H. Press and B. P. Flannery and S. A. Teukolsky and W. T. Vetterling");
+      additional.setValue(Field.YEAR, "1992");
+      additional.setValue(Field.TITLE, "Numerical Recipes in C");
+      additional.setValue(Field.PUBLISHER, "Cambridge University Press");
+      additional.setValue(Field.EDITION, "Second");
+      
+      additional = result.add(Type.ARTICLE);
+      additional.setValue(Field.AUTHOR, "P. E. Gill and G. H. Golub and W. Murray and M. A. Saunders");
+      additional.setValue(Field.YEAR, "1974");
+      additional.setValue(Field.TITLE, "Methods for modifying matrix factorizations");
+      additional.setValue(Field.JOURNAL, "Mathematics of Computation");
+      additional.setValue(Field.VOLUME, "28");
+      additional.setValue(Field.NUMBER, "126");
+      additional.setValue(Field.PAGES, "505-535");
+      
+      return result;
+    }
+    
+    /**
+     * Subclass should implement this procedure to evaluate objective
+     * function to be minimized
+     * 
+     * @param x the variable values
+     * @return the objective function value
+     * @throws Exception if something goes wrong
+     */
+    protected abstract double objectiveFunction(double[] x) throws Exception;
+
+    /**
+     * Subclass should implement this procedure to evaluate gradient
+     * of the objective function
+     * 
+     * @param x the variable values
+     * @return the gradient vector
+     * @throws Exception if something goes wrong
+     */
+    protected abstract double[] evaluateGradient(double[] x) throws Exception;
+
+    /**
+     * Subclass is recommended to override this procedure to evaluate second-order
+     * gradient of the objective function.  If it's not provided, it returns
+     * null.
+     *
+     * @param x the variables
+     * @param index the row index in the Hessian matrix
+     * @return one row (the row #index) of the Hessian matrix, null as default
+     * @throws Exception if something goes wrong
+     */
+    protected double[] evaluateHessian(double[] x, int index) throws Exception{
+	return null;
+    }
+
+    /**
+     * Get the minimal function value
+     *
+     * @return minimal function value found
+     */
+    public double getMinFunction() {
+      return m_f;
+    }
+
+    /**
+     * Set the maximal number of iterations in searching (Default 200)
+     *
+     * @param it the maximal number of iterations
+     */
+    public void setMaxIteration(int it) {
+      m_MAXITS=it;
+    }
+      
+    /**
+     * Set whether in debug mode
+     *
+     * @param db use debug or not
+     */
+    public void setDebug(boolean db) {
+      m_Debug = db;
+    }
+    
+    /**
+     * Get the variable values.  Only needed when iterations exceeds 
+     * the max threshold.
+     *
+     * @return the current variable values
+     */
+    public double[] getVarbValues() {
+      return m_X;
+    }
+    
+    /**
+     * Find a new point x in the direction p from a point xold at which the
+     * value of the function has decreased sufficiently, the positive 
+     * definiteness of B matrix (approximation of the inverse of the Hessian)
+     * is preserved and no bound constraints are violated.  Details see "Numerical 
+     * Methods for Unconstrained Optimization and Nonlinear Equations".
+     * "Numeric Recipes in C" was also consulted.
+     *
+     * @param xold old x value 
+     * @param gradient gradient at that point
+     * @param direct direction vector
+     * @param stpmax maximum step length
+     * @param isFixed indicating whether a variable has been fixed
+     * @param nwsBounds non-working set bounds.  Means these variables are free and
+     *                  subject to the bound constraints in this step
+     * @param wsBdsIndx index of variables that has working-set bounds.  Means
+     *                  these variables are already fixed and no longer subject to
+     *                  the constraints
+     * @return new value along direction p from xold, null if no step was taken
+     * @throws Exception if an error occurs
+     */
+    public double[] lnsrch(double[] xold, double[] gradient, 
+			   double[] direct, double stpmax,
+			   boolean[] isFixed, double[][] nwsBounds,
+			   DynamicIntArray wsBdsIndx)
+	throws Exception {
+	
+	int i, k,len=xold.length, 
+	    fixedOne=-1; // idx of variable to be fixed
+	double alam, alamin; // lambda to be found, and its lower bound
+	
+	// For convergence and bound test
+	double temp,test,alpha=Double.POSITIVE_INFINITY,fold=m_f,sum; 
+	
+	// For cubic interpolation
+	double a,alam2=0,b,disc=0,maxalam=1.0,rhs1,rhs2,tmplam;
+	
+	double[] x = new double[len]; // New variable values
+	
+	// Scale the step 
+	for (sum=0.0,i=0;i<len;i++){
+	    if(!isFixed[i]) // For fixed variables, direction = 0
+		sum += direct[i]*direct[i];
+	}	
+	sum = Math.sqrt(sum);
+	
+	if (m_Debug)
+	    System.err.println("fold:  "+Utils.doubleToString(fold,10,7)+"\n"+
+			       "sum:  "+Utils.doubleToString(sum,10,7)+"\n"+
+			       "stpmax:  "+Utils.doubleToString(stpmax,10,7));
+	if (sum > stpmax){
+	    for (i=0;i<len;i++)
+		if(!isFixed[i])
+		    direct[i] *= stpmax/sum;		
+	}
+	else
+	    maxalam = stpmax/sum;
+	
+	// Compute initial rate of decrease, g'*d 
+	m_Slope=0.0;
+	for (i=0;i<len;i++){
+	    x[i] = xold[i];
+	    if(!isFixed[i])
+		m_Slope += gradient[i]*direct[i];
+	}
+	
+	if (m_Debug)
+	    System.err.print("slope:  " + Utils.doubleToString(m_Slope,10,7)+ "\n");
+	
+	// Slope too small
+	if(Math.abs(m_Slope)<=m_Zero){
+	    if (m_Debug)
+		System.err.println("Gradient and direction orthogonal -- "+
+				   "Min. found with current fixed variables"+
+				   " (or all variables fixed). Try to release"+
+				   " some variables now.");
+	    return x;
+	}
+	
+	// Err: slope > 0
+	if(m_Slope > m_Zero){
+	    if(m_Debug)
+		for(int h=0; h<x.length; h++)
+		    System.err.println(h+": isFixed="+isFixed[h]+", x="+
+				       x[h]+", grad="+gradient[h]+", direct="+
+				       direct[h]);
+	    throw new Exception("g'*p positive! -- Try to debug from here: line 327.");
+	}
+	
+	// Compute LAMBDAmin and upper bound of lambda--alpha
+	test=0.0;
+	for(i=0;i<len;i++){	    
+	    if(!isFixed[i]){// No need for fixed variables
+		temp=Math.abs(direct[i])/Math.max(Math.abs(x[i]),1.0);
+		if (temp > test) test=temp;
+	    }
+	}
+	
+	if(test>m_Zero) // Not converge
+	    alamin = m_TOLX/test;
+	else{
+	    if (m_Debug)
+		System.err.println("Zero directions for all free variables -- "+
+				   "Min. found with current fixed variables"+
+				   " (or all variables fixed). Try to release"+
+				   " some variables now.");
+	    return x;
+	}
+		
+	// Check whether any non-working-set bounds are "binding"
+	for(i=0;i<len;i++){
+	    if(!isFixed[i]){// No need for fixed variables
+		double alpi;
+		if((direct[i]<-m_Epsilon) && !Double.isNaN(nwsBounds[0][i])){//Not feasible
+		    alpi = (nwsBounds[0][i]-xold[i])/direct[i];
+		    if(alpi <= m_Zero){ // Zero
+			if (m_Debug)
+			    System.err.println("Fix variable "+i+
+					       " to lower bound "+ nwsBounds[0][i]+
+					       " from value "+ xold[i]);
+			x[i] = nwsBounds[0][i];
+			isFixed[i]=true; // Fix this variable
+			alpha = 0.0;
+			nwsBounds[0][i]=Double.NaN; //Add cons. to working set
+			wsBdsIndx.addElement(i);
+		    }
+		    else if(alpha > alpi){ // Fix one variable in one iteration
+			alpha = alpi;
+			fixedOne = i;
+		    }			
+		}
+		else if((direct[i]>m_Epsilon) && !Double.isNaN(nwsBounds[1][i])){//Not feasible
+		    alpi = (nwsBounds[1][i]-xold[i])/direct[i];
+		    if(alpi <= m_Zero){ // Zero
+			if (m_Debug)
+			    System.err.println("Fix variable "+i+
+					       " to upper bound "+ nwsBounds[1][i]+
+					       " from value "+ xold[i]);
+			x[i] = nwsBounds[1][i];
+			isFixed[i]=true; // Fix this variable
+			alpha = 0.0;
+			nwsBounds[1][i]=Double.NaN; //Add cons. to working set
+			wsBdsIndx.addElement(i);
+		    }
+		    else if(alpha > alpi){
+			alpha = alpi;
+			fixedOne = i;
+		    }			
+		}				
+	    }
+	}	
+	
+	if (m_Debug){
+	    System.err.println("alamin: " + Utils.doubleToString(alamin,10,7));
+	    System.err.println("alpha: " + Utils.doubleToString(alpha,10,7));
+	}
+	
+	if(alpha <= m_Zero){ // Zero	   
+	    m_IsZeroStep = true;
+	    if (m_Debug)
+		System.err.println("Alpha too small, try again");
+	    return x;
+	}
+	
+	alam = alpha; // Always try full feasible newton step 
+	if(alam > 1.0)
+	    alam = 1.0;
+	
+	// Iteration of one newton step, if necessary, backtracking is done
+	double initF=fold, // Initial function value
+	    hi=alam, lo=alam, newSlope=0, fhi=m_f, flo=m_f;// Variables used for beta condition
+	double[] newGrad;  // Gradient on the new variable values
+	
+	kloop:
+	for (k=0;;k++) {
+	    if(m_Debug)
+		System.err.println("\nLine search iteration: " + k);
+	    
+	    for (i=0;i<len;i++){
+		if(!isFixed[i]){
+		    x[i] = xold[i]+alam*direct[i];  // Compute xnew
+		    if(!Double.isNaN(nwsBounds[0][i]) && (x[i]<nwsBounds[0][i])){    
+			x[i] = nwsBounds[0][i]; //Rounding error	
+		    }
+		    else if(!Double.isNaN(nwsBounds[1][i]) && (x[i]>nwsBounds[1][i])){		
+			x[i] = nwsBounds[1][i]; //Rounding error	
+		    }
+		}
+	    }
+	    
+	    m_f = objectiveFunction(x);    // Compute fnew
+	    if(Double.isNaN(m_f))
+		throw new Exception("Objective function value is NaN!");
+	
+	    while(Double.isInfinite(m_f)){ // Avoid infinity
+		if(m_Debug)
+		    System.err.println("Too large m_f.  Shrink step by half.");
+		alam *= 0.5; // Shrink by half
+		if(alam <= m_Epsilon){
+		    if(m_Debug)
+			System.err.println("Wrong starting points, change them!");
+		    return x;
+		}
+		
+		for (i=0;i<len;i++)
+		    if(!isFixed[i])
+			x[i] = xold[i]+alam*direct[i]; 
+		
+		m_f = objectiveFunction(x); 
+		if(Double.isNaN(m_f))
+		    throw new Exception("Objective function value is NaN!");
+	
+		initF = Double.POSITIVE_INFINITY;
+	    }
+	    
+	    if(m_Debug) {
+		System.err.println("obj. function: " + 
+				   Utils.doubleToString(m_f, 10, 7));
+		System.err.println("threshold: " + 
+				   Utils.doubleToString(fold+m_ALF*alam*m_Slope,10,7));
+	    }
+	    
+	    if(m_f<=fold+m_ALF*alam*m_Slope){// Alpha condition: sufficient function decrease
+		if(m_Debug)		
+		    System.err.println("Sufficient function decrease (alpha condition): "); 
+		newGrad = evaluateGradient(x);
+		for(newSlope=0.0,i=0; i<len; i++)
+		    if(!isFixed[i])
+			newSlope += newGrad[i]*direct[i];
+
+		if(newSlope >= m_BETA*m_Slope){ // Beta condition: ensure pos. defnty.	
+		    if(m_Debug)		
+			System.err.println("Increasing derivatives (beta condition): "); 	
+
+		    if((fixedOne!=-1) && (alam>=alpha)){ // Has bounds and over
+			if(direct[fixedOne] > 0){
+			    x[fixedOne] = nwsBounds[1][fixedOne]; // Avoid rounding error
+			    nwsBounds[1][fixedOne]=Double.NaN; //Add cons. to working set
+			}
+			else{
+			    x[fixedOne] = nwsBounds[0][fixedOne]; // Avoid rounding error
+			    nwsBounds[0][fixedOne]=Double.NaN; //Add cons. to working set
+			}
+			
+			if(m_Debug)
+			    System.err.println("Fix variable "
+					       +fixedOne+" to bound "+ x[fixedOne]+
+					       " from value "+ xold[fixedOne]);
+			isFixed[fixedOne]=true; // Fix the variable
+			wsBdsIndx.addElement(fixedOne);
+		    }		
+		    return x;
+		}
+		else if(k==0){ // First time: increase alam 
+		    // Search for the smallest value not complying with alpha condition
+		    double upper = Math.min(alpha,maxalam); 
+		    if(m_Debug)
+			System.err.println("Alpha condition holds, increase alpha... ");
+		    while(!((alam>=upper) || (m_f>fold+m_ALF*alam*m_Slope))){
+			lo = alam;
+			flo = m_f;
+			alam *= 2.0;
+			if(alam>=upper)  // Avoid rounding errors
+			    alam=upper;
+
+			for (i=0;i<len;i++)
+			    if(!isFixed[i])
+				x[i] = xold[i]+alam*direct[i];
+			m_f = objectiveFunction(x);
+			if(Double.isNaN(m_f))
+			    throw new Exception("Objective function value is NaN!");
+			
+			newGrad = evaluateGradient(x);
+			for(newSlope=0.0,i=0; i<len; i++)
+			    if(!isFixed[i])
+				newSlope += newGrad[i]*direct[i];
+			
+			if(newSlope >= m_BETA*m_Slope){
+			    if (m_Debug)		
+				System.err.println("Increasing derivatives (beta condition): \n"+
+						   "newSlope = "+Utils.doubleToString(newSlope,10,7));
+			    
+			    if((fixedOne!=-1) && (alam>=alpha)){ // Has bounds and over
+				if(direct[fixedOne] > 0){
+				    x[fixedOne] = nwsBounds[1][fixedOne]; // Avoid rounding error
+				    nwsBounds[1][fixedOne]=Double.NaN; //Add cons. to working set
+				}
+				else{
+				    x[fixedOne] = nwsBounds[0][fixedOne]; // Avoid rounding error
+				    nwsBounds[0][fixedOne]=Double.NaN; //Add cons. to working set
+				}
+				
+				if(m_Debug)
+				    System.err.println("Fix variable "
+						       +fixedOne+" to bound "+ x[fixedOne]+
+						       " from value "+ xold[fixedOne]);
+				isFixed[fixedOne]=true; // Fix the variable
+				wsBdsIndx.addElement(fixedOne);
+			    }		 				    
+			    return x;
+			}
+		    }
+		    hi = alam;
+		    fhi = m_f;			
+		    break kloop;
+		}
+		else{
+		    if(m_Debug)
+			System.err.println("Alpha condition holds.");
+		    hi = alam2; lo = alam; flo = m_f;
+		    break kloop;
+		}		    
+	    }        
+	    else if (alam < alamin) { // No feasible lambda found       
+		if(initF<fold){ 
+		    alam = Math.min(1.0,alpha);
+		    for (i=0;i<len;i++)
+			if(!isFixed[i])
+			    x[i] = xold[i]+alam*direct[i]; //Still take Alpha
+		    
+		    if (m_Debug)
+			System.err.println("No feasible lambda: still take"+
+					   " alpha="+alam);
+		    
+		    if((fixedOne!=-1) && (alam>=alpha)){ // Has bounds and over
+			if(direct[fixedOne] > 0){
+			    x[fixedOne] = nwsBounds[1][fixedOne]; // Avoid rounding error
+			    nwsBounds[1][fixedOne]=Double.NaN; //Add cons. to working set
+			}
+			else{
+			    x[fixedOne] = nwsBounds[0][fixedOne]; // Avoid rounding error
+			    nwsBounds[0][fixedOne]=Double.NaN; //Add cons. to working set
+			}
+			
+			if(m_Debug)
+			    System.err.println("Fix variable "
+					       +fixedOne+" to bound "+ x[fixedOne]+
+					       " from value "+ xold[fixedOne]);
+			isFixed[fixedOne]=true; // Fix the variable
+			wsBdsIndx.addElement(fixedOne);
+		    }		 		    
+		}
+		else{   // Convergence on delta(x)
+		    for(i=0;i<len;i++) 
+			x[i]=xold[i];
+		    m_f=fold;
+		    if (m_Debug)
+			System.err.println("Cannot find feasible lambda"); 
+		}
+		
+		return x; 
+	    }
+	    else { // Backtracking by polynomial interpolation
+		if(k==0){ // First time backtrack: quadratic interpolation
+		    if(!Double.isInfinite(initF))
+			initF = m_f;		    
+		    // lambda = -g'(0)/(2*g''(0))
+		    tmplam = -0.5*alam*m_Slope/((m_f-fold)/alam-m_Slope);
+		}
+		else {    // Subsequent backtrack: cubic interpolation 
+		    rhs1 = m_f-fold-alam*m_Slope;
+		    rhs2 = fhi-fold-alam2*m_Slope;
+		    a=(rhs1/(alam*alam)-rhs2/(alam2*alam2))/(alam-alam2);
+		    b=(-alam2*rhs1/(alam*alam)+alam*rhs2/(alam2*alam2))/(alam-alam2);
+		    if (a == 0.0) tmplam = -m_Slope/(2.0*b);
+		    else {
+			disc=b*b-3.0*a*m_Slope;
+			if (disc < 0.0) disc = 0.0;
+			double numerator = -b+Math.sqrt(disc);
+			if(numerator >= Double.MAX_VALUE){
+			    numerator = Double.MAX_VALUE;
+			    if (m_Debug)
+				System.err.print("-b+sqrt(disc) too large! Set it to MAX_VALUE.");
+			}
+			tmplam=numerator/(3.0*a);
+		    }
+		    if (m_Debug)
+			System.err.print("Cubic interpolation: \n" + 
+					 "a:   " + Utils.doubleToString(a,10,7)+ "\n" +
+					 "b:   " + Utils.doubleToString(b,10,7)+ "\n" +
+					 "disc:   " + Utils.doubleToString(disc,10,7)+ "\n" +
+					 "tmplam:   " + tmplam + "\n" +
+					 "alam:   " + Utils.doubleToString(alam,10,7)+ "\n");	
+		    if (tmplam>0.5*alam)
+			tmplam=0.5*alam;             // lambda <= 0.5*lambda_old
+		}
+	    }
+	    alam2=alam;
+	    fhi=m_f;
+	    alam=Math.max(tmplam,0.1*alam);          // lambda >= 0.1*lambda_old
+	    
+	    if(alam>alpha){
+		throw new Exception("Sth. wrong in lnsrch:"+
+				    "Lambda infeasible!(lambda="+alam+
+				    ", alpha="+alpha+", upper="+tmplam+
+				    "|"+(-alpha*m_Slope/(2.0*((m_f-fold)/alpha-m_Slope)))+
+				    ", m_f="+m_f+", fold="+fold+
+				    ", slope="+m_Slope);
+	    }	    
+	} // Endfor(k=0;;k++)
+	
+	// Quadratic interpolation between lamda values between lo and hi.
+	// If cannot find a value satisfying beta condition, use lo.
+	double ldiff = hi-lo, lincr;
+	if(m_Debug)
+	    System.err.println("Last stage of searching for beta condition (alam between "
+			       +Utils.doubleToString(lo,10,7)+" and "
+			       +Utils.doubleToString(hi,10,7)+")...\n"+
+			       "Quadratic Interpolation(QI):\n"+
+			       "Last newSlope = "+Utils.doubleToString(newSlope, 10, 7));
+	
+	while((newSlope<m_BETA*m_Slope) && (ldiff>=alamin)){
+	    lincr = -0.5*newSlope*ldiff*ldiff/(fhi-flo-newSlope*ldiff);
+
+	    if(m_Debug)
+		System.err.println("fhi = "+fhi+"\n"+
+				   "flo = "+flo+"\n"+
+				   "ldiff = "+ldiff+"\n"+
+				   "lincr (using QI) = "+lincr+"\n");
+	    
+	    if(lincr<0.2*ldiff) lincr=0.2*ldiff;
+	    alam = lo+lincr;
+	    if(alam >= hi){ // We cannot go beyond the bounds, so the best we can try is hi
+	    	alam=hi;
+		lincr=ldiff;
+	    }
+	    for (i=0;i<len;i++)
+		if(!isFixed[i])
+		    x[i] = xold[i]+alam*direct[i];
+	    m_f = objectiveFunction(x);
+	    if(Double.isNaN(m_f))
+		throw new Exception("Objective function value is NaN!");
+	
+	    if(m_f>fold+m_ALF*alam*m_Slope){ 
+		// Alpha condition fails, shrink lambda_upper
+		ldiff = lincr;
+		fhi = m_f;
+	    }	    
+	    else{ // Alpha condition holds	    
+		newGrad = evaluateGradient(x);
+		for(newSlope=0.0,i=0; i<len; i++)
+		    if(!isFixed[i])
+			newSlope += newGrad[i]*direct[i];
+		
+		if(newSlope < m_BETA*m_Slope){
+		    // Beta condition fails, shrink lambda_lower
+		    lo = alam;
+		    ldiff -= lincr;
+		    flo = m_f;
+		}
+	    }
+	} 
+	
+	if(newSlope < m_BETA*m_Slope){ // Cannot satisfy beta condition, take lo
+	    if(m_Debug)
+		System.err.println("Beta condition cannot be satisfied, take alpha condition");
+	    alam=lo;
+	    for (i=0;i<len;i++)
+		if(!isFixed[i])
+		    x[i] = xold[i]+alam*direct[i];
+	    m_f = flo;
+	}
+	else if(m_Debug)
+	    System.err.println("Both alpha and beta conditions are satisfied. alam="
+			       +Utils.doubleToString(alam,10,7));
+	
+	if((fixedOne!=-1) && (alam>=alpha)){ // Has bounds and over
+	    if(direct[fixedOne] > 0){
+		x[fixedOne] = nwsBounds[1][fixedOne]; // Avoid rounding error
+		nwsBounds[1][fixedOne]=Double.NaN; //Add cons. to working set
+	    }
+	    else{
+		x[fixedOne] = nwsBounds[0][fixedOne]; // Avoid rounding error
+		nwsBounds[0][fixedOne]=Double.NaN; //Add cons. to working set
+	    }
+	    
+	    if(m_Debug)
+		System.err.println("Fix variable "
+				   +fixedOne+" to bound "+ x[fixedOne]+
+				   " from value "+ xold[fixedOne]);
+	    isFixed[fixedOne]=true; // Fix the variable
+	    wsBdsIndx.addElement(fixedOne);
+	}
+	
+	return x;
+    }
+    
+    /**
+     * Main algorithm.  Descriptions see "Practical Optimization"
+     *
+     * @param initX initial point of x, assuming no value's on the bound!
+     * @param constraints the bound constraints of each variable
+     *                    constraints[0] is the lower bounds and 
+     *                    constraints[1] is the upper bounds
+     * @return the solution of x, null if number of iterations not enough
+     * @throws Exception if an error occurs
+     */
+    public double[] findArgmin(double[] initX, double[][] constraints) 
+	throws Exception{
+	int l = initX.length;
+	
+	// Initially all variables are free, all bounds are constraints of
+	// non-working-set constraints
+	boolean[] isFixed = new boolean[l];
+	double[][] nwsBounds = new double[2][l];
+	// Record indice of fixed variables, simply for efficiency
+	DynamicIntArray wsBdsIndx = new DynamicIntArray(constraints.length); 
+	// Vectors used to record the variable indices to be freed 	
+	DynamicIntArray toFree=null, oldToFree=null;	
+
+	// Initial value of obj. function, gradient and inverse of the Hessian
+	m_f = objectiveFunction(initX);
+	if(Double.isNaN(m_f))
+	    throw new Exception("Objective function value is NaN!");
+	
+	double sum=0;
+	double[] grad=evaluateGradient(initX), oldGrad, oldX,
+	    deltaGrad=new double[l], deltaX=new double[l],
+	    direct = new double[l], x = new double[l];
+	Matrix L = new Matrix(l, l);  // Lower triangle of Cholesky factor 
+	double[] D = new double[l];   // Diagonal of Cholesky factor
+	for(int i=0; i<l; i++){
+	    L.setRow(i, new double[l]);
+	    L.setElement(i,i,1.0);
+	    D[i] = 1.0;
+	    direct[i] = -grad[i];
+	    sum += grad[i]*grad[i];
+	    x[i] = initX[i];
+	    nwsBounds[0][i] = constraints[0][i];
+	    nwsBounds[1][i] = constraints[1][i];
+	    isFixed[i] = false;
+	}	
+	double stpmax = m_STPMX*Math.max(Math.sqrt(sum), l);
+	
+	iterates:
+	for(int step=0; step < m_MAXITS; step++){
+	    if (m_Debug)
+		System.err.println("\nIteration # " + step + ":");	    
+	    
+	    // Try at most one feasible newton step, i.e. 0<lamda<=alpha
+	    oldX = x;
+	    oldGrad = grad;
+	    
+	    // Also update grad
+	    if (m_Debug)
+		System.err.println("Line search ... ");
+	    m_IsZeroStep = false;
+	    x=lnsrch(x, grad, direct, stpmax, 
+		     isFixed, nwsBounds, wsBdsIndx);
+	    if (m_Debug)
+		System.err.println("Line search finished.");
+	    
+	    if(m_IsZeroStep){ // Zero step, simply delete rows/cols of D and L
+		for(int f=0; f<wsBdsIndx.size(); f++){
+		    int idx=wsBdsIndx.elementAt(f);
+		    L.setRow(idx, new double[l]);
+		    L.setColumn(idx, new double[l]);
+		    D[idx] = 0.0;
+		}		
+		grad = evaluateGradient(x);
+		step--;
+	    }
+	    else{
+		// Check converge on x
+		boolean finish = false;
+		double test=0.0;
+		for(int h=0; h<l; h++){
+		    deltaX[h] = x[h]-oldX[h];
+		    double tmp=Math.abs(deltaX[h])/
+			Math.max(Math.abs(x[h]), 1.0);
+		    if(tmp > test) test = tmp;				    
+		}
+		if(test < m_Zero){
+		    if (m_Debug)
+			System.err.println("\nDeltaX converge: "+test);
+		    finish = true;
+		}
+		
+		// Check zero gradient	    
+		grad = evaluateGradient(x);
+		test=0.0;
+		double denom=0.0, dxSq=0.0, dgSq=0.0, newlyBounded=0.0; 
+		for(int g=0; g<l; g++){
+		    if(!isFixed[g]){ 		   
+			deltaGrad[g] = grad[g] - oldGrad[g];		  
+			// Calculate the denominators			    
+			denom += deltaX[g]*deltaGrad[g];
+			dxSq += deltaX[g]*deltaX[g];
+			dgSq += deltaGrad[g]*deltaGrad[g];
+		    }
+		    else // Only newly bounded variables will be non-zero
+			newlyBounded +=  deltaX[g]*(grad[g]-oldGrad[g]);
+		    
+		    // Note: CANNOT use projected gradient for testing 
+		    // convergence because of newly bounded variables
+		    double tmp = Math.abs(grad[g])*
+			Math.max(Math.abs(direct[g]),1.0)/
+			Math.max(Math.abs(m_f),1.0);
+		    if(tmp > test) test = tmp;	
+		}
+		
+		if(test < m_Zero){
+		    if (m_Debug)
+			System.err.println("Gradient converge: "+test);
+		    finish = true;
+		}	    
+		
+		// dg'*dx could be < 0 using inexact lnsrch
+		if(m_Debug)
+		    System.err.println("dg'*dx="+(denom+newlyBounded));	
+		// dg'*dx = 0
+		if(Math.abs(denom+newlyBounded) < m_Zero)
+		    finish = true;
+		
+		int size = wsBdsIndx.size();
+		boolean isUpdate = true;  // Whether to update BFGS formula	    
+		// Converge: check whether release any current constraints
+		if(finish){
+		    if (m_Debug)
+			System.err.println("Test any release possible ...");
+		    	
+		    if(toFree != null)
+			oldToFree = (DynamicIntArray)toFree.copy();
+		    toFree = new DynamicIntArray(wsBdsIndx.size());
+		    
+		    for(int m=size-1; m>=0; m--){
+			int index=wsBdsIndx.elementAt(m);
+			double[] hessian = evaluateHessian(x, index);			
+			double deltaL=0.0;
+			if(hessian != null){
+			    for(int mm=0; mm<hessian.length; mm++)
+				if(!isFixed[mm]) // Free variable
+				    deltaL += hessian[mm]*direct[mm];
+			}
+			
+			// First and second order Lagrangian multiplier estimate
+			// If user didn't provide Hessian, use first-order only
+			double L1, L2;
+			if(x[index] >= constraints[1][index]) // Upper bound
+			    L1 = -grad[index];
+			else if(x[index] <= constraints[0][index])// Lower bound
+			    L1 = grad[index];
+			else
+			    throw new Exception("x["+index+"] not fixed on the"+
+						" bounds where it should have been!");
+			
+			// L2 = L1 + deltaL
+			L2 = L1 + deltaL;			
+			if (m_Debug)
+			    System.err.println("Variable "+index+
+					       ": Lagrangian="+L1+"|"+L2);
+			
+			//Check validity of Lagrangian multiplier estimate
+			boolean isConverge = 
+			    (2.0*Math.abs(deltaL)) < Math.min(Math.abs(L1),
+							      Math.abs(L2));  
+			if((L1*L2>0.0) && isConverge){ //Same sign and converge: valid
+			    if(L2 < 0.0){// Negative Lagrangian: feasible
+				toFree.addElement(index);
+				wsBdsIndx.removeElementAt(m);
+				finish=false; // Not optimal, cannot finish
+			    }
+			}
+			
+			// Although hardly happen, better check it
+			// If the first-order Lagrangian multiplier estimate is wrong,
+			// avoid zigzagging
+			if((hessian==null) && (toFree != null) && toFree.equal(oldToFree)) 
+			    finish = true;           
+		    }
+		    
+		    if(finish){// Min. found
+			if (m_Debug)
+			    System.err.println("Minimum found.");
+			m_f = objectiveFunction(x);
+			if(Double.isNaN(m_f))
+			    throw new Exception("Objective function value is NaN!");	
+			return x;
+		    }
+		    
+		    // Free some variables
+		    for(int mmm=0; mmm<toFree.size(); mmm++){
+			int freeIndx=toFree.elementAt(mmm);
+			isFixed[freeIndx] = false; // Free this variable
+			if(x[freeIndx] <= constraints[0][freeIndx]){// Lower bound
+			    nwsBounds[0][freeIndx] = constraints[0][freeIndx];
+			    if (m_Debug)
+				System.err.println("Free variable "+freeIndx+
+						   " from bound "+ 
+						   nwsBounds[0][freeIndx]);
+			}
+			else{ // Upper bound
+			    nwsBounds[1][freeIndx] = constraints[1][freeIndx];
+			    if (m_Debug)
+				System.err.println("Free variable "+freeIndx+
+						   " from bound "+ 
+						   nwsBounds[1][freeIndx]);
+			}			
+			L.setElement(freeIndx, freeIndx, 1.0);
+			D[freeIndx] = 1.0;
+			isUpdate = false;			
+		    }			
+		}
+		
+		if(denom<Math.max(m_Zero*Math.sqrt(dxSq)*Math.sqrt(dgSq), m_Zero)){
+		    if (m_Debug) 
+			System.err.println("dg'*dx negative!");
+		    isUpdate = false; // Do not update		    
+		}		
+		// If Hessian will be positive definite, update it
+		if(isUpdate){
+		    
+		    // modify once: dg*dg'/(dg'*dx)	
+		    double coeff = 1.0/denom; // 1/(dg'*dx)	
+		    updateCholeskyFactor(L,D,deltaGrad,coeff,isFixed);
+		    
+		    // modify twice: g*g'/(g'*p)	
+		    coeff = 1.0/m_Slope; // 1/(g'*p)
+		    updateCholeskyFactor(L,D,oldGrad,coeff,isFixed);  		    
+		}
+	    }
+	    
+	    // Find new direction 
+	    Matrix LD = new Matrix(l,l); // L*D
+	    double[] b = new double[l];
+	    
+	    for(int k=0; k<l; k++){
+		if(!isFixed[k])  b[k] = -grad[k];
+		else             b[k] = 0.0;
+		
+		for(int j=k; j<l; j++){ // Lower triangle	
+		    if(!isFixed[j] && !isFixed[k])
+			LD.setElement(j, k, L.getElement(j,k)*D[k]);
+		}		
+	    }	    	
+	    
+	    // Solve (LD)*y = -g, where y=L'*direct
+	    double[] LDIR = solveTriangle(LD, b, true, isFixed);	    
+	    LD = null;
+	    
+	    for(int m=0; m<LDIR.length; m++){
+		if(Double.isNaN(LDIR[m]))
+		    throw new Exception("L*direct["+m+"] is NaN!"
+					+"|-g="+b[m]+"|"+isFixed[m]
+					+"|diag="+D[m]);
+	    }
+	    
+	    // Solve L'*direct = y
+	    direct = solveTriangle(L, LDIR, false, isFixed);
+	    for(int m=0; m<direct.length; m++){
+		if(Double.isNaN(direct[m]))
+		    throw new Exception("direct is NaN!");
+	    }
+	    
+	    //System.gc();
+	}
+	
+	if(m_Debug)
+	    System.err.println("Cannot find minimum"+
+			       " -- too many interations!");
+	m_X = x;
+	return null;
+    }
+    
+    /** 
+     * Solve the linear equation of TX=B where T is a triangle matrix
+     * It can be solved using back/forward substitution, with O(N^2) 
+     * complexity
+     * @param t the matrix T
+     * @param b the vector B
+     * @param isLower whether T is a lower or higher triangle matrix
+     * @param isZero which row(s) of T are not used when solving the equation. 
+     *               If it's null or all 'false', then every row is used.
+     * @return the solution of X
+     */     
+    public static double[] solveTriangle(Matrix t, double[] b, 
+					 boolean isLower, boolean[] isZero){
+	int n = b.length; 
+	double[] result = new double[n];
+	if(isZero == null)
+	    isZero = new boolean[n];
+	
+	if(isLower){ // lower triangle, forward-substitution
+	    int j = 0;
+	    while((j<n)&&isZero[j]){result[j]=0.0; j++;} // go to the first row
+	    
+	    if(j<n){
+		result[j] = b[j]/t.getElement(j,j);
+		
+		for(; j<n; j++){
+		    if(!isZero[j]){
+			double numerator=b[j];
+			for(int k=0; k<j; k++)
+			    numerator -= t.getElement(j,k)*result[k];
+			result[j] = numerator/t.getElement(j,j);
+		    }
+		    else 
+			result[j] = 0.0;
+		}
+	    }
+	}
+	else{ // Upper triangle, back-substitution
+	    int j=n-1;
+	    while((j>=0)&&isZero[j]){result[j]=0.0; j--;} // go to the last row
+	    
+	    if(j>=0){
+		result[j] = b[j]/t.getElement(j,j);
+		
+		for(; j>=0; j--){
+		    if(!isZero[j]){
+			double numerator=b[j];
+			for(int k=j+1; k<n; k++)
+			    numerator -= t.getElement(k,j)*result[k];
+			result[j] = numerator/t.getElement(j,j);
+		    }
+		    else 
+			result[j] = 0.0;
+		}
+	    }
+	}
+	
+	return result;
+    }
+
+    /**
+     * One rank update of the Cholesky factorization of B matrix in BFGS updates,
+     * i.e. B = LDL', and B_{new} = LDL' + coeff*(vv') where L is a unit lower triangle
+     * matrix and D is a diagonal matrix, and v is a vector.<br/>
+     * When coeff > 0, we use C1 algorithm, and otherwise we use C2 algorithm described
+     * in ``Methods for Modifying Matrix Factorizations'' 
+     *
+     * @param L the unit triangle matrix L
+     * @param D the diagonal matrix D
+     * @param v the update vector v
+     * @param coeff the coeffcient of update
+     * @param isFixed which variables are not to be updated
+     */    
+    protected void updateCholeskyFactor(Matrix L, double[] D, 
+					double[] v, double coeff,
+					boolean[] isFixed)
+	throws Exception{
+	double t, p, b;
+	int n = v.length;
+	double[] vp =  new double[n];	
+	for (int i=0; i<v.length; i++)
+	    if(!isFixed[i])
+		vp[i]=v[i];
+	    else
+		vp[i]=0.0;
+	
+	if(coeff>0.0){
+	    t = coeff;	    
+	    for(int j=0; j<n; j++){		
+		if(isFixed[j]) continue;		
+		
+		p = vp[j];
+		double d=D[j], dbarj=d+t*p*p;
+		D[j] = dbarj;
+		
+		b = p*t/dbarj;
+		t *= d/dbarj;
+		for(int r=j+1; r<n; r++){
+		    if(!isFixed[r]){
+			double l=L.getElement(r, j);
+			vp[r] -= p*l;
+			L.setElement(r, j, l+b*vp[r]);
+		    }
+		    else
+		    	L.setElement(r, j, 0.0);
+		}
+	    }
+	}
+	else{
+	    double[] P = solveTriangle(L, v, true, isFixed);	    
+	    t = 0.0;
+	    for(int i=0; i<n; i++)
+		if(!isFixed[i])
+		    t += P[i]*P[i]/D[i];	    	
+	    
+	    double sqrt=1.0+coeff*t;
+	    sqrt = (sqrt<0.0)? 0.0 : Math.sqrt(sqrt);
+	    
+	    double alpha=coeff, sigma=coeff/(1.0+sqrt), rho, theta;
+	    
+	    for(int j=0; j<n; j++){
+		if(isFixed[j]) continue;
+		
+		double d=D[j];
+		p = P[j]*P[j]/d;
+		theta = 1.0+sigma*p;
+		t -= p; 
+		if(t<0.0) t=0.0; // Rounding error
+
+		double plus = sigma*sigma*p*t;
+		if((j<n-1) && (plus <= m_Zero)) 
+		    plus=m_Zero; // Avoid rounding error
+		rho = theta*theta + plus;		
+		D[j] = rho*d;
+		
+		if(Double.isNaN(D[j])){
+		    throw new Exception("d["+j+"] NaN! P="+P[j]+",d="+d+
+					",t="+t+",p="+p+",sigma="+sigma+
+					",sclar="+coeff);
+		}
+		
+		b=alpha*P[j]/(rho*d);
+		alpha /= rho;
+		rho = Math.sqrt(rho);
+		double sigmaOld = sigma;
+		sigma *= (1.0+rho)/(rho*(theta+rho));	 
+		if((j<n-1) && 
+		   (Double.isNaN(sigma) || Double.isInfinite(sigma)))
+		    throw new Exception("sigma NaN/Inf! rho="+rho+
+				       ",theta="+theta+",P["+j+"]="+
+				       P[j]+",p="+p+",d="+d+",t="+t+
+				       ",oldsigma="+sigmaOld);
+		
+		for(int r=j+1; r<n; r++){
+		    if(!isFixed[r]){
+			double l=L.getElement(r, j);
+			vp[r] -= P[j]*l;
+			L.setElement(r, j, l+b*vp[r]);
+		    }
+		    else
+		    	L.setElement(r, j, 0.0);
+		}
+	    }
+	}	
+    }
+
+  /**
+   * Implements a simple dynamic array for ints.
+   */
+  private class DynamicIntArray
+    implements RevisionHandler {
+
+    /** The int array. */
+    private int[] m_Objects;
+
+    /** The current size; */
+    private int m_Size = 0;
+
+    /** The capacity increment */
+    private int m_CapacityIncrement = 1;
+  
+    /** The capacity multiplier. */
+    private int m_CapacityMultiplier = 2;
+
+    /**
+     * Constructs a vector with the given capacity.
+     *
+     * @param capacity the vector's initial capacity
+     */
+    public DynamicIntArray(int capacity) {
+      
+      m_Objects = new int[capacity];
+    }
+
+    /**
+     * Adds an element to this vector. Increases its
+     * capacity if its not large enough.
+     *
+     * @param element the element to add
+     */
+    public final void addElement(int element) {
+      
+      if (m_Size == m_Objects.length) {
+	int[] newObjects;
+	newObjects = new int[m_CapacityMultiplier *
+			     (m_Objects.length +
+			      m_CapacityIncrement)];
+	System.arraycopy(m_Objects, 0, newObjects, 0, m_Size);
+	m_Objects = newObjects;
+      }
+      m_Objects[m_Size] = element;
+      m_Size++;
+    }
+
+    /**
+     * Produces a copy of this vector.
+     *
+     * @return the new vector
+     */
+    public final Object copy() {
+      
+      
+      DynamicIntArray copy = new DynamicIntArray(m_Objects.length);
+      
+      copy.m_Size = m_Size;
+      copy.m_CapacityIncrement = m_CapacityIncrement;
+      copy.m_CapacityMultiplier = m_CapacityMultiplier;
+      System.arraycopy(m_Objects, 0, copy.m_Objects, 0, m_Size);
+      return copy;
+    }
+
+    /**
+     * Returns the element at the given position.
+     *
+     * @param index the element's index
+     * @return the element with the given index
+     */
+    public final int elementAt(int index) {
+      
+      return m_Objects[index];
+    }
+    
+    /**
+     * Check whether the two integer vectors equal to each other
+     * Two integer vectors are equal if all the elements are the 
+     * same, regardless of the order of the elements
+     *
+     * @param b another integer vector
+     * @return whether they are equal
+     */ 
+    private boolean equal(DynamicIntArray b){
+      if((b==null) || (size()!=b.size()))
+	  return false;
+      
+      int size=size();
+      
+      // Only values matter, order does not matter
+      int[] sorta=Utils.sort(m_Objects), sortb=Utils.sort(b.m_Objects);
+      for(int j=0; j<size;j++)
+	if(m_Objects[sorta[j]] != b.m_Objects[sortb[j]])
+	  return false;
+      
+      return true;
+    }
+
+    /**
+     * Deletes an element from this vector.
+     *
+     * @param index the index of the element to be deleted
+     */
+    public final void removeElementAt(int index) {
+      
+      System.arraycopy(m_Objects, index + 1, m_Objects, index, 
+		       m_Size - index - 1);
+      m_Size--;
+    }
+
+    /**
+     * Removes all components from this vector and sets its 
+     * size to zero. 
+     */
+    public final void removeAllElements() {
+      
+      m_Objects = new int[m_Objects.length];
+      m_Size = 0;
+    }
+
+    /**
+     * Returns the vector's current size.
+     *
+     * @return the vector's current size
+     */
+    public final int size() {
+      
+      return m_Size;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Option.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Option.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Option.java	(revision 29)
@@ -0,0 +1,115 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Option.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Class to store information about an option. <p>
+ *
+ * Typical usage: <p>
+ *
+ * <code>Option myOption = new Option("Uses extended mode.", "E", 0, "-E")); </code><p>
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class Option
+  implements RevisionHandler {
+
+  /** What does this option do? */
+  private String m_Description;
+
+  /** The synopsis. */
+  private String m_Synopsis;
+
+  /** What's the option's name? */
+  private String m_Name;
+
+  /** How many arguments does it take? */
+  private int m_NumArguments;
+
+  /**
+   * Creates new option with the given parameters.
+   *
+   * @param description the option's description
+   * @param name the option's name
+   * @param numArguments the number of arguments
+   */
+  public Option(String description, String name, 
+		int numArguments, String synopsis) {
+  
+    m_Description = description;
+    m_Name = name;
+    m_NumArguments = numArguments;
+    m_Synopsis = synopsis;
+  }
+
+  /**
+   * Returns the option's description.
+   *
+   * @return the option's description
+   */
+  public String description() {
+  
+    return m_Description;
+  }
+
+  /**
+   * Returns the option's name.
+   *
+   * @return the option's name
+   */
+  public String name() {
+
+    return m_Name;
+  }
+
+  /**
+   * Returns the option's number of arguments.
+   *
+   * @return the option's number of arguments
+   */
+  public int numArguments() {
+  
+    return m_NumArguments;
+  }
+
+  /**
+   * Returns the option's synopsis.
+   *
+   * @return the option's synopsis
+   */
+  public String synopsis() {
+  
+    return m_Synopsis;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/OptionHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/OptionHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/OptionHandler.java	(revision 29)
@@ -0,0 +1,71 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OptionHandler.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.*;
+
+/** 
+ * Interface to something that understands options.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface OptionHandler {
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return an enumeration of all available options.
+   */
+  Enumeration listOptions();
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  //@ requires options != null;
+  //@ requires \nonnullelements(options);
+  void setOptions(String[] options) throws Exception;
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  //@ ensures \result != null;
+  //@ ensures \nonnullelements(\result);
+  /*@pure@*/ String[] getOptions();
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/OptionHandlerJavadoc.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/OptionHandlerJavadoc.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/OptionHandlerJavadoc.java	(revision 29)
@@ -0,0 +1,222 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * OptionHandlerJavadoc.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Generates Javadoc comments from the OptionHandler's options. Can 
+ * automatically update the option comments if they're surrounded by
+ * the OPTIONS_STARTTAG and OPTIONS_ENDTAG (the indention is determined via
+ * the OPTIONS_STARTTAG). <p/>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The class to load.</pre>
+ * 
+ * <pre> -nostars
+ *  Suppresses the '*' in the Javadoc.</pre>
+ * 
+ * <pre> -dir &lt;dir&gt;
+ *  The directory above the package hierarchy of the class.</pre>
+ * 
+ * <pre> -silent
+ *  Suppresses printing in the console.</pre>
+ * 
+ * <pre> -noprolog
+ *  Suppresses the 'Valid options are...' prolog in the Javadoc.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see #OPTIONS_STARTTAG
+ * @see #OPTIONS_ENDTAG
+ */
+public class OptionHandlerJavadoc 
+  extends Javadoc {
+  
+  /** the start comment tag for inserting the generated Javadoc */
+  public final static String OPTIONS_STARTTAG = "<!-- options-start -->";
+  
+  /** the end comment tag for inserting the generated Javadoc */
+  public final static String OPTIONS_ENDTAG = "<!-- options-end -->";
+  
+  /** whether to include the "Valid options..." prolog in the Javadoc */
+  protected boolean m_Prolog = true;
+  
+  /**
+   * default constructor 
+   */
+  public OptionHandlerJavadoc() {
+    super();
+    
+    m_StartTag    = new String[1];
+    m_EndTag      = new String[1];
+    m_StartTag[0] = OPTIONS_STARTTAG;
+    m_EndTag[0]   = OPTIONS_ENDTAG;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>        result;
+    Enumeration   en;
+    
+    result = new Vector<Option>();
+    
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+
+    result.addElement(new Option(
+        "\tSuppresses the 'Valid options are...' prolog in the Javadoc.",
+        "noprolog", 0, "-noprolog"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    setProlog(!Utils.getFlag("noprolog", options));
+  }
+  
+  /**
+   * Gets the current settings of this object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (!getProlog())
+      result.add("-noprolog");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets whether to add the "Valid options are..." prolog
+   * 
+   * @param value	true if the prolog is to be added
+   */
+  public void setProlog(boolean value) {
+    m_Prolog = value;
+  }
+  
+  /**
+   * whether "Valid options are..." prolog is included in the Javadoc
+   * 
+   * @return		true if the prolog is printed
+   */
+  public boolean getProlog() {
+    return m_Prolog;
+  }
+  
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair.
+   * 
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String generateJavadoc(int index) throws Exception {
+    String		result;
+    OptionHandler	handler;
+    String		optionStr;
+    
+    result = "";
+    
+    if (index == 0) {
+      if (!canInstantiateClass())
+	return result;
+	    
+      if (!ClassDiscovery.hasInterface(OptionHandler.class, getInstance().getClass()))
+	throw new Exception("Class '" + getClassname() + "' is not an OptionHandler!");
+      
+      // any options at all?
+      handler = (OptionHandler) getInstance();
+      Enumeration enm = handler.listOptions();
+      if (!enm.hasMoreElements())
+	return result;
+      
+      // prolog?
+      if (getProlog())
+	result = "Valid options are: <p/>\n\n";
+      
+      // options
+      enm = handler.listOptions();
+      while (enm.hasMoreElements()) {
+	Option option = (Option) enm.nextElement();
+	optionStr =   toHTML(option.synopsis()) 
+                    + "\n" 
+                    + toHTML(option.description().replaceAll("\\t", " "));
+	result += "<pre> " + optionStr.replaceAll("<br/>", "") + "</pre>\n\n";
+      }
+      
+      // stars?
+      if (getUseStars()) 
+	result = indent(result, 1, "* ");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Parses the given commandline parameters and generates the Javadoc.
+   * 
+   * @param args	the commandline parameters for the object
+   */
+  public static void main(String[] args) {
+    runJavadoc(new OptionHandlerJavadoc(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/PropertyPath.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/PropertyPath.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/PropertyPath.java	(revision 29)
@@ -0,0 +1,600 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PropertyPath.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * A helper class for accessing properties in nested objects, e.g., accessing
+ * the "getRidge" method of a LinearRegression classifier part of 
+ * MultipleClassifierCombiner, e.g., Vote. For doing so, one needs to 
+ * supply the object to work on and a property path. The property path is a
+ * dot delimited path of property names ("getFoo()" and "setFoo(int)" have 
+ * "foo" as property name), indices of arrays are 0-based. E.g.: <p/>
+ * 
+ * <code>getPropertyDescriptor(vote, "classifiers[1].ridge")</code> will return
+ * the second classifier (which should be our LinearRegression) of the given
+ * Vote meta-classifier and there the property descriptor of the "ridge" 
+ * property. <code>getValue(...)</code> will return the actual value of the
+ * ridge parameter and <code>setValue(...)</code> will set it.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class PropertyPath
+  implements RevisionHandler {
+
+  /**
+   * Represents a single element of a property path
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class PathElement
+    implements Cloneable, RevisionHandler {
+    
+    /** the property */
+    protected String m_Name;
+    
+    /** the index of the array (-1 for none) */
+    protected int m_Index;
+    
+    /**
+     * initializes the path element with the given property
+     * 
+     * @param property	the property to initialize with
+     */
+    public PathElement(String property) {
+      super();
+      
+      if (property.indexOf("[") > -1) {
+	m_Name  = property.replaceAll("\\[.*$", "");
+	m_Index = Integer.parseInt(
+    		     property.replaceAll(".*\\[", "").replaceAll("\\].*", ""));
+      }
+      else {
+	m_Name   = property;
+	m_Index = -1;
+      }
+    }
+
+    /**
+     * returns a clone of the current object
+     * 
+     * @return		the clone of the current state
+     */
+    public Object clone() {
+      return new PathElement(this.toString());
+    }
+    
+    /**
+     * returns the name of the property
+     * 
+     * @return		the name of the property
+     */
+    public String getName() {
+      return m_Name;
+    }
+    
+    /**
+     * returns whether the property is an index-based one
+     * 
+     * @return		true if the property has an index
+     */
+    public boolean hasIndex() {
+      return (getIndex() > -1);
+    }
+    
+    /**
+     * returns the index of the property, -1 if the property is not an
+     * index-based one
+     * 
+     * @return		the index of the property
+     */
+    public int getIndex() {
+      return m_Index;
+    }
+    
+    /**
+     * returns the element once again as string
+     * 
+     * @return		the property as string
+     */
+    public String toString() {
+      String	result;
+      
+      result = getName();
+      if (hasIndex())
+	result += "[" + getIndex() + "]";
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * Contains a (property) path structure
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class Path
+    implements RevisionHandler {
+    
+    /** the structure */
+    protected Vector<PathElement> m_Elements;
+    
+    /**
+     * default constructor, only used internally
+     */
+    protected Path() {
+      super();
+      
+      m_Elements = new Vector<PathElement>();
+    }
+    
+    /**
+     * uses the given dot-path
+     * 
+     * @param path	path in dot-notation
+     */
+    public Path(String path) {
+      this();
+      
+      m_Elements = breakUp(path);
+    }
+    
+    /**
+     * uses the vector with PathElement objects to initialize with
+     * 
+     * @param elements	the PathElements to use
+     */
+    public Path(Vector<PathElement> elements) {
+      this();
+      
+      for (int i = 0; i < elements.size(); i++)
+	m_Elements.add((PathElement) elements.get(i).clone());
+    }
+    
+    /**
+     * uses the given array as elements for the path
+     * 
+     * @param elements	the path elements to use
+     */
+    public Path(String[] elements) {
+      this();
+      
+      for (int i = 0; i < elements.length; i++)
+	m_Elements.add(new PathElement(elements[i]));
+    }
+    
+    /**
+     * breaks up the given path and returns it as vector
+     * 
+     * @param path	the path to break up
+     * @return		the single elements of the path
+     */
+    protected Vector<PathElement> breakUp(String path) {
+      Vector<PathElement>		result;
+      StringTokenizer	tok;
+      
+      result = new Vector<PathElement>();
+      
+      tok = new StringTokenizer(path, ".");
+      while (tok.hasMoreTokens())
+        result.add(new PathElement(tok.nextToken()));
+      
+      return result;
+    }
+
+    /**
+     * returns the element at the given index
+     * 
+     * @param index	the index of the element to return
+     * @return		the specified element
+     */
+    public PathElement get(int index) {
+      return (PathElement) m_Elements.get(index);
+    }
+
+    /**
+     * returns the number of path elements of this structure
+     * 
+     * @return		the number of path elements
+     */
+    public int size() {
+      return m_Elements.size();
+    }
+    
+    /**
+     * returns a path object based on the given path string
+     * 
+     * @param path	path to work on
+     * @return		the path structure
+     */
+    public static Path parsePath(String path) {
+      return new Path(path);
+    }
+
+    /**
+     * returns a subpath of the current structure, starting with the specified
+     * element index up to the end
+     * 
+     * @param startIndex	the first element of the subpath
+     * @return			the new subpath
+     */
+    public Path subpath(int startIndex) {
+      return subpath(startIndex, size());
+    }
+
+    /**
+     * returns a subpath of the current structure, starting with the specified
+     * element index up. The endIndex specifies the element that is not part
+     * of the new subpath. In other words, the new path contains the elements
+     * from "startIndex" up to "(endIndex-1)".
+     * 
+     * @param startIndex	the first element of the subpath
+     * @param endIndex		the element that is after the last added element
+     * @return			the new subpath
+     */
+    public Path subpath(int startIndex, int endIndex) {
+      Vector<PathElement>	list;
+      int	i;
+      
+      list = new Vector<PathElement>();
+      for (i = startIndex; i < endIndex; i++)
+	list.add(get(i));
+      
+      return new Path(list);
+    }
+    
+    /**
+     * returns the structure again as a dot-path
+     * 
+     * @return		the path structure as dot-path
+     */
+    public String toString() {
+      String	result;
+      int	i;
+      
+      result = "";
+      
+      for (i = 0; i < m_Elements.size(); i++) {
+	if (i > 0)
+	  result += ".";
+	result += m_Elements.get(i);
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+
+  /**
+   * A helper class that stores Object and PropertyDescriptor together.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  protected static class PropertyContainer
+    implements RevisionHandler {
+    
+    /** the descriptor */
+    protected PropertyDescriptor m_Descriptor;
+    
+    /** the associated object */
+    protected Object m_Object;
+    
+    /**
+     * initializes the container
+     * 
+     * @param desc	the property descriptor
+     * @param obj	the associated object
+     */
+    public PropertyContainer(PropertyDescriptor desc, Object obj) {
+      super();
+      
+      m_Descriptor = desc;
+      m_Object     = obj;
+    }
+    
+    /**
+     * returns the stored descriptor
+     * 
+     * @return		the stored descriptor
+     */
+    public PropertyDescriptor getDescriptor() {
+      return m_Descriptor;
+    }
+    
+    /**
+     * returns the stored object
+     * 
+     * @return		the stored object
+     */
+    public Object getObject() {
+      return m_Object;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * returns the property and object associated with the given path, null if 
+   * a problem occurred.
+   * 
+   * @param src		the object to start from
+   * @param path	the path to follow
+   * @return		not null, if the property could be found
+   */
+  public static PropertyContainer find(Object src, Path path) {
+    PropertyContainer	result;
+    PropertyDescriptor	desc;
+    Object		newSrc;
+    PathElement		part;
+    Method		method;
+    Object		methodResult;
+
+    // get descriptor
+    part = path.get(0);
+    try {
+      desc = new PropertyDescriptor(part.getName(), src.getClass());
+    }
+    catch (Exception e) {
+      desc = null;
+      e.printStackTrace();
+    }
+
+    // problem occurred? -> stop
+    if (desc == null)
+      return null;
+    
+    // end of path reached?
+    if (path.size() == 1) {
+      result = new PropertyContainer(desc, src);
+    }
+    // recurse further
+    else {
+      try {
+	method       = desc.getReadMethod();
+	methodResult = method.invoke(src, (Object[]) null);
+	if (part.hasIndex())
+	  newSrc = Array.get(methodResult, part.getIndex());
+	else
+	  newSrc = methodResult;
+	result = find(newSrc, path.subpath(1));
+      }
+      catch (Exception e) {
+	result = null;
+	e.printStackTrace();
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the property associated with the given path, null if a problem
+   * occurred.
+   * 
+   * @param src		the object to start from
+   * @param path	the path to follow
+   * @return		not null, if the property could be found
+   */
+  public static PropertyDescriptor getPropertyDescriptor(Object src, Path path) {
+    PropertyContainer	cont;
+    
+    cont = find(src, path);
+    
+    if (cont == null)
+      return null;
+    else
+      return cont.getDescriptor();
+  }
+  
+  /**
+   * returns the property associated with the given path
+   * 
+   * @param src		the object to start from
+   * @param path	the path to follow
+   * @return		not null, if the property could be found
+   */
+  public static PropertyDescriptor getPropertyDescriptor(Object src, String path) {
+    return getPropertyDescriptor(src, new Path(path));
+  }
+  
+  /**
+   * returns the value specified by the given path from the object
+   * 
+   * @param src		the object to work on
+   * @param path	the retrieval path
+   * @return		the value, null if an error occurred
+   */
+  public static Object getValue(Object src, Path path) {
+    Object		result;
+    PropertyContainer	cont;
+    Method		method;
+    Object		methodResult;
+    PathElement		part;
+    
+    result = null;
+    
+    cont = find(src, path);
+    // problem?
+    if (cont == null)
+      return null;
+    
+    // retrieve the value
+    try {
+      part         = path.get(path.size() - 1);
+      method       = cont.getDescriptor().getReadMethod();
+      methodResult = method.invoke(cont.getObject(), (Object[]) null);
+      if (part.hasIndex())
+	result = Array.get(methodResult, part.getIndex());
+      else
+	result = methodResult;
+    }
+    catch (Exception e) {
+      result = null;
+      e.printStackTrace();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the value specified by the given path from the object
+   * 
+   * @param src		the object to work on
+   * @param path	the retrieval path
+   * @return		the value, null if an error occurred
+   */
+  public static Object getValue(Object src, String path) {
+    return getValue(src, new Path(path));
+  }
+  
+  /**
+   * set the given value specified by the given path in the object
+   * 
+   * @param src		the object to work on
+   * @param path	the retrieval path
+   * @param value	the value to set
+   * @return		true if the value could be set
+   */
+  public static boolean setValue(Object src, Path path, Object value) {
+    boolean		result;
+    PropertyContainer	cont;
+    Method		methodRead;
+    Method		methodWrite;
+    Object		methodResult;
+    PathElement		part;
+    
+    result = false;
+    
+    cont = find(src, path);
+    // problem?
+    if (cont == null)
+      return result;
+    
+    // set the value
+    try {
+      part         = path.get(path.size() - 1);
+      methodRead   = cont.getDescriptor().getReadMethod();
+      methodWrite  = cont.getDescriptor().getWriteMethod();
+      if (part.hasIndex()) {
+	methodResult = methodRead.invoke(cont.getObject(), (Object[]) null);
+	Array.set(methodResult, part.getIndex(), value);
+	methodWrite.invoke(cont.getObject(), new Object[]{methodResult});
+      }
+      else {
+	methodWrite.invoke(cont.getObject(), new Object[]{value});
+      }
+      result = true;
+    }
+    catch (Exception e) {
+      result = false;
+      e.printStackTrace();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * set the given value specified by the given path in the object
+   * 
+   * @param src		the object to work on
+   * @param path	the retrieval path
+   * @param value	the value to set
+   */
+  public static void setValue(Object src, String path, Object value) {
+    setValue(src, new Path(path), value);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * for testing only
+   * 
+   * @param args	the commandline options - ignored
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    // Path
+    Path path = new Path("hello.world[2].nothing");
+    System.out.println("Path: " + path);
+    System.out.println(" -size: " + path.size());
+    System.out.println(" -elements:");
+    for (int i = 0; i < path.size(); i++)
+      System.out.println(
+	  "  " + i + ". " + path.get(i).getName() 
+	  + " -> " + path.get(i).getIndex());
+    
+    /*
+    // retrieving ridge with path
+    weka.classifiers.meta.Vote vote = new weka.classifiers.meta.Vote();
+    vote.setClassifiers(
+	new weka.classifiers.Classifier[]{
+	    new weka.classifiers.trees.J48(),
+	    new weka.classifiers.functions.LinearRegression()});
+    path = new Path("classifiers[1].ridge");
+    System.out.println("path: " + path + " -> " + getValue(vote, path));
+    
+    // setting ridge with path and retrieving it again
+    setValue(vote, path.toString(), new Double(0.1));
+    System.out.println("path: " + path + " -> " + getValue(vote, path));
+    */
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/ProtectedProperties.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/ProtectedProperties.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/ProtectedProperties.java	(revision 29)
@@ -0,0 +1,148 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ProtectedProperties.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Simple class that extends the Properties class so that the properties are
+ * unable to be modified.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class ProtectedProperties
+  extends Properties
+  implements RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3876658672657323985L;
+
+  /** the properties need to be open during construction of the object */
+  private boolean closed = false;
+
+  /**
+   * Creates a set of protected properties from a set of normal ones.
+   *
+   * @param props the properties to be stored and protected.
+   */
+  public ProtectedProperties(Properties props)
+  {
+
+    Enumeration propEnum = props.propertyNames();
+    while (propEnum.hasMoreElements()) {
+      String propName = (String) propEnum.nextElement();
+      String propValue = props.getProperty(propName);
+      super.setProperty(propName, propValue);
+    }
+    closed = true; // no modifications allowed from now on
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @return never returns without throwing an exception.
+   * @throws UnsupportedOperationException always.
+   */
+  public Object setProperty(String key, String value)
+    {
+    
+    if (closed) 
+      throw new
+	UnsupportedOperationException("ProtectedProperties cannot be modified!");
+    else return super.setProperty(key, value);
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @throws UnsupportedOperationException always.
+   */  
+  public void load(InputStream inStream) {
+    
+    throw new
+      UnsupportedOperationException("ProtectedProperties cannot be modified!");
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @throws UnsupportedOperationException always.
+   */
+  public void clear() {
+    
+    throw new
+      UnsupportedOperationException("ProtectedProperties cannot be modified!");
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @return never returns without throwing an exception.
+   * @throws UnsupportedOperationException always.
+   */
+  public Object put(Object key,
+		    Object value) {
+
+    if (closed) 
+      throw new
+	UnsupportedOperationException("ProtectedProperties cannot be modified!");
+    else return super.put(key, value);
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @throws UnsupportedOperationException always.
+   */
+  public void putAll(Map t) {
+    
+    throw new
+      UnsupportedOperationException("ProtectedProperties cannot be modified!");
+  }
+
+  /**
+   * Overrides a method to prevent the properties from being modified.
+   *
+   * @return never returns without throwing an exception.
+   * @throws UnsupportedOperationException always.
+   */
+  public Object remove(Object key) {
+
+    throw new
+      UnsupportedOperationException("ProtectedProperties cannot be modified!");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/Queue.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Queue.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Queue.java	(revision 29)
@@ -0,0 +1,314 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Queue.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ *    Modified March-May 2004 by Mark Utting to add JML specs
+ *    (this was done as the example solution of an assignment for a
+ *     software engineering course, so the specifications are more precise
+ *     and complete than one would normally do).
+ *    Passed a static analysis using ESC/Java-2.0a6 with no warnings.
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+
+/** 
+ * Class representing a FIFO queue.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class Queue
+  extends Object
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1141282001146389780L;
+
+  /**
+   * Represents one node in the queue.
+   */
+  protected class QueueNode
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -5119358279412097455L;
+
+    /** The next node in the queue */
+    protected /*@ spec_public @*/ QueueNode m_Next;
+
+    /** The nodes contents
+     */
+    protected /*@ non_null spec_public @*/ Object m_Contents;
+
+    /** 
+     * Creates a queue node with the given contents 
+     */
+    //@ requires contents != null;
+    //@ assignable m_Contents, m_Next;
+    //@ ensures m_Contents == contents;
+    //@ ensures m_Next == null;
+    public QueueNode(Object contents) {
+      m_Contents = contents;
+      next(null);
+    }
+
+    /**
+     * Sets the next node in the queue, and returns it.
+     */
+    //@ requires next != this ;
+    //@ assignable m_Next;
+    //@ ensures m_Next==next && \result==next;
+    public QueueNode next(QueueNode next) {
+      return m_Next = next;
+    } //@ nowarn Invariant; // Because it stupidly checks the Queue invariant!
+
+    /**
+     * Gets the next node in the queue. 
+     */
+    //@ ensures \result == m_Next;
+    public /*@ pure @*/ QueueNode next() {
+      return m_Next;
+    }
+
+    /**
+     * Sets the contents of the node.
+     */
+    //@ requires contents != null;
+    //@ assignable m_Contents;
+    //@ ensures  m_Contents == contents && \result == contents;
+    public Object contents(Object contents) {
+      return m_Contents = contents;
+    }
+
+    /**
+     * Returns the contents in the node.
+     */
+      //@ ensures \result == m_Contents;
+    public /*@ pure @*/ Object contents() {
+      return m_Contents;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+
+  /** Store a reference to the head of the queue */
+  protected /*@ spec_public @*/ QueueNode m_Head = null;
+
+  /** Store a reference to the tail of the queue */
+  protected /*@ spec_public @*/ QueueNode m_Tail = null;
+
+  /** Store the c m_Tail.m_Nexturrent number of elements in the queue */
+  protected /*@ spec_public @*/ int m_Size = 0;
+
+  //@ public invariant m_Head == null <==> m_Tail == null;
+  //@public invariant m_Tail != null ==> m_Tail.m_Next == null;
+  //@ public invariant m_Size >= 0;
+  //@ public invariant m_Size == 0 <==> m_Head == null;
+  //@ public invariant m_Size == 1 <==> m_Head != null && m_Head == m_Tail;
+  //@ public invariant m_Size > 1 ==> m_Head != m_Tail;
+  //@ public invariant m_Size > 1 <== m_Head != m_Tail;
+
+
+
+  /**
+   * Removes all objects from the queue m_Tail.m_Next.
+   */
+  //@ assignable m_Size, m_Head, m_Tail;
+  //@ ensures m_Size == 0;
+  //@ ensures m_Head == null;
+  //@ ensures m_Tail == null;
+  public final synchronized void removeAllElements() {
+    m_Size = 0;
+    m_Head = null;
+    m_Tail = null;
+  }
+
+  /**
+   * Appends an object to the back of the queue.
+   *
+   * @param item the object to be appended
+   * @return the object appended
+   */
+  //@ requires item != null;
+  //@ assignable m_Head, m_Tail, m_Tail.m_Next, m_Head.m_Next, m_Size;
+  //@ ensures m_Head != null;
+  //@ ensures m_Tail != \old(m_Tail);
+  //@ ensures m_Size == \old(m_Size) + 1;
+  //@ ensures \old(m_Size) == 0 ==> m_Head == m_Tail; 
+  //@ ensures \old(m_Size) != 0 ==> m_Head == \old(m_Head);
+  //@ ensures m_Tail.contents() == \old(item);
+  //@ ensures \result == item;
+  public synchronized Object push(Object item) {
+    QueueNode newNode = new QueueNode(item);
+    
+    if (m_Head == null) {
+      m_Head = m_Tail = newNode;
+    } else {
+      m_Tail = m_Tail.next(newNode);
+    }
+    m_Size++;
+    return item;
+  }
+
+  /**
+   * Pops an object from the front of the queue.
+   *
+   * @return the object at the front of the queue
+   * @exception RuntimeException if the queue is empty
+   */
+  //@ assignable m_Head, m_Tail, m_Size;
+  //@ ensures m_Size == \old(m_Size) - 1;
+  //@ ensures m_Head == \old(m_Head.m_Next);
+  //@ ensures m_Head != null ==> m_Tail == \old(m_Tail);
+  //@ ensures \result == \old(m_Head.m_Contents);
+  //@ signals (RuntimeException) \old(m_Head) == null;
+  public synchronized Object pop() 
+      throws RuntimeException   // REDUNDANT, BUT ESCJAVA REQUIRES THIS
+  {
+    if (m_Head == null) {
+	throw new RuntimeException("Queue is empty");
+    }
+    Object retval = m_Head.contents();
+    m_Size--;
+    m_Head = m_Head.next();
+    // Here we need to either tell ESC/Java some facts about
+    // the contents of the list after popping off the head,
+    // or turn off the 'invariant' warnings.
+    //
+    //@ assume m_Size == 0 <==> m_Head == null;
+    //@ assume m_Size == 1 <==> m_Head == m_Tail;
+    if (m_Head == null) {
+      m_Tail = null;
+    }
+    return retval;
+  }
+
+  /**
+   * Gets object from the front of the queue.
+   *
+   * @return the object at the front of the queue
+   * @exception RuntimeException if the queue is empty
+   */
+  //@ ensures \result == \old(m_Head.m_Contents);
+  //@ signals (RuntimeException) \old(m_Head) == null;
+  public /*@ pure @*/ synchronized Object peek() 
+    throws RuntimeException
+  { 
+    if (m_Head == null) {
+      throw new RuntimeException("Queue is empty");
+    }
+    return m_Head.contents();
+  }
+
+  /**
+   * Checks if queue is empty.
+   * 
+   * @return true if queue is empty
+   */
+  //@ ensures \result <==> m_Head == null;
+  public /*@ pure @*/ boolean empty() {
+    return m_Head == null;
+  }
+
+  /**
+   * Gets queue's size.
+   *
+   * @return size of queue
+   */
+  //@ ensures \result == m_Size;
+  public /*@ pure @*/ int size() {
+    return m_Size;
+  }
+
+  /**
+   * Produces textual description of queue.
+   *
+   * @return textual description of queue
+   */
+  //@ also
+  //@ ensures \result != null;
+  //@ ensures (* \result == textual description of the queue *);
+  public  /*@ pure @*/ String toString() {
+
+    String retval = "Queue Contents "+m_Size+" elements\n";
+    QueueNode current = m_Head;
+    if (current == null) {
+      return retval + "Empty\n";
+    } else {
+      while (current != null) {
+        retval += current.contents().toString()+"\n"; //@nowarn Modifies;
+	current = current.next();
+      }
+    }
+    return retval;
+  } //@ nowarn Post;
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv a set of strings that are pushed on a test queue
+   */
+  //@ requires argv.length >= 0;
+  //@ requires argv != null;
+  //@ requires (\forall int i; 0 <= i && i < argv.length; argv[i] != null);
+  public static void main(String [] argv) {
+
+    try {
+      Queue queue = new Queue();
+      for(int i = 0; i < argv.length; i++) {
+	queue.push(argv[i]);
+      }
+      System.out.println("After pushing command line arguments");
+      System.out.println(queue.toString());
+      while (!queue.empty()) {
+	System.out.println("Pop: " + queue.pop().toString());
+      }
+      // try one more pop, to make sure we get an exception
+      try 
+	{
+	  queue.pop();
+	  System.out.println("ERROR: pop did not throw exception!");
+	}
+      catch (RuntimeException ex)
+        {
+	  System.out.println("Pop on empty queue correctly gave exception.");
+	}
+    } catch (Exception ex) {
+      System.out.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/RandomVariates.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/RandomVariates.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/RandomVariates.java	(revision 29)
@@ -0,0 +1,306 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomVariates.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.Random;
+
+/**
+ * Class implementing some simple random variates generator.
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 5359 $
+ */
+public final class RandomVariates extends Random implements RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4763742718209460354L;
+
+  /** 
+   * Simply the constructor of super class
+   */
+  public RandomVariates(){ super(); }
+
+  /** 
+   * Simply the constructor of super class
+   *
+   * @param seed the seed in this random object
+   */
+  public RandomVariates(long seed){ super(seed); }
+
+  /** 
+   * Simply use the method of the super class
+   *
+   * @param bits - random bits
+   * @return the next pseudorandom value from this random number 
+   * generator's sequence.
+   */
+  protected int next(int bits) {return super.next(bits);}
+
+  /**
+   * Generate a value of a variate following standard exponential
+   * distribution using simple inverse method.<p>
+   *
+   * Variates related to standard Exponential can be generated using simple
+   * transformations.
+   * @return a value of the variate
+   */ 
+  public double nextExponential(){
+    return -Math.log(1.0-super.nextDouble());
+  }
+
+  /**
+   * Generate a value of a variate following standard Erlang distribution.
+   * It can be used when the shape parameter is an integer and not too large,
+   * say, <100.  When the parameter is not an integer (which is often named
+   * Gamma distribution) or is large, use <code>nextGamma(double a)</code>
+   * instead.
+   *
+   * @param a the shape parameter, must be no less than 1
+   * @return a value of the variate
+   * @exception Exception if parameter less than 1
+   */
+  public double nextErlang(int a) throws Exception{
+    if(a<1)
+      throw new Exception("Shape parameter of Erlang distribution must be greater than 1!");
+
+    double product = 1.0;
+    for(int i=1; i<=a; i++)
+      product *= super.nextDouble();
+
+    return -Math.log(product);
+  }
+
+  /**
+   * Generate a value of a variate following standard Gamma distribution 
+   * with shape parameter a.<p>
+   * If a>1, it uses a rejection method developed by Minh(1988)"Generating
+   * Gamma Variates", ACM Trans. on Math. Software, Vol.14, No.3, pp261-266.
+   * <br>
+   * If a<1, it uses the algorithm "GS" developed by Ahrens and Dieter(1974)
+   * "COMPUTER METHODS FOR SAMPLING FROM GAMMA, BETA, POISSON AND BINOMIAL
+   * DISTRIBUTIONS", COMPUTING, 12 (1974), pp223-246, and further implemented
+   * in Fortran by Ahrens, Kohrt and Dieter(1983) "Algorithm 599: sampling
+   * from Gamma and Poisson distributions", ACM Trans. on Math. Software, 
+   * Vol.9 No.2, pp255-257.<p> 
+   * 
+   * Variates related to standard Gamma can be generated using simple
+   * transformations.
+   *
+   * @param a the shape parameter, must be greater than 1
+   * @return a value of the variate
+   * @exception Exception if parameter not greater than 1
+   */
+  public double nextGamma(double a) throws Exception{
+    if(a<=0.0)
+      throw new Exception("Shape parameter of Gamma distribution"+
+      "must be greater than 0!");
+    else if (a==1.0)
+      return nextExponential();
+    else if (a<1.0){
+      double b=1.0+Math.exp(-1.0)*a, p,x, condition;
+      do{
+        p=b*super.nextDouble();
+        if(p<1.0){
+          x = Math.exp(Math.log(p)/a);
+          condition = x;
+        }
+        else{
+          x = -Math.log((b-p)/a);
+          condition = (1.0-a)*Math.log(x);
+        }
+      }
+      while(nextExponential() < condition);
+      return x;	    
+    }
+    else{ // a>1
+      double b=a-1.0, D=Math.sqrt(b), D1,x1,x2,xl,f1,f2,x4,x5,xr,f4,f5,
+      p1,p2,p3,p4;
+
+      // Initialization
+      if(a<=2.0){
+        D1 = b/2.0;
+        x1 = 0.0;
+        x2 = D1;
+        xl = -1.0;
+        f1 = 0.0;
+      }
+      else{
+        D1 = D-0.5;
+        x2 = b-D1;
+        x1 = x2-D1;
+        xl = 1.0-b/x1;
+        f1 = Math.exp(b*Math.log(x1/b)+2.0*D1);
+      }
+
+      f2=Math.exp(b*Math.log(x2/b)+D1);
+      x4 = b+D;
+      x5 = x4+D;
+      xr = 1.0-b/x5;
+      f4 = Math.exp(b*Math.log(x4/b)-D);
+      f5 = Math.exp(b*Math.log(x5/b)-2.0*D);
+      p1 = 2.0*f4*D;
+      p2 = 2.0*f2*D1+p1;
+      p3 = f5/xr+p2;
+      p4 = -f1/xl+p3;
+
+      // Generation
+      double u, w=Double.MAX_VALUE, x=b, v, xp;
+      while(Math.log(w) > (b*Math.log(x/b)+b-x) || x < 0.0){
+        u=super.nextDouble()*p4;
+        if(u<=p1){ // step 5-6
+          w = u/D-f4;
+          if(w<=0.0) return (b+u/f4);
+          if(w<=f5)  return (x4+(w*D)/f5);
+
+          v = super.nextDouble();
+          x=x4+v*D;
+          xp=2.0*x4-x;
+
+          if(w >= f4+(f4-1)*(x-x4)/(x4-b))
+            return xp;
+          if(w <= f4+(b/x4-1)*f4*(x-x4))
+            return x;
+          if((w < 2.0*f4-1.0) || 
+              (w < 2.0*f4-Math.exp(b*Math.log(xp/b)+b-xp)))
+            continue;
+          return xp;
+        }
+        else if(u<=p2){ // step 7-8
+          w = (u-p1)/D1-f2;
+          if(w<=0.0) return (b-(u-p1)/f2);
+          if(w<=f1)  return (x1+w*D1/f1);
+
+          v = super.nextDouble();
+          x=x1+v*D1;
+          xp=2.0*x2-x;
+
+          if(w >= f2+(f2-1)*(x-x2)/(x2-b))
+            return xp;
+          if(w <= f2*(x-x1)/D1)
+            return x;
+          if((w < 2.0*f2-1.0) || 
+              (w < 2.0*f2-Math.exp(b*Math.log(xp/b)+b-xp)))
+            continue;
+          return xp;
+        }
+        else if(u<p3){ // step 9
+          w = super.nextDouble();
+          u = (p3-u)/(p3-p2);
+          x = x5-Math.log(u)/xr;
+          if(w <= (xr*(x5-x)+1.0)/u) return x;
+          w = w*f5*u;
+        }
+        else{ // step 10
+          w = super.nextDouble();
+          u = (p4-u)/(p4-p3);
+          x = x1-Math.log(u)/xl;
+          if(x<0.0) continue;
+          if(w <= (xl*(x1-x)+1.0)/u) return x;
+          w = w*f1*u;
+        }
+      }
+
+      return x;
+    }	
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5359 $");
+  }
+
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param ops # of variates/seed, default is 10/45
+   */
+  public static void main(String[] ops) {
+
+    int n = Integer.parseInt(ops[0]);
+    if(n<=0)
+      n=10;
+    long seed = Long.parseLong(ops[1]);
+    if(seed <= 0)
+      seed = 45;
+    RandomVariates var = new RandomVariates(seed);
+    double varb[] = new double[n];
+
+    try {
+      System.out.println("Generate "+n+" values with std. exp dist:");
+      for(int i=0; i<n; i++){
+        varb[i] = var.nextExponential();
+        System.out.print("["+i+"] "+varb[i]+", ");
+      }
+
+      System.out.println("\nMean is "+Utils.mean(varb)+
+          ", Variance is "+Utils.variance(varb)+
+          "\n\nGenerate "+n+" values with"+
+      " std. Erlang-5 dist:");
+
+      for(int i=0; i<n; i++){
+        varb[i] = var.nextErlang(5);
+        System.out.print("["+i+"] "+varb[i]+", ");
+      }
+
+      System.out.println("\nMean is "+Utils.mean(varb)+
+          ", Variance is "+Utils.variance(varb)+
+          "\n\nGenerate "+n+" values with"+
+      " std. Gamma(4.5) dist:");
+
+      for(int i=0; i<n; i++){
+        varb[i] = var.nextGamma(4.5);
+        System.out.print("["+i+"] "+varb[i]+", ");
+      }	 
+
+      System.out.println("\nMean is "+Utils.mean(varb)+
+          ", Variance is "+Utils.variance(varb)+
+          "\n\nGenerate "+n+" values with"+
+      " std. Gamma(0.5) dist:");
+
+      for(int i=0; i<n; i++){
+        varb[i] = var.nextGamma(0.5);
+        System.out.print("["+i+"] "+varb[i]+", ");
+      }	  	  
+
+      System.out.println("\nMean is "+Utils.mean(varb)+
+          ", Variance is "+Utils.variance(varb)+
+          "\n\nGenerate "+n+" values with"+
+      " std. Gaussian(5, 2) dist:");
+
+      for(int i=0; i<n; i++){
+        varb[i] = var.nextGaussian()*2.0+5.0;
+        System.out.print("["+i+"] "+varb[i]+", ");
+      }	  	  
+      System.out.println("\nMean is "+Utils.mean(varb)+
+          ", Variance is "+Utils.variance(varb)+"\n");
+
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Randomizable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Randomizable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Randomizable.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Randomizable.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Interface to something that has random behaviour that is able to be
+ * seeded with an integer.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Randomizable {
+
+  /**
+   * Set the seed for random number generation.
+   *
+   * @param seed the seed 
+   */
+  void setSeed(int seed);
+  
+  /**
+   * Gets the seed for the random number generations
+   *
+   * @return the seed for the random number generation
+   */
+  int getSeed();
+}
Index: branches/MetisMQI/src/main/java/weka/core/Range.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Range.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Range.java	(revision 29)
@@ -0,0 +1,458 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Range.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ * Class representing a range of cardinal numbers. The range is set by a 
+ * string representation such as: <P>
+ *
+ * <code>
+ *   first-last
+ *   1,2,3,4
+ * </code> <P>
+ * or combinations thereof. The range is internally converted from
+ * 1-based to 0-based (so methods that set or get numbers not in string
+ * format should use 0-based numbers).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class Range
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3667337062176835900L;
+
+  /** Record the string representations of the columns to delete */
+  /*@non_null spec_public@*/Vector m_RangeStrings = new Vector();
+
+  /** Whether matching should be inverted */
+  /*@spec_public@*/ boolean m_Invert;
+
+  /** The array of flags for whether an column is selected */
+  /*@spec_public@*/boolean [] m_SelectFlags;
+
+  /** Store the maximum value permitted in the range. -1 indicates that
+      no upper value has been set */
+  /*@spec_public@*/ int m_Upper = -1;
+
+  /** Default constructor. */
+  //@assignable this.*;
+    public Range() {
+  }
+
+  /**
+   * Constructor to set initial range.
+   *
+   * @param rangeList the initial range
+   * @throws IllegalArgumentException if the range list is invalid
+   */
+  public Range(/*@non_null@*/ String rangeList) {
+
+    setRanges(rangeList);
+  }
+
+  /**
+   * Sets the value of "last".
+   *
+   * @param newUpper the value of "last"
+   */
+  public void setUpper(int newUpper) {
+
+    if (newUpper >= 0) {
+      m_Upper = newUpper;
+      setFlags();
+    }
+  }
+  
+  /**
+   * Gets whether the range sense is inverted, i.e. all <i>except</i>
+   * the values included by the range string are selected.
+   * 
+   * @return whether the matching sense is inverted
+   */
+  //@ensures \result <==> m_Invert;
+  public /*@pure@*/boolean getInvert() {
+
+    return m_Invert;
+  }
+
+  /**
+   * Sets whether the range sense is inverted, i.e. all <i>except</i>
+   * the values included by the range string are selected.
+   * 
+   * @param newSetting true if the matching sense is inverted
+   */
+  public void setInvert(boolean newSetting) {
+
+    m_Invert = newSetting;
+  }
+
+  /**
+   * Gets the string representing the selected range of values
+   *
+   * @return the range selection string
+   */
+  public /*@non_null pure@*/String getRanges() {
+
+    StringBuffer result = new StringBuffer(m_RangeStrings.size()*4);
+    boolean first = true;
+    char sep = ',';
+    for (int i = 0; i < m_RangeStrings.size(); i++) {
+      if (first) {
+        result.append((String)m_RangeStrings.elementAt(i));
+        first = false;
+      } else {
+        result.append(sep + (String)m_RangeStrings.elementAt(i));
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * Sets the ranges from a string representation. Note that setUpper()
+   * must be called afterwards for ranges to be actually set internally.
+   *
+   * @param rangeList the comma separated list of ranges. The empty
+   * string sets the range to empty.
+   * @throws IllegalArgumentException if the rangeList was not well formed
+   */
+  //@requires rangeList != null;
+  //@assignable m_RangeStrings,m_SelectFlags;
+  public void setRanges(String rangeList) {
+
+    Vector<String> ranges = new Vector<String> (10);
+
+    // Split the rangeList up into the vector
+    while (!rangeList.equals("")) {
+      String range = rangeList.trim();
+      int commaLoc = rangeList.indexOf(',');
+      if (commaLoc != -1) {
+	range = rangeList.substring(0, commaLoc).trim();
+	rangeList = rangeList.substring(commaLoc + 1).trim();
+      } else {
+	rangeList = "";
+      }
+      if (!range.equals("")) {
+	ranges.addElement(range);
+      }
+    }
+    m_RangeStrings = ranges;
+    m_SelectFlags = null;
+  }
+
+  /**
+   * Gets whether the supplied cardinal number is included in the current
+   * range.
+   *
+   * @param index the number of interest
+   * @return true if index is in the current range
+   * @throws RuntimeException if the upper limit of the range hasn't been defined
+   */
+  //@requires m_Upper >= 0;
+  //@requires 0 <= index && index < m_SelectFlags.length;
+  public /*@pure@*/ boolean isInRange(int index) {
+
+    if (m_Upper == -1) {
+      throw new RuntimeException("No upper limit has been specified for range");
+    }
+    if (m_Invert) {
+      return !m_SelectFlags[index];
+    } else {
+      return m_SelectFlags[index];
+    }
+  }
+
+  /**
+   * Constructs a representation of the current range. Being a string
+   * representation, the numbers are based from 1.
+   * 
+   * @return the string representation of the current range
+   */
+  public /*@non_null pure@*/ String toString() {
+
+    if (m_RangeStrings.size() == 0) {
+      return "Empty";
+    }
+    String result ="Strings: ";
+    Enumeration enu = m_RangeStrings.elements();
+    while (enu.hasMoreElements()) {
+      result += (String)enu.nextElement() + " ";
+    }
+    result += "\n";
+
+    result += "Invert: " + m_Invert + "\n";
+
+    try {
+      if (m_Upper == -1) {
+	throw new RuntimeException("Upper limit has not been specified");
+      }
+      String cols = null;
+      for (int i = 0; i < m_SelectFlags.length; i++) {
+	if (isInRange(i)) {
+	  if (cols == null) {
+	    cols = "Cols: " + (i + 1);
+	  } else {
+	    cols += "," + (i + 1);
+	  }
+	}
+      }
+      if (cols != null) {
+	result += cols + "\n";
+      }
+    } catch (Exception ex) {
+      result += ex.getMessage();
+    }
+    return result;
+  }
+
+  /**
+   * Gets an array containing all the selected values, in the order
+   * that they were selected (or ascending order if range inversion is on)
+   *
+   * @return the array of selected values
+   * @throws RuntimeException if the upper limit of the range hasn't been defined
+   */
+  //@requires m_Upper >= 0;
+  public /*@non_null@*/ int [] getSelection() {
+
+    if (m_Upper == -1) {
+      throw new RuntimeException("No upper limit has been specified for range");
+    }
+    int [] selectIndices = new int [m_Upper + 1];
+    int numSelected = 0;
+    if (m_Invert)
+    {
+      for (int i = 0; i <= m_Upper; i++) {
+	if (!m_SelectFlags[i]) {
+	  selectIndices[numSelected++] = i;
+	}
+      }
+    }
+    else
+    {
+      Enumeration enu = m_RangeStrings.elements();
+      while (enu.hasMoreElements()) {
+	String currentRange = (String)enu.nextElement();
+	int start = rangeLower(currentRange);
+	int end = rangeUpper(currentRange);
+	for (int i = start; (i <= m_Upper) && (i <= end); i++) {
+	  if (m_SelectFlags[i]) {
+	    selectIndices[numSelected++] = i;
+	  }
+	}
+      }
+    }
+    int [] result = new int [numSelected];
+    System.arraycopy(selectIndices, 0, result, 0, numSelected);
+    return result;
+  }
+
+  /**
+   * Creates a string representation of the indices in the supplied array.
+   *
+   * @param indices an array containing indices to select.
+   * Since the array will typically come from a program, indices are assumed
+   * from 0, and thus will have 1 added in the String representation.
+   * @return the string representation of the indices
+   */
+  public static /*@non_null pure@*/String indicesToRangeList(/*@non_null@*/ int []indices) {
+
+    StringBuffer rl = new StringBuffer();
+    int last = -2;
+    boolean range = false;
+    for(int i = 0; i < indices.length; i++) {
+      if (i == 0) {
+	rl.append(indices[i] + 1);
+      } else if (indices[i] == last) {
+        range = true;
+      } else {
+        if (range) {
+          rl.append('-').append(last);
+          range = false;
+        }
+	rl.append(',').append(indices[i] + 1);
+      }
+      last = indices[i] + 1;
+    }
+    if (range) {
+      rl.append('-').append(last);
+    }
+    return rl.toString();
+  }
+
+  /** Sets the flags array. */
+  protected void setFlags() {
+
+    m_SelectFlags = new boolean [m_Upper + 1];
+    Enumeration enu = m_RangeStrings.elements();
+    while (enu.hasMoreElements()) {
+      String currentRange = (String)enu.nextElement();
+      if (!isValidRange(currentRange)) {
+	throw new IllegalArgumentException("Invalid range list at " + currentRange);
+      }
+      int start = rangeLower(currentRange);
+      int end = rangeUpper(currentRange);
+      for (int i = start; (i <= m_Upper) && (i <= end); i++) {
+	m_SelectFlags[i] = true;
+      }
+    }
+  }
+
+
+  /**
+   * Translates a single string selection into it's internal 0-based equivalent
+   *
+   * @param single the string representing the selection (eg: 1 first last)
+   * @return the number corresponding to the selected value
+   */
+  protected /*@pure@*/ int rangeSingle(/*@non_null@*/ String single) {
+
+    if (single.toLowerCase().equals("first")) {
+      return 0;
+    }
+    if (single.toLowerCase().equals("last")) {
+      return m_Upper;
+    }
+    int index = Integer.parseInt(single) - 1;
+    if (index < 0) {
+      index = 0;
+    }
+    if (index > m_Upper) {
+      index = m_Upper;
+    }
+    return index;
+  }
+
+  /**
+   * Translates a range into it's lower index.
+   *
+   * @param range the string representation of the range
+   * @return the lower index of the range
+   */ 
+  protected int rangeLower(/*@non_null@*/ String range) {
+
+    int hyphenIndex;
+    if ((hyphenIndex = range.indexOf('-')) >= 0) {
+      return Math.min(rangeLower(range.substring(0, hyphenIndex)),
+		       rangeLower(range.substring(hyphenIndex + 1)));
+    }
+    return rangeSingle(range);
+  }
+
+  /**
+   * Translates a range into it's upper index. Must only be called once
+   * setUpper has been called.
+   *
+   * @param range the string representation of the range
+   * @return the upper index of the range
+   */
+  protected int rangeUpper(/*@non_null@*/ String range) {
+
+    int hyphenIndex;
+    if ((hyphenIndex = range.indexOf('-')) >= 0) {
+      return Math.max(rangeUpper(range.substring(0, hyphenIndex)),
+		       rangeUpper(range.substring(hyphenIndex + 1)));
+    }
+    return rangeSingle(range);
+  }
+
+  /**
+   * Determines if a string represents a valid index or simple range.
+   * Examples: <code>first  last   2   first-last  first-4  4-last</code>
+   * Doesn't check that a < b for a-b
+   *
+   * @param range the string to check
+   * @return true if the range is valid
+   */
+  protected boolean isValidRange(String range) {
+
+    if (range == null) {
+      return false;
+    }
+    int hyphenIndex;
+    if ((hyphenIndex = range.indexOf('-')) >= 0) {
+      if (isValidRange(range.substring(0, hyphenIndex)) &&
+	  isValidRange(range.substring(hyphenIndex + 1))) {
+	return true;
+      }
+      return false;
+    }
+    if (range.toLowerCase().equals("first")) {
+      return true;
+    }
+    if (range.toLowerCase().equals("last")) {
+      return true;
+    }
+    try {
+      int index = Integer.parseInt(range);
+      if ((index > 0) && (index <= m_Upper + 1)){
+	return true;
+      }
+      return false;
+    } catch (NumberFormatException ex) {
+      return false;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv one parameter: a test range specification
+   */
+  public static void main(String [] argv) {
+
+    try {
+      if (argv.length == 0) {
+	throw new Exception("Usage: Range <rangespec>");
+      }
+      Range range = new Range();
+      range.setRanges(argv[0]);
+      range.setUpper(9);
+      range.setInvert(false);
+      System.out.println("Input: " + argv[0] + "\n"
+			 + range.toString());
+      int [] rangeIndices = range.getSelection();
+      for (int i = 0; i < rangeIndices.length; i++)
+	System.out.print(" " + (rangeIndices[i] + 1));
+      System.out.println("");
+    } catch (Exception ex) {
+      System.out.println(ex.getMessage());
+    }
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/core/RelationalLocator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/RelationalLocator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/RelationalLocator.java	(revision 29)
@@ -0,0 +1,177 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RelationalLocator.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+
+/**
+ * This class locates and records the indices of relational attributes, 
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Attribute#RELATIONAL
+ */
+public class RelationalLocator
+  extends AttributeLocator {
+
+  /** for serialization */
+
+  private static final long serialVersionUID = 4646872277151854732L;
+
+  /**
+   * Initializes the RelationalLocator with the given data.
+   * 
+   * @param data	the data to work on
+   */
+  public RelationalLocator(Instances data) {
+    super(data, Attribute.RELATIONAL);
+  }
+  
+  /**
+   * Initializes the RelationalLocator with the given data. 
+   * Checks only the given range.
+   * 
+   * @param data	the data to work on
+   * @param fromIndex	the first index to inspect (including)
+   * @param toIndex	the last index to check (including)
+   */
+  public RelationalLocator(Instances data, int fromIndex, int toIndex) {
+    super(data, Attribute.RELATIONAL, fromIndex, toIndex);
+  }
+  
+  /**
+   * Initializes the RelationalLocator with the given data.
+   * Checks only the specified attribute indices.
+   * 
+   * @param data	the data to work on
+   * @param indices	the attribute indices to check
+   */
+  public RelationalLocator(Instances data, int[] indices) {
+    super(data, Attribute.RELATIONAL, indices);
+  }
+
+  /**
+   * Copies relational values contained in the instance copied to a new
+   * dataset. The Instance must already be assigned to a dataset. This
+   * dataset and the destination dataset must have the same structure.
+   *
+   * @param inst 		the Instance containing the relational values 
+   * 				to copy.
+   * @param destDataset 	the destination set of Instances
+   * @param strAtts 		an AttributeLocator containing the indices of 
+   * 				any relational attributes in the dataset.  
+   */
+  public static void copyRelationalValues(Instance inst, Instances destDataset, 
+                               AttributeLocator strAtts) {
+
+    if (inst.dataset() == null) {
+      throw new IllegalArgumentException("Instance has no dataset assigned!!");
+    } else if (inst.dataset().numAttributes() != destDataset.numAttributes()) {
+      throw new IllegalArgumentException("Src and Dest differ in # of attributes!!");
+    } 
+    copyRelationalValues(inst, true, inst.dataset(), strAtts,
+                     destDataset, strAtts);
+  }
+
+  /**
+   * Takes relational values referenced by an Instance and copies them from a
+   * source dataset to a destination dataset. The instance references are
+   * updated to be valid for the destination dataset. The instance may have the 
+   * structure (i.e. number and attribute position) of either dataset (this
+   * affects where references are obtained from). Only works if the number
+   * of relational attributes is the same in both indices (implicitly these 
+   * relational attributes should be semantically same but just with shifted 
+   * positions).
+   *
+   * @param instance 		the instance containing references to relations 
+   * 				in the source dataset that will have references 
+   * 				updated to be valid for the destination dataset.
+   * @param instSrcCompat 	true if the instance structure is the same as 
+   * 				the source, or false if it is the same as the 
+   * 				destination (i.e. which of the relational 
+   * 				attribute indices contains the correct 
+   * 				locations for this instance).
+   * @param srcDataset 		the dataset for which the current instance 
+   * 				relationvalue references are valid (after any 
+   * 				position mapping if needed)
+   * @param srcLoc 		an AttributeLocator containing the indices of 
+   * 				relational attributes in the source datset.
+   * @param destDataset 	the dataset for which the current instance 
+   * 				relation references need to be inserted (after 
+   * 				any position mapping if needed)
+   * @param destLoc 	an AttributeLocator containing the indices of 
+   * 				relational attributes in the destination datset.
+   */
+  public static void copyRelationalValues(Instance instance, boolean instSrcCompat,
+                                  Instances srcDataset, AttributeLocator srcLoc,
+                                  Instances destDataset, AttributeLocator destLoc) {
+    if (srcDataset == destDataset)
+      return;
+    
+    if (srcLoc.getAttributeIndices().length != destLoc.getAttributeIndices().length)
+      throw new IllegalArgumentException("Src and Dest relational indices differ in length!!");
+
+    if (srcLoc.getLocatorIndices().length != destLoc.getLocatorIndices().length)
+      throw new IllegalArgumentException("Src and Dest locator indices differ in length!!");
+
+    for (int i = 0; i < srcLoc.getAttributeIndices().length; i++) {
+      int instIndex  = instSrcCompat 
+      		 	  ? srcLoc.getActualIndex(srcLoc.getAttributeIndices()[i]) 
+      		 	  : destLoc.getActualIndex(destLoc.getAttributeIndices()[i]);
+      Attribute src  = srcDataset.attribute(srcLoc.getActualIndex(srcLoc.getAttributeIndices()[i]));
+      Attribute dest = destDataset.attribute(destLoc.getActualIndex(destLoc.getAttributeIndices()[i]));
+      if (!instance.isMissing(instIndex)) {
+        int valIndex = dest.addRelation(src.relation((int)instance.value(instIndex)));
+        instance.setValue(instIndex, (double)valIndex);
+      }
+    }
+    
+    // recurse if necessary
+    int[] srcIndices  = srcLoc.getLocatorIndices();
+    int[] destIndices = destLoc.getLocatorIndices();
+    for (int i = 0; i < srcIndices.length; i++) {
+      int index = instSrcCompat
+	         ? srcLoc.getActualIndex(srcIndices[i])
+	         : destLoc.getActualIndex(destIndices[i]);
+      if (instance.isMissing(index))
+	continue;
+      Instances rel = instSrcCompat
+      		         ? instance.relationalValue(index)
+      		         : instance.relationalValue(index);
+      AttributeLocator srcRelAttsNew = srcLoc.getLocator(srcIndices[i]);
+      Instances srcDatasetNew = srcRelAttsNew.getData();
+      AttributeLocator destRelAttsNew = destLoc.getLocator(destIndices[i]);
+      Instances destDatasetNew = destRelAttsNew.getData();
+      for (int n = 0; n < rel.numInstances(); n++) {
+        copyRelationalValues(rel.instance(n), instSrcCompat, srcDatasetNew, srcRelAttsNew, destDatasetNew, destRelAttsNew);
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/RevisionHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/RevisionHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/RevisionHandler.java	(revision 29)
@@ -0,0 +1,39 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RevisionHandler.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+/**
+ * For classes that should return their source control revision.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see     weka.core.RevisionUtils
+ */
+public interface RevisionHandler {
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision();
+}
Index: branches/MetisMQI/src/main/java/weka/core/RevisionUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/RevisionUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/RevisionUtils.java	(revision 29)
@@ -0,0 +1,153 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RevisionUtils.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+/**
+ * Contains utility functions for handling revisions.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class RevisionUtils {
+  
+  /**
+   * Enumeration of source control types.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public enum Type {
+    /** unknown source control revision. */
+    UNKNOWN,
+    /** CVS. */
+    CVS,
+    /** Subversion. */
+    SUBVERSION;
+  }
+  
+  /**
+   * Extracts the revision string returned by the RevisionHandler.
+   * 
+   * @param handler	the RevisionHandler to get the revision for
+   * @return		the actual revision string
+   */
+  public static String extract(RevisionHandler handler) {
+    return extract(handler.getRevision());
+  }
+  
+  /**
+   * Extracts the revision string.
+   * 
+   * @param s		the string to get the revision string from
+   * @return		the actual revision string
+   */
+  public static String extract(String s) {
+    String	result;
+    
+    result = s;
+    result = result.replaceAll("\\$Revision:", "");
+    result = result.replaceAll("\\$", "");
+    result = result.replaceAll(" ", "");
+    
+    return result;
+  }
+  
+  /**
+   * Determines the type of a (sanitized) revision string returned by the 
+   * RevisionHandler.
+   * 
+   * @param handler	the RevisionHandler to determine the type for
+   * @return		the type, UNKNOWN if it cannot be determined
+   */
+  public static Type getType(RevisionHandler handler) {
+    return getType(extract(handler));
+  }
+  
+  /**
+   * Determines the type of a (sanitized) revision string. Use extract(String)
+   * method to extract the revision first before calling this method.
+   * 
+   * @param revision	the revision to get the type for
+   * @return		the type, UNKNOWN if it cannot be determined
+   * @see #extract(String)
+   */
+  public static Type getType(String revision) {
+    Type	result;
+    String[]	parts;
+    int		i;
+    
+    result = Type.UNKNOWN;
+    
+    // subversion?
+    try {
+      Integer.parseInt(revision);
+      result = Type.SUBVERSION;
+    }
+    catch (Exception e) {
+      // ignored
+    }
+    
+    // CVS?
+    if (result == Type.UNKNOWN) {
+      try {
+	// must contain at least ONE dot
+	if (revision.indexOf('.') == -1)
+	  throw new Exception("invalid CVS revision - not dots!");
+	
+	parts = revision.split("\\.");
+
+	// must consist of at least TWO parts/integers
+	if (parts.length < 2)
+	  throw new Exception("invalid CVS revision - not enough parts separated by dots!");
+
+	// try parsing parts of revision string - must be ALL integers
+	for (i = 0; i < parts.length; i++)
+	  Integer.parseInt(parts[i]);
+	
+	result = Type.CVS;
+      }
+      catch (Exception e) {
+	// ignored
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * For testing only. The first parameter must be a classname of a
+   * class implementing the weka.core.RevisionHandler interface.
+   * 
+   * @param args	the commandline arguments
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    if (args.length != 1) {
+      System.err.println("\nUsage: " + RevisionUtils.class.getName() + " <classname>\n");
+      System.exit(1);
+    }
+    
+    RevisionHandler handler = (RevisionHandler) Class.forName(args[0]).newInstance();
+    System.out.println("Type: " + getType(handler));
+    System.out.println("Revision: " + extract(handler));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SelectedTag.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SelectedTag.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SelectedTag.java	(revision 29)
@@ -0,0 +1,152 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SelectedTag.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.util.HashSet;
+
+/**
+ * Represents a selected value from a finite set of values, where each
+ * value is a Tag (i.e. has some string associated with it). Primarily
+ * used in schemes to select between alternative behaviours,
+ * associating names with the alternative behaviours.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a> 
+ * @version $Revision: 5953 $
+ */
+public class SelectedTag
+  implements RevisionHandler {
+  
+  /** The index of the selected tag */
+  protected int m_Selected;
+  
+  /** The set of tags to choose from */
+  protected Tag[] m_Tags;
+  
+  /**
+   * Creates a new <code>SelectedTag</code> instance.
+   *
+   * @param tagID the id of the selected tag.
+   * @param tags an array containing the possible valid Tags.
+   * @throws IllegalArgumentException if the selected tag isn't in the array
+   * of valid values or the IDs/IDStrs are not unique.
+   */
+  public SelectedTag(int tagID, Tag[] tags) {
+    // are IDs unique?
+    HashSet<Integer> ID = new HashSet<Integer>();
+    HashSet<String> IDStr = new HashSet<String>();
+    for (int i = 0; i < tags.length; i++) {
+      ID.add(new Integer(tags[i].getID()));
+      IDStr.add(tags[i].getIDStr());
+    }
+    if (ID.size() != tags.length)
+      throw new IllegalArgumentException("The IDs are not unique!");
+    if (IDStr.size() != tags.length)
+      throw new IllegalArgumentException("The ID strings are not unique!");
+
+    for (int i = 0; i < tags.length; i++) {
+      if (tags[i].getID() == tagID) {
+	m_Selected = i;
+	m_Tags = tags;
+	return;
+      }
+    }
+    
+    throw new IllegalArgumentException("Selected tag is not valid");
+  }
+  
+  /**
+   * Creates a new <code>SelectedTag</code> instance.
+   *
+   * @param tagText the text of the selected tag (case-insensitive).
+   * @param tags an array containing the possible valid Tags.
+   * @throws IllegalArgumentException if the selected tag isn't in the array
+   * of valid values.
+   */
+  public SelectedTag(String tagText, Tag[] tags) {
+    for (int i = 0; i < tags.length; i++) {
+      if (    tags[i].getReadable().equalsIgnoreCase(tagText)
+	   || tags[i].getIDStr().equalsIgnoreCase(tagText) ) {
+        m_Selected = i;
+        m_Tags = tags;
+        return;
+      }
+    }
+    throw new IllegalArgumentException("Selected tag is not valid");
+  }
+  
+  /**
+   * Returns true if this SelectedTag equals another object
+   * 
+   * @param o the object to compare with
+   * @return true if the tags and the selected tag are the same
+   */
+  public boolean equals(Object o) {
+    if ((o == null) || !(o.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    SelectedTag s = (SelectedTag)o;
+    if ((s.getTags() == m_Tags)
+	&& (s.getSelectedTag() == m_Tags[m_Selected])) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  
+  
+  /**
+   * Gets the selected Tag.
+   *
+   * @return the selected Tag.
+   */
+  public Tag getSelectedTag() {
+    return m_Tags[m_Selected];
+  }
+  
+  /**
+   * Gets the set of all valid Tags.
+   *
+   * @return an array containing the valid Tags.
+   */
+  public Tag[] getTags() {
+    return m_Tags;
+  }
+  
+  /**
+   * returns the selected tag in string representation
+   * 
+   * @return the selected tag as string
+   */
+  public String toString() {
+    return getSelectedTag().toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SerializationHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SerializationHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SerializationHelper.java	(revision 29)
@@ -0,0 +1,353 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SerializationHelper.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.Vector;
+
+/**
+ * A helper class for determining serialVersionUIDs and checking whether
+ * classes contain one and/or need one. One can also serialize and deserialize
+ * objects to and fro files or streams.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class SerializationHelper
+  implements RevisionHandler {
+
+  /** the field name of serialVersionUID. */
+  public final static String SERIAL_VERSION_UID = "serialVersionUID";
+  
+  /**
+   * checks whether a class is serializable.
+   * 
+   * @param classname	the class to check
+   * @return		true if the class or one of its ancestors implements
+   * 			the Serializable interface, otherwise false (also if 
+   * 			the class cannot be loaded)
+   */
+  public static boolean isSerializable(String classname) {
+    boolean	result;
+    
+    try {
+      result = isSerializable(Class.forName(classname));
+    }
+    catch (Exception e) {
+      result = false;
+    }
+      
+    return result;
+  }
+  
+  /**
+   * checks whether a class is serializable.
+   * 
+   * @param c		the class to check
+   * @return		true if the class or one of its ancestors implements
+   * 			the Serializable interface, otherwise false
+   */
+  public static boolean isSerializable(Class c) {
+    return ClassDiscovery.hasInterface(Serializable.class, c);
+  }
+  
+  /**
+   * checks whether the given class contains a serialVersionUID.
+   * 
+   * @param classname	the class to check
+   * @return		true if the class contains a serialVersionUID, 
+   * 			otherwise false (also if the class is not
+   * 			implementing serializable or cannot be loaded)
+   */
+  public static boolean hasUID(String classname) {
+    boolean	result;
+    
+    try {
+      result = hasUID(Class.forName(classname));
+    }
+    catch (Exception e) {
+      result = false;
+    }
+    
+    return result;
+  }
+
+  /**
+   * checks whether the given class contains a serialVersionUID.
+   * 
+   * @param c		the class to check
+   * @return		true if the class contains a serialVersionUID, 
+   * 			otherwise false (also if the class is not
+   * 			implementing serializable)
+   */
+  public static boolean hasUID(Class c) {
+    boolean	result;
+    
+    result = false;
+    
+    if (isSerializable(c)) {
+      try {
+	c.getDeclaredField(SERIAL_VERSION_UID);
+	result = true;
+      }
+      catch (Exception e) {
+	result = false;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether a class needs to declare a serialVersionUID, i.e., it
+   * implements the java.io.Serializable interface but doesn't declare a
+   * serialVersionUID.
+   * 
+   * @param classname	the class to check
+   * @return		true if the class needs to declare one, false otherwise
+   * 			(also if the class cannot be loaded!)
+   */
+  public static boolean needsUID(String classname) {
+    boolean	result;
+    
+    try {
+      result = needsUID(Class.forName(classname));
+    }
+    catch (Exception e) {
+      result = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether a class needs to declare a serialVersionUID, i.e., it
+   * implements the java.io.Serializable interface but doesn't declare a
+   * serialVersionUID.
+   * 
+   * @param c		the class to check
+   * @return		true if the class needs to declare one, false otherwise
+   */
+  public static boolean needsUID(Class c) {
+    boolean	result;
+    
+    if (isSerializable(c))
+      result = !hasUID(c);
+    else
+      result = false;
+    
+    return result;
+  }
+  
+  /**
+   * reads or creates the serialVersionUID for the given class.
+   * 
+   * @param classname	the class to get the serialVersionUID for
+   * @return		the UID, 0L for non-serializable classes (or if the
+   * 			class cannot be loaded)
+   */
+  public static long getUID(String classname) {
+    long	result;
+    
+    try {
+      result = getUID(Class.forName(classname));
+    }
+    catch (Exception e) {
+      result = 0L;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * reads or creates the serialVersionUID for the given class.
+   * 
+   * @param c		the class to get the serialVersionUID for
+   * @return		the UID, 0L for non-serializable classes
+   */
+  public static long getUID(Class c) {
+    return ObjectStreamClass.lookup(c).getSerialVersionUID();
+  }
+
+  /**
+   * serializes the given object to the specified file.
+   * 
+   * @param filename	the file to write the object to
+   * @param o		the object to serialize
+   * @throws Exception	if serialization fails
+   */
+  public static void write(String filename, Object o) throws Exception {
+    write(new FileOutputStream(filename), o);
+  }
+
+  /**
+   * serializes the given object to the specified stream.
+   * 
+   * @param stream	the stream to write the object to
+   * @param o		the object to serialize
+   * @throws Exception	if serialization fails
+   */
+  public static void write(OutputStream stream, Object o) throws Exception {
+    ObjectOutputStream	oos;
+    
+    oos = new ObjectOutputStream(stream);
+    oos.writeObject(o);
+    oos.flush();
+    oos.close();
+  }
+
+  /**
+   * serializes the given objects to the specified file.
+   * 
+   * @param filename	the file to write the object to
+   * @param o		the objects to serialize
+   * @throws Exception	if serialization fails
+   */
+  public static void writeAll(String filename, Object[] o) throws Exception {
+    writeAll(new FileOutputStream(filename), o);
+  }
+
+  /**
+   * serializes the given objects to the specified stream.
+   * 
+   * @param stream	the stream to write the object to
+   * @param o		the objects to serialize
+   * @throws Exception	if serialization fails
+   */
+  public static void writeAll(OutputStream stream, Object[] o) throws Exception {
+    ObjectOutputStream	oos;
+    int			i;
+    
+    oos = new ObjectOutputStream(stream);
+    for (i = 0; i < o.length; i++)
+      oos.writeObject(o[i]);
+    oos.flush();
+    oos.close();
+  }
+
+  /**
+   * deserializes the given file and returns the object from it.
+   * 
+   * @param filename	the file to deserialize from
+   * @return		the deserialized object
+   * @throws Exception	if deserialization fails
+   */
+  public static Object read(String filename) throws Exception {
+    return read(new FileInputStream(filename));
+  }
+
+  /**
+   * deserializes from the given stream and returns the object from it.
+   * 
+   * @param stream	the stream to deserialize from
+   * @return		the deserialized object
+   * @throws Exception	if deserialization fails
+   */
+  public static Object read(InputStream stream) throws Exception {
+    ObjectInputStream 	ois;
+    Object		result;
+    
+    ois = new ObjectInputStream(stream);
+    result = ois.readObject();
+    ois.close();
+    
+    return result;
+  }
+
+  /**
+   * deserializes the given file and returns the objects from it.
+   * 
+   * @param filename	the file to deserialize from
+   * @return		the deserialized objects
+   * @throws Exception	if deserialization fails
+   */
+  public static Object[] readAll(String filename) throws Exception {
+    return readAll(new FileInputStream(filename));
+  }
+
+  /**
+   * deserializes from the given stream and returns the object from it.
+   * 
+   * @param stream	the stream to deserialize from
+   * @return		the deserialized object
+   * @throws Exception	if deserialization fails
+   */
+  public static Object[] readAll(InputStream stream) throws Exception {
+    ObjectInputStream 	ois;
+    Vector<Object>		result;
+    
+    ois    = new ObjectInputStream(stream);
+    result = new Vector<Object>();
+    try {
+      while (true) {
+	result.add(ois.readObject());
+      }
+    }
+    catch (Exception e) {
+      // ignored
+    }
+    ois.close();
+    
+    return result.toArray(new Object[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Outputs information about a class on the commandline, takes class
+   * name as arguments.
+   * 
+   * @param args	the classnames to check
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      System.out.println("\nUsage: " + SerializationHelper.class.getName() + " classname [classname [classname [...]]]\n");
+      System.exit(1);
+    }
+    
+    // check all the classes
+    System.out.println();
+    for (int i = 0; i < args.length; i++) {
+      System.out.println(args[i]);
+      System.out.println("- is serializable: " + isSerializable(args[i]));
+      System.out.println("- has " + SERIAL_VERSION_UID + ": " + hasUID(args[i]));
+      System.out.println("- needs " + SERIAL_VERSION_UID + ": " + needsUID(args[i]));
+      System.out.println("- " + SERIAL_VERSION_UID + ": private static final long serialVersionUID = " + getUID(args[i]) + "L;");
+      System.out.println();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SerializedObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SerializedObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SerializedObject.java	(revision 29)
@@ -0,0 +1,170 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedObject.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import weka.core.scripting.Jython;
+import weka.core.scripting.JythonSerializableObject;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Class for storing an object in serialized form in memory. It can be used 
+ * to make deep copies of objects, and also allows compression to conserve
+ * memory. <p>
+ *
+ * @author Richard Kirkby (rbk1@cs.waikato.ac.nz)
+ * @version $Revision: 5018 $ 
+ */
+public class SerializedObject
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6635502953928860434L;
+
+  /** The array storing the object. */
+  private byte[] m_storedObjectArray;
+
+  /** Whether or not the object is compressed. */
+  private boolean m_isCompressed;
+
+  /** Whether it is a Jython object or not */
+  private boolean m_isJython;
+  
+  /**
+   * Creates a new serialized object (without compression).
+   *
+   * @param toStore the object to store
+   * @exception Exception if the object couldn't be serialized
+   */ 
+  public SerializedObject(Object toStore) throws Exception {
+
+    this(toStore, false);
+  }
+
+  /**
+   * Creates a new serialized object.
+   *
+   * @param toStore the object to store
+   * @param compress whether or not to use compression
+   * @exception Exception if the object couldn't be serialized
+   */ 
+  public SerializedObject(Object toStore, boolean compress) throws Exception {
+
+    ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+    OutputStream os = ostream;
+    ObjectOutputStream p;
+    if (!compress)
+      p = new ObjectOutputStream(new BufferedOutputStream(os));
+    else
+      p = new ObjectOutputStream(new BufferedOutputStream(new GZIPOutputStream(os)));
+    p.writeObject(toStore);
+    p.flush();
+    p.close(); // used to be ostream.close() !
+    m_storedObjectArray = ostream.toByteArray();
+
+    m_isCompressed = compress;
+    m_isJython     = (toStore instanceof JythonSerializableObject);
+  }
+
+  /*
+   * Checks to see whether this object is equal to another.
+   *
+   * @param compareTo the object to compare to
+   * @return whether or not the objects are equal
+   */
+  public final boolean equals(Object compareTo) {
+
+    if (compareTo == null) return false;
+    if (!compareTo.getClass().equals(this.getClass())) return false;
+    byte[] compareArray = ((SerializedObject)compareTo).m_storedObjectArray;
+    if (compareArray.length != m_storedObjectArray.length) return false;
+    for (int i=0; i<compareArray.length; i++) {
+      if (compareArray[i] != m_storedObjectArray[i]) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns a hashcode for this object.
+   *
+   * @return the hashcode
+   */
+  public int hashCode() {
+
+    return m_storedObjectArray.length;
+  }
+
+  /**
+   * Returns a serialized object. Uses org.python.util.PythonObjectInputStream 
+   * for Jython objects (read 
+   * <a href="http://aspn.activestate.com/ASPN/Mail/Message/Jython-users/1001401">here</a>
+   * for more details).
+   *
+   * @return the restored object
+   * @exception Exception if the object couldn't be restored
+   */ 
+  public Object getObject() {
+
+    try {
+      ByteArrayInputStream istream = new ByteArrayInputStream(m_storedObjectArray);
+      ObjectInputStream p;
+      Object toReturn = null;
+      if (m_isJython) {
+	if (!m_isCompressed)
+	  toReturn = Jython.deserialize(new BufferedInputStream(istream));
+	else 
+	  toReturn = Jython.deserialize(new BufferedInputStream(new GZIPInputStream(istream)));
+      }
+      else {
+	if (!m_isCompressed)
+	  p = new ObjectInputStream(new BufferedInputStream(istream));
+	else 
+	  p = new ObjectInputStream(new BufferedInputStream(new GZIPInputStream(istream)));
+	toReturn = p.readObject();
+      }
+      istream.close();
+      return toReturn;
+    } catch (Exception e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5018 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SingleIndex.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SingleIndex.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SingleIndex.java	(revision 29)
@@ -0,0 +1,239 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SingleIndex.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+
+/** 
+ * Class representing a single cardinal number. The number is set by a 
+ * string representation such as: <P>
+ *
+ * <code>
+ *   first
+ *   last
+ *   1
+ *   3
+ * </code> <P>
+ * The number is internally converted from 1-based to 0-based (so methods that 
+ * set or get numbers not in string format should use 0-based numbers).
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class SingleIndex
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5285169134430839303L;
+
+  /** Record the string representation of the number */
+  protected /*@non_null spec_public@*/ String m_IndexString = "";
+
+  /** The selected index */
+  protected /*@ spec_public @*/ int m_SelectedIndex = -1;
+
+  /** Store the maximum value permitted. -1 indicates that no upper
+      value has been set */
+  protected /*@ spec_public @*/ int m_Upper = -1;
+
+  /**
+   * Default constructor.
+   *
+   */
+  //@ assignable m_IndexString, m_SelectedIndex, m_Upper;
+  //@ ensures m_SelectedIndex == -1;
+  //@ ensures m_Upper == -1;
+  public SingleIndex() {
+  }
+
+  /**
+   * Constructor to set initial index.
+   *
+   * @param index the initial index
+   * @throws IllegalArgumentException if the index is invalid
+   */
+  //@ assignable m_IndexString, m_SelectedIndex, m_Upper;
+  //@ ensures m_IndexString == index;
+  //@ ensures m_SelectedIndex == -1;
+  //@ ensures m_Upper == -1;
+  public SingleIndex(/*@non_null@*/ String index) {
+
+    setSingleIndex(index);
+  }
+
+  /**
+   * Sets the value of "last".
+   *
+   * @param newUpper the value of "last"
+   */
+  //@ assignable m_Upper, m_IndexString, m_SelectedIndex;
+  //@ ensures newUpper < 0 ==> m_Upper == \old(m_Upper);
+  //@ ensures newUpper >= 0 ==> m_Upper == newUpper;
+  public void setUpper(int newUpper) {
+
+    if (newUpper >= 0) {
+      m_Upper = newUpper;
+      setValue();
+    }
+  }
+
+  /**
+   * Gets the string representing the selected range of values
+   *
+   * @return the range selection string
+   */
+  //@ ensures \result == m_IndexString;
+  public /*@pure@*/ String getSingleIndex() {
+
+    return m_IndexString;
+  }
+
+  /**
+   * Sets the index from a string representation. Note that setUpper()
+   * must be called for the value to be actually set
+   *
+   * @param index the index set
+   * @throws IllegalArgumentException if the index was not well formed
+   */
+  //@ assignable m_IndexString, m_SelectedIndex;
+  //@ ensures m_IndexString == index;
+  //@ ensures m_SelectedIndex == -1;
+  public void setSingleIndex(/*@non_null@*/ String index) {
+
+    m_IndexString = index;
+    m_SelectedIndex = -1;
+  }
+
+  /**
+   * Constructs a representation of the current range. Being a string
+   * representation, the numbers are based from 1.
+   * 
+   * @return the string representation of the current range
+   */
+  //@ also signals (RuntimeException e) \old(m_Upper) < 0;
+  //@ ensures \result != null;
+  public /*@pure@*/ String toString() {
+
+    if (m_IndexString.equals("")) {
+      return "No index set";
+    }
+    if (m_Upper == -1) {
+      throw new RuntimeException("Upper limit has not been specified");
+    }
+    return m_IndexString;
+  }
+
+  /**
+   * Gets the selected index
+   *
+   * @return the selected index
+   * @throws RuntimeException if the upper limit of the index hasn't been defined
+   */
+  //@ requires m_Upper >= 0;
+  //@ requires m_IndexString.length() > 0;
+  //@ ensures \result == m_SelectedIndex;
+  public /*@pure@*/ int getIndex() {
+
+    if (m_IndexString.equals("")) {
+      throw new RuntimeException("No index set");
+    }
+    if (m_Upper == -1) {
+      throw new RuntimeException("No upper limit has been specified for index");
+    }
+    return m_SelectedIndex;
+  }
+
+  /**
+   * Creates a string representation of the given index.
+   *
+   * @param index the index to turn into a string.
+   * Since the index will typically come from a program, indices are assumed
+   * from 0, and thus will have 1 added in the String representation.
+   * @return the string representation
+   */
+  //@ requires index >= 0;
+  public static /*@pure non_null@*/ String indexToString(int index) {
+
+    return "" + (index + 1);
+  }
+
+  /**
+   * Translates a single string selection into it's internal 0-based equivalent
+   */
+  //@ assignable m_SelectedIndex, m_IndexString;
+  protected void setValue() {
+
+    if (m_IndexString.equals("")) {
+      throw new RuntimeException("No index set");
+    }
+    if (m_IndexString.toLowerCase().equals("first")) {
+      m_SelectedIndex = 0;
+    } else if (m_IndexString.toLowerCase().equals("last")) {
+      m_SelectedIndex = m_Upper;
+    } else {
+      m_SelectedIndex = Integer.parseInt(m_IndexString) - 1;
+      if (m_SelectedIndex < 0) {
+	m_IndexString = "";
+	throw new IllegalArgumentException("Index must be greater than zero");
+      }
+      if (m_SelectedIndex > m_Upper) {
+	m_IndexString = "";
+	throw new IllegalArgumentException("Index is too large");
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv one parameter: a test index specification
+   */
+  //@ requires \nonnullelements(argv);
+  public static void main(/*@non_null@*/ String [] argv) {
+
+    try {
+      if (argv.length == 0) {
+	throw new Exception("Usage: SingleIndex <indexspec>");
+      }
+      SingleIndex singleIndex = new SingleIndex();
+      singleIndex.setSingleIndex(argv[0]);
+      singleIndex.setUpper(9);
+      System.out.println("Input: " + argv[0] + "\n"
+			 + singleIndex.toString());
+      int selectedIndex = singleIndex.getIndex();
+      System.out.println(selectedIndex + "");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SparseInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SparseInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SparseInstance.java	(revision 29)
@@ -0,0 +1,782 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SparseInstance.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.ArrayList;
+
+/**
+ * Class for storing an instance as a sparse vector. A sparse instance
+ * only requires storage for those attribute values that are non-zero.
+ * Since the objective is to reduce storage requirements for datasets
+ * with large numbers of default values, this also includes nominal
+ * attributes -- the first nominal value (i.e. that which has index 0)
+ * will not require explicit storage, so rearrange your nominal attribute
+ * value orderings if necessary. Missing values will be stored
+ * explicitly.
+ *
+ * @author Eibe Frank
+ * @version $Revision: 5987 $
+ */
+public class SparseInstance extends AbstractInstance {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3579051291332630149L;
+
+  /** The index of the attribute associated with each stored value. */
+  protected int[] m_Indices;
+
+  /** The maximum number of values that can be stored. */
+  protected int m_NumAttributes;
+
+  /**
+   * Constructor that generates a sparse instance from the given
+   * instance. Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param instance the instance from which the attribute values
+   * and the weight are to be copied
+   */
+  public SparseInstance(Instance instance) {
+    
+    m_Weight = instance.weight();
+    m_Dataset = null;
+    m_NumAttributes = instance.numAttributes();
+    if (instance instanceof SparseInstance) {
+      m_AttValues = ((SparseInstance)instance).m_AttValues;
+      m_Indices = ((SparseInstance)instance).m_Indices;
+    } else {
+      double[] tempValues = new double[instance.numAttributes()];
+      int[] tempIndices = new int[instance.numAttributes()];
+      int vals = 0;
+      for (int i = 0; i < instance.numAttributes(); i++) {
+	if (instance.value(i) != 0) {
+	  tempValues[vals] = instance.value(i);
+	  tempIndices[vals] = i;
+	  vals++;
+	}
+      }
+      m_AttValues = new double[vals];
+      m_Indices = new int[vals];
+      System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
+      System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
+    }
+  }
+
+  /**
+   * Constructor that copies the info from the given instance. 
+   * Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param instance the instance from which the attribute
+   * info is to be copied 
+   */
+  public SparseInstance(SparseInstance instance) {
+    
+    m_AttValues = instance.m_AttValues;
+    m_Indices = instance.m_Indices;
+    m_Weight = instance.m_Weight;
+    m_NumAttributes = instance.m_NumAttributes;
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor that generates a sparse instance from the given
+   * parameters. Reference to the dataset is set to null.
+   * (ie. the instance doesn't have access to information about the
+   * attribute types)
+   *
+   * @param weight the instance's weight
+   * @param attValues a vector of attribute values 
+   */
+  public SparseInstance(double weight, double[] attValues) {
+    
+    m_Weight = weight;
+    m_Dataset = null;
+    m_NumAttributes = attValues.length;
+    double[] tempValues = new double[m_NumAttributes];
+    int[] tempIndices = new int[m_NumAttributes];
+    int vals = 0;
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (attValues[i] != 0) {
+	tempValues[vals] = attValues[i];
+	tempIndices[vals] = i;
+	vals++;
+      }
+    }
+    m_AttValues = new double[vals];
+    m_Indices = new int[vals];
+    System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
+    System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
+  }
+  
+  /**
+   * Constructor that inititalizes instance variable with given
+   * values. Reference to the dataset is set to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   * Note that the indices need to be sorted in ascending order. Otherwise
+   * things won't work properly.
+   *
+   * @param weight the instance's weight
+   * @param attValues a vector of attribute values (just the ones to be stored)
+   * @param indices the indices of the given values in the full vector (need to
+   * be sorted in ascending order)
+   * @param maxNumValues the maximium number of values that can be stored
+   */
+  public SparseInstance(double weight, double[] attValues,
+			int[] indices, int maxNumValues){
+    
+    int vals = 0; 
+    m_AttValues = new double [attValues.length];
+    m_Indices = new int [indices.length];
+    for (int i = 0; i < attValues.length; i++) {
+      if (attValues[i] != 0) {
+        m_AttValues[vals] = attValues[i];
+        m_Indices[vals] = indices[i];
+        vals++;
+      }
+    }
+    if (vals != attValues.length) {
+      // Need to truncate.
+      double [] newVals = new double[vals];
+      System.arraycopy(m_AttValues, 0, newVals, 0, vals);
+      m_AttValues = newVals;
+      int [] newIndices = new int[vals];
+      System.arraycopy(m_Indices, 0, newIndices, 0, vals);
+      m_Indices = newIndices;
+    }
+    m_Weight = weight;
+    m_NumAttributes = maxNumValues;
+    m_Dataset = null;
+  }
+
+  /**
+   * Constructor of an instance that sets weight to one, all values to
+   * be missing, and the reference to the dataset to null. (ie. the instance
+   * doesn't have access to information about the attribute types)
+   *
+   * @param numAttributes the size of the instance 
+   */
+  public SparseInstance(int numAttributes) {
+    
+    m_AttValues = new double[numAttributes];
+    m_NumAttributes = numAttributes;
+    m_Indices = new int[numAttributes];
+    for (int i = 0; i < m_AttValues.length; i++) {
+      m_AttValues[i] = Utils.missingValue();
+      m_Indices[i] = i;
+    }
+    m_Weight = 1;
+    m_Dataset = null;
+  }
+
+  /**
+   * Produces a shallow copy of this instance. The copy has
+   * access to the same dataset. (if you want to make a copy
+   * that doesn't have access to the dataset, use 
+   * <code>new SparseInstance(instance)</code>
+   *
+   * @return the shallow copy
+   */
+  public Object copy() {
+
+    SparseInstance result = new SparseInstance(this);
+    result.m_Dataset = m_Dataset;
+    return result;
+  }
+
+  /**
+   * Returns the index of the attribute stored at the given position.
+   *
+   * @param position the position 
+   * @return the index of the attribute stored at the given position
+   */
+  public int index(int position) {
+
+    return m_Indices[position];
+  }
+
+  /**
+   * Locates the greatest index that is not greater than the
+   * given index.
+   *
+   * @return the internal index of the attribute index. Returns
+   * -1 if no index with this property could be found
+   */
+  public int locateIndex(int index) {
+
+    int min = 0, max = m_Indices.length - 1;
+
+    if (max == -1) {
+      return -1;
+    }
+
+    // Binary search
+    while ((m_Indices[min] <= index) && (m_Indices[max] >= index)) {
+      int current = (max + min) / 2;
+      if (m_Indices[current] > index) {
+	max = current - 1;
+      } else if (m_Indices[current] < index) {
+	min = current + 1;
+      } else {
+	return current;
+      }
+    }
+    if (m_Indices[max] < index) {
+      return max;
+    } else {
+      return min - 1;
+    }
+  }
+
+  /**
+   * Merges this instance with the given instance and returns
+   * the result. Dataset is set to null.
+   *
+   * @param inst the instance to be merged with this one
+   * @return the merged instances
+   */
+  public Instance mergeInstance(Instance inst) {
+
+    double[] values = new double[numValues() + inst.numValues()];
+    int[] indices = new int[numValues() + inst.numValues()];
+
+    int m = 0;
+    for (int j = 0; j < numValues(); j++, m++) {
+      values[m] = valueSparse(j);
+      indices[m] = index(j);
+    }
+    for (int j = 0; j < inst.numValues(); j++, m++) {
+      values[m] = inst.valueSparse(j);
+      indices[m] = numAttributes() + inst.index(j);
+    }
+    
+    return new SparseInstance(1.0, values, indices, numAttributes() +
+			      inst.numAttributes());
+  }
+
+  /**
+   * Returns the number of attributes.
+   *
+   * @return the number of attributes as an integer
+   */
+  public int numAttributes() {
+
+    return m_NumAttributes;
+  }
+
+  /**
+   * Returns the number of values in the sparse vector.
+   *
+   * @return the number of values
+   */
+  public int numValues() {
+
+    return m_Indices.length;
+  }
+
+  /** 
+   * Replaces all missing values in the instance with the 
+   * values contained in the given array. A deep copy of
+   * the vector of attribute values is performed before the
+   * values are replaced.
+   *
+   * @param array containing the means and modes
+   * @exception IllegalArgumentException if numbers of attributes are unequal
+   */
+  public void replaceMissingValues(double[] array) {
+	 
+    if ((array == null) || (array.length != m_NumAttributes)) {
+      throw new IllegalArgumentException("Unequal number of attributes!");
+    }
+    double[] tempValues = new double[m_AttValues.length];
+    int[] tempIndices = new int[m_AttValues.length];
+    int vals = 0;
+    for (int i = 0; i < m_AttValues.length; i++) {
+      if (isMissingSparse(i)) {
+	if (array[m_Indices[i]] != 0) {
+	  tempValues[vals] = array[m_Indices[i]];
+	  tempIndices[vals] = m_Indices[i];
+	  vals++;
+	} 
+      } else {
+	tempValues[vals] = m_AttValues[i];
+	tempIndices[vals] = m_Indices[i];
+	vals++;
+      }
+    }
+    m_AttValues = new double[vals];
+    m_Indices = new int[vals];
+    System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
+    System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param attIndex the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValue(int attIndex, double value) {
+
+    int index = locateIndex(attIndex);
+    
+    if ((index >= 0) && (m_Indices[index] == attIndex)) {
+      if (value != 0) {
+	double[] tempValues = new double[m_AttValues.length];
+	System.arraycopy(m_AttValues, 0, tempValues, 0, m_AttValues.length);
+	tempValues[index] = value;
+	m_AttValues = tempValues;
+      } else {
+	double[] tempValues = new double[m_AttValues.length - 1];
+	int[] tempIndices = new int[m_Indices.length - 1];
+	System.arraycopy(m_AttValues, 0, tempValues, 0, index);
+	System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+	System.arraycopy(m_AttValues, index + 1, tempValues, index, 
+			 m_AttValues.length - index - 1);
+	System.arraycopy(m_Indices, index + 1, tempIndices, index, 
+			 m_Indices.length - index - 1);
+	m_AttValues = tempValues;
+	m_Indices = tempIndices;
+      }
+    } else {
+      if (value != 0) {
+	double[] tempValues = new double[m_AttValues.length + 1];
+	int[] tempIndices = new int[m_Indices.length + 1];
+	System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
+	System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+	tempIndices[index + 1] = attIndex;
+	tempValues[index + 1] = value;
+	System.arraycopy(m_AttValues, index + 1, tempValues, index + 2, 
+			 m_AttValues.length - index - 1);
+	System.arraycopy(m_Indices, index + 1, tempIndices, index + 2, 
+			 m_Indices.length - index - 1);
+	m_AttValues = tempValues;
+	m_Indices = tempIndices;
+      }
+    }
+  }
+
+  /**
+   * Sets a specific value in the instance to the given value 
+   * (internal floating-point format). Performs a deep copy
+   * of the vector of attribute values before the value is set.
+   *
+   * @param indexOfIndex the index of the attribute's index 
+   * @param value the new attribute value (If the corresponding
+   * attribute is nominal (or a string) then this is the new value's
+   * index as a double).  
+   */
+  public void setValueSparse(int indexOfIndex, double value) {
+
+    if (value != 0) {
+      double[] tempValues = new double[m_AttValues.length];
+      System.arraycopy(m_AttValues, 0, tempValues, 0, m_AttValues.length);
+      m_AttValues = tempValues;
+      m_AttValues[indexOfIndex] = value;
+    } else {
+      double[] tempValues = new double[m_AttValues.length - 1];
+      int[] tempIndices = new int[m_Indices.length - 1];
+      System.arraycopy(m_AttValues, 0, tempValues, 0, indexOfIndex);
+      System.arraycopy(m_Indices, 0, tempIndices, 0, indexOfIndex);
+      System.arraycopy(m_AttValues, indexOfIndex + 1, tempValues, indexOfIndex, 
+		       m_AttValues.length - indexOfIndex - 1);
+      System.arraycopy(m_Indices, indexOfIndex + 1, tempIndices, indexOfIndex, 
+		       m_Indices.length - indexOfIndex - 1);
+      m_AttValues = tempValues;
+      m_Indices = tempIndices;
+    }
+  }
+  
+  /**
+   * Returns the values of each attribute as an array of doubles.
+   *
+   * @return an array containing all the instance attribute values
+   */
+  public double[] toDoubleArray() {
+
+    double[] newValues = new double[m_NumAttributes];
+    for (int i = 0; i < m_AttValues.length; i++) {
+      newValues[m_Indices[i]] = m_AttValues[i];
+    }
+    return newValues;
+  }
+
+  /**
+   * Returns the description of one instance in sparse format. 
+   * If the instance doesn't have access to a dataset, it returns the 
+   * internal floating-point values. Quotes string values that contain 
+   * whitespace characters.
+   *
+   * @return the instance's description as a string
+   */
+  public String toStringNoWeight() {
+
+    StringBuffer text = new StringBuffer();
+    
+    text.append('{');
+    for (int i = 0; i < m_Indices.length; i++) {
+      if (i > 0) text.append(",");
+      if (isMissingSparse(i)) {
+	text.append(m_Indices[i] + " ?");
+      } else {
+	if (m_Dataset == null) {
+	  text.append(m_Indices[i] + " " + 
+		      Utils.doubleToString(m_AttValues[i],6));
+	} else {
+	  if (m_Dataset.attribute(m_Indices[i]).isNominal() || 
+	      m_Dataset.attribute(m_Indices[i]).isString() ||
+	      m_Dataset.attribute(m_Indices[i]).isDate()) {
+	    try {
+	      text.append(m_Indices[i] + " " +
+		  Utils.quote(stringValue(m_Indices[i])));
+	    } catch (Exception e) {
+              e.printStackTrace();
+              System.err.println(new Instances(m_Dataset, 0));
+              System.err.println("Att:" + m_Indices[i] + " Val:" + valueSparse(i));
+	      throw new Error("This should never happen!");
+	    }
+	  } else if (m_Dataset.attribute(m_Indices[i]).isRelationValued()) {
+	    try {
+	      text.append(m_Indices[i] + " " +
+			  Utils.quote(m_Dataset.attribute(m_Indices[i]).
+				      relation((int)valueSparse(i)).
+                                      stringWithoutHeader()));
+            } catch (Exception e) {
+              e.printStackTrace();
+              System.err.println(new Instances(m_Dataset, 0));
+              System.err.println("Att:" + m_Indices[i] + " Val:" + valueSparse(i));
+	      throw new Error("This should never happen!");
+	    }
+	  } else {
+	    text.append(m_Indices[i] + " " +
+			Utils.doubleToString(m_AttValues[i],6));
+	  }
+	}
+      }
+    }
+    text.append('}');
+
+    return text.toString();
+  }
+
+  /**
+   * Returns an instance's attribute value in internal format.
+   *
+   * @param attIndex the attribute's index
+   * @return the specified value as a double (If the corresponding
+   * attribute is nominal (or a string) then it returns the value's index as a 
+   * double).
+   */
+  public double value(int attIndex) {
+
+    int index = locateIndex(attIndex);
+    if ((index >= 0) && (m_Indices[index] == attIndex)) {
+      return m_AttValues[index];
+    } else {
+      return 0.0;
+    }
+  }  
+
+  /**
+   * Deletes an attribute at the given position (0 to 
+   * numAttributes() - 1).
+   *
+   * @param pos the attribute's position
+   */
+  protected void forceDeleteAttributeAt(int position) {
+
+    int index = locateIndex(position);
+
+    m_NumAttributes--;
+    if ((index >= 0) && (m_Indices[index] == position)) {
+      int[] tempIndices = new int[m_Indices.length - 1];
+      double[] tempValues = new double[m_AttValues.length - 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+      System.arraycopy(m_AttValues, 0, tempValues, 0, index);
+      for (int i = index; i < m_Indices.length - 1; i++) {
+	tempIndices[i] = m_Indices[i + 1] - 1;
+	tempValues[i] = m_AttValues[i + 1];
+      }
+      m_Indices = tempIndices;
+      m_AttValues = tempValues;
+    } else {
+      int[] tempIndices = new int[m_Indices.length];
+      double[] tempValues = new double[m_AttValues.length];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+      System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
+      for (int i = index + 1; i < m_Indices.length; i++) {
+	tempIndices[i] = m_Indices[i] - 1;
+	tempValues[i] = m_AttValues[i];
+      }
+      m_Indices = tempIndices;
+      m_AttValues = tempValues;
+    }
+  }
+
+  /**
+   * Inserts an attribute at the given position
+   * (0 to numAttributes()) and sets its value to be missing. 
+   *
+   * @param pos the attribute's position
+   */
+  protected void forceInsertAttributeAt(int position)  {
+
+    int index = locateIndex(position);
+
+    m_NumAttributes++;
+    if ((index >= 0) && (m_Indices[index] == position)) {
+      int[] tempIndices = new int[m_Indices.length + 1];
+      double[] tempValues = new double[m_AttValues.length + 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index);
+      System.arraycopy(m_AttValues, 0, tempValues, 0, index);
+      tempIndices[index] = position;
+      tempValues[index] = Utils.missingValue();
+      for (int i = index; i < m_Indices.length; i++) {
+	tempIndices[i + 1] = m_Indices[i] + 1;
+	tempValues[i + 1] = m_AttValues[i];
+      }
+      m_Indices = tempIndices;
+      m_AttValues = tempValues;
+    } else {
+      int[] tempIndices = new int[m_Indices.length + 1];
+      double[] tempValues = new double[m_AttValues.length + 1];
+      System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
+      System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
+      tempIndices[index + 1] = position;
+      tempValues[index + 1] = Utils.missingValue();
+      for (int i = index + 1; i < m_Indices.length; i++) {
+	tempIndices[i + 1] = m_Indices[i] + 1;
+	tempValues[i + 1] = m_AttValues[i];
+      }
+      m_Indices = tempIndices;
+      m_AttValues = tempValues;
+    }
+  }
+
+  /**
+   * Constructor for sub classes.
+   */
+  protected SparseInstance() {};
+
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] options) {
+
+    try {
+
+      // Create numeric attributes "length" and "weight"
+      Attribute length = new Attribute("length");
+      Attribute weight = new Attribute("weight");
+      
+      // Create vector to hold nominal values "first", "second", "third" 
+      ArrayList<String> my_nominal_values = new ArrayList<String>(3); 
+      my_nominal_values.add("first"); 
+      my_nominal_values.add("second"); 
+      my_nominal_values.add("third"); 
+      
+      // Create nominal attribute "position" 
+      Attribute position = new Attribute("position", my_nominal_values);
+      
+      // Create vector of the above attributes 
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>(3);
+      attributes.add(length);
+      attributes.add(weight);
+      attributes.add(position);
+      
+      // Create the empty dataset "race" with above attributes
+      Instances race = new Instances("race", attributes, 0);
+      
+      // Make position the class attribute
+      race.setClassIndex(position.index());
+      
+      // Create empty instance with three attribute values
+      SparseInstance inst = new SparseInstance(3);
+      
+      // Set instance's values for the attributes "length", "weight", and "position"
+      inst.setValue(length, 5.3);
+      inst.setValue(weight, 300);
+      inst.setValue(position, "first");
+      
+      // Set instance's dataset to be the dataset "race"
+      inst.setDataset(race);
+      
+      // Print the instance
+      System.out.println("The instance: " + inst);
+      
+      // Print the first attribute
+      System.out.println("First attribute: " + inst.attribute(0));
+      
+      // Print the class attribute
+      System.out.println("Class attribute: " + inst.classAttribute());
+      
+      // Print the class index
+      System.out.println("Class index: " + inst.classIndex());
+      
+      // Say if class is missing
+      System.out.println("Class is missing: " + inst.classIsMissing());
+      
+      // Print the instance's class value in internal format
+      System.out.println("Class value (internal format): " + inst.classValue());
+      
+      // Print a shallow copy of this instance
+      SparseInstance copy = (SparseInstance) inst.copy();
+      System.out.println("Shallow copy: " + copy);
+      
+      // Set dataset for shallow copy
+      copy.setDataset(inst.dataset());
+      System.out.println("Shallow copy with dataset set: " + copy);
+
+      // Print out all values in internal format
+      System.out.print("All stored values in internal format: ");
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Set all values to zero
+      System.out.print("All values set to zero: ");
+      while (inst.numValues() > 0) {
+	inst.setValueSparse(0, 0);
+      }
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Set all values to one
+      System.out.print("All values set to one: ");
+      for (int i = 0; i < inst.numAttributes(); i++) {
+	inst.setValue(i, 1);
+      }
+      for (int i = 0; i < inst.numValues(); i++) {
+	if (i > 0) {
+	  System.out.print(",");
+	}
+	System.out.print(inst.valueSparse(i));
+      }
+      System.out.println();
+
+      // Unset dataset for copy, delete first attribute, and insert it again
+      copy.setDataset(null);
+      copy.deleteAttributeAt(0);
+      copy.insertAttributeAt(0);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with first attribute deleted and inserted: " + copy); 
+
+      // Same for second attribute
+      copy.setDataset(null);
+      copy.deleteAttributeAt(1);
+      copy.insertAttributeAt(1);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with second attribute deleted and inserted: " + copy); 
+
+      // Same for last attribute
+      copy.setDataset(null);
+      copy.deleteAttributeAt(2);
+      copy.insertAttributeAt(2);
+      copy.setDataset(inst.dataset());
+      System.out.println("Copy with third attribute deleted and inserted: " + copy); 
+      
+      // Enumerate attributes (leaving out the class attribute)
+      System.out.println("Enumerating attributes (leaving out class):");
+      Enumeration enu = inst.enumerateAttributes();
+      while (enu.hasMoreElements()) {
+	Attribute att = (Attribute) enu.nextElement();
+	System.out.println(att);
+      }
+      
+      // Headers are equivalent?
+      System.out.println("Header of original and copy equivalent: " +
+			 inst.equalHeaders(copy));
+
+      // Test for missing values
+      System.out.println("Length of copy missing: " + copy.isMissing(length));
+      System.out.println("Weight of copy missing: " + copy.isMissing(weight.index()));
+      System.out.println("Length of copy missing: " + 
+			 Utils.isMissingValue(copy.value(length)));
+
+      // Prints number of attributes and classes
+      System.out.println("Number of attributes: " + copy.numAttributes());
+      System.out.println("Number of classes: " + copy.numClasses());
+
+      // Replace missing values
+      double[] meansAndModes = {2, 3, 0};
+      copy.replaceMissingValues(meansAndModes);
+      System.out.println("Copy with missing value replaced: " + copy);
+
+      // Setting and getting values and weights
+      copy.setClassMissing();
+      System.out.println("Copy with missing class: " + copy);
+      copy.setClassValue(0);
+      System.out.println("Copy with class value set to first value: " + copy);
+      copy.setClassValue("third");
+      System.out.println("Copy with class value set to \"third\": " + copy);
+      copy.setMissing(1);
+      System.out.println("Copy with second attribute set to be missing: " + copy);
+      copy.setMissing(length);
+      System.out.println("Copy with length set to be missing: " + copy);
+      copy.setValue(0, 0);
+      System.out.println("Copy with first attribute set to 0: " + copy);
+      copy.setValue(weight, 1);
+      System.out.println("Copy with weight attribute set to 1: " + copy);
+      copy.setValue(position, "second");
+      System.out.println("Copy with position set to \"second\": " + copy);
+      copy.setValue(2, "first");
+      System.out.println("Copy with last attribute set to \"first\": " + copy);
+      System.out.println("Current weight of instance copy: " + copy.weight());
+      copy.setWeight(2);
+      System.out.println("Current weight of instance copy (set to 2): " + copy.weight());
+      System.out.println("Last value of copy: " + copy.toString(2));
+      System.out.println("Value of position for copy: " + copy.toString(position));
+      System.out.println("Last value of copy (internal format): " + copy.value(2));
+      System.out.println("Value of position for copy (internal format): " + 
+			 copy.value(position));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/SpecialFunctions.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SpecialFunctions.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SpecialFunctions.java	(revision 29)
@@ -0,0 +1,111 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SpecialFunctions.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.lang.Math;
+
+/**
+ * Class implementing some mathematical functions.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public final class SpecialFunctions
+  implements RevisionHandler {
+
+  /** Some constants */
+  private static double log2 = Math.log(2);
+
+  /**
+   * Returns natural logarithm of factorial using gamma function.
+   *
+   * @param x the value
+   * @return natural logarithm of factorial
+   */
+  public static double lnFactorial(double x){
+
+    return Statistics.lnGamma(x+1);
+  }
+
+  /**
+   * Returns base 2 logarithm of binomial coefficient using gamma function.
+   *
+   * @param a upper part of binomial coefficient
+   * @param b lower part
+   * @return the base 2 logarithm of the binominal coefficient a over b
+   */
+  public static double log2Binomial(double a, double b) {
+    
+    if (Utils.gr(b,a)) {
+      throw new ArithmeticException("Can't compute binomial coefficient.");
+    }
+    return (lnFactorial(a)-lnFactorial(b)-lnFactorial(a-b))/log2;
+  }
+
+  /**
+   * Returns base 2 logarithm of multinomial using gamma function.
+   *
+   * @param a upper part of multinomial coefficient
+   * @param bs lower part
+   * @return multinomial coefficient of a over the bs
+   */
+  public static double log2Multinomial(double a, double[] bs)
+       {
+    
+    double sum = 0;
+    int i;
+    
+    for (i=0;i<bs.length;i++) {
+      if (Utils.gr(bs[i],a)) {
+	throw 
+	  new ArithmeticException("Can't compute multinomial coefficient.");
+      } else {
+	sum = sum+lnFactorial(bs[i]);
+      }
+    }
+    return (lnFactorial(a)-sum)/log2;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] ops) {
+
+    double[] doubles = {1, 2, 3};
+
+    System.out.println("6!: " + Math.exp(SpecialFunctions.lnFactorial(6)));
+    System.out.println("Binomial 6 over 2: " +
+		       Math.pow(2, SpecialFunctions.log2Binomial(6, 2)));
+    System.out.println("Multinomial 6 over 1, 2, 3: " +
+		       Math.pow(2, SpecialFunctions.log2Multinomial(6, doubles)));
+  }    
+}
Index: branches/MetisMQI/src/main/java/weka/core/Statistics.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Statistics.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Statistics.java	(revision 29)
@@ -0,0 +1,1069 @@
+package weka.core;
+
+/**
+ * Class implementing some distributions, tests, etc. The code is mostly adapted from the CERN
+ * Jet Java libraries:
+ * 
+ * Copyright 2001 University of Waikato
+ * Copyright 1999 CERN - European Organization for Nuclear Research.
+ * Permission to use, copy, modify, distribute and sell this software and its documentation for
+ * any purpose is hereby granted without fee, provided that the above copyright notice appear
+ * in all copies and that both that copyright notice and this permission notice appear in
+ * supporting documentation. 
+ * CERN and the University of Waikato make no representations about the suitability of this 
+ * software for any purpose. It is provided "as is" without expressed or implied warranty.
+ *
+ * @author peter.gedeck@pharma.Novartis.com
+ * @author wolfgang.hoschek@cern.ch
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5616 $
+ */
+public class Statistics
+  implements RevisionHandler {
+
+  /** Some constants */
+  protected static final double MACHEP =  1.11022302462515654042E-16;
+  protected static final double MAXLOG =  7.09782712893383996732E2;
+  protected static final double MINLOG = -7.451332191019412076235E2;
+  protected static final double MAXGAM = 171.624376956302725;
+  protected static final double SQTPI  =  2.50662827463100050242E0;
+  protected static final double SQRTH  =  7.07106781186547524401E-1;
+  protected static final double LOGPI  =  1.14472988584940017414;
+  
+  protected static final double big    =  4.503599627370496e15;
+  protected static final double biginv =  2.22044604925031308085e-16;
+
+  /*************************************************
+   *    COEFFICIENTS FOR METHOD  normalInverse()   *
+   *************************************************/
+  /* approximation for 0 <= |y - 0.5| <= 3/8 */
+  protected static final double P0[] = {
+    -5.99633501014107895267E1,
+    9.80010754185999661536E1,
+    -5.66762857469070293439E1,
+    1.39312609387279679503E1,
+    -1.23916583867381258016E0,
+  };
+  protected static final double Q0[] = {
+    /* 1.00000000000000000000E0,*/
+    1.95448858338141759834E0,
+    4.67627912898881538453E0,
+    8.63602421390890590575E1,
+    -2.25462687854119370527E2,
+    2.00260212380060660359E2,
+    -8.20372256168333339912E1,
+    1.59056225126211695515E1,
+    -1.18331621121330003142E0,
+  };
+  
+  /* Approximation for interval z = sqrt(-2 log y ) between 2 and 8
+   * i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14.
+   */
+  protected static final double P1[] = {
+    4.05544892305962419923E0,
+    3.15251094599893866154E1,
+    5.71628192246421288162E1,
+    4.40805073893200834700E1,
+    1.46849561928858024014E1,
+    2.18663306850790267539E0,
+    -1.40256079171354495875E-1,
+    -3.50424626827848203418E-2,
+    -8.57456785154685413611E-4,
+  };
+  protected static final double Q1[] = {
+    /*  1.00000000000000000000E0,*/
+    1.57799883256466749731E1,
+    4.53907635128879210584E1,
+    4.13172038254672030440E1,
+    1.50425385692907503408E1,
+    2.50464946208309415979E0,
+    -1.42182922854787788574E-1,
+    -3.80806407691578277194E-2,
+    -9.33259480895457427372E-4,
+  };
+  
+  /* Approximation for interval z = sqrt(-2 log y ) between 8 and 64
+   * i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890.
+   */
+  protected static final double  P2[] = {
+    3.23774891776946035970E0,
+    6.91522889068984211695E0,
+    3.93881025292474443415E0,
+    1.33303460815807542389E0,
+    2.01485389549179081538E-1,
+    1.23716634817820021358E-2,
+    3.01581553508235416007E-4,
+    2.65806974686737550832E-6,
+    6.23974539184983293730E-9,
+  };
+  protected static final double  Q2[] = {
+    /*  1.00000000000000000000E0,*/
+    6.02427039364742014255E0,
+    3.67983563856160859403E0,
+    1.37702099489081330271E0,
+    2.16236993594496635890E-1,
+    1.34204006088543189037E-2,
+    3.28014464682127739104E-4,
+    2.89247864745380683936E-6,
+    6.79019408009981274425E-9,
+  };
+  
+  /**
+   * Computes standard error for observed values of a binomial
+   * random variable.
+   *
+   * @param p the probability of success
+   * @param n the size of the sample
+   * @return the standard error
+   */
+  public static double binomialStandardError(double p, int n) {
+    
+    if (n == 0) {
+      return 0; 
+    }
+    return Math.sqrt((p*(1-p))/(double) n);
+  }
+  
+  /**
+   * Returns chi-squared probability for given value and degrees
+   * of freedom. (The probability that the chi-squared variate
+   * will be greater than x for the given degrees of freedom.)
+   *
+   * @param x the value
+   * @param v the number of degrees of freedom
+   * @return the chi-squared probability
+   */
+  public static double chiSquaredProbability(double x, double v) { 
+
+    if( x < 0.0 || v < 1.0 ) return 0.0;
+    return incompleteGammaComplement( v/2.0, x/2.0 );
+  }
+
+  /**
+   * Computes probability of F-ratio.
+   *
+   * @param F the F-ratio
+   * @param df1 the first number of degrees of freedom
+   * @param df2 the second number of degrees of freedom
+   * @return the probability of the F-ratio.
+   */
+  public static double FProbability(double F, int df1, int df2) {
+    
+    return incompleteBeta( df2/2.0, df1/2.0, df2/(df2+df1*F) );
+  }
+
+  /**
+   * Returns the area under the Normal (Gaussian) probability density
+   * function, integrated from minus infinity to <tt>x</tt>
+   * (assumes mean is zero, variance is one).
+   * <pre>
+   *                            x
+   *                             -
+   *                   1        | |          2
+   *  normal(x)  = ---------    |    exp( - t /2 ) dt
+   *               sqrt(2pi)  | |
+   *                           -
+   *                          -inf.
+   *
+   *             =  ( 1 + erf(z) ) / 2
+   *             =  erfc(z) / 2
+   * </pre>
+   * where <tt>z = x/sqrt(2)</tt>.
+   * Computation is via the functions <tt>errorFunction</tt> and <tt>errorFunctionComplement</tt>.
+   *
+   * @param a the z-value
+   * @return the probability of the z value according to the normal pdf
+   */
+  public static double normalProbability(double a) { 
+
+    double x, y, z;
+ 
+    x = a * SQRTH;
+    z = Math.abs(x);
+ 
+    if( z < SQRTH ) y = 0.5 + 0.5 * errorFunction(x);
+    else {
+      y = 0.5 * errorFunctionComplemented(z);
+      if( x > 0 )  y = 1.0 - y;
+    } 
+    return y;
+  }
+
+  /**
+   * Returns the value, <tt>x</tt>, for which the area under the
+   * Normal (Gaussian) probability density function (integrated from
+   * minus infinity to <tt>x</tt>) is equal to the argument <tt>y</tt>
+   * (assumes mean is zero, variance is one).
+   * <p>
+   * For small arguments <tt>0 < y < exp(-2)</tt>, the program computes
+   * <tt>z = sqrt( -2.0 * log(y) )</tt>;  then the approximation is
+   * <tt>x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z)</tt>.
+   * There are two rational functions P/Q, one for <tt>0 < y < exp(-32)</tt>
+   * and the other for <tt>y</tt> up to <tt>exp(-2)</tt>. 
+   * For larger arguments,
+   * <tt>w = y - 0.5</tt>, and  <tt>x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2))</tt>.
+   *
+   * @param y0 the area under the normal pdf
+   * @return the z-value
+   */
+  public static double normalInverse(double y0) { 
+
+    double x, y, z, y2, x0, x1;
+    int code;
+
+    final double s2pi = Math.sqrt(2.0*Math.PI);
+
+    if( y0 <= 0.0 ) throw new IllegalArgumentException();
+    if( y0 >= 1.0 ) throw new IllegalArgumentException();
+    code = 1;
+    y = y0;
+    if( y > (1.0 - 0.13533528323661269189) ) { /* 0.135... = exp(-2) */
+      y = 1.0 - y;
+      code = 0;
+    }
+
+    if( y > 0.13533528323661269189 ) {
+      y = y - 0.5;
+      y2 = y * y;
+      x = y + y * (y2 * polevl( y2, P0, 4)/p1evl( y2, Q0, 8 ));
+      x = x * s2pi; 
+      return(x);
+    }
+
+    x = Math.sqrt( -2.0 * Math.log(y) );
+    x0 = x - Math.log(x)/x;
+
+    z = 1.0/x;
+    if( x < 8.0 ) /* y > exp(-32) = 1.2664165549e-14 */
+      x1 = z * polevl( z, P1, 8 )/p1evl( z, Q1, 8 );
+    else
+      x1 = z * polevl( z, P2, 8 )/p1evl( z, Q2, 8 );
+    x = x0 - x1;
+    if( code != 0 )
+      x = -x;
+    return( x );
+  }
+
+  /**
+   * Returns natural logarithm of gamma function.
+   *
+   * @param x the value
+   * @return natural logarithm of gamma function
+   */
+  public static double lnGamma(double x) {
+
+    double p, q, w, z;
+ 
+    double A[] = {
+      8.11614167470508450300E-4,
+      -5.95061904284301438324E-4,
+      7.93650340457716943945E-4,
+      -2.77777777730099687205E-3,
+      8.33333333333331927722E-2
+    };
+    double B[] = {
+      -1.37825152569120859100E3,
+      -3.88016315134637840924E4,
+      -3.31612992738871184744E5,
+      -1.16237097492762307383E6,
+      -1.72173700820839662146E6,
+      -8.53555664245765465627E5
+    };
+    double C[] = {
+      /* 1.00000000000000000000E0, */
+      -3.51815701436523470549E2,
+      -1.70642106651881159223E4,
+      -2.20528590553854454839E5,
+      -1.13933444367982507207E6,
+      -2.53252307177582951285E6,
+      -2.01889141433532773231E6
+    };
+ 
+    if( x < -34.0 ) {
+      q = -x;
+      w = lnGamma(q);
+      p = Math.floor(q);
+      if( p == q ) throw new ArithmeticException("lnGamma: Overflow");
+      z = q - p;
+      if( z > 0.5 ) {
+	p += 1.0;
+	z = p - q;
+      }
+      z = q * Math.sin( Math.PI * z );
+      if( z == 0.0 ) throw new 
+	ArithmeticException("lnGamma: Overflow");
+      z = LOGPI - Math.log( z ) - w;
+      return z;
+    }
+ 
+    if( x < 13.0 ) {
+      z = 1.0;
+      while( x >= 3.0 ) {
+	x -= 1.0;
+	z *= x;
+      }
+      while( x < 2.0 ) {
+	if( x == 0.0 ) throw new 
+	  ArithmeticException("lnGamma: Overflow");
+	z /= x;
+	x += 1.0;
+      }
+      if( z < 0.0 ) z = -z;
+      if( x == 2.0 ) return Math.log(z);
+      x -= 2.0;
+      p = x * polevl( x, B, 5 ) / p1evl( x, C, 6);
+      return( Math.log(z) + p );
+    }
+ 
+    if( x > 2.556348e305 ) throw new ArithmeticException("lnGamma: Overflow");
+ 
+    q = ( x - 0.5 ) * Math.log(x) - x + 0.91893853320467274178;
+  
+    if( x > 1.0e8 ) return( q );
+ 
+    p = 1.0/(x*x);
+    if( x >= 1000.0 )
+      q += ((   7.9365079365079365079365e-4 * p
+		- 2.7777777777777777777778e-3) *p
+	    + 0.0833333333333333333333) / x;
+    else
+      q += polevl( p, A, 4 ) / x;
+    return q;
+  }
+
+  /**
+   * Returns the error function of the normal distribution.
+   * The integral is
+   * <pre>
+   *                           x 
+   *                            -
+   *                 2         | |          2
+   *   erf(x)  =  --------     |    exp( - t  ) dt.
+   *              sqrt(pi)   | |
+   *                          -
+   *                           0
+   * </pre>
+   * <b>Implementation:</b>
+   * For <tt>0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2)</tt>; otherwise
+   * <tt>erf(x) = 1 - erfc(x)</tt>.
+   * <p>
+   * Code adapted from the <A HREF="http://www.sci.usq.edu.au/staff/leighb/graph/Top.html">
+   * Java 2D Graph Package 2.4</A>,
+   * which in turn is a port from the
+   * <A HREF="http://people.ne.mediaone.net/moshier/index.html#Cephes">Cephes 2.2</A>
+   * Math Library (C).
+   *
+   * @param a the argument to the function.
+   */
+  public static double errorFunction(double x) { 
+    double y, z;
+    final double T[] = {
+      9.60497373987051638749E0,
+      9.00260197203842689217E1,
+      2.23200534594684319226E3,
+      7.00332514112805075473E3,
+      5.55923013010394962768E4
+    };
+    final double U[] = {
+      //1.00000000000000000000E0,
+      3.35617141647503099647E1,
+      5.21357949780152679795E2,
+      4.59432382970980127987E3,
+      2.26290000613890934246E4,
+      4.92673942608635921086E4
+    };
+  
+    if( Math.abs(x) > 1.0 ) return( 1.0 - errorFunctionComplemented(x) );
+    z = x * x;
+    y = x * polevl( z, T, 4 ) / p1evl( z, U, 5 );
+    return y;
+  }
+
+  /**
+   * Returns the complementary Error function of the normal distribution.
+   * <pre>
+   *  1 - erf(x) =
+   *
+   *                           inf. 
+   *                             -
+   *                  2         | |          2
+   *   erfc(x)  =  --------     |    exp( - t  ) dt
+   *               sqrt(pi)   | |
+   *                           -
+   *                            x
+   * </pre>
+   * <b>Implementation:</b>
+   * For small x, <tt>erfc(x) = 1 - erf(x)</tt>; otherwise rational
+   * approximations are computed.
+   * <p>
+   * Code adapted from the <A HREF="http://www.sci.usq.edu.au/staff/leighb/graph/Top.html">
+   * Java 2D Graph Package 2.4</A>,
+   * which in turn is a port from the
+   * <A HREF="http://people.ne.mediaone.net/moshier/index.html#Cephes">Cephes 2.2</A>
+   * Math Library (C).
+   *
+   * @param a the argument to the function.
+   */
+  public static double errorFunctionComplemented(double a) { 
+    double x,y,z,p,q;
+  
+    double P[] = {
+      2.46196981473530512524E-10,
+      5.64189564831068821977E-1,
+      7.46321056442269912687E0,
+      4.86371970985681366614E1,
+      1.96520832956077098242E2,
+      5.26445194995477358631E2,
+      9.34528527171957607540E2,
+      1.02755188689515710272E3,
+      5.57535335369399327526E2
+    };
+    double Q[] = {
+      //1.0
+      1.32281951154744992508E1,
+      8.67072140885989742329E1,
+      3.54937778887819891062E2,
+      9.75708501743205489753E2,
+      1.82390916687909736289E3,
+      2.24633760818710981792E3,
+      1.65666309194161350182E3,
+      5.57535340817727675546E2
+    };
+  
+    double R[] = {
+      5.64189583547755073984E-1,
+      1.27536670759978104416E0,
+      5.01905042251180477414E0,
+      6.16021097993053585195E0,
+      7.40974269950448939160E0,
+      2.97886665372100240670E0
+    };
+    double S[] = {
+      //1.00000000000000000000E0, 
+      2.26052863220117276590E0,
+      9.39603524938001434673E0,
+      1.20489539808096656605E1,
+      1.70814450747565897222E1,
+      9.60896809063285878198E0,
+      3.36907645100081516050E0
+    };
+  
+    if( a < 0.0 )   x = -a;
+    else            x = a;
+  
+    if( x < 1.0 )   return 1.0 - errorFunction(a);
+  
+    z = -a * a;
+  
+    if( z < -MAXLOG ) {
+      if( a < 0 )  return( 2.0 );
+      else         return( 0.0 );
+    }
+  
+    z = Math.exp(z);
+  
+    if( x < 8.0 ) {
+      p = polevl( x, P, 8 );
+      q = p1evl( x, Q, 8 );
+    } else {
+      p = polevl( x, R, 5 );
+      q = p1evl( x, S, 6 );
+    }
+  
+    y = (z * p)/q;
+  
+    if( a < 0 ) y = 2.0 - y;
+  
+    if( y == 0.0 ) {
+      if( a < 0 ) return 2.0;
+      else        return( 0.0 );
+    }
+    return y;
+  }
+  
+  /**
+   * Evaluates the given polynomial of degree <tt>N</tt> at <tt>x</tt>.
+   * Evaluates polynomial when coefficient of N is 1.0.
+   * Otherwise same as <tt>polevl()</tt>.
+   * <pre>
+   *                     2          N
+   * y  =  C  + C x + C x  +...+ C x
+   *        0    1     2          N
+   *
+   * Coefficients are stored in reverse order:
+   *
+   * coef[0] = C  , ..., coef[N] = C  .
+   *            N                   0
+   * </pre>
+   * The function <tt>p1evl()</tt> assumes that <tt>coef[N] = 1.0</tt> and is
+   * omitted from the array.  Its calling arguments are
+   * otherwise the same as <tt>polevl()</tt>.
+   * <p>
+   * In the interest of speed, there are no checks for out of bounds arithmetic.
+   *
+   * @param x argument to the polynomial.
+   * @param coef the coefficients of the polynomial.
+   * @param N the degree of the polynomial.
+   */
+  public static double p1evl( double x, double coef[], int N ) {
+  
+    double ans;
+    ans = x + coef[0];
+  
+    for(int i=1; i<N; i++) ans = ans*x+coef[i];
+  
+    return ans;
+  }
+
+  /**
+   * Evaluates the given polynomial of degree <tt>N</tt> at <tt>x</tt>.
+   * <pre>
+   *                     2          N
+   * y  =  C  + C x + C x  +...+ C x
+   *        0    1     2          N
+   *
+   * Coefficients are stored in reverse order:
+   *
+   * coef[0] = C  , ..., coef[N] = C  .
+   *            N                   0
+   * </pre>
+   * In the interest of speed, there are no checks for out of bounds arithmetic.
+   *
+   * @param x argument to the polynomial.
+   * @param coef the coefficients of the polynomial.
+   * @param N the degree of the polynomial.
+   */
+  public static double polevl( double x, double coef[], int N ) {
+
+    double ans;
+    ans = coef[0];
+  
+    for(int i=1; i<=N; i++) ans = ans*x+coef[i];
+  
+    return ans;
+  }
+
+  /**
+   * Returns the Incomplete Gamma function.
+   * @param a the parameter of the gamma distribution.
+   * @param x the integration end point.
+   */
+  public static double incompleteGamma(double a, double x) 
+    { 
+ 
+    double ans, ax, c, r;
+ 
+    if( x <= 0 || a <= 0 ) return 0.0;
+ 
+    if( x > 1.0 && x > a ) return 1.0 - incompleteGammaComplement(a,x);
+
+    /* Compute  x**a * exp(-x) / gamma(a)  */
+    ax = a * Math.log(x) - x - lnGamma(a);
+    if( ax < -MAXLOG ) return( 0.0 );
+
+    ax = Math.exp(ax);
+
+    /* power series */
+    r = a;
+    c = 1.0;
+    ans = 1.0;
+
+    do {
+      r += 1.0;
+      c *= x/r;
+      ans += c;
+    }
+    while( c/ans > MACHEP );
+ 
+    return( ans * ax/a );
+  }
+
+  /**
+   * Returns the Complemented Incomplete Gamma function.
+   * @param a the parameter of the gamma distribution.
+   * @param x the integration start point.
+   */
+  public static double incompleteGammaComplement( double a, double x ) {
+
+    double ans, ax, c, yc, r, t, y, z;
+    double pk, pkm1, pkm2, qk, qkm1, qkm2;
+
+    if( x <= 0 || a <= 0 ) return 1.0;
+ 
+    if( x < 1.0 || x < a ) return 1.0 - incompleteGamma(a,x);
+ 
+    ax = a * Math.log(x) - x - lnGamma(a);
+    if( ax < -MAXLOG ) return 0.0;
+ 
+    ax = Math.exp(ax);
+ 
+    /* continued fraction */
+    y = 1.0 - a;
+    z = x + y + 1.0;
+    c = 0.0;
+    pkm2 = 1.0;
+    qkm2 = x;
+    pkm1 = x + 1.0;
+    qkm1 = z * x;
+    ans = pkm1/qkm1;
+ 
+    do {
+      c += 1.0;
+      y += 1.0;
+      z += 2.0;
+      yc = y * c;
+      pk = pkm1 * z  -  pkm2 * yc;
+      qk = qkm1 * z  -  qkm2 * yc;
+      if( qk != 0 ) {
+	r = pk/qk;
+        t = Math.abs( (ans - r)/r );
+	ans = r;
+      } else
+	t = 1.0;
+
+      pkm2 = pkm1;
+      pkm1 = pk;
+      qkm2 = qkm1;
+      qkm1 = qk;
+      if( Math.abs(pk) > big ) {
+	pkm2 *= biginv;
+        pkm1 *= biginv;
+	qkm2 *= biginv;
+	qkm1 *= biginv;
+      }
+    } while( t > MACHEP );
+ 
+    return ans * ax;
+  }
+
+  /**
+   * Returns the Gamma function of the argument.
+   */
+  public static double gamma(double x) {
+
+    double P[] = {
+      1.60119522476751861407E-4,
+      1.19135147006586384913E-3,
+      1.04213797561761569935E-2,
+      4.76367800457137231464E-2,
+      2.07448227648435975150E-1,
+      4.94214826801497100753E-1,
+      9.99999999999999996796E-1
+    };
+    double Q[] = {
+      -2.31581873324120129819E-5,
+      5.39605580493303397842E-4,
+      -4.45641913851797240494E-3,
+      1.18139785222060435552E-2,
+      3.58236398605498653373E-2,
+      -2.34591795718243348568E-1,
+      7.14304917030273074085E-2,
+      1.00000000000000000320E0
+    };
+
+    double p, z;
+    int i;
+
+    double q = Math.abs(x);
+
+    if( q > 33.0 ) {
+      if( x < 0.0 ) {
+	p = Math.floor(q);
+	if( p == q ) throw new ArithmeticException("gamma: overflow");
+	i = (int)p;
+	z = q - p;
+	if( z > 0.5 ) {
+	  p += 1.0;
+	  z = q - p;
+	}
+	z = q * Math.sin( Math.PI * z );
+	if( z == 0.0 ) throw new ArithmeticException("gamma: overflow");
+	z = Math.abs(z);
+	z = Math.PI/(z * stirlingFormula(q) );
+
+	return -z;
+      } else {
+	return stirlingFormula(x);
+      }
+    }
+
+    z = 1.0;
+    while( x >= 3.0 ) {
+      x -= 1.0;
+      z *= x;
+    }
+
+    while( x < 0.0 ) {
+      if( x == 0.0 ) {
+	throw new ArithmeticException("gamma: singular");
+      } else
+	if( x > -1.E-9 ) {
+	  return( z/((1.0 + 0.5772156649015329 * x) * x) );
+	}
+      z /= x;
+      x += 1.0;
+    }
+
+    while( x < 2.0 ) {
+      if( x == 0.0 ) {
+	throw new ArithmeticException("gamma: singular");
+      } else
+	if( x < 1.e-9 ) {
+	  return( z/((1.0 + 0.5772156649015329 * x) * x) );
+	}
+      z /= x;
+      x += 1.0;
+    }
+
+    if( (x == 2.0) || (x == 3.0) ) 	return z;
+
+    x -= 2.0;
+    p = polevl( x, P, 6 );
+    q = polevl( x, Q, 7 );
+    return  z * p / q;
+  }
+
+  /**
+   * Returns the Gamma function computed by Stirling's formula.
+   * The polynomial STIR is valid for 33 <= x <= 172.
+   */
+  public static double stirlingFormula(double x) {
+
+    double STIR[] = {
+      7.87311395793093628397E-4,
+      -2.29549961613378126380E-4,
+      -2.68132617805781232825E-3,
+      3.47222221605458667310E-3,
+      8.33333333333482257126E-2,
+    };
+    double MAXSTIR = 143.01608;
+
+    double w = 1.0/x;
+    double  y = Math.exp(x);
+
+    w = 1.0 + w * polevl( w, STIR, 4 );
+
+    if( x > MAXSTIR ) {
+      /* Avoid overflow in Math.pow() */
+      double v = Math.pow( x, 0.5 * x - 0.25 );
+      y = v * (v / y);
+    } else {
+      y = Math.pow( x, x - 0.5 ) / y;
+    }
+    y = SQTPI * y * w;
+    return y;
+  }
+
+  /**
+   * Returns the Incomplete Beta Function evaluated from zero to <tt>xx</tt>.
+   *
+   * @param aa the alpha parameter of the beta distribution.
+   * @param bb the beta parameter of the beta distribution.
+   * @param xx the integration end point.
+   */
+  public static double incompleteBeta( double aa, double bb, double xx ) {
+
+    double a, b, t, x, xc, w, y;
+    boolean flag;
+
+    if( aa <= 0.0 || bb <= 0.0 ) throw new 
+      ArithmeticException("ibeta: Domain error!");
+
+    if( (xx <= 0.0) || ( xx >= 1.0) ) {
+      if( xx == 0.0 ) return 0.0;
+      if( xx == 1.0 ) return 1.0;
+      throw new ArithmeticException("ibeta: Domain error!");
+    }
+
+    flag = false;
+    if( (bb * xx) <= 1.0 && xx <= 0.95) {
+      t = powerSeries(aa, bb, xx);
+      return t;
+    }
+
+    w = 1.0 - xx;
+
+    /* Reverse a and b if x is greater than the mean. */
+    if( xx > (aa/(aa+bb)) ) {
+      flag = true;
+      a = bb;
+      b = aa;
+      xc = xx;
+      x = w;
+    } else {
+      a = aa;
+      b = bb;
+      xc = w;
+      x = xx;
+    }
+
+    if( flag  && (b * x) <= 1.0 && x <= 0.95) {
+      t = powerSeries(a, b, x);
+      if( t <= MACHEP ) 	t = 1.0 - MACHEP;
+      else  		        t = 1.0 - t;
+      return t;
+    }
+
+    /* Choose expansion for better convergence. */
+    y = x * (a+b-2.0) - (a-1.0);
+    if( y < 0.0 )
+      w = incompleteBetaFraction1( a, b, x );
+    else
+      w = incompleteBetaFraction2( a, b, x ) / xc;
+
+    /* Multiply w by the factor
+       a      b   _             _     _
+       x  (1-x)   | (a+b) / ( a | (a) | (b) ) .   */
+
+    y = a * Math.log(x);
+    t = b * Math.log(xc);
+    if( (a+b) < MAXGAM && Math.abs(y) < MAXLOG && Math.abs(t) < MAXLOG ) {
+      t = Math.pow(xc,b);
+      t *= Math.pow(x,a);
+      t /= a;
+      t *= w;
+      t *= gamma(a+b) / (gamma(a) * gamma(b));
+      if( flag ) {
+	if( t <= MACHEP ) 	t = 1.0 - MACHEP;
+	else  		        t = 1.0 - t;
+      }
+      return t;
+    }
+    /* Resort to logarithms.  */
+    y += t + lnGamma(a+b) - lnGamma(a) - lnGamma(b);
+    y += Math.log(w/a);
+    if( y < MINLOG )
+      t = 0.0;
+    else
+      t = Math.exp(y);
+
+    if( flag ) {
+      if( t <= MACHEP ) 	t = 1.0 - MACHEP;
+      else  		        t = 1.0 - t;
+    }
+    return t;
+  }   
+
+  /**
+   * Continued fraction expansion #1 for incomplete beta integral.
+   */
+  public static double incompleteBetaFraction1( double a, double b, double x ) {
+
+    double xk, pk, pkm1, pkm2, qk, qkm1, qkm2;
+    double k1, k2, k3, k4, k5, k6, k7, k8;
+    double r, t, ans, thresh;
+    int n;
+
+    k1 = a;
+    k2 = a + b;
+    k3 = a;
+    k4 = a + 1.0;
+    k5 = 1.0;
+    k6 = b - 1.0;
+    k7 = k4;
+    k8 = a + 2.0;
+
+    pkm2 = 0.0;
+    qkm2 = 1.0;
+    pkm1 = 1.0;
+    qkm1 = 1.0;
+    ans = 1.0;
+    r = 1.0;
+    n = 0;
+    thresh = 3.0 * MACHEP;
+    do {
+      xk = -( x * k1 * k2 )/( k3 * k4 );
+      pk = pkm1 +  pkm2 * xk;
+      qk = qkm1 +  qkm2 * xk;
+      pkm2 = pkm1;
+      pkm1 = pk;
+      qkm2 = qkm1;
+      qkm1 = qk;
+
+      xk = ( x * k5 * k6 )/( k7 * k8 );
+      pk = pkm1 +  pkm2 * xk;
+      qk = qkm1 +  qkm2 * xk;
+      pkm2 = pkm1;
+      pkm1 = pk;
+      qkm2 = qkm1;
+      qkm1 = qk;
+
+      if( qk != 0 )		r = pk/qk;
+      if( r != 0 ) {
+	t = Math.abs( (ans - r)/r );
+	ans = r;
+      }	else
+	t = 1.0;
+
+      if( t < thresh ) return ans;
+
+      k1 += 1.0;
+      k2 += 1.0;
+      k3 += 2.0;
+      k4 += 2.0;
+      k5 += 1.0;
+      k6 -= 1.0;
+      k7 += 2.0;
+      k8 += 2.0;
+
+      if( (Math.abs(qk) + Math.abs(pk)) > big ) {
+	pkm2 *= biginv;
+	pkm1 *= biginv;
+	qkm2 *= biginv;
+	qkm1 *= biginv;
+      }
+      if( (Math.abs(qk) < biginv) || (Math.abs(pk) < biginv) ) {
+	pkm2 *= big;
+	pkm1 *= big;
+	qkm2 *= big;
+	qkm1 *= big;
+      }
+    } while( ++n < 300 );
+
+    return ans;
+  }   
+
+  /**
+   * Continued fraction expansion #2 for incomplete beta integral.
+   */
+  public static double incompleteBetaFraction2( double a, double b, double x ) {
+
+    double xk, pk, pkm1, pkm2, qk, qkm1, qkm2;
+    double k1, k2, k3, k4, k5, k6, k7, k8;
+    double r, t, ans, z, thresh;
+    int n;
+
+    k1 = a;
+    k2 = b - 1.0;
+    k3 = a;
+    k4 = a + 1.0;
+    k5 = 1.0;
+    k6 = a + b;
+    k7 = a + 1.0;;
+    k8 = a + 2.0;
+
+    pkm2 = 0.0;
+    qkm2 = 1.0;
+    pkm1 = 1.0;
+    qkm1 = 1.0;
+    z = x / (1.0-x);
+    ans = 1.0;
+    r = 1.0;
+    n = 0;
+    thresh = 3.0 * MACHEP;
+    do {
+      xk = -( z * k1 * k2 )/( k3 * k4 );
+      pk = pkm1 +  pkm2 * xk;
+      qk = qkm1 +  qkm2 * xk;
+      pkm2 = pkm1;
+      pkm1 = pk;
+      qkm2 = qkm1;
+      qkm1 = qk;
+
+      xk = ( z * k5 * k6 )/( k7 * k8 );
+      pk = pkm1 +  pkm2 * xk;
+      qk = qkm1 +  qkm2 * xk;
+      pkm2 = pkm1;
+      pkm1 = pk;
+      qkm2 = qkm1;
+      qkm1 = qk;
+
+      if( qk != 0 )  r = pk/qk;
+      if( r != 0 ) {
+	t = Math.abs( (ans - r)/r );
+	ans = r;
+      } else
+	t = 1.0;
+
+      if( t < thresh ) return ans;
+
+      k1 += 1.0;
+      k2 -= 1.0;
+      k3 += 2.0;
+      k4 += 2.0;
+      k5 += 1.0;
+      k6 += 1.0;
+      k7 += 2.0;
+      k8 += 2.0;
+
+      if( (Math.abs(qk) + Math.abs(pk)) > big ) {
+	pkm2 *= biginv;
+	pkm1 *= biginv;
+	qkm2 *= biginv;
+	qkm1 *= biginv;
+      }
+      if( (Math.abs(qk) < biginv) || (Math.abs(pk) < biginv) ) {
+	pkm2 *= big;
+	pkm1 *= big;
+	qkm2 *= big;
+	qkm1 *= big;
+      }
+    } while( ++n < 300 );
+
+    return ans;
+  }
+
+  /**
+   * Power series for incomplete beta integral.
+   * Use when b*x is small and x not too close to 1.  
+   */
+  public static double powerSeries( double a, double b, double x ) {
+
+    double s, t, u, v, n, t1, z, ai;
+    
+    ai = 1.0 / a;
+    u = (1.0 - b) * x;
+    v = u / (a + 1.0);
+    t1 = v;
+    t = u;
+    n = 2.0;
+    s = 0.0;
+    z = MACHEP * ai;
+    while( Math.abs(v) > z ) {
+      u = (n - b) * x / n;
+      t *= u;
+      v = t / (a + n);
+      s += v; 
+      n += 1.0;
+    }
+    s += t1;
+    s += ai;
+
+    u = a * Math.log(x);
+    if( (a+b) < MAXGAM && Math.abs(u) < MAXLOG ) {
+      t = gamma(a+b)/(gamma(a)*gamma(b));
+      s = s * t * Math.pow(x,a);
+    } else {
+      t = lnGamma(a+b) - lnGamma(a) - lnGamma(b) + u + Math.log(s);
+      if( t < MINLOG ) 	s = 0.0;
+      else  	            s = Math.exp(t);
+    }
+    return s;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5616 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] ops) {
+
+    System.out.println("Binomial standard error (0.5, 100): " + 
+		       Statistics.binomialStandardError(0.5, 100));
+    System.out.println("Chi-squared probability (2.558, 10): " +
+		       Statistics.chiSquaredProbability(2.558, 10));
+    System.out.println("Normal probability (0.2): " +
+		       Statistics.normalProbability(0.2));
+    System.out.println("F probability (5.1922, 4, 5): " +
+		       Statistics.FProbability(5.1922, 4, 5));
+    System.out.println("lnGamma(6): "+ Statistics.lnGamma(6));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Stopwords.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Stopwords.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Stopwords.java	(revision 29)
@@ -0,0 +1,857 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Stopwords.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ * Class that can test whether a given string is a stop word.
+ * Lowercases all words before the test. <p/>
+ * The format for reading and writing is one word per line, lines starting
+ * with '#' are interpreted as comments and therefore skipped. <p/>
+ * The default stopwords are based on <a href="http://www.cs.cmu.edu/~mccallum/bow/rainbow/" target="_blank">Rainbow</a>. <p/>
+ *
+ * Accepts the following parameter: <p/>
+ *
+ * -i file <br/>
+ * loads the stopwords from the given file <p/>
+ *
+ * -o file <br/>
+ * saves the stopwords to the given file <p/>
+ *
+ * -p <br/>
+ * outputs the current stopwords on stdout <p/>
+ *
+ * Any additional parameters are interpreted as words to test as stopwords.
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Stopwords
+  implements RevisionHandler {
+  
+  /** The hash set containing the list of stopwords */
+  protected HashSet<String> m_Words = null;
+
+  /** The default stopwords object (stoplist based on Rainbow) */
+  protected static Stopwords m_Stopwords;
+
+  static {
+    if (m_Stopwords == null) {
+      m_Stopwords = new Stopwords();
+    }
+  }
+
+  /**
+   * initializes the stopwords (based on <a href="http://www.cs.cmu.edu/~mccallum/bow/rainbow/" target="_blank">Rainbow</a>).
+   */
+  public Stopwords() {
+    m_Words = new HashSet<String>();
+
+    //Stopwords list from Rainbow
+    add("a");
+    add("able");
+    add("about");
+    add("above");
+    add("according");
+    add("accordingly");
+    add("across");
+    add("actually");
+    add("after");
+    add("afterwards");
+    add("again");
+    add("against");
+    add("all");
+    add("allow");
+    add("allows");
+    add("almost");
+    add("alone");
+    add("along");
+    add("already");
+    add("also");
+    add("although");
+    add("always");
+    add("am");
+    add("among");
+    add("amongst");
+    add("an");
+    add("and");
+    add("another");
+    add("any");
+    add("anybody");
+    add("anyhow");
+    add("anyone");
+    add("anything");
+    add("anyway");
+    add("anyways");
+    add("anywhere");
+    add("apart");
+    add("appear");
+    add("appreciate");
+    add("appropriate");
+    add("are");
+    add("around");
+    add("as");
+    add("aside");
+    add("ask");
+    add("asking");
+    add("associated");
+    add("at");
+    add("available");
+    add("away");
+    add("awfully");
+    add("b");
+    add("be");
+    add("became");
+    add("because");
+    add("become");
+    add("becomes");
+    add("becoming");
+    add("been");
+    add("before");
+    add("beforehand");
+    add("behind");
+    add("being");
+    add("believe");
+    add("below");
+    add("beside");
+    add("besides");
+    add("best");
+    add("better");
+    add("between");
+    add("beyond");
+    add("both");
+    add("brief");
+    add("but");
+    add("by");
+    add("c");
+    add("came");
+    add("can");
+    add("cannot");
+    add("cant");
+    add("cause");
+    add("causes");
+    add("certain");
+    add("certainly");
+    add("changes");
+    add("clearly");
+    add("co");
+    add("com");
+    add("come");
+    add("comes");
+    add("concerning");
+    add("consequently");
+    add("consider");
+    add("considering");
+    add("contain");
+    add("containing");
+    add("contains");
+    add("corresponding");
+    add("could");
+    add("course");
+    add("currently");
+    add("d");
+    add("definitely");
+    add("described");
+    add("despite");
+    add("did");
+    add("different");
+    add("do");
+    add("does");
+    add("doing");
+    add("done");
+    add("down");
+    add("downwards");
+    add("during");
+    add("e");
+    add("each");
+    add("edu");
+    add("eg");
+    add("eight");
+    add("either");
+    add("else");
+    add("elsewhere");
+    add("enough");
+    add("entirely");
+    add("especially");
+    add("et");
+    add("etc");
+    add("even");
+    add("ever");
+    add("every");
+    add("everybody");
+    add("everyone");
+    add("everything");
+    add("everywhere");
+    add("ex");
+    add("exactly");
+    add("example");
+    add("except");
+    add("f");
+    add("far");
+    add("few");
+    add("fifth");
+    add("first");
+    add("five");
+    add("followed");
+    add("following");
+    add("follows");
+    add("for");
+    add("former");
+    add("formerly");
+    add("forth");
+    add("four");
+    add("from");
+    add("further");
+    add("furthermore");
+    add("g");
+    add("get");
+    add("gets");
+    add("getting");
+    add("given");
+    add("gives");
+    add("go");
+    add("goes");
+    add("going");
+    add("gone");
+    add("got");
+    add("gotten");
+    add("greetings");
+    add("h");
+    add("had");
+    add("happens");
+    add("hardly");
+    add("has");
+    add("have");
+    add("having");
+    add("he");
+    add("hello");
+    add("help");
+    add("hence");
+    add("her");
+    add("here");
+    add("hereafter");
+    add("hereby");
+    add("herein");
+    add("hereupon");
+    add("hers");
+    add("herself");
+    add("hi");
+    add("him");
+    add("himself");
+    add("his");
+    add("hither");
+    add("hopefully");
+    add("how");
+    add("howbeit");
+    add("however");
+    add("i");
+    add("ie");
+    add("if");
+    add("ignored");
+    add("immediate");
+    add("in");
+    add("inasmuch");
+    add("inc");
+    add("indeed");
+    add("indicate");
+    add("indicated");
+    add("indicates");
+    add("inner");
+    add("insofar");
+    add("instead");
+    add("into");
+    add("inward");
+    add("is");
+    add("it");
+    add("its");
+    add("itself");
+    add("j");
+    add("just");
+    add("k");
+    add("keep");
+    add("keeps");
+    add("kept");
+    add("know");
+    add("knows");
+    add("known");
+    add("l");
+    add("last");
+    add("lately");
+    add("later");
+    add("latter");
+    add("latterly");
+    add("least");
+    add("less");
+    add("lest");
+    add("let");
+    add("like");
+    add("liked");
+    add("likely");
+    add("little");
+    add("ll"); //added to avoid words like you'll,I'll etc.
+    add("look");
+    add("looking");
+    add("looks");
+    add("ltd");
+    add("m");
+    add("mainly");
+    add("many");
+    add("may");
+    add("maybe");
+    add("me");
+    add("mean");
+    add("meanwhile");
+    add("merely");
+    add("might");
+    add("more");
+    add("moreover");
+    add("most");
+    add("mostly");
+    add("much");
+    add("must");
+    add("my");
+    add("myself");
+    add("n");
+    add("name");
+    add("namely");
+    add("nd");
+    add("near");
+    add("nearly");
+    add("necessary");
+    add("need");
+    add("needs");
+    add("neither");
+    add("never");
+    add("nevertheless");
+    add("new");
+    add("next");
+    add("nine");
+    add("no");
+    add("nobody");
+    add("non");
+    add("none");
+    add("noone");
+    add("nor");
+    add("normally");
+    add("not");
+    add("nothing");
+    add("novel");
+    add("now");
+    add("nowhere");
+    add("o");
+    add("obviously");
+    add("of");
+    add("off");
+    add("often");
+    add("oh");
+    add("ok");
+    add("okay");
+    add("old");
+    add("on");
+    add("once");
+    add("one");
+    add("ones");
+    add("only");
+    add("onto");
+    add("or");
+    add("other");
+    add("others");
+    add("otherwise");
+    add("ought");
+    add("our");
+    add("ours");
+    add("ourselves");
+    add("out");
+    add("outside");
+    add("over");
+    add("overall");
+    add("own");
+    add("p");
+    add("particular");
+    add("particularly");
+    add("per");
+    add("perhaps");
+    add("placed");
+    add("please");
+    add("plus");
+    add("possible");
+    add("presumably");
+    add("probably");
+    add("provides");
+    add("q");
+    add("que");
+    add("quite");
+    add("qv");
+    add("r");
+    add("rather");
+    add("rd");
+    add("re");
+    add("really");
+    add("reasonably");
+    add("regarding");
+    add("regardless");
+    add("regards");
+    add("relatively");
+    add("respectively");
+    add("right");
+    add("s");
+    add("said");
+    add("same");
+    add("saw");
+    add("say");
+    add("saying");
+    add("says");
+    add("second");
+    add("secondly");
+    add("see");
+    add("seeing");
+    add("seem");
+    add("seemed");
+    add("seeming");
+    add("seems");
+    add("seen");
+    add("self");
+    add("selves");
+    add("sensible");
+    add("sent");
+    add("serious");
+    add("seriously");
+    add("seven");
+    add("several");
+    add("shall");
+    add("she");
+    add("should");
+    add("since");
+    add("six");
+    add("so");
+    add("some");
+    add("somebody");
+    add("somehow");
+    add("someone");
+    add("something");
+    add("sometime");
+    add("sometimes");
+    add("somewhat");
+    add("somewhere");
+    add("soon");
+    add("sorry");
+    add("specified");
+    add("specify");
+    add("specifying");
+    add("still");
+    add("sub");
+    add("such");
+    add("sup");
+    add("sure");
+    add("t");
+    add("take");
+    add("taken");
+    add("tell");
+    add("tends");
+    add("th");
+    add("than");
+    add("thank");
+    add("thanks");
+    add("thanx");
+    add("that");
+    add("thats");
+    add("the");
+    add("their");
+    add("theirs");
+    add("them");
+    add("themselves");
+    add("then");
+    add("thence");
+    add("there");
+    add("thereafter");
+    add("thereby");
+    add("therefore");
+    add("therein");
+    add("theres");
+    add("thereupon");
+    add("these");
+    add("they");
+    add("think");
+    add("third");
+    add("this");
+    add("thorough");
+    add("thoroughly");
+    add("those");
+    add("though");
+    add("three");
+    add("through");
+    add("throughout");
+    add("thru");
+    add("thus");
+    add("to");
+    add("together");
+    add("too");
+    add("took");
+    add("toward");
+    add("towards");
+    add("tried");
+    add("tries");
+    add("truly");
+    add("try");
+    add("trying");
+    add("twice");
+    add("two");
+    add("u");
+    add("un");
+    add("under");
+    add("unfortunately");
+    add("unless");
+    add("unlikely");
+    add("until");
+    add("unto");
+    add("up");
+    add("upon");
+    add("us");
+    add("use");
+    add("used");
+    add("useful");
+    add("uses");
+    add("using");
+    add("usually");
+    add("uucp");
+    add("v");
+    add("value");
+    add("various");
+    add("ve"); //added to avoid words like I've,you've etc.
+    add("very");
+    add("via");
+    add("viz");
+    add("vs");
+    add("w");
+    add("want");
+    add("wants");
+    add("was");
+    add("way");
+    add("we");
+    add("welcome");
+    add("well");
+    add("went");
+    add("were");
+    add("what");
+    add("whatever");
+    add("when");
+    add("whence");
+    add("whenever");
+    add("where");
+    add("whereafter");
+    add("whereas");
+    add("whereby");
+    add("wherein");
+    add("whereupon");
+    add("wherever");
+    add("whether");
+    add("which");
+    add("while");
+    add("whither");
+    add("who");
+    add("whoever");
+    add("whole");
+    add("whom");
+    add("whose");
+    add("why");
+    add("will");
+    add("willing");
+    add("wish");
+    add("with");
+    add("within");
+    add("without");
+    add("wonder");
+    add("would");
+    add("would");
+    add("x");
+    add("y");
+    add("yes");
+    add("yet");
+    add("you");
+    add("your");
+    add("yours");
+    add("yourself");
+    add("yourselves");
+    add("z");
+    add("zero");
+  }
+
+  /**
+   * removes all stopwords
+   */
+  public void clear() {
+    m_Words.clear();
+  }
+
+  /**
+   * adds the given word to the stopword list (is automatically converted to
+   * lower case and trimmed)
+   *
+   * @param word the word to add
+   */
+  public void add(String word) {
+    if (word.trim().length() > 0)
+      m_Words.add(word.trim().toLowerCase());
+  }
+
+  /**
+   * removes the word from the stopword list
+   *
+   * @param word the word to remove
+   * @return true if the word was found in the list and then removed
+   */
+  public boolean remove(String word) {
+    return m_Words.remove(word);
+  }
+  
+  /** 
+   * Returns true if the given string is a stop word.
+   * 
+   * @param word the word to test
+   * @return true if the word is a stopword
+   */
+  public boolean is(String word) {
+    return m_Words.contains(word.toLowerCase());
+  }
+
+  /**
+   * Returns a sorted enumeration over all stored stopwords
+   *
+   * @return the enumeration over all stopwords
+   */
+  public Enumeration elements() {
+    Iterator<String>    iter;
+    Vector<String>      list;
+
+    iter = m_Words.iterator();
+    list = new Vector<String>();
+
+    while (iter.hasNext())
+      list.add(iter.next());
+
+    // sort list
+    Collections.sort(list);
+
+    return list.elements();
+  }
+
+  /**
+   * Generates a new Stopwords object from the given file
+   *
+   * @param filename the file to read the stopwords from
+   * @throws Exception if reading fails
+   */
+  public void read(String filename) throws Exception {
+    read(new File(filename));
+  }
+
+  /**
+   * Generates a new Stopwords object from the given file
+   *
+   * @param file the file to read the stopwords from
+   * @throws Exception if reading fails
+   */
+  public void read(File file) throws Exception {
+    read(new BufferedReader(new FileReader(file)));
+  }
+
+  /**
+   * Generates a new Stopwords object from the reader. The reader is
+   * closed automatically.
+   *
+   * @param reader the reader to get the stopwords from
+   * @throws Exception if reading fails
+   */
+  public void read(BufferedReader reader) throws Exception {
+    String      line;
+
+    clear();
+    
+    while ((line = reader.readLine()) != null) {
+      line = line.trim();
+      // comment?
+      if (line.startsWith("#"))
+        continue;
+      add(line);
+    }
+
+    reader.close();
+  }
+
+  /**
+   * Writes the current stopwords to the given file
+   *
+   * @param filename the file to write the stopwords to
+   * @throws Exception if writing fails
+   */
+  public void write(String filename) throws Exception {
+    write(new File(filename));
+  }
+
+  /**
+   * Writes the current stopwords to the given file
+   *
+   * @param file the file to write the stopwords to
+   * @throws Exception if writing fails
+   */
+  public void write(File file) throws Exception {
+    write(new BufferedWriter(new FileWriter(file)));
+  }
+
+  /**
+   * Writes the current stopwords to the given writer. The writer is closed
+   * automatically.
+   *
+   * @param writer the writer to get the stopwords from
+   * @throws Exception if writing fails
+   */
+  public void write(BufferedWriter writer) throws Exception {
+    Enumeration   enm;
+
+    // header
+    writer.write("# generated " + new Date());
+    writer.newLine();
+
+    enm = elements();
+
+    while (enm.hasMoreElements()) {
+      writer.write(enm.nextElement().toString());
+      writer.newLine();
+    }
+
+    writer.flush();
+    writer.close();
+  }
+
+  /**
+   * returns the current stopwords in a string
+   *
+   * @return the current stopwords
+   */
+  public String toString() {
+    Enumeration   enm;
+    StringBuffer  result;
+
+    result = new StringBuffer();
+    enm    = elements();
+    while (enm.hasMoreElements()) {
+      result.append(enm.nextElement().toString());
+      if (enm.hasMoreElements())
+        result.append(",");
+    }
+
+    return result.toString();
+  }
+  
+  /** 
+   * Returns true if the given string is a stop word.
+   * 
+   * @param str the word to test
+   * @return true if the word is a stopword
+   */
+  public static boolean isStopword(String str) {
+    return m_Stopwords.is(str.toLowerCase());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Accepts the following parameter: <p/>
+   *
+   * -i file <br/>
+   * loads the stopwords from the given file <p/>
+   *
+   * -o file <br/>
+   * saves the stopwords to the given file <p/>
+   *
+   * -p <br/>
+   * outputs the current stopwords on stdout <p/>
+   *
+   * Any additional parameters are interpreted as words to test as stopwords.
+   * 
+   * @param args commandline parameters
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    String input = Utils.getOption('i', args);
+    String output = Utils.getOption('o', args);
+    boolean print = Utils.getFlag('p', args);
+
+    // words to process?
+    Vector<String> words = new Vector<String>();
+    for (int i = 0; i < args.length; i++) {
+      if (args[i].trim().length() > 0)
+        words.add(args[i].trim());
+    }
+    
+    Stopwords stopwords = new Stopwords();
+
+    // load from file?
+    if (input.length() != 0)
+      stopwords.read(input);
+
+    // write to file?
+    if (output.length() != 0)
+      stopwords.write(output);
+    
+    // output to stdout?
+    if (print) {
+      System.out.println("\nStopwords:");
+      Enumeration enm = stopwords.elements();
+      int i = 0;
+      while (enm.hasMoreElements()) {
+        System.out.println((i+1) + ". " + enm.nextElement());
+        i++;
+      }
+    }
+
+    // check words for being a stopword
+    if (words.size() > 0) {
+      System.out.println("\nChecking for stopwords:");
+      for (int i = 0; i < words.size(); i++) {
+        System.out.println(
+            (i+1) + ". " + words.get(i) + ": " 
+            + stopwords.is(words.get(i).toString()));
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/StringLocator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/StringLocator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/StringLocator.java	(revision 29)
@@ -0,0 +1,177 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * StringLocator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+
+/**
+ * This class locates and records the indices of String attributes, 
+ * recursively in case of Relational attributes. The indices are normally
+ * used for copying the Strings from one Instances object to another.
+ * 
+ * @author fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Attribute#STRING
+ * @see Attribute#RELATIONAL
+ */
+public class StringLocator
+  extends AttributeLocator {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 7805522230268783972L;
+
+  /**
+   * initializes the StringLocator with the given data
+   * 
+   * @param data	the data to work on
+   */
+  public StringLocator(Instances data) {
+    super(data, Attribute.STRING);
+  }
+  
+  /**
+   * Initializes the StringLocator with the given data. 
+   * Checks only the given range.
+   * 
+   * @param data	the data to work on
+   * @param fromIndex	the first index to inspect (including)
+   * @param toIndex	the last index to check (including)
+   */
+  public StringLocator(Instances data, int fromIndex, int toIndex) {
+    super(data, Attribute.STRING, fromIndex, toIndex);
+  }
+  
+  /**
+   * Initializes the AttributeLocator with the given data.
+   * Checks only the specified attribute indices.
+   * 
+   * @param data	the data to work on
+   * @param indices	the attribute indices to check
+   */
+  public StringLocator(Instances data, int[] indices) {
+    super(data, Attribute.STRING, indices);
+  }
+
+  /**
+   * Copies string values contained in the instance copied to a new
+   * dataset. The Instance must already be assigned to a dataset. This
+   * dataset and the destination dataset must have the same structure.
+   *
+   * @param inst 		the Instance containing the string values to copy.
+   * @param destDataset 	the destination set of Instances
+   * @param strAtts 		an AttributeLocator containing the indices of 
+   * 				any string attributes in the dataset.  
+   */
+  public static void copyStringValues(Instance inst, Instances destDataset, 
+                               AttributeLocator strAtts) {
+
+    if (inst.dataset() == null) {
+      throw new IllegalArgumentException("Instance has no dataset assigned!!");
+    } else if (inst.dataset().numAttributes() != destDataset.numAttributes()) {
+      throw new IllegalArgumentException("Src and Dest differ in # of attributes!!");
+    } 
+    copyStringValues(inst, true, inst.dataset(), strAtts,
+                     destDataset, strAtts);
+  }
+
+  /**
+   * Takes string values referenced by an Instance and copies them from a
+   * source dataset to a destination dataset. The instance references are
+   * updated to be valid for the destination dataset. The instance may have the 
+   * structure (i.e. number and attribute position) of either dataset (this
+   * affects where references are obtained from). Only works if the number
+   * of string attributes is the same in both indices (implicitly these string
+   * attributes should be semantically same but just with shifted positions).
+   *
+   * @param instance 		the instance containing references to strings 
+   * 				in the source dataset that will have references 
+   * 				updated to be valid for the destination dataset.
+   * @param instSrcCompat 	true if the instance structure is the same as 
+   * 				the source, or false if it is the same as the 
+   * 				destination (i.e. which of the string attribute 
+   * 				indices contains the correct locations for this 
+   * 				instance).
+   * @param srcDataset 		the dataset for which the current instance 
+   * 				string references are valid (after any position 
+   * 				mapping if needed)
+   * @param srcLoc 		an AttributeLocator containing the indices of 
+   * 				string attributes in the source datset.
+   * @param destDataset 	the dataset for which the current instance 
+   * 				string references need to be inserted (after 
+   * 				any position mapping if needed)
+   * @param destLoc 	an AttributeLocator containing the indices of 
+   * 				string attributes in the destination datset.
+   */
+  public static void copyStringValues(Instance instance, boolean instSrcCompat,
+                                  Instances srcDataset, AttributeLocator srcLoc,
+                                  Instances destDataset, AttributeLocator destLoc) {
+    if (srcDataset == destDataset)
+      return;
+    
+    if (srcLoc.getAttributeIndices().length != destLoc.getAttributeIndices().length)
+      throw new IllegalArgumentException("Src and Dest string indices differ in length!!");
+
+    if (srcLoc.getLocatorIndices().length != destLoc.getLocatorIndices().length)
+      throw new IllegalArgumentException("Src and Dest locator indices differ in length!!");
+
+    for (int i = 0; i < srcLoc.getAttributeIndices().length; i++) {
+      int instIndex  = instSrcCompat 
+      			  ? srcLoc.getActualIndex(srcLoc.getAttributeIndices()[i]) 
+      			  : destLoc.getActualIndex(destLoc.getAttributeIndices()[i]);
+      Attribute src  = srcDataset.attribute(srcLoc.getActualIndex(srcLoc.getAttributeIndices()[i]));
+      Attribute dest = destDataset.attribute(destLoc.getActualIndex(destLoc.getAttributeIndices()[i]));
+      if (!instance.isMissing(instIndex)) {
+        int valIndex = dest.addStringValue(src, (int)instance.value(instIndex));
+        instance.setValue(instIndex, (double)valIndex);
+      }
+    }
+    
+    // recurse if necessary
+    int[] srcIndices  = srcLoc.getLocatorIndices();
+    int[] destIndices = destLoc.getLocatorIndices();
+    for (int i = 0; i < srcIndices.length; i++) {
+      int index = instSrcCompat
+                 ? srcLoc.getActualIndex(srcIndices[i])
+                 : destLoc.getActualIndex(destIndices[i]);
+      if (instance.isMissing(index))
+        continue;
+      Instances rel = instSrcCompat
+		         ? instance.relationalValue(index)
+		         : instance.relationalValue(index);
+      AttributeLocator srcStrAttsNew = srcLoc.getLocator(srcIndices[i]);
+      Instances srcDatasetNew = srcStrAttsNew.getData();
+      AttributeLocator destStrAttsNew = destLoc.getLocator(destIndices[i]);
+      Instances destDatasetNew = destStrAttsNew.getData();
+      for (int n = 0; n < rel.numInstances(); n++) {
+        copyStringValues(rel.instance(n), instSrcCompat, srcDatasetNew, srcStrAttsNew, destDatasetNew, destStrAttsNew);
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Summarizable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Summarizable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Summarizable.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Summarizable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Interface to something that provides a short textual summary (as opposed
+ * to toString() which is usually a fairly complete description) of itself.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Summarizable {
+
+  /**
+   * Returns a string that summarizes the object.
+   *
+   * @return the object summarized as a string
+   */
+  String toSummaryString();
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/SystemInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/SystemInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/SystemInfo.java	(revision 29)
@@ -0,0 +1,156 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SystemInfo.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core;
+
+import weka.gui.LookAndFeel;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * This class prints some information about the system setup, like Java
+ * version, JVM settings etc. Useful for Bug-Reports.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class SystemInfo
+  implements RevisionHandler {
+  
+  /** for storing the information */
+  private Hashtable<String,String> m_Info = null;
+  
+  /**
+   * initializes the object and reads the system information
+   */
+  public SystemInfo() {
+    m_Info = new Hashtable<String,String>();
+    readProperties();
+  }
+
+  /**
+   * reads all the properties and stores them in the hashtable
+   */
+  private void readProperties() {
+    Properties          props;
+    Enumeration         enm;
+    String              name;
+    String[]            laf;
+    String              tmpStr;
+    int                 i;
+    Memory              mem;
+    
+    m_Info.clear();
+
+    // System information
+    props = System.getProperties();
+    enm   = props.propertyNames();
+    while (enm.hasMoreElements()) {
+      name = (String)enm.nextElement();
+      m_Info.put(name, (String)props.get(name));
+    }
+
+    // additional WEKA info
+    m_Info.put("weka.version", Version.VERSION);
+
+    // look and feel info
+    laf    = LookAndFeel.getInstalledLookAndFeels();
+    tmpStr = "";
+    for (i = 0; i < laf.length; i++) {
+      if (i > 0)
+        tmpStr += ",";
+      tmpStr += laf[i];
+    }
+    m_Info.put("ui.installedLookAndFeels", tmpStr);
+    m_Info.put("ui.currentLookAndFeel", LookAndFeel.getSystemLookAndFeel());
+
+    // memory info
+    mem = new Memory();
+    m_Info.put(
+        "memory.initial", 
+        "" + Utils.doubleToString(Memory.toMegaByte(mem.getInitial()), 1) + "MB" 
+        + " (" + mem.getInitial() + ")");
+    m_Info.put(
+        "memory.max", 
+        "" + Utils.doubleToString(Memory.toMegaByte(mem.getMax()), 1) + "MB"
+        + " (" + mem.getMax() + ")");
+  }
+
+  /**
+   * returns a copy of the system info. the key is the name of the property
+   * and the associated object is the value of the property (a string).
+   */
+  public Hashtable getSystemInfo() {
+    return (Hashtable) m_Info.clone();
+  }
+
+  /**
+   * returns a string representation of all the system properties
+   */
+  public String toString() {
+    Enumeration<String>     enm;
+    String          result;
+    String          key;
+    Vector<String>          keys;
+    int             i;
+    String          value;
+
+    result = "";
+    keys   = new Vector<String>();
+    
+    // get names and sort them
+    enm = m_Info.keys();
+    while (enm.hasMoreElements())
+      keys.add(enm.nextElement());
+    Collections.sort(keys);
+    
+    // generate result
+    for (i = 0; i < keys.size(); i++) {
+      key   = keys.get(i).toString();
+      value = m_Info.get(key).toString();
+      if (key.equals("line.separator"))
+        value = Utils.backQuoteChars(value);
+      result += key + ": " + value + "\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * for printing the system info to stdout.
+   */
+  public static void main(String[] args) {
+    System.out.println(new SystemInfo());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Tag.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Tag.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Tag.java	(revision 29)
@@ -0,0 +1,201 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Tag.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+
+/**
+ * A <code>Tag</code> simply associates a numeric ID with a String description.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 5953 $
+ */
+public class Tag implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 3326379903447135320L;
+
+  /** The ID */
+  protected int m_ID;
+
+  /** The unique string for this tag, doesn't have to be numeric */
+  protected String m_IDStr;
+  
+  /** The descriptive text */
+  protected String m_Readable;
+
+  /**
+   * Creates a new default Tag
+   *
+   */
+  public Tag() {
+    this(0, "A new tag", "A new tag", true);
+  }
+
+  /**
+   * Creates a new <code>Tag</code> instance.
+   *
+   * @param ident the ID for the new Tag.
+   * @param readable the description for the new Tag.
+   */
+  public Tag(int ident, String readable) {
+    this(ident, "", readable);
+  }
+  
+  /**
+   * Creates a new <code>Tag</code> instance.
+   *
+   * @param ident the ID for the new Tag.
+   * @param identStr the ID string for the new Tag (case-insensitive).
+   * @param readable the description for the new Tag.
+   */
+  public Tag(int ident, String identStr, String readable) {
+    this(ident, identStr, readable, true);
+  }
+
+  public Tag(int ident, String identStr, String readable, boolean upperCase) {
+    m_ID = ident;
+    if (identStr.length() == 0) {
+      m_IDStr = "" + ident;
+    } else {
+        m_IDStr = identStr;
+      if (upperCase) {
+        m_IDStr = identStr.toUpperCase();
+      } 
+    }
+    m_Readable = readable;
+  }
+
+  /**
+   * Gets the numeric ID of the Tag.
+   *
+   * @return the ID of the Tag.
+   */
+  public int getID() {
+    return m_ID;
+  }
+
+  /**
+   * Sets the numeric ID of the Tag.
+   *
+   * @param id the ID of the Tag.
+   */
+  public void setID(int id) {
+    m_ID = id;
+  } 
+
+  /**
+   * Gets the string ID of the Tag.
+   *
+   * @return the string ID of the Tag.
+   */
+  public String getIDStr() {
+    return m_IDStr;
+  }
+
+  /**
+   * Sets the string ID of the Tag.
+   *
+   * @param str the string ID of the Tag.
+   */
+  public void setIDStr(String str) {
+    m_IDStr = str;
+  }
+
+  /**
+   * Gets the string description of the Tag.
+   *
+   * @return the description of the Tag.
+   */
+  public String getReadable() {
+    return m_Readable;
+  }
+
+  /**
+   * Sets the string description of the Tag.
+   *
+   * @param r the description of the Tag.
+   */
+  public void setReadable(String r) {
+    m_Readable = r;
+  }
+  
+  /**
+   * returns the IDStr
+   * 
+   * @return the IDStr
+   */
+  public String toString() {
+    return m_IDStr;
+  }
+  
+  /**
+   * returns a list that can be used in the listOption methods to list all
+   * the available ID strings, e.g.: &lt;0|1|2&gt; or &lt;what|ever&gt;
+   * 
+   * @param tags the tags to create the list for
+   * @return a list of all ID strings
+   */
+  public static String toOptionList(Tag[] tags) {
+    String	result;
+    int		i;
+    
+    result = "<";
+    for (i = 0; i < tags.length; i++) {
+      if (i > 0)
+	result += "|";
+      result += tags[i];
+    }
+    result += ">";
+    
+    return result;
+  }
+  
+  /**
+   * returns a string that can be used in the listOption methods to list all
+   * the available options, i.e., "\t\tID = Text\n" for each option
+   * 
+   * @param tags the tags to create the string for
+   * @return a string explaining the tags
+   */
+  public static String toOptionSynopsis(Tag[] tags) {
+    String	result;
+    int		i;
+
+    result = "";
+    for (i = 0; i < tags.length; i++) {
+      result += "\t\t" + tags[i].getIDStr() + " = " + tags[i].getReadable() + "\n";
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/TechnicalInformation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/TechnicalInformation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/TechnicalInformation.java	(revision 29)
@@ -0,0 +1,735 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TechnicalInformation.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Used for paper references in the Javadoc and for BibTex generation.
+ * Based on documentation found here: <p/>
+ * <a href="http://www.ecst.csuchico.edu/~jacobsd/bib/formats/bibtex.html" target="_blank">http://www.ecst.csuchico.edu/~jacobsd/bib/formats/bibtex.html</a>
+ * <p/>
+ * BibTex examples can be found here: <p/>
+ * <a href="http://bib2web.djvuzone.org/bibtex.html" target="_blank">http://bib2web.djvuzone.org/bibtex.html</a>
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see TechnicalInformationHandler
+ */
+public class TechnicalInformation
+  implements RevisionHandler {
+
+  /** the different types of information */
+  public enum Type {
+    /** An article from a journal or magazine. */
+    ARTICLE("article", "An article from a journal or magazine."),  
+    /** A book with an explicit publisher. */
+    BOOK("book", "A book with an explicit publisher."),
+    /** A work that is printed and bound, but without a named publisher or sponsoring institution. */
+    BOOKLET("booklet", "A work that is printed and bound, but without a named publisher or sponsoring institution."),
+    /** The same as inproceedings. */
+    CONFERENCE("conference", "The same as inproceedings."),
+    /** A part of a book, which may be a chapter (or section or whatever) and/or a range of pages. */
+    INBOOK("inbook", "A part of a book, which may be a chapter (or section or whatever) and/or a range of pages."),
+    /** A part of a book having its own title. */
+    INCOLLECTION("incollection", "A part of a book having its own title."),
+    /** An article in a conference proceedings. */
+    INPROCEEDINGS("inproceedings", "An article in a conference proceedings."),
+    /** Technical documentation. */
+    MANUAL("manual", "Technical documentation."),
+    /** A Master's thesis. */
+    MASTERSTHESIS("mastersthesis", "A Master's thesis."),
+    /** Use this type when nothing else fits. */
+    MISC("misc", "Use this type when nothing else fits."),
+    /** A PhD thesis. */
+    PHDTHESIS("phdthesis", "A PhD thesis."),
+    /** The proceedings of a conference. */
+    PROCEEDINGS("proceedings", "The proceedings of a conference."),
+    /** A report published by a school or other institution, usually numbered within a series. */
+    TECHREPORT("techreport", "A report published by a school or other institution, usually numbered within a series."),
+    /** A document having an author and title, but not formally published. */
+    UNPUBLISHED("unpublished", "A document having an author and title, but not formally published.");
+    
+    /** the string used in toString()  */
+    protected String m_Display;
+    
+    /** the comment about this type */
+    protected String m_Comment;
+    
+    /**
+     * the constructor
+     * 
+     * @param display	the string to return in toString()
+     * @param comment	a comment about the type
+     */
+    private Type(String display, String comment) {
+      m_Display = display;
+      m_Comment = comment;
+    }
+    
+    /**
+     * returns the display string
+     * 
+     * @return 		the display string
+     */
+    public String getDisplay() {
+      return m_Display;
+    }
+    
+    /**
+     * returns the comment string
+     * 
+     * @return		the comment string
+     */
+    public String getComment() {
+      return m_Comment;
+    }
+    
+    /**
+     * returns the display string of the Type
+     * 
+     * @return		the display string
+     */
+    public String toString() {
+      return m_Display;
+    }
+  }
+  
+  /** the possible fields */
+  public enum Field {
+    /** Usually the address of the publisher or other type of institution. For major publishing houses, van Leunen recommends omitting the information entirely. For small publishers, on the other hand, you can help the reader by giving the complete address. */
+    ADDRESS("address", "Usually the address of the publisher or other type of institution. For major publishing houses, van Leunen recommends omitting the information entirely. For small publishers, on the other hand, you can help the reader by giving the complete address."),
+    /** An annotation. It is not used by the standard bibliography styles, but may be used by others that produce an annotated bibliography. */
+    ANNOTE("annote", "An annotation. It is not used by the standard bibliography styles, but may be used by others that produce an annotated bibliography."),
+    /** The name(s) of the author(s), in the format described in the LaTeX book. */
+    AUTHOR("author", "The name(s) of the author(s), in the format described in the LaTeX book."),
+    /** Title of a book, part of which is being cited. See the LaTeX book for how to type titles. For book entries, use the title field instead. */
+    BOOKTITLE("booktitle", "Title of a book, part of which is being cited. See the LaTeX book for how to type titles. For book entries, use the title field instead."),
+    /** A chapter (or section or whatever) number. */
+    CHAPTER("chapter", "A chapter (or section or whatever) number."),
+    /** The database key of the entry being cross referenced. Any fields that are missing from the current record are inherited from the field being cross referenced. */
+    CROSSREF("crossref", "The database key of the entry being cross referenced. Any fields that are missing from the current record are inherited from the field being cross referenced."),
+    /** The edition of a book---for example, ``Second''. This should be an ordinal, and should have the first letter capitalized, as shown here; the standard styles convert to lower case when necessary. */
+    EDITION("edition", "The edition of a book---for example, ``Second''. This should be an ordinal, and should have the first letter capitalized, as shown here; the standard styles convert to lower case when necessary."),
+    /** Name(s) of editor(s), typed as indicated in the LaTeX book. If there is also an author field, then the editor field gives the editor of the book or collection in which the reference appears. */
+    EDITOR("editor", "Name(s) of editor(s), typed as indicated in the LaTeX book. If there is also an author field, then the editor field gives the editor of the book or collection in which the reference appears."),
+    /** How something strange has been published. The first word should be capitalized. */
+    HOWPUBLISHED("howpublished", "How something strange has been published. The first word should be capitalized."),
+    /** The sponsoring institution of a technical report. */
+    INSTITUTION("institution", "The sponsoring institution of a technical report."),
+    /** A journal name. Abbreviations are provided for many journals. */
+    JOURNAL("journal", "A journal name. Abbreviations are provided for many journals."),
+    /** Used for alphabetizing, cross referencing, and creating a label when the ``author'' information is missing. This field should not be confused with the key that appears in the cite command and at the beginning of the database entry. */
+    KEY("key", "Used for alphabetizing, cross referencing, and creating a label when the ``author'' information is missing. This field should not be confused with the key that appears in the cite command and at the beginning of the database entry."),
+    /** The month in which the work was published or, for an unpublished work, in which it was written. You should use the standard three-letter abbreviation, as described in Appendix B.1.3 of the LaTeX book. */
+    MONTH("month", "The month in which the work was published or, for an unpublished work, in which it was written. You should use the standard three-letter abbreviation, as described in Appendix B.1.3 of the LaTeX book."),
+    /** Any additional information that can help the reader. The first word should be capitalized. */
+    NOTE("note", "Any additional information that can help the reader. The first word should be capitalized."),
+    /** The number of a journal, magazine, technical report, or of a work in a series. An issue of a journal or magazine is usually identified by its volume and number; the organization that issues a technical report usually gives it a number; and sometimes books are given numbers in a named series. */
+    NUMBER("number", "The number of a journal, magazine, technical report, or of a work in a series. An issue of a journal or magazine is usually identified by its volume and number; the organization that issues a technical report usually gives it a number; and sometimes books are given numbers in a named series."),
+    /** The organization that sponsors a conference or that publishes a manual. */
+    ORGANIZATION("organization", "The organization that sponsors a conference or that publishes a manual."),
+    /** One or more page numbers or range of numbers, such as 42--111 or 7,41,73--97 or 43+ (the `+' in this last example indicates pages following that don't form a simple range). To make it easier to maintain Scribe-compatible databases, the standard styles convert a single dash (as in 7-33) to the double dash used in TeX to denote number ranges (as in 7--33). */
+    PAGES("pages", "One or more page numbers or range of numbers, such as 42--111 or 7,41,73--97 or 43+ (the `+' in this last example indicates pages following that don't form a simple range). To make it easier to maintain Scribe-compatible databases, the standard styles convert a single dash (as in 7-33) to the double dash used in TeX to denote number ranges (as in 7--33)."),
+    /** The publisher's name. */
+    PUBLISHER("publisher", "The publisher's name."),
+    /** The name of the school where a thesis was written. */
+    SCHOOL("school", "The name of the school where a thesis was written."),
+    /** The name of a series or set of books. When citing an entire book, the the title field gives its title and an optional series field gives the name of a series or multi-volume set in which the book is published. */
+    SERIES("series", "The name of a series or set of books. When citing an entire book, the the title field gives its title and an optional series field gives the name of a series or multi-volume set in which the book is published."),
+    /** The work's title, typed as explained in the LaTeX book. */
+    TITLE("title", "The work's title, typed as explained in the LaTeX book."),
+    /** The type of a technical report---for example, ``Research Note''. */
+    TYPE("type", "The type of a technical report---for example, ``Research Note''."),
+    /** The volume of a journal or multi-volume book. */
+    VOLUME("volume", "The volume of a journal or multi-volume book."),
+    /** The year of publication or, for an unpublished work, the year it was written. Generally it should consist of four numerals, such as 1984, although the standard styles can handle any year whose last four nonpunctuation characters are numerals, such as `\\hbox{(about 1984)}'. */
+    YEAR("year", "The year of publication or, for an unpublished work, the year it was written. Generally it should consist of four numerals, such as 1984, although the standard styles can handle any year whose last four nonpunctuation characters are numerals, such as `\\hbox{(about 1984)}'."),
+    // other fields
+    /** The authors affiliation. */
+    AFFILIATION("affiliation", "The authors affiliation."),
+    /** An abstract of the work. */
+    ABSTRACT("abstract", "An abstract of the work."),
+    /** A Table of Contents.  */
+    CONTENTS("contents", "A Table of Contents "),
+    /** Copyright information. */
+    COPYRIGHT("copyright", "Copyright information."),
+    /** The International Standard Book Number (10 digits). */
+    ISBN("ISBN", "The International Standard Book Number (10 digits)."),
+    /** The International Standard Book Number (13 digits). */
+    ISBN13("ISBN-13", "The International Standard Book Number (13 digits)."),
+    /** The International Standard Serial Number. Used to identify a journal. */
+    ISSN("ISSN", "The International Standard Serial Number. Used to identify a journal."),
+    /** Key words used for searching or possibly for annotation. */
+    KEYWORDS("keywords", "Key words used for searching or possibly for annotation."),
+    /** The language the document is in. */
+    LANGUAGE("language", "The language the document is in."),
+    /** A location associated with the entry, such as the city in which a conference took place. */
+    LOCATION("location", "A location associated with the entry, such as the city in which a conference took place."),
+    /** The Library of Congress Call Number. I've also seen this as lib-congress. */
+    LCCN("LCCN", "The Library of Congress Call Number. I've also seen this as lib-congress."),
+    /** The Mathematical Reviews number. */
+    MRNUMBER("mrnumber", "The Mathematical Reviews number."),
+    /** The price of the document. */
+    PRICE("price", "The price of the document."),
+    /** The physical dimensions of a work. */
+    SIZE("size", "The physical dimensions of a work."),
+    /** The WWW Universal Resource Locator that points to the item being referenced. This often is used for technical reports to point to the ftp site where the postscript source of the report is located. */
+    URL("URL", "The WWW Universal Resource Locator that points to the item being referenced. This often is used for technical reports to point to the ftp site where the postscript source of the report is located."),
+    // additional fields
+    /** A link to a postscript file. */
+    PS("PS", "A link to a postscript file."),
+    /** A link to a postscript file. */
+    PDF("PDF", "A link to a PDF file."),
+    /** A link to a postscript file. */
+    HTTP("HTTP", "A hyperlink to a resource.");
+    
+    /** the string used in toString()  */
+    protected String m_Display;
+    
+    /** the comment about this type */
+    protected String m_Comment;
+    
+    /**
+     * the constructor
+     * 
+     * @param display	the string to return in toString()
+     * @param comment	a comment about the type
+     */
+    private Field(String display, String comment) {
+      m_Display = display;
+      m_Comment = comment;
+    }
+    
+    /**
+     * returns the display string
+     * 
+     * @return		the display string
+     */
+    public String getDisplay() {
+      return m_Display;
+    }
+    
+    /**
+     * returns the comment string
+     * 
+     * @return		the comment string
+     */
+    public String getComment() {
+      return m_Comment;
+    }
+    
+    /**
+     * returns the display string of the Type
+     * 
+     * @return		the display string
+     */
+    public String toString() {
+      return m_Display;
+    }
+  }
+
+  /** will be returned if no ID can be generated */
+  protected final static String MISSING_ID = "missing_id";
+  
+  /** the type of this technical information */
+  protected Type m_Type = null;
+  
+  /** the unique identifier of this information, will be generated 
+   * automatically if left empty */
+  protected String m_ID = "";
+  
+  /** stores all the values associated with the fields (FIELD - String) */
+  protected Hashtable<Field,String> m_Values = new Hashtable<Field,String>();
+
+  /** additional technical informations */
+  protected Vector<TechnicalInformation> m_Additional = new Vector<TechnicalInformation>();
+  
+  /**
+   * Initializes the information with the given type
+   * 
+   * @param type	the type of this information
+   * @see Type
+   */
+  public TechnicalInformation(Type type) {
+    this(type, "");
+  }
+
+  /**
+   * Initializes the information with the given type
+   * 
+   * @param type	the type of this information
+   * @param id		the unique ID (for BibTex), can be empty
+   * @see Type
+   */
+  public TechnicalInformation(Type type, String id) {
+    m_Type = type;
+    m_ID   = id;
+  }
+  
+  /**
+   * returns the type of this technical information
+   * 
+   * @return		the type of this information
+   */
+  public Type getType() {
+    return m_Type;
+  }
+  
+  /**
+   * splits the authors on the " and " and returns a vector with the names
+   * 
+   * @return		the authors in an array
+   */
+  protected String[] getAuthors() {
+    return getValue(Field.AUTHOR).split(" and ");
+  }
+  
+  /**
+   * Generates an ID based on the current settings and returns it. If nothing
+   * can be generated the MISSING_ID string will be returned
+   * 
+   * @return		the ID
+   * @see #MISSING_ID
+   */
+  protected String generateID() {
+    String 	result;
+    String[]	authors;
+    String[]	parts;
+    
+    result = m_ID;
+    
+    // try surname of first author and year
+    if (result.length() == 0) {
+      if (exists(Field.AUTHOR) && exists(Field.YEAR)) {
+	authors = getAuthors();
+	if (authors[0].indexOf(",") > -1) {
+	  parts = authors[0].split(",");
+	  result = parts[0];
+	}
+	else {
+	  parts = authors[0].split(" ");
+	  if (parts.length == 1)
+	    result = parts[0];
+	  else
+	    result = parts[parts.length - 1];
+	}
+	result += getValue(Field.YEAR);
+	result = result.replaceAll(" ", "");
+      }
+    }
+    
+    // still nothing?
+    if (result.length() == 0)
+      result = MISSING_ID;
+    
+    return result;
+  }
+  
+  /**
+   * returns the unique ID (either the one used in creating this instance
+   * or the automatically generated one)
+   * 
+   * @return		the ID for this information
+   */
+  public String getID() {
+    return generateID();
+  }
+  
+  /**
+   * sets the value for the given field, overwrites any previously existing one.
+   * 
+   * @param field	the field to set the value for
+   * @param value	the value of the field
+   */
+  public void setValue(Field field, String value) {
+    m_Values.put(field, value);
+  }
+  
+  /**
+   * returns the value associated with the given field, or empty if field is
+   * not currently stored.
+   * 
+   * @param field	the field to retrieve the value for
+   * @return		the value associated with this field, empty if not existing
+   */
+  public String getValue(Field field) {
+    if (m_Values.containsKey(field))
+      return (String) m_Values.get(field);
+    else
+      return "";
+  }
+  
+  /**
+   * returns TRUE if the field is stored and has a value different from the
+   * empty string.
+   * 
+   * @param field	the field to check
+   * @return 		true if a value is stored for the field and non-empty
+   */
+  public boolean exists(Field field) {
+    return (    m_Values.containsKey(field) 
+	     && (((String) m_Values.get(field)).length() != 0) );
+  }
+  
+  /**
+   * returns an enumeration over all the stored fields
+   * 
+   * @return		all currently stored fields
+   */
+  public Enumeration<Field> fields() {
+    return m_Values.keys();
+  }
+  
+  /**
+   * returns true if there are further technical informations stored in this
+   * 
+   * @return true if there are further technical informations available
+   */
+  public boolean hasAdditional() {
+    return (m_Additional.size() > 0);
+  }
+  
+  /**
+   * returns an enumeration of all the additional technical informations (if
+   * there are any)
+   * 
+   * @return an enumeration over all additional technical informations
+   */
+  public Enumeration<TechnicalInformation> additional() {
+    return m_Additional.elements();
+  }
+  
+  /**
+   * adds the given information to the list of additional technical 
+   * informations
+   * 
+   * @param value the information to add
+   */
+  public void add(TechnicalInformation value) {
+    if (value == this)
+      throw new IllegalArgumentException("Can't add object to itself!");
+    m_Additional.add(value);
+  }
+  /**
+   * Adds an empty technical information with the given type to the list
+   * of additional informations and returns the instance.
+   * 
+   * @param type	the type of the new information to add
+   * @return 		the generated information
+   */
+  public TechnicalInformation add(Type type) {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(type);
+    add(result);
+    
+    return result;
+  }
+  
+  /**
+   * Returns a plain-text string representing this technical information.
+   * Note: it only returns a string based on some fields. At least AUTHOR,
+   * YEAR and TITLE are necessary.
+   * 
+   * @return		a string representation of this information
+   */
+  public String toString() {
+    String	result;
+    String[]	authors;
+    int		i;
+    Enumeration<TechnicalInformation>	enm;
+    
+    result  = "";
+    authors = getAuthors();
+    
+    // BOOK
+    if (getType() == Type.BOOK) {
+      for (i = 0; i < authors.length; i++) {
+	if (i > 0)
+	  result += ", ";
+	result += authors[i];
+      }
+      if (exists(Field.YEAR))
+	result += " (" + getValue(Field.YEAR) + ").";
+      else
+	result += ".";
+      result += " " + getValue(Field.TITLE) + ".";
+      result += " " + getValue(Field.PUBLISHER);
+      if (exists(Field.ADDRESS))
+	result += ", " + getValue(Field.ADDRESS);
+      result += ".";
+    }
+    // ARTICLE
+    else if (getType() == Type.ARTICLE) {
+      for (i = 0; i < authors.length; i++) {
+	if (i > 0)
+	  result += ", ";
+	result += authors[i];
+      }
+      if (exists(Field.YEAR))
+	result += " (" + getValue(Field.YEAR) + ").";
+      else
+	result += ".";
+      result += " " + getValue(Field.TITLE) + ".";
+      
+      // journal
+      if (exists(Field.JOURNAL)) {
+	result += " " + getValue(Field.JOURNAL) + ".";
+
+	if (exists(Field.VOLUME))
+	  result += " " + getValue(Field.VOLUME);
+	if (exists(Field.NUMBER))
+	  result += "(" + getValue(Field.NUMBER) + ")";
+	if (exists(Field.PAGES))
+	  result += ":" + getValue(Field.PAGES);
+	
+	result += ".";
+      }
+      
+      // other than JOURNAL???
+
+      // URL
+      if (exists(Field.URL))
+	result += " URL " + getValue(Field.URL) + ".";
+    }
+    // CONFERENCE/INPROCEEDINGS
+    else if ( (getType() == Type.CONFERENCE) || (getType() == Type.INPROCEEDINGS) ) {
+      for (i = 0; i < authors.length; i++) {
+	if (i > 0)
+	  result += ", ";
+	result += authors[i];
+      }
+      result += ": " + getValue(Field.TITLE) + ".";
+      result += " In: " + getValue(Field.BOOKTITLE);
+      
+      if (exists(Field.ADDRESS))
+	result += ", " + getValue(Field.ADDRESS);
+      if (exists(Field.PAGES))
+	result += ", " + getValue(Field.PAGES);
+	
+      if (exists(Field.YEAR))
+	result += ", " + getValue(Field.YEAR) + ".";
+      else
+	result += ".";
+    }
+    // INCOLLECTION
+    else if (getType() == Type.INCOLLECTION) {
+      for (i = 0; i < authors.length; i++) {
+	if (i > 0)
+	  result += ", ";
+	result += authors[i];
+      }
+      result += ": " + getValue(Field.TITLE) + ".";
+      result += " In ";
+      if (exists(Field.EDITOR))
+	result += getValue(Field.EDITOR) + ", editors, ";
+      result += getValue(Field.BOOKTITLE);
+      
+      if (exists(Field.ADDRESS))
+	result += ", " + getValue(Field.ADDRESS);
+      if (exists(Field.PAGES))
+	result += ", " + getValue(Field.PAGES);
+	
+      if (exists(Field.YEAR))
+	result += ", " + getValue(Field.YEAR) + ".";
+      else
+	result += ".";
+    }
+    // default
+    else {
+      for (i = 0; i < authors.length; i++) {
+	if (i > 0)
+	  result += ", ";
+	result += authors[i];
+      }
+      if (exists(Field.YEAR))
+	result += " (" + getValue(Field.YEAR) + ").";
+      else
+	result += ".";
+      result += " " + getValue(Field.TITLE) + ".";
+      if (exists(Field.ADDRESS))
+	result += " " + getValue(Field.ADDRESS) + ".";
+      if (exists(Field.URL))
+	result += " URL " + getValue(Field.URL) + ".";
+    }
+    
+    // additional informations?
+    enm = additional();
+    while (enm.hasMoreElements()) {
+      result += "\n\n" + enm.nextElement().toString();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns a BibTex string representing this technical information. 
+   * Note: this is just a very raw implementation, special characters need to
+   * be escaped manually for LaTeX.
+   * 
+   * @return		the BibTeX representation of this information
+   */
+  public String toBibTex() {
+    String		result;
+    Field		field;
+    Vector<Field>		list;
+    int			i;
+    String		value;
+    
+    result = "@" + getType() + "{" + getID() + "";
+    
+    // sort the fields
+    list = new Vector<Field>();
+    Enumeration<Field> enm  = fields();
+    while (enm.hasMoreElements())
+      list.add(enm.nextElement());
+    Collections.sort(list);
+    
+    // list field=value pairs
+    for (i = 0; i < list.size(); i++) {
+      field = (Field) list.get(i);
+      if (!exists(field))
+	continue;
+      value = getValue(field);
+      value = value.replaceAll("\\~", "\\\\~");
+      result += ",\n   " + field + " = {" + value +  "}";
+    }
+    
+    result += "\n}";
+    
+    // additional informations?
+    Enumeration<TechnicalInformation> enm2 = additional();
+    while (enm2.hasMoreElements()) {
+      result += "\n\n" + ((TechnicalInformation) enm2.nextElement()).toBibTex();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Prints some examples of technical informations if there are no 
+   * commandline options given. Otherwise the information of a given 
+   * TechnicalInformationHandler can be printed. <p/>
+   * 
+   * Valid options are: <p/>
+   * 
+   * -W classname <br/>
+   *  The classname of the TechnicalInformationHandler to print the 
+   *  information for <p/>
+   *  
+   * -bibtex <br/>
+   *  Print the information in BibTeX format <p/>
+   *  
+   * -plaintext <br/>
+   *  Print the information in plain text format <p/>
+   * 
+   * @param args 	the commandline options
+   * @throws Exception	if the option parsing fails
+   */
+  public static void main(String[] args) throws Exception {
+    TechnicalInformation	info;
+    TechnicalInformation	additional;
+    String			tmpStr;
+    Class			cls;
+    TechnicalInformationHandler	handler;
+    
+    // example from command line
+    if (args.length != 0) {
+      info = null;
+      
+      tmpStr = Utils.getOption('W', args);
+      if (tmpStr.length() != 0) {
+	cls     = Class.forName(tmpStr);
+	handler = (TechnicalInformationHandler) cls.newInstance();
+	info    = handler.getTechnicalInformation();
+      }
+      else {
+	throw new IllegalArgumentException("A classname has to be provided with the -W option!");
+      }
+      
+      if (Utils.getFlag("bibtex", args))
+        System.out.println("\n" + handler.getClass().getName() + ":\n" + info.toBibTex());
+
+      if (Utils.getFlag("plaintext", args))
+        System.out.println("\n" + handler.getClass().getName() + ":\n" + info.toString());
+    }
+    else {
+      // first example
+      info = new TechnicalInformation(Type.BOOK);
+      info.setValue(Field.AUTHOR, "Ross Quinlan");
+      info.setValue(Field.YEAR, "1993");
+      info.setValue(Field.TITLE, "C4.5: Programs for Machine Learning");
+      info.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+      info.setValue(Field.ADDRESS, "San Mateo, CA");
+      additional = info;
+      
+      System.out.println("\ntoString():\n" + info.toString());
+      System.out.println("\ntoBibTex():\n" + info.toBibTex());
+      
+      // second example
+      info = new TechnicalInformation(Type.INPROCEEDINGS);
+      info.setValue(Field.AUTHOR, "Freund, Y. and Mason, L.");
+      info.setValue(Field.YEAR, "1999");
+      info.setValue(Field.TITLE, "The alternating decision tree learning algorithm");
+      info.setValue(Field.BOOKTITLE, "Proceeding of the Sixteenth International Conference on Machine Learning");
+      info.setValue(Field.ADDRESS, "Bled, Slovenia");
+      info.setValue(Field.PAGES, "124-133");
+      
+      System.out.println("\ntoString():\n" + info.toString());
+      System.out.println("\ntoBibTex():\n" + info.toBibTex());
+      
+      // third example
+      info = new TechnicalInformation(Type.ARTICLE);
+      info.setValue(Field.AUTHOR, "R. Quinlan");
+      info.setValue(Field.YEAR, "1986");
+      info.setValue(Field.TITLE, "Induction of decision trees");
+      info.setValue(Field.JOURNAL, "Machine Learning");
+      info.setValue(Field.VOLUME, "1");
+      info.setValue(Field.NUMBER, "1");
+      info.setValue(Field.PAGES, "81-106");
+      
+      additional = new TechnicalInformation(Type.BOOK);
+      additional.setValue(Field.AUTHOR, "Ross Quinlan");
+      additional.setValue(Field.YEAR, "1993");
+      additional.setValue(Field.TITLE, "C4.5: Programs for Machine Learning");
+      additional.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+      additional.setValue(Field.ADDRESS, "San Mateo, CA");
+      info.add(additional);
+      
+      System.out.println("\ntoString():\n" + info.toString());
+      System.out.println("\ntoBibTex():\n" + info.toBibTex());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandler.java	(revision 29)
@@ -0,0 +1,42 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TechnicalInformationHandler.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+/**
+ * For classes that are based on some kind of publications. They return
+ * a TechnicalInformation object filled with the data of the publication.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see TechnicalInformation
+ */
+public interface TechnicalInformationHandler {
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation();
+}
Index: branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandlerJavadoc.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandlerJavadoc.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/TechnicalInformationHandlerJavadoc.java	(revision 29)
@@ -0,0 +1,225 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TechnicalInformationHandlerJavadoc.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Generates Javadoc comments from the TechnicalInformationHandler's data.
+ * Update the BibTex references and the plaintext techincal information. 
+ * <p/>
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The class to load.</pre>
+ * 
+ * <pre> -nostars
+ *  Suppresses the '*' in the Javadoc.</pre>
+ * 
+ * <pre> -dir &lt;dir&gt;
+ *  The directory above the package hierarchy of the class.</pre>
+ * 
+ * <pre> -silent
+ *  Suppresses printing in the console.</pre>
+ * 
+ * <pre> -noprolog
+ *  Suppresses the 'BibTex:' prolog in the Javadoc.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see #PLAINTEXT_STARTTAG
+ * @see #PLAINTEXT_ENDTAG
+ * @see #BIBTEX_STARTTAG
+ * @see #BIBTEX_ENDTAG
+ */
+public class TechnicalInformationHandlerJavadoc 
+  extends Javadoc {
+  
+  /** the start comment tag for inserting the generated BibTex */
+  public final static String PLAINTEXT_STARTTAG = "<!-- technical-plaintext-start -->";
+  
+  /** the end comment tag for inserting the generated BibTex */
+  public final static String PLAINTEXT_ENDTAG = "<!-- technical-plaintext-end -->";
+  
+  /** the start comment tag for inserting the generated BibTex */
+  public final static String BIBTEX_STARTTAG = "<!-- technical-bibtex-start -->";
+  
+  /** the end comment tag for inserting the generated BibTex */
+  public final static String BIBTEX_ENDTAG = "<!-- technical-bibtex-end -->";
+  
+  /** whether to include the "Valid options..." prolog in the Javadoc */
+  protected boolean m_Prolog = true;
+  
+  /**
+   * default constructor 
+   */
+  public TechnicalInformationHandlerJavadoc() {
+    super();
+    
+    m_StartTag    = new String[2];
+    m_EndTag      = new String[2];
+    m_StartTag[0] = PLAINTEXT_STARTTAG;
+    m_EndTag[0]   = PLAINTEXT_ENDTAG;
+    m_StartTag[1] = BIBTEX_STARTTAG;
+    m_EndTag[1]   = BIBTEX_ENDTAG;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>        result;
+    Enumeration   en;
+    
+    result = new Vector<Option>();
+    
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+
+    result.addElement(new Option(
+        "\tSuppresses the 'BibTex:' prolog in the Javadoc.",
+        "noprolog", 0, "-noprolog"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    setProlog(!Utils.getFlag("noprolog", options));
+  }
+  
+  /**
+   * Gets the current settings of this object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (!getProlog())
+      result.add("-noprolog");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets whether to add the "Valid options are..." prolog
+   * 
+   * @param value	true if the prolog is to be used
+   */
+  public void setProlog(boolean value) {
+    m_Prolog = value;
+  }
+  
+  /**
+   * whether "Valid options are..." prolog is included in the Javadoc
+   * 
+   * @return		whether the prolog is currently used
+   */
+  public boolean getProlog() {
+    return m_Prolog;
+  }
+  
+  /**
+   * generates and returns the Javadoc for the specified start/end tag pair.
+   * 
+   * @param index	the index in the start/end tag array
+   * @return		the generated Javadoc
+   * @throws Exception 	in case the generation fails
+   */
+  protected String generateJavadoc(int index) throws Exception {
+    String			result;
+    TechnicalInformationHandler	handler;
+    
+    result = "";
+    
+    if (!canInstantiateClass())
+      return result;
+    
+    if (!ClassDiscovery.hasInterface(TechnicalInformationHandler.class, getInstance().getClass()))
+	throw new Exception("Class '" + getClassname() + "' is not a TechnicalInformationHandler!");
+
+    handler = (TechnicalInformationHandler) getInstance();
+    
+    switch (index) {
+      case 0:  // plain text
+	result = toHTML(handler.getTechnicalInformation().toString()) + "\n";
+	break;
+	
+      case 1:  // bibtex
+	// prolog?
+	if (getProlog())
+	  result = "BibTeX:\n";
+	result += "<pre>\n";
+	result += toHTML(handler.getTechnicalInformation().toBibTex()).replaceAll("<br/>", "") + "\n";
+	result += "</pre>\n<p/>\n";
+	break;
+    }
+    
+    // stars?
+    if (getUseStars()) 
+      result = indent(result, 1, "* ");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Parses the given commandline parameters and generates the Javadoc.
+   * 
+   * @param args	the commandline parameters for the object
+   */
+  public static void main(String[] args) {
+    runJavadoc(new TechnicalInformationHandlerJavadoc(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Tee.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Tee.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Tee.java	(revision 29)
@@ -0,0 +1,548 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Tee.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.Vector;
+
+/**
+* This class pipelines print/println's to several PrintStreams. Useful for
+* redirecting System.out and System.err to files etc.<br/>
+* E.g., for redirecting stderr/stdout to files with timestamps and:<br/>
+* <pre>
+*    import java.io.*;
+*    import weka.core.Tee;
+*
+*    ...
+*    // stdout
+*    Tee teeOut = new Tee(System.out);
+*    teeOut.add(new PrintStream(new FileOutputStream("out.txt")), true);
+*    System.setOut(teeOut);
+*    
+*    // stderr
+*    Tee teeErr = new Tee(System.err);
+*    teeErr.add(new PrintStream(new FileOutputStream("err.txt")), true);
+*    System.setOut(teeErr);
+*    ...
+* </pre>
+*
+* @author   FracPete (fracpete at waikato dot ac dot nz)
+* @version  $Revision: 5953 $
+*/
+
+public class Tee
+  extends PrintStream
+  implements RevisionHandler {
+  
+  /** the different PrintStreams. */
+  protected Vector<PrintStream> m_Streams = new Vector<PrintStream>();
+  
+  /** whether to add timestamps or not. */
+  protected Vector<Boolean> m_Timestamps = new Vector<Boolean>();
+  
+  /** whether to add a prefix or not. */
+  protected Vector<String> m_Prefixes = new Vector<String>();
+  
+  /** the default printstream. */
+  protected PrintStream m_Default = null;
+
+  /**
+   * initializes the object, with a default printstream.
+   */
+  public Tee() {
+    this(null);
+  }
+
+  /**
+   * initializes the object with the given default printstream, e.g.,
+   * System.out.
+   * 
+   * @param def     the default printstream, remains also after calling clear()
+   */
+  public Tee(PrintStream def) {
+    super(def);
+
+    m_Default = def;
+    clear();
+  }
+
+  /**
+   * removes all streams and places the default printstream, if any, again in
+   * the list.
+   * 
+   * @see #getDefault()
+   */
+  public void clear() {
+    m_Streams.clear();
+    m_Timestamps.clear();
+    m_Prefixes.clear();
+    
+    if (getDefault() != null)
+      add(getDefault());
+  }
+
+  /**
+   * returns the default printstrean, can be NULL.
+   * 
+   * @return the default printstream
+   * @see #m_Default
+   */
+  public PrintStream getDefault() {
+    return m_Default;
+  }
+
+  /**
+   * adds the given PrintStream to the list of streams, with NO timestamp and
+   * NO prefix.
+   * 
+   * @param p       the printstream to add
+   */
+  public void add(PrintStream p) {
+    add(p, false);
+  }
+
+  /**
+   * adds the given PrintStream to the list of streams, with NO prefix.
+   * 
+   * @param p           the printstream to add
+   * @param timestamp   whether to use timestamps or not
+   */
+  public void add(PrintStream p, boolean timestamp) {
+    add(p, timestamp, "");
+  }
+
+  /**
+   * adds the given PrintStream to the list of streams.
+   * 
+   * @param p           the printstream to add
+   * @param timestamp   whether to use timestamps or not
+   * @param prefix      the prefix to use
+   */
+  public void add(PrintStream p, boolean timestamp, String prefix) {
+    if (m_Streams.contains(p))
+      remove(p);
+
+    // make sure it's not null
+    if (prefix == null)
+      prefix = "";
+
+    m_Streams.add(p);
+    m_Timestamps.add(new Boolean(timestamp));
+    m_Prefixes.add(prefix);
+  }
+
+  /**
+   * returns the specified PrintStream from the list.
+   * 
+   * @param index the index of the PrintStream to return
+   * @return the specified PrintStream, or null if invalid index
+   */
+  public PrintStream get(int index) {
+    if ( (index >= 0) && (index < size()) )
+      return (PrintStream) m_Streams.get(index);
+    else
+      return null;
+  }
+
+  /**
+   * removes the given PrintStream from the list.
+   * 
+   * @param p the PrintStream to remove
+   * @return returns the removed PrintStream if it could be removed, null otherwise
+   */
+  public PrintStream remove(PrintStream p) {
+    int         index;
+
+    if (contains(p)) {
+      index = m_Streams.indexOf(p);
+      m_Timestamps.remove(index);
+      m_Prefixes.remove(index);
+      return (PrintStream) m_Streams.remove(index);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * removes the given PrintStream from the list.
+   * 
+   * @param index the index of the PrintStream to remove
+   * @return returns the removed PrintStream if it could be removed, null otherwise
+   */
+  public PrintStream remove(int index) {
+    if ( (index >= 0) && (index < size()) ) {
+      m_Timestamps.remove(index);
+      m_Prefixes.remove(index);
+      return (PrintStream) m_Streams.remove(index);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * checks whether the given PrintStream is already in the list.
+   * 
+   * @param p the PrintStream to look for
+   * @return true if the PrintStream is in the list
+   */
+  public boolean contains(PrintStream p) {
+    return m_Streams.contains(p);
+  }
+
+  /**
+   * returns the number of streams currently in the list.
+   * 
+   * @return the number of streams in the list
+   */
+  public int size() {
+    return m_Streams.size();
+  }
+
+  /**
+   * prints the prefix/timestamp (timestampe only to those streams that want
+   * one).
+   */
+  private void printHeader() {
+    for (int i = 0; i < size(); i++) {
+      // prefix
+      if (!((String) m_Prefixes.get(i)).equals(""))
+        ((PrintStream) m_Streams.get(i)).print("[" + m_Prefixes.get(i) + "]\t");
+      
+      // timestamp
+      if (((Boolean) m_Timestamps.get(i)).booleanValue())
+        ((PrintStream) m_Streams.get(i)).print("[" + new Date() + "]\t");
+    }
+  }
+
+  /**
+   * flushes all the printstreams.
+   */
+  public void flush() {
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).flush();
+  }
+
+  /**
+   * prints the given int to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(int x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given long to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(long x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given float to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(float x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given double to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(double x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given boolean to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(boolean x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given char to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(char x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given char array to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(char[] x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given string to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(String x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints the given object to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void print(Object x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).print(x);
+    flush();
+  }
+
+  /**
+   * prints a new line to the streams.
+   */
+  public void println() {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println();
+    flush();
+  }
+
+  /**
+   * prints the given int to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(int x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given long to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(long x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given float to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(float x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given double to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(double x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given boolean to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(boolean x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given char to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(char x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given char array to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(char[] x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given string to the streams.
+   * 
+   * @param x the object to print
+   */
+  public void println(String x) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * prints the given object to the streams (for Throwables we print the stack
+   * trace).
+   * 
+   * @param x the object to print
+   */
+  public void println(Object x) {
+    String                  line;
+    Throwable               t;
+    StackTraceElement[]     trace;
+    int                     i;
+
+    if (x instanceof Throwable) {
+      t     = (Throwable) x;
+      trace = t.getStackTrace();
+      line  = t.toString() + "\n";
+      for (i = 0; i < trace.length; i++)
+        line += "\t" + trace[i].toString() + "\n";
+      x = line;
+    }
+
+    printHeader();
+    for (i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).println(x);
+    flush();
+  }
+
+  /**
+   * Writes <code>len</code> bytes from the specified byte array starting at
+   * offset <code>off</code> to this stream.  If automatic flushing is
+   * enabled then the <code>flush</code> method will be invoked.
+   *
+   * <p> Note that the bytes will be written as given; to write characters
+   * that will be translated according to the platform's default character
+   * encoding, use the <code>print(char)</code> or <code>println(char)</code>
+   * methods.
+   *
+   * @param  buf   A byte array
+   * @param  off   Offset from which to start taking bytes
+   * @param  len   Number of bytes to write
+   */
+  public void write(byte buf[], int off, int len) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).write(buf, off, len);
+    flush();
+  }
+
+  /**
+   * Writes the specified byte to this stream.  If the byte is a newline and
+   * automatic flushing is enabled then the <code>flush</code> method will be
+   * invoked.
+   *
+   * <p> Note that the byte is written as given; to write a character that
+   * will be translated according to the platform's default character
+   * encoding, use the <code>print(char)</code> or <code>println(char)</code>
+   * methods.
+   *
+   * @param  b  The byte to be written
+   * @see #print(char)
+   * @see #println(char)
+   */
+  public void write(int b) {
+    printHeader();
+    for (int i = 0; i < size(); i++)
+      ((PrintStream) m_Streams.get(i)).write(b);
+    flush();
+  }
+
+  /**
+   * returns only the classname and the number of streams.
+   * 
+   * @return only the classname and the number of streams
+   */
+  public String toString() {
+    return this.getClass().getName() + ": " + m_Streams.size();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/TestInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/TestInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/TestInstances.java	(revision 29)
@@ -0,0 +1,1785 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TestInstances.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.core.Capabilities.Capability;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ * Generates artificial datasets for testing. In case of Multi-Instance data
+ * the settings for the number of attributes applies to the data inside
+ * the bag. Originally based on code from the CheckClassifier.<p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -relation &lt;name&gt;
+ *  The name of the data set.</pre>
+ * 
+ * <pre> -seed &lt;num&gt;
+ *  The seed value.</pre>
+ * 
+ * <pre> -num-instances &lt;num&gt;
+ *  The number of instances in the datasets (default 20).</pre>
+ * 
+ * <pre> -class-type &lt;num&gt;
+ *  The class type, see constants in weka.core.Attribute
+ *  (default 1=nominal).</pre>
+ * 
+ * <pre> -class-values &lt;num&gt;
+ *  The number of classes to generate (for nominal classes only)
+ *  (default 2).</pre>
+ * 
+ * <pre> -class-index &lt;num&gt;
+ *  The class index, with -1=last, (default -1).</pre>
+ * 
+ * <pre> -no-class
+ *  Doesn't include a class attribute in the output.</pre>
+ * 
+ * <pre> -nominal &lt;num&gt;
+ *  The number of nominal attributes (default 1).</pre>
+ * 
+ * <pre> -nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes (default 2).</pre>
+ * 
+ * <pre> -numeric &lt;num&gt;
+ *  The number of numeric attributes (default 0).</pre>
+ * 
+ * <pre> -string &lt;num&gt;
+ *  The number of string attributes (default 0).</pre>
+ * 
+ * <pre> -words &lt;comma-separated-list&gt;
+ *  The words to use in string attributes.</pre>
+ * 
+ * <pre> -word-separators &lt;chars&gt;
+ *  The word separators to use in string attributes.</pre>
+ * 
+ * <pre> -date &lt;num&gt;
+ *  The number of date attributes (default 0).</pre>
+ * 
+ * <pre> -relational &lt;num&gt;
+ *  The number of relational attributes (default 0).</pre>
+ * 
+ * <pre> -relational-nominal &lt;num&gt;
+ *  The number of nominal attributes in a rel. attribute (default 1).</pre>
+ * 
+ * <pre> -relational-nominal-values &lt;num&gt;
+ *  The number of values for nominal attributes in a rel. attribute (default 2).</pre>
+ * 
+ * <pre> -relational-numeric &lt;num&gt;
+ *  The number of numeric attributes in a rel. attribute (default 0).</pre>
+ * 
+ * <pre> -relational-string &lt;num&gt;
+ *  The number of string attributes in a rel. attribute (default 0).</pre>
+ * 
+ * <pre> -relational-date &lt;num&gt;
+ *  The number of date attributes in a rel. attribute (default 0).</pre>
+ * 
+ * <pre> -num-instances-relational &lt;num&gt;
+ *  The number of instances in relational/bag attributes (default 10).</pre>
+ * 
+ * <pre> -multi-instance
+ *  Generates multi-instance data.</pre>
+ * 
+ * <pre> -W &lt;classname&gt;
+ *  The Capabilities handler to base the dataset on.
+ *  The other parameters can be used to override the ones
+ *  determined from the handler. Additional parameters for
+ *  handler can be passed on after the '--'.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see weka.classifiers.CheckClassifier
+ */
+public class TestInstances 
+  implements Cloneable, Serializable, OptionHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6263968936330390469L;
+
+  /** can be used for settting the class attribute index to last 
+   * @see #setClassIndex(int) */
+  public final static int CLASS_IS_LAST = -1;
+  
+  /** can be used to avoid generating a class attribute
+   * @see #setClassIndex(int) */
+  public final static int NO_CLASS = -2;
+
+  /** the default list of words used in strings */
+  public final static String[] DEFAULT_WORDS = {"The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"};
+
+  /** the default word separators used in strings */
+  public final static String DEFAULT_SEPARATORS = " ";
+  
+  /** for generating String attributes/classes */
+  protected String[] m_Words = DEFAULT_WORDS;
+  
+  /** for generating String attributes/classes */
+  protected String m_WordSeparators = DEFAULT_SEPARATORS;
+  
+  /** the name of the relation */
+  protected String m_Relation = "Testdata";
+  
+  /** the seed value */
+  protected int m_Seed = 1;
+  
+  /** the random number generator */
+  protected Random m_Random = new Random(m_Seed);
+  
+  /** the number of instances */
+  protected int m_NumInstances = 20;
+  
+  /** the class type */
+  protected int m_ClassType = Attribute.NOMINAL;
+  
+  /** the number of classes (in case of NOMINAL class) */
+  protected int m_NumClasses = 2;
+
+  /** the class index (-1 is LAST, -2 means no class) 
+   * @see #CLASS_IS_LAST
+   * @see #NO_CLASS */
+  protected int m_ClassIndex = CLASS_IS_LAST;
+  
+  /** the number of nominal attributes */
+  protected int m_NumNominal = 1;
+  
+  /** the number of values for nominal attributes */
+  protected int m_NumNominalValues = 2;
+  
+  /** the number of numeric attributes */
+  protected int m_NumNumeric = 0;
+  
+  /** the number of string attributes */
+  protected int m_NumString = 0;
+  
+  /** the number of date attributes */
+  protected int m_NumDate = 0;
+  
+  /** the number of relational attributes */
+  protected int m_NumRelational = 0;
+  
+  /** the number of nominal attributes in a relational attribute */
+  protected int m_NumRelationalNominal = 1;
+  
+  /** the number of values for nominal attributes in relational attributes */
+  protected int m_NumRelationalNominalValues = 2;
+  
+  /** the number of numeric attributes in a relational attribute */
+  protected int m_NumRelationalNumeric = 0;
+  
+  /** the number of string attributes in a relational attribute */
+  protected int m_NumRelationalString = 0;
+  
+  /** the number of date attributes in a relational attribute */
+  protected int m_NumRelationalDate = 0;
+  
+  /** whether to generate Multi-Instance data or not */
+  protected boolean m_MultiInstance = false;
+  
+  /** the number of instances in relational attributes (applies also for bags
+   * in multi-instance) */
+  protected int m_NumInstancesRelational = 10;
+  
+  /** the format of the multi-instance data */
+  protected Instances[] m_RelationalFormat = null;
+  
+  /** the format of the multi-instance data of the class */
+  protected Instances m_RelationalClassFormat = null;
+  
+  /** the generated data */
+  protected Instances m_Data = null;
+  
+  /** the CapabilitiesHandler to get the Capabilities from */
+  protected CapabilitiesHandler m_Handler = null;
+
+  /**
+   * the default constructor
+   */
+  public TestInstances() {
+    super();
+    
+    setRelation("Testdata");
+    setSeed(1);
+    setNumInstances(20);
+    setClassType(Attribute.NOMINAL);
+    setNumClasses(2);
+    setClassIndex(CLASS_IS_LAST);
+    setNumNominal(1);
+    setNumNominalValues(2);
+    setNumNumeric(0);
+    setNumString(0);
+    setNumDate(0);
+    setNumRelational(0);
+    setNumRelationalNominal(1);
+    setNumRelationalNominalValues(2);
+    setNumRelationalNumeric(0);
+    setNumRelationalString(0);
+    setNumRelationalDate(0);
+    setNumInstancesRelational(10);
+    setMultiInstance(false);
+    setWords(arrayToList(DEFAULT_WORDS));
+    setWordSeparators(DEFAULT_SEPARATORS);
+  }
+  
+  /**
+   * creates a clone of the current object
+   * 
+   * @return		a clone of the current object
+   */
+  public Object clone() {
+    TestInstances     result;
+    
+    result = new TestInstances();
+    result.assign(this);
+    
+    return result;
+  }
+  
+  /**
+   * updates itself with all the settings from the given TestInstances
+   * object
+   * 
+   * @param t		the object to get the settings from
+   */
+  public void assign(TestInstances t) {
+    setRelation(t.getRelation());
+    setSeed(t.getSeed());
+    setNumInstances(t.getNumInstances());
+    setClassType(t.getClassType());
+    setNumClasses(t.getNumClasses());
+    setClassIndex(t.getClassIndex());
+    setNumNominal(t.getNumNominal());
+    setNumNominalValues(t.getNumNominalValues());
+    setNumNumeric(t.getNumNumeric());
+    setNumString(t.getNumString());
+    setNumDate(t.getNumDate());
+    setNumRelational(t.getNumRelational());
+    setNumRelationalNominal(t.getNumRelationalNominal());
+    setNumRelationalNominalValues(t.getNumRelationalNominalValues());
+    setNumRelationalNumeric(t.getNumRelationalNumeric());
+    setNumRelationalString(t.getNumRelationalString());
+    setNumRelationalDate(t.getNumRelationalDate());
+    setMultiInstance(t.getMultiInstance());
+    for (int i = 0; i < t.getNumRelational(); i++)
+      setRelationalFormat(i, t.getRelationalFormat(i));
+    setRelationalClassFormat(t.getRelationalClassFormat());
+    setNumInstancesRelational(t.getNumInstancesRelational());
+    setWords(t.getWords());
+    setWordSeparators(t.getWordSeparators());
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.add(new Option(
+        "\tThe name of the data set.",
+        "relation", 1, "-relation <name>"));
+    
+    result.add(new Option(
+        "\tThe seed value.",
+        "seed", 1, "-seed <num>"));
+    
+    result.add(new Option(
+        "\tThe number of instances in the datasets (default 20).",
+        "num-instances", 1, "-num-instances <num>"));
+    
+    result.add(new Option(
+        "\tThe class type, see constants in weka.core.Attribute\n"
+	+ "\t(default 1=nominal).",
+        "class-type", 1, "-class-type <num>"));
+    
+    result.add(new Option(
+        "\tThe number of classes to generate (for nominal classes only)\n"
+	+ "\t(default 2).",
+        "class-values", 1, "-class-values <num>"));
+    
+    result.add(new Option(
+        "\tThe class index, with -1=last, (default -1).",
+        "class-index", 1, "-class-index <num>"));
+    
+    result.add(new Option(
+        "\tDoesn't include a class attribute in the output.",
+        "no-class", 0, "-no-class"));
+
+    result.add(new Option(
+        "\tThe number of nominal attributes (default 1).",
+        "nominal", 1, "-nominal <num>"));
+    
+    result.add(new Option(
+        "\tThe number of values for nominal attributes (default 2).",
+        "nominal-values", 1, "-nominal-values <num>"));
+    
+    result.add(new Option(
+        "\tThe number of numeric attributes (default 0).",
+        "numeric", 1, "-numeric <num>"));
+    
+    result.add(new Option(
+        "\tThe number of string attributes (default 0).",
+        "string", 1, "-string <num>"));
+    
+    result.add(new Option(
+        "\tThe words to use in string attributes.",
+        "words", 1, "-words <comma-separated-list>"));
+    
+    result.add(new Option(
+        "\tThe word separators to use in string attributes.",
+        "word-separators", 1, "-word-separators <chars>"));
+    
+    result.add(new Option(
+        "\tThe number of date attributes (default 0).",
+        "date", 1, "-date <num>"));
+    
+    result.add(new Option(
+        "\tThe number of relational attributes (default 0).",
+        "relational", 1, "-relational <num>"));
+    
+    result.add(new Option(
+        "\tThe number of nominal attributes in a rel. attribute (default 1).",
+        "relational-nominal", 1, "-relational-nominal <num>"));
+    
+    result.add(new Option(
+        "\tThe number of values for nominal attributes in a rel. attribute (default 2).",
+        "relational-nominal-values", 1, "-relational-nominal-values <num>"));
+    
+    result.add(new Option(
+        "\tThe number of numeric attributes in a rel. attribute (default 0).",
+        "relational-numeric", 1, "-relational-numeric <num>"));
+    
+    result.add(new Option(
+        "\tThe number of string attributes in a rel. attribute (default 0).",
+        "relational-string", 1, "-relational-string <num>"));
+    
+    result.add(new Option(
+        "\tThe number of date attributes in a rel. attribute (default 0).",
+        "relational-date", 1, "-relational-date <num>"));
+    
+    result.add(new Option(
+        "\tThe number of instances in relational/bag attributes (default 10).",
+        "num-instances-relational", 1, "-num-instances-relational <num>"));
+    
+    result.add(new Option(
+        "\tGenerates multi-instance data.",
+        "multi-instance", 0, "-multi-instance"));
+
+    result.add(new Option(
+        "\tThe Capabilities handler to base the dataset on.\n"
+	+ "\tThe other parameters can be used to override the ones\n"
+	+ "\tdetermined from the handler. Additional parameters for\n"
+	+ "\thandler can be passed on after the '--'.",
+        "W", 1, "-W <classname>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -relation &lt;name&gt;
+   *  The name of the data set.</pre>
+   * 
+   * <pre> -seed &lt;num&gt;
+   *  The seed value.</pre>
+   * 
+   * <pre> -num-instances &lt;num&gt;
+   *  The number of instances in the datasets (default 20).</pre>
+   * 
+   * <pre> -class-type &lt;num&gt;
+   *  The class type, see constants in weka.core.Attribute
+   *  (default 1=nominal).</pre>
+   * 
+   * <pre> -class-values &lt;num&gt;
+   *  The number of classes to generate (for nominal classes only)
+   *  (default 2).</pre>
+   * 
+   * <pre> -class-index &lt;num&gt;
+   *  The class index, with -1=last, (default -1).</pre>
+   * 
+   * <pre> -no-class
+   *  Doesn't include a class attribute in the output.</pre>
+   * 
+   * <pre> -nominal &lt;num&gt;
+   *  The number of nominal attributes (default 1).</pre>
+   * 
+   * <pre> -nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes (default 2).</pre>
+   * 
+   * <pre> -numeric &lt;num&gt;
+   *  The number of numeric attributes (default 0).</pre>
+   * 
+   * <pre> -string &lt;num&gt;
+   *  The number of string attributes (default 0).</pre>
+   * 
+   * <pre> -words &lt;comma-separated-list&gt;
+   *  The words to use in string attributes.</pre>
+   * 
+   * <pre> -word-separators &lt;chars&gt;
+   *  The word separators to use in string attributes.</pre>
+   * 
+   * <pre> -date &lt;num&gt;
+   *  The number of date attributes (default 0).</pre>
+   * 
+   * <pre> -relational &lt;num&gt;
+   *  The number of relational attributes (default 0).</pre>
+   * 
+   * <pre> -relational-nominal &lt;num&gt;
+   *  The number of nominal attributes in a rel. attribute (default 1).</pre>
+   * 
+   * <pre> -relational-nominal-values &lt;num&gt;
+   *  The number of values for nominal attributes in a rel. attribute (default 2).</pre>
+   * 
+   * <pre> -relational-numeric &lt;num&gt;
+   *  The number of numeric attributes in a rel. attribute (default 0).</pre>
+   * 
+   * <pre> -relational-string &lt;num&gt;
+   *  The number of string attributes in a rel. attribute (default 0).</pre>
+   * 
+   * <pre> -relational-date &lt;num&gt;
+   *  The number of date attributes in a rel. attribute (default 0).</pre>
+   * 
+   * <pre> -num-instances-relational &lt;num&gt;
+   *  The number of instances in relational/bag attributes (default 10).</pre>
+   * 
+   * <pre> -multi-instance
+   *  Generates multi-instance data.</pre>
+   * 
+   * <pre> -W &lt;classname&gt;
+   *  The Capabilities handler to base the dataset on.
+   *  The other parameters can be used to override the ones
+   *  determined from the handler. Additional parameters for
+   *  handler can be passed on after the '--'.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      		tmpStr;
+    Class			cls;
+    CapabilitiesHandler		handler;
+    boolean			initialized;
+    
+    initialized = false;
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) {
+      cls = Class.forName(tmpStr);
+      if (ClassDiscovery.hasInterface(CapabilitiesHandler.class, cls)) {
+	initialized = true;
+	handler = (CapabilitiesHandler) cls.newInstance();
+	if (handler instanceof OptionHandler)
+	  ((OptionHandler) handler).setOptions(Utils.partitionOptions(options));
+	setHandler(handler);
+	// initialize
+	this.assign(forCapabilities(handler.getCapabilities()));
+      }
+      else {
+	throw new IllegalArgumentException("Class '" + tmpStr + "' is not a CapabilitiesHandler!");
+      }
+    }
+
+    tmpStr = Utils.getOption("relation", options);
+    if (tmpStr.length() != 0)
+      setRelation(tmpStr);
+    else if (!initialized)
+      setRelation("Testdata");
+    
+    tmpStr = Utils.getOption("seed", options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setSeed(1);
+    
+    tmpStr = Utils.getOption("num-instances", options);
+    if (tmpStr.length() != 0)
+      setNumInstances(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumInstances(20);
+    
+    setNoClass(Utils.getFlag("no-class", options));
+    
+    if (!getNoClass()) {
+      tmpStr = Utils.getOption("class-type", options);
+      if (tmpStr.length() != 0)
+        setClassType(Integer.parseInt(tmpStr));
+      else if (!initialized)
+        setClassType(Attribute.NOMINAL);
+
+      tmpStr = Utils.getOption("class-values", options);
+      if (tmpStr.length() != 0)
+        setNumClasses(Integer.parseInt(tmpStr));
+      else if (!initialized)
+        setNumClasses(2);
+
+      tmpStr = Utils.getOption("class-index", options);
+      if (tmpStr.length() != 0)
+        setClassIndex(Integer.parseInt(tmpStr));
+      else if (!initialized)
+        setClassIndex(-1);
+    }
+    
+    tmpStr = Utils.getOption("nominal", options);
+    if (tmpStr.length() != 0)
+      setNumNominal(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumNominal(1);
+    
+    tmpStr = Utils.getOption("nominal-values", options);
+    if (tmpStr.length() != 0)
+      setNumNominalValues(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumNominalValues(2);
+    
+    tmpStr = Utils.getOption("numeric", options);
+    if (tmpStr.length() != 0)
+      setNumNumeric(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumNumeric(0);
+    
+    tmpStr = Utils.getOption("string", options);
+    if (tmpStr.length() != 0)
+      setNumString(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumString(0);
+    
+    tmpStr = Utils.getOption("words", options);
+    if (tmpStr.length() != 0)
+      setWords(tmpStr);
+    else if (!initialized)
+      setWords(arrayToList(DEFAULT_WORDS));
+    
+    if (Utils.getOptionPos("word-separators", options) > -1) {
+      tmpStr = Utils.getOption("word-separators", options);
+      setWordSeparators(tmpStr);
+    }
+    else if (!initialized) {
+      setWordSeparators(DEFAULT_SEPARATORS);
+    }
+    
+    tmpStr = Utils.getOption("date", options);
+    if (tmpStr.length() != 0)
+      setNumDate(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumDate(0);
+    
+    tmpStr = Utils.getOption("relational", options);
+    if (tmpStr.length() != 0)
+      setNumRelational(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumRelational(0);
+    
+    tmpStr = Utils.getOption("relational-nominal", options);
+    if (tmpStr.length() != 0)
+      setNumRelationalNominal(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumRelationalNominal(1);
+    
+    tmpStr = Utils.getOption("relational-nominal-values", options);
+    if (tmpStr.length() != 0)
+      setNumRelationalNominalValues(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumRelationalNominalValues(2);
+    
+    tmpStr = Utils.getOption("relational-numeric", options);
+    if (tmpStr.length() != 0)
+      setNumRelationalNumeric(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumRelationalNumeric(0);
+    
+    tmpStr = Utils.getOption("relational-string", options);
+    if (tmpStr.length() != 0)
+      setNumRelationalString(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumRelationalString(0);
+    
+    tmpStr = Utils.getOption("num-instances-relational", options);
+    if (tmpStr.length() != 0)
+      setNumInstancesRelational(Integer.parseInt(tmpStr));
+    else if (!initialized)
+      setNumInstancesRelational(10);
+    
+    if (!initialized)
+      setMultiInstance(Utils.getFlag("multi-instance", options));
+  }
+  
+  /**
+   * Gets the current settings of this object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String> 	result;
+    String[]	options;
+    int		i;
+
+    result = new Vector<String>();
+    
+    result.add("-relation");
+    result.add(getRelation());
+    
+    result.add("-seed");
+    result.add("" + getSeed());
+    
+    result.add("-num-instances");
+    result.add("" + getNumInstances());
+    
+    if (getNoClass()) {
+      result.add("-no-class");
+    }
+    else {
+      result.add("-class-type");
+      result.add("" + getClassType());
+      
+      result.add("-class-values");
+      result.add("" + getNumClasses());
+      
+      result.add("-class-index");
+      result.add("" + getClassIndex());
+    }
+    
+    result.add("-nominal");
+    result.add("" + getNumNominal());
+    
+    result.add("-nominal-values");
+    result.add("" + getNumNominalValues());
+    
+    result.add("-numeric");
+    result.add("" + getNumNumeric());
+    
+    result.add("-string");
+    result.add("" + getNumString());
+    
+    result.add("-words");
+    result.add("" + getWords());
+    
+    result.add("-word-separators");
+    result.add("" + getWordSeparators());
+    
+    result.add("-date");
+    result.add("" + getNumDate());
+    
+    result.add("-relational");
+    result.add("" + getNumRelational());
+    
+    result.add("-relational-nominal");
+    result.add("" + getNumRelationalNominal());
+    
+    result.add("-relational-nominal-values");
+    result.add("" + getNumRelationalNominalValues());
+    
+    result.add("-relational-numeric");
+    result.add("" + getNumRelationalNumeric());
+    
+    result.add("-relational-string");
+    result.add("" + getNumRelationalString());
+    
+    result.add("-relational-date");
+    result.add("" + getNumRelationalDate());
+    
+    result.add("-num-instances-relational");
+    result.add("" + getNumInstancesRelational());
+
+    if (getMultiInstance())
+      result.add("-multi-instance");
+
+    if (getHandler() != null) {
+      result.add("-W");
+      result.add(getHandler().getClass().getName());
+      if (getHandler() instanceof OptionHandler) {
+	result.add("--");
+	options = ((OptionHandler) getHandler()).getOptions();
+	for (i = 0; i < options.length; i++)
+	  result.add(options[i]);
+      }
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets the name of the relation
+   * 
+   * @param value 	the name of the relation
+   */
+  public void setRelation(String value) {
+    m_Relation = value;
+  }
+  
+  /**
+   * returns the current name of the relation
+   * 
+   * @return 		the name of the relation
+   */
+  public String getRelation() {
+    return m_Relation;
+  }
+  
+  /**
+   * sets the seed value for the random number generator
+   * 
+   * @param value	the seed
+   */
+  public void setSeed(int value) {
+    m_Seed   = value;
+    m_Random = new Random(m_Seed);
+  }
+  
+  /**
+   * returns the current seed value
+   * 
+   * @return 		the seed value
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+  
+  /**
+   * sets the number of instances to produce
+   * 
+   * @param value	the number of instances
+   */
+  public void setNumInstances(int value) {
+    m_NumInstances = value;
+  }
+  
+  /**
+   * returns the current number of instances to produce
+   * 
+   * @return the number of instances
+   */
+  public int getNumInstances() {
+    return m_NumInstances;
+  }
+  
+  /**
+   * sets the class attribute type
+   * 
+   * @param value	the class attribute type
+   */
+  public void setClassType(int value) {
+    m_ClassType = value;
+    m_RelationalClassFormat = null;
+  }
+  
+  /**
+   * returns the current class type
+   * 
+   * @return the class attribute type
+   */
+  public int getClassType() {
+    return m_ClassType;
+  }
+  
+  /**
+   * sets the number of classes
+   * 
+   * @param value	the number of classes
+   */
+  public void setNumClasses(int value) {
+    m_NumClasses = value;
+  }
+  
+  /**
+   * returns the current number of classes
+   * 
+   * @return the number of classes
+   */
+  public int getNumClasses() {
+    return m_NumClasses;
+  }
+  
+  /**
+   * sets the class index (0-based)
+   * 
+   * @param value	the class index 
+   * @see #CLASS_IS_LAST
+   * @see #NO_CLASS
+   */
+  public void setClassIndex(int value) {
+    m_ClassIndex = value;
+  }
+  
+  /**
+   * returns the current class index (0-based), -1 is last attribute
+   * 
+   * @return 		the class index
+   * @see #CLASS_IS_LAST
+   * @see #NO_CLASS
+   */
+  public int getClassIndex() {
+    return m_ClassIndex;
+  }
+  
+  /**
+   * whether to have no class, e.g., for clusterers; otherwise the class
+   * attribute index is set to last
+   * 
+   * @param value whether to have no class
+   * @see #CLASS_IS_LAST
+   * @see #NO_CLASS
+   */
+  public void setNoClass(boolean value) {
+    if (value)
+      setClassIndex(NO_CLASS);
+    else
+      setClassIndex(CLASS_IS_LAST);
+  }
+  
+  /**
+   * whether no class attribute is generated
+   * 
+   * @return 		true if no class attribute is generated
+   */
+  public boolean getNoClass() {
+    return (getClassIndex() == NO_CLASS);
+  }
+  
+  /**
+   * sets the number of nominal attributes
+   * 
+   * @param value	the number of nominal attributes
+   */
+  public void setNumNominal(int value) {
+    m_NumNominal = value;
+  }
+  
+  /**
+   * returns the current number of nominal attributes
+   * 
+   * @return 		the number of nominal attributes
+   */
+  public int getNumNominal() {
+    return m_NumNominal;
+  }
+  
+  /**
+   * sets the number of values for nominal attributes
+   * 
+   * @param value	the number of values
+   */
+  public void setNumNominalValues(int value) {
+    m_NumNominalValues = value;
+  }
+  
+  /**
+   * returns the current number of values for nominal attributes
+   * 
+   * @return 		the number of values
+   */
+  public int getNumNominalValues() {
+    return m_NumNominalValues;
+  }
+  
+  /**
+   * sets the number of numeric attributes
+   * 
+   * @param value 	the number of numeric attributes
+   */
+  public void setNumNumeric(int value) {
+    m_NumNumeric = value;
+  }
+  
+  /**
+   * returns the current number of numeric attributes
+   * 
+   * @return 		the number of numeric attributes
+   */
+  public int getNumNumeric() {
+    return m_NumNumeric;
+  }
+  
+  /**
+   * sets the number of string attributes
+   * 
+   * @param value 	the number of string attributes
+   */
+  public void setNumString(int value) {
+    m_NumString = value;
+  }
+  
+  /**
+   * returns the current number of string attributes
+   * 
+   * @return 		the number of string attributes
+   */
+  public int getNumString() {
+    return m_NumString;
+  }
+
+  /**
+   * turns the comma-separated list into an array
+   * 
+   * @param value	the list to process
+   * @return		the list as array
+   */
+  protected static String[] listToArray(String value) {
+    StringTokenizer	tok;
+    Vector<String>		list;
+    
+    list = new Vector<String>();
+    tok = new StringTokenizer(value, ",");
+    while (tok.hasMoreTokens())
+      list.add(tok.nextToken());
+    
+    return (String[]) list.toArray(new String[list.size()]);
+  }
+  
+  /**
+   * turns the array into a comma-separated list
+   * 
+   * @param value	the array to process
+   * @return		the array as list
+   */
+  protected static String arrayToList(String[] value) {
+    String	result;
+    int		i;
+    
+    result = "";
+    
+    for (i = 0; i < value.length; i++) {
+      if (i > 0)
+	result += ",";
+      result += value[i];
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Sets the comma-separated list of words to use for generating strings. The
+   * list must contain at least 2 words, otherwise an exception will be thrown.
+   * 
+   * @param value			the list of words
+   * @throws IllegalArgumentException	if not at least 2 words are provided
+   */
+  public void setWords(String value) {
+    if (listToArray(value).length < 2)
+      throw new IllegalArgumentException("At least 2 words must be provided!");
+    
+    m_Words = listToArray(value);
+  }
+  
+  /**
+   * returns the words used for assembling strings in a comma-separated list.
+   * 
+   * @return		the words as comma-separated list
+   */
+  public String getWords() {
+    return arrayToList(m_Words);
+  }
+
+  /**
+   * sets the word separators (chars) to use for assembling strings.
+   * 
+   * @param value	the characters to use as separators
+   */
+  public void setWordSeparators(String value) {
+    m_WordSeparators = value;
+  }
+  
+  /**
+   * returns the word separators (chars) to use for assembling strings.
+   * 
+   * @return		the current separators
+   */
+  public String getWordSeparators() {
+    return m_WordSeparators;
+  }
+  
+  /**
+   * sets the number of date attributes
+   * 
+   * @param value	the number of date attributes
+   */
+  public void setNumDate(int value) {
+    m_NumDate = value;
+  }
+  
+  /**
+   * returns the current number of date attributes
+   * 
+   * @return		the number of date attributes
+   */
+  public int getNumDate() {
+    return m_NumDate;
+  }
+  
+  /**
+   * sets the number of relational attributes
+   * 
+   * @param value	the number of relational attributes
+   */
+  public void setNumRelational(int value) {
+    m_NumRelational = value;
+    m_RelationalFormat = new Instances[value];
+  }
+  
+  /**
+   * returns the current number of relational attributes
+   * 
+   * @return		the number of relational attributes
+   */
+  public int getNumRelational() {
+    return m_NumRelational;
+  }
+  
+  /**
+   * sets the number of nominal attributes in a relational attribute
+   * 
+   * @param value	the number of nominal attributes
+   */
+  public void setNumRelationalNominal(int value) {
+    m_NumRelationalNominal = value;
+  }
+  
+  /**
+   * returns the current number of nominal attributes in a relational attribute
+   * 
+   * @return 		the number of nominal attributes
+   */
+  public int getNumRelationalNominal() {
+    return m_NumRelationalNominal;
+  }
+  
+  /**
+   * sets the number of values for nominal attributes in a relational attribute
+   * 
+   * @param value	the number of values
+   */
+  public void setNumRelationalNominalValues(int value) {
+    m_NumRelationalNominalValues = value;
+  }
+  
+  /**
+   * returns the current number of values for nominal attributes in a relational attribute
+   * 
+   * @return 		the number of values
+   */
+  public int getNumRelationalNominalValues() {
+    return m_NumRelationalNominalValues;
+  }
+  
+  /**
+   * sets the number of numeric attributes in a relational attribute
+   * 
+   * @param value 	the number of numeric attributes
+   */
+  public void setNumRelationalNumeric(int value) {
+    m_NumRelationalNumeric = value;
+  }
+  
+  /**
+   * returns the current number of numeric attributes in a relational attribute
+   * 
+   * @return 		the number of numeric attributes
+   */
+  public int getNumRelationalNumeric() {
+    return m_NumRelationalNumeric;
+  }
+  
+  /**
+   * sets the number of string attributes in a relational attribute
+   * 
+   * @param value 	the number of string attributes
+   */
+  public void setNumRelationalString(int value) {
+    m_NumRelationalString = value;
+  }
+  
+  /**
+   * returns the current number of string attributes in a relational attribute
+   * 
+   * @return 		the number of string attributes
+   */
+  public int getNumRelationalString() {
+    return m_NumRelationalString;
+  }
+  
+  /**
+   * sets the number of date attributes in a relational attribute
+   * 
+   * @param value	the number of date attributes
+   */
+  public void setNumRelationalDate(int value) {
+    m_NumRelationalDate = value;
+  }
+  
+  /**
+   * returns the current number of date attributes in a relational attribute
+   * 
+   * @return		the number of date attributes
+   */
+  public int getNumRelationalDate() {
+    return m_NumRelationalDate;
+  }
+  
+  /**
+   * sets the number of instances in relational/bag attributes to produce
+   * 
+   * @param value	the number of instances
+   */
+  public void setNumInstancesRelational(int value) {
+    m_NumInstancesRelational = value;
+  }
+  
+  /**
+   * returns the current number of instances in relational/bag attributes to produce
+   * 
+   * @return		the number of instances
+   */
+  public int getNumInstancesRelational() {
+    return m_NumInstancesRelational;
+  }
+
+  /**
+   * sets whether multi-instance data should be generated (with a fixed
+   * data structure)
+   * 
+   * @param value	whether multi-instance data is generated
+   */
+  public void setMultiInstance(boolean value) {
+    m_MultiInstance = value;
+  }
+  
+  /**
+   * Gets whether multi-instance data (with a fixed structure) is generated
+   * 
+   * @return		true if multi-instance data is generated
+   */
+  public boolean getMultiInstance() {
+    return m_MultiInstance;
+  }
+  
+  /**
+   * sets the structure for the bags for the relational attribute
+   * 
+   * @param index       the index of the relational attribute
+   * @param value       the new structure
+   */
+  public void setRelationalFormat(int index, Instances value) {
+    if (value != null)
+      m_RelationalFormat[index] = new Instances(value, 0);
+    else
+      m_RelationalFormat[index] = null;
+  }
+  
+  /**
+   * returns the format for the specified relational attribute, can be null
+   * 
+   * @param index       the index of the relational attribute
+   * @return            the current structure
+   */
+  public Instances getRelationalFormat(int index) {
+    return m_RelationalFormat[index];
+  }
+
+  /**
+   * sets the structure for the relational class attribute
+   * 
+   * @param value	the structure for the relational attribute
+   */
+  public void setRelationalClassFormat(Instances value) {
+    if (value != null)
+      m_RelationalClassFormat = new Instances(value, 0);
+    else
+      m_RelationalClassFormat = null;
+  }
+  
+  /**
+   * returns the current strcuture of the relational class attribute, can
+   * be null
+   * 
+   * @return 		the relational structure of the class attribute
+   */
+  public Instances getRelationalClassFormat() {
+    return m_RelationalClassFormat;
+  }
+  
+  /**
+   * returns the overall number of attributes (incl. class, if that is also
+   * generated)
+   * 
+   * @return 		the overall number of attributes
+   */
+  public int getNumAttributes() {
+    int		result;
+    
+    result = m_NumNominal + m_NumNumeric + m_NumString + m_NumDate + m_NumRelational;
+    
+    if (!getNoClass())
+      result++;
+      
+    return result;
+  }
+  
+  /**
+   * returns the current dataset, can be null
+   * 
+   * @return		the current dataset
+   */
+  public Instances getData() {
+    return m_Data;
+  }
+  
+  /**
+   * sets the Capabilities handler to generate the data for
+   * 
+   * @param value	the handler to generate the data for
+   */
+  public void setHandler(CapabilitiesHandler value) {
+    m_Handler = value;
+  }
+  
+  /**
+   * returns the current set CapabilitiesHandler to generate the dataset
+   * for, can be null
+   * 
+   * @return		the handler to generate the data for
+   */
+  public CapabilitiesHandler getHandler() {
+    return m_Handler;
+  }
+  
+  /**
+   * creates a new Attribute of the given type
+   * 
+   * @param index the index of the current attribute (0-based)
+   * @param attType the attribute type (NUMERIC, NOMINAL, etc.)
+   * @return the configured attribute
+   * @throws Exception if something goes wrong, e.g., an unknown attribute type
+   * 
+   * @see Attribute#type()
+   * @see #CLASS_IS_LAST
+   * @see #NO_CLASS
+   */
+  protected Attribute generateAttribute(int index, int attType) throws Exception {
+    Attribute     result;
+    String        name;
+    int           valIndex;
+    int           nomCount;
+    String        prefix;
+    
+    result = null;
+
+    // determine name and start-index
+    if (index == CLASS_IS_LAST) {
+      valIndex = 0;
+      name     = "Class";
+      prefix   = "class";
+      nomCount = getNumClasses();
+    }
+    else {
+      valIndex = index;
+      nomCount = getNumNominalValues();
+      prefix   = "att" + (valIndex + 1) + "val";
+      
+      switch (attType) {
+        case Attribute.NOMINAL:
+          name = "Nominal" + (valIndex + 1);
+          break;
+          
+        case Attribute.NUMERIC:
+          name = "Numeric" + (valIndex + 1);
+          break;
+          
+        case Attribute.STRING:
+          name = "String" + (valIndex + 1);
+          break;
+          
+        case Attribute.DATE:
+          name = "Date" + (valIndex + 1);
+          break;
+          
+        case Attribute.RELATIONAL:
+          name = "Relational" + (valIndex + 1);
+          break;
+          
+        default:
+          throw new IllegalArgumentException("Attribute type '" + attType + "' unknown!");
+      }
+    }
+    
+    switch (attType) {
+      case Attribute.NOMINAL:
+        ArrayList<String> nomStrings = new ArrayList<String>(valIndex + 1);
+        for (int j = 0; j < nomCount; j++)
+          nomStrings.add(prefix + (j + 1));
+        result = new Attribute(name, nomStrings);
+        break;
+        
+      case Attribute.NUMERIC:
+        result = new Attribute(name);
+        break;
+        
+      case Attribute.STRING:
+        result = new Attribute(name, (ArrayList<String>) null);
+        break;
+        
+      case Attribute.DATE:
+        result = new Attribute(name, "yyyy-mm-dd");
+        break;
+        
+      case Attribute.RELATIONAL:
+        Instances rel;
+        if (index == CLASS_IS_LAST)
+          rel = getRelationalClassFormat();
+        else
+          rel = getRelationalFormat(index);
+        
+        if (rel == null) {
+          TestInstances dataset = new TestInstances();
+          dataset.setNumNominal(getNumRelationalNominal());
+          dataset.setNumNominalValues(getNumRelationalNominalValues());
+          dataset.setNumNumeric(getNumRelationalNumeric());
+          dataset.setNumString(getNumRelationalString());
+          dataset.setNumDate(getNumRelationalDate());
+          dataset.setNumInstances(0);
+          dataset.setClassType(Attribute.NOMINAL);  // dummy to avoid endless recursion, will be deleted anyway
+          rel = new Instances(dataset.generate());
+          if (!getNoClass()) {
+            int clsIndex = rel.classIndex();
+            rel.setClassIndex(-1);
+            rel.deleteAttributeAt(clsIndex);
+          }
+        }
+        result = new Attribute(name, rel);
+        break;
+        
+      default:
+        throw new IllegalArgumentException("Attribute type '" + attType + "' unknown!");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Generates the class value
+   * 
+   * @param data  	the dataset to work on
+   * @return      	the new class value
+   * @throws Exception 	if something goes wrong
+   */
+  protected double generateClassValue(Instances data) throws Exception {
+    double result = Double.NaN;
+    
+    switch (m_ClassType) {
+      case Attribute.NUMERIC:
+        result = m_Random.nextFloat() * 0.25
+            + Math.abs(m_Random.nextInt())
+            % Math.max(2, m_NumNominal);
+        break;
+        
+      case Attribute.NOMINAL:
+        result = Math.abs(m_Random.nextInt()) % data.numClasses();
+        break;
+        
+      case Attribute.STRING:
+        String str = "";
+        for (int n = 0; n < m_Words.length; n++) {
+          if ( (n > 0) && (m_WordSeparators.length() != 0) )
+            str += m_WordSeparators.charAt(m_Random.nextInt(m_WordSeparators.length()));
+          str += m_Words[m_Random.nextInt(m_Words.length)];
+        }
+        result = data.classAttribute().addStringValue(str);
+        break;
+        
+      case Attribute.DATE:
+        result = data.classAttribute().parseDate(
+                (2000 + m_Random.nextInt(100)) + "-01-01");
+        break;
+        
+      case Attribute.RELATIONAL:
+        if (getRelationalClassFormat() != null) {
+          result = data.classAttribute().addRelation(getRelationalClassFormat());
+        }
+        else {
+          TestInstances dataset = new TestInstances();
+          dataset.setNumNominal(getNumRelationalNominal());
+          dataset.setNumNominalValues(getNumRelationalNominalValues());
+          dataset.setNumNumeric(getNumRelationalNumeric());
+          dataset.setNumString(getNumRelationalString());
+          dataset.setNumDate(getNumRelationalDate());
+          dataset.setNumInstances(getNumInstancesRelational());
+          dataset.setClassType(Attribute.NOMINAL);  // dummy to avoid endless recursion, will be deleted anyway
+          Instances rel = new Instances(dataset.generate());
+          int clsIndex = rel.classIndex();
+          rel.setClassIndex(-1);
+          rel.deleteAttributeAt(clsIndex);
+          result = data.classAttribute().addRelation(rel);
+        }
+        break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Generates a new value for the specified attribute. The classValue
+   * might be used in the process.
+   * 
+   * @param data          the dataset to work on
+   * @param index         the index of the attribute
+   * @param classVal      the class value for the current instance, might be 
+   *                      used in the calculation
+   * @return              the new attribute value
+   * @throws Exception    if something goes wrong
+   */
+  protected double generateAttributeValue(Instances data, int index, double classVal) throws Exception {
+    double result = Double.NaN;
+    
+    switch (data.attribute(index).type()) {
+      case Attribute.NUMERIC:
+        result = classVal * 4 + m_Random.nextFloat() * 1 - 0.5;
+        break;
+        
+      case Attribute.NOMINAL:
+        if (m_Random.nextFloat() < 0.2) {
+          result = Math.abs(m_Random.nextInt())
+          % data.attribute(index).numValues();
+        } else {
+          result = ((int)classVal) % data.attribute(index).numValues();
+        }
+	//result = m_Random.nextInt(data.attribute(index).numValues());
+        break;
+        
+      case Attribute.STRING:
+        String str = "";
+        for (int n = 0; n < m_Words.length; n++) {
+          if ( (n > 0) && (m_WordSeparators.length() != 0) )
+            str += m_WordSeparators.charAt(m_Random.nextInt(m_WordSeparators.length()));
+          str += m_Words[m_Random.nextInt(m_Words.length)];
+        }
+        result = data.attribute(index).addStringValue(str);
+        break;
+        
+      case Attribute.DATE:
+        result = data.attribute(index).parseDate(
+                (2000 + m_Random.nextInt(100)) + "-01-01");
+        break;
+        
+      case Attribute.RELATIONAL:
+        Instances rel = new Instances(data.attribute(index).relation(), 0);
+        for (int n = 0; n < getNumInstancesRelational(); n++) {
+          Instance inst = new DenseInstance(rel.numAttributes());
+          inst.setDataset(data);
+          for (int i = 0; i < rel.numAttributes(); i++) {
+            inst.setValue(i, generateAttributeValue(rel, i, 0));
+          }
+          rel.add(inst);
+        }
+        result = data.attribute(index).addRelation(rel);
+        break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * generates a new dataset.
+   * 
+   * @return 		the generated data
+   * @throws Exception	if something goes wrong
+   */
+  public Instances generate() throws Exception {
+    if (getMultiInstance()) {
+      TestInstances bag = (TestInstances) this.clone();
+      bag.setMultiInstance(false);
+      bag.setNumInstances(0);
+      bag.setSeed(m_Random.nextInt());
+      Instances bagFormat = bag.generate();
+      bagFormat.setClassIndex(-1);
+      bagFormat.deleteAttributeAt(bagFormat.numAttributes() - 1);
+
+      // generate multi-instance structure
+      TestInstances structure = new TestInstances();
+      structure.setSeed(m_Random.nextInt());
+      structure.setNumNominal(1);
+      structure.setNumRelational(1);
+      structure.setRelationalFormat(0, bagFormat);
+      structure.setClassType(getClassType());
+      structure.setNumClasses(getNumClasses());
+      structure.setRelationalClassFormat(getRelationalClassFormat());
+      structure.setNumInstances(getNumInstances());
+      m_Data = structure.generate();
+      
+      // generate bags
+      bag.setNumInstances(getNumInstancesRelational());
+      for (int i = 0; i < getNumInstances(); i++) {
+        bag.setSeed(m_Random.nextInt());
+        Instances bagData = new Instances(bag.generate());
+        bagData.setClassIndex(-1);
+        bagData.deleteAttributeAt(bagData.numAttributes() - 1);
+        double val = m_Data.attribute(1).addRelation(bagData);
+        m_Data.instance(i).setValue(1, val);
+      }
+    }
+    else {
+      // initialize
+      int clsIndex = m_ClassIndex;
+      if (clsIndex == CLASS_IS_LAST)
+        clsIndex = getNumAttributes() - 1;
+
+      // generate attributes
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>(getNumAttributes());
+      // Add Nominal attributes
+      for (int i = 0; i < getNumNominal(); i++)
+        attributes.add(generateAttribute(i, Attribute.NOMINAL));
+      
+      // Add m_Numeric attributes
+      for (int i = 0; i < getNumNumeric(); i++)
+        attributes.add(generateAttribute(i, Attribute.NUMERIC));
+      
+      // Add some String attributes...
+      for (int i = 0; i < getNumString(); i++)
+        attributes.add(generateAttribute(i, Attribute.STRING));
+      
+      // Add some Date attributes...
+      for (int i = 0; i < getNumDate(); i++)
+        attributes.add(generateAttribute(i, Attribute.DATE));
+      
+      // Add some Relational attributes...
+      for (int i = 0; i < getNumRelational(); i++)
+        attributes.add(generateAttribute(i, Attribute.RELATIONAL));
+      
+      // Add class attribute
+      if (clsIndex != NO_CLASS)
+	attributes.add(clsIndex, generateAttribute(CLASS_IS_LAST, getClassType()));
+      
+      m_Data = new Instances(getRelation(), attributes, getNumInstances());
+      m_Data.setClassIndex(clsIndex);
+
+      // generate instances
+      for (int i = 0; i < getNumInstances(); i++) {
+        Instance current = new DenseInstance(getNumAttributes());
+        current.setDataset(m_Data);
+
+        // class
+        double classVal;
+        if (clsIndex != NO_CLASS) {
+          classVal = generateClassValue(m_Data);
+          current.setClassValue(classVal);
+        }
+        else {
+          classVal = m_Random.nextFloat();
+        }
+        
+        // other attributes
+        for (int n = 0; n < getNumAttributes(); n++) {
+          if (clsIndex == n)
+            continue;
+          
+          current.setValue(n, generateAttributeValue(m_Data, n, classVal));
+        }
+        
+        m_Data.add(current);
+      }
+    }
+
+    if (m_Data.classIndex() == NO_CLASS)
+      m_Data.setClassIndex(-1);
+    
+    return getData();
+  }
+  
+  /**
+   * returns a TestInstances instance setup already for the the given
+   * capabilities.
+   * 
+   * @param c		the capabilities to base the TestInstances on
+   * @return		the configured TestInstances object
+   */
+  public static TestInstances forCapabilities(Capabilities c) {
+    TestInstances	result;
+    
+    result = new TestInstances();
+    
+    // multi-instance?
+    if (c.getOwner() instanceof MultiInstanceCapabilitiesHandler) {
+      Capabilities multi = (Capabilities) ((MultiInstanceCapabilitiesHandler) c.getOwner()).getMultiInstanceCapabilities().clone();
+      multi.setOwner(null);  // otherwise recursive!
+      result = forCapabilities(multi);
+      result.setMultiInstance(true);
+    }
+    else  {
+      // class
+      if (c.handles(Capability.NO_CLASS))
+	result.setClassIndex(NO_CLASS);
+      else if (c.handles(Capability.NOMINAL_CLASS))
+	result.setClassType(Attribute.NOMINAL);
+      else if (c.handles(Capability.BINARY_CLASS))
+	result.setClassType(Attribute.NOMINAL);
+      else if (c.handles(Capability.NUMERIC_CLASS))
+	result.setClassType(Attribute.NUMERIC);
+      else if (c.handles(Capability.DATE_CLASS))
+	result.setClassType(Attribute.DATE);
+      else if (c.handles(Capability.STRING_CLASS))
+	result.setClassType(Attribute.STRING);
+      else if (c.handles(Capability.RELATIONAL_CLASS))
+	result.setClassType(Attribute.RELATIONAL);
+
+      // # of classes
+      if (c.handles(Capability.UNARY_CLASS))
+	result.setNumClasses(1);
+      if (c.handles(Capability.BINARY_CLASS))
+	result.setNumClasses(2);
+      if (c.handles(Capability.NOMINAL_CLASS))
+	result.setNumClasses(4);
+      
+      // attributes
+      if (c.handles(Capability.NOMINAL_ATTRIBUTES)) {
+	result.setNumNominal(1);
+	result.setNumRelationalNominal(1);
+      }
+      else {
+	result.setNumNominal(0);
+	result.setNumRelationalNominal(0);
+      }
+
+      if (c.handles(Capability.NUMERIC_ATTRIBUTES)) {
+	result.setNumNumeric(1);
+	result.setNumRelationalNumeric(1);
+      }
+      else {
+	result.setNumNumeric(0);
+	result.setNumRelationalNumeric(0);
+      }
+
+      if (c.handles(Capability.DATE_ATTRIBUTES)) {
+	result.setNumDate(1);
+	result.setNumRelationalDate(1);
+      }
+      else {
+	result.setNumDate(0);
+	result.setNumRelationalDate(0);
+      }
+      
+      if (c.handles(Capability.STRING_ATTRIBUTES)) {
+	result.setNumString(1);
+	result.setNumRelationalString(1);
+      }
+      else {
+	result.setNumString(0);
+	result.setNumRelationalString(0);
+      }
+      
+      if (c.handles(Capability.RELATIONAL_ATTRIBUTES))
+	result.setNumRelational(1);
+      else
+	result.setNumRelational(0);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns a string representation of the object
+   * 
+   * @return		a string representation of the object
+   */
+  public String toString() {
+    String	result;
+    
+    result = "";
+    result += "Relation: " + getRelation() + "\n";
+    result += "Seed: " + getSeed() + "\n";
+    result += "# Instances: " + getNumInstances() + "\n";
+    result += "ClassType: " + getClassType() + "\n";
+    result += "# Classes: " + getNumClasses() + "\n";
+    result += "Class index: " + getClassIndex() + "\n";
+    result += "# Nominal: " +     getNumNominal() + "\n";
+    result += "# Nominal values: " + getNumNominalValues() + "\n";
+    result += "# Numeric: " + getNumNumeric() + "\n";
+    result += "# String: " + getNumString() + "\n";
+    result += "# Date: " + getNumDate() + "\n";
+    result += "# Relational: " + getNumRelational() + "\n";
+    result += "  - # Nominal: " +     getNumRelationalNominal() + "\n";
+    result += "  - # Nominal values: " + getNumRelationalNominalValues() + "\n";
+    result += "  - # Numeric: " + getNumRelationalNumeric() + "\n";
+    result += "  - # String: " + getNumRelationalString() + "\n";
+    result += "  - # Date: " + getNumRelationalDate() + "\n";
+    result += "  - # Instances: " + getNumInstancesRelational() + "\n";
+    result += "Multi-Instance: " + getMultiInstance() + "\n";
+    result += "Words: " + getWords() + "\n";
+    result += "Word separators: " + getWordSeparators() + "\n";
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * for running the class from commandline, prints the generated data
+   * to stdout
+   * 
+   * @param args	the commandline parameters
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    TestInstances inst;
+    
+    inst = new TestInstances();
+
+    // help requested?
+    if (Utils.getFlag("h", args) || Utils.getFlag("help", args)) {
+      StringBuffer result = new StringBuffer();
+      result.append("\nTest data generator options:\n\n");
+
+      result.append("-h|-help\n\tprints this help\n");
+      
+      Enumeration enm = inst.listOptions();
+      while (enm.hasMoreElements()) {
+        Option option = (Option) enm.nextElement();
+        result.append(option.synopsis() + "\n" + option.description() + "\n");
+      }
+
+      System.out.println(result);
+      System.exit(0);
+    }
+    
+    // generate data
+    inst.setOptions(args);
+    System.out.println(inst.generate());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Trie.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Trie.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Trie.java	(revision 29)
@@ -0,0 +1,887 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Trie.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * A class representing a Trie data structure for strings.
+ * See also <a href="http://en.wikipedia.org/wiki/Trie" target="_blank">Trie</a> 
+ * on WikiPedia.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Trie
+  implements Serializable, Cloneable, Collection<String>, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5897980928817779048L;
+
+  /**
+   * Represents a node in the trie.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class TrieNode
+    extends DefaultMutableTreeNode
+    implements RevisionHandler {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -2252907099391881148L;
+    
+    /** the stop character */
+    public final static Character STOP = '\0';
+
+    /** for fast access to the children */
+    protected Hashtable<Character,TrieNode> m_Children;
+    
+    /**
+     * initializes the node
+     * 
+     * @param c		the value of this node
+     */
+    public TrieNode(char c) {
+      this(new Character(c));
+    }
+    
+    /**
+     * initializes the node
+     * 
+     * @param c		the value of this node
+     */
+    public TrieNode(Character c) {
+      super(c);
+      
+      m_Children = new Hashtable<Character,TrieNode>(100);
+    }
+    
+    /**
+     * returns the stored character
+     * 
+     * @return		the stored character
+     */
+    public Character getChar() {
+      return (Character) getUserObject();
+    }
+    
+    /**
+     * sets the character this node represents
+     * 
+     * @param value	the character to store
+     */
+    public void setChar(Character value) {
+      setUserObject(value);
+    }
+
+    /**
+     * adds the given string to its children (creates children if necessary)
+     * 
+     * @param suffix	the suffix to add to its children
+     * @return		true if the add operation changed the structure
+     */
+    public boolean add(String suffix) {
+      boolean		result;
+      Character 	c;
+      String		newSuffix;
+      TrieNode		child;
+      
+      result    = false;
+      c         = suffix.charAt(0);
+      newSuffix = suffix.substring(1);
+      
+      // find child and add if necessary
+      child = m_Children.get(c);
+      if (child == null) {
+	result = true;
+	child = add(c);
+      }
+      
+      // propagate remaining suffix
+      if (newSuffix.length() > 0)
+	result = child.add(newSuffix) || result;
+      
+      return result;
+    }
+    
+    /**
+     * adds the given charater to its children
+     * 
+     * @param c		the character to add
+     * @return		the generated child node
+     */
+    protected TrieNode add(Character c) {
+      TrieNode	child;
+      
+      child = new TrieNode(c);
+      add(child);
+      m_Children.put(c, child);
+      
+      return child;
+    }
+    
+    /**
+     * removes the given characted from its children
+     * 
+     * @param c		the character to remove
+     */
+    protected void remove(Character c) {
+      TrieNode	child;
+      
+      child = m_Children.get(c);
+      remove(child);
+      m_Children.remove(c);
+    }
+    
+    /**
+     * Removes a suffix from the trie.
+     * 
+     * @param suffix	the suffix to remove
+     * @return		true if this trie changed as a result of the call
+     */
+    public boolean remove(String suffix) {
+      boolean		result;
+      Character		c;
+      String		newSuffix;
+      TrieNode		child;
+      
+      c         = suffix.charAt(0);
+      newSuffix = suffix.substring(1);
+      child     = m_Children.get(c);
+      
+      if (child == null) {
+	result = false;
+      }
+      else if (newSuffix.length() == 0) {
+	remove(c);
+	result = true;
+      }
+      else {
+	result = child.remove(newSuffix);
+	if (child.getChildCount() == 0)
+	  remove(child.getChar());
+      }
+      
+      return result;
+    }
+
+    /**
+     * checks whether a suffix can be found in its children
+     * 
+     * @param suffix	the suffix to look for
+     * @return		true if suffix was found
+     */
+    public boolean contains(String suffix) {
+      boolean		result;
+      Character 	c;
+      String		newSuffix;
+      TrieNode		child;
+      
+      c         = suffix.charAt(0);
+      newSuffix = suffix.substring(1);
+      child     = m_Children.get(c);
+      
+      if (child == null)
+	result = false;
+      else if (newSuffix.length() == 0)
+	result = true;
+      else
+	result = child.contains(newSuffix);
+
+      return result;
+    }
+    
+    /**
+     * creates a deep copy of itself
+     * 
+     * @return		a deep copy of itself
+     */
+    public Object clone() {
+      TrieNode			result;
+      Enumeration<Character>	keys;
+      Character			key;
+      TrieNode			child;
+      
+      result = new TrieNode(getChar());
+      keys   = m_Children.keys();
+      while (keys.hasMoreElements()) {
+	key   = keys.nextElement();
+	child = (TrieNode) m_Children.get(key).clone();
+	result.add(child);
+	result.m_Children.put(key, child);
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Indicates whether some other object is "equal to" this one.
+     * 
+     * @param obj	the object to check for equality
+     * @return		true if equal
+     */
+    public boolean equals(Object obj) {
+      boolean			result;
+      TrieNode 			node;
+      Enumeration<Character>	keys;
+      Character			key;
+      
+      node   = (TrieNode) obj;
+      
+      // is payload the same?
+      if (getChar() == null)
+	result = (node.getChar() == null);
+      else
+	result = getChar().equals(node.getChar());
+      
+      // check children
+      if (result) {
+	keys = m_Children.keys();
+	while (keys.hasMoreElements()) {
+	  key    = keys.nextElement();
+	  result = m_Children.get(key).equals(node.m_Children.get(key));
+	  if (!result)
+	    break;
+	}
+      }
+      
+      return result;
+    }
+
+    /**
+     * returns the node with the given suffix
+     * 
+     * @param suffix	the suffix to look for
+     * @return		null if unsuccessful otherwise the corresponding node
+     */
+    public TrieNode find(String suffix) {
+      TrieNode		result;
+      Character 	c;
+      String		newSuffix;
+      TrieNode		child;
+      
+      c         = suffix.charAt(0);
+      newSuffix = suffix.substring(1);
+      child     = m_Children.get(c);
+      
+      if (child == null)
+	result = null;
+      else if (newSuffix.length() == 0)
+	result = child;
+      else
+	result = child.find(newSuffix);
+
+      return result;
+    }
+
+    /**
+     * returns the common prefix for all the nodes starting with this node.
+     * The result includes this node, unless it's the root node or a STOP node.
+     * 
+     * @return			the result of the search
+     */
+    public String getCommonPrefix() {
+      return getCommonPrefix("");
+    }
+
+    /**
+     * returns the common prefix for all the nodes starting with the node
+     * for the specified prefix. Can be null if initial prefix is not found.
+     * The result includes this node, unless it's the root node or a STOP node.
+     * Using the empty string means starting with this node.
+     * 
+     * @param startPrefix	the prefix of the node to start the search from
+     * @return			the result of the search, null if startPrefix
+     * 				cannot be found
+     */
+    public String getCommonPrefix(String startPrefix) {
+      String	result;
+      TrieNode	startNode;
+
+      if (startPrefix.length() == 0)
+	startNode = this;
+      else
+	startNode = find(startPrefix);
+      
+      if (startNode == null)
+	result = null;
+      else
+	result = startPrefix + startNode.determineCommonPrefix("");
+	
+      return result;
+    }
+
+    /**
+     * determines the common prefix of the nodes.
+     * 
+     * @param currentPrefix	the common prefix found so far
+     * @return			the result of the search
+     */
+    protected String determineCommonPrefix(String currentPrefix) {
+      String	result;
+      String	newPrefix;
+      
+      if (!isRoot() && (getChar() != STOP))
+	newPrefix = currentPrefix + getChar();
+      else
+	newPrefix = currentPrefix;
+      
+      if (m_Children.size() == 1)
+	result = ((TrieNode) getChildAt(0)).determineCommonPrefix(newPrefix);
+      else
+	result = newPrefix;
+      
+      return result;
+    }
+    
+    /**
+     * returns the number of stored strings, i.e., leaves
+     * 
+     * @return		the number of stored strings
+     */
+    public int size() {
+      int	result;
+      TrieNode	leaf;
+      
+      result = 0;
+      leaf   = (TrieNode) getFirstLeaf();
+      while (leaf != null) {
+	if (leaf != getRoot())
+	  result++;
+	leaf = (TrieNode) leaf.getNextLeaf();
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns the full string up to the root
+     * 
+     * @return		the full string to the root
+     */
+    public String getString() {
+      char[]	result;
+      TrieNode	node;
+      
+      result = new char[this.getLevel()];
+      node   = this;
+      while (node.getParent() != null) {
+	if (node.isRoot())
+	  break;
+	else
+	  result[node.getLevel() - 1] = node.getChar();
+	node = (TrieNode) node.getParent();
+      }
+      
+      return new String(result);
+    }
+    
+    /**
+     * returns the node in a string representation
+     * 
+     * @return		the node as string
+     */
+    public String toString() {
+      return "" + getChar();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * Represents an iterator over a trie
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5953 $
+   */
+  public static class TrieIterator 
+    implements Iterator<String>, RevisionHandler {
+    
+    /** the node to use as root */
+    protected TrieNode m_Root;
+    
+    /** the last leaf for this root node */
+    protected TrieNode m_LastLeaf;
+    
+    /** the current leaf node */
+    protected TrieNode m_CurrentLeaf;
+    
+    /**
+     * initializes the iterator
+     * 
+     * @param node		the node to use as root
+     */
+    public TrieIterator(TrieNode node) {
+      super();
+      
+      m_Root        = node;
+      m_CurrentLeaf = (TrieNode) m_Root.getFirstLeaf();
+      m_LastLeaf    = (TrieNode) m_Root.getLastLeaf();
+    }
+    
+    /**
+     * Returns true if the iteration has more elements.
+     * 
+     * @return		true if there is at least one more element
+     */
+    public boolean hasNext() {
+      return (m_CurrentLeaf != null);
+    }
+    
+    /**
+     * Returns the next element in the iteration.
+     * 
+     * @return		the next element
+     */
+    public String next() {
+      String	result;
+      
+      result        = m_CurrentLeaf.getString();
+      result        = result.substring(0, result.length() - 1);  // remove STOP
+      if (m_CurrentLeaf != m_LastLeaf)
+	m_CurrentLeaf = (TrieNode) m_CurrentLeaf.getNextLeaf();
+      else
+	m_CurrentLeaf = null;
+      
+      return result;
+    }
+    
+    /**
+     * ignored
+     */
+    public void remove() {
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /** the root node */
+  protected TrieNode m_Root;
+
+  /** the hash code */
+  protected int m_HashCode;
+  
+  /** whether the structure got modified and the hash code needs to be 
+   * re-calculated */
+  protected boolean m_RecalcHashCode;
+  
+  /**
+   * initializes the data structure
+   */
+  public Trie() {
+    super();
+    
+    m_Root           = new TrieNode(null);
+    m_RecalcHashCode = true;
+  }
+  
+  /**
+   * Ensures that this collection contains the specified element.
+   * 
+   * @param o		the string to add
+   * @return		true if the structure changed
+   */
+  public boolean add(String o) {
+    return m_Root.add(o + TrieNode.STOP);
+  }
+  
+  /**
+   * Adds all of the elements in the specified collection to this collection 
+   * 
+   * @param c		the collection to add
+   */
+  public boolean addAll(Collection<? extends String> c) {
+    boolean		result;
+    Iterator<? extends String>	iter;
+    
+    result = false;
+    
+    iter = c.iterator();
+    while (iter.hasNext())
+      result = add(iter.next()) || result;
+    
+    return result;
+  }
+  
+  /**
+   * Removes all of the elements from this collection
+   */
+  public void clear() {
+    m_Root.removeAllChildren();
+    m_RecalcHashCode = true;
+  }
+
+  /**
+   * returns a deep copy of itself
+   * 
+   * @return		a copy of itself
+   */
+  public Object clone() {
+    Trie	result;
+    
+    result = new Trie();
+    result.m_Root = (TrieNode) m_Root.clone();
+    
+    return result;
+  }
+  
+  /**
+   * Returns true if this collection contains the specified element.
+   * 
+   * @param o		the object to check for in trie
+   * @return		true if found
+   */
+  public boolean contains(Object o) {
+    return m_Root.contains(((String) o) + TrieNode.STOP);
+  }
+  
+  /**
+   * Returns true if this collection contains all of the elements in the 
+   * specified collection.
+   * 
+   * @param c		the collection to look for in the trie
+   * @return		true if all elements were found
+   */
+  public boolean containsAll(Collection<?> c) {
+    boolean	result;
+    Iterator	iter;
+    
+    result = true;
+    
+    iter = c.iterator();
+    while (iter.hasNext()) {
+      if (!contains(iter.next())) {
+	result = false;
+	break;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the given prefix is stored in the trie
+   * 
+   * @param prefix	the prefix to check
+   * @return		true if the prefix is part of the trie
+   */
+  public boolean containsPrefix(String prefix) {
+    return m_Root.contains(prefix);
+  }
+  
+  /**
+   * Compares the specified object with this collection for equality.
+   * 
+   * @param o		the object to check for equality
+   */
+  public boolean equals(Object o) {
+    return m_Root.equals(((Trie) o).getRoot());
+  }
+
+  /**
+   * returns the common prefix for all the nodes
+   * 
+   * @return		the result of the search
+   */
+  public String getCommonPrefix() {
+    return m_Root.getCommonPrefix();
+  }
+
+  /**
+   * returns the root node of the trie
+   * 
+   * @return		the root node
+   */
+  public TrieNode getRoot() {
+    return m_Root;
+  }
+
+  /**
+   * returns all stored strings that match the given prefix
+   * 
+   * @param prefix	the prefix that all strings must have
+   * @return		all strings that match the prefix
+   */
+  public Vector<String> getWithPrefix(String prefix) {
+    Vector<String>	result;
+    TrieNode		node;
+    TrieIterator	iter;
+    
+    result = new Vector<String>();
+    
+    if (containsPrefix(prefix)) {
+      node = m_Root.find(prefix);
+      iter = new TrieIterator(node);
+      while (iter.hasNext())
+	result.add(iter.next());
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the hash code value for this collection.
+   * 
+   * @return		the hash code
+   */
+  public int hashCode() {
+    if (m_RecalcHashCode) {
+      m_HashCode       = toString().hashCode();
+      m_RecalcHashCode = false;
+    }
+    
+    return m_HashCode;
+  }
+  
+  /**
+   * Returns true if this collection contains no elements.
+   * 
+   * @return		true if empty
+   */
+  public boolean isEmpty() {
+    return (m_Root.getChildCount() == 0);
+  }
+
+  /**
+   * Returns an iterator over the elements in this collection.
+   * 
+   * @return		returns an iterator over all the stored strings
+   */
+  public Iterator<String> iterator() {
+    return new TrieIterator(m_Root);
+  }
+  
+  /**
+   * Removes a single instance of the specified element from this collection, 
+   * if it is present.
+   * 
+   * @param o		the object to remove
+   * @return		true if this collection changed as a result of the call
+   */
+  public boolean remove(Object o) {
+    boolean	result;
+    
+    result = m_Root.remove(((String) o) + TrieNode.STOP);
+    
+    m_RecalcHashCode = result;
+    
+    return result;
+  }
+  
+  /**
+   * Removes all this collection's elements that are also contained in the 
+   * specified collection
+   * 
+   * @param c		the collection to remove
+   * @return		true if the collection changed
+   */
+  public boolean removeAll(Collection<?> c) {
+    boolean	result;
+    Iterator	iter;
+    
+    result = false;
+    
+    iter = c.iterator();
+    while (iter.hasNext()) {
+      result = remove(iter.next()) || result;
+    }
+    
+    m_RecalcHashCode = result;
+    
+    return result;
+  }
+  
+  /**
+   * Retains only the elements in this collection that are contained in 
+   * the specified collection
+   * 
+   * @param c		the collection to use as reference
+   * @return		true if this collection changed as a result of the call
+   */
+  public boolean retainAll(Collection<?> c) {
+    boolean	result;
+    Iterator	iter;
+    Object	o;
+    
+    result = false;
+    iter   = iterator();
+    while (iter.hasNext()) {
+      o = iter.next();
+      if (!c.contains(o))
+	result = remove(o) || result;
+    }
+    
+    m_RecalcHashCode = result;
+
+    return result;
+  }
+  
+  /**
+   * Returns the number of elements in this collection.
+   * 
+   * @return		the number of nodes in the tree
+   */
+  public int size() {
+    return m_Root.size();
+  }
+  
+  /**
+   * Returns an array containing all of the elements in this collection.
+   * 
+   * @return		the stored strings as array
+   */
+  public Object[] toArray() {
+    return toArray(new String[0]);
+  }
+  
+  /**
+   * Returns an array containing all of the elements in this collection; the 
+   * runtime type of the returned array is that of the specified array.
+   * 
+   * @param a		the array into which the elements of this collection 
+   * 			are to be stored
+   * @return		an array containing the elements of this collection
+   */
+  public <T> T[] toArray(T[] a) {
+    T[]	result;
+    Iterator<T>	iter;
+    Vector<T>	list;
+    int		i;
+    
+    list = new Vector<T>();
+    iter = Utils.<Iterator<T>>cast(iterator());
+    while (iter.hasNext())
+      list.add(iter.next());
+    
+    if (Array.getLength(a) != list.size())
+      result = Utils.<T[]>cast(Array.newInstance(a.getClass().getComponentType(), list.size()));
+    else
+      result = a;
+    
+    for (i = 0; i < list.size(); i++)
+      result[i] = list.get(i);
+    
+    return result;
+  }
+
+  /**
+   * returns the node as String
+   * 
+   * @param node	the node to turn into a string
+   * @return		the node as string
+   */
+  protected String toString(TrieNode node) {
+    StringBuffer	result;
+    int			i;
+    StringBuffer	indentation;
+    
+    result = new StringBuffer();
+    
+    // indent the node
+    indentation = new StringBuffer();
+    for (i = 0; i < node.getLevel(); i++)
+      indentation.append(" | ");
+    result.append(indentation.toString());
+    
+    // add the node label
+    if (node.getChar() == null)
+      result.append("<root>");
+    else if (node.getChar() == TrieNode.STOP)
+      result.append("STOP");
+    else
+      result.append("'" + node.getChar() + "'");
+    result.append("\n");
+
+    // add the children
+    for (i = 0; i < node.getChildCount(); i++)
+      result.append(toString((TrieNode) node.getChildAt(i)));
+    
+    return result.toString();
+  }
+  
+  /**
+   * returns the trie in string representation
+   * 
+   * @return		the trie as string
+   */
+  public String toString() {
+    return toString(m_Root);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Only for testing (prints the built Trie). Arguments are added to the Trie. 
+   * If not arguments provided then a few default strings are uses for building.
+   * 
+   * @param args	commandline arguments
+   */
+  public static void main(String[] args) {
+    String[] data;
+    
+    if (args.length == 0) {
+      data = new String[3];
+      data[0] = "this is a test";
+      data[1] = "this is another test";
+      data[2] = "and something else";
+    }
+    else {
+      data = args.clone();
+    }
+
+    // build trie
+    Trie t = new Trie();
+    for (int i = 0; i < data.length; i++)
+      t.add(data[i]);
+    System.out.println(t);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/UnassignedClassException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/UnassignedClassException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/UnassignedClassException.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnassignedClassException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Exception that is raised when trying to use some data that has no
+ * class assigned to it, but a class is needed to perform the operation.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class UnassignedClassException
+  extends RuntimeException {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6268278694768818235L;
+
+  /**
+   * Creates a new UnassignedClassException with no message.
+   *
+   */
+  public UnassignedClassException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new UnassignedClassException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public UnassignedClassException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/UnassignedDatasetException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/UnassignedDatasetException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/UnassignedDatasetException.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnassignedDatasetException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Exception that is raised when trying to use something that has no
+ * reference to a dataset, when one is required.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class UnassignedDatasetException
+  extends RuntimeException {
+
+  /** for serialization */
+  private static final long serialVersionUID = -9000116786626328854L;
+
+  /**
+   * Creates a new UnassignedDatasetException with no message.
+   *
+   */
+  public UnassignedDatasetException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new UnassignedDatasetException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public UnassignedDatasetException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Undoable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Undoable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Undoable.java	(revision 29)
@@ -0,0 +1,64 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copyable.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Interface implemented by classes that support undo.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public interface Undoable {
+  /**
+   * returns whether undo support is enabled
+   */
+  public boolean isUndoEnabled();
+  
+  /**
+   * sets whether undo support is enabled
+   */
+  public void setUndoEnabled(boolean enabled);
+
+  /**
+   * removes the undo history
+   */
+  public void clearUndo();
+  
+  /**
+   * returns whether an undo is possible, i.e. whether there are any undo points
+   * saved so far
+   * 
+   * @return returns TRUE if there is an undo possible 
+   */
+  public boolean canUndo();
+  
+  /**
+   * undoes the last action
+   */
+  public void undo();
+  
+  /**
+   * adds an undo point to the undo history 
+   */
+  public void addUndoPoint();
+}
Index: branches/MetisMQI/src/main/java/weka/core/UnsupportedAttributeTypeException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/UnsupportedAttributeTypeException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/UnsupportedAttributeTypeException.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnsuppotedAttributeTypeException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Exception that is raised by an object that is unable to process some of the
+ * attribute types it has been passed.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class UnsupportedAttributeTypeException
+  extends WekaException {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2658987325328414838L;
+
+  /**
+   * Creates a new UnsupportedAttributeTypeException with no message.
+   *
+   */
+  public UnsupportedAttributeTypeException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new UnsupportedAttributeTypeException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public UnsupportedAttributeTypeException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/UnsupportedClassTypeException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/UnsupportedClassTypeException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/UnsupportedClassTypeException.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnsuppotedClassTypeException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Exception that is raised by an object that is unable to process the
+ * class type of the data it has been passed.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class UnsupportedClassTypeException
+  extends WekaException {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5175741076972192151L;
+
+  /**
+   * Creates a new UnsupportedClassTypeException with no message.
+   *
+   */
+  public UnsupportedClassTypeException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new UnsupportedClassTypeException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public UnsupportedClassTypeException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/Utils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Utils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Utils.java	(revision 29)
@@ -0,0 +1,2072 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Utils.java
+ *    Copyright (C) 1999-2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.lang.Math;
+import java.lang.reflect.Array;
+import java.util.Properties;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class implementing some simple utility methods.
+ *
+ * @author Eibe Frank 
+ * @author Yong Wang 
+ * @author Len Trigg 
+ * @author Julien Prados
+ * @version $Revision: 5987 $
+ */
+public final class Utils
+  implements RevisionHandler {
+
+  /** The natural logarithm of 2. */
+  public static double log2 = Math.log(2);
+
+  /** The small deviation allowed in double comparisons. */
+  public static double SMALL = 1e-6;
+
+  /**
+   * Tests if the given value codes "missing".
+   *
+   * @param val the value to be tested
+   * @return true if val codes "missing"
+   */
+  public static boolean isMissingValue(double val) {
+
+    return Double.isNaN(val);
+  }
+
+  /**
+   * Returns the value used to code a missing value.  Note that
+   * equality tests on this value will always return false, so use
+   * isMissingValue(double val) for testing..
+   *
+   * @return the value used as missing value.
+   */
+  public static double missingValue() {
+    
+    return Double.NaN;
+  }
+
+  /**
+   * Casting an object without "unchecked" compile-time warnings.
+   * Use only when absolutely necessary (e.g. when using clone()).
+   */
+  @SuppressWarnings("unchecked")
+    public static <T> T cast(Object x) {
+    return (T) x;
+  }
+  
+  /**
+   * Reads properties that inherit from three locations. Properties
+   * are first defined in the system resource location (i.e. in the
+   * CLASSPATH).  These default properties must exist. Properties
+   * defined in the users home directory (optional) override default
+   * settings. Properties defined in the current directory (optional)
+   * override all these settings.
+   *
+   * @param resourceName the location of the resource that should be
+   * loaded.  e.g.: "weka/core/Utils.props". (The use of hardcoded
+   * forward slashes here is OK - see
+   * jdk1.1/docs/guide/misc/resources.html) This routine will also
+   * look for the file (in this case) "Utils.props" in the users home
+   * directory and the current directory.
+   * @return the Properties
+   * @exception Exception if no default properties are defined, or if
+   * an error occurs reading the properties files.  
+   */
+  public static Properties readProperties(String resourceName)
+    throws Exception {
+
+    Properties defaultProps = new Properties();
+    try {
+      // Apparently hardcoded slashes are OK here
+      // jdk1.1/docs/guide/misc/resources.html
+      //      defaultProps.load(ClassLoader.getSystemResourceAsStream(resourceName));
+      defaultProps.load((new Utils()).getClass().getClassLoader().getResourceAsStream(resourceName));
+    } catch (Exception ex) {
+/*      throw new Exception("Problem reading default properties: "
+	+ ex.getMessage()); */
+      System.err.println("Warning, unable to load properties file from "
+			 +"system resource (Utils.java)");
+    }
+
+    // Hardcoded slash is OK here
+    // eg: see jdk1.1/docs/guide/misc/resources.html
+    int slInd = resourceName.lastIndexOf('/');
+    if (slInd != -1) {
+      resourceName = resourceName.substring(slInd + 1);
+    }
+
+    // Allow a properties file in the home directory to override
+    Properties userProps = new Properties(defaultProps);    
+    File propFile = new File(System.getProperties().getProperty("user.home")
+                             + File.separatorChar
+                             + resourceName);
+    if (propFile.exists()) {
+      try {
+        userProps.load(new FileInputStream(propFile));
+      } catch (Exception ex) {
+        throw new Exception("Problem reading user properties: " + propFile);
+      }
+    }
+
+    // Allow a properties file in the current directory to override
+    Properties localProps = new Properties(userProps);
+    propFile = new File(resourceName);
+    if (propFile.exists()) {
+      try {
+        localProps.load(new FileInputStream(propFile));
+      } catch (Exception ex) {
+        throw new Exception("Problem reading local properties: " + propFile);
+      }
+    }
+    
+    return localProps;
+  }
+
+  /**
+   * Returns the correlation coefficient of two double vectors.
+   *
+   * @param y1 double vector 1
+   * @param y2 double vector 2
+   * @param n the length of two double vectors
+   * @return the correlation coefficient
+   */
+  public static final double correlation(double y1[],double y2[],int n) {
+
+    int i;
+    double av1 = 0.0, av2 = 0.0, y11 = 0.0, y22 = 0.0, y12 = 0.0, c;
+    
+    if (n <= 1) {
+      return 1.0;
+    }
+    for (i = 0; i < n; i++) {
+      av1 += y1[i];
+      av2 += y2[i];
+    }
+    av1 /= (double) n;
+    av2 /= (double) n;
+    for (i = 0; i < n; i++) {
+      y11 += (y1[i] - av1) * (y1[i] - av1);
+      y22 += (y2[i] - av2) * (y2[i] - av2);
+      y12 += (y1[i] - av1) * (y2[i] - av2);
+    }
+    if (y11 * y22 == 0.0) {
+      c=1.0;
+    } else {
+      c = y12 / Math.sqrt(Math.abs(y11 * y22));
+    }
+    
+    return c;
+  }
+
+  /**
+   * Removes all occurrences of a string from another string.
+   *
+   * @param inString the string to remove substrings from.
+   * @param substring the substring to remove.
+   * @return the input string with occurrences of substring removed.
+   */
+  public static String removeSubstring(String inString, String substring) {
+
+    StringBuffer result = new StringBuffer();
+    int oldLoc = 0, loc = 0;
+    while ((loc = inString.indexOf(substring, oldLoc))!= -1) {
+      result.append(inString.substring(oldLoc, loc));
+      oldLoc = loc + substring.length();
+    }
+    result.append(inString.substring(oldLoc));
+    return result.toString();
+  }
+
+  /**
+   * Replaces with a new string, all occurrences of a string from 
+   * another string.
+   *
+   * @param inString the string to replace substrings in.
+   * @param subString the substring to replace.
+   * @param replaceString the replacement substring
+   * @return the input string with occurrences of substring replaced.
+   */
+  public static String replaceSubstring(String inString, String subString,
+					String replaceString) {
+
+    StringBuffer result = new StringBuffer();
+    int oldLoc = 0, loc = 0;
+    while ((loc = inString.indexOf(subString, oldLoc))!= -1) {
+      result.append(inString.substring(oldLoc, loc));
+      result.append(replaceString);
+      oldLoc = loc + subString.length();
+    }
+    result.append(inString.substring(oldLoc));
+    return result.toString();
+  }
+
+
+  /**
+   * Pads a string to a specified length, inserting spaces on the left
+   * as required. If the string is too long, characters are removed (from
+   * the right).
+   *
+   * @param inString the input string
+   * @param length the desired length of the output string
+   * @return the output string
+   */
+  public static String padLeft(String inString, int length) {
+
+    return fixStringLength(inString, length, false);
+  }
+  
+  /**
+   * Pads a string to a specified length, inserting spaces on the right
+   * as required. If the string is too long, characters are removed (from
+   * the right).
+   *
+   * @param inString the input string
+   * @param length the desired length of the output string
+   * @return the output string
+   */
+  public static String padRight(String inString, int length) {
+
+    return fixStringLength(inString, length, true);
+  }
+  
+  /**
+   * Pads a string to a specified length, inserting spaces as
+   * required. If the string is too long, characters are removed (from
+   * the right).
+   *
+   * @param inString the input string
+   * @param length the desired length of the output string
+   * @param right true if inserted spaces should be added to the right
+   * @return the output string
+   */
+  private static /*@pure@*/ String fixStringLength(String inString, int length,
+					boolean right) {
+
+    if (inString.length() < length) {
+      while (inString.length() < length) {
+	inString = (right ? inString.concat(" ") : " ".concat(inString));
+      }
+    } else if (inString.length() > length) {
+      inString = inString.substring(0, length);
+    }
+    return inString;
+  }
+ 
+  /**
+   * Rounds a double and converts it into String.
+   *
+   * @param value the double value
+   * @param afterDecimalPoint the (maximum) number of digits permitted
+   * after the decimal point
+   * @return the double as a formatted string
+   */
+  public static /*@pure@*/ String doubleToString(double value, int afterDecimalPoint) {
+    
+    StringBuffer stringBuffer;
+    double temp;
+    int dotPosition;
+    long precisionValue;
+    
+    temp = value * Math.pow(10.0, afterDecimalPoint);
+    if (Math.abs(temp) < Long.MAX_VALUE) {
+      precisionValue = 	(temp > 0) ? (long)(temp + 0.5) 
+                                   : -(long)(Math.abs(temp) + 0.5);
+      if (precisionValue == 0) {
+	stringBuffer = new StringBuffer(String.valueOf(0));
+      } else {
+	stringBuffer = new StringBuffer(String.valueOf(precisionValue));
+      }
+      if (afterDecimalPoint == 0) {
+	return stringBuffer.toString();
+      }
+      dotPosition = stringBuffer.length() - afterDecimalPoint;
+      while (((precisionValue < 0) && (dotPosition < 1)) ||
+	     (dotPosition < 0)) {
+	if (precisionValue < 0) {
+	  stringBuffer.insert(1, '0');
+	} else {
+	  stringBuffer.insert(0, '0');
+	}
+	dotPosition++;
+      }
+      stringBuffer.insert(dotPosition, '.');
+      if ((precisionValue < 0) && (stringBuffer.charAt(1) == '.')) {
+	stringBuffer.insert(1, '0');
+      } else if (stringBuffer.charAt(0) == '.') {
+	stringBuffer.insert(0, '0');
+      }
+      int currentPos = stringBuffer.length() - 1;
+      while ((currentPos > dotPosition) &&
+	     (stringBuffer.charAt(currentPos) == '0')) {
+	stringBuffer.setCharAt(currentPos--, ' ');
+      }
+      if (stringBuffer.charAt(currentPos) == '.') {
+	stringBuffer.setCharAt(currentPos, ' ');
+      }
+      
+      return stringBuffer.toString().trim();
+    }
+    return new String("" + value);
+  }
+
+  /**
+   * Rounds a double and converts it into a formatted decimal-justified String.
+   * Trailing 0's are replaced with spaces.
+   *
+   * @param value the double value
+   * @param width the width of the string
+   * @param afterDecimalPoint the number of digits after the decimal point
+   * @return the double as a formatted string
+   */
+  public static /*@pure@*/ String doubleToString(double value, int width,
+				      int afterDecimalPoint) {
+    
+    String tempString = doubleToString(value, afterDecimalPoint);
+    char[] result;
+    int dotPosition;
+
+    if ((afterDecimalPoint >= width) 
+        || (tempString.indexOf('E') != -1)) { // Protects sci notation
+      return tempString;
+    }
+
+    // Initialize result
+    result = new char[width];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = ' ';
+    }
+
+    if (afterDecimalPoint > 0) {
+      // Get position of decimal point and insert decimal point
+      dotPosition = tempString.indexOf('.');
+      if (dotPosition == -1) {
+	dotPosition = tempString.length();
+      } else {
+	result[width - afterDecimalPoint - 1] = '.';
+      }
+    } else {
+      dotPosition = tempString.length();
+    }
+    
+
+    int offset = width - afterDecimalPoint - dotPosition;
+    if (afterDecimalPoint > 0) {
+      offset--;
+    }
+
+    // Not enough room to decimal align within the supplied width
+    if (offset < 0) {
+      return tempString;
+    }
+
+    // Copy characters before decimal point
+    for (int i = 0; i < dotPosition; i++) {
+      result[offset + i] = tempString.charAt(i);
+    }
+
+    // Copy characters after decimal point
+    for (int i = dotPosition + 1; i < tempString.length(); i++) {
+      result[offset + i] = tempString.charAt(i);
+    }
+
+    return new String(result);
+  }
+
+  /**
+   * Returns the basic class of an array class (handles multi-dimensional
+   * arrays).
+   * @param c        the array to inspect
+   * @return         the class of the innermost elements
+   */
+  public static Class getArrayClass(Class c) {
+     if (c.getComponentType().isArray())
+        return getArrayClass(c.getComponentType());
+     else
+        return c.getComponentType();
+  }
+
+  /**
+   * Returns the dimensions of the given array. Even though the
+   * parameter is of type "Object" one can hand over primitve arrays, e.g.
+   * int[3] or double[2][4].
+   *
+   * @param array       the array to determine the dimensions for
+   * @return            the dimensions of the array
+   */
+  public static int getArrayDimensions(Class array) {
+    if (array.getComponentType().isArray())
+      return 1 + getArrayDimensions(array.getComponentType());
+    else
+      return 1;
+  }
+
+  /**
+   * Returns the dimensions of the given array. Even though the
+   * parameter is of type "Object" one can hand over primitve arrays, e.g.
+   * int[3] or double[2][4].
+   *
+   * @param array       the array to determine the dimensions for
+   * @return            the dimensions of the array
+   */
+  public static int getArrayDimensions(Object array) {
+    return getArrayDimensions(array.getClass());
+  }
+
+  /**
+   * Returns the given Array in a string representation. Even though the
+   * parameter is of type "Object" one can hand over primitve arrays, e.g.
+   * int[3] or double[2][4].
+   * 
+   * @param array       the array to return in a string representation
+   * @return            the array as string
+   */
+  public static String arrayToString(Object array) {
+    String        result;
+    int           dimensions;
+    int           i;       
+
+    result     = "";
+    dimensions = getArrayDimensions(array);
+    
+    if (dimensions == 0) {
+      result = "null";
+    }
+    else if (dimensions == 1) {
+      for (i = 0; i < Array.getLength(array); i++) {
+        if (i > 0)
+          result += ",";
+        if (Array.get(array, i) == null)
+          result += "null";
+        else
+          result += Array.get(array, i).toString();
+      }
+    }
+    else {
+      for (i = 0; i < Array.getLength(array); i++) {
+        if (i > 0)
+          result += ",";
+        result += "[" + arrayToString(Array.get(array, i)) + "]";
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tests if a is equal to b.
+   *
+   * @param a a double
+   * @param b a double
+   */
+  public static /*@pure@*/ boolean eq(double a, double b){
+    
+    return (a - b < SMALL) && (b - a < SMALL); 
+  }
+
+  /**
+   * Checks if the given array contains any non-empty options.
+   *
+   * @param options an array of strings
+   * @exception Exception if there are any non-empty options
+   */
+  public static void checkForRemainingOptions(String[] options) 
+    throws Exception {
+    
+    int illegalOptionsFound = 0;
+    StringBuffer text = new StringBuffer();
+
+    if (options == null) {
+      return;
+    }
+    for (int i = 0; i < options.length; i++) {
+      if (options[i].length() > 0) {
+	illegalOptionsFound++;
+	text.append(options[i] + ' ');
+      }
+    }
+    if (illegalOptionsFound > 0) {
+      throw new Exception("Illegal options: " + text);
+    }
+  }
+  
+  /**
+   * Checks if the given array contains the flag "-Char". Stops
+   * searching at the first marker "--". If the flag is found,
+   * it is replaced with the empty string.
+   *
+   * @param flag the character indicating the flag.
+   * @param options the array of strings containing all the options.
+   * @return true if the flag was found
+   * @exception Exception if an illegal option was found
+   */
+  public static boolean getFlag(char flag, String[] options) 
+    throws Exception {
+    
+    return getFlag("" + flag, options);
+  }
+  
+  /**
+   * Checks if the given array contains the flag "-String". Stops
+   * searching at the first marker "--". If the flag is found,
+   * it is replaced with the empty string.
+   *
+   * @param flag the String indicating the flag.
+   * @param options the array of strings containing all the options.
+   * @return true if the flag was found
+   * @exception Exception if an illegal option was found
+   */
+  public static boolean getFlag(String flag, String[] options) 
+    throws Exception {
+    
+    int pos = getOptionPos(flag, options);
+
+    if (pos > -1)
+      options[pos] = "";
+    
+    return (pos > -1);
+  }
+
+  /**
+   * Gets an option indicated by a flag "-Char" from the given array
+   * of strings. Stops searching at the first marker "--". Replaces 
+   * flag and option with empty strings.
+   *
+   * @param flag the character indicating the option.
+   * @param options the array of strings containing all the options.
+   * @return the indicated option or an empty string
+   * @exception Exception if the option indicated by the flag can't be found
+   */
+  public static /*@non_null@*/ String getOption(char flag, String[] options) 
+    throws Exception {
+    
+    return getOption("" + flag, options);
+  }
+
+  /**
+   * Gets an option indicated by a flag "-String" from the given array
+   * of strings. Stops searching at the first marker "--". Replaces 
+   * flag and option with empty strings.
+   *
+   * @param flag the String indicating the option.
+   * @param options the array of strings containing all the options.
+   * @return the indicated option or an empty string
+   * @exception Exception if the option indicated by the flag can't be found
+   */
+  public static /*@non_null@*/ String getOption(String flag, String[] options) 
+    throws Exception {
+
+    String newString;
+    int i = getOptionPos(flag, options);
+
+    if (i > -1) {
+      if (options[i].equals("-" + flag)) {
+	if (i + 1 == options.length) {
+	  throw new Exception("No value given for -" + flag + " option.");
+	}
+	options[i] = "";
+	newString = new String(options[i + 1]);
+	options[i + 1] = "";
+	return newString;
+      }
+      if (options[i].charAt(1) == '-') {
+	return "";
+      }
+    }
+    
+    return "";
+  }
+
+  /**
+   * Gets the index of an option or flag indicated by a flag "-Char" from 
+   * the given array of strings. Stops searching at the first marker "--".
+   *
+   * @param flag 	the character indicating the option.
+   * @param options 	the array of strings containing all the options.
+   * @return 		the position if found, or -1 otherwise
+   */
+  public static int getOptionPos(char flag, String[] options) {
+     return getOptionPos("" + flag, options);
+  }
+
+  /**
+   * Gets the index of an option or flag indicated by a flag "-String" from 
+   * the given array of strings. Stops searching at the first marker "--".
+   *
+   * @param flag 	the String indicating the option.
+   * @param options 	the array of strings containing all the options.
+   * @return 		the position if found, or -1 otherwise
+   */
+  public static int getOptionPos(String flag, String[] options) {
+    if (options == null)
+      return -1;
+    
+    for (int i = 0; i < options.length; i++) {
+      if ((options[i].length() > 0) && (options[i].charAt(0) == '-')) {
+	// Check if it is a negative number
+	try {
+	  Double.valueOf(options[i]);
+	} 
+	catch (NumberFormatException e) {
+	  // found?
+	  if (options[i].equals("-" + flag))
+	    return i;
+	  // did we reach "--"?
+	  if (options[i].charAt(1) == '-')
+	    return -1;
+	}
+      }
+    }
+    
+    return -1;
+  }
+
+  /**
+   * Quotes a string if it contains special characters.
+   * 
+   * The following rules are applied:
+   *
+   * A character is backquoted version of it is one 
+   * of <tt>" ' % \ \n \r \t</tt>.
+   *
+   * A string is enclosed within single quotes if a character has been
+   * backquoted using the previous rule above or contains 
+   * <tt>{ }</tt> or is exactly equal to the strings 
+   * <tt>, ? space or ""</tt> (empty string).
+   *
+   * A quoted question mark distinguishes it from the missing value which
+   * is represented as an unquoted question mark in arff files.
+   *
+   * @param string 	the string to be quoted
+   * @return 		the string (possibly quoted)
+   * @see		#unquote(String)
+   */
+  public static /*@pure@*/ String quote(String string) {
+      boolean quote = false;
+
+      // backquote the following characters 
+      if ((string.indexOf('\n') != -1) || (string.indexOf('\r') != -1) || 
+	  (string.indexOf('\'') != -1) || (string.indexOf('"') != -1) || 
+	  (string.indexOf('\\') != -1) || 
+	  (string.indexOf('\t') != -1) || (string.indexOf('%') != -1)) {
+	  string = backQuoteChars(string);
+	  quote = true;
+      }
+
+      // Enclose the string in 's if the string contains a recently added
+      // backquote or contains one of the following characters.
+      if((quote == true) || 
+	 (string.indexOf('{') != -1) || (string.indexOf('}') != -1) ||
+	 (string.indexOf(',') != -1) || (string.equals("?")) ||
+	 (string.indexOf(' ') != -1) || (string.equals(""))) {
+	  string = ("'".concat(string)).concat("'");
+      }
+
+      return string;
+  }
+
+  /**
+   * unquotes are previously quoted string (but only if necessary), i.e., it
+   * removes the single quotes around it. Inverse to quote(String).
+   * 
+   * @param string	the string to process
+   * @return		the unquoted string
+   * @see		#quote(String)
+   */
+  public static String unquote(String string) {
+    if (string.startsWith("'") && string.endsWith("'")) {
+      string = string.substring(1, string.length() - 1);
+      
+      if ((string.indexOf("\\n") != -1) || (string.indexOf("\\r") != -1) || 
+	  (string.indexOf("\\'") != -1) || (string.indexOf("\\\"") != -1) || 
+	  (string.indexOf("\\\\") != -1) || 
+	  (string.indexOf("\\t") != -1) || (string.indexOf("\\%") != -1)) {
+	string = unbackQuoteChars(string);
+      }
+    }
+
+    return string;
+  }
+
+  /**
+   * Converts carriage returns and new lines in a string into \r and \n.
+   * Backquotes the following characters: ` " \ \t and %
+   * 
+   * @param string 	the string
+   * @return 		the converted string
+   * @see		#unbackQuoteChars(String)
+   */
+  public static /*@pure@*/ String backQuoteChars(String string) {
+
+    int index;
+    StringBuffer newStringBuffer;
+
+    // replace each of the following characters with the backquoted version
+    char   charsFind[] =    {'\\',   '\'',  '\t',  '\n',  '\r',  '"',    '%'};
+    String charsReplace[] = {"\\\\", "\\'", "\\t", "\\n", "\\r", "\\\"", "\\%"};
+    for (int i = 0; i < charsFind.length; i++) {
+      if (string.indexOf(charsFind[i]) != -1 ) {
+	newStringBuffer = new StringBuffer();
+	while ((index = string.indexOf(charsFind[i])) != -1) {
+	  if (index > 0) {
+	    newStringBuffer.append(string.substring(0, index));
+	  }
+	  newStringBuffer.append(charsReplace[i]);
+	  if ((index + 1) < string.length()) {
+	    string = string.substring(index + 1);
+	  } else {
+	    string = "";
+	  }
+	}
+	newStringBuffer.append(string);
+	string = newStringBuffer.toString();
+      }
+    }
+
+    return string;
+  }
+
+  /**
+   * Converts carriage returns and new lines in a string into \r and \n.
+   *
+   * @param string the string
+   * @return the converted string
+   */
+  public static String convertNewLines(String string) {
+    int index;
+
+    // Replace with \n
+    StringBuffer newStringBuffer = new StringBuffer();
+    while ((index = string.indexOf('\n')) != -1) {
+      if (index > 0) {
+	newStringBuffer.append(string.substring(0, index));
+      }
+      newStringBuffer.append('\\');
+      newStringBuffer.append('n');
+      if ((index + 1) < string.length()) {
+	string = string.substring(index + 1);
+      } else {
+	string = "";
+      }
+    }
+    newStringBuffer.append(string);
+    string = newStringBuffer.toString();
+
+    // Replace with \r
+    newStringBuffer = new StringBuffer();
+    while ((index = string.indexOf('\r')) != -1) {
+      if (index > 0) {
+	newStringBuffer.append(string.substring(0, index));
+      }
+      newStringBuffer.append('\\');
+      newStringBuffer.append('r');
+      if ((index + 1) < string.length()){
+	string = string.substring(index + 1);
+      } else {
+	string = "";
+      }
+    }
+    newStringBuffer.append(string);
+    return newStringBuffer.toString();
+  }
+
+  /**
+   * Reverts \r and \n in a string into carriage returns and new lines.
+   * 
+   * @param string the string
+   * @return the converted string
+   */
+  public static String revertNewLines(String string) {
+    int index;
+
+    // Replace with \n
+    StringBuffer newStringBuffer = new StringBuffer();
+    while ((index = string.indexOf("\\n")) != -1) {
+      if (index > 0) {
+	newStringBuffer.append(string.substring(0, index));
+      }
+      newStringBuffer.append('\n');
+      if ((index + 2) < string.length()) {
+	string = string.substring(index + 2);
+      } else {
+	string = "";
+      }
+    }
+    newStringBuffer.append(string);
+    string = newStringBuffer.toString();
+
+    // Replace with \r
+    newStringBuffer = new StringBuffer();
+    while ((index = string.indexOf("\\r")) != -1) {
+      if (index > 0) {
+	newStringBuffer.append(string.substring(0, index));
+      }
+      newStringBuffer.append('\r');
+      if ((index + 2) < string.length()){
+	string = string.substring(index + 2);
+      } else {
+	string = "";
+      }
+    }
+    newStringBuffer.append(string);
+    
+    return newStringBuffer.toString();
+  }
+
+  /**
+   * Returns the secondary set of options (if any) contained in
+   * the supplied options array. The secondary set is defined to
+   * be any options after the first "--". These options are removed from
+   * the original options array.
+   *
+   * @param options the input array of options
+   * @return the array of secondary options
+   */
+  public static String[] partitionOptions(String[] options) {
+
+    for (int i = 0; i < options.length; i++) {
+      if (options[i].equals("--")) {
+	options[i++] = "";
+	String[] result = new String [options.length - i];
+	for (int j = i; j < options.length; j++) {
+	  result[j - i] = options[j];
+	  options[j] = "";
+	}
+	return result;
+      }
+    }
+    return new String [0];
+  }
+    
+  /**
+   * The inverse operation of backQuoteChars().
+   * Converts back-quoted carriage returns and new lines in a string 
+   * to the corresponding character ('\r' and '\n').
+   * Also "un"-back-quotes the following characters: ` " \ \t and %
+   *
+   * @param string 	the string
+   * @return 		the converted string
+   * @see		#backQuoteChars(String)
+   */
+  public static String unbackQuoteChars(String string) {
+
+    int index;
+    StringBuffer newStringBuffer;
+    
+    // replace each of the following characters with the backquoted version
+    String charsFind[]    = {"\\\\", "\\'", "\\t", "\\n", "\\r", "\\\"", "\\%"};
+    char   charsReplace[] = {'\\',   '\'',  '\t',  '\n',  '\r',  '"',    '%'};
+    int pos[] = new int[charsFind.length];
+    int	curPos;
+    
+    String str = new String(string);
+    newStringBuffer = new StringBuffer();
+    while (str.length() > 0) {
+      // get positions and closest character to replace
+      curPos = str.length();
+      index  = -1;
+      for (int i = 0; i < pos.length; i++) {
+	pos[i] = str.indexOf(charsFind[i]);
+	if ( (pos[i] > -1) && (pos[i] < curPos) ) {
+	  index  = i;
+	  curPos = pos[i];
+	}
+      }
+      
+      // replace character if found, otherwise finished
+      if (index == -1) {
+	newStringBuffer.append(str);
+	str = "";
+      }
+      else {
+	newStringBuffer.append(str.substring(0, pos[index]));
+	newStringBuffer.append(charsReplace[index]);
+	str = str.substring(pos[index] + charsFind[index].length());
+      }
+    }
+
+    return newStringBuffer.toString();
+  }    
+  
+  /**
+   * Split up a string containing options into an array of strings,
+   * one for each option.
+   *
+   * @param 		quotedOptionString the string containing the options
+   * @return 		the array of options
+   * @throws Exception 	in case of an unterminated string, unknown character or
+   * 			a parse error
+   */
+  public static String[] splitOptions(String quotedOptionString) throws Exception{
+
+    Vector<String> optionsVec = new Vector<String>();
+    String str = new String(quotedOptionString);
+    int i;
+    
+    while (true){
+
+      //trimLeft 
+      i = 0;
+      while ((i < str.length()) && (Character.isWhitespace(str.charAt(i)))) i++;
+      str = str.substring(i);
+      
+      //stop when str is empty
+      if (str.length() == 0) break;
+      
+      //if str start with a double quote
+      if (str.charAt(0) == '"'){
+	
+	//find the first not anti-slached double quote
+	i = 1;
+	while(i < str.length()){
+	  if (str.charAt(i) == str.charAt(0)) break;
+	  if (str.charAt(i) == '\\'){
+	    i += 1;
+	    if (i >= str.length()) 
+	      throw new Exception("String should not finish with \\");
+	  }
+	  i += 1;
+	}
+	if (i >= str.length()) throw new Exception("Quote parse error.");
+	
+	//add the founded string to the option vector (without quotes)
+	String optStr = str.substring(1,i);
+	optStr = unbackQuoteChars(optStr);
+	optionsVec.addElement(optStr);
+	str = str.substring(i+1);
+      } else {
+	//find first whiteSpace
+	i=0;
+	while((i < str.length()) && (!Character.isWhitespace(str.charAt(i)))) i++;
+	
+	//add the founded string to the option vector
+	String optStr = str.substring(0,i);
+	optionsVec.addElement(optStr);
+	str = str.substring(i);
+      }
+    }
+    
+    //convert optionsVec to an array of String
+    String[] options = new String[optionsVec.size()];
+    for (i = 0; i < optionsVec.size(); i++) {
+      options[i] = (String)optionsVec.elementAt(i);
+    }
+    return options;
+  }    
+
+  /**
+   * Joins all the options in an option array into a single string,
+   * as might be used on the command line.
+   *
+   * @param optionArray the array of options
+   * @return the string containing all options.
+   */
+  public static String joinOptions(String[] optionArray) {
+
+    String optionString = "";
+    for (int i = 0; i < optionArray.length; i++) {
+      if (optionArray[i].equals("")) {
+	continue;
+      }
+      boolean escape = false;
+      for (int n = 0; n < optionArray[i].length(); n++) {
+	if (Character.isWhitespace(optionArray[i].charAt(n))) {
+	  escape = true;
+	  break;
+	}
+      }
+      if (escape) {
+	optionString += '"' + backQuoteChars(optionArray[i]) + '"';
+      } else {
+	optionString += optionArray[i];
+      }
+      optionString += " ";
+    }
+    return optionString.trim();
+  }
+  
+  /**
+   * Creates a new instance of an object given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * object implements OptionHandler and the options parameter is
+   * non-null, the object will have it's options set. Example use:<p>
+   *
+   * <code> <pre>
+   * String classifierName = Utils.getOption('W', options);
+   * Classifier c = (Classifier)Utils.forName(Classifier.class,
+   *                                          classifierName,
+   *                                          options);
+   * setClassifier(c);
+   * </pre></code>
+   *
+   * @param classType the class that the instantiated object should
+   * be assignable to -- an exception is thrown if this is not the case
+   * @param className the fully qualified class name of the object
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null. Any options accepted by the object will be removed from the
+   * array.
+   * @return the newly created object, ready for use.
+   * @exception Exception if the class name is invalid, or if the
+   * class is not assignable to the desired class type, or the options
+   * supplied are not acceptable to the object
+   */
+  public static Object forName(Class<?> classType,
+			       String className,
+			       String[] options) throws Exception {
+
+    Class<?> c = null;
+    try {
+      c = Class.forName(className);
+    } catch (Exception ex) {
+      throw new Exception("Can't find class called: " + className);
+    }
+    if (!classType.isAssignableFrom(c)) {
+      throw new Exception(classType.getName() + " is not assignable from "
+			  + className);
+    }
+    Object o = c.newInstance();
+    if ((o instanceof OptionHandler)
+	&& (options != null)) {
+      ((OptionHandler)o).setOptions(options);
+      Utils.checkForRemainingOptions(options);
+    }
+    return o;
+  }
+
+  /**
+   * Generates a commandline of the given object. If the object is not 
+   * implementing OptionHandler, then it will only return the classname,
+   * otherwise also the options.
+   * 
+   * @param obj		the object to turn into a commandline
+   * @return		the commandline
+   */
+  public static String toCommandLine(Object obj) {
+    StringBuffer	result;
+    
+    result = new StringBuffer();
+    
+    if (obj != null) {
+      result.append(obj.getClass().getName());
+      if (obj instanceof OptionHandler)
+	result.append(" " + joinOptions(((OptionHandler) obj).getOptions()));
+    }
+    
+    return result.toString().trim();
+  }
+  
+  /**
+   * Computes entropy for an array of integers.
+   *
+   * @param counts array of counts
+   * @return - a log2 a - b log2 b - c log2 c + (a+b+c) log2 (a+b+c)
+   * when given array [a b c]
+   */
+  public static /*@pure@*/ double info(int counts[]) {
+    
+    int total = 0;
+    double x = 0;
+    for (int j = 0; j < counts.length; j++) {
+      x -= xlogx(counts[j]);
+      total += counts[j];
+    }
+    return x + xlogx(total);
+  }
+
+  /**
+   * Tests if a is smaller or equal to b.
+   *
+   * @param a a double
+   * @param b a double
+   */
+  public static /*@pure@*/ boolean smOrEq(double a,double b) {
+    
+    return (a-b < SMALL);
+  }
+
+  /**
+   * Tests if a is greater or equal to b.
+   *
+   * @param a a double
+   * @param b a double
+   */
+  public static /*@pure@*/ boolean grOrEq(double a,double b) {
+    
+    return (b-a < SMALL);
+  }
+  
+  /**
+   * Tests if a is smaller than b.
+   *
+   * @param a a double
+   * @param b a double
+   */
+  public static /*@pure@*/ boolean sm(double a,double b) {
+    
+    return (b-a > SMALL);
+  }
+
+  /**
+   * Tests if a is greater than b.
+   *
+   * @param a a double
+   * @param b a double 
+   */
+  public static /*@pure@*/ boolean gr(double a,double b) {
+    
+    return (a-b > SMALL);
+  }
+
+  /**
+   * Returns the kth-smallest value in the array.
+   *
+   * @param array the array of integers
+   * @param k the value of k
+   * @return the kth-smallest value
+   */
+  public static double kthSmallestValue(int[] array, int k) {
+
+    int[] index = new int[array.length];
+    
+    for (int i = 0; i < index.length; i++) {
+      index[i] = i;
+    }
+
+    return array[index[select(array, index, 0, array.length - 1, k)]];
+  }
+
+  /**
+   * Returns the kth-smallest value in the array
+   *
+   * @param array the array of double
+   * @param k the value of k
+   * @return the kth-smallest value
+   */
+  public static double kthSmallestValue(double[] array, int k) {
+
+    int[] index = new int[array.length];
+    
+    for (int i = 0; i < index.length; i++) {
+      index[i] = i;
+    }
+
+    return array[index[select(array, index, 0, array.length - 1, k)]];
+  }
+
+  /**
+   * Returns the logarithm of a for base 2.
+   *
+   * @param a 	a double
+   * @return	the logarithm for base 2
+   */
+  public static /*@pure@*/ double log2(double a) {
+    
+    return Math.log(a) / log2;
+  }
+
+  /**
+   * Returns index of maximum element in a given
+   * array of doubles. First maximum is returned.
+   *
+   * @param doubles the array of doubles
+   * @return the index of the maximum element
+   */
+  public static /*@pure@*/ int maxIndex(double[] doubles) {
+
+    double maximum = 0;
+    int maxIndex = 0;
+
+    for (int i = 0; i < doubles.length; i++) {
+      if ((i == 0) || (doubles[i] > maximum)) {
+	maxIndex = i;
+	maximum = doubles[i];
+      }
+    }
+
+    return maxIndex;
+  }
+
+  /**
+   * Returns index of maximum element in a given
+   * array of integers. First maximum is returned.
+   *
+   * @param ints the array of integers
+   * @return the index of the maximum element
+   */
+  public static /*@pure@*/ int maxIndex(int[] ints) {
+
+    int maximum = 0;
+    int maxIndex = 0;
+
+    for (int i = 0; i < ints.length; i++) {
+      if ((i == 0) || (ints[i] > maximum)) {
+	maxIndex = i;
+	maximum = ints[i];
+      }
+    }
+
+    return maxIndex;
+  }
+
+  /**
+   * Computes the mean for an array of doubles.
+   *
+   * @param vector the array
+   * @return the mean
+   */
+  public static /*@pure@*/ double mean(double[] vector) {
+  
+    double sum = 0;
+
+    if (vector.length == 0) {
+      return 0;
+    }
+    for (int i = 0; i < vector.length; i++) {
+      sum += vector[i];
+    }
+    return sum / (double) vector.length;
+  }
+
+  /**
+   * Returns index of minimum element in a given
+   * array of integers. First minimum is returned.
+   *
+   * @param ints the array of integers
+   * @return the index of the minimum element
+   */
+  public static /*@pure@*/ int minIndex(int[] ints) {
+
+    int minimum = 0;
+    int minIndex = 0;
+
+    for (int i = 0; i < ints.length; i++) {
+      if ((i == 0) || (ints[i] < minimum)) {
+	minIndex = i;
+	minimum = ints[i];
+      }
+    }
+
+    return minIndex;
+  }
+
+  /**
+   * Returns index of minimum element in a given
+   * array of doubles. First minimum is returned.
+   *
+   * @param doubles the array of doubles
+   * @return the index of the minimum element
+   */
+  public static /*@pure@*/ int minIndex(double[] doubles) {
+
+    double minimum = 0;
+    int minIndex = 0;
+
+    for (int i = 0; i < doubles.length; i++) {
+      if ((i == 0) || (doubles[i] < minimum)) {
+	minIndex = i;
+	minimum = doubles[i];
+      }
+    }
+
+    return minIndex;
+  }
+
+  /**
+   * Normalizes the doubles in the array by their sum.
+   *
+   * @param doubles the array of double
+   * @exception IllegalArgumentException if sum is Zero or NaN
+   */
+  public static void normalize(double[] doubles) {
+
+    double sum = 0;
+    for (int i = 0; i < doubles.length; i++) {
+      sum += doubles[i];
+    }
+    normalize(doubles, sum);
+  }
+
+  /**
+   * Normalizes the doubles in the array using the given value.
+   *
+   * @param doubles the array of double
+   * @param sum the value by which the doubles are to be normalized
+   * @exception IllegalArgumentException if sum is zero or NaN
+   */
+  public static void normalize(double[] doubles, double sum) {
+
+    if (Double.isNaN(sum)) {
+      throw new IllegalArgumentException("Can't normalize array. Sum is NaN.");
+    }
+    if (sum == 0) {
+      // Maybe this should just be a return.
+      throw new IllegalArgumentException("Can't normalize array. Sum is zero.");
+    }
+    for (int i = 0; i < doubles.length; i++) {
+      doubles[i] /= sum;
+    }
+  }
+
+  /**
+   * Converts an array containing the natural logarithms of
+   * probabilities stored in a vector back into probabilities.
+   * The probabilities are assumed to sum to one.
+   *
+   * @param a an array holding the natural logarithms of the probabilities
+   * @return the converted array 
+   */
+  public static double[] logs2probs(double[] a) {
+
+    double max = a[maxIndex(a)];
+    double sum = 0.0;
+
+    double[] result = new double[a.length];
+    for(int i = 0; i < a.length; i++) {
+      result[i] = Math.exp(a[i] - max);
+      sum += result[i];
+    }
+
+    normalize(result, sum);
+
+    return result;
+  } 
+
+  /**
+   * Returns the log-odds for a given probabilitiy.
+   *
+   * @param prob the probabilitiy
+   *
+   * @return the log-odds after the probability has been mapped to
+   * [Utils.SMALL, 1-Utils.SMALL]
+   */
+  public static /*@pure@*/ double probToLogOdds(double prob) {
+
+    if (gr(prob, 1) || (sm(prob, 0))) {
+      throw new IllegalArgumentException("probToLogOdds: probability must " +
+				     "be in [0,1] "+prob);
+    }
+    double p = SMALL + (1.0 - 2 * SMALL) * prob;
+    return Math.log(p / (1 - p));
+  }
+
+  /**
+   * Rounds a double to the next nearest integer value. The JDK version
+   * of it doesn't work properly.
+   *
+   * @param value the double value
+   * @return the resulting integer value
+   */
+  public static /*@pure@*/ int round(double value) {
+
+    int roundedValue = value > 0
+      ? (int)(value + 0.5)
+      : -(int)(Math.abs(value) + 0.5);
+    
+    return roundedValue;
+  }
+
+  /**
+   * Rounds a double to the next nearest integer value in a probabilistic
+   * fashion (e.g. 0.8 has a 20% chance of being rounded down to 0 and a
+   * 80% chance of being rounded up to 1). In the limit, the average of
+   * the rounded numbers generated by this procedure should converge to
+   * the original double.
+   *
+   * @param value the double value
+   * @param rand the random number generator
+   * @return the resulting integer value
+   */
+  public static int probRound(double value, Random rand) {
+
+    if (value >= 0) {
+      double lower = Math.floor(value);
+      double prob = value - lower;
+      if (rand.nextDouble() < prob) {
+	return (int)lower + 1;
+      } else {
+	return (int)lower;
+      }
+    } else {
+      double lower = Math.floor(Math.abs(value));
+      double prob = Math.abs(value) - lower;
+      if (rand.nextDouble() < prob) {
+	return -((int)lower + 1);
+      } else {
+	return -(int)lower;
+      }
+    }
+  }
+
+  /**
+   * Rounds a double to the given number of decimal places.
+   *
+   * @param value the double value
+   * @param afterDecimalPoint the number of digits after the decimal point
+   * @return the double rounded to the given precision
+   */
+  public static /*@pure@*/ double roundDouble(double value,int afterDecimalPoint) {
+
+    double mask = Math.pow(10.0, (double)afterDecimalPoint);
+
+    return (double)(Math.round(value * mask)) / mask;
+  }
+
+  /**
+   * Sorts a given array of integers in ascending order and returns an 
+   * array of integers with the positions of the elements of the original 
+   * array in the sorted array. The sort is stable. (Equal elements remain
+   * in their original order.)
+   *
+   * @param array this array is not changed by the method!
+   * @return an array of integers with the positions in the sorted
+   * array.
+   */
+  public static /*@pure@*/ int[] sort(int[] array) {
+
+    int[] index = new int[array.length];
+    int[] newIndex = new int[array.length];
+    int[] helpIndex;
+    int numEqual;
+    
+    for (int i = 0; i < index.length; i++) {
+      index[i] = i;
+    }
+    quickSort(array, index, 0, array.length - 1);
+
+    // Make sort stable
+    int i = 0;
+    while (i < index.length) {
+      numEqual = 1;
+      for (int j = i + 1; ((j < index.length)
+			   && (array[index[i]] == array[index[j]]));
+	   j++) {
+	numEqual++;
+      }
+      if (numEqual > 1) {
+	helpIndex = new int[numEqual];
+	for (int j = 0; j < numEqual; j++) {
+	  helpIndex[j] = i + j;
+	}
+	quickSort(index, helpIndex, 0, numEqual - 1);
+	for (int j = 0; j < numEqual; j++) {
+	  newIndex[i + j] = index[helpIndex[j]];
+	}
+	i += numEqual;
+      } else {
+	newIndex[i] = index[i];
+	i++;
+      }
+    }
+    return newIndex;
+  }
+
+  /**
+   * Sorts a given array of doubles in ascending order and returns an
+   * array of integers with the positions of the elements of the
+   * original array in the sorted array. NOTE THESE CHANGES: the sort
+   * is no longer stable and it doesn't use safe floating-point
+   * comparisons anymore. Occurrences of Double.NaN are treated as 
+   * Double.MAX_VALUE
+   *
+   * @param array this array is not changed by the method!
+   * @return an array of integers with the positions in the sorted
+   * array.  
+   */
+  public static /*@pure@*/ int[] sort(/*@non_null@*/ double[] array) {
+
+    int[] index = new int[array.length];
+    array = (double[])array.clone();
+    for (int i = 0; i < index.length; i++) {
+      index[i] = i;
+      if (Double.isNaN(array[i])) {
+        array[i] = Double.MAX_VALUE;
+      }
+    }
+    quickSort(array, index, 0, array.length - 1);
+    return index;
+  }
+
+  /**
+   * Sorts a given array of doubles in ascending order and returns an 
+   * array of integers with the positions of the elements of the original 
+   * array in the sorted array. The sort is stable (Equal elements remain
+   * in their original order.) Occurrences of Double.NaN are treated as 
+   * Double.MAX_VALUE
+   *
+   * @param array this array is not changed by the method!
+   * @return an array of integers with the positions in the sorted
+   * array.
+   */
+  public static /*@pure@*/ int[] stableSort(double[] array){
+
+    int[] index = new int[array.length];
+    int[] newIndex = new int[array.length];
+    int[] helpIndex;
+    int numEqual;
+    
+    array = (double[])array.clone();
+    for (int i = 0; i < index.length; i++) {
+      index[i] = i;
+      if (Double.isNaN(array[i])) {
+        array[i] = Double.MAX_VALUE;
+      }
+    }
+    quickSort(array,index,0,array.length-1);
+
+    // Make sort stable
+
+    int i = 0;
+    while (i < index.length) {
+      numEqual = 1;
+      for (int j = i+1; ((j < index.length) && Utils.eq(array[index[i]],
+							array[index[j]])); j++)
+	numEqual++;
+      if (numEqual > 1) {
+	helpIndex = new int[numEqual];
+	for (int j = 0; j < numEqual; j++)
+	  helpIndex[j] = i+j;
+	quickSort(index, helpIndex, 0, numEqual-1);
+	for (int j = 0; j < numEqual; j++) 
+	  newIndex[i+j] = index[helpIndex[j]];
+	i += numEqual;
+      } else {
+	newIndex[i] = index[i];
+	i++;
+      }
+    }
+
+    return newIndex;
+  }
+
+  /**
+   * Computes the variance for an array of doubles.
+   *
+   * @param vector the array
+   * @return the variance
+   */
+  public static /*@pure@*/ double variance(double[] vector) {
+  
+    double sum = 0, sumSquared = 0;
+
+    if (vector.length <= 1) {
+      return 0;
+    }
+    for (int i = 0; i < vector.length; i++) {
+      sum += vector[i];
+      sumSquared += (vector[i] * vector[i]);
+    }
+    double result = (sumSquared - (sum * sum / (double) vector.length)) / 
+      (double) (vector.length - 1);
+
+    // We don't like negative variance
+    if (result < 0) {
+      return 0;
+    } else {
+      return result;
+    }
+  }
+
+  /**
+   * Computes the sum of the elements of an array of doubles.
+   *
+   * @param doubles the array of double
+   * @return the sum of the elements
+   */
+  public static /*@pure@*/ double sum(double[] doubles) {
+
+    double sum = 0;
+
+    for (int i = 0; i < doubles.length; i++) {
+      sum += doubles[i];
+    }
+    return sum;
+  }
+
+  /**
+   * Computes the sum of the elements of an array of integers.
+   *
+   * @param ints the array of integers
+   * @return the sum of the elements
+   */
+  public static /*@pure@*/ int sum(int[] ints) {
+
+    int sum = 0;
+
+    for (int i = 0; i < ints.length; i++) {
+      sum += ints[i];
+    }
+    return sum;
+  }
+
+  /**
+   * Returns c*log2(c) for a given integer value c.
+   *
+   * @param c an integer value
+   * @return c*log2(c) (but is careful to return 0 if c is 0)
+   */
+  public static /*@pure@*/ double xlogx(int c) {
+    
+    if (c == 0) {
+      return 0.0;
+    }
+    return c * Utils.log2((double) c);
+  }
+
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param array the array of doubles to be sorted
+   * @param index the index into the array of doubles
+   * @param l the first index of the subset 
+   * @param r the last index of the subset 
+   *
+   * @return the index of the middle element
+   */
+  private static int partition(double[] array, int[] index, int l, int r) {
+    
+    double pivot = array[index[(l + r) / 2]];
+    int help;
+
+    while (l < r) {
+      while ((array[index[l]] < pivot) && (l < r)) {
+        l++;
+      }
+      while ((array[index[r]] > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[l];
+        index[l] = index[r];
+        index[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (array[index[r]] > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param array the array of integers to be sorted
+   * @param index the index into the array of integers
+   * @param l the first index of the subset 
+   * @param r the last index of the subset 
+   *
+   * @return the index of the middle element
+   */
+  private static int partition(int[] array, int[] index, int l, int r) {
+    
+    double pivot = array[index[(l + r) / 2]];
+    int help;
+
+    while (l < r) {
+      while ((array[index[l]] < pivot) && (l < r)) {
+        l++;
+      }
+      while ((array[index[r]] > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[l];
+        index[l] = index[r];
+        index[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (array[index[r]] > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+  
+  /**
+   * Implements quicksort according to Manber's "Introduction to
+   * Algorithms".
+   *
+   * @param array the array of doubles to be sorted
+   * @param index the index into the array of doubles
+   * @param left the first index of the subset to be sorted
+   * @param right the last index of the subset to be sorted
+   */
+  //@ requires 0 <= first && first <= right && right < array.length;
+  //@ requires (\forall int i; 0 <= i && i < index.length; 0 <= index[i] && index[i] < array.length);
+  //@ requires array != index;
+  //  assignable index;
+  private static void quickSort(/*@non_null@*/ double[] array, /*@non_null@*/ int[] index, 
+                                int left, int right) {
+
+    if (left < right) {
+      int middle = partition(array, index, left, right);
+      quickSort(array, index, left, middle);
+      quickSort(array, index, middle + 1, right);
+    }
+  }
+  
+  /**
+   * Implements quicksort according to Manber's "Introduction to
+   * Algorithms".
+   *
+   * @param array the array of integers to be sorted
+   * @param index the index into the array of integers
+   * @param left the first index of the subset to be sorted
+   * @param right the last index of the subset to be sorted
+   */
+  //@ requires 0 <= first && first <= right && right < array.length;
+  //@ requires (\forall int i; 0 <= i && i < index.length; 0 <= index[i] && index[i] < array.length);
+  //@ requires array != index;
+  //  assignable index;
+  private static void quickSort(/*@non_null@*/ int[] array, /*@non_null@*/  int[] index, 
+                                int left, int right) {
+
+    if (left < right) {
+      int middle = partition(array, index, left, right);
+      quickSort(array, index, left, middle);
+      quickSort(array, index, middle + 1, right);
+    }
+  }
+  
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param array the array of double
+   * @param index the index into the array of doubles
+   * @param left the first index of the subset 
+   * @param right the last index of the subset 
+   * @param k the value of k
+   *
+   * @return the index of the kth-smallest element
+   */
+  //@ requires 0 <= first && first <= right && right < array.length;
+  private static int select(/*@non_null@*/ double[] array, /*@non_null@*/ int[] index, 
+                            int left, int right, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(array, index, left, right);
+      if ((middle - left + 1) >= k) {
+        return select(array, index, left, middle, k);
+      } else {
+        return select(array, index, middle + 1, right, k - (middle - left + 1));
+      }
+    }
+  }
+
+  /**
+   * Converts a File's absolute path to a path relative to the user
+   * (ie start) directory. Includes an additional workaround for Cygwin, which
+   * doesn't like upper case drive letters.
+   * @param absolute the File to convert to relative path
+   * @return a File with a path that is relative to the user's directory
+   * @exception Exception if the path cannot be constructed
+   */
+  public static File convertToRelativePath(File absolute) throws Exception {
+    File        result;
+    String      fileStr;
+    
+    result = null;
+    
+    // if we're running windows, it could be Cygwin
+    if (File.separator.equals("\\")) {
+      // Cygwin doesn't like upper case drives -> try lower case drive
+      try {
+        fileStr = absolute.getPath();
+        fileStr =   fileStr.substring(0, 1).toLowerCase() 
+                  + fileStr.substring(1);
+        result = createRelativePath(new File(fileStr));
+      }
+      catch (Exception e) {
+        // no luck with Cygwin workaround, convert it like it is
+        result = createRelativePath(absolute);
+      }
+    }
+    else {
+      result = createRelativePath(absolute);
+    }
+
+    return result;
+  }
+
+  /**
+   * Converts a File's absolute path to a path relative to the user
+   * (ie start) directory.
+   * 
+   * @param absolute the File to convert to relative path
+   * @return a File with a path that is relative to the user's directory
+   * @exception Exception if the path cannot be constructed
+   */
+  protected static File createRelativePath(File absolute) throws Exception {
+    File userDir = new File(System.getProperty("user.dir"));
+    String userPath = userDir.getAbsolutePath() + File.separator;
+    String targetPath = (new File(absolute.getParent())).getPath() 
+      + File.separator;
+    String fileName = absolute.getName();
+    StringBuffer relativePath = new StringBuffer();
+    //    relativePath.append("."+File.separator);
+    //    System.err.println("User dir "+userPath);
+    //    System.err.println("Target path "+targetPath);
+    
+    // file is in user dir (or subdir)
+    int subdir = targetPath.indexOf(userPath);
+    if (subdir == 0) {
+      if (userPath.length() == targetPath.length()) {
+	relativePath.append(fileName);
+      } else {
+	int ll = userPath.length();
+	relativePath.append(targetPath.substring(ll));
+	relativePath.append(fileName);
+      }
+    } else {
+      int sepCount = 0;
+      String temp = new String(userPath);
+      while (temp.indexOf(File.separator) != -1) {
+	int ind = temp.indexOf(File.separator);
+	sepCount++;
+	temp = temp.substring(ind+1, temp.length());
+      }
+      
+      String targetTemp = new String(targetPath);
+      String userTemp = new String(userPath);
+      int tcount = 0;
+      while (targetTemp.indexOf(File.separator) != -1) {
+	int ind = targetTemp.indexOf(File.separator);
+	int ind2 = userTemp.indexOf(File.separator);
+	String tpart = targetTemp.substring(0,ind+1);
+	String upart = userTemp.substring(0,ind2+1);
+	if (tpart.compareTo(upart) != 0) {
+	  if (tcount == 0) {
+	    tcount = -1;
+	  }
+	  break;
+	}
+	tcount++;
+	targetTemp = targetTemp.substring(ind+1, targetTemp.length());
+	userTemp = userTemp.substring(ind2+1, userTemp.length());
+      }
+      if (tcount == -1) {
+	// then target file is probably on another drive (under windows)
+	throw new Exception("Can't construct a path to file relative to user "
+			    +"dir.");
+      }
+      if (targetTemp.indexOf(File.separator) == -1) {
+	targetTemp = "";
+      }
+      for (int i = 0; i < sepCount - tcount; i++) {
+	relativePath.append(".."+File.separator);
+      }
+      relativePath.append(targetTemp + fileName);
+    }
+    //    System.err.println("new path : "+relativePath.toString());
+    return new File(relativePath.toString());
+  }
+  
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param array the array of integers
+   * @param index the index into the array of integers
+   * @param left the first index of the subset 
+   * @param right the last index of the subset 
+   * @param k the value of k
+   *
+   * @return the index of the kth-smallest element
+   */
+  //@ requires 0 <= first && first <= right && right < array.length;
+  private static int select(/*@non_null@*/ int[] array, /*@non_null@*/ int[] index, 
+                            int left, int right, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(array, index, left, right);
+      if ((middle - left + 1) >= k) {
+        return select(array, index, left, middle, k);
+      } else {
+        return select(array, index, middle + 1, right, k - (middle - left + 1));
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param ops some dummy options
+   */
+  public static void main(String[] ops) {
+
+    double[] doublesWithNaN = {4.5, 6.7, Double.NaN, 3.4, 4.8, 1.2, 3.4};
+    double[] doubles = {4.5, 6.7, 6.7, 3.4, 4.8, 1.2, 3.4, 6.7, 6.7, 3.4};
+    int[] ints = {12, 6, 2, 18, 16, 6, 7, 5, 18, 18, 17};
+
+    try {
+
+      // Option handling
+      System.out.println("First option split up:");
+      if (ops.length > 0) {
+	String[] firstOptionSplitUp = Utils.splitOptions(ops[0]);
+	for (int i = 0; i < firstOptionSplitUp.length; i ++) {
+	  System.out.println(firstOptionSplitUp[i]);
+	}
+      }					       
+      System.out.println("Partitioned options: ");
+      String[] partitionedOptions = Utils.partitionOptions(ops);
+      for (int i  = 0; i < partitionedOptions.length; i++) {
+	System.out.println(partitionedOptions[i]);
+      }
+      System.out.println("Get position of flag -f: " + Utils.getOptionPos('f', ops));
+      System.out.println("Get flag -f: " + Utils.getFlag('f', ops));
+      System.out.println("Get position of option -o: " + Utils.getOptionPos('o', ops));
+      System.out.println("Get option -o: " + Utils.getOption('o', ops));
+      System.out.println("Checking for remaining options... ");
+      Utils.checkForRemainingOptions(ops);
+      
+      // Statistics
+      System.out.println("Original array with NaN (doubles): ");
+      for (int i = 0; i < doublesWithNaN.length; i++) {
+	System.out.print(doublesWithNaN[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Original array (doubles): ");
+      for (int i = 0; i < doubles.length; i++) {
+	System.out.print(doubles[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Original array (ints): ");
+      for (int i = 0; i < ints.length; i++) {
+	System.out.print(ints[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Correlation: " + Utils.correlation(doubles, doubles, 
+							     doubles.length));
+      System.out.println("Mean: " + Utils.mean(doubles));
+      System.out.println("Variance: " + Utils.variance(doubles));
+      System.out.println("Sum (doubles): " + Utils.sum(doubles));
+      System.out.println("Sum (ints): " + Utils.sum(ints));
+      System.out.println("Max index (doubles): " + Utils.maxIndex(doubles));
+      System.out.println("Max index (ints): " + Utils.maxIndex(ints));
+      System.out.println("Min index (doubles): " + Utils.minIndex(doubles));
+      System.out.println("Min index (ints): " + Utils.minIndex(ints));
+      System.out.println("Median (doubles): " + 
+                         Utils.kthSmallestValue(doubles, doubles.length / 2));
+      System.out.println("Median (ints): " + 
+                         Utils.kthSmallestValue(ints, ints.length / 2));
+
+      // Sorting and normalizing
+      System.out.println("Sorted array with NaN (doubles): ");
+      int[] sorted = Utils.sort(doublesWithNaN);
+      for (int i = 0; i < doublesWithNaN.length; i++) {
+	System.out.print(doublesWithNaN[sorted[i]] + " ");
+      }
+      System.out.println();
+      System.out.println("Sorted array (doubles): ");
+      sorted = Utils.sort(doubles);
+      for (int i = 0; i < doubles.length; i++) {
+	System.out.print(doubles[sorted[i]] + " ");
+      }
+      System.out.println();
+      System.out.println("Sorted array (ints): ");
+      sorted = Utils.sort(ints);
+      for (int i = 0; i < ints.length; i++) {
+	System.out.print(ints[sorted[i]] + " ");
+      }
+      System.out.println();
+      System.out.println("Indices from stable sort (doubles): ");
+      sorted = Utils.stableSort(doubles);
+      for (int i = 0; i < doubles.length; i++) {
+	System.out.print(sorted[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Indices from sort (ints): ");
+      sorted = Utils.sort(ints);
+      for (int i = 0; i < ints.length; i++) {
+	System.out.print(sorted[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Normalized array (doubles): ");
+      Utils.normalize(doubles);
+      for (int i = 0; i < doubles.length; i++) {
+	System.out.print(doubles[i] + " ");
+      }
+      System.out.println();
+      System.out.println("Normalized again (doubles): ");
+      Utils.normalize(doubles, Utils.sum(doubles));
+      for (int i = 0; i < doubles.length; i++) {
+	System.out.print(doubles[i] + " ");
+      }
+      System.out.println();
+      
+      // Pretty-printing
+      System.out.println("-4.58: " + Utils.doubleToString(-4.57826535, 2));
+      System.out.println("-6.78: " + Utils.doubleToString(-6.78214234, 6,2));
+      
+      // Comparisons
+      System.out.println("5.70001 == 5.7 ? " + Utils.eq(5.70001, 5.7));
+      System.out.println("5.70001 > 5.7 ? " + Utils.gr(5.70001, 5.7));
+      System.out.println("5.70001 >= 5.7 ? " + Utils.grOrEq(5.70001, 5.7));
+      System.out.println("5.7 < 5.70001 ? " + Utils.sm(5.7, 5.70001));
+      System.out.println("5.7 <= 5.70001 ? " + Utils.smOrEq(5.7, 5.70001));
+      
+      // Math
+      System.out.println("Info (ints): " + Utils.info(ints));
+      System.out.println("log2(4.6): " + Utils.log2(4.6));
+      System.out.println("5 * log(5): " + Utils.xlogx(5));
+      System.out.println("5.5 rounded: " + Utils.round(5.5));
+      System.out.println("5.55555 rounded to 2 decimal places: " + 
+			 Utils.roundDouble(5.55555, 2));
+      
+      // Arrays
+      System.out.println("Array-Dimensions of 'new int[][]': " + Utils.getArrayDimensions(new int[][]{}));
+      System.out.println("Array-Dimensions of 'new int[][]{{1,2,3},{4,5,6}}': " + Utils.getArrayDimensions(new int[][]{{1,2,3},{4,5,6}}));
+      String[][][] s = new String[3][4][];
+      System.out.println("Array-Dimensions of 'new String[3][4][]': " + Utils.getArrayDimensions(s));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+  
Index: branches/MetisMQI/src/main/java/weka/core/Version.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/Version.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/Version.java	(revision 29)
@@ -0,0 +1,290 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Version.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+
+/**
+ * This class contains the version number of the current WEKA release and some
+ * methods for comparing another version string. The normal layout of a
+ * version string is "MAJOR.MINOR.REVISION", but it can also handle partial
+ * version strings, e.g. "3.4". <br/>
+ * Should be used e.g. in exports to XML for keeping track, with which version 
+ * of WEKA the file was produced.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class Version
+  implements Comparable, RevisionHandler {
+  
+  /** the version file */
+  public final static String VERSION_FILE = "weka/core/version.txt";
+  
+  /** the major version */
+  public static int MAJOR = 3; 
+  
+  /** the minor version */
+  public static int MINOR = 4; 
+  
+  /** the revision */
+  public static int REVISION = 3;
+
+  static {
+    try {
+      InputStream inR = (new Version()).getClass().getClassLoader().getResourceAsStream(VERSION_FILE);
+      //      InputStream inR = ClassLoader.getSystemResourceAsStream(VERSION_FILE);
+      LineNumberReader lnr = new LineNumberReader(new InputStreamReader(inR));
+      
+      String line = lnr.readLine();
+      int[] maj = new int[1];
+      int[] min = new int[1];
+      int[] rev = new int[1];
+      parseVersion(line, maj, min, rev);
+      MAJOR    = maj[0];
+      MINOR    = min[0];
+      REVISION = rev[0];
+      lnr.close();
+    }
+    catch (Exception e) {
+      System.err.println(
+	  Version.class.getName() + ": Unable to load version information!");
+    }
+  }
+
+  /** the complete version */
+  public static String VERSION = MAJOR + "." + MINOR + "." + REVISION;
+
+  /**
+   * parses the version and stores the result in the arrays
+   * 
+   * @param version	the version string to parse (contains "-" instead of "."!)
+   * @param maj		the major version
+   * @param min		the minor version
+   * @param rev		the revision version
+   */
+  private static void parseVersion(String version, int[] maj, int[] min, int[] rev) {
+    int major = 0;
+    int minor = 0;
+    int revision = 0;
+
+    try {
+      String tmpStr = version;
+      tmpStr = tmpStr.replace('-', '.');
+      if (tmpStr.indexOf(".") > -1) {
+        major  = Integer.parseInt(tmpStr.substring(0, tmpStr.indexOf(".")));
+        tmpStr = tmpStr.substring(tmpStr.indexOf(".") + 1);
+        if (tmpStr.indexOf(".") > -1) {
+          minor  = Integer.parseInt(tmpStr.substring(0, tmpStr.indexOf(".")));
+          tmpStr = tmpStr.substring(tmpStr.indexOf(".") + 1);
+          if (!tmpStr.equals(""))
+            revision = Integer.parseInt(tmpStr);
+          else
+            revision = 0;
+        }
+        else {
+          if (!tmpStr.equals(""))
+            minor = Integer.parseInt(tmpStr);
+          else
+            minor = 0;
+        }
+      }
+      else {
+        if (!tmpStr.equals(""))
+          major = Integer.parseInt(tmpStr);
+        else
+          major = 0;
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      major    = -1;
+      minor    = -1;
+      revision = -1;
+    } finally {
+      maj[0] = major;
+      min[0] = minor;
+      rev[0] = revision;
+    }
+  }
+
+  /**
+   * checks the version of this class against the given version-string
+   * 
+   * @param o     the version-string to compare with
+   * @return      -1 if this version is less, 0 if equal and +1 if greater
+   *              than the provided version 
+   */
+  public int compareTo(Object o) {
+    int       result;
+    int       major;
+    int       minor;
+    int       revision;
+    int       [] maj = new int [1];
+    int       [] min = new int [1];
+    int       [] rev = new int [1];
+   
+    
+    // do we have a string?
+    if (o instanceof String) {
+      parseVersion((String)o, maj, min, rev);
+      major = maj[0];
+      minor = min[0];
+      revision = rev[0];
+    }
+    else {
+      System.out.println(this.getClass().getName() + ": no version-string for comparTo povided!");
+      major    = -1;
+      minor    = -1;
+      revision = -1;
+    }
+
+    if (MAJOR < major) {
+      result = -1;
+    }
+    else if (MAJOR == major) {
+      if (MINOR < minor) {
+        result = -1;
+      }
+      else if (MINOR == minor) {
+        if (REVISION < revision) {
+          result = -1;
+        }
+        else if (REVISION == revision) {
+          result = 0;
+        }
+        else {
+          result = 1;
+        }
+      }
+      else {
+        result = 1;
+      }
+    }
+    else {
+      result = 1;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * whether the given version string is equal to this version
+   * 
+   * @param o       the version-string to compare to
+   * @return        TRUE if the version-string is equals to its own
+   */
+  public boolean equals(Object o) {
+    return (compareTo(o) == 0);
+  }
+  
+  /**
+   * checks whether this version is older than the one from the given 
+   * version string
+   * 
+   * @param o       the version-string to compare with
+   * @return        TRUE if this version is older than the given one
+   */
+  public boolean isOlder(Object o) {
+    return (compareTo(o) == -1);
+  }
+  
+  /**
+   * checks whether this version is newer than the one from the given 
+   * version string
+   * 
+   * @param o       the version-string to compare with
+   * @return        TRUE if this version is newer than the given one
+   */
+  public boolean isNewer(Object o) {
+    return (compareTo(o) == 1);
+  }
+  
+  /**
+   * returns the current version as string
+   * 
+   * @return        the current version
+   */
+  public String toString() {
+    return VERSION;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * only for testing
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String[] args) {
+    Version       v;
+    String        tmpStr;
+
+    // print version
+    System.out.println(VERSION + "\n");
+    
+    // test on different versions
+    v = new Version();
+    System.out.println("-1? " + v.compareTo("5.0.1"));
+    System.out.println(" 0? " + v.compareTo(VERSION));
+    System.out.println("+1? " + v.compareTo("3.4.0"));
+    
+    tmpStr = "5.0.1";
+    System.out.println("\ncomparing with " + tmpStr);
+    System.out.println("isOlder? " + v.isOlder(tmpStr));
+    System.out.println("equals ? " + v.equals(tmpStr));
+    System.out.println("isNewer? " + v.isNewer(tmpStr));
+    
+    tmpStr = VERSION;
+    System.out.println("\ncomparing with " + tmpStr);
+    System.out.println("isOlder? " + v.isOlder(tmpStr));
+    System.out.println("equals ? " + v.equals(tmpStr));
+    System.out.println("isNewer? " + v.isNewer(tmpStr));
+    
+    tmpStr = "3.4.0";
+    System.out.println("\ncomparing with " + tmpStr);
+    System.out.println("isOlder? " + v.isOlder(tmpStr));
+    System.out.println("equals ? " + v.equals(tmpStr));
+    System.out.println("isNewer? " + v.isNewer(tmpStr));
+    
+    tmpStr = "3.4";
+    System.out.println("\ncomparing with " + tmpStr);
+    System.out.println("isOlder? " + v.isOlder(tmpStr));
+    System.out.println("equals ? " + v.equals(tmpStr));
+    System.out.println("isNewer? " + v.isNewer(tmpStr));
+    
+    tmpStr = "5";
+    System.out.println("\ncomparing with " + tmpStr);
+    System.out.println("isOlder? " + v.isOlder(tmpStr));
+    System.out.println("equals ? " + v.equals(tmpStr));
+    System.out.println("isNewer? " + v.isNewer(tmpStr));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/WeightedInstancesHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/WeightedInstancesHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/WeightedInstancesHandler.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WeightedInstancesHandler.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/** 
+ * Interface to something that makes use of the information provided
+ * by instance weights.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $ 
+*/
+public interface WeightedInstancesHandler {
+
+  // Nothing in here, because the class is just used as an indicator
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/WekaEnumeration.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/WekaEnumeration.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/WekaEnumeration.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WekaEnumeration.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core;
+
+import java.util.Enumeration;
+import java.util.List;
+import weka.core.RevisionHandler;
+
+/**
+ * Class for enumerating an array list's elements.
+ */
+public class WekaEnumeration
+  implements Enumeration, RevisionHandler {
+  
+  /** The counter. */
+  private int m_Counter;
+  // These JML commands say how m_Counter implements Enumeration
+  //@ in moreElements;
+  //@ private represents moreElements = m_Counter < m_Vector.size();
+  //@ private invariant 0 <= m_Counter && m_Counter <= m_Vector.size();
+  
+  /** The vector. */
+  private /*@non_null@*/ List m_Vector;
+  
+  /** Special element. Skipped during enumeration. */
+  private int m_SpecialElement;
+  //@ private invariant -1 <= m_SpecialElement;
+  //@ private invariant m_SpecialElement < m_Vector.size();
+  //@ private invariant m_SpecialElement>=0 ==> m_Counter!=m_SpecialElement;
+  
+  /**
+   * Constructs an enumeration.
+   *
+   * @param vector the vector which is to be enumerated
+   */
+  public WekaEnumeration(/*@non_null@*/List vector) {
+    
+    m_Counter = 0;
+    m_Vector = vector;
+    m_SpecialElement = -1;
+  }
+  
+  /**
+   * Constructs an enumeration with a special element.
+   * The special element is skipped during the enumeration.
+   *
+   * @param vector the vector which is to be enumerated
+   * @param special the index of the special element
+   */
+  //@ requires 0 <= special && special < vector.size();
+  public WekaEnumeration(/*@non_null@*/List vector, int special){
+    
+    m_Vector = vector;
+    m_SpecialElement = special;
+    if (special == 0) {
+      m_Counter = 1;
+    } else {
+      m_Counter = 0;
+    }
+  }
+  
+  
+  /**
+   * Tests if there are any more elements to enumerate.
+   *
+   * @return true if there are some elements left
+   */
+  public final /*@pure@*/ boolean hasMoreElements() {
+    
+    if (m_Counter < m_Vector.size()) {
+      return true;
+    }
+    return false;
+  }
+  
+  /**
+   * Returns the next element.
+   *
+   * @return the next element to be enumerated
+   */
+  //@ also requires hasMoreElements();
+  public final Object nextElement() {
+    
+    Object result = m_Vector.get(m_Counter);
+    
+    m_Counter++;
+    if (m_Counter == m_SpecialElement) {
+      m_Counter++;
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5955 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/WekaException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/WekaException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/WekaException.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WekaException.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core;
+
+/**
+ * Class for Weka-specific exceptions.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class WekaException
+  extends Exception {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6428269989006208585L;
+
+  /**
+   * Creates a new WekaException with no message.
+   *
+   */
+  public WekaException() {
+
+    super();
+  }
+
+  /**
+   * Creates a new WekaException.
+   *
+   * @param message the reason for raising an exception.
+   */
+  public WekaException(String message) {
+
+    super(message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileLoader.java	(revision 29)
@@ -0,0 +1,334 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AbstractFileLoader.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.zip.GZIPInputStream;
+
+
+/**
+ * Abstract superclass for all file loaders.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4983 $
+ */
+public abstract class AbstractFileLoader
+  extends AbstractLoader
+  implements FileSourcedConverter, EnvironmentHandler {
+
+  /** the file */
+  protected String m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+
+  /** Holds the determined structure (header) of the data set. */
+  protected Instances m_structure = null;
+
+  /** Holds the source of the data set. */
+  protected File m_sourceFile = null;
+
+  /** the extension for compressed files */
+  public static String FILE_EXTENSION_COMPRESSED = ".gz";
+
+  /** use relative file paths */
+  protected boolean m_useRelativePath = false;
+  
+  /** Environment variables */
+  protected transient Environment m_env;
+  
+  /**
+   * get the File specified as the source
+   *
+   * @return the source file
+   */
+  public File retrieveFile() {
+    return new File(m_File);
+  }
+
+  /**
+   * sets the source File
+   *
+   * @param file the source file
+   * @exception IOException if an error occurs
+   */
+  public void setFile(File file) throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+
+    //m_File = file.getAbsolutePath();
+    setSource(file);
+  }
+  
+  /**
+   * Set the environment variables to use.
+   * 
+   * @param env the environment variables to use
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+    try {
+      // causes setSource(File) to be called and 
+      // forces the input stream to be reset with a new file
+      // that has environment variables resolved with those
+      // in the new Environment object
+      reset();
+    } catch (IOException ex) {
+      // we won't complain about it here...
+    }
+  }
+  
+  /**
+   * Resets the loader ready to read a new data set
+   * 
+   * @throws IOException if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file 		the source file.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(File file) throws IOException {
+    File original = file;
+    m_structure = null;
+    
+    setRetrieval(NONE);
+
+    if (file == null)
+      throw new IOException("Source file object is null!");
+
+  //  try {
+      String fName = file.getPath();
+      try {
+        if (m_env == null) {
+          m_env = Environment.getSystemWide();
+        }
+        fName = m_env.substitute(fName);
+      } catch (Exception e) {
+        // ignore any missing environment variables at this time
+        // as it is possible that these may be set by the time
+        // the actual file is processed
+        
+        //throw new IOException(e.getMessage());
+      }
+      file = new File(fName);
+      // set the source only if the file exists
+      if (file.exists()) {
+        if (file.getName().endsWith(getFileExtension() + FILE_EXTENSION_COMPRESSED)) {
+          setSource(new GZIPInputStream(new FileInputStream(file)));
+        } else {
+          setSource(new FileInputStream(file));
+        }
+      }
+   // }
+  /*  catch (FileNotFoundException ex) {
+      throw new IOException("File not found");
+    } */
+
+    if (m_useRelativePath) {
+      try {
+        m_sourceFile = Utils.convertToRelativePath(original);
+        m_File = m_sourceFile.getPath();
+      } catch (Exception ex) {
+        //        System.err.println("[AbstractFileLoader] can't convert path to relative path.");
+        m_sourceFile = original;
+        m_File       = m_sourceFile.getPath();
+      }
+    } else {
+      m_sourceFile = original;
+      m_File       = m_sourceFile.getPath();
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file the source file.
+   * @exception IOException if an error occurs
+   *
+  public void setSource(File file) throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+
+    if (file == null) {
+      throw new IOException("Source file object is null!");
+    }
+
+    try {
+      setSource(new FileInputStream(file));
+    }
+    catch (FileNotFoundException ex) {
+      throw new IOException("File not found");
+    }
+
+    m_sourceFile = file;
+    m_File       = file.getAbsolutePath();
+    } */
+
+  /**
+   * Tip text suitable for displaying int the GUI
+   *
+   * @return a description of this property as a String
+   */
+  public String useRelativePathTipText() {
+    return "Use relative rather than absolute paths";
+  }
+
+  /**
+   * Set whether to use relative rather than absolute paths
+   *
+   * @param rp true if relative paths are to be used
+   */
+  public void setUseRelativePath(boolean rp) {
+    m_useRelativePath = rp;
+  }
+
+  /**
+   * Gets whether relative paths are to be used
+   *
+   * @return true if relative paths are to be used
+   */
+  public boolean getUseRelativePath() {
+    return m_useRelativePath;
+  }
+
+  /**
+   * generates a string suitable for output on the command line displaying
+   * all available options (currently only a simple usage).
+   * 
+   * @param loader	the loader to create the option string for
+   * @return		the option string
+   */
+  protected static String makeOptionStr(AbstractFileLoader loader) {
+    StringBuffer 	result;
+    Option 		option;
+    
+    result = new StringBuffer("\nUsage:\n");
+    result.append("\t" + loader.getClass().getName().replaceAll(".*\\.", ""));
+    if (loader instanceof OptionHandler)
+      result.append(" [options]");
+    result.append(" <");
+    String[] ext = loader.getFileExtensions();
+    for (int i = 0; i < ext.length; i++) {
+	if (i > 0)
+	  result.append(" | ");
+	result.append("file" + ext[i]);
+    }
+    result.append(">\n");
+
+    if (loader instanceof OptionHandler) {
+      result.append("\nOptions:\n\n");
+      Enumeration enm = ((OptionHandler) loader).listOptions();
+      while (enm.hasMoreElements()) {
+	option = (Option) enm.nextElement();
+	result.append(option.synopsis() + "\n");
+	result.append(option.description() + "\n");
+      }
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * runs the given loader with the provided options
+   * 
+   * @param loader	the loader to run
+   * @param options	the commandline options, first argument must be the
+   * 			file to load
+   */
+  public static void runFileLoader(AbstractFileLoader loader, String[] options) {
+    // help request?
+    try {
+      String[] tmpOptions = (String[]) options.clone();
+      if (Utils.getFlag('h', tmpOptions)) {
+	System.err.println("\nHelp requested\n" + makeOptionStr(loader));
+	return;
+      }
+    }
+    catch (Exception e) {
+      // ignore it
+    }
+    
+    if (options.length > 0) {
+      if (loader instanceof OptionHandler) {
+	// set options
+	try {
+	  ((OptionHandler) loader).setOptions(options);
+	  // find file
+	  for (int i = 0; i < options.length; i++) {
+	    if (options[i].length() > 0) {
+	      options = new String[]{options[i]};
+	      break;
+	    }
+	  }
+	}
+	catch (Exception ex) {
+	  System.err.println(makeOptionStr(loader));
+	  System.exit(1);
+	}
+      }
+      
+      try {
+	loader.setFile(new File(options[0]));
+	// incremental
+	if (loader instanceof IncrementalConverter) {
+	  Instances structure = loader.getStructure();
+	  System.out.println(structure);
+	  Instance temp;
+	  do {
+	    temp = loader.getNextInstance(structure);
+	    if (temp != null)
+	      System.out.println(temp);
+	  }
+	  while (temp != null);
+	}
+	// batch
+	else {
+	  System.out.println(loader.getDataSet());
+	}
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+    else {
+      System.err.println(makeOptionStr(loader));
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/AbstractFileSaver.java	(revision 29)
@@ -0,0 +1,531 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractFileSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract class for Savers that save to a file
+ *
+ * Valid options are:
+ *
+ * -i input arff file <br>
+ * The input filw in arff format. <p>
+ *
+ * -o the output file <br>
+ * The output file. The prefix of the output file is sufficient. If no output file is given, Saver tries to use standard out. <p>
+ *
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class AbstractFileSaver
+  extends AbstractSaver
+  implements OptionHandler, FileSourcedConverter, EnvironmentHandler {
+  
+  
+  /** The destination file. */
+  private File m_outputFile;
+  
+  /** The writer. */
+  private transient BufferedWriter m_writer;
+  
+  /** The file extension of the destination file. */
+  private String FILE_EXTENSION;
+  
+  
+  /** The prefix for the filename (chosen in the GUI). */
+  private String m_prefix;
+  
+  /** The directory of the file (chosen in the GUI).*/
+  private String m_dir;
+  
+  /** Counter. In incremental mode after reading 100 instances they will be written to a file.*/
+  protected int m_incrementalCounter;
+
+  /** use relative file paths */
+  protected boolean m_useRelativePath = false;
+  
+  /** Environment variables */
+  protected transient Environment m_env;
+  
+  
+  /**
+   * resets the options
+   *
+   */
+  public void resetOptions(){
+      
+     super.resetOptions();
+     m_outputFile = null;
+     m_writer = null;
+     m_prefix = "";
+     m_dir = "";
+     m_incrementalCounter = 0;
+  }
+  
+ 
+ 
+  /**
+   * Gets the writer
+   *
+   * @return the BufferedWriter
+   */
+  public BufferedWriter getWriter(){
+   
+      return m_writer;
+  }
+  
+  /** Sets the writer to null. */  
+  public void resetWriter(){
+   
+      m_writer = null;
+  }
+  
+  /**
+   * Gets ihe file extension.
+   *
+   * @return the file extension as a string.
+   */
+  public String getFileExtension(){
+   
+      return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+  
+  
+  /**
+   * Sets ihe file extension.
+   *
+   * @param ext the file extension as a string startin with '.'.
+   */ 
+  protected void setFileExtension(String ext){
+   
+      FILE_EXTENSION = ext;
+  }
+  
+  
+  /**
+   * Gets the destination file.
+   *
+   * @return the destination file.
+   */
+  public File retrieveFile(){
+  
+      return m_outputFile;
+  }
+ 
+  /** Sets the destination file.
+   * @param outputFile the destination file.
+   * @throws IOException throws an IOException if file cannot be set
+   */
+  public void setFile(File outputFile) throws IOException  {
+      
+      m_outputFile = outputFile;
+      setDestination(outputFile);
+      
+  }
+
+  
+  /** Sets the file name prefix
+   * @param prefix the file name prefix
+   */  
+  public void setFilePrefix(String prefix){
+   
+      m_prefix = prefix;
+  }
+  
+  /** Gets the file name prefix
+   * @return the prefix of the filename
+   */  
+  public String filePrefix(){
+   
+      return m_prefix;
+  }
+  
+  /** Sets the directory where the instances should be stored
+   * @param dir a string containing the directory path and name
+   */  
+  public void setDir(String dir){
+
+    m_dir = dir;
+  }
+  
+  /** Gets the directory
+   * @return a string with the file name
+   */  
+  public String retrieveDir(){
+   
+      return m_dir;
+  }
+  
+  /**
+   * Set the environment variables to use.
+   * 
+   * @param env the environment variables to use
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+    if (m_outputFile != null) {
+      try {
+        // try and resolve any new environment variables
+        setFile(m_outputFile);
+      } catch (IOException ex) {
+        // we won't complain about it here...
+      }
+    }
+  }
+  
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector<Option> newVector = new Vector<Option>();
+
+    newVector.addElement(new Option(
+	"\tThe input file", 
+	"i", 1, "-i <the input file>"));
+    
+    newVector.addElement(new Option(
+	"\tThe output file", 
+	"o", 1, "-o <the output file>"));
+    
+    return newVector.elements();
+  }
+
+ 
+/**
+   * Parses a given list of options. Valid option is:<p>
+   *   
+   * -i input arff file <br>
+   * The input filw in arff format. <p>
+   *
+   * -o the output file <br>
+   * The output file. The prefix of the output file is sufficient. If no output file is given, Saver tries to use standard out. <p>
+   *
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String outputString = Utils.getOption('o', options);
+    String inputString = Utils.getOption('i', options);
+    
+    ArffLoader loader = new ArffLoader();
+    
+    resetOptions();
+    
+    if(inputString.length() != 0){
+        try{
+            File input = new File(inputString);
+            loader.setFile(input);
+            setInstances(loader.getDataSet());
+        } catch(Exception ex){
+            throw new IOException("No data set loaded. Data set has to be in ARFF format.");
+        }
+    }
+    else
+        throw new IOException("No data set to save.");
+    if (outputString.length() != 0){ 
+        //add appropriate file extension
+        if(!outputString.endsWith(FILE_EXTENSION)){
+            if(outputString.lastIndexOf('.') != -1)
+                outputString = (outputString.substring(0,outputString.lastIndexOf('.'))) + FILE_EXTENSION;
+            else
+                outputString = outputString + FILE_EXTENSION;
+        }
+        try{
+            File output = new File(outputString);
+            setFile(output);
+        } catch(Exception ex) {
+            throw new IOException("Cannot create output file (Reason: " + ex.toString() + "). Standard out is used.");
+        }
+    }
+  }
+
+  /**
+   * Gets the current settings of the Saver object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [10];
+    int current = 0;
+    if(m_outputFile != null){
+        options[current++] = "-o"; options[current++] = "" + m_outputFile;
+    }
+    else{
+        options[current++] = "-o"; options[current++] = "";
+    }
+    if(getInstances() != null){
+        options[current++] = "-i"; options[current++] = "" + getInstances().relationName();
+    }
+    else{
+        options[current++] = "-i"; options[current++] = "";
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+
+  /** Cancels the incremental saving process. */  
+  public void cancel(){
+  
+      if(getWriteMode() == CANCEL){
+        if(m_outputFile != null && m_outputFile.exists()){
+            if(m_outputFile.delete())
+                System.out.println("File deleted.");
+        }
+        resetOptions();
+      }
+  }
+  
+  /**
+   * Sets the destination file (and directories if necessary).
+   *
+   * @param file the File
+   * @exception IOException always
+   */
+  public void setDestination(File file) throws IOException {
+
+    boolean success = false;
+    String tempOut = file.getPath();
+    try {
+      if (m_env == null) {
+        m_env = Environment.getSystemWide();
+      }
+      tempOut = m_env.substitute(tempOut);
+    } catch (Exception ex) {
+      // don't complain about it here...
+      // throw new IOException("[AbstractFileSaver]: " + ex.getMessage());
+    }
+    file = new File(tempOut);
+    String out = file.getAbsolutePath();
+    if(m_outputFile != null){
+        try{
+            if(file.exists()){
+                if(!file.delete())                    
+                    throw new IOException("File already exists."); 
+            }
+            if(out.lastIndexOf(File.separatorChar) == -1){
+                success = file.createNewFile();
+            }
+            else{
+                String outPath = out.substring(0,out.lastIndexOf(File.separatorChar));
+                File dir = new File(outPath);
+                if(dir.exists())
+                    success = file.createNewFile();
+                else{
+                    dir.mkdirs();
+                    success = file.createNewFile();
+                }
+            }
+            if(success){ 
+              if (m_useRelativePath) {
+                try {
+                  m_outputFile = Utils.convertToRelativePath(file);
+                } catch (Exception e) {
+                  m_outputFile = file;
+                }
+              } else {
+                m_outputFile = file;
+              }
+              setDestination(new FileOutputStream(m_outputFile));
+            }
+        } catch(Exception ex){
+            throw new IOException("Cannot create a new output file (Reason: " + ex.toString() + "). Standard out is used.");
+        } finally{
+            if(!success){
+                System.err.println("Cannot create a new output file. Standard out is used.");
+                m_outputFile = null; //use standard out
+            }
+        }
+    }
+  }
+  
+  
+  /** Sets the destination output stream.
+   * @param output the output stream.
+   * @throws IOException throws an IOException if destination cannot be set
+   */
+  public void setDestination(OutputStream output) throws IOException {
+
+    m_writer = new BufferedWriter(new OutputStreamWriter(output));
+  }
+  
+
+  /** Sets the directory and the file prefix.
+   * This method is used in the KnowledgeFlow GUI
+   * @param relationName the name of the relation to save
+   * @param add additional string which should be part of the filename
+   */  
+  public void setDirAndPrefix(String relationName, String add){
+  
+      try{
+        if(m_dir.equals("")) {
+          setDir(System.getProperty("user.dir"));
+        }
+        if(m_prefix.equals("")) {
+          if (relationName.length() == 0) {
+            throw new IOException("[Saver] Empty filename!!");
+          }
+            setFile(new File(m_dir + File.separator + relationName+ add + FILE_EXTENSION));
+        } else {
+          if (relationName.length() > 0) {
+            relationName = "_" + relationName;
+          }
+           setFile(new File(m_dir + File.separator + m_prefix + relationName+ add + FILE_EXTENSION));
+        }
+      }catch(Exception ex){
+        System.err.println("File prefix and/or directory could not have been set.");
+        ex.printStackTrace();
+      }
+  }
+  
+  /**
+   * to be pverridden
+   *
+   * @return the file type description.
+   */
+  public abstract String getFileDescription();
+
+  /**
+   * Tip text suitable for displaying int the GUI
+   *
+   * @return a description of this property as a String
+   */
+  public String useRelativePathTipText() {
+    return "Use relative rather than absolute paths";
+  }
+
+  /**
+   * Set whether to use relative rather than absolute paths
+   *
+   * @param rp true if relative paths are to be used
+   */
+  public void setUseRelativePath(boolean rp) {
+    m_useRelativePath = rp;
+  }
+
+  /**
+   * Gets whether relative paths are to be used
+   *
+   * @return true if relative paths are to be used
+   */
+  public boolean getUseRelativePath() {
+    return m_useRelativePath;
+  }
+
+  /**
+   * generates a string suitable for output on the command line displaying
+   * all available options.
+   * 
+   * @param saver	the saver to create the option string for
+   * @return		the option string
+   */
+  protected static String makeOptionStr(AbstractFileSaver saver) {
+    StringBuffer 	result;
+    Option 		option;
+    
+    result = new StringBuffer();
+    
+    // build option string
+    result.append("\n");
+    result.append(saver.getClass().getName().replaceAll(".*\\.", ""));
+    result.append(" options:\n\n");
+    Enumeration enm = saver.listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      result.append(option.synopsis() + "\n");
+      result.append(option.description() + "\n");
+    }
+
+    return result.toString();
+  }
+  
+  /**
+   * runs the given saver with the specified options
+   * 
+   * @param saver	the saver to run
+   * @param options	the commandline options
+   */
+  public static void runFileSaver(AbstractFileSaver saver, String[] options) {
+    // help request?
+    try {
+      String[] tmpOptions = (String[]) options.clone();
+      if (Utils.getFlag('h', tmpOptions)) {
+	System.err.println("\nHelp requested\n" + makeOptionStr(saver));
+	return;
+      }
+    }
+    catch (Exception e) {
+      // ignore it
+    }
+    
+    try {
+      // set options
+      try {
+	saver.setOptions(options);  
+      }
+      catch (Exception ex) {
+	System.err.println(makeOptionStr(saver));
+	System.exit(1);
+      }
+      
+      saver.writeBatch();
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/AbstractLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/AbstractLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/AbstractLoader.java	(revision 29)
@@ -0,0 +1,111 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractLoader.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instances;
+import weka.core.Instance;
+import java.io.*;
+
+/**
+ * Abstract class gives default implementation of setSource 
+ * methods. All other methods must be overridden.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class AbstractLoader implements Loader {
+  
+  /** The retrieval modes */
+  protected static final int NONE = 0;
+  protected static final int BATCH = 1;
+  protected static final int INCREMENTAL = 2;
+
+  /** The current retrieval mode */
+  protected int m_retrieval;
+
+  /**
+   * Sets the retrieval mode.
+   *
+   * @param mode the retrieval mode
+   */
+  protected void setRetrieval(int mode) {
+
+    m_retrieval = mode;
+  }
+
+  /**
+   * Gets the retrieval mode.
+   *
+   * @return the retrieval mode
+   */
+  protected int getRetrieval() {
+
+    return m_retrieval;
+  }
+
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param file the File
+   * @exception IOException always
+   */
+  public void setSource(File file) throws IOException {
+
+    throw new IOException("Setting File as source not supported");
+  }
+
+  /**
+   * Default implementation sets retrieval mode to NONE
+   *
+   * @exception never.
+   */
+  public void reset() throws Exception {
+    m_retrieval = NONE;
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param input the input stream
+   * @exception IOException always
+   */
+  public void setSource(InputStream input) throws IOException {
+
+    throw new IOException("Setting InputStream as source not supported");
+  }
+  
+  /*
+   * To be overridden.
+   */
+  public abstract Instances getStructure() throws IOException;
+
+  /*
+   * To be overridden.
+   */
+  public abstract Instances getDataSet() throws IOException;
+
+  /*
+   * To be overridden.
+   */
+  public abstract Instance getNextInstance(Instances structure) throws IOException;
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/AbstractSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/AbstractSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/AbstractSaver.java	(revision 29)
@@ -0,0 +1,323 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Abstract class for Saver
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class AbstractSaver
+  implements Saver, CapabilitiesHandler {
+  
+  
+  /** The write modes */
+  protected static final int WRITE = 0;
+  protected static final int WAIT = 1;
+  protected static final int CANCEL = 2;
+  protected static final int STRUCTURE_READY = 3;
+  
+  
+  /** The instances that should be stored */
+  private Instances m_instances;
+
+  /** The current retrieval mode */
+  protected int m_retrieval;
+
+  /** The current write mode */
+  private int m_writeMode;
+  
+  
+  /**
+   * resets the options
+   *
+   */
+  public void resetOptions(){
+      
+     m_instances = null;
+     m_writeMode = WAIT;
+  }
+  
+  
+  /** Resets the structure (header information of the instances) */  
+  public void resetStructure(){
+   
+      m_instances = null;
+      m_writeMode = WAIT;
+  }
+  
+  
+  /**
+   * Sets the retrieval mode.
+   *
+   * @param mode the retrieval mode
+   */
+  public void setRetrieval(int mode) {
+
+    m_retrieval = mode;
+  }
+
+  /**
+   * Gets the retrieval mode.
+   *
+   * @return the retrieval mode
+   */
+  protected int getRetrieval() {
+
+    return m_retrieval;
+  }
+  
+  
+  /**
+   * Sets the write mode.
+   *
+   * @param mode the write mode
+   */
+  protected void setWriteMode(int mode) {
+
+    m_writeMode = mode;
+  }
+
+  /**
+   * Gets the write mode.
+   *
+   * @return the write mode
+   */
+  public int getWriteMode() {
+
+    return m_writeMode;
+  }
+  
+  
+  /**
+   * Sets instances that should be stored.
+   *
+   * @param instances the instances
+   */
+  public void setInstances(Instances instances){
+
+      Capabilities cap = getCapabilities();
+      if (!cap.test(instances))
+	throw new IllegalArgumentException(cap.getFailReason());
+    
+      if(m_retrieval == INCREMENTAL){
+          if(setStructure(instances) == CANCEL)
+              cancel();
+      }
+      else  
+        m_instances = instances;
+  }
+  
+  /**
+   * Gets instances that should be stored.
+   *
+   * @return the instances
+   */
+  public Instances getInstances(){
+   
+      return m_instances;
+  }
+ 
+
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param file the File
+   * @exception IOException always
+   */
+  public void setDestination(File file) throws IOException {
+  
+    throw new IOException("Writing to a file not supported");
+  }
+  
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param output the OutputStream
+   * @exception IOException always
+   */
+  public void setDestination(OutputStream output) throws IOException {
+
+    throw new IOException("Writing to an outputstream not supported");
+  }
+
+  /** 
+   * Returns the Capabilities of this saver. Derived savers have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+  
+  /** Sets the strcuture of the instances for the first step of incremental saving.
+   * The instances only need to have a header.
+   * @param headerInfo an instances object.
+   * @return the appropriate write mode
+   */  
+  public int setStructure(Instances headerInfo){
+  
+      Capabilities cap = getCapabilities();
+      if (!cap.test(headerInfo))
+	throw new IllegalArgumentException(cap.getFailReason());
+  
+      if(m_writeMode == WAIT && headerInfo != null){
+        m_instances = headerInfo;
+        m_writeMode = STRUCTURE_READY;
+      }
+      else{
+        if((headerInfo == null)  || !(m_writeMode == STRUCTURE_READY) || !headerInfo.equalHeaders(m_instances)){
+            m_instances = null;
+            if(m_writeMode != WAIT)
+                System.err.println("A structure cannot be set up during an active incremental saving process.");
+            m_writeMode = CANCEL;
+        }
+      }
+      return m_writeMode;
+  }
+  
+  /** Cancels the incremental saving process if the write mode is CANCEL. */  
+  public void cancel(){
+  
+      if(m_writeMode == CANCEL)
+        resetOptions();
+  }
+  
+
+  
+  /** Method for incremental saving.
+   * Standard behaviour: no incremental saving is possible, therefore throw an
+   * IOException.
+   * An incremental saving process is stopped by calling this method with null.
+   * @param i the instance to be saved
+   * @throws IOException IOEXception if the instance acnnot be written to the specified destination
+   */  
+  public void writeIncremental(Instance i) throws IOException{
+  
+      throw new IOException("No Incremental saving possible.");
+  }
+  
+  
+  
+  /** Writes to a file in batch mode
+   * To be overridden.
+   * @throws IOException exception if writting is not possible
+   */  
+  public abstract void writeBatch() throws IOException;
+
+   
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @exception IOException always
+   */
+  public String getFileExtension() throws Exception{
+  
+      throw new Exception("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param file the File
+   * @exception IOException always
+   */
+  public void setFile(File file)throws IOException{
+  
+      throw new IOException("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param prefix the file prefix
+   * @exception IOException always
+   */
+  public void setFilePrefix(String prefix) throws Exception{
+  
+      throw new Exception("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @exception IOException always
+   */
+  public String filePrefix() throws Exception{
+  
+      throw new Exception("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param dir the name of the directory to save in
+   * @exception IOException always
+   */
+  public void setDir(String dir) throws IOException{
+    
+      throw new IOException("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @param relationName 
+   * @param add
+   * @exception IOException always
+   */
+  public void setDirAndPrefix(String relationName, String add) throws IOException{
+  
+      throw new IOException("Saving in a file not supported.");
+  }
+  
+  /**
+   * Default implementation throws an IOException.
+   *
+   * @exception IOException always
+   */
+  public String retrieveDir() throws IOException{
+  
+      throw new IOException("Saving in a file not supported.");
+  }
+  
+  
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/ArffLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/ArffLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/ArffLoader.java	(revision 29)
@@ -0,0 +1,1072 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ArffLoader.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in arff (attribute relation file format) format.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class ArffLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, IncrementalConverter, URLSourcedLoader {
+
+  /** for serialization */
+  static final long serialVersionUID = 2726929550544048587L;
+  
+  /** the file extension */
+  public static String FILE_EXTENSION = Instances.FILE_EXTENSION;
+  public static String FILE_EXTENSION_COMPRESSED = FILE_EXTENSION + ".gz";
+
+  /** the url */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** The parser for the ARFF file */
+  protected transient ArffReader m_ArffReader = null;
+  
+  /**
+   * Reads data from an ARFF file, either in incremental or batch mode. <p/>
+   * 
+   * Typical code for batch usage:
+   * <pre>
+   * BufferedReader reader = new BufferedReader(new FileReader("/some/where/file.arff"));
+   * ArffReader arff = new ArffReader(reader);
+   * Instances data = arff.getData();
+   * data.setClassIndex(data.numAttributes() - 1);
+   * </pre>
+   * 
+   * Typical code for incremental usage:
+   * <pre>
+   * BufferedReader reader = new BufferedReader(new FileReader("/some/where/file.arff"));
+   * ArffReader arff = new ArffReader(reader, 1000);
+   * Instances data = arff.getStructure();
+   * data.setClassIndex(data.numAttributes() - 1);
+   * Instance inst;
+   * while ((inst = arff.readInstance(data)) != null) {
+   *   data.add(inst);
+   * }
+   * </pre>
+   * 
+   * @author  Eibe Frank (eibe@cs.waikato.ac.nz)
+   * @author  Len Trigg (trigg@cs.waikato.ac.nz)
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5987 $
+   */
+  public static class ArffReader
+    implements RevisionHandler {
+
+    /** the tokenizer for reading the stream */
+    protected StreamTokenizer m_Tokenizer;
+    
+    /** Buffer of values for sparse instance */
+    protected double[] m_ValueBuffer;
+
+    /** Buffer of indices for sparse instance */
+    protected int[] m_IndicesBuffer;
+
+    /** the actual data */
+    protected Instances m_Data;
+
+    /** the number of lines read so far */
+    protected int m_Lines;
+    
+    /**
+     * Reads the data completely from the reader. The data can be accessed
+     * via the <code>getData()</code> method.
+     * 
+     * @param reader		the reader to use
+     * @throws IOException	if something goes wrong
+     * @see			#getData()
+     */
+    public ArffReader(Reader reader) throws IOException {
+      m_Tokenizer = new StreamTokenizer(reader);
+      initTokenizer();
+
+      readHeader(1000);
+      initBuffers();
+      
+      Instance inst;
+      while ((inst = readInstance(m_Data)) != null) {
+        m_Data.add(inst);
+      };
+      
+      compactify();
+    }
+    
+    /**
+     * Reads only the header and reserves the specified space for instances.
+     * Further instances can be read via <code>readInstance()</code>.
+     * 
+     * @param reader			the reader to use
+     * @param capacity 			the capacity of the new dataset 
+     * @throws IOException		if something goes wrong
+     * @throws IllegalArgumentException	if capacity is negative
+     * @see				#getStructure()
+     * @see				#readInstance(Instances)
+     */
+    public ArffReader(Reader reader, int capacity) throws IOException {
+      if (capacity < 0)
+	throw new IllegalArgumentException("Capacity has to be positive!");
+
+      m_Tokenizer = new StreamTokenizer(reader);
+      initTokenizer();
+
+      readHeader(capacity);
+      initBuffers();
+    }
+    
+    /**
+     * Reads the data without header according to the specified template.
+     * The data can be accessed via the <code>getData()</code> method.
+     * 
+     * @param reader		the reader to use
+     * @param template		the template header
+     * @param lines		the lines read so far
+     * @throws IOException	if something goes wrong
+     * @see			#getData()
+     */
+    public ArffReader(Reader reader, Instances template, int lines) throws IOException {
+      this(reader, template, lines, 100);
+
+      Instance inst;
+      while ((inst = readInstance(m_Data)) != null) {
+        m_Data.add(inst);
+      };
+
+      compactify();
+    }
+    
+    /**
+     * Initializes the reader without reading the header according to the 
+     * specified template. The data must be read via the 
+     * <code>readInstance()</code> method.
+     * 
+     * @param reader		the reader to use
+     * @param template		the template header
+     * @param lines		the lines read so far
+     * @param capacity 		the capacity of the new dataset 
+     * @throws IOException	if something goes wrong
+     * @see			#getData()
+     */
+    public ArffReader(Reader reader, Instances template, int lines, int capacity) throws IOException {
+      m_Lines     = lines;
+      m_Tokenizer = new StreamTokenizer(reader);
+      initTokenizer();
+
+      m_Data = new Instances(template, capacity);
+      initBuffers();
+    }
+
+    /**
+     * initializes the buffers for sparse instances to be read
+     * 
+     * @see			#m_ValueBuffer
+     * @see			#m_IndicesBuffer
+     */
+    protected void initBuffers() {
+      m_ValueBuffer = new double[m_Data.numAttributes()];
+      m_IndicesBuffer = new int[m_Data.numAttributes()];
+    }
+    
+    /**
+     * compactifies the data
+     */
+    protected void compactify() {
+      if (m_Data != null)
+        m_Data.compactify();
+    }
+    
+    /**
+     * Throws error message with line number and last token read.
+     *
+     * @param msg 		the error message to be thrown
+     * @throws IOException 	containing the error message
+     */
+    protected void errorMessage(String msg) throws IOException {
+      String str = msg + ", read " + m_Tokenizer.toString();
+      if (m_Lines > 0) {
+	int line = Integer.parseInt(str.replaceAll(".* line ", ""));
+	str = str.replaceAll(" line .*", " line " + (m_Lines + line - 1));
+      }
+      throw new IOException(str);
+    }
+
+    /**
+     * returns the current line number
+     * 
+     * @return			the current line number
+     */
+    public int getLineNo() {
+      return m_Lines + m_Tokenizer.lineno();
+    }
+    
+    /**
+     * Gets next token, skipping empty lines.
+     *
+     * @throws IOException 	if reading the next token fails
+     */
+    protected void getFirstToken() throws IOException {
+      while (m_Tokenizer.nextToken() == StreamTokenizer.TT_EOL) {};
+      
+      if ((m_Tokenizer.ttype == '\'') ||
+  	(m_Tokenizer.ttype == '"')) {
+        m_Tokenizer.ttype = StreamTokenizer.TT_WORD;
+      } else if ((m_Tokenizer.ttype == StreamTokenizer.TT_WORD) &&
+  	       (m_Tokenizer.sval.equals("?"))){
+        m_Tokenizer.ttype = '?';
+      }
+    }
+
+    /**
+     * Gets index, checking for a premature and of line.
+     *
+     * @throws IOException 	if it finds a premature end of line
+     */
+    protected void getIndex() throws IOException {
+      if (m_Tokenizer.nextToken() == StreamTokenizer.TT_EOL) {
+        errorMessage("premature end of line");
+      }
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        errorMessage("premature end of file");
+      }
+    }
+    
+    /**
+     * Gets token and checks if its end of line.
+     *
+     * @param endOfFileOk 	whether EOF is OK
+     * @throws IOException 	if it doesn't find an end of line
+     */
+    protected void getLastToken(boolean endOfFileOk) throws IOException {
+      if ((m_Tokenizer.nextToken() != StreamTokenizer.TT_EOL) &&
+  	((m_Tokenizer.ttype != StreamTokenizer.TT_EOF) || !endOfFileOk)) {
+        errorMessage("end of line expected");
+      }
+    }
+
+    /**
+     * Gets the value of an instance's weight (if one exists)
+     *
+     * @return the value of the instance's weight, or NaN
+     * if no weight has been supplied in the file
+     */
+    protected double getInstanceWeight() throws IOException {
+      double weight = Double.NaN;
+      m_Tokenizer.nextToken();
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOL || 
+          m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        return weight;
+      }
+      // see if we can read an instance weight
+      //      m_Tokenizer.pushBack();
+      if (m_Tokenizer.ttype == '{') {
+        m_Tokenizer.nextToken();
+        String weightS = m_Tokenizer.sval;
+        // try to parse weight as a double
+        try {
+          weight = Double.parseDouble(weightS);
+        } catch (NumberFormatException e) {
+          // quietly ignore
+          return weight;
+        }
+        // see if we have the closing brace
+        m_Tokenizer.nextToken();
+        if (m_Tokenizer.ttype != '}') {
+          errorMessage("Problem reading instance weight");
+        }
+      }
+      return weight;
+    }
+
+    /**
+     * Gets next token, checking for a premature and of line.
+     *
+     * @throws IOException 	if it finds a premature end of line
+     */
+    protected void getNextToken() throws IOException {
+      if (m_Tokenizer.nextToken() == StreamTokenizer.TT_EOL) {
+        errorMessage("premature end of line");
+      }
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        errorMessage("premature end of file");
+      } else if ((m_Tokenizer.ttype == '\'') ||
+  	       (m_Tokenizer.ttype == '"')) {
+        m_Tokenizer.ttype = StreamTokenizer.TT_WORD;
+      } else if ((m_Tokenizer.ttype == StreamTokenizer.TT_WORD) &&
+  	       (m_Tokenizer.sval.equals("?"))){
+        m_Tokenizer.ttype = '?';
+      }
+    }
+  	
+    /**
+     * Initializes the StreamTokenizer used for reading the ARFF file.
+     */
+    protected void initTokenizer(){
+      m_Tokenizer.resetSyntax();         
+      m_Tokenizer.whitespaceChars(0, ' ');    
+      m_Tokenizer.wordChars(' '+1,'\u00FF');
+      m_Tokenizer.whitespaceChars(',',',');
+      m_Tokenizer.commentChar('%');
+      m_Tokenizer.quoteChar('"');
+      m_Tokenizer.quoteChar('\'');
+      m_Tokenizer.ordinaryChar('{');
+      m_Tokenizer.ordinaryChar('}');
+      m_Tokenizer.eolIsSignificant(true);
+    }
+    
+    /**
+     * Reads a single instance using the tokenizer and returns it. 
+     *
+     * @param structure 	the dataset header information, will get updated 
+     * 				in case of string or relational attributes
+     * @return 			null if end of file has been reached
+     * @throws IOException 	if the information is not read 
+     * successfully
+     */ 
+    public Instance readInstance(Instances structure) throws IOException {
+      return readInstance(structure, true);
+    }
+    
+    /**
+     * Reads a single instance using the tokenizer and returns it. 
+     *
+     * @param structure 	the dataset header information, will get updated 
+     * 				in case of string or relational attributes
+     * @param flag 		if method should test for carriage return after 
+     * 				each instance
+     * @return 			null if end of file has been reached
+     * @throws IOException 	if the information is not read 
+     * successfully
+     */ 
+    public Instance readInstance(Instances structure, boolean flag) throws IOException {
+      return getInstance(structure, flag);
+    }
+    
+    /**
+     * Reads a single instance using the tokenizer and returns it. 
+     *
+     * @param structure 	the dataset header information, will get updated 
+     * 				in case of string or relational attributes
+     * @param flag 		if method should test for carriage return after 
+     * 				each instance
+     * @return 			null if end of file has been reached
+     * @throws IOException 	if the information is not read 
+     * 				successfully
+     */ 
+    protected Instance getInstance(Instances structure, boolean flag) throws IOException {
+      m_Data = structure;
+      
+      // Check if any attributes have been declared.
+      if (m_Data.numAttributes() == 0) {
+        errorMessage("no header information available");
+      }
+
+      // Check if end of file reached.
+      getFirstToken();
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        return null;
+      }
+      
+      // Parse instance
+      if (m_Tokenizer.ttype == '{') {
+        return getInstanceSparse(flag);
+      } else {
+        return getInstanceFull(flag);
+      }
+    }
+
+    /**
+     * Reads a single instance using the tokenizer and returns it.
+     *
+     * @param flag 		if method should test for carriage return after 
+     * 				each instance
+     * @return 			null if end of file has been reached
+     * @throws IOException 	if the information is not read 
+     * 				successfully
+     */ 
+    protected Instance getInstanceSparse(boolean flag) throws IOException {
+      int valIndex, numValues = 0, maxIndex = -1;
+      
+      // Get values
+      do {
+        // Get index
+        getIndex();
+        if (m_Tokenizer.ttype == '}') {
+  	break;
+        }
+   
+        // Is index valid?
+        try{
+  	m_IndicesBuffer[numValues] = Integer.valueOf(m_Tokenizer.sval).intValue();
+        } catch (NumberFormatException e) {
+  	errorMessage("index number expected");
+        }
+        if (m_IndicesBuffer[numValues] <= maxIndex) {
+  	errorMessage("indices have to be ordered");
+        }
+        if ((m_IndicesBuffer[numValues] < 0) || 
+  	  (m_IndicesBuffer[numValues] >= m_Data.numAttributes())) {
+  	errorMessage("index out of bounds");
+        }
+        maxIndex = m_IndicesBuffer[numValues];
+
+        // Get value;
+        getNextToken();
+
+        // Check if value is missing.
+        if  (m_Tokenizer.ttype == '?') {
+          m_ValueBuffer[numValues] = Utils.missingValue();
+        } else {
+
+  	// Check if token is valid.
+  	if (m_Tokenizer.ttype != StreamTokenizer.TT_WORD) {
+  	  errorMessage("not a valid value");
+  	}
+          switch (m_Data.attribute(m_IndicesBuffer[numValues]).type()) {
+            case Attribute.NOMINAL:
+              // Check if value appears in header.
+              valIndex = 
+                m_Data.attribute(m_IndicesBuffer[numValues]).indexOfValue(m_Tokenizer.sval);
+              if (valIndex == -1) {
+                errorMessage("nominal value not declared in header");
+              }
+              m_ValueBuffer[numValues] = (double)valIndex;
+              break;
+  	case Attribute.NUMERIC:
+  	  // Check if value is really a number.
+  	  try{
+  	    m_ValueBuffer[numValues] = Double.valueOf(m_Tokenizer.sval).
+  	      doubleValue();
+  	  } catch (NumberFormatException e) {
+  	    errorMessage("number expected");
+  	  }
+            break;
+  	case Attribute.STRING:
+  	  m_ValueBuffer[numValues] = 
+  	    m_Data.attribute(m_IndicesBuffer[numValues]).addStringValue(m_Tokenizer.sval);
+            break;
+          case Attribute.DATE:
+            try {
+              m_ValueBuffer[numValues] = 
+                m_Data.attribute(m_IndicesBuffer[numValues]).parseDate(m_Tokenizer.sval);
+            } catch (ParseException e) {
+              errorMessage("unparseable date: " + m_Tokenizer.sval);
+            }
+            break;
+          case Attribute.RELATIONAL:
+            try {
+              ArffReader arff = new ArffReader(new StringReader(m_Tokenizer.sval), m_Data.attribute(m_IndicesBuffer[numValues]).relation(), 0);
+              Instances data = arff.getData();
+              m_ValueBuffer[numValues] = m_Data.attribute(m_IndicesBuffer[numValues]).addRelation(data);
+            }
+            catch (Exception e) {
+              throw new IOException(e.toString() + " of line " + getLineNo());
+            }
+            break;
+          default:
+            errorMessage("unknown attribute type in column " + m_IndicesBuffer[numValues]);
+  	}
+        }
+        numValues++;
+      } while (true);
+
+      double weight = 1.0;
+      if (flag) {
+        // check for an instance weight
+        weight = getInstanceWeight();
+        if (!Double.isNaN(weight)) {
+          getLastToken(true);
+        } else {
+          weight = 1.0;
+        }        
+      }
+        
+      // Add instance to dataset
+      double[] tempValues = new double[numValues];
+      int[] tempIndices = new int[numValues];
+      System.arraycopy(m_ValueBuffer, 0, tempValues, 0, numValues);
+      System.arraycopy(m_IndicesBuffer, 0, tempIndices, 0, numValues);
+      Instance inst = new SparseInstance(weight, tempValues, tempIndices, m_Data.numAttributes());
+      inst.setDataset(m_Data);
+      
+      return inst;
+    }
+
+    /**
+     * Reads a single instance using the tokenizer and returns it.
+     *
+     * @param flag 		if method should test for carriage return after 
+     * 				each instance
+     * @return 			null if end of file has been reached
+     * @throws IOException 	if the information is not read 
+     * 				successfully
+     */ 
+    protected Instance getInstanceFull(boolean flag) throws IOException {
+      double[] instance = new double[m_Data.numAttributes()];
+      int index;
+      
+      // Get values for all attributes.
+      for (int i = 0; i < m_Data.numAttributes(); i++){
+        // Get next token
+        if (i > 0) {
+  	getNextToken();
+        }
+              
+        // Check if value is missing.
+        if  (m_Tokenizer.ttype == '?') {
+  	instance[i] = Utils.missingValue();
+        } else {
+
+  	// Check if token is valid.
+  	if (m_Tokenizer.ttype != StreamTokenizer.TT_WORD) {
+  	  errorMessage("not a valid value");
+  	}
+          switch (m_Data.attribute(i).type()) {
+          case Attribute.NOMINAL:
+  	  // Check if value appears in header.
+  	  index = m_Data.attribute(i).indexOfValue(m_Tokenizer.sval);
+  	  if (index == -1) {
+  	    errorMessage("nominal value not declared in header");
+  	  }
+  	  instance[i] = (double)index;
+            break;
+  	case Attribute.NUMERIC:
+  	  // Check if value is really a number.
+  	  try{
+  	    instance[i] = Double.valueOf(m_Tokenizer.sval).
+  	      doubleValue();
+  	  } catch (NumberFormatException e) {
+  	    errorMessage("number expected");
+  	  }
+            break;
+  	case Attribute.STRING:
+  	  instance[i] = m_Data.attribute(i).addStringValue(m_Tokenizer.sval);
+            break;
+          case Attribute.DATE:
+            try {
+              instance[i] = m_Data.attribute(i).parseDate(m_Tokenizer.sval);
+            } catch (ParseException e) {
+              errorMessage("unparseable date: " + m_Tokenizer.sval);
+            }
+            break;
+          case Attribute.RELATIONAL:
+            try {
+              ArffReader arff = new ArffReader(new StringReader(m_Tokenizer.sval), m_Data.attribute(i).relation(), 0);
+              Instances data = arff.getData();
+              instance[i] = m_Data.attribute(i).addRelation(data);
+            }
+            catch (Exception e) {
+              throw new IOException(e.toString() + " of line " + getLineNo());
+            }
+            break;
+          default:
+            errorMessage("unknown attribute type in column " + i);
+  	}
+        }
+      }
+      
+      double weight = 1.0;
+      if (flag) {
+        // check for an instance weight
+        weight = getInstanceWeight();
+        if (!Double.isNaN(weight)) {
+          getLastToken(true);
+        } else {
+          weight = 1.0;
+        }
+      }
+        
+      // Add instance to dataset
+      Instance inst = new DenseInstance(weight, instance);
+      inst.setDataset(m_Data);
+      
+      return inst;
+    }
+
+    /**
+     * Reads and stores header of an ARFF file.
+     *
+     * @param capacity 		the number of instances to reserve in the data 
+     * 				structure
+     * @throws IOException 	if the information is not read 
+     * 				successfully
+     */ 
+    protected void readHeader(int capacity) throws IOException {
+      m_Lines = 0;
+      String relationName = "";
+      
+      // Get name of relation.
+      getFirstToken();
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        errorMessage("premature end of file");
+      }
+      if (Instances.ARFF_RELATION.equalsIgnoreCase(m_Tokenizer.sval)) {
+        getNextToken();
+        relationName = m_Tokenizer.sval;
+        getLastToken(false);
+      } else {
+        errorMessage("keyword " + Instances.ARFF_RELATION + " expected");
+      }
+
+      // Create vectors to hold information temporarily.
+      ArrayList<Attribute> attributes = new ArrayList<Attribute>();
+   
+      // Get attribute declarations.
+      getFirstToken();
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+        errorMessage("premature end of file");
+      }
+
+      while (Attribute.ARFF_ATTRIBUTE.equalsIgnoreCase(m_Tokenizer.sval)) {
+        attributes = parseAttribute(attributes);
+      }
+
+      // Check if data part follows. We can't easily check for EOL.
+      if (!Instances.ARFF_DATA.equalsIgnoreCase(m_Tokenizer.sval)) {
+        errorMessage("keyword " + Instances.ARFF_DATA + " expected");
+      }
+      
+      // Check if any attributes have been declared.
+      if (attributes.size() == 0) {
+        errorMessage("no attributes declared");
+      }
+      
+      m_Data = new Instances(relationName, attributes, capacity);
+    }
+
+    /**
+     * Parses the attribute declaration.
+     *
+     * @param attributes 		the current attributes vector
+     * @return 			the new attributes vector
+     * @throws IOException 	if the information is not read 
+     * 				successfully
+     */
+    protected ArrayList<Attribute> parseAttribute(ArrayList<Attribute> attributes) throws IOException {
+      String attributeName;
+      ArrayList<String> attributeValues;
+
+      // Get attribute name.
+      getNextToken();
+      attributeName = m_Tokenizer.sval;
+      getNextToken();
+      
+      // Check if attribute is nominal.
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_WORD) {
+        
+        // Attribute is real, integer, or string.
+        if (m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_REAL) ||
+            m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_INTEGER) ||
+            m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_NUMERIC)) {
+          attributes.add(new Attribute(attributeName, attributes.size()));
+          readTillEOL();
+        } else if (m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_STRING)) {
+          attributes.add(new Attribute(attributeName, (ArrayList<String>)null,
+                attributes.size()));
+          readTillEOL();
+        } else if (m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_DATE)) {
+          String format = null;
+          if (m_Tokenizer.nextToken() != StreamTokenizer.TT_EOL) {
+            if ((m_Tokenizer.ttype != StreamTokenizer.TT_WORD) &&
+                (m_Tokenizer.ttype != '\'') &&
+                (m_Tokenizer.ttype != '\"')) {
+              errorMessage("not a valid date format");
+            }
+            format = m_Tokenizer.sval;
+            readTillEOL();
+          } else {
+            m_Tokenizer.pushBack();
+          }
+          attributes.add(new Attribute(attributeName, format, attributes.size()));
+          
+        } else if (m_Tokenizer.sval.equalsIgnoreCase(Attribute.ARFF_ATTRIBUTE_RELATIONAL)) {
+          readTillEOL();
+          
+          // Read attributes for subrelation
+          // First, save current set of attributes
+          ArrayList<Attribute> atts = attributes;
+          attributes = new ArrayList<Attribute>();
+          
+          // Now, read attributes until we hit end of declaration of relational value
+          getFirstToken();
+          if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF) {
+            errorMessage("premature end of file");
+          }
+          do {
+            if (Attribute.ARFF_ATTRIBUTE.equalsIgnoreCase(m_Tokenizer.sval)) {
+              attributes = parseAttribute(attributes);
+            } else if (Attribute.ARFF_END_SUBRELATION.equalsIgnoreCase(m_Tokenizer.sval)) {
+              getNextToken();
+              if (!attributeName.equalsIgnoreCase(m_Tokenizer.sval)) {
+                errorMessage("declaration of subrelation " + attributeName + 
+                      " must be terminated by " + "@end " + attributeName);
+              }
+              break;
+            } else {
+              errorMessage("declaration of subrelation " + attributeName + 
+                    " must be terminated by " + "@end " + attributeName);
+            }
+          } while (true);
+          
+          // Make relation and restore original set of attributes
+          Instances relation = new Instances(attributeName, attributes, 0);
+          attributes = atts;
+          attributes.add(new Attribute(attributeName, relation, attributes.size()));
+        } else {
+          errorMessage("no valid attribute type or invalid "+
+                "enumeration");
+        }
+      } else {
+        
+        // Attribute is nominal.
+        attributeValues = new ArrayList<String>();
+        m_Tokenizer.pushBack();
+        
+        // Get values for nominal attribute.
+        if (m_Tokenizer.nextToken() != '{') {
+          errorMessage("{ expected at beginning of enumeration");
+        }
+        while (m_Tokenizer.nextToken() != '}') {
+          if (m_Tokenizer.ttype == StreamTokenizer.TT_EOL) {
+            errorMessage("} expected at end of enumeration");
+          } else {
+            attributeValues.add(m_Tokenizer.sval);
+          }
+        }
+        attributes.add(new Attribute(attributeName, attributeValues,
+              attributes.size()));
+      }
+      getLastToken(false);
+      getFirstToken();
+      if (m_Tokenizer.ttype == StreamTokenizer.TT_EOF)
+        errorMessage("premature end of file");
+      
+      return attributes;
+    }
+
+    /**
+     * Reads and skips all tokens before next end of line token.
+     *
+     * @throws IOException 	in case something goes wrong
+     */
+    protected void readTillEOL() throws IOException {
+      while (m_Tokenizer.nextToken() != StreamTokenizer.TT_EOL) {};
+      
+      m_Tokenizer.pushBack();
+    }
+
+    /**
+     * Returns the header format
+     * 
+     * @return			the header format
+     */
+    public Instances getStructure() {
+      return new Instances(m_Data, 0);
+    }
+    
+    /**
+     * Returns the data that was read
+     * 
+     * @return			the data
+     */
+    public Instances getData() {
+      return m_Data;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /**
+   * Returns a string describing this Loader
+   * @return a description of the Loader suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Reads a source that is in arff (attribute relation file format) "
+      +"format. ";
+  }
+
+  /**
+   * Get the file extension used for arff files
+   *
+   * @return the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{FILE_EXTENSION, FILE_EXTENSION_COMPRESSED};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "Arff data files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set or the
+   * same data set again.
+   * 
+   * @throws IOException if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    } else if (m_URL != null && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url the source url.
+   * @throws IOException if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+  
+
+  /**
+   * get the File specified as the source
+   *
+   * @return the source file
+   */
+  public File retrieveFile() {
+    return new File(m_File);
+  }
+
+  /**
+   * sets the source File
+   *
+   * @param file the source file
+   * @throws IOException if an error occurs
+   */
+  public void setFile(File file) throws IOException {
+    m_File = file.getPath();
+    setSource(file);
+  }
+
+  /**
+   * Set the url to load from
+   *
+   * @param url the url to load from
+   * @throws IOException if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in the source InputStream.
+   * @throws IOException always thrown.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+
+    if (m_sourceReader == null) {
+      throw new IOException("No source has been specified");
+    }
+
+    if (m_structure == null) {
+      try {
+	m_ArffReader = new ArffReader(m_sourceReader, 1);
+	m_structure  = m_ArffReader.getStructure();
+      } catch (Exception ex) {
+	throw new IOException("Unable to determine structure as arff (Reason: " + ex.toString() + ").");
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+
+    if (m_sourceReader == null) {
+      throw new IOException("No source has been specified");
+    }
+    if (getRetrieval() == INCREMENTAL) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    setRetrieval(BATCH);
+    if (m_structure == null) {
+      getStructure();
+    }
+
+    // Read all instances
+    Instance inst;
+    while ((inst = m_ArffReader.readInstance(m_structure)) != null)
+      m_structure.add(inst);
+    
+    Instances readIn = new Instances(m_structure);
+
+    // close the stream
+    m_sourceReader.close();
+    
+    return readIn;
+  }
+
+  /**
+   * Read the data set incrementally---get the next instance in the data 
+   * set or returns null if there are no
+   * more instances to get. If the structure hasn't yet been 
+   * determined by a call to getStructure then method should do so before
+   * returning the next instance in the data set.
+   *
+   * @param structure the dataset header information, will get updated in 
+   * case of string or relational attributes
+   * @return the next instance in the data set as an Instance object or null
+   * if there are no more instances to be read
+   * @throws IOException if there is an error during parsing
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+
+    m_structure = structure;
+
+    if (getRetrieval() == BATCH) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    setRetrieval(INCREMENTAL);
+
+    Instance current = m_ArffReader.readInstance(m_structure);
+    if (current == null) {
+      try {
+        // close the stream
+        m_sourceReader.close();
+        //        reset();
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+    return current;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the name of an input file.
+   */
+  public static void main(String [] args) {
+    runFileLoader(new ArffLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/ArffSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/ArffSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/ArffSaver.java	(revision 29)
@@ -0,0 +1,234 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ArffSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Writes to a destination in arff text format. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ * The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ * The output file</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class ArffSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter {
+
+  /** for serialization */
+  static final long serialVersionUID = 2223634248900042228L;    
+  
+  /** Constructor */  
+  public ArffSaver(){
+  
+      resetOptions();
+  }
+   
+   
+  /**
+   * Returns a string describing this Saver
+   * @return a description of the Saver suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Writes to a destination that is in arff (attribute relation file format) "
+      +"format. ";
+  }
+
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "Arff data files";
+  }
+
+  /**
+   * Resets the Saver 
+   */
+  public void resetOptions() {
+
+    super.resetOptions();
+    setFileExtension(".arff");
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /** Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * @param inst the instance to save
+   * @throws IOException throws IOEXception if an instance cannot be saved incrementally.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+  
+      int writeMode = getWriteMode();
+      Instances structure = getInstances();
+      PrintWriter outW = null;
+      
+      if(getRetrieval() == BATCH || getRetrieval() == NONE)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      if(getWriter() != null)
+          outW = new PrintWriter(getWriter());
+          
+      if(writeMode == WAIT){
+        if(structure == null){
+            setWriteMode(CANCEL);
+            if(inst != null)
+                System.err.println("Structure(Header Information) has to be set in advance");
+        }
+        else
+            setWriteMode(STRUCTURE_READY);
+        writeMode = getWriteMode();
+      }
+      if(writeMode == CANCEL){
+          if(outW != null)
+              outW.close();
+          cancel();
+      }
+      if(writeMode == STRUCTURE_READY){
+          setWriteMode(WRITE);
+          //write header
+          Instances header = new Instances(structure,0);
+          if(retrieveFile() == null || outW == null)
+              System.out.println(header.toString());
+          else{
+              outW.print(header.toString());
+              outW.print("\n");
+              outW.flush();
+          }
+          writeMode = getWriteMode();
+      }
+      if(writeMode == WRITE){
+          if(structure == null)
+              throw new IOException("No instances information available.");
+          if(inst != null){
+          //write instance 
+              if(retrieveFile() == null || outW == null)
+                System.out.println(inst);
+              else{
+                outW.println(inst);
+                m_incrementalCounter++;
+                //flush every 100 instances
+                if(m_incrementalCounter > 100){
+                    m_incrementalCounter = 0;
+                    outW.flush();
+                }
+              }
+          }
+          else{
+          //close
+              if(outW != null){
+                outW.flush();
+                outW.close();
+              }
+              m_incrementalCounter = 0;
+              resetStructure();
+              outW = null;
+              resetWriter();
+          }
+      }
+  }
+  
+  /** Writes a Batch of instances
+   * @throws IOException throws IOException if saving in batch mode is not possible
+   */
+  public void writeBatch() throws IOException {
+  
+      if(getInstances() == null)
+          throw new IOException("No instances to save");
+      if(getRetrieval() == INCREMENTAL)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      setRetrieval(BATCH);
+      setWriteMode(WRITE);
+      if(retrieveFile() == null && getWriter() == null){
+          System.out.println((getInstances()).toString());
+          setWriteMode(WAIT);
+          return;
+      }
+      PrintWriter outW = new PrintWriter(getWriter());
+      outW.print((getInstances()).toString());
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new ArffSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/BatchConverter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/BatchConverter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/BatchConverter.java	(revision 29)
@@ -0,0 +1,33 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BatchConverter.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+/**
+ * Marker interface for a loader/saver that can retrieve instances in batch mode
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision 1.0 $
+ */
+public interface BatchConverter {
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/C45Loader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/C45Loader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/C45Loader.java	(revision 29)
@@ -0,0 +1,522 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45Loader.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a file that is C45 format. Can take a filestem or filestem with .names or .data appended. Assumes that path/&lt;filestem&gt;.names and path/&lt;filestem&gt;.data exist and contain the names and data respectively.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class C45Loader 
+  extends AbstractFileLoader 
+  implements BatchConverter, IncrementalConverter {
+
+  /** for serialization */
+  static final long serialVersionUID = 5454329403218219L;
+  
+  /** the file extension */
+  public static String FILE_EXTENSION = ".names";
+
+  /**
+   * Describe variable <code>m_sourceFileData</code> here.
+   */
+  private File m_sourceFileData = null;
+
+  /**
+   * Reader for names file
+   */
+  private transient Reader m_namesReader = null;
+
+  /**
+   * Reader for data file
+   */
+  private transient Reader m_dataReader = null;
+
+  /**
+   * Holds the filestem.
+   */
+  private String m_fileStem;
+
+  /**
+   * Number of attributes in the data (including ignore and label attributes).
+   */
+  private int m_numAttribs;
+
+  /**
+   * Which attributes are ignore or label. These are *not* included in the
+   * arff representation.
+   */
+  private boolean [] m_ignore;
+
+  /**
+   * Returns a string describing this attribute evaluator
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Reads a file that is C45 format. Can take a filestem or filestem "
+      +"with .names or .data appended. Assumes that path/<filestem>.names and "
+      +"path/<filestem>.data exist and contain the names and data "
+      +"respectively.";
+  }
+  
+  /**
+   * Resets the Loader ready to read a new data set or the
+   * same data set again.
+   * 
+   * @throws IOException if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    }
+  }
+
+  /**
+   * Get the file extension used for arff files
+   *
+   * @return the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{".names", ".data"};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "C4.5 data files";
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file the source file.
+   * @exception IOException if an error occurs
+   */
+  public void setSource(File file) throws IOException {
+    m_structure = null;
+    setRetrieval(NONE);
+
+    if (file == null) {
+      throw new IOException("Source file object is null!");
+    }
+
+    String fname = file.getName();
+    String fileStem;
+    String path = file.getParent();
+    if (path != null) {
+      path += File.separator;
+    } else {
+      path = "";
+    }
+    if (fname.indexOf('.') < 0) {
+      fileStem = fname;
+      fname += ".names";
+    } else {
+      fileStem = fname.substring(0, fname.lastIndexOf('.'));
+      fname = fileStem + ".names";
+    }
+    m_fileStem = fileStem;
+    file = new File(path+fname);
+
+    m_sourceFile = file;
+    try {
+      BufferedReader br = new BufferedReader(new FileReader(file));
+      m_namesReader = br;
+    } catch (FileNotFoundException ex) {
+      throw new IOException("File not found : "+(path+fname));
+    }
+
+    m_sourceFileData = new File(path+fileStem+".data");
+    try {
+      BufferedReader br = new BufferedReader(new FileReader(m_sourceFileData));
+      m_dataReader = br;
+    } catch (FileNotFoundException ex) {
+      throw new IOException("File not found : "+(path+fname));
+    }
+    m_File = file.getAbsolutePath();
+  }
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @exception IOException if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    if (m_sourceFile == null) {
+      throw new IOException("No source has beenspecified");
+    }
+
+    if (m_structure == null) {
+      setSource(m_sourceFile);
+      StreamTokenizer st = new StreamTokenizer(m_namesReader);
+      initTokenizer(st);
+      readHeader(st);
+    }
+    
+    return m_structure;
+  }
+
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @exception IOException if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    if (m_sourceFile == null) {
+      throw new IOException("No source has been specified");
+    }
+    if (getRetrieval() == INCREMENTAL) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    setRetrieval(BATCH);
+    if (m_structure == null) {
+      getStructure();
+    }
+    StreamTokenizer st = new StreamTokenizer(m_dataReader);
+    initTokenizer(st);
+    //    st.ordinaryChar('.');
+    Instances result = new Instances(m_structure);
+    Instance current = getInstance(st);
+
+    while (current != null) {
+      result.add(current);
+      current = getInstance(st);
+    }
+    try {
+      // close the stream
+      m_dataReader.close();
+      //      reset();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return result;
+  }
+
+  /**
+   * Read the data set incrementally---get the next instance in the data 
+   * set or returns null if there are no
+   * more instances to get. If the structure hasn't yet been 
+   * determined by a call to getStructure then method should do so before
+   * returning the next instance in the data set.
+   *
+   * If it is not possible to read the data set incrementally (ie. in cases
+   * where the data set structure cannot be fully established before all
+   * instances have been seen) then an exception should be thrown.
+   *
+   * @param structure the dataset header information, will get updated in 
+   * case of string or relational attributes
+   * @return the next instance in the data set as an Instance object or null
+   * if there are no more instances to be read
+   * @exception IOException if there is an error during parsing
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    if (m_sourceFile == null) {
+      throw new IOException("No source has been specified");
+    }
+    
+    if (getRetrieval() == BATCH) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    setRetrieval(INCREMENTAL);
+
+    if (m_structure == null) {
+      getStructure();
+    }
+
+    StreamTokenizer st = new StreamTokenizer(m_dataReader);
+    initTokenizer(st);
+    //    st.ordinaryChar('.');
+    Instance nextI = getInstance(st);
+    if (nextI != null) {
+      nextI.setDataset(m_structure);
+    }
+    else{
+      try {
+        // close the stream
+        m_dataReader.close();
+        //        reset();
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+    return nextI;
+  }
+
+  /**
+   * Reads an instance using the supplied tokenizer.
+   *
+   * @param tokenizer the tokenizer to use
+   * @return an Instance or null if there are no more instances to read
+   * @exception IOException if an error occurs
+   */
+  private Instance getInstance(StreamTokenizer tokenizer) 
+    throws IOException {
+    double [] instance = new double[m_structure.numAttributes()];
+    
+    ConverterUtils.getFirstToken(tokenizer);
+    if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
+      return null;
+    }
+    
+    int counter = 0;
+    for (int i = 0; i < m_numAttribs; i++) {
+      if (i > 0) {
+	ConverterUtils.getToken(tokenizer);
+      }
+
+      if (!m_ignore[i]) {
+	// Check if value is missing.
+	if  (tokenizer.ttype == '?') {
+	  instance[counter++] = Utils.missingValue();
+	} else {
+	  String val = tokenizer.sval;
+
+	  if (i == m_numAttribs - 1) {
+	    // remove trailing period	    
+	    if (val.charAt(val.length()-1) == '.') {
+	      val = val.substring(0,val.length()-1);
+	    }
+	  }
+	  if (m_structure.attribute(counter).isNominal()) {
+	    int index = m_structure.attribute(counter).indexOfValue(val);
+	    if (index == -1) {
+	      ConverterUtils.errms(tokenizer, "nominal value not declared in "
+				   +"header :"+val+" column "+i);
+	    }
+	    instance[counter++] = (double)index;
+	  } else if (m_structure.attribute(counter).isNumeric()) {
+	    try {
+	      instance[counter++] = Double.valueOf(val).doubleValue();
+	    } catch (NumberFormatException e) {
+	      ConverterUtils.errms(tokenizer, "number expected");
+	    }
+	  } else {
+	    System.err.println("Shouldn't get here");
+	    System.exit(1);
+	  }
+	}
+      }
+    }
+
+    return new DenseInstance(1.0, instance);
+  }
+
+  /**
+   * removes the trailing period
+   * 
+   * @param val the string to work on
+   * @return the processed string
+   */
+  private String removeTrailingPeriod(String val) {
+    // remove trailing period
+    if (val.charAt(val.length()-1) == '.') {
+      val = val.substring(0,val.length()-1);
+    }
+    return val;
+  }
+
+  /**
+   * Reads header (from the names file) using the supplied tokenizer
+   *
+   * @param tokenizer the tokenizer to use
+   * @exception IOException if an error occurs
+   */
+  private void readHeader(StreamTokenizer tokenizer) throws IOException {
+
+    ArrayList<Attribute> attribDefs = new ArrayList<Attribute>();
+    ArrayList<Integer> ignores = new ArrayList<Integer>();
+    ConverterUtils.getFirstToken(tokenizer);
+    if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
+      ConverterUtils.errms(tokenizer,"premature end of file");
+    }
+
+    m_numAttribs = 1;
+    // Read the class values
+    ArrayList<String> classVals = new ArrayList<String>();
+    while (tokenizer.ttype != StreamTokenizer.TT_EOL) {
+      String val = tokenizer.sval.trim();
+      
+      if (val.length() > 0) {
+	val = removeTrailingPeriod(val);
+	classVals.add(val);
+      }
+      ConverterUtils.getToken(tokenizer);
+    }
+
+    // read the attribute names and types
+    int counter = 0;
+    while (tokenizer.ttype != StreamTokenizer.TT_EOF) {
+      ConverterUtils.getFirstToken(tokenizer);
+      if (tokenizer.ttype != StreamTokenizer.TT_EOF) {
+
+	String attribName = tokenizer.sval;
+
+	ConverterUtils.getToken(tokenizer);
+	if (tokenizer.ttype == StreamTokenizer.TT_EOL) {
+	  ConverterUtils.errms(tokenizer, "premature end of line. Expected "
+			       +"attribute type.");
+	}
+	String temp = tokenizer.sval.toLowerCase().trim();
+	if (temp.startsWith("ignore") || temp.startsWith("label")) {
+	  ignores.add(new Integer(counter));
+	  counter++;
+	} else if (temp.startsWith("continuous")) {
+	  attribDefs.add(new Attribute(attribName));
+	  counter++;
+	} else {
+	  counter++;
+	  // read the values of the attribute
+	  ArrayList<String> attribVals = new ArrayList<String>();
+	  while (tokenizer.ttype != StreamTokenizer.TT_EOL &&
+		 tokenizer.ttype != StreamTokenizer.TT_EOF) {
+	    String val = tokenizer.sval.trim();
+
+	    if (val.length() > 0) {
+	      val = removeTrailingPeriod(val);
+	      attribVals.add(val);
+	    }
+	    ConverterUtils.getToken(tokenizer);
+	  }
+	  attribDefs.add(new Attribute(attribName, attribVals));
+	}
+      }
+    }
+
+    boolean ok = true;
+    int i = -1;
+    if (classVals.size() == 1) {
+      // look to see if this is an attribute name (ala c5 names file style)
+      for (i = 0; i < attribDefs.size(); i++) {
+	if (((Attribute)attribDefs.get(i))
+	    .name().compareTo((String)classVals.get(0)) == 0) {
+	  ok = false;
+	  m_numAttribs--;
+	  break;
+	}
+      }
+    }
+
+    if (ok) {
+      attribDefs.add(new Attribute("Class", classVals));
+    }
+
+    m_structure = new Instances(m_fileStem, attribDefs, 0);
+
+    try {
+      if (ok) {
+	m_structure.setClassIndex(m_structure.numAttributes()-1);
+      } else {
+	m_structure.setClassIndex(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    m_numAttribs = m_structure.numAttributes() + ignores.size();
+    m_ignore = new boolean[m_numAttribs];
+    for (i = 0; i < ignores.size(); i++) {
+      m_ignore[((Integer)ignores.get(i)).intValue()] = true;
+    }
+  }
+
+  /**
+   * Initializes the stream tokenizer
+   *
+   * @param tokenizer the tokenizer to initialize
+   */
+  private void initTokenizer(StreamTokenizer tokenizer) {
+    tokenizer.resetSyntax();         
+    tokenizer.whitespaceChars(0, (' '-1));    
+    tokenizer.wordChars(' ','\u00FF');
+    tokenizer.whitespaceChars(',',',');
+    tokenizer.whitespaceChars(':',':');
+    //    tokenizer.whitespaceChars('.','.');
+    tokenizer.commentChar('|');
+    tokenizer.whitespaceChars('\t','\t');
+    tokenizer.quoteChar('"');
+    tokenizer.quoteChar('\'');
+    tokenizer.eolIsSignificant(true);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain &lt;filestem&gt;[.names | data]
+   */
+  public static void main (String [] args) {
+    runFileLoader(new C45Loader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/C45Saver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/C45Saver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/C45Saver.java	(revision 29)
@@ -0,0 +1,522 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    C45Saver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in the format used by the C4.5 algorithm.<br/>
+ * Therefore it outputs a names and a data file.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ * The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ * The output file</pre>
+ * 
+ * <pre> -c &lt;the class index&gt;
+ * The class index</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class C45Saver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -821428878384253377L;
+  
+  /** Constructor */  
+  public C45Saver(){
+  
+      resetOptions();
+  }
+   
+  /**
+   * Returns a string describing this Saver
+   * @return a description of the Saver suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Writes to a destination that is in the format used by the C4.5 algorithm.\nTherefore it outputs a names and a data file.";
+  }
+
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "C4.5 file format";
+  }
+
+  /**
+   * Resets the Saver 
+   */
+  public void resetOptions() {
+
+    super.resetOptions();
+    setFileExtension(".names");
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /** Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * @param inst the instance to save
+   * @throws IOException throws IOEXception if an instance cannot be saved incrementally.
+   */  
+    public void writeIncremental(Instance inst) throws IOException{
+  
+      int writeMode = getWriteMode();
+      Instances structure = getInstances();
+      PrintWriter outW = null;
+      
+      if(structure != null){
+          if(structure.classIndex() == -1){
+            structure.setClassIndex(structure.numAttributes()-1);
+            System.err.println("No class specified. Last attribute is used as class attribute.");
+          }
+          if(structure.attribute(structure.classIndex()).isNumeric())
+            throw new IOException("To save in C4.5 format the class attribute cannot be numeric.");
+      }
+      if(getRetrieval() == BATCH || getRetrieval() == NONE)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      if(retrieveFile() == null || getWriter() == null){
+          throw new IOException("C4.5 format requires two files. Therefore no output to standard out can be generated.\nPlease specifiy output files using the -o option.");
+      }
+      
+      
+      outW = new PrintWriter(getWriter());
+          
+      if(writeMode == WAIT){
+        if(structure == null){
+            setWriteMode(CANCEL);
+            if(inst != null)
+                System.err.println("Structure(Header Information) has to be set in advance");
+        }
+        else
+            setWriteMode(STRUCTURE_READY);
+        writeMode = getWriteMode();
+      }
+      if(writeMode == CANCEL){
+          if(outW != null)
+              outW.close();
+          cancel();
+      }
+      if(writeMode == STRUCTURE_READY){
+          setWriteMode(WRITE);
+          //write header: here names file
+          for (int i = 0; i < structure.attribute(structure.classIndex()).numValues(); i++) {
+            outW.write(structure.attribute(structure.classIndex()).value(i));
+            if (i < structure.attribute(structure.classIndex()).numValues()-1) {
+                outW.write(",");
+            } else {
+                outW.write(".\n");
+            }
+          }
+          for (int i = 0; i < structure.numAttributes(); i++) {
+            if (i != structure.classIndex()) {
+                outW.write(structure.attribute(i).name()+": ");
+                if (structure.attribute(i).isNumeric() || structure.attribute(i).isDate()) {
+                    outW.write("continuous.\n");
+                } else {
+                    Attribute temp = structure.attribute(i);
+                    for (int j = 0; j < temp.numValues(); j++) {
+                        outW.write(temp.value(j));
+                        if (j < temp.numValues()-1) {
+                            outW.write(",");
+                        } else {
+                            outW.write(".\n");
+                        }
+                    }
+                }
+            }
+          }
+          outW.flush();
+          outW.close();
+          
+          writeMode = getWriteMode();
+          
+          String out = retrieveFile().getAbsolutePath();
+          setFileExtension(".data");
+          out = out.substring(0, out.lastIndexOf('.')) + getFileExtension();
+          File namesFile = new File(out);
+          try{
+            setFile(namesFile);
+          } catch(Exception ex){
+            throw new IOException("Cannot create data file, only names file created.");
+          }
+          if(retrieveFile() == null || getWriter() == null){
+            throw new IOException("Cannot create data file, only names file created.");
+          }
+          outW = new PrintWriter(getWriter());
+      }
+      if(writeMode == WRITE){
+          if(structure == null)
+              throw new IOException("No instances information available.");
+          if(inst != null){
+            //write instance: here data file
+            for(int j = 0; j < inst.numAttributes(); j++){
+                if(j != structure.classIndex()){
+                    if (inst.isMissing(j)) {
+                        outW.write("?,");
+                    } else 
+                        if (structure.attribute(j).isNominal() || 
+                            structure.attribute(j).isString()) {
+                                outW.write(structure.attribute(j).value((int)inst.value(j))+",");
+                        } else {
+                                outW.write(""+inst.value(j)+",");
+                        }
+                    }
+            }
+            // write the class value
+            if (inst.isMissing(structure.classIndex())) {
+                outW.write("?");
+            } 
+            else {
+                outW.write(structure.attribute(structure.classIndex()).value((int)inst.value(structure.classIndex())));
+            }
+            outW.write("\n");
+            //flushes every 100 instances
+            m_incrementalCounter++;
+            if(m_incrementalCounter > 100){
+                m_incrementalCounter = 0;
+                outW.flush();
+            }
+          }
+          else{
+          //close
+              if(outW != null){
+                outW.flush();
+                outW.close();
+              }
+              setFileExtension(".names");
+              m_incrementalCounter = 0;
+              resetStructure();
+              outW = null;
+              resetWriter();
+          }
+      }
+  }
+
+  
+  /** 
+   * Writes a Batch of instances
+   * @throws IOException throws IOException if saving in batch mode is not possible
+   */
+  public void writeBatch() throws IOException {
+      
+      Instances instances = getInstances();
+      
+      if(instances == null)
+          throw new IOException("No instances to save");
+      if(instances.classIndex() == -1){
+          instances.setClassIndex(instances.numAttributes()-1);
+          System.err.println("No class specified. Last attribute is used as class attribute.");
+      }
+      if(instances.attribute(instances.classIndex()).isNumeric())
+          throw new IOException("To save in C4.5 format the class attribute cannot be numeric.");
+      if(getRetrieval() == INCREMENTAL)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      
+      setRetrieval(BATCH);
+      if(retrieveFile() == null || getWriter() == null){
+          throw new IOException("C4.5 format requires two files. Therefore no output to standard out can be generated.\nPlease specifiy output files using the -o option.");
+      }
+      setWriteMode(WRITE);
+      //print names file
+      setFileExtension(".names");
+      PrintWriter outW = new PrintWriter(getWriter());
+      for (int i = 0; i < instances.attribute(instances.classIndex()).numValues(); i++) {
+        outW.write(instances.attribute(instances.classIndex()).value(i));
+        if (i < instances.attribute(instances.classIndex()).numValues()-1) {
+            outW.write(",");
+        } else {
+            outW.write(".\n");
+	}
+      }
+      for (int i = 0; i < instances.numAttributes(); i++) {
+        if (i != instances.classIndex()) {
+            outW.write(instances.attribute(i).name()+": ");
+            if (instances.attribute(i).isNumeric() || instances.attribute(i).isDate()) {
+                outW.write("continuous.\n");
+            } else {
+                Attribute temp = instances.attribute(i);
+		for (int j = 0; j < temp.numValues(); j++) {
+                    outW.write(temp.value(j));
+		    if (j < temp.numValues()-1) {
+			outW.write(",");
+		    } else {
+			outW.write(".\n");
+		    }
+		 }
+             }
+        }
+      }
+      outW.flush();
+      outW.close();
+      
+      //print data file
+      String out = retrieveFile().getAbsolutePath();
+      setFileExtension(".data");
+      out = out.substring(0, out.lastIndexOf('.')) + getFileExtension();
+      File namesFile = new File(out);
+      try{
+        setFile(namesFile);
+      } catch(Exception ex){
+          throw new IOException("Cannot create data file, only names file created (Reason: " + ex.toString() + ").");
+      }
+      if(retrieveFile() == null || getWriter() == null){
+          throw new IOException("Cannot create data file, only names file created.");
+      }
+      outW = new PrintWriter(getWriter());
+      // print data file
+      for (int i = 0; i < instances.numInstances(); i++) {
+	Instance temp = instances.instance(i);
+        for(int j = 0; j < temp.numAttributes(); j++){
+            if(j != instances.classIndex()){
+                if (temp.isMissing(j)) {
+		      outW.write("?,");
+		    } else if (instances.attribute(j).isNominal() || 
+			       instances.attribute(j).isString()) {
+		      outW.write(instances.attribute(j).value((int)temp.value(j))+",");
+		    } else {
+		      outW.write(""+temp.value(j)+",");
+		    }
+            }
+        }
+        // write the class value
+        if (temp.isMissing(instances.classIndex())) {
+            outW.write("?");
+        } 
+        else {
+            outW.write(instances.attribute(instances.classIndex()).value((int)temp.value(instances.classIndex())));
+        }
+        outW.write("\n");
+      }
+      outW.flush();
+      outW.close();
+      setFileExtension(".names");
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+  }
+  
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+
+    result.addElement(new Option(
+	"The class index", 
+	"c", 1, "-c <the class index>"));
+    
+    return result.elements();
+  }
+
+ 
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   * The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   * The output file</pre>
+   * 
+   * <pre> -c &lt;the class index&gt;
+   * The class index</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String outputString = Utils.getOption('o', options);
+    String inputString = Utils.getOption('i', options);
+    String indexString = Utils.getOption('c', options);
+    
+    ArffLoader loader = new ArffLoader();
+    
+    resetOptions();
+
+    // parse index
+    int index = -1;
+    if (indexString.length() != 0){
+      if(indexString.equals("first"))
+	index = 0;
+      else {
+	if (indexString.equals("last"))
+	  index = -1;
+	else
+	  index = Integer.parseInt(indexString);
+      }
+    }
+    
+    if (inputString.length() != 0){
+      try {
+	File input = new File(inputString);
+	loader.setFile(input);
+	Instances inst = loader.getDataSet();
+	if (index == -1)
+	  inst.setClassIndex(inst.numAttributes() - 1);
+	else
+	  inst.setClassIndex(index);
+	setInstances(inst);
+      } catch(Exception ex){
+	throw new IOException("No data set loaded. Data set has to be arff format (Reason: " + ex.toString() + ").");
+      }
+    }
+    else
+      throw new IOException("No data set to save.");
+
+    if (outputString.length() != 0){ 
+      //add appropriate file extension
+      if (!outputString.endsWith(getFileExtension())){
+	if (outputString.lastIndexOf('.') != -1)
+	  outputString = (outputString.substring(0,outputString.lastIndexOf('.'))) + getFileExtension();
+	else
+	  outputString = outputString + getFileExtension();
+      }
+      try {
+	File output = new File(outputString);
+	setFile(output);
+      } catch(Exception ex){
+	throw new IOException("Cannot create output file.");
+      }
+    }
+
+    if (index == -1)
+      index = getInstances().numAttributes() - 1;
+    getInstances().setClassIndex(index);
+  }
+
+  /**
+   * Gets the current settings of the C45Saver object.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [10];
+    int current = 0;
+    if(retrieveFile() != null){
+        options[current++] = "-o"; options[current++] = "" + retrieveFile();
+    }
+    else{
+        options[current++] = "-o"; options[current++] = "";
+    }
+    if(getInstances() != null){
+        options[current++] = "-i"; options[current++] = "" + getInstances().relationName();
+        options[current++] = "-c"; options[current++] = "" + getInstances().classIndex();
+    }
+    else{
+        options[current++] = "-i"; options[current++] = "";
+        options[current++] = "-c"; options[current++] = "";
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new C45Saver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/CSVLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/CSVLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/CSVLoader.java	(revision 29)
@@ -0,0 +1,867 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CSVLoader.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StreamTokenizer;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in comma separated format (the default). One can also change the column separator from comma to tab or another character. Assumes that the first row in the file determines the number of and names of the attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;range&gt;
+ *  The range of attributes to force type to be NOMINAL.
+ *  'first' and 'last' are accepted as well.
+ *  Examples: "first-last", "1,4,5-27,50-last"
+ *  (default: -none-)</pre>
+ * 
+ * <pre> -S &lt;range&gt;
+ *  The range of attribute to force type to be STRING.
+ *  'first' and 'last' are accepted as well.
+ *  Examples: "first-last", "1,4,5-27,50-last"
+ *  (default: -none-)</pre>
+ * 
+ * <pre> -M &lt;str&gt;
+ *  The string representing a missing value.
+ *  (default: ?)</pre>
+ * 
+ * <pre> -F &lt;separator&gt;
+ *  The field separator to be used.
+ *  '\t' can be used as well.
+ *  (default: ',')</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 6098 $
+ * @see Loader
+ */
+public class CSVLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, OptionHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = 5607529739745491340L;
+  
+  /** the file extension. */
+  public static String FILE_EXTENSION = ".csv";
+
+  /**
+   * A list of hash tables for accumulating nominal values during parsing.
+   */
+  protected ArrayList<Hashtable<Object,Integer>> m_cumulativeStructure;
+
+  /**
+   * Holds instances accumulated so far.
+   */
+  protected ArrayList<ArrayList<Object>> m_cumulativeInstances;
+  
+  /** The reader for the data. */         
+  protected transient BufferedReader m_sourceReader;
+  
+  /** Tokenizer for the data. */
+  protected transient StreamTokenizer m_st;
+  
+  /** The range of attributes to force to type nominal. */
+  protected Range m_NominalAttributes = new Range();
+  
+  /** The range of attributes to force to type string. */
+  protected Range m_StringAttributes = new Range();
+  
+  /** The placeholder for missing values. */
+  protected String m_MissingValue = "?";
+  
+  /** the field separator. */
+  protected String m_FieldSeparator = ",";
+  
+  /** whether the first row has been read. */
+  protected boolean m_FirstCheck;
+  
+  /**
+   * default constructor.
+   */
+  public CSVLoader() {
+    // No instances retrieved yet
+    setRetrieval(NONE);
+  }
+
+  /**
+   * Get the file extension used for arff files.
+   *
+   * @return the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "CSV data files";
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+
+  /**
+   * Returns a string describing this attribute evaluator.
+   * 
+   * @return a description of the evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads a source that is in comma separated format (the default). "
+      + "One can also change the column separator from comma to tab or " 
+      + "another character. "
+      + "Assumes that the first row in the file determines the number of "
+      + "and names of the attributes.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.add(new Option(
+        "\tThe range of attributes to force type to be NOMINAL.\n"
+        + "\t'first' and 'last' are accepted as well.\n"
+        + "\tExamples: \"first-last\", \"1,4,5-27,50-last\"\n"
+        + "\t(default: -none-)",
+        "N", 1, "-N <range>"));
+    
+    result.add(new Option(
+        "\tThe range of attribute to force type to be STRING.\n"
+        + "\t'first' and 'last' are accepted as well.\n"
+        + "\tExamples: \"first-last\", \"1,4,5-27,50-last\"\n"
+        + "\t(default: -none-)",
+        "S", 1, "-S <range>"));
+    
+    result.add(new Option(
+        "\tThe string representing a missing value.\n"
+        + "\t(default: ?)",
+        "M", 1, "-M <str>"));
+    
+    result.addElement(new Option(
+        "\tThe field separator to be used.\n"
+        + "\t'\\t' can be used as well.\n"
+        + "\t(default: ',')",
+        "F", 1, "-F <separator>"));
+      
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;range&gt;
+   *  The range of attributes to force type to be NOMINAL.
+   *  'first' and 'last' are accepted as well.
+   *  Examples: "first-last", "1,4,5-27,50-last"
+   *  (default: -none-)</pre>
+   * 
+   * <pre> -S &lt;range&gt;
+   *  The range of attribute to force type to be STRING.
+   *  'first' and 'last' are accepted as well.
+   *  Examples: "first-last", "1,4,5-27,50-last"
+   *  (default: -none-)</pre>
+   * 
+   * <pre> -M &lt;str&gt;
+   *  The string representing a missing value.
+   *  (default: ?)</pre>
+   * 
+   * <pre> -F &lt;separator&gt;
+   *  The field separator to be used.
+   *  '\t' can be used as well.
+   *  (default: ',')</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNominalAttributes(tmpStr);
+    else
+      setNominalAttributes("");
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setStringAttributes(tmpStr);
+    else
+      setStringAttributes("");
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setMissingValue(tmpStr);
+    else
+      setMissingValue("?");
+    
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setFieldSeparator(tmpStr);
+    else
+      setFieldSeparator(",");
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result  = new Vector<String>();
+
+    if (getNominalAttributes().length() > 0) {
+      result.add("-N");
+      result.add(getNominalAttributes());
+    }
+    
+    if (getStringAttributes().length() > 0) {
+      result.add("-S");
+      result.add(getStringAttributes());
+    }
+
+    result.add("-M");
+    result.add(getMissingValue());
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Sets the attribute range to be forced to type nominal.
+   * 
+   * @param value	the range
+   */
+  public void setNominalAttributes(String value) {
+    m_NominalAttributes.setRanges(value);
+  }
+  
+  /**
+   * Returns the current attribute range to be forced to type nominal.
+   * 
+   * @return		the range
+   */
+  public String getNominalAttributes() {
+    return m_NominalAttributes.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String nominalAttributesTipText() {
+    return 
+        "The range of attributes to force to be of type NOMINAL, example "
+      + "ranges: 'first-last', '1,4,7-14,50-last'.";
+  }
+  
+  /**
+   * Sets the attribute range to be forced to type string.
+   * 
+   * @param value	the range
+   */
+  public void setStringAttributes(String value) {
+    m_StringAttributes.setRanges(value);
+  }
+  
+  /**
+   * Returns the current attribute range to be forced to type string.
+   * 
+   * @return		the range
+   */
+  public String getStringAttributes() {
+    return m_StringAttributes.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String stringAttributesTipText() {
+    return 
+        "The range of attributes to force to be of type STRING, example "
+      + "ranges: 'first-last', '1,4,7-14,50-last'.";
+  }
+  
+  /**
+   * Sets the placeholder for missing values.
+   * 
+   * @param value	the placeholder
+   */
+  public void setMissingValue(String value) {
+    m_MissingValue = value;
+  }
+  
+  /**
+   * Returns the current placeholder for missing values.
+   * 
+   * @return		the placeholder
+   */
+  public String getMissingValue() {
+    return m_MissingValue;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String missingValueTipText() {
+    return "The placeholder for missing values, default is '?'.";
+  }
+  
+  /**
+   * Sets the character used as column separator.
+   * 
+   * @param value	the character to use
+   */
+  public void setFieldSeparator(String value) {
+    m_FieldSeparator = Utils.unbackQuoteChars(value);
+    if (m_FieldSeparator.length() != 1) {
+      m_FieldSeparator = ",";
+      System.err.println(
+	  "Field separator can only be a single character (exception being '\t'), "
+	  + "defaulting back to '" + m_FieldSeparator + "'!");
+    }
+  }
+  
+  /**
+   * Returns the character used as column separator.
+   * 
+   * @return		the character to use
+   */
+  public String getFieldSeparator() {
+    return Utils.backQuoteChars(m_FieldSeparator);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String fieldSeparatorTipText() {
+    return "The character to use as separator for the columns/fields (use '\\t' for TAB).";
+  }
+  
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied Stream object.
+   *
+   * @param input the input stream
+   * @exception IOException if an error occurs
+   */
+  @Override
+  public void setSource(InputStream input) throws IOException {    
+    m_structure    = null;
+    m_sourceFile   = null;
+    m_File         = null;
+    m_FirstCheck     = true;
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(input));
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file the source file.
+   * @exception IOException if an error occurs
+   */
+  @Override
+  public void setSource(File file) throws IOException {
+    super.setSource(file);
+  }
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @exception IOException if an error occurs
+   */
+  @Override
+  public Instances getStructure() throws IOException {
+    if ((m_sourceFile == null) && (m_sourceReader == null)) {
+      throw new IOException("No source has been specified");
+    }
+
+    if (m_structure == null) {
+      try {
+	m_st = new StreamTokenizer(m_sourceReader);
+	initTokenizer(m_st);
+	readStructure(m_st);
+      } catch (FileNotFoundException ex) {
+      }
+    }
+    
+    return m_structure;
+  }
+
+  /**
+   * reads the structure.
+   * 
+   * @param st the stream tokenizer to read from
+   * @throws IOException if reading fails
+   */
+  private void readStructure(StreamTokenizer st) throws IOException {
+    readHeader(st);
+  }
+
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @exception IOException if there is no source or parsing fails
+   */
+  @Override
+  public Instances getDataSet() throws IOException {
+    if ((m_sourceFile == null) && (m_sourceReader == null)) {
+      throw new IOException("No source has been specified");
+    }
+    
+    if (m_structure == null) {
+      getStructure();
+    }
+    
+    if (m_st == null) {
+      m_st = new StreamTokenizer(m_sourceReader);
+      initTokenizer(m_st);      
+    }
+        
+    m_st.ordinaryChar(m_FieldSeparator.charAt(0));
+    
+    m_cumulativeStructure = new ArrayList<Hashtable<Object,Integer>>(m_structure.numAttributes());
+    for (int i = 0; i < m_structure.numAttributes(); i++) {
+      m_cumulativeStructure.add(new Hashtable<Object,Integer>());
+    }
+    
+    m_cumulativeInstances = new ArrayList<ArrayList<Object>>();
+    ArrayList<Object> current;
+    while ((current = getInstance(m_st)) != null) {
+      m_cumulativeInstances.add(current);
+    }
+
+    ArrayList<Attribute> atts = new ArrayList<Attribute>(m_structure.numAttributes());
+    for (int i = 0; i < m_structure.numAttributes(); i++) {
+      String attname = m_structure.attribute(i).name();
+      Hashtable<Object,Integer> tempHash = m_cumulativeStructure.get(i);
+      if (tempHash.size() == 0) {
+	atts.add(new Attribute(attname));
+      } else {
+	if (m_StringAttributes.isInRange(i)) {
+	  atts.add(new Attribute(attname, (ArrayList<String>) null));
+	}
+	else {
+	  ArrayList<String> values = new ArrayList<String>(tempHash.size());
+	  // add dummy objects in order to make the ArrayList's size == capacity
+	  for (int z = 0; z < tempHash.size(); z++) {
+	    values.add("dummy");
+	  }
+	  Enumeration e = tempHash.keys();
+	  while (e.hasMoreElements()) {
+	    Object ob = e.nextElement();
+	    //	  if (ob instanceof Double) {
+	    int index = ((Integer)tempHash.get(ob)).intValue();
+	    String s = ob.toString();
+	    if (s.startsWith("'") || s.startsWith("\""))
+	      s = s.substring(1, s.length() - 1);
+	    values.set(index, new String(s));
+	    //	  }
+	  }
+	  atts.add(new Attribute(attname, values));
+	}
+      }
+    }
+
+    // make the instances
+    String relationName;
+    if (m_sourceFile != null)
+      relationName = (m_sourceFile.getName()).replaceAll("\\.[cC][sS][vV]$","");
+    else
+      relationName = "stream";
+    Instances dataSet = new Instances(relationName, 
+				      atts, 
+				      m_cumulativeInstances.size());
+
+    for (int i = 0; i < m_cumulativeInstances.size(); i++) {
+      current = m_cumulativeInstances.get(i);
+      double [] vals = new double[dataSet.numAttributes()];
+      for (int j = 0; j < current.size(); j++) {
+	Object cval = current.get(j);
+	if (cval instanceof String) {
+	  if (((String)cval).compareTo(m_MissingValue) == 0) {
+	    vals[j] = Utils.missingValue();
+	  } else {
+	    if (dataSet.attribute(j).isString()) {
+	      vals[j] = dataSet.attribute(j).addStringValue((String) cval);
+	    }
+	    else if (dataSet.attribute(j).isNominal()) {
+	      // find correct index
+	      Hashtable<Object,Integer> lookup = m_cumulativeStructure.get(j);
+	      int index = ((Integer)lookup.get(cval)).intValue();
+	      vals[j] = index;
+	    }
+	    else {
+	      throw new IllegalStateException("Wrong attribute type at position " + (i+1) + "!!!");
+	    }
+	  }
+	} else if (dataSet.attribute(j).isNominal()) {
+	  // find correct index
+	  Hashtable<Object,Integer> lookup = m_cumulativeStructure.get(j);
+	  int index = ((Integer)lookup.get(cval)).intValue();
+	  vals[j] = index;
+	} else if (dataSet.attribute(j).isString()) {
+	  vals[j] = dataSet.attribute(j).addStringValue("" + cval);
+	} else {
+	  vals[j] = ((Double)cval).doubleValue();
+	}
+      }
+      dataSet.add(new DenseInstance(1.0, vals));
+    }
+    m_structure = new Instances(dataSet, 0);
+    setRetrieval(BATCH);
+    m_cumulativeStructure = null; // conserve memory
+    
+    // close the stream
+    m_sourceReader.close();
+    
+    return dataSet;
+  }
+
+  /**
+   * CSVLoader is unable to process a data set incrementally.
+   *
+   * @param structure ignored
+   * @return never returns without throwing an exception
+   * @exception IOException always. CSVLoader is unable to process a data
+   * set incrementally.
+   */
+  @Override
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("CSVLoader can't read data sets incrementally.");
+  }
+
+  /**
+   * Attempts to parse a line of the data set.
+   *
+   * @param tokenizer the tokenizer
+   * @return a ArrayList containg String and Double objects representing
+   * the values of the instance.
+   * @exception IOException if an error occurs
+   *
+   * <pre><jml>
+   *    private_normal_behavior
+   *      requires: tokenizer != null;
+   *      ensures: \result  != null;
+   *  also
+   *    private_exceptional_behavior
+   *      requires: tokenizer == null
+   *                || (* unsucessful parse *);
+   *      signals: (IOException);
+   * </jml></pre>
+   */
+  private ArrayList<Object> getInstance(StreamTokenizer tokenizer) 
+    throws IOException {
+
+    ArrayList<Object> current = new ArrayList<Object>();
+
+    // Check if end of file reached.
+    ConverterUtils.getFirstToken(tokenizer);
+    if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
+      return null;
+    }
+    boolean first = true;
+    boolean wasSep;
+
+    while (tokenizer.ttype != StreamTokenizer.TT_EOL &&
+	   tokenizer.ttype != StreamTokenizer.TT_EOF) {
+      
+      // Get next token
+      if (!first) {
+	ConverterUtils.getToken(tokenizer);
+      }
+
+      if (tokenizer.ttype == m_FieldSeparator.charAt(0) || 
+	  tokenizer.ttype == StreamTokenizer.TT_EOL) {
+	current.add(m_MissingValue);
+	wasSep = true;
+      } else {
+	wasSep = false;
+	if (tokenizer.sval.equals(m_MissingValue)) {
+	  current.add(new String(m_MissingValue));
+	}
+	else {
+	  // try to parse as a number
+	  try {
+	    double val = Double.valueOf(tokenizer.sval).doubleValue();
+	    current.add(new Double(val));
+	  } catch (NumberFormatException e) {
+	    // otherwise assume its an enumerated value
+	    current.add(new String(tokenizer.sval));
+	  }
+	}
+      }
+      
+      if (!wasSep) {
+	ConverterUtils.getToken(tokenizer);
+      }
+      first = false;
+    }
+    
+    // check number of values read
+    if (current.size() != m_structure.numAttributes()) {
+      ConverterUtils.errms(tokenizer, 
+			   "wrong number of values. Read "+current.size()
+			   +", expected "+m_structure.numAttributes());
+    }
+
+    // check for structure update
+    try {
+      checkStructure(current);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    return current;
+  }
+
+  /**
+   * Checks the current instance against what is known about the structure
+   * of the data set so far. If there is a nominal value for an attribute
+   * that was beleived to be numeric then all previously seen values for this
+   * attribute are stored in a Hashtable.
+   *
+   * @param current a <code>ArrayList</code> value
+   * @exception Exception if an error occurs
+   *
+   * <pre><jml>
+   *    private_normal_behavior
+   *      requires: current != null;
+   *  also
+   *    private_exceptional_behavior
+   *      requires: current == null
+   *                || (* unrecognized object type in current *);
+   *      signals: (Exception);
+   * </jml></pre>
+   */
+  private void checkStructure(ArrayList<Object> current) throws Exception {
+    if (current == null) {
+      throw new Exception("current shouldn't be null in checkStructure");
+    }
+
+    // initialize ranges, if necessary
+    if (m_FirstCheck) {
+      m_NominalAttributes.setUpper(current.size() - 1);
+      m_StringAttributes.setUpper(current.size() - 1);
+      m_FirstCheck = false;
+    }
+    
+    for (int i = 0; i < current.size(); i++) {
+      Object ob = current.get(i);
+      if ((ob instanceof String) || (m_NominalAttributes.isInRange(i)) || (m_StringAttributes.isInRange(i))) {
+	if (ob.toString().compareTo(m_MissingValue) == 0) {
+	  // do nothing
+	} else {
+	  Hashtable<Object,Integer> tempHash = m_cumulativeStructure.get(i);
+	  if (!tempHash.containsKey(ob)) {
+	    // may have found a nominal value in what was previously thought to
+	    // be a numeric variable.
+	    if (tempHash.size() == 0) {
+	      for (int j = 0; j < m_cumulativeInstances.size(); j++) {
+		ArrayList tempUpdate = 
+		  ((ArrayList)m_cumulativeInstances.get(j));
+		Object tempO = tempUpdate.get(i);
+		if (tempO instanceof String) {
+		  // must have been a missing value
+		} else {
+		  if (!tempHash.containsKey(tempO)) {
+		    tempHash.put(new Double(((Double)tempO).doubleValue()), 
+				 new Integer(tempHash.size()));
+		  }
+		}
+	      }
+	    }
+	    int newIndex = tempHash.size();
+	    tempHash.put(ob, new Integer(newIndex));
+	  }
+	}
+      } else if (ob instanceof Double) {
+	Hashtable<Object,Integer> tempHash = m_cumulativeStructure.get(i);
+	if (tempHash.size() != 0) {
+	  if (!tempHash.containsKey(ob)) {
+	    int newIndex = tempHash.size();
+	    tempHash.put(new Double(((Double)ob).doubleValue()), 
+				    new Integer(newIndex));
+	  }
+	}
+      } else {
+	throw new Exception("Wrong object type in checkStructure!");
+      }
+    }
+  }
+
+  /**
+   * Assumes the first line of the file contains the attribute names.
+   * Assumes all attributes are real (Reading the full data set with
+   * getDataSet will establish the true structure).
+   *
+   * @param tokenizer a <code>StreamTokenizer</code> value
+   * @exception IOException if an error occurs
+   *
+   * <pre><jml>
+   *    private_normal_behavior
+   *      requires: tokenizer != null;
+   *      modifiable: m_structure;
+   *      ensures: m_structure != null;
+   *  also
+   *    private_exceptional_behavior
+   *      requires: tokenizer == null
+   *                || (* unsucessful parse *);
+   *      signals: (IOException);
+   * </jml></pre>
+   */
+  private void readHeader(StreamTokenizer tokenizer) throws IOException {
+   
+    ArrayList<Attribute> attribNames = new ArrayList<Attribute>();
+    ConverterUtils.getFirstToken(tokenizer);
+    if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
+      ConverterUtils.errms(tokenizer,"premature end of file");
+    }
+
+    while (tokenizer.ttype != StreamTokenizer.TT_EOL) {
+      attribNames.add(new Attribute(tokenizer.sval));
+      ConverterUtils.getToken(tokenizer);
+    }
+    String relationName;
+    if (m_sourceFile != null)
+      relationName = (m_sourceFile.getName()).replaceAll("\\.[cC][sS][vV]$","");
+    else
+      relationName = "stream";
+    m_structure = new Instances(relationName, attribNames, 0);
+  }
+
+  /**
+   * Initializes the stream tokenizer.
+   *
+   * @param tokenizer the tokenizer to initialize
+   */
+  private void initTokenizer(StreamTokenizer tokenizer) {
+    tokenizer.resetSyntax();         
+    tokenizer.whitespaceChars(0, (' '-1));    
+    tokenizer.wordChars(' ','\u00FF');
+    tokenizer.whitespaceChars(m_FieldSeparator.charAt(0),m_FieldSeparator.charAt(0));
+    tokenizer.commentChar('%');
+    tokenizer.quoteChar('"');
+    tokenizer.quoteChar('\'');
+    tokenizer.eolIsSignificant(true);
+  }
+  
+  /**
+   * Resets the Loader ready to read a new data set or the
+   * same data set again.
+   * 
+   * @throws IOException if something goes wrong
+   */
+  @Override
+  public void reset() throws IOException {
+    m_structure = null;
+    m_st = null;
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6098 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the name of an input file.
+   */
+  public static void main(String [] args) {
+    runFileLoader(new CSVLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/CSVSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/CSVSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/CSVSaver.java	(revision 29)
@@ -0,0 +1,503 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CSVSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in CSV (comma-separated values) format. The column separator can be chosen (default is ',') as well as the value representing missing values (default is '?').
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;separator&gt;
+ *  The field separator to be used.
+ *  '\t' can be used as well.
+ *  (default: ',')</pre>
+ * 
+ * <pre> -M &lt;str&gt;
+ *  The string representing a missing value.
+ *  (default: ?)</pre>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 6098 $
+ * @see Saver
+ */
+public class CSVSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter, FileSourcedConverter {
+
+  /** for serialization. */
+  static final long serialVersionUID = 476636654410701807L;
+  
+  /** the field separator. */
+  protected String m_FieldSeparator = ",";
+  
+  /** The placeholder for missing values. */
+  protected String m_MissingValue = "?";
+  
+  /** 
+   * Constructor.
+   */  
+  public CSVSaver(){
+    resetOptions();
+  }
+   
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Writes to a destination that is in CSV (comma-separated values) format. "
+      + "The column separator can be chosen (default is ',') as well as the value "
+      + "representing missing values (default is '?').";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tThe field separator to be used.\n"
+        + "\t'\\t' can be used as well.\n"
+        + "\t(default: ',')",
+        "F", 1, "-F <separator>"));
+    
+    result.addElement(new Option(
+        "\tThe string representing a missing value.\n"
+        + "\t(default: ?)",
+        "M", 1, "-M <str>"));
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+      
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;separator&gt;
+   *  The field separator to be used.
+   *  '\t' can be used as well.
+   *  (default: ',')</pre>
+   * 
+   * <pre> -M &lt;str&gt;
+   *  The string representing a missing value.
+   *  (default: ?)</pre>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setFieldSeparator(tmpStr);
+    else
+      setFieldSeparator(",");
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setMissingValue(tmpStr);
+    else
+      setMissingValue("?");
+    
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result  = new Vector<String>();
+
+    result.add("-F");
+    result.add(getFieldSeparator());
+
+    result.add("-M");
+    result.add(getMissingValue());
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Sets the character used as column separator.
+   * 
+   * @param value	the character to use
+   */
+  public void setFieldSeparator(String value) {
+    m_FieldSeparator = Utils.unbackQuoteChars(value);
+    if (m_FieldSeparator.length() != 1) {
+      m_FieldSeparator = ",";
+      System.err.println(
+	  "Field separator can only be a single character (exception being '\t'), "
+	  + "defaulting back to '" + m_FieldSeparator + "'!");
+    }
+  }
+  
+  /**
+   * Returns the character used as column separator.
+   * 
+   * @return		the character to use
+   */
+  public String getFieldSeparator() {
+    return Utils.backQuoteChars(m_FieldSeparator);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String fieldSeparatorTipText() {
+    return "The character to use as separator for the columns/fields (use '\\t' for TAB).";
+  }
+  
+  /**
+   * Sets the placeholder for missing values.
+   * 
+   * @param value	the placeholder
+   */
+  public void setMissingValue(String value) {
+    m_MissingValue = value;
+  }
+  
+  /**
+   * Returns the current placeholder for missing values.
+   * 
+   * @return		the placeholder
+   */
+  public String getMissingValue() {
+    return m_MissingValue;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String missingValueTipText() {
+    return "The placeholder for missing values, default is '?'.";
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "CSV file: comma separated files";
+  }
+
+  /**
+   * Resets the Saver.
+   */
+  public void resetOptions() {
+    super.resetOptions();
+    
+    setFileExtension(".csv");
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.STRING_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /** Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * @param inst the instance to save
+   * @throws IOException throws IOEXception if an instance cannot be saved incrementally.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+  
+      int writeMode = getWriteMode();
+      Instances structure = getInstances();
+      PrintWriter outW = null;
+      
+      if(getRetrieval() == BATCH || getRetrieval() == NONE)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      if(getWriter() != null)
+          outW = new PrintWriter(getWriter());
+          
+      if(writeMode == WAIT){
+        if(structure == null){
+            setWriteMode(CANCEL);
+            if(inst != null)
+                System.err.println("Structure(Header Information) has to be set in advance");
+        }
+        else
+            setWriteMode(STRUCTURE_READY);
+        writeMode = getWriteMode();
+      }
+      if(writeMode == CANCEL){
+          if(outW != null)
+              outW.close();
+          cancel();
+      }
+      if(writeMode == STRUCTURE_READY){
+          setWriteMode(WRITE);
+          //write header
+          if(retrieveFile() == null || outW == null){
+              // print out attribute names as first row
+              for (int i = 0; i < structure.numAttributes(); i++) {
+                System.out.print(structure.attribute(i).name());
+                if (i < structure.numAttributes()-1) {
+                    System.out.print(m_FieldSeparator);
+                } else {
+                    System.out.println();
+                }
+              } 
+          }
+          else{
+              for (int i = 0; i < structure.numAttributes(); i++) {
+                outW.print(structure.attribute(i).name());
+                if (i < structure.numAttributes()-1) {
+                    outW.print(m_FieldSeparator);
+                } else {
+                    outW.println();
+                }
+              }
+              outW.flush();
+          }
+          writeMode = getWriteMode();
+      }
+      if(writeMode == WRITE){
+          if(structure == null)
+              throw new IOException("No instances information available.");
+          if(inst != null){
+          //write instance 
+              if(retrieveFile() == null || outW == null)
+                System.out.println(inst);
+              else{
+                outW.println(instanceToString(inst));
+                //flushes every 100 instances
+                m_incrementalCounter++;
+                if(m_incrementalCounter > 100){
+                    m_incrementalCounter = 0;
+                    outW.flush();
+                }
+              }
+          }
+          else{
+          //close
+              if(outW != null){
+                outW.flush();
+                outW.close();
+              }
+              m_incrementalCounter = 0;
+              resetStructure();
+              outW = null;
+              resetWriter();
+          }
+      }
+  }  
+
+  /**
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException throws IOException if saving in batch mode is not possible
+   */
+  public void writeBatch() throws IOException {
+  
+      if(getInstances() == null)
+          throw new IOException("No instances to save");
+      if(getRetrieval() == INCREMENTAL)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      setRetrieval(BATCH);
+      setWriteMode(WRITE);
+      if(retrieveFile() == null && getWriter() == null){
+          // print out attribute names as first row
+          for (int i = 0; i < getInstances().numAttributes(); i++) {
+            System.out.print(getInstances().attribute(i).name());
+            if (i < getInstances().numAttributes()-1) {
+                System.out.print(m_FieldSeparator);
+            } else {
+            System.out.println();
+            }
+        }
+        for (int i = 0; i < getInstances().numInstances(); i++) {
+            System.out.println(getInstances().instance(i));
+        }
+        setWriteMode(WAIT);
+        return;
+      }
+      PrintWriter outW = new PrintWriter(getWriter());
+      // print out attribute names as first row
+      for (int i = 0; i < getInstances().numAttributes(); i++) {
+	outW.print(getInstances().attribute(i).name());
+	if (i < getInstances().numAttributes()-1) {
+	  outW.print(m_FieldSeparator);
+	} else {
+	  outW.println();
+	}
+      }
+      for (int i = 0; i < getInstances().numInstances(); i++) {
+	outW.println(instanceToString((getInstances().instance(i))));
+      }
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+  }
+
+  /**
+   * turns an instance into a string. takes care of sparse instances as well.
+   *
+   * @param inst the instance to turn into a string
+   * @return the generated string
+   */
+  protected String instanceToString(Instance inst) {
+    StringBuffer	result;
+    Instance 		outInst;
+    int			i;
+    String		field;
+
+    result = new StringBuffer();
+    
+    if (inst instanceof SparseInstance) {
+      outInst = new DenseInstance(inst.weight(), inst.toDoubleArray());
+      outInst.setDataset(inst.dataset());
+    }
+    else {
+      outInst = inst;
+    }
+    
+    for (i = 0; i < outInst.numAttributes(); i++) {
+      if (i > 0)
+	result.append(m_FieldSeparator);
+      
+      if (outInst.isMissing(i))
+	field = m_MissingValue;
+      else
+	field = outInst.toString(i);
+      
+      // make sure that custom field separators, like ";" get quoted correctly
+      // as well
+      if ((field.indexOf(m_FieldSeparator) > -1) && !field.startsWith("'") && !field.endsWith("'"))
+	field = "'" + field + "'";
+      
+      result.append(field);
+    }
+
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6098 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new CSVSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/ConverterUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/ConverterUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/ConverterUtils.java	(revision 29)
@@ -0,0 +1,1171 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConverterUtils.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.ClassDiscovery;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.gui.GenericObjectEditor;
+import weka.gui.GenericPropertiesCreator;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * Utility routines for the converter package.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6155 $
+ * @see Serializable
+ */
+public class ConverterUtils
+  implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = -2460855349276148760L;
+
+  /**
+   * Helper class for loading data from files and URLs. Via the ConverterUtils
+   * class it determines which converter to use for loading the data into 
+   * memory. If the chosen converter is an incremental one, then the data
+   * will be loaded incrementally, otherwise as batch. In both cases the 
+   * same interface will be used (<code>hasMoreElements</code>, 
+   * <code>nextElement</code>). Before the
+   * data can be read again, one has to call the <code>reset</code> method.
+   * The data source can also be initialized with an Instances object, in 
+   * order to provide a unified interface to files and already loaded datasets.
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 6155 $
+   * @see #hasMoreElements(Instances)
+   * @see #nextElement(Instances)
+   * @see #reset()
+   * @see DataSink
+   */
+  public static class DataSource
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -613122395928757332L;
+
+    /** the file to load. */
+    protected File m_File;
+    
+    /** the URL to load. */
+    protected URL m_URL;
+    
+    /** the loader.*/
+    protected Loader m_Loader;
+    
+    /** whether the loader is incremental. */
+    protected boolean m_Incremental;
+    
+    /** the instance counter for the batch case. */
+    protected int m_BatchCounter;
+
+    /** the last internally read instance. */
+    protected Instance m_IncrementalBuffer;
+    
+    /** the batch buffer. */
+    protected Instances m_BatchBuffer;
+    
+    /**
+     * Tries to load the data from the file. Can be either a regular file or
+     * a web location (http://, https://, ftp:// or file://).
+     * 
+     * @param location		the name of the file to load
+     * @throws Exception	if initialization fails
+     */
+    public DataSource(String location) throws Exception {
+      super();
+      
+      // file or URL?
+      if (    location.startsWith("http://")
+	   || location.startsWith("https://")
+	   || location.startsWith("ftp://")
+	   || location.startsWith("file://") )
+	m_URL = new URL(location);
+      else
+	m_File = new File(location);
+      
+      // quick check: is it ARFF?
+      if (isArff(location)) {
+	m_Loader = new ArffLoader();
+      }
+      else {
+	if (m_File != null)
+	  m_Loader = ConverterUtils.getLoaderForFile(location);
+	else
+	  m_Loader = ConverterUtils.getURLLoaderForFile(location);
+	
+	// do we have a converter?
+	if (m_Loader == null)
+	  throw new IllegalArgumentException("No suitable converter found for '" + location + "'!");
+      }
+      
+      // incremental loader?
+      m_Incremental = (m_Loader instanceof IncrementalConverter);
+      
+      reset();
+    }
+    
+    /**
+     * Initializes the datasource with the given dataset.
+     * 
+     * @param inst		the dataset to use
+     */
+    public DataSource(Instances inst) {
+      super();
+      
+      m_BatchBuffer = inst;
+      m_Loader      = null;
+      m_File        = null;
+      m_URL         = null;
+      m_Incremental = false;
+    }
+    
+    /**
+     * Initializes the datasource with the given Loader.
+     * 
+     * @param loader		the Loader to use
+     */
+    public DataSource(Loader loader) {
+      super();
+
+      m_BatchBuffer = null;
+      m_Loader      = loader;
+      m_File        = null;
+      m_URL         = null;
+      m_Incremental = (m_Loader instanceof IncrementalConverter);
+      
+      initBatchBuffer();
+    }
+
+    /**
+     * Initializes the datasource with the given input stream. This stream
+     * is always interpreted as ARFF.
+     * 
+     * @param stream		the stream to use
+     */
+    public DataSource(InputStream stream) {
+      super();
+      
+      m_BatchBuffer = null;
+      m_Loader      = new ArffLoader();
+      try {
+	m_Loader.setSource(stream);
+      }
+      catch (Exception e) {
+	m_Loader = null;
+      }
+      m_File        = null;
+      m_URL         = null;
+      m_Incremental = (m_Loader instanceof IncrementalConverter);
+      
+      initBatchBuffer();
+    }
+
+    /**
+     * initializes the batch buffer if necessary, i.e., for non-incremental
+     * loaders.
+     */
+    protected void initBatchBuffer() {
+      try {
+	if (!isIncremental())
+	  m_BatchBuffer = m_Loader.getDataSet();
+	else
+	  m_BatchBuffer = null;
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+    
+    /**
+     * returns whether the extension of the location is likely to be of ARFF
+     * format, i.e., ending in ".arff" or ".arff.gz" (case-insensitive).
+     * 
+     * @param location		the file location to check
+     * @return			true if the location seems to be of ARFF format
+     */
+    public static boolean isArff(String location) {
+      if (    location.toLowerCase().endsWith(ArffLoader.FILE_EXTENSION.toLowerCase())
+	   || location.toLowerCase().endsWith(ArffLoader.FILE_EXTENSION_COMPRESSED.toLowerCase()) )
+	return true;
+      else
+	return false;
+    }
+    
+    /**
+     * returns whether the loader is an incremental one.
+     * 
+     * @return		true if the loader is a true incremental one
+     */
+    public boolean isIncremental() {
+      return m_Incremental;
+    }
+    
+    /**
+     * returns the determined loader, null if the DataSource was initialized
+     * with data alone and not a file/URL.
+     * 
+     * @return		the loader used for retrieving the data
+     */
+    public Loader getLoader() {
+      return m_Loader;
+    }
+    
+    /**
+     * returns the full dataset, can be null in case of an error.
+     * 
+     * @return			the full dataset
+     * @throws Exception 	if resetting of loader fails
+     */
+    public Instances getDataSet() throws Exception {
+      Instances		result;
+      
+      result = null;
+      
+      // reset the loader
+      reset();
+      
+      try {
+	if (m_BatchBuffer == null)
+	  result = m_Loader.getDataSet();
+	else
+	  result = m_BatchBuffer;
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	result = null;
+      }
+
+      return result;
+    }
+    
+    /**
+     * returns the full dataset with the specified class index set, 
+     * can be null in case of an error.
+     * 
+     * @param classIndex	the class index for the dataset
+     * @return			the full dataset
+     * @throws Exception 	if resetting of loader fails
+     */
+    public Instances getDataSet(int classIndex) throws Exception {
+      Instances		result;
+      
+      result = getDataSet();
+      if (result != null)
+	result.setClassIndex(classIndex);
+      
+      return result;
+    }
+    
+    /**
+     * resets the loader.
+     * 
+     * @throws Exception	if resetting fails
+     */
+    public void reset() throws Exception {
+      if (m_File != null)
+	((AbstractFileLoader) m_Loader).setFile(m_File);
+      else if (m_URL != null)
+	((URLSourcedLoader) m_Loader).setURL(m_URL.toString());
+      else if (m_Loader != null)
+	// m_Loader.reset(); we can't really reset the loader if the source has been set as an input stream
+      
+      m_BatchCounter      = 0;
+      m_IncrementalBuffer = null;
+
+      if (m_Loader != null) {
+	if (!isIncremental())
+	  m_BatchBuffer = m_Loader.getDataSet();
+	else
+	  m_BatchBuffer = null;
+      }
+    }
+
+    /**
+     * returns the structure of the data.
+     * 
+     * @return			the structure of the data
+     * @throws Exception	if something goes wrong
+     */
+    public Instances getStructure() throws Exception {
+      if (m_BatchBuffer == null)
+	return m_Loader.getStructure();
+      else
+	return new Instances(m_BatchBuffer, 0);
+    }
+
+    /**
+     * returns the structure of the data, with the defined class index.
+     * 
+     * @param classIndex	the class index for the dataset
+     * @return			the structure of the data
+     * @throws Exception	if something goes wrong
+     */
+    public Instances getStructure(int classIndex) throws Exception {
+      Instances		result;
+      
+      result = getStructure();
+      if (result != null)
+	result.setClassIndex(classIndex);
+      
+      return result;
+    }
+    
+    /**
+     * returns whether there are more Instance objects in the data.
+     * 
+     * @param structure	the structure of the dataset
+     * @return		true if there are more Instance objects 
+     * 			available
+     * @see		#nextElement(Instances)
+     */
+    public boolean hasMoreElements(Instances structure) {
+      boolean	result;
+      
+      result = false;
+      
+      if (isIncremental()) {
+	// user still hasn't collected the last one?
+	if (m_IncrementalBuffer != null) {
+	  result = true;
+	}
+	else {
+	  try {
+	    m_IncrementalBuffer = m_Loader.getNextInstance(structure);
+	    result              = (m_IncrementalBuffer != null);
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    result = false;
+	  }
+	}
+      }
+      else {
+	result = (m_BatchCounter < m_BatchBuffer.numInstances());
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns the next element and sets the specified dataset, null if 
+     * none available.
+     * 
+     * @param dataset	the dataset to set for the instance
+     * @return		the next Instance
+     */
+    public Instance nextElement(Instances dataset) {
+      Instance	result;
+      
+      result = null;
+      
+      if (isIncremental()) {
+	// is there still an instance in the buffer?
+	if (m_IncrementalBuffer != null) {
+	  result              = m_IncrementalBuffer;
+	  m_IncrementalBuffer = null;
+	}
+	else {
+	  try {
+	    result = m_Loader.getNextInstance(dataset);
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    result = null;
+	  }
+	}
+      }
+      else {
+	if (m_BatchCounter < m_BatchBuffer.numInstances()) {
+	  result = m_BatchBuffer.instance(m_BatchCounter);
+	  m_BatchCounter++;
+	}
+      }
+
+      result.setDataset(dataset);
+      
+      return result;
+    }
+    
+    /**
+     * convencience method for loading a dataset in batch mode.
+     * 
+     * @param location		the dataset to load
+     * @return			the dataset
+     * @throws Exception	if loading fails
+     */
+    public static Instances read(String location) throws Exception {
+      DataSource	source;
+      Instances		result;
+      
+      source = new DataSource(location);
+      result = source.getDataSet();
+      
+      return result;
+    }
+    
+    /**
+     * convencience method for loading a dataset in batch mode from a stream.
+     * 
+     * @param stream		the stream to load the dataset from
+     * @return			the dataset
+     * @throws Exception	if loading fails
+     */
+    public static Instances read(InputStream stream) throws Exception {
+      DataSource	source;
+      Instances		result;
+      
+      source = new DataSource(stream);
+      result = source.getDataSet();
+      
+      return result;
+    }
+    
+    /**
+     * convencience method for loading a dataset in batch mode.
+     * 
+     * @param loader		the loader to get the dataset from
+     * @return			the dataset
+     * @throws Exception	if loading fails
+     */
+    public static Instances read(Loader loader) throws Exception {
+      DataSource	source;
+      Instances		result;
+      
+      source = new DataSource(loader);
+      result = source.getDataSet();
+      
+      return result;
+    }
+    
+    /**
+     * for testing only - takes a data file as input.
+     * 
+     * @param args		the commandline arguments
+     * @throws Exception 	if something goes wrong
+     */
+    public static void main(String[] args) throws Exception {
+      if (args.length != 1) {
+	System.out.println("\nUsage: " + DataSource.class.getName() + " <file>\n");
+	System.exit(1);
+      }
+      
+      DataSource loader = new DataSource(args[0]);
+      
+      System.out.println("Incremental? " + loader.isIncremental());
+      System.out.println("Loader: " + loader.getLoader().getClass().getName());
+      System.out.println("Data:\n");
+      Instances structure = loader.getStructure();
+      System.out.println(structure);
+      while (loader.hasMoreElements(structure))
+	System.out.println(loader.nextElement(structure));
+      
+      Instances inst = loader.getDataSet();
+      loader = new DataSource(inst);
+      System.out.println("\n\nProxy-Data:\n");
+      System.out.println(loader.getStructure());
+      while (loader.hasMoreElements(structure))
+	System.out.println(loader.nextElement(inst));
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6155 $");
+    }
+  }
+
+  /**
+   * Helper class for saving data to files. Via the ConverterUtils
+   * class it determines which converter to use for saving the data.
+   * It is the logical counterpart to <code>DataSource</code>.
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 6155 $
+   * @see DataSource
+   */
+  public static class DataSink
+    implements Serializable, RevisionHandler {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -1504966891136411204L;
+
+    /** the saver to use for storing the data. */
+    protected Saver m_Saver = null;
+    
+    /** the stream to store the data in (always in ARFF format). */
+    protected OutputStream m_Stream = null;
+    
+    /**
+     * initializes the sink to save the data to the given file.
+     * 
+     * @param filename		the file to save data to
+     * @throws Exception	if set of saver fails
+     */
+    public DataSink(String filename) throws Exception {
+      m_Stream = null;
+      
+      if (DataSource.isArff(filename))
+	m_Saver = new ArffSaver();
+      else
+	m_Saver = getSaverForFile(filename);
+      
+      ((AbstractFileSaver) m_Saver).setFile(new File(filename));
+    }
+    
+    /**
+     * initializes the sink to save the data to the given Saver (expected to be 
+     * fully configured).
+     * 
+     * @param saver	the saver to use for saving the data
+     */
+    public DataSink(Saver saver) {
+      m_Saver  = saver;
+      m_Stream = null;
+    }
+    
+    /**
+     * initializes the sink to save the data in the stream (always in ARFF 
+     * format).
+     * 
+     * @param stream	the output stream to use for storing the data in ARFF
+     * 			format
+     */
+    public DataSink(OutputStream stream) {
+      m_Saver  = null;
+      m_Stream = stream;
+    }
+
+    /**
+     * writes the given data either via the saver or to the defined 
+     * output stream (depending on the constructor). In case of the stream, 
+     * the stream is only flushed, but not closed.
+     * 
+     * @param data		the data to save
+     * @throws Exception	if saving fails
+     */
+    public void write(Instances data) throws Exception {
+      if (m_Saver != null) {
+	m_Saver.setInstances(data);
+	m_Saver.writeBatch();
+      }
+      else {
+	m_Stream.write(data.toString().getBytes());
+	m_Stream.flush();
+      }
+    }
+    
+    /**
+     * writes the data to the given file.
+     * 
+     * @param filename		the file to write the data to
+     * @param data		the data to store
+     * @throws Exception	if writing fails
+     */
+    public static void write(String filename, Instances data) throws Exception {
+      DataSink	sink;
+      
+      sink = new DataSink(filename);
+      sink.write(data);
+    }
+    
+    /**
+     * writes the data via the given saver.
+     * 
+     * @param saver		the saver to use for writing the data
+     * @param data		the data to store
+     * @throws Exception	if writing fails
+     */
+    public static void write(Saver saver, Instances data) throws Exception {
+      DataSink	sink;
+      
+      sink = new DataSink(saver);
+      sink.write(data);
+    }
+    
+    /**
+     * writes the data to the given stream (always in ARFF format).
+     * 
+     * @param stream		the stream to write the data to (ARFF format)
+     * @param data		the data to store
+     * @throws Exception	if writing fails
+     */
+    public static void write(OutputStream stream, Instances data) throws Exception {
+      DataSink	sink;
+      
+      sink = new DataSink(stream);
+      sink.write(data);
+    }
+    
+    /**
+     * for testing only - takes a data file as input and a data file for the
+     * output.
+     * 
+     * @param args		the commandline arguments
+     * @throws Exception 	if something goes wrong
+     */
+    public static void main(String[] args) throws Exception {
+      if (args.length != 2) {
+	System.out.println(
+	    "\nUsage: " + DataSource.class.getName() + " <input-file> <output-file>\n");
+	System.exit(1);
+      }
+
+      // load data
+      Instances data = DataSource.read(args[0]);
+      
+      // save data
+      DataSink.write(args[1], data);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 6155 $");
+    }
+  }
+  
+  /** the core loaders - hardcoded list necessary for RMI/Remote Experiments 
+   * (comma-separated list). */
+  public final static String CORE_FILE_LOADERS = 
+      weka.core.converters.ArffLoader.class.getName() + ","
+    + weka.core.converters.C45Loader.class.getName() + ","
+    + weka.core.converters.CSVLoader.class.getName() + ","
+    + weka.core.converters.DatabaseConverter.class.getName() + ","
+    + weka.core.converters.LibSVMLoader.class.getName() + ","
+    + weka.core.converters.MatlabLoader.class.getName() + ","
+    + weka.core.converters.SVMLightLoader.class.getName() + ","
+    + weka.core.converters.SerializedInstancesLoader.class.getName() + ","
+    + weka.core.converters.TextDirectoryLoader.class.getName() + ","
+    + weka.core.converters.XRFFLoader.class.getName();
+
+  /** the core savers - hardcoded list necessary for RMI/Remote Experiments 
+   * (comma-separated list). */
+  public final static String CORE_FILE_SAVERS =
+      weka.core.converters.ArffSaver.class.getName() + ","
+    + weka.core.converters.C45Saver.class.getName() + ","
+    + weka.core.converters.CSVSaver.class.getName() + ","
+    + weka.core.converters.DatabaseConverter.class.getName() + ","
+    + weka.core.converters.LibSVMSaver.class.getName() + ","
+    + weka.core.converters.MatlabSaver.class.getName() + ","
+    + weka.core.converters.SVMLightSaver.class.getName() + ","
+    + weka.core.converters.SerializedInstancesSaver.class.getName() + ","
+    + weka.core.converters.XRFFSaver.class.getName();
+  
+  /** all available loaders (extension &lt;-&gt; classname). */
+  protected static Hashtable<String,String> m_FileLoaders;
+  
+  /** all available URL loaders (extension &lt;-&gt; classname). */
+  protected static Hashtable<String,String> m_URLFileLoaders;
+
+  /** all available savers (extension &lt;-&gt; classname). */
+  protected static Hashtable<String,String> m_FileSavers;
+  
+  // determine all loaders/savers
+  static {
+    Vector classnames;
+    
+    try {
+      // generate properties 
+      // Note: does NOT work with RMI, hence m_FileLoadersCore/m_FileSaversCore
+      GenericPropertiesCreator creator = new GenericPropertiesCreator();
+      creator.execute(false);
+      Properties props = creator.getOutputProperties();
+
+      // init
+      m_FileLoaders    = new Hashtable<String,String>();
+      m_URLFileLoaders = new Hashtable<String,String>();
+      m_FileSavers     = new Hashtable<String,String>();
+      
+      // loaders
+      m_FileLoaders = getFileConverters(
+	  		props.getProperty(Loader.class.getName(), CORE_FILE_LOADERS),
+	  		new String[]{FileSourcedConverter.class.getName()});
+      
+      // URL loaders
+      m_URLFileLoaders = getFileConverters(
+	  		   props.getProperty(Loader.class.getName(), CORE_FILE_LOADERS),
+	  		   new String[]{
+	  		     FileSourcedConverter.class.getName(), 
+	  		     URLSourcedLoader.class.getName()});
+
+      // savers
+      m_FileSavers = getFileConverters(
+	  		props.getProperty(Saver.class.getName(), CORE_FILE_SAVERS),
+	  		new String[]{FileSourcedConverter.class.getName()});
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      // ignore
+    }
+    finally {
+      // loaders
+      if (m_FileLoaders.size() == 0) {
+	classnames = GenericObjectEditor.getClassnames(AbstractFileLoader.class.getName());
+	if (classnames.size() > 0)
+	  m_FileLoaders = getFileConverters(
+	                    classnames,
+	                    new String[]{FileSourcedConverter.class.getName()});
+	else
+	  m_FileLoaders = getFileConverters(
+	                    CORE_FILE_LOADERS,
+	                    new String[]{FileSourcedConverter.class.getName()});
+      }
+
+      // URL loaders
+      if (m_URLFileLoaders.size() == 0) {
+        classnames = GenericObjectEditor.getClassnames(AbstractFileLoader.class.getName());
+        if (classnames.size() > 0)
+	  m_URLFileLoaders = getFileConverters(
+	  		       classnames,
+	  		       new String[]{
+	  			   FileSourcedConverter.class.getName(), 
+	  			   URLSourcedLoader.class.getName()});
+        else
+          m_URLFileLoaders = getFileConverters(
+	                       CORE_FILE_LOADERS,
+	                       new String[]{
+	                	   FileSourcedConverter.class.getName(), 
+	                	   URLSourcedLoader.class.getName()});
+      }
+
+      // savers
+      if (m_FileSavers.size() == 0) {
+	classnames = GenericObjectEditor.getClassnames(AbstractFileSaver.class.getName());
+	if (classnames.size() > 0)
+	  m_FileSavers = getFileConverters(
+	  		   classnames,
+	  		   new String[]{FileSourcedConverter.class.getName()});
+	else
+	  m_FileSavers = getFileConverters(
+	                   CORE_FILE_SAVERS,
+	                   new String[]{FileSourcedConverter.class.getName()});
+      }
+    }
+  }
+  
+  /**
+   * returns a hashtable with the association 
+   * "file extension &lt;-&gt; converter classname" for the comma-separated list
+   * of converter classnames.
+   * 
+   * @param classnames	comma-separated list of converter classnames
+   * @param intf	interfaces the converters have to implement
+   * @return		hashtable with ExtensionFileFilters
+   */
+  protected static Hashtable<String,String> getFileConverters(String classnames, String[] intf) {
+    Vector<String>	list;
+    String[]	names;
+    int		i;
+    
+    list  = new Vector<String>();
+    names = classnames.split(",");
+    for (i = 0; i < names.length; i++)
+      list.add(names[i]);
+    
+    return getFileConverters(list, intf);
+  }
+  
+  /**
+   * returns a hashtable with the association 
+   * "file extension &lt;-&gt; converter classname" for the list of converter 
+   * classnames.
+   * 
+   * @param classnames	list of converter classnames
+   * @param intf	interfaces the converters have to implement
+   * @return		hashtable with ExtensionFileFilters
+   */
+  protected static Hashtable<String,String> getFileConverters(Vector classnames, String[] intf) {
+    Hashtable<String,String>	result;
+    String 			classname;
+    Class 			cls;
+    String[] 			ext;
+    FileSourcedConverter 	converter;
+    int 			i;
+    int				n;
+    
+    result = new Hashtable<String,String>();
+    
+    for (i = 0; i < classnames.size(); i++) {
+      classname = (String) classnames.get(i);
+
+      // all necessary interfaces implemented?
+      for (n = 0; n < intf.length; n++) {
+	if (!ClassDiscovery.hasInterface(intf[n], classname))
+	  continue;
+      }
+      
+      // get data from converter
+      try {
+	cls       = Class.forName(classname);
+	converter = (FileSourcedConverter) cls.newInstance();
+	ext       = converter.getFileExtensions();
+      }
+      catch (Exception e) {
+	cls       = null;
+	converter = null;
+	ext       = new String[0];
+      }
+      
+      if (converter == null)
+	continue;
+
+      for (n = 0; n < ext.length; n++)
+	result.put(ext[n], classname);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Gets token, skipping empty lines.
+   *
+   * @param tokenizer 		the stream tokenizer
+   * @throws IOException 	if reading the next token fails
+   */
+  public static void getFirstToken(StreamTokenizer tokenizer) 
+    throws IOException {
+    
+    while (tokenizer.nextToken() == StreamTokenizer.TT_EOL){};
+    if ((tokenizer.ttype == '\'') ||
+	(tokenizer.ttype == '"')) {
+      tokenizer.ttype = StreamTokenizer.TT_WORD;
+    } else if ((tokenizer.ttype == StreamTokenizer.TT_WORD) &&
+	       (tokenizer.sval.equals("?"))) {
+      tokenizer.ttype = '?';
+    }
+  }
+
+  /**
+   * Gets token.
+   *
+   * @param tokenizer 		the stream tokenizer
+   * @throws IOException 	if reading the next token fails
+   */
+  public static void getToken(StreamTokenizer tokenizer) throws IOException {
+    
+    tokenizer.nextToken();
+    if (tokenizer.ttype== StreamTokenizer.TT_EOL) {
+      return;
+    }
+
+    if ((tokenizer.ttype == '\'') ||
+	(tokenizer.ttype == '"')) {
+      tokenizer.ttype = StreamTokenizer.TT_WORD;
+    } else if ((tokenizer.ttype == StreamTokenizer.TT_WORD) &&
+	       (tokenizer.sval.equals("?"))) {
+      tokenizer.ttype = '?';
+    }
+  }
+
+  /**
+   * Throws error message with line number and last token read.
+   *
+   * @param theMsg 		the error message to be thrown
+   * @param tokenizer 		the stream tokenizer
+   * @throws IOException 	containing the error message
+   */
+  public static void errms(StreamTokenizer tokenizer, String theMsg) 
+    throws IOException {
+    
+    throw new IOException(theMsg + ", read " + tokenizer.toString());
+  }
+
+  /**
+   * returns a vector with the classnames of all the loaders from the 
+   * given hashtable.
+   * 
+   * @param ht		the hashtable with the extension/converter relation
+   * @return		the classnames of the loaders
+   */
+  protected static Vector<String> getConverters(Hashtable<String,String> ht) {
+    Vector<String>	result;
+    Enumeration<String>	enm;
+    String		converter;
+    
+    result = new Vector<String>();
+    
+    // get all classnames
+    enm = ht.elements();
+    while (enm.hasMoreElements()) {
+      converter = enm.nextElement();
+      if (!result.contains(converter))
+	result.add(converter);
+    }
+    
+    // sort names
+    Collections.sort(result);
+    
+    return result;
+  }
+  
+  /**
+   * tries to determine the converter to use for this kind of file, returns
+   * null if none can be found in the given hashtable.
+   * 
+   * @param filename	the file to return a converter for
+   * @param ht		the hashtable with the relation extension/converter
+   * @return		the converter if one was found, null otherwise
+   */
+  protected static Object getConverterForFile(String filename, Hashtable<String,String> ht) {
+    Object	result;
+    String	extension;
+    int		index;
+    
+    result = null;
+    
+    index = filename.lastIndexOf('.');
+    if (index > -1) {
+      extension = filename.substring(index).toLowerCase();
+      result    = getConverterForExtension(extension, ht);
+      // is it a compressed format?
+      if (extension.equals(".gz") && result == null) {
+	index     = filename.lastIndexOf('.', index - 1);
+	extension = filename.substring(index).toLowerCase();
+	result    = getConverterForExtension(extension, ht);
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * tries to determine the loader to use for this kind of extension, returns
+   * null if none can be found.
+   * 
+   * @param extension	the file extension to return a converter for
+   * @param ht		the hashtable with the relation extension/converter
+   * @return		the converter if one was found, null otherwise
+   */
+  protected static Object getConverterForExtension(String extension, Hashtable<String,String> ht) {
+    Object	result;
+    String	classname;
+    
+    result    = null;
+    classname = (String) ht.get(extension);
+    if (classname != null) {
+      try {
+	result = Class.forName(classname).newInstance();
+      }
+      catch (Exception e) {
+	result = null;
+	e.printStackTrace();
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the given class is one of the hardcoded core file loaders.
+   * 
+   * @param classname	the class to check
+   * @return		true if the class is one of the core loaders
+   * @see		#CORE_FILE_LOADERS
+   */
+  public static boolean isCoreFileLoader(String classname) {
+    boolean	result;
+    String[]	classnames;
+    
+    classnames = CORE_FILE_LOADERS.split(",");
+    result     = (Arrays.binarySearch(classnames, classname) >= 0);
+    
+    return result;
+  }
+  
+  /**
+   * returns a vector with the classnames of all the file loaders.
+   * 
+   * @return		the classnames of the loaders
+   */
+  public static Vector<String> getFileLoaders() {
+    return getConverters(m_FileLoaders);
+  }
+  
+  /**
+   * tries to determine the loader to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param filename	the file to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getLoaderForFile(String filename) {
+    return (AbstractFileLoader) getConverterForFile(filename, m_FileLoaders);
+  }
+
+  /**
+   * tries to determine the loader to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param file	the file to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getLoaderForFile(File file) {
+    return getLoaderForFile(file.getAbsolutePath());
+  }
+
+  /**
+   * tries to determine the loader to use for this kind of extension, returns
+   * null if none can be found.
+   * 
+   * @param extension	the file extension to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getLoaderForExtension(String extension) {
+    return (AbstractFileLoader) getConverterForExtension(extension, m_FileLoaders);
+  }
+
+  /**
+   * returns a vector with the classnames of all the URL file loaders.
+   * 
+   * @return		the classnames of the loaders
+   */
+  public static Vector<String> getURLFileLoaders() {
+    return getConverters(m_URLFileLoaders);
+  }
+  
+  /**
+   * tries to determine the URL loader to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param filename	the file to return a URL converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getURLLoaderForFile(String filename) {
+    return (AbstractFileLoader) getConverterForFile(filename, m_URLFileLoaders);
+  }
+
+  /**
+   * tries to determine the URL loader to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param file	the file to return a URL converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getURLLoaderForFile(File file) {
+    return getURLLoaderForFile(file.getAbsolutePath());
+  }
+
+  /**
+   * tries to determine the URL loader to use for this kind of extension, returns
+   * null if none can be found.
+   * 
+   * @param extension	the file extension to return a URL converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileLoader getURLLoaderForExtension(String extension) {
+    return (AbstractFileLoader) getConverterForExtension(extension, m_URLFileLoaders);
+  }
+  
+  /**
+   * checks whether the given class is one of the hardcoded core file savers.
+   * 
+   * @param classname	the class to check
+   * @return		true if the class is one of the core savers
+   * @see		#CORE_FILE_SAVERS
+   */
+  public static boolean isCoreFileSaver(String classname) {
+    boolean	result;
+    String[]	classnames;
+    
+    classnames = CORE_FILE_SAVERS.split(",");
+    result     = (Arrays.binarySearch(classnames, classname) >= 0);
+    
+    return result;
+  }
+
+  /**
+   * returns a vector with the classnames of all the file savers.
+   * 
+   * @return		the classnames of the savers
+   */
+  public static Vector<String> getFileSavers() {
+    return getConverters(m_FileSavers);
+  }
+
+  /**
+   * tries to determine the saver to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param filename	the file to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileSaver getSaverForFile(String filename) {
+    return (AbstractFileSaver) getConverterForFile(filename, m_FileSavers);
+  }
+
+  /**
+   * tries to determine the saver to use for this kind of file, returns
+   * null if none can be found.
+   * 
+   * @param file	the file to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileSaver getSaverForFile(File file) {
+    return getSaverForFile(file.getAbsolutePath());
+  }
+
+  /**
+   * tries to determine the saver to use for this kind of extension, returns
+   * null if none can be found.
+   * 
+   * @param extension	the file extension to return a converter for
+   * @return		the converter if one was found, null otherwise
+   */
+  public static AbstractFileSaver getSaverForExtension(String extension) {
+    return (AbstractFileSaver) getConverterForExtension(extension, m_FileSavers);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6155 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConnection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConnection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConnection.java	(revision 29)
@@ -0,0 +1,98 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseConnection.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.RevisionUtils;
+import weka.experiment.DatabaseUtils;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * Connects to a database.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class DatabaseConnection 
+  extends DatabaseUtils {
+
+  /** for serialization */
+  static final long serialVersionUID = 1673169848863178695L;
+  
+  /**
+   * Sets up the database drivers
+   *
+   * @throws Exception if an error occurs
+   */
+  public DatabaseConnection() throws Exception {
+    super();
+  }
+
+  /** 
+   * Check if the property checkUpperCaseNames in the DatabaseUtils file is 
+   * set to true or false.
+   *
+   * @return  	true if the property checkUpperCaseNames in the DatabaseUtils 
+   * 		file is set to true, false otherwise.
+   */
+  public boolean getUpperCase(){
+    return m_checkForUpperCaseNames;
+  }
+  
+  /**
+   * Gets meta data for the database connection object.
+   *
+   * @return the meta data.
+   * @throws Exception if an error occurs
+   */
+  public DatabaseMetaData getMetaData() throws Exception{
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    return m_Connection.getMetaData();
+  }
+  
+  /**
+   * Dewtermines if the current query retrieves a result set or updates a table
+   *
+   * @return the update count (-1 if the query retrieves a result set).
+   * @throws SQLException if an error occurs
+   */
+  public int getUpdateCount() throws SQLException {
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    return m_PreparedStatement.getUpdateCount();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConverter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConverter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/DatabaseConverter.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseConverter.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+/**
+ * Marker interface for a loader/saver that uses a database
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision 1.0 $
+ */
+public interface DatabaseConverter {
+    
+    public String getUrl();
+    
+    public String getUser();
+    
+    public void setUrl(String url);
+    
+    public void setUser(String user);
+    
+    public void setPassword(String password);
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/DatabaseLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/DatabaseLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/DatabaseLoader.java	(revision 29)
@@ -0,0 +1,1617 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseLoader.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Attribute;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Option;
+import java.io.IOException;
+import java.sql.*;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads Instances from a Database. Can read a database in batch or incremental mode.<br/>
+ * In inremental mode MySQL and HSQLDB are supported.<br/>
+ * For all other DBMS set a pseudoincremental mode is used:<br/>
+ * In pseudo incremental mode the instances are read into main memory all at once and then incrementally provided to the user.<br/>
+ * For incremental loading the rows in the database table have to be ordered uniquely.<br/>
+ * The reason for this is that every time only a single row is fetched by extending the user query by a LIMIT clause.<br/>
+ * If this extension is impossible instances will be loaded pseudoincrementally. To ensure that every row is fetched exaclty once, they have to ordered.<br/>
+ * Therefore a (primary) key is necessary.This approach is chosen, instead of using JDBC driver facilities, because the latter one differ betweeen different drivers.<br/>
+ * If you use the DatabaseSaver and save instances by generating automatically a primary key (its name is defined in DtabaseUtils), this primary key will be used for ordering but will not be part of the output. The user defined SQL query to extract the instances should not contain LIMIT and ORDER BY clauses (see -Q option).<br/>
+ * In addition, for incremental loading,  you can define in the DatabaseUtils file how many distinct values a nominal attribute is allowed to have. If this number is exceeded, the column will become a string attribute.<br/>
+ * In batch mode no string attributes will be created.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -url &lt;JDBC URL&gt;
+ *  The JDBC URL to connect to.
+ *  (default: from DatabaseUtils.props file)</pre>
+ * 
+ * <pre> -user &lt;name&gt;
+ *  The user to connect with to the database.
+ *  (default: none)</pre>
+ * 
+ * <pre> -password &lt;password&gt;
+ *  The password to connect with to the database.
+ *  (default: none)</pre>
+ * 
+ * <pre> -Q &lt;query&gt;
+ *  SQL query of the form
+ *   SELECT &lt;list of columns&gt;|* FROM &lt;table&gt; [WHERE]
+ *  to execute.
+ *  (default: Select * From Results0)</pre>
+ * 
+ * <pre> -P &lt;list of column names&gt;
+ *  List of column names uniquely defining a DB row
+ *  (separated by ', ').
+ *  Used for incremental loading.
+ *  If not specified, the key will be determined automatically,
+ *  if possible with the used JDBC driver.
+ *  The auto ID column created by the DatabaseSaver won't be loaded.</pre>
+ * 
+ * <pre> -I
+ *  Sets incremental loading</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class DatabaseLoader 
+  extends AbstractLoader 
+  implements BatchConverter, IncrementalConverter, DatabaseConverter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -7936159015338318659L;
+  
+  /** The header information that is retrieved in the beginning of incremental loading */
+  protected Instances m_structure;
+  
+  /** Used in pseudoincremental mode. The whole dataset from which instances will be read incrementally.*/
+  private Instances m_datasetPseudoInc;
+  
+  /** Set of instances that equals m_structure except that the auto_generated_id column is not included as an attribute*/
+  private Instances m_oldStructure;
+  
+  /** The database connection */
+  private DatabaseConnection m_DataBaseConnection;
+  
+  /** The user defined query to load instances. (form: SELECT *|&ltcolumn-list&gt; FROM &lttable&gt; [WHERE &lt;condition&gt;]) */
+  private String m_query = "Select * from Results0";
+  
+  /** Flag indicating that pseudo incremental mode is used (all instances load at once into main memeory and then incrementally from main memory instead of the database) */
+  private boolean m_pseudoIncremental;
+  
+  /** If true it checks whether or not the table exists in the database before loading depending on jdbc metadata information.
+   *  Set flag to false if no check is required or if jdbc metadata is not complete. */
+  private boolean m_checkForTable;
+  
+  /** Limit when an attribute is treated as string attribute and not as a nominal one because it has to many values. */
+  private int m_nominalToStringLimit;
+  
+  /** The number of rows obtained by m_query, eg the size of the ResultSet to load*/
+  private int m_rowCount;
+  
+  /** Indicates how many rows has already been loaded incrementally */
+  private int m_counter;
+  
+  /** Decides which SQL statement to limit the number of rows should be used. DBMS dependent. Algorithm just tries several possibilities. */
+  private int m_choice;
+  
+  /** Flag indicating that incremental process wants to read first instance*/
+  private boolean m_firstTime;
+  
+  /** Flag indicating that incremental mode is chosen (for command line use only)*/
+  private boolean m_inc;
+  
+  /** Contains the name of the columns that uniquely define a row in the ResultSet. Ensures a unique ordering of instances for indremental loading.*/
+  private ArrayList<String> m_orderBy;
+  
+  /** Stores the index of a nominal value */
+  private Hashtable<String,Double> [] m_nominalIndexes;
+  
+  /**  Stores the nominal value*/
+  private ArrayList<String> [] m_nominalStrings;
+  
+  /** Name of the primary key column that will allow unique ordering necessary for incremental loading. The name is specified in the DatabaseUtils file.*/
+  private String m_idColumn;
+  
+  /** The property file for the database connection */
+  protected static String PROPERTY_FILE = DatabaseConnection.PROPERTY_FILE;
+  
+  /** Properties associated with the database connection */
+  protected static Properties PROPERTIES;
+
+  /** the JDBC URL to use */
+  protected String m_URL = null;
+
+  /** the database user to use */
+  protected String m_User = null;
+
+  /** the database password to use */
+  protected String m_Password = null;
+  
+  /** the keys for unique ordering */
+  protected String m_Keys = null;
+  
+  /** reads the property file */
+  static {
+
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+   
+    } catch (Exception ex) {
+      System.err.println("Problem reading properties. Fix before continuing.");
+      System.err.println(ex);
+    }
+  }
+  
+  /**
+   * Constructor
+   * 
+   * @throws Exception if initialization fails
+   */
+  public DatabaseLoader() throws Exception{
+  
+      reset();
+      m_pseudoIncremental=false;
+      m_checkForTable=true;
+      String props=PROPERTIES.getProperty("nominalToStringLimit");
+      m_nominalToStringLimit = Integer.parseInt(props);
+      m_idColumn=PROPERTIES.getProperty("idColumn");
+      if (PROPERTIES.getProperty("checkForTable", "").equalsIgnoreCase("FALSE"))
+	m_checkForTable=false;
+  }
+
+  /**
+   * Returns a string describing this Loader
+   * 
+   * @return a description of the Loader suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads Instances from a Database. "
+      + "Can read a database in batch or incremental mode.\n"
+      + "In inremental mode MySQL and HSQLDB are supported.\n"
+      + "For all other DBMS set a pseudoincremental mode is used:\n"
+      + "In pseudo incremental mode the instances are read into main memory all at once and then incrementally provided to the user.\n"
+      + "For incremental loading the rows in the database table have to be ordered uniquely.\n"
+      + "The reason for this is that every time only a single row is fetched by extending the user query by a LIMIT clause.\n"
+      + "If this extension is impossible instances will be loaded pseudoincrementally. To ensure that every row is fetched exaclty once, they have to ordered.\n"
+      + "Therefore a (primary) key is necessary.This approach is chosen, instead of using JDBC driver facilities, because the latter one differ betweeen different drivers.\n"
+      + "If you use the DatabaseSaver and save instances by generating automatically a primary key (its name is defined in DtabaseUtils), this primary key will "
+      + "be used for ordering but will not be part of the output. The user defined SQL query to extract the instances should not contain LIMIT and ORDER BY clauses (see -Q option).\n"
+      + "In addition, for incremental loading,  you can define in the DatabaseUtils file how many distinct values a nominal attribute is allowed to have. If this number is exceeded, the column will become a string attribute.\n"
+      + "In batch mode no string attributes will be created.";
+  }
+
+  
+
+  /** Resets the Loader ready to read a new data set
+   * @throws Exception if an error occurs while disconnecting from the database
+   */
+  public void reset() throws Exception{
+
+    resetStructure();
+    if(m_DataBaseConnection != null && m_DataBaseConnection.isConnected())
+        m_DataBaseConnection.disconnectFromDatabase();
+    m_DataBaseConnection = new DatabaseConnection();
+
+    // don't lose previously set connection data!
+    if (m_URL != null)
+      m_DataBaseConnection.setDatabaseURL(m_URL);
+    if (m_User != null)
+      m_DataBaseConnection.setUsername(m_User);
+    if (m_Password != null)
+      m_DataBaseConnection.setPassword(m_Password);
+
+    m_orderBy = new ArrayList<String>();
+    // don't lose previously set key columns!
+    if (m_Keys != null)
+      setKeys(m_Keys);
+      
+    m_inc = false;
+    
+  }
+  
+  
+  /** 
+   * Resets the structure of instances
+   */
+  public void resetStructure(){
+  
+      m_structure = null;
+      m_datasetPseudoInc = null;
+      m_oldStructure = null;
+      m_rowCount = 0;
+      m_counter = 0;
+      m_choice = 0;
+      m_firstTime = true;
+      setRetrieval(NONE);
+  }
+  
+  
+  /**
+   * Sets the query to execute against the database
+   * 
+   * @param q the query to execute
+   */
+  public void setQuery(String q) {
+    q = q.replaceAll("[fF][rR][oO][mM]","FROM");
+    q = q.replaceFirst("[sS][eE][lL][eE][cC][tT]","SELECT");  
+    m_query = q;
+  }
+
+  /**
+   * Gets the query to execute against the database
+   * 
+   * @return the query
+   */
+  public String getQuery() {
+    return m_query;
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return the tip text
+   */
+  public String queryTipText(){
+  
+      return "The query that should load the instances."
+        +"\n The query has to be of the form SELECT <column-list>|* FROM <table> [WHERE <conditions>]";
+  }
+  
+  /**
+   * Sets the key columns of a database table
+   * 
+   * @param keys a String containing the key columns in a comma separated list.
+   */
+  public void setKeys(String keys){
+  
+    m_Keys = keys;
+    m_orderBy.clear();
+    StringTokenizer st = new StringTokenizer(keys, ",");
+    while (st.hasMoreTokens()) {
+        String column = st.nextToken();
+        column = column.replaceAll(" ","");
+        m_orderBy.add(column);
+    }
+  }
+  
+   /**
+   * Gets the key columns' name
+   * 
+   * @return name of the key columns'
+   */
+  public String getKeys(){
+  
+      StringBuffer key = new StringBuffer();
+      for(int i = 0;i < m_orderBy.size(); i++){
+        key.append((String)m_orderBy.get(i));
+        if(i != m_orderBy.size()-1)
+          key.append(", ");
+      }
+      return key.toString();
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return the tip text
+   */
+  public String keysTipText(){
+  
+      return "For incremental loading a unique identiefer has to be specified."
+        +"\nIf the query includes all columns of a table (SELECT *...) a primary key"
+        +"\ncan be detected automatically depending on the JDBC driver. If that is not possible"
+        +"\nspecify the key columns here in a comma separated list.";
+  }
+  
+  /**
+   * Sets the database URL
+   * 
+   * @param url string with the database URL
+   */
+  public void setUrl(String url){
+      
+      m_URL = url;
+      m_DataBaseConnection.setDatabaseURL(url);
+    
+  }
+  
+  /**
+   * Gets the URL
+   * 
+   * @return the URL
+   */
+  public String getUrl(){
+  
+      return m_DataBaseConnection.getDatabaseURL();
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return the tip text
+   */
+  public String urlTipText(){
+  
+      return "The URL of the database";
+  }
+  
+  /**
+   * Sets the database user
+   * 
+   * @param user the database user name
+   */
+  public void setUser(String user){
+   
+      m_User = user;
+      m_DataBaseConnection.setUsername(user);
+  }
+  
+  /**
+   * Gets the user name
+   * 
+   * @return name of database user
+   */
+  public String getUser(){
+   
+      return m_DataBaseConnection.getUsername();
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return the tip text
+   */
+  public String userTipText(){
+  
+      return "The user name for the database";
+  }
+  
+  /**
+   * Sets user password for the database
+   * 
+   * @param password the password
+   */
+  public void setPassword(String password){
+   
+      m_Password = password;
+      m_DataBaseConnection.setPassword(password);
+  }
+
+  /**
+   * Returns the database password
+   *
+   * @return the database password
+   */
+  public String getPassword() {
+    return m_DataBaseConnection.getPassword();
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return the tip text
+   */
+  public String passwordTipText(){
+  
+      return "The database password";
+  }
+  
+  
+  /** 
+   * Sets the database url, user and pw
+   * 
+   * @param url the database url
+   * @param userName the user name
+   * @param password the password
+   */  
+  public void setSource(String url, String userName, String password){
+  
+      try{
+        m_DataBaseConnection = new DatabaseConnection();
+        setUrl(url);
+        setUser(userName);
+        setPassword(password);
+      } catch(Exception ex) {
+            printException(ex);
+      }    
+  }
+  
+  /** 
+   * Sets the database url
+   * 
+   * @param url the database url
+   */  
+  public void setSource(String url){
+  
+      try{
+        m_DataBaseConnection = new DatabaseConnection();
+        setUrl(url);
+        m_User = m_DataBaseConnection.getUsername();
+        m_Password = m_DataBaseConnection.getPassword();
+      } catch(Exception ex) {
+            printException(ex);
+       }    
+  }
+  
+  /** 
+   * Sets the database url using the DatabaseUtils file
+   * 
+   * @throws Exception if something goes wrong
+   */  
+  public void setSource() throws Exception{
+  
+        m_DataBaseConnection = new DatabaseConnection();
+        m_URL = m_DataBaseConnection.getDatabaseURL();
+        m_User = m_DataBaseConnection.getUsername();
+        m_Password = m_DataBaseConnection.getPassword();
+  }
+  
+  /**
+   * Opens a connection to the database
+   */
+  public void connectToDatabase() {
+   
+      try{
+        if(!m_DataBaseConnection.isConnected()){
+            m_DataBaseConnection.connectToDatabase();
+        }
+      } catch(Exception ex) {
+	printException(ex);
+       }    
+  }
+  
+  
+  /** 
+   * Returns the table name or all after the FROM clause of the user specified query
+   * to retrieve instances.
+   * 
+   * @param onlyTableName true if only the table name should be returned, false otherwise
+   * @return the end of the query
+   */  
+  private String endOfQuery(boolean onlyTableName){
+      String table;
+      int beginIndex, endIndex;
+      
+      beginIndex = m_query.indexOf("FROM ")+5;
+      while(m_query.charAt(beginIndex) == ' ')
+          beginIndex++;
+      endIndex = m_query.indexOf(" ",beginIndex);
+      if(endIndex != -1 && onlyTableName)
+          table = m_query.substring(beginIndex,endIndex);
+      else
+          table = m_query.substring(beginIndex);
+      if(m_DataBaseConnection.getUpperCase())
+          table = table.toUpperCase();
+      return table;
+  }
+  
+  /** 
+   * Checks for a unique key using the JDBC driver's method:
+   * getPrimaryKey(), getBestRowIdentifier().
+   * Depending on their implementation a key can be detected.
+   * The key is needed to order the instances uniquely for an inremental loading.
+   * If an existing key cannot be detected, use -P option.
+   * 
+   * @throws Exception if database error occurs
+   * @return true, if a key could have been detected, false otherwise
+   */  
+  private boolean checkForKey() throws Exception {
+  
+      String query = m_query;
+      
+      query = query.replaceAll(" +"," ");
+      //query has to use all columns
+      if(!query.startsWith("SELECT *"))
+          return false;
+      m_orderBy.clear();
+      if(!m_DataBaseConnection.isConnected())
+            m_DataBaseConnection.connectToDatabase();
+      DatabaseMetaData dmd = m_DataBaseConnection.getMetaData();
+      String table = endOfQuery(true);
+      //System.out.println(table);
+      //check for primary keys
+      ResultSet rs = dmd.getPrimaryKeys(null,null,table);
+      while(rs.next()){
+          m_orderBy.add(rs.getString(4));
+      }
+      rs.close();
+      if(m_orderBy.size() != 0)
+          return true;
+      //check for unique keys
+      rs = dmd.getBestRowIdentifier(null,null,table,DatabaseMetaData.bestRowSession,false);
+      ResultSetMetaData rmd = rs.getMetaData();
+      int help = 0;
+      while(rs.next()){
+          m_orderBy.add(rs.getString(2));
+          help++;
+      }
+      rs.close();
+      if(help == rmd.getColumnCount()){
+          m_orderBy.clear();
+      }
+      if(m_orderBy.size() != 0)
+          return true;
+      
+      return false;
+  }
+  
+  /** 
+   * Converts string attribute into nominal ones for an instance read during
+   * incremental loading
+   * 
+   * @param rs The result set
+   * @param i the index of the nominal attribute
+   * @throws Exception exception if it cannot be converted
+   */  
+  private void stringToNominal(ResultSet rs, int i) throws Exception{
+  
+      while(rs.next()){
+        String str = rs.getString(1);
+        if(!rs.wasNull()){
+            Double index = (Double)m_nominalIndexes[i - 1].get(str);
+            if (index == null) {
+                index = new Double(m_nominalStrings[i - 1].size());
+                m_nominalIndexes[i - 1].put(str, index);
+                m_nominalStrings[i - 1].add(str);
+            }
+        }
+      }
+  }
+  
+  /** 
+   * Used in incremental loading. Modifies the SQL statement,
+   * so that only one instance per time is tretieved and the instances are ordered
+   * uniquely.
+   * 
+   * @param query the query to modify for incremental loading
+   * @param offset sets which tuple out of the uniquely ordered ones should be returned
+   * @param choice the kind of query that is suitable for the used DBMS
+   * @return the modified query that returns only one result tuple.
+   */  
+  private String limitQuery(String query, int offset, int choice){
+  
+      String limitedQuery;
+      StringBuffer order = new StringBuffer();
+      String orderByString = "";
+      
+      if(m_orderBy.size() != 0){
+        order.append(" ORDER BY ");
+        for(int i = 0; i < m_orderBy.size()-1; i++){
+            if(m_DataBaseConnection.getUpperCase())
+                order.append(((String)m_orderBy.get(i)).toUpperCase());
+            else
+                order.append((String)m_orderBy.get(i));
+            order.append(", ");
+        }
+        if(m_DataBaseConnection.getUpperCase())
+            order.append(((String)m_orderBy.get(m_orderBy.size()-1)).toUpperCase());
+        else
+            order.append((String)m_orderBy.get(m_orderBy.size()-1));
+        orderByString = order.toString();
+      }
+      if(choice == 0){
+          limitedQuery = query.replaceFirst("SELECT","SELECT LIMIT "+offset+" 1");
+          limitedQuery = limitedQuery.concat(orderByString);
+          return limitedQuery;
+      }
+      if(choice == 1){
+          limitedQuery = query.concat(orderByString+" LIMIT 1 OFFSET "+offset);
+          return limitedQuery;
+      }
+      limitedQuery = query.concat(orderByString+" LIMIT "+offset+", 1");
+      //System.out.println(limitedQuery);
+      return limitedQuery;
+  }
+  
+  /** 
+   * Counts the number of rows that are loaded from the database
+   * 
+   * @throws Exception if the number of rows cannot be calculated
+   * @return the entire number of rows
+   */  
+  private int getRowCount() throws Exception{
+  
+    String query = "SELECT COUNT(*) FROM "+endOfQuery(false);
+    if(m_DataBaseConnection.execute(query) == false) {
+        throw new Exception("Cannot count results tuples.");
+    }
+    ResultSet rs = m_DataBaseConnection.getResultSet();
+    rs.next();
+    int i = rs.getInt(1);
+    rs.close();
+    return i;
+  }
+
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+
+    if (m_DataBaseConnection == null) {
+      throw new IOException("No source database has been specified");
+    }
+    connectToDatabase();
+  pseudo:
+      try{
+    if(m_pseudoIncremental && m_structure == null){
+        if (getRetrieval() == BATCH) {
+            throw new IOException("Cannot mix getting instances in both incremental and batch modes");
+        }
+        setRetrieval(NONE);  
+        m_datasetPseudoInc = getDataSet();
+        m_structure = new Instances(m_datasetPseudoInc,0);
+        setRetrieval(NONE);
+        return m_structure;
+    }
+    if (m_structure == null) {
+      if(m_checkForTable) {
+        if(!m_DataBaseConnection.tableExists(endOfQuery(true)))
+          throw new IOException(
+              "Table does not exist according to metadata from JDBC driver. "
+              + "If you are convinced the table exists, set 'checkForTable' "
+              + "to 'False' in your DatabaseUtils.props file and try again.");
+      }
+        //finds out which SQL statement to use for the DBMS to limit the number of resulting rows to one
+        int choice = 0;
+        boolean rightChoice = false;
+        while (!rightChoice){
+            try{
+                if (m_DataBaseConnection.execute(limitQuery(m_query,0,choice)) == false) {
+                    throw new IOException("Query didn't produce results");
+                }
+                m_choice = choice;
+                rightChoice = true;
+            }
+            catch (SQLException ex) {
+                choice++;
+                if(choice == 3){
+                    System.out.println("Incremental loading not supported for that DBMS. Pseudoincremental mode is used if you use incremental loading.\nAll rows are loaded into memory once and retrieved incrementally from memory instead of from the database.");
+                    m_pseudoIncremental = true;
+                    break pseudo;
+                }
+            }
+        }
+        String end = endOfQuery(false);
+        ResultSet rs = m_DataBaseConnection.getResultSet();
+        ResultSetMetaData md = rs.getMetaData();
+        rs.close();
+        int numAttributes = md.getColumnCount();
+        int [] attributeTypes = new int [numAttributes];
+        m_nominalIndexes = Utils.cast(new Hashtable [numAttributes]);
+        m_nominalStrings = Utils.cast(new ArrayList [numAttributes]);
+        for (int i = 1; i <= numAttributes; i++) {
+            switch (m_DataBaseConnection.translateDBColumnType(md.getColumnTypeName(i))) {
+                case DatabaseConnection.STRING :
+                    //System.err.println("String --> nominal");
+                    ResultSet rs1;
+                    String columnName = md.getColumnName(i);
+                    if(m_DataBaseConnection.getUpperCase())
+                        columnName = columnName.toUpperCase();
+                    m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+                    m_nominalStrings[i - 1] = new ArrayList<String>();
+                    String query = "SELECT COUNT(DISTINCT( "+columnName+" )) FROM " + end;
+                    if (m_DataBaseConnection.execute(query) == true){
+                        rs1 = m_DataBaseConnection.getResultSet();
+                        rs1.next();
+                        int count = rs1.getInt(1);
+                        rs1.close();
+                        //                        if(count > m_nominalToStringLimit || m_DataBaseConnection.execute("SELECT DISTINCT ( "+columnName+" ) FROM "+ end) == false){
+                        if(count > m_nominalToStringLimit || 
+                           m_DataBaseConnection.execute("SELECT DISTINCT ( "
+                                                        + columnName
+                                                        + " ) FROM "
+                                                        + end
+                                                        + " ORDER BY "
+                                                        + columnName) == false){
+                            attributeTypes[i - 1] = Attribute.STRING;
+                            break;
+                        }
+                        rs1 = m_DataBaseConnection.getResultSet();
+                    }
+                    else{
+                        //System.err.println("Count for nominal values cannot be calculated. Attribute "+columnName+" treated as String.");
+                        attributeTypes[i - 1] = Attribute.STRING;
+                        break;
+                    }
+                    attributeTypes[i - 1] = Attribute.NOMINAL;
+                    stringToNominal(rs1,i);
+                    rs1.close();
+                    break;
+                case DatabaseConnection.TEXT:
+                    //System.err.println("boolean --> string");
+                    columnName = md.getColumnName(i);
+                    if(m_DataBaseConnection.getUpperCase())
+                      columnName = columnName.toUpperCase();
+                    m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+                    m_nominalStrings[i - 1] = new ArrayList<String>();
+                    query = "SELECT COUNT(DISTINCT( "+columnName+" )) FROM " + end;
+                    if (m_DataBaseConnection.execute(query) == true){
+                      rs1 = m_DataBaseConnection.getResultSet();
+                      stringToNominal(rs1,i);
+                      rs1.close();
+                    }
+                    attributeTypes[i - 1] = Attribute.STRING;
+                    break;
+                case DatabaseConnection.BOOL:
+                    //System.err.println("boolean --> nominal");
+                    attributeTypes[i - 1] = Attribute.NOMINAL;
+                    m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+                    m_nominalIndexes[i - 1].put("false", new Double(0));
+                    m_nominalIndexes[i - 1].put("true", new Double(1));
+                    m_nominalStrings[i - 1] = new ArrayList<String>();
+                    m_nominalStrings[i - 1].add("false");
+                    m_nominalStrings[i - 1].add("true");
+                    break;
+                case DatabaseConnection.DOUBLE:
+                    //System.err.println("BigDecimal --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.BYTE:
+                    //System.err.println("byte --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.SHORT:
+                    //System.err.println("short --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.INTEGER:
+                    //System.err.println("int --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.LONG:
+                    //System.err.println("long --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.FLOAT:
+                    //System.err.println("float --> numeric");
+                    attributeTypes[i - 1] = Attribute.NUMERIC;
+                    break;
+                case DatabaseConnection.DATE:
+                    attributeTypes[i - 1] = Attribute.DATE;
+                    break;
+                case DatabaseConnection.TIME:
+                  attributeTypes[i - 1] = Attribute.DATE;
+                  break;
+                default:
+                    //System.err.println("Unknown column type");
+                    attributeTypes[i - 1] = Attribute.STRING;
+            }
+        }
+        ArrayList<Attribute> attribInfo = new ArrayList<Attribute>();
+        for (int i = 0; i < numAttributes; i++) {
+            /* Fix for databases that uppercase column names */
+            //String attribName = attributeCaseFix(md.getColumnName(i + 1));
+            String attribName = md.getColumnName(i + 1);
+            switch (attributeTypes[i]) {
+                case Attribute.NOMINAL:
+                    attribInfo.add(new Attribute(attribName, m_nominalStrings[i]));
+                    break;
+                case Attribute.NUMERIC:
+                    attribInfo.add(new Attribute(attribName));
+                    break;
+                case Attribute.STRING:
+                    Attribute att = new Attribute(attribName, (ArrayList<String>)null);
+                    for (int n = 0; n < m_nominalStrings[i].size(); n++) {
+                      att.addStringValue((String) m_nominalStrings[i].get(n));
+                    }
+                    attribInfo.add(att);
+                    break;
+                case Attribute.DATE:
+                    attribInfo.add(new Attribute(attribName, (String)null));
+                    break;
+                default:
+                    throw new IOException("Unknown attribute type");
+            }
+        }
+        m_structure = new Instances(endOfQuery(true), attribInfo,0);
+        //get rid of m_idColumn
+        if(m_DataBaseConnection.getUpperCase())
+              m_idColumn = m_idColumn.toUpperCase();
+        //System.out.println(m_structure.attribute(0).name().equals(idColumn));
+        if(m_structure.attribute(0).name().equals(m_idColumn)){
+            m_oldStructure = new Instances(m_structure,0);
+            m_oldStructure.deleteAttributeAt(0);
+            //System.out.println(m_structure);
+        }
+        else
+            m_oldStructure = new Instances(m_structure,0);
+    }
+    else{
+        if(m_oldStructure == null)
+            m_oldStructure = new Instances(m_structure,0);
+    }
+    m_DataBaseConnection.disconnectFromDatabase();
+    }
+    catch(Exception ex) {
+        ex.printStackTrace();
+	printException(ex);
+    } 
+    return m_oldStructure;
+    
+  }
+  
+  
+
+  /**
+   * Return the full data set in batch mode (header and all intances at once).
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+
+    if (m_DataBaseConnection == null) {
+      throw new IOException("No source database has been specified");
+    }
+    if (getRetrieval() == INCREMENTAL) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    setRetrieval(BATCH);
+    connectToDatabase();
+    
+    
+    Instances result = null;
+    try{
+    if (m_DataBaseConnection.execute(m_query) == false) 
+      throw new Exception("Query didn't produce results");
+    ResultSet rs = m_DataBaseConnection.getResultSet();
+    ResultSetMetaData md = rs.getMetaData();
+    // Determine structure of the instances
+    int numAttributes = md.getColumnCount();
+    int [] attributeTypes = new int [numAttributes];
+    m_nominalIndexes = Utils.cast(new Hashtable [numAttributes]);
+    m_nominalStrings = Utils.cast(new ArrayList [numAttributes]);
+    for (int i = 1; i <= numAttributes; i++) {
+      switch (m_DataBaseConnection.translateDBColumnType(md.getColumnTypeName(i))) {
+	
+      case DatabaseConnection.STRING :
+        ResultSet rs1;
+        String columnName = md.getColumnName(i);
+        if(m_DataBaseConnection.getUpperCase())
+            columnName = columnName.toUpperCase();
+        String end = endOfQuery(false);
+        m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+        m_nominalStrings[i - 1] = new ArrayList<String>();
+        if(m_DataBaseConnection.execute("SELECT DISTINCT ( "
+                                        + columnName+" ) FROM "
+                                        + end
+                                        + " ORDER BY "
+                                        + columnName) == false){
+            throw new Exception("Nominal values cannot be retrieved");
+        }
+        rs1 = m_DataBaseConnection.getResultSet();
+        attributeTypes[i - 1] = Attribute.NOMINAL;
+        stringToNominal(rs1,i);
+        rs1.close();  
+	break;
+      case DatabaseConnection.TEXT:
+        columnName = md.getColumnName(i);
+        if(m_DataBaseConnection.getUpperCase())
+            columnName = columnName.toUpperCase();
+        end = endOfQuery(false);
+        m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+        m_nominalStrings[i - 1] = new ArrayList<String>();
+        if(m_DataBaseConnection.execute("SELECT DISTINCT ( "+columnName+" ) FROM "+ end) == false){
+            throw new Exception("Nominal values cannot be retrieved");
+        }
+        rs1 = m_DataBaseConnection.getResultSet();
+        attributeTypes[i - 1] = Attribute.STRING;
+        stringToNominal(rs1,i);
+        rs1.close();  
+	break;
+      case DatabaseConnection.BOOL:
+	//System.err.println("boolean --> nominal");
+	attributeTypes[i - 1] = Attribute.NOMINAL;
+	m_nominalIndexes[i - 1] = new Hashtable<String,Double>();
+	m_nominalIndexes[i - 1].put("false", new Double(0));
+	m_nominalIndexes[i - 1].put("true", new Double(1));
+	m_nominalStrings[i - 1] = new ArrayList<String>();
+	m_nominalStrings[i - 1].add("false");
+	m_nominalStrings[i - 1].add("true");
+	break;
+      case DatabaseConnection.DOUBLE:
+	//System.err.println("BigDecimal --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.BYTE:
+	//System.err.println("byte --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.SHORT:
+	//System.err.println("short --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.INTEGER:
+	//System.err.println("int --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.LONG:
+	//System.err.println("long --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.FLOAT:
+	//System.err.println("float --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DatabaseConnection.DATE:
+	attributeTypes[i - 1] = Attribute.DATE;
+	break;
+      case DatabaseConnection.TIME:
+	attributeTypes[i - 1] = Attribute.DATE;
+	break;
+      default:
+	//System.err.println("Unknown column type");
+	attributeTypes[i - 1] = Attribute.STRING;
+      }
+    }
+
+    // Step through the tuples
+    //System.err.println("Creating instances...");
+    ArrayList<Instance> instances = new ArrayList<Instance>();
+    while(rs.next()) {
+      double[] vals = new double[numAttributes];
+      for(int i = 1; i <= numAttributes; i++) {
+	switch (m_DataBaseConnection.translateDBColumnType(md.getColumnTypeName(i))) {
+	case DatabaseConnection.STRING :
+	  String str = rs.getString(i);
+	  
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+            } else {
+                Double index = (Double)m_nominalIndexes[i - 1].get(str);
+                if (index == null) {
+                    index = new Double(m_structure.attribute(i-1).addStringValue(str));
+                }
+                vals[i - 1] = index.doubleValue();
+            }
+	  break;
+	case DatabaseConnection.TEXT:
+	  str = rs.getString(i);
+
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  }
+	  else {
+	    Double index = (Double)m_nominalIndexes[i - 1].get(str);
+	    if (index == null) {
+	      index = new Double(m_structure.attribute(i-1).addStringValue(str));
+	    }
+	    vals[i - 1] = index.doubleValue();
+	  }
+	  break;
+	case DatabaseConnection.BOOL:
+	  boolean boo = rs.getBoolean(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (boo ? 1.0 : 0.0);
+	  }
+	  break;
+	case DatabaseConnection.DOUBLE:
+	  double dd = rs.getDouble(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] =  dd;
+	  }
+	  break;
+	case DatabaseConnection.BYTE:
+	  byte by = rs.getByte(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)by;
+	  }
+	  break;
+	case DatabaseConnection.SHORT:
+	  short sh = rs.getShort(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)sh;
+	  }
+	  break;
+	case DatabaseConnection.INTEGER:
+	  int in = rs.getInt(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)in;
+	  }
+	  break;
+	case DatabaseConnection.LONG:
+	  long lo = rs.getLong(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)lo;
+	  }
+	  break;
+	case DatabaseConnection.FLOAT:
+	  float fl = rs.getFloat(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)fl;
+	  }
+	  break;
+	case DatabaseConnection.DATE:
+          Date date = rs.getDate(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double)date.getTime();
+          }
+          break;
+	case DatabaseConnection.TIME:
+          Time time = rs.getTime(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double) time.getTime();
+          }
+          break;
+	default:
+	  vals[i - 1] = Utils.missingValue();
+	}
+      }
+      Instance newInst;
+      newInst = new DenseInstance(1.0, vals);
+      instances.add(newInst);
+    }   
+    
+    // Create the header and add the instances to the dataset
+    //System.err.println("Creating header...");
+    ArrayList<Attribute> attribInfo = new ArrayList<Attribute>();
+    for (int i = 0; i < numAttributes; i++) {
+      /* Fix for databases that uppercase column names */
+      //String attribName = attributeCaseFix(md.getColumnName(i + 1));
+      String attribName = md.getColumnName(i + 1);
+      switch (attributeTypes[i]) {
+      case Attribute.NOMINAL:
+	attribInfo.add(new Attribute(attribName, m_nominalStrings[i]));
+	break;
+      case Attribute.NUMERIC:
+	attribInfo.add(new Attribute(attribName));
+	break;
+      case Attribute.STRING:
+	Attribute att = new Attribute(attribName, (ArrayList<String>) null);
+	attribInfo.add(att);
+	for (int n = 0; n < m_nominalStrings[i].size(); n++) {
+	  att.addStringValue((String) m_nominalStrings[i].get(n));
+	}
+	break;
+      case Attribute.DATE:
+	attribInfo.add(new Attribute(attribName, (String)null));
+	break;
+      default:
+	throw new IOException("Unknown attribute type");
+      }
+    }
+    result = new Instances(endOfQuery(true), attribInfo, 
+				     instances.size());
+    for (int i = 0; i < instances.size(); i++) {
+      result.add((Instance)instances.get(i));
+    }
+    rs.close();
+    m_DataBaseConnection.disconnectFromDatabase();
+    //get rid of m_idColumn
+    if(m_DataBaseConnection.getUpperCase())
+        m_idColumn = m_idColumn.toUpperCase();
+    if(result.attribute(0).name().equals(m_idColumn)){
+        result.deleteAttributeAt(0);
+    }
+    m_structure = new Instances(result,0);
+    }
+    catch(Exception ex) {
+	printException(ex);
+        StringBuffer text = new StringBuffer();
+        if(m_query.equals("Select * from Results0")){
+            text.append("\n\nDatabaseLoader options:\n");
+            Enumeration enumi = listOptions();
+            while (enumi.hasMoreElements()) {
+                Option option = (Option)enumi.nextElement();
+                text.append(option.synopsis()+'\n');
+                text.append(option.description()+'\n');
+            }
+            System.out.println(text);
+        }
+    }
+    //System.out.println(result);
+    return result;
+  }
+  
+  /** 
+   * Reads an instance from a database.
+   * 
+   * @param rs the ReusltSet to load
+   * @throws Exception if instance cannot be read
+   * @return an instance read from the database
+   */  
+  private Instance readInstance(ResultSet rs) throws Exception{
+  
+      ResultSetMetaData md = rs.getMetaData();
+      int numAttributes = md.getColumnCount();
+      double[] vals = new double[numAttributes];
+      m_structure.delete();
+      for(int i = 1; i <= numAttributes; i++) {
+	switch (m_DataBaseConnection.translateDBColumnType(md.getColumnTypeName(i))) {
+	case DatabaseConnection.STRING :
+	  String str = rs.getString(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    Double index = (Double)m_nominalIndexes[i - 1].get(str);
+	    if (index == null) {
+              index = new Double(m_structure.attribute(i-1).addStringValue(str));
+	    }
+	    vals[i - 1] = index.doubleValue();
+	  }
+	  break;
+	case DatabaseConnection.TEXT:
+	  str = rs.getString(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  }
+	  else {
+	    Double index = (Double)m_nominalIndexes[i - 1].get(str);
+	    if (index == null) {
+              index = new Double(m_structure.attribute(i-1).addStringValue(str));
+	    }
+	    vals[i - 1] = index.doubleValue();
+	  }
+	  break;
+	case DatabaseConnection.BOOL:
+	  boolean boo = rs.getBoolean(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (boo ? 1.0 : 0.0);
+	  }
+	  break;
+	case DatabaseConnection.DOUBLE:
+	  //	  BigDecimal bd = rs.getBigDecimal(i, 4); 
+	  double dd = rs.getDouble(i);
+	  // Use the column precision instead of 4?
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    //	    newInst.setValue(i - 1, bd.doubleValue());
+	    vals[i - 1] =  dd;
+	  }
+	  break;
+	case DatabaseConnection.BYTE:
+	  byte by = rs.getByte(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)by;
+	  }
+	  break;
+	case DatabaseConnection.SHORT:
+	  short sh = rs.getShort(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)sh;
+	  }
+	  break;
+	case DatabaseConnection.INTEGER:
+	  int in = rs.getInt(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)in;
+	  }
+	  break;
+	case DatabaseConnection.LONG:
+	  long lo = rs.getLong(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)lo;
+	  }
+	  break;
+	case DatabaseConnection.FLOAT:
+	  float fl = rs.getFloat(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)fl;
+	  }
+	  break;
+	case DatabaseConnection.DATE:
+          Date date = rs.getDate(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double)date.getTime();
+          }
+          break;
+	case DatabaseConnection.TIME:
+          Time time = rs.getTime(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double) time.getTime();
+          }
+          break;
+	default:
+	  vals[i - 1] = Utils.missingValue();
+	}
+      }
+       Instance inst = new DenseInstance(1.0, vals);
+       //get rid of m_idColumn
+       if(m_DataBaseConnection.getUpperCase())
+              m_idColumn = m_idColumn.toUpperCase();
+       if(m_structure.attribute(0).name().equals(m_idColumn)){
+            inst.deleteAttributeAt(0);
+            m_oldStructure.add(inst);
+            inst = m_oldStructure.instance(0);
+            m_oldStructure.delete(0);
+       }
+       else{
+        //instances is added to and deleted from the structure to get the true nominal values instead of the index of the values.
+        m_structure.add(inst);
+        inst = m_structure.instance(0);
+        m_structure.delete(0);
+       }
+       return inst;
+       
+  }
+
+  /**
+   * Read the data set incrementally---get the next instance in the data 
+   * set or returns null if there are no
+   * more instances to get. If the structure hasn't yet been 
+   * determined by a call to getStructure then method does so before
+   * returning the next instance in the data set.
+   *
+   * @param structure the dataset header information, will get updated in 
+   * case of string or relational attributes
+   * @return the next instance in the data set as an Instance object or null
+   * if there are no more instances to be read
+   * @throws IOException if there is an error during parsing
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+
+    m_structure = structure;
+      
+    if (m_DataBaseConnection == null) 
+      throw new IOException("No source database has been specified"); 
+    if (getRetrieval() == BATCH) {
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+    }
+    //pseudoInremental: Load all instances into main memory in batch mode and give them incrementally to user
+    if(m_pseudoIncremental){
+        setRetrieval(INCREMENTAL);
+        if(m_datasetPseudoInc.numInstances() > 0){
+            Instance current = m_datasetPseudoInc.instance(0);
+            m_datasetPseudoInc.delete(0);
+            return current;
+        }
+        else{
+            resetStructure();
+            return null;
+        }
+    }
+    //real incremental mode. At the moment(version 1.0) only for MySQL and HSQLDB (Postgres not tested, should work)
+    setRetrieval(INCREMENTAL);
+    try{
+        if(!m_DataBaseConnection.isConnected())
+            connectToDatabase();
+        //if no key columns specified by user, try to detect automatically
+        if(m_firstTime && m_orderBy.size() == 0){
+            if(!checkForKey())
+                throw new Exception("A unique order cannot be detected automatically.\nYou have to use SELECT * in your query to enable this feature.\nMaybe JDBC driver is not able to detect key.\nDefine primary key in your database or use -P option (command line) or enter key columns in the GUI.");
+        }
+        if(m_firstTime){
+            m_firstTime = false;
+            m_rowCount = getRowCount();
+        }
+        //as long as not all rows has been loaded
+        if(m_counter < m_rowCount){
+            if (m_DataBaseConnection.execute(limitQuery(m_query,m_counter,m_choice)) == false) {
+                throw new Exception("Tuple could not be retrieved.");
+            }
+            m_counter++;
+            ResultSet rs = m_DataBaseConnection.getResultSet();
+            rs.next();
+            Instance current = readInstance(rs);
+            rs.close();
+            return current;
+        }
+        else{
+            m_DataBaseConnection.disconnectFromDatabase();
+            resetStructure();
+            return null;
+        }
+    }catch(Exception ex) {
+        printException(ex);
+    }
+    return null;
+  }
+  
+  
+  
+  /** 
+   * Gets the setting
+   * 
+   * @return the current setting
+   */  
+  public String[] getOptions() {
+      
+    Vector<String> options = new Vector<String>();
+
+    if ( (getUrl() != null) && (getUrl().length() != 0) ) {
+      options.add("-url");
+      options.add(getUrl());
+    }
+    
+    if ( (getUser() != null) && (getUser().length() != 0) ) {
+      options.add("-user");
+      options.add(getUser());
+    }
+    
+    if ( (getPassword() != null) && (getPassword().length() != 0) ) {
+      options.add("-password");
+      options.add(getPassword());
+    }
+
+    options.add("-Q"); 
+    options.add(getQuery());
+    
+    StringBuffer text = new StringBuffer();
+    for (int i = 0; i < m_orderBy.size(); i++) {
+      if (i > 0)
+        text.append(", ");
+      text.append((String) m_orderBy.get(i));
+    }
+    options.add("-P"); 
+    options.add(text.toString());
+    
+    if (m_inc)
+      options.add("-I");
+    
+    return (String[]) options.toArray(new String[options.size()]);
+  }
+  
+  /** 
+   * Lists the available options
+   * 
+   * @return an enumeration of the available options
+   */  
+  public java.util.Enumeration listOptions() {
+      
+     Vector<Option> newVector = new Vector<Option>();
+
+     newVector.add(new Option(
+           "\tThe JDBC URL to connect to.\n"
+           + "\t(default: from DatabaseUtils.props file)",
+           "url", 1, "-url <JDBC URL>"));
+     
+     newVector.add(new Option(
+           "\tThe user to connect with to the database.\n"
+           + "\t(default: none)",
+           "user", 1, "-user <name>"));
+     
+     newVector.add(new Option(
+           "\tThe password to connect with to the database.\n"
+           + "\t(default: none)",
+           "password", 1, "-password <password>"));
+     
+     newVector.add(new Option(
+	 "\tSQL query of the form\n"
+	 + "\t\tSELECT <list of columns>|* FROM <table> [WHERE]\n"
+	 + "\tto execute.\n"
+         + "\t(default: Select * From Results0)",
+	 "Q",1,"-Q <query>"));
+     
+     newVector.add(new Option(
+	 "\tList of column names uniquely defining a DB row\n"
+	 + "\t(separated by ', ').\n"
+         + "\tUsed for incremental loading.\n"
+	 + "\tIf not specified, the key will be determined automatically,\n"
+	 + "\tif possible with the used JDBC driver.\n"
+	 + "\tThe auto ID column created by the DatabaseSaver won't be loaded.",
+	 "P",1,"-P <list of column names>"));
+
+     newVector.add(new Option(
+	 "\tSets incremental loading", 
+	 "I", 0, "-I"));
+     
+     return  newVector.elements();
+  }
+  
+  /** 
+   * Sets the options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -url &lt;JDBC URL&gt;
+   *  The JDBC URL to connect to.
+   *  (default: from DatabaseUtils.props file)</pre>
+   * 
+   * <pre> -user &lt;name&gt;
+   *  The user to connect with to the database.
+   *  (default: none)</pre>
+   * 
+   * <pre> -password &lt;password&gt;
+   *  The password to connect with to the database.
+   *  (default: none)</pre>
+   * 
+   * <pre> -Q &lt;query&gt;
+   *  SQL query of the form
+   *   SELECT &lt;list of columns&gt;|* FROM &lt;table&gt; [WHERE]
+   *  to execute.
+   *  (default: Select * From Results0)</pre>
+   * 
+   * <pre> -P &lt;list of column names&gt;
+   *  List of column names uniquely defining a DB row
+   *  (separated by ', ').
+   *  Used for incremental loading.
+   *  If not specified, the key will be determined automatically,
+   *  if possible with the used JDBC driver.
+   *  The auto ID column created by the DatabaseSaver won't be loaded.</pre>
+   * 
+   * <pre> -I
+   *  Sets incremental loading</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the options
+   * @throws Exception if options cannot be set
+   */  
+  public void setOptions(String[] options) throws Exception {
+      
+    String optionString, keyString, tmpStr;
+    
+    optionString = Utils.getOption('Q', options);
+    
+    keyString = Utils.getOption('P', options);
+    
+    reset();
+    
+    tmpStr = Utils.getOption("url", options);
+    if (tmpStr.length() != 0)
+      setUrl(tmpStr);
+
+    tmpStr = Utils.getOption("user", options);
+    if (tmpStr.length() != 0)
+      setUser(tmpStr);
+    
+    tmpStr = Utils.getOption("password", options);
+    if (tmpStr.length() != 0)
+      setPassword(tmpStr);
+    
+    if (optionString.length() != 0)
+      setQuery(optionString);
+    
+    m_orderBy.clear();
+    
+    m_inc = Utils.getFlag('I', options);
+    
+    if(m_inc){
+        StringTokenizer st = new StringTokenizer(keyString, ",");
+        while (st.hasMoreTokens()) {
+            String column = st.nextToken();
+            column = column.replaceAll(" ","");
+            m_orderBy.add(column);
+        }
+    }
+  }
+  
+  /**Prints an exception
+   * @param ex the exception to print
+   */  
+  private void printException(Exception ex){
+  
+      System.out.println("\n--- Exception caught ---\n");
+	while (ex != null) {
+		System.out.println("Message:   "
+                                   + ex.getMessage ());
+                if(ex instanceof SQLException){
+                    System.out.println("SQLState:  "
+                                   + ((SQLException)ex).getSQLState ());
+                    System.out.println("ErrorCode: "
+                                   + ((SQLException)ex).getErrorCode ());
+                    ex = ((SQLException)ex).getNextException();
+                }
+                else
+                    ex = null;
+		System.out.println("");
+	}
+      
+      
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /** Main method.
+   * @param options the options
+   */
+  public static void main(String [] options) {
+    
+      DatabaseLoader atf;
+      try {
+	atf = new DatabaseLoader();
+        atf.setOptions(options);
+        atf.setSource(atf.getUrl(), atf.getUser(), atf.getPassword());
+        if(!atf.m_inc)
+            System.out.println(atf.getDataSet());
+        else{
+            Instances structure = atf.getStructure();
+            System.out.println(structure);
+            Instance temp;
+            do {
+            temp = atf.getNextInstance(structure);
+            if (temp != null) {
+                System.out.println(temp);
+            }
+            } while (temp != null);
+        }
+      } catch (Exception e) {
+	e.printStackTrace();
+        System.out.println("\n"+e.getMessage());
+      }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/DatabaseSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/DatabaseSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/DatabaseSaver.java	(revision 29)
@@ -0,0 +1,918 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a database (tested with MySQL, InstantDB, HSQLDB).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -url &lt;JDBC URL&gt;
+ *  The JDBC URL to connect to.
+ *  (default: from DatabaseUtils.props file)</pre>
+ * 
+ * <pre> -user &lt;name&gt;
+ *  The user to connect with to the database.
+ *  (default: none)</pre>
+ * 
+ * <pre> -password &lt;password&gt;
+ *  The password to connect with to the database.
+ *  (default: none)</pre>
+ * 
+ * <pre> -T &lt;table name&gt;
+ *  The name of the table.
+ *  (default: the relation name)</pre>
+ * 
+ * <pre> -P
+ *  Add an ID column as primary key. The name is specified
+ *  in the DatabaseUtils file ('idColumn'). The DatabaseLoader
+ *  won't load this column.</pre>
+ * 
+ * <pre> -i &lt;input file name&gt;
+ *  Input file in arff format that should be saved in database.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class DatabaseSaver 
+  extends AbstractSaver 
+  implements BatchConverter, IncrementalConverter, DatabaseConverter, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = 863971733782624956L;
+  
+  /** The database connection. */
+  private DatabaseConnection m_DataBaseConnection;
+  
+  /** The name of the table in which the instances should be stored. */
+  private String m_tableName;
+  
+  /** An input arff file (for command line use). */
+  private String m_inputFile;
+  
+  /** The database specific type for a string (read in from the properties file). */
+  private String m_createText;
+  
+  /** The database specific type for a double (read in from the properties file). */
+  private String m_createDouble;
+  
+  /** The database specific type for an int (read in from the properties file). */
+  private String m_createInt;
+  
+  /** The database specific type for a date (read in from the properties file). */
+  private String m_createDate;
+  
+  /** For converting the date value into a database string. */
+  private SimpleDateFormat m_DateFormat;
+  
+  /** The name of the primary key column that will be automatically generated (if enabled). The name is read from DatabaseUtils.*/
+  private String m_idColumn;
+  
+  /** counts the rows and used as a primary key value. */
+  private int m_count;
+  
+  /** Flag indicating if a primary key column should be added. */
+  private boolean m_id;
+  
+  /** Flag indicating whether the default name of the table is the relaion name or not.*/
+  private boolean m_tabName;
+  
+  /** the user name for the database. */
+  private String m_Username;
+  
+  /** the password for the database. */
+  private String m_Password;
+  
+  /** The property file for the database connection. */
+  protected static String PROPERTY_FILE = DatabaseConnection.PROPERTY_FILE;
+  
+  /** Properties associated with the database connection. */
+  protected static Properties PROPERTIES;
+
+  /** reads the property file */
+  static {
+
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+   
+    } catch (Exception ex) {
+      System.err.println("Problem reading properties. Fix before continuing.");
+      System.err.println(ex);
+    }
+  }
+  
+   /** 
+    * Constructor.
+    * 
+    * @throws Exception throws Exception if property file cannot be read
+    */
+  public DatabaseSaver() throws Exception{
+  
+      resetOptions();
+      m_createText = PROPERTIES.getProperty("CREATE_STRING");
+      m_createDouble = PROPERTIES.getProperty("CREATE_DOUBLE");
+      m_createInt = PROPERTIES.getProperty("CREATE_INT");
+      m_createDate = PROPERTIES.getProperty("CREATE_DATE", "DATETIME");
+      m_DateFormat = new SimpleDateFormat(PROPERTIES.getProperty("DateFormat", "yyyy-MM-dd HH:mm:ss"));
+      m_idColumn = PROPERTIES.getProperty("idColumn");
+  }
+  
+  /** 
+   * Resets the Saver ready to save a new data set.
+   */
+  public void resetOptions(){
+
+    super.resetOptions();
+    setRetrieval(NONE);
+    m_tableName = "";
+    m_Username = "";
+    m_Password = "";
+    m_count = 1;
+    m_id = false;
+    m_tabName = true;
+    try{
+        if(m_DataBaseConnection != null && m_DataBaseConnection.isConnected())
+            m_DataBaseConnection.disconnectFromDatabase();
+        m_DataBaseConnection = new DatabaseConnection();
+    }catch(Exception ex) {
+        printException(ex);
+    }    
+  }
+  
+  /** 
+   * Cancels the incremental saving process and tries to drop the table if 
+   * the write mode is CANCEL.
+   */  
+  public void cancel(){
+  
+      if(getWriteMode() == CANCEL){
+          try{
+              m_DataBaseConnection.update("DROP TABLE "+m_tableName);
+              if(m_DataBaseConnection.tableExists(m_tableName))
+                System.err.println("Table cannot be dropped.");
+          }catch(Exception ex) {
+              printException(ex);
+          }
+          resetOptions();
+      }
+  }
+   
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return a description of the Saver suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Writes to a database (tested with MySQL, InstantDB, HSQLDB).";
+  }
+
+  
+  /** 
+   * Sets the table's name.
+   * 
+   * @param tn the name of the table
+   */  
+  public void setTableName(String tn){
+   
+      m_tableName = tn;
+  }
+  
+  /** 
+   * Gets the table's name.
+   * 
+   * @return the table's name
+   */  
+  public String getTableName(){
+  
+      return m_tableName;
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return the tip text for this property
+   */
+  public String tableNameTipText(){
+  
+      return "Sets the name of the table.";
+  }
+  
+  /** 
+   * En/Dis-ables the automatic generation of a primary key.
+   * 
+   * @param flag flag for automatic key-genereration
+   */  
+  public void setAutoKeyGeneration(boolean flag){
+  
+      m_id = flag;
+  }
+  
+  /** 
+   * Gets whether or not a primary key will be generated automatically.
+   * 
+   * @return true if a primary key column will be generated, false otherwise
+   */  
+  public boolean getAutoKeyGeneration(){
+   
+      return m_id;
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property
+   */
+  public String autoKeyGenerationTipText(){
+  
+      return "If set to true, a primary key column is generated automatically (containing the row number as INTEGER). The name of the key is read from DatabaseUtils (idColumn)"
+        +" This primary key can be used for incremental loading (requires an unique key). This primary key will not be loaded as an attribute.";
+  }
+  
+  /** 
+   * En/Dis-ables that the relation name is used for the name of the table (default enabled).
+   * 
+   * @param flag if true the relation name is used as table name
+   */  
+  public void setRelationForTableName(boolean flag){
+  
+      m_tabName = flag;
+  }
+  
+  /** 
+   * Gets whether or not the relation name is used as name of the table.
+   * 
+   * @return true if the relation name is used as the name of the table, false otherwise
+   */  
+  public boolean getRelationForTableName(){
+   
+      return m_tabName;
+  }
+  
+  /** 
+   * Returns the tip text fo this property.
+   * 
+   * @return the tip text for this property
+   */
+  public String relationForTableNameTipText(){
+  
+      return "If set to true, the relation name will be used as name for the database table. Otherwise the user has to provide a table name.";
+  }
+  
+  /** 
+   * Sets the database URL.
+   * 
+   * @param url the URL
+   */  
+  public void setUrl(String url){
+      
+      m_DataBaseConnection.setDatabaseURL(url);
+    
+  }
+  
+  /** 
+   * Gets the database URL.
+   * 
+   * @return the URL
+   */  
+  public String getUrl(){
+  
+      return m_DataBaseConnection.getDatabaseURL();
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return the tip text for this property
+   */
+  public String urlTipText(){
+  
+      return "The URL of the database";
+  }
+  
+  /** 
+   * Sets the database user.
+   * 
+   * @param user the user name
+   */  
+  public void setUser(String user){
+      m_Username = user;
+      m_DataBaseConnection.setUsername(user);
+  }
+  
+  /** 
+   * Gets the database user.
+   * 
+   * @return the user name
+   */  
+  public String getUser(){
+   
+      return m_DataBaseConnection.getUsername();
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return the tip text for this property
+   */
+  public String userTipText(){
+  
+      return "The user name for the database";
+  }
+  
+  /** 
+   * Sets the database password.
+   * 
+   * @param password the password
+   */  
+  public void setPassword(String password){
+      m_Password = password;
+      m_DataBaseConnection.setPassword(password);
+  }
+
+  /**
+   * Returns the database password.
+   *
+   * @return the database password
+   */
+  public String getPassword() {
+    return m_DataBaseConnection.getPassword();
+  }
+  
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return the tip text for this property
+   */
+  public String passwordTipText(){
+  
+      return "The database password";
+  }
+      
+  /** 
+   * Sets the database url.
+   * 
+   * @param url the database url
+   * @param userName the user name
+   * @param password the password
+   */  
+  public void setDestination(String url, String userName, String password){
+  
+      try{
+        m_DataBaseConnection = new DatabaseConnection();
+        m_DataBaseConnection.setDatabaseURL(url);
+        m_DataBaseConnection.setUsername(userName);
+        m_DataBaseConnection.setPassword(password);
+      } catch(Exception ex) {
+            printException(ex);
+      }    
+  }
+  
+  /** 
+   * Sets the database url.
+   * 
+   * @param url the database url
+   */  
+  public void setDestination(String url){
+  
+      try{
+        m_DataBaseConnection = new DatabaseConnection();
+        m_DataBaseConnection.setDatabaseURL(url);
+        m_DataBaseConnection.setUsername(m_Username);
+        m_DataBaseConnection.setPassword(m_Password);
+      } catch(Exception ex) {
+            printException(ex);
+       }    
+  }
+  
+  /** Sets the database url using the DatabaseUtils file. */  
+  public void setDestination(){
+  
+      try{
+        m_DataBaseConnection = new DatabaseConnection();
+        m_DataBaseConnection.setUsername(m_Username);
+        m_DataBaseConnection.setPassword(m_Password);
+      } catch(Exception ex) {
+            printException(ex);
+       }    
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.NO_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+  
+   /**
+   * Opens a connection to the database.
+   *
+   */
+  public void connectToDatabase() {
+   
+      try{
+        if(!m_DataBaseConnection.isConnected())
+            m_DataBaseConnection.connectToDatabase();
+      } catch(Exception ex) {
+	printException(ex);
+       }    
+  }
+  
+  /** 
+   * Writes the structure (header information) to a database by creating a new table.
+   * 
+   * @throws Exception if something goes wrong
+   */
+  private void writeStructure() throws Exception{
+  
+      StringBuffer query = new StringBuffer();
+      Instances structure = getInstances();
+      query.append("CREATE TABLE ");
+      if(m_tabName || m_tableName.equals(""))
+        m_tableName = m_DataBaseConnection.maskKeyword(structure.relationName());
+      if(m_DataBaseConnection.getUpperCase()){
+        m_tableName = m_tableName.toUpperCase();
+        m_createInt = m_createInt.toUpperCase(); 
+        m_createDouble = m_createDouble.toUpperCase(); 
+        m_createText = m_createText.toUpperCase(); 
+        m_createDate = m_createDate.toUpperCase(); 
+      }
+      m_tableName = m_tableName.replaceAll("[^\\w]","_");
+      m_tableName = m_DataBaseConnection.maskKeyword(m_tableName);
+      query.append(m_tableName);
+      if(structure.numAttributes() == 0)
+          throw new Exception("Instances have no attribute.");
+      query.append(" ( ");
+      if(m_id){
+        if(m_DataBaseConnection.getUpperCase())
+              m_idColumn = m_idColumn.toUpperCase();
+        query.append(m_DataBaseConnection.maskKeyword(m_idColumn));
+        query.append(" ");
+        query.append(m_createInt);
+        query.append(" PRIMARY KEY,");
+      }
+      for(int i = 0;i < structure.numAttributes(); i++){
+          Attribute att = structure.attribute(i);
+          String attName = att.name();
+          attName = attName.replaceAll("[^\\w]","_");
+          attName = m_DataBaseConnection.maskKeyword(attName);
+          if(m_DataBaseConnection.getUpperCase())
+              query.append(attName.toUpperCase());
+          else
+              query.append(attName);
+          if(att.isDate())
+              query.append(" " + m_createDate);
+          else{
+              if(att.isNumeric())
+                  query.append(" "+m_createDouble);
+              else
+                  query.append(" "+m_createText);
+          }
+          if(i != structure.numAttributes()-1)
+              query.append(", ");
+      }
+      query.append(" )");
+      //System.out.println(query.toString());
+      m_DataBaseConnection.update(query.toString());
+      m_DataBaseConnection.close();
+      if(!m_DataBaseConnection.tableExists(m_tableName)){
+          throw new IOException("Table cannot be built.");
+      }
+  }
+  
+  /**
+   * inserts the given instance into the table.
+   * 
+   * @param inst the instance to insert
+   * @throws Exception if something goes wrong
+   */
+  private void writeInstance(Instance inst) throws Exception{
+  
+      StringBuffer insert = new StringBuffer();
+      insert.append("INSERT INTO ");
+      insert.append(m_tableName);
+      insert.append(" VALUES ( ");
+      if(m_id){
+        insert.append(m_count);
+        insert.append(", ");
+        m_count++;
+      }
+      for(int j = 0; j < inst.numAttributes(); j++){
+        if(inst.isMissing(j))
+            insert.append("NULL");
+        else{
+            if((inst.attribute(j)).isDate())
+                insert.append("'" + m_DateFormat.format((long) inst.value(j)) + "'");
+            else if((inst.attribute(j)).isNumeric())
+                insert.append(inst.value(j));
+            else{
+                String stringInsert = "'"+inst.stringValue(j)+"'";
+                if (stringInsert.length() > 2)
+                  stringInsert = stringInsert.replaceAll("''","'");
+                insert.append(stringInsert);
+            }
+        }
+        if(j != inst.numAttributes()-1)
+            insert.append(", ");
+      }
+      insert.append(" )");
+      //System.out.println(insert.toString());
+      if (m_DataBaseConnection.update(insert.toString()) < 1) {
+        throw new IOException("Tuple cannot be inserted.");
+      }
+      else {
+	m_DataBaseConnection.close();
+      }
+  }
+  
+  /** 
+   * Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method. When a structure is set, a table is created. 
+   * 
+   * @param inst the instance to save
+   * @throws IOException throws IOEXception.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+  
+      int writeMode = getWriteMode();
+      Instances structure = getInstances();
+      
+      if(m_DataBaseConnection == null)
+           throw new IOException("No database has been set up.");
+      if(getRetrieval() == BATCH)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      setRetrieval(INCREMENTAL);
+      
+      try{
+        if(!m_DataBaseConnection.isConnected())
+            connectToDatabase();
+        if(writeMode == WAIT){
+            if(structure == null){
+                setWriteMode(CANCEL);
+                if(inst != null)
+                    throw new Exception("Structure(Header Information) has to be set in advance");
+            }
+            else
+                setWriteMode(STRUCTURE_READY);
+            writeMode = getWriteMode();
+        }
+        if(writeMode == CANCEL){
+          cancel();
+        }
+        if(writeMode == STRUCTURE_READY){
+          setWriteMode(WRITE);
+          writeStructure();
+          writeMode = getWriteMode();
+        }
+        if(writeMode == WRITE){
+          if(structure == null)
+              throw new IOException("No instances information available.");
+          if(inst != null){
+            //write instance 
+            writeInstance(inst);  
+          }
+          else{
+          //close
+              m_DataBaseConnection.disconnectFromDatabase();
+              resetStructure();
+              m_count = 1;
+          }
+        }
+      }catch(Exception ex) {
+            printException(ex);
+       }    
+  }
+  
+  /** 
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException throws IOException
+   */
+  public void writeBatch() throws IOException {
+  
+      Instances instances = getInstances();
+      if(instances == null)
+          throw new IOException("No instances to save");
+      if(getRetrieval() == INCREMENTAL)
+          throw new IOException("Batch and incremental saving cannot be mixed.");
+      if(m_DataBaseConnection == null)
+           throw new IOException("No database has been set up.");
+      setRetrieval(BATCH);
+      try{
+          if(!m_DataBaseConnection.isConnected())
+              connectToDatabase();
+          setWriteMode(WRITE);
+          writeStructure();
+          for(int i = 0; i < instances.numInstances(); i++){
+            writeInstance(instances.instance(i));
+          }
+          m_DataBaseConnection.disconnectFromDatabase();
+          setWriteMode(WAIT);
+          resetStructure();
+          m_count = 1;
+      } catch(Exception ex) {
+            printException(ex);
+       }    
+  }
+
+  /**
+   * Prints an exception.
+   * 
+   * @param ex the exception to print
+   */  
+  private void printException(Exception ex){
+  
+      System.out.println("\n--- Exception caught ---\n");
+	while (ex != null) {
+		System.out.println("Message:   "
+                                   + ex.getMessage ());
+                if(ex instanceof SQLException){
+                    System.out.println("SQLState:  "
+                                   + ((SQLException)ex).getSQLState ());
+                    System.out.println("ErrorCode: "
+                                   + ((SQLException)ex).getErrorCode ());
+                    ex = ((SQLException)ex).getNextException();
+                }
+                else
+                    ex = null;
+		System.out.println("");
+	}
+      
+  }
+  
+  /** 
+   * Gets the setting.
+   * 
+   * @return the current setting
+   */  
+  public String[] getOptions() {
+    Vector<String> options = new Vector<String>();
+
+    if ( (getUrl() != null) && (getUrl().length() != 0) ) {
+      options.add("-url");
+      options.add(getUrl());
+    }
+
+    if ( (getUser() != null) && (getUser().length() != 0) ) {
+      options.add("-user");
+      options.add(getUser());
+    }
+
+    if ( (getPassword() != null) && (getPassword().length() != 0) ) {
+      options.add("-password");
+      options.add(getPassword());
+    }
+
+    if ( (m_tableName != null) && (m_tableName.length() != 0) ) {
+      options.add("-T"); 
+      options.add(m_tableName);
+    }
+    
+    if (m_id)
+        options.add("-P");
+
+    if ( (m_inputFile != null) && (m_inputFile.length() != 0) ) {
+      options.add("-i"); 
+      options.add(m_inputFile);
+    }
+    
+    return (String[]) options.toArray(new String[options.size()]);
+  }
+  
+  /** 
+   * Lists the available options.
+   * 
+   * @return an enumeration of the available options
+   */  
+  public java.util.Enumeration listOptions() {
+      
+     Vector<Option> newVector = new Vector<Option>();
+
+     newVector.addElement(new Option(
+           "\tThe JDBC URL to connect to.\n"
+           + "\t(default: from DatabaseUtils.props file)",
+           "url", 1, "-url <JDBC URL>"));
+     
+     newVector.addElement(new Option(
+           "\tThe user to connect with to the database.\n"
+           + "\t(default: none)",
+           "user", 1, "-user <name>"));
+     
+     newVector.addElement(new Option(
+           "\tThe password to connect with to the database.\n"
+           + "\t(default: none)",
+           "password", 1, "-password <password>"));
+     
+     newVector.addElement(new Option(
+           "\tThe name of the table.\n"
+           + "\t(default: the relation name)",
+           "T", 1, "-T <table name>"));
+     
+     newVector.addElement(new Option(
+           "\tAdd an ID column as primary key. The name is specified\n"
+           + "\tin the DatabaseUtils file ('idColumn'). The DatabaseLoader\n"
+           + "\twon't load this column.",
+           "P", 0, "-P"));
+     
+     newVector.addElement(new Option(
+           "\tInput file in arff format that should be saved in database.",
+           "i", 1, "-i <input file name>"));
+     
+     return  newVector.elements();
+  }
+  
+  /** 
+   * Sets the options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -url &lt;JDBC URL&gt;
+   *  The JDBC URL to connect to.
+   *  (default: from DatabaseUtils.props file)</pre>
+   * 
+   * <pre> -user &lt;name&gt;
+   *  The user to connect with to the database.
+   *  (default: none)</pre>
+   * 
+   * <pre> -password &lt;password&gt;
+   *  The password to connect with to the database.
+   *  (default: none)</pre>
+   * 
+   * <pre> -T &lt;table name&gt;
+   *  The name of the table.
+   *  (default: the relation name)</pre>
+   * 
+   * <pre> -P
+   *  Add an ID column as primary key. The name is specified
+   *  in the DatabaseUtils file ('idColumn'). The DatabaseLoader
+   *  won't load this column.</pre>
+   * 
+   * <pre> -i &lt;input file name&gt;
+   *  Input file in arff format that should be saved in database.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the options
+   * @throws Exception if options cannot be set
+   */  
+  public void setOptions(String[] options) throws Exception {
+      
+    String tableString, inputString, tmpStr;
+    
+    resetOptions();
+
+    tmpStr = Utils.getOption("url", options);
+    if (tmpStr.length() != 0)
+      setUrl(tmpStr);
+
+    tmpStr = Utils.getOption("user", options);
+    if (tmpStr.length() != 0)
+      setUser(tmpStr);
+
+    tmpStr = Utils.getOption("password", options);
+    if (tmpStr.length() != 0)
+      setPassword(tmpStr);
+    
+    tableString = Utils.getOption('T', options);
+    
+    inputString = Utils.getOption('i', options);
+    
+    if(tableString.length() != 0){
+        m_tableName = tableString;
+        m_tabName = false;
+    }
+    
+    m_id = Utils.getFlag('P', options);
+    
+    if(inputString.length() != 0){
+        try{
+            m_inputFile = inputString;
+            ArffLoader al = new ArffLoader();
+            File inputFile = new File(inputString);
+            al.setSource(inputFile);
+            setInstances(al.getDataSet());
+            //System.out.println(getInstances());
+            if(tableString.length() == 0)
+                m_tableName = getInstances().relationName();
+        }catch(Exception ex) {
+            printException(ex);
+            ex.printStackTrace();
+      }    
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param options should contain the options of a Saver.
+   */
+  public static void main(String [] options) {
+      
+      StringBuffer text = new StringBuffer();
+      text.append("\n\nDatabaseSaver options:\n");
+      try {
+	DatabaseSaver asv = new DatabaseSaver();
+        try {
+          Enumeration enumi = asv.listOptions();
+          while (enumi.hasMoreElements()) {
+            Option option = (Option)enumi.nextElement();
+            text.append(option.synopsis()+'\n');
+            text.append(option.description()+'\n');
+        }  
+          asv.setOptions(options);
+          asv.setDestination();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+	}
+        //incremental
+        
+        /*asv.setRetrieval(INCREMENTAL);
+        Instances instances = asv.getInstances();
+        asv.setStructure(instances);
+        for(int i = 0; i < instances.numInstances(); i++){ //last instance is null and finishes incremental saving
+            asv.writeIncremental(instances.instance(i));
+        }
+        asv.writeIncremental(null);*/
+        
+        
+        //batch
+        asv.writeBatch();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+        System.out.println(text);
+	}
+      
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/FileSourcedConverter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/FileSourcedConverter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/FileSourcedConverter.java	(revision 29)
@@ -0,0 +1,85 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FileSourcedConverter.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core.converters;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Interface to a loader/saver that loads/saves from a file source.
+ *
+ * @author Mark Hall
+ * @version $Revision: 5953 $
+ */
+public interface FileSourcedConverter {
+
+  /**
+   * Get the file extension used for this type of file
+   *
+   * @return the file extension
+   */
+  public String getFileExtension();
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions();
+
+  /**
+   * Get a one line description of the type of file
+   *
+   * @return a description of the file type
+   */
+  public String getFileDescription();
+
+  /**
+   * Set the file to load from/ to save in
+   *
+   * @param file the file to load from
+   * @exception IOException if an error occurs
+   */
+  public void setFile(File file) throws IOException;
+
+  /**
+   * Return the current source file/ destination file
+   *
+   * @return a <code>File</code> value
+   */
+  public File retrieveFile();
+
+  /**
+   * Set whether to use relative rather than absolute paths
+   *
+   * @param rp true if relative paths are to be used
+   */
+  public void setUseRelativePath(boolean rp);
+
+  /**
+   * Gets whether relative paths are to be used
+   *
+   * @return true if relative paths are to be used
+   */
+  public boolean getUseRelativePath();
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/IncrementalConverter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/IncrementalConverter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/IncrementalConverter.java	(revision 29)
@@ -0,0 +1,32 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncremenalConverter.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core.converters;
+
+/**
+ * Marker interface for a loader/saver that can retrieve instances incrementally
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision 1.0 $
+ */
+public interface IncrementalConverter {
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/JSONLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/JSONLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/JSONLoader.java	(revision 29)
@@ -0,0 +1,308 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JSONLoader.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.json.JSONInstances;
+import weka.core.json.JSONNode;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.zip.GZIPInputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in the JSON format.<br/>
+ * It automatically decompresses the data if the extension is '.json.gz'.<br/>
+ * <br/>
+ * For more information, see JSON homepage:<br/>
+ * http://www.json.org/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5784 $
+ * @see Loader
+ */
+public class JSONLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, URLSourcedLoader {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 3764533621135196582L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = ".json";
+
+  /** the extension for compressed files. */
+  public static String FILE_EXTENSION_COMPRESSED = FILE_EXTENSION + ".gz";
+
+  /** the url. */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** the loaded JSON object. */
+  protected JSONNode m_JSON;
+  
+  /**
+   * Returns a string describing this Loader.
+   * 
+   * @return 		a description of the Loader suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads a source that is in the JSON format.\n"
+      + "It automatically decompresses the data if the extension is '" 
+      + FILE_EXTENSION_COMPRESSED + "'.\n\n"
+      + "For more information, see JSON homepage:\n"
+      + "http://www.json.org/";
+  }
+
+  /**
+   * Get the file extension used for JSON files.
+   *
+   * @return 		the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{FILE_EXTENSION, FILE_EXTENSION_COMPRESSED};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "JSON Instances files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set.
+   * 
+   * @throws IOException 	if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    m_JSON      = null;
+
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    }
+    else if ((m_URL != null) && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file 		the source file.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(File file) throws IOException {
+    m_structure = null;
+    m_JSON      = null;
+    
+    setRetrieval(NONE);
+
+    if (file == null)
+      throw new IOException("Source file object is null!");
+
+    try {
+      if (file.getName().endsWith(FILE_EXTENSION_COMPRESSED))
+	setSource(new GZIPInputStream(new FileInputStream(file)));
+      else
+	setSource(new FileInputStream(file));
+    }
+    catch (FileNotFoundException ex) {
+      throw new IOException("File not found");
+    }
+    
+    m_sourceFile = file;
+    m_File       = file.getAbsolutePath();
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url 	the source url.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure = null;
+    m_JSON      = null;
+    
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+  
+  /**
+   * Set the url to load from.
+   *
+   * @param url 		the url to load from
+   * @throws IOException 	if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url.
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in 			the source InputStream.
+   * @throws IOException 	if initialization of reader fails.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL  = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty set 
+   * 				of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+
+    if (m_structure == null) {
+      try {
+	m_JSON      = JSONNode.read(m_sourceReader);
+	m_structure = new Instances(JSONInstances.toHeader(m_JSON), 0);
+      }
+      catch (IOException ioe) {
+	// just re-throw it
+	throw ioe;
+      }
+      catch (Exception e) {
+	throw new RuntimeException(e);
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+
+    setRetrieval(BATCH);
+    if (m_structure == null)
+      getStructure();
+
+    try {
+      // close the stream
+      m_sourceReader.close();
+    } catch (Exception ex) {
+    }
+
+    return JSONInstances.toInstances(m_JSON);
+  }
+
+  /**
+   * JSONLoader is unable to process a data set incrementally.
+   *
+   * @param structure		ignored
+   * @return 			never returns without throwing an exception
+   * @throws IOException 	always. JSONLoader is unable to process a 
+   * 				data set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("JSONLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5784 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new JSONLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/JSONSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/JSONSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/JSONSaver.java	(revision 29)
@@ -0,0 +1,415 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JSONSaver.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.json.JSONInstances;
+import weka.core.json.JSONNode;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in JSON format.<br/>
+ * The data can be compressed with gzip, in order to save space.<br/>
+ * <br/>
+ * For more information, see JSON homepage:<br/>
+ * http://www.json.org/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ * <pre> -C &lt;class index&gt;
+ *  The class index (first and last are valid as well).
+ *  (default: last)</pre>
+ * 
+ * <pre> -compress
+ *  Compresses the data (uses '.json.gz' as extension instead of '.json')
+ *  (default: off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class JSONSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -1047134047244534557L;
+
+  /** the class index. */
+  protected SingleIndex m_ClassIndex = new SingleIndex(); 
+  
+  /** whether to compress the output. */
+  protected boolean m_CompressOutput = false;
+  
+  /**
+   * Constructor.
+   */
+  public JSONSaver(){
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Writes to a destination that is in JSON format.\n"
+      + "The data can be compressed with gzip, in order to save space.\n\n"
+      + "For more information, see JSON homepage:\n"
+      + "http://www.json.org/";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>      result;
+    
+    result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(
+        new Option(
+            "\tThe class index (first and last are valid as well).\n"
+            + "\t(default: last)",
+            "C", 1, "-C <class index>"));
+    
+    result.addElement(
+        new Option(
+            "\tCompresses the data (uses '" 
+            + JSONLoader.FILE_EXTENSION_COMPRESSED 
+            + "' as extension instead of '" 
+            + JSONLoader.FILE_EXTENSION + "')\n"
+            + "\t(default: off)",
+            "compress", 0, "-compress"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup.
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector<String>    	result;
+    String[]  	options;
+
+    result = new Vector<String>();
+
+    if (getClassIndex().length() != 0) {
+      result.add("-C");
+      result.add(getClassIndex());
+    }
+
+    if (getCompressOutput())
+      result.add("-compress");
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   * <pre> -C &lt;class index&gt;
+   *  The class index (first and last are valid as well).
+   *  (default: last)</pre>
+   * 
+   * <pre> -compress
+   *  Compresses the data (uses '.json.gz' as extension instead of '.json')
+   *  (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setClassIndex(tmpStr);
+    else
+      setClassIndex("last");
+
+    setCompressOutput(Utils.getFlag("compress", options));
+    
+    super.setOptions(options);
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "JSON data files";
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{JSONLoader.FILE_EXTENSION, JSONLoader.FILE_EXTENSION_COMPRESSED};
+  }
+  
+  /** 
+   * Sets the destination file.
+   * 
+   * @param outputFile the destination file.
+   * @throws IOException throws an IOException if file cannot be set
+   */
+  public void setFile(File outputFile) throws IOException  {
+    if (outputFile.getAbsolutePath().endsWith(JSONLoader.FILE_EXTENSION_COMPRESSED))
+      setCompressOutput(true);
+    
+    super.setFile(outputFile);
+  }
+  
+  /**
+   * Resets the Saver.
+   */
+  public void resetOptions() {
+    super.resetOptions();
+    
+    if (getCompressOutput())
+      setFileExtension(JSONLoader.FILE_EXTENSION_COMPRESSED);
+    else
+      setFileExtension(JSONLoader.FILE_EXTENSION);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Sets the class index (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the class attribute.
+   *
+   * @return 		the index of the class attribute
+   */
+  public String getClassIndex() {
+    return m_ClassIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the class attribute.
+   *
+   * @param value 	the index of the class attribute
+   */
+  public void setClassIndex(String value) {
+    m_ClassIndex.setSingleIndex(value);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String compressOutputTipText() {
+    return "Optional compression of the output data";
+  }
+
+  /**
+   * Gets whether the output data is compressed.
+   *
+   * @return 		true if the output data is compressed
+   */
+  public boolean getCompressOutput() {
+    return m_CompressOutput;
+  }
+
+  /**
+   * Sets whether to compress the output.
+   *
+   * @param value 	if truee the output will be compressed
+   */
+  public void setCompressOutput(boolean value) {
+    m_CompressOutput = value;
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.STRING_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets instances that should be stored.
+   *
+   * @param instances 	the instances
+   */
+  public void setInstances(Instances instances) {
+    if (m_ClassIndex.getSingleIndex().length() != 0) {
+      m_ClassIndex.setUpper(instances.numAttributes() - 1);
+      instances.setClassIndex(m_ClassIndex.getIndex());
+    }
+    
+    super.setInstances(instances);
+  }
+  
+  /** 
+   * Sets the destination output stream.
+   * 
+   * @param output 		the output stream.
+   * @throws IOException 	throws an IOException if destination cannot be set
+   */
+  public void setDestination(OutputStream output) throws IOException {
+    if (getCompressOutput())
+      super.setDestination(new GZIPOutputStream(output));
+    else
+      super.setDestination(output);
+  }
+  
+  /**
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException 	throws IOException if saving in batch mode 
+   * 				is not possible
+   */
+  public void writeBatch() throws IOException {
+    if (getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    setRetrieval(BATCH);
+    setWriteMode(WRITE);
+    
+    PrintWriter outW;
+    if ((retrieveFile() == null) && (getWriter() == null))
+      outW = new PrintWriter(System.out);
+    else
+      outW = new PrintWriter(getWriter());
+    
+    JSONNode json = JSONInstances.toJSON(getInstances());
+    StringBuffer buffer = new StringBuffer();
+    json.toString(buffer);
+    outW.println(buffer.toString());
+    outW.flush();
+    
+    if (getWriter() != null)
+      outW.close();
+    
+    setWriteMode(WAIT);
+    outW = null;
+    resetWriter();
+    setWriteMode(CANCEL);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new JSONSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/LibSVMLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/LibSVMLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/LibSVMLoader.java	(revision 29)
@@ -0,0 +1,405 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LibSVMLoader.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in libsvm format.<br/>
+ * <br/>
+ * For more information about libsvm see:<br/>
+ * <br/>
+ * http://www.csie.ntu.edu.tw/~cjlin/libsvm/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Loader
+ */
+public class LibSVMLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, URLSourcedLoader {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4988360125354664417L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = ".libsvm";
+
+  /** the url. */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** the buffer of the rows read so far. */
+  protected Vector<double[]> m_Buffer = null;
+  
+  /**
+   * Returns a string describing this Loader.
+   * 
+   * @return 		a description of the Loader suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads a source that is in libsvm format.\n\n"
+      + "For more information about libsvm see:\n\n"
+      + "http://www.csie.ntu.edu.tw/~cjlin/libsvm/";
+  }
+
+  /**
+   * Get the file extension used for libsvm files.
+   *
+   * @return 		the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "libsvm data files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set.
+   * 
+   * @throws IOException 	if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    if ((m_File != null) && (new File(m_File)).isFile()) {
+      setFile(new File(m_File));
+    }
+    else if ((m_URL != null) && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url 	the source url.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+
+  /**
+   * Set the url to load from.
+   *
+   * @param url 		the url to load from
+   * @throws IOException 		if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url.
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in 			the source InputStream.
+   * @throws IOException 	if initialization of reader fails.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL  = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+
+  /**
+   * turns a libsvm row into a double array with the class as the last
+   * entry.
+   * 
+   * @param row		the row to turn into a double array
+   * @return		the corresponding double array
+   */
+  protected double[] libsvmToArray(String row) {
+    double[]		result;
+    StringTokenizer	tok;
+    int			index;
+    int			max;
+    String		col;
+    double		value;
+
+    // determine max index
+    max = 0;
+    tok = new StringTokenizer(row, " \t");
+    tok.nextToken();  // skip class
+    while (tok.hasMoreTokens()) {
+      col   = tok.nextToken();
+      index = Integer.parseInt(col.substring(0, col.indexOf(":")));
+      if (index > max)
+	max = index;
+    }
+
+    // read values into array
+    tok    = new StringTokenizer(row, " \t");
+    result = new double[max + 1];
+    
+    // 1. class
+    result[result.length - 1] = Double.parseDouble(tok.nextToken());
+    
+    // 2. attributes
+    while (tok.hasMoreTokens()) {
+      col   = tok.nextToken();
+      index = Integer.parseInt(col.substring(0, col.indexOf(":")));
+      value = Double.parseDouble(col.substring(col.indexOf(":") + 1));
+      result[index - 1] = value;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * determines the number of attributes, if the number of attributes in the
+   * given row is greater than the current amount then this number will be
+   * returned, otherwise the current number.
+   * 
+   * @param row		row to determine the number of attributes from
+   * @param num		the current number of attributes
+   * @return 		the new number of attributes
+   */
+  protected int determineNumAttributes(String row, int num) {
+    int		result;
+    int		count;
+    
+    result = num;
+    
+    count = libsvmToArray(row).length;
+    if (count > result)
+      result = count;
+    
+    return result;
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty set 
+   * 				of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    StringBuffer	line;
+    int			cInt;
+    char		c;
+    int			numAtt;
+    ArrayList<Attribute>		atts;
+    int			i;
+    String		relName;
+    
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+
+    if (m_structure == null) {
+      m_Buffer = new Vector<double[]>();
+      try {
+	// determine number of attributes
+	numAtt = 0;
+	line   = new StringBuffer();
+	while ((cInt = m_sourceReader.read()) != -1) {
+	  c = (char) cInt;
+	  if ((c == '\n') || (c == '\r')) {
+	    if (line.length() > 0) {
+	      m_Buffer.add(libsvmToArray(line.toString()));
+	      numAtt = determineNumAttributes(line.toString(), numAtt);
+	    }
+	    line = new StringBuffer();
+	  }
+	  else {
+	    line.append(c);
+	  }
+	}
+	
+	// last line?
+	if (line.length() != 0) {
+	  m_Buffer.add(libsvmToArray(line.toString()));
+	  numAtt = determineNumAttributes(line.toString(), numAtt);
+	}
+	
+	// generate header
+	atts = new ArrayList<Attribute>(numAtt);
+	for (i = 0; i < numAtt - 1; i++)
+	  atts.add(new Attribute("att_" + (i+1)));
+	atts.add(new Attribute("class"));
+	
+	if (!m_URL.equals("http://"))
+	  relName = m_URL;
+	else
+	  relName = m_File;
+	
+	m_structure = new Instances(relName, atts, 0);
+	m_structure.setClassIndex(m_structure.numAttributes() - 1);
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+	throw new IOException("Unable to determine structure as libsvm: " + ex);
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    Instances 	result;
+    double[]	sparse;
+    double[]	data;
+    int		i;
+
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+
+    setRetrieval(BATCH);
+    if (m_structure == null)
+      getStructure();
+
+    result = new Instances(m_structure, 0);
+
+    // create instances from buffered arrays
+    for (i = 0; i < m_Buffer.size(); i++) {
+      sparse = (double[]) m_Buffer.get(i);
+      
+      if (sparse.length != m_structure.numAttributes()) {
+	data = new double[m_structure.numAttributes()];
+	// attributes
+	System.arraycopy(sparse, 0, data, 0, sparse.length - 1);
+	// class
+	data[data.length - 1] = sparse[sparse.length - 1];
+      }
+      else {
+	data = sparse;
+      }
+      
+      result.add(new SparseInstance(1, data));
+    }
+
+    try {
+      // close the stream
+      m_sourceReader.close();
+    } catch (Exception ex) {
+
+    }
+    
+    return result;
+  }
+
+  /**
+   * LibSVmLoader is unable to process a data set incrementally.
+   *
+   * @param structure 		ignored
+   * @return 			never returns without throwing an exception
+   * @throws IOException 	always. LibSVMLoader is unable to process a 
+   * 				data set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("LibSVMLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new LibSVMLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/LibSVMSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/LibSVMSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/LibSVMSaver.java	(revision 29)
@@ -0,0 +1,412 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LibSVMSaver.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in libsvm format.<br/>
+ * <br/>
+ * For more information about libsvm see:<br/>
+ * <br/>
+ * http://www.csie.ntu.edu.tw/~cjlin/libsvm/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ * <pre> -c &lt;class index&gt;
+ *  The class index
+ *  (default: last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class LibSVMSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 2792295817125694786L;
+
+  /** the file extension */
+  public static String FILE_EXTENSION = LibSVMLoader.FILE_EXTENSION;
+
+  /** the class index */
+  protected SingleIndex m_ClassIndex = new SingleIndex("last"); 
+
+  /**
+   * Constructor
+   */
+  public LibSVMSaver(){
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this Saver
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Writes to a destination that is in libsvm format.\n\n"
+      + "For more information about libsvm see:\n\n"
+      + "http://www.csie.ntu.edu.tw/~cjlin/libsvm/";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>      result;
+    
+    result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(
+        new Option(
+            "\tThe class index\n"
+            + "\t(default: last)",
+            "c", 1, "-c <class index>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector<String>    	result;
+    String[]  	options;
+
+    result = new Vector<String>();
+
+    result.add("-c");
+    result.add(getClassIndex());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   * <pre> -c &lt;class index&gt;
+   *  The class index
+   *  (default: last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0)
+      setClassIndex(tmpStr);
+    else
+      setClassIndex("last");
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "libsvm data files";
+  }
+  
+  /**
+   * Resets the Saver 
+   */
+  public void resetOptions() {
+    super.resetOptions();
+    setFileExtension(LibSVMLoader.FILE_EXTENSION);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Sets the class index (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the class attribute.
+   *
+   * @return 		the index of the class attribute
+   */
+  public String getClassIndex() {
+    return m_ClassIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the class attribute.
+   *
+   * @param value 	the index of the class attribute
+   */
+  public void setClassIndex(String value) {
+    m_ClassIndex.setSingleIndex(value);
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets instances that should be stored.
+   *
+   * @param instances 	the instances
+   */
+  public void setInstances(Instances instances) {
+    m_ClassIndex.setUpper(instances.numAttributes() - 1);
+    instances.setClassIndex(m_ClassIndex.getIndex());
+    
+    super.setInstances(instances);
+  }
+  
+  /**
+   * turns the instance into a libsvm row
+   * 
+   * @param inst	the instance to transform
+   * @return		the generated libsvm row
+   */
+  protected String instanceToLibsvm(Instance inst) {
+    StringBuffer	result;
+    int			i;
+    
+    // class
+    result = new StringBuffer("" + inst.classValue());
+
+    // attributes
+    for (i = 0; i < inst.numAttributes(); i++) {
+      if (i == inst.classIndex())
+	continue;
+      if (inst.value(i) == 0)
+	continue;
+      result.append(" " + (i+1) + ":" + inst.value(i));
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * 
+   * @param inst 		the instance to save
+   * @throws IOException 	throws IOEXception if an instance cannot be 
+   * 				saved incrementally.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+    int writeMode = getWriteMode();
+    Instances structure = getInstances();
+    PrintWriter outW = null;
+    
+    if ((getRetrieval() == BATCH) || (getRetrieval() == NONE))
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+
+    if (getWriter() != null)
+      outW = new PrintWriter(getWriter());
+    
+    if (writeMode == WAIT) {
+      if (structure == null) {
+	setWriteMode(CANCEL);
+	if (inst != null)
+	  System.err.println("Structure (Header Information) has to be set in advance");
+      }
+      else {
+	setWriteMode(STRUCTURE_READY);
+      }
+      writeMode = getWriteMode();
+    }
+    
+    if (writeMode == CANCEL) {
+      if (outW != null)
+	outW.close();
+      cancel();
+    }
+    
+    // header
+    if (writeMode == STRUCTURE_READY) {
+      setWriteMode(WRITE);
+      // no header
+      writeMode = getWriteMode();
+    }
+
+    // row
+    if (writeMode == WRITE){
+      if (structure == null)
+	throw new IOException("No instances information available.");
+      
+      if (inst != null) {
+	//write instance 
+	if ((retrieveFile() == null) || (outW == null)) {
+	  System.out.println(instanceToLibsvm(inst));
+	}
+	else {
+	  outW.println(instanceToLibsvm(inst));
+	  m_incrementalCounter++;
+	  //flush every 100 instances
+	  if (m_incrementalCounter > 100){
+	    m_incrementalCounter = 0;
+	    outW.flush();
+	  }
+	}
+      }
+      else{
+	//close
+	if (outW != null) {
+	  outW.flush();
+	  outW.close();
+	}
+	m_incrementalCounter = 0;
+	resetStructure();
+	outW = null;
+	resetWriter();
+      }
+    }
+  }
+  
+  /**
+   * Writes a Batch of instances
+   * 
+   * @throws IOException 	throws IOException if saving in batch mode 
+   * 				is not possible
+   */
+  public void writeBatch() throws IOException {
+    if (getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    setRetrieval(BATCH);
+    setWriteMode(WRITE);
+
+    if ((retrieveFile() == null) && (getWriter() == null)) {
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	System.out.println(instanceToLibsvm(getInstances().instance(i)));
+      setWriteMode(WAIT);
+    }
+    else {
+      PrintWriter outW = new PrintWriter(getWriter());
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	outW.println(instanceToLibsvm(getInstances().instance(i)));
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new LibSVMSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/Loader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/Loader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/Loader.java	(revision 29)
@@ -0,0 +1,172 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Loader.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+/** 
+ * Interface to something that can load Instances from an input source in some
+ * format.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Loader
+  extends Serializable, RevisionHandler {
+
+  /**
+   * Resets the Loader object ready to begin loading.
+   * If there is an existing source, implementations should
+   * attempt to reset in such a fashion as to be able to
+   * load from the beginning of the source.
+   * @throws Exception if Loader can't be reset for some reason.
+   */
+  void reset() throws Exception;
+
+  /*@ public model instance boolean model_structureDetermined
+    @   initially: model_structureDetermined == false;
+    @*/
+
+  /*@ public model instance boolean model_sourceSupplied
+    @   initially: model_sourceSupplied == false;
+    @*/
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file the File
+   * @throws IOException if an error occurs
+   * support loading from a File.
+   *
+   * <pre><jml>
+   *    public_normal_behavior
+   *      requires: file != null
+   *                && (* file exists *);
+   *      modifiable: model_sourceSupplied, model_structureDetermined;
+   *      ensures: model_sourceSupplied == true 
+   *               && model_structureDetermined == false;
+   *  also
+   *    public_exceptional_behavior
+   *      requires: file == null
+   *                || (* file does not exist *);
+   *    signals: (IOException);
+   * </jml></pre>
+   */
+  void setSource(File file) throws IOException;
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param input the source InputStream
+   * @throws IOException if this Loader doesn't
+   * support loading from a File.
+   */
+  void setSource(InputStream input) throws IOException;
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if there is no source or parsing fails
+   *
+   * <pre><jml>
+   *    public_normal_behavior
+   *      requires: model_sourceSupplied == true
+   *                && model_structureDetermined == false
+   *                && (* successful parse *);
+   *      modifiable: model_structureDetermined;
+   *      ensures: \result != null
+   *               && \result.numInstances() == 0
+   *               && model_structureDetermined == true;
+   *  also
+   *    public_exceptional_behavior
+   *      requires: model_sourceSupplied == false
+   *                || (* unsuccessful parse *);
+   *      signals: (IOException);
+   * </jml></pre>
+   */
+  Instances getStructure() throws IOException;
+
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then the method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the full data set as an Instances object
+   * @throws IOException if there is an error during parsing or if 
+   * getNextInstance has been called on this source (either incremental
+   * or batch loading can be used, not both).
+   *
+   * <pre><jml>
+   *    public_normal_behavior
+   *      requires: model_sourceSupplied == true
+   *                && (* successful parse *);
+   *      modifiable: model_structureDetermined;
+   *      ensures: \result != null
+   *               && \result.numInstances() >= 0
+   *               && model_structureDetermined == true;
+   *  also
+   *    public_exceptional_behavior
+   *      requires: model_sourceSupplied == false
+   *                || (* unsuccessful parse *);
+   *      signals: (IOException);
+   * </jml></pre>
+   */
+  Instances getDataSet() throws IOException;
+
+  /**
+   * Read the data set incrementally---get the next instance in the data 
+   * set or returns null if there are no
+   * more instances to get. If the structure hasn't yet been 
+   * determined by a call to getStructure then method should do so before
+   * returning the next instance in the data set.
+   *
+   * If it is not possible to read the data set incrementally (ie. in cases
+   * where the data set structure cannot be fully established before all
+   * instances have been seen) then an exception should be thrown.
+   *
+   * @param structure the dataset header information, will get updated in 
+   * case of string or relational attributes
+   * @return the next instance in the data set as an Instance object or null
+   * if there are no more instances to be read
+   * @throws IOException if there is an error during parsing or if
+   * getDataSet has been called on this source (either incremental
+   * or batch loading can be used, not both).
+   */
+  Instance getNextInstance(Instances structure) throws IOException;
+}
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/converters/MatlabLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/MatlabLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/MatlabLoader.java	(revision 29)
@@ -0,0 +1,353 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MatlabLoader.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a Matlab file containing a single matrix in ASCII format.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class MatlabLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, URLSourcedLoader {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -8861142318612875251L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = ".m";
+
+  /** the url. */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** the buffer of the rows read so far. */
+  protected Vector<Vector<Double>> m_Buffer = null;
+  
+  /**
+   * Returns a string describing this Loader.
+   * 
+   * @return 		a description of the Loader suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Reads a Matlab file containing a single matrix in ASCII format.";
+  }
+
+  /**
+   * Get the file extension used for libsvm files.
+   *
+   * @return 		the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "Matlab ASCII files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set.
+   * 
+   * @throws IOException 	if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    if ((m_File != null) && (new File(m_File)).isFile()) {
+      setFile(new File(m_File));
+    }
+    else if ((m_URL != null) && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url 	the source url.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+
+  /**
+   * Set the url to load from.
+   *
+   * @param url 		the url to load from
+   * @throws IOException 		if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url.
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in 			the source InputStream.
+   * @throws IOException 	if initialization of reader fails.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL  = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty set 
+   * 				of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    int			numAtt;
+    ArrayList<Attribute>		atts;
+    int			i;
+    String		relName;
+    Vector<Double>	row;
+    int			c;
+    char		chr;
+    StringBuffer	str;
+    boolean		isComment;
+    
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+
+    if (m_structure == null) {
+      numAtt    = 0;
+      m_Buffer  = new Vector<Vector<Double>>();
+      row       = new Vector<Double>();
+      str       = new StringBuffer();
+      isComment = false;
+      m_Buffer.add(row);
+      try {
+	// determine number of attributes
+	while ((c = m_sourceReader.read()) != -1) {
+	  chr = (char) c;
+	  
+	  // comment found?
+	  if (chr == '%')
+	    isComment = true;
+	  
+	  // end of line reached
+	  if ((chr == '\n') || (chr == '\r')) {
+	    isComment = false;
+	    if (str.length() > 0)
+	      row.add(new Double(str.toString()));
+	    if (numAtt == 0)
+	      numAtt = row.size();
+	    if (row.size() > 0) {
+	      row = new Vector<Double>();
+	      m_Buffer.add(row);
+	    }
+	    str = new StringBuffer();
+	    continue;
+	  }
+
+	  // skip till end of comment line
+	  if (isComment)
+	    continue;
+	  
+	  // separator found?
+	  if ((chr == '\t') || (chr == ' ')) {
+	    if (str.length() > 0) {
+	      row.add(new Double(str.toString()));
+	      str = new StringBuffer();
+	    }
+	  }
+	  else {
+	    str.append(chr);
+	  }
+	}
+	
+	// last number?
+	if (str.length() > 0)
+	  row.add(new Double(str.toString()));
+	
+	// generate header
+	atts = new ArrayList<Attribute>(numAtt);
+	for (i = 0; i < numAtt; i++)
+	  atts.add(new Attribute("att_" + (i+1)));
+	
+	if (!m_URL.equals("http://"))
+	  relName = m_URL;
+	else
+	  relName = m_File;
+	
+	m_structure = new Instances(relName, atts, 0);
+	m_structure.setClassIndex(m_structure.numAttributes() - 1);
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+	throw new IOException("Unable to determine structure as Matlab ASCII file: " + ex);
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    Instances 		result;
+    Vector<Double>	row;
+    double[]		data;
+    int			i;
+    int			n;
+
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+
+    setRetrieval(BATCH);
+    if (m_structure == null)
+      getStructure();
+
+    result = new Instances(m_structure, 0);
+
+    // create instances from buffered data
+    for (i = 0; i < m_Buffer.size(); i++) {
+      row = m_Buffer.get(i);
+      if (row.size() == 0)
+	continue;
+      data = new double[row.size()];
+      for (n = 0; n < row.size(); n++)
+	data[n] = row.get(n);
+      
+      result.add(new DenseInstance(1.0, data));
+    }
+
+    // close the stream
+    try {
+      m_sourceReader.close();
+    }
+    catch (Exception ex) {
+      // ignored
+    }
+    
+    return result;
+  }
+
+  /**
+   * MatlabLoader is unable to process a data set incrementally.
+   *
+   * @param structure 		ignored
+   * @return 			never returns without throwing an exception
+   * @throws IOException 	always. MatlabLoader is unable to process a 
+   * 				data set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("MatlabLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new MatlabLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/MatlabSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/MatlabSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/MatlabSaver.java	(revision 29)
@@ -0,0 +1,478 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MatlabSaver.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes Matlab ASCII files, in single or double precision format.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ * <pre> -double
+ *  Use double precision format.
+ *  (default: single precision)</pre>
+ * 
+ * <pre> -tabs
+ *  Use tabs as separator.
+ *  (default: blanks)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class MatlabSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 4118356803697172614L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = MatlabLoader.FILE_EXTENSION;
+
+  /** whether to save in double instead of single precision format. */
+  protected boolean m_UseDouble;
+
+  /** whether to use tabs instead of blanks. */
+  protected boolean m_UseTabs;
+
+  /** whether the header was written already. */
+  protected boolean m_HeaderWritten;
+  
+  /** for formatting the numbers. */
+  protected DecimalFormat m_Format;
+  
+  /**
+   * Constructor.
+   */
+  public MatlabSaver(){
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Writes Matlab ASCII files, in single or double precision format.";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>      result;
+    
+    result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(
+        new Option(
+            "\tUse double precision format.\n"
+            + "\t(default: single precision)",
+            "double", 0, "-double"));
+    
+    result.addElement(
+        new Option(
+            "\tUse tabs as separator.\n"
+            + "\t(default: blanks)",
+            "tabs", 0, "-tabs"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup.
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       		i;
+    Vector<String>    	result;
+    String[]  		options;
+
+    result = new Vector<String>();
+
+    if (getUseDouble())
+      result.add("-double");
+
+    if (getUseTabs())
+      result.add("-tabs");
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   * <pre> -double
+   *  Use double precision format.
+   *  (default: single precision)</pre>
+   * 
+   * <pre> -tabs
+   *  Use tabs as separator.
+   *  (default: blanks)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    setUseDouble(Utils.getFlag("double", options));
+
+    setUseTabs(Utils.getFlag("tabs", options));
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "Matlab ASCII files";
+  }
+  
+  /**
+   * Resets the Saver.
+   */
+  public void resetOptions() {
+    super.resetOptions();
+
+    setFileExtension(MatlabLoader.FILE_EXTENSION);
+    setUseDouble(false);
+    setUseTabs(false);
+    
+    m_HeaderWritten = false;
+  }
+
+  /**
+   * Sets whether to use double or single precision.
+   *
+   * @param value 	if true then double precision is used
+   */
+  public void setUseDouble(boolean value) {
+    m_UseDouble = value;
+    if (m_UseDouble)
+      m_Format = new DecimalFormat("   0.0000000000000000E00;  -0.0000000000000000E00");
+    else
+      m_Format = new DecimalFormat("   0.00000000E00;  -0.00000000E00");
+  }
+
+  /**
+   * Returns whether double or single precision is used.
+   *
+   * @return 		true if double precision is used
+   */
+  public boolean getUseDouble() {
+    return m_UseDouble;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useDoubleTipText() {
+    return "Sets whether to use double instead of single precision.";
+  }
+
+  /**
+   * Sets whether to use tabs instead of blanks.
+   *
+   * @param value 	if true then tabs are used
+   */
+  public void setUseTabs(boolean value) {
+    m_UseTabs = value;
+  }
+
+  /**
+   * Returns whether tabs are used instead of blanks.
+   *
+   * @return 		true if tabs are used
+   */
+  public boolean getUseTabs() {
+    return m_UseTabs;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String useTabsTipText() {
+    return "Sets whether to use tabs as separators instead of blanks.";
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Generates a comment header.
+   * 
+   * @return		the header
+   */
+  protected String matlabHeader() {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+    result.append("% Relation: " + getInstances().relationName() + "\n");
+    result.append("% Generated on: " + new Date() + "\n");
+    result.append("% Generated by: WEKA " + Version.VERSION + "\n");
+    result.append("%\n");
+    
+    result.append("%  ");
+    for (i = 0; i < getInstances().numAttributes(); i++) {
+      if (i > 0)
+	result.append((m_UseTabs ? "\t   " : "    "));
+      result.append(Utils.padRight(getInstances().attribute(i).name(), (m_UseDouble ? 16 : 8) + 5));
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * turns the instance into a Matlab row.
+   * 
+   * @param inst	the instance to transform
+   * @return		the generated Matlab row
+   */
+  protected String instanceToMatlab(Instance inst) {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+
+    // attributes
+    for (i = 0; i < inst.numAttributes(); i++) {
+      if (i > 0)
+	result.append((m_UseTabs ? "\t" : " "));
+      result.append(m_Format.format(inst.value(i)));
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * 
+   * @param inst 		the instance to save
+   * @throws IOException 	throws IOEXception if an instance cannot be 
+   * 				saved incrementally.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+    int writeMode = getWriteMode();
+    Instances structure = getInstances();
+    PrintWriter outW = null;
+    
+    if ((getRetrieval() == BATCH) || (getRetrieval() == NONE))
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+
+    if (getWriter() != null)
+      outW = new PrintWriter(getWriter());
+    
+    if (writeMode == WAIT) {
+      if (structure == null) {
+	setWriteMode(CANCEL);
+	if (inst != null)
+	  System.err.println("Structure (Header Information) has to be set in advance");
+      }
+      else {
+	setWriteMode(STRUCTURE_READY);
+      }
+      writeMode = getWriteMode();
+    }
+    
+    if (writeMode == CANCEL) {
+      if (outW != null)
+	outW.close();
+      cancel();
+    }
+    
+    // header
+    if (writeMode == STRUCTURE_READY) {
+      setWriteMode(WRITE);
+      if ((retrieveFile() == null) || (outW == null))
+	System.out.println(matlabHeader());
+      else
+	outW.println(matlabHeader());
+      writeMode = getWriteMode();
+    }
+
+    // row
+    if (writeMode == WRITE){
+      if (structure == null)
+	throw new IOException("No instances information available.");
+      
+      if (inst != null) {
+	//write instance 
+	if ((retrieveFile() == null) || (outW == null)) {
+	  System.out.println(instanceToMatlab(inst));
+	}
+	else {
+	  outW.println(instanceToMatlab(inst));
+	  m_incrementalCounter++;
+	  // flush every 100 instances
+	  if (m_incrementalCounter > 100){
+	    m_incrementalCounter = 0;
+	    outW.flush();
+	  }
+	}
+      }
+      else{
+	//close
+	if (outW != null) {
+	  outW.flush();
+	  outW.close();
+	}
+	m_incrementalCounter = 0;
+	resetStructure();
+	outW = null;
+	resetWriter();
+      }
+    }
+  }
+  
+  /**
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException 	throws IOException if saving in batch mode 
+   * 				is not possible
+   */
+  public void writeBatch() throws IOException {
+    if (getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    setRetrieval(BATCH);
+    setWriteMode(WRITE);
+
+    if ((retrieveFile() == null) && (getWriter() == null)) {
+      System.out.println(matlabHeader());
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	System.out.println(instanceToMatlab(getInstances().instance(i)));
+      setWriteMode(WAIT);
+    }
+    else {
+      PrintWriter outW = new PrintWriter(getWriter());
+      outW.println(matlabHeader());
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	outW.println(instanceToMatlab(getInstances().instance(i)));
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new MatlabSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/SVMLightLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/SVMLightLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/SVMLightLoader.java	(revision 29)
@@ -0,0 +1,481 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SVMLightLoader.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in svm light format.<br/>
+ * <br/>
+ * For more information about svm light see:<br/>
+ * <br/>
+ * http://svmlight.joachims.org/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Loader
+ */
+public class SVMLightLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, URLSourcedLoader {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4988360125354664417L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = ".dat";
+
+  /** the url. */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** the buffer of the rows read so far. */
+  protected Vector<double[]> m_Buffer = null;
+  
+  /**
+   * Returns a string describing this Loader.
+   * 
+   * @return 		a description of the Loader suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads a source that is in svm light format.\n\n"
+      + "For more information about svm light see:\n\n"
+      + "http://svmlight.joachims.org/";
+  }
+
+  /**
+   * Get the file extension used for svm light files.
+   *
+   * @return 		the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file.
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "svm light data files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set.
+   * 
+   * @throws IOException 	if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    }
+    else if ((m_URL != null) && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url 	the source url.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure = null;
+    m_Buffer    = null;
+    
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+
+  /**
+   * Set the url to load from.
+   *
+   * @param url 		the url to load from
+   * @throws IOException 		if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url.
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in 			the source InputStream.
+   * @throws IOException 	if initialization of reader fails.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL  = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+
+  /**
+   * turns a svm light row into a double array with the class as the last
+   * entry.
+   * 
+   * @param row		the row to turn into a double array
+   * @return		the corresponding double array
+   * @throws Exception	if a parsing error is encountered 
+   */
+  protected double[] svmlightToArray(String row) throws Exception {
+    double[]		result;
+    StringTokenizer	tok;
+    int			index;
+    int			max;
+    String		col;
+    double		value;
+
+    // actual data
+    try {
+      // determine max index
+      max = 0;
+      tok = new StringTokenizer(row, " \t");
+      tok.nextToken();  // skip class
+      while (tok.hasMoreTokens()) {
+	col = tok.nextToken();
+	// finished?
+	if (col.startsWith("#"))
+	  break;
+	// qid is not supported
+	if (col.startsWith("qid:"))
+	  continue;
+	// actual value
+	index = Integer.parseInt(col.substring(0, col.indexOf(":")));
+	if (index > max)
+	  max = index;
+      }
+
+      // read values into array
+      tok    = new StringTokenizer(row, " \t");
+      result = new double[max + 1];
+
+      // 1. class
+      result[result.length - 1] = Double.parseDouble(tok.nextToken());
+
+      // 2. attributes
+      while (tok.hasMoreTokens()) {
+	col  = tok.nextToken();
+	// finished?
+	if (col.startsWith("#"))
+	  break;
+	// qid is not supported
+	if (col.startsWith("qid:"))
+	  continue;
+	// actual value
+	index = Integer.parseInt(col.substring(0, col.indexOf(":")));
+	value = Double.parseDouble(col.substring(col.indexOf(":") + 1));
+	result[index - 1] = value;
+      }
+    }
+    catch (Exception e) {
+      System.err.println("Error parsing line '" + row + "': " + e);
+      throw new Exception(e);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * determines the number of attributes, if the number of attributes in the
+   * given row is greater than the current amount then this number will be
+   * returned, otherwise the current number.
+   * 
+   * @param values	the parsed values
+   * @param num		the current number of attributes
+   * @return 		the new number of attributes
+   * @throws Exception	if parsing fails
+   */
+  protected int determineNumAttributes(double[] values, int num) throws Exception {
+    int		result;
+    int		count;
+    
+    result = num;
+    
+    count = values.length;
+    if (count > result)
+      result = count;
+    
+    return result;
+  }
+  
+  /**
+   * Determines the class attribute, either a binary +1/-1 or numeric attribute.
+   * 
+   * @return		the generated attribute
+   */
+  protected Attribute determineClassAttribute() {
+    Attribute	result;
+    boolean	binary;
+    int		i;
+    ArrayList<String>	values;
+    double[]	dbls;
+    double	cls;
+    
+    binary = true;
+    
+    for (i = 0; i < m_Buffer.size(); i++) {
+      dbls = (double[]) m_Buffer.get(i);
+      cls  = dbls[dbls.length - 1];
+      if ((cls != -1.0) && (cls != +1.0)) {
+	binary = false;
+	break;
+      }
+    }
+    
+    if (binary) {
+      values = new ArrayList<String>();
+      values.add("+1");
+      values.add("-1");
+      result = new Attribute("class", values);
+    }
+    else {
+      result = new Attribute("class");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty set 
+   * 				of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    StringBuffer	line;
+    int			cInt;
+    char		c;
+    int			numAtt;
+    ArrayList<Attribute>		atts;
+    int			i;
+    String		relName;
+    
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+
+    if (m_structure == null) {
+      m_Buffer = new Vector<double[]>();
+      try {
+	// determine number of attributes
+	numAtt = 0;
+	line   = new StringBuffer();
+	while ((cInt = m_sourceReader.read()) != -1) {
+	  c = (char) cInt;
+	  if ((c == '\n') || (c == '\r')) {
+	    if ((line.length() > 0) && (line.charAt(0) != '#')) {
+	      // actual data
+	      try {
+		m_Buffer.add(svmlightToArray(line.toString()));
+		numAtt = determineNumAttributes((double[]) m_Buffer.lastElement(), numAtt);
+	      }
+	      catch (Exception e) {
+		throw new Exception("Error parsing line '" + line + "': " + e);
+	      }
+	    }
+	    line = new StringBuffer();
+	  }
+	  else {
+	    line.append(c);
+	  }
+	}
+	
+	// last line?
+	if ((line.length() != 0) && (line.charAt(0) != '#')) {
+	  m_Buffer.add(svmlightToArray(line.toString()));
+	  numAtt = determineNumAttributes((double[]) m_Buffer.lastElement(), numAtt);
+	}
+	
+	// generate header
+	atts = new ArrayList<Attribute>(numAtt);
+	for (i = 0; i < numAtt - 1; i++)
+	  atts.add(new Attribute("att_" + (i+1)));
+	atts.add(determineClassAttribute());
+	
+	if (!m_URL.equals("http://"))
+	  relName = m_URL;
+	else
+	  relName = m_File;
+	
+	m_structure = new Instances(relName, atts, 0);
+	m_structure.setClassIndex(m_structure.numAttributes() - 1);
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+	throw new IOException("Unable to determine structure as svm light: " + ex);
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    Instances 	result;
+    double[]	sparse;
+    double[]	data;
+    int		i;
+
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+
+    setRetrieval(BATCH);
+    if (m_structure == null)
+      getStructure();
+
+    result = new Instances(m_structure, 0);
+
+    // create instances from buffered arrays
+    for (i = 0; i < m_Buffer.size(); i++) {
+      sparse = (double[]) m_Buffer.get(i);
+      
+      if (sparse.length != m_structure.numAttributes()) {
+	data = new double[m_structure.numAttributes()];
+	// attributes
+	System.arraycopy(sparse, 0, data, 0, sparse.length - 1);
+	// class
+	data[data.length - 1] = sparse[sparse.length - 1];
+      }
+      else {
+	data = sparse;
+      }
+      
+      // fix class
+      if (result.classAttribute().isNominal()) {
+	if (data[data.length - 1] == 1.0)
+	  data[data.length - 1] = result.classAttribute().indexOfValue("+1");
+	else if (data[data.length - 1] == -1)
+	  data[data.length - 1] = result.classAttribute().indexOfValue("-1");
+	else
+	  throw new IllegalStateException("Class is not binary!");
+      }
+      
+      result.add(new SparseInstance(1, data));
+    }
+
+    try {
+      // close the stream
+      m_sourceReader.close();
+    } catch (Exception ex) {
+
+    }
+    
+    return result;
+  }
+
+  /**
+   * SVMLightLoader is unable to process a data set incrementally.
+   *
+   * @param structure 		ignored
+   * @return 			never returns without throwing an exception
+   * @throws IOException 	always. SVMLightLoader is unable to process a 
+   * 				data set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("SVMLightLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new SVMLightLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/SVMLightSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/SVMLightSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/SVMLightSaver.java	(revision 29)
@@ -0,0 +1,425 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SVMLightSaver.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, NZ
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in svm light format.<br/>
+ * <br/>
+ * For more information about svm light see:<br/>
+ * <br/>
+ * http://svmlight.joachims.org/
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ * <pre> -c &lt;class index&gt;
+ *  The class index
+ *  (default: last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class SVMLightSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter, IncrementalConverter {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2605714599263995835L;
+
+  /** the file extension. */
+  public static String FILE_EXTENSION = SVMLightLoader.FILE_EXTENSION;
+
+  /** the number of digits after the decimal point. */
+  public static int MAX_DIGITS = 18;
+  
+  /** the class index. */
+  protected SingleIndex m_ClassIndex = new SingleIndex("last"); 
+
+  /**
+   * Constructor.
+   */
+  public SVMLightSaver(){
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Writes to a destination that is in svm light format.\n\n"
+      + "For more information about svm light see:\n\n"
+      + "http://svmlight.joachims.org/";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>      result;
+    
+    result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(
+        new Option(
+            "\tThe class index\n"
+            + "\t(default: last)",
+            "c", 1, "-c <class index>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup.
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector<String>    	result;
+    String[]  	options;
+
+    result = new Vector<String>();
+
+    result.add("-c");
+    result.add(getClassIndex());
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   * <pre> -c &lt;class index&gt;
+   *  The class index
+   *  (default: last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0)
+      setClassIndex(tmpStr);
+    else
+      setClassIndex("last");
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "svm light data files";
+  }
+  
+  /**
+   * Resets the Saver.
+   */
+  public void resetOptions() {
+    super.resetOptions();
+    setFileExtension(SVMLightLoader.FILE_EXTENSION);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Sets the class index (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the class attribute.
+   *
+   * @return 		the index of the class attribute
+   */
+  public String getClassIndex() {
+    return m_ClassIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the class attribute.
+   *
+   * @param value 	the index of the class attribute
+   */
+  public void setClassIndex(String value) {
+    m_ClassIndex.setSingleIndex(value);
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    
+    // class
+    result.enable(Capability.BINARY_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets instances that should be stored.
+   *
+   * @param instances 	the instances
+   */
+  public void setInstances(Instances instances) {
+    m_ClassIndex.setUpper(instances.numAttributes() - 1);
+    instances.setClassIndex(m_ClassIndex.getIndex());
+    
+    super.setInstances(instances);
+  }
+  
+  /**
+   * turns the instance into a svm light row.
+   * 
+   * @param inst	the instance to transform
+   * @return		the generated svm light row
+   */
+  protected String instanceToSvmlight(Instance inst) {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // class
+    if (inst.classAttribute().isNominal()) {
+      if (inst.classValue() == 0)
+	result.append("1");
+      else if (inst.classValue() == 1)
+	result.append("-1");
+    }
+    else {
+      result.append("" + Utils.doubleToString(inst.classValue(), MAX_DIGITS));
+    }
+
+    // attributes
+    for (i = 0; i < inst.numAttributes(); i++) {
+      if (i == inst.classIndex())
+	continue;
+      if (inst.value(i) == 0)
+	continue;
+      result.append(" " + (i+1) + ":" + Utils.doubleToString(inst.value(i), MAX_DIGITS));
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Saves an instances incrementally. Structure has to be set by using the
+   * setStructure() method or setInstances() method.
+   * 
+   * @param inst 		the instance to save
+   * @throws IOException 	throws IOEXception if an instance cannot be 
+   * 				saved incrementally.
+   */  
+  public void writeIncremental(Instance inst) throws IOException{
+    int writeMode = getWriteMode();
+    Instances structure = getInstances();
+    PrintWriter outW = null;
+    
+    if ((getRetrieval() == BATCH) || (getRetrieval() == NONE))
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+
+    if (getWriter() != null)
+      outW = new PrintWriter(getWriter());
+    
+    if (writeMode == WAIT) {
+      if (structure == null) {
+	setWriteMode(CANCEL);
+	if (inst != null)
+	  System.err.println("Structure (Header Information) has to be set in advance");
+      }
+      else {
+	setWriteMode(STRUCTURE_READY);
+      }
+      writeMode = getWriteMode();
+    }
+    
+    if (writeMode == CANCEL) {
+      if (outW != null)
+	outW.close();
+      cancel();
+    }
+    
+    // header
+    if (writeMode == STRUCTURE_READY) {
+      setWriteMode(WRITE);
+      // no header
+      writeMode = getWriteMode();
+    }
+
+    // row
+    if (writeMode == WRITE){
+      if (structure == null)
+	throw new IOException("No instances information available.");
+      
+      if (inst != null) {
+	//write instance 
+	if ((retrieveFile() == null) || (outW == null)) {
+	  System.out.println(instanceToSvmlight(inst));
+	}
+	else {
+	  outW.println(instanceToSvmlight(inst));
+	  m_incrementalCounter++;
+	  //flush every 100 instances
+	  if (m_incrementalCounter > 100){
+	    m_incrementalCounter = 0;
+	    outW.flush();
+	  }
+	}
+      }
+      else{
+	//close
+	if (outW != null) {
+	  outW.flush();
+	  outW.close();
+	}
+	m_incrementalCounter = 0;
+	resetStructure();
+	outW = null;
+	resetWriter();
+      }
+    }
+  }
+  
+  /**
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException 	throws IOException if saving in batch mode 
+   * 				is not possible
+   */
+  public void writeBatch() throws IOException {
+    if (getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    setRetrieval(BATCH);
+    setWriteMode(WRITE);
+
+    if ((retrieveFile() == null) && (getWriter() == null)) {
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	System.out.println(instanceToSvmlight(getInstances().instance(i)));
+      setWriteMode(WAIT);
+    }
+    else {
+      PrintWriter outW = new PrintWriter(getWriter());
+      for (int i = 0; i < getInstances().numInstances(); i++)
+	outW.println(instanceToSvmlight(getInstances().instance(i)));
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new SVMLightSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/Saver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/Saver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/Saver.java	(revision 29)
@@ -0,0 +1,172 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Saver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/** 
+ * Interface to something that can save Instances to an output destination in some
+ * format.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public interface Saver
+  extends Serializable, RevisionHandler {
+    
+    /** The retrieval modes */
+  static final int NONE = 0;
+  static final int BATCH = 1;
+  static final int INCREMENTAL = 2;
+  
+
+
+  /*@ public model instance boolean model_structureDetermined
+    @   initially: model_structureDetermined == false;
+    @*/
+
+  /*@ public model instance boolean model_sourceSupplied
+    @   initially: model_sourceSupplied == false;
+    @*/
+
+  /**
+   * Resets the Saver object and sets the destination to be 
+   * the supplied File object.
+   *
+   * @param file the File
+   * @exception IOException if an error occurs
+   * support loading from a File.
+   *
+   * <pre><jml>
+   *    public_normal_behavior
+   *      requires: file != null
+   *                && (* file exists *);
+   *      modifiable: model_sourceSupplied, model_structureDetermined;
+   *      ensures: model_sourceSupplied == true 
+   *               && model_structureDetermined == false;
+   *  also
+   *    public_exceptional_behavior
+   *      requires: file == null
+   *                || (* file does not exist *);
+   *    signals: (IOException);
+   * </jml></pre>
+   */
+  void setDestination(File file) throws IOException;
+
+  /** Resets the Saver object and sets the destination to be
+   * the supplied InputStream.
+   * @param output the output stream
+   * @exception IOException if this Loader doesn't
+   * support loading from a File.
+   */
+  void setDestination(OutputStream output) throws IOException;
+  
+  /** Sets the retrieval mode
+   * @param mode an integer representing a retrieval mode
+   */  
+  void setRetrieval(int mode);
+  
+  /** Gets the file extension
+   * @return a string conatining the file extension (including the '.')
+   * @throws Exception exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  String getFileExtension() throws Exception;
+  
+  /** Sets the output file
+   * @param file the output file
+   * @throws IOException exception if new output file cannot be set
+   */  
+  void setFile(File file)throws IOException;
+  
+  /** Sets the file prefix.
+   * This method is used in the KnowledgeFlow GUI.
+   * @param prefix the prefix of the file name
+   * @throws Exception exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  void setFilePrefix(String prefix) throws Exception;
+  
+  /** Gets the file prefix
+   * This method is used in the KnowledgeFlow GUI.
+   * @return the prefix of the file name
+   * @throws Exception exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  String filePrefix() throws Exception;
+  
+  /** Sets the directory of the output file.
+   * This method is used in the KnowledgeFlow GUI.
+   * @param dir a string containing the path and name of the directory
+   * @throws IOException exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  void setDir(String dir) throws IOException;
+  
+  /** Sets the file prefix and the directory.
+   * This method is used in the KnowledgeFlow GUI.
+   * @param relationName the name of the realtion to be saved
+   * @param add additional String for the file name
+   * @throws IOException exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  public void setDirAndPrefix(String relationName, String add) throws IOException; 
+  
+  /** Gets the driectory of the output file
+   * This method is used in the KnowledgeFlow GUI.
+   * @return the directory as a string
+   * @throws IOException exception if a Saver not implementing FileSourcedConverter is used.
+   */  
+  String retrieveDir() throws IOException;
+  
+  /** Sets the instances to be saved
+   * @param instances the instances
+   */  
+  void setInstances(Instances instances);
+
+  /** Writes to a destination in batch mode
+   * @throws IOException throws exection if writting in batch mode is not possible
+   */  
+  void writeBatch() throws IOException;
+  
+  /** Writes to a destination in incremental mode.
+   * If the instance is null, the outputfile will be closed.
+   * @param inst the instance to write, if null the output file is closed
+   * @throws IOException throws exception if incremental writting is not possible
+   */  
+  void writeIncremental(Instance inst) throws IOException;
+  
+  /** Gets the write mode
+   * @return an integer representing the write mode
+   */  
+  public int getWriteMode();
+  
+}
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesLoader.java	(revision 29)
@@ -0,0 +1,204 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedInstancesLoader.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that contains serialized Instances.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 5953 $
+ * @see Loader
+ */
+public class SerializedInstancesLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, IncrementalConverter {
+
+  /** for serialization */
+  static final long serialVersionUID = 2391085836269030715L;
+  
+  /** the file extension */
+  public static String FILE_EXTENSION = 
+    Instances.SERIALIZED_OBJ_FILE_EXTENSION;
+  
+  /** Holds the structure (header) of the data set. */
+  protected Instances m_Dataset = null;
+
+  /** The current index position for incremental reading */
+  protected int m_IncrementalIndex = 0;
+  
+  /**
+   * Returns a string describing this object
+   * 
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Reads a source that contains serialized Instances.";
+  }
+
+  /** Resets the Loader ready to read a new data set */
+  public void reset() {
+
+    m_Dataset = null;
+    m_IncrementalIndex = 0;
+  }
+  
+  /**
+   * Get the file extension used for arff files
+   *
+   * @return the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{getFileExtension()};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "Binary serialized instances";
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in the source InputStream.
+   * @throws IOException if there is a problem with IO
+   */
+  public void setSource(InputStream in) throws IOException {
+
+    ObjectInputStream oi = new ObjectInputStream(new BufferedInputStream(in));
+    try {
+      m_Dataset = (Instances)oi.readObject();
+    } catch (ClassNotFoundException ex) {
+      throw new IOException("Could not deserialize instances from this source.");
+    }
+
+    // close the stream
+    oi.close();
+  }
+
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+
+    if (m_Dataset == null) {
+      throw new IOException("No source has been specified");
+    }
+
+    // We could cache a structure-only if getStructure is likely to be called
+    // many times.
+    return new Instances(m_Dataset, 0);
+  }
+
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+
+    if (m_Dataset == null) {
+      throw new IOException("No source has been specified");
+    }
+
+    return m_Dataset;
+  }
+
+  /**
+   * Read the data set incrementally---get the next instance in the data 
+   * set or returns null if there are no
+   * more instances to get. If the structure hasn't yet been 
+   * determined by a call to getStructure then method should do so before
+   * returning the next instance in the data set.
+   *
+   * @param structure ignored
+   * @return the next instance in the data set as an Instance object or null
+   * if there are no more instances to be read
+   * @throws IOException if there is an error during parsing
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+
+    if (m_Dataset == null) {
+      throw new IOException("No source has been specified");
+    }
+
+    // We have to fake this method, since we can only deserialize an entire
+    // dataset at a time.
+    if (m_IncrementalIndex == m_Dataset.numInstances()) {
+      return null;
+    }
+ 
+    return m_Dataset.instance(m_IncrementalIndex++);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new SerializedInstancesLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/SerializedInstancesSaver.java	(revision 29)
@@ -0,0 +1,182 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedInstancesSaver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Serializes the instances to a file with extension bsi.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ * The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ * The output file</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stefan Mutter (mutter@cs.waikato.ac.nz)
+ * @version $Revision: 4912 $
+ * @see Saver
+ */
+public class SerializedInstancesSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter {
+
+  /** for serialization. */
+  static final long serialVersionUID = -7717010648500658872L;
+  
+  /** the output stream. */
+  protected ObjectOutputStream m_objectstream;
+  
+  /** Constructor. */  
+  public SerializedInstancesSaver(){
+      resetOptions();
+  }
+    
+  /**
+   * Returns a string describing this Saver.
+   * 
+   * @return a description of the Saver suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Serializes the instances to a file with extension bsi.";
+  }
+ 
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "Binary serialized instances";
+  }
+
+  /**
+   * Resets the Saver.
+   */
+  public void resetOptions() {
+
+    super.resetOptions();
+    setFileExtension(".bsi");
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Resets the writer, setting writer and objectstream to null.
+   */  
+  public void resetWriter() {
+    super.resetWriter();
+    
+    m_objectstream = null;
+  }
+  
+  /**
+   * Sets the destination output stream.
+   * 
+   * @param output the output stream.
+   * @throws IOException throws an IOException if destination cannot be set
+   */
+  public void setDestination(OutputStream output) throws IOException {
+    super.setDestination(output);
+    
+    m_objectstream = new ObjectOutputStream(output);
+  }
+  
+  /** 
+   * Writes a Batch of instances.
+   * 
+   * @throws IOException throws IOException if saving in batch mode is not possible
+   */
+  public void writeBatch() throws IOException {
+    if(getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    if(getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    setRetrieval(BATCH);
+    
+    if (m_objectstream == null)
+      throw new IOException("No output for serialization.");
+
+    setWriteMode(WRITE);
+    m_objectstream.writeObject(getInstances());
+    m_objectstream.flush();
+    m_objectstream.close();
+    setWriteMode(WAIT);
+    resetWriter();
+    setWriteMode(CANCEL);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4912 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new SerializedInstancesSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/TextDirectoryLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/TextDirectoryLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/TextDirectoryLoader.java	(revision 29)
@@ -0,0 +1,464 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TextDirectoryLoader.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * Loads all text files in a directory and uses the subdirectory names as class labels. The content of the text files will be stored in a String attribute, the filename can be stored as well.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Enables debug output.
+ *  (default: off)</pre>
+ * 
+ * <pre> -F
+ *  Stores the filename in an additional attribute.
+ *  (default: off)</pre>
+ * 
+ * <pre> -dir &lt;directory&gt;
+ *  The directory to work on.
+ *  (default: current directory)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Based on code from the TextDirectoryToArff tool:
+ * <ul>
+ *    <li><a href="https://list.scms.waikato.ac.nz/mailman/htdig/wekalist/2002-October/000685.html" target="_blank">Original tool</a></li>
+ *    <li><a href="https://list.scms.waikato.ac.nz/mailman/htdig/wekalist/2004-January/002160.html" target="_blank">Current version</a></li>
+ *    <li><a href="http://weka.wikispaces.com/ARFF+files+from+Text+Collections" target="_blank">Wiki article</a></li>
+ * </ul>
+ *
+ * @author Ashraf M. Kibriya (amk14 at cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby at cs.waikato.ac.nz)
+ * @author fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class TextDirectoryLoader
+  extends AbstractLoader
+  implements BatchConverter, OptionHandler {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 2592118773712247647L;
+  
+  /** Holds the determined structure (header) of the data set. */
+  protected Instances m_structure = null;
+  
+  /** Holds the source of the data set. */
+  protected File m_sourceFile = new File(System.getProperty("user.dir"));
+  
+  /** whether to print some debug information */
+  protected boolean m_Debug = false;
+  
+  /** whether to include the filename as an extra attribute */
+  protected boolean m_OutputFilename = false;
+  
+  /**
+   * default constructor
+   */
+  public TextDirectoryLoader() {
+    // No instances retrieved yet
+    setRetrieval(NONE);
+  }
+  
+  /**
+   * Returns a string describing this loader
+   * 
+   * @return 		a description of the evaluator suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Loads all text files in a directory and uses the subdirectory names "
+      + "as class labels. The content of the text files will be stored in a "
+      + "String attribute, the filename can be stored as well.";
+  }
+  
+  /** 
+   * Lists the available options
+   * 
+   * @return 		an enumeration of the available options
+   */  
+  public Enumeration listOptions() {
+    
+    Vector<Option> result = new Vector<Option>();
+    
+    result.add(new Option(
+	"\tEnables debug output.\n"
+	+ "\t(default: off)",
+	"D", 0, "-D"));
+    
+    result.add(new Option(
+	"\tStores the filename in an additional attribute.\n"
+	+ "\t(default: off)",
+	"F", 0, "-F"));
+    
+    result.add(new Option(
+	"\tThe directory to work on.\n"
+	+ "\t(default: current directory)",
+	"dir", 0, "-dir <directory>"));
+    
+    return  result.elements();
+  }
+  
+  /** 
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Enables debug output.
+   *  (default: off)</pre>
+   * 
+   * <pre> -F
+   *  Stores the filename in an additional attribute.
+   *  (default: off)</pre>
+   * 
+   * <pre> -dir &lt;directory&gt;
+   *  The directory to work on.
+   *  (default: current directory)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the options
+   * @throws Exception if options cannot be set
+   */  
+  public void setOptions(String[] options) throws Exception {
+    setDebug(Utils.getFlag("D", options));
+    
+    setOutputFilename(Utils.getFlag("F", options));
+    
+    setDirectory(new File(Utils.getOption("dir", options)));
+  }
+  
+  /** 
+   * Gets the setting
+   * 
+   * @return the current setting
+   */  
+  public String[] getOptions() {
+    Vector<String> options = new Vector<String>();
+    
+    if (getDebug())
+      options.add("-D");
+    
+    if (getOutputFilename())
+      options.add("-F");
+
+    options.add("-dir");
+    options.add(getDirectory().getAbsolutePath());
+    
+    return (String[]) options.toArray(new String[options.size()]);
+  }
+  
+  /**
+   * Sets whether to print some debug information.
+   * 
+   * @param value	if true additional debug information will be printed.
+   */
+  public void setDebug(boolean value) {
+    m_Debug = value;
+  }
+  
+  /**
+   * Gets whether additional debug information is printed.
+   * 
+   * @return		true if additional debug information is printed
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return 		the tip text
+   */
+  public String debugTipText(){
+    return "Whether to print additional debug information to the console.";
+  }
+  
+  /**
+   * Sets whether the filename will be stored as an extra attribute.
+   * 
+   * @param value	if true the filename will be stored in an extra
+   * 			attribute
+   */
+  public void setOutputFilename(boolean value) {
+    m_OutputFilename = value;
+    reset();
+  }
+  
+  /**
+   * Gets whether the filename will be stored as an extra attribute.
+   * 
+   * @return		true if the filename is stored in an extra attribute
+   */
+  public boolean getOutputFilename() {
+    return m_OutputFilename;
+  }
+  
+  /**
+   * the tip text for this property
+   * 
+   * @return 		the tip text
+   */
+  public String outputFilenameTipText(){
+    return "Whether to store the filename in an additional attribute.";
+  }
+  
+  /**
+   * Returns a description of the file type, actually it's directories.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "Directories";
+  }
+  
+  /**
+   * get the Dir specified as the source
+   *
+   * @return 		the source directory
+   */
+  public File getDirectory() {
+    return new File(m_sourceFile.getAbsolutePath());
+  }
+  
+  /**
+   * sets the source directory
+   *
+   * @param 			dir the source directory
+   * @throws IOException 	if an error occurs
+   */
+  public void setDirectory(File dir) throws IOException {
+    setSource(dir);
+  }
+  
+  /**
+   * Resets the loader ready to read a new data set
+   */
+  public void reset() {
+    m_structure = null;
+    setRetrieval(NONE);
+  }
+  
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param dir 		the source directory.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(File dir) throws IOException {
+    reset();
+    
+    if (dir == null) {
+      throw new IOException("Source directory object is null!");
+    }
+    
+    m_sourceFile = dir;
+    if (!dir.exists() || !dir.isDirectory())
+      throw new IOException("Directory '" + dir + "' not found");
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    if (getDirectory() == null) {
+      throw new IOException("No directory/source has been specified");
+    }
+    
+    // determine class labels, i.e., sub-dirs
+    if (m_structure == null) {
+      String directoryPath = getDirectory().getAbsolutePath();
+      ArrayList<Attribute> atts = new ArrayList<Attribute>();
+      ArrayList<String> classes = new ArrayList<String>();
+      
+      File dir = new File(directoryPath);
+      String[] subdirs = dir.list();
+      
+      for (int i = 0; i < subdirs.length; i++) {
+	File subdir = new File(directoryPath + File.separator + subdirs[i]);
+	if (subdir.isDirectory())
+	  classes.add(subdirs[i]);
+      }
+      
+      atts.add(new Attribute("text", (ArrayList<String>) null));
+      if (m_OutputFilename)
+	atts.add(new Attribute("filename", (ArrayList<String>) null));
+      atts.add(new Attribute("class", classes));
+      
+      String relName = directoryPath.replaceAll("/", "_");
+      relName = relName.replaceAll("\\\\", "_").replaceAll(":", "_");
+      m_structure = new Instances(relName, atts, 0);    
+      m_structure.setClassIndex(m_structure.numAttributes() - 1);
+    }
+    
+    return m_structure;
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return the structure of the data set as an empty set of Instances
+   * @throws IOException if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    if (getDirectory() == null)
+      throw new IOException("No directory/source has been specified");
+    
+    String directoryPath = getDirectory().getAbsolutePath();
+    ArrayList<String> classes = new ArrayList<String>();
+    Enumeration enm = getStructure().classAttribute().enumerateValues();
+    while (enm.hasMoreElements())
+      classes.add((String)enm.nextElement());
+    
+    Instances data = getStructure();
+    int fileCount = 0;
+    for (int k = 0; k < classes.size(); k++) {
+      String subdirPath = (String) classes.get(k);
+      File subdir = new File(directoryPath + File.separator + subdirPath);
+      String[] files = subdir.list();
+      for (int j = 0; j < files.length; j++) {
+	try {
+	  fileCount++;
+	  if (getDebug())
+	    System.err.println(
+		"processing " + fileCount + " : " + subdirPath + " : " + files[j]); 
+	  
+	  double[] newInst = null;
+	  if (m_OutputFilename)
+	    newInst = new double[3];
+	  else
+	    newInst = new double[2];		    
+	  File txt = new File(directoryPath + File.separator + subdirPath + File.separator + files[j]);
+	  BufferedInputStream is;
+	  is = new BufferedInputStream(new FileInputStream(txt));
+	  StringBuffer txtStr = new StringBuffer();
+	  int c;
+	  while ((c = is.read()) != -1) {
+	    txtStr.append((char) c);
+	  }
+	  
+	  newInst[0] = (double) data.attribute(0).addStringValue(txtStr.toString());
+	  if (m_OutputFilename)
+	    newInst[1] = (double) data.attribute(1).addStringValue(subdirPath + File.separator + files[j]);
+	  newInst[data.classIndex()] = (double) k;
+	  data.add(new DenseInstance(1.0, newInst));
+          is.close();
+	}
+	catch (Exception e) {
+	  System.err.println("failed to convert file: " + directoryPath + File.separator + subdirPath + File.separator + files[j]);
+	}
+      }
+    }
+    
+    return data;
+  }
+  
+  /**
+   * TextDirectoryLoader is unable to process a data set incrementally.
+   *
+   * @param structure ignored
+   * @return never returns without throwing an exception
+   * @throws IOException always. TextDirectoryLoader is unable to process a data
+   * set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("TextDirectoryLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    if (args.length > 0) {
+      try {
+	TextDirectoryLoader loader = new TextDirectoryLoader();
+	loader.setOptions(args);
+	System.out.println(loader.getDataSet());
+      } 
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    } 
+    else {
+      System.err.println(
+	  "\nUsage:\n" 
+	  + "\tTextDirectoryLoader [options]\n"
+	  + "\n"
+	  + "Options:\n");
+
+      Enumeration enm = ((OptionHandler) new TextDirectoryLoader()).listOptions();
+      while (enm.hasMoreElements()) {
+	Option option = (Option) enm.nextElement();
+	System.err.println(option.synopsis());
+	System.err.println(option.description());
+      }
+      
+      System.err.println();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/URLSourcedLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/URLSourcedLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/URLSourcedLoader.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    URLSourcedLoader.java
+ *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core.converters;
+
+import java.net.URL;
+
+/**
+ * Interface to a loader that can load from a http url
+ *
+ * @author Mark Hall
+ * @version $Revision 1.0 $
+ */
+public interface URLSourcedLoader {
+
+  /**
+   * Set the url to load from
+   *
+   * @param url the url to load from
+   * @exception Exception if the url can't be set.
+   */
+  void setURL(String url) throws Exception;
+
+  /**
+   * Return the current url
+   *
+   * @return the current url
+   */
+  String retrieveURL();
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/XRFFLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/XRFFLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/XRFFLoader.java	(revision 29)
@@ -0,0 +1,302 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XRFFLoader.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.xml.XMLInstances;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.zip.GZIPInputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Reads a source that is in the XML version of the ARFF format. It automatically decompresses the data if the extension is '.xrff.gz'.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see Loader
+ */
+public class XRFFLoader 
+  extends AbstractFileLoader 
+  implements BatchConverter, URLSourcedLoader {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3764533621135196582L;
+
+  /** the file extension */
+  public static String FILE_EXTENSION = XMLInstances.FILE_EXTENSION;
+
+  /** the extension for compressed files */
+  public static String FILE_EXTENSION_COMPRESSED = FILE_EXTENSION + ".gz";
+
+  /** the url */
+  protected String m_URL = "http://";
+
+  /** The reader for the source file. */
+  protected transient Reader m_sourceReader = null;
+
+  /** the loaded XML document */
+  protected XMLInstances m_XMLInstances;
+  
+  /**
+   * Returns a string describing this Loader
+   * 
+   * @return 		a description of the Loader suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Reads a source that is in the XML version of the ARFF format. "
+      + "It automatically decompresses the data if the extension is '" 
+      + FILE_EXTENSION_COMPRESSED + "'.";
+  }
+
+  /**
+   * Get the file extension used for libsvm files
+   *
+   * @return 		the file extension
+   */
+  public String getFileExtension() {
+    return FILE_EXTENSION;
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{FILE_EXTENSION, FILE_EXTENSION_COMPRESSED};
+  }
+
+  /**
+   * Returns a description of the file type.
+   *
+   * @return 		a short file description
+   */
+  public String getFileDescription() {
+    return "XRFF data files";
+  }
+
+  /**
+   * Resets the Loader ready to read a new data set
+   * 
+   * @throws IOException 	if something goes wrong
+   */
+  public void reset() throws IOException {
+    m_structure    = null;
+    m_XMLInstances = null;
+
+    setRetrieval(NONE);
+    
+    if (m_File != null) {
+      setFile(new File(m_File));
+    }
+    else if ((m_URL != null) && !m_URL.equals("http://")) {
+      setURL(m_URL);
+    }
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied File object.
+   *
+   * @param file 		the source file.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(File file) throws IOException {
+    m_structure    = null;
+    m_XMLInstances = null;
+    
+    setRetrieval(NONE);
+
+    if (file == null)
+      throw new IOException("Source file object is null!");
+
+    try {
+      if (file.getName().endsWith(FILE_EXTENSION_COMPRESSED))
+	setSource(new GZIPInputStream(new FileInputStream(file)));
+      else
+	setSource(new FileInputStream(file));
+    }
+    catch (FileNotFoundException ex) {
+      throw new IOException("File not found");
+    }
+    
+    m_sourceFile = file;
+    m_File       = file.getAbsolutePath();
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied url.
+   *
+   * @param url 	the source url.
+   * @throws IOException 	if an error occurs
+   */
+  public void setSource(URL url) throws IOException {
+    m_structure    = null;
+    m_XMLInstances = null;
+    
+    setRetrieval(NONE);
+    
+    setSource(url.openStream());
+
+    m_URL = url.toString();
+  }
+  
+  /**
+   * Set the url to load from
+   *
+   * @param url 		the url to load from
+   * @throws IOException 	if the url can't be set.
+   */
+  public void setURL(String url) throws IOException {
+    m_URL = url;
+    setSource(new URL(url));
+  }
+
+  /**
+   * Return the current url
+   *
+   * @return the current url
+   */
+  public String retrieveURL() {
+    return m_URL;
+  }
+
+  /**
+   * Resets the Loader object and sets the source of the data set to be 
+   * the supplied InputStream.
+   *
+   * @param in 			the source InputStream.
+   * @throws IOException 	if initialization of reader fails.
+   */
+  public void setSource(InputStream in) throws IOException {
+    m_File = (new File(System.getProperty("user.dir"))).getAbsolutePath();
+    m_URL  = "http://";
+
+    m_sourceReader = new BufferedReader(new InputStreamReader(in));
+  }
+  
+  /**
+   * Determines and returns (if possible) the structure (internally the 
+   * header) of the data set as an empty set of instances.
+   *
+   * @return 			the structure of the data set as an empty set 
+   * 				of Instances
+   * @throws IOException 	if an error occurs
+   */
+  public Instances getStructure() throws IOException {
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+
+    if (m_structure == null) {
+      try {
+	m_XMLInstances = new XMLInstances(m_sourceReader);
+	m_structure    = new Instances(m_XMLInstances.getInstances(), 0);
+      }
+      catch (IOException ioe) {
+	// just re-throw it
+	throw ioe;
+      }
+      catch (Exception e) {
+	throw new RuntimeException(e);
+      }
+    }
+
+    return new Instances(m_structure, 0);
+  }
+  
+  /**
+   * Return the full data set. If the structure hasn't yet been determined
+   * by a call to getStructure then method should do so before processing
+   * the rest of the data set.
+   *
+   * @return 			the structure of the data set as an empty 
+   * 				set of Instances
+   * @throws IOException 	if there is no source or parsing fails
+   */
+  public Instances getDataSet() throws IOException {
+    if (m_sourceReader == null)
+      throw new IOException("No source has been specified");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Cannot mix getting Instances in both incremental and batch modes");
+
+    setRetrieval(BATCH);
+    if (m_structure == null)
+      getStructure();
+
+    try {
+      // close the stream
+      m_sourceReader.close();
+    } catch (Exception ex) {
+    }
+
+    return m_XMLInstances.getInstances();
+  }
+
+  /**
+   * XRFFLoader is unable to process a data set incrementally.
+   *
+   * @param structure		ignored
+   * @return 			never returns without throwing an exception
+   * @throws IOException 	always. XRFFLoader is unable to process a 
+   * 				data set incrementally.
+   */
+  public Instance getNextInstance(Instances structure) throws IOException {
+    throw new IOException("XRFFLoader can't read data sets incrementally.");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the name of an input file.
+   */
+  public static void main(String[] args) {
+    runFileLoader(new XRFFLoader(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/converters/XRFFSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/converters/XRFFSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/converters/XRFFSaver.java	(revision 29)
@@ -0,0 +1,410 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XRFFSaver.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.converters;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.xml.XMLInstances;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ <!-- globalinfo-start -->
+ * Writes to a destination that is in the XML version of the ARFF format. The data can be compressed with gzip, in order to save space.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -i &lt;the input file&gt;
+ *  The input file</pre>
+ * 
+ * <pre> -o &lt;the output file&gt;
+ *  The output file</pre>
+ * 
+ * <pre> -C &lt;class index&gt;
+ *  The class index (first and last are valid as well).
+ *  (default: last)</pre>
+ * 
+ * <pre> -compress
+ *  Compresses the data (uses '.xrff.gz' as extension instead of '.xrff')
+ *  (default: off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see Saver
+ */
+public class XRFFSaver 
+  extends AbstractFileSaver 
+  implements BatchConverter {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -7226404765213522043L;
+
+  /** the class index */
+  protected SingleIndex m_ClassIndex = new SingleIndex(); 
+
+  /** the generated XML document */
+  protected XMLInstances m_XMLInstances;
+  
+  /** whether to compress the output */
+  protected boolean m_CompressOutput = false;
+  
+  /**
+   * Constructor
+   */
+  public XRFFSaver(){
+    resetOptions();
+  }
+  
+  /**
+   * Returns a string describing this Saver
+   * 
+   * @return 		a description of the Saver suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Writes to a destination that is in the XML version of the ARFF format. "
+      + "The data can be compressed with gzip, in order to save space.";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>      result;
+    
+    result = new Vector<Option>();
+    
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement((Option)en.nextElement());
+    
+    result.addElement(
+        new Option(
+            "\tThe class index (first and last are valid as well).\n"
+            + "\t(default: last)",
+            "C", 1, "-C <class index>"));
+    
+    result.addElement(
+        new Option(
+            "\tCompresses the data (uses '" 
+            + XRFFLoader.FILE_EXTENSION_COMPRESSED 
+            + "' as extension instead of '" 
+            + XRFFLoader.FILE_EXTENSION + "')\n"
+            + "\t(default: off)",
+            "compress", 0, "-compress"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    int       	i;
+    Vector<String>    	result;
+    String[]  	options;
+
+    result = new Vector<String>();
+
+    if (getClassIndex().length() != 0) {
+      result.add("-C");
+      result.add(getClassIndex());
+    }
+
+    if (getCompressOutput())
+      result.add("-compress");
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -i &lt;the input file&gt;
+   *  The input file</pre>
+   * 
+   * <pre> -o &lt;the output file&gt;
+   *  The output file</pre>
+   * 
+   * <pre> -C &lt;class index&gt;
+   *  The class index (first and last are valid as well).
+   *  (default: last)</pre>
+   * 
+   * <pre> -compress
+   *  Compresses the data (uses '.xrff.gz' as extension instead of '.xrff')
+   *  (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setClassIndex(tmpStr);
+    else
+      setClassIndex("last");
+
+    setCompressOutput(Utils.getFlag("compress", options));
+    
+    super.setOptions(options);
+  }
+  
+  /**
+   * Returns a description of the file type.
+   *
+   * @return a short file description
+   */
+  public String getFileDescription() {
+    return "XRFF data files";
+  }
+
+  /**
+   * Gets all the file extensions used for this type of file
+   *
+   * @return the file extensions
+   */
+  public String[] getFileExtensions() {
+    return new String[]{XRFFLoader.FILE_EXTENSION, XRFFLoader.FILE_EXTENSION_COMPRESSED};
+  }
+  
+  /** 
+   * Sets the destination file.
+   * 
+   * @param outputFile the destination file.
+   * @throws IOException throws an IOException if file cannot be set
+   */
+  public void setFile(File outputFile) throws IOException  {
+    if (outputFile.getAbsolutePath().endsWith(XRFFLoader.FILE_EXTENSION_COMPRESSED))
+      setCompressOutput(true);
+    
+    super.setFile(outputFile);
+  }
+  
+  /**
+   * Resets the Saver 
+   */
+  public void resetOptions() {
+    super.resetOptions();
+    
+    if (getCompressOutput())
+      setFileExtension(XRFFLoader.FILE_EXTENSION_COMPRESSED);
+    else
+      setFileExtension(XRFFLoader.FILE_EXTENSION);
+    
+    try {
+      m_XMLInstances = new XMLInstances();
+    }
+    catch (Exception e) {
+      m_XMLInstances = null;
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return "Sets the class index (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the class attribute.
+   *
+   * @return 		the index of the class attribute
+   */
+  public String getClassIndex() {
+    return m_ClassIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the class attribute.
+   *
+   * @param value 	the index of the class attribute
+   */
+  public void setClassIndex(String value) {
+    m_ClassIndex.setSingleIndex(value);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String compressOutputTipText() {
+    return "Optional compression of the output data";
+  }
+
+  /**
+   * Gets whether the output data is compressed.
+   *
+   * @return 		true if the output data is compressed
+   */
+  public boolean getCompressOutput() {
+    return m_CompressOutput;
+  }
+
+  /**
+   * Sets whether to compress the output.
+   *
+   * @param value 	if truee the output will be compressed
+   */
+  public void setCompressOutput(boolean value) {
+    m_CompressOutput = value;
+  }
+
+  /** 
+   * Returns the Capabilities of this saver.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets instances that should be stored.
+   *
+   * @param instances 	the instances
+   */
+  public void setInstances(Instances instances) {
+    if (m_ClassIndex.getSingleIndex().length() != 0) {
+      m_ClassIndex.setUpper(instances.numAttributes() - 1);
+      instances.setClassIndex(m_ClassIndex.getIndex());
+    }
+    
+    super.setInstances(instances);
+  }
+  
+  /** 
+   * Sets the destination output stream.
+   * 
+   * @param output 		the output stream.
+   * @throws IOException 	throws an IOException if destination cannot be set
+   */
+  public void setDestination(OutputStream output) throws IOException {
+    if (getCompressOutput())
+      super.setDestination(new GZIPOutputStream(output));
+    else
+      super.setDestination(output);
+  }
+  
+  /**
+   * Writes a Batch of instances
+   * 
+   * @throws IOException 	throws IOException if saving in batch mode 
+   * 				is not possible
+   */
+  public void writeBatch() throws IOException {
+    if (getInstances() == null)
+      throw new IOException("No instances to save");
+    
+    if (getRetrieval() == INCREMENTAL)
+      throw new IOException("Batch and incremental saving cannot be mixed.");
+    
+    setRetrieval(BATCH);
+    setWriteMode(WRITE);
+
+    // generate XML
+    m_XMLInstances.setInstances(getInstances());
+    
+    if ((retrieveFile() == null) && (getWriter() == null)) {
+      System.out.println(m_XMLInstances.toString());
+      setWriteMode(WAIT);
+    }
+    else {
+      PrintWriter outW = new PrintWriter(getWriter());
+      outW.println(m_XMLInstances.toString());
+      outW.flush();
+      outW.close();
+      setWriteMode(WAIT);
+      outW = null;
+      resetWriter();
+      setWriteMode(CANCEL);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method.
+   *
+   * @param args 	should contain the options of a Saver.
+   */
+  public static void main(String[] args) {
+    runFileSaver(new XRFFSaver(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/json/JSONInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/JSONInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/JSONInstances.java	(revision 29)
@@ -0,0 +1,402 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JSONInstances.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.json;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.DenseInstance;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.util.ArrayList;
+
+/**
+ * Class for transforming Instances objects into <a href="http://www.json.org/" target="_blank">JSON</a>
+ * objects and vice versa.
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class JSONInstances {
+
+  /** the header section. */
+  public final static String HEADER = "header";
+
+  /** the data section. */
+  public final static String DATA = "data";
+
+  /** the relation name. */
+  public final static String RELATION = "relation";
+
+  /** the attributes object. */
+  public final static String ATTRIBUTES = "attributes";
+
+  /** the name attribute. */
+  public final static String NAME = "name";
+
+  /** the type attribute. */
+  public final static String TYPE = "type";
+
+  /** the class attribute indicator. */
+  public final static String CLASS = "class";
+
+  /** the labels attribute. */
+  public final static String LABELS = "labels";
+
+  /** the weight attribute. */
+  public final static String WEIGHT = "weight";
+
+  /** the dateformat attribute. */
+  public final static String DATEFORMAT = "dateformat";
+
+  /** the sparse attribute. */
+  public final static String SPARSE = "sparse";
+
+  /** the values attribute. */
+  public final static String VALUES = "values";
+
+  /** the separator for index/value in case of sparse instances. */
+  public final static String SPARSE_SEPARATOR = ":";
+  
+  /**
+   * Turns the JSON object into an Attribute, if possible.
+   * 
+   * @param att		the JSON object to turn into an Attribute
+   * @param classAtt	for storing whether the attribute is the class attribute
+   * @return		the Attribute, null in case of an error
+   */
+  protected static Attribute toAttribute(JSONNode att, boolean[] classAtt) {
+    Attribute	result;
+    String	name;
+    String	type;
+    String	dateformat;
+    JSONNode	labels;
+    ArrayList<String>	values;
+    int		i;
+    double	weight;
+    
+    name   = (String) att.getChild(NAME).getValue("noname");
+    type   = (String) att.getChild(TYPE).getValue("");
+    weight = (Double) att.getChild(WEIGHT).getValue(new Double(1.0));
+    if (type.equals(Attribute.typeToString(Attribute.NUMERIC))) {
+      result = new Attribute(name);
+    }
+    else if (type.equals(Attribute.typeToString(Attribute.NOMINAL))) {
+      labels = att.getChild(LABELS);
+      values = new ArrayList<String>();
+      for (i = 0; i < labels.getChildCount(); i++)
+	values.add((String)((JSONNode) labels.getChildAt(i)).getValue());
+      result = new Attribute(name, values);
+    }
+    else if (type.equals(Attribute.typeToString(Attribute.DATE))) {
+      dateformat = (String) att.getChild(TYPE).getValue("yyyy-MM-dd'T'HH:mm:ss");
+      result     = new Attribute(name, dateformat);
+    }
+    else if (type.equals(Attribute.typeToString(Attribute.STRING))) {
+      result = new Attribute(name, (ArrayList<String>) null);
+    }
+    else {
+      System.err.println("Unhandled attribute type '" + type + "'!");
+      return null;
+    }
+    result.setWeight(weight);
+    
+    return result;
+  }
+
+  /**
+   * Turns the JSON Object into an Instance, if possible.
+   * 
+   * @param inst	the JSON object to turn into an Instance
+   * @param data	the data so far (only used for header information)
+   * @return		the Instance, null in case of an error
+   */
+  protected static Instance toInstance(JSONNode inst, Instances data) {
+    Instance	result;
+    boolean	sparse;
+    double	weight;
+    JSONNode	values;
+    int		i;
+    int		index;
+    int		pos;
+    String	value;
+    double[]	vals;
+
+    sparse = (Boolean) inst.getChild(SPARSE).getValue(new Boolean(false));
+    weight = (Double) inst.getChild(WEIGHT).getValue(new Double(1.0));
+    values = inst.getChild(VALUES);
+    vals   = new double[data.numAttributes()];
+    for (i = 0; i < values.getChildCount(); i++) {
+      if (sparse) {
+	value = "" + ((JSONNode) values.getChildAt(i)).getValue();
+	pos   = value.indexOf(SPARSE_SEPARATOR);
+	index = Integer.parseInt(value.substring(0, pos));
+	value = value.substring(pos + 1);
+      }
+      else {
+	index = i;
+	value = "" + ((JSONNode) values.getChildAt(i)).getValue();
+      }
+      
+      try {
+	if (data.attribute(index).isNumeric()) {
+	  vals[index] = Double.parseDouble(value);
+	}
+	else if (data.attribute(index).isNominal()) {
+	  vals[index] = data.attribute(index).indexOfValue(value);
+	  if ((vals[index] == -1) && value.startsWith("'") && value.endsWith("'"))
+	    vals[index] = data.attribute(index).indexOfValue(Utils.unquote(value));
+	  if (vals[index] == -1) {
+	    System.err.println("Unknown label '" + value + "' for attribute #" + (index+1) + "!");
+	    return null;
+	  }
+	}
+	else if (data.attribute(index).isDate()) {
+	  vals[index] = data.attribute(index).parseDate(value);
+	}
+	else if (data.attribute(index).isString()) {
+	  vals[index] = data.attribute(index).addStringValue(value);
+	}
+	else {
+	  System.err.println("Unhandled attribute type '" + Attribute.typeToString(data.attribute(index).type()) + "'!");
+	  return null;
+	}
+      }
+      catch (Exception e) {
+	System.err.println("Error parsing value #" + (index+1) + ": " + e.toString());
+	return null;
+      }
+    }
+
+    result = new DenseInstance(weight, vals);
+    result.setDataset(data);
+      
+    return result;
+  }
+  
+  /**
+   * Turns a JSON object, if possible, into an Instances object.
+   * 
+   * @param json	the JSON object to convert
+   * @param onlyHeader	whether to retrieve only the header
+   * @return		the generated Instances object, null if not possible
+   */
+  protected static Instances toInstances(JSONNode json, boolean onlyHeader) {
+    Instances	result;
+    JSONNode	header;
+    JSONNode	attributes;
+    JSONNode	data;
+    ArrayList<Attribute>	atts;
+    Attribute	att;
+    Instance	inst;
+    int		i;
+    int		classIndex;
+    boolean[]	classAtt;
+    
+    header = json.getChild(HEADER);
+    if (header == null) {
+      System.err.println("No '" + HEADER + "' section!");
+      return null;
+    }
+    data = json.getChild(DATA);
+    if (data == null) {
+      System.err.println("No '" + DATA + "' section!");
+      return null;
+    }
+    
+    // attributes
+    attributes = header.getChild(ATTRIBUTES);
+    if (attributes == null) {
+      System.err.println("No '" + ATTRIBUTES + "' array!");
+      return null;
+    }
+    atts       = new ArrayList<Attribute>();
+    classAtt   = new boolean[1];
+    classIndex = -1;
+    for (i = 0; i < attributes.getChildCount(); i++) {
+      att = toAttribute((JSONNode) attributes.getChildAt(i), classAtt);
+      if (att == null) {
+	System.err.println("Could not convert attribute #" + (i+1) + "!");
+	return null;
+      }
+      if (classAtt[0])
+	classIndex = i;
+      atts.add(att);
+    }
+    result = new Instances(
+	header.getChild(RELATION).getValue("unknown").toString(), 
+	atts, 
+	(onlyHeader ? 0 : data.getChildCount()));
+    result.setClassIndex(classIndex);
+    
+    // data
+    if (!onlyHeader) {
+      for (i = 0; i < data.getChildCount(); i++) {
+	inst = toInstance((JSONNode) data.getChildAt(i), result);
+	if (inst == null) {
+	  System.err.println("Could not convert instance #" + (i+1) + "!");
+	  return null;
+	}
+	result.add(inst);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Turns a JSON object, if possible, into an Instances object.
+   * 
+   * @param json	the JSON object to convert
+   * @return		the generated Instances object, null if not possible
+   */
+  public static Instances toInstances(JSONNode json) {
+    return toInstances(json, false);
+  }
+  
+  /**
+   * Turns a JSON object, if possible, into an Instances object (only header).
+   * 
+   * @param json	the JSON object to convert
+   * @return		the generated Instances header object, null if not possible
+   */
+  public static Instances toHeader(JSONNode json) {
+    return toInstances(json, true);
+  }
+  
+  /**
+   * Turns the Attribute into a JSON object.
+   * 
+   * @param inst	the corresponding dataset
+   * @param att		the attribute to convert
+   * @return		the JSON object
+   */
+  protected static JSONNode toJSON(Instances inst, Attribute att) {
+    JSONNode	result;
+    JSONNode	labels;
+    int		i;
+    
+    result = new JSONNode();
+
+    result.addPrimitive(NAME, att.name());
+    result.addPrimitive(TYPE, Attribute.typeToString(att));
+    result.addPrimitive(CLASS, (att.index() == inst.classIndex()));
+    result.addPrimitive(WEIGHT, att.weight());
+    if (att.isNominal()) {
+      labels = result.addArray(LABELS);
+      for (i = 0; i < att.numValues(); i++)
+	labels.addArrayElement(att.value(i));
+    }
+    if (att.isDate())
+      result.addPrimitive(DATEFORMAT, att.getDateFormat());
+    
+    return result;
+  }
+  
+  /**
+   * Turns the Instance into a JSON object.
+   * 
+   * @param inst	the Instance to convert
+   * @return		the JSON object
+   */
+  protected static JSONNode toJSON(Instance inst) {
+    JSONNode	result;
+    JSONNode	values;
+    int		i;
+    boolean	sparse;
+    
+    result = new JSONNode();
+    
+    sparse = (inst instanceof SparseInstance);
+    result.addPrimitive(SPARSE, sparse);
+    result.addPrimitive(WEIGHT, inst.weight());
+    values = result.addArray(VALUES);
+    if (sparse) {
+      for (i = 0; i < inst.numValues(); i++)
+	values.addArrayElement(inst.index(i) + SPARSE_SEPARATOR + inst.toString(inst.index(i)));
+    }
+    else {
+      for (i = 0; i < inst.numAttributes(); i++)
+	values.addArrayElement(inst.toString(i));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Turns the Instances object into a JSON object.
+   * 
+   * @param inst	the Instances to turn into a JSON object
+   * @return		the JSON object
+   */
+  public static JSONNode toJSON(Instances inst) {
+    JSONNode	result;
+    JSONNode	header;
+    JSONNode	atts;
+    JSONNode	data;
+    int		i;
+    
+    result = new JSONNode();
+    
+    // header
+    header = result.addObject(HEADER);
+    header.addPrimitive(RELATION, inst.relationName());
+    atts = header.addArray(ATTRIBUTES);
+    for (i = 0; i < inst.numAttributes(); i++)
+      atts.add(toJSON(inst, inst.attribute(i)));
+    
+    // data
+    data = result.addArray(DATA);
+    for (i = 0; i < inst.numInstances(); i++)
+      data.add(toJSON(inst.instance(i)));
+    
+    return result;
+  }
+  
+  /**
+   * For testing only.
+   * 
+   * @param args	expects a dataset as first parameter
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    if (args.length != 1) {
+      System.err.println("No dataset supplied!");
+      System.exit(1);
+    }
+
+    // load dataset
+    Instances data = DataSource.read(args[0]);
+    
+    // turn Instances into JSON object and output it
+    JSONNode json = toJSON(data);
+    StringBuffer buffer = new StringBuffer();
+    json.toString(buffer);
+    System.out.println(buffer.toString());
+    
+    // turn JSON object back into Instances and output it
+    Instances inst = toInstances(json);
+    System.out.println(inst);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/json/JSONNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/JSONNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/JSONNode.java	(revision 29)
@@ -0,0 +1,599 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JSONObject.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.json;
+
+import java.awt.BorderLayout;
+import java.io.Reader;
+
+import java_cup.runtime.DefaultSymbolFactory;
+import java_cup.runtime.SymbolFactory;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * Container class for storing a <a href="http://www.json.org/" target="_blank">JSON</a> 
+ * data structure.
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5786 $
+ */
+public class JSONNode
+  extends DefaultMutableTreeNode {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -3047440914507883491L;
+
+  /**
+   * The type of a node.
+   * 
+   * @author  FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5786 $
+   */
+  public static enum NodeType {
+    /** a primitive. */
+    PRIMITIVE,
+    /** an object with nested key-value pairs. */
+    OBJECT,
+    /** an array. */
+    ARRAY
+  }
+ 
+  /** the name of the node. */
+  protected String m_Name;
+  
+  /** the value of the node. */
+  protected Object m_Value;
+  
+  /** the type of the node. */
+  protected NodeType m_NodeType;
+  
+  /**
+   * Initializes the root container.
+   */
+  public JSONNode() {
+    this(null, NodeType.OBJECT);
+  }
+  
+  /**
+   * Initializes the primitive container.
+   * 
+   * @param name	the name
+   * @param value	the primitive value
+   */
+  public JSONNode(String name, Boolean value) {
+    this(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Initializes the primitive container.
+   * 
+   * @param name	the name
+   * @param value	the primitive value
+   */
+  public JSONNode(String name, Integer value) {
+    this(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Initializes the primitive container.
+   * 
+   * @param name	the name
+   * @param value	the primitive value
+   */
+  public JSONNode(String name, Double value) {
+    this(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Initializes the primitive container.
+   * 
+   * @param name	the name
+   * @param value	the primitive value
+   */
+  public JSONNode(String name, String value) {
+    this(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Initializes the object container with null value.
+   * 
+   * @param name	the name
+   * @param type	the node type
+   */
+  protected JSONNode(String name, NodeType type) {
+    this(name, null, type);
+  }
+  
+  /**
+   * Initializes the container.
+   * 
+   * @param name	the name
+   * @param value	the primitive value
+   * @param type	the type of the node, null for primitives
+   */
+  protected JSONNode(String name, Object value, NodeType type) {
+    super();
+    
+    m_Name     = name;
+    m_Value    = value;
+    m_NodeType = type;
+  }
+  
+  /**
+   * Checks whether the node is anonymous.
+   * 
+   * @return		true if no name available
+   */
+  public boolean isAnonymous() {
+    return (m_Name == null);
+  }
+  
+  /**
+   * Returns the name of the node.
+   * 
+   * @return		the name, null for anonymous nodes
+   */
+  public String getName() {
+    return m_Name;
+  }
+  
+  /**
+   * Returns the stored value.
+   * 
+   * @return		the stored value, can be null
+   */
+  public Object getValue() {
+    return getValue(null);
+  }
+  
+  /**
+   * Returns the stored value.
+   * 
+   * @param defValue	the default value, if value is null
+   * @return		the stored value, can be null
+   */
+  public Object getValue(Object defValue) {
+    if (m_Value == null)
+      return defValue;
+    else
+      return m_Value;
+  }
+  
+  /**
+   * Returns whether the node stores a primitive value or a an array/object.
+   * 
+   * @return		true if a primitive, false in case of an array/object
+   */
+  public boolean isPrimitive() {
+    return (m_NodeType == NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Returns wether the node is an array.
+   * 
+   * @return		true if the node is array container
+   */
+  public boolean isArray() {
+    return (m_NodeType == NodeType.ARRAY);
+  }
+  
+  /**
+   * Returns wether the node is an object.
+   * 
+   * @return		true if the node is object container
+   */
+  public boolean isObject() {
+    return (m_NodeType == NodeType.OBJECT);
+  }
+  
+  /**
+   * Returns the type of the container.
+   * 
+   * @return		the type
+   */
+  public NodeType getNodeType() {
+    return m_NodeType;
+  }
+  
+  /**
+   * Adds a "null" child to the object.
+   * 
+   * @param name	the name of the null value
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addNull(String name) {
+    return add(name, null, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Adds a key-value child to the object.
+   * 
+   * @param name	the name of the pair
+   * @param value	the value
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addPrimitive(String name, Boolean value) {
+    return add(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Adds a key-value child to the object.
+   * 
+   * @param name	the name of the pair
+   * @param value	the value
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addPrimitive(String name, Integer value) {
+    return add(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Adds a key-value child to the object.
+   * 
+   * @param name	the name of the pair
+   * @param value	the value
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addPrimitive(String name, Double value) {
+    return add(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Adds a key-value child to the object.
+   * 
+   * @param name	the name of the pair
+   * @param value	the value
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addPrimitive(String name, String value) {
+    return add(name, value, NodeType.PRIMITIVE);
+  }
+  
+  /**
+   * Adds an array child to the object.
+   * 
+   * @param name	the name of the pair
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addArray(String name) {
+    return add(name, null, NodeType.ARRAY);
+  }
+  
+  /**
+   * Adds an array element child to the array.
+   * 
+   * @param value	the value of the element array
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addArrayElement(Object value) {
+    NodeType	type;
+
+    if (getNodeType() != NodeType.ARRAY)
+      return null;
+    
+    type = null;
+    
+    if (value != null) {
+      if (value instanceof Boolean)
+	type = NodeType.PRIMITIVE;
+      else if (value instanceof Integer)
+	type = NodeType.PRIMITIVE;
+      else if (value instanceof Double)
+	type = NodeType.PRIMITIVE;
+      else if (value instanceof String)
+	type = NodeType.PRIMITIVE;
+      else if (value.getClass().isArray())
+	type = NodeType.ARRAY;
+      else
+	type = NodeType.OBJECT;
+    }
+      
+    return add(null, value, type);
+  }
+  
+  /**
+   * Adds an object child to the object.
+   * 
+   * @param name	the name of the pair
+   * @return		the new node, or null if none added
+   */
+  public JSONNode addObject(String name) {
+    return add(name, null, NodeType.OBJECT);
+  }
+  
+  /**
+   * Adds a key-value child to the object.
+   * 
+   * @param name	the name of the pair
+   * @param value	the value
+   * @param type	the node type, null for primitives
+   * @return		the new node, or null if none added
+   */
+  protected JSONNode add(String name, Object value, NodeType type) {
+    JSONNode	child;
+    
+    if (isPrimitive())
+      return null;
+    
+    child = new JSONNode(name, value, type);
+    add(child);
+    
+    return child;
+  }
+  
+  /**
+   * Checks whether the node has a child with the given name.
+   * 
+   * @param name	the name of the child
+   * @return		true if child with that name is available
+   */
+  public boolean hasChild(String name) {
+    return (getChild(name) != null);
+  }
+  
+  /**
+   * Returns the child with the given name.
+   * 
+   * @param name	the name of the child
+   * @return		the child if available, null otherwise
+   */
+  public JSONNode getChild(String name) {
+    JSONNode	result;
+    JSONNode	node;
+    int		i;
+    
+    result = null;
+    
+    for (i = 0; i < getChildCount(); i++) {
+      node = (JSONNode) getChildAt(i);
+      if (!node.isAnonymous() && node.getName().equals(name)) {
+	result = node;
+	break;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Generates the indentation string.
+   * 
+   * @param level	the level
+   * @return		the indentation string (tabs)
+   */
+  protected String getIndentation(int level) {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+    for (i = 0; i < level; i++)
+      result.append("\t");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Escapes ", \, /, \b, \f, \n, \r, \t in strings.
+   * 
+   * @param o		the object to process (only strings get processed)
+   * @return		the processed object
+   */
+  protected Object escape(Object o) {
+    if (o instanceof String)
+      return escape((String) o);
+    else
+      return o;
+  }
+  
+  /**
+   * Escapes ", /, \b, \f, \n, \r, \t.
+   * 
+   * @param s		the string to process
+   * @return		the processed
+   */
+  protected String escape(String s) {
+    StringBuffer	result;
+    int			i;
+    char		c;
+    
+    if (    (s.indexOf('\"') > -1)
+         || (s.indexOf('\\') > -1) 
+         || (s.indexOf('\b') > -1) 
+         || (s.indexOf('\f') > -1) 
+         || (s.indexOf('\n') > -1) 
+         || (s.indexOf('\r') > -1) 
+         || (s.indexOf('\t') > -1) ) {
+      result = new StringBuffer();
+      for (i = 0; i < s.length(); i++) {
+	c = s.charAt(i);
+	if (c == '\"')
+	  result.append("\\\"");
+	else if (c == '\\')
+	  result.append("\\\\");
+	else if (c == '\b')
+	  result.append("\\b");
+	else if (c == '\f')
+	  result.append("\\f");
+	else if (c == '\n')
+	  result.append("\\n");
+	else if (c == '\r')
+	  result.append("\\r");
+	else if (c == '\t')
+	  result.append("\\t");
+	else
+	  result.append(c);
+      }
+    }
+    else {
+      result = new StringBuffer(s);
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Dumps the node structure into JSON format.
+   * 
+   * @param buffer	the buffer to add the data to
+   */
+  public void toString(StringBuffer buffer) {
+    int		level;
+    boolean	isLast;
+    String	indent;
+    int		i;
+    
+    level  = getLevel();
+    isLast = (getNextSibling() == null);
+    indent = getIndentation(level);
+    
+    buffer.append(indent);
+    if (m_Name != null) {
+      buffer.append("\"");
+      buffer.append(escape(m_Name));
+      buffer.append("\" : ");
+    }
+    
+    if (isObject()) {
+      buffer.append("{\n");
+      for (i = 0; i < getChildCount(); i++)
+	((JSONNode) getChildAt(i)).toString(buffer);
+      buffer.append(indent);
+      buffer.append("}");
+    }
+    else if (isArray()) {
+      buffer.append("[\n");
+      for (i = 0; i < getChildCount(); i++)
+	((JSONNode) getChildAt(i)).toString(buffer);
+      buffer.append(indent);
+      buffer.append("]");
+    }
+    else {
+      if (m_Value == null) {
+	buffer.append("null");
+      }
+      else if (m_Value instanceof String) {
+	buffer.append("\"");
+	buffer.append(escape((String) m_Value));
+	buffer.append("\"");
+      }
+      else {
+	buffer.append(m_Value.toString());
+      }
+    }
+    
+    if (!isLast)
+      buffer.append(",");
+    buffer.append("\n");
+  }
+
+  /**
+   * Returns a string representation of the node.
+   * 
+   * @return		the string representation
+   */
+  public String toString() {
+    String	result;
+    
+    result = null;
+    
+    if (isObject()) {
+      if (isRoot())
+	result = "JSON";
+      else if (m_Name == null)
+	result = "<object>";
+      else
+	result = escape(m_Name) + " (Object)";
+    }
+    else if (isArray()) {
+      if (m_Name == null)
+	result = "<array>";
+      else
+	result = escape(m_Name) + " (Array)";
+    }
+    else {
+      if (m_Name != null)
+	result = escape(m_Name) + ": " + escape(m_Value);
+      else
+	result = "" + m_Value;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Reads the JSON object from the given reader.
+   * 
+   * @param reader	the reader to read the JSON object from
+   * @return		the generated JSON object
+   * @throws Exception	if parsing fails
+   */
+  public static JSONNode read(Reader reader) throws Exception {
+    SymbolFactory 	sf;
+    Parser 		parser;
+    
+    sf     = new DefaultSymbolFactory();
+    parser = new Parser(new Scanner(reader, sf), sf);
+    parser.parse();
+    
+    return parser.getResult();
+  }
+  
+  /**
+   * Only for testing. Generates a simple JSON object and displays it.
+   * 
+   * @param args	ignored
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    // generates the example listed here:
+    // http://en.wikipedia.org/wiki/JSON
+    JSONNode person = new JSONNode();
+    person.addPrimitive("firstName", "John");
+    person.addPrimitive("lastName", "Smith");
+    JSONNode address = person.addObject("address");
+    address.addPrimitive("streetAddress", "21 2nd Street");
+    address.addPrimitive("city", "New York");
+    address.addPrimitive("state", "NY");
+    address.addPrimitive("postalCode", 10021);
+    JSONNode phonenumbers = person.addArray("phoneNumbers");
+    phonenumbers.addArrayElement("212 555-1234");
+    phonenumbers.addArrayElement("646 555-4567");
+    
+    // output in console
+    StringBuffer buffer = new StringBuffer();
+    person.toString(buffer);
+    System.out.println(buffer.toString());
+    
+    // display GUI
+    JTree tree = new JTree(person);
+    JFrame frame = new JFrame("JSON");
+    frame.setSize(800, 600);
+    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    frame.getContentPane().setLayout(new BorderLayout());
+    frame.getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
+    frame.setLocationRelativeTo(null);
+    frame.setVisible(true);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/json/Parser.cup
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/Parser.cup	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/Parser.cup	(revision 29)
@@ -0,0 +1,280 @@
+/*
+ * STANDARD ML OF NEW JERSEY COPYRIGHT NOTICE, LICENSE AND DISCLAIMER.
+ * 
+ * Copyright (c) 1989-1998 by Lucent Technologies
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both the
+ * copyright notice and this permission notice and warranty disclaimer appear
+ * in supporting documentation, and that the name of Lucent Technologies, Bell
+ * Labs or any Lucent entity not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior permission.
+ *
+ * Lucent disclaims all warranties with regard to this software, including all
+ * implied warranties of merchantability and fitness. In no event shall Lucent
+ * be liable for any special, indirect or consequential damages or any damages
+ * whatsoever resulting from loss of use, data or profits, whether in an action
+ * of contract, negligence or other tortious action, arising out of or in
+ * connection with the use or performance of this software. 
+ *
+ * Taken from this URL:
+ * http://www.smlnj.org/license.html
+ * 
+ * This license is compatible with the GNU GPL (see section "Standard ML of New
+ * Jersey Copyright License"):
+ * http://www.gnu.org/licenses/license-list.html#StandardMLofNJ
+ */
+
+/*
+ * Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian
+ */
+
+package weka.core.json;
+
+import java_cup.runtime.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A parser for parsing JSON files.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5785 $
+ */
+
+parser code {:
+  /** variable - value relation. */
+  protected HashMap m_Symbols;
+
+  /** for storing the parsed JSON data structure. */
+  protected JSONNode m_Result;
+  
+  /** the stack for keeping track of the current parent node. */
+  protected Stack<JSONNode> m_Stack;
+
+  /**
+   * Returns the JSON data structure.
+   * 
+   * @return the result
+   */
+  public JSONNode getResult() {
+    return m_Result;
+  }
+  
+  /**
+   * Returns the stack used internally for keeping track of the current
+   * parent node.
+   * 
+   * @return the stack
+   */
+  protected Stack<JSONNode> getStack() {
+    return m_Stack;
+  }
+
+  /**
+   * Runs the parser from commandline. Expects a filename as first parameter,
+   * pointing to a JSON file.
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    if (args.length != 1) {
+      System.err.println("No JSON file specified!");
+      System.exit(1);
+    }
+    
+    FileInputStream stream = new FileInputStream(args[0]);
+    SymbolFactory sf = new DefaultSymbolFactory();
+    Parser parser = new Parser(new Scanner(stream, sf), sf);
+    parser.parse();
+    StringBuffer buffer = new StringBuffer();
+    parser.getResult().toString(buffer);
+    System.out.println(buffer.toString());
+  }
+:}
+
+init with {:
+  m_Symbols = new HashMap();
+  m_Result  = new JSONNode();
+  m_Stack   = new Stack<JSONNode>();
+  m_Stack.push(m_Result);
+:}
+
+terminal COMMA;
+terminal LSQUARE;
+terminal RSQUARE;
+terminal LCURLY;
+terminal RCURLY;
+terminal COLON;
+terminal NULL;
+terminal Boolean BOOLEAN;
+terminal Integer INTEGER;
+terminal Double DOUBLE;
+terminal String STRING;
+
+non terminal json;
+non terminal pairs;
+non terminal pair;
+non terminal primitive;
+non terminal null;
+non terminal Boolean boolean;
+non terminal Integer integer;
+non terminal Double double;
+non terminal String string;
+non terminal anon_object;
+non terminal named_object;
+non terminal named_object_start;
+non terminal anon_object_start;
+non terminal object_content;
+non terminal object_end;
+non terminal anon_array;
+non terminal named_array;
+non terminal named_array_start;
+non terminal anon_array_start;
+non terminal array_content;
+non terminal array_end;
+non terminal elements;
+non terminal element;
+
+json               ::=   LCURLY RCURLY
+                       | LCURLY pairs RCURLY
+                       ;
+
+pairs              ::=   pairs COMMA pair 
+                       | pair
+                       ; 
+
+pair               ::=   primitive 
+                       | named_object 
+                       | named_array
+                       ;
+
+primitive          ::=   null
+                       | boolean
+                       | integer
+                       | double
+                       | string
+                       ;
+                    
+null               ::= STRING:name COLON NULL
+                       {: 
+                          parser.getStack().peek().addNull(name);
+                       :}
+                       ;
+                    
+boolean            ::= STRING:name COLON BOOLEAN:b
+                       {:
+                          parser.getStack().peek().addPrimitive(name, b);
+                       :}
+                       ;
+                    
+integer            ::= STRING:name COLON INTEGER:i
+                       {:
+                          parser.getStack().peek().addPrimitive(name, i);
+                       :}
+                       ;
+                    
+double             ::= STRING:name COLON DOUBLE:d
+                       {:
+                          parser.getStack().peek().addPrimitive(name, d);
+                       :}
+                       ;
+                    
+string             ::= STRING:name COLON STRING:s
+                       {: 
+                          parser.getStack().peek().addPrimitive(name, s);
+                       :}
+                       ;
+                    
+named_object       ::=   named_object_start object_end
+                       | named_object_start object_content object_end
+                       ;
+                    
+named_object_start ::= STRING:name COLON LCURLY
+                       {:
+                          JSONNode node = parser.getStack().peek().addObject(name);
+                          parser.getStack().push(node);
+                       :}
+                       ;
+                    
+anon_object        ::=   anon_object_start object_end
+                       | anon_object_start object_content object_end
+                       ;
+                    
+anon_object_start  ::= LCURLY
+                       {:
+                          JSONNode node = parser.getStack().peek().addObject(null);
+                          parser.getStack().push(node);
+                       :}
+                       ;
+                    
+object_content     ::= pairs
+                       ;
+                    
+object_end         ::= RCURLY
+                       {:
+                          parser.getStack().pop();
+                       :}
+                       ;
+                    
+named_array        ::=   named_array_start array_end
+                       | named_array_start array_content array_end
+                       ;
+                    
+named_array_start  ::= STRING:name COLON LSQUARE
+                       {:
+                          JSONNode node = parser.getStack().peek().addArray(name);
+                          parser.getStack().push(node);
+                       :}
+                       ;
+                    
+anon_array         ::=   anon_array_start array_end
+                       | anon_array_start array_content array_end
+                       ;
+                    
+anon_array_start   ::= LSQUARE
+                       {:
+                          JSONNode node = parser.getStack().peek().addArray(null);
+                          parser.getStack().push(node);
+                       :}
+                       ;
+                    
+array_content      ::= elements
+                       ;
+                    
+array_end          ::= RSQUARE
+                       {:
+                          parser.getStack().pop();
+                       :}
+                       ;
+                    
+elements           ::=   elements COMMA element 
+                       | element
+                       ;
+                    
+element            ::=   NULL 
+                         {: 
+                            parser.getStack().peek().addArrayElement(null);
+                         :}
+                       | BOOLEAN:b 
+                         {: 
+                            parser.getStack().peek().addArrayElement(b);
+                         :}
+                       | INTEGER:i 
+                         {:
+                            parser.getStack().peek().addArrayElement(i);
+                         :}
+                       | DOUBLE:d 
+                         {:
+                            parser.getStack().peek().addArrayElement(d);
+                         :}
+                       | STRING:s
+                         {: 
+                            parser.getStack().peek().addArrayElement(s);
+                         :}
+                       | anon_object
+                       | anon_array
+                       ;
Index: branches/MetisMQI/src/main/java/weka/core/json/Parser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/Parser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/Parser.java	(revision 29)
@@ -0,0 +1,735 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Wed Jul 22 21:31:13 NZST 2009
+//----------------------------------------------------
+
+package weka.core.json;
+
+import java_cup.runtime.*;
+import java.io.*;
+import java.util.*;
+
+/** CUP v0.11a beta 20060608 generated parser.
+  * @version Wed Jul 22 21:31:13 NZST 2009
+  */
+public class Parser extends java_cup.runtime.lr_parser {
+
+  /** Default constructor. */
+  public Parser() {super();}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s) {super(s);}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s, java_cup.runtime.SymbolFactory sf) {super(s,sf);}
+
+  /** Production table. */
+  protected static final short _production_table[][] = 
+    unpackFromStrings(new String[] {
+    "\000\053\000\002\002\004\000\002\002\004\000\002\002" +
+    "\005\000\002\003\005\000\002\003\003\000\002\004\003" +
+    "\000\002\004\003\000\002\004\003\000\002\005\003\000" +
+    "\002\005\003\000\002\005\003\000\002\005\003\000\002" +
+    "\005\003\000\002\006\005\000\002\007\005\000\002\010" +
+    "\005\000\002\011\005\000\002\012\005\000\002\014\004" +
+    "\000\002\014\005\000\002\015\005\000\002\013\004\000" +
+    "\002\013\005\000\002\016\003\000\002\017\003\000\002" +
+    "\020\003\000\002\022\004\000\002\022\005\000\002\023" +
+    "\005\000\002\021\004\000\002\021\005\000\002\024\003" +
+    "\000\002\025\003\000\002\026\003\000\002\027\005\000" +
+    "\002\027\003\000\002\030\003\000\002\030\003\000\002" +
+    "\030\003\000\002\030\003\000\002\030\003\000\002\030" +
+    "\003\000\002\030\003" });
+
+  /** Access to production table. */
+  public short[][] production_table() {return _production_table;}
+
+  /** Parse-action table. */
+  protected static final short[][] _action_table = 
+    unpackFromStrings(new String[] {
+    "\000\073\000\004\007\005\001\002\000\004\002\075\001" +
+    "\002\000\006\010\020\016\014\001\002\000\006\004\062" +
+    "\010\074\001\002\000\006\010\060\016\014\001\002\000" +
+    "\022\005\035\006\043\007\051\012\042\013\041\014\037" +
+    "\015\047\016\045\001\002\000\006\004\ufffd\010\ufffd\001" +
+    "\002\000\006\004\ufffb\010\ufffb\001\002\000\006\004\ufff7" +
+    "\010\ufff7\001\002\000\004\011\024\001\002\000\006\004" +
+    "\ufff9\010\ufff9\001\002\000\006\004\ufffc\010\ufffc\001\002" +
+    "\000\006\004\ufff5\010\ufff5\001\002\000\004\002\001\001" +
+    "\002\000\006\004\ufff6\010\ufff6\001\002\000\006\004\ufffa" +
+    "\010\ufffa\001\002\000\006\004\ufff8\010\ufff8\001\002\000" +
+    "\020\005\025\007\031\012\032\013\033\014\026\015\027" +
+    "\016\030\001\002\000\022\005\uffe5\006\uffe5\007\uffe5\012" +
+    "\uffe5\013\uffe5\014\uffe5\015\uffe5\016\uffe5\001\002\000\006" +
+    "\004\ufff2\010\ufff2\001\002\000\006\004\ufff1\010\ufff1\001" +
+    "\002\000\006\004\ufff0\010\ufff0\001\002\000\006\010\uffed" +
+    "\016\uffed\001\002\000\006\004\ufff4\010\ufff4\001\002\000" +
+    "\006\004\ufff3\010\ufff3\001\002\000\006\004\067\006\uffe1" +
+    "\001\002\000\022\005\uffe2\006\uffe2\007\uffe2\012\uffe2\013" +
+    "\uffe2\014\uffe2\015\uffe2\016\uffe2\001\002\000\022\005\035" +
+    "\006\043\007\051\012\042\013\041\014\037\015\047\016" +
+    "\045\001\002\000\006\004\uffdb\006\uffdb\001\002\000\006" +
+    "\004\uffe7\010\uffe7\001\002\000\006\004\uffdc\006\uffdc\001" +
+    "\002\000\006\004\uffdd\006\uffdd\001\002\000\010\004\uffe0" +
+    "\006\uffe0\010\uffe0\001\002\000\006\004\uffde\006\uffde\001" +
+    "\002\000\006\004\uffd9\006\uffd9\001\002\000\006\010\060" +
+    "\016\014\001\002\000\006\004\uffda\006\uffda\001\002\000" +
+    "\004\006\043\001\002\000\006\010\uffea\016\uffea\001\002" +
+    "\000\006\004\uffd7\006\uffd7\001\002\000\006\004\uffd8\006" +
+    "\uffd8\001\002\000\006\004\uffe6\010\uffe6\001\002\000\006" +
+    "\004\062\010\uffe9\001\002\000\004\010\060\001\002\000" +
+    "\006\004\uffec\006\uffec\001\002\000\010\004\uffe8\006\uffe8" +
+    "\010\uffe8\001\002\000\006\004\uffeb\006\uffeb\001\002\000" +
+    "\004\016\014\001\002\000\006\004\ufffe\010\ufffe\001\002" +
+    "\000\006\004\uffe4\006\uffe4\001\002\000\004\006\043\001" +
+    "\002\000\006\004\uffe3\006\uffe3\001\002\000\020\005\035" +
+    "\007\051\012\042\013\041\014\037\015\047\016\045\001" +
+    "\002\000\006\004\uffdf\006\uffdf\001\002\000\004\010\060" +
+    "\001\002\000\006\004\uffef\010\uffef\001\002\000\006\004" +
+    "\uffee\010\uffee\001\002\000\004\002\uffff\001\002\000\004" +
+    "\002\000\001\002" });
+
+  /** Access to parse-action table. */
+  public short[][] action_table() {return _action_table;}
+
+  /** <code>reduce_goto</code> table. */
+  protected static final short[][] _reduce_table = 
+    unpackFromStrings(new String[] {
+    "\000\073\000\004\002\003\001\001\000\002\001\001\000" +
+    "\032\003\005\004\010\005\015\006\014\007\022\010\012" +
+    "\011\020\012\016\014\011\015\006\022\021\023\007\001" +
+    "\001\000\002\001\001\000\036\003\054\004\010\005\015" +
+    "\006\014\007\022\010\012\011\020\012\016\014\011\015" +
+    "\006\017\070\020\071\022\021\023\007\001\001\000\022" +
+    "\013\052\016\045\021\051\024\035\025\047\026\037\027" +
+    "\033\030\043\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\002\001" +
+    "\001\000\002\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\022" +
+    "\013\052\016\045\021\051\024\035\025\064\026\063\027" +
+    "\033\030\043\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\036\003\054\004\010" +
+    "\005\015\006\014\007\022\010\012\011\020\012\016\014" +
+    "\011\015\006\017\055\020\056\022\021\023\007\001\001" +
+    "\000\002\001\001\000\004\026\053\001\001\000\002\001" +
+    "\001\000\002\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\004\020\060\001\001\000\002\001" +
+    "\001\000\002\001\001\000\002\001\001\000\030\004\062" +
+    "\005\015\006\014\007\022\010\012\011\020\012\016\014" +
+    "\011\015\006\022\021\023\007\001\001\000\002\001\001" +
+    "\000\002\001\001\000\004\026\065\001\001\000\002\001" +
+    "\001\000\014\013\052\016\045\021\051\024\035\030\067" +
+    "\001\001\000\002\001\001\000\004\020\072\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001" });
+
+  /** Access to <code>reduce_goto</code> table. */
+  public short[][] reduce_table() {return _reduce_table;}
+
+  /** Instance of action encapsulation class. */
+  protected CUP$Parser$actions action_obj;
+
+  /** Action encapsulation object initializer. */
+  protected void init_actions()
+    {
+      action_obj = new CUP$Parser$actions(this);
+    }
+
+  /** Invoke a user supplied parse action. */
+  public java_cup.runtime.Symbol do_action(
+    int                        act_num,
+    java_cup.runtime.lr_parser parser,
+    java.util.Stack            stack,
+    int                        top)
+    throws java.lang.Exception
+  {
+    /* call code in generated class */
+    return action_obj.CUP$Parser$do_action(act_num, parser, stack, top);
+  }
+
+  /** Indicates start state. */
+  public int start_state() {return 0;}
+  /** Indicates start production. */
+  public int start_production() {return 1;}
+
+  /** <code>EOF</code> Symbol index. */
+  public int EOF_sym() {return 0;}
+
+  /** <code>error</code> Symbol index. */
+  public int error_sym() {return 1;}
+
+
+  /** User initialization code. */
+  public void user_init() throws java.lang.Exception
+    {
+
+  m_Symbols = new HashMap();
+  m_Result  = new JSONNode();
+  m_Stack   = new Stack<JSONNode>();
+  m_Stack.push(m_Result);
+
+    }
+
+
+  /** variable - value relation. */
+  protected HashMap m_Symbols;
+
+  /** for storing the parsed JSON data structure. */
+  protected JSONNode m_Result;
+  
+  /** the stack for keeping track of the current parent node. */
+  protected Stack<JSONNode> m_Stack;
+
+  /**
+   * Returns the JSON data structure.
+   * 
+   * @return the result
+   */
+  public JSONNode getResult() {
+    return m_Result;
+  }
+  
+  /**
+   * Returns the stack used internally for keeping track of the current
+   * parent node.
+   * 
+   * @return the stack
+   */
+  protected Stack<JSONNode> getStack() {
+    return m_Stack;
+  }
+
+  /**
+   * Runs the parser from commandline. Expects a filename as first parameter,
+   * pointing to a JSON file.
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    if (args.length != 1) {
+      System.err.println("No JSON file specified!");
+      System.exit(1);
+    }
+    
+    FileInputStream stream = new FileInputStream(args[0]);
+    SymbolFactory sf = new DefaultSymbolFactory();
+    Parser parser = new Parser(new Scanner(stream, sf), sf);
+    parser.parse();
+    StringBuffer buffer = new StringBuffer();
+    parser.getResult().toString(buffer);
+    System.out.println(buffer.toString());
+  }
+
+}
+
+/** Cup generated class to encapsulate user supplied action code.*/
+class CUP$Parser$actions {
+  private final Parser parser;
+
+  /** Constructor */
+  CUP$Parser$actions(Parser parser) {
+    this.parser = parser;
+  }
+
+  /** Method with the actual generated action code. */
+  public final java_cup.runtime.Symbol CUP$Parser$do_action(
+    int                        CUP$Parser$act_num,
+    java_cup.runtime.lr_parser CUP$Parser$parser,
+    java.util.Stack            CUP$Parser$stack,
+    int                        CUP$Parser$top)
+    throws java.lang.Exception
+    {
+      /* Symbol object for return from actions */
+      java_cup.runtime.Symbol CUP$Parser$result;
+
+      /* select the action based on the action number */
+      switch (CUP$Parser$act_num)
+        {
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 42: // element ::= anon_array 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 41: // element ::= anon_object 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 40: // element ::= STRING 
+            {
+              Object RESULT =null;
+		int sleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int sright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		String s = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 
+                            parser.getStack().peek().addArrayElement(s);
+                         
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 39: // element ::= DOUBLE 
+            {
+              Object RESULT =null;
+		int dleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int dright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double d = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		
+                            parser.getStack().peek().addArrayElement(d);
+                         
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 38: // element ::= INTEGER 
+            {
+              Object RESULT =null;
+		int ileft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int iright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Integer i = (Integer)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		
+                            parser.getStack().peek().addArrayElement(i);
+                         
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 37: // element ::= BOOLEAN 
+            {
+              Object RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 
+                            parser.getStack().peek().addArrayElement(b);
+                         
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 36: // element ::= NULL 
+            {
+              Object RESULT =null;
+		 
+                            parser.getStack().peek().addArrayElement(null);
+                         
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("element",22, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 35: // elements ::= element 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("elements",21, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 34: // elements ::= elements COMMA element 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("elements",21, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 33: // array_end ::= RSQUARE 
+            {
+              Object RESULT =null;
+		
+                          parser.getStack().pop();
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("array_end",20, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 32: // array_content ::= elements 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("array_content",19, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 31: // anon_array_start ::= LSQUARE 
+            {
+              Object RESULT =null;
+		
+                          JSONNode node = parser.getStack().peek().addArray(null);
+                          parser.getStack().push(node);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_array_start",18, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 30: // anon_array ::= anon_array_start array_content array_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_array",15, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 29: // anon_array ::= anon_array_start array_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_array",15, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 28: // named_array_start ::= STRING COLON LSQUARE 
+            {
+              Object RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		
+                          JSONNode node = parser.getStack().peek().addArray(name);
+                          parser.getStack().push(node);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_array_start",17, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 27: // named_array ::= named_array_start array_content array_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_array",16, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 26: // named_array ::= named_array_start array_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_array",16, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 25: // object_end ::= RCURLY 
+            {
+              Object RESULT =null;
+		
+                          parser.getStack().pop();
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("object_end",14, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 24: // object_content ::= pairs 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("object_content",13, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 23: // anon_object_start ::= LCURLY 
+            {
+              Object RESULT =null;
+		
+                          JSONNode node = parser.getStack().peek().addObject(null);
+                          parser.getStack().push(node);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_object_start",12, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 22: // anon_object ::= anon_object_start object_content object_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_object",9, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 21: // anon_object ::= anon_object_start object_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("anon_object",9, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 20: // named_object_start ::= STRING COLON LCURLY 
+            {
+              Object RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		
+                          JSONNode node = parser.getStack().peek().addObject(name);
+                          parser.getStack().push(node);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_object_start",11, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 19: // named_object ::= named_object_start object_content object_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_object",10, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 18: // named_object ::= named_object_start object_end 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("named_object",10, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 17: // string ::= STRING COLON STRING 
+            {
+              String RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int sleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int sright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		String s = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 
+                          parser.getStack().peek().addPrimitive(name, s);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("string",8, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 16: // double ::= STRING COLON DOUBLE 
+            {
+              Double RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int dleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int dright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double d = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		
+                          parser.getStack().peek().addPrimitive(name, d);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("double",7, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 15: // integer ::= STRING COLON INTEGER 
+            {
+              Integer RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int ileft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int iright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Integer i = (Integer)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		
+                          parser.getStack().peek().addPrimitive(name, i);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("integer",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 14: // boolean ::= STRING COLON BOOLEAN 
+            {
+              Boolean RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		
+                          parser.getStack().peek().addPrimitive(name, b);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolean",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 13: // null ::= STRING COLON NULL 
+            {
+              Object RESULT =null;
+		int nameleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int nameright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String name = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		 
+                          parser.getStack().peek().addNull(name);
+                       
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("null",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 12: // primitive ::= string 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("primitive",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 11: // primitive ::= double 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("primitive",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 10: // primitive ::= integer 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("primitive",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 9: // primitive ::= boolean 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("primitive",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 8: // primitive ::= null 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("primitive",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 7: // pair ::= named_array 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("pair",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 6: // pair ::= named_object 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("pair",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 5: // pair ::= primitive 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("pair",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 4: // pairs ::= pair 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("pairs",1, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 3: // pairs ::= pairs COMMA pair 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("pairs",1, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 2: // json ::= LCURLY pairs RCURLY 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("json",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 1: // $START ::= json EOF 
+            {
+              Object RESULT =null;
+		int start_valleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int start_valright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Object start_val = (Object)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		RESULT = start_val;
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("$START",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          /* ACCEPT */
+          CUP$Parser$parser.done_parsing();
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 0: // json ::= LCURLY RCURLY 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("json",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /* . . . . . .*/
+          default:
+            throw new Exception(
+               "Invalid action number found in internal parse table");
+
+        }
+    }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/json/Scanner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/Scanner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/Scanner.java	(revision 29)
@@ -0,0 +1,655 @@
+/* The following code was generated by JFlex 1.4.2 on 22/07/09 9:31 PM */
+
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.json;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for JSON data files.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5786 $
+ */
+
+public class Scanner implements java_cup.runtime.Scanner {
+
+  /** This character denotes the end of file */
+  public static final int YYEOF = -1;
+
+  /** initial size of the lookahead buffer */
+  private static final int ZZ_BUFFERSIZE = 16384;
+
+  /** lexical states */
+  public static final int STRING = 2;
+  public static final int YYINITIAL = 0;
+
+  /**
+   * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+   * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+   *                  at the beginning of a line
+   * l is of the form l = 2*k, k a non negative integer
+   */
+  private static final int ZZ_LEXSTATE[] = { 
+     0,  0,  1, 1
+  };
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final char [] ZZ_CMAP = {
+     0,  0,  0,  0,  0,  0,  0,  0,  0, 20, 24,  0, 20, 22,  0,  0, 
+     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
+    20,  0, 19,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5, 18, 17,  0, 
+    16, 16, 16, 16, 16, 16, 16, 16, 16, 16,  6,  0,  0,  0,  0,  0, 
+     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
+     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3, 21,  4,  0,  0, 
+     0, 14, 23,  0,  0, 12, 13,  0,  0,  0,  0,  0,  9,  0,  7,  0, 
+     0,  0, 11, 15, 10,  8,  0,  0,  0,  0,  0,  1,  0,  2,  0,  0
+  };
+
+  /** 
+   * Translates DFA states to action switch labels.
+   */
+  private static final int [] ZZ_ACTION = zzUnpackAction();
+
+  private static final String ZZ_ACTION_PACKED_0 =
+    "\2\0\1\1\1\2\1\3\1\4\1\5\1\6\1\7"+
+    "\3\1\1\10\1\1\1\11\1\12\1\13\1\14\1\15"+
+    "\3\0\2\16\1\17\1\20\1\21\1\22\1\23\1\24"+
+    "\3\0\1\25\1\26";
+
+  private static int [] zzUnpackAction() {
+    int [] result = new int[35];
+    int offset = 0;
+    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAction(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /** 
+   * Translates a state to a row index in the transition table
+   */
+  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+  private static final String ZZ_ROWMAP_PACKED_0 =
+    "\0\0\0\31\0\62\0\62\0\62\0\62\0\62\0\62"+
+    "\0\62\0\113\0\144\0\175\0\226\0\257\0\62\0\62"+
+    "\0\310\0\62\0\341\0\372\0\u0113\0\u012c\0\u0145\0\u015e"+
+    "\0\62\0\62\0\62\0\62\0\62\0\62\0\u0177\0\u0190"+
+    "\0\u01a9\0\62\0\62";
+
+  private static int [] zzUnpackRowMap() {
+    int [] result = new int[35];
+    int offset = 0;
+    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+    int i = 0;  /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int high = packed.charAt(i++) << 16;
+      result[j++] = high | packed.charAt(i++);
+    }
+    return j;
+  }
+
+  /** 
+   * The transition table of the DFA
+   */
+  private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+  private static final String ZZ_TRANS_PACKED_0 =
+    "\1\3\1\4\1\5\1\6\1\7\1\10\1\11\1\12"+
+    "\2\3\1\13\2\3\1\14\2\3\1\15\1\3\1\16"+
+    "\1\17\1\20\1\3\1\20\1\3\1\20\23\21\1\22"+
+    "\1\21\1\23\1\3\1\21\42\0\1\24\33\0\1\25"+
+    "\33\0\1\26\32\0\1\15\1\27\27\0\1\30\10\0"+
+    "\23\21\1\0\1\21\2\0\1\21\10\0\1\31\2\0"+
+    "\1\32\1\33\1\0\1\34\5\0\1\35\3\0\1\36"+
+    "\12\0\1\37\27\0\1\40\31\0\1\41\37\0\1\27"+
+    "\30\0\1\30\1\27\20\0\1\42\33\0\1\43\33\0"+
+    "\1\40\11\0";
+
+  private static int [] zzUnpackTrans() {
+    int [] result = new int[450];
+    int offset = 0;
+    offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackTrans(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      value--;
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /* error codes */
+  private static final int ZZ_UNKNOWN_ERROR = 0;
+  private static final int ZZ_NO_MATCH = 1;
+  private static final int ZZ_PUSHBACK_2BIG = 2;
+
+  /* error messages for the codes above */
+  private static final String ZZ_ERROR_MSG[] = {
+    "Unkown internal scanner error",
+    "Error: could not match input",
+    "Error: pushback value was too large"
+  };
+
+  /**
+   * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+   */
+  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+  private static final String ZZ_ATTRIBUTE_PACKED_0 =
+    "\2\0\7\11\5\1\2\11\1\1\1\11\1\1\3\0"+
+    "\2\1\6\11\3\0\2\11";
+
+  private static int [] zzUnpackAttribute() {
+    int [] result = new int[35];
+    int offset = 0;
+    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+  /** the input device */
+  private java.io.Reader zzReader;
+
+  /** the current state of the DFA */
+  private int zzState;
+
+  /** the current lexical state */
+  private int zzLexicalState = YYINITIAL;
+
+  /** this buffer contains the current text to be matched and is
+      the source of the yytext() string */
+  private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
+
+  /** the textposition at the last accepting state */
+  private int zzMarkedPos;
+
+  /** the current text position in the buffer */
+  private int zzCurrentPos;
+
+  /** startRead marks the beginning of the yytext() string in the buffer */
+  private int zzStartRead;
+
+  /** endRead marks the last character in the buffer, that has been read
+      from input */
+  private int zzEndRead;
+
+  /** number of newlines encountered up to the start of the matched text */
+  private int yyline;
+
+  /** the number of characters up to the start of the matched text */
+  private int yychar;
+
+  /**
+   * the number of characters from the last newline up to the start of the 
+   * matched text
+   */
+  private int yycolumn;
+
+  /** 
+   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+   */
+  private boolean zzAtBOL = true;
+
+  /** zzAtEOF == true <=> the scanner is at the EOF */
+  private boolean zzAtEOF;
+
+  /* user code: */
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 5786 $
+  protected SymbolFactory m_SF;
+
+  protected StringBuffer m_String = new StringBuffer();
+
+  public Scanner(InputStream r, SymbolFactory sf) {
+    this(r);
+    m_SF = sf;
+  }
+
+  public Scanner(Reader r, SymbolFactory sf) {
+    this(r);
+    m_SF = sf;
+  }
+
+
+  /**
+   * Creates a new scanner
+   * There is also a java.io.InputStream version of this constructor.
+   *
+   * @param   in  the java.io.Reader to read input from.
+   */
+  public Scanner(java.io.Reader in) {
+    this.zzReader = in;
+  }
+
+  /**
+   * Creates a new scanner.
+   * There is also java.io.Reader version of this constructor.
+   *
+   * @param   in  the java.io.Inputstream to read input from.
+   */
+  public Scanner(java.io.InputStream in) {
+    this(new java.io.InputStreamReader(in));
+  }
+
+
+  /**
+   * Refills the input buffer.
+   *
+   * @return      <code>false</code>, iff there was new input.
+   * 
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  private boolean zzRefill() throws java.io.IOException {
+
+    /* first: make room (if you can) */
+    if (zzStartRead > 0) {
+      System.arraycopy(zzBuffer, zzStartRead,
+                       zzBuffer, 0,
+                       zzEndRead-zzStartRead);
+
+      /* translate stored positions */
+      zzEndRead-= zzStartRead;
+      zzCurrentPos-= zzStartRead;
+      zzMarkedPos-= zzStartRead;
+      zzStartRead = 0;
+    }
+
+    /* is the buffer big enough? */
+    if (zzCurrentPos >= zzBuffer.length) {
+      /* if not: blow it up */
+      char newBuffer[] = new char[zzCurrentPos*2];
+      System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
+      zzBuffer = newBuffer;
+    }
+
+    /* finally: fill the buffer with new input */
+    int numRead = zzReader.read(zzBuffer, zzEndRead,
+                                            zzBuffer.length-zzEndRead);
+
+    if (numRead > 0) {
+      zzEndRead+= numRead;
+      return false;
+    }
+    // unlikely but not impossible: read 0 characters, but not at end of stream    
+    if (numRead == 0) {
+      int c = zzReader.read();
+      if (c == -1) {
+        return true;
+      } else {
+        zzBuffer[zzEndRead++] = (char) c;
+        return false;
+      }     
+    }
+
+	// numRead < 0
+    return true;
+  }
+
+    
+  /**
+   * Closes the input stream.
+   */
+  public final void yyclose() throws java.io.IOException {
+    zzAtEOF = true;            /* indicate end of file */
+    zzEndRead = zzStartRead;  /* invalidate buffer    */
+
+    if (zzReader != null)
+      zzReader.close();
+  }
+
+
+  /**
+   * Resets the scanner to read from a new input stream.
+   * Does not close the old reader.
+   *
+   * All internal variables are reset, the old input stream 
+   * <b>cannot</b> be reused (internal buffer is discarded and lost).
+   * Lexical state is set to <tt>ZZ_INITIAL</tt>.
+   *
+   * @param reader   the new input stream 
+   */
+  public final void yyreset(java.io.Reader reader) {
+    zzReader = reader;
+    zzAtBOL  = true;
+    zzAtEOF  = false;
+    zzEndRead = zzStartRead = 0;
+    zzCurrentPos = zzMarkedPos = 0;
+    yyline = yychar = yycolumn = 0;
+    zzLexicalState = YYINITIAL;
+  }
+
+
+  /**
+   * Returns the current lexical state.
+   */
+  public final int yystate() {
+    return zzLexicalState;
+  }
+
+
+  /**
+   * Enters a new lexical state
+   *
+   * @param newState the new lexical state
+   */
+  public final void yybegin(int newState) {
+    zzLexicalState = newState;
+  }
+
+
+  /**
+   * Returns the text matched by the current regular expression.
+   */
+  public final String yytext() {
+    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+  }
+
+
+  /**
+   * Returns the character at position <tt>pos</tt> from the 
+   * matched text. 
+   * 
+   * It is equivalent to yytext().charAt(pos), but faster
+   *
+   * @param pos the position of the character to fetch. 
+   *            A value from 0 to yylength()-1.
+   *
+   * @return the character at position pos
+   */
+  public final char yycharat(int pos) {
+    return zzBuffer[zzStartRead+pos];
+  }
+
+
+  /**
+   * Returns the length of the matched text region.
+   */
+  public final int yylength() {
+    return zzMarkedPos-zzStartRead;
+  }
+
+
+  /**
+   * Reports an error that occured while scanning.
+   *
+   * In a wellformed scanner (no or only correct usage of 
+   * yypushback(int) and a match-all fallback rule) this method 
+   * will only be called with things that "Can't Possibly Happen".
+   * If this method is called, something is seriously wrong
+   * (e.g. a JFlex bug producing a faulty scanner etc.).
+   *
+   * Usual syntax/scanner level error handling should be done
+   * in error fallback rules.
+   *
+   * @param   errorCode  the code of the errormessage to display
+   */
+  private void zzScanError(int errorCode) {
+    String message;
+    try {
+      message = ZZ_ERROR_MSG[errorCode];
+    }
+    catch (ArrayIndexOutOfBoundsException e) {
+      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+    }
+
+    throw new Error(message);
+  } 
+
+
+  /**
+   * Pushes the specified amount of characters back into the input stream.
+   *
+   * They will be read again by then next call of the scanning method
+   *
+   * @param number  the number of characters to be read again.
+   *                This number must not be greater than yylength()!
+   */
+  public void yypushback(int number)  {
+    if ( number > yylength() )
+      zzScanError(ZZ_PUSHBACK_2BIG);
+
+    zzMarkedPos -= number;
+  }
+
+
+  /**
+   * Resumes scanning until the next regular expression is matched,
+   * the end of input is encountered or an I/O-Error occurs.
+   *
+   * @return      the next token
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  public java_cup.runtime.Symbol next_token() throws java.io.IOException {
+    int zzInput;
+    int zzAction;
+
+    // cached fields:
+    int zzCurrentPosL;
+    int zzMarkedPosL;
+    int zzEndReadL = zzEndRead;
+    char [] zzBufferL = zzBuffer;
+    char [] zzCMapL = ZZ_CMAP;
+
+    int [] zzTransL = ZZ_TRANS;
+    int [] zzRowMapL = ZZ_ROWMAP;
+    int [] zzAttrL = ZZ_ATTRIBUTE;
+
+    while (true) {
+      zzMarkedPosL = zzMarkedPos;
+
+      zzAction = -1;
+
+      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+  
+      zzState = ZZ_LEXSTATE[zzLexicalState];
+
+
+      zzForAction: {
+        while (true) {
+    
+          if (zzCurrentPosL < zzEndReadL)
+            zzInput = zzBufferL[zzCurrentPosL++];
+          else if (zzAtEOF) {
+            zzInput = YYEOF;
+            break zzForAction;
+          }
+          else {
+            // store back cached positions
+            zzCurrentPos  = zzCurrentPosL;
+            zzMarkedPos   = zzMarkedPosL;
+            boolean eof = zzRefill();
+            // get translated positions and possibly new buffer
+            zzCurrentPosL  = zzCurrentPos;
+            zzMarkedPosL   = zzMarkedPos;
+            zzBufferL      = zzBuffer;
+            zzEndReadL     = zzEndRead;
+            if (eof) {
+              zzInput = YYEOF;
+              break zzForAction;
+            }
+            else {
+              zzInput = zzBufferL[zzCurrentPosL++];
+            }
+          }
+          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+          if (zzNext == -1) break zzForAction;
+          zzState = zzNext;
+
+          int zzAttributes = zzAttrL[zzState];
+          if ( (zzAttributes & 1) == 1 ) {
+            zzAction = zzState;
+            zzMarkedPosL = zzCurrentPosL;
+            if ( (zzAttributes & 8) == 8 ) break zzForAction;
+          }
+
+        }
+      }
+
+      // store back cached position
+      zzMarkedPos = zzMarkedPosL;
+
+      switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+        case 20: 
+          { m_String.append('\b');
+          }
+        case 23: break;
+        case 7: 
+          { return m_SF.newSymbol("Colon", sym.COLON);
+          }
+        case 24: break;
+        case 13: 
+          { m_String.append('\\');
+          }
+        case 25: break;
+        case 22: 
+          { return m_SF.newSymbol("Boolean", sym.BOOLEAN, new Boolean(yytext()));
+          }
+        case 26: break;
+        case 14: 
+          { return m_SF.newSymbol("Double", sym.DOUBLE, new Double(yytext()));
+          }
+        case 27: break;
+        case 17: 
+          { m_String.append('\r');
+          }
+        case 28: break;
+        case 3: 
+          { return m_SF.newSymbol("Right curly bracket", sym.RCURLY);
+          }
+        case 29: break;
+        case 19: 
+          { m_String.append('\"');
+          }
+        case 30: break;
+        case 1: 
+          { System.err.println("Illegal character: " + yytext());
+          }
+        case 31: break;
+        case 18: 
+          { m_String.append('\f');
+          }
+        case 32: break;
+        case 21: 
+          { return m_SF.newSymbol("Null", sym.NULL);
+          }
+        case 33: break;
+        case 16: 
+          { m_String.append('\t');
+          }
+        case 34: break;
+        case 4: 
+          { return m_SF.newSymbol("Left square bracket", sym.LSQUARE);
+          }
+        case 35: break;
+        case 12: 
+          { yybegin(YYINITIAL); return m_SF.newSymbol("String", sym.STRING, m_String.toString());
+          }
+        case 36: break;
+        case 15: 
+          { m_String.append('\n');
+          }
+        case 37: break;
+        case 2: 
+          { return m_SF.newSymbol("Left curly bracket", sym.LCURLY);
+          }
+        case 38: break;
+        case 6: 
+          { return m_SF.newSymbol("Comma", sym.COMMA);
+          }
+        case 39: break;
+        case 8: 
+          { return m_SF.newSymbol("Integer", sym.INTEGER, new Integer(yytext()));
+          }
+        case 40: break;
+        case 9: 
+          { m_String.setLength(0); yybegin(STRING);
+          }
+        case 41: break;
+        case 11: 
+          { m_String.append(yytext());
+          }
+        case 42: break;
+        case 10: 
+          { /* ignore white space. */
+          }
+        case 43: break;
+        case 5: 
+          { return m_SF.newSymbol("Right square bracket", sym.RSQUARE);
+          }
+        case 44: break;
+        default: 
+          if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+            zzAtEOF = true;
+              {     return m_SF.newSymbol("EOF", sym.EOF);
+ }
+          } 
+          else {
+            zzScanError(ZZ_NO_MATCH);
+          }
+      }
+    }
+  }
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/json/Scanner.jflex
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/Scanner.jflex	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/Scanner.jflex	(revision 29)
@@ -0,0 +1,92 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.json;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for JSON data files.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5786 $
+ */
+%%
+%cup
+%public
+%class Scanner
+%{
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 5786 $
+  protected SymbolFactory m_SF;
+
+  protected StringBuffer m_String = new StringBuffer();
+
+  public Scanner(InputStream r, SymbolFactory sf) {
+    this(r);
+    m_SF = sf;
+  }
+
+  public Scanner(Reader r, SymbolFactory sf) {
+    this(r);
+    m_SF = sf;
+  }
+%}
+%eofval{
+    return m_SF.newSymbol("EOF", sym.EOF);
+%eofval}
+
+%state STRING
+
+%%
+<YYINITIAL> "{"                  { return m_SF.newSymbol("Left curly bracket", sym.LCURLY); }
+<YYINITIAL> "}"                  { return m_SF.newSymbol("Right curly bracket", sym.RCURLY); }
+
+<YYINITIAL> {
+  "["                            { return m_SF.newSymbol("Left square bracket", sym.LSQUARE); }
+  "]"                            { return m_SF.newSymbol("Right square bracket", sym.RSQUARE); }
+  ","                            { return m_SF.newSymbol("Comma", sym.COMMA); }
+  ":"                            { return m_SF.newSymbol("Colon", sym.COLON); }
+  "null"                         { return m_SF.newSymbol("Null", sym.NULL); }
+  "true"                         { return m_SF.newSymbol("Boolean", sym.BOOLEAN, new Boolean(yytext())); }
+  "false"                        { return m_SF.newSymbol("Boolean", sym.BOOLEAN, new Boolean(yytext())); }
+  [0-9][0-9]*                    { return m_SF.newSymbol("Integer", sym.INTEGER, new Integer(yytext())); }
+  [0-9][0-9]*\.?[0-9]*           { return m_SF.newSymbol("Double", sym.DOUBLE, new Double(yytext())); }
+  -[0-9][0-9]*\.?[0-9]*          { return m_SF.newSymbol("Double", sym.DOUBLE, new Double(yytext())); }
+  \"                             { m_String.setLength(0); yybegin(STRING); }
+  [ \r\n\t\f]                    { /* ignore white space. */ }
+}
+
+<STRING> {
+  \"                             { yybegin(YYINITIAL); return m_SF.newSymbol("String", sym.STRING, m_String.toString()); }
+  [^\n\r\"\\]+                   { m_String.append(yytext()); }
+  \\\"                           { m_String.append('\"'); }
+  \\b                            { m_String.append('\b'); }
+  \\f                            { m_String.append('\f'); }
+  \\n                            { m_String.append('\n'); }
+  \\r                            { m_String.append('\r'); }
+  \\t                            { m_String.append('\t'); }
+  \\                             { m_String.append('\\'); }
+}
+
+// catch all
+.                                { System.err.println("Illegal character: " + yytext()); }
Index: branches/MetisMQI/src/main/java/weka/core/json/sym.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/json/sym.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/json/sym.java	(revision 29)
@@ -0,0 +1,26 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Wed Jul 22 21:31:13 NZST 2009
+//----------------------------------------------------
+
+package weka.core.json;
+
+/** CUP generated interface containing symbol constants. */
+public interface sym {
+  /* terminals */
+  public static final int LSQUARE = 3;
+  public static final int INTEGER = 10;
+  public static final int COLON = 7;
+  public static final int BOOLEAN = 9;
+  public static final int NULL = 8;
+  public static final int RSQUARE = 4;
+  public static final int STRING = 12;
+  public static final int EOF = 0;
+  public static final int DOUBLE = 11;
+  public static final int error = 1;
+  public static final int COMMA = 2;
+  public static final int RCURLY = 6;
+  public static final int LCURLY = 5;
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/logging/ConsoleLogger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/logging/ConsoleLogger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/logging/ConsoleLogger.java	(revision 29)
@@ -0,0 +1,60 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ConsoleLogger.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.logging;
+
+import weka.core.RevisionUtils;
+
+import java.util.Date;
+
+/**
+ * A simple logger that outputs the logging information in the console.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4716 $
+ */
+public class ConsoleLogger
+  extends Logger {
+
+  /**
+   * Performs the actual logging. 
+   * 
+   * @param level	the level of the message
+   * @param msg		the message to log
+   * @param cls		the classname originating the log event
+   * @param method	the method originating the log event
+   * @param lineno	the line number originating the log event
+   */
+  protected void doLog(Level level, String msg, String cls, String method, int lineno) {
+    System.err.println(
+	m_DateFormat.format(new Date()) + " " + cls + " " + method + "\n" 
+	+ level + ": " + msg);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4716 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/logging/FileLogger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/logging/FileLogger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/logging/FileLogger.java	(revision 29)
@@ -0,0 +1,138 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * FileLogger.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.logging;
+
+import weka.core.RevisionUtils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Date;
+
+/**
+ * A simple file logger, that just logs to a single file. Deletes the file
+ * when an object gets instantiated.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5219 $
+ */
+public class FileLogger
+  extends ConsoleLogger {
+
+  /** the log file. */
+  protected File m_LogFile;
+  
+  /** the line feed. */
+  protected String m_LineFeed;
+  
+  /**
+   * Initializes the logger.
+   */
+  protected void initialize() {
+    super.initialize();
+
+    // log file
+    m_LogFile = getLogFile();
+    // try to remove file
+    try {
+      if ((m_LogFile != null) && m_LogFile.exists())
+	m_LogFile.delete();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    // the line feed
+    m_LineFeed = System.getProperty("line.separator");
+  }
+  
+  /**
+   * Returns the log file to use.
+   * 
+   * @return		the log file
+   */
+  protected File getLogFile() {
+    String	filename;
+    File	result;
+    
+    filename = m_Properties.getProperty("LogFile", "%h/weka.log");
+    filename = filename.replaceAll("%t", System.getProperty("java.io.tmpdir"));
+    filename = filename.replaceAll("%h", System.getProperty("user.home"));
+    filename = filename.replaceAll("%c", System.getProperty("user.dir"));
+    filename = filename.replaceAll("%%", System.getProperty("%"));
+    
+    result = new File(filename);
+    
+    return result;
+  }
+  
+  /**
+   * Appends the given string to the log file (without new line!).
+   * 
+   * @param s		the string to append
+   */
+  protected void append(String s) {
+    BufferedWriter	writer;
+   
+    if (m_LogFile == null)
+      return;
+    
+    // append output to file
+    try {
+      writer = new BufferedWriter(new FileWriter(m_LogFile, true));
+      writer.write(s);
+      writer.flush();
+      writer.close();
+    }
+    catch (Exception e) {
+      // ignored
+    }
+  }
+
+  /**
+   * Performs the actual logging. 
+   * 
+   * @param level	the level of the message
+   * @param msg		the message to log
+   * @param cls		the classname originating the log event
+   * @param method	the method originating the log event
+   * @param lineno	the line number originating the log event
+   */
+  protected void doLog(Level level, String msg, String cls, String method, int lineno) {
+    // output to console
+    super.doLog(level, msg, cls, method, lineno);
+    
+    // append output to file
+    append(
+	m_DateFormat.format(new Date()) + " " + cls + " " + method + m_LineFeed
+	+ level + ": " + msg + m_LineFeed);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5219 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/logging/Logger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/logging/Logger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/logging/Logger.java	(revision 29)
@@ -0,0 +1,257 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Logger.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.logging;
+
+import weka.core.RevisionHandler;
+import weka.core.Utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Properties;
+
+/**
+ * Abstract superclass for all loggers.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4716 $
+ */
+public abstract class Logger
+  implements RevisionHandler {
+
+  /** the properties file. */
+  public final static String PROPERTIES_FILE = "weka/core/logging/Logging.props";
+  
+  /**
+   * The logging level.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4716 $
+   */
+  public enum Level {
+    /** logs all messages. */
+    ALL(0),
+    /** FINEST level. */
+    FINEST(1),
+    /** FINEST level. */
+    FINER(2),
+    /** FINER level. */
+    FINE(3),
+    /** FINE level. */
+    INFO(4),
+    /** WARNING level. */
+    WARNING(5),
+    /** SEVERE level. */
+    SEVERE(6),
+    /** turns logging off. */
+    OFF(10);
+    
+    /** the order of the level. */
+    private int m_Order;
+    
+    /**
+     * Initializes the level.
+     * 
+     * @param order	the order of the level
+     */
+    private Level(int order) {
+      m_Order = order;
+    }
+    
+    /**
+     * Returns the order of this level.
+     * 
+     * @return		the order
+     */
+    public int getOrder() {
+      return m_Order;
+    }
+  }
+  
+  /** the minimum level of log events to have in order to end up in the log. */
+  protected Level m_MinLevel;
+
+  /** the singleton instance of the logger. */
+  protected static Logger m_Singleton;
+  
+  /** the properties file. */
+  protected static Properties m_Properties;
+
+  /** for formatting the dates. */
+  protected static SimpleDateFormat m_DateFormat;
+  
+  static {
+    try {
+      m_Properties = Utils.readProperties(PROPERTIES_FILE);
+    }
+    catch (Exception e) {
+      System.err.println(
+	  "Error reading the logging properties '" + PROPERTIES_FILE + "': " + e);
+      m_Properties = new Properties();
+    }
+  }
+  
+  /**
+   * Initializes the logger.
+   */
+  public Logger() {
+    super();
+    
+    initialize();
+  }
+  
+  /**
+   * Initializes the logger.
+   */
+  protected void initialize() {
+    m_MinLevel = Level.valueOf(m_Properties.getProperty("MinLevel", "INFO"));
+  }
+  
+  /**
+   * Returns the minimum level log messages must have in order to appear in
+   * the log.
+   * 
+   * @return		the level
+   */
+  public Level getMinLevel() {
+    return m_MinLevel;
+  }
+  
+  /**
+   * Returns the location the logging happened.
+   * 
+   * @return		the classname (= [0]), the method (= [1]) and the
+   * 			line number (= [2]) that generated the logging event
+   */
+  protected static String[] getLocation() {
+    String[]		result;
+    Throwable 		t;
+    StackTraceElement[]	trace;
+    int			i;
+    
+    result = new String[3];
+    
+    t = new Throwable();
+    t.fillInStackTrace();
+    trace = t.getStackTrace();
+
+    for (i = 0; i < trace.length; i++) {
+      // skip the Logger class
+      if (trace[i].getClassName().equals(Logger.class.getName()))
+	continue;
+      
+      if (trace[i].getClassName().equals(weka.gui.LogPanel.class.getName()))
+        continue;
+      
+      // fill in result
+      result[0] = trace[i].getClassName();
+      result[1] = trace[i].getMethodName();
+      result[2] = "" + trace[i].getLineNumber();
+      break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Performs the actual logging. 
+   * Actual logger implementations must override this method.
+   * 
+   * @param level	the level of the message
+   * @param msg		the message to log
+   * @param cls		the classname originating the log event
+   * @param method	the method originating the log event
+   * @param lineno	the line number originating the log event
+   */
+  protected abstract void doLog(Level level, String msg, String cls, String method, int lineno);
+  
+  /**
+   * Returns the singleton instance of the logger.
+   * 
+   * @return		the logger instance
+   */
+  public static Logger getSingleton() {
+    String	classname;
+    
+    if (m_Singleton == null) {
+      // logger
+      classname = m_Properties.getProperty("Logger", ConsoleLogger.class.getName());
+      try {
+	m_Singleton = (Logger) Class.forName(classname).newInstance();
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+      
+      // date format
+      m_DateFormat = new SimpleDateFormat(m_Properties.getProperty("DateFormat", "yyyy-MM-dd HH:mm:ss"));
+    }
+    
+    return m_Singleton;
+  }
+  
+  /**
+   * Logs the given message under the given level.
+   * 
+   * @param level	the level of the message
+   * @param msg		the message to log
+   */
+  public static void log(Level level, String msg) {
+    Logger	logger;
+    boolean	log;
+    String[]	location;
+    
+    logger = getSingleton();
+    if (logger == null)
+      return;
+    
+    synchronized(logger) {
+      log = false;
+      if (logger.getMinLevel() == Level.ALL)
+	log = true;
+      else if (level.getOrder() >= logger.getMinLevel().getOrder())
+	log = true;
+      if (!log)
+	return;
+      location = getLocation();
+      logger.doLog(level, msg, location[0], location[1], Integer.parseInt(location[2]));
+    }
+  }
+  
+  /**
+   * Logs the given message under the given level.
+   * 
+   * @param level	the level of the message
+   * @param t		the throwable to log
+   */
+  public static void log(Level level, Throwable t) {
+    StringWriter	swriter;
+    PrintWriter		pwriter;
+    
+    swriter = new StringWriter();
+    pwriter = new PrintWriter(swriter);
+    t.printStackTrace(pwriter);
+    pwriter.close();
+    
+    log(level, swriter.toString());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/logging/Logging.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/logging/Logging.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/logging/Logging.props	(revision 29)
@@ -0,0 +1,32 @@
+# Defines the logging used in Weka
+#
+# Author: FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 4716 $
+
+# the logging class to use
+Logger=weka.core.logging.OutputLogger
+#Logger=weka.core.logging.ConsoleLogger
+#Logger=weka.core.logging.FileLogger
+
+# the logging level that log message must have at least in order to appear in
+# the log. Possible values:
+#   ALL
+#   FINEST
+#   FINER
+#   FINE
+#   INFO
+#   WARNING
+#   SEVERE
+#   OFF
+MinLevel=INFO
+
+# the date format
+DateFormat=yyyy-MM-dd HH:mm:ss
+
+# the output file for the weka.core.logging.FileLogger
+# the following placeholders are recognized
+#   %t - the temp directory
+#   %h - the user's home directory
+#   %c - the current directory
+#   %% - gets replaced by a single percentage sign
+LogFile=%h/weka.log
Index: branches/MetisMQI/src/main/java/weka/core/logging/OutputLogger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/logging/OutputLogger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/logging/OutputLogger.java	(revision 29)
@@ -0,0 +1,213 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * OutputLogger.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.logging;
+
+import weka.core.RevisionUtils;
+import weka.core.Tee;
+
+import java.io.PrintStream;
+import java.util.Date;
+
+/**
+ * A logger that logs all output on stdout and stderr to a file.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4716 $
+ */
+public class OutputLogger
+  extends FileLogger {
+
+  /**
+   * A print stream class to capture all data from stdout and stderr.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4716 $
+   */
+  public static class OutputPrintStream
+    extends PrintStream {
+    
+    /** the owning logger. */
+    protected OutputLogger m_Owner;
+    
+    /** the line feed. */
+    protected String m_LineFeed;
+    
+    /**
+     * Default constructor.
+     * 
+     * @param owner		the owning logger
+     * @param stream		the stream
+     * @throws Exception	if something goes wrong
+     */
+    public OutputPrintStream(OutputLogger owner, PrintStream stream) throws Exception {
+      super(stream);
+      
+      m_Owner    = owner;
+      m_LineFeed = System.getProperty("line.separator");
+    }
+
+    /**
+     * ignored.
+     */
+    public void flush() {
+    }
+
+    /**
+     * prints the given int to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void print(int x) {
+      m_Owner.append("" + x);
+    }
+
+    /**
+     * prints the given boolean to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void print(boolean x) {
+      m_Owner.append("" + x);
+    }
+
+    /**
+     * prints the given string to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void print(String x) {
+      m_Owner.append("" + x);
+    }
+
+    /**
+     * prints the given object to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void print(Object x) {
+      m_Owner.append("" + x);
+    }
+
+    /**
+     * prints a new line to the streams.
+     */
+    public void println() {
+      m_Owner.append(m_LineFeed);
+    }
+
+    /**
+     * prints the given int to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void println(int x) {
+      m_Owner.append(x + m_LineFeed);
+    }
+
+    /**
+     * prints the given boolean to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void println(boolean x) {
+      m_Owner.append(x + m_LineFeed);
+    }
+
+    /**
+     * prints the given string to the streams.
+     * 
+     * @param x 	the object to print
+     */
+    public void println(String x) {
+      m_Owner.append(x + m_LineFeed);
+    }
+
+    /**
+     * prints the given object to the streams (for Throwables we print the stack
+     * trace).
+     * 
+     * @param x 	the object to print
+     */
+    public void println(Object x) {
+      m_Owner.append(x + m_LineFeed);
+    }
+  }
+  
+  /** the stream object used for logging stdout. */
+  protected OutputPrintStream m_StreamOut;
+  
+  /** the stream object used for logging stderr. */
+  protected OutputPrintStream m_StreamErr;
+
+  /** the Tee instance to redirect stdout. */
+  protected Tee m_StdOut;
+
+  /** the Tee instance to redirect stderr. */
+  protected Tee m_StdErr;
+  
+  /**
+   * Initializes the logger.
+   */
+  protected void initialize() {
+    super.initialize();
+    
+    try {
+      m_StdOut = new Tee(System.out);
+      System.setOut(m_StdOut);
+      m_StreamOut = new OutputPrintStream(this, m_StdOut.getDefault());
+      m_StdOut.add(m_StreamOut);
+      
+      m_StdErr = new Tee(System.err);
+      System.setErr(m_StdErr);
+      m_StreamErr = new OutputPrintStream(this, m_StdErr.getDefault());
+      m_StdErr.add(m_StreamErr);
+    }
+    catch (Exception e) {
+      // ignored
+    }
+  }
+  
+  /**
+   * Performs the actual logging. 
+   * 
+   * @param level	the level of the message
+   * @param msg		the message to log
+   * @param cls		the classname originating the log event
+   * @param method	the method originating the log event
+   * @param lineno	the line number originating the log event
+   */
+  protected void doLog(Level level, String msg, String cls, String method, int lineno) {
+    // append output to file
+    append(
+	m_DateFormat.format(new Date()) + " " + cls + " " + method + m_LineFeed
+	+ level + ": " + msg + m_LineFeed);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4716 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.cup
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.cup	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.cup	(revision 29)
@@ -0,0 +1,261 @@
+/*
+ * STANDARD ML OF NEW JERSEY COPYRIGHT NOTICE, LICENSE AND DISCLAIMER.
+ * 
+ * Copyright (c) 1989-1998 by Lucent Technologies
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both the
+ * copyright notice and this permission notice and warranty disclaimer appear
+ * in supporting documentation, and that the name of Lucent Technologies, Bell
+ * Labs or any Lucent entity not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior permission.
+ *
+ * Lucent disclaims all warranties with regard to this software, including all
+ * implied warranties of merchantability and fitness. In no event shall Lucent
+ * be liable for any special, indirect or consequential damages or any damages
+ * whatsoever resulting from loss of use, data or profits, whether in an action
+ * of contract, negligence or other tortious action, arising out of or in
+ * connection with the use or performance of this software. 
+ *
+ * Taken from this URL:
+ * http://www.smlnj.org/license.html
+ * 
+ * This license is compatible with the GNU GPL (see section "Standard ML of New
+ * Jersey Copyright License"):
+ * http://www.gnu.org/licenses/license-list.html#StandardMLofNJ
+ */
+
+/*
+ * Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian
+ */
+
+package weka.core.mathematicalexpression;
+
+import java_cup.runtime.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A parser for parsing mathematical expressions.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+
+parser code {:
+  /** variable - value relation. */
+  protected HashMap m_Symbols = new HashMap();
+
+  /** for storing the result of the expresion. */
+  protected Double m_Result = null;
+
+  /**
+   * Sets the variable - value relation to use.
+   * 
+   * @param value the variable-value relation
+   */
+  public void setSymbols(HashMap value) {
+    m_Symbols = value;
+  }
+
+  /**
+   * Returns the current variable - value relation in use.
+   * 
+   * @return the variable-value relation
+   */
+  public HashMap getSymbols() {
+    return m_Symbols;
+  }
+
+  /**
+   * Sets the result of the evaluation.
+   * 
+   * @param value the result
+   */
+  public void setResult(Double value) {
+    m_Result = value;
+  }
+
+  /**
+   * Returns the result of the evaluation.
+   * 
+   * @return the result
+   */
+  public Double getResult() {
+    return m_Result;
+  }
+
+  /**
+   * Runs the parser from commandline. Either reads lines from System.in
+   * or from a provided file (line by line). With 
+   * <code>-symbols <semi-colon separated list of variable/value pairs></code>
+   * as first parameter one can provide predefined variable values. E.g.:
+   * <code>-symbols "Y=10;X=3" "X+Y"</code>
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    // read symbols, if present
+    HashMap symbols = new HashMap();
+    if (args.length > 0) {
+      if (args[0].equals("-symbols")) {
+        // parse symbols
+        String[] pairs = args[1].replaceAll(" ", "").split(";");
+        for (int i = 0; i < pairs.length; i++) {
+          String[] parts = pairs[i].split("=");
+          symbols.put(parts[0], new Double(parts[1]));
+        }
+        // print symbols
+        System.out.println("\nSymbols provided:");
+        Iterator iter = symbols.keySet().iterator();
+        while (iter.hasNext()) {
+          String key = (String) iter.next();
+          System.out.println(key + "=" + symbols.get(key));
+        }
+        // remove symbols from commandline
+        String[] argsNew = new String[args.length - 2];
+        System.arraycopy(args, 2, argsNew, 0, argsNew.length);
+        args = argsNew;
+      }
+    }
+
+    // setup input stream
+    int index = -1;
+    if (args.length == 1)
+      index = 0;
+    BufferedReader input = null;
+    if (index == -1) {
+      System.out.println("\nPlease type in expressions (and press <Enter>), exit with <Ctrl+D>:");
+      input = new BufferedReader(new InputStreamReader(System.in));
+    }
+    else {
+      System.out.println("\nReading expressions from file '" + args[index] + "':");
+      input = new BufferedReader(new FileReader(args[index]));
+    }
+
+    // process stream
+    SymbolFactory sf = new DefaultSymbolFactory();
+    String line;
+    while ((line = input.readLine()) != null) {
+      ByteArrayInputStream parserInput = new ByteArrayInputStream(line.getBytes());
+      Parser parser = new Parser(new Scanner(parserInput,sf), sf);
+      parser.setSymbols(symbols);
+      parser.parse();
+      System.out.println(line + " = " + parser.getResult());
+    }
+  }
+:}
+
+terminal COMMA, LPAREN, RPAREN;
+terminal MINUS, PLUS, TIMES, DIVISION;
+terminal ABS, SQRT, LOG, EXP, SIN, COS, TAN, RINT, FLOOR, POW, CEIL, IFELSE;
+terminal TRUE, FALSE, LT, LE, GT, GE, EQ, NOT, AND, OR;
+terminal Double NUMBER;
+terminal Boolean BOOLEAN;
+terminal String VARIABLE;
+
+non terminal expr_list, expr_part;
+non terminal Double expr;
+non terminal Double opexpr;
+non terminal Double varexpr;
+non terminal Double funcexpr;
+non terminal Boolean boolexpr;
+
+precedence left PLUS, MINUS;
+precedence left TIMES, DIVISION;
+precedence left LPAREN, RPAREN;
+precedence left ABS, SQRT, LOG, EXP, SIN, COS, TAN, RINT, FLOOR, POW, CEIL;
+precedence left AND, OR;
+precedence left NOT;
+
+expr_list ::= expr_list expr_part | expr_part;
+expr_part ::= expr:e {: parser.setResult(e); :} ;
+expr      ::=   NUMBER:n
+                {: RESULT = n; :}
+              | LPAREN expr:e RPAREN
+                {: RESULT = e; :}
+              | opexpr:o
+                {: RESULT = o; :}
+              | varexpr:v
+                {: RESULT = v; :}
+              | funcexpr:f
+                {: RESULT = f; :}
+              ;
+
+opexpr    ::=   expr:l PLUS expr:r
+                {: RESULT = new Double(l.doubleValue() + r.doubleValue()); :}
+              | expr:l MINUS expr:r
+                {: RESULT = new Double(l.doubleValue() - r.doubleValue()); :}
+              | expr:l TIMES expr:r
+                {: RESULT = new Double(l.doubleValue() * r.doubleValue()); :}
+              | expr:l DIVISION expr:r
+                {: RESULT = new Double(l.doubleValue() / r.doubleValue()); :}
+              ;
+
+varexpr  ::=    VARIABLE:v
+                {: if (parser.getSymbols().containsKey(v)) 
+                     RESULT = (Double) parser.getSymbols().get(v); 
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + v + "'!"); 
+                :}
+              ;
+
+funcexpr ::=    ABS LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.abs(e)); :}
+              | SQRT LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.sqrt(e)); :}
+              | LOG LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.log(e)); :}
+              | EXP LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.exp(e)); :}
+              | SIN LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.sin(e)); :}
+              | COS LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.cos(e)); :}
+              | TAN LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.tan(e)); :}
+              | RINT LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.rint(e)); :}
+              | FLOOR LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.floor(e)); :}
+              | POW LPAREN expr:base COMMA expr:exponent RPAREN
+                {: RESULT = new Double(Math.pow(base, exponent)); :}
+              | CEIL LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.ceil(e)); :}
+              | IFELSE LPAREN boolexpr:b COMMA expr:e_true COMMA expr:e_false RPAREN
+                {: if (b) 
+                     RESULT = e_true; 
+                   else
+                     RESULT = e_false; 
+                :}
+              ;
+
+boolexpr ::=    BOOLEAN:b 
+                {: RESULT = b; :}
+              | TRUE
+                {: RESULT = new Boolean(true); :}
+              | FALSE
+                {: RESULT = new Boolean(false); :}
+              | expr:l LT expr:r
+                {: RESULT = new Boolean(l.doubleValue() < r.doubleValue()); :}
+              | expr:l LE expr:r
+                {: RESULT = new Boolean(l.doubleValue() <= r.doubleValue()); :}
+              | expr:l GT expr:r
+                {: RESULT = new Boolean(l.doubleValue() > r.doubleValue()); :}
+              | expr:l GE expr:r
+                {: RESULT = new Boolean(l.doubleValue() >= r.doubleValue()); :}
+              | expr:l EQ expr:r
+                {: RESULT = new Boolean(l.doubleValue() == r.doubleValue()); :}
+              | LPAREN boolexpr:b RPAREN
+                {: RESULT = b; :}
+              | NOT boolexpr:b
+                {: RESULT = !b; :}
+              | boolexpr:l AND boolexpr:r
+                {: RESULT = l && r; :}
+              | boolexpr:l OR boolexpr:r
+                {: RESULT = l || r; :}
+              ;
+
Index: branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Parser.java	(revision 29)
@@ -0,0 +1,1018 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Sun Jan 11 11:14:12 NZDT 2009
+//----------------------------------------------------
+
+package weka.core.mathematicalexpression;
+
+import java_cup.runtime.*;
+import java.io.*;
+import java.util.*;
+
+/** CUP v0.11a beta 20060608 generated parser.
+  * @version Sun Jan 11 11:14:12 NZDT 2009
+  */
+public class Parser extends java_cup.runtime.lr_parser {
+
+  /** Default constructor. */
+  public Parser() {super();}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s) {super(s);}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s, java_cup.runtime.SymbolFactory sf) {super(s,sf);}
+
+  /** Production table. */
+  protected static final short _production_table[][] = 
+    unpackFromStrings(new String[] {
+    "\000\046\000\002\002\004\000\002\002\004\000\002\002" +
+    "\003\000\002\003\003\000\002\004\003\000\002\004\005" +
+    "\000\002\004\003\000\002\004\003\000\002\004\003\000" +
+    "\002\005\005\000\002\005\005\000\002\005\005\000\002" +
+    "\005\005\000\002\006\003\000\002\007\006\000\002\007" +
+    "\006\000\002\007\006\000\002\007\006\000\002\007\006" +
+    "\000\002\007\006\000\002\007\006\000\002\007\006\000" +
+    "\002\007\006\000\002\007\010\000\002\007\006\000\002" +
+    "\007\012\000\002\010\003\000\002\010\003\000\002\010" +
+    "\003\000\002\010\005\000\002\010\005\000\002\010\005" +
+    "\000\002\010\005\000\002\010\005\000\002\010\005\000" +
+    "\002\010\004\000\002\010\005\000\002\010\005" });
+
+  /** Access to production table. */
+  public short[][] production_table() {return _production_table;}
+
+  /** Parse-action table. */
+  protected static final short[][] _action_table = 
+    unpackFromStrings(new String[] {
+    "\000\144\000\040\005\016\013\025\014\011\015\021\016" +
+    "\020\017\005\020\010\021\030\022\015\023\014\024\012" +
+    "\025\017\026\004\041\023\043\013\001\002\000\004\005" +
+    "\110\001\002\000\004\005\105\001\002\000\052\002\ufffe" +
+    "\005\ufffe\007\036\010\034\011\037\012\035\013\ufffe\014" +
+    "\ufffe\015\ufffe\016\ufffe\017\ufffe\020\ufffe\021\ufffe\022\ufffe" +
+    "\023\ufffe\024\ufffe\025\ufffe\026\ufffe\041\ufffe\043\ufffe\001" +
+    "\002\000\074\002\ufffa\004\ufffa\005\ufffa\006\ufffa\007\ufffa" +
+    "\010\ufffa\011\ufffa\012\ufffa\013\ufffa\014\ufffa\015\ufffa\016" +
+    "\ufffa\017\ufffa\020\ufffa\021\ufffa\022\ufffa\023\ufffa\024\ufffa" +
+    "\025\ufffa\026\ufffa\031\ufffa\032\ufffa\033\ufffa\034\ufffa\035" +
+    "\ufffa\037\ufffa\040\ufffa\041\ufffa\043\ufffa\001\002\000\004" +
+    "\005\102\001\002\000\004\005\077\001\002\000\004\005" +
+    "\072\001\002\000\074\002\ufff4\004\ufff4\005\ufff4\006\ufff4" +
+    "\007\ufff4\010\ufff4\011\ufff4\012\ufff4\013\ufff4\014\ufff4\015" +
+    "\ufff4\016\ufff4\017\ufff4\020\ufff4\021\ufff4\022\ufff4\023\ufff4" +
+    "\024\ufff4\025\ufff4\026\ufff4\031\ufff4\032\ufff4\033\ufff4\034" +
+    "\ufff4\035\ufff4\037\ufff4\040\ufff4\041\ufff4\043\ufff4\001\002" +
+    "\000\004\005\067\001\002\000\004\005\064\001\002\000" +
+    "\040\005\016\013\025\014\011\015\021\016\020\017\005" +
+    "\020\010\021\030\022\015\023\014\024\012\025\017\026" +
+    "\004\041\023\043\013\001\002\000\004\005\057\001\002" +
+    "\000\004\005\054\001\002\000\004\005\051\001\002\000" +
+    "\042\002\uffff\005\uffff\013\uffff\014\uffff\015\uffff\016\uffff" +
+    "\017\uffff\020\uffff\021\uffff\022\uffff\023\uffff\024\uffff\025" +
+    "\uffff\026\uffff\041\uffff\043\uffff\001\002\000\074\002\ufffd" +
+    "\004\ufffd\005\ufffd\006\ufffd\007\ufffd\010\ufffd\011\ufffd\012" +
+    "\ufffd\013\ufffd\014\ufffd\015\ufffd\016\ufffd\017\ufffd\020\ufffd" +
+    "\021\ufffd\022\ufffd\023\ufffd\024\ufffd\025\ufffd\026\ufffd\031" +
+    "\ufffd\032\ufffd\033\ufffd\034\ufffd\035\ufffd\037\ufffd\040\ufffd" +
+    "\041\ufffd\043\ufffd\001\002\000\074\002\ufff9\004\ufff9\005" +
+    "\ufff9\006\ufff9\007\ufff9\010\ufff9\011\ufff9\012\ufff9\013\ufff9" +
+    "\014\ufff9\015\ufff9\016\ufff9\017\ufff9\020\ufff9\021\ufff9\022" +
+    "\ufff9\023\ufff9\024\ufff9\025\ufff9\026\ufff9\031\ufff9\032\ufff9" +
+    "\033\ufff9\034\ufff9\035\ufff9\037\ufff9\040\ufff9\041\ufff9\043" +
+    "\ufff9\001\002\000\004\005\046\001\002\000\042\002\045" +
+    "\005\016\013\025\014\011\015\021\016\020\017\005\020" +
+    "\010\021\030\022\015\023\014\024\012\025\017\026\004" +
+    "\041\023\043\013\001\002\000\074\002\ufffb\004\ufffb\005" +
+    "\ufffb\006\ufffb\007\ufffb\010\ufffb\011\ufffb\012\ufffb\013\ufffb" +
+    "\014\ufffb\015\ufffb\016\ufffb\017\ufffb\020\ufffb\021\ufffb\022" +
+    "\ufffb\023\ufffb\024\ufffb\025\ufffb\026\ufffb\031\ufffb\032\ufffb" +
+    "\033\ufffb\034\ufffb\035\ufffb\037\ufffb\040\ufffb\041\ufffb\043" +
+    "\ufffb\001\002\000\004\005\031\001\002\000\040\005\016" +
+    "\013\025\014\011\015\021\016\020\017\005\020\010\021" +
+    "\030\022\015\023\014\024\012\025\017\026\004\041\023" +
+    "\043\013\001\002\000\014\006\033\007\036\010\034\011" +
+    "\037\012\035\001\002\000\074\002\uffed\004\uffed\005\uffed" +
+    "\006\uffed\007\uffed\010\uffed\011\uffed\012\uffed\013\uffed\014" +
+    "\uffed\015\uffed\016\uffed\017\uffed\020\uffed\021\uffed\022\uffed" +
+    "\023\uffed\024\uffed\025\uffed\026\uffed\031\uffed\032\uffed\033" +
+    "\uffed\034\uffed\035\uffed\037\uffed\040\uffed\041\uffed\043\uffed" +
+    "\001\002\000\040\005\016\013\025\014\011\015\021\016" +
+    "\020\017\005\020\010\021\030\022\015\023\014\024\012" +
+    "\025\017\026\004\041\023\043\013\001\002\000\040\005" +
+    "\016\013\025\014\011\015\021\016\020\017\005\020\010" +
+    "\021\030\022\015\023\014\024\012\025\017\026\004\041" +
+    "\023\043\013\001\002\000\040\005\016\013\025\014\011" +
+    "\015\021\016\020\017\005\020\010\021\030\022\015\023" +
+    "\014\024\012\025\017\026\004\041\023\043\013\001\002" +
+    "\000\040\005\016\013\025\014\011\015\021\016\020\017" +
+    "\005\020\010\021\030\022\015\023\014\024\012\025\017" +
+    "\026\004\041\023\043\013\001\002\000\074\002\ufff6\004" +
+    "\ufff6\005\ufff6\006\ufff6\007\ufff6\010\ufff6\011\ufff6\012\ufff6" +
+    "\013\ufff6\014\ufff6\015\ufff6\016\ufff6\017\ufff6\020\ufff6\021" +
+    "\ufff6\022\ufff6\023\ufff6\024\ufff6\025\ufff6\026\ufff6\031\ufff6" +
+    "\032\ufff6\033\ufff6\034\ufff6\035\ufff6\037\ufff6\040\ufff6\041" +
+    "\ufff6\043\ufff6\001\002\000\074\002\ufff7\004\ufff7\005\ufff7" +
+    "\006\ufff7\007\ufff7\010\ufff7\011\037\012\035\013\ufff7\014" +
+    "\ufff7\015\ufff7\016\ufff7\017\ufff7\020\ufff7\021\ufff7\022\ufff7" +
+    "\023\ufff7\024\ufff7\025\ufff7\026\ufff7\031\ufff7\032\ufff7\033" +
+    "\ufff7\034\ufff7\035\ufff7\037\ufff7\040\ufff7\041\ufff7\043\ufff7" +
+    "\001\002\000\074\002\ufff5\004\ufff5\005\ufff5\006\ufff5\007" +
+    "\ufff5\010\ufff5\011\ufff5\012\ufff5\013\ufff5\014\ufff5\015\ufff5" +
+    "\016\ufff5\017\ufff5\020\ufff5\021\ufff5\022\ufff5\023\ufff5\024" +
+    "\ufff5\025\ufff5\026\ufff5\031\ufff5\032\ufff5\033\ufff5\034\ufff5" +
+    "\035\ufff5\037\ufff5\040\ufff5\041\ufff5\043\ufff5\001\002\000" +
+    "\074\002\ufff8\004\ufff8\005\ufff8\006\ufff8\007\ufff8\010\ufff8" +
+    "\011\037\012\035\013\ufff8\014\ufff8\015\ufff8\016\ufff8\017" +
+    "\ufff8\020\ufff8\021\ufff8\022\ufff8\023\ufff8\024\ufff8\025\ufff8" +
+    "\026\ufff8\031\ufff8\032\ufff8\033\ufff8\034\ufff8\035\ufff8\037" +
+    "\ufff8\040\ufff8\041\ufff8\043\ufff8\001\002\000\042\002\001" +
+    "\005\001\013\001\014\001\015\001\016\001\017\001\020" +
+    "\001\021\001\022\001\023\001\024\001\025\001\026\001" +
+    "\041\001\043\001\001\002\000\004\002\000\001\002\000" +
+    "\040\005\016\013\025\014\011\015\021\016\020\017\005" +
+    "\020\010\021\030\022\015\023\014\024\012\025\017\026" +
+    "\004\041\023\043\013\001\002\000\014\006\050\007\036" +
+    "\010\034\011\037\012\035\001\002\000\074\002\ufff3\004" +
+    "\ufff3\005\ufff3\006\ufff3\007\ufff3\010\ufff3\011\ufff3\012\ufff3" +
+    "\013\ufff3\014\ufff3\015\ufff3\016\ufff3\017\ufff3\020\ufff3\021" +
+    "\ufff3\022\ufff3\023\ufff3\024\ufff3\025\ufff3\026\ufff3\031\ufff3" +
+    "\032\ufff3\033\ufff3\034\ufff3\035\ufff3\037\ufff3\040\ufff3\041" +
+    "\ufff3\043\ufff3\001\002\000\040\005\016\013\025\014\011" +
+    "\015\021\016\020\017\005\020\010\021\030\022\015\023" +
+    "\014\024\012\025\017\026\004\041\023\043\013\001\002" +
+    "\000\014\006\053\007\036\010\034\011\037\012\035\001" +
+    "\002\000\074\002\ufff1\004\ufff1\005\ufff1\006\ufff1\007\ufff1" +
+    "\010\ufff1\011\ufff1\012\ufff1\013\ufff1\014\ufff1\015\ufff1\016" +
+    "\ufff1\017\ufff1\020\ufff1\021\ufff1\022\ufff1\023\ufff1\024\ufff1" +
+    "\025\ufff1\026\ufff1\031\ufff1\032\ufff1\033\ufff1\034\ufff1\035" +
+    "\ufff1\037\ufff1\040\ufff1\041\ufff1\043\ufff1\001\002\000\040" +
+    "\005\016\013\025\014\011\015\021\016\020\017\005\020" +
+    "\010\021\030\022\015\023\014\024\012\025\017\026\004" +
+    "\041\023\043\013\001\002\000\014\006\056\007\036\010" +
+    "\034\011\037\012\035\001\002\000\074\002\ufff0\004\ufff0" +
+    "\005\ufff0\006\ufff0\007\ufff0\010\ufff0\011\ufff0\012\ufff0\013" +
+    "\ufff0\014\ufff0\015\ufff0\016\ufff0\017\ufff0\020\ufff0\021\ufff0" +
+    "\022\ufff0\023\ufff0\024\ufff0\025\ufff0\026\ufff0\031\ufff0\032" +
+    "\ufff0\033\ufff0\034\ufff0\035\ufff0\037\ufff0\040\ufff0\041\ufff0" +
+    "\043\ufff0\001\002\000\040\005\016\013\025\014\011\015" +
+    "\021\016\020\017\005\020\010\021\030\022\015\023\014" +
+    "\024\012\025\017\026\004\041\023\043\013\001\002\000" +
+    "\014\006\061\007\036\010\034\011\037\012\035\001\002" +
+    "\000\074\002\uffe9\004\uffe9\005\uffe9\006\uffe9\007\uffe9\010" +
+    "\uffe9\011\uffe9\012\uffe9\013\uffe9\014\uffe9\015\uffe9\016\uffe9" +
+    "\017\uffe9\020\uffe9\021\uffe9\022\uffe9\023\uffe9\024\uffe9\025" +
+    "\uffe9\026\uffe9\031\uffe9\032\uffe9\033\uffe9\034\uffe9\035\uffe9" +
+    "\037\uffe9\040\uffe9\041\uffe9\043\uffe9\001\002\000\014\006" +
+    "\063\007\036\010\034\011\037\012\035\001\002\000\074" +
+    "\002\ufffc\004\ufffc\005\ufffc\006\ufffc\007\ufffc\010\ufffc\011" +
+    "\ufffc\012\ufffc\013\ufffc\014\ufffc\015\ufffc\016\ufffc\017\ufffc" +
+    "\020\ufffc\021\ufffc\022\ufffc\023\ufffc\024\ufffc\025\ufffc\026" +
+    "\ufffc\031\ufffc\032\ufffc\033\ufffc\034\ufffc\035\ufffc\037\ufffc" +
+    "\040\ufffc\041\ufffc\043\ufffc\001\002\000\040\005\016\013" +
+    "\025\014\011\015\021\016\020\017\005\020\010\021\030" +
+    "\022\015\023\014\024\012\025\017\026\004\041\023\043" +
+    "\013\001\002\000\014\006\066\007\036\010\034\011\037" +
+    "\012\035\001\002\000\074\002\uffec\004\uffec\005\uffec\006" +
+    "\uffec\007\uffec\010\uffec\011\uffec\012\uffec\013\uffec\014\uffec" +
+    "\015\uffec\016\uffec\017\uffec\020\uffec\021\uffec\022\uffec\023" +
+    "\uffec\024\uffec\025\uffec\026\uffec\031\uffec\032\uffec\033\uffec" +
+    "\034\uffec\035\uffec\037\uffec\040\uffec\041\uffec\043\uffec\001" +
+    "\002\000\040\005\016\013\025\014\011\015\021\016\020" +
+    "\017\005\020\010\021\030\022\015\023\014\024\012\025" +
+    "\017\026\004\041\023\043\013\001\002\000\014\006\071" +
+    "\007\036\010\034\011\037\012\035\001\002\000\074\002" +
+    "\uffeb\004\uffeb\005\uffeb\006\uffeb\007\uffeb\010\uffeb\011\uffeb" +
+    "\012\uffeb\013\uffeb\014\uffeb\015\uffeb\016\uffeb\017\uffeb\020" +
+    "\uffeb\021\uffeb\022\uffeb\023\uffeb\024\uffeb\025\uffeb\026\uffeb" +
+    "\031\uffeb\032\uffeb\033\uffeb\034\uffeb\035\uffeb\037\uffeb\040" +
+    "\uffeb\041\uffeb\043\uffeb\001\002\000\040\005\016\013\025" +
+    "\014\011\015\021\016\020\017\005\020\010\021\030\022" +
+    "\015\023\014\024\012\025\017\026\004\041\023\043\013" +
+    "\001\002\000\014\004\074\007\036\010\034\011\037\012" +
+    "\035\001\002\000\040\005\016\013\025\014\011\015\021" +
+    "\016\020\017\005\020\010\021\030\022\015\023\014\024" +
+    "\012\025\017\026\004\041\023\043\013\001\002\000\014" +
+    "\006\076\007\036\010\034\011\037\012\035\001\002\000" +
+    "\074\002\uffea\004\uffea\005\uffea\006\uffea\007\uffea\010\uffea" +
+    "\011\uffea\012\uffea\013\uffea\014\uffea\015\uffea\016\uffea\017" +
+    "\uffea\020\uffea\021\uffea\022\uffea\023\uffea\024\uffea\025\uffea" +
+    "\026\uffea\031\uffea\032\uffea\033\uffea\034\uffea\035\uffea\037" +
+    "\uffea\040\uffea\041\uffea\043\uffea\001\002\000\040\005\016" +
+    "\013\025\014\011\015\021\016\020\017\005\020\010\021" +
+    "\030\022\015\023\014\024\012\025\017\026\004\041\023" +
+    "\043\013\001\002\000\014\006\101\007\036\010\034\011" +
+    "\037\012\035\001\002\000\074\002\ufff2\004\ufff2\005\ufff2" +
+    "\006\ufff2\007\ufff2\010\ufff2\011\ufff2\012\ufff2\013\ufff2\014" +
+    "\ufff2\015\ufff2\016\ufff2\017\ufff2\020\ufff2\021\ufff2\022\ufff2" +
+    "\023\ufff2\024\ufff2\025\ufff2\026\ufff2\031\ufff2\032\ufff2\033" +
+    "\ufff2\034\ufff2\035\ufff2\037\ufff2\040\ufff2\041\ufff2\043\ufff2" +
+    "\001\002\000\040\005\016\013\025\014\011\015\021\016" +
+    "\020\017\005\020\010\021\030\022\015\023\014\024\012" +
+    "\025\017\026\004\041\023\043\013\001\002\000\014\006" +
+    "\104\007\036\010\034\011\037\012\035\001\002\000\074" +
+    "\002\uffee\004\uffee\005\uffee\006\uffee\007\uffee\010\uffee\011" +
+    "\uffee\012\uffee\013\uffee\014\uffee\015\uffee\016\uffee\017\uffee" +
+    "\020\uffee\021\uffee\022\uffee\023\uffee\024\uffee\025\uffee\026" +
+    "\uffee\031\uffee\032\uffee\033\uffee\034\uffee\035\uffee\037\uffee" +
+    "\040\uffee\041\uffee\043\uffee\001\002\000\040\005\016\013" +
+    "\025\014\011\015\021\016\020\017\005\020\010\021\030" +
+    "\022\015\023\014\024\012\025\017\026\004\041\023\043" +
+    "\013\001\002\000\014\006\107\007\036\010\034\011\037" +
+    "\012\035\001\002\000\074\002\uffef\004\uffef\005\uffef\006" +
+    "\uffef\007\uffef\010\uffef\011\uffef\012\uffef\013\uffef\014\uffef" +
+    "\015\uffef\016\uffef\017\uffef\020\uffef\021\uffef\022\uffef\023" +
+    "\uffef\024\uffef\025\uffef\026\uffef\031\uffef\032\uffef\033\uffef" +
+    "\034\uffef\035\uffef\037\uffef\040\uffef\041\uffef\043\uffef\001" +
+    "\002\000\050\005\114\013\025\014\011\015\021\016\020" +
+    "\017\005\020\010\021\030\022\015\023\014\024\012\025" +
+    "\017\026\004\027\113\030\117\036\112\041\023\042\115" +
+    "\043\013\001\002\000\024\007\036\010\034\011\037\012" +
+    "\035\031\140\032\136\033\137\034\134\035\135\001\002" +
+    "\000\050\005\114\013\025\014\011\015\021\016\020\017" +
+    "\005\020\010\021\030\022\015\023\014\024\012\025\017" +
+    "\026\004\027\113\030\117\036\112\041\023\042\115\043" +
+    "\013\001\002\000\012\004\uffe6\006\uffe6\037\uffe6\040\uffe6" +
+    "\001\002\000\050\005\114\013\025\014\011\015\021\016" +
+    "\020\017\005\020\010\021\030\022\015\023\014\024\012" +
+    "\025\017\026\004\027\113\030\117\036\112\041\023\042" +
+    "\115\043\013\001\002\000\012\004\uffe7\006\uffe7\037\uffe7" +
+    "\040\uffe7\001\002\000\010\004\120\037\121\040\122\001" +
+    "\002\000\012\004\uffe5\006\uffe5\037\uffe5\040\uffe5\001\002" +
+    "\000\040\005\016\013\025\014\011\015\021\016\020\017" +
+    "\005\020\010\021\030\022\015\023\014\024\012\025\017" +
+    "\026\004\041\023\043\013\001\002\000\050\005\114\013" +
+    "\025\014\011\015\021\016\020\017\005\020\010\021\030" +
+    "\022\015\023\014\024\012\025\017\026\004\027\113\030" +
+    "\117\036\112\041\023\042\115\043\013\001\002\000\050" +
+    "\005\114\013\025\014\011\015\021\016\020\017\005\020" +
+    "\010\021\030\022\015\023\014\024\012\025\017\026\004" +
+    "\027\113\030\117\036\112\041\023\042\115\043\013\001" +
+    "\002\000\012\004\uffdc\006\uffdc\037\uffdc\040\uffdc\001\002" +
+    "\000\012\004\uffdd\006\uffdd\037\uffdd\040\uffdd\001\002\000" +
+    "\014\004\126\007\036\010\034\011\037\012\035\001\002" +
+    "\000\040\005\016\013\025\014\011\015\021\016\020\017" +
+    "\005\020\010\021\030\022\015\023\014\024\012\025\017" +
+    "\026\004\041\023\043\013\001\002\000\014\006\130\007" +
+    "\036\010\034\011\037\012\035\001\002\000\074\002\uffe8" +
+    "\004\uffe8\005\uffe8\006\uffe8\007\uffe8\010\uffe8\011\uffe8\012" +
+    "\uffe8\013\uffe8\014\uffe8\015\uffe8\016\uffe8\017\uffe8\020\uffe8" +
+    "\021\uffe8\022\uffe8\023\uffe8\024\uffe8\025\uffe8\026\uffe8\031" +
+    "\uffe8\032\uffe8\033\uffe8\034\uffe8\035\uffe8\037\uffe8\040\uffe8" +
+    "\041\uffe8\043\uffe8\001\002\000\026\006\063\007\036\010" +
+    "\034\011\037\012\035\031\140\032\136\033\137\034\134" +
+    "\035\135\001\002\000\010\006\133\037\121\040\122\001" +
+    "\002\000\012\004\uffdf\006\uffdf\037\uffdf\040\uffdf\001\002" +
+    "\000\040\005\016\013\025\014\011\015\021\016\020\017" +
+    "\005\020\010\021\030\022\015\023\014\024\012\025\017" +
+    "\026\004\041\023\043\013\001\002\000\040\005\016\013" +
+    "\025\014\011\015\021\016\020\017\005\020\010\021\030" +
+    "\022\015\023\014\024\012\025\017\026\004\041\023\043" +
+    "\013\001\002\000\040\005\016\013\025\014\011\015\021" +
+    "\016\020\017\005\020\010\021\030\022\015\023\014\024" +
+    "\012\025\017\026\004\041\023\043\013\001\002\000\040" +
+    "\005\016\013\025\014\011\015\021\016\020\017\005\020" +
+    "\010\021\030\022\015\023\014\024\012\025\017\026\004" +
+    "\041\023\043\013\001\002\000\040\005\016\013\025\014" +
+    "\011\015\021\016\020\017\005\020\010\021\030\022\015" +
+    "\023\014\024\012\025\017\026\004\041\023\043\013\001" +
+    "\002\000\022\004\uffe4\006\uffe4\007\036\010\034\011\037" +
+    "\012\035\037\uffe4\040\uffe4\001\002\000\022\004\uffe2\006" +
+    "\uffe2\007\036\010\034\011\037\012\035\037\uffe2\040\uffe2" +
+    "\001\002\000\022\004\uffe3\006\uffe3\007\036\010\034\011" +
+    "\037\012\035\037\uffe3\040\uffe3\001\002\000\022\004\uffe0" +
+    "\006\uffe0\007\036\010\034\011\037\012\035\037\uffe0\040" +
+    "\uffe0\001\002\000\022\004\uffe1\006\uffe1\007\036\010\034" +
+    "\011\037\012\035\037\uffe1\040\uffe1\001\002\000\012\004" +
+    "\uffde\006\uffde\037\uffde\040\uffde\001\002" });
+
+  /** Access to parse-action table. */
+  public short[][] action_table() {return _action_table;}
+
+  /** <code>reduce_goto</code> table. */
+  protected static final short[][] _reduce_table = 
+    unpackFromStrings(new String[] {
+    "\000\144\000\016\002\025\003\021\004\005\005\026\006" +
+    "\006\007\023\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\012\004\061\005\026\006" +
+    "\006\007\023\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\014\003\043\004\005" +
+    "\005\026\006\006\007\023\001\001\000\002\001\001\000" +
+    "\002\001\001\000\012\004\031\005\026\006\006\007\023" +
+    "\001\001\000\002\001\001\000\002\001\001\000\012\004" +
+    "\042\005\026\006\006\007\023\001\001\000\012\004\041" +
+    "\005\026\006\006\007\023\001\001\000\012\004\040\005" +
+    "\026\006\006\007\023\001\001\000\012\004\037\005\026" +
+    "\006\006\007\023\001\001\000\002\001\001\000\002\001" +
+    "\001\000\002\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\012\004\046\005\026\006\006\007" +
+    "\023\001\001\000\002\001\001\000\002\001\001\000\012" +
+    "\004\051\005\026\006\006\007\023\001\001\000\002\001" +
+    "\001\000\002\001\001\000\012\004\054\005\026\006\006" +
+    "\007\023\001\001\000\002\001\001\000\002\001\001\000" +
+    "\012\004\057\005\026\006\006\007\023\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\002\001" +
+    "\001\000\012\004\064\005\026\006\006\007\023\001\001" +
+    "\000\002\001\001\000\002\001\001\000\012\004\067\005" +
+    "\026\006\006\007\023\001\001\000\002\001\001\000\002" +
+    "\001\001\000\012\004\072\005\026\006\006\007\023\001" +
+    "\001\000\002\001\001\000\012\004\074\005\026\006\006" +
+    "\007\023\001\001\000\002\001\001\000\002\001\001\000" +
+    "\012\004\077\005\026\006\006\007\023\001\001\000\002" +
+    "\001\001\000\002\001\001\000\012\004\102\005\026\006" +
+    "\006\007\023\001\001\000\002\001\001\000\002\001\001" +
+    "\000\012\004\105\005\026\006\006\007\023\001\001\000" +
+    "\002\001\001\000\002\001\001\000\014\004\110\005\026" +
+    "\006\006\007\023\010\115\001\001\000\002\001\001\000" +
+    "\014\004\110\005\026\006\006\007\023\010\145\001\001" +
+    "\000\002\001\001\000\014\004\130\005\026\006\006\007" +
+    "\023\010\131\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\012\004\124\005\026\006\006\007" +
+    "\023\001\001\000\014\004\110\005\026\006\006\007\023" +
+    "\010\123\001\001\000\014\004\110\005\026\006\006\007" +
+    "\023\010\122\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\012\004\126\005\026\006\006\007" +
+    "\023\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\012\004" +
+    "\144\005\026\006\006\007\023\001\001\000\012\004\143" +
+    "\005\026\006\006\007\023\001\001\000\012\004\142\005" +
+    "\026\006\006\007\023\001\001\000\012\004\141\005\026" +
+    "\006\006\007\023\001\001\000\012\004\140\005\026\006" +
+    "\006\007\023\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001" });
+
+  /** Access to <code>reduce_goto</code> table. */
+  public short[][] reduce_table() {return _reduce_table;}
+
+  /** Instance of action encapsulation class. */
+  protected CUP$Parser$actions action_obj;
+
+  /** Action encapsulation object initializer. */
+  protected void init_actions()
+    {
+      action_obj = new CUP$Parser$actions(this);
+    }
+
+  /** Invoke a user supplied parse action. */
+  public java_cup.runtime.Symbol do_action(
+    int                        act_num,
+    java_cup.runtime.lr_parser parser,
+    java.util.Stack            stack,
+    int                        top)
+    throws java.lang.Exception
+  {
+    /* call code in generated class */
+    return action_obj.CUP$Parser$do_action(act_num, parser, stack, top);
+  }
+
+  /** Indicates start state. */
+  public int start_state() {return 0;}
+  /** Indicates start production. */
+  public int start_production() {return 1;}
+
+  /** <code>EOF</code> Symbol index. */
+  public int EOF_sym() {return 0;}
+
+  /** <code>error</code> Symbol index. */
+  public int error_sym() {return 1;}
+
+
+
+  /** variable - value relation. */
+  protected HashMap m_Symbols = new HashMap();
+
+  /** for storing the result of the expresion. */
+  protected Double m_Result = null;
+
+  /**
+   * Sets the variable - value relation to use.
+   * 
+   * @param value the variable-value relation
+   */
+  public void setSymbols(HashMap value) {
+    m_Symbols = value;
+  }
+
+  /**
+   * Returns the current variable - value relation in use.
+   * 
+   * @return the variable-value relation
+   */
+  public HashMap getSymbols() {
+    return m_Symbols;
+  }
+
+  /**
+   * Sets the result of the evaluation.
+   * 
+   * @param value the result
+   */
+  public void setResult(Double value) {
+    m_Result = value;
+  }
+
+  /**
+   * Returns the result of the evaluation.
+   * 
+   * @return the result
+   */
+  public Double getResult() {
+    return m_Result;
+  }
+
+  /**
+   * Runs the parser from commandline. Either reads lines from System.in
+   * or from a provided file (line by line). With 
+   * <code>-symbols <semi-colon separated list of variable/value pairs></code>
+   * as first parameter one can provide predefined variable values. E.g.:
+   * <code>-symbols "Y=10;X=3" "X+Y"</code>
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    // read symbols, if present
+    HashMap<String,Double> symbols = new HashMap<String,Double>();
+    if (args.length > 0) {
+      if (args[0].equals("-symbols")) {
+        // parse symbols
+        String[] pairs = args[1].replaceAll(" ", "").split(";");
+        for (int i = 0; i < pairs.length; i++) {
+          String[] parts = pairs[i].split("=");
+          symbols.put(parts[0], new Double(parts[1]));
+        }
+        // print symbols
+        System.out.println("\nSymbols provided:");
+        Iterator iter = symbols.keySet().iterator();
+        while (iter.hasNext()) {
+          String key = (String) iter.next();
+          System.out.println(key + "=" + symbols.get(key));
+        }
+        // remove symbols from commandline
+        String[] argsNew = new String[args.length - 2];
+        System.arraycopy(args, 2, argsNew, 0, argsNew.length);
+        args = argsNew;
+      }
+    }
+
+    // setup input stream
+    int index = -1;
+    if (args.length == 1)
+      index = 0;
+    BufferedReader input = null;
+    if (index == -1) {
+      System.out.println("\nPlease type in expressions (and press <Enter>), exit with <Ctrl+D>:");
+      input = new BufferedReader(new InputStreamReader(System.in));
+    }
+    else {
+      System.out.println("\nReading expressions from file '" + args[index] + "':");
+      input = new BufferedReader(new FileReader(args[index]));
+    }
+
+    // process stream
+    SymbolFactory sf = new DefaultSymbolFactory();
+    String line;
+    while ((line = input.readLine()) != null) {
+      ByteArrayInputStream parserInput = new ByteArrayInputStream(line.getBytes());
+      Parser parser = new Parser(new Scanner(parserInput,sf), sf);
+      parser.setSymbols(symbols);
+      parser.parse();
+      System.out.println(line + " = " + parser.getResult());
+    }
+  }
+
+}
+
+/** Cup generated class to encapsulate user supplied action code.*/
+class CUP$Parser$actions {
+  private final Parser parser;
+
+  /** Constructor */
+  CUP$Parser$actions(Parser parser) {
+    this.parser = parser;
+  }
+
+  /** Method with the actual generated action code. */
+  public final java_cup.runtime.Symbol CUP$Parser$do_action(
+    int                        CUP$Parser$act_num,
+    java_cup.runtime.lr_parser CUP$Parser$parser,
+    java.util.Stack            CUP$Parser$stack,
+    int                        CUP$Parser$top)
+    throws java.lang.Exception
+    {
+      /* Symbol object for return from actions */
+      java_cup.runtime.Symbol CUP$Parser$result;
+
+      /* select the action based on the action number */
+      switch (CUP$Parser$act_num)
+        {
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 37: // boolexpr ::= boolexpr OR boolexpr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Boolean l = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean r = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = l || r; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 36: // boolexpr ::= boolexpr AND boolexpr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Boolean l = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean r = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = l && r; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 35: // boolexpr ::= NOT boolexpr 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = !b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 34: // boolexpr ::= LPAREN boolexpr RPAREN 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 33: // boolexpr ::= expr EQ expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() == r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 32: // boolexpr ::= expr GE expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() >= r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 31: // boolexpr ::= expr GT expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() > r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 30: // boolexpr ::= expr LE expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() <= r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 29: // boolexpr ::= expr LT expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() < r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 28: // boolexpr ::= FALSE 
+            {
+              Boolean RESULT =null;
+		 RESULT = new Boolean(false); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 27: // boolexpr ::= TRUE 
+            {
+              Boolean RESULT =null;
+		 RESULT = new Boolean(true); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 26: // boolexpr ::= BOOLEAN 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",6, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 25: // funcexpr ::= IFELSE LPAREN boolexpr COMMA expr COMMA expr RPAREN 
+            {
+              Double RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-5)).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-5)).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-5)).value;
+		int e_trueleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).left;
+		int e_trueright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).right;
+		Double e_true = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-3)).value;
+		int e_falseleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int e_falseright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e_false = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 if (b) 
+                     RESULT = e_true; 
+                   else
+                     RESULT = e_false; 
+                
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-7)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 24: // funcexpr ::= CEIL LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.ceil(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 23: // funcexpr ::= POW LPAREN expr COMMA expr RPAREN 
+            {
+              Double RESULT =null;
+		int baseleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).left;
+		int baseright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).right;
+		Double base = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-3)).value;
+		int exponentleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int exponentright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double exponent = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.pow(base, exponent)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-5)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 22: // funcexpr ::= FLOOR LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.floor(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 21: // funcexpr ::= RINT LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.rint(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 20: // funcexpr ::= TAN LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.tan(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 19: // funcexpr ::= COS LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.cos(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 18: // funcexpr ::= SIN LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.sin(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 17: // funcexpr ::= EXP LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.exp(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 16: // funcexpr ::= LOG LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.log(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 15: // funcexpr ::= SQRT LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.sqrt(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 14: // funcexpr ::= ABS LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.abs(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 13: // varexpr ::= VARIABLE 
+            {
+              Double RESULT =null;
+		int vleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int vright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		String v = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 if (parser.getSymbols().containsKey(v)) 
+                     RESULT = (Double) parser.getSymbols().get(v); 
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + v + "'!"); 
+                
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("varexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 12: // opexpr ::= expr DIVISION expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() / r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 11: // opexpr ::= expr TIMES expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() * r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 10: // opexpr ::= expr MINUS expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() - r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 9: // opexpr ::= expr PLUS expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() + r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 8: // expr ::= funcexpr 
+            {
+              Double RESULT =null;
+		int fleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int fright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double f = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = f; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 7: // expr ::= varexpr 
+            {
+              Double RESULT =null;
+		int vleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int vright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double v = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = v; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 6: // expr ::= opexpr 
+            {
+              Double RESULT =null;
+		int oleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int oright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double o = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = o; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 5: // expr ::= LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = e; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 4: // expr ::= NUMBER 
+            {
+              Double RESULT =null;
+		int nleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int nright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double n = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = n; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 3: // expr_part ::= expr 
+            {
+              Object RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 parser.setResult(e); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr_part",1, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 2: // expr_list ::= expr_part 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr_list",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 1: // $START ::= expr_list EOF 
+            {
+              Object RESULT =null;
+		int start_valleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int start_valright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Object start_val = (Object)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		RESULT = start_val;
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("$START",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          /* ACCEPT */
+          CUP$Parser$parser.done_parsing();
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 0: // expr_list ::= expr_list expr_part 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr_list",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /* . . . . . .*/
+          default:
+            throw new Exception(
+               "Invalid action number found in internal parse table");
+
+        }
+    }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.java	(revision 29)
@@ -0,0 +1,705 @@
+/* The following code was generated by JFlex 1.4.2 on 11/01/09 11:14 AM */
+
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.mathematicalexpression;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for mathematical expressions.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+
+public class Scanner implements java_cup.runtime.Scanner {
+
+  /** This character denotes the end of file */
+  public static final int YYEOF = -1;
+
+  /** initial size of the lookahead buffer */
+  private static final int ZZ_BUFFERSIZE = 16384;
+
+  /** lexical states */
+  public static final int YYINITIAL = 0;
+
+  /**
+   * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+   * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+   *                  at the beginning of a line
+   * l is of the form l = 2*k, k a non negative integer
+   */
+  private static final int ZZ_LEXSTATE[] = { 
+     0, 0
+  };
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final char [] ZZ_CMAP = {
+     0,  0,  0,  0,  0,  0,  0,  0,  0, 32, 32,  0, 32, 32,  0,  0, 
+     0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 
+    32,  8,  0,  0,  0,  0,  9,  0, 34, 35,  3,  2, 33,  1, 30,  4, 
+    29, 29, 29, 29, 29, 29, 29, 29, 29, 29,  0,  0,  5,  6,  7,  0, 
+     0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 
+    31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,  0,  0,  0,  0,  0, 
+     0, 16, 19, 27,  0, 14, 15, 22,  0, 25,  0,  0, 17,  0, 26, 21, 
+    24, 20, 12, 18, 11, 13,  0, 28, 23,  0,  0,  0, 10,  0,  0,  0
+  };
+
+  /** 
+   * Translates DFA states to action switch labels.
+   */
+  private static final int [] ZZ_ACTION = zzUnpackAction();
+
+  private static final String ZZ_ACTION_PACKED_0 =
+    "\1\0\1\1\1\2\1\3\1\4\1\5\1\6\1\7"+
+    "\1\10\1\11\1\12\1\13\12\1\1\14\1\15\1\16"+
+    "\1\17\1\20\1\21\1\22\1\23\16\0\1\14\1\0"+
+    "\1\24\1\0\1\25\2\0\1\26\1\27\1\0\1\30"+
+    "\1\31\2\0\1\32\1\33\1\34\2\0\1\35\1\0"+
+    "\1\36\1\37\1\40\1\0\1\41";
+
+  private static int [] zzUnpackAction() {
+    int [] result = new int[70];
+    int offset = 0;
+    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAction(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /** 
+   * Translates a state to a row index in the transition table
+   */
+  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+  private static final String ZZ_ROWMAP_PACKED_0 =
+    "\0\0\0\44\0\110\0\44\0\44\0\44\0\154\0\44"+
+    "\0\220\0\44\0\44\0\44\0\264\0\330\0\374\0\u0120"+
+    "\0\u0144\0\u0168\0\u018c\0\u01b0\0\u01d4\0\u01f8\0\u021c\0\u0240"+
+    "\0\44\0\44\0\44\0\44\0\44\0\44\0\u0264\0\u0288"+
+    "\0\u02ac\0\u02d0\0\u02f4\0\u0318\0\u033c\0\u0360\0\u0384\0\u03a8"+
+    "\0\u03cc\0\u03f0\0\u0414\0\u0438\0\u045c\0\u0480\0\44\0\u04a4"+
+    "\0\44\0\u04c8\0\u04ec\0\44\0\44\0\u0510\0\44\0\44"+
+    "\0\u0534\0\u0558\0\44\0\44\0\44\0\u057c\0\u05a0\0\44"+
+    "\0\u05c4\0\44\0\44\0\44\0\u05e8\0\44";
+
+  private static int [] zzUnpackRowMap() {
+    int [] result = new int[70];
+    int offset = 0;
+    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+    int i = 0;  /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int high = packed.charAt(i++) << 16;
+      result[j++] = high | packed.charAt(i++);
+    }
+    return j;
+  }
+
+  /** 
+   * The transition table of the DFA
+   */
+  private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+  private static final String ZZ_TRANS_PACKED_0 =
+    "\1\2\1\3\1\4\1\5\1\6\1\7\1\10\1\11"+
+    "\1\12\1\13\1\14\1\15\1\16\1\2\1\17\1\20"+
+    "\1\21\1\22\1\23\5\2\1\24\1\25\1\2\1\26"+
+    "\1\2\1\27\1\2\1\30\1\31\1\32\1\33\1\34"+
+    "\101\0\1\27\14\0\1\35\43\0\1\36\51\0\1\37"+
+    "\3\0\1\40\54\0\1\41\41\0\1\42\34\0\1\43"+
+    "\1\44\45\0\1\45\45\0\1\46\42\0\1\47\4\0"+
+    "\1\50\37\0\1\51\35\0\1\52\42\0\1\53\6\0"+
+    "\1\54\53\0\1\27\1\55\44\0\1\30\21\0\1\56"+
+    "\60\0\1\57\43\0\1\60\41\0\1\61\34\0\1\62"+
+    "\47\0\1\63\40\0\1\64\47\0\1\65\31\0\1\66"+
+    "\61\0\1\67\45\0\1\70\25\0\1\71\56\0\1\72"+
+    "\34\0\1\73\56\0\1\55\24\0\1\74\40\0\1\75"+
+    "\52\0\1\76\46\0\1\77\31\0\1\100\51\0\1\101"+
+    "\43\0\1\102\40\0\1\103\41\0\1\104\51\0\1\105"+
+    "\37\0\1\106\25\0";
+
+  private static int [] zzUnpackTrans() {
+    int [] result = new int[1548];
+    int offset = 0;
+    offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackTrans(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      value--;
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /* error codes */
+  private static final int ZZ_UNKNOWN_ERROR = 0;
+  private static final int ZZ_NO_MATCH = 1;
+  private static final int ZZ_PUSHBACK_2BIG = 2;
+
+  /* error messages for the codes above */
+  private static final String ZZ_ERROR_MSG[] = {
+    "Unkown internal scanner error",
+    "Error: could not match input",
+    "Error: pushback value was too large"
+  };
+
+  /**
+   * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+   */
+  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+  private static final String ZZ_ATTRIBUTE_PACKED_0 =
+    "\1\0\1\11\1\1\3\11\1\1\1\11\1\1\3\11"+
+    "\14\1\6\11\16\0\1\1\1\0\1\11\1\0\1\11"+
+    "\2\0\2\11\1\0\2\11\2\0\3\11\2\0\1\11"+
+    "\1\0\3\11\1\0\1\11";
+
+  private static int [] zzUnpackAttribute() {
+    int [] result = new int[70];
+    int offset = 0;
+    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+  /** the input device */
+  private java.io.Reader zzReader;
+
+  /** the current state of the DFA */
+  private int zzState;
+
+  /** the current lexical state */
+  private int zzLexicalState = YYINITIAL;
+
+  /** this buffer contains the current text to be matched and is
+      the source of the yytext() string */
+  private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
+
+  /** the textposition at the last accepting state */
+  private int zzMarkedPos;
+
+  /** the current text position in the buffer */
+  private int zzCurrentPos;
+
+  /** startRead marks the beginning of the yytext() string in the buffer */
+  private int zzStartRead;
+
+  /** endRead marks the last character in the buffer, that has been read
+      from input */
+  private int zzEndRead;
+
+  /** number of newlines encountered up to the start of the matched text */
+  private int yyline;
+
+  /** the number of characters up to the start of the matched text */
+  private int yychar;
+
+  /**
+   * the number of characters from the last newline up to the start of the 
+   * matched text
+   */
+  private int yycolumn;
+
+  /** 
+   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+   */
+  private boolean zzAtBOL = true;
+
+  /** zzAtEOF == true <=> the scanner is at the EOF */
+  private boolean zzAtEOF;
+
+  /* user code: */
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 4939 $
+  protected SymbolFactory sf;
+
+  public Scanner(InputStream r, SymbolFactory sf){
+    this(r);
+    this.sf = sf;
+  }
+
+
+  /**
+   * Creates a new scanner
+   * There is also a java.io.InputStream version of this constructor.
+   *
+   * @param   in  the java.io.Reader to read input from.
+   */
+  public Scanner(java.io.Reader in) {
+    this.zzReader = in;
+  }
+
+  /**
+   * Creates a new scanner.
+   * There is also java.io.Reader version of this constructor.
+   *
+   * @param   in  the java.io.Inputstream to read input from.
+   */
+  public Scanner(java.io.InputStream in) {
+    this(new java.io.InputStreamReader(in));
+  }
+
+
+  /**
+   * Refills the input buffer.
+   *
+   * @return      <code>false</code>, iff there was new input.
+   * 
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  private boolean zzRefill() throws java.io.IOException {
+
+    /* first: make room (if you can) */
+    if (zzStartRead > 0) {
+      System.arraycopy(zzBuffer, zzStartRead,
+                       zzBuffer, 0,
+                       zzEndRead-zzStartRead);
+
+      /* translate stored positions */
+      zzEndRead-= zzStartRead;
+      zzCurrentPos-= zzStartRead;
+      zzMarkedPos-= zzStartRead;
+      zzStartRead = 0;
+    }
+
+    /* is the buffer big enough? */
+    if (zzCurrentPos >= zzBuffer.length) {
+      /* if not: blow it up */
+      char newBuffer[] = new char[zzCurrentPos*2];
+      System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
+      zzBuffer = newBuffer;
+    }
+
+    /* finally: fill the buffer with new input */
+    int numRead = zzReader.read(zzBuffer, zzEndRead,
+                                            zzBuffer.length-zzEndRead);
+
+    if (numRead > 0) {
+      zzEndRead+= numRead;
+      return false;
+    }
+    // unlikely but not impossible: read 0 characters, but not at end of stream    
+    if (numRead == 0) {
+      int c = zzReader.read();
+      if (c == -1) {
+        return true;
+      } else {
+        zzBuffer[zzEndRead++] = (char) c;
+        return false;
+      }     
+    }
+
+	// numRead < 0
+    return true;
+  }
+
+    
+  /**
+   * Closes the input stream.
+   */
+  public final void yyclose() throws java.io.IOException {
+    zzAtEOF = true;            /* indicate end of file */
+    zzEndRead = zzStartRead;  /* invalidate buffer    */
+
+    if (zzReader != null)
+      zzReader.close();
+  }
+
+
+  /**
+   * Resets the scanner to read from a new input stream.
+   * Does not close the old reader.
+   *
+   * All internal variables are reset, the old input stream 
+   * <b>cannot</b> be reused (internal buffer is discarded and lost).
+   * Lexical state is set to <tt>ZZ_INITIAL</tt>.
+   *
+   * @param reader   the new input stream 
+   */
+  public final void yyreset(java.io.Reader reader) {
+    zzReader = reader;
+    zzAtBOL  = true;
+    zzAtEOF  = false;
+    zzEndRead = zzStartRead = 0;
+    zzCurrentPos = zzMarkedPos = 0;
+    yyline = yychar = yycolumn = 0;
+    zzLexicalState = YYINITIAL;
+  }
+
+
+  /**
+   * Returns the current lexical state.
+   */
+  public final int yystate() {
+    return zzLexicalState;
+  }
+
+
+  /**
+   * Enters a new lexical state
+   *
+   * @param newState the new lexical state
+   */
+  public final void yybegin(int newState) {
+    zzLexicalState = newState;
+  }
+
+
+  /**
+   * Returns the text matched by the current regular expression.
+   */
+  public final String yytext() {
+    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+  }
+
+
+  /**
+   * Returns the character at position <tt>pos</tt> from the 
+   * matched text. 
+   * 
+   * It is equivalent to yytext().charAt(pos), but faster
+   *
+   * @param pos the position of the character to fetch. 
+   *            A value from 0 to yylength()-1.
+   *
+   * @return the character at position pos
+   */
+  public final char yycharat(int pos) {
+    return zzBuffer[zzStartRead+pos];
+  }
+
+
+  /**
+   * Returns the length of the matched text region.
+   */
+  public final int yylength() {
+    return zzMarkedPos-zzStartRead;
+  }
+
+
+  /**
+   * Reports an error that occured while scanning.
+   *
+   * In a wellformed scanner (no or only correct usage of 
+   * yypushback(int) and a match-all fallback rule) this method 
+   * will only be called with things that "Can't Possibly Happen".
+   * If this method is called, something is seriously wrong
+   * (e.g. a JFlex bug producing a faulty scanner etc.).
+   *
+   * Usual syntax/scanner level error handling should be done
+   * in error fallback rules.
+   *
+   * @param   errorCode  the code of the errormessage to display
+   */
+  private void zzScanError(int errorCode) {
+    String message;
+    try {
+      message = ZZ_ERROR_MSG[errorCode];
+    }
+    catch (ArrayIndexOutOfBoundsException e) {
+      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+    }
+
+    throw new Error(message);
+  } 
+
+
+  /**
+   * Pushes the specified amount of characters back into the input stream.
+   *
+   * They will be read again by then next call of the scanning method
+   *
+   * @param number  the number of characters to be read again.
+   *                This number must not be greater than yylength()!
+   */
+  public void yypushback(int number)  {
+    if ( number > yylength() )
+      zzScanError(ZZ_PUSHBACK_2BIG);
+
+    zzMarkedPos -= number;
+  }
+
+
+  /**
+   * Resumes scanning until the next regular expression is matched,
+   * the end of input is encountered or an I/O-Error occurs.
+   *
+   * @return      the next token
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  public java_cup.runtime.Symbol next_token() throws java.io.IOException {
+    int zzInput;
+    int zzAction;
+
+    // cached fields:
+    int zzCurrentPosL;
+    int zzMarkedPosL;
+    int zzEndReadL = zzEndRead;
+    char [] zzBufferL = zzBuffer;
+    char [] zzCMapL = ZZ_CMAP;
+
+    int [] zzTransL = ZZ_TRANS;
+    int [] zzRowMapL = ZZ_ROWMAP;
+    int [] zzAttrL = ZZ_ATTRIBUTE;
+
+    while (true) {
+      zzMarkedPosL = zzMarkedPos;
+
+      zzAction = -1;
+
+      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+  
+      zzState = ZZ_LEXSTATE[zzLexicalState];
+
+
+      zzForAction: {
+        while (true) {
+    
+          if (zzCurrentPosL < zzEndReadL)
+            zzInput = zzBufferL[zzCurrentPosL++];
+          else if (zzAtEOF) {
+            zzInput = YYEOF;
+            break zzForAction;
+          }
+          else {
+            // store back cached positions
+            zzCurrentPos  = zzCurrentPosL;
+            zzMarkedPos   = zzMarkedPosL;
+            boolean eof = zzRefill();
+            // get translated positions and possibly new buffer
+            zzCurrentPosL  = zzCurrentPos;
+            zzMarkedPosL   = zzMarkedPos;
+            zzBufferL      = zzBuffer;
+            zzEndReadL     = zzEndRead;
+            if (eof) {
+              zzInput = YYEOF;
+              break zzForAction;
+            }
+            else {
+              zzInput = zzBufferL[zzCurrentPosL++];
+            }
+          }
+          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+          if (zzNext == -1) break zzForAction;
+          zzState = zzNext;
+
+          int zzAttributes = zzAttrL[zzState];
+          if ( (zzAttributes & 1) == 1 ) {
+            zzAction = zzState;
+            zzMarkedPosL = zzCurrentPosL;
+            if ( (zzAttributes & 8) == 8 ) break zzForAction;
+          }
+
+        }
+      }
+
+      // store back cached position
+      zzMarkedPos = zzMarkedPosL;
+
+      switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+        case 11: 
+          { return sf.newSymbol("Or", sym.OR);
+          }
+        case 34: break;
+        case 24: 
+          { return sf.newSymbol("Sin", sym.SIN);
+          }
+        case 35: break;
+        case 23: 
+          { return sf.newSymbol("Log", sym.LOG);
+          }
+        case 36: break;
+        case 16: 
+          { return sf.newSymbol("Left Bracket", sym.LPAREN);
+          }
+        case 37: break;
+        case 27: 
+          { return sf.newSymbol("True", sym.TRUE);
+          }
+        case 38: break;
+        case 1: 
+          { System.err.println("Illegal character: "+yytext());
+          }
+        case 39: break;
+        case 29: 
+          { return sf.newSymbol("Sqrt", sym.SQRT);
+          }
+        case 40: break;
+        case 30: 
+          { return sf.newSymbol("Ceil", sym.CEIL);
+          }
+        case 41: break;
+        case 31: 
+          { return sf.newSymbol("False", sym.FALSE);
+          }
+        case 42: break;
+        case 18: 
+          { return sf.newSymbol("Less or equal than", sym.LE);
+          }
+        case 43: break;
+        case 17: 
+          { return sf.newSymbol("Right Bracket", sym.RPAREN);
+          }
+        case 44: break;
+        case 26: 
+          { return sf.newSymbol("Cos", sym.COS);
+          }
+        case 45: break;
+        case 21: 
+          { return sf.newSymbol("Exp", sym.EXP);
+          }
+        case 46: break;
+        case 12: 
+          { return sf.newSymbol("Number", sym.NUMBER, new Double(yytext()));
+          }
+        case 47: break;
+        case 3: 
+          { return sf.newSymbol("Plus", sym.PLUS);
+          }
+        case 48: break;
+        case 13: 
+          { return sf.newSymbol("Variable", sym.VARIABLE, new String(yytext()));
+          }
+        case 49: break;
+        case 22: 
+          { return sf.newSymbol("Abs", sym.ABS);
+          }
+        case 50: break;
+        case 20: 
+          { return sf.newSymbol("Tan", sym.TAN);
+          }
+        case 51: break;
+        case 15: 
+          { return sf.newSymbol("Comma", sym.COMMA);
+          }
+        case 52: break;
+        case 33: 
+          { return sf.newSymbol("IfElse", sym.IFELSE);
+          }
+        case 53: break;
+        case 28: 
+          { return sf.newSymbol("Rint", sym.RINT);
+          }
+        case 54: break;
+        case 8: 
+          { return sf.newSymbol("Greater than", sym.GT);
+          }
+        case 55: break;
+        case 19: 
+          { return sf.newSymbol("Greater or equal than", sym.GE);
+          }
+        case 56: break;
+        case 5: 
+          { return sf.newSymbol("Division", sym.DIVISION);
+          }
+        case 57: break;
+        case 7: 
+          { return sf.newSymbol("Equals", sym.EQ);
+          }
+        case 58: break;
+        case 10: 
+          { return sf.newSymbol("And", sym.AND);
+          }
+        case 59: break;
+        case 9: 
+          { return sf.newSymbol("Not", sym.NOT);
+          }
+        case 60: break;
+        case 4: 
+          { return sf.newSymbol("Times", sym.TIMES);
+          }
+        case 61: break;
+        case 32: 
+          { return sf.newSymbol("Floor", sym.FLOOR);
+          }
+        case 62: break;
+        case 6: 
+          { return sf.newSymbol("Less than", sym.LT);
+          }
+        case 63: break;
+        case 25: 
+          { return sf.newSymbol("Pow", sym.POW);
+          }
+        case 64: break;
+        case 14: 
+          { /* ignore white space. */
+          }
+        case 65: break;
+        case 2: 
+          { return sf.newSymbol("Minus", sym.MINUS);
+          }
+        case 66: break;
+        default: 
+          if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+            zzAtEOF = true;
+              {     return sf.newSymbol("EOF",sym.EOF);
+ }
+          } 
+          else {
+            zzScanError(ZZ_NO_MATCH);
+          }
+      }
+    }
+  }
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.jflex
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.jflex	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/Scanner.jflex	(revision 29)
@@ -0,0 +1,98 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.mathematicalexpression;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for mathematical expressions.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+%%
+%cup
+%public
+%class Scanner
+%{
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 4939 $
+  protected SymbolFactory sf;
+
+  public Scanner(InputStream r, SymbolFactory sf){
+    this(r);
+    this.sf = sf;
+  }
+%}
+%eofval{
+    return sf.newSymbol("EOF",sym.EOF);
+%eofval}
+
+%%
+// operands
+"-" { return sf.newSymbol("Minus", sym.MINUS); }
+"+" { return sf.newSymbol("Plus", sym.PLUS); }
+"*" { return sf.newSymbol("Times", sym.TIMES); }
+"/" { return sf.newSymbol("Division", sym.DIVISION); }
+
+// boolean stuff
+"<" { return sf.newSymbol("Less than", sym.LT); }
+"<=" { return sf.newSymbol("Less or equal than", sym.LE); }
+">" { return sf.newSymbol("Greater than", sym.GT); }
+">=" { return sf.newSymbol("Greater or equal than", sym.GE); }
+"=" { return sf.newSymbol("Equals", sym.EQ); }
+"!" { return sf.newSymbol("Not", sym.NOT); }
+"&" { return sf.newSymbol("And", sym.AND); }
+"|" { return sf.newSymbol("Or", sym.OR); }
+"true" { return sf.newSymbol("True", sym.TRUE); }
+"false" { return sf.newSymbol("False", sym.FALSE); }
+
+// functions
+"abs" { return sf.newSymbol("Abs", sym.ABS); }
+"sqrt" { return sf.newSymbol("Sqrt", sym.SQRT); }
+"log" { return sf.newSymbol("Log", sym.LOG); }
+"exp" { return sf.newSymbol("Exp", sym.EXP); }
+"sin" { return sf.newSymbol("Sin", sym.SIN); }
+"cos" { return sf.newSymbol("Cos", sym.COS); }
+"tan" { return sf.newSymbol("Tan", sym.TAN); }
+"rint" { return sf.newSymbol("Rint", sym.RINT); }
+"floor" { return sf.newSymbol("Floor", sym.FLOOR); }
+"pow" { return sf.newSymbol("Pow", sym.POW); }
+"ceil" { return sf.newSymbol("Ceil", sym.CEIL); }
+"ifelse" { return sf.newSymbol("IfElse", sym.IFELSE); }
+
+// numbers and variables
+[0-9][0-9]*\.?[0-9]* { return sf.newSymbol("Number", sym.NUMBER, new Double(yytext())); }
+-[0-9][0-9]*\.?[0-9]* { return sf.newSymbol("Number", sym.NUMBER, new Double(yytext())); }
+[A-Z]+ { return sf.newSymbol("Variable", sym.VARIABLE, new String(yytext())); }
+
+// whitespaces
+[ \r\n\t\f] { /* ignore white space. */ }
+
+// various
+"," { return sf.newSymbol("Comma", sym.COMMA); }
+"(" { return sf.newSymbol("Left Bracket", sym.LPAREN); }
+")" { return sf.newSymbol("Right Bracket", sym.RPAREN); }
+
+// catch all
+. { System.err.println("Illegal character: "+yytext()); }
Index: branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/sym.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/sym.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/mathematicalexpression/sym.java	(revision 29)
@@ -0,0 +1,47 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Sun Jan 11 11:14:12 NZDT 2009
+//----------------------------------------------------
+
+package weka.core.mathematicalexpression;
+
+/** CUP generated interface containing symbol constants. */
+public interface sym {
+  /* terminals */
+  public static final int TIMES = 7;
+  public static final int AND = 29;
+  public static final int IFELSE = 20;
+  public static final int LT = 23;
+  public static final int SIN = 13;
+  public static final int PLUS = 6;
+  public static final int OR = 30;
+  public static final int RPAREN = 4;
+  public static final int COS = 14;
+  public static final int DIVISION = 8;
+  public static final int NOT = 28;
+  public static final int POW = 18;
+  public static final int SQRT = 10;
+  public static final int VARIABLE = 33;
+  public static final int TRUE = 21;
+  public static final int FLOOR = 17;
+  public static final int RINT = 16;
+  public static final int GT = 25;
+  public static final int CEIL = 19;
+  public static final int LPAREN = 3;
+  public static final int LE = 24;
+  public static final int LOG = 11;
+  public static final int EXP = 12;
+  public static final int BOOLEAN = 32;
+  public static final int COMMA = 2;
+  public static final int NUMBER = 31;
+  public static final int EOF = 0;
+  public static final int ABS = 9;
+  public static final int GE = 26;
+  public static final int FALSE = 22;
+  public static final int MINUS = 5;
+  public static final int error = 1;
+  public static final int EQ = 27;
+  public static final int TAN = 15;
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/matrix/CholeskyDecomposition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/CholeskyDecomposition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/CholeskyDecomposition.java	(revision 29)
@@ -0,0 +1,162 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * CholeskyDecomposition.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/** 
+ * Cholesky Decomposition.
+ * <P>
+ * For a symmetric, positive definite matrix A, the Cholesky decomposition is
+ * an lower triangular matrix L so that A = L*L'.
+ * <P>
+ * If the matrix is not symmetric or positive definite, the constructor
+ * returns a partial decomposition and sets an internal flag that may
+ * be queried by the isSPD() method.
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class CholeskyDecomposition 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8739775942782694701L;
+
+  /** 
+   * Array for internal storage of decomposition.
+   * @serial internal array storage.
+   */
+  private double[][] L;
+
+  /** 
+   * Row and column dimension (square matrix).
+   * @serial matrix dimension.
+   */
+  private int n;
+
+  /** 
+   * Symmetric and positive definite flag.
+   * @serial is symmetric and positive definite flag.
+   */
+  private boolean isspd;
+
+  /** 
+   * Cholesky algorithm for symmetric and positive definite matrix.
+   *
+   * @param  Arg   Square, symmetric matrix.
+   */
+  public CholeskyDecomposition(Matrix Arg) {
+    // Initialize.
+    double[][] A = Arg.getArray();
+    n = Arg.getRowDimension();
+    L = new double[n][n];
+    isspd = (Arg.getColumnDimension() == n);
+    // Main loop.
+    for (int j = 0; j < n; j++) {
+      double[] Lrowj = L[j];
+      double d = 0.0;
+      for (int k = 0; k < j; k++) {
+        double[] Lrowk = L[k];
+        double s = 0.0;
+        for (int i = 0; i < k; i++) {
+          s += Lrowk[i]*Lrowj[i];
+        }
+        Lrowj[k] = s = (A[j][k] - s)/L[k][k];
+        d = d + s*s;
+        isspd = isspd & (A[k][j] == A[j][k]); 
+      }
+      d = A[j][j] - d;
+      isspd = isspd & (d > 0.0);
+      L[j][j] = Math.sqrt(Math.max(d,0.0));
+      for (int k = j+1; k < n; k++) {
+        L[j][k] = 0.0;
+      }
+    }
+  }
+
+  /** 
+   * Is the matrix symmetric and positive definite?
+   * @return     true if A is symmetric and positive definite.
+   */
+  public boolean isSPD() {
+    return isspd;
+  }
+
+  /** 
+   * Return triangular factor.
+   * @return     L
+   */
+  public Matrix getL() {
+    return new Matrix(L,n,n);
+  }
+
+  /** 
+   * Solve A*X = B
+   * @param  B   A Matrix with as many rows as A and any number of columns.
+   * @return     X so that L*L'*X = B
+   * @exception  IllegalArgumentException  Matrix row dimensions must agree.
+   * @exception  RuntimeException  Matrix is not symmetric positive definite.
+   */
+  public Matrix solve(Matrix B) {
+    if (B.getRowDimension() != n) {
+      throw new IllegalArgumentException("Matrix row dimensions must agree.");
+    }
+    if (!isspd) {
+      throw new RuntimeException("Matrix is not symmetric positive definite.");
+    }
+
+    // Copy right hand side.
+    double[][] X = B.getArrayCopy();
+    int nx = B.getColumnDimension();
+
+    // Solve L*Y = B;
+    for (int k = 0; k < n; k++) {
+      for (int j = 0; j < nx; j++) {
+	for (int i = 0; i < k ; i++) {
+	  X[k][j] -= X[i][j]*L[k][i];
+	}
+	X[k][j] /= L[k][k];
+      }
+    }
+
+    // Solve L'*X = Y;
+    for (int k = n-1; k >= 0; k--) {
+      for (int j = 0; j < nx; j++) {
+	for (int i = k+1; i < n ; i++) {
+	  X[k][j] -= X[i][j]*L[i][k];
+	}
+	X[k][j] /= L[k][k];
+      }
+    }
+
+    return new Matrix(X,n,nx);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/DoubleVector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/DoubleVector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/DoubleVector.java	(revision 29)
@@ -0,0 +1,749 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    DoubleVector.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * A vector specialized on doubles.
+ * 
+ * @author Yong Wang
+ * @version $Revision: 5953 $
+ */
+public class  DoubleVector
+  implements Cloneable, RevisionHandler {
+
+  double[] V; // array for internal storage of elements.
+
+  private int  sizeOfVector;      // size of the vector
+  
+  /* ------------------------
+     Constructors
+     * ------------------------ */
+
+  /** Constructs a null vector.
+   */
+  public DoubleVector() {
+    this( 0 );
+  }
+    
+  /** Constructs an n-vector of zeros. 
+      @param n    length.
+  */
+  public DoubleVector( int n ){
+    V = new double[ n ];
+    setSize( n );
+  }
+    
+  /** Constructs a constant n-vector.
+      @param n    length.
+      @param s    the scalar value used to fill the vector
+  */
+  public DoubleVector( int n, double s ){
+    this( n );
+    set( s );
+  }
+    
+  /** Constructs a vector directly from a double array
+   *  @param v   the array
+   */
+  public DoubleVector( double v[] ){
+    if( v == null ) {
+      V = new double[0];
+      setSize( 0 );
+    }
+    else {
+      V = v;
+      setSize( v.length );
+    }
+  }
+    
+  /* ------------------------
+   *  Public Methods
+   * ------------------------ */
+    
+  /** Set a single element.
+   *  @param i    Index.
+   *  @param s    a[i].
+   */
+  public void  set( int i, double s ) {
+    
+    V[i] = s;
+  }
+    
+  /** Set all elements to a value
+   *  @param s    the value
+   */
+  public void  set( double s ) {
+    set(0, size()-1, s);
+  }
+
+  /** Set some elements to a value
+   *  @param i0 the index of the first element
+   *  @param i1 the index of the second element
+   *  @param s the value 
+   */
+  public void set( int i0, int i1, double s ) {
+
+    for(int i = i0; i <= i1; i++ )
+      V[i] = s;
+  }
+
+  /** Set some elements using a 2-D array
+   *  @param i0 the index of the first element
+   *  @param i1 the index of the second element
+   *  @param j0 the index of the starting element in the 2-D array
+   *  @param v the values
+   */
+  public void  set( int i0, int i1, double [] v, int j0){
+    for(int i = i0; i<= i1; i++)
+      V[i] = v[j0 + i - i0];
+  }
+    
+  /** Set the elements using a DoubleVector
+   *  @param v the DoubleVector
+   */
+  public void  set( DoubleVector v ){
+    set( 0, v.size() - 1, v, 0);
+  }
+  
+  /** Set some elements using a DoubleVector.
+   *  @param i0 the index of the first element
+   *  @param i1 the index of the second element
+   *  @param v the DoubleVector
+   *  @param j0 the index of the starting element in the DoubleVector
+   */
+  public void  set( int i0, int i1, DoubleVector v, int j0){
+    for(int i = i0; i<= i1; i++)
+      V[i] = v.V[j0 + i - i0];
+  }
+  
+  /** Access the internal one-dimensional array.
+      @return     Pointer to the one-dimensional array of vector elements.
+  */
+  public double []  getArray() {
+    return V;
+  }
+    
+  void  setArray( double [] a ) {
+    V = a;
+  }
+
+  /** Returns a copy of the DoubleVector usng a double array.
+      @return the one-dimensional array.  */
+  public double[] getArrayCopy() {
+    double v[] = new double[size()];
+    
+    for(int i= 0; i < size(); i++ ) 
+      v[i] = V[i];
+    
+    return v;
+  }
+    
+  /** Sorts the array in place */
+  public void  sort() {
+    Arrays.sort( V, 0, size() );
+  }
+
+  /** Sorts the array in place with index returned */
+  public IntVector  sortWithIndex() {
+    IntVector index = IntVector.seq( 0, size()-1 );
+    sortWithIndex( 0, size()-1, index );
+    return index;
+  }
+  
+  /** Sorts the array in place with index changed 
+   *  @param xi   first index
+   *  @param xj   last index
+   *  @param index array that stores all indices
+   */
+  public void  sortWithIndex( int xi, int xj, IntVector index ) {
+    if( xi < xj ) { 
+      double x, f, k;
+      int xm = (int) (xi + xj) / 2; // median index
+      x = Math.min( V[xi],             // median of three
+		    Math.max( V[xm], V[xj])); 
+      int i = xi;
+      int j = xj;
+      while( i < j ) {
+	while( V[i] < x && i < xj ) i++;
+	while( V[j] > x && j > xi ) j--;
+	if( i <= j ){
+	  swap(i, j);
+	  index.swap(i, j);
+	  i++;
+	  j--;
+	}
+      }
+      sortWithIndex(xi, j, index);
+      sortWithIndex(i, xj, index);
+    }
+  }
+  
+  /** Gets the size of the vector.
+      @return     the size
+  */
+  public int  size(){
+    return sizeOfVector;
+  }
+    
+  /** 
+   *  Sets the size of the vector
+   *  @param m the size
+   */ 
+  public void  setSize( int m ){
+    if( m > capacity() ) 
+      throw new IllegalArgumentException("insufficient capacity");
+    sizeOfVector = m;
+  }
+    
+  /** Gets the capacity of the vector.
+   *  @return     the capacity.
+   */
+  public int  capacity() {
+    if( V == null ) return 0;
+    return V.length;
+  } 
+
+  /** Sets the capacity of the vector
+   *  @param n the capacity.  
+   */
+  public void  setCapacity ( int n ) {
+    if( n == capacity() ) return;
+    double [] oldV = V;
+    int m = Math.min( n, size() );
+    V = new double[ n ];
+    setSize( m );
+    set(0, m-1, oldV, 0);
+  }
+
+  /** Gets a single element.
+   *  @param i    Index.
+   *  @return     the value of the i-th element
+   */
+  public double  get( int i ) {
+    return V[i];
+  }
+    
+  /** 
+   *  Adds a value to an element 
+   *  @param i  the index of the element 
+   *  @param s the value
+   */
+  public void  setPlus( int i, double s ) {
+    V[i] += s;
+  }
+    
+  /** 
+   *  Multiplies a value to an element 
+   *  @param i  the index of the element 
+   *  @param s the value
+   */
+  public void  setTimes( int i, double s ) {
+    V[i] *= s;
+  }
+    
+  /**
+   *  Adds an element into the vector
+   *  @param x  the value of the new element
+   */
+  public void addElement( double x ) {
+    if( capacity() == 0 ) setCapacity( 10 );
+    if( size() == capacity() ) setCapacity( 2 * capacity() );
+    V[size()] = x;
+    setSize( size() + 1 );
+  }
+  
+  /**
+   *  Returns the squared vector 
+   */
+  public DoubleVector square() {
+    DoubleVector v = new DoubleVector( size() ); 
+    for(int i = 0; i < size(); i++ ) v.V[i] = V[i] * V[i];
+    return v;
+  }
+
+  /**
+   *  Returns the square-root of all the elements in the vector 
+   */
+  public DoubleVector sqrt() {
+    DoubleVector v = new DoubleVector( size() ); 
+    for(int i = 0; i < size(); i++ ) v.V[i] = Math.sqrt(V[i]);
+    return v;
+  }
+
+  /** Makes a deep copy of the vector
+   */
+  public DoubleVector  copy() { 
+    return (DoubleVector) clone();
+  }
+    
+  /** Clones the DoubleVector object.
+   */
+  public Object  clone() { 
+    int n = size();
+    DoubleVector u = new DoubleVector( n );
+    for( int i = 0; i < n; i++) 
+      u.V[i] = V[i];
+    return u;
+  }
+    
+  /** 
+   * Returns the inner product of two DoubleVectors
+   * @param v the second DoubleVector
+   * @return the product
+   */
+  public double  innerProduct(DoubleVector v) {
+    if(size() != v.size()) 
+      throw new IllegalArgumentException("sizes unmatch");
+    double p = 0;
+    for (int i = 0; i < size(); i++) {
+      p += V[i] * v.V[i];
+    }
+    return p;
+  }
+    
+  /** 
+   * Returns the signs of all elements in terms of -1, 0 and +1.
+   */
+  public DoubleVector sign() 
+  {
+    DoubleVector s = new DoubleVector( size() );
+    for( int i = 0; i < size(); i++ ) {
+      if( V[i] > 0 ) s.V[i] = 1;
+      else if( V[i] < 0 ) s.V[i] = -1;
+      else s.V[i] = 0;
+    }
+    return s;
+  } 
+
+  /** Returns the sum of all elements in the vector.
+   */
+  public double  sum() 
+  {
+    double s = 0;
+    for( int i=0; i< size(); i++) s += V[i];
+    return s;
+  }
+    
+  /** Returns the squared sum of all elements in the vector.
+   */
+  public double  sum2()
+  {
+    double s2 = 0;
+    for( int i=0; i< size(); i++) s2 += V[i] * V[i];
+    return s2;
+  }
+  
+  /** Returns the L1-norm of the vector
+   */
+  public double norm1()
+  {
+    double s = 0;
+    for( int i=0; i< size(); i++) s += Math.abs(V[i]);
+    return s;
+  }
+  
+  /** Returns the L2-norm of the vector
+   */
+  public double norm2()
+  {
+    return Math.sqrt( sum2() );
+  }
+
+  /** Returns ||u-v||^2
+   *  @param v the second vector
+   */
+  public double sum2( DoubleVector v ) 
+  {
+    return minus( v ).sum2();
+  }
+  
+  /** Returns a subvector.
+   *  @param i0   the index of the first element
+   *  @param i1   the index of the last element
+   *  @return     v[i0:i1]
+   */
+  public DoubleVector  subvector( int i0, int i1 ) 
+  {
+    DoubleVector v = new DoubleVector( i1-i0+1 );
+    v.set(0, i1 - i0, this, i0);
+    return v;
+  }
+  
+  /** Returns a subvector.
+   *  @param index stores the indices of the needed elements
+   *  @return     v[index]
+   */
+  public DoubleVector  subvector( IntVector index ) {
+    DoubleVector v = new DoubleVector( index.size() );
+    for( int i = 0; i < index.size(); i++ )
+      v.V[i] = V[index.V[i]];
+    return v;
+  }
+
+  /** Returns a vector from the pivoting indices. Elements not indexed are
+   *  set to zero.
+   *  @param index stores the pivoting indices
+   *  @param length the total number of the potential elements
+   *  @return the subvector */
+  public DoubleVector  unpivoting( IntVector index, int length ) {
+    if( index.size() > length ) 
+      throw new IllegalArgumentException("index.size() > length ");
+    DoubleVector u = new DoubleVector( length );
+    for( int i = 0; i < index.size(); i++ ) {
+      u.V[index.V[i]] =  V[i];
+    }
+    return u;
+  }
+  
+  /** Adds a value to all the elements 
+   *  @param x the value
+   */
+  public DoubleVector  plus ( double x ) {
+    return copy().plusEquals( x );	
+  }
+  
+  /** Adds a value to all the elements in place
+   *  @param x the value
+   */
+  public DoubleVector plusEquals ( double x ) {
+    for( int i = 0; i < size(); i++ )
+      V[i] += x;
+    return this;
+  }
+  
+  /** 
+   *  Adds another vector element by element 
+   *  @param v the second vector
+   */
+  public DoubleVector  plus( DoubleVector v ) {
+    return copy().plusEquals( v );
+  }
+  
+  /** 
+   *  Adds another vector in place element by element 
+   *  @param v the second vector
+   */
+  public DoubleVector  plusEquals( DoubleVector v ) {
+    for(int i = 0; i < size(); i++ )
+      V[i] += v.V[i];
+    return this;
+  }
+  
+  /** 
+   *  Subtracts a value
+   *  @param x the value
+   */
+  public DoubleVector  minus( double x ) {
+    return plus( -x );
+  }
+  
+  /** 
+   *  Subtracts a value in place
+   *  @param x the value
+   */
+  public DoubleVector  minusEquals( double x ) {
+    plusEquals( -x );
+    return this;
+  }
+  
+  /** 
+   *  Subtracts another DoubleVector element by element 
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  minus( DoubleVector v ) {
+    return copy().minusEquals( v );
+  }
+  
+  /** 
+   *  Subtracts another DoubleVector element by element in place
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  minusEquals( DoubleVector v ) {
+    for(int i = 0; i < size(); i++ )
+      V[i] -=  v.V[i];
+    return this;
+  }
+    
+  /** Multiplies a scalar
+      @param s    scalar
+      @return     s * v
+  */
+  public DoubleVector  times( double s ) {
+    return copy().timesEquals( s );
+  }
+    
+  /** Multiply a vector by a scalar in place, u = s * u
+      @param s    scalar
+      @return     replace u by s * u
+  */
+  public DoubleVector  timesEquals( double s ) {
+    for (int i = 0; i < size(); i++) {
+      V[i] *= s;
+    }
+    return this;
+  }
+    
+  /** 
+   *  Multiplies another DoubleVector element by element
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  times( DoubleVector v ) {
+    return copy().timesEquals( v ); 
+    
+  }
+    
+  /** 
+   *  Multiplies another DoubleVector element by element in place 
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  timesEquals( DoubleVector v ) {
+    for(int i = 0; i < size(); i++ )
+      V[i] *= v.V[i];
+    return this;
+  }
+  
+  /** 
+   *  Divided by another DoubleVector element by element
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  dividedBy ( DoubleVector v ) {
+    return copy().dividedByEquals( v );
+  }
+  
+  /** 
+   *  Divided by another DoubleVector element by element in place 
+   *  @param v the second DoubleVector
+   */
+  public DoubleVector  dividedByEquals ( DoubleVector v ) {
+    for( int i = 0; i < size(); i++ ) {
+      V[i] /= v.V[i];
+    }
+    return this;
+  }
+  
+  /** 
+   *  Checks if it is an empty vector
+   */
+  public boolean  isEmpty() {
+    if( size() == 0 ) return true;
+    return false;
+  }
+  
+  /** 
+   * Returns a vector that stores the cumulated values of the original
+   * vector */
+  public DoubleVector cumulate() 
+  {
+    return copy().cumulateInPlace();
+  }
+	
+  /** 
+   * Cumulates the original vector in place 
+   */
+  public DoubleVector cumulateInPlace() 
+  {
+    for (int i = 1; i < size(); i++) {
+      V[i] += V[i-1];
+    }
+    return this;
+  }
+	
+  /** 
+   * Returns the index of the maximum. <p>
+   * If multiple maximums exist, the index of the first is returned.
+   */
+  public int  indexOfMax()
+  {
+    int index = 0;
+    double ma = V[0];
+
+    for( int i = 1; i < size(); i++ ){
+      if( ma < V[i] ) {
+	ma = V[i];
+	index = i;
+      }
+    }
+    return index;
+  }
+  
+
+  /** 
+   * Returns true if vector not sorted
+   */
+  public boolean unsorted () {
+    if( size() < 2 ) return false;
+    for( int i = 1; i < size(); i++ ) {
+      if( V[i-1] > V[i] )
+	return true;
+    }
+    return false;
+  }
+  
+  /**
+   *  Combine two vectors together
+   *  @param v the second vector
+   */
+  public DoubleVector  cat( DoubleVector v ) {
+    DoubleVector w = new DoubleVector( size() + v.size() );
+    w.set(0, size() - 1, this, 0);
+    w.set(size(), size() + v.size()-1, v, 0);
+    return w;
+  }
+    
+  /**
+   *  Swaps the values stored at i and j
+   *  @param i the index i
+   *  @param j the index j
+   */
+  public void  swap( int i, int j ){
+    if( i == j ) return;
+    double t = V[i];
+    V[i] = V[j];
+    V[j] = t;
+  }
+
+  /**
+   *  Returns the maximum value of all elements
+   */
+  public double max () {
+    if( size() < 1 ) throw new IllegalArgumentException("zero size");
+    double ma = V[0];
+    if( size() < 2 ) return ma;
+    for( int i = 1; i < size(); i++ ) {
+      if( V[i] > ma ) ma = V[i];
+    }
+    return ma;
+  }
+  
+
+  /**
+   *  Applies a method to the vector
+   *  @param className the class name
+   *  @param method the method
+   */
+  public DoubleVector map( String className, String method ) {
+    try {
+      Class<?> c = Class.forName( className );
+      Class [] cs = new Class[1]; 
+      cs[ 0 ] = Double.TYPE;
+      Method m = c.getMethod( method, cs );
+      
+      DoubleVector w = new DoubleVector( size() );
+      Object [] obj = new Object[1];
+      for( int i = 0; i < size(); i++ ) {
+	obj[0] = new Double( V[i] );
+	w.set( i, Double.parseDouble(m.invoke( null, obj ).toString()) ); 
+      }
+      return w;
+    }
+    catch ( Exception e ) {
+      e.printStackTrace();
+      System.exit(1);
+    }
+    return null;
+  }
+
+  /**
+   * Returns the reverse vector
+   */ 
+  public DoubleVector  rev() {
+    int n = size();
+    DoubleVector w = new DoubleVector( n );
+    for(int i = 0; i < n; i++ )
+      w.V[i] = V[n-i-1];
+    return w;
+  }
+  
+  /**
+   * Returns a random vector of uniform distribution
+   * @param n the size of the vector
+   */ 
+  public static DoubleVector  random( int n ) {
+    DoubleVector v = new DoubleVector( n );
+    for (int i = 0; i < n; i++) {
+      v.V[i] = Math.random();
+    }
+    return v;
+  }
+
+  /** Convert the DoubleVecor to a string
+   */ 
+  public String  toString() {
+    return toString( 5, false );
+  }
+    
+  /** Convert the DoubleVecor to a string
+   *  @param digits the number of digits after decimal point
+   *  @param trailing true if trailing zeros are to be shown
+   */ 
+  public String  toString( int digits, boolean trailing ) {
+    if( isEmpty() ) return "null vector";
+
+    StringBuffer text = new StringBuffer();
+    FlexibleDecimalFormat nf = new FlexibleDecimalFormat( digits, 
+							  trailing );
+    nf.grouping( true );
+    for( int i = 0; i < size(); i ++ ) nf.update( V[i] );
+    int count = 0;
+    int width = 80;
+    String number;
+    for( int i = 0; i < size(); i++ ) {
+      number = nf.format(V[i]);
+      count += 1 + number.length();
+      if( count > width-1 ) { 
+	text.append('\n'); 
+	count = 1 + number.length();
+      }
+      text.append( " " + number );
+    }
+	
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  public static void  main( String args[] ) {
+
+    
+    DoubleVector u = random(10);
+    DoubleVector v = random(10);
+    DoubleVector a = random(10);
+    DoubleVector w = a; 
+
+    System.out.println( random(10).plus(v).plus(w) );
+
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/matrix/EigenvalueDecomposition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/EigenvalueDecomposition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/EigenvalueDecomposition.java	(revision 29)
@@ -0,0 +1,987 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * EigenvalueDecomposition.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/** 
+ * Eigenvalues and eigenvectors of a real matrix. 
+ * <P>
+ * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is diagonal
+ * and the eigenvector matrix V is orthogonal.  I.e. A =
+ * V.times(D.times(V.transpose())) and V.times(V.transpose()) equals the
+ * identity matrix.
+ * <P>
+ * If A is not symmetric, then the eigenvalue matrix D is block diagonal with
+ * the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, lambda +
+ * i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda].  The columns of V
+ * represent the eigenvectors in the sense that A*V = V*D, i.e. A.times(V)
+ * equals V.times(D).  The matrix V may be badly conditioned, or even singular,
+ * so the validity of the equation A = V*D*inverse(V) depends upon V.cond().
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class EigenvalueDecomposition 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4011654467211422319L;
+
+  /** 
+   * Row and column dimension (square matrix).
+   * @serial matrix dimension.
+   */
+  private int n;
+
+  /** 
+   * Symmetry flag.
+   * @serial internal symmetry flag.
+   */
+  private boolean issymmetric;
+
+  /** 
+   * Arrays for internal storage of eigenvalues.
+   * @serial internal storage of eigenvalues.
+   */
+  private double[] d, e;
+
+  /** 
+   * Array for internal storage of eigenvectors.
+   * @serial internal storage of eigenvectors.
+   */
+  private double[][] V;
+
+  /** 
+   * Array for internal storage of nonsymmetric Hessenberg form.
+   * @serial internal storage of nonsymmetric Hessenberg form.
+   */
+  private double[][] H;
+
+  /** 
+   * Working storage for nonsymmetric algorithm.
+   * @serial working storage for nonsymmetric algorithm.
+   */
+  private double[] ort;
+
+  /**
+   * helper variables for the comples scalar division
+   * @see #cdiv(double,double,double,double)
+   */
+  private transient double cdivr, cdivi;
+
+  /** 
+   * Symmetric Householder reduction to tridiagonal form.
+   * <p/>
+   * This is derived from the Algol procedures tred2 by Bowdler, Martin,
+   * Reinsch, and Wilkinson, Handbook for Auto. Comp., Vol.ii-Linear Algebra,
+   * and the corresponding Fortran subroutine in EISPACK.
+   */
+  private void tred2() {
+
+    for (int j = 0; j < n; j++) {
+      d[j] = V[n-1][j];
+    }
+
+    // Householder reduction to tridiagonal form.
+
+    for (int i = n-1; i > 0; i--) {
+
+      // Scale to avoid under/overflow.
+
+      double scale = 0.0;
+      double h = 0.0;
+      for (int k = 0; k < i; k++) {
+        scale = scale + Math.abs(d[k]);
+      }
+      if (scale == 0.0) {
+        e[i] = d[i-1];
+        for (int j = 0; j < i; j++) {
+          d[j] = V[i-1][j];
+          V[i][j] = 0.0;
+          V[j][i] = 0.0;
+        }
+      } else {
+
+        // Generate Householder vector.
+
+        for (int k = 0; k < i; k++) {
+          d[k] /= scale;
+          h += d[k] * d[k];
+        }
+        double f = d[i-1];
+        double g = Math.sqrt(h);
+        if (f > 0) {
+          g = -g;
+        }
+        e[i] = scale * g;
+        h = h - f * g;
+        d[i-1] = f - g;
+        for (int j = 0; j < i; j++) {
+          e[j] = 0.0;
+        }
+
+        // Apply similarity transformation to remaining columns.
+
+        for (int j = 0; j < i; j++) {
+          f = d[j];
+          V[j][i] = f;
+          g = e[j] + V[j][j] * f;
+          for (int k = j+1; k <= i-1; k++) {
+            g += V[k][j] * d[k];
+            e[k] += V[k][j] * f;
+          }
+          e[j] = g;
+        }
+        f = 0.0;
+        for (int j = 0; j < i; j++) {
+          e[j] /= h;
+          f += e[j] * d[j];
+        }
+        double hh = f / (h + h);
+        for (int j = 0; j < i; j++) {
+          e[j] -= hh * d[j];
+        }
+        for (int j = 0; j < i; j++) {
+          f = d[j];
+          g = e[j];
+          for (int k = j; k <= i-1; k++) {
+            V[k][j] -= (f * e[k] + g * d[k]);
+          }
+          d[j] = V[i-1][j];
+          V[i][j] = 0.0;
+        }
+      }
+      d[i] = h;
+    }
+
+    // Accumulate transformations.
+
+    for (int i = 0; i < n-1; i++) {
+      V[n-1][i] = V[i][i];
+      V[i][i] = 1.0;
+      double h = d[i+1];
+      if (h != 0.0) {
+        for (int k = 0; k <= i; k++) {
+          d[k] = V[k][i+1] / h;
+        }
+        for (int j = 0; j <= i; j++) {
+          double g = 0.0;
+          for (int k = 0; k <= i; k++) {
+            g += V[k][i+1] * V[k][j];
+          }
+          for (int k = 0; k <= i; k++) {
+            V[k][j] -= g * d[k];
+          }
+        }
+      }
+      for (int k = 0; k <= i; k++) {
+        V[k][i+1] = 0.0;
+      }
+    }
+    for (int j = 0; j < n; j++) {
+      d[j] = V[n-1][j];
+      V[n-1][j] = 0.0;
+    }
+    V[n-1][n-1] = 1.0;
+    e[0] = 0.0;
+  } 
+
+  /** 
+   * Symmetric tridiagonal QL algorithm.
+   * <p/>
+   * This is derived from the Algol procedures tql2, by Bowdler, Martin,
+   * Reinsch, and Wilkinson, Handbook for Auto. Comp., Vol.ii-Linear Algebra,
+   * and the corresponding Fortran subroutine in EISPACK.
+   */
+  private void tql2() {
+
+    for (int i = 1; i < n; i++) {
+      e[i-1] = e[i];
+    }
+    e[n-1] = 0.0;
+
+    double f = 0.0;
+    double tst1 = 0.0;
+    double eps = Math.pow(2.0,-52.0);
+    for (int l = 0; l < n; l++) {
+
+      // Find small subdiagonal element
+
+      tst1 = Math.max(tst1,Math.abs(d[l]) + Math.abs(e[l]));
+      int m = l;
+      while (m < n) {
+        if (Math.abs(e[m]) <= eps*tst1) {
+          break;
+        }
+        m++;
+      }
+
+      // If m == l, d[l] is an eigenvalue,
+      // otherwise, iterate.
+
+      if (m > l) {
+        int iter = 0;
+        do {
+          iter = iter + 1;  // (Could check iteration count here.)
+
+          // Compute implicit shift
+
+          double g = d[l];
+          double p = (d[l+1] - g) / (2.0 * e[l]);
+          double r = Maths.hypot(p,1.0);
+          if (p < 0) {
+            r = -r;
+          }
+          d[l] = e[l] / (p + r);
+          d[l+1] = e[l] * (p + r);
+          double dl1 = d[l+1];
+          double h = g - d[l];
+          for (int i = l+2; i < n; i++) {
+            d[i] -= h;
+          }
+          f = f + h;
+
+          // Implicit QL transformation.
+
+          p = d[m];
+          double c = 1.0;
+          double c2 = c;
+          double c3 = c;
+          double el1 = e[l+1];
+          double s = 0.0;
+          double s2 = 0.0;
+          for (int i = m-1; i >= l; i--) {
+            c3 = c2;
+            c2 = c;
+            s2 = s;
+            g = c * e[i];
+            h = c * p;
+            r = Maths.hypot(p,e[i]);
+            e[i+1] = s * r;
+            s = e[i] / r;
+            c = p / r;
+            p = c * d[i] - s * g;
+            d[i+1] = h + s * (c * g + s * d[i]);
+
+            // Accumulate transformation.
+
+            for (int k = 0; k < n; k++) {
+              h = V[k][i+1];
+              V[k][i+1] = s * V[k][i] + c * h;
+              V[k][i] = c * V[k][i] - s * h;
+            }
+          }
+          p = -s * s2 * c3 * el1 * e[l] / dl1;
+          e[l] = s * p;
+          d[l] = c * p;
+
+          // Check for convergence.
+
+        } while (Math.abs(e[l]) > eps*tst1);
+      }
+      d[l] = d[l] + f;
+      e[l] = 0.0;
+    }
+
+    // Sort eigenvalues and corresponding vectors.
+
+    for (int i = 0; i < n-1; i++) {
+      int k = i;
+      double p = d[i];
+      for (int j = i+1; j < n; j++) {
+        if (d[j] < p) {
+          k = j;
+          p = d[j];
+        }
+      }
+      if (k != i) {
+        d[k] = d[i];
+        d[i] = p;
+        for (int j = 0; j < n; j++) {
+          p = V[j][i];
+          V[j][i] = V[j][k];
+          V[j][k] = p;
+        }
+      }
+    }
+  }
+
+  /**
+   * Nonsymmetric reduction to Hessenberg form.
+   * <p/>
+   * This is derived from the Algol procedures orthes and ortran, by Martin
+   * and Wilkinson, Handbook for Auto. Comp., Vol.ii-Linear Algebra, and the
+   * corresponding Fortran subroutines in EISPACK.
+   */
+  private void orthes() {
+
+    int low = 0;
+    int high = n-1;
+
+    for (int m = low+1; m <= high-1; m++) {
+
+      // Scale column.
+
+      double scale = 0.0;
+      for (int i = m; i <= high; i++) {
+        scale = scale + Math.abs(H[i][m-1]);
+      }
+      if (scale != 0.0) {
+
+        // Compute Householder transformation.
+
+        double h = 0.0;
+        for (int i = high; i >= m; i--) {
+          ort[i] = H[i][m-1]/scale;
+          h += ort[i] * ort[i];
+        }
+        double g = Math.sqrt(h);
+        if (ort[m] > 0) {
+          g = -g;
+        }
+        h = h - ort[m] * g;
+        ort[m] = ort[m] - g;
+
+        // Apply Householder similarity transformation
+        // H = (I-u*u'/h)*H*(I-u*u')/h)
+
+        for (int j = m; j < n; j++) {
+          double f = 0.0;
+          for (int i = high; i >= m; i--) {
+            f += ort[i]*H[i][j];
+          }
+          f = f/h;
+          for (int i = m; i <= high; i++) {
+            H[i][j] -= f*ort[i];
+          }
+        }
+
+        for (int i = 0; i <= high; i++) {
+          double f = 0.0;
+          for (int j = high; j >= m; j--) {
+            f += ort[j]*H[i][j];
+          }
+          f = f/h;
+          for (int j = m; j <= high; j++) {
+            H[i][j] -= f*ort[j];
+          }
+        }
+        ort[m] = scale*ort[m];
+        H[m][m-1] = scale*g;
+      }
+    }
+
+    // Accumulate transformations (Algol's ortran).
+
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        V[i][j] = (i == j ? 1.0 : 0.0);
+      }
+    }
+
+    for (int m = high-1; m >= low+1; m--) {
+      if (H[m][m-1] != 0.0) {
+        for (int i = m+1; i <= high; i++) {
+          ort[i] = H[i][m-1];
+        }
+        for (int j = m; j <= high; j++) {
+          double g = 0.0;
+          for (int i = m; i <= high; i++) {
+            g += ort[i] * V[i][j];
+          }
+          // Double division avoids possible underflow
+          g = (g / ort[m]) / H[m][m-1];
+          for (int i = m; i <= high; i++) {
+            V[i][j] += g * ort[i];
+          }
+        }
+      }
+    }
+  }
+
+
+  /** 
+   * Complex scalar division.
+   */
+  private void cdiv(double xr, double xi, double yr, double yi) {
+    double r,d;
+    if (Math.abs(yr) > Math.abs(yi)) {
+      r = yi/yr;
+      d = yr + r*yi;
+      cdivr = (xr + r*xi)/d;
+      cdivi = (xi - r*xr)/d;
+    } else {
+      r = yr/yi;
+      d = yi + r*yr;
+      cdivr = (r*xr + xi)/d;
+      cdivi = (r*xi - xr)/d;
+    }
+  }
+
+
+  /**
+   * Nonsymmetric reduction from Hessenberg to real Schur form.
+   * <p/>
+   * This is derived from the Algol procedure hqr2, by Martin and Wilkinson,
+   * Handbook for Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+   * Fortran subroutine in EISPACK.
+   */
+  private void hqr2() {
+
+    // Initialize
+
+    int nn = this.n;
+    int n = nn-1;
+    int low = 0;
+    int high = nn-1;
+    double eps = Math.pow(2.0,-52.0);
+    double exshift = 0.0;
+    double p=0,q=0,r=0,s=0,z=0,t,w,x,y;
+
+    // Store roots isolated by balanc and compute matrix norm
+
+    double norm = 0.0;
+    for (int i = 0; i < nn; i++) {
+      if (i < low | i > high) {
+        d[i] = H[i][i];
+        e[i] = 0.0;
+      }
+      for (int j = Math.max(i-1,0); j < nn; j++) {
+        norm = norm + Math.abs(H[i][j]);
+      }
+    }
+
+    // Outer loop over eigenvalue index
+
+    int iter = 0;
+    while (n >= low) {
+
+      // Look for single small sub-diagonal element
+
+      int l = n;
+      while (l > low) {
+        s = Math.abs(H[l-1][l-1]) + Math.abs(H[l][l]);
+        if (s == 0.0) {
+          s = norm;
+        }
+        if (Math.abs(H[l][l-1]) < eps * s) {
+          break;
+        }
+        l--;
+      }
+
+      // Check for convergence
+      // One root found
+
+      if (l == n) {
+        H[n][n] = H[n][n] + exshift;
+        d[n] = H[n][n];
+        e[n] = 0.0;
+        n--;
+        iter = 0;
+
+        // Two roots found
+
+      } else if (l == n-1) {
+        w = H[n][n-1] * H[n-1][n];
+        p = (H[n-1][n-1] - H[n][n]) / 2.0;
+        q = p * p + w;
+        z = Math.sqrt(Math.abs(q));
+        H[n][n] = H[n][n] + exshift;
+        H[n-1][n-1] = H[n-1][n-1] + exshift;
+        x = H[n][n];
+
+        // Real pair
+
+        if (q >= 0) {
+          if (p >= 0) {
+            z = p + z;
+          } else {
+            z = p - z;
+          }
+          d[n-1] = x + z;
+          d[n] = d[n-1];
+          if (z != 0.0) {
+            d[n] = x - w / z;
+          }
+          e[n-1] = 0.0;
+          e[n] = 0.0;
+          x = H[n][n-1];
+          s = Math.abs(x) + Math.abs(z);
+          p = x / s;
+          q = z / s;
+          r = Math.sqrt(p * p+q * q);
+          p = p / r;
+          q = q / r;
+
+          // Row modification
+
+          for (int j = n-1; j < nn; j++) {
+            z = H[n-1][j];
+            H[n-1][j] = q * z + p * H[n][j];
+            H[n][j] = q * H[n][j] - p * z;
+          }
+
+          // Column modification
+
+          for (int i = 0; i <= n; i++) {
+            z = H[i][n-1];
+            H[i][n-1] = q * z + p * H[i][n];
+            H[i][n] = q * H[i][n] - p * z;
+          }
+
+          // Accumulate transformations
+
+          for (int i = low; i <= high; i++) {
+            z = V[i][n-1];
+            V[i][n-1] = q * z + p * V[i][n];
+            V[i][n] = q * V[i][n] - p * z;
+          }
+
+          // Complex pair
+
+        } else {
+          d[n-1] = x + p;
+          d[n] = x + p;
+          e[n-1] = z;
+          e[n] = -z;
+        }
+        n = n - 2;
+        iter = 0;
+
+        // No convergence yet
+
+      } else {
+
+        // Form shift
+
+        x = H[n][n];
+        y = 0.0;
+        w = 0.0;
+        if (l < n) {
+          y = H[n-1][n-1];
+          w = H[n][n-1] * H[n-1][n];
+        }
+
+        // Wilkinson's original ad hoc shift
+
+        if (iter == 10) {
+          exshift += x;
+          for (int i = low; i <= n; i++) {
+            H[i][i] -= x;
+          }
+          s = Math.abs(H[n][n-1]) + Math.abs(H[n-1][n-2]);
+          x = y = 0.75 * s;
+          w = -0.4375 * s * s;
+        }
+
+        // MATLAB's new ad hoc shift
+
+        if (iter == 30) {
+          s = (y - x) / 2.0;
+          s = s * s + w;
+          if (s > 0) {
+            s = Math.sqrt(s);
+            if (y < x) {
+              s = -s;
+            }
+            s = x - w / ((y - x) / 2.0 + s);
+            for (int i = low; i <= n; i++) {
+              H[i][i] -= s;
+            }
+            exshift += s;
+            x = y = w = 0.964;
+          }
+        }
+
+        iter = iter + 1;   // (Could check iteration count here.)
+
+        // Look for two consecutive small sub-diagonal elements
+
+        int m = n-2;
+        while (m >= l) {
+          z = H[m][m];
+          r = x - z;
+          s = y - z;
+          p = (r * s - w) / H[m+1][m] + H[m][m+1];
+          q = H[m+1][m+1] - z - r - s;
+          r = H[m+2][m+1];
+          s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+          p = p / s;
+          q = q / s;
+          r = r / s;
+          if (m == l) {
+            break;
+          }
+          if (Math.abs(H[m][m-1]) * (Math.abs(q) + Math.abs(r)) <
+              eps * (Math.abs(p) * (Math.abs(H[m-1][m-1]) + Math.abs(z) +
+                  Math.abs(H[m+1][m+1])))) {
+            break;
+                  }
+          m--;
+        }
+
+        for (int i = m+2; i <= n; i++) {
+          H[i][i-2] = 0.0;
+          if (i > m+2) {
+            H[i][i-3] = 0.0;
+          }
+        }
+
+        // Double QR step involving rows l:n and columns m:n
+
+        for (int k = m; k <= n-1; k++) {
+          boolean notlast = (k != n-1);
+          if (k != m) {
+            p = H[k][k-1];
+            q = H[k+1][k-1];
+            r = (notlast ? H[k+2][k-1] : 0.0);
+            x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+            if (x != 0.0) {
+              p = p / x;
+              q = q / x;
+              r = r / x;
+            }
+          }
+          if (x == 0.0) {
+            break;
+          }
+          s = Math.sqrt(p * p + q * q + r * r);
+          if (p < 0) {
+            s = -s;
+          }
+          if (s != 0) {
+            if (k != m) {
+              H[k][k-1] = -s * x;
+            } else if (l != m) {
+              H[k][k-1] = -H[k][k-1];
+            }
+            p = p + s;
+            x = p / s;
+            y = q / s;
+            z = r / s;
+            q = q / p;
+            r = r / p;
+
+            // Row modification
+
+            for (int j = k; j < nn; j++) {
+              p = H[k][j] + q * H[k+1][j];
+              if (notlast) {
+                p = p + r * H[k+2][j];
+                H[k+2][j] = H[k+2][j] - p * z;
+              }
+              H[k][j] = H[k][j] - p * x;
+              H[k+1][j] = H[k+1][j] - p * y;
+            }
+
+            // Column modification
+
+            for (int i = 0; i <= Math.min(n,k+3); i++) {
+              p = x * H[i][k] + y * H[i][k+1];
+              if (notlast) {
+                p = p + z * H[i][k+2];
+                H[i][k+2] = H[i][k+2] - p * r;
+              }
+              H[i][k] = H[i][k] - p;
+              H[i][k+1] = H[i][k+1] - p * q;
+            }
+
+            // Accumulate transformations
+
+            for (int i = low; i <= high; i++) {
+              p = x * V[i][k] + y * V[i][k+1];
+              if (notlast) {
+                p = p + z * V[i][k+2];
+                V[i][k+2] = V[i][k+2] - p * r;
+              }
+              V[i][k] = V[i][k] - p;
+              V[i][k+1] = V[i][k+1] - p * q;
+            }
+          }  // (s != 0)
+        }  // k loop
+      }  // check convergence
+    }  // while (n >= low)
+
+    // Backsubstitute to find vectors of upper triangular form
+
+    if (norm == 0.0) {
+      return;
+    }
+
+    for (n = nn-1; n >= 0; n--) {
+      p = d[n];
+      q = e[n];
+
+      // Real vector
+
+      if (q == 0) {
+        int l = n;
+        H[n][n] = 1.0;
+        for (int i = n-1; i >= 0; i--) {
+          w = H[i][i] - p;
+          r = 0.0;
+          for (int j = l; j <= n; j++) {
+            r = r + H[i][j] * H[j][n];
+          }
+          if (e[i] < 0.0) {
+            z = w;
+            s = r;
+          } else {
+            l = i;
+            if (e[i] == 0.0) {
+              if (w != 0.0) {
+                H[i][n] = -r / w;
+              } else {
+                H[i][n] = -r / (eps * norm);
+              }
+
+              // Solve real equations
+
+            } else {
+              x = H[i][i+1];
+              y = H[i+1][i];
+              q = (d[i] - p) * (d[i] - p) + e[i] * e[i];
+              t = (x * s - z * r) / q;
+              H[i][n] = t;
+              if (Math.abs(x) > Math.abs(z)) {
+                H[i+1][n] = (-r - w * t) / x;
+              } else {
+                H[i+1][n] = (-s - y * t) / z;
+              }
+            }
+
+            // Overflow control
+
+            t = Math.abs(H[i][n]);
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                H[j][n] = H[j][n] / t;
+              }
+            }
+          }
+        }
+
+        // Complex vector
+
+      } else if (q < 0) {
+        int l = n-1;
+
+        // Last vector component imaginary so matrix is triangular
+
+        if (Math.abs(H[n][n-1]) > Math.abs(H[n-1][n])) {
+          H[n-1][n-1] = q / H[n][n-1];
+          H[n-1][n] = -(H[n][n] - p) / H[n][n-1];
+        } else {
+          cdiv(0.0,-H[n-1][n],H[n-1][n-1]-p,q);
+          H[n-1][n-1] = cdivr;
+          H[n-1][n] = cdivi;
+        }
+        H[n][n-1] = 0.0;
+        H[n][n] = 1.0;
+        for (int i = n-2; i >= 0; i--) {
+          double ra,sa,vr,vi;
+          ra = 0.0;
+          sa = 0.0;
+          for (int j = l; j <= n; j++) {
+            ra = ra + H[i][j] * H[j][n-1];
+            sa = sa + H[i][j] * H[j][n];
+          }
+          w = H[i][i] - p;
+
+          if (e[i] < 0.0) {
+            z = w;
+            r = ra;
+            s = sa;
+          } else {
+            l = i;
+            if (e[i] == 0) {
+              cdiv(-ra,-sa,w,q);
+              H[i][n-1] = cdivr;
+              H[i][n] = cdivi;
+            } else {
+
+              // Solve complex equations
+
+              x = H[i][i+1];
+              y = H[i+1][i];
+              vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q;
+              vi = (d[i] - p) * 2.0 * q;
+              if (vr == 0.0 & vi == 0.0) {
+                vr = eps * norm * (Math.abs(w) + Math.abs(q) +
+                    Math.abs(x) + Math.abs(y) + Math.abs(z));
+              }
+              cdiv(x*r-z*ra+q*sa,x*s-z*sa-q*ra,vr,vi);
+              H[i][n-1] = cdivr;
+              H[i][n] = cdivi;
+              if (Math.abs(x) > (Math.abs(z) + Math.abs(q))) {
+                H[i+1][n-1] = (-ra - w * H[i][n-1] + q * H[i][n]) / x;
+                H[i+1][n] = (-sa - w * H[i][n] - q * H[i][n-1]) / x;
+              } else {
+                cdiv(-r-y*H[i][n-1],-s-y*H[i][n],z,q);
+                H[i+1][n-1] = cdivr;
+                H[i+1][n] = cdivi;
+              }
+            }
+
+            // Overflow control
+
+            t = Math.max(Math.abs(H[i][n-1]),Math.abs(H[i][n]));
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                H[j][n-1] = H[j][n-1] / t;
+                H[j][n] = H[j][n] / t;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Vectors of isolated roots
+
+    for (int i = 0; i < nn; i++) {
+      if (i < low | i > high) {
+        for (int j = i; j < nn; j++) {
+          V[i][j] = H[i][j];
+        }
+      }
+    }
+
+    // Back transformation to get eigenvectors of original matrix
+
+    for (int j = nn-1; j >= low; j--) {
+      for (int i = low; i <= high; i++) {
+        z = 0.0;
+        for (int k = low; k <= Math.min(j,high); k++) {
+          z = z + V[i][k] * H[k][j];
+        }
+        V[i][j] = z;
+      }
+    }
+  }
+
+
+  /** 
+   * Check for symmetry, then construct the eigenvalue decomposition
+   *
+   * @param Arg    Square matrix
+   */
+  public EigenvalueDecomposition(Matrix Arg) {
+    double[][] A = Arg.getArray();
+    n = Arg.getColumnDimension();
+    V = new double[n][n];
+    d = new double[n];
+    e = new double[n];
+
+    issymmetric = true;
+    for (int j = 0; (j < n) & issymmetric; j++) {
+      for (int i = 0; (i < n) & issymmetric; i++) {
+        issymmetric = (A[i][j] == A[j][i]);
+      }
+    }
+
+    if (issymmetric) {
+      for (int i = 0; i < n; i++) {
+        for (int j = 0; j < n; j++) {
+          V[i][j] = A[i][j];
+        }
+      }
+
+      // Tridiagonalize.
+      tred2();
+
+      // Diagonalize.
+      tql2();
+
+    } else {
+      H = new double[n][n];
+      ort = new double[n];
+
+      for (int j = 0; j < n; j++) {
+        for (int i = 0; i < n; i++) {
+          H[i][j] = A[i][j];
+        }
+      }
+
+      // Reduce to Hessenberg form.
+      orthes();
+
+      // Reduce Hessenberg to real Schur form.
+      hqr2();
+    }
+  }
+
+  /** 
+   * Return the eigenvector matrix
+   * @return     V
+   */
+  public Matrix getV() {
+    return new Matrix(V,n,n);
+  }
+
+  /** 
+   * Return the real parts of the eigenvalues
+   * @return     real(diag(D))
+   */
+  public double[] getRealEigenvalues() {
+    return d;
+  }
+
+  /** 
+   * Return the imaginary parts of the eigenvalues
+   * @return     imag(diag(D))
+   */
+  public double[] getImagEigenvalues() {
+    return e;
+  }
+
+  /** 
+   * Return the block diagonal eigenvalue matrix
+   * @return     D
+   */
+  public Matrix getD() {
+    Matrix X = new Matrix(n,n);
+    double[][] D = X.getArray();
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        D[i][j] = 0.0;
+      }
+      D[i][i] = d[i];
+      if (e[i] > 0) {
+        D[i][i+1] = e[i];
+      } else if (e[i] < 0) {
+        D[i][i-1] = e[i];
+      }
+    }
+    return X;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/ExponentialFormat.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/ExponentialFormat.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/ExponentialFormat.java	(revision 29)
@@ -0,0 +1,112 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    ExponentialFormat.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * @author Yong Wang
+ * @version $Revision: 5953 $
+ */
+public class ExponentialFormat
+  extends DecimalFormat
+  implements RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5298981701073897741L;
+    
+  protected DecimalFormat nf ;
+  protected boolean sign;
+  protected int digits;
+  protected int exp;
+  protected boolean trailing = true;
+
+  public ExponentialFormat () {
+    this( 5 );
+  }
+    
+  public ExponentialFormat( int digits ) {
+    this( digits, false );
+  }
+
+  public ExponentialFormat( int digits, boolean trailing ) {
+    this( digits, 2, true, trailing );
+  }
+    
+  public ExponentialFormat( int digits, int exp, boolean sign, 
+			    boolean trailing ) {
+    this.digits = digits;
+    this.exp = exp;
+    this.sign = sign;
+    this.trailing = trailing;
+    nf = new DecimalFormat( pattern() );
+    nf.setPositivePrefix("+");
+    nf.setNegativePrefix("-");
+  }
+    
+  public int width () {
+    if( !trailing ) throw new RuntimeException( "flexible width" );
+    if( sign ) return 1 + digits + 2 + exp;
+    else return digits + 2 + exp;
+  }
+
+  public StringBuffer format(double number, StringBuffer toAppendTo, 
+			     FieldPosition pos) {
+    StringBuffer s = new StringBuffer( nf.format(number) );
+    if( sign ) {
+      if( s.charAt(0) == '+' ) s.setCharAt(0, ' ');
+    }
+    else {
+      if( s.charAt(0) == '-' ) s.setCharAt(0, '*');
+      else s.deleteCharAt(0);
+    }
+	
+    return toAppendTo.append( s );
+  }
+    
+  private String  pattern() {
+    StringBuffer s = new StringBuffer();      // "-##0.00E-00"   // fw.d
+    s.append("0.");
+    for(int i = 0; i < digits - 1; i ++)
+      if( trailing ) s.append('0');
+      else s.append('#');
+	
+    s.append('E');
+    for(int i = 0; i < exp; i ++)
+      s.append('0');
+	
+    return s.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/FlexibleDecimalFormat.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/FlexibleDecimalFormat.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/FlexibleDecimalFormat.java	(revision 29)
@@ -0,0 +1,229 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    FlexibleDecimalFormat.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * @author Yong Wang
+ * @version $Revision: 5953 $
+ */
+public class FlexibleDecimalFormat
+  extends DecimalFormat
+  implements RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 110912192794064140L;
+
+  private DecimalFormat nf = null;
+  private int      digits = 7;
+  private boolean  exp = false; 
+  private int      intDigits = 1;
+  private int      decimalDigits = 0;
+  private int      expIntDigits = 1;          // ??? 
+  private int      expDecimalDigits = 0;      // ???
+  private int      power = 2;
+  private boolean  trailing = false;
+  private boolean  grouping = false;
+  private boolean  sign = false;
+    
+  public FlexibleDecimalFormat ( ) {
+    this( 5 );
+  }
+
+  public FlexibleDecimalFormat ( int digits ) {
+    if( digits < 1 ) 
+      throw new IllegalArgumentException("digits < 1");
+    this.digits = digits;
+    intDigits = 1;
+  }
+
+  public FlexibleDecimalFormat ( int digits, boolean trailing ) {
+    this( digits );
+    this.trailing = trailing;
+  }
+
+  public FlexibleDecimalFormat ( int digits, boolean exp, boolean trailing, 
+				 boolean grouping ) {
+    this.trailing = trailing;
+    this.exp = exp;
+    this.digits = digits;
+    this.grouping = grouping;
+    if( exp ) {
+      this.intDigits = 1;
+      this.decimalDigits = digits - intDigits;
+    }
+    else {
+      this.decimalDigits = decimalDigits;
+      this.intDigits = Math.max( 1, digits - decimalDigits );
+    }
+  }
+
+  public FlexibleDecimalFormat ( double d ) {
+    newFormat( d );
+  }
+
+  private void newFormat ( double d ) {
+    if( needExponentialFormat( d ) ) {
+      exp = true;
+      intDigits = 1;
+      expDecimalDigits = decimalDigits( d, true );
+      if( d < 0) sign = true;
+      else sign = false;
+    }
+    else {
+      exp = false;
+      intDigits = Math.max(1, intDigits( d ));
+      decimalDigits = decimalDigits( d, false );
+      if( d < 0.0 ) sign = true;
+      else sign = false;
+    }
+  }
+
+  public void update ( double d ) {
+    if( Math.abs( intDigits(d) -1 ) > 99 ) power = 3;
+    expIntDigits = 1;
+    expDecimalDigits = Math.max( expDecimalDigits, 
+				 decimalDigits( d, true ));
+    if( d < 0) sign = true;
+    if( needExponentialFormat( d ) || exp ) {
+      exp = true;
+    }
+    else {
+      intDigits = Math.max(intDigits, intDigits( d ));
+      decimalDigits = Math.max(decimalDigits, decimalDigits( d, false ));
+      if( d < 0) sign = true;
+    }
+  }
+
+  private static int intDigits ( double d ) {
+    return (int) Math.floor( Math.log( Math.abs( d * (1 + 1e-14) ) ) / 
+			     Math.log ( 10 ) ) + 1;
+  }
+    
+  private int decimalDigits ( double d, boolean expo ) {
+    if( d == 0.0 ) return 0;
+    d = Math.abs( d );
+    int e = intDigits( d );
+    if( expo ) {
+      d /= Math.pow(10, e-1);
+      e = 1;
+    }
+    if( e >= digits ) return 0;
+    int iD = Math.max(1, e);  
+    int dD = digits - e;
+    if( !trailing && dD > 0 ) {  // to get rid of trailing zeros
+      FloatingPointFormat f = new 
+	FloatingPointFormat( iD + 1 + dD, dD, true);
+      String dString = f.format( d );
+      while( dD > 0 ) {
+	if( dString.charAt(iD+1+dD-1) == '0' ) {
+	  dD--;
+	}
+	else break;
+      }
+    }
+    return dD;
+  }
+    
+  public boolean  needExponentialFormat ( double d ) {
+    if( d == 0.0 ) return false;
+    int e = intDigits( d );
+    if( e > digits + 5 || e < -3 ) return true;
+    else return false;
+  }
+    
+  public void grouping ( boolean grouping ) {
+    this.grouping = grouping;
+  }
+    
+  private static void println ( Object obj ){
+    System.out.println( obj );
+  }
+
+  private void setFormat ( ) {
+    int dot = 1;
+    if( decimalDigits == 0) dot = 0;
+    if( exp ) 
+      nf = new ExponentialFormat( 1 + expDecimalDigits, power, sign, 
+				  grouping || trailing );
+    else { 
+      int s = sign ? 1 : 0;
+      nf = new FloatingPointFormat( s +intDigits +dot +decimalDigits, 
+				    decimalDigits, grouping || trailing);
+    }
+  }
+    
+  private void setFormat ( double d ) {
+    newFormat( d );
+    setFormat();
+  }
+
+  public StringBuffer format (double number, StringBuffer toAppendTo,
+			      FieldPosition pos) {
+    if( grouping ) {
+      if( nf == null ) {
+	setFormat();
+      }
+    }
+    else setFormat( number );
+	
+    return toAppendTo.append( nf.format(number) );
+  }
+
+  public int width () {
+	
+    if( !trailing && !grouping ) 
+      throw new RuntimeException( "flexible width" );
+	
+    return format(0.).length();
+  }
+    
+  public StringBuffer formatString ( String str ) {
+    int w = width();
+    int h = ( w - str.length() ) / 2;
+    StringBuffer text = new StringBuffer();
+    for(int i = 0; i < h; i++ ){
+      text.append( ' ' );
+    }
+    text.append( str );
+    for(int i = 0; i < w - h - str.length(); i++ ){
+      text.append( ' ' );
+    }
+    return text;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/core/matrix/FloatingPointFormat.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/FloatingPointFormat.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/FloatingPointFormat.java	(revision 29)
@@ -0,0 +1,133 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    FloatingPoint.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * Class for the format of floating point numbers
+ *
+ * @author Yong Wang
+ * @version $Revision: 5953 $
+ */
+public class FloatingPointFormat
+  extends DecimalFormat
+  implements RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4500373755333429499L;
+    
+  protected DecimalFormat nf ;
+  protected int width;
+  protected int decimal;
+  protected boolean trailing = true;
+
+  /**
+   * Default constructor
+   */
+  public FloatingPointFormat () {
+    this( 8, 5 );
+  }
+
+  public FloatingPointFormat ( int digits ) {
+    this( 8, 2 );
+  }
+
+  public FloatingPointFormat( int w, int d ) {
+    width = w;
+    decimal = d;
+    nf = new DecimalFormat( pattern(w, d) );
+    nf.setPositivePrefix(" ");
+    nf.setNegativePrefix("-");
+  }
+
+  public FloatingPointFormat( int w, int d, boolean trailingZeros ) {
+    this( w, d );
+    this.trailing = trailingZeros;
+  }
+
+  public StringBuffer format(double number, StringBuffer toAppendTo, 
+			     FieldPosition pos) {
+    StringBuffer s = new StringBuffer( nf.format(number) );
+    if( s.length() > width ) {
+      if( s.charAt(0) == ' ' && s.length() == width + 1 ) {
+	s.deleteCharAt(0);
+      }
+      else {
+	s.setLength( width );
+	for( int i = 0; i < width; i ++ )
+	  s.setCharAt(i, '*');
+      }
+    }
+    else {
+      for (int i = 0; i < width - s.length(); i++)  // padding
+	s.insert(0,' ');
+    }
+    if( !trailing && decimal > 0 ) { // delete trailing zeros
+      while( s.charAt( s.length()-1 ) == '0' )
+	s.deleteCharAt( s.length()-1 );
+      if( s.charAt( s.length()-1 ) == '.' )
+	s.deleteCharAt( s.length()-1 );
+    }
+	
+    return toAppendTo.append( s );
+  }
+
+  public static String  pattern( int w, int d ) {
+    StringBuffer s = new StringBuffer();      // "-##0.00"   // fw.d
+    s.append( padding(w - d - 3, '#') );
+    if( d == 0) s.append('0');
+    else {
+      s.append("0.");
+      s.append( padding( d, '0') );
+    }
+    return s.toString();
+  }
+
+  private static StringBuffer  padding( int n, char c ) {
+    StringBuffer text = new StringBuffer();
+	
+    for(int i = 0; i < n; i++ ){
+      text.append( c );
+    }
+
+    return text;
+  }
+
+  public int width () {
+    if( !trailing ) throw new RuntimeException( "flexible width" );
+    return width;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/IntVector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/IntVector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/IntVector.java	(revision 29)
@@ -0,0 +1,359 @@
+/*
+ *    This program 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 2 of the License, or (at
+ *    your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/*
+ *    IntVector.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Arrays;
+
+/**
+ * A vector specialized on integers.
+ * 
+ * @author Yong Wang
+ * @version $Revision: 5953 $
+ */
+public class  IntVector
+  implements Cloneable, RevisionHandler {
+
+  /** Array for internal storage of elements. */
+  int[]  V;
+
+  /** size of the vector */
+  private int  sizeOfVector;
+
+
+  /* ------------------------
+     Constructors
+     * ------------------------ */
+
+  /** Constructs a null vector.
+   */
+  public IntVector(){
+    V = new int[ 0 ];
+    setSize( 0 );
+  }
+    
+  /** Constructs an n-vector of zeros. 
+   *  @param n    Length.
+  */
+  public IntVector( int n ){
+    V = new int[ n ];
+    setSize( n );
+  }
+    
+  /** Constructs an n-vector of a constant
+   *  @param n    Length.
+  */
+  public IntVector( int n, int s ){
+    this(n);
+    set( s );
+  }
+    
+  /** Constructs a vector given an int array
+   *  @param v the int array
+  */
+  public IntVector( int v[] ){
+    if( v == null ) {
+      V = new int[ 0 ];
+      setSize( 0 );
+    }
+    else {
+      V = new int[ v.length ];
+      setSize( v.length );
+      set(0, size() - 1, v, 0);
+    }
+  }
+    
+  /* ------------------------
+     Public Methods
+     * ------------------------ */
+    
+  /** Gets the size of the vector.
+   *  @return Size.  */
+  public int  size(){
+    return sizeOfVector;
+  }
+    
+  /** 
+   * Sets the size of the vector. The provided size can't be greater than
+   * the capacity of the vector.
+   * @param size the new Size.
+   */
+  public void  setSize( int size ){
+    if( size > capacity() ) 
+      throw new IllegalArgumentException("insufficient capacity");
+    sizeOfVector = size;
+  }
+  
+  /** Sets the value of an element.
+   *  @param s the value for the element */
+  public void  set( int s ) {
+    for( int i = 0; i < size(); i++ )
+      set(i, s);
+  }
+
+  /** Sets the values of elements from an int array.
+   *  @param i0 the index of the first element
+   *  @param i1 the index of the last element
+   *  @param v the int array that stores the values
+   *  @param j0 the index of the first element in the int array */
+  public void  set( int i0, int i1, int [] v, int j0){
+    for(int i = i0; i<= i1; i++)
+      set( i, v[j0 + i - i0] );
+  }
+
+  /** Sets the values of elements from another IntVector.
+   *  @param i0 the index of the first element
+   *  @param i1 the index of the last element
+   *  @param v the IntVector that stores the values
+   *  @param j0 the index of the first element in the IntVector */
+  public void  set( int i0, int i1, IntVector v, int j0){
+    for(int i = i0; i<= i1; i++)
+      set( i, v.get(j0 + i - i0) );
+  }
+
+  /** Sets the values of elements from another IntVector.
+   *  @param v the IntVector that stores the values 
+   */
+  public void  set( IntVector v ){
+    set( 0, v.size() - 1, v, 0);
+  }
+
+  /** Generates an IntVector that stores all integers inclusively between
+   *  two integers.
+   *  @param i0 the first integer
+   *  @param i1 the second integer 
+   */
+  public static IntVector  seq( int i0, int i1 ) {
+    if( i1 < i0 ) throw new IllegalArgumentException("i1 < i0 ");
+    IntVector v = new IntVector( i1 - i0 + 1 );
+    for( int i = 0; i < i1 - i0 + 1; i++ ) {
+      v.set(i, i + i0);
+    }
+    return v; 
+  } 
+  
+  /** Access the internal one-dimensional array.
+      @return Pointer to the one-dimensional array of vector elements. */
+  public int []  getArray() {
+    return V;
+  }
+    
+  /** Sets the internal one-dimensional array.
+      @param a Pointer to the one-dimensional array of vector elements. */
+  protected void  setArray( int [] a ) {
+    V = a;
+  }
+    
+  /** Sorts the elements in place 
+   */
+  public void  sort() {
+    Arrays.sort( V, 0, size() );
+  }
+
+  /** Returns a copy of the internal one-dimensional array.
+      @return One-dimensional array copy of vector elements.  */
+  public int[]  getArrayCopy() {
+    int [] b = new int[ size() ];
+    for( int i = 0; i <= size() - 1; i++ ) {
+      b[i] = V[i];
+    }
+    return b;
+  }
+
+  /** Returns the capacity of the vector 
+   */
+  public int capacity() {
+    return V.length;
+  }
+
+  /** Sets the capacity of the vector 
+   *  @param capacity the new capacity of the vector
+   */
+  public void  setCapacity( int capacity ) {
+    if( capacity == capacity() ) return;
+    int [] old_V = V;
+    int m = Math.min( capacity, size() );
+    V = new int[ capacity ];
+    setSize( capacity );
+    set(0, m-1, old_V, 0);
+  }
+
+  /** Sets a single element.
+   *  @param i    the index of the element
+   *  @param s    the new value
+  */
+  public void  set( int i, int s ) {
+    V[i] = s;
+  }
+    
+  /** Gets the value of an element.
+   *  @param i    the index of the element
+   *  @return     the value of the element
+  */
+  public int  get( int i ) {
+    return V[i];
+  }
+  
+  /** Makes a deep copy of the vector
+   */
+  public IntVector  copy() { 
+    return (IntVector) clone();
+  }
+    
+  /** Clones the IntVector object.
+   */
+  public Object  clone() { 
+    IntVector u = new IntVector( size() );
+    for( int i = 0; i < size(); i++) 
+      u.V[i] = V[i];
+    return u;
+  }
+  
+  /** Returns a subvector.
+   *  @param i0   the index of the first element
+   *  @param i1   the index of the last element
+   *  @return the subvector
+  */
+  public IntVector  subvector( int i0, int i1 ) 
+  {
+    IntVector v = new IntVector( i1-i0+1 );
+    v.set(0, i1 - i0, this, i0);
+    return v;
+  }
+
+  /** Returns a subvector as indexed by an IntVector.
+   *  @param index   the index
+   *  @return the subvector
+  */
+  public IntVector  subvector( IntVector index ) {
+    IntVector v = new IntVector( index.size() );
+    for( int i = 0; i < index.size(); i++ )
+      v.V[i] = V[index.V[i]];
+    return v;
+  }
+
+  /**
+   *  Swaps the values stored at i and j
+   *  @param i the index i
+   *  @param j the index j
+   */
+  public void  swap( int i, int j ){
+    if( i == j ) return;
+    int t = get( i );
+    set( i, get(j) );
+    set( j, t );
+  }
+  
+  /** 
+   *  Shifts an element to another position. Elements between them are
+   *  shifted one position left.
+   *  @param i the index of the element
+   *  @param j the index of the new position */
+  public void  shift( int i, int j ){
+    if( i == j ) return;
+    if( i < j ) {
+      int t = V[i];
+      for( int k = i; k <= j-1; k++ )
+  	V[k] = V[k+1];
+      V[j] = t;
+    }
+    else shift( j, i );
+  }
+  
+  /** 
+   *  Shifts an element to the end of the vector. Elements between them are
+   *  shifted one position left.
+   *  @param j the index of the element
+   */
+  public void  shiftToEnd( int j ){
+    shift( j, size()-1 );
+  }
+  
+  /** 
+   * Returns true if the vector is empty
+   */
+  public boolean  isEmpty() {
+    if( size() == 0 ) return true;
+    return false;
+  }
+
+  /** Converts the IntVecor to a string
+   */ 
+  public String  toString() {
+    return toString( 5, false );
+  }
+    
+  /** Convert the IntVecor to a string
+   *  @param digits number of digits to be shown
+   *  @param trailing true if trailing zeros are to be shown
+   */ 
+  public String  toString( int digits, boolean trailing ) {
+    if( isEmpty() ) return "null vector";
+
+    StringBuffer text = new StringBuffer();
+    FlexibleDecimalFormat nf = new FlexibleDecimalFormat( digits, 
+							  trailing );
+    nf.grouping( true );
+    for( int i = 0; i < size(); i ++ ) nf.update( get(i) );
+    int count = 0;
+    int width = 80;
+    String number;
+    for( int i = 0; i < size(); i++ ) {
+      number = nf.format(get(i));
+      count += 1 + number.length();
+      if( count > width-1 ) { 
+	text.append('\n'); 
+	count = 1 + number.length();
+      }
+      text.append( " " + number );
+    }
+	
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /** 
+   *  Tests the IntVector class
+   */
+  public static void  main( String args[] ) {
+    
+    IntVector u = new IntVector();
+    System.out.println( u );
+
+    IntVector v = IntVector.seq(10, 25);
+    System.out.println( v );
+
+    IntVector w = IntVector.seq(25, 10);
+    System.out.println( w );
+
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/LUDecomposition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/LUDecomposition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/LUDecomposition.java	(revision 29)
@@ -0,0 +1,280 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * LUDecomposition.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/** 
+ * LU Decomposition.
+ * <P>
+ * For an m-by-n matrix A with m &gt;= n, the LU decomposition is an m-by-n
+ * unit lower triangular matrix L, an n-by-n upper triangular matrix U, and a
+ * permutation vector piv of length m so that A(piv,:) = L*U.  If m &lt; n,
+ * then L is m-by-m and U is m-by-n.
+ * <P>
+ * The LU decompostion with pivoting always exists, even if the matrix is
+ * singular, so the constructor will never fail.  The primary use of the LU
+ * decomposition is in the solution of square systems of simultaneous linear
+ * equations.  This will fail if isNonsingular() returns false.
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class LUDecomposition 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2731022568037808629L;
+
+  /** 
+   * Array for internal storage of decomposition.
+   * @serial internal array storage.
+   */
+  private double[][] LU;
+
+  /** 
+   * Row and column dimensions, and pivot sign.
+   * @serial column dimension.
+   * @serial row dimension.
+   * @serial pivot sign.
+   */
+  private int m, n, pivsign; 
+
+  /** 
+   * Internal storage of pivot vector.
+   * @serial pivot vector.
+   */
+  private int[] piv;
+
+  /** 
+   * LU Decomposition
+   * @param  A   Rectangular matrix
+   */
+  public LUDecomposition(Matrix A) {
+
+    // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+
+    LU = A.getArrayCopy();
+    m = A.getRowDimension();
+    n = A.getColumnDimension();
+    piv = new int[m];
+    for (int i = 0; i < m; i++) {
+      piv[i] = i;
+    }
+    pivsign = 1;
+    double[] LUrowi;
+    double[] LUcolj = new double[m];
+
+    // Outer loop.
+
+    for (int j = 0; j < n; j++) {
+
+      // Make a copy of the j-th column to localize references.
+
+      for (int i = 0; i < m; i++) {
+        LUcolj[i] = LU[i][j];
+      }
+
+      // Apply previous transformations.
+
+      for (int i = 0; i < m; i++) {
+        LUrowi = LU[i];
+
+        // Most of the time is spent in the following dot product.
+
+        int kmax = Math.min(i,j);
+        double s = 0.0;
+        for (int k = 0; k < kmax; k++) {
+          s += LUrowi[k]*LUcolj[k];
+        }
+
+        LUrowi[j] = LUcolj[i] -= s;
+      }
+
+      // Find pivot and exchange if necessary.
+
+      int p = j;
+      for (int i = j+1; i < m; i++) {
+        if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
+          p = i;
+        }
+      }
+      if (p != j) {
+        for (int k = 0; k < n; k++) {
+          double t = LU[p][k]; LU[p][k] = LU[j][k]; LU[j][k] = t;
+        }
+        int k = piv[p]; piv[p] = piv[j]; piv[j] = k;
+        pivsign = -pivsign;
+      }
+
+      // Compute multipliers.
+
+      if (j < m & LU[j][j] != 0.0) {
+        for (int i = j+1; i < m; i++) {
+          LU[i][j] /= LU[j][j];
+        }
+      }
+    }
+  }
+
+  /** 
+   * Is the matrix nonsingular?
+   * @return     true if U, and hence A, is nonsingular.
+   */
+  public boolean isNonsingular() {
+    for (int j = 0; j < n; j++) {
+      if (LU[j][j] == 0)
+        return false;
+    }
+    return true;
+  }
+
+  /** 
+   * Return lower triangular factor
+   * @return     L
+   */
+  public Matrix getL() {
+    Matrix X = new Matrix(m,n);
+    double[][] L = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        if (i > j) {
+          L[i][j] = LU[i][j];
+        } else if (i == j) {
+          L[i][j] = 1.0;
+        } else {
+          L[i][j] = 0.0;
+        }
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Return upper triangular factor
+   * @return     U
+   */
+  public Matrix getU() {
+    Matrix X = new Matrix(n,n);
+    double[][] U = X.getArray();
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        if (i <= j) {
+          U[i][j] = LU[i][j];
+        } else {
+          U[i][j] = 0.0;
+        }
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Return pivot permutation vector
+   * @return     piv
+   */
+  public int[] getPivot() {
+    int[] p = new int[m];
+    for (int i = 0; i < m; i++) {
+      p[i] = piv[i];
+    }
+    return p;
+  }
+
+  /** 
+   * Return pivot permutation vector as a one-dimensional double array
+   * @return     (double) piv
+   */
+  public double[] getDoublePivot() {
+    double[] vals = new double[m];
+    for (int i = 0; i < m; i++) {
+      vals[i] = (double) piv[i];
+    }
+    return vals;
+  }
+
+  /** 
+   * Determinant
+   * @return     det(A)
+   * @exception  IllegalArgumentException  Matrix must be square
+   */
+  public double det() {
+    if (m != n) {
+      throw new IllegalArgumentException("Matrix must be square.");
+    }
+    double d = (double) pivsign;
+    for (int j = 0; j < n; j++) {
+      d *= LU[j][j];
+    }
+    return d;
+  }
+
+  /** 
+   * Solve A*X = B
+   * @param  B   A Matrix with as many rows as A and any number of columns.
+   * @return     X so that L*U*X = B(piv,:)
+   * @exception  IllegalArgumentException Matrix row dimensions must agree.
+   * @exception  RuntimeException  Matrix is singular.
+   */
+  public Matrix solve(Matrix B) {
+    if (B.getRowDimension() != m) {
+      throw new IllegalArgumentException("Matrix row dimensions must agree.");
+    }
+    if (!this.isNonsingular()) {
+      throw new RuntimeException("Matrix is singular.");
+    }
+
+    // Copy right hand side with pivoting
+    int nx = B.getColumnDimension();
+    Matrix Xmat = B.getMatrix(piv,0,nx-1);
+    double[][] X = Xmat.getArray();
+
+    // Solve L*Y = B(piv,:)
+    for (int k = 0; k < n; k++) {
+      for (int i = k+1; i < n; i++) {
+        for (int j = 0; j < nx; j++) {
+          X[i][j] -= X[k][j]*LU[i][k];
+        }
+      }
+    }
+    // Solve U*X = Y;
+    for (int k = n-1; k >= 0; k--) {
+      for (int j = 0; j < nx; j++) {
+        X[k][j] /= LU[k][k];
+      }
+      for (int i = 0; i < k; i++) {
+        for (int j = 0; j < nx; j++) {
+          X[i][j] -= X[k][j]*LU[i][k];
+        }
+      }
+    }
+    return Xmat;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/LinearRegression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/LinearRegression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/LinearRegression.java	(revision 29)
@@ -0,0 +1,142 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * LinearRegression.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ * Class for performing (ridged) linear regression.
+ *
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+ 
+public class LinearRegression
+  implements RevisionHandler {
+
+  /** the coefficients */
+  protected double[] m_Coefficients = null;
+
+  /**
+   * Performs a (ridged) linear regression.
+   *
+   * @param a the matrix to perform the regression on
+   * @param y the dependent variable vector
+   * @param ridge the ridge parameter
+   * @throws IllegalArgumentException if not successful
+   */
+  public LinearRegression(Matrix a, Matrix y, double ridge) {
+    calculate(a, y, ridge);
+  }
+
+  /**
+   * Performs a weighted (ridged) linear regression. 
+   *
+   * @param a the matrix to perform the regression on
+   * @param y the dependent variable vector
+   * @param w the array of data point weights
+   * @param ridge the ridge parameter
+   * @throws IllegalArgumentException if the wrong number of weights were
+   * provided.
+   */
+  public LinearRegression(Matrix a, Matrix y, double[] w, double ridge) {
+
+    if (w.length != a.getRowDimension())
+      throw new IllegalArgumentException("Incorrect number of weights provided");
+    Matrix weightedThis = new Matrix(
+                              a.getRowDimension(), a.getColumnDimension());
+    Matrix weightedDep = new Matrix(a.getRowDimension(), 1);
+    for (int i = 0; i < w.length; i++) {
+      double sqrt_weight = Math.sqrt(w[i]);
+      for (int j = 0; j < a.getColumnDimension(); j++)
+        weightedThis.set(i, j, a.get(i, j) * sqrt_weight);
+      weightedDep.set(i, 0, y.get(i, 0) * sqrt_weight);
+    }
+
+    calculate(weightedThis, weightedDep, ridge);
+  }
+
+  /**
+   * performs the actual regression.
+   *
+   * @param a the matrix to perform the regression on
+   * @param y the dependent variable vector
+   * @param ridge the ridge parameter
+   * @throws IllegalArgumentException if not successful
+   */
+  protected void calculate(Matrix a, Matrix y, double ridge) {
+
+    if (y.getColumnDimension() > 1)
+      throw new IllegalArgumentException("Only one dependent variable allowed");
+
+    int nc = a.getColumnDimension();
+    m_Coefficients = new double[nc];
+    Matrix xt = a.transpose();
+    Matrix solution;
+
+    boolean success = true;
+
+    do {
+      Matrix ss = xt.times(a);
+
+      // Set ridge regression adjustment
+      for (int i = 0; i < nc; i++)
+        ss.set(i, i, ss.get(i, i) + ridge);
+
+      // Carry out the regression
+      Matrix bb = xt.times(y);
+      for(int i = 0; i < nc; i++)
+        m_Coefficients[i] = bb.get(i, 0);
+
+      try {
+        solution = ss.solve(new Matrix(m_Coefficients, m_Coefficients.length));
+        for (int i = 0; i < nc; i++)
+          m_Coefficients[i] = solution.get(i, 0);
+        success = true;
+      } 
+      catch (Exception ex) {
+        ridge *= 10;
+        success = false;
+      }
+    } while (!success);
+  }
+
+  /**
+   * returns the calculated coefficients
+   *
+   * @return the coefficients
+   */
+  public final double[] getCoefficients() {
+    return m_Coefficients;
+  }
+
+  /**
+   * returns the coefficients in a string representation
+   */
+  public String toString() {
+    return Utils.arrayToString(getCoefficients());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/Maths.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/Maths.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/Maths.java	(revision 29)
@@ -0,0 +1,366 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * Maths.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+
+import java.util.Random;
+
+/**
+ * Utility class.
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Maths
+  implements RevisionHandler {
+  
+  /** The constant 1 / sqrt(2 pi) */
+  public static final double PSI = 0.3989422804014327028632;
+
+  /** The constant - log( sqrt(2 pi) ) */
+  public static final double logPSI = -0.9189385332046726695410;
+
+  /** Distribution type: undefined */
+  public static final int undefinedDistribution = 0;
+
+  /** Distribution type: noraml */
+  public static final int normalDistribution = 1;
+
+  /** Distribution type: chi-squared */
+  public static final int chisqDistribution = 2;
+
+  /** 
+   * sqrt(a^2 + b^2) without under/overflow. 
+   */
+  public static double hypot(double a, double b) {
+    double r;
+    if (Math.abs(a) > Math.abs(b)) {
+      r = b/a;
+      r = Math.abs(a)*Math.sqrt(1+r*r);
+    } else if (b != 0) {
+      r = a/b;
+      r = Math.abs(b)*Math.sqrt(1+r*r);
+    } else {
+      r = 0.0;
+    }
+    return r;
+  }
+
+  /**
+   *  Returns the square of a value
+   *  @param x 
+   *  @return the square
+   */
+  public static double  square( double x ) 
+  {
+    return x * x;
+  }
+
+  /* methods for normal distribution */
+
+  /**
+   *  Returns the cumulative probability of the standard normal.
+   *  @param x the quantile
+   */
+  public static double  pnorm( double x ) 
+  {
+    return Statistics.normalProbability( x );
+  }
+    
+  /** 
+   *  Returns the cumulative probability of a normal distribution.
+   *  @param x the quantile
+   *  @param mean the mean of the normal distribution
+   *  @param sd the standard deviation of the normal distribution.
+   */
+  public static double  pnorm( double x, double mean, double sd ) 
+  {
+    if( sd <= 0.0 )
+      throw new IllegalArgumentException("standard deviation <= 0.0");
+    return pnorm( (x - mean) / sd );
+  }
+    
+  /** 
+   *  Returns the cumulative probability of a set of normal distributions
+   *  with different means.
+   *  @param x the vector of quantiles
+   *  @param mean the means of the normal distributions
+   *  @param sd the standard deviation of the normal distribution.
+   *  @return the cumulative probability */
+  public static DoubleVector  pnorm( double x, DoubleVector mean, 
+                                     double sd ) 
+  {
+    DoubleVector p = new DoubleVector( mean.size() );
+        
+    for( int i = 0; i < mean.size(); i++ ) {
+      p.set( i, pnorm(x, mean.get(i), sd) );
+    }
+    return p;
+  }
+    
+  /** Returns the density of the standard normal.
+   *  @param x the quantile
+   *  @return the density
+   */
+  public static double  dnorm( double x ) 
+  {
+    return Math.exp( - x * x / 2. ) * PSI;
+  }
+    
+  /** Returns the density value of a standard normal.
+   *  @param x the quantile
+   *  @param mean the mean of the normal distribution
+   *  @param sd the standard deviation of the normal distribution.
+   *  @return the density */
+  public static double  dnorm( double x, double mean, double sd ) 
+  {
+    if( sd <= 0.0 )
+      throw new IllegalArgumentException("standard deviation <= 0.0");
+    return dnorm( (x - mean) / sd );
+  }
+    
+  /** Returns the density values of a set of normal distributions with
+   *  different means.
+   *  @param x the quantile
+   *  @param mean the means of the normal distributions
+   *  @param sd the standard deviation of the normal distribution.
+   * @return the density */
+  public static DoubleVector  dnorm( double x, DoubleVector mean, 
+                                     double sd ) 
+  {
+    DoubleVector den = new DoubleVector( mean.size() );
+        
+    for( int i = 0; i < mean.size(); i++ ) {
+      den.set( i, dnorm(x, mean.get(i), sd) );
+    }
+    return den;
+  }
+    
+  /** Returns the log-density of the standard normal.
+   *  @param x the quantile
+   *  @return the density
+   */
+  public static double  dnormLog( double x ) 
+  {
+    return logPSI - x * x / 2.;
+  }
+    
+  /** Returns the log-density value of a standard normal.
+   *  @param x the quantile
+   *  @param mean the mean of the normal distribution
+   *  @param sd the standard deviation of the normal distribution.
+   *  @return the density */
+  public static double  dnormLog( double x, double mean, double sd ) {
+    if( sd <= 0.0 )
+      throw new IllegalArgumentException("standard deviation <= 0.0");
+    return - Math.log(sd) + dnormLog( (x - mean) / sd );
+  }
+    
+  /** Returns the log-density values of a set of normal distributions with
+   *  different means.
+   *  @param x the quantile
+   *  @param mean the means of the normal distributions
+   *  @param sd the standard deviation of the normal distribution.
+   * @return the density */
+  public static DoubleVector  dnormLog( double x, DoubleVector mean, 
+                                        double sd ) 
+  {
+    DoubleVector denLog = new DoubleVector( mean.size() );
+        
+    for( int i = 0; i < mean.size(); i++ ) {
+      denLog.set( i, dnormLog(x, mean.get(i), sd) );
+    }
+    return denLog;
+  }
+    
+  /** 
+   *  Generates a sample of a normal distribution.
+   *  @param n the size of the sample
+   *  @param mean the mean of the normal distribution
+   *  @param sd the standard deviation of the normal distribution.
+   *  @param random the random stream
+   *  @return the sample
+   */
+  public static DoubleVector rnorm( int n, double mean, double sd, 
+                                    Random random ) 
+  {
+    if( sd < 0.0)
+      throw new IllegalArgumentException("standard deviation < 0.0");
+        
+    if( sd == 0.0 ) return new DoubleVector( n, mean );
+    DoubleVector v = new DoubleVector( n );
+    for( int i = 0; i < n; i++ ) 
+      v.set( i, (random.nextGaussian() + mean) / sd );
+    return v;
+  }
+    
+  /* methods for Chi-square distribution */
+
+  /** Returns the cumulative probability of the Chi-squared distribution
+   *  @param x the quantile
+   */
+  public static double  pchisq( double x ) 
+  {
+    double xh = Math.sqrt( x );
+    return pnorm( xh ) - pnorm( -xh );
+  }
+    
+  /** Returns the cumulative probability of the noncentral Chi-squared
+   *  distribution.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameter */
+  public static double  pchisq( double x, double ncp ) 
+  {
+    double mean = Math.sqrt( ncp );
+    double xh = Math.sqrt( x );
+    return pnorm( xh - mean ) - pnorm( -xh - mean );
+  }
+    
+  /** Returns the cumulative probability of a set of noncentral Chi-squared
+   *  distributions.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameters */
+  public static DoubleVector  pchisq( double x, DoubleVector ncp )
+  {
+    int n = ncp.size();
+    DoubleVector p = new DoubleVector( n );
+    double mean;
+    double xh = Math.sqrt( x );
+        
+    for( int i = 0; i < n; i++ ) {
+      mean = Math.sqrt( ncp.get(i) );
+      p.set( i, pnorm( xh - mean ) - pnorm( -xh - mean ) );
+    }
+    return p;
+  }
+    
+  /** Returns the density of the Chi-squared distribution.
+   *  @param x the quantile
+   *  @return the density
+   */
+  public static double  dchisq( double x ) 
+  {
+    if( x == 0.0 ) return Double.POSITIVE_INFINITY;
+    double xh = Math.sqrt( x );
+    return dnorm( xh ) / xh;
+  }
+    
+  /** Returns the density of the noncentral Chi-squared distribution.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameter
+   */
+  public static double  dchisq( double x, double ncp ) 
+  {
+    if( ncp == 0.0 ) return dchisq( x );
+    double xh = Math.sqrt( x );
+    double mean = Math.sqrt( ncp );
+    return (dnorm( xh - mean ) + dnorm( -xh - mean)) / (2 * xh);
+  }
+    
+  /** Returns the density of the noncentral Chi-squared distribution.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameters 
+   */
+  public static DoubleVector  dchisq( double x, DoubleVector ncp )
+  {
+    int n = ncp.size();
+    DoubleVector d = new DoubleVector( n );
+    double xh = Math.sqrt( x );
+    double mean;
+    for( int i = 0; i < n; i++ ) {
+      mean = Math.sqrt( ncp.get(i) );
+      if( ncp.get(i) == 0.0 ) d.set( i, dchisq( x ) );
+      else d.set( i, (dnorm( xh - mean ) + dnorm( -xh - mean)) / 
+                  (2 * xh) );
+    }
+    return d;
+  }
+    
+  /** Returns the log-density of the noncentral Chi-square distribution.
+   *  @param x the quantile
+   *  @return the density
+   */
+  public static double  dchisqLog( double x ) 
+  {
+    if( x == 0.0) return Double.POSITIVE_INFINITY;
+    double xh = Math.sqrt( x );
+    return dnormLog( xh ) - Math.log( xh );
+  }
+    
+  /** Returns the log-density value of a noncentral Chi-square distribution.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameter
+   *  @return the density */
+  public static double  dchisqLog( double x, double ncp ) {
+    if( ncp == 0.0 ) return dchisqLog( x );
+    double xh = Math.sqrt( x );
+    double mean = Math.sqrt( ncp );
+    return Math.log( dnorm( xh - mean ) + dnorm( -xh - mean) ) - 
+    Math.log(2 * xh);
+  }
+    
+  /** Returns the log-density of a set of noncentral Chi-squared
+   *  distributions.
+   *  @param x the quantile
+   *  @param ncp the noncentral parameters */
+  public static DoubleVector  dchisqLog( double x, DoubleVector ncp )
+  {
+    DoubleVector dLog = new DoubleVector( ncp.size() );
+    double xh = Math.sqrt( x );
+    double mean;
+        
+    for( int i = 0; i < ncp.size(); i++ ) {
+      mean = Math.sqrt( ncp.get(i) );
+      if( ncp.get(i) == 0.0 ) dLog.set( i, dchisqLog( x ) );
+      else dLog.set( i, Math.log( dnorm( xh - mean ) + dnorm( -xh - mean) ) - 
+                     Math.log(2 * xh) );
+    }
+    return dLog;
+  }
+    
+  /** 
+   *  Generates a sample of a Chi-square distribution.
+   *  @param n the size of the sample
+   *  @param ncp the noncentral parameter
+   *  @param random the random stream
+   *  @return the sample
+   */
+  public static DoubleVector  rchisq( int n, double ncp, Random random ) 
+  {
+    DoubleVector v = new DoubleVector( n );
+    double mean = Math.sqrt( ncp );
+    double x;
+    for( int i = 0; i < n; i++ ) {
+      x = random.nextGaussian() + mean;
+      v.set( i, x * x );
+    }
+    return v;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/Matrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/Matrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/Matrix.java	(revision 29)
@@ -0,0 +1,1548 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * Matrix.java
+ * Copyright (C) 1999 The Mathworks and NIST and 2005 University of Waikato,
+ *               Hamilton, New Zealand
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.LineNumberReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+/**
+ * Jama = Java Matrix class.
+ * <P>
+ * The Java Matrix Class provides the fundamental operations of numerical linear
+ * algebra.  Various constructors create Matrices from two dimensional arrays of
+ * double precision floating point numbers.  Various "gets" and "sets" provide
+ * access to submatrices and matrix elements.  Several methods implement basic
+ * matrix arithmetic, including matrix addition and multiplication, matrix
+ * norms, and element-by-element array operations.  Methods for reading and
+ * printing matrices are also included.  All the operations in this version of
+ * the Matrix Class involve real matrices.  Complex matrices may be handled in a
+ * future version.
+ * <P>
+ * Five fundamental matrix decompositions, which consist of pairs or triples of
+ * matrices, permutation vectors, and the like, produce results in five
+ * decomposition classes.  These decompositions are accessed by the Matrix class
+ * to compute solutions of simultaneous linear equations, determinants, inverses
+ * and other matrix functions.  The five decompositions are:
+ * <P>
+ * <UL>
+ *    <LI>Cholesky Decomposition of symmetric, positive definite matrices.
+ *    <LI>LU Decomposition of rectangular matrices.
+ *    <LI>QR Decomposition of rectangular matrices.
+ *    <LI>Singular Value Decomposition of rectangular matrices.
+ *    <LI>Eigenvalue Decomposition of both symmetric and nonsymmetric square matrices.
+ * </UL>
+ * <DL>
+ * <DT><B>Example of use:</B></DT>
+ * <P>
+ * <DD>Solve a linear system A x = b and compute the residual norm, ||b - A x||.
+ * <P><PRE>
+ *       double[][] vals = {{1.,2.,3},{4.,5.,6.},{7.,8.,10.}};
+ *       Matrix A = new Matrix(vals);
+ *       Matrix b = Matrix.random(3,1);
+ *       Matrix x = A.solve(b);
+ *       Matrix r = A.times(x).minus(b);
+ *       double rnorm = r.normInf();
+ * </PRE></DD>
+ * </DL>
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package. Additional methods are tagged with the 
+ * <code>@author</code> tag.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Matrix 
+  implements Cloneable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7856794138418366180L;
+
+  /** 
+   * Array for internal storage of elements.
+   * @serial internal array storage.
+   */
+  protected double[][] A;
+
+  /** 
+   * Row and column dimensions.
+   * @serial row dimension.
+   * @serial column dimension.
+   */
+  protected int m, n;
+
+  /** 
+   * Construct an m-by-n matrix of zeros. 
+   * @param m    Number of rows.
+   * @param n    Number of colums.
+   */
+  public Matrix(int m, int n) {
+    this.m = m;
+    this.n = n;
+    A = new double[m][n];
+  }
+
+  /** 
+   * Construct an m-by-n constant matrix.
+   * @param m    Number of rows.
+   * @param n    Number of colums.
+   * @param s    Fill the matrix with this scalar value.
+   */
+  public Matrix(int m, int n, double s) {
+    this.m = m;
+    this.n = n;
+    A = new double[m][n];
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = s;
+      }
+    }
+  }
+
+  /** 
+   * Construct a matrix from a 2-D array.
+   * @param A    Two-dimensional array of doubles.
+   * @throws  IllegalArgumentException All rows must have the same length
+   * @see        #constructWithCopy
+   */
+  public Matrix(double[][] A) {
+    m = A.length;
+    n = A[0].length;
+    for (int i = 0; i < m; i++) {
+      if (A[i].length != n) {
+        throw new IllegalArgumentException("All rows must have the same length.");
+      }
+    }
+    this.A = A;
+  }
+
+  /** 
+   * Construct a matrix quickly without checking arguments.
+   * @param A    Two-dimensional array of doubles.
+   * @param m    Number of rows.
+   * @param n    Number of colums.
+   */
+  public Matrix(double[][] A, int m, int n) {
+    this.A = A;
+    this.m = m;
+    this.n = n;
+  }
+
+  /** 
+   * Construct a matrix from a one-dimensional packed array
+   * @param vals One-dimensional array of doubles, packed by columns (ala
+   * Fortran).
+   * @param m    Number of rows.
+   * @throws  IllegalArgumentException Array length must be a multiple of m.
+   */
+  public Matrix(double vals[], int m) {
+    this.m = m;
+    n = (m != 0 ? vals.length/m : 0);
+    if (m*n != vals.length) {
+      throw new IllegalArgumentException("Array length must be a multiple of m.");
+    }
+    A = new double[m][n];
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = vals[i+j*m];
+      }
+    }
+  }
+
+  /**
+   * Reads a matrix from a reader. The first line in the file should
+   * contain the number of rows and columns. Subsequent lines
+   * contain elements of the matrix.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     r the reader containing the matrix
+   * @throws    Exception if an error occurs
+   * @see       #write(Writer)
+   */
+  public Matrix(Reader r) throws Exception {
+    LineNumberReader lnr = new LineNumberReader(r);
+    String line;
+    int currentRow = -1;
+
+    while ((line = lnr.readLine()) != null) {
+
+      // Comments
+      if (line.startsWith("%"))  
+        continue;
+      
+      StringTokenizer st = new StringTokenizer(line);
+      // Ignore blank lines
+      if (!st.hasMoreTokens())  
+        continue;
+
+      if (currentRow < 0) {
+        int rows = Integer.parseInt(st.nextToken());
+        if (!st.hasMoreTokens())
+          throw new Exception("Line " + lnr.getLineNumber() 
+              + ": expected number of columns");
+
+        int cols = Integer.parseInt(st.nextToken());
+        A = new double[rows][cols];
+        m = rows;
+        n = cols;
+        currentRow++;
+        continue;
+
+      } 
+      else {
+        if (currentRow == getRowDimension())
+          throw new Exception("Line " + lnr.getLineNumber() 
+              + ": too many rows provided");
+
+        for (int i = 0; i < getColumnDimension(); i++) {
+          if (!st.hasMoreTokens())
+            throw new Exception("Line " + lnr.getLineNumber() 
+                + ": too few matrix elements provided");
+
+          set(currentRow, i, Double.valueOf(st.nextToken()).doubleValue());
+        }
+        currentRow++;
+      }
+    }
+
+    if (currentRow == -1)
+      throw new Exception("Line " + lnr.getLineNumber() 
+          + ": expected number of rows");
+    else if (currentRow != getRowDimension())
+      throw new Exception("Line " + lnr.getLineNumber() 
+          + ": too few rows provided");
+  }
+
+  /** 
+   * Construct a matrix from a copy of a 2-D array.
+   * @param A    Two-dimensional array of doubles.
+   * @throws  IllegalArgumentException All rows must have the same length
+   */
+  public static Matrix constructWithCopy(double[][] A) {
+    int m = A.length;
+    int n = A[0].length;
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      if (A[i].length != n) {
+        throw new IllegalArgumentException
+          ("All rows must have the same length.");
+      }
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Make a deep copy of a matrix
+   */
+  public Matrix copy() {
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Clone the Matrix object.
+   */
+  public Object clone() {
+    return this.copy();
+  }
+
+  /** 
+   * Access the internal two-dimensional array.
+   * @return     Pointer to the two-dimensional array of matrix elements.
+   */
+  public double[][] getArray() {
+    return A;
+  }
+
+  /** 
+   * Copy the internal two-dimensional array.
+   * @return     Two-dimensional array copy of matrix elements.
+   */
+  public double[][] getArrayCopy() {
+    double[][] C = new double[m][n];
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j];
+      }
+    }
+    return C;
+  }
+
+  /** 
+   * Make a one-dimensional column packed copy of the internal array.
+   * @return     Matrix elements packed in a one-dimensional array by columns.
+   */
+  public double[] getColumnPackedCopy() {
+    double[] vals = new double[m*n];
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        vals[i+j*m] = A[i][j];
+      }
+    }
+    return vals;
+  }
+
+  /** 
+   * Make a one-dimensional row packed copy of the internal array.
+   * @return     Matrix elements packed in a one-dimensional array by rows.
+   */
+  public double[] getRowPackedCopy() {
+    double[] vals = new double[m*n];
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        vals[i*n+j] = A[i][j];
+      }
+    }
+    return vals;
+  }
+
+  /** 
+   * Get row dimension.
+   * @return     m, the number of rows.
+   */
+  public int getRowDimension() {
+    return m;
+  }
+
+  /** 
+   * Get column dimension.
+   * @return     n, the number of columns.
+   */
+  public int getColumnDimension() {
+    return n;
+  }
+
+  /** 
+   * Get a single element.
+   * @param i    Row index.
+   * @param j    Column index.
+   * @return     A(i,j)
+   * @throws  ArrayIndexOutOfBoundsException
+   */
+  public double get(int i, int j) {
+    return A[i][j];
+  }
+
+  /** 
+   * Get a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @return     A(i0:i1,j0:j1)
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public Matrix getMatrix(int i0, int i1, int j0, int j1) {
+    Matrix X = new Matrix(i1-i0+1,j1-j0+1);
+    double[][] B = X.getArray();
+    try {
+      for (int i = i0; i <= i1; i++) {
+        for (int j = j0; j <= j1; j++) {
+          B[i-i0][j-j0] = A[i][j];
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+    return X;
+  }
+
+  /** 
+   * Get a submatrix.
+   * @param r    Array of row indices.
+   * @param c    Array of column indices.
+   * @return     A(r(:),c(:))
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public Matrix getMatrix(int[] r, int[] c) {
+    Matrix X = new Matrix(r.length,c.length);
+    double[][] B = X.getArray();
+    try {
+      for (int i = 0; i < r.length; i++) {
+        for (int j = 0; j < c.length; j++) {
+          B[i][j] = A[r[i]][c[j]];
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+    return X;
+  }
+
+  /** 
+   * Get a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param c    Array of column indices.
+   * @return     A(i0:i1,c(:))
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public Matrix getMatrix(int i0, int i1, int[] c) {
+    Matrix X = new Matrix(i1-i0+1,c.length);
+    double[][] B = X.getArray();
+    try {
+      for (int i = i0; i <= i1; i++) {
+        for (int j = 0; j < c.length; j++) {
+          B[i-i0][j] = A[i][c[j]];
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+    return X;
+  }
+
+  /** 
+   * Get a submatrix.
+   * @param r    Array of row indices.
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @return     A(r(:),j0:j1)
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public Matrix getMatrix(int[] r, int j0, int j1) {
+    Matrix X = new Matrix(r.length,j1-j0+1);
+    double[][] B = X.getArray();
+    try {
+      for (int i = 0; i < r.length; i++) {
+        for (int j = j0; j <= j1; j++) {
+          B[i][j-j0] = A[r[i]][j];
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+    return X;
+  }
+
+  /** 
+   * Set a single element.
+   * @param i    Row index.
+   * @param j    Column index.
+   * @param s    A(i,j).
+   * @throws  ArrayIndexOutOfBoundsException
+   */
+  public void set(int i, int j, double s) {
+    A[i][j] = s;
+  }
+
+  /** 
+   * Set a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @param X    A(i0:i1,j0:j1)
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int i0, int i1, int j0, int j1, Matrix X) {
+    try {
+      for (int i = i0; i <= i1; i++) {
+        for (int j = j0; j <= j1; j++) {
+          A[i][j] = X.get(i-i0,j-j0);
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+  }
+
+  /** 
+   * Set a submatrix.
+   * @param r    Array of row indices.
+   * @param c    Array of column indices.
+   * @param X    A(r(:),c(:))
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int[] r, int[] c, Matrix X) {
+    try {
+      for (int i = 0; i < r.length; i++) {
+        for (int j = 0; j < c.length; j++) {
+          A[r[i]][c[j]] = X.get(i,j);
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+  }
+
+  /** 
+   * Set a submatrix.
+   * @param r    Array of row indices.
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @param X    A(r(:),j0:j1)
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int[] r, int j0, int j1, Matrix X) {
+    try {
+      for (int i = 0; i < r.length; i++) {
+        for (int j = j0; j <= j1; j++) {
+          A[r[i]][j] = X.get(i,j-j0);
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+  }
+
+  /** 
+   * Set a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param c    Array of column indices.
+   * @param X    A(i0:i1,c(:))
+   * @throws  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int i0, int i1, int[] c, Matrix X) {
+    try {
+      for (int i = i0; i <= i1; i++) {
+        for (int j = 0; j < c.length; j++) {
+          A[i][c[j]] = X.get(i-i0,j);
+        }
+      }
+    } catch(ArrayIndexOutOfBoundsException e) {
+      throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+    }
+  }
+  
+  /**
+   * Returns true if the matrix is symmetric.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @return boolean true if matrix is symmetric.
+   */
+  public boolean isSymmetric() {
+    int nr = A.length, nc = A[0].length;
+    if (nr != nc)
+      return false;
+
+    for (int i = 0; i < nc; i++) {
+      for (int j = 0; j < i; j++) {
+        if (A[i][j] != A[j][i])
+          return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * returns whether the matrix is a square matrix or not.
+   *
+   * @return true if the matrix is a square matrix
+   */
+  public boolean isSquare() {
+    return (getRowDimension() == getColumnDimension());
+  }
+
+  /** 
+   * Matrix transpose.
+   * @return    A'
+   */
+  public Matrix transpose() {
+    Matrix X = new Matrix(n,m);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[j][i] = A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * One norm
+   * @return    maximum column sum.
+   */
+  public double norm1() {
+    double f = 0;
+    for (int j = 0; j < n; j++) {
+      double s = 0;
+      for (int i = 0; i < m; i++) {
+        s += Math.abs(A[i][j]);
+      }
+      f = Math.max(f,s);
+    }
+    return f;
+  }
+
+  /** 
+   * Two norm
+   * @return    maximum singular value.
+   */
+  public double norm2() {
+    return (new SingularValueDecomposition(this).norm2());
+  }
+
+  /** 
+   * Infinity norm
+   * @return    maximum row sum.
+   */
+  public double normInf() {
+    double f = 0;
+    for (int i = 0; i < m; i++) {
+      double s = 0;
+      for (int j = 0; j < n; j++) {
+        s += Math.abs(A[i][j]);
+      }
+      f = Math.max(f,s);
+    }
+    return f;
+  }
+
+  /** 
+   * Frobenius norm
+   * @return    sqrt of sum of squares of all elements.
+   */
+  public double normF() {
+    double f = 0;
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        f = Maths.hypot(f,A[i][j]);
+      }
+    }
+    return f;
+  }
+
+  /**  
+   * Unary minus
+   * @return    -A
+   */
+  public Matrix uminus() {
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = -A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * C = A + B
+   * @param B    another matrix
+   * @return     A + B
+   */
+  public Matrix plus(Matrix B) {
+    checkMatrixDimensions(B);
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j] + B.A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * A = A + B
+   * @param B    another matrix
+   * @return     A + B
+   */
+  public Matrix plusEquals(Matrix B) {
+    checkMatrixDimensions(B);
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = A[i][j] + B.A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * C = A - B
+   * @param B    another matrix
+   * @return     A - B
+   */
+  public Matrix minus(Matrix B) {
+    checkMatrixDimensions(B);
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j] - B.A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * A = A - B
+   * @param B    another matrix
+   * @return     A - B
+   */
+  public Matrix minusEquals(Matrix B) {
+    checkMatrixDimensions(B);
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = A[i][j] - B.A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * Element-by-element multiplication, C = A.*B
+   * @param B    another matrix
+   * @return     A.*B
+   */
+  public Matrix arrayTimes(Matrix B) {
+    checkMatrixDimensions(B);
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j] * B.A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Element-by-element multiplication in place, A = A.*B
+   * @param B    another matrix
+   * @return     A.*B
+   */
+  public Matrix arrayTimesEquals(Matrix B) {
+    checkMatrixDimensions(B);
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = A[i][j] * B.A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * Element-by-element right division, C = A./B
+   * @param B    another matrix
+   * @return     A./B
+   */
+  public Matrix arrayRightDivide(Matrix B) {
+    checkMatrixDimensions(B);
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = A[i][j] / B.A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Element-by-element right division in place, A = A./B
+   * @param B    another matrix
+   * @return     A./B
+   */
+  public Matrix arrayRightDivideEquals(Matrix B) {
+    checkMatrixDimensions(B);
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = A[i][j] / B.A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * Element-by-element left division, C = A.\B
+   * @param B    another matrix
+   * @return     A.\B
+   */
+  public Matrix arrayLeftDivide(Matrix B) {
+    checkMatrixDimensions(B);
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = B.A[i][j] / A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Element-by-element left division in place, A = A.\B
+   * @param B    another matrix
+   * @return     A.\B
+   */
+  public Matrix arrayLeftDivideEquals(Matrix B) {
+    checkMatrixDimensions(B);
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = B.A[i][j] / A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * Multiply a matrix by a scalar, C = s*A
+   * @param s    scalar
+   * @return     s*A
+   */
+  public Matrix times(double s) {
+    Matrix X = new Matrix(m,n);
+    double[][] C = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        C[i][j] = s*A[i][j];
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Multiply a matrix by a scalar in place, A = s*A
+   * @param s    scalar
+   * @return     replace A by s*A
+   */
+  public Matrix timesEquals(double s) {
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        A[i][j] = s*A[i][j];
+      }
+    }
+    return this;
+  }
+
+  /** 
+   * Linear algebraic matrix multiplication, A * B
+   * @param B    another matrix
+   * @return     Matrix product, A * B
+   * @throws  IllegalArgumentException Matrix inner dimensions must agree.
+   */
+  public Matrix times(Matrix B) {
+    if (B.m != n) {
+      throw new IllegalArgumentException("Matrix inner dimensions must agree.");
+    }
+    Matrix X = new Matrix(m,B.n);
+    double[][] C = X.getArray();
+    double[] Bcolj = new double[n];
+    for (int j = 0; j < B.n; j++) {
+      for (int k = 0; k < n; k++) {
+        Bcolj[k] = B.A[k][j];
+      }
+      for (int i = 0; i < m; i++) {
+        double[] Arowi = A[i];
+        double s = 0;
+        for (int k = 0; k < n; k++) {
+          s += Arowi[k]*Bcolj[k];
+        }
+        C[i][j] = s;
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * LU Decomposition
+   * @return     LUDecomposition
+   * @see LUDecomposition
+   */
+  public LUDecomposition lu() {
+    return new LUDecomposition(this);
+  }
+
+  /** 
+   * QR Decomposition
+   * @return     QRDecomposition
+   * @see QRDecomposition
+   */
+  public QRDecomposition qr() {
+    return new QRDecomposition(this);
+  }
+
+  /** 
+   * Cholesky Decomposition
+   * @return     CholeskyDecomposition
+   * @see CholeskyDecomposition
+   */
+  public CholeskyDecomposition chol() {
+    return new CholeskyDecomposition(this);
+  }
+
+  /** 
+   * Singular Value Decomposition
+   * @return     SingularValueDecomposition
+   * @see SingularValueDecomposition
+   */
+  public SingularValueDecomposition svd() {
+    return new SingularValueDecomposition(this);
+  }
+
+  /** 
+   * Eigenvalue Decomposition
+   * @return     EigenvalueDecomposition
+   * @see EigenvalueDecomposition
+   */
+  public EigenvalueDecomposition eig() {
+    return new EigenvalueDecomposition(this);
+  }
+
+  /** 
+   * Solve A*X = B
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public Matrix solve(Matrix B) {
+    return (m == n ? (new LUDecomposition(this)).solve(B) :
+        (new QRDecomposition(this)).solve(B));
+  }
+
+  /** 
+   * Solve X*A = B, which is also A'*X' = B'
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise.
+   */
+  public Matrix solveTranspose(Matrix B) {
+    return transpose().solve(B.transpose());
+  }
+
+  /** 
+   * Matrix inverse or pseudoinverse
+   * @return     inverse(A) if A is square, pseudoinverse otherwise.
+   */
+  public Matrix inverse() {
+    return solve(identity(m,m));
+  }
+
+  /**
+   * returns the square root of the matrix, i.e., X from the equation
+   * X*X = A.<br/>
+   * Steps in the Calculation (see <a href="http://www.mathworks.com/access/helpdesk/help/techdoc/ref/sqrtm.html" target="blank"><code>sqrtm</code></a> in Matlab):<br/>
+   * <ol>
+   *   <li>perform eigenvalue decomposition<br/>[V,D]=eig(A)</li>
+   *   <li>take the square root of all elements in D (only the ones with 
+   *       positive sign are considered for further computation)<br/>
+   *       S=sqrt(D)</li>
+   *   <li>calculate the root<br/>
+   *       X=V*S/V, which can be also written as X=(V'\(V*S)')'</li>
+   * </ol>
+   * <p/>
+   * <b>Note:</b> since this method uses other high-level methods, it generates
+   * several instances of matrices. This can be problematic with large
+   * matrices.
+   * <p/>
+   * Examples:
+   * <ol>
+   *   <li>
+   *   <pre>
+   *  X =
+   *   5   -4    1    0    0
+   *  -4    6   -4    1    0
+   *   1   -4    6   -4    1
+   *   0    1   -4    6   -4
+   *   0    0    1   -4    5
+   * 
+   *  sqrt(X) =
+   *   2   -1   -0   -0   -0 
+   *  -1    2   -1    0   -0 
+   *   0   -1    2   -1    0 
+   *  -0    0   -1    2   -1 
+   *  -0   -0   -0   -1    2 
+   *  
+   *  Matrix m = new Matrix(new double[][]{{5,-4,1,0,0},{-4,6,-4,1,0},{1,-4,6,-4,1},{0,1,-4,6,-4},{0,0,1,-4,5}});
+   *   </pre>
+   *   </li>
+   *   <li>
+   *   <pre>
+   *  X =
+   *   7   10
+   *  15   22
+   *  
+   *  sqrt(X) =
+   *  1.5667    1.7408
+   *  2.6112    4.1779
+   * 
+   *  Matrix m = new Matrix(new double[][]{{7, 10},{15, 22}});
+   *   </pre>
+   *   </li>
+   * </ol>
+   *
+   * @return    sqrt(A)
+   */
+  public Matrix sqrt() {
+    EigenvalueDecomposition   evd;
+    Matrix                    s;
+    Matrix                    v;
+    Matrix                    d;
+    Matrix                    result;
+    Matrix                    a;
+    Matrix                    b;
+    int                       i;
+    int                       n;
+
+    result = null;
+    
+    // eigenvalue decomp.
+    // [V, D] = eig(A) with A = this
+    evd = this.eig();
+    v   = evd.getV();
+    d   = evd.getD();
+
+    // S = sqrt of cells of D
+    s = new Matrix(d.getRowDimension(), d.getColumnDimension());
+    for (i = 0; i < s.getRowDimension(); i++)
+      for (n = 0; n < s.getColumnDimension(); n++)
+        s.set(i, n, StrictMath.sqrt(d.get(i, n)));
+
+    // to calculate:
+    //      result = V*S/V
+    //
+    //    with   X = B/A
+    //    and  B/A = (A'\B')'
+    //    and V=A and V*S=B
+    // we get 
+    //      result = (V'\(V*S)')'
+    //      
+    //         A*X = B
+    //           X = A\B
+    // which is 
+    //           X = A.solve(B)
+    //           
+    // with A=V' and B=(V*S)' 
+    // we get
+    //           X = V'.solve((V*S)')
+    // or
+    //      result = X'
+    //
+    // which is in full length
+    //      result = (V'.solve((V*S)'))'
+    a      = v.inverse();
+    b      = v.times(s).inverse();
+    v      = null;
+    d      = null;
+    evd    = null;
+    s      = null;
+    result = a.solve(b).inverse();
+
+    return result;
+  }
+
+  /**
+   * Performs a (ridged) linear regression.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     y the dependent variable vector
+   * @param     ridge the ridge parameter
+   * @return    the coefficients 
+   * @throws    IllegalArgumentException if not successful
+   */
+  public LinearRegression regression(Matrix y, double ridge) {
+    return new LinearRegression(this, y, ridge);
+  }
+
+  /**
+   * Performs a weighted (ridged) linear regression. 
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     y the dependent variable vector
+   * @param     w the array of data point weights
+   * @param     ridge the ridge parameter
+   * @return    the coefficients 
+   * @throws    IllegalArgumentException if the wrong number of weights were
+   *            provided.
+   */
+  public final LinearRegression regression(Matrix y, double[] w, double ridge) {
+    return new LinearRegression(this, y, w, ridge);
+  }
+
+  /** 
+   * Matrix determinant
+   * @return     determinant
+   */
+  public double det() {
+    return new LUDecomposition(this).det();
+  }
+
+  /** 
+   * Matrix rank
+   * @return     effective numerical rank, obtained from SVD.
+   */
+  public int rank() {
+    return new SingularValueDecomposition(this).rank();
+  }
+
+  /** 
+   * Matrix condition (2 norm)
+   * @return     ratio of largest to smallest singular value.
+   */
+  public double cond() {
+    return new SingularValueDecomposition(this).cond();
+  }
+
+  /** 
+   * Matrix trace.
+   * @return     sum of the diagonal elements.
+   */
+  public double trace() {
+    double t = 0;
+    for (int i = 0; i < Math.min(m,n); i++) {
+      t += A[i][i];
+    }
+    return t;
+  }
+
+  /** 
+   * Generate matrix with random elements
+   * @param m    Number of rows.
+   * @param n    Number of colums.
+   * @return     An m-by-n matrix with uniformly distributed random elements.
+   */
+  public static Matrix random(int m, int n) {
+    Matrix A = new Matrix(m,n);
+    double[][] X = A.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        X[i][j] = Math.random();
+      }
+    }
+    return A;
+  }
+
+  /** 
+   * Generate identity matrix
+   * @param m    Number of rows.
+   * @param n    Number of colums.
+   * @return     An m-by-n matrix with ones on the diagonal and zeros elsewhere.
+   */
+  public static Matrix identity(int m, int n) {
+    Matrix A = new Matrix(m,n);
+    double[][] X = A.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        X[i][j] = (i == j ? 1.0 : 0.0);
+      }
+    }
+    return A;
+  }
+
+  /** 
+   * Print the matrix to stdout.   Line the elements up in columns
+   * with a Fortran-like 'Fw.d' style format.
+   * @param w    Column width.
+   * @param d    Number of digits after the decimal.
+   */
+  public void print(int w, int d) {
+    print(new PrintWriter(System.out,true),w,d); 
+  }
+
+  /** 
+   * Print the matrix to the output stream.   Line the elements up in
+   * columns with a Fortran-like 'Fw.d' style format.
+   * @param output Output stream.
+   * @param w      Column width.
+   * @param d      Number of digits after the decimal.
+   */
+  public void print(PrintWriter output, int w, int d) {
+    DecimalFormat format = new DecimalFormat();
+    format.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
+    format.setMinimumIntegerDigits(1);
+    format.setMaximumFractionDigits(d);
+    format.setMinimumFractionDigits(d);
+    format.setGroupingUsed(false);
+    print(output,format,w+2);
+  }
+
+  /** 
+   * Print the matrix to stdout.  Line the elements up in columns.
+   * Use the format object, and right justify within columns of width
+   * characters.
+   * Note that is the matrix is to be read back in, you probably will want
+   * to use a NumberFormat that is set to US Locale.
+   * @param format A  Formatting object for individual elements.
+   * @param width     Field width for each column.
+   * @see java.text.DecimalFormat#setDecimalFormatSymbols
+   */
+  public void print(NumberFormat format, int width) {
+    print(new PrintWriter(System.out,true),format,width); 
+  }
+
+  // DecimalFormat is a little disappointing coming from Fortran or C's printf.
+  // Since it doesn't pad on the left, the elements will come out different
+  // widths.  Consequently, we'll pass the desired column width in as an
+  // argument and do the extra padding ourselves.
+
+  /** 
+   * Print the matrix to the output stream.  Line the elements up in columns.
+   * Use the format object, and right justify within columns of width
+   * characters.
+   * Note that is the matrix is to be read back in, you probably will want
+   * to use a NumberFormat that is set to US Locale.
+   * @param output the output stream.
+   * @param format A formatting object to format the matrix elements 
+   * @param width  Column width.
+   * @see java.text.DecimalFormat#setDecimalFormatSymbols
+   */
+  public void print(PrintWriter output, NumberFormat format, int width) {
+    output.println();  // start on new line.
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        String s = format.format(A[i][j]); // format the number
+        int padding = Math.max(1,width-s.length()); // At _least_ 1 space
+        for (int k = 0; k < padding; k++)
+          output.print(' ');
+        output.print(s);
+      }
+      output.println();
+    }
+    output.println();   // end with blank line.
+  }
+
+  /** 
+   * Read a matrix from a stream.  The format is the same the print method,
+   * so printed matrices can be read back in (provided they were printed using
+   * US Locale).  Elements are separated by
+   * whitespace, all the elements for each row appear on a single line,
+   * the last row is followed by a blank line.
+   * <p/>
+   * Note: This format differs from the one that can be read via the
+   * Matrix(Reader) constructor! For that format, the write(Writer) method
+   * is used (from the original weka.core.Matrix class).
+   *
+   * @param input the input stream.
+   * @see #Matrix(Reader)
+   * @see #write(Writer)
+   */
+  public static Matrix read(BufferedReader input) throws java.io.IOException {
+    StreamTokenizer tokenizer= new StreamTokenizer(input);
+
+    // Although StreamTokenizer will parse numbers, it doesn't recognize
+    // scientific notation (E or D); however, Double.valueOf does.
+    // The strategy here is to disable StreamTokenizer's number parsing.
+    // We'll only get whitespace delimited words, EOL's and EOF's.
+    // These words should all be numbers, for Double.valueOf to parse.
+
+    tokenizer.resetSyntax();
+    tokenizer.wordChars(0,255);
+    tokenizer.whitespaceChars(0, ' ');
+    tokenizer.eolIsSignificant(true);
+    java.util.Vector<Object> v = new java.util.Vector<Object>();
+
+    // Ignore initial empty lines
+    while (tokenizer.nextToken() == StreamTokenizer.TT_EOL);
+    if (tokenizer.ttype == StreamTokenizer.TT_EOF)
+      throw new java.io.IOException("Unexpected EOF on matrix read.");
+    do {
+      v.addElement(Double.valueOf(tokenizer.sval)); // Read & store 1st row.
+    } while (tokenizer.nextToken() == StreamTokenizer.TT_WORD);
+
+    int n = v.size();  // Now we've got the number of columns!
+    double row[] = new double[n];
+    for (int j=0; j<n; j++)  // extract the elements of the 1st row.
+      row[j]=((Double)v.elementAt(j)).doubleValue();
+    v.removeAllElements();
+    v.addElement(row);  // Start storing rows instead of columns.
+    while (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
+      // While non-empty lines
+      v.addElement(row = new double[n]);
+      int j = 0;
+      do {
+        if (j >= n) throw new java.io.IOException
+          ("Row " + v.size() + " is too long.");
+        row[j++] = Double.valueOf(tokenizer.sval).doubleValue();
+      } while (tokenizer.nextToken() == StreamTokenizer.TT_WORD);
+      if (j < n) throw new java.io.IOException
+        ("Row " + v.size() + " is too short.");
+    }
+    int m = v.size();  // Now we've got the number of rows.
+    double[][] A = new double[m][];
+    v.copyInto(A);  // copy the rows out of the vector
+    return new Matrix(A);
+  }
+
+
+  /** 
+   * Check if size(A) == size(B) 
+   */
+  private void checkMatrixDimensions(Matrix B) {
+    if (B.m != m || B.n != n) {
+      throw new IllegalArgumentException("Matrix dimensions must agree.");
+    }
+  }
+
+  /**
+   * Writes out a matrix. The format can be read via the Matrix(Reader)
+   * constructor.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @param     w the output Writer
+   * @throws    Exception if an error occurs
+   * @see       #Matrix(Reader)
+   */
+  public void write(Writer w) throws Exception {
+    w.write("% Rows\tColumns\n");
+    w.write("" + getRowDimension() + "\t" + getColumnDimension() + "\n");
+    w.write("% Matrix elements\n");
+    for(int i = 0; i < getRowDimension(); i++) {
+      for(int j = 0; j < getColumnDimension(); j++)
+        w.write("" + get(i, j) + "\t");
+      w.write("\n");
+    }
+    w.flush();
+  }
+
+  /** 
+   * Converts a matrix to a string.
+   * (FracPete: taken from old weka.core.Matrix class)
+   *
+   * @return    the converted string
+   */
+  public String toString() {
+    // Determine the width required for the maximum element,
+    // and check for fractional display requirement.
+    double maxval = 0;
+    boolean fractional = false;
+    for (int i = 0; i < getRowDimension(); i++) {
+      for (int j = 0; j < getColumnDimension(); j++) {
+        double current = get(i, j);
+        if (current < 0)
+          current *= -11;
+        if (current > maxval)
+          maxval = current;
+        double fract = Math.abs(current - Math.rint(current));
+        if (!fractional
+            && ((Math.log(fract) / Math.log(10)) >= -2)) {
+          fractional = true;
+        }
+      }
+    }
+    int width = (int)(Math.log(maxval) / Math.log(10) 
+        + (fractional ? 4 : 1));
+
+    StringBuffer text = new StringBuffer();   
+    for (int i = 0; i < getRowDimension(); i++) {
+      for (int j = 0; j < getColumnDimension(); j++)
+        text.append(" ").append(Utils.doubleToString(get(i, j),
+              width, (fractional ? 2 : 0)));
+      text.append("\n");
+    }
+
+    return text.toString();
+  } 
+
+  /**
+   * converts the Matrix into a single line Matlab string: matrix is enclosed 
+   * by parentheses, rows are separated by semicolon and single cells by
+   * blanks, e.g., [1 2; 3 4].
+   * @return      the matrix in Matlab single line format
+   */
+  public String toMatlab() {
+    StringBuffer      result;
+    int               i;
+    int               n;
+
+    result = new StringBuffer();
+
+    result.append("[");
+
+    for (i = 0; i < getRowDimension(); i++) {
+      if (i > 0)
+        result.append("; ");
+      
+      for (n = 0; n < getColumnDimension(); n++) {
+        if (n > 0)
+          result.append(" ");
+        result.append(Double.toString(get(i, n)));
+      }
+    }
+    
+    result.append("]");
+
+    return result.toString();
+  }
+
+  /**
+   * creates a matrix from the given Matlab string.
+   * @param matlab  the matrix in matlab format
+   * @return        the matrix represented by the given string
+   * @see           #toMatlab()
+   */
+  public static Matrix parseMatlab(String matlab) throws Exception {
+    StringTokenizer   tokRow;
+    StringTokenizer   tokCol;
+    int               rows;
+    int               cols;
+    Matrix            result;
+    String            cells;
+    
+    // get content
+    cells = matlab.substring(
+              matlab.indexOf("[") + 1, matlab.indexOf("]")).trim();
+    
+    // determine dimenions
+    tokRow = new StringTokenizer(cells, ";");
+    rows   = tokRow.countTokens();
+    tokCol = new StringTokenizer(tokRow.nextToken(), " ");
+    cols   = tokCol.countTokens();
+    
+    // fill matrix
+    result = new Matrix(rows, cols);
+    tokRow = new StringTokenizer(cells, ";");
+    rows   = 0;
+    while (tokRow.hasMoreTokens()) {
+      tokCol = new StringTokenizer(tokRow.nextToken(), " ");
+      cols   = 0;
+      while (tokCol.hasMoreTokens()) {
+        result.set(rows, cols, Double.parseDouble(tokCol.nextToken()));
+        cols++;
+      }
+      rows++;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   */
+  public static void main(String[] args) {
+    Matrix        I;
+    Matrix        A;
+    Matrix        B;
+
+    try {
+      // Identity
+      System.out.println("\nIdentity\n");
+      I = Matrix.identity(3, 5);
+      System.out.println("I(3,5)\n" + I);
+      
+      // basic operations - square
+      System.out.println("\nbasic operations - square\n");
+      A = Matrix.random(3, 3);
+      B = Matrix.random(3, 3);
+      System.out.println("A\n" + A);
+      System.out.println("B\n" + B);
+      System.out.println("A'\n" + A.inverse());
+      System.out.println("A^T\n" + A.transpose());
+      System.out.println("A+B\n" + A.plus(B));
+      System.out.println("A*B\n" + A.times(B));
+      System.out.println("X from A*X=B\n" + A.solve(B));
+
+      // basic operations - non square
+      System.out.println("\nbasic operations - non square\n");
+      A = Matrix.random(2, 3);
+      B = Matrix.random(3, 4);
+      System.out.println("A\n" + A);
+      System.out.println("B\n" + B);
+      System.out.println("A*B\n" + A.times(B));
+
+      // sqrt
+      System.out.println("\nsqrt (1)\n");
+      A = new Matrix(new double[][]{{5,-4,1,0,0},{-4,6,-4,1,0},{1,-4,6,-4,1},{0,1,-4,6,-4},{0,0,1,-4,5}});
+      System.out.println("A\n" + A);
+      System.out.println("sqrt(A)\n" + A.sqrt());
+
+      // sqrt
+      System.out.println("\nsqrt (2)\n");
+      A = new Matrix(new double[][]{{7, 10},{15, 22}});
+      System.out.println("A\n" + A);
+      System.out.println("sqrt(A)\n" + A.sqrt());
+      System.out.println("det(A)\n" + A.det() + "\n");
+
+      // eigenvalue decomp.
+      System.out.println("\nEigenvalue Decomposition\n");
+      EigenvalueDecomposition evd = A.eig();
+      System.out.println("[V,D] = eig(A)");
+      System.out.println("- V\n" + evd.getV());
+      System.out.println("- D\n" + evd.getD());
+
+      // LU decomp.
+      System.out.println("\nLU Decomposition\n");
+      LUDecomposition lud = A.lu();
+      System.out.println("[L,U,P] = lu(A)");
+      System.out.println("- L\n" + lud.getL());
+      System.out.println("- U\n" + lud.getU());
+      System.out.println("- P\n" + Utils.arrayToString(lud.getPivot()) + "\n");
+
+      // regression
+      System.out.println("\nRegression\n");
+      B = new Matrix(new double[][]{{3},{2}});
+      double ridge = 0.5;
+      double[] weights = new double[]{0.3, 0.7};
+      LinearRegression lr = A.regression(B, ridge);
+      System.out.println("A\n" + A);
+      System.out.println("B\n" + B);
+      System.out.println("ridge = " + ridge + "\n");
+      System.out.println("weights = " + Utils.arrayToString(weights) + "\n");
+      System.out.println("A.regression(B, ridge)\n" 
+          + A.regression(B, ridge) + "\n");
+      System.out.println("A.regression(B, weights, ridge)\n" 
+          + A.regression(B, weights, ridge) + "\n");
+
+      // writer/reader
+      System.out.println("\nWriter/Reader\n");
+      StringWriter writer = new StringWriter();
+      A.write(writer);
+      System.out.println("A.write(Writer)\n" + writer);
+      A = new Matrix(new StringReader(writer.toString()));
+      System.out.println("A = new Matrix.read(Reader)\n" + A);
+
+      // Matlab
+      System.out.println("\nMatlab-Format\n");
+      String matlab = "[ 1   2;3 4 ]";
+      System.out.println("Matlab: " + matlab);
+      System.out.println("from Matlab:\n" + Matrix.parseMatlab(matlab));
+      System.out.println("to Matlab:\n" + Matrix.parseMatlab(matlab).toMatlab());
+      matlab = "[1 2 3 4;3 4 5 6;7 8 9 10]";
+      System.out.println("Matlab: " + matlab);
+      System.out.println("from Matlab:\n" + Matrix.parseMatlab(matlab));
+      System.out.println("to Matlab:\n" + Matrix.parseMatlab(matlab).toMatlab() + "\n");
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/QRDecomposition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/QRDecomposition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/QRDecomposition.java	(revision 29)
@@ -0,0 +1,243 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * QRDecomposition.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/** 
+ * QR Decomposition.
+ * <P>
+ * For an m-by-n matrix A with m &gt;= n, the QR decomposition is an m-by-n
+ * orthogonal matrix Q and an n-by-n upper triangular matrix R so that A = Q*R.
+ * <P>
+ * The QR decompostion always exists, even if the matrix does not have full
+ * rank, so the constructor will never fail.  The primary use of the QR
+ * decomposition is in the least squares solution of nonsquare systems of
+ * simultaneous linear equations.  This will fail if isFullRank() returns false.
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class QRDecomposition 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5013090736132211418L;
+
+  /** 
+   * Array for internal storage of decomposition.
+   *    @serial internal array storage.
+   */
+  private double[][] QR;
+
+  /** 
+   * Row and column dimensions.
+   *    @serial column dimension.
+   *    @serial row dimension.
+   */
+  private int m, n;
+
+  /** 
+   * Array for internal storage of diagonal of R.
+   *    @serial diagonal of R.
+   */
+  private double[] Rdiag;
+
+  /** 
+   * QR Decomposition, computed by Householder reflections.
+   * @param A    Rectangular matrix
+   */
+  public QRDecomposition(Matrix A) {
+    // Initialize.
+    QR = A.getArrayCopy();
+    m = A.getRowDimension();
+    n = A.getColumnDimension();
+    Rdiag = new double[n];
+
+    // Main loop.
+    for (int k = 0; k < n; k++) {
+      // Compute 2-norm of k-th column without under/overflow.
+      double nrm = 0;
+      for (int i = k; i < m; i++) {
+        nrm = Maths.hypot(nrm,QR[i][k]);
+      }
+
+      if (nrm != 0.0) {
+        // Form k-th Householder vector.
+        if (QR[k][k] < 0) {
+          nrm = -nrm;
+        }
+        for (int i = k; i < m; i++) {
+          QR[i][k] /= nrm;
+        }
+        QR[k][k] += 1.0;
+
+        // Apply transformation to remaining columns.
+        for (int j = k+1; j < n; j++) {
+          double s = 0.0; 
+          for (int i = k; i < m; i++) {
+            s += QR[i][k]*QR[i][j];
+          }
+          s = -s/QR[k][k];
+          for (int i = k; i < m; i++) {
+            QR[i][j] += s*QR[i][k];
+          }
+        }
+      }
+      Rdiag[k] = -nrm;
+    }
+  }
+
+  /** 
+   * Is the matrix full rank?
+   * @return     true if R, and hence A, has full rank.
+   */
+  public boolean isFullRank() {
+    for (int j = 0; j < n; j++) {
+      if (Rdiag[j] == 0)
+        return false;
+    }
+    return true;
+  }
+
+  /** 
+   * Return the Householder vectors
+   * @return     Lower trapezoidal matrix whose columns define the reflections
+   */
+  public Matrix getH() {
+    Matrix X = new Matrix(m,n);
+    double[][] H = X.getArray();
+    for (int i = 0; i < m; i++) {
+      for (int j = 0; j < n; j++) {
+        if (i >= j) {
+          H[i][j] = QR[i][j];
+        } else {
+          H[i][j] = 0.0;
+        }
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Return the upper triangular factor
+   * @return     R
+   */
+  public Matrix getR() {
+    Matrix X = new Matrix(n,n);
+    double[][] R = X.getArray();
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        if (i < j) {
+          R[i][j] = QR[i][j];
+        } else if (i == j) {
+          R[i][j] = Rdiag[i];
+        } else {
+          R[i][j] = 0.0;
+        }
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Generate and return the (economy-sized) orthogonal factor
+   * @return     Q
+   */
+  public Matrix getQ() {
+    Matrix X = new Matrix(m,n);
+    double[][] Q = X.getArray();
+    for (int k = n-1; k >= 0; k--) {
+      for (int i = 0; i < m; i++) {
+        Q[i][k] = 0.0;
+      }
+      Q[k][k] = 1.0;
+      for (int j = k; j < n; j++) {
+        if (QR[k][k] != 0) {
+          double s = 0.0;
+          for (int i = k; i < m; i++) {
+            s += QR[i][k]*Q[i][j];
+          }
+          s = -s/QR[k][k];
+          for (int i = k; i < m; i++) {
+            Q[i][j] += s*QR[i][k];
+          }
+        }
+      }
+    }
+    return X;
+  }
+
+  /** 
+   * Least squares solution of A*X = B
+   * @param B    A Matrix with as many rows as A and any number of columns.
+   * @return     X that minimizes the two norm of Q*R*X-B.
+   * @exception  IllegalArgumentException  Matrix row dimensions must agree.
+   * @exception  RuntimeException  Matrix is rank deficient.
+   */
+  public Matrix solve(Matrix B) {
+    if (B.getRowDimension() != m) {
+      throw new IllegalArgumentException("Matrix row dimensions must agree.");
+    }
+    if (!this.isFullRank()) {
+      throw new RuntimeException("Matrix is rank deficient.");
+    }
+
+    // Copy right hand side
+    int nx = B.getColumnDimension();
+    double[][] X = B.getArrayCopy();
+
+    // Compute Y = transpose(Q)*B
+    for (int k = 0; k < n; k++) {
+      for (int j = 0; j < nx; j++) {
+        double s = 0.0; 
+        for (int i = k; i < m; i++) {
+          s += QR[i][k]*X[i][j];
+        }
+        s = -s/QR[k][k];
+        for (int i = k; i < m; i++) {
+          X[i][j] += s*QR[i][k];
+        }
+      }
+    }
+    // Solve R*X = Y;
+    for (int k = n-1; k >= 0; k--) {
+      for (int j = 0; j < nx; j++) {
+        X[k][j] /= Rdiag[k];
+      }
+      for (int i = 0; i < k; i++) {
+        for (int j = 0; j < nx; j++) {
+          X[i][j] -= X[k][j]*QR[i][k];
+        }
+      }
+    }
+    return (new Matrix(X,n,nx).getMatrix(0,n-1,0,nx-1));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/matrix/SingularValueDecomposition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/matrix/SingularValueDecomposition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/matrix/SingularValueDecomposition.java	(revision 29)
@@ -0,0 +1,575 @@
+/*
+ * This software is a cooperative product of The MathWorks and the National
+ * Institute of Standards and Technology (NIST) which has been released to the
+ * public domain. Neither The MathWorks nor NIST assumes any responsibility
+ * whatsoever for its use by other parties, and makes no guarantees, expressed
+ * or implied, about its quality, reliability, or any other characteristic.
+ */
+
+/*
+ * SingularValueDecomposition.java
+ * Copyright (C) 1999 The Mathworks and NIST
+ *
+ */
+
+package weka.core.matrix;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/** 
+ * Singular Value Decomposition.
+ * <P>
+ * For an m-by-n matrix A with m &gt;= n, the singular value decomposition is an
+ * m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and an n-by-n
+ * orthogonal matrix V so that A = U*S*V'.
+ * <P>
+ * The singular values, sigma[k] = S[k][k], are ordered so that sigma[0] &gt;=
+ * sigma[1] &gt;= ... &gt;= sigma[n-1].
+ * <P>
+ * The singular value decompostion always exists, so the constructor will never
+ * fail.  The matrix condition number and the effective numerical rank can be
+ * computed from this decomposition.
+ * <p/>
+ * Adapted from the <a href="http://math.nist.gov/javanumerics/jama/" target="_blank">JAMA</a> package.
+ *
+ * @author The Mathworks and NIST 
+ * @author Fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class SingularValueDecomposition 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8738089610999867951L;
+  
+  /** 
+   * Arrays for internal storage of U and V.
+   * @serial internal storage of U.
+   * @serial internal storage of V.
+   */
+  private double[][] U, V;
+
+  /** 
+   * Array for internal storage of singular values.
+   * @serial internal storage of singular values.
+   */
+  private double[] s;
+
+  /** 
+   * Row and column dimensions.
+   * @serial row dimension.
+   * @serial column dimension.
+   */
+  private int m, n;
+
+  /** 
+   * Construct the singular value decomposition
+   * @param Arg    Rectangular matrix
+   */
+  public SingularValueDecomposition(Matrix Arg) {
+
+    // Derived from LINPACK code.
+    // Initialize.
+    double[][] A = Arg.getArrayCopy();
+    m = Arg.getRowDimension();
+    n = Arg.getColumnDimension();
+
+    /* Apparently the failing cases are only a proper subset of (m<n), 
+	 so let's not throw error.  Correct fix to come later?
+    if (m<n) {
+	  throw new IllegalArgumentException("Jama SVD only works for m >= n"); }
+    */
+
+    int nu = Math.min(m,n);
+    s = new double [Math.min(m+1,n)];
+    U = new double [m][nu];
+    V = new double [n][n];
+    double[] e = new double [n];
+    double[] work = new double [m];
+    boolean wantu = true;
+    boolean wantv = true;
+
+    // Reduce A to bidiagonal form, storing the diagonal elements
+    // in s and the super-diagonal elements in e.
+
+    int nct = Math.min(m-1,n);
+    int nrt = Math.max(0,Math.min(n-2,m));
+    for (int k = 0; k < Math.max(nct,nrt); k++) {
+      if (k < nct) {
+
+        // Compute the transformation for the k-th column and
+        // place the k-th diagonal in s[k].
+        // Compute 2-norm of k-th column without under/overflow.
+        s[k] = 0;
+        for (int i = k; i < m; i++) {
+          s[k] = Maths.hypot(s[k],A[i][k]);
+        }
+        if (s[k] != 0.0) {
+          if (A[k][k] < 0.0) {
+            s[k] = -s[k];
+          }
+          for (int i = k; i < m; i++) {
+            A[i][k] /= s[k];
+          }
+          A[k][k] += 1.0;
+        }
+        s[k] = -s[k];
+      }
+      for (int j = k+1; j < n; j++) {
+        if ((k < nct) & (s[k] != 0.0))  {
+
+          // Apply the transformation.
+
+          double t = 0;
+          for (int i = k; i < m; i++) {
+            t += A[i][k]*A[i][j];
+          }
+          t = -t/A[k][k];
+          for (int i = k; i < m; i++) {
+            A[i][j] += t*A[i][k];
+          }
+        }
+
+        // Place the k-th row of A into e for the
+        // subsequent calculation of the row transformation.
+
+        e[j] = A[k][j];
+      }
+      if (wantu & (k < nct)) {
+
+        // Place the transformation in U for subsequent back
+        // multiplication.
+
+        for (int i = k; i < m; i++) {
+          U[i][k] = A[i][k];
+        }
+      }
+      if (k < nrt) {
+
+        // Compute the k-th row transformation and place the
+        // k-th super-diagonal in e[k].
+        // Compute 2-norm without under/overflow.
+        e[k] = 0;
+        for (int i = k+1; i < n; i++) {
+          e[k] = Maths.hypot(e[k],e[i]);
+        }
+        if (e[k] != 0.0) {
+          if (e[k+1] < 0.0) {
+            e[k] = -e[k];
+          }
+          for (int i = k+1; i < n; i++) {
+            e[i] /= e[k];
+          }
+          e[k+1] += 1.0;
+        }
+        e[k] = -e[k];
+        if ((k+1 < m) & (e[k] != 0.0)) {
+
+          // Apply the transformation.
+
+          for (int i = k+1; i < m; i++) {
+            work[i] = 0.0;
+          }
+          for (int j = k+1; j < n; j++) {
+            for (int i = k+1; i < m; i++) {
+              work[i] += e[j]*A[i][j];
+            }
+          }
+          for (int j = k+1; j < n; j++) {
+            double t = -e[j]/e[k+1];
+            for (int i = k+1; i < m; i++) {
+              A[i][j] += t*work[i];
+            }
+          }
+        }
+        if (wantv) {
+
+          // Place the transformation in V for subsequent
+          // back multiplication.
+
+          for (int i = k+1; i < n; i++) {
+            V[i][k] = e[i];
+          }
+        }
+      }
+    }
+
+    // Set up the final bidiagonal matrix or order p.
+
+    int p = Math.min(n,m+1);
+    if (nct < n) {
+      s[nct] = A[nct][nct];
+    }
+    if (m < p) {
+      s[p-1] = 0.0;
+    }
+    if (nrt+1 < p) {
+      e[nrt] = A[nrt][p-1];
+    }
+    e[p-1] = 0.0;
+
+    // If required, generate U.
+
+    if (wantu) {
+      for (int j = nct; j < nu; j++) {
+        for (int i = 0; i < m; i++) {
+          U[i][j] = 0.0;
+        }
+        U[j][j] = 1.0;
+      }
+      for (int k = nct-1; k >= 0; k--) {
+        if (s[k] != 0.0) {
+          for (int j = k+1; j < nu; j++) {
+            double t = 0;
+            for (int i = k; i < m; i++) {
+              t += U[i][k]*U[i][j];
+            }
+            t = -t/U[k][k];
+            for (int i = k; i < m; i++) {
+              U[i][j] += t*U[i][k];
+            }
+          }
+          for (int i = k; i < m; i++ ) {
+            U[i][k] = -U[i][k];
+          }
+          U[k][k] = 1.0 + U[k][k];
+          for (int i = 0; i < k-1; i++) {
+            U[i][k] = 0.0;
+          }
+        } else {
+          for (int i = 0; i < m; i++) {
+            U[i][k] = 0.0;
+          }
+          U[k][k] = 1.0;
+        }
+      }
+    }
+
+    // If required, generate V.
+
+    if (wantv) {
+      for (int k = n-1; k >= 0; k--) {
+        if ((k < nrt) & (e[k] != 0.0)) {
+          for (int j = k+1; j < nu; j++) {
+            double t = 0;
+            for (int i = k+1; i < n; i++) {
+              t += V[i][k]*V[i][j];
+            }
+            t = -t/V[k+1][k];
+            for (int i = k+1; i < n; i++) {
+              V[i][j] += t*V[i][k];
+            }
+          }
+        }
+        for (int i = 0; i < n; i++) {
+          V[i][k] = 0.0;
+        }
+        V[k][k] = 1.0;
+      }
+    }
+
+    // Main iteration loop for the singular values.
+
+    int pp = p-1;
+    int iter = 0;
+    double eps = Math.pow(2.0,-52.0);
+    double tiny = Math.pow(2.0,-966.0);
+    while (p > 0) {
+      int k,kase;
+
+      // Here is where a test for too many iterations would go.
+
+      // This section of the program inspects for
+      // negligible elements in the s and e arrays.  On
+      // completion the variables kase and k are set as follows.
+
+      // kase = 1     if s(p) and e[k-1] are negligible and k<p
+      // kase = 2     if s(k) is negligible and k<p
+      // kase = 3     if e[k-1] is negligible, k<p, and
+      //              s(k), ..., s(p) are not negligible (qr step).
+      // kase = 4     if e(p-1) is negligible (convergence).
+
+      for (k = p-2; k >= -1; k--) {
+        if (k == -1) {
+          break;
+        }
+        if (Math.abs(e[k]) <=
+          tiny + eps*(Math.abs(s[k]) + Math.abs(s[k+1]))) {
+          e[k] = 0.0;
+          break;
+        }
+      }
+      if (k == p-2) {
+        kase = 4;
+      } else {
+        int ks;
+        for (ks = p-1; ks >= k; ks--) {
+          if (ks == k) {
+            break;
+          }
+          double t = (ks != p ? Math.abs(e[ks]) : 0.) + 
+                                                  (ks != k+1 ? Math.abs(e[ks-1]) : 0.);
+          if (Math.abs(s[ks]) <= tiny + eps*t)  {
+            s[ks] = 0.0;
+            break;
+          }
+        }
+        if (ks == k) {
+          kase = 3;
+        } else if (ks == p-1) {
+          kase = 1;
+        } else {
+          kase = 2;
+          k = ks;
+        }
+      }
+      k++;
+
+      // Perform the task indicated by kase.
+
+      switch (kase) {
+
+        // Deflate negligible s(p).
+
+        case 1: {
+                  double f = e[p-2];
+                  e[p-2] = 0.0;
+                  for (int j = p-2; j >= k; j--) {
+                    double t = Maths.hypot(s[j],f);
+                    double cs = s[j]/t;
+                    double sn = f/t;
+                    s[j] = t;
+                    if (j != k) {
+                      f = -sn*e[j-1];
+                      e[j-1] = cs*e[j-1];
+                    }
+                    if (wantv) {
+                      for (int i = 0; i < n; i++) {
+                        t = cs*V[i][j] + sn*V[i][p-1];
+                        V[i][p-1] = -sn*V[i][j] + cs*V[i][p-1];
+                        V[i][j] = t;
+                      }
+                    }
+                  }
+        }
+        break;
+
+        // Split at negligible s(k).
+
+        case 2: {
+                  double f = e[k-1];
+                  e[k-1] = 0.0;
+                  for (int j = k; j < p; j++) {
+                    double t = Maths.hypot(s[j],f);
+                    double cs = s[j]/t;
+                    double sn = f/t;
+                    s[j] = t;
+                    f = -sn*e[j];
+                    e[j] = cs*e[j];
+                    if (wantu) {
+                      for (int i = 0; i < m; i++) {
+                        t = cs*U[i][j] + sn*U[i][k-1];
+                        U[i][k-1] = -sn*U[i][j] + cs*U[i][k-1];
+                        U[i][j] = t;
+                      }
+                    }
+                  }
+        }
+        break;
+
+        // Perform one qr step.
+
+        case 3: {
+
+                  // Calculate the shift.
+
+                  double scale = Math.max(Math.max(Math.max(Math.max(
+                            Math.abs(s[p-1]),Math.abs(s[p-2])),Math.abs(e[p-2])), 
+                        Math.abs(s[k])),Math.abs(e[k]));
+                  double sp = s[p-1]/scale;
+                  double spm1 = s[p-2]/scale;
+                  double epm1 = e[p-2]/scale;
+                  double sk = s[k]/scale;
+                  double ek = e[k]/scale;
+                  double b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0;
+                  double c = (sp*epm1)*(sp*epm1);
+                  double shift = 0.0;
+                  if ((b != 0.0) | (c != 0.0)) {
+                    shift = Math.sqrt(b*b + c);
+                    if (b < 0.0) {
+                      shift = -shift;
+                    }
+                    shift = c/(b + shift);
+                  }
+                  double f = (sk + sp)*(sk - sp) + shift;
+                  double g = sk*ek;
+
+                  // Chase zeros.
+
+                  for (int j = k; j < p-1; j++) {
+                    double t = Maths.hypot(f,g);
+                    double cs = f/t;
+                    double sn = g/t;
+                    if (j != k) {
+                      e[j-1] = t;
+                    }
+                    f = cs*s[j] + sn*e[j];
+                    e[j] = cs*e[j] - sn*s[j];
+                    g = sn*s[j+1];
+                    s[j+1] = cs*s[j+1];
+                    if (wantv) {
+                      for (int i = 0; i < n; i++) {
+                        t = cs*V[i][j] + sn*V[i][j+1];
+                        V[i][j+1] = -sn*V[i][j] + cs*V[i][j+1];
+                        V[i][j] = t;
+                      }
+                    }
+                    t = Maths.hypot(f,g);
+                    cs = f/t;
+                    sn = g/t;
+                    s[j] = t;
+                    f = cs*e[j] + sn*s[j+1];
+                    s[j+1] = -sn*e[j] + cs*s[j+1];
+                    g = sn*e[j+1];
+                    e[j+1] = cs*e[j+1];
+                    if (wantu && (j < m-1)) {
+                      for (int i = 0; i < m; i++) {
+                        t = cs*U[i][j] + sn*U[i][j+1];
+                        U[i][j+1] = -sn*U[i][j] + cs*U[i][j+1];
+                        U[i][j] = t;
+                      }
+                    }
+                  }
+                  e[p-2] = f;
+                  iter = iter + 1;
+        }
+        break;
+
+        // Convergence.
+
+        case 4: {
+
+                  // Make the singular values positive.
+
+                  if (s[k] <= 0.0) {
+                    s[k] = (s[k] < 0.0 ? -s[k] : 0.0);
+                    if (wantv) {
+                      for (int i = 0; i <= pp; i++) {
+                        V[i][k] = -V[i][k];
+                      }
+                    }
+                  }
+
+                  // Order the singular values.
+
+                  while (k < pp) {
+                    if (s[k] >= s[k+1]) {
+                      break;
+                    }
+                    double t = s[k];
+                    s[k] = s[k+1];
+                    s[k+1] = t;
+                    if (wantv && (k < n-1)) {
+                      for (int i = 0; i < n; i++) {
+                        t = V[i][k+1]; V[i][k+1] = V[i][k]; V[i][k] = t;
+                      }
+                    }
+                    if (wantu && (k < m-1)) {
+                      for (int i = 0; i < m; i++) {
+                        t = U[i][k+1]; U[i][k+1] = U[i][k]; U[i][k] = t;
+                      }
+                    }
+                    k++;
+                  }
+                  iter = 0;
+                  p--;
+        }
+        break;
+      }
+    }
+  }
+
+  /** 
+   * Return the left singular vectors
+   * @return     U
+   */
+  public Matrix getU() {
+    return new Matrix(U,m,Math.min(m+1,n));
+  }
+
+  /** 
+   * Return the right singular vectors
+   * @return     V
+   */
+  public Matrix getV() {
+    return new Matrix(V,n,n);
+  }
+
+  /** 
+   * Return the one-dimensional array of singular values
+   * @return     diagonal of S.
+   */
+  public double[] getSingularValues() {
+    return s;
+  }
+
+  /** 
+   * Return the diagonal matrix of singular values
+   * @return     S
+   */
+  public Matrix getS() {
+    Matrix X = new Matrix(n,n);
+    double[][] S = X.getArray();
+    for (int i = 0; i < n; i++) {
+      for (int j = 0; j < n; j++) {
+        S[i][j] = 0.0;
+      }
+      S[i][i] = this.s[i];
+    }
+    return X;
+  }
+
+  /** 
+   * Two norm
+   * @return     max(S)
+   */
+  public double norm2() {
+    return s[0];
+  }
+
+  /** 
+   * Two norm condition number
+   * @return     max(S)/min(S)
+   */
+  public double cond() {
+    return s[0]/s[Math.min(m,n)-1];
+  }
+
+  /** 
+   * Effective numerical matrix rank
+   * @return     Number of nonnegligible singular values.
+   */
+  public int rank() {
+    double eps = Math.pow(2.0,-52.0);
+    double tol = Math.max(m,n)*s[0]*eps;
+    int r = 0;
+    for (int i = 0; i < s.length; i++) {
+      if (s[i] > tol) {
+        r++;
+      }
+    }
+    return r;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/BallTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/BallTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/BallTree.java	(revision 29)
@@ -0,0 +1,634 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BallTree.java
+ * Copyright (C) 2007 University of Waikato
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.neighboursearch.balltrees.BallNode;
+import weka.core.neighboursearch.balltrees.BallTreeConstructor;
+import weka.core.neighboursearch.balltrees.TopDownConstructor;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the BallTree/Metric Tree algorithm for nearest neighbour search.<br/>
+ * The connection to dataset is only a reference. For the tree structure the indexes are stored in an array.<br/>
+ * See the implementing classes of different construction methods of the trees for details on its construction.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Stephen M. Omohundro (1989). Five Balltree Construction Algorithms.<br/>
+ * <br/>
+ * Jeffrey K. Uhlmann (1991). Satisfying general proximity/similarity queries with metric trees. Information Processing Letters. 40(4):175-179.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Omohundro1989,
+ *    author = {Stephen M. Omohundro},
+ *    institution = {International Computer Science Institute},
+ *    month = {December},
+ *    number = {TR-89-063},
+ *    title = {Five Balltree Construction Algorithms},
+ *    year = {1989}
+ * }
+ * 
+ * &#64;article{Uhlmann1991,
+ *    author = {Jeffrey K. Uhlmann},
+ *    journal = {Information Processing Letters},
+ *    month = {November},
+ *    number = {4},
+ *    pages = {175-179},
+ *    title = {Satisfying general proximity/similarity queries with metric trees},
+ *    volume = {40},
+ *    year = {1991}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;classname and options&gt;
+ *  The construction method to employ. Either TopDown or BottomUp
+ *  (default: weka.core.TopDownConstructor)</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class BallTree
+  extends NearestNeighbourSearch 
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 728763855952698328L;
+
+  /** 
+   * The instances indices sorted inorder of appearence in the tree from left
+   * most leaf node to the right most leaf node.
+   */
+  protected int[] m_InstList;
+  
+  /** 
+   * The maximum number of instances in a leaf. A node is made into a leaf if 
+   * the number of instances in it become less than or equal to this value.
+   */
+  protected int m_MaxInstancesInLeaf = 40;
+  
+  /** Tree Stats variables. */
+  protected TreePerformanceStats m_TreeStats = null;
+
+  /** The root node of the BallTree. */
+  protected BallNode m_Root;
+  
+  /** The constructor method to use to build the tree. */
+  protected BallTreeConstructor m_TreeConstructor = new TopDownConstructor();
+  
+  /** Array holding the distances of the nearest neighbours. It is filled up
+   *  both by nearestNeighbour() and kNearestNeighbours(). 
+   */
+  protected double[] m_Distances;
+
+  /**
+   * Creates a new instance of BallTree.
+   */
+  public BallTree() {
+    super();
+    if(getMeasurePerformance())
+      m_Stats = m_TreeStats = new TreePerformanceStats();
+  }
+  
+  /**
+   * Creates a new instance of BallTree. 
+   * It also builds the tree on supplied set of Instances.
+   * @param insts The instances/points on which the BallTree 
+   * should be built on.
+   */
+  public BallTree(Instances insts) {
+    super(insts);
+    if(getMeasurePerformance())
+      m_Stats = m_TreeStats = new TreePerformanceStats();
+  }
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the BallTree/Metric Tree algorithm for "
+      + "nearest neighbour search.\n"
+      + "The connection to dataset is only a reference. For the tree "
+      + "structure the indexes are stored in an array.\n"
+      + "See the implementing classes of different construction methods of "
+      + "the trees for details on its construction.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Stephen M. Omohundro");
+    result.setValue(Field.YEAR, "1989");
+    result.setValue(Field.TITLE, "Five Balltree Construction Algorithms");
+    result.setValue(Field.MONTH, "December");
+    result.setValue(Field.NUMBER, "TR-89-063");
+    result.setValue(Field.INSTITUTION, "International Computer Science Institute");
+
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "Jeffrey K. Uhlmann");
+    additional.setValue(Field.TITLE, "Satisfying general proximity/similarity queries with metric trees");
+    additional.setValue(Field.JOURNAL, "Information Processing Letters");
+    additional.setValue(Field.MONTH, "November");
+    additional.setValue(Field.YEAR, "1991");
+    additional.setValue(Field.NUMBER, "4");
+    additional.setValue(Field.VOLUME, "40");
+    additional.setValue(Field.PAGES, "175-179");
+    
+    return result;
+  }
+  
+  /**
+   * Builds the BallTree on the supplied set of 
+   * instances/points (supplied with setInstances(Instances) 
+   * method and referenced by the m_Instances field). This
+   * method should not be called by outside classes. They
+   * should only use setInstances(Instances) method.
+   * 
+   * @throws Exception If no instances are supplied 
+   * (m_Instances is null), or if some other error in the 
+   * supplied BallTreeConstructor occurs while building 
+   * the tree.  
+   */
+  protected void buildTree() throws Exception {
+    if(m_Instances==null)
+      throw new Exception("No instances supplied yet. Have to call " +
+                          "setInstances(instances) with a set of Instances " +
+                          "first.");
+    m_InstList = new int[m_Instances.numInstances()];
+    
+    for(int i=0; i<m_InstList.length; i++) {
+      m_InstList[i] = i;
+    } //end for
+    
+    m_DistanceFunction.setInstances(m_Instances);
+    m_TreeConstructor.setInstances(m_Instances);
+    m_TreeConstructor.setInstanceList(m_InstList);
+    m_TreeConstructor.setEuclideanDistanceFunction(
+                      (EuclideanDistance)m_DistanceFunction);
+    
+    m_Root = m_TreeConstructor.buildTree();
+  }
+   
+  /**
+   * Returns k nearest instances in the current neighbourhood to the supplied
+   * instance. &gt;k instances can be returned if there is more than one instance 
+   * at the kth boundary (i.e. if there are more than 1 instance with the 
+   * same distance as the kth nearest neighbour).
+   * 
+   * @param target 	The instance to find the k nearest neighbours for.
+   * @param k		The number of nearest neighbours to find.
+   * @throws Exception 	If the neighbours could not be found.
+   * @return The k nearest neighbours of the given target instance 
+   * (&gt;k nearest neighbours, if there are more instances that have same 
+   *  distance as the kth nearest neighbour).
+   */
+  public Instances kNearestNeighbours(Instance target, int k) throws Exception {
+    MyHeap heap = new MyHeap(k);
+
+    if(m_Stats!=null)
+      m_Stats.searchStart();
+    
+    nearestNeighbours(heap, m_Root, target, k);
+    
+    if(m_Stats!=null)
+      m_Stats.searchFinish();
+
+    Instances neighbours = new Instances(m_Instances, heap.totalSize());
+    m_Distances = new double[heap.totalSize()];
+    int [] indices = new int[heap.totalSize()];
+    int i=1; MyHeapElement h;
+    while(heap.noOfKthNearest()>0) {
+      h = heap.getKthNearest();
+      indices[indices.length-i] = h.index;
+      m_Distances[indices.length-i] = h.distance;
+      i++;
+    }
+    while(heap.size()>0) {
+      h = heap.get();
+      indices[indices.length-i] = h.index;
+      m_Distances[indices.length-i] = h.distance;
+      i++;
+    }
+    
+    m_DistanceFunction.postProcessDistances(m_Distances);
+    
+    for(i=0; i<indices.length; i++)
+      neighbours.add(m_Instances.instance(indices[i]));
+    
+    return neighbours;  // <---Check this statement
+  }
+
+  /** 
+   * Does NN search according to Moore's method. 
+   * Should not be used by outside classes. They should instead
+   * use kNearestNeighbours(Instance, int).
+   * P.S.: The distance returned are squared. Need to post process the 
+   * distances. 
+   * @param heap MyHeap object to store/update NNs found during the search.
+   * @param node The BallNode to do the NN search on.
+   * @param target The target instance for which the NNs are required.
+   * @param k The number of NNs to find.
+   * @throws Exception If the structure of the BallTree is not correct, 
+   * or if there is some problem putting NNs in the heap.
+   */
+  protected void nearestNeighbours(MyHeap heap, BallNode node, Instance target,
+                                   int k) throws Exception{
+    double distance = Double.NEGATIVE_INFINITY;
+
+    if (heap.totalSize() >= k)
+      distance = m_DistanceFunction.distance(target, node.getPivot());
+
+    // The radius is not squared so need to take sqrt before comparison
+    if (distance > -0.000001
+        && Math.sqrt(heap.peek().distance) < distance - node.getRadius()) {
+      return;
+    } else if (node.m_Left != null && node.m_Right != null) { // if node is not
+                                                              // a leaf
+      if (m_TreeStats != null) {
+        m_TreeStats.incrIntNodeCount();
+      }
+      double leftPivotDist = Math.sqrt(m_DistanceFunction.distance(target,
+          node.m_Left.getPivot(), Double.POSITIVE_INFINITY));
+      double rightPivotDist = Math.sqrt(m_DistanceFunction.distance(target,
+          node.m_Right.getPivot(), Double.POSITIVE_INFINITY));
+      double leftBallDist = leftPivotDist - node.m_Left.getRadius();
+      double rightBallDist = rightPivotDist - node.m_Right.getRadius();
+      // if target is inside both balls then see which center is closer
+      if (leftBallDist < 0 && rightBallDist < 0) {
+        if (leftPivotDist < rightPivotDist) {
+          nearestNeighbours(heap, node.m_Left, target, k);
+          nearestNeighbours(heap, node.m_Right, target, k);
+        } else {
+          nearestNeighbours(heap, node.m_Right, target, k);
+          nearestNeighbours(heap, node.m_Left, target, k);
+        }
+      }
+      // else see which ball is closer (if dist < 0 target is inside a ball, and
+      // hence the ball is closer).
+      else {
+        if (leftBallDist < rightBallDist) {
+          nearestNeighbours(heap, node.m_Left, target, k);
+          nearestNeighbours(heap, node.m_Right, target, k);
+        } else {
+          nearestNeighbours(heap, node.m_Right, target, k);
+          nearestNeighbours(heap, node.m_Left, target, k);
+        }
+      }
+    } else if (node.m_Left != null || node.m_Right != null) { // invalid leaves
+                                                              // assignment
+      throw new Exception("Error: Only one leaf of the built ball tree is " + 
+                          "assigned. Please check code.");
+    } else if (node.m_Left == null && node.m_Right == null) { // if node is a
+                                                              // leaf
+      if (m_TreeStats != null) {
+        m_TreeStats.updatePointCount(node.numInstances());
+        m_TreeStats.incrLeafCount();
+      }
+      for (int i = node.m_Start; i <= node.m_End; i++) {
+        if (target == m_Instances.instance(m_InstList[i])) //for hold-one-out cross-validation
+          continue;
+        if (heap.totalSize() < k) {
+          distance = m_DistanceFunction.distance(target, m_Instances
+              .instance(m_InstList[i]), Double.POSITIVE_INFINITY, m_Stats);
+          heap.put(m_InstList[i], distance);
+        } else {
+          MyHeapElement head = heap.peek();
+          distance = m_DistanceFunction.distance(target, 
+              m_Instances.instance(m_InstList[i]), head.distance, m_Stats);
+          if (distance < head.distance) {
+            heap.putBySubstitute(m_InstList[i], distance);
+          } else if (distance == head.distance) {
+            heap.putKthNearest(m_InstList[i], distance);
+          }
+        }//end else(heap.totalSize())
+      }
+    }//end else if node is a leaf
+  }
+  
+  /**
+   * Returns the nearest instance in the current neighbourhood to the supplied
+   * instance.
+   * 
+   * @param target 	The instance to find the nearest neighbour for.
+   * @throws Exception 	if the nearest neighbour could not be found.
+   * @return The nearest neighbour of the given target instance.
+   */
+  public Instance nearestNeighbour(Instance target) throws Exception {
+    return kNearestNeighbours(target, 1).instance(0);
+  }
+
+ /** 
+   * Returns the distances of the k nearest neighbours. The kNearestNeighbours
+   * or nearestNeighbour must always be called before calling this function. If
+   * this function is called before calling either the kNearestNeighbours or 
+   * the nearestNeighbour, then it throws an exception. If, however, any 
+   * one of the two functions is called at any point in the past, then no   
+   * exception is thrown and the distances of NN(s) from the training set for 
+   * the last supplied target instance (to either one of the nearestNeighbour 
+   * functions) is/are returned.
+   *
+   * @return 		array containing the distances of the 
+   *            	nearestNeighbours. The length and ordering of the 
+   *            	array is the same as that of the instances returned 
+   *            	by nearestNeighbour functions.
+   * @throws Exception 	if called before calling kNearestNeighbours
+   *            	or nearestNeighbours.
+   */
+  public double[] getDistances() throws Exception {
+    if(m_Distances==null)
+      throw new Exception("No distances available. Please call either "+
+                          "kNearestNeighbours or nearestNeighbours first.");
+    return m_Distances;    
+  }
+
+  /**
+   * Adds one instance to the BallTree. This involves creating/updating the 
+   * structure to reflect the newly added training instance 
+   * 
+   * @param ins The instance to be added. Usually the newly added instance in the
+   *            training set.
+   * @throws Exception If the instance cannot be added to the tree.
+   */
+  public void update(Instance ins) throws Exception {
+    addInstanceInfo(ins);
+    m_InstList = m_TreeConstructor.addInstance(m_Root, ins);    
+  }
+  
+  /** 
+   * Adds the given instance's info. This implementation updates the attributes' 
+   * range datastructures of EuclideanDistance class.
+   * 
+   * @param ins		The instance to add the information of. Usually this is
+   * 			the test instance supplied to update the range of 
+   * 			attributes in the distance function.
+   */
+  public void addInstanceInfo(Instance ins) {
+    if(m_Instances!=null)
+      m_DistanceFunction.update(ins);
+  }  
+
+  /**
+   * Builds the BallTree based on the given set of instances.
+   * @param insts The insts for which the BallTree is to be 
+   * built. 
+   * @throws Exception If some error occurs while 
+   * building the BallTree
+   */
+  public void setInstances(Instances insts) throws Exception {
+    super.setInstances(insts);
+    buildTree();
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String ballTreeConstructorTipText() {
+    return "The tree constructor being used.";
+  }
+      
+  /**
+   * Returns the BallTreeConstructor currently in use.
+   * @return The BallTreeConstructor currently in use.
+   */
+  public BallTreeConstructor getBallTreeConstructor() {
+    return m_TreeConstructor;
+  }
+  
+  /**
+   * Sets the BallTreeConstructor for building the BallTree 
+   * (default TopDownConstructor).
+   * @param constructor The new BallTreeConstructor. 
+   */
+  public void setBallTreeConstructor(BallTreeConstructor constructor) {
+    m_TreeConstructor = constructor;
+  }
+  
+  /**
+   * Returns the size of the tree.
+   * 
+   * @return 		the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_TreeConstructor.getNumNodes();
+  }
+  
+  /**
+   * Returns the number of leaves.
+   * 
+   * @return 		the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_TreeConstructor.getNumLeaves();
+  }
+  
+  /**
+   * Returns the depth of the tree. 
+   * 
+   * @return 		the number of rules
+   */
+  public double measureMaxDepth() {
+    return m_TreeConstructor.getMaxDepth();
+  }
+    
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector = new Vector<String>();
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureMaxDepth");
+    if (m_Stats != null) {
+      for (Enumeration e = m_Stats.enumerateMeasures(); e.hasMoreElements();) {
+        newVector.addElement((String)e.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName 	the name of the measure to query for 
+   * 					its value.
+   * @return 				the value of the named measure.
+   * @throws IllegalArgumentException 	if the named measure is not supported.
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureMaxDepth") == 0) {
+      return measureMaxDepth();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else if(m_Stats!=null) {
+      return m_Stats.getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (BallTree)");
+    }
+  }
+  	
+  /**
+   * Sets whether to calculate the performance statistics or not.
+   * @param measurePerformance This should be true if performance
+   * statistics are to be calculated.
+   */
+  public void setMeasurePerformance(boolean measurePerformance) {
+    m_MeasurePerformance = measurePerformance;
+    if (m_MeasurePerformance) {
+      if (m_Stats == null)
+        m_Stats = m_TreeStats = new TreePerformanceStats();
+    } else
+      m_Stats = m_TreeStats = null;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.addElement(new Option(
+	"\tThe construction method to employ. Either TopDown or BottomUp\n"
+	+ "\t(default: weka.core.TopDownConstructor)",
+	"C", 1, "-C <classname and options>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;classname and options&gt;
+   *  The construction method to employ. Either TopDown or BottomUp
+   *  (default: weka.core.TopDownConstructor)</pre>
+   * 
+   <!-- options-end --> 
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {
+
+    super.setOptions(options);
+
+    String optionString = Utils.getOption('C', options);
+    if(optionString.length() != 0) {
+      String constructorSpec[] = Utils.splitOptions(optionString);
+      if(constructorSpec.length == 0) { 
+        throw new Exception("Invalid BallTreeConstructor specification string."); 
+      }
+      String className = constructorSpec[0];
+      constructorSpec[0] = "";
+
+      setBallTreeConstructor( (BallTreeConstructor)
+                            Utils.forName( BallTreeConstructor.class, 
+                                           className, constructorSpec) );
+    }
+    else {
+      setBallTreeConstructor(new TopDownConstructor());  
+    }
+  }
+
+  /**
+   * Gets the current settings of KDtree.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-C");
+    result.add(
+	(m_TreeConstructor.getClass().getName() + " " +
+	 Utils.joinOptions(m_TreeConstructor.getOptions())).trim());
+
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/CoverTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/CoverTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/CoverTree.java	(revision 29)
@@ -0,0 +1,1977 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CoverTree.java
+ * Copyright (C) 2006 Alina Beygelzimer and Sham Kakade and John Langford
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.converters.CSVLoader;
+import weka.core.neighboursearch.covertrees.Stack;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the CoverTree datastructure.<br/>
+ * The class is very much a translation of the c source code made available by the authors.<br/>
+ * <br/>
+ * For more information and original source code see:<br/>
+ * <br/>
+ * Alina Beygelzimer, Sham Kakade, John Langford: Cover trees for nearest neighbor. In: ICML'06: Proceedings of the 23rd international conference on Machine learning, New York, NY, USA, 97-104, 2006.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Beygelzimer2006,
+ *    address = {New York, NY, USA},
+ *    author = {Alina Beygelzimer and Sham Kakade and John Langford},
+ *    booktitle = {ICML'06: Proceedings of the 23rd international conference on Machine learning},
+ *    pages = {97-104},
+ *    publisher = {ACM Press},
+ *    title = {Cover trees for nearest neighbor},
+ *    year = {2006},
+ *    location = {Pittsburgh, Pennsylvania},
+ *    HTTP = {http://hunch.net/\~jl/projects/cover_tree/cover_tree.html}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -B &lt;value&gt;
+ *  Set base of the expansion constant
+ *  (default = 1.3).</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Alina Beygelzimer (original C++ code)
+ * @author Sham Kakade (original C++ code)
+ * @author John Langford (original C++ code)
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz) (Java port)
+ * @version $Revision: 5953 $
+ */
+public class CoverTree
+  extends NearestNeighbourSearch
+  implements TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 7617412821497807586L;
+
+  /**
+   * class representing a node of the cover tree.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  public class CoverTreeNode
+    implements Serializable, RevisionHandler {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 1808760031169036512L;
+    
+    /** ID for the node. */
+    private int nodeid;
+    
+    /** Index of the instance represented by this node in the index array. */
+    private Integer idx;
+    
+    /** The distance of the furthest descendant of the node. */
+    private double max_dist; // The maximum distance to any grandchild.
+
+    /** The distance to the nodes parent. */ 
+    private double parent_dist; // The distance to the parent.
+
+    /** The children of the node. */
+    private Stack<CoverTreeNode> children;
+
+    /** The number of children node has.  */
+    private int num_children; // The number of children.
+
+    /** The min i that makes base^i &lt;= max_dist. */
+    private int scale; // Essentially, an upper bound on the distance to any child.
+
+    /** Constructor for the class. */
+    public CoverTreeNode() {
+    }
+    
+    /**
+     * Constructor.
+     * @param i The index of the Instance this node is
+     * associated with.
+     * @param md The distance of the furthest descendant.
+     * @param pd The distance of the node to its parent.
+     * @param childs Children of the node in a stack.
+     * @param numchilds The number of children of the 
+     * node.
+     * @param s The scale/level of the node in the tree.
+     */
+    public CoverTreeNode(Integer i, double md, double pd,
+      Stack<CoverTreeNode> childs, int numchilds, int s) {
+      idx = i;
+      max_dist = md;
+      parent_dist = pd;
+      children = childs;
+      num_children = numchilds;
+      scale = s;
+    }
+    
+    /** Returns the instance represented by the node.
+     * @return The instance represented by the node.
+     */
+    public Instance p() {
+      return m_Instances.instance(idx);
+    }
+    
+    /** Returns whether if the node is a leaf or not.
+     * @return true if the node is a leaf node. 
+     */
+    public boolean isALeaf() {
+      return num_children==0;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+
+  /**
+   * Private class holding a point's distance to the current reference
+   * point p.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  private class DistanceNode
+    implements RevisionHandler {
+    
+    /**
+     * The last distance is to the current reference point
+     * (potential current parent). The previous ones are
+     * to reference points that were previously looked at
+     * (all potential ancestors).      
+     */
+    Stack<Double> dist;
+    
+    /** The index of the instance represented by this node. */
+    Integer idx;
+    
+    /**
+     * Returns the instance represent by this DistanceNode.
+     * @return The instance represented by this node. 
+     */
+    public Instance q() {
+      return m_Instances.instance(idx);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+
+  /** The euclidean distance function to use. */
+  protected EuclideanDistance m_EuclideanDistance;
+  { // to make sure we have only one object of EuclideanDistance
+    if (m_DistanceFunction instanceof EuclideanDistance)
+      m_EuclideanDistance = (EuclideanDistance) m_DistanceFunction;
+    else
+      m_DistanceFunction = m_EuclideanDistance = new EuclideanDistance();
+  }
+
+  /** The root node. */
+  protected CoverTreeNode m_Root;
+
+  /** 
+   * Array holding the distances of the nearest neighbours. It is filled up
+   *  both by nearestNeighbour() and kNearestNeighbours(). 
+   */
+  protected double [] m_DistanceList;
+
+  /** Number of nodes in the tree. */
+  protected int m_NumNodes, m_NumLeaves, m_MaxDepth;
+  
+  /** Tree Stats variables. */
+  protected TreePerformanceStats m_TreeStats = null;
+
+  /**
+   * The base of our expansion constant. In other words the 2 in 2^i used
+   * in covering tree and separation invariants of a cover tree. P.S.: In
+   * paper it's suggested the separation invariant is relaxed in batch
+   * construction.
+   */
+  protected double m_Base = 1.3;
+
+  /**
+   * if we have base 2 then this can be viewed as 1/ln(2), which can be
+   * used later on to do il2*ln(d) instead of ln(d)/ln(2), to get log2(d),
+   * in get_scale method.
+   */
+  protected double il2 = 1.0 / Math.log(m_Base);
+
+  /**
+   * default constructor.
+   */
+  public CoverTree() {
+    super();
+    if(getMeasurePerformance())
+      m_Stats = m_TreeStats = new TreePerformanceStats();
+  }
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the CoverTree datastructure.\n"
+      + "The class is very much a translation of the c source code made "
+      + "available by the authors.\n\n"
+      + "For more information and original source code see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Alina Beygelzimer and Sham Kakade and John Langford");
+    result.setValue(Field.TITLE, "Cover trees for nearest neighbor");
+    result.setValue(Field.BOOKTITLE, "ICML'06: Proceedings of the 23rd international conference on Machine learning");
+    result.setValue(Field.PAGES, "97-104");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.PUBLISHER, "ACM Press");
+    result.setValue(Field.ADDRESS, "New York, NY, USA");
+    result.setValue(Field.LOCATION, "Pittsburgh, Pennsylvania");
+    result.setValue(Field.HTTP, "http://hunch.net/~jl/projects/cover_tree/cover_tree.html");
+
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+
+    newVector.addElement(new Option(
+	"\tSet base of the expansion constant\n"
+	+ "\t(default = 1.3).",
+	"B", 1, "-B <value>"));
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -B &lt;value&gt;
+   *  Set base of the expansion constant
+   *  (default = 1.3).</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception	if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {    
+    
+    super.setOptions(options);
+    
+    String optionString = Utils.getOption('B', options);
+    if (optionString.length() != 0)
+      setBase(Double.parseDouble(optionString));
+    else
+      setBase(1.3);      
+  }
+
+  /**
+   * Gets the current settings of KDtree.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-B");
+    result.add("" + getBase());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the distance/value of a given scale/level. I.e. the value of
+   * base^i (e.g. 2^i).
+   * 
+   * @param s 		the level/scale
+   * @return 		base^s
+   */
+  protected double dist_of_scale(int s) {
+    return Math.pow(m_Base, s);
+  }
+
+  /**
+   * Finds the scale/level of a given value. I.e. the "i" in base^i.
+   * 
+   * @param d 		the value whose scale/level is to be determined.
+   * @return 		the scale/level of the given value.
+   */
+  protected int get_scale(double d) {
+    return (int) Math.ceil(il2 * Math.log(d));
+  }
+
+  /**
+   * Creates a new internal node for a given Instance/point p.
+   * @param idx The index of the instance the node represents.
+   * @return Newly created CoverTreeNode. 
+   */
+  protected CoverTreeNode new_node(Integer idx) { // const point &p)
+    CoverTreeNode new_node = new CoverTreeNode();
+    new_node.idx = idx;
+    return new_node;
+  }
+
+  /**
+   * Creates a new leaf node for a given Instance/point p.
+   * @param idx The index of the instance this leaf node 
+   * represents.
+   * @return Newly created leaf CoverTreeNode.
+   */
+  protected CoverTreeNode new_leaf(Integer idx) { // (const point &p)
+    CoverTreeNode new_leaf = new CoverTreeNode(idx, 0.0, 0.0, null, 0, 100);
+    return new_leaf;
+  }
+
+  /**
+   * Returns the max distance of the reference point p in current node to
+   * it's children nodes.
+   * @param v The stack of DistanceNode objects.
+   * @return Distance of the furthest child.
+   */
+  protected double max_set(Stack<DistanceNode> v) { // rename to
+                                                        // maxChildDist
+    double max = 0.0;
+    for (int i = 0; i < v.length; i++) {
+      DistanceNode n = v.element(i);
+      if (max < n.dist.element(n.dist.length - 1).floatValue()) { // v[i].dist.last())
+        max = n.dist.element(n.dist.length - 1).floatValue(); // v[i].dist.last();
+      }
+    }
+    return max;
+  }
+
+  /**
+   * Splits a given point_set into near and far based on the given
+   * scale/level. All points with distance > base^max_scale would be moved
+   * to far set. In other words, all those points that are not covered by the 
+   * next child ball of a point p (ball made of the same point p but of 
+   * smaller radius at the next lower level) are removed from the supplied
+   * current point_set and put into far_set.  
+   * 
+   * @param point_set The supplied set from which all far points 
+   * would be removed.
+   * @param far_set The set in which all far points having distance
+   * > base^max_scale would be put into. 
+   * @param max_scale The given scale based on which the distances
+   * of points are judged to be far or near.   
+   */
+  protected void split(Stack<DistanceNode> point_set,
+      Stack<DistanceNode> far_set, int max_scale) {
+    int new_index = 0;
+    double fmax = dist_of_scale(max_scale);
+    for (int i = 0; i < point_set.length; i++) {
+      DistanceNode n = point_set.element(i);
+      if (n.dist.element(n.dist.length - 1).doubleValue() <= fmax) {
+        point_set.set(new_index++, point_set.element(i));
+      } else
+        far_set.push(point_set.element(i)); // point_set[i]);
+    }
+    List<DistanceNode> l = new java.util.LinkedList<DistanceNode>();
+    for (int i = 0; i < new_index; i++)
+      l.add(point_set.element(i));
+    //removing all and adding only the near points
+    point_set.clear();
+    point_set.addAll(l); // point_set.index=new_index;
+  }
+
+  /**
+   * Moves all the points in point_set covered by (the ball of) new_point 
+   * into new_point_set, based on the given scale/level.
+   * 
+   * @param point_set The supplied set of instances from which
+   * all points covered by new_point will be removed.
+   * @param new_point_set The set in which all points covered by
+   * new_point will be put into.
+   * @param new_point The given new point.
+   * @param max_scale The scale based on which distances are 
+   * judged (radius of cover ball is calculated).
+   */
+  protected void dist_split(Stack<DistanceNode> point_set,
+      Stack<DistanceNode> new_point_set, 
+      DistanceNode new_point, int max_scale) {
+    int new_index = 0;
+    double fmax = dist_of_scale(max_scale);
+    for (int i = 0; i < point_set.length; i++) {
+      double new_d =  Math.sqrt(m_DistanceFunction.distance(new_point.q(), 
+	  	       point_set.element(i).q(), fmax*fmax));
+      if (new_d <= fmax) {
+        point_set.element(i).dist.push(new_d);
+        new_point_set.push(point_set.element(i));
+      } else
+        point_set.set(new_index++, point_set.element(i));
+    }
+    List<DistanceNode> l = new java.util.LinkedList<DistanceNode>();
+    for (int i = 0; i < new_index; i++)
+      l.add(point_set.element(i));
+    point_set.clear();
+    point_set.addAll(l);
+  }
+
+  /**
+   * Creates a cover tree recursively using batch insert method. 
+   * 
+   * @param p The index of the instance from which to create the
+   * first node. All other points will be inserted beneath this node
+   * for p.
+   * @param max_scale The current scale/level where the node is to be
+   * created (Also determines the radius of the cover balls created at 
+   * this level).
+   * @param top_scale The max scale in the whole tree.
+   * @param point_set The set of unprocessed points from which child nodes
+   * need to be created.  
+   * @param consumed_set The set of processed points from which child
+   * nodes have already been created. This would be used to find the 
+   * radius of the cover ball of p. 
+   * @return the node of cover tree created with p.
+   */
+  protected CoverTreeNode batch_insert(Integer p, int max_scale, // current
+                                                                 // scale/level
+      int top_scale, // max scale/level for this dataset
+      Stack<DistanceNode> point_set, // set of points that are nearer to p
+                                        // [will also contain returned unused
+                                        // points]
+      Stack<DistanceNode> consumed_set) // to return the set of points that have
+                                        // been used to calc. max_dist to a
+                                        // descendent
+      // Stack<Stack<DistanceNode>> stack) //may not be needed
+      {
+    if (point_set.length == 0) {
+      CoverTreeNode leaf = new_leaf(p);
+      leaf.nodeid = m_NumNodes;
+      m_NumNodes++; // incrementing node count
+      m_NumLeaves++; // incrementing leaves count
+      return leaf;
+    } else {
+      double max_dist = max_set(point_set); // O(|point_set|) the max dist
+      // in point_set to point "p".
+      int next_scale = Math.min(max_scale - 1, get_scale(max_dist));
+      if (next_scale == Integer.MIN_VALUE) { // We have points with distance
+        // 0. if max_dist is 0.
+        Stack<CoverTreeNode> children = new Stack<CoverTreeNode>();
+        CoverTreeNode leaf = new_leaf(p);
+        leaf.nodeid = m_NumNodes;
+        children.push(leaf);
+        m_NumLeaves++;
+        m_NumNodes++; // incrementing node and leaf count
+        while (point_set.length > 0) {
+          DistanceNode tmpnode = point_set.pop();
+          leaf = new_leaf(tmpnode.idx);
+          leaf.nodeid = m_NumNodes;
+          children.push(leaf);
+          m_NumLeaves++;
+          m_NumNodes++; // incrementing node and leaf count
+          consumed_set.push(tmpnode);
+        }
+        CoverTreeNode n = new_node(p); // make a new node out of p and assign
+        // it the children.
+        n.nodeid = m_NumNodes;
+        m_NumNodes++; // incrementing node count
+        n.scale = 100; // A magic number meant to be larger than all scales.
+        n.max_dist = 0; // since all points have distance 0 to p
+        n.num_children = children.length;
+        n.children = children;
+        return n;
+      } else {
+        Stack<DistanceNode> far = new Stack<DistanceNode>();
+        split(point_set, far, max_scale); // O(|point_set|)
+
+        CoverTreeNode child = batch_insert(p, next_scale, top_scale, point_set,
+            consumed_set);
+
+        if (point_set.length == 0) { // not creating any node in this
+          // recursive call
+          // push(stack,point_set);
+          point_set.replaceAllBy(far); // point_set=far;
+          return child;
+        } else {
+          CoverTreeNode n = new_node(p);
+          n.nodeid = m_NumNodes;
+          m_NumNodes++; // incrementing node count
+          Stack<CoverTreeNode> children = new Stack<CoverTreeNode>();
+          children.push(child);
+
+          while (point_set.length != 0) { // O(|point_set| * num_children)
+            Stack<DistanceNode> new_point_set = new Stack<DistanceNode>();
+            Stack<DistanceNode> new_consumed_set = new Stack<DistanceNode>();
+            DistanceNode tmpnode = point_set.pop();
+            double new_dist = tmpnode.dist.last();
+            consumed_set.push(tmpnode);
+
+            // putting points closer to new_point into new_point_set (and
+            // removing them from point_set)
+            dist_split(point_set, new_point_set, tmpnode, max_scale); // O(|point_saet|)
+            // putting points closer to new_point into new_point_set (and
+            // removing them from far)
+            dist_split(far, new_point_set, tmpnode, max_scale); // O(|far|)
+
+            CoverTreeNode new_child = batch_insert(tmpnode.idx, next_scale,
+                top_scale, new_point_set, new_consumed_set);
+            new_child.parent_dist = new_dist;
+
+            children.push(new_child);
+
+            // putting the unused points from new_point_set back into
+            // point_set and far
+            double fmax = dist_of_scale(max_scale);
+            tmpnode = null;
+            for (int i = 0; i < new_point_set.length; i++) { // O(|new_point_set|)
+              tmpnode = new_point_set.element(i);
+              tmpnode.dist.pop();
+              if (tmpnode.dist.last() <= fmax)
+                point_set.push(tmpnode);
+              else
+                far.push(tmpnode);
+            }
+            // putting the points consumed while recursing for new_point
+            // into consumed_set
+            tmpnode = null;
+            for (int i = 0; i < new_consumed_set.length; i++) { // O(|new_point_set|)
+              tmpnode = new_consumed_set.element(i);
+              tmpnode.dist.pop();
+              consumed_set.push(tmpnode);
+            }
+          }// end while(point_size.size!=0)
+          point_set.replaceAllBy(far); // point_set=far;
+          n.scale = top_scale - max_scale;
+          n.max_dist = max_set(consumed_set);
+          n.num_children = children.length;
+          n.children = children;
+          return n;
+        }// end else if(pointset!=0)
+      }// end else if(next_scale != -214....
+    }// end else if(pointset!=0)
+  }
+
+  /** 
+   * Builds the tree on the given set of instances.
+   * P.S.: For internal use only. Outside classes 
+   * should call setInstances(). 
+   * @param insts The instances on which to build 
+   * the cover tree.
+   * @throws Exception If the supplied set of 
+   * Instances is empty, or if there are missing
+   * values. 
+   */
+  protected void buildCoverTree(Instances insts) throws Exception {
+    if (insts.numInstances() == 0)
+      throw new Exception(
+	  "CoverTree: Empty set of instances. Cannot build tree.");
+    checkMissing(insts);
+    if (m_EuclideanDistance == null)
+      m_DistanceFunction = m_EuclideanDistance = new EuclideanDistance(insts);
+    else
+      m_EuclideanDistance.setInstances(insts);
+    
+    Stack<DistanceNode> point_set = new Stack<DistanceNode>();
+    Stack<DistanceNode> consumed_set = new Stack<DistanceNode>();
+
+    Instance point_p = insts.instance(0); int p_idx = 0;
+    double max_dist=-1, dist=0.0; Instance max_q=point_p;
+    
+    for (int i = 1; i < insts.numInstances(); i++) {
+      DistanceNode temp = new DistanceNode();
+      temp.dist = new Stack<Double>();
+      dist = Math.sqrt(m_DistanceFunction.distance(point_p, insts.instance(i), Double.POSITIVE_INFINITY));
+      if(dist > max_dist) {
+        max_dist = dist; max_q = insts.instance(i);
+      }
+      temp.dist.push(dist);
+      temp.idx = i;
+      point_set.push(temp);
+    }
+    
+      max_dist = max_set(point_set);
+      m_Root = batch_insert(p_idx, get_scale(max_dist), get_scale(max_dist),
+                            point_set, consumed_set);
+  }
+
+/*********************************NNSearch related stuff********************/
+
+  /**
+   * A class for a heap to store the nearest k neighbours to an instance. 
+   * The heap also takes care of cases where multiple neighbours are the same 
+   * distance away.
+   * i.e. the minimum size of the heap is k.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  protected class MyHeap
+    implements RevisionHandler {
+    
+    /** the heap. */
+    MyHeapElement m_heap[] = null;
+    
+    /**
+     * constructor.
+     * @param maxSize   the maximum size of the heap
+     */
+    public MyHeap(int maxSize) {
+      if((maxSize%2)==0)
+        maxSize++;
+      
+      m_heap = new MyHeapElement[maxSize+1];
+      m_heap[0] = new MyHeapElement(-1);
+    }
+    
+    /**
+     * returns the size of the heap.
+     * @return the size
+     */
+    public int size() {
+      return m_heap[0].index;
+    }
+    
+    /**
+     * peeks at the first element.
+     * @return the first element
+     */
+    public MyHeapElement peek() {
+      return m_heap[1];
+    }
+    
+    /**
+     * returns the first element and removes it from the heap.
+     * @return the first element
+     * @throws Exception  if no elements in heap
+     */
+    public MyHeapElement get() throws Exception  {
+      if(m_heap[0].index==0)
+        throw new Exception("No elements present in the heap");
+      MyHeapElement r = m_heap[1];
+      m_heap[1] = m_heap[m_heap[0].index];
+      m_heap[0].index--;
+      downheap();
+      return r;
+    }
+    
+    /**
+     * adds the distance value to the heap.
+     * 
+     * @param d the distance value 
+     * @throws Exception  if the heap gets too large
+     */
+    public void put(double d) throws Exception {
+      if((m_heap[0].index+1)>(m_heap.length-1))
+        throw new Exception("the number of elements cannot exceed the "+
+        "initially set maximum limit");
+      m_heap[0].index++;
+      m_heap[m_heap[0].index] = new MyHeapElement(d);
+      upheap();
+    }
+    
+    /**
+     * Puts an element by substituting it in place of 
+     * the top most element.
+     * 
+     * @param d The distance value.
+     * @throws Exception If distance is smaller than that of the head
+     *         element.
+     */
+    public void putBySubstitute(double d) throws Exception {
+      MyHeapElement head = get();
+      put(d);
+      if(head.distance == m_heap[1].distance) {
+        putKthNearest(head.distance);
+      }
+      else if(head.distance > m_heap[1].distance) {
+        m_KthNearest = null;
+        m_KthNearestSize = 0;
+        initSize = 10;
+      }
+      else if(head.distance < m_heap[1].distance) {
+        throw new Exception("The substituted element is greater than the "+
+        "head element. put() should have been called "+
+        "in place of putBySubstitute()");
+      }
+    }
+    
+    /** the kth nearest ones. */
+    MyHeapElement m_KthNearest[] = null;
+    
+    /** The number of kth nearest elements. */
+    int m_KthNearestSize = 0;
+    
+    /** the initial size of the heap. */
+    int initSize=10;
+    
+    /**
+     * returns the number of k nearest.
+     * 
+     * @return the number of k nearest
+     * @see     #m_KthNearestSize
+     */
+    public int noOfKthNearest() {
+      return m_KthNearestSize;
+    }
+    
+    /**
+     * Stores kth nearest elements (if there are 
+     * more than one).
+     * @param d the distance 
+     */
+    public void putKthNearest(double d) {
+      if(m_KthNearest==null) {
+        m_KthNearest = new MyHeapElement[initSize];
+      }
+      if(m_KthNearestSize>=m_KthNearest.length) {
+        initSize += initSize;
+        MyHeapElement temp[] = new MyHeapElement[initSize];
+        System.arraycopy(m_KthNearest, 0, temp, 0, m_KthNearest.length);
+        m_KthNearest = temp;
+      }
+      m_KthNearest[m_KthNearestSize++] = new MyHeapElement(d);
+    }
+    
+    /**
+     * returns the kth nearest element or null if none there.
+     * 
+     * @return      the kth nearest element
+     */
+    public MyHeapElement getKthNearest() {
+      if(m_KthNearestSize==0)
+        return null;
+      m_KthNearestSize--;
+      return m_KthNearest[m_KthNearestSize];
+    }
+    
+    /** 
+     * performs upheap operation for the heap 
+     * to maintian its properties. 
+     */
+    protected void upheap() {
+      int i = m_heap[0].index;
+      MyHeapElement temp;
+      while( i > 1  && m_heap[i].distance>m_heap[i/2].distance) {
+        temp = m_heap[i];
+        m_heap[i] = m_heap[i/2];
+        i = i/2;
+        m_heap[i] = temp; //this is i/2 done here to avoid another division.
+      }
+    }
+    
+    /** 
+     * performs downheap operation for the heap 
+     * to maintian its properties. 
+     */
+    protected void downheap() {
+      int i = 1;
+      MyHeapElement temp;
+      while( ( (2*i) <= m_heap[0].index &&
+      m_heap[i].distance < m_heap[2*i].distance )
+      ||
+      ( (2*i+1) <= m_heap[0].index &&
+      m_heap[i].distance < m_heap[2*i+1].distance) ) {
+        if((2*i+1)<=m_heap[0].index) {
+          if(m_heap[2*i].distance>m_heap[2*i+1].distance) {
+            temp = m_heap[i];
+            m_heap[i] = m_heap[2*i];
+            i = 2*i;
+            m_heap[i] = temp;
+          }
+          else {
+            temp = m_heap[i];
+            m_heap[i] = m_heap[2*i+1];
+            i = 2*i+1;
+            m_heap[i] = temp;
+          }
+        }
+        else {
+          temp = m_heap[i];
+          m_heap[i] = m_heap[2*i];
+          i = 2*i;
+          m_heap[i] = temp;
+        }
+      }
+    }
+    
+    /**
+     * returns the total size.
+     * 
+     * @return      the total size
+     */
+    public int totalSize() {
+      return size()+noOfKthNearest();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A class for storing data about a neighboring instance.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  protected class MyHeapElement
+    implements RevisionHandler {
+    
+    /** the distance. */
+    public double distance;
+    
+    /** 
+     * The index of this element. Also used as 
+     * the size of the heap in the first element.
+     */
+    int index = 0;
+    
+    /**
+     * constructor.
+     * 
+     * @param d   the distance
+     */
+    public MyHeapElement(double d) {
+      distance = d;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * stores a CoverTreeNode and its distance to the current query node.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  private class d_node
+    implements RevisionHandler {
+    
+    /** The distance of the node's point to the query point. */
+    double dist;
+    
+    /** The node. */
+    CoverTreeNode n;
+    
+    /** 
+     * Constructor.
+     * @param d The distance of the node to the query.
+     * @param node The node. 
+     */
+    public d_node(double d, CoverTreeNode node) {
+      dist = d;
+      n = node;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  };
+
+  /** 
+   * Initializes a heap with k values of the the given upper_bound.
+   * 
+   * @param heap The heap to put values into.
+   * @param upper_bound The value to put into heap (the value with 
+   * which it should be initialized).
+   * @param k The number of times upper_bound should be put into
+   * heap for initialization.
+   * @throws Exception If there is some problem in initializing 
+   * the heap (if k &gt; size of the heap).
+   */
+  protected void setter(MyHeap heap, double upper_bound, final int k) throws Exception {
+    if(heap.size()>0)
+      heap.m_heap[0].index=0;
+
+    while(heap.size() < k) {
+      heap.put(upper_bound);
+    }
+  }
+
+  /** 
+   * Replaces the current top/max value in the heap with the new one.
+   * The new max value should be &lt;= the old one.
+   * 
+   * @param upper_bound The heap.
+   * @param new_bound The new value that should replace the old top one.
+   * @throws Exception if the new value is greater than the old value.
+   */
+  protected void update(MyHeap upper_bound, double new_bound) throws Exception {
+    upper_bound.putBySubstitute(new_bound);
+  }
+  
+  /**
+   * Returns a cover set for a given level/scale.
+   * A cover set for a level consists of nodes whose 
+   * Instances/centres are which are inside the query
+   * ball at that level. If no cover set exists for the
+   * given level (if it is the first time it is going 
+   * to be used), than a new one is created.  
+   * 
+   * @param idx The level/scale for which the cover set 
+   * is required.
+   * @param cover_sets The covers sets. Consists of stack 
+   * of a stack of d_node objects. 
+   * @return The cover set for the given level/scale.
+   */
+  protected Stack<d_node> getCoverSet(int idx, Stack<Stack<d_node>> cover_sets) {
+    if (cover_sets.length <= idx) {
+      int i = cover_sets.length - 1;
+      while (i < idx) {
+        i++;
+        Stack<d_node> new_cover_set = new Stack<d_node>();
+        cover_sets.push(new_cover_set);
+      }
+    }
+    return cover_sets.element(idx);
+  }
+  
+  /**
+   * Copies the contents of one zero set to the other. This
+   * is required if we are going to inspect child of some query node 
+   * (if the queries are given in batch in the form of a cover tree).
+   * Only those nodes are copied to the new zero set that are inside
+   * the query ball of query_chi.
+   * P.S.: A zero set is a set of all leaf nodes that are found
+   * to be inside the query ball.  
+   *   
+   * @param query_chi The child node of our query node that we are 
+   * going to inspect. 
+   * @param new_upper_k New heap that will store the distances of the
+   * k NNs for query_chi.
+   * @param zero_set The zero set of query_chi's parent that needs
+   * to be copied.
+   * @param new_zero_set The new zero set of query_chi where old zero
+   * sets need to be copied into.
+   * @throws Exception If there is some problem.
+   */
+  protected void copy_zero_set(CoverTreeNode query_chi, MyHeap new_upper_k, 
+      			Stack<d_node> zero_set, Stack<d_node> new_zero_set) throws Exception {
+    new_zero_set.clear();
+    d_node ele;
+    for (int i = 0; i < zero_set.length; i++) {
+      ele = zero_set.element(i);
+      double upper_dist = new_upper_k.peek().distance + query_chi.max_dist;
+      if (shell(ele.dist, query_chi.parent_dist, upper_dist)) {
+        double d = Math.sqrt(m_DistanceFunction.distance(query_chi.p(), ele.n
+            .p(), upper_dist * upper_dist));
+        if (m_TreeStats != null)
+          m_TreeStats.incrPointCount();
+        if (d <= upper_dist) {
+          if (d < new_upper_k.peek().distance)
+            update(new_upper_k, d);
+          d_node temp = new d_node(d, ele.n);
+          new_zero_set.push(temp);
+          if (m_TreeStats != null)
+            m_TreeStats.incrLeafCount();
+        }//end if(d<newupperbound)
+      }//end if(shell(...
+    }//end for
+  }
+  
+
+  /**
+   * Copies the contents of one set of cover sets to the other. It
+   * is required if we are going to inspect child of some query node 
+   * (if the queries are given in batch in the form of a cover tree).
+   * For each level, only those nodes are copied to the new set 
+   * which are inside the query ball of query_chi at that level.
+   * 
+   * @param query_chi The child node of our query node that we are 
+   * going to inspect. 
+   * @param new_upper_k New heap that will store the distances of the
+   * k NNs for query_chi.
+   * @param cover_sets The cover_sets of query_chi's parent, which
+   * need to be copied to new_cover_sets.
+   * @param new_cover_sets The new set of cover_sets that need to
+   * contain contents of cover_sets. 
+   * @param current_scale The scale/level we are inspecting in our 
+   * cover tree.
+   * @param max_scale The maximum level so far possible in our 
+   * search (this is only updated as we descend and a deeper
+   * child is found inside the query ball).   
+   * @throws Exception If there is problem.
+   */
+  protected void copy_cover_sets(CoverTreeNode query_chi, MyHeap new_upper_k,
+      		Stack<Stack<d_node>> cover_sets,
+      		Stack<Stack<d_node>> new_cover_sets,
+      		int current_scale, int max_scale) throws Exception {
+    new_cover_sets.clear();
+    for (; current_scale <= max_scale; current_scale++) {
+      d_node ele;
+      Stack<d_node> cover_set_currentscale = getCoverSet(current_scale,
+          cover_sets);
+      for (int i = 0; i < cover_set_currentscale.length; i++) { // ; ele != end;
+                                                                // ele++) {
+        ele = cover_set_currentscale.element(i);
+        double upper_dist = new_upper_k.peek().distance + query_chi.max_dist
+            + ele.n.max_dist;
+        if (shell(ele.dist, query_chi.parent_dist, upper_dist)) {
+          double d = Math.sqrt(m_DistanceFunction.distance(query_chi.p(), ele.n
+              .p(), upper_dist * upper_dist));
+          if (m_TreeStats != null)
+            m_TreeStats.incrPointCount();
+          if (d <= upper_dist) {
+            if (d < new_upper_k.peek().distance)
+              update(new_upper_k, d);
+            d_node temp = new d_node(d, ele.n);
+            new_cover_sets.element(current_scale).push(temp);
+            if (m_TreeStats != null)
+              m_TreeStats.incrIntNodeCount();
+          }// end if(d<=..
+        }// end if(shell(...
+      }// end for(coverset_i)
+    }// end for(scales)
+  }
+  
+
+  /**
+   * Prints the given cover sets and zero set.
+   * 
+   * @param cover_sets The cover sets to print.
+   * @param zero_set The zero set to print.  
+   * @param current_scale The scale/level to start printing
+   * the cover sets from. 
+   * @param max_scale The max scale/level to print the cover
+   * sets upto. 
+   */
+  void print_cover_sets(Stack<Stack<d_node>> cover_sets,
+      Stack<d_node> zero_set, int current_scale, int max_scale) {
+    d_node ele;
+    println("cover set = ");
+    for (; current_scale <= max_scale; current_scale++) {
+      println("" + current_scale);
+      for (int i = 0; i < cover_sets.element(current_scale).length; i++) {
+        ele = cover_sets.element(current_scale).element(i);
+        CoverTreeNode n = ele.n;
+        println(n.p());
+      }
+    }
+    println("infinity");
+    for (int i = 0; i < zero_set.length; i++) {
+      ele = zero_set.element(i);
+      CoverTreeNode n = ele.n;
+      println(n.p());
+    }
+  }
+  
+  
+  /**
+   * Swap two nodes in a cover set.
+   * 
+   * @param a The index first node.
+   * @param b The index of second node.
+   * @param cover_set The cover set in which the two nodes are.
+   */
+
+  protected void SWAP(int a, int b, Stack<d_node>cover_set) {				
+    d_node tmp = cover_set.element(a);
+    cover_set.set(a, cover_set.element(b));
+    cover_set.set(b, tmp);
+  }
+  
+  
+  
+  /**
+   * Returns the difference of two given nodes distance to 
+   * the query. It is used in half-sorting a cover set. 
+   *   
+   * @param p1 The index of first node.
+   * @param p2 The index of second node.
+   * @param cover_set The cover set containing the two given
+   * nodes.
+   * @return dist_to_query_of_p1 - dist_to_query_of_p2
+   */
+  
+  protected double compare(final int p1, final int p2, Stack<d_node> cover_set) {
+    return cover_set.element(p1).dist - cover_set.element(p2).dist;
+  }
+  
+  /**
+   * Half-sorts a cover set, so that nodes nearer to the query
+   * are at the front. 
+   * @param cover_set The cover set to sort.
+   */
+
+  protected void halfsort(Stack<d_node> cover_set) {
+    if(cover_set.length <= 1)
+      return;
+    int start=0;
+    int hi = cover_set.length-1;
+    int right = hi;
+    int left;
+    
+    while (right > start) {
+      int mid = start + ((hi - start) >> 1);
+
+      boolean jumpover = false;
+      if (compare(mid, start, cover_set) < 0.0)
+        SWAP(mid, start, cover_set);
+      if (compare(hi, mid, cover_set) < 0.0)
+        SWAP(mid, hi, cover_set);
+      else
+        jumpover = true;
+      if (!jumpover && compare(mid, start, cover_set) < 0.0)
+        SWAP(mid, start, cover_set);
+      jump_over:
+      ;
+
+      left = start + 1;
+      right = hi - 1;
+
+      do {
+        while (compare(left, mid, cover_set) < 0.0)
+          left++;
+
+        while (compare(mid, right, cover_set) < 0.0)
+          right--;
+
+        if (left < right) {
+          SWAP(left, right, cover_set);
+          if (mid == left)
+            mid = right;
+          else if (mid == right)
+            mid = left;
+          left++;
+          right--;
+        } else if (left == right) {
+          left++;
+          right--;
+          break;
+        }
+      } while (left <= right);
+      hi = right;
+    }
+  }
+
+  /**
+   * Function to check if a child node can be inside a query ball, 
+   * without calculating the child node's distance to the query.
+   * This further avoids unnecessary distance calculation. 
+   *  
+   * @param parent_query_dist The distance of parent to the query
+   * @param child_parent_dist The distance of child to the parent.
+   * @param upper_bound The distance to the query of the best kth 
+   * NN found so far.
+   * @return true If child can be inside the query ball.
+   */
+  protected boolean shell(double parent_query_dist, double child_parent_dist, double upper_bound) {
+    return parent_query_dist - child_parent_dist <= upper_bound;
+  }
+  
+  /**
+   * This functions adds nodes for inspection at the next level during NN 
+   * search. The internal nodes are added to one of the cover sets (at 
+   * the level of the child node which is added) and leaf nodes are
+   * added to the zero set.  
+   *  
+   * An optimization to consider:
+   * Make all distance evaluations occur in descend.
+   * 
+   * Instead of passing a cover_set, pass a stack of cover sets.  The
+   * last element holds d_nodes with your distance.  The next lower
+   * element holds a d_node with the distance to your query parent,
+   * next = query grand parent, etc..
+   * 
+   * Compute distances in the presence of the tighter upper bound.
+   * @param query The query (in shape of a cover tree node, as we 
+   * are doing batch searching).
+   * @param upper_k Heap containing distances of best k-NNs found so 
+   * far.
+   * @param current_scale The current scale/level being looked at in 
+   * the tree.
+   * @param max_scale The max scale/level that has so far been looked
+   * at.
+   * @param cover_sets The cover sets of tree nodes for each level of 
+   * our trees for.
+   * @param zero_set The set containing leaf nodes.
+   * @return A new max_scale, if we descend to a deeper level.
+   * @throws Exception If there is some problem (in updating the 
+   * heap upper_k).
+   */
+  protected int descend(final CoverTreeNode query, MyHeap upper_k,
+      int current_scale, int max_scale, // amk14comment: make sure this gets
+                                        // passed by reference in Java
+      Stack<Stack<d_node>> cover_sets, // amk14comment: contains children in
+                                        // set Q in paper
+      Stack<d_node> zero_set) // amk14comment: zeroset contains the children at
+                              // the lowest level i.e. -infinity
+      throws Exception {
+    d_node parent;
+    Stack<d_node> cover_set_currentscale = getCoverSet(current_scale,
+        cover_sets);
+    for (int i = 0; i < cover_set_currentscale.length; i++) {
+      parent = cover_set_currentscale.element(i);
+      CoverTreeNode par = parent.n;
+      double upper_dist = upper_k.peek().distance + query.max_dist
+          + query.max_dist; // *upper_bound + query->max_dist + query->max_dist;
+      if (parent.dist <= upper_dist + par.max_dist) {
+        CoverTreeNode chi;
+        if (par == m_Root && par.num_children == 0) // if our tree consists of
+                                                    // only one root(which is
+                                                    // also leaf) node
+          chi = par;
+        else
+          chi = par.children.element(0);
+        if (parent.dist <= upper_dist + chi.max_dist) { // amk14comment: looking
+                                                        // at child_0 (which is
+                                                        // the parent itself)
+          if (chi.num_children > 0) {
+            if (max_scale < chi.scale) {
+              max_scale = chi.scale;
+            }
+            d_node temp = new d_node(parent.dist, chi);
+            getCoverSet(chi.scale, cover_sets).push(temp);
+            if (m_TreeStats != null)
+              m_TreeStats.incrIntNodeCount();
+          } else if (parent.dist <= upper_dist) {
+            d_node temp = new d_node(parent.dist, chi);
+            zero_set.push(temp);
+            if (m_TreeStats != null)
+              m_TreeStats.incrLeafCount();
+          }
+        }
+        for (int c = 1; c < par.num_children; c++) {
+          chi = par.children.element(c);
+          double upper_chi = upper_k.peek().distance + chi.max_dist
+              + query.max_dist + query.max_dist; // *upper_bound + chi.max_dist
+                                                  // + query.max_dist +
+                                                  // query.max_dist;
+          if (shell(parent.dist, chi.parent_dist, upper_chi)) { // amk14comment:parent_query_dist
+                                                                // -
+                                                                // child_parent_dist
+                                                                // <= upper_chi - if child can be 
+                                                                // inside the shrunk query ball 
+            // NOT the same as above parent->dist <= upper_dist + chi->max_dist
+            double d = Math.sqrt(m_DistanceFunction.distance(query.p(),
+                chi.p(), upper_chi * upper_chi, m_TreeStats));
+            if (m_TreeStats != null)
+              m_TreeStats.incrPointCount();
+            if (d <= upper_chi) { //if child is inside the shrunk query ball
+              if (d < upper_k.peek().distance) // *upper_bound)
+                update(upper_k, d);
+              if (chi.num_children > 0) {
+                if (max_scale < chi.scale) {
+                  max_scale = chi.scale;
+                }
+                d_node temp = new d_node(d, chi);
+                getCoverSet(chi.scale, cover_sets).push(temp);
+                if (m_TreeStats != null)
+                  m_TreeStats.incrIntNodeCount();
+              } else if (d <= upper_chi - chi.max_dist) {
+                d_node temp = new d_node(d, chi);
+                zero_set.push(temp);
+                if (m_TreeStats != null)
+                  m_TreeStats.incrLeafCount();
+              }
+            }//end if(d<=upper_chi)
+          }//end if(shell(parent.dist,...
+        }//end for(child_1 to n)
+      }//end if(parent.dist<=upper_dist..
+    }//end for(covers_sets[current_scale][i])
+    return max_scale;
+  }
+  
+  /**
+   * Does a brute force NN search on the nodes in the given zero set.
+   * A zero set might have some nodes added to it that were not k-NNs,
+   * so need to do a brute-force to pick only the k-NNs (without 
+   * calculating distances, as each node in the zero set already had 
+   * its distance calculated to the query, which is stored with the
+   * node).
+   *  
+   * @param k The k in kNN.
+   * @param query The query. 
+   * @param zero_set The zero set on which the brute force NN search
+   * is performed.
+   * @param upper_k The heap storing distances of k-NNs found during
+   * the search.
+   * @param results The returned k-NNs.
+   * @throws Exception If there is somem problem.
+   */
+  protected void brute_nearest(final int k, final CoverTreeNode query,
+      Stack<d_node> zero_set, MyHeap upper_k, Stack<NeighborList> results)
+      throws Exception {
+    if (query.num_children > 0) {
+      Stack<d_node> new_zero_set = new Stack<d_node>();
+      CoverTreeNode query_chi = query.children.element(0);
+      brute_nearest(k, query_chi, zero_set, upper_k, results);
+      MyHeap new_upper_k = new MyHeap(k);
+
+      for (int i = 1; i < query.children.length; i++) {
+        query_chi = query.children.element(i);
+        setter(new_upper_k, upper_k.peek().distance + query_chi.parent_dist, k);
+        copy_zero_set(query_chi, new_upper_k, zero_set, new_zero_set);
+        brute_nearest(k, query_chi, new_zero_set, new_upper_k, results);
+      }
+    } else {
+      NeighborList temp = new NeighborList(k);
+      d_node ele;
+      for (int i = 0; i < zero_set.length; i++) {
+        ele = zero_set.element(i);
+        if (ele.dist <= upper_k.peek().distance) {
+          temp.insertSorted(ele.dist, ele.n.p()); // temp.push(ele.n.p());
+        }
+      }
+      results.push(temp);
+    }
+  }
+  
+  /**
+   * Performs a recursive k-NN search for a given batch of queries provided in the
+   * form of a cover tree. P.S.: This function should not be called from outside. 
+   * Outside classes should use kNearestNeighbours() instead.
+   *  
+   * @param k The number of NNs to find.
+   * @param query_node The node of the query tree to start the search from.
+   * @param cover_sets The set of sets that contains internal
+   * nodes that were found to be inside the query ball at previous scales/levels
+   * (intially there would be just the root node at root level).
+   * @param zero_set The set that'll contain the leaf nodes that are found to
+   * be inside the query ball.
+   * @param current_scale The level/scale to do the search from (this value
+   * would be used to inspect the cover set in the provided set of cover sets).
+   * @param max_scale The max scale/level that has so far been inspected.
+   * @param upper_k The heap containing distances of the best k-NNs found so
+   * far (initialized to Double.POSITIVE_INFINITY).
+   * @param results The list of returned k-NNs.
+   * @throws Exception If there is some problem during the search.
+   */
+  protected void internal_batch_nearest_neighbor(final int k, 
+      					final CoverTreeNode query_node,
+      					Stack<Stack<d_node>> cover_sets,
+      					Stack<d_node> zero_set,
+      					int current_scale,
+      					int max_scale,
+      					MyHeap upper_k,
+      					Stack<NeighborList> results) throws Exception {
+    if (current_scale > max_scale) { // All remaining points are in the zero set.
+      brute_nearest(k, query_node, zero_set, upper_k, results);
+    } else {
+      // Our query_node has too much scale. Reduce.
+      if (query_node.scale <= current_scale && query_node.scale != 100) { // amk14comment:if j>=i in paper
+        CoverTreeNode query_chi;
+        Stack<d_node> new_zero_set = new Stack<d_node>();
+        Stack<Stack<d_node>> new_cover_sets = new Stack<Stack<d_node>>();
+        MyHeap new_upper_k = new MyHeap(k);
+
+        for (int i = 1; i < query_node.num_children; i++) { //processing child_1 and onwards
+          query_chi = query_node.children.element(i);
+          setter(new_upper_k, upper_k.peek().distance + query_chi.parent_dist, k);
+          //copy the zero set that satisfy a certain bound to the new zero set
+          copy_zero_set(query_chi, new_upper_k, zero_set, new_zero_set);
+          //copy the coversets[current_scale] nodes that satisfy a certain
+          //bound to the new_cover_sets[current_scale]
+          copy_cover_sets(query_chi, new_upper_k, cover_sets, new_cover_sets,
+              current_scale, max_scale);
+          //search for the query_node child in the nodes nearer to it.
+          internal_batch_nearest_neighbor(k, query_chi, new_cover_sets,
+              new_zero_set, current_scale, max_scale, new_upper_k, results);
+        }
+        new_cover_sets = null;
+        new_zero_set = null;
+        new_upper_k = null;
+        // now doing child_0 //which is the parent itself, that's why we don't
+        // need new_zero_set or new_cover_sets
+        internal_batch_nearest_neighbor(k, query_node.children.element(0),
+            cover_sets, zero_set, current_scale, max_scale, upper_k, results);
+      } else { // reduce cover set scale -- amk14comment: if j<i in paper
+        Stack<d_node> cover_set_i = getCoverSet(current_scale, cover_sets);
+        // println("sorting");
+        halfsort(cover_set_i);
+        max_scale = descend(query_node, upper_k, current_scale, max_scale,
+            cover_sets, zero_set);
+        cover_set_i.clear();
+        current_scale++;
+        internal_batch_nearest_neighbor(k, query_node, cover_sets, zero_set,
+            current_scale, max_scale, upper_k, results);
+      }
+    }
+  }
+  
+  /**
+   * Performs k-NN search for a batch of queries provided in the form
+   * of a cover tree. P.S.: Outside classes should call 
+   * kNearestNeighbours().
+   * 
+   * @param k The number of k-NNs to find.
+   * @param tree_root The root of the cover tree on which k-NN search
+   * is to be performed.
+   * @param query_root The root of the cover tree consisting of queries. 
+   * @param results The list of returned k-NNs.
+   * @throws Exception If there is some problem during the search.
+   */
+  protected void batch_nearest_neighbor(final int k, CoverTreeNode tree_root, CoverTreeNode query_root, 
+      			      Stack<NeighborList> results) throws Exception {
+    //amk14comment: These contain the covering nodes at each level    
+    Stack<Stack<d_node>> cover_sets = new Stack<Stack<d_node>>(100);  
+    //amk14comment: These contain the nodes thought to be nearest at the leaf level
+    Stack<d_node> zero_set = new Stack<d_node>(); 
+    MyHeap upper_k = new MyHeap(k);
+    //probably not needed //amk14comment:initializes the array to MAXFLOAT
+    setter(upper_k, Double.POSITIVE_INFINITY, k); 
+
+    // amk14comment:distance from top query point to top node point
+    double treeroot_to_query_dist = Math.sqrt(m_DistanceFunction.distance(
+        query_root.p(), tree_root.p(), Double.POSITIVE_INFINITY));
+    // amk14comment:probably stores the kth smallest distances encountered so
+    // far
+    update(upper_k, treeroot_to_query_dist);
+
+    d_node temp = new d_node(treeroot_to_query_dist, tree_root);
+    getCoverSet(0, cover_sets).push(temp);
+
+    // incrementing counts for the root node
+    if (m_TreeStats != null) {
+      m_TreeStats.incrPointCount();
+      if (tree_root.num_children > 0)
+        m_TreeStats.incrIntNodeCount();
+      else
+        m_TreeStats.incrLeafCount();
+    }
+
+    internal_batch_nearest_neighbor(k, query_root, cover_sets, zero_set, 0, 0,
+        upper_k, results);
+  }
+  
+  /**
+   * Performs k-NN serach for a single given query/test Instance.
+   * 
+   * @param target The query/test instance.
+   * @param k Number of k-NNs to find.
+   * @return List of k-NNs.
+   * @throws Exception If there is some problem during the search
+   * for k-NNs.
+   */
+  protected NeighborList findKNearest(final Instance target, final int k) throws Exception {
+    Stack<d_node> cover_set_current = new Stack<d_node>(),
+    	           cover_set_next,
+    	           zero_set = new Stack<d_node>();
+    CoverTreeNode parent, child; d_node par;
+    MyHeap upper_k = new MyHeap(k);    
+    double d = Math.sqrt(m_DistanceFunction.distance(m_Root.p(), target, Double.POSITIVE_INFINITY, m_TreeStats)),
+           upper_bound;
+    cover_set_current.push(new d_node(d, m_Root));    
+    setter(upper_k, Double.POSITIVE_INFINITY, k);
+    this.update(upper_k, d);
+    //updating stats for the root node
+    if(m_TreeStats!=null) {
+      	if(m_Root.num_children > 0)
+      	  m_TreeStats.incrIntNodeCount();
+      	else
+      	  m_TreeStats.incrLeafCount();
+      	m_TreeStats.incrPointCount();
+    }
+    
+    //if root is the only node
+    if(m_Root.num_children==0) {
+      NeighborList list = new NeighborList(k);
+      list.insertSorted(d, m_Root.p());
+      return list;
+    }
+    //else
+    while(cover_set_current.length>0) {
+      cover_set_next = new Stack<d_node>();
+      for(int i=0; i<cover_set_current.length; i++) {
+	par = cover_set_current.element(i);
+	parent = par.n;
+	for(int c=0; c<parent.num_children; c++) {
+	  child = parent.children.element(c);
+	  upper_bound = upper_k.peek().distance;
+	  if(c==0)
+	    d = par.dist;
+	  else {
+	    d = upper_bound + child.max_dist;
+	    d = Math.sqrt(m_DistanceFunction.distance(child.p(), target, d*d, m_TreeStats));
+	      if(m_TreeStats!=null)
+		m_TreeStats.incrPointCount();
+	  }
+	  if(d <= (upper_bound + child.max_dist)) {
+	    if(c>0 && d < upper_bound) {
+	      update(upper_k, d);
+	    }
+	    if(child.num_children > 0) {
+	      cover_set_next.push(new d_node(d, child));
+	      if(m_TreeStats!=null)
+		m_TreeStats.incrIntNodeCount();
+	    }
+	    else if (d <= upper_bound){
+	      zero_set.push(new d_node(d, child));
+	      if(m_TreeStats!=null)
+		m_TreeStats.incrLeafCount();
+	    }
+	  }
+	} //end for current_set children
+      } //end for current_set elements
+      cover_set_current = cover_set_next;
+    } //end while(curret_set not empty)
+    
+    NeighborList list = new NeighborList(k);
+    d_node tmpnode;
+    upper_bound = upper_k.peek().distance;      
+    for(int i=0; i<zero_set.length; i++) {
+      tmpnode = zero_set.element(i);
+      if(tmpnode.dist <= upper_bound)
+	list.insertSorted(tmpnode.dist, tmpnode.n.p());
+    }
+    
+    if(list.currentLength()<=0)
+      throw new Exception("Error: No neighbour found. This cannot happen");
+    
+    return list;
+  }
+  
+/*********************************NNSearch related stuff above.********************/  
+
+  /**
+   * Returns k-NNs of a given target instance, from among the previously
+   * supplied training instances (supplied through setInstances method)
+   * P.S.: May return more than k-NNs if more one instances have
+   * the same distance to the target as the kth NN.
+   * 
+   * @param target The instance for which k-NNs are required.
+   * @param k The number of k-NNs to find.
+   * @return The k-NN instances of the given target instance. 
+   * @throws Exception If there is some problem find the k-NNs.
+   */
+  public Instances kNearestNeighbours(Instance target, int k) throws Exception {
+    if(m_Stats!=null)
+      m_Stats.searchStart();
+    CoverTree querytree = new CoverTree();
+    Instances insts = new Instances(m_Instances, 0);
+    insts.add(target);
+    querytree.setInstances(insts);
+    Stack<NeighborList> result = new Stack<NeighborList>();
+    batch_nearest_neighbor(k, this.m_Root, querytree.m_Root, result);
+    if(m_Stats!=null)
+      m_Stats.searchFinish();
+
+    insts = new Instances(m_Instances, 0);
+    NeighborNode node = result.element(0).getFirst();
+    m_DistanceList = new double[result.element(0).currentLength()];
+    int i=0;
+    while(node != null) {
+      insts.add(node.m_Instance);
+      m_DistanceList[i] = node.m_Distance;
+      i++; node = node.m_Next;
+    }
+    return insts;
+  }
+  
+  /**
+   * Returns the NN instance of a given target instance, from among
+   * the previously supplied training instances.
+   * 
+   * @param target The instance for which NN is required.
+   * @throws Exception If there is some problem finding the nearest
+   * neighbour.
+   * @return The NN instance of the target instance.
+   */
+  public Instance nearestNeighbour(Instance target) throws Exception {
+    return kNearestNeighbours(target, 1).instance(0);
+  }
+
+  /**
+   * Returns the distances of the (k)-NN(s) found earlier
+   * by kNearestNeighbours()/nearestNeighbour().
+   * 
+   * @throws Exception If the tree hasn't been built (by calling 
+   * setInstances()), or none of kNearestNeighbours() or 
+   * nearestNeighbour() has been called before. 
+   * @return The distances (in the same order) of the k-NNs. 
+   */
+  public double[] getDistances() throws Exception {
+    if(m_Instances==null || m_DistanceList==null)
+      throw new Exception("The tree has not been supplied with a set of " +
+	  		  "instances or getDistances() has been called " +
+      			  "before calling kNearestNeighbours().");
+    return m_DistanceList;
+  }
+  
+  /**
+   * Checks if there is any instance with missing values. Throws an
+   * exception if there is, as KDTree does not handle missing values.
+   * 
+   * @param instances 	the instances to check
+   * @throws Exception 	if missing values are encountered
+   */
+  protected void checkMissing(Instances instances) throws Exception {
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Instance ins = instances.instance(i);
+      for (int j = 0; j < ins.numValues(); j++) {
+	if (ins.index(j) != ins.classIndex())
+	  if (ins.isMissingSparse(j)) {
+	    throw new Exception("ERROR: KDTree can not deal with missing "
+		+ "values. Please run ReplaceMissingValues filter "
+		+ "on the dataset before passing it on to the KDTree.");
+	  }
+      }
+    }
+  }
+
+  /**
+   * Builds the Cover Tree on the given set of instances.
+   * 
+   * @param instances The insts on which the Cover Tree is to be 
+   * built. 
+   * @throws Exception If some error occurs while 
+   * building the Cover Tree
+   */
+  public void setInstances(Instances instances) throws Exception {
+    super.setInstances(instances);
+    buildCoverTree(instances);
+  }
+
+  /** 
+   * Adds an instance to the cover tree. 
+   * P.S.: The current version doesn't allow
+   * addition of instances after batch construction.
+   * 
+   * @param ins The instance to add.
+   * @throws Exception Alway throws this, as current 
+   * implementation doesn't allow addition of instances 
+   * after building.
+   */
+  public void update(Instance ins) throws Exception {
+    throw new Exception("BottomUpConstruction method does not allow addition " +
+    "of new Instances.");
+  }
+
+  /** 
+   * Adds the given instance info. This implementation updates only the 
+   * range datastructures of the EuclideanDistance. Nothing is 
+   * required to be updated in the built Cover Tree.
+   * 
+   * @param ins 	The instance to add the information of. Usually this is
+   * 			the test instance supplied to update the range of 
+   * 			attributes in the distance function.
+   */
+  public void addInstanceInfo(Instance ins) {
+    if(m_Instances!=null) {
+      try {
+      m_DistanceFunction.update(ins);
+      } catch(Exception ex) { ex.printStackTrace(); }
+    }
+    else 
+      if(m_Instances==null)
+	      throw new IllegalStateException("No instances supplied yet. Cannot update without"+
+	                          "supplying a set of instances first.");
+  }
+  
+  /**
+   * Sets the distance function to use for nearest neighbour search.
+   * Currently only EuclideanDistance is supported.
+   * 
+   * @param df 		the distance function to use 
+   * @throws Exception 	if not EuclideanDistance
+   */
+  public void setDistanceFunction(DistanceFunction df) throws Exception {
+    if (!(df instanceof EuclideanDistance))
+      throw new Exception("CoverTree currently only works with "
+	  + "EuclideanDistanceFunction.");
+    m_DistanceFunction = m_EuclideanDistance = (EuclideanDistance) df;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String baseTipText() {
+    return "The base for the expansion constant.";
+  }
+
+  /**
+   * Returns the base in use for expansion constant.
+   * 
+   * @return base 	currently in use.
+   */
+  public double getBase() {
+    return m_Base;
+  }
+  
+  /**
+   * Sets the base to use for expansion constant.
+   * The 2 in 2^i in the paper.
+   * 
+   * @param b 		the new base;
+   */
+  public void setBase(double b) {
+    m_Base = b;
+  }
+  
+  /**
+   * Returns the size of the tree. 
+   * (number of internal nodes + number of leaves)
+   * 
+   * @return 		the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_NumNodes;
+  }
+  
+  /**
+   * Returns the number of leaves.
+   * 
+   * @return 		the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_NumLeaves;
+  }
+  
+  /**
+   * Returns the depth of the tree.
+   * 
+   * @return 		the number of rules
+   */
+  public double measureMaxDepth() {
+    return m_MaxDepth;
+  }
+    
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector = new Vector<String>();
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureMaxDepth");
+    if(m_Stats!=null) {
+      for(Enumeration e = m_Stats.enumerateMeasures(); e.hasMoreElements();) {
+        newVector.addElement((String)e.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName 	the name of the measure to query for 
+   * 					its value
+   * @return 				the value of the named measure
+   * @throws IllegalArgumentException 	if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureMaxDepth") == 0) {
+      return measureMaxDepth();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else if(m_Stats!=null) {
+      return m_Stats.getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported (KDTree)");
+    }
+  }
+  
+  /********Utility print functions.****** */
+  /**
+   * Prints a string to stdout. 
+   * 
+   * @param s The string to print.
+   */
+  protected static void print(String s) {
+    System.out.print(s);
+  }
+
+  /** 
+   * Prints a string to stdout followed by 
+   * newline.
+   * 
+   * @param s The string to print. 
+   */
+  protected static void println(String s) {
+    System.out.println(s);
+  }
+
+  /** 
+   * Prints an object to stdout.
+   * 
+   * @param o The object to print. 
+   */
+  protected static void print(Object o) {
+    System.out.print(o);
+  }
+
+  /** 
+   * Prints an object to stdout followed by 
+   * newline.
+   * 
+   * @param o The object to print.  
+   */
+  protected static void println(Object o) {
+    System.out.println(o);
+  }
+
+  /** 
+   * Prints the specified number of spaces.
+   * 
+   * @param s The number of space characters to print.  
+   */
+  protected static void print_space(int s) {
+    for (int i = 0; i < s; i++)
+      System.out.print(" ");
+  }
+
+  /**
+   * Prints a cover tree starting from the given node.
+   * 
+   * @param depth The depth of top_node.
+   * @param top_node The node to start printing from. 
+   */
+  protected static void print(int depth, CoverTreeNode top_node) {
+    print_space(depth);
+    println(top_node.p());
+    if (top_node.num_children > 0) {
+      print_space(depth);
+      print("scale = " + top_node.scale + "\n");
+      print_space(depth);
+      print("num children = " + top_node.num_children + "\n");
+      System.out.flush();
+      for (int i = 0; i < top_node.num_children; i++)
+        print(depth + 1, top_node.children.element(i)); // top_node.children[i]);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /** 
+   * Method for testing the class from command line. 
+   * 
+   * @param args The supplied command line arguments.
+   */
+  public static void main(String[] args) {
+    if (args.length != 1) {
+      System.err.println("Usage: CoverTree <ARFF file>");
+      System.exit(-1);
+    }
+    try {
+      Instances insts = null;
+      if (args[0].endsWith(".csv")) {
+        CSVLoader csv = new CSVLoader();
+        csv.setFile(new File(args[0]));
+        insts = csv.getDataSet();
+      } else {
+        insts = new Instances(new BufferedReader(new FileReader(args[0])));
+      }
+
+      CoverTree tree = new CoverTree();
+      tree.setInstances(insts);
+      print("Created data tree:\n");
+      print(0, tree.m_Root);
+      println("");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/KDTree.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/KDTree.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/KDTree.java	(revision 29)
@@ -0,0 +1,1334 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KDTree.java
+ *    Copyright (C) 2000-2007 University of Waikato
+ *    
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.neighboursearch.kdtrees.KDTreeNode;
+import weka.core.neighboursearch.kdtrees.KDTreeNodeSplitter;
+import weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the KDTree search algorithm for nearest neighbour search.<br/>
+ * The connection to dataset is only a reference. For the tree structure the indexes are stored in an array. <br/>
+ * Building the tree:<br/>
+ * If a node has &lt;maximal-inst-number&gt; (option -L) instances no further splitting is done. Also if the split would leave one side empty, the branch is not split any further even if the instances in the resulting node are more than &lt;maximal-inst-number&gt; instances.<br/>
+ * **PLEASE NOTE:** The algorithm can not handle missing values, so it is advisable to run ReplaceMissingValues filter if there are any missing values in the dataset.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Jerome H. Friedman, Jon Luis Bentley, Raphael Ari Finkel (1977). An Algorithm for Finding Best Matches in Logarithmic Expected Time. ACM Transactions on Mathematics Software. 3(3):209-226.<br/>
+ * <br/>
+ * Andrew Moore (1991). A tutorial on kd-trees.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Friedman1977,
+ *    author = {Jerome H. Friedman and Jon Luis Bentley and Raphael Ari Finkel},
+ *    journal = {ACM Transactions on Mathematics Software},
+ *    month = {September},
+ *    number = {3},
+ *    pages = {209-226},
+ *    title = {An Algorithm for Finding Best Matches in Logarithmic Expected Time},
+ *    volume = {3},
+ *    year = {1977}
+ * }
+ * 
+ * &#64;techreport{Moore1991,
+ *    author = {Andrew Moore},
+ *    booktitle = {University of Cambridge Computer Laboratory Technical Report No. 209},
+ *    howpublished = {Extract from PhD Thesis},
+ *    title = {A tutorial on kd-trees},
+ *    year = {1991},
+ *    HTTP = {Available from http://www.autonlab.org/autonweb/14665.html}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;classname and options&gt;
+ *  Node splitting method to use.
+ *  (default: weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide)</pre>
+ * 
+ * <pre> -W &lt;value&gt;
+ *  Set minimal width of a box
+ *  (default: 1.0E-2).</pre>
+ * 
+ * <pre> -L
+ *  Maximal number of instances in a leaf
+ *  (default: 40).</pre>
+ * 
+ * <pre> -N
+ *  Normalizing will be done
+ *  (Select dimension for split, with normalising to universe).</pre>
+ * 
+ <!-- options-end --> 
+ * 
+ * @author Gabi Schmidberger (gabi[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @author Malcolm Ware (mfw4[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5987 $
+ */
+public class KDTree
+  extends NearestNeighbourSearch
+  implements TechnicalInformationHandler {
+
+  /** For serialization. */
+  private static final long serialVersionUID = 1505717283763272533L;
+
+  /**
+   * Array holding the distances of the nearest neighbours. It is filled up both
+   * by nearestNeighbour() and kNearestNeighbours().
+   */
+  protected double[] m_DistanceList;
+
+  /**
+   * Indexlist of the instances of this kdtree. Instances get sorted according
+   * to the splits. the nodes of the KDTree just hold their start and end
+   * indices
+   */
+  protected int[] m_InstList;
+
+  /** The root node of the tree. */
+  protected KDTreeNode m_Root;
+
+  /** The node splitter. */
+  protected KDTreeNodeSplitter m_Splitter = new SlidingMidPointOfWidestSide();
+
+  /** Tree stats. */
+  protected int m_NumNodes, m_NumLeaves, m_MaxDepth;
+
+  /** Tree Stats variables. */
+  protected TreePerformanceStats m_TreeStats = null;
+
+  // Constants
+  /** The index of MIN value in attributes' range array. */
+  public static final int MIN = EuclideanDistance.R_MIN;
+  
+  /** The index of MAX value in attributes' range array. */
+  public static final int MAX = EuclideanDistance.R_MAX; 
+  
+  /** The index of WIDTH (MAX-MIN) value in attributes' range array. */
+  public static final int WIDTH = EuclideanDistance.R_WIDTH;
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Jerome H. Friedman and Jon Luis Bentley and Raphael Ari Finkel");
+    result.setValue(Field.YEAR, "1977");
+    result.setValue(Field.TITLE, "An Algorithm for Finding Best Matches in Logarithmic Expected Time");
+    result.setValue(Field.JOURNAL, "ACM Transactions on Mathematics Software");
+    result.setValue(Field.PAGES, "209-226");
+    result.setValue(Field.MONTH, "September");
+    result.setValue(Field.VOLUME, "3");
+    result.setValue(Field.NUMBER, "3");
+
+    additional = result.add(Type.TECHREPORT);
+    additional.setValue(Field.AUTHOR, "Andrew Moore");
+    additional.setValue(Field.YEAR, "1991");
+    additional.setValue(Field.TITLE, "A tutorial on kd-trees");
+    additional.setValue(Field.HOWPUBLISHED, "Extract from PhD Thesis");
+    additional.setValue(Field.BOOKTITLE, "University of Cambridge Computer Laboratory Technical Report No. 209");
+    additional.setValue(Field.HTTP, "Available from http://www.autonlab.org/autonweb/14665.html");
+
+    return result;
+  }
+
+  /**
+   * Creates a new instance of KDTree.
+   */
+  public KDTree() {
+    super();
+    if (getMeasurePerformance())
+      m_Stats = m_TreeStats = new TreePerformanceStats();
+  }
+
+  /**
+   * Creates a new instance of KDTree. 
+   * It also builds the tree on supplied set of Instances.
+   * @param insts The instances/points on which the BallTree 
+   * should be built on.
+   */
+  public KDTree(Instances insts) {
+    super(insts);
+    if (getMeasurePerformance())
+      m_Stats = m_TreeStats = new TreePerformanceStats();
+  }
+
+  /**
+   * Builds the KDTree on the supplied set of instances/points. It 
+   * is adviseable to run the replace missing attributes filter 
+   * on the passed instances first.
+   * NOTE: This method should not be called from outside this 
+   * class. Outside classes should call setInstances(Instances)
+   * instead.
+   * 
+   * @param instances	The instances to build the tree on
+   * @throws Exception	if something goes wrong
+   */
+  protected void buildKDTree(Instances instances) throws Exception {
+
+    checkMissing(instances);
+    if (m_EuclideanDistance == null)
+      m_DistanceFunction = m_EuclideanDistance = new EuclideanDistance(
+          instances);
+    else
+      m_EuclideanDistance.setInstances(instances);
+
+    m_Instances = instances;
+    int numInst = m_Instances.numInstances();
+
+    // Make the global index list
+    m_InstList = new int[numInst];
+
+    for (int i = 0; i < numInst; i++) {
+      m_InstList[i] = i;
+    }
+
+    double[][] universe = m_EuclideanDistance.getRanges();
+
+    // initializing internal fields of KDTreeSplitter
+    m_Splitter.setInstances(m_Instances);
+    m_Splitter.setInstanceList(m_InstList);
+    m_Splitter.setEuclideanDistanceFunction(m_EuclideanDistance);
+    m_Splitter.setNodeWidthNormalization(m_NormalizeNodeWidth);
+
+    // building tree
+    m_NumNodes = m_NumLeaves = 1;
+    m_MaxDepth = 0;
+    m_Root = new KDTreeNode(m_NumNodes, 0, m_Instances.numInstances() - 1,
+        universe);
+
+    splitNodes(m_Root, universe, m_MaxDepth + 1);
+  }
+
+  /** 
+   * Recursively splits nodes of a tree starting from the supplied node.
+   * The splitting stops for any node for which the number of instances/points
+   * falls below a given threshold (given by m_MaxInstInLeaf), or if the 
+   * maximum relative width/range of the instances/points 
+   * (i.e. max_i(max(att_i) - min(att_i)) ) falls below a given threshold 
+   * (given by m_MinBoxRelWidth). 
+   * 
+   * @param node The node to start splitting from.
+   * @param universe The attribute ranges of the whole dataset.
+   * @param depth The depth of the supplied node.  
+   * @throws Exception If there is some problem 
+   * splitting.
+   */
+  protected void splitNodes(KDTreeNode node, double[][] universe,
+      int depth) throws Exception {
+    double[][] nodeRanges = m_EuclideanDistance.initializeRanges(m_InstList,
+                                                 node.m_Start, node.m_End);
+    if (node.numInstances() <= m_MaxInstInLeaf
+        || getMaxRelativeNodeWidth(nodeRanges, universe) <= m_MinBoxRelWidth)
+      return;
+
+    // splitting a node so it is no longer a leaf
+    m_NumLeaves--;
+
+    if (depth > m_MaxDepth)
+      m_MaxDepth = depth;
+
+    m_Splitter.splitNode(node, m_NumNodes, nodeRanges, universe);
+    m_NumNodes += 2;
+    m_NumLeaves += 2;
+
+    splitNodes(node.m_Left, universe, depth + 1);
+    splitNodes(node.m_Right, universe, depth + 1);
+  }
+
+  /**
+   * Returns (in the supplied heap object) the k nearest 
+   * neighbours of the given instance starting from the give 
+   * tree node. &gt;k neighbours are returned if there are more than 
+   * one neighbours at the kth boundary. NOTE: This method should 
+   * not be used from outside this class. Outside classes should 
+   * call kNearestNeighbours(Instance, int).
+   * 
+   * @param target  The instance to find the nearest neighbours for.
+   * @param node The KDTreeNode to start the search from.
+   * @param k    The number of neighbours to find.
+   * @param heap The MyHeap object to store/update the kNNs found
+   * during the search.
+   * @param distanceToParents The distance of the supplied target 
+   * to the parents of the supplied tree node. 
+   * @throws Exception  if the nearest neighbour could not be found.
+   */
+  protected void findNearestNeighbours(Instance target, KDTreeNode node, int k,
+      MyHeap heap, double distanceToParents) throws Exception {
+    if (node.isALeaf()) {
+      if (m_TreeStats != null) {
+        m_TreeStats.updatePointCount(node.numInstances());
+        m_TreeStats.incrLeafCount();
+      }
+      double distance;
+      // look at all the instances in this leaf
+      for (int idx = node.m_Start; idx <= node.m_End; idx++) {
+        if (target == m_Instances.instance(m_InstList[idx])) // for
+                                                              // hold-one-out
+                                                              // cross-validation
+          continue;
+        if (heap.size() < k) {
+          distance = m_EuclideanDistance.distance(target, m_Instances
+              .instance(m_InstList[idx]), Double.POSITIVE_INFINITY, m_Stats);
+          heap.put(m_InstList[idx], distance);
+        } else {
+          MyHeapElement temp = heap.peek();
+          distance = m_EuclideanDistance.distance(target, m_Instances
+              .instance(m_InstList[idx]), temp.distance, m_Stats);
+          if (distance < temp.distance) {
+            heap.putBySubstitute(m_InstList[idx], distance);
+          } else if (distance == temp.distance) {
+            heap.putKthNearest(m_InstList[idx], distance);
+          }
+        }// end else heap.size==k
+      }// end for
+
+    } else {
+      if (m_TreeStats != null) {
+        m_TreeStats.incrIntNodeCount();
+      }
+      KDTreeNode nearer, further;
+      boolean targetInLeft = m_EuclideanDistance.valueIsSmallerEqual(target,
+          node.m_SplitDim, node.m_SplitValue);
+
+      if (targetInLeft) {
+        nearer = node.m_Left;
+        further = node.m_Right;
+      } else {
+        nearer = node.m_Right;
+        further = node.m_Left;
+      }
+      findNearestNeighbours(target, nearer, k, heap, distanceToParents);
+
+      // ... now look in further half if maxDist reaches into it
+      if (heap.size() < k) { // if haven't found the first k
+        double distanceToSplitPlane = distanceToParents
+            + m_EuclideanDistance.sqDifference(node.m_SplitDim, target
+                .value(node.m_SplitDim), node.m_SplitValue);
+        findNearestNeighbours(target, further, k, heap, distanceToSplitPlane);
+        return;
+      } else { // else see if ball centered at query intersects with the other
+                // side.
+        double distanceToSplitPlane = distanceToParents
+            + m_EuclideanDistance.sqDifference(node.m_SplitDim, target
+                .value(node.m_SplitDim), node.m_SplitValue);
+        if (heap.peek().distance >= distanceToSplitPlane) {
+          findNearestNeighbours(target, further, k, heap, distanceToSplitPlane);
+        }
+      }// end else
+    }// end else_if an internal node
+  }
+
+  /**
+   * Returns the k nearest neighbours of the supplied instance.
+   * &gt;k neighbours are returned if there are more than one 
+   * neighbours at the kth boundary. 
+   * 
+   * @param target	The instance to find the nearest neighbours for.
+   * @param k 		The number of neighbours to find.
+   * @return The k nearest neighbours (or &gt;k if more there are than
+   * one neighbours at the kth boundary). 
+   * @throws Exception 	if the nearest neighbour could not be found.
+   */
+  public Instances kNearestNeighbours(Instance target, int k) throws Exception {
+    checkMissing(target);
+
+    if (m_Stats != null)
+      m_Stats.searchStart();
+
+    MyHeap heap = new MyHeap(k);
+    findNearestNeighbours(target, m_Root, k, heap, 0.0);
+
+    if (m_Stats != null)
+      m_Stats.searchFinish();
+
+    Instances neighbours = new Instances(m_Instances, (heap.size() + heap
+        .noOfKthNearest()));
+    m_DistanceList = new double[heap.size() + heap.noOfKthNearest()];
+    int[] indices = new int[heap.size() + heap.noOfKthNearest()];
+    int i = indices.length - 1;
+    MyHeapElement h;
+    while (heap.noOfKthNearest() > 0) {
+      h = heap.getKthNearest();
+      indices[i] = h.index;
+      m_DistanceList[i] = h.distance;
+      i--;
+    }
+    while (heap.size() > 0) {
+      h = heap.get();
+      indices[i] = h.index;
+      m_DistanceList[i] = h.distance;
+      i--;
+    }
+    m_DistanceFunction.postProcessDistances(m_DistanceList);
+
+    for (int idx = 0; idx < indices.length; idx++) {
+      neighbours.add(m_Instances.instance(indices[idx]));
+    }
+
+    return neighbours;
+  }
+  
+
+  /**
+   * Returns the nearest neighbour of the supplied target 
+   * instance. 
+   *  
+   * @param target	The instance to find the nearest neighbour for.
+   * @return The nearest neighbour from among the previously 
+   * supplied training instances.
+   * @throws Exception 	if the neighbours could not be found.
+   */
+  public Instance nearestNeighbour(Instance target) throws Exception {
+    return (kNearestNeighbours(target, 1)).instance(0);
+  }
+  
+  /**
+   * Returns the distances to the kNearest or 1 nearest neighbour currently
+   * found with either the kNearestNeighbours or the nearestNeighbour method.
+   * 
+   * @return array containing the distances of the
+   *         nearestNeighbours. The length and ordering of the array 
+   *         is the same as that of the instances returned by 
+   *         nearestNeighbour functions.
+   * @throws Exception 	if called before calling kNearestNeighbours or
+   * 			nearestNeighbours.
+   */
+  public double[] getDistances() throws Exception {
+    if (m_Instances == null || m_DistanceList == null)
+      throw new Exception("The tree has not been supplied with a set of "
+          + "instances or getDistances() has been called "
+          + "before calling kNearestNeighbours().");
+    return m_DistanceList;
+  }
+  
+
+  /**
+   * Builds the KDTree on the given set of instances.
+   * @param instances The insts on which the KDTree is to be 
+   * built. 
+   * @throws Exception If some error occurs while 
+   * building the KDTree
+   */
+  public void setInstances(Instances instances) throws Exception {
+    super.setInstances(instances);
+    buildKDTree(instances);
+  }
+  
+
+  /**
+   * Adds one instance to the KDTree. This updates the KDTree structure to take
+   * into account the newly added training instance.
+   * 
+   * @param instance 	the instance to be added. Usually the newly added instance in the
+   *          		training set.
+   * @throws Exception If the instance cannot be added.
+   */
+  public void update(Instance instance) throws Exception { // better to change
+                                                            // to addInstance
+    if (m_Instances == null)
+      throw new Exception("No instances supplied yet. Have to call "
+          + "setInstances(instances) with a set of Instances " + "first.");
+
+    addInstanceInfo(instance);
+    addInstanceToTree(instance, m_Root);
+  }
+
+  /**
+   * Recursively adds an instance to the tree starting from
+   * the supplied KDTreeNode.
+   * NOTE: This should not be called by outside classes,
+   * outside classes should instead call update(Instance)
+   * method. 
+   *  
+   * @param inst The instance to add to the tree
+   * @param node The node to start the recursive search 
+   * from, for the leaf node where the supplied instance 
+   * would go.
+   * @throws Exception If some error occurs while adding
+   * the instance.
+   */
+  protected void addInstanceToTree(Instance inst, KDTreeNode node)
+      throws Exception {
+    if (node.isALeaf()) {
+      int instList[] = new int[m_Instances.numInstances()];
+      try {
+        System.arraycopy(m_InstList, 0, instList, 0, node.m_End + 1); // m_InstList.squeezeIn(m_End,
+                                                                      // index);
+        if (node.m_End < m_InstList.length - 1)
+          System.arraycopy(m_InstList, node.m_End + 1, instList,
+              node.m_End + 2, m_InstList.length - node.m_End - 1);
+        instList[node.m_End + 1] = m_Instances.numInstances() - 1;
+      } catch (ArrayIndexOutOfBoundsException ex) {
+        System.err.println("m_InstList.length: " + m_InstList.length
+            + " instList.length: " + instList.length + "node.m_End+1: "
+            + (node.m_End + 1) + "m_InstList.length-node.m_End+1: "
+            + (m_InstList.length - node.m_End - 1));
+        throw ex;
+      }
+      m_InstList = instList;
+
+      node.m_End++;
+      node.m_NodeRanges = m_EuclideanDistance.updateRanges(inst,
+          node.m_NodeRanges);
+
+      m_Splitter.setInstanceList(m_InstList);
+
+      // split this leaf node if necessary
+      double[][] universe = m_EuclideanDistance.getRanges();
+      if (node.numInstances() > m_MaxInstInLeaf
+          && getMaxRelativeNodeWidth(node.m_NodeRanges, universe) > m_MinBoxRelWidth) {
+        m_Splitter.splitNode(node, m_NumNodes, node.m_NodeRanges, universe);
+        m_NumNodes += 2;
+      }
+    }// end if node is a leaf
+    else {
+      if (m_EuclideanDistance.valueIsSmallerEqual(inst, node.m_SplitDim,
+          node.m_SplitValue)) {
+        addInstanceToTree(inst, node.m_Left);
+        afterAddInstance(node.m_Right);
+      } else
+        addInstanceToTree(inst, node.m_Right);
+
+      node.m_End++;
+      node.m_NodeRanges = m_EuclideanDistance.updateRanges(inst,
+          node.m_NodeRanges);
+    }
+  }
+
+  /**
+   * Corrects the start and end indices of a 
+   * KDTreeNode after an instance is added to
+   * the tree. The start and end indices for
+   * the master index array (m_InstList) 
+   * stored in the nodes need to be updated
+   * for all nodes in the subtree on the 
+   * right of a node where the instance 
+   * was added. 
+   * NOTE: No outside class should call this
+   * method.
+   * 
+   * @param node KDTreeNode whose start and end indices 
+   * need to be updated.
+   */
+  protected void afterAddInstance(KDTreeNode node) {
+    node.m_Start++;
+    node.m_End++;
+    if (!node.isALeaf()) {
+      afterAddInstance(node.m_Left);
+      afterAddInstance(node.m_Right);
+    }
+  }
+
+  /**
+   * Adds one instance to KDTree loosly. It only changes the ranges in
+   * EuclideanDistance, and does not affect the structure of the KDTree.
+   * 
+   * @param instance	the new instance. Usually this is the test instance 
+   * 			supplied to update the range of attributes in the distance function.
+   */
+  public void addInstanceInfo(Instance instance) {
+    m_EuclideanDistance.updateRanges(instance);
+  }
+
+  /**
+   * Checks if there is any instance with missing values. Throws an exception if
+   * there is, as KDTree does not handle missing values.
+   * 
+   * @param instances	the instances to check
+   * @throws Exception	if missing values are encountered
+   */
+  protected void checkMissing(Instances instances) throws Exception {
+    for (int i = 0; i < instances.numInstances(); i++) {
+      Instance ins = instances.instance(i);
+      for (int j = 0; j < ins.numValues(); j++) {
+        if (ins.index(j) != ins.classIndex())
+          if (ins.isMissingSparse(j)) {
+            throw new Exception("ERROR: KDTree can not deal with missing "
+                + "values. Please run ReplaceMissingValues filter "
+                + "on the dataset before passing it on to the KDTree.");
+          }
+      }
+    }
+  }
+
+  /**
+   * Checks if there is any missing value in the given 
+   * instance.
+   * @param ins The instance to check missing values in.
+   * @throws Exception If there is a missing value in the 
+   * instance.
+   */
+  protected void checkMissing(Instance ins) throws Exception {
+    for (int j = 0; j < ins.numValues(); j++) {
+      if (ins.index(j) != ins.classIndex())
+        if (ins.isMissingSparse(j)) {
+          throw new Exception("ERROR: KDTree can not deal with missing "
+              + "values. Please run ReplaceMissingValues filter "
+              + "on the dataset before passing it on to the KDTree.");
+        }
+    }
+  }
+  
+  /** 
+   * Returns the maximum attribute width of instances/points 
+   * in a KDTreeNode relative to the whole dataset. 
+   * 
+   * @param nodeRanges The attribute ranges of the 
+   * KDTreeNode whose maximum relative width is to be 
+   * determined.
+   * @param universe The attribute ranges of the whole
+   * dataset (training instances + test instances so 
+   * far encountered).
+   * @return The maximum relative width
+   */
+  protected double getMaxRelativeNodeWidth(double[][] nodeRanges,
+      double[][] universe) {
+    int widest = widestDim(nodeRanges, universe);
+    if(widest < 0)
+    	return 0.0;
+    else
+    	return nodeRanges[widest][WIDTH] / universe[widest][WIDTH];
+  }
+
+  /**
+   * Returns the widest dimension/attribute in a 
+   * KDTreeNode (widest after normalizing).
+   * @param nodeRanges The attribute ranges of 
+   * the KDTreeNode.
+   * @param universe The attribute ranges of the 
+   * whole dataset (training instances + test 
+   * instances so far encountered).
+   * @return The index of the widest 
+   * dimension/attribute.
+   */
+  protected int widestDim(double[][] nodeRanges, double[][] universe) {
+    final int classIdx = m_Instances.classIndex();
+    double widest = 0.0;
+    int w = -1;
+    if (m_NormalizeNodeWidth) {
+      for (int i = 0; i < nodeRanges.length; i++) {
+        double newWidest = nodeRanges[i][WIDTH] / universe[i][WIDTH];
+        if (newWidest > widest) {
+          if (i == classIdx)
+            continue;
+          widest = newWidest;
+          w = i;
+        }
+      }
+    } else {
+      for (int i = 0; i < nodeRanges.length; i++) {
+        if (nodeRanges[i][WIDTH] > widest) {
+          if (i == classIdx)
+            continue;
+          widest = nodeRanges[i][WIDTH];
+          w = i;
+        }
+      }
+    }
+    return w;
+  }
+
+  /**
+   * Returns the size of the tree.
+   * 
+   * @return 		the size of the tree
+   */
+  public double measureTreeSize() {
+    return m_NumNodes;
+  }
+
+  /**
+   * Returns the number of leaves.
+   * 
+   * @return 		the number of leaves
+   */
+  public double measureNumLeaves() {
+    return m_NumLeaves;
+  }
+
+  /**
+   * Returns the depth of the tree.
+   * 
+   * @return The depth of the tree
+   */
+  public double measureMaxDepth() {
+    return m_MaxDepth;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector = new Vector<String>();
+    newVector.addElement("measureTreeSize");
+    newVector.addElement("measureNumLeaves");
+    newVector.addElement("measureMaxDepth");
+    if (m_Stats != null) {
+      for (Enumeration e = m_Stats.enumerateMeasures(); e.hasMoreElements();) {
+        newVector.addElement((String)e.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName	the name of 
+   * the measure to query for its value.
+   * @return The value of the named measure
+   * @throws IllegalArgumentException	If the named measure 
+   * is not supported.
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (additionalMeasureName.compareToIgnoreCase("measureMaxDepth") == 0) {
+      return measureMaxDepth();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureTreeSize") == 0) {
+      return measureTreeSize();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureNumLeaves") == 0) {
+      return measureNumLeaves();
+    } else if (m_Stats != null) {
+      return m_Stats.getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName
+          + " not supported (KDTree)");
+    }
+  }
+
+  /**
+   * Sets whether to calculate the performance statistics or not.
+   * @param measurePerformance Should be true if performance 
+   * statistics are to be measured.
+   */
+  public void setMeasurePerformance(boolean measurePerformance) {
+    m_MeasurePerformance = measurePerformance;
+    if (m_MeasurePerformance) {
+      if (m_Stats == null)
+        m_Stats = m_TreeStats = new TreePerformanceStats();
+    } else
+      m_Stats = m_TreeStats = null;
+  }
+
+  /**
+   * Assigns instances to centers using KDTree.
+   * 
+   * @param centers	the current centers
+   * @param assignments	the centerindex for each instance
+   * @param pc		the threshold value for pruning.
+   * @throws Exception If there is some problem 
+   * assigning instances to centers.
+   */
+  public void centerInstances(Instances centers, int[] assignments, double pc)
+      throws Exception {
+
+    int[] centList = new int[centers.numInstances()];
+    for (int i = 0; i < centers.numInstances(); i++)
+      centList[i] = i;
+
+    determineAssignments(m_Root, centers, centList, assignments, pc);
+  }
+
+  /**
+   * Assigns instances to the current centers called candidates.
+   * 
+   * @param node The node to start assigning the instances from.
+   * @param centers	all the current centers.
+   * @param candidates	the current centers the method works on.
+   * @param assignments	the center index for each instance.
+   * @param pc the threshold value for pruning.
+   * @throws Exception If there is some problem assigning 
+   * instances to centers.
+   */
+  protected void determineAssignments(KDTreeNode node, Instances centers,
+      int[] candidates, int[] assignments, double pc) throws Exception {
+
+    // reduce number of owners for current hyper rectangle
+    int[] owners = refineOwners(node, centers, candidates);
+
+    // only one owner
+    if (owners.length == 1) {
+      // all instances of this node are owned by one center
+      for (int i = node.m_Start; i <= node.m_End; i++) {
+        assignments[m_InstList[i]] // the assignment of this instance
+        = owners[0]; // is the current owner
+      }
+    } else if (!node.isALeaf()) {
+      // more than one owner and it is not a leaf
+      determineAssignments(node.m_Left, centers, owners, assignments, pc);
+      determineAssignments(node.m_Right, centers, owners, assignments, pc);
+    } else {
+      // this is a leaf and there are more than 1 owner
+      // XMeans.
+      assignSubToCenters(node, centers, owners, assignments);
+    }
+  }
+
+  /**
+   * Refines the ownerlist.
+   * 
+   * @param node The current tree node.
+   * @param centers	all centers
+   * @param candidates	the indexes of those centers that are candidates.
+   * @return list of owners
+   * @throws Exception If some problem occurs in refining.
+   */
+  protected int[] refineOwners(KDTreeNode node, Instances centers,
+      int[] candidates) throws Exception {
+
+    int[] owners = new int[candidates.length];
+    double minDistance = Double.POSITIVE_INFINITY;
+    int ownerIndex = -1;
+    Instance owner;
+    int numCand = candidates.length;
+    double[] distance = new double[numCand];
+    boolean[] inside = new boolean[numCand];
+    for (int i = 0; i < numCand; i++) {
+      distance[i] = distanceToHrect(node, centers.instance(candidates[i]));
+      inside[i] = (distance[i] == 0.0);
+      if (distance[i] < minDistance) {
+        minDistance = distance[i];
+        ownerIndex = i;
+      }
+    }
+    owner = (Instance)centers.instance(candidates[ownerIndex]).copy();
+
+    // are there other owners
+    // loop also goes over already found owner, keeps order
+    // in owner list
+    int index = 0;
+    for (int i = 0; i < numCand; i++) {
+      // 1. all centers that are points within rectangle are owners
+      if ((inside[i])
+
+      // 2. take all points with same distance to the rect. as the owner
+          || (distance[i] == distance[ownerIndex])) {
+
+        // add competitor to owners list
+        owners[index++] = candidates[i];
+      } else {
+
+        Instance competitor = (Instance)centers.instance(candidates[i]).copy();
+        if
+
+        // 3. point has larger distance to rectangle but still can compete
+        // with owner for some points in the rectangle
+        (!candidateIsFullOwner(node, owner, competitor))
+
+        {
+          // also add competitor to owners list
+          owners[index++] = candidates[i];
+        }
+      }
+    }
+    int[] result = new int[index];
+    for (int i = 0; i < index; i++)
+      result[i] = owners[i];
+    return result;
+  }
+
+  /**
+   * Returns the distance between a point and an hyperrectangle.
+   * 
+   * @param node The current node from whose hyperrectangle 
+   * the distance is to be measured.
+   * @param x		the point
+   * @return 		the distance
+   * @throws Exception If some problem occurs in determining 
+   * the distance to the hyperrectangle.
+   */
+  protected double distanceToHrect(KDTreeNode node, Instance x) throws Exception {
+    double distance = 0.0;
+
+    Instance closestPoint = (Instance)x.copy();
+    boolean inside;
+    inside = clipToInsideHrect(node, closestPoint);
+    if (!inside)
+      distance = m_EuclideanDistance.distance(closestPoint, x);
+    return distance;
+  }
+
+  /**
+   * Finds the closest point in the hyper rectangle to a given point. Change the
+   * given point to this closest point by clipping of at all the dimensions to
+   * be clipped of. If the point is inside the rectangle it stays unchanged. The
+   * return value is true if the point was not changed, so the the return value
+   * is true if the point was inside the rectangle.
+   * 
+   * @param node The current KDTreeNode in whose hyperrectangle the closest 
+   * point is to be found.
+   * @param x		a point
+   * @return 		true if the input point stayed unchanged.
+   */
+  protected boolean clipToInsideHrect(KDTreeNode node, Instance x) {
+    boolean inside = true;
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      // TODO treat nominals differently!??
+
+      if (x.value(i) < node.m_NodeRanges[i][MIN]) {
+        x.setValue(i, node.m_NodeRanges[i][MIN]);
+        inside = false;
+      } else if (x.value(i) > node.m_NodeRanges[i][MAX]) {
+        x.setValue(i, node.m_NodeRanges[i][MAX]);
+        inside = false;
+      }
+    }
+    return inside;
+  }
+
+  /**
+   * Returns true if candidate is a full owner in respect to a competitor.
+   * <p>
+   * 
+   * The candidate has been the closer point to the current rectangle or even
+   * has been a point within the rectangle. The competitor is competing with the
+   * candidate for a few points out of the rectangle although it is a point
+   * further away from the rectangle then the candidate. The extrem point is the
+   * corner of the rectangle that is furthest away from the candidate towards
+   * the direction of the competitor.
+   * 
+   * If the distance candidate to this extreme point is smaller then the
+   * distance competitor to this extreme point, then it is proven that none of
+   * the points in the rectangle can be owned be the competitor and the
+   * candidate is full owner of the rectangle in respect to this competitor. See
+   * also D. Pelleg and A. Moore's paper 'Accelerating exact k-means Algorithms
+   * with Geometric Reasoning'.
+   * <p>
+   * 
+   * @param node The current KDTreeNode / hyperrectangle.
+   * @param candidate	instance that is candidate to be owner
+   * @param competitor	instance that competes against the candidate
+   * @return 		true if candidate is full owner
+   * @throws Exception If some problem occurs.
+   */
+  protected boolean candidateIsFullOwner(KDTreeNode node, Instance candidate,
+      Instance competitor) throws Exception {
+    // get extreme point
+    Instance extreme = (Instance)candidate.copy();
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      if ((competitor.value(i) - candidate.value(i)) > 0) {
+        extreme.setValue(i, node.m_NodeRanges[i][MAX]);
+      } else {
+        extreme.setValue(i, node.m_NodeRanges[i][MIN]);
+      }
+    }
+    boolean isFullOwner = m_EuclideanDistance.distance(extreme, candidate) < m_EuclideanDistance
+        .distance(extreme, competitor);
+
+    return isFullOwner;
+  }
+
+  /**
+   * Assigns instances of this node to center. Center to be assign to is decided
+   * by the distance function.
+   * 
+   * @param node The KDTreeNode whose instances are to be assigned.
+   * @param centers	all the input centers
+   * @param centList	the list of centers to work with
+   * @param assignments	index list of last assignments
+   * @throws Exception If there is error assigning the instances.
+   */
+  public void assignSubToCenters(KDTreeNode node, Instances centers,
+      int[] centList, int[] assignments) throws Exception {
+    // todo: undecided situations
+    int numCent = centList.length;
+
+    // WARNING: assignments is "input/output-parameter"
+    // should not be null and the following should not happen
+    if (assignments == null) {
+      assignments = new int[m_Instances.numInstances()];
+      for (int i = 0; i < assignments.length; i++) {
+        assignments[i] = -1;
+      }
+    }
+
+    // set assignments for all instances of this node
+    for (int i = node.m_Start; i <= node.m_End; i++) {
+      int instIndex = m_InstList[i];
+      Instance inst = m_Instances.instance(instIndex);
+      // if (instList[i] == 664) System.out.println("664***");
+      int newC = m_EuclideanDistance.closestPoint(inst, centers, centList);
+      // int newC = clusterProcessedInstance(inst, centers);
+      assignments[instIndex] = newC;
+    }
+  }
+
+  /**
+   * Properties' variables =====================================================
+   */
+
+  /** flag for normalizing. */
+  boolean m_NormalizeNodeWidth = true;
+
+  /** The euclidean distance function to use. */
+  protected EuclideanDistance m_EuclideanDistance;
+  { // to make sure we have only one object of EuclideanDistance
+    if (m_DistanceFunction instanceof EuclideanDistance)
+      m_EuclideanDistance = (EuclideanDistance) m_DistanceFunction;
+    else
+      m_DistanceFunction = m_EuclideanDistance = new EuclideanDistance();
+  }
+
+  /** minimal relative width of a KDTree rectangle. */
+  protected double m_MinBoxRelWidth = 1.0E-2;
+
+  /** maximal number of instances in a leaf. */
+  protected int m_MaxInstInLeaf = 40;
+
+  /**
+   * the GET and SET - functions ===============================================
+   */
+
+  /**
+   * Tip text for this property.
+   * 
+   * @return 		the tip text for this property
+   */
+  public String minBoxRelWidthTipText() {
+    return "The minimum relative width of the box. A node is only made a leaf "
+        + "if the width of the split dimension of the instances in a node "
+        + "normalized over the width of the split dimension of all the "
+        + "instances is less than or equal to this minimum relative width.";
+  }
+
+  /**
+   * Sets the minimum relative box width.
+   * 
+   * @param i		the minimum relative box width
+   */
+  public void setMinBoxRelWidth(double i) {
+    m_MinBoxRelWidth = i;
+  }
+
+  /**
+   * Gets the minimum relative box width.
+   * 
+   * @return 		the minimum relative box width
+   */
+  public double getMinBoxRelWidth() {
+    return m_MinBoxRelWidth;
+  }
+
+  /**
+   * Tip text for this property.
+   * 
+   * @return 		the tip text for this property
+   */
+  public String maxInstInLeafTipText() {
+    return "The max number of instances in a leaf.";
+  }
+
+  /**
+   * Sets the maximum number of instances in a leaf.
+   * 
+   * @param i		the maximum number of instances in a leaf
+   */
+  public void setMaxInstInLeaf(int i) {
+    m_MaxInstInLeaf = i;
+  }
+
+  /**
+   * Get the maximum number of instances in a leaf.
+   * 
+   * @return 		the maximum number of instances in a leaf
+   */
+  public int getMaxInstInLeaf() {
+    return m_MaxInstInLeaf;
+  }
+
+  /**
+   * Tip text for this property.
+   * 
+   * @return 		the tip text for this property
+   */
+  public String normalizeNodeWidthTipText() {
+    return "Whether if the widths of the KDTree node should be normalized "
+        + "by the width of the universe or not. "
+        + "Where, width of the node is the range of the split attribute "
+        + "based on the instances in that node, and width of the "
+        + "universe is the range of the split attribute based on all the "
+        + "instances (default: false).";
+  }
+
+  /**
+   * Sets the flag for normalizing the widths of a KDTree Node by the width of
+   * the dimension in the universe.
+   * 
+   * @param n		true to use normalizing.
+   */
+  public void setNormalizeNodeWidth(boolean n) {
+    m_NormalizeNodeWidth = n;
+  }
+
+  /**
+   * Gets the normalize flag.
+   * 
+   * @return 		True if normalizing
+   */
+  public boolean getNormalizeNodeWidth() {
+    return m_NormalizeNodeWidth;
+  }
+
+  /**
+   * returns the distance function currently in use.
+   * 
+   * @return 		the distance function
+   */
+  public DistanceFunction getDistanceFunction() {
+    return (DistanceFunction) m_EuclideanDistance;
+  }
+
+  /**
+   * sets the distance function to use for nearest neighbour search.
+   * 
+   * @param df		the distance function to use
+   * @throws Exception	if not EuclideanDistance
+   */
+  public void setDistanceFunction(DistanceFunction df) throws Exception {
+    if (!(df instanceof EuclideanDistance))
+      throw new Exception("KDTree currently only works with "
+          + "EuclideanDistanceFunction.");
+    m_DistanceFunction = m_EuclideanDistance = (EuclideanDistance) df;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String nodeSplitterTipText() {
+    return "The the splitting method to split the nodes of the KDTree.";
+  }
+
+  /**
+   * Returns the splitting method currently in use to split the nodes of the
+   * KDTree.
+   * 
+   * @return The KDTreeNodeSplitter currently in use.
+   */
+  public KDTreeNodeSplitter getNodeSplitter() {
+    return m_Splitter;
+  }
+
+  /**
+   * Sets the splitting method to use to split the nodes of the KDTree.
+   * 
+   * @param splitter The KDTreeNodeSplitter to use.
+   */
+  public void setNodeSplitter(KDTreeNodeSplitter splitter) {
+    m_Splitter = splitter;
+  }
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the KDTree search algorithm for nearest "
+      + "neighbour search.\n"
+      + "The connection to dataset is only a reference. For the tree "
+      + "structure the indexes are stored in an array. \n"
+      + "Building the tree:\n"
+      + "If a node has <maximal-inst-number> (option -L) instances no "
+      + "further splitting is done. Also if the split would leave one "
+      + "side empty, the branch is not split any further even if the "
+      + "instances in the resulting node are more than "
+      + "<maximal-inst-number> instances.\n"
+      + "**PLEASE NOTE:** The algorithm can not handle missing values, so it "
+      + "is advisable to run ReplaceMissingValues filter if there are any "
+      + "missing values in the dataset.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.add(new Option(
+	"\tNode splitting method to use.\n"
+	+ "\t(default: weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide)",
+	"S", 1, "-S <classname and options>"));
+    
+    newVector.addElement(new Option(
+	"\tSet minimal width of a box\n"
+        + "\t(default: 1.0E-2).", 
+        "W", 0, "-W <value>"));
+    
+    newVector.addElement(new Option(
+	"\tMaximal number of instances in a leaf\n"
+        + "\t(default: 40).",
+        "L", 0, "-L"));
+    
+    newVector.addElement(new Option(
+	"\tNormalizing will be done\n"
+        + "\t(Select dimension for split, with normalising to universe).",
+        "N", 0, "-N"));
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;classname and options&gt;
+   *  Node splitting method to use.
+   *  (default: weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide)</pre>
+   * 
+   * <pre> -W &lt;value&gt;
+   *  Set minimal width of a box
+   *  (default: 1.0E-2).</pre>
+   * 
+   * <pre> -L
+   *  Maximal number of instances in a leaf
+   *  (default: 40).</pre>
+   * 
+   * <pre> -N
+   *  Normalizing will be done
+   *  (Select dimension for split, with normalising to universe).</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options	the list of options as an array of strings
+   * @throws Exception	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    String optionString = Utils.getOption('S', options);
+    if (optionString.length() != 0) {
+      String splitMethodSpec[] = Utils.splitOptions(optionString);
+      if (splitMethodSpec.length == 0) {
+        throw new Exception("Invalid DistanceFunction specification string.");
+      }
+      String className = splitMethodSpec[0];
+      splitMethodSpec[0] = "";
+
+      setNodeSplitter((KDTreeNodeSplitter) Utils.forName(
+          KDTreeNodeSplitter.class, className, splitMethodSpec));
+    }
+    else {
+      setNodeSplitter(new SlidingMidPointOfWidestSide());
+    }
+
+    optionString = Utils.getOption('W', options);
+    if (optionString.length() != 0)
+      setMinBoxRelWidth(Double.parseDouble(optionString));
+    else
+      setMinBoxRelWidth(1.0E-2);
+
+    optionString = Utils.getOption('L', options);
+    if (optionString.length() != 0)
+      setMaxInstInLeaf(Integer.parseInt(optionString));
+    else
+      setMaxInstInLeaf(40);
+
+    setNormalizeNodeWidth(Utils.getFlag('N', options));
+  }
+
+  /**
+   * Gets the current settings of KDtree.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-S");
+    result.add(
+	(m_Splitter.getClass().getName() + " " +
+	 Utils.joinOptions(m_Splitter.getOptions())).trim());
+
+    result.add("-W");
+    result.add("" + getMinBoxRelWidth());
+
+    result.add("-L");
+    result.add("" + getMaxInstInLeaf());
+
+    if (getNormalizeNodeWidth())
+      result.add("-N");
+
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/LinearNNSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/LinearNNSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/LinearNNSearch.java	(revision 29)
@@ -0,0 +1,355 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LinearNNSearch.java
+ *    Copyright (C) 1999-2007 University of Waikato
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class implementing the brute force search algorithm for nearest neighbour search.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S
+ *  Skip identical instances (distances equal to zero).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class LinearNNSearch
+  extends NearestNeighbourSearch {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1915484723703917241L;
+
+  /** Array holding the distances of the nearest neighbours. It is filled up
+   *  both by nearestNeighbour() and kNearestNeighbours(). 
+   */
+  protected double[] m_Distances;
+    
+  /** Whether to skip instances from the neighbours that are identical to the query instance. */
+  protected boolean m_SkipIdentical = false;
+
+  /**
+   * Constructor. Needs setInstances(Instances) 
+   * to be called before the class is usable.
+   */
+  public LinearNNSearch() {
+    super();
+  }
+  
+  /**
+   * Constructor that uses the supplied set of 
+   * instances.
+   * 
+   * @param insts	the instances to use
+   */
+  public LinearNNSearch(Instances insts) {
+    super(insts);
+    m_DistanceFunction.setInstances(insts);
+  }
+  
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class implementing the brute force search algorithm for nearest "
+      + "neighbour search.";  
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    result.add(new Option(
+	"\tSkip identical instances (distances equal to zero).\n",
+	"S", 1,"-S"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S
+   *  Skip identical instances (distances equal to zero).
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    setSkipIdentical(Utils.getFlag('S', options));
+  }
+
+  /**
+   * Gets the current settings.
+   *
+   * @return 		an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getSkipIdentical())
+      result.add("-S");
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String skipIdenticalTipText() {
+    return "Whether to skip identical instances (with distance 0 to the target)";
+  }
+  
+  /**
+   * Sets the property to skip identical instances (with distance zero from 
+   * the target) from the set of neighbours returned.
+   * 
+   * @param skip 	if true, identical intances are skipped
+   */
+  public void setSkipIdentical(boolean skip) {
+    m_SkipIdentical = skip;
+  }
+  
+  /**
+   * Gets whether if identical instances are skipped from the neighbourhood.
+   * 
+   * @return 		true if identical instances are skipped
+   */
+  public boolean getSkipIdentical() {
+    return m_SkipIdentical;
+  }
+
+  
+  /** 
+   * Returns the nearest instance in the current neighbourhood to the supplied
+   * instance.
+   *  
+   * @param target 	The instance to find the nearest neighbour for.
+   * @return		the nearest instance
+   * @throws Exception 	if the nearest neighbour could not be found.
+   */
+  public Instance nearestNeighbour(Instance target) throws Exception {
+    return (kNearestNeighbours(target, 1)).instance(0);
+  }
+  
+  /**
+   * Returns k nearest instances in the current neighbourhood to the supplied
+   * instance.
+   *  
+   * @param target 	The instance to find the k nearest neighbours for.
+   * @param kNN		The number of nearest neighbours to find.
+   * @return		the k nearest neighbors
+   * @throws Exception  if the neighbours could not be found.
+   */
+  public Instances kNearestNeighbours(Instance target, int kNN) throws Exception {
+  
+    //debug
+    boolean print=false;
+
+    if(m_Stats!=null)
+      m_Stats.searchStart();
+ 
+    MyHeap heap = new MyHeap(kNN);
+    double distance; int firstkNN=0;
+    for(int i=0; i<m_Instances.numInstances(); i++) {
+      if(target == m_Instances.instance(i)) //for hold-one-out cross-validation
+        continue;
+      if(m_Stats!=null) 
+        m_Stats.incrPointCount();
+      if(firstkNN<kNN) {
+        if(print)
+          System.out.println("K(a): "+(heap.size()+heap.noOfKthNearest()));
+        distance = m_DistanceFunction.distance(target, m_Instances.instance(i), Double.POSITIVE_INFINITY, m_Stats);
+        if(distance == 0.0 && m_SkipIdentical)
+          if(i<m_Instances.numInstances()-1)
+            continue;
+          else
+            heap.put(i, distance);
+        heap.put(i, distance);
+        firstkNN++;
+      }
+      else {
+        MyHeapElement temp = heap.peek();
+        if(print)
+          System.out.println("K(b): "+(heap.size()+heap.noOfKthNearest()));
+        distance = m_DistanceFunction.distance(target, m_Instances.instance(i), temp.distance, m_Stats);
+        if(distance == 0.0 && m_SkipIdentical)
+          continue;
+        if(distance < temp.distance) {
+          heap.putBySubstitute(i, distance);
+        }
+        else if(distance == temp.distance) {
+          heap.putKthNearest(i, distance);
+        }
+
+      }
+    }
+    
+    Instances neighbours = new Instances(m_Instances, (heap.size()+heap.noOfKthNearest()));
+    m_Distances = new double[heap.size()+heap.noOfKthNearest()];
+    int [] indices = new int[heap.size()+heap.noOfKthNearest()];
+    int i=1; MyHeapElement h;
+    while(heap.noOfKthNearest()>0) {
+      h = heap.getKthNearest();
+      indices[indices.length-i] = h.index;
+      m_Distances[indices.length-i] = h.distance;
+      i++;
+    }
+    while(heap.size()>0) {
+      h = heap.get();
+      indices[indices.length-i] = h.index;
+      m_Distances[indices.length-i] = h.distance;
+      i++;
+    }
+    
+    m_DistanceFunction.postProcessDistances(m_Distances);
+    
+    for(int k=0; k<indices.length; k++) {
+      neighbours.add(m_Instances.instance(indices[k]));
+    }
+    
+    if(m_Stats!=null)
+      m_Stats.searchFinish();
+    
+    return neighbours;    
+  }
+  
+  /** 
+   * Returns the distances of the k nearest neighbours. The kNearestNeighbours
+   * or nearestNeighbour must always be called before calling this function. If
+   * this function is called before calling either the kNearestNeighbours or 
+   * the nearestNeighbour, then it throws an exception. If, however, if either
+   * of the nearestNeighbour functions are called at any point in the 
+   * past then no exception is thrown and the distances of the training set from
+   * the last supplied target instance (to either one of the nearestNeighbour 
+   * functions) is/are returned.
+   *
+   * @return 		array containing the distances of the 
+   * 			nearestNeighbours. The length and ordering of the 
+   * 			array is the same as that of the instances returned 
+   * 			by nearestNeighbour functions.
+   * @throws Exception 	if called before calling kNearestNeighbours
+   *            	or nearestNeighbours.
+   */
+  public double[] getDistances() throws Exception {
+    if(m_Distances==null)
+      throw new Exception("No distances available. Please call either "+
+                          "kNearestNeighbours or nearestNeighbours first.");
+    return m_Distances;    
+  }
+
+  /** 
+   * Sets the instances comprising the current neighbourhood.
+   * 
+   * @param insts 	The set of instances on which the nearest neighbour 
+   * 			search is carried out. Usually this set is the 
+   * 			training set. 
+   * @throws Exception	if setting of instances fails
+   */
+  public void setInstances(Instances insts) throws Exception {
+    m_Instances = insts;
+    m_DistanceFunction.setInstances(insts);
+  }
+  
+  /** 
+   * Updates the LinearNNSearch to cater for the new added instance. This 
+   * implementation only updates the ranges of the DistanceFunction class, 
+   * since our set of instances is passed by reference and should already have 
+   * the newly added instance.
+   * 
+   * @param ins 	The instance to add. Usually this is the instance that 
+   * 			is added to our neighbourhood i.e. the training 
+   * 			instances.
+   * @throws Exception	if the given instances are null
+   */
+  public void update(Instance ins) throws Exception {
+    if(m_Instances==null)
+      throw new Exception("No instances supplied yet. Cannot update without"+
+                          "supplying a set of instances first.");
+    m_DistanceFunction.update(ins);
+  }
+  
+  /** 
+   * Adds the given instance info. This implementation updates the range
+   * datastructures of the DistanceFunction class.
+   * 
+   * @param ins 	The instance to add the information of. Usually this is
+   * 			the test instance supplied to update the range of 
+   * 			attributes in the  distance function.
+   */
+  public void addInstanceInfo(Instance ins) {
+    if(m_Instances!=null)
+      try{ update(ins); }
+      catch(Exception ex) { ex.printStackTrace(); }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/NearestNeighbourSearch.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/NearestNeighbourSearch.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/NearestNeighbourSearch.java	(revision 29)
@@ -0,0 +1,922 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NearestNeighbourSearch.java
+ *    Copyright (C) 1999-2007 University of Waikato
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract class for nearest neighbour search. All algorithms (classes) that
+ * do nearest neighbour search should extend this class.
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class NearestNeighbourSearch
+  implements Serializable, OptionHandler, AdditionalMeasureProducer,
+             RevisionHandler {
+
+  /**
+   * A class for a heap to store the nearest k neighbours to an instance. 
+   * The heap also takes care of cases where multiple neighbours are the same 
+   * distance away.
+   * i.e. the minimum size of the heap is k.
+   *
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  protected class MyHeap
+    implements RevisionHandler {
+    
+    /** the heap. */
+    MyHeapElement m_heap[] = null;
+    
+    /**
+     * constructor.
+     * 
+     * @param maxSize		the maximum size of the heap
+     */
+    public MyHeap(int maxSize) {
+      if((maxSize%2)==0)
+        maxSize++;
+      
+      m_heap = new MyHeapElement[maxSize+1];
+      m_heap[0] = new MyHeapElement(0, 0);
+    }
+    
+    /**
+     * returns the size of the heap.
+     * 
+     * @return			the size
+     */
+    public int size() {
+      return m_heap[0].index;
+    }
+    
+    /**
+     * peeks at the first element.
+     * 
+     * @return			the first element
+     */
+    public MyHeapElement peek() {
+      return m_heap[1];
+    }
+    
+    /**
+     * returns the first element and removes it from the heap.
+     * 
+     * @return			the first element
+     * @throws Exception	if no elements in heap
+     */
+    public MyHeapElement get() throws Exception  {
+      if(m_heap[0].index==0)
+        throw new Exception("No elements present in the heap");
+      MyHeapElement r = m_heap[1];
+      m_heap[1] = m_heap[m_heap[0].index];
+      m_heap[0].index--;
+      downheap();
+      return r;
+    }
+    
+    /**
+     * adds the value to the heap.
+     * 
+     * @param i			the index
+     * @param d			the distance
+     * @throws Exception	if the heap gets too large
+     */
+    public void put(int i, double d) throws Exception {
+      if((m_heap[0].index+1)>(m_heap.length-1))
+        throw new Exception("the number of elements cannot exceed the "+
+        "initially set maximum limit");
+      m_heap[0].index++;
+      m_heap[m_heap[0].index] = new MyHeapElement(i, d);
+      upheap();
+    }
+    
+    /**
+     * Puts an element by substituting it in place of 
+     * the top most element.
+     * 
+     * @param i			the index
+     * @param d			the distance
+     * @throws Exception	if distance is smaller than that of the head
+     * 				element
+     */
+    public void putBySubstitute(int i, double d) throws Exception {
+      MyHeapElement head = get();
+      put(i, d);
+      //      System.out.println("previous: "+head.distance+" current: "+m_heap[1].distance);
+      if(head.distance == m_heap[1].distance) { //Utils.eq(head.distance, m_heap[1].distance)) {
+        putKthNearest(head.index, head.distance);
+      }
+      else if(head.distance > m_heap[1].distance) { //Utils.gr(head.distance, m_heap[1].distance)) {
+        m_KthNearest = null;
+        m_KthNearestSize = 0;
+        initSize = 10;
+      }
+      else if(head.distance < m_heap[1].distance) {
+        throw new Exception("The substituted element is smaller than the "+
+        "head element. put() should have been called "+
+        "in place of putBySubstitute()");
+      }
+    }
+    
+    /** the kth nearest ones. */
+    MyHeapElement m_KthNearest[] = null;
+    
+    /** The number of kth nearest elements. */
+    int m_KthNearestSize = 0;
+    
+    /** the initial size of the heap. */
+    int initSize=10;
+
+    /**
+     * returns the number of k nearest.
+     * 
+     * @return the number of k nearest
+     * @see			#m_KthNearestSize
+     */
+    public int noOfKthNearest() {
+      return m_KthNearestSize;
+    }
+    
+    /**
+     * Stores kth nearest elements (if there are 
+     * more than one).
+     * @param i			the index
+     * @param d			the distance
+     */
+    public void putKthNearest(int i,  double d) {
+      if(m_KthNearest==null) {
+        m_KthNearest = new MyHeapElement[initSize];
+      }
+      if(m_KthNearestSize>=m_KthNearest.length) {
+        initSize += initSize;
+        MyHeapElement temp[] = new MyHeapElement[initSize];
+        System.arraycopy(m_KthNearest, 0, temp, 0, m_KthNearest.length);
+        m_KthNearest = temp;
+      }
+      m_KthNearest[m_KthNearestSize++] = new MyHeapElement(i, d);
+    }
+    
+    /**
+     * returns the kth nearest element or null if none there.
+     * 
+     * @return			the kth nearest element
+     */
+    public MyHeapElement getKthNearest() {
+      if(m_KthNearestSize==0)
+        return null;
+      m_KthNearestSize--;
+      return m_KthNearest[m_KthNearestSize];
+    }
+    
+    /** 
+     * performs upheap operation for the heap 
+     * to maintian its properties. 
+     */
+    protected void upheap() {
+      int i = m_heap[0].index;
+      MyHeapElement temp;
+      while( i > 1  && m_heap[i].distance>m_heap[i/2].distance) {
+        temp = m_heap[i];
+        m_heap[i] = m_heap[i/2];
+        i = i/2;
+        m_heap[i] = temp; //this is i/2 done here to avoid another division.
+      }
+    }
+    
+    /** 
+     * performs downheap operation for the heap 
+     * to maintian its properties. 
+     */
+    protected void downheap() {
+      int i = 1;
+      MyHeapElement temp;
+      while( ( (2*i) <= m_heap[0].index &&
+      m_heap[i].distance < m_heap[2*i].distance )
+      ||
+      ( (2*i+1) <= m_heap[0].index &&
+      m_heap[i].distance < m_heap[2*i+1].distance) ) {
+        if((2*i+1)<=m_heap[0].index) {
+          if(m_heap[2*i].distance>m_heap[2*i+1].distance) {
+            temp = m_heap[i];
+            m_heap[i] = m_heap[2*i];
+            i = 2*i;
+            m_heap[i] = temp;
+          }
+          else {
+            temp = m_heap[i];
+            m_heap[i] = m_heap[2*i+1];
+            i = 2*i+1;
+            m_heap[i] = temp;
+          }
+        }
+        else {
+          temp = m_heap[i];
+          m_heap[i] = m_heap[2*i];
+          i = 2*i;
+          m_heap[i] = temp;
+        }
+      }
+    }
+    
+    /**
+     * returns the total size.
+     * 
+     * @return			the total size
+     */
+    public int totalSize() {
+      return size()+noOfKthNearest();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A class for storing data about a neighboring instance.
+   *
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */
+  protected class MyHeapElement
+    implements RevisionHandler {
+    
+    /** the index of this element. */
+    public int index;
+    
+    /** the distance of this element. */
+    public double distance;
+    
+    /**
+     * constructor.
+     * 
+     * @param i		the index
+     * @param d		the distance
+     */
+    public MyHeapElement(int i, double d) {
+      distance = d;
+      index = i;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /**
+   * A class for storing data about a neighboring instance.
+   *
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */ //better to change this into a heap element
+  protected class NeighborNode
+    implements RevisionHandler {
+
+    /** The neighbor instance. */
+    public Instance m_Instance;
+
+    /** The distance from the current instance to this neighbor. */
+    public double m_Distance;
+
+    /** A link to the next neighbor instance. */
+    public NeighborNode m_Next;
+    
+    /**
+     * Create a new neighbor node.
+     *
+     * @param distance 		the distance to the neighbor
+     * @param instance 		the neighbor instance
+     * @param next 		the next neighbor node
+     */
+    public NeighborNode(double distance, Instance instance, NeighborNode next) {
+      m_Distance = distance;
+      m_Instance = instance;
+      m_Next = next;
+    }
+
+    /**
+     * Create a new neighbor node that doesn't link to any other nodes.
+     *
+     * @param distance 		the distance to the neighbor
+     * @param instance 		the neighbor instance
+     */
+    public NeighborNode(double distance, Instance instance) {
+
+      this(distance, instance, null);
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  } 
+
+  /**
+   * A class for a linked list to store the nearest k neighbours
+   * to an instance. We use a list so that we can take care of
+   * cases where multiple neighbours are the same distance away.
+   * i.e. the minimum length of the list is k.
+   *
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5953 $
+   */ //better to change this into a heap
+  protected class NeighborList
+    implements RevisionHandler {
+
+    /** The first node in the list. */
+    protected NeighborNode m_First;
+
+    /** The last node in the list. */
+    protected NeighborNode m_Last;
+
+    /** The number of nodes to attempt to maintain in the list. */
+    protected int m_Length = 1;
+        
+    /**
+     * Creates the neighborlist with a desired length.
+     *
+     * @param length 		the length of list to attempt to maintain
+     */
+    public NeighborList(int length) {
+      m_Length = length;
+    }
+
+    /**
+     * Gets whether the list is empty.
+     *
+     * @return 			true if list is empty
+     */
+    public boolean isEmpty() {
+      return (m_First == null);
+    }
+
+    /**
+     * Gets the current length of the list.
+     *
+     * @return 			the current length of the list
+     */
+    public int currentLength() {
+      int i = 0;
+      NeighborNode current = m_First;
+      while (current != null) {
+        i++;
+        current = current.m_Next;
+      }
+      return i;
+    }
+
+    /**
+     * Inserts an instance neighbor into the list, maintaining the list
+     * sorted by distance.
+     *
+     * @param distance 		the distance to the instance
+     * @param instance 		the neighboring instance
+     */
+    public void insertSorted(double distance, Instance instance) {
+      
+      if (isEmpty()) {
+        m_First = m_Last = new NeighborNode(distance, instance);
+      } else {
+        NeighborNode current = m_First;
+        if (distance < m_First.m_Distance) {// Insert at head
+          m_First = new NeighborNode(distance, instance, m_First);
+        } else { // Insert further down the list
+          for( ;(current.m_Next != null) &&
+          (current.m_Next.m_Distance < distance);
+          current = current.m_Next);
+          current.m_Next = new NeighborNode(distance, instance,
+          current.m_Next);
+          if (current.equals(m_Last)) {
+            m_Last = current.m_Next;
+          }
+        }
+        
+        // Trip down the list until we've got k list elements (or more if the
+        // distance to the last elements is the same).
+        int valcount = 0;
+        for(current = m_First; current.m_Next != null;
+        current = current.m_Next) {
+          valcount++;
+          if ((valcount >= m_Length) && (current.m_Distance !=
+          current.m_Next.m_Distance)) {
+            m_Last = current;
+            current.m_Next = null;
+            break;
+          }
+        }
+      }
+    }
+
+    /**
+     * Prunes the list to contain the k nearest neighbors. If there are
+     * multiple neighbors at the k'th distance, all will be kept.
+     *
+     * @param k 		the number of neighbors to keep in the list.
+     */
+    public void pruneToK(int k) {
+      
+      if (isEmpty()) {
+        return;
+      }
+      if (k < 1) {
+        k = 1;
+      }
+      int currentK = 0;
+      double currentDist = m_First.m_Distance;
+      NeighborNode current = m_First;
+      for(; current.m_Next != null; current = current.m_Next) {
+        currentK++;
+        currentDist = current.m_Distance;
+        if ((currentK >= k) && (currentDist != current.m_Next.m_Distance)) {
+          m_Last = current;
+          current.m_Next = null;
+          break;
+        }
+      }
+    }
+    
+    /**
+     * Prints out the contents of the neighborlist.
+     */
+    public void printList() {
+      
+      if (isEmpty()) {
+        System.out.println("Empty list");
+      } else {
+        NeighborNode current = m_First;
+        while (current != null) {
+          System.out.println("Node: instance " + current.m_Instance
+          + ", distance " + current.m_Distance);
+          current = current.m_Next;
+        }
+        System.out.println();
+      }
+    }
+    
+    /**
+     * returns the first element in the list.
+     * 
+     * @return			the first element
+     */
+    public NeighborNode getFirst() {
+      return m_First;
+    }
+    
+    /**
+     * returns the last element in the list.
+     * 
+     * @return			the last element
+     */
+    public NeighborNode getLast() {
+      return m_Last;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5953 $");
+    }
+  }
+  
+  /** The neighbourhood of instances to find neighbours in. */
+  protected Instances m_Instances;
+  
+  /** The number of neighbours to find. */
+  protected int m_kNN;
+
+  /** the distance function used. */
+  protected DistanceFunction m_DistanceFunction = new EuclideanDistance();
+
+  /** Performance statistics. */
+  protected PerformanceStats m_Stats = null;
+  
+  /** Should we measure Performance. */
+  protected boolean m_MeasurePerformance = false;
+  
+  /**
+   * Constructor.
+   */
+  public NearestNeighbourSearch() {
+    if(m_MeasurePerformance)
+      m_Stats = new PerformanceStats();
+  }
+  
+  /**
+   * Constructor. 
+   * 
+   * @param insts 	The set of instances that constitute the neighbourhood.
+   */
+  public NearestNeighbourSearch(Instances insts) {
+    this();
+    m_Instances = insts;
+  }
+  
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Abstract class for nearest neighbour search. All algorithms (classes) that "
+      + "do nearest neighbour search should extend this class.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+
+    newVector.add(new Option(
+	"\tDistance function to use.\n"
+	+ "\t(default: weka.core.EuclideanDistance)",
+	"A", 1,"-A <classname and options>"));
+    
+    newVector.add(new Option(
+	"\tCalculate performance statistics.",
+	"P", 0,"-P"));
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Parses a given list of options. Valid options are:
+   *
+   <!-- options-start -->
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String nnSearchClass = Utils.getOption('A', options);
+    if(nnSearchClass.length() != 0) {
+      String nnSearchClassSpec[] = Utils.splitOptions(nnSearchClass);
+      if(nnSearchClassSpec.length == 0) { 
+        throw new Exception("Invalid DistanceFunction specification string."); 
+      }
+      String className = nnSearchClassSpec[0];
+      nnSearchClassSpec[0] = "";
+
+      setDistanceFunction( (DistanceFunction)
+                            Utils.forName( DistanceFunction.class, 
+                                           className, nnSearchClassSpec) );
+    }
+    else {
+      setDistanceFunction(new EuclideanDistance());
+    }
+    
+    setMeasurePerformance(Utils.getFlag('P',options));
+  }
+
+  /**
+   * Gets the current settings.
+   *
+   * @return 		an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+
+    result.add("-A");
+    result.add((m_DistanceFunction.getClass().getName() + " " +
+                   Utils.joinOptions(m_DistanceFunction.getOptions())).trim());
+    
+    if(getMeasurePerformance())
+      result.add("-P");
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String distanceFunctionTipText() {
+    return "The distance function to use for finding neighbours " +
+           "(default: weka.core.EuclideanDistance). ";
+  }
+  
+  /**
+   * returns the distance function currently in use.
+   * 
+   * @return		the distance function
+   */
+  public DistanceFunction getDistanceFunction() {
+    return m_DistanceFunction;
+  }
+  
+  /**
+   * sets the distance function to use for nearest neighbour search.
+   * 
+   * @param df		the new distance function to use
+   * @throws Exception	if instances cannot be processed
+   */
+  public void setDistanceFunction(DistanceFunction df) throws Exception {
+    m_DistanceFunction = df;
+  }
+
+  /** 
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   *         		displaying in the explorer/experimenter gui
+   */
+  public String measurePerformanceTipText() {
+    return "Whether to calculate performance statistics " +
+           "for the NN search or not";
+  }
+  
+  /**
+   * Gets whether performance statistics are being calculated or not.
+   * 
+   * @return		true if the measure performance is calculated
+   */
+  public boolean getMeasurePerformance() {
+    return m_MeasurePerformance;
+  }
+  
+  /**
+   * Sets whether to calculate the performance statistics or not.
+   * 
+   * @param measurePerformance	if true then the performance is calculated
+   */
+  public void setMeasurePerformance(boolean measurePerformance) {
+    m_MeasurePerformance = measurePerformance;
+    if(m_MeasurePerformance) {
+      if(m_Stats==null)
+        m_Stats = new PerformanceStats();
+    }
+    else
+      m_Stats = null;
+  }
+    
+  /** 
+   * Returns the nearest instance in the current neighbourhood to the supplied
+   * instance.
+   * 
+   * @param target 	The instance to find the nearest neighbour for.
+   * @return		the nearest neighbor
+   * @throws Exception 	if the nearest neighbour could not be found.
+   */
+  public abstract Instance nearestNeighbour(Instance target) throws Exception;
+  
+  /**
+   * Returns k nearest instances in the current neighbourhood to the supplied
+   * instance.
+   *  
+   * @param target 	The instance to find the k nearest neighbours for.
+   * @param k		The number of nearest neighbours to find.
+   * @return		the k nearest neighbors
+   * @throws Exception 	if the neighbours could not be found.
+   */
+  public abstract Instances kNearestNeighbours(Instance target, int k) throws Exception;
+ 
+  /**
+   * Returns the distances of the k nearest neighbours. The kNearestNeighbours
+   * or nearestNeighbour needs to be called first for this to work.
+   *
+   * @return		the distances
+   * @throws Exception 	if called before calling kNearestNeighbours
+   *            	or nearestNeighbours.
+   */
+  public abstract double[] getDistances() throws Exception;
+  
+  /**
+   * Updates the NearNeighbourSearch algorithm for the new added instance.
+   * P.S.: The method assumes the instance has already been added to the 
+   * m_Instances object by the caller.
+   * 
+   * @param ins		the instance to add
+   * @throws Exception	if updating fails
+   */
+  public abstract void update(Instance ins) throws Exception;
+
+  /** 
+   * Adds information from the given instance without modifying the 
+   * datastructure a lot.
+   * 
+   * @param ins		the instance to add the information from
+   */
+  public void addInstanceInfo(Instance ins) {
+  }
+  
+  /**
+   * Sets the instances.
+   * 
+   * @param insts	the instances to use
+   * @throws Exception	if setting fails
+   */
+  public void setInstances(Instances insts) throws Exception {
+    m_Instances = insts;
+  }
+  
+  /** 
+   * returns the instances currently set.
+   * 
+   * @return		the current instances
+   */
+  public Instances getInstances() {
+    return m_Instances;
+  }
+
+  /** 
+   * Gets the class object that contains the performance statistics of
+   * the search method. 
+   * 
+   * @return		the performance statistics
+   */
+  public PerformanceStats getPerformanceStats() {
+    return m_Stats;
+  }
+
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector; 
+    if(m_Stats == null) {
+      newVector = new Vector<String>(0);
+    }
+    else {
+      newVector = new Vector<String>();
+      Enumeration en = m_Stats.enumerateMeasures();
+      while(en.hasMoreElements())
+        newVector.add((String)en.nextElement());
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName 	the name of the measure to query for 
+   * 					its value
+   * @return 				the value of the named measure
+   * @throws IllegalArgumentException 	if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if(m_Stats==null)
+      throw new IllegalArgumentException(additionalMeasureName 
+        + " not supported (NearestNeighbourSearch)");
+    else
+      return m_Stats.getMeasure(additionalMeasureName);
+  }
+
+  /** 
+   * sorts the two given arrays.
+   * 
+   * @param arrayToSort 	The array sorting should be based on.
+   * @param linkedArray		The array that should have the same ordering as 
+   * 				arrayToSort.
+   */
+  public static void combSort11(double arrayToSort[], int linkedArray[]) {
+    int switches, j, top, gap;
+    double hold1; int hold2;
+    gap = arrayToSort.length;
+    do {
+      gap=(int)(gap/1.3);
+      switch(gap) {
+        case 0:
+          gap = 1;
+          break;
+        case 9:
+        case 10:
+          gap=11;
+          break;
+        default:
+          break;
+      }
+      switches=0;
+      top = arrayToSort.length-gap;
+      for(int i=0; i<top; i++) {
+        j=i+gap;
+        if(arrayToSort[i] > arrayToSort[j]) {
+          hold1=arrayToSort[i];
+          hold2=linkedArray[i];
+          arrayToSort[i]=arrayToSort[j];
+          linkedArray[i]=linkedArray[j];
+          arrayToSort[j]=hold1;
+          linkedArray[j]=hold2;
+          switches++;
+        }//endif
+      }//endfor
+    } while(switches>0 || gap>1);
+  }
+   
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param arrayToSort 	the array of doubles to be sorted
+   * @param linkedArray		the linked array
+   * @param l 			the first index of the subset 
+   * @param r 			the last index of the subset 
+   * @return 			the index of the middle element
+   */
+  protected static int partition(double[] arrayToSort, double[] linkedArray, int l, int r) {
+    double pivot = arrayToSort[(l + r) / 2];
+    double help;
+
+    while (l < r) {
+      while ((arrayToSort[l] < pivot) && (l < r)) {
+        l++;
+      }
+      while ((arrayToSort[r] > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = arrayToSort[l];
+        arrayToSort[l] = arrayToSort[r];
+        arrayToSort[r] = help;
+        help = linkedArray[l];
+        linkedArray[l] = linkedArray[r];
+        linkedArray[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (arrayToSort[r] > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+  
+  /**
+   * performs quicksort.
+   * 
+   * @param arrayToSort		the array to sort
+   * @param linkedArray		the linked array
+   * @param left 		the first index of the subset 
+   * @param right		the last index of the subset 
+   */
+  public static void quickSort(double[] arrayToSort, double[] linkedArray, int left, int right) {
+    if (left < right) {
+      int middle = partition(arrayToSort, linkedArray, left, right);
+      quickSort(arrayToSort, linkedArray, left, middle);
+      quickSort(arrayToSort, linkedArray, middle + 1, right);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/PerformanceStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/PerformanceStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/PerformanceStats.java	(revision 29)
@@ -0,0 +1,344 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PerformanceStats.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * The class that measures the performance of a nearest
+ * neighbour search (NNS) algorithm.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class PerformanceStats
+  implements AdditionalMeasureProducer, Serializable, RevisionHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -7215110351388368092L;
+  
+  /** The total number of queries looked at. */
+  protected int m_NumQueries;
+  
+  //Point-stats variables
+  /** The min and max data points looked for a query by 
+   * the NNS algorithm. */
+  public double m_MinP, m_MaxP;
+
+  /** The sum of data points looked 
+   * at for all the queries. 
+   */
+  public double m_SumP; 
+  /** The squared sum of data points looked 
+   * at for all the queries. 
+   */
+  public double m_SumSqP;
+  /** The number of data points looked at
+   * for the current/last query.
+   */
+  public double m_PointCount;
+  
+  //Coord-stats variables
+  /** The min and max coordinates(attributes) looked
+   * at per query. 
+   */
+  public double m_MinC, m_MaxC;
+  /** The sum of coordinates/attributes looked at
+   * for all the queries.
+   */
+  public double m_SumC;
+  /** The squared sum of coordinates/attributes looked at
+   * for all the queries.
+   */
+  public double m_SumSqC;
+  /**
+   * The number of coordinates looked at for
+   * the current/last query.
+   */
+  public double m_CoordCount;
+  
+  /**
+   * default constructor.
+   */
+  public PerformanceStats() { 
+    reset(); 
+  }
+  
+  /**
+   * Resets all internal fields/counters.
+   */ 
+  public void reset() {
+    m_NumQueries = 0;
+    //point stats
+    m_SumP = m_SumSqP = m_PointCount = 0;
+    m_MinP = Integer.MAX_VALUE;
+    m_MaxP = Integer.MIN_VALUE;
+    //coord stats
+    m_SumC = m_SumSqC = m_CoordCount = 0;
+    m_MinC = Integer.MAX_VALUE;
+    m_MaxC = Integer.MIN_VALUE;
+  }
+  
+  /**
+   * Signals start of the nearest neighbour search.
+   * Initializes the stats object.
+   */
+  public void searchStart() {
+    m_PointCount = 0;
+    m_CoordCount = 0;
+  }
+  
+  /**
+   * Signals end of the nearest neighbour search.
+   * Calculates the statistics for the search.
+   */
+  public void searchFinish() {
+    m_NumQueries++;  m_SumP += m_PointCount;  m_SumSqP += m_PointCount*m_PointCount;
+    if (m_PointCount < m_MinP) m_MinP = m_PointCount;
+    if (m_PointCount > m_MaxP) m_MaxP = m_PointCount;
+    //coord stats
+    double coordsPerPt = m_CoordCount / m_PointCount;;
+    m_SumC += coordsPerPt; m_SumSqC += coordsPerPt*coordsPerPt; 
+    if(coordsPerPt < m_MinC) m_MinC = coordsPerPt;
+    if(coordsPerPt > m_MaxC) m_MaxC = coordsPerPt;
+  }
+  
+  /**
+   * Increments the point count 
+   * (number of datapoints looked at).
+   */
+  public void incrPointCount() {
+    m_PointCount++;
+  }
+  
+  /**
+   * Increments the coordinate count
+   * (number of coordinates/attributes 
+   * looked at).
+   */
+  public void incrCoordCount() {
+    m_CoordCount++;
+  }
+  
+  /**
+   * adds the given number to the point count.
+   * 
+   * @param n The number to add to the point count.
+   */
+  public void updatePointCount(int n) {
+    m_PointCount += n;
+  }
+  
+  /**
+   * Returns the number of queries.
+   * 
+   * @return The number of queries.
+   */
+  public int getNumQueries() {
+    return m_NumQueries;
+  }
+  
+  /**
+   * Returns the total number of points visited.
+   * 
+   * @return The total number.
+   */
+  public double getTotalPointsVisited() {
+    return m_SumP;
+  }
+  
+  /**
+   * Returns the mean of points visited.
+   * 
+   * @return The mean points visited.
+   */
+  public double getMeanPointsVisited() {
+    return m_SumP/(double)m_NumQueries;
+  }
+  
+  /** 
+   * Returns the standard deviation of points visited.
+   * 
+   * @return The standard deviation.
+   */
+  public double getStdDevPointsVisited() {
+    return Math.sqrt((m_SumSqP - (m_SumP*m_SumP)/(double)m_NumQueries)/(m_NumQueries-1));
+  }
+  
+  /**
+   * Returns the minimum of points visited.
+   * 
+   * @return The minimum.
+   */
+  public double getMinPointsVisited() {
+    return m_MinP;
+  }
+  
+  /**
+   * Returns the maximum of points visited.
+   * 
+   * @return The maximum.
+   */
+  public double getMaxPointsVisited() {
+    return m_MaxP;
+  }
+
+  /*************----------Coord Stat functions---------**************/
+  
+  /**
+   * Returns the total sum of coords per point.
+   * 
+   * @return The total per point.
+   */
+  public double getTotalCoordsPerPoint() {
+    return m_SumC;
+  }
+  
+  /**
+   * Returns the mean of coords per point.
+   * 
+   * @return The mean.
+   */
+  public double getMeanCoordsPerPoint() {
+    return m_SumC/(double)m_NumQueries;
+  } 
+  
+  /**
+   * Returns the standard deviation of coords per point.
+   * 
+   * @return The standard deviation.
+   */
+  public double getStdDevCoordsPerPoint() {
+    return Math.sqrt((m_SumSqC - (m_SumC*m_SumC)/(double)m_NumQueries)/(m_NumQueries-1));
+  }
+  
+  /**
+   * Returns the minimum of coords per point.
+   * 
+   * @return The minimum.
+   */
+  public double getMinCoordsPerPoint() {
+    return m_MinC;
+  }
+  
+  /**
+   * Returns the maximum of coords per point.
+   * 
+   * @return The maximum.
+   */
+  public double getMaxCoordsPerPoint() {
+    return m_MaxC;
+  }
+
+  /*****----MiscFunctions----****/
+  
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return An enumeration of the measure names.
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector = new Vector<String>();
+    
+    newVector.addElement("measureTotal_points_visited");
+    newVector.addElement("measureMean_points_visited");
+    newVector.addElement("measureStdDev_points_visited");
+    newVector.addElement("measureMin_points_visited");
+    newVector.addElement("measureMax_points_visited");
+    //coord stats
+    newVector.addElement("measureTotalCoordsPerPoint");
+    newVector.addElement("measureMeanCoordsPerPoint");
+    newVector.addElement("measureStdDevCoordsPerPoint");
+    newVector.addElement("measureMinCoordsPerPoint");
+    newVector.addElement("measureMaxCoordsPerPoint");
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName The name of the measure to query for 
+   * its value.
+   * @return The value of the named measure.
+   * @throws IllegalArgumentException If the named measure is not 
+   * supported.
+   */
+  public double getMeasure(String additionalMeasureName) throws IllegalArgumentException {
+    if (additionalMeasureName.compareToIgnoreCase("measureTotal_points_visited") == 0) {
+      return (double) getTotalPointsVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMean_points_visited") == 0) {
+      return (double) getMeanPointsVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_points_visited") == 0) {
+      return (double) getStdDevPointsVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMin_points_visited") == 0) {
+      return (double) getMinPointsVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMax_points_visited") == 0) {
+      return (double) getMaxPointsVisited();
+    }
+    //coord stats
+    else if (additionalMeasureName.compareToIgnoreCase("measureTotalCoordsPerPoint") == 0) {
+      return (double) getTotalCoordsPerPoint();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMeanCoordsPerPoint") == 0) {
+      return (double) getMeanCoordsPerPoint();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDevCoordsPerPoint") == 0) {
+      return (double) getStdDevCoordsPerPoint();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMinCoordsPerPoint") == 0) {
+      return (double) getMinCoordsPerPoint();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMaxCoordsPerPoint") == 0) {
+      return (double) getMaxCoordsPerPoint();
+    } else {
+      throw new IllegalArgumentException(additionalMeasureName 
+			  + " not supported by PerformanceStats.");
+    }
+  }
+  
+  /**
+   * Returns a string representation of the statistics.
+   * 
+   * @return The statistics as string.
+   */
+  public String getStats() {
+    StringBuffer buf = new StringBuffer();
+    
+    buf.append("           min, max, total, mean, stddev\n");
+    buf.append("Points:    "+getMinPointsVisited()+", "+getMaxPointsVisited()+","+getTotalPointsVisited()+
+	       ","+getMeanPointsVisited()+", "+getStdDevPointsVisited()+"\n");
+    
+    return buf.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/TreePerformanceStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/TreePerformanceStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/TreePerformanceStats.java	(revision 29)
@@ -0,0 +1,322 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TreePerformanceStats.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.RevisionUtils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * The class that measures the performance of a tree based 
+ * nearest neighbour search algorithm.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class TreePerformanceStats
+  extends PerformanceStats {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -6637636693340810373L;
+  
+  // Variables for leaves
+  /** The min and max number leaf nodes looked 
+   * for a query by the tree based NNS algorithm. */
+  protected int m_MinLeaves, m_MaxLeaves;
+  
+  /** The sum of leaf nodes looked 
+   * at for all the queries. 
+   */
+  protected int m_SumLeaves;
+  /** The squared sum of leaf nodes looked 
+   * at for all the queries. 
+   */
+  protected int m_SumSqLeaves;
+  /** The number of leaf nodes looked at
+   * for the current/last query.
+   */
+  protected int m_LeafCount;
+  
+  // Variables for internal nodes
+  /** The min and max number internal nodes looked 
+   * for a query by the tree based NNS algorithm. */
+  protected int m_MinIntNodes, m_MaxIntNodes;
+  /** The sum of internal nodes looked 
+   * at for all the queries. 
+   */
+  protected int m_SumIntNodes;
+  /** The squared sum of internal nodes looked 
+   * at for all the queries. 
+   */
+  protected int m_SumSqIntNodes;
+  /** The number of internal nodes looked at
+   * for the current/last query.
+   */
+  protected int m_IntNodeCount;
+  
+  /**
+   * Default constructor.
+   */
+  public TreePerformanceStats() { 
+    reset(); 
+  }
+  
+  /**
+   * Resets all internal fields/counters.
+   */
+  public void reset() {
+    super.reset();
+    //initializing leaf variables
+    m_SumLeaves = m_SumSqLeaves = m_LeafCount = 0;
+    m_MinLeaves = Integer.MAX_VALUE;
+    m_MaxLeaves = Integer.MIN_VALUE;
+    //initializing internal variables
+    m_SumIntNodes = m_SumSqIntNodes = m_IntNodeCount = 0;
+    m_MinIntNodes = Integer.MAX_VALUE;
+    m_MaxIntNodes = Integer.MIN_VALUE;
+  }
+  
+  /**
+   * Signals start of the nearest neighbour search.
+   * Initializes the stats object.
+   */
+  public void searchStart() {
+    super.searchStart();
+    m_LeafCount = 0;
+    m_IntNodeCount = 0;
+  }
+  
+  /**
+   * Signals end of the nearest neighbour search.
+   * Calculates the statistics for the search.
+   */
+  public void searchFinish() {
+    super.searchFinish();
+    //updating stats for leaf nodes
+    m_SumLeaves += m_LeafCount;  m_SumSqLeaves += m_LeafCount*m_LeafCount;
+    if (m_LeafCount < m_MinLeaves) m_MinLeaves = m_LeafCount;
+    if (m_LeafCount > m_MaxLeaves) m_MaxLeaves = m_LeafCount;
+    //updating stats for internal nodes
+    m_SumIntNodes += m_IntNodeCount;  m_SumSqIntNodes += m_IntNodeCount*m_IntNodeCount;
+    if (m_IntNodeCount < m_MinIntNodes) m_MinIntNodes = m_IntNodeCount;
+    if (m_IntNodeCount > m_MaxIntNodes) m_MaxIntNodes = m_IntNodeCount;
+  }
+  
+  /**
+   * Increments the leaf count.
+   */
+  public void incrLeafCount() {
+    m_LeafCount++;
+  }
+
+  /**
+   * Increments the internal node count.
+   */
+  public void incrIntNodeCount() {
+    m_IntNodeCount++;
+  }
+
+  // Getter functions for leaves
+  
+  /**
+   * Returns the total number of leaves visited.
+   * 
+   * @return The total number.
+   */
+  public int getTotalLeavesVisited() {
+    return m_SumLeaves;
+  }
+  
+  /**
+   * Returns the mean of number of leaves visited.
+   * 
+   * @return The mean number of leaves visited.
+   */
+  public double getMeanLeavesVisited() {
+    return m_SumLeaves/(double)m_NumQueries;
+  }
+  
+  /**
+   * Returns the standard deviation of leaves visited.
+   * 
+   * @return The standard deviation of leaves visited.
+   */
+  public double getStdDevLeavesVisited() {
+    return Math.sqrt((m_SumSqLeaves - (m_SumLeaves*m_SumLeaves)/(double)m_NumQueries)/(m_NumQueries-1));
+  }
+  
+  /**
+   * Returns the minimum number of leaves visited.
+   * 
+   * @return The minimum number of leaves visited.
+   */
+  public int getMinLeavesVisited() {
+    return m_MinLeaves;
+  }
+  
+  /**
+   * Returns the maximum number of leaves visited.
+   * 
+   * @return The maximum number of leaves visited.
+   */
+  public int getMaxLeavesVisited() {
+    return m_MaxLeaves;
+  }
+  
+  // Getter functions for internal nodes
+  
+  /**
+   * Returns the total number of internal nodes visited.
+   * 
+   * @return The total number of internal nodes visited.
+   */
+  public int getTotalIntNodesVisited() {
+    return m_SumIntNodes;
+  }
+  
+  /**
+   * Returns the mean of internal nodes visited.
+   * 
+   * @return The mean number of internal nodes 
+   * visited.
+   */
+  public double getMeanIntNodesVisited() {
+    return m_SumIntNodes/(double)m_NumQueries;
+  }
+  
+  /**
+   * Returns the standard deviation of internal nodes visited.
+   * 
+   * @return The standard deviation of internal nodes visited.
+   */
+  public double getStdDevIntNodesVisited() {
+    return Math.sqrt((m_SumSqIntNodes - (m_SumIntNodes*m_SumIntNodes)/(double)m_NumQueries)/(m_NumQueries-1));
+  }
+  
+  /**
+   * Returns the minimum of internal nodes visited.
+   * 
+   * @return The minimum of internal nodes visited. 
+   */
+  public int getMinIntNodesVisited() {
+    return m_MinIntNodes;
+  }
+  
+  /**
+   * returns the maximum of internal nodes visited.
+   * 
+   * @return The maximum of internal nodes visited.
+   */
+  public int getMaxIntNodesVisited() {
+    return m_MaxIntNodes;
+  }
+  
+  /**
+   * Returns an enumeration of the additional measure names.
+   * 
+   * @return An enumeration of the measure names.
+   */
+  public Enumeration enumerateMeasures() {
+    Vector<String> newVector = new Vector<String>();
+    
+    Enumeration en = super.enumerateMeasures();
+    while(en.hasMoreElements())
+      newVector.addElement((String)en.nextElement());
+    
+    newVector.addElement("measureTotal_nodes_visited");
+    newVector.addElement("measureMean_nodes_visited");
+    newVector.addElement("measureStdDev_nodes_visited");
+    newVector.addElement("measureMin_nodes_visited");
+    newVector.addElement("measureMax_nodes_visited");
+    //coord stats
+    newVector.addElement("measureTotal_leaves_visited");
+    newVector.addElement("measureMean_leaves_visited");
+    newVector.addElement("measureStdDev_leaves_visited");
+    newVector.addElement("measureMin_leaves_visited");
+    newVector.addElement("measureMax_leaves_visited");
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName The name of the measure to query for 
+   * its value.
+   * @return The value of the named measure.
+   * @throws IllegalArgumentException If the named measure is not 
+   * supported.
+   */
+  public double getMeasure(String additionalMeasureName) throws IllegalArgumentException {
+    if (additionalMeasureName.compareToIgnoreCase("measureTotal_nodes_visited") == 0) {
+      return (double) getTotalIntNodesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMean_nodes_visited") == 0) {
+      return (double) getMeanIntNodesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_nodes_visited") == 0) {
+      return (double) getStdDevIntNodesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMin_nodes_visited") == 0) {
+      return (double) getMinIntNodesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMax_nodes_visited") == 0) {
+      return (double) getMaxIntNodesVisited();
+    }
+    //coord stats
+    else if (additionalMeasureName.compareToIgnoreCase("measureTotal_leaves_visited") == 0) {
+      return (double) getTotalLeavesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMean_leaves_visited") == 0) {
+      return (double) getMeanLeavesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_leaves_visited") == 0) {
+      return (double) getStdDevLeavesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMin_leaves_visited") == 0) {
+      return (double) getMinLeavesVisited();
+    } else if (additionalMeasureName.compareToIgnoreCase("measureMax_leaves_visited") == 0) {
+      return (double) getMaxLeavesVisited();
+    } else {
+      return super.getMeasure(additionalMeasureName);
+    }
+  }
+  
+  /**
+   * Returns a string representation of the statistics.
+   * 
+   * @return The statistics as string.
+   */
+  public String getStats() {
+    StringBuffer buf = new StringBuffer(super.getStats());
+    
+    buf.append("leaves:    "+getMinLeavesVisited()+", "+getMaxLeavesVisited()+
+	       ","+getTotalLeavesVisited()+","+getMeanLeavesVisited()+", "+getStdDevLeavesVisited()+"\n");
+    buf.append("Int nodes: "+getMinIntNodesVisited()+", "+getMaxIntNodesVisited()+
+	       ","+getTotalIntNodesVisited()+","+getMeanIntNodesVisited()+", "+getStdDevIntNodesVisited()+"\n");
+
+    return buf.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallNode.java	(revision 29)
@@ -0,0 +1,371 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BallNode.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.DistanceFunction;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Class representing a node of a BallTree.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5987 $
+ */
+public class BallNode
+  implements Serializable, RevisionHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -8289151861759883510L;
+  
+  /**
+   * The start index of the portion of the master index array, 
+   * which stores the indices of the instances/points the node 
+   * contains.
+   */
+  public int m_Start;
+  
+  /**
+   * The end index of the portion of the master index array, 
+   * which stores indices of the instances/points the node 
+   * contains.
+   */
+  public int m_End;
+  
+  /** The number of instances/points in the node. */
+  public int m_NumInstances;
+  
+  /** The node number/id. */
+  public int m_NodeNumber;
+  
+  /** The attribute that splits this node (not 
+   * always used). */
+  public int m_SplitAttrib = -1;
+  
+  /** The value of m_SpiltAttrib that splits this
+   * node (not always used).
+   */
+  public double m_SplitVal = -1;
+  
+  /** The left child of the node. */
+  public BallNode m_Left = null;
+  
+  /** The right child of the node. */
+  public BallNode m_Right = null;
+  
+  /** 
+   * The pivot/centre of the ball. 
+   */
+  protected Instance m_Pivot;
+  
+  /** The radius of this ball (hyper sphere). */
+  protected double m_Radius;
+  
+  /**
+   * Constructor.
+   * @param nodeNumber The node's number/id.
+   */
+  public BallNode(int nodeNumber) {
+    m_NodeNumber = nodeNumber;
+  }
+  
+  /**
+   * Creates a new instance of BallNode.
+   * @param start The begining index of the portion of
+   * the master index array belonging to this node.
+   * @param end The end index of the portion of the 
+   * master index array belonging to this node. 
+   * @param nodeNumber The node's number/id.
+   */
+  public BallNode(int start, int end, int nodeNumber) {
+    m_Start = start;
+    m_End = end;
+    m_NodeNumber = nodeNumber;
+    m_NumInstances = end - start + 1;
+  }
+  
+  /**
+   * Creates a new instance of BallNode.
+   * @param start The begining index of the portion of
+   * the master index array belonging to this node.
+   * @param end The end index of the portion of the 
+   * master index array belonging to this node. 
+   * @param nodeNumber The node's number/id.
+   * @param pivot The pivot/centre of the node's ball.
+   * @param radius The radius of the node's ball.
+   */
+  public BallNode(int start, int end, int nodeNumber, Instance pivot, double radius) {
+    m_Start = start;
+    m_End = end;
+    m_NodeNumber = nodeNumber; 
+    m_Pivot = pivot;
+    m_Radius = radius;
+    m_NumInstances = end - start + 1;
+  }
+  
+  /** 
+   * Returns true if the node is a leaf node (if
+   * both its left and right child are null).
+   * @return true if the node is a leaf node.
+   */
+  public boolean isALeaf() {
+    return (m_Left==null && m_Right==null);
+  }
+  
+  /** 
+   * Sets the the start and end index of the
+   * portion of the master index array that is
+   * assigned to this node.  
+   * @param start The start index of the 
+   * master index array. 
+   * @param end The end index of the master
+   * indext array. 
+   */
+  public void setStartEndIndices(int start, int end) {
+    m_Start = start;
+    m_End = end;
+    m_NumInstances = end - start + 1;    
+  }
+
+  /**
+   * Sets the pivot/centre of this nodes
+   * ball.
+   * @param pivot The centre/pivot.
+   */
+  public void setPivot(Instance pivot) {
+    m_Pivot = pivot;
+  }
+  
+  /**
+   * Returns the pivot/centre of the
+   * node's ball.
+   * @return The ball pivot/centre.
+   */
+  public Instance getPivot() {
+    return m_Pivot;
+  }
+  
+  /** 
+   * Sets the radius of the node's 
+   * ball.
+   * @param radius The radius of the nodes ball.
+   */
+  public void setRadius(double radius) {
+    m_Radius = radius;
+  }
+  
+  /**
+   * Returns the radius of the node's ball.
+   * @return Radius of node's ball.
+   */
+  public double getRadius() {
+    return m_Radius;
+  }
+  
+  /** 
+   * Returns the number of instances in the
+   * hyper-spherical region of this node. 
+   * @return The number of instances in the
+   * node. 
+   */
+  public int numInstances() {
+    return (m_End-m_Start+1);
+  }
+  
+  /**
+   * Calculates the centroid pivot of a node. The node is given
+   * in the form of an indices array that contains the 
+   * indices of the points inside the node.   
+   * @param instList The indices array pointing to the 
+   * instances in the node.
+   * @param insts The actual instances. The instList
+   * points to instances in this object.  
+   * @return The calculated centre/pivot of the node.  
+   */
+  public static Instance calcCentroidPivot(int[] instList, Instances insts) {
+    double[] attrVals = new double[insts.numAttributes()];
+    
+    Instance temp;
+    for(int i=0; i<instList.length; i++) {
+      temp = insts.instance(instList[i]);
+      for(int j=0; j<temp.numValues(); j++) {
+        attrVals[j] += temp.valueSparse(j);
+      }
+    }
+    for(int j=0, numInsts=instList.length; j<attrVals.length; j++) {
+      attrVals[j] /= numInsts;
+    }
+    temp = new DenseInstance(1.0, attrVals);
+    return temp;
+  }
+  
+  /**
+   * Calculates the centroid pivot of a node. The node is given
+   * in the form of the portion of an indices array that 
+   * contains the indices of the points inside the node.
+   * @param start The start index marking the start of 
+   * the portion belonging to the node.
+   * @param end The end index marking the end of the
+   * portion in the indices array that belongs to the node.    
+   * @param instList The indices array pointing to the 
+   * instances in the node.
+   * @param insts The actual instances. The instList
+   * points to instances in this object.  
+   * @return The calculated centre/pivot of the node.  
+   */
+  public static Instance calcCentroidPivot(int start, int end, int[] instList, 
+                                          Instances insts) {
+    double[] attrVals = new double[insts.numAttributes()];
+    Instance temp;
+    for(int i=start; i<=end; i++) {
+      temp = insts.instance(instList[i]);
+      for(int j=0; j<temp.numValues(); j++) {
+        attrVals[j] += temp.valueSparse(j);
+      }
+    }
+    for(int j=0, numInsts=end-start+1; j<attrVals.length; j++) {
+      attrVals[j] /= numInsts;
+    }
+    
+    temp = new DenseInstance(1.0, attrVals);    
+    return temp;
+  }
+  
+  /**
+   * Calculates the radius of node.
+   *  
+   * @param instList The indices array containing the indices of the 
+   * instances inside the node. 
+   * @param insts The actual instances object. instList points to 
+   * instances in this object.
+   * @param pivot The centre/pivot of the node.
+   * @param distanceFunction The distance fuction to use to calculate 
+   * the radius. 
+   * @return The radius of the node. 
+   * @throws Exception If there is some problem in calculating the 
+   * radius. 
+   */
+  public static double calcRadius(int[] instList, Instances insts,Instance pivot, 
+                                 DistanceFunction distanceFunction) 
+                                                  throws Exception {
+    return calcRadius(0, instList.length-1, instList, insts, 
+                      pivot, distanceFunction);
+  }
+  
+  /**
+   * Calculates the radius of a node.
+   * 
+   * @param start The start index of the portion in indices array 
+   * that belongs to the node.
+   * @param end The end index of the portion in indices array 
+   * that belongs to the node. 
+   * @param instList The indices array holding indices of 
+   * instances. 
+   * @param insts The actual instances. instList points to 
+   * instances in this object. 
+   * @param pivot The centre/pivot of the node. 
+   * @param distanceFunction The distance function to use to 
+   * calculate the radius. 
+   * @return The radius of the node. 
+   * @throws Exception If there is some problem calculating the 
+   * radius. 
+   */
+  public static double calcRadius(int start, int end, int[] instList, 
+                                 Instances insts, Instance pivot, 
+                                 DistanceFunction distanceFunction) 
+                                                             throws Exception {
+    double radius = Double.NEGATIVE_INFINITY;
+    
+    for(int i=start; i<=end; i++) {
+      double dist = distanceFunction.distance(pivot, 
+                                              insts.instance(instList[i]), Double.POSITIVE_INFINITY);
+      
+      if(dist>radius)
+        radius = dist;
+    }
+    return Math.sqrt(radius);
+  }
+ 
+  /**
+   * Calculates the centroid pivot of a node based on its
+   * two child nodes (if merging two nodes).
+   * @param child1 The first child of the node.
+   * @param child2 The second child of the node.
+   * @param insts The set of instances on which 
+   * the tree is (or is to be) built.
+   * @return The centre/pivot of the node.
+   * @throws Exception If there is some problem calculating
+   * the pivot.
+   */
+  public static Instance calcPivot(BallNode child1, BallNode child2, 
+                                         Instances insts)  throws Exception {
+    Instance p1 = child1.getPivot(), p2 = child2.getPivot();
+    double[] attrVals = new double[p1.numAttributes()];
+    
+    for(int j=0; j<attrVals.length; j++) {
+      attrVals[j] += p1.value(j);
+      attrVals[j] += p2.value(j);
+      attrVals[j] /= 2D;
+    }
+    
+    p1 = new DenseInstance(1.0, attrVals);
+    return p1;
+  }
+
+  /**
+   * Calculates the radius of a node based on its two 
+   * child nodes (if merging two nodes).
+   * @param child1 The first child of the node.
+   * @param child2 The second child of the node.
+   * @param pivot The centre/pivot of the node. 
+   * @param distanceFunction The distance function to 
+   * use to calculate the radius
+   * @return The radius of the node. 
+   * @throws Exception If there is some problem 
+   * in calculating the radius.
+   */
+  public static double calcRadius(BallNode child1, BallNode child2, 
+                                  Instance pivot, 
+                                  DistanceFunction distanceFunction) 
+                                                             throws Exception {
+    Instance p1 = child1.getPivot(), p2 = child2.getPivot();                                                               
+    
+    double radius = child1.getRadius() + distanceFunction.distance(p1, p2) + 
+                    child2.getRadius();
+    
+    return radius/2;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallSplitter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallSplitter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallSplitter.java	(revision 29)
@@ -0,0 +1,173 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BallSplitter.java
+ * Copyright (C) 2007 University of Waikato
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract class for splitting a ball tree's BallNode.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class BallSplitter
+  implements Serializable, OptionHandler, RevisionHandler {
+  
+  /** The instance on which the tree is built. */
+  protected Instances m_Instances;
+  
+  /** The distance function (metric) from which
+   * the tree is (OR is to be) built. */
+  protected EuclideanDistance m_DistanceFunction;
+  
+  /** 
+   * The master index array that'll be reshuffled as nodes
+   * are split (and the tree is constructed). 
+   */
+  protected int[] m_Instlist; 
+
+  /**
+   * default constructor.
+   */
+  public BallSplitter() {
+  }
+  
+  /**
+   * Creates a new instance of BallSplitter.
+   * @param instList The master index array.
+   * @param insts The instances on which the tree
+   * is (or is to be) built.
+   * @param e The Euclidean distance function to 
+   * use for splitting.
+   */
+  public BallSplitter(int[] instList, Instances insts, EuclideanDistance e) { 
+    m_Instlist = instList;
+    m_Instances = insts;
+    m_DistanceFunction = e;
+  }
+
+  /**
+   * Checks whether if this ball splitter is 
+   * correctly intialized or not (i.e. master index
+   * array, instances, and distance function is 
+   * supplied or not)
+   * @throws Exception If the object is not correctly
+   * initialized.
+   */
+  protected void correctlyInitialized() throws Exception {
+    if(m_Instances==null)
+      throw new Exception("No instances supplied.");
+    else if(m_Instlist==null) 
+      throw new Exception("No instance list supplied.");
+    else if(m_DistanceFunction==null)
+      throw new Exception("No Euclidean distance function supplied.");
+    else if(m_Instances.numInstances() != m_Instlist.length)
+      throw new Exception("The supplied instance list doesn't seem to match " +
+                          "the supplied instances");
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    return new Vector().elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+  }
+
+  /**
+   * Gets the current settings of the object.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    return new String[0];
+  }
+  
+  /** 
+   * Splits a node into two. 
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public abstract void splitNode(BallNode node, int numNodesCreated) 
+      throws Exception;
+  
+  /**
+   * Sets the training instances on which the tree is 
+   * (or is to be) built. 
+   * @param inst The training instances.
+   */
+  public void setInstances(Instances inst) {
+    m_Instances = inst;
+  }
+  
+  /** 
+   * Sets the master index array containing indices of the
+   * training instances. This array will be rearranged as 
+   * the tree is built (or a node is split_), so that each 
+   * node is assigned a portion in this array which 
+   * contain the instances insides the node's region.
+   * @param instList The master index array.
+   */
+  public void setInstanceList(int[] instList) {
+    m_Instlist = instList;
+  }
+  
+  /**
+   * Sets the distance function used to (or to be used 
+   * to) build the tree. 
+   * @param func The distance function. 
+   */
+  public void setEuclideanDistanceFunction(EuclideanDistance func) {
+    m_DistanceFunction = func;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallTreeConstructor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallTreeConstructor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BallTreeConstructor.java	(revision 29)
@@ -0,0 +1,329 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BallTreeConstructor.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.DistanceFunction;
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract class for constructing a BallTree .
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class BallTreeConstructor 
+  implements OptionHandler, Serializable, RevisionHandler {
+  
+  /** The maximum number of instances allowed in a leaf. */
+  protected int m_MaxInstancesInLeaf=40;
+  
+  /** The maximum relative radius of a leaf node 
+   * (relative to the smallest ball enclosing all the 
+   * data (training) points). */
+  protected double m_MaxRelLeafRadius=0.001;
+  
+  /** Should a parent ball completely enclose the balls
+   * of its two children, or only the points inside
+   * its children. */
+  protected boolean m_FullyContainChildBalls = false;
+  
+  /** The instances on which to build the tree. */
+  protected Instances m_Instances;
+  
+  /** The distance function to use to build the tree. */
+  protected DistanceFunction m_DistanceFunction;
+  
+  /** The number of internal and leaf nodes in the 
+   * built tree. */
+  protected int m_NumNodes;
+  
+  /** The number of leaf nodes in the built tree. */
+  protected int m_NumLeaves;
+  
+  /** The depth of the built tree. */
+  protected int m_MaxDepth;
+  
+  /** The master index array. */
+  protected int[] m_InstList;
+  
+  /**
+   * Creates a new instance of BallTreeConstructor.
+   */
+  public BallTreeConstructor() {
+  }
+  
+  /**
+   *  Builds the ball tree. 
+   * @return The root node of the tree. 
+   * @throws Exception If there is problem building
+   * the tree.
+   */
+  public abstract BallNode buildTree() throws Exception;
+  
+  /**
+   * Adds an instance to the ball tree. 
+   * @param node The root node of the tree.
+   * @param inst The instance to add to the tree.
+   * @return The new master index array after 
+   * adding the instance. 
+   * @throws Exception If there is some problem adding
+   * the given instance to the tree. 
+   */
+  public abstract int[] addInstance(BallNode node, Instance inst) 
+      throws Exception;
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui.
+   */
+  public String maxInstancesInLeafTipText() {
+    return "The maximum number of instances allowed in a leaf.";
+  }
+  
+  /**
+   * Returns the maximum number of instances allowed in a leaf.
+   * @return The maximum number of instances allowed in a leaf.
+   */
+  public int getMaxInstancesInLeaf() {
+    return m_MaxInstancesInLeaf;
+  }
+  
+  /**
+   * Sets the maximum number of instances allowed in a leaf.
+   * @param num The maximum number of instances allowed in 
+   * a leaf.
+   * @throws Exception If the num is < 1. 
+   */ 
+  public void setMaxInstancesInLeaf(int num) throws Exception {
+    if(num<1)
+      throw new Exception("The maximum number of instances in a leaf must " +
+                          "be >=1.");
+    m_MaxInstancesInLeaf = num;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui.
+   */
+  public String maxRelativeLeafRadiusTipText() {
+    return "The maximum relative radius allowed for a leaf node. " +
+    		"Itis relative to the radius of the smallest ball " +
+    		"enclosing all the data points (that were used to " +
+    		"build the tree). This smallest ball would be the " +
+    		"same as the root node's ball, if ContainChildBalls " +
+    		"property is set to false (default).";
+  }
+
+  /** Returns the maximum relative radius of a leaf node. 
+   * It is relative to the radius of the smallest ball enclosing all 
+   * the data points (that were used to build the tree). This smallest
+   * ball would be the same as the root node's ball, if 
+   * ContainChildBalls property is set to false (default).
+   * @return The maximum relative radius allowed for a leaf.
+   */
+  public double getMaxRelativeLeafRadius() {
+    return m_MaxRelLeafRadius;
+  }
+
+  /** Sets the maximum relative radius, allowed for a leaf node. The 
+   * radius is relative to the radius of the smallest ball enclosing all 
+   * the data points (that were used to build the tree). This smallest
+   * ball would be the same as the root node's ball, if 
+   * ContainChildBalls property is set to false (default).
+   * @param radius The maximum relative radius allowed for a leaf.
+   * @throws Exception If radius is < 0.0.
+   */
+  public void setMaxRelativeLeafRadius(double radius) throws Exception {
+	if(radius < 0.0)
+	  throw new Exception("The radius for the leaves should be >= 0.0");
+	m_MaxRelLeafRadius = radius;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui.
+   */
+  public String containChildBallsTipText() {
+    return "Whether to contain fully the child balls.";
+  }
+  
+  /**
+   * Gets whether if a parent ball should completely enclose
+   * its two child balls.
+   * @return true if parent ball is to enclose its child 
+   * balls.
+   */
+  public boolean getContainChildBalls() {
+    return m_FullyContainChildBalls;
+  }
+  
+  /**
+   * Sets whether if a parent ball should completely enclose
+   * its two child balls.
+   * @param containChildBalls Should be tree if parent ball
+   * is to enclose its child balls.
+   */
+  public void setContainChildBalls(boolean containChildBalls) {
+    m_FullyContainChildBalls = containChildBalls;
+  }
+  
+  /**
+   * Sets the instances on which the tree is to be built.
+   * @param inst The instances on which to build the 
+   * ball tree.
+   */
+  public void setInstances(Instances inst) {
+    m_Instances = inst;
+  }
+  
+  /**
+   * Sets the master index array that points to 
+   * instances in m_Instances, so that only this array
+   * is manipulated, and m_Instances is left 
+   * untouched.
+   * @param instList The master index array.
+   */
+  public void setInstanceList(int[] instList) {
+    m_InstList = instList;
+  }
+  
+  /**
+   * Sets the distance function to use to build the 
+   * tree.
+   * @param func The distance function.
+   */
+  public void setEuclideanDistanceFunction(EuclideanDistance func) {
+    m_DistanceFunction = func;
+  }
+  
+  /**
+   * Returns the number of nodes (internal + leaf) 
+   * in the built tree. 
+   * @return The number of nodes in the tree. 
+   */ 
+  public int getNumNodes() {
+    return m_NumNodes;
+  }
+  
+  /**
+   * Returns the number of leaves in the built tree.
+   * @return The number of leaves in the tree.
+   */
+  public int getNumLeaves() {
+    return m_NumLeaves;
+  }
+  
+  /**
+   * Returns the depth of the built tree. 
+   * @return The depth of the tree. 
+   */
+  public int getMaxDepth() {
+    return m_MaxDepth;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.addElement(new Option(
+	"\tSet maximum number of instances in a leaf node\n"
+	+ "\t(default: 40)", 
+	"N", 0, "-N <value>")); 
+    
+    newVector.addElement(new Option(
+	"\tSet internal nodes' radius to the sum \n"
+	+ "\tof the child balls radii. So that it \n" 
+	+ "contains the child balls.", 
+	"R", 0, "-R")); 
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {
+    
+    String optionString = Utils.getOption('N', options);
+    if(optionString.length() !=  0) {
+      setMaxInstancesInLeaf(Integer.parseInt(optionString));
+    }
+    else {
+      setMaxInstancesInLeaf(40);
+    }
+    
+    setContainChildBalls(Utils.getFlag('R', options));
+  }
+
+  /**
+   * Gets the current settings.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    result.add("-N");
+    result.add("" + getMaxInstancesInLeaf());
+    
+    if (getContainChildBalls())
+      result.add("-R");
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BottomUpConstructor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BottomUpConstructor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/BottomUpConstructor.java	(revision 29)
@@ -0,0 +1,378 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BottomUpConstructor.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.ArrayList;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that constructs a ball tree bottom up.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Omohundro1989,
+ *    author = {Stephen M. Omohundro},
+ *    institution = {International Computer Science Institute},
+ *    month = {December},
+ *    number = {TR-89-063},
+ *    title = {Five Balltree Construction Algorithms},
+ *    year = {1989}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;value&gt;
+ *  Set maximum number of instances in a leaf node
+ *  (default: 40)</pre>
+ * 
+ * <pre> -R
+ *  Set internal nodes' radius to the sum 
+ *  of the child balls radii. So that it 
+ * contains the child balls.</pre>
+ * 
+ <!-- options-end --> 
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5987 $
+ */
+public class BottomUpConstructor
+  extends BallTreeConstructor 
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 5864250777657707687L;
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "The class that constructs a ball tree bottom up.";
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Stephen M. Omohundro");
+    result.setValue(Field.YEAR, "1989");
+    result.setValue(Field.TITLE, "Five Balltree Construction Algorithms");
+    result.setValue(Field.MONTH, "December");
+    result.setValue(Field.NUMBER, "TR-89-063");
+    result.setValue(Field.INSTITUTION, "International Computer Science Institute");
+
+    return result;
+  }
+
+  /**
+   * Creates a new instance of BottomUpConstructor.
+   */
+  public BottomUpConstructor() {
+  }
+
+  /**
+   * Builds the ball tree bottom up. 
+   * @return The root node of the tree. 
+   * @throws Exception If there is problem building
+   * the tree.
+   */
+  public BallNode buildTree() throws Exception {
+    ArrayList<TempNode> list = new ArrayList<TempNode>();
+    
+    for(int i=0; i<m_InstList.length; i++) {
+      TempNode n = new TempNode();
+      n.points = new int[1]; n.points[0] = m_InstList[i];
+      n.anchor = m_Instances.instance(m_InstList[i]);
+      n.radius = 0.0;
+      list.add(n);
+    }
+    
+    return mergeNodes(list, 0, m_InstList.length-1, m_InstList);
+  }
+
+  /**
+   * Merges nodes into one top node.
+   *  
+   * @param list List of bottom most nodes (the actual
+   * instances).
+   * @param startIdx The index marking the start of 
+   * the portion of master index array containing 
+   * instances that need to be merged. 
+   * @param endIdx The index marking the end of 
+   * the portion of master index array containing 
+   * instances that need to be merged.
+   * @param instList The master index array.
+   * @return The root node of the tree resulting
+   * from merging of bottom most nodes.
+   * @throws Exception If there is some problem
+   * merging the nodes. 
+   */
+  protected BallNode mergeNodes(ArrayList<TempNode> list, int startIdx, int endIdx, 
+                                int[] instList) throws Exception {
+    double minRadius=Double.POSITIVE_INFINITY, tmpRadius;
+    Instance pivot, minPivot=null; int min1=-1, min2=-1;
+    int [] minInstList=null; int merge=1;
+    TempNode parent;
+    
+    while(list.size() > 1) { //main merging loop
+      System.err.print("merge step: "+merge+++"               \r");
+      minRadius = Double.POSITIVE_INFINITY;
+      min1 = -1; min2 = -1; 
+   
+      for(int i=0; i<list.size(); i++) {
+        TempNode first = (TempNode) list.get(i);
+        for(int j=i+1; j<list.size(); j++) {
+          TempNode second = (TempNode) list.get(j);
+          pivot = calcPivot(first, second, m_Instances);
+          tmpRadius = calcRadius(first, second); 
+          if(tmpRadius < minRadius) {
+            minRadius = tmpRadius; 
+            min1=i; min2=j;
+            minPivot = pivot;
+          }
+        }//end for(j)
+      }//end for(i)
+      parent = new TempNode();
+      parent.left  = (TempNode) list.get(min1);
+      parent.right = (TempNode) list.get(min2);
+      minInstList = new int[parent.left.points.length+parent.right.points.length]; 
+      System.arraycopy(parent.left.points, 0, minInstList, 0, parent.left.points.length);
+      System.arraycopy(parent.right.points, 0, minInstList, parent.left.points.length, 
+          	       parent.right.points.length);
+      parent.points = minInstList;
+      parent.anchor = minPivot;
+      parent.radius = BallNode.calcRadius(parent.points, m_Instances, minPivot, m_DistanceFunction);
+      list.remove(min1); list.remove(min2-1);
+      list.add(parent);
+    }//end while
+    System.err.println("");
+    TempNode tmpRoot = (TempNode)list.get(0);
+    
+    if(m_InstList.length != tmpRoot.points.length)
+      throw new Exception("Root nodes instance list is of irregular length. " +
+                          "Please check code.");
+    System.arraycopy(tmpRoot.points, 0, m_InstList, 0, tmpRoot.points.length);
+
+    m_NumNodes = m_MaxDepth = m_NumLeaves = 0;
+    tmpRadius = BallNode.calcRadius(instList, m_Instances, tmpRoot.anchor, m_DistanceFunction);    
+    BallNode node = makeBallTree(tmpRoot, startIdx, endIdx, instList, 0, tmpRadius); 
+    
+    return node;    
+  }
+  
+  /**
+   * Makes ball tree nodes of temp nodes that were used
+   * in the merging process. 
+   * @param node The temp root node.
+   * @param startidx The index marking the start of the 
+   * portion of master index array containing instances 
+   * to be merged. 
+   * @param endidx The index marking the end of the 
+   * portion of master index array containing instances 
+   * to be merged. 
+   * @param instList The master index array.
+   * @param depth The depth of the provided temp node.
+   * @param rootRadius The smallest ball enclosing all
+   * data points.
+   * @return The proper top BallTreeNode. 
+   * @throws Exception If there is some problem.
+   */
+  protected BallNode makeBallTree(TempNode node, int startidx, int endidx, 
+                                int[] instList, int depth, final double rootRadius) throws Exception {
+    BallNode ball=null;
+    Instance pivot;
+    
+    if(m_MaxDepth < depth)
+      m_MaxDepth = depth;
+    
+    if(node.points.length > m_MaxInstancesInLeaf && 
+       (rootRadius==0 ? false : node.radius/rootRadius >= m_MaxRelLeafRadius) && 
+       node.left!=null && node.right!=null) { //make an internal node
+      ball = new BallNode(
+      startidx, endidx, m_NumNodes, 
+      (pivot=BallNode.calcCentroidPivot(startidx, endidx, instList, m_Instances)),
+      BallNode.calcRadius(startidx, endidx, instList, m_Instances, pivot, 
+                          m_DistanceFunction)
+      );
+      m_NumNodes += 1;
+      ball.m_Left = makeBallTree(node.left, startidx, startidx+node.left.points.length-1, instList, depth+1, rootRadius);
+      ball.m_Right= makeBallTree(node.right, startidx+node.left.points.length, endidx, instList, depth+1, rootRadius);
+    }
+    else { //make a leaf node
+      ball = new BallNode(startidx, endidx, m_NumNodes,       
+      (pivot=BallNode.calcCentroidPivot(startidx, endidx, instList, m_Instances)),
+      BallNode.calcRadius(startidx, endidx, instList, m_Instances, pivot, 
+                          m_DistanceFunction)
+                         );
+      m_NumNodes += 1;
+      m_NumLeaves++;
+    }
+    return ball;
+  }
+  
+  /**
+   * Adds an instance to the ball tree. 
+   * @param node The root node of the tree.
+   * @param inst The instance to add to the tree.
+   * @return The new master index array after adding the 
+   * instance. 
+   * @throws Exception Always as BottomUpConstructor
+   * does not allow addition of instances after batch 
+   * construction. 
+   */
+  
+  public int[] addInstance(BallNode node, Instance inst) throws Exception {
+    throw new Exception("BottomUpConstruction method does not allow addition " +
+                        "of new Instances.");
+  }
+
+  /**
+   * Calculates the centroid pivot of a node based on its
+   * two child nodes. 
+   * @param node1 The first child node.
+   * @param node2 The second child node.
+   * @param insts The instance on which the tree is to be
+   * built.
+   * @return The centre/pivot of the node. 
+   * @throws Exception If there is some problem calculating 
+   * the centre/pivot of the node.
+   */
+  public Instance calcPivot(TempNode node1, TempNode node2, Instances insts) 
+  throws Exception {
+    int classIdx = m_Instances.classIndex();
+    double[] attrVals = new double[insts.numAttributes()];
+    Instance temp;
+    double anchr1Ratio = (double)node1.points.length / 
+    (node1.points.length+node2.points.length),
+    anchr2Ratio = (double)node2.points.length / 
+    (node1.points.length+node2.points.length);                         
+    for(int k=0; k<node1.anchor.numValues(); k++) {
+      if(node1.anchor.index(k)==classIdx)
+	continue;
+      attrVals[k] += node1.anchor.valueSparse(k)*anchr1Ratio;
+    }
+    for(int k=0; k<node2.anchor.numValues(); k++) {
+      if(node2.anchor.index(k)==classIdx)
+	continue;
+      attrVals[k] += node2.anchor.valueSparse(k)*anchr2Ratio;
+    }
+    temp = new DenseInstance(1.0, attrVals);
+    return temp;
+  }
+  
+  /**
+   * Calculates the radius of a node based on its two
+   * child nodes. 
+   * @param n1 The first child node. 
+   * @param n2 The second child node.
+   * @return The calculated radius of the the node. 
+   * @throws Exception If there is some problem 
+   * in calculating the radius. 
+   */
+  public double calcRadius(TempNode n1, TempNode n2) throws Exception {
+    Instance a1 = n1.anchor, a2 = n2.anchor;
+    double radius = n1.radius + m_DistanceFunction.distance(a1, a2) + n2.radius;
+    return radius/2;
+  }
+
+  /** 
+   * Temp class to represent either a leaf node or an internal node. Should only 
+   * have two children (could be the case one child is an instance and the 
+   * other another node).
+   *
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5987 $
+   */
+  protected class TempNode
+    implements RevisionHandler {
+    
+    /** The centre/pivot of the node. */
+    Instance anchor;
+    /** The radius of the node. */
+    double radius;
+    /** Indices of the points in the node. */
+    int [] points;
+    /** The node's left child. */
+    TempNode left = null;
+    /** The node's right child. */
+    TempNode right = null;
+    
+    /** 
+     * Prints the node.
+     * @return The node as a string.
+     */
+    public String toString() {
+      StringBuffer bf = new StringBuffer();
+      bf.append("p: ");
+      for(int i=0; i<points.length; i++) 
+        if(i!=0)
+          bf.append(", "+points[i]);
+        else
+          bf.append(""+points[i]);
+      return bf.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianDistanceFromArbitraryPoint.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianDistanceFromArbitraryPoint.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianDistanceFromArbitraryPoint.java	(revision 29)
@@ -0,0 +1,397 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MedianDistanceFromArbitraryPoint.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class that splits a BallNode of a ball tree using Uhlmann's described method.<br/>
+ * <br/>
+ * For information see:<br/>
+ * <br/>
+ * Jeffrey K. Uhlmann (1991). Satisfying general proximity/similarity queries with metric trees. Information Processing Letters. 40(4):175-179.<br/>
+ * <br/>
+ * Ashraf Masood Kibriya (2007). Fast Algorithms for Nearest Neighbour Search. Hamilton, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Uhlmann1991,
+ *    author = {Jeffrey K. Uhlmann},
+ *    journal = {Information Processing Letters},
+ *    month = {November},
+ *    number = {4},
+ *    pages = {175-179},
+ *    title = {Satisfying general proximity/similarity queries with metric trees},
+ *    volume = {40},
+ *    year = {1991}
+ * }
+ * 
+ * &#64;mastersthesis{Kibriya2007,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Ashraf Masood Kibriya},
+ *    school = {Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato},
+ *    title = {Fast Algorithms for Nearest Neighbour Search},
+ *    year = {2007}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  The seed value for the random number generator.
+ *  (default: 17)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class MedianDistanceFromArbitraryPoint
+  extends BallSplitter 
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 5617378551363700558L;
+  
+  /** Seed for random number generator. */
+  protected int m_RandSeed = 17;
+
+  /** 
+   * Random number generator for selecting
+   * an abitrary (random) point. 
+   */
+  protected Random m_Rand;
+  
+  /** Constructor. */
+  public MedianDistanceFromArbitraryPoint() {
+  }
+  
+  /**
+   * Constructor. 
+   * @param instList The master index array.
+   * @param insts The instances on which the tree
+   * is (or is to be) built.
+   * @param e The Euclidean distance function to 
+   * use for splitting.
+   */
+  public MedianDistanceFromArbitraryPoint(int[] instList, Instances insts, EuclideanDistance e) {
+    super(instList, insts, e);
+  }
+  
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class that splits a BallNode of a ball tree using Uhlmann's "
+      + "described method.\n\n"
+      + "For information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Jeffrey K. Uhlmann");
+    result.setValue(Field.TITLE, "Satisfying general proximity/similarity queries with metric trees");
+    result.setValue(Field.JOURNAL, "Information Processing Letters");
+    result.setValue(Field.MONTH, "November");
+    result.setValue(Field.YEAR, "1991");
+    result.setValue(Field.NUMBER, "4");
+    result.setValue(Field.VOLUME, "40");
+    result.setValue(Field.PAGES, "175-179");
+
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Ashraf Masood Kibriya");
+    additional.setValue(Field.TITLE, "Fast Algorithms for Nearest Neighbour Search");
+    additional.setValue(Field.YEAR, "2007");
+    additional.setValue(Field.SCHOOL, "Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato");
+    additional.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> result = new Vector<Option>();
+    
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement((Option)enm.nextElement());
+      
+    result.addElement(new Option(
+        "\tThe seed value for the random number generator.\n"
+        + "\t(default: 17)",
+        "S", 1, "-S <num>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  The seed value for the random number generator.
+   *  (default: 17)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() > 0)
+      setRandomSeed(Integer.parseInt(tmpStr));
+    else
+      setRandomSeed(17);
+  }
+
+  /**
+   * Gets the current settings of the object.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result  = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-S");
+    result.add("" + getRandomSeed());
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Sets the seed for random number generator.
+   * @param seed The seed value to set.
+   */
+  public void setRandomSeed(int seed) {
+    m_RandSeed = seed;
+  }
+  
+  /**
+   * Returns the seed value of random 
+   * number generator.
+   * @return The random seed currently in use.
+   */
+  public int getRandomSeed() {
+    return m_RandSeed;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui.
+   */
+  public String randomSeedTipText() {
+    return "The seed value for the random number generator.";
+  }
+  
+  /** 
+   * Splits a ball into two. 
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(BallNode node, int numNodesCreated) throws Exception {
+    correctlyInitialized();
+
+    m_Rand = new Random(m_RandSeed);
+    
+    int ridx = node.m_Start+m_Rand.nextInt(node.m_NumInstances);
+    Instance randomInst = (Instance)
+                            m_Instances.instance( m_Instlist[ridx] ).copy();
+    double [] distList = new double[node.m_NumInstances-1];
+    Instance temp;
+    for(int i=node.m_Start, j=0; i<node.m_End; i++, j++) {
+      temp = m_Instances.instance( m_Instlist[i] );
+      distList[j] = m_DistanceFunction.distance(randomInst, temp, 
+	  Double.POSITIVE_INFINITY);
+    }
+    
+    int medianIdx = select(distList, m_Instlist, 0, distList.length-1, 
+                           node.m_Start, (node.m_End-node.m_Start)/2+1) + 
+                    node.m_Start;
+    
+    Instance pivot;
+    node.m_Left = new BallNode(node.m_Start, medianIdx, numNodesCreated+1,
+                              (pivot=BallNode.calcCentroidPivot(node.m_Start,
+                                                       medianIdx, m_Instlist, 
+                                                       m_Instances)), 
+                              BallNode.calcRadius(node.m_Start, medianIdx, 
+                                                  m_Instlist, m_Instances, 
+                                                  pivot, m_DistanceFunction)
+                              );
+    
+    node.m_Right = new BallNode(medianIdx+1, node.m_End, numNodesCreated+2,
+                              (pivot=BallNode.calcCentroidPivot(medianIdx+1,
+                                                       node.m_End, m_Instlist, 
+                                                       m_Instances)), 
+                              BallNode.calcRadius(medianIdx+1, node.m_End, 
+                                                  m_Instlist, m_Instances, 
+                                                  pivot, m_DistanceFunction)
+                              );
+  }
+  
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param array The array of distances of the points to the
+   * arbitrarily selected point.
+   * @param index The master index array containing indices of the 
+   * instances.
+   * @param l The relative begining index of the portion of master 
+   * index array that should be partitioned. 
+   * @param r The relative end index of the portion of master index 
+   * array that should be partitioned.
+   * @param indexStart The absolute begining index of the portion 
+   * of master index array that should be partitioned. 
+   * @return the index of the middle element (in the master 
+   * index array, i.e. index of the index of middle element).
+   */
+  protected int partition(double[] array, int[] index, int l, int r, 
+                        final int indexStart) {
+    
+    double pivot = array[(l + r) / 2];
+    int help;
+
+    while (l < r) {
+      while ((array[l] < pivot) && (l < r)) {
+        l++;
+      }
+      while ((array[r] > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[indexStart+l];
+        index[indexStart+l] = index[indexStart+r];
+        index[indexStart+r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (array[r] > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+  
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param array Array containing the distances of points from
+   * the arbitrarily selected.
+   * @param indices The master index array containing indices of 
+   * the instances.
+   * @param left The relative begining index of the portion of the 
+   * master index array in which to find the kth-smallest element.
+   * @param right The relative end index of the portion of the 
+   * master index array in which to find the kth-smallest element.
+   * @param indexStart The absolute begining index of the portion 
+   * of the master index array in which to find the kth-smallest 
+   * element. 
+   * @param k The value of k
+   * @return The index of the kth-smallest element
+   */
+  protected int select(double[] array, int[] indices, 
+                            int left, int right, final int indexStart, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(array, indices, left, right, indexStart);
+      if ((middle - left + 1) >= k) {
+        return select(array, indices, left, middle, indexStart, k);
+      } else {
+        return select(array, indices, middle + 1, right, 
+                      indexStart, k - (middle - left + 1));
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianOfWidestDimension.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianOfWidestDimension.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MedianOfWidestDimension.java	(revision 29)
@@ -0,0 +1,390 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MedianOfWidestDimension.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Class that splits a BallNode of a ball tree based on the median value of the widest dimension of the points in the ball. It essentially implements Omohundro's  KD construction algorithm.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Omohundro1989,
+ *    author = {Stephen M. Omohundro},
+ *    institution = {International Computer Science Institute},
+ *    month = {December},
+ *    number = {TR-89-063},
+ *    title = {Five Balltree Construction Algorithms},
+ *    year = {1989}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Normalize dimensions' widths.</pre>
+ * 
+ <!-- options-end --> 
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class MedianOfWidestDimension
+  extends BallSplitter 
+  implements OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 3054842574468790421L;
+  
+  /** 
+   * Should we normalize the widths(ranges) of the dimensions (attributes) 
+   * before selecting the widest one. 
+   */
+  protected boolean m_NormalizeDimWidths = true;
+  
+  /**
+   * Constructor.
+   */
+  public MedianOfWidestDimension() {
+  }
+  
+  /**
+   * Constructor. 
+   * @param instList The master index array.
+   * @param insts The instances on which the tree
+   * is (or is to be) built.
+   * @param e The Euclidean distance function to 
+   * use for splitting.
+   */
+  public MedianOfWidestDimension(int[] instList, Instances insts, 
+                                 EuclideanDistance e) {
+    super(instList, insts, e);
+  }
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Class that splits a BallNode of a ball tree based on the "
+      + "median value of the widest dimension of the points in the ball. "
+      + "It essentially implements Omohundro's  KD construction algorithm.";
+  }
+
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Stephen M. Omohundro");
+    result.setValue(Field.YEAR, "1989");
+    result.setValue(Field.TITLE, "Five Balltree Construction Algorithms");
+    result.setValue(Field.MONTH, "December");
+    result.setValue(Field.NUMBER, "TR-89-063");
+    result.setValue(Field.INSTITUTION, "International Computer Science Institute");
+
+    return result;
+  }
+  
+  /** 
+   * Splits a ball into two. 
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(BallNode node, int numNodesCreated) throws Exception {
+    correctlyInitialized();
+    //int[] instList = getNodesInstsList(node); 
+    double[][] ranges = m_DistanceFunction.initializeRanges(m_Instlist, 
+                                                            node.m_Start, 
+                                                            node.m_End);
+    
+    int splitAttrib = widestDim(ranges, m_DistanceFunction.getRanges());
+    
+    //In this case median is defined to be either the middle value (in case of
+    //odd number of values) or the left of the two middle values (in case of 
+    //even number of values).
+    int medianIdxIdx = node.m_Start + (node.m_End-node.m_Start)/2;
+    //the following finds the median and also re-arranges the array so all 
+    //elements to the left are < median and those to the right are > median.
+    int medianIdx = select(splitAttrib, m_Instlist, node.m_Start, node.m_End, (node.m_End-node.m_Start)/2+1); //Utils.select(array, indices, node.m_Start, node.m_End, (node.m_End-node.m_Start)/2+1); //(int) (node.m_NumInstances/2D+0.5D);
+
+    Instance pivot;
+    
+    node.m_SplitAttrib = splitAttrib;
+    node.m_SplitVal = m_Instances.instance(m_Instlist[medianIdx])
+                                            .value(splitAttrib);
+
+    node.m_Left = new BallNode(node.m_Start, medianIdxIdx, numNodesCreated+1,
+                              (pivot=BallNode.calcCentroidPivot(node.m_Start,
+                                                       medianIdxIdx, m_Instlist, 
+                                                       m_Instances)), 
+                              BallNode.calcRadius(node.m_Start, medianIdxIdx, 
+                                                  m_Instlist, m_Instances, 
+                                                  pivot, m_DistanceFunction)
+                              );
+    node.m_Right = new BallNode(medianIdxIdx+1, node.m_End, numNodesCreated+2,
+                              (pivot=BallNode.calcCentroidPivot(medianIdxIdx+1,
+                                                       node.m_End, m_Instlist, 
+                                                       m_Instances)), 
+                              BallNode.calcRadius(medianIdxIdx+1, node.m_End, 
+                                                  m_Instlist, m_Instances, 
+                                                  pivot, m_DistanceFunction)
+                              );
+  }
+
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param attIdx The attribution/dimension based on which the 
+   * instances should be partitioned.
+   * @param index The master index array containing indices of the 
+   * instances.
+   * @param l The begining index of the portion of master index 
+   * array that should be partitioned. 
+   * @param r The end index of the portion of master index array 
+   * that should be partitioned.
+   * @return the index of the middle element (in the master 
+   * index array, i.e. index of the index of middle element).
+   */
+  protected int partition(int attIdx, int[] index, int l, int r) {
+    
+    double pivot = m_Instances.instance(index[(l + r) / 2]).value(attIdx);
+    int help;
+
+    while (l < r) {
+      while ((m_Instances.instance(index[l]).value(attIdx) < pivot) && (l < r)) {
+        l++;
+      }
+      while ((m_Instances.instance(index[r]).value(attIdx) > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[l];
+        index[l] = index[r];
+        index[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (m_Instances.instance(index[r]).value(attIdx) > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param attIdx The dimension/attribute of the instances in 
+   * which to find the kth-smallest element.
+   * @param indices The master index array containing indices of 
+   * the instances.
+   * @param left The begining index of the portion of the master 
+   * index array in which to find the kth-smallest element.
+   * @param right The end index of the portion of the master index 
+   * array in which to find the kth-smallest element.
+   * @param k The value of k
+   * @return The index of the kth-smallest element
+   */
+  public int select(int attIdx, int[] indices, 
+                            int left, int right, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(attIdx, indices, left, right);
+      if ((middle - left + 1) >= k) {
+        return select(attIdx, indices, left, middle, k);
+      } else {
+        return select(attIdx, indices, middle + 1, right, k - (middle - left + 1));
+      }
+    }
+  }
+  
+  /**
+   * Returns the widest dimension. The width of each 
+   * dimension (for the points inside the node) is 
+   * normalized, if m_NormalizeNodeWidth is set to 
+   * true.
+   * @param nodeRanges The attributes' range of the 
+   * points inside the node that is to be split.
+   * @param universe The attributes' range for the
+   * whole point-space.
+   * @return The index of the attribute/dimension
+   * in which the points of the node have widest
+   * spread.
+   */
+  protected int widestDim(double[][] nodeRanges, double[][] universe) {
+    final int classIdx = m_Instances.classIndex();
+    double widest = 0.0;
+    int w = -1;
+    if (m_NormalizeDimWidths) {
+	for (int i = 0; i < nodeRanges.length; i++) {
+	  double newWidest = nodeRanges[i][m_DistanceFunction.R_WIDTH] / 
+	  		     universe[i][m_DistanceFunction.R_WIDTH];
+	  if (newWidest > widest) {
+	    if(i == classIdx) continue;
+	    widest = newWidest;
+	    w = i;
+	  }
+	}
+    }
+    else {
+	for (int i = 0; i < nodeRanges.length; i++) {
+	  if (nodeRanges[i][m_DistanceFunction.R_WIDTH] > widest) {
+	    if(i == classIdx) continue;
+	    widest = nodeRanges[i][m_DistanceFunction.R_WIDTH];
+	    w = i;
+	  }
+	}
+    }
+    return w;
+  }  
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String normalizeDimWidthsTipText() {
+    return 
+        "Whether to normalize the widths(ranges) of the dimensions "
+      + "(attributes) before selecting the widest one.";
+  }
+  
+  /** 
+   * Should we normalize the widths(ranges) of the dimensions (attributes) 
+   * before selecting the widest one. 
+   * @param normalize Should be true if the widths are to be
+   * normalized. 
+   */
+  public void setNormalizeDimWidths(boolean normalize) {
+    m_NormalizeDimWidths = normalize;
+  }
+  
+  /** 
+   * Whether we are normalizing the widths(ranges) of the dimensions (attributes) 
+   * or not.
+   * @return true if widths are being normalized.
+   */
+  public boolean getNormalizeDimWidths() {
+    return m_NormalizeDimWidths;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.addElement(new Option(
+	"\tNormalize dimensions' widths.",
+	"N", 0, "-N"));
+    
+    return newVector.elements();
+  }
+
+  /** 
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Normalize dimensions' widths.</pre>
+   * 
+   <!-- options-end --> 
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {
+    
+    setNormalizeDimWidths(Utils.getFlag('N', options));
+  }
+
+  /** 
+   * Gets the current settings.
+   * @return An array of strings suitable for passing to 
+   * setOptions or to be displayed by a 
+   * GenericObjectEditor.
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+
+    if (getNormalizeDimWidths())
+      result.add("-N");
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MiddleOutConstructor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MiddleOutConstructor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/MiddleOutConstructor.java	(revision 29)
@@ -0,0 +1,1251 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MiddleOutConstructor.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Randomizable;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import java.io.Serializable;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that builds a BallTree middle out.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Andrew W. Moore: The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data. In: UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence, San Francisco, CA, USA, 397-405, 2000.<br/>
+ * <br/>
+ * Ashraf Masood Kibriya (2007). Fast Algorithms for Nearest Neighbour Search. Hamilton, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Moore2000,
+ *    address = {San Francisco, CA, USA},
+ *    author = {Andrew W. Moore},
+ *    booktitle = {UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence},
+ *    pages = {397-405},
+ *    publisher = {Morgan Kaufmann Publishers Inc.},
+ *    title = {The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data},
+ *    year = {2000}
+ * }
+ * 
+ * &#64;mastersthesis{Kibriya2007,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Ashraf Masood Kibriya},
+ *    school = {Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato},
+ *    title = {Fast Algorithms for Nearest Neighbour Search},
+ *    year = {2007}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  The seed for the random number generator used
+ *  in selecting random anchor.
+ * (default: 1)</pre>
+ * 
+ * <pre> -R
+ *  Use randomly chosen initial anchors.</pre>
+ * 
+ <!-- options-end --> 
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5987 $
+ */
+public class MiddleOutConstructor
+  extends BallTreeConstructor
+  implements Randomizable, TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -8523314263062524462L;
+
+  /** Seed form random number generator. */
+  protected int m_RSeed = 1;
+	
+  /** 
+   * The random number generator for selecting 
+   * the first anchor point randomly 
+   * (if selecting randomly).
+   */
+  protected Random rand = new Random(m_RSeed);
+ 
+  /**
+   * The radius of the smallest ball enclosing all the data points.
+   */
+  private double rootRadius = -1;
+  
+  /** 
+   * True if the initial anchor is chosen randomly. False if it is the furthest
+   * point from the mean/centroid.
+   */
+  protected boolean m_RandomInitialAnchor = true;
+
+  /**
+   * Creates a new instance of MiddleOutConstructor.
+   */
+  public MiddleOutConstructor() {
+  }
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class that builds a BallTree middle out.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+    
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Andrew W. Moore");
+    result.setValue(Field.TITLE, "The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.BOOKTITLE, "UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence");
+    result.setValue(Field.PAGES, "397-405");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers Inc.");
+    result.setValue(Field.ADDRESS, "San Francisco, CA, USA");
+
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Ashraf Masood Kibriya");
+    additional.setValue(Field.TITLE, "Fast Algorithms for Nearest Neighbour Search");
+    additional.setValue(Field.YEAR, "2007");
+    additional.setValue(Field.SCHOOL, "Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato");
+    additional.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+    
+    return result;
+  }
+
+  /**
+   * Builds a ball tree middle out. 
+   * @return The root node of the tree. 
+   * @throws Exception If there is problem building
+   * the tree.
+   */
+  public BallNode buildTree() throws Exception {
+    m_NumNodes = m_MaxDepth = m_NumLeaves = 0;
+    if(rootRadius == -1) {
+      rootRadius = BallNode.calcRadius(m_InstList, m_Instances, 
+                		BallNode.calcCentroidPivot(m_InstList, m_Instances),
+                		m_DistanceFunction);
+    }
+    BallNode root = buildTreeMiddleOut(0, m_Instances.numInstances()-1);
+    return root;
+  }
+
+  /** 
+   * Builds a ball tree middle out from the 
+   * portion of the master index array given
+   * by supplied start and end index.
+   * @param startIdx The start of the portion
+   * in master index array.
+   * @param endIdx the end of the portion in 
+   * master index array.
+   * @return The root node of the built tree.
+   * @throws Exception If there is some 
+   * problem building the tree. 
+   */
+  protected BallNode buildTreeMiddleOut(int startIdx, int endIdx) 
+    throws Exception {
+	
+    Instance pivot;
+    double radius;
+    Vector<TempNode> anchors;
+    int numInsts = endIdx - startIdx + 1;
+    int numAnchors = (int) Math.round(Math.sqrt(numInsts));
+    
+    //create anchor's hierarchy
+    if (numAnchors > 1) {
+      pivot = BallNode.calcCentroidPivot(startIdx, endIdx, m_InstList,m_Instances);
+      radius = BallNode.calcRadius(startIdx, endIdx, m_InstList, m_Instances, 
+    	  						   pivot, m_DistanceFunction);      
+      if(numInsts <= m_MaxInstancesInLeaf || 
+    	 (rootRadius==0 ? true : radius/rootRadius < m_MaxRelLeafRadius)) { //just make a leaf don't make anchors hierarchy 
+		BallNode node = new BallNode(startIdx, endIdx, m_NumNodes,pivot, radius);
+		return node;
+	  }
+      anchors = new Vector<TempNode>(numAnchors);
+      createAnchorsHierarchy(anchors, numAnchors, startIdx, endIdx);
+
+      BallNode node = mergeNodes(anchors, startIdx, endIdx);
+      
+      buildLeavesMiddleOut(node);
+      
+      return node;
+    }// end anchors hierarchy
+    else {
+      BallNode node = new BallNode(startIdx, endIdx, m_NumNodes, 
+                      (pivot=BallNode.calcCentroidPivot(startIdx, endIdx, 
+                                                      m_InstList, m_Instances)), 
+                      BallNode.calcRadius(startIdx, endIdx, m_InstList, 
+                                          m_Instances, pivot, 
+                                          m_DistanceFunction)
+                         );
+      return node;
+    }        
+  }
+  
+  /**
+   * Creates an anchors hierarchy from a portion
+   * of master index array.
+   * 
+   * @param anchors The vector for putting the anchors
+   * into. 
+   * @param numAnchors The number of anchors to create.
+   * @param startIdx The start of the portion of master
+   * index array.
+   * @param endIdx The end of the portion of master 
+   * index array.
+   * @throws Exception If there is some problem in creating 
+   * the hierarchy.
+   */
+  protected void createAnchorsHierarchy(Vector<TempNode> anchors, final int numAnchors, 
+      final int startIdx, final int endIdx) 
+    throws Exception {
+    
+    TempNode anchr1 = m_RandomInitialAnchor ? 
+            getRandomAnchor(startIdx, endIdx) : 
+            getFurthestFromMeanAnchor(startIdx, endIdx);
+	              
+    TempNode amax = anchr1; //double maxradius = anchr1.radius;
+    TempNode newAnchor;
+    Vector<double[]> anchorDistances = new Vector<double[]>(numAnchors-1);
+    anchors.add(anchr1);
+
+    //creating anchors
+    while(anchors.size() < numAnchors) {
+      //create new anchor
+      newAnchor = new TempNode();
+      newAnchor.points = new MyIdxList();        
+      Instance newpivot = m_Instances.instance(((ListNode)amax.points.getFirst()).idx);
+      newAnchor.anchor = newpivot;
+      newAnchor.idx = ((ListNode)amax.points.getFirst()).idx;
+
+      setInterAnchorDistances(anchors, newAnchor, anchorDistances);
+      if(stealPoints(newAnchor, anchors, anchorDistances)) //if points stolen
+    	newAnchor.radius = ((ListNode)newAnchor.points.getFirst()).distance;
+      else
+    	newAnchor.radius = 0.0;
+      anchors.add(newAnchor);
+
+      //find new amax        
+      amax = (TempNode)anchors.elementAt(0);
+      for(int i=1; i<anchors.size(); i++) {
+        newAnchor = (TempNode)anchors.elementAt(i);
+        if(newAnchor.radius > amax.radius)
+          amax = newAnchor;
+      }//end for
+    }//end while
+  }
+  
+  /**
+   * Applies the middle out build procedure to 
+   * the leaves of the tree. The leaf nodes 
+   * should be the ones that were created by 
+   * createAnchorsHierarchy(). The process
+   * continues recursively for the leaves 
+   * created for each leaf of the given tree 
+   * until for some leaf node <= 
+   * m_MaxInstancesInLeaf instances remain
+   * in the leaf.
+   * 
+   * @param node The root of the tree.
+   * @throws Exception If there is some problem
+   * in building the tree leaves.
+   */
+  protected void buildLeavesMiddleOut(BallNode node) throws Exception {
+    if(node.m_Left!=null && node.m_Right!=null) { //if an internal node
+      buildLeavesMiddleOut(node.m_Left);
+      buildLeavesMiddleOut(node.m_Right);
+    }
+    else if(node.m_Left!=null || node.m_Right!=null) {
+      throw new Exception("Invalid leaf assignment. Please check code");
+    }
+    else { //if node is a leaf
+      BallNode n2 = buildTreeMiddleOut(node.m_Start, node.m_End);
+      if(n2.m_Left!=null && n2.m_Right!=null) {
+        node.m_Left = n2.m_Left;
+        node.m_Right = n2.m_Right;
+        buildLeavesMiddleOut(node); 
+        //the stopping condition in buildTreeMiddleOut will stop the recursion,
+        //where it won't split a node at all, and we won't recurse here.
+      }
+      else if(n2.m_Left!=null || n2.m_Right!=null)
+        throw new Exception("Invalid leaf assignment. Please check code");
+    }
+  }
+
+  /**
+   * Merges nodes created by createAnchorsHierarchy()
+   * into one top node.
+   * 
+   * @param list List of anchor nodes.
+   * @param startIdx The start of the portion of 
+   * master index array containing these anchor 
+   * nodes. 
+   * @param endIdx The end of the portion of master 
+   * index array containing these anchor nodes. 
+   * @return The top/root node after merging 
+   * the given anchor nodes.
+   * @throws Exception IF there is some problem in
+   * merging.
+   */
+  protected BallNode mergeNodes(Vector<TempNode> list, int startIdx, int endIdx)
+    throws Exception {
+    
+    for(int i=0; i<list.size(); i++) {
+      TempNode n = (TempNode) list.get(i);
+      n.anchor = calcPivot(n.points, new MyIdxList(), m_Instances);
+      n.radius = calcRadius(n.points, new MyIdxList(), n.anchor, m_Instances);
+    }
+    double minRadius, tmpRadius; //tmpVolume, minVolume;
+    Instance pivot, minPivot=null;
+    TempNode parent; int min1=-1, min2=-1;    
+    
+    while(list.size() > 1) { //main merging loop
+      minRadius=Double.POSITIVE_INFINITY;      
+      
+      for(int i=0; i<list.size(); i++) {
+        TempNode first = (TempNode) list.get(i);
+        for(int j=i+1; j<list.size(); j++) {
+          TempNode second = (TempNode) list.get(j);
+          pivot = calcPivot(first, second, m_Instances); 
+          tmpRadius = calcRadius(first, second); //calcRadius(first.points, second.points, pivot, m_Instances);    
+          if(tmpRadius < minRadius) { //(tmpVolume < minVolume) {
+            minRadius = tmpRadius; //minVolume = tmpVolume; 
+            minPivot = pivot; 
+            min1=i; min2=j; 
+            //minInstList = tmpInstList;
+          }
+        }//end for(j)
+      }//end for(i)
+      parent = new TempNode();
+      parent.left  = (TempNode) list.get(min1);
+      parent.right = (TempNode) list.get(min2);
+      parent.anchor = minPivot;
+      parent.radius = calcRadius(parent.left.points, parent.right.points, minPivot, m_Instances); //minRadius;
+      parent.points = parent.left.points.append(parent.left.points, parent.right.points);
+      list.remove(min1); list.remove(min2-1);
+      list.add(parent);
+    }//end while
+    TempNode tmpRoot = (TempNode)list.get(list.size()-1);
+    
+    if((endIdx-startIdx+1)!= tmpRoot.points.length()) {
+      throw new Exception("Root nodes instance list is of irregular length. " +
+                          "Please check code. Length should " +
+                          "be: " + (endIdx-startIdx+1) + 
+                          " whereas it is found to be: "+tmpRoot.points.length());
+    }
+    for(int i=0; i<tmpRoot.points.length(); i++) {
+      m_InstList[startIdx+i] = ((ListNode)tmpRoot.points.get(i)).idx;
+    }
+    
+    BallNode node = makeBallTreeNodes(tmpRoot, startIdx, endIdx, 0);
+    
+    return node;    
+  }
+  
+  /**
+   * Makes BallTreeNodes out of TempNodes.
+   *  
+   * @param node The root TempNode
+   * @param startidx The start of the portion of 
+   * master index array the TempNodes 
+   * are made from. 
+   * @param endidx The end of the portion of 
+   * master index array the TempNodes are 
+   * made from. 
+   * @param depth The depth in the tree where 
+   * this root TempNode is made (needed when 
+   * leaves of a tree deeper down are built 
+   * middle out).
+   * @return The root BallTreeNode.
+   */
+  protected BallNode makeBallTreeNodes(TempNode node, int startidx, 
+      int endidx, int depth) {
+    BallNode ball=null;
+    
+    if(node.left!=null && node.right!=null) { //make an internal node
+      ball = new BallNode(
+      startidx, endidx, m_NumNodes, 
+      node.anchor,
+      node.radius
+      );
+      m_NumNodes += 1;
+      ball.m_Left = makeBallTreeNodes(node.left, startidx, startidx+node.left.points.length()-1, depth+1);
+      ball.m_Right= makeBallTreeNodes(node.right, startidx+node.left.points.length(), endidx, depth+1);
+      m_MaxDepth++;
+    }
+    else { //make a leaf node
+      ball = new BallNode(startidx, endidx, m_NumNodes,       
+      node.anchor, 
+      node.radius 
+                         );
+      m_NumNodes += 1;      
+      m_NumLeaves += 1;
+    }
+    return ball;
+  }
+    
+  /**
+   * Returns an anchor point which is furthest from the
+   * mean point for a given set of points (instances) 
+   * (The anchor instance is chosen from the given
+   * set of points).
+   * 
+   * @param startIdx The start index of the points
+   * for which anchor point is required.
+   * @param endIdx The end index of the points for
+   * which anchor point is required.
+   * @return The furthest point/instance from the mean 
+   * of given set of points.
+   */
+  protected TempNode getFurthestFromMeanAnchor(int startIdx, int endIdx) {
+    TempNode anchor = new TempNode();
+    Instance centroid = BallNode.calcCentroidPivot(startIdx, endIdx, m_InstList, 
+                                                   m_Instances);
+    Instance temp;
+    double tmpr;
+    anchor.radius = Double.NEGATIVE_INFINITY;
+    for(int i=startIdx; i<=endIdx; i++) {
+      temp = m_Instances.instance(m_InstList[i]);
+      tmpr = m_DistanceFunction.distance(centroid, temp);
+      if(tmpr > anchor.radius) {
+        anchor.idx = m_InstList[i];
+        anchor.anchor = temp;
+        anchor.radius = tmpr;
+      }
+    }
+    
+    setPoints(anchor, startIdx, endIdx, m_InstList);
+    return anchor;
+  }
+  
+  /** 
+   * Returns a random anchor point/instance from a 
+   * given set of points/instances.
+   * 
+   * @param startIdx The start index of the points
+   * for which anchor is required.
+   * @param endIdx The end index of the points for
+   * which anchor is required.
+   * @return The random anchor point/instance
+   * for the given set of 
+   */
+  protected TempNode getRandomAnchor(int startIdx, int endIdx) {
+    TempNode anchr1 = new TempNode();
+    anchr1.idx = m_InstList[startIdx+rand.nextInt((endIdx-startIdx+1))];
+    anchr1.anchor = m_Instances.instance(anchr1.idx);
+    setPoints(anchr1, startIdx, endIdx, m_InstList);
+    anchr1.radius = ((ListNode)anchr1.points.getFirst()).distance;
+    
+    return anchr1;
+  }
+  
+  /**
+   * Sets the points of an anchor node. It takes the
+   * indices of points from the given portion of 
+   * an index array and stores those indices, together
+   * with their distances to the given anchor node, 
+   * in the point index list of the anchor node.
+   *  
+   * @param node The node in which the points are
+   * needed to be set.
+   * @param startIdx The start of the portion in 
+   * the given index array (the master index
+   * array).
+   * @param endIdx The end of the portion in the
+   * given index array. 
+   * @param indices The index array.
+   */
+  public void setPoints(TempNode node, int startIdx, int endIdx, int[] indices) {
+    node.points = new MyIdxList();    
+    Instance temp; double dist;
+    for(int i=startIdx; i<=endIdx; i++) {
+      temp = m_Instances.instance(indices[i]);
+      dist = m_DistanceFunction.distance(node.anchor, temp);
+      node.points.insertReverseSorted(indices[i], dist);
+    }
+  }
+
+  /**
+   * Sets the distances of a supplied new
+   * anchor to all the rest of the 
+   * previous anchor points.
+   * @param anchors The old anchor points.
+   * @param newAnchor The new anchor point.
+   * @param anchorDistances The vector to
+   * store the distances of newAnchor to 
+   * each of the old anchors.
+   * @throws Exception If there is some 
+   * problem in calculating the distances.
+   */
+  public void setInterAnchorDistances(Vector<TempNode> anchors, TempNode newAnchor,
+                                      Vector<double[]> anchorDistances) throws Exception {
+    double[] distArray = new double[anchors.size()];
+    
+    for(int i=0; i<anchors.size(); i++) {
+      Instance anchr = ((TempNode)anchors.elementAt(i)).anchor;
+      distArray[i] = m_DistanceFunction.distance(anchr, newAnchor.anchor);
+    }
+    anchorDistances.add(distArray);
+  }
+  
+  /**
+   * Removes points from old anchors that
+   * are nearer to the given new anchor and
+   * adds them to the list of points of the
+   * new anchor. 
+   * @param newAnchor The new anchor.
+   * @param anchors The old anchors.
+   * @param anchorDistances The distances
+   * of new anchor to each of the old 
+   * anchors.
+   * @return true if any points are removed
+   * from the old anchors
+   */
+  public boolean stealPoints(TempNode newAnchor, Vector anchors, 
+                          Vector anchorDistances) {
+                            
+    int maxIdx = -1; 
+    double maxDist = Double.NEGATIVE_INFINITY;
+    double[] distArray = (double[])anchorDistances.lastElement();
+    
+    for(int i=0; i<distArray.length; i++)
+      if(maxDist < distArray[i]) {
+        maxDist = distArray[i]; maxIdx = i;
+      }
+    
+    boolean anyPointsStolen=false, pointsStolen=false;
+    TempNode anchorI;
+    double newDist, distI, interAnchMidDist;
+    Instance newAnchInst = newAnchor.anchor, anchIInst;
+    for(int i=0; i<anchors.size(); i++) {
+      anchorI = (TempNode)anchors.elementAt(i);
+      anchIInst = anchorI.anchor;
+      
+      pointsStolen = false;
+      interAnchMidDist = m_DistanceFunction.distance(newAnchInst, anchIInst)/2D;
+      for(int j=0; j<anchorI.points.length(); j++) {
+        ListNode tmp = (ListNode) anchorI.points.get(j);
+        //break if we reach a point whose distance is less than the midpoint
+        //of inter anchor distance
+        if(tmp.distance < interAnchMidDist)
+          break;
+        //else test if this point can be stolen by the new anchor
+        newDist = m_DistanceFunction.distance(newAnchInst, 
+                                              m_Instances.instance(tmp.idx));
+        distI = tmp.distance;
+        if(newDist < distI) {
+          newAnchor.points.insertReverseSorted(tmp.idx, newDist);
+          anchorI.points.remove(j);
+          anyPointsStolen=pointsStolen=true;
+        }
+      }
+      if (pointsStolen)
+        anchorI.radius = ((ListNode)anchorI.points.getFirst()).distance;
+    }//end for
+    return anyPointsStolen;
+  }//end stealPoints()
+
+  /**
+  /**
+   * Calculates the centroid pivot of a node based on its
+   * two child nodes (if merging two nodes).
+   * @param node1 The first child.
+   * @param node2 The second child.
+   * @param insts The set of instances on which the tree 
+   * is being built (as dataset header information is 
+   * required). 
+   * @return The centroid pivot of a node. 
+   */
+  public Instance calcPivot(TempNode node1, TempNode node2, Instances insts) {
+    int classIdx = m_Instances.classIndex();
+    double[] attrVals = new double[insts.numAttributes()];
+    Instance temp;
+    double anchr1Ratio = (double)node1.points.length() / 
+                         (node1.points.length()+node2.points.length()),
+           anchr2Ratio = (double)node2.points.length() / 
+                         (node1.points.length()+node2.points.length());                         ;
+    for(int k=0; k<node1.anchor.numValues(); k++) {
+      if(node1.anchor.index(k)==classIdx)
+        continue;
+      attrVals[k] += node1.anchor.valueSparse(k)*anchr1Ratio;
+    }
+    for(int k=0; k<node2.anchor.numValues(); k++) {
+      if(node2.anchor.index(k)==classIdx)
+        continue;
+      attrVals[k] += node2.anchor.valueSparse(k)*anchr2Ratio;
+    }
+    temp = new DenseInstance(1.0, attrVals);
+    return temp;
+  }
+  
+  /**
+   * Calculates the centroid pivot of a node based on 
+   * the list of points that it  contains (tbe two 
+   * lists of its children are provided).
+   * @param list1 The point index list of first child.
+   * @param list2 The point index list of second 
+   * child.
+   * @param insts The insts object on which the tree 
+   * is being built (for header information). 
+   * @return The centroid pivot of the node. 
+   */
+  public Instance calcPivot(MyIdxList list1, MyIdxList list2, Instances insts) {
+    int classIdx = m_Instances.classIndex();
+    double[] attrVals = new double[insts.numAttributes()];
+    
+    Instance temp;
+    for(int i=0; i<list1.length(); i++) {
+      temp = insts.instance(((ListNode)list1.get(i)).idx);
+      for(int k=0; k<temp.numValues(); k++) {
+        if(temp.index(k)==classIdx)
+          continue;
+        attrVals[k] += temp.valueSparse(k);
+      }
+    }
+    for(int j=0; j<list2.length(); j++) {
+      temp = insts.instance(((ListNode)list2.get(j)).idx);
+      for(int k=0; k<temp.numValues(); k++) {
+        if(temp.index(k)==classIdx)
+          continue;
+        attrVals[k] += temp.valueSparse(k);
+      }
+    }
+    for(int j=0, numInsts=list1.length()+list2.length(); 
+        j < attrVals.length; j++) {
+      attrVals[j] /= numInsts;
+    }
+    temp = new DenseInstance(1.0, attrVals);
+    return temp;
+  }
+  
+  /** 
+   * Calculates the radius of a node based on its two 
+   * child nodes (if merging two nodes).
+   * @param n1 The first child of the node.
+   * @param n2 The second child of the node.
+   * @return The radius of the node. 
+   * @throws Exception
+   */
+  public double calcRadius(TempNode n1, TempNode n2) {
+	  Instance p1 = n1.anchor, p2 = n2.anchor;
+	  double radius = n1.radius + m_DistanceFunction.distance(p1, p2) + n2.radius;
+	  return radius/2;
+  }
+  
+  /**
+   * Calculates the radius of a node based on the
+   * list of points that it contains (the two lists of 
+   * its children are provided). 
+   * @param list1 The point index list of first child.
+   * @param list2 The point index list of second child.
+   * @param pivot The centre/pivot of the node.
+   * @param insts The instances on which the tree is 
+   * being built (for header info). 
+   * @return The radius of the node. 
+   */
+  public double calcRadius(MyIdxList list1, MyIdxList list2, 
+                           Instance pivot, Instances insts) {
+    double radius = Double.NEGATIVE_INFINITY;
+    
+    for(int i=0; i<list1.length(); i++) {
+      double dist = m_DistanceFunction.distance(pivot, 
+                                              insts.instance(((ListNode)list1.get(i)).idx));
+      if(dist>radius)
+        radius = dist;
+    }
+    for(int j=0; j<list2.length(); j++) {
+      double dist = m_DistanceFunction.distance(pivot, 
+                                              insts.instance(((ListNode)list2.get(j)).idx));
+      if(dist>radius)
+        radius = dist;
+    }
+    return radius;
+  }
+  
+  /**
+   * Adds an instance to the tree. This implementation of 
+   * MiddleOutConstructor doesn't support addition of 
+   * instances to already built tree, hence it always
+   * throws an exception.
+   * @param node The root of the tree to which the 
+   * instance is to be added.
+   * @param inst The instance to add to the tree.
+   * @return The updated master index array after 
+   * adding the instance.
+   * @throws Exception Always as this implementation of
+   * MiddleOutConstructor doesn't support addition of
+   * instances after batch construction of the tree.
+   */
+  public int[] addInstance(BallNode node, Instance inst) throws Exception {
+    throw new Exception("Addition of instances after the tree is built, not " +
+                        "possible with MiddleOutConstructor.");
+  }
+
+  /**
+   * Sets the maximum number of instances allowed in a leaf.
+   * @param num The maximum number of instances allowed in 
+   * a leaf.
+   * @throws Exception If the num is < 2, as the method 
+   * cannot work for < 2 instances. 
+   */ 
+  public void setMaxInstancesInLeaf(int num) throws Exception {
+    if(num<2)
+      throw new Exception("The maximum number of instances in a leaf for " +
+                          "using MiddleOutConstructor must be >=2.");
+    super.setMaxInstancesInLeaf(num);
+  }  
+
+  /**
+   * Sets the instances on which the tree is to be built.
+   * @param insts The instances on which to build the 
+   * ball tree.
+   */
+  public void setInstances(Instances insts) {
+    super.setInstances(insts);
+    rootRadius = -1; //this needs to be re-calculated by buildTree()
+  }
+  
+  /**
+   * Sets the master index array that points to 
+   * instances in m_Instances, so that only this array
+   * is manipulated, and m_Instances is left 
+   * untouched.
+   * @param instList The master index array.
+   */
+  public void setInstanceList(int[] instList) {
+    super.setInstanceList(instList); 
+    rootRadius = -1; //this needs to be re-calculated by buildTree()
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String initialAnchorRandomTipText() {
+    return "Whether the initial anchor is chosen randomly.";
+  }
+  
+  /** 
+   * Gets whether if the initial anchor is chosen randomly.
+   * @return true if the initial anchor is a random one. 
+   */
+  public boolean isInitialAnchorRandom() {
+    return m_RandomInitialAnchor;
+  }
+  
+  /** 
+   * Sets whether if the initial anchor is chosen randomly. If not 
+   * then if it is the furthest point from the mean/centroid.
+   * @param randomInitialAnchor Should be true if the first 
+   * anchor is to be chosen randomly.
+   */
+  public void setInitialAnchorRandom(boolean randomInitialAnchor) {
+    m_RandomInitialAnchor = randomInitialAnchor;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed value for the random number generator.";
+  }
+
+  /**
+   * Returns the seed for random number generator.
+   * @return The random number seed.
+   */
+  public int getSeed() {
+    return m_RSeed;
+  }
+  
+  /**
+   * Sets the seed for random number generator 
+   * (that is used for selecting the first anchor 
+   * point randomly).
+   * @param seed The seed. 
+   */
+  public void setSeed(int seed) {
+    m_RSeed = seed;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+    
+    newVector.addElement(new Option(
+	"\tThe seed for the random number generator used\n"
+	+ "\tin selecting random anchor.\n"
+	+ "(default: 1)", 
+	"S", 1, "-S <num>"));
+    
+    newVector.addElement(new Option(
+	"\tUse randomly chosen initial anchors.",
+	"R", 0, "-R"));
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  The seed for the random number generator used
+   *  in selecting random anchor.
+   * (default: 1)</pre>
+   * 
+   * <pre> -R
+   *  Use randomly chosen initial anchors.</pre>
+   * 
+   <!-- options-end --> 
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception	if an option is not supported
+   **/
+  public void setOptions(String[] options)
+    throws Exception {
+
+    super.setOptions(options);
+   
+    String temp = Utils.getOption('S', options);
+    if(temp.length()>0) {
+      setSeed(Integer.parseInt(temp));
+    }
+    else {
+      setSeed(1);
+    }
+    
+    setInitialAnchorRandom(Utils.getFlag('R', options));
+  }
+
+  /**
+   * Gets the current settings of this BallTree MiddleOutConstructor.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-S");
+    result.add("" + getSeed());
+    
+    if(isInitialAnchorRandom())
+      result.add("-R");
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Checks whether if the points in an index list
+   * are in some specified of the master index array. 
+   * @param list The point list.
+   * @param startidx The start of the portion in 
+   * master index array. 
+   * @param endidx The end of the portion in master
+   * index array.
+   * @throws Exception If some point in the point
+   * list is not in the specified portion of master
+   * index array. 
+   */
+  public void checkIndicesList(MyIdxList list, int startidx, int endidx) 
+    throws Exception {
+    
+    boolean found;
+    ListNode node;
+    for(int i=0; i<list.size(); i++) {
+      node = (ListNode)list.get(i);
+      found=false;
+      for(int j=startidx; j<=endidx; j++) {
+        if(node.idx==m_InstList[j]) {
+          found=true; 
+          break;
+        }
+      }
+      if(!found)
+        throw new Exception("Error: Element "+node.idx+" of the list not in " +
+                            "the array." +
+                            "\nArray: "+printInsts(startidx, endidx)+
+                            "\nList: "+printList(list));
+    }
+  }
+  
+  /**
+   * For printing indices in some given portion
+   * of the master index array. 
+   * @param startIdx The start of the portion 
+   * in master index array. 
+   * @param endIdx The end of the portion in 
+   * master index array.
+   * @return The string containing the indices
+   * in specified portion of the master index 
+   * array. 
+   */
+  public String printInsts(int startIdx, int endIdx) {
+    StringBuffer bf = new StringBuffer();
+    try {
+      bf.append("i: ");
+      for (int i = startIdx; i <= endIdx; i++) {
+        if (i == startIdx)
+          bf.append("" + m_InstList[i]);
+        else
+          bf.append(", " + m_InstList[i]);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return bf.toString();
+  }
+  
+  /**
+   * For printing indices in a given point list.
+   * @param points The point list.
+   * @return String containing indices of the
+   * points in the point list.
+   */
+  public String printList(MyIdxList points) {
+    if(points==null || points.length()==0) return "";
+    StringBuffer bf = new StringBuffer();
+    try {
+      ListNode temp;
+      for(int i=0; i<points.size(); i++) {
+        temp = (ListNode) points.get(i);
+        if(i==0)
+          bf.append(""+temp.idx);
+        else
+          bf.append(", "+temp.idx);
+      }
+    } catch(Exception ex) { ex.printStackTrace(); }
+    return bf.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /** 
+   * Temp class to represent either a leaf node or an internal node. Should only 
+   * have two children (could be the case one child is an instance and the 
+   * other another node). Primarily used for anchor nodes. It stores the
+   * points contained in a node together with their distances to the 
+   * node's centre/anchor point.
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5987 $
+   */
+  protected class TempNode
+    implements RevisionHandler {
+    
+    /** The anchor point of the node. */
+    Instance anchor;
+    
+    /** The index of the anchor point. */
+    int idx;
+    
+    /** The radius of the node. */
+    double radius;
+    
+    /** The list of points inside the node. */
+    MyIdxList points;
+    
+    /** Node's left child. */
+    TempNode left;
+
+    /** Node's right child. */
+    TempNode right;
+    
+    /**
+     * Returns a string represention of the node.
+     * @return The string representation of the 
+     * node.
+     */
+    public String toString() {
+      if(points==null || points.length()==0) return idx+"";
+      StringBuffer bf = new StringBuffer();
+      try {
+        bf.append(idx+" p: ");
+        ListNode temp; 
+        for(int i=0; i<points.size(); i++) {
+          temp = (ListNode) points.get(i);
+          if(i==0)
+            bf.append(""+temp.idx);
+          else
+            bf.append(", "+temp.idx);
+        }
+      } catch(Exception ex) { ex.printStackTrace(); }
+      return bf.toString();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /**
+   * An element of MyIdxList. It stores a points index, and 
+   * its distance to some specific point (usually a node's
+   * anchor point). 
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5987 $
+   */
+  protected class ListNode
+    implements RevisionHandler {
+    
+    /** The index of the point. */
+    int idx = -1;
+    
+    /** The distance of the point to the anchor.*/
+    double distance = Double.NEGATIVE_INFINITY;
+    
+    /**
+     * Constructor. 
+     * @param i The point's index. 
+     * @param d The point's distance to the 
+     * anchor.
+     */
+    public ListNode(int i, double d) {
+      idx = i;
+      distance = d;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+  
+  /**
+   * Class implementing a list. It stores indices of 
+   * instances/points, together with their distances to a nodes
+   * centre/pivot/anchor, in a (reverse sorted) list.  
+   * 
+   * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+   * @version $Revision: 5987 $
+   */
+  protected class MyIdxList implements Serializable, RevisionHandler {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -2283869109722934927L;    
+    
+    /** The array list backing this list */
+    protected ArrayList<ListNode> m_List;
+    
+    /**
+     * Constructor.
+     */
+    public MyIdxList() {
+      m_List = new ArrayList<ListNode>();
+    }
+
+    /**
+     * Constructor for given capacity.
+     */
+    public MyIdxList(int capacity) {
+      m_List = new ArrayList<ListNode>(capacity);
+    }
+
+    /**
+     * Returns the first element in the list.
+     * @return The list's first element.
+     */
+    public ListNode getFirst() {
+      return m_List.get(0);
+    }
+    
+    /**
+     * Inserts an element in reverse sorted order in 
+     * the list.
+     * @param idx The index of the point to insert.
+     * @param distance The distance of the point to
+     * a node's anchor (this would be used to 
+     * determine the sort order).
+     */
+    public void insertReverseSorted(final int idx, final double distance) {
+
+      int i=0;
+      for (ListNode temp : m_List) {
+        if(temp.distance < distance)
+          break;
+        i++;
+      }
+      m_List.add(i, new ListNode(idx, distance));
+    }
+    
+    /**
+     * Returns an element at the specified index in 
+     * the list. 
+     * @param index The index of the element in the 
+     * list.
+     * @return The element at the given index.
+     */
+    public ListNode get(int index) {
+      return m_List.get(index);
+    }
+    
+    /** 
+     * Removes an element at the specified index 
+     * from the list.
+     * @param index The index of the element
+     * in the list to remove.
+     */
+    public void remove(int index) {
+      m_List.remove(index);
+    }
+    
+    /**
+     * Returns the size of the list.
+     * @return The size of the list.
+     */
+    public int length() {
+      return m_List.size();
+    }
+    
+    /**
+     * Returns the size of the list.
+     * @return The size of the list.
+     */
+    public int size() {
+      return m_List.size();
+    }
+    
+    /**
+     * Appends one list at the end of the other. 
+     * @param list1 The list to which the other
+     * list would be appended.
+     * @param list2 The list to append to the 
+     * other list.
+     * @return The new list with list2 appended 
+     * to list1.
+     */
+    public MyIdxList append(MyIdxList list1, MyIdxList list2) {
+      MyIdxList temp = new MyIdxList(list1.size()+list2.size());
+      temp.m_List.addAll(list1.m_List);
+      temp.m_List.addAll(list2.m_List);
+      return temp;
+    }
+    
+    /**
+     * Checks the sorting of a list.
+     * @param list The list whose sorting is
+     * to be checked.
+     * @throws Exception If the list is not
+     * in (reverse) sorted order.
+     */
+    public void checkSorting(MyIdxList list) throws Exception {
+      Iterator<ListNode> en = m_List.iterator();
+      ListNode first=null, second=null;
+      while(en.hasNext()) {
+        if(first==null)
+          first = (ListNode) en.next();
+        else {
+          second = (ListNode)en.next();
+          if(first.distance < second.distance)
+            throw new Exception("List not sorted correctly." +
+                                " first.distance: " + first.distance +
+                                " second.distance: " + second.distance +
+                                " Please check code.");            
+        }
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/PointsClosestToFurthestChildren.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/PointsClosestToFurthestChildren.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/PointsClosestToFurthestChildren.java	(revision 29)
@@ -0,0 +1,225 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PointsClosestToFurthestChildren.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * Implements the Moore's method to split a node of a ball tree.<br/>
+ * <br/>
+ * For more information please see section 2 of the 1st and 3.2.3 of the 2nd:<br/>
+ * <br/>
+ * Andrew W. Moore: The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data. In: UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence, San Francisco, CA, USA, 397-405, 2000.<br/>
+ * <br/>
+ * Ashraf Masood Kibriya (2007). Fast Algorithms for Nearest Neighbour Search. Hamilton, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Moore2000,
+ *    address = {San Francisco, CA, USA},
+ *    author = {Andrew W. Moore},
+ *    booktitle = {UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence},
+ *    pages = {397-405},
+ *    publisher = {Morgan Kaufmann Publishers Inc.},
+ *    title = {The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data},
+ *    year = {2000}
+ * }
+ * 
+ * &#64;mastersthesis{Kibriya2007,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Ashraf Masood Kibriya},
+ *    school = {Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato},
+ *    title = {Fast Algorithms for Nearest Neighbour Search},
+ *    year = {2007}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+//better rename to MidPoint of Furthest Pair/Children
+public class PointsClosestToFurthestChildren
+  extends BallSplitter
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -2947177543565818260L;
+
+  /**
+   * Returns a string describing this object.
+   * 
+   * @return A description of the algorithm for displaying in the
+   * explorer/experimenter gui.
+   */
+  public String globalInfo() {
+    return 
+        "Implements the Moore's method to split a node of a ball tree.\n\n"
+      + "For more information please see section 2 of the 1st and 3.2.3 of "
+      + "the 2nd:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return The technical information about this class.
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    TechnicalInformation additional;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Andrew W. Moore");
+    result.setValue(Field.TITLE, "The Anchors Hierarchy: Using the Triangle Inequality to Survive High Dimensional Data");
+    result.setValue(Field.YEAR, "2000");
+    result.setValue(Field.BOOKTITLE, "UAI '00: Proceedings of the 16th Conference on Uncertainty in Artificial Intelligence");
+    result.setValue(Field.PAGES, "397-405");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers Inc.");
+    result.setValue(Field.ADDRESS, "San Francisco, CA, USA");
+
+    additional = result.add(Type.MASTERSTHESIS);
+    additional.setValue(Field.AUTHOR, "Ashraf Masood Kibriya");
+    additional.setValue(Field.TITLE, "Fast Algorithms for Nearest Neighbour Search");
+    additional.setValue(Field.YEAR, "2007");
+    additional.setValue(Field.SCHOOL, "Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato");
+    additional.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+    
+    return result;
+  }
+
+  /**  Constructor. */
+  public PointsClosestToFurthestChildren() {
+  }
+  
+  /**
+   * Constructor. 
+   * @param instList The master index array.
+   * @param insts The instances on which the tree
+   * is (or is to be) built.
+   * @param e The Euclidean distance function to 
+   * use for splitting.
+   */
+  public PointsClosestToFurthestChildren(int[] instList, Instances insts, 
+                                         EuclideanDistance e) {
+    super(instList, insts, e);
+  }
+  
+  /** 
+   * Splits a ball into two. 
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(BallNode node, int numNodesCreated) throws Exception {
+    correctlyInitialized();
+    
+    double maxDist = Double.NEGATIVE_INFINITY, dist = 0.0;
+    Instance furthest1=null, furthest2=null, pivot=node.getPivot(), temp;
+    double distList[] = new double[node.m_NumInstances];
+    for(int i=node.m_Start; i<=node.m_End; i++) {
+      temp = m_Instances.instance(m_Instlist[i]);
+      dist = m_DistanceFunction.distance(pivot, temp, Double.POSITIVE_INFINITY);
+      if(dist > maxDist) {
+        maxDist = dist; furthest1 = temp;
+      }
+    }
+    maxDist = Double.NEGATIVE_INFINITY;
+    furthest1 = (Instance)furthest1.copy();
+    for(int i=0; i < node.m_NumInstances; i++) {
+      temp = m_Instances.instance(m_Instlist[i+node.m_Start]);
+      distList[i] = m_DistanceFunction.distance(furthest1, temp, 
+                                                Double.POSITIVE_INFINITY);
+      if(distList[i] > maxDist) {
+        maxDist = distList[i]; furthest2 = temp; //tempidx = i+node.m_Start;
+      }
+    }
+    furthest2 = (Instance) furthest2.copy();
+    dist = 0.0; int numRight=0;
+    //moving indices in the right branch to the right end of the array
+    for(int i=0, j=0; i < node.m_NumInstances-numRight; i++, j++) {
+      temp = m_Instances.instance(m_Instlist[i+node.m_Start]);
+      dist = m_DistanceFunction.distance(furthest2, temp, Double.POSITIVE_INFINITY);
+      if(dist < distList[i]) {
+        int t = m_Instlist[node.m_End-numRight];
+        m_Instlist[node.m_End-numRight] = m_Instlist[i+node.m_Start];
+        m_Instlist[i+node.m_Start] = t;
+        double d = distList[distList.length-1-numRight];
+        distList[distList.length-1-numRight] = distList[i];
+        distList[i] = d;
+        numRight++;
+        i--;
+      }
+    }
+    
+    if(!(numRight > 0 && numRight < node.m_NumInstances)) 
+      throw new Exception("Illegal value for numRight: "+numRight);
+    
+    node.m_Left = new BallNode(node.m_Start, node.m_End-numRight, numNodesCreated+1,
+                              (pivot=BallNode.calcCentroidPivot(node.m_Start,
+                                                node.m_End-numRight, m_Instlist, 
+                                                m_Instances)), 
+                              BallNode.calcRadius(node.m_Start, 
+                                                node.m_End-numRight, m_Instlist, 
+                                                m_Instances, pivot, 
+                                                m_DistanceFunction)
+                              );
+    
+    node.m_Right = new BallNode(node.m_End-numRight+1, node.m_End, numNodesCreated+2,
+                       (pivot=BallNode.calcCentroidPivot(node.m_End-numRight+1,
+                                                         node.m_End, m_Instlist, 
+                                                         m_Instances)), 
+                          BallNode.calcRadius(node.m_End-numRight+1, node.m_End, 
+                                              m_Instlist, m_Instances, pivot, 
+                                              m_DistanceFunction)
+                              );
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/TopDownConstructor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/TopDownConstructor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/balltrees/TopDownConstructor.java	(revision 29)
@@ -0,0 +1,387 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TopDownConstructor.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.balltrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instance;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.neighboursearch.balltrees.BallNode;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * The class implementing the TopDown construction method of ball trees. It further uses one of a number of different splitting methods to split a ball while constructing the tree top down.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Stephen M. Omohundro (1989). Five Balltree Construction Algorithms.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Omohundro1989,
+ *    author = {Stephen M. Omohundro},
+ *    institution = {International Computer Science Institute},
+ *    month = {December},
+ *    number = {TR-89-063},
+ *    title = {Five Balltree Construction Algorithms},
+ *    year = {1989}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;classname and options&gt;
+ *  Ball splitting algorithm to use.</pre>
+ * 
+ <!-- options-end --> 
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class TopDownConstructor
+  extends BallTreeConstructor 
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -5150140645091889979L;
+  
+  /** 
+   * The BallSplitter algorithm used by the TopDown BallTree constructor, if it 
+   * is selected. 
+   */
+  protected BallSplitter m_Splitter = new PointsClosestToFurthestChildren();
+
+  /** 
+   * Creates a new instance of TopDownConstructor.
+   */
+  public TopDownConstructor() {
+  }
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class implementing the TopDown construction method of "
+      + "ball trees. It further uses one of a number of different splitting "
+      + "methods to split a ball while constructing the tree top down.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+    
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Stephen M. Omohundro");
+    result.setValue(Field.YEAR, "1989");
+    result.setValue(Field.TITLE, "Five Balltree Construction Algorithms");
+    result.setValue(Field.MONTH, "December");
+    result.setValue(Field.NUMBER, "TR-89-063");
+    result.setValue(Field.INSTITUTION, "International Computer Science Institute");
+
+    return result;
+  }
+
+  /**
+   * Builds the ball tree top down. 
+   * @return The root node of the tree. 
+   * @throws Exception If there is problem building
+   * the tree.
+   */
+  public BallNode buildTree() throws Exception {
+    BallNode root;
+    
+    m_NumNodes = m_MaxDepth = 0;
+    m_NumLeaves = 1;
+    
+    m_Splitter.setInstances(m_Instances);
+    m_Splitter.setInstanceList(m_InstList);
+    m_Splitter.
+    setEuclideanDistanceFunction((EuclideanDistance)m_DistanceFunction);
+    
+    root = new BallNode(0, m_InstList.length-1, 0);
+    root.setPivot(BallNode.calcCentroidPivot(m_InstList, m_Instances));
+    root.setRadius(BallNode.calcRadius(m_InstList, m_Instances, root.getPivot(), m_DistanceFunction));
+    
+    splitNodes(root, m_MaxDepth+1, root.m_Radius);
+    
+    return root; 
+  }
+    
+  /**
+   * Recursively splits nodes of a ball tree until 
+   * <=m_MaxInstancesInLeaf instances remain in a node.
+   * @param node The node to split.
+   * @param depth The depth of this node in the tree, 
+   * so that m_MaxDepth is correctly updated.
+   * @param rootRadius The smallest ball enclosing all
+   * the data points.
+   * @throws Exception If there is some problem in 
+   * splitting.
+   */
+  protected void splitNodes(BallNode node, int depth, final double rootRadius) throws Exception {
+    
+    if(node.m_NumInstances <= m_MaxInstancesInLeaf || 
+       (rootRadius==0 ? true : node.m_Radius/rootRadius < m_MaxRelLeafRadius))
+      return;
+    
+    m_NumLeaves--;
+    m_Splitter.splitNode(node, m_NumNodes);
+    m_NumNodes += 2;
+    m_NumLeaves += 2;
+    
+    if(m_MaxDepth < depth)
+      m_MaxDepth = depth;
+  
+    splitNodes(node.m_Left, depth+1, rootRadius);
+    splitNodes(node.m_Right, depth+1, rootRadius);
+    
+    if(m_FullyContainChildBalls) {
+      double radius = BallNode.calcRadius(node.m_Left, node.m_Right, 
+                                         node.getPivot(), m_DistanceFunction);
+      Instance pivot = BallNode.calcPivot(node.m_Left, node.m_Right, m_Instances);
+//      System.err.println("Left Radius: "+node.m_Left.getRadius()+
+//                         " Right Radius: "+node.m_Right.getRadius()+
+//                         " d(p1,p2): "+
+//                         m_DistanceFunction.distance(node.m_Left.getPivot(), node.m_Right.getPivot())+
+//                         " node's old radius: "+node.getRadius()+
+//                         " node's new Radius: "+radius+
+//                         " node;s old pivot: "+node.getPivot()+
+//                         " node's new pivot: "+pivot);
+      node.setRadius(radius);
+    }    
+  }
+    
+  /**
+   * Adds an instance to the ball tree. 
+   * @param node The root node of the tree.
+   * @param inst The instance to add to the tree.
+   * @return The new master index array after adding the 
+   * instance. 
+   * @throws Exception If there is some problem adding the 
+   * given instance to the tree. 
+   */
+  public int[] addInstance(BallNode node, Instance inst) throws Exception {
+    
+    double leftDist, rightDist;
+    
+    if (node.m_Left!=null && node.m_Right!=null) { //if node is not a leaf      
+      // go further down the tree to look for the leaf the instance should be in
+      
+      leftDist = m_DistanceFunction.distance(inst, node.m_Left.getPivot(), 
+                                    Double.POSITIVE_INFINITY); //instance.value(m_SplitDim);
+      rightDist = m_DistanceFunction.distance(inst, node.m_Right.getPivot(), 
+                                    Double.POSITIVE_INFINITY); 
+      if (leftDist < rightDist) {
+        addInstance(node.m_Left, inst);
+        // go into right branch to correct instance list boundaries
+        processNodesAfterAddInstance(node.m_Right);
+      }
+      else {
+        addInstance(node.m_Right, inst);
+      }
+      // correct end index of instance list of this node
+      node.m_End++;
+    }
+    else if(node.m_Left!=null || node.m_Right!=null) {
+      throw new Exception("Error: Only one leaf of the built ball tree is " +
+                          "assigned. Please check code.");
+    }
+    else { // found the leaf to insert instance
+           
+      int index = m_Instances.numInstances() - 1;
+      
+      int instList[] = new int[m_Instances.numInstances()];
+      System.arraycopy(m_InstList, 0, instList, 0, node.m_End+1);
+      if(node.m_End < m_InstList.length-1)
+        System.arraycopy(m_InstList, node.m_End+2, instList, node.m_End+2, m_InstList.length-node.m_End-1);      
+      instList[node.m_End+1] = index;
+      node.m_End++;
+      node.m_NumInstances++;
+      m_InstList = instList;
+      
+      m_Splitter.setInstanceList(m_InstList);
+      
+      if(node.m_NumInstances > m_MaxInstancesInLeaf) {
+        m_Splitter.splitNode(node, m_NumNodes);
+        m_NumNodes += 2;
+      }
+    }
+    return m_InstList;
+  }
+  
+  /**
+   * Post process method to correct the start and end 
+   * indices of BallNodes on the right of the 
+   * node where the instance was added.  
+   * @param node The node whose m_Start and m_End 
+   * need to be updated.
+   */
+  protected void processNodesAfterAddInstance(BallNode node) {
+    //updating start and end indices for the node
+    node.m_Start++;
+    node.m_End++;    
+    //processing child nodes
+    if(node.m_Left!=null && node.m_Right!=null) {
+      processNodesAfterAddInstance(node.m_Left);
+      processNodesAfterAddInstance(node.m_Right);
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String ballSplitterTipText() {
+    return 
+        "The BallSplitter algorithm set that would be used by the TopDown "
+      + "BallTree constructor.";
+  }
+  
+  /** 
+   * Returns the BallSplitter algorithm set that would be 
+   * used by the TopDown BallTree constructor.
+   * @return The BallSplitter currently in use.
+   */
+  public BallSplitter getBallSplitter() {
+    return m_Splitter;
+  }
+  
+  /**
+   * Sets the ball splitting algorithm to be used by the 
+   * TopDown constructor.
+   * @param splitter The BallSplitter to use.
+   */
+  public void setBallSplitter(BallSplitter splitter) {
+    m_Splitter = splitter;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   * 
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option> newVector = new Vector<Option>();
+
+    newVector.addElement(new Option(
+	"\tBall splitting algorithm to use.",
+	"S", 1, "-S <classname and options>"));
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;classname and options&gt;
+   *  Ball splitting algorithm to use.</pre>
+   * 
+   <!-- options-end --> 
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception	if an option is not supported
+   */
+  public void setOptions(String[] options)
+    throws Exception {
+
+    super.setOptions(options);
+    
+    String optionString = Utils.getOption('S', options);
+    if(optionString.length() != 0) {
+      String nnSearchClassSpec[] = Utils.splitOptions(optionString);
+      if(nnSearchClassSpec.length == 0) { 
+        throw new Exception("Invalid BallSplitter specification string."); 
+      }
+      String className = nnSearchClassSpec[0];
+      nnSearchClassSpec[0] = "";
+
+      setBallSplitter( (BallSplitter)
+                            Utils.forName( BallSplitter.class, 
+                                           className, nnSearchClassSpec) );
+    }
+    else {
+      setBallSplitter(new PointsClosestToFurthestChildren());  
+    }
+  }
+
+  /**
+   * Gets the current settings of KDtree.
+   * 
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-S");
+    result.add(m_Splitter.getClass().getName());
+
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/covertrees/Stack.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/covertrees/Stack.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/covertrees/Stack.java	(revision 29)
@@ -0,0 +1,181 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Stack.java
+ * Copyright (C) 2006 Alina Beygelzimer and Sham Kakade and John Langford
+ */
+
+package weka.core.neighboursearch.covertrees;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Class implementing a stack.
+ * 
+ * @param <T> The type of elements to be stored in 
+ * the stack.
+ * @author Alina Beygelzimer (original C++ code)
+ * @author Sham Kakade (original C++ code)
+ * @author John Langford (original C++ code)
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz) (Java port)
+ * @version $Revision: 5953 $
+ */
+public class Stack<T>
+  implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 5604056321825539264L;
+  
+  /** The number of elements in the stack. */
+  public int length;
+
+  /** The elements inside the stack. */
+  public ArrayList<T> elements;
+  
+  /** Constructor. */
+  public Stack() { 
+    length=0; 
+    elements = new ArrayList<T>();
+  }
+
+  /**
+   * Constructor. 
+   * @param capacity The initial capacity of the stack.
+   */
+  public Stack(int capacity) { 
+    length=0; 
+    elements = new ArrayList<T>(capacity);
+  }
+  
+  /**
+   * Returns the last element in the stack.
+   * @return The last element.
+   */
+  public T last() { 
+    return elements.get(length-1);
+  }
+  
+  /**
+   * Returns the ith element in the stack.
+   * @param i The index of the element to return.
+   * @return The ith element. 
+   */
+  public T element(int i) { 
+    return elements.get(i);
+  }
+  
+  /**
+   * Sets the ith element in the stack. 
+   * @param i The index at which the element is
+   * to be inserted. 
+   * @param e The element to insert. 
+   */
+  public void set(int i, T e) {
+    elements.set(i, e);
+  }
+  
+  /**
+   * Returns a sublist of the elements in the 
+   * stack. 
+   * @param beginIdx The start index of the 
+   * sublist. 
+   * @param uptoLength The length of the 
+   * sublist.
+   * @return The sublist starting from 
+   * beginIdx and of length uptoLength.
+   */
+  public List subList(int beginIdx, int uptoLength) {
+    return elements.subList(beginIdx, uptoLength);
+  }
+  
+  /** Removes all the elements from the stack. */
+  public void clear() {
+    elements.clear();
+    length=0;
+  }
+  
+  /** 
+   * Adds all the given elements in the stack.
+   * @param c The collection of elements to add
+   * in the stack.
+   */ 
+  public void addAll(Collection<? extends T> c) {
+    elements.addAll(c);
+    length = c.size();
+  }
+  
+  /**
+   * Replace all elements in the stack with 
+   * the elements of another given stack.
+   * It first removes all the elements 
+   * currently in the stack, and then adds all
+   * the elements of the provided stack. 
+   * @param s The stack whose elements should 
+   * be put in this stack.
+   */
+  public void replaceAllBy(Stack<T> s) {
+    elements.clear();
+    elements.addAll(s.elements);
+    length = elements.size();
+  }
+  
+  /** 
+   * Pops (removes) the first (last added) 
+   * element in the stack.
+   * @return The poped element. 
+   */
+  public T pop() {
+    length--;
+    return elements.remove(length);    
+  }
+  
+  /**
+   * Pushes the given element to the stack.
+   * @param new_ele The element to be pushed
+   * to the stack. 
+   */
+  public void push(T new_ele) {
+    length++;
+    elements.add(new_ele);
+  }
+  
+  /**
+   * Pushes the given element onto the given 
+   * stack. 
+   * @param v The stack onto push the element.
+   * @param new_ele The element to push.
+   */
+  public void push(Stack<T> v, T new_ele) {
+    length++; 
+    v.elements.add(new_ele);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNode.java	(revision 29)
@@ -0,0 +1,182 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KDTreeNode.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * A class representing a KDTree node. A node does not explicitly
+ * store the instances that it contains. Instead, it only stores 
+ * the start and end index of a portion in a master index array. Each
+ * node is assigned a portion in the master index array that stores 
+ * the indices of the instances that the node contains. Every time a 
+ * node is split by the KDTree's contruction method, the instances of 
+ * its left child are moved to the left and the instances of its 
+ * right child are moved to the right, in the portion of the master 
+ * index array belonging to the node. The start and end index in each
+ * of its children are then set accordingly within that portion so 
+ * that each have their own portion which contains their instances.   
+ * P.S.: The master index array is only stored in KDTree class.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class KDTreeNode
+  implements Serializable, RevisionHandler {
+   
+  /** for serialization. */
+  private static final long serialVersionUID = -3660396067582792648L;
+
+  /** node number (only for debug). */
+  public int m_NodeNumber;
+
+  /** left subtree; contains instances with smaller or equal to split value. */
+  public KDTreeNode m_Left = null;
+
+  /** right subtree; contains instances with larger than split value. */
+  public KDTreeNode m_Right = null;
+
+  /** value to split on. */
+  public double m_SplitValue;
+
+  /** attribute to split on. */
+  public int m_SplitDim;
+
+  /**
+   * lowest and highest value and width (= high - low) for each
+   * dimension.
+   */
+  public double[][] m_NodeRanges;
+
+  /** 
+   * The lo and high bounds of the hyper rectangle described by the
+   * node.
+   */
+  public double[][] m_NodesRectBounds;
+
+  /**
+   * The start index of the portion of the master index array, 
+   * which stores the indices of the instances/points the node 
+   * contains.
+   */
+  public int m_Start = 0;
+  
+  /**
+   * The end index of the portion of the master index array, 
+   * which stores indices of the instances/points the node 
+   * contains.
+   */
+  public int m_End = 0;
+
+  /**
+   * Constructor.
+   */
+  public KDTreeNode() {}
+
+  /**
+   * Constructor.
+   * 
+   * @param nodeNum The node number/id.
+   * @param startidx The start index of node's portion 
+   * in master index array.
+   * @param endidx The start index of node's portion 
+   * in master index array.
+   * @param nodeRanges The attribute ranges of the 
+   * Instances/points contained in this node.
+   */
+  public KDTreeNode(int nodeNum, int startidx, int endidx, double[][] nodeRanges) {
+    m_NodeNumber = nodeNum;
+    m_Start = startidx; m_End = endidx;
+    m_NodeRanges = nodeRanges;
+  }
+
+  /**
+   * 
+   * @param nodeNum The node number/id.
+   * @param startidx The start index of node's portion 
+   * in master index array.
+   * @param endidx The start index of node's portion 
+   * in master index array.
+   * @param nodeRanges The attribute ranges of the 
+   * Instances/points contained in this node.
+   * @param rectBounds The range of the rectangular 
+   * region in the point space that this node 
+   * represents (points inside this rectangular
+   * region can have different range).
+   */
+  public KDTreeNode(int nodeNum, int startidx, int endidx, double[][] nodeRanges, double[][] rectBounds) {
+    m_NodeNumber = nodeNum;
+    m_Start = startidx; m_End = endidx;
+    m_NodeRanges = nodeRanges;
+    m_NodesRectBounds = rectBounds;
+  }
+
+  /**
+   * Gets the splitting dimension.
+   * 
+   * @return 		splitting dimension
+   */
+  public int getSplitDim() {
+    return m_SplitDim;
+  }
+
+  /**
+   * Gets the splitting value.
+   * 
+   * @return 		splitting value
+   */
+  public double getSplitValue() {
+    return m_SplitValue;
+  }
+
+  /**
+   * Checks if node is a leaf.
+   * 
+   * @return 		true if it is a leaf
+   */
+  public boolean isALeaf() {
+    return (m_Left == null);
+  }         
+
+  /**
+   * Returns the number of Instances 
+   * in the rectangular region defined 
+   * by this node.
+   * @return The number of instances in
+   * this KDTreeNode.
+   */
+  public int numInstances() {
+    return (m_End-m_Start+1);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNodeSplitter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNodeSplitter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KDTreeNodeSplitter.java	(revision 29)
@@ -0,0 +1,253 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KDTreeNodeSplitter.java
+ * Copyright (C) 1999-2007 University of Waikato
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.EuclideanDistance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Class that splits up a KDTreeNode.
+ * 
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class KDTreeNodeSplitter
+  implements Serializable, OptionHandler, RevisionHandler {
+  
+  /** The instances that'll be used for tree construction. */
+  protected Instances m_Instances;
+  
+  /** The distance function used for building the tree. */
+  protected EuclideanDistance m_EuclideanDistance;
+  
+  /** 
+   * The master index array that'll be reshuffled as nodes
+   * are split and the tree is constructed. 
+   */
+  protected int[] m_InstList;
+  
+  /** 
+   * Stores whether if the width of a KDTree
+   * node is normalized or not.
+   */
+  protected boolean m_NormalizeNodeWidth;
+ 
+  // Constants
+  /** Index of min value in an array of attributes' range. */
+  public static final int MIN   = EuclideanDistance.R_MIN;
+
+  /** Index of max value in an array of attributes' range. */
+  public static final int MAX   = EuclideanDistance.R_MAX;
+  
+  /** Index of width value (max-min) in an array of attributes' range. */
+  public static final int WIDTH = EuclideanDistance.R_WIDTH;
+
+  /**
+   * default constructor.
+   */
+  public KDTreeNodeSplitter() {
+  }
+  
+  /**
+   * Creates a new instance of KDTreeNodeSplitter.
+   * @param instList Reference of the master index array.
+   * @param insts The set of training instances on which 
+   * the tree is built.
+   * @param e The EuclideanDistance object that is used
+   * in tree contruction.
+   */
+  public KDTreeNodeSplitter(int[] instList, Instances insts, EuclideanDistance e) { 
+    m_InstList = instList;
+    m_Instances = insts;
+    m_EuclideanDistance = e;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    return new Vector().elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+  }
+
+  /**
+   * Gets the current settings of the object.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    return new String[0];
+  }
+
+  /** 
+   * Checks whether an object of this class has been correctly
+   * initialized. Performs checks to see if all the necessary
+   * things (master index array, training instances, distance
+   * function) have been supplied or not.
+   * @throws Exception If the object has not been correctly 
+   * initialized.
+   */
+  protected void correctlyInitialized() throws Exception {
+    if(m_Instances==null)
+      throw new Exception("No instances supplied.");
+    else if(m_InstList==null) 
+      throw new Exception("No instance list supplied.");
+    else if(m_EuclideanDistance==null)
+      throw new Exception("No Euclidean distance function supplied.");
+    else if(m_Instances.numInstances() != m_InstList.length)
+      throw new Exception("The supplied instance list doesn't seem to match " +
+                          "the supplied instances");
+  }
+  
+  /** 
+   * Splits a node into two. After splitting two new nodes are created
+   * and correctly initialised. And, node.left and node.right are 
+   * set appropriately.
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @param nodeRanges The attributes' range for the points inside
+   * the node that is to be split.
+   * @param universe The attributes' range for the whole 
+   * point-space.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public abstract void splitNode(KDTreeNode node, int numNodesCreated, 
+      				 double[][] nodeRanges, double[][] universe) 
+  throws Exception;
+  
+  /**
+   * Sets the training instances on which the tree is (or is 
+   * to be) built. 
+   * @param inst The training instances.
+   */
+  public void setInstances(Instances inst) {
+    m_Instances = inst;
+  }
+  
+  /** 
+   * Sets the master index array containing indices of the
+   * training instances. This array will be rearranged as 
+   * the tree is built, so that each node is assigned a 
+   * portion in this array which contain the instances 
+   * insides the node's region.
+   * @param instList The master index array.
+   */
+  public void setInstanceList(int[] instList) {
+    m_InstList = instList;
+  }
+  
+  /**
+   * Sets the EuclideanDistance object to use for 
+   * splitting nodes.
+   * @param func The EuclideanDistance object.
+   */
+  public void setEuclideanDistanceFunction(EuclideanDistance func) {
+    m_EuclideanDistance = func;
+  }
+
+  /**
+   * Sets whether if a nodes region is normalized 
+   * or not. If set to true then, when selecting 
+   * the widest attribute/dimension for splitting, 
+   * the width of each attribute/dimension,
+   * of the points inside the node's region, is 
+   * divided by the width of that 
+   * attribute/dimension for the whole point-space.
+   * Thus, each attribute/dimension of that node
+   * is normalized.
+   *   
+   * @param normalize Should be true if 
+   * normalization is required.
+   */
+  public void setNodeWidthNormalization(boolean normalize) {
+    m_NormalizeNodeWidth = normalize;
+  }
+  
+  /**
+   * Returns the widest dimension. The width of each 
+   * dimension (for the points inside the node) is 
+   * normalized, if m_NormalizeNodeWidth is set to 
+   * true.
+   * @param nodeRanges The attributes' range of the 
+   * points inside the node that is to be split.
+   * @param universe The attributes' range for the
+   * whole point-space.
+   * @return The index of the attribute/dimension
+   * in which the points of the node have widest
+   * spread.
+   */
+  protected int widestDim(double[][] nodeRanges, double[][] universe) {
+    final int classIdx = m_Instances.classIndex();
+    double widest = 0.0;
+    int w = -1;
+    if (m_NormalizeNodeWidth) {
+      for (int i = 0; i < nodeRanges.length; i++) {
+        double newWidest = nodeRanges[i][WIDTH] / universe[i][WIDTH];
+        if (newWidest > widest) {
+          if (i == classIdx)
+            continue;
+          widest = newWidest;
+          w = i;
+        }
+      }
+    } else {
+      for (int i = 0; i < nodeRanges.length; i++) {
+        if (nodeRanges[i][WIDTH] > widest) {
+          if (i == classIdx)
+            continue;
+          widest = nodeRanges[i][WIDTH];
+          w = i;
+        }
+      }
+    }
+    return w;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KMeansInpiredMethod.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KMeansInpiredMethod.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/KMeansInpiredMethod.java	(revision 29)
@@ -0,0 +1,381 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KMeansInpiredMethod.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that splits a node into two such that the overall sum of squared distances of points to their centres on both sides of the (axis-parallel) splitting plane is minimum.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Ashraf Masood Kibriya (2007). Fast Algorithms for Nearest Neighbour Search. Hamilton, New Zealand.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;mastersthesis{Kibriya2007,
+ *    address = {Hamilton, New Zealand},
+ *    author = {Ashraf Masood Kibriya},
+ *    school = {Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato},
+ *    title = {Fast Algorithms for Nearest Neighbour Search},
+ *    year = {2007}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class KMeansInpiredMethod
+  extends KDTreeNodeSplitter
+  implements TechnicalInformationHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -866783749124714304L;
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class that splits a node into two such that the overall sum "
+      + "of squared distances of points to their centres on both sides " 
+      + "of the (axis-parallel) splitting plane is minimum.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.MASTERSTHESIS);
+    result.setValue(Field.AUTHOR, "Ashraf Masood Kibriya");
+    result.setValue(Field.TITLE, "Fast Algorithms for Nearest Neighbour Search");
+    result.setValue(Field.YEAR, "2007");
+    result.setValue(Field.SCHOOL, "Department of Computer Science, School of Computing and Mathematical Sciences, University of Waikato");
+    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
+
+    return result;
+  }
+
+  /** 
+   * Splits a node into two such that the overall sum of squared distances 
+   * of points to their centres on both sides of the (axis-parallel) 
+   * splitting plane is minimum. The two nodes created after the whole 
+   * splitting are correctly initialised. And, node.left and node.right 
+   * are set appropriately.
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @param nodeRanges The attributes' range for the points inside
+   * the node that is to be split.
+   * @param universe The attributes' range for the whole 
+   * point-space.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(KDTreeNode node, int numNodesCreated,
+      double[][] nodeRanges, double[][] universe) throws Exception {
+
+    correctlyInitialized();
+
+    int splitDim = -1;
+    double splitVal = Double.NEGATIVE_INFINITY;
+
+    double leftAttSum[] = new double[m_Instances.numAttributes()], 
+           rightAttSum[] = new double[m_Instances.numAttributes()], 
+           leftAttSqSum[] = new double[m_Instances.numAttributes()], 
+           rightAttSqSum[] = new double[m_Instances.numAttributes()], 
+           rightSqMean, leftSqMean, leftSqSum, rightSqSum, 
+           minSum = Double.POSITIVE_INFINITY, val;
+
+    for (int dim = 0; dim < m_Instances.numAttributes(); dim++) {
+      // m_MaxRelativeWidth in KDTree ensure there'll be atleast one dim with
+      // width > 0.0
+      if (node.m_NodeRanges[dim][WIDTH] == 0.0
+          || dim == m_Instances.classIndex())
+        continue;
+
+      quickSort(m_Instances, m_InstList, dim, node.m_Start, node.m_End);
+
+      for (int i = node.m_Start; i <= node.m_End; i++) {
+        for (int j = 0; j < m_Instances.numAttributes(); j++) {
+          if (j == m_Instances.classIndex())
+            continue;
+          val = m_Instances.instance(m_InstList[i]).value(j);
+          if (m_NormalizeNodeWidth) {
+            if (Double.isNaN(universe[j][MIN])
+                || universe[j][MIN] == universe[j][MAX])
+              val = 0.0;
+            else
+              val = ((val - universe[j][MIN]) / universe[j][WIDTH]); // normalizing
+                                                                      // value
+          }
+          if (i == node.m_Start) {
+            leftAttSum[j] = rightAttSum[j] = leftAttSqSum[j] = rightAttSqSum[j] = 0.0;
+          }
+          rightAttSum[j] += val;
+          rightAttSqSum[j] += val * val;
+        }
+      }
+
+      for (int i = node.m_Start; i <= node.m_End - 1; i++) {
+        Instance inst = m_Instances.instance(m_InstList[i]);
+        leftSqSum = rightSqSum = 0.0;
+        for (int j = 0; j < m_Instances.numAttributes(); j++) {
+          if (j == m_Instances.classIndex())
+            continue;
+          val = inst.value(j);
+
+          if (m_NormalizeNodeWidth) {
+            if (Double.isNaN(universe[j][MIN])
+                || universe[j][MIN] == universe[j][MAX])
+              val = 0.0;
+            else
+              val = ((val - universe[j][MIN]) / universe[j][WIDTH]); // normalizing
+                                                                      // value
+          }
+
+          leftAttSum[j] += val;
+          rightAttSum[j] -= val;
+          leftAttSqSum[j] += val * val;
+          rightAttSqSum[j] -= val * val;
+          leftSqMean = leftAttSum[j] / (i - node.m_Start + 1);
+          leftSqMean *= leftSqMean;
+          rightSqMean = rightAttSum[j] / (node.m_End - i);
+          rightSqMean *= rightSqMean;
+
+          leftSqSum += leftAttSqSum[j] - (i - node.m_Start + 1) * leftSqMean;
+          rightSqSum += rightAttSqSum[j] - (node.m_End - i) * rightSqMean;
+        }
+
+        if (minSum > (leftSqSum + rightSqSum)) {
+          minSum = leftSqSum + rightSqSum;
+
+          if (i < node.m_End)
+            splitVal = (m_Instances.instance(m_InstList[i]).value(dim) + m_Instances
+                .instance(m_InstList[i + 1]).value(dim)) / 2;
+          else
+            splitVal = m_Instances.instance(m_InstList[i]).value(dim);
+
+          splitDim = dim;
+        }
+      }// end for instance i
+    }// end for attribute dim
+
+    int rightStart = rearrangePoints(m_InstList, node.m_Start, node.m_End,
+        splitDim, splitVal);
+
+    if (rightStart == node.m_Start || rightStart > node.m_End) {
+      System.out.println("node.m_Start: " + node.m_Start + " node.m_End: "
+          + node.m_End + " splitDim: " + splitDim + " splitVal: " + splitVal
+          + " node.min: " + node.m_NodeRanges[splitDim][MIN] + " node.max: "
+          + node.m_NodeRanges[splitDim][MAX] + " node.numInstances: "
+          + node.numInstances());
+
+      if (rightStart == node.m_Start)
+        throw new Exception("Left child is empty in node " + node.m_NodeNumber
+            + ". Not possible with "
+            + "KMeanInspiredMethod splitting method. Please " + "check code.");
+      else
+        throw new Exception("Right child is empty in node " + node.m_NodeNumber
+            + ". Not possible with "
+            + "KMeansInspiredMethod splitting method. Please " + "check code.");
+    }
+
+    node.m_SplitDim = splitDim;
+    node.m_SplitValue = splitVal;
+    node.m_Left = new KDTreeNode(numNodesCreated + 1, node.m_Start,
+        rightStart - 1, m_EuclideanDistance.initializeRanges(m_InstList,
+            node.m_Start, rightStart - 1));
+    node.m_Right = new KDTreeNode(numNodesCreated + 2, rightStart, node.m_End,
+        m_EuclideanDistance
+            .initializeRanges(m_InstList, rightStart, node.m_End));
+  }
+
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param insts	The instances on which the tree is (or is 
+   * to be) built.
+   * @param index The master index array containing indices 
+   * of the instances.
+   * @param attidx The attribution/dimension based on which
+   * the instances should be partitioned.
+   * @param l	The begining index of the portion of master index 
+   * array that should be partitioned. 
+   * @param r	The end index of the portion of master index array 
+   * that should be partitioned.
+   * @return the index of the middle element
+   */
+  protected static int partition(Instances insts, int[] index, int attidx, int l, int r) {
+    
+    double pivot = insts.instance(index[(l + r) / 2]).value(attidx);
+    int help;
+
+    while (l < r) {
+      while ((insts.instance(index[l]).value(attidx) < pivot) && (l < r)) {
+        l++;
+      }
+      while ((insts.instance(index[r]).value(attidx) > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[l];
+        index[l] = index[r];
+        index[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (insts.instance(index[r]).value(attidx) > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+  
+  /**
+   * Sorts the instances according to the given attribute/dimension.
+   * The sorting is done on the master index array and not on the
+   * actual instances object.
+   * 
+   * @param insts The instances on which the tree is (or is 
+   * to be) built.
+   * @param indices The master index array containing indices 
+   * of the instances.
+   * @param attidx The dimension/attribute based on which 
+   * the instances should be sorted.
+   * @param left The begining index of the portion of the master 
+   * index array that needs to be sorted.
+   * @param right The end index of the portion of the master index 
+   * array that needs to be sorted.
+   */
+  protected static void quickSort(Instances insts, int[] indices, int attidx, int left, int right) {
+
+    if (left < right) {
+      int middle = partition(insts, indices, attidx, left, right);
+      quickSort(insts, indices, attidx, left, middle);
+      quickSort(insts, indices, attidx, middle + 1, right);
+    }
+  }  
+
+  /**
+   * Method to validate the sorting done by quickSort().
+   * 
+   * @param insts The instances on which the tree is (or is 
+   * to be) built.
+   * @param indices The master index array containing indices 
+   * of the instances.
+   * @param attidx The dimension/attribute based on which 
+   * the instances should be sorted.
+   * @param start The start of the portion in master index
+   * array that needs to be sorted.
+   * @param end The end of the portion in master index 
+   * array that needs to be sorted.
+   * @throws Exception If the indices of the instances 
+   * are not in sorted order.
+   */
+  private static void checkSort(Instances insts, int[] indices, int attidx, 
+                               int start, int end) throws Exception {
+    for(int i=start+1; i<=end; i++) {
+      if( insts.instance(indices[i-1]).value(attidx) > 
+          insts.instance(indices[i]).value(attidx) ) {
+        System.out.println("value[i-1]: "+insts.instance(indices[i-1]).value(attidx));
+        System.out.println("value[i]: "+insts.instance(indices[i]).value(attidx));
+        System.out.println("indices[i-1]: "+indices[i-1]);
+        System.out.println("indices[i]: "+indices[i]);
+        System.out.println("i: "+i);
+        if(insts.instance(indices[i-1]).value(attidx) > insts.instance(indices[i]).value(attidx))
+          System.out.println("value[i-1] > value[i]");
+        
+        throw new Exception("Indices not sorted correctly.");
+      }//end if
+    }
+  }
+  
+  /** 
+   * Re-arranges the indices array so that in the portion of the array
+   * belonging to the node to be split, the points <= to the splitVal 
+   * are on the left of the portion and those > the splitVal are on the right.
+   * 
+   * @param indices The master index array.
+   * @param startidx The begining index of portion of indices that needs 
+   * re-arranging. 
+   * @param endidx The end index of portion of indices that needs 
+   * re-arranging. 
+   * @param splitDim The split dimension/attribute.
+   * @param splitVal The split value.
+   * @return The startIdx of the points > the splitVal (the points 
+   * belonging to the right child of the node).
+   */
+  protected int rearrangePoints(int[] indices, final int startidx, final int endidx,
+      			      final int splitDim, final double splitVal) {
+    
+    int tmp, left = startidx - 1;
+    for (int i = startidx; i <= endidx; i++) {
+      if (m_EuclideanDistance.valueIsSmallerEqual(m_Instances
+          .instance(indices[i]), splitDim, splitVal)) {
+        left++;
+        tmp = indices[left];
+        indices[left] = indices[i];
+        indices[i] = tmp;
+      }// end valueIsSmallerEqual
+    }// endfor
+    return left + 1;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MedianOfWidestDimension.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MedianOfWidestDimension.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MedianOfWidestDimension.java	(revision 29)
@@ -0,0 +1,227 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MedianOfWidestDimension.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that splits a KDTree node based on the median value of a dimension in which the node's points have the widest spread.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Jerome H. Friedman, Jon Luis Bentley, Raphael Ari Finkel (1977). An Algorithm for Finding Best Matches in Logarithmic Expected Time. ACM Transactions on Mathematics Software. 3(3):209-226.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Friedman1977,
+ *    author = {Jerome H. Friedman and Jon Luis Bentley and Raphael Ari Finkel},
+ *    journal = {ACM Transactions on Mathematics Software},
+ *    month = {September},
+ *    number = {3},
+ *    pages = {209-226},
+ *    title = {An Algorithm for Finding Best Matches in Logarithmic Expected Time},
+ *    volume = {3},
+ *    year = {1977}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class MedianOfWidestDimension
+  extends KDTreeNodeSplitter 
+  implements TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1383443320160540663L;
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class that splits a KDTree node based on the median value of "
+      + "a dimension in which the node's points have the widest spread.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Jerome H. Friedman and Jon Luis Bentley and Raphael Ari Finkel");
+    result.setValue(Field.YEAR, "1977");
+    result.setValue(Field.TITLE, "An Algorithm for Finding Best Matches in Logarithmic Expected Time");
+    result.setValue(Field.JOURNAL, "ACM Transactions on Mathematics Software");
+    result.setValue(Field.PAGES, "209-226");
+    result.setValue(Field.MONTH, "September");
+    result.setValue(Field.VOLUME, "3");
+    result.setValue(Field.NUMBER, "3");
+
+    return result;
+  }
+  
+  /** 
+   * Splits a node into two based on the median value of the dimension 
+   * in which the points have the widest spread. After splitting two 
+   * new nodes are created and correctly initialised. And, node.left 
+   * and node.right are set appropriately.
+   * 
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @param nodeRanges The attributes' range for the points inside
+   * the node that is to be split.
+   * @param universe The attributes' range for the whole 
+   * point-space.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(KDTreeNode node, int numNodesCreated, 
+      			double[][] nodeRanges, double[][] universe) throws Exception {
+    
+    correctlyInitialized();
+    
+    int splitDim = widestDim(nodeRanges, universe);
+    
+    //In this case median is defined to be either the middle value (in case of
+    //odd number of values) or the left of the two middle values (in case of 
+    //even number of values).
+    int medianIdxIdx = node.m_Start + (node.m_End-node.m_Start)/2;
+    //the following finds the median and also re-arranges the array so all 
+    //elements to the left are < median and those to the right are > median.
+    int medianIdx = select(splitDim, m_InstList, node.m_Start, node.m_End, (node.m_End-node.m_Start)/2+1);
+    
+    
+    node.m_SplitDim = splitDim;
+    node.m_SplitValue = m_Instances.instance(m_InstList[medianIdx]).value(splitDim);
+    
+    node.m_Left  = new KDTreeNode(numNodesCreated+1, node.m_Start, medianIdxIdx,
+	m_EuclideanDistance.initializeRanges(m_InstList, node.m_Start, medianIdxIdx));
+    node.m_Right = new KDTreeNode(numNodesCreated+2, medianIdxIdx+1, node.m_End,
+	m_EuclideanDistance.initializeRanges(m_InstList, medianIdxIdx+1, node.m_End));	
+  }
+  
+  /**
+   * Partitions the instances around a pivot. Used by quicksort and
+   * kthSmallestValue.
+   *
+   * @param attIdx The attribution/dimension based on which the 
+   * instances should be partitioned.
+   * @param index The master index array containing indices of the 
+   * instances.
+   * @param l The begining index of the portion of master index 
+   * array that should be partitioned. 
+   * @param r The end index of the portion of master index array 
+   * that should be partitioned.
+   * @return the index of the middle element
+   */
+  protected int partition(int attIdx, int[] index, int l, int r) {
+    
+    double pivot = m_Instances.instance(index[(l + r) / 2]).value(attIdx);
+    int help;
+
+    while (l < r) {
+      while ((m_Instances.instance(index[l]).value(attIdx) < pivot) && (l < r)) {
+        l++;
+      }
+      while ((m_Instances.instance(index[r]).value(attIdx) > pivot) && (l < r)) {
+        r--;
+      }
+      if (l < r) {
+        help = index[l];
+        index[l] = index[r];
+        index[r] = help;
+        l++;
+        r--;
+      }
+    }
+    if ((l == r) && (m_Instances.instance(index[r]).value(attIdx) > pivot)) {
+      r--;
+    } 
+
+    return r;
+  }
+
+  /**
+   * Implements computation of the kth-smallest element according
+   * to Manber's "Introduction to Algorithms".
+   *
+   * @param attIdx The dimension/attribute of the instances in 
+   * which to find the kth-smallest element.
+   * @param indices The master index array containing indices of 
+   * the instances.
+   * @param left The begining index of the portion of the master 
+   * index array in which to find the kth-smallest element.
+   * @param right The end index of the portion of the master index 
+   * array in which to find the kth-smallest element.
+   * @param k The value of k
+   * @return The index of the kth-smallest element
+   */
+  public int select(int attIdx, int[] indices, int left, int right, int k) {
+    
+    if (left == right) {
+      return left;
+    } else {
+      int middle = partition(attIdx, indices, left, right);
+      if ((middle - left + 1) >= k) {
+        return select(attIdx, indices, left, middle, k);
+      } else {
+        return select(attIdx, indices, middle + 1, right, k - (middle - left + 1));
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MidPointOfWidestDimension.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MidPointOfWidestDimension.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/MidPointOfWidestDimension.java	(revision 29)
@@ -0,0 +1,193 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MidPointOfWidestDimension.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that splits a KDTree node based on the midpoint value of a dimension in which the node's points have the widest spread.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * Andrew Moore (1991). A tutorial on kd-trees.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;techreport{Moore1991,
+ *    author = {Andrew Moore},
+ *    booktitle = {University of Cambridge Computer Laboratory Technical Report No. 209},
+ *    howpublished = {Extract from PhD Thesis},
+ *    title = {A tutorial on kd-trees},
+ *    year = {1991},
+ *    HTTP = {http://www.autonlab.org/autonweb/14665.html}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
+ * @version $Revision: 5953 $
+ */
+public class MidPointOfWidestDimension
+  extends KDTreeNodeSplitter 
+  implements TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -7617277960046591906L;
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class that splits a KDTree node based on the midpoint value of "
+      + "a dimension in which the node's points have the widest spread.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.TECHREPORT);
+    result.setValue(Field.AUTHOR, "Andrew Moore");
+    result.setValue(Field.YEAR, "1991");
+    result.setValue(Field.TITLE, "A tutorial on kd-trees");
+    result.setValue(Field.HOWPUBLISHED, "Extract from PhD Thesis");
+    result.setValue(Field.BOOKTITLE, "University of Cambridge Computer Laboratory Technical Report No. 209");
+    result.setValue(Field.HTTP, "http://www.autonlab.org/autonweb/14665.html");
+
+    return result;
+  }
+  
+  /** 
+   * Splits a node into two based on the midpoint value of the dimension 
+   * in which the points have the widest spread. After splitting two 
+   * new nodes are created and correctly initialised. And, node.left 
+   * and node.right are set appropriately.  
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @param nodeRanges The attributes' range for the points inside
+   * the node that is to be split.
+   * @param universe The attributes' range for the whole 
+   * point-space.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(KDTreeNode node, int numNodesCreated, 
+      			double[][] nodeRanges, double[][] universe) throws Exception {
+    
+    correctlyInitialized();
+
+    int splitDim = widestDim(nodeRanges, universe);
+
+    double splitVal = m_EuclideanDistance.getMiddle(nodeRanges[splitDim]);
+
+    int rightStart = rearrangePoints(m_InstList, node.m_Start, node.m_End,
+        splitDim, splitVal);
+
+    if (rightStart == node.m_Start || rightStart > node.m_End) {
+      if (rightStart == node.m_Start)
+        throw new Exception("Left child is empty in node " 
+                            + node.m_NodeNumber + 
+                            ". Not possible with " + 
+                            "MidPointofWidestDim splitting method. Please " + 
+                            "check code.");
+      else
+        throw new Exception("Right child is empty in node " + node.m_NodeNumber + 
+                            ". Not possible with " + 
+                            "MidPointofWidestDim splitting method. Please " + 
+                            "check code.");
+    }
+    
+    node.m_SplitDim = splitDim;
+    node.m_SplitValue = splitVal;
+    node.m_Left = new KDTreeNode(numNodesCreated + 1, node.m_Start,
+        rightStart - 1, m_EuclideanDistance.initializeRanges(m_InstList,
+            node.m_Start, rightStart - 1));
+    node.m_Right = new KDTreeNode(numNodesCreated + 2, rightStart, node.m_End,
+        m_EuclideanDistance
+            .initializeRanges(m_InstList, rightStart, node.m_End));	
+  }
+  
+  /** 
+   * Re-arranges the indices array such that the points <= to the splitVal 
+   * are on the left of the array and those > the splitVal are on the right.
+   * 
+   * @param indices The master index array.
+   * @param startidx The begining index of portion of indices that needs 
+   * re-arranging. 
+   * @param endidx The end index of portion of indices that needs 
+   * re-arranging. 
+   * @param splitDim The split dimension/attribute.
+   * @param splitVal The split value.
+   * @return The startIdx of the points > the splitVal (the points 
+   * belonging to the right child of the node).
+   */
+  protected int rearrangePoints(int[] indices, final int startidx, final int endidx,
+      			      final int splitDim, final double splitVal) {
+    
+    int tmp, left = startidx - 1;
+    for (int i = startidx; i <= endidx; i++) {
+      if (m_EuclideanDistance.valueIsSmallerEqual(m_Instances
+          .instance(indices[i]), splitDim, splitVal)) {
+        left++;
+        tmp = indices[left];
+        indices[left] = indices[i];
+        indices[i] = tmp;
+      }//end if valueIsSmallerEqual
+    }//end for
+    return left + 1;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/SlidingMidPointOfWidestSide.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/SlidingMidPointOfWidestSide.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/neighboursearch/kdtrees/SlidingMidPointOfWidestSide.java	(revision 29)
@@ -0,0 +1,267 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SlidingMidPointOfWidestSide.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch.kdtrees;
+
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+/**
+ <!-- globalinfo-start -->
+ * The class that splits a node into two based on the midpoint value of the dimension in which the node's rectangle is widest. If after splitting one side is empty then it is slided towards the non-empty side until there is at least one point on the empty side.<br/>
+ * <br/>
+ * For more information see also:<br/>
+ * <br/>
+ * David M. Mount (2006). ANN Programming Manual. College Park, MD, USA.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;manual{Mount2006,
+ *    address = {College Park, MD, USA},
+ *    author = {David M. Mount},
+ *    organization = {Department of Computer Science, University of Maryland},
+ *    title = {ANN Programming Manual},
+ *    year = {2006},
+ *    HTTP = {Available from http://www.cs.umd.edu/\~mount/ANN/}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ <!-- options-end -->
+ *
+ * @author  Ashraf M. Kibriya (amk14@waikato.ac.nz)
+ * @version $Revision: 5953 $
+ */
+public class SlidingMidPointOfWidestSide
+  extends KDTreeNodeSplitter 
+  implements TechnicalInformationHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 852857628205680562L;
+
+  /** The floating point error to tolerate in finding the widest 
+   * rectangular side. */
+  protected static double ERR = 0.001;
+
+  /**
+   * Returns a string describing this nearest neighbour search algorithm.
+   * 
+   * @return 		a description of the algorithm for displaying in the
+   *         		explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "The class that splits a node into two based on the midpoint value of "
+      + "the dimension in which the node's rectangle is widest. If after "
+      + "splitting one side is empty then it is slided towards the non-empty "
+      + "side until there is at least one point on the empty side.\n\n"
+      + "For more information see also:\n\n"
+      + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing detailed
+   * information about the technical background of this class, e.g., paper
+   * reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result;
+
+    result = new TechnicalInformation(Type.MANUAL);
+    result.setValue(Field.AUTHOR, "David M. Mount");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.TITLE, "ANN Programming Manual");
+    result.setValue(Field.ORGANIZATION, "Department of Computer Science, University of Maryland");
+    result.setValue(Field.ADDRESS,
+        "College Park, MD, USA");
+    result.setValue(Field.HTTP,
+        "Available from http://www.cs.umd.edu/~mount/ANN/");
+
+    return result;
+  }
+
+  /** 
+   * Splits a node into two based on the midpoint value of the dimension 
+   * in which the node's rectangle is widest. If after splitting one side
+   * is empty then it is slided towards the non-empty side until there is 
+   * at least one point on the empty side. The two nodes created after the 
+   * whole splitting are correctly initialised. And, node.left and 
+   * node.right are set appropriately.  
+   * @param node The node to split.
+   * @param numNodesCreated The number of nodes that so far have been
+   * created for the tree, so that the newly created nodes are 
+   * assigned correct/meaningful node numbers/ids.
+   * @param nodeRanges The attributes' range for the points inside
+   * the node that is to be split.
+   * @param universe The attributes' range for the whole 
+   * point-space.
+   * @throws Exception If there is some problem in splitting the
+   * given node.
+   */
+  public void splitNode(KDTreeNode node, int numNodesCreated,
+      double[][] nodeRanges, double[][] universe) throws Exception {
+
+    correctlyInitialized();
+
+    if (node.m_NodesRectBounds == null) {
+      node.m_NodesRectBounds = new double[2][node.m_NodeRanges.length];
+      for (int i = 0; i < node.m_NodeRanges.length; i++) {
+        node.m_NodesRectBounds[MIN][i] = node.m_NodeRanges[i][MIN];
+        node.m_NodesRectBounds[MAX][i] = node.m_NodeRanges[i][MAX];
+      }
+    }
+
+    // finding widest side of the hyper rectangle
+    double maxRectWidth = Double.NEGATIVE_INFINITY, maxPtWidth = Double.NEGATIVE_INFINITY, tempval;
+    int splitDim = -1, classIdx = m_Instances.classIndex();
+
+    for (int i = 0; i < node.m_NodesRectBounds[0].length; i++) {
+      if (i == classIdx)
+        continue;
+      tempval = node.m_NodesRectBounds[MAX][i] - node.m_NodesRectBounds[MIN][i];
+      if (m_NormalizeNodeWidth) {
+        tempval = tempval / universe[i][WIDTH];
+      }
+      if (tempval > maxRectWidth && node.m_NodeRanges[i][WIDTH] > 0.0)
+        maxRectWidth = tempval;
+    }
+
+    for (int i = 0; i < node.m_NodesRectBounds[0].length; i++) {
+      if (i == classIdx)
+        continue;
+      tempval = node.m_NodesRectBounds[MAX][i] - node.m_NodesRectBounds[MIN][i];
+      if (m_NormalizeNodeWidth) {
+        tempval = tempval / universe[i][WIDTH];
+      }
+      if (tempval >= maxRectWidth * (1 - ERR)
+          && node.m_NodeRanges[i][WIDTH] > 0.0) {
+        if (node.m_NodeRanges[i][WIDTH] > maxPtWidth) {
+          maxPtWidth = node.m_NodeRanges[i][WIDTH];
+          if (m_NormalizeNodeWidth)
+            maxPtWidth = maxPtWidth / universe[i][WIDTH];
+          splitDim = i;
+        }
+      }
+    }
+
+    double splitVal = node.m_NodesRectBounds[MIN][splitDim]
+        + (node.m_NodesRectBounds[MAX][splitDim] - node.m_NodesRectBounds[MIN][splitDim])
+        * 0.5;
+    // might want to try to slide it further to contain more than one point on
+    // the
+    // side that is resulting empty
+    if (splitVal < node.m_NodeRanges[splitDim][MIN])
+      splitVal = node.m_NodeRanges[splitDim][MIN];
+    else if (splitVal >= node.m_NodeRanges[splitDim][MAX])
+      splitVal = node.m_NodeRanges[splitDim][MAX]
+          - node.m_NodeRanges[splitDim][WIDTH] * 0.001;
+
+    int rightStart = rearrangePoints(m_InstList, node.m_Start, node.m_End,
+        splitDim, splitVal);
+
+    if (rightStart == node.m_Start || rightStart > node.m_End) {
+      if (rightStart == node.m_Start)
+        throw new Exception("Left child is empty in node " + node.m_NodeNumber
+            + ". Not possible with "
+            + "SlidingMidPointofWidestSide splitting method. Please "
+            + "check code.");
+      else
+        throw new Exception("Right child is empty in node " + node.m_NodeNumber
+            + ". Not possible with "
+            + "SlidingMidPointofWidestSide splitting method. Please "
+            + "check code.");
+    }
+
+    node.m_SplitDim = splitDim;
+    node.m_SplitValue = splitVal;
+
+    double[][] widths = new double[2][node.m_NodesRectBounds[0].length];
+
+    System.arraycopy(node.m_NodesRectBounds[MIN], 0, widths[MIN], 0,
+        node.m_NodesRectBounds[MIN].length);
+    System.arraycopy(node.m_NodesRectBounds[MAX], 0, widths[MAX], 0,
+        node.m_NodesRectBounds[MAX].length);
+    widths[MAX][splitDim] = splitVal;
+
+    node.m_Left = new KDTreeNode(numNodesCreated + 1, node.m_Start,
+        rightStart - 1, m_EuclideanDistance.initializeRanges(m_InstList,
+            node.m_Start, rightStart - 1), widths);
+
+    widths = new double[2][node.m_NodesRectBounds[0].length];
+    System.arraycopy(node.m_NodesRectBounds[MIN], 0, widths[MIN], 0,
+        node.m_NodesRectBounds[MIN].length);
+    System.arraycopy(node.m_NodesRectBounds[MAX], 0, widths[MAX], 0,
+        node.m_NodesRectBounds[MAX].length);
+    widths[MIN][splitDim] = splitVal;
+
+    node.m_Right = new KDTreeNode(numNodesCreated + 2, rightStart, node.m_End,
+        m_EuclideanDistance.initializeRanges(m_InstList, rightStart, node.m_End), widths);
+  }
+  
+  /** 
+   * Re-arranges the indices array such that the points <= to the splitVal 
+   * are on the left of the array and those > the splitVal are on the right.
+   * 
+   * @param indices The master index array.
+   * @param startidx The begining index of portion of indices that needs 
+   * re-arranging. 
+   * @param endidx The end index of portion of indices that needs 
+   * re-arranging. 
+   * @param splitDim The split dimension/attribute.
+   * @param splitVal The split value.
+   * @return The startIdx of the points > the splitVal (the points 
+   * belonging to the right child of the node).
+   */
+  protected int rearrangePoints(int[] indices, final int startidx,
+      final int endidx, final int splitDim, final double splitVal) {
+
+    int tmp, left = startidx - 1;
+    for (int i = startidx; i <= endidx; i++) {
+      if (m_EuclideanDistance.valueIsSmallerEqual(m_Instances
+          .instance(indices[i]), splitDim, splitVal)) {
+        left++;
+        tmp = indices[left];
+        indices[left] = indices[i];
+        indices[i] = tmp;
+      }// end valueIsSmallerEqual
+    }// endfor
+    return left + 1;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Apply.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Apply.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Apply.java	(revision 29)
@@ -0,0 +1,219 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Apply.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+
+/**
+ * Class encapsulating an Apply Expression.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+class Apply extends Expression {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -2790648331300695083L;
+
+  /** The list of arguments the function encapsulated in this Apply Expression */
+  protected ArrayList<Expression> m_arguments = new ArrayList<Expression>();
+  
+  /** The function to apply (either built-in or a DefineFunction) */
+  protected Function m_function = null;
+  
+  /** The structure of the result of Apply Expression */
+  protected Attribute m_outputStructure = null;
+  
+  /**
+   * Constructor. Reads the function name and argument Expressions for
+   * this Apply Expression.
+   * 
+   * @param apply the Element encapsulating this Apply
+   * @param opType the optype for this expression (taken from either the 
+   * enclosing DefineFunction or DerivedField)
+   * @param fieldDefs an ArrayList of Attributes for the fields that this
+   * Expression might need to access
+   * @param transDict the TransformationDictionary (may be null if there is
+   * no dictionary)
+   * @throws Exception if there is a problem parsing this Apply Expression
+   */
+  protected Apply(Element apply, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs,
+      TransformationDictionary transDict) 
+    throws Exception {
+    super(opType, fieldDefs);
+    
+    String functionName = apply.getAttribute("function");
+    if (functionName == null || functionName.length() == 0) {
+      // try the attribute "name" - a sample file produced by MARS
+      // uses this attribute name rather than "function" as defined
+      // in the PMML spec
+      functionName = apply.getAttribute("name");
+    }
+    
+    if (functionName == null || functionName.length() == 0) {
+      throw new Exception("[Apply] No function name specified!!");
+    }
+    //System.err.println(" *** " + functionName);
+    m_function = Function.getFunction(functionName, transDict);
+    
+    // now read the arguments
+    NodeList children = apply.getChildNodes();
+    for (int i = 0; i < children.getLength(); i++) {
+      Node child = children.item(i);
+      if (child.getNodeType() == Node.ELEMENT_NODE) {
+        String tagName = ((Element)child).getTagName();
+        if (!tagName.equals("Extension")) {
+          //System.err.println(" ++ " + tagName);
+          Expression tempExpression = 
+            Expression.getExpression(tagName, child, m_opType, m_fieldDefs, transDict);
+          if (tempExpression != null) {
+            m_arguments.add(tempExpression);
+          }
+        }
+      }
+    }
+    
+    if (fieldDefs != null) {
+      updateDefsForArgumentsAndFunction();
+    }
+  }
+  
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    super.setFieldDefs(fieldDefs);
+    updateDefsForArgumentsAndFunction();
+  }
+  
+  private void updateDefsForArgumentsAndFunction() throws Exception {
+    for (int i = 0; i < m_arguments.size(); i++) {
+      m_arguments.get(i).setFieldDefs(m_fieldDefs);
+    }
+    
+    // set the parameter defs for the function here so that we can determine
+    // the structure of the output we produce
+    ArrayList<Attribute> functionFieldDefs = new ArrayList<Attribute>(m_arguments.size());
+    for (int i = 0; i < m_arguments.size(); i++) {     
+      functionFieldDefs.add(m_arguments.get(i).getOutputDef());
+    }
+    m_function.setParameterDefs(functionFieldDefs);
+    m_outputStructure = m_function.getOutputDef();
+  }
+
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of evaluating the expression
+   * @throws Exception if there is a problem computing the result
+   */
+  public double getResult(double[] incoming)
+      throws Exception {
+    
+    // assemble incoming to apply function to by processing each Expression
+    // in the list of arguments
+    double[] functionIncoming = new double[m_arguments.size()];
+    //ArrayList<Attribute> functionParamTypes = m_function.getParameters();
+    for (int i = 0; i < m_arguments.size(); i++) {
+      functionIncoming[i] = m_arguments.get(i).getResult(incoming);
+    }
+    
+    
+    double result = m_function.getResult(functionIncoming);
+    
+    return result;
+  }
+
+  /**
+   * Get the result of evaluating the expression for continuous
+   * optype. Is the same as calling getResult() when the optype
+   * is continuous.
+   * 
+   * @param incoming the incoming parameter values
+   * mining schema
+   * @return the result of evaluating the expression.
+   * @throws Exception if the optype is not continuous.
+   */
+  public String getResultCategorical(double[] incoming) throws Exception {
+        
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new IllegalArgumentException("[Apply] Can't return result as "
+          + "categorical/ordinal because optype is continuous!");
+    }
+    
+    double result = getResult(incoming);
+    return m_outputStructure.value((int)result);
+  }
+  
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  public Attribute getOutputDef() {
+    if (m_outputStructure == null) {
+      // return a "default" output def. This will get replaced
+      // by a final one when the final field defs are are set
+      // for all expressions after all derived fields are collected
+      return (m_opType == FieldMetaInfo.Optype.CATEGORICAL ||
+          m_opType == FieldMetaInfo.Optype.ORDINAL)
+      ? new Attribute("Placeholder", new ArrayList<String>())
+      : new Attribute("Placeholder");
+    }
+    return m_outputStructure;//.copy(attName);
+  }
+  
+  public String toString(String pad) {
+    StringBuffer buff = new StringBuffer();
+    
+    // Used for DefineFunctions so that we can see which arguments
+    // correspond to which parameters
+    String[] parameterNames = null;
+    
+    buff.append(pad + "Apply [" + m_function.toString() +"]:\n");
+    buff.append(pad + "args:");
+    if (m_function instanceof DefineFunction) {
+      parameterNames = m_function.getParameterNames();
+    }
+    for (int i = 0; i < m_arguments.size(); i++) {
+      Expression e = m_arguments.get(i);
+      buff.append("\n" + 
+          ((parameterNames != null)
+              ? pad + parameterNames[i] + " = "
+              : "") 
+              + e.toString(pad + "  "));
+    }
+    
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Array.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Array.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Array.java	(revision 29)
@@ -0,0 +1,371 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Array.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+
+/**
+ * Class for encapsulating a PMML Array element.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class Array implements Serializable {
+  
+  /**
+   * Utility method to check if an XML element is an array.
+   * 
+   * @param arrayE the XML element to check
+   * @return returns true if the XML element is an array
+   */
+  public static boolean isArray(Element arrayE) {
+    String name = arrayE.getTagName();
+
+    if (name.equals("Array") || name.equals("NUM-ARRAY") || name.equals("INT-ARRAY")
+        || name.equals("REAL-ARRAY") || name.equals("STRING-ARRAY") 
+        || isSparseArray(arrayE)) {
+      return true;
+    }
+    return false;
+  }
+  
+  /**
+   * Utility method to check if an XML element is a sparse array.
+   * 
+   * @param arrayE the XML element to check.
+   * @return true if the XML element is a sparse array.
+   */
+  private static boolean isSparseArray(Element arrayE) {
+    String name = arrayE.getTagName();
+    
+    if (name.equals("INT-SparseArray") || name.equals("REAL-SparseArray")) {
+      return true;
+    }
+    
+    return false;
+  }
+  
+  /**
+   * Static factory method for creating non-sparse or sparse
+   * array types as needed.
+   * 
+   * @param arrayE the XML element encapsulating the array
+   * @return an appropriate Array type
+   * @throws Exception if there is a problem when constructing the array
+   */
+  public static Array create(Element arrayE) throws Exception {
+    if (!isArray(arrayE)) {
+      throw new Exception("[Array] the supplied element does not contain an array!");
+    }
+    
+    if (isSparseArray(arrayE)) {
+      // TODO: implement sparse array subclass :-)
+    }
+     
+    return new Array(arrayE);
+  }
+  
+  public static enum ArrayType {
+    NUM("NUM-ARRAY"),
+    INT("INT-ARRAY"),
+    REAL("REAL-ARRAY"),
+    STRING("STRING-ARRAY");
+    
+    private final String m_stringVal;
+    
+    ArrayType(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** The values of the array */
+  protected ArrayList<String> m_values = new ArrayList<String>();
+  
+  /** The type of the array */
+  protected ArrayType m_type = ArrayType.NUM;
+  
+  protected Array(Element arrayE) throws Exception {
+    String arrayS = arrayE.getTagName();
+    
+    // get the type of the array
+    if (arrayS.equals("Array")) {
+      String type = arrayE.getAttribute("type"); 
+      if (type.equals("int")) {
+        m_type = ArrayType.INT;
+      } else if (type.equals("real")) {
+        m_type = ArrayType.REAL;
+      } else if (type.equals("string")) {
+        m_type = ArrayType.STRING;
+      }
+    } else {
+      for (ArrayType a : ArrayType.values()) {
+        if (a.toString().equals(arrayS)) {
+          m_type = a;
+          break;
+        }
+      }
+    }
+    // now read the values
+    String contents = arrayE.getChildNodes().item(0).getNodeValue();
+    StringReader sr = new StringReader(contents);
+    StreamTokenizer st = new StreamTokenizer(sr);
+    st.resetSyntax();
+    st.whitespaceChars(0, ' ');
+    st.wordChars(' '+1,'\u00FF');
+    st.whitespaceChars(' ',' ');
+    st.quoteChar('"');
+    st.quoteChar('\'');
+    //m_Tokenizer.eolIsSignificant(true);
+    
+    st.nextToken();
+    while (st.ttype != StreamTokenizer.TT_EOF && 
+        st.ttype != StreamTokenizer.TT_EOL) {
+      m_values.add(st.sval);
+      st.nextToken();
+    }
+  }
+  
+  /**
+   * Get the type of this array.
+   * 
+   * @return the type of the array.
+   */
+  public ArrayType getType() {
+    return m_type;
+  }
+  
+  /**
+   * Is this array a SparseArray?
+   * 
+   * @return true if this array is sparse.
+   */
+  public boolean isSparse() {
+    return false;
+  }
+  
+  /**
+   * Get the number of values in this array.
+   * 
+   * @return the number of values in this array.
+   */
+  public int numValues() {
+    return m_values.size();
+  }
+  
+  /**
+   * Returns true if the array contains this string value.
+   * 
+   * @param value the value to check for.
+   * @return true if the array contains this string value
+   */
+  public boolean contains(String value) {
+    return m_values.contains(value);
+  }
+  
+  /**
+   * Returns true if the array contains this integer value.
+   * 
+   * @param value the value to check for
+   * @return true if the array contains this integer value
+   */
+  public boolean contains(int value) {
+    return contains(new Integer(value).toString());
+  }
+  
+  /**
+   * Returns true if the array contains this real value.
+   * 
+   * @param value the value to check for
+   * @return true if the array contains this real value
+   */
+  public boolean contains(double value) {
+    return contains(new Double(value).toString());
+  }
+  
+  /**
+   * Returns true if the array contains this real value.
+   * 
+   * @param value the value to check for
+   * @return true if the array contains this real value
+   */
+  public boolean contains(float value) {
+    return contains(new Float(value).toString());
+  }
+  
+  private void checkInRange(int index) throws Exception {
+    if (index >= m_values.size() || index < 0) {
+      throw new IllegalArgumentException("[Array] index out of range " + index);
+    }
+  }
+  
+  /**
+   * Gets the value at index from the array.
+   * 
+   * @param index the index of the value to get.
+   * @return the value at index in the array as as String.
+   * @throws Exception if index is out of bounds.
+   */
+  public String value(int index) throws Exception {
+    checkInRange(index);
+    
+    return m_values.get(index);
+  }
+  
+  /**
+   * Gets the value at index from the array as a String. Calls
+   * value().
+   * 
+   * @param index the index of the value to get.
+   * @return the value at index in the array as a String.
+   * @throws Exception if index is out of bounds.
+   */
+  public String valueString(int index) throws Exception {
+    return value(index);
+  }
+  
+  /**
+   * Gets the value at index from the array as a double.
+   * 
+   * @param index the index of the value to get.
+   * @return the value at index in the array as a double.
+   * @throws Exception if index is out of bounds.
+   */
+  public double valueDouble(int index) throws Exception {
+    if (m_type == ArrayType.STRING) {
+      throw new Exception("[Array] Array does not contain numbers!");
+    }
+    return Double.parseDouble(value(index));
+  }
+  
+  /**
+   * Gets the value at index from the array as a float.
+   * 
+   * @param index the index of the value to get.
+   * @return the value at index in the array as a float.
+   * @throws Exception if index is out of bounds.
+   */
+  public float valueFloat(int index) throws Exception {
+    if (m_type == ArrayType.STRING) {
+      throw new Exception("[Array] Array does not contain numbers!");
+    }
+    return Float.parseFloat(value(index));
+  }
+  
+  /**
+   * Gets the value at index from the array as an int.
+   * 
+   * @param index the index of the value to get.
+   * @return the value at index in the array as an int.
+   * @throws Exception if index is out of bounds.
+   */
+  public int valueInt(int index) throws Exception {
+    if (m_type == ArrayType.INT) {
+      throw new Exception("[Array] Array does not contain integers!");
+    }
+    return Integer.parseInt(value(index));
+  }
+  
+  /**
+   * Gets the value at indexOfIndex from the array. Does the
+   * same as value() if this array is not sparse.
+   * 
+   * @param indexOfIndex the index of the index of the value to get.
+   * @return a value from the array as a String.
+   * @throws Exception if indexOfIndex is out of bounds.
+   */
+  public String valueSparse(int indexOfIndex) throws Exception {
+    return value(indexOfIndex);
+  }
+  
+  /**
+   * Gets the value at indexOfIndex from the array. Does the
+   * same as value() if this array is not sparse.
+   * 
+   * @param indexOfIndex the index of the index of the value to get.
+   * @return a value from the array as a String.
+   * @throws Exception if indexOfIndex is out of bounds.
+   */
+  public String valueSparseString(int indexOfIndex) throws Exception {
+    return valueSparse(indexOfIndex);
+  }
+  
+  /**
+   * Gets the value at indexOfIndex from the array. Does the
+   * same as value() if this array is not sparse.
+   * 
+   * @param indexOfIndex the index of the index of the value to get.
+   * @return a value from the array as a double.
+   * @throws Exception if indexOfIndex is out of bounds.
+   */
+  public double valueSparseDouble(int indexOfIndex) throws Exception {
+    return valueDouble(indexOfIndex);
+  }
+  
+  /**
+   * Gets the value at indexOfIndex from the array. Does the
+   * same as value() if this array is not sparse.
+   * 
+   * @param indexOfIndex the index of the index of the value to get.
+   * @return a value from the array as a float.
+   * @throws Exception if indexOfIndex is out of bounds.
+   */
+  public float valueSparseFloat(int indexOfIndex) throws Exception {
+    return valueFloat(indexOfIndex);
+  }
+  
+  /**
+   * Gets the value at indexOfIndex from the array. Does the
+   * same as value() if this array is not sparse.
+   * 
+   * @param indexOfIndex the index of the index of the value to get.
+   * @return a value from the array as an int.
+   * @throws Exception if indexOfIndex is out of bounds.
+   */
+  public int valueSparseInt(int indexOfIndex) throws Exception {
+    return valueInt(indexOfIndex);
+  }
+  
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    
+    text.append("[");
+    for (int i = 0; i < m_values.size(); i++) {
+      text.append(m_values.get(i));
+      if (i < m_values.size() - 1) {
+        text.append(",");
+      }
+    }
+    
+    text.append("]");
+    return text.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInArithmetic.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInArithmetic.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInArithmetic.java	(revision 29)
@@ -0,0 +1,162 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Arithmetic.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import weka.core.Attribute;
+
+/**
+ * Built-in function for +, -, *, /.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class BuiltInArithmetic extends Function {
+  
+  /**
+   * For serialization.
+   */
+  private static final long serialVersionUID = 2275009453597279459L;
+
+  /**
+   * Enumerated type for the operator.
+   */
+  enum Operator {
+    ADDITION (" + ") {
+      double eval(double a, double b) {
+        return a + b;
+      }
+    },
+    SUBTRACTION (" - ") {
+      double eval(double a, double b) {
+        return a - b;
+      }
+    },
+    MULTIPLICATION (" * ") {
+      double eval(double a, double b) {
+        return a * b;
+      }
+    },
+    DIVISION (" / ") {
+      double eval(double a, double b) {
+        return a / b;
+      }
+    };
+    
+    abstract double eval(double a, double b);
+    
+    private final String m_stringVal;
+    
+    Operator(String opName) {
+      m_stringVal = opName;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** The operator for this function */
+  protected Operator m_operator = Operator.ADDITION;
+  
+  /**
+   * Construct a new Arithmetic built-in pmml function.
+   * @param op the operator to use.
+   */
+  public BuiltInArithmetic(Operator op) {
+    m_operator = op;
+    m_functionName = m_operator.toString();
+  }
+  
+  /**
+   * Set the structure of the parameters that are expected as input by
+   * this function. This must be called before getOutputDef() is called.
+   * 
+   * @param paramDefs the structure of the input parameters
+   * @throws Exception if the number or types of parameters are not acceptable by
+   * this function
+   */
+  public void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception {
+    m_parameterDefs = paramDefs;
+    
+    if (m_parameterDefs.size() != 2) {
+      throw new Exception("[Arithmetic] wrong number of parameters. Recieved " 
+          + m_parameterDefs.size() + ", expected 2.");
+    }
+  }
+  
+  /**
+   * Returns an array of the names of the parameters expected
+   * as input by this function
+   * 
+   * @return an array of the parameter names
+   */
+  public String[] getParameterNames() {
+    String[] result = {"A", "B"};
+    return result;
+  }
+  
+  /**
+   * Get the structure of the result produced by this function.
+   * Subclasses must implement.
+   * 
+   * @return the structure of the result produced by this function.
+   */
+  public Attribute getOutputDef() {
+    return new Attribute("BuiltInArithmeticResult:" + m_operator.toString());
+  }
+  
+  /**
+   * Get the result of applying this function.
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   */
+  public double getResult(double[] incoming) throws Exception {
+    if (m_parameterDefs == null) {
+      throw new Exception("[BuiltInArithmetic] incoming parameter structure has not been set!");
+    }
+    
+    if (m_parameterDefs.size() != 2 || incoming.length != 2) {
+      throw new Exception("[BuiltInArithmetic] wrong number of parameters!");
+    }
+    
+    double result = m_operator.eval(incoming[0], incoming[1]);
+    
+    return result;
+  }
+  
+  public String toString() {
+    return toString("");
+  }
+  
+  public String toString(String pad) {
+    return pad + m_parameterDefs.get(0).name() + m_functionName
+      + m_parameterDefs.get(1).name();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInMath.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInMath.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInMath.java	(revision 29)
@@ -0,0 +1,336 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Arithmetic.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import weka.core.Attribute;
+import weka.core.Utils;
+
+/**
+ * Built-in function for min, max, sum, avg, log10,
+ * ln, sqrt, abs, exp, pow, threshold, floor, ceil and round.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class BuiltInMath extends Function {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -8092338695602573652L;
+
+  /**
+   * Enum for the math functions.
+   */
+  enum MathFunc {
+    MIN ("min") {
+      double eval(double[] args) {
+        return args[Utils.minIndex(args)];
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num > 0);
+      }
+      
+      String[] getParameterNames() {
+        return null; // unbounded number of parameters
+      }
+    },
+    MAX ("max") {
+      double eval(double[] args) {
+        return args[Utils.maxIndex(args)];
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num > 0);
+      }
+      
+      String[] getParameterNames() {
+        return null; // unbounded number of parameters
+      }
+    },
+    SUM ("sum") {
+      double eval(double[] args) {
+        return Utils.sum(args);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num > 0);
+      }
+      
+      String[] getParameterNames() {
+        return null; // unbounded number of parameters
+      }
+    },
+    AVG ("avg") {
+      double eval(double[] args) {
+        return Utils.mean(args);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num > 0);
+      }
+      
+      String[] getParameterNames() {
+        return null; // unbounded number of parameters
+      }
+    },
+    LOG10 ("log10") {
+      double eval(double[] args) {
+        return Math.log10(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    LN ("ln") {
+      double eval(double[] args) {
+        return Math.log(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    SQRT ("sqrt") {
+      double eval(double[] args) {
+        return Math.sqrt(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    ABS ("abs") {
+      double eval(double[] args) {
+        return Math.abs(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    EXP ("exp") {
+      double eval(double[] args) {
+        return Math.exp(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    POW ("pow") {
+      double eval(double[] args) {
+        return Math.pow(args[0], args[1]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 2);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A", "B"};
+      }
+    },
+    THRESHOLD ("threshold") {
+      double eval(double[] args) {
+        if (args[0] > args[1]) {
+          return 1.0;
+        } else {
+          return 0.0;
+        }
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 2);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A", "B"};
+      }
+    },
+    FLOOR ("floor") {
+      double eval(double[] args) {
+        return Math.floor(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    CEIL ("ceil") {
+      double eval(double[] args) {
+        return Math.ceil(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    },
+    ROUND ("round") {
+      double eval(double[] args) {
+        return Math.round(args[0]);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"A"};
+      }
+    };
+    
+    abstract double eval(double[] args);
+    abstract boolean legalNumParams(int num);
+    abstract String[] getParameterNames();
+    
+    private final String m_stringVal;
+    
+    MathFunc(String funcName) {
+      m_stringVal = funcName;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** The function to apply */
+  protected MathFunc m_func = MathFunc.ABS;
+  
+  /**
+   * Construct a new built-in pmml Math function.
+   * @param func the math function to use
+   */
+  public BuiltInMath(MathFunc func) {
+    m_func = func;
+    m_functionName = m_func.toString();
+  }
+  
+  /**
+   * Set the structure of the parameters that are expected as input by
+   * this function. This must be called before getOutputDef() is called.
+   * 
+   * @param paramDefs the structure of the input parameters
+   * @throws Exception if the number or types of parameters are not acceptable by
+   * this function
+   */
+  public void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception {
+    m_parameterDefs = paramDefs;
+    
+    if (!m_func.legalNumParams(m_parameterDefs.size())) {
+      throw new Exception("[BuiltInMath] illegal number of parameters for function: " 
+          + m_functionName);
+    }
+  }
+
+  /**
+   * Get the structure of the result produced by this function.
+   * Subclasses must implement.
+   * 
+   * @return the structure of the result produced by this function.
+   */
+  public Attribute getOutputDef() {
+    return new Attribute("BuiltInMathResult:" + m_func.toString());
+  }
+
+  /**
+   * Returns an array of the names of the parameters expected
+   * as input by this function. May return null if the function
+   * can accept an unbounded number of arguments.
+   * 
+   * @return an array of the parameter names (or null if the function
+   * can accept any number of arguments).
+   */
+  public String[] getParameterNames() {
+    return m_func.getParameterNames();
+  }
+
+  /**
+   * Get the result of applying this function.
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   */
+  public double getResult(double[] incoming) throws Exception {
+    if (m_parameterDefs == null) {
+      throw new Exception("[BuiltInMath] incoming parameter structure has not been set");
+    }
+    
+    if (!m_func.legalNumParams(incoming.length)) {
+      throw new Exception("[BuiltInMath] wrong number of parameters!");
+    }
+    
+    double result = m_func.eval(incoming);
+    
+    return result;
+  }
+  
+  public String toString() {
+    String result = m_func.toString() + "(";
+    for (int i = 0; i < m_parameterDefs.size(); i++) {
+      result += m_parameterDefs.get(i).name();
+      if (i != m_parameterDefs.size() - 1) {
+        result += ", ";
+      } else {
+        result += ")";
+      }
+    }
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInString.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInString.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/BuiltInString.java	(revision 29)
@@ -0,0 +1,245 @@
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import weka.core.Attribute;
+
+/**
+ * Built-in function for uppercase, substring and trimblanks.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision 1.0 $
+ */
+public class BuiltInString extends Function {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -7391516909331728653L;
+
+  /**
+   * Enum for the string functions
+   */
+  enum StringFunc {
+    UPPERCASE ("uppercase") {
+      String eval(Object[] args) {
+        return ((String)args[0]).toUpperCase();
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"input"};
+      }
+    },
+    SUBSTRING ("substring") {
+      String eval(Object[] args) {
+        String input = (String)args[0];
+        int startPos = ((Integer)args[1]).intValue();
+        int length = ((Integer)args[2]).intValue();
+        return input.substring(startPos-1, startPos + length);
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 3);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"input", "startPos", "length"};
+      }
+    },
+    TRIMBLANKS ("trimBlanks") {
+      String eval(Object[] args) {
+        return ((String)args[0]).trim();
+      }
+      
+      boolean legalNumParams(int num) {
+        return (num == 1);
+      }
+      
+      String[] getParameterNames() {
+        return new String[] {"input"};
+      }
+    };
+    
+    abstract String eval(Object[] args);
+    abstract boolean legalNumParams(int num);
+    abstract String[] getParameterNames();
+    
+    private String m_stringVal;
+    
+    StringFunc(String funcName) {
+      m_stringVal = funcName;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** The function to apply */
+  protected StringFunc m_func;
+  
+  /** The output structure produced by this function */
+  protected Attribute m_outputDef = null;
+  
+  BuiltInString(StringFunc func) {
+    m_func = func;
+    m_functionName = m_func.toString();
+  }
+
+  /**
+   * Get the structure of the result produced by this function.
+   * Subclasses must implement.
+   * 
+   * @return the structure of the result produced by this function.
+   */
+  public Attribute getOutputDef() {
+    
+    if (m_outputDef == null) {
+      if (m_func == StringFunc.SUBSTRING) {
+        // there is no way we can compute the legal values for this attribute
+        // in advance of the application of this function. So return a string attribute
+        m_outputDef = new Attribute("BuiltInStringResult:substring", (ArrayList<String>)null);
+      }
+      // for the other functions we can compute the resulting set of values
+      Attribute inputVals = m_parameterDefs.get(0);
+      ArrayList<String> newVals = new ArrayList<String>();
+      for (int i = 0; i < inputVals.numValues(); i++) {
+        String inVal = inputVals.value(i);
+        newVals.add(m_func.eval(new Object[] {inVal}));
+      }
+      m_outputDef = new Attribute("BuiltInStringResult:" + m_func.toString(), newVals); 
+    }
+    
+    return m_outputDef;
+  }
+
+  /**
+   * Returns an array of the names of the parameters expected
+   * as input by this function. May return null if the function
+   * can accept an unbounded number of arguments.
+   * 
+   * @return an array of the parameter names (or null if the function
+   * can accept any number of arguments).
+   */
+  public String[] getParameterNames() {
+    return m_func.getParameterNames();
+  }
+  
+  private Object[] setUpArgs(double[] incoming) {
+    // construct the input to the function
+    Object[] args = new Object[incoming.length];
+    Attribute input = m_parameterDefs.get(0);
+    args[0] = input.value((int)incoming[0]);
+    for (int i = 1; i < incoming.length; i++) {
+      args[i] = new Integer((int)incoming[i]);
+    }
+    
+    return args;
+  }
+
+  /**
+   * Get the result of applying this function.
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   */
+  public double getResult(double[] incoming) throws Exception {
+    
+    if (m_parameterDefs == null) {
+      throw new Exception("[BuiltInString] incoming parameter structure has not been set");
+    }
+    
+    if (!m_func.legalNumParams(incoming.length)) {
+      throw new Exception("[BuiltInString] wrong number of parameters!");
+    }
+    
+    // construct the input to the function
+    Object[] args = setUpArgs(incoming);
+    
+    // get the result
+    String result = m_func.eval(args);
+    int resultI = m_outputDef.indexOfValue(result);
+    if (resultI < 0) {
+      if (m_outputDef.isString()) {
+        // add this as a new value
+        resultI = m_outputDef.addStringValue(result);
+      } else {
+        throw new Exception("[BuiltInString] unable to find value " + result
+            + " in nominal result type!");
+      }
+    }
+    
+    return resultI;
+  }
+  
+  /**
+   * Get the result of applying this function when the output type categorical.
+   * Will throw an exception for numeric output. If subclasses output definition
+   * is a string attribute (i.e. because all legal values can't be computed apriori),
+   * then the subclass will need to overide this method and return something sensible
+   * in this case.
+   * 
+   * @param incoming the incoming arguments to this function (supplied in order to match
+   * that of the parameter definitions
+   * @return the result of applying this function as a String.
+   * @throws Exception if this method is not applicable because the optype is not
+   * categorical/ordinal
+   *
+  public String getResultCategorical(double[] incoming) throws Exception {
+    if (m_parameterDefs == null) {
+      throw new Exception("[BuiltInString] incoming parameter structure has not been set");
+    }
+    
+    if (!m_func.legalNumParams(incoming.length)) {
+      throw new Exception("[BuiltInString] wrong number of parameters!");
+    }
+    
+    // construct the input to the function
+    Object[] args = setUpArgs(incoming);
+        
+    // get the result
+    String result = m_func.eval(args);
+    
+    return result;
+  }*/
+
+  /**
+   * Set the structure of the parameters that are expected as input by
+   * this function. This must be called before getOutputDef() is called.
+   * 
+   * @param paramDefs the structure of the input parameters
+   * @throws Exception if the number or types of parameters are not acceptable by
+   * this function
+   */
+  public void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception {
+    
+    m_parameterDefs = paramDefs;
+    
+    if (!m_func.legalNumParams(m_parameterDefs.size())) {
+      throw new Exception("[BuiltInMath] illegal number of parameters for function: " 
+          + m_functionName);
+    }
+  }
+  
+  public String toString() {
+    String result = m_func.toString() + "(";
+    for (int i = 0; i < m_parameterDefs.size(); i++) {
+      result += m_parameterDefs.get(i).name();
+      if (i != m_parameterDefs.size() - 1) {
+        result += ", ";
+      } else {
+        result += ")";
+      }
+    }
+    return result;
+  }
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Constant.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Constant.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Constant.java	(revision 29)
@@ -0,0 +1,154 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Constant.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+
+/**
+ * Class encapsulating a Constant Expression.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision 1.0 $
+ */
+public class Constant extends Expression {
+  
+  /**
+   * For serialization 
+   */
+  private static final long serialVersionUID = -304829687822452424L;
+  
+  protected String m_categoricalConst = null;
+  protected double m_continuousConst = Double.NaN;
+
+  /**
+   * Construct an new Constant Expression.
+   * 
+   * @param constant the xml Element containing the Constant
+   * @param opType the optype for the Constant
+   * @param fieldDefs an ArrayList of Attributes for the fields that this
+   * Expression might need to access (not needed for a constant!)
+   * @throws Exception if the optype is specified as continuous
+   * and there is a problem parsing the value of the Constant
+   */
+  public Constant(Element constant, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs) 
+    throws Exception {
+    super(opType, fieldDefs);
+    
+    NodeList constL = constant.getChildNodes();
+    String c = constL.item(0).getNodeValue();
+    
+    if (m_opType == FieldMetaInfo.Optype.CATEGORICAL ||
+        m_opType == FieldMetaInfo.Optype.ORDINAL) {
+      m_categoricalConst = c;
+    } else {
+      try {
+        m_continuousConst = Double.parseDouble(c);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[Constant] Unable to parse continuous constant: "
+            + c);
+      }
+    }
+  }
+  
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  protected Attribute getOutputDef() {
+    
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      return new Attribute("Constant: " + m_continuousConst);
+    }
+    
+    ArrayList<String> nom = new ArrayList<String>();
+    nom.add(m_categoricalConst);
+    return new Attribute("Constant: " + m_categoricalConst, nom);
+  }
+  
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of evaluating the expression
+   */
+  public double getResult(double[] incoming) {
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      return m_continuousConst;
+    }
+    return 0; // constant (first and only value of a nominal attribute)
+  }
+  
+  /**
+   * Gets the result of evaluating the expression when the
+   * optype is categorical or ordinal as the actual String
+   * value.
+   * 
+   * @param incoming the incoming parameter values 
+   * @return the result of evaluating the expression
+   * @throws Exception if the optype is continuous
+   */
+  public String getResultCategorical(double[] incoming) 
+    throws Exception {
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new IllegalArgumentException("[Constant] Cant't return result as "
+          +"categorical/ordinal as optype is continuous!");
+    }
+    return m_categoricalConst;
+  }
+  
+  public static void main(String[] args) {
+    try {
+      java.io.File f = new java.io.File(args[0]);
+      javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
+      javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
+      org.w3c.dom.Document doc = db.parse(f);
+      doc.getDocumentElement().normalize();
+      NodeList constL = doc.getElementsByTagName("Constant");
+      Node c = constL.item(0);
+      
+      if (c.getNodeType() == Node.ELEMENT_NODE) {
+        Constant constC = new Constant((Element)c, FieldMetaInfo.Optype.CONTINUOUS, null);
+        System.err.println("Value of first constant: " + constC.getResult(null));
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+  
+  public String toString(String pad) {
+    return pad + "Constant: " + ((m_categoricalConst != null)
+        ? m_categoricalConst
+        : "" + m_continuousConst); 
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/DefineFunction.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/DefineFunction.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/DefineFunction.java	(revision 29)
@@ -0,0 +1,248 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DefineFunction.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+
+/**
+ * Class encapsulating DefineFunction (used in TransformationDictionary).
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision 1.0 $
+ */
+public class DefineFunction extends Function {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -1976646917527243888L;
+
+  /**
+   * Inner class for handling Parameters
+   */
+  protected class ParameterField extends FieldMetaInfo {
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = 3918895902507585558L;
+
+    protected ParameterField(Element field) {
+      super(field);
+    }
+    
+    public Attribute getFieldAsAttribute() {
+      if (m_optype == Optype.CONTINUOUS) {
+        return new Attribute(m_fieldName);
+      }
+      // return a string attribute for categorical/ordinal optypes
+      return new Attribute(m_fieldName, (ArrayList<String>)null);
+    }
+  }
+  
+  /** 
+   * The list of parameters expected by this function. We can use this to do 
+   * some error/type checking when users call setParameterDefs() on us 
+   */
+  protected ArrayList<ParameterField> m_parameters = new ArrayList<ParameterField>();
+  
+  /** The optype for this function */
+  FieldMetaInfo.Optype m_optype = FieldMetaInfo.Optype.NONE;
+  
+  /** The Expression for this function to use */
+  protected Expression m_expression = null;
+  
+  public DefineFunction(Element container, TransformationDictionary transDict) throws Exception {
+    
+    m_functionName = container.getAttribute("name");
+    
+    // get the optype for this function
+    String opType = container.getAttribute("optype");
+    if (opType != null && opType.length() > 0) {
+      for (FieldMetaInfo.Optype o : FieldMetaInfo.Optype.values()) {
+        if (o.toString().equals(opType)) {
+          m_optype = o;
+          break;
+        }
+      }
+    } else {
+      throw new Exception("[DefineFunction] no optype specified!!");
+    }
+    
+    m_parameterDefs = new ArrayList<Attribute>();
+    
+    // get all the parameters
+    NodeList paramL = container.getElementsByTagName("ParameterField");
+    for (int i = 0; i < paramL.getLength(); i++) {
+      Node paramN = paramL.item(i);
+      if (paramN.getNodeType() == Node.ELEMENT_NODE) {
+        ParameterField newP = new ParameterField((Element)paramN);
+        m_parameters.add(newP);
+        
+        // set up default parameter definitions - these will probably get replaced
+        // by more informative ones (i.e. possibly nominal attributes instead of
+        // string attributes) later
+        m_parameterDefs.add(newP.getFieldAsAttribute());
+      }
+    }
+    
+    m_expression = Expression.getExpression(container, m_optype, m_parameterDefs, transDict);
+    
+    // check that the optype of the Expression is compatible with ours
+    if (m_optype == FieldMetaInfo.Optype.CONTINUOUS && 
+        m_expression.getOptype() != m_optype) {
+      throw new Exception("[DefineFunction] optype is continuous but our Expression's optype "
+          + "is not.");
+    }
+    
+    if ((m_optype == FieldMetaInfo.Optype.CATEGORICAL || m_optype == FieldMetaInfo.Optype.ORDINAL) !=
+        (m_expression.getOptype() == FieldMetaInfo.Optype.CATEGORICAL || 
+         m_expression.getOptype() == FieldMetaInfo.Optype.ORDINAL)) {
+      throw new Exception("[DefineFunction] optype is categorical/ordinal but our Expression's optype "
+          + "is not."); 
+    }
+  }
+  
+  public void pushParameterDefs() throws Exception {
+    if (m_parameterDefs == null) {
+      throw new Exception("[DefineFunction] parameter definitions are null! Can't "
+          + "push them to encapsulated expression.");
+    }
+    
+    m_expression.setFieldDefs(m_parameterDefs);
+  }
+
+  /**
+   * Get the structure of the result produced by this function.
+   * 
+   * @return the structure of the result produced by this function.
+   */
+  public Attribute getOutputDef() {
+    return m_expression.getOutputDef();
+  }
+
+  /**
+   * Returns an array of the names of the parameters expected
+   * as input by this function. May return null if this function
+   * can take an unbounded number of parameters (i.e. min, max, etc.).
+   * 
+   * @return an array of the parameter names or null if there are an
+   * unbounded number of parameters.
+   */
+  public String[] getParameterNames() {
+    String[] result = new String[m_parameters.size()];
+    for (int i = 0; i < m_parameters.size(); i++) {
+      result[i] = m_parameters.get(i).getFieldName();
+    }
+    
+    return result;
+  }
+
+  /**
+   * Get the result of applying this function.
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   */
+  public double getResult(double[] incoming) throws Exception {
+    
+    if (incoming.length != m_parameters.size()) {
+      throw new IllegalArgumentException("[DefineFunction] wrong number of arguments: expected "
+          + m_parameters.size() + ", recieved " + incoming.length);
+    }
+
+    return m_expression.getResult(incoming);
+  }
+
+  /**
+   * Set the structure of the parameters that are expected as input by
+   * this function. This must be called before getOutputDef() is called.
+   * 
+   * @param paramDefs the structure of the input parameters
+   * @throws Exception if the number or types of parameters are not acceptable by
+   * this function
+   */
+  public void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception {
+    if (paramDefs.size() != m_parameters.size()) {
+      throw new Exception("[DefineFunction] number of parameter definitions does not match "
+          + "number of parameters!");
+    }
+    
+    // check these defs against the optypes of the parameters
+    for (int i = 0; i < m_parameters.size(); i++) {
+      if (m_parameters.get(i).getOptype() == FieldMetaInfo.Optype.CONTINUOUS) {
+        if (!paramDefs.get(i).isNumeric()) {
+          throw new Exception("[DefineFunction] parameter "
+              + m_parameters.get(i).getFieldName() + " is continuous, but corresponding "
+              + "supplied parameter def " + paramDefs.get(i).name() + " is not!");
+        }
+      } else {
+        if (!paramDefs.get(i).isNominal() && !paramDefs.get(i).isString()) {
+          throw new Exception("[DefineFunction] parameter "
+              + m_parameters.get(i).getFieldName() + " is categorical/ordinal, but corresponding "
+              + "supplied parameter def " + paramDefs.get(i).name() + " is not!");
+        }
+      }
+    }
+    
+    // now we need to rename these argument definitions to match the names of
+    // the actual parameters
+    ArrayList<Attribute> newParamDefs = new ArrayList<Attribute>();
+    for (int i = 0; i < paramDefs.size(); i++) {
+      Attribute a = paramDefs.get(i);
+      newParamDefs.add(a.copy(m_parameters.get(i).getFieldName()));
+    }
+    
+    m_parameterDefs = newParamDefs;
+    
+    // update the Expression
+    m_expression.setFieldDefs(m_parameterDefs);
+  }
+  
+  public String toString() {
+    return toString("");
+  }
+  
+  public String toString(String pad) {
+    StringBuffer buff = new StringBuffer();
+    
+    buff.append(pad + "DefineFunction (" + m_functionName + "):\n"
+        + pad + "nparameters:\n");
+    
+    for (ParameterField p : m_parameters) {
+      buff.append(pad + p.getFieldAsAttribute() + "\n");
+    }
+    
+    buff.append(pad + "expression:\n" + m_expression.toString(pad + "  "));
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/DerivedFieldMetaInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/DerivedFieldMetaInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/DerivedFieldMetaInfo.java	(revision 29)
@@ -0,0 +1,136 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DerivedFieldMetaInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+
+public class DerivedFieldMetaInfo extends FieldMetaInfo implements Serializable {
+  
+  /** for serialization */
+  
+  /** display name */
+  protected String m_displayName = null;
+  
+  /** 
+   * the list of values (if the field is ordinal) - may be of size zero if none are specified.
+   * If none are specified, we may be able to construct this by querying the Expression in
+   * this derived field 
+   */
+  protected ArrayList<Value> m_values = new ArrayList<Value>();
+  
+  /** the single expression that defines the value of this field */
+  protected Expression m_expression;
+  
+  public DerivedFieldMetaInfo(Element derivedField, ArrayList<Attribute> fieldDefs,
+                              TransformationDictionary transDict) throws Exception {
+    super(derivedField);
+    // m_fieldName = derivedField.getAttribute("name");
+    String displayName = derivedField.getAttribute("displayName");
+    if (displayName != null && displayName.length() > 0) {
+      m_displayName = displayName;
+    }
+    
+    // get any values
+    NodeList valL = derivedField.getElementsByTagName("Value");
+    if (valL.getLength() > 0) {
+      for (int i = 0; i < valL.getLength(); i++) {
+        Node valueN = valL.item(i);
+        if (valueN.getNodeType() == Node.ELEMENT_NODE) {
+          Value v = new Value((Element)valueN);
+          m_values.add(v);
+        }
+      }
+    }
+    
+    // now get the expression
+    m_expression = Expression.getExpression(derivedField, m_optype, fieldDefs, transDict);
+  }
+  
+  /**
+   * Upadate the field definitions for this derived field
+   * 
+   * @param fieldDefs the fields as an ArrayList of Attributes
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    m_expression.setFieldDefs(fieldDefs);
+  }
+  
+  /**
+   * Upadate the field definitions for this derived field
+   * 
+   * @param fields the fields as an Instances object
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(Instances fields) throws Exception {
+    ArrayList<Attribute> tempDefs = new ArrayList<Attribute>();
+    for (int i = 0; i < fields.numAttributes(); i++) {
+      tempDefs.add(fields.attribute(i));
+    }
+    setFieldDefs(tempDefs);
+  }
+  
+  /**
+   * Get this derived field as an Attribute.
+   * 
+   * @return an Attribute for this derived field.
+   */
+  public Attribute getFieldAsAttribute() {
+    return m_expression.getOutputDef().copy(m_fieldName);
+  }
+  
+  /**
+   * Get the derived field value for the given incoming vector of
+   * values. Incoming values are assumed to be in the same order
+   * as the attributes supplied in the field definitions ArrayList
+   * used to construct this DerivedField.
+   * 
+   * If the optype of this derived field is continuous, then a real
+   * number is returned. Otherwise, the number returned is the index
+   * of the categorical/ordinal value corresponding to result of computing
+   * the derived field value.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of computing the derived value
+   * @throws Exception if there is a problem computing the value
+   */
+  public double getDerivedValue(double[] incoming) throws Exception {
+    return m_expression.getResult(incoming);
+  }
+  
+  public String toString() {
+    StringBuffer buff = new StringBuffer();
+    buff.append(getFieldAsAttribute() + "\nexpression:\n");
+    buff.append(m_expression + "\n");
+    
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Discretize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Discretize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Discretize.java	(revision 29)
@@ -0,0 +1,421 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Discretize.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Utils;
+
+/**
+ * Class encapsulating a Discretize Expression.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class Discretize extends Expression {
+  
+  /**
+   * Inner class to encapsulate DiscretizeBin elements
+   */
+  protected class DiscretizeBin implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = 5810063243316808400L;
+
+    /** The intervals for this DiscretizeBin */
+    private ArrayList<FieldMetaInfo.Interval> m_intervals =
+      new ArrayList<FieldMetaInfo.Interval>();
+    
+    /** The bin value for this DiscretizeBin */
+    private String m_binValue;
+    
+    /** 
+     * If the optype is continuous or ordinal, we will attempt to parse
+     * the bin value as a number and store it here.
+     */
+    private double m_numericBinValue = Utils.missingValue();
+    
+    protected DiscretizeBin(Element bin, 
+        FieldMetaInfo.Optype opType) throws Exception {
+      NodeList iL = bin.getElementsByTagName("Interval");
+      for (int i = 0; i < iL.getLength(); i++) {
+        Node iN = iL.item(i);
+        if (iN.getNodeType() == Node.ELEMENT_NODE) {
+          FieldMetaInfo.Interval tempInterval = new FieldMetaInfo.Interval((Element)iN);
+          m_intervals.add(tempInterval);
+        }
+      }
+      
+      m_binValue = bin.getAttribute("binValue");
+      
+      if (opType == FieldMetaInfo.Optype.CONTINUOUS ||
+          opType == FieldMetaInfo.Optype.ORDINAL) {
+        try {
+          m_numericBinValue = Double.parseDouble(m_binValue);
+        } catch (NumberFormatException ex) {
+          // quietly ignore...
+        }
+      }
+    }
+    
+    /**
+     * Get the bin value for this DiscretizeBin
+     * 
+     * @return the bin value
+     */
+    protected String getBinValue() {
+      return m_binValue;
+    }
+    
+    /**
+     * Get the value of this bin as a number (parsed from the string value).
+     * 
+     * @return the value of this bin as a number or Double.NaN if the string
+     * value of the bin could not be interpreted as a number.
+     */
+    protected double getBinValueNumeric() {
+      return m_numericBinValue;
+    }
+    
+    /**
+     * Returns true if there is an interval that contains the incoming
+     * value.
+     * 
+     * @param value the value to check against
+     * @return true if there is an interval that containst the supplied value
+     */
+    protected boolean containsValue(double value) {
+      boolean result = false;
+      
+      for (FieldMetaInfo.Interval i : m_intervals) {
+        if (i.containsValue(value)) {
+          result = true;
+          break;
+        }
+      }
+
+      return result;
+    }
+    
+    public String toString() {
+      StringBuffer buff = new StringBuffer();
+      
+      buff.append("\"" + m_binValue + "\" if value in: ");
+      boolean first = true;
+      for (FieldMetaInfo.Interval i : m_intervals) {
+        if (!first) {
+          buff.append(", ");
+        } else {
+          first = false;
+        }
+        buff.append(i.toString());
+      }
+      
+      return buff.toString();
+    }
+  }
+  
+  
+  /** The name of the field to be discretized */
+  protected String m_fieldName;
+  
+  /** The index of the field */
+  protected int m_fieldIndex;
+  
+  /** True if a replacement for missing values has been specified */
+  protected boolean m_mapMissingDefined = false;
+  
+  /** The value of the missing value replacement (if defined) */
+  protected String m_mapMissingTo;
+  
+  /** True if a default value has been specified */
+  protected boolean m_defaultValueDefined = false;
+  
+  /** The default value (if defined) */
+  protected String m_defaultValue;
+  
+  /** The bins for this discretization */
+  protected ArrayList<DiscretizeBin> m_bins = new ArrayList<DiscretizeBin>();
+  
+  /** The output structure of this discretization */
+  protected Attribute m_outputDef;
+  
+  /**
+   * Constructs a Discretize Expression
+   * 
+   * @param discretize the Element containing the discretize expression
+   * @param opType the optype of this Discretize Expression
+   * @param fieldDefs the structure of the incoming fields
+   * @throws Exception if the optype is not categorical/ordinal or if there
+   * is a problem parsing this element
+   */
+  public Discretize(Element discretize, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs) 
+    throws Exception {
+    super(opType, fieldDefs);
+  
+/*    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new Exception("[Discretize] must have a categorical or ordinal optype");
+    } */
+    
+    m_fieldName = discretize.getAttribute("field");
+    
+    m_mapMissingTo = discretize.getAttribute("mapMissingTo");
+    if (m_mapMissingTo != null && m_mapMissingTo.length() > 0) {
+      m_mapMissingDefined = true;
+    }
+    
+    m_defaultValue = discretize.getAttribute("defaultValue");
+    if (m_defaultValue != null && m_defaultValue.length() > 0) {
+      m_defaultValueDefined = true;
+    }
+    
+    // get the DiscretizeBin Elements
+    NodeList dbL = discretize.getElementsByTagName("DiscretizeBin");
+    for (int i = 0; i < dbL.getLength(); i++) {
+      Node dbN = dbL.item(i);
+      if (dbN.getNodeType() == Node.ELEMENT_NODE) {
+        Element dbE = (Element)dbN;
+        DiscretizeBin db = new DiscretizeBin(dbE, m_opType);
+        m_bins.add(db);
+      }
+    }
+   
+    if (fieldDefs != null) {
+      setUpField();
+    }
+  }
+  
+  /**
+   * Set the field definitions for this Expression to use
+   * 
+   * @param fieldDefs the field definitions to use
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    super.setFieldDefs(fieldDefs);
+    setUpField();
+  }
+  
+  private void setUpField() throws Exception {
+    m_fieldIndex = -1;
+    
+    if (m_fieldDefs != null) {
+      m_fieldIndex = getFieldDefIndex(m_fieldName);
+      if (m_fieldIndex < 0) {
+        throw new Exception("[Discretize] Can't find field " + m_fieldName
+            + " in the supplied field definitions.");
+      }
+      
+      Attribute field = m_fieldDefs.get(m_fieldIndex);
+      if (!field.isNumeric()) {
+        throw new Exception("[Discretize] reference field " + m_fieldName
+            +" must be continuous.");
+      }
+    }
+    
+    // set up the output structure
+    Attribute tempAtt = null;
+    boolean categorical = false;
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS ||
+        m_opType == FieldMetaInfo.Optype.ORDINAL) {
+      // check to see if all bin values could be parsed as numbers
+      for (DiscretizeBin d : m_bins) {
+        if (Utils.isMissingValue(d.getBinValueNumeric())) {
+          categorical = true;
+          break;
+        }
+      }
+    } else {
+      categorical = true;
+    }
+    tempAtt = (categorical) 
+    ? new Attribute("temp", (ArrayList<String>)null) 
+    : new Attribute(m_fieldName + "_discretized(optype=continuous)");
+      
+    if (categorical) {
+      for (DiscretizeBin d : m_bins) {
+        tempAtt.addStringValue(d.getBinValue());
+      }
+
+      // add the default value (just in case it is some other value than one
+      // of the bins
+      if (m_defaultValueDefined) {
+        tempAtt.addStringValue(m_defaultValue);
+      }
+
+      // add the map missing to value (just in case it is some other value than one
+      // of the bins
+      if (m_mapMissingDefined) {
+        tempAtt.addStringValue(m_mapMissingTo);
+      }
+
+      // now make this into a nominal attribute
+      ArrayList<String> values = new ArrayList<String>();
+      for (int i = 0; i < tempAtt.numValues(); i++) {
+        values.add(tempAtt.value(i)); 
+      }
+
+      m_outputDef = new Attribute(m_fieldName + "_discretized", values);
+    } else {
+      m_outputDef = tempAtt;
+    }
+  }
+
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  protected Attribute getOutputDef() {
+    if (m_outputDef == null) {
+      // return a "default" output def. This will get replaced
+      // by a final one when the final field defs are are set
+      // for all expressions after all derived fields are collected
+      return (m_opType == FieldMetaInfo.Optype.CATEGORICAL || 
+          m_opType == FieldMetaInfo.Optype.ORDINAL)
+      ? new Attribute(m_fieldName + "_discretized", new ArrayList<String>())
+      : new Attribute(m_fieldName + "_discretized(optype=continuous)");
+    }
+    return m_outputDef;
+  }
+
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of evaluating the expression
+   * @throws Exception if there is a problem computing the result
+   */
+  public double getResult(double[] incoming) throws Exception {
+    
+    // default of a missing value for the result if none of the following
+    // logic applies
+    double result = Utils.missingValue();
+    
+    double value = incoming[m_fieldIndex];
+    
+    if (Utils.isMissingValue(value)) {
+      if (m_mapMissingDefined) {
+        if (m_outputDef.isNominal()) {
+          result = m_outputDef.indexOfValue(m_mapMissingTo);
+        } else {
+          try {
+            result = Double.parseDouble(m_mapMissingTo);
+          } catch (NumberFormatException ex) {
+            throw new Exception("[Discretize] Optype is continuous but value of mapMissingTo "
+                +"can not be parsed as a number!");
+          }
+        }
+      }
+    } else {
+      // look for a bin that has an interval that contains this value
+      boolean found = false;
+      for (DiscretizeBin b : m_bins) {
+        if (b.containsValue(value)) {
+          found = true;
+          if (m_outputDef.isNominal()) {
+            result = m_outputDef.indexOfValue(b.getBinValue());
+          } else {
+            result = b.getBinValueNumeric();
+          }
+          break;
+        }
+      }
+      
+      if (!found) {
+        if (m_defaultValueDefined) {
+          if (m_outputDef.isNominal()) {
+            result = m_outputDef.indexOfValue(m_defaultValue);
+          } else {
+            try {
+              result = Double.parseDouble(m_defaultValue);
+            } catch (NumberFormatException ex) {
+              throw new Exception("[Discretize] Optype is continuous but value of " +
+              		"default value can not be parsed as a number!");   
+            }
+          }
+        }
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Gets the result of evaluating the expression when the
+   * optype is categorical or ordinal as the actual String
+   * value.
+   * 
+   * @param incoming the incoming parameter values 
+   * @return the result of evaluating the expression
+   * @throws Exception if the optype is continuous
+   */
+  public String getResultCategorical(double[] incoming) throws Exception {
+    double index = getResult(incoming);
+    if (Utils.isMissingValue(index)) {
+      return "**Missing Value**";
+    }
+    
+    return m_outputDef.value((int)index);
+  }
+  
+  /* (non-Javadoc)
+   * @see weka.core.pmml.Expression#toString(java.lang.String)
+   */
+  public String toString(String pad) {
+    StringBuffer buff = new StringBuffer();
+    
+    buff.append(pad + "Discretize (" + m_fieldName + "):");
+    for (DiscretizeBin d : m_bins) {
+      buff.append("\n" + pad + d.toString());
+    }
+    
+    if (m_outputDef.isNumeric()) {
+      buff.append("\n" + pad + "(bin values interpreted as numbers)");
+    }
+    
+    if (m_mapMissingDefined) {
+      buff.append("\n" + pad + "map missing values to: " + m_mapMissingTo);
+    }
+    
+    if (m_defaultValueDefined) {
+      buff.append("\n" + pad + "default value: " + m_defaultValue);
+    }
+    
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Expression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Expression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Expression.java	(revision 29)
@@ -0,0 +1,255 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Expression.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+
+public abstract class Expression  implements Serializable {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = 4448840549804800321L;
+  
+  /** The optype of this Expression */
+  protected FieldMetaInfo.Optype m_opType;
+
+  /** The field defs */
+  protected ArrayList<Attribute> m_fieldDefs = null;
+  
+  // NOTE - might need to pass in mining schema in order
+  // to determine values for nominal optypes
+  public Expression(FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs) {
+    m_opType = opType;
+    m_fieldDefs = fieldDefs;
+  }
+  
+  /**
+   * Set the field definitions for this Expression to use
+   * 
+   * @param fieldDefs the field definitions to use
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    m_fieldDefs = fieldDefs;
+  }
+    
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of evaluating the expression
+   * @throws Exception if there is a problem computing the result
+   */
+  public abstract double getResult(double[] incoming) throws Exception;
+  
+  /**
+   * Get the result of evaluating the expression for continuous
+   * optype. Is the same as calling getResult() when the optype
+   * is continuous.
+   * 
+   * @param incoming the incoming parameter values
+   * mining schema
+   * @return the result of evaluating the expression.
+   * @throws Exception if the optype is not continuous.
+   */
+  public double getResultContinuous(double[] incoming) throws Exception {
+    if (!(m_opType == FieldMetaInfo.Optype.CONTINUOUS)) {
+      throw new Exception("[Expression] Can't return continuous result "
+          + "as optype is not continuous");
+    }
+    return getResult(incoming);
+  }
+  
+  /**
+   * Gets the result of evaluating the expression when the
+   * optype is categorical or ordinal as the actual String
+   * value.
+   * 
+   * @param incoming the incoming parameter values 
+   * @return the result of evaluating the expression
+   * @throws Exception if the optype is continuous
+   */
+  public abstract String getResultCategorical(double[] incoming) 
+    throws Exception;
+  
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  protected abstract Attribute getOutputDef(); 
+
+  /**
+   * Static factory method that returns a subclass of Expression that
+   * encapsulates the type of expression contained in the Element
+   * supplied. Assumes that there is just one expression contained
+   * in the supplied Element.
+   * 
+   * @param container the Node containing the expression
+   * @param opType the optype of the value returned by this Expression.
+   * @param fieldDefs an ArrayList of Attributes for the fields that this
+   * Expression may need to access
+   * Since Expressions are children of either DerivedFields or
+   * DefineFuntions, they will have the same optype as their parent.
+   * @param transDict the TransformationDictionary (may be null if there
+   * is no dictionary)
+   * @return an Expression object or null if there is no known expression in
+   * the container 
+   * @throws Exception for unsupported Expression types 
+   */
+  public static Expression getExpression(Node container, 
+      FieldMetaInfo.Optype opType,
+      ArrayList<Attribute> fieldDefs,
+      TransformationDictionary transDict) throws Exception {
+    
+    // we need to examine children of this Node to find an expression,
+    // not the entire subtree (as would be returned by Element.getElementsByTagName()
+    
+    Expression result = null;
+    String tagName = "";
+    
+    NodeList children = container.getChildNodes();
+    if (children.getLength() == 0) {
+      throw new Exception("[Expression] container has no children!");
+    }
+    
+    // at this level in the tree there should be only one expression type
+    // specified - look for it here.
+    for (int i = 0; i < children.getLength(); i++) {
+      Node child = children.item(i);
+      if (child.getNodeType() == Node.ELEMENT_NODE) {
+        tagName = ((Element)child).getTagName();
+        result = getExpression(tagName, child, opType, fieldDefs, transDict);
+        if (result != null) {
+          break;
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Static factory method that returns a subclass of Expression that
+   * encapsulates the type of expression supplied as an argument.
+   * 
+   * @param name the name of the Expression to get
+   * @param expression the Node containing the expression
+   * @param opType the optype of the value returned by this Expression.
+   * @param fieldDefs an ArrayList of Attributes for the fields that this
+   * Expression may need to access
+   * Since Expressions are children of either DerivedFields or
+   * DefineFuntions, they will have the same optype as their parent.
+   * @param transDict the TransformationDictionary (may be null if there
+   * is no dictionary)
+   * @return an Expression object or null if there is no known expression in
+   * the container 
+   * @throws Exception for unsupported Expression types 
+   */
+  public static Expression getExpression(String name, 
+      Node expression,
+      FieldMetaInfo.Optype opType,
+      ArrayList<Attribute> fieldDefs,
+      TransformationDictionary transDict) throws Exception {
+   
+    Expression result = null;
+    
+    if (name.equals("Constant")) {
+      // construct a Constant expression
+      result = new Constant((Element)expression, opType, fieldDefs);
+    } else if (name.equals("FieldRef")) {
+      // construct a FieldRef expression
+      result = new FieldRef((Element)expression, opType, fieldDefs);
+    } else if (name.equals("Apply")) {
+      // construct an Apply expression
+      result = new Apply((Element)expression, opType, fieldDefs, transDict);
+    } else if (name.equals("NormDiscrete")) {
+      result = new NormDiscrete((Element)expression, opType, fieldDefs);
+    } else if (name.equals("NormContinuous")) {
+      result = new NormContinuous((Element)expression, opType, fieldDefs);
+    } else if (name.equals("Discretize")) {
+      result = new Discretize((Element)expression, opType, fieldDefs);
+    } else if (name.equals("MapValues") ||
+        name.equals("Aggregate")) {
+      throw new Exception("[Expression] Unhandled Expression type " + name);
+    }
+    return result;
+  }
+  
+  /**
+   * Return the named attribute from the list of reference fields.
+   * 
+   * @param attName the name of the attribute to retrieve
+   * @return the named attribute (or null if it can't be found).
+   */
+  public Attribute getFieldDef(String attName) {
+    Attribute returnV = null;
+    for (int i = 0; i < m_fieldDefs.size(); i++) {
+      if (m_fieldDefs.get(i).name().equals(attName)) {
+        returnV = m_fieldDefs.get(i);
+        break;
+      }
+    }
+    return returnV;
+  }
+  
+  public int getFieldDefIndex(String attName) {
+    int returnV = -1;
+    for (int i = 0; i < m_fieldDefs.size(); i++) {
+      if (m_fieldDefs.get(i).name().equals(attName)) {
+        returnV = i;
+        break;
+      }
+    }
+    return returnV;
+  }
+  
+  /**
+   * Get the optype of the result of applying this Expression.
+   * 
+   * @return the optype of the result of applying this Expression
+   */
+  public FieldMetaInfo.Optype getOptype() {
+    return m_opType;
+  }
+  
+  public String toString() {
+    return toString("");
+  }
+  
+  public String toString(String pad) {
+    return pad + this.getClass().getName();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/FieldMetaInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/FieldMetaInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/FieldMetaInfo.java	(revision 29)
@@ -0,0 +1,317 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FieldMetaInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import org.w3c.dom.Element;
+
+import weka.core.Attribute;
+
+/**
+ * Abstract superclass for various types of field meta
+ * data.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision 1.0 $
+ */
+public abstract class FieldMetaInfo implements Serializable {
+
+  /**
+   * Inner class for Values
+   */
+  public static class Value implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -3981030320273649739L;
+
+    /** The value */
+    protected String m_value;
+    
+    /** 
+     * The display value (might hold a human readable value - e.g.
+     * product name instead of cryptic code).
+     */
+    protected String m_displayValue;
+    
+    /**
+     * Enumerated type for the property. A value
+     * can be valid, invalid or indicate a value
+     * that should be considered as "missing".
+     */
+    public enum Property {
+      VALID ("valid"),
+      INVALID ("invalid"),
+      MISSING ("missing");
+      
+      private final String m_stringVal;
+      
+      Property(String name) {
+        m_stringVal = name;
+      }
+      
+      public String toString() {
+        return m_stringVal;
+      }
+    }
+    protected Property m_property = Property.VALID;
+    
+    /**
+     * Construct a value.
+     * 
+     * @param value the Element containing the value
+     * @throws Exception if there is a problem constucting the value
+     */
+    protected Value(Element value) throws Exception {
+      m_value = value.getAttribute("value");
+      String displayV = value.getAttribute("displayValue");
+      if (displayV != null && displayV.length() > 0) {
+        m_displayValue = displayV;
+      }
+      String property = value.getAttribute("property");
+      if (property != null && property.length() > 0) {
+        for (Property p: Property.values()) {
+          if (p.toString().equals(property)) {
+            m_property = p;
+            break;
+          }
+        }
+      }
+    }
+    
+    public String toString() {
+      String retV = m_value;
+      if (m_displayValue != null) {
+        retV += "(" + m_displayValue + "): " + m_property.toString();
+      }
+      return retV;
+    }
+    
+    public String getValue() {
+      return m_value;
+    }
+    
+    public String getDisplayValue() {
+      return m_displayValue;
+    }
+    
+    public Property getProperty() {
+      return m_property;
+    }
+  }
+  
+  /**
+   * Inner class for an Interval.
+   */
+  public static class Interval implements Serializable {
+    
+    /**
+     * For serialization
+     */
+    private static final long serialVersionUID = -7339790632684638012L;
+
+    /** The left boundary value */
+    protected double m_leftMargin = Double.NEGATIVE_INFINITY;
+    
+    /** The right boundary value */
+    protected double m_rightMargin = Double.POSITIVE_INFINITY;
+    
+    /**
+     * Enumerated type for the closure.
+     */
+    public enum Closure {
+      OPENCLOSED ("openClosed", "(", "]"),
+      OPENOPEN ("openOpen", "(", ")"),
+      CLOSEDOPEN ("closedOpen", "[", ")"),
+      CLOSEDCLOSED ("closedClosed", "[", "]");
+      
+      private final String m_stringVal;
+      private final String m_left;
+      private final String m_right;
+      
+      Closure(String name, String left, String right) {
+        m_stringVal = name;
+        m_left = left;
+        m_right = right;
+      }
+      
+      public String toString() {
+        return m_stringVal;
+      }
+      
+      public String toString(double leftMargin, double rightMargin) {
+        return m_left + leftMargin + "-" + rightMargin + m_right;
+      }
+    }
+    protected Closure m_closure = Closure.OPENOPEN;
+    
+    /**
+     * Construct an interval.
+     * 
+     * @param interval the Element containing the interval
+     * @throws Exception if there is a problem constructing the interval
+     */
+    protected Interval(Element interval) throws Exception {
+      String leftM = interval.getAttribute("leftMargin");
+      try {
+        m_leftMargin = Double.parseDouble(leftM);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[Interval] Can't parse left margin as a number");
+      }
+      
+      String rightM = interval.getAttribute("rightMargin");
+      try {
+        m_rightMargin = Double.parseDouble(rightM);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[Interval] Can't parse right margin as a number");
+      }
+      
+      String closure = interval.getAttribute("closure");
+      if (closure == null || closure.length() == 0) {
+        throw new Exception("[Interval] No closure specified!");
+      }
+      for (Closure c : Closure.values()) {
+        if (c.toString().equals(closure)) {
+          m_closure = c;
+          break;
+        }
+      }
+    }
+    
+    /**
+     * Returns true if this interval contains the supplied value.
+     * 
+     * @param value the value to check
+     * @return true if the interval contains the supplied value
+     */
+    public boolean containsValue(double value) {
+      boolean result = false;
+      
+      switch (m_closure) {
+      case OPENCLOSED:
+        if (value > m_leftMargin && value <= m_rightMargin) {
+          result = true;
+        }
+        break;
+      case OPENOPEN:
+        if (value > m_leftMargin && value < m_rightMargin) {
+          result = true;
+        }
+        break;
+      case CLOSEDOPEN:
+        if (value >= m_leftMargin && value < m_rightMargin) {
+          result = true;
+        }
+        break;
+      case CLOSEDCLOSED:
+        if (value >= m_leftMargin && value <= m_rightMargin) {
+          result = true;
+        }
+        break;
+      default:
+        result = false;
+        break;
+      }
+        
+      return result;
+    }
+    
+    public String toString() {
+      return m_closure.toString(m_leftMargin, m_rightMargin);
+    }
+  }
+
+  // -----------------------------
+  
+
+  /** the name of the field */
+  protected String m_fieldName;
+
+  /**
+   * Enumerated type for the Optype
+   */
+  public enum Optype {
+    NONE ("none"), 
+    CONTINUOUS ("continuous"), 
+    CATEGORICAL ("categorical"),
+    ORDINAL ("ordinal");
+  
+    private final String m_stringVal;
+    
+    Optype(String name) {
+      m_stringVal = name;
+    }
+  
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+
+  /** The optype for the target */
+  protected Optype m_optype = Optype.NONE;
+  
+  /**
+   * Get the optype.
+   * 
+   * @return the optype
+   */
+  public Optype getOptype() {
+    return m_optype;
+  }
+  
+  /**
+   * Get the name of this field.
+   * 
+   * @return the name of this field
+   */
+  public String getFieldName() {
+    return m_fieldName;
+  }
+  
+  /**
+   * Construct a new FieldMetaInfo.
+   * 
+   * @param field the Element containing the field
+   */
+  public FieldMetaInfo(Element field) {
+    m_fieldName = field.getAttribute("name");
+    
+    String opType = field.getAttribute("optype");
+    if (opType != null && opType.length() > 0) {
+      for (Optype o : Optype.values()) {
+        if (o.toString().equals(opType)) {
+          m_optype = o;
+          break;
+        }
+      }
+    }
+  }
+  
+  /**
+   * Return this field as an Attribute.
+   * 
+   * @return an Attribute for this field.
+   */
+  public abstract Attribute getFieldAsAttribute();
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/FieldRef.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/FieldRef.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/FieldRef.java	(revision 29)
@@ -0,0 +1,141 @@
+package weka.core.pmml;
+
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+
+import weka.core.Attribute;
+
+/**
+ * Class encapsulating a FieldRef Expression. Is simply a
+ * pass-through to an existing field.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class FieldRef extends Expression {
+  
+  /** The name of the field to reference */
+  protected String m_fieldName = null;
+  
+  public FieldRef(Element fieldRef, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs) 
+    throws Exception {
+    super(opType, fieldDefs);
+    
+    m_fieldName = fieldRef.getAttribute("field");
+  }
+  
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    super.setFieldDefs(fieldDefs);
+    validateField();    
+  }
+  
+  protected void validateField() throws Exception {
+    // do some type checking here
+    if (m_fieldDefs != null) {
+      Attribute a = getFieldDef(m_fieldName);
+      if (a == null) {
+        throw new Exception("[FieldRef] Can't find field " + m_fieldName
+            + " in the supplied field definitions");
+      }
+      if ((m_opType == FieldMetaInfo.Optype.CATEGORICAL ||
+          m_opType == FieldMetaInfo.Optype.ORDINAL) && a.isNumeric()) {
+        throw new IllegalArgumentException("[FieldRef] Optype is categorical/ordinal but matching "
+            + "parameter in the field definitions is not!");
+      }
+      
+      if (m_opType == FieldMetaInfo.Optype.CONTINUOUS && a.isNominal()) {
+        throw new IllegalArgumentException("[FieldRef] Optype is continuous but matching "
+            + "parameter in the field definitions is not!");
+      }
+    }
+  }
+
+  @Override
+  public double getResult(double[] incoming) throws Exception {
+
+    double result = Double.NaN;
+    boolean found = false;
+    
+    for (int i = 0; i < m_fieldDefs.size(); i++) {
+      Attribute a = m_fieldDefs.get(i);
+      if (a.name().equals(m_fieldName)) {
+        if (a.isNumeric()) {
+          if (m_opType == FieldMetaInfo.Optype.CATEGORICAL ||
+              m_opType == FieldMetaInfo.Optype.ORDINAL) {
+            throw new IllegalArgumentException("[FieldRef] Optype is categorical/ordinal but matching "
+                + "parameter is not!");         
+          }          
+        } else if (a.isNominal()) {
+          if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+            throw new IllegalArgumentException("[FieldRef] Optype is continuous but matching "
+                + "parameter is not!");
+          }
+        } else {
+          throw new IllegalArgumentException("[FieldRef] Unhandled attribute type");
+        }
+        result = incoming[i];
+        found = true;
+        break;
+      }
+    }
+    
+    if (!found) {
+      throw new Exception("[FieldRef] this field: " + m_fieldName + " is not in the supplied "
+          + "list of parameters!");
+    }
+    return result;
+  }
+
+  @Override
+  public String getResultCategorical(double[] incoming)
+      throws Exception {
+    
+    if (m_opType == FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new IllegalArgumentException("[FieldRef] Can't return result as "
+          +"categorical/ordinal because optype is continuous!");
+    }
+    
+    boolean found = false;
+    String result = null;
+    
+    for (int i = 0; i < m_fieldDefs.size(); i++) {
+      Attribute a = m_fieldDefs.get(i);
+      if (a.name().equals(m_fieldName)) {
+        found = true;
+        result = a.value((int)incoming[i]);
+        break;
+      }
+    }
+    
+    if (!found) {
+      throw new Exception("[FieldRef] this field: " + m_fieldName + " is not in the supplied "
+          + "list of parameters!");
+    }
+    return result;
+  }
+  
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  public Attribute getOutputDef() {
+    
+    Attribute a = getFieldDef(m_fieldName);
+    if (a != null) {
+      return a;
+      /* Attribute result = a.copy(attName);
+      return result; */
+    }
+    
+    // If we can't find the reference field in the field definitions then
+    // we can't return a definition for the result
+    return null;
+  }
+  
+  public String toString(String pad) {
+    return pad + "FieldRef: " + m_fieldName;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/Function.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/Function.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/Function.java	(revision 29)
@@ -0,0 +1,240 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Function.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import weka.core.Attribute;
+
+/**
+ * Abstract superclass for PMML built-in and DefineFunctions.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public abstract class Function implements Serializable {
+  
+  /**
+   * For serialization 
+   */
+  private static final long serialVersionUID = -6997738288201933171L;
+  
+  /** The name of this function */
+  protected String m_functionName;
+  
+  /** The structure of the parameters to this function */
+  protected ArrayList<Attribute> m_parameterDefs = null;
+  
+    
+  public String getName() {
+    return m_functionName;
+  }
+  
+  /**
+   * Returns an array of the names of the parameters expected
+   * as input by this function. May return null if this function
+   * can take an unbounded number of parameters (i.e. min, max, etc.).
+   * 
+   * @return an array of the parameter names or null if there are an
+   * unbounded number of parameters.
+   */
+  public abstract String[] getParameterNames();
+  
+  /**
+   * Set the structure of the parameters that are expected as input by
+   * this function. This must be called before getOutputDef() is called.
+   * 
+   * @param paramDefs the structure of the input parameters
+   * @throws Exception if the number or types of parameters are not acceptable by
+   * this function
+   */
+  public abstract void setParameterDefs(ArrayList<Attribute> paramDefs) throws Exception;
+  
+  /**
+   * Get the structure of the result produced by this function.
+   * 
+   * @return the structure of the result produced by this function.
+   */
+  public abstract Attribute getOutputDef();
+  
+  /**
+   * Get the result of applying this function.
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   */
+  public abstract double getResult(double[] incoming) throws Exception;
+  
+  /**
+   * Get the result of applying this function. Subclasses should overide this
+   * method when they might produce categorical values where the legal set of
+   * values can't be determined apriori (i.e. by just using the input parameter
+   * definitions). An example is the substring function - in this case there
+   * is no way of knowing apriori what all the legal values will be because the
+   * start position and length parameters are not known until the function is
+   * invoked. In this scenario, a client could call getResultCategorical()
+   * repeatedly (in an initialization routine) in order to manually build the
+   * list of legal values and then call this method at processing time, passing
+   * in the pre-computed output structure.
+   * 
+   *  This default implementation ignores the supplied output definition argument
+   *  and simply invokes getResult(incoming).
+   * 
+   * @param incoming the arguments to this function (supplied in order to match that
+   * of the parameter definitions
+   * @param outputDef the output definition to use for looking up the index of
+   * result values (in the case of categorical results)
+   * @return the result of applying this function. When the optype is
+   * categorical or ordinal, an index into the values of the output definition
+   * is returned.
+   * @throws Exception if there is a problem computing the result of this function
+   *
+  public double getResult(double[] incoming, Attribute outputDef) throws Exception {
+    if (outputDef.isString()) {
+      throw new Exception("[Function] outputDef argument must not be a String attribute!");
+    }
+    return getResult(incoming);
+  }*/
+  
+  /**
+   * Get the result of applying this function when the output type categorical.
+   * Will throw an exception for numeric output. If subclasses output definition
+   * is a string attribute (i.e. because all legal values can't be computed apriori),
+   * then the subclass will need to overide this method and return something sensible
+   * in this case.
+   * 
+   * @param incoming the incoming arguments to this function (supplied in order to match
+   * that of the parameter definitions
+   * @return the result of applying this function as a String.
+   * @throws Exception if this method is not applicable because the optype is not
+   * categorical/ordinal
+   *
+  public String getResultCategorical(double[] incoming) throws Exception {
+    if (getOutputDef().isNumeric()) {
+      throw new Exception("[Function] can't return nominal value, output is numeric!!");
+    }
+    
+    if (getOutputDef().isString()) {
+      throw new Exception("[Function] subclass neeeds to overide this method and do "
+          + "something sensible when the output def is a string attribute.");
+    }
+    
+    return getOutputDef().value((int)getResult(incoming));
+  } */
+  
+  
+  //public static FieldMetaInfo.Optype
+  
+  /**
+   * Get a built-in PMML Function.
+   * 
+   * @param name the name of the function to get.
+   * @return a built-in Function or null if the named function is not 
+   * known/supported.
+   */
+  public static Function getFunction(String name) {
+    Function result = null;
+    
+    name = name.trim();
+    if (name.equals("+")) {
+      result = new BuiltInArithmetic(BuiltInArithmetic.Operator.ADDITION);
+    } else if (name.equals("-")) {
+      result = new BuiltInArithmetic(BuiltInArithmetic.Operator.SUBTRACTION);
+    } else if (name.equals("*")) {
+      result = new BuiltInArithmetic(BuiltInArithmetic.Operator.MULTIPLICATION);
+    } else if (name.equals("/")) {
+      result = new BuiltInArithmetic(BuiltInArithmetic.Operator.DIVISION);
+    } else if (name.equals(BuiltInMath.MathFunc.MIN.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.MIN);
+    } else if (name.equals(BuiltInMath.MathFunc.MAX.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.MAX);
+    } else if (name.equals(BuiltInMath.MathFunc.SUM.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.SUM);
+    } else if (name.equals(BuiltInMath.MathFunc.AVG.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.AVG);
+    } else if (name.equals(BuiltInMath.MathFunc.LOG10.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.LOG10);
+    } else if (name.equals(BuiltInMath.MathFunc.LN.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.LN);
+    } else if (name.equals(BuiltInMath.MathFunc.SQRT.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.SQRT);
+    } else if (name.equals(BuiltInMath.MathFunc.ABS.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.ABS);
+    } else if (name.equals(BuiltInMath.MathFunc.EXP.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.EXP);
+    } else if (name.equals(BuiltInMath.MathFunc.POW.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.POW);
+    } else if (name.equals(BuiltInMath.MathFunc.THRESHOLD.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.THRESHOLD);
+    } else if (name.equals(BuiltInMath.MathFunc.FLOOR.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.FLOOR);
+    } else if (name.equals(BuiltInMath.MathFunc.CEIL.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.CEIL);
+    } else if (name.equals(BuiltInMath.MathFunc.ROUND.toString())) {
+      result = new BuiltInMath(BuiltInMath.MathFunc.ROUND);
+    }
+    
+    return result;
+  }
+  
+  
+  /**
+   * Get either a function. Built-in functions are queried first, and then
+   * DefineFunctions in the TransformationDictionary (if any).
+   * 
+   * @param name the name of the function to get.
+   * @param transDict the TransformationDictionary (may be null if there is
+   * no dictionary).
+   * @return the function
+   * @throws Exception if the named function is not known/supported.
+   */
+  public static Function getFunction(String name, TransformationDictionary transDict)
+    throws Exception {
+    
+    Function result = getFunction(name);
+    
+    // try the defined functions in the TransformationDictionary (if any)
+    if (result == null && transDict != null) {
+      result = transDict.getFunction(name);
+    }
+    
+    if (result == null) {
+      throw new Exception("[Function] unknown/unsupported function " + name);
+    }
+    
+    return result;
+  }
+  
+  public String toString() {
+    return toString("");
+  }
+  
+  public String toString(String pad) {
+    return pad + this.getClass().getName();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/MappingInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/MappingInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/MappingInfo.java	(revision 29)
@@ -0,0 +1,314 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MappingInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.Logger;
+
+/**
+ * Class that maintains the mapping between incoming data set structure
+ * and that of the mining schema.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 5987 $
+ */
+public class MappingInfo implements Serializable {
+  
+  /** For serialization */
+  
+  /** 
+   * Index for incoming nominal values that are not defined in the mining
+   * schema.
+   */
+  public static final int UNKNOWN_NOMINAL_VALUE = -1;
+  
+  /** 
+   * Map the incoming attributes to the mining schema attributes.
+   * Each entry holds the index of the incoming attribute that
+   * corresponds to this mining schema attribute.  
+   */
+  private int[] m_fieldsMap = null;
+  
+  /** 
+   * Map indexes for nominal values in incoming structure to those
+   * in the mining schema. There will be as many entries as there are
+   * attributes in this array. Non-nominal attributes will have
+   * null entries. Each non-null entry is an array of integer indexes.
+   * Each entry in a given array (for a given attribute) holds the index of
+   * the mining schema value that corresponds to this incoming value.
+   * UNKNOWN_NOMINAL_VALUE is used as the index for those incoming values 
+   * that are not defined in the mining schema. 
+   */
+  private int[][] m_nominalValueMaps = null;
+  
+  /** Holds a textual description of the fields mapping */
+  private String m_fieldsMappingText = null;
+  
+  /** For logging */
+  private Logger m_log = null;
+  
+  public MappingInfo(Instances dataSet, MiningSchema miningSchema,
+                     Logger log) throws Exception {
+    m_log = log;
+    //miningSchema.convertStringAttsToNominal();
+    Instances fieldsI = miningSchema.getMiningSchemaAsInstances();
+ 
+    m_fieldsMap = new int[fieldsI.numAttributes()];
+    m_nominalValueMaps = new int[fieldsI.numAttributes()][];
+    
+    for (int i = 0; i < fieldsI.numAttributes(); i++) {
+      String schemaAttName = fieldsI.attribute(i).name();
+      boolean found = false;
+      for (int j = 0; j < dataSet.numAttributes(); j++) {
+        if (dataSet.attribute(j).name().equals(schemaAttName)) {
+          Attribute miningSchemaAtt = fieldsI.attribute(i);
+          Attribute incomingAtt = dataSet.attribute(j);
+          // check type match
+          if (miningSchemaAtt.type() != incomingAtt.type()) {
+            throw new Exception("[MappingInfo] type mismatch for field " +
+                schemaAttName + ". Mining schema type " + 
+                miningSchemaAtt.toString() + ". Incoming type " +
+                incomingAtt.toString() + ".");
+          }
+
+          // check nominal values (number, names...)
+          if (miningSchemaAtt.numValues() != incomingAtt.numValues()) {
+            String warningString = "[MappingInfo] WARNING: incoming nominal attribute "
+              + incomingAtt.name() + " does not have the same "
+              + "number of values as the corresponding mining "
+              + "schema attribute.";
+            if (m_log != null) {
+              m_log.logMessage(warningString);
+            } else {
+              System.err.println(warningString);
+            }
+          }
+          if (miningSchemaAtt.isNominal() || miningSchemaAtt.isString()) {
+            int[] valuesMap = new int[incomingAtt.numValues()];
+            for (int k = 0; k < incomingAtt.numValues(); k++) {
+              String incomingNomVal = incomingAtt.value(k);
+              int indexInSchema = miningSchemaAtt.indexOfValue(incomingNomVal);
+              if (indexInSchema < 0) {
+                String warningString = "[MappingInfo] WARNING: incoming nominal attribute "
+                  + incomingAtt.name() + " has value "
+                  + incomingNomVal + " that doesn't occur in the mining schema.";
+                if (m_log != null) {
+                  m_log.logMessage(warningString);
+                } else {
+                  System.err.println(warningString);
+                }
+                valuesMap[k] = UNKNOWN_NOMINAL_VALUE;
+              } else {
+                valuesMap[k] = indexInSchema;
+              }
+            }
+            m_nominalValueMaps[i] = valuesMap;
+          }
+
+          /*if (miningSchemaAtt.isNominal()) {
+            for (int k = 0; k < miningSchemaAtt.numValues(); k++) {
+              if (!miningSchemaAtt.value(k).equals(incomingAtt.value(k))) {
+                throw new Exception("[PMMLUtils] value " + k + " (" + 
+                                    miningSchemaAtt.value(k) + ") does not match " +
+                                    "incoming value (" + incomingAtt.value(k) +
+                                    ") for attribute " + miningSchemaAtt.name() +
+                                    ".");
+
+              }
+            }
+          }*/
+          found = true;
+          m_fieldsMap[i] = j;
+        }
+      }
+      if (!found) {
+        throw new Exception("[MappingInfo] Unable to find a match for mining schema "
+            + "attribute " + schemaAttName + " in the "
+            + "incoming instances!");
+      }
+    }
+
+    // check class attribute (if set)
+   if (fieldsI.classIndex() >= 0) {
+      if (dataSet.classIndex() < 0) {
+	// first see if we can find a matching class
+	String className = fieldsI.classAttribute().name();
+	Attribute classMatch = dataSet.attribute(className);
+	if (classMatch == null) {
+	  throw new Exception("[MappingInfo] Can't find match for target field " + className
+	      + "in incoming instances!");
+	}
+	dataSet.setClass(classMatch);
+      } else if (!fieldsI.classAttribute().name().equals(dataSet.classAttribute().name())) {
+        throw new Exception("[MappingInfo] class attribute in mining schema does not match "
+            + "class attribute in incoming instances!");
+      }
+    }
+    
+    // Set up the textual description of the mapping
+    fieldsMappingString(fieldsI, dataSet);
+  }
+  
+  private void fieldsMappingString(Instances miningSchemaI, Instances incomingI) {
+    StringBuffer result = new StringBuffer();
+    
+    int maxLength = 0;
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      if (miningSchemaI.attribute(i).name().length() > maxLength) {
+        maxLength = miningSchemaI.attribute(i).name().length();
+      }
+    }
+    maxLength += 12; // length of " (nominal)"/" (numeric)"
+    
+    int minLength = 13; // "Mining schema".length()                                                        
+    String headerS = "Mining schema";
+    String sep = "-------------";
+
+    if (maxLength < minLength) {
+      maxLength = minLength;
+    }
+    
+    headerS = PMMLUtils.pad(headerS, " ", maxLength, false);
+    sep = PMMLUtils.pad(sep, "-", maxLength, false);
+    
+    sep += "\t    ----------------\n";
+    headerS += "\t    Incoming fields\n";
+    result.append(headerS);
+    result.append(sep);
+    
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      Attribute temp = miningSchemaI.attribute(i);
+      String attName = "("
+        + ((temp.isNumeric())
+           ? "numeric)"
+           : "nominal)")
+        + " " + temp.name();
+      attName = PMMLUtils.pad(attName, " ", maxLength, false);
+      attName +=  "\t--> ";
+      result.append(attName);
+      
+      Attribute incoming = incomingI.attribute(m_fieldsMap[i]);
+      String fieldName = "" + (m_fieldsMap[i] + 1) + " ("
+        + ((incoming.isNumeric())
+            ? "numeric)"
+            : "nominal)");
+      fieldName += " " + incoming.name();
+      result.append(fieldName + "\n");
+    }
+    
+    m_fieldsMappingText = result.toString();
+  }
+  
+  /**
+   * Convert an <code>Instance</code> to an array of values that matches the
+   * format of the mining schema. First maps raw attribute values and then
+   * applies rules for missing values, outliers etc.
+   *
+   * @param inst the <code>Instance</code> to convert
+   * @param miningSchema the mining schema
+   * incoming instance attributes
+   * @return an array of doubles that are values from the incoming Instances,
+   * correspond to the format of the mining schema and have had missing values,
+   * outliers etc. dealt with.
+   * @throws Exception if something goes wrong
+   */
+  public double[] instanceToSchema(Instance inst, 
+                                   MiningSchema miningSchema) throws Exception {
+    Instances miningSchemaI = miningSchema.getMiningSchemaAsInstances();
+    
+    // allocate enough space for both mining schema fields and any derived fields
+    double[] result = new double[miningSchema.getFieldsAsInstances().numAttributes()];
+
+    // Copy over the values
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      //if (miningSchemaI.attribute(i).isNumeric()) {
+      result[i] = inst.value(m_fieldsMap[i]);
+      if (miningSchemaI.attribute(i).isNominal() || 
+          miningSchemaI.attribute(i).isString()) {
+        // If not missing, look up the index of this incoming categorical value in
+        // the mining schema
+        if (!Utils.isMissingValue(inst.value(m_fieldsMap[i]))) {
+          int[] valueMap = m_nominalValueMaps[i];
+          int index = valueMap[(int)inst.value(m_fieldsMap[i])];
+          String incomingAttValue = 
+            inst.attribute(m_fieldsMap[i]).value((int)inst.value(m_fieldsMap[i]));
+          /*int index = miningSchemaI.attribute(i).indexOfValue(incomingAttValue); */
+          if (index >= 0) {
+            result[i] = index;
+          } else {
+            // set this to "unknown" (-1) for nominal valued attributes
+            result[i] = UNKNOWN_NOMINAL_VALUE;
+            String warningString = "[MappingInfo] WARNING: Can't match nominal value "
+              + incomingAttValue;
+            if (m_log != null) {
+              m_log.logMessage(warningString);
+            } else {
+              System.err.println(warningString);
+            }
+          }
+        }
+      }
+    }
+
+    // Now deal with missing values and outliers...
+    miningSchema.applyMissingAndOutlierTreatments(result);
+    //    printInst(result);
+    
+    // now fill in any derived values
+    ArrayList<DerivedFieldMetaInfo> derivedFields = miningSchema.getDerivedFields();
+    for (int i = 0; i < derivedFields.size(); i++) {
+      DerivedFieldMetaInfo temp = derivedFields.get(i);
+//      System.err.println("Applying : " + temp);
+      double r = temp.getDerivedValue(result);
+      result[i + miningSchemaI.numAttributes()] = r;
+    }
+    
+    /*System.err.print("==> ");
+    for (int i = 0; i < result.length; i++) {
+      System.err.print(" " + result[i]);
+    }
+    System.err.println();*/
+    
+    return result;
+  }
+  
+  /**
+   * Get a textual description of them mapping between mining schema
+   * fields and incoming data fields.
+   * 
+   * @return a description of the fields mapping as a String
+   */
+  public String getFieldsMappingString() {
+    if (m_fieldsMappingText == null) {
+      return "No fields mapping constructed!";
+    }
+    return m_fieldsMappingText;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/pmml/MiningFieldMetaInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/MiningFieldMetaInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/MiningFieldMetaInfo.java	(revision 29)
@@ -0,0 +1,352 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MiningFieldMetaInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+
+import org.w3c.dom.Element;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+
+/**
+ * Class encapsulating information about a MiningField.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class MiningFieldMetaInfo extends FieldMetaInfo implements Serializable {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -1256774332779563185L;
+  
+  enum Usage {
+    ACTIVE ("active"),
+    PREDICTED ("predicted"),
+    SUPPLEMENTARY ("supplementary"),
+    GROUP ("group"),
+    ORDER ("order");
+    
+    private final String m_stringVal;
+    Usage(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  
+  /** usage type */
+  Usage m_usageType = Usage.ACTIVE;
+
+  enum Outlier {
+    ASIS ("asIs"),
+    ASMISSINGVALUES ("asMissingValues"),
+    ASEXTREMEVALUES ("asExtremeValues");
+    
+    private final String m_stringVal;
+    Outlier(String name){
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  /** outlier treatmemnt method */
+  protected Outlier m_outlierTreatmentMethod = Outlier.ASIS;
+  
+  /** outlier low value */
+  protected double m_lowValue;
+  /** outlier high value */
+  protected double m_highValue;
+  
+  enum Missing {
+    ASIS ("asIs"),
+    ASMEAN ("asMean"),
+    ASMODE ("asMode"),
+    ASMEDIAN ("asMedian"),
+    ASVALUE ("asValue");
+    
+    private final String m_stringVal;
+    Missing(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+  /** missing values treatment method */
+  protected Missing m_missingValueTreatmentMethod = Missing.ASIS;    
+
+
+  /** actual missing value replacements (if specified) */
+  protected String m_missingValueReplacementNominal;
+  protected double m_missingValueReplacementNumeric;
+
+  /** optype overrides (override data dictionary type - NOT SUPPORTED AT PRESENT) */
+  protected FieldMetaInfo.Optype m_optypeOverride = FieldMetaInfo.Optype.NONE;
+
+  /** the index of the field in the mining schema Instances */
+  protected int m_index;
+
+  /** importance (if defined) */
+  protected double m_importance;
+  
+  /** mining schema (needed for toString method) */
+  Instances m_miningSchemaI = null;
+
+  // TO-DO: invalid values?
+  
+  /**
+   * Set the Instances that represent the mining schema. Needed so that
+   * the toString() method for this class can output attribute names
+   * and values.
+   * 
+   * @param miningSchemaI the mining schema as an Instances object
+   */
+  protected void setMiningSchemaInstances(Instances miningSchemaI) {
+    m_miningSchemaI = miningSchemaI;
+  }
+  
+  /**
+   * Get the usage type of this field.
+   *
+   * @return the usage type of this field
+   */
+  public Usage getUsageType() {
+    return m_usageType;
+  }
+
+  /**
+   * Return a textual representation of this MiningField.
+   * 
+   * @return a String describing this mining field
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    temp.append(m_miningSchemaI.attribute(m_index));
+    temp.append("\n\tusage: " + m_usageType 
+                + "\n\toutlier treatment: " + m_outlierTreatmentMethod);
+    if (m_outlierTreatmentMethod == Outlier.ASEXTREMEVALUES) {
+      temp.append(" (lowValue = " + m_lowValue + " highValue = " + m_highValue + ")");
+    }
+
+    temp.append("\n\tmissing value treatment: " 
+                + m_missingValueTreatmentMethod);
+    if (m_missingValueTreatmentMethod != Missing.ASIS) {
+      temp.append(" (replacementValue = " 
+                  + ((m_missingValueReplacementNominal != null)
+                     ? m_missingValueReplacementNominal
+                     : Utils.doubleToString(m_missingValueReplacementNumeric, 4))
+                  + ")");
+    }
+
+    return temp.toString();
+  }
+
+  /**
+   * Set the index of this field in the mining schema Instances
+   *
+   * @param index the index of the attribute in the mining schema Instances
+   * that this field represents
+   */
+  public void setIndex(int index) {
+    m_index = index;
+  }
+
+  /**
+   * Get the name of this field.
+   *
+   * @return the name of this field
+   */
+  public String getName() {
+    return m_fieldName;
+  }
+
+  /**
+   * Get the outlier treatment method used for this field.
+   *
+   * @return the outlier treatment method
+   */
+  public Outlier getOutlierTreatmentMethod() {
+    return m_outlierTreatmentMethod;
+  }
+
+  /**
+   * Get the missing value treatment method for this field.
+   *
+   * @return the missing value treatment method
+   */
+  public Missing getMissingValueTreatmentMethod() {
+    return m_missingValueTreatmentMethod;
+  }
+
+  /**
+   * Apply the missing value treatment method for this field.
+   *
+   * @param value the incoming value to apply the treatment to
+   * @return the value after applying the missing value treatment (if any)
+   * @throws Exception if there is a problem
+   */
+  public double applyMissingValueTreatment(double value) throws Exception {
+    double newVal = value;
+    if (m_missingValueTreatmentMethod != Missing.ASIS && 
+        Utils.isMissingValue(value)) {
+      if (m_missingValueReplacementNominal != null) {
+        Attribute att = m_miningSchemaI.attribute(m_index);
+        int valIndex = att.indexOfValue(m_missingValueReplacementNominal);
+        if (valIndex < 0) {
+          throw new Exception("[MiningSchema] Nominal missing value replacement value doesn't "
+                              + "exist in the mining schema Instances!");
+        }
+        newVal = valIndex;
+      } else {
+        newVal = m_missingValueReplacementNumeric;
+      }
+    }
+    return newVal;
+  }
+
+  /**
+   * Apply the outlier treatment method for this field.
+   *
+   * @param value the incoming value to apply the treatment to
+   * @return the value after applying the treatment (if any)
+   * @throws Exception if there is a problem
+   */
+  public double applyOutlierTreatment(double value) throws Exception {
+    double newVal = value;
+    if (m_outlierTreatmentMethod != Outlier.ASIS) {
+      if (m_outlierTreatmentMethod == Outlier.ASMISSINGVALUES) {
+        newVal = applyMissingValueTreatment(value);
+      } else {
+        if (value < m_lowValue) {
+          newVal = m_lowValue;
+        } else if (value > m_highValue) {
+          newVal = m_highValue;
+        }
+      }
+    }
+    return newVal;
+  }
+
+  /**
+   * Return this mining field as an Attribute.
+   * 
+   * @return an Attribute for this field.
+   */
+  public Attribute getFieldAsAttribute() {
+    return m_miningSchemaI.attribute(m_index);
+  }
+  /**
+   * Constructs a new MiningFieldMetaInfo object.
+   * 
+   * @param field the Element that contains the field information
+   * @throws Exception if there is a problem during construction
+   */
+  public MiningFieldMetaInfo(Element field) throws Exception {
+    super(field);
+    // m_fieldName = field.getAttribute("name");
+
+    // get the usage type
+    String usage = field.getAttribute("usageType");
+    for (MiningFieldMetaInfo.Usage u : Usage.values()) {
+      if (u.toString().equals(usage)) {
+        m_usageType = u;
+        break;
+      }
+    }
+    
+    // optype override
+    /*String optype = field.getAttribute("optype");
+    if (optype.length() > 0) {
+      if (optype.equals("continuous")) {
+        m_optypeOverride = FieldMetaInfo.Optype.CONTINUOUS;
+      } else if (optype.equals("categorical")) {
+        m_optypeOverride = FieldMetaInfo.Optype.CATEGORICAL;
+      } else if (optype.equals("ordinal")) {
+        m_optypeOverride = FieldMetaInfo.Optype.ORDINAL;
+      }
+    }*/
+  
+    // importance
+    String importance = field.getAttribute("importance");
+    if (importance.length() > 0) {
+      m_importance = Double.parseDouble(importance);
+    }
+
+    // outliers
+    String outliers = field.getAttribute("outliers");
+    for (MiningFieldMetaInfo.Outlier o : Outlier.values()) {
+      if (o.toString().equals(outliers)) {
+        m_outlierTreatmentMethod = o;
+        break;
+      }
+    }
+    
+    if (outliers.length() > 0 && m_outlierTreatmentMethod == Outlier.ASEXTREMEVALUES) {
+      // low and high values are required for as extreme values handling
+      String lowValue = field.getAttribute("lowValue");
+      if (lowValue.length() > 0) {
+        m_lowValue = Double.parseDouble(lowValue);
+      } else {
+        throw new Exception("[MiningFieldMetaInfo] as extreme values outlier treatment "
+            + "specified, but no low value defined!");
+      }
+      String highValue = field.getAttribute("highValue");
+      if (highValue.length() > 0) {
+        m_highValue = Double.parseDouble(highValue);
+      } else {
+        throw new Exception("[MiningFieldMetaInfo] as extreme values outlier treatment "
+            + "specified, but no high value defined!");
+      }
+    }
+    
+
+    // missing values
+    String missingReplacement = field.getAttribute("missingValueReplacement");
+    if (missingReplacement.length() > 0) {
+      // try and parse it as a number
+      try {
+        m_missingValueReplacementNumeric = Double.parseDouble(missingReplacement);
+      } catch (IllegalArgumentException ex) {
+        // must be numeric
+        m_missingValueReplacementNominal = missingReplacement;
+      }
+    
+      // treatment type
+      String missingTreatment = field.getAttribute("missingValueTreatment");
+      for (MiningFieldMetaInfo.Missing m : Missing.values()) {
+        if (m.toString().equals(missingTreatment)) {
+          m_missingValueTreatmentMethod = m;
+          break;
+        }
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/MiningSchema.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/MiningSchema.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/MiningSchema.java	(revision 29)
@@ -0,0 +1,440 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MiningSchema.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.lang.String;
+import java.io.Serializable;
+import java.util.ArrayList;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+
+/**
+ * This class encapsulates the mining schema from
+ * a PMML xml file. Specifically, it contains the
+ * fields used in the PMML model as an Instances
+ * object (just the header). It also contains meta
+ * information such as value ranges and how to handle
+ * missing values, outliers etc.
+ *
+ * We also store various other PMML elements here, such as
+ * the TransformationDictionary, DerivedFields and Targets 
+ * (if defined). They are not part of the mining schema per se, but
+ * relate to inputs used by the model and it is convenient to
+ * store them here.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class MiningSchema implements Serializable {
+
+  /** For serialization */
+  private static final long serialVersionUID = 7144380586726330455L;
+
+  /** The structure of all the fields (both mining schema and derived) as Instances */
+  protected Instances m_fieldInstancesStructure;
+  
+  /** Just the mining schema fields as Instances */
+  protected Instances m_miningSchemaInstancesStructure;
+
+  /** Meta information about the mining schema fields */
+  protected ArrayList<MiningFieldMetaInfo> m_miningMeta = 
+    new ArrayList<MiningFieldMetaInfo>();
+  
+  /** 
+   * Meta information about derived fields (those defined in
+   * the TransformationDictionary followed by those defined in
+   * LocalTransformations)
+   */
+  protected ArrayList<DerivedFieldMetaInfo> m_derivedMeta = 
+    new ArrayList<DerivedFieldMetaInfo>();
+  
+  /** The transformation dictionary (if defined) */
+  protected TransformationDictionary m_transformationDictionary = null;
+
+  /** target meta info (may be null if not defined) */
+  protected TargetMetaInfo m_targetMetaInfo = null;
+  
+  private void getLocalTransformations(Element model) throws Exception {
+    NodeList temp = model.getElementsByTagName("LocalTransformations");
+    
+    if (temp.getLength() > 0) {
+      // should be just one LocalTransformations element
+      Element localT = (Element)temp.item(0);
+      
+      // Set up some field defs to pass in
+      /*ArrayList<Attribute> fieldDefs = new ArrayList<Attribute>();
+      for (int i = 0; i < m_miningSchemaInstancesStructure.numAttributes(); i++) {
+        fieldDefs.add(m_miningSchemaInstancesStructure.attribute(i));
+      } */
+      
+      NodeList localDerivedL = localT.getElementsByTagName("DerivedField");
+      for (int i = 0; i < localDerivedL.getLength(); i++) {
+        Node localDerived = localDerivedL.item(i);
+        if (localDerived.getNodeType() == Node.ELEMENT_NODE) {
+          DerivedFieldMetaInfo d = 
+            new DerivedFieldMetaInfo((Element)localDerived, null /*fieldDefs*/, m_transformationDictionary);
+          m_derivedMeta.add(d);
+        }
+      }
+    }
+  }
+
+  /**
+   * Constructor for MiningSchema.
+   *
+   * @param model the <code>Element</code> encapsulating the pmml model
+   * @param dataDictionary the data dictionary as an Instances object
+   * @throws Exception if something goes wrong during construction of the
+   * mining schema
+   */
+  public MiningSchema(Element model, 
+                      Instances dataDictionary,
+                      TransformationDictionary transDict) throws Exception {
+    
+    /*// First check for transformation dictionary/local transformations and derived fields.
+    // These are not supported yet.
+    NodeList temp = model.getElementsByTagName("LocalTransformations");
+    if (temp.getLength() > 0) {
+      throw new Exception("[MiningSchema] LocalTransformations "
+          + "are not supported yet.");
+    }*/
+
+    ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
+    NodeList fieldList = model.getElementsByTagName("MiningField");
+    int classIndex = -1;
+    int addedCount = 0;
+    for (int i = 0; i < fieldList.getLength(); i++) {
+      Node miningField = fieldList.item(i);
+      if (miningField.getNodeType() == Node.ELEMENT_NODE) {
+        Element miningFieldEl = (Element)miningField;
+
+        MiningFieldMetaInfo mfi = new MiningFieldMetaInfo(miningFieldEl);
+
+        if (mfi.getUsageType() == MiningFieldMetaInfo.Usage.ACTIVE ||
+            mfi.getUsageType() == MiningFieldMetaInfo.Usage.PREDICTED) {
+
+          // find this attribute in the dataDictionary
+          Attribute miningAtt = dataDictionary.attribute(mfi.getName());
+          if (miningAtt != null) {
+            mfi.setIndex(addedCount);
+            attInfo.add(miningAtt);
+            addedCount++;
+
+            if (mfi.getUsageType() == MiningFieldMetaInfo.Usage.PREDICTED) {
+              classIndex = addedCount - 1;
+            }
+
+            // add to the array list
+            m_miningMeta.add(mfi);
+          } else {
+          throw new Exception("Can't find mining field: " + mfi.getName() 
+                              + " in the data dictionary.");
+          }
+        }
+      }
+    }
+
+    m_miningSchemaInstancesStructure = new Instances("miningSchema", attInfo, 0);
+    
+    // set these instances on the MiningFieldMetaInfos so that the
+    // toString() method can operate correctly
+    for (MiningFieldMetaInfo m : m_miningMeta) {
+      m.setMiningSchemaInstances(m_miningSchemaInstancesStructure);
+    }
+    
+    m_transformationDictionary = transDict;
+    
+    // Handle transformation dictionary and any local transformations
+    if (m_transformationDictionary != null) {      
+      ArrayList<DerivedFieldMetaInfo> transDerived = transDict.getDerivedFields();
+      m_derivedMeta.addAll(transDerived);
+    }
+    
+    // Get any local transformations
+    getLocalTransformations(model);
+    
+    // Set up the full instances structure: combo of mining schema fields and
+    // all derived fields
+    ArrayList<Attribute> newStructure = new ArrayList<Attribute>();
+    for (MiningFieldMetaInfo m : m_miningMeta) {
+      newStructure.add(m.getFieldAsAttribute());
+    }
+    
+    for (DerivedFieldMetaInfo d : m_derivedMeta) {
+      newStructure.add(d.getFieldAsAttribute());
+    }
+    m_fieldInstancesStructure = new Instances("FieldStructure", newStructure, 0);
+    
+    if (m_transformationDictionary != null) {
+      // first update the field defs for any derived fields in the transformation dictionary
+      // and our complete list of derived fields, now that we have a fixed 
+      // ordering for the mining schema + derived attributes (i.e. could
+      // be different from the order of attributes in the data dictionary that was
+      // used when the transformation dictionary was initially constructed
+      m_transformationDictionary.setFieldDefsForDerivedFields(m_fieldInstancesStructure);
+    }
+
+    // update the field defs for all our derived fields.
+    for (DerivedFieldMetaInfo d : m_derivedMeta) {
+      d.setFieldDefs(m_fieldInstancesStructure);
+    }
+    
+    if (classIndex != -1) {
+      m_fieldInstancesStructure.setClassIndex(classIndex);
+      m_miningSchemaInstancesStructure.setClassIndex(classIndex);
+    }
+
+    // do Targets (if any)
+    NodeList targetsList = model.getElementsByTagName("Targets");
+    if (targetsList.getLength() > 0) {
+      if (targetsList.getLength() > 1) {
+        throw new Exception("[MiningSchema] Can only handle a single Target");
+      } else {
+        Node te = targetsList.item(0);
+        if (te.getNodeType() == Node.ELEMENT_NODE) {
+          m_targetMetaInfo = new TargetMetaInfo((Element)te);
+
+          // fill in any necessary categorical values in the mining schema 
+          // class attribute
+          if (m_fieldInstancesStructure.classIndex() >= 0 && 
+              m_fieldInstancesStructure.classAttribute().isString()) {
+            ArrayList<String> targetVals = m_targetMetaInfo.getValues();
+            if (targetVals.size() > 0) {
+              Attribute classAtt = m_fieldInstancesStructure.classAttribute();
+              for (int i = 0; i < targetVals.size(); i++) {
+                classAtt.addStringValue(targetVals.get(i));
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Apply the missing value treatments (if any) to an incoming instance.
+   *
+   * @param values an array of doubles in order of the fields in the mining schema
+   * that represents the incoming instance (note: use PMMLUtils.instanceToSchema()
+   * to generate this).
+   * @throws Exception if something goes wrong during missing value handling
+   */
+  public void applyMissingValuesTreatment(double[] values) throws Exception {
+    for (int i = 0; i < m_miningMeta.size(); i++) {
+      MiningFieldMetaInfo mfi = m_miningMeta.get(i);
+      values[i] = mfi.applyMissingValueTreatment(values[i]);
+    }
+  }
+
+  /**
+   * Apply the outlier treatment methods (if any) to an incoming instance.
+   *
+   * @param values an array of doubles in order of the fields in the mining schema
+   * that represents the incoming instance (note: use PMMLUtils.instanceToSchema()
+   * to generate this).
+   * @throws Exception if something goes wrong during outlier treatment handling
+   */
+  public void applyOutlierTreatment(double[] values) throws Exception {
+    for (int i = 0; i < m_miningMeta.size(); i++) {
+      MiningFieldMetaInfo mfi = m_miningMeta.get(i);
+      values[i] = mfi.applyOutlierTreatment(values[i]);
+    }
+  }
+
+  /**
+   * Apply both missing and outlier treatments to an incoming instance.
+   * @param values an array of doubles in order of the fields in the mining schema
+   * that represents the incoming instance (note: use MappingInfo.instanceToSchema()
+   * to generate this).
+   * @throws Exception if something goes wrong during this process
+   */
+  public void applyMissingAndOutlierTreatments(double[] values) throws Exception {
+    for (int i = 0; i < m_miningMeta.size(); i++) {
+      MiningFieldMetaInfo mfi = m_miningMeta.get(i);
+      values[i] = mfi.applyMissingValueTreatment(values[i]);
+      values[i] = mfi.applyOutlierTreatment(values[i]);
+    }
+  }
+
+  /**
+   * Get the all the fields (both mining schema and derived) as Instances.
+   * Attributes are in order of those in the mining schema, followed by
+   * derived attributes from the TransformationDictionary followed by
+   * derived attributes from LocalTransformations.
+   *
+   * @return all the fields as an Instances object
+   */
+  public Instances getFieldsAsInstances() {
+    return m_fieldInstancesStructure;
+  }
+  
+  /**
+   * Get the mining schema fields as an Instances object.
+   * 
+   * @return the mining schema fields as an Instances object.
+   */
+  public Instances getMiningSchemaAsInstances() {
+    return m_miningSchemaInstancesStructure;
+  }
+  
+  /**
+   * Get the transformation dictionary .
+   * 
+   * @return the transformation dictionary or null if none is
+   * defined.
+   */
+  public TransformationDictionary getTransformationDictionary() {
+    return m_transformationDictionary;
+  }
+  
+  /**
+   * Returns true if there is Target meta data.
+   *
+   * @return true if there is Target meta data
+   */
+  public boolean hasTargetMetaData() {
+    return (m_targetMetaInfo != null);
+  }
+
+  /**
+   * Get the Target meta data.
+   *
+   * @return the Target meta data
+   */
+  public TargetMetaInfo getTargetMetaData() {
+    return m_targetMetaInfo;
+  }
+
+  /**
+   * Method to convert any string attributes in the mining schema
+   * Instances to nominal attributes. This may be necessary if there are
+   * no Value elements defined for categorical fields in the data dictionary.
+   * In this case, elements in the actual model definition will probably reveal
+   * the valid values for categorical fields.
+   */
+  public void convertStringAttsToNominal() {
+    Instances miningSchemaI = getFieldsAsInstances();
+    if (miningSchemaI.checkForStringAttributes()) {
+      ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
+      for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+        Attribute tempA = miningSchemaI.attribute(i);
+        if (tempA.isString()) {
+          ArrayList<String> valueVector = new ArrayList<String>();
+          for (int j = 0; j < tempA.numValues(); j++) {
+            valueVector.add(tempA.value(j));
+          }
+          Attribute newAtt = new Attribute(tempA.name(), valueVector);
+          attInfo.add(newAtt);
+        } else {
+          attInfo.add(tempA);
+        }
+      }
+      Instances newI = new Instances("miningSchema", attInfo, 0);
+      if (m_fieldInstancesStructure.classIndex() >= 0) {
+        newI.setClassIndex(m_fieldInstancesStructure.classIndex());
+      }
+      m_fieldInstancesStructure = newI;
+
+      /*      StringToNominal stn = new StringToNominal();
+      stn.setInputFormat(miningSchemaI);
+      Instances newI = Filter.useFilter(miningSchemaI, stn);
+      m_miningSchema = newI; */
+    }
+  }
+
+  /**
+   * Convert a numeric attribute in the mining schema to nominal.
+   * 
+   * @param index the index of the attribute to convert
+   * @param newVals an ArrayList of the values of the nominal attribute
+   */
+  public void convertNumericAttToNominal(int index, 
+                                         ArrayList<String> newVals) {
+    Instances miningSchemaI = getFieldsAsInstances();
+    if (miningSchemaI.attribute(index).isNominal()) {
+      throw new IllegalArgumentException("[MiningSchema] convertNumericAttToNominal: attribute is "
+                                         + "already nominal!");
+    }
+
+    ArrayList<String> newValues = new ArrayList<String>();
+    for (int i = 0; i < newVals.size(); i++) {
+      newValues.add(newVals.get(i));
+    }
+
+    ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
+    for (int i = 0; i < miningSchemaI.numAttributes(); i++) {
+      Attribute tempA = miningSchemaI.attribute(i);
+      if (i == index) {
+        Attribute newAtt = new Attribute(tempA.name(), newValues);
+        attInfo.add(newAtt);
+      } else {
+        attInfo.add(tempA);
+      }
+    }
+
+    Instances newI = new Instances("miningSchema", attInfo, 0);
+    if (m_fieldInstancesStructure.classIndex() >= 0) {
+      newI.setClassIndex(m_fieldInstancesStructure.classIndex());
+    }
+    m_fieldInstancesStructure = newI;
+  }
+  
+  public ArrayList<DerivedFieldMetaInfo> getDerivedFields() {
+    return m_derivedMeta;
+  }
+  
+  public ArrayList<MiningFieldMetaInfo> getMiningFields() {
+    return m_miningMeta;
+  }
+
+  /**
+   * Get a textual description of the mining schema.
+   *
+   * @return a textual description of the mining schema
+   */
+  public String toString() {
+    StringBuffer temp = new StringBuffer();
+    
+    if (m_transformationDictionary != null) {
+      temp.append(m_transformationDictionary);
+    }
+    
+    temp.append("Mining schema:\n\n");
+    for (MiningFieldMetaInfo m : m_miningMeta) {
+      temp.append(m + "\n");
+    }
+    
+    if (m_derivedMeta.size() > 0) {
+      temp.append("\nDerived fields:\n\n");
+      for (DerivedFieldMetaInfo d : m_derivedMeta) {
+        temp.append(d + "\n");
+      }
+    }
+    temp.append("\n");
+    return temp.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/NormContinuous.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/NormContinuous.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/NormContinuous.java	(revision 29)
@@ -0,0 +1,278 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NormContinuous.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Utils;
+
+
+/**
+ * Class encapsulating a NormContinuous Expression.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class NormContinuous extends Expression {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = 4714332374909851542L;
+
+  /** The name of the field to use */
+  protected String m_fieldName;
+  
+  /** The index of the field */
+  protected int m_fieldIndex;
+  
+  /** True if a replacement for missing values has been specified */
+  protected boolean m_mapMissingDefined = false;
+  
+  /** The value of the missing value replacement (if defined) */
+  protected double m_mapMissingTo;
+  
+  /** Outlier treatment method (default = asIs) */
+  protected MiningFieldMetaInfo.Outlier m_outlierTreatmentMethod =
+    MiningFieldMetaInfo.Outlier.ASIS;
+  
+  /** original values for the LinearNorm entries */
+  protected double[] m_linearNormOrig;
+  
+  /** norm values for the LinearNorm entries */
+  protected double[] m_linearNormNorm;
+  
+  public NormContinuous(Element normCont, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs) 
+    throws Exception {
+    super(opType, fieldDefs);
+    
+    if (opType != FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new Exception("[NormContinuous] can only have a continuous optype");
+    }
+    
+    m_fieldName = normCont.getAttribute("field");
+    
+    String mapMissing = normCont.getAttribute("mapMissingTo");
+    if (mapMissing != null && mapMissing.length() > 0) {
+      m_mapMissingTo = Double.parseDouble(mapMissing);
+      m_mapMissingDefined = true;
+    }
+    
+    String outliers = normCont.getAttribute("outliers");
+    if (outliers != null && outliers.length() > 0) {
+      for (MiningFieldMetaInfo.Outlier o : MiningFieldMetaInfo.Outlier.values()) {
+        if (o.toString().equals(outliers)) {
+          m_outlierTreatmentMethod = o;
+          break;
+        }
+      }
+    }
+    
+    // get the LinearNorm elements
+    NodeList lnL = normCont.getElementsByTagName("LinearNorm");
+    if (lnL.getLength() < 2) {
+      throw new Exception("[NormContinuous] Must be at least 2 LinearNorm elements!");
+    }
+    m_linearNormOrig = new double[lnL.getLength()];
+    m_linearNormNorm = new double[lnL.getLength()];
+    
+    for (int i = 0; i < lnL.getLength(); i++) {
+      Node lnN = lnL.item(i);
+      if (lnN.getNodeType() == Node.ELEMENT_NODE) {
+        Element lnE = (Element)lnN;
+        
+        String orig = lnE.getAttribute("orig");
+        m_linearNormOrig[i] = Double.parseDouble(orig);
+        
+        String norm = lnE.getAttribute("norm");
+        m_linearNormNorm[i] = Double.parseDouble(norm);
+      }
+    }
+    
+    if (fieldDefs != null) {
+      setUpField();
+    }
+  }
+  
+  /**
+   * Set the field definitions for this Expression to use
+   * 
+   * @param fieldDefs the field definitions to use
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    super.setFieldDefs(fieldDefs);
+    setUpField();
+  }
+  
+  private void setUpField() throws Exception {
+    m_fieldIndex = -1;
+    
+    if (m_fieldDefs != null) {
+      m_fieldIndex = getFieldDefIndex(m_fieldName);
+//      System.err.println("NormCont... index of " + m_fieldName + " " + m_fieldIndex);
+      if (m_fieldIndex < 0) {
+        throw new Exception("[NormContinuous] Can't find field " + m_fieldName
+            + " in the supplied field definitions.");
+      }
+      
+      Attribute field = m_fieldDefs.get(m_fieldIndex);
+      if (!field.isNumeric()) {
+        throw new Exception("[NormContinuous] reference field " + m_fieldName
+            +" must be continuous.");
+      }
+    }
+  }
+
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  protected Attribute getOutputDef() {
+    return new Attribute(m_fieldName + "_normContinuous");
+  }
+
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of normalizing the input field
+   * @throws Exception if there is a problem computing the result
+   */
+  public double getResult(double[] incoming) throws Exception {
+    
+    double[] a = m_linearNormOrig;
+    double[] b = m_linearNormNorm;
+    
+    return computeNorm(a, b, incoming);
+  }
+  
+  /**
+   * Compute the inverse of the normalization (i.e. map back to a unormalized value).
+   * 
+   * @param incoming the incoming parameter values
+   * @return the unormalized value
+   */
+  public double getResultInverse(double[] incoming) {
+    double[] a = m_linearNormNorm;
+    double[] b = m_linearNormOrig;
+    
+    return computeNorm(a, b, incoming);
+  }
+  
+  private double computeNorm(double[] a, double[] b, double[] incoming) {
+    double result = 0.0;
+    
+    if (Utils.isMissingValue(incoming[m_fieldIndex])) {
+      if (m_mapMissingDefined) {
+        result = m_mapMissingTo;
+      } else {
+        result = incoming[m_fieldIndex]; // just return the missing value
+      }
+    } else {
+      double x = incoming[m_fieldIndex];
+      /*System.err.println("NormCont (index): " + m_fieldIndex);
+      System.err.println("NormCont (input val): " + x); */
+      
+      // boundary cases first
+      if (x < a[0]) {
+        if (m_outlierTreatmentMethod == MiningFieldMetaInfo.Outlier.ASIS) {
+          double slope = (b[1] - b[0]) /
+            (a[1] - a[0]);
+          double offset = b[0] - (slope * a[0]);
+          result = slope * x + offset;
+        } else if (m_outlierTreatmentMethod == MiningFieldMetaInfo.Outlier.ASEXTREMEVALUES) {
+          result = b[0];
+        } else {
+          // map to missing replacement value
+          result = m_mapMissingTo;
+        }
+      } else if (x > a[a.length - 1]) {
+        int length = a.length;
+        if (m_outlierTreatmentMethod == MiningFieldMetaInfo.Outlier.ASIS) {
+          double slope = (b[length - 1] - b[length - 2]) /
+            (a[length - 1] - a[length - 2]);
+          double offset = b[length - 1] - (slope * a[length - 1]);
+          result = slope * x + offset;
+        } else if (m_outlierTreatmentMethod == MiningFieldMetaInfo.Outlier.ASEXTREMEVALUES) {
+          result = b[length - 1];
+        } else {
+          // map to missing replacement value
+          result = m_mapMissingTo;
+        }
+      } else {
+        // find the segment that this value falls in to
+        for (int i = 1; i < a.length; i++) {
+          if (x <= a[i]) {
+            result = b[i - 1];
+            result += ((x - a[i - 1])/(a[i] - a[i - 1]) * 
+                        (b[i] - b[i - 1]));
+            break;
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Always throws an Exception since the result of NormContinuous must
+   * be continuous.
+   * 
+   * @param incoming the incoming parameter values
+   * @throws Exception always
+   */
+  public String getResultCategorical(double[] incoming) throws Exception {
+    throw new Exception("[NormContinuous] Can't return the result as a categorical value!");
+  }
+  
+  public String toString(String pad) {
+    StringBuffer buff = new StringBuffer();
+    
+    buff.append(pad + "NormContinuous (" + m_fieldName + "):\n" + pad + "linearNorm: ");
+    for (int i = 0; i < m_linearNormOrig.length; i++) {
+      buff.append("" + m_linearNormOrig[i] + ":" + m_linearNormNorm[i] + " ");
+    }
+    buff.append("\n" + pad);
+    buff.append("outlier treatment: " + m_outlierTreatmentMethod.toString());
+    if (m_mapMissingDefined) {
+      buff.append("\n" + pad);
+      buff.append("map missing values to: " + m_mapMissingTo);
+    }
+    
+    return buff.toString();
+  }
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/NormDiscrete.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/NormDiscrete.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/NormDiscrete.java	(revision 29)
@@ -0,0 +1,215 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NormDiscrete.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Utils;
+
+/**
+ * Class encapsulating a NormDiscrete Expression. Creates an
+ * indicator for a particular discrete value.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class NormDiscrete extends Expression {
+  
+  /**
+   * For serialization 
+   */
+  private static final long serialVersionUID = -8854409417983908220L;
+
+  /** The name of the field to lookup our value in */
+  protected String m_fieldName;
+  
+  /** The actual attribute itself */
+  protected Attribute m_field;
+  
+  /** The index of the attribute */
+  protected int m_fieldIndex = -1;
+  
+  /** The actual value (as a String) that will correspond to an output of 1 */
+  protected String m_fieldValue;
+  
+  /** True if a replacement for missing values has been specified */
+  protected boolean m_mapMissingDefined = false;
+  
+  /** The value of the missing value replacement (if defined) */
+  protected double m_mapMissingTo;
+  
+  /**
+   *  If we are referring to a nominal (rather than String) attribute
+   * then this holds the index of the value in question. Will be faster
+   * than searching for the value each time.
+   */
+  protected int m_fieldValueIndex = -1;
+  
+  /**
+   * Constructor. Reads the field name and field value for this NormDiscrete
+   * Expression.
+   * 
+   * @param normDisc the Element encapsulating this NormDiscrete
+   * @param opType the optype for this expression (taken from either the
+   * enclosing DefineFunction or DerivedField)
+   * @param fieldDefs an ArrayList of Attributes for the fields that this
+   * Expression might need to access
+   * enclosing DefineFunction or DerivedField)
+   * @throws Exception if there is a problem parsing this Apply Expression
+   */
+  public NormDiscrete(Element normDisc, FieldMetaInfo.Optype opType, ArrayList<Attribute> fieldDefs)
+    throws Exception {
+    super(opType, fieldDefs);
+    
+    if (opType != FieldMetaInfo.Optype.CONTINUOUS) {
+      throw new Exception("[NormDiscrete] can only have a continuous optype");
+    }
+    
+    m_fieldName = normDisc.getAttribute("field");
+    m_fieldValue = normDisc.getAttribute("value");
+    
+    String mapMissing = normDisc.getAttribute("mapMissingTo");
+    if (mapMissing != null && mapMissing.length() > 0) {
+      m_mapMissingTo = Double.parseDouble(mapMissing);
+      m_mapMissingDefined = true;
+    }
+    
+    if (fieldDefs != null) {
+      setUpField();
+    }
+  }
+  
+  /**
+   * Set the field definitions for this Expression to use
+   * 
+   * @param fieldDefs the field definitions to use
+   * @throws Exception if there is a problem setting the field definitions
+   */
+  public void setFieldDefs(ArrayList<Attribute> fieldDefs) throws Exception {
+    super.setFieldDefs(fieldDefs);
+    setUpField();
+  }
+  
+  /**
+   * Find the named field, set up the index(es) etc.
+   * 
+   * @throws Exception if a problem occurs.
+   */
+  private void setUpField() throws Exception {
+    m_fieldIndex = -1;
+    m_fieldValueIndex = -1;
+    m_field = null;
+    
+    if (m_fieldDefs != null) {
+      m_fieldIndex = getFieldDefIndex(m_fieldName);
+
+      if (m_fieldIndex < 0) {
+        throw new Exception("[NormDiscrete] Can't find field " + m_fieldName
+            + " in the supplied field definitions.");
+      }
+      m_field = m_fieldDefs.get(m_fieldIndex);
+      
+      if (!(m_field.isString() || m_field.isNominal())) {
+        throw new Exception("[NormDiscrete] reference field " + m_fieldName
+            +" must be categorical");
+      }
+      
+      if (m_field.isNominal()) {
+        // set up the value index
+        m_fieldValueIndex = m_field.indexOfValue(m_fieldValue);
+        if (m_fieldValueIndex < 0) {
+          throw new Exception("[NormDiscrete] Unable to find value " + m_fieldValue
+              + " in nominal attribute " + m_field.name());
+        }
+      } else if (m_field.isString()) {
+        // add our value to this attribute (if it is already there
+        // then this will have no effect).
+        m_fieldValueIndex = m_field.addStringValue(m_fieldValue);
+      }
+    }
+  }
+
+  /**
+   * Return the structure of the result of applying this Expression
+   * as an Attribute.
+   * 
+   * @return the structure of the result of applying this Expression as an
+   * Attribute.
+   */
+  protected Attribute getOutputDef() {    
+    return new Attribute(m_fieldName + "=" + m_fieldValue);
+  }
+
+  /**
+   * Get the result of evaluating the expression. In the case
+   * of a continuous optype, a real number is returned; in
+   * the case of a categorical/ordinal optype, the index of the nominal
+   * value is returned as a double.
+   * 
+   * @param incoming the incoming parameter values
+   * @return the result of evaluating the expression
+   * @throws Exception if there is a problem computing the result
+   */
+  public double getResult(double[] incoming) throws Exception {
+    
+    double result = 0.0;
+    if (Utils.isMissingValue(incoming[m_fieldIndex])) {
+      if (m_mapMissingDefined) {
+        result = m_mapMissingTo; // return the replacement
+      } else {
+        result = incoming[m_fieldIndex]; // just return the missing value
+      }
+    } else {
+      if (m_fieldValueIndex == (int)incoming[m_fieldIndex]) {
+        result = 1.0;
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Always throws an Exception since the result of NormDiscrete must
+   * be continuous.
+   * 
+   * @param incoming the incoming parameter values
+   * @throws Exception always
+   */
+  public String getResultCategorical(double[] incoming) throws Exception {
+    throw new Exception("[NormDiscrete] Can't return the result as a categorical value!");
+  }
+  
+  public String toString(String pad) {
+    StringBuffer buff = new StringBuffer();
+    buff.append("NormDiscrete: " + m_fieldName + "=" + m_fieldValue);
+    if (m_mapMissingDefined) {
+      buff.append("\n" + pad + "map missing values to: " + m_mapMissingTo);
+    }
+    
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/PMMLFactory.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/PMMLFactory.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/PMMLFactory.java	(revision 29)
@@ -0,0 +1,585 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PMMLFactory.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.pmml.consumer.GeneralRegression;
+import weka.classifiers.pmml.consumer.NeuralNetwork;
+import weka.classifiers.pmml.consumer.PMMLClassifier;
+import weka.classifiers.pmml.consumer.Regression;
+import weka.classifiers.pmml.consumer.RuleSetModel;
+import weka.classifiers.pmml.consumer.TreeModel;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.Logger;
+
+import java.util.ArrayList;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class is a factory class for reading/writing PMML models
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class PMMLFactory {
+
+  /** for serialization */
+  
+  protected enum ModelType {
+    UNKNOWN_MODEL ("unknown"),
+    REGRESSION_MODEL ("Regression"),
+    GENERAL_REGRESSION_MODEL ("GeneralRegression"),
+    NEURAL_NETWORK_MODEL ("NeuralNetwork"),
+    TREE_MODEL ("TreeModel"),
+    RULESET_MODEL("RuleSetModel");
+    
+    private final String m_stringVal;
+    
+    ModelType(String name) {
+      m_stringVal = name;
+    }
+    
+    public String toString() {
+      return m_stringVal;
+    }
+  }
+
+  /**
+   * Read and return a PMML model.
+   *
+   * @param filename the name of the file to read from
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading the file
+   */
+  public static PMMLModel getPMMLModel(String filename) throws Exception {
+    return getPMMLModel(filename, null);
+  }
+  
+  /**
+   * Read and return a PMML model.
+   *
+   * @param file a <code>File</code> to read from
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading the file
+   */
+  public static PMMLModel getPMMLModel(File file) throws Exception {
+    return getPMMLModel(file, null);
+  }
+  
+  /**
+   * Read and return a PMML model.
+   *
+   * @param stream the <code>InputStream</code> to read from
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading from the stream
+   */
+  public static PMMLModel getPMMLModel(InputStream stream) throws Exception {
+    return getPMMLModel(stream, null);
+  }
+  
+  /**
+   * Read and return a PMML model.
+   *
+   * @param filename the name of the file to read from
+   * @param log the logging object to use (or null if none is to be used)
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading the file
+   */
+  public static PMMLModel getPMMLModel(String filename, Logger log) throws Exception {
+    return getPMMLModel(new File(filename), log);
+  }
+
+  /**
+   * Read and return a PMML model.
+   *
+   * @param file a <code>File</code> to read from
+   * @param log the logging object to use (or null if none is to be used)
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading the file
+   */
+  public static PMMLModel getPMMLModel(File file, Logger log) throws Exception {
+    return getPMMLModel(new BufferedInputStream(new FileInputStream(file)), log);
+  }
+  
+  private static boolean isPMML(Document doc) {
+    NodeList tempL = doc.getElementsByTagName("PMML");
+    if (tempL.getLength() == 0) {
+      return false;
+    }
+    
+    return true;
+  }
+
+  /**
+   * Read and return a PMML model.
+   *
+   * @param stream the <code>InputStream</code> to read from
+   * @param log the logging object to use (or null if none is to be used)
+   * @return a PMML model
+   * @throws Exception if there is a problem while reading from the stream
+   */
+  public static PMMLModel getPMMLModel(InputStream stream, Logger log) throws Exception {
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+    DocumentBuilder db = dbf.newDocumentBuilder();
+    Document doc = db.parse(stream);
+    stream.close();
+    doc.getDocumentElement().normalize();
+    if (!isPMML(doc)) {
+      throw new IllegalArgumentException("[PMMLFactory] Source is not a PMML file!!");
+    }
+    
+    //    System.out.println("Root element " + doc.getDocumentElement().getNodeName());
+
+    Instances dataDictionary = getDataDictionaryAsInstances(doc);
+    TransformationDictionary transDict = getTransformationDictionary(doc, dataDictionary);
+    
+    ModelType modelType = getModelType(doc);
+    if (modelType == ModelType.UNKNOWN_MODEL) {
+      throw new Exception("Unsupported PMML model type");
+    }
+    Element model = getModelElement(doc, modelType);
+
+    // Construct mining schema and meta data
+    MiningSchema ms = new MiningSchema(model, dataDictionary, transDict);
+
+    //System.out.println(ms);
+    //System.exit(1);
+    //    Instances miningSchema = getMiningSchemaAsInstances(model, dataDictionary);
+    PMMLModel theModel = getModelInstance(doc, modelType, model, dataDictionary, ms);
+    if (log != null) {
+      theModel.setLog(log);
+    }
+    return theModel;
+  }
+  
+  /**
+   * Get the transformation dictionary (if there is one).
+   * 
+   * @param doc the Document containing the PMML model
+   * @param dataDictionary the data dictionary as an Instances object
+   * @return the transformation dictionary or null if there is none defined in
+   * the Document
+   * @throws Exception if there is a problem getting the transformation
+   * dictionary
+   */
+  protected static TransformationDictionary getTransformationDictionary(Document doc, 
+      Instances dataDictionary) throws Exception {
+    TransformationDictionary transDict = null;
+    
+    NodeList transL = doc.getElementsByTagName("TransformationDictionary");
+    // should be of size 0 or 1
+    if (transL.getLength() > 0) {
+      Node transNode = transL.item(0);
+      if (transNode.getNodeType() == Node.ELEMENT_NODE) {
+        transDict = new TransformationDictionary((Element)transNode, dataDictionary);
+      }
+    }
+    
+    return transDict;
+  }
+
+  /**
+   * Serialize a <code>PMMLModel</code> object that encapsulates a PMML model
+   *
+   * @param model the <code>PMMLModel</code> to serialize
+   * @param filename the name of the file to save to
+   * @throws Exception if something goes wrong during serialization
+   */
+  public static void serializePMMLModel(PMMLModel model, String filename) 
+    throws Exception {
+    serializePMMLModel(model, new File(filename));
+  }
+
+  /**
+   * Serialize a <code>PMMLModel</code> object that encapsulates a PMML model
+   *
+   * @param model the <code>PMMLModel</code> to serialize
+   * @param file the <code>File</code> to save to
+   * @throws Exception if something goes wrong during serialization
+   */
+  public static void serializePMMLModel(PMMLModel model, File file)
+    throws Exception {
+    serializePMMLModel(model, new BufferedOutputStream(new FileOutputStream(file)));
+  }
+
+  /**
+   * Serialize a <code>PMMLModel</code> object that encapsulates a PMML model
+   *
+   * @param model the <code>PMMLModel</code> to serialize
+   * @param stream the <code>OutputStream</code> to serialize to
+   * @throws Exception if something goes wrong during serialization
+   */
+  public static void serializePMMLModel(PMMLModel model, OutputStream stream)
+    throws Exception {
+    ObjectOutputStream oo = new ObjectOutputStream(stream);
+    Instances header = model.getMiningSchema().getFieldsAsInstances();
+    oo.writeObject(header);
+    oo.writeObject(model);
+    oo.flush();
+    oo.close();
+  }
+
+  /**
+   * Get an instance of a PMMLModel from the supplied Document
+   *
+   * @param doc the Document holding the pmml
+   * @param modelType the type of model
+   * @param model the Element encapsulating the model part of the Document
+   * @param dataDictionary the data dictionary as an Instances object
+   * @param miningSchema the mining schema
+   * @return a PMMLModel object
+   * @throws Exception if there is a problem constructing the model or
+   * if the model type is not supported
+   */
+  protected static PMMLModel getModelInstance(Document doc, 
+                                              ModelType modelType, Element model,
+                                              Instances dataDictionary,
+                                              MiningSchema miningSchema) throws Exception {
+    PMMLModel pmmlM = null;
+    switch (modelType) {
+    case REGRESSION_MODEL:
+      pmmlM = new Regression(model, dataDictionary, miningSchema);
+      //System.out.println(pmmlM);
+      break;
+    case GENERAL_REGRESSION_MODEL:
+      pmmlM = new GeneralRegression(model, dataDictionary, miningSchema);
+      //System.out.println(pmmlM);
+      break;
+    case NEURAL_NETWORK_MODEL:
+      pmmlM = new NeuralNetwork(model, dataDictionary, miningSchema);
+      break;
+    case TREE_MODEL:
+      pmmlM = new TreeModel(model, dataDictionary, miningSchema);
+      break;
+    case RULESET_MODEL:
+      pmmlM = new RuleSetModel(model, dataDictionary, miningSchema);
+      break;
+    default:
+      throw new Exception("[PMMLFactory] Unknown model type!!");
+    }
+    pmmlM.setPMMLVersion(doc);
+    pmmlM.setCreatorApplication(doc);
+    return pmmlM;
+  }
+
+  /**
+   * Get the type of model
+   *
+   * @param doc the Document encapsulating the pmml
+   * @return the type of model
+   */
+  protected static ModelType getModelType(Document doc) {
+    NodeList temp = doc.getElementsByTagName("RegressionModel");
+    if (temp.getLength() > 0) {
+      return ModelType.REGRESSION_MODEL;
+    }
+
+    temp = doc.getElementsByTagName("GeneralRegressionModel");
+    if (temp.getLength() > 0) {
+      return ModelType.GENERAL_REGRESSION_MODEL;
+    }
+    
+    temp = doc.getElementsByTagName("NeuralNetwork");
+    if (temp.getLength() > 0) {
+      return ModelType.NEURAL_NETWORK_MODEL;
+    }
+    
+    temp = doc.getElementsByTagName("TreeModel");
+    if (temp.getLength() > 0) {
+      return ModelType.TREE_MODEL;
+    }
+    
+    temp = doc.getElementsByTagName("RuleSetModel");
+    if (temp.getLength() > 0) {
+      return ModelType.RULESET_MODEL;
+    }
+
+    return ModelType.UNKNOWN_MODEL;
+  }
+
+  /**
+   * Get the Element that contains the pmml model
+   *
+   * @param doc the Document encapsulating the pmml
+   * @param modelType the type of model
+   * @throws Exception if the model type is unsupported/unknown
+   */
+  protected static Element getModelElement(Document doc, ModelType modelType) 
+    throws Exception {
+    NodeList temp = null;
+    Element model = null;
+    switch (modelType) {
+    case REGRESSION_MODEL:
+      temp = doc.getElementsByTagName("RegressionModel");
+      break;
+    case GENERAL_REGRESSION_MODEL:
+      temp = doc.getElementsByTagName("GeneralRegressionModel");
+      break;
+    case NEURAL_NETWORK_MODEL:
+      temp = doc.getElementsByTagName("NeuralNetwork");
+      break;
+    case TREE_MODEL:
+      temp = doc.getElementsByTagName("TreeModel");
+      break;
+    case RULESET_MODEL:
+      temp = doc.getElementsByTagName("RuleSetModel");
+      break;
+    default:
+      throw new Exception("[PMMLFactory] unknown/unsupported model type.");
+    }
+
+    if (temp != null && temp.getLength() > 0) {
+      Node modelNode = temp.item(0);
+      if (modelNode.getNodeType() == Node.ELEMENT_NODE) {
+        model = (Element)modelNode;
+      }
+    }
+
+    return model;
+  }
+
+  /**
+   * Get the mining schema as an Instances object
+   *
+   * @param model the Element containing the pmml model
+   * @param dataDictionary the data dictionary as an Instances object
+   * @return the mining schema as an Instances object
+   * @throws Exception if something goes wrong during reading the mining schema
+   * @deprecated Use the MiningSchema class instead
+   */
+  protected static Instances getMiningSchemaAsInstances(Element model,
+                                                        Instances dataDictionary) 
+    throws Exception {
+    ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
+    NodeList fieldList = model.getElementsByTagName("MiningField");
+    int classIndex = -1;
+    int addedCount = 0;
+    for (int i = 0; i < fieldList.getLength(); i++) {
+      Node miningField = fieldList.item(i);
+      if (miningField.getNodeType() == Node.ELEMENT_NODE) {
+        Element miningFieldEl = (Element)miningField;
+        String name = miningFieldEl.getAttribute("name");
+        String usage = miningFieldEl.getAttribute("usageType");
+        // TO-DO: also missing value replacement etc.
+
+        // find this attribute in the dataDictionary
+        Attribute miningAtt = dataDictionary.attribute(name);
+        if (miningAtt != null) {
+          if (usage.length() == 0 || usage.equals("active") || usage.equals("predicted")) {
+            attInfo.add(miningAtt);
+            addedCount++;
+          }
+          if (usage.equals("predicted")) {
+            classIndex = addedCount - 1;
+          }
+        } else {
+          throw new Exception("Can't find mining field: " + name 
+                              + " in the data dictionary.");
+        }
+      }
+    }
+    
+    Instances insts = new Instances("miningSchema", attInfo, 0);
+    //    System.out.println(insts);
+    if (classIndex != -1) {
+      insts.setClassIndex(classIndex);
+    }
+
+
+    return insts;
+  }
+
+  /**
+   * Get the data dictionary as an Instances object
+   *
+   * @param doc the Document encapsulating the pmml
+   * @return the data dictionary as an Instances object
+   * @throws Exception if there are fields that are not continuous, 
+   * ordinal or categorical in the data dictionary
+   */
+  protected static Instances getDataDictionaryAsInstances(Document doc) 
+    throws Exception {
+
+    // TO-DO: definition of missing values (see below)
+
+    ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
+    NodeList dataDictionary = doc.getElementsByTagName("DataField");
+    for (int i = 0; i < dataDictionary.getLength(); i++) {
+      Node dataField = dataDictionary.item(i);
+      if (dataField.getNodeType() == Node.ELEMENT_NODE) {
+        Element dataFieldEl = (Element)dataField;
+        String name = dataFieldEl.getAttribute("name");
+        String type = dataFieldEl.getAttribute("optype");
+        Attribute tempAtt = null;
+        if (name != null && type != null) {
+          if (type.equals("continuous")) {
+            tempAtt = new Attribute(name);
+          } else if (type.equals("categorical") || type.equals("ordinal")) {
+            NodeList valueList = dataFieldEl.getElementsByTagName("Value");
+            if (valueList == null || valueList.getLength() == 0) {
+              // assume that categorical values will be revealed in the actual model.
+              // Create a string attribute for now
+              ArrayList<String> nullV = null;
+              tempAtt = new Attribute(name, (ArrayList<String>)nullV);
+            } else {
+              // add the values (if defined as "valid")
+              ArrayList<String> valueVector = new ArrayList<String>();
+              for (int j = 0; j < valueList.getLength(); j++) {
+                Node val = valueList.item(j);
+                if (val.getNodeType() == Node.ELEMENT_NODE) {
+                  // property is optional (default value is "valid")
+                  String property = ((Element)val).getAttribute("property");
+                  if (property == null || property.length() == 0 || property.equals("valid")) {
+                    String value = ((Element)val).getAttribute("value");
+                    valueVector.add(value);
+                  } else {
+                    // Just ignore invalid or missing value definitions for now...
+                    // TO-DO: implement Value meta data with missing/invalid value defs.
+                  }
+                }
+              }
+              tempAtt = new Attribute(name, valueVector);
+            }
+          } else {
+            throw new Exception("[PMMLFactory] can't handle " + type + "attributes.");
+          }
+          attInfo.add(tempAtt);
+        }
+      }
+    }
+
+    // TO-DO: check whether certain values are declared to represent
+    // missing or invalid values (applies to both categorical and continuous
+    // attributes
+
+    // create the Instances structure
+    Instances insts = new Instances("dataDictionary", attInfo, 0);
+    //    System.out.println(insts);
+
+    return insts;
+  }
+
+  public static String applyClassifier(PMMLModel model, Instances test) throws Exception {
+    StringBuffer buff = new StringBuffer();
+    if (!(model instanceof PMMLClassifier)) {
+      throw new Exception("PMML model is not a classifier!");
+    }
+
+    double[] preds = null;
+    PMMLClassifier classifier = (PMMLClassifier)model;
+    for (int i = 0; i < test.numInstances(); i++) {
+      buff.append("Actual: ");
+      Instance temp = test.instance(i);
+      if (temp.classAttribute().isNumeric()) {
+        buff.append(temp.value(temp.classIndex()) + " ");
+      } else {
+        buff.append(temp.classAttribute().value((int)temp.value(temp.classIndex())) + " ");
+      }
+      preds = classifier.distributionForInstance(temp);
+      buff.append(" Predicted: ");
+      for (int j = 0; j < preds.length; j++) {
+        buff.append("" + preds[j] + " ");
+      }
+      buff.append("\n");
+    }
+    return buff.toString();
+  }
+  
+  private static class PMMLClassifierRunner extends AbstractClassifier {
+    public double[] distributionForInstance(Instance test) throws Exception {
+      throw new Exception("Don't call this method!!");
+    }
+    
+    public void buildClassifier(Instances instances) throws Exception {
+      throw new Exception("Don't call this method!!");
+    }
+    
+    public String getRevision() {
+      return weka.core.RevisionUtils.extract("$Revision: 5987 $");
+    }
+    
+    public void evaluatePMMLClassifier(String[] options) {
+      runClassifier(this, options);
+    }
+  }
+
+  public static void main(String[] args) {
+    try {
+      String[] optionsTmp = new String[args.length];
+      for (int i = 0; i < args.length; i++) {
+        optionsTmp[i] = args[i];
+      }
+      String pmmlFile = Utils.getOption('l', optionsTmp); 
+      if (pmmlFile.length() == 0) {
+        throw new Exception("[PMMLFactory] must specify a PMML file using the -l option.");
+      }
+      // see if it is supported before going any further
+      PMMLModel model = getPMMLModel(pmmlFile, null);
+      
+      PMMLClassifierRunner pcr = new PMMLClassifierRunner();
+      pcr.evaluatePMMLClassifier(args);
+
+      
+      /*PMMLModel model = getPMMLModel(args[0], null);
+      System.out.println(model);
+      if (args.length == 2) {
+        // load an arff file
+        Instances testData = new Instances(new java.io.BufferedReader(new java.io.FileReader(args[1])));
+        Instances miningSchemaI = model.getMiningSchema().getFieldsAsInstances();
+        if (miningSchemaI.classIndex() >= 0) {
+          String className = miningSchemaI.classAttribute().name();
+          for (int i = 0; i < testData.numAttributes(); i++) {
+            if (testData.attribute(i).name().equals(className)) {
+              testData.setClassIndex(i);
+              System.out.println("Found class " + className + " in test data.");
+              break;
+            }
+          }
+        }
+        System.out.println(applyClassifier(model, testData));
+      }*/
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/PMMLModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/PMMLModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/PMMLModel.java	(revision 29)
@@ -0,0 +1,87 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PMMLModel.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import org.w3c.dom.Document;
+
+import weka.gui.Logger;
+
+/**
+ * Interface for all PMML models
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public interface PMMLModel {
+  
+  /**
+   * Set the version of the PMML.
+   *
+   * @param doc the Document encapsulating the pmml
+   */
+  void setPMMLVersion(Document doc);
+  
+  /**
+   * Get the version of PMML used to encode this model.
+   *
+   * @return the version as a String
+   */
+  String getPMMLVersion();
+  
+  /**
+   * Set the name of the application (if specified) that created this.
+   * model
+   * 
+   * @param doc the Document encapsulating the pmml
+   */
+  void setCreatorApplication(Document doc);
+  
+  /**
+   * Get the name of the application that created this model.
+   * 
+   * @return the name of the creating application or null
+   * if not specified in the pmml.
+   */
+  String getCreatorApplication();
+
+  /**
+   * Get the mining schema.
+   *
+   * @return the mining schema
+   */
+  MiningSchema getMiningSchema();
+  
+  /**
+   * Set a logger to use.
+   * 
+   * @param log the logger to use
+   */
+  void setLog(Logger log);
+  
+  /**
+   * Get the logger.
+   * 
+   * @return the logger (or null if none is being used)
+   */
+  Logger getLog();
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/PMMLUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/PMMLUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/PMMLUtils.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PMMLUtils.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+/**
+ * Utility routines.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5987 $
+ */
+public class PMMLUtils {
+
+  /**
+   * Utility method to left or right pad strings with arbitrary characters.
+   * 
+   * @param source the source string
+   * @param padChar the character to pad with
+   * @param length the length of the resulting string
+   * @param leftPad pad to the left instead of the right
+   * @return a padded string
+   */
+  public static String pad(String source, String padChar, 
+                            int length, boolean leftPad) {
+    StringBuffer temp = new StringBuffer();
+
+    if (leftPad) {
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+      temp.append(source);
+    } else {
+      temp.append(source);
+      for (int i = 0; i< length; i++) {
+        temp.append(padChar);
+      }
+    }
+    return temp.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/TargetMetaInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/TargetMetaInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/TargetMetaInfo.java	(revision 29)
@@ -0,0 +1,328 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TargetMetaInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Utils;
+
+/**
+ * Class to encapsulate information about a Target.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class TargetMetaInfo extends FieldMetaInfo implements Serializable {
+
+  /** For serialization */
+  private static final long serialVersionUID = 863500462237904927L;
+
+  /** min and max */
+  protected double m_min = Double.NaN;
+  protected double m_max = Double.NaN;
+
+  /** re-scaling of target value (if defined) */
+  protected double m_rescaleConstant = 0;
+  protected double m_rescaleFactor = 1.0;
+
+  /** cast integers (default no casting) */
+  protected String m_castInteger = "";
+
+  // -------------------------------------------------------
+
+  /** default value (numeric) or prior distribution (categorical) */
+  protected double[] m_defaultValueOrPriorProbs;
+
+  /**  for categorical values. Actual values */
+  protected ArrayList<String> m_values = new ArrayList<String>();
+  
+  /** corresponding display values */
+  protected ArrayList<String> m_displayValues = new ArrayList<String>();
+
+
+  // TODO: toString method.
+
+  /**
+   * Constructor.
+   *
+   * @param target the <code>Element</code> encapsulating a Target
+   * @throws Exception if there is a problem reading the Target
+   */
+  protected TargetMetaInfo(Element target) throws Exception {
+    super(target);
+
+    // check for an OPTYPE
+    /*String op = target.getAttribute("optype");
+    if (op != null && op.length() > 0) {
+      for (int i = 0; i < Optype.values().length; i++) {
+        if (op.equals(Optype.values()[i].toString())) {
+          m_optype = Optype.values()[i];
+          break;
+        }
+      }
+    }*/
+
+    // min and max (if defined)
+    String min = target.getAttribute("min");
+    if (min != null && min.length() > 0) {
+      try {
+        m_min = Double.parseDouble(min);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[TargetMetaInfo] can't parse min value for target field "
+                            + m_fieldName);
+      }
+    }
+
+    String max = target.getAttribute("max");
+    if (max != null && max.length() > 0) {
+      try {
+        m_max = Double.parseDouble(max);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[TargetMetaInfo] can't parse max value for target field "
+                            + m_fieldName);
+      }
+    }
+
+    // Re-scaling (if any)
+    String rsc = target.getAttribute("rescaleConstant");
+    if (rsc != null && rsc.length() > 0) {
+      try {
+        m_rescaleConstant = Double.parseDouble(rsc);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[TargetMetaInfo] can't parse rescale constant value for "
+                            + "target field " + m_fieldName);
+      }
+    }
+    String rsf = target.getAttribute("rescaleFactor");
+    if (rsf != null && rsf.length() > 0) {
+      try {
+        m_rescaleFactor = Double.parseDouble(rsf);
+      } catch (IllegalArgumentException ex) {
+        throw new Exception("[TargetMetaInfo] can't parse rescale factor value for "
+                            + "target field " + m_fieldName);
+      }
+    }
+
+    // Cast integers
+    String cstI = target.getAttribute("castInteger");
+    if (cstI != null && cstI.length() > 0) {
+      m_castInteger = cstI;
+    }
+    
+    // Get the target value(s). Apparently, there doesn't have to
+    // be any target values defined.
+    NodeList vals = target.getElementsByTagName("TargetValue");
+    if (vals.getLength() > 0) {
+      m_defaultValueOrPriorProbs = new double[vals.getLength()];
+      
+      for (int i = 0; i < vals.getLength(); i++) {
+        Node value = vals.item(i);
+        if (value.getNodeType() == Node.ELEMENT_NODE) {
+          Element valueE = (Element)value;
+          String valueName = valueE.getAttribute("value");
+          if (valueName != null && valueName.length() > 0) {
+            // we have a categorical value - set optype if it's not
+            // already set
+            if (m_optype != Optype.CATEGORICAL &&
+                m_optype != Optype.NONE) {
+              throw new Exception("[TargetMetaInfo] TargetValue element has categorical value but "
+                                  + "optype is not categorical!");
+            }
+
+            if (m_optype == Optype.NONE) {
+              m_optype = Optype.CATEGORICAL;
+            }
+
+            m_values.add(valueName);
+            // get display value (if any)
+            String displayValue = valueE.getAttribute("displayValue");
+            if (displayValue != null && displayValue.length() > 0) {
+              m_displayValues.add(displayValue);
+            } else {
+              // use the value as the display value
+              m_displayValues.add(valueName);
+            }
+
+            // get prior probability (should be defined!!)
+            String prior = valueE.getAttribute("priorProbability");
+            if (prior != null && prior.length() > 0) {
+              try {
+                m_defaultValueOrPriorProbs[i] = Double.parseDouble(prior);
+              } catch (IllegalArgumentException ex) {
+                throw new Exception("[TargetMetaInfo] Can't parse probability from "
+                                    + "TargetValue element.");
+              }
+            } else {
+              throw new Exception("[TargetMetaInfo] No prior probability defined for value "
+                                  + valueName);
+            }
+          } else {
+            // we have a numeric field
+            // check the optype
+            if (m_optype != Optype.CONTINUOUS &&
+                m_optype != Optype.NONE) {
+              throw new Exception("[TargetMetaInfo] TargetValue element has continuous value but "
+                                  + "optype is not continuous!");
+            }
+
+            if (m_optype == Optype.NONE) {
+              m_optype = Optype.CONTINUOUS;
+            }
+
+            // get the default value
+            String defaultV = valueE.getAttribute("defaultValue");
+            if (defaultV != null && defaultV.length() > 0) {
+              try {
+                m_defaultValueOrPriorProbs[i] = Double.parseDouble(defaultV);
+              } catch (IllegalArgumentException ex) {
+                throw new Exception("[TargetMetaInfo] Can't parse default value from "
+                                    + "TargetValue element.");
+              }
+            } else {
+              throw new Exception("[TargetMetaInfo] No default value defined for target "
+                                  + m_fieldName);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the prior probability for the supplied value.
+   * 
+   * @param value the value to get the probability for
+   * @return the probability
+   * @throws Exception if there are no TargetValues defined or
+   * if the supplied value is not in the list of TargetValues
+   */
+  public double getPriorProbability(String value) throws Exception {
+    if (m_defaultValueOrPriorProbs == null) {
+      throw new Exception("[TargetMetaInfo] no TargetValues defined (getPriorProbability)");
+    }
+    double result = Double.NaN;
+    boolean found = false;
+    for (int i = 0; i < m_values.size(); i++) {
+      if (value.equals(m_values.get(i))) {
+        found = true;
+        result = m_defaultValueOrPriorProbs[i];
+        break;
+      }
+    }
+    if (!found) {
+      throw new Exception("[TargetMetaInfo] couldn't find value " + value 
+                          + "(getPriorProbability)");
+    }
+    return result;
+  }
+
+  /**
+   * Get the default value (numeric target)
+   *
+   * @return the default value
+   * @throws Exception if there is no TargetValue defined
+   */
+  public double getDefaultValue() throws Exception {
+    if (m_defaultValueOrPriorProbs == null) {
+      throw new Exception("[TargetMetaInfo] no TargetValues defined (getPriorProbability)");
+    }
+    return m_defaultValueOrPriorProbs[0];
+  }
+
+  /**
+   * Get the values (discrete case only) for this Target. Note: the
+   * list may be empty if the pmml doesn't specify any values.
+   *
+   * @return the values of this Target
+   */
+  public ArrayList<String> getValues() {
+    return new ArrayList<String>(m_values);
+  }
+
+  /**
+   * Apply min and max, rescaleFactor, rescaleConstant and castInteger - in
+   * that order (where defined).
+   *
+   * @param prediction the prediction to apply these modification to
+   * @return the modified prediction
+   * @throws Exception if this target is not a continuous one
+   */
+  public double applyMinMaxRescaleCast(double prediction) throws Exception {
+    if (m_optype != Optype.CONTINUOUS) {
+      throw new Exception("[TargetMetaInfo] target must be continuous!");
+    }
+
+    if (!Utils.isMissingValue(m_min) && prediction < m_min) {
+      prediction = m_min;
+    }
+    if (!Utils.isMissingValue(m_max) && prediction > m_max) {
+      prediction = m_max;
+    }
+
+    prediction *= m_rescaleFactor;
+    prediction += m_rescaleConstant;
+
+    if (m_castInteger.length() > 0) {
+      if (m_castInteger.equals("round")) {
+        prediction = Math.round(prediction);
+      } else if (m_castInteger.equals("ceiling")) {
+        prediction = Math.ceil(prediction);
+      } else if (m_castInteger.equals("floor")) {
+        prediction = Math.floor(prediction);
+      } else {
+        throw new Exception("[TargetMetaInfo] unknown castInteger value "
+                            + m_castInteger);
+      }
+    }
+    
+    return prediction;
+  }
+  
+  /**
+   * Return this field as an Attribute.
+   * 
+   * @return an Attribute for this field.
+   */
+  public Attribute getFieldAsAttribute() {
+    if (m_optype == Optype.CONTINUOUS) {
+      return new Attribute(m_fieldName);
+    }
+    if (m_values.size() == 0) {
+      // return a String attribute
+      return new Attribute(m_fieldName, (ArrayList<String>)null);
+    }
+    
+    ArrayList<String> values = new ArrayList<String>();
+    for (String val : m_values) {
+      values.add(val);
+    }
+    return new Attribute(m_fieldName, values);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/pmml/TransformationDictionary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/pmml/TransformationDictionary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/pmml/TransformationDictionary.java	(revision 29)
@@ -0,0 +1,201 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TransformationDictionary.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.pmml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.SerializedObject;
+
+/**
+ * Class encapsulating the TransformationDictionary element. Contains
+ * a list of DefineFunctions and DerivedFields (if any).
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision 1.0 $
+ */
+class TransformationDictionary implements Serializable {
+  
+  /** The defined functions (if any) */
+  protected ArrayList<DefineFunction> m_defineFunctions = new ArrayList<DefineFunction>();
+  
+  /** The derived fields (if any) */
+  protected ArrayList<DerivedFieldMetaInfo> m_derivedFields = 
+    new ArrayList<DerivedFieldMetaInfo>();
+  
+  /**
+   * Construct a new TransformationDictionary
+   * 
+   * @param dictionary the Element containing the dictionary
+   * @param dataDictionary the data dictionary as an Instances object
+   * @throws Exception if there is a problem constructing the transformation
+   * dictionary
+   */
+  protected TransformationDictionary(Element dictionary, 
+                                  Instances dataDictionary) throws Exception {
+    
+    // set up incoming field definitions
+/*    ArrayList<Attribute> incomingFieldDefs = new ArrayList<Attribute>();
+    for (int i = 0; i < dataDictionary.numAttributes(); i++) {
+      incomingFieldDefs.add(dataDictionary.attribute(i));
+    } */
+    
+    // get any derived fields and DefineFunctions
+    NodeList derivedL = dictionary.getChildNodes();
+    for (int i = 0; i < derivedL.getLength(); i++) {
+      Node child = derivedL.item(i);
+      if (child.getNodeType() == Node.ELEMENT_NODE) {
+        String tagName = ((Element)child).getTagName();
+        if (tagName.equals("DerivedField")) {
+          DerivedFieldMetaInfo df = new DerivedFieldMetaInfo((Element)child, null /*incomingFieldDefs*/, null);
+          m_derivedFields.add(df);
+        } else if (tagName.equals("DefineFunction")) {
+          DefineFunction defF = new DefineFunction((Element)child, null);
+          m_defineFunctions.add(defF);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Set the field definitions for the derived fields. Usually called once the
+   * structure of the mining schema + derived fields has been determined. 
+   * Calling this method with an array list of field definitions in the order of 
+   * attributes in the mining schema + derived fields will allow the expressions 
+   * used in the derived fields to access the correct attribute values from the 
+   * incoming instance (also allows for derived fields to reference other
+   * derived fields). This is necessary because construction of the TransformationDictionary uses the
+   * data dictionary to reference fields (the order of fields in the data dictionary
+   * is not guaranteed to be the same as the order in the mining schema).
+   * 
+   * IMPORTANT: for derived field x to be able to reference derived field y, y must
+   * have been declared before x in the PMML file. This is because the process
+   * of constructing an input vector of values to the model proceeds in a linear
+   * left-to-right fashion - so any referenced derived field (e.g. field y),
+   * must have already computed its value when x is evaluated.
+   * 
+   * @param fieldDefs the definition of the incoming fields as an array list of attributes
+   * @throws Exception if a problem occurs
+   */
+  protected void setFieldDefsForDerivedFields(ArrayList<Attribute> fieldDefs) throws Exception {
+    for (int i = 0; i < m_derivedFields.size(); i++) {
+      m_derivedFields.get(i).setFieldDefs(fieldDefs);
+    }
+    
+    // refresh the define functions - force them to pass on their parameter
+    // definitions as field defs to their encapsulated expression. Parameter
+    // defs were not passed on by expressions encapsulated in DefineFunctions
+    // at construction time because the encapsulated expression does not know
+    // whether it is contained in a DefineFunction or a DerivedField. Since
+    // we delay passing on field definitions until all derived fields are 
+    // loaded (in order to allow derived fields to reference other derived fields),
+    // we must tell DefineFunctions to pass on their parameter definitions
+    for (int i = 0; i < m_defineFunctions.size(); i++) {
+      m_defineFunctions.get(i).pushParameterDefs();
+    }
+  }
+  
+  /**
+   * Set the field definitions for the derived fields. Usually called once the
+   * structure of the mining schema has been determined. Calling this method
+   * with an array list of field definitions in the order of attributes in the
+   * mining schema will allow the expressions used in the derived fields to
+   * access the correct attribute values from the incoming instances. This is
+   * necessary because construction of the TransformationDictionary uses the
+   * data dictionary to reference fields (the order of fields in the data dictionary
+   * is not guaranteed to be the same as the order in the mining schema).
+   * 
+   * @param fieldDefs the definition of the incoming fields as an Instances object
+   * @throws Exception if a problem occurs
+   */
+  protected void setFieldDefsForDerivedFields(Instances fieldDefs) throws Exception {
+    ArrayList<Attribute> tempDefs = new ArrayList<Attribute>();
+    for (int i = 0; i < fieldDefs.numAttributes(); i++) {
+      tempDefs.add(fieldDefs.attribute(i));
+    }
+    setFieldDefsForDerivedFields(tempDefs);
+  }
+  
+  protected ArrayList<DerivedFieldMetaInfo> getDerivedFields() {
+    return new ArrayList<DerivedFieldMetaInfo>(m_derivedFields);
+  }
+  
+  /**
+   * Get a named DefineFunction. Returns a deep copy of the
+   * function.
+   * 
+   * @param functionName the name of the function to get
+   * @return the named function or null if it cannot be found
+   * @throws Exception if there is a problem deep copying the function
+   */
+  protected DefineFunction getFunction(String functionName) throws Exception {
+
+    DefineFunction copy = null;
+    DefineFunction match = null;
+    for (DefineFunction f : m_defineFunctions) {
+      if (f.getName().equals(functionName)) {
+        match = f;
+        //System.err.println("Found a match!!!");
+        break;
+      }
+    }
+    
+    if (match != null) {
+      SerializedObject so = new SerializedObject(match, false);
+      copy = (DefineFunction)so.getObject();
+      //System.err.println(copy);
+    }
+    
+    return copy;
+  }
+  
+  public String toString() {
+    StringBuffer buff = new StringBuffer();
+    
+    buff.append("Transformation dictionary:\n");
+    
+    if (m_derivedFields.size() > 0) {
+      buff.append("derived fields:\n");
+      for (DerivedFieldMetaInfo d : m_derivedFields) {
+        buff.append("" + d.getFieldAsAttribute() + "\n");
+      }
+    }
+    
+    if (m_defineFunctions.size() > 0) {
+      buff.append("\nfunctions:\n");
+      for (DefineFunction f : m_defineFunctions) {
+        buff.append(f.toString("  ") + "\n");
+      }
+    }
+    
+    buff.append("\n");
+    
+    return buff.toString();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/scripting/Groovy.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/scripting/Groovy.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/scripting/Groovy.java	(revision 29)
@@ -0,0 +1,231 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Groovy.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.scripting;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.File;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * A helper class for <a href="http://groovy.codehaus.org/" target="_blank">Groovy</a>.
+ * <p/>
+ * In order to use Groovy, the jar containing all the classes must be present
+ * in the CLASSPATH. This jar is normally found in the <i>embeddable</i>
+ * sub-directory of the Groovy installation.
+ * <p/>
+ * Tested with Groovy 1.5.7.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Groovy
+  implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -2628766602043134673L;
+
+  /** the classname of the Groovy classloader. */
+  public final static String CLASS_GROOVYCLASSLOADER = "groovy.lang.GroovyClassLoader";
+  
+  /** whether the Groovy classes are in the Classpath. */
+  protected static boolean m_Present = false;
+  static {
+    try {
+      Class.forName(CLASS_GROOVYCLASSLOADER);
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+  
+  /** the classloader. */
+  protected Object m_ClassLoader;
+  
+  /**
+   * default constructor, tries to instantiate a Groovy classloader.
+   */
+  public Groovy() {
+    m_ClassLoader = newClassLoader();
+  }
+  
+  /**
+   * returns the currently used Groovy classloader.
+   * 
+   * @return		the classloader, can be null
+   */
+  public Object getClassLoader() {
+    return m_ClassLoader;
+  }
+  
+  /**
+   * executes the specified method on the current interpreter and returns the 
+   * result, if any.
+   * 
+   * @param methodName		the name of the method
+   * @param paramClasses	the classes of the parameters
+   * @param paramValues		the values of the parameters
+   * @return			the return value of the method, if any (in that case null)
+   */
+  public Object invoke(String methodName, Class[] paramClasses, Object[] paramValues) {
+    Object	result;
+    
+    result = null;
+    if (getClassLoader() != null)
+      result = invoke(getClassLoader(), methodName, paramClasses, paramValues);
+    
+    return result;
+  }
+  
+  /**
+   * returns whether the Groovy classes are present or not, i.e. whether the 
+   * classes are in the classpath or not
+   *
+   * @return 			whether the Groovy classes are available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+
+  /**
+   * initializes and returns a Groovy Interpreter.
+   * 
+   * @return			the interpreter or null if Groovy classes not present
+   */
+  public static Object newClassLoader() {
+    Object	result;
+    Class<?>	cls;
+    Constructor	constr;
+    
+    result = null;
+    
+    if (isPresent()) {
+      try {
+	cls    = Class.forName(CLASS_GROOVYCLASSLOADER);
+	constr = cls.getConstructor(new Class[]{ClassLoader.class});
+	result = constr.newInstance(Groovy.class.getClassLoader());
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	result = null;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * loads the module and returns a new instance of it as instance of the
+   * provided Java class template.
+   * 
+   * @param file		the Groovy module file
+   * @param template		the template for the returned Java object
+   * @return			the Groovy object
+   */
+  public static Object newInstance(File file, Class template) {
+    Object 	result;
+    Object	interpreter;
+    Class	cls;
+
+    result = null;
+
+    if (!isPresent())
+      return result;
+    
+    interpreter = newClassLoader();
+    if (interpreter == null)
+      return result;
+
+    try {
+      cls    = (Class) invoke(interpreter, "parseClass", new Class[]{File.class}, new Object[]{file});
+      result = cls.newInstance();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+  
+  /**
+   * executes the specified method and returns the result, if any.
+   * 
+   * @param o			the object the method should be called from,
+   * 				e.g., a Groovy Interpreter
+   * @param methodName		the name of the method
+   * @param paramClasses	the classes of the parameters
+   * @param paramValues		the values of the parameters
+   * @return			the return value of the method, if any (in that case null)
+   */
+  public static Object invoke(Object o, String methodName, Class[] paramClasses, Object[] paramValues) {
+    Method      m;
+    Object      result;
+    
+    result = null;
+    
+    try {
+      m      = o.getClass().getMethod(methodName, paramClasses);
+      result = m.invoke(o, paramValues);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * If no arguments are given, it just prints the presence of the Groovy
+   * classes, otherwise it expects a Groovy filename to execute.
+   * 
+   * @param args		commandline arguments
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.out.println("Groovy present: " + isPresent());
+    }
+    else {
+      Groovy groovy = new Groovy();
+      if (groovy.getClassLoader() == null) {
+	System.err.println("Cannot instantiate Groovy ClassLoader!");
+      }
+      else {
+	Object groovyObject = Groovy.newInstance(new File(args[0]), Object.class);
+	Groovy.invoke(groovyObject, "run", new Class[]{}, new Object[]{});
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/scripting/Jython.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/scripting/Jython.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/scripting/Jython.java	(revision 29)
@@ -0,0 +1,307 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Jython.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.scripting;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+
+/**
+ * A helper class for <a href="http://www.jython.org/" target="_blank">Jython</a>.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Jython
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6972298704460209252L;
+
+  /** the classname of the Python interpreter */
+  public final static String CLASS_PYTHONINERPRETER = "org.python.util.PythonInterpreter";
+  
+  /** the classname of the Python ObjectInputStream */
+  public final static String CLASS_PYTHONOBJECTINPUTSTREAM = "org.python.util.PythonObjectInputStream";
+  
+  /** whether the Jython classes are in the Classpath */
+  protected static boolean m_Present = false;
+  static {
+    try {
+      Class.forName(CLASS_PYTHONINERPRETER);
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+  
+  /** the interpreter */
+  protected Object m_Interpreter;
+  
+  /**
+   * default constructor, tries to instantiate a Python Interpreter
+   */
+  public Jython() {
+    m_Interpreter = newInterpreter();
+  }
+  
+  /**
+   * returns the currently used Python Interpreter
+   * 
+   * @return		the interpreter, can be null
+   */
+  public Object getInterpreter() {
+    return m_Interpreter;
+  }
+  
+  /**
+   * executes the specified method on the current interpreter and returns the 
+   * result, if any.
+   * 
+   * @param methodName		the name of the method
+   * @param paramClasses	the classes of the parameters
+   * @param paramValues		the values of the parameters
+   * @return			the return value of the method, if any (in that case null)
+   */
+  public Object invoke(String methodName, Class[] paramClasses, Object[] paramValues) {
+    Object	result;
+    
+    result = null;
+    if (getInterpreter() != null)
+      result = invoke(getInterpreter(), methodName, paramClasses, paramValues);
+    
+    return result;
+  }
+  
+  /**
+   * returns whether the Jython classes are present or not, i.e. whether the 
+   * classes are in the classpath or not
+   *
+   * @return 			whether the Jython classes are available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+
+  /**
+   * initializes and returns a Python Interpreter
+   * 
+   * @return			the interpreter or null if Jython classes not present
+   */
+  public static Object newInterpreter() {
+    Object	result;
+    
+    result = null;
+    
+    if (isPresent()) {
+      try {
+	result = Class.forName(CLASS_PYTHONINERPRETER).newInstance();
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	result = null;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * loads the module and returns a new instance of it as instance of the
+   * provided Java class template.
+   * 
+   * @param file		the Jython module file
+   * @param template		the template for the returned Java object
+   * @return			the Jython object
+   */
+  public static Object newInstance(File file, Class template) {
+    return newInstance(file, template, new File[0]);
+  }
+
+  /**
+   * loads the module and returns a new instance of it as instance of the
+   * provided Java class template. The paths are added to 'sys.path' - can 
+   * be used if the module depends on other Jython modules.
+   * 
+   * @param file		the Jython module file
+   * @param template		the template for the returned Java object
+   * @param paths		additional paths to add to "sys.path"
+   * @return			the Jython object
+   */
+  public static Object newInstance(File file, Class template, File[] paths) {
+    Object 		result;
+    String 		tempName;
+    String 		instanceName;
+    String 		javaClassName;
+    String 		objectDef;
+    int			i;
+    String[]		tmpPaths;
+    HashSet<String>	currentPaths;
+    String		filename;
+    Object		interpreter;
+
+    result = null;
+
+    if (!isPresent())
+      return result;
+    
+    interpreter = newInterpreter();
+    if (interpreter == null)
+      return result;
+    
+    // add paths to sys.path
+    if (paths.length > 0) {
+      invoke(interpreter, "exec", new Class[]{String.class}, new Object[]{"import sys"});
+
+      // determine currently set paths
+      instanceName = "syspath";
+      invoke(interpreter, "exec", new Class[]{String.class}, new Object[]{instanceName + " = sys.path"});
+      currentPaths = new HashSet<String>();
+      try {
+	tmpPaths = (String[]) invoke(interpreter, "get", new Class[]{String.class, Class.class}, new Object[]{instanceName, String[].class});
+	for (i = 0; i < tmpPaths.length; i++)
+	  currentPaths.add(tmpPaths[i]);
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+      }
+
+      // add only new paths
+      for (i = 0; i < paths.length; i++) {
+	if (!currentPaths.contains(paths[i].getAbsolutePath()))
+	  invoke(interpreter, "exec", new Class[]{String.class}, new Object[]{"sys.path.append('" + paths[i].getAbsolutePath() + "')"});
+      }
+    }
+
+    // get object
+    filename      = file.getAbsolutePath();
+    invoke(interpreter, "execfile", new Class[]{String.class}, new Object[]{filename});
+    tempName      = filename.substring(filename.lastIndexOf("/") + 1);
+    tempName      = tempName.substring(0, tempName.indexOf("."));
+    instanceName  = tempName.toLowerCase();
+    javaClassName = tempName.substring(0,1).toUpperCase() + tempName.substring(1);
+    objectDef     = "=" + javaClassName + "()";
+    invoke(interpreter, "exec", new Class[]{String.class}, new Object[]{instanceName + objectDef});
+    try {
+      result = invoke(interpreter, "get", new Class[]{String.class, Class.class}, new Object[]{instanceName, template});
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    return result;
+  }
+  
+  /**
+   * executes the specified method and returns the result, if any
+   * 
+   * @param o			the object the method should be called from,
+   * 				e.g., a Python Interpreter
+   * @param methodName		the name of the method
+   * @param paramClasses	the classes of the parameters
+   * @param paramValues		the values of the parameters
+   * @return			the return value of the method, if any (in that case null)
+   */
+  public static Object invoke(Object o, String methodName, Class[] paramClasses, Object[] paramValues) {
+    Method      m;
+    Object      result;
+    
+    result = null;
+    
+    try {
+      m      = o.getClass().getMethod(methodName, paramClasses);
+      result = m.invoke(o, paramValues);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * deserializes the Python Object from the stream
+   * 
+   * @param in			the stream to use
+   * @return			the deserialized object
+   */
+  public static Object deserialize(InputStream in) {
+    Class<?> 		cls;
+    Class[] 		paramTypes;
+    Constructor 	constr;
+    Object[] 		arglist;
+    Object 		obj;
+    Object 		result;
+
+    result = null;
+
+    try {
+      cls        = Class.forName(CLASS_PYTHONOBJECTINPUTSTREAM);
+      paramTypes = new Class[]{InputStream.class};
+      constr     = cls.getConstructor(paramTypes);
+      arglist    = new Object[]{in};
+      obj        = constr.newInstance(arglist);
+      result     = invoke(obj, "readObject", new Class[]{}, new Object[]{});
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * If no arguments are given, it just prints the presence of the Jython
+   * classes, otherwise it expects a Jython filename to execute.
+   * 
+   * @param args		commandline arguments
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.out.println("Jython present: " + isPresent());
+    }
+    else {
+      Jython jython = new Jython();
+      if (jython.getInterpreter() == null)
+	System.err.println("Cannot instantiate Python Interpreter!");
+      else
+	jython.invoke("execfile", new Class[]{String.class}, new Object[]{args[0]});
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/scripting/JythonObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/scripting/JythonObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/scripting/JythonObject.java	(revision 29)
@@ -0,0 +1,33 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JythonObject.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.scripting;
+
+
+/**
+ * An indicator interface for Jython objects.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5018 $
+ */
+public interface JythonObject {
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/scripting/JythonSerializableObject.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/scripting/JythonSerializableObject.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/scripting/JythonSerializableObject.java	(revision 29)
@@ -0,0 +1,36 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JythonSerializableObject.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.scripting;
+
+
+import java.io.Serializable;
+
+
+/**
+ * An indicator interface for serializable Jython objects.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5018 $
+ */
+public interface JythonSerializableObject extends Serializable, JythonObject {
+
+}
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/IteratedLovinsStemmer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/IteratedLovinsStemmer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/IteratedLovinsStemmer.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * IteratedLovinsStemmer.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.RevisionUtils;
+
+/**
+ <!-- globalinfo-start -->
+ * An iterated version of the Lovins stemmer. It stems the word (in case it's longer than 2 characters) until it no further changes.<br/>
+ * <br/>
+ * For more information about the Lovins stemmer see:<br/>
+ * <br/>
+ * Julie Beth Lovins (1968). Development of a stemming algorithm. Mechanical Translation and Computational Linguistics. 11:22-31.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Lovins1968,
+ *    author = {Julie Beth Lovins},
+ *    journal = {Mechanical Translation and Computational Linguistics},
+ *    pages = {22-31},
+ *    title = {Development of a stemming algorithm},
+ *    volume = {11},
+ *    year = {1968}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author  Eibe Frank (eibe at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ * @see     LovinsStemmer
+ */
+public class IteratedLovinsStemmer 
+  extends LovinsStemmer {
+
+  /** for serialization */
+  static final long serialVersionUID = 960689687163788264L;
+  
+  /**
+   * Returns a string describing the stemmer
+   * @return a description suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "An iterated version of the Lovins stemmer. It stems the word (in "
+      + "case it's longer than 2 characters) until it no further changes.\n\n"
+      + "For more information about the Lovins stemmer see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Iterated stemming of the given word.
+   * Word is converted to lower case.
+   * 
+   * @param str 	the word to stem
+   * @return 		the stemmed word
+   */
+  public String stem(String str) {
+
+    if (str.length() <= 2) {
+      return str;
+    }
+    String stemmed = super.stem(str);
+    while (!stemmed.equals(str)) {
+      str = stemmed;
+      stemmed = super.stem(stemmed);
+    }
+    return stemmed;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the stemmer with the given options
+   *
+   * @param args      the options
+   */
+  public static void main(String[] args) {
+    try {
+      Stemming.useStemmer(new IteratedLovinsStemmer(), args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/LovinsStemmer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/LovinsStemmer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/LovinsStemmer.java	(revision 29)
@@ -0,0 +1,1002 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LovinsStemmer.java
+ * Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+
+import java.util.HashMap;
+
+/**
+ <!-- globalinfo-start -->
+ * A stemmer based on the Lovins stemmer, described here:<br/>
+ * <br/>
+ * Julie Beth Lovins (1968). Development of a stemming algorithm. Mechanical Translation and Computational Linguistics. 11:22-31.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Lovins1968,
+ *    author = {Julie Beth Lovins},
+ *    journal = {Mechanical Translation and Computational Linguistics},
+ *    pages = {22-31},
+ *    title = {Development of a stemming algorithm},
+ *    volume = {11},
+ *    year = {1968}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ * @author  Eibe Frank (eibe at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class LovinsStemmer 
+  implements Stemmer, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -6113024782588197L;
+  
+  /** Enters C version compatibility mode if set to true (emulates
+    features of the original C implementation that are inconsistent
+    with the algorithm as described in Lovins's paper) */
+  private static boolean m_CompMode = false;
+
+  /** The hash tables containing the list of endings. */
+  private static HashMap<String,String> m_l11 = null;
+  private static HashMap<String,String> m_l10 = null;
+  private static HashMap<String,String> m_l9 = null;
+  private static HashMap<String,String> m_l8 = null;
+  private static HashMap<String,String> m_l7 = null;
+  private static HashMap<String,String> m_l6 = null;
+  private static HashMap<String,String> m_l5 = null;
+  private static HashMap<String,String> m_l4 = null;
+  private static HashMap<String,String> m_l3 = null;
+  private static HashMap<String,String> m_l2 = null;
+  private static HashMap<String,String> m_l1 = null;
+
+  static {
+
+    m_l11 = new HashMap<String,String>();
+    m_l11.put("alistically", "B");
+    m_l11.put("arizability", "A");
+    m_l11.put("izationally", "B");
+    m_l10 = new HashMap<String,String>();
+    m_l10.put("antialness", "A");
+    m_l10.put("arisations", "A");
+    m_l10.put("arizations", "A");
+    m_l10.put("entialness", "A");
+    m_l9 = new HashMap<String,String>();
+    m_l9.put("allically", "C");
+    m_l9.put("antaneous", "A");
+    m_l9.put("antiality", "A");
+    m_l9.put("arisation", "A");
+    m_l9.put("arization", "A");
+    m_l9.put("ationally", "B");
+    m_l9.put("ativeness", "A");
+    m_l9.put("eableness", "E");
+    m_l9.put("entations", "A");
+    m_l9.put("entiality", "A");
+    m_l9.put("entialize", "A");
+    m_l9.put("entiation", "A");
+    m_l9.put("ionalness", "A");
+    m_l9.put("istically", "A");
+    m_l9.put("itousness", "A");
+    m_l9.put("izability", "A");
+    m_l9.put("izational", "A");
+    m_l8 = new HashMap<String,String>();
+    m_l8.put("ableness", "A");
+    m_l8.put("arizable", "A");
+    m_l8.put("entation", "A");
+    m_l8.put("entially", "A");
+    m_l8.put("eousness", "A");
+    m_l8.put("ibleness", "A");
+    m_l8.put("icalness", "A");
+    m_l8.put("ionalism", "A");
+    m_l8.put("ionality", "A");
+    m_l8.put("ionalize", "A");
+    m_l8.put("iousness", "A");
+    m_l8.put("izations", "A");
+    m_l8.put("lessness", "A");
+    m_l7 = new HashMap<String,String>();
+    m_l7.put("ability", "A");
+    m_l7.put("aically", "A");
+    m_l7.put("alistic", "B");
+    m_l7.put("alities", "A");
+    m_l7.put("ariness", "E");
+    m_l7.put("aristic", "A");
+    m_l7.put("arizing", "A");
+    m_l7.put("ateness", "A");
+    m_l7.put("atingly", "A");
+    m_l7.put("ational", "B");
+    m_l7.put("atively", "A");
+    m_l7.put("ativism", "A");
+    m_l7.put("elihood", "E");
+    m_l7.put("encible", "A");
+    m_l7.put("entally", "A");
+    m_l7.put("entials", "A");
+    m_l7.put("entiate", "A");
+    m_l7.put("entness", "A");
+    m_l7.put("fulness", "A");
+    m_l7.put("ibility", "A");
+    m_l7.put("icalism", "A");
+    m_l7.put("icalist", "A");
+    m_l7.put("icality", "A");
+    m_l7.put("icalize", "A");
+    m_l7.put("ication", "G");
+    m_l7.put("icianry", "A");
+    m_l7.put("ination", "A");
+    m_l7.put("ingness", "A");
+    m_l7.put("ionally", "A");
+    m_l7.put("isation", "A");
+    m_l7.put("ishness", "A");
+    m_l7.put("istical", "A");
+    m_l7.put("iteness", "A");
+    m_l7.put("iveness", "A");
+    m_l7.put("ivistic", "A");
+    m_l7.put("ivities", "A");
+    m_l7.put("ization", "F");
+    m_l7.put("izement", "A");
+    m_l7.put("oidally", "A");
+    m_l7.put("ousness", "A");
+    m_l6 = new HashMap<String,String>();
+    m_l6.put("aceous", "A");
+    m_l6.put("acious", "B");
+    m_l6.put("action", "G");
+    m_l6.put("alness", "A");
+    m_l6.put("ancial", "A");
+    m_l6.put("ancies", "A");
+    m_l6.put("ancing", "B");
+    m_l6.put("ariser", "A");
+    m_l6.put("arized", "A");
+    m_l6.put("arizer", "A");
+    m_l6.put("atable", "A");
+    m_l6.put("ations", "B");
+    m_l6.put("atives", "A");
+    m_l6.put("eature", "Z");
+    m_l6.put("efully", "A");
+    m_l6.put("encies", "A");
+    m_l6.put("encing", "A");
+    m_l6.put("ential", "A");
+    m_l6.put("enting", "C");
+    m_l6.put("entist", "A");
+    m_l6.put("eously", "A");
+    m_l6.put("ialist", "A");
+    m_l6.put("iality", "A");
+    m_l6.put("ialize", "A");
+    m_l6.put("ically", "A");
+    m_l6.put("icance", "A");
+    m_l6.put("icians", "A");
+    m_l6.put("icists", "A");
+    m_l6.put("ifully", "A");
+    m_l6.put("ionals", "A");
+    m_l6.put("ionate", "D");
+    m_l6.put("ioning", "A");
+    m_l6.put("ionist", "A");
+    m_l6.put("iously", "A");
+    m_l6.put("istics", "A");
+    m_l6.put("izable", "E");
+    m_l6.put("lessly", "A");
+    m_l6.put("nesses", "A");
+    m_l6.put("oidism", "A");
+    m_l5 = new HashMap<String,String>();
+    m_l5.put("acies", "A");
+    m_l5.put("acity", "A");
+    m_l5.put("aging", "B");
+    m_l5.put("aical", "A");
+    if (!m_CompMode) {
+      m_l5.put("alist", "A");
+    }
+    m_l5.put("alism", "B");
+    m_l5.put("ality", "A");
+    m_l5.put("alize", "A");
+    m_l5.put("allic", "b");
+    m_l5.put("anced", "B");
+    m_l5.put("ances", "B");
+    m_l5.put("antic", "C");
+    m_l5.put("arial", "A");
+    m_l5.put("aries", "A");
+    m_l5.put("arily", "A");
+    m_l5.put("arity", "B");
+    m_l5.put("arize", "A");
+    m_l5.put("aroid", "A");
+    m_l5.put("ately", "A");
+    m_l5.put("ating", "I");
+    m_l5.put("ation", "B");
+    m_l5.put("ative", "A");
+    m_l5.put("ators", "A");
+    m_l5.put("atory", "A");
+    m_l5.put("ature", "E");
+    m_l5.put("early", "Y");
+    m_l5.put("ehood", "A");
+    m_l5.put("eless", "A");
+    if (!m_CompMode) {
+      m_l5.put("elily", "A");
+    } else {
+      m_l5.put("elity", "A");
+    }
+    m_l5.put("ement", "A");
+    m_l5.put("enced", "A");
+    m_l5.put("ences", "A");
+    m_l5.put("eness", "E");
+    m_l5.put("ening", "E");
+    m_l5.put("ental", "A");
+    m_l5.put("ented", "C");
+    m_l5.put("ently", "A");
+    m_l5.put("fully", "A");
+    m_l5.put("ially", "A");
+    m_l5.put("icant", "A");
+    m_l5.put("ician", "A");
+    m_l5.put("icide", "A");
+    m_l5.put("icism", "A");
+    m_l5.put("icist", "A");
+    m_l5.put("icity", "A");
+    m_l5.put("idine", "I");
+    m_l5.put("iedly", "A");
+    m_l5.put("ihood", "A");
+    m_l5.put("inate", "A");
+    m_l5.put("iness", "A");
+    m_l5.put("ingly", "B");
+    m_l5.put("inism", "J");
+    m_l5.put("inity", "c");
+    m_l5.put("ional", "A");
+    m_l5.put("ioned", "A");
+    m_l5.put("ished", "A");
+    m_l5.put("istic", "A");
+    m_l5.put("ities", "A");
+    m_l5.put("itous", "A");
+    m_l5.put("ively", "A");
+    m_l5.put("ivity", "A");
+    m_l5.put("izers", "F");
+    m_l5.put("izing", "F");
+    m_l5.put("oidal", "A");
+    m_l5.put("oides", "A");
+    m_l5.put("otide", "A");
+    m_l5.put("ously", "A");
+    m_l4 = new HashMap<String,String>();
+    m_l4.put("able", "A");
+    m_l4.put("ably", "A");
+    m_l4.put("ages", "B");
+    m_l4.put("ally", "B");
+    m_l4.put("ance", "B");
+    m_l4.put("ancy", "B");
+    m_l4.put("ants", "B");
+    m_l4.put("aric", "A");
+    m_l4.put("arly", "K");
+    m_l4.put("ated", "I");
+    m_l4.put("ates", "A");
+    m_l4.put("atic", "B");
+    m_l4.put("ator", "A");
+    m_l4.put("ealy", "Y");
+    m_l4.put("edly", "E");
+    m_l4.put("eful", "A");
+    m_l4.put("eity", "A");
+    m_l4.put("ence", "A");
+    m_l4.put("ency", "A");
+    m_l4.put("ened", "E");
+    m_l4.put("enly", "E");
+    m_l4.put("eous", "A");
+    m_l4.put("hood", "A");
+    m_l4.put("ials", "A");
+    m_l4.put("ians", "A");
+    m_l4.put("ible", "A");
+    m_l4.put("ibly", "A");
+    m_l4.put("ical", "A");
+    m_l4.put("ides", "L");
+    m_l4.put("iers", "A");
+    m_l4.put("iful", "A");
+    m_l4.put("ines", "M");
+    m_l4.put("ings", "N");
+    m_l4.put("ions", "B");
+    m_l4.put("ious", "A");
+    m_l4.put("isms", "B");
+    m_l4.put("ists", "A");
+    m_l4.put("itic", "H");
+    m_l4.put("ized", "F");
+    m_l4.put("izer", "F");
+    m_l4.put("less", "A");
+    m_l4.put("lily", "A");
+    m_l4.put("ness", "A");
+    m_l4.put("ogen", "A");
+    m_l4.put("ward", "A");
+    m_l4.put("wise", "A");
+    m_l4.put("ying", "B");
+    m_l4.put("yish", "A");
+    m_l3 = new HashMap<String,String>();
+    m_l3.put("acy", "A");
+    m_l3.put("age", "B");
+    m_l3.put("aic", "A");
+    m_l3.put("als", "b");
+    m_l3.put("ant", "B");
+    m_l3.put("ars", "O");
+    m_l3.put("ary", "F");
+    m_l3.put("ata", "A");
+    m_l3.put("ate", "A");
+    m_l3.put("eal", "Y");
+    m_l3.put("ear", "Y");
+    m_l3.put("ely", "E");
+    m_l3.put("ene", "E");
+    m_l3.put("ent", "C");
+    m_l3.put("ery", "E");
+    m_l3.put("ese", "A");
+    m_l3.put("ful", "A");
+    m_l3.put("ial", "A");
+    m_l3.put("ian", "A");
+    m_l3.put("ics", "A");
+    m_l3.put("ide", "L");
+    m_l3.put("ied", "A");
+    m_l3.put("ier", "A");
+    m_l3.put("ies", "P");
+    m_l3.put("ily", "A");
+    m_l3.put("ine", "M");
+    m_l3.put("ing", "N");
+    m_l3.put("ion", "Q");
+    m_l3.put("ish", "C");
+    m_l3.put("ism", "B");
+    m_l3.put("ist", "A");
+    m_l3.put("ite", "a");
+    m_l3.put("ity", "A");
+    m_l3.put("ium", "A");
+    m_l3.put("ive", "A");
+    m_l3.put("ize", "F");
+    m_l3.put("oid", "A");
+    m_l3.put("one", "R");
+    m_l3.put("ous", "A");
+    m_l2 = new HashMap<String,String>();
+    m_l2.put("ae", "A"); 
+    m_l2.put("al", "b");
+    m_l2.put("ar", "X");
+    m_l2.put("as", "B");
+    m_l2.put("ed", "E");
+    m_l2.put("en", "F");
+    m_l2.put("es", "E");
+    m_l2.put("ia", "A");
+    m_l2.put("ic", "A");
+    m_l2.put("is", "A");
+    m_l2.put("ly", "B");
+    m_l2.put("on", "S");
+    m_l2.put("or", "T");
+    m_l2.put("um", "U");
+    m_l2.put("us", "V");
+    m_l2.put("yl", "R");
+    m_l2.put("s\'", "A");
+    m_l2.put("\'s", "A");
+    m_l1 = new HashMap<String,String>();
+    m_l1.put("a", "A");
+    m_l1.put("e", "A");
+    m_l1.put("i", "A");
+    m_l1.put("o", "A");
+    m_l1.put("s", "W");
+    m_l1.put("y", "B");	
+  }
+
+  /**
+   * Returns a string describing the stemmer
+   * @return a description suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A stemmer based on the Lovins stemmer, described here:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Julie Beth Lovins");
+    result.setValue(Field.YEAR, "1968");
+    result.setValue(Field.TITLE, "Development of a stemming algorithm");
+    result.setValue(Field.JOURNAL, "Mechanical Translation and Computational Linguistics");
+    result.setValue(Field.VOLUME, "11");
+    result.setValue(Field.PAGES, "22-31");
+
+    return result;
+  }
+
+  /**
+   * Finds and removes ending from given word.
+   * 
+   * @param word	the word to work on
+   * @return 		the processed word
+   */
+  private String removeEnding(String word) {
+
+    int length = word.length();
+    int el = 11;
+
+    while (el > 0) {
+      if (length - el > 1) {
+        String ending = word.substring(length - el);
+        String conditionCode = null;
+        switch (el) {
+          case 11: conditionCode = (String)m_l11.get(ending);
+                   break;
+          case 10: conditionCode = (String)m_l10.get(ending);
+                   break; 
+          case 9: conditionCode = (String)m_l9.get(ending);
+                  break;
+          case 8: conditionCode = (String)m_l8.get(ending);
+                  break;   
+          case 7: conditionCode = (String)m_l7.get(ending);
+                  break;   
+          case 6: conditionCode = (String)m_l6.get(ending);
+                  break;   
+          case 5: conditionCode = (String)m_l5.get(ending);
+                  break;   
+          case 4: conditionCode = (String)m_l4.get(ending);
+                  break;   
+          case 3: conditionCode = (String)m_l3.get(ending);
+                  break;   
+          case 2: conditionCode = (String)m_l2.get(ending);
+                  break;   
+          case 1: conditionCode = (String)m_l1.get(ending);
+                  break;   
+          default:
+        }
+        if (conditionCode != null) {
+          switch (conditionCode.charAt(0)) {
+            case 'A':
+              return word.substring(0, length - el);
+            case 'B':
+              if (length - el > 2) {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'C':
+              if (length - el > 3) {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'D':
+              if (length - el > 4) {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'E':
+              if (word.charAt(length - el - 1) != 'e') {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'F':
+              if ((length - el > 2) &&
+                  (word.charAt(length - el - 1) != 'e')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'G':
+              if ((length - el > 2) &&
+                  (word.charAt(length - el - 1) == 'f')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'H':
+              if ((word.charAt(length - el - 1) == 't') ||
+                  ((word.charAt(length - el - 1) == 'l') &&
+                   (word.charAt(length - el - 2) == 'l'))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'I':
+              if ((word.charAt(length - el - 1) != 'o') &&
+                  (word.charAt(length - el - 1) != 'e')) { 
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'J':
+              if ((word.charAt(length - el - 1) != 'a') &&
+                  (word.charAt(length - el - 1) != 'e')) { 
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'K':
+              if ((length - el > 2) &&
+                  ((word.charAt(length - el - 1) == 'l') ||
+                   (word.charAt(length - el - 1) == 'i') ||
+                   ((word.charAt(length - el - 1) == 'e') &&
+                    (word.charAt(length - el - 3) == 'u')))) {
+                return word.substring(0, length - el);
+                    }
+              break;
+            case 'L':
+              if ((word.charAt(length - el - 1) != 'u') &&
+                  (word.charAt(length - el - 1) != 'x') &&
+                  ((word.charAt(length - el - 1) != 's') ||
+                   (word.charAt(length - el - 2) == 'o'))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'M':
+              if ((word.charAt(length - el - 1) != 'a') &&
+                  (word.charAt(length - el - 1) != 'c') &&
+                  (word.charAt(length - el - 1) != 'e') &&
+                  (word.charAt(length - el - 1) != 'm')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'N':
+              if ((length - el > 3) || 
+                  ((length - el == 3) &&
+                   ((word.charAt(length - el - 3) != 's')))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'O':
+              if ((word.charAt(length - el - 1) == 'l') ||
+                  (word.charAt(length - el - 1) == 'i')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'P':
+              if (word.charAt(length - el - 1) != 'c') {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'Q':
+              if ((length - el > 2) &&
+                  (word.charAt(length - el - 1) != 'l') &&
+                  (word.charAt(length - el - 1) != 'n')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'R':
+              if ((word.charAt(length - el - 1) == 'n') ||
+                  (word.charAt(length - el - 1) == 'r')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'S':
+              if (((word.charAt(length - el - 1) == 'r') &&
+                    (word.charAt(length - el - 2) == 'd')) ||
+                  ((word.charAt(length - el - 1) == 't') &&
+                   (word.charAt(length - el - 2) != 't'))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'T':
+              if ((word.charAt(length - el - 1) == 's') ||
+                  ((word.charAt(length - el - 1) == 't') &&
+                   (word.charAt(length - el - 2) != 'o'))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'U':
+              if ((word.charAt(length - el - 1) == 'l') ||
+                  (word.charAt(length - el - 1) == 'm') ||
+                  (word.charAt(length - el - 1) == 'n') ||
+                  (word.charAt(length - el - 1) == 'r')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'V':
+              if (word.charAt(length - el - 1) == 'c') {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'W':
+              if ((word.charAt(length - el - 1) != 's') &&
+                  (word.charAt(length - el - 1) != 'u')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'X':
+              if ((word.charAt(length - el - 1) == 'l') ||
+                  (word.charAt(length - el - 1) == 'i') ||
+                  ((length - el > 2) &&
+                   (word.charAt(length - el - 1) == 'e') &&
+                   (word.charAt(length - el - 3) == 'u'))) {
+                return word.substring(0, length - el);
+                   }
+              break;
+            case 'Y':
+              if ((word.charAt(length - el - 1) == 'n') &&
+                  (word.charAt(length - el - 2) == 'i')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'Z':
+              if (word.charAt(length - el - 1) != 'f') {
+                return word.substring(0, length - el);
+              }
+              break;
+            case 'a':
+              if ((word.charAt(length - el - 1) == 'd') ||
+                  (word.charAt(length - el - 1) == 'f') ||
+                  (((word.charAt(length - el - 1) == 'h') &&
+                    (word.charAt(length - el - 2) == 'p'))) ||
+                  (((word.charAt(length - el - 1) == 'h') &&
+                    (word.charAt(length - el - 2) == 't'))) ||
+                  (word.charAt(length - el - 1) == 'l') ||
+                  (((word.charAt(length - el - 1) == 'r') &&
+                    (word.charAt(length - el - 2) == 'e'))) ||
+                  (((word.charAt(length - el - 1) == 'r') &&
+                    (word.charAt(length - el - 2) == 'o'))) ||
+                  (((word.charAt(length - el - 1) == 's') &&
+                    (word.charAt(length - el - 2) == 'e'))) ||
+                  (word.charAt(length - el - 1) == 't')) {
+                return word.substring(0, length - el);
+                  }
+              break;
+            case 'b':
+              if (m_CompMode) {
+                if (((length - el == 3 ) &&
+                      (!((word.charAt(length - el - 1) == 't') &&
+                         (word.charAt(length - el - 2) == 'e') &&
+                         (word.charAt(length - el - 3) == 'm')))) ||
+                    ((length - el > 3) &&
+                     (!((word.charAt(length - el - 1) == 't') &&
+                        (word.charAt(length - el - 2) == 's') &&
+                        (word.charAt(length - el - 3) == 'y') &&
+                        (word.charAt(length - el - 4) == 'r'))))) {
+                  return word.substring(0, length - el);
+                        }
+              } else {
+                if ((length - el > 2) &&
+                    (!((word.charAt(length - el - 1) == 't') &&
+                       (word.charAt(length - el - 2) == 'e') &&
+                       (word.charAt(length - el - 3) == 'm'))) &&
+                    ((length - el < 4) ||
+                     (!((word.charAt(length - el - 1) == 't') &&
+                        (word.charAt(length - el - 2) == 's') &&
+                        (word.charAt(length - el - 3) == 'y') &&
+                        (word.charAt(length - el - 4) == 'r'))))) {
+                  return word.substring(0, length - el);
+                        }
+              } 
+              break;
+            case 'c':
+              if (word.charAt(length - el - 1) == 'l') {
+                return word.substring(0, length - el);
+              }
+              break;
+            default:
+              throw new IllegalArgumentException("Fatal error.");
+          }
+        }
+      }
+      el--;
+    }
+    return word;
+  }
+
+  /**
+   * Recodes ending of given word.
+   * 
+   * @param word	the word to work on
+   * @return		the processed word
+   */
+  private String recodeEnding(String word) {
+
+    int lastPos = word.length() - 1;
+
+    // Rule 1
+    if (word.endsWith("bb") ||
+        word.endsWith("dd") ||
+        word.endsWith("gg") ||
+        word.endsWith("ll") ||
+        word.endsWith("mm") ||
+        word.endsWith("nn") ||
+        word.endsWith("pp") ||
+        word.endsWith("rr") ||
+        word.endsWith("ss") ||
+        word.endsWith("tt")) {
+      word = word.substring(0, lastPos);
+      lastPos--;
+        }
+
+    // Rule 2
+    if (word.endsWith("iev")) {
+      word = word.substring(0, lastPos - 2).concat("ief");
+    }
+
+    // Rule 3
+    if (word.endsWith("uct")) {
+      word = word.substring(0, lastPos - 2).concat("uc");
+      lastPos--;
+    }
+
+    // Rule 4
+    if (word.endsWith("umpt")) {
+      word = word.substring(0, lastPos - 3).concat("um");
+      lastPos -= 2;
+    }
+
+    // Rule 5
+    if (word.endsWith("rpt")) {
+      word = word.substring(0, lastPos - 2).concat("rb");
+      lastPos--;
+    }
+
+    // Rule 6
+    if (word.endsWith("urs")) {
+      word = word.substring(0, lastPos - 2).concat("ur");
+      lastPos--;
+    }
+
+    // Rule 7
+    if (word.endsWith("istr")) {
+      word = word.substring(0, lastPos - 3).concat("ister");
+      lastPos++;
+    }
+
+    // Rule 7a
+    if (word.endsWith("metr")) {
+      word = word.substring(0, lastPos - 3).concat("meter");
+      lastPos++;
+    }
+
+    // Rule 8
+    if (word.endsWith("olv")) {
+      word = word.substring(0, lastPos - 2).concat("olut");
+      lastPos++;
+    }
+
+    // Rule 9
+    if (word.endsWith("ul")) {
+      if ((lastPos - 2 < 0) ||
+          ((word.charAt(lastPos - 2) != 'a') &&
+           (word.charAt(lastPos - 2) != 'i') &&
+           (word.charAt(lastPos - 2) != 'o'))) {
+        word = word.substring(0, lastPos - 1).concat("l");
+        lastPos--;
+           }
+    }
+
+    // Rule 10
+    if (word.endsWith("bex")) {
+      word = word.substring(0, lastPos - 2).concat("bic");
+    }
+
+    // Rule 11
+    if (word.endsWith("dex")) {
+      word = word.substring(0, lastPos - 2).concat("dic");
+    }
+
+    // Rule 12
+    if (word.endsWith("pex")) {
+      word = word.substring(0, lastPos - 2).concat("pic");
+    }
+
+    // Rule 13
+    if (word.endsWith("tex")) {
+      word = word.substring(0, lastPos - 2).concat("tic");
+    }
+
+    // Rule 14
+    if (word.endsWith("ax")) {
+      word = word.substring(0, lastPos - 1).concat("ac");
+    }
+
+    // Rule 15
+    if (word.endsWith("ex")) {
+      word = word.substring(0, lastPos - 1).concat("ec");
+    }
+
+    // Rule 16
+    if (word.endsWith("ix")) {
+      word = word.substring(0, lastPos - 1).concat("ic");
+    }
+
+    // Rule 17
+    if (word.endsWith("lux")) {
+      word = word.substring(0, lastPos - 2).concat("luc");
+    }
+
+    // Rule 18
+    if (word.endsWith("uad")) {
+      word = word.substring(0, lastPos - 2).concat("uas");
+    }
+
+    // Rule 19
+    if (word.endsWith("vad")) {
+      word = word.substring(0, lastPos - 2).concat("vas");
+    }
+
+    // Rule 20
+    if (word.endsWith("cid")) {
+      word = word.substring(0, lastPos - 2).concat("cis");
+    }
+
+    // Rule 21
+    if (word.endsWith("lid")) {
+      word = word.substring(0, lastPos - 2).concat("lis");
+    }
+
+    // Rule 22
+    if (word.endsWith("erid")) {
+      word = word.substring(0, lastPos - 3).concat("eris");
+    }
+
+    // Rule 23
+    if (word.endsWith("pand")) {
+      word = word.substring(0, lastPos - 3).concat("pans");
+    }
+
+    // Rule 24
+    if (word.endsWith("end")) {
+      if ((lastPos - 3 < 0) ||
+          (word.charAt(lastPos - 3) != 's')) {
+        word = word.substring(0, lastPos - 2).concat("ens");
+          }
+    }
+
+    // Rule 25
+    if (word.endsWith("ond")) {
+      word = word.substring(0, lastPos - 2).concat("ons");
+    }
+
+    // Rule 26
+    if (word.endsWith("lud")) {
+      word = word.substring(0, lastPos - 2).concat("lus");
+    }
+
+    // Rule 27
+    if (word.endsWith("rud")) {
+      word = word.substring(0, lastPos - 2).concat("rus");
+    }
+
+    // Rule 28
+    if (word.endsWith("her")) {
+      if ((lastPos - 3 < 0) ||
+          ((word.charAt(lastPos - 3) != 'p') &&
+           (word.charAt(lastPos - 3) != 't'))) {
+        word = word.substring(0, lastPos - 2).concat("hes");
+           }
+    }
+
+    // Rule 29
+    if (word.endsWith("mit")) {
+      word = word.substring(0, lastPos - 2).concat("mis");
+    }
+
+    // Rule 30
+    if (word.endsWith("end")) {
+      if ((lastPos - 3 < 0) ||
+          (word.charAt(lastPos - 3) != 'm')) {
+        word = word.substring(0, lastPos - 2).concat("ens");
+          }
+    }
+
+    // Rule 31
+    if (word.endsWith("ert")) {
+      word = word.substring(0, lastPos - 2).concat("ers");
+    }
+
+    // Rule 32
+    if (word.endsWith("et")) {
+      if ((lastPos - 2 < 0) ||
+          (word.charAt(lastPos - 2) != 'n')) {
+        word = word.substring(0, lastPos - 1).concat("es");
+          }
+    }
+
+    // Rule 33
+    if (word.endsWith("yt")) {
+      word = word.substring(0, lastPos - 1).concat("ys");
+    }
+
+    // Rule 34
+    if (word.endsWith("yz")) {
+      word = word.substring(0, lastPos - 1).concat("ys");
+    }
+
+    return word;
+  }
+
+  /**
+   * Returns the stemmed version of the given word.
+   * Word is converted to lower case before stemming.
+   * 
+   * @param word 	a string consisting of a single word
+   * @return 		the stemmed word
+   */
+  public String stem(String word) {
+
+    if (word.length() > 2) {
+      return recodeEnding(removeEnding(word.toLowerCase()));
+    } else {
+      return word.toLowerCase();
+    }
+  }
+
+  /**
+   * Stems everything in the given string. String
+   * is converted to lower case before stemming.
+   * 
+   * @param str		the string to stem
+   * @return 		the processed string
+   */
+  public String stemString(String str) {
+
+    StringBuffer result = new StringBuffer();
+    int start = -1;
+    for (int j = 0; j < str.length(); j++) {
+      char c = str.charAt(j);
+      if (Character.isLetterOrDigit(c)) {
+        if (start == -1) {
+          start = j;
+        }
+      } else if (c == '\'') {
+        if (start == -1) {
+          result.append(c);
+        }
+      } else {
+        if (start != -1) {
+          result.append(stem(str.substring(start, j)));
+          start = -1;
+        }
+        result.append(c);
+      }
+    }
+    if (start != -1) {
+      result.append(stem(str.substring(start, str.length())));
+    }
+    return result.toString();  
+  }
+
+  /**
+   * returns a string representation of the stemmer
+   * 
+   * @return a string representation of the stemmer
+   */
+  public String toString() {
+    return getClass().getName();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the stemmer with the given options
+   *
+   * @param args      the options
+   */
+  public static void main(String[] args) {
+    try {
+      Stemming.useStemmer(new LovinsStemmer(), args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/NullStemmer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/NullStemmer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/NullStemmer.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NullStemmer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.RevisionUtils;
+
+/**
+ <!-- globalinfo-start -->
+ * A dummy stemmer that performs no stemming at all.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 5953 $
+ */
+public class NullStemmer 
+  implements Stemmer {
+
+  /** for serialization */
+  static final long serialVersionUID = -3671261636532625496L;
+  
+  /**
+   * Returns a string describing the stemmer
+   * @return a description suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A dummy stemmer that performs no stemming at all.";
+  }
+  
+  /**
+   * Returns the word as it is.
+   *
+   * @param word      the unstemmed word
+   * @return          the unstemmed word, again
+   */
+  public String stem(String word) {
+    return new String(word);
+  }
+
+  /**
+   * returns a string representation of the stemmer
+   * 
+   * @return a string representation of the stemmer
+   */
+  public String toString() {
+    return getClass().getName();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the stemmer with the given options
+   *
+   * @param args      the options
+   */
+  public static void main(String[] args) {
+    try {
+      Stemming.useStemmer(new NullStemmer(), args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/SnowballStemmer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/SnowballStemmer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/SnowballStemmer.java	(revision 29)
@@ -0,0 +1,463 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SnowballStemmer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.ClassDiscovery;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.gui.GenericObjectEditor;
+
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A wrapper class for the Snowball stemmers. Only available if the Snowball classes are in the classpath.<br/>
+ * If the class discovery is not dynamic, i.e., the property 'UseDynamic' in the props file 'weka/gui/GenericPropertiesCreator.props' is 'false', then the property 'org.tartarus.snowball.SnowballProgram' in the 'weka/gui/GenericObjectEditor.props' file has to be uncommented as well. If necessary you have to discover and fill in the snowball stemmers manually. You can use the 'weka.core.ClassDiscovery' for this:<br/>
+ *   java weka.core.ClassDiscovery org.tartarus.snowball.SnowballProgram org.tartarus.snowball.ext<br/>
+ * <br/>
+ * For more information visit these web sites:<br/>
+ *   http://weka.wikispaces.com/Stemmers<br/>
+ *   http://snowball.tartarus.org/<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;name&gt;
+ *  The name of the snowball stemmer (default 'porter').
+ *  available stemmers:
+ *     danish, dutch, english, finnish, french, german, italian, 
+ *     norwegian, porter, portuguese, russian, spanish, swedish
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 5953 $
+ */
+public class SnowballStemmer 
+  implements Stemmer, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = -6111170431963015178L;
+  
+  /** the package name for snowball. */
+  public final static String PACKAGE = "org.tartarus.snowball";
+  
+  /** the package name where the stemmers are located. */
+  public final static String PACKAGE_EXT = PACKAGE + ".ext";
+
+  /** the snowball program, all stemmers are derived from. */
+  protected final static String SNOWBALL_PROGRAM = PACKAGE + ".SnowballProgram";
+  
+  /** whether the snowball stemmers are in the Classpath. */
+  protected static boolean m_Present = false;
+
+  /** contains the all the found stemmers (language names). */
+  protected static Vector<String> m_Stemmers;
+
+  /** the current stemmer. */
+  protected Object m_Stemmer;
+
+  /** the stem method. */
+  protected transient Method m_StemMethod;
+
+  /** the setCurrent method. */
+  protected transient Method m_SetCurrentMethod;
+
+  /** the getCurrent method. */
+  protected transient Method m_GetCurrentMethod;
+   
+  /** check for Snowball statically (needs only to be done once) */
+  static {
+    checkForSnowball();
+  }
+
+  /**
+   * initializes the stemmer ("porter").
+   */
+  public SnowballStemmer() {
+    this("porter");
+    initStemmers();
+  }
+
+  /**
+   * initializes the stemmer with the given stemmer.
+   *
+   * @param name        the name of the stemmer
+   */
+  public SnowballStemmer(String name) {
+    super();
+      
+    setStemmer(name);
+  }
+
+  /**
+   * checks whether Snowball is present in the classpath.
+   */
+  private static void checkForSnowball() {
+    try {
+      Class.forName(SNOWBALL_PROGRAM);
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+
+  /**
+   * Returns a string describing the stemmer.
+   * 
+   * @return a description suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A wrapper class for the Snowball stemmers. Only available if the "
+      + "Snowball classes are in the classpath.\n"
+      + "If the class discovery is not dynamic, i.e., the property 'UseDynamic' "
+      + "in the props file 'weka/gui/GenericPropertiesCreator.props' is 'false', "
+      + "then the property 'org.tartarus.snowball.SnowballProgram' in the "
+      + "'weka/gui/GenericObjectEditor.props' file has to be uncommented "
+      + "as well. If necessary you have to discover and fill in the snowball "
+      + "stemmers manually. You can use the 'weka.core.ClassDiscovery' for this:\n"
+      + "  java weka.core.ClassDiscovery org.tartarus.snowball.SnowballProgram org.tartarus.snowball.ext\n"
+      + "\n"
+      + "For more information visit these web sites:\n"
+      + "  http://weka.wikispaces.com/Stemmers\n"
+      + "  http://snowball.tartarus.org/\n";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>        result;
+    
+    result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tThe name of the snowball stemmer (default 'porter').\n"
+        + "\tavailable stemmers:\n" 
+        + getStemmerList(65, "\t   "),
+        "S", 1, "-S <name>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Parses the options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;name&gt;
+   *  The name of the snowball stemmer (default 'porter').
+   *  available stemmers:
+   *     danish, dutch, english, finnish, french, german, italian, 
+   *     norwegian, porter, portuguese, russian, spanish, swedish
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to parse
+   * @throws Exception 	if parsing fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setStemmer(tmpStr);
+    else
+      setStemmer("porter");
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>        result;
+    
+    result  = new Vector<String>();
+    
+    if (getStemmer() != null) {
+      result.add("-S");
+      result.add("" + getStemmer());
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * extracts the stemmer name form the classname.
+   * 
+   * @param classname     the full classname of the stemmer
+   * @return              the name of the stemmer
+   */
+  private static String getStemmerName(String classname) {
+    return classname.replaceAll(".*\\.", "").replaceAll("Stemmer$", "");
+  }
+
+  /**
+   * returns the full classname of the stemmer.
+   *
+   * @param name          the name of the stemmer
+   * @return              the full classname of the stemmer
+   * @see                 #PACKAGE_EXT
+   */
+  private static String getStemmerClassname(String name) {
+    return PACKAGE_EXT + "." + name + "Stemmer";
+  }
+
+  /**
+   * retrieves the language names of the availabel stemmers.
+   */
+  private static void initStemmers() {
+    Vector        classnames;
+    int           i;
+    
+    if (m_Stemmers != null)
+      return;
+    
+    m_Stemmers = new Vector<String>();
+    
+    if (!m_Present)
+      return;
+
+    classnames = GenericObjectEditor.getClassnames(SNOWBALL_PROGRAM);
+    // try dynamic discovery if not in props file
+    if (classnames.size() == 0) {
+      classnames = ClassDiscovery.find(SNOWBALL_PROGRAM, PACKAGE_EXT);
+      for (i = 0; i < classnames.size(); i++)
+	m_Stemmers.add(getStemmerName(classnames.get(i).toString()));
+    }
+  }
+
+  /**
+   * returns whether Snowball is present or not, i.e. whether the classes are
+   * in the classpath or not
+   *
+   * @return whether Snowball is available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+
+  /**
+   * returns an enumeration over all currently stored stemmer names.
+   * 
+   * @return all available stemmers
+   */
+  public static Enumeration listStemmers() {
+    initStemmers();
+    
+    return m_Stemmers.elements();
+  }
+
+  /**
+   * generates a comma list of the available stemmers.
+   * 
+   * @param lineLength    the max line length, before a linefeed is inserted
+   *                      (0 is unlimited)
+   * @param indention     the indention of a line
+   * @return              the generated list
+   */
+  private static String getStemmerList(int lineLength, String indention) {
+    String        result;
+    Enumeration   enm;
+    String        name;
+    String        line;
+    
+    result = "";
+    line   = "";
+    enm    = listStemmers();
+    while (enm.hasMoreElements()) {
+      name = enm.nextElement().toString();
+      if (line.length() > 0)
+        line += ", ";
+      if ( (lineLength > 0) && (line.length() + name.length() > lineLength) ) {
+        result += indention + line + "\n";
+        line    = "";
+      }
+      line += name;
+    }
+
+    if (line.length() > 0)
+      result += indention + line + "\n";
+    
+    return result;
+  }
+
+  /**
+   * returns the name of the current stemmer, null if none is set.
+   * 
+   * @return the name of the stemmer
+   */
+  public String getStemmer() {
+    initStemmers();
+    
+    if (m_Stemmer == null)
+      return null;
+    else
+      return getStemmerName(m_Stemmer.getClass().getName());
+  }
+
+  /**
+   * sets the stemmer with the given name, e.g., "porter".
+   *
+   * @param name        the name of the stemmer, e.g., "porter"
+   */
+  public void setStemmer(String name) {
+    Class<?>       snowballClass;
+    Class[]     argClasses;
+    
+    initStemmers();
+    
+    if (m_Stemmers.contains(name)) {
+      try {
+        snowballClass = Class.forName(getStemmerClassname(name));
+        m_Stemmer     = snowballClass.newInstance();
+
+        // methods
+        argClasses         = new Class[0];
+        m_StemMethod       = snowballClass.getMethod("stem", argClasses);
+        
+        argClasses         = new Class[1];
+        argClasses[0]      = String.class;
+        m_SetCurrentMethod = snowballClass.getMethod("setCurrent", argClasses);
+        
+        argClasses         = new Class[0];
+        m_GetCurrentMethod = snowballClass.getMethod("getCurrent", argClasses);
+      }
+      catch (Exception e) {
+        System.out.println(
+              "Error initializing stemmer '" + name + "'!"
+            + e.getMessage());
+        m_Stemmer = null;
+      }
+    }
+    else {
+      System.err.println("Stemmer '" + name + "' unknown!");
+      m_Stemmer = null;
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String stemmerTipText() {
+    return "The Snowball stemmer to use, available: " + getStemmerList(0, "");
+  }
+
+  /**
+   * Returns the word in its stemmed form.
+   *
+   * @param word      the unstemmed word
+   * @return          the stemmed word
+   */
+  public String stem(String word) {
+    String      result;
+    Object[]    args;
+    
+    if (m_Stemmer == null) {
+      result = new String(word);
+    }
+    else {
+      // after de-serialization, the methods are null and need to be
+      // re-initialized
+      if (m_SetCurrentMethod == null)
+	setStemmer(getStemmer());
+      
+      try {
+        // set word
+        args    = new Object[1];
+        args[0] = word;
+        m_SetCurrentMethod.invoke(m_Stemmer, args);
+
+        // stem word
+        args = new Object[0];
+        m_StemMethod.invoke(m_Stemmer, args);
+
+        // get word
+        args   = new Object[0];
+        result = (String) m_GetCurrentMethod.invoke(m_Stemmer, args);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+        result = word;
+      }
+    }
+      
+    return result;
+  }
+
+  /**
+   * returns a string representation of the stemmer.
+   * 
+   * @return a string representation of the stemmer
+   */
+  public String toString() {
+    String      result;
+
+    result  = getClass().getName();
+    result += " " + Utils.joinOptions(getOptions());
+
+    return result.trim();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the stemmer with the given options.
+   *
+   * @param args      the options
+   */
+  public static void main(String[] args) {
+    try {
+      Stemming.useStemmer(new SnowballStemmer(), args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/Stemmer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/Stemmer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/Stemmer.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Stemmer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.RevisionHandler;
+
+import java.io.Serializable;
+
+/**
+ * Interface for all stemming algorithms.
+ *
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 5953 $
+ */
+public interface Stemmer 
+  extends Serializable, RevisionHandler {
+
+  /**
+   * Stems the given word and returns the stemmed version
+   *
+   * @param word      the unstemmed word
+   * @return          the stemmed word
+   */
+  public String stem(String word);
+}
Index: branches/MetisMQI/src/main/java/weka/core/stemmers/Stemming.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/stemmers/Stemming.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/stemmers/Stemming.java	(revision 29)
@@ -0,0 +1,195 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Stemming.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.stemmers;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A helper class for using the stemmers. Run with option '-h' to list
+ * all the available options.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class Stemming
+  implements RevisionHandler {
+
+  /**
+   * lists all the options on the command line
+   *
+   * @param stemmer     the stemmer to list the parameters for
+   * @return 		the option string
+   */
+  protected static String makeOptionsString(Stemmer stemmer) {
+    Vector<Option>          options;
+    Enumeration     enm;
+    StringBuffer    result;
+
+    options = new Vector<Option>();
+    
+    // general options
+    options.add(
+        new Option(
+          "\tDisplays this help.",
+          "h", 0, "-h"));
+
+    options.add(
+        new Option(
+          "\tThe file to process.",
+          "i", 1, "-i <input-file>"));
+
+    options.add(
+        new Option(
+          "\tThe file to output the processed data to (default stdout).",
+          "o", 1, "-o <output-file>"));
+
+    options.add(
+        new Option(
+          "\tUses lowercase strings.",
+          "l", 0, "-l"));
+
+    // stemmer options?
+    if (stemmer instanceof OptionHandler) {
+      enm = ((OptionHandler) stemmer).listOptions();
+      while (enm.hasMoreElements())
+        options.add((Option)enm.nextElement());
+    }
+
+    // print options
+    result = new StringBuffer();
+    result.append("\nStemmer options:\n\n");
+    enm = options.elements();
+    while (enm.hasMoreElements()) {
+      Option option = (Option) enm.nextElement();
+      result.append(option.synopsis() + "\n");
+      result.append(option.description() + "\n");
+    }
+
+    return result.toString();
+  }
+  
+  /**
+   * Applies the given stemmer according to the given options. '-h' lists
+   * all the available options for the given stemmer.
+   *
+   * @param stemmer     the stemmer to use
+   * @param options     the options for the stemmer
+   * @throws Exception	if something goes wrong
+   */
+  public static void useStemmer(Stemmer stemmer, String[] options) 
+    throws Exception {
+
+    Reader          reader;
+    StringBuffer    input;
+    Writer          output;
+    String          tmpStr;
+    boolean         lowerCase;
+    
+    // help?
+    if (Utils.getFlag('h', options)) {
+      System.out.println(makeOptionsString(stemmer));
+      return;
+    }
+
+    // input file
+    tmpStr = Utils.getOption('i', options);
+    if (tmpStr.length() == 0)
+      throw new IllegalArgumentException(
+          "No input file defined!" + makeOptionsString(stemmer));
+    else
+      reader = new BufferedReader(
+                  new InputStreamReader(new FileInputStream(tmpStr)));
+
+    input = new StringBuffer();
+
+    // output file?
+    tmpStr = Utils.getOption('o', options);
+    if (tmpStr.length() == 0)
+      output = new BufferedWriter(
+                  new OutputStreamWriter(System.out));
+    else
+      output = new BufferedWriter(
+                  new OutputStreamWriter(new FileOutputStream(tmpStr)));
+
+    // lowercase?
+    lowerCase = Utils.getFlag('l', options);
+
+    // stemmer options
+    if (stemmer instanceof OptionHandler)
+      ((OptionHandler) stemmer).setOptions(options);
+    
+    // unknown options?
+    try {
+      Utils.checkForRemainingOptions(options);
+    }
+    catch (Exception e) {
+      System.out.println(e.getMessage());
+      System.out.println(makeOptionsString(stemmer));
+      return;
+    }
+    
+    // process file
+    int character;
+    while ((character = reader.read()) != -1) {
+      char ch = (char) character;
+      if (Character.isWhitespace((char) ch)) {
+        if (input.length() > 0) {
+          output.write(stemmer.stem(input.toString()));
+          input = new StringBuffer();
+        }
+        output.write(ch);
+      } 
+      else {
+        if (lowerCase)
+          input.append(Character.toLowerCase(ch));
+        else
+          input.append(ch);
+      }
+    }
+    output.flush();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/tokenizers/AlphabeticTokenizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/tokenizers/AlphabeticTokenizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/tokenizers/AlphabeticTokenizer.java	(revision 29)
@@ -0,0 +1,148 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AlphabeticStringTokenizer.java
+ * Copyright (C) 2003, 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.RevisionUtils;
+
+import java.util.NoSuchElementException;
+
+/**
+ <!-- globalinfo-start -->
+ * Alphabetic string tokenizer, tokens are to be formed only from contiguous alphabetic sequences.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author  Asrhaf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class AlphabeticTokenizer
+  extends Tokenizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6705199562609861697L;
+
+  /** the characters of the string */
+  protected char[] m_Str;
+  
+  /** the current position */
+  protected int m_CurrentPos;
+  
+  /**
+   * Returns a string describing the stemmer
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Alphabetic string tokenizer, tokens are to be formed only from "
+      + "contiguous alphabetic sequences.";
+  }
+  
+  /**
+   * returns whether there are more elements still
+   * 
+   * @return true 	if there are still more elements
+   */
+  public boolean hasMoreElements() {
+    int beginpos = m_CurrentPos;
+
+    while ( (beginpos < m_Str.length) && 
+	((m_Str[beginpos] < 'a') || (m_Str[beginpos] > 'z')) &&
+	((m_Str[beginpos] < 'A') || (m_Str[beginpos] > 'Z')) ) {
+      beginpos++;    
+    }
+    m_CurrentPos = beginpos;
+
+    if ( (beginpos < m_Str.length) && 
+	(((m_Str[beginpos] >= 'a') && (m_Str[beginpos] <= 'z')) ||
+	 ((m_Str[beginpos] >= 'A') && (m_Str[beginpos] <= 'Z'))) ) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  /**
+   * returns the next element
+   * 
+   * @return 		the next element
+   */
+  public Object nextElement() {
+    int beginpos, endpos;
+    
+    beginpos = m_CurrentPos;
+
+    while ( (beginpos < m_Str.length) && 
+	((m_Str[beginpos] < 'a') && (m_Str[beginpos] > 'z')) &&
+	((m_Str[beginpos] < 'A') && (m_Str[beginpos] > 'Z')) ) {
+      beginpos++;    
+    }
+    m_CurrentPos = endpos = beginpos;
+
+    if (beginpos >= m_Str.length)
+      throw new NoSuchElementException("No more tokens present");
+
+    while ((endpos < m_Str.length) &&
+	( ((m_Str[endpos] >= 'a') && (m_Str[endpos]<='z')) ||
+	  ((m_Str[endpos] >= 'A') && (m_Str[endpos]<='Z'))) ) {
+      endpos++;
+    }
+
+    String s = new String(m_Str, beginpos, endpos - m_CurrentPos);
+    m_CurrentPos = endpos;
+
+    return s;
+  }      
+
+  /**
+   * Sets the string to tokenize. Tokenization happens immediately.
+   * 
+   * @param s		the string to tokenize
+   */
+  public void tokenize(String s) {
+    m_CurrentPos = 0;
+    m_Str = new char[s.length()];
+    s.getChars(0, s.length(), m_Str, 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the tokenizer with the given options and strings to tokenize.
+   * The tokens are printed to stdout.
+   * 
+   * @param args	the commandline options and strings to tokenize
+   */
+  public static void main(String[] args) {
+    runTokenizer(new AlphabeticTokenizer(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/tokenizers/CharacterDelimitedTokenizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/tokenizers/CharacterDelimitedTokenizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/tokenizers/CharacterDelimitedTokenizer.java	(revision 29)
@@ -0,0 +1,128 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DelimitedTokenizer.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract superclass for tokenizers that take characters as delimiters.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class CharacterDelimitedTokenizer
+  extends Tokenizer {
+
+  /** Delimiters used in tokenization */
+  protected String m_Delimiters = " \r\n\t.,;:'\"()?!";
+  
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>	result;
+    
+    result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tThe delimiters to use\n"
+	+ "\t(default ' \\r\\n\\t.,;:'\"()?!').",
+        "delimiters", 1, "-delimiters <value>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return 		the list of current option settings as an array of 
+   * 			strings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    result.add("-delimiters");
+    result.add(getDelimiters());
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption("delimiters", options);
+    if (tmpStr.length() != 0)
+      setDelimiters(tmpStr);
+    else
+      setDelimiters(" \r\n\t.,;:'\"()?!");
+  }
+
+  /**
+   * Get the value of delimiters (not backquoted).
+   *
+   * @return 		Value of delimiters.
+   */
+  public String getDelimiters() {
+    return m_Delimiters;
+  }
+    
+  /**
+   * Set the value of delimiters. For convenienve, the strings 
+   * "\r", "\n", "\t", "\'", "\\" get automatically translated into their 
+   * character representations '\r', '\n', '\t', '\'', '\\'. This means, one 
+   * can either use <code>setDelimiters("\r\n\t\\");</code> or 
+   * <code>setDelimiters("\\r\\n\\t\\\\");</code>.
+   *
+   * @param value 	Value to assign to delimiters.
+   * @see 		Utils#unbackQuoteChars(String)
+   */
+  public void setDelimiters(String value) {
+    m_Delimiters = Utils.unbackQuoteChars(value);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String delimitersTipText() {
+    return "Set of delimiter characters to use in tokenizing (\\r, \\n and \\t can be used for carriage-return, line-feed and tab)";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/tokenizers/NGramTokenizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/tokenizers/NGramTokenizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/tokenizers/NGramTokenizer.java	(revision 29)
@@ -0,0 +1,332 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NGramTokenizer.java
+ * Copyright (C) 2007 University of Waikato
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Splits a string into an n-gram with min and max grams.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -delimiters &lt;value&gt;
+ *  The delimiters to use
+ *  (default ' \r\n\t.,;:'"()?!').</pre>
+ * 
+ * <pre> -max &lt;int&gt;
+ *  The max size of the Ngram (default = 3).</pre>
+ * 
+ * <pre> -min &lt;int&gt;
+ *  The min size of the Ngram (default = 1).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Sebastian Germesin (sebastian.germesin@dfki.de)
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class NGramTokenizer
+  extends CharacterDelimitedTokenizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2181896254171647219L;
+
+  /** the maximum number of N */
+  protected int m_NMax = 3;
+  
+  /** the minimum number of N */
+  protected int m_NMin = 1;
+  
+  /** the current length of the N-grams */
+  protected int m_N;
+  
+  /** the number of strings available */
+  protected int m_MaxPosition;
+  
+  /** the current position for returning elements */
+  protected int m_CurrentPosition;
+  
+  /** all the available grams */
+  protected String[] m_SplitString;
+  
+  /**
+   * Returns a string describing the stemmer
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Splits a string into an n-gram with min and max grams.";
+  }
+  
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>	result;
+    Enumeration enm;
+    
+    result = new Vector<Option>();
+    
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement((Option)enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe max size of the Ngram (default = 3).",
+	"max", 1, "-max <int>"));
+
+    result.addElement(new Option(
+	"\tThe min size of the Ngram (default = 1).",
+	"min", 1, "-min <int>"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return 		the list of current option settings as an array of 
+   * 			strings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[]		options;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-max");
+    result.add("" + getNGramMaxSize());
+
+    result.add("-min");
+    result.add("" + getNGramMinSize());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -delimiters &lt;value&gt;
+   *  The delimiters to use
+   *  (default ' \r\n\t.,;:'"()?!').</pre>
+   * 
+   * <pre> -max &lt;int&gt;
+   *  The max size of the Ngram (default = 3).</pre>
+   * 
+   * <pre> -min &lt;int&gt;
+   *  The min size of the Ngram (default = 1).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	value;
+    
+    super.setOptions(options);
+
+    value = Utils.getOption("max", options);
+    if (value.length() != 0)
+      setNGramMaxSize(Integer.parseInt(value));
+    else
+      setNGramMaxSize(3);
+
+    value = Utils.getOption("min", options);
+    if (value.length() != 0)
+      setNGramMinSize(Integer.parseInt(value));
+    else
+      setNGramMinSize(1);
+  }
+  
+  /**
+   * Gets the max N of the NGram.
+   * 
+   * @return 		the size (N) of the NGram.
+   */
+  public int getNGramMaxSize() {
+    return m_NMax;
+  }
+
+  /**
+   * Sets the max size of the Ngram.
+   * 
+   * @param value 	the size of the NGram.
+   */
+  public void setNGramMaxSize(int value) {
+    if (value < 1)
+      m_NMax = 1;
+    else
+      m_NMax = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String NGramMaxSizeTipText() {
+    return "The max N of the NGram.";
+  }
+
+  /**
+   * Sets the min size of the Ngram.
+   * 
+   * @param value 	the size of the NGram.
+   */
+  public void setNGramMinSize(int value) {
+    if (value < 1)
+      m_NMin = 1;
+    else
+      m_NMin = value;
+  }
+
+  /**
+   * Gets the min N of the NGram.
+   * 
+   * @return 		the size (N) of the NGram.
+   */
+  public int getNGramMinSize() {
+    return m_NMin;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String NGramMinSizeTipText() {
+    return "The min N of the NGram.";
+  }
+
+  /**
+   * returns true if there's more elements available
+   * 
+   * @return		true if there are more elements available
+   */
+  public boolean hasMoreElements() {
+    return (m_CurrentPosition < m_MaxPosition && 
+	m_N - 1 + m_CurrentPosition < m_MaxPosition && 
+	m_N >= m_NMin);
+  }
+  
+  /**
+   * Returns N-grams and also (N-1)-grams and .... and 1-grams.
+   * 
+   * @return		the next element
+   */
+  public Object nextElement() {
+    String retValue = "";
+    
+    for (int i = 0; i < m_N && i + m_CurrentPosition < m_MaxPosition; i++)
+      retValue += " " + m_SplitString[m_CurrentPosition + i];
+    
+    m_CurrentPosition++;
+    
+    if (m_CurrentPosition + m_N - 1 == m_MaxPosition) {
+      m_CurrentPosition = 0;
+      m_N--;
+    }
+
+    return retValue.trim();
+  }
+
+  /** 
+   * filters out empty strings in m_SplitString and
+   * replaces m_SplitString with the cleaned version.
+   * 
+   * @see #m_SplitString
+   */
+  protected void filterOutEmptyStrings() {
+    String[] newSplit;
+    LinkedList<String> clean = new LinkedList<String>();
+
+    for (int i = 0; i < m_SplitString.length; i++) {
+      if (!m_SplitString[i].equals(""))
+	clean.add(m_SplitString[i]);
+    }
+
+    newSplit = new String[clean.size()];
+    for (int i = 0; i < clean.size(); i++) 
+      newSplit[i] = clean.get(i);
+
+    m_SplitString = newSplit;
+  }
+  
+  /**
+   * Sets the string to tokenize. Tokenization happens immediately.
+   * 
+   * @param s		the string to tokenize
+   */
+  public void tokenize(String s) {
+    m_N           = m_NMax;
+    m_SplitString = s.split("[" + getDelimiters() + "]");
+    
+    filterOutEmptyStrings();
+
+    m_CurrentPosition = 0;
+    m_MaxPosition     = m_SplitString.length;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the tokenizer with the given options and strings to tokenize.
+   * The tokens are printed to stdout.
+   * 
+   * @param args	the commandline options and strings to tokenize
+   */
+  public static void main(String[] args) {
+    runTokenizer(new NGramTokenizer(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/tokenizers/Tokenizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/tokenizers/Tokenizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/tokenizers/Tokenizer.java	(revision 29)
@@ -0,0 +1,184 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Tokenizer.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A superclass for all tokenizer algorithms.
+ * 
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public abstract class Tokenizer
+  implements Enumeration, OptionHandler, Serializable, RevisionHandler {
+  
+  /**
+   * Returns a string describing the stemmer
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public abstract String globalInfo();
+    
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    return (new Vector()).elements();
+  }
+  
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return 		the list of current option settings as an array of 
+   * 			strings
+   */
+  public String[] getOptions() {
+    return new String[0];
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    // nothing in this class
+  }
+
+  /**
+   * Tests if this enumeration contains more elements.
+   * 
+   * @return 		true if and only if this enumeration object contains 
+   * 			at least one more element to provide; false otherwise.
+   */
+  public abstract boolean hasMoreElements();
+
+  /**
+   * Returns the next element of this enumeration if this enumeration object 
+   * has at least one more element to provide.
+   * 
+   * @return		the next element of this enumeration.
+   */
+  public abstract Object nextElement();
+  
+  /**
+   * Sets the string to tokenize. Tokenization happens immediately.
+   * 
+   * @param s		the string to tokenize
+   */
+  public abstract void tokenize(String s);
+  
+  /**
+   * initializes the given tokenizer with the given options and runs the
+   * tokenizer over all the remaining strings in the options array. If no 
+   * strings remained in the option string then data is read from stdin, line 
+   * by line.
+   * 
+   * @param tokenizer	the tokenizer to use
+   * @param options	the options for the tokenizer
+   * @return		the tokenized strings
+   * @throws Exception	if setting of options or tokenization fails
+   */
+  public static String[] tokenize(Tokenizer tokenizer, String[] options) throws Exception {
+    Vector<String>	result;
+    Vector<String>	tmpResult;
+    Vector<String>	data;
+    int			i;
+    boolean		processed;
+    BufferedReader	reader;
+    String		line;
+    
+    result = new Vector<String>();
+    
+    // init tokenizer
+    tokenizer.setOptions(options);
+
+    // for storing the data to process
+    data = new Vector<String>();
+    
+    // run over all un-processed strings in the options array
+    processed = false;
+    for (i = 0; i < options.length; i++) {
+      if (options[i].length() != 0) {
+	processed = true;
+	data.add(options[i]);
+      }
+    }
+    
+    // if no strings in option string then read from stdin
+    if (!processed) {
+      reader = new BufferedReader(new InputStreamReader(System.in));
+      while ((line = reader.readLine()) != null) {
+	data.add(line);
+      }
+    }
+
+    // process data
+    for (i = 0; i < data.size(); i++) {
+      tmpResult = new Vector<String>();
+      tokenizer.tokenize(data.get(i));
+      while (tokenizer.hasMoreElements())
+	tmpResult.add((String) tokenizer.nextElement());
+      // add to result
+      result.addAll(tmpResult);
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * initializes the given tokenizer with the given options and runs the
+   * tokenizer over all the remaining strings in the options array. The 
+   * generated tokens are then printed to stdout. If no strings remained
+   * in the option string then data is read from stdin, line by line.
+   * 
+   * @param tokenizer	the tokenizer to use
+   * @param options	the options for the tokenizer
+   */
+  public static void runTokenizer(Tokenizer tokenizer, String[] options) {
+    String[]	result;
+    int		i;
+
+    try {
+      result = tokenize(tokenizer, options);
+      for (i = 0; i < result.length; i++)
+	System.out.println(result[i]);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/tokenizers/WordTokenizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/tokenizers/WordTokenizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/tokenizers/WordTokenizer.java	(revision 29)
@@ -0,0 +1,115 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleStringTokenizer.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.RevisionUtils;
+
+import java.util.StringTokenizer;
+
+/**
+ <!-- globalinfo-start -->
+ * A simple tokenizer that is using the java.util.StringTokenizer class to tokenize the strings.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -delimiters &lt;value&gt;
+ *  The delimiters to use
+ *  (default ' \r\n\t.,;:'"()?!').</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class WordTokenizer
+  extends CharacterDelimitedTokenizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -930893034037880773L;
+  
+  /** the actual tokenizer */
+  protected transient StringTokenizer m_Tokenizer;
+  
+  /**
+   * Returns a string describing the stemmer
+   * 
+   * @return 		a description suitable for displaying in the 
+   * 			explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A simple tokenizer that is using the java.util.StringTokenizer "
+      + "class to tokenize the strings.";
+  }
+
+  /**
+   * Tests if this enumeration contains more elements.
+   * 
+   * @return 		true if and only if this enumeration object contains 
+   * 			at least one more element to provide; false otherwise.
+   */
+  public boolean hasMoreElements() {
+    return m_Tokenizer.hasMoreElements();
+  }
+
+  /**
+   * Returns the next element of this enumeration if this enumeration object 
+   * has at least one more element to provide.
+   * 
+   * @return		the next element of this enumeration.
+   */
+  public Object nextElement() {
+    return m_Tokenizer.nextElement();
+  }
+  
+  /**
+   * Sets the string to tokenize. Tokenization happens immediately.
+   * 
+   * @param s		the string to tokenize
+   */
+  public void tokenize(String s) {
+    m_Tokenizer = new StringTokenizer(s, getDelimiters());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * Runs the tokenizer with the given options and strings to tokenize.
+   * The tokens are printed to stdout.
+   * 
+   * @param args	the commandline options and strings to tokenize
+   */
+  public static void main(String[] args) {
+    runTokenizer(new WordTokenizer(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/core/version.txt
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/version.txt	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/version.txt	(revision 29)
@@ -0,0 +1,1 @@
+3-7-1
Index: branches/MetisMQI/src/main/java/weka/core/xml/KOML.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/KOML.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/KOML.java	(revision 29)
@@ -0,0 +1,240 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KOML.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class is a helper class for XML serialization using <a href="http://koala.ilog.fr/XML/serialization/" target="_blank">KOML</a> .
+ * KOML does not need to be present, since the class-calls are done generically via Reflection.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision 1.0$
+ */
+public class KOML
+   implements RevisionHandler {
+  
+   /**
+    * indicates whether <a href="http://koala.ilog.fr/XML/serialization/" target="_blank">KOML</a> 
+    * (Koala Object Markup Language) is present
+    */
+   protected static boolean m_Present = false;
+
+   /** the extension for KOML files (including '.') */
+   public final static String FILE_EXTENSION = ".koml";
+   
+   /** check for KOML statically (needs only to be done once) */
+   static {
+      checkForKOML();
+   }
+
+   /**
+    * checks whether the KOML is present in the class path
+    */
+   private static void checkForKOML() {
+      try {
+         Class.forName("fr.dyade.koala.xml.koml.KOMLSerializer");
+         m_Present = true;
+      }
+      catch (Exception e) {
+         m_Present = false;
+      }
+   }
+  
+   /**
+    * returns whether KOML is present or not, i.e. whether the classes are in the
+    * classpath or not
+    *
+    * @return whether KOML is available
+    */
+   public static boolean isPresent() {
+      return m_Present;
+   }
+   
+   /**
+    * reads the XML-serialized object from the given file
+    * @param filename the file to deserialize the object from
+    * @return the deserialized object
+    * @throws Exception if something goes wrong while reading from the file
+    */
+   public static Object read(String filename) throws Exception {
+      return read(new FileInputStream(filename));
+   }
+   
+   /**
+    * reads the XML-serialized object from the given file
+    * @param file the file to deserialize the object from
+    * @return the deserialized object
+    * @throws Exception if something goes wrong while reading from the file
+    */
+   public static Object read(File file) throws Exception {
+      return read(new FileInputStream(file));
+   }
+   
+   /**
+    * reads the XML-serialized object from a stream
+    * @param stream the stream to deserialize the object from
+    * @return the deserialized object
+    * @throws Exception if something goes wrong while reading from the stream
+    */
+   public static Object read(InputStream stream) throws Exception {
+      Class<?>                            komlClass;
+      Class[]                          komlClassArgs;
+      Object[]                         komlArgs;
+      java.lang.reflect.Constructor    constructor;
+      Object                           koml;
+      java.lang.reflect.Method         methodRead;
+      java.lang.reflect.Method         methodClose;
+      Class[]                          readArgsClasses;
+      Class[]                          closeArgsClasses;
+      Object[]                         readArgs;
+      Object[]                         closeArgs;
+      Object                           result;
+
+      result = null;
+      
+      // get Deserializer
+      komlClass        = Class.forName("fr.dyade.koala.xml.koml.KOMLDeserializer");
+      komlClassArgs    = new Class[2];
+      komlClassArgs[0] = java.io.InputStream.class;
+      komlClassArgs[1] = Boolean.TYPE;
+      komlArgs         = new Object[2];
+      komlArgs[0]      = stream;
+      komlArgs[1]      = new Boolean(false);
+      constructor      = komlClass.getConstructor(komlClassArgs);
+      koml             = constructor.newInstance(komlArgs);
+      readArgsClasses  = new Class[0];
+      methodRead       = komlClass.getMethod("readObject", readArgsClasses);
+      readArgs         = new Object[0];
+      closeArgsClasses = new Class[0];
+      methodClose      = komlClass.getMethod("close", closeArgsClasses);
+      closeArgs        = new Object[0];
+
+      // execute it
+      try {
+         result = methodRead.invoke(koml, readArgs);
+      }
+      catch (Exception e) {
+         result = null;
+      } 
+      finally {
+         methodClose.invoke(koml, closeArgs);
+      }
+      
+      return result;
+   }
+   
+   /**
+    * writes the XML-serialized object to the given file
+    * @param filename the file to serialize the object to
+    * @param o the object to write to the file
+    * @return whether writing was successful or not
+    * @throws Exception if something goes wrong while writing to the file
+    */
+   public static boolean write(String filename, Object o) throws Exception {
+      return write(new FileOutputStream(filename), o);
+   }
+   
+   /**
+    * write the XML-serialized object to the given file
+    * @param file the file to serialize the object to
+    * @param o the object to write to the file
+    * @return whether writing was successful or not
+    * @throws Exception if something goes wrong while writing to the file
+    */
+   public static boolean write(File file, Object o) throws Exception {
+      return write(new FileOutputStream(file), o);
+   }
+   
+   /**
+    * writes the XML-serialized object to a stream
+    * @param stream the stream to serialize the object to
+    * @param o the object to write to the stream
+    * @return whether writing was successful or not
+    * @throws Exception if something goes wrong while writing to the stream
+    */
+   public static boolean write(OutputStream stream, Object o) throws Exception {
+      Class<?>                            komlClass;
+      Class[]                          komlClassArgs;
+      Object[]                         komlArgs;
+      java.lang.reflect.Constructor    constructor;
+      Object                           koml;
+      java.lang.reflect.Method         methodAdd;
+      java.lang.reflect.Method         methodClose;
+      Class[]                          addArgsClasses;
+      Class[]                          closeArgsClasses;
+      Object[]                         addArgs;
+      Object[]                         closeArgs;
+      boolean                          result;
+      
+      result = false;
+
+      // get Deserializer
+      komlClass        = Class.forName("fr.dyade.koala.xml.koml.KOMLSerializer");
+      komlClassArgs    = new Class[2];
+      komlClassArgs[0] = java.io.OutputStream.class;
+      komlClassArgs[1] = Boolean.TYPE;
+      komlArgs         = new Object[2];
+      komlArgs[0]      = stream;
+      komlArgs[1]      = new Boolean(false);
+      constructor      = komlClass.getConstructor(komlClassArgs);
+      koml             = constructor.newInstance(komlArgs);
+      addArgsClasses   = new Class[1];
+      addArgsClasses[0] = Object.class;
+      methodAdd        = komlClass.getMethod("addObject", addArgsClasses);
+      addArgs          = new Object[1];
+      addArgs[0]       = o;
+      closeArgsClasses = new Class[0];
+      methodClose      = komlClass.getMethod("close", closeArgsClasses);
+      closeArgs        = new Object[0];
+
+      // execute it
+      try {
+         methodAdd.invoke(koml, addArgs);
+         result = true;
+      }
+      catch (Exception e) {
+         result = false;
+      } 
+      finally {
+         methodClose.invoke(koml, closeArgs);
+      }
+      
+      return result;
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/MethodHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/MethodHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/MethodHandler.java	(revision 29)
@@ -0,0 +1,205 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MethodHandler.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * This class handles relationships between display names of properties 
+ * (or classes) and Methods that are associated with them. 
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class MethodHandler
+   implements RevisionHandler {
+  
+   /** 
+    * stores the properties/class - Method relationship
+    * 
+    * @see #keys()
+    * @see #add(Class, Method)
+    * @see #add(String, Method)
+    * @see #remove(Class)
+    * @see #remove(String)
+    * @see #get(Class)
+    * @see #get(String)
+    * @see #contains(Class)
+    * @see #contains(String)  
+    */
+  protected Hashtable<Object,Method> m_Methods = null;
+   
+   /**
+    * initializes the handler
+    */
+   public MethodHandler() {
+      super();
+      m_Methods  = new Hashtable<Object,Method>();
+   }
+   
+   /**
+    * returns an enumeration over all currently stored custom methods, i.e. it
+    * returns the display names/classes in the enumeration.
+    * 
+    * @return the currently stored methods
+    * @see #m_Methods
+    */
+   public Enumeration keys() {
+      return m_Methods.keys();
+   }
+   
+   /**
+    * adds the specified method for the property with the given displayname
+    * to its internal list.
+    * 
+    * @param displayName the display name of the property to handle manually
+    * @param method the method, which will be invoked by reflection to handle
+    *        the property manually 
+    * @see #m_Methods
+    */
+   public void add(String displayName, Method method) {
+      if (method != null)
+         m_Methods.put(displayName, method);
+   }
+   
+   /**
+    * adds the specified method for the given class to its internal list.
+    * 
+    * @param c the class to handle manually
+    * @param method the method, which will be invoked by reflection to handle
+    *        the property manually 
+    * @see #m_Methods
+    */
+   public void add(Class c, Method method) {
+      if (method != null)
+         m_Methods.put(c, method);
+   }
+   
+   /**
+    * removes the method for the property specified by the display name
+    * from its internal list. 
+    * 
+    * @param displayName the display name of the propery to remove the custom
+    *        method for
+    * @return whether the method was stored in the list at all 
+    * @see #m_Methods
+    */
+   public boolean remove(String displayName) {
+      return (m_Methods.remove(displayName) != null);
+   }
+   
+   /**
+    * removes the method for the specified class from its internal list. 
+    * 
+    * @param c the class to remove the custom method for
+    * @return whether the method was stored in the list at all 
+    * @see #m_Methods
+    */
+   public boolean remove(Class c) {
+      return (m_Methods.remove(c) != null);
+   }
+   
+   /**
+    * checks whether a method is stored for the given property
+    * 
+    * @param displayName the display name of the property to check for a method
+    * @return whether a method is currently stored
+    * @see #m_Methods
+    */
+   public boolean contains(String displayName) {
+      return m_Methods.containsKey(displayName);
+   }
+   
+   /**
+    * checks whether a method is stored for the given class
+    * 
+    * @param c the class to check for a method
+    * @return whether a method is currently stored
+    * @see #m_Methods
+    */
+   public boolean contains(Class c) {
+      return m_Methods.containsKey(c);
+   }
+   
+   /**
+    * returns the stored method for the given property
+    * 
+    * @param displayName the display name of the property to retrieve the 
+    *        method for
+    * @return the method associated with the display name, can be <code>null</code>
+    * @see #m_Methods
+    */
+   public Method get(String displayName) {
+      return (Method) m_Methods.get(displayName);
+   }
+   
+   /**
+    * returns the stored method for the given class
+    * 
+    * @param c the class to retrieve the method for
+    * @return the method associated with the class, can be <code>null</code>
+    * @see #m_Methods
+    */
+   public Method get(Class c) {
+      return (Method) m_Methods.get(c);
+   }
+   
+   /**
+    * returns the number of currently stored Methods
+    * 
+    * @return the nummber of methods
+    */
+   public int size() {
+      return m_Methods.size();
+   }
+   
+   /**
+    * removes all mappings 
+    */
+   public void clear() {
+      m_Methods.clear();
+   }
+   
+   /**
+    * returns the internal Hashtable (propety/class - method relationship) in
+    * a string representation
+    * 
+    * @return the object as string 
+    */
+   public String toString() {
+      return m_Methods.toString();
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/PropertyHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/PropertyHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/PropertyHandler.java	(revision 29)
@@ -0,0 +1,364 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PropertyHandler.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+
+/**
+ * This class stores information about properties to ignore or properties
+ * that are allowed for a certain class.
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class PropertyHandler
+   implements RevisionHandler {
+  
+   /** 
+    * contains display names of properties to ignore in the serialization
+    * process
+    * 
+    * @see #ignored()
+    * @see #addIgnored(String)
+    * @see #removeIgnored(String)
+    * @see #isIgnored(String)
+    */
+  protected Hashtable<Object,HashSet<String>> m_Ignored = null;
+   
+   /**
+    * lists for a class the properties allowed to use for setting and getting.
+    * if a class is not listed, then all get/set-methods are allowed.<br>
+    * Mapping: classname (String) - properties (HashSet, containing the Property-Names)
+    * 
+    * @see #allowed()
+    * @see #addAllowed(Class,String)
+    * @see #removeAllowed(Class,String)
+    * @see #isAllowed(Class,String)
+    */
+  protected Hashtable<Object,HashSet<String>> m_Allowed = null;
+
+   /**
+    * initializes the handling 
+    */
+   public PropertyHandler() {
+      super();
+
+      m_Ignored = new Hashtable<Object,HashSet<String>>();
+      m_Allowed = new Hashtable<Object,HashSet<String>>();
+   }
+   
+   /**
+    * returns an enumeration of the stored display names and classes of 
+    * properties to ignore.<br> 
+    * <b>NOTE:</b> String and Class Objects are mixed in this enumeration, depending
+    * whether it is a global property to ignore or just one for a certain class!
+    * 
+    * @return the display names and classes
+    * @see #m_Ignored
+    */
+   public Enumeration ignored() {
+      return m_Ignored.keys();
+   }
+   
+   /**
+    * adds the given display name of a property to the ignore list. Can either
+    * be a complete path (e.g. <code>__root__.options</code>) or only a 
+    * property name (e.g. <code>options</code>). In the latter case it matches 
+    * all occurences of this display name.
+    * 
+    * @param displayName the property to ignore
+    * @see #m_Ignored 
+    */
+   public void addIgnored(String displayName) {
+      HashSet<String>        list;
+      
+      list = new HashSet<String>();
+      list.add(displayName);
+      
+      m_Ignored.put(displayName, list);
+   }
+   
+   /**
+    * adds the given class with the display name of a property to the ignore list.
+    * I.e. this property is only ignored for this class. 
+    * 
+    * @param c the class for which a property is to be ignored
+    * @param displayName the property to ignore
+    * @see #m_Ignored 
+    */
+   public void addIgnored(Class c, String displayName) {
+      HashSet<String>        list;
+      
+      // retrieve list
+      if (m_Ignored.contains(c)) {
+         list = (HashSet<String>) m_Ignored.get(c);
+      }
+      else {
+         list = new HashSet<String>();
+         m_Ignored.put(c, list);
+      }
+      
+      list.add(displayName);
+   }
+   
+   /**
+    * removes the given display name from the ignore list. returns whether the 
+    * removing was succesful, i.e. whether the display name was in the list.
+    * 
+    * @param displayName the property to remove from the ignore list
+    * @return whether the ignore list contained the specified property
+    * @see #m_Ignored
+    */
+   public boolean removeIgnored(String displayName) {
+      return (m_Ignored.remove(displayName) != null);
+   }
+   
+   /**
+    * removes the given display name from the ignore list of the class. 
+    * returns whether the removing was succesful, i.e. whether the display 
+    * name was in the list.
+    * 
+    * @param c the class to remove the property from
+    * @param displayName the property to remove from the ignore list
+    * @return whether the ignore list contained the specified property
+    * @see #m_Ignored
+    */
+   public boolean removeIgnored(Class c, String displayName) {
+      HashSet        list;
+      
+      // retrieve list
+      if (m_Ignored.contains(c))
+         list = (HashSet) m_Ignored.get(c);
+      else
+         list = new HashSet();
+      
+      return list.remove(displayName);
+   }
+   
+   /**
+    * checks whether the given display name is an ignored property
+    * 
+    * @param displayName the property to check whether it is on the ignore list
+    * @return whether the property is in the ignored list
+    * @see #m_Ignored
+    */
+   public boolean isIgnored(String displayName) {
+      return m_Ignored.containsKey(displayName);
+   }
+   
+   /**
+    * checks whether the given display name of a certain class is an ignored 
+    * property. It only checks for this certain class and no derivative classes.
+    * If you also want to check for derivative classes, use 
+    * <code>isIgnored(Object,String)</code>. 
+    * 
+    * @param c the class to check the property for
+    * @param displayName the property to check whether it is on the ignore list
+    * @return whether the property is in the ignored list
+    * @see #m_Ignored
+    * @see #isIgnored(Object, String)
+    */
+   public boolean isIgnored(Class c, String displayName) {
+      HashSet        list;
+      
+      // retrieve list
+      if (m_Ignored.containsKey(c))
+         list = (HashSet) m_Ignored.get(c);
+      else
+         list = new HashSet();
+
+      return list.contains(displayName);
+   }
+   
+   /**
+    * checks whether the given display name of a given object is an ignored 
+    * property. The object is checked for each stored class whether it is an 
+    * <code>instanceof</code>. If the class is not stored then it will default
+    * to <code>false</code>, since there are no restrictions for this class. 
+    * 
+    * @param o the object to check the property for
+    * @param displayName the property to check whether it is on the ignore list
+    * @return whether the property is in the ignored list
+    * @see #m_Ignored
+    */
+   public boolean isIgnored(Object o, String displayName) {
+      Enumeration    enm;
+      Class          c;
+      Object         element;
+      boolean        result;
+      HashSet        list;
+      
+      result = false;
+      
+      enm = ignored();
+      while (enm.hasMoreElements()) {
+         element = enm.nextElement();
+         
+         // has to be class! not a display name
+         if (!(element instanceof Class))
+            continue;
+         
+         c = (Class) element;
+         
+         // is it an instance of this class?
+         if (c.isInstance(o)) {
+            list   = (HashSet) m_Ignored.get(c);
+            result = list.contains(displayName); 
+            break;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * returns an enumeration of the classnames for which only certain properties
+    * (display names) are allowed
+    * 
+    * @return the classnames with restriction to properties
+    */
+   public Enumeration allowed() {
+      return m_Allowed.keys();
+   }
+   
+   /**
+    * adds the given property (display name) to the list of allowed properties 
+    * for the specified class.
+    * 
+    * @param c the class to add a property for
+    * @param displayName the property to allow for the class
+    * @see #m_Allowed
+    */
+   public void addAllowed(Class c, String displayName) {
+      HashSet<String>        list;
+      
+      // retrieve list
+      list = (HashSet<String>) m_Allowed.get(c);
+      if (list == null) {
+         list = new HashSet<String>();
+         m_Allowed.put(c, list);
+      }
+      
+      // add property
+      list.add(displayName);
+   }
+   
+   /**
+    * removes the given property (display name) for the specified class from 
+    * the list of allowed properties.
+    * 
+    * @param c the class to remove the property for
+    * @param displayName the property to remove
+    * @return whether the property was found
+    * @see #m_Allowed
+    */
+   public boolean removeAllowed(Class c, String displayName) {
+      boolean           result;
+      HashSet           list;
+      
+      result = false;
+      
+      // retrieve list
+      list = (HashSet) m_Allowed.get(c);
+      
+      // remove property
+      if (list != null)
+         result = list.remove(displayName);
+      
+      return result;
+   }
+   
+   /**
+    * returns whether the given property (display name) is allowed for the
+    * given class. It only checks for this certain class and no derivative classes.
+    * If you also want to check for derivative classes, use 
+    * <code>isAllowed(Object,String)</code>.
+    * 
+    * @param c the class to check the property for
+    * @param displayName the property (display name) to check
+    * @return whether the property is allowed in that context
+    * @see #m_Allowed
+    * @see #isAllowed(Object, String) 
+    */
+   public boolean isAllowed(Class c, String displayName) {
+      boolean        result;
+      HashSet        list;
+      
+      result = true;
+      
+      // retrieve list
+      list = (HashSet) m_Allowed.get(c);
+      
+      // check list
+      if (list != null)
+         result = list.contains(displayName);
+      
+      return result;
+   }
+   
+   /**
+    * returns whether the given property (display name) is allowed for the given
+    * object . The object is checked for each stored class whether it is an 
+    * <code>instanceof</code>. If the class is not stored then it will default
+    * to <code>true</code>, since there are no restrictions for this class.
+    * 
+    * @param o the object to check the property for
+    * @param displayName the property (display name) to check
+    * @return whether the property is allowed in that context 
+    */
+   public boolean isAllowed(Object o, String displayName) {
+      Enumeration    enm;
+      Class          c;
+      boolean        result;
+      HashSet        list;
+      
+      result = true;
+      
+      enm = allowed();
+      while (enm.hasMoreElements()) {
+         c = (Class) enm.nextElement();
+         
+         // is it an instance of this class?
+         if (c.isInstance(o)) {
+            list   = (HashSet) m_Allowed.get(c);
+            result = list.contains(displayName); 
+            break;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/SerialUIDChanger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/SerialUIDChanger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/SerialUIDChanger.java	(revision 29)
@@ -0,0 +1,253 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SerialUIDChanger.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * This class enables one to change the UID of a serialized object and therefore
+ * not losing the data stored in the binary format.
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class SerialUIDChanger
+   implements RevisionHandler {
+  
+   /**
+    * checks whether KOML is present
+    * 
+    * @return returns <code>true</code> if KOML is present
+    * @throws Exception if KOML is not present 
+    */
+   protected static boolean checkKOML() throws Exception {
+      if (!KOML.isPresent())
+         throw new Exception("KOML is not present!");
+      else 
+         return true; 
+   }
+   
+   /**
+    * checks whether the given filename ends with ".koml"
+    * 
+    * @param filename the filename to check
+    * @return whether it is a KOML file or not
+    * @see KOML#FILE_EXTENSION
+    */
+   public static boolean isKOML(String filename) {
+      return filename.toLowerCase().endsWith(KOML.FILE_EXTENSION);
+   }
+   
+   /**
+    * loads a serialized object and returns it
+    * 
+    * @param binary the filename that points to the file containing the
+    *        serialized object
+    * @return the object from the file
+    * @throws Exception if reading fails
+    */
+   protected static Object readBinary(String binary) throws Exception {
+      FileInputStream         fi;
+      ObjectInputStream       oi;
+      Object                  o;
+      
+      fi = new FileInputStream(binary);
+      oi = new ObjectInputStream(new BufferedInputStream(fi));
+      o  = oi.readObject();
+      oi.close();
+      
+      return o;
+   }
+   
+   /**
+    * serializes the given object into the given file
+    * 
+    * @param binary the file to store the object in
+    * @param o the object to serialize
+    * @throws Exception if saving fails 
+    */
+   protected static void writeBinary(String binary, Object o) throws Exception {
+      FileOutputStream        fo;
+      ObjectOutputStream      oo;
+
+      fo = new FileOutputStream(binary);
+      oo = new ObjectOutputStream(new BufferedOutputStream(fo));
+      oo.writeObject(o);
+      oo.close();
+   }
+   
+   /**
+    * converts a binary file into a KOML XML file
+    * 
+    * @param binary the binary file to convert
+    * @param koml where to store the XML output
+    * @throws Exception if conversion fails
+    */
+   public static void binaryToKOML(String binary, String koml) throws Exception {
+      Object            o;
+      
+      // can we use KOML?
+      checkKOML();
+
+      // read binary
+      o = readBinary(binary);
+      if (o == null)
+         throw new Exception("Failed to deserialize object from binary file '" + binary + "'!");
+      
+      // save as KOML
+      KOML.write(koml, o);
+   }
+   
+   /**
+    * converts a KOML file into a binary one
+    * 
+    * @param koml the filename with the XML data
+    * @param binary the name of the 
+    */
+   public static void komlToBinary(String koml, String binary) throws Exception {
+      Object         o;
+      
+      // can we use KOML? 
+      checkKOML();
+
+      // read KOML
+      o = KOML.read(koml);
+      if (o == null)
+         throw new Exception("Failed to deserialize object from XML file '" + koml + "'!");
+      
+      // write binary
+      writeBinary(binary, o);
+   }
+   
+   /**
+    * changes the oldUID into newUID from the given file (binary/KOML) into the
+    * other one (binary/KOML). it basically does a replace in the XML, i.e. it
+    * looks for " uid='oldUID'" and replaces it with " uid='newUID'".
+    * 
+    * @param oldUID the old UID to change
+    * @param newUID the new UID to use
+    * @param fromFile the original file with the old UID
+    * @param toFile the new file where to store the modified UID
+    * @throws Exception if conversion fails
+    */
+   public static void changeUID(long oldUID, long newUID, String fromFile, String toFile) throws Exception {
+      String            inputFile;
+      String            tempFile;
+      File              file;
+      String            content;
+      String            line;
+      BufferedReader    reader;
+      BufferedWriter    writer;
+      
+      // input
+      if (!isKOML(fromFile)) {
+         inputFile = fromFile + ".koml";
+         binaryToKOML(fromFile, inputFile);
+      }
+      else {
+         inputFile = fromFile;
+      }
+      
+      // load KOML
+      reader = new BufferedReader(new FileReader(inputFile));
+      content = "";
+      while ((line = reader.readLine()) != null) {
+         if (!content.equals(""))
+            content += "\n";
+         content += line;
+      }
+      reader.close();
+      
+      // transform UID
+      content = content.replaceAll(" uid='" + Long.toString(oldUID) + "'", " uid='" + Long.toString(newUID) + "'");
+      
+      // save to tempFile
+      tempFile = inputFile + ".temp";
+      writer = new BufferedWriter(new FileWriter(tempFile));
+      writer.write(content);
+      writer.flush();
+      writer.close();
+      
+      // output
+      if (!isKOML(toFile)) {
+         komlToBinary(tempFile, toFile);
+      }
+      else {
+         writer = new BufferedWriter(new FileWriter(toFile));
+         writer.write(content);
+         writer.flush();
+         writer.close();
+      }
+      
+      // remove tempFile
+      file = new File(tempFile);
+      file.delete();
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+   
+   /**
+    * exchanges an old UID for a new one. a file that doesn't end with ".koml"
+    * is considered being binary.
+    * takes four arguments: oldUID newUID oldFilename newFilename
+    * 
+    * @param args the command line parameters
+    * @see KOML#FILE_EXTENSION
+    */
+   public static void main(String[] args) throws Exception {
+      if (args.length != 4) {
+         System.out.println();
+         System.out.println("Usage: " + SerialUIDChanger.class.getName() + " <oldUID> <newUID> <oldFilename> <newFilename>");
+         System.out.println("       <oldFilename> and <newFilename> have to be different");
+         System.out.println();
+      }
+      else {
+         if (args[2].equals(args[3]))
+            throw new Exception("Filenames have to be different!");
+         
+         changeUID( Long.parseLong(args[0]),
+                    Long.parseLong(args[1]),
+                    args[2],
+                    args[3] );
+      }
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLBasicSerialization.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLBasicSerialization.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLBasicSerialization.java	(revision 29)
@@ -0,0 +1,573 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLBasicSerialization.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Stack;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.DefaultListModel;
+
+import org.w3c.dom.Element;
+
+/**
+ * This serializer contains some read/write methods for common classes that
+ * are not beans-conform. Currently supported are:
+ * <ul>
+ *    <li>java.util.HashMap</li>
+ *    <li>java.util.HashSet</li>
+ *    <li>java.util.Hashtable</li>
+ *    <li>java.util.LinkedList</li>
+ *    <li>java.util.Properties</li>
+ *    <li>java.util.Stack</li>
+ *    <li>java.util.TreeMap</li>
+ *    <li>java.util.TreeSet</li>
+ *    <li>java.util.Vector</li>
+ *    <li>javax.swing.DefaultListModel</li>
+ * </ul>
+ *
+ * Weka classes:
+ * <ul>
+ *    <li>weka.core.Matrix</li>
+ *    <li>weka.core.matrix.Matrix</li>
+ * </ul>
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class XMLBasicSerialization
+   extends XMLSerialization {
+
+   /** the value for mapping, e.g., Maps */
+   public final static String VAL_MAPPING = "mapping";
+
+   /** the value for a mapping-key, e.g., Maps */
+   public final static String VAL_KEY = "key";
+
+   /** the value for mapping-value, e.g., Maps */
+   public final static String VAL_VALUE = "value";
+
+   /** the matrix cells */
+   public final static String VAL_CELLS = "cells";
+
+   /**
+    * initializes the serialization
+    * 
+    * @throws Exception if initialization fails
+    */
+   public XMLBasicSerialization() throws Exception {
+      super();
+   }
+   
+   /**
+    * generates internally a new XML document and clears also the IgnoreList
+    * and the mappings for the Read/Write-Methods
+    * 
+    * @throws Exception if initializing fails
+    */
+   public void clear() throws Exception {
+      super.clear();
+      
+      // Java classes
+      m_CustomMethods.register(this, DefaultListModel.class, "DefaultListModel");
+      m_CustomMethods.register(this, HashMap.class, "Map");
+      m_CustomMethods.register(this, HashSet.class, "Collection");
+      m_CustomMethods.register(this, Hashtable.class, "Map");
+      m_CustomMethods.register(this, LinkedList.class, "Collection");
+      m_CustomMethods.register(this, Properties.class, "Map");
+      m_CustomMethods.register(this, Stack.class, "Collection");
+      m_CustomMethods.register(this, TreeMap.class, "Map");
+      m_CustomMethods.register(this, TreeSet.class, "Collection");
+      m_CustomMethods.register(this, Vector.class, "Collection");
+
+      // Weka classes
+      m_CustomMethods.register(this, weka.core.matrix.Matrix.class, "Matrix");
+      m_CustomMethods.register(this, weka.core.Matrix.class, "MatrixOld");
+      m_CustomMethods.register(this, weka.classifiers.CostMatrix.class, "CostMatrixOld");
+   }
+   
+   /**
+    * adds the given DefaultListModel to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see javax.swing.DefaultListModel
+    */
+   public Element writeDefaultListModel(Element parent, Object o, String name) 
+      throws Exception {
+
+      Element              node;
+      int                  i;
+      DefaultListModel     model;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      model = (DefaultListModel) o;
+      node = addElement(parent, name, o.getClass().getName(), false);
+
+      for (i = 0; i < model.getSize(); i++)
+         invokeWriteToXML(node, model.get(i), Integer.toString(i));
+      
+      return node;
+   }
+
+   /**
+    * builds the DefaultListModel from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see javax.swing.DefaultListModel
+    */
+   public Object readDefaultListModel(Element node) throws Exception {
+      DefaultListModel     model;
+      Vector               children;
+      Element              child;
+      int                  i;
+      int                  index;
+      int                  currIndex;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      children = XMLDocument.getChildTags(node); 
+      model    = new DefaultListModel();
+      
+      // determine highest index for size
+      index    = children.size() - 1;
+      for (i = 0; i < children.size(); i++) {
+        child     = (Element) children.get(i);
+        currIndex = Integer.parseInt(child.getAttribute(ATT_NAME));
+        if (currIndex > index)
+          index = currIndex;
+      }
+      model.setSize(index + 1);
+
+      // set values
+      for (i = 0; i < children.size(); i++) {
+         child = (Element) children.get(i);
+         model.set(
+             Integer.parseInt(child.getAttribute(ATT_NAME)), 
+             invokeReadFromXML(child));
+      }
+      
+      return model;
+   }
+   
+   /**
+    * adds the given Collection to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see java.util.Collection
+    */
+   public Element writeCollection(Element parent, Object o, String name) 
+      throws Exception {
+
+      Element         node;
+      Iterator        iter;
+      int             i;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      iter = ((Collection) o).iterator();
+      node = addElement(parent, name, o.getClass().getName(), false);
+
+      i = 0;
+      while (iter.hasNext()) {
+         invokeWriteToXML(node, iter.next(), Integer.toString(i));
+         i++;
+      }
+      
+      return node;
+   }
+
+   /**
+    * builds the Collection from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see java.util.Collection
+    */
+   public Object readCollection(Element node) throws Exception {
+      Collection<Object>           coll;
+      Vector<Object>               v;
+      Vector<Element>               children;
+      Element              child;
+      int                  i;
+      int                  index;
+      int                  currIndex;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      children = XMLDocument.getChildTags(node); 
+      v        = new Vector<Object>();
+
+      // determine highest index for size
+      index    = children.size() - 1;
+      for (i = 0; i < children.size(); i++) {
+        child     = (Element) children.get(i);
+        currIndex = Integer.parseInt(child.getAttribute(ATT_NAME));
+        if (currIndex > index)
+          index = currIndex;
+      }
+      v.setSize(index + 1);
+
+
+      // put the children in the vector to sort them according their index
+      for (i = 0; i < children.size(); i++) {
+         child = (Element) children.get(i);
+         v.set(
+               Integer.parseInt(child.getAttribute(ATT_NAME)), 
+               invokeReadFromXML(child));
+      }
+      
+      // populate collection
+      coll = Utils.cast(Class.forName(node.getAttribute(ATT_CLASS)).
+                        newInstance());
+      coll.addAll(v);
+      
+      return coll;
+   }
+   
+   /**
+    * adds the given Map to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see java.util.Map
+    */
+   public Element writeMap(Element parent, Object o, String name) 
+      throws Exception {
+
+      Map            map;
+      Object         key;
+      Element        node;
+      Element        child;
+      Iterator       iter;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      map  = (Map) o;
+      iter = map.keySet().iterator();
+      node = addElement(parent, name, o.getClass().getName(), false);
+
+      while (iter.hasNext()) {
+         key   = iter.next();
+         child = addElement(
+                     node, VAL_MAPPING, Object.class.getName(), false);
+         invokeWriteToXML(child, key,          VAL_KEY);
+         invokeWriteToXML(child, map.get(key), VAL_VALUE);
+      }
+      
+      return node;
+   }
+
+   /**
+    * builds the Map from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see java.util.Map
+    */
+   public Object readMap(Element node) throws Exception {
+     Map<Object,Object>                  map;
+      Object               key;
+      Object               value;
+      Vector               children;
+      Vector               cchildren;
+      Element              child;
+      Element              cchild;
+      int                  i;
+      int                  n;
+      String               name;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      map      = Utils.cast(Class.forName(node.getAttribute(ATT_CLASS)).
+                            newInstance());
+      children = XMLDocument.getChildTags(node); 
+
+      for (i = 0; i < children.size(); i++) {
+         child     = (Element) children.get(i);
+         cchildren = XMLDocument.getChildTags(child);
+         key       = null;
+         value     = null;
+         
+         for (n = 0; n < cchildren.size(); n++) {
+            cchild = (Element) cchildren.get(n);
+            name   = cchild.getAttribute(ATT_NAME);
+            if (name.equals(VAL_KEY))
+               key = invokeReadFromXML(cchild);
+            else if (name.equals(VAL_VALUE))
+               value = invokeReadFromXML(cchild);
+            else
+               System.out.println("WARNING: '" 
+                     + name + "' is not a recognized name for maps!");
+         }
+         
+         map.put(key, value);
+      }
+      
+      return map;
+   }
+   
+   /**
+    * adds the given Matrix to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see weka.core.matrix.Matrix
+    */
+   public Element writeMatrix(Element parent, Object o, String name) 
+      throws Exception {
+
+      weka.core.matrix.Matrix    matrix;
+      Element                    node;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      matrix = (weka.core.matrix.Matrix) o;
+      node   = addElement(parent, name, o.getClass().getName(), false);
+
+      invokeWriteToXML(node, matrix.getArray(), VAL_CELLS);
+      
+      return node;
+   }
+
+   /**
+    * builds the Matrix from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see weka.core.matrix.Matrix
+    */
+   public Object readMatrix(Element node) throws Exception {
+      weka.core.matrix.Matrix    matrix;
+      Vector                     children;
+      Element                    child;
+      int                        i;
+      String                     name;
+      Object                     o;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      matrix   = null;
+      children = XMLDocument.getChildTags(node); 
+      for (i = 0; i < children.size(); i++) {
+         child = (Element) children.get(i);
+         name  = child.getAttribute(ATT_NAME);
+
+         if (name.equals(VAL_CELLS)) {
+            o = invokeReadFromXML(child);
+            matrix = new weka.core.matrix.Matrix(
+                        (double[][]) o);
+         }
+      }
+      
+      return matrix;
+   }
+   
+   /**
+    * adds the given Matrix (old) to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see weka.core.Matrix
+    */
+   public Element writeMatrixOld(Element parent, Object o, String name) 
+      throws Exception {
+
+      weka.core.Matrix  matrix;
+      Element           node;
+      double[][]        array;
+      int               i;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      matrix = (weka.core.Matrix) o;
+      node   = addElement(parent, name, o.getClass().getName(), false);
+
+      array = new double[matrix.numRows()][];
+      for (i = 0; i < array.length; i++)
+         array[i] = matrix.getRow(i);
+      invokeWriteToXML(node, array, VAL_CELLS);
+      
+      return node;
+   }
+
+   /**
+    * builds the Matrix (old) from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see weka.core.Matrix
+    */
+   public Object readMatrixOld(Element node) throws Exception {
+      weka.core.Matrix           matrix;
+      weka.core.matrix.Matrix    matrixNew;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      matrixNew = (weka.core.matrix.Matrix) readMatrix(node);
+      matrix    = new weka.core.Matrix(matrixNew.getArrayCopy());
+      
+      return matrix;
+   }
+   
+   /**
+    * adds the given CostMatrix (old) to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a
+    * member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see weka.classifiers.CostMatrix
+    */
+   public Element writeCostMatrixOld(Element parent, Object o, String name) 
+      throws Exception {
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      return writeMatrixOld(parent, o, name);
+   }
+
+   /**
+    * builds the Matrix (old) from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see weka.classifiers.CostMatrix
+    */
+   public Object readCostMatrixOld(Element node) throws Exception {
+      weka.classifiers.CostMatrix   matrix;
+      weka.core.matrix.Matrix       matrixNew;
+      StringWriter                  writer;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      matrixNew = (weka.core.matrix.Matrix) readMatrix(node);
+      writer    = new StringWriter();
+      matrixNew.write(writer);
+      matrix    = new weka.classifiers.CostMatrix(new StringReader(writer.toString()));
+      
+      return matrix;
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLDocument.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLDocument.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLDocument.java	(revision 29)
@@ -0,0 +1,685 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLDocument.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Vector;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * This class offers some methods for generating, reading and writing 
+ * XML documents.<br>
+ * It can only handle UTF-8.
+ * 
+ * @see #PI 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class XMLDocument
+  implements RevisionHandler {
+  
+  /** the parsing instructions "&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;" 
+   * (may not show up in Javadoc due to tags!). */
+  public final static String PI = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
+  
+  // DTD placeholders
+  /** the DocType definition. */
+  public final static String DTD_DOCTYPE = "DOCTYPE";
+  
+  /** the Element definition. */
+  public final static String DTD_ELEMENT = "ELEMENT";
+  
+  /** the AttList definition. */
+  public final static String DTD_ATTLIST = "ATTLIST";
+  
+  /** the optional marker. */
+  public final static String DTD_OPTIONAL = "?";
+  
+  /** the at least one marker. */
+  public final static String DTD_AT_LEAST_ONE = "+";
+  
+  /** the zero or more marker. */
+  public final static String DTD_ZERO_OR_MORE = "*";
+  
+  /** the option separator. */
+  public final static String DTD_SEPARATOR = "|";
+  
+  /** the CDATA placeholder. */
+  public final static String DTD_CDATA = "CDATA"; 
+  
+  /** the ANY placeholder. */
+  public final static String DTD_ANY = "ANY"; 
+  
+  /** the #PCDATA placeholder. */
+  public final static String DTD_PCDATA = "#PCDATA"; 
+  
+  /** the #IMPLIED placeholder. */
+  public final static String DTD_IMPLIED = "#IMPLIED"; 
+  
+  /** the #REQUIRED placeholder. */
+  public final static String DTD_REQUIRED = "#REQUIRED"; 
+
+  // often used attributes
+  /** the "version" attribute. */
+  public final static String ATT_VERSION = "version";
+ 
+  /** the "name" attribute. */
+  public final static String ATT_NAME = "name";
+
+  // often used values
+  /** the value "yes". */
+  public final static String VAL_YES = "yes";
+  
+  /** the value "no". */
+  public final static String VAL_NO = "no";
+  
+  // members
+  /** the factory for DocumentBuilder. */
+  protected DocumentBuilderFactory m_Factory = null;
+  
+  /** the instance of a DocumentBuilder. */
+  protected DocumentBuilder m_Builder = null;
+  
+  /** whether to use a validating parser or not. */
+  protected boolean m_Validating = false;
+  
+  /** the DOM document. */
+  protected Document m_Document = null;
+  
+  /** the DOCTYPE node as String. */
+  protected String m_DocType = null;
+  
+  /** the root node as String. */
+  protected String m_RootNode = null;
+  
+  /** for XPath queries. */
+  protected XPath m_XPath = null;
+  
+  /**
+   * initializes the factory with non-validating parser.
+   * 
+   * @throws Exception 	if the construction fails
+   */
+  public XMLDocument() throws Exception {
+    m_Factory = DocumentBuilderFactory.newInstance();
+    m_XPath   = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI).newXPath();
+    setDocType(null);
+    setRootNode(null);
+    setValidating(false);
+  }
+  
+  /** 
+   * Creates a new instance of XMLDocument.
+   * 
+   * @param xml 	the xml to parse (if "<?xml" is not found then it is considered a file)
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLDocument(String xml) throws Exception {
+    this();
+    read(xml);
+  }
+  
+  /** 
+   * Creates a new instance of XMLDocument.
+   * 
+   * @param file 	the XML file to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLDocument(File file) throws Exception {
+    this();
+    read(file);
+  }
+  
+  /** 
+   * Creates a new instance of XMLDocument.
+   * 
+   * @param stream 	the XML stream to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLDocument(InputStream stream) throws Exception {
+    this();
+    read(stream);
+  }
+  
+  /** 
+   * Creates a new instance of XMLDocument.
+   * 
+   * @param reader 	the XML reader to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLDocument(Reader reader) throws Exception {
+    this();
+    read(reader);
+  }
+  
+  /**
+   * returns the DocumentBuilderFactory.
+   * 
+   * @return 		the DocumentBuilderFactory
+   */
+  public DocumentBuilderFactory getFactory() {
+    return m_Factory;
+  }
+  
+  /**
+   * returns the DocumentBuilder.
+   * 
+   * @return 		the DocumentBuilder
+   */
+  public DocumentBuilder getBuilder() {
+    return m_Builder;
+  }
+  
+  /**
+   * returns whether a validating parser is used.
+   * 
+   * @return 		whether a validating parser is used
+   */
+  public boolean getValidating() {
+    return m_Validating;
+  }
+  
+  /**
+   * sets whether to use a validating parser or not.<br>
+   * Note: this does clear the current DOM document! 
+   * 
+   * @param validating 	whether to use a validating parser
+   * @throws Exception 	if the instantiating of the DocumentBuilder fails
+   */
+  public void setValidating(boolean validating) throws Exception {
+    m_Validating = validating;
+    m_Factory.setValidating(validating);
+    m_Builder    = m_Factory.newDocumentBuilder();
+    clear();
+  }
+  
+  /**
+   * returns the parsed DOM document.
+   * 
+   * @return 		the parsed DOM document
+   */
+  public Document getDocument() {
+    return m_Document;
+  }
+  
+  /**
+   * sets the DOM document to use.
+   * 
+   * @param newDocument the DOM document to use 
+   */
+  public void setDocument(Document newDocument) {
+    m_Document = newDocument;
+  }
+  
+  /**
+   * sets the DOCTYPE-String to use in the XML output. Performs NO checking!
+   * if it is <code>null</code> the DOCTYPE is omitted. 
+   *  
+   * @param docType 	the DOCTYPE definition to use in XML output 
+   */
+  public void setDocType(String docType) {
+    m_DocType = docType; 
+  }
+  
+  /**
+   * returns the current DOCTYPE, can be <code>null</code>.
+   * 
+   * @return 		the current DOCTYPE definition, can be <code>null</code>
+   */
+  public String getDocType()  {
+    return m_DocType;
+  }
+  
+  /**
+   * sets the root node to use in the XML output. Performs NO checking with 
+   * DOCTYPE!
+   *  
+   * @param rootNode 	the root node to use in the XML output
+   */
+  public void setRootNode(String rootNode) {
+    if (rootNode == null)
+      m_RootNode = "root";
+    else
+      m_RootNode = rootNode; 
+  }
+  
+  /**
+   * returns the current root node.
+   * 
+   * @return 		the current root node
+   */
+  public String getRootNode()  {
+    return m_RootNode;
+  }
+  
+  /**
+   * sets up an empty DOM document, with the current DOCTYPE and root node.
+   * 
+   * @see 		#setRootNode(String)
+   * @see 		#setDocType(String)
+   */
+  public void clear() {
+    newDocument(getDocType(), getRootNode());
+  }
+  
+  /**
+   * creates a new Document with the given information.
+   * 
+   * @param docType 	the DOCTYPE definition (no checking happens!), can be null
+   * @param rootNode 	the name of the root node (must correspond to the one 
+   *        		given in <code>docType</code>) 
+   * @return 		returns the just created DOM document for convenience
+   */
+  public Document newDocument(String docType, String rootNode) {
+    m_Document = getBuilder().newDocument();
+    m_Document.appendChild(m_Document.createElement(rootNode));
+    setDocType(docType);
+    
+    return getDocument();
+  }
+  
+  /**
+   * parses the given XML string (can be XML or a filename) and returns a
+   * DOM Document.
+   * 
+   * @param xml 	the xml to parse (if "<?xml" is not found then it is considered a file)
+   * @return 		the parsed DOM document
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public Document read(String xml) throws Exception {
+    if (xml.toLowerCase().indexOf("<?xml") > -1)
+      return read(new ByteArrayInputStream(xml.getBytes()));
+    else
+      return read(new File(xml));
+  }
+  
+  /**
+   * parses the given file and returns a DOM document.
+   * 
+   * @param file 	the XML file to parse
+   * @return 		the parsed DOM document
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public Document read(File file) throws Exception {
+    m_Document = getBuilder().parse(file);
+    return getDocument();
+  }
+  
+  /**
+   * parses the given stream and returns a DOM document.
+   * 
+   * @param stream 	the XML stream to parse
+   * @return 		the parsed DOM document
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public Document read(InputStream stream) throws Exception {
+    m_Document = getBuilder().parse(stream);
+    return getDocument();
+  }
+  
+  /**
+   * parses the given reader and returns a DOM document.
+   * 
+   * @param reader 	the XML reader to parse
+   * @return 		the parsed DOM document
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public Document read(Reader reader) throws Exception {
+    m_Document = getBuilder().parse(new InputSource(reader));
+    return getDocument();
+  }
+  
+  
+  /**
+   * writes the current DOM document into the given file.
+   * 
+   * @param file 	the filename to write to
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public void write(String file) throws Exception {
+    write(new File(file));
+  }
+  
+  /**
+   * writes the current DOM document into the given file.
+   * 
+   * @param file 	the filename to write to
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public void write(File file) throws Exception {
+    write(new BufferedWriter(new FileWriter(file)));
+  }
+  
+  /**
+   * writes the current DOM document into the given stream.
+   * 
+   * @param stream 	the filename to write to
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public void write(OutputStream stream) throws Exception {
+    String		xml;
+    
+    xml = toString();
+    stream.write(xml.getBytes(), 0, xml.length());
+    stream.flush();
+  }
+  
+  /**
+   * writes the current DOM document into the given writer.
+   * 
+   * @param writer 	the filename to write to
+   * @throws Exception 	if something goes wrong with the parsing
+   */
+  public void write(Writer writer) throws Exception {
+    writer.write(toString());
+    writer.flush();
+  }
+  
+  /**
+   * returns all non tag-children from the given node.
+   * 
+   * @param parent 	the node to get the children from
+   * @return 		a vector containing all the non-text children
+   */
+  public static Vector<Element> getChildTags(Node parent) {
+    return getChildTags(parent, "");
+  }
+  
+  /**
+   * returns all non tag-children from the given node.
+   * 
+   * @param parent 	the node to get the children from
+   * @param name 	the name of the tags to return, "" for all
+   * @return 		a vector containing all the non-text children
+   */
+  public static Vector<Element> getChildTags(Node parent, String name) {
+    Vector<Element>         result;
+    int            i;
+    NodeList       list;
+    
+    result = new Vector<Element>();
+    
+    list = parent.getChildNodes();
+    for (i = 0; i < list.getLength(); i++) {
+      if (!(list.item(i) instanceof Element))
+	continue;
+      // only tags with a certain name?
+      if (name.length() != 0) {
+	if (!((Element) list.item(i)).getTagName().equals(name))
+	  continue;
+      }
+      result.add((Element)list.item(i));
+    }
+    
+    return result;
+  }
+
+  /**
+   * Returns the specified result of the XPath expression. 
+   * Can return null if an error occurred.
+   * 
+   * @param xpath	the XPath expression to run on the document
+   * @param type	the type of the result
+   * @return		the result
+   */
+  protected Object eval(String xpath, QName type) {
+    Object	result;
+    
+    try {
+      result = m_XPath.evaluate(xpath, m_Document, type);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Returns the nodes that the given xpath expression will find in the 
+   * document. Can return null if an error occurred.
+   * 
+   * @param xpath	the XPath expression to run on the document
+   * @return		the nodelist
+   */
+  public NodeList findNodes(String xpath) {
+    return (NodeList) eval(xpath, XPathConstants.NODESET);
+  }
+
+  /**
+   * Returns the node represented by the XPath expression. 
+   * Can return null if an error occurred.
+   * 
+   * @param xpath	the XPath expression to run on the document
+   * @return		the node
+   */
+  public Node getNode(String xpath) {
+    return (Node) eval(xpath, XPathConstants.NODE);
+  }
+  
+  /**
+   * Evaluates and returns the boolean result of the XPath expression.
+   * 
+   * @param xpath	the expression to evaluate
+   * @return		the result of the evaluation, null in case of an error
+   */
+  public Boolean evalBoolean(String xpath) {
+    return (Boolean) eval(xpath, XPathConstants.BOOLEAN);
+  }
+  
+  /**
+   * Evaluates and returns the double result of the XPath expression.
+   * 
+   * @param xpath	the expression to evaluate
+   * @return		the result of the evaluation, null in case of
+   * 			an error
+   */
+  public Double evalDouble(String xpath) {
+    return (Double) eval(xpath, XPathConstants.NUMBER);
+  }
+  
+  /**
+   * Evaluates and returns the boolean result of the XPath expression.
+   * 
+   * @param xpath	the expression to evaluate
+   * @return		the result of the evaluation
+   */
+  public String evalString(String xpath) {
+    return (String) eval(xpath, XPathConstants.STRING);
+  }
+  
+  /**
+   * returns the text between the opening and closing tag of a node
+   * (performs a <code>trim()</code> on the result).
+   * 
+   * @param node 	the node to get the text from
+   * @return 		the content of the given node
+   */
+  public static String getContent(Element node) {
+    NodeList       list;
+    Node           item;
+    int            i;
+    String         result;
+    
+    result = "";
+    list   = node.getChildNodes();
+    
+    for (i = 0; i < list.getLength(); i++) {
+      item = list.item(i);
+      if (item.getNodeType() == Node.TEXT_NODE)
+	result += item.getNodeValue();
+    }
+    
+    return result.trim();
+  }
+  
+  /**
+   * turns the given node into a XML-stringbuffer according to the depth.
+   * 
+   * @param buf 	the stringbuffer so far
+   * @param parent 	the current node
+   * @param depth 	the current depth
+   * @return 		the new XML-stringbuffer
+   */
+  protected StringBuffer toString(StringBuffer buf, Node parent, int depth) {
+    NodeList       list;
+    Node           node;
+    int            i;
+    int            n;
+    String         indent;
+    NamedNodeMap   atts;
+    
+    // build indent
+    indent = "";
+    for (i = 0; i < depth; i++)
+      indent += "   ";
+    
+    if (parent.getNodeType() == Node.TEXT_NODE) {
+      if (!parent.getNodeValue().trim().equals(""))
+	buf.append(indent + parent.getNodeValue().trim() + "\n");
+    }
+    else 
+      if (parent.getNodeType() == Node.COMMENT_NODE) {
+	buf.append(indent + "<!--" + parent.getNodeValue() + "-->\n");
+      }
+      else {
+	buf.append(indent + "<" + parent.getNodeName());
+	// attributes?
+	if (parent.hasAttributes()) {
+	  atts = parent.getAttributes();
+	  for (n = 0; n < atts.getLength(); n++) {
+	    node = atts.item(n);
+	    buf.append(" " + node.getNodeName() + "=\"" + node.getNodeValue() + "\"");
+	  }
+	}
+	// children?
+	if (parent.hasChildNodes()) {
+	  list = parent.getChildNodes();
+	  // just a text node?
+	  if ( (list.getLength() == 1) && (list.item(0).getNodeType() == Node.TEXT_NODE) ) {
+	    buf.append(">");
+	    buf.append(list.item(0).getNodeValue().trim());
+	    buf.append("</" + parent.getNodeName() + ">\n");
+	  }
+	  else {
+	    buf.append(">\n");
+	    for (n = 0; n < list.getLength(); n++) {
+	      node = list.item(n);
+	      toString(buf, node, depth + 1);
+	    }
+	    buf.append(indent + "</" + parent.getNodeName() + ">\n");
+	  }
+	}
+	else {
+	  buf.append("/>\n");
+	}
+      }
+    
+    return buf;
+  }
+  
+  /**
+   * prints the current DOM document to standard out.
+   */
+  public void print() {
+    System.out.println(toString());
+  }
+  
+  /**
+   * returns the current DOM document as XML-string.
+   * 
+   * @return 		the document as XML-string representation
+   */
+  public String toString() {
+    String         header;
+    
+    header = PI + "\n\n";
+    if (getDocType() != null)
+      header += getDocType() + "\n\n";
+    
+    return toString(new StringBuffer(header), getDocument().getDocumentElement(), 0).toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+  
+  /**
+   * for testing only. takes the name of an XML file as first arg, reads that
+   * file, prints it to stdout and if a second filename is given, writes the
+   * parsed document to that again.
+   * 
+   * @param args	the commandline arguments
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    XMLDocument		doc;
+    
+    if (args.length > 0) {
+      doc = new XMLDocument();
+      
+      // read
+      doc.read(args[0]);
+      
+      // print to stdout
+      doc.print();
+      
+      // output?
+      if (args.length > 1) {
+	doc.write(args[1]);
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLInstances.java	(revision 29)
@@ -0,0 +1,893 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLInstances.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.ProtectedProperties;
+import weka.core.RevisionUtils;
+import weka.core.DenseInstance;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Version;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+import java.util.ArrayList;
+
+import org.w3c.dom.Element;
+
+/**
+ * XML representation of the Instances class.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class XMLInstances
+  extends XMLDocument
+  implements Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3626821327547416099L;
+  
+  /** The filename extension that should be used for xrff files */
+  public static String FILE_EXTENSION = ".xrff";
+  
+  // tags
+  /** the root element */
+  public final static String TAG_DATASET = "dataset";
+
+  /** the header element */
+  public final static String TAG_HEADER = "header";
+
+  /** the body element */
+  public final static String TAG_BODY = "body";
+
+  /** the notes element */
+  public final static String TAG_NOTES = "notes";
+
+  /** the attributes element */
+  public final static String TAG_ATTRIBUTES = "attributes";
+
+  /** the attribute element */
+  public final static String TAG_ATTRIBUTE = "attribute";
+
+  /** the labels element */
+  public final static String TAG_LABELS = "labels";
+
+  /** the label element */
+  public final static String TAG_LABEL = "label";
+
+  /** the meta-data element */
+  public final static String TAG_METADATA = "metadata";
+
+  /** the property element */
+  public final static String TAG_PROPERTY = "property";
+
+  /** the data element */
+  public final static String TAG_INSTANCES = "instances";
+
+  /** the instance element */
+  public final static String TAG_INSTANCE = "instance";
+
+  /** the value element */
+  public final static String TAG_VALUE = "value";
+  
+  // attributes
+  /** the version attribute */
+  public final static String ATT_VERSION = "version";
+  
+  /** the type attribute */
+  public final static String ATT_TYPE = "type";
+  
+  /** the format attribute (for date attributes) */
+  public final static String ATT_FORMAT = "format";
+  
+  /** the class attribute */
+  public final static String ATT_CLASS = "class";
+  
+  /** the index attribute */
+  public final static String ATT_INDEX = "index";
+  
+  /** the weight attribute */
+  public final static String ATT_WEIGHT = "weight";
+  
+  /** the missing attribute */
+  public final static String ATT_MISSING = "missing";
+  
+  // values
+  /** the value for numeric */
+  public final static String VAL_NUMERIC = "numeric";
+  
+  /** the value for date */
+  public final static String VAL_DATE = "date";
+  
+  /** the value for nominal */
+  public final static String VAL_NOMINAL = "nominal";
+  
+  /** the value for string */
+  public final static String VAL_STRING = "string";
+  
+  /** the value for relational */
+  public final static String VAL_RELATIONAL = "relational";
+  
+  /** the value for normal */
+  public final static String VAL_NORMAL = "normal";
+  
+  /** the value for sparse */
+  public final static String VAL_SPARSE = "sparse";
+  
+  /** the DTD */
+  public final static String DOCTYPE = 
+      "<!" + DTD_DOCTYPE + " " + TAG_DATASET + "\n"
+    + "[\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_DATASET + " (" + TAG_HEADER + "," + TAG_BODY + ")" + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_DATASET + " " + ATT_NAME + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_DATASET + " " + ATT_VERSION + " " + DTD_CDATA + " \"" + Version.VERSION + "\">\n"
+    + "\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_HEADER + " (" + TAG_NOTES + DTD_OPTIONAL + "," + TAG_ATTRIBUTES + ")" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_BODY   + " (" + TAG_INSTANCES  + ")" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_NOTES + " " + DTD_ANY + ">   <!--  comments, information, copyright, etc. -->\n"
+    + "\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_ATTRIBUTES + " (" + TAG_ATTRIBUTE + DTD_AT_LEAST_ONE + ")" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_ATTRIBUTE + " (" + TAG_LABELS + DTD_OPTIONAL + "," + TAG_METADATA + DTD_OPTIONAL + "," + TAG_ATTRIBUTES + DTD_OPTIONAL + ")" + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_NAME + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_TYPE + " (" + VAL_NUMERIC + DTD_SEPARATOR + VAL_DATE + DTD_SEPARATOR + VAL_NOMINAL + DTD_SEPARATOR + VAL_STRING + DTD_SEPARATOR + VAL_RELATIONAL + ") " + DTD_REQUIRED + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_FORMAT + " " + DTD_CDATA + " " + DTD_IMPLIED + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_ATTRIBUTE + " " + ATT_CLASS + " (" + VAL_YES + DTD_SEPARATOR + VAL_NO + ") \"" + VAL_NO + "\"" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_LABELS + " (" + TAG_LABEL + DTD_ZERO_OR_MORE + ")" + ">   <!-- only for type \"nominal\" -->\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_LABEL + " " + DTD_ANY + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_METADATA + " (" + TAG_PROPERTY + DTD_ZERO_OR_MORE + ")" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_PROPERTY + " " + DTD_ANY + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_PROPERTY + " " + ATT_NAME + " " + DTD_CDATA + " " + DTD_REQUIRED + ">\n"
+    + "\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_INSTANCES + " (" + TAG_INSTANCE + DTD_ZERO_OR_MORE + ")" + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_INSTANCE + " (" + TAG_VALUE + DTD_ZERO_OR_MORE + ")" + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_INSTANCE + " " + ATT_TYPE + " (" + VAL_NORMAL + DTD_SEPARATOR + VAL_SPARSE + ") \"" + VAL_NORMAL + "\"" + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_INSTANCE + " " + ATT_WEIGHT + " " + DTD_CDATA + " " + DTD_IMPLIED + ">\n"
+    + "   <!" + DTD_ELEMENT + " " + TAG_VALUE + " (" + DTD_PCDATA + DTD_SEPARATOR + TAG_INSTANCES + ")" + DTD_ZERO_OR_MORE + ">\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_VALUE + " " + ATT_INDEX + " " + DTD_CDATA + " " + DTD_IMPLIED + ">   <!-- 1-based index (only used for instance format \"sparse\") -->\n"
+    + "   <!" + DTD_ATTLIST + " " + TAG_VALUE + " " + ATT_MISSING + " (" + VAL_YES + DTD_SEPARATOR + VAL_NO + ") \"" + VAL_NO + "\"" + ">\n"
+    + "]\n"
+    + ">";
+
+  /** the precision for numbers */
+  protected int m_Precision = 6;
+  
+  /** the underlying Instances */
+  protected Instances m_Instances;
+  
+  /**
+   * the default constructor
+   * 
+   * @throws Exception	if XML initialization fails
+   */
+  public XMLInstances() throws Exception {
+    super();
+    
+    m_Instances = null;
+
+    setDocType(DOCTYPE);
+    setRootNode(TAG_DATASET);
+    setValidating(true);
+  }
+  
+  /**
+   * generates the XML structure based on the given data
+   * 
+   * @param data	the data to build the XML structure from
+   * @throws Exception	if initialization/generation fails
+   */
+  public XMLInstances(Instances data) throws Exception {
+    this();
+    
+    setInstances(data);
+  }
+  
+  /**
+   * generates the Instances directly from the reader containing the
+   * XML data.
+   * 
+   * @param reader	the reader for the XML data
+   * @throws Exception	if something goes wrong
+   */
+  public XMLInstances(Reader reader) throws Exception {
+    this();
+    
+    setXML(reader);
+  }
+  
+  /**
+   * adds the attribute to the XML structure
+   * 
+   * @param parent	the parent node to add the attribute node as child
+   * @param att		the attribute to add
+   */
+  protected void addAttribute(Element parent, Attribute att) {
+    Element		node;
+    Element		child;
+    Element		property;
+    Element		label;
+    String		tmpStr;
+    Enumeration		enm;
+    int			i;
+    
+    node = m_Document.createElement(TAG_ATTRIBUTE);
+    parent.appendChild(node);
+    
+    // XML attributes
+    // name
+    node.setAttribute(ATT_NAME, validContent(att.name()));
+    
+    // type
+    switch (att.type()) {
+      case Attribute.NUMERIC:
+	node.setAttribute(ATT_TYPE, VAL_NUMERIC);
+	break;
+	
+      case Attribute.DATE:
+	node.setAttribute(ATT_TYPE, VAL_DATE);
+	break;
+	
+      case Attribute.NOMINAL:
+	node.setAttribute(ATT_TYPE, VAL_NOMINAL);
+	break;
+	
+      case Attribute.STRING:
+	node.setAttribute(ATT_TYPE, VAL_STRING);
+	break;
+	
+      case Attribute.RELATIONAL:
+	node.setAttribute(ATT_TYPE, VAL_RELATIONAL);
+	break;
+	
+      default:
+	node.setAttribute(ATT_TYPE, "???");
+    }
+    
+    // labels
+    if (att.isNominal()) {
+      child = m_Document.createElement(TAG_LABELS);
+      node.appendChild(child);
+      enm = att.enumerateValues();
+      while (enm.hasMoreElements()) {
+	tmpStr = enm.nextElement().toString();
+	label = m_Document.createElement(TAG_LABEL);
+	child.appendChild(label);
+	label.appendChild(m_Document.createTextNode(validContent(tmpStr)));
+      }
+    }
+    
+    // format
+    if (att.isDate())
+      node.setAttribute(ATT_FORMAT, validContent(att.getDateFormat()));
+    
+    // class
+    if (m_Instances.classIndex() > -1) {
+      if (att == m_Instances.classAttribute())
+	node.setAttribute(ATT_CLASS, VAL_YES);
+    }
+    
+    // add meta-data
+    if ( (att.getMetadata() != null) && (att.getMetadata().size() > 0) ) {
+      child = m_Document.createElement(TAG_METADATA);
+      node.appendChild(child);
+      enm = att.getMetadata().propertyNames();
+      while (enm.hasMoreElements()) {
+	tmpStr = enm.nextElement().toString();
+	property = m_Document.createElement(TAG_PROPERTY);
+	child.appendChild(property);
+	property.setAttribute(ATT_NAME, validContent(tmpStr));
+	property.appendChild(m_Document.createTextNode(validContent(att.getMetadata().getProperty(tmpStr, ""))));
+      }
+    }
+    
+    // relational attribute?
+    if (att.isRelationValued()) {
+      child = m_Document.createElement(TAG_ATTRIBUTES);
+      node.appendChild(child);
+      for (i = 0; i < att.relation().numAttributes(); i++)
+	addAttribute(child, att.relation().attribute(i));
+    }
+  }
+
+  /**
+   * turns all &lt;, &gt; and &amp;into character entities and returns that 
+   * string. Necessary for TextNodes.
+   * 
+   * @param content	string to convert
+   * @return		the valid content string
+   */
+  protected String validContent(String content) {
+    String	result;
+    
+    result = content;
+    
+    // these five entities are recognized by every XML processor
+    // see http://www.xml.com/pub/a/2001/03/14/trxml10.html
+    result = result.replaceAll("&", "&amp;")
+                   .replaceAll("\"", "&quot;")
+                   .replaceAll("'", "&apos;")
+                   .replaceAll("<", "&lt;")
+                   .replaceAll(">", "&gt;");
+    // in addition, replace some other entities as well
+    result = result.replaceAll("\n", "&#10;")
+                   .replaceAll("\r", "&#13;")
+                   .replaceAll("\t", "&#9;");
+    
+    return result;
+  }
+  
+  /**
+   * adds the instance to the XML structure
+   * 
+   * @param parent	the parent node to add the instance node as child
+   * @param inst	the instance to add
+   */
+  protected void addInstance(Element parent, Instance inst) {
+    Element		node;
+    Element		value;
+    Element		child;
+    boolean		sparse;
+    int			i;
+    int			n;
+    int			index;
+    
+    node = m_Document.createElement(TAG_INSTANCE);
+    parent.appendChild(node);
+    
+    // sparse?
+    sparse = (inst instanceof SparseInstance);
+    if (sparse)
+      node.setAttribute(ATT_TYPE, VAL_SPARSE);
+    
+    // weight
+    if (inst.weight() != 1.0)
+      node.setAttribute(ATT_WEIGHT, Utils.doubleToString(inst.weight(), m_Precision));
+    
+    // values
+    for (i = 0; i < inst.numValues(); i++) {
+      index = inst.index(i);
+      
+      value = m_Document.createElement(TAG_VALUE);
+      node.appendChild(value);
+
+      if (inst.isMissing(index)) {
+	value.setAttribute(ATT_MISSING, VAL_YES);
+      }
+      else {
+	if (inst.attribute(index).isRelationValued()) {
+	  child = m_Document.createElement(TAG_INSTANCES);
+	  value.appendChild(child);
+	  for (n = 0; n < inst.relationalValue(i).numInstances(); n++)
+	    addInstance(child, inst.relationalValue(i).instance(n));
+	}
+	else {
+	  if (inst.attribute(index).type() == Attribute.NUMERIC)
+	    value.appendChild(m_Document.createTextNode(Utils.doubleToString(inst.value(index), m_Precision)));
+	  else
+	    value.appendChild(m_Document.createTextNode(validContent(inst.stringValue(index))));
+	}
+      }
+      
+      if (sparse)
+	value.setAttribute(ATT_INDEX, "" + (index+1));
+    }
+  }
+  
+  /**
+   * generates the XML structure for the header
+   */
+  protected void headerToXML() {
+    Element	root;
+    Element	node;
+    Element	child;
+    int		i;
+    
+    root = m_Document.getDocumentElement();
+    root.setAttribute(ATT_NAME, validContent(m_Instances.relationName()));
+    root.setAttribute(ATT_VERSION, Version.VERSION);
+    
+    // create "header" node
+    node = m_Document.createElement(TAG_HEADER);
+    root.appendChild(node);
+
+    // add all attributes
+    child = m_Document.createElement(TAG_ATTRIBUTES);
+    node.appendChild(child);
+    for (i = 0; i < m_Instances.numAttributes(); i++)
+      addAttribute(child, m_Instances.attribute(i));
+  }
+  
+  /**
+   * generates the XML structure from the rows
+   */
+  protected void dataToXML() {
+    Element	root;
+    Element	node;
+    Element	child;
+    int		i;
+    
+    root = m_Document.getDocumentElement();
+    
+    // create "body" node
+    node = m_Document.createElement(TAG_BODY);
+    root.appendChild(node);
+
+    // add all instances
+    child = m_Document.createElement(TAG_INSTANCES);
+    node.appendChild(child);
+    for (i = 0; i < m_Instances.numInstances(); i++)
+      addInstance(child, m_Instances.instance(i));
+  }
+  
+  /**
+   * builds up the XML structure based on the given data
+   * 
+   * @param data	data to generate the XML from
+   */
+  public void setInstances(Instances data) {
+    m_Instances = new Instances(data);
+    clear();
+    headerToXML();
+    dataToXML();
+  }
+  
+  /**
+   * returns the current instances, either the ones that were set or the ones
+   * that were generated from the XML structure.
+   * 
+   * @return		the current instances
+   */
+  public Instances getInstances() {
+    return m_Instances;
+  }
+
+  /**
+   * returns the metadata, if any available underneath this node, otherwise
+   * just null
+   * 
+   * @param parent	the attribute node
+   * @return		the metadata, or null if none found
+   * @throws Exception	if generation fails
+   */
+  protected ProtectedProperties createMetadata(Element parent) throws Exception {
+    ProtectedProperties	result;
+    Properties		props;
+    Vector		list;
+    Element		node;
+    Element		metanode;
+    int			i;
+    
+    result = null;
+    
+    // find metadata node directly underneath this attribute, but not in
+    // deeper nested attributes (e.g., within relational attributes)
+    metanode = null;
+    list     = getChildTags(parent, TAG_METADATA);
+    if (list.size() > 0)
+      metanode = (Element) list.get(0);
+    
+    // generate properties
+    if (metanode != null) {
+      props = new Properties();
+      list  = getChildTags(metanode, TAG_PROPERTY);
+      for (i = 0; i < list.size(); i++) {
+	node = (Element) list.get(i);
+	props.setProperty(node.getAttribute(ATT_NAME), getContent(node));
+      }
+      result = new ProtectedProperties(props);
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns the labels listed underneath this (nominal) attribute in a 
+   * ArrayList
+   * 
+   * @param parent	the (nominal) attribute node
+   * @return		the label vector
+   * @throws Exception	if generation fails
+   */
+  protected ArrayList<String> createLabels(Element parent) throws Exception {
+    ArrayList<String>		result;
+    Vector		list;
+    Element		node;
+    Element		labelsnode;
+    int			i;
+    
+    result = new ArrayList<String>();
+    
+    // find labels node directly underneath this attribute, but not in
+    // deeper nested attributes (e.g., within relational attributes)
+    labelsnode = null;
+    list     = getChildTags(parent, TAG_LABELS);
+    if (list.size() > 0)
+      labelsnode = (Element) list.get(0);
+    
+    // retrieve all labels
+    if (labelsnode != null) {
+      list  = getChildTags(labelsnode, TAG_LABEL);
+      for (i = 0; i < list.size(); i++) {
+	node = (Element) list.get(i);
+	result.add(getContent(node));
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * creates an Attribute from the given XML node
+   * 
+   * @param node	the node with the setup
+   * @return		the configured Attribute
+   * @throws Exception	if generation fails, e.g., due to unknown attribute type
+   */
+  protected Attribute createAttribute(Element node) throws Exception {
+    String		typeStr;
+    String		name;
+    int			type;
+    Attribute		result;
+    ArrayList<String>		values;
+    ProtectedProperties	metadata;
+    Vector		list;
+    ArrayList<Attribute>		atts;
+    
+    result = null;
+    
+    // name
+    name = node.getAttribute(ATT_NAME);
+
+    // type
+    typeStr = node.getAttribute(ATT_TYPE);
+    if (typeStr.equals(VAL_NUMERIC))
+      type = Attribute.NUMERIC;
+    else if (typeStr.equals(VAL_DATE))
+      type = Attribute.DATE;
+    else if (typeStr.equals(VAL_NOMINAL))
+      type = Attribute.NOMINAL;
+    else if (typeStr.equals(VAL_STRING))
+      type = Attribute.STRING;
+    else if (typeStr.equals(VAL_RELATIONAL))
+      type = Attribute.RELATIONAL;
+    else
+      throw new Exception(
+	  "Attribute type '" + typeStr + "' is not supported!");
+
+    // metadata
+    metadata = createMetadata(node);
+    
+    switch (type) {
+      case Attribute.NUMERIC:
+	if (metadata == null)
+	  result = new Attribute(name);
+	else
+	  result = new Attribute(name, metadata);
+	break;
+
+      case Attribute.DATE:
+	if (metadata == null)
+	  result = new Attribute(name, node.getAttribute(ATT_FORMAT));
+	else
+	  result = new Attribute(name, node.getAttribute(ATT_FORMAT), metadata);
+	break;
+	
+      case Attribute.NOMINAL:
+	values = createLabels(node);
+	if (metadata == null)
+	  result = new Attribute(name, values);
+	else
+	  result = new Attribute(name, values, metadata);
+	break;
+	
+      case Attribute.STRING:
+	if (metadata == null)
+	  result = new Attribute(name, (ArrayList<String>) null);
+	else
+	  result = new Attribute(name, (ArrayList<String>) null, metadata);
+	break;
+	
+      case Attribute.RELATIONAL:
+	list = getChildTags(node, TAG_ATTRIBUTES);
+	node = (Element) list.get(0);
+	atts = createAttributes(node, new int[1]);
+	if (metadata == null)
+	  result = new Attribute(name, new Instances(name, atts, 0));
+	else
+	  result = new Attribute(name, new Instances(name, atts, 0), metadata);
+	break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns a list of generated attributes
+   * 
+   * @param parent	the attributes node
+   * @param classIndex	array of length 1 to return the class index, if any
+   * @return		the vector with the generated attributes
+   * @throws Exception	if generation fails, e.g., due to unknown attribute type
+   */
+  protected ArrayList<Attribute> createAttributes(Element parent, int[] classIndex) throws Exception {
+    Vector	list;
+    ArrayList<Attribute>	result;
+    int		i;
+    Element	node;
+    Attribute	att;
+
+    result        = new ArrayList<Attribute>();
+    classIndex[0] = -1;
+    
+    list = getChildTags(parent, TAG_ATTRIBUTE);
+    for (i = 0; i < list.size(); i++) {
+      node = (Element) list.get(i);
+      att = createAttribute(node);
+      if (node.getAttribute(ATT_CLASS).equals(VAL_YES))
+	classIndex[0] = i;
+      result.add(att);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * creates an Instance from the given XML node
+   * 
+   * @param header	the data this instance will belong to
+   * @param parent	the instance node
+   * @return		the configured Instance
+   * @throws Exception	if generation fails, e.g., due to unknown attribute type
+   */
+  protected Instance createInstance(Instances header, Element parent) throws Exception {
+    Instance	result;
+    Element	node;
+    Element	child;
+    boolean	sparse;
+    int		i;
+    int		index;
+    Vector	list;
+    Vector	subList;
+    double[]	values;
+    String	content;
+    double	weight;
+    Instances	data;
+    
+    result = null;
+
+    // sparse?
+    sparse = (parent.getAttribute(ATT_TYPE).equals(VAL_SPARSE));
+    values = new double[header.numAttributes()];
+    
+    // weight
+    if (parent.getAttribute(ATT_WEIGHT).length() != 0)
+      weight = Double.parseDouble(parent.getAttribute(ATT_WEIGHT));
+    else
+      weight = 1.0;
+    
+    list = getChildTags(parent, TAG_VALUE);
+    for (i = 0; i < list.size(); i++) {
+      node = (Element) list.get(i);
+      
+      // determine index
+      if (sparse)
+	index = Integer.parseInt(node.getAttribute(ATT_INDEX)) - 1;
+      else
+	index = i;
+
+      // set value
+      if (node.getAttribute(ATT_MISSING).equals(VAL_YES)) {
+	values[index] = Utils.missingValue();
+      }
+      else {
+	content = getContent(node);
+	switch (header.attribute(index).type()) {
+	  case Attribute.NUMERIC:
+	    values[index] = Double.parseDouble(content);
+	    break;
+	    
+	  case Attribute.DATE:
+	    values[index] = header.attribute(index).parseDate(content);
+	    break;
+	    
+	  case Attribute.NOMINAL:
+	    values[index] = header.attribute(index).indexOfValue(content);
+	    break;
+	    
+	  case Attribute.STRING:
+	    values[index] = header.attribute(index).addStringValue(content);
+	    break;
+	    
+	  case Attribute.RELATIONAL:
+	    subList       = getChildTags(node, TAG_INSTANCES);
+	    child         = (Element) subList.get(0);
+	    data          = createInstances(header.attribute(index).relation(), child);
+	    values[index] = header.attribute(index).addRelation(data);
+	    break;
+	    
+	  default:
+	    throw new Exception(
+		"Attribute type " + header.attribute(index).type() 
+		+ " is not supported!");  
+	}
+      }
+    }
+    
+    // create instance
+    if (sparse)
+      result = new SparseInstance(weight, values);
+    else
+      result = new DenseInstance(weight, values);
+    
+    return result;
+  }
+  
+  /**
+   * creates Instances from the given XML node
+   * 
+   * @param header	the header of this data
+   * @param parent	the instances node
+   * @return		the generated Instances
+   * @throws Exception	if generation fails, e.g., due to unknown attribute type
+   */
+  protected Instances createInstances(Instances header, Element parent) throws Exception {
+    Instances	result;
+    Vector	list;
+    int		i;
+    
+    result = new Instances(header, 0);
+    
+    list = getChildTags(parent, TAG_INSTANCE);
+    for (i = 0; i < list.size(); i++)
+      result.add(createInstance(result, (Element) list.get(i)));
+    
+    return result;
+  }
+  
+  /**
+   * generates the header from the XML document
+   * 
+   * @return		the generated header
+   * @throws Exception	if generation fails
+   */
+  protected Instances headerFromXML() throws Exception {
+    Instances	result;
+    Element	root;
+    Element	node;
+    Vector	list;
+    ArrayList<Attribute>	atts;
+    Version	version;
+    int[]	classIndex;
+
+    root = m_Document.getDocumentElement();
+    
+    // check version
+    version = new Version();
+    if (version.isOlder(root.getAttribute(ATT_VERSION)))
+      System.out.println(
+	  "WARNING: loading data of version " + root.getAttribute(ATT_VERSION)
+	  + " with version " + Version.VERSION);
+    
+    // attributes
+    list       = getChildTags(root, TAG_HEADER);
+    node       = (Element) list.get(0);
+    list       = getChildTags(node, TAG_ATTRIBUTES);
+    node       = (Element) list.get(0);
+    classIndex = new int[1];
+    atts       = createAttributes(node, classIndex);
+
+    // generate header
+    result = new Instances(root.getAttribute(ATT_NAME), atts, 0);
+    result.setClassIndex(classIndex[0]);
+    
+    return result;
+  }
+  
+  /**
+   * generates the complete dataset from the XML document
+   * 
+   * @param header	the header structure
+   * @return		the complete dataset
+   * @throws Exception	if generation fails
+   */
+  protected Instances dataFromXML(Instances header) throws Exception {
+    Instances	result;
+    Element	node;
+    Vector	list;
+
+    list   = getChildTags(m_Document.getDocumentElement(), TAG_BODY);
+    node   = (Element) list.get(0);
+    list   = getChildTags(node, TAG_INSTANCES);
+    node   = (Element) list.get(0);
+    result = createInstances(header, node);
+    
+    return result;
+  }
+  
+  /**
+   * reads the XML structure from the given reader
+   * 
+   * @param reader	the reader to get the XML from
+   * @throws Exception	if 
+   */
+  public void setXML(Reader reader) throws Exception {
+    read(reader);
+    
+    // interprete XML structure
+    m_Instances = dataFromXML(headerFromXML());
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * takes an XML document as first argument and then outputs the Instances
+   * statistics
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    try {
+      Reader r = null;
+      if (args.length != 1) {
+	throw (new Exception("Usage: XMLInstances <filename>"));
+      }
+      else {
+	InputStream in = new FileInputStream(args[0]);
+	// compressed file?
+	if (args[0].endsWith(".gz"))
+	  in = new GZIPInputStream(in);
+        r = new BufferedReader(new InputStreamReader(in));
+     }
+      
+      if (args[0].endsWith(Instances.FILE_EXTENSION)) {
+	XMLInstances i = new XMLInstances(new Instances(r));
+	System.out.println(i.toString());
+      }
+      else {
+	Instances i = new XMLInstances(r).getInstances();
+	System.out.println(i.toSummaryString());
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLOptions.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLOptions.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLOptions.java	(revision 29)
@@ -0,0 +1,405 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLOptions.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Vector;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * A class for transforming options listed in XML to a regular WEKA command
+ * line string.<p>
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $
+ */
+public class XMLOptions
+  implements RevisionHandler {
+  
+  /** tag for a single option. */
+  public final static String TAG_OPTION = "option";
+
+  /** tag for a list of options. */
+  public final static String TAG_OPTIONS = "options";
+
+  /** the name attribute. */
+  public final static String ATT_NAME = "name";
+
+  /** the type attribute. */
+  public final static String ATT_TYPE = "type";
+
+  /** the value attribute. */
+  public final static String ATT_VALUE = "value";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_FLAG = "flag";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_SINGLE = "single";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_HYPHENS = "hyphens";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_QUOTES = "quotes";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_CLASSIFIER = "classifier";
+
+  /** a value of the type attribute. */
+  public final static String VAL_TYPE_OPTIONHANDLER = "optionhandler";
+
+  /** the root node. */
+  public final static String ROOT_NODE = TAG_OPTIONS;
+
+  /** the DTD for the XML file. */
+  public final static String DOCTYPE = 
+    "<!DOCTYPE " + ROOT_NODE + "\n"
+    + "[\n"
+    + "   <!ELEMENT " + TAG_OPTIONS + " (" + TAG_OPTION + ")*>\n"
+    + "   <!ATTLIST " + TAG_OPTIONS + " " + ATT_TYPE + " CDATA \"" + VAL_TYPE_OPTIONHANDLER + "\">\n"
+    + "   <!ATTLIST " + TAG_OPTIONS + " " + ATT_VALUE + " CDATA \"\">\n"
+    + "   <!ELEMENT " + TAG_OPTION + " (#PCDATA | " + TAG_OPTIONS + ")*>\n"
+    + "   <!ATTLIST " + TAG_OPTION + " " + ATT_NAME + " CDATA #REQUIRED>\n"
+    + "   <!ATTLIST " + TAG_OPTION + " " + ATT_TYPE + " (" + VAL_TYPE_FLAG + " | " + VAL_TYPE_SINGLE + " | " + VAL_TYPE_HYPHENS + " | " + VAL_TYPE_QUOTES + ") \"" + VAL_TYPE_SINGLE + "\">\n"
+    + "]\n"
+    + ">";
+
+  /** the XML document. */
+  protected XMLDocument m_XMLDocument = null;
+
+  /** 
+   * Creates a new instance of XMLOptions.
+   * 
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLOptions() throws Exception {
+    m_XMLDocument = new XMLDocument(); 
+    m_XMLDocument.setRootNode(ROOT_NODE);
+    m_XMLDocument.setDocType(DOCTYPE);
+    setValidating(true);
+  }
+
+  /** 
+   * Creates a new instance of XMLOptions.
+   *  
+   * @param xml 	the xml to parse (if "<?xml" is not found then it is considered a file)
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLOptions(String xml) throws Exception {
+    this();
+    getXMLDocument().read(xml);
+  }
+
+  /** 
+   * Creates a new instance of XMLOptions.
+   * 
+   * @param file 	the XML file to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLOptions(File file) throws Exception {
+    this();
+    getXMLDocument().read(file);
+  }
+
+  /** 
+   * Creates a new instance of XMLOptions.
+   * 
+   * @param stream 	the XML stream to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLOptions(InputStream stream) throws Exception {
+    this();
+    getXMLDocument().read(stream);
+  }
+
+  /** 
+   * Creates a new instance of XMLOptions.
+   * 
+   * @param reader 	the XML reader to parse
+   * @throws Exception 	if the construction of the DocumentBuilder fails
+   * @see 		#setValidating(boolean)
+   */
+  public XMLOptions(Reader reader) throws Exception {
+    this();
+    getXMLDocument().read(reader);
+  }
+
+  /**
+   * returns whether a validating parser is used.
+   * 
+   * @return 		whether a validating parser is used
+   */
+  public boolean getValidating() {
+    return m_XMLDocument.getValidating();
+  }
+
+  /**
+   * sets whether to use a validating parser or not. <br>
+   * Note: this does clear the current DOM document! 
+   * 
+   * @param validating 	whether to use a validating parser
+   * @throws Exception 	if the instantiating of the DocumentBuilder fails
+   */
+  public void setValidating(boolean validating) throws Exception {
+    m_XMLDocument.setValidating(validating);
+  }
+
+  /**
+   * returns the parsed DOM document.
+   * 
+   * @return 		the parsed DOM document
+   */
+  public Document getDocument() {
+    fixHyphens();
+    return m_XMLDocument.getDocument();
+  }
+
+  /**
+   * returns the handler of the XML document. the internal DOM document can 
+   * be accessed via the <code>getDocument()</code> method.
+   * 
+   * @return 		the object handling the XML document
+   * @see 		#getDocument()
+   */
+  public XMLDocument getXMLDocument() {
+    fixHyphens();
+    return m_XMLDocument;
+  }
+
+  /**
+   * pushes any options with type VAL_TYPE_HYPHENS to the end, i.e., the "--" 
+   * are really added at the end.
+   * 
+   * @see		#VAL_TYPE_HYPHENS
+   */
+  protected void fixHyphens() {
+    NodeList	list;
+    Vector<Element>	hyphens;
+    int		i;
+    Node	node;
+    Node	tmpNode;
+    boolean	isLast;
+
+    // get all option tags
+    list = m_XMLDocument.findNodes("//" + TAG_OPTION);
+
+    // get all hyphen tags
+    hyphens = new Vector<Element>();
+    for (i = 0; i < list.getLength(); i++) {
+      if (((Element) list.item(i)).getAttribute(ATT_TYPE).equals(VAL_TYPE_HYPHENS))
+	hyphens.add((Element)list.item(i));
+    }
+
+    // check all hyphen tags whether they are at the end, if not fix it
+    for (i = 0; i < hyphens.size(); i++) {
+      node = (Node) hyphens.get(i);
+
+      // at the end?
+      isLast  = true;
+      tmpNode = node;
+      while (tmpNode.getNextSibling() != null) {
+	// normal tag?
+	if (tmpNode.getNextSibling().getNodeType() == Node.ELEMENT_NODE) {
+	  isLast = false;
+	  break;
+	}
+	tmpNode = tmpNode.getNextSibling();
+      }
+
+      // move
+      if (!isLast) {
+	tmpNode = node.getParentNode();
+	tmpNode.removeChild(node);
+	tmpNode.appendChild(node);
+      }
+    }
+  }
+
+  /**
+   * converts the given node into a command line representation and returns it.
+   * 
+   * @param parent 	the node to convert to command line
+   * @return 		the new command line
+   */
+  protected String toCommandLine(Element parent) {
+    Vector<String>	result;
+    Vector		list;
+    Vector		subList;
+    NodeList		subNodeList;
+    String[]		params;
+    int			i;
+    int			n;
+    String		tmpStr;
+    
+    result = new Vector<String>();
+    
+    // "options" tag
+    if (parent.getNodeName().equals(TAG_OPTIONS)) {
+      // children
+      list = XMLDocument.getChildTags(parent);
+
+      if (parent.getAttribute(ATT_TYPE).equals(VAL_TYPE_CLASSIFIER)) {
+	System.err.println(
+	    "Type '" + VAL_TYPE_CLASSIFIER + "' is deprecated, "
+	    + "use '" + VAL_TYPE_OPTIONHANDLER + "' instead!");
+	parent.setAttribute(ATT_TYPE, VAL_TYPE_OPTIONHANDLER);
+      }
+      
+      if (parent.getAttribute(ATT_TYPE).equals(VAL_TYPE_OPTIONHANDLER)) {
+	result.add(parent.getAttribute(ATT_VALUE));
+
+	// hyphens?
+	if (    (list.size() > 0)
+	     && (parent.getParentNode() != null) 
+	     && (parent.getParentNode() instanceof Element) 
+	     && (((Element) parent.getParentNode()).getNodeName().equals(TAG_OPTION)) 
+	     && (((Element) parent.getParentNode()).getAttribute(ATT_TYPE).equals(VAL_TYPE_HYPHENS)) )
+	  result.add("--");
+      }
+
+      // process children
+      for (i = 0; i < list.size(); i++) {
+	tmpStr = toCommandLine((Element) list.get(i));
+	try {
+	  params = Utils.splitOptions(tmpStr);
+	  for (n = 0; n < params.length; n++)
+	    result.add(params[n]);
+	}
+	catch (Exception e) {
+	  System.err.println("Error splitting: " + tmpStr);
+	  e.printStackTrace();
+	}
+      }
+    }
+    // "option" tag
+    else if (parent.getNodeName().equals(TAG_OPTION)) {
+      subList     = XMLDocument.getChildTags(parent);
+      subNodeList = parent.getChildNodes();
+
+      result.add("-" + parent.getAttribute(ATT_NAME));
+
+      // single argument
+      if (parent.getAttribute(ATT_TYPE).equals(VAL_TYPE_SINGLE)) {
+	if (    (subNodeList.getLength() > 0) 
+	     && (subNodeList.item(0).getNodeValue().trim().length() > 0) )
+          result.add(subNodeList.item(0).getNodeValue());
+      }
+      // compound argument surrounded by quotes
+      else if (parent.getAttribute(ATT_TYPE).equals(VAL_TYPE_QUOTES)) {
+	result.add(toCommandLine((Element) subList.get(0)));
+      }
+      // classname + further options after "--"
+      else if (parent.getAttribute(ATT_TYPE).equals(VAL_TYPE_HYPHENS)) {
+	tmpStr = toCommandLine((Element) subList.get(0));
+	try {
+	  params = Utils.splitOptions(tmpStr);
+	  for (n = 0; n < params.length; n++)
+	    result.add(params[n]);
+	}
+	catch (Exception e) {
+	  System.err.println("Error splitting: " + tmpStr);
+	  e.printStackTrace();
+	}
+      }
+    }
+    // other tag
+    else {
+      System.err.println("Unsupported tag '" + parent.getNodeName() + "' - skipped!");
+    }
+    
+    return Utils.joinOptions(result.toArray(new String[result.size()]));
+  }
+
+  /**
+   * returns the given DOM document as command line.
+   * 
+   * @return 		the document as command line
+   * @throws Exception 	if anything goes wrong initializing the parsing
+   */
+  public String toCommandLine() throws Exception {
+    return toCommandLine(getDocument().getDocumentElement());
+  }
+
+  /**
+   * returns the current DOM document as string array.
+   * 
+   * @return 		the document as string array
+   * @throws Exception 	if anything goes wrong initializing the parsing
+   */
+  public String[] toArray() throws Exception {
+    return Utils.splitOptions(toCommandLine());
+  }
+
+  /**
+   * returns the object in a string representation (as indented XML output).
+   * 
+   * @return 		the object in a string representation
+   */
+  public String toString() {
+    return getXMLDocument().toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5953 $");
+  }
+
+  /**
+   * for testing only. prints the given XML file, the resulting commandline and
+   * the string array.
+   * 
+   * @param args	the commandline options.
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    if (args.length > 0) {
+      System.out.println("\nXML:\n\n" + new XMLOptions(args[0]).toString()); 
+
+      System.out.println("\nCommandline:\n\n" + new XMLOptions(args[0]).toCommandLine());
+
+      System.out.println("\nString array:\n");
+      String[] options = new XMLOptions(args[0]).toArray();
+      for (int i = 0; i < options.length; i++)
+	System.out.println(options[i]);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLSerialization.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLSerialization.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLSerialization.java	(revision 29)
@@ -0,0 +1,1704 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLSerialization.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Version;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * With this class objects can be serialized to XML instead into a binary 
+ * format. It uses introspection (cf. beans) to retrieve the data from the
+ * given object, i.e. it can only access beans-conform fields automatically.
+ * <p>
+ * The generic approach of writing data as XML can be overriden by adding 
+ * custom methods for reading/writing in a derived class
+ * (cf. <code>m_Properties</code>, <code>m_CustomMethods</code>).<br>
+ * Custom read and write methods must have the same signature (and also be 
+ * <code>public</code>!) as the <code>readFromXML</code> and <code>writeToXML</code>
+ * methods. Methods that apply to the naming rule <code>read + property name</code>
+ * are added automatically to the list of methods by the method 
+ * <code>XMLSerializationMethodHandler.addMethods(...)</code>.  
+ * <p>
+ * Other properties that are not conform the bean set/get-methods have to be 
+ * processed manually in a derived class (cf. <code>readPostProcess(Object)</code>, 
+ * <code>writePostProcess(Object)</code>).
+ * <p>
+ * For a complete XML serialization/deserialization have a look at the 
+ * <code>KOML</code> class.
+ * <p>
+ * If a stored class has a constructor that takes a String to initialize
+ * (e.g. String or Double) then the content of the tag will used for the
+ * constructor, e.g. from 
+ * <pre>&lt;object name="name" class="String" primitive="no"&gt;Smith&lt;/object&gt;</pre>
+ * "Smith" will be used to instantiate a String object as constructor argument.
+ * <p>   
+ * 
+ * @see KOML
+ * @see #fromXML(Document)
+ * @see #toXML(Object)
+ * @see #m_Properties
+ * @see #m_CustomMethods
+ * @see #readPostProcess(Object)
+ * @see #writePostProcess(Object)
+ * @see #readFromXML(Element)
+ * @see #writeToXML(Element, Object, String)
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class XMLSerialization
+   implements RevisionHandler {
+  
+   /** for debugging purposes only */
+   protected static boolean DEBUG = false;
+  
+   /** the node that is currently processed, in case of writing the parent node
+    * (something might go wrong writing the new child) and in case of reading 
+    * the actual node that is tried to process */
+   protected Element m_CurrentNode = null;
+   
+   /** the tag for an object */
+   public final static String TAG_OBJECT = "object";
+   
+   /** the version attribute */
+   public final static String ATT_VERSION = XMLDocument.ATT_VERSION;
+  
+   /** the tag for the name */
+   public final static String ATT_NAME = XMLDocument.ATT_NAME;
+   
+   /** the tag for the class */
+   public final static String ATT_CLASS = "class";
+   
+   /** the tag whether primitive or not (yes/no) */
+   public final static String ATT_PRIMITIVE = "primitive";
+   
+   /** the tag whether array or not (yes/no) */
+   public final static String ATT_ARRAY = "array";
+   
+   /** the tag whether null or not (yes/no) */
+   public final static String ATT_NULL = "null";
+   
+   /** the value "yes" for the primitive and array attribute */
+   public final static String VAL_YES = XMLDocument.VAL_YES;
+   
+   /** the value "no" for the primitive and array attribute */
+   public final static String VAL_NO = XMLDocument.VAL_NO;
+   
+   /** the value of the name for the root node */
+   public final static String VAL_ROOT = "__root__";
+   
+   /** the root node of the XML document */
+   public final static String ROOT_NODE = TAG_OBJECT; 
+   
+   /** default value for attribute ATT_PRIMITIVE
+    * @see #ATT_PRIMITIVE */
+   public final static String ATT_PRIMITIVE_DEFAULT = VAL_NO;
+   
+   /** default value for attribute ATT_ARRAY
+    * @see #ATT_ARRAY */
+   public final static String ATT_ARRAY_DEFAULT = VAL_NO;
+   
+   /** default value for attribute ATT_NULL
+    * @see #ATT_NULL */
+   public final static String ATT_NULL_DEFAULT = VAL_NO;
+   
+   /** the DOCTYPE for the serialization */
+   public final static String DOCTYPE = 
+        "<!" + XMLDocument.DTD_DOCTYPE + " " + ROOT_NODE + "\n"
+      + "[\n"
+      + "   <!" + XMLDocument.DTD_ELEMENT + " " + TAG_OBJECT + " (" + XMLDocument.DTD_PCDATA + XMLDocument.DTD_SEPARATOR + TAG_OBJECT + ")" + XMLDocument.DTD_ZERO_OR_MORE + ">\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_NAME + "      " + XMLDocument.DTD_CDATA + " " + XMLDocument.DTD_REQUIRED + ">\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_CLASS + "     " + XMLDocument.DTD_CDATA + " " + XMLDocument.DTD_REQUIRED + ">\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_PRIMITIVE + " " + XMLDocument.DTD_CDATA + " \"" + ATT_PRIMITIVE_DEFAULT + "\">\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_ARRAY + "     " + XMLDocument.DTD_CDATA + " \"" + ATT_ARRAY_DEFAULT + "\">   <!-- the dimensions of the array; no=0, yes=1 -->\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_NULL + "      " + XMLDocument.DTD_CDATA + " \"" + ATT_NULL_DEFAULT + "\">\n"
+      + "   <!" + XMLDocument.DTD_ATTLIST + " " + TAG_OBJECT + " " + ATT_VERSION + "   " + XMLDocument.DTD_CDATA + " \"" + Version.VERSION + "\">\n"
+      + "]\n"
+      + ">";
+   
+   /** the XMLDocument that performs the transformation to and fro XML */
+   protected XMLDocument m_Document = null;
+   
+   /** for handling properties (ignored/allowed) */
+   protected PropertyHandler m_Properties = null;
+   
+   /** for handling custom read/write methods */
+   protected XMLSerializationMethodHandler m_CustomMethods = null;
+
+   /** for overriding class names (Class &lt;-&gt; Classname (String)) 
+    * @see #overrideClassname(Object) */
+   protected Hashtable<Class,String> m_ClassnameOverride = null;
+   
+   /**
+    * initializes the serialization
+    * 
+    * @throws Exception if initialization fails
+    */
+   public XMLSerialization() throws Exception {
+      super();
+      clear();
+   }
+   
+   /**
+    * used for debugging purposes, i.e. only if DEBUG is set to true.
+    * needs a newly generated Throwable instance to get the method/line from
+    * @param t      a throwable instance, generated in the calling method
+    * @param msg    a message to pring
+    * @see          #DEBUG
+    */
+   protected void trace(Throwable t, String msg) {
+     if ( (DEBUG) && (t.getStackTrace().length > 0) ) {
+       System.out.println("trace: " + t.getStackTrace()[0] + ": " + msg);
+     }
+   }
+   
+   /**
+    * generates internally a new XML document and clears also the IgnoreList and
+    * the mappings for the Read/Write-Methods
+    * 
+    * @throws Exception	if something goes wrong
+    */
+   public void clear() throws Exception {
+      m_Document = new XMLDocument();
+      m_Document.setValidating(true);
+      m_Document.newDocument(DOCTYPE, ROOT_NODE);
+      
+      m_Properties        = new PropertyHandler();
+      m_CustomMethods     = new XMLSerializationMethodHandler(this);
+
+      m_ClassnameOverride = new Hashtable<Class,String>();
+      // java.io.File is sometimes represented as another class:
+      // - Win32: sun.awt.shell.Win32ShellFolder2 
+      // - Linux: sun.awt.shell.DefaultShellFolder
+      // -> we set it to "java.io.File"
+      m_ClassnameOverride.put(java.io.File.class, java.io.File.class.getName());
+      
+      setVersion(Version.VERSION); 
+      
+      m_CurrentNode = null;
+   }
+   
+   /**
+    * sets the given version string in the XML document
+    * 
+    * @param version	the new version string
+    */
+   private void setVersion(String version) {
+      Document     doc;
+      
+      doc = m_Document.getDocument();
+      doc.getDocumentElement().setAttribute(ATT_VERSION, version);
+   }
+   
+   /**
+    * returns the WEKA version with which the serialized object was created
+    * 
+    * @return		the current version
+    * @see Version 
+    */
+   public String getVersion() {
+      Document     doc;
+      String       result;
+      
+      doc    = m_Document.getDocument();
+      result = doc.getDocumentElement().getAttribute(ATT_VERSION);
+      
+      return result;
+   }
+   
+   /**
+    * Checks the version in the current Document with the one of the current
+    * release. If the version differ, a warning is printed.
+    */
+   private void checkVersion() {
+      String            versionStr;
+      Version           version;
+      
+      version    = new Version();
+      versionStr = getVersion();
+      if (versionStr.equals(""))
+         System.out.println("WARNING: has no version!");
+      else if (version.isOlder(versionStr))
+         System.out.println("WARNING: loading a newer version (" + versionStr + " > " + Version.VERSION + ")!");
+      else if (version.isNewer(versionStr))
+         System.out.println("NOTE: loading an older version (" + versionStr + " < " + Version.VERSION + ")!");
+   }
+   
+   /**
+    * returns a hashtable with PropertyDescriptors that have "get" and "set" 
+    * methods indexed by the property name.
+    * 
+    * @see java.beans.PropertyDescriptor
+    * @param o the object to retrieve the descriptors from
+    * @return the PropertyDescriptors indexed by name of the property
+    * @throws Exception if the introspection fails
+    */
+   protected Hashtable getDescriptors(Object o) throws Exception {
+      BeanInfo                   info;
+      PropertyDescriptor[]       desc;
+      int                        i;
+      Hashtable<String,PropertyDescriptor>                  result;
+      
+      result = new Hashtable<String,PropertyDescriptor>();
+
+      info = Introspector.getBeanInfo(o.getClass());
+      desc = info.getPropertyDescriptors();
+      for (i = 0; i < desc.length; i++) {
+         // get AND set method?
+         if ( (desc[i].getReadMethod() != null) && (desc[i].getWriteMethod() != null) ) {
+            // in ignore list, i.e. a general ignore without complete path?
+            if (m_Properties.isIgnored(desc[i].getDisplayName()))
+               continue;
+            
+            // in ignore list of the class?
+            if (m_Properties.isIgnored(o, desc[i].getDisplayName()))
+               continue;
+            
+            // not an allowed property
+            if (!m_Properties.isAllowed(o, desc[i].getDisplayName()))
+               continue;
+            
+            result.put(desc[i].getDisplayName(), desc[i]);
+         }
+      }
+      
+      return result;
+   }
+
+   /**
+    * returns the path of the "name" attribute from the root down to this node
+    * (including it).
+    * 
+    * @param node the node to get the path for
+    * @return the complete "name" path of this node
+    */
+   protected String getPath(Element node) {
+      String            result;
+      
+      result = node.getAttribute(ATT_NAME);
+
+      while (node.getParentNode() != node.getOwnerDocument()) {
+         node   = (Element) node.getParentNode(); 
+         result = node.getAttribute(ATT_NAME) + "." + result;
+      }
+      
+      return result;
+   }
+   
+   /**
+    * returns either <code>VAL_YES</code> or <code>VAL_NO</code> depending 
+    * on the value of <code>b</code>
+    * 
+    * @param b the boolean to turn into a string
+    * @return the value in string representation
+    */
+   protected String booleanToString(boolean b) {
+      if (b)
+         return VAL_YES;
+      else
+         return VAL_NO;
+   }
+   
+   /**
+    * turns the given string into a boolean, if a positive number is given, 
+    * then zero is considered FALSE, every other number TRUE; the empty string 
+    * is also considered being FALSE
+    * 
+    * @param s the string to turn into a boolean
+    * @return the string as boolean
+    */
+   protected boolean stringToBoolean(String s) {
+      if (s.equals(""))
+         return false;
+      else if (s.equals(VAL_YES))
+         return true;
+      else if (s.equalsIgnoreCase("true"))
+         return true;
+      else if (s.replaceAll("[0-9]*", "").equals(""))
+         return (Integer.parseInt(s) != 0);
+      else
+         return false;
+   }
+   
+   /**
+    * appends a new node to the parent with the given parameters (a non-array)
+    * 
+    * @param parent the parent of this node. if it is <code>null</code> the 
+    *        document root element is used
+    * @param name the name of the node
+    * @param classname the classname for this node
+    * @param primitive whether it is a primitve data type or not (i.e. an object)
+    * @return the generated node 
+    */
+   protected Element addElement(Element parent, String name, String classname, boolean primitive) {
+     return addElement(parent, name, classname, primitive, 0);
+   }
+   
+   /**
+    * appends a new node to the parent with the given parameters
+    * 
+    * @param parent the parent of this node. if it is <code>null</code> the 
+    *        document root element is used
+    * @param name the name of the node
+    * @param classname the classname for this node
+    * @param primitive whether it is a primitve data type or not (i.e. an object)
+    * @param array the dimensions of the array (0 if not an array)
+    * @return the generated node 
+    */
+   protected Element addElement(Element parent, String name, String classname, boolean primitive, int array) {
+     return addElement(parent, name, classname, primitive, array, false);
+   }
+   
+   /**
+    * appends a new node to the parent with the given parameters
+    * 
+    * @param parent the parent of this node. if it is <code>null</code> the 
+    *        document root element is used
+    * @param name the name of the node
+    * @param classname the classname for this node
+    * @param primitive whether it is a primitve data type or not (i.e. an object)
+    * @param array the dimensions of the array (0 if not an array)
+    * @param isnull whether it is null
+    * @return the generated node 
+    */
+   protected Element addElement(Element parent, String name, String classname, boolean primitive, int array, boolean isnull) {
+      Element           result;
+
+      if (parent == null)
+         result = m_Document.getDocument().getDocumentElement();
+      else
+         result = (Element) parent.appendChild(m_Document.getDocument().createElement(TAG_OBJECT));
+      
+      // attributes
+      // mandatory attributes:
+      result.setAttribute(ATT_NAME, name);
+      result.setAttribute(ATT_CLASS, classname);
+      
+      // add following attributes only if necessary, i.e., different from default:
+      if (!booleanToString(primitive).equals(ATT_PRIMITIVE_DEFAULT))
+        result.setAttribute(ATT_PRIMITIVE, booleanToString(primitive));
+
+      // multi-dimensional array?
+      if (array > 1) {
+        result.setAttribute(ATT_ARRAY, Integer.toString(array));
+      }
+      // backwards compatible: 0 -> no array ("no"), 1 -> 1-dim. array ("yes")
+      else {
+        if (!booleanToString(array == 1).equals(ATT_ARRAY_DEFAULT))
+          result.setAttribute(ATT_ARRAY, booleanToString(array == 1));
+      }
+      
+      if (!booleanToString(isnull).equals(ATT_NULL_DEFAULT))
+        result.setAttribute(ATT_NULL, booleanToString(isnull));
+      
+      return result;
+   }
+   
+   /**
+    * if the class of the given object (or one of its ancestors) is stored in 
+    * the classname override hashtable, then the override name is returned 
+    * otherwise the classname of the given object.
+    * 
+    * @param o          the object to check for overriding its classname
+    * @return           if overridden then the classname stored in the hashtable,
+    *                   otherwise the classname of the given object
+    * @see              #m_ClassnameOverride
+    */
+   protected String overrideClassname(Object o) {
+      Enumeration    enm;
+      String         result;
+      Class          currentCls;
+     
+      result = o.getClass().getName();
+
+      // check overrides
+      enm    = m_ClassnameOverride.keys();
+      while (enm.hasMoreElements()) {
+         currentCls = (Class) enm.nextElement();
+         if (currentCls.isInstance(o)) {
+           result = (String) m_ClassnameOverride.get(currentCls);
+           break;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * if the given classname is stored in the classname override hashtable, 
+    * then the override name is returned otherwise the given classname.
+    * <b>Note:</b> in contrast to <code>overrideClassname(Object)</code> does
+    * this method only look for exact name matches. The other method checks
+    * whether the class of the given object is a subclass of any of the stored
+    * overrides.  
+    * 
+    * @param classname  the classname to check for overriding
+    * @return           if overridden then the classname stored in the hashtable,
+    *                   otherwise the given classname
+    * @see              #m_ClassnameOverride
+    * @see              #overrideClassname(Object)
+    */
+   protected String overrideClassname(String classname) {
+      Enumeration    enm;
+      String         result;
+      Class          currentCls;
+     
+      result = classname;
+
+      // check overrides
+      enm    = m_ClassnameOverride.keys();
+      while (enm.hasMoreElements()) {
+         currentCls = (Class) enm.nextElement();
+         if (currentCls.getName().equals(classname)) {
+           result = (String) m_ClassnameOverride.get(currentCls);
+           break;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * returns a property descriptor if possible, otherwise <code>null</code>
+    * 
+    * @param className the name of the class to get the descriptor for
+    * @param displayName the name of the property
+    * @return the descriptor if available, otherwise <code>null</code>
+    */
+   protected PropertyDescriptor determineDescriptor(String className, String displayName) {
+      PropertyDescriptor      result;
+      
+      result = null;
+      
+      try {
+         result = new PropertyDescriptor(displayName, Class.forName(className));
+      }
+      catch (Exception e) {
+         result = null;
+      }
+      
+      return result;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeBooleanToXML(Element parent, boolean o, String name) throws Exception {
+     Element      node;
+
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Boolean.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Boolean(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeByteToXML(Element parent, byte o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Byte.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Byte(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeCharToXML(Element parent, char o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Character.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Character(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeDoubleToXML(Element parent, double o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Double.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Double(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeFloatToXML(Element parent, float o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Float.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Float(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeIntToXML(Element parent, int o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Integer.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Integer(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeLongToXML(Element parent, long o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+
+     node = addElement(parent, name, Long.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Long(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * adds the given primitive to the DOM structure.
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the primitive to describe in XML
+    * @param name the name of the primitive
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   protected Element writeShortToXML(Element parent, short o, String name) throws Exception {
+     Element      node;
+     
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), name);
+     
+     m_CurrentNode = parent;
+     
+     node = addElement(parent, name, Short.TYPE.getName(), true);
+     node.appendChild(node.getOwnerDocument().createTextNode(new Short(o).toString()));
+     
+     return node;
+   }
+   
+   /**
+    * checks whether the innermost class is a primitive class (handles 
+    * multi-dimensional arrays)
+    * @param c        the array class to inspect
+    * @return         whether the array consists of primitive elements
+    */
+   protected boolean isPrimitiveArray(Class c) {
+     if (c.getComponentType().isArray())
+       return isPrimitiveArray(c.getComponentType());
+    else
+       return c.getComponentType().isPrimitive();
+   }
+   
+   /**
+    * adds the given Object to a DOM structure. 
+    * (only public due to reflection).<br>
+    * <b>Note:</b> <code>overrideClassname(Object)</code> is not invoked in case of
+    * arrays, since the array class could be a superclass, whereas the elements of
+    * the array can be specialized subclasses. In case of an array the method 
+    * <code>overrideClassname(String)</code> is invoked, which searches for an 
+    * exact match of the classname in the override hashtable.
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    * @see #overrideClassname(Object)
+    * @see #overrideClassname(String)
+    * @see #m_ClassnameOverride
+    */
+   public Element writeToXML(Element parent, Object o, String name) throws Exception {
+      String               classname;
+      Element              node;
+      Hashtable            memberlist;
+      Enumeration          enm;
+      Object               member;
+      String               memberName;
+      Method               method;
+      PropertyDescriptor   desc;
+      boolean              primitive;
+      int                  array;
+      int                  i;
+      Object               obj;
+      String               tmpStr;
+
+      node = null;
+      
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+
+      // special handling of null-objects
+      if (o == null) {
+        node = addElement(parent, name, "" + null, false, 0, true);
+        return node;
+      }
+      
+      // used for overriding the classname
+      obj = null;
+      
+      // get information about object
+      array = 0;
+      if (o.getClass().isArray())
+        array = Utils.getArrayDimensions(o);
+      if (array > 0) {
+        classname = Utils.getArrayClass(o.getClass()).getName();
+        primitive = isPrimitiveArray(o.getClass()); 
+      }
+      else {
+         // try to get property descriptor to determine real class
+         // (for primitives the getClass() method returns the corresponding Object-Class!)
+         desc = null;
+         if (parent != null)
+            desc = determineDescriptor(parent.getAttribute(ATT_CLASS), name);
+         
+         if (desc != null)
+            primitive = desc.getPropertyType().isPrimitive(); 
+         else
+            primitive = o.getClass().isPrimitive(); 
+
+         // for primitives: retrieve primitive type, otherwise the object's real 
+         // class. For non-primitives we can't use the descriptor, since that
+         // might only return an interface as class!
+         if (primitive) {
+            classname = desc.getPropertyType().getName();
+         }
+         else {
+            obj       = o;
+            classname = o.getClass().getName();
+         }
+      }
+      
+      // fix class/primitive if parent is array of primitives, thanks to 
+      // reflection the elements of the array are objects and not primitives!
+      if (    (parent != null) 
+           && (!parent.getAttribute(ATT_ARRAY).equals(""))
+           && (!parent.getAttribute(ATT_ARRAY).equals(VAL_NO))
+           && (stringToBoolean(parent.getAttribute(ATT_PRIMITIVE))) ) {
+         primitive = true;
+         classname = parent.getAttribute(ATT_CLASS);
+         obj       = null;
+      }
+
+      // perhaps we need to override the classname
+      if (obj != null)
+        classname = overrideClassname(obj);         // for non-arrays
+      else
+        classname = overrideClassname(classname);   // for arrays
+      
+      // create node for current object
+      node = addElement(parent, name, classname, primitive, array);
+      
+      // array? -> save as child with 'name="<index>"'
+      if (array > 0) {
+         for (i = 0; i < Array.getLength(o); i++) {
+            invokeWriteToXML(node, Array.get(o, i), Integer.toString(i));
+         }
+      }
+      // non-array
+      else {
+         // primitive? -> only toString()
+         if (primitive) {
+            node.appendChild(node.getOwnerDocument().createTextNode(o.toString()));
+         }
+         // object
+         else {
+            // process recursively members of this object 
+            memberlist = getDescriptors(o);
+            // if no get/set methods -> we assume it has String-Constructor
+            if (memberlist.size() == 0) {
+              if (!o.toString().equals("")) {
+        	tmpStr = o.toString();
+        	// these five entities are recognized by every XML processor
+        	// see http://www.xml.com/pub/a/2001/03/14/trxml10.html
+        	tmpStr = tmpStr.replaceAll("&", "&amp;")
+        	               .replaceAll("\"", "&quot;")
+        	               .replaceAll("'", "&apos;")
+        	               .replaceAll("<", "&lt;")
+        	               .replaceAll(">", "&gt;");
+        	// in addition, replace some other entities as well
+        	tmpStr = tmpStr.replaceAll("\n", "&#10;")
+        	               .replaceAll("\r", "&#13;")
+        	               .replaceAll("\t", "&#9;");
+        	node.appendChild(node.getOwnerDocument().createTextNode(tmpStr));
+              }
+            }
+            else {
+               enm = memberlist.keys();
+               while (enm.hasMoreElements()) {
+                  memberName = enm.nextElement().toString();
+                  
+                  // in ignore list?
+                  if (    (m_Properties.isIgnored(memberName))
+                       || (m_Properties.isIgnored(getPath(node) + "." + memberName))
+                       || (m_Properties.isIgnored(o, getPath(node) + "." + memberName)) )
+                     continue;
+
+                  // is it allowed?
+                  if (!m_Properties.isAllowed(o, memberName))
+                     continue;
+                  
+                  desc   = (PropertyDescriptor) memberlist.get(memberName);
+                  method = desc.getReadMethod();
+                  member = method.invoke(o, (Object[]) null);
+                  invokeWriteToXML(node, member, memberName);
+               }
+            }
+         }
+      }
+      
+      return node;
+   }
+   
+   /**
+    * either invokes a custom method to write a specific property/class or the standard
+    * method <code>writeToXML(Element,Object,String)</code>
+    * 
+    * @param parent the parent XML node
+    * @param o the object's content will be added as children to the given parent node
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if invocation or turning into XML fails
+    */
+   protected Element invokeWriteToXML(Element parent, Object o, String name) throws Exception {
+      Method         method;
+      Class[]        methodClasses;
+      Object[]       methodArgs;
+      boolean        array;
+      Element        node;
+      boolean        useDefault;
+      
+      node       = null;
+      method     = null;
+      useDefault = false;
+
+      m_CurrentNode = parent;
+      
+      // default, if null
+      if (o == null)
+         useDefault = true;
+      
+      try {
+         if (!useDefault) {
+            array = o.getClass().isArray();
+           
+            // display name?
+            if (m_CustomMethods.write().contains(name))
+               method = (Method) m_CustomMethods.write().get(o.getClass());
+            else
+            // class?
+            if ( (!array) && (m_CustomMethods.write().contains(o.getClass())) )
+               method = (Method) m_CustomMethods.write().get(o.getClass());
+            else
+               method = null;
+            
+            useDefault = (method == null);
+         }
+
+         // custom
+         if (!useDefault) {
+             methodClasses    = new Class[3];
+             methodClasses[0] = Element.class;
+             methodClasses[1] = Object.class;
+             methodClasses[2] = String.class;
+             methodArgs       = new Object[3];
+             methodArgs[0]    = parent;
+             methodArgs[1]    = o;
+             methodArgs[2]    = name;
+             node = (Element) method.invoke(this, methodArgs);
+         }
+         // standard
+         else {
+            node = writeToXML(parent, o, name);
+         }
+      }
+      catch (Exception e) {
+         if (DEBUG)
+            e.printStackTrace();
+         
+         if (m_CurrentNode != null) {
+           System.out.println("Happened near: " + getPath(m_CurrentNode));
+           // print it only once!
+           m_CurrentNode = null;
+         }
+         System.out.println("PROBLEM (write): " + name);
+
+         throw (Exception) e.fillInStackTrace();
+      }
+      
+      return node;
+   }
+   
+   /**
+    * enables derived classes to due some pre-processing on the objects, that's
+    * about to be serialized. Right now it only returns the object.
+    * 
+    * @param o the object that is serialized into XML
+    * @return the possibly altered object
+    * @throws Exception if post-processing fails
+    */
+   protected Object writePreProcess(Object o) throws Exception {
+     return o;
+   }
+   
+   /**
+    * enables derived classes to add other properties to the DOM tree, e.g.
+    * ones that do not apply to the get/set convention of beans. only implemented
+    * with empty method body.
+    * 
+    * @param o the object that is serialized into XML
+    * @throws Exception if post-processing fails
+    */
+   protected void writePostProcess(Object o) throws Exception {
+   }
+   
+   /**
+    * extracts all accesible properties from the given object
+    * 
+    * @param o the object to turn into an XML representation
+    * @return the generated DOM document 
+    * @throws Exception if XML generation fails 
+    */
+   public XMLDocument toXML(Object o) throws Exception {
+      clear();
+      invokeWriteToXML(null, writePreProcess(o), VAL_ROOT);
+      writePostProcess(o);
+      return m_Document;
+   }
+   
+   /**
+    * returns a descriptor for a given objet by providing the name
+    * 
+    * @param o the object the get the descriptor for
+    * @param name the display name of the descriptor
+    * @return the Descriptor, if found, otherwise <code>null</code>
+    * @throws Exception if introsepction fails 
+    */
+   protected PropertyDescriptor getDescriptorByName(Object o, String name) throws Exception {
+      PropertyDescriptor      result;
+      PropertyDescriptor[]    desc;
+      int                     i;
+      
+      result = null;
+      
+      desc   = Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors();
+      for (i = 0; i < desc.length; i++) {
+         if (desc[i].getDisplayName().equals(name)) {
+            result = desc[i];
+            break;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * returns the associated class for the given name
+    * 
+    * @param name the name of the class to return a Class object for
+    * @return the class if  it could be retrieved
+    * @throws Exception if it class retrieval fails
+    */
+   protected Class determineClass(String name) throws Exception {
+      Class       result;
+      
+      if (name.equals(Boolean.TYPE.getName()))
+         result = Boolean.TYPE;
+      else
+      if (name.equals(Byte.TYPE.getName()))
+         result = Byte.TYPE;
+      else
+      if (name.equals(Character.TYPE.getName()))
+         result = Character.TYPE;
+      else
+      if (name.equals(Double.TYPE.getName()))
+         result = Double.TYPE;
+      else
+      if (name.equals(Float.TYPE.getName()))
+         result = Float.TYPE;
+      else
+      if (name.equals(Integer.TYPE.getName()))
+         result = Integer.TYPE;
+      else
+      if (name.equals(Long.TYPE.getName()))
+         result = Long.TYPE;
+      else
+      if (name.equals(Short.TYPE.getName()))
+         result = Short.TYPE;
+      else
+         result = Class.forName(name);
+      
+      return result;
+   }
+   
+   /**
+    * returns an Object representing the primitive described by the given node.
+    * Here we use a trick to return an object even though its a primitive: by 
+    * creating a primitive array with reflection of length 1, setting the 
+    * primtive value as real object and then returning the "object" at 
+    * position 1 of the array.
+    * 
+    * @param node the node to return the value as "primitive" object
+    * @return the primitive as "pseudo" object
+    * @throws Exception if the instantiation of the array fails or any of the
+    *         String conversions fails
+    */
+   protected Object getPrimitive(Element node) throws Exception {
+      Object            result;
+      Object            tmpResult;
+      Class             cls;
+      
+      cls       = determineClass(node.getAttribute(ATT_CLASS));
+      tmpResult = Array.newInstance(cls, 1);
+      
+      if (cls == Boolean.TYPE)
+         Array.set(tmpResult, 0, new Boolean(XMLDocument.getContent(node)));
+      else
+      if (cls == Byte.TYPE)
+         Array.set(tmpResult, 0, new Byte(XMLDocument.getContent(node)));
+      else
+      if (cls == Character.TYPE)
+         Array.set(tmpResult, 0, new Character(XMLDocument.getContent(node).charAt(0)));
+      else
+      if (cls == Double.TYPE)
+         Array.set(tmpResult, 0, new Double(XMLDocument.getContent(node)));
+      else
+      if (cls == Float.TYPE)
+         Array.set(tmpResult, 0, new Float(XMLDocument.getContent(node)));
+      else
+      if (cls == Integer.TYPE)
+         Array.set(tmpResult, 0, new Integer(XMLDocument.getContent(node)));
+      else
+      if (cls == Long.TYPE)
+         Array.set(tmpResult, 0, new Long(XMLDocument.getContent(node)));
+      else
+      if (cls == Short.TYPE)
+         Array.set(tmpResult, 0, new Short(XMLDocument.getContent(node)));
+      else
+         throw new Exception("Cannot get primitive for class '" + cls.getName() + "'!");
+      
+      result = Array.get(tmpResult, 0);
+      
+      return result;
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public boolean readBooleanFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Boolean) getPrimitive(node)).booleanValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public byte readByteFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Byte) getPrimitive(node)).byteValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public char readCharFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Character) getPrimitive(node)).charValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public double readDoubleFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Double) getPrimitive(node)).doubleValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public float readFloatFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Float) getPrimitive(node)).floatValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public int readIntFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Integer) getPrimitive(node)).intValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public long readLongFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Long) getPrimitive(node)).longValue();
+   }
+   
+   /**
+    * builds the primitive from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the primitive created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public short readShortFromXML(Element node) throws Exception {
+     // for debugging only
+     if (DEBUG)
+        trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+     m_CurrentNode = node;
+     
+     return ((Short) getPrimitive(node)).shortValue();
+   }
+   
+   /**
+    * adds the specific node to the object via a set method
+    * 
+    * @param o            the object to set a property
+    * @param name         the name of the object for which to set a property
+    *                     (only for information reasons)
+    * @param child        the value of the property to add
+    * @return             the provided object, but augmented by the child
+    * @throws Exception   if something goes wrong
+    */
+   public Object readFromXML(Object o, String name, Element child) throws Exception {
+      Object               result;
+      Hashtable            descriptors;
+      PropertyDescriptor   descriptor;
+      String               methodName;
+      Method               method;
+      Object[]             methodArgs;
+      Object               tmpResult;
+      Class                paramClass;
+     
+      result      = o;
+      descriptors = getDescriptors(result);
+      methodName  = child.getAttribute(ATT_NAME);
+
+      // in ignore list?
+      if (m_Properties.isIgnored(getPath(child)))
+         return result;
+      
+      // in ignore list of class?
+      if (m_Properties.isIgnored(result, getPath(child)))
+        return result;
+      
+      // is it allowed?
+      if (!m_Properties.isAllowed(result, methodName))
+        return result;
+      
+      descriptor = (PropertyDescriptor) descriptors.get(methodName);
+
+      // unknown property?
+      if (descriptor == null) {
+         if (!m_CustomMethods.read().contains(methodName))
+            System.out.println("WARNING: unknown property '" + name + "." + methodName + "'!");
+         return result;
+      }
+      
+      method     = descriptor.getWriteMethod();
+      methodArgs = new Object[1];
+      tmpResult  = invokeReadFromXML(child);
+      paramClass = method.getParameterTypes()[0];
+      
+      // array?
+      if (paramClass.isArray()) {
+         // no data?
+         if (Array.getLength(tmpResult) == 0)
+           return result;
+         methodArgs[0] = (Object[]) tmpResult;
+      }
+      // non-array
+      else {
+         methodArgs[0] = tmpResult;
+      }
+
+      method.invoke(result, methodArgs);
+     
+      return result;
+   }
+   
+   /**
+    * returns an array with the dimensions of the array stored in XML
+    * @param node the node to determine the dimensions for
+    * @return the dimensions of the array
+    */
+   protected int[] getArrayDimensions(Element node) {
+     Vector<Element>         children;
+     Vector<Integer>         tmpVector;      
+     int[]          tmp;
+     int[]          result;
+     int            i;
+     
+     // have we reached the innermost dimension?
+     if (stringToBoolean(node.getAttribute(ATT_ARRAY)))
+       children = XMLDocument.getChildTags(node);
+     else
+       children = null;
+     
+     if (children != null) {
+       tmpVector = new Vector<Integer>();
+
+       if (children.size() > 0) {
+         // are children also arrays?
+         tmp = getArrayDimensions((Element) children.get(0));
+         
+         // further dimensions
+         if (tmp != null) {
+           for (i = tmp.length - 1; i >= 0; i--)
+             tmpVector.add(new Integer(tmp[i]));
+         }
+  
+         // add current dimension
+         tmpVector.add(0, new Integer(children.size()));
+       }
+       else {
+         tmpVector.add(new Integer(0));
+       }
+       
+       // generate result
+       result = new int[tmpVector.size()];
+       for (i = 0; i < result.length; i++)
+         result[i] = ((Integer) tmpVector.get(tmpVector.size() - i - 1)).intValue();
+     }
+     else {
+       result = null;
+     }
+     
+     return result;
+   }
+   
+   /**
+    * builds the object from the given DOM node. 
+    * (only public due to reflection) 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    */
+   public Object readFromXML(Element node) throws Exception {
+      String               classname;
+      String               name;
+      boolean              primitive;
+      boolean              array;
+      boolean              isnull;
+      Class<?>                cls;
+      Vector<Element>               children;
+      Object               result;
+      int                  i;
+      Constructor          constructor;
+      Class[]              methodClasses;
+      Object[]             methodArgs;
+      Element              child;
+           
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      result    = null;
+      
+      name      = node.getAttribute(ATT_NAME);
+      classname = node.getAttribute(ATT_CLASS);
+      primitive = stringToBoolean(node.getAttribute(ATT_PRIMITIVE));
+      array     = stringToBoolean(node.getAttribute(ATT_ARRAY));
+      isnull    = stringToBoolean(node.getAttribute(ATT_NULL));
+
+      // special handling of null
+      if (isnull)
+        return result;
+
+      children  = XMLDocument.getChildTags(node);
+      cls       = determineClass(classname);
+      
+      // array
+      if (array) {
+         result = Array.newInstance(cls, getArrayDimensions(node));
+         for (i = 0; i < children.size(); i++) {
+            child = (Element) children.get(i);
+            Array.set(result, Integer.parseInt(child.getAttribute(ATT_NAME)), invokeReadFromXML(child));
+         }
+      }
+      // non-array
+      else {
+         // primitive/String-constructor
+         if (children.size() == 0) {
+            // primitive
+            if (primitive) {
+               result = getPrimitive(node);
+            }
+            // assumed String-constructor
+            else {
+               methodClasses    = new Class[1];
+               methodClasses[0] = String.class;
+               methodArgs       = new Object[1];
+               methodArgs[0]    = XMLDocument.getContent(node);
+               try {
+                  constructor   = cls.getConstructor(methodClasses);
+                  result        = constructor.newInstance(methodArgs);
+               }
+               catch (Exception e) {
+                  // if it's not a class with String constructor, let's try standard constructor
+                  try {
+                     result = cls.newInstance();
+                  }
+                  catch (Exception e2) {
+                     // sorry, can't instantiate!
+                     result = null;
+                     System.out.println("ERROR: Can't instantiate '" + classname + "'!");
+                  }
+               }
+            }
+         }
+         // normal get/set methods
+         else {
+            result = cls.newInstance();
+            for (i = 0; i < children.size(); i++)
+              result = readFromXML(result, name, (Element) children.get(i));
+         }
+      }
+            
+      return result;
+   }
+   
+   /**
+    * either invokes a custom method to read a specific property/class or the standard
+    * method <code>readFromXML(Element)</code>
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails
+    */
+   protected Object invokeReadFromXML(Element node) throws Exception {
+      Method         method;
+      Class[]        methodClasses;
+      Object[]       methodArgs;
+      boolean        array;
+      boolean        useDefault;
+
+      useDefault = false;
+      method     = null;
+      m_CurrentNode = node;
+      
+      try {
+         // special handling of null values
+         if (stringToBoolean(node.getAttribute(ATT_NULL)))
+           useDefault = true;
+        
+         if (!useDefault) {
+            array = stringToBoolean(node.getAttribute(ATT_ARRAY));
+           
+            // display name?
+            if (m_CustomMethods.read().contains(node.getAttribute(ATT_NAME)))
+               method = (Method) m_CustomMethods.read().get(node.getAttribute(ATT_NAME));
+            else
+            // class name?
+            if ( (!array) && (m_CustomMethods.read().contains(determineClass(node.getAttribute(ATT_CLASS)))) )
+               method = (Method) m_CustomMethods.read().get(determineClass(node.getAttribute(ATT_CLASS)));
+            else
+               method = null;
+            
+            useDefault = (method == null);
+         }
+
+         // custom method
+         if (!useDefault) {
+            methodClasses    = new Class[1];
+            methodClasses[0] = Element.class;
+            methodArgs       = new Object[1];
+            methodArgs[0]    = node;
+            return method.invoke(this, methodArgs);
+         }
+         // standard
+         else {
+            return readFromXML(node);
+         }
+      }
+      catch (Exception e) {
+         if (DEBUG)
+            e.printStackTrace();
+         
+         if (m_CurrentNode != null) {
+           System.out.println("Happened near: " + getPath(m_CurrentNode));
+           // print it only once!
+           m_CurrentNode = null;
+         }
+         System.out.println("PROBLEM (read): " + node.getAttribute("name"));
+
+         throw (Exception) e.fillInStackTrace();
+      }
+   }
+   
+   /**
+    * additional pre-processing can happen in derived classes before the 
+    * actual reading from XML (working on the raw XML). right now it does 
+    * nothing with the document.
+    * 
+    * @param document 	the document to pre-process
+    * @return the processed object
+    * @throws Exception if post-processing fails
+    */
+   protected Document readPreProcess(Document document) throws Exception {
+      return document;
+   }
+   
+   /**
+    * additional post-processing can happen in derived classes after reading 
+    * from XML. right now it only returns the object as it is.
+    * 
+    * @param o the object to perform some additional processing on
+    * @return the processed object
+    * @throws Exception if post-processing fails
+    */
+   protected Object readPostProcess(Object o) throws Exception {
+      return o;
+   }
+
+   /**
+    * returns the given DOM document as an instance of the specified class
+    * 
+    * @param document the parsed DOM document representing the object
+    * @return the XML as object 
+    * @throws Exception if object instantiation fails
+    */
+   public Object fromXML(Document document) throws Exception {
+      if (!document.getDocumentElement().getNodeName().equals(ROOT_NODE))
+         throw new Exception("Expected '" + ROOT_NODE + "' as root element, but found '" + document.getDocumentElement().getNodeName() + "'!");
+      m_Document.setDocument(readPreProcess(document));
+      checkVersion();
+      return readPostProcess(invokeReadFromXML(m_Document.getDocument().getDocumentElement()));
+   }
+   
+   /**
+    * parses the given XML string (can be XML or a filename) and returns an
+    * Object generated from the representation
+    * 
+    * @param xml the xml to parse (if "<?xml" is not found then it is considered a file)
+    * @return the generated instance
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public Object read(String xml) throws Exception {
+      return fromXML(m_Document.read(xml));
+   }
+   
+   /**
+    * parses the given file and returns a DOM document
+    * 
+    * @param file the XML file to parse
+    * @return the parsed DOM document
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public Object read(File file) throws Exception {
+      return fromXML(m_Document.read(file));
+   }
+   
+   /**
+    * parses the given stream and returns a DOM document
+    * 
+    * @param stream the XML stream to parse
+    * @return the parsed DOM document
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public Object read(InputStream stream) throws Exception {
+      return fromXML(m_Document.read(stream));
+   }
+   
+   /**
+    * parses the given reader and returns a DOM document
+    * 
+    * @param reader the XML reader to parse
+    * @return the parsed DOM document
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public Object read(Reader reader) throws Exception {
+      return fromXML(m_Document.read(reader));
+   }
+   
+   
+   /**
+    * writes the given object into the file
+    * 
+    * @param file the filename to write to
+    * @param o the object to serialize as XML
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public void write(String file, Object o) throws Exception {
+      toXML(o).write(file);
+   }
+   
+   /**
+    * writes the given object into the file
+    * 
+    * @param file the filename to write to
+    * @param o the object to serialize as XML
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public void write(File file, Object o) throws Exception {
+      toXML(o).write(file);
+   }
+   
+   /**
+    * writes the given object into the stream
+    * 
+    * @param stream the filename to write to
+    * @param o the object to serialize as XML
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public void write(OutputStream stream, Object o) throws Exception {
+      toXML(o).write(stream);
+   }
+   
+   /**
+    * writes the given object into the writer
+    * 
+    * @param writer the filename to write to
+    * @param o the object to serialize as XML
+    * @throws Exception if something goes wrong with the parsing
+    */
+   public void write(Writer writer, Object o) throws Exception {
+      toXML(o).write(writer);
+   }
+   
+   /**
+    * for testing only. if the first argument is a filename with ".xml"
+    * as extension it tries to generate an instance from the XML description
+    * and does a <code>toString()</code> of the generated object.
+    */
+   public static void main(String[] args) throws Exception {
+      if (args.length > 0) {
+         // read xml and print
+         if (args[0].toLowerCase().endsWith(".xml")) {
+            System.out.println(new XMLSerialization().read(args[0]).toString());
+         }
+         // read binary and print generated XML
+         else {
+            // read
+            FileInputStream fi = new FileInputStream(args[0]);
+            ObjectInputStream oi = new ObjectInputStream(
+                                   new BufferedInputStream(fi));
+            Object o = oi.readObject();
+            oi.close();
+            // print to stdout
+            //new XMLSerialization().write(System.out, o);
+            new XMLSerialization().write(new BufferedOutputStream(new FileOutputStream(args[0] + ".xml")), o);
+            // print to binary file
+            FileOutputStream fo = new FileOutputStream(args[0] + ".exp");
+            ObjectOutputStream oo = new ObjectOutputStream(
+                                   new BufferedOutputStream(fo));
+            oo.writeObject(o);
+            oo.close();
+         }
+      }
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XMLSerializationMethodHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XMLSerializationMethodHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XMLSerializationMethodHandler.java	(revision 29)
@@ -0,0 +1,276 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLSerializationMethodHandler.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.lang.reflect.Method;
+
+import org.w3c.dom.Element;
+
+
+/**
+ * This class handles relationships between display names of properties 
+ * (or classes) and Methods that are associated with them. It differentiates 
+ * between read and write methods. It automatically stores public methods that 
+ * have the same signature as the <code>readFromXML()</code> and 
+ * <code>writeToXML()</code> methods in the <code>XMLSerialization</code>
+ * class.  
+ *  
+ * @see MethodHandler
+ * @see XMLSerialization
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5953 $ 
+ */
+public class XMLSerializationMethodHandler
+   implements RevisionHandler {
+  
+   /** for storing read methods */
+   protected MethodHandler m_ReadMethods = null;
+   
+   /** for storing write methods */
+   protected MethodHandler m_WriteMethods = null;
+   
+   /** the object to retrieve the methods from */
+   protected Object owner = null;
+   
+   /**
+    * initializes the method handling, executes also <code>clear()</code>, which
+    * adds initial methods automatically.
+    * 
+    * @param owner the owner to retrieve the methods from
+    * @throws Exception if initialization fails
+    * @see #clear() 
+    */
+   public XMLSerializationMethodHandler(Object owner) throws Exception {
+      super();
+      
+      this.owner     = owner;
+      m_ReadMethods  = new MethodHandler();
+      m_WriteMethods = new MethodHandler();
+      
+      clear();
+   }
+   
+   /**
+    * adds all methods that are like <code>template</code> to the method list
+    * 
+    * @param handler the list to add fitting methods to
+    * @param template the signature to check the given methods against
+    * @param methods the methods to check
+    */
+   protected void addMethods(MethodHandler handler, Method template, Method[] methods) {
+      int            i;
+      int            n;
+      Method         method;
+      boolean        equal;
+      String         name;
+      
+      for (i = 0; i < methods.length; i++) {
+         method = methods[i];
+         
+         // is it template?
+         if (template.equals(method))
+            continue;
+         
+         // tests
+         // 1. return type
+         if (!template.getReturnType().equals(method.getReturnType()))
+            continue;
+            
+         // 2. signature
+         if (template.getParameterTypes().length != method.getParameterTypes().length)
+            continue;
+         
+         equal = true;
+         for (n = 0; n < template.getParameterTypes().length; n++) {
+            if (!template.getParameterTypes()[n].equals(method.getParameterTypes()[n])) {
+               equal = false;
+               break;
+            }
+         }
+            
+         // add to list
+         if (equal) {
+            name = method.getName();
+            name = name.replaceAll("read|write", "");
+            name = name.substring(0, 1).toLowerCase() + name.substring(1);
+            handler.add(name, method);
+         }
+      }
+   }
+   
+   /**
+    * automatically adds all fitting methods to the custom read/write lists,
+    * it excludes only the generic ones. it is automatically called in 
+    * <code>clear()</code>
+    * It only work with methods that apply to the naming rule "read" + property
+    * name (same for "write")
+    *
+    * @throws Exception if retrieving of methods fails
+    * @see #clear()
+    */
+   protected void addMethods() throws Exception {
+      Method         method;
+      Class[]        params;
+      
+      // read
+      params    = new Class[1];
+      params[0] = Element.class;
+      method    = owner.getClass().getMethod("readFromXML", params);
+      addMethods(m_ReadMethods, method, owner.getClass().getMethods());
+      
+      // write
+      params    = new Class[3];
+      params[0] = Element.class;
+      params[1] = Object.class;
+      params[2] = String.class;
+      method    = owner.getClass().getMethod("writeToXML", params);
+      addMethods(m_WriteMethods, method, owner.getClass().getMethods());
+   }
+   
+   /**
+    * returns the method with the given name that has the same signature as
+    * <code>readFromXML()</code> of the <code>XMLSerialiation</code> class. 
+    * simplifies the adding of custom methods.
+    * 
+    * @param o the object to inspect
+    * @param name the name of the method to return
+    * @return either <code>null</code> if no method was found or a reference 
+    * @see XMLSerialization#readFromXML(Element)
+    */
+   public static Method findReadMethod(Object o, String name) {
+      Class[]        params;
+      Method         result;
+      
+      result    = null;
+      
+      params    = new Class[1];
+      params[0] = Element.class; 
+      try {
+         result = o.getClass().getMethod(name, params);
+      }
+      catch (Exception e) {
+         result = null;  
+      }
+      
+      return result;
+   }
+   
+   /**
+    * returns the method with the given name that has the same signature as
+    * <code>writeToXML()</code> of the <code>XMLSerialiation</code> class. 
+    * simplifies the adding of custom methods.
+    * 
+    * @param o the object to inspect
+    * @param name the name of the method to return
+    * @return either <code>null</code> if no method was found or a reference 
+    * @see XMLSerialization#writeToXML(Element, Object, String)
+    */
+   public static Method findWriteMethod(Object o, String name) {
+      Class[]        params;
+      Method         result;
+      
+      result    = null;
+      
+      params    = new Class[3];
+      params[0] = Element.class; 
+      params[1] = Object.class; 
+      params[2] = String.class; 
+      try {
+         result = o.getClass().getMethod(name, params);
+      }
+      catch (Exception e) {
+         result = null;  
+      }
+      
+      return result;
+   }
+   
+   /**
+    * removes all current methods and adds the methods according to the 
+    *  
+    */
+   public void clear() {
+      m_ReadMethods.clear();
+      m_WriteMethods.clear();
+      
+      try {
+         addMethods();
+      }
+      catch (Exception e) {
+         e.printStackTrace();
+      }
+   }
+   
+   /**
+    * returns the handler for read methods
+    * 
+    * @return the methodhandler for read methods
+    */
+   public MethodHandler read() {
+      return m_ReadMethods;
+   }
+   
+   /**
+    * returns the handler for write methods
+    * 
+    * @return the methodhandler for read methods
+    */
+   public MethodHandler write() {
+      return m_WriteMethods;
+   }
+   
+   /**
+    * adds read and write methods for the given class, i.e., read&;lt;name&gt;
+    * and write&lt;name&gt; ("name" is prefixed by read and write)
+    * 
+    * @param handler  the handler class that contains the read and write method
+    * @param cls      the class to register the read and write method for
+    * @param name     the suffix of the read and write method
+    */
+   public void register(Object handler, Class cls, String name) {
+     read().add(cls, XMLSerializationMethodHandler.findReadMethod(handler, "read" + name));
+     write().add(cls, XMLSerializationMethodHandler.findWriteMethod(handler, "write" + name));
+   }
+   
+   /**
+    * returns the read and write method handlers as string
+    * 
+    * @return the read/write method handlers as string 
+    */
+   public String toString() {
+      return "Read Methods:\n" + read() + "\n\n" + "Write Methods:\n" + write();
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5953 $");
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/core/xml/XStream.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/core/xml/XStream.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/core/xml/XStream.java	(revision 29)
@@ -0,0 +1,350 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XStream.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.xml;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * This class is a helper class for XML serialization using 
+ * <a href="http://xstream.codehaus.org" target="_blank">XStream</a> .
+ * XStream does not need to be present, since the class-calls are done generically via Reflection.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5987 $
+ */
+public class XStream
+  implements RevisionHandler {
+
+  /**
+   * indicates whether <a href="http://xstream.codehaus.org" target="_blank">XStream</a> 
+   * is present
+   */
+  protected static boolean m_Present = false;
+
+  /** the extension for XStream files (including '.') */
+  public final static String FILE_EXTENSION = ".xstream";
+   
+  /** check for XStream statically (needs only to be done once) */
+  static {
+    checkForXStream();
+  }
+
+  /**
+   * checks whether the XStream is present in the class path
+   */
+  private static void checkForXStream() {
+    try {
+      Class.forName("com.thoughtworks.xstream.XStream");
+      m_Present = true;
+    }
+    catch (Exception e) {
+      m_Present = false;
+    }
+  }
+  
+  /**
+   * returns whether XStream is present or not, i.e. whether the classes are in the
+   * classpath or not
+   *
+   * @return whether XStream is available
+   */
+  public static boolean isPresent() {
+    return m_Present;
+  }
+ 
+  /**
+   * Serializes the supplied object xml
+   *
+   * @param toSerialize the object to serialize
+   * @return the serialized object as an XML string
+   * @throws Exception if something goes wrong
+   */
+  public static String serialize(Object toSerialize) throws Exception {
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] serializeArgsClasses = new Class[1];
+    Object [] serializeArgs = new Object[1];
+    java.lang.reflect.Method methodSerialize;
+    String result;
+    
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    serializeArgsClasses[0] = Object.class;
+    serializeArgs[0] = toSerialize;
+    methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);
+    
+    // execute it
+    try {
+      result = (String)methodSerialize.invoke(xstream, serializeArgs);
+    } catch (Exception ex) {
+      result = null;
+    }
+
+    return result;
+  }
+
+ /**
+   * writes the XML-serialized object to the given file
+   * @param filename the file to serialize the object to
+   * @param o the object to write to the file
+   * @return whether writing was successful or not
+   * @throws Exception if something goes wrong while writing to the file
+   */
+  public static boolean write(String filename, Object o) throws Exception {
+    return write(new File(filename), o);
+  }
+
+  /**
+   * write the XML-serialized object to the given file
+   * @param file the file to serialize the object to
+   * @param o the object to write to the file
+   * @return whether writing was successful or not
+   * @throws Exception if something goes wrong while writing to the file
+   */
+  public static boolean write(File file, Object o) throws Exception {
+    return write(new BufferedOutputStream(new FileOutputStream(file)), o);
+  }
+
+  /**
+   * writes the XML-serialized object to the given output stream
+   *
+   * @param stream the output stream
+   * @param o the object to write
+   * @return true if everything goes ok
+   */
+  public static boolean write(OutputStream stream, Object o) throws Exception {
+
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] serializeArgsClasses = new Class[2];
+    Object [] serializeArgs = new Object[2];
+    java.lang.reflect.Method methodSerialize;
+    boolean result = false;
+    
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    serializeArgsClasses[0] = Object.class;
+    serializeArgsClasses[1] = OutputStream.class;
+    serializeArgs[0] = o;
+    serializeArgs[1] = stream;
+    methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);
+    
+    // execute it
+    try {
+      methodSerialize.invoke(xstream, serializeArgs);
+      result = true;
+    } catch (Exception ex) {
+      result = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * writes the XML-serialized object to the given Writer.
+   *
+   * @param writer the Writer
+   * @param toSerialize the object to write
+   * @return true if everything goes ok
+   * @throws Exception if something goes wrong
+   */
+  public static boolean write(Writer writer, Object toSerialize) throws Exception {
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] serializeArgsClasses = new Class[2];
+    Object [] serializeArgs = new Object[2];
+    java.lang.reflect.Method methodSerialize;
+    boolean result = false;
+    
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    serializeArgsClasses[0] = Object.class;
+    serializeArgsClasses[1] = Writer.class;
+    serializeArgs[0] = toSerialize;
+    serializeArgs[1] = writer;
+    methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);
+    
+    // execute it
+    try {
+      methodSerialize.invoke(xstream, serializeArgs);
+      result = true;
+    } catch (Exception ex) {
+      result = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * reads the XML-serialized object from the given file
+   * @param filename the file to deserialize the object from
+   * @return the deserialized object
+   * @throws Exception if something goes wrong while reading from the file
+   */
+  public static Object read(String filename) throws Exception {
+    return read(new File(filename));
+  }
+  
+  /**
+   * reads the XML-serialized object from the given file
+   * @param file the file to deserialize the object from
+   * @return the deserialized object
+   * @throws Exception if something goes wrong while reading from the file
+   */
+  public static Object read(File file) throws Exception {
+    return read(new BufferedInputStream(new FileInputStream(file)));
+  }
+
+  /**
+   * reads the XML-serialized object from the given input stream
+   *
+   * @param stream the input stream
+   * @return the deserialized object
+   * @throws Exception if something goes wrong while reading from stream
+   */
+  public static Object read(InputStream stream) throws Exception {
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] deSerializeArgsClasses = new Class[1];
+    Object [] deSerializeArgs = new Object[1];
+    java.lang.reflect.Method methodDeSerialize;
+    Object result;
+
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    deSerializeArgsClasses[0] = InputStream.class;
+    deSerializeArgs[0] = stream;
+    methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);
+
+    // execute it
+    try {
+      result = methodDeSerialize.invoke(xstream, deSerializeArgs);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * reads the XML-serialized object from the given Reader
+   *
+   * @param r the reader
+   * @return the deserialized object
+   * @throws Exception if something goes wrong while reading from stream
+   */
+  public static Object read(Reader r) throws Exception {
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] deSerializeArgsClasses = new Class[1];
+    Object [] deSerializeArgs = new Object[1];
+    java.lang.reflect.Method methodDeSerialize;
+    Object result;
+
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    deSerializeArgsClasses[0] = Reader.class;
+    deSerializeArgs[0] = r;
+    methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);
+
+    // execute it
+    try {
+      result = methodDeSerialize.invoke(xstream, deSerializeArgs);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * Deserializes an object from the supplied XML string
+   * 
+   * @param xmlString the XML to deserialize from
+   * @return the deserialized object
+   * @throws Exception if something goes wrong
+   */
+  public static Object deSerialize(String xmlString) throws Exception {
+    Class<?> xstreamClass;
+    java.lang.reflect.Constructor constructor;
+    Object xstream;
+    Class [] deSerializeArgsClasses = new Class[1];
+    Object [] deSerializeArgs = new Object[1];
+    java.lang.reflect.Method methodDeSerialize;
+    Object result;
+
+    xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
+    constructor = xstreamClass.getConstructor();
+    xstream = constructor.newInstance();
+
+    deSerializeArgsClasses[0] = String.class;
+    deSerializeArgs[0] = xmlString;
+    methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);
+
+    // execute it
+    try {
+      result = methodDeSerialize.invoke(xstream, deSerializeArgs);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      result = null;
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/ClassificationGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/ClassificationGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/ClassificationGenerator.java	(revision 29)
@@ -0,0 +1,145 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClassificationGenerator.java
+ * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.Option;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ * Abstract class for data generators for classifiers. <p/>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public abstract class ClassificationGenerator 
+  extends DataGenerator {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5261662546673517844L;
+
+  /** Number of instances*/
+  protected int m_NumExamples;
+
+  /**
+   * initializes with default values
+   */
+  public ClassificationGenerator() {
+    super();
+
+    setNumExamples(defaultNumExamples());
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+    
+    result.addElement(new Option(
+        "\tThe number of examples to generate (default " 
+        + defaultNumExamples() + ")",
+        "n", 1, "-n <num>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the options.
+   *
+   * @param options the options 
+   * @throws Exception if invalid option
+   */
+  public void setOptions(String[] options) throws Exception { 
+    String        tmpStr;
+   
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('n', options);
+    if (tmpStr.length() != 0)
+      setNumExamples(Integer.parseInt(tmpStr));
+    else
+      setNumExamples(defaultNumExamples());
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-n");
+    result.add("" + getNumExamples());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of examples
+   * 
+   * @return the default number of examples
+   */
+  protected int defaultNumExamples() {
+    return 100;
+  }
+
+  /**
+   * Sets the number of examples, given by option.
+   * @param numExamples the new number of examples
+   */
+  public void setNumExamples(int numExamples) { 
+    m_NumExamples = numExamples; 
+  }
+
+  /**
+   * Gets the number of examples, given by option.
+   * @return the number of examples, given by option 
+   */
+  public int getNumExamples() { 
+    return m_NumExamples; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numExamplesTipText() {
+    return "The number of examples to generate.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/ClusterDefinition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/ClusterDefinition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/ClusterDefinition.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClusterDefinition.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+/**
+ * Ancestor to all ClusterDefinitions, i.e., subclasses that handle their
+ * own parameters that the cluster generator only passes on.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ */
+
+public abstract class ClusterDefinition
+  implements Serializable, OptionHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5950001207047429961L;
+
+  /** the parent of the cluster */
+  protected ClusterGenerator m_Parent;
+
+  /**
+   * initializes the cluster, without a parent cluster (necessary for GOE)
+   */
+  public ClusterDefinition() {
+    this(null);
+  }
+
+  /**
+   * initializes the cluster
+   *
+   * @param parent    the datagenerator this cluster belongs to
+   */
+  public ClusterDefinition(ClusterGenerator parent) {
+    m_Parent = parent;
+
+    try {
+      setDefaults();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * sets the default values
+   * 
+   * @throws Exception if setting of defaults fails
+   */
+  protected abstract void setDefaults() throws Exception;
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Contains informations about a certain cluster of a cluster generator.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public abstract Enumeration listOptions();
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   * For list of valid options see class description.<p/>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public abstract void setOptions(String[] options) throws Exception;
+
+  /**
+   * Gets the current settings of the datagenerator BIRCHCluster.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public abstract String[] getOptions();
+
+  /**
+   * returns the parent datagenerator this cluster belongs to
+   * 
+   * @return the parent this cluster belongs to
+   */
+  public ClusterGenerator getParent() {
+    return m_Parent;
+  }
+
+  /**
+   * sets the parent datagenerator this cluster belongs to
+   * 
+   * @param parent the parent datagenerator
+   */
+  public void setParent(ClusterGenerator parent) {
+    m_Parent = parent;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String parentTipText() {
+    return "The cluster generator this object belongs to.";
+  }
+
+  /**
+   * returns a string representation of the cluster
+   * 
+   * @return the cluster definition as string
+   */
+  public String toString() {
+    return this.getClass().getName() + ": " + Utils.joinOptions(getOptions());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/ClusterGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/ClusterGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/ClusterGenerator.java	(revision 29)
@@ -0,0 +1,341 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClusterGenerator.java
+ * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.Utils;
+import weka.datagenerators.DataGenerator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ * Abstract class for cluster data generators. <p/>
+ *
+ * Example usage as the main of a datagenerator called RandomGenerator:
+ * <pre>
+ * public static void main(String[] args) {
+ *   try {
+ *     DataGenerator.makeData(new RandomGenerator(), args);
+ *   } 
+ *   catch (Exception e) {
+ *     e.printStackTrace();
+ *     System.err.println(e.getMessage());
+ *   }
+ * }
+ * </pre>
+ * <p/>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.6 $
+ */
+public abstract class ClusterGenerator 
+  extends DataGenerator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6131722618472046365L;
+
+  /** Number of attribute the dataset should have */
+  protected int m_NumAttributes;
+
+  /** class flag  */
+  protected boolean m_ClassFlag = false;
+
+  /** Stores which columns are boolean (default numeric) */
+  protected Range m_booleanCols;
+
+  /** Stores which columns are nominal (default numeric)  */
+  protected Range m_nominalCols;
+
+  /**
+   * initializes the generator 
+   */
+  public ClusterGenerator() {
+    super();
+
+    setNumAttributes(defaultNumAttributes());
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+          "\tThe number of attributes (default " 
+          + defaultNumAttributes() + ").",
+          "a", 1, "-a <num>"));
+
+    result.addElement(new Option(
+        "\tClass Flag, if set, the cluster is listed in extra attribute.",
+        "c", 0, "-c"));
+    
+    result.addElement(new Option(
+        "\tThe indices for boolean attributes.",
+        "b", 1, "-b <range>"));
+    
+    result.addElement(new Option(
+        "\tThe indices for nominal attributes.",
+        "m", 1, "-m <range>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the options.
+   *
+   * @param options the options 
+   * @throws Exception if invalid option
+   */
+  public void setOptions(String[] options) throws Exception { 
+    String        tmpStr;
+   
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('a', options);
+    if (tmpStr.length() != 0)
+      setNumAttributes(Integer.parseInt(tmpStr));
+    else
+      setNumAttributes(defaultNumAttributes());
+
+    setClassFlag(Utils.getFlag('c', options));
+
+    tmpStr = Utils.getOption('b', options);
+    setBooleanIndices(tmpStr);
+    m_booleanCols.setUpper(getNumAttributes());
+
+    tmpStr = Utils.getOption('m', options);
+    setNominalIndices(tmpStr);
+    m_nominalCols.setUpper(getNumAttributes());
+
+    // check indices
+    tmpStr = checkIndices();
+    if (tmpStr.length() > 0)
+      throw new IllegalArgumentException(tmpStr);
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-a");
+    result.add("" + getNumAttributes());
+
+    if (getClassFlag())
+      result.add("-c");
+    
+    if (!getBooleanCols().toString().equalsIgnoreCase("empty")) {
+      result.add("-b");
+      result.add("" + getBooleanCols());
+    }
+    
+    if (!getNominalCols().toString().equalsIgnoreCase("empty")) {
+      result.add("-m");
+      result.add("" + getNominalCols());
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of attributes
+   * 
+   * @return the default number of attributes
+   */
+  protected int defaultNumAttributes() {
+    return 10;
+  }
+
+  /**
+   * Sets the number of attributes the dataset should have.
+   * @param numAttributes the new number of attributes
+   */
+  public void setNumAttributes(int numAttributes) {
+    m_NumAttributes = numAttributes;
+    getBooleanCols().setUpper(getNumAttributes());
+    getNominalCols().setUpper(getNumAttributes());
+  }
+
+  /**
+   * Gets the number of attributes that should be produced.
+   * @return the number of attributes that should be produced
+   */
+  public int getNumAttributes() { 
+    return m_NumAttributes; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes the generated data will contain.";
+  }
+
+  /**
+   * Sets the class flag, if class flag is set, 
+   * the cluster is listed as class atrribute in an extra attribute.
+   * @param classFlag the new class flag
+   */
+  public void setClassFlag(boolean classFlag) { 
+    m_ClassFlag = classFlag; 
+  }
+
+  /**
+   * Gets the class flag.
+   * @return the class flag 
+   */
+  public boolean getClassFlag() {
+    return m_ClassFlag; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String classFlagTipText() {
+    return "If set to TRUE, lists the cluster as an extra attribute.";
+  }
+
+  /**
+   * Sets which attributes are boolean 
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br/>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setBooleanIndices(String rangeList) {
+    m_booleanCols.setRanges(rangeList);
+  }
+
+  /**
+   * Sets which attributes are boolean.
+   * @param value the range to use
+   */
+  public void setBooleanCols(Range value) {
+    m_booleanCols.setRanges(value.getRanges());
+  }
+
+  /**
+   * returns the range of boolean attributes.
+   * 
+   * @return the range of boolean attributes
+   */
+  public Range getBooleanCols() {
+    if (m_booleanCols == null)
+      m_booleanCols = new Range();
+
+    return m_booleanCols;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String booleanColsTipText() {
+    return "The range of attributes that are generated as boolean ones.";
+  }
+
+  /**
+   * Sets which attributes are nominal
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br/>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setNominalIndices(String rangeList) {
+    m_nominalCols.setRanges(rangeList);
+  }
+
+  /**
+   * Sets which attributes are nominal.
+   * @param value the range to use
+   */
+  public void setNominalCols(Range value) {
+    m_nominalCols.setRanges(value.getRanges());
+  }
+
+  /**
+   * returns the range of nominal attributes
+   * 
+   * @return the range of nominal attributes
+   */
+  public Range getNominalCols() {
+    if (m_nominalCols == null)
+      m_nominalCols = new Range();
+
+    return m_nominalCols;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String nominalColsTipText() {
+    return "The range of attributes to generate as nominal ones.";
+  }
+
+  /**
+   * check if attribute types are not contradicting
+   * 
+   * @return empty string if no problem, otherwise error message
+   */
+  protected String checkIndices() {
+    for (int i = 1; i < getNumAttributes() + 1; i++) {
+      m_booleanCols.isInRange(i);
+      if (m_booleanCols.isInRange(i) && m_nominalCols.isInRange(i)) {
+	return   "Error in attribute type: Attribute " 
+               + i + " is set boolean and nominal.";
+      }
+    } 
+    return "";
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/datagenerators/DataGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/DataGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/DataGenerator.java	(revision 29)
@@ -0,0 +1,788 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DataGenerator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Randomizable;
+import weka.core.RevisionHandler;
+import weka.core.Utils;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ * Abstract superclass for data generators that generate data for 
+ * classifiers and clusterers.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.8 $
+ */
+public abstract class DataGenerator 
+  implements OptionHandler, Randomizable, Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3698585946221802578L;
+
+  /** Debugging mode */
+  protected boolean m_Debug = false;
+
+  /** The format for the generated dataset */
+  protected Instances m_DatasetFormat = null;
+
+  /** Relation name the dataset should have */
+  protected String m_RelationName = "";
+
+  /** Number of instances that should be produced into the dataset 
+   * this number is by default m_NumExamples,
+   * but can be reset by the generator 
+   */
+  protected int m_NumExamplesAct;
+
+  /** default output (is printed to stdout after generation) */
+  protected transient StringWriter m_DefaultOutput = new StringWriter();
+
+  /** PrintWriter for outputting the generated data */
+  protected transient PrintWriter m_Output = new PrintWriter(m_DefaultOutput);
+
+  /** random number generator seed*/ 
+  protected int m_Seed;
+
+  /** random number generator*/ 
+  protected Random m_Random = null;
+
+  /** flag, that indicates whether the relationname is currently assembled */
+  protected boolean m_CreatingRelationName = false;
+
+  /** a black list for options not to be listed (for derived generators) 
+   *  in the makeOptionString method 
+   *  @see #makeOptionString(DataGenerator) */
+  protected static HashSet m_OptionBlacklist;
+  static {
+    m_OptionBlacklist = new HashSet();
+  }
+
+  /**
+   * initializes with default settings. <br/>
+   * Note: default values are set via a default&lt;name&gt; method. These 
+   * default methods are also used in the listOptions method and in the
+   * setOptions method. Why? Derived generators can override the return value
+   * of these default methods, to avoid exceptions. 
+   */
+  public DataGenerator() {
+    clearBlacklist();
+    
+    setNumExamplesAct(defaultNumExamplesAct());
+    setSeed(defaultSeed());
+  }
+
+  /**
+   * creates a vector out of the enumeration from the listOptions of the
+   * super class. Only a "convenience" method.
+   * @param enm     the Enumeration to dump into a vector
+   * @return        the elements of the enumeration in a vector
+   */
+  protected Vector enumToVector(Enumeration enm) {
+    Vector      result;
+
+    result = new Vector();
+
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector      result;
+
+    result = new Vector();
+
+    result.addElement(new Option(
+          "\tPrints this help.",
+          "h", 1, "-h"));
+
+    result.addElement(new Option(
+          "\tThe name of the output file, otherwise the generated data is\n"
+          + "\tprinted to stdout.",
+          "o", 1, "-o <file>"));
+
+    result.addElement(new Option(
+          "\tThe name of the relation.",
+          "r", 1, "-r <name>"));
+
+    result.addElement(new Option(
+          "\tWhether to print debug informations.",
+          "d", 0, "-d"));
+
+    result.addElement(new Option(
+          "\tThe seed for random function (default " 
+          + defaultSeed() + ")",
+          "S", 1, "-S"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   * For list of valid options see class description. <p/>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    // remove unwanted options
+    options = removeBlacklist(options);
+
+    tmpStr = Utils.getOption('r', options);
+    if (tmpStr.length() != 0)
+      setRelationName(Utils.unquote(tmpStr));
+    else
+      setRelationName("");
+
+    tmpStr = Utils.getOption('o', options);
+    if (tmpStr.length() != 0)
+      setOutput(new PrintWriter(new FileOutputStream(tmpStr)));
+    else if (getOutput() == null)
+      throw new Exception("No Output defined!");
+
+    setDebug(Utils.getFlag('d', options));
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(defaultSeed());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator RDG1. Removing of 
+   * blacklisted options has to be done in the derived class, that defines
+   * the blacklist-entry.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   * @see    #removeBlacklist(String[])
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result = new Vector();
+
+    // to avoid endless loop
+    if (!m_CreatingRelationName) {
+      result.add("-r");
+      result.add(Utils.quote(getRelationNameToUse()));
+    }
+
+    if (getDebug())
+      result.add("-d");
+    
+    result.add("-S");
+    result.add("" + getSeed());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used. Also sets a default relation name in case
+   * the current relation name is empty.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see #defaultRelationName()
+   */
+  public Instances defineDataFormat() throws Exception {
+    if (getRelationName().length() == 0)
+      setRelationName(defaultRelationName());
+
+    return m_DatasetFormat;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public abstract Instance generateExample() throws Exception;
+
+  /**
+   * Generates all examples of the dataset. 
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   */
+  public abstract Instances generateExamples() throws Exception;
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentation fails
+   */
+  public abstract String generateStart () throws Exception;
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the end of the produced output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentation fails
+   */
+  public abstract String generateFinished () throws Exception;
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public abstract boolean getSingleModeFlag () throws Exception;
+
+  /**
+   * Sets the debug flag.
+   * @param debug the new debug flag
+   */
+  public void setDebug(boolean debug) { 
+    m_Debug = debug;
+  }
+
+  /**
+   * Gets the debug flag.
+   * @return the debug flag 
+   */
+  public boolean getDebug() { 
+    return m_Debug; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Whether the generator is run in debug mode or not.";
+  }
+
+  /**
+   * Sets the relation name the dataset should have.
+   * @param relationName the new relation name
+   */
+  public void setRelationName(String relationName) {
+    m_RelationName = relationName;
+  }
+
+  /**
+   * returns a relation name based on the options
+   * 
+   * @return a relation name based on the options
+   */
+  protected String defaultRelationName() {
+    StringBuffer    result;
+    String[]        options;
+    String          option;
+    int             i;
+
+    m_CreatingRelationName = true;
+
+    result = new StringBuffer(this.getClass().getName());
+
+    options = getOptions();
+    for (i = 0; i < options.length; i++) {
+      option = options[i].trim();
+      if (i > 0)
+        result.append("_");
+      result.append(option.replaceAll(" ", "_"));
+    }
+
+    m_CreatingRelationName = false;
+
+    return result.toString();
+  }
+
+  /**
+   * returns the relation name to use, i.e., in case the currently set
+   * relation name is empty, a generic one is returned. Must be used in
+   * defineDataFormat()
+   * @return the relation name
+   * @see #defaultRelationName()
+   * @see #defineDataFormat()
+   */
+  protected String getRelationNameToUse() {
+    String        result;
+
+    result = getRelationName();
+    if (result.length() == 0)
+      result = defaultRelationName();
+
+    return result;
+  }
+
+  /**
+   * Gets the relation name the dataset should have.
+   * @return the relation name the dataset should have
+   */
+  public String getRelationName() { 
+    return m_RelationName;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String relationNameTipText() {
+    return "The relation name of the generated data (if empty, a generic one will be supplied).";
+  }
+
+  /**
+   * returns the default number of actual examples
+   * 
+   * @return the default number of actual examples
+   */
+  protected int defaultNumExamplesAct() {
+    return 0;
+  }
+
+  /**
+   * Sets the number of examples the dataset should have.
+   * @param numExamplesAct the new number of examples
+   */
+  protected void setNumExamplesAct(int numExamplesAct) { 
+    m_NumExamplesAct = numExamplesAct;
+  }
+
+  /**
+   * Gets the number of examples the dataset should have.
+   * @return the number of examples the dataset should have
+   */
+  public int getNumExamplesAct() { 
+    return m_NumExamplesAct; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  protected String numExamplesActTipText() {
+    return "The actual number of examples to generate.";
+  }
+
+  /**
+   * Sets the print writer.
+   * @param newOutput the new print writer
+   */
+  public void setOutput(PrintWriter newOutput) {
+    m_Output        = newOutput;
+    m_DefaultOutput = null;
+  }
+
+  /**
+   * Gets the print writer.
+   * @return print writer object
+   */
+  public PrintWriter getOutput() { 
+    return m_Output; 
+  }
+
+  /**
+   * Gets the string writer, which is used for outputting to stdout.
+   * A workaround for the problem of closing stdout when closing the 
+   * associated Printwriter.
+   * @return print string writer object
+   */
+  public StringWriter defaultOutput() { 
+    return m_DefaultOutput; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String outputTipText() {
+    return "The output writer to use for printing the generated data.";
+  }
+
+  /**
+   * Sets the format of the dataset that is to be generated. 
+   * @param newFormat the new dataset format of the dataset 
+   */
+  public void setDatasetFormat(Instances newFormat) {
+    m_DatasetFormat = new Instances(newFormat, 0);
+  }
+
+  /**
+   * Gets the format of the dataset that is to be generated. 
+   * @return the dataset format of the dataset
+   */
+  public Instances getDatasetFormat() {
+    if (m_DatasetFormat != null)
+      return new Instances(m_DatasetFormat, 0);
+    else
+      return null;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String formatTipText() {
+    return "The data format to use.";
+  }
+
+  /**
+   * returns the default seed
+   * 
+   * @return the default seed
+   */
+  protected int defaultSeed() {
+    return 1;
+  }
+  
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getSeed() { 
+    return m_Seed; 
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setSeed(int newSeed) { 
+    m_Seed   = newSeed; 
+    m_Random = new Random(newSeed);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed value for the random number generator.";
+  }
+
+  /**
+   * Gets the random generator.
+   *
+   * @return the random generator
+   */
+  public Random getRandom() {
+    if (m_Random == null)
+      m_Random = new Random (getSeed());
+
+    return m_Random;
+  }
+  
+  /**
+   * Sets the random generator.
+   *
+   * @param newRandom is the random generator.
+   */
+  public void setRandom(Random newRandom) {
+    m_Random = newRandom;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String randomTipText() {
+    return "The random number generator to use.";
+  }
+
+  /**
+   * Returns a string representing the dataset in the instance queue.
+   * @return the string representing the output data format
+   */
+  protected String toStringFormat() {
+    if (m_DatasetFormat == null)
+      return "";
+    return 
+      m_DatasetFormat.toString();
+  }
+
+  /**
+   * removes all entries from the options blacklist
+   */
+  protected static void clearBlacklist() {
+    m_OptionBlacklist.clear();
+  }
+  
+  /**
+   * adds the given option, e.g., for "-V" use "V", to the blacklist of options
+   * that are not to be output via the makeOptionString method
+   * @param option      the option to exclude from listing
+   * @see #makeOptionString(DataGenerator)
+   */
+  protected static void addToBlacklist(String option) {
+    m_OptionBlacklist.add(option);
+  }
+
+  /**
+   * checks, whether the given option is in the blacklist of options not to
+   * be output by makeOptionString
+   * @param option      the option to check
+   * @return true if the option is on the blacklist
+   * @see #makeOptionString(DataGenerator)
+   */
+  protected static boolean isOnBlacklist(String option) {
+    return m_OptionBlacklist.contains(option);
+  }
+
+  /**
+   * removes all the options from the options array that are blacklisted
+   * 
+   * @param options the options to remove from the blacklist
+   * @return the processed options array
+   */
+  protected String[] removeBlacklist(String[] options) {
+    Enumeration     enm;
+    Hashtable       pool;
+    Option          option;
+
+    // retrieve options that are on blacklist
+    enm  = listOptions();
+    pool = new Hashtable();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      if (isOnBlacklist(option.name()))
+        pool.put(option.name(), option);
+    }
+
+    // remove options
+    enm = pool.keys();
+    while (enm.hasMoreElements()) {
+      option = (Option) pool.get(enm.nextElement());
+      try {
+        if (option.numArguments() == 0)
+          Utils.getFlag(option.name(), options);
+        else
+          Utils.getOption(option.name(), options);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+
+    return options;
+  }
+
+  /**
+   * returns all the options in a string
+   * 
+   * @param generator the DataGenerator to return all the options for
+   * @return the assembled option string
+   */
+  protected static String makeOptionString(DataGenerator generator) {
+    StringBuffer    result;
+    Enumeration     enm;
+    Option          option;
+    
+    result = new StringBuffer();
+    result.append("\nData Generator options:\n\n");
+
+    enm = generator.listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      // skip option if on blacklist
+      if (isOnBlacklist(option.name()))
+        continue;
+      result.append(option.synopsis() + "\n" + option.description() + "\n");
+    }
+
+    return result.toString();
+  }
+
+  /**
+   * Calls the data generator.
+   *
+   * @param generator one of the data generators 
+   * @param options options of the data generator
+   * @throws Exception if there was an error in the option list
+   */
+  public static void makeData(DataGenerator generator, String[] options) 
+    throws Exception {
+
+    boolean     printhelp;
+    Vector      unknown;
+    int         i;
+    
+    // help?
+    printhelp = (Utils.getFlag('h', options));
+
+    // read options
+    if (!printhelp) {
+      try {
+        options = generator.removeBlacklist(options);
+        generator.setOptions(options);
+        
+        // check for left-over options, but don't raise exception
+        unknown = new Vector();
+        for (i = 0; i < options.length; i++) {
+          if (options[i].length() != 0) 
+            unknown.add(options[i]);
+        }
+        if (unknown.size() > 0) {
+          System.out.print("Unknown options:");
+          for (i = 0; i < unknown.size(); i++)
+            System.out.print(" " + unknown.get(i));
+          System.out.println();
+        }
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+        printhelp = true;
+      }
+    }
+    
+    if (printhelp) {
+      System.out.println(makeOptionString(generator));
+      return;
+    }
+    
+    // define dataset format 
+    // computes actual number of examples to be produced
+    generator.setDatasetFormat(generator.defineDataFormat());
+
+    // get print writer
+    PrintWriter output = generator.getOutput();
+
+    // output of options
+    output.println("%");
+    output.println("% Commandline");
+    output.println("%");
+    output.println("% " + generator.getClass().getName() + " " 
+                      + Utils.joinOptions(generator.getOptions()));
+    output.println("%");
+
+    // comment at beginning of ARFF File
+    String commentAtStart = generator.generateStart();
+  
+    if (commentAtStart.length() > 0) {
+      output.println("%");
+      output.println("% Prologue");
+      output.println("%");
+      output.println(commentAtStart.trim());
+      output.println("%");
+    }
+
+    // ask data generator which mode
+    boolean singleMode = generator.getSingleModeFlag();
+
+    // start data producer
+    if (singleMode) {
+      // output of dataset header
+      output.println(generator.toStringFormat());
+      for (i = 0; i < generator.getNumExamplesAct(); i++)  {
+        // over all examples to be produced
+        Instance inst = generator.generateExample();
+        output.println(inst);
+      }
+    } 
+    else { // generator produces all instances at once
+      Instances dataset = generator.generateExamples();
+      // output of  dataset
+      output.println(dataset);      
+    }
+    // comment at end of ARFF File
+    String commentAtEnd = generator.generateFinished();
+  
+    if (commentAtEnd.length() > 0) {
+      output.println("%");
+      output.println("% Epilogue");
+      output.println("%");
+      output.println(commentAtEnd.trim());
+      output.println("%");
+    }
+    
+    output.flush();
+    output.close();
+
+    // print result to stdout?
+    if (generator.defaultOutput() != null)
+      System.out.println(generator.defaultOutput().toString());
+  }
+  
+  /**
+   * runs the datagenerator instance with the given options.
+   * 
+   * @param datagenerator		the datagenerator to run
+   * @param options	the commandline options
+   */
+  protected static void runDataGenerator(DataGenerator datagenerator, String[] options) {
+    try {
+      DataGenerator.makeData(datagenerator, options);
+    } 
+    catch (Exception e) {
+      if (    (e.getMessage() != null)
+	   && (e.getMessage().indexOf("Data Generator options") == -1) )
+	e.printStackTrace();
+      else
+	System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/RegressionGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/RegressionGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/RegressionGenerator.java	(revision 29)
@@ -0,0 +1,158 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RegressionGenerator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.Option;
+import weka.core.Utils;
+import weka.datagenerators.DataGenerator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ * Abstract class for data generators for regression classifiers. <p/>
+ *
+ * Example usage as the main of a datagenerator called RandomGenerator:
+ * <pre>
+ * public static void main(String[] args) {
+ *   try {
+ *     DataGenerator.makeData(new RandomGenerator(), args);
+ *   } 
+ *   catch (Exception e) {
+ *     e.printStackTrace();
+ *     System.err.println(e.getMessage());
+ *   }
+ * }
+ * </pre>
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public abstract class RegressionGenerator 
+  extends DataGenerator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3073254041275658221L;
+
+  /** Number of instances*/
+  protected int m_NumExamples;
+
+  /**
+   * initializes the generator with default values
+   */
+  public RegressionGenerator() {
+    super();
+
+    setNumExamples(defaultNumExamples());
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+        "\tThe number of examples to generate (default " 
+        + defaultNumExamples() + ")",
+        "n", 1, "-n <num>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the options.
+   *
+   * @param options the options 
+   * @throws Exception if invalid option
+   */
+  public void setOptions(String[] options) throws Exception { 
+    String        tmpStr;
+    
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('n', options);
+    if (tmpStr.length() != 0)
+      setNumExamples(Integer.parseInt(tmpStr));
+    else
+      setNumExamples(defaultNumExamples());
+  }
+  
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-n");
+    result.add("" + getNumExamples());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of examples
+   * 
+   * @return the default number of examples
+   */
+  protected int defaultNumExamples() {
+    return 100;
+  }
+
+  /**
+   * Sets the number of examples, given by option.
+   * @param numExamples the new number of examples
+   */
+  public void setNumExamples(int numExamples) { 
+    m_NumExamples = numExamples; 
+  }
+
+  /**
+   * Gets the number of examples, given by option.
+   * @return the number of examples, given by option 
+   */
+  public int getNumExamples() { 
+    return m_NumExamples; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numExamplesTipText() {
+    return "The number of examples to generate.";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/Test.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/Test.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/Test.java	(revision 29)
@@ -0,0 +1,246 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Test.java
+ * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/** 
+ * Class to represent a test. <br/>
+ * <br/>
+ * The string representation of the test can be supplied in standard notation
+ * or for a subset of types of attributes  in Prolog notation.<br/>
+ *
+ * Following examples for all possible tests that can be represented by
+ * this class, given in standard notation.<br/>
+ * <br/>
+ * Examples of tests for numeric attributes:<br/>
+ * B &gt;= 2.333<br/>        B &lt; 4.56<br/>
+ * <br/>
+ * Examples of tests for nominal attributes with more then 2 values:<br/>
+ * A = rain <br/>            A != rain<br/>
+ * <br/>
+ * Examples of tests for nominal attribute with exactly 2 values:<br/>
+ * A = false <br/>            A = true<br/>
+ * <br/>
+ * <br/>
+ * The Prolog notation is only supplied for numeric attributes and
+ * for nominal attributes that have the values "true" and "false".<br/>
+ * <br/>
+ * Following examples for the Prolog notation provided.<br/>
+ * <br/>
+ * Examples of tests for numeric attributes:<br/>
+ * The same as for standard notation above.<br/>
+ * <br/>
+ * Examples of tests for nominal attributes with values "true"and "false":<br/>
+ * A<br/>
+ * not(A)<br/>
+ * <br/>
+ * (Other nominal attributes are not supported by the Prolog notation.)<br/>
+ * <br/>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ 
+ **/
+
+public class Test 
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -8890645875887157782L;
+  
+  /** the attribute index */
+  int m_AttIndex;
+  
+  /** the split */
+  double m_Split;
+  
+  /** whether to negate the test */
+  boolean m_Not;
+  
+  /** the dataset */
+  Instances m_Dataset;
+	
+  /** 
+   * Constructor 
+   * 
+   * @param i the attribute index
+   * @param s the split
+   * @param dataset the dataset
+   */
+  public Test(int i, double s, Instances dataset) { 
+    m_AttIndex = i; 
+    m_Split = s;
+    m_Dataset = dataset;
+   
+    m_Not = false;
+  }
+
+  /** 
+   * Constructor 
+   * 
+   * @param i the attribute index
+   * @param s the split
+   * @param dataset the dataset
+   * @param n whether to negate the test
+   */
+  public Test(int i, double s, Instances dataset, boolean n) {
+    m_AttIndex = i;
+    m_Split = s;
+    m_Dataset = dataset;
+    m_Not = n;
+  }
+
+  /** 
+   * Negates the test.
+   *
+   * @return the test itself negated
+   */
+  public Test getNot() { // returns a modified copy
+    return new Test(m_AttIndex, m_Split, m_Dataset, m_Not ? false : true);
+    }
+
+  /**
+   * Determines whether an instance passes the test.
+   *
+   * @param inst the instance
+   * @return true if the instance satisfies the test, false otherwise
+   * @throws Exception if something goes wrong
+   */   
+  public boolean passesTest(Instance inst) throws Exception {
+    if (inst.isMissing(m_AttIndex)) return false; // missing values fail
+	
+    boolean isNominal = inst.attribute(m_AttIndex).isNominal();
+    double attribVal = inst.value(m_AttIndex);
+    if (!m_Not) {
+      if (isNominal) {
+        if (((int) attribVal) != ((int) m_Split)) return false;
+      }
+      else if (attribVal >= m_Split) return false;
+    } else {
+      if (isNominal) {
+	if (((int) attribVal) == ((int) m_Split)) return false;
+      }
+      else if (attribVal < m_Split) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the test represented by a string.
+   *
+   * @return a string representing the test
+   */   
+  public String toString() {
+    return (m_Dataset.attribute(m_AttIndex).name() + " " +
+    testComparisonString());
+  }
+
+  /**
+   * Returns the test represented by a string in Prolog notation.
+   *
+   * @return a string representing the test in Prolog notation
+   */   
+  public String toPrologString() {
+    Attribute att = m_Dataset.attribute(m_AttIndex);
+    StringBuffer str = new StringBuffer();
+    String attName = m_Dataset.attribute(m_AttIndex).name();
+    if (att.isNumeric()) {
+      str = str.append(attName + " ");
+      if (m_Not) str = str.append(">= " + Utils.doubleToString(m_Split, 3));
+      else str = str.append("< " + Utils.doubleToString(m_Split, 3));
+    } else {
+      String value = att.value((int)m_Split);
+    
+      if (value == "false") { str = str.append("not(" + attName + ")"); }      
+      else { str = str.append(attName); }
+    }
+  return str.toString();
+  }
+
+  /**
+   * Gives a string representation of the test, starting from the comparison
+   * symbol.
+   *
+   * @return a string representing the test
+   */   
+  private String testComparisonString() {
+    Attribute att = m_Dataset.attribute(m_AttIndex);
+    if (att.isNumeric()) {
+      return ((m_Not ? ">= " : "< ") + Utils.doubleToString(m_Split,3));
+    }
+    else {
+      if (att.numValues() != 2) 
+        return ((m_Not ? "!= " : "= ") + att.value((int)m_Split));
+      else return ("= " 
+                   + (m_Not ?
+      att.value((int)m_Split == 0 ? 1 : 0) : att.value((int)m_Split)));
+    }
+  }
+
+  /**
+   * Gives a string representation of the test in Prolog notation, starting
+   * from the comparison symbol.
+   *
+   * @return a string representing the test in Prolog notation
+   */   
+  private String testPrologComparisonString() {
+    Attribute att = m_Dataset.attribute(m_AttIndex);
+    if (att.isNumeric()) {
+      return ((m_Not ? ">= " : "< ") + Utils.doubleToString(m_Split,3));
+    }
+    else {
+      if (att.numValues() != 2) 
+        return ((m_Not ? "!= " : "= ") + att.value((int)m_Split));
+      else return ("= " 
+                   + (m_Not ? att.value((int)m_Split == 0 ? 1 : 0) 
+                          : att.value((int)m_Split)));
+    }
+  }
+
+  /**
+   * Compares the test with the test that is given as parameter.
+   *
+   * @param t the test the object is compared to
+   * @return true if the two Tests are equal 
+   */   
+  public boolean equalTo(Test t) {
+    return (m_AttIndex == t.m_AttIndex && m_Split == t.m_Split && m_Not == t.m_Not);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/Agrawal.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/Agrawal.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/Agrawal.java	(revision 29)
@@ -0,0 +1,940 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Agrawal.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.datagenerators.ClassificationGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates a people database and is based on the paper by Agrawal et al.:<br/>
+ * R. Agrawal, T. Imielinski, A. Swami (1993). Database Mining: A Performance Perspective. IEEE Transactions on Knowledge and Data Engineering. 5(6):914-925. URL http://www.almaden.ibm.com/software/quest/Publications/ByDate.html.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Agrawal1993,
+ *    author = {R. Agrawal and T. Imielinski and A. Swami},
+ *    journal = {IEEE Transactions on Knowledge and Data Engineering},
+ *    note = {Special issue on Learning and Discovery in Knowledge-Based Databases},
+ *    number = {6},
+ *    pages = {914-925},
+ *    title = {Database Mining: A Performance Perspective},
+ *    volume = {5},
+ *    year = {1993},
+ *    URL = {http://www.almaden.ibm.com/software/quest/Publications/ByDate.html},
+ *    PDF = {http://www.almaden.ibm.com/software/quest/Publications/papers/tkde93.pdf}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -F &lt;num&gt;
+ *  The function to use for generating the data. (default 1)</pre>
+ * 
+ * <pre> -B
+ *  Whether to balance the class.</pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  The perturbation factor. (default 0.05)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby at cs dot waikato dot ac dot nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+
+public class Agrawal
+  extends ClassificationGenerator
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 2254651939636143025L;
+  
+  /**
+   * the interface for the class functions
+   */
+  protected interface ClassFunction {
+    /**
+     * returns a class value based on the given inputs
+     * @param salary the salary
+     * @param commission the commission
+     * @param age the age
+     * @param elevel the education level
+     * @param car 
+     * @param zipcode the zip code
+     * @param hvalue
+     * @param hyears
+     * @param loan
+     */
+    public long determineClass(double salary, double commission, int age,
+        int elevel, int car, int zipcode, double hvalue, int hyears,
+        double loan);
+  }
+
+  /** 
+   * built in functions are based on the paper (page 924),
+   * which turn out to be functions pred20 thru pred29 in the public c code
+   */
+  protected static ClassFunction[] builtInFunctions = {
+    // function 1
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        if (age < 40 || 60 <= age)
+          return 0;
+        else
+          return 1;
+      }
+    },
+    // function 2
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        if (age < 40)
+          if (50000 <= salary && salary <= 100000)
+            return 0;
+          else
+            return 1;
+        else if (age < 60) // && age >= 40
+          if (75000 <= salary && salary <= 125000)
+            return 0;
+          else
+            return 1;
+        else // age >= 60
+          if (25000 <= salary && salary <= 75000)
+            return 0;
+          else
+            return 1;
+      }
+    },
+    // function 3
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        if (age < 40)
+          if (elevel == 0 || elevel == 1)
+            return 0;
+          else
+            return 1;
+        else if (age < 60) // && age >= 40
+          if (elevel == 1 || elevel == 2 || elevel == 3)
+            return 0;
+          else
+            return 1;
+        else // age >= 60
+          if (elevel == 2 || elevel == 3 || elevel == 4)
+            return 0;
+          else
+            return 1;
+      }
+    },
+    // function 4
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        if (age < 40)
+          if (elevel == 0 || elevel == 1)
+            if (25000 <= salary && salary <= 75000)
+              return 0;
+            else
+              return 1;
+          else if (50000 <= salary && salary <= 100000)
+            return 0;
+          else
+            return 1;
+        else if (age < 60) // && age >= 40
+          if (elevel == 1 || elevel == 2 || elevel == 3)
+            if (50000 <= salary && salary <= 100000)
+              return 0;
+            else
+              return 1;
+          else if (75000 <= salary && salary <= 125000)
+            return 0;
+          else
+            return 1;
+        else // age >= 60
+          if (elevel == 2 || elevel == 3 || elevel == 4)
+            if (50000 <= salary && salary <= 100000)
+              return 0;
+            else
+              return 1;
+          else if (25000 <= salary && salary <= 75000)
+            return 0;
+          else
+            return 1;
+      }
+    },
+    // function 5
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        if (age < 40)
+          if (50000 <= salary && salary <= 100000)
+            if (100000 <= loan && loan <= 300000)
+              return 0;
+            else
+              return 1;
+          else if (200000 <= loan && loan <= 400000)
+            return 0;
+          else
+            return 1;
+        else if (age < 60) // && age >= 40
+          if (75000 <= salary && salary <= 125000)
+            if (200000 <= loan && loan <= 400000)
+              return 0;
+            else
+              return 1;
+          else if (300000 <= loan && loan <= 500000)
+            return 0;
+          else
+            return 1;
+        else // age >= 60
+          if (25000 <= salary && salary <= 75000)
+            if (300000 <= loan && loan <= 500000)
+              return 0;
+            else
+              return 1;
+          else if (100000 <= loan && loan <= 300000)
+            return 0;
+          else
+            return 1;
+      }
+    },
+    // function 6
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        double totalSalary = salary + commission;
+        if (age < 40)
+          if (50000 <= totalSalary && totalSalary <= 100000)
+            return 0;
+          else
+            return 1;
+        else if (age < 60) // && age >= 40
+          if (75000 <= totalSalary && totalSalary <= 125000)
+            return 0;
+          else
+            return 1;
+        else // age >= 60
+          if (25000 <= totalSalary && totalSalary <= 75000)
+            return 0;
+          else
+            return 1;
+      }
+    },
+    // function 7
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        double disposable = (2.0 * (salary + commission) / 3.0
+            - loan / 5.0 - 20000.0);
+        return disposable > 0 ? 0 : 1;
+      }
+    },
+    // function 8
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        double disposable = (2.0 * (salary + commission) / 3.0
+            - 5000.0 * (double) elevel - 20000.0);
+        return disposable > 0 ? 0 : 1;
+      }
+    },
+    // function 9
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        double disposable = (2.0 * (salary + commission) / 3.0
+            - 5000.0 * (double) elevel - loan / 5.0 - 10000.0);
+        return disposable > 0 ? 0 : 1;
+      }
+    },
+    // function 10
+    new ClassFunction() {
+      public long determineClass(double salary, double commission,
+          int age, int elevel, int car, int zipcode,
+          double hvalue, int hyears, double loan) {
+        double equity = 0.0;
+        if (hyears >= 20)
+          equity = hvalue * ((double) hyears - 20.0) / 10.0;
+        double disposable = (2.0 * (salary + commission) / 3.0
+            - 5000.0 * (double) elevel + equity / 5.0 - 10000.0);
+        return disposable > 0 ? 0 : 1;
+      }
+    } 
+  };
+
+  /** function 1 */
+  public final static int FUNCTION_1 = 1;
+  /** function 2 */
+  public final static int FUNCTION_2 = 2;
+  /** function 3 */
+  public final static int FUNCTION_3 = 3;
+  /** function 4 */
+  public final static int FUNCTION_4 = 4;
+  /** function 5 */
+  public final static int FUNCTION_5 = 5;
+  /** function 6 */
+  public final static int FUNCTION_6 = 6;
+  /** function 7 */
+  public final static int FUNCTION_7 = 7;
+  /** function 8 */
+  public final static int FUNCTION_8 = 8;
+  /** function 9 */
+  public final static int FUNCTION_9 = 9;
+  /** function 10 */
+  public final static int FUNCTION_10 = 10;
+  /** the funtion tags */
+  public static final Tag[] FUNCTION_TAGS = {
+    new Tag(FUNCTION_1,  "Function 1"),
+    new Tag(FUNCTION_2,  "Function 2"),
+    new Tag(FUNCTION_3,  "Function 3"),
+    new Tag(FUNCTION_4,  "Function 4"),
+    new Tag(FUNCTION_5,  "Function 5"),
+    new Tag(FUNCTION_6,  "Function 6"),
+    new Tag(FUNCTION_7,  "Function 7"),
+    new Tag(FUNCTION_8,  "Function 8"),
+    new Tag(FUNCTION_9,  "Function 9"),
+    new Tag(FUNCTION_10, "Function 10"),
+  };
+  
+  /** the function to use for generating the data */
+  protected int m_Function;
+
+  /** whether to balance the class */
+  protected boolean m_BalanceClass;
+
+  /** the perturabation fraction */
+  protected double m_PerturbationFraction;
+  
+  /** used for balancing the class */
+  protected boolean m_nextClassShouldBeZero;
+
+  /** the last class label that was generated */
+  protected double m_lastLabel;
+  
+  /**
+   * initializes the generator with default values
+   */
+  public Agrawal() {
+    super();
+
+    setFunction(defaultFunction());
+    setBalanceClass(defaultBalanceClass());
+    setPerturbationFraction(defaultPerturbationFraction());
+  }
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+         "Generates a people database and is based on the paper by Agrawal "
+       + "et al.:\n"
+       + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "R. Agrawal and T. Imielinski and A. Swami");
+    result.setValue(Field.YEAR, "1993");
+    result.setValue(Field.TITLE, "Database Mining: A Performance Perspective");
+    result.setValue(Field.JOURNAL, "IEEE Transactions on Knowledge and Data Engineering");
+    result.setValue(Field.VOLUME, "5");
+    result.setValue(Field.NUMBER, "6");
+    result.setValue(Field.PAGES, "914-925");
+    result.setValue(Field.NOTE, "Special issue on Learning and Discovery in Knowledge-Based Databases");
+    result.setValue(Field.URL, "http://www.almaden.ibm.com/software/quest/Publications/ByDate.html");
+    result.setValue(Field.PDF, "http://www.almaden.ibm.com/software/quest/Publications/papers/tkde93.pdf");
+    
+    return result;
+  }
+
+ /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.add(new Option(
+              "\tThe function to use for generating the data. (default " 
+              + defaultFunction().getSelectedTag().getID() + ")",
+              "F", 1, "-F <num>"));
+
+    result.add(new Option(
+              "\tWhether to balance the class.",
+              "B", 0, "-B"));
+
+    result.add(new Option(
+              "\tThe perturbation factor. (default " 
+              + defaultPerturbationFraction() + ")",
+              "P", 1, "-P <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -F &lt;num&gt;
+   *  The function to use for generating the data. (default 1)</pre>
+   * 
+   * <pre> -B
+   *  Whether to balance the class.</pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  The perturbation factor. (default 0.05)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setFunction(new SelectedTag(Integer.parseInt(tmpStr), FUNCTION_TAGS));
+    else
+      setFunction(defaultFunction());
+
+    setBalanceClass(Utils.getFlag('B', options));
+
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setPerturbationFraction(Double.parseDouble(tmpStr));
+    else
+      setPerturbationFraction(defaultPerturbationFraction());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-F");
+    result.add("" + m_Function);
+    
+    if (getBalanceClass())
+      result.add("-B");
+    
+    result.add("-P");
+    result.add("" + getPerturbationFraction());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default function
+   * 
+   * @return the default function
+   */
+  protected SelectedTag defaultFunction() {
+    return new SelectedTag(FUNCTION_1, FUNCTION_TAGS);
+  }
+  
+  /**
+   * Gets the function for generating the data.
+   *
+   * @return the function.
+   * @see #FUNCTION_TAGS
+   */
+  public SelectedTag getFunction() {
+    return new SelectedTag(m_Function, FUNCTION_TAGS);
+  }
+  
+  /**
+   * Sets the function for generating the data.
+   *
+   * @param value the function.
+   * @see #FUNCTION_TAGS
+   */
+  public void setFunction(SelectedTag value) {
+    if (value.getTags() == FUNCTION_TAGS)
+      m_Function = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String functionTipText() {
+    return "The function to use for generating the data.";
+  }
+
+  /**
+   * returns the default for balancing the class
+   * 
+   * @return the default for balancing the class
+   */
+  protected boolean defaultBalanceClass() {
+    return false;
+  }
+  
+  /**
+   * Gets whether the class is balanced.
+   *
+   * @return whether the class is balanced.
+   */
+  public boolean getBalanceClass() { 
+    return m_BalanceClass; 
+  }
+  
+  /**
+   * Sets whether the class is balanced.
+   *
+   * @param value whether to balance the class.
+   */
+  public void setBalanceClass(boolean value) { 
+    m_BalanceClass = value;
+  }  
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String balanceClassTipText() {
+    return "Whether to balance the class.";
+  }
+
+  /**
+   * returns the default perturbation fraction
+   * 
+   * @return the default perturbation fraction
+   */
+  protected double defaultPerturbationFraction() {
+    return 0.05;
+  }
+  
+  /**
+   * Gets the perturbation fraction.
+   *
+   * @return the perturbation fraction.
+   */
+  public double getPerturbationFraction() { 
+    return m_PerturbationFraction; 
+  }
+  
+  /**
+   * Sets the perturbation fraction.
+   *
+   * @param value the perturbation fraction.
+   */
+  public void setPerturbationFraction(double value) { 
+    if ( (value >= 0.0) && (value <= 1.0) )
+      m_PerturbationFraction = value;
+    else
+      throw new IllegalArgumentException(
+          "Perturbation fraction must be in [0,1] (provided: " + value + ")!");
+  }  
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String perturbationFractionTipText() {
+    return "The perturbation fraction: 0 <= fraction <= 1.";
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return true;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    FastVector      atts;
+    FastVector      attValues;
+    int             i;
+
+    m_Random = new Random(getSeed());
+    m_nextClassShouldBeZero = true;
+    m_lastLabel             = Double.NaN;
+
+    // number of examples is the same as given per option
+    setNumExamplesAct(getNumExamples());
+
+    // set up attributes
+    atts = new FastVector();
+    
+    atts.addElement(new Attribute("salary"));
+    
+    atts.addElement(new Attribute("commission"));
+    
+    attValues = new FastVector();
+    atts.addElement(new Attribute("age"));
+
+    attValues = new FastVector();
+    for (i = 0; i < 5; i++)
+      attValues.addElement("" + i);
+    atts.addElement(new Attribute("elevel", attValues));
+    
+    attValues = new FastVector();
+    for (i = 1; i <= 20; i++)
+      attValues.addElement("" + i);
+    atts.addElement(new Attribute("car", attValues));
+    
+    attValues = new FastVector();
+    for (i = 0; i < 9; i++)
+      attValues.addElement("" + i);
+    atts.addElement(new Attribute("zipcode", attValues));
+    
+    atts.addElement(new Attribute("hvalue"));
+    
+    atts.addElement(new Attribute("hyears"));
+    
+    atts.addElement(new Attribute("loan"));
+    
+    attValues = new FastVector();
+    for (i = 0; i < 2; i++)
+      attValues.addElement("" + i);
+    atts.addElement(new Attribute("group", attValues));
+    
+    // dataset
+    m_DatasetFormat = new Instances(getRelationNameToUse(), atts, 0);
+    
+    return m_DatasetFormat;
+  }
+
+  /**
+   * perturbs the given value
+   * 
+   * @param val the value to perturb
+   * @param min the minimum
+   * @param max the maximum
+   * @return the perturbed value
+   */
+  protected double perturbValue(double val, double min, double max) {
+    return perturbValue(val, max - min, min, max);
+  }
+
+  /**
+   * perturbs the given value
+   * 
+   * @param val the value to perturb
+   * @param range the range for the perturbation
+   * @param min the minimum
+   * @param max the maximum
+   * @return the perturbed value
+   */
+  protected double perturbValue(double val, double range, 
+      double min, double max) {
+    
+    val += range * (2.0 * (getRandom().nextDouble() - 0.5)) 
+                 * getPerturbationFraction();
+
+    if (val < min)
+      val = min;
+    else if (val > max)
+      val = max;
+    
+    return val;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    Instance      result;
+    double        salary;
+    double        commission;
+    double        hvalue;
+    double        loan;
+    int           age;
+    int           elevel;
+    int           car;
+    int           zipcode;
+    int           hyears;
+    boolean       desiredClassFound;
+    double[]      atts;
+    Random        random;
+    ClassFunction classFunction;
+
+    result = null;
+    random = getRandom();
+
+    if (m_DatasetFormat == null)
+      throw new Exception("Dataset format not defined.");
+
+    salary            = 0;
+    commission        = 0;
+    hvalue            = 0;
+    loan              = 0;
+    age               = 0;
+    elevel            = 0;
+    car               = 0;
+    zipcode           = 0;
+    hyears            = 0;
+    desiredClassFound = false;
+    classFunction     = builtInFunctions[m_Function - 1];
+
+    while (!desiredClassFound) {
+      // generate attributes
+      salary     = 20000.0 + 130000.0 * random.nextDouble();
+      commission = (salary >= 75000.0) ? 
+                   0 : (10000.0 + 65000.0 * random.nextDouble());
+      age        = 20 + random.nextInt(61);
+      elevel     = random.nextInt(5);
+      car        = 1 + random.nextInt(20);
+      zipcode    = random.nextInt(9);
+      hvalue     = (9.0 - (double) zipcode) * 100000.0
+                   * (0.5 + random.nextDouble());
+      hyears     = 1 + random.nextInt(30);
+      loan       = random.nextDouble() * 500000.0;
+      
+      // determine class
+      m_lastLabel = classFunction.determineClass(salary, commission, age,
+          elevel, car, zipcode, hvalue, hyears, loan);
+      if (!getBalanceClass()) {
+        desiredClassFound = true;
+      }
+      else {
+        // balance the classes
+        if (    ( m_nextClassShouldBeZero && (m_lastLabel == 0))
+             || (!m_nextClassShouldBeZero && (m_lastLabel == 1)) ) {
+          desiredClassFound = true;
+          m_nextClassShouldBeZero = !m_nextClassShouldBeZero;
+        } // else keep searching
+      }
+    }
+    
+    // perturb values
+    if (getPerturbationFraction() > 0.0) {
+      salary = perturbValue(salary, 20000, 150000);
+      if (commission > 0)
+        commission = perturbValue(commission, 10000, 75000);
+      age    = (int) Math.round(perturbValue(age, 20, 80));
+      hvalue = perturbValue(
+                  hvalue, (9.0 - (double) zipcode) * 100000.0, 0, 135000);
+      hyears = (int) Math.round(perturbValue(hyears, 1, 30));
+      loan   = perturbValue(loan, 0, 500000);
+    }
+
+    // create instance
+    atts    = new double[m_DatasetFormat.numAttributes()];
+    atts[0] = salary;
+    atts[1] = commission;
+    atts[2] = age;
+    atts[3] = elevel;
+    atts[4] = car - 1;
+    atts[5] = zipcode;
+    atts[6] = hvalue;
+    atts[7] = hyears;
+    atts[8] = loan;
+    atts[9] = m_lastLabel;
+    result  = new DenseInstance(1.0, atts);
+    result.setDataset(m_DatasetFormat);
+
+    return result;
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    Instances       result;
+    int             i;
+
+    result   = new Instances(m_DatasetFormat, 0);
+    m_Random = new Random(getSeed());
+
+    for (i = 0; i < getNumExamplesAct(); i++)
+      result.add(generateExample());
+    
+    return result;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new Agrawal(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/BayesNet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/BayesNet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/BayesNet.java	(revision 29)
@@ -0,0 +1,614 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BayesNet.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.classifiers.bayes.net.BayesNetGenerator;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.datagenerators.ClassificationGenerator;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates random instances based on a Bayes network.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  The number of arcs to use. (default 20)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The cardinality of the attributes and the class. (default 2)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see BayesNetGenerator
+ */
+
+public class BayesNet
+  extends ClassificationGenerator {
+  
+  /** for serialization */
+  static final long serialVersionUID = -796118162379901512L;
+  
+  /** the bayesian net generator, that produces the actual data */
+  protected BayesNetGenerator m_Generator;
+
+  /**
+   * initializes the generator
+   */
+  public BayesNet() {
+    super();
+
+    setNumAttributes(defaultNumAttributes());
+    setNumArcs(defaultNumArcs());
+    setCardinality(defaultCardinality());
+  }
+  
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Generates random instances based on a Bayes network.";
+  }
+
+ /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.add(new Option(
+              "\tThe number of arcs to use. (default " 
+              + defaultNumArcs() + ")",
+              "A", 1, "-A <num>"));
+
+    result.add(new Option(
+              "\tThe cardinality of the attributes and the class. (default " 
+              + defaultCardinality() + ")",
+              "C", 1, "-C <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  The number of arcs to use. (default 20)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The cardinality of the attributes and the class. (default 2)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+    Vector        list;
+
+    super.setOptions(options);
+
+    list = new Vector();
+
+    list.add("-N");
+    list.add("" + getNumAttributes());
+
+    list.add("-M");
+    list.add("" + getNumExamples());
+    
+    list.add("-S");
+    list.add("" + getSeed());
+    
+    list.add("-A");
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.length() != 0)
+      list.add(tmpStr);
+    else
+      list.add("" + defaultNumArcs());
+
+    list.add("-C");
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      list.add(tmpStr);
+    else
+      list.add("" + defaultCardinality());
+
+    setGeneratorOptions(list);
+  }
+
+  /**
+   * Gets the current settings of the datagenerator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = removeBlacklist(super.getOptions());
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    // determine options from generator
+    options = getGenerator().getOptions();
+
+    try {
+      result.add("-A");
+      result.add(Utils.getOption('A', options));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    try {
+      result.add("-C");
+      result.add(Utils.getOption('C', options));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * sets the given options of the BayesNetGenerator
+   * 
+   * @param generator the generator to set the options for
+   * @param options the options to set
+   */
+  protected void setGeneratorOptions(
+      BayesNetGenerator generator, Vector options) {
+
+    try {
+      generator.setOptions(
+          (String[]) options.toArray(new String[options.size()]));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * returns the actual datagenerator
+   * 
+   * @return the actual datagenerator
+   */
+  protected BayesNetGenerator getGenerator() {
+    if (m_Generator == null)
+      m_Generator = new BayesNetGenerator();
+
+    return m_Generator;
+  }
+
+  /**
+   * sets the given options of the BayesNetGenerator
+   * 
+   * @param options the options to set
+   */
+  protected void setGeneratorOptions(Vector options) {
+    setGeneratorOptions(getGenerator(), options);
+  }
+
+  /**
+   * sets a specific option/value of the generator (option must be w/o
+   * then '-')
+   * @param generator       the generator to set the option for
+   * @param option          the option to set
+   * @param value           the new value for the option
+   */
+  protected void setGeneratorOption( BayesNetGenerator generator, 
+                                     String option, String value ) {
+
+    String[]      options;
+    Vector        list;
+    int           i;
+
+    try {
+      // get options and remove specific option
+      options = generator.getOptions();
+      Utils.getOption(option, options);
+
+      // add option and set the new options
+      list = new Vector();
+      for (i = 0; i < options.length; i++) {
+        if (options[i].length() != 0)
+          list.add(options[i]);
+      }
+      list.add("-" + option);
+      list.add(value);
+      setGeneratorOptions(generator, list);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * sets a specific option/value of the generator (option must be w/o
+   * then '-')
+   * @param option          the option to set
+   * @param value           the new value for the option
+   */
+  protected void setGeneratorOption(String option, String value) {
+    setGeneratorOption(getGenerator(), option, value);
+  }
+
+  /**
+   * returns the default number of attributes
+   * 
+   * @return the default number of attributes
+   */
+  protected int defaultNumAttributes() {
+    return 10;
+  }
+
+  /**
+   * Sets the number of attributes the dataset should have.
+   * @param numAttributes the new number of attributes
+   */
+  public void setNumAttributes(int numAttributes) {
+    setGeneratorOption("N", "" + numAttributes);
+  }
+
+  /**
+   * Gets the number of attributes that should be produced.
+   * @return the number of attributes that should be produced
+   */
+  public int getNumAttributes() { 
+    int       result;
+    
+    result = -1;
+    try {
+      result = Integer.parseInt(
+          Utils.getOption('N', getGenerator().getOptions()));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = -1;
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes the generated data will contain (including class attribute), ie the number of nodes in the bayesian net.";
+  }
+
+  /**
+   * returns the default cardinality
+   * 
+   * @return the default cardinality
+   */
+  protected int defaultCardinality() {
+    return 2;
+  }
+
+  /**
+   * Sets the cardinality of the attributes (incl class attribute)
+   * @param value the cardinality
+   */
+  public void setCardinality(int value) { 
+    setGeneratorOption("C", "" + value);
+  }
+
+  /**
+   * Gets the cardinality of the attributes (incl class attribute)
+   * @return the cardinality of the attributes
+   */
+  public int getCardinality() { 
+    int       result;
+    
+    result = -1;
+    try {
+      result = Integer.parseInt(
+          Utils.getOption('C', getGenerator().getOptions()));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = -1;
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String cardinalityTipText() {
+    return "The cardinality of the attributes, incl the class attribute.";
+  }
+
+  /**
+   * returns the default number of arcs
+   * 
+   * @return the default number of arcs
+   */
+  protected int defaultNumArcs() {
+    return 20;
+  }
+
+  /**
+   * Sets the number of arcs for the bayesian net
+   * @param value the number of arcs
+   */
+  public void setNumArcs(int value) {
+    int       nodes;
+    int       minArcs;
+    int       maxArcs;
+
+    nodes   = getNumAttributes();
+    minArcs = nodes - 1;
+    maxArcs = nodes * (nodes - 1) / 2;
+    
+    if (value > maxArcs)
+      throw new IllegalArgumentException(
+          "Number of arcs should be at most nodes * (nodes - 1) / 2 = " 
+          + maxArcs + " instead of " + value + " (nodes = numAttributes)!");
+    else if (value < minArcs)
+      throw new IllegalArgumentException(
+          "Number of arcs should be at least (nodes - 1) = " + minArcs 
+          + " instead of " + value + " (nodes = numAttributes)!");
+    else
+      setGeneratorOption("A", "" + value);
+  }
+
+  /**
+   * Gets the number of arcs for the bayesian net
+   * @return the number of arcs
+   */
+  public int getNumArcs() { 
+    int       result;
+    
+    result = -1;
+    try {
+      result = Integer.parseInt(
+          Utils.getOption('A', getGenerator().getOptions()));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = -1;
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numArcsTipText() {
+    return "The number of arcs in the bayesian net, at most: n * (n - 1) / 2 and at least: (n - 1); with n = numAttributes";
+  }
+
+  /**
+   * Sets the number of examples, given by option.
+   * @param numExamples the new number of examples
+   */
+  public void setNumExamples(int numExamples) { 
+    super.setNumExamples(numExamples);
+    setGeneratorOption("M", "" + numExamples);
+  }
+
+  /**
+   * Gets the number of examples, given by option.
+   * @return the number of examples, given by option 
+   */
+  public int getNumExamples() { 
+    int       result;
+    
+    result = -1;
+    try {
+      result = Integer.parseInt(
+          Utils.getOption('M', getGenerator().getOptions()));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = -1;
+    }
+
+    return result;
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return false;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    BayesNetGenerator   bng;
+
+    bng = new BayesNetGenerator();
+    bng.setOptions(getGenerator().getOptions());
+    setGeneratorOption(bng, "M", "1");
+    bng.generateRandomNetwork();
+    bng.generateInstances();
+    bng.m_Instances.renameAttribute(0, "class");
+    bng.m_Instances.setRelationName(getRelationNameToUse());
+    
+    return bng.m_Instances;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    throw new Exception("Cannot generate examples one-by-one!");
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    getGenerator().setOptions(getGenerator().getOptions());
+    getGenerator().generateRandomNetwork();
+    getGenerator().generateInstances();
+    getGenerator().m_Instances.renameAttribute(0, "class");
+    getGenerator().m_Instances.setRelationName(getRelationNameToUse());
+    
+    return getGenerator().m_Instances;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new BayesNet(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/LED24.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/LED24.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/LED24.java	(revision 29)
@@ -0,0 +1,456 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LED24.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.datagenerators.ClassificationGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This generator produces data for a display with 7 LEDs. The original output consists of 10 concepts and 7 boolean attributes. Here, in addition to the 7 necessary boolean attributes, 17 other, irrelevant boolean attributes with random values are added to make it harder. By default 10 percent of noise are added to the data.<br/>
+ * <br/>
+ * More information can be found here:<br/>
+ * L. Breiman J.H. Friedman R.A. Olshen, C.J. Stone (1984). Classification and Regression Trees. Belmont, California. URL http://www.ics.uci.edu/~mlearn/databases/led-display-creator/.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ * Link: <br/>
+ * <a href="http://www.ics.uci.edu/~mlearn/databases/led-display-creator/">http://www.ics.uci.edu/~mlearn/databases/led-display-creator/</a> <p/>
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inbook{Olshen1984,
+ *    address = {Belmont, California},
+ *    author = {L. Breiman J.H. Friedman R.A. Olshen and C.J. Stone},
+ *    pages = {43-49},
+ *    publisher = {Wadsworth International Group},
+ *    title = {Classification and Regression Trees},
+ *    year = {1984},
+ *    ISBN = {0412048418},
+ *    URL = {http://www.ics.uci.edu/\~mlearn/databases/led-display-creator/}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The noise percentage. (default 10.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby at cs dot waikato dot ac dot nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+
+public class LED24
+  extends ClassificationGenerator
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7880209100415868737L;  
+  
+  /** the noise rate */
+  protected double m_NoisePercent;
+  
+  /** the 7-bit LEDs */
+  protected static final int m_originalInstances[][] = {
+    { 1, 1, 1, 0, 1, 1, 1 }, { 0, 0, 1, 0, 0, 1, 0 },
+    { 1, 0, 1, 1, 1, 0, 1 }, { 1, 0, 1, 1, 0, 1, 1 },
+    { 0, 1, 1, 1, 0, 1, 0 }, { 1, 1, 0, 1, 0, 1, 1 },
+    { 1, 1, 0, 1, 1, 1, 1 }, { 1, 0, 1, 0, 0, 1, 0 },
+    { 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 0, 1, 1 } };
+
+  /** used for generating the output, i.e., the additional noise attributes */
+  protected int m_numIrrelevantAttributes = 17;
+
+  /**
+   * initializes the generator with default values
+   */
+  public LED24() {
+    super();
+
+    setNoisePercent(defaultNoisePercent());
+  }
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+         "This generator produces data for a display with 7 LEDs. The original "
+       + "output consists of 10 concepts and 7 boolean attributes. Here, in "
+       + "addition to the 7 necessary boolean attributes, 17 other, irrelevant "
+       + "boolean attributes with random values are added to make it harder. "
+       + "By default 10 percent of noise are added to the data.\n"
+       + "\n"
+       + "More information can be found here:\n"
+       + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INBOOK);
+    result.setValue(Field.AUTHOR, "L. Breiman J.H. Friedman R.A. Olshen and C.J. Stone");
+    result.setValue(Field.YEAR, "1984");
+    result.setValue(Field.TITLE, "Classification and Regression Trees");
+    result.setValue(Field.PUBLISHER, "Wadsworth International Group");
+    result.setValue(Field.ADDRESS, "Belmont, California");
+    result.setValue(Field.PAGES, "43-49");
+    result.setValue(Field.ISBN, "0412048418");
+    result.setValue(Field.URL, "http://www.ics.uci.edu/~mlearn/databases/led-display-creator/");
+    
+    return result;
+  }
+
+ /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.add(new Option(
+              "\tThe noise percentage. (default " 
+              + defaultNoisePercent() + ")",
+              "N", 1, "-N <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The noise percentage. (default 10.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNoisePercent(Double.parseDouble(tmpStr));
+    else
+      setNoisePercent(defaultNoisePercent());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-N");
+    result.add("" + getNoisePercent());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default noise percentage
+   * 
+   * @return the default noise percentage
+   */
+  protected double defaultNoisePercent() {
+    return 10;
+  }
+  
+  /**
+   * Gets the noise percentage.
+   *
+   * @return the noise percentage.
+   */
+  public double getNoisePercent() { 
+    return m_NoisePercent; 
+  }
+  
+  /**
+   * Sets the noise percentage.
+   *
+   * @param value the noise percentage.
+   */
+  public void setNoisePercent(double value) { 
+    if ( (value >= 0.0) && (value <= 100.0) )
+      m_NoisePercent = value;
+    else
+      throw new IllegalArgumentException(
+          "Noise percent must be in [0,100] (provided: " + value + ")!");
+  }  
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String noisePercentTipText() {
+    return "The noise percent: 0 <= perc <= 100.";
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return true;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    FastVector      atts;
+    FastVector      attValues;
+    int             i;
+    int             n;
+
+    m_Random = new Random(getSeed());
+
+    // number of examples is the same as given per option
+    setNumExamplesAct(getNumExamples());
+
+    // set up attributes
+    atts = new FastVector();
+    
+    for (n = 1; n <= 24; n++) {
+      attValues = new FastVector();
+      for (i = 0; i < 2; i++)
+        attValues.addElement("" + i);
+      atts.addElement(new Attribute("att" + n, attValues));
+    }
+    
+    attValues = new FastVector();
+    for (i = 0; i < 10; i++)
+      attValues.addElement("" + i);
+    atts.addElement(new Attribute("class", attValues));
+    
+    // dataset
+    m_DatasetFormat = new Instances(getRelationNameToUse(), atts, 0);
+    
+    return m_DatasetFormat;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    Instance      result;
+    double[]      atts;
+    int           i;
+    int           selected;
+    Random        random;
+
+    result = null;
+    random = getRandom();
+
+    if (m_DatasetFormat == null)
+      throw new Exception("Dataset format not defined.");
+
+    atts     = new double[m_DatasetFormat.numAttributes()];
+    selected = random.nextInt(10);
+    for (i = 0; i < 7; i++) {
+      if ((1 + (random.nextInt(100))) <= getNoisePercent())
+        atts[i] = m_originalInstances[selected][i] == 0 ? 1 : 0;
+      else
+        atts[i] = m_originalInstances[selected][i];
+    }
+
+    for (i = 0; i < m_numIrrelevantAttributes; i++)
+      atts[i + 7] = random.nextInt(2);
+
+    atts[atts.length - 1] = selected;
+
+    // create instance
+    result  = new DenseInstance(1.0, atts);
+    result.setDataset(m_DatasetFormat);
+
+    return result;
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    Instances       result;
+    int             i;
+
+    result   = new Instances(m_DatasetFormat, 0);
+    m_Random = new Random(getSeed());
+
+    for (i = 0; i < getNumExamplesAct(); i++)
+      result.add(generateExample());
+    
+    return result;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new LED24(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RDG1.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RDG1.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RDG1.java	(revision 29)
@@ -0,0 +1,1227 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RDG1.java
+ * Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.datagenerators.ClassificationGenerator;
+import weka.datagenerators.Test;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A data generator that produces data randomly by producing a decision list.<br/>
+ * The decision list consists of rules.<br/>
+ * Instances are generated randomly one by one. If decision list fails to classify the current instance, a new rule according to this current instance is generated and added to the decision list.<br/>
+ * <br/>
+ * The option -V switches on voting, which means that at the end of the generation all instances are reclassified to the class value that is supported by the most rules.<br/>
+ * <br/>
+ * This data generator can generate 'boolean' attributes (= nominal with the values {true, false}) and numeric attributes. The rules can be 'A' or 'NOT A' for boolean values and 'B &lt; random_value' or 'B &gt;= random_value' for numeric values.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -a &lt;num&gt;
+ *  The number of attributes (default 10).</pre>
+ * 
+ * <pre> -c &lt;num&gt;
+ *  The number of classes (default 2)</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  maximum size for rules (default 10) </pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  minimum size for rules (default 1) </pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  number of irrelevant attributes (default 0)</pre>
+ * 
+ * <pre> -N
+ *  number of numeric attributes (default 0)</pre>
+ * 
+ * <pre> -V
+ *  switch on voting (default is no voting)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Following an example of a generated dataset: <br/>
+ * <pre>
+ * %
+ * % weka.datagenerators.RDG1 -r expl -a 2 -c 3 -n 4 -N 1 -I 0 -M 2 -R 10 -S 2
+ * %
+ * relation expl
+ *
+ * attribute a0 {false,true}
+ * attribute a1 numeric
+ * attribute class {c0,c1,c2}
+ *
+ * data
+ *
+ * true,0.496823,c0
+ * false,0.743158,c1
+ * false,0.408285,c1
+ * false,0.993687,c2
+ * %
+ * % Number of attributes chosen as irrelevant = 0
+ * %
+ * % DECISIONLIST (number of rules = 3):
+ * % RULE 0:   c0 := a1 &lt; 0.986, a0
+ * % RULE 1:   c1 := a1 &lt; 0.95, not(a0)
+ * % RULE 2:   c2 := not(a0), a1 &gt;= 0.562
+ * </pre>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class RDG1 
+  extends ClassificationGenerator {
+
+  /** for serialization */
+  static final long serialVersionUID = 7751005204635320414L;  
+  
+  /**
+   * class to represent decisionlist
+   */
+  private class RuleList 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = 2830125413361938177L;
+    
+    /** rule list */
+    private FastVector m_RuleList = null;
+    
+    /** class */
+    double m_ClassValue = 0.0;
+
+    /**
+     * returns the class value
+     * 
+     * @return the class value
+     */
+    public double getClassValue() { 
+      return m_ClassValue; 
+    }
+    
+    /**
+     * sets the class value
+     * 
+     * @param newClassValue the new classvalue
+     */
+    public void setClassValue(double newClassValue) {
+      m_ClassValue = newClassValue;
+    }
+    
+    /**
+     * adds the given test to the list
+     * 
+     * @param newTest the test to add
+     */
+    private void addTest (Test newTest) { 
+      if (m_RuleList == null)
+	m_RuleList = new FastVector();
+      
+      m_RuleList.addElement(newTest);
+    }
+    
+    /**
+     * classifies the given example
+     * 
+     * @param example the instance to classify
+     * @return the classification
+     * @throws Exception if classification fails
+     */
+    private double classifyInstance (Instance example) throws Exception {
+      boolean passedAllTests = true;
+      for (Enumeration e = m_RuleList.elements(); 
+	   passedAllTests && e.hasMoreElements(); ) {
+	Test test = (Test) e.nextElement();
+	passedAllTests = test.passesTest(example);
+      }
+      if (passedAllTests) return m_ClassValue;
+      else return -1.0;
+    }
+    
+    /**
+     * returns a string representation of the rule list
+     * 
+     * @return the rule list as string
+     */
+    public String toString () {
+      StringBuffer str = new StringBuffer();
+      str = str.append("  c" + (int) m_ClassValue + " := ");
+      Enumeration e = m_RuleList.elements();
+      if (e.hasMoreElements()) {
+	Test test = (Test) e.nextElement();
+	str = str.append(test.toPrologString()); 
+      }
+      while (e.hasMoreElements()) {
+	Test test = (Test) e.nextElement();
+	str = str.append(", " + test.toPrologString());       
+      }
+      return str.toString();
+    } 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  } /*end class RuleList ******/
+
+  /** Number of attribute the dataset should have */
+  protected int m_NumAttributes;
+
+  /** Number of Classes the dataset should have */
+  protected int m_NumClasses;
+
+  /** maximum rule size*/ 
+  private int m_MaxRuleSize;
+  
+  /** minimum rule size*/ 
+  private int m_MinRuleSize;
+  
+  /** number of irrelevant attributes.*/
+  private int m_NumIrrelevant;
+
+  /** number of numeric attribute*/
+  private int m_NumNumeric;
+ 
+  /** flag that stores if voting is wished*/ 
+  private boolean m_VoteFlag = false;
+
+   /** decision list */
+  private FastVector m_DecisionList = null;
+
+  /** array defines which attributes are irrelevant, with:
+   * true = attribute is irrelevant; false = attribute is not irrelevant*/
+  boolean[] m_AttList_Irr;
+
+  /**
+   * initializes the generator with default values
+   */
+  public RDG1() {
+    super();
+
+    setNumAttributes(defaultNumAttributes());
+    setNumClasses(defaultNumClasses());
+    setMaxRuleSize(defaultMaxRuleSize());
+    setMinRuleSize(defaultMinRuleSize());
+    setNumIrrelevant(defaultNumIrrelevant());
+    setNumNumeric(defaultNumNumeric());
+  }
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "A data generator that produces data randomly by producing a decision list.\n"
+      + "The decision list consists of rules.\n"
+      + "Instances are generated randomly one by one. If decision list fails "
+      + "to classify the current instance, a new rule according to this current "
+      + "instance is generated and added to the decision list.\n\n"
+      + "The option -V switches on voting, which means that at the end "
+      + "of the generation all instances are "
+      + "reclassified to the class value that is supported by the most rules.\n\n"
+      + "This data generator can generate 'boolean' attributes (= nominal with "
+      + "the values {true, false}) and numeric attributes. The rules can be "
+      + "'A' or 'NOT A' for boolean values and 'B < random_value' or "
+      + "'B >= random_value' for numeric values.";
+  }
+
+ /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+          "\tThe number of attributes (default " 
+          + defaultNumAttributes() + ").",
+          "a", 1, "-a <num>"));
+
+    result.addElement(new Option(
+        "\tThe number of classes (default " + defaultNumClasses() + ")",
+        "c", 1, "-c <num>"));
+
+    result.addElement(new Option(
+          "\tmaximum size for rules (default " 
+          + defaultMaxRuleSize() + ") ",
+          "R", 1, "-R <num>"));
+    
+    result.addElement(new Option(
+          "\tminimum size for rules (default " 
+          + defaultMinRuleSize() + ") ",
+          "M", 1, "-M <num>"));
+    
+    result.addElement(new Option(
+          "\tnumber of irrelevant attributes (default " 
+          + defaultNumIrrelevant() + ")",
+          "I", 1, "-I <num>"));
+    
+    result.addElement(new Option(
+          "\tnumber of numeric attributes (default "
+          + defaultNumNumeric() + ")",
+          "N", 1, "-N"));
+    
+    result.addElement(new Option(
+          "\tswitch on voting (default is no voting)",
+          "V", 1, "-V"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -a &lt;num&gt;
+   *  The number of attributes (default 10).</pre>
+   * 
+   * <pre> -c &lt;num&gt;
+   *  The number of classes (default 2)</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  maximum size for rules (default 10) </pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  minimum size for rules (default 1) </pre>
+   * 
+   * <pre> -I &lt;num&gt;
+   *  number of irrelevant attributes (default 0)</pre>
+   * 
+   * <pre> -N
+   *  number of numeric attributes (default 0)</pre>
+   * 
+   * <pre> -V
+   *  switch on voting (default is no voting)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('a', options);
+    if (tmpStr.length() != 0)
+      setNumAttributes(Integer.parseInt(tmpStr));
+    else
+      setNumAttributes(defaultNumAttributes());
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0)
+      setNumClasses(Integer.parseInt(tmpStr));
+    else
+      setNumClasses(defaultNumClasses());
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setMaxRuleSize(Integer.parseInt(tmpStr));
+    else 
+      setMaxRuleSize(defaultMaxRuleSize());
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setMinRuleSize(Integer.parseInt(tmpStr));
+    else
+      setMinRuleSize(defaultMinRuleSize());
+
+    tmpStr = Utils.getOption('I', options);
+    if (tmpStr.length() != 0)
+      setNumIrrelevant(Integer.parseInt(tmpStr));
+    else
+      setNumIrrelevant(defaultNumIrrelevant());
+
+    if ((getNumAttributes() - getNumIrrelevant()) < getMinRuleSize())
+       throw new Exception("Possible rule size is below minimal rule size.");
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNumNumeric(Integer.parseInt(tmpStr));
+    else
+      setNumNumeric(defaultNumNumeric());
+
+    setVoteFlag(Utils.getFlag('V', options));
+  }
+
+  /**
+   * Gets the current settings of the datagenerator RDG1.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-a");
+    result.add("" + getNumAttributes());
+    
+    result.add("-c");
+    result.add("" + getNumClasses());
+
+    result.add("-N"); 
+    result.add("" + getNumNumeric());
+    
+    result.add("-I"); 
+    result.add("" + getNumIrrelevant());
+    
+    result.add("-M"); 
+    result.add("" + getMinRuleSize());
+    
+    result.add("-R"); 
+    result.add("" + getMaxRuleSize());
+    
+    if (getVoteFlag())
+      result.add("-V"); 
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of attributes
+   * 
+   * @return the default number of attributes
+   */
+  protected int defaultNumAttributes() {
+    return 10;
+  }
+
+  /**
+   * Sets the number of attributes the dataset should have.
+   * @param numAttributes the new number of attributes
+   */
+  public void setNumAttributes(int numAttributes) {
+    m_NumAttributes = numAttributes;
+  }
+
+  /**
+   * Gets the number of attributes that should be produced.
+   * @return the number of attributes that should be produced
+   */
+  public int getNumAttributes() { 
+    return m_NumAttributes; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes the generated data will contain.";
+  }
+
+  /**
+   * returns the default number of classes
+   * 
+   * @return the default number of classes
+   */
+  protected int defaultNumClasses() {
+    return 2;
+  }
+
+  /**
+   * Sets the number of classes the dataset should have.
+   * @param numClasses the new number of classes
+   */
+  public void setNumClasses(int numClasses) { 
+    m_NumClasses = numClasses; 
+  }
+
+  /**
+   * Gets the number of classes the dataset should have.
+   * @return the number of classes the dataset should have
+   */
+  public int getNumClasses() { 
+    return m_NumClasses; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numClassesTipText() {
+    return "The number of classes to generate.";
+  }
+
+  /**
+   * returns the default max size of rules
+   * 
+   * @return the default max size of rules
+   */
+  protected int defaultMaxRuleSize() {
+    return 10;
+  }
+
+  /**
+   * Gets the maximum number of tests in rules.
+   *
+   * @return the maximum number of tests allowed in rules
+   */
+  public int getMaxRuleSize() { 
+    return m_MaxRuleSize; 
+  }
+  
+  /**
+   * Sets the maximum number of tests in rules.
+   *
+   * @param newMaxRuleSize new maximum number of tests allowed in rules.
+   */
+  public void setMaxRuleSize(int newMaxRuleSize) {
+    m_MaxRuleSize = newMaxRuleSize;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String maxRuleSizeTipText() {
+    return "The maximum number of tests in rules.";
+  }
+
+  /**
+   * returns the default min size of rules
+   * 
+   * @return the default min size of rules
+   */
+  protected int defaultMinRuleSize() {
+    return 1;
+  }
+
+  /**
+   * Gets the minimum number of tests in rules.
+   *
+   * @return the minimum number of tests allowed in rules
+   */
+  public int getMinRuleSize() { 
+    return m_MinRuleSize; 
+  }
+  
+  /**
+   * Sets the minimum number of tests in rules.
+   *
+   * @param newMinRuleSize new minimum number of test in rules.
+   */
+  public void setMinRuleSize(int newMinRuleSize) {
+    m_MinRuleSize = newMinRuleSize;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String minRuleSizeTipText() {
+    return "The minimum number of tests in rules.";
+  }
+
+  /**
+   * returns the default number of irrelevant attributes
+   * 
+   * @return the default number of irrelevant attributes
+   */
+  protected int defaultNumIrrelevant() {
+    return 0;
+  }
+
+  /**
+   * Gets the number of irrelevant attributes.
+   *
+   * @return the number of irrelevant attributes
+   */
+  public int getNumIrrelevant() { 
+    return m_NumIrrelevant; 
+  }
+  
+  /**
+   * Sets the number of irrelevant attributes.
+   *
+   * @param newNumIrrelevant the number of irrelevant attributes.
+   */
+  public void setNumIrrelevant(int newNumIrrelevant) {
+    m_NumIrrelevant = newNumIrrelevant;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numIrrelevantTipText() {
+    return "The number of irrelevant attributes.";
+  }
+
+  /**
+   * returns the default number of numeric attributes
+   * 
+   * @return the default number of numeric attributes
+   */
+  protected int defaultNumNumeric() {
+    return 0;
+  }
+
+  /**
+   * Gets the number of numerical attributes.
+   *
+   * @return the number of numerical attributes.
+   */
+  public int getNumNumeric() { 
+    return m_NumNumeric; 
+  }
+  
+  /**
+   * Sets the number of numerical attributes.
+   *
+   * @param newNumNumeric the number of numerical attributes.
+   */
+  public void setNumNumeric(int newNumNumeric) { 
+    m_NumNumeric = newNumNumeric;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numNumericTipText() {
+    return "The number of numerical attributes.";
+  }
+
+  /**
+   * Gets the vote flag.
+   *
+   * @return voting flag.
+   */
+  public boolean getVoteFlag() { 
+    return m_VoteFlag; 
+  }
+  
+  /**
+   * Sets the vote flag.
+   *
+   * @param newVoteFlag boolean with the new setting of the vote flag.
+   */
+  public void setVoteFlag(boolean newVoteFlag) { 
+    m_VoteFlag = newVoteFlag; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String voteFlagTipText() {
+    return "Whether to use voting or not.";
+  }
+
+  /**
+   * Gets the single mode flag.
+   *
+   * @return true if methode generateExample can be used.
+   */
+  public boolean getSingleModeFlag() { 
+    return (!getVoteFlag()); 
+  }
+
+  /**
+   * Gets the array that defines which of the attributes
+   * are seen to be irrelevant.
+   *
+   * @return the array that defines the irrelevant attributes
+   */
+  public boolean[] getAttList_Irr() { 
+    return m_AttList_Irr; 
+  }
+  
+  /**
+   * Sets the array that defines which of the attributes
+   * are seen to be irrelevant.
+   *
+   * @param newAttList_Irr array that defines the irrelevant attributes.
+   */
+  public void setAttList_Irr(boolean[] newAttList_Irr) {
+    m_AttList_Irr = newAttList_Irr;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String attList_IrrTipText() {
+    return "The array with the indices of the irrelevant attributes.";
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   *
+   * @return the output data format
+   * @throws Exception data format could not be defined 
+   */
+  public Instances defineDataFormat() throws Exception {
+    Instances dataset;
+    Random random = new Random (getSeed());
+    setRandom(random);
+
+    m_DecisionList = new FastVector();
+
+    // number of examples is the same as given per option
+    setNumExamplesAct(getNumExamples());
+
+    // define dataset
+    dataset = defineDataset(random);
+    return dataset; 
+  }
+
+  /**
+   * Generate an example of the dataset dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined or generating <br/>
+   * examples one by one is not possible, because voting is chosen
+   */
+  public Instance generateExample() throws Exception {
+    Random random = getRandom();
+    Instances format = getDatasetFormat();
+
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+    if (getVoteFlag()) 
+      throw new Exception("Examples cannot be generated one by one.");
+
+    // generate values for all attributes
+    format = generateExamples(1, random, format);
+
+    return format.lastInstance();
+  }
+
+  /**
+   * Generate all examples of the dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined or generating <br/>
+   * examples one by one is not possible, because voting is chosen
+   */
+  public Instances generateExamples() throws Exception {
+    Random random = getRandom();
+    Instances format = getDatasetFormat();
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+
+    // generate values for all attributes
+    format = generateExamples(getNumExamplesAct(), random, format);
+
+    // vote all examples, and set new class value
+    if (getVoteFlag())
+      format = voteDataset(format);
+
+    return format;
+  }
+
+  /**
+   * Generate all examples of the dataset. 
+   * @param num the number of examples to generate
+   * @param random the random number generator to use
+   * @param format the dataset format
+   * @return the instance generated
+   * @throws Exception if format not defined or generating <br/>
+   * examples one by one is not possible, because voting is chosen
+   */
+  public Instances generateExamples(int num, 
+                                   Random random,
+                                   Instances format) throws Exception {
+
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+    
+    // generate values for all attributes
+    for (int i = 0; i < num; i++)  {
+      // over all examples to be produced
+      Instance example = generateExample(random, format);
+
+      // set class of example using decision list
+      boolean classDefined = classifyExample(example);
+      if (!classDefined) {
+        // set class with newly generated rule
+        example = updateDecisionList(random, example);
+      }
+      example.setDataset(format);
+      format.add(example);
+    }
+
+    return (format);
+  }
+
+ /**
+   * Generates a new rule for the decision list.
+   * and classifies the new example
+   * @param random random number generator
+   * @param example example used to update decision list 
+   * @return the classified example
+   * @throws Exception if dataset format not defined
+   */
+  private Instance updateDecisionList(Random random, Instance example)
+   throws Exception {
+
+    FastVector TestList;
+    Instances format = getDatasetFormat();
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+
+    TestList = generateTestList(random, example);
+
+    int maxSize = getMaxRuleSize() < TestList.size() ? 
+                            getMaxRuleSize() : TestList.size();
+    int ruleSize = ((int) (random.nextDouble() * 
+                             (double) (maxSize - getMinRuleSize())))
+                                   + getMinRuleSize();
+
+    RuleList newRule = new RuleList();
+    for (int i=0; i < ruleSize; i++) {
+      int testIndex = (int) (random.nextDouble() * (double) TestList.size());
+      Test test = (Test) TestList.elementAt(testIndex);
+          
+      newRule.addTest(test);
+      TestList.removeElementAt(testIndex);
+    }
+    double newClassValue = 0.0;
+    if (m_DecisionList.size() > 0) {
+      RuleList r = (RuleList)(m_DecisionList.lastElement());
+      double oldClassValue = (double) 
+                        (r.getClassValue());
+      newClassValue = (double)((int)oldClassValue + 1)
+                               % getNumClasses();
+    }
+    newRule.setClassValue(newClassValue);
+    m_DecisionList.addElement(newRule);
+    example = (Instance)example.copy();
+    example.setDataset(format);
+    example.setClassValue(newClassValue);
+    return example;
+  }
+
+ /**
+   * Generates a new rule for the decision list
+   * and classifies the new example.
+   *
+   * @param random random number generator
+   * @param example the instance to classify
+   * @return a list of tests
+   * @throws Exception if dataset format not defined
+   */
+  private FastVector generateTestList(Random random, Instance example) 
+   throws Exception {
+
+    Instances format = getDatasetFormat();
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+
+    int numTests = getNumAttributes() - getNumIrrelevant();
+    FastVector TestList = new FastVector(numTests);
+    boolean[] irrelevant = getAttList_Irr();
+
+    for (int i = 0; i < getNumAttributes(); i++) {
+      if (!irrelevant[i]) {
+        Test newTest = null;
+        Attribute att = example.attribute(i);
+        if (att.isNumeric()) {
+          double newSplit = random.nextDouble();
+          boolean newNot = newSplit < example.value(i);
+          newTest = new Test(i, newSplit, format, newNot);
+        } else {
+          newTest = new Test(i, example.value(i), format, false);
+        }
+      TestList.addElement (newTest);     
+      }
+    }
+    
+    return TestList;
+  }
+
+ /**
+   * Generates an example with its classvalue set to missing
+   * and binds it to the datasets.
+   *
+   * @param random random number generator
+   * @param format dataset the example gets bind to
+   * @return the generated example
+   * @throws Exception if attribute type not supported
+   */
+  private Instance generateExample(Random random, Instances format) 
+    throws Exception {     
+    double[] attributes;
+    Instance example;
+
+    attributes = new double[getNumAttributes() + 1];
+    for (int i = 0; i < getNumAttributes(); i++) {
+      double value = random.nextDouble();
+      if (format.attribute(i).isNumeric()) {
+        attributes[i] = value; 
+      } else {
+	if (format.attribute(i).isNominal())
+	  attributes[i] = (value > 0.5) ? 1.0 : 0.0;
+	else
+	  throw new Exception ("Attribute type is not supported.");
+      }
+    }
+    example = new DenseInstance(1.0, attributes);
+    example.setDataset(format);
+    example.setClassMissing();
+
+    return example; 
+  }
+
+ /**
+   * Tries to classify an example. 
+   * 
+   * @param example the example to classify
+   * @return true if it could be classified
+   * @throws Exception if something goes wrong
+   */
+  private boolean classifyExample(Instance example) throws Exception {
+    double classValue = -1.0;  
+
+    for (Enumeration e = m_DecisionList.elements(); 
+         e.hasMoreElements() && classValue < 0.0;) {
+      RuleList rl = (RuleList) e.nextElement();
+      classValue = rl.classifyInstance(example);   
+    }
+    if (classValue >= 0.0) {
+      example.setClassValue(classValue);
+      return true;
+    } 
+    else {
+      return false;
+    }
+  }
+
+ /**
+   * Classify example with maximum vote the following way.
+   * With every rule in the decisionlist, it is evaluated if
+   * the given instance could be the class of the rule.
+   * Finally the class value that receives the highest number of votes
+   * is assigned to the example.
+   * 
+   * @param example example to be reclassified
+   * @return instance with new class value
+   * @throws Exception if classification fails
+   */
+  private Instance votedReclassifyExample(Instance example) throws Exception {
+    int classVotes[] = new int [getNumClasses()]; 
+    for (int i = 0; i < classVotes.length; i++) classVotes[i] = 0; 
+
+    for (Enumeration e = m_DecisionList.elements(); 
+         e.hasMoreElements();) {
+      RuleList rl = (RuleList) e.nextElement();
+      int classValue = (int) rl.classifyInstance(example);
+      if (classValue >= 0) classVotes[classValue]++;  
+    }
+    int maxVote = 0;
+    int vote = -1;
+    for (int i = 0; i < classVotes.length; i++) {
+      if (classVotes[i] > maxVote) {
+        maxVote = classVotes[i];
+        vote = i; 
+      }
+    }
+    if (vote >= 0)
+      example.setClassValue((double) vote);
+    else
+      throw new Exception ("Error in instance classification.");
+
+    return example;
+  }
+
+ /**
+   * Returns a dataset header.
+   * @param random random number generator
+   * @return dataset header
+   * @throws Exception if something goes wrong
+   */
+  private Instances defineDataset(Random random) throws Exception {
+
+    boolean[] attList_Irr;
+    int[] attList_Num;
+    FastVector attributes = new FastVector();
+    Attribute attribute;
+    FastVector nominalValues = new FastVector (2);
+    nominalValues.addElement("false"); 
+    nominalValues.addElement("true"); 
+    FastVector classValues = new FastVector (getNumClasses());
+    Instances dataset;
+     
+    // set randomly those attributes that are irrelevant
+    attList_Irr = defineIrrelevant(random);
+    setAttList_Irr(attList_Irr);
+
+    // set randomly those attributes that are numeric
+    attList_Num = defineNumeric(random); 
+
+    // define dataset
+    for (int i = 0; i < getNumAttributes(); i++) {
+      if (attList_Num[i] == Attribute.NUMERIC)
+        attribute = new Attribute("a" + i); 
+      else
+        attribute = new Attribute("a" + i, nominalValues); 
+      attributes.addElement(attribute);
+    }
+    for (int i = 0; i < getNumClasses(); i++)
+      classValues.addElement("c" + i);
+    attribute = new Attribute ("class", classValues); 
+    attributes.addElement(attribute);
+
+    dataset = new Instances(getRelationNameToUse(), attributes,
+                            getNumExamplesAct());
+    dataset.setClassIndex(getNumAttributes());
+
+    // set dataset format of this class
+    Instances format = new Instances(dataset, 0);
+    setDatasetFormat(format);
+    
+    return dataset; 
+  } 
+
+ /**
+   * Defines randomly the attributes as irrelevant.
+   * Number of attributes to be set as irrelevant is either set
+   * with a preceeding call of setNumIrrelevant() or is per default 0.
+   *
+   * @param random the random number generator to use
+   * @return list of boolean values with one value for each attribute,
+   * and each value set true or false according to if the corresponding
+   * attribute was defined irrelevant or not
+   */
+  private boolean[] defineIrrelevant(Random random) {
+
+    boolean[] irr = new boolean [getNumAttributes()];
+ 
+    // initialize
+    for (int i = 0; i < irr.length; i++)
+      irr[i] = false;
+
+    // set randomly
+    int numIrr = 0;
+    for (int i = 0; 
+         (numIrr < getNumIrrelevant()) && (i < getNumAttributes() * 5);
+          i++) {
+      int maybeNext = (int) (random.nextDouble() * (double) irr.length);
+      if (irr[maybeNext] == false) {
+        irr [maybeNext] = true;
+        numIrr++;
+      }
+    }
+    
+    return irr;
+  }
+
+ /**
+   * Chooses randomly the attributes that get datatyp numeric.
+   * @param random the random number generator to use
+   * @return list of integer values, with one value for each attribute,
+   * and each value set to Attribut.NOMINAL or Attribut.NUMERIC
+   */
+  private int[] defineNumeric(Random random) {
+    
+    int[] num = new int [getNumAttributes()];
+
+    // initialize
+    for (int i = 0; i < num.length; i++)
+      num[i] = Attribute.NOMINAL;
+
+    int numNum = 0;
+    for (int i = 0;
+         (numNum < getNumNumeric()) && (i < getNumAttributes() * 5); i++) {
+      int maybeNext = (int) (random.nextDouble() * (double) num.length);
+      if (num[maybeNext] != Attribute.NUMERIC) {
+        num[maybeNext] = Attribute.NUMERIC;
+        numNum++;
+      }
+    }
+    
+    return num;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Compiles documentation about the data generation. This is the number of
+   * irrelevant attributes and the decisionlist with all rules.
+   * Considering that the decisionlist might get enhanced until
+   * the last instance is generated, this method should be called at the
+   * end of the data generation process. 
+   *
+   * @return string with additional information about generated dataset
+   * @throws Exception no input structure has been defined
+   */
+  public String generateFinished() throws Exception {
+
+    StringBuffer dLString = new StringBuffer();
+
+    // string for output at end of ARFF-File
+    boolean[] attList_Irr = getAttList_Irr();
+    Instances format = getDatasetFormat();
+    dLString.append("%\n% Number of attributes chosen as irrelevant = " +
+                    getNumIrrelevant() + "\n");
+    for (int i = 0; i < attList_Irr.length; i++) {
+      if (attList_Irr[i])
+        dLString.append("% " + format.attribute(i).name() + "\n");
+    }
+
+    dLString.append("%\n% DECISIONLIST (number of rules = " +
+                    m_DecisionList.size() + "):\n");
+     
+    for (int i = 0; i < m_DecisionList.size(); i++) {
+      RuleList rl = (RuleList) m_DecisionList.elementAt(i);
+      dLString.append("% RULE " + i + ": " + rl.toString() + "\n");
+    }
+    
+    return dLString.toString();
+  }
+
+ /**
+   * Resets the class values of all instances using voting.
+   * For each instance the class value that satisfies the most rules
+   * is choosen as new class value.
+   *
+   * @param dataset the dataset to work on
+   * @return the changed instances
+   * @throws Exception if something goes wrong
+   */
+  private Instances voteDataset(Instances dataset) throws Exception {
+    for (int i = 0; i < dataset.numInstances(); i++) {
+      Instance inst = dataset.firstInstance();
+      inst = votedReclassifyExample(inst); 
+      dataset.add(inst);
+      dataset.delete(0);
+    }  
+
+    return dataset;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new RDG1(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RandomRBF.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RandomRBF.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/classification/RandomRBF.java	(revision 29)
@@ -0,0 +1,596 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomRBF.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.datagenerators.ClassificationGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * RandomRBF data is generated by first creating a random set of centers for each class. Each center is randomly assigned a weight, a central point per attribute, and a standard deviation. To generate new instances, a center is chosen at random taking the weights of each center into consideration. Attribute values are randomly generated and offset from the center, where the overall vector has been scaled so that its length equals a value sampled randomly from the Gaussian distribution of the center. The particular center chosen determines the class of the instance.<br/>
+ *  RandomRBF data contains only numeric attributes as it is non-trivial to include nominal values.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -a &lt;num&gt;
+ *  The number of attributes (default 10).</pre>
+ * 
+ * <pre> -c &lt;num&gt;
+ *  The number of classes (default 2)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The number of centroids to use. (default 50)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby at cs dot waikato dot ac dot nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+
+public class RandomRBF
+  extends ClassificationGenerator {
+
+  /** for serialization */
+  static final long serialVersionUID = 6069033710635728720L;  
+  
+  /** Number of attribute the dataset should have */
+  protected int m_NumAttributes;
+
+  /** Number of Classes the dataset should have */
+  protected int m_NumClasses;
+
+  /** the number of centroids to use for generation */
+  protected int m_NumCentroids;
+  
+  /** the centroids */
+  protected double[][] m_centroids;
+  
+  /** the classes of the centroids */
+  protected int[] m_centroidClasses;
+  
+  /** the weights of the centroids */
+  protected double[] m_centroidWeights;
+  
+  /** the stddevs of the centroids */
+  protected double[] m_centroidStdDevs;
+
+  /**
+   * initializes the generator with default values
+   */
+  public RandomRBF() {
+    super();
+
+    setNumAttributes(defaultNumAttributes());
+    setNumClasses(defaultNumClasses());
+    setNumCentroids(defaultNumCentroids());
+  }
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "RandomRBF data is generated by first creating a random set of "
+      + "centers for each class. Each center is randomly assigned a weight, "
+      + "a central point per attribute, and a standard deviation. To "
+      + "generate new instances, a center is chosen at random taking the "
+      + "weights of each center into consideration. Attribute values are "
+      + "randomly generated and offset from the center, where the overall "
+      + "vector has been scaled so that its length equals a value sampled "
+      + "randomly from the Gaussian distribution of the center. The "
+      + "particular center chosen determines the class of the instance.\n "
+      + "RandomRBF data contains only numeric attributes as it is "
+      + "non-trivial to include nominal values.";
+  }
+
+ /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+          "\tThe number of attributes (default " 
+          + defaultNumAttributes() + ").",
+          "a", 1, "-a <num>"));
+
+    result.addElement(new Option(
+        "\tThe number of classes (default " + defaultNumClasses() + ")",
+        "c", 1, "-c <num>"));
+
+    result.add(new Option(
+              "\tThe number of centroids to use. (default " 
+              + defaultNumCentroids() + ")",
+              "C", 1, "-C <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -a &lt;num&gt;
+   *  The number of attributes (default 10).</pre>
+   * 
+   * <pre> -c &lt;num&gt;
+   *  The number of classes (default 2)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The number of centroids to use. (default 50)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('a', options);
+    if (tmpStr.length() != 0)
+      setNumAttributes(Integer.parseInt(tmpStr));
+    else
+      setNumAttributes(defaultNumAttributes());
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0)
+      setNumClasses(Integer.parseInt(tmpStr));
+    else
+      setNumClasses(defaultNumClasses());
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setNumCentroids(Integer.parseInt(tmpStr));
+    else
+      setNumCentroids(defaultNumCentroids());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-a");
+    result.add("" + getNumAttributes());
+
+    result.add("-c");
+    result.add("" + getNumClasses());
+
+    result.add("-C");
+    result.add("" + getNumCentroids());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of attributes
+   * 
+   * @return the default number of attributes
+   */
+  protected int defaultNumAttributes() {
+    return 10;
+  }
+
+  /**
+   * Sets the number of attributes the dataset should have.
+   * @param numAttributes the new number of attributes
+   */
+  public void setNumAttributes(int numAttributes) {
+    m_NumAttributes = numAttributes;
+  }
+
+  /**
+   * Gets the number of attributes that should be produced.
+   * @return the number of attributes that should be produced
+   */
+  public int getNumAttributes() { 
+    return m_NumAttributes; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes the generated data will contain.";
+  }
+
+  /**
+   * returns the default number of classes
+   * 
+   * @return the default number of classes
+   */
+  protected int defaultNumClasses() {
+    return 2;
+  }
+
+  /**
+   * Sets the number of classes the dataset should have.
+   * @param numClasses the new number of classes
+   */
+  public void setNumClasses(int numClasses) { 
+    m_NumClasses = numClasses; 
+  }
+
+  /**
+   * Gets the number of classes the dataset should have.
+   * @return the number of classes the dataset should have
+   */
+  public int getNumClasses() { 
+    return m_NumClasses; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numClassesTipText() {
+    return "The number of classes to generate.";
+  }
+
+  /**
+   * returns the default number of centroids
+   * 
+   * @return the default number of centroids
+   */
+  protected int defaultNumCentroids() {
+    return 50;
+  }
+  
+  /**
+   * Gets the number of centroids.
+   *
+   * @return the number of centroids.
+   */
+  public int getNumCentroids() { 
+    return m_NumCentroids; 
+  }
+  
+  /**
+   * Sets the number of centroids to use.
+   *
+   * @param value the number of centroids to use.
+   */
+  public void setNumCentroids(int value) { 
+    if (value > 0)
+      m_NumCentroids = value; 
+    else
+      System.out.println("At least 1 centroid is necessary (provided: " 
+          + value + ")!");
+  }  
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numCentroidsTipText() {
+    return "The number of centroids to use.";
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return true;
+  }
+
+  /**
+   * returns a random index based on the given proportions
+   *
+   * @param proportionArray     the proportions
+   * @param random              the random number generator to use
+   * @return the random index
+   */
+  protected int chooseRandomIndexBasedOnProportions(
+      double[] proportionArray, Random random) {
+
+    double      probSum;
+    double      val;
+    int         index;
+    double      sum;
+
+    probSum = Utils.sum(proportionArray);
+    val     = random.nextDouble() * probSum;
+    index   = 0;
+    sum     = 0.0;
+    
+    while ((sum <= val) && (index < proportionArray.length))
+      sum += proportionArray[index++];
+    
+    return index - 1;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    int             i;
+    int             j;
+    FastVector      atts;
+    FastVector      clsValues;
+    Random          rand;
+
+    m_Random = new Random(getSeed());
+    rand     = getRandom();
+
+    // number of examples is the same as given per option
+    setNumExamplesAct(getNumExamples());
+
+    // initialize centroids
+    m_centroids       = new double[getNumCentroids()][getNumAttributes()];
+    m_centroidClasses = new int[getNumCentroids()];
+    m_centroidWeights = new double[getNumCentroids()];
+    m_centroidStdDevs = new double[getNumCentroids()];
+
+    for (i = 0; i < getNumCentroids(); i++) {
+      for (j = 0; j < getNumAttributes(); j++)
+        m_centroids[i][j] = rand.nextDouble();
+      m_centroidClasses[i] = rand.nextInt(getNumClasses());
+      m_centroidWeights[i] = rand.nextDouble();
+      m_centroidStdDevs[i] = rand.nextDouble();
+    }
+
+    // initialize dataset format
+    atts = new FastVector();
+    for (i = 0; i < getNumAttributes(); i++)
+      atts.addElement(new Attribute("a" + i));
+
+    clsValues = new FastVector();
+    for (i = 0; i < getNumClasses(); i++)
+      clsValues.addElement("c" + i);
+    atts.addElement(new Attribute("class", clsValues));
+    
+    m_DatasetFormat = new Instances(getRelationNameToUse(), atts, 0);
+    
+    return m_DatasetFormat;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    Instance    result;
+    int         centroid;
+    double[]    atts;
+    double      magnitude;
+    double      desiredMag;
+    double      scale;
+    int         i;
+    double      label;
+    Random      rand;
+
+    result = null;
+    rand   = getRandom();
+
+    if (m_DatasetFormat == null)
+      throw new Exception("Dataset format not defined.");
+
+    // generate class label based on class probs
+    centroid = chooseRandomIndexBasedOnProportions(m_centroidWeights, rand);
+    label    = m_centroidClasses[centroid];
+
+    // generate attributes
+    atts = new double[getNumAttributes() + 1];
+    for (i = 0; i < getNumAttributes(); i++)
+      atts[i] = (rand.nextDouble() * 2.0) - 1.0;
+    atts[atts.length - 1] = label;
+    
+    magnitude = 0.0;
+    for (i = 0; i < getNumAttributes(); i++)
+      magnitude += atts[i] * atts[i];
+    
+    magnitude  = Math.sqrt(magnitude);
+    desiredMag = rand.nextGaussian() * m_centroidStdDevs[centroid];
+    scale      = desiredMag / magnitude;
+    for (i = 0; i < getNumAttributes(); i++) {
+      atts[i] *= scale;
+      atts[i] += m_centroids[centroid][i];
+      result   = new DenseInstance(1.0, atts);
+    }
+
+    // dataset reference
+    result.setDataset(m_DatasetFormat);
+    
+    return result;
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    Instances       result;
+    int             i;
+
+    result   = new Instances(m_DatasetFormat, 0);
+    m_Random = new Random(getSeed());
+
+    for (i = 0; i < getNumExamplesAct(); i++)
+      result.add(generateExample());
+    
+    return result;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    StringBuffer        result;
+    int                 i;
+
+    result = new StringBuffer();
+
+    result.append("%\n");
+    result.append("% centroids:\n");
+    for (i = 0; i < getNumCentroids(); i++)
+      result.append(
+          "% " + i + ".: " + Utils.arrayToString(m_centroids[i]) + "\n");
+    result.append("%\n");
+    result.append(
+        "% centroidClasses: " + Utils.arrayToString(m_centroidClasses) + "\n");
+    result.append("%\n");
+    result.append(
+        "% centroidWeights: " + Utils.arrayToString(m_centroidWeights) + "\n");
+    result.append("%\n");
+    result.append(
+        "% centroidStdDevs: " + Utils.arrayToString(m_centroidStdDevs) + "\n");
+    result.append("%\n");
+    
+    return result.toString();
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new RandomRBF(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/Expression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/Expression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/Expression.java	(revision 29)
@@ -0,0 +1,435 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Expression.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.regression;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.unsupervised.attribute.AddExpression;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A data generator for generating y according to a given expression out of randomly generated x.<br/>
+ * E.g., the mexican hat can be generated like this:<br/>
+ *    sin(abs(a1)) / abs(a1)<br/>
+ * In addition to this function, the amplitude can be changed and gaussian noise can be added.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  The amplitude multiplier (default 1.0).</pre>
+ * 
+ * <pre> -R &lt;num&gt;..&lt;num&gt;
+ *  The range x is randomly drawn from (default -10.0..10.0).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The noise rate (default 0.0).</pre>
+ * 
+ * <pre> -V &lt;num&gt;
+ *  The noise variance (default 1.0).</pre>
+ * 
+ * <pre> -E &lt;expression&gt;
+ *  The expression to use for generating y out of x 
+ *  (default sin(abs(a1)) / abs(a1)).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see     AddExpression
+ * @see     MexicanHat
+ */
+
+public class Expression
+  extends MexicanHat {
+
+  /** for serialization */
+  static final long serialVersionUID = -4237047357682277211L;  
+  
+  /** the expression for computing y */
+  protected String m_Expression;
+
+  /** the filter for generating y out of x */
+  protected AddExpression m_Filter;
+
+  /** the input data structure for the filter */
+  protected Instances m_RawData;
+  
+  /**
+   * initializes the generator
+   */
+  public Expression() {
+    super();
+
+    setExpression(defaultExpression());
+  }
+  
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A data generator for generating y according to a given expression "
+        + "out of randomly generated x.\n"
+        + "E.g., the mexican hat can be generated like this:\n"
+        + "   sin(abs(a1)) / abs(a1)\n"
+        + "In addition to this function, the amplitude can be changed and "
+        + "gaussian noise can be added.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+              "\tThe expression to use for generating y out of x \n"
+              + "\t(default " + defaultExpression() + ").",
+              "E", 1, "-E <expression>"));
+
+    return result.elements();
+  }
+  
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  The amplitude multiplier (default 1.0).</pre>
+   * 
+   * <pre> -R &lt;num&gt;..&lt;num&gt;
+   *  The range x is randomly drawn from (default -10.0..10.0).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The noise rate (default 0.0).</pre>
+   * 
+   * <pre> -V &lt;num&gt;
+   *  The noise variance (default 1.0).</pre>
+   * 
+   * <pre> -E &lt;expression&gt;
+   *  The expression to use for generating y out of x 
+   *  (default sin(abs(a1)) / abs(a1)).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+   
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setExpression(tmpStr);
+    else
+      setExpression(defaultExpression());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator BIRCHCluster.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-E"); 
+    result.add("" + getExpression());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String amplitudeTipText() {
+    return "The amplitude to multiply the y value with.";
+  }
+
+  /**
+   * returns the default expression
+   * 
+   * @return the default expression
+   */
+  protected String defaultExpression() {
+    return "sin(abs(a1)) / abs(a1)";
+  }
+
+  /**
+   * Gets the mathematical expression for generating y out of x
+   *
+   * @return the expression for computing y
+   */
+  public String getExpression() { 
+    return m_Expression; 
+  }
+  
+  /**
+   * Sets the mathematical expression to generate y out of x.
+   *
+   * @param value the expression for computing y
+   */
+  public void setExpression(String value) {
+    if (value.length() != 0)
+      m_Expression = value;
+    else
+      throw new IllegalArgumentException(
+          "An expression has to be provided!");
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String expressionTipText() {
+    return "The expression for generating y out of x.";
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return true;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    FastVector      atts;
+
+    // initialize input format
+    atts = new FastVector();
+    atts.addElement(new Attribute("x"));
+    
+    m_RawData = new Instances(getRelationNameToUse(), atts, 0);
+
+    m_Filter = new AddExpression();
+    m_Filter.setName("y");
+    m_Filter.setExpression(getExpression());
+    m_Filter.setInputFormat(m_RawData);
+
+    return super.defineDataFormat();
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    Instance    result;
+    Random      rand;
+    double      x;
+    double      y;
+    double[]    atts;
+    Instance    inst;
+
+    result = null;
+    rand   = getRandom();
+
+    if (m_DatasetFormat == null)
+      throw new Exception("Dataset format not defined.");
+
+    // random x
+    x = rand.nextDouble();
+    // fit into range
+    x = x * (getMaxRange() - getMinRange()) + getMinRange();
+    
+    // generate y
+    atts    = new double[1];
+    atts[0] = x;
+    inst    = new DenseInstance(1.0, atts);
+    m_Filter.input(inst);
+    m_Filter.batchFinished();
+    inst = m_Filter.output();
+    
+    // noise
+    y = inst.value(1) + getAmplitude() 
+            * m_NoiseRandom.nextGaussian() 
+            * getNoiseRate() * getNoiseVariance();
+
+    // generate attributes
+    atts = new double[m_DatasetFormat.numAttributes()];
+    
+    atts[0] = x;
+    atts[1] = y;
+    result = new DenseInstance(1.0, atts);
+
+    // dataset reference
+    result.setDataset(m_DatasetFormat);
+    
+    return result;
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    Instances       result;
+    int             i;
+
+    result   = new Instances(m_DatasetFormat, 0);
+    m_Random = new Random(getSeed());
+
+    for (i = 0; i < getNumExamplesAct(); i++)
+      result.add(generateExample());
+    
+    return result;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new Expression(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/MexicanHat.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/MexicanHat.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/classifiers/regression/MexicanHat.java	(revision 29)
@@ -0,0 +1,650 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MexicanHat.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.classifiers.regression;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.datagenerators.RegressionGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A data generator for the simple 'Mexian Hat' function:<br/>
+ *    y = sin|x| / |x|<br/>
+ * In addition to this simple function, the amplitude can be changed and gaussian noise can be added.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -n &lt;num&gt;
+ *  The number of examples to generate (default 100)</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  The amplitude multiplier (default 1.0).</pre>
+ * 
+ * <pre> -R &lt;num&gt;..&lt;num&gt;
+ *  The range x is randomly drawn from (default -10.0..10.0).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The noise rate (default 0.0).</pre>
+ * 
+ * <pre> -V &lt;num&gt;
+ *  The noise variance (default 1.0).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+
+public class MexicanHat
+  extends RegressionGenerator {
+
+  /** for serialization */
+  static final long serialVersionUID = 4577016375261512975L;
+  
+  /** the amplitude of y */
+  protected double m_Amplitude;
+
+  /** the lower boundary of the range, x is drawn from */
+  protected double m_MinRange;
+
+  /** the upper boundary of the range, x is drawn from */
+  protected double m_MaxRange;
+
+  /** the rate of the gaussian noise */
+  protected double m_NoiseRate;
+
+  /** the variance of the gaussian noise */
+  protected double m_NoiseVariance;
+
+  /** the random number generator for the noise */
+  protected Random m_NoiseRandom = null;
+
+  /**
+   * initializes the generator
+   */
+  public MexicanHat() {
+    super();
+
+    setAmplitude(defaultAmplitude());
+    setMinRange(defaultMinRange());
+    setMaxRange(defaultMaxRange());
+    setNoiseRate(defaultNoiseRate());
+    setNoiseVariance(defaultNoiseVariance());
+  }
+  
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A data generator for the simple 'Mexian Hat' function:\n"
+        + "   y = sin|x| / |x|\n"
+        + "In addition to this simple function, the amplitude can be changed and "
+        + "gaussian noise can be added.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+              "\tThe amplitude multiplier (default " 
+              + defaultAmplitude() + ").",
+              "A", 1, "-A <num>"));
+
+    result.addElement(new Option(
+              "\tThe range x is randomly drawn from (default " 
+              + defaultMinRange() + ".." + defaultMaxRange() + ").",
+              "R", 1, "-R <num>..<num>"));
+
+    result.addElement(new Option(
+              "\tThe noise rate (default " 
+              + defaultNoiseRate() + ").",
+              "N", 1, "-N <num>"));
+
+    result.addElement(new Option(
+              "\tThe noise variance (default "
+              + defaultNoiseVariance() + ").",
+              "V", 1, "-V <num>"));
+
+    return result.elements();
+  }
+  
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -n &lt;num&gt;
+   *  The number of examples to generate (default 100)</pre>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  The amplitude multiplier (default 1.0).</pre>
+   * 
+   * <pre> -R &lt;num&gt;..&lt;num&gt;
+   *  The range x is randomly drawn from (default -10.0..10.0).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The noise rate (default 0.0).</pre>
+   * 
+   * <pre> -V &lt;num&gt;
+   *  The noise variance (default 1.0).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+   
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.length() != 0)
+      setAmplitude(Double.parseDouble(tmpStr));
+    else
+      setAmplitude(defaultAmplitude());
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setRange(tmpStr);
+    else
+      setRange(defaultMinRange() + ".." + defaultMaxRange());
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNoiseRate(Double.parseDouble(tmpStr));
+    else
+      setNoiseRate(defaultNoiseRate());
+
+    tmpStr = Utils.getOption('V', options);
+    if (tmpStr.length() != 0)
+      setNoiseVariance(Double.parseDouble(tmpStr));
+    else
+      setNoiseVariance(defaultNoiseVariance());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator BIRCHCluster.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = removeBlacklist(super.getOptions());
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-A"); 
+    result.add("" + getAmplitude());
+
+    result.add("-R"); 
+    result.add("" + getRange());
+
+    result.add("-N"); 
+    result.add("" + getNoiseRate());
+
+    result.add("-V"); 
+    result.add("" + getNoiseVariance());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default amplitude
+   * 
+   * @return the default amplitude
+   */
+  protected double defaultAmplitude() {
+    return 1.0;
+  }
+
+  /**
+   * Gets the amplitude multiplier.
+   *
+   * @return the amplitude multiplier
+   */
+  public double getAmplitude() { 
+    return m_Amplitude; 
+  }
+  
+  /**
+   * Sets the amplitude multiplier.
+   *
+   * @param value the amplitude multiplier
+   */
+  public void setAmplitude(double value) {
+    m_Amplitude = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String amplitudeTipText() {
+    return "The amplitude of the mexican hat.";
+  }
+
+  /**
+   * Sets the upper and lower boundary for the range of x
+   *
+   * @param fromTo the string containing the upper and lower boundary for
+   *               the range of x, separated by ..
+   */
+  protected void setRange(String fromTo) {
+    int i = fromTo.indexOf("..");
+    String from = fromTo.substring(0, i);
+    setMinRange(Double.valueOf(from).doubleValue());
+    String to = fromTo.substring(i + 2, fromTo.length());
+    setMaxRange(Double.valueOf(to).doubleValue());
+  }
+
+  /**
+   * Gets the upper and lower boundary for the range of x
+   *
+   * @return the string containing the upper and lower boundary for
+   *         the range of x, separated by ..
+   */
+  protected String getRange() {
+    String fromTo = "" 
+                    + Utils.doubleToString(getMinRange(), 2) + ".."
+                    + Utils.doubleToString(getMaxRange(), 2);
+    return fromTo;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  protected String rangeTipText() {
+    return "The upper and lower boundary for the range x is drawn from randomly.";
+  }
+
+  /**
+   * returns the default min range
+   * 
+   * @return the default min range
+   */
+  protected double defaultMinRange() {
+    return -10;
+  }
+
+  /**
+   * Sets the lower boundary for the range of x
+   *
+   * @param value the lower boundary
+   */
+  public void setMinRange(double value) {
+    m_MinRange = value;
+  }
+
+  /**
+   * Gets the lower boundary for the range of x
+   *
+   * @return the lower boundary for the range of x
+   */
+  public double getMinRange() {
+    return m_MinRange;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String minRangeTipText() {
+    return "The lower boundary for the range x is drawn from randomly.";
+  }
+
+  /**
+   * returns the default max range
+   * 
+   * @return the default max range
+   */
+  protected double defaultMaxRange() {
+    return 10;
+  }
+
+  /**
+   * Sets the upper boundary for the range of x
+   *
+   * @param value the upper boundary
+   */
+  public void setMaxRange(double value) {
+    m_MaxRange = value;
+  }
+
+  /**
+   * Gets the upper boundary for the range of x
+   *
+   * @return the upper boundary for the range of x
+   */
+  public double getMaxRange() {
+    return m_MaxRange;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String maxRangeTipText() {
+    return "The upper boundary for the range x is drawn from randomly.";
+  }
+
+  /**
+   * returns the default gaussian noise rate
+   * 
+   * @return the default gaussian noise rate
+   */
+  protected double defaultNoiseRate() {
+    return 0.0;
+  }
+
+  /**
+   * Gets the gaussian noise rate.
+   *
+   * @return the gaussian noise rate
+   */
+  public double getNoiseRate() { 
+    return m_NoiseRate; 
+  }
+  
+  /**
+   * Sets the gaussian noise rate.
+   *
+   * @param value the gaussian noise rate
+   */
+  public void setNoiseRate(double value) {
+    m_NoiseRate = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String noiseRateTipText() {
+    return "The gaussian noise rate to use.";
+  }
+
+  /**
+   * returns the default variance of the noise rate
+   * 
+   * @return the default variance of the noise rate
+   */
+  protected double defaultNoiseVariance() {
+    return 1.0;
+  }
+
+  /**
+   * Gets the noise variance
+   *
+   * @return the noise variance
+   */
+  public double getNoiseVariance() { 
+    return m_NoiseVariance; 
+  }
+  
+  /**
+   * Sets the noise variance
+   *
+   * @param value the noise variance
+   */
+  public void setNoiseVariance(double value) {
+    if (value > 0)
+      m_NoiseVariance = value;
+    else
+      throw new IllegalArgumentException(
+          "Noise variance needs to be > 0 (provided: " + value + ")!");
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String noiseVarianceTipText() {
+    return "The noise variance to use.";
+  }
+
+  /**
+   * Return if single mode is set for the given data generator
+   * mode depends on option setting and or generator type.
+   * 
+   * @return single mode flag
+   * @throws Exception if mode is not set yet
+   */
+  public boolean getSingleModeFlag() throws Exception {
+    return true;
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   * Must be called before the generateExample or generateExamples
+   * methods are used.
+   * Re-initializes the random number generator with the given seed.
+   *
+   * @return the format for the dataset 
+   * @throws Exception if the generating of the format failed
+   * @see  #getSeed()
+   */
+  public Instances defineDataFormat() throws Exception {
+    FastVector      atts;
+
+    m_Random      = new Random(getSeed());
+    m_NoiseRandom = new Random(getSeed());
+
+    // number of examples is the same as given per option
+    setNumExamplesAct(getNumExamples());
+
+    // initialize dataset format
+    atts = new FastVector();
+    atts.addElement(new Attribute("x"));
+    atts.addElement(new Attribute("y"));
+    
+    m_DatasetFormat = new Instances(getRelationNameToUse(), atts, 0);
+    
+    return m_DatasetFormat;
+  }
+
+  /**
+   * Generates one example of the dataset. 
+   *
+   * @return the generated example
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExamples
+   * which means in non single mode
+   */
+  public Instance generateExample() throws Exception {
+    Instance    result;
+    Random      rand;
+    double      x;
+    double      y;
+    double[]    atts;
+
+    result = null;
+    rand   = getRandom();
+
+    if (m_DatasetFormat == null)
+      throw new Exception("Dataset format not defined.");
+
+    // generate attributes
+    atts = new double[m_DatasetFormat.numAttributes()];
+    
+    // random x
+    x = rand.nextDouble();
+    // fit into range
+    x = x * (getMaxRange() - getMinRange()) + getMinRange();
+    
+    // generate y
+    if (Utils.eq(x, 0))
+      y = getAmplitude();
+    else
+      y = getAmplitude() 
+          * StrictMath.sin(StrictMath.abs(x)) / StrictMath.abs(x);
+    // noise
+    y = y + getAmplitude() 
+            * m_NoiseRandom.nextGaussian() 
+            * getNoiseRate() * getNoiseVariance();
+
+    atts[0] = x;
+    atts[1] = y;
+    result = new DenseInstance(1.0, atts);
+
+    // dataset reference
+    result.setDataset(m_DatasetFormat);
+    
+    return result;
+  }
+
+  /**
+   * Generates all examples of the dataset. Re-initializes the random number
+   * generator with the given seed, before generating instances.
+   *
+   * @return the generated dataset
+   * @throws Exception if the format of the dataset is not yet defined
+   * @throws Exception if the generator only works with generateExample,
+   * which means in single mode
+   * @see   #getSeed()
+   */
+  public Instances generateExamples() throws Exception {
+    Instances       result;
+    int             i;
+
+    result   = new Instances(m_DatasetFormat, 0);
+    m_Random = new Random(getSeed());
+
+    for (i = 0; i < getNumExamplesAct(); i++)
+      result.add(generateExample());
+
+    return result;
+  }
+
+  /**
+   * Generates a comment string that documentates the data generator.
+   * By default this string is added at the beginning of the produced output
+   * as ARFF file type, next after the options.
+   * 
+   * @return string contains info about the generated rules
+   */
+  public String generateStart () {
+    return "";
+  }
+
+  /**
+   * Generates a comment string that documentats the data generator.
+   * By default this string is added at the end of theproduces output
+   * as ARFF file type.
+   * 
+   * @return string contains info about the generated rules
+   * @throws Exception if the generating of the documentaion fails
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new MexicanHat(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/BIRCHCluster.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/BIRCHCluster.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/BIRCHCluster.java	(revision 29)
@@ -0,0 +1,1528 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BIRCHCluster.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.datagenerators.ClusterGenerator;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Cluster data generator designed for the BIRCH System<br/>
+ * <br/>
+ * Dataset is generated with instances in K clusters.<br/>
+ * Instances are 2-d data points.<br/>
+ * Each cluster is characterized by the number of data points in itits radius and its center. The location of the cluster centers isdetermined by the pattern parameter. Three patterns are currentlysupported grid, sine and random.<br/>
+ * <br/>
+ * For more information refer to:<br/>
+ * <br/>
+ * Tian Zhang, Raghu Ramakrishnan, Miron Livny: BIRCH: An Efficient Data Clustering Method for Very Large Databases. In: ACM SIGMOD International Conference on Management of Data, 103-114, 1996.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Zhang1996,
+ *    author = {Tian Zhang and Raghu Ramakrishnan and Miron Livny},
+ *    booktitle = {ACM SIGMOD International Conference on Management of Data},
+ *    pages = {103-114},
+ *    publisher = {ACM Press},
+ *    title = {BIRCH: An Efficient Data Clustering Method for Very Large Databases},
+ *    year = {1996}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -a &lt;num&gt;
+ *  The number of attributes (default 10).</pre>
+ * 
+ * <pre> -c
+ *  Class Flag, if set, the cluster is listed in extra attribute.</pre>
+ * 
+ * <pre> -b &lt;range&gt;
+ *  The indices for boolean attributes.</pre>
+ * 
+ * <pre> -m &lt;range&gt;
+ *  The indices for nominal attributes.</pre>
+ * 
+ * <pre> -k &lt;num&gt;
+ *  The number of clusters (default 4)</pre>
+ * 
+ * <pre> -G
+ *  Set pattern to grid (default is random).
+ *  This flag cannot be used at the same time as flag I.
+ *  The pattern is random, if neither flag G nor flag I is set.</pre>
+ * 
+ * <pre> -I
+ *  Set pattern to sine (default is random).
+ *  This flag cannot be used at the same time as flag I.
+ *  The pattern is random, if neither flag G nor flag I is set.</pre>
+ * 
+ * <pre> -N &lt;num&gt;..&lt;num&gt;
+ *  The range of number of instances per cluster (default 1..50).
+ *  Lower number must be between 0 and 2500,
+ *  upper number must be between 50 and 2500.</pre>
+ * 
+ * <pre> -R &lt;num&gt;..&lt;num&gt;
+ *  The range of radius per cluster (default 0.1..1.4142135623730951).
+ *  Lower number must be between 0 and SQRT(2), 
+ *  upper number must be between SQRT(2) and SQRT(32).</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  The distance multiplier (default 4.0).</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The number of cycles (default 4).</pre>
+ * 
+ * <pre> -O
+ *  Flag for input order is ORDERED. If flag is not set then 
+ *  input order is RANDOMIZED. RANDOMIZED is currently not 
+ *  implemented, therefore is the input order always ORDERED.</pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  The noise rate in percent (default 0.0).
+ *  Can be between 0% and 30%. (Remark: The original 
+ *  algorithm only allows noise up to 10%.)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $ 
+ */
+public class BIRCHCluster 
+  extends ClusterGenerator
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -334820527230755027L;
+  
+  /** Number of Clusters the dataset should have */
+  protected int m_NumClusters;
+
+  /** minimal number of instances per cluster (option N)*/ 
+  private int m_MinInstNum;
+  
+  /** maximal number of instances per cluster (option N)*/ 
+  private int m_MaxInstNum;
+  
+  /** minimum radius (option R)*/ 
+  private double m_MinRadius;
+  
+  /** maximum radius (option R)*/ 
+  private double m_MaxRadius;
+  
+  /**  Constant set for choice of pattern. (option G)*/
+  public static final int GRID = 0;
+  /**  Constant set for choice of pattern. (option I)*/
+  public static final int SINE = 1;
+  /**  Constant set for choice of pattern. (default)*/
+  public static final int RANDOM = 2;
+  /** the pattern tags */
+  public static final Tag[] TAGS_PATTERN = {
+    new Tag(GRID,   "Grid"),
+    new Tag(SINE,   "Sine"),
+    new Tag(RANDOM, "Random")
+  };
+  
+  /** pattern (changed with options G or S)*/ 
+  private int m_Pattern;
+  
+  /** distance multiplier (option M)*/
+  private double m_DistMult;
+
+  /** number of cycles (option C)*/
+  private int m_NumCycles;
+
+  /**  Constant set for input order (option O)*/
+  public static final int ORDERED = 0;
+  /**  Constant set for input order (default)*/
+  public static final int RANDOMIZED = 1;
+  /** the input order tags */
+  public static final Tag[] TAGS_INPUTORDER = {
+    new Tag(ORDERED,    "ordered"),
+    new Tag(RANDOMIZED, "randomized")
+  };
+
+  /** input order (changed with option O)*/ 
+  private int m_InputOrder;
+
+  /** noise rate in percent (option P,  between 0 and 30)*/ 
+  private double m_NoiseRate;
+
+  /** cluster list */
+  private FastVector m_ClusterList;
+
+  // following are used for pattern is GRID
+  /** grid size*/
+  private int m_GridSize;
+
+  /** grid width*/
+  private double m_GridWidth;
+  
+  /**
+   * class to represent cluster
+   */
+  private class Cluster 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -8336901069823498140L;    
+    
+    /** number of instances for this cluster */
+    private int m_InstNum;
+
+    /** radius of cluster
+     *   variance is radius ** 2 / 2 */
+    private double m_Radius;
+
+    /** center of cluster = array of Double values */
+    private double[] m_Center;
+
+    /**
+     * Constructor, used for pattern = RANDOM
+     *
+     * @param instNum the number of instances
+     * @param radius radius of the cluster
+     * @param random the random number generator to use 
+     */
+    private Cluster(int instNum, double radius, Random random) {
+      m_InstNum = instNum;
+      m_Radius = radius;
+      m_Center = new double[getNumAttributes()];
+      for (int i = 0; i < getNumAttributes(); i++) {
+	m_Center[i] = random.nextDouble() * (double) m_NumClusters;
+      }
+    }
+
+    /**
+     * Constructor, used for pattern = GRID
+     *
+     * @param instNum the number of instances
+     * @param radius radius of the cluster
+     * @param gridVector vector for grid positions
+     * @param gridWidth factor for grid position
+     */
+      // center is defined in the constructor of cluster
+    private Cluster(int instNum,
+		    double radius,
+		    int[] gridVector,
+		    double gridWidth) {
+      m_InstNum = instNum;
+      m_Radius = radius;
+      m_Center = new double[getNumAttributes()];
+      for (int i = 0; i < getNumAttributes(); i++) {
+	m_Center[i] = ((double) gridVector[i] + 1.0) * gridWidth;
+      }
+      
+    }
+   
+    /**
+     * returns the number of instances
+     * 
+     * @return the number of instances
+     */
+    private int getInstNum() { 
+      return m_InstNum; 
+    }
+    
+    /**
+     * returns the radius
+     * 
+     * @return the radius
+     */
+    private double getRadius() { 
+      return m_Radius; 
+    }
+    
+    /**
+     * returns the variance
+     * 
+     * @return the variance
+     */
+    private double getVariance() { 
+      return Math.pow(m_Radius, 2.0) / 2.0; 
+    }
+    
+    /**
+     * returns the standard deviation
+     * 
+     * @return the standard deviation
+     */
+    private double getStdDev() { 
+      return (m_Radius / Math.pow(2.0, 0.5)); 
+    }
+    
+    /**
+     * returns the centers
+     * 
+     * @return the centers
+     */
+    private double[] getCenter() { 
+      return m_Center; 
+    }
+    
+    /**
+     * returns the center value for a given dimension
+     * 
+     * @param dimension the dimension to return the center for
+     * @return the center value for the given dimension
+     * @throws Exception if dimension invalid
+     */
+    private double getCenterValue(int dimension) throws Exception {
+      if (dimension >= m_Center.length)
+	throw new Exception("Current system has only " +
+			    m_Center.length + " dimensions.");
+      return m_Center[dimension];
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  } // end class Cluster
+
+  /**
+   * class to represent Vector for placement of the center in space
+   */
+  private class GridVector 
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    static final long serialVersionUID = -1900309948991039522L;
+    
+    /** array of integer */
+    private int[] m_GridVector;
+
+    /**  one higher then the highest possible integer value
+     *  in any of the integers in the gridvector */
+    private int m_Base;
+
+    /** size of vector */
+    private int m_Size;
+
+    /**
+     * Constructor
+     *
+     * @param numDim number of dimensions = number of attributes
+     * @param base is one higher then the highest possible integer value
+     * in any of the integers in the gridvector
+     */
+    private GridVector(int numDim, int base) {
+      m_Size = numDim;
+      m_Base = base;
+      m_GridVector = new int [numDim];
+      for (int i = 0; i < numDim; i++)
+	m_GridVector[i] = 0;
+    }
+
+    /**
+     * returns the integer array
+     *
+     * @return the integer array
+     */
+    private int[] getGridVector() {
+      return m_GridVector;
+    }
+
+    /**
+     * Overflow has occurred when integer is zero.
+     *
+     *@param digit the input integer
+     *@return true if digit is 0
+     */
+    private boolean overflow(int digit) {
+      return (digit == 0);
+    }
+
+    /**
+     * Adds one to integer and sets to zero, if new value was
+     * equal m_Base.
+     *
+     *@param digit the input integer
+     *@return new integer object
+     */
+    private int addOne(int digit) {
+      int value = digit + 1;
+      if (value >= m_Base) value = 0;
+      return value;
+    }
+
+    /**
+     * add 1 to vector
+     */
+    private void addOne() {
+      m_GridVector[0] = addOne(m_GridVector[0]);
+      int i = 1;
+      while (overflow(m_GridVector[i - 1]) && i < m_Size) {
+        m_GridVector[i] = addOne(m_GridVector[i]);
+	i++;
+      }
+	
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  } // end class GridVector
+  
+  /**
+   * initializes the generator with default values
+   */
+  public BIRCHCluster() {
+    super();
+
+    setNumClusters(defaultNumClusters());
+    setMinInstNum(defaultMinInstNum());
+    setMaxInstNum(defaultMaxInstNum());
+    setMinRadius(defaultMinRadius());
+    setMaxRadius(defaultMaxRadius());
+    setPattern(defaultPattern());
+    setDistMult(defaultDistMult());
+    setNumCycles(defaultNumCycles());
+    setInputOrder(defaultInputOrder());
+    setNoiseRate(defaultNoiseRate());
+  }
+  
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Cluster data generator designed for the BIRCH System\n\n"
+      + "Dataset is generated with instances in K clusters.\n"
+      + "Instances are 2-d data points.\n"
+      + "Each cluster is characterized by the number of data points in it"
+      + "its radius and its center. The location of the cluster centers is"
+      + "determined by the pattern parameter. Three patterns are currently"
+      + "supported grid, sine and random.\n\n"
+      + "For more information refer to:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Tian Zhang and Raghu Ramakrishnan and Miron Livny");
+    result.setValue(Field.TITLE, "BIRCH: An Efficient Data Clustering Method for Very Large Databases");
+    result.setValue(Field.BOOKTITLE, "ACM SIGMOD International Conference on Management of Data");
+    result.setValue(Field.YEAR, "1996");
+    result.setValue(Field.PAGES, "103-114");
+    result.setValue(Field.PUBLISHER, "ACM Press");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+          "\tThe number of clusters (default "
+          + defaultNumClusters() + ")",
+          "k", 1, "-k <num>"));
+
+    result.addElement(new Option(
+          "\tSet pattern to grid (default is random).\n"
+	  + "\tThis flag cannot be used at the same time as flag I.\n"
+	  + "\tThe pattern is random, if neither flag G nor flag I is set.",
+          "G", 0, "-G"));
+
+    result.addElement(new Option(
+          "\tSet pattern to sine (default is random).\n"
+	  + "\tThis flag cannot be used at the same time as flag I.\n"
+	  + "\tThe pattern is random, if neither flag G nor flag I is set.",
+          "I", 0, "-I"));
+
+    result.addElement(new Option(
+          "\tThe range of number of instances per cluster (default "
+          + defaultMinInstNum() + ".." + defaultMaxInstNum() + ").\n"
+          + "\tLower number must be between 0 and 2500,\n"
+          + "\tupper number must be between 50 and 2500.",
+          "N", 1, "-N <num>..<num>"));
+
+    result.addElement(new Option(
+          "\tThe range of radius per cluster (default "
+          + defaultMinRadius() + ".." + defaultMaxRadius() + ").\n"
+          + "\tLower number must be between 0 and SQRT(2), \n"
+          + "\tupper number must be between SQRT(2) and SQRT(32).",
+          "R", 1, "-R <num>..<num>"));
+
+    result.addElement(new Option(
+          "\tThe distance multiplier (default " 
+          + defaultDistMult() + ").",
+          "M", 1, "-M <num>"));
+
+    result.addElement(new Option(
+          "\tThe number of cycles (default "
+          + defaultNumCycles() + ").",
+          "C", 1, "-C <num>"));
+
+    result.addElement(new Option(
+  	  "\tFlag for input order is ORDERED. If flag is not set then \n"
+	  + "\tinput order is RANDOMIZED. RANDOMIZED is currently not \n"
+	  + "\timplemented, therefore is the input order always ORDERED.",
+          "O", 0, "-O"));
+
+    result.addElement(new Option(
+          "\tThe noise rate in percent (default " 
+          + defaultNoiseRate() + ").\n"
+          + "\tCan be between 0% and 30%. (Remark: The original \n"
+          + "\talgorithm only allows noise up to 10%.)",
+          "P", 1, "-P <num>"));
+
+    return result.elements();
+  }
+  
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -a &lt;num&gt;
+   *  The number of attributes (default 10).</pre>
+   * 
+   * <pre> -c
+   *  Class Flag, if set, the cluster is listed in extra attribute.</pre>
+   * 
+   * <pre> -b &lt;range&gt;
+   *  The indices for boolean attributes.</pre>
+   * 
+   * <pre> -m &lt;range&gt;
+   *  The indices for nominal attributes.</pre>
+   * 
+   * <pre> -k &lt;num&gt;
+   *  The number of clusters (default 4)</pre>
+   * 
+   * <pre> -G
+   *  Set pattern to grid (default is random).
+   *  This flag cannot be used at the same time as flag I.
+   *  The pattern is random, if neither flag G nor flag I is set.</pre>
+   * 
+   * <pre> -I
+   *  Set pattern to sine (default is random).
+   *  This flag cannot be used at the same time as flag I.
+   *  The pattern is random, if neither flag G nor flag I is set.</pre>
+   * 
+   * <pre> -N &lt;num&gt;..&lt;num&gt;
+   *  The range of number of instances per cluster (default 1..50).
+   *  Lower number must be between 0 and 2500,
+   *  upper number must be between 50 and 2500.</pre>
+   * 
+   * <pre> -R &lt;num&gt;..&lt;num&gt;
+   *  The range of radius per cluster (default 0.1..1.4142135623730951).
+   *  Lower number must be between 0 and SQRT(2), 
+   *  upper number must be between SQRT(2) and SQRT(32).</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  The distance multiplier (default 4.0).</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The number of cycles (default 4).</pre>
+   * 
+   * <pre> -O
+   *  Flag for input order is ORDERED. If flag is not set then 
+   *  input order is RANDOMIZED. RANDOMIZED is currently not 
+   *  implemented, therefore is the input order always ORDERED.</pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  The noise rate in percent (default 0.0).
+   *  Can be between 0% and 30%. (Remark: The original 
+   *  algorithm only allows noise up to 10%.)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+   
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption('k', options);
+    if (tmpStr.length() != 0)
+      setNumClusters(Integer.parseInt(tmpStr));
+    else
+      setNumClusters(defaultNumClusters());
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setInstNums(tmpStr);
+    else
+      setInstNums(defaultMinInstNum() + ".." + defaultMaxInstNum());
+    
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setRadiuses(tmpStr);
+    else
+      setRadiuses(defaultMinRadius() + ".." + defaultMaxRadius());
+
+    boolean grid = Utils.getFlag('G', options);
+    boolean sine = Utils.getFlag('I', options);
+
+    if (grid && sine)
+      throw new Exception("Flags -G and -I can only be set mutually exclusiv.");
+
+    setPattern(new SelectedTag(RANDOM, TAGS_PATTERN));
+    if (grid)
+      setPattern(new SelectedTag(GRID, TAGS_PATTERN));
+    if (sine)
+      setPattern(new SelectedTag(SINE, TAGS_PATTERN));
+
+    tmpStr= Utils.getOption('M', options);
+    if (tmpStr.length() != 0) {
+      if (!grid)
+	throw new Exception("Option M can only be used with GRID pattern.");
+      setDistMult(Double.parseDouble(tmpStr));
+    }
+    else {
+      setDistMult(defaultDistMult());
+    }
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0) {
+      if (!sine)
+	throw new Exception("Option C can only be used with SINE pattern.");
+      setNumCycles(Integer.parseInt(tmpStr));
+    } 
+    else {
+      setNumCycles(defaultNumCycles());
+    }
+
+    if (Utils.getFlag('O', options))
+      setInputOrder(new SelectedTag(ORDERED, TAGS_INPUTORDER));
+    else
+      setInputOrder(defaultInputOrder());
+
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setNoiseRate(Double.parseDouble(tmpStr));
+    else
+      setNoiseRate(defaultNoiseRate());
+  }
+
+  /**
+   * Gets the current settings of the datagenerator BIRCHCluster.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    result.add("-k");
+    result.add("" + getNumClusters());
+    
+    result.add("-N"); 
+    result.add("" + getInstNums());
+
+    result.add("-R"); 
+    result.add("" + getRadiuses());
+
+    if (m_Pattern == GRID) {
+      result.add("-G");
+      
+      result.add("-M"); 
+      result.add("" + getDistMult());
+    }
+
+    if (m_Pattern == SINE) {
+      result.add("-I");
+      
+      result.add("-C"); 
+      result.add("" + getNumCycles());
+    }
+
+    if (getOrderedFlag())
+      result.add("-O");
+
+    result.add("-P"); 
+    result.add("" + getNoiseRate());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the default number of clusters
+   * 
+   * @return the default number of clusters
+   */
+  protected int defaultNumClusters() {
+    return 4;
+  }
+
+  /**
+   * Sets the number of clusters the dataset should have.
+   * @param numClusters the new number of clusters
+   */
+  public void setNumClusters(int numClusters) { 
+    m_NumClusters = numClusters; 
+  }
+
+  /**
+   * Gets the number of clusters the dataset should have.
+   * @return the number of clusters the dataset should have
+   */
+  public int getNumClusters() { 
+    return m_NumClusters; 
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numClustersTipText() {
+    return "The number of clusters to generate.";
+  }
+
+  /**
+   * Sets the upper and lower boundary for instances per cluster.
+   *
+   * @param fromTo  the string containing the upper and lower boundary for
+   *                instances per cluster separated by ..
+   */
+  protected void setInstNums(String fromTo) {
+    int i = fromTo.indexOf("..");
+    String from = fromTo.substring(0, i);
+    setMinInstNum(Integer.parseInt(from));
+    String to = fromTo.substring(i + 2, fromTo.length());
+    setMaxInstNum(Integer.parseInt(to));
+  }
+  
+  /**
+   * Gets the upper and lower boundary for instances per cluster.
+   *
+   * @return the string containing the upper and lower boundary for
+   * instances per cluster separated by ..
+   */
+  protected String getInstNums() {
+    String fromTo = "" 
+                    + getMinInstNum() + ".."
+                    + getMaxInstNum();
+    return fromTo;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  protected String instNumsTipText() {
+    return "The upper and lowet boundary for instances per cluster.";
+  }
+
+  /**
+   * returns the default min number of instances
+   * 
+   * @return the default min number of instances
+   */
+  protected int defaultMinInstNum() {
+    return 1;
+  }
+
+  /**
+   * Gets the lower boundary for instances per cluster.
+   *
+   * @return the the lower boundary for instances per cluster
+   */
+  public int getMinInstNum() { 
+    return m_MinInstNum; 
+  }
+  
+  /**
+   * Sets the lower boundary for instances per cluster.
+   *
+   * @param newMinInstNum new lower boundary for instances per cluster
+   */
+  public void setMinInstNum(int newMinInstNum) {
+    m_MinInstNum = newMinInstNum;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String minInstNumTipText() {
+    return "The lower boundary for instances per cluster.";
+  }
+
+  /**
+   * returns the default max number of instances
+   * 
+   * @return the default max number of instances
+   */
+  protected int defaultMaxInstNum() {
+    return 50;
+  }
+
+  /**
+   * Gets the upper boundary for instances per cluster.
+   *
+   * @return the upper boundary for instances per cluster
+   */
+  public int getMaxInstNum() { 
+    return m_MaxInstNum; 
+  }
+  
+  /**
+   * Sets the upper boundary for instances per cluster.
+   *
+   * @param newMaxInstNum new upper boundary for instances per cluster
+   */
+  public void setMaxInstNum(int newMaxInstNum) {
+    m_MaxInstNum = newMaxInstNum;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String maxInstNumTipText() {
+    return "The upper boundary for instances per cluster.";
+  }
+
+  /**
+   * Sets the upper and lower boundary for the radius of the clusters.
+   *
+   * @param fromTo the string containing the upper and lower boundary for
+   * the radius  of the clusters, separated by ..
+   */
+  protected void setRadiuses(String fromTo) {
+    int i = fromTo.indexOf("..");
+    String from = fromTo.substring(0, i);
+    setMinRadius(Double.valueOf(from).doubleValue());
+    String to = fromTo.substring(i + 2, fromTo.length());
+    setMaxRadius(Double.valueOf(to).doubleValue());
+  }
+
+  /**
+   * Gets the upper and lower boundary for the radius of the clusters.
+   *
+   * @return the string containing the upper and lower boundary for
+   * the radius  of the clusters, separated by ..
+   */
+  protected String getRadiuses() {
+    String fromTo = "" 
+                    + Utils.doubleToString(getMinRadius(), 2) + ".."
+                    + Utils.doubleToString(getMaxRadius(), 2);
+    return fromTo;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  protected String radiusesTipText() {
+    return "The upper and lower boundary for the radius of the clusters.";
+  }
+
+  /**
+   * returns the default min radius
+   * 
+   * @return the default min radius
+   */
+  protected double defaultMinRadius() {
+    return 0.1;
+  }
+
+  /**
+   * Gets the lower boundary for the radiuses of the clusters.
+   *
+   * @return the lower boundary for the radiuses of the clusters
+   */
+  public double getMinRadius() { 
+    return m_MinRadius; 
+  }
+  
+  /**
+   * Sets the lower boundary for the radiuses of the clusters.
+   *
+   * @param newMinRadius new lower boundary for the radiuses of the clusters
+   */
+  public void setMinRadius(double newMinRadius) {
+    m_MinRadius = newMinRadius;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String minRadiusTipText() {
+    return "The lower boundary for the radius of the clusters.";
+  }
+
+  /**
+   * returns the default max radius
+   * 
+   * @return the default max radius
+   */
+  protected double defaultMaxRadius() {
+    return Math.sqrt(2.0);
+  }
+
+  /**
+   * Gets the upper boundary for the radiuses of the clusters.
+   *
+   * @return the upper boundary for the radiuses of the clusters
+   */
+  public double getMaxRadius() { 
+    return m_MaxRadius; 
+  }
+  
+  /**
+   * Sets the upper boundary for the radiuses of the clusters.
+   *
+   * @param newMaxRadius new upper boundary for the radiuses of the clusters
+   */
+  public void setMaxRadius(double newMaxRadius) {
+    m_MaxRadius = newMaxRadius;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String maxRadiusTipText() {
+    return "The upper boundary for the radius of the clusters.";
+  }
+
+  /**
+   * returns the default pattern
+   * 
+   * @return the default pattern
+   */
+  protected SelectedTag defaultPattern() {
+    return new SelectedTag(RANDOM, TAGS_PATTERN);
+  }
+  
+  /**
+   * Gets the pattern type.
+   *
+   * @return the current pattern type
+   */
+  public SelectedTag getPattern() { 
+    return new SelectedTag(m_Pattern, TAGS_PATTERN);
+  }
+
+  /**
+   * Sets the pattern type.
+   *
+   * @param value new pattern type 
+   */
+  public void setPattern(SelectedTag value) {
+    if (value.getTags() == TAGS_PATTERN)
+      m_Pattern = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String patternTipText() {
+    return "The pattern for generating the data.";
+  }
+
+  /**
+   * returns the default distance multiplier
+   * 
+   * @return the default distance multiplier
+   */
+  protected double defaultDistMult() {
+    return 4.0;
+  }
+
+  /**
+   * Gets the distance multiplier.
+   *
+   * @return the distance multiplier
+   */
+  public double getDistMult() { 
+    return m_DistMult; 
+  }
+  
+  /**
+   * Sets the distance multiplier.
+   *
+   * @param newDistMult new distance multiplier
+   */
+  public void setDistMult(double newDistMult) {
+    m_DistMult = newDistMult;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String distMultTipText() {
+    return "The distance multiplier (in combination with the 'Grid' pattern).";
+  }
+
+  /**
+   * returns the default number of cycles
+   * 
+   * @return the default number of cycles
+   */
+  protected int defaultNumCycles() {
+    return 4;
+  }
+
+  /**
+   * Gets the number of cycles.
+   *
+   * @return the number of cycles
+   */
+  public int getNumCycles() { 
+    return m_NumCycles; 
+  }
+  
+  /**
+   * Sets the the number of cycles.
+   *
+   * @param newNumCycles new number of cycles 
+   */
+  public void setNumCycles(int newNumCycles) {
+    m_NumCycles = newNumCycles;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numCyclesTipText() {
+    return "The number of cycles to use (in combination with the 'Sine' pattern).";
+  }
+
+  /**
+   * returns the default input order
+   * 
+   * @return the default input order
+   */
+  protected SelectedTag defaultInputOrder() {
+    return new SelectedTag(ORDERED, TAGS_INPUTORDER);  // TODO: the only one that is currently implemented, normally RANDOMIZED
+  }
+
+  /**
+   * Gets the input order.
+   *
+   * @return the current input order
+   */
+  public SelectedTag getInputOrder() { 
+    return new SelectedTag(m_InputOrder, TAGS_INPUTORDER);
+  }
+
+  /**
+   * Sets the input order.
+   *
+   * @param value new input order 
+   */
+  public void setInputOrder(SelectedTag value) {
+    if (value.getTags() == TAGS_INPUTORDER)
+      m_InputOrder = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String inputOrderTipText() {
+    return "The input order to use.";
+  }
+
+  /**
+   * Gets the ordered flag (option O).
+   *
+   * @return true if ordered flag is set
+   */
+  public boolean getOrderedFlag() { 
+    return m_InputOrder == ORDERED; 
+  }
+
+  /**
+   * returns the default noise rate
+   * 
+   * @return the default noise rate
+   */
+  protected double defaultNoiseRate() {
+    return 0.0;
+  }
+
+  /**
+   * Gets the percentage of noise set.
+   *
+   * @return the percentage of noise set
+   */
+  public double getNoiseRate() { 
+    return m_NoiseRate; 
+  }
+  
+  /**
+   * Sets the percentage of noise set.
+   *
+   * @param newNoiseRate new percentage of noise 
+   */
+  public void setNoiseRate(double newNoiseRate) {
+    m_NoiseRate = newNoiseRate;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String noiseRateTipText() {
+    return "The noise rate to use.";
+  }
+
+  /**
+   * Gets the single mode flag.
+   *
+   * @return true if methode generateExample can be used.
+   */
+  public boolean getSingleModeFlag() { 
+    return false; 
+  }
+ 
+  /**
+   * Initializes the format for the dataset produced. 
+   *
+   * @return the output data format
+   * @throws Exception data format could not be defined 
+   */
+
+  public Instances defineDataFormat() throws Exception {
+    Random random = new Random (getSeed());
+    setRandom(random);
+    Instances dataset;
+    FastVector attributes = new FastVector(3);
+    Attribute attribute;
+    boolean classFlag = getClassFlag();
+    
+    FastVector classValues = null;
+    if (classFlag) classValues = new FastVector (m_NumClusters);     
+
+    // define dataset
+    for (int i = 0; i < getNumAttributes(); i++) {
+      attribute = new Attribute("X" + i); 
+      attributes.addElement(attribute);
+    }
+    
+    if (classFlag) {
+      for (int i = 0; i < m_NumClusters; i++)
+	classValues.addElement("c" + i);
+      attribute = new Attribute ("class", classValues); 
+      attributes.addElement(attribute);
+    }
+
+    dataset = new Instances(getRelationNameToUse(), attributes, 0);
+    if (classFlag) 
+      dataset.setClassIndex(getNumAttributes());
+
+    // set dataset format of this class
+    Instances format = new Instances(dataset, 0);
+    setDatasetFormat(format);
+
+    m_ClusterList = defineClusters(random);
+
+    //System.out.println("dataset" + dataset.numAttributes());
+    return dataset; 
+  }
+
+  /**
+   * Generate an example of the dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined or generating <br/>
+   * examples one by one is not possible, because voting is chosen
+   */
+
+  public Instance generateExample() throws Exception {
+    throw new Exception("Examples cannot be generated" +
+                                           " one by one.");
+  }
+
+  /**
+   * Generate all examples of the dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined 
+   */
+
+  public Instances generateExamples() throws Exception {
+    Random random = getRandom();
+    Instances data = getDatasetFormat();
+    if (data == null) throw new Exception("Dataset format not defined.");
+
+    // generate examples
+    if (getOrderedFlag())
+      data = generateExamples(random, data);
+    else
+      throw new Exception("RANDOMIZED is not yet implemented.");
+  
+    return (data);
+  }
+
+  /**
+   * Generate all examples of the dataset. 
+   * 
+   * @param random the random number generator to use
+   * @param format the dataset format
+   * @return the instance generated
+   * @throws Exception if format not defined
+   */
+  public Instances generateExamples(Random random,
+				    Instances format) throws Exception {
+    Instance example = null;
+    
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+
+    // generate examples for one cluster after another
+    int cNum = 0;
+    for (Enumeration enm = m_ClusterList.elements();
+	 enm.hasMoreElements(); cNum++) {
+      Cluster cl  = (Cluster) enm.nextElement();
+      double stdDev = cl.getStdDev();
+      int instNum = cl.getInstNum();
+      double[] center = cl.getCenter();
+      String cName = "c" + cNum;
+
+      for (int i = 0; i < instNum; i++) {
+	// generate example
+	example = generateInstance(
+                    format, random, stdDev, center, cName);
+       
+	if (example != null)
+	  example.setDataset(format);
+	format.add(example);
+      }
+    }
+
+    return (format);
+  }
+
+  /**
+   * Generate an example of the dataset. 
+   * 
+   * @param format the dataset format
+   * @param randomG the random number generator
+   * @param stdDev the standard deviation to use
+   * @param center the centers
+   * @param cName the class value
+   * @return the instance generated
+   * examples one by one is not possible, because voting is chosen
+   */
+  private Instance generateInstance (Instances format,
+				     Random randomG,
+				     double stdDev,
+				     double[] center,
+				     String cName) {
+    Instance example;
+    int numAtts = getNumAttributes();
+    if (getClassFlag()) 
+      numAtts++;
+
+    example = new DenseInstance(numAtts);
+    example.setDataset(format);
+        
+    for (int i = 0; i < getNumAttributes(); i++)
+      example.setValue(i, randomG.nextGaussian() * stdDev + center[i]); 
+    
+    if (getClassFlag())
+      example.setClassValue(cName);
+
+    return example; 
+  }
+
+ /**
+   * Defines the clusters 
+   *
+   * @param random random number generator
+   * @return the cluster definitions
+   * @throws Exception if defining fails
+   */
+  private FastVector defineClusters(Random random)
+   throws Exception {
+
+    if (m_Pattern == GRID)
+      return defineClustersGRID(random);
+    else
+      return defineClustersRANDOM(random);
+  }
+
+  /**
+   * Defines the clusters if pattern is GRID
+   *
+   * @param random random number generator
+   * @return the defined clusters for GRID
+   * @throws Exception if something goes wrong
+   */
+  private FastVector defineClustersGRID(Random random)
+    throws Exception {
+
+    FastVector clusters = new FastVector(m_NumClusters);
+    double diffInstNum = (double) (m_MaxInstNum - m_MinInstNum);
+    double minInstNum = (double) m_MinInstNum;
+    double diffRadius = m_MaxRadius - m_MinRadius;
+    Cluster cluster;
+
+    // compute gridsize
+    double gs = Math.pow(m_NumClusters, 1.0 / getNumAttributes());
+    
+    if (gs - ((double) ((int) gs))  > 0.0) {
+      m_GridSize = (int) (gs + 1.0);
+    } else { m_GridSize = (int) gs; }
+
+    // compute gridwidth
+    m_GridWidth = ((m_MaxRadius + m_MinRadius) / 2) * m_DistMult;
+
+    //System.out.println("GridSize= " + m_GridSize);
+    //System.out.println("GridWidth= " + m_GridWidth);
+    
+    // initialize gridvector with zeros
+    GridVector gv = new GridVector(getNumAttributes(), m_GridSize);
+
+    for (int i = 0; i < m_NumClusters; i++) {
+      int instNum = (int) (random.nextDouble() * diffInstNum
+                                   + minInstNum);
+      double radius = (random.nextDouble() * diffRadius) + m_MinRadius;
+
+      // center is defined in the constructor of cluster
+      cluster = new Cluster(instNum, radius,
+			    gv.getGridVector(), m_GridWidth);
+      clusters.addElement((Object) cluster);
+      gv.addOne();
+    }
+    return clusters;
+  }
+
+ /**
+   * Defines the clusters if pattern is RANDOM
+   *
+   * @param random random number generator
+   * @return the cluster definitions
+   * @throws Exception if something goes wrong
+   */
+  private FastVector defineClustersRANDOM(Random random)
+    throws Exception {
+
+    FastVector clusters = new FastVector(m_NumClusters);
+    double diffInstNum = (double) (m_MaxInstNum - m_MinInstNum);
+    double minInstNum = (double) m_MinInstNum;
+    double diffRadius = m_MaxRadius - m_MinRadius;
+    Cluster cluster;
+
+    for (int i = 0; i < m_NumClusters; i++) {
+      int instNum = (int) (random.nextDouble() * diffInstNum
+                                   + minInstNum);
+      double radius = (random.nextDouble() * diffRadius) + m_MinRadius;
+
+      // center is defined in the constructor of cluster
+      cluster = new Cluster(instNum, radius, random);
+      clusters.addElement((Object) cluster);
+    }
+    return clusters;
+  }
+
+
+  /**
+   * Compiles documentation about the data generation after
+   * the generation process
+   *
+   * @return string with additional information about generated dataset
+   * @throws Exception no input structure has been defined
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+  
+  /**
+   * Compiles documentation about the data generation before
+   * the generation process
+   *
+   * @return string with additional information 
+   */
+  public String generateStart() {
+    StringBuffer docu = new StringBuffer();
+
+    int sumInst = 0;
+    int cNum = 0;
+    for (Enumeration enm = m_ClusterList.elements();
+	 enm.hasMoreElements(); cNum++) {
+      Cluster cl  = (Cluster) enm.nextElement();
+      docu.append("%\n");
+      docu.append("% Cluster: c"+ cNum + "\n");
+      docu.append("% ----------------------------------------------\n");
+      docu.append("% StandardDeviation: "
+		  + Utils.doubleToString(cl.getStdDev(), 2) + "\n");
+      docu.append("% Number of instances: "
+		  + cl.getInstNum() + "\n");
+      sumInst += cl.getInstNum();
+      double[] center = cl.getCenter();
+      docu.append("% "); 
+      for (int i = 0; i < center.length - 1; i++) {
+        docu.append(Utils.doubleToString(center[i], 2) + ", ");
+      }
+      docu.append(Utils.doubleToString(center[center.length - 1], 2) + "\n");
+    }
+    docu.append("%\n% ----------------------------------------------\n"); 
+    docu.append("% Total number of instances: " + sumInst + "\n");
+    docu.append("%                            in " + cNum + " clusters\n");
+    docu.append("% Pattern chosen           : ");
+    if (m_Pattern == GRID) 
+      docu.append(
+          "GRID, " + "distance multiplier = " 
+          + Utils.doubleToString(m_DistMult, 2) + "\n");
+    else if (m_Pattern == SINE) 
+      docu.append("SINE\n");
+    else
+      docu.append("RANDOM\n");
+
+    return docu.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new BIRCHCluster(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceCluster.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceCluster.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceCluster.java	(revision 29)
@@ -0,0 +1,1003 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SubspaceCluster.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.datagenerators.ClusterDefinition;
+import weka.datagenerators.ClusterGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A data generator that produces data points in hyperrectangular subspace clusters.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -h
+ *  Prints this help.</pre>
+ * 
+ * <pre> -o &lt;file&gt;
+ *  The name of the output file, otherwise the generated data is
+ *  printed to stdout.</pre>
+ * 
+ * <pre> -r &lt;name&gt;
+ *  The name of the relation.</pre>
+ * 
+ * <pre> -d
+ *  Whether to print debug informations.</pre>
+ * 
+ * <pre> -S
+ *  The seed for random function (default 1)</pre>
+ * 
+ * <pre> -a &lt;num&gt;
+ *  The number of attributes (default 1).</pre>
+ * 
+ * <pre> -c
+ *  Class Flag, if set, the cluster is listed in extra attribute.</pre>
+ * 
+ * <pre> -b &lt;range&gt;
+ *  The indices for boolean attributes.</pre>
+ * 
+ * <pre> -m &lt;range&gt;
+ *  The indices for nominal attributes.</pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  The noise rate in percent (default 0.0).
+ *  Can be between 0% and 30%. (Remark: The original 
+ *  algorithm only allows noise up to 10%.)</pre>
+ * 
+ * <pre> -C &lt;cluster-definition&gt;
+ *  A cluster definition of class 'SubspaceClusterDefinition'
+ *  (definition needs to be quoted to be recognized as 
+ *  a single argument).</pre>
+ * 
+ * <pre> 
+ * Options specific to weka.datagenerators.clusterers.SubspaceClusterDefinition:
+ * </pre>
+ * 
+ * <pre> -A &lt;range&gt;
+ *  Generates randomly distributed instances in the cluster.</pre>
+ * 
+ * <pre> -U &lt;range&gt;
+ *  Generates uniformly distributed instances in the cluster.</pre>
+ * 
+ * <pre> -G &lt;range&gt;
+ *  Generates gaussian distributed instances in the cluster.</pre>
+ * 
+ * <pre> -D &lt;num&gt;,&lt;num&gt;
+ *  The attribute min/max (-A and -U) or mean/stddev (-G) for
+ *  the cluster.</pre>
+ * 
+ * <pre> -N &lt;num&gt;..&lt;num&gt;
+ *  The range of number of instances per cluster (default 1..50).</pre>
+ * 
+ * <pre> -I
+ *  Uses integer instead of continuous values (default continuous).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $ 
+ */
+public class SubspaceCluster 
+  extends ClusterGenerator {
+
+  /** for serialization */
+  static final long serialVersionUID = -3454999858505621128L;
+  
+  /** noise rate in percent (option P,  between 0 and 30)*/ 
+  protected double m_NoiseRate;
+
+  /** cluster list */
+  protected ClusterDefinition[] m_Clusters;
+
+  /** if nominal, store number of values */
+  protected int[] m_numValues;
+
+  /** store global min values */
+  protected double[] m_globalMinValue;
+
+  /** store global max values */
+  protected double[] m_globalMaxValue;
+
+  /** cluster type: uniform/random */
+  public static final int UNIFORM_RANDOM = 0;  
+  /** cluster type: total uniform */
+  public static final int TOTAL_UNIFORM = 1;
+  /** cluster type: gaussian */
+  public static final int GAUSSIAN = 2;
+  /** the tags for the cluster types */
+  public static final Tag[] TAGS_CLUSTERTYPE = {
+    new Tag(UNIFORM_RANDOM, "uniform/random"),
+    new Tag(TOTAL_UNIFORM,  "total uniform"),
+    new Tag(GAUSSIAN,       "gaussian")
+  };
+
+  /** cluster subtype: continuous */
+  public static final int CONTINUOUS = 0;
+  /** cluster subtype: integer */
+  public static final int INTEGER = 1;
+  /** the tags for the cluster types */
+  public static final Tag[] TAGS_CLUSTERSUBTYPE = {
+    new Tag(CONTINUOUS, "continuous"),
+    new Tag(INTEGER,    "integer")
+  };
+
+  /**
+   * initializes the generator, sets the number of clusters to 0, since user
+   * has to specify them explicitly
+   */
+  public SubspaceCluster() {
+    super();
+
+    setNoiseRate(defaultNoiseRate());
+  }
+
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "A data generator that produces data points in "
+      + "hyperrectangular subspace clusters.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = enumToVector(super.listOptions());
+
+    result.addElement(new Option(
+          "\tThe noise rate in percent (default " 
+          + defaultNoiseRate() + ").\n"
+          + "\tCan be between 0% and 30%. (Remark: The original \n"
+          + "\talgorithm only allows noise up to 10%.)",
+          "P", 1, "-P <num>"));
+
+    result.addElement(new Option(
+          "\tA cluster definition of class '" 
+	  + SubspaceClusterDefinition.class.getName().replaceAll(".*\\.", "") + "'\n"
+	  + "\t(definition needs to be quoted to be recognized as \n"
+	  + "\ta single argument).",
+          "C", 1, "-C <cluster-definition>"));
+
+    result.addElement(new Option(
+	      "", "", 0, 
+	      "\nOptions specific to " 
+	      + SubspaceClusterDefinition.class.getName() + ":"));
+
+    result.addAll(
+        enumToVector(new SubspaceClusterDefinition(this).listOptions()));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -h
+   *  Prints this help.</pre>
+   * 
+   * <pre> -o &lt;file&gt;
+   *  The name of the output file, otherwise the generated data is
+   *  printed to stdout.</pre>
+   * 
+   * <pre> -r &lt;name&gt;
+   *  The name of the relation.</pre>
+   * 
+   * <pre> -d
+   *  Whether to print debug informations.</pre>
+   * 
+   * <pre> -S
+   *  The seed for random function (default 1)</pre>
+   * 
+   * <pre> -a &lt;num&gt;
+   *  The number of attributes (default 1).</pre>
+   * 
+   * <pre> -c
+   *  Class Flag, if set, the cluster is listed in extra attribute.</pre>
+   * 
+   * <pre> -b &lt;range&gt;
+   *  The indices for boolean attributes.</pre>
+   * 
+   * <pre> -m &lt;range&gt;
+   *  The indices for nominal attributes.</pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  The noise rate in percent (default 0.0).
+   *  Can be between 0% and 30%. (Remark: The original 
+   *  algorithm only allows noise up to 10%.)</pre>
+   * 
+   * <pre> -C &lt;cluster-definition&gt;
+   *  A cluster definition of class 'SubspaceClusterDefinition'
+   *  (definition needs to be quoted to be recognized as 
+   *  a single argument).</pre>
+   * 
+   * <pre> 
+   * Options specific to weka.datagenerators.clusterers.SubspaceClusterDefinition:
+   * </pre>
+   * 
+   * <pre> -A &lt;range&gt;
+   *  Generates randomly distributed instances in the cluster.</pre>
+   * 
+   * <pre> -U &lt;range&gt;
+   *  Generates uniformly distributed instances in the cluster.</pre>
+   * 
+   * <pre> -G &lt;range&gt;
+   *  Generates gaussian distributed instances in the cluster.</pre>
+   * 
+   * <pre> -D &lt;num&gt;,&lt;num&gt;
+   *  The attribute min/max (-A and -U) or mean/stddev (-G) for
+   *  the cluster.</pre>
+   * 
+   * <pre> -N &lt;num&gt;..&lt;num&gt;
+   *  The range of number of instances per cluster (default 1..50).</pre>
+   * 
+   * <pre> -I
+   *  Uses integer instead of continuous values (default continuous).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String                      tmpStr;
+    SubspaceClusterDefinition   cl;
+    Vector                      list;
+    int                         clCount;
+
+    super.setOptions(options);
+
+    m_numValues = new int[getNumAttributes()];
+    // numValues might be changed by a cluster definition
+    // (only relevant for nominal data)
+    for (int i = 0; i < getNumAttributes(); i++)
+      m_numValues[i] = 1;
+
+    tmpStr = Utils.getOption('P', options);
+    if (tmpStr.length() != 0)
+      setNoiseRate(Double.parseDouble(tmpStr));
+    else
+      setNoiseRate(defaultNoiseRate());
+
+    // cluster definitions
+    list = new Vector();
+    
+    clCount = 0;
+    do {
+      tmpStr = Utils.getOption('C', options);
+      if (tmpStr.length() != 0) {
+        clCount++;
+        cl = new SubspaceClusterDefinition(this);
+        cl.setOptions(Utils.splitOptions(tmpStr));
+        list.add(cl);
+      }
+    }
+    while (tmpStr.length() != 0);
+
+    m_Clusters = (ClusterDefinition[]) 
+                    list.toArray(new ClusterDefinition[list.size()]);
+    // in case no cluster definition was provided, make sure that there's at
+    // least one definition present -> see getClusters()
+    getClusters();
+  }
+
+
+  /**
+   * Gets the current settings of the datagenerator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result  = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-P"); 
+    result.add("" + getNoiseRate());
+
+    for (i = 0; i < getClusters().length; i++)  {
+      result.add("-C");
+      result.add(Utils.joinOptions(getClusters()[i].getOptions()));
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the current cluster definitions, if necessary initializes them
+   * 
+   * @return the current cluster definitions
+   */
+  protected ClusterDefinition[] getClusters() {
+    if ( (m_Clusters == null) || (m_Clusters.length == 0) ) {
+      if (m_Clusters != null)
+        System.out.println("NOTE: at least 1 cluster definition is necessary, " 
+            + "created default one.");
+      m_Clusters = new ClusterDefinition[]{new SubspaceClusterDefinition(this)};
+    }
+
+    return m_Clusters;
+  }
+
+  /**
+   * returns the default number of attributes
+   * 
+   * @return the default number of attributes
+   */
+  protected int defaultNumAttributes() {
+    return 1;
+  }
+
+  /**
+   * Sets the number of attributes the dataset should have.
+   * @param numAttributes the new number of attributes
+   */
+  public void setNumAttributes(int numAttributes) {
+    super.setNumAttributes(numAttributes);
+    m_numValues = new int[getNumAttributes()];
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes the generated data will contain (Note: they must be covered by the cluster definitions!)";
+  }
+
+  /**
+   * returns the default noise rate
+   * 
+   * @return the default noise rate
+   */
+  protected double defaultNoiseRate() {
+    return 0.0;
+  }
+
+  /**
+   * Gets the percentage of noise set.
+   *
+   * @return the percentage of noise set
+   */
+  public double getNoiseRate() { 
+    return m_NoiseRate; 
+  }
+
+  /**
+   * Sets the percentage of noise set.
+   *
+   * @param newNoiseRate new percentage of noise 
+   */
+  public void setNoiseRate(double newNoiseRate) {
+    m_NoiseRate = newNoiseRate;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String noiseRateTipText() {
+    return "The noise rate to use.";
+  }
+
+  /**
+   * returns the currently set clusters
+   * 
+   * @return the currently set clusters
+   */
+  public ClusterDefinition[] getClusterDefinitions() {
+    return getClusters();
+  }
+
+  /**
+   * sets the clusters to use
+   * 
+   * @param value the clusters do use
+   * @throws Exception if clusters are not the correct class
+   */
+  public void setClusterDefinitions(ClusterDefinition[] value) 
+    throws Exception {
+
+    String      indexStr;
+    
+    indexStr   = "";
+    m_Clusters = value;
+    for (int i = 0; i < getClusters().length; i++) {
+      if (!(getClusters()[i] instanceof SubspaceClusterDefinition)) {
+        if (indexStr.length() != 0)
+          indexStr += ",";
+        indexStr += "" + (i+1);
+      }
+      getClusters()[i].setParent(this);
+      getClusters()[i].setOptions(getClusters()[i].getOptions()); // for initializing!
+    }
+
+    // any wrong classes encountered?
+    if (indexStr.length() != 0)
+      throw new Exception("These cluster definitions are not '" 
+          + SubspaceClusterDefinition.class.getName() + "': " + indexStr);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String clusterDefinitionsTipText() {
+    return "The clusters to use.";
+  }
+
+  /**
+   * Checks, whether all attributes are covered by cluster definitions and 
+   * returns TRUE in that case.
+   * 
+   * @return whether all attributes are covered
+   */
+  protected boolean checkCoverage() {
+    int         i;
+    int         n;
+    int[]       count;
+    Range       r;
+    String      attrIndex;
+    SubspaceClusterDefinition  cl;
+    
+    // check whether all the attributes are covered
+    count = new int[getNumAttributes()];
+    for (i = 0; i < getNumAttributes(); i++) {
+      for (n = 0; n < getClusters().length; n++) {
+        cl = (SubspaceClusterDefinition) getClusters()[n];
+        r  = new Range(cl.getAttrIndexRange());
+        r.setUpper(getNumAttributes());
+        if (r.isInRange(i))
+          count[i]++;
+      }
+    }
+
+    // list all indices that are not covered
+    attrIndex = "";
+    for (i = 0; i < count.length; i++) {
+      if (count[i] == 0) {
+        if (attrIndex.length() != 0)
+          attrIndex += ",";
+        attrIndex += (i+1);
+      }
+    }
+
+    if (attrIndex.length() != 0)
+      throw new IllegalArgumentException(
+          "The following attributes are not covered by a cluster "
+          + "definition: " + attrIndex + "\n");
+
+    return true;
+  }
+
+  /**
+   * Gets the single mode flag.
+   *
+   * @return true if methode generateExample can be used.
+   */
+  public boolean getSingleModeFlag() { 
+    return false; 
+  }
+
+  /**
+   * Initializes the format for the dataset produced. 
+   *
+   * @return the output data format
+   * @throws Exception data format could not be defined 
+   */
+
+  public Instances defineDataFormat() throws Exception {
+
+    // initialize
+    setOptions(getOptions());
+
+    checkCoverage();
+
+    Random random = new Random (getSeed());
+    setRandom(random);
+    Instances dataset;
+    FastVector attributes = new FastVector(3);
+    Attribute attribute;
+    boolean classFlag = getClassFlag();
+
+    FastVector classValues = null;
+    if (classFlag) 
+      classValues = new FastVector(getClusters().length);     
+    FastVector boolValues = new FastVector(2);
+    boolValues.addElement("false");
+    boolValues.addElement("true");
+    FastVector nomValues = null;
+
+    // define dataset
+    for (int i = 0; i < getNumAttributes(); i++) {
+      // define boolean attribute
+      if (m_booleanCols.isInRange(i)) {
+        attribute = new Attribute("B" + i, boolValues);
+      } 
+      else if (m_nominalCols.isInRange(i)) {
+        // define nominal attribute
+        nomValues = new FastVector(m_numValues[i]);
+        for (int j = 0; j < m_numValues[i]; j++)
+          nomValues.addElement("value-" + j);
+        attribute = new Attribute("N" + i, nomValues);
+      } 
+      else {
+        // numerical attribute
+        attribute = new Attribute("X" + i); 
+      }
+      attributes.addElement(attribute);
+    }
+
+    if (classFlag) {
+      for (int i = 0; i < getClusters().length; i++)
+        classValues.addElement("c" + i);
+      attribute = new Attribute ("class", classValues); 
+      attributes.addElement(attribute);
+    }
+
+    dataset = new Instances(getRelationNameToUse(), attributes, 0);
+    if (classFlag) 
+      dataset.setClassIndex(m_NumAttributes);
+
+    // set dataset format of this class
+    Instances format = new Instances(dataset, 0);
+    setDatasetFormat(format);
+
+    for (int i = 0; i < getClusters().length; i++) {
+      SubspaceClusterDefinition cl = (SubspaceClusterDefinition) getClusters()[i];
+      cl.setNumInstances(random);
+      cl.setParent(this);
+    }
+
+    return dataset; 
+  }
+
+  /**
+   * Returns true if attribute is boolean
+   *@param index of the attribute
+   *@return true if the attribute is boolean
+   */
+  public boolean isBoolean(int index) {
+    return m_booleanCols.isInRange(index); 
+  }
+
+  /**
+   * Returns true if attribute is nominal
+   *@param index of the attribute
+   *@return true if the attribute is nominal
+   */
+  public boolean isNominal(int index) {
+    return m_nominalCols.isInRange(index);
+  }
+
+  /**
+   * returns array that stores the number of values for a nominal attribute.
+   * 
+   * @return the array that stores the number of values for a nominal attribute
+   */
+  public int[] getNumValues() {
+    return m_numValues;
+  }
+
+  /**
+   * Generate an example of the dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined or generating <br/>
+   * examples one by one is not possible, because voting is chosen
+   */
+
+  public Instance generateExample() throws Exception {
+    throw new Exception("Examples cannot be generated one by one.");
+  }
+
+  /**
+   * Generate all examples of the dataset. 
+   * @return the instance generated
+   * @throws Exception if format not defined 
+   */
+
+  public Instances generateExamples() throws Exception {
+    Instances format = getDatasetFormat();
+    Instance example = null;
+
+    if (format == null) 
+      throw new Exception("Dataset format not defined.");
+
+    // generate examples for one cluster after another
+    for (int cNum = 0; cNum < getClusters().length; cNum++) {
+      SubspaceClusterDefinition cl  = (SubspaceClusterDefinition) getClusters()[cNum];
+
+      //get the number of instances to create
+      int instNum = cl.getNumInstances();
+
+      //class value is c + cluster number
+      String cName = "c" + cNum;
+
+      switch (cl.getClusterType().getSelectedTag().getID()) {
+        case (UNIFORM_RANDOM):
+          for (int i = 0; i < instNum; i++) {
+            // generate example
+            example = generateExample(format, getRandom(), cl, cName);
+            if (example != null)
+              format.add(example);
+          }
+          break;
+        case (TOTAL_UNIFORM):
+          // generate examples
+          if (!cl.isInteger())
+            generateUniformExamples(format, instNum, cl, cName);
+          else
+            generateUniformIntegerExamples(format, instNum, cl, cName);
+          break;
+        case (GAUSSIAN):
+          // generate examples
+          generateGaussianExamples(format, instNum, getRandom(), cl, cName);
+          break;
+      }
+    }
+
+    return format;
+  }
+
+  /**
+   * Generate an example of the dataset. 
+   * 
+   * @param format the dataset format
+   * @param randomG the random number generator to use
+   * @param cl the cluster definition
+   * @param cName the class value
+   * @return the generated instance
+   */
+  private Instance generateExample(
+      Instances format, Random randomG, SubspaceClusterDefinition cl, 
+      String cName) {
+
+    boolean makeInteger = cl.isInteger();
+    int num = -1;
+    Instance example = null;
+    int numAtts = m_NumAttributes;
+    if (getClassFlag()) numAtts++;
+
+    example = new DenseInstance(numAtts);
+    example.setDataset(format);
+    boolean[] attributes = cl.getAttributes();
+    double[] minValue = cl.getMinValue();
+    double[] maxValue = cl.getMaxValue();
+    double value;
+
+    int clusterI = -1;
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (attributes[i]) {
+        clusterI++;
+        num++;
+        // boolean  or nominal attribute
+        if (isBoolean(i) || isNominal(i)) {
+
+          if (minValue[clusterI] == maxValue[clusterI]) {
+            value = minValue[clusterI];
+          } 
+          else {
+            int numValues = (int)(maxValue[clusterI] - minValue[clusterI] + 1.0);
+            value = randomG.nextInt(numValues);
+            value += minValue[clusterI];
+          }
+        } 
+        else {
+          // numeric attribute
+          value = randomG.nextDouble() * 
+            (maxValue[num] - minValue[num]) + minValue[num];
+          if (makeInteger)
+            value = Math.round(value);
+        }
+        example.setValue(i, value);
+      } 
+      else {
+        example.setMissing(i);
+      }
+    }
+
+    if (getClassFlag())
+      example.setClassValue(cName);
+
+    return example; 
+  }
+
+  /**
+   * Generate examples for a uniform cluster dataset. 
+   * 
+   * @param format the dataset format
+   * @param numInstances the number of instances to generator
+   * @param cl the cluster definition
+   * @param cName the class value
+   */
+  private void generateUniformExamples(
+      Instances format, int numInstances, SubspaceClusterDefinition cl, 
+      String cName) {
+
+    Instance example = null;
+    int numAtts = m_NumAttributes;
+    if (getClassFlag()) numAtts++;
+
+    example = new DenseInstance(numAtts);
+    example.setDataset(format);
+    boolean[] attributes = cl.getAttributes();
+    double[] minValue = cl.getMinValue();
+    double[] maxValue = cl.getMaxValue();
+    double[] diff = new double[minValue.length];
+
+    for (int i = 0; i < minValue.length; i++)
+      diff[i] = (maxValue[i] - minValue[i]);
+
+    for (int j = 0; j < numInstances; j++) {
+      int num = -1;
+      for (int i = 0; i < m_NumAttributes; i++) {
+        if (attributes[i]) {
+          num++;
+          double value = minValue[num] + (diff[num] * (double)((double)j / (double)(numInstances - 1)));
+          example.setValue(i, value);
+        } 
+        else {
+          example.setMissing(i);
+        }
+      }
+      if (getClassFlag())
+        example.setClassValue(cName);
+      format.add(example);
+    }
+  }
+
+  /**
+   * Generate examples for a uniform cluster dataset. 
+   * 
+   * @param format the dataset format
+   * @param numInstances the number of instances to generator
+   * @param cl the cluster definition
+   * @param cName the class value
+   */
+  private void generateUniformIntegerExamples(
+      Instances format, int numInstances, SubspaceClusterDefinition cl, 
+      String cName) {
+
+    Instance example = null;
+    int numAtts = m_NumAttributes;
+    if (getClassFlag()) numAtts++;
+
+    example = new DenseInstance(numAtts);
+    example.setDataset(format);
+    boolean[] attributes = cl.getAttributes();
+    double[] minValue = cl.getMinValue();
+    double[] maxValue = cl.getMaxValue();
+    int[] minInt = new int[minValue.length];
+    int[] maxInt = new int[maxValue.length];
+    int[] intValue = new int[maxValue.length];
+    int[] numInt = new int[minValue.length];
+
+    int num = 1;
+    for (int i = 0; i < minValue.length; i++) {
+      minInt[i] = (int)Math.ceil(minValue[i]);
+      maxInt[i] = (int)Math.floor(maxValue[i]);
+      numInt[i] = (maxInt[i] - minInt[i] + 1);
+      num = num * numInt[i];
+    }
+    int numEach = numInstances / num;
+    int rest = numInstances - numEach * num;
+
+    // initialize with smallest values combination
+    for (int i = 0; i < m_NumAttributes; i++) {
+      if (attributes[i]) {
+        example.setValue(i, (double)minInt[i]);
+        intValue[i] = minInt[i];
+      } 
+      else {
+        example.setMissing(i);
+      }
+    }
+    if (getClassFlag())
+      example.setClassValue(cName);
+    int added = 0;
+    int attr = 0;
+    // do while not added all
+    do {
+      // add all for one value combination
+      for (int k = 0; k < numEach; k++) {
+        format.add(example);
+        example = (Instance) example.copy();
+        added++;
+      }
+      if (rest > 0) {
+        format.add(example);
+        example = (Instance) example.copy();
+        added++;
+        rest--;
+      }
+
+      if (added >= numInstances) break;
+      // switch to the next value combination
+      boolean done = false;
+      do {
+        if (attributes[attr] && (intValue[attr] + 1 <= maxInt[attr])) {
+          intValue[attr]++;
+          done = true;
+        } 
+        else {
+          attr++;
+        }
+      } while (!done);
+
+      example.setValue(attr, (double)intValue[attr]);
+    } while (added < numInstances);
+  }
+
+  /**
+   * Generate examples for a uniform cluster dataset. 
+   * 
+   * @param format the dataset format
+   * @param numInstances the number of instances to generate
+   * @param random the random number generator
+   * @param cl the cluster definition
+   * @param cName the class value
+   */
+  private void generateGaussianExamples(
+      Instances format, int numInstances, Random random, 
+      SubspaceClusterDefinition cl, String cName) {
+
+    boolean makeInteger = cl.isInteger();
+    Instance example = null;
+    int numAtts = m_NumAttributes;
+    if (getClassFlag()) numAtts++;
+
+    example = new DenseInstance(numAtts);
+    example.setDataset(format);
+    boolean[] attributes = cl.getAttributes();
+    double[] meanValue = cl.getMeanValue();
+    double[] stddevValue = cl.getStddevValue();
+
+    for (int j = 0; j < numInstances; j++) {
+      int num = -1;
+      for (int i = 0; i < m_NumAttributes; i++) {
+        if (attributes[i]) {
+          num++;
+          double value = meanValue[num] + (random.nextGaussian() * stddevValue[num]);
+          if (makeInteger)
+            value = Math.round(value);
+          example.setValue(i, value);
+        } 
+        else {
+          example.setMissing(i);
+        }
+      }
+      if (getClassFlag())
+        example.setClassValue(cName);
+      format.add(example);
+    }
+  }
+
+  /**
+   * Compiles documentation about the data generation after
+   * the generation process
+   *
+   * @return string with additional information about generated dataset
+   * @throws Exception no input structure has been defined
+   */
+  public String generateFinished() throws Exception {
+    return "";
+  }
+
+  /**
+   * Compiles documentation about the data generation before
+   * the generation process
+   *
+   * @return string with additional information 
+   */
+  public String generateStart() {
+    StringBuffer docu = new StringBuffer();
+
+    int sumInst = 0;
+    for (int cNum = 0; cNum < getClusters().length; cNum++) {
+      SubspaceClusterDefinition cl  = (SubspaceClusterDefinition) getClusters()[cNum];
+      docu.append("%\n");
+      docu.append("% Cluster: c"+ cNum + "   ");
+      switch (cl.getClusterType().getSelectedTag().getID()) {
+        case UNIFORM_RANDOM: 
+          docu.append("Uniform Random");
+          break;
+        case TOTAL_UNIFORM: 
+          docu.append("Total Random");
+          break;
+        case GAUSSIAN: 
+          docu.append("Gaussian");
+          break;
+      }
+      if (cl.isInteger()) {
+        docu.append(" / INTEGER");
+      }
+
+      docu.append("\n% ----------------------------------------------\n");
+      docu.append("%"+cl.attributesToString());
+
+      docu.append("\n% Number of Instances:            "  + cl.getInstNums() + "\n");
+      docu.append(  "% Generated Number of Instances:  "  + cl.getNumInstances() + "\n");
+      sumInst += cl.getNumInstances();
+        }
+    docu.append("%\n% ----------------------------------------------\n"); 
+    docu.append("% Total Number of Instances: " + sumInst + "\n");
+    docu.append("%                            in " + getClusters().length + " Cluster(s)\n%");
+
+    return docu.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments for the data producer: 
+   */
+  public static void main(String[] args) {
+    runDataGenerator(new SubspaceCluster(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceClusterDefinition.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceClusterDefinition.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/datagenerators/clusterers/SubspaceClusterDefinition.java	(revision 29)
@@ -0,0 +1,902 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SubspaceClusterDefinition.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Utils;
+import weka.datagenerators.ClusterDefinition;
+import weka.datagenerators.ClusterGenerator;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A single cluster for the SubspaceCluster datagenerator
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;range&gt;
+ *  Generates randomly distributed instances in the cluster.</pre>
+ * 
+ * <pre> -U &lt;range&gt;
+ *  Generates uniformly distributed instances in the cluster.</pre>
+ * 
+ * <pre> -G &lt;range&gt;
+ *  Generates gaussian distributed instances in the cluster.</pre>
+ * 
+ * <pre> -D &lt;num&gt;,&lt;num&gt;
+ *  The attribute min/max (-A and -U) or mean/stddev (-G) for
+ *  the cluster.</pre>
+ * 
+ * <pre> -N &lt;num&gt;..&lt;num&gt;
+ *  The range of number of instances per cluster (default 1..50).</pre>
+ * 
+ * <pre> -I
+ *  Uses integer instead of continuous values (default continuous).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ * @see SubspaceCluster
+ */
+public class SubspaceClusterDefinition 
+  extends ClusterDefinition {
+
+  /** for serialization */
+  static final long serialVersionUID = 3135678125044007231L;
+  
+  /** cluster type */
+  protected int m_clustertype;
+
+  /** cluster subtypes */
+  protected int m_clustersubtype;
+
+  /** number of attributes the cluster is defined for */
+  protected int m_numClusterAttributes;
+
+  /** number of instances for this cluster */
+  protected int m_numInstances;
+
+  /** minimal number of instances for this cluster */
+  protected int m_MinInstNum;
+
+  /** maximal number of instances for this cluster */
+  protected int m_MaxInstNum;
+
+  /** range of atttributes */
+  protected Range m_AttrIndexRange;
+
+  /** attributes of this cluster */
+  protected boolean[] m_attributes;
+
+  /** global indices of the attributes of the cluster */
+  protected int[] m_attrIndices;
+
+  /** ranges of each attribute (min); not used if gaussian */
+  protected double[] m_minValue;
+
+  /** ranges of each attribute (max); not used if gaussian */
+  protected double[] m_maxValue;
+
+  /** mean ; only used if gaussian */
+  protected double[] m_meanValue;
+  
+  /** standarddev; only used if gaussian */
+  protected double[] m_stddevValue;
+
+  /**
+   * initializes the cluster, without a parent cluster (necessary for GOE)
+   */
+  public SubspaceClusterDefinition() {
+    super();
+  }
+
+  /**
+   * initializes the cluster with default values
+   *
+   * @param parent    the datagenerator this cluster belongs to
+   */
+  public SubspaceClusterDefinition(ClusterGenerator parent) {
+    super(parent);
+  }
+
+  /**
+   * sets the default values
+   * 
+   * @throws Exception if setting of defaults fails
+   */
+  protected void setDefaults() throws Exception {
+    setClusterType(defaultClusterType());
+    setClusterSubType(defaultClusterSubType());
+    setMinInstNum(defaultMinInstNum());
+    setMaxInstNum(defaultMaxInstNum());
+    setAttrIndexRange(defaultAttrIndexRange());
+    m_numClusterAttributes = 1;
+    setValuesList(defaultValuesList());
+  }
+
+  
+  /**
+   * Returns a string describing this data generator.
+   *
+   * @return a description of the data generator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "A single cluster for the SubspaceCluster datagenerator";
+  }
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+          "\tGenerates randomly distributed instances in the cluster.",
+          "A", 1, "-A <range>"));
+
+    result.addElement(new Option(
+          "\tGenerates uniformly distributed instances in the cluster.",
+          "U", 1, "-U <range>"));
+
+    result.addElement(new Option(
+          "\tGenerates gaussian distributed instances in the cluster.",
+          "G", 1, "-G <range>"));
+
+    result.addElement(new Option(
+          "\tThe attribute min/max (-A and -U) or mean/stddev (-G) for\n"
+          + "\tthe cluster.",
+          "D", 1, "-D <num>,<num>"));
+
+    result.addElement(new Option(
+          "\tThe range of number of instances per cluster (default "
+          + defaultMinInstNum() + ".." + defaultMaxInstNum() + ").",
+          "N", 1, "-N <num>..<num>"));
+
+    result.addElement(new Option(
+          "\tUses integer instead of continuous values (default continuous).",
+          "I", 0, "-I"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -A &lt;range&gt;
+   *  Generates randomly distributed instances in the cluster.</pre>
+   * 
+   * <pre> -U &lt;range&gt;
+   *  Generates uniformly distributed instances in the cluster.</pre>
+   * 
+   * <pre> -G &lt;range&gt;
+   *  Generates gaussian distributed instances in the cluster.</pre>
+   * 
+   * <pre> -D &lt;num&gt;,&lt;num&gt;
+   *  The attribute min/max (-A and -U) or mean/stddev (-G) for
+   *  the cluster.</pre>
+   * 
+   * <pre> -N &lt;num&gt;..&lt;num&gt;
+   *  The range of number of instances per cluster (default 1..50).</pre>
+   * 
+   * <pre> -I
+   *  Uses integer instead of continuous values (default continuous).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+    String        fromToStr;
+    int           typeCount;
+
+    typeCount = 0;
+    fromToStr = "";
+
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.length() != 0) {
+      fromToStr = tmpStr;
+      setClusterType(
+          new SelectedTag(
+            SubspaceCluster.UNIFORM_RANDOM, SubspaceCluster.TAGS_CLUSTERTYPE));
+      typeCount++;
+    }
+
+    tmpStr = Utils.getOption('U', options);
+    if (tmpStr.length() != 0) {
+      fromToStr = tmpStr;
+      setClusterType(
+          new SelectedTag(
+            SubspaceCluster.TOTAL_UNIFORM, SubspaceCluster.TAGS_CLUSTERTYPE));
+      typeCount++;
+    }
+
+    tmpStr = Utils.getOption('G', options);
+    if (tmpStr.length() != 0) {
+      fromToStr = tmpStr;
+      setClusterType(
+          new SelectedTag(
+            SubspaceCluster.GAUSSIAN, SubspaceCluster.TAGS_CLUSTERTYPE));
+      typeCount++;
+    }
+
+    // default is uniform/random
+    if (typeCount == 0)
+      setClusterType(
+          new SelectedTag(
+            SubspaceCluster.UNIFORM_RANDOM, SubspaceCluster.TAGS_CLUSTERTYPE));
+    else if (typeCount > 1)
+      throw new Exception("Only one cluster type can be specified!");
+
+    setAttrIndexRange(fromToStr);
+    
+    tmpStr = Utils.getOption('D', options);
+    if (isGaussian()) {
+      if (tmpStr.length() != 0)
+        setMeanStddev(tmpStr);
+      else
+        setMeanStddev(defaultMeanStddev());
+    }
+    else {
+      if (tmpStr.length() != 0)
+        setValuesList(tmpStr);
+      else
+        setValuesList(defaultValuesList());
+    }
+
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setInstNums(tmpStr);
+    else
+      setInstNums(defaultMinInstNum() + ".." + defaultMaxInstNum());
+
+    if (Utils.getFlag('I', options))
+      setClusterSubType(
+          new SelectedTag(
+            SubspaceCluster.INTEGER, SubspaceCluster.TAGS_CLUSTERSUBTYPE));
+    else
+      setClusterSubType(
+          new SelectedTag(
+            SubspaceCluster.CONTINUOUS, SubspaceCluster.TAGS_CLUSTERSUBTYPE));
+  }
+
+  /**
+   * Gets the current settings of the datagenerator BIRCHCluster.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result  = new Vector();
+
+    if (isRandom()) {
+      result.add("-A");
+      result.add("" + getAttrIndexRange());
+      result.add("-D");
+      result.add("" + getValuesList());
+    }
+    else if (isUniform()) {
+      result.add("-U");
+      result.add("" + getAttrIndexRange());
+      result.add("-D");
+      result.add("" + getValuesList());
+    }
+    else if (isGaussian()) {
+      result.add("-G");
+      result.add("" + getAttrIndexRange());
+      result.add("-D");
+      result.add("" + getMeanStddev());
+    }
+
+    result.add("-N"); 
+    result.add("" + getInstNums());
+
+    if (m_clustersubtype == SubspaceCluster.INTEGER)
+      result.add("-I");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Make a string from the attribues list.
+   * 
+   * @return the attributes as string
+   */
+  public String attributesToString() {
+    StringBuffer text = new StringBuffer();
+    int j = 0;
+    for (int i = 0; i < m_attributes.length; i++) {
+      if (m_attributes[i]) {
+        if (isGaussian()) {
+          text.append(" Attribute: " + i);
+          text.append(" Mean: "+ m_meanValue[j]);
+          text.append(" StdDev: "+m_stddevValue[j]+"\n%");
+        } 
+        else {
+          text.append(" Attribute: " + i);
+          text.append(" Range: "+ m_minValue[j]);
+          text.append(" - "+m_maxValue[j]+"\n%");
+        }
+        j++;
+      }
+    }
+    return text.toString();
+  }
+
+  /**
+   * Make a string from the cluster features.
+   * 
+   * @return the cluster features as string
+   */
+  public String toString() {
+    StringBuffer text = new StringBuffer();
+    text.append("attributes " + attributesToString() + "\n");
+    text.append("number of instances " + getInstNums()); 
+    return text.toString();
+  }
+
+  /**
+   * sets the parent datagenerator this cluster belongs to
+   * @param parent      the parent datagenerator
+   */
+  public void setParent(SubspaceCluster parent) {
+    super.setParent(parent);
+    m_AttrIndexRange.setUpper(getParent().getNumAttributes());
+  }
+
+  /**
+   * returns the default attribute index range
+   * 
+   * @return the default attribute index range
+   */
+  protected String defaultAttrIndexRange() {
+    return "1";
+  }
+
+  /**
+   * Sets which attributes are used in the cluster
+   * attributes among the selection will be discretized.
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br/>
+   * eg: first-3,5,6-last
+   */
+  public void setAttrIndexRange(String rangeList) {
+    m_numClusterAttributes = 0; 
+    if (m_AttrIndexRange == null)
+      m_AttrIndexRange = new Range();
+    m_AttrIndexRange.setRanges(rangeList);
+
+    if (getParent() != null) {
+      m_AttrIndexRange.setUpper(getParent().getNumAttributes());
+      m_attributes = new boolean [getParent().getNumAttributes()];
+      for (int i = 0; i < m_attributes.length; i++) {
+        if (m_AttrIndexRange.isInRange(i)) {
+          m_numClusterAttributes++;
+          m_attributes[i] = true; 
+        } 
+        else {
+          m_attributes[i] = false; 
+        }
+      }
+
+      //store translation from attr in cluster to attr in whole dataset
+      m_attrIndices = new int[m_numClusterAttributes];
+      int clusterI = -1;
+      for (int i = 0; i < m_attributes.length; i++) {
+        if (m_AttrIndexRange.isInRange(i)) {
+          clusterI++;
+          m_attrIndices[clusterI] = i;
+        }
+      }
+    }
+  }
+
+  /**
+   * returns the attribute range(s).
+   * 
+   * @return the attribute range(s).
+   */
+  public String getAttrIndexRange() {
+    return m_AttrIndexRange.getRanges();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attrIndexRangeTipText() {
+    return "The attribute range(s).";
+  }
+
+  public boolean[] getAttributes() {
+    return m_attributes;
+  }
+
+  public double[] getMinValue() {
+    return m_minValue;
+  }
+
+  public double[] getMaxValue() {
+    return m_maxValue;
+  }
+
+  public double[] getMeanValue() {
+    return m_meanValue;
+  }
+
+  public double[] getStddevValue() {
+    return m_stddevValue;
+  }
+
+  public int getNumInstances () { 
+    return m_numInstances; 
+  }
+
+  /**
+   * returns the default cluster type
+   * 
+   * @return the default cluster type
+   */
+  protected SelectedTag defaultClusterType() {
+    return new SelectedTag(
+        SubspaceCluster.UNIFORM_RANDOM, SubspaceCluster.TAGS_CLUSTERTYPE);
+  }
+  
+  /**
+   * Gets the cluster type.
+   *
+   * @return the cluster type
+   * @see SubspaceCluster#TAGS_CLUSTERTYPE
+   */
+  public SelectedTag getClusterType() {
+    return new SelectedTag(m_clustertype, SubspaceCluster.TAGS_CLUSTERTYPE);
+  }
+  
+  /**
+   * Sets the cluster type.
+   *
+   * @param value the new cluster type.
+   * @see SubspaceCluster#TAGS_CLUSTERTYPE
+   */
+  public void setClusterType(SelectedTag value) {
+    if (value.getTags() == SubspaceCluster.TAGS_CLUSTERTYPE)
+      m_clustertype = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clusterTypeTipText() {
+    return "The type of cluster to use.";
+  }
+
+  /**
+   * returns the default cluster sub type
+   * 
+   * @return the default cluster sub type
+   */
+  protected SelectedTag defaultClusterSubType() {
+    return new SelectedTag(
+        SubspaceCluster.CONTINUOUS, SubspaceCluster.TAGS_CLUSTERSUBTYPE);
+  }
+  
+  /**
+   * Gets the cluster sub type.
+   *
+   * @return the cluster sub type
+   * @see SubspaceCluster#TAGS_CLUSTERSUBTYPE
+   */
+  public SelectedTag getClusterSubType() {
+    return new SelectedTag(
+                  m_clustersubtype, SubspaceCluster.TAGS_CLUSTERSUBTYPE);
+  }
+  
+  /**
+   * Sets the cluster sub type.
+   *
+   * @param value the new cluster sub type.
+   * @see SubspaceCluster#TAGS_CLUSTERSUBTYPE
+   */
+  public void setClusterSubType(SelectedTag value) {
+    if (value.getTags() == SubspaceCluster.TAGS_CLUSTERSUBTYPE)
+      m_clustersubtype = value.getSelectedTag().getID();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clusterSubTypeTipText() {
+    return "The sub-type of cluster to use.";
+  }
+
+  /** 
+   * checks, whether cluster type is random
+   * 
+   * @return true if cluster type is random
+   */
+  public boolean isRandom() {
+    return (m_clustertype == SubspaceCluster.UNIFORM_RANDOM);
+  }
+
+  /** 
+   * checks, whether cluster type is uniform
+   * 
+   * @return true if cluster type is uniform
+   */
+  public boolean isUniform() {
+    return (m_clustertype == SubspaceCluster.TOTAL_UNIFORM);
+  }
+
+  /** 
+   * checks, whether cluster type is gaussian
+   * 
+   * @return true if cluster type is gaussian
+   */
+  public boolean isGaussian() {
+    return (m_clustertype == SubspaceCluster.GAUSSIAN);
+  }
+
+  /** 
+   * checks, whether cluster sub type is continuous
+   * 
+   * @return true if cluster sub type is continuous
+   */
+  public boolean isContinuous() {
+    return (m_clustertype == SubspaceCluster.CONTINUOUS);
+  }
+
+  /** 
+   * checks, whether cluster sub type is integer
+   * 
+   * @return true if cluster sub type is integer
+   */
+  public boolean isInteger() {
+    return (m_clustertype == SubspaceCluster.INTEGER);
+  }
+
+  /**
+   * Sets the upper and lower boundary for instances for this cluster.
+   *
+   * @param fromTo  the string containing the upper and lower boundary for
+   *                instances per cluster separated by ..
+   */
+  protected void setInstNums(String fromTo) {
+    int i = fromTo.indexOf("..");
+    if (i == -1) 
+      i = fromTo.length();
+    String from = fromTo.substring(0, i);
+    m_MinInstNum = Integer.parseInt(from);
+    if (i < fromTo.length()) {
+      String to = fromTo.substring(i + 2, fromTo.length());
+      m_MaxInstNum = Integer.parseInt(to);
+    } 
+    else {
+      m_MaxInstNum = m_MinInstNum;
+    }
+  }
+
+  /**
+   * Get a string with the  upper and lower boundary for the 
+   * number of instances for this cluster.
+   *
+   * @return the string containing the upper and lower boundary for
+   * instances per cluster separated by ..
+   */
+  protected String getInstNums() {
+    String text = new String(""+m_MinInstNum+".."+m_MaxInstNum);
+    return text;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  protected String instNumsTipText() {
+    return "The lower and upper boundary for the number of instances in this cluster.";
+  }
+
+  /**
+   * returns the default min number of instances
+   * 
+   * @return the default min number of instances
+   */
+  protected int defaultMinInstNum() {
+    return 1;
+  }
+
+  /**
+   * Gets the lower boundary for instances per cluster.
+   *
+   * @return the the lower boundary for instances per cluster
+   */
+  public int getMinInstNum() { 
+    return m_MinInstNum; 
+  }
+  
+  /**
+   * Sets the lower boundary for instances per cluster.
+   *
+   * @param newMinInstNum new lower boundary for instances per cluster
+   */
+  public void setMinInstNum(int newMinInstNum) {
+    m_MinInstNum = newMinInstNum;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String minInstNumTipText() {
+    return "The lower boundary for instances per cluster.";
+  }
+
+  /**
+   * returns the default max number of instances
+   * 
+   * @return the default max number of instances
+   */
+  protected int defaultMaxInstNum() {
+    return 50;
+  }
+
+  /**
+   * Gets the upper boundary for instances per cluster.
+   *
+   * @return the upper boundary for instances per cluster
+   */
+  public int getMaxInstNum() { 
+    return m_MaxInstNum; 
+  }
+  
+  /**
+   * Sets the upper boundary for instances per cluster.
+   *
+   * @param newMaxInstNum new upper boundary for instances per cluster
+   */
+  public void setMaxInstNum(int newMaxInstNum) {
+    m_MaxInstNum = newMaxInstNum;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   *         displaying in the explorer/experimenter gui
+   */
+  public String maxInstNumTipText() {
+    return "The upper boundary for instances per cluster.";
+  }
+
+  /**
+   * Sets the real number of instances for this cluster.
+   *
+   * @param r random number generator 
+   */
+  public void setNumInstances(Random r) {
+    if (m_MaxInstNum > m_MinInstNum)
+      m_numInstances = (int)(r.nextDouble() 
+                       * (m_MaxInstNum - m_MinInstNum) + m_MinInstNum);
+    else
+      m_numInstances =  m_MinInstNum;
+  }
+
+  /**
+   * returns the default values list
+   * 
+   * @return the default values list
+   */
+  protected String defaultValuesList() {
+    return "1,10";
+  }
+
+  /**
+   * Sets the ranges for each attribute.
+   *
+   * @param fromToList the string containing the upper and lower boundary for
+   * instances per cluster separated by ..
+   * @throws Exception if values are not correct in number or value
+   */
+  public void setValuesList(String fromToList) throws Exception {
+    m_minValue = new double [m_numClusterAttributes];
+    m_maxValue = new double [m_numClusterAttributes];
+    setValuesList(fromToList, m_minValue, m_maxValue, "D");
+    SubspaceCluster parent = (SubspaceCluster) getParent();
+
+    for (int i = 0; i < m_numClusterAttributes; i++) {
+      if (m_minValue[i] > m_maxValue[i])
+        throw new Exception("Min must be smaller than max.");
+
+      if (getParent() != null) {
+        // boolean values are only 0.0 and 1.0
+        if (parent.isBoolean(m_attrIndices[i])) {
+          parent.getNumValues()[m_attrIndices[i]] = 2;
+          if (((m_minValue[i] != 0.0) && (m_minValue[i] != 1.0)) ||
+              ((m_maxValue[i] != 0.0) && (m_maxValue[i] != 1.0)))
+            throw new Exception("Ranges for boolean must be 0 or 1 only.");
+        }
+
+        if (parent.isNominal(m_attrIndices[i])) {
+          // nominal values: attributes range might have to be enlarged
+          double rest = m_minValue[i] - Math.rint(m_minValue[i]);
+          if (rest != 0.0) 
+            throw new Exception(" Ranges for nominal must be integer"); 
+          rest = m_maxValue[i] - Math.rint(m_maxValue[i]);
+          if (rest != 0.0) 
+            throw new Exception("Ranges for nominal must be integer"); 
+          if (m_minValue[i] < 0.0) 
+            throw new Exception("Range for nominal must start with number 0.0 or higher");
+          if (m_maxValue[i] + 1 > parent.getNumValues()[m_attrIndices[i]]) {
+            // add new values to attribute
+            // (actual format is not yet defined)
+            parent.getNumValues()[m_attrIndices[i]] = (int)m_maxValue[i] + 1;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * returns the range for each attribute as string
+   */
+  public String getValuesList() {
+    String        result;
+    int           i;
+
+    result = "";
+
+    if (m_minValue != null) {
+      for (i = 0; i < m_minValue.length; i++) {
+        if (i > 0)
+          result += ",";
+        result += "" + m_minValue[i] + "," + m_maxValue[i];
+      }
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String valuesListTipText() {
+    return "The range for each each attribute as string.";
+  }
+
+  /**
+   * returns the default mean/stddev list
+   */
+  protected String defaultMeanStddev() {
+    return "0,1.0";
+  }
+
+  /**
+   * Sets mean and standarddeviation.
+   *
+   * @param meanstddev the string containing the upper and lower boundary for
+   * instances per cluster separated by ..
+   * @throws Exception if values are not correct in number or value
+   */
+  public void setMeanStddev(String meanstddev) throws Exception {
+    m_meanValue   = new double [m_numClusterAttributes];
+    m_stddevValue = new double [m_numClusterAttributes];
+    setValuesList(meanstddev, m_meanValue, m_stddevValue, "D");
+  }
+
+  /**
+   * returns the current mean/stddev setup
+   */
+  public String getMeanStddev() {
+    String        result;
+    int           i;
+
+    result = "";
+
+    if (m_meanValue != null) {
+      for (i = 0; i < m_meanValue.length; i++) {
+        if (i > 0)
+          result += ",";
+        result += "" + m_meanValue[i] + "," + m_stddevValue[i];
+      }
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String meanStddevTipText() {
+    return "The mean and stddev, in case of gaussian.";
+  }
+
+  /**
+   * Sets the ranges for each attribute.
+   *
+   * @param fromToList the string containing the upper and lower boundary for
+   * instances per cluster separated by ..
+   * @param first the "from's"
+   * @param second the "to's"
+   * @param optionLetter the option, from which the list came
+   * @throws Exception if values are not correct in number or value
+   */
+  public void setValuesList(String fromToList, double[] first, double[] second, 
+      String optionLetter) throws Exception {
+
+    StringTokenizer     tok;
+    int                 index;
+    
+    tok = new StringTokenizer(fromToList, ",");
+    if (tok.countTokens() != first.length + second.length)
+      throw new Exception(
+          "Wrong number of values for option '-" + optionLetter + "'.");
+
+    index = 0;
+    while (tok.hasMoreTokens()) {
+      first[index]  = Double.parseDouble(tok.nextToken());
+      second[index] = Double.parseDouble(tok.nextToken());
+      index++;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/CheckEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/CheckEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/CheckEstimator.java	(revision 29)
@@ -0,0 +1,2152 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TestInstances;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Class for examining the capabilities and finding problems with 
+ * estimators. If you implement a estimator using the WEKA.libraries,
+ * you should run the checks on it to ensure robustness and correct
+ * operation. Passing all the tests of this object does not mean
+ * bugs in the estimator don't exist, but this will help find some
+ * common ones. <p/>
+ * 
+ * Typical usage: <p/>
+ * <code>java weka.estimators.CheckEstimator -W estimator_name 
+ * estimator_options </code><p/>
+ * 
+ * This class uses code from the CheckEstimatorClass
+ * ATTENTION! Current estimators can only 
+ * 1. split on a nominal class attribute
+ * 2. build estimators for nominal and numeric attributes
+ * 3. build estimators independendly of the class type
+ * The functionality to test on other class and attribute types
+ * is left in big parts in the code. 
+ * 
+ * CheckEstimator reports on the following:
+ * <ul>
+ *    <li> Estimator abilities 
+ *      <ul>
+ *         <li> Possible command line options to the estimator </li>
+ *         <li> Whether the estimator can predict nominal, numeric, string, 
+ *              date or relational class attributes. Warnings will be displayed if 
+ *              performance is worse than ZeroR </li>
+ *         <li> Whether the estimator can be trained incrementally </li>
+ *         <li> Whether the estimator can build estimates for numeric attributes </li>
+ *         <li> Whether the estimator can handle nominal attributes </li>
+ *         <li> Whether the estimator can handle string attributes </li>
+ *         <li> Whether the estimator can handle date attributes </li>
+ *         <li> Whether the estimator can handle relational  attributes </li>
+ *         <li> Whether the estimator build estimates for multi-instance data </li>
+ *         <li> Whether the estimator can handle missing attribute values </li>
+ *         <li> Whether the estimator can handle missing class values </li>
+ *         <li> Whether a nominal estimator only handles 2 class problems </li>
+ *         <li> Whether the estimator can handle instance weights </li>
+ *      </ul>
+ *    </li>
+ *    <li> Correct functioning 
+ *      <ul>
+ *         <li> Correct initialisation during addvalues (i.e. no result
+ *              changes when addValues called repeatedly) </li>
+ *         <li> Whether incremental training produces the same results
+ *              as during non-incremental training (which may or may not 
+ *              be OK) </li>
+ *         <li> Whether the estimator alters the data pased to it 
+ *              (number of instances, instance order, instance weights, etc) </li>
+ *      </ul>
+ *    </li>
+ *    <li> Degenerate cases 
+ *      <ul>
+ *         <li> building estimator with zero training instances </li>
+ *         <li> all but one attribute attribute values missing </li>
+ *         <li> all attribute attribute values missing </li>
+ *         <li> all but one class values missing </li>
+ *         <li> all class values missing </li>
+ *      </ul>
+ *    </li>
+ * </ul>
+ * Running CheckEstimator with the debug option set will output the 
+ * training and test datasets for any failed tests.<p/>
+ *
+ * The <code>weka.estimators.AbstractEstimatorTest</code> uses this
+ * class to test all the estimators. Any changes here, have to be 
+ * checked in that abstract test class, too. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turn on debugging output.</pre>
+ * 
+ * <pre> -S
+ *  Silent mode - prints nothing to stdout.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  The number of instances in the datasets (default 100).</pre>
+ * 
+ * <pre> -W
+ *  Full name of the estimator analysed.
+ *  eg: weka.estimators.NormalEstimator</pre>
+ * 
+ * <pre> 
+ * Options specific to estimator weka.estimators.NormalEstimator:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, estimator is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated estimator.<p/>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4997 $
+ * @see TestInstances
+ */
+public class CheckEstimator implements OptionHandler, RevisionHandler {
+
+  /*
+   * Note about test methods:
+   * - methods return array of booleans
+   * - first index: success or not
+   * - second index: acceptable or not (e.g., Exception is OK)
+   * - in case the performance is worse than that of ZeroR both indices are true
+   *
+   * FracPete (fracpete at waikato dot ac dot nz)
+   */
+  
+  /** a class for postprocessing the test-data 
+   */
+  public class PostProcessor
+    implements RevisionHandler {
+    /**
+     * Provides a hook for derived classes to further modify the data. Currently,
+     * the data is just passed through.
+     * 
+     * @param data	the data to process
+     * @return		the processed data
+     */
+    protected Instances process(Instances data) {
+      return data;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4997 $");
+    }
+  }
+  
+  /*** The estimator to be examined */
+  protected Estimator m_Estimator = (Estimator) new weka.estimators.NormalEstimator(0.000001);
+  
+  /** The options to be passed to the base estimator. */
+  protected String[] m_EstimatorOptions;
+  
+  /** The results of the analysis as a string */
+  protected String m_AnalysisResults;
+  
+  /** Debugging mode, gives extra output if true */
+  protected boolean m_Debug = false;
+  
+  /** Silent mode, for no output at all to stdout */
+  protected boolean m_Silent = false;
+  
+  /** The number of instances in the datasets */
+  protected int m_NumInstances = 100;
+  
+  /** for post-processing the data even further */
+  protected PostProcessor m_PostProcessor = null;
+  
+  /** whether classpath problems occurred */
+  protected boolean m_ClasspathProblems = false;
+  
+  /**
+   * class that contains info about the attribute types the estimator can estimate
+   * estimator work on one attribute only
+   */
+  public static class AttrTypes
+    implements RevisionHandler {
+    
+    boolean nominal = false;
+    boolean numeric = false; 
+    boolean string = false;
+    boolean date = false;
+    boolean relational = false;
+	
+    AttrTypes() {
+    }
+
+    AttrTypes (AttrTypes newTypes) {
+      nominal = newTypes.nominal;
+      numeric = newTypes.numeric;
+      string = newTypes.string;
+      date = newTypes.date;
+      relational = newTypes.relational;
+    }
+			
+    AttrTypes (int type) {
+      if (type == Attribute.NOMINAL) nominal = true;
+      if (type == Attribute.NUMERIC) numeric = true;
+      if (type == Attribute.STRING) string = true;
+      if (type == Attribute.DATE) date = true;
+      if (type == Attribute.RELATIONAL) relational = true;
+    }
+
+    int getSetType() throws Exception {			
+      int sum = 0;
+      int type = -1;
+      if (nominal) { sum ++; type = Attribute.NOMINAL; }
+      if (numeric) { sum ++; type = Attribute.NUMERIC; }
+      if (string) { sum ++; type = Attribute.STRING; }
+      if (date) { sum ++; type = Attribute.DATE; }
+      if (relational) { sum ++; type = Attribute.RELATIONAL; }
+      if (sum > 1)
+	throw new Exception("Expected to have only one type set used wrongly.");
+      if (type < 0)
+	throw new Exception("No type set.");
+      return type;
+    }
+
+    boolean oneIsSet() {
+      return (nominal || numeric || string || date || relational);
+    }
+
+    public Vector getVectorOfAttrTypes() {
+      Vector attrs = new Vector();
+      if (nominal) attrs.add(new Integer(Attribute.NOMINAL));
+      if (numeric) attrs.add(new Integer(Attribute.NUMERIC));
+      if (string) attrs.add(new Integer(Attribute.STRING));
+      if (date) attrs.add(new Integer(Attribute.DATE));
+      if (relational) attrs.add(new Integer(Attribute.RELATIONAL));
+      return attrs;
+    }   
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4997 $");
+    }
+  }
+
+  /**
+   * public class that contains info about the chosen attribute type
+   * estimator work on one attribute only
+   */
+  public static class EstTypes
+    implements RevisionHandler {
+    
+    boolean incremental = false;
+    boolean weighted = false;
+    boolean supervised = false;
+
+    /**
+     * Constructor
+     */
+    public EstTypes () {
+    }
+
+    /**
+     * Constructor
+     */
+    public EstTypes (boolean i, boolean w, boolean s) {
+      incremental = i;
+      weighted    = w;
+      supervised  = s;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 4997 $");
+    }
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(2);
+    
+    newVector.addElement(new Option(
+        "\tTurn on debugging output.",
+        "D", 0, "-D"));
+    
+    newVector.addElement(new Option(
+        "\tSilent mode - prints nothing to stdout.",
+        "S", 0, "-S"));
+    
+    newVector.addElement(new Option(
+        "\tThe number of instances in the datasets (default 100).",
+        "N", 1, "-N <num>"));
+    
+    newVector.addElement(new Option(
+        "\tFull name of the estimator analysed.\n"
+        +"\teg: weka.estimators.NormalEstimator",
+        "W", 1, "-W"));
+    
+    if ((m_Estimator != null) 
+        && (m_Estimator instanceof OptionHandler)) {
+      newVector.addElement(new Option("", "", 0, 
+          "\nOptions specific to estimator "
+          + m_Estimator.getClass().getName()
+          + ":"));
+      Enumeration enu = ((OptionHandler)m_Estimator).listOptions();
+      while (enu.hasMoreElements())
+        newVector.addElement(enu.nextElement());
+    }
+    
+    return newVector.elements();
+  }
+  
+  /**
+   * Parses a given list of options. 
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turn on debugging output.</pre>
+   * 
+   * <pre> -S
+   *  Silent mode - prints nothing to stdout.</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  The number of instances in the datasets (default 100).</pre>
+   * 
+   * <pre> -W
+   *  Full name of the estimator analysed.
+   *  eg: weka.estimators.NormalEstimator</pre>
+   * 
+   * <pre> 
+   * Options specific to estimator weka.estimators.NormalEstimator:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, estimator is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    
+    setDebug(Utils.getFlag('D', options));
+    
+    setSilent(Utils.getFlag('S', options));
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      setNumInstances(Integer.parseInt(tmpStr));
+    else
+      setNumInstances(100);
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      throw new Exception("A estimator must be specified with the -W option.");
+    setEstimator(Estimator.forName(tmpStr, Utils.partitionOptions(options)));
+  }
+  
+  /**
+   * Gets the current settings of the CheckEstimator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+    
+    result = new Vector();
+    
+    if (getDebug())
+      result.add("-D");
+    
+    if (getSilent())
+      result.add("-S");
+    
+    result.add("-N");
+    result.add("" + getNumInstances());
+    
+    if (getEstimator() != null) {
+      result.add("-W");
+      result.add(getEstimator().getClass().getName());
+    }
+    
+    if ((m_Estimator != null) && (m_Estimator instanceof OptionHandler))
+      options = ((OptionHandler) m_Estimator).getOptions();
+    else
+      options = new String[0];
+    
+    if (options.length > 0) {
+      result.add("--");
+      for (i = 0; i < options.length; i++)
+        result.add(options[i]);
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * sets the PostProcessor to use
+   * 
+   * @param value	the new PostProcessor
+   * @see #m_PostProcessor
+   */
+  public void setPostProcessor(PostProcessor value) {
+    m_PostProcessor = value;
+  }
+  
+  /**
+   * returns the current PostProcessor, can be null
+   * 
+   * @return		the current PostProcessor
+   */
+  public PostProcessor getPostProcessor() {
+    return m_PostProcessor;
+  }
+  
+  /**
+   * returns TRUE if the estimator returned a "not in classpath" Exception
+   * 
+   * @return	true if CLASSPATH problems occurred
+   */
+  public boolean hasClasspathProblems() {
+    return m_ClasspathProblems;
+  }
+  
+  /**
+   * Begin the tests, reporting results to System.out
+   */
+  public void doTests() {
+    
+    if (getEstimator() == null) {
+      println("\n=== No estimator set ===");
+      return;
+    }
+    println("\n=== Check on Estimator: "
+        + getEstimator().getClass().getName()
+        + " ===\n");
+    
+    m_ClasspathProblems = false;
+
+    // Start tests with test for options
+    canTakeOptions();
+
+    // test what type of estimator it is 
+    EstTypes estTypes = new EstTypes();
+    estTypes.incremental = incrementalEstimator()[0];
+    estTypes.weighted = weightedInstancesHandler()[0];
+    estTypes.supervised = supervisedEstimator()[0];
+   
+    // in none of the estimators yet the functionality is depending on the class type
+    // since this could change the basic structure taken from checkclassifiers is kept here
+    int classType = Attribute.NOMINAL;
+    AttrTypes attrTypes = testsPerClassType(classType, estTypes);
+    
+ 
+    // only nominal class can be split up so far
+    canSplitUpClass(attrTypes, classType);
+ }
+  
+  
+  /**
+   * Set debugging mode
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+    m_Debug = debug;
+
+    // disable silent mode, if necessary
+    if (getDebug())
+      setSilent(false);
+  }
+  
+  /**
+   * Get whether debugging is turned on
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Set slient mode, i.e., no output at all to stdout
+   *
+   * @param value whether silent mode is active or not
+   */
+  public void setSilent(boolean value) {
+    m_Silent = value;
+  }
+  
+  /**
+   * Get whether silent mode is turned on
+   *
+   * @return true if silent mode is on
+   */
+  public boolean getSilent() {
+    return m_Silent;
+  }
+  
+  /**
+   * Sets the number of instances to use in the datasets (some estimators
+   * might require more instances).
+   *
+   * @param value the number of instances to use
+   */
+  public void setNumInstances(int value) {
+    m_NumInstances = value;
+  }
+  
+  /**
+   * Gets the current number of instances to use for the datasets.
+   *
+   * @return the number of instances
+   */
+  public int getNumInstances() {
+    return m_NumInstances;
+  }
+  
+  /**
+   * Set the estimator for boosting. 
+   *
+   * @param newEstimator the Estimator to use.
+   */
+  public void setEstimator(Estimator newEstimator) {
+    m_Estimator = newEstimator;
+  }
+  
+  /**
+   * Get the estimator used as the estimator
+   *
+   * @return the estimator used as the estimator
+   */
+  public Estimator getEstimator() {
+    return m_Estimator;
+  }
+  
+  /**
+   * prints the given message to stdout, if not silent mode
+   * 
+   * @param msg         the text to print to stdout
+   */
+  protected void print(Object msg) {
+    if (!getSilent())
+      System.out.print(msg);
+  }
+  
+  /**
+   * prints the given message (+ LF) to stdout, if not silent mode
+   * 
+   * @param msg         the message to println to stdout
+   */
+  protected void println(Object msg) {
+    print(msg + "\n");
+  }
+  
+  /**
+   * prints a LF to stdout, if not silent mode
+   */
+  protected void println() {
+    print("\n");
+  }
+  
+  /**
+   * Run a battery of tests for a given class attribute type
+   *
+   * @param classType true if the class attribute should be numeric
+   * @param estTypes types the estimator is, like incremental, weighted, supervised etc
+   * @return attribute types estimator can work with
+   */
+  protected AttrTypes testsPerClassType(int classType, EstTypes estTypes) {
+    
+    // in none of the estimators yet is the estimation depending on the class type
+    // since this could change the basic structure taken from checkclassifiers is kept here
+    
+    // test A: simple test - if can estimate
+    AttrTypes attrTypes = new AttrTypes();
+    AttrTypes at = new AttrTypes(Attribute.NOMINAL);
+    attrTypes.nominal = canEstimate(at, estTypes.supervised, classType)[0];
+    at = new AttrTypes(Attribute.NUMERIC);
+    attrTypes.numeric = canEstimate(at, estTypes.supervised, classType)[0];
+    attrTypes.string = false;
+    attrTypes.date = false;
+    attrTypes.relational = false;
+    
+//  if (!multiInstance)
+//  PRel = canEstimate(false, false, false, false,  true, classType)[0];
+//  else
+//  PRel = false;
+    
+//  one of the attribute types succeeded
+    
+    if (attrTypes.oneIsSet()) {
+      Vector attributesSet = attrTypes.getVectorOfAttrTypes();
+      
+      // make tests for each attribute
+      for (int i = 0; i < attributesSet.size(); i++) {
+        AttrTypes workAttrTypes = new AttrTypes(((Integer) attributesSet.elementAt(i)).intValue());
+        
+        // test B: weights change estimate or not
+        if (estTypes.weighted)
+          instanceWeights(workAttrTypes, classType);
+        
+        if (classType == Attribute.NOMINAL) {
+          int numClasses = 4;
+          canHandleNClasses(workAttrTypes, numClasses);
+        }
+        
+        // tests with class not the last attribute and the attribute not the first
+        
+        //   if (!multiInstance) {
+        int numAtt = 4; 
+        
+        canHandleClassAsNthAttribute(workAttrTypes, numAtt, 0, classType, 1);
+        
+        //TODOTODOcanHandleAttrAsNthAttribute(workAttrTypes, numAtt, 2, classType);
+        //}
+        
+        canHandleZeroTraining(workAttrTypes, classType);
+        boolean handleMissingAttributes = canHandleMissing(workAttrTypes, 
+            classType, true, false, 20)[0];
+        if (handleMissingAttributes)
+          canHandleMissing(workAttrTypes, classType, true, false, 100);
+        
+        boolean handleMissingClass = canHandleMissing(workAttrTypes, 
+            classType, 
+            false, true, 20)[0];
+        if (handleMissingClass)
+          canHandleMissing(workAttrTypes, classType, false, true, 100);
+        
+        correctBuildInitialisation(workAttrTypes, classType);
+        datasetIntegrity(workAttrTypes, classType,
+            handleMissingAttributes, handleMissingClass);
+        
+        if (estTypes.incremental)
+          incrementingEquality(workAttrTypes, classType);
+      }
+    }
+    return attrTypes;
+  }
+  
+  /**
+   * Checks whether the scheme can take command line options.
+   *
+   * @return index 0 is true if the estimator can take options
+   */
+  protected boolean[] canTakeOptions() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("options...");
+    if (m_Estimator instanceof OptionHandler) {
+      println("yes");
+      if (m_Debug) {
+        println("\n=== Full report ===");
+        Enumeration enu = ((OptionHandler)m_Estimator).listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          print(option.synopsis() + "\n" 
+              + option.description() + "\n");
+        }
+        println("\n");
+      }
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme can build models incrementally.
+   *
+   * @return index 0 is true if the estimator can train incrementally
+   */
+  protected boolean[] incrementalEstimator() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("incremental estimator...");
+    if (m_Estimator instanceof IncrementalEstimator) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme says it can handle instance weights.
+   *
+   * @return true if the estimator handles instance weights
+   */
+  protected boolean[] weightedInstancesHandler() {
+    
+    boolean[] result = new boolean[2];
+    
+    print("weighted instances estimator...");
+    if (m_Estimator instanceof WeightedInstancesHandler) {
+      println("yes");
+      result[0] = true;
+    }
+    else {
+      println("no");
+      result[0] = false;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Checks whether the estimator is supervised.
+   *
+   * @return true if the estimator handles instance weights
+   */
+  protected boolean[] supervisedEstimator() {
+    boolean[] result = new boolean[2];
+    result[0] = false;
+    return result;
+  }
+
+  /**
+   * Checks basic estimation of one attribute of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param attrTypes the types the estimator can work with
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canEstimate(AttrTypes attrTypes, boolean supervised, int classType) {
+    
+  // supervised is ignored, no supervised estimators used yet
+    
+    print("basic estimation");
+    printAttributeSummary(attrTypes, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("nominal");
+    accepts.addElement("numeric");
+    accepts.addElement("string");
+    accepts.addElement("date");
+    accepts.addElement("relational");
+    accepts.addElement("not in classpath");
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    int numAtts = 1, attrIndex = 0;
+
+    return runBasicTest(attrTypes, numAtts, attrIndex,
+			classType, 
+			missingLevel, attributeMissing, classMissing,
+			numTrain, numTest, numClasses, 
+			accepts);
+  }
+  
+  /**
+   * Checks basic estimation of one attribute of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param attrTypes the types the estimator can work with
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+    */
+  protected void canSplitUpClass(AttrTypes attrTypes, int classType) {
+    
+    if (attrTypes.nominal)
+      canSplitUpClass(Attribute.NOMINAL, classType);
+    if (attrTypes.numeric)
+      canSplitUpClass(Attribute.NUMERIC, classType);
+  }
+  
+  /**
+   * Checks basic estimation of one attribute of the scheme, for simple non-troublesome
+   * datasets.
+   *
+   * @param attrType the type of the estimator
+   * @param classType the class type (NOMINAL, NUMERIC, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canSplitUpClass(int attrType, int classType) {
+    
+    boolean[] result = new boolean[2];
+
+    FastVector accepts = new FastVector();
+    accepts.addElement("not in classpath");
+
+    // supervised is ignored, no supervised estimators used yet
+    print("split per class type ");
+    printAttributeSummary(attrType, Attribute.NOMINAL);
+    print("...");
+      
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2;
+    boolean attributeMissing = false, classMissing = false;
+    int numAtts = 3, attrIndex = 0, classIndex = 1;
+    Instances train = null;
+    Vector test;
+    Estimator estimator = null;
+    boolean built = false;
+    
+    try {
+      AttrTypes at = new AttrTypes(attrType);
+      train = makeTestDataset(42, numTrain, numAtts, at,
+          numClasses, classType, classIndex);
+      
+       // prepare training data set and test value list
+      test = makeTestValueList(24, numTest, train, attrIndex,
+          attrType);
+      
+       estimator = Estimator.makeCopies(getEstimator(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      estimator.addValues(train, attrIndex, classType, classIndex);
+      built = true;
+      
+      testWithTestValues(estimator, test);
+      
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+        msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+        m_ClasspathProblems = true;
+      
+      for (int i = 0; i < accepts.size(); i++) {
+        if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+          acceptable = true;
+        }
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+          println("=== Test Dataset ===\n"
+              + test.toString() + "\n\n");
+        }
+        
+      }
+    }
+    return result;
+   }
+  
+  /**
+   * Checks whether nominal schemes can handle more than two classes.
+   * If a scheme is only designed for two-class problems it should
+   * throw an appropriate exception for multi-class problems.
+   *
+   * @param attrTypes attribute types the estimator excepts 
+   * @param numClasses the number of classes to test
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleNClasses(AttrTypes attrTypes, int numClasses) {
+    
+    print("more than two class problems");
+    printAttributeSummary(attrTypes, Attribute.NOMINAL);
+    print("...");
+
+    FastVector accepts = new FastVector();
+    accepts.addElement("number");
+    accepts.addElement("class");
+
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+      missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    int numAttr = 1, attrIndex = 0;
+
+    return runBasicTest(attrTypes,
+                        numAttr, attrIndex,
+                        Attribute.NOMINAL,
+                        missingLevel, attributeMissing, classMissing,
+                        numTrain, numTest, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle class attributes as Nth attribute.
+   *
+   * @param attrTypes the attribute types the estimator accepts
+   * @param numAtts of attributes
+   * @param attrIndex the index of the attribute
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class attribute (0-based, -1 means last attribute)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   * @see TestInstances#CLASS_IS_LAST
+   */
+  protected boolean[] canHandleClassAsNthAttribute(AttrTypes attrTypes,
+						   int numAtts,
+						   int attrIndex,
+						   int classType,
+						   int classIndex) {
+    
+    if (classIndex == TestInstances.CLASS_IS_LAST)
+      print("class attribute as last attribute");
+    else
+      print("class attribute as " + (classIndex + 1) + ". attribute");
+    printAttributeSummary(attrTypes, classType);
+    print("...");
+    FastVector accepts = new FastVector();
+    int numTrain = getNumInstances(), numTest = getNumInstances(), numClasses = 2, 
+    missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    
+    return runBasicTest(attrTypes,
+			numAtts, attrIndex,
+                        classType, classIndex,
+                        missingLevel, attributeMissing, classMissing,
+                        numTrain, numTest, numClasses, 
+                        accepts);
+  }
+  
+  /**
+   * Checks whether the scheme can handle zero training instances.
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleZeroTraining(AttrTypes attrTypes, int classType) {
+    
+    print("handle zero training instances");
+    printAttributeSummary(attrTypes, classType);
+
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("train");
+    accepts.addElement("value");
+    int numTrain = 0, numTest = getNumInstances(), numClasses = 2, 
+    missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    int numAtts = 1;
+    int attrIndex = 0;
+    return runBasicTest(
+              attrTypes, numAtts, attrIndex,
+              classType, 
+              missingLevel, attributeMissing, classMissing,
+              numTrain, numTest, numClasses, 
+              accepts);
+  }
+  
+  /**
+   * Checks whether the scheme correctly initialises models when 
+   * buildEstimator is called. This test calls buildEstimator with
+   * one training dataset and records performance on a test set. 
+   * buildEstimator is then called on a training set with different
+   * structure, and then again with the original training set. The
+   * performance on the test set is compared with the original results
+   * and any performance difference noted as incorrect build initialisation.
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed, index 1 is true if the
+   *         scheme performs worse than ZeroR, but without error (index 0 is
+   *         false)
+   */
+  protected boolean[] correctBuildInitialisation(AttrTypes attrTypes,
+						 int classType) {
+
+    boolean[] result = new boolean[2];
+    
+    print("correct initialisation during buildEstimator");
+    printAttributeSummary(attrTypes, classType);
+
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    
+    Instances train1 = null;
+    Instances test1 = null;
+    Instances train2 = null;
+    Instances test2 = null;
+    Estimator estimator = null;
+    Estimator estimator1 = null;
+    
+    boolean built = false;
+    int stage = 0;
+    int attrIndex1 = 1;
+    int attrIndex2 = 2;
+
+    try {
+      
+      // Make two sets of train/test splits with different 
+      // numbers of attributes
+      train1 = makeTestDataset(42, numTrain, 2, attrTypes,
+                               numClasses, 
+                               classType);
+      train2 = makeTestDataset(84, numTrain, 3, attrTypes,
+                               numClasses, 
+                               classType);
+      if (missingLevel > 0) {
+        addMissing(train1, missingLevel, attributeMissing, classMissing, attrIndex1);
+        addMissing(train2, missingLevel, attributeMissing, classMissing, attrIndex2);
+      }
+      
+      estimator = Estimator.makeCopies(getEstimator(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      //TESTING??
+      stage = 0;
+      estimator.addValues(train1, attrIndex1);
+      built = true;
+
+      estimator1 = estimator.makeCopies(getEstimator(), 1)[0];
+      
+      stage = 1;
+      built = false;
+      estimator.addValues(train2, attrIndex2);
+      built = true;
+       
+      stage = 2;
+      built = false;
+      estimator.addValues(train1, attrIndex1);
+      built = true;
+      
+      stage = 3;
+      if (!estimator.equals(estimator1)) {
+        if (m_Debug) {
+          println("\n=== Full report ===\n"
+		  + "\nFirst build estimator\n"+
+                  estimator.toString() + "\n\n");
+          println("\nSecond build estimator\n"+
+		  estimator.toString() + "\n\n");
+	}
+        throw new Exception("Results differ between buildEstimator calls");
+      }
+      println("yes");
+      result[0] = true;
+      
+      if (false && m_Debug) {
+        println("\n=== Full report ===\n"
+		+ "\nFirst buildEstimator()"
+                + "\n\n");
+        println("\nSecond buildEstimator()" 
+		+ "\n\n");
+      }
+    }
+    catch (Exception ex) {
+      String msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("worse than zeror") >= 0) {
+        println("warning: performs worse than ZeroR");
+        result[0] = true;
+        result[1] = true;
+      } else {
+        println("no");
+        result[0] = false;
+      }
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        switch (stage) {
+          case 0:
+            print(" of dataset 1");
+            break;
+          case 1:
+            print(" of dataset 2");
+            break;
+          case 2:
+            print(" of dataset 1 (2nd build)");
+            break;
+          case 3:
+            print(", comparing results from builds of dataset 1");
+            break;	  
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("here are the datasets:\n");
+        println("=== Train1 Dataset ===\n"
+            + train1.toString() + "\n");
+        println("=== Test1 Dataset ===\n"
+            + test1.toString() + "\n\n");
+        println("=== Train2 Dataset ===\n"
+            + train2.toString() + "\n");
+        println("=== Test2 Dataset ===\n"
+            + test2.toString() + "\n\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks basic missing value handling of the scheme. If the missing
+   * values cause an exception to be thrown by the scheme, this will be
+   * recorded.
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param attributeMissing true if the missing values may be in 
+   * the attributes
+   * @param classMissing true if the missing values may be in the class
+   * @param missingLevel the percentage of missing values
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] canHandleMissing(AttrTypes attrTypes,
+				       int classType,
+				       boolean attributeMissing,
+				       boolean classMissing,
+				       int missingLevel) {
+    
+    if (missingLevel == 100)
+      print("100% ");
+    print("missing");
+    if (attributeMissing) {
+      print(" attribute");
+      if (classMissing)
+        print(" and");
+    }
+    if (classMissing)
+      print(" class");
+    print(" values");
+    printAttributeSummary(attrTypes, classType);
+
+    print("...");
+    FastVector accepts = new FastVector();
+    accepts.addElement("missing");
+    accepts.addElement("value");
+    accepts.addElement("train");
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2;
+    
+    int numAtts = 1, attrIndex = 0;
+    return runBasicTest(attrTypes,
+			numAtts, attrIndex,
+			classType, 
+			missingLevel, attributeMissing, classMissing,
+			numTrain, numTest, numClasses, 
+			accepts);
+  }
+  
+  /**
+   * Checks whether an incremental scheme produces the same model when
+   * trained incrementally as when batch trained. The model itself
+   * cannot be compared, so we compare the evaluation on test data
+   * for both models. It is possible to get a false positive on this
+   * test (likelihood depends on the estimator).
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] incrementingEquality(AttrTypes attrTypes,
+					   int classType) {
+    
+    print("incremental training produces the same results"
+        + " as batch training");
+    printAttributeSummary(attrTypes, classType);
+
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2, missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Estimator [] estimators = null;
+    boolean built = false;
+    int attrIndex = 0;
+    Vector test;
+    try {
+      train = makeTestDataset(42, numTrain, 1, attrTypes,
+                              numClasses, 
+                              classType
+                              );
+
+        // prepare training data set and test value list
+      test = makeTestValueList(24, numTest, train, attrIndex,
+			       attrTypes.getSetType());
+
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, attributeMissing, classMissing, attrIndex);
+      }
+      estimators = Estimator.makeCopies(getEstimator(), 2);
+      estimators[0].addValues(train, attrIndex);
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      for (int i = 0; i < train.numInstances(); i++) {
+        ((IncrementalEstimator)estimators[1]).addValue(train.instance(i).value(attrIndex), 1.0);
+      }
+      built = true;
+      if (!estimators[0].equals(estimators[1])) {
+        println("no");
+        result[0] = false;
+       
+        if (m_Debug) {
+          println("\n=== Full Report ===");
+          println("Results differ between batch and "
+              + "incrementally built models.\n"
+              + "Depending on the estimator, this may be OK");
+          println("Here are the results:\n");
+          println("batch built results\n" + estimators[0].toString());
+          println("incrementally built results\n" + estimators[1].toString());
+          println("Here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+          println("=== Test Dataset ===\n"
+              + test.toString() + "\n\n");
+        }
+      }
+      else {
+        println("yes");
+        result[0] = true;
+      }
+    } catch (Exception ex) {
+      result[0] = false;
+      
+      print("Problem during");
+      if (built)
+        print(" testing");
+      else
+        print(" training");
+      println(": " + ex.getMessage() + "\n");
+    }
+    
+    return result;
+  }
+  
+  
+  /**
+   * Checks whether the estimator can handle instance weights.
+   * This test compares the estimator performance on two datasets
+   * that are identical except for the training weights. If the 
+   * results change, then the estimator must be using the weights. It
+   * may be possible to get a false positive from this test if the 
+   * weight changes aren't significant enough to induce a change
+   * in estimator performance (but the weights are chosen to minimize
+   * the likelihood of this).
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return index 0 true if the test was passed
+   */
+  protected boolean[] instanceWeights(AttrTypes attrTypes,
+				      int classType) {
+    
+    print("estimator uses instance weights");
+    printAttributeSummary(attrTypes, classType);
+
+    print("...");
+
+    int numTrain = 2 * getNumInstances(), numTest = getNumInstances(), 
+      numClasses = 2, missingLevel = 0;
+    boolean attributeMissing = false, classMissing = false;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Vector test = null;
+    Estimator [] estimators = null;
+    
+    Vector resultProbsO = null;
+    Vector resultProbsW = null;
+    boolean built = false;
+    boolean evalFail = false;
+    int attrIndex = 0;
+    try {
+      train = makeTestDataset(42, numTrain, 1, 
+                              attrTypes, numClasses, 
+                              classType);
+  
+      // prepare training data set and test value list
+      test = makeTestValueList(24, numTest, train, attrIndex,
+			       attrTypes.getSetType());
+
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, attributeMissing, classMissing, attrIndex);
+      }
+
+      estimators = Estimator.makeCopies(getEstimator(), 2);
+
+      estimators[0].addValues(train, attrIndex);
+      resultProbsO = testWithTestValues(estimators[0], test);
+
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+            
+      // Now modify instance weights and re-built
+      for (int i = 0; i < train.numInstances(); i++) {
+        train.instance(i).setWeight(0);
+      }
+      Random random = new Random(1);
+      for (int i = 0; i < train.numInstances() / 2; i++) {
+        int inst = Math.abs(random.nextInt()) % train.numInstances();
+        int weight = Math.abs(random.nextInt()) % 10 + 1;
+        train.instance(inst).setWeight(weight);
+      }
+      estimators[1].addValues(train, attrIndex);
+      resultProbsW = testWithTestValues(estimators[1], test);
+
+      built = true;
+      if (resultProbsO.equals(resultProbsW)) {
+        //	println("no");
+        evalFail = true;
+        throw new Exception("evalFail");
+      }
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        
+        if (evalFail) {
+          println("Results don't differ between non-weighted and "
+              + "weighted instance models.");
+          println("Here are the results:\n");
+          println(probsToString(resultProbsO));
+        } else {
+          print("Problem during");
+          if (built) {
+            print(" testing");
+          } else {
+            print(" training");
+          }
+          println(": " + ex.getMessage() + "\n");
+        }
+        println("Here are the datasets:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+        println("=== Train Weights ===\n");
+        for (int i = 0; i < train.numInstances(); i++) {
+          println(" " + (i + 1) 
+              + "    " + train.instance(i).weight());
+        }
+        println("=== Test Dataset ===\n"
+            + test.toString() + "\n\n");	
+        println("(test weights all 1.0\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether the scheme alters the training dataset during
+   * training. If the scheme needs to modify the training
+   * data it should take a copy of the training data. Currently checks
+   * for changes to header structure, number of instances, order of
+   * instances, instance weights.
+   *
+   * @param attrTypes attribute types that can be estimated
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param attributeMissing true if we know the estimator can handle
+   * (at least) moderate missing attribute values
+   * @param classMissing true if we know the estimator can handle
+   * (at least) moderate missing class values
+   * @return index 0 is true if the test was passed
+   */
+  protected boolean[] datasetIntegrity(AttrTypes attrTypes,
+				       int classType,
+				       boolean attributeMissing,
+				       boolean classMissing) {
+    
+    Estimator estimator = null;
+    print("estimator doesn't alter original datasets");
+    printAttributeSummary(attrTypes, classType);
+    print("...");
+    int numTrain = getNumInstances(), numTest = getNumInstances(), 
+    numClasses = 2, missingLevel = 100;
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+     boolean built = false;
+    try {
+      train = makeTestDataset(42, numTrain, 1, attrTypes,
+                              numClasses, 
+                              classType);
+      int attrIndex = 0;
+ 
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, attributeMissing, classMissing, attrIndex);
+      }
+      estimator = Estimator.makeCopies(getEstimator(), 1)[0];
+    } catch (Exception ex) {
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      Instances trainCopy = new Instances(train);
+      int attrIndex = 0;
+      estimator.addValues(trainCopy, attrIndex);
+      compareDatasets(train, trainCopy);
+      built = true;
+      
+      println("yes");
+      result[0] = true;
+    } catch (Exception ex) {
+      println("no");
+      result[0] = false;
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        println(": " + ex.getMessage() + "\n");
+        println("Here are the datasets:\n");
+        println("=== Train Dataset ===\n"
+            + train.toString() + "\n");
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param attrTypes attribute types that can be estimated
+   * @param numAtts number of attributes
+   * @param attrIndex attribute index 
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param missingLevel the percentage of missing values
+   * @param attributeMissing true if the missing values may be in 
+   * the attributes
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numTest the number of instaces in the test set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(AttrTypes attrTypes,
+				   int numAtts,
+				   int attrIndex,
+				   int classType,
+				   int missingLevel,
+				   boolean attributeMissing,
+				   boolean classMissing,
+				   int numTrain,
+				   int numTest,
+				   int numClasses,
+				   FastVector accepts) {
+    
+    return runBasicTest(attrTypes,
+			numAtts,
+			attrIndex,
+			classType, 
+			TestInstances.CLASS_IS_LAST,
+			missingLevel,
+			attributeMissing,
+			classMissing,
+			numTrain,
+			numTest,
+			numClasses,
+		accepts);
+  }
+  
+  /**
+   * Runs a text on the datasets with the given characteristics.
+   * 
+   * @param attrTypes attribute types that can be estimated
+   * @param numAtts number of attributes
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the attribute index of the class
+   * @param missingLevel the percentage of missing values
+   * @param attributeMissing true if the missing values may be in 
+   * the attributes
+   * @param classMissing true if the missing values may be in the class
+   * @param numTrain the number of instances in the training set
+   * @param numTest the number of instaces in the test set
+   * @param numClasses the number of classes
+   * @param accepts the acceptable string in an exception
+   * @return index 0 is true if the test was passed, index 1 is true if test 
+   *         was acceptable
+   */
+  protected boolean[] runBasicTest(AttrTypes attrTypes,
+				   int numAtts,
+				   int attrIndex,
+				   int classType,
+				   int classIndex,
+				   int missingLevel,
+				   boolean attributeMissing,
+				   boolean classMissing,
+				   int numTrain,
+				   int numTest,
+				   int numClasses,
+				   FastVector accepts) {
+    
+    boolean[] result = new boolean[2];
+    Instances train = null;
+    Vector test = null;
+    Estimator estimator = null;
+    boolean built = false;
+   
+    try {
+      train = makeTestDataset(42, numTrain, numAtts, attrTypes,
+          numClasses, 
+          classType,
+          classIndex);
+            
+      // prepare training data set and test value list
+      if (numTrain > 0) {
+        test = makeTestValueList(24, numTest, train, attrIndex,
+            attrTypes.getSetType());
+     
+      } else {
+        double min = -10.0;
+        double max = 8.0;
+        test = makeTestValueList(24, numTest, min, max,
+            attrTypes.getSetType());
+     }
+      
+      if (missingLevel > 0) {
+        addMissing(train, missingLevel, attributeMissing, classMissing, attrIndex);
+      }
+      estimator = Estimator.makeCopies(getEstimator(), 1)[0];
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      throw new Error("Error setting up for tests: " + ex.getMessage());
+    }
+    try {
+      estimator.addValues(train, attrIndex);
+      built = true;
+      
+      testWithTestValues(estimator, test);
+      
+      println("yes");
+      result[0] = true;
+    } 
+    catch (Exception ex) {
+      boolean acceptable = false;
+      String msg;
+      if (ex.getMessage() == null)
+        msg = "";
+      else
+        msg = ex.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+        m_ClasspathProblems = true;
+      
+      for (int i = 0; i < accepts.size(); i++) {
+        if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
+          acceptable = true;
+        }
+      }
+      
+      println("no" + (acceptable ? " (OK error message)" : ""));
+      result[1] = acceptable;
+      
+      
+      if (m_Debug) {
+        println("\n=== Full Report ===");
+        print("Problem during");
+        if (built) {
+          print(" testing");
+        } else {
+          print(" training");
+        }
+        println(": " + ex.getMessage() + "\n");
+        if (!acceptable) {
+          if (accepts.size() > 0) {
+            print("Error message doesn't mention ");
+            for (int i = 0; i < accepts.size(); i++) {
+              if (i != 0) {
+                print(" or ");
+              }
+              print('"' + (String)accepts.elementAt(i) + '"');
+            }
+          }
+          println("here are the datasets:\n");
+          println("=== Train Dataset ===\n"
+              + train.toString() + "\n");
+          println("=== Test Dataset ===\n"
+              + test.toString() + "\n\n");
+        }
+        
+      }
+    }
+    return result;
+  }
+  
+  /**
+   * Compare two datasets to see if they differ.
+   *
+   * @param data1 one set of instances
+   * @param data2 the other set of instances
+   * @throws Exception if the datasets differ
+   */
+  protected void compareDatasets(Instances data1, Instances data2)
+  throws Exception {
+    if (!data2.equalHeaders(data1)) {
+      throw new Exception("header has been modified\n" + data2.equalHeadersMsg(data1));
+    }
+    if (!(data2.numInstances() == data1.numInstances())) {
+      throw new Exception("number of instances has changed");
+    }
+    for (int i = 0; i < data2.numInstances(); i++) {
+      Instance orig = data1.instance(i);
+      Instance copy = data2.instance(i);
+      for (int j = 0; j < orig.numAttributes(); j++) {
+        if (orig.isMissing(j)) {
+          if (!copy.isMissing(j)) {
+            throw new Exception("instances have changed");
+          }
+        } else if (orig.value(j) != copy.value(j)) {
+          throw new Exception("instances have changed");
+        }
+        if (orig.weight() != copy.weight()) {
+          throw new Exception("instance weights have changed");
+        }	  
+      }
+    }
+  }
+  
+  /**
+   * Add missing values to a dataset.
+   *
+   * @param data the instances to add missing values to
+   * @param level the level of missing values to add (if positive, this
+   * is the probability that a value will be set to missing, if negative
+   * all but one value will be set to missing (not yet implemented))
+   * @param attributeMissing if true, attributes will be modified
+   * @param classMissing if true, the class attribute will be modified
+   * @param attrIndex index of the attribute
+   */
+  protected void addMissing(Instances data, int level,
+			    boolean attributeMissing, boolean classMissing,
+			    int attrIndex) {
+    
+    int classIndex = data.classIndex();
+    Random random = new Random(1);
+    for (int i = 0; i < data.numInstances(); i++) {
+      Instance current = data.instance(i);
+
+      for (int j = 0; j < data.numAttributes(); j++) {
+        if (((j == classIndex) && classMissing) ||
+            ((j == attrIndex) && attributeMissing)) {
+          if (Math.abs(random.nextInt()) % 100 < level)
+            current.setMissing(j);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Make a simple set of instances, which can later be modified
+   * for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numAttr the number of attributes
+   * @param attrTypes the attribute types
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, 
+				      int numInstances, 
+				      int numAttr,
+				      AttrTypes attrTypes,
+				      int numClasses, 
+				      int classType)
+    throws Exception {
+    
+    return makeTestDataset(
+			   seed,
+			   numInstances,
+			   numAttr,
+			   attrTypes,
+			   numClasses, 
+			   classType,
+			   TestInstances.CLASS_IS_LAST);
+  }
+
+
+  /**
+   * Make a simple set of instances with variable position of the class 
+   * attribute, which can later be modified for use in specific tests.
+   *
+   * @param seed the random number seed
+   * @param numInstances the number of instances to generate
+   * @param numAttr the number of attributes to generate
+   * @param attrTypes the type of attrbute that is excepted
+   * @param numClasses the number of classes (if nominal class)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   * @param classIndex the index of the class (0-based, -1 as last)
+   * @return the test dataset
+   * @throws Exception if the dataset couldn't be generated
+   * @see TestInstances#CLASS_IS_LAST
+   * @see #process(Instances)
+   */
+  protected Instances makeTestDataset(int seed, int numInstances, 
+				      int numAttr, AttrTypes attrTypes,
+				      int numClasses, int classType,
+				      int classIndex)
+    throws Exception {
+    
+    TestInstances dataset = new TestInstances();
+    
+    dataset.setSeed(seed);
+    dataset.setNumInstances(numInstances);
+    dataset.setNumNominal   (attrTypes.nominal     ? numAttr : 0);
+    dataset.setNumNumeric   (attrTypes.numeric     ? numAttr : 0);
+    dataset.setNumString    (attrTypes.string      ? numAttr : 0);
+    dataset.setNumDate      (attrTypes.date        ? numAttr : 0);
+    dataset.setNumRelational(attrTypes.relational  ? numAttr : 0);
+    dataset.setNumClasses(numClasses);
+    dataset.setClassType(classType);
+    dataset.setClassIndex(classIndex);
+    
+    return process(dataset.generate());
+  }
+
+  /**
+   * Make a simple set of values. Only one of the num'type' parameters should be larger 0.
+   * (just to make parameter similar to the makeTestDataset parameters)
+   *
+   * @param seed the random number seed
+   * @param numValues the number of values to generate
+   * @param data the dataset to make test examples for
+   * @param attrIndex index of the attribute
+   * @param attrType the class type (NUMERIC, NOMINAL, etc.)
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Vector makeTestValueList(int seed, int numValues, 
+      Instances data, int attrIndex, int attrType)
+  throws Exception {
+    
+    // get min max
+    double []minMax = getMinimumMaximum(data, attrIndex);
+    double minValue = minMax[0];
+    double maxValue = minMax[1];
+    
+    // make value list and put into a VECTOR
+    double range = maxValue - minValue; 
+    Vector values = new Vector(numValues); 
+    Random random = new Random(seed);
+    
+    if (attrType == Attribute.NOMINAL) {
+      for (int i = 0; i < numValues; i++) {
+        Double v = new Double((Math.abs(random.nextInt()) % (int)range)+ (int)minValue);
+        values.add(v);
+      }
+    }
+    if (attrType == Attribute.NUMERIC) {
+      for (int i = 0; i < numValues; i++) {
+        Double v = new Double(random.nextDouble() * range + minValue);
+        values.add(v);
+      }
+    }
+    return values;
+  }
+
+  /**
+   * Make a simple set of values. Only one of the num'type' parameters should be larger 0.
+   * (just to make parameter similar to the makeTestDataset parameters)
+   *
+   * @param seed the random number seed
+   * @param numValues the number of values to generate
+   * @param minValue the minimal data value
+   * @param maxValue the maximal data value
+   * @param attrType the class type (NUMERIC, NOMINAL, etc.)
+   * @throws Exception if the dataset couldn't be generated
+   * @see #process(Instances)
+   */
+  protected Vector makeTestValueList(int seed, int numValues, 
+      double minValue, double maxValue, int attrType)
+  throws Exception {
+    
+      
+    // make value list and put into a VECTOR
+    double range = maxValue - minValue; 
+    Vector values = new Vector(numValues); 
+    Random random = new Random(seed);
+    
+    if (attrType == Attribute.NOMINAL) {
+      for (int i = 0; i < numValues; i++) {
+        Double v = new Double((Math.abs(random.nextInt()) % (int)range)+ (int)minValue);
+        values.add(v);
+      }
+    }
+    if (attrType == Attribute.NUMERIC) {
+      for (int i = 0; i < numValues; i++) {
+        Double v = new Double(random.nextDouble() * range + minValue);
+        values.add(v);
+      }
+    }
+    return values;
+  }
+
+  /**
+   * Test with test values.
+   *
+   * @param est estimator to be tested
+   * @param test vector with test values
+   *
+   **/
+  protected Vector testWithTestValues(Estimator est, Vector test) {
+    
+    Vector results = new Vector();
+    for (int i = 0; i < test.size(); i++) {
+      double testValue = ((Double)(test.elementAt(i))).doubleValue();
+      double prob = est.getProbability(testValue);
+      Double p = new Double(prob);
+      results.add(p);
+    }
+    return results;
+  }
+
+  /**
+   * Gets the minimum and maximum of the values a the first attribute
+   * of the given data set
+   *
+   * @param inst the instance
+   * @param attrIndex the index of the attribut to find min and max
+   * @return the array with the minimum value on index 0 and the max on index 1
+   */
+  
+  protected double[] getMinimumMaximum(Instances inst, int attrIndex) {
+    double []minMax = new double[2];
+    
+    try {
+      int num = getMinMax(inst, attrIndex, minMax);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+    return minMax;
+    //      double minValue = minMax[0];
+    //      double maxValue = minMax[1];
+  }
+  
+  /** 
+   * Find the minimum and the maximum of the attribute and return it in 
+   * the last parameter..
+   * @param inst instances used to build the estimator
+   * @param attrIndex index of the attribute
+   * @param minMax the array to return minimum and maximum in
+   * @return number of not missing values
+   * @exception Exception if parameter minMax wasn't initialized properly
+   */
+  public static int getMinMax(Instances inst, int attrIndex, double [] minMax) 
+    throws Exception {
+    double min = Double.NaN;
+    double max = Double.NaN;
+    Instance instance = null;
+    int numNotMissing = 0;
+    if ((minMax == null) || (minMax.length < 2)) {
+      throw new Exception("Error in Program, privat method getMinMax");
+    }
+    
+    Enumeration enumInst = inst.enumerateInstances();
+    if (enumInst.hasMoreElements()) {
+      do {
+	instance = (Instance) enumInst.nextElement();
+      } while (instance.isMissing(attrIndex) && (enumInst.hasMoreElements()));
+      
+      // add values if not  missing
+      if (!instance.isMissing(attrIndex)) {
+	numNotMissing++;
+	min = instance.value(attrIndex);
+	max = instance.value(attrIndex);
+      }
+      while (enumInst.hasMoreElements()) {
+	instance = (Instance) enumInst.nextElement();
+	if (!instance.isMissing(attrIndex)) {
+	  numNotMissing++;
+	  if (instance.value(attrIndex) < min) {
+	    min = (instance.value(attrIndex));
+	  } else {
+	    if (instance.value(attrIndex) > max) {	      
+	      max = (instance.value(attrIndex));
+	    }
+	  }
+	}
+      }
+    }
+    minMax[0] = min;
+    minMax[1] = max;
+    return numNotMissing;
+  }
+
+  /**
+   * Print the probabilities after testing
+   * @param probs vector with probability values
+   * @return string with probability values printed
+   */ 
+  private String probsToString(Vector probs) {
+    StringBuffer txt = new StringBuffer (" ");
+    for (int i = 0; i < probs.size(); i++) {
+      txt.append("" + ((Double)(probs.elementAt(i))).doubleValue() + " ");
+    }
+    return txt.toString();
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. 
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   * @see #m_PostProcessor
+   */
+  protected Instances process(Instances data) {
+    if (getPostProcessor() == null)
+      return data;
+    else
+      return getPostProcessor().process(data);
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param attrTypes the attribute types used (NUMERIC, NOMINAL, etc.)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(AttrTypes attrTypes, int classType) {
+    
+    String str = "";
+    
+    if (attrTypes.numeric)
+      str += " numeric";
+    
+    if (attrTypes.nominal) {
+      if (str.length() > 0)
+        str += " &";
+      str += " nominal";
+    }
+    
+    if (attrTypes.string) {
+      if (str.length() > 0)
+        str += " &";
+      str += " string";
+    }
+    
+    if (attrTypes.date) {
+      if (str.length() > 0)
+        str += " &";
+      str += " date";
+    }
+    
+    if (attrTypes.relational) {
+      if (str.length() > 0)
+        str += " &";
+      str += " relational";
+    }
+    
+    str += " attributes)";
+    
+    switch (classType) {
+      case Attribute.NUMERIC:
+        str = " (numeric class," + str;
+        break;
+      case Attribute.NOMINAL:
+        str = " (nominal class," + str;
+        break;
+      case Attribute.STRING:
+        str = " (string class," + str;
+        break;
+      case Attribute.DATE:
+        str = " (date class," + str;
+        break;
+      case Attribute.RELATIONAL:
+        str = " (relational class," + str;
+        break;
+    }
+    
+    print(str);
+  }
+  
+  /**
+   * Print out a short summary string for the dataset characteristics
+   *
+   * @param attrType the attribute type (NUMERIC, NOMINAL, etc.)
+   * @param classType the class type (NUMERIC, NOMINAL, etc.)
+   */
+  protected void printAttributeSummary(int attrType, int classType) {
+    
+    String str = "";
+    
+    switch (attrType) {
+    case Attribute.NUMERIC:
+      str = " numeric" + str;
+      break;
+    case Attribute.NOMINAL:
+      str = " nominal" + str;
+      break;
+    case Attribute.STRING:
+      str = " string" + str;
+      break;
+    case Attribute.DATE:
+      str = " date" + str;
+      break;
+    case Attribute.RELATIONAL:
+      str = " relational" + str;
+      break;
+    }
+    str += " attribute(s))";
+    
+    switch (classType) {
+    case Attribute.NUMERIC:
+      str = " (numeric class," + str;
+      break;
+    case Attribute.NOMINAL:
+      str = " (nominal class," + str;
+      break;
+    case Attribute.STRING:
+      str = " (string class," + str;
+      break;
+    case Attribute.DATE:
+      str = " (date class," + str;
+      break;
+    case Attribute.RELATIONAL:
+      str = " (relational class," + str;
+      break;
+    }
+    
+    print(str);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 4997 $");
+  }
+
+  /**
+   * Test method for this class
+   * 
+   * @param args the commandline parameters
+   */
+  public static void main(String [] args) {
+    try {
+      CheckEstimator check = new CheckEstimator();
+      
+      try {
+        check.setOptions(args);
+        Utils.checkForRemainingOptions(args);
+      } catch (Exception ex) {
+        String result = ex.getMessage() + "\n\n" + check.getClass().getName().replaceAll(".*\\.", "") + " Options:\n\n";
+        Enumeration enu = check.listOptions();
+        while (enu.hasMoreElements()) {
+          Option option = (Option) enu.nextElement();
+          result += option.synopsis() + "\n" + option.description() + "\n";
+        }
+        throw new Exception(result);
+      }
+      
+      check.doTests();
+    } catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/estimators/ConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/ConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/ConditionalEstimator.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionHandler;
+
+ 
+/** 
+ * Interface for conditional probability estimators. Example code: <p>
+ *
+ * <code> <pre>
+ *   NNConditionalEstimator newEst = new NNConditionalEstimator();
+ *
+ *   // Create 50 random points and add them
+ *   Random r = new Random(seed);
+ *   for(int i = 0; i < 50; i++) {
+ *     int x = Math.abs(r.nextInt() % 100);
+ *     int y = Math.abs(r.nextInt() % 100);
+ *     System.out.println("# " + x + "  " + y);
+ *     newEst.addValue(x, y, 1);
+ *   }
+ *
+ *   // Pick a random conditional value
+ *   int cond = Math.abs(r.nextInt() % 100);
+ *   System.out.println("## Conditional = " + cond);
+ *
+ *   // Print the probabilities conditional on that value
+ *   Estimator result = newEst.getEstimator(cond);
+ *   for(int i = 0; i <= 100; i+= 5) {
+ *     System.out.println(" " + i + "  " + result.getProbability(i));
+ *   }
+ * </pre> </code>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public interface ConditionalEstimator extends RevisionHandler {
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  void addValue(double data, double given, double weight);
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  Estimator getEstimator(double given);
+
+  /**
+   * Get a probability for a value conditional on another value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  double getProbability(double data, double given);
+
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/estimators/DDConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/DDConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/DDConditionalEstimator.java	(revision 29)
@@ -0,0 +1,154 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DDConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionUtils;
+
+ 
+/** 
+ * Conditional probability estimator for a discrete domain conditional upon
+ * a discrete domain.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class DDConditionalEstimator implements ConditionalEstimator {
+
+  /** Hold the sub-estimators */
+  private DiscreteEstimator [] m_Estimators;
+
+  /**
+   * Constructor
+   *
+   * @param numSymbols the number of possible symbols (remember to include 0)
+   * @param numCondSymbols the number of conditioning symbols 
+   * @param laplace if true, sub-estimators will use laplace
+   */
+  public DDConditionalEstimator(int numSymbols, int numCondSymbols,
+				boolean laplace) {
+    
+    m_Estimators = new DiscreteEstimator [numCondSymbols];
+    for(int i = 0; i < numCondSymbols; i++) {
+      m_Estimators[i] = new DiscreteEstimator(numSymbols, laplace);
+    }
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+    
+    m_Estimators[(int)given].addValue(data, weight);
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+    
+    return m_Estimators[(int)given];
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+    
+    return getEstimator(given).getProbability(data);
+  }
+
+  /** Display a representation of this estimator */
+  public String toString() {
+    
+    String result = "DD Conditional Estimator. " 
+      + m_Estimators.length + " sub-estimators:\n";
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result += "Sub-estimator " + i + ": " + m_Estimators[i];
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of pairs of integers which
+   * will be treated as symbolic.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      int currentA = Integer.parseInt(argv[0]);
+      int maxA = currentA;
+      int currentB = Integer.parseInt(argv[1]);
+      int maxB = currentB;
+      for(int i = 2; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	if (currentA > maxA) {
+	  maxA = currentA;
+	}
+	if (currentB > maxB) {
+	  maxB = currentB;
+	}
+      }
+      DDConditionalEstimator newEst = new DDConditionalEstimator(maxA + 1,
+								 maxB + 1,
+								 true);
+      for(int i = 0; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	System.out.println(newEst);
+	System.out.println("Prediction for " + currentA + '|' + currentB 
+			   + " = "
+			   + newEst.getProbability(currentA, currentB));
+	newEst.addValue(currentA, currentB, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/DKConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/DKConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/DKConditionalEstimator.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DKConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionUtils;
+
+/** 
+ * Conditional probability estimator for a discrete domain conditional upon
+ * a numeric domain.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class DKConditionalEstimator implements ConditionalEstimator {
+
+  /** Hold the sub-estimators */
+  private KernelEstimator [] m_Estimators;
+
+  /** Hold the weights for each of the sub-estimators */
+  private DiscreteEstimator m_Weights;
+
+  /**
+   * Constructor
+   *
+   * @param numSymbols the number of symbols 
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public DKConditionalEstimator(int numSymbols, double precision) {
+
+    m_Estimators = new KernelEstimator [numSymbols];
+    for(int i = 0; i < numSymbols; i++) {
+      m_Estimators[i] = new KernelEstimator(precision);
+    }
+    m_Weights = new DiscreteEstimator(numSymbols, true);
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+
+    m_Estimators[(int)data].addValue(given, weight);
+    m_Weights.addValue((int)data, weight);
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+
+    Estimator result = new DiscreteEstimator(m_Estimators.length,false);
+    for(int i = 0; i < m_Estimators.length; i++) {
+      //System.out.println("Val " + i
+      //			 + " Weight:" + m_Weights.getProbability(i)
+      //			 +" EstProb(" + given + ")="
+      //			 + m_Estimators[i].getProbability(given));
+      result.addValue(i, m_Weights.getProbability(i)
+		      * m_Estimators[i].getProbability(given));
+    }
+    return result;
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+
+    return getEstimator(given).getProbability(data);
+  }
+
+  /**
+   * Display a representation of this estimator
+   */
+  public String toString() {
+
+    String result = "DK Conditional Estimator. " 
+      + m_Estimators.length + " sub-estimators:\n";
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result += "Sub-estimator " + i + ": " + m_Estimators[i];
+    }
+    result += "Weights of each estimator given by " + m_Weights;
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of pairs of integers which
+   * will be treated as pairs of symbolic, numeric.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      int currentA = Integer.parseInt(argv[0]);
+      int maxA = currentA;
+      int currentB = Integer.parseInt(argv[1]);
+      int maxB = currentB;
+      for(int i = 2; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	if (currentA > maxA) {
+	  maxA = currentA;
+	}
+	if (currentB > maxB) {
+	  maxB = currentB;
+	}
+      }
+      DKConditionalEstimator newEst = new DKConditionalEstimator(maxA + 1,
+								 1);
+      for(int i = 0; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	System.out.println(newEst);
+	System.out.println("Prediction for " + currentA + '|' + currentB 
+			   + " = "
+			   + newEst.getProbability(currentA, currentB));
+	newEst.addValue(currentA, currentB, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/DNConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/DNConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/DNConditionalEstimator.java	(revision 29)
@@ -0,0 +1,163 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DNConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionUtils;
+
+/** 
+ * Conditional probability estimator for a discrete domain conditional upon
+ * a numeric domain.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class DNConditionalEstimator implements ConditionalEstimator {
+
+  /** Hold the sub-estimators */
+  private NormalEstimator [] m_Estimators;
+
+  /** Hold the weights for each of the sub-estimators */
+  private DiscreteEstimator m_Weights;
+
+  /**
+   * Constructor
+   *
+   * @param numSymbols the number of symbols 
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public DNConditionalEstimator(int numSymbols, double precision) {
+
+    m_Estimators = new NormalEstimator [numSymbols];
+    for(int i = 0; i < numSymbols; i++) {
+	m_Estimators[i] = new NormalEstimator(precision);
+    }
+    m_Weights = new DiscreteEstimator(numSymbols, true);
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+
+    m_Estimators[(int)data].addValue(given, weight);
+    m_Weights.addValue((int)data, weight);
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+
+    Estimator result = new DiscreteEstimator(m_Estimators.length,false);
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result.addValue(i,m_Weights.getProbability(i)
+		      *m_Estimators[i].getProbability(given));
+    }
+    return result;
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+
+    return getEstimator(given).getProbability(data);
+  }
+
+  /** Display a representation of this estimator */
+  public String toString() {
+
+    String result = "DN Conditional Estimator. " 
+      + m_Estimators.length + " sub-estimators:\n";
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result += "Sub-estimator " + i + ": " + m_Estimators[i];
+    }
+    result += "Weights of each estimator given by " + m_Weights;
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of pairs of integers which
+   * will be treated as pairs of symbolic, numeric.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      int currentA = Integer.parseInt(argv[0]);
+      int maxA = currentA;
+      int currentB = Integer.parseInt(argv[1]);
+      int maxB = currentB;
+      for(int i = 2; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	if (currentA > maxA) {
+	  maxA = currentA;
+	}
+	if (currentB > maxB) {
+	  maxB = currentB;
+	}
+      }
+      DNConditionalEstimator newEst = new DNConditionalEstimator(maxA + 1,
+								 1);
+      for(int i = 0; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	System.out.println(newEst);
+	System.out.println("Prediction for " + currentA + '|' + currentB 
+			   + " = "
+			   + newEst.getProbability(currentA, currentB));
+	newEst.addValue(currentA, currentB, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/DiscreteEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/DiscreteEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/DiscreteEstimator.java	(revision 29)
@@ -0,0 +1,229 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DiscreteEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/** 
+ * Simple symbolic probability estimator based on symbol counts.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5490 $
+ */
+public class DiscreteEstimator extends Estimator implements IncrementalEstimator {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -5526486742612434779L;
+
+  /** Hold the counts */
+  private double [] m_Counts;
+  
+  /** Hold the sum of counts */
+  private double m_SumOfCounts;
+  
+  
+  /**
+   * Constructor
+   *
+   * @param numSymbols the number of possible symbols (remember to include 0)
+   * @param laplace if true, counts will be initialised to 1
+   */
+  public DiscreteEstimator(int numSymbols, boolean laplace) {
+    
+    m_Counts = new double [numSymbols];
+    m_SumOfCounts = 0;
+    if (laplace) {
+      for(int i = 0; i < numSymbols; i++) {
+        m_Counts[i] = 1;
+      }
+      m_SumOfCounts = (double)numSymbols;
+    }
+  }
+  
+  /**
+   * Constructor
+   *
+   * @param nSymbols the number of possible symbols (remember to include 0)
+   * @param fPrior value with which counts will be initialised
+   */
+  public DiscreteEstimator(int nSymbols, double fPrior) {    
+    
+    m_Counts = new double [nSymbols];
+    for(int iSymbol = 0; iSymbol < nSymbols; iSymbol++) {
+      m_Counts[iSymbol] = fPrior;
+    }
+    m_SumOfCounts = fPrior * (double) nSymbols;
+  }
+  
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+    
+    m_Counts[(int)data] += weight;
+    m_SumOfCounts += weight;
+  }
+  
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+    
+    if (m_SumOfCounts == 0) {
+      return 0;
+    }
+    return (double)m_Counts[(int)data] / m_SumOfCounts;
+  }
+  
+  /**
+   * Gets the number of symbols this estimator operates with
+   *
+   * @return the number of estimator symbols
+   */
+  public int getNumSymbols() {
+    
+    return (m_Counts == null) ? 0 : m_Counts.length;
+  }
+  
+  
+  /**
+   * Get the count for a value
+   *
+   * @param data the value to get the count of
+   * @return the count of the supplied value
+   */
+  public double getCount(double data) {
+    
+    if (m_SumOfCounts == 0) {
+      return 0;
+    }
+    return m_Counts[(int)data];
+  }
+  
+  
+  /**
+   * Get the sum of all the counts
+   *
+   * @return the total sum of counts
+   */
+  public double getSumOfCounts() {
+    
+    return m_SumOfCounts;
+  }
+  
+  
+  /**
+   * Display a representation of this estimator
+   */
+  public String toString() {
+    
+    StringBuffer result = new StringBuffer("Discrete Estimator. Counts = ");
+    if (m_SumOfCounts > 1) {
+      for(int i = 0; i < m_Counts.length; i++) {
+        result.append(" ").append(Utils.doubleToString(m_Counts[i], 2));
+      }
+      result.append("  (Total = " ).append(Utils.doubleToString(m_SumOfCounts, 2));
+      result.append(")\n"); 
+    } else {
+      for(int i = 0; i < m_Counts.length; i++) {
+        result.append(" ").append(m_Counts[i]);
+      }
+      result.append("  (Total = ").append(m_SumOfCounts).append(")\n"); 
+    }
+    return result.toString();
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5490 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of integers which
+   * will be treated as symbolic.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+        System.out.println("Please specify a set of instances.");
+        return;
+      }
+      int current = Integer.parseInt(argv[0]);
+      int max = current;
+      for(int i = 1; i < argv.length; i++) {
+        current = Integer.parseInt(argv[i]);
+        if (current > max) {
+          max = current;
+        }
+      }
+      DiscreteEstimator newEst = new DiscreteEstimator(max + 1, true);
+      for(int i = 0; i < argv.length; i++) {
+        current = Integer.parseInt(argv[i]);
+        System.out.println(newEst);
+        System.out.println("Prediction for " + current 
+            + " = " + newEst.getProbability(current));
+        newEst.addValue(current, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/Estimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/Estimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/Estimator.java	(revision 29)
@@ -0,0 +1,720 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Estimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+ 
+/** 
+ *
+ * Abstract class for all estimators.
+ *
+ * Example code for a nonincremental estimator
+ * <code> <pre>
+ *   // create a histogram for estimation
+ *   EqualWidthEstimator est = new EqualWidthEstimator();
+ *   est.addValues(instances, attrIndex);
+ * </pre> </code>
+ *
+ *
+ * Example code for an incremental estimator (incremental
+ * estimators must implement interface IncrementalEstimator)
+ * <code> <pre>
+ *   // Create a discrete estimator that takes values 0 to 9
+ *   DiscreteEstimator newEst = new DiscreteEstimator(10, true);
+ *
+ *   // Create 50 random integers first predicting the probability of the
+ *   // value, then adding the value to the estimator
+ *   Random r = new Random(seed);
+ *   for(int i = 0; i < 50; i++) {
+ *     current = Math.abs(r.nextInt() % 10);
+ *     System.out.println(newEst);
+ *     System.out.println("Prediction for " + current 
+ *                        + " = " + newEst.getProbability(current));
+ *     newEst.addValue(current, 1);
+ *   }
+ * </pre> </code>
+ *
+ *
+ * Example code for a main method for an estimator.<p>
+ * <code> <pre>
+ * public static void main(String [] argv) {
+ *
+ *   try {
+ *     LoglikeliEstimator est = new LoglikeliEstimator();      
+ *     Estimator.buildEstimator((Estimator) est, argv, false);      
+ *     System.out.println(est.toString());
+ *   } catch (Exception ex) {
+ *     ex.printStackTrace();
+ *     System.out.println(ex.getMessage());
+ *   }
+ * }
+ * </pre> </code>
+ *
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5489 $
+ */
+public abstract class Estimator 
+  implements Cloneable, Serializable, OptionHandler, CapabilitiesHandler, 
+             RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5902411487362274342L;
+  
+  /** Debugging mode */
+  private boolean m_Debug = false;
+  
+  /** The class value index is > -1 if subset is taken with specific class value only*/
+  protected double m_classValueIndex = -1.0;
+  
+  /** set if class is not important */
+  protected boolean m_noClass = true;
+  
+  /**
+   * Class to support a building process of an estimator.
+   */
+  private static class Builder
+    implements Serializable, RevisionHandler {
+
+    /** for serialization */
+    private static final long serialVersionUID = -5810927990193597303L;
+    
+    /** instances of the builder */
+    Instances m_instances = null;
+    
+    /** attribute index of the builder */
+    int m_attrIndex = -1;
+    
+    /** class index of the builder, only relevant if class value index is set*/
+    int m_classIndex = -1; 
+
+    /** class value index of the builder */
+    int m_classValueIndex = -1; 
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5489 $");
+    }
+  }
+  
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+    try {  
+      throw new Exception("Method to add single value is not implemented!\n"+
+			  "Estimator should implement IncrementalEstimator.");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+  }
+
+  /**
+   * Initialize the estimator with a new dataset.
+   * Finds min and max first.
+   *
+   * @param data the dataset used to build this estimator 
+   * @param attrIndex attribute the estimator is for
+   * @exception Exception if building of estimator goes wrong
+   */
+  public void addValues(Instances data, int attrIndex) throws Exception {
+    // can estimator handle the data?
+    getCapabilities().testWithFail(data);
+   
+    double []minMax = new double[2];
+    
+    try {
+      EstimatorUtils.getMinMax(data, attrIndex, minMax);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+    
+    double min = minMax[0];
+    double max = minMax[1];
+
+    // factor is 1.0, data set has not been reduced
+    addValues(data, attrIndex, min, max, 1.0);
+  }
+  
+  /**
+   * Initialize the estimator with all values of one attribute of a dataset. 
+   * Some estimator might ignore the min and max values.
+   *
+   * @param data the dataset used to build this estimator 
+   * @param attrIndex attribute the estimator is for
+   * @param min minimal border of range
+   * @param max maximal border of range
+   * @param factor number of instances has been reduced to that factor
+   * @exception Exception if building of estimator goes wrong
+   */
+  public void addValues(Instances data, int attrIndex,
+			double min, double max, double factor) throws Exception {
+    // no handling of factor, would have to be overridden
+
+    // no handling of min and max, would have to be overridden
+
+    int numInst = data.numInstances();
+    for (int i = 1; i < numInst; i++) {
+      addValue(data.instance(i).value(attrIndex), 1.0);
+    }
+  }
+ 
+  /**
+   * Initialize the estimator using only the instance of one class. 
+   * It is using the values of one attribute only.
+   *
+   * @param data the dataset used to build this estimator 
+   * @param attrIndex attribute the estimator is for
+   * @param classIndex index of the class attribute
+   * @param classValue the class value 
+   * @exception Exception if building of estimator goes wrong
+   */
+  public void addValues(Instances data, int attrIndex,
+			int classIndex, int classValue) throws Exception{
+    // can estimator handle the data?
+    m_noClass = false;    
+    getCapabilities().testWithFail(data);
+    
+    // find the minimal and the maximal value
+    double []minMax = new double[2];
+    
+    try {
+      EstimatorUtils.getMinMax(data, attrIndex, minMax);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+    
+    double min = minMax[0];
+    double max = minMax[1];
+ 
+    // extract the instances with the given class value
+    Instances workData = new Instances(data, 0);
+    double factor = getInstancesFromClass(data, attrIndex,
+					  classIndex, 
+					  (double)classValue, workData);
+
+    // if no data return
+    if (workData.numInstances() == 0) return;
+
+    addValues(data, attrIndex, min, max, factor);
+  }
+  
+  /**
+   * Initialize the estimator using only the instance of one class. 
+   * It is using the values of one attribute only.
+   *
+   * @param data the dataset used to build this estimator 
+   * @param attrIndex attribute the estimator is for
+   * @param classIndex index of the class attribute
+   * @param classValue the class value 
+   * @param min minimal value of this attribute
+   * @param max maximal value of this attribute
+   * @exception Exception if building of estimator goes wrong
+   */
+  public void addValues(Instances data, int attrIndex,
+      int classIndex, int classValue,
+      double min, double max) throws Exception{
+     
+    // extract the instances with the given class value
+    Instances workData = new Instances(data, 0);
+    double factor = getInstancesFromClass(data, attrIndex,
+            classIndex, 
+            (double)classValue, workData);
+
+    // if no data return
+    if (workData.numInstances() == 0) return;
+
+    addValues(data, attrIndex, min, max, factor);
+  }
+  
+ 
+  /**
+   * Returns a dataset that contains all instances of a certain class value.
+   *
+   * @param data dataset to select the instances from
+   * @param attrIndex index of the relevant attribute
+   * @param classIndex index of the class attribute
+   * @param classValue the relevant class value 
+   * @return a dataset with only 
+   */
+  private double getInstancesFromClass(Instances data, int attrIndex,
+				       int classIndex,
+				       double classValue, Instances workData) {
+    //DBO.pln("getInstancesFromClass classValue"+classValue+" workData"+data.numInstances());
+
+    int num = 0;
+    int numClassValue = 0;
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (!data.instance(i).isMissing(attrIndex)) {
+	num++;
+	if (data.instance(i).value(classIndex) == classValue) {
+	  workData.add(data.instance(i));
+	  numClassValue++;
+	}
+      }
+    } 
+
+    Double alphaFactor = new Double((double)numClassValue/(double)num);
+    return alphaFactor;
+  }
+
+  /**
+   * Get a probability estimate for a value.
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public abstract double getProbability(double data);
+
+  /**
+   * Build an estimator using the options. The data is given in the options.
+   *
+   * @param est the estimator used
+   * @param options the list of options
+   * @param isIncremental true if estimator is incremental
+   * @exception Exception if something goes wrong or the user requests help on
+   * command options
+   */
+  public static void buildEstimator(Estimator est, String [] options,
+				    boolean isIncremental) 
+    throws Exception {
+    //DBO.pln("buildEstimator");
+    
+    boolean debug = false;
+    boolean helpRequest;
+    
+    // read all options
+    Builder build = new Builder();
+    try {
+      setGeneralOptions(build, est, options);
+      
+      if (est instanceof OptionHandler) {
+	((OptionHandler)est).setOptions(options);
+      }
+      
+      Utils.checkForRemainingOptions(options);
+      
+    
+      buildEstimator(est, build.m_instances, build.m_attrIndex,
+		     build.m_classIndex, build.m_classValueIndex, isIncremental);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+      String specificOptions = "";
+      // Output the error and also the valid options
+      if (est instanceof OptionHandler) {
+	specificOptions += "\nEstimator options:\n\n";
+	Enumeration enumOptions = ((OptionHandler)est).listOptions();
+	while (enumOptions.hasMoreElements()) {
+	  Option option = (Option) enumOptions.nextElement();
+	  specificOptions += option.synopsis() + '\n'
+	    + option.description() + "\n";
+	}
+      }
+      
+      String genericOptions = "\nGeneral options:\n\n"
+	+ "-h\n"
+	+ "\tGet help on available options.\n"
+	+ "-i <file>\n"
+	+ "\tThe name of the file containing input instances.\n"
+	+ "\tIf not supplied then instances will be read from stdin.\n"
+	+ "-a <attribute index>\n"
+	+ "\tThe number of the attribute the probability distribution\n"
+	+ "\testimation is done for.\n"
+	+ "\t\"first\" and \"last\" are also valid entries.\n"
+	+ "\tIf not supplied then no class is assigned.\n"
+	+ "-c <class index>\n"
+	+ "\tIf class value index is set, this attribute is taken as class.\n"
+	+ "\t\"first\" and \"last\" are also valid entries.\n"
+	+ "\tIf not supplied then last is default.\n"
+	+ "-v <class value index>\n"
+	+ "\tIf value is different to -1, select instances of this class value.\n"
+	+ "\t\"first\" and \"last\" are also valid entries.\n"
+	+ "\tIf not supplied then all instances are taken.\n";
+      
+      throw new Exception('\n' + ex.getMessage()
+			  + specificOptions+genericOptions);
+    }
+  }
+
+  public static void buildEstimator(Estimator est,
+				    Instances instances, int attrIndex, 
+				    int classIndex, int classValueIndex,
+				    boolean isIncremental) throws Exception {
+
+    // DBO.pln("buildEstimator 2 " + classValueIndex);
+
+    // non-incremental estimator add all instances at once
+    if (!isIncremental) {
+      
+      if (classValueIndex == -1) {
+	// DBO.pln("before addValues -- Estimator");
+	est.addValues(instances, attrIndex);
+      } else {
+	// DBO.pln("before addValues with classvalue -- Estimator");
+	est.addValues(instances, attrIndex, 
+		      classIndex, classValueIndex);
+      }
+    } else {
+      // incremental estimator, read one value at a time
+      Enumeration enumInsts = (instances).enumerateInstances();
+      while (enumInsts.hasMoreElements()) {
+	Instance instance = 
+	  (Instance) enumInsts.nextElement();
+	((IncrementalEstimator)est).addValue(instance.value(attrIndex),
+					     instance.weight());
+      }
+    }
+  }
+  
+  /**
+   * Parses and sets the general options
+   * @param build contains the data used
+   * @param est the estimator used
+   * @param options the options from the command line
+   */
+  private static void setGeneralOptions(Builder build, Estimator est, 
+					String [] options)  
+    throws Exception {
+    Reader input = null;
+    
+    // help request option
+    boolean helpRequest = Utils.getFlag('h', options);
+    if (helpRequest) {
+      throw new Exception("Help requested.\n");
+    }
+    
+    // instances used
+    String infileName = Utils.getOption('i', options);
+    if (infileName.length() != 0) {
+      input = new BufferedReader(new FileReader(infileName));
+    } else {
+      input = new BufferedReader(new InputStreamReader(System.in));
+    }
+    
+    build.m_instances = new Instances(input);
+    
+    // attribute index
+    String attrIndex = Utils.getOption('a', options);
+    
+    if (attrIndex.length() != 0) {
+      if (attrIndex.equals("first")) {
+	build.m_attrIndex = 0;
+      } else if (attrIndex.equals("last")) {
+	build.m_attrIndex = build.m_instances.numAttributes() - 1;
+      } else {
+	int index = Integer.parseInt(attrIndex) - 1;
+	if ((index < 0) || (index >= build.m_instances.numAttributes())) {
+	  throw new IllegalArgumentException("Option a: attribute index out of range.");
+	}
+	build.m_attrIndex = index;
+	
+      }
+    } else {
+      // default is the first attribute
+      build.m_attrIndex = 0;
+    }
+    
+    //class index, if not given is set to last attribute
+    String classIndex = Utils.getOption('c', options);
+    if (classIndex.length() == 0) classIndex = "last";
+
+    if (classIndex.length() != 0) {
+      if (classIndex.equals("first")) {
+	build.m_classIndex = 0;
+      } else if (classIndex.equals("last")) {
+	build.m_classIndex = build.m_instances.numAttributes() - 1;
+      } else {
+	int cl = Integer.parseInt(classIndex);
+	if (cl == -1) {
+	  build.m_classIndex = build.m_instances.numAttributes() - 1;
+	} else {
+	  build.m_classIndex = cl - 1;	
+	}
+      }
+    } 
+    
+    //class value index, if not given is set to  -1
+    String classValueIndex = Utils.getOption('v', options);
+    if (classValueIndex.length() != 0) {
+      if (classValueIndex.equals("first")) {
+	build.m_classValueIndex = 0;
+      } else if (classValueIndex.equals("last")) {
+	build.m_classValueIndex = build.m_instances.numAttributes() - 1;
+      } else {
+	int cl = Integer.parseInt(classValueIndex);
+	if (cl == -1) {
+	  build.m_classValueIndex = -1;
+	} else {
+	  build.m_classValueIndex = cl - 1;	
+	}
+      }
+    } 
+    
+    build.m_instances.setClassIndex(build.m_classIndex);
+  }
+  
+  /**
+   * Creates a deep copy of the given estimator using serialization.
+   *
+   * @param model the estimator to copy
+   * @return a deep copy of the estimator
+   * @exception Exception if an error occurs
+   */
+  public static Estimator clone(Estimator model) throws Exception {
+    
+    return makeCopy(model);
+  }
+  
+  /**
+   * Creates a deep copy of the given estimator using serialization.
+   *
+   * @param model the estimator to copy
+   * @return a deep copy of the estimator
+   * @exception Exception if an error occurs
+   */
+  public static Estimator makeCopy(Estimator model) throws Exception {
+
+    return (Estimator)new SerializedObject(model).getObject();
+  }
+
+  /**
+   * Creates a given number of deep copies of the given estimator using serialization.
+   * 
+   * @param model the estimator to copy
+   * @param num the number of estimator copies to create.
+   * @return an array of estimators.
+   * @exception Exception if an error occurs
+   */
+  public static Estimator [] makeCopies(Estimator model,
+					 int num) throws Exception {
+
+    if (model == null) {
+      throw new Exception("No model estimator set");
+    }
+    Estimator [] estimators = new Estimator [num];
+    SerializedObject so = new SerializedObject(model);
+    for(int i = 0; i < estimators.length; i++) {
+      estimators[i] = (Estimator) so.getObject();
+    }
+    return estimators;
+  }
+ 
+  /**
+   * Tests whether the current estimation object is equal to another
+   * estimation object
+   *
+   * @param obj the object to compare against
+   * @return true if the two objects are equal
+   */
+  public boolean equals(Object obj) {
+    
+    if ((obj == null) || !(obj.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    Estimator cmp = (Estimator) obj;
+    if (m_Debug != cmp.m_Debug) return false;
+    if (m_classValueIndex != cmp.m_classValueIndex) return false;
+    if (m_noClass != cmp.m_noClass) return false;
+    
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+	      "\tIf set, estimator is run in debug mode and\n"
+	      + "\tmay output additional info to the console",
+	      "D", 0, "-D"));
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -D  <br>
+   * If set, estimator is run in debug mode and 
+   * may output additional info to the console.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of the Estimator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options;
+    if (getDebug()) {
+      options = new String[1];
+      options[0] = "-D";
+    } else {
+      options = new String[0];
+    }
+    return options;
+  }
+  
+  /**
+   * Creates a new instance of a estimatorr given it's class name and
+   * (optional) arguments to pass to it's setOptions method. If the
+   * classifier implements OptionHandler and the options parameter is
+   * non-null, the classifier will have it's options set.
+   *
+   * @param name the fully qualified class name of the estimatorr
+   * @param options an array of options suitable for passing to setOptions. May
+   * be null.
+   * @return the newly created classifier, ready for use.
+   * @exception Exception if the classifier name is invalid, or the options
+   * supplied are not acceptable to the classifier
+   */
+  public static Estimator forName(String name,
+      String [] options) throws Exception {
+    
+    return (Estimator)Utils.forName(Estimator.class,
+        name,
+        options);
+  }
+
+ /**
+   * Set debugging mode.
+   *
+   * @param debug true if debug output should be printed
+   */
+  public void setDebug(boolean debug) {
+
+    m_Debug = debug;
+  }
+
+  /**
+   * Get whether debugging is turned on.
+   *
+   * @return true if debugging output is on
+   */
+  public boolean getDebug() {
+
+    return m_Debug;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "If set to true, estimator may output additional info to " +
+      "the console.";
+  }
+ 
+  /** 
+   * Returns the Capabilities of this Estimator. Derived estimators have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = new Capabilities(this);
+    result.enableAll();
+    
+/*    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    } */
+       
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5489 $");
+  }
+  
+  /** 
+   * Test if the estimator can handle the data.
+   * @param data the dataset the estimator takes an attribute from
+   * @param attrIndex the index of the attribute
+   * @see Capabilities
+   */
+  public void testCapabilities(Instances data, int attrIndex) throws Exception {
+    getCapabilities().testWithFail(data);
+    getCapabilities().testWithFail(data.attribute(attrIndex));
+  }
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/estimators/EstimatorUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/EstimatorUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/EstimatorUtils.java	(revision 29)
@@ -0,0 +1,331 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EstimatorUtils.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+ 
+/** 
+ * Contains static utility functions for Estimators.<p>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class EstimatorUtils
+  implements RevisionHandler {
+  
+  /** 
+   * Find the minimum distance between values
+   * @param inst sorted instances, sorted
+   * @param attrIndex index of the attribute, they are sorted after
+   * @return the minimal distance
+   */
+  public static double findMinDistance(Instances inst, int attrIndex) {
+    double min = Double.MAX_VALUE;
+    int numInst = inst.numInstances();
+    double diff;
+    if (numInst < 2) return min;
+    int begin = -1;
+    Instance instance = null;
+    do { 
+      begin++;
+      if (begin < numInst) 
+	{ instance = inst.instance(begin); }
+    } while (begin < numInst && instance.isMissing(attrIndex)); 
+
+    double secondValue = inst.instance(begin).value(attrIndex);
+    for (int i = begin; i < numInst && !inst.instance(i).isMissing(attrIndex);  i++) {
+      double firstValue = secondValue; 
+      secondValue = inst.instance(i).value(attrIndex);
+      if (secondValue != firstValue) {
+	diff = secondValue - firstValue;
+	if (diff < min && diff > 0.0) {
+	  min = diff;
+	}
+      }
+    }
+    return min;
+  }
+
+  /** 
+   * Find the minimum and the maximum of the attribute and return it in 
+   * the last parameter..
+   * @param inst instances used to build the estimator
+   * @param attrIndex index of the attribute
+   * @param minMax the array to return minimum and maximum in
+   * @return number of not missing values
+   * @exception Exception if parameter minMax wasn't initialized properly
+   */
+  public static int getMinMax(Instances inst, int attrIndex, double [] minMax) 
+    throws Exception {
+    double min = Double.NaN;
+    double max = Double.NaN;
+    Instance instance = null;
+    int numNotMissing = 0;
+    if ((minMax == null) || (minMax.length < 2)) {
+      throw new Exception("Error in Program, privat method getMinMax");
+    }
+    
+    Enumeration enumInst = inst.enumerateInstances();
+    if (enumInst.hasMoreElements()) {
+      do {
+	instance = (Instance) enumInst.nextElement();
+      } while (instance.isMissing(attrIndex) && (enumInst.hasMoreElements()));
+      
+      // add values if not  missing
+      if (!instance.isMissing(attrIndex)) {
+	numNotMissing++;
+	min = instance.value(attrIndex);
+	max = instance.value(attrIndex);
+      }
+      while (enumInst.hasMoreElements()) {
+	instance = (Instance) enumInst.nextElement();
+	if (!instance.isMissing(attrIndex)) {
+	  numNotMissing++;
+	  if (instance.value(attrIndex) < min) {
+	    min = (instance.value(attrIndex));
+	  } else {
+	    if (instance.value(attrIndex) > max) {	      
+	      max = (instance.value(attrIndex));
+	    }
+	  }
+	}
+      }
+    }
+    minMax[0] = min;
+    minMax[1] = max;
+    return numNotMissing;
+  }
+
+  /**
+   * Returns a dataset that contains all instances of a certain class value.
+   *
+   * @param data dataset to select the instances from
+   * @param attrIndex index of the relevant attribute
+   * @param classIndex index of the class attribute
+   * @param classValue the relevant class value 
+   * @return a dataset with only 
+   */
+  public static Vector getInstancesFromClass(Instances data, int attrIndex,
+					     int classIndex,
+					     double classValue, Instances workData) {
+    //Oops.pln("getInstancesFromClass classValue"+classValue+" workData"+data.numInstances());
+    Vector dataPlusInfo = new Vector(0);
+    int num = 0;
+    int numClassValue = 0;
+    //workData = new Instances(data, 0);
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (!data.instance(i).isMissing(attrIndex)) {
+	num++;
+	if (data.instance(i).value(classIndex) == classValue) {
+	  workData.add(data.instance(i));
+	  numClassValue++;
+	}
+      }
+    } 
+
+    Double alphaFactor = new Double((double)numClassValue/(double)num);
+    dataPlusInfo.add(workData);
+    dataPlusInfo.add(alphaFactor);
+    return dataPlusInfo;
+  }
+
+
+  /**
+   * Returns a dataset that contains of all instances of a certain class value.
+   * @param data dataset to select the instances from
+   * @param classIndex index of the class attribute
+   * @param classValue the class value 
+   * @return a dataset with only instances of one class value
+   */
+  public static Instances getInstancesFromClass(Instances data, int classIndex,
+						double classValue) {
+     Instances workData = new Instances(data, 0);
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (data.instance(i).value(classIndex) == classValue) {
+	workData.add(data.instance(i));
+      }
+     
+    }
+    return workData;
+  }
+  
+    
+   
+  /**
+   * Output of an n points of a density curve.
+   * Filename is parameter f + ".curv".
+   *
+   * @param f string to build filename
+   * @param est
+   * @param min
+   * @param max
+   * @param numPoints
+   * @throws Exception if something goes wrong
+   */
+  public static void writeCurve(String f, Estimator est, 
+				double min, double max,
+				int numPoints) throws Exception {
+
+    PrintWriter output = null;
+    StringBuffer text = new StringBuffer("");
+    
+    if (f.length() != 0) {
+      // add attribute indexnumber to filename and extension .hist
+      String name = f + ".curv";
+      output = new PrintWriter(new FileOutputStream(name));
+    } else {
+      return;
+    }
+
+    double diff = (max - min) / ((double)numPoints - 1.0);
+    try {
+      text.append("" + min + " " + est.getProbability(min) + " \n");
+
+      for (double value = min + diff; value < max; value += diff) {
+	text.append("" + value + " " + est.getProbability(value) + " \n");
+      }
+      text.append("" + max + " " + est.getProbability(max) + " \n");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+    output.println(text.toString());    
+
+    // close output
+    if (output != null) {
+      output.close();
+    }
+  }
+
+  /**
+   * Output of an n points of a density curve.
+   * Filename is parameter f + ".curv".
+   *
+   * @param f string to build filename
+   * @param est
+   * @param classEst
+   * @param classIndex
+   * @param min
+   * @param max
+   * @param numPoints
+   * @throws Exception if something goes wrong
+   */
+  public static void writeCurve(String f, Estimator est, 
+				Estimator classEst,
+				double classIndex,
+				double min, double max,
+				int numPoints) throws Exception {
+
+    PrintWriter output = null;
+    StringBuffer text = new StringBuffer("");
+    
+    if (f.length() != 0) {
+      // add attribute indexnumber to filename and extension .hist
+      String name = f + ".curv";
+      output = new PrintWriter(new FileOutputStream(name));
+    } else {
+      return;
+    }
+
+    double diff = (max - min) / ((double)numPoints - 1.0);
+    try {
+      text.append("" + min + " " + 
+		  est.getProbability(min) * classEst.getProbability(classIndex)
+		  + " \n");
+
+      for (double value = min + diff; value < max; value += diff) {
+	text.append("" + value + " " + 
+		    est.getProbability(value) * classEst.getProbability(classIndex)
+		    + " \n");
+      }
+      text.append("" + max + " " +
+		  est.getProbability(max) * classEst.getProbability(classIndex)
+		  + " \n");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.out.println(ex.getMessage());
+    }
+    output.println(text.toString());    
+
+    // close output
+    if (output != null) {
+      output.close();
+    }
+  }
+
+  
+  /**
+   * Returns a dataset that contains of all instances of a certain value
+   * for the given attribute.
+   * @param data dataset to select the instances from
+   * @param index the index of the attribute  
+   * @param v the value 
+   * @return a subdataset with only instances of one value for the attribute 
+   */
+  public static Instances getInstancesFromValue(Instances data, int index,
+					  double v) {
+    Instances workData = new Instances(data, 0);
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (data.instance(i).value(index) == v) {
+	workData.add(data.instance(i));
+      }
+    } 
+    return workData;
+  }
+
+   
+  /**
+   * Returns a string representing the cutpoints
+   */
+  public static String cutpointsToString(double [] cutPoints, boolean [] cutAndLeft) {
+    StringBuffer text = new StringBuffer("");
+    if (cutPoints == null) {
+      text.append("\n# no cutpoints found - attribute \n"); 
+    } else {
+      text.append("\n#* "+cutPoints.length+" cutpoint(s) -\n"); 
+      for (int i = 0; i < cutPoints.length; i++) {
+	text.append("# "+cutPoints[i]+" "); 
+	text.append(""+cutAndLeft[i]+"\n");
+      }
+      text.append("# end\n");
+    }
+    return text.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/IncrementalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/IncrementalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/IncrementalEstimator.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalEstimator.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+
+/** 
+ * Interface for an incremental probability estimators.<p>
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 1.2 $
+ */
+public interface IncrementalEstimator {
+
+  /**
+   * Add one value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  void addValue(double data, double weight);
+
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/estimators/KDConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/KDConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/KDConditionalEstimator.java	(revision 29)
@@ -0,0 +1,153 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KDConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionUtils;
+
+/** 
+ * Conditional probability estimator for a numeric domain conditional upon
+ * a discrete domain (utilises separate kernel estimators for each discrete
+ * conditioning value).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class KDConditionalEstimator implements ConditionalEstimator {
+
+  /** Hold the sub-estimators */
+  private KernelEstimator [] m_Estimators;
+
+  /**
+   * Constructor
+   *
+   * @param numCondSymbols the number of conditioning symbols 
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public KDConditionalEstimator(int numCondSymbols, double precision) {
+
+    m_Estimators = new KernelEstimator [numCondSymbols];
+    for(int i = 0; i < numCondSymbols; i++) {
+      m_Estimators[i] = new KernelEstimator(precision);
+    }
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+
+    m_Estimators[(int)given].addValue(data, weight);
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+
+    return m_Estimators[(int)given];
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+
+    return getEstimator(given).getProbability(data);
+  }
+
+  /** Display a representation of this estimator */
+  public String toString() {
+
+    String result = "KD Conditional Estimator. " 
+      + m_Estimators.length + " sub-estimators:\n";
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result += "Sub-estimator " + i + ": " + m_Estimators[i];
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of pairs of integers which
+   * will be treated as numeric, symbolic.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      int currentA = Integer.parseInt(argv[0]);
+      int maxA = currentA;
+      int currentB = Integer.parseInt(argv[1]);
+      int maxB = currentB;
+      for(int i = 2; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	if (currentA > maxA) {
+	  maxA = currentA;
+	}
+	if (currentB > maxB) {
+	  maxB = currentB;
+	}
+      }
+      KDConditionalEstimator newEst = new KDConditionalEstimator(maxB + 1,
+								 1);
+      for(int i = 0; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	System.out.println(newEst);
+	System.out.println("Prediction for " + currentA + '|' + currentB 
+			   + " = "
+			   + newEst.getProbability(currentA, currentB));
+	newEst.addValue(currentA, currentB, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/KKConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/KKConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/KKConditionalEstimator.java	(revision 29)
@@ -0,0 +1,304 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KKConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import java.util.Random;
+
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+
+/** 
+ * Conditional probability estimator for a numeric domain conditional upon
+ * a numeric domain.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class KKConditionalEstimator implements ConditionalEstimator {
+
+  /** Vector containing all of the values seen */
+  private double [] m_Values;
+
+  /** Vector containing all of the conditioning values seen */
+  private double [] m_CondValues;
+
+  /** Vector containing the associated weights */
+  private double [] m_Weights;
+
+  /**
+   * Number of values stored in m_Weights, m_CondValues, and m_Values so far
+   */
+  private int m_NumValues;
+
+  /** The sum of the weights so far */
+  private double m_SumOfWeights;
+
+  /** Current standard dev */
+  private double m_StandardDev;
+
+  /** Whether we can optimise the kernel summation */
+  private boolean m_AllWeightsOne;
+
+  /** The numeric precision */
+  private double m_Precision;
+
+  /**
+   * Execute a binary search to locate the nearest data value
+   *
+   * @param key the data value to locate
+   * @param secondaryKey the data value to locate
+   * @return the index of the nearest data value
+   */
+  private int findNearestPair(double key, double secondaryKey) {
+
+    int low = 0; 
+    int high = m_NumValues;
+    int middle = 0;
+    while (low < high) {
+      middle = (low + high) / 2;
+      double current = m_CondValues[middle];
+      if (current == key) {
+	double secondary = m_Values[middle];
+	if (secondary == secondaryKey) {
+	  return middle;
+	}
+	if (secondary > secondaryKey) {
+	  high = middle;
+	} else if (secondary < secondaryKey) {
+	  low = middle+1;
+	}
+      }
+      if (current > key) {
+	high = middle;
+      } else if (current < key) {
+	low = middle+1;
+      }
+    }
+    return low;
+  }
+
+  /**
+   * Round a data value using the defined precision for this estimator
+   *
+   * @param data the value to round
+   * @return the rounded data value
+   */
+  private double round(double data) {
+
+    return Math.rint(data / m_Precision) * m_Precision;
+  }
+  
+  /**
+   * Constructor
+   *
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public KKConditionalEstimator(double precision) {
+
+    m_CondValues = new double [50];
+    m_Values = new double [50];
+    m_Weights = new double [50];
+    m_NumValues = 0;
+    m_SumOfWeights = 0;
+    m_StandardDev = 0;
+    m_AllWeightsOne = true;
+    m_Precision = precision;
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+
+    data = round(data);
+    given = round(given);
+    int insertIndex = findNearestPair(given, data);
+    if ((m_NumValues <= insertIndex)
+	|| (m_CondValues[insertIndex] != given)
+	|| (m_Values[insertIndex] != data)) {
+      if (m_NumValues < m_Values.length) {
+	int left = m_NumValues - insertIndex; 
+	System.arraycopy(m_Values, insertIndex, 
+			 m_Values, insertIndex + 1, left);
+	System.arraycopy(m_CondValues, insertIndex, 
+			 m_CondValues, insertIndex + 1, left);
+	System.arraycopy(m_Weights, insertIndex, 
+			 m_Weights, insertIndex + 1, left);
+	m_Values[insertIndex] = data;
+	m_CondValues[insertIndex] = given;
+	m_Weights[insertIndex] = weight;
+	m_NumValues++;
+      } else {
+	double [] newValues = new double [m_Values.length*2];
+	double [] newCondValues = new double [m_Values.length*2];
+	double [] newWeights = new double [m_Values.length*2];
+	int left = m_NumValues - insertIndex; 
+	System.arraycopy(m_Values, 0, newValues, 0, insertIndex);
+	System.arraycopy(m_CondValues, 0, newCondValues, 0, insertIndex);
+	System.arraycopy(m_Weights, 0, newWeights, 0, insertIndex);
+	newValues[insertIndex] = data;
+	newCondValues[insertIndex] = given;
+	newWeights[insertIndex] = weight;
+	System.arraycopy(m_Values, insertIndex, 
+			 newValues, insertIndex+1, left);
+	System.arraycopy(m_CondValues, insertIndex, 
+			 newCondValues, insertIndex+1, left);
+	System.arraycopy(m_Weights, insertIndex, 
+			 newWeights, insertIndex+1, left);
+	m_NumValues++;
+	m_Values = newValues;
+	m_CondValues = newCondValues;
+	m_Weights = newWeights;
+      }
+      if (weight != 1) {
+	m_AllWeightsOne = false;
+      }
+    } else {
+      m_Weights[insertIndex] += weight;
+      m_AllWeightsOne = false;      
+    }
+    m_SumOfWeights += weight;
+    double range = m_CondValues[m_NumValues-1] - m_CondValues[0];
+    m_StandardDev = Math.max(range / Math.sqrt(m_SumOfWeights), 
+			     // allow at most 3 sds within one interval
+			     m_Precision / (2 * 3));
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+
+    Estimator result = new KernelEstimator(m_Precision);
+    if (m_NumValues == 0) {
+      return result;
+    }
+
+    double delta = 0, currentProb = 0;
+    double zLower, zUpper;
+    for(int i = 0; i < m_NumValues; i++) {
+      delta = m_CondValues[i] - given;
+      zLower = (delta - (m_Precision / 2)) / m_StandardDev;
+      zUpper = (delta + (m_Precision / 2)) / m_StandardDev;
+      currentProb = (Statistics.normalProbability(zUpper)
+		     - Statistics.normalProbability(zLower));
+      result.addValue(m_Values[i], currentProb * m_Weights[i]);
+    }
+    return result;
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+
+    return getEstimator(given).getProbability(data);
+  }
+
+  /**
+   * Display a representation of this estimator
+   */
+  public String toString() {
+
+    String result = "KK Conditional Estimator. " 
+      + m_NumValues + " Normal Kernels:\n"
+      + "StandardDev = " + Utils.doubleToString(m_StandardDev,4,2) 
+      + "  \nMeans =";
+    for(int i = 0; i < m_NumValues; i++) {
+      result += " (" + m_Values[i] + ", " + m_CondValues[i] + ")";
+      if (!m_AllWeightsOne) {
+	  result += "w=" + m_Weights[i];
+      }
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class. Creates some random points
+   * in the range 0 - 100, 
+   * and prints out a distribution conditional on some value
+   *
+   * @param argv should contain: seed conditional_value numpoints
+   */
+  public static void main(String [] argv) {
+
+    try {
+      int seed = 42;
+      if (argv.length > 0) {
+	seed = Integer.parseInt(argv[0]);
+      }
+      KKConditionalEstimator newEst = new KKConditionalEstimator(0.1);
+
+      // Create 100 random points and add them
+      Random r = new Random(seed);
+      
+      int numPoints = 50;
+      if (argv.length > 2) {
+	numPoints = Integer.parseInt(argv[2]);
+      }
+      for(int i = 0; i < numPoints; i++) {
+	int x = Math.abs(r.nextInt()%100);
+	int y = Math.abs(r.nextInt()%100);
+	System.out.println("# " + x + "  " + y);
+	newEst.addValue(x, y, 1);
+      }
+      //    System.out.println(newEst);
+      int cond;
+      if (argv.length > 1) {
+	cond = Integer.parseInt(argv[1]);
+      } else {
+	cond = Math.abs(r.nextInt()%100);
+      }
+      System.out.println("## Conditional = " + cond);
+      Estimator result = newEst.getEstimator(cond);
+      for(int i = 0; i <= 100; i+= 5) {
+	System.out.println(" " + i + "  " + result.getProbability(i));
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/KernelEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/KernelEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/KernelEstimator.java	(revision 29)
@@ -0,0 +1,365 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KernelEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Statistics;
+
+/** 
+ * Simple kernel density estimator. Uses one gaussian kernel per observed
+ * data value.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5490 $
+ */
+public class KernelEstimator extends Estimator implements IncrementalEstimator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3646923563367683925L;
+
+  /** Vector containing all of the values seen */
+  private double [] m_Values;
+
+  /** Vector containing the associated weights */
+  private double [] m_Weights;
+
+  /** Number of values stored in m_Weights and m_Values so far */
+  private int m_NumValues;
+
+  /** The sum of the weights so far */
+  private double m_SumOfWeights;
+
+  /** The standard deviation */
+  private double m_StandardDev;
+
+  /** The precision of data values */
+  private double m_Precision;
+
+  /** Whether we can optimise the kernel summation */
+  private boolean m_AllWeightsOne;
+
+  /** Maximum percentage error permitted in probability calculations */
+  private static double MAX_ERROR = 0.01;
+
+
+  /**
+   * Execute a binary search to locate the nearest data value
+   *
+   * @param the data value to locate
+   * @return the index of the nearest data value
+   */
+  private int findNearestValue(double key) {
+
+    int low = 0; 
+    int high = m_NumValues;
+    int middle = 0;
+    while (low < high) {
+      middle = (low + high) / 2;
+      double current = m_Values[middle];
+      if (current == key) {
+	return middle;
+      }
+      if (current > key) {
+	high = middle;
+      } else if (current < key) {
+	low = middle + 1;
+      }
+    }
+    return low;
+  }
+
+  /**
+   * Round a data value using the defined precision for this estimator
+   *
+   * @param data the value to round
+   * @return the rounded data value
+   */
+  private double round(double data) {
+
+    return Math.rint(data / m_Precision) * m_Precision;
+  }
+  
+  // ===============
+  // Public methods.
+  // ===============
+  
+  /**
+   * Constructor that takes a precision argument.
+   *
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public KernelEstimator(double precision) {
+
+    m_Values = new double [50];
+    m_Weights = new double [50];
+    m_NumValues = 0;
+    m_SumOfWeights = 0;
+    m_AllWeightsOne = true;
+    m_Precision = precision;
+    // precision cannot be zero
+    if (m_Precision < Utils.SMALL) m_Precision = Utils.SMALL;
+    //    m_StandardDev = 1e10 * m_Precision; // Set the standard deviation initially very wide
+    m_StandardDev = m_Precision / (2 * 3);
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+    
+    if (weight == 0) {
+      return;
+    }
+    data = round(data);
+    int insertIndex = findNearestValue(data);
+    if ((m_NumValues <= insertIndex) || (m_Values[insertIndex] != data)) {
+      if (m_NumValues < m_Values.length) {
+        int left = m_NumValues - insertIndex; 
+        System.arraycopy(m_Values, insertIndex, 
+            m_Values, insertIndex + 1, left);
+        System.arraycopy(m_Weights, insertIndex, 
+            m_Weights, insertIndex + 1, left);
+        
+        m_Values[insertIndex] = data;
+        m_Weights[insertIndex] = weight;
+        m_NumValues++;
+      } else {
+        double [] newValues = new double [m_Values.length * 2];
+        double [] newWeights = new double [m_Values.length * 2];
+        int left = m_NumValues - insertIndex; 
+        System.arraycopy(m_Values, 0, newValues, 0, insertIndex);
+        System.arraycopy(m_Weights, 0, newWeights, 0, insertIndex);
+        newValues[insertIndex] = data;
+        newWeights[insertIndex] = weight;
+        System.arraycopy(m_Values, insertIndex, 
+            newValues, insertIndex + 1, left);
+        System.arraycopy(m_Weights, insertIndex, 
+            newWeights, insertIndex + 1, left);
+        m_NumValues++;
+        m_Values = newValues;
+        m_Weights = newWeights;
+      }
+      if (weight != 1) {
+        m_AllWeightsOne = false;
+      }
+    } else {
+      m_Weights[insertIndex] += weight;
+      m_AllWeightsOne = false;      
+    }
+    m_SumOfWeights += weight;
+    double range = m_Values[m_NumValues - 1] - m_Values[0];
+    if (range > 0) {
+      m_StandardDev = Math.max(range / Math.sqrt(m_SumOfWeights), 
+          // allow at most 3 sds within one interval
+          m_Precision / (2 * 3));
+    }
+  }
+  
+  /**
+   * Get a probability estimate for a value.
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+
+    double delta = 0, sum = 0, currentProb = 0;
+    double zLower = 0, zUpper = 0;
+    if (m_NumValues == 0) {
+      zLower = (data - (m_Precision / 2)) / m_StandardDev;
+      zUpper = (data + (m_Precision / 2)) / m_StandardDev;
+      return (Statistics.normalProbability(zUpper)
+	      - Statistics.normalProbability(zLower));
+    }
+    double weightSum = 0;
+    int start = findNearestValue(data);
+    for (int i = start; i < m_NumValues; i++) {
+      delta = m_Values[i] - data;
+      zLower = (delta - (m_Precision / 2)) / m_StandardDev;
+      zUpper = (delta + (m_Precision / 2)) / m_StandardDev;
+      currentProb = (Statistics.normalProbability(zUpper)
+		     - Statistics.normalProbability(zLower));
+      sum += currentProb * m_Weights[i];
+      /*
+      System.out.print("zL" + (i + 1) + ": " + zLower + " ");
+      System.out.print("zU" + (i + 1) + ": " + zUpper + " ");
+      System.out.print("P" + (i + 1) + ": " + currentProb + " ");
+      System.out.println("total: " + (currentProb * m_Weights[i]) + " ");
+      */
+      weightSum += m_Weights[i];
+      if (currentProb * (m_SumOfWeights - weightSum) < sum * MAX_ERROR) {
+	break;
+      }
+    }
+    for (int i = start - 1; i >= 0; i--) {
+      delta = m_Values[i] - data;
+      zLower = (delta - (m_Precision / 2)) / m_StandardDev;
+      zUpper = (delta + (m_Precision / 2)) / m_StandardDev;
+      currentProb = (Statistics.normalProbability(zUpper)
+		     - Statistics.normalProbability(zLower));
+      sum += currentProb * m_Weights[i];
+      weightSum += m_Weights[i];
+      if (currentProb * (m_SumOfWeights - weightSum) < sum * MAX_ERROR) {
+	break;
+      }
+    }
+    return sum / m_SumOfWeights;
+  }
+
+  /** Display a representation of this estimator */
+  public String toString() {
+
+    String result = m_NumValues + " Normal Kernels. \nStandardDev = " 
+      + Utils.doubleToString(m_StandardDev,6,4)
+      + " Precision = " + m_Precision;
+    if (m_NumValues == 0) {
+      result += "  \nMean = 0";
+    } else {
+      result += "  \nMeans =";
+      for (int i = 0; i < m_NumValues; i++) {
+	result += " " + m_Values[i];
+      }
+      if (!m_AllWeightsOne) {
+	result += "\nWeights = ";
+	for (int i = 0; i < m_NumValues; i++) {
+	  result += " " + m_Weights[i];
+	}
+      }
+    }
+    return result + "\n";
+  }
+
+  /**
+   * Return the number of kernels in this kernel estimator
+   *
+   * @return the number of kernels
+   */
+  public int getNumKernels() {
+    return m_NumValues;
+  }
+
+  /**
+   * Return the means of the kernels.
+   *
+   * @return the means of the kernels
+   */
+  public double[] getMeans() {
+    return m_Values;
+  }
+
+  /**
+   * Return the weights of the kernels.
+   *
+   * @return the weights of the kernels
+   */
+  public double[] getWeights() {
+    return m_Weights;
+  }
+
+  /**
+   * Return the precision of this kernel estimator.
+   *
+   * @return the precision
+   */
+  public double getPrecision() {
+    return m_Precision;
+  }
+
+  /**
+   * Return the standard deviation of this kernel estimator.
+   *
+   * @return the standard deviation
+   */
+  public double getStdDev() {
+    return m_StandardDev;
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5490 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of numeric values
+   */
+  public static void main(String [] argv) {
+
+    try {
+      if (argv.length < 2) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      KernelEstimator newEst = new KernelEstimator(0.01);
+      for (int i = 0; i < argv.length - 3; i += 2) {
+	newEst.addValue(Double.valueOf(argv[i]).doubleValue(), 
+			Double.valueOf(argv[i + 1]).doubleValue());
+      }
+      System.out.println(newEst);
+
+      double start = Double.valueOf(argv[argv.length - 2]).doubleValue();
+      double finish = Double.valueOf(argv[argv.length - 1]).doubleValue();
+      for (double current = start; current < finish; 
+	  current += (finish - start) / 50) {
+	System.out.println("Data: " + current + " " 
+			   + newEst.getProbability(current));
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/MahalanobisEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/MahalanobisEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/MahalanobisEstimator.java	(revision 29)
@@ -0,0 +1,242 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MahalanobisEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities.Capability;
+import weka.core.matrix.Matrix;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/** 
+ * Simple probability estimator that places a single normal distribution
+ * over the observed values.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5490 $
+ */
+public class MahalanobisEstimator extends Estimator implements IncrementalEstimator {
+  
+  /** for serialization  */
+  private static final long serialVersionUID = 8950225468990043868L;
+  
+  /** The inverse of the covariance matrix */
+  private Matrix m_CovarianceInverse;
+  
+  /** The determinant of the covariance matrix */
+  private double m_Determinant;
+  
+  /**
+   * The difference between the conditioning value and the conditioning mean
+   */
+  private double m_ConstDelta;
+  
+  /** The mean of the values */
+  private double m_ValueMean;
+  
+  /** 2 * PI */
+  private static double TWO_PI = 2 * Math.PI;
+  
+  /**
+   * Returns value for normal kernel
+   *
+   * @param x the argument to the kernel function
+   * @param variance the variance
+   * @return the value for a normal kernel
+   */
+  private double normalKernel(double x) {
+    
+    Matrix thisPoint = new Matrix(1, 2);
+    thisPoint.set(0, 0, x);
+    thisPoint.set(0, 1, m_ConstDelta);
+    return Math.exp(-thisPoint.times(m_CovarianceInverse).
+        times(thisPoint.transpose()).get(0, 0) 
+        / 2) / (Math.sqrt(TWO_PI) * m_Determinant);
+  }
+  
+  /**
+   * Constructor
+   *
+   * @param covariance
+   * @param constDelta
+   * @param valueMean
+   */
+  public MahalanobisEstimator(Matrix covariance, double constDelta,
+      double valueMean) {
+    
+    m_CovarianceInverse = null;
+    if ((covariance.getRowDimension() == 2) && (covariance.getColumnDimension() == 2)) {
+      double a = covariance.get(0, 0);
+      double b = covariance.get(0, 1);
+      double c = covariance.get(1, 0);
+      double d = covariance.get(1, 1);
+      if (a == 0) {
+        a = c; c = 0;
+        double temp = b;
+        b = d; d = temp;
+      }
+      if (a == 0) {
+        return;
+      }
+      double denom = d - c * b / a;
+      if (denom == 0) {
+        return;
+      }
+      m_Determinant = covariance.get(0, 0) * covariance.get(1, 1)
+      - covariance.get(1, 0) * covariance.get(0, 1);
+      m_CovarianceInverse = new Matrix(2, 2);
+      m_CovarianceInverse.set(0, 0, 1.0 / a + b * c / a / a / denom);
+      m_CovarianceInverse.set(0, 1, -b / a / denom);
+      m_CovarianceInverse.set(1, 0, -c / a / denom);
+      m_CovarianceInverse.set(1, 1, 1.0 / denom);
+      m_ConstDelta = constDelta;
+      m_ValueMean = valueMean;
+    }
+  }
+  
+  /**
+   * Add a new data value to the current estimator. Does nothing because the
+   * data is provided in the constructor.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+    
+  }
+  
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+    
+    double delta = data - m_ValueMean;
+    if (m_CovarianceInverse == null) {
+      return 0;
+    }
+    return normalKernel(delta);
+  }
+  
+  /** Display a representation of this estimator */
+  public String toString() {
+    
+    if (m_CovarianceInverse == null) {
+      return "No covariance inverse\n";
+    }
+    return "Mahalanovis Distribution. Mean = "
+    + Utils.doubleToString(m_ValueMean, 4, 2)
+    + "  ConditionalOffset = "
+    + Utils.doubleToString(m_ConstDelta, 4, 2) + "\n"
+    + "Covariance Matrix: Determinant = " + m_Determinant 
+    + "  Inverse:\n" + m_CovarianceInverse;
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5490 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of numeric values
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      double delta = 0.5;
+      double xmean = 0;
+      double lower = 0;
+      double upper = 10;
+      Matrix covariance = new Matrix(2, 2);
+      covariance.set(0, 0, 2);
+      covariance.set(0, 1, -3);
+      covariance.set(1, 0, -4);
+      covariance.set(1, 1, 5);
+      if (argv.length > 0) {
+        covariance.set(0, 0, Double.valueOf(argv[0]).doubleValue());
+      }
+      if (argv.length > 1) {
+        covariance.set(0, 1, Double.valueOf(argv[1]).doubleValue());
+      }
+      if (argv.length > 2) {
+        covariance.set(1, 0, Double.valueOf(argv[2]).doubleValue());
+      }
+      if (argv.length > 3) {
+        covariance.set(1, 1, Double.valueOf(argv[3]).doubleValue());
+      }
+      if (argv.length > 4) {
+        delta = Double.valueOf(argv[4]).doubleValue();
+      }
+      if (argv.length > 5) {
+        xmean = Double.valueOf(argv[5]).doubleValue();
+      }
+      
+      MahalanobisEstimator newEst = new MahalanobisEstimator(covariance,
+          delta, xmean);
+      if (argv.length > 6) {
+        lower = Double.valueOf(argv[6]).doubleValue();
+        if (argv.length > 7) {
+          upper = Double.valueOf(argv[7]).doubleValue();
+        }
+        double increment = (upper - lower) / 50;
+        for(double current = lower; current <= upper; current+= increment)
+          System.out.println(current + "  " + newEst.getProbability(current));
+      } else {
+        System.out.println("Covariance Matrix\n" + covariance);
+        System.out.println(newEst);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/NDConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/NDConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/NDConditionalEstimator.java	(revision 29)
@@ -0,0 +1,155 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NDConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.RevisionUtils;
+
+/** 
+ * Conditional probability estimator for a numeric domain conditional upon
+ * a discrete domain (utilises separate normal estimators for each discrete
+ * conditioning value).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public class NDConditionalEstimator implements ConditionalEstimator {
+
+  /** Hold the sub-estimators */
+  private NormalEstimator [] m_Estimators;
+
+  /**
+   * Constructor
+   *
+   * @param numCondSymbols the number of conditioning symbols 
+   * @param precision the  precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public NDConditionalEstimator(int numCondSymbols, double precision) {
+
+    m_Estimators = new NormalEstimator [numCondSymbols];
+    for(int i = 0; i < numCondSymbols; i++) {
+      m_Estimators[i] = new NormalEstimator(precision);
+    }
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+
+    m_Estimators[(int)given].addValue(data, weight);
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+
+    return m_Estimators[(int)given];
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+
+    return getEstimator(given).getProbability(data);
+  }
+
+  /**
+   * Display a representation of this estimator
+   */
+  public String toString() {
+
+    String result = "ND Conditional Estimator. " 
+      + m_Estimators.length + " sub-estimators:\n";
+    for(int i = 0; i < m_Estimators.length; i++) {
+      result += "Sub-estimator " + i + ": " + m_Estimators[i];
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of pairs of integers which
+   * will be treated as numeric, symbolic.
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      int currentA = Integer.parseInt(argv[0]);
+      int maxA = currentA;
+      int currentB = Integer.parseInt(argv[1]);
+      int maxB = currentB;
+      for(int i = 2; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	if (currentA > maxA) {
+	  maxA = currentA;
+	}
+	if (currentB > maxB) {
+	  maxB = currentB;
+	}
+      }
+      NDConditionalEstimator newEst = new NDConditionalEstimator(maxB + 1,
+								 1);
+      for(int i = 0; i < argv.length - 1; i += 2) {
+	currentA = Integer.parseInt(argv[i]);
+	currentB = Integer.parseInt(argv[i + 1]);
+	System.out.println(newEst);
+	System.out.println("Prediction for " + currentA + '|' + currentB 
+			   + " = "
+			   + newEst.getProbability(currentA, currentB));
+	newEst.addValue(currentA, currentB, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/NNConditionalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/NNConditionalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/NNConditionalEstimator.java	(revision 29)
@@ -0,0 +1,278 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NNConditionalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import java.util.Random;
+import java.util.Vector;
+
+import weka.core.matrix.Matrix;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/** 
+ * Conditional probability estimator for a numeric domain conditional upon
+ * a numeric domain (using Mahalanobis distance).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class NNConditionalEstimator implements ConditionalEstimator {
+
+  /** Vector containing all of the values seen */
+  private Vector m_Values = new Vector();
+
+  /** Vector containing all of the conditioning values seen */
+  private Vector m_CondValues = new Vector();
+
+  /** Vector containing the associated weights */
+  private Vector m_Weights = new Vector();
+
+  /** The sum of the weights so far */
+  private double m_SumOfWeights;
+
+  /** Current Conditional mean */
+  private double m_CondMean;
+
+  /** Current Values mean */
+  private double m_ValueMean;
+
+  /** Current covariance matrix */
+  private Matrix m_Covariance;
+
+  /** Whether we can optimise the kernel summation */
+  private boolean m_AllWeightsOne = true;
+
+  /** 2 * PI */
+  private static double TWO_PI = 2 * Math.PI;
+  
+  // ===============
+  // Private methods
+  // ===============
+
+  /**
+   * Execute a binary search to locate the nearest data value
+   *
+   * @param key the data value to locate
+   * @param secondaryKey the data value to locate
+   * @return the index of the nearest data value
+   */
+  private int findNearestPair(double key, double secondaryKey) {
+    
+    int low = 0; 
+    int high = m_CondValues.size();
+    int middle = 0;
+    while (low < high) {
+      middle = (low + high) / 2;
+      double current = ((Double)m_CondValues.elementAt(middle)).doubleValue();
+      if (current == key) {
+	double secondary = ((Double)m_Values.elementAt(middle)).doubleValue();
+	if (secondary == secondaryKey) {
+	  return middle;
+	}
+	if (secondary > secondaryKey) {
+	  high = middle;
+	} else if (secondary < secondaryKey) {
+	  low = middle + 1;
+	}
+      }
+      if (current > key) {
+	high = middle;
+      } else if (current < key) {
+	low = middle + 1;
+      }
+    }
+    return low;
+  }
+
+  /** Calculate covariance and value means */
+  private void calculateCovariance() {
+    
+    double sumValues = 0, sumConds = 0;
+    for(int i = 0; i < m_Values.size(); i++) {
+      sumValues += ((Double)m_Values.elementAt(i)).doubleValue()
+	* ((Double)m_Weights.elementAt(i)).doubleValue();
+      sumConds += ((Double)m_CondValues.elementAt(i)).doubleValue()
+	* ((Double)m_Weights.elementAt(i)).doubleValue();
+    }
+    m_ValueMean = sumValues / m_SumOfWeights;
+    m_CondMean = sumConds / m_SumOfWeights;
+    double c00 = 0, c01 = 0, c10 = 0, c11 = 0;
+    for(int i = 0; i < m_Values.size(); i++) {
+      double x = ((Double)m_Values.elementAt(i)).doubleValue();
+      double y = ((Double)m_CondValues.elementAt(i)).doubleValue();
+      double weight = ((Double)m_Weights.elementAt(i)).doubleValue();
+      c00 += (x - m_ValueMean) * (x - m_ValueMean) * weight;
+      c01 += (x - m_ValueMean) * (y - m_CondMean) * weight;
+      c11 += (y - m_CondMean) * (y - m_CondMean) * weight;
+    }
+    c00 /= (m_SumOfWeights - 1.0);
+    c01 /= (m_SumOfWeights - 1.0);
+    c10 = c01;
+    c11 /= (m_SumOfWeights - 1.0);
+    m_Covariance = new Matrix(2, 2);
+    m_Covariance.set(0, 0, c00);
+    m_Covariance.set(0, 1, c01);
+    m_Covariance.set(1, 0, c10);
+    m_Covariance.set(1, 1, c11);
+  }
+
+  /**
+   * Returns value for normal kernel
+   *
+   * @param x the argument to the kernel function
+   * @param variance the variance
+   * @return the value for a normal kernel
+   */
+  private double normalKernel(double x, double variance) {
+    
+    return Math.exp(-x * x / (2 * variance)) / Math.sqrt(variance * TWO_PI);
+  }
+  
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param given the new value that data is conditional upon 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double given, double weight) {
+    
+    int insertIndex = findNearestPair(given, data);
+    if ((m_Values.size() <= insertIndex)
+	|| (((Double)m_CondValues.elementAt(insertIndex)).doubleValue()
+	    != given)
+	|| (((Double)m_Values.elementAt(insertIndex)).doubleValue()
+	    != data)) {
+      m_CondValues.insertElementAt(new Double(given), insertIndex);
+      m_Values.insertElementAt(new Double(data), insertIndex);
+      m_Weights.insertElementAt(new Double(weight), insertIndex);
+      if (weight != 1) {
+	m_AllWeightsOne = false;
+      }
+    } else {
+      double newWeight = ((Double)m_Weights.elementAt(insertIndex))
+	.doubleValue();
+      newWeight += weight;
+      m_Weights.setElementAt(new Double(newWeight), insertIndex);
+      m_AllWeightsOne = false;      
+    }
+    m_SumOfWeights += weight;
+    // Invalidate any previously calculated covariance matrix
+    m_Covariance = null;
+  }
+
+  /**
+   * Get a probability estimator for a value
+   *
+   * @param given the new value that data is conditional upon 
+   * @return the estimator for the supplied value given the condition
+   */
+  public Estimator getEstimator(double given) {
+    
+    if (m_Covariance == null) {
+      calculateCovariance();
+    }
+    Estimator result = new MahalanobisEstimator(m_Covariance,
+						given - m_CondMean,
+						m_ValueMean);
+    return result;
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @param given the new value that data is conditional upon 
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data, double given) {
+    
+    return getEstimator(given).getProbability(data);
+  }
+
+  /** Display a representation of this estimator */
+  public String toString() {
+    
+    if (m_Covariance == null) {
+      calculateCovariance();
+    }
+    String result = "NN Conditional Estimator. "
+      + m_CondValues.size() 
+      + " data points.  Mean = " + Utils.doubleToString(m_ValueMean, 4, 2)
+      + "  Conditional mean = " + Utils.doubleToString(m_CondMean, 4, 2);
+    result += "  Covariance Matrix: \n" + m_Covariance;
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of numeric values
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      int seed = 42;
+      if (argv.length > 0) {
+	seed = Integer.parseInt(argv[0]);
+      }
+      NNConditionalEstimator newEst = new NNConditionalEstimator();
+
+      // Create 100 random points and add them
+      Random r = new Random(seed);
+      
+      int numPoints = 50;
+      if (argv.length > 2) {
+	numPoints = Integer.parseInt(argv[2]);
+      }
+      for(int i = 0; i < numPoints; i++) {
+	int x = Math.abs(r.nextInt() % 100);
+	int y = Math.abs(r.nextInt() % 100);
+	System.out.println("# " + x + "  " + y);
+	newEst.addValue(x, y, 1);
+      }
+      //    System.out.println(newEst);
+      int cond;
+      if (argv.length > 1) {
+	cond = Integer.parseInt(argv[1]);
+      }
+      else cond = Math.abs(r.nextInt() % 100);
+      System.out.println("## Conditional = " + cond);
+      Estimator result = newEst.getEstimator(cond);
+      for(int i = 0; i <= 100; i+= 5) {
+	System.out.println(" " + i + "  " + result.getProbability(i));
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/NormalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/NormalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/NormalEstimator.java	(revision 29)
@@ -0,0 +1,244 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NormalEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+
+/** 
+ * Simple probability estimator that places a single normal distribution
+ * over the observed values.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5490 $
+ */
+public class NormalEstimator
+  extends Estimator
+  implements IncrementalEstimator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 93584379632315841L;
+
+  /** The sum of the weights */
+  private double m_SumOfWeights;
+
+  /** The sum of the values seen */
+  private double m_SumOfValues;
+
+  /** The sum of the values squared */
+  private double m_SumOfValuesSq;
+
+  /** The current mean */
+  private double m_Mean;
+
+  /** The current standard deviation */
+  private double m_StandardDev;
+
+  /** The precision of numeric values ( = minimum std dev permitted) */
+  private double m_Precision;
+
+  /**
+   * Round a data value using the defined precision for this estimator
+   *
+   * @param data the value to round
+   * @return the rounded data value
+   */
+  private double round(double data) {
+
+    return Math.rint(data / m_Precision) * m_Precision;
+  }
+  
+  // ===============
+  // Public methods.
+  // ===============
+  
+  /**
+   * Constructor that takes a precision argument.
+   *
+   * @param precision the precision to which numeric values are given. For
+   * example, if the precision is stated to be 0.1, the values in the
+   * interval (0.25,0.35] are all treated as 0.3. 
+   */
+  public NormalEstimator(double precision) {
+
+    m_Precision = precision;
+
+    // Allow at most 3 sd's within one interval
+    m_StandardDev = m_Precision / (2 * 3);
+  }
+
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+
+    if (weight == 0) {
+      return;
+    }
+    data = round(data);
+    m_SumOfWeights += weight;
+    m_SumOfValues += data * weight;
+    m_SumOfValuesSq += data * data * weight;
+
+    if (m_SumOfWeights > 0) {
+      m_Mean = m_SumOfValues / m_SumOfWeights;
+      double stdDev = Math.sqrt(Math.abs(m_SumOfValuesSq 
+					  - m_Mean * m_SumOfValues) 
+					 / m_SumOfWeights);
+      // If the stdDev ~= 0, we really have no idea of scale yet, 
+      // so stick with the default. Otherwise...
+      if (stdDev > 1e-10) {
+	m_StandardDev = Math.max(m_Precision / (2 * 3), 
+				 // allow at most 3sd's within one interval 
+				 stdDev);
+      }
+    }
+  }
+
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+
+    data = round(data);
+    double zLower = (data - m_Mean - (m_Precision / 2)) / m_StandardDev;
+    double zUpper = (data - m_Mean + (m_Precision / 2)) / m_StandardDev;
+    
+    double pLower = Statistics.normalProbability(zLower);
+    double pUpper = Statistics.normalProbability(zUpper);
+    return pUpper - pLower;
+  }
+
+  /**
+   * Display a representation of this estimator
+   */
+  public String toString() {
+
+    return "Normal Distribution. Mean = " + Utils.doubleToString(m_Mean, 4)
+      + " StandardDev = " + Utils.doubleToString(m_StandardDev, 4)
+      + " WeightSum = " + Utils.doubleToString(m_SumOfWeights, 4)
+      + " Precision = " + m_Precision + "\n";
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+
+  /**
+   * Return the value of the mean of this normal estimator.
+   *
+   * @return the mean
+   */
+  public double getMean() {
+    return m_Mean;
+  }
+
+  /**
+   * Return the value of the standard deviation of this normal estimator.
+   *
+   * @return the standard deviation
+   */
+  public double getStdDev() {
+    return m_StandardDev;
+  }
+
+  /**
+   * Return the value of the precision of this normal estimator.
+   *
+   * @return the precision
+   */
+  public double getPrecision() {
+    return m_Precision;
+  }
+
+  /**
+   * Return the sum of the weights for this normal estimator.
+   *
+   * @return the sum of the weights
+   */
+  public double getSumOfWeights() {
+    return m_SumOfWeights;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5490 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of numeric values
+   */
+  public static void main(String [] argv) {
+
+    try {
+
+      if (argv.length == 0) {
+	System.out.println("Please specify a set of instances.");
+	return;
+      }
+      NormalEstimator newEst = new NormalEstimator(0.01);
+      for(int i = 0; i < argv.length; i++) {
+	double current = Double.valueOf(argv[i]).doubleValue();
+	System.out.println(newEst);
+	System.out.println("Prediction for " + current 
+			   + " = " + newEst.getProbability(current));
+	newEst.addValue(current, 1);
+      }
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/PoissonEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/PoissonEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/PoissonEstimator.java	(revision 29)
@@ -0,0 +1,171 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PoissonEstimator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/** 
+ * Simple probability estimator that places a single Poisson distribution
+ * over the observed values.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5490 $
+ */
+public class PoissonEstimator
+  extends Estimator
+  implements IncrementalEstimator {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7669362595289236662L;
+  
+  /** The number of values seen */
+  private double m_NumValues;
+  
+  /** The sum of the values seen */
+  private double m_SumOfValues;
+  
+  /** 
+   * The average number of times
+   * an event occurs in an interval.
+   */
+  private double m_Lambda;
+  
+  
+  /**
+   * Calculates the log factorial of a number.
+   *
+   * @param x input number.
+   * @return log factorial of x.
+   */
+  private double logFac(double x) {
+    
+    double result = 0;
+    for (double i = 2; i <= x; i++) {
+      result += Math.log(i);
+    }
+    return result;
+  }
+  
+  /**
+   * Returns value for Poisson distribution
+   *
+   * @param x the argument to the kernel function
+   * @return the value for a Poisson kernel
+   */
+  private double Poisson(double x) {
+    
+    return Math.exp(-m_Lambda + (x * Math.log(m_Lambda)) - logFac(x));
+  }
+  
+  /**
+   * Add a new data value to the current estimator.
+   *
+   * @param data the new data value 
+   * @param weight the weight assigned to the data value 
+   */
+  public void addValue(double data, double weight) {
+    
+    m_NumValues += weight;
+    m_SumOfValues += data * weight;
+    if (m_NumValues != 0) {
+      m_Lambda = m_SumOfValues / m_NumValues;
+    }
+  }
+  
+  /**
+   * Get a probability estimate for a value
+   *
+   * @param data the value to estimate the probability of
+   * @return the estimated probability of the supplied value
+   */
+  public double getProbability(double data) {
+    
+    return Poisson(data);
+  }
+  
+  /** Display a representation of this estimator */
+  public String toString() {
+    
+    return "Poisson Lambda = " + Utils.doubleToString(m_Lambda, 4, 2) + "\n";
+  }
+  
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // class
+    if (!m_noClass) {
+      result.enable(Capability.NOMINAL_CLASS);
+      result.enable(Capability.MISSING_CLASS_VALUES);
+    } else {
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5490 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain a sequence of numeric values
+   */
+  public static void main(String [] argv) {
+    
+    try {
+      if (argv.length == 0) {
+        System.out.println("Please specify a set of instances.");
+        return;
+      }
+      PoissonEstimator newEst = new PoissonEstimator();
+      for(int i = 0; i < argv.length; i++) {
+        double current = Double.valueOf(argv[i]).doubleValue();
+        System.out.println(newEst);
+        System.out.println("Prediction for " + current 
+            + " = " + newEst.getProbability(current));
+        newEst.addValue(current, 1);
+      }
+      
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateDensityEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateDensityEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateDensityEstimator.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateDensityEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+/**
+ * Interface that can be implemented by simple weighted univariate
+ * density estimators.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5680 $
+ */
+public interface UnivariateDensityEstimator {
+
+  /**
+   * Adds a value to the density estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  void addValue(double value, double weight);
+
+  /**
+   * Returns the natural logarithm of the density estimate at the given
+   * point.
+   *
+   * @param value the value at which to evaluate
+   * @return the natural logarithm of the density estimate at the given
+   * value
+   */
+  double logDensity(double value);
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateEqualFrequencyHistogramEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateEqualFrequencyHistogramEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateEqualFrequencyHistogramEstimator.java	(revision 29)
@@ -0,0 +1,648 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateEqualFrequencyEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import java.util.Random;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import weka.core.Statistics;
+import weka.core.Utils;
+
+/**
+ * Simple histogram density estimator. Uses equal-frequency histograms
+ * based on the specified number of bins (default: 10).
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5680 $
+ */
+public class UnivariateEqualFrequencyHistogramEstimator implements UnivariateDensityEstimator,
+                                                     UnivariateIntervalEstimator {
+
+  /** The collection used to store the weighted values. */
+  protected TreeMap<Double, Double> m_TM = new TreeMap<Double, Double>();
+
+  /** The interval boundaries. */
+  protected double[] m_Boundaries = null;
+
+  /** The weight of each interval. */
+  protected double[] m_Weights = null;
+
+  /** The weighted sum of values */
+  protected double m_WeightedSum = 0;
+
+  /** The weighted sum of squared values */
+  protected double m_WeightedSumSquared = 0;
+
+  /** The total sum of weights. */
+  protected double m_SumOfWeights = 0;
+
+  /** The number of bins to use. */
+  protected int m_NumBins = 10;
+
+  /** The current bandwidth (only computed when needed) */
+  protected double m_Width = Double.MAX_VALUE;
+
+  /** The exponent to use in computation of bandwidth (default: -0.25) */
+  protected double m_Exponent = -0.25;
+
+  /** The minimum allowed value of the kernel width (default: 1.0E-6) */
+  protected double m_MinWidth = 1.0E-6;
+
+  /** Constant for Gaussian density. */
+  public static final double CONST = - 0.5 * Math.log(2 * Math.PI);
+
+  /** The number of intervals used to approximate prediction interval. */
+  protected int m_NumIntervals = 1000;
+
+  /** Whether boundaries are updated or only weights. */
+  protected boolean m_UpdateWeightsOnly = false;
+
+  /**
+   * Gets the number of bins 
+   *
+   * @return the number of bins.
+   */
+  public int getNumBins() {
+
+    return m_NumBins;
+  }
+
+  /**
+   * Sets the number of bins 
+   *
+   * @param numBins the number of bins
+   */
+  public void setNumBins(int numBins) {
+
+    m_NumBins = numBins;
+  }
+
+  /**
+   * Triggers construction of estimator based on current data
+   * and then initializes the statistics.
+   */
+  public void initializeStatistics() {
+
+    updateBoundariesAndOrWeights();
+
+    m_TM = new TreeMap<Double, Double>();
+    m_WeightedSum = 0;
+    m_WeightedSumSquared = 0;
+    m_SumOfWeights = 0;
+    m_Weights = null;
+  }    
+
+  /**
+   * Sets whether only weights should be udpated.
+   */
+  public void setUpdateWeightsOnly(boolean flag) {
+
+    m_UpdateWeightsOnly = flag;
+  }
+
+  /**
+   * Gets whether only weights should be udpated.*
+   */
+  public boolean getUpdateWeightsOnly() {
+
+    return m_UpdateWeightsOnly;
+  }
+
+  /**
+   * Adds a value to the density estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  public void addValue(double value, double weight) {
+
+    // Add data point to collection
+    m_WeightedSum += value * weight;
+    m_WeightedSumSquared += value * value * weight;
+    m_SumOfWeights += weight;
+    if (m_TM.get(value) == null) {
+      m_TM.put(value, weight);
+    } else {
+      m_TM.put(value, m_TM.get(value) + weight);
+    }
+
+    // Make sure estimator is updated
+    if (!getUpdateWeightsOnly()) {
+      m_Boundaries = null;
+    }
+    m_Weights = null;
+  }
+
+  /**
+   * Updates the boundaries if necessary.
+   */
+  protected void updateBoundariesAndOrWeights() {
+
+    // Do we need to update?
+    if (m_Weights != null) {
+      return;
+    }
+
+    // Update widths for cases that are out of bounds,
+    // using same code as in kernel estimator
+
+    // First, compute variance for scaling
+    double mean = m_WeightedSum / m_SumOfWeights;
+    double variance = m_WeightedSumSquared / m_SumOfWeights - mean * mean;
+    if (variance < 0) {
+      variance = 0;
+    }
+    
+    // Compute kernel bandwidth
+    m_Width = Math.sqrt(variance) * Math.pow(m_SumOfWeights, m_Exponent);
+    
+    if (m_Width <= m_MinWidth) {
+      m_Width = m_MinWidth;
+    }
+    
+    // Do we need to update weights only
+    if (getUpdateWeightsOnly()) {
+      updateWeightsOnly();
+    } else {
+      updateBoundariesAndWeights();
+    }
+  }
+   
+  /**
+   * Updates the weights only.
+   */
+  protected void updateWeightsOnly() throws IllegalArgumentException {
+
+    // Get values and keys from tree map
+    Iterator<Map.Entry<Double,Double>> itr = m_TM.entrySet().iterator();
+    int j = 1;
+    m_Weights = new double[m_Boundaries.length - 1];
+    while(itr.hasNext()) {
+      Map.Entry<Double,Double> entry = itr.next();
+      double value = entry.getKey();
+      double weight = entry.getValue();
+      if ((value < m_Boundaries[0]) || (value > m_Boundaries[m_Boundaries.length - 1])) {
+        throw new IllegalArgumentException("Out-of-range value during weight update");
+      }
+      while (value > m_Boundaries[j]) {
+        j++;
+      }
+      m_Weights[j - 1] += weight;
+    }
+  }
+
+  /**
+   * Updates the boundaries and weights.
+   */
+  protected void updateBoundariesAndWeights() {
+
+    // Get values and keys from tree map
+    double[] values = new double[m_TM.size()];
+    double[] weights = new double[m_TM.size()];
+    Iterator<Map.Entry<Double,Double>> itr = m_TM.entrySet().iterator();
+    int j = 0;
+    while(itr.hasNext()) {
+      Map.Entry<Double,Double> entry = itr.next();
+      values[j] = entry.getKey();
+      weights[j] = entry.getValue();
+      j++;
+    }
+
+    double freq = m_SumOfWeights / m_NumBins;
+    double[] cutPoints = new double[m_NumBins - 1];
+    double[] binWeights = new double[m_NumBins];
+    double sumOfWeights = m_SumOfWeights;
+
+    // Compute break points
+    double weightSumSoFar = 0, lastWeightSum = 0;
+    int cpindex = 0, lastIndex = -1;
+    for (int i = 0; i < values.length - 1; i++) {
+
+      // Update weight statistics
+      weightSumSoFar += weights[i];
+      sumOfWeights -= weights[i];
+
+      // Have we passed the ideal size?
+      if (weightSumSoFar >= freq) {
+
+        // Is this break point worse than the last one?
+        if (((freq - lastWeightSum) < (weightSumSoFar - freq)) && (lastIndex != -1)) {
+          cutPoints[cpindex] = (values[lastIndex] + values[lastIndex + 1]) / 2;
+          weightSumSoFar -= lastWeightSum;
+          binWeights[cpindex] = lastWeightSum;
+          lastWeightSum = weightSumSoFar;
+          lastIndex = i;
+        } else {
+          cutPoints[cpindex] = (values[i] + values[i + 1]) / 2;
+          binWeights[cpindex] = weightSumSoFar;
+          weightSumSoFar = 0;
+          lastWeightSum = 0;
+          lastIndex = -1;
+        }
+        cpindex++;
+        freq = (sumOfWeights + weightSumSoFar) / ((cutPoints.length + 1) - cpindex);
+      } else {
+        lastIndex = i;
+        lastWeightSum = weightSumSoFar;
+      }
+    }
+
+    // Check whether there was another possibility for a cut point
+    if ((cpindex < cutPoints.length) && (lastIndex != -1)) {
+      cutPoints[cpindex] = (values[lastIndex] + values[lastIndex + 1]) / 2;      
+      binWeights[cpindex] = lastWeightSum;
+      cpindex++;
+      binWeights[cpindex] = weightSumSoFar - lastWeightSum;
+    } else {
+      binWeights[cpindex] = weightSumSoFar;
+    }
+
+    // Did we find any cutpoints?
+    if (cpindex == 0) {
+      m_Boundaries = null;
+      m_Weights = null;
+    } else {
+
+      // Need to add weight of last data point to right-most bin
+      binWeights[cpindex] += weights[values.length - 1];
+
+      // Copy over boundaries and weights
+      m_Boundaries = new double[cpindex + 2];
+      m_Boundaries[0] = m_TM.firstKey();
+      m_Boundaries[cpindex + 1] = m_TM.lastKey();
+      System.arraycopy(cutPoints, 0, m_Boundaries, 1, cpindex);
+      m_Weights = new double[cpindex + 1];
+      System.arraycopy(binWeights, 0, m_Weights, 0, cpindex + 1);
+    }
+  }
+   
+
+  /**
+   * Returns the interval for the given confidence value. 
+   * 
+   * @param conf the confidence value in the interval [0, 1]
+   * @return the interval
+   */
+  public double[][] predictIntervals(double conf) {
+
+    // Update the bandwidth
+    updateBoundariesAndOrWeights();
+
+    // Compute minimum and maximum value, and delta
+    double val = Statistics.normalInverse(1.0 - (1.0 - conf) / 2);
+    double min = m_TM.firstKey() - val * m_Width;
+    double max = m_TM.lastKey() + val * m_Width;
+    double delta = (max - min) / m_NumIntervals;
+
+    // Create array with estimated probabilities
+    double[] probabilities = new double[m_NumIntervals];
+    double leftVal = Math.exp(logDensity(min));
+    for (int i = 0; i < m_NumIntervals; i++) {
+      double rightVal = Math.exp(logDensity(min + (i + 1) * delta));
+      probabilities[i] = 0.5 * (leftVal + rightVal) * delta;
+      leftVal = rightVal;
+    }
+
+    // Sort array based on area of bin estimates
+    int[] sortedIndices = Utils.sort(probabilities);
+
+    // Mark the intervals to use
+    double sum = 0;
+    boolean[] toUse = new boolean[probabilities.length];
+    int k = 0;
+    while ((sum < conf) && (k < toUse.length)){
+      toUse[sortedIndices[toUse.length - (k + 1)]] = true;
+      sum += probabilities[sortedIndices[toUse.length - (k + 1)]];
+      k++;
+    }
+
+    // Don't need probabilities anymore
+    probabilities = null;
+
+    // Create final list of intervals
+    ArrayList<double[]> intervals = new ArrayList<double[]>();
+
+    // The current interval
+    double[] interval = null;
+    
+    // Iterate through kernels
+    boolean haveStartedInterval = false;
+    for (int i = 0; i < m_NumIntervals; i++) {
+
+      // Should the current bin be used?
+      if (toUse[i]) {
+
+        // Do we need to create a new interval?
+        if (haveStartedInterval == false) {
+          haveStartedInterval = true;
+          interval = new double[2];
+          interval[0] = min + i * delta;
+        }
+
+        // Regardless, we should update the upper boundary
+        interval[1] = min + (i + 1) * delta;
+      } else {
+
+        // We need to finalize and store the last interval
+        // if necessary.
+        if (haveStartedInterval) {
+          haveStartedInterval = false;
+          intervals.add(interval);
+        }
+      }
+    }
+
+    // Add last interval if there is one
+    if (haveStartedInterval) {
+      intervals.add(interval);
+    }
+
+    return intervals.toArray(new double[0][0]);
+  }
+
+  /**
+   * Returns the natural logarithm of the density estimate at the given
+   * point.
+   *
+   * @param value the value at which to evaluate
+   * @return the natural logarithm of the density estimate at the given
+   * value
+   */
+  public double logDensity(double value) {
+
+    // Update boundaries if necessary
+    updateBoundariesAndOrWeights();
+
+    if (m_Boundaries == null) {
+      return Math.log(Double.MIN_VALUE);
+    }
+
+    // Find the bin
+    int index = Arrays.binarySearch(m_Boundaries, value);
+
+    // Is the value outside?
+    if ((index == -1) || (index == -m_Boundaries.length - 1)) {
+
+      // Use normal density outside
+      double val = 0;
+      if (index == -1) { // Smaller than minimum
+        val = m_TM.firstKey() - value;
+      } else {
+        val = value - m_TM.lastKey();
+      }
+      return (CONST - Math.log(m_Width) - 0.5 * (val * val / (m_Width * m_Width))) -
+        Math.log(m_SumOfWeights + 2); 
+    }
+    
+    // Is value exactly equal to right-most boundary?
+    if (index == m_Boundaries.length - 1) {
+      index--;
+    } else {
+
+      // Need to reverse index if necessary
+      if (index < 0) {
+        index = -index - 2;
+      }
+    }
+    
+    // Figure out of width
+    double width = m_Boundaries[index + 1] - m_Boundaries[index];
+
+    // Density compontent from smeared-out data point
+    double densSmearedOut = 1.0 / ((m_SumOfWeights + 2) * (m_Boundaries[m_Boundaries.length - 1] -
+                                                           m_Boundaries[0]));
+
+    // Return log of density
+    if (m_Weights[index] <= 0) {
+
+      /*      System.out.println(value);
+      System.out.println(this);
+      System.exit(1);*/
+      // Just use one smeared-out data point
+      return Math.log(densSmearedOut);
+    } else {
+      return Math.log(densSmearedOut + m_Weights[index] / ((m_SumOfWeights + 2) * width));
+    }
+  }
+
+  /**
+   * Returns textual description of this estimator.
+   */
+  public String toString() {
+
+    StringBuffer text = new StringBuffer();
+
+    text.append("EqualFrequencyHistogram estimator\n\n" +
+                "Bandwidth for out of range cases " + m_Width + 
+                ", total weight " + m_SumOfWeights);
+
+    if (m_Boundaries != null) {
+      text.append("\nLeft boundary\tRight boundary\tWeight\n");
+      for (int i = 0; i < m_Boundaries.length - 1; i++) {
+        text.append(m_Boundaries[i] + "\t" + m_Boundaries[i + 1] + "\t" + m_Weights[i] + "\t" +
+                    Math.exp(logDensity((m_Boundaries[i + 1] + m_Boundaries[i]) / 2)) + "\n");
+      }
+    }
+
+    return text.toString();
+  }
+
+  /**
+   * Main method, used for testing this class.
+   */
+  public static void main(String[] args) {
+
+    // Get random number generator initialized by system
+    Random r = new Random();
+
+    // Create density estimator
+    UnivariateEqualFrequencyHistogramEstimator e = new UnivariateEqualFrequencyHistogramEstimator();
+
+    // Output the density estimator
+    System.out.println(e);
+    
+    // Monte Carlo integration
+    double sum = 0;
+    for (int i = 0; i < 1000; i++) {
+      sum += Math.exp(e.logDensity(r.nextDouble() * 10.0 - 5.0));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / 1000);
+    
+    // Add Gaussian values into it
+    for (int i = 0; i < 1000; i++) {
+      e.addValue(0.1 * r.nextGaussian() - 3, 1);
+      e.addValue(r.nextGaussian() * 0.25, 3);
+    }
+
+    // Monte Carlo integration
+    sum = 0;
+    int points = 10000000;
+    for (int i = 0; i < points; i++) {
+      double value = r.nextDouble() * 20.0 - 10.0;
+      sum += Math.exp(e.logDensity(value));
+    }
+
+    // Output the density estimator
+    System.out.println(e);
+
+    System.out.println("Approximate integral: " + 20.0 * sum / points);
+
+    // Check interval estimates
+    double[][] Intervals = e.predictIntervals(0.9);
+    
+    System.out.println("Printing histogram intervals ---------------------");
+    
+    for (int k = 0; k < Intervals.length; k++) {
+      System.out.println("Left: " + Intervals[k][0] + "\t Right: " + Intervals[k][1]);
+    }
+    
+    System.out.println("Finished histogram printing intervals ---------------------");
+
+    double Covered = 0;
+    for (int i = 0; i < 1000; i++) {
+      double val = -1;
+      if (r.nextDouble() < 0.25) {
+        val = 0.1 * r.nextGaussian() - 3.0;
+      } else {
+        val = r.nextGaussian() * 0.25;
+      }
+      for (int k = 0; k < Intervals.length; k++) {
+        if (val >= Intervals[k][0] && val <= Intervals[k][1]) {
+          Covered++;
+          break;
+        }
+      }
+    }
+    System.out.println("Coverage at 0.9 level for histogram intervals: " + Covered / 1000);
+
+    for (int j = 1; j < 5; j++) {
+      double numTrain = Math.pow(10, j);
+      System.out.println("Number of training cases: " +
+                         numTrain); 
+
+      // Compare performance to normal estimator on normally distributed data
+      UnivariateEqualFrequencyHistogramEstimator eHistogram = new UnivariateEqualFrequencyHistogramEstimator();
+      UnivariateNormalEstimator eNormal = new UnivariateNormalEstimator();
+      
+      // Add training cases
+      for (int i = 0; i < numTrain; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        /*        if (j == 4) {
+          System.err.println(val);
+          }*/
+        eHistogram.addValue(val, 1);
+        eNormal.addValue(val, 1);
+      }
+
+      // Monte Carlo integration
+      sum = 0;
+      points = 10000000;
+      for (int i = 0; i < points; i++) {
+        double value = r.nextDouble() * 20.0 - 10.0;
+        sum += Math.exp(eHistogram.logDensity(value));
+      }
+      System.out.println(eHistogram);
+      System.out.println("Approximate integral for histogram estimator: " + 20.0 * sum / points);
+
+      // Evaluate estimators
+      double loglikelihoodHistogram = 0, loglikelihoodNormal = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        loglikelihoodHistogram += eHistogram.logDensity(val);
+        loglikelihoodNormal += eNormal.logDensity(val);
+      }
+      System.out.println("Loglikelihood for histogram estimator: " +
+                         loglikelihoodHistogram / 1000);
+      System.out.println("Loglikelihood for normal estimator: " +
+                         loglikelihoodNormal / 1000);
+
+      // Check interval estimates
+      double[][] histogramIntervals = eHistogram.predictIntervals(0.95);
+      double[][] normalIntervals = eNormal.predictIntervals(0.95);
+
+      System.out.println("Printing histogram intervals ---------------------");
+      
+      for (int k = 0; k < histogramIntervals.length; k++) {
+        System.out.println("Left: " + histogramIntervals[k][0] + "\t Right: " + histogramIntervals[k][1]);
+      }
+
+      System.out.println("Finished histogram printing intervals ---------------------");
+
+      System.out.println("Printing normal intervals ---------------------");
+      
+      for (int k = 0; k < normalIntervals.length; k++) {
+        System.out.println("Left: " + normalIntervals[k][0] + "\t Right: " + normalIntervals[k][1]);
+      }
+
+      System.out.println("Finished normal printing intervals ---------------------");
+ 
+      double histogramCovered = 0;
+      double normalCovered = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        for (int k = 0; k < histogramIntervals.length; k++) {
+          if (val >= histogramIntervals[k][0] && val <= histogramIntervals[k][1]) {
+            histogramCovered++;
+            break;
+          }
+        }
+        for (int k = 0; k < normalIntervals.length; k++) {
+          if (val >= normalIntervals[k][0] && val <= normalIntervals[k][1]) {
+            normalCovered++;
+            break;
+          }
+        }
+      }
+      System.out.println("Coverage at 0.95 level for histogram intervals: " + histogramCovered / 1000);
+      System.out.println("Coverage at 0.95 level for normal intervals: " + normalCovered / 1000);
+      
+      histogramIntervals = eHistogram.predictIntervals(0.8);
+      normalIntervals = eNormal.predictIntervals(0.8);
+      histogramCovered = 0;
+      normalCovered = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        for (int k = 0; k < histogramIntervals.length; k++) {
+          if (val >= histogramIntervals[k][0] && val <= histogramIntervals[k][1]) {
+            histogramCovered++;
+            break;
+          }
+        }
+        for (int k = 0; k < normalIntervals.length; k++) {
+          if (val >= normalIntervals[k][0] && val <= normalIntervals[k][1]) {
+            normalCovered++;
+            break;
+          }
+        }
+      }
+      System.out.println("Coverage at 0.8 level for histogram intervals: " + histogramCovered / 1000);
+      System.out.println("Coverage at 0.8 level for normal intervals: " + normalCovered / 1000);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateIntervalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateIntervalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateIntervalEstimator.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateIntervalEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+/**
+ * Interface that can be implemented by simple weighted univariate
+ * interval estimators.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5861 $
+ */
+public interface UnivariateIntervalEstimator {
+
+  /**
+   * Adds a value to the interval estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  void addValue(double value, double weight);
+
+  /**
+   * Returns the intervals at the given confidence value. Each row has
+   * one interval. The first element in each row is the lower bound,
+   * the second element the upper one.
+   *
+   * @param confidenceValue the value at which to evaluate
+   * @return the interval
+   */
+  double[][] predictIntervals(double confidenceValue);
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateKernelEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateKernelEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateKernelEstimator.java	(revision 29)
@@ -0,0 +1,467 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateKernelEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import java.util.Random;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.ArrayList;
+
+import weka.core.Statistics;
+import weka.core.Utils;
+
+/**
+ * Simple weighted kernel density estimator.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5680 $
+ */
+public class UnivariateKernelEstimator implements UnivariateDensityEstimator,
+                                                  UnivariateIntervalEstimator {
+
+  /** The collection used to store the weighted values. */
+  protected TreeMap<Double, Double> m_TM = new TreeMap<Double, Double>();
+
+  /** The weighted sum of values */
+  protected double m_WeightedSum = 0;
+
+  /** The weighted sum of squared values */
+  protected double m_WeightedSumSquared = 0;
+
+  /** The weight of the values collected so far */
+  protected double m_SumOfWeights = 0;
+
+  /** The current bandwidth (only computed when needed) */
+  protected double m_Width = Double.MAX_VALUE;
+
+  /** The exponent to use in computation of bandwidth (default: -0.25) */
+  protected double m_Exponent = -0.25;
+
+  /** The minimum allowed value of the kernel width (default: 1.0E-6) */
+  protected double m_MinWidth = 1.0E-6;
+
+  /** Constant for Gaussian density. */
+  public static final double CONST = - 0.5 * Math.log(2 * Math.PI);
+
+  /** Threshold at which further kernels are no longer added to sum. */
+  protected double m_Threshold = 1.0E-6;
+
+  /** The number of intervals used to approximate prediction interval. */
+  protected int m_NumIntervals = 1000;
+
+  /**
+   * Adds a value to the density estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  public void addValue(double value, double weight) {
+
+    m_WeightedSum += value * weight;
+    m_WeightedSumSquared += value * value * weight;
+    m_SumOfWeights += weight;
+    if (m_TM.get(value) == null) {
+      m_TM.put(value, weight);
+    } else {
+      m_TM.put(value, m_TM.get(value) + weight);
+    }
+  }
+
+  /**
+   * Updates bandwidth: the sample standard deviation is multiplied by
+   * the total weight to the power of the given exponent.
+   *
+   * If the total weight is not greater than zero, the width is set to
+   * Double.MAX_VALUE. If that is not the case, but the width becomes
+   * smaller than m_MinWidth, the width is set to the value of
+   * m_MinWidth.
+   */
+  public void updateWidth() {
+
+    // OK, need to do some work
+    if (m_SumOfWeights > 0) {
+
+      // Compute variance for scaling
+      double mean = m_WeightedSum / m_SumOfWeights;
+      double variance = m_WeightedSumSquared / m_SumOfWeights - mean * mean;
+      if (variance < 0) {
+        variance = 0;
+      }
+
+      // Compute kernel bandwidth
+      m_Width = Math.sqrt(variance) * Math.pow(m_SumOfWeights, m_Exponent);
+
+      if (m_Width <= m_MinWidth) {
+        m_Width = m_MinWidth;
+      }
+    } else {
+      m_Width = Double.MAX_VALUE;
+    }
+  }
+   
+
+  /**
+   * Returns the interval for the given confidence value. 
+   * 
+   * @param conf the confidence value in the interval [0, 1]
+   * @return the interval
+   */
+  public double[][] predictIntervals(double conf) {
+
+    // Update the bandwidth
+    updateWidth();
+
+    // Compute minimum and maximum value, and delta
+    double val = Statistics.normalInverse(1.0 - (1.0 - conf) / 2);
+    double min = m_TM.firstKey() - val * m_Width;
+    double max = m_TM.lastKey() + val * m_Width;
+    double delta = (max - min) / m_NumIntervals;
+
+    // Create array with estimated probabilities
+    double[] probabilities = new double[m_NumIntervals];
+    double leftVal = Math.exp(logDensity(min));
+    for (int i = 0; i < m_NumIntervals; i++) {
+      double rightVal = Math.exp(logDensity(min + (i + 1) * delta));
+      probabilities[i] = 0.5 * (leftVal + rightVal) * delta;
+      leftVal = rightVal;
+    }
+
+    // Sort array based on area of bin estimates
+    int[] sortedIndices = Utils.sort(probabilities);
+
+    // Mark the intervals to use
+    double sum = 0;
+    boolean[] toUse = new boolean[probabilities.length];
+    int k = 0;
+    while ((sum < conf) && (k < toUse.length)){
+      toUse[sortedIndices[toUse.length - (k + 1)]] = true;
+      sum += probabilities[sortedIndices[toUse.length - (k + 1)]];
+      k++;
+    }
+
+    // Don't need probabilities anymore
+    probabilities = null;
+
+    // Create final list of intervals
+    ArrayList<double[]> intervals = new ArrayList<double[]>();
+
+    // The current interval
+    double[] interval = null;
+    
+    // Iterate through kernels
+    boolean haveStartedInterval = false;
+    for (int i = 0; i < m_NumIntervals; i++) {
+
+      // Should the current bin be used?
+      if (toUse[i]) {
+
+        // Do we need to create a new interval?
+        if (haveStartedInterval == false) {
+          haveStartedInterval = true;
+          interval = new double[2];
+          interval[0] = min + i * delta;
+        }
+
+        // Regardless, we should update the upper boundary
+        interval[1] = min + (i + 1) * delta;
+      } else {
+
+        // We need to finalize and store the last interval
+        // if necessary.
+        if (haveStartedInterval) {
+          haveStartedInterval = false;
+          intervals.add(interval);
+        }
+      }
+    }
+
+    // Add last interval if there is one
+    if (haveStartedInterval) {
+      intervals.add(interval);
+    }
+
+    return intervals.toArray(new double[0][0]);
+  }
+
+  /**
+   * Computes the logarithm of x and y given the logarithms of x and y.
+   *
+   * This is based on Tobias P. Mann's description in "Numerically
+   * Stable Hidden Markov Implementation" (2006).
+   */
+  protected double logOfSum(double logOfX, double logOfY) {
+
+    // Check for cases where log of zero is present
+    if (Double.isNaN(logOfX)) {
+      return logOfY;
+    } 
+    if (Double.isNaN(logOfY)) {
+      return logOfX;
+    }
+
+    // Otherwise return proper result, taken care of overflows
+    if (logOfX > logOfY) {
+      return logOfX + Math.log(1 + Math.exp(logOfY - logOfX));
+    } else {
+      return logOfY + Math.log(1 + Math.exp(logOfX - logOfY));
+    }
+  }
+
+  /**
+   * Compute running sum of density values and weights.
+   */
+  protected void runningSum(Set<Map.Entry<Double,Double>> c, double value, 
+                            double[] sums) {
+
+    // Auxiliary variables
+    double offset = CONST - Math.log(m_Width);
+    double logFactor = Math.log(m_Threshold) - Math.log(1 - m_Threshold);
+    double logSumOfWeights = Math.log(m_SumOfWeights);
+
+    // Iterate through values
+    Iterator<Map.Entry<Double,Double>> itr = c.iterator();
+    while(itr.hasNext()) {
+      Map.Entry<Double,Double> entry = itr.next();
+
+      // Skip entry if weight is zero because it cannot contribute to sum
+      if (entry.getValue() > 0) {
+        double diff = (entry.getKey() - value) / m_Width;
+        double logDensity = offset - 0.5 * diff * diff;
+        double logWeight = Math.log(entry.getValue());
+        sums[0] = logOfSum(sums[0], logWeight + logDensity);
+        sums[1] = logOfSum(sums[1], logWeight);
+
+        // Can we stop assuming worst case?
+        if (logDensity + logSumOfWeights < logOfSum(logFactor + sums[0], logDensity + sums[1])) {
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the natural logarithm of the density estimate at the given
+   * point.
+   *
+   * @param value the value at which to evaluate
+   * @return the natural logarithm of the density estimate at the given
+   * value
+   */
+  public double logDensity(double value) {
+
+    // Update the bandwidth
+    updateWidth();
+
+    // Array used to keep running sums
+    double[] sums = new double[2];
+    sums[0] = Double.NaN;
+    sums[1] = Double.NaN;
+
+    // Examine right-hand size of value
+    runningSum(m_TM.tailMap(value, true).entrySet(), value, sums);
+
+    // Examine left-hand size of value
+    runningSum(m_TM.headMap(value, false).descendingMap().entrySet(), value, sums);
+
+    // Need to normalize
+    return sums[0] - Math.log(m_SumOfWeights);
+  }
+
+  /**
+   * Returns textual description of this estimator.
+   */
+  public String toString() {
+
+    return "Kernel estimator with bandwidth " + m_Width + 
+      " and total weight " + m_SumOfWeights +
+      " based on\n" + m_TM.toString();
+  }
+
+  /**
+   * Main method, used for testing this class.
+   */
+  public static void main(String[] args) {
+
+    // Get random number generator initialized by system
+    Random r = new Random();
+
+    // Create density estimator
+    UnivariateKernelEstimator e = new UnivariateKernelEstimator();
+
+    // Output the density estimator
+    System.out.println(e);
+    
+    // Monte Carlo integration
+    double sum = 0;
+    for (int i = 0; i < 1000; i++) {
+      sum += Math.exp(e.logDensity(r.nextDouble() * 10.0 - 5.0));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / 1000);
+    
+    // Add Gaussian values into it
+    for (int i = 0; i < 1000; i++) {
+      e.addValue(0.1 * r.nextGaussian() - 3, 1);
+      e.addValue(r.nextGaussian() * 0.25, 3);
+    }
+
+    // Monte Carlo integration
+    sum = 0;
+    int points = 10000;
+    for (int i = 0; i < points; i++) {
+      double value = r.nextDouble() * 10.0 - 5.0;
+      sum += Math.exp(e.logDensity(value));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / points);
+
+    // Check interval estimates
+    double[][] Intervals = e.predictIntervals(0.9);
+    
+    System.out.println("Printing kernel intervals ---------------------");
+    
+    for (int k = 0; k < Intervals.length; k++) {
+      System.out.println("Left: " + Intervals[k][0] + "\t Right: " + Intervals[k][1]);
+    }
+    
+    System.out.println("Finished kernel printing intervals ---------------------");
+
+    double Covered = 0;
+    for (int i = 0; i < 1000; i++) {
+      double val = -1;
+      if (r.nextDouble() < 0.25) {
+        val = 0.1 * r.nextGaussian() - 3.0;
+      } else {
+        val = r.nextGaussian() * 0.25;
+      }
+      for (int k = 0; k < Intervals.length; k++) {
+        if (val >= Intervals[k][0] && val <= Intervals[k][1]) {
+          Covered++;
+          break;
+        }
+      }
+    }
+    System.out.println("Coverage at 0.9 level for kernel intervals: " + Covered / 1000);
+
+    // Compare performance to normal estimator on normally distributed data
+    UnivariateKernelEstimator eKernel = new UnivariateKernelEstimator();
+    UnivariateNormalEstimator eNormal = new UnivariateNormalEstimator();
+
+    for (int j = 1; j < 5; j++) {
+      double numTrain = Math.pow(10, j);
+      System.out.println("Number of training cases: " +
+                         numTrain); 
+
+      // Add training cases
+      for (int i = 0; i < numTrain; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        eKernel.addValue(val, 1);
+        eNormal.addValue(val, 1);
+      }
+
+      // Monte Carlo integration
+      sum = 0;
+      points = 10000;
+      for (int i = 0; i < points; i++) {
+        double value = r.nextDouble() * 20.0 - 10.0;
+        sum += Math.exp(eKernel.logDensity(value));
+      }
+      System.out.println("Approximate integral for kernel estimator: " + 20.0 * sum / points);
+
+      // Evaluate estimators
+      double loglikelihoodKernel = 0, loglikelihoodNormal = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        loglikelihoodKernel += eKernel.logDensity(val);
+        loglikelihoodNormal += eNormal.logDensity(val);
+      }
+      System.out.println("Loglikelihood for kernel estimator: " +
+                         loglikelihoodKernel / 1000);
+      System.out.println("Loglikelihood for normal estimator: " +
+                         loglikelihoodNormal / 1000);
+
+      // Check interval estimates
+      double[][] kernelIntervals = eKernel.predictIntervals(0.95);
+      double[][] normalIntervals = eNormal.predictIntervals(0.95);
+
+      System.out.println("Printing kernel intervals ---------------------");
+      
+      for (int k = 0; k < kernelIntervals.length; k++) {
+        System.out.println("Left: " + kernelIntervals[k][0] + "\t Right: " + kernelIntervals[k][1]);
+      }
+
+      System.out.println("Finished kernel printing intervals ---------------------");
+
+      System.out.println("Printing normal intervals ---------------------");
+      
+      for (int k = 0; k < normalIntervals.length; k++) {
+        System.out.println("Left: " + normalIntervals[k][0] + "\t Right: " + normalIntervals[k][1]);
+      }
+
+      System.out.println("Finished normal printing intervals ---------------------");
+ 
+      double kernelCovered = 0;
+      double normalCovered = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        for (int k = 0; k < kernelIntervals.length; k++) {
+          if (val >= kernelIntervals[k][0] && val <= kernelIntervals[k][1]) {
+            kernelCovered++;
+            break;
+          }
+        }
+        for (int k = 0; k < normalIntervals.length; k++) {
+          if (val >= normalIntervals[k][0] && val <= normalIntervals[k][1]) {
+            normalCovered++;
+            break;
+          }
+        }
+      }
+      System.out.println("Coverage at 0.95 level for kernel intervals: " + kernelCovered / 1000);
+      System.out.println("Coverage at 0.95 level for normal intervals: " + normalCovered / 1000);
+      
+      kernelIntervals = eKernel.predictIntervals(0.8);
+      normalIntervals = eNormal.predictIntervals(0.8);
+      kernelCovered = 0;
+      normalCovered = 0;
+      for (int i = 0; i < 1000; i++) {
+        double val = r.nextGaussian() * 1.5 + 0.5;
+        for (int k = 0; k < kernelIntervals.length; k++) {
+          if (val >= kernelIntervals[k][0] && val <= kernelIntervals[k][1]) {
+            kernelCovered++;
+            break;
+          }
+        }
+        for (int k = 0; k < normalIntervals.length; k++) {
+          if (val >= normalIntervals[k][0] && val <= normalIntervals[k][1]) {
+            normalCovered++;
+            break;
+          }
+        }
+      }
+      System.out.println("Coverage at 0.8 level for kernel intervals: " + kernelCovered / 1000);
+      System.out.println("Coverage at 0.8 level for normal intervals: " + normalCovered / 1000);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateNormalEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateNormalEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateNormalEstimator.java	(revision 29)
@@ -0,0 +1,238 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateNormalEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+import java.util.Random;
+
+import weka.core.Statistics;
+
+/**
+ * Simple weighted normal density estimator.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5680 $
+ */
+public class UnivariateNormalEstimator implements UnivariateDensityEstimator,
+                                                  UnivariateIntervalEstimator {
+
+  /** The weighted sum of values */
+  protected double m_WeightedSum = 0;
+
+  /** The weighted sum of squared values */
+  protected double m_WeightedSumSquared = 0;
+
+  /** The weight of the values collected so far */
+  protected double m_SumOfWeights = 0;
+
+  /** The mean value (only updated when needed) */
+  protected double m_Mean = 0;
+
+  /** The variance (only updated when needed) */
+  protected double m_Variance = Double.MAX_VALUE;
+
+  /** The minimum allowed value of the variance (default: 1.0E-6 * 1.0E-6) */
+  protected double m_MinVar = 1.0E-6 * 1.0E-6;
+
+  /** Constant for Gaussian density */
+  public static final double CONST = Math.log(2 * Math.PI);
+
+  /**
+   * Adds a value to the density estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  public void addValue(double value, double weight) {
+
+    m_WeightedSum += value * weight;
+    m_WeightedSumSquared += value * value * weight;
+    m_SumOfWeights += weight;
+  }
+
+  /**
+   * Updates mean and variance based on sufficient statistics.
+   * Variance is set to m_MinVar if it becomes smaller than that
+   * value. It is set to Double.MAX_VALUE if the sum of weights is
+   * zero.
+   */
+  protected void updateMeanAndVariance() {
+    
+    // Compute mean
+    m_Mean = 0;
+    if (m_SumOfWeights > 0) {
+      m_Mean = m_WeightedSum / m_SumOfWeights;
+    }
+
+    // Compute variance
+    m_Variance = Double.MAX_VALUE;
+    if (m_SumOfWeights > 0) {
+      m_Variance = m_WeightedSumSquared / m_SumOfWeights - m_Mean * m_Mean; 
+    }
+
+    // Hack for case where variance is 0
+    if (m_Variance <= m_MinVar) {
+      m_Variance = m_MinVar;
+    }
+  }
+
+  /**
+   * Returns the interval for the given confidence value. 
+   * 
+   * @param conf the confidence value in the interval [0, 1]
+   * @return the interval
+   */
+  public double[][] predictIntervals(double conf) {
+    
+    updateMeanAndVariance();
+
+    double val = Statistics.normalInverse(1.0 - (1.0 - conf) / 2.0);
+
+    double[][] arr = new double[1][2];
+    arr[0][1] = m_Mean + val * Math.sqrt(m_Variance);
+    arr[0][0] = m_Mean - val * Math.sqrt(m_Variance);
+
+    return arr;
+  }
+
+  /**
+   * Returns the natural logarithm of the density estimate at the given
+   * point.
+   *
+   * @param value the value at which to evaluate
+   * @return the natural logarithm of the density estimate at the given
+   * value
+   */
+  public double logDensity(double value) {
+    
+    updateMeanAndVariance();
+
+    // Return natural logarithm of density
+    double val = -0.5 * (CONST + Math.log(m_Variance) + 
+                         (value - m_Mean) * (value - m_Mean) / m_Variance); 
+
+    return val;
+  }
+
+  /**
+   * Returns textual description of this estimator.
+   */
+  public String toString() {
+
+    updateMeanAndVariance();
+
+    return "Mean: " + m_Mean + "\t" + "Variance: " + m_Variance;
+  }
+
+  /**
+   * Main method, used for testing this class.
+   */
+  public static void main(String[] args) {
+
+    // Get random number generator initialized by system
+    Random r = new Random();
+
+    // Create density estimator
+    UnivariateNormalEstimator e = new UnivariateNormalEstimator();
+
+    // Output the density estimator
+    System.out.println(e);
+
+    // Monte Carlo integration
+    double sum = 0;
+    for (int i = 0; i < 100000; i++) {
+      sum += Math.exp(e.logDensity(r.nextDouble() * 10.0 - 5.0));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / 100000);
+
+    // Add Gaussian values into it
+    for (int i = 0; i < 100000; i++) {
+      e.addValue(r.nextGaussian(), 1);
+      e.addValue(r.nextGaussian() * 2.0, 3);
+    }
+
+    // Output the density estimator
+    System.out.println(e);
+
+    // Monte Carlo integration
+    sum = 0;
+    for (int i = 0; i < 100000; i++) {
+      sum += Math.exp(e.logDensity(r.nextDouble() * 10.0 - 5.0));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / 100000);
+
+    // Create density estimator
+    e = new UnivariateNormalEstimator();
+
+    // Add Gaussian values into it
+    for (int i = 0; i < 100000; i++) {
+      e.addValue(r.nextGaussian(), 1);
+      e.addValue(r.nextGaussian() * 2.0, 1);
+      e.addValue(r.nextGaussian() * 2.0, 1);
+      e.addValue(r.nextGaussian() * 2.0, 1);
+    }
+
+    // Output the density estimator
+    System.out.println(e);
+
+    // Monte Carlo integration
+    sum = 0;
+    for (int i = 0; i < 100000; i++) {
+      sum += Math.exp(e.logDensity(r.nextDouble() * 10.0 - 5.0));
+    }
+    System.out.println("Approximate integral: " + 10.0 * sum / 100000);
+
+    // Create density estimator
+    e = new UnivariateNormalEstimator();
+
+    // Add Gaussian values into it
+    for (int i = 0; i < 100000; i++) {
+      e.addValue(r.nextGaussian() * 5.0 + 3.0 , 1);
+    }
+
+    // Output the density estimator
+    System.out.println(e);
+
+    // Check interval estimates
+    double[][] intervals = e.predictIntervals(0.95);
+    System.out.println("Lower: " + intervals[0][0] + " Upper: " + intervals[0][1]);
+    double covered = 0;
+    for (int i = 0; i < 100000; i++) {
+      double val = r.nextGaussian() * 5.0 + 3.0;
+      if (val >= intervals[0][0] && val <= intervals[0][1]) {
+        covered++;
+      }
+    }
+    System.out.println("Coverage: " + covered / 100000);
+
+    intervals = e.predictIntervals(0.8);
+    System.out.println("Lower: " + intervals[0][0] + " Upper: " + intervals[0][1]);
+    covered = 0;
+    for (int i = 0; i < 100000; i++) {
+      double val = r.nextGaussian() * 5.0 + 3.0;
+      if (val >= intervals[0][0] && val <= intervals[0][1]) {
+        covered++;
+      }
+    }
+    System.out.println("Coverage: " + covered / 100000);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/estimators/UnivariateQuantileEstimator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/estimators/UnivariateQuantileEstimator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/estimators/UnivariateQuantileEstimator.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnivariateQuantileEstimator.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.estimators;
+
+/**
+ * Interface that can be implemented by simple weighted univariate
+ * quantile estimators.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5924 $
+ */
+public interface UnivariateQuantileEstimator {
+
+  /**
+   * Adds a value to the interval estimator.
+   *
+   * @param value the value to add
+   * @param weight the weight of the value
+   */
+  void addValue(double value, double weight);
+
+  /**
+   * Returns the quantile for the given percentage
+   *
+   * @param value the value at which to evaluate
+   * @return the quantile
+   */
+  double predictQuantile(double quantile);
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/AveragingResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/AveragingResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/AveragingResultProducer.java	(revision 29)
@@ -0,0 +1,1169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AveragingResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Takes the results from a ResultProducer and submits the average to the result listener. Normally used with a CrossValidationResultProducer to perform n x m fold cross validation. For non-numeric result fields, the first value is used.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;field name&gt;
+ *  The name of the field to average over.
+ *  (default "Fold")</pre>
+ * 
+ * <pre> -X &lt;num results&gt;
+ *  The number of results expected per average.
+ *  (default 10)</pre>
+ * 
+ * <pre> -S
+ *  Calculate standard deviations.
+ *  (default only averages)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a ResultProducer.
+ *  eg: weka.experiment.CrossValidationResultProducer</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+ * </pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  The number of folds to use for the cross-validation.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * All options after -- will be passed to the result producer.
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.18 $
+ */
+public class AveragingResultProducer 
+  implements ResultListener, ResultProducer, OptionHandler,
+	     AdditionalMeasureProducer, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 2551284958501991352L;
+  
+  /** The dataset of interest */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The ResultProducer used to generate results */
+  protected ResultProducer m_ResultProducer
+    = new CrossValidationResultProducer();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+  
+  /** The number of results expected to average over for each run */
+  protected int m_ExpectedResultsPerAverage = 10;
+
+  /** True if standard deviation fields should be produced */
+  protected boolean m_CalculateStdDevs;
+    
+  /**
+   * The name of the field that will contain the number of results
+   * averaged over.
+   */
+  protected String m_CountFieldName = "Num_" + CrossValidationResultProducer
+    .FOLD_FIELD_NAME;
+
+  /** The name of the key field to average over */
+  protected String m_KeyFieldName = CrossValidationResultProducer
+    .FOLD_FIELD_NAME;
+
+  /** The index of the field to average over in the resultproducers key */
+  protected int m_KeyIndex = -1;
+
+  /** Collects the keys from a single run */
+  protected FastVector m_Keys = new FastVector();
+  
+  /** Collects the results from a single run */
+  protected FastVector m_Results = new FastVector();
+
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Takes the results from a ResultProducer "
+      +"and submits the average to the result listener. Normally used with "
+      +"a CrossValidationResultProducer to perform n x m fold cross "
+      +"validation. For non-numeric result fields, the first value is used.";
+  }
+
+  /**
+   * Scans through the key field names of the result producer to find
+   * the index of the key field to average over. Sets the value of
+   * m_KeyIndex to the index, or -1 if no matching key field was found.
+   *
+   * @return the index of the key field to average over
+   */
+  protected int findKeyIndex() {
+
+    m_KeyIndex = -1;
+    try {
+      if (m_ResultProducer != null) {
+	String [] keyNames = m_ResultProducer.getKeyNames();
+	for (int i = 0; i < keyNames.length; i++) {
+	  if (keyNames[i].equals(m_KeyFieldName)) {
+	    m_KeyIndex = i;
+	    break;
+	  }
+	}
+      }
+    } catch (Exception ex) {
+    }
+    return m_KeyIndex;
+  }
+
+  /**
+   * Determines if there are any constraints (imposed by the
+   * destination) on the result columns to be produced by
+   * resultProducers. Null should be returned if there are NO
+   * constraints, otherwise a list of column names should be
+   * returned as an array of Strings.
+   * @param rp the ResultProducer to which the constraints will apply
+   * @return an array of column names to which resutltProducer's
+   * results will be restricted.
+   * @throws Exception if constraints can't be determined
+   */
+  public String [] determineColumnConstraints(ResultProducer rp) 
+    throws Exception {
+    return null;
+  }
+
+  /**
+   * Simulates a run to collect the keys the sub-resultproducer could
+   * generate. Does some checking on the keys and determines the 
+   * template key.
+   *
+   * @param run the run number
+   * @return a template key (null for the field being averaged)
+   * @throws Exception if an error occurs
+   */
+  protected Object [] determineTemplate(int run) throws Exception {
+
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+    m_ResultProducer.setInstances(m_Instances);
+
+    // Clear the collected results
+    m_Keys.removeAllElements();
+    m_Results.removeAllElements();
+    
+    m_ResultProducer.doRunKeys(run);
+    checkForMultipleDifferences();
+
+    Object [] template = (Object [])((Object [])m_Keys.elementAt(0)).clone();
+    template[m_KeyIndex] = null;
+    // Check for duplicate keys
+    checkForDuplicateKeys(template);
+
+    return template;
+  }
+
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+
+    // Generate the template
+    Object [] template = determineTemplate(run);
+    String [] newKey = new String [template.length - 1];
+    System.arraycopy(template, 0, newKey, 0, m_KeyIndex);
+    System.arraycopy(template, m_KeyIndex + 1,
+		     newKey, m_KeyIndex,
+		     template.length - m_KeyIndex - 1);
+    m_ResultListener.acceptResult(this, newKey, null);      
+  }
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    // Generate the key and ask whether the result is required
+    Object [] template = determineTemplate(run);
+    String [] newKey = new String [template.length - 1];
+    System.arraycopy(template, 0, newKey, 0, m_KeyIndex);
+    System.arraycopy(template, m_KeyIndex + 1,
+		     newKey, m_KeyIndex,
+		     template.length - m_KeyIndex - 1);
+
+    if (m_ResultListener.isResultRequired(this, newKey)) {
+      // Clear the collected keys
+      m_Keys.removeAllElements();
+      m_Results.removeAllElements();
+      
+      m_ResultProducer.doRun(run);
+      
+      // Average the results collected
+      //System.err.println("Number of results collected: " + m_Keys.size());
+      
+      // Check that the keys only differ on the selected key field
+      checkForMultipleDifferences();
+      
+      template = (Object [])((Object [])m_Keys.elementAt(0)).clone();
+      template[m_KeyIndex] = null;
+      // Check for duplicate keys
+      checkForDuplicateKeys(template);
+      // Calculate the average and submit it if necessary
+      doAverageResult(template);
+    }
+  }
+
+  
+  /**
+   * Compares a key to a template to see whether they match. Null
+   * fields in the template are ignored in the matching.
+   *
+   * @param template the template to match against
+   * @param test the key to test
+   * @return true if the test key matches the template on all non-null template
+   * fields
+   */
+  protected boolean matchesTemplate(Object [] template, Object [] test) {
+    
+    if (template.length != test.length) {
+      return false;
+    }
+    for (int i = 0; i < test.length; i++) {
+      if ((template[i] != null) && (!template[i].equals(test[i]))) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  /**
+   * Asks the resultlistener whether an average result is required, and
+   * if so, calculates it.
+   *
+   * @param template the template to match keys against when calculating the
+   * average
+   * @throws Exception if an error occurs
+   */
+  protected void doAverageResult(Object [] template) throws Exception {
+
+    // Generate the key and ask whether the result is required
+    String [] newKey = new String [template.length - 1];
+    System.arraycopy(template, 0, newKey, 0, m_KeyIndex);
+    System.arraycopy(template, m_KeyIndex + 1,
+		     newKey, m_KeyIndex,
+		     template.length - m_KeyIndex - 1);
+    if (m_ResultListener.isResultRequired(this, newKey)) {
+      Object [] resultTypes = m_ResultProducer.getResultTypes();
+      Stats [] stats = new Stats [resultTypes.length];
+      for (int i = 0; i < stats.length; i++) {
+	stats[i] = new Stats();
+      }
+      Object [] result = getResultTypes();
+      int numMatches = 0;
+      for (int i = 0; i < m_Keys.size(); i++) {
+	Object [] currentKey = (Object [])m_Keys.elementAt(i);
+	// Skip non-matching keys
+	if (!matchesTemplate(template, currentKey)) {
+	  continue;
+	}
+	// Add the results to the stats accumulator
+	Object [] currentResult = (Object [])m_Results.elementAt(i);
+	numMatches++;
+	for (int j = 0; j < resultTypes.length; j++) {
+	  if (resultTypes[j] instanceof Double) {
+	    if (currentResult[j] == null) {
+
+	      // set the stats object for this result to null---
+	      // more than likely this is an additional measure field
+	      // not supported by the low level split evaluator
+	      if (stats[j] != null) {
+		stats[j] = null;
+	      }
+	      
+	      /* throw new Exception("Null numeric result field found:\n"
+		 + DatabaseUtils.arrayToString(currentKey)
+		 + " -- "
+		 + DatabaseUtils
+		 .arrayToString(currentResult)); */
+	    }
+	    if (stats[j] != null) {
+	      double currentVal = ((Double)currentResult[j]).doubleValue();
+	      stats[j].add(currentVal);
+	    }
+	  }
+	}
+      }
+      if (numMatches != m_ExpectedResultsPerAverage) {
+	throw new Exception("Expected " + m_ExpectedResultsPerAverage
+			    + " results matching key \""
+			    + DatabaseUtils.arrayToString(template)
+			    + "\" but got "
+			    + numMatches);
+      }
+      result[0] = new Double(numMatches);
+      Object [] currentResult = (Object [])m_Results.elementAt(0);
+      int k = 1;
+      for (int j = 0; j < resultTypes.length; j++) {
+	if (resultTypes[j] instanceof Double) {
+	  if (stats[j] != null) {
+	    stats[j].calculateDerived();
+	    result[k++] = new Double(stats[j].mean);
+	  } else {
+	    result[k++] = null;
+	  }
+	  if (getCalculateStdDevs()) {
+	    if (stats[j] != null) {
+	      result[k++] = new Double(stats[j].stdDev);
+	    } else {
+	      result[k++] = null;
+	    }
+	  }
+	} else {
+	  result[k++] = currentResult[j];
+	}
+      }
+      m_ResultListener.acceptResult(this, newKey, result);      
+    }
+  }
+  
+  /**
+   * Checks whether any duplicate results (with respect to a key template)
+   * were received.
+   *
+   * @param template the template key.
+   * @throws Exception if duplicate results are detected
+   */
+  protected void checkForDuplicateKeys(Object [] template) throws Exception {
+
+    Hashtable hash = new Hashtable();
+    int numMatches = 0;
+    for (int i = 0; i < m_Keys.size(); i++) {
+      Object [] current = (Object [])m_Keys.elementAt(i);
+      // Skip non-matching keys
+      if (!matchesTemplate(template, current)) {
+	continue;
+      }
+      if (hash.containsKey(current[m_KeyIndex])) {
+	throw new Exception("Duplicate result received:"
+			    + DatabaseUtils.arrayToString(current));
+      }
+      numMatches++;
+      hash.put(current[m_KeyIndex], current[m_KeyIndex]);
+    }
+    if (numMatches != m_ExpectedResultsPerAverage) {
+      throw new Exception("Expected " + m_ExpectedResultsPerAverage
+			  + " results matching key \""
+			  + DatabaseUtils.arrayToString(template)
+			  + "\" but got "
+			  + numMatches);
+    }
+  }
+  
+  /**
+   * Checks that the keys for a run only differ in one key field. If they
+   * differ in more than one field, a more sophisticated averager will submit
+   * multiple results - for now an exception is thrown. Currently assumes that
+   * the most differences will be shown between the first and last
+   * result received.
+   *
+   * @throws Exception if the keys differ on fields other than the
+   * key averaging field
+   */
+  protected void checkForMultipleDifferences() throws Exception {
+    
+    Object [] firstKey = (Object [])m_Keys.elementAt(0);
+    Object [] lastKey = (Object [])m_Keys.elementAt(m_Keys.size() - 1);
+    /*
+    System.err.println("First key:" +  DatabaseUtils.arrayToString(firstKey));
+    System.err.println("Last key :" + DatabaseUtils.arrayToString(lastKey));
+    */
+    for (int i = 0; i < firstKey.length; i++) {
+      if ((i != m_KeyIndex) && !firstKey[i].equals(lastKey[i])) {
+	throw new Exception("Keys differ on fields other than \""
+			    + m_KeyFieldName
+			    + "\" -- time to implement multiple averaging");
+      }
+    }
+  }
+  
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    m_ResultListener.preProcess(this);
+  }
+
+  /**
+   * Prepare to generate results. The ResultProducer should call
+   * preProcess(this) on the ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+    
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+    findKeyIndex();
+    if (m_KeyIndex == -1) {
+      throw new Exception("No key field called " + m_KeyFieldName
+			  + " produced by "
+			  + m_ResultProducer.getClass().getName());
+    }
+    m_ResultProducer.preProcess();
+  }
+  
+  /**
+   * When this method is called, it indicates that no more results
+   * will be sent that need to be grouped together in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @throws Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+
+    m_ResultListener.postProcess(this);
+  }
+
+  /**
+   * When this method is called, it indicates that no more requests to
+   * generate results for the current experiment will be sent. The
+   * ResultProducer should call preProcess(this) on the
+   * ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultProducer.postProcess();
+  }
+  
+  /**
+   * Accepts results from a ResultProducer.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @param result the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if the result could not be accepted.
+   */
+  public void acceptResult(ResultProducer rp, Object [] key, Object [] result)
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    m_Keys.addElement(key);
+    m_Results.addElement(result);
+  }
+
+  /**
+   * Determines whether the results for a specified key must be
+   * generated.
+   *
+   * @param rp the ResultProducer wanting to generate the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @return true if the result should be generated
+   * @throws Exception if it could not be determined if the result 
+   * is needed.
+   */
+  public boolean isResultRequired(ResultProducer rp, Object [] key) 
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    return true;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if key names cannot be generated
+   */
+  public String [] getKeyNames() throws Exception {
+
+    if (m_KeyIndex == -1) {
+      throw new Exception("No key field called " + m_KeyFieldName
+			  + " produced by "
+			  + m_ResultProducer.getClass().getName());
+    }
+    String [] keyNames = m_ResultProducer.getKeyNames();
+    String [] newKeyNames = new String [keyNames.length - 1];
+    System.arraycopy(keyNames, 0, newKeyNames, 0, m_KeyIndex);
+    System.arraycopy(keyNames, m_KeyIndex + 1,
+		     newKeyNames, m_KeyIndex,
+		     keyNames.length - m_KeyIndex - 1);
+    return newKeyNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if the key types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public Object [] getKeyTypes() throws Exception {
+
+    if (m_KeyIndex == -1) {
+      throw new Exception("No key field called " + m_KeyFieldName
+			  + " produced by "
+			  + m_ResultProducer.getClass().getName());
+    }
+    Object [] keyTypes = m_ResultProducer.getKeyTypes();
+    // Find and remove the key field that is being averaged over
+    Object [] newKeyTypes = new String [keyTypes.length - 1];
+    System.arraycopy(keyTypes, 0, newKeyTypes, 0, m_KeyIndex);
+    System.arraycopy(keyTypes, m_KeyIndex + 1,
+		     newKeyTypes, m_KeyIndex,
+		     keyTypes.length - m_KeyIndex - 1);
+    return newKeyTypes;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * A new result field is added for the number of results used to
+   * produce each average.
+   * If only averages are being produced the names are not altered, if
+   * standard deviations are produced then "Dev_" and "Avg_" are prepended
+   * to each result deviation and average field respectively.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if the result names could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public String [] getResultNames() throws Exception {
+
+    String [] resultNames = m_ResultProducer.getResultNames();
+    // Add in the names of our extra Result fields
+    if (getCalculateStdDevs()) {
+      Object [] resultTypes = m_ResultProducer.getResultTypes();
+      int numNumeric = 0;
+      for (int i = 0; i < resultTypes.length; i++) {
+	if (resultTypes[i] instanceof Double) {
+	  numNumeric++;
+	}
+      }
+      String [] newResultNames = new String [resultNames.length +
+					    1 + numNumeric];
+      newResultNames[0] = m_CountFieldName;
+      int j = 1;
+      for (int i = 0; i < resultNames.length; i++) {
+	newResultNames[j++] = "Avg_" + resultNames[i];
+	if (resultTypes[i] instanceof Double) {
+	  newResultNames[j++] = "Dev_" + resultNames[i];
+	}
+      }
+      return newResultNames;
+    } else {
+      String [] newResultNames = new String [resultNames.length + 1];
+      newResultNames[0] = m_CountFieldName;
+      System.arraycopy(resultNames, 0, newResultNames, 1, resultNames.length);
+      return newResultNames;
+    }
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if the result types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public Object [] getResultTypes() throws Exception {
+
+    Object [] resultTypes = m_ResultProducer.getResultTypes();
+    // Add in the types of our extra Result fields
+    if (getCalculateStdDevs()) {
+      int numNumeric = 0;
+      for (int i = 0; i < resultTypes.length; i++) {
+	if (resultTypes[i] instanceof Double) {
+	  numNumeric++;
+	}
+      }
+      Object [] newResultTypes = new Object [resultTypes.length +
+					    1 + numNumeric];
+      newResultTypes[0] = new Double(0);
+      int j = 1;
+      for (int i = 0; i < resultTypes.length; i++) {
+	newResultTypes[j++] = resultTypes[i];
+	if (resultTypes[i] instanceof Double) {
+	  newResultTypes[j++] = new Double(0);
+	}
+      }
+      return newResultTypes;
+    } else {
+      Object [] newResultTypes = new Object [resultTypes.length + 1];
+      newResultTypes[0] = new Double(0);
+      System.arraycopy(resultTypes, 0, newResultTypes, 1, resultTypes.length);
+      return newResultTypes;
+    }
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  public String getCompatibilityState() {
+
+    String result = // "-F " + Utils.quote(getKeyFieldName())
+      " -X " + getExpectedResultsPerAverage() + " ";
+    if (getCalculateStdDevs()) {
+      result += "-S ";
+    }
+    if (m_ResultProducer == null) {
+      result += "<null ResultProducer>";
+    } else {
+      result += "-W " + m_ResultProducer.getClass().getName();
+    }
+    result  += " -- " + m_ResultProducer.getCompatibilityState();
+    return result.trim();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	     "\tThe name of the field to average over.\n"
+	      +"\t(default \"Fold\")", 
+	     "F", 1, 
+	     "-F <field name>"));
+    newVector.addElement(new Option(
+	     "\tThe number of results expected per average.\n"
+	      +"\t(default 10)", 
+	     "X", 1, 
+	     "-X <num results>"));
+    newVector.addElement(new Option(
+	     "\tCalculate standard deviations.\n"
+	      +"\t(default only averages)", 
+	     "S", 0, 
+	     "-S"));
+    newVector.addElement(new Option(
+	     "\tThe full class name of a ResultProducer.\n"
+	      +"\teg: weka.experiment.CrossValidationResultProducer", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    if ((m_ResultProducer != null) &&
+	(m_ResultProducer instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to result producer "
+	     + m_ResultProducer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_ResultProducer).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;field name&gt;
+   *  The name of the field to average over.
+   *  (default "Fold")</pre>
+   * 
+   * <pre> -X &lt;num results&gt;
+   *  The number of results expected per average.
+   *  (default 10)</pre>
+   * 
+   * <pre> -S
+   *  Calculate standard deviations.
+   *  (default only averages)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a ResultProducer.
+   *  eg: weka.experiment.CrossValidationResultProducer</pre>
+   * 
+   * <pre> 
+   * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+   * </pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  The number of folds to use for the cross-validation.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the result producer.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String keyFieldName = Utils.getOption('F', options);
+    if (keyFieldName.length() != 0) {
+      setKeyFieldName(keyFieldName);
+    } else {
+      setKeyFieldName(CrossValidationResultProducer.FOLD_FIELD_NAME);
+    }
+
+    String numResults = Utils.getOption('X', options);
+    if (numResults.length() != 0) {
+      setExpectedResultsPerAverage(Integer.parseInt(numResults));
+    } else {
+      setExpectedResultsPerAverage(10);
+    }
+
+    setCalculateStdDevs(Utils.getFlag('S', options));
+    
+    String rpName = Utils.getOption('W', options);
+    if (rpName.length() == 0) {
+      throw new Exception("A ResultProducer must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // RP.
+    setResultProducer((ResultProducer)Utils.forName(
+		      ResultProducer.class,
+		      rpName,
+		      null));
+    if (getResultProducer() instanceof OptionHandler) {
+      ((OptionHandler) getResultProducer())
+	.setOptions(Utils.partitionOptions(options));
+    }
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] seOptions = new String [0];
+    if ((m_ResultProducer != null) && 
+	(m_ResultProducer instanceof OptionHandler)) {
+      seOptions = ((OptionHandler)m_ResultProducer).getOptions();
+    }
+    
+    String [] options = new String [seOptions.length + 8];
+    int current = 0;
+
+    options[current++] = "-F";
+    options[current++] = "" + getKeyFieldName();
+    options[current++] = "-X";
+    options[current++] = "" + getExpectedResultsPerAverage();
+    if (getCalculateStdDevs()) {
+      options[current++] = "-S";
+    }
+    if (getResultProducer() != null) {
+      options[current++] = "-W";
+      options[current++] = getResultProducer().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(seOptions, 0, options, current, 
+		     seOptions.length);
+    current += seOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current resultProducer) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_ResultProducer != null) {
+      System.err.println("AveragingResultProducer: setting additional "
+			 +"measures for "
+			 +"ResultProducer");
+      m_ResultProducer.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the result producer
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_ResultProducer).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_ResultProducer).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("AveragingResultProducer: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_ResultProducer.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    
+    m_Instances = instances;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String calculateStdDevsTipText() {
+    return "Record standard deviations for each run.";
+  }
+
+  /**
+   * Get the value of CalculateStdDevs.
+   *
+   * @return Value of CalculateStdDevs.
+   */
+  public boolean getCalculateStdDevs() {
+    
+    return m_CalculateStdDevs;
+  }
+  
+  /**
+   * Set the value of CalculateStdDevs.
+   *
+   * @param newCalculateStdDevs Value to assign to CalculateStdDevs.
+   */
+  public void setCalculateStdDevs(boolean newCalculateStdDevs) {
+    
+    m_CalculateStdDevs = newCalculateStdDevs;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String expectedResultsPerAverageTipText() {
+    return "Set the expected number of results to average per run. "
+      +"For example if a CrossValidationResultProducer is being used "
+      +"(with the number of folds set to 10), then the expected number "
+      +"of results per run is 10.";
+  }
+
+  /**
+   * Get the value of ExpectedResultsPerAverage.
+   *
+   * @return Value of ExpectedResultsPerAverage.
+   */
+  public int getExpectedResultsPerAverage() {
+    
+    return m_ExpectedResultsPerAverage;
+  }
+  
+  /**
+   * Set the value of ExpectedResultsPerAverage.
+   *
+   * @param newExpectedResultsPerAverage Value to assign to
+   * ExpectedResultsPerAverage.
+   */
+  public void setExpectedResultsPerAverage(int newExpectedResultsPerAverage) {
+    
+    m_ExpectedResultsPerAverage = newExpectedResultsPerAverage;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String keyFieldNameTipText() {
+    return "Set the field name that will be unique for a run.";
+  }
+
+  /**
+   * Get the value of KeyFieldName.
+   *
+   * @return Value of KeyFieldName.
+   */
+  public String getKeyFieldName() {
+    
+    return m_KeyFieldName;
+  }
+  
+  /**
+   * Set the value of KeyFieldName.
+   *
+   * @param newKeyFieldName Value to assign to KeyFieldName.
+   */
+  public void setKeyFieldName(String newKeyFieldName) {
+    
+    m_KeyFieldName = newKeyFieldName;
+    m_CountFieldName = "Num_" + m_KeyFieldName;
+    findKeyIndex();
+  }
+  
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+
+    m_ResultListener = listener;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String resultProducerTipText() {
+    return "Set the resultProducer for which results are to be averaged.";
+  }
+
+  /**
+   * Get the ResultProducer.
+   *
+   * @return the ResultProducer.
+   */
+  public ResultProducer getResultProducer() {
+    
+    return m_ResultProducer;
+  }
+  
+  /**
+   * Set the ResultProducer.
+   *
+   * @param newResultProducer new ResultProducer to use.
+   */
+  public void setResultProducer(ResultProducer newResultProducer) {
+
+    m_ResultProducer = newResultProducer;
+    m_ResultProducer.setResultListener(this);
+    findKeyIndex();
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "AveragingResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.18 $");
+  }
+} // AveragingResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/CSVResultListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/CSVResultListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/CSVResultListener.java	(revision 29)
@@ -0,0 +1,364 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CSVResultListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Takes results from a result producer and assembles them into comma separated value form.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -O &lt;file name&gt;
+ *  The filename where output will be stored. Use - for stdout.
+ *  (default temp file)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.13 $
+ */
+public class CSVResultListener 
+  implements ResultListener, OptionHandler, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -623185072785174658L;
+
+  /** The ResultProducer sending us results */
+  protected ResultProducer m_RP;
+
+  /** The destination output file, null sends to System.out */
+  protected File m_OutputFile = null;
+
+  /** The name of the output file. Empty for temporary file. */
+  protected String m_OutputFileName = "";
+
+  /** The destination for results (typically connected to the output file) */
+  protected transient PrintWriter m_Out = new PrintWriter(System.out, true);
+
+  /** 
+   * Sets temporary file.
+   */
+  public CSVResultListener() {
+
+    File resultsFile;
+    try {
+      resultsFile = File.createTempFile("weka_experiment", ".csv");
+      resultsFile.deleteOnExit();
+    } catch (Exception e) {
+      System.err.println("Cannot create temp file, writing to standard out.");
+      resultsFile = new File("-");
+    }
+    setOutputFile(resultsFile);
+    setOutputFileName("");
+  }
+
+  /**
+   * Returns a string describing this result listener
+   * @return a description of the result listener suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Takes results from a result producer and assembles them into "
+      +"comma separated value form.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+	     "\tThe filename where output will be stored. Use - for stdout.\n"
+	      +"\t(default temp file)", 
+	     "O", 1, 
+	     "-O <file name>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -O &lt;file name&gt;
+   *  The filename where output will be stored. Use - for stdout.
+   *  (default temp file)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String fName = Utils.getOption('O', options);
+    if (fName.length() != 0) {
+      setOutputFile(new File(fName));
+    } else {
+      File resultsFile;
+      try {
+	resultsFile = File.createTempFile("weka_experiment", null);
+	resultsFile.deleteOnExit();
+      } catch (Exception e) {
+	System.err.println("Cannot create temp file, writing to standard out.");
+	resultsFile = new File("-");
+      }
+      setOutputFile(resultsFile);
+      setOutputFileName("");
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [2];
+    int current = 0;
+
+    options[current++] = "-O";
+    options[current++] = getOutputFile().getName();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String outputFileTipText() {
+    return "File to save to. Use '-' to write to standard out.";
+  }
+
+  /**
+   * Get the value of OutputFile.
+   *
+   * @return Value of OutputFile.
+   */
+  public File getOutputFile() {
+    
+    return m_OutputFile;
+  }
+  
+  /**
+   * Set the value of OutputFile. Also sets the
+   * OutputFileName.
+   *
+   * @param newOutputFile Value to assign to OutputFile.
+   */
+  public void setOutputFile(File newOutputFile) {
+    
+    m_OutputFile = newOutputFile;
+    setOutputFileName(newOutputFile.getName());
+  }
+
+  /**
+   * Get the value of OutputFileName.
+   *
+   * @return Value of OutputFile.
+   */
+  public String outputFileName() {
+    
+    return m_OutputFileName;
+  }
+
+  /**
+   * Set the value of OutputFileName. Must be used
+   * AFTER setOutputFile.
+   *
+   * @param name the name of OutputFile.
+   */
+  public void setOutputFileName(String name) {
+    
+    m_OutputFileName = name;
+  }
+  
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    m_RP = rp;
+    if ((m_OutputFile == null) || (m_OutputFile.getName().equals("-"))) {
+      m_Out = new PrintWriter(System.out, true);
+    } else {
+      m_Out = new PrintWriter(
+	      new BufferedOutputStream(
+	      new FileOutputStream(m_OutputFile)), true);
+    }
+    printResultNames(m_RP);
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more results will be sent that need to be grouped together
+   * in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @throws Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+    
+    if (!(m_OutputFile == null) && !(m_OutputFile.getName().equals("-"))) {
+      m_Out.close();
+    }
+  }
+
+  /**
+   * Determines if there are any constraints (imposed by the
+   * destination) on the result columns to be produced by
+   * resultProducers. Null should be returned if there are NO
+   * constraints, otherwise a list of column names should be
+   * returned as an array of Strings.
+   * @param rp the ResultProducer to which the constraints will apply
+   * @return an array of column names to which resutltProducer's
+   * results will be restricted.
+   * @throws Exception if an error occurs.
+   */
+  public String [] determineColumnConstraints(ResultProducer rp) throws Exception {
+    return null;
+  }
+
+  /**
+   * Just prints out each result as it is received.
+   *
+   * @param rp the ResultProducer that generated the result
+   * @param key The key for the results.
+   * @param result The actual results.
+   * @throws Exception if the result could not be accepted.
+   */
+  public void acceptResult(ResultProducer rp, Object[] key, Object[] result) 
+    throws Exception {
+
+    if (m_RP != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    for (int i = 0; i < key.length; i++) {
+      if (i != 0) {
+	m_Out.print(',');
+      }
+      if (key[i] == null) {
+	m_Out.print("?");
+      } else {
+	m_Out.print(Utils.quote(key[i].toString()));
+      }
+    }
+    for (int i = 0; i < result.length; i++) {
+      m_Out.print(',');
+      if (result[i] == null) {
+	m_Out.print("?");
+      } else {
+	m_Out.print(Utils.quote(result[i].toString()));
+      }
+    }
+    m_Out.println("");
+  }
+
+  /**
+   * Always says a result is required. If this is the first call,
+   * prints out the header for the csv output.
+   *
+   * @param rp the ResultProducer wanting to generate the result
+   * @param key The key for which a result may be needed.
+   * @return true if the result should be calculated.
+   * @throws Exception if it could not be determined if the result 
+   * is needed.
+   */
+  public boolean isResultRequired(ResultProducer rp, Object[] key) 
+    throws Exception {
+
+    return true;
+  }
+
+
+  /**
+   * Prints the names of each field out as the first row of the CSV output.
+   *
+   * @param rp the ResultProducer generating our results.
+   * @throws Exception if the field names could not be determined.
+   */
+  private void printResultNames(ResultProducer rp) throws Exception {
+
+    String [] key = rp.getKeyNames();
+    for (int i = 0; i < key.length; i++) {
+      if (i != 0) {
+	m_Out.print(',');
+      }
+      if (key[i] == null) {
+	m_Out.print("?");
+      } else {
+	m_Out.print("Key_" + key[i].toString());
+      }
+    }
+    String [] result = rp.getResultNames();
+    for (int i = 0; i < result.length; i++) {
+      m_Out.print(',');
+      if (result[i] == null) {
+	m_Out.print("?");
+      } else {
+	m_Out.print(result[i].toString());
+      }
+    }
+    m_Out.println("");
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.13 $");
+  }
+} // CSVResultListener
Index: branches/MetisMQI/src/main/java/weka/experiment/ClassifierSplitEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ClassifierSplitEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ClassifierSplitEvaluator.java	(revision 29)
@@ -0,0 +1,1098 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierSplitEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.rules.ZeroR;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.Utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * A SplitEvaluator that produces results for a classification scheme on a nominal class attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * All options after -- will be passed to the classifier.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class ClassifierSplitEvaluator 
+  implements SplitEvaluator, OptionHandler, AdditionalMeasureProducer, 
+             RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8511241602760467265L;
+  
+  /** The template classifier */
+  protected Classifier m_Template = new ZeroR();
+
+  /** The classifier used for evaluation */
+  protected Classifier m_Classifier;
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** Array of booleans corresponding to the measures in m_AdditionalMeasures
+      indicating which of the AdditionalMeasures the current classifier
+      can produce */
+  protected boolean [] m_doesProduce = null;
+
+  /** The number of additional measures that need to be filled in
+      after taking into account column constraints imposed by the final
+      destination for results */
+  protected int m_numberAdditionalMeasures = 0;
+
+  /** Holds the statistics for the most recent application of the classifier */
+  protected String m_result = null;
+
+  /** The classifier options (if any) */
+  protected String m_ClassifierOptions = "";
+
+  /** The classifier version */
+  protected String m_ClassifierVersion = "";
+
+  /** The length of a key */
+  private static final int KEY_SIZE = 3;
+
+  /** The length of a result */
+  private static final int RESULT_SIZE = 30;
+
+  /** The number of IR statistics */
+  private static final int NUM_IR_STATISTICS = 14;
+  
+  /** The number of averaged IR statistics */
+  private static final int NUM_WEIGHTED_IR_STATISTICS = 8;
+  
+  /** The number of unweighted averaged IR statistics */
+  private static final int NUM_UNWEIGHTED_IR_STATISTICS = 2;
+  
+  /** Class index for information retrieval statistics (default 0) */
+  private int m_IRclass = 0;
+  
+  /** Flag for prediction and target columns output.*/
+  private boolean m_predTargetColumn = false;
+
+  /** Attribute index of instance identifier (default -1) */
+  private int m_attID = -1;
+
+  /**
+   * No args constructor.
+   */
+  public ClassifierSplitEvaluator() {
+
+    updateOptions();
+  }
+
+  /**
+   * Returns a string describing this split evaluator
+   * @return a description of the split evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return " A SplitEvaluator that produces results for a classification "
+      +"scheme on a nominal class attribute.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+	     "\tThe full class name of the classifier.\n"
+	      +"\teg: weka.classifiers.bayes.NaiveBayes", 
+	     "W", 1, 
+	     "-W <class name>"));
+    newVector.addElement(new Option(
+	     "\tThe index of the class for which IR statistics\n" +
+	     "\tare to be output. (default 1)",
+	     "C", 1, 
+	     "-C <index>"));
+    newVector.addElement(new Option(
+	     "\tThe index of an attribute to output in the\n" +
+	     "\tresults. This attribute should identify an\n" +
+             "\tinstance in order to know which instances are\n" +
+             "\tin the test set of a cross validation. if 0\n" +
+             "\tno output (default 0).",
+	     "I", 1, 
+	     "-I <index>"));
+    newVector.addElement(new Option(
+	     "\tAdd target and prediction columns to the result\n" +
+             "\tfor each fold.",
+	     "P", 0, 
+	     "-P"));
+
+    if ((m_Template != null) &&
+	(m_Template instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to classifier "
+	     + m_Template.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_Template).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the classifier.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String cName = Utils.getOption('W', options);
+    if (cName.length() == 0) {
+      throw new Exception("A classifier must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // Classifier.
+    setClassifier(AbstractClassifier.forName(cName, null));
+    if (getClassifier() instanceof OptionHandler) {
+      ((OptionHandler) getClassifier())
+	.setOptions(Utils.partitionOptions(options));
+      updateOptions();
+    }
+
+    String indexName = Utils.getOption('C', options);
+    if (indexName.length() != 0) {
+      m_IRclass = (new Integer(indexName)).intValue() - 1;
+    } else {
+      m_IRclass = 0;
+    }
+
+    String attID = Utils.getOption('I', options);
+    if (attID.length() != 0) {
+      m_attID = (new Integer(attID)).intValue() - 1;
+    } else {
+      m_attID = -1;
+    }
+    
+    m_predTargetColumn = Utils.getFlag('P', options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] classifierOptions = new String [0];
+    if ((m_Template != null) && 
+	(m_Template instanceof OptionHandler)) {
+      classifierOptions = ((OptionHandler)m_Template).getOptions();
+    }
+    
+    String [] options = new String [classifierOptions.length + 8];
+    int current = 0;
+
+    if (getClassifier() != null) {
+      options[current++] = "-W";
+      options[current++] = getClassifier().getClass().getName();
+    }
+    options[current++] = "-I"; 
+    options[current++] = "" + (m_attID + 1);
+
+    if (getPredTargetColumn()) options[current++] = "-P";
+    
+    options[current++] = "-C"; 
+    options[current++] = "" + (m_IRclass + 1);
+    options[current++] = "--";
+    
+    System.arraycopy(classifierOptions, 0, options, current, 
+		     classifierOptions.length);
+    current += classifierOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in Classifiers. This could contain many measures (of which only a
+   * subset may be produceable by the current Classifier) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures a list of method names
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    // System.err.println("ClassifierSplitEvaluator: setting additional measures");
+    m_AdditionalMeasures = additionalMeasures;
+    
+    // determine which (if any) of the additional measures this classifier
+    // can produce
+    if (m_AdditionalMeasures != null && m_AdditionalMeasures.length > 0) {
+      m_doesProduce = new boolean [m_AdditionalMeasures.length];
+
+      if (m_Template instanceof AdditionalMeasureProducer) {
+	Enumeration en = ((AdditionalMeasureProducer)m_Template).
+	  enumerateMeasures();
+	while (en.hasMoreElements()) {
+	  String mname = (String)en.nextElement();
+	  for (int j=0;j<m_AdditionalMeasures.length;j++) {
+	    if (mname.compareToIgnoreCase(m_AdditionalMeasures[j]) == 0) {
+	      m_doesProduce[j] = true;
+	    }
+	  }
+	}
+      }
+    } else {
+      m_doesProduce = null;
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the classifier
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_Template instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_Template).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_Template instanceof AdditionalMeasureProducer) {
+      if (m_Classifier == null) {
+	throw new IllegalArgumentException("ClassifierSplitEvaluator: " +
+					   "Can't return result for measure, " +
+					   "classifier has not been built yet.");
+      }
+      return ((AdditionalMeasureProducer)m_Classifier).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("ClassifierSplitEvaluator: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_Template.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+
+  /**
+   * Gets the data types of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each key column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getKeyTypes() {
+
+    Object [] keyTypes = new Object[KEY_SIZE];
+    keyTypes[0] = "";
+    keyTypes[1] = "";
+    keyTypes[2] = "";
+    return keyTypes;
+  }
+
+  /**
+   * Gets the names of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each key column
+   */
+  public String [] getKeyNames() {
+
+    String [] keyNames = new String[KEY_SIZE];
+    keyNames[0] = "Scheme";
+    keyNames[1] = "Scheme_options";
+    keyNames[2] = "Scheme_version_ID";
+    return keyNames;
+  }
+
+  /**
+   * Gets the key describing the current SplitEvaluator. For example
+   * This may contain the name of the classifier used for classifier
+   * predictive evaluation. The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array of objects containing the key.
+   */
+  public Object [] getKey(){
+
+    Object [] key = new Object[KEY_SIZE];
+    key[0] = m_Template.getClass().getName();
+    key[1] = m_ClassifierOptions;
+    key[2] = m_ClassifierVersion;
+    return key;
+  }
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run. The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    int overall_length = RESULT_SIZE+addm;
+    overall_length += NUM_IR_STATISTICS;
+    overall_length += NUM_WEIGHTED_IR_STATISTICS;
+    overall_length += NUM_UNWEIGHTED_IR_STATISTICS;
+    if (getAttributeID() >= 0) overall_length += 1;
+    if (getPredTargetColumn()) overall_length += 2;
+    Object [] resultTypes = new Object[overall_length];
+    Double doub = new Double(0);
+    int current = 0;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // IR stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // Unweighted IR stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // Weighted IR stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // Timing stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // sizes
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // Prediction interval statistics
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // ID/Targets/Predictions
+    if (getAttributeID() >= 0) resultTypes[current++] = "";
+    if (getPredTargetColumn()){
+        resultTypes[current++] = "";
+        resultTypes[current++] = "";
+    }
+    
+    // Classifier defined extras
+    resultTypes[current++] = "";
+
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultTypes[current++] = doub;
+    }
+    if (current != overall_length) {
+      throw new Error("ResultTypes didn't fit RESULT_SIZE");
+    }
+    return resultTypes;
+  }
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each result column
+   */
+  public String [] getResultNames() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    int overall_length = RESULT_SIZE+addm;
+    overall_length += NUM_IR_STATISTICS;
+    overall_length += NUM_WEIGHTED_IR_STATISTICS;
+    overall_length += NUM_UNWEIGHTED_IR_STATISTICS;
+    if (getAttributeID() >= 0) overall_length += 1;
+    if (getPredTargetColumn()) overall_length += 2;
+
+    String [] resultNames = new String[overall_length];
+    int current = 0;
+    resultNames[current++] = "Number_of_training_instances";
+    resultNames[current++] = "Number_of_testing_instances";
+
+    // Basic performance stats - right vs wrong
+    resultNames[current++] = "Number_correct";
+    resultNames[current++] = "Number_incorrect";
+    resultNames[current++] = "Number_unclassified";
+    resultNames[current++] = "Percent_correct";
+    resultNames[current++] = "Percent_incorrect";
+    resultNames[current++] = "Percent_unclassified";
+    resultNames[current++] = "Kappa_statistic";
+
+    // Sensitive stats - certainty of predictions
+    resultNames[current++] = "Mean_absolute_error";
+    resultNames[current++] = "Root_mean_squared_error";
+    resultNames[current++] = "Relative_absolute_error";
+    resultNames[current++] = "Root_relative_squared_error";
+
+    // SF stats
+    resultNames[current++] = "SF_prior_entropy";
+    resultNames[current++] = "SF_scheme_entropy";
+    resultNames[current++] = "SF_entropy_gain";
+    resultNames[current++] = "SF_mean_prior_entropy";
+    resultNames[current++] = "SF_mean_scheme_entropy";
+    resultNames[current++] = "SF_mean_entropy_gain";
+
+    // K&B stats
+    resultNames[current++] = "KB_information";
+    resultNames[current++] = "KB_mean_information";
+    resultNames[current++] = "KB_relative_information";
+
+    // IR stats
+    resultNames[current++] = "True_positive_rate";
+    resultNames[current++] = "Num_true_positives";
+    resultNames[current++] = "False_positive_rate";
+    resultNames[current++] = "Num_false_positives";
+    resultNames[current++] = "True_negative_rate";
+    resultNames[current++] = "Num_true_negatives";
+    resultNames[current++] = "False_negative_rate";
+    resultNames[current++] = "Num_false_negatives";
+    resultNames[current++] = "IR_precision";
+    resultNames[current++] = "IR_recall";
+    resultNames[current++] = "F_measure";
+    resultNames[current++] = "Area_under_ROC";
+    
+    // Weighted IR stats
+    resultNames[current++] = "Weighted_avg_true_positive_rate";
+    resultNames[current++] = "Weighted_avg_false_positive_rate";
+    resultNames[current++] = "Weighted_avg_true_negative_rate";
+    resultNames[current++] = "Weighted_avg_false_negative_rate";
+    resultNames[current++] = "Weighted_avg_IR_precision";
+    resultNames[current++] = "Weighted_avg_IR_recall";
+    resultNames[current++] = "Weighted_avg_F_measure";
+    resultNames[current++] = "Weighted_avg_area_under_ROC";
+    
+    // Unweighted IR stats
+    resultNames[current++] = "Unweighted_macro_avg_F_measure";
+    resultNames[current++] = "Unweighted_micro_avg_F_measure";
+    
+    // Timing stats
+    resultNames[current++] = "Elapsed_Time_training";
+    resultNames[current++] = "Elapsed_Time_testing";
+    resultNames[current++] = "UserCPU_Time_training";
+    resultNames[current++] = "UserCPU_Time_testing";
+
+    // sizes
+    resultNames[current++] = "Serialized_Model_Size";
+    resultNames[current++] = "Serialized_Train_Set_Size";
+    resultNames[current++] = "Serialized_Test_Set_Size";
+
+    // Prediction interval statistics
+    resultNames[current++] = "Coverage_of_Test_Cases_By_Regions";
+    resultNames[current++] = "Size_of_Predicted_Regions";
+    
+    // ID/Targets/Predictions
+    if (getAttributeID() >= 0) resultNames[current++] = "Instance_ID";
+    if (getPredTargetColumn()){
+        resultNames[current++] = "Targets";
+        resultNames[current++] = "Predictions";
+    }
+    
+    // Classifier defined extras
+    resultNames[current++] = "Summary";
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultNames[current++] = m_AdditionalMeasures[i];
+    }
+    if (current != overall_length) {
+      throw new Error("ResultNames didn't fit RESULT_SIZE");
+    }
+    return resultNames;
+  }
+
+  /**
+   * Gets the results for the supplied train and test datasets. Now performs
+   * a deep copy of the classifier before it is built and evaluated (just in case
+   * the classifier is not initialized properly in buildClassifier()).
+   *
+   * @param train the training Instances.
+   * @param test the testing Instances.
+   * @return the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public Object [] getResult(Instances train, Instances test)
+  throws Exception {
+    
+    if (train.classAttribute().type() != Attribute.NOMINAL) {
+      throw new Exception("Class attribute is not nominal!");
+    }
+    if (m_Template == null) {
+      throw new Exception("No classifier has been specified");
+    }
+    int addm = (m_AdditionalMeasures != null) ? m_AdditionalMeasures.length : 0;
+    int overall_length = RESULT_SIZE+addm;
+    overall_length += NUM_IR_STATISTICS;
+    overall_length += NUM_WEIGHTED_IR_STATISTICS;
+    overall_length += NUM_UNWEIGHTED_IR_STATISTICS;
+    if (getAttributeID() >= 0) overall_length += 1;
+    if (getPredTargetColumn()) overall_length += 2;
+    
+    ThreadMXBean thMonitor = ManagementFactory.getThreadMXBean();
+    boolean canMeasureCPUTime = thMonitor.isThreadCpuTimeSupported();
+    if(!thMonitor.isThreadCpuTimeEnabled())
+      thMonitor.setThreadCpuTimeEnabled(true);
+    
+    Object [] result = new Object[overall_length];
+    Evaluation eval = new Evaluation(train);
+    m_Classifier = AbstractClassifier.makeCopy(m_Template);
+    double [] predictions;
+    long thID = Thread.currentThread().getId();
+    long CPUStartTime=-1, trainCPUTimeElapsed=-1, testCPUTimeElapsed=-1,
+         trainTimeStart, trainTimeElapsed, testTimeStart, testTimeElapsed;    
+
+    //training classifier
+    trainTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime)
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    m_Classifier.buildClassifier(train);    
+    if(canMeasureCPUTime)
+      trainCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    
+    //testing classifier
+    testTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime) 
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    predictions = eval.evaluateModel(m_Classifier, test);
+    if(canMeasureCPUTime)
+      testCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+    thMonitor = null;
+    
+    m_result = eval.toSummaryString();
+    // The results stored are all per instance -- can be multiplied by the
+    // number of instances to get absolute numbers
+    int current = 0;
+    result[current++] = new Double(train.numInstances());
+    result[current++] = new Double(eval.numInstances());
+    result[current++] = new Double(eval.correct());
+    result[current++] = new Double(eval.incorrect());
+    result[current++] = new Double(eval.unclassified());
+    result[current++] = new Double(eval.pctCorrect());
+    result[current++] = new Double(eval.pctIncorrect());
+    result[current++] = new Double(eval.pctUnclassified());
+    result[current++] = new Double(eval.kappa());
+    
+    result[current++] = new Double(eval.meanAbsoluteError());
+    result[current++] = new Double(eval.rootMeanSquaredError());
+    result[current++] = new Double(eval.relativeAbsoluteError());
+    result[current++] = new Double(eval.rootRelativeSquaredError());
+    
+    result[current++] = new Double(eval.SFPriorEntropy());
+    result[current++] = new Double(eval.SFSchemeEntropy());
+    result[current++] = new Double(eval.SFEntropyGain());
+    result[current++] = new Double(eval.SFMeanPriorEntropy());
+    result[current++] = new Double(eval.SFMeanSchemeEntropy());
+    result[current++] = new Double(eval.SFMeanEntropyGain());
+    
+    // K&B stats
+    result[current++] = new Double(eval.KBInformation());
+    result[current++] = new Double(eval.KBMeanInformation());
+    result[current++] = new Double(eval.KBRelativeInformation());
+    
+    // IR stats
+    result[current++] = new Double(eval.truePositiveRate(m_IRclass));
+    result[current++] = new Double(eval.numTruePositives(m_IRclass));
+    result[current++] = new Double(eval.falsePositiveRate(m_IRclass));
+    result[current++] = new Double(eval.numFalsePositives(m_IRclass));
+    result[current++] = new Double(eval.trueNegativeRate(m_IRclass));
+    result[current++] = new Double(eval.numTrueNegatives(m_IRclass));
+    result[current++] = new Double(eval.falseNegativeRate(m_IRclass));
+    result[current++] = new Double(eval.numFalseNegatives(m_IRclass));
+    result[current++] = new Double(eval.precision(m_IRclass));
+    result[current++] = new Double(eval.recall(m_IRclass));
+    result[current++] = new Double(eval.fMeasure(m_IRclass));
+    result[current++] = new Double(eval.areaUnderROC(m_IRclass));
+    
+    // Weighted IR stats
+    result[current++] = new Double(eval.weightedTruePositiveRate());
+    result[current++] = new Double(eval.weightedFalsePositiveRate());
+    result[current++] = new Double(eval.weightedTrueNegativeRate());
+    result[current++] = new Double(eval.weightedFalseNegativeRate());
+    result[current++] = new Double(eval.weightedPrecision());
+    result[current++] = new Double(eval.weightedRecall());
+    result[current++] = new Double(eval.weightedFMeasure());
+    result[current++] = new Double(eval.weightedAreaUnderROC());
+    
+    // Unweighted IR stats
+    result[current++] = new Double(eval.unweightedMacroFmeasure());
+    result[current++] = new Double(eval.unweightedMicroFmeasure());
+    
+    // Timing stats
+    result[current++] = new Double(trainTimeElapsed / 1000.0);
+    result[current++] = new Double(testTimeElapsed / 1000.0);
+    if(canMeasureCPUTime) {
+      result[current++] = new Double((trainCPUTimeElapsed/1000000.0) / 1000.0);
+      result[current++] = new Double((testCPUTimeElapsed /1000000.0) / 1000.0);
+    }
+    else {
+      result[current++] = new Double(Utils.missingValue());
+      result[current++] = new Double(Utils.missingValue());
+    }
+
+    // sizes
+    ByteArrayOutputStream bastream = new ByteArrayOutputStream();
+    ObjectOutputStream oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(m_Classifier);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(train);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(test);
+    result[current++] = new Double(bastream.size());
+    
+    // Prediction interval statistics
+    result[current++] = new Double(eval.coverageOfTestCasesByPredictedRegions());
+    result[current++] = new Double(eval.sizeOfPredictedRegions());
+
+    // IDs
+    if (getAttributeID() >= 0){
+      String idsString = "";
+      if (test.attribute(m_attID).isNumeric()){
+        if (test.numInstances() > 0)
+          idsString += test.instance(0).value(m_attID);
+        for(int i=1;i<test.numInstances();i++){
+          idsString += "|" + test.instance(i).value(m_attID);
+        }
+      } else {
+        if (test.numInstances() > 0)
+          idsString += test.instance(0).stringValue(m_attID);
+        for(int i=1;i<test.numInstances();i++){
+          idsString += "|" + test.instance(i).stringValue(m_attID);
+        }
+      }
+      result[current++] = idsString;
+    }
+    
+    if (getPredTargetColumn()){
+      if (test.classAttribute().isNumeric()){
+        // Targets
+        if (test.numInstances() > 0){
+          String targetsString = "";
+          targetsString += test.instance(0).value(test.classIndex());
+          for(int i=1;i<test.numInstances();i++){
+            targetsString += "|" + test.instance(i).value(test.classIndex());
+          }
+          result[current++] = targetsString;
+        }
+        
+        // Predictions
+        if (predictions.length > 0){
+          String predictionsString = "";
+          predictionsString += predictions[0];
+          for(int i=1;i<predictions.length;i++){
+            predictionsString += "|" + predictions[i];
+          }
+          result[current++] = predictionsString;
+        }
+      } else {
+        // Targets
+        if (test.numInstances() > 0){
+          String targetsString = "";
+          targetsString += test.instance(0).stringValue(test.classIndex());
+          for(int i=1;i<test.numInstances();i++){
+            targetsString += "|" + test.instance(i).stringValue(test.classIndex());
+          }
+          result[current++] = targetsString;
+        }
+        
+        // Predictions
+        if (predictions.length > 0){
+          String predictionsString = "";
+          predictionsString += test.classAttribute().value((int) predictions[0]);
+          for(int i=1;i<predictions.length;i++){
+            predictionsString += "|" + test.classAttribute().value((int) predictions[i]);
+          }
+          result[current++] = predictionsString;
+        }
+      }
+    }
+    
+    if (m_Classifier instanceof Summarizable) {
+      result[current++] = ((Summarizable)m_Classifier).toSummaryString();
+    } else {
+      result[current++] = null;
+    }
+    
+    for (int i=0;i<addm;i++) {
+      if (m_doesProduce[i]) {
+        try {
+          double dv = ((AdditionalMeasureProducer)m_Classifier).
+          getMeasure(m_AdditionalMeasures[i]);
+          if (!Utils.isMissingValue(dv)) {
+            Double value = new Double(dv);
+            result[current++] = value;
+          } else {
+            result[current++] = null;
+          }
+        } catch (Exception ex) {
+          System.err.println(ex);
+        }
+      } else {
+        result[current++] = null;
+      }
+    }
+    
+    if (current != overall_length) {
+      throw new Error("Results didn't fit RESULT_SIZE");
+    }
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "The classifier to use.";
+  }
+
+  /**
+   * Get the value of Classifier.
+   *
+   * @return Value of Classifier.
+   */
+  public Classifier getClassifier() {
+    
+    return m_Template;
+  }
+  
+  /**
+   * Sets the classifier.
+   *
+   * @param newClassifier the new classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+    
+    m_Template = newClassifier;
+    updateOptions();
+  }
+  
+  /**
+   * Get the value of ClassForIRStatistics.
+   * @return Value of ClassForIRStatistics.
+   */
+  public int getClassForIRStatistics() {
+    return m_IRclass;
+  }
+  
+  /**
+   * Set the value of ClassForIRStatistics.
+   * @param v  Value to assign to ClassForIRStatistics.
+   */
+  public void setClassForIRStatistics(int v) {
+    m_IRclass = v;
+  }
+
+  /**
+   * Get the index of Attibute Identifying the instances
+   * @return index of outputed Attribute.
+   */
+  public int getAttributeID() {
+    return m_attID;
+  }
+  
+  /**
+   * Set the index of Attibute Identifying the instances
+   * @param v index the attribute to output
+   */
+  public void setAttributeID(int v) {
+    m_attID = v;
+  }
+    
+  /**
+   *@return true if the prediction and target columns must be outputed.
+   */
+  public boolean getPredTargetColumn(){
+      return m_predTargetColumn;
+  }
+
+  /**
+   * Set the flag for prediction and target output.
+   *@param v true if the 2 columns have to be outputed. false otherwise.
+   */
+  public void setPredTargetColumn(boolean v){
+      m_predTargetColumn = v;
+  }
+  
+  /**
+   * Updates the options that the current classifier is using.
+   */
+  protected void updateOptions() {
+    
+    if (m_Template instanceof OptionHandler) {
+      m_ClassifierOptions = Utils.joinOptions(((OptionHandler)m_Template)
+					      .getOptions());
+    } else {
+      m_ClassifierOptions = "";
+    }
+    if (m_Template instanceof Serializable) {
+      ObjectStreamClass obs = ObjectStreamClass.lookup(m_Template
+						       .getClass());
+      m_ClassifierVersion = "" + obs.getSerialVersionUID();
+    } else {
+      m_ClassifierVersion = "";
+    }
+  }
+
+  /**
+   * Set the Classifier to use, given it's class name. A new classifier will be
+   * instantiated.
+   *
+   * @param newClassifierName the Classifier class name.
+   * @throws Exception if the class name is invalid.
+   */
+  public void setClassifierName(String newClassifierName) throws Exception {
+
+    try {
+      setClassifier((Classifier)Class.forName(newClassifierName)
+		    .newInstance());
+    } catch (Exception ex) {
+      throw new Exception("Can't find Classifier with class name: "
+			  + newClassifierName);
+    }
+  }
+
+  /**
+   * Gets the raw output from the classifier
+   * @return the raw output from th,0e classifier
+   */
+  public String getRawResultOutput() {
+    StringBuffer result = new StringBuffer();
+
+    if (m_Classifier == null) {
+      return "<null> classifier";
+    }
+    result.append(toString());
+    result.append("Classifier model: \n"+m_Classifier.toString()+'\n');
+
+    // append the performance statistics
+    if (m_result != null) {
+      result.append(m_result);
+      
+      if (m_doesProduce != null) {
+	for (int i=0;i<m_doesProduce.length;i++) {
+	  if (m_doesProduce[i]) {
+	    try {
+	      double dv = ((AdditionalMeasureProducer)m_Classifier).
+		getMeasure(m_AdditionalMeasures[i]);
+	      if (!Utils.isMissingValue(dv)) {
+		Double value = new Double(dv);
+		result.append(m_AdditionalMeasures[i]+" : "+value+'\n');
+	      } else {
+		result.append(m_AdditionalMeasures[i]+" : "+'?'+'\n');
+	      }
+	    } catch (Exception ex) {
+	      System.err.println(ex);
+	    }
+	  } 
+	}
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a text description of the split evaluator.
+   *
+   * @return a text description of the split evaluator.
+   */
+  public String toString() {
+
+    String result = "ClassifierSplitEvaluator: ";
+    if (m_Template == null) {
+      return result + "<null> classifier";
+    }
+    return result + m_Template.getClass().getName() + " " 
+      + m_ClassifierOptions + "(version " + m_ClassifierVersion + ")";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+} // ClassifierSplitEvaluator
Index: branches/MetisMQI/src/main/java/weka/experiment/Compute.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/Compute.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/Compute.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Compute.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Interface to something that can accept remote connections and execute
+ * a task.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface Compute extends Remote {
+  
+  /**
+   * Execute a task
+   * @param t Task to be executed
+   * @exception RemoteException if something goes wrong.
+   * @return a unique ID for the task
+   */
+  Object executeTask(Task t) throws RemoteException;
+
+  /**
+   * Check on the status of a <code>Task</code>
+   *
+   * @param taskId the ID for the Task to be checked
+   * @return the status of the Task
+   * @exception Exception if an error occurs
+   */
+  Object checkStatus(Object taskId) throws Exception;
+}
+
Index: branches/MetisMQI/src/main/java/weka/experiment/CostSensitiveClassifierSplitEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/CostSensitiveClassifierSplitEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/CostSensitiveClassifierSplitEvaluator.java	(revision 29)
@@ -0,0 +1,561 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostSensitiveClassifierSplitEvaluator.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.CostMatrix;
+import weka.classifiers.Evaluation;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.ObjectOutputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * SplitEvaluator that produces results for a classification scheme on a nominal class attribute, including weighted misclassification costs.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ * <pre> -D &lt;directory&gt;
+ *  Name of a directory to search for cost files when loading
+ *  costs on demand (default current directory).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * All options after -- will be passed to the classifier.
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5987 $
+ */
+public class CostSensitiveClassifierSplitEvaluator 
+  extends ClassifierSplitEvaluator {
+
+  /** for serialization */
+  static final long serialVersionUID = -8069566663019501276L;
+
+  /** 
+   * The directory used when loading cost files on demand, null indicates
+   * current directory 
+   */
+  protected File m_OnDemandDirectory = new File(System.getProperty("user.dir"));
+
+  /** The length of a result */
+  private static final int RESULT_SIZE = 31;
+
+  /**
+   * Returns a string describing this split evaluator
+   * @return a description of the split evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return " SplitEvaluator that produces results for a classification scheme "
+      +"on a nominal class attribute, including weighted misclassification "
+      +"costs.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+    Enumeration enu = super.listOptions();
+    while (enu.hasMoreElements()) {
+      newVector.addElement(enu.nextElement());
+    }
+
+    newVector.addElement(new Option(
+              "\tName of a directory to search for cost files when loading\n"
+              +"\tcosts on demand (default current directory).",
+              "D", 1, "-D <directory>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   * <pre> -D &lt;directory&gt;
+   *  Name of a directory to search for cost files when loading
+   *  costs on demand (default current directory).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the classifier.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String demandDir = Utils.getOption('D', options);
+    if (demandDir.length() != 0) {
+      setOnDemandDirectory(new File(demandDir));
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] superOptions = super.getOptions();
+    String [] options = new String [superOptions.length + 3];
+    int current = 0;
+
+    options[current++] = "-D";
+    options[current++] = "" + getOnDemandDirectory();
+
+    System.arraycopy(superOptions, 0, options, current, 
+		     superOptions.length);
+    current += superOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String onDemandDirectoryTipText() {
+    return "The directory to look in for cost files. This directory will be "
+      +"searched for cost files when loading on demand.";
+  }
+
+  /**
+   * Returns the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @return The cost file search directory.
+   */
+  public File getOnDemandDirectory() {
+
+    return m_OnDemandDirectory;
+  }
+
+  /**
+   * Sets the directory that will be searched for cost files when
+   * loading on demand.
+   *
+   * @param newDir The cost file search directory.
+   */
+  public void setOnDemandDirectory(File newDir) {
+
+    if (newDir.isDirectory()) {
+      m_OnDemandDirectory = newDir;
+    } else {
+      m_OnDemandDirectory = new File(newDir.getParent());
+    }
+  }
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run. The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    Object [] resultTypes = new Object[RESULT_SIZE+addm];
+    Double doub = new Double(0);
+    int current = 0;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // Timing stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // sizes
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    resultTypes[current++] = "";
+
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultTypes[current++] = doub;
+    }
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("ResultTypes didn't fit RESULT_SIZE");
+    }
+    return resultTypes;
+  }
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each result column
+   */
+  public String [] getResultNames() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    String [] resultNames = new String[RESULT_SIZE+addm];
+    int current = 0;
+    resultNames[current++] = "Number_of_training_instances";
+    resultNames[current++] = "Number_of_testing_instances";
+
+    // Basic performance stats - right vs wrong
+    resultNames[current++] = "Number_correct";
+    resultNames[current++] = "Number_incorrect";
+    resultNames[current++] = "Number_unclassified";
+    resultNames[current++] = "Percent_correct";
+    resultNames[current++] = "Percent_incorrect";
+    resultNames[current++] = "Percent_unclassified";
+    resultNames[current++] = "Total_cost";
+    resultNames[current++] = "Average_cost";
+
+    // Sensitive stats - certainty of predictions
+    resultNames[current++] = "Mean_absolute_error";
+    resultNames[current++] = "Root_mean_squared_error";
+    resultNames[current++] = "Relative_absolute_error";
+    resultNames[current++] = "Root_relative_squared_error";
+
+    // SF stats
+    resultNames[current++] = "SF_prior_entropy";
+    resultNames[current++] = "SF_scheme_entropy";
+    resultNames[current++] = "SF_entropy_gain";
+    resultNames[current++] = "SF_mean_prior_entropy";
+    resultNames[current++] = "SF_mean_scheme_entropy";
+    resultNames[current++] = "SF_mean_entropy_gain";
+
+    // K&B stats
+    resultNames[current++] = "KB_information";
+    resultNames[current++] = "KB_mean_information";
+    resultNames[current++] = "KB_relative_information";
+
+    // Timing stats
+    resultNames[current++] = "Elapsed_Time_training";
+    resultNames[current++] = "Elapsed_Time_testing";
+    resultNames[current++] = "UserCPU_Time_training";
+    resultNames[current++] = "UserCPU_Time_testing";
+
+    // sizes
+    resultNames[current++] = "Serialized_Model_Size";
+    resultNames[current++] = "Serialized_Train_Set_Size";
+    resultNames[current++] = "Serialized_Test_Set_Size";
+
+    // Classifier defined extras
+    resultNames[current++] = "Summary";
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultNames[current++] = m_AdditionalMeasures[i];
+    }
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("ResultNames didn't fit RESULT_SIZE");
+    }
+    return resultNames;
+  }
+
+  /**
+   * Gets the results for the supplied train and test datasets. Now performs
+   * a deep copy of the classifier before it is built and evaluated (just in case
+   * the classifier is not initialized properly in buildClassifier()).
+   *
+   * @param train the training Instances.
+   * @param test the testing Instances.
+   * @return the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public Object [] getResult(Instances train, Instances test)
+  throws Exception {
+    
+    if (train.classAttribute().type() != Attribute.NOMINAL) {
+      throw new Exception("Class attribute is not nominal!");
+    }
+    if (m_Template == null) {
+      throw new Exception("No classifier has been specified");
+    }
+    ThreadMXBean thMonitor = ManagementFactory.getThreadMXBean();
+    boolean canMeasureCPUTime = thMonitor.isThreadCpuTimeSupported();
+    if(!thMonitor.isThreadCpuTimeEnabled())
+      thMonitor.setThreadCpuTimeEnabled(true);
+    
+    int addm = (m_AdditionalMeasures != null) ? m_AdditionalMeasures.length : 0;
+    Object [] result = new Object[RESULT_SIZE+addm];
+    long thID = Thread.currentThread().getId();
+    long CPUStartTime=-1, trainCPUTimeElapsed=-1, testCPUTimeElapsed=-1,
+         trainTimeStart, trainTimeElapsed, testTimeStart, testTimeElapsed;    
+    
+    String costName = train.relationName() + CostMatrix.FILE_EXTENSION;
+    File costFile = new File(getOnDemandDirectory(), costName);
+    if (!costFile.exists()) {
+      throw new Exception("On-demand cost file doesn't exist: " + costFile);
+    }
+    CostMatrix costMatrix = new CostMatrix(new BufferedReader(
+    new FileReader(costFile)));
+    
+    Evaluation eval = new Evaluation(train, costMatrix);    
+    m_Classifier = AbstractClassifier.makeCopy(m_Template);
+    
+    trainTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime)
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    m_Classifier.buildClassifier(train);
+    if(canMeasureCPUTime)
+      trainCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    testTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime)
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    eval.evaluateModel(m_Classifier, test);
+    if(canMeasureCPUTime)
+      testCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+    thMonitor = null;
+    
+    m_result = eval.toSummaryString();
+    // The results stored are all per instance -- can be multiplied by the
+    // number of instances to get absolute numbers
+    int current = 0;
+    result[current++] = new Double(train.numInstances());
+    result[current++] = new Double(eval.numInstances());
+    
+    result[current++] = new Double(eval.correct());
+    result[current++] = new Double(eval.incorrect());
+    result[current++] = new Double(eval.unclassified());
+    result[current++] = new Double(eval.pctCorrect());
+    result[current++] = new Double(eval.pctIncorrect());
+    result[current++] = new Double(eval.pctUnclassified());
+    result[current++] = new Double(eval.totalCost());
+    result[current++] = new Double(eval.avgCost());
+    
+    result[current++] = new Double(eval.meanAbsoluteError());
+    result[current++] = new Double(eval.rootMeanSquaredError());
+    result[current++] = new Double(eval.relativeAbsoluteError());
+    result[current++] = new Double(eval.rootRelativeSquaredError());
+    
+    result[current++] = new Double(eval.SFPriorEntropy());
+    result[current++] = new Double(eval.SFSchemeEntropy());
+    result[current++] = new Double(eval.SFEntropyGain());
+    result[current++] = new Double(eval.SFMeanPriorEntropy());
+    result[current++] = new Double(eval.SFMeanSchemeEntropy());
+    result[current++] = new Double(eval.SFMeanEntropyGain());
+    
+    // K&B stats
+    result[current++] = new Double(eval.KBInformation());
+    result[current++] = new Double(eval.KBMeanInformation());
+    result[current++] = new Double(eval.KBRelativeInformation());
+    
+    // Timing stats
+    result[current++] = new Double(trainTimeElapsed / 1000.0);
+    result[current++] = new Double(testTimeElapsed / 1000.0);
+    if(canMeasureCPUTime) {
+      result[current++] = new Double((trainCPUTimeElapsed/1000000.0) / 1000.0);
+      result[current++] = new Double((testCPUTimeElapsed /1000000.0) / 1000.0);
+    }
+    else {
+      result[current++] = new Double(Utils.missingValue());
+      result[current++] = new Double(Utils.missingValue());
+    }
+    
+    // sizes
+    ByteArrayOutputStream bastream = new ByteArrayOutputStream();
+    ObjectOutputStream oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(m_Classifier);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(train);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(test);
+    result[current++] = new Double(bastream.size());
+    
+    if (m_Classifier instanceof Summarizable) {
+      result[current++] = ((Summarizable)m_Classifier).toSummaryString();
+    } else {
+      result[current++] = null;
+    }
+    
+    for (int i=0;i<addm;i++) {
+      if (m_doesProduce[i]) {
+        try {
+          double dv = ((AdditionalMeasureProducer)m_Classifier).
+          getMeasure(m_AdditionalMeasures[i]);
+          if (!Utils.isMissingValue(dv)) {
+            Double value = new Double(dv);
+            result[current++] = value;
+          } else {
+            result[current++] = null;
+          }
+        } catch (Exception ex) {
+          System.err.println(ex);
+        }
+      } else {
+        result[current++] = null;
+      }
+    }
+    
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("Results didn't fit RESULT_SIZE");
+    }
+    return result;
+  }
+
+  /**
+   * Returns a text description of the split evaluator.
+   *
+   * @return a text description of the split evaluator.
+   */
+  public String toString() {
+
+    String result = "CostSensitiveClassifierSplitEvaluator: ";
+    if (m_Template == null) {
+      return result + "<null> classifier";
+    }
+    return result + m_Template.getClass().getName() + " " 
+      + m_ClassifierOptions + "(version " + m_ClassifierVersion + ")";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+} // CostSensitiveClassifierSplitEvaluator
Index: branches/MetisMQI/src/main/java/weka/experiment/CrossValidationResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/CrossValidationResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/CrossValidationResultProducer.java	(revision 29)
@@ -0,0 +1,827 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CrossValidationResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * Generates for each run, carries out an n-fold cross-validation, using the set SplitEvaluator to generate some results. If the class attribute is nominal, the dataset is stratified. Results for each fold are generated, so you may wish to use this in addition with an AveragingResultProducer to obtain averages for each run.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  The number of folds to use for the cross-validation.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * All options after -- will be passed to the split evaluator.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.17 $
+ */
+public class CrossValidationResultProducer 
+  implements ResultProducer, OptionHandler, AdditionalMeasureProducer, 
+             RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1580053925080091917L;
+  
+  /** The dataset of interest */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The number of folds in the cross-validation */
+  protected int m_NumFolds = 10;
+
+  /** Save raw output of split evaluators --- for debugging purposes */
+  protected boolean m_debugOutput = false;
+
+  /** The output zipper to use for saving raw splitEvaluator output */
+  protected OutputZipper m_ZipDest = null;
+
+  /** The destination output file/directory for raw output */
+  protected File m_OutputFile = new File(
+				new File(System.getProperty("user.dir")), 
+				"splitEvalutorOut.zip");
+
+  /** The SplitEvaluator used to generate results */
+  protected SplitEvaluator m_SplitEvaluator = new ClassifierSplitEvaluator();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** The name of the key field containing the dataset name */
+  public static String DATASET_FIELD_NAME = "Dataset";
+
+  /** The name of the key field containing the run number */
+  public static String RUN_FIELD_NAME = "Run";
+
+  /** The name of the key field containing the fold number */
+  public static String FOLD_FIELD_NAME = "Fold";
+
+  /** The name of the result field containing the timestamp */
+  public static String TIMESTAMP_FIELD_NAME = "Date_time";
+
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Generates for each run, carries out an n-fold cross-validation, "
+      + "using the set SplitEvaluator to generate some results. If the class "
+      + "attribute is nominal, the dataset is stratified. Results for each fold "
+      + "are generated, so you may wish to use this in addition with an "
+      + "AveragingResultProducer to obtain averages for each run.";
+  }
+
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    
+    m_Instances = instances;
+  }
+
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+
+    m_ResultListener = listener;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current SplitEvaluator) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_SplitEvaluator != null) {
+      System.err.println("CrossValidationResultProducer: setting additional "
+			 +"measures for "
+			 +"split evaluator");
+      m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the SplitEvaluator
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_SplitEvaluator).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_SplitEvaluator).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("CrossValidationResultProducer: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_SplitEvaluator.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+  
+  /**
+   * Gets a Double representing the current date and time.
+   * eg: 1:46pm on 20/5/1999 -> 19990520.1346
+   *
+   * @return a value of type Double
+   */
+  public static Double getTimestamp() {
+
+    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+    double timestamp = now.get(Calendar.YEAR) * 10000
+      + (now.get(Calendar.MONTH) + 1) * 100
+      + now.get(Calendar.DAY_OF_MONTH)
+      + now.get(Calendar.HOUR_OF_DAY) / 100.0
+      + now.get(Calendar.MINUTE) / 10000.0;
+    return new Double(timestamp);
+  }
+  
+  /**
+   * Prepare to generate results.
+   *
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+
+    if (m_SplitEvaluator == null) {
+      throw new Exception("No SplitEvalutor set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    m_ResultListener.preProcess(this);
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more requests to generate results for the current experiment
+   * will be sent.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultListener.postProcess(this);
+
+    if (m_debugOutput) {
+      if (m_ZipDest != null) {
+	m_ZipDest.finished();
+	m_ZipDest = null;
+      }
+    }
+  }
+  
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+    /*    // Randomize on a copy of the original dataset
+    Instances runInstances = new Instances(m_Instances);
+    runInstances.randomize(new Random(run));
+    if (runInstances.classAttribute().isNominal()) {
+      runInstances.stratify(m_NumFolds);
+      } */
+    for (int fold = 0; fold < m_NumFolds; fold++) {
+      // Add in some fields to the key like run and fold number, dataset name
+      Object [] seKey = m_SplitEvaluator.getKey();
+      Object [] key = new Object [seKey.length + 3];
+      key[0] = Utils.backQuoteChars(m_Instances.relationName());
+      key[1] = "" + run;
+      key[2] = "" + (fold + 1);
+      System.arraycopy(seKey, 0, key, 3, seKey.length);
+      if (m_ResultListener.isResultRequired(this, key)) {
+	try {
+	  m_ResultListener.acceptResult(this, key, null);
+	} catch (Exception ex) {
+	  // Save the train and test datasets for debugging purposes?
+	  throw ex;
+	}
+      }
+    }
+  }
+
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    if (getRawOutput()) {
+      if (m_ZipDest == null) {
+	m_ZipDest = new OutputZipper(m_OutputFile);
+      }
+    }
+
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+    // Randomize on a copy of the original dataset
+    Instances runInstances = new Instances(m_Instances);
+    Random random = new Random(run);
+    runInstances.randomize(random);
+    if (runInstances.classAttribute().isNominal()) {
+      runInstances.stratify(m_NumFolds);
+    }
+    for (int fold = 0; fold < m_NumFolds; fold++) {
+      // Add in some fields to the key like run and fold number, dataset name
+      Object [] seKey = m_SplitEvaluator.getKey();
+      Object [] key = new Object [seKey.length + 3];
+      key[0] =  Utils.backQuoteChars(m_Instances.relationName());
+      key[1] = "" + run;
+      key[2] = "" + (fold + 1);
+      System.arraycopy(seKey, 0, key, 3, seKey.length);
+      if (m_ResultListener.isResultRequired(this, key)) {
+	Instances train = runInstances.trainCV(m_NumFolds, fold, random);
+	Instances test = runInstances.testCV(m_NumFolds, fold);
+	try {
+	  Object [] seResults = m_SplitEvaluator.getResult(train, test);
+	  Object [] results = new Object [seResults.length + 1];
+	  results[0] = getTimestamp();
+	  System.arraycopy(seResults, 0, results, 1,
+			   seResults.length);
+	  if (m_debugOutput) {
+	    String resultName = (""+run+"."+(fold+1)+"."
+	      + Utils.backQuoteChars(runInstances.relationName())
+	      +"."
+	      +m_SplitEvaluator.toString()).replace(' ','_');
+	    resultName = Utils.removeSubstring(resultName, 
+					       "weka.classifiers.");
+	    resultName = Utils.removeSubstring(resultName, 
+					       "weka.filters.");
+	    resultName = Utils.removeSubstring(resultName, 
+					       "weka.attributeSelection.");
+	    m_ZipDest.zipit(m_SplitEvaluator.getRawResultOutput(), resultName);
+	  }
+	  m_ResultListener.acceptResult(this, key, results);
+	} catch (Exception ex) {
+	  // Save the train and test datasets for debugging purposes?
+	  throw ex;
+	}
+      }
+    }
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing the name of each column
+   */
+  public String [] getKeyNames() {
+
+    String [] keyNames = m_SplitEvaluator.getKeyNames();
+    // Add in the names of our extra key fields
+    String [] newKeyNames = new String [keyNames.length + 3];
+    newKeyNames[0] = DATASET_FIELD_NAME;
+    newKeyNames[1] = RUN_FIELD_NAME;
+    newKeyNames[2] = FOLD_FIELD_NAME;
+    System.arraycopy(keyNames, 0, newKeyNames, 3, keyNames.length);
+    return newKeyNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getKeyTypes() {
+
+    Object [] keyTypes = m_SplitEvaluator.getKeyTypes();
+    // Add in the types of our extra fields
+    Object [] newKeyTypes = new String [keyTypes.length + 3];
+    newKeyTypes[0] = new String();
+    newKeyTypes[1] = new String();
+    newKeyTypes[2] = new String();
+    System.arraycopy(keyTypes, 0, newKeyTypes, 3, keyTypes.length);
+    return newKeyTypes;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing the name of each column
+   */
+  public String [] getResultNames() {
+
+    String [] resultNames = m_SplitEvaluator.getResultNames();
+    // Add in the names of our extra Result fields
+    String [] newResultNames = new String [resultNames.length + 1];
+    newResultNames[0] = TIMESTAMP_FIELD_NAME;
+    System.arraycopy(resultNames, 0, newResultNames, 1, resultNames.length);
+    return newResultNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+
+    Object [] resultTypes = m_SplitEvaluator.getResultTypes();
+    // Add in the types of our extra Result fields
+    Object [] newResultTypes = new Object [resultTypes.length + 1];
+    newResultTypes[0] = new Double(0);
+    System.arraycopy(resultTypes, 0, newResultTypes, 1, resultTypes.length);
+    return newResultTypes;
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  public String getCompatibilityState() {
+
+    String result = "-X " + m_NumFolds + " " ;
+    if (m_SplitEvaluator == null) {
+      result += "<null SplitEvaluator>";
+    } else {
+      result += "-W " + m_SplitEvaluator.getClass().getName();
+    }
+    return result + " --";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String outputFileTipText() {
+    return "Set the destination for saving raw output. If the rawOutput "
+      +"option is selected, then output from the splitEvaluator for "
+      +"individual folds is saved. If the destination is a directory, "
+      +"then each output is saved to an individual gzip file; if the "
+      +"destination is a file, then each output is saved as an entry "
+      +"in a zip file.";
+  }
+
+  /**
+   * Get the value of OutputFile.
+   *
+   * @return Value of OutputFile.
+   */
+  public File getOutputFile() {
+    
+    return m_OutputFile;
+  }
+  
+  /**
+   * Set the value of OutputFile.
+   *
+   * @param newOutputFile Value to assign to OutputFile.
+   */
+  public void setOutputFile(File newOutputFile) {
+    
+    m_OutputFile = newOutputFile;
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+    return "Number of folds to use in cross validation.";
+  }
+
+  /**
+   * Get the value of NumFolds.
+   *
+   * @return Value of NumFolds.
+   */
+  public int getNumFolds() {
+    
+    return m_NumFolds;
+  }
+  
+  /**
+   * Set the value of NumFolds.
+   *
+   * @param newNumFolds Value to assign to NumFolds.
+   */
+  public void setNumFolds(int newNumFolds) {
+    
+    m_NumFolds = newNumFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String rawOutputTipText() {
+    return "Save raw output (useful for debugging). If set, then output is "
+      +"sent to the destination specified by outputFile";
+  }
+
+  /**
+   * Get if raw split evaluator output is to be saved
+   * @return true if raw split evalutor output is to be saved
+   */
+  public boolean getRawOutput() {
+    return m_debugOutput;
+  }
+  
+  /**
+   * Set to true if raw split evaluator output is to be saved
+   * @param d true if output is to be saved
+   */
+  public void setRawOutput(boolean d) {
+    m_debugOutput = d;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String splitEvaluatorTipText() {
+    return "The evaluator to apply to the cross validation folds. "
+      +"This may be a classifier, regression scheme etc.";
+  }
+ 
+  /**
+   * Get the SplitEvaluator.
+   *
+   * @return the SplitEvaluator.
+   */
+  public SplitEvaluator getSplitEvaluator() {
+    
+    return m_SplitEvaluator;
+  }
+  
+  /**
+   * Set the SplitEvaluator.
+   *
+   * @param newSplitEvaluator new SplitEvaluator to use.
+   */
+  public void setSplitEvaluator(SplitEvaluator newSplitEvaluator) {
+    
+    m_SplitEvaluator = newSplitEvaluator;
+    m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+	     "\tThe number of folds to use for the cross-validation.\n"
+	      +"\t(default 10)", 
+	     "X", 1, 
+	     "-X <number of folds>"));
+
+    newVector.addElement(new Option(
+	     "Save raw split evaluator output.",
+	     "D",0,"-D"));
+
+    newVector.addElement(new Option(
+	     "\tThe filename where raw output will be stored.\n"
+	     +"\tIf a directory name is specified then then individual\n"
+	     +"\toutputs will be gzipped, otherwise all output will be\n"
+	     +"\tzipped to the named file. Use in conjuction with -D."
+	     +"\t(default splitEvalutorOut.zip)", 
+	     "O", 1, 
+	     "-O <file/directory name/path>"));
+
+    newVector.addElement(new Option(
+	     "\tThe full class name of a SplitEvaluator.\n"
+	      +"\teg: weka.experiment.ClassifierSplitEvaluator", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    if ((m_SplitEvaluator != null) &&
+	(m_SplitEvaluator instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to split evaluator "
+	     + m_SplitEvaluator.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_SplitEvaluator).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  The number of folds to use for the cross-validation.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the split evaluator.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    setRawOutput(Utils.getFlag('D', options));
+
+    String fName = Utils.getOption('O', options);
+    if (fName.length() != 0) {
+      setOutputFile(new File(fName));
+    }
+
+    String numFolds = Utils.getOption('X', options);
+    if (numFolds.length() != 0) {
+      setNumFolds(Integer.parseInt(numFolds));
+    } else {
+      setNumFolds(10);
+    }
+
+    String seName = Utils.getOption('W', options);
+    if (seName.length() == 0) {
+      throw new Exception("A SplitEvaluator must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // SE.
+    setSplitEvaluator((SplitEvaluator)Utils.forName(
+		      SplitEvaluator.class,
+		      seName,
+		      null));
+    if (getSplitEvaluator() instanceof OptionHandler) {
+      ((OptionHandler) getSplitEvaluator())
+	.setOptions(Utils.partitionOptions(options));
+    }
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] seOptions = new String [0];
+    if ((m_SplitEvaluator != null) && 
+	(m_SplitEvaluator instanceof OptionHandler)) {
+      seOptions = ((OptionHandler)m_SplitEvaluator).getOptions();
+    }
+    
+    String [] options = new String [seOptions.length + 8];
+    int current = 0;
+
+    options[current++] = "-X"; options[current++] = "" + getNumFolds();
+
+    if (getRawOutput()) {
+      options[current++] = "-D";
+    }
+
+    options[current++] = "-O"; 
+    options[current++] = getOutputFile().getName();
+    
+    if (getSplitEvaluator() != null) {
+      options[current++] = "-W";
+      options[current++] = getSplitEvaluator().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(seOptions, 0, options, current, 
+		     seOptions.length);
+    current += seOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "CrossValidationResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " +  Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.17 $");
+  }
+    
+  /** 
+   * Quick test of timestamp
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String [] args) {
+    
+    System.err.println(Utils.doubleToString(getTimestamp().doubleValue(), 4));
+  }
+} // CrossValidationResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/CrossValidationSplitResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/CrossValidationSplitResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/CrossValidationSplitResultProducer.java	(revision 29)
@@ -0,0 +1,255 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CrossValidationSplitResultProducer.java
+ *    Copyright (C) 1999, 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Carries out one split of a repeated k-fold cross-validation, using the set SplitEvaluator to generate some results. Note that the run number is actually the nth split of a repeated k-fold cross-validation, i.e. if k=10, run number 100 is the 10th fold of the 10th cross-validation run. This producer's sole purpose is to allow more fine-grained distribution of cross-validation experiments. If the class attribute is nominal, the dataset is stratified.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  The number of folds to use for the cross-validation.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * All options after -- will be passed to the split evaluator.
+ *
+ * @author Len Trigg 
+ * @author Eibe Frank
+ * @version $Revision: 5828 $
+ */
+public class CrossValidationSplitResultProducer 
+  extends CrossValidationResultProducer {
+  
+  /** for serialization */
+  static final long serialVersionUID = 1403798164046795073L;
+  
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Carries out one split of a repeated k-fold cross-validation, "
+      + "using the set SplitEvaluator to generate some results. "
+      + "Note that the run number is actually the nth split of a repeated "
+      + "k-fold cross-validation, i.e. if k=10, run number 100 is the 10th "
+      + "fold of the 10th cross-validation run. This producer's sole purpose "
+      + "is to allow more fine-grained distribution of cross-validation "
+      + "experiments. If the class attribute is nominal, the dataset is stratified.";
+  }
+  
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Add in some fields to the key like run and fold number, dataset name
+    Object [] seKey = m_SplitEvaluator.getKey();
+    Object [] key = new Object [seKey.length + 3];
+    key[0] = Utils.backQuoteChars(m_Instances.relationName());
+    key[2] = "" + (((run - 1) % m_NumFolds) + 1);
+    key[1] = "" + (((run - 1) / m_NumFolds) + 1);
+    System.arraycopy(seKey, 0, key, 3, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+      try {
+        m_ResultListener.acceptResult(this, key, null);
+      } catch (Exception ex) {
+        // Save the train and test datasets for debugging purposes?
+        throw ex;
+      }
+    }
+  }
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    if (getRawOutput()) {
+      if (m_ZipDest == null) {
+	m_ZipDest = new OutputZipper(m_OutputFile);
+      }
+    }
+
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Compute run and fold number from given run
+    int fold = (run - 1) % m_NumFolds;
+    run = ((run - 1) / m_NumFolds) + 1; 
+    
+
+    // Randomize on a copy of the original dataset
+    Instances runInstances = new Instances(m_Instances);
+    Random random = new Random(run);
+    runInstances.randomize(random);
+    if (runInstances.classAttribute().isNominal()) {
+      runInstances.stratify(m_NumFolds);
+    }
+
+    // Add in some fields to the key like run and fold number, dataset name
+    Object [] seKey = m_SplitEvaluator.getKey();
+    Object [] key = new Object [seKey.length + 3];
+    key[0] =  Utils.backQuoteChars(m_Instances.relationName());
+    key[1] = "" + run;
+    key[2] = "" + (fold + 1);
+    System.arraycopy(seKey, 0, key, 3, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+      Instances train = runInstances.trainCV(m_NumFolds, fold, random);
+      Instances test = runInstances.testCV(m_NumFolds, fold);
+      try {
+        Object [] seResults = m_SplitEvaluator.getResult(train, test);
+        Object [] results = new Object [seResults.length + 1];
+        results[0] = getTimestamp();
+        System.arraycopy(seResults, 0, results, 1,
+                         seResults.length);
+        if (m_debugOutput) {
+          String resultName = (""+run+"."+(fold+1)+"."
+                               + Utils.backQuoteChars(runInstances.relationName())
+                               +"."
+                               +m_SplitEvaluator.toString()).replace(' ','_');
+          resultName = Utils.removeSubstring(resultName, 
+                                             "weka.classifiers.");
+          resultName = Utils.removeSubstring(resultName, 
+                                             "weka.filters.");
+          resultName = Utils.removeSubstring(resultName, 
+                                             "weka.attributeSelection.");
+          m_ZipDest.zipit(m_SplitEvaluator.getRawResultOutput(), resultName);
+        }
+        m_ResultListener.acceptResult(this, key, results);
+      } catch (Exception ex) {
+        // Save the train and test datasets for debugging purposes?
+        throw ex;
+      }
+    }
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "CrossValidationSplitResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5828 $");
+  }
+} // CrossValidationSplitResultProducer
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultListener.java	(revision 29)
@@ -0,0 +1,406 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseResultListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.FastVector;
+import weka.core.RevisionUtils;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+
+/**
+ <!-- globalinfo-start -->
+ * Takes results from a result producer and sends them to a database.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5126 $
+ */
+public class DatabaseResultListener 
+  extends DatabaseUtils
+  implements ResultListener {
+
+  /** for serialization */
+  static final long serialVersionUID = 7388014746954652818L;  
+  
+  /** The ResultProducer to listen to */
+  protected ResultProducer m_ResultProducer;
+  
+  /** The name of the current results table */
+  protected String m_ResultsTableName;
+
+  /** True if debugging output should be printed */
+  protected boolean m_Debug = true;
+
+  /** Holds the name of the key field to cache upon, or null if no caching */
+  protected String m_CacheKeyName = "";
+
+  /** Stores the index of the key column holding the cache key data */
+  protected int m_CacheKeyIndex;
+
+  /** Stores the key for which the cache is valid */
+  protected Object [] m_CacheKey;
+
+  /** Stores the cached values */
+  protected FastVector m_Cache = new FastVector();
+
+
+  /**
+   * Returns a string describing this result listener
+   * @return a description of the result listener suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Takes results from a result producer and sends them to a "
+      +"database.";
+  }
+
+  /**
+   * Sets up the database drivers
+   *
+   * @throws Exception if an error occurs
+   */
+  public DatabaseResultListener() throws Exception {
+
+    super();
+  }
+
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    m_ResultProducer = rp;
+    // Connect to the database and find out what table corresponds to this
+    //   ResultProducer
+    updateResultsTableName(m_ResultProducer);
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more results will be sent that need to be grouped together
+   * in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @throws Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer calling postProcess!!");
+    }
+    disconnectFromDatabase();
+  }
+  
+  /**
+   * Determines if there are any constraints (imposed by the
+   * destination) on any additional measures produced by
+   * resultProducers. Null should be returned if there are NO
+   * constraints, otherwise a list of column names should be
+   * returned as an array of Strings. In the case of
+   * DatabaseResultListener, the structure of an existing database
+   * will impose constraints.
+   * @param rp the ResultProducer to which the constraints will apply
+   * @return an array of column names to which resutltProducer's
+   * results will be restricted.
+   * @throws Exception if an error occurs.
+   */
+  public String [] determineColumnConstraints(ResultProducer rp) 
+    throws Exception {
+    FastVector cNames = new FastVector();
+    updateResultsTableName(rp);
+    DatabaseMetaData dbmd = m_Connection.getMetaData();
+    ResultSet rs;
+    // gets a result set where each row is info on a column
+    if (m_checkForUpperCaseNames) {
+      rs = dbmd.getColumns(null, null, m_ResultsTableName.toUpperCase(), null);
+    } else {
+      rs = dbmd.getColumns(null, null, m_ResultsTableName, null);
+    }
+    boolean tableExists=false;
+    int numColumns = 0;
+   
+    while (rs.next()) {
+      tableExists = true;
+      // column four contains the column name
+      String name = rs.getString(4);
+      if (name.toLowerCase().startsWith("measure")) {
+	numColumns++;
+	cNames.addElement(name);
+      }
+    }
+
+    // no constraints on any additional measures if the table does not exist
+    if (!tableExists) {
+      return null;
+    }
+
+    // a zero element array indicates maximum constraint
+    String [] columnNames = new String [numColumns];
+    for (int i=0;i<numColumns;i++) {
+      columnNames[i] = (String)(cNames.elementAt(i));
+    }
+
+    return columnNames;
+  }
+
+  /**
+   * Submit the result to the appropriate table of the database
+   *
+   * @param rp the ResultProducer that generated the result
+   * @param key The key for the results.
+   * @param result The actual results.
+   * @throws Exception if the result couldn't be sent to the database
+   */
+  public void acceptResult(ResultProducer rp, Object[] key, Object[] result) 
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer calling acceptResult!!");
+    }
+
+    // null result could occur from a chain of doRunKeys calls
+    if (result != null) {
+      putResultInTable(m_ResultsTableName, rp, key, result);      
+    }
+  }
+
+  /**
+   * Always says a result is required. If this is the first call,
+   * prints out the header for the Database output.
+   *
+   * @param rp the ResultProducer wanting to generate the result
+   * @param key The key for which a result may be needed.
+   * @return true if the result should be calculated.
+   * @throws Exception if the database couldn't be queried
+   */
+  public boolean isResultRequired(ResultProducer rp, Object[] key)
+    throws Exception {
+    
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer calling isResultRequired!");
+    }
+    if (m_Debug) {
+      System.err.print("Is result required...");
+      for (int i = 0; i < key.length; i++) {
+	System.err.print(" " + key[i]);
+      }
+      System.err.flush();
+    }
+    boolean retval = false;
+
+    // Check the key cache first
+    if (!m_CacheKeyName.equals("")) {
+      if (!isCacheValid(key)) {
+	loadCache(rp, key);
+      }
+      retval = !isKeyInCache(rp, key);
+    } else {
+      // Ask whether the results are needed
+      retval = !isKeyInTable(m_ResultsTableName,
+					     rp, key);
+    }
+    
+    if (m_Debug) {
+      System.err.println(" ..." + (retval ? "required" : "not required")
+			 + (m_CacheKeyName.equals("") ? "" : " (cache)"));
+      System.err.flush();
+    }
+    return retval;
+  }
+
+  
+  /**
+   * Determines the table name that results will be inserted into. If
+   * required: a connection will be opened, an experiment index table created,
+   * and the results table created.
+   *
+   * @param rp the ResultProducer
+   * @throws Exception if an error occurs
+   */
+  protected void updateResultsTableName(ResultProducer rp) throws Exception {
+
+    if (!isConnected()) {
+      connectToDatabase();
+    }
+    if (!experimentIndexExists()) {
+      createExperimentIndex();
+    }
+
+    String tableName = getResultsTableName(rp);
+    if (tableName == null) {
+      tableName = createExperimentIndexEntry(rp);
+    }
+    if (!tableExists(tableName)) {
+      createResultsTable(rp, tableName);
+    }
+    m_ResultsTableName = tableName;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String cacheKeyNameTipText() {
+    return "Set the name of the key field by which to cache.";
+  }
+  
+  /**
+   * Get the value of CacheKeyName.
+   *
+   * @return Value of CacheKeyName.
+   */
+  public String getCacheKeyName() {
+    
+    return m_CacheKeyName;
+  }
+
+  
+  /**
+   * Set the value of CacheKeyName.
+   *
+   * @param newCacheKeyName Value to assign to CacheKeyName.
+   */
+  public void setCacheKeyName(String newCacheKeyName) {
+    
+    m_CacheKeyName = newCacheKeyName;
+  }
+
+  
+
+  /**
+   * Checks whether the current cache contents are valid for the supplied
+   * key.
+   *
+   * @param key the results key
+   * @return true if the cache contents are valid for the key given
+   */
+  protected boolean isCacheValid(Object []key) {
+
+    if (m_CacheKey == null) {
+      return false;
+    }
+    if (m_CacheKey.length != key.length) {
+      return false;
+    }
+    for (int i = 0; i < key.length; i++) {
+      if ((i != m_CacheKeyIndex) && (!m_CacheKey[i].equals(key[i]))) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if the supplied key is in the key cache (and thus
+   * we do not need to execute a database query).
+   *
+   * @param rp the ResultProducer the key belongs to.
+   * @param key the result key
+   * @return true if the key is in the key cache
+   * @throws Exception if an error occurs
+   */
+  protected boolean isKeyInCache(ResultProducer rp, Object[] key)
+    throws Exception {
+
+    for (int i = 0; i < m_Cache.size(); i++) {
+      if (m_Cache.elementAt(i).equals(key[m_CacheKeyIndex])) {
+	return true;
+      }
+    }
+    return false;
+  }
+  
+  /**
+   * Executes a database query to fill the key cache
+   *
+   * @param rp the ResultProducer the key belongs to
+   * @param key the key
+   * @throws Exception if an error occurs
+   */
+  protected void loadCache(ResultProducer rp, Object[] key)
+    throws Exception {
+
+    System.err.print(" (updating cache)"); System.err.flush();
+    m_Cache.removeAllElements();
+    m_CacheKey = null;
+    String query = "SELECT Key_" + m_CacheKeyName
+      + " FROM " + m_ResultsTableName;
+    String [] keyNames = rp.getKeyNames();
+    if (keyNames.length != key.length) {
+      throw new Exception("Key names and key values of different lengths");
+    }
+    m_CacheKeyIndex = -1;
+    for (int i = 0; i < keyNames.length; i++) {
+      if (keyNames[i].equalsIgnoreCase(m_CacheKeyName)) {
+	m_CacheKeyIndex = i;
+	break;
+      }
+    }
+    if (m_CacheKeyIndex == -1) {
+      throw new Exception("No key field named " + m_CacheKeyName
+			  + " (as specified for caching)");
+    }
+    boolean first = true;
+    for (int i = 0; i < key.length; i++) {
+      if ((key[i] != null) && (i != m_CacheKeyIndex)) {
+	if (first) {
+	  query += " WHERE ";
+	  first = false;
+	} else {
+	  query += " AND ";
+	}
+	query += "Key_" + keyNames[i] + '=';
+	if (key[i] instanceof String) {
+	  query += "'" + DatabaseUtils.processKeyString(key[i].toString()) + "'";
+	} else {
+	  query += key[i].toString();
+	}
+      }
+    }
+    ResultSet rs = select(query);
+    while (rs.next()) {
+      String keyVal = rs.getString(1);
+      if (!rs.wasNull()) {
+	m_Cache.addElement(keyVal);
+      }
+    }
+    close(rs);
+    m_CacheKey = (Object [])key.clone();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5126 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseResultProducer.java	(revision 29)
@@ -0,0 +1,715 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Examines a database and extracts out the results produced by the specified ResultProducer and submits them to the specified ResultListener. If a result needs to be generated, the ResultProducer is used to obtain the result.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -F &lt;field name&gt;
+ *  The name of the database field to cache over.
+ *  eg: "Fold" (default none)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a ResultProducer.
+ *  eg: weka.experiment.CrossValidationResultProducer</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+ * </pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  The number of folds to use for the cross-validation.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.18 $
+ */
+public class DatabaseResultProducer 
+  extends DatabaseResultListener
+  implements ResultProducer, OptionHandler, AdditionalMeasureProducer {
+
+  /** for serialization */
+  static final long serialVersionUID = -5620660780203158666L;
+  
+  /** The dataset of interest */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The ResultProducer used to generate results */
+  protected ResultProducer m_ResultProducer
+    = new CrossValidationResultProducer();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Examines a database and extracts out "
+      +"the results produced by the specified ResultProducer "
+      +"and submits them to the specified ResultListener. If a result needs "
+      +"to be generated, the ResultProducer is used to obtain the result.";
+  }
+
+  /**
+   * Creates the DatabaseResultProducer, letting the parent constructor do
+   * it's thing.
+   *
+   * @throws Exception if an error occurs
+   */
+  public DatabaseResultProducer() throws Exception {
+
+    super();
+  }
+  
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+    m_ResultProducer.setInstances(m_Instances);
+    m_ResultProducer.doRunKeys(run);
+  }
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+    m_ResultProducer.setInstances(m_Instances);
+    m_ResultProducer.doRun(run);
+
+  }
+  
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    super.preProcess(rp);
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    m_ResultListener.preProcess(this);
+  }
+
+  /**
+   * When this method is called, it indicates that no more results
+   * will be sent that need to be grouped together in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @throws Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+
+    super.postProcess(rp);
+    m_ResultListener.postProcess(this);
+  }
+  
+  /**
+   * Prepare to generate results. The ResultProducer should call
+   * preProcess(this) on the ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+    
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    m_ResultProducer.setResultListener(this);
+    m_ResultProducer.preProcess();
+  }
+  
+  /**
+   * When this method is called, it indicates that no more requests to
+   * generate results for the current experiment will be sent. The
+   * ResultProducer should call preProcess(this) on the
+   * ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultProducer.postProcess();
+  }
+    
+  /**
+   * Accepts results from a ResultProducer.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @param result the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if the result could not be accepted.
+   */
+  public void acceptResult(ResultProducer rp, Object [] key, Object [] result)
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    //    System.err.println("DBRP::acceptResult");
+
+    // Is the result needed by the listener?
+    boolean isRequiredByListener = m_ResultListener.isResultRequired(this,
+								     key);
+    // Is the result already in the database?
+    boolean isRequiredByDatabase = super.isResultRequired(rp, key);
+
+    // Insert it into the database here
+    if (isRequiredByDatabase) {
+      // We could alternatively throw an exception if we only want values
+      // that are already in the database
+      if (result != null) {
+
+	// null result could occur from a chain of doRunKeys calls
+	super.acceptResult(rp, key, result);
+      }
+    }
+
+    // Pass it on
+    if (isRequiredByListener) {
+      m_ResultListener.acceptResult(this, key, result);
+    }
+  }
+
+  /**
+   * Determines whether the results for a specified key must be
+   * generated.
+   *
+   * @param rp the ResultProducer wanting to generate the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @return true if the result should be generated
+   * @throws Exception if it could not be determined if the result 
+   * is needed.
+   */
+  public boolean isResultRequired(ResultProducer rp, Object [] key) 
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    //    System.err.println("DBRP::isResultRequired");
+
+    // Is the result needed by the listener?
+    boolean isRequiredByListener = m_ResultListener.isResultRequired(this,
+								     key);
+    // Is the result already in the database?
+    boolean isRequiredByDatabase = super.isResultRequired(rp, key);
+
+    if (!isRequiredByDatabase && isRequiredByListener) {
+      // Pass the result through to the listener
+      Object [] result = getResultFromTable(m_ResultsTableName,
+					    rp, key);
+      System.err.println("Got result from database: "
+			 + DatabaseUtils.arrayToString(result));
+      m_ResultListener.acceptResult(this, key, result);
+      return false;
+    }
+
+    return (isRequiredByListener || isRequiredByDatabase);
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if something goes wrong.
+   */
+  public String [] getKeyNames() throws Exception {
+
+    return m_ResultProducer.getKeyNames();
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if something goes wrong.
+   */
+  public Object [] getKeyTypes() throws Exception {
+
+    return m_ResultProducer.getKeyTypes();
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * A new result field is added for the number of results used to
+   * produce each average.
+   * If only averages are being produced the names are not altered, if
+   * standard deviations are produced then "Dev_" and "Avg_" are prepended
+   * to each result deviation and average field respectively.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if something goes wrong.
+   */
+  public String [] getResultNames() throws Exception {
+
+    return m_ResultProducer.getResultNames();
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if something goes wrong.
+   */
+  public Object [] getResultTypes() throws Exception {
+
+    return m_ResultProducer.getResultTypes();
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  public String getCompatibilityState() {
+
+    String result = "";
+    if (m_ResultProducer == null) {
+      result += "<null ResultProducer>";
+    } else {
+      result += "-W " + m_ResultProducer.getClass().getName();
+    }
+    result  += " -- " + m_ResultProducer.getCompatibilityState();
+    return result.trim();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	     "\tThe name of the database field to cache over.\n"
+	      +"\teg: \"Fold\" (default none)", 
+	     "F", 1, 
+	     "-F <field name>"));
+    newVector.addElement(new Option(
+	     "\tThe full class name of a ResultProducer.\n"
+	      +"\teg: weka.experiment.CrossValidationResultProducer", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    if ((m_ResultProducer != null) &&
+	(m_ResultProducer instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to result producer "
+	     + m_ResultProducer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_ResultProducer).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -F &lt;field name&gt;
+   *  The name of the database field to cache over.
+   *  eg: "Fold" (default none)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a ResultProducer.
+   *  eg: weka.experiment.CrossValidationResultProducer</pre>
+   * 
+   * <pre> 
+   * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+   * </pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  The number of folds to use for the cross-validation.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All option after -- will be passed to the result producer.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    setCacheKeyName(Utils.getOption('F', options));
+    
+    String rpName = Utils.getOption('W', options);
+    if (rpName.length() == 0) {
+      throw new Exception("A ResultProducer must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // RP.
+    setResultProducer((ResultProducer)Utils.forName(
+		      ResultProducer.class,
+		      rpName,
+		      null));
+    if (getResultProducer() instanceof OptionHandler) {
+      ((OptionHandler) getResultProducer())
+	.setOptions(Utils.partitionOptions(options));
+    }
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] seOptions = new String [0];
+    if ((m_ResultProducer != null) && 
+	(m_ResultProducer instanceof OptionHandler)) {
+      seOptions = ((OptionHandler)m_ResultProducer).getOptions();
+    }
+    
+    String [] options = new String [seOptions.length + 8];
+    int current = 0;
+
+    if (!getCacheKeyName().equals("")) {
+      options[current++] = "-F";
+      options[current++] = getCacheKeyName();
+    }
+    if (getResultProducer() != null) {
+      options[current++] = "-W";
+      options[current++] = getResultProducer().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(seOptions, 0, options, current, 
+		     seOptions.length);
+    current += seOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current resultProducer) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_ResultProducer != null) {
+      System.err.println("DatabaseResultProducer: setting additional "
+			 +"measures for "
+			 +"ResultProducer");
+      m_ResultProducer.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the result producer
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_ResultProducer).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_ResultProducer).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("DatabaseResultProducer: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_ResultProducer.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+  
+  
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    
+    m_Instances = instances;
+  }
+  
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+
+    m_ResultListener = listener;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String resultProducerTipText() {
+    return "Set the result producer to use. If some results are not found "
+      +"in the source database then this result producer is used to generate "
+      +"them.";
+  }
+  
+  /**
+   * Get the ResultProducer.
+   *
+   * @return the ResultProducer.
+   */
+  public ResultProducer getResultProducer() {
+    
+    return m_ResultProducer;
+  }
+  
+  /**
+   * Set the ResultProducer.
+   *
+   * @param newResultProducer new ResultProducer to use.
+   */
+  public void setResultProducer(ResultProducer newResultProducer) {
+    
+    m_ResultProducer = newResultProducer;
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "DatabaseResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.18 $");
+  }
+} // DatabaseResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.java	(revision 29)
@@ -0,0 +1,1394 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseUtils.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * DatabaseUtils provides utility functions for accessing the experiment
+ * database. The jdbc
+ * driver and database to be used default to "jdbc.idbDriver" and
+ * "jdbc:idb=experiments.prp". These may be changed by creating
+ * a java properties file called DatabaseUtils.props in user.home or
+ * the current directory. eg:<p>
+ *
+ * <code><pre>
+ * jdbcDriver=jdbc.idbDriver
+ * jdbcURL=jdbc:idb=experiments.prp
+ * </pre></code><p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5238 $
+ */
+public class DatabaseUtils
+  implements Serializable, RevisionHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = -8252351994547116729L;
+  
+  /** The name of the table containing the index to experiments. */
+  public static final String EXP_INDEX_TABLE = "Experiment_index";
+
+  /** The name of the column containing the experiment type (ResultProducer). */
+  public static final String EXP_TYPE_COL = "Experiment_type";
+
+  /** The name of the column containing the experiment setup (parameters). */
+  public static final String EXP_SETUP_COL = "Experiment_setup";
+  
+  /** The name of the column containing the results table name. */
+  public static final String EXP_RESULT_COL = "Result_table";
+
+  /** The prefix for result table names. */
+  public static final String EXP_RESULT_PREFIX = "Results";
+
+  /** The name of the properties file. */
+  public final static String PROPERTY_FILE = "weka/experiment/DatabaseUtils.props";
+
+  /** Holds the jdbc drivers to be used (only to stop them being gc'ed). */
+  protected Vector DRIVERS = new Vector();
+
+  /** keeping track of drivers that couldn't be loaded. */
+  protected static Vector DRIVERS_ERRORS;
+
+  /** Properties associated with the database connection. */
+  protected Properties PROPERTIES;
+
+  /* Type mapping used for reading experiment results */
+  /** Type mapping for STRING used for reading experiment results. */
+  public static final int STRING = 0;
+  /** Type mapping for BOOL used for reading experiment results. */
+  public static final int BOOL = 1;
+  /** Type mapping for DOUBLE used for reading experiment results. */
+  public static final int DOUBLE = 2;
+  /** Type mapping for BYTE used for reading experiment results. */
+  public static final int BYTE = 3;
+  /** Type mapping for SHORT used for reading experiment results. */
+  public static final int SHORT = 4;
+  /** Type mapping for INTEGER used for reading experiment results. */
+  public static final int INTEGER = 5;
+  /** Type mapping for LONG used for reading experiment results. */
+  public static final int LONG = 6;
+  /** Type mapping for FLOAT used for reading experiment results. */
+  public static final int FLOAT = 7;
+  /** Type mapping for DATE used for reading experiment results. */
+  public static final int DATE = 8; 
+  /** Type mapping for TEXT used for reading, e.g., text blobs. */
+  public static final int TEXT = 9; 
+  /** Type mapping for TIME used for reading TIME columns. */
+  public static final int TIME = 10; 
+  
+  /** Database URL. */
+  protected String m_DatabaseURL;
+ 
+  /** The prepared statement used for database queries. */
+  protected transient PreparedStatement m_PreparedStatement;
+   
+  /** The database connection. */
+  protected transient Connection m_Connection;
+
+  /** True if debugging output should be printed. */
+  protected boolean m_Debug = false;
+  
+  /** Database username. */
+  protected String m_userName = "";
+
+  /** Database Password. */
+  protected String m_password = "";
+
+  /* mappings used for creating Tables. Can be overridden in DatabaseUtils.props*/
+  /** string type for the create table statement. */
+  protected String m_stringType = "LONGVARCHAR";
+  /** integer type for the create table statement. */
+  protected String m_intType = "INT";
+  /** double type for the create table statement. */
+  protected String m_doubleType = "DOUBLE";
+
+  /** For databases where Tables and Columns are created in upper case. */
+  protected boolean m_checkForUpperCaseNames = false;
+
+  /** For databases where Tables and Columns are created in lower case. */
+  protected boolean m_checkForLowerCaseNames = false;
+
+  /** setAutoCommit on the database? */
+  protected boolean m_setAutoCommit = true;
+
+  /** create index on the database? */
+  protected boolean m_createIndex = false;
+
+  /** the keywords for the current database type. */
+  protected HashSet<String> m_Keywords = new HashSet<String>();
+
+  /** the character to mask SQL keywords (by appending this character). */
+  protected String m_KeywordsMaskChar = "_";
+  
+  /**
+   * Reads properties and sets up the database drivers.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  public DatabaseUtils() throws Exception {
+    if (DRIVERS_ERRORS == null)
+      DRIVERS_ERRORS = new Vector();
+
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+
+      // Register the drivers in jdbc DriverManager
+      String drivers = PROPERTIES.getProperty("jdbcDriver", "jdbc.idbDriver");
+
+      if (drivers == null) {
+        throw new Exception("No database drivers (JDBC) specified");
+      }
+      // The call to newInstance() is necessary on some platforms
+      // (with some java VM implementations)
+      StringTokenizer st = new StringTokenizer(drivers, ", ");
+      while (st.hasMoreTokens()) {
+        String driver = st.nextToken();
+        boolean result;
+        try {
+          Class.forName(driver);
+          DRIVERS.addElement(driver);
+          result = true;
+        }
+        catch (Exception e) {
+          result = false;
+        }
+        if (m_Debug || (!result && !DRIVERS_ERRORS.contains(driver))) 
+          System.err.println(
+              "Trying to add database driver (JDBC): " + driver 
+              + " - " + (result ? "Success!" : "Error, not in CLASSPATH?"));
+        if (!result)
+          DRIVERS_ERRORS.add(driver);
+      }
+    } catch (Exception ex) {
+      System.err.println("Problem reading properties. Fix before continuing.");
+      System.err.println(ex);
+    }
+
+    m_DatabaseURL = PROPERTIES.getProperty("jdbcURL", "jdbc:idb=experiments.prp");
+    m_stringType  = PROPERTIES.getProperty("CREATE_STRING", "LONGVARCHAR");
+    m_intType     = PROPERTIES.getProperty("CREATE_INT", "INT");
+    m_doubleType  = PROPERTIES.getProperty("CREATE_DOUBLE", "DOUBLE");
+    m_checkForUpperCaseNames = PROPERTIES.getProperty(
+				"checkUpperCaseNames", "false").equals("true");
+    m_checkForLowerCaseNames = PROPERTIES.getProperty(
+				"checkLowerCaseNames", "false").equals("true");
+    m_setAutoCommit = PROPERTIES.getProperty(
+			"setAutoCommit", "false").equals("true");
+    m_createIndex   = PROPERTIES.getProperty(
+			"createIndex", "false").equals("true");
+    setKeywords(PROPERTIES.getProperty(
+			"Keywords", "AND,ASC,BY,DESC,FROM,GROUP,INSERT,ORDER,SELECT,UPDATE,WHERE"));
+    setKeywordsMaskChar(PROPERTIES.getProperty("KeywordsMaskChar", "_"));
+  }
+  
+  /** 
+   * returns key column headings in their original case. Used for
+   * those databases that create uppercase column names.
+   * 
+   * @param columnName	the column to retrieve the original case for
+   * @return		the original case
+   */
+  protected String attributeCaseFix(String columnName){
+    if (m_checkForUpperCaseNames) {
+      String ucname = columnName.toUpperCase();
+      if (ucname.equals(EXP_TYPE_COL.toUpperCase())) {
+	return EXP_TYPE_COL;
+      } else if (ucname.equals(EXP_SETUP_COL.toUpperCase())) {
+	return EXP_SETUP_COL;
+      } else if (ucname.equals(EXP_RESULT_COL.toUpperCase())) {
+	return EXP_RESULT_COL;
+      } else {
+	return columnName;
+      }
+    }
+    else if (m_checkForLowerCaseNames) {
+      String ucname = columnName.toLowerCase();
+      if (ucname.equals(EXP_TYPE_COL.toLowerCase())) {
+	return EXP_TYPE_COL;
+      } else if (ucname.equals(EXP_SETUP_COL.toLowerCase())) {
+	return EXP_SETUP_COL;
+      } else if (ucname.equals(EXP_RESULT_COL.toLowerCase())) {
+	return EXP_RESULT_COL;
+      } else {
+	return columnName;
+      }
+    }
+    else {
+      return columnName;
+    }
+  }
+ 
+  /**
+   * translates the column data type string to an integer value that indicates
+   * which data type / get()-Method to use in order to retrieve values from the
+   * database (see DatabaseUtils.Properties, InstanceQuery()). Blanks in the type 
+   * are replaced with underscores "_", since Java property names can't contain blanks.
+   * 
+   * @param type 	the column type as retrieved with 
+   * 			java.sql.MetaData.getColumnTypeName(int)
+   * @return 		an integer value that indicates
+   * 			which data type / get()-Method to use in order to 
+   * 			retrieve values from the
+   */
+  public int translateDBColumnType(String type) {
+    try {
+      // Oracle, e.g., has datatypes like "DOUBLE PRECISION"
+      // BUT property names can't have blanks in the name (unless escaped with
+      // a backslash), hence also check for names where the blanks are 
+      // replaced with underscores "_":
+      String value = PROPERTIES.getProperty(type);
+      String typeUnderscore = type.replaceAll(" ", "_");
+      if (value == null)
+	value = PROPERTIES.getProperty(typeUnderscore);
+      return Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException(
+	  "Unknown data type: " + type + ". "
+	  + "Add entry in " + PROPERTY_FILE + ".\n"
+	  + "If the type contains blanks, either escape them with a backslash "
+	  + "or use underscores instead of blanks.");
+    }
+  }
+
+  /**
+   * Converts an array of objects to a string by inserting a space
+   * between each element. Null elements are printed as ?
+   *
+   * @param array 	the array of objects
+   * @return 		a value of type 'String'
+   */
+  public static String arrayToString(Object[] array) {
+    String result = "";
+    if (array == null) {
+      result = "<null>";
+    } else {
+      for (int i = 0; i < array.length; i++) {
+	if (array[i] == null) {
+	  result += " ?";
+	} else {
+	  result += " " + array[i];
+	}
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the name associated with a SQL type.
+   *
+   * @param type 	the SQL type
+   * @return 		the name of the type
+   */
+  public static String typeName(int type) {
+    switch (type) {
+      case Types.BIGINT :
+	return "BIGINT ";
+      case Types.BINARY:
+	return "BINARY";
+      case Types.BIT:
+	return "BIT";
+      case Types.CHAR:
+	return "CHAR";
+      case Types.DATE:
+	return "DATE";
+      case Types.DECIMAL:
+	return "DECIMAL";
+      case Types.DOUBLE:
+	return "DOUBLE";
+      case Types.FLOAT:
+	return "FLOAT";
+      case Types.INTEGER:
+	return "INTEGER";
+      case Types.LONGVARBINARY:
+	return "LONGVARBINARY";
+      case Types.LONGVARCHAR:
+	return "LONGVARCHAR";
+      case Types.NULL:
+	return "NULL";
+      case Types.NUMERIC:
+	return "NUMERIC";
+      case Types.OTHER:
+	return "OTHER";
+      case Types.REAL:
+	return "REAL";
+      case Types.SMALLINT:
+	return "SMALLINT";
+      case Types.TIME:
+	return "TIME";
+      case Types.TIMESTAMP:
+	return "TIMESTAMP";
+      case Types.TINYINT:
+	return "TINYINT";
+      case Types.VARBINARY:
+	return "VARBINARY";
+      case Types.VARCHAR:
+	return "VARCHAR";
+      default:
+	return "Unknown";
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String databaseURLTipText() {
+    return "Set the URL to the database.";
+  }
+
+  /**
+   * Get the value of DatabaseURL.
+   *
+   * @return 		Value of DatabaseURL.
+   */
+  public String getDatabaseURL() {
+    return m_DatabaseURL;
+  }
+  
+  /**
+   * Set the value of DatabaseURL.
+   *
+   * @param newDatabaseURL 	Value to assign to DatabaseURL.
+   */
+  public void setDatabaseURL(String newDatabaseURL) {
+    m_DatabaseURL = newDatabaseURL;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Whether debug information is printed.";
+  }
+  
+  /**
+   * Sets whether there should be printed some debugging output to stderr or not.
+   * 
+   * @param d 		true if output should be printed
+   */
+  public void setDebug(boolean d) {
+    m_Debug = d;
+  }
+
+  /**
+   * Gets whether there should be printed some debugging output to stderr or not.
+   * 
+   * @return 		true if output should be printed
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String usernameTipText() {
+    return "The user to use for connecting to the database.";
+  }
+
+  /** 
+   * Set the database username.
+   *
+   * @param username 	Username for Database.
+   */
+  public void setUsername(String username){
+    m_userName = username; 
+  }
+  
+  /** 
+   * Get the database username.
+   *
+   * @return 		Database username
+   */
+  public String getUsername(){
+    return m_userName;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String passwordTipText() {
+    return "The password to use for connecting to the database.";
+  }
+
+  /** 
+   * Set the database password.
+   *
+   * @param password 	Password for Database.
+   */
+  public void setPassword(String password){
+    m_password = password;
+  }
+  
+  /** 
+   * Get the database password.
+   *
+   * @return  		Password for Database.
+   */
+  public String getPassword(){
+    return m_password;
+  }
+
+  /**
+   * Opens a connection to the database.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  public void connectToDatabase() throws Exception {
+    if (m_Debug) {
+      System.err.println("Connecting to " + m_DatabaseURL);
+    }
+    if (m_Connection == null) {
+      if (m_userName.equals("")) {
+	try {
+	  m_Connection = DriverManager.getConnection(m_DatabaseURL);
+	} catch (java.sql.SQLException e) {
+	  
+	  // Try loading the drivers
+	  for (int i = 0; i < DRIVERS.size(); i++) {
+	    try {
+	      Class.forName((String)DRIVERS.elementAt(i));
+	    } catch (Exception ex) {
+	      // Drop through
+	    }
+	  }
+	  m_Connection = DriverManager.getConnection(m_DatabaseURL);
+	}
+      } else {
+	try {
+	  m_Connection = DriverManager.getConnection(m_DatabaseURL, m_userName,
+						     m_password);
+	} catch (java.sql.SQLException e) {
+	  
+	  // Try loading the drivers
+	  for (int i = 0; i < DRIVERS.size(); i++) {
+	    try {
+	      Class.forName((String)DRIVERS.elementAt(i));
+	    } catch (Exception ex) {
+	      // Drop through
+	    }
+	  }
+	  m_Connection = DriverManager.getConnection(m_DatabaseURL, m_userName,
+						     m_password);
+	}
+      }
+    }
+    m_Connection.setAutoCommit(m_setAutoCommit);
+  }
+
+  /**
+   * Closes the connection to the database.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  public void disconnectFromDatabase() throws Exception {
+    if (m_Debug) {
+      System.err.println("Disconnecting from " + m_DatabaseURL);
+    }
+    if (m_Connection != null) {
+      m_Connection.close();
+      m_Connection = null;
+    }
+  }
+  
+  /**
+   * Returns true if a database connection is active.
+   *
+   * @return 		a value of type 'boolean'
+   */
+  public boolean isConnected() {
+    return (m_Connection != null);
+  }
+
+  /**
+   * Returns whether the cursors only support forward movement or are
+   * scroll sensitive (with ResultSet.CONCUR_READ_ONLY concurrency).
+   * Returns always false if not connected
+   * 
+   * @return		true if connected and the cursor is scroll-sensitive
+   * @see		ResultSet#TYPE_SCROLL_SENSITIVE
+   * @see		ResultSet#TYPE_FORWARD_ONLY
+   * @see		ResultSet#CONCUR_READ_ONLY
+   */
+  public boolean isCursorScrollSensitive() {
+    boolean	result;
+    
+    result = false;
+    
+    try {
+      if (isConnected())
+	result = m_Connection.getMetaData().supportsResultSetConcurrency(
+	    		ResultSet.TYPE_SCROLL_SENSITIVE, 
+	    		ResultSet.CONCUR_READ_ONLY);
+    }
+    catch (Exception e) {
+      // ignored
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Checks whether cursors are scrollable in general, false otherwise 
+   * (also if not connected).
+   * 
+   * @return		true if scrollable and connected
+   * @see		#getSupportedCursorScrollType()
+   */
+  public boolean isCursorScrollable() {
+    return (getSupportedCursorScrollType() != -1);
+  }
+  
+  /**
+   * Returns the type of scrolling that the cursor supports, -1 if not
+   * supported or not connected. Checks first for TYPE_SCROLL_SENSITIVE
+   * and then for TYPE_SCROLL_INSENSITIVE. In both cases CONCUR_READ_ONLY
+   * as concurrency is used.
+   * 
+   * @return		the scroll type, or -1 if not connected or no scrolling supported
+   * @see		ResultSet#TYPE_SCROLL_SENSITIVE
+   * @see		ResultSet#TYPE_SCROLL_INSENSITIVE
+   */
+  public int getSupportedCursorScrollType() {
+    int		result;
+    
+    result = -1;
+    
+    try {
+      if (isConnected()) {
+	if (m_Connection.getMetaData().supportsResultSetConcurrency(
+	    		ResultSet.TYPE_SCROLL_SENSITIVE, 
+	    		ResultSet.CONCUR_READ_ONLY))
+	  result = ResultSet.TYPE_SCROLL_SENSITIVE;
+	
+	if (result == -1) {
+	  if (m_Connection.getMetaData().supportsResultSetConcurrency(
+	      		ResultSet.TYPE_SCROLL_INSENSITIVE, 
+	      		ResultSet.CONCUR_READ_ONLY))
+	    result = ResultSet.TYPE_SCROLL_INSENSITIVE;
+	}
+      }
+    }
+    catch (Exception e) {
+      // ignored
+    }
+    
+    return result;
+  }
+
+  /**
+   * Executes a SQL query. Caller must clean up manually with 
+   * <code>close()</code>.
+   *
+   * @param query 	the SQL query
+   * @return 		true if the query generated results
+   * @throws SQLException if an error occurs
+   * @see #close()
+   */
+  public boolean execute(String query) throws SQLException {
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    if (!isCursorScrollable())
+      m_PreparedStatement = m_Connection.prepareStatement(
+	  query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+    else
+      m_PreparedStatement = m_Connection.prepareStatement(
+	  query, getSupportedCursorScrollType(), ResultSet.CONCUR_READ_ONLY);
+    
+    return(m_PreparedStatement.execute());
+  }
+
+  /**
+   * Gets the results generated by a previous query. Caller must clean up 
+   * manually with <code>close(ResultSet)</code>. Returns null if object has
+   * been deserialized.
+   *
+   * @return 		the result set.
+   * @throws SQLException if an error occurs
+   * @see #close(ResultSet)
+   */
+  public ResultSet getResultSet() throws SQLException {
+    if (m_PreparedStatement != null)
+      return m_PreparedStatement.getResultSet();
+    else
+      return null;
+  }
+
+  /**
+   * Executes a SQL DDL query or an INSERT, DELETE or UPDATE.
+   *
+   * @param query 	the SQL DDL query
+   * @return 		the number of affected rows
+   * @throws SQLException if an error occurs
+   */
+  public int update(String query) throws SQLException {
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    Statement statement;
+    if (!isCursorScrollable())
+      statement = m_Connection.createStatement(
+	  ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+    else
+      statement = m_Connection.createStatement(
+	  getSupportedCursorScrollType(), ResultSet.CONCUR_READ_ONLY);
+    int result = statement.executeUpdate(query);
+    statement.close();
+    
+    return result;
+  }
+
+  /**
+   * Executes a SQL SELECT query that returns a ResultSet. Note: the ResultSet
+   * object must be closed by the caller.
+   *
+   * @param query 	the SQL query
+   * @return 		the generated ResultSet
+   * @throws SQLException if an error occurs
+   */
+  public ResultSet select(String query) throws SQLException {
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    Statement statement;
+    if (!isCursorScrollable())
+      statement = m_Connection.createStatement(
+	  ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+    else
+      statement = m_Connection.createStatement(
+	  getSupportedCursorScrollType(), ResultSet.CONCUR_READ_ONLY);
+    ResultSet result = statement.executeQuery(query);
+    
+    return result;
+  }
+
+  /**
+   * closes the ResultSet and the statement that generated the ResultSet to
+   * avoid memory leaks in JDBC drivers - in contrast to the JDBC specs, a lot
+   * of JDBC drives don't clean up correctly.
+   * 
+   * @param rs		the ResultSet to clean up
+   */
+  public void close(ResultSet rs) {
+    try {
+      Statement statement = rs.getStatement();
+      rs.close();
+      statement.close();
+      statement = null;
+      rs = null;
+    }
+    catch (Exception e) {
+      // ignored
+    }
+  }
+  
+  /**
+   * closes the m_PreparedStatement to avoid memory leaks.
+   */
+  public void close() {
+    if (m_PreparedStatement != null) {
+      try {
+	m_PreparedStatement.close();
+	m_PreparedStatement = null;
+      }
+      catch (Exception e) {
+	// ignored
+      }
+    }
+  }
+  
+  /**
+   * Checks that a given table exists.
+   *
+   * @param tableName 	the name of the table to look for.
+   * @return 		true if the table exists.
+   * @throws Exception 	if an error occurs.
+   */
+  public boolean tableExists(String tableName) throws Exception {
+    if (!isConnected())
+      throw new IllegalStateException("Not connected, please connect first!");
+    
+    if (m_Debug) {
+      System.err.println("Checking if table " + tableName + " exists...");
+    }
+    DatabaseMetaData dbmd = m_Connection.getMetaData();
+    ResultSet rs;
+    if (m_checkForUpperCaseNames) {
+      rs = dbmd.getTables (null, null, tableName.toUpperCase(), null);
+    } else if (m_checkForLowerCaseNames) {
+      rs = dbmd.getTables (null, null, tableName.toLowerCase(), null);
+    } else {
+      rs = dbmd.getTables (null, null, tableName, null);
+    }
+    boolean tableExists = rs.next();
+    if (rs.next()) {
+      throw new Exception("This table seems to exist more than once!");
+    }
+    rs.close();
+    if (m_Debug) {
+      if (tableExists) {
+	System.err.println("... " + tableName + " exists");
+      } else {
+	System.err.println("... " + tableName + " does not exist");
+      }
+    }
+    return tableExists;
+  }
+
+  /**
+   * processes the string in such a way that it can be stored in the
+   * database, i.e., it changes backslashes into slashes and doubles single 
+   * quotes.
+   * 
+   * @param s		the string to work on
+   * @return		the processed string
+   */
+  public static String processKeyString(String s) {
+    return s.replaceAll("\\\\", "/").replaceAll("'", "''");
+  }
+  
+  /**
+   * Executes a database query to see whether a result for the supplied key
+   * is already in the database.           
+   *
+   * @param tableName 	the name of the table to search for the key in
+   * @param rp 		the ResultProducer who will generate the result if 
+   * 			required
+   * @param key 	the key for the result
+   * @return 		true if the result with that key is in the database 
+   * 			already
+   * @throws Exception 	if an error occurs
+   */
+  protected boolean isKeyInTable(String tableName,
+				 ResultProducer rp,
+				 Object[] key)
+    throws Exception {
+
+    String query = "SELECT Key_Run"
+      + " FROM " + tableName;
+    String [] keyNames = rp.getKeyNames();
+    if (keyNames.length != key.length) {
+      throw new Exception("Key names and key values of different lengths");
+    }
+    boolean first = true;
+    for (int i = 0; i < key.length; i++) {
+      if (key[i] != null) {
+	if (first) {
+	  query += " WHERE ";
+	  first = false;
+	} else {
+	  query += " AND ";
+	}
+	query += "Key_" + keyNames[i] + '=';
+	if (key[i] instanceof String) {
+	  query += "'" + processKeyString(key[i].toString()) + "'";
+	} else {
+	  query += key[i].toString();
+	}
+      }
+    }
+    boolean retval = false;
+    ResultSet rs = select(query);
+    if (rs.next()) {
+      retval = true;
+      if (rs.next()) {
+	throw new Exception("More than one result entry "
+	    + "for result key: " + query);
+      }
+    }
+    close(rs);
+    return retval;
+  }
+
+  /**
+   * Executes a database query to extract a result for the supplied key
+   * from the database.           
+   *
+   * @param tableName 	the name of the table where the result is stored
+   * @param rp 		the ResultProducer who will generate the result if 
+   * 			required
+   * @param key 	the key for the result
+   * @return 		true if the result with that key is in the database 
+   * 			already
+   * @throws Exception 	if an error occurs
+   */
+  public Object[] getResultFromTable(String tableName,
+					 ResultProducer rp,
+					 Object [] key)
+    throws Exception {
+
+    String query = "SELECT ";
+    String [] resultNames = rp.getResultNames();
+    for (int i = 0; i < resultNames.length; i++) {
+      if (i != 0) {
+	query += ", ";
+      }
+      query += resultNames[i];
+    }
+    query += " FROM " + tableName;
+    String [] keyNames = rp.getKeyNames();
+    if (keyNames.length != key.length) {
+      throw new Exception("Key names and key values of different lengths");
+    }
+    boolean first = true;
+    for (int i = 0; i < key.length; i++) {
+      if (key[i] != null) {
+	if (first) {
+	  query += " WHERE ";
+	  first = false;
+	} else {
+	  query += " AND ";
+	}
+	query += "Key_" + keyNames[i] + '=';
+	if (key[i] instanceof String) {
+	  query += "'" + processKeyString(key[i].toString()) + "'";
+	} else {
+	  query += key[i].toString();
+	}
+      }
+    }
+    ResultSet rs = select(query);
+    ResultSetMetaData md = rs.getMetaData();
+    int numAttributes = md.getColumnCount();
+    if (!rs.next()) {
+      throw new Exception("No result for query: " + query);
+    }
+    // Extract the columns for the result
+    Object [] result = new Object [numAttributes];
+    for(int i = 1; i <= numAttributes; i++) {
+      switch (translateDBColumnType(md.getColumnTypeName(i))) {
+	case STRING : 
+	  result[i - 1] = rs.getString(i);
+	  if (rs.wasNull()) {
+	    result[i - 1] = null;
+	  }
+	  break;
+	case FLOAT:
+	case DOUBLE:
+	  result[i - 1] = new Double(rs.getDouble(i));
+	  if (rs.wasNull()) {
+	    result[i - 1] = null;
+	  }
+	  break;
+	default:
+	  throw new Exception("Unhandled SQL result type (field " + (i + 1)
+	      + "): "
+	      + DatabaseUtils.typeName(md.getColumnType(i)));
+      }
+    }
+    if (rs.next()) {
+      throw new Exception("More than one result entry "
+			  + "for result key: " + query);
+    }
+    close(rs);
+    return result;
+  }
+
+  /**
+   * Executes a database query to insert a result for the supplied key
+   * into the database.           
+   *
+   * @param tableName 	the name of the table where the result is stored
+   * @param rp 		the ResultProducer who will generate the result if 
+   * 			required
+   * @param key 	the key for the result
+   * @param result 	the result to store
+   * @throws Exception 	if an error occurs
+   */
+  public void putResultInTable(String tableName,
+			       ResultProducer rp,
+			       Object [] key,
+			       Object [] result)
+    throws Exception {
+    
+    String query = "INSERT INTO " + tableName
+      + " VALUES ( ";
+    // Add the results to the table
+    for (int i = 0; i < key.length; i++) {
+      if (i != 0) {
+	query += ',';
+      }
+      if (key[i] != null) {
+	if (key[i] instanceof String) {
+	  query += "'" + processKeyString(key[i].toString()) + "'";
+	} else if (key[i] instanceof Double) {
+	  query += safeDoubleToString((Double)key[i]);
+	} else {
+	  query += key[i].toString();
+	}
+      } else {
+	query += "NULL";
+      }
+    }
+    for (int i = 0; i < result.length; i++) {
+      query +=  ',';
+      if (result[i] != null) {
+	if (result[i] instanceof String) {
+	  query += "'" + result[i].toString() + "'";
+	} else  if (result[i] instanceof Double) {
+	  query += safeDoubleToString((Double)result[i]);
+	} else {
+	  query += result[i].toString();
+	  //!!
+	  //System.err.println("res: "+ result[i].toString());
+	}
+      } else {
+	query += "NULL";
+      }
+    }
+    query += ')';
+    
+    if (m_Debug) {
+      System.err.println("Submitting result: " + query);
+    }
+    update(query);
+    close();
+  }
+  
+  /**
+   * Inserts a + if the double is in scientific notation.
+   * MySQL doesn't understand the number otherwise.
+   * 
+   * @param number	the number to convert
+   * @return		the number as string
+   */
+  private String safeDoubleToString(Double number) {
+    // NaN is treated as NULL
+    if (number.isNaN())
+      return "NULL";
+
+    String orig = number.toString();
+
+    int pos = orig.indexOf('E');
+    if ((pos == -1) || (orig.charAt(pos + 1) == '-')) {
+      return orig;
+    } else {
+      StringBuffer buff = new StringBuffer(orig);
+      buff.insert(pos + 1, '+');
+      return new String(buff);
+    }
+  }
+
+  /**
+   * Returns true if the experiment index exists.
+   *
+   * @return 		true if the index exists
+   * @throws Exception 	if an error occurs
+   */
+  public boolean experimentIndexExists() throws Exception {
+    return tableExists(EXP_INDEX_TABLE);
+  }
+  
+  /**
+   * Attempts to create the experiment index table.
+   *
+   * @throws Exception 	if an error occurs.
+   */
+  public void createExperimentIndex() throws Exception {
+    if (m_Debug) {
+      System.err.println("Creating experiment index table...");
+    }
+    String query;
+
+    // Workaround for MySQL (doesn't support LONGVARBINARY)
+    // Also for InstantDB which attempts to interpret numbers when storing
+    // in LONGVARBINARY
+    /* if (m_Connection.getMetaData().getDriverName().
+	equals("Mark Matthews' MySQL Driver")
+	|| (m_Connection.getMetaData().getDriverName().
+	indexOf("InstantDB JDBC Driver") != -1)) {
+      query = "CREATE TABLE " + EXP_INDEX_TABLE 
+	+ " ( " + EXP_TYPE_COL + " TEXT,"
+	+ "  " + EXP_SETUP_COL + " TEXT,"
+	+ "  " + EXP_RESULT_COL + " INT )";
+	} else { */
+    
+      query = "CREATE TABLE " + EXP_INDEX_TABLE 
+	+ " ( " + EXP_TYPE_COL + " "+ m_stringType+","
+	+ "  " + EXP_SETUP_COL + " "+ m_stringType+","
+	+ "  " + EXP_RESULT_COL + " "+ m_intType+" )";
+      // }
+    // Other possible fields:
+    //   creator user name (from System properties)
+    //   creation date
+    update(query);
+    close();
+  }
+
+  /**
+   * Attempts to insert a results entry for the table into the
+   * experiment index.
+   *
+   * @param rp 		the ResultProducer generating the results
+   * @return 		the name of the created results table
+   * @throws Exception 	if an error occurs.
+   */
+  public String createExperimentIndexEntry(ResultProducer rp)
+    throws Exception {
+
+    if (m_Debug) {
+      System.err.println("Creating experiment index entry...");
+    }
+
+    // Execute compound transaction
+    int numRows = 0;
+    
+    // Workaround for MySQL (doesn't support transactions)
+    /*  if (m_Connection.getMetaData().getDriverName().
+	equals("Mark Matthews' MySQL Driver")) {
+      m_Statement.execute("LOCK TABLES " + EXP_INDEX_TABLE + " WRITE");
+      System.err.println("LOCKING TABLE");
+      } else {*/
+      
+      //}
+
+    // Get the number of rows
+    String query = "SELECT COUNT(*) FROM " + EXP_INDEX_TABLE;
+    ResultSet rs = select(query);
+    if (m_Debug) {
+      System.err.println("...getting number of rows");
+    }
+    if (rs.next()) {
+      numRows = rs.getInt(1);
+    }
+    close(rs);
+
+    // Add an entry in the index table
+    String expType = rp.getClass().getName();
+    String expParams = rp.getCompatibilityState();
+    query = "INSERT INTO " + EXP_INDEX_TABLE
+      +" VALUES ('"
+      + expType + "', '" + expParams
+      + "', " + numRows + " )"; 
+    if (update(query) > 0) {
+      if (m_Debug) {
+	System.err.println("...create returned resultset");
+      }
+    }
+    close();
+    
+    // Finished compound transaction
+    // Workaround for MySQL (doesn't support transactions)
+    /* if (m_Connection.getMetaData().getDriverName().
+	equals("Mark Matthews' MySQL Driver")) {
+      m_Statement.execute("UNLOCK TABLES");
+      System.err.println("UNLOCKING TABLE");
+      } else { */
+    if (!m_setAutoCommit) {
+      m_Connection.commit();
+      m_Connection.setAutoCommit(true);
+    }
+      //}
+
+    String tableName = getResultsTableName(rp);
+    if (tableName == null) {
+      throw new Exception("Problem adding experiment index entry");
+    }
+
+    // Drop any existing table by that name (shouldn't occur unless
+    // the experiment index is destroyed, in which case the experimental
+    // conditions of the existing table are unknown)
+    try {
+      query = "DROP TABLE " + tableName;
+      if (m_Debug) {
+	System.err.println(query);
+      }
+      update(query);
+    } catch (SQLException ex) {
+      System.err.println(ex.getMessage());
+    }
+    return tableName;
+  }
+
+  /**
+   * Gets the name of the experiment table that stores results from a
+   * particular ResultProducer.
+   *
+   * @param rp 		the ResultProducer
+   * @return 		the name of the table where the results for this 
+   * 			ResultProducer are stored, or null if there is no 
+   * 			table for this ResultProducer.
+   * @throws Exception 	if an error occurs
+   */
+  public String getResultsTableName(ResultProducer rp) throws Exception {
+    // Get the experiment table name, or create a new table if necessary.
+    if (m_Debug) {
+      System.err.println("Getting results table name...");
+    }
+    String expType = rp.getClass().getName();
+    String expParams = rp.getCompatibilityState();
+    String query = "SELECT " + EXP_RESULT_COL 
+      + " FROM " + EXP_INDEX_TABLE
+       + " WHERE " + EXP_TYPE_COL + "='" + expType 
+      + "' AND " + EXP_SETUP_COL + "='" + expParams + "'";
+    String tableName = null;
+    ResultSet rs = select(query);
+    if (rs.next()) {
+      tableName = rs.getString(1);
+      if (rs.next()) {
+	throw new Exception("More than one index entry "
+	    + "for experiment config: " + query);
+      }
+    }
+    close(rs);
+    if (m_Debug) {
+      System.err.println("...results table = " + ((tableName == null) 
+						  ? "<null>" 
+						  : EXP_RESULT_PREFIX
+						  + tableName));
+    }
+    return (tableName == null) ? tableName : EXP_RESULT_PREFIX + tableName;
+  }
+
+  /**
+   * Creates a results table for the supplied result producer.
+   *
+   * @param rp 		the ResultProducer generating the results
+   * @param tableName 	the name of the resultsTable
+   * @return 		the name of the created results table
+   * @throws Exception 	if an error occurs.
+   */
+  public String createResultsTable(ResultProducer rp, String tableName)
+    throws Exception {
+
+    if (m_Debug) {
+      System.err.println("Creating results table " + tableName + "...");
+    }
+    String query = "CREATE TABLE " + tableName + " ( ";
+    // Loop over the key fields
+    String [] names = rp.getKeyNames();
+    Object [] types = rp.getKeyTypes();
+    if (names.length != types.length) {
+      throw new Exception("key names types differ in length");
+    }
+    for (int i = 0; i < names.length; i++) {
+      query += "Key_" + names[i] + " ";
+      if (types[i] instanceof Double) {
+	query += m_doubleType;
+      } else if (types[i] instanceof String) {
+
+	// Workaround for MySQL (doesn't support LONGVARCHAR)
+	// Also for InstantDB which attempts to interpret numbers when storing
+	// in LONGVARBINARY
+	/*if (m_Connection.getMetaData().getDriverName().
+	    equals("Mark Matthews' MySQL Driver")
+	    || (m_Connection.getMetaData().getDriverName().
+		indexOf("InstantDB JDBC Driver")) != -1) {
+	  query += "TEXT ";
+	  } else { */
+	//query += "LONGVARCHAR ";
+	  query += m_stringType+" ";
+	  //}
+      } else {
+	throw new Exception("Unknown/unsupported field type in key");
+      }
+      query += ", ";
+    }
+    // Loop over the result fields
+    names = rp.getResultNames();
+    types = rp.getResultTypes();
+    if (names.length != types.length) {
+      throw new Exception("result names and types differ in length");
+    }
+    for (int i = 0; i < names.length; i++) {
+      query += names[i] + " ";
+      if (types[i] instanceof Double) {
+	query += m_doubleType;
+      } else if (types[i] instanceof String) {
+	
+	// Workaround for MySQL (doesn't support LONGVARCHAR)
+	// Also for InstantDB which attempts to interpret numbers when storing
+	// in LONGVARBINARY
+	/*if (m_Connection.getMetaData().getDriverName().
+	    equals("Mark Matthews' MySQL Driver")
+	    || (m_Connection.getMetaData().getDriverName().
+		equals("InstantDB JDBC Driver"))) {
+	  query += "TEXT ";
+	  } else {*/
+	//query += "LONGVARCHAR ";
+	query += m_stringType+" ";
+	  //}
+      } else {
+	throw new Exception("Unknown/unsupported field type in key");
+      }
+      if (i < names.length - 1) {
+	query += ", ";
+      }
+    }
+    query += " )";
+    
+    update(query);
+    if (m_Debug) 
+      System.err.println("table created");
+    close();
+
+
+    if (m_createIndex) {
+      query = "CREATE UNIQUE INDEX Key_IDX ON "+ tableName +" (";
+
+      String [] keyNames = rp.getKeyNames();
+    
+      boolean first = true;
+      for (int i = 0; i < keyNames.length; i++) {
+	if (keyNames[i] != null) {
+	  if (first) {
+	    first = false;
+	    query += "Key_" + keyNames[i];
+	  } else {
+	    query += ",Key_" + keyNames[i];
+	  } 
+	}
+      }
+      query += ")";
+    
+      update(query);
+    }
+    return tableName;
+  }
+  
+  /**
+   * Sets the keywords (comma-separated list) to use.
+   * 
+   * @param value	the list of keywords
+   */
+  public void setKeywords(String value) {
+    String[] 	keywords;
+    int		i;
+    
+    m_Keywords.clear();
+    
+    keywords = value.replaceAll(" ", "").split(",");
+    for (i = 0; i < keywords.length; i++)
+      m_Keywords.add(keywords[i].toUpperCase());
+  }
+  
+  /**
+   * Returns the currently stored keywords (as comma-separated list).
+   * 
+   * @return		the list of keywords
+   */
+  public String getKeywords() {
+    String		result;
+    Vector<String>	list;
+    int			i;
+    
+    list = new Vector<String>(m_Keywords);
+    Collections.sort(list);
+    
+    result = "";
+    for (i = 0; i < list.size(); i++) {
+      if (i > 0)
+	result += ",";
+      result += list.get(i);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Sets the mask character to append to table or attribute names that
+   * are a reserved keyword.
+   * 
+   * @param value	the new character
+   */
+  public void setKeywordsMaskChar(String value) {
+    m_KeywordsMaskChar = value;
+  }
+  
+  /**
+   * Returns the currently set mask character.
+   * 
+   * @return		the character
+   */
+  public String getKeywordsMaskChar() {
+    return m_KeywordsMaskChar;
+  }
+  
+  /**
+   * Checks whether the given string is a reserved keyword.
+   * 
+   * @param s		the string to check
+   * @return		true if the string is a keyword
+   * @see		#m_Keywords
+   */
+  public boolean isKeyword(String s) {
+    return m_Keywords.contains(s.toUpperCase());
+  }
+  
+  /**
+   * If the given string is a keyword, then the mask character will be 
+   * appended and returned. Otherwise, the same string will be returned
+   * unchanged.
+   * 
+   * @param s		the string to check
+   * @return		the potentially masked string
+   * @see		#m_KeywordsMaskChar
+   * @see		#isKeyword(String)
+   */
+  public String maskKeyword(String s) {
+    if (isKeyword(s))
+      return s + m_KeywordsMaskChar;
+    else
+      return s;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5238 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props	(revision 29)
@@ -0,0 +1,109 @@
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# Version: $Revision: 5837 $
+
+# The comma-separated list of jdbc drivers to use
+#jdbcDriver=RmiJdbc.RJDriver,jdbc.idbDriver
+#jdbcDriver=jdbc.idbDriver
+jdbcDriver=RmiJdbc.RJDriver,jdbc.idbDriver,org.gjt.mm.mysql.Driver,com.mckoi.JDBCDriver,org.hsqldb.jdbcDriver
+#jdbcDriver=org.gjt.mm.mysql.Driver
+
+# The url to the experiment database
+#jdbcURL=jdbc:rmi://expserver/jdbc:idb=experiments.prp
+jdbcURL=jdbc:idb=experiments.prp
+#jdbcURL=jdbc:mysql://mysqlserver/username
+
+# the method that is used to retrieve values from the db 
+# (java datatype + RecordSet.<method>)
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+# the original conversion: <column type>=<conversion>
+#char=0
+#varchar=0
+#longvarchar=0
+#binary=0
+#varbinary=0
+#longvarbinary=0
+#bit=1
+#numeric=2
+#decimal=2
+#tinyint=3
+#smallint=4
+#integer=5
+#bigint=6
+#real=7
+#float=2
+#double=2
+#date=8
+#time=10
+#timestamp=8
+#mysql-conversion
+CHAR=0
+TEXT=0
+VARCHAR=0
+LONGVARCHAR=9
+BINARY=0
+VARBINARY=0
+LONGVARBINARY=9
+BIT=1
+NUMERIC=2
+DECIMAL=2
+FLOAT=2
+DOUBLE=2
+TINYINT=3
+SMALLINT=4
+#SHORT=4
+SHORT=5
+INTEGER=5
+BIGINT=6
+LONG=6
+REAL=7
+DATE=8
+TIME=10
+TIMESTAMP=8 
+
+#mappings for table creation
+CREATE_STRING=TEXT
+CREATE_INT=INT
+CREATE_DOUBLE=DOUBLE
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+
+#database flags
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+setAutoCommit=true
+createIndex=false
+
+# All the reserved keywords for this database
+Keywords=\
+  AND,\
+  ASC,\
+  BY,\
+  DESC,\
+  FROM,\
+  GROUP,\
+  INSERT,\
+  ORDER,\
+  SELECT,\
+  UPDATE,\
+  WHERE
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.hsql
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.hsql	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.hsql	(revision 29)
@@ -0,0 +1,196 @@
+# Database settings for HSQLDB 1.8.x
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://hsqldb.sourceforge.net/
+# jdbc:    http://sourceforge.net/projects/hsqldb/
+# howto:   http://hsqldb.sourceforge.net/doc/guide/ch01.html
+# author:  Dale Fletcher (dale at cs dot waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=org.hsqldb.jdbcDriver
+
+# database URL
+jdbcURL=jdbc:hsqldb:hsql://server_name/database_name
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+# other options
+CREATE_INT=INT
+CREATE_DOUBLE=DOUBLE
+CREATE_STRING=LONGVARCHAR
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=true
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://hsqldb.org/doc/guide/ch09.html
+Keywords=\
+  ABS,\
+  ACOS,\
+  ALIAS,\
+  ALL,\
+  ALTER,\
+  ASCII,\
+  ASIN,\
+  ATAN,\
+  ATAN2,\
+  AUTOCOMMIT,\
+  BITAND,\
+  BIT_LENGTH,\
+  BITOR,\
+  CACHED,\
+  CALL,\
+  CASEWHEN,\
+  CEILING,\
+  CHAR,\
+  CHAR_LENGTH,\
+  CHECKPOINT,\
+  COALESCE,\
+  COLLATION,\
+  COMMIT,\
+  CONCAT,\
+  CONNECT,\
+  CONVERT,\
+  COS,\
+  COT,\
+  CREATE,\
+  CURDATE,\
+  CURRENT_DATE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  CURRENT_USER,\
+  CURTIME,\
+  DATABASE,\
+  DAYNAME,\
+  DAYOFMONTH,\
+  DAYOFWEEK,\
+  DAYOFYEAR,\
+  DEFRAG,\
+  DEGREES,\
+  DELAY,\
+  DELETE,\
+  DIFFERENCE,\
+  DISCONNECT,\
+  DISTINCT,\
+  DROP,\
+  EXCEPT,\
+  EXP,\
+  EXPLAIN,\
+  EXTRACT,\
+  FLOOR,\
+  FROM,\
+  GRANT,\
+  GROUPBY,\
+  HAVING,\
+  HEXTORAW,\
+  HOUR,\
+  IDENTITY,\
+  IFNULL,\
+  IGNORECASE,\
+  INDEX,\
+  INITIAL,\
+  INSERT,\
+  INTEGRITY,\
+  INTERSECT,\
+  INTO,\
+  LCASE,\
+  LEFT,\
+  LENGTH,\
+  LIMIT,\
+  LOCATE,\
+  LOG,\
+  LOG10,\
+  LOGSIZE,\
+  LOWER,\
+  LTRIM,\
+  MAXROWS,\
+  MINUS,\
+  MINUTE,\
+  MOD,\
+  MONTH,\
+  MONTHNAME,\
+  NOW,\
+  NULLIF,\
+  OCTET_LENGTH,\
+  OFFSET,\
+  ORDERBY,\
+  PASSWORD,\
+  PI,\
+  PLAN,\
+  POWER,\
+  PROPERTY,\
+  QUARTER,\
+  RADIANS,\
+  RAND,\
+  RAWTOHEX,\
+  READONLY,\
+  REFERENTIAL,\
+  REPEAT,\
+  REPLACE,\
+  REVOKE,\
+  RIGHT,\
+  ROLE,\
+  ROLLBACK,\
+  ROUND,\
+  ROUNDMAGIC,\
+  RTRIM,\
+  SAVEPOINT,\
+  SCHEMA,\
+  SCRIPT,\
+  SCRIPTFORMAT,\
+  SECOND,\
+  SELECT,\
+  SEQUENCE,\
+  SET,\
+  SHUTDOWN,\
+  SIGN,\
+  SIN,\
+  SOUNDEX,\
+  SOURCE,\
+  SPACE,\
+  SQRT,\
+  SUBSTR,\
+  SUBSTRING,\
+  TABLE,\
+  TAN,\
+  TEMP,\
+  TEXT,\
+  TOP,\
+  TRIGGER,\
+  TRUNCATE,\
+  UCASE,\
+  UNION,\
+  UPDATE,\
+  UPPER,\
+  USER,\
+  VIEW,\
+  WEEK,\
+  WHERE,\
+  WRITE,\
+  YEAR
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+# flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.msaccess
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.msaccess	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.msaccess	(revision 29)
@@ -0,0 +1,264 @@
+# Database settings for ODBC in conjunction with MS Access 2000
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://weka.wikispaces.com/Windows+Databases
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=sun.jdbc.odbc.JdbcOdbcDriver
+
+# database URL
+jdbcURL=jdbc:odbc:DSN_Name
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+varchar=0
+float=2
+tinyint=3
+int=5
+text=0
+
+# other options
+CREATE_DOUBLE=DOUBLE
+CREATE_STRING=TEXT
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://support.microsoft.com/default.aspx?scid=kb;en-us;286335
+Keywords=\
+  ADD,\
+  ALL,\
+  ALTER,\
+  AND,\
+  ANY,\
+  AS,\
+  ASC,\
+  AUTOINCREMENT,\
+  Alphanumeric,\
+  Application,\
+  Assistant,\
+  Avg,\
+  BETWEEN,\
+  BINARY,\
+  BIT,\
+  BOOLEAN,\
+  BY,\
+  BYTE,\
+  CHAR,\
+  CHARACTER,\
+  COLUMN,\
+  CONSTRAINT,\
+  COUNTER,\
+  CREATE,\
+  CURRENCY,\
+  CompactDatabase,\
+  Container,\
+  Count,\
+  CreateDatabase,\
+  CreateField,\
+  CreateGroup,\
+  CreateIndex,\
+  CreateObject,\
+  CreateProperty,\
+  CreateRelation,\
+  CreateTableDef,\
+  CreateUser,\
+  CreateWorkspace,\
+  CurrentUser,\
+  DATABASE,\
+  DATE,\
+  DATETIME,\
+  DELETE,\
+  DESC,\
+  DISALLOW,\
+  DISTINCT,\
+  DISTINCTROW,\
+  DOUBLE,\
+  DROP,\
+  Description,\
+  Document,\
+  EXISTS,\
+  Echo,\
+  Else,\
+  End,\
+  Eqv,\
+  Error,\
+  Exit,\
+  FALSE,\
+  FLOAT,\
+  FLOAT4,\
+  FLOAT8,\
+  FOREIGN,\
+  FROM,\
+  FUNCTION,\
+  Field,\
+  Fields,\
+  FillCache,\
+  Form,\
+  Forms,\
+  Full,\
+  GENERAL,\
+  GROUP,\
+  GROUPBY,\
+  GUID,\
+  GetObject,\
+  GetOption,\
+  GotoPage,\
+  HAVING,\
+  IEEEDOUBLE,\
+  IEEESINGLE,\
+  IGNORE,\
+  IN,\
+  INDEX,\
+  INNER,\
+  INSERT,\
+  INT,\
+  INTEGER,\
+  INTEGER1,\
+  INTEGER2,\
+  INTEGER4,\
+  INTO,\
+  IS,\
+  Idle,\
+  If,\
+  Imp,\
+  Index,\
+  Indexes,\
+  InsertText,\
+  JOIN,\
+  KEY,\
+  LEFT,\
+  LOGICAL,\
+  LOGICAL1,\
+  LONG,\
+  LONGBINARY,\
+  LONGTEXT,\
+  LastModified,\
+  Level,\
+  Like,\
+  MEMO,\
+  MONEY,\
+  Macro,\
+  Match,\
+  Max,\
+  Min,\
+  Mod,\
+  Module,\
+  Move,\
+  NAME,\
+  NO,\
+  NULL,\
+  NUMBER,\
+  NUMERIC,\
+  NewPassword,\
+  Not,\
+  Note,\
+  OFF,\
+  OLEOBJECT,\
+  ON,\
+  OPTION,\
+  OR,\
+  ORDER,\
+  OWNERACCESS,\
+  Object,\
+  OpenRecordset,\
+  Orientation,\
+  Outer,\
+  PARAMETERS,\
+  PERCENT,\
+  PIVOT,\
+  PRIMARY,\
+  PROCEDURE,\
+  Parameter,\
+  Partial,\
+  Property,\
+  Queries,\
+  Query,\
+  Quit,\
+  REAL,\
+  REFERENCES,\
+  RIGHT,\
+  Recalc,\
+  Recordset,\
+  Refresh,\
+  RefreshLink,\
+  RegisterDatabase,\
+  Relation,\
+  Repaint,\
+  RepairDatabase,\
+  Report,\
+  Reports,\
+  Requery,\
+  SCREEN,\
+  SECTION,\
+  SELECT,\
+  SET,\
+  SHORT,\
+  SINGLE,\
+  SMALLINT,\
+  SOME,\
+  SQL,\
+  STRING,\
+  SetFocus,\
+  SetOption,\
+  StDev,\
+  StDevP,\
+  Sum,\
+  TABLE,\
+  TEXT,\
+  TIME,\
+  TIMESTAMP,\
+  TOP,\
+  TRANSFORM,\
+  TRUE,\
+  TableDef,\
+  TableDefs,\
+  TableID,\
+  Type,\
+  UNION,\
+  UNIQUE,\
+  UPDATE,\
+  USER,\
+  VALUE,\
+  VALUES,\
+  VARBINARY,\
+  VARCHAR,\
+  VERSION,\
+  Var,\
+  VarP,\
+  WHERE,\
+  WITH,\
+  Workspace,\
+  Xor,\
+  YES,\
+  YESNO,\
+  Year
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver	(revision 29)
@@ -0,0 +1,231 @@
+# Database settings for Microsoft SQL Server 2000
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.microsoft.com/
+# jdbc:    http://www.microsoft.com/downloads/details.aspx?familyid=07287B11-0502-461A-B138-2AA54BFDC03A
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=com.microsoft.jdbc.sqlserver.SQLServerDriver
+
+# database URL
+jdbcURL=jdbc:sqlserver://server_name:1433
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+varchar=0
+float=2
+tinyint=3
+int=5
+
+# other options
+CREATE_DOUBLE=DOUBLE PRECISION
+CREATE_STRING=VARCHAR(8000)
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://msdn.microsoft.com/en-us/library/aa238507.aspx
+Keywords=\
+  ADD,\
+  ALL,\
+  ALTER,\
+  AND,\
+  ANY,\
+  AS,\
+  ASC,\
+  AUTHORIZATION,\
+  BACKUP,\
+  BEGIN,\
+  BETWEEN,\
+  BREAK,\
+  BROWSE,\
+  BULK,\
+  BY,\
+  CASCADE,\
+  CASE,\
+  CHECK,\
+  CHECKPOINT,\
+  CLOSE,\
+  CLUSTERED,\
+  COALESCE,\
+  COLLATE,\
+  COLUMN,\
+  COMMIT,\
+  COMPUTE,\
+  CONSTRAINT,\
+  CONTAINS,\
+  CONTAINSTABLE,\
+  CONTINUE,\
+  CONVERT,\
+  CREATE,\
+  CROSS,\
+  CURRENT,\
+  CURRENT_DATE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  CURRENT_USER,\
+  CURSOR,\
+  DATABASE,\
+  DBCC,\
+  DEALLOCATE,\
+  DECLARE,\
+  DEFAULT,\
+  DELETE,\
+  DENY,\
+  DESC,\
+  DISK,\
+  DISTINCT,\
+  DISTRIBUTED,\
+  DOUBLE,\
+  DROP,\
+  DUMMY,\
+  DUMP,\
+  ELSE,\
+  END,\
+  ERRLVL,\
+  ESCAPE,\
+  EXCEPT,\
+  EXEC,\
+  EXECUTE,\
+  EXISTS,\
+  EXIT,\
+  FETCH,\
+  FILE,\
+  FILLFACTOR,\
+  FOR,\
+  FOREIGN,\
+  FREETEXT,\
+  FREETEXTTABLE,\
+  FROM,\
+  FULL,\
+  FUNCTION,\
+  GOTO,\
+  GRANT,\
+  GROUP,\
+  HAVING,\
+  HOLDLOCK,\
+  IDENTITY,\
+  IDENTITYCOL,\
+  IDENTITY_INSERT,\
+  IF,\
+  IN,\
+  INDEX,\
+  INNER,\
+  INSERT,\
+  INTERSECT,\
+  INTO,\
+  IS,\
+  JOIN,\
+  KEY,\
+  KILL,\
+  LEFT,\
+  LIKE,\
+  LINENO,\
+  LOAD,\
+  NATIONAL,\
+  NOCHECK,\
+  NONCLUSTERED,\
+  NOT,\
+  NULL,\
+  NULLIF,\
+  OF,\
+  OFF,\
+  OFFSETS,\
+  ON,\
+  OPEN,\
+  OPENDATASOURCE,\
+  OPENQUERY,\
+  OPENROWSET,\
+  OPENXML,\
+  OPTION,\
+  OR,\
+  ORDER,\
+  OUTER,\
+  OVER,\
+  PERCENT,\
+  PLAN,\
+  PRECISION,\
+  PRIMARY,\
+  PRINT,\
+  PROC,\
+  PROCEDURE,\
+  PUBLIC,\
+  RAISERROR,\
+  READ,\
+  READTEXT,\
+  RECONFIGURE,\
+  REFERENCES,\
+  REPLICATION,\
+  RESTORE,\
+  RESTRICT,\
+  RETURN,\
+  REVOKE,\
+  RIGHT,\
+  ROLLBACK,\
+  ROWCOUNT,\
+  ROWGUIDCOL,\
+  RULE,\
+  SAVE,\
+  SCHEMA,\
+  SELECT,\
+  SESSION_USER,\
+  SET,\
+  SETUSER,\
+  SHUTDOWN,\
+  SOME,\
+  STATISTICS,\
+  SYSTEM_USER,\
+  TABLE,\
+  TEXTSIZE,\
+  THEN,\
+  TO,\
+  TOP,\
+  TRAN,\
+  TRANSACTION,\
+  TRIGGER,\
+  TRUNCATE,\
+  TSEQUAL,\
+  UNION,\
+  UNIQUE,\
+  UPDATE,\
+  UPDATETEXT,\
+  USE,\
+  USER,\
+  VALUES,\
+  VARYING,\
+  VIEW,\
+  WAITFOR,\
+  WHEN,\
+  WHERE,\
+  WHILE,\
+  WITH,\
+  WRITETEXT
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver2005
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver2005	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mssqlserver2005	(revision 29)
@@ -0,0 +1,236 @@
+# Database settings for Microsoft SQL Server 2005 Express Edition
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.microsoft.com/
+# jdbc:    http://msdn2.microsoft.com/en-us/data/aa937724.aspx
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=com.microsoft.sqlserver.jdbc.SQLServerDriver
+
+# database URL
+jdbcURL=jdbc:sqlserver://localhost;databaseName=blahblah
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+varchar=0
+float=2
+tinyint=3
+int=5
+
+# other options
+CREATE_DOUBLE=DOUBLE PRECISION
+CREATE_STRING=VARCHAR(8000)
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://msdn.microsoft.com/en-us/library/ms189822(SQL.90).aspx
+Keywords=\
+  ADD,\
+  ALL,\
+  ALTER,\
+  AND,\
+  ANY,\
+  AS,\
+  ASC,\
+  AUTHORIZATION,\
+  BACKUP,\
+  BEGIN,\
+  BETWEEN,\
+  BREAK,\
+  BROWSE,\
+  BULK,\
+  BY,\
+  CASCADE,\
+  CASE,\
+  CHECK,\
+  CHECKPOINT,\
+  CLOSE,\
+  CLUSTERED,\
+  COALESCE,\
+  COLLATE,\
+  COLUMN,\
+  COMMIT,\
+  COMPUTE,\
+  CONSTRAINT,\
+  CONTAINS,\
+  CONTAINSTABLE,\
+  CONTINUE,\
+  CONVERT,\
+  CREATE,\
+  CROSS,\
+  CURRENT,\
+  CURRENT_DATE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  CURRENT_USER,\
+  CURSOR,\
+  DATABASE,\
+  DBCC,\
+  DEALLOCATE,\
+  DECLARE,\
+  DEFAULT,\
+  DELETE,\
+  DENY,\
+  DESC,\
+  DISK,\
+  DISTINCT,\
+  DISTRIBUTED,\
+  DOUBLE,\
+  DROP,\
+  DUMP,\
+  ELSE,\
+  END,\
+  ERRLVL,\
+  ESCAPE,\
+  EXCEPT,\
+  EXEC,\
+  EXECUTE,\
+  EXISTS,\
+  EXIT,\
+  EXTERNAL,\
+  FETCH,\
+  FILE,\
+  FILLFACTOR,\
+  FOR,\
+  FOREIGN,\
+  FREETEXT,\
+  FREETEXTTABLE,\
+  FROM,\
+  FULL,\
+  FUNCTION,\
+  GOTO,\
+  GRANT,\
+  GROUP,\
+  HAVING,\
+  HOLDLOCK,\
+  IDENTITY,\
+  IDENTITYCOL,\
+  IDENTITY_INSERT,\
+  IF,\
+  IN,\
+  INDEX,\
+  INNER,\
+  INSERT,\
+  INTERSECT,\
+  INTO,\
+  IS,\
+  JOIN,\
+  KEY,\
+  KILL,\
+  LEFT,\
+  LIKE,\
+  LINENO,\
+  LOAD,\
+  NATIONAL,\
+  NOCHECK,\
+  NONCLUSTERED,\
+  NOT,\
+  NULL,\
+  NULLIF,\
+  OF,\
+  OFF,\
+  OFFSETS,\
+  ON,\
+  OPEN,\
+  OPENDATASOURCE,\
+  OPENQUERY,\
+  OPENROWSET,\
+  OPENXML,\
+  OPTION,\
+  OR,\
+  ORDER,\
+  OUTER,\
+  OVER,\
+  PERCENT,\
+  PIVOT,\
+  PLAN,\
+  PRECISION,\
+  PRIMARY,\
+  PRINT,\
+  PROC,\
+  PROCEDURE,\
+  PUBLIC,\
+  RAISERROR,\
+  READ,\
+  READTEXT,\
+  RECONFIGURE,\
+  REFERENCES,\
+  REPLICATION,\
+  RESTORE,\
+  RESTRICT,\
+  RETURN,\
+  REVERT,\
+  REVOKE,\
+  RIGHT,\
+  ROLLBACK,\
+  ROWCOUNT,\
+  ROWGUIDCOL,\
+  RULE,\
+  SAVE,\
+  SCHEMA,\
+  SECURITYAUDIT,\
+  SELECT,\
+  SESSION_USER,\
+  SET,\
+  SETUSER,\
+  SHUTDOWN,\
+  SOME,\
+  STATISTICS,\
+  SYSTEM_USER,\
+  TABLE,\
+  TABLESAMPLE,\
+  TEXTSIZE,\
+  THEN,\
+  TO,\
+  TOP,\
+  TRAN,\
+  TRANSACTION,\
+  TRIGGER,\
+  TRUNCATE,\
+  TSEQUAL,\
+  UNION,\
+  UNIQUE,\
+  UNPIVOT,\
+  UPDATE,\
+  UPDATETEXT,\
+  USE,\
+  USER,\
+  VALUES,\
+  VARYING,\
+  VIEW,\
+  WAITFOR,\
+  WHEN,\
+  WHERE,\
+  WHILE,\
+  WITH,\
+  WRITETEXT
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mysql
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mysql	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.mysql	(revision 29)
@@ -0,0 +1,279 @@
+# Database settings for MySQL 3.23.x, 4.x
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.mysql.com/
+# jdbc:    http://www.mysql.com/products/connector/j/
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=org.gjt.mm.mysql.Driver
+
+# database URL
+jdbcURL=jdbc:mysql://server_name:3306/database_name
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+# other options
+CREATE_DOUBLE=DOUBLE
+CREATE_STRING=TEXT
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-0.html
+Keywords=\
+  ADD,\
+  ALL,\
+  ALTER,\
+  ANALYZE,\
+  AND,\
+  AS,\
+  ASC,\
+  ASENSITIVE,\
+  BEFORE,\
+  BETWEEN,\
+  BIGINT,\
+  BINARY,\
+  BLOB,\
+  BOTH,\
+  BY,\
+  CALL,\
+  CASCADE,\
+  CASE,\
+  CHANGE,\
+  CHAR,\
+  CHARACTER,\
+  CHECK,\
+  COLLATE,\
+  COLUMN,\
+  COLUMNS,\
+  CONDITION,\
+  CONNECTION,\
+  CONSTRAINT,\
+  CONTINUE,\
+  CONVERT,\
+  CREATE,\
+  CROSS,\
+  CURRENT_DATE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  CURRENT_USER,\
+  CURSOR,\
+  DATABASE,\
+  DATABASES,\
+  DAY_HOUR,\
+  DAY_MICROSECOND,\
+  DAY_MINUTE,\
+  DAY_SECOND,\
+  DEC,\
+  DECIMAL,\
+  DECLARE,\
+  DEFAULT,\
+  DELAYED,\
+  DELETE,\
+  DESC,\
+  DESCRIBE,\
+  DETERMINISTIC,\
+  DISTINCT,\
+  DISTINCTROW,\
+  DIV,\
+  DOUBLE,\
+  DROP,\
+  DUAL,\
+  EACH,\
+  ELSE,\
+  ELSEIF,\
+  ENCLOSED,\
+  ESCAPED,\
+  EXISTS,\
+  EXIT,\
+  EXPLAIN,\
+  FALSE,\
+  FETCH,\
+  FIELDS,\
+  FLOAT,\
+  FLOAT4,\
+  FLOAT8,\
+  FOR,\
+  FORCE,\
+  FOREIGN,\
+  FROM,\
+  FULLTEXT,\
+  GOTO,\
+  GRANT,\
+  GROUP,\
+  HAVING,\
+  HIGH_PRIORITY,\
+  HOUR_MICROSECOND,\
+  HOUR_MINUTE,\
+  HOUR_SECOND,\
+  IF,\
+  IGNORE,\
+  IN,\
+  INDEX,\
+  INFILE,\
+  INNER,\
+  INOUT,\
+  INSENSITIVE,\
+  INSERT,\
+  INT,\
+  INT1,\
+  INT2,\
+  INT3,\
+  INT4,\
+  INT8,\
+  INTEGER,\
+  INTERVAL,\
+  INTO,\
+  IS,\
+  ITERATE,\
+  JOIN,\
+  KEY,\
+  KEYS,\
+  KILL,\
+  LABEL,\
+  LEADING,\
+  LEAVE,\
+  LEFT,\
+  LIKE,\
+  LIMIT,\
+  LINES,\
+  LOAD,\
+  LOCALTIME,\
+  LOCALTIMESTAMP,\
+  LOCK,\
+  LONG,\
+  LONGBLOB,\
+  LONGTEXT,\
+  LOOP,\
+  LOW_PRIORITY,\
+  MATCH,\
+  MEDIUMBLOB,\
+  MEDIUMINT,\
+  MEDIUMTEXT,\
+  MIDDLEINT,\
+  MINUTE_MICROSECOND,\
+  MINUTE_SECOND,\
+  MOD,\
+  MODIFIES,\
+  NATURAL,\
+  NOT,\
+  NO_WRITE_TO_BINLOG,\
+  NULL,\
+  NUMERIC,\
+  ON,\
+  OPTIMIZE,\
+  OPTION,\
+  OPTIONALLY,\
+  OR,\
+  ORDER,\
+  OUT,\
+  OUTER,\
+  OUTFILE,\
+  PRECISION,\
+  PRIMARY,\
+  PRIVILEGES,\
+  PROCEDURE,\
+  PURGE,\
+  READ,\
+  READS,\
+  REAL,\
+  REFERENCES,\
+  REGEXP,\
+  RELEASE,\
+  RENAME,\
+  REPEAT,\
+  REPLACE,\
+  REQUIRE,\
+  RESTRICT,\
+  RETURN,\
+  REVOKE,\
+  RIGHT,\
+  RLIKE,\
+  SCHEMA,\
+  SCHEMAS,\
+  SECOND_MICROSECOND,\
+  SELECT,\
+  SENSITIVE,\
+  SEPARATOR,\
+  SET,\
+  SHOW,\
+  SMALLINT,\
+  SONAME,\
+  SPATIAL,\
+  SPECIFIC,\
+  SQL,\
+  SQLEXCEPTION,\
+  SQLSTATE,\
+  SQLWARNING,\
+  SQL_BIG_RESULT,\
+  SQL_CALC_FOUND_ROWS,\
+  SQL_SMALL_RESULT,\
+  SSL,\
+  STARTING,\
+  STRAIGHT_JOIN,\
+  TABLE,\
+  TABLES,\
+  TERMINATED,\
+  THEN,\
+  TINYBLOB,\
+  TINYINT,\
+  TINYTEXT,\
+  TO,\
+  TRAILING,\
+  TRIGGER,\
+  TRUE,\
+  UNDO,\
+  UNION,\
+  UNIQUE,\
+  UNLOCK,\
+  UNSIGNED,\
+  UPDATE,\
+  UPGRADE,\
+  USAGE,\
+  USE,\
+  USING,\
+  UTC_DATE,\
+  UTC_TIME,\
+  UTC_TIMESTAMP,\
+  VALUES,\
+  VARBINARY,\
+  VARCHAR,\
+  VARCHARACTER,\
+  VARYING,\
+  WHEN,\
+  WHERE,\
+  WHILE,\
+  WITH,\
+  WRITE,\
+  XOR,\
+  YEAR_MONTH,\
+  ZEROFILL
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.odbc
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.odbc	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.odbc	(revision 29)
@@ -0,0 +1,65 @@
+# Database settings for ODBC in conjunction with MS SQL Server 2000
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://weka.wikispaces.com/Windows+Databases
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=sun.jdbc.odbc.JdbcOdbcDriver
+
+# database URL
+jdbcURL=jdbc:odbc:DSN_name
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+varchar=0
+float=2
+tinyint=3
+int=5
+
+# other options
+CREATE_DOUBLE=DOUBLE PRECISION
+CREATE_STRING=VARCHAR(8000)
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+Keywords=\
+  AND,\
+  ASC,\
+  BY,\
+  DESC,\
+  FROM,\
+  GROUP,\
+  INSERT,\
+  ORDER,\
+  SELECT,\
+  UPDATE,\
+  WHERE
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.oracle
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.oracle	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.oracle	(revision 29)
@@ -0,0 +1,165 @@
+# Database settings for Oracle 10g Express Edition
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.oracle.com/
+# jdbc:    http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=oracle.jdbc.driver.OracleDriver
+
+# database URL
+jdbcURL=jdbc:oracle:thin:@server_name:1521:XE
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+VARCHAR2=0
+NUMBER=2
+DOUBLE_PRECISION=2
+TIMESTAMP=8
+
+# other options
+CREATE_INT=INTEGER
+CREATE_STRING=VARCHAR2(4000)
+CREATE_DOUBLE=NUMBER
+CREATE_DATE=TIMESTAMP
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=true
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/ap_keywd.htm
+Keywords=\
+  ACCESS,\
+  ADD,\
+  ALL,\
+  ALTER,\
+  AND,\
+  ANY,\
+  AS,\
+  ASC,\
+  AUDIT,\
+  BETWEEN,\
+  BY,\
+  CHAR,\
+  CHECK,\
+  CLUSTER,\
+  COLUMN,\
+  COMMENT,\
+  COMPRESS,\
+  CONNECT,\
+  CREATE,\
+  CURRENT,\
+  DATE,\
+  DECIMAL,\
+  DEFAULT,\
+  DELETE,\
+  DESC,\
+  DISTINCT,\
+  DROP,\
+  ELSE,\
+  EXCLUSIVE,\
+  EXISTS,\
+  FILE,\
+  FLOAT,\
+  FOR,\
+  FROM,\
+  GRANT,\
+  GROUP,\
+  HAVING,\
+  IDENTIFIED,\
+  IMMEDIATE,\
+  IN,\
+  INCREMENT,\
+  INDEX,\
+  INITIAL,\
+  INSERT,\
+  INTEGER,\
+  INTERSECT,\
+  INTO,\
+  IS,\
+  LEVEL,\
+  LIKE,\
+  LOCK,\
+  LONG,\
+  MAXEXTENTS,\
+  MINUS,\
+  MLSLABEL,\
+  MODE,\
+  MODIFY,\
+  NOAUDIT,\
+  NOCOMPRESS,\
+  NOT,\
+  NOWAIT,\
+  NULL,\
+  NUMBER,\
+  OF,\
+  OFFLINE,\
+  ON,\
+  ONLINE,\
+  OPTION,\
+  OR,\
+  ORDER,\
+  PCTFREE,\
+  PRIOR,\
+  PRIVILEGES,\
+  PUBLIC,\
+  RAW,\
+  RENAME,\
+  RESOURCE,\
+  REVOKE,\
+  ROW,\
+  ROWID,\
+  ROWNUM,\
+  ROWS,\
+  SELECT,\
+  SESSION,\
+  SET,\
+  SHARE,\
+  SIZE,\
+  SMALLINT,\
+  START,\
+  SUCCESSFUL,\
+  SYNONYM,\
+  SYSDATE,\
+  TABLE,\
+  THEN,\
+  TO,\
+  TRIGGER,\
+  UID,\
+  UNION,\
+  UNIQUE,\
+  UPDATE,\
+  USER,\
+  VALIDATE,\
+  VALUES,\
+  VARCHAR,\
+  VARCHAR2,\
+  VIEW,\
+  WHENEVER,\
+  WHERE,\
+  WITH
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.postgresql
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.postgresql	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.postgresql	(revision 29)
@@ -0,0 +1,593 @@
+# Database settings for PostgreSQL 7.4
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.postgresql.org/
+# jdbc:    http://jdbc.postgresql.org/
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=org.postgresql.Driver
+
+# database URL
+jdbcURL=jdbc:postgresql://server_name:5432/database_name
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+varchar=0
+text=0
+float4=2
+float8=2
+int4=5
+oid=5
+timestamp=8
+date=8
+
+# other options
+CREATE_DOUBLE=float8
+CREATE_STRING=text
+CREATE_INT=int
+CREATE_DATE=TIMESTAMP
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=true
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://www.postgresql.org/docs/7.4/static/sql-keywords-appendix.html
+Keywords=\
+  ABORT,\
+  ABS,\
+  ABSOLUTE,\
+  ACCESS,\
+  ACTION,\
+  ADA,\
+  ADD,\
+  ADMIN,\
+  AFTER,\
+  AGGREGATE,\
+  ALIAS,\
+  ALL,\
+  ALLOCATE,\
+  ALTER,\
+  ANALYSE,\
+  ANALYZE,\
+  AND,\
+  ANY,\
+  ARE,\
+  ARRAY,\
+  AS,\
+  ASC,\
+  ASENSITIVE,\
+  ASSERTION,\
+  ASSIGNMENT,\
+  ASYMMETRIC,\
+  AT,\
+  ATOMIC,\
+  AUTHORIZATION,\
+  AVG,\
+  BACKWARD,\
+  BEFORE,\
+  BEGIN,\
+  BETWEEN,\
+  BIGINT,\
+  BINARY,\
+  BIT,\
+  BITVAR,\
+  BIT_LENGTH,\
+  BLOB,\
+  BOOLEAN,\
+  BOTH,\
+  BREADTH,\
+  BY,\
+  C,\
+  CACHE,\
+  CALL,\
+  CALLED,\
+  CARDINALITY,\
+  CASCADE,\
+  CASCADED,\
+  CASE,\
+  CAST,\
+  CATALOG,\
+  CATALOG_NAME,\
+  CHAIN,\
+  CHAR,\
+  CHARACTER,\
+  CHARACTERISTICS,\
+  CHARACTER_LENGTH,\
+  CHARACTER_SET_CATALOG,\
+  CHARACTER_SET_NAME,\
+  CHARACTER_SET_SCHEMA,\
+  CHAR_LENGTH,\
+  CHECK,\
+  CHECKED,\
+  CHECKPOINT,\
+  CLASS,\
+  CLASS_ORIGIN,\
+  CLOB,\
+  CLOSE,\
+  CLUSTER,\
+  COALESCE,\
+  COBOL,\
+  COLLATE,\
+  COLLATION,\
+  COLLATION_CATALOG,\
+  COLLATION_NAME,\
+  COLLATION_SCHEMA,\
+  COLUMN,\
+  COLUMN_NAME,\
+  COMMAND_FUNCTION,\
+  COMMAND_FUNCTION_CODE,\
+  COMMENT,\
+  COMMIT,\
+  COMMITTED,\
+  COMPLETION,\
+  CONDITION_NUMBER,\
+  CONNECT,\
+  CONNECTION,\
+  CONNECTION_NAME,\
+  CONSTRAINT,\
+  CONSTRAINTS,\
+  CONSTRAINT_CATALOG,\
+  CONSTRAINT_NAME,\
+  CONSTRAINT_SCHEMA,\
+  CONSTRUCTOR,\
+  CONTAINS,\
+  CONTINUE,\
+  CONVERSION,\
+  CONVERT,\
+  COPY,\
+  CORRESPONDING,\
+  COUNT,\
+  CREATE,\
+  CREATEDB,\
+  CREATEUSER,\
+  CROSS,\
+  CUBE,\
+  CURRENT,\
+  CURRENT_DATE,\
+  CURRENT_PATH,\
+  CURRENT_ROLE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  CURRENT_USER,\
+  CURSOR,\
+  CURSOR_NAME,\
+  CYCLE,\
+  DATA,\
+  DATABASE,\
+  DATE,\
+  DATETIME_INTERVAL_CODE,\
+  DATETIME_INTERVAL_PRECISION,\
+  DAY,\
+  DEALLOCATE,\
+  DEC,\
+  DECIMAL,\
+  DECLARE,\
+  DEFAULT,\
+  DEFAULTS,\
+  DEFERRABLE,\
+  DEFERRED,\
+  DEFINED,\
+  DEFINER,\
+  DELETE,\
+  DELIMITER,\
+  DELIMITERS,\
+  DEPTH,\
+  DEREF,\
+  DESC,\
+  DESCRIBE,\
+  DESCRIPTOR,\
+  DESTROY,\
+  DESTRUCTOR,\
+  DETERMINISTIC,\
+  DIAGNOSTICS,\
+  DICTIONARY,\
+  DISCONNECT,\
+  DISPATCH,\
+  DISTINCT,\
+  DO,\
+  DOMAIN,\
+  DOUBLE,\
+  DROP,\
+  DYNAMIC,\
+  DYNAMIC_FUNCTION,\
+  DYNAMIC_FUNCTION_CODE,\
+  EACH,\
+  ELSE,\
+  ENCODING,\
+  ENCRYPTED,\
+  END,\
+  END-EXEC,\
+  EQUALS,\
+  ESCAPE,\
+  EVERY,\
+  EXCEPT,\
+  EXCEPTION,\
+  EXCLUDING,\
+  EXCLUSIVE,\
+  EXEC,\
+  EXECUTE,\
+  EXISTING,\
+  EXISTS,\
+  EXPLAIN,\
+  EXTERNAL,\
+  EXTRACT,\
+  FALSE,\
+  FETCH,\
+  FINAL,\
+  FIRST,\
+  FLOAT,\
+  FOR,\
+  FORCE,\
+  FOREIGN,\
+  FORTRAN,\
+  FORWARD,\
+  FOUND,\
+  FREE,\
+  FREEZE,\
+  FROM,\
+  FULL,\
+  FUNCTION,\
+  G,\
+  GENERAL,\
+  GENERATED,\
+  GET,\
+  GLOBAL,\
+  GO,\
+  GOTO,\
+  GRANT,\
+  GRANTED,\
+  GROUP,\
+  GROUPING,\
+  HANDLER,\
+  HAVING,\
+  HIERARCHY,\
+  HOLD,\
+  HOST,\
+  HOUR,\
+  IDENTITY,\
+  IGNORE,\
+  ILIKE,\
+  IMMEDIATE,\
+  IMMUTABLE,\
+  IMPLEMENTATION,\
+  IMPLICIT,\
+  IN,\
+  INCLUDING,\
+  INCREMENT,\
+  INDEX,\
+  INDICATOR,\
+  INFIX,\
+  INHERITS,\
+  INITIALIZE,\
+  INITIALLY,\
+  INNER,\
+  INOUT,\
+  INPUT,\
+  INSENSITIVE,\
+  INSERT,\
+  INSTANCE,\
+  INSTANTIABLE,\
+  INSTEAD,\
+  INT,\
+  INTEGER,\
+  INTERSECT,\
+  INTERVAL,\
+  INTO,\
+  INVOKER,\
+  IS,\
+  ISNULL,\
+  ISOLATION,\
+  ITERATE,\
+  JOIN,\
+  K,\
+  KEY,\
+  KEY_MEMBER,\
+  KEY_TYPE,\
+  LANCOMPILER,\
+  LANGUAGE,\
+  LARGE,\
+  LAST,\
+  LATERAL,\
+  LEADING,\
+  LEFT,\
+  LENGTH,\
+  LESS,\
+  LEVEL,\
+  LIKE,\
+  LIMIT,\
+  LISTEN,\
+  LOAD,\
+  LOCAL,\
+  LOCALTIME,\
+  LOCALTIMESTAMP,\
+  LOCATION,\
+  LOCATOR,\
+  LOCK,\
+  LOWER,\
+  M,\
+  MAP,\
+  MATCH,\
+  MAX,\
+  MAXVALUE,\
+  MESSAGE_LENGTH,\
+  MESSAGE_OCTET_LENGTH,\
+  MESSAGE_TEXT,\
+  METHOD,\
+  MIN,\
+  MINUTE,\
+  MINVALUE,\
+  MOD,\
+  MODE,\
+  MODIFIES,\
+  MODIFY,\
+  MODULE,\
+  MONTH,\
+  MORE,\
+  MOVE,\
+  MUMPS,\
+  NAME,\
+  NAMES,\
+  NATIONAL,\
+  NATURAL,\
+  NCHAR,\
+  NCLOB,\
+  NEW,\
+  NEXT,\
+  NO,\
+  NOCREATEDB,\
+  NOCREATEUSER,\
+  NONE,\
+  NOT,\
+  NOTHING,\
+  NOTIFY,\
+  NOTNULL,\
+  NULL,\
+  NULLABLE,\
+  NULLIF,\
+  NUMBER,\
+  NUMERIC,\
+  OBJECT,\
+  OCTET_LENGTH,\
+  OF,\
+  OFF,\
+  OFFSET,\
+  OIDS,\
+  OLD,\
+  ON,\
+  ONLY,\
+  OPEN,\
+  OPERATION,\
+  OPERATOR,\
+  OPTION,\
+  OPTIONS,\
+  OR,\
+  ORDER,\
+  ORDINALITY,\
+  OUT,\
+  OUTER,\
+  OUTPUT,\
+  OVERLAPS,\
+  OVERLAY,\
+  OVERRIDING,\
+  OWNER,\
+  PAD,\
+  PARAMETER,\
+  PARAMETERS,\
+  PARAMETER_MODE,\
+  PARAMETER_NAME,\
+  PARAMETER_ORDINAL_POSITION,\
+  PARAMETER_SPECIFIC_CATALOG,\
+  PARAMETER_SPECIFIC_NAME,\
+  PARAMETER_SPECIFIC_SCHEMA,\
+  PARTIAL,\
+  PASCAL,\
+  PASSWORD,\
+  PATH,\
+  PENDANT,\
+  PLACING,\
+  PLI,\
+  POSITION,\
+  POSTFIX,\
+  PRECISION,\
+  PREFIX,\
+  PREORDER,\
+  PREPARE,\
+  PRESERVE,\
+  PRIMARY,\
+  PRIOR,\
+  PRIVILEGES,\
+  PROCEDURAL,\
+  PROCEDURE,\
+  PUBLIC,\
+  READ,\
+  READS,\
+  REAL,\
+  RECHECK,\
+  RECURSIVE,\
+  REF,\
+  REFERENCES,\
+  REFERENCING,\
+  REINDEX,\
+  RELATIVE,\
+  RENAME,\
+  REPEATABLE,\
+  REPLACE,\
+  RESET,\
+  RESTART,\
+  RESTRICT,\
+  RESULT,\
+  RETURN,\
+  RETURNED_LENGTH,\
+  RETURNED_OCTET_LENGTH,\
+  RETURNED_SQLSTATE,\
+  RETURNS,\
+  REVOKE,\
+  RIGHT,\
+  ROLE,\
+  ROLLBACK,\
+  ROLLUP,\
+  ROUTINE,\
+  ROUTINE_CATALOG,\
+  ROUTINE_NAME,\
+  ROUTINE_SCHEMA,\
+  ROW,\
+  ROWS,\
+  ROW_COUNT,\
+  RULE,\
+  SAVEPOINT,\
+  SCALE,\
+  SCHEMA,\
+  SCHEMA_NAME,\
+  SCOPE,\
+  SCROLL,\
+  SEARCH,\
+  SECOND,\
+  SECTION,\
+  SECURITY,\
+  SELECT,\
+  SELF,\
+  SENSITIVE,\
+  SEQUENCE,\
+  SERIALIZABLE,\
+  SERVER_NAME,\
+  SESSION,\
+  SESSION_USER,\
+  SET,\
+  SETOF,\
+  SETS,\
+  SHARE,\
+  SHOW,\
+  SIMILAR,\
+  SIMPLE,\
+  SIZE,\
+  SMALLINT,\
+  SOME,\
+  SOURCE,\
+  SPACE,\
+  SPECIFIC,\
+  SPECIFICTYPE,\
+  SPECIFIC_NAME,\
+  SQL,\
+  SQLCODE,\
+  SQLERROR,\
+  SQLEXCEPTION,\
+  SQLSTATE,\
+  SQLWARNING,\
+  STABLE,\
+  START,\
+  STATE,\
+  STATEMENT,\
+  STATIC,\
+  STATISTICS,\
+  STDIN,\
+  STDOUT,\
+  STORAGE,\
+  STRICT,\
+  STRUCTURE,\
+  STYLE,\
+  SUBCLASS_ORIGIN,\
+  SUBLIST,\
+  SUBSTRING,\
+  SUM,\
+  SYMMETRIC,\
+  SYSID,\
+  SYSTEM,\
+  SYSTEM_USER,\
+  TABLE,\
+  TABLE_NAME,\
+  TEMP,\
+  TEMPLATE,\
+  TEMPORARY,\
+  TERMINATE,\
+  THAN,\
+  THEN,\
+  TIME,\
+  TIMESTAMP,\
+  TIMEZONE_HOUR,\
+  TIMEZONE_MINUTE,\
+  TO,\
+  TOAST,\
+  TRAILING,\
+  TRANSACTION,\
+  TRANSACTIONS_COMMITTED,\
+  TRANSACTIONS_ROLLED_BACK,\
+  TRANSACTION_ACTIVE,\
+  TRANSFORM,\
+  TRANSFORMS,\
+  TRANSLATE,\
+  TRANSLATION,\
+  TREAT,\
+  TRIGGER,\
+  TRIGGER_CATALOG,\
+  TRIGGER_NAME,\
+  TRIGGER_SCHEMA,\
+  TRIM,\
+  TRUE,\
+  TRUNCATE,\
+  TRUSTED,\
+  TYPE,\
+  UNCOMMITTED,\
+  UNDER,\
+  UNENCRYPTED,\
+  UNION,\
+  UNIQUE,\
+  UNKNOWN,\
+  UNLISTEN,\
+  UNNAMED,\
+  UNNEST,\
+  UNTIL,\
+  UPDATE,\
+  UPPER,\
+  USAGE,\
+  USER,\
+  USER_DEFINED_TYPE_CATALOG,\
+  USER_DEFINED_TYPE_NAME,\
+  USER_DEFINED_TYPE_SCHEMA,\
+  USING,\
+  VACUUM,\
+  VALID,\
+  VALIDATOR,\
+  VALUE,\
+  VALUES,\
+  VARCHAR,\
+  VARIABLE,\
+  VARYING,\
+  VERBOSE,\
+  VERSION,\
+  VIEW,\
+  VOLATILE,\
+  WHEN,\
+  WHENEVER,\
+  WHERE,\
+  WITH,\
+  WITHOUT,\
+  WORK,\
+  WRITE,\
+  YEAR,\
+  ZONE
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.sqlite3
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.sqlite3	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DatabaseUtils.props.sqlite3	(revision 29)
@@ -0,0 +1,171 @@
+# Database settings for sqlite 3.x
+#
+# General information on database access can be found here:
+# http://weka.wikispaces.com/Databases
+#
+# url:     http://www.sqlite.org/
+# jdbc:    http://www.zentus.com/sqlitejdbc/
+# author:  Fracpete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5837 $
+
+# JDBC driver (comma-separated list)
+jdbcDriver=org.sqlite.JDBC
+
+# database URL
+jdbcURL=jdbc:sqlite:/path/to/database.db
+
+# specific data types
+# string, getString() = 0;    --> nominal
+# boolean, getBoolean() = 1;  --> nominal
+# double, getDouble() = 2;    --> numeric
+# byte, getByte() = 3;        --> numeric
+# short, getByte()= 4;        --> numeric
+# int, getInteger() = 5;      --> numeric
+# long, getLong() = 6;        --> numeric
+# float, getFloat() = 7;      --> numeric
+# date, getDate() = 8;        --> date
+# text, getString() = 9;      --> string
+# time, getTime() = 10;       --> date
+
+# other options
+CREATE_DOUBLE=DOUBLE
+CREATE_STRING=TEXT
+CREATE_INT=INT
+CREATE_DATE=DATETIME
+DateFormat=yyyy-MM-dd HH:mm:ss
+checkUpperCaseNames=false
+checkLowerCaseNames=false
+checkForTable=true
+
+# All the reserved keywords for this database
+# Based on the keywords listed at the following URL (2009-04-13):
+# http://www.sqlite.org/lang_keywords.html
+Keywords=\
+  ABORT,\
+  ADD,\
+  AFTER,\
+  ALL,\
+  ALTER,\
+  ANALYZE,\
+  AND,\
+  AS,\
+  ASC,\
+  ATTACH,\
+  AUTOINCREMENT,\
+  BEFORE,\
+  BEGIN,\
+  BETWEEN,\
+  BY,\
+  CASCADE,\
+  CASE,\
+  CAST,\
+  CHECK,\
+  COLLATE,\
+  COLUMN,\
+  COMMIT,\
+  CONFLICT,\
+  CONSTRAINT,\
+  CREATE,\
+  CROSS,\
+  CURRENT_DATE,\
+  CURRENT_TIME,\
+  CURRENT_TIMESTAMP,\
+  DATABASE,\
+  DEFAULT,\
+  DEFERRABLE,\
+  DEFERRED,\
+  DELETE,\
+  DESC,\
+  DETACH,\
+  DISTINCT,\
+  DROP,\
+  EACH,\
+  ELSE,\
+  END,\
+  ESCAPE,\
+  EXCEPT,\
+  EXCLUSIVE,\
+  EXISTS,\
+  EXPLAIN,\
+  FAIL,\
+  FOR,\
+  FOREIGN,\
+  FROM,\
+  FULL,\
+  GLOB,\
+  GROUP,\
+  HAVING,\
+  IF,\
+  IGNORE,\
+  IMMEDIATE,\
+  IN,\
+  INDEX,\
+  INDEXED,\
+  INITIALLY,\
+  INNER,\
+  INSERT,\
+  INSTEAD,\
+  INTERSECT,\
+  INTO,\
+  IS,\
+  ISNULL,\
+  JOIN,\
+  KEY,\
+  LEFT,\
+  LIKE,\
+  LIMIT,\
+  MATCH,\
+  NATURAL,\
+  NOT,\
+  NOTNULL,\
+  NULL,\
+  OF,\
+  OFFSET,\
+  ON,\
+  OR,\
+  ORDER,\
+  OUTER,\
+  PLAN,\
+  PRAGMA,\
+  PRIMARY,\
+  QUERY,\
+  RAISE,\
+  REFERENCES,\
+  REGEXP,\
+  REINDEX,\
+  RELEASE,\
+  RENAME,\
+  REPLACE,\
+  RESTRICT,\
+  RIGHT,\
+  ROLLBACK,\
+  ROW,\
+  SAVEPOINT,\
+  SELECT,\
+  SET,\
+  TABLE,\
+  TEMP,\
+  TEMPORARY,\
+  THEN,\
+  TO,\
+  TRANSACTION,\
+  TRIGGER,\
+  UNION,\
+  UNIQUE,\
+  UPDATE,\
+  USING,\
+  VACUUM,\
+  VALUES,\
+  VIEW,\
+  VIRTUAL,\
+  WHEN,\
+  WHERE
+
+# The character to append to attribute names to avoid exceptions due to
+# clashes between keywords and attribute names
+KeywordsMaskChar=_
+
+#flags for loading and saving instances using DatabaseLoader/Saver
+nominalToStringLimit=50
+idColumn=auto_generated_id
+
Index: branches/MetisMQI/src/main/java/weka/experiment/DensityBasedClustererSplitEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/DensityBasedClustererSplitEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/DensityBasedClustererSplitEvaluator.java	(revision 29)
@@ -0,0 +1,636 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DensityBasedClustererSplitEvaluator.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.clusterers.ClusterEvaluation;
+import weka.clusterers.Clusterer;
+import weka.clusterers.AbstractClusterer;
+import weka.clusterers.AbstractDensityBasedClusterer;
+import weka.clusterers.DensityBasedClusterer;
+import weka.clusterers.EM;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A SplitEvaluator that produces results for a density based clusterer.
+ *
+ * -W classname <br>
+ * Specify the full class name of the clusterer to evaluate. <p>
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 5563 $
+ */
+
+public class DensityBasedClustererSplitEvaluator 
+  implements SplitEvaluator,
+	     OptionHandler,
+	     AdditionalMeasureProducer,
+	     RevisionHandler {
+
+  /** Remove the class column (if set) from the data */
+  protected boolean m_removeClassColumn = true;
+
+  /** The clusterer used for evaluation */
+  protected DensityBasedClusterer m_clusterer = new EM();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_additionalMeasures = null;
+
+  /** Array of booleans corresponding to the measures in m_AdditionalMeasures
+      indicating which of the AdditionalMeasures the current clusterer
+      can produce */
+  protected boolean [] m_doesProduce = null;
+
+  /** The number of additional measures that need to be filled in
+      after taking into account column constraints imposed by the final
+      destination for results */
+  protected int m_numberAdditionalMeasures = 0;
+
+  /** Holds the statistics for the most recent application of the clusterer */
+  protected String m_result = null;
+
+  /** The clusterer options (if any) */
+  protected String m_clustererOptions = "";
+
+  /** The clusterer version */
+  protected String m_clustererVersion = "";
+
+  /** The length of a key */
+  private static final int KEY_SIZE = 3;
+
+  /** The length of a result */
+  private static final int RESULT_SIZE = 6;
+
+  
+  public DensityBasedClustererSplitEvaluator() {
+    updateOptions();
+  }
+
+  /**
+   * Returns a string describing this split evaluator
+   * @return a description of the split evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return " A SplitEvaluator that produces results for a density based clusterer. ";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+				    "\tThe full class name of the density based clusterer.\n"
+				    +"\teg: weka.clusterers.EM", 
+				    "W", 1, 
+				    "-W <class name>"));
+
+    if ((m_clusterer != null) &&
+	(m_clusterer instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+				      "",
+				      "", 0, "\nOptions specific to clusterer "
+				      + m_clusterer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_clusterer).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. Valid options are:<p>
+   *
+   * -W classname <br>
+   * Specify the full class name of the clusterer to evaluate. <p>
+   *
+   * All option after -- will be passed to the classifier.
+   *
+   * @param options the list of options as an array of strings
+   * @exception Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String cName = Utils.getOption('W', options);
+    if (cName.length() == 0) {
+      throw new Exception("A clusterer must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // Classifier.
+    setClusterer((DensityBasedClusterer)AbstractClusterer.forName(cName, null));
+    if (getClusterer() instanceof OptionHandler) {
+      ((OptionHandler) getClusterer())
+	.setOptions(Utils.partitionOptions(options));
+      updateOptions();
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] clustererOptions = new String [0];
+    if ((m_clusterer != null) && 
+	(m_clusterer instanceof OptionHandler)) {
+      clustererOptions = ((OptionHandler)m_clusterer).getOptions();
+    }
+    
+    String [] options = new String [clustererOptions.length + 3];
+    int current = 0;
+
+    if (getClusterer() != null) {
+      options[current++] = "-W";
+      options[current++] = getClusterer().getClass().getName();
+    }
+
+    options[current++] = "--";
+
+    System.arraycopy(clustererOptions, 0, options, current, 
+		     clustererOptions.length);
+    current += clustererOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in Classifiers. This could contain many measures (of which only a
+   * subset may be produceable by the current Classifier) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures a list of method names
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    // System.err.println("ClassifierSplitEvaluator: setting additional measures");
+    m_additionalMeasures = additionalMeasures;
+    
+    // determine which (if any) of the additional measures this clusterer
+    // can produce
+    if (m_additionalMeasures != null && m_additionalMeasures.length > 0) {
+      m_doesProduce = new boolean [m_additionalMeasures.length];
+
+      if (m_clusterer instanceof AdditionalMeasureProducer) {
+	Enumeration en = ((AdditionalMeasureProducer)m_clusterer).
+	  enumerateMeasures();
+	while (en.hasMoreElements()) {
+	  String mname = (String)en.nextElement();
+	  for (int j=0;j<m_additionalMeasures.length;j++) {
+	    if (mname.compareToIgnoreCase(m_additionalMeasures[j]) == 0) {
+	      m_doesProduce[j] = true;
+	    }
+	  }
+	}
+      }
+    } else {
+      m_doesProduce = null;
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the classifier
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_clusterer instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_clusterer).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @exception IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_clusterer instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_clusterer).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("DensityBasedClustererSplitEvaluator: "
+					 +"Can't return value for : "+additionalMeasureName
+					 +". "+m_clusterer.getClass().getName()+" "
+					 +"is not an AdditionalMeasureProducer");
+    }
+  }
+
+  /**
+   * Gets the data types of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each key column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getKeyTypes() {
+
+    Object [] keyTypes = new Object[KEY_SIZE];
+    keyTypes[0] = "";
+    keyTypes[1] = "";
+    keyTypes[2] = "";
+    return keyTypes;
+  }
+
+  /**
+   * Gets the names of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each key column
+   */
+  public String [] getKeyNames() {
+
+    String [] keyNames = new String[KEY_SIZE];
+    keyNames[0] = "Scheme";
+    keyNames[1] = "Scheme_options";
+    keyNames[2] = "Scheme_version_ID";
+    return keyNames;
+  }
+
+  /**
+   * Gets the key describing the current SplitEvaluator. For example
+   * This may contain the name of the classifier used for classifier
+   * predictive evaluation. The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array of objects containing the key.
+   */
+  public Object [] getKey(){
+
+    Object [] key = new Object[KEY_SIZE];
+    key[0] = m_clusterer.getClass().getName();
+    key[1] = m_clustererOptions;
+    key[2] = m_clustererVersion;
+    return key;
+  }
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run. The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+    int addm = (m_additionalMeasures != null) 
+      ? m_additionalMeasures.length 
+      : 0;
+    int overall_length = RESULT_SIZE+addm;
+
+    Object [] resultTypes = new Object[overall_length];
+    Double doub = new Double(0);
+    int current = 0;
+    
+    // number of training and testing instances
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // log liklihood
+    resultTypes[current++] = doub;
+    // number of clusters
+    resultTypes[current++] = doub;
+
+    // timing stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+
+    //    resultTypes[current++] = "";
+
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultTypes[current++] = doub;
+    }
+    if (current != overall_length) {
+      throw new Error("ResultTypes didn't fit RESULT_SIZE");
+    }
+    return resultTypes;
+  }
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each result column
+   */
+  public String [] getResultNames() {
+    int addm = (m_additionalMeasures != null) 
+      ? m_additionalMeasures.length 
+      : 0;
+    int overall_length = RESULT_SIZE+addm;
+   
+    String [] resultNames = new String[overall_length];
+    int current = 0;
+    resultNames[current++] = "Number_of_training_instances";
+    resultNames[current++] = "Number_of_testing_instances";
+
+    // Basic performance stats
+    resultNames[current++] = "Log_likelihood";
+    resultNames[current++] = "Number_of_clusters";
+
+    // Timing stats
+    resultNames[current++] = "Time_training";
+    resultNames[current++] = "Time_testing";
+
+    // Classifier defined extras
+    //    resultNames[current++] = "Summary";
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultNames[current++] = m_additionalMeasures[i];
+    }
+    if (current != overall_length) {
+      throw new Error("ResultNames didn't fit RESULT_SIZE");
+    }
+    return resultNames;
+  }
+
+  /**
+   * Gets the results for the supplied train and test datasets.
+   *
+   * @param train the training Instances.
+   * @param test the testing Instances.
+   * @return the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @exception Exception if a problem occurs while getting the results
+   */
+  public Object [] getResult(Instances train, Instances test) 
+    throws Exception {
+    
+    if (m_clusterer == null) {
+      throw new Exception("No clusterer has been specified");
+    }
+    int addm = (m_additionalMeasures != null) 
+      ? m_additionalMeasures.length 
+      : 0;
+    int overall_length = RESULT_SIZE+addm;
+
+    if (m_removeClassColumn && train.classIndex() != -1) {
+      // remove the class column from the training and testing data
+      Remove r = new Remove();
+      r.setAttributeIndicesArray(new int [] {train.classIndex()});
+      r.setInvertSelection(false);
+      r.setInputFormat(train);
+      train = Filter.useFilter(train, r);
+      
+      test = Filter.useFilter(test, r);
+    }
+    train.setClassIndex(-1);
+    test.setClassIndex(-1);
+      
+
+    ClusterEvaluation eval = new ClusterEvaluation();
+
+    Object [] result = new Object[overall_length];
+    long trainTimeStart = System.currentTimeMillis();
+    m_clusterer.buildClusterer(train);
+    double numClusters = m_clusterer.numberOfClusters();
+    eval.setClusterer(m_clusterer);
+    long trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    long testTimeStart = System.currentTimeMillis();
+    eval.evaluateClusterer(test);
+    long testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+    //    m_result = eval.toSummaryString();
+
+    // The results stored are all per instance -- can be multiplied by the
+    // number of instances to get absolute numbers
+    int current = 0;
+    result[current++] = new Double(train.numInstances());
+    result[current++] = new Double(test.numInstances());
+
+    result[current++] = new Double(eval.getLogLikelihood());
+    result[current++] = new Double(numClusters);
+    
+    // Timing stats
+    result[current++] = new Double(trainTimeElapsed / 1000.0);
+    result[current++] = new Double(testTimeElapsed / 1000.0);
+    
+    for (int i=0;i<addm;i++) {
+      if (m_doesProduce[i]) {
+	try {
+	  double dv = ((AdditionalMeasureProducer)m_clusterer).
+	    getMeasure(m_additionalMeasures[i]);
+	  Double value = new Double(dv);
+	  
+	  result[current++] = value;
+	} catch (Exception ex) {
+	  System.err.println(ex);
+	}
+      } else {
+	result[current++] = null;
+      }
+    }
+    
+    if (current != overall_length) {
+      throw new Error("Results didn't fit RESULT_SIZE");
+    }
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String removeClassColumnTipText() {
+    return "Remove the class column (if set) from the data.";
+  }
+
+  /**
+   * Set whether the class column should be removed from the data.
+   *
+   * @param r true if the class column is to be removed.
+   */
+  public void setRemoveClassColumn(boolean r) {
+    m_removeClassColumn = r;
+  }
+
+  /**
+   * Get whether the class column is to be removed.
+   *
+   * @return true if the class column is to be removed.
+   */
+  public boolean getRemoveClassColumn() {
+    return m_removeClassColumn;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clustererTipText() {
+    return "The density based clusterer to use.";
+  }
+
+  /**
+   * Get the value of clusterer
+   *
+   * @return Value of clusterer.
+   */
+  public DensityBasedClusterer getClusterer() {
+    
+    return m_clusterer;
+  }
+  
+  /**
+   * Sets the clusterer.
+   *
+   * @param newClusterer the new clusterer to use.
+   */
+  public void setClusterer(DensityBasedClusterer newClusterer) {
+    
+    m_clusterer = newClusterer;
+    updateOptions();
+  }
+
+
+  protected void updateOptions() {
+    
+    if (m_clusterer instanceof OptionHandler) {
+      m_clustererOptions = Utils.joinOptions(((OptionHandler)m_clusterer)
+					     .getOptions());
+    } else {
+      m_clustererOptions = "";
+    }
+    if (m_clusterer instanceof Serializable) {
+      ObjectStreamClass obs = ObjectStreamClass.lookup(m_clusterer
+						       .getClass());
+      m_clustererVersion = "" + obs.getSerialVersionUID();
+    } else {
+      m_clustererVersion = "";
+    }
+  }
+
+  /**
+   * Set the Clusterer to use, given it's class name. A new clusterer will be
+   * instantiated.
+   *
+   * @param newClustererName the clusterer class name.
+   * @exception Exception if the class name is invalid.
+   */
+  public void setClustererName(String newClustererName) throws Exception {
+
+    try {
+      setClusterer((DensityBasedClusterer)Class.forName(newClustererName)
+		    .newInstance());
+    } catch (Exception ex) {
+      throw new Exception("Can't find Clusterer with class name: "
+			  + newClustererName);
+    }
+  }
+
+  /**
+   * Gets the raw output from the classifier
+   * @return the raw output from the classifier
+   */
+  public String getRawResultOutput() {
+    StringBuffer result = new StringBuffer();
+
+    if (m_clusterer == null) {
+      return "<null> clusterer";
+    }
+    result.append(toString());
+    result.append("Clustering model: \n"+m_clusterer.toString()+'\n');
+
+    // append the performance statistics
+    if (m_result != null) {
+      //      result.append(m_result);
+      
+      if (m_doesProduce != null) {
+	for (int i=0;i<m_doesProduce.length;i++) {
+	  if (m_doesProduce[i]) {
+	    try {
+	      double dv = ((AdditionalMeasureProducer)m_clusterer).
+		getMeasure(m_additionalMeasures[i]);
+	      Double value = new Double(dv);
+	      
+	      result.append(m_additionalMeasures[i]+" : "+value+'\n');
+	    } catch (Exception ex) {
+	      System.err.println(ex);
+	    }
+	  } 
+	}
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a text description of the split evaluator.
+   *
+   * @return a text description of the split evaluator.
+   */
+  public String toString() {
+
+    String result = "DensityBasedClustererSplitEvaluator: ";
+    if (m_clusterer == null) {
+      return result + "<null> clusterer";
+    }
+    return result + m_clusterer.getClass().getName() + " " 
+      + m_clustererOptions + "(version " + m_clustererVersion + ")";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/Experiment.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/Experiment.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/Experiment.java	(revision 29)
@@ -0,0 +1,1207 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Experiment.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.ConverterUtils;
+import weka.core.xml.KOML;
+import weka.core.xml.XMLOptions;
+import weka.experiment.xml.XMLExperiment;
+
+import java.beans.PropertyDescriptor;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.DefaultListModel;
+
+/**
+ * Holds all the necessary configuration information for a standard
+ * type experiment. This object is able to be serialized for storage
+ * on disk.
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  The lower run number to start the experiment from.
+ *  (default 1)</pre>
+ * 
+ * <pre> -U &lt;num&gt;
+ *  The upper run number to end the experiment at (inclusive).
+ *  (default 10)</pre>
+ * 
+ * <pre> -T &lt;arff file&gt;
+ *  The dataset to run the experiment on.
+ *  (required, may be specified multiple times)</pre>
+ * 
+ * <pre> -P &lt;class name&gt;
+ *  The full class name of a ResultProducer (required).
+ *  eg: weka.experiment.RandomSplitResultProducer</pre>
+ * 
+ * <pre> -D &lt;class name&gt;
+ *  The full class name of a ResultListener (required).
+ *  eg: weka.experiment.CSVResultListener</pre>
+ * 
+ * <pre> -N &lt;string&gt;
+ *  A string containing any notes about the experiment.
+ *  (default none)</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.RandomSplitResultProducer:
+ * </pre>
+ * 
+ * <pre> -P &lt;percent&gt;
+ *  The percentage of instances to use for training.
+ *  (default 66)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> -R
+ *  Set when data is not to be randomized and the data sets' size.
+ *  Is not to be determined via probabilistic rounding.</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * All options after -- will be passed to the result producer. <p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5399 $
+ */
+public class Experiment 
+  implements Serializable, OptionHandler, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 44945596742646663L;
+  
+  /** The filename extension that should be used for experiment files */
+  public static String FILE_EXTENSION = ".exp";
+
+  /** Where results will be sent */
+  protected ResultListener m_ResultListener = new InstancesResultListener();
+  
+  /** The result producer */
+  protected ResultProducer m_ResultProducer = new RandomSplitResultProducer();
+
+  /** Lower run number */
+  protected int m_RunLower = 1;
+
+  /** Upper run number */
+  protected int m_RunUpper = 10;
+
+  /** An array of dataset files */
+  protected DefaultListModel m_Datasets = new DefaultListModel();
+
+  /** True if the exp should also iterate over a property of the RP */
+  protected boolean m_UsePropertyIterator = false;
+  
+  /** The path to the iterator property */
+  protected PropertyNode [] m_PropertyPath;
+  
+  /** The array of values to set the property to */
+  protected Object m_PropertyArray;
+
+  /** User notes about the experiment */
+  protected String m_Notes = "";
+
+  /** Method names of additional measures of objects contained in the 
+      custom property iterator. Only methods names beginning with "measure"
+      and returning doubles are recognised */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** True if the class attribute is the first attribute for all
+      datasets involved in this experiment. */
+  protected boolean m_ClassFirst = false;
+
+  /** If true an experiment will advance the current data set befor
+      any custom itererator */
+  protected boolean m_AdvanceDataSetFirst = true;
+
+  /**
+   * Sets whether the first attribute is treated as the class
+   * for all datasets involved in the experiment. This information
+   * is not output with the result of the experiments!
+   * 
+   * @param flag	whether the class attribute is the first and not the last
+   */
+  public void classFirst(boolean flag) {
+    
+    m_ClassFirst = flag;
+  }
+  
+  /**
+   * Get the value of m_DataSetFirstFirst.
+   *
+   * @return Value of m_DataSetFirstFirst.
+   */
+  public boolean getAdvanceDataSetFirst() {
+    
+    return m_AdvanceDataSetFirst;
+  }
+  
+  /**
+   * Set the value of m_AdvanceDataSetFirst.
+   *
+   * @param newAdvanceDataSetFirst Value to assign to m_AdvanceRunFirst.
+   */
+  public void setAdvanceDataSetFirst(boolean newAdvanceDataSetFirst) {
+    
+    m_AdvanceDataSetFirst = newAdvanceDataSetFirst;
+  }
+  
+  /**
+   * Gets whether the custom property iterator should be used.
+   *
+   * @return true if so
+   */
+  public boolean getUsePropertyIterator() {
+    
+    return m_UsePropertyIterator;
+  }
+
+  /**
+   * Sets whether the custom property iterator should be used.
+   *
+   * @param newUsePropertyIterator true if so
+   */
+  public void setUsePropertyIterator(boolean newUsePropertyIterator) {
+    
+    m_UsePropertyIterator = newUsePropertyIterator;
+  }
+
+  /**
+   * Gets the path of properties taken to get to the custom property
+   * to iterate over.
+   *
+   * @return an array of PropertyNodes
+   */
+  public PropertyNode [] getPropertyPath() {
+    
+    return m_PropertyPath;
+  }
+  
+  /**
+   * Sets the path of properties taken to get to the custom property
+   * to iterate over.
+   *
+   * @param newPropertyPath an array of PropertyNodes
+   */
+  public void setPropertyPath(PropertyNode [] newPropertyPath) {
+    
+    m_PropertyPath = newPropertyPath;
+  }
+  
+  /**
+   * Sets the array of values to set the custom property to.
+   *
+   * @param newPropArray a value of type Object which should be an
+   * array of the appropriate values.
+   */
+  public void setPropertyArray(Object newPropArray) {
+
+    m_PropertyArray = newPropArray;
+  }
+
+  /**
+   * Gets the array of values to set the custom property to.
+   *
+   * @return a value of type Object which should be an
+   * array of the appropriate values.
+   */
+  public Object getPropertyArray() {
+
+    return m_PropertyArray;
+  }
+
+  /**
+   * Gets the number of custom iterator values that have been defined
+   * for the experiment.
+   *
+   * @return the number of custom property iterator values.
+   */
+  public int getPropertyArrayLength() {
+
+    return Array.getLength(m_PropertyArray);
+  }
+
+  /**
+   * Gets a specified value from the custom property iterator array.
+   *
+   * @param index the index of the value wanted
+   * @return the property array value
+   */
+  public Object getPropertyArrayValue(int index) {
+
+    return Array.get(m_PropertyArray, index);
+  }
+  
+  /* These may potentially want to be made un-transient if it is decided
+   * that experiments may be saved mid-run and later resumed
+   */
+  /** The current run number when the experiment is running */
+  protected transient int m_RunNumber;
+  /** The current dataset number when the experiment is running */
+  protected transient int m_DatasetNumber;
+  /** The current custom property value index when the experiment is running */
+  protected transient int m_PropertyNumber;
+  /** True if the experiment has finished running */
+  protected transient boolean m_Finished = true;
+  /** The dataset currently being used */
+  protected transient Instances m_CurrentInstances;
+  /** The custom property value that has actually been set */
+  protected transient int m_CurrentProperty;
+
+  /**
+   * When an experiment is running, this returns the current run number.
+   *
+   * @return the current run number.
+   */
+  public int getCurrentRunNumber() {
+    return m_RunNumber;
+  }
+
+  /**
+   * When an experiment is running, this returns the current dataset number.
+   *
+   * @return the current dataset number.
+   */
+  public int getCurrentDatasetNumber() {
+    return m_DatasetNumber;
+  }
+
+  /**
+   * When an experiment is running, this returns the index of the
+   * current custom property value.
+   *
+   * @return the index of the current custom property value.
+   */
+  public int getCurrentPropertyNumber() {
+    return m_PropertyNumber;
+  }
+  
+  /**
+   * Prepares an experiment for running, initializing current iterator
+   * settings.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void initialize() throws Exception {
+    
+    m_RunNumber = getRunLower();
+    m_DatasetNumber = 0;
+    m_PropertyNumber = 0;
+    m_CurrentProperty = -1;
+    m_CurrentInstances = null;
+    m_Finished = false;
+    if (m_UsePropertyIterator && (m_PropertyArray == null)) {
+      throw new Exception("Null array for property iterator");
+    }
+    if (getRunLower() > getRunUpper()) {
+      throw new Exception("Lower run number is greater than upper run number");
+    }
+    if (getDatasets().size() == 0) {
+      throw new Exception("No datasets have been specified");
+    }
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+
+    //    if (m_UsePropertyIterator && (m_PropertyArray != null)) {
+    determineAdditionalResultMeasures();
+      //    }
+
+    m_ResultProducer.setResultListener(m_ResultListener);
+    m_ResultProducer.setAdditionalMeasures(m_AdditionalMeasures);
+    m_ResultProducer.preProcess();
+
+    // constrain the additional measures to be only those allowable
+    // by the ResultListener
+    String [] columnConstraints = m_ResultListener.
+      determineColumnConstraints(m_ResultProducer);
+
+    if (columnConstraints != null) {
+      m_ResultProducer.setAdditionalMeasures(columnConstraints);
+    }
+  }
+
+  /**
+   * Iterate over the objects in the property array to determine what
+   * (if any) additional measures they support
+   * 
+   * @throws Exception 	if additional measures don't comply to the naming 
+   * 			convention (starting with "measure")
+   */
+  private void determineAdditionalResultMeasures() throws Exception {
+    m_AdditionalMeasures = null;
+    FastVector measureNames = new FastVector();
+
+    // first try the result producer, then property array if applicable
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      Enumeration am = ((AdditionalMeasureProducer)m_ResultProducer).
+	enumerateMeasures();
+      while (am.hasMoreElements()) {
+	String mname = (String)am.nextElement();
+	if (mname.startsWith("measure")) {
+	  if (measureNames.indexOf(mname) == -1) {
+	    measureNames.addElement(mname);
+	  }
+	} else {
+	  throw new Exception ("Additional measures in "
+			       + m_ResultProducer.getClass().getName()
+			       +" must obey the naming convention"
+			       +" of starting with \"measure\"");
+	}
+      }
+    }
+
+    if (m_UsePropertyIterator && (m_PropertyArray != null)) {
+      for (int i = 0; i < Array.getLength(m_PropertyArray); i++) {
+	Object current = Array.get(m_PropertyArray, i);
+
+	if (current instanceof AdditionalMeasureProducer) {
+	  Enumeration am = ((AdditionalMeasureProducer)current).
+	    enumerateMeasures();
+	  while (am.hasMoreElements()) {
+	    String mname = (String)am.nextElement();
+	    if (mname.startsWith("measure")) {
+	      if (measureNames.indexOf(mname) == -1) {
+		measureNames.addElement(mname);
+	      }
+	    } else {
+	      throw new Exception ("Additional measures in "
+				   + current.getClass().getName()
+				   +" must obey the naming convention"
+				   +" of starting with \"measure\"");
+	    }
+	  }
+	}
+      }
+    }
+    if (measureNames.size() > 0) {
+      m_AdditionalMeasures = new String [measureNames.size()];
+      for (int i=0;i<measureNames.size();i++) {
+	m_AdditionalMeasures[i] = (String)measureNames.elementAt(i);
+      }
+    }
+  }
+
+  
+  /**
+   * Recursively sets the custom property value, by setting all values
+   * along the property path.
+   *
+   * @param propertyDepth the current position along the property path
+   * @param origValue the value to set the property to
+   * @throws Exception if an error occurs
+   */
+  protected void setProperty(int propertyDepth, Object origValue)
+    throws Exception {
+    
+    PropertyDescriptor current = m_PropertyPath[propertyDepth].property;
+    Object subVal = null;
+    if (propertyDepth < m_PropertyPath.length - 1) {
+      Method getter = current.getReadMethod();
+      Object getArgs [] = { };
+      subVal = getter.invoke(origValue, getArgs);
+      setProperty(propertyDepth + 1, subVal);
+    } else {
+      subVal = Array.get(m_PropertyArray, m_PropertyNumber);
+    }
+    Method setter = current.getWriteMethod();
+    Object [] args = { subVal };
+    setter.invoke(origValue, args);
+  }
+
+  /**
+   * Returns true if there are more iterations to carry out in the experiment.
+   *
+   * @return true if so
+   */
+  public boolean hasMoreIterations() {
+
+    return !m_Finished;
+  }
+  
+  /**
+   * Carries out the next iteration of the experiment.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void nextIteration() throws Exception {
+    
+    if (m_UsePropertyIterator) {
+      if (m_CurrentProperty != m_PropertyNumber) {
+	setProperty(0, m_ResultProducer);
+	m_CurrentProperty = m_PropertyNumber;
+      }
+    }
+    
+    if (m_CurrentInstances == null) {
+      File currentFile = (File) getDatasets().elementAt(m_DatasetNumber);
+      AbstractFileLoader loader = ConverterUtils.getLoaderForFile(currentFile);
+      loader.setFile(currentFile);
+      Instances data = new Instances(loader.getDataSet());
+      // only set class attribute if not already done by loader
+      if (data.classIndex() == -1) {
+	if (m_ClassFirst) {
+	  data.setClassIndex(0);
+	} else {
+	  data.setClassIndex(data.numAttributes() - 1);
+	}
+      }
+      m_CurrentInstances = data;
+      m_ResultProducer.setInstances(m_CurrentInstances);
+    }
+    
+    m_ResultProducer.doRun(m_RunNumber);
+
+    advanceCounters();
+  }
+
+  /**
+   * Increments iteration counters appropriately.
+   */
+  public void advanceCounters() {
+
+    if (m_AdvanceDataSetFirst) {
+      m_RunNumber ++;
+      if (m_RunNumber > getRunUpper()) {
+	m_RunNumber = getRunLower();
+	m_DatasetNumber ++;
+	m_CurrentInstances = null;
+	if (m_DatasetNumber >= getDatasets().size()) {
+	  m_DatasetNumber = 0;
+	  if (m_UsePropertyIterator) {
+	    m_PropertyNumber ++;
+	    if (m_PropertyNumber >= Array.getLength(m_PropertyArray)) {
+	      m_Finished = true;
+	    }
+	  } else {
+	    m_Finished = true;
+	  }
+	}
+      }
+    } else { // advance by custom iterator before data set
+      m_RunNumber ++;
+      if (m_RunNumber > getRunUpper()) {
+	m_RunNumber = getRunLower();
+	if (m_UsePropertyIterator) {
+	  m_PropertyNumber ++;
+	  if (m_PropertyNumber >= Array.getLength(m_PropertyArray)) {
+	    m_PropertyNumber = 0;
+	    m_DatasetNumber ++;
+	    m_CurrentInstances = null;
+	    if (m_DatasetNumber >= getDatasets().size()) {
+	      m_Finished = true;
+	    } 
+	  }
+	} else {
+	  m_DatasetNumber ++;
+	  m_CurrentInstances = null;
+	  if (m_DatasetNumber >= getDatasets().size()) {
+	    m_Finished = true;
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Runs all iterations of the experiment, continuing past errors.
+   */
+  public void runExperiment() {
+
+    while (hasMoreIterations()) {
+      try {
+	nextIteration();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	System.err.println(ex.getMessage());
+	advanceCounters(); // Try to keep plowing through
+      }
+    }
+  }
+
+  /**
+   * Signals that the experiment is finished running, so that cleanup
+   * can be done.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultProducer.postProcess();
+  }
+  
+  /**
+   * Gets the datasets in the experiment.
+   *
+   * @return the datasets in the experiment.
+   */
+  public DefaultListModel getDatasets() {
+    return m_Datasets;
+  }
+
+  /**
+   * Set the datasets to use in the experiment
+   * @param ds the list of datasets to use
+   */
+  public void setDatasets(DefaultListModel ds) {
+    m_Datasets = ds;
+  }
+
+  /**
+   * Gets the result listener where results will be sent.
+   *
+   * @return the result listener where results will be sent.
+   */
+  public ResultListener getResultListener() {
+    
+    return m_ResultListener;
+  }
+  
+  /**
+   * Sets the result listener where results will be sent.
+   *
+   * @param newResultListener the result listener where results will be sent.
+   */
+  public void setResultListener(ResultListener newResultListener) {
+    
+    m_ResultListener = newResultListener;
+  }
+  
+  /**
+   * Get the result producer used for the current experiment.
+   *
+   * @return the result producer used for the current experiment.
+   */
+  public ResultProducer getResultProducer() {
+    
+    return m_ResultProducer;
+  }
+  
+  /**
+   * Set the result producer used for the current experiment.
+   *
+   * @param newResultProducer result producer to use for the current 
+   * experiment.
+   */
+  public void setResultProducer(ResultProducer newResultProducer) {
+    
+    m_ResultProducer = newResultProducer;
+  }
+  
+  /**
+   * Get the upper run number for the experiment.
+   *
+   * @return the upper run number for the experiment.
+   */
+  public int getRunUpper() {
+    
+    return m_RunUpper;
+  }
+  
+  /**
+   * Set the upper run number for the experiment.
+   *
+   * @param newRunUpper the upper run number for the experiment.
+   */
+  public void setRunUpper(int newRunUpper) {
+    
+    m_RunUpper = newRunUpper;
+  }
+  
+  /**
+   * Get the lower run number for the experiment.
+   *
+   * @return the lower run number for the experiment.
+   */
+  public int getRunLower() {
+    
+    return m_RunLower;
+  }
+  
+  /**
+   * Set the lower run number for the experiment.
+   *
+   * @param newRunLower the lower run number for the experiment.
+   */
+  public void setRunLower(int newRunLower) {
+    
+    m_RunLower = newRunLower;
+  }
+
+  
+  /**
+   * Get the user notes.
+   *
+   * @return User notes associated with the experiment.
+   */
+  public String getNotes() {
+    
+    return m_Notes;
+  }
+  
+  /**
+   * Set the user notes.
+   *
+   * @param newNotes New user notes.
+   */
+  public void setNotes(String newNotes) {
+    
+    m_Notes = newNotes;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	     "\tThe lower run number to start the experiment from.\n"
+	      +"\t(default 1)", 
+	     "L", 1, 
+	     "-L <num>"));
+    newVector.addElement(new Option(
+	     "\tThe upper run number to end the experiment at (inclusive).\n"
+	      +"\t(default 10)", 
+	     "U", 1, 
+	     "-U <num>"));
+    newVector.addElement(new Option(
+	     "\tThe dataset to run the experiment on.\n"
+	     + "\t(required, may be specified multiple times)", 
+	     "T", 1, 
+	     "-T <arff file>"));
+    newVector.addElement(new Option(
+	     "\tThe full class name of a ResultProducer (required).\n"
+	      +"\teg: weka.experiment.RandomSplitResultProducer", 
+	     "P", 1, 
+	     "-P <class name>"));
+    newVector.addElement(new Option(
+	     "\tThe full class name of a ResultListener (required).\n"
+	      +"\teg: weka.experiment.CSVResultListener", 
+	     "D", 1, 
+	     "-D <class name>"));
+    newVector.addElement(new Option(
+	     "\tA string containing any notes about the experiment.\n"
+	      +"\t(default none)", 
+	     "N", 1, 
+	     "-N <string>"));
+
+    if ((m_ResultProducer != null) &&
+	(m_ResultProducer instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to result producer "
+	     + m_ResultProducer.getClass().getName() + ":"));
+      Enumeration enm = ((OptionHandler)m_ResultProducer).listOptions();
+      while (enm.hasMoreElements()) {
+	newVector.addElement(enm.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -L &lt;num&gt;
+   *  The lower run number to start the experiment from.
+   *  (default 1)</pre>
+   * 
+   * <pre> -U &lt;num&gt;
+   *  The upper run number to end the experiment at (inclusive).
+   *  (default 10)</pre>
+   * 
+   * <pre> -T &lt;arff file&gt;
+   *  The dataset to run the experiment on.
+   *  (required, may be specified multiple times)</pre>
+   * 
+   * <pre> -P &lt;class name&gt;
+   *  The full class name of a ResultProducer (required).
+   *  eg: weka.experiment.RandomSplitResultProducer</pre>
+   * 
+   * <pre> -D &lt;class name&gt;
+   *  The full class name of a ResultListener (required).
+   *  eg: weka.experiment.CSVResultListener</pre>
+   * 
+   * <pre> -N &lt;string&gt;
+   *  A string containing any notes about the experiment.
+   *  (default none)</pre>
+   * 
+   * <pre> 
+   * Options specific to result producer weka.experiment.RandomSplitResultProducer:
+   * </pre>
+   * 
+   * <pre> -P &lt;percent&gt;
+   *  The percentage of instances to use for training.
+   *  (default 66)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> -R
+   *  Set when data is not to be randomized and the data sets' size.
+   *  Is not to be determined via probabilistic rounding.</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the result producer. <p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String [] options) throws Exception {
+
+    String lowerString = Utils.getOption('L', options);
+    if (lowerString.length() != 0) {
+      setRunLower(Integer.parseInt(lowerString));
+    } else {
+      setRunLower(1);
+    }
+    String upperString = Utils.getOption('U', options);
+    if (upperString.length() != 0) {
+      setRunUpper(Integer.parseInt(upperString));
+    } else {
+      setRunUpper(10);
+    }
+    if (getRunLower() > getRunUpper()) {
+      throw new Exception("Lower (" + getRunLower() 
+			  + ") is greater than upper (" 
+			  + getRunUpper() + ")");
+    }
+    
+    setNotes(Utils.getOption('N', options));
+    
+    getDatasets().removeAllElements();
+    String dataName;
+    do {
+      dataName = Utils.getOption('T', options);
+      if (dataName.length() != 0) {
+	File dataset = new File(dataName);
+	getDatasets().addElement(dataset);
+      }
+    } while (dataName.length() != 0);
+    if (getDatasets().size() == 0) {
+      throw new Exception("Required: -T <arff file name>");
+    }
+
+    String rlName = Utils.getOption('D', options);
+    if (rlName.length() == 0) {
+      throw new Exception("Required: -D <ResultListener class name>");
+    }
+    rlName = rlName.trim();
+    // split off any options
+    int breakLoc = rlName.indexOf(' ');
+    String clName = rlName;
+    String rlOptionsString = "";
+    String [] rlOptions = null;
+    if (breakLoc != -1) {
+      clName = rlName.substring(0, breakLoc);
+      rlOptionsString = rlName.substring(breakLoc).trim();
+      rlOptions = Utils.splitOptions(rlOptionsString);
+    }
+    setResultListener((ResultListener)Utils.forName(ResultListener.class,
+						    clName, rlOptions));
+
+    String rpName = Utils.getOption('P', options);
+    if (rpName.length() == 0) {
+      throw new Exception("Required: -P <ResultProducer class name>");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // RP.
+    //GHF -- nice idea, but it prevents you from using result producers that
+    //       have *required* parameters
+    setResultProducer((ResultProducer)Utils.forName(
+		      ResultProducer.class,
+		      rpName,
+		      Utils.partitionOptions(options) )); //GHF
+    //GHF if (getResultProducer() instanceof OptionHandler) {
+    //GHF  ((OptionHandler) getResultProducer())
+    //GHF  .setOptions(Utils.partitionOptions(options));
+    //GHF }
+  }
+
+  /**
+   * Gets the current settings of the experiment iterator.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    // Currently no way to set custompropertyiterators from the command line
+
+    m_UsePropertyIterator = false;
+    m_PropertyPath = null;
+    m_PropertyArray = null;
+    
+    String [] rpOptions = new String [0];
+    if ((m_ResultProducer != null) && 
+	(m_ResultProducer instanceof OptionHandler)) {
+      rpOptions = ((OptionHandler)m_ResultProducer).getOptions();
+    }
+    
+    String [] options = new String [rpOptions.length 
+				   + getDatasets().size() * 2
+				   + 11];
+    int current = 0;
+
+    options[current++] = "-L"; options[current++] = "" + getRunLower();
+    options[current++] = "-U"; options[current++] = "" + getRunUpper();
+    if (getDatasets().size() != 0) {
+      for (int i = 0; i < getDatasets().size(); i++) {
+	options[current++] = "-T";
+	options[current++] = getDatasets().elementAt(i).toString();
+      }
+    }
+    if (getResultListener() != null) {
+      options[current++] = "-D";
+      options[current++] = getResultListener().getClass().getName();
+    }
+    if (getResultProducer() != null) {
+      options[current++] = "-P";
+      options[current++] = getResultProducer().getClass().getName();
+    }
+    if (!getNotes().equals("")) {
+      options[current++] = "-N"; options[current++] = getNotes();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(rpOptions, 0, options, current, 
+		     rpOptions.length);
+    current += rpOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Gets a string representation of the experiment configuration.
+   *
+   * @return a value of type 'String'
+   */
+  public String toString() {
+
+    String result = "Runs from: " + m_RunLower + " to: " + m_RunUpper + '\n';
+    result += "Datasets:";
+    for (int i = 0; i < m_Datasets.size(); i ++) {
+      result += " " + m_Datasets.elementAt(i);
+    }
+    result += '\n';
+    result += "Custom property iterator: "
+      + (m_UsePropertyIterator ? "on" : "off")
+      + "\n";
+    if (m_UsePropertyIterator) {
+      if (m_PropertyPath == null) {
+	throw new Error("*** null propertyPath ***");
+      }
+      if (m_PropertyArray == null) {
+	throw new Error("*** null propertyArray ***");
+      }
+      if (m_PropertyPath.length > 1) {
+	result += "Custom property path:\n";
+	for (int i = 0; i < m_PropertyPath.length - 1; i++) {
+	  PropertyNode pn = m_PropertyPath[i];
+	  result += "" + (i + 1) + "  " + pn.parentClass.getName()
+	    + "::" + pn.toString()
+	    + ' ' + pn.value.toString() + '\n';
+	}
+      }
+      result += "Custom property name:"
+	+ m_PropertyPath[m_PropertyPath.length - 1].toString() + '\n';
+      result += "Custom property values:\n";
+      for (int i = 0; i < Array.getLength(m_PropertyArray); i++) {
+	Object current = Array.get(m_PropertyArray, i);
+	result += " " + (i + 1)
+	  + " " + current.getClass().getName()
+	  + " " + current.toString() + '\n';
+      }
+    }
+    result += "ResultProducer: " + m_ResultProducer + '\n';
+    result += "ResultListener: " + m_ResultListener + '\n';
+    if (!getNotes().equals("")) {
+      result += "Notes: " + getNotes();
+    }
+    return result;
+  }
+
+  /**
+   * Loads an experiment from a file.
+   * 
+   * @param filename	the file to load the experiment from
+   * @return		the experiment
+   * @throws Exception	if loading fails
+   */
+  public static Experiment read(String filename) throws Exception {
+    Experiment	result;
+    
+    // KOML?
+    if ( (KOML.isPresent()) && (filename.toLowerCase().endsWith(KOML.FILE_EXTENSION)) ) {
+      result = (Experiment) KOML.read(filename);
+    }
+    // XML?
+    else if (filename.toLowerCase().endsWith(".xml")) {
+      XMLExperiment xml = new XMLExperiment(); 
+      result = (Experiment) xml.read(filename);
+    }
+    // binary
+    else {
+      FileInputStream fi = new FileInputStream(filename);
+      ObjectInputStream oi = new ObjectInputStream(
+	  new BufferedInputStream(fi));
+      result = (Experiment)oi.readObject();
+      oi.close();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Writes the experiment to disk.
+   * 
+   * @param filename	the file to write to
+   * @param exp		the experiment to save
+   * @throws Exception	if writing fails
+   */
+  public static void write(String filename, Experiment exp) throws Exception {
+    // KOML?
+    if ( (KOML.isPresent()) && (filename.toLowerCase().endsWith(KOML.FILE_EXTENSION)) ) {
+      KOML.write(filename, exp);
+    }
+    // XML?
+    else if (filename.toLowerCase().endsWith(".xml")) {
+      XMLExperiment xml = new XMLExperiment(); 
+      xml.write(filename, exp);
+    }
+    // binary
+    else {
+      FileOutputStream fo = new FileOutputStream(filename);
+      ObjectOutputStream oo = new ObjectOutputStream(
+	  new BufferedOutputStream(fo));
+      oo.writeObject(exp);
+      oo.close();
+    }
+  }
+  
+  /**
+   * Configures/Runs the Experiment from the command line.
+   *
+   * @param args command line arguments to the Experiment.
+   */
+  public static void main(String[] args) {
+
+    try {
+      Experiment exp = null;
+      // get options from XML?
+      String xmlOption = Utils.getOption("xml", args);
+      if (!xmlOption.equals(""))
+         args = new XMLOptions(xmlOption).toArray();
+      
+      String expFile = Utils.getOption('l', args);
+      String saveFile = Utils.getOption('s', args);
+      boolean runExp = Utils.getFlag('r', args);
+      if (expFile.length() == 0) {
+	exp = new Experiment();
+	try {
+	  exp.setOptions(args);
+	  Utils.checkForRemainingOptions(args);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	  String result = "Usage:\n\n"
+	    + "-l <exp|xml file>\n"
+	    + "\tLoad experiment from file (default use cli options).\n"
+      + "\tThe type is determined, based on the extension (" 
+        + FILE_EXTENSION + " or .xml)\n"
+	    + "-s <exp|xml file>\n"
+	    + "\tSave experiment to file after setting other options.\n"
+      + "\tThe type is determined, based on the extension (" 
+        + FILE_EXTENSION + " or .xml)\n"
+	    + "\t(default don't save)\n"
+	    + "-r\n"
+	    + "\tRun experiment (default don't run)\n"
+	    + "-xml <filename | xml-string>\n"
+	    + "\tget options from XML-Data instead from parameters\n"
+            + "\n";
+	  Enumeration enm = ((OptionHandler)exp).listOptions();
+	  while (enm.hasMoreElements()) {
+	    Option option = (Option) enm.nextElement();
+	    result += option.synopsis() + "\n";
+	    result += option.description() + "\n";
+	  }
+	  throw new Exception(result + "\n" + ex.getMessage());
+	}
+      } else {
+	exp = read(expFile);
+
+	// allow extra datasets to be added to pre-loaded experiment from command line
+	String dataName;
+	do {
+	  dataName = Utils.getOption('T', args);
+	  if (dataName.length() != 0) {
+	    File dataset = new File(dataName);
+	    exp.getDatasets().addElement(dataset);
+	  }
+	} while (dataName.length() != 0);
+	
+      }
+      System.err.println("Experiment:\n" + exp.toString());
+
+      if (saveFile.length() != 0)
+	write(saveFile, exp);
+      
+      if (runExp) {
+	System.err.println("Initializing...");
+	exp.initialize();
+	System.err.println("Iterating...");
+	exp.runExperiment();
+	System.err.println("Postprocessing...");
+	exp.postProcess();
+      }
+      
+    } catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5399 $");
+  }
+} // Experiment
Index: branches/MetisMQI/src/main/java/weka/experiment/ExplicitTestsetResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ExplicitTestsetResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ExplicitTestsetResultProducer.java	(revision 29)
@@ -0,0 +1,1127 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ExplicitTestsetResultProducer.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Loads the external test set and calls the appropriate SplitEvaluator to generate some results.<br/>
+ * The filename of the test set is constructed as follows:<br/>
+ *    &lt;dir&gt; + / + &lt;prefix&gt; + &lt;relation-name&gt; + &lt;suffix&gt;<br/>
+ * The relation-name can be modified by using the regular expression to replace the matching sub-string with a specified replacement string. In order to get rid of the string that the Weka filters add to the end of the relation name, just use '.*-weka' as the regular expression to find.<br/>
+ * The suffix determines the type of file to load, i.e., one is not restricted to ARFF files. As long as Weka recognizes the extension specified in the suffix, the data will be loaded with one of Weka's converters.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D.
+ *  (default: splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> -R
+ *  Set when data is to be randomized.</pre>
+ * 
+ * <pre> -dir &lt;directory&gt;
+ *  The directory containing the test sets.
+ *  (default: current directory)</pre>
+ * 
+ * <pre> -prefix &lt;string&gt;
+ *  An optional prefix for the test sets (before the relation name).
+ * (default: empty string)</pre>
+ * 
+ * <pre> -suffix &lt;string&gt;
+ *  The suffix to append to the test set.
+ *  (default: _test.arff)</pre>
+ * 
+ * <pre> -find &lt;regular expression&gt;
+ *  The regular expression to search the relation name with.
+ *  Not used if an empty string.
+ *  (default: empty string)</pre>
+ * 
+ * <pre> -replace &lt;string&gt;
+ *  The replacement string for the all the matches of '-find'.
+ *  (default: empty string)</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * All options after -- will be passed to the split evaluator.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5353 $
+ */
+public class ExplicitTestsetResultProducer 
+  implements ResultProducer, OptionHandler, AdditionalMeasureProducer, 
+             RevisionHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 2613585409333652530L;
+
+  /** the default suffix. */
+  public final static String DEFAULT_SUFFIX = "_test.arff";
+  
+  /** The dataset of interest. */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to. */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The directory containing all the test sets. */
+  protected File m_TestsetDir = new File(System.getProperty("user.dir"));
+
+  /** The prefix for all the test sets. */
+  protected String m_TestsetPrefix = "";
+
+  /** The suffix for all the test sets. */
+  protected String m_TestsetSuffix = DEFAULT_SUFFIX;
+
+  /** The regular expression to search for in the relation name. */
+  protected String m_RelationFind = "";
+
+  /** The string to use to replace the matches of the regular expression. */
+  protected String m_RelationReplace = "";
+
+  /** Whether dataset is to be randomized. */
+  protected boolean m_randomize = false;
+
+  /** The SplitEvaluator used to generate results. */
+  protected SplitEvaluator m_SplitEvaluator = new ClassifierSplitEvaluator();
+
+  /** The names of any additional measures to look for in SplitEvaluators. */
+  protected String[] m_AdditionalMeasures = null;
+
+  /** Save raw output of split evaluators --- for debugging purposes. */
+  protected boolean m_debugOutput = false;
+
+  /** The output zipper to use for saving raw splitEvaluator output. */
+  protected OutputZipper m_ZipDest = null;
+
+  /** The destination output file/directory for raw output. */
+  protected File m_OutputFile = new File(
+			        new File(System.getProperty("user.dir")), 
+				"splitEvalutorOut.zip");
+
+  /** The name of the key field containing the dataset name. */
+  public static String DATASET_FIELD_NAME = "Dataset";
+
+  /** The name of the key field containing the run number. */
+  public static String RUN_FIELD_NAME = "Run";
+
+  /** The name of the result field containing the timestamp. */
+  public static String TIMESTAMP_FIELD_NAME = "Date_time";
+
+  /**
+   * Returns a string describing this result producer.
+   * 
+   * @return 		a description of the result producer suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Loads the external test set and calls the appropriate "
+      + "SplitEvaluator to generate some results.\n"
+      + "The filename of the test set is constructed as follows:\n"
+      + "   <dir> + / + <prefix> + <relation-name> + <suffix>\n"
+      + "The relation-name can be modified by using the regular expression "
+      + "to replace the matching sub-string with a specified replacement "
+      + "string. In order to get rid of the string that the Weka filters "
+      + "add to the end of the relation name, just use '.*-weka' as the "
+      + "regular expression to find.\n"
+      + "The suffix determines the type of file to load, i.e., one is "
+      + "not restricted to ARFF files. As long as Weka recognizes the "
+      + "extension specified in the suffix, the data will be loaded with "
+      + "one of Weka's converters.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"Save raw split evaluator output.",
+	"D", 0, "-D"));
+
+    result.addElement(new Option(
+	"\tThe filename where raw output will be stored.\n"
+	+"\tIf a directory name is specified then then individual\n"
+	+"\toutputs will be gzipped, otherwise all output will be\n"
+	+"\tzipped to the named file. Use in conjuction with -D.\n"
+	+"\t(default: splitEvalutorOut.zip)", 
+	"O", 1, "-O <file/directory name/path>"));
+
+    result.addElement(new Option(
+	"\tThe full class name of a SplitEvaluator.\n"
+	+"\teg: weka.experiment.ClassifierSplitEvaluator", 
+	"W", 1, "-W <class name>"));
+
+    result.addElement(new Option(
+	"\tSet when data is to be randomized.",
+	"R", 0 ,"-R"));
+
+    result.addElement(new Option(
+	"\tThe directory containing the test sets.\n"
+	+ "\t(default: current directory)", 
+	"dir", 1, "-dir <directory>"));
+
+    result.addElement(new Option(
+	"\tAn optional prefix for the test sets (before the relation name).\n"
+	+ "(default: empty string)", 
+	"prefix", 1, "-prefix <string>"));
+
+    result.addElement(new Option(
+	"\tThe suffix to append to the test set.\n"
+	+ "\t(default: " + DEFAULT_SUFFIX + ")", 
+	"suffix", 1, "-suffix <string>"));
+
+    result.addElement(new Option(
+	"\tThe regular expression to search the relation name with.\n"
+	+ "\tNot used if an empty string.\n"
+	+ "\t(default: empty string)", 
+	"find", 1, "-find <regular expression>"));
+
+    result.addElement(new Option(
+	"\tThe replacement string for the all the matches of '-find'.\n"
+	+ "\t(default: empty string)", 
+	"replace", 1, "-replace <string>"));
+    
+    if ((m_SplitEvaluator != null) && (m_SplitEvaluator instanceof OptionHandler)) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to split evaluator "
+	  + m_SplitEvaluator.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_SplitEvaluator).listOptions();
+      while (enu.hasMoreElements())
+	result.addElement(enu.nextElement());
+    }
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D.
+   *  (default: splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> -R
+   *  Set when data is to be randomized.</pre>
+   * 
+   * <pre> -dir &lt;directory&gt;
+   *  The directory containing the test sets.
+   *  (default: current directory)</pre>
+   * 
+   * <pre> -prefix &lt;string&gt;
+   *  An optional prefix for the test sets (before the relation name).
+   * (default: empty string)</pre>
+   * 
+   * <pre> -suffix &lt;string&gt;
+   *  The suffix to append to the test set.
+   *  (default: _test.arff)</pre>
+   * 
+   * <pre> -find &lt;regular expression&gt;
+   *  The regular expression to search the relation name with.
+   *  Not used if an empty string.
+   *  (default: empty string)</pre>
+   * 
+   * <pre> -replace &lt;string&gt;
+   *  The replacement string for the all the matches of '-find'.
+   *  (default: empty string)</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the split evaluator.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    setRawOutput(Utils.getFlag('D', options));
+    setRandomizeData(!Utils.getFlag('R', options));
+
+    tmpStr = Utils.getOption('O', options);
+    if (tmpStr.length() != 0)
+      setOutputFile(new File(tmpStr));
+
+    tmpStr = Utils.getOption("dir", options);
+    if (tmpStr.length() > 0)
+      setTestsetDir(new File(tmpStr));
+    else
+      setTestsetDir(new File(System.getProperty("user.dir")));
+
+    tmpStr = Utils.getOption("prefix", options);
+    if (tmpStr.length() > 0)
+      setTestsetPrefix(tmpStr);
+    else
+      setTestsetPrefix("");
+
+    tmpStr = Utils.getOption("suffix", options);
+    if (tmpStr.length() > 0)
+      setTestsetSuffix(tmpStr);
+    else
+      setTestsetSuffix(DEFAULT_SUFFIX);
+    
+    tmpStr = Utils.getOption("find", options);
+    if (tmpStr.length() > 0)
+      setRelationFind(tmpStr);
+    else
+      setRelationFind("");
+    
+    tmpStr = Utils.getOption("replace", options);
+    if ((tmpStr.length() > 0) && (getRelationFind().length() > 0))
+      setRelationReplace(tmpStr);
+    else
+      setRelationReplace("");
+    
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() == 0)
+      throw new Exception("A SplitEvaluator must be specified with the -W option.");
+    
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // SE.
+    setSplitEvaluator((SplitEvaluator)Utils.forName(SplitEvaluator.class, tmpStr, null));
+    if (getSplitEvaluator() instanceof OptionHandler)
+      ((OptionHandler) getSplitEvaluator()).setOptions(Utils.partitionOptions(options));
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    String[] 		seOptions;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    seOptions = new String [0];
+    if ((m_SplitEvaluator != null) && (m_SplitEvaluator instanceof OptionHandler))
+      seOptions = ((OptionHandler)m_SplitEvaluator).getOptions();
+
+    if (getRawOutput())
+      result.add("-D");
+    
+    if (!getRandomizeData())
+      result.add("-R");
+
+    result.add("-O"); 
+    result.add(getOutputFile().getName());
+
+    result.add("-dir");
+    result.add(getTestsetDir().getPath());
+    
+    if (getTestsetPrefix().length() > 0) {
+      result.add("-prefix");
+      result.add(getTestsetPrefix());
+    }
+
+    result.add("-suffix");
+    result.add(getTestsetSuffix());
+    
+    if (getRelationFind().length() > 0) {
+      result.add("-find");
+      result.add(getRelationFind());
+      
+      if (getRelationReplace().length() > 0) {
+	result.add("-replace");
+	result.add(getRelationReplace());
+      }
+    }
+
+    if (getSplitEvaluator() != null) {
+      result.add("-W");
+      result.add(getSplitEvaluator().getClass().getName());
+    }
+    
+    if (seOptions.length > 0) {
+      result.add("--");
+      for (i = 0; i < seOptions.length; i++)
+	result.add(seOptions[i]);
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    m_Instances = instances;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current SplitEvaluator) if an experiment
+   * is the type that iterates over a set of properties.
+   * 
+   * @param additionalMeasures 	an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String[] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_SplitEvaluator != null) {
+      System.err.println(
+	  "ExplicitTestsetResultProducer: setting additional "
+	  + "measures for split evaluator");
+      m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+  
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the SplitEvaluator.
+   * 
+   * @return 		an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector result = new Vector();
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_SplitEvaluator).enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String) en.nextElement();
+	result.addElement(mname);
+      }
+    }
+    return result.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure.
+   * 
+   * @param additionalMeasureName 	the name of the measure to query for its value
+   * @return 				the value of the named measure
+   * @throws IllegalArgumentException 	if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer)
+      return ((AdditionalMeasureProducer)m_SplitEvaluator).getMeasure(additionalMeasureName);
+    else
+      throw new IllegalArgumentException(
+	  "ExplicitTestsetResultProducer: "
+	  + "Can't return value for : " + additionalMeasureName
+	  + ". " + m_SplitEvaluator.getClass().getName() + " "
+	  + "is not an AdditionalMeasureProducer");
+  }
+  
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener 	a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+    m_ResultListener = listener;
+  }
+
+  /**
+   * Gets a Double representing the current date and time.
+   * eg: 1:46pm on 20/5/1999 -> 19990520.1346
+   *
+   * @return 		a value of type Double
+   */
+  public static Double getTimestamp() {
+    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+    double timestamp = now.get(Calendar.YEAR) * 10000
+      + (now.get(Calendar.MONTH) + 1) * 100
+      + now.get(Calendar.DAY_OF_MONTH)
+      + now.get(Calendar.HOUR_OF_DAY) / 100.0
+      + now.get(Calendar.MINUTE) / 10000.0;
+    return new Double(timestamp);
+  }
+
+  /**
+   * Prepare to generate results.
+   *
+   * @throws Exception 	if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+    if (m_SplitEvaluator == null)
+      throw new Exception("No SplitEvalutor set");
+
+    if (m_ResultListener == null)
+      throw new Exception("No ResultListener set");
+
+    m_ResultListener.preProcess(this);
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more requests to generate results for the current experiment
+   * will be sent.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  public void postProcess() throws Exception {
+    m_ResultListener.postProcess(this);
+    if (m_debugOutput) {
+      if (m_ZipDest != null) {
+	m_ZipDest.finished();
+	m_ZipDest = null;
+      }
+    }
+  }
+
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run 	the run number to get keys for.
+   * @throws Exception 	if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+    if (m_Instances == null)
+      throw new Exception("No Instances set");
+
+    // Add in some fields to the key like run number, dataset name
+    Object[] seKey = m_SplitEvaluator.getKey();
+    Object[] key = new Object [seKey.length + 2];
+    key[0] = Utils.backQuoteChars(m_Instances.relationName());
+    key[1] = "" + run;
+    System.arraycopy(seKey, 0, key, 2, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+      try {
+	m_ResultListener.acceptResult(this, key, null);
+      }
+      catch (Exception ex) {
+	// Save the train and test datasets for debugging purposes?
+	throw ex;
+      }
+    }
+  }
+
+  /**
+   * Generates a new filename for the given relation based on the current 
+   * setup.
+   * 
+   * @param inst	the instances to create the filename for
+   * @return		the generated filename
+   */
+  protected String createFilename(Instances inst) {
+    String	result;
+    String	name;
+
+    name = inst.relationName();
+    if (getRelationFind().length() > 0)
+      name = name.replaceAll(getRelationFind(), getRelationReplace());
+    
+    result  = getTestsetDir().getPath() + File.separator;
+    result += getTestsetPrefix() + name + getTestsetSuffix();
+    
+    return result;
+  }
+  
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run 	the run number to get results for.
+   * @throws Exception 	if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+    if (getRawOutput()) {
+      if (m_ZipDest == null)
+	m_ZipDest = new OutputZipper(m_OutputFile);
+    }
+
+    if (m_Instances == null)
+      throw new Exception("No Instances set");
+    
+    // Add in some fields to the key like run number, dataset name
+    Object[] seKey = m_SplitEvaluator.getKey();
+    Object[] key = new Object [seKey.length + 2];
+    key[0] = Utils.backQuoteChars(m_Instances.relationName());
+    key[1] = "" + run;
+    System.arraycopy(seKey, 0, key, 2, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+      // training set
+      Instances train = new Instances(m_Instances);
+      if (m_randomize) {
+	Random rand = new Random(run);
+	train.randomize(rand);
+      }
+
+      // test set
+      String filename = createFilename(train);
+      File file = new File(filename);
+      if (!file.exists())
+	throw new WekaException("Test set '" + filename + "' not found!");
+      Instances test = DataSource.read(filename);
+      // can we set the class attribute safely?
+      if (train.numAttributes() == test.numAttributes())
+	test.setClassIndex(train.classIndex());
+      else
+	throw new WekaException(
+	    "Train and test set (= " + filename + ") "
+	    + "differ in number of attributes: "
+	    + train.numAttributes() + " != " + test.numAttributes());
+      // test headers
+      if (!train.equalHeaders(test))
+	throw new WekaException(
+	    "Train and test set (= " + filename + ") "
+	    + "are not compatible:\n"
+	    + train.equalHeadersMsg(test));
+      
+      try {
+	Object[] seResults = m_SplitEvaluator.getResult(train, test);
+	Object[] results = new Object [seResults.length + 1];
+	results[0] = getTimestamp();
+	System.arraycopy(seResults, 0, results, 1,
+			 seResults.length);
+	if (m_debugOutput) {
+	  String resultName = 
+	    (""+run+"."+
+	     Utils.backQuoteChars(train.relationName())
+	     +"."
+	     +m_SplitEvaluator.toString()).replace(' ','_');
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.classifiers.");
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.filters.");
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.attributeSelection.");
+	  m_ZipDest.zipit(m_SplitEvaluator.getRawResultOutput(), resultName);
+	}
+	m_ResultListener.acceptResult(this, key, results);
+      }
+      catch (Exception e) {
+	// Save the train and test datasets for debugging purposes?
+	throw e;
+      }
+    }
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return 		an array containing the name of each column
+   */
+  public String[] getKeyNames() {
+    String[] keyNames = m_SplitEvaluator.getKeyNames();
+    // Add in the names of our extra key fields
+    String[] newKeyNames = new String [keyNames.length + 2];
+    newKeyNames[0] = DATASET_FIELD_NAME;
+    newKeyNames[1] = RUN_FIELD_NAME;
+    System.arraycopy(keyNames, 0, newKeyNames, 2, keyNames.length);
+    return newKeyNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return 		an array containing objects of the type of each column. 
+   * 			The objects should be Strings, or Doubles.
+   */
+  public Object[] getKeyTypes() {
+    Object[] keyTypes = m_SplitEvaluator.getKeyTypes();
+    // Add in the types of our extra fields
+    Object[] newKeyTypes = new String [keyTypes.length + 2];
+    newKeyTypes[0] = new String();
+    newKeyTypes[1] = new String();
+    System.arraycopy(keyTypes, 0, newKeyTypes, 2, keyTypes.length);
+    return newKeyTypes;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return 		an array containing the name of each column
+   */
+  public String[] getResultNames() {
+    String[] resultNames = m_SplitEvaluator.getResultNames();
+    // Add in the names of our extra Result fields
+    String[] newResultNames = new String [resultNames.length + 1];
+    newResultNames[0] = TIMESTAMP_FIELD_NAME;
+    System.arraycopy(resultNames, 0, newResultNames, 1, resultNames.length);
+    return newResultNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return 		an array containing objects of the type of each column. 
+   * 			The objects should be Strings, or Doubles.
+   */
+  public Object[] getResultTypes() {
+    Object[] resultTypes = m_SplitEvaluator.getResultTypes();
+    // Add in the types of our extra Result fields
+    Object[] newResultTypes = new Object [resultTypes.length + 1];
+    newResultTypes[0] = new Double(0);
+    System.arraycopy(resultTypes, 0, newResultTypes, 1, resultTypes.length);
+    return newResultTypes;
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return 		the description of the ResultProducer state, or null
+   * 			if no state is defined
+   */
+  public String getCompatibilityState() {
+    String 	result;
+    
+    result = "";
+    if (getRandomizeData())
+      result += " -R";
+
+    result += " -dir " + getTestsetDir();
+    
+    if (getTestsetPrefix().length() > 0)
+      result += " -prefix " + getTestsetPrefix();
+    
+    result += " -suffix " + getTestsetSuffix();
+    
+    if (getRelationFind().length() > 0) {
+      result += " -find " + getRelationFind();
+      
+      if (getRelationReplace().length() > 0)
+        result += " -replace " + getRelationReplace();
+    }
+    
+    if (m_SplitEvaluator == null)
+      result += " <null SplitEvaluator>";
+    else
+      result += " -W " + m_SplitEvaluator.getClass().getName();
+
+    return result + " --";
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputFileTipText() {
+    return "Set the destination for saving raw output. If the rawOutput "
+      +"option is selected, then output from the splitEvaluator for "
+      +"individual train-test splits is saved. If the destination is a "
+      +"directory, "
+      +"then each output is saved to an individual gzip file; if the "
+      +"destination is a file, then each output is saved as an entry "
+      +"in a zip file.";
+  }
+
+  /**
+   * Get the value of OutputFile.
+   *
+   * @return 		Value of OutputFile.
+   */
+  public File getOutputFile() {
+    return m_OutputFile;
+  }
+  
+  /**
+   * Set the value of OutputFile.
+   *
+   * @param value 	Value to assign to OutputFile.
+   */
+  public void setOutputFile(File value) {
+    m_OutputFile = value;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String randomizeDataTipText() {
+    return "Do not randomize dataset and do not perform probabilistic rounding " +
+      "if true";
+  }
+
+  /**
+   * Get if dataset is to be randomized.
+   * 
+   * @return 		true if dataset is to be randomized
+   */
+  public boolean getRandomizeData() {
+    return m_randomize;
+  }
+  
+  /**
+   * Set to true if dataset is to be randomized.
+   * 
+   * @param value 		true if dataset is to be randomized
+   */
+  public void setRandomizeData(boolean value) {
+    m_randomize = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String rawOutputTipText() {
+    return "Save raw output (useful for debugging). If set, then output is "
+      +"sent to the destination specified by outputFile";
+  }
+
+  /**
+   * Get if raw split evaluator output is to be saved.
+   * 
+   * @return 		true if raw split evalutor output is to be saved
+   */
+  public boolean getRawOutput() {
+    return m_debugOutput;
+  }
+  
+  /**
+   * Set to true if raw split evaluator output is to be saved.
+   * 
+   * @param value 		true if output is to be saved
+   */
+  public void setRawOutput(boolean value) {
+    m_debugOutput = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String splitEvaluatorTipText() {
+    return "The evaluator to apply to the test data. "
+      +"This may be a classifier, regression scheme etc.";
+  }
+
+  /**
+   * Get the SplitEvaluator.
+   *
+   * @return 		the SplitEvaluator.
+   */
+  public SplitEvaluator getSplitEvaluator() {
+    return m_SplitEvaluator;
+  }
+  
+  /**
+   * Set the SplitEvaluator.
+   *
+   * @param value 	new SplitEvaluator to use.
+   */
+  public void setSplitEvaluator(SplitEvaluator value) {
+    m_SplitEvaluator = value;
+    m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String testsetDirTipText() {
+    return "The directory containing the test sets.";
+  }
+
+  /**
+   * Returns the currently set directory for the test sets.
+   *
+   * @return 		the directory
+   */
+  public File getTestsetDir() {
+    return m_TestsetDir;
+  }
+  
+  /**
+   * Sets the directory to use for the test sets.
+   *
+   * @param value 	the directory to use
+   */
+  public void setTestsetDir(File value) {
+    m_TestsetDir = value;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String testsetPrefixTipText() {
+    return "The prefix to use for the filename of the test sets.";
+  }
+
+  /**
+   * Returns the currently set prefix.
+   *
+   * @return 		the prefix
+   */
+  public String getTestsetPrefix() {
+    return m_TestsetPrefix;
+  }
+  
+  /**
+   * Sets the prefix to use for the test sets.
+   *
+   * @param value 	the prefix
+   */
+  public void setTestsetPrefix(String value) {
+    m_TestsetPrefix = value;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String testsetSuffixTipText() {
+    return 
+        "The suffix to use for the filename of the test sets - must contain "
+      + "the file extension.";
+  }
+
+  /**
+   * Returns the currently set suffix.
+   *
+   * @return 		the suffix
+   */
+  public String getTestsetSuffix() {
+    return m_TestsetSuffix;
+  }
+  
+  /**
+   * Sets the suffix to use for the test sets.
+   *
+   * @param value 	the suffix
+   */
+  public void setTestsetSuffix(String value) {
+    if ((value == null) || (value.length() == 0))
+      value = DEFAULT_SUFFIX;
+    m_TestsetSuffix = value;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String relationFindTipText() {
+    return 
+        "The regular expression to use for removing parts of the relation "
+      + "name, ignored if empty.";
+  }
+
+  /**
+   * Returns the currently set regular expression to use on the relation name.
+   *
+   * @return 		the regular expression
+   */
+  public String getRelationFind() {
+    return m_RelationFind;
+  }
+  
+  /**
+   * Sets the regular expression to use on the relation name.
+   *
+   * @param value 	the regular expression
+   */
+  public void setRelationFind(String value) {
+    m_RelationFind = value;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String relationReplaceTipText() {
+    return "The string to replace all matches of the regular expression with.";
+  }
+
+  /**
+   * Returns the currently set replacement string to use on the relation name.
+   *
+   * @return 		the replacement string
+   */
+  public String getRelationReplace() {
+    return m_RelationReplace;
+  }
+  
+  /**
+   * Sets the replacement string to use on the relation name.
+   *
+   * @param value 	the regular expression
+   */
+  public void setRelationReplace(String value) {
+    m_RelationReplace = value;
+  }  
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return 		a text description of the result producer.
+   */
+  public String toString() {
+    String result = "ExplicitTestsetResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null)
+      result += ": <null Instances>";
+    else
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5353 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/InstanceQuery.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/InstanceQuery.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/InstanceQuery.java	(revision 29)
@@ -0,0 +1,628 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceQuery.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Time;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Convert the results of a database query into instances. The jdbc
+ * driver and database to be used default to "jdbc.idbDriver" and
+ * "jdbc:idb=experiments.prp". These may be changed by creating
+ * a java properties file called DatabaseUtils.props in user.home or
+ * the current directory. eg:<p>
+ *
+ * <code><pre>
+ * jdbcDriver=jdbc.idbDriver
+ * jdbcURL=jdbc:idb=experiments.prp
+ * </pre></code><p>
+ *
+ * Command line use just outputs the instances to System.out. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -Q &lt;query&gt;
+ *  SQL query to execute.</pre>
+ * 
+ * <pre> -S
+ *  Return sparse rather than normal instances.</pre>
+ * 
+ * <pre> -U &lt;username&gt;
+ *  The username to use for connecting.</pre>
+ * 
+ * <pre> -P &lt;password&gt;
+ *  The password to use for connecting.</pre>
+ * 
+ * <pre> -D
+ *  Enables debug output.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class InstanceQuery 
+  extends DatabaseUtils 
+  implements OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 718158370917782584L;
+
+  /** Determines whether sparse data is created */
+  boolean m_CreateSparseData = false;
+  
+  /** Query to execute */
+  String m_Query = "SELECT * from ?";
+
+  /**
+   * Sets up the database drivers
+   *
+   * @throws Exception if an error occurs
+   */
+  public InstanceQuery() throws Exception {
+
+    super();
+  }
+
+  /**
+   * Returns an enumeration describing the available options <p>
+   *
+   * @return an enumeration of all options
+   */
+   public Enumeration listOptions () {
+     Vector result = new Vector();
+
+     result.addElement(
+         new Option("\tSQL query to execute.",
+                    "Q",1,"-Q <query>"));
+     
+     result.addElement(
+         new Option("\tReturn sparse rather than normal instances.", 
+                    "S", 0, "-S"));
+     
+     result.addElement(
+         new Option("\tThe username to use for connecting.", 
+                    "U", 1, "-U <username>"));
+     
+     result.addElement(
+         new Option("\tThe password to use for connecting.", 
+                    "P", 1, "-P <password>"));
+     
+     result.addElement(
+         new Option("\tEnables debug output.", 
+                    "D", 0, "-D"));
+     
+     return result.elements();
+   }
+
+  /**
+   * Parses a given list of options.
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -Q &lt;query&gt;
+   *  SQL query to execute.</pre>
+   * 
+   * <pre> -S
+   *  Return sparse rather than normal instances.</pre>
+   * 
+   * <pre> -U &lt;username&gt;
+   *  The username to use for connecting.</pre>
+   * 
+   * <pre> -P &lt;password&gt;
+   *  The password to use for connecting.</pre>
+   * 
+   * <pre> -D
+   *  Enables debug output.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+    throws Exception {
+
+    String      tmpStr;
+    
+    setSparseData(Utils.getFlag('S',options));
+
+    tmpStr = Utils.getOption('Q',options);
+    if (tmpStr.length() != 0)
+      setQuery(tmpStr);
+
+    tmpStr = Utils.getOption('U',options);
+    if (tmpStr.length() != 0)
+      setUsername(tmpStr);
+
+    tmpStr = Utils.getOption('P',options);
+    if (tmpStr.length() != 0)
+      setPassword(tmpStr);
+
+    setDebug(Utils.getFlag('D',options));
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String queryTipText() {
+    return "The SQL query to execute against the database.";
+  }
+  
+  /**
+   * Set the query to execute against the database
+   * @param q the query to execute
+   */
+  public void setQuery(String q) {
+    m_Query = q;
+  }
+
+  /**
+   * Get the query to execute against the database
+   * @return the query
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sparseDataTipText() {
+    return "Encode data as sparse instances.";
+  }
+
+  /**
+   * Sets whether data should be encoded as sparse instances
+   * @param s true if data should be encoded as a set of sparse instances
+   */
+  public void setSparseData(boolean s) {
+    m_CreateSparseData = s;
+  }
+
+  /**
+   * Gets whether data is to be returned as a set of sparse instances
+   * @return true if data is to be encoded as sparse instances
+   */
+  public boolean getSparseData() {
+    return m_CreateSparseData;
+  }
+
+  /**
+   * Gets the current settings of InstanceQuery
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+
+    Vector options = new Vector();
+
+    options.add("-Q"); 
+    options.add(getQuery());
+ 
+    if (getSparseData())
+      options.add("-S");
+
+    if (!getUsername().equals("")) {
+      options.add("-U");
+      options.add(getUsername());
+    }
+
+    if (!getPassword().equals("")) {
+      options.add("-P");
+      options.add(getPassword());
+    }
+
+    if (getDebug())
+      options.add("-D");
+
+    return (String[]) options.toArray(new String[options.size()]);
+  }
+
+  /**
+   * Makes a database query using the query set through the -Q option 
+   * to convert a table into a set of instances
+   *
+   * @return the instances contained in the result of the query
+   * @throws Exception if an error occurs
+   */
+  public Instances retrieveInstances() throws Exception {
+    return retrieveInstances(m_Query);
+  }
+
+  /**
+   * Makes a database query to convert a table into a set of instances
+   *
+   * @param query the query to convert to instances
+   * @return the instances contained in the result of the query, NULL if the
+   *         SQL query doesn't return a ResultSet, e.g., DELETE/INSERT/UPDATE
+   * @throws Exception if an error occurs
+   */
+  public Instances retrieveInstances(String query) throws Exception {
+
+    if (m_Debug) 
+      System.err.println("Executing query: " + query);
+    connectToDatabase();
+    if (execute(query) == false) {
+      if (m_PreparedStatement.getUpdateCount() == -1) {
+        throw new Exception("Query didn't produce results");
+      }
+      else {
+        if (m_Debug) 
+          System.err.println(m_PreparedStatement.getUpdateCount() 
+              + " rows affected.");
+        close();
+        return null;
+      }
+    }
+    ResultSet rs = getResultSet();
+    if (m_Debug) 
+      System.err.println("Getting metadata...");
+    ResultSetMetaData md = rs.getMetaData();
+    if (m_Debug) 
+      System.err.println("Completed getting metadata...");
+    
+    
+    // Determine structure of the instances
+    int numAttributes = md.getColumnCount();
+    int [] attributeTypes = new int [numAttributes];
+    Hashtable [] nominalIndexes = new Hashtable [numAttributes];
+    FastVector [] nominalStrings = new FastVector [numAttributes];
+    for (int i = 1; i <= numAttributes; i++) {
+      /* switch (md.getColumnType(i)) {
+      case Types.CHAR:
+      case Types.VARCHAR:
+      case Types.LONGVARCHAR:
+      case Types.BINARY:
+      case Types.VARBINARY:
+      case Types.LONGVARBINARY:*/
+      
+      switch (translateDBColumnType(md.getColumnTypeName(i))) {
+	
+      case STRING :
+	//System.err.println("String --> nominal");
+	attributeTypes[i - 1] = Attribute.NOMINAL;
+	nominalIndexes[i - 1] = new Hashtable();
+	nominalStrings[i - 1] = new FastVector();
+	break;
+      case TEXT:
+	//System.err.println("Text --> string");
+	attributeTypes[i - 1] = Attribute.STRING;
+	nominalIndexes[i - 1] = new Hashtable();
+	nominalStrings[i - 1] = new FastVector();
+	break;
+      case BOOL:
+	//System.err.println("boolean --> nominal");
+	attributeTypes[i - 1] = Attribute.NOMINAL;
+	nominalIndexes[i - 1] = new Hashtable();
+	nominalIndexes[i - 1].put("false", new Double(0));
+	nominalIndexes[i - 1].put("true", new Double(1));
+	nominalStrings[i - 1] = new FastVector();
+	nominalStrings[i - 1].addElement("false");
+	nominalStrings[i - 1].addElement("true");
+	break;
+      case DOUBLE:
+	//System.err.println("BigDecimal --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case BYTE:
+	//System.err.println("byte --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case SHORT:
+	//System.err.println("short --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case INTEGER:
+	//System.err.println("int --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case LONG:
+	//System.err.println("long --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case FLOAT:
+	//System.err.println("float --> numeric");
+	attributeTypes[i - 1] = Attribute.NUMERIC;
+	break;
+      case DATE:
+	attributeTypes[i - 1] = Attribute.DATE;
+	break;
+      case TIME:
+	attributeTypes[i - 1] = Attribute.DATE;
+	break;
+      default:
+	//System.err.println("Unknown column type");
+	attributeTypes[i - 1] = Attribute.STRING;
+      }
+    }
+
+    // For sqlite
+    // cache column names because the last while(rs.next()) { iteration for
+    // the tuples below will close the md object:  
+    Vector<String> columnNames = new Vector<String>(); 
+    for (int i = 0; i < numAttributes; i++) {
+      columnNames.add(md.getColumnName(i + 1));
+    }
+
+    // Step through the tuples
+    if (m_Debug) 
+      System.err.println("Creating instances...");
+    FastVector instances = new FastVector();
+    int rowCount = 0;
+    while(rs.next()) {
+      if (rowCount % 100 == 0) {
+        if (m_Debug)  {
+	  System.err.print("read " + rowCount + " instances \r");
+	  System.err.flush();
+        }
+      }
+      double[] vals = new double[numAttributes];
+      for(int i = 1; i <= numAttributes; i++) {
+	/*switch (md.getColumnType(i)) {
+	case Types.CHAR:
+	case Types.VARCHAR:
+	case Types.LONGVARCHAR:
+	case Types.BINARY:
+	case Types.VARBINARY:
+	case Types.LONGVARBINARY:*/
+	switch (translateDBColumnType(md.getColumnTypeName(i))) {
+	case STRING :
+	  String str = rs.getString(i);
+	  
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    Double index = (Double)nominalIndexes[i - 1].get(str);
+	    if (index == null) {
+	      index = new Double(nominalStrings[i - 1].size());
+	      nominalIndexes[i - 1].put(str, index);
+	      nominalStrings[i - 1].addElement(str);
+	    }
+	    vals[i - 1] = index.doubleValue();
+	  }
+	  break;
+	case TEXT:
+	  String txt = rs.getString(i);
+	  
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    Double index = (Double)nominalIndexes[i - 1].get(txt);
+	    if (index == null) {
+	      index = new Double(nominalStrings[i - 1].size());
+	      nominalIndexes[i - 1].put(txt, index);
+	      nominalStrings[i - 1].addElement(txt);
+	    }
+	    vals[i - 1] = index.doubleValue();
+	  }
+	  break;
+	case BOOL:
+	  boolean boo = rs.getBoolean(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (boo ? 1.0 : 0.0);
+	  }
+	  break;
+	case DOUBLE:
+	  //	  BigDecimal bd = rs.getBigDecimal(i, 4); 
+	  double dd = rs.getDouble(i);
+	  // Use the column precision instead of 4?
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    //	    newInst.setValue(i - 1, bd.doubleValue());
+	    vals[i - 1] =  dd;
+	  }
+	  break;
+	case BYTE:
+	  byte by = rs.getByte(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)by;
+	  }
+	  break;
+	case SHORT:
+	  short sh = rs.getShort(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)sh;
+	  }
+	  break;
+	case INTEGER:
+	  int in = rs.getInt(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)in;
+	  }
+	  break;
+	case LONG:
+	  long lo = rs.getLong(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)lo;
+	  }
+	  break;
+	case FLOAT:
+	  float fl = rs.getFloat(i);
+	  if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+	    vals[i - 1] = (double)fl;
+	  }
+	  break;
+	case DATE:
+          Date date = rs.getDate(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double)date.getTime();
+          }
+          break;
+	case TIME:
+          Time time = rs.getTime(i);
+          if (rs.wasNull()) {
+	    vals[i - 1] = Utils.missingValue();
+	  } else {
+            // TODO: Do a value check here.
+            vals[i - 1] = (double) time.getTime();
+          }
+          break;
+	default:
+	  vals[i - 1] = Utils.missingValue();
+	}
+      }
+      Instance newInst;
+      if (m_CreateSparseData) {
+	newInst = new SparseInstance(1.0, vals);
+      } else {
+	newInst = new DenseInstance(1.0, vals);
+      }
+      instances.addElement(newInst);
+      rowCount++;
+    }
+    //disconnectFromDatabase();  (perhaps other queries might be made)
+    
+    // Create the header and add the instances to the dataset
+    if (m_Debug) 
+      System.err.println("Creating header...");
+    FastVector attribInfo = new FastVector();
+    for (int i = 0; i < numAttributes; i++) {
+      /* Fix for databases that uppercase column names */
+      // String attribName = attributeCaseFix(md.getColumnName(i + 1));
+      String attribName = attributeCaseFix(columnNames.get(i));
+      switch (attributeTypes[i]) {
+      case Attribute.NOMINAL:
+	attribInfo.addElement(new Attribute(attribName, nominalStrings[i]));
+	break;
+      case Attribute.NUMERIC:
+	attribInfo.addElement(new Attribute(attribName));
+	break;
+      case Attribute.STRING:
+	Attribute att = new Attribute(attribName, (FastVector) null);
+	attribInfo.addElement(att);
+	for (int n = 0; n < nominalStrings[i].size(); n++) {
+	  att.addStringValue((String) nominalStrings[i].elementAt(n));
+	}
+	break;
+      case Attribute.DATE:
+	attribInfo.addElement(new Attribute(attribName, (String)null));
+	break;
+      default:
+	throw new Exception("Unknown attribute type");
+      }
+    }
+    Instances result = new Instances("QueryResult", attribInfo, 
+				     instances.size());
+    for (int i = 0; i < instances.size(); i++) {
+      result.add((Instance)instances.elementAt(i));
+    }
+    close(rs);
+   
+    return result;
+  }
+
+  /**
+   * Test the class from the command line. The instance
+   * query should be specified with -Q sql_query
+   *
+   * @param args contains options for the instance query
+   */
+  public static void main(String args[]) {
+
+    try {
+      InstanceQuery iq = new InstanceQuery();
+      String query = Utils.getOption('Q', args);
+      if (query.length() == 0) {
+	iq.setQuery("select * from Experiment_index");
+      } else {
+	iq.setQuery(query);
+      }
+      iq.setOptions(args);
+      try {
+	Utils.checkForRemainingOptions(args);
+      } catch (Exception e) {
+	System.err.println("Options for weka.experiment.InstanceQuery:\n");
+	Enumeration en = iq.listOptions();
+	while (en.hasMoreElements()) {
+	  Option o = (Option)en.nextElement();
+	  System.err.println(o.synopsis()+"\n"+o.description());
+	}
+	System.exit(1);
+      }
+     
+      Instances aha = iq.retrieveInstances();
+      iq.disconnectFromDatabase();
+      // query returned no result -> exit
+      if (aha == null)
+        return;
+      // The dataset may be large, so to make things easier we'll
+      // output an instance at a time (rather than having to convert
+      // the entire dataset to one large string)
+      System.out.println(new Instances(aha, 0));
+      for (int i = 0; i < aha.numInstances(); i++) {
+	System.out.println(aha.instance(i));
+      }
+    } catch(Exception e) {
+      e.printStackTrace();
+      System.err.println(e.getMessage());
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/InstancesResultListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/InstancesResultListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/InstancesResultListener.java	(revision 29)
@@ -0,0 +1,266 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstancesResultListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.Hashtable;
+
+/**
+ <!-- globalinfo-start -->
+ * Outputs the received results in arff format to a Writer. All results must be received before the instances can be written out.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -O &lt;file name&gt;
+ *  The filename where output will be stored. Use - for stdout.
+ *  (default temp file)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class InstancesResultListener 
+  extends CSVResultListener {
+  
+  /** for serialization */
+  static final long serialVersionUID = -2203808461809311178L;
+
+  /** Stores the instances created so far, before assigning to a header */
+  protected transient FastVector m_Instances;
+  
+  /** Stores the attribute types for each column */
+  protected transient int [] m_AttributeTypes;
+
+  /** For lookup of indices given a string value for each nominal attribute */
+  protected transient Hashtable [] m_NominalIndexes;
+
+  /** Contains strings seen so far for each nominal attribute */ 
+  protected transient FastVector [] m_NominalStrings;
+ 
+  /** 
+   * Sets temporary file.
+   */
+  public InstancesResultListener() {
+
+    File resultsFile;
+    try {
+      resultsFile = File.createTempFile("weka_experiment", ".arff");
+      resultsFile.deleteOnExit();
+    } catch (Exception e) {
+      System.err.println("Cannot create temp file, writing to standard out.");
+      resultsFile = new File("-");
+    }
+    setOutputFile(resultsFile);
+    setOutputFileName("");
+  } 
+
+  /**
+   * Returns a string describing this result listener
+   * @return a description of the result listener suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Outputs the received results in arff format to "
+      + "a Writer. All results must be received before the instances can be "
+      + "written out.";
+  }
+
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @exception Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    m_RP = rp;
+    if ((m_OutputFile == null) || (m_OutputFile.getName().equals("-"))) {
+      m_Out = new PrintWriter(System.out, true);
+    } else {
+      m_Out = new PrintWriter(
+	      new BufferedOutputStream(
+	      new FileOutputStream(m_OutputFile)), true);
+    }
+
+    Object [] keyTypes = m_RP.getKeyTypes();
+    Object [] resultTypes = m_RP.getResultTypes();
+
+    m_AttributeTypes = new int [keyTypes.length + resultTypes.length];
+    m_NominalIndexes = new Hashtable [m_AttributeTypes.length];
+    m_NominalStrings = new FastVector [m_AttributeTypes.length];
+    m_Instances = new FastVector();
+
+    for (int i = 0; i < m_AttributeTypes.length; i++) {
+      Object attribute = null;
+      if (i < keyTypes.length) {
+	attribute = keyTypes[i];
+      } else {
+	attribute = resultTypes[i - keyTypes.length];
+      }
+      if (attribute instanceof String) {
+	m_AttributeTypes[i] = Attribute.NOMINAL;
+	m_NominalIndexes[i] = new Hashtable();
+	m_NominalStrings[i] = new FastVector();
+      } else if (attribute instanceof Double) {
+	m_AttributeTypes[i] = Attribute.NUMERIC;
+      } else {
+	throw new Exception("Unknown attribute type in column " + (i + 1));
+      }
+    }
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more results will be sent that need to be grouped together
+   * in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @exception Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+
+    if (m_RP != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    String [] keyNames = m_RP.getKeyNames();
+    String [] resultNames = m_RP.getResultNames();
+    FastVector attribInfo = new FastVector();
+    for (int i = 0; i < m_AttributeTypes.length; i++) {
+      String attribName = "Unknown";
+      if (i < keyNames.length) {
+	attribName = "Key_" + keyNames[i];
+      } else {
+	attribName = resultNames[i - keyNames.length];
+      }
+      
+      switch (m_AttributeTypes[i]) {
+      case Attribute.NOMINAL:
+	if (m_NominalStrings[i].size() > 0) {
+	  attribInfo.addElement(new Attribute(attribName,
+					      m_NominalStrings[i]));
+	} else {
+	  attribInfo.addElement(new Attribute(attribName, (FastVector)null));
+	}
+	break;
+      case Attribute.NUMERIC:
+	attribInfo.addElement(new Attribute(attribName));
+	break;
+      case Attribute.STRING:
+	attribInfo.addElement(new Attribute(attribName, (FastVector)null));
+	break;
+      default:
+	throw new Exception("Unknown attribute type");
+      }
+    }
+
+    Instances result = new Instances("InstanceResultListener", attribInfo, 
+				     m_Instances.size());
+    for (int i = 0; i < m_Instances.size(); i++) {
+      result.add((Instance)m_Instances.elementAt(i));
+    }
+
+    m_Out.println(new Instances(result, 0));
+    for (int i = 0; i < result.numInstances(); i++) {
+      m_Out.println(result.instance(i));
+    }
+    
+    if (!(m_OutputFile == null) && !(m_OutputFile.getName().equals("-"))) {
+      m_Out.close();
+    }
+  }
+
+  /**
+   * Collects each instance and adjusts the header information.
+   *
+   * @param rp the ResultProducer that generated the result
+   * @param key The key for the results.
+   * @param result The actual results.
+   * @exception Exception if the result could not be accepted.
+   */
+  public void acceptResult(ResultProducer rp, Object[] key, Object[] result) 
+    throws Exception {
+
+    if (m_RP != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    
+    Instance newInst = new DenseInstance(m_AttributeTypes.length);
+    for(int i = 0; i < m_AttributeTypes.length; i++) {
+      Object val = null;
+      if (i < key.length) {
+	val = key[i];
+      } else {
+	val = result[i - key.length];
+      }
+      if (val == null) {
+	newInst.setValue(i, Utils.missingValue());
+      } else {
+	switch (m_AttributeTypes[i]) {
+	case Attribute.NOMINAL:
+	  String str = (String) val;
+	  Double index = (Double)m_NominalIndexes[i].get(str);
+	  if (index == null) {
+	    index = new Double(m_NominalStrings[i].size());
+	    m_NominalIndexes[i].put(str, index);
+	    m_NominalStrings[i].addElement(str);
+	  }
+	  newInst.setValue(i, index.doubleValue());
+	  break;
+	case Attribute.NUMERIC:
+	  double dou = ((Double) val).doubleValue();
+	  newInst.setValue(i, (double)dou);
+	  break;
+	default:
+	  newInst.setValue(i, Utils.missingValue());
+	}
+      }
+    }
+    m_Instances.addElement(newInst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+} // InstancesResultListener
Index: branches/MetisMQI/src/main/java/weka/experiment/LearningRateResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/LearningRateResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/LearningRateResultProducer.java	(revision 29)
@@ -0,0 +1,921 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LearningRateResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Tells a sub-ResultProducer to reproduce the current run for varying sized subsamples of the dataset. Normally used with an AveragingResultProducer and CrossValidationResultProducer combo to generate learning curve results. For non-numeric result fields, the first value is used.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -X &lt;num steps&gt;
+ *  The number of steps in the learning rate curve.
+ *  (default 10)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a ResultProducer.
+ *  eg: weka.experiment.CrossValidationResultProducer</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.AveragingResultProducer:
+ * </pre>
+ * 
+ * <pre> -F &lt;field name&gt;
+ *  The name of the field to average over.
+ *  (default "Fold")</pre>
+ * 
+ * <pre> -X &lt;num results&gt;
+ *  The number of results expected per average.
+ *  (default 10)</pre>
+ * 
+ * <pre> -S
+ *  Calculate standard deviations.
+ *  (default only averages)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a ResultProducer.
+ *  eg: weka.experiment.CrossValidationResultProducer</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+ * </pre>
+ * 
+ * <pre> -X &lt;number of folds&gt;
+ *  The number of folds to use for the cross-validation.
+ *  (default 10)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * All options after -- will be passed to the result producer.
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5597 $
+ */
+public class LearningRateResultProducer 
+  implements ResultListener, ResultProducer, OptionHandler,
+	     AdditionalMeasureProducer, RevisionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3841159673490861331L;
+  
+  /** The dataset of interest */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The ResultProducer used to generate results */
+  protected ResultProducer m_ResultProducer
+    = new AveragingResultProducer();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** 
+   * The minimum number of instances to use. If this is zero, the first
+   * step will contain m_StepSize instances 
+   */
+  protected int m_LowerSize = 0;
+  
+  /**
+   * The maximum number of instances to use. -1 indicates no maximum 
+   * (other than the total number of instances)
+   */
+  protected int m_UpperSize = -1;
+
+  /** The number of instances to add at each step */
+  protected int m_StepSize = 10;
+
+  /** The current dataset size during stepping */
+  protected int m_CurrentSize = 0;
+
+  /** The name of the key field containing the learning rate step number */
+  public static String STEP_FIELD_NAME = "Total_instances";
+
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Tells a sub-ResultProducer to reproduce the current run for "
+      +"varying sized subsamples of the dataset. Normally used with "
+      +"an AveragingResultProducer and CrossValidationResultProducer "
+      +"combo to generate learning curve results. For non-numeric "
+      +"result fields, the first value is used.";
+  }
+
+
+  /**
+   * Determines if there are any constraints (imposed by the
+   * destination) on the result columns to be produced by
+   * resultProducers. Null should be returned if there are NO
+   * constraints, otherwise a list of column names should be
+   * returned as an array of Strings.
+   * @param rp the ResultProducer to which the constraints will apply
+   * @return an array of column names to which resutltProducer's
+   * results will be restricted.
+   * @throws Exception if constraints can't be determined
+   */
+  public String [] determineColumnConstraints(ResultProducer rp) 
+    throws Exception {
+    return null;
+  }
+
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+    m_ResultProducer.setInstances(m_Instances);
+
+    // For each subsample size
+    if (m_LowerSize == 0) {
+      m_CurrentSize = m_StepSize;
+    } else {
+      m_CurrentSize = m_LowerSize;
+    }
+    while (m_CurrentSize <= m_Instances.numInstances() &&
+           ((m_UpperSize == -1) ||
+            (m_CurrentSize <= m_UpperSize))) {
+      m_ResultProducer.doRunKeys(run);
+      m_CurrentSize += m_StepSize;
+    }
+  }
+
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+
+    // Randomize on a copy of the original dataset
+    Instances runInstances = new Instances(m_Instances);
+    runInstances.randomize(new Random(run));
+    
+    /*if (runInstances.classAttribute().isNominal() && (m_Instances.numInstances() / m_StepSize >= 1)) {
+//      runInstances.stratify(m_Instances.numInstances() / m_StepSize);
+    }*/
+
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+
+    // For each subsample size
+    if (m_LowerSize == 0) {
+      m_CurrentSize = m_StepSize;
+    } else {
+      m_CurrentSize = m_LowerSize;
+    }
+    while (m_CurrentSize <= m_Instances.numInstances() &&
+           ((m_UpperSize == -1) ||
+            (m_CurrentSize <= m_UpperSize))) {
+      m_ResultProducer.setInstances(new Instances(runInstances, 0, 
+                                                  m_CurrentSize));
+      m_ResultProducer.doRun(run);
+      m_CurrentSize += m_StepSize;
+    }
+  }
+
+  
+  
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess(ResultProducer rp) throws Exception {
+
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    m_ResultListener.preProcess(this);
+  }
+
+  /**
+   * Prepare to generate results. The ResultProducer should call
+   * preProcess(this) on the ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+    
+    if (m_ResultProducer == null) {
+      throw new Exception("No ResultProducer set");
+    }
+    // Tell the resultproducer to send results to us
+    m_ResultProducer.setResultListener(this);
+    m_ResultProducer.preProcess();
+  }
+  
+  /**
+   * When this method is called, it indicates that no more results
+   * will be sent that need to be grouped together in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @throws Exception if an error occurs
+   */
+  public void postProcess(ResultProducer rp) throws Exception {
+
+    m_ResultListener.postProcess(this);
+  }
+
+  /**
+   * When this method is called, it indicates that no more requests to
+   * generate results for the current experiment will be sent. The
+   * ResultProducer should call preProcess(this) on the
+   * ResultListener it is to send results to.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultProducer.postProcess();
+  }
+  
+  /**
+   * Accepts results from a ResultProducer.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @param result the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if the result could not be accepted.
+   */
+  public void acceptResult(ResultProducer rp, Object [] key, Object [] result)
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    // Add in current step as key field
+    Object [] newKey = new Object [key.length + 1];
+    System.arraycopy(key, 0, newKey, 0, key.length);
+    newKey[key.length] = new String("" + m_CurrentSize);
+    // Pass on to result listener
+    m_ResultListener.acceptResult(this, newKey, result);
+  }
+
+  /**
+   * Determines whether the results for a specified key must be
+   * generated.
+   *
+   * @param rp the ResultProducer wanting to generate the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @return true if the result should be generated
+   * @throws Exception if it could not be determined if the result 
+   * is needed.
+   */
+  public boolean isResultRequired(ResultProducer rp, Object [] key) 
+    throws Exception {
+
+    if (m_ResultProducer != rp) {
+      throw new Error("Unrecognized ResultProducer sending results!!");
+    }
+    // Add in current step as key field
+    Object [] newKey = new Object [key.length + 1];
+    System.arraycopy(key, 0, newKey, 0, key.length);
+    newKey[key.length] = new String("" + m_CurrentSize);
+    // Pass on request to result listener
+    return m_ResultListener.isResultRequired(this, newKey);
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if key names cannot be generated
+   */
+  public String [] getKeyNames() throws Exception {
+
+    String [] keyNames = m_ResultProducer.getKeyNames();
+    String [] newKeyNames = new String [keyNames.length + 1];
+    System.arraycopy(keyNames, 0, newKeyNames, 0, keyNames.length);
+    // Think of a better name for this key field
+    newKeyNames[keyNames.length] = STEP_FIELD_NAME;
+    return newKeyNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if the key types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public Object [] getKeyTypes() throws Exception {
+
+    Object [] keyTypes = m_ResultProducer.getKeyTypes();
+    Object [] newKeyTypes = new Object [keyTypes.length + 1];
+    System.arraycopy(keyTypes, 0, newKeyTypes, 0, keyTypes.length);
+    newKeyTypes[keyTypes.length] = "";
+    return newKeyTypes;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * A new result field is added for the number of results used to
+   * produce each average.
+   * If only averages are being produced the names are not altered, if
+   * standard deviations are produced then "Dev_" and "Avg_" are prepended
+   * to each result deviation and average field respectively.
+   *
+   * @return an array containing the name of each column
+   * @throws Exception if the result names could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public String [] getResultNames() throws Exception {
+
+    return m_ResultProducer.getResultNames();
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   * @throws Exception if the result types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  public Object [] getResultTypes() throws Exception {
+
+    return m_ResultProducer.getResultTypes();
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  public String getCompatibilityState() {
+
+    String result = " ";
+    // + "-F " + Utils.quote(getKeyFieldName())
+    // + " -X " + getStepSize() + " ";
+    if (m_ResultProducer == null) {
+      result += "<null ResultProducer>";
+    } else {
+      result += "-W " + m_ResultProducer.getClass().getName();
+    }
+    result  += " -- " + m_ResultProducer.getCompatibilityState();
+    return result.trim();
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	     "\tThe number of steps in the learning rate curve.\n"
+	      +"\t(default 10)", 
+	     "X", 1, 
+	     "-X <num steps>"));
+    newVector.addElement(new Option(
+	     "\tThe full class name of a ResultProducer.\n"
+	      +"\teg: weka.experiment.CrossValidationResultProducer", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    if ((m_ResultProducer != null) &&
+	(m_ResultProducer instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to result producer "
+	     + m_ResultProducer.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_ResultProducer).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -X &lt;num steps&gt;
+   *  The number of steps in the learning rate curve.
+   *  (default 10)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a ResultProducer.
+   *  eg: weka.experiment.CrossValidationResultProducer</pre>
+   * 
+   * <pre> 
+   * Options specific to result producer weka.experiment.AveragingResultProducer:
+   * </pre>
+   * 
+   * <pre> -F &lt;field name&gt;
+   *  The name of the field to average over.
+   *  (default "Fold")</pre>
+   * 
+   * <pre> -X &lt;num results&gt;
+   *  The number of results expected per average.
+   *  (default 10)</pre>
+   * 
+   * <pre> -S
+   *  Calculate standard deviations.
+   *  (default only averages)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a ResultProducer.
+   *  eg: weka.experiment.CrossValidationResultProducer</pre>
+   * 
+   * <pre> 
+   * Options specific to result producer weka.experiment.CrossValidationResultProducer:
+   * </pre>
+   * 
+   * <pre> -X &lt;number of folds&gt;
+   *  The number of folds to use for the cross-validation.
+   *  (default 10)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the result producer.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String stepSize = Utils.getOption('S', options);
+    if (stepSize.length() != 0) {
+      setStepSize(Integer.parseInt(stepSize));
+    } else {
+      setStepSize(10);
+    }
+
+    String lowerSize = Utils.getOption('L', options);
+    if (lowerSize.length() != 0) {
+      setLowerSize(Integer.parseInt(lowerSize));
+    } else {
+      setLowerSize(0);
+    }
+    
+    String upperSize = Utils.getOption('U', options);
+    if (upperSize.length() != 0) {
+      setUpperSize(Integer.parseInt(upperSize));
+    } else {
+      setUpperSize(-1);
+    }
+
+    String rpName = Utils.getOption('W', options);
+    if (rpName.length() == 0) {
+      throw new Exception("A ResultProducer must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // RP.
+    setResultProducer((ResultProducer)Utils.forName(
+		      ResultProducer.class,
+		      rpName,
+		      null));
+    if (getResultProducer() instanceof OptionHandler) {
+      ((OptionHandler) getResultProducer())
+	.setOptions(Utils.partitionOptions(options));
+    }
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] seOptions = new String [0];
+    if ((m_ResultProducer != null) && 
+	(m_ResultProducer instanceof OptionHandler)) {
+      seOptions = ((OptionHandler)m_ResultProducer).getOptions();
+    }
+    
+    String [] options = new String [seOptions.length + 9];
+    int current = 0;
+
+    options[current++] = "-S";
+    options[current++] = "" + getStepSize();
+    options[current++] = "-L";
+    options[current++] = "" + getLowerSize();
+    options[current++] = "-U";
+    options[current++] = "" + getUpperSize();
+    if (getResultProducer() != null) {
+      options[current++] = "-W";
+      options[current++] = getResultProducer().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(seOptions, 0, options, current, 
+		     seOptions.length);
+    current += seOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current resultProducer) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_ResultProducer != null) {
+      System.err.println("LearningRateResultProducer: setting additional "
+			 +"measures for "
+			 +"ResultProducer");
+      m_ResultProducer.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+
+  /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the result producer
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_ResultProducer).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_ResultProducer instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_ResultProducer).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("LearningRateResultProducer: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_ResultProducer.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    
+    m_Instances = instances;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lowerSizeTipText() {
+    return "Set the minmum number of instances in a dataset. Setting zero "
+      + "here will actually use <stepSize> number of instances at the first "
+      + "step (since it makes no sense to use zero instances :-))";
+  }
+
+  /**
+   * Get the value of LowerSize.
+   *
+   * @return Value of LowerSize.
+   */
+  public int getLowerSize() {
+    
+    return m_LowerSize;
+  }
+  
+  /**
+   * Set the value of LowerSize.
+   *
+   * @param newLowerSize Value to assign to
+   * LowerSize.
+   */
+  public void setLowerSize(int newLowerSize) {
+    
+    m_LowerSize = newLowerSize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String upperSizeTipText() {
+    return "Set the maximum number of instances in a dataset. Setting -1 "
+      + "sets no upper limit (other than the total number of instances "
+      + "in the full dataset)";
+  }
+
+  /**
+   * Get the value of UpperSize.
+   *
+   * @return Value of UpperSize.
+   */
+  public int getUpperSize() {
+    
+    return m_UpperSize;
+  }
+  
+  /**
+   * Set the value of UpperSize.
+   *
+   * @param newUpperSize Value to assign to
+   * UpperSize.
+   */
+  public void setUpperSize(int newUpperSize) {
+    
+    m_UpperSize = newUpperSize;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String stepSizeTipText() {
+    return "Set the number of instances to add at each step.";
+  }
+
+  /**
+   * Get the value of StepSize.
+   *
+   * @return Value of StepSize.
+   */
+  public int getStepSize() {
+    
+    return m_StepSize;
+  }
+  
+  /**
+   * Set the value of StepSize.
+   *
+   * @param newStepSize Value to assign to
+   * StepSize.
+   */
+  public void setStepSize(int newStepSize) {
+    
+    m_StepSize = newStepSize;
+  }
+
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+
+    m_ResultListener = listener;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String resultProducerTipText() {
+    return "Set the resultProducer for which learning rate results should be "
+      + "generated.";
+  }
+
+  /**
+   * Get the ResultProducer.
+   *
+   * @return the ResultProducer.
+   */
+  public ResultProducer getResultProducer() {
+    
+    return m_ResultProducer;
+  }
+  
+  /**
+   * Set the ResultProducer.
+   *
+   * @param newResultProducer new ResultProducer to use.
+   */
+  public void setResultProducer(ResultProducer newResultProducer) {
+
+    m_ResultProducer = newResultProducer;
+    m_ResultProducer.setResultListener(this);
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "LearningRateResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5597 $");
+  }
+} // LearningRateResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/OutputZipper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/OutputZipper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/OutputZipper.java	(revision 29)
@@ -0,0 +1,140 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OutputZipper.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * OutputZipper writes output to either gzipped files or to a
+ * multi entry zip file. If the destination file is a directory
+ * each output string will be written to an individually named
+ * gzip file. If the destination file is a file, then each
+ * output string is appended as a named entry to the zip file until
+ * finished() is called to close the file.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class OutputZipper
+  implements RevisionHandler {
+  
+  File m_destination;
+  DataOutputStream m_zipOut = null;
+  ZipOutputStream m_zs = null;
+
+  /**
+   * Constructor.
+   * 
+   * @param destination a destination file or directory
+   * @throws Exception if something goes wrong.
+   */
+  public OutputZipper(File destination) throws Exception { 
+
+    m_destination = destination;
+
+    // if a directory is specified then use gzip format, otherwise
+    // use zip
+    if (!m_destination.isDirectory()) {
+      m_zs = new ZipOutputStream(new FileOutputStream(m_destination));
+      m_zipOut = new DataOutputStream(m_zs);
+    }    
+  }
+
+  /**
+   * Saves a string to either an individual gzipped file or as
+   * an entry in a zip file.
+   * 
+   * @param outString the output string to save
+   * @param name the name of the file/entry to save it to
+   * @throws Exception if something goes wrong
+   */
+  public void zipit(String outString, String name) throws Exception {
+    File saveFile;
+    ZipEntry ze;
+    
+    if (m_zipOut == null) {
+      saveFile = new File(m_destination, name+".gz");
+      DataOutputStream dout = 
+	new DataOutputStream(new GZIPOutputStream(
+			     new FileOutputStream(saveFile)));
+      
+      dout.writeBytes(outString);
+      dout.close();
+    } else {
+      ze = new ZipEntry(name);
+      m_zs.putNextEntry(ze);
+      m_zipOut.writeBytes(outString);
+      m_zs.closeEntry();
+    }
+  }
+
+  /**
+   * Closes the zip file.
+   * 
+   * @throws Exception if something goes wrong
+   */
+  public void finished() throws Exception {
+    if (m_zipOut != null) {
+      m_zipOut.close();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.8 $");
+  }
+
+  /**
+   * Main method for testing this class
+   */
+  public static void main(String [] args) {
+    
+    try {
+      File testF = new File(new File(System.getProperty("user.dir")), 
+			    "testOut.zip");
+      OutputZipper oz = new OutputZipper(testF);
+      
+      /*      OutputZipper oz = new OutputZipper(
+	      new File(System.getProperty("user.dir"))); */
+      oz.zipit("Here is some test text to be zipped","testzip");
+      oz.zipit("Here is a second entry to be zipped","testzip2");
+      oz.finished();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/PairedCorrectedTTester.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/PairedCorrectedTTester.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/PairedCorrectedTTester.java	(revision 29)
@@ -0,0 +1,341 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PairedCorrectedTTester.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformation.Type;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformationHandler;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Enumeration;
+
+/**
+ * Behaves the same as PairedTTester, only it uses the corrected
+ * resampled t-test statistic.<p/>
+ *
+ * For more information see:<p/>
+ *
+ <!-- technical-plaintext-start -->
+ * Claude Nadeau, Yoshua Bengio (2001). Inference for the Generalization Error. Machine Learning..
+ <!-- technical-plaintext-end -->
+ *
+ * <p/>
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Nadeau2001,
+ *    author = {Claude Nadeau and Yoshua Bengio},
+ *    journal = {Machine Learning},
+ *    title = {Inference for the Generalization Error},
+ *    year = {2001},
+ *    PDF = {http://www.iro.umontreal.ca/\~lisa/bib/pub_subject/comparative/pointeurs/nadeau_MLJ1597.pdf}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D &lt;index,index2-index4,...&gt;
+ *  Specify list of columns that specify a unique
+ *  dataset.
+ *  First and last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -R &lt;index&gt;
+ *  Set the index of the column containing the run number</pre>
+ * 
+ * <pre> -F &lt;index&gt;
+ *  Set the index of the column containing the fold number</pre>
+ * 
+ * <pre> -G &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns that specify a unique
+ *  'result generator' (eg: classifier name and options).
+ *  First and last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -S &lt;significance level&gt;
+ *  Set the significance level for comparisons (default 0.05)</pre>
+ * 
+ * <pre> -V
+ *  Show standard deviations</pre>
+ * 
+ * <pre> -L
+ *  Produce table comparisons in Latex table format</pre>
+ * 
+ * <pre> -csv
+ *  Produce table comparisons in CSV table format</pre>
+ * 
+ * <pre> -html
+ *  Produce table comparisons in HTML table format</pre>
+ * 
+ * <pre> -significance
+ *  Produce table comparisons with only the significance values</pre>
+ * 
+ * <pre> -gnuplot
+ *  Produce table comparisons output suitable for GNUPlot</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.13 $
+ */
+public class PairedCorrectedTTester 
+  extends PairedTTester
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3105268939845653323L;
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Claude Nadeau and Yoshua Bengio");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.TITLE, "Inference for the Generalization Error");
+    result.setValue(Field.JOURNAL, "Machine Learning");
+    result.setValue(Field.PDF, "http://www.iro.umontreal.ca/~lisa/bib/pub_subject/comparative/pointeurs/nadeau_MLJ1597.pdf");
+
+    return result;
+  }
+
+  /**
+   * Computes a paired t-test comparison for a specified dataset between
+   * two resultsets.
+   *
+   * @param datasetSpecifier the dataset specifier
+   * @param resultset1Index the index of the first resultset
+   * @param resultset2Index the index of the second resultset
+   * @param comparisonColumn the column containing values to compare
+   * @return the results of the paired comparison
+   * @throws Exception if an error occurs
+   */
+  public PairedStats calculateStatistics(Instance datasetSpecifier,
+					 int resultset1Index,
+					 int resultset2Index,
+					 int comparisonColumn) throws Exception {
+
+    if (m_Instances.attribute(comparisonColumn).type()
+	!= Attribute.NUMERIC) {
+      throw new Exception("Comparison column " + (comparisonColumn + 1)
+			  + " ("
+			  + m_Instances.attribute(comparisonColumn).name()
+			  + ") is not numeric");
+    }
+    if (!m_ResultsetsValid) {
+      prepareData();
+    }
+
+    Resultset resultset1 = (Resultset) m_Resultsets.elementAt(resultset1Index);
+    Resultset resultset2 = (Resultset) m_Resultsets.elementAt(resultset2Index);
+    FastVector dataset1 = resultset1.dataset(datasetSpecifier);
+    FastVector dataset2 = resultset2.dataset(datasetSpecifier);
+    String datasetName = templateString(datasetSpecifier);
+    if (dataset1 == null) {
+      throw new Exception("No results for dataset=" + datasetName
+			 + " for resultset=" + resultset1.templateString());
+    } else if (dataset2 == null) {
+      throw new Exception("No results for dataset=" + datasetName
+			 + " for resultset=" + resultset2.templateString());
+    } else if (dataset1.size() != dataset2.size()) {
+      throw new Exception("Results for dataset=" + datasetName
+			  + " differ in size for resultset="
+			  + resultset1.templateString()
+			  + " and resultset="
+			  + resultset2.templateString()
+			  );
+    }
+
+    // calculate the test/train ratio
+    double testTrainRatio = 0.0;
+    int trainSizeIndex = -1;
+    int testSizeIndex = -1;
+    // find the columns with the train/test sizes
+    for (int i=0; i<m_Instances.numAttributes(); i++) {
+      if (m_Instances.attribute(i).name().toLowerCase().equals("number_of_training_instances")) {
+	trainSizeIndex = i;
+      } else if (m_Instances.attribute(i).name().toLowerCase().equals("number_of_testing_instances")) {
+	testSizeIndex = i;
+      }
+    }
+    if (trainSizeIndex >= 0 && testSizeIndex >= 0) {
+      double totalTrainSize = 0.0;
+      double totalTestSize = 0.0;
+      for (int k = 0; k < dataset1.size(); k ++) {
+	Instance current = (Instance) dataset1.elementAt(k);
+	totalTrainSize += current.value(trainSizeIndex);
+	totalTestSize += current.value(testSizeIndex);
+      }
+      testTrainRatio = totalTestSize / totalTrainSize;
+    }
+    PairedStats pairedStats =
+      new PairedStatsCorrected(m_SignificanceLevel, testTrainRatio);
+
+    for (int k = 0; k < dataset1.size(); k ++) {
+      Instance current1 = (Instance) dataset1.elementAt(k);
+      Instance current2 = (Instance) dataset2.elementAt(k);
+      if (current1.isMissing(comparisonColumn)) {
+	System.err.println("Instance has missing value in comparison "
+			   + "column!\n" + current1);
+	continue;
+      }
+      if (current2.isMissing(comparisonColumn)) {
+	System.err.println("Instance has missing value in comparison "
+			   + "column!\n" + current2);
+	continue;
+      }
+      if (current1.value(m_RunColumn) != current2.value(m_RunColumn)) {
+	System.err.println("Run numbers do not match!\n"
+			    + current1 + current2);
+      }
+      if (m_FoldColumn != -1) {
+	if (current1.value(m_FoldColumn) != current2.value(m_FoldColumn)) {
+	  System.err.println("Fold numbers do not match!\n"
+			     + current1 + current2);
+	}
+      }
+
+      double value1 = current1.value(comparisonColumn);
+      double value2 = current2.value(comparisonColumn);
+      pairedStats.add(value1, value2);
+    }
+    pairedStats.calculateDerived();
+    return pairedStats;
+  }
+
+  /**
+   * Test the class from the command line.
+   *
+   * @param args contains options for the instance ttests
+   */
+  public static void main(String args[]) {
+    
+    try {
+      PairedCorrectedTTester tt = new PairedCorrectedTTester();
+      String datasetName = Utils.getOption('t', args);
+      String compareColStr = Utils.getOption('c', args);
+      String baseColStr = Utils.getOption('b', args);
+      boolean summaryOnly = Utils.getFlag('s', args);
+      boolean rankingOnly = Utils.getFlag('r', args);
+      try {
+	if ((datasetName.length() == 0)
+	    || (compareColStr.length() == 0)) {
+	  throw new Exception("-t and -c options are required");
+	}
+	tt.setOptions(args);
+	Utils.checkForRemainingOptions(args);
+      } catch (Exception ex) {
+	String result = "";
+	Enumeration enu = tt.listOptions();
+	while (enu.hasMoreElements()) {
+	  Option option = (Option) enu.nextElement();
+	  result += option.synopsis() + '\n'
+	    + option.description() + '\n';
+	}
+	throw new Exception(
+	      "Usage:\n\n"
+	      + "-t <file>\n"
+	      + "\tSet the dataset containing data to evaluate\n"
+	      + "-b <index>\n"
+	      + "\tSet the resultset to base comparisons against (optional)\n"
+	      + "-c <index>\n"
+	      + "\tSet the column to perform a comparison on\n"
+	      + "-s\n"
+	      + "\tSummarize wins over all resultset pairs\n\n"
+	      + "-r\n"
+	      + "\tGenerate a resultset ranking\n\n"
+	      + result);
+      }
+      Instances data = new Instances(new BufferedReader(
+				  new FileReader(datasetName)));
+      tt.setInstances(data);
+      //      tt.prepareData();
+      int compareCol = Integer.parseInt(compareColStr) - 1;
+      System.out.println(tt.header(compareCol));
+      if (rankingOnly) {
+	System.out.println(tt.multiResultsetRanking(compareCol));
+      } else if (summaryOnly) {
+	System.out.println(tt.multiResultsetSummary(compareCol));
+      } else {
+	System.out.println(tt.resultsetKey());
+	if (baseColStr.length() == 0) {
+	  for (int i = 0; i < tt.getNumResultsets(); i++) {
+	    System.out.println(tt.multiResultsetFull(i, compareCol));
+	  }
+	} else {
+	  int baseCol = Integer.parseInt(baseColStr) - 1;
+	  System.out.println(tt.multiResultsetFull(baseCol, compareCol));
+	}
+      }
+    } catch(Exception e) {
+      e.printStackTrace();
+      System.err.println(e.getMessage());
+    }
+  }
+
+  /**
+   * returns the name of the tester
+   * 
+   * @return the display name
+   */
+  public String getDisplayName() {
+    return "Paired T-Tester (corrected)";
+  }
+
+  /**
+   * returns a string that is displayed as tooltip on the "perform test"
+   * button in the experimenter
+   * 
+   * @return the string for the tool tip
+   */
+  public String getToolTipText() {
+    return "Performs test using corrected resampled t-test statistic (Nadeau and Bengio)";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.13 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/PairedStats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/PairedStats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/PairedStats.java	(revision 29)
@@ -0,0 +1,331 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PairedStats.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Statistics;
+import weka.core.Utils;
+
+/**
+ * A class for storing stats on a paired comparison (t-test and correlation)
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public class PairedStats
+  implements RevisionHandler {
+  
+  /** The stats associated with the data in column 1 */
+  public Stats xStats;
+  
+  /** The stats associated with the data in column 2 */
+  public Stats yStats;
+  
+  /** The stats associated with the paired differences */
+  public Stats differencesStats;
+
+  /** The probability of obtaining the observed differences */
+  public double differencesProbability;
+
+  /** The correlation coefficient */
+  public double correlation;
+
+  /** The sum of the products */
+  public double xySum;
+  
+  /** The number of data points seen */
+  public double count;
+  
+  /**
+   * A significance indicator:
+   * 0 if the differences are not significant
+   * > 0 if x significantly greater than y
+   * < 0 if x significantly less than y
+   */
+  public int differencesSignificance;
+  
+  /** The significance level for comparisons */
+  public double sigLevel;
+
+  /** The degrees of freedom (if set programmatically) */
+  protected int m_degreesOfFreedom = 0;
+    
+  /**
+   * Creates a new PairedStats object with the supplied significance level.
+   *
+   * @param sig the significance level for comparisons
+   */
+  public PairedStats(double sig) {
+      
+    xStats = new Stats();
+    yStats = new Stats();
+    differencesStats = new Stats();
+    sigLevel = sig;
+  }
+
+  /**
+   * Sets the degrees of freedom (if calibration is required).
+   */
+  public void setDegreesOfFreedom(int d) {
+   
+    if (d <= 0) {
+      throw new IllegalArgumentException("PairedStats: degrees of freedom must be >= 1");
+    }
+    m_degreesOfFreedom = d;
+  }
+
+  /**
+   * Gets the degrees of freedom.
+   */
+  public int getDegreesOfFreedom() {
+
+    return m_degreesOfFreedom;
+  }
+
+  /**
+   * Add an observed pair of values.
+   *
+   * @param value1 the value from column 1
+   * @param value2 the value from column 2
+   */
+  public void add(double value1, double value2) {
+
+    xStats.add(value1);
+    yStats.add(value2);
+    differencesStats.add(value1 - value2);
+    xySum += value1 * value2;
+    count ++;
+  }
+    
+  /**
+   * Removes an observed pair of values.
+   *
+   * @param value1 the value from column 1
+   * @param value2 the value from column 2
+   */
+  public void subtract(double value1, double value2) {
+
+    xStats.subtract(value1);
+    yStats.subtract(value2);
+    differencesStats.subtract(value1 - value2);
+    xySum -= value1 * value2;
+    count --;
+  }
+
+    
+  /**
+   * Adds an array of observed pair of values.
+   *
+   * @param value1 the array containing values from column 1
+   * @param value2 the array containing values from column 2
+   */
+  public void add(double value1[], double value2[]) {
+    if ((value1 == null) || (value2 == null)) {
+      throw new NullPointerException();
+    }
+    if (value1.length != value2.length) {
+      throw new IllegalArgumentException("Arrays must be of the same length");
+    }
+    for (int i = 0; i < value1.length; i++) {
+      add(value1[i], value2[i]);
+    }
+  }
+
+
+  /**
+   * Removes an array of observed pair of values.
+   *
+   * @param value1 the array containing values from column 1
+   * @param value2 the array containing values from column 2
+   */
+  public void subtract(double value1[], double value2[]) {
+    if ((value1 == null) || (value2 == null)) {
+      throw new NullPointerException();
+    }
+    if (value1.length != value2.length) {
+      throw new IllegalArgumentException("Arrays must be of the same length");
+    }
+    for (int i = 0; i < value1.length; i++) {
+      subtract(value1[i], value2[i]);
+    }
+  }  
+
+
+  /**
+   * Calculates the derived statistics (significance etc).
+   */
+  public void calculateDerived() {
+
+    xStats.calculateDerived();
+    yStats.calculateDerived();
+    differencesStats.calculateDerived();
+
+    correlation = Double.NaN;
+    if (!Double.isNaN(xStats.stdDev) && !Double.isNaN(yStats.stdDev)
+	&& !Utils.eq(xStats.stdDev, 0)) {
+      double slope = (xySum - xStats.sum * yStats.sum / count)
+	/ (xStats.sumSq - xStats.sum * xStats.mean);
+      if (!Utils.eq(yStats.stdDev, 0)) {
+	correlation = slope * xStats.stdDev / yStats.stdDev;
+      } else {
+	correlation = 1.0;
+      }
+    }
+
+    if (Utils.gr(differencesStats.stdDev, 0)) {
+      double tval = differencesStats.mean
+	* Math.sqrt(count)
+	/ differencesStats.stdDev;
+
+      if (m_degreesOfFreedom >= 1){
+        differencesProbability = Statistics.FProbability(tval * tval, 1,
+                                                         m_degreesOfFreedom);
+      } else {
+        if (count > 1) {
+          differencesProbability = Statistics.FProbability(tval * tval, 1,
+                                                           (int) count - 1);
+        } else {
+          differencesProbability = 1;
+        }
+      }
+    } else {
+      if (differencesStats.sumSq == 0) {
+	differencesProbability = 1.0;
+      } else {
+	differencesProbability = 0.0;
+      }
+    }
+    differencesSignificance = 0;
+    if (differencesProbability <= sigLevel) {
+      if (xStats.mean > yStats.mean) {
+	differencesSignificance = 1;
+      } else {
+	differencesSignificance = -1;
+      }
+    }
+  }
+    
+  /**
+   * Returns statistics on the paired comparison.
+   *
+   * @return the t-test statistics as a string
+   */
+  public String toString() {
+
+    return "Analysis for " + Utils.doubleToString(count, 0)
+      + " points:\n"
+      + "                "
+      + "         Column 1"
+      + "         Column 2"
+      + "       Difference\n"
+      + "Minimums        "
+      + Utils.doubleToString(xStats.min, 17, 4)
+      + Utils.doubleToString(yStats.min, 17, 4)
+      + Utils.doubleToString(differencesStats.min, 17, 4) + '\n'
+      + "Maximums        "
+      + Utils.doubleToString(xStats.max, 17, 4)
+      + Utils.doubleToString(yStats.max, 17, 4)
+      + Utils.doubleToString(differencesStats.max, 17, 4) + '\n'
+      + "Sums            "
+      + Utils.doubleToString(xStats.sum, 17, 4)
+      + Utils.doubleToString(yStats.sum, 17, 4)
+      + Utils.doubleToString(differencesStats.sum, 17, 4) + '\n'
+      + "SumSquares      "
+      + Utils.doubleToString(xStats.sumSq, 17, 4)
+      + Utils.doubleToString(yStats.sumSq, 17, 4)
+      + Utils.doubleToString(differencesStats.sumSq, 17, 4) + '\n'
+      + "Means           "
+      + Utils.doubleToString(xStats.mean, 17, 4)
+      + Utils.doubleToString(yStats.mean, 17, 4)
+      + Utils.doubleToString(differencesStats.mean, 17, 4) + '\n'
+      + "SDs             "
+      + Utils.doubleToString(xStats.stdDev, 17, 4)
+      + Utils.doubleToString(yStats.stdDev, 17, 4)
+      + Utils.doubleToString(differencesStats.stdDev, 17, 4) + '\n'
+      + "Prob(differences) "
+      + Utils.doubleToString(differencesProbability, 4)
+      + " (sigflag " + differencesSignificance + ")\n"
+      + "Correlation       "
+      + Utils.doubleToString(correlation,4) + "\n";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+
+  /**
+   * Tests the paired stats object from the command line.
+   * reads line from stdin, expecting two values per line.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    try {
+      PairedStats ps = new PairedStats(0.05);
+      java.io.LineNumberReader r = new java.io.LineNumberReader(
+				   new java.io.InputStreamReader(System.in));
+      String line;
+      while ((line = r.readLine()) != null) {
+        line = line.trim();
+        if (line.equals("") || line.startsWith("@") || line.startsWith("%")) {
+          continue;
+        }
+	java.util.StringTokenizer s 
+          = new java.util.StringTokenizer(line, " ,\t\n\r\f");
+	int count = 0;
+	double v1 = 0, v2 = 0;
+	while (s.hasMoreTokens()) {
+	  double val = (new Double(s.nextToken())).doubleValue();
+	  if (count == 0) {
+	    v1 = val;
+	  } else if (count == 1) {
+	    v2 = val;
+	  } else {
+            System.err.println("MSG: Too many values in line \"" 
+                               + line + "\", skipped.");
+	    break;
+	  }
+	  count++;
+	}
+        if (count == 2) {
+          ps.add(v1, v2);
+        }
+      }
+      ps.calculateDerived();
+      System.err.println(ps);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+} // PairedStats
+
+
Index: branches/MetisMQI/src/main/java/weka/experiment/PairedStatsCorrected.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/PairedStatsCorrected.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/PairedStatsCorrected.java	(revision 29)
@@ -0,0 +1,117 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PairedStatsCorrected.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Statistics;
+
+/**
+ * A class for storing stats on a paired comparison. This version is
+ * based on the corrected resampled t-test statistic, which uses the
+ * ratio of the number of test examples/the number of training examples.<p>
+ *
+ * For more information see:<p>
+ *
+ * Claude Nadeau and Yoshua Bengio, "Inference for the Generalization Error,"
+ * Machine Learning, 2001.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class PairedStatsCorrected
+  extends PairedStats {
+
+  /** The ratio used to correct the significane test */
+  protected double m_testTrainRatio;
+
+  /**
+   * Creates a new PairedStatsCorrected object with the supplied
+   * significance level and train/test ratio.
+   *
+   * @param sig the significance level for comparisons
+   * @param testTrainRatio the number test examples/training examples
+   */
+  public PairedStatsCorrected(double sig, double testTrainRatio) {
+      
+    super(sig);
+    m_testTrainRatio = testTrainRatio;
+  }
+
+  /**
+   * Calculates the derived statistics (significance etc).
+   */
+  public void calculateDerived() {
+
+    xStats.calculateDerived();
+    yStats.calculateDerived();
+    differencesStats.calculateDerived();
+
+    correlation = Double.NaN;
+    if (!Double.isNaN(xStats.stdDev) && !Double.isNaN(yStats.stdDev)
+	&& !Utils.eq(xStats.stdDev, 0)) {
+      double slope = (xySum - xStats.sum * yStats.sum / count)
+	/ (xStats.sumSq - xStats.sum * xStats.mean);
+      if (!Utils.eq(yStats.stdDev, 0)) {
+	correlation = slope * xStats.stdDev / yStats.stdDev;
+      } else {
+	correlation = 1.0;
+      }
+    }
+
+    if (Utils.gr(differencesStats.stdDev, 0)) {
+
+      double tval = differencesStats.mean
+	/ Math.sqrt((1 / count + m_testTrainRatio)
+		    * differencesStats.stdDev * differencesStats.stdDev);
+      
+      if (count > 1) {
+	differencesProbability = Statistics.FProbability(tval * tval, 1,
+							 (int) count - 1);
+      } else differencesProbability = 1;
+    } else {
+      if (differencesStats.sumSq == 0) {
+	differencesProbability = 1.0;
+      } else {
+	differencesProbability = 0.0;
+      }
+    }
+    differencesSignificance = 0;
+    if (differencesProbability <= sigLevel) {
+      if (xStats.mean > yStats.mean) {
+	differencesSignificance = 1;
+      } else {
+	differencesSignificance = -1;
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/PairedTTester.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/PairedTTester.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/PairedTTester.java	(revision 29)
@@ -0,0 +1,1532 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PairedTTester.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Calculates T-Test statistics on data stored in a set of instances. <p/>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D &lt;index,index2-index4,...&gt;
+ *  Specify list of columns that specify a unique
+ *  dataset.
+ *  First and last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -R &lt;index&gt;
+ *  Set the index of the column containing the run number</pre>
+ * 
+ * <pre> -F &lt;index&gt;
+ *  Set the index of the column containing the fold number</pre>
+ * 
+ * <pre> -G &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns that specify a unique
+ *  'result generator' (eg: classifier name and options).
+ *  First and last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -S &lt;significance level&gt;
+ *  Set the significance level for comparisons (default 0.05)</pre>
+ * 
+ * <pre> -V
+ *  Show standard deviations</pre>
+ * 
+ * <pre> -L
+ *  Produce table comparisons in Latex table format</pre>
+ * 
+ * <pre> -csv
+ *  Produce table comparisons in CSV table format</pre>
+ * 
+ * <pre> -html
+ *  Produce table comparisons in HTML table format</pre>
+ * 
+ * <pre> -significance
+ *  Produce table comparisons with only the significance values</pre>
+ * 
+ * <pre> -gnuplot
+ *  Produce table comparisons output suitable for GNUPlot</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5415 $
+ */
+public class PairedTTester 
+  implements OptionHandler, Tester, RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 8370014624008728610L;
+
+  /** The set of instances we will analyse */
+  protected Instances m_Instances;
+
+  /** The index of the column containing the run number */
+  protected int m_RunColumn = 0;
+
+  /** The option setting for the run number column (-1 means last) */
+  protected int m_RunColumnSet = -1;
+
+  /** The option setting for the fold number column (-1 means none) */
+  protected int m_FoldColumn = -1;
+
+  /** The column to sort on (-1 means default sorting) */
+  protected int m_SortColumn = -1;
+
+  /** The sorting of the datasets (according to the sort column) */
+  protected int[] m_SortOrder = null;
+
+  /** The sorting of the columns (test base is always first) */
+  protected int[] m_ColOrder = null;
+
+  /** The significance level for comparisons */
+  protected double m_SignificanceLevel = 0.05;
+
+  /**
+   * The range of columns that specify a unique "dataset"
+   * (eg: scheme plus configuration)
+   */
+  protected Range m_DatasetKeyColumnsRange = new Range();
+
+  /** An array containing the indexes of just the selected columns */ 
+  protected int [] m_DatasetKeyColumns;
+
+  /** The list of dataset specifiers */
+  protected DatasetSpecifiers m_DatasetSpecifiers = 
+    new DatasetSpecifiers();
+
+  /**
+   * The range of columns that specify a unique result set
+   * (eg: scheme plus configuration)
+   */
+  protected Range m_ResultsetKeyColumnsRange = new Range();
+
+  /** An array containing the indexes of just the selected columns */ 
+  protected int [] m_ResultsetKeyColumns;
+
+  /** An array containing the indexes of the datasets to display */
+  protected int[] m_DisplayedResultsets = null;
+
+  /** Stores a vector for each resultset holding all instances in each set */
+  protected FastVector m_Resultsets = new FastVector();
+
+  /** Indicates whether the instances have been partitioned */
+  protected boolean m_ResultsetsValid;
+
+  /** Indicates whether standard deviations should be displayed */
+  protected boolean m_ShowStdDevs = false;
+  
+  /** the instance of the class to produce the output. */
+  protected ResultMatrix m_ResultMatrix = new ResultMatrixPlainText();
+  
+  /** A list of unique "dataset" specifiers that have been observed */
+  protected class DatasetSpecifiers
+    implements RevisionHandler, Serializable {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -9020938059902723401L;
+    
+    /** the specifiers that have been observed */
+    FastVector m_Specifiers = new FastVector();
+
+    /**
+     * Removes all specifiers.
+     */
+    protected void removeAllSpecifiers() {
+
+      m_Specifiers.removeAllElements();
+    }
+
+    /** 
+     * Add an instance to the list of specifiers (if necessary)
+     * 
+     * @param inst	the instance to add
+     */
+    protected void add(Instance inst) {
+      
+      for (int i = 0; i < m_Specifiers.size(); i++) {
+	Instance specifier = (Instance)m_Specifiers.elementAt(i);
+	boolean found = true;
+	for (int j = 0; j < m_DatasetKeyColumns.length; j++) {
+	  if (inst.value(m_DatasetKeyColumns[j]) !=
+	      specifier.value(m_DatasetKeyColumns[j])) {
+	    found = false;
+	  }
+	}
+	if (found) {
+	  return;
+	}
+      }
+      m_Specifiers.addElement(inst);
+    }
+
+    /**
+     * Get the template at the given position.
+     * 
+     * @param i		the index
+     * @return		the template
+     */
+    protected Instance specifier(int i) {
+
+      return (Instance)m_Specifiers.elementAt(i);
+    }
+
+    /**
+     * Gets the number of specifiers.
+     * 
+     * @return		the current number of specifiers
+     */
+    protected int numSpecifiers() {
+
+      return m_Specifiers.size();
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5415 $");
+    }
+  }
+
+  /** Utility class to store the instances pertaining to a dataset */
+  protected class Dataset
+    implements RevisionHandler, Serializable {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -2801397601839433282L;
+
+    /** the template */
+    Instance m_Template;
+
+    /** the dataset */
+    FastVector m_Dataset;
+
+    /**
+     * Constructor
+     * 
+     * @param template	the template
+     */
+    public Dataset(Instance template) {
+
+      m_Template = template;
+      m_Dataset = new FastVector();
+      add(template);
+    }
+    
+    /**
+     * Returns true if the two instances match on those attributes that have
+     * been designated key columns (eg: scheme name and scheme options)
+     *
+     * @param first the first instance
+     * @return true if first and second match on the currently set key columns
+     */
+    protected boolean matchesTemplate(Instance first) {
+      
+      for (int i = 0; i < m_DatasetKeyColumns.length; i++) {
+	if (first.value(m_DatasetKeyColumns[i]) !=
+	    m_Template.value(m_DatasetKeyColumns[i])) {
+	  return false;
+	}
+      }
+      return true;
+    }
+
+    /**
+     * Adds the given instance to the dataset
+     * 
+     * @param inst	the instance to add
+     */
+    protected void add(Instance inst) {
+      
+      m_Dataset.addElement(inst);
+    }
+
+    /**
+     * Returns a vector containing the instances in the dataset
+     * 
+     * @return 		the current contents
+     */
+    protected FastVector contents() {
+
+      return m_Dataset;
+    }
+
+    /**
+     * Sorts the instances in the dataset by the run number.
+     *
+     * @param runColumn a value of type 'int'
+     */
+    public void sort(int runColumn) {
+
+      double [] runNums = new double [m_Dataset.size()];
+      for (int j = 0; j < runNums.length; j++) {
+	runNums[j] = ((Instance) m_Dataset.elementAt(j)).value(runColumn);
+      }
+      int [] index = Utils.stableSort(runNums);
+      FastVector newDataset = new FastVector(runNums.length);
+      for (int j = 0; j < index.length; j++) {
+	newDataset.addElement(m_Dataset.elementAt(index[j]));
+      }
+      m_Dataset = newDataset;
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5415 $");
+    }
+  }
+ 
+  /** Utility class to store the instances in a resultset */
+  protected class Resultset
+    implements RevisionHandler, Serializable {
+
+    /** for serialization. */
+    private static final long serialVersionUID = 1543786683821339978L;
+
+    /** the template */
+    Instance m_Template;
+    
+    /** the dataset */
+    FastVector m_Datasets;
+
+    /**
+     * Constructir
+     * 
+     * @param template		the template
+     */
+    public Resultset(Instance template) {
+
+      m_Template = template;
+      m_Datasets = new FastVector();
+      add(template);
+    }
+    
+    /**
+     * Returns true if the two instances match on those attributes that have
+     * been designated key columns (eg: scheme name and scheme options)
+     *
+     * @param first the first instance
+     * @return true if first and second match on the currently set key columns
+     */
+    protected boolean matchesTemplate(Instance first) {
+      
+      for (int i = 0; i < m_ResultsetKeyColumns.length; i++) {
+	if (first.value(m_ResultsetKeyColumns[i]) !=
+	    m_Template.value(m_ResultsetKeyColumns[i])) {
+	  return false;
+	}
+      }
+      return true;
+    }
+
+    /**
+     * Returns a string descriptive of the resultset key column values
+     * for this resultset
+     *
+     * @return a value of type 'String'
+     */
+    protected String templateString() {
+
+      String result = "";
+      String tempResult = "";
+      for (int i = 0; i < m_ResultsetKeyColumns.length; i++) {
+	tempResult = m_Template.toString(m_ResultsetKeyColumns[i]) + ' ';
+
+	// compact the string
+        tempResult = Utils.removeSubstring(tempResult, "weka.classifiers.");
+        tempResult = Utils.removeSubstring(tempResult, "weka.filters.");
+        tempResult = Utils.removeSubstring(tempResult, "weka.attributeSelection.");
+	result += tempResult;
+      }
+      return result.trim();
+    }
+    
+    /**
+     * Returns a vector containing all instances belonging to one dataset.
+     *
+     * @param inst a template instance
+     * @return a value of type 'FastVector'
+     */
+    public FastVector dataset(Instance inst) {
+
+      for (int i = 0; i < m_Datasets.size(); i++) {
+	if (((Dataset)m_Datasets.elementAt(i)).matchesTemplate(inst)) {
+	  return ((Dataset)m_Datasets.elementAt(i)).contents();
+	} 
+      }
+      return null;
+    }
+    
+    /**
+     * Adds an instance to this resultset
+     *
+     * @param newInst a value of type 'Instance'
+     */
+    public void add(Instance newInst) {
+      
+      for (int i = 0; i < m_Datasets.size(); i++) {
+	if (((Dataset)m_Datasets.elementAt(i)).matchesTemplate(newInst)) {
+	  ((Dataset)m_Datasets.elementAt(i)).add(newInst);
+	  return;
+	}
+      }
+      Dataset newDataset = new Dataset(newInst);
+      m_Datasets.addElement(newDataset);
+    }
+
+    /**
+     * Sorts the instances in each dataset by the run number.
+     *
+     * @param runColumn a value of type 'int'
+     */
+    public void sort(int runColumn) {
+
+      for (int i = 0; i < m_Datasets.size(); i++) {
+	((Dataset)m_Datasets.elementAt(i)).sort(runColumn);
+      }
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5415 $");
+    }
+  } // Resultset
+
+
+  /**
+   * Returns a string descriptive of the key column values for
+   * the "datasets
+   *
+   * @param template the template
+   * @return a value of type 'String'
+   */
+  protected String templateString(Instance template) {
+    
+    String result = "";
+    for (int i = 0; i < m_DatasetKeyColumns.length; i++) {
+      result += template.toString(m_DatasetKeyColumns[i]) + ' ';
+    }
+    if (result.startsWith("weka.classifiers.")) {
+      result = result.substring("weka.classifiers.".length());
+    }
+    return result.trim();
+  }
+
+  /**
+   * Sets the matrix to use to produce the output.
+   * @param matrix the instance to use to produce the output
+   * @see ResultMatrix
+   */
+  public void setResultMatrix(ResultMatrix matrix) {
+    m_ResultMatrix = matrix;
+  }
+
+  /**
+   * Gets the instance that produces the output.
+   * @return the instance to produce the output
+   */
+  public ResultMatrix getResultMatrix() {
+    return m_ResultMatrix;
+  }
+
+  /**
+   * Set whether standard deviations are displayed or not.
+   * @param s true if standard deviations are to be displayed
+   */
+  public void setShowStdDevs(boolean s) {
+    m_ShowStdDevs = s;
+  }
+
+  /**
+   * Returns true if standard deviations have been requested.
+   * @return true if standard deviations are to be displayed.
+   */
+  public boolean getShowStdDevs() {
+    return m_ShowStdDevs;
+  }
+  
+  /**
+   * Separates the instances into resultsets and by dataset/run.
+   *
+   * @throws Exception if the TTest parameters have not been set.
+   */
+  protected void prepareData() throws Exception {
+
+    if (m_Instances == null) {
+      throw new Exception("No instances have been set");
+    }
+    if (m_RunColumnSet == -1) {
+      m_RunColumn = m_Instances.numAttributes() - 1;
+    } else {
+      m_RunColumn = m_RunColumnSet;
+    }
+
+    if (m_ResultsetKeyColumnsRange == null) {
+      throw new Exception("No result specifier columns have been set");
+    }
+    m_ResultsetKeyColumnsRange.setUpper(m_Instances.numAttributes() - 1);
+    m_ResultsetKeyColumns = m_ResultsetKeyColumnsRange.getSelection();
+
+    if (m_DatasetKeyColumnsRange == null) {
+      throw new Exception("No dataset specifier columns have been set");
+    }
+    m_DatasetKeyColumnsRange.setUpper(m_Instances.numAttributes() - 1);
+    m_DatasetKeyColumns = m_DatasetKeyColumnsRange.getSelection();
+    
+    //  Split the data up into result sets
+    m_Resultsets.removeAllElements();  
+    m_DatasetSpecifiers.removeAllSpecifiers();
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      Instance current = m_Instances.instance(i);
+      if (current.isMissing(m_RunColumn)) {
+	throw new Exception("Instance has missing value in run "
+			    + "column!\n" + current);
+      } 
+      for (int j = 0; j < m_ResultsetKeyColumns.length; j++) {
+	if (current.isMissing(m_ResultsetKeyColumns[j])) {
+	  throw new Exception("Instance has missing value in resultset key "
+			      + "column " + (m_ResultsetKeyColumns[j] + 1)
+			      + "!\n" + current);
+	}
+      }
+      for (int j = 0; j < m_DatasetKeyColumns.length; j++) {
+	if (current.isMissing(m_DatasetKeyColumns[j])) {
+	  throw new Exception("Instance has missing value in dataset key "
+			      + "column " + (m_DatasetKeyColumns[j] + 1)
+			      + "!\n" + current);
+	}
+      }
+      boolean found = false;
+      for (int j = 0; j < m_Resultsets.size(); j++) {
+	Resultset resultset = (Resultset) m_Resultsets.elementAt(j);
+	if (resultset.matchesTemplate(current)) {
+	  resultset.add(current);
+	  found = true;
+	  break;
+	}
+      }
+      if (!found) {
+	Resultset resultset = new Resultset(current);
+	m_Resultsets.addElement(resultset);
+      }
+
+      m_DatasetSpecifiers.add(current);
+    }
+
+    // Tell each resultset to sort on the run column
+    for (int j = 0; j < m_Resultsets.size(); j++) {
+      Resultset resultset = (Resultset) m_Resultsets.elementAt(j);
+      if (m_FoldColumn >= 0) {
+        // sort on folds first in case they are out of order
+        resultset.sort(m_FoldColumn);
+      }
+      resultset.sort(m_RunColumn);
+    }
+
+    m_ResultsetsValid = true;
+  }
+
+  /**
+   * Gets the number of datasets in the resultsets
+   *
+   * @return the number of datasets in the resultsets
+   */
+  public int getNumDatasets() {
+
+    if (!m_ResultsetsValid) {
+      try {
+	prepareData();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	return 0;
+      }
+    }
+    return m_DatasetSpecifiers.numSpecifiers();
+  }
+
+  /**
+   * Gets the number of resultsets in the data.
+   *
+   * @return the number of resultsets in the data
+   */
+  public int getNumResultsets() {
+
+    if (!m_ResultsetsValid) {
+      try {
+  prepareData();
+      } catch (Exception ex) {
+  ex.printStackTrace();
+  return 0;
+      }
+    }
+    return m_Resultsets.size();
+  }
+
+  /**
+   * Gets a string descriptive of the specified resultset.
+   *
+   * @param index the index of the resultset
+   * @return a descriptive string for the resultset
+   */
+  public String getResultsetName(int index) {
+
+    if (!m_ResultsetsValid) {
+      try {
+	prepareData();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	return null;
+      }
+    }
+    return ((Resultset) m_Resultsets.elementAt(index)).templateString();
+  }
+  
+  /**
+   * Checks whether the resultset with the given index shall be displayed.
+   * 
+   * @param index the index of the resultset to check whether it shall be displayed 
+   * @return whether the specified resultset is displayed 
+   */
+  public boolean displayResultset(int index) {
+    boolean       result;
+    int           i;
+    
+    result = true;
+
+    if (m_DisplayedResultsets != null) {
+      result = false;
+      for (i = 0; i < m_DisplayedResultsets.length; i++) {
+        if (m_DisplayedResultsets[i] == index) {
+          result = true;
+          break;
+        }
+      }
+    }
+      
+    return result;
+  }
+  
+  /**
+   * Computes a paired t-test comparison for a specified dataset between
+   * two resultsets.
+   *
+   * @param datasetSpecifier the dataset specifier
+   * @param resultset1Index the index of the first resultset
+   * @param resultset2Index the index of the second resultset
+   * @param comparisonColumn the column containing values to compare
+   * @return the results of the paired comparison
+   * @throws Exception if an error occurs
+   */
+  public PairedStats calculateStatistics(Instance datasetSpecifier,
+					 int resultset1Index,
+					 int resultset2Index,
+					 int comparisonColumn) throws Exception {
+
+    if (m_Instances.attribute(comparisonColumn).type()
+	!= Attribute.NUMERIC) {
+      throw new Exception("Comparison column " + (comparisonColumn + 1)
+			  + " ("
+			  + m_Instances.attribute(comparisonColumn).name()
+			  + ") is not numeric");
+    }
+    if (!m_ResultsetsValid) {
+      prepareData();
+    }
+
+    Resultset resultset1 = (Resultset) m_Resultsets.elementAt(resultset1Index);
+    Resultset resultset2 = (Resultset) m_Resultsets.elementAt(resultset2Index);
+    FastVector dataset1 = resultset1.dataset(datasetSpecifier);
+    FastVector dataset2 = resultset2.dataset(datasetSpecifier);
+    String datasetName = templateString(datasetSpecifier);
+    if (dataset1 == null) {
+      throw new Exception("No results for dataset=" + datasetName
+			 + " for resultset=" + resultset1.templateString());
+    } else if (dataset2 == null) {
+      throw new Exception("No results for dataset=" + datasetName
+			 + " for resultset=" + resultset2.templateString());
+    } else if (dataset1.size() != dataset2.size()) {
+      throw new Exception("Results for dataset=" + datasetName
+			  + " differ in size for resultset="
+			  + resultset1.templateString()
+			  + " and resultset="
+			  + resultset2.templateString()
+			  );
+    }
+    
+    PairedStats pairedStats = new PairedStats(m_SignificanceLevel);
+
+    for (int k = 0; k < dataset1.size(); k ++) {
+      Instance current1 = (Instance) dataset1.elementAt(k);
+      Instance current2 = (Instance) dataset2.elementAt(k);
+      if (current1.isMissing(comparisonColumn)) {
+	System.err.println("Instance has missing value in comparison "
+			   + "column!\n" + current1);
+	continue;
+      }
+      if (current2.isMissing(comparisonColumn)) {
+	System.err.println("Instance has missing value in comparison "
+			   + "column!\n" + current2);
+	continue;
+      }
+      if (current1.value(m_RunColumn) != current2.value(m_RunColumn)) {
+	System.err.println("Run numbers do not match!\n"
+			    + current1 + current2);
+      }
+      if (m_FoldColumn != -1) {
+	if (current1.value(m_FoldColumn) != current2.value(m_FoldColumn)) {
+	  System.err.println("Fold numbers do not match!\n"
+			     + current1 + current2);
+	}
+      }
+      double value1 = current1.value(comparisonColumn);
+      double value2 = current2.value(comparisonColumn);
+      pairedStats.add(value1, value2);
+    }
+    pairedStats.calculateDerived();
+    //System.err.println("Differences stats:\n" + pairedStats.differencesStats);
+    return pairedStats;
+
+  }
+  
+  /**
+   * Creates a key that maps resultset numbers to their descriptions.
+   *
+   * @return a value of type 'String'
+   */
+  public String resultsetKey() {
+
+    if (!m_ResultsetsValid) {
+      try {
+	prepareData();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	return ex.getMessage();
+      }
+    }
+    String result = "";
+    for (int j = 0; j < getNumResultsets(); j++) {
+      result += "(" + (j + 1) + ") " + getResultsetName(j) + '\n';
+    }
+    return result + '\n';
+  }
+  
+  /**
+   * Creates a "header" string describing the current resultsets.
+   *
+   * @param comparisonColumn a value of type 'int'
+   * @return a value of type 'String'
+   */
+  public String header(int comparisonColumn) {
+
+    if (!m_ResultsetsValid) {
+      try {
+	prepareData();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	return ex.getMessage();
+      }
+    }
+    
+    initResultMatrix();
+    m_ResultMatrix.addHeader("Tester", getClass().getName());
+    m_ResultMatrix.addHeader("Analysing", m_Instances.attribute(comparisonColumn).name());
+    m_ResultMatrix.addHeader("Datasets", Integer.toString(getNumDatasets()));
+    m_ResultMatrix.addHeader("Resultsets", Integer.toString(getNumResultsets()));
+    m_ResultMatrix.addHeader("Confidence", getSignificanceLevel() + " (two tailed)");
+    m_ResultMatrix.addHeader("Sorted by", getSortColumnName());
+    m_ResultMatrix.addHeader("Date", (new SimpleDateFormat()).format(new Date()));
+
+    return m_ResultMatrix.toStringHeader() + "\n";
+  }
+
+  /**
+   * Carries out a comparison between all resultsets, counting the number
+   * of datsets where one resultset outperforms the other.
+   *
+   * @param comparisonColumn the index of the comparison column
+   * @param nonSigWin for storing the non-significant wins
+   * @return a 2d array where element [i][j] is the number of times resultset
+   * j performed significantly better than resultset i.
+   * @throws Exception if an error occurs
+   */
+  public int [][] multiResultsetWins(int comparisonColumn, int [][] nonSigWin)
+    throws Exception {
+
+    int numResultsets = getNumResultsets();
+    int [][] win = new int [numResultsets][numResultsets];
+    //    int [][] nonSigWin = new int [numResultsets][numResultsets];
+    for (int i = 0; i < numResultsets; i++) {
+      for (int j = i + 1; j < numResultsets; j++) {
+	System.err.print("Comparing (" + (i + 1) + ") with ("
+			 + (j + 1) + ")\r");
+	System.err.flush();
+	for (int k = 0; k < getNumDatasets(); k++) {
+	  try {
+	    PairedStats pairedStats = 
+	      calculateStatistics(m_DatasetSpecifiers.specifier(k), i, j,
+				  comparisonColumn);
+	    if (pairedStats.differencesSignificance < 0) {
+	      win[i][j]++;
+	    } else if (pairedStats.differencesSignificance > 0) {
+	      win[j][i]++;
+	    }
+
+	    if (pairedStats.differencesStats.mean < 0) {
+	      nonSigWin[i][j]++;
+	    } else if (pairedStats.differencesStats.mean > 0) {
+	      nonSigWin[j][i]++;
+	    }
+	  } catch (Exception ex) {
+	    //ex.printStackTrace();
+	    System.err.println(ex.getMessage());
+	  }
+	}
+      }
+    }
+    return win;
+  }
+
+  /**
+   * clears the content and fills the column and row names according to the
+   * given sorting
+   */
+  protected void initResultMatrix() {
+    m_ResultMatrix.setSize(getNumResultsets(), getNumDatasets());
+    m_ResultMatrix.setShowStdDev(m_ShowStdDevs);
+
+    for (int i = 0; i < getNumDatasets(); i++)
+      m_ResultMatrix.setRowName(i, 
+          templateString(m_DatasetSpecifiers.specifier(i)));
+
+    for (int j = 0; j < getNumResultsets(); j++) {
+      m_ResultMatrix.setColName(j, getResultsetName(j));
+      m_ResultMatrix.setColHidden(j, !displayResultset(j));
+    }
+  }
+  
+  /**
+   * Carries out a comparison between all resultsets, counting the number
+   * of datsets where one resultset outperforms the other. The results
+   * are summarized in a table.
+   *
+   * @param comparisonColumn the index of the comparison column
+   * @return the results in a string
+   * @throws Exception if an error occurs
+   */
+  public String multiResultsetSummary(int comparisonColumn)
+    throws Exception {
+    
+    int[][] nonSigWin = new int [getNumResultsets()][getNumResultsets()];
+    int[][] win = multiResultsetWins(comparisonColumn, nonSigWin);
+    
+    initResultMatrix();    
+    m_ResultMatrix.setSummary(nonSigWin, win);
+    
+    return m_ResultMatrix.toStringSummary();
+  }
+
+  /**
+   * returns a ranking of the resultsets
+   * 
+   * @param comparisonColumn	the column to compare with
+   * @return			the ranking
+   * @throws Exception		if something goes wrong
+   */
+  public String multiResultsetRanking(int comparisonColumn)
+    throws Exception {
+    
+    int[][] nonSigWin = new int [getNumResultsets()][getNumResultsets()];
+    int[][] win       = multiResultsetWins(comparisonColumn, nonSigWin);
+    
+    initResultMatrix();    
+    m_ResultMatrix.setRanking(win);
+
+    return m_ResultMatrix.toStringRanking();
+  }
+				    
+  /**
+   * Creates a comparison table where a base resultset is compared to the
+   * other resultsets. Results are presented for every dataset.
+   *
+   * @param baseResultset the index of the base resultset
+   * @param comparisonColumn the index of the column to compare over
+   * @return the comparison table string
+   * @throws Exception if an error occurs
+   */
+  public String multiResultsetFull(int baseResultset,
+				   int comparisonColumn) throws Exception {
+
+    int maxWidthMean = 2;
+    int maxWidthStdDev = 2;
+    
+    double[] sortValues = new double[getNumDatasets()];
+      
+    // determine max field width
+    for (int i = 0; i < getNumDatasets(); i++) {
+      sortValues[i] = Double.POSITIVE_INFINITY;  // sorts skipped cols to end
+      
+      for (int j = 0; j < getNumResultsets(); j++) {
+        if (!displayResultset(j))
+          continue;
+	try {
+	  PairedStats pairedStats = 
+	    calculateStatistics(m_DatasetSpecifiers.specifier(i), 
+				baseResultset, j, comparisonColumn);
+          if (!Double.isInfinite(pairedStats.yStats.mean) &&
+              !Double.isNaN(pairedStats.yStats.mean)) {
+            double width = ((Math.log(Math.abs(pairedStats.yStats.mean)) / 
+                             Math.log(10))+1);
+            if (width > maxWidthMean) {
+              maxWidthMean = (int)width;
+            }
+          }
+
+          if (j == baseResultset) {
+            if (getSortColumn() != -1)
+              sortValues[i] = calculateStatistics(
+                                m_DatasetSpecifiers.specifier(i), 
+                                baseResultset, j, getSortColumn()).xStats.mean;
+            else
+              sortValues[i] = i;
+          }
+	  
+	  if (m_ShowStdDevs &&
+              !Double.isInfinite(pairedStats.yStats.stdDev) &&
+              !Double.isNaN(pairedStats.yStats.stdDev)) {
+	    double width = ((Math.log(Math.abs(pairedStats.yStats.stdDev)) / 
+                             Math.log(10))+1);
+	    if (width > maxWidthStdDev) {
+	      maxWidthStdDev = (int)width;
+	    }
+	  }
+	}  catch (Exception ex) {
+	  //ex.printStackTrace();
+          System.err.println(ex);
+	}
+      }
+    }
+
+    // sort rows according to sort column
+    m_SortOrder = Utils.sort(sortValues);
+
+    // determine column order
+    m_ColOrder = new int[getNumResultsets()];
+    m_ColOrder[0] = baseResultset;
+    int index = 1;
+    for (int i = 0; i < getNumResultsets(); i++) {
+      if (i == baseResultset)
+        continue;
+      m_ColOrder[index] = i;
+      index++;
+    }
+
+    // setup matrix
+    initResultMatrix();    
+    m_ResultMatrix.setRowOrder(m_SortOrder);
+    m_ResultMatrix.setColOrder(m_ColOrder);
+    m_ResultMatrix.setMeanWidth(maxWidthMean);
+    m_ResultMatrix.setStdDevWidth(maxWidthStdDev);
+    m_ResultMatrix.setSignificanceWidth(1);
+
+    // make sure that test base is displayed, even though it might not be
+    // selected
+    for (int i = 0; i < m_ResultMatrix.getColCount(); i++) {
+      if (    (i == baseResultset)
+           && (m_ResultMatrix.getColHidden(i)) ) {
+        m_ResultMatrix.setColHidden(i, false);
+        System.err.println("Note: test base was hidden - set visible!");
+      }
+    }
+    
+    // the data
+    for (int i = 0; i < getNumDatasets(); i++) {
+      m_ResultMatrix.setRowName(i, 
+          templateString(m_DatasetSpecifiers.specifier(i)));
+
+      for (int j = 0; j < getNumResultsets(); j++) {
+        try {
+          // calc stats
+          PairedStats pairedStats = 
+            calculateStatistics(m_DatasetSpecifiers.specifier(i), 
+                baseResultset, j, comparisonColumn);
+
+          // count
+          m_ResultMatrix.setCount(i, pairedStats.count);
+
+          // mean
+          m_ResultMatrix.setMean(j, i, pairedStats.yStats.mean);
+          
+          // std dev
+          m_ResultMatrix.setStdDev(j, i, pairedStats.yStats.stdDev);
+
+          // significance
+          if (pairedStats.differencesSignificance < 0)
+            m_ResultMatrix.setSignificance(j, i, ResultMatrix.SIGNIFICANCE_WIN);
+          else if (pairedStats.differencesSignificance > 0)
+            m_ResultMatrix.setSignificance(j, i, ResultMatrix.SIGNIFICANCE_LOSS);
+          else
+            m_ResultMatrix.setSignificance(j, i, ResultMatrix.SIGNIFICANCE_TIE);
+        }
+        catch (Exception e) {
+          //e.printStackTrace();
+          System.err.println(e);
+        }
+      }
+    }
+
+    // generate output
+    StringBuffer result = new StringBuffer(1000);
+    try {
+      result.append(m_ResultMatrix.toStringMatrix());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    // append a key so that we can tell the difference between long
+    // scheme+option names
+    result.append("\n\n" + m_ResultMatrix.toStringKey());
+
+    return result.toString();
+  }
+
+  /**
+   * Lists options understood by this object.
+   *
+   * @return an enumeration of Options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+             "\tSpecify list of columns that specify a unique\n"
+	      + "\tdataset.\n"
+	      + "\tFirst and last are valid indexes. (default none)",
+              "D", 1, "-D <index,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tSet the index of the column containing the run number",
+              "R", 1, "-R <index>"));
+    newVector.addElement(new Option(
+	      "\tSet the index of the column containing the fold number",
+              "F", 1, "-F <index>"));
+    newVector.addElement(new Option(
+              "\tSpecify list of columns that specify a unique\n"
+	      + "\t'result generator' (eg: classifier name and options).\n"
+	      + "\tFirst and last are valid indexes. (default none)",
+              "G", 1, "-G <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tSet the significance level for comparisons (default 0.05)",
+              "S", 1, "-S <significance level>"));
+    newVector.addElement(new Option(
+	      "\tShow standard deviations",
+              "V", 0, "-V"));
+    newVector.addElement(new Option(
+	      "\tProduce table comparisons in Latex table format",
+              "L", 0, "-L"));
+    newVector.addElement(new Option(
+         "\tProduce table comparisons in CSV table format",
+         "csv", 0, "-csv"));
+    newVector.addElement(new Option(
+         "\tProduce table comparisons in HTML table format",
+         "html", 0, "-html"));
+    newVector.addElement(new Option(
+         "\tProduce table comparisons with only the significance values",
+         "significance", 0, "-significance"));
+    newVector.addElement(new Option(
+         "\tProduce table comparisons output suitable for GNUPlot",
+         "gnuplot", 0, "-gnuplot"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D &lt;index,index2-index4,...&gt;
+   *  Specify list of columns that specify a unique
+   *  dataset.
+   *  First and last are valid indexes. (default none)</pre>
+   * 
+   * <pre> -R &lt;index&gt;
+   *  Set the index of the column containing the run number</pre>
+   * 
+   * <pre> -F &lt;index&gt;
+   *  Set the index of the column containing the fold number</pre>
+   * 
+   * <pre> -G &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns that specify a unique
+   *  'result generator' (eg: classifier name and options).
+   *  First and last are valid indexes. (default none)</pre>
+   * 
+   * <pre> -S &lt;significance level&gt;
+   *  Set the significance level for comparisons (default 0.05)</pre>
+   * 
+   * <pre> -V
+   *  Show standard deviations</pre>
+   * 
+   * <pre> -L
+   *  Produce table comparisons in Latex table format</pre>
+   * 
+   * <pre> -csv
+   *  Produce table comparisons in CSV table format</pre>
+   * 
+   * <pre> -html
+   *  Produce table comparisons in HTML table format</pre>
+   * 
+   * <pre> -significance
+   *  Produce table comparisons with only the significance values</pre>
+   * 
+   * <pre> -gnuplot
+   *  Produce table comparisons output suitable for GNUPlot</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options an array containing options to set.
+   * @throws Exception if invalid options are given
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setShowStdDevs(Utils.getFlag('V', options));
+    if (Utils.getFlag('L', options))
+      setResultMatrix(new ResultMatrixLatex());
+    if (Utils.getFlag("csv", options))
+      setResultMatrix(new ResultMatrixCSV());
+    if (Utils.getFlag("html", options))
+      setResultMatrix(new ResultMatrixHTML());
+    if (Utils.getFlag("significance", options))
+      setResultMatrix(new ResultMatrixSignificance());
+
+    String datasetList = Utils.getOption('D', options);
+    Range datasetRange = new Range();
+    if (datasetList.length() != 0) {
+      datasetRange.setRanges(datasetList);
+    }
+    setDatasetKeyColumns(datasetRange);
+
+    String indexStr = Utils.getOption('R', options);
+    if (indexStr.length() != 0) {
+      if (indexStr.equals("first")) {
+	setRunColumn(0);
+      } else if (indexStr.equals("last")) {
+	setRunColumn(-1);
+      } else {
+	setRunColumn(Integer.parseInt(indexStr) - 1);
+      }    
+    } else {
+      setRunColumn(-1);
+    }
+
+    String foldStr = Utils.getOption('F', options);
+    if (foldStr.length() != 0) {
+      setFoldColumn(Integer.parseInt(foldStr) - 1);
+    } else {
+      setFoldColumn(-1);
+    }
+
+    String sigStr = Utils.getOption('S', options);
+    if (sigStr.length() != 0) {
+      setSignificanceLevel((new Double(sigStr)).doubleValue());
+    } else {
+      setSignificanceLevel(0.05);
+    }
+    
+    String resultsetList = Utils.getOption('G', options);
+    Range generatorRange = new Range();
+    if (resultsetList.length() != 0) {
+      generatorRange.setRanges(resultsetList);
+    }
+    setResultsetKeyColumns(generatorRange);
+  }
+  
+  /**
+   * Gets current settings of the PairedTTester.
+   *
+   * @return an array of strings containing current options.
+   */
+  public String[] getOptions() {
+
+    String [] options = new String [11];
+    int current = 0;
+
+    if (!getResultsetKeyColumns().getRanges().equals("")) {
+      options[current++] = "-G";
+      options[current++] = getResultsetKeyColumns().getRanges();
+    }
+    if (!getDatasetKeyColumns().getRanges().equals("")) {
+      options[current++] = "-D";
+      options[current++] = getDatasetKeyColumns().getRanges();
+    }
+    options[current++] = "-R";
+    options[current++] = "" + (getRunColumn() + 1);
+    options[current++] = "-S";
+    options[current++] = "" + getSignificanceLevel();
+    
+    if (getShowStdDevs()) {
+      options[current++] = "-V";
+    }
+
+    if (getResultMatrix().equals(ResultMatrixLatex.class))
+      options[current++] = "-L";
+
+    if (getResultMatrix().equals(ResultMatrixCSV.class))
+      options[current++] = "-csv";
+   
+    if (getResultMatrix().equals(ResultMatrixHTML.class))
+      options[current++] = "-html";
+   
+    if (getResultMatrix().equals(ResultMatrixSignificance.class))
+      options[current++] = "-significance";
+   
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Get the value of ResultsetKeyColumns.
+   *
+   * @return Value of ResultsetKeyColumns.
+   */
+  public Range getResultsetKeyColumns() {
+    
+    return m_ResultsetKeyColumnsRange;
+  }
+  
+  /**
+   * Set the value of ResultsetKeyColumns.
+   *
+   * @param newResultsetKeyColumns Value to assign to ResultsetKeyColumns.
+   */
+  public void setResultsetKeyColumns(Range newResultsetKeyColumns) {
+    
+    m_ResultsetKeyColumnsRange = newResultsetKeyColumns;
+    m_ResultsetsValid = false;
+  }
+  
+  /**
+   * Gets the indices of the the datasets that are displayed (if <code>null</code>
+   * then all are displayed). The base is always displayed.
+   * 
+   * @return the indices of the datasets to display
+   */
+  public int[] getDisplayedResultsets() {
+    return m_DisplayedResultsets;
+  }
+  
+  /**
+   * Sets the indicies of the datasets to display (<code>null</code> means all).
+   * The base is always displayed.
+   * 
+   * @param cols the indices of the datasets to display
+   */
+  public void setDisplayedResultsets(int[] cols) {
+    m_DisplayedResultsets = cols;
+  }
+  
+  /**
+   * Get the value of SignificanceLevel.
+   *
+   * @return Value of SignificanceLevel.
+   */
+  public double getSignificanceLevel() {
+    
+    return m_SignificanceLevel;
+  }
+  
+  /**
+   * Set the value of SignificanceLevel.
+   *
+   * @param newSignificanceLevel Value to assign to SignificanceLevel.
+   */
+  public void setSignificanceLevel(double newSignificanceLevel) {
+    
+    m_SignificanceLevel = newSignificanceLevel;
+  }
+
+  /**
+   * Get the value of DatasetKeyColumns.
+   *
+   * @return Value of DatasetKeyColumns.
+   */
+  public Range getDatasetKeyColumns() {
+    
+    return m_DatasetKeyColumnsRange;
+  }
+  
+  /**
+   * Set the value of DatasetKeyColumns.
+   *
+   * @param newDatasetKeyColumns Value to assign to DatasetKeyColumns.
+   */
+  public void setDatasetKeyColumns(Range newDatasetKeyColumns) {
+    
+    m_DatasetKeyColumnsRange = newDatasetKeyColumns;
+    m_ResultsetsValid = false;
+  }
+  
+  /**
+   * Get the value of RunColumn.
+   *
+   * @return Value of RunColumn.
+   */
+  public int getRunColumn() {
+    
+    return m_RunColumnSet;
+  }
+  
+  /**
+   * Set the value of RunColumn.
+   *
+   * @param newRunColumn Value to assign to RunColumn.
+   */
+  public void setRunColumn(int newRunColumn) {
+    
+    m_RunColumnSet = newRunColumn;
+    m_ResultsetsValid = false;
+  }
+
+  /**
+   * Get the value of FoldColumn.
+   *
+   * @return Value of FoldColumn.
+   */
+  public int getFoldColumn() {
+    
+    return m_FoldColumn;
+  }
+  
+  /**
+   * Set the value of FoldColumn.
+   *
+   * @param newFoldColumn Value to assign to FoldColumn.
+   */
+  public void setFoldColumn(int newFoldColumn) {
+    
+    m_FoldColumn = newFoldColumn;
+    m_ResultsetsValid = false;
+  }
+
+  /**
+   * Returns the name of the column to sort on.
+   *
+   * @return the name of the column to sort on.
+   */
+  public String getSortColumnName() {
+    if (getSortColumn() == -1)
+      return "-";
+    else
+      return m_Instances.attribute(getSortColumn()).name();
+  }
+
+  /**
+   * Returns the column to sort on, -1 means the default sorting.
+   *
+   * @return the column to sort on.
+   */
+  public int getSortColumn() {
+    return m_SortColumn;
+  }
+  
+  /**
+   * Set the column to sort on, -1 means the default sorting.
+   *
+   * @param newSortColumn the new sort column.
+   */
+  public void setSortColumn(int newSortColumn) {
+    if (newSortColumn >= -1)
+      m_SortColumn = newSortColumn;
+  }
+  
+  /**
+   * Get the value of Instances.
+   *
+   * @return Value of Instances.
+   */
+  public Instances getInstances() {
+    
+    return m_Instances;
+  }
+  
+  /**
+   * Set the value of Instances.
+   *
+   * @param newInstances Value to assign to Instances.
+   */
+  public void setInstances(Instances newInstances) {
+    
+    m_Instances = newInstances;
+    m_ResultsetsValid = false;
+  }
+
+  /**
+   * retrieves all the settings from the given Tester
+   *
+   * @param tester      the Tester to get the settings from
+   */
+  public void assign(Tester tester) {
+    setInstances(tester.getInstances());
+    setResultMatrix(tester.getResultMatrix());
+    setShowStdDevs(tester.getShowStdDevs());
+    setResultsetKeyColumns(tester.getResultsetKeyColumns());
+    setDisplayedResultsets(tester.getDisplayedResultsets());
+    setSignificanceLevel(tester.getSignificanceLevel());
+    setDatasetKeyColumns(tester.getDatasetKeyColumns());
+    setRunColumn(tester.getRunColumn());
+    setFoldColumn(tester.getFoldColumn());
+    setSortColumn(tester.getSortColumn());
+  }
+
+  /**
+   * returns a string that is displayed as tooltip on the "perform test"
+   * button in the experimenter
+   * 
+   * @return	the tool tip
+   */
+  public String getToolTipText() {
+    return "Performs test using t-test statistic";
+  }
+
+  /**
+   * returns the name of the tester
+   * 
+   * @return	the display name
+   */
+  public String getDisplayName() {
+    return "Paired T-Tester";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5415 $");
+  }
+  
+  /**
+   * Test the class from the command line.
+   *
+   * @param args contains options for the instance ttests
+   */
+  public static void main(String args[]) {
+
+    try {
+      PairedTTester tt = new PairedTTester();
+      String datasetName = Utils.getOption('t', args);
+      String compareColStr = Utils.getOption('c', args);
+      String baseColStr = Utils.getOption('b', args);
+      boolean summaryOnly = Utils.getFlag('s', args);
+      boolean rankingOnly = Utils.getFlag('r', args);
+      try {
+	if ((datasetName.length() == 0)
+	    || (compareColStr.length() == 0)) {
+	  throw new Exception("-t and -c options are required");
+	}
+	tt.setOptions(args);
+	Utils.checkForRemainingOptions(args);
+      } catch (Exception ex) {
+	String result = "";
+	Enumeration enu = tt.listOptions();
+	while (enu.hasMoreElements()) {
+	  Option option = (Option) enu.nextElement();
+	  result += option.synopsis() + '\n'
+	    + option.description() + '\n';
+	}
+	throw new Exception(
+	      "Usage:\n\n"
+	      + "-t <file>\n"
+	      + "\tSet the dataset containing data to evaluate\n"
+	      + "-b <index>\n"
+	      + "\tSet the resultset to base comparisons against (optional)\n"
+	      + "-c <index>\n"
+	      + "\tSet the column to perform a comparison on\n"
+	      + "-s\n"
+	      + "\tSummarize wins over all resultset pairs\n\n"
+	      + "-r\n"
+	      + "\tGenerate a resultset ranking\n\n"
+	      + result);
+      }
+      Instances data = new Instances(new BufferedReader(
+				  new FileReader(datasetName)));
+      tt.setInstances(data);
+      //      tt.prepareData();
+      int compareCol = Integer.parseInt(compareColStr) - 1;
+      System.out.println(tt.header(compareCol));
+      if (rankingOnly) {
+	System.out.println(tt.multiResultsetRanking(compareCol));
+      } else if (summaryOnly) {
+	System.out.println(tt.multiResultsetSummary(compareCol));
+      } else {
+	System.out.println(tt.resultsetKey());
+	if (baseColStr.length() == 0) {
+	  for (int i = 0; i < tt.getNumResultsets(); i++) {
+            if (!tt.displayResultset(i))
+              continue;
+	    System.out.println(tt.multiResultsetFull(i, compareCol));
+	  }
+	} else {
+	  int baseCol = Integer.parseInt(baseColStr) - 1;
+	  System.out.println(tt.multiResultsetFull(baseCol, compareCol));
+	}
+      }
+    } catch(Exception e) {
+      e.printStackTrace();
+      System.err.println(e.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/PropertyNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/PropertyNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/PropertyNode.java	(revision 29)
@@ -0,0 +1,141 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyNode.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Stores information on a property of an object: the class of the
+ * object with the property; the property descriptor, and the current
+ * value.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public class PropertyNode
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8718165742572631384L;
+
+  /** The current property value */
+  public Object value;
+
+  /** The class of the object with this property */
+  public Class parentClass;
+
+  /** Other info about the property */
+  public PropertyDescriptor property;
+  
+  /**
+   * Creates a mostly empty property.
+   *
+   * @param pValue a property value.
+   */
+  public PropertyNode(Object pValue) {
+    
+    this(pValue, null, null);
+  }
+
+  /**
+   * Creates a fully specified property node.
+   *
+   * @param pValue the current property value.
+   * @param prop the PropertyDescriptor.
+   * @param pClass the Class of the object with this property.
+   */
+  public PropertyNode(Object pValue, PropertyDescriptor prop, Class pClass) {
+    
+    value = pValue;
+    property = prop;
+    parentClass = pClass;
+  }
+
+  /**
+   * Returns a string description of this property.
+   *
+   * @return a value of type 'String'
+   */
+  public String toString() {
+    
+    if (property == null) {
+      return "Available properties";
+    }
+    return property.getDisplayName();
+  }
+
+  /*
+   * Handle serialization ourselves since PropertyDescriptor isn't
+   * serializable
+   */
+  private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+
+    try {
+      out.writeObject(value);
+    } catch (Exception ex) {
+      throw new IOException("Can't serialize object: " + ex.getMessage());
+    }
+    out.writeObject(parentClass);
+    out.writeObject(property.getDisplayName());
+    out.writeObject(property.getReadMethod().getName());
+    out.writeObject(property.getWriteMethod().getName());
+  }
+  private void readObject(java.io.ObjectInputStream in)
+    throws IOException, ClassNotFoundException {
+
+    value = in.readObject();
+    parentClass = (Class) in.readObject();
+    String name = (String) in.readObject();
+    String getter = (String) in.readObject();
+    String setter = (String) in.readObject();
+    /*
+    System.err.println("Loading property descriptor:\n"
+		       + "\tparentClass: " + parentClass.getName()
+		       + "\tname: " + name
+		       + "\tgetter: " + getter
+		       + "\tsetter: " + setter);
+    */
+    try {
+      property = new PropertyDescriptor(name, parentClass, getter, setter);
+    } catch (IntrospectionException ex) {
+      throw new ClassNotFoundException("Couldn't create property descriptor: "
+				       + parentClass.getName() + "::"
+				       + name);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.7 $");
+  }
+} // PropertyNode
Index: branches/MetisMQI/src/main/java/weka/experiment/RandomSplitResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RandomSplitResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RandomSplitResultProducer.java	(revision 29)
@@ -0,0 +1,914 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomSplitResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates a single train/test split and calls the appropriate SplitEvaluator to generate some results.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;percent&gt;
+ *  The percentage of instances to use for training.
+ *  (default 66)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> -R
+ *  Set when data is not to be randomized and the data sets' size.
+ *  Is not to be determined via probabilistic rounding.</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * All options after -- will be passed to the split evaluator.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.20 $
+ */
+public class RandomSplitResultProducer 
+  implements ResultProducer, OptionHandler, AdditionalMeasureProducer, 
+             RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 1403798165056795073L;
+  
+  /** The dataset of interest */
+  protected Instances m_Instances;
+
+  /** The ResultListener to send results to */
+  protected ResultListener m_ResultListener = new CSVResultListener();
+
+  /** The percentage of instances to use for training */
+  protected double m_TrainPercent = 66;
+
+  /** Whether dataset is to be randomized */
+  protected boolean m_randomize = true;
+
+  /** The SplitEvaluator used to generate results */
+  protected SplitEvaluator m_SplitEvaluator = new ClassifierSplitEvaluator();
+
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** Save raw output of split evaluators --- for debugging purposes */
+  protected boolean m_debugOutput = false;
+
+  /** The output zipper to use for saving raw splitEvaluator output */
+  protected OutputZipper m_ZipDest = null;
+
+  /** The destination output file/directory for raw output */
+  protected File m_OutputFile = new File(
+			        new File(System.getProperty("user.dir")), 
+				"splitEvalutorOut.zip");
+
+  /** The name of the key field containing the dataset name */
+  public static String DATASET_FIELD_NAME = "Dataset";
+
+  /** The name of the key field containing the run number */
+  public static String RUN_FIELD_NAME = "Run";
+
+  /** The name of the result field containing the timestamp */
+  public static String TIMESTAMP_FIELD_NAME = "Date_time";
+
+  /**
+   * Returns a string describing this result producer
+   * @return a description of the result producer suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "Generates a single train/test split and calls the appropriate "
+      + "SplitEvaluator to generate some results.";
+  }
+
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  public void setInstances(Instances instances) {
+    
+    m_Instances = instances;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in SplitEvaluators. This could contain many measures (of which only a
+   * subset may be produceable by the current SplitEvaluator) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of measure names, null if none
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    if (m_SplitEvaluator != null) {
+      System.err.println("RandomSplitResultProducer: setting additional "
+			 +"measures for "
+			 +"split evaluator");
+      m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+    }
+  }
+  
+    /**
+     * Returns an enumeration of any additional measure names that might be
+   * in the SplitEvaluator
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_SplitEvaluator).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_SplitEvaluator instanceof AdditionalMeasureProducer) {
+      return ((AdditionalMeasureProducer)m_SplitEvaluator).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("RandomSplitResultProducer: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_SplitEvaluator.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+  
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  public void setResultListener(ResultListener listener) {
+
+    m_ResultListener = listener;
+  }
+
+  /**
+   * Gets a Double representing the current date and time.
+   * eg: 1:46pm on 20/5/1999 -> 19990520.1346
+   *
+   * @return a value of type Double
+   */
+  public static Double getTimestamp() {
+
+    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+    double timestamp = now.get(Calendar.YEAR) * 10000
+      + (now.get(Calendar.MONTH) + 1) * 100
+      + now.get(Calendar.DAY_OF_MONTH)
+      + now.get(Calendar.HOUR_OF_DAY) / 100.0
+      + now.get(Calendar.MINUTE) / 10000.0;
+    return new Double(timestamp);
+  }
+
+  /**
+   * Prepare to generate results.
+   *
+   * @throws Exception if an error occurs during preprocessing.
+   */
+  public void preProcess() throws Exception {
+
+    if (m_SplitEvaluator == null) {
+      throw new Exception("No SplitEvalutor set");
+    }
+    if (m_ResultListener == null) {
+      throw new Exception("No ResultListener set");
+    }
+    m_ResultListener.preProcess(this);
+  }
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more requests to generate results for the current experiment
+   * will be sent.
+   *
+   * @throws Exception if an error occurs
+   */
+  public void postProcess() throws Exception {
+
+    m_ResultListener.postProcess(this);
+    if (m_debugOutput) {
+      if (m_ZipDest != null) {
+	m_ZipDest.finished();
+	m_ZipDest = null;
+      }
+    }
+  }
+
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @throws Exception if a problem occurs while getting the keys
+   */
+  public void doRunKeys(int run) throws Exception {
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+    // Add in some fields to the key like run number, dataset name
+    Object [] seKey = m_SplitEvaluator.getKey();
+    Object [] key = new Object [seKey.length + 2];
+    key[0] = Utils.backQuoteChars(m_Instances.relationName());
+    key[1] = "" + run;
+    System.arraycopy(seKey, 0, key, 2, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+      try {
+	m_ResultListener.acceptResult(this, key, null);
+      } catch (Exception ex) {
+	// Save the train and test datasets for debugging purposes?
+	throw ex;
+      }
+    }
+  }
+
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get results for.
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public void doRun(int run) throws Exception {
+
+    if (getRawOutput()) {
+      if (m_ZipDest == null) {
+	m_ZipDest = new OutputZipper(m_OutputFile);
+      }
+    }
+
+    if (m_Instances == null) {
+      throw new Exception("No Instances set");
+    }
+    // Add in some fields to the key like run number, dataset name
+    Object [] seKey = m_SplitEvaluator.getKey();
+    Object [] key = new Object [seKey.length + 2];
+    key[0] = Utils.backQuoteChars(m_Instances.relationName());
+    key[1] = "" + run;
+    System.arraycopy(seKey, 0, key, 2, seKey.length);
+    if (m_ResultListener.isResultRequired(this, key)) {
+
+      // Randomize on a copy of the original dataset
+      Instances runInstances = new Instances(m_Instances);
+
+      Instances train;
+      Instances test;
+
+      if (!m_randomize) {
+
+	// Don't do any randomization
+	int trainSize = Utils.round(runInstances.numInstances() * m_TrainPercent / 100);
+	int testSize = runInstances.numInstances() - trainSize;
+	train = new Instances(runInstances, 0, trainSize);
+	test = new Instances(runInstances, trainSize, testSize);
+      } else {
+	Random rand = new Random(run);
+	runInstances.randomize(rand);
+	
+	// Nominal class
+	if (runInstances.classAttribute().isNominal()) {
+	  
+	  // create the subset for each classs
+	  int numClasses = runInstances.numClasses();
+	  Instances[] subsets = new Instances[numClasses + 1];
+	  for (int i=0; i < numClasses + 1; i++) {
+	    subsets[i] = new Instances(runInstances, 10);
+	  }
+	  
+	  // divide instances into subsets
+	  Enumeration e = runInstances.enumerateInstances();
+	  while(e.hasMoreElements()) {
+	    Instance inst = (Instance) e.nextElement();
+	    if (inst.classIsMissing()) {
+	      subsets[numClasses].add(inst);
+	    } else {
+	      subsets[(int) inst.classValue()].add(inst);
+	    }
+	  }
+	  
+	  // Compactify them
+	  for (int i=0; i < numClasses + 1; i++) {
+	    subsets[i].compactify();
+	  }
+	  
+	  // merge into train and test sets
+	  train = new Instances(runInstances, runInstances.numInstances());
+	  test = new Instances(runInstances, runInstances.numInstances());
+	  for (int i = 0; i < numClasses + 1; i++) {
+	    int trainSize = 
+	      Utils.probRound(subsets[i].numInstances() * m_TrainPercent / 100, rand);
+	    for (int j = 0; j < trainSize; j++) {
+	      train.add(subsets[i].instance(j));
+	    }
+	    for (int j = trainSize; j < subsets[i].numInstances(); j++) {
+	      test.add(subsets[i].instance(j));
+	    }
+	    // free memory
+	    subsets[i] = null;
+	  }
+	  train.compactify();
+	  test.compactify();
+	  
+	  // randomize the final sets
+	  train.randomize(rand);
+	  test.randomize(rand);
+	} else {
+	  
+	  // Numeric target 
+	  int trainSize = 
+	    Utils.probRound(runInstances.numInstances() * m_TrainPercent / 100, rand);
+	  int testSize = runInstances.numInstances() - trainSize;
+	  train = new Instances(runInstances, 0, trainSize);
+	  test = new Instances(runInstances, trainSize, testSize);
+	}
+      }
+      try {
+	Object [] seResults = m_SplitEvaluator.getResult(train, test);
+	Object [] results = new Object [seResults.length + 1];
+	results[0] = getTimestamp();
+	System.arraycopy(seResults, 0, results, 1,
+			 seResults.length);
+	if (m_debugOutput) {
+	  String resultName = 
+	    (""+run+"."+
+	     Utils.backQuoteChars(runInstances.relationName())
+	     +"."
+	     +m_SplitEvaluator.toString()).replace(' ','_');
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.classifiers.");
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.filters.");
+	  resultName = Utils.removeSubstring(resultName, 
+					     "weka.attributeSelection.");
+	  m_ZipDest.zipit(m_SplitEvaluator.getRawResultOutput(), resultName);
+	}
+	m_ResultListener.acceptResult(this, key, results);
+      } catch (Exception ex) {
+	// Save the train and test datasets for debugging purposes?
+	throw ex;
+      }
+    }
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing the name of each column
+   */
+  public String [] getKeyNames() {
+
+    String [] keyNames = m_SplitEvaluator.getKeyNames();
+    // Add in the names of our extra key fields
+    String [] newKeyNames = new String [keyNames.length + 2];
+    newKeyNames[0] = DATASET_FIELD_NAME;
+    newKeyNames[1] = RUN_FIELD_NAME;
+    System.arraycopy(keyNames, 0, newKeyNames, 2, keyNames.length);
+    return newKeyNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getKeyTypes() {
+
+    Object [] keyTypes = m_SplitEvaluator.getKeyTypes();
+    // Add in the types of our extra fields
+    Object [] newKeyTypes = new String [keyTypes.length + 2];
+    newKeyTypes[0] = new String();
+    newKeyTypes[1] = new String();
+    System.arraycopy(keyTypes, 0, newKeyTypes, 2, keyTypes.length);
+    return newKeyTypes;
+  }
+
+  /**
+   * Gets the names of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing the name of each column
+   */
+  public String [] getResultNames() {
+
+    String [] resultNames = m_SplitEvaluator.getResultNames();
+    // Add in the names of our extra Result fields
+    String [] newResultNames = new String [resultNames.length + 1];
+    newResultNames[0] = TIMESTAMP_FIELD_NAME;
+    System.arraycopy(resultNames, 0, newResultNames, 1, resultNames.length);
+    return newResultNames;
+  }
+
+  /**
+   * Gets the data types of each of the columns produced for a single run.
+   * This method should really be static.
+   *
+   * @return an array containing objects of the type of each column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+
+    Object [] resultTypes = m_SplitEvaluator.getResultTypes();
+    // Add in the types of our extra Result fields
+    Object [] newResultTypes = new Object [resultTypes.length + 1];
+    newResultTypes[0] = new Double(0);
+    System.arraycopy(resultTypes, 0, newResultTypes, 1, resultTypes.length);
+    return newResultTypes;
+  }
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent the command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  public String getCompatibilityState() {
+
+    String result = "-P " + m_TrainPercent;
+    if (!getRandomizeData()) {
+      result += " -R";
+    }
+    if (m_SplitEvaluator == null) {
+      result += " <null SplitEvaluator>";
+    } else {
+      result += " -W " + m_SplitEvaluator.getClass().getName();
+    }
+    return result + " --";
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String outputFileTipText() {
+    return "Set the destination for saving raw output. If the rawOutput "
+      +"option is selected, then output from the splitEvaluator for "
+      +"individual train-test splits is saved. If the destination is a "
+      +"directory, "
+      +"then each output is saved to an individual gzip file; if the "
+      +"destination is a file, then each output is saved as an entry "
+      +"in a zip file.";
+  }
+
+  /**
+   * Get the value of OutputFile.
+   *
+   * @return Value of OutputFile.
+   */
+  public File getOutputFile() {
+    
+    return m_OutputFile;
+  }
+  
+  /**
+   * Set the value of OutputFile.
+   *
+   * @param newOutputFile Value to assign to OutputFile.
+   */
+  public void setOutputFile(File newOutputFile) {
+    
+    m_OutputFile = newOutputFile;
+  }  
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomizeDataTipText() {
+    return "Do not randomize dataset and do not perform probabilistic rounding " +
+      "if true";
+  }
+
+  /**
+   * Get if dataset is to be randomized
+   * @return true if dataset is to be randomized
+   */
+  public boolean getRandomizeData() {
+    return m_randomize;
+  }
+  
+  /**
+   * Set to true if dataset is to be randomized
+   * @param d true if dataset is to be randomized
+   */
+  public void setRandomizeData(boolean d) {
+    m_randomize = d;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String rawOutputTipText() {
+    return "Save raw output (useful for debugging). If set, then output is "
+      +"sent to the destination specified by outputFile";
+  }
+
+  /**
+   * Get if raw split evaluator output is to be saved
+   * @return true if raw split evalutor output is to be saved
+   */
+  public boolean getRawOutput() {
+    return m_debugOutput;
+  }
+  
+  /**
+   * Set to true if raw split evaluator output is to be saved
+   * @param d true if output is to be saved
+   */
+  public void setRawOutput(boolean d) {
+    m_debugOutput = d;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String trainPercentTipText() {
+    return "Set the percentage of data to use for training.";
+  }
+
+  /**
+   * Get the value of TrainPercent.
+   *
+   * @return Value of TrainPercent.
+   */
+  public double getTrainPercent() {
+    
+    return m_TrainPercent;
+  }
+  
+  /**
+   * Set the value of TrainPercent.
+   *
+   * @param newTrainPercent Value to assign to TrainPercent.
+   */
+  public void setTrainPercent(double newTrainPercent) {
+    
+    m_TrainPercent = newTrainPercent;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String splitEvaluatorTipText() {
+    return "The evaluator to apply to the test data. "
+      +"This may be a classifier, regression scheme etc.";
+  }
+
+  /**
+   * Get the SplitEvaluator.
+   *
+   * @return the SplitEvaluator.
+   */
+  public SplitEvaluator getSplitEvaluator() {
+    
+    return m_SplitEvaluator;
+  }
+  
+  /**
+   * Set the SplitEvaluator.
+   *
+   * @param newSplitEvaluator new SplitEvaluator to use.
+   */
+  public void setSplitEvaluator(SplitEvaluator newSplitEvaluator) {
+    
+    m_SplitEvaluator = newSplitEvaluator;
+    m_SplitEvaluator.setAdditionalMeasures(m_AdditionalMeasures);
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+	     "\tThe percentage of instances to use for training.\n"
+	      +"\t(default 66)", 
+	     "P", 1, 
+	     "-P <percent>"));
+
+    newVector.addElement(new Option(
+	     "Save raw split evaluator output.",
+	     "D",0,"-D"));
+
+    newVector.addElement(new Option(
+	     "\tThe filename where raw output will be stored.\n"
+	     +"\tIf a directory name is specified then then individual\n"
+	     +"\toutputs will be gzipped, otherwise all output will be\n"
+	     +"\tzipped to the named file. Use in conjuction with -D."
+	     +"\t(default splitEvalutorOut.zip)", 
+	     "O", 1, 
+	     "-O <file/directory name/path>"));
+
+    newVector.addElement(new Option(
+	     "\tThe full class name of a SplitEvaluator.\n"
+	      +"\teg: weka.experiment.ClassifierSplitEvaluator", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    newVector.addElement(new Option(
+	     "\tSet when data is not to be randomized and the data sets' size.\n"
+	     + "\tIs not to be determined via probabilistic rounding.",
+	     "R",0,"-R"));
+
+ 
+    if ((m_SplitEvaluator != null) &&
+	(m_SplitEvaluator instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to split evaluator "
+	     + m_SplitEvaluator.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_SplitEvaluator).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;percent&gt;
+   *  The percentage of instances to use for training.
+   *  (default 66)</pre>
+   * 
+   * <pre> -D
+   * Save raw split evaluator output.</pre>
+   * 
+   * <pre> -O &lt;file/directory name/path&gt;
+   *  The filename where raw output will be stored.
+   *  If a directory name is specified then then individual
+   *  outputs will be gzipped, otherwise all output will be
+   *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of a SplitEvaluator.
+   *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+   * 
+   * <pre> -R
+   *  Set when data is not to be randomized and the data sets' size.
+   *  Is not to be determined via probabilistic rounding.</pre>
+   * 
+   * <pre> 
+   * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+   * </pre>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  The index of the class for which IR statistics
+   *  are to be output. (default 1)</pre>
+   * 
+   * <pre> -I &lt;index&gt;
+   *  The index of an attribute to output in the
+   *  results. This attribute should identify an
+   *  instance in order to know which instances are
+   *  in the test set of a cross validation. if 0
+   *  no output (default 0).</pre>
+   * 
+   * <pre> -P
+   *  Add target and prediction columns to the result
+   *  for each fold.</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All options after -- will be passed to the split evaluator.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    setRawOutput(Utils.getFlag('D', options));
+    setRandomizeData(!Utils.getFlag('R', options));
+
+    String fName = Utils.getOption('O', options);
+    if (fName.length() != 0) {
+      setOutputFile(new File(fName));
+    }
+
+    String trainPct = Utils.getOption('P', options);
+    if (trainPct.length() != 0) {
+      setTrainPercent((new Double(trainPct)).doubleValue());
+    } else {
+      setTrainPercent(66);
+    }
+
+    String seName = Utils.getOption('W', options);
+    if (seName.length() == 0) {
+      throw new Exception("A SplitEvaluator must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // SE.
+    setSplitEvaluator((SplitEvaluator)Utils.forName(
+		      SplitEvaluator.class,
+		      seName,
+		      null));
+    if (getSplitEvaluator() instanceof OptionHandler) {
+      ((OptionHandler) getSplitEvaluator())
+	.setOptions(Utils.partitionOptions(options));
+    }
+  }
+
+  /**
+   * Gets the current settings of the result producer.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] seOptions = new String [0];
+    if ((m_SplitEvaluator != null) && 
+	(m_SplitEvaluator instanceof OptionHandler)) {
+      seOptions = ((OptionHandler)m_SplitEvaluator).getOptions();
+    }
+    
+    String [] options = new String [seOptions.length + 9];
+    int current = 0;
+
+    options[current++] = "-P"; options[current++] = "" + getTrainPercent();
+    
+    if (getRawOutput()) {
+      options[current++] = "-D";
+    }
+    
+    if (!getRandomizeData()) {
+      options[current++] = "-R";
+    }
+
+    options[current++] = "-O"; 
+    options[current++] = getOutputFile().getName();
+
+    if (getSplitEvaluator() != null) {
+      options[current++] = "-W";
+      options[current++] = getSplitEvaluator().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(seOptions, 0, options, current, 
+		     seOptions.length);
+    current += seOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Gets a text descrption of the result producer.
+   *
+   * @return a text description of the result producer.
+   */
+  public String toString() {
+
+    String result = "RandomSplitResultProducer: ";
+    result += getCompatibilityState();
+    if (m_Instances == null) {
+      result += ": <null Instances>";
+    } else {
+      result += ": " + Utils.backQuoteChars(m_Instances.relationName());
+    }
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.20 $");
+  }
+} // RandomSplitResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/RegressionSplitEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RegressionSplitEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RegressionSplitEvaluator.java	(revision 29)
@@ -0,0 +1,735 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RegressionSplitEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.rules.ZeroR;
+import weka.core.AdditionalMeasureProducer;
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Summarizable;
+import weka.core.Utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A SplitEvaluator that produces results for a classification scheme on a numeric class attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class RegressionSplitEvaluator 
+  implements SplitEvaluator, OptionHandler, AdditionalMeasureProducer,
+             RevisionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -328181640503349202L;
+
+  /** The template classifier */
+  protected Classifier m_Template = new ZeroR();
+
+  /** The classifier used for evaluation */
+  protected Classifier m_Classifier;
+  
+  /** The names of any additional measures to look for in SplitEvaluators */
+  protected String [] m_AdditionalMeasures = null;
+
+  /** Array of booleans corresponding to the measures in m_AdditionalMeasures
+      indicating which of the AdditionalMeasures the current classifier
+      can produce */
+  protected boolean [] m_doesProduce = null;
+
+  /** Holds the statistics for the most recent application of the classifier */
+  protected String m_result = null;
+
+  /** The classifier options (if any) */
+  protected String m_ClassifierOptions = "";
+
+  /** The classifier version */
+  protected String m_ClassifierVersion = "";
+
+  /** The length of a key */
+  private static final int KEY_SIZE = 3;
+
+  /** The length of a result */
+  private static final int RESULT_SIZE = 23;
+
+  /**
+   * No args constructor.
+   */
+  public RegressionSplitEvaluator() {
+
+    updateOptions();
+  }
+
+  /**
+   * Returns a string describing this split evaluator
+   * @return a description of the split evaluator suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "A SplitEvaluator that produces results for a classification "
+      +"scheme on a numeric class attribute.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+	     "\tThe full class name of the classifier.\n"
+	      +"\teg: weka.classifiers.bayes.NaiveBayes", 
+	     "W", 1, 
+	     "-W <class name>"));
+
+    if ((m_Template != null) &&
+	(m_Template instanceof OptionHandler)) {
+      newVector.addElement(new Option(
+	     "",
+	     "", 0, "\nOptions specific to classifier "
+	     + m_Template.getClass().getName() + ":"));
+      Enumeration enu = ((OptionHandler)m_Template).listOptions();
+      while (enu.hasMoreElements()) {
+	newVector.addElement(enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;class name&gt;
+   *  The full class name of the classifier.
+   *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+   * 
+   * <pre> 
+   * Options specific to classifier weka.classifiers.rules.ZeroR:
+   * </pre>
+   * 
+   * <pre> -D
+   *  If set, classifier is run in debug mode and
+   *  may output additional info to the console</pre>
+   * 
+   <!-- options-end -->
+   *
+   * All option after -- will be passed to the classifier.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String cName = Utils.getOption('W', options);
+    if (cName.length() == 0) {
+      throw new Exception("A classifier must be specified with"
+			  + " the -W option.");
+    }
+    // Do it first without options, so if an exception is thrown during
+    // the option setting, listOptions will contain options for the actual
+    // Classifier.
+    setClassifier(AbstractClassifier.forName(cName, null));
+    if (getClassifier() instanceof OptionHandler) {
+      ((OptionHandler) getClassifier())
+	.setOptions(Utils.partitionOptions(options));
+      updateOptions();
+    }
+  }
+
+  /**
+   * Gets the current settings of the Classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] classifierOptions = new String [0];
+    if ((m_Template != null) && 
+	(m_Template instanceof OptionHandler)) {
+      classifierOptions = ((OptionHandler)m_Template).getOptions();
+    }
+    
+    String [] options = new String [classifierOptions.length + 3];
+    int current = 0;
+
+    if (getClassifier() != null) {
+      options[current++] = "-W";
+      options[current++] = getClassifier().getClass().getName();
+    }
+    options[current++] = "--";
+
+    System.arraycopy(classifierOptions, 0, options, current, 
+		     classifierOptions.length);
+    current += classifierOptions.length;
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Set a list of method names for additional measures to look for
+   * in Classifiers. This could contain many measures (of which only a
+   * subset may be produceable by the current Classifier) if an experiment
+   * is the type that iterates over a set of properties.
+   * @param additionalMeasures an array of method names.
+   */
+  public void setAdditionalMeasures(String [] additionalMeasures) {
+    m_AdditionalMeasures = additionalMeasures;
+
+    // determine which (if any) of the additional measures this classifier
+    // can produce
+    if (m_AdditionalMeasures != null && m_AdditionalMeasures.length > 0) {
+      m_doesProduce = new boolean [m_AdditionalMeasures.length];
+
+      if (m_Template instanceof AdditionalMeasureProducer) {
+	Enumeration en = ((AdditionalMeasureProducer)m_Template).
+	  enumerateMeasures();
+	while (en.hasMoreElements()) {
+	  String mname = (String)en.nextElement();
+	  for (int j=0;j<m_AdditionalMeasures.length;j++) {
+	    if (mname.compareToIgnoreCase(m_AdditionalMeasures[j]) == 0) {
+	      m_doesProduce[j] = true;
+	    }
+	  }
+	}
+      }
+    } else {
+      m_doesProduce = null;
+    }
+  }
+  
+
+    /**
+   * Returns an enumeration of any additional measure names that might be
+   * in the classifier
+   * @return an enumeration of the measure names
+   */
+  public Enumeration enumerateMeasures() {
+    Vector newVector = new Vector();
+    if (m_Template instanceof AdditionalMeasureProducer) {
+      Enumeration en = ((AdditionalMeasureProducer)m_Template).
+	enumerateMeasures();
+      while (en.hasMoreElements()) {
+	String mname = (String)en.nextElement();
+	newVector.addElement(mname);
+      }
+    }
+    return newVector.elements();
+  }
+  
+  /**
+   * Returns the value of the named measure
+   * @param additionalMeasureName the name of the measure to query for its value
+   * @return the value of the named measure
+   * @throws IllegalArgumentException if the named measure is not supported
+   */
+  public double getMeasure(String additionalMeasureName) {
+    if (m_Template instanceof AdditionalMeasureProducer) {
+      if (m_Classifier == null) {
+	throw new IllegalArgumentException("ClassifierSplitEvaluator: " +
+					   "Can't return result for measure, " +
+					   "classifier has not been built yet.");
+      }
+      return ((AdditionalMeasureProducer)m_Classifier).
+	getMeasure(additionalMeasureName);
+    } else {
+      throw new IllegalArgumentException("ClassifierSplitEvaluator: "
+			  +"Can't return value for : "+additionalMeasureName
+			  +". "+m_Template.getClass().getName()+" "
+			  +"is not an AdditionalMeasureProducer");
+    }
+  }
+
+  /**
+   * Gets the data types of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each key column. The 
+   * objects should be Strings, or Doubles.
+   */
+  public Object [] getKeyTypes() {
+
+    Object [] keyTypes = new Object[KEY_SIZE];
+    keyTypes[0] = "";
+    keyTypes[1] = "";
+    keyTypes[2] = "";
+    return keyTypes;
+  }
+
+  /**
+   * Gets the names of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each key column
+   */
+  public String [] getKeyNames() {
+
+    String [] keyNames = new String[KEY_SIZE];
+    keyNames[0] = "Scheme";
+    keyNames[1] = "Scheme_options";
+    keyNames[2] = "Scheme_version_ID";
+    return keyNames;
+  }
+
+  /**
+   * Gets the key describing the current SplitEvaluator. For example
+   * This may contain the name of the classifier used for classifier
+   * predictive evaluation. The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array of objects containing the key.
+   */
+  public Object [] getKey(){
+
+    Object [] key = new Object[KEY_SIZE];
+    key[0] = m_Template.getClass().getName();
+    key[1] = m_ClassifierOptions;
+    key[2] = m_ClassifierVersion;
+    return key;
+  }
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run. The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   */
+  public Object [] getResultTypes() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    Object [] resultTypes = new Object[RESULT_SIZE+addm];
+    Double doub = new Double(0);
+    int current = 0;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // Timing stats
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    
+    // sizes
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    // Prediction interval statistics
+    resultTypes[current++] = doub;
+    resultTypes[current++] = doub;
+
+    resultTypes[current++] = "";
+
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultTypes[current++] = doub;
+    }
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("ResultTypes didn't fit RESULT_SIZE");
+    }
+    return resultTypes;
+  }
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each result column
+   */
+  public String [] getResultNames() {
+    int addm = (m_AdditionalMeasures != null) 
+      ? m_AdditionalMeasures.length 
+      : 0;
+    String [] resultNames = new String[RESULT_SIZE+addm];
+    int current = 0;
+    resultNames[current++] = "Number_of_training_instances";
+    resultNames[current++] = "Number_of_testing_instances";
+
+    // Sensitive stats - certainty of predictions
+    resultNames[current++] = "Mean_absolute_error";
+    resultNames[current++] = "Root_mean_squared_error";
+    resultNames[current++] = "Relative_absolute_error";
+    resultNames[current++] = "Root_relative_squared_error";
+    resultNames[current++] = "Correlation_coefficient";
+
+    // SF stats
+    resultNames[current++] = "SF_prior_entropy";
+    resultNames[current++] = "SF_scheme_entropy";
+    resultNames[current++] = "SF_entropy_gain";
+    resultNames[current++] = "SF_mean_prior_entropy";
+    resultNames[current++] = "SF_mean_scheme_entropy";
+    resultNames[current++] = "SF_mean_entropy_gain";
+
+    // Timing stats
+    resultNames[current++] = "Elapsed_Time_training";
+    resultNames[current++] = "Elapsed_Time_testing";
+    resultNames[current++] = "UserCPU_Time_training";
+    resultNames[current++] = "UserCPU_Time_testing";
+
+    // sizes
+    resultNames[current++] = "Serialized_Model_Size";
+    resultNames[current++] = "Serialized_Train_Set_Size";
+    resultNames[current++] = "Serialized_Test_Set_Size";
+    
+    // Prediction interval statistics
+    resultNames[current++] = "Coverage_of_Test_Cases_By_Regions";
+    resultNames[current++] = "Size_of_Predicted_Regions";
+
+    // Classifier defined extras
+    resultNames[current++] = "Summary";
+    // add any additional measures
+    for (int i=0;i<addm;i++) {
+      resultNames[current++] = m_AdditionalMeasures[i];
+    }
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("ResultNames didn't fit RESULT_SIZE");
+    }
+    return resultNames;
+  }
+
+  /**
+   * Gets the results for the supplied train and test datasets. Now performs
+   * a deep copy of the classifier before it is built and evaluated (just in case
+   * the classifier is not initialized properly in buildClassifier()).
+   *
+   * @param train the training Instances.
+   * @param test the testing Instances.
+   * @return the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @throws Exception if a problem occurs while getting the results
+   */
+  public Object [] getResult(Instances train, Instances test) 
+    throws Exception {
+
+    if (train.classAttribute().type() != Attribute.NUMERIC) {
+      throw new Exception("Class attribute is not numeric!");
+    }
+    if (m_Template == null) {
+      throw new Exception("No classifier has been specified");
+    }
+    ThreadMXBean thMonitor = ManagementFactory.getThreadMXBean();
+    boolean canMeasureCPUTime = thMonitor.isThreadCpuTimeSupported();
+    if(!thMonitor.isThreadCpuTimeEnabled())
+      thMonitor.setThreadCpuTimeEnabled(true);
+    
+    int addm = (m_AdditionalMeasures != null) ? m_AdditionalMeasures.length : 0;
+    Object [] result = new Object[RESULT_SIZE+addm];
+    long thID = Thread.currentThread().getId();
+    long CPUStartTime=-1, trainCPUTimeElapsed=-1, testCPUTimeElapsed=-1,
+         trainTimeStart, trainTimeElapsed, testTimeStart, testTimeElapsed;    
+    Evaluation eval = new Evaluation(train);
+    m_Classifier = AbstractClassifier.makeCopy(m_Template);
+
+    trainTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime)
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    m_Classifier.buildClassifier(train);
+    if(canMeasureCPUTime)
+      trainCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+    testTimeStart = System.currentTimeMillis();
+    if(canMeasureCPUTime)
+      CPUStartTime = thMonitor.getThreadUserTime(thID);
+    eval.evaluateModel(m_Classifier, test);
+    if(canMeasureCPUTime)
+      testCPUTimeElapsed = thMonitor.getThreadUserTime(thID) - CPUStartTime;
+    testTimeElapsed = System.currentTimeMillis() - testTimeStart;
+    thMonitor = null;
+    
+    m_result = eval.toSummaryString();
+    // The results stored are all per instance -- can be multiplied by the
+    // number of instances to get absolute numbers
+    int current = 0;
+    result[current++] = new Double(train.numInstances());
+    result[current++] = new Double(eval.numInstances());
+
+    result[current++] = new Double(eval.meanAbsoluteError());
+    result[current++] = new Double(eval.rootMeanSquaredError());
+    result[current++] = new Double(eval.relativeAbsoluteError());
+    result[current++] = new Double(eval.rootRelativeSquaredError());
+    result[current++] = new Double(eval.correlationCoefficient());
+
+    result[current++] = new Double(eval.SFPriorEntropy());
+    result[current++] = new Double(eval.SFSchemeEntropy());
+    result[current++] = new Double(eval.SFEntropyGain());
+    result[current++] = new Double(eval.SFMeanPriorEntropy());
+    result[current++] = new Double(eval.SFMeanSchemeEntropy());
+    result[current++] = new Double(eval.SFMeanEntropyGain());
+    
+    // Timing stats
+    result[current++] = new Double(trainTimeElapsed / 1000.0);
+    result[current++] = new Double(testTimeElapsed / 1000.0);
+    if(canMeasureCPUTime) {
+      result[current++] = new Double((trainCPUTimeElapsed/1000000.0) / 1000.0);
+      result[current++] = new Double((testCPUTimeElapsed /1000000.0) / 1000.0);
+    }
+    else {
+      result[current++] = new Double(Utils.missingValue());
+      result[current++] = new Double(Utils.missingValue());
+    }
+    
+    // sizes
+    ByteArrayOutputStream bastream = new ByteArrayOutputStream();
+    ObjectOutputStream oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(m_Classifier);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(train);
+    result[current++] = new Double(bastream.size());
+    bastream = new ByteArrayOutputStream();
+    oostream = new ObjectOutputStream(bastream);
+    oostream.writeObject(test);
+    result[current++] = new Double(bastream.size());
+    
+    // Prediction interval statistics
+    result[current++] = new Double(eval.coverageOfTestCasesByPredictedRegions());
+    result[current++] = new Double(eval.sizeOfPredictedRegions());
+
+    if (m_Classifier instanceof Summarizable) {
+      result[current++] = ((Summarizable)m_Classifier).toSummaryString();
+    } else {
+      result[current++] = null;
+    }
+    
+    for (int i=0;i<addm;i++) {
+      if (m_doesProduce[i]) {
+        try {
+          double dv = ((AdditionalMeasureProducer)m_Classifier).
+          getMeasure(m_AdditionalMeasures[i]);
+          if (!Utils.isMissingValue(dv)) {
+            Double value = new Double(dv);
+            result[current++] = value;
+          } else {
+            result[current++] = null;
+          }
+        } catch (Exception ex) {
+          System.err.println(ex);
+        }
+      } else {
+        result[current++] = null;
+      }
+    }
+    
+    if (current != RESULT_SIZE+addm) {
+      throw new Error("Results didn't fit RESULT_SIZE");
+    }
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "The classifier to use.";
+  }
+
+  /**
+   * Get the value of Classifier.
+   *
+   * @return Value of Classifier.
+   */
+  public Classifier getClassifier() {
+    
+    return m_Template;
+  }
+  
+  /**
+   * Sets the classifier.
+   *
+   * @param newClassifier the new classifier to use.
+   */
+  public void setClassifier(Classifier newClassifier) {
+    
+    m_Template = newClassifier;
+    updateOptions();
+
+    System.err.println("RegressionSplitEvaluator: In set classifier");
+  }
+
+  /**
+   * Updates the options that the current classifier is using.
+   */
+  protected void updateOptions() {
+    
+    if (m_Template instanceof OptionHandler) {
+      m_ClassifierOptions = Utils.joinOptions(((OptionHandler)m_Template)
+					      .getOptions());
+    } else {
+      m_ClassifierOptions = "";
+    }
+    if (m_Template instanceof Serializable) {
+      ObjectStreamClass obs = ObjectStreamClass.lookup(m_Template
+						       .getClass());
+      m_ClassifierVersion = "" + obs.getSerialVersionUID();
+    } else {
+      m_ClassifierVersion = "";
+    }
+  }
+
+  /**
+   * Set the Classifier to use, given it's class name. A new classifier will be
+   * instantiated.
+   *
+   * @param newClassifierName the Classifier class name.
+   * @throws Exception if the class name is invalid.
+   */
+  public void setClassifierName(String newClassifierName) throws Exception {
+
+    try {
+      setClassifier((Classifier)Class.forName(newClassifierName)
+		    .newInstance());
+    } catch (Exception ex) {
+      throw new Exception("Can't find Classifier with class name: "
+			  + newClassifierName);
+    }
+  }
+
+  /**
+   * Gets the raw output from the classifier
+   * @return the raw output from the classifier
+   */
+  public String getRawResultOutput() {
+    StringBuffer result = new StringBuffer();
+
+    if (m_Classifier == null) {
+      return "<null> classifier";
+    }
+    result.append(toString());
+    result.append("Classifier model: \n"+m_Classifier.toString()+'\n');
+
+    // append the performance statistics
+    if (m_result != null) {
+      result.append(m_result);
+      
+      if (m_doesProduce != null) {
+	for (int i=0;i<m_doesProduce.length;i++) {
+	  if (m_doesProduce[i]) {
+	    try {
+	      double dv = ((AdditionalMeasureProducer)m_Classifier).
+		getMeasure(m_AdditionalMeasures[i]);
+	      if (!Utils.isMissingValue(dv)) {
+		Double value = new Double(dv);
+		result.append(m_AdditionalMeasures[i]+" : "+value+'\n');
+	      } else {
+		result.append(m_AdditionalMeasures[i]+" : "+'?'+'\n');
+	      }
+	    } catch (Exception ex) {
+	      System.err.println(ex);
+	    }
+	  } 
+	}
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * Returns a text description of the split evaluator.
+   *
+   * @return a text description of the split evaluator.
+   */
+  public String toString() {
+
+    String result = "RegressionSplitEvaluator: ";
+    if (m_Template == null) {
+      return result + "<null> classifier";
+    }
+    return result + m_Template.getClass().getName() + " " 
+      + m_ClassifierOptions + "(version " + m_ClassifierVersion + ")";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+} // RegressionSplitEvaluator
Index: branches/MetisMQI/src/main/java/weka/experiment/RemoteEngine.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RemoteEngine.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RemoteEngine.java	(revision 29)
@@ -0,0 +1,375 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoteEngine.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Queue;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.rmi.Naming;
+import java.rmi.RMISecurityManager;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A general purpose server for executing Task objects sent via RMI.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.12 $
+ */
+public class RemoteEngine
+  extends UnicastRemoteObject
+  implements Compute, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1021538162895448259L;
+
+  /** The name of the host that this engine is started on */
+  private String m_HostName = "local";
+
+  /** A queue of waiting tasks */
+  private Queue m_TaskQueue = new Queue();
+
+  /** A queue of corresponding ID's for tasks */
+  private Queue m_TaskIdQueue = new Queue();
+
+  /** A hashtable of experiment status */
+  private Hashtable m_TaskStatus = new Hashtable();
+
+  /** Is there a task running */
+  private boolean m_TaskRunning = false;
+  
+  /** Clean up interval (in ms) */
+  protected static long CLEANUPTIMEOUT = 3600000;
+
+  /**
+   * Constructor
+   * @param hostName name of the host
+   * @exception RemoteException if something goes wrong
+   */
+  public RemoteEngine(String hostName) throws RemoteException {
+    super();
+    m_HostName = hostName;
+
+    /* launch a clean-up thread. Will purge any failed or finished 
+       tasks still in the TaskStatus hashtable after an hour */
+       
+    Thread cleanUpThread;
+    cleanUpThread = new Thread() {
+	public void run() {
+	  while (true) {
+	    try {
+	      // sleep for a while
+	      Thread.sleep(CLEANUPTIMEOUT);
+	    } catch (InterruptedException ie) {}
+
+	    if (m_TaskStatus.size() > 0) {
+	      purge();
+	    } else {
+	      System.err.println("RemoteEngine : purge - no tasks to check.");
+	    }
+	  }
+	}
+      };
+    cleanUpThread.setPriority(Thread.MIN_PRIORITY);
+    cleanUpThread.setDaemon(true);
+    cleanUpThread.start();
+  }
+  
+  /**
+   * Takes a task object and queues it for execution
+   * @param t the Task object to execute
+   * @return an identifier for the Task that can be used when querying
+   * Task status
+   */
+  public synchronized Object executeTask(Task t) throws RemoteException {
+    String taskId = ""+System.currentTimeMillis()+":";
+    taskId += t.hashCode();
+    addTaskToQueue(t, taskId);
+
+    return taskId;
+    //    return t.execute();
+  }
+
+  /**
+   * Returns status information on a particular task
+   *
+   * @param taskId the ID of the task to check
+   * @return a <code>TaskStatusInfo</code> encapsulating task status info
+   * @exception Exception if an error occurs
+   */
+  public Object checkStatus(Object taskId) throws Exception {
+    
+    TaskStatusInfo inf = (TaskStatusInfo)m_TaskStatus.get(taskId);
+
+    if (inf == null) {
+      throw new Exception("RemoteEngine ("+m_HostName+") : Task not found.");
+    }
+    
+    TaskStatusInfo result = new TaskStatusInfo();
+    result.setExecutionStatus(inf.getExecutionStatus());
+    result.setStatusMessage(inf.getStatusMessage());
+    result.setTaskResult(inf.getTaskResult());
+
+    if (inf.getExecutionStatus() == TaskStatusInfo.FINISHED ||
+	inf.getExecutionStatus() == TaskStatusInfo.FAILED) {
+      System.err.println("Finished/failed Task id : " 
+			 + taskId + " checked by client. Removing.");
+      inf.setTaskResult(null);
+      inf = null;
+      m_TaskStatus.remove(taskId);
+    }
+    inf = null;
+    return result;
+  }
+
+  /**
+   * Adds a new task to the queue.
+   *
+   * @param t a <code>Task</code> value to be added
+   * @param taskId the id of the task to be added
+   */
+  private synchronized void addTaskToQueue(Task t, String taskId) {
+    TaskStatusInfo newTask = t.getTaskStatus();
+    if (newTask == null) {
+      newTask = new TaskStatusInfo();
+    }
+    m_TaskQueue.push(t);
+    m_TaskIdQueue.push(taskId);
+    newTask.setStatusMessage("RemoteEngine ("
+			     +m_HostName
+			     +") : task " + taskId + " queued at postion: "
+			     +m_TaskQueue.size());
+    // add task status to HashTable
+    m_TaskStatus.put(taskId, newTask);
+    System.err.println("Task id : " + taskId + " Queued.");
+    if (m_TaskRunning == false) {
+      startTask();
+    }
+  }
+
+  /**
+   * Checks to see if there are any waiting tasks, and if no task is
+   * currently running starts a waiting task.
+   */
+  private void startTask() {
+
+    if (m_TaskRunning == false && m_TaskQueue.size() > 0) {
+      Thread activeTaskThread;
+      activeTaskThread = new Thread() {
+	  public void run() {
+	    m_TaskRunning = true;
+	    Task currentTask = (Task)m_TaskQueue.pop();
+	    String taskId = (String)m_TaskIdQueue.pop();
+	    TaskStatusInfo tsi = (TaskStatusInfo)m_TaskStatus.get(taskId);
+	    tsi.setExecutionStatus(TaskStatusInfo.PROCESSING);
+	    tsi.setStatusMessage("RemoteEngine ("
+				 +m_HostName
+				 +") : task " + taskId + " running...");
+	    try {
+	      System.err.println("Launching task id : "
+				 + taskId + "...");
+	      currentTask.execute();
+	      TaskStatusInfo runStatus = currentTask.getTaskStatus();
+	      tsi.setExecutionStatus(runStatus.getExecutionStatus());
+	      tsi.setStatusMessage("RemoteExperiment ("
+				   +m_HostName+") "
+				   +runStatus.getStatusMessage());
+	      tsi.setTaskResult(runStatus.getTaskResult());
+	    } catch (Error er) {
+              // Object initialization can raise Error, which are not subclass of Exception
+	      tsi.setExecutionStatus(TaskStatusInfo.FAILED);
+              if (er.getCause() instanceof java.security.AccessControlException) {
+                tsi.setStatusMessage("RemoteEngine ("
+                                     +m_HostName
+                                     +") : security error, check remote policy file.");
+                System.err.println("Task id " + taskId + " Failed! Check remote policy file");
+              }
+              else {
+                tsi.setStatusMessage("RemoteEngine ("
+                                     +m_HostName
+                                     +") : unknown initialization error.");
+                System.err.println("Task id " + taskId + " Unknown initialization error");
+              }
+	    } catch (Exception ex) {
+	      tsi.setExecutionStatus(TaskStatusInfo.FAILED);
+              if (ex instanceof java.io.FileNotFoundException) {
+                tsi.setStatusMessage("RemoteEngine ("
+                                     +m_HostName
+                                     +") : " + ex.getMessage());
+                System.err.println("Task id " + taskId + " Failed, " + ex.getMessage());
+              }
+              else {
+                tsi.setStatusMessage("RemoteEngine ("
+                                     +m_HostName
+                                     +") : task " + taskId + " failed.");
+                System.err.println("Task id " + taskId + " Failed!");
+              }
+	    } finally {
+	      if (m_TaskStatus.size() == 0) {
+		purgeClasses();
+	      }
+	      m_TaskRunning = false;
+	      // start any waiting tasks
+	      startTask();
+	    }
+	  }
+	};
+      activeTaskThread.setPriority(Thread.MIN_PRIORITY);
+      activeTaskThread.start();
+    }
+  }
+
+  /**
+   * Attempts to purge class types from the virtual machine. May take some
+   * time as it relies on garbage collection
+   */
+  private void purgeClasses() {
+    try {
+      // see if we can purge classes
+      ClassLoader prevCl = 
+	Thread.currentThread().getContextClassLoader();
+      ClassLoader urlCl = 
+	URLClassLoader.newInstance(new URL[] {new URL("file:.")}, prevCl);
+      Thread.currentThread().setContextClassLoader(urlCl);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+  
+  /**
+   * Checks the hash table for failed/finished tasks. Any that have been
+   * around for an @seeCLEANUPTIMEOUT or more are removed. Clients are expected to check
+   * on the status of their remote tasks. Checking on the status of a
+   * finished/failed task will remove it from the hash table, therefore
+   * any failed/finished tasks left lying around for more than an hour
+   * suggest that their client has died..
+   *
+   */
+  private void purge() {
+    Enumeration keys = m_TaskStatus.keys();
+    long currentTime = System.currentTimeMillis();
+    System.err.println("RemoteEngine purge. Current time : " + currentTime);
+    while (keys.hasMoreElements()) {
+      String taskId = (String)keys.nextElement();
+      System.err.print("Examining task id : " + taskId + "... ");
+      String timeString = taskId.substring(0, taskId.indexOf(':'));
+      long ts = Long.valueOf(timeString).longValue();
+      if (currentTime - ts > CLEANUPTIMEOUT) {
+	TaskStatusInfo tsi = (TaskStatusInfo)m_TaskStatus.get(taskId);
+	if ((tsi != null) 
+	    && (tsi.getExecutionStatus() == TaskStatusInfo.FINISHED ||
+                tsi.getExecutionStatus() == TaskStatusInfo.FAILED)) {
+	  System.err.println("\nTask id : " 
+			     + taskId + " has gone stale. Removing.");
+	  m_TaskStatus.remove(taskId);
+	  tsi.setTaskResult(null);
+	  tsi = null;
+	}
+      } else {
+	System.err.println("ok.");
+      }
+    }
+    if (m_TaskStatus.size() == 0) {
+      purgeClasses();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.12 $");
+  }
+
+  /**
+   * Main method. Gets address of the local host, creates a remote engine
+   * object and binds it in the RMI registry. If there is no RMI registry,
+   * then it tries to create one with default port 1099.
+   *
+   * @param args 
+   */
+  public static void main(String[] args) {
+    if (System.getSecurityManager() == null) {
+      System.setSecurityManager(new RMISecurityManager());
+    }
+    
+    int port = 1099;
+    InetAddress localhost = null;
+    try {
+      localhost = InetAddress.getLocalHost();
+      System.err.println("Host name : "+localhost.getHostName());
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    String name;
+    if (localhost != null) {
+      name = localhost.getHostName();
+    } else {
+      name = "localhost";
+    }
+    
+    // get optional port
+    try {
+      String portOption = Utils.getOption("p", args);
+      if (!portOption.equals("")) 
+        port = Integer.parseInt(portOption);
+    } catch (Exception ex) {
+      System.err.println("Usage : -p <port>");
+    }
+
+    if (port != 1099) {
+      name = name + ":" + port;
+    }
+    name = "//"+name+"/RemoteEngine";
+    
+    try {
+      Compute engine = new RemoteEngine(name);
+      
+      try {      
+        Naming.rebind(name, engine);
+        System.out.println("RemoteEngine bound in RMI registry");
+      } catch (RemoteException ex) {
+        // try to bootstrap a new registry
+        System.err.println("Attempting to start RMI registry on port " + port + "...");
+        java.rmi.registry.LocateRegistry.createRegistry(port);
+        Naming.bind(name, engine);
+        System.out.println("RemoteEngine bound in RMI registry");
+      }
+      
+    } catch (Exception e) {
+      System.err.println("RemoteEngine exception: " + 
+			 e.getMessage());
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/RemoteExperiment.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RemoteExperiment.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RemoteExperiment.java	(revision 29)
@@ -0,0 +1,974 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoteExperiment.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.FastVector;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Queue;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.xml.KOML;
+import weka.core.xml.XMLOptions;
+import weka.experiment.xml.XMLExperiment;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.rmi.Naming;
+import java.util.Enumeration;
+
+import javax.swing.DefaultListModel;
+
+/**
+ * Holds all the necessary configuration information for a distributed
+ * experiment. This object is able to be serialized for storage on disk.<p>
+ * 
+ * This class is experimental at present. Has been tested using 
+ * CSVResultListener (sending results to standard out) and 
+ * DatabaseResultListener (InstantDB + RmiJdbc bridge). <p>
+ *
+ * Getting started:<p>
+ *
+ * Start InstantDB (with the RMI bridge) on some machine. If using java2
+ * then specify -Djava.security.policy=db.policy to the
+ * virtual machine. Where db.policy is as follows: <br>
+ * <pre>
+ * grant {
+ *   permission java.security.AllPermission;
+ * };
+ * </pre><p>
+ *
+ * Start RemoteEngine servers on x machines as per the instructons in the
+ * README_Experiment_Gui file. There must be a 
+ * DatabaseUtils.props in either the HOME or current directory of each
+ * machine, listing all necessary jdbc drivers.<p>
+ *
+ * The machine where a RemoteExperiment is started must also have a copy
+ * of DatabaseUtils.props listing the URL to the machine where the 
+ * database server is running (RmiJdbc + InstantDB). <p>
+ *
+ * Here is an example of starting a RemoteExperiment: <p>
+ *
+ * <pre>
+ *
+ * java -Djava.rmi.server.codebase=file:/path to weka classes/ \
+ * weka.experiment.RemoteExperiment -L 1 -U 10 \
+ * -T /home/ml/datasets/UCI/iris.arff \
+ * -D "weka.experiment.DatabaseResultListener" \
+ * -P "weka.experiment.RandomSplitResultProducer" \
+ * -h rosebud.cs.waikato.ac.nz -h blackbird.cs.waikato.ac.nz -r -- \
+ * -W weka.experiment.ClassifierSplitEvaluator -- \
+ * -W weka.classifiers.bayes.NaiveBayes
+ *
+ * </pre> <p>
+ * The "codebase" property tells rmi where to serve up weka classes from.
+ * This can either be a file url (as long as a shared file system is being
+ * used that is accessable by the remoteEngine servers), or http url (which
+ * of course supposes that a web server is running and you have put your
+ * weka classes somewhere that is web accessable). If using a file url the
+ * trailing "/" is *most* important unless the weka classes are in a jar
+ * file. <p>
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  The lower run number to start the experiment from.
+ *  (default 1)</pre>
+ * 
+ * <pre> -U &lt;num&gt;
+ *  The upper run number to end the experiment at (inclusive).
+ *  (default 10)</pre>
+ * 
+ * <pre> -T &lt;arff file&gt;
+ *  The dataset to run the experiment on.
+ *  (required, may be specified multiple times)</pre>
+ * 
+ * <pre> -P &lt;class name&gt;
+ *  The full class name of a ResultProducer (required).
+ *  eg: weka.experiment.RandomSplitResultProducer</pre>
+ * 
+ * <pre> -D &lt;class name&gt;
+ *  The full class name of a ResultListener (required).
+ *  eg: weka.experiment.CSVResultListener</pre>
+ * 
+ * <pre> -N &lt;string&gt;
+ *  A string containing any notes about the experiment.
+ *  (default none)</pre>
+ * 
+ * <pre> 
+ * Options specific to result producer weka.experiment.RandomSplitResultProducer:
+ * </pre>
+ * 
+ * <pre> -P &lt;percent&gt;
+ *  The percentage of instances to use for training.
+ *  (default 66)</pre>
+ * 
+ * <pre> -D
+ * Save raw split evaluator output.</pre>
+ * 
+ * <pre> -O &lt;file/directory name/path&gt;
+ *  The filename where raw output will be stored.
+ *  If a directory name is specified then then individual
+ *  outputs will be gzipped, otherwise all output will be
+ *  zipped to the named file. Use in conjuction with -D. (default splitEvalutorOut.zip)</pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of a SplitEvaluator.
+ *  eg: weka.experiment.ClassifierSplitEvaluator</pre>
+ * 
+ * <pre> -R
+ *  Set when data is not to be randomized and the data sets' size.
+ *  Is not to be determined via probabilistic rounding.</pre>
+ * 
+ * <pre> 
+ * Options specific to split evaluator weka.experiment.ClassifierSplitEvaluator:
+ * </pre>
+ * 
+ * <pre> -W &lt;class name&gt;
+ *  The full class name of the classifier.
+ *  eg: weka.classifiers.bayes.NaiveBayes</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  The index of the class for which IR statistics
+ *  are to be output. (default 1)</pre>
+ * 
+ * <pre> -I &lt;index&gt;
+ *  The index of an attribute to output in the
+ *  results. This attribute should identify an
+ *  instance in order to know which instances are
+ *  in the test set of a cross validation. if 0
+ *  no output (default 0).</pre>
+ * 
+ * <pre> -P
+ *  Add target and prediction columns to the result
+ *  for each fold.</pre>
+ * 
+ * <pre> 
+ * Options specific to classifier weka.classifiers.rules.ZeroR:
+ * </pre>
+ * 
+ * <pre> -D
+ *  If set, classifier is run in debug mode and
+ *  may output additional info to the console</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.16 $
+ */
+public class RemoteExperiment 
+  extends Experiment {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7357668825635314937L;
+
+  /** The list of objects listening for remote experiment events */
+  private FastVector m_listeners = new FastVector();
+
+  /** Holds the names of machines with remoteEngine servers running */
+  protected DefaultListModel m_remoteHosts = new DefaultListModel();
+  
+  /** The queue of available hosts */
+  private Queue m_remoteHostsQueue = new Queue();
+
+  /** The status of each of the remote hosts */
+  private int [] m_remoteHostsStatus;
+
+  /** The number of times tasks have failed on each remote host */
+  private int [] m_remoteHostFailureCounts;
+
+  /** status of the remote host: available */
+  protected static final int AVAILABLE=0;
+  /** status of the remote host: in use */
+  protected static final int IN_USE=1;
+  /** status of the remote host: connection failed */
+  protected static final int CONNECTION_FAILED=2;
+  /** status of the remote host: some other failure */
+  protected static final int SOME_OTHER_FAILURE=3;
+
+//    protected static final int TO_BE_RUN=0;
+//    protected static final int PROCESSING=1;
+//    protected static final int FAILED=2;
+//    protected static final int FINISHED=3;
+
+  /** allow at most 3 failures on a host before it is removed from the list
+      of usable hosts */
+  protected static final int MAX_FAILURES=3;
+
+  /** Set to true if MAX_FAILURES exceeded on all hosts or connections fail 
+      on all hosts or user aborts experiment (via gui) */
+  private boolean m_experimentAborted = false;
+
+  /** The number of hosts removed due to exceeding max failures */
+  private int m_removedHosts;
+
+  /** The count of failed sub-experiments */
+  private int m_failedCount;
+
+  /** The count of successfully completed sub-experiments */
+  private int m_finishedCount;
+
+  /** The base experiment to split up into sub experiments for remote
+      execution */
+  private Experiment m_baseExperiment = null;
+
+  /** The sub experiments */
+  protected Experiment [] m_subExperiments;
+
+  /** The queue of sub experiments waiting to be processed */
+  private Queue m_subExpQueue = new Queue();
+
+  /** The status of each of the sub-experiments */
+  protected int [] m_subExpComplete;
+
+  /**
+   * If true, then sub experiments are created on the basis of data sets
+   * rather than run number.
+   */
+  protected boolean m_splitByDataSet = true;
+
+
+  /**
+   * Returns true if sub experiments are to be created on the basis of
+   * data set..
+   *
+   * @return a <code>boolean</code> value indicating whether sub
+   * experiments are to be created on the basis of data set (true) or
+   * run number (false).
+   */
+  public boolean getSplitByDataSet() {
+    return m_splitByDataSet;
+  }
+
+  /**
+   * Set whether sub experiments are to be created on the basis of
+   * data set.
+   *
+   * @param sd true if sub experiments are to be created on the basis
+   * of data set. Otherwise sub experiments are created on the basis of
+   * run number.
+   */
+  public void setSplitByDataSet(boolean sd) {
+    m_splitByDataSet = sd;
+  }
+  
+  /**
+   * Construct a new RemoteExperiment using an empty Experiment as base 
+   * Experiment
+   * @throws Exception if the base experiment is null
+   */
+  public RemoteExperiment() throws Exception {
+     this(new Experiment());
+  }
+  
+  /**
+   * Construct a new RemoteExperiment using a base Experiment
+   * @param base the base experiment to use
+   * @throws Exception if the base experiment is null
+   */
+  public RemoteExperiment(Experiment base) throws Exception {
+    setBaseExperiment(base);
+  }
+
+  /**
+   * Add an object to the list of those interested in recieving update
+   * information from the RemoteExperiment
+   * @param r a listener
+   */
+  public void addRemoteExperimentListener(RemoteExperimentListener r) {
+    m_listeners.addElement(r);
+  }
+
+  /**
+   * Get the base experiment used by this remote experiment
+   * @return the base experiment
+   */
+  public Experiment getBaseExperiment() {
+    return m_baseExperiment;
+  }
+
+  /**
+   * Set the base experiment. A sub experiment will be created for each
+   * run in the base experiment.
+   * @param base the base experiment to use.
+   * @throws Exception if supplied base experiment is null
+   */
+  public void setBaseExperiment(Experiment base) throws Exception {
+    if (base == null) {
+      throw new Exception("Base experiment is null!");
+    }
+    m_baseExperiment = base;
+    setRunLower(m_baseExperiment.getRunLower());
+    setRunUpper(m_baseExperiment.getRunUpper());
+    setResultListener(m_baseExperiment.getResultListener());
+    setResultProducer(m_baseExperiment.getResultProducer());
+    setDatasets(m_baseExperiment.getDatasets());
+    setUsePropertyIterator(m_baseExperiment.getUsePropertyIterator());
+    setPropertyPath(m_baseExperiment.getPropertyPath());
+    setPropertyArray(m_baseExperiment.getPropertyArray());
+    setNotes(m_baseExperiment.getNotes());
+    m_ClassFirst = m_baseExperiment.m_ClassFirst;
+    m_AdvanceDataSetFirst = m_baseExperiment.m_AdvanceDataSetFirst;
+  }
+  
+  /**
+   * Set the user notes.
+   *
+   * @param newNotes New user notes.
+   */
+  public void setNotes(String newNotes) {
+    
+    super.setNotes(newNotes);
+    m_baseExperiment.setNotes(newNotes);
+  }
+
+  /**
+   * Set the lower run number for the experiment.
+   *
+   * @param newRunLower the lower run number for the experiment.
+   */
+  public void setRunLower(int newRunLower) {
+    
+    super.setRunLower(newRunLower);
+    m_baseExperiment.setRunLower(newRunLower);
+  }
+
+  /**
+   * Set the upper run number for the experiment.
+   *
+   * @param newRunUpper the upper run number for the experiment.
+   */
+  public void setRunUpper(int newRunUpper) {
+    
+    super.setRunUpper(newRunUpper);
+    m_baseExperiment.setRunUpper(newRunUpper);
+  }
+
+  /**
+   * Sets the result listener where results will be sent.
+   *
+   * @param newResultListener the result listener where results will be sent.
+   */
+  public void setResultListener(ResultListener newResultListener) {
+    
+    super.setResultListener(newResultListener);
+    m_baseExperiment.setResultListener(newResultListener);
+  }
+
+  /**
+   * Set the result producer used for the current experiment.
+   *
+   * @param newResultProducer result producer to use for the current 
+   * experiment.
+   */
+  public void setResultProducer(ResultProducer newResultProducer) {
+    
+    super.setResultProducer(newResultProducer);
+    m_baseExperiment.setResultProducer(newResultProducer);
+  }
+
+  /**
+   * Set the datasets to use in the experiment
+   * @param ds the list of datasets to use
+   */
+  public void setDatasets(DefaultListModel ds) {
+    super.setDatasets(ds);
+    m_baseExperiment.setDatasets(ds);
+  }
+
+  /**
+   * Sets whether the custom property iterator should be used.
+   *
+   * @param newUsePropertyIterator true if so
+   */
+  public void setUsePropertyIterator(boolean newUsePropertyIterator) {
+    
+    super.setUsePropertyIterator(newUsePropertyIterator);
+    m_baseExperiment.setUsePropertyIterator(newUsePropertyIterator);
+  }
+
+  /**
+   * Sets the path of properties taken to get to the custom property
+   * to iterate over.
+   *
+   * @param newPropertyPath an array of PropertyNodes
+   */
+  public void setPropertyPath(PropertyNode [] newPropertyPath) {
+    
+    super.setPropertyPath(newPropertyPath);
+    m_baseExperiment.setPropertyPath(newPropertyPath);
+  }
+
+  /**
+   * Sets the array of values to set the custom property to.
+   *
+   * @param newPropArray a value of type Object which should be an
+   * array of the appropriate values.
+   */
+  public void setPropertyArray(Object newPropArray) {
+    super.setPropertyArray(newPropArray);
+    m_baseExperiment.setPropertyArray(newPropArray);
+  }
+
+    
+  /**
+   * Prepares a remote experiment for running, creates sub experiments
+   *
+   * @throws Exception if an error occurs
+   */
+  public void initialize() throws Exception {
+    if (m_baseExperiment == null) {
+      throw new Exception("No base experiment specified!");
+    }
+
+    m_experimentAborted = false;
+    m_finishedCount = 0;
+    m_failedCount = 0;
+    m_RunNumber = getRunLower();
+    m_DatasetNumber = 0;
+    m_PropertyNumber = 0;
+    m_CurrentProperty = -1;
+    m_CurrentInstances = null;
+    m_Finished = false;
+
+    if (m_remoteHosts.size() == 0) {
+      throw new Exception("No hosts specified!");
+    }
+    // initialize all remote hosts to available
+    m_remoteHostsStatus = new int [m_remoteHosts.size()];    
+    m_remoteHostFailureCounts = new int [m_remoteHosts.size()];
+
+    m_remoteHostsQueue = new Queue();
+    // prime the hosts queue
+    for (int i=0;i<m_remoteHosts.size();i++) {
+      m_remoteHostsQueue.push(new Integer(i));
+    }
+
+    // set up sub experiments
+    m_subExpQueue = new Queue();
+    int numExps;
+    if (getSplitByDataSet()) {
+      numExps = m_baseExperiment.getDatasets().size();
+    } else {
+      numExps = getRunUpper() - getRunLower() + 1;
+    }
+    m_subExperiments = new Experiment[numExps];
+    m_subExpComplete = new int[numExps];
+    // create copy of base experiment
+    SerializedObject so = new SerializedObject(m_baseExperiment);
+
+    if (getSplitByDataSet()) {
+      for (int i = 0; i < m_baseExperiment.getDatasets().size(); i++) {
+	m_subExperiments[i] = (Experiment)so.getObject();
+	// one for each data set
+	DefaultListModel temp = new DefaultListModel();
+	temp.addElement(m_baseExperiment.getDatasets().elementAt(i));
+	m_subExperiments[i].setDatasets(temp);
+	m_subExpQueue.push(new Integer(i));
+      }
+    } else {
+      for (int i = getRunLower(); i <= getRunUpper(); i++) {
+	m_subExperiments[i-getRunLower()] = (Experiment)so.getObject();
+	// one run for each sub experiment
+	m_subExperiments[i-getRunLower()].setRunLower(i);
+	m_subExperiments[i-getRunLower()].setRunUpper(i);
+	
+	m_subExpQueue.push(new Integer(i-getRunLower()));
+      }    
+    }
+  }
+
+  /**
+   * Inform all listeners of progress
+   * @param status true if this is a status type of message
+   * @param log true if this is a log type of message
+   * @param finished true if the remote experiment has finished
+   * @param message the message.
+   */
+  private synchronized void notifyListeners(boolean status, 
+					    boolean log, 
+					    boolean finished,
+					    String message) {
+    if (m_listeners.size() > 0) {
+      for (int i=0;i<m_listeners.size();i++) {
+	RemoteExperimentListener r = 
+	  (RemoteExperimentListener)(m_listeners.elementAt(i));
+	r.remoteExperimentStatus(new RemoteExperimentEvent(status,
+							   log,
+							   finished,
+							   message));
+      }
+    } else {
+      System.err.println(message);
+    }
+  }
+
+  /**
+   * Set the abort flag
+   */
+  public void abortExperiment() {
+    m_experimentAborted = true;
+  }
+
+  /**
+   * Increment the number of successfully completed sub experiments
+   */
+  protected synchronized void incrementFinished() {
+    m_finishedCount++;
+  }
+
+  /**
+   * Increment the overall number of failures and the number of failures for
+   * a particular host
+   * @param hostNum the index of the host to increment failure count
+   */
+  protected synchronized void incrementFailed(int hostNum) {
+    m_failedCount++;
+    m_remoteHostFailureCounts[hostNum]++;
+  }
+
+  /**
+   * Push an experiment back on the queue of waiting experiments
+   * @param expNum the index of the experiment to push onto the queue
+   */
+  protected synchronized void waitingExperiment(int expNum) {
+    m_subExpQueue.push(new Integer(expNum));
+  }
+
+  /**
+   * Check to see if we have failed to connect to all hosts
+   * 
+   * @return true if failed to connect to all hosts
+   */
+  private boolean checkForAllFailedHosts() {
+    boolean allbad = true;
+    for (int i = 0; i < m_remoteHostsStatus.length; i++) {
+      if (m_remoteHostsStatus[i] != CONNECTION_FAILED) {
+	allbad = false;
+	break;
+      }
+    }
+    if (allbad) {
+      abortExperiment();
+      notifyListeners(false,true,true,"Experiment aborted! All connections "
+		      +"to remote hosts failed.");
+    }
+    return allbad;
+  }
+
+  /**
+   * Returns some post experiment information.
+   * @return a String containing some post experiment info
+   */
+  private String postExperimentInfo() {
+    StringBuffer text = new StringBuffer();
+    text.append(m_finishedCount+(m_splitByDataSet 
+				 ? " data sets" 
+				 : " runs") + " completed successfully. "
+		+m_failedCount+" failures during running.\n");
+    System.err.print(text.toString());
+    return text.toString();
+  }
+
+  /**
+   * Pushes a host back onto the queue of available hosts and attempts to
+   * launch a waiting experiment (if any).
+   * @param hostNum the index of the host to push back onto the queue of
+   * available hosts
+   */
+  protected synchronized void availableHost(int hostNum) {
+    if (hostNum >= 0) { 
+      if (m_remoteHostFailureCounts[hostNum] < MAX_FAILURES) {
+	m_remoteHostsQueue.push(new Integer(hostNum));
+      } else {
+	notifyListeners(false,true,false,"Max failures exceeded for host "
+			+((String)m_remoteHosts.elementAt(hostNum))
+			+". Removed from host list.");
+	m_removedHosts++;
+      }
+    }
+
+    // check for all sub exp complete or all hosts failed or failed count
+    // exceeded
+    if (m_failedCount == (MAX_FAILURES * m_remoteHosts.size())) {
+      abortExperiment();
+      notifyListeners(false,true,true,"Experiment aborted! Max failures "
+		      +"exceeded on all remote hosts.");
+      return;
+    }
+
+    if ((getSplitByDataSet() && 
+	 (m_baseExperiment.getDatasets().size() == m_finishedCount)) ||
+	(!getSplitByDataSet() && 
+	 ((getRunUpper() - getRunLower() + 1) == m_finishedCount))) {
+      notifyListeners(false,true,false,"Experiment completed successfully.");
+      notifyListeners(false,true,true,postExperimentInfo());
+      return;
+    }
+    
+    if (checkForAllFailedHosts()) {
+      return;
+    }
+
+    if (m_experimentAborted && 
+	(m_remoteHostsQueue.size() + m_removedHosts) == m_remoteHosts.size()) {
+      notifyListeners(false,true,true,"Experiment aborted. All remote tasks "
+		      +"finished.");
+    }
+        
+    if (!m_subExpQueue.empty() && !m_experimentAborted) {
+      if (!m_remoteHostsQueue.empty()) {
+	int availHost, waitingExp;
+	try {
+	  availHost = ((Integer)m_remoteHostsQueue.pop()).intValue();
+	  waitingExp = ((Integer)m_subExpQueue.pop()).intValue();
+	  launchNext(waitingExp, availHost);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+    }    
+  }
+
+  /**
+   * Launch a sub experiment on a remote host
+   * @param wexp the index of the sub experiment to launch
+   * @param ah the index of the available host to launch on
+   */
+  public void launchNext(final int wexp, final int ah) {
+    
+    Thread subExpThread;
+    subExpThread = new Thread() {
+	public void run() {	      
+	  m_remoteHostsStatus[ah] = IN_USE;
+	  m_subExpComplete[wexp] = TaskStatusInfo.PROCESSING;
+	  RemoteExperimentSubTask expSubTsk = new RemoteExperimentSubTask();
+	  expSubTsk.setExperiment(m_subExperiments[wexp]);
+	  String subTaskType = (getSplitByDataSet())
+	    ? "dataset :" + ((File)m_subExperiments[wexp].getDatasets().
+			     elementAt(0)).getName()
+	    : "run :" + m_subExperiments[wexp].getRunLower();
+	  try {
+	    String name = "//"
+	      +((String)m_remoteHosts.elementAt(ah))
+	      +"/RemoteEngine";
+	    Compute comp = (Compute) Naming.lookup(name);
+	    // assess the status of the sub-exp
+	    notifyListeners(false,true,false,"Starting "
+			    +subTaskType
+			    +" on host "
+			    +((String)m_remoteHosts.elementAt(ah)));
+	    Object subTaskId = comp.executeTask(expSubTsk);
+	    boolean finished = false;
+	    TaskStatusInfo is = null;
+	    while (!finished) {
+	      try {
+		Thread.sleep(2000);
+		
+		TaskStatusInfo cs = (TaskStatusInfo)comp.
+		  checkStatus(subTaskId);
+		if (cs.getExecutionStatus() == TaskStatusInfo.FINISHED) {
+		  // push host back onto queue and try launching any waiting 
+		  // sub-experiments
+		  notifyListeners(false, true, false,  cs.getStatusMessage());
+		  m_remoteHostsStatus[ah] = AVAILABLE;
+		  incrementFinished();
+		  availableHost(ah);
+		  finished = true;
+		} else if (cs.getExecutionStatus() == TaskStatusInfo.FAILED) {
+		  // a non connection related error---possibly host doesn't have
+		  // access to data sets or security policy is not set up
+		  // correctly or classifier(s) failed for some reason
+		  notifyListeners(false, true, false,  cs.getStatusMessage());
+		  m_remoteHostsStatus[ah] = SOME_OTHER_FAILURE;
+		  m_subExpComplete[wexp] = TaskStatusInfo.FAILED;
+		  notifyListeners(false,true,false,subTaskType
+				  +" "+cs.getStatusMessage()
+				  +". Scheduling for execution on another host.");
+		  incrementFailed(ah);
+		  // push experiment back onto queue
+		  waitingExperiment(wexp);	
+		  // push host back onto queue and try launching any waiting 
+		  // sub-experiments. Host is pushed back on the queue as the
+		  // failure may be temporary---eg. with InstantDB using the
+		  // RMI bridge, two or more threads may try to create the
+		  // experiment index or results table simultaneously; all but
+		  // one will throw an exception. These hosts are still usable
+		  // however.
+		  availableHost(ah);
+		  finished = true;
+		} else {
+		  if (is == null) {
+		    is = cs;
+		    notifyListeners(false, true, false, cs.getStatusMessage());
+		  } else {
+		    if (cs.getStatusMessage().
+			compareTo(is.getStatusMessage()) != 0) {
+		     
+		      notifyListeners(false, true, false,  
+				      cs.getStatusMessage());
+		    }
+		    is = cs;
+		  }  
+		}
+	      } catch (InterruptedException ie) {
+	      }
+	    }	      
+
+	  } catch (Exception ce) {
+	    m_remoteHostsStatus[ah] = CONNECTION_FAILED;
+	    m_subExpComplete[wexp] = TaskStatusInfo.TO_BE_RUN;
+	    System.err.println(ce);
+	    ce.printStackTrace();
+	    notifyListeners(false,true,false,"Connection to "
+			    +((String)m_remoteHosts.elementAt(ah))
+			    +" failed. Scheduling "
+			    +subTaskType
+			    +" for execution on another host.");
+	    checkForAllFailedHosts();
+	    waitingExperiment(wexp);
+	  } finally {
+	    if (isInterrupted()) {
+	      System.err.println("Sub exp Interupted!");
+	    }
+	  }
+	}	   
+      };
+    subExpThread.setPriority(Thread.MIN_PRIORITY);
+    subExpThread.start();
+  }
+
+  /**
+   * Overides the one in Experiment
+   * @throws Exception never throws an exception
+   */
+  public void nextIteration() throws Exception {
+
+  }
+
+  /** 
+   * overides the one in Experiment
+   */
+  public void advanceCounters() {
+
+  }
+
+  /** 
+   * overides the one in Experiment
+   */
+  public void postProcess() {
+   
+  }
+
+  /**
+   * Add a host name to the list of remote hosts
+   * @param hostname the host name to add to the list
+   */
+  public void addRemoteHost(String hostname) {
+    m_remoteHosts.addElement(hostname);
+  }
+
+  /**
+   * Get the list of remote host names
+   * @return the list of remote host names
+   */
+  public DefaultListModel getRemoteHosts() {
+    return m_remoteHosts;
+  }
+
+  /**
+   * Set the list of remote host names
+   * @param list the list of remote host names
+   */
+  public void setRemoteHosts(DefaultListModel list) {
+    m_remoteHosts = list;
+  }
+
+  /**
+   * Overides toString in Experiment
+   * @return a description of this remote experiment
+   */
+  public String toString() {
+    String result = m_baseExperiment.toString();
+
+    result += "\nRemote Hosts:\n";
+    for (int i=0;i<m_remoteHosts.size();i++) {
+      result += ((String)m_remoteHosts.elementAt(i)) +'\n';
+    }
+    return result;
+  }
+
+  /**
+   * Overides runExperiment in Experiment
+   */
+  public void runExperiment() {
+    int totalHosts = m_remoteHostsQueue.size();
+    // Try to launch sub experiments on all available hosts
+    for (int i = 0; i < totalHosts; i++) {
+      availableHost(-1);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.16 $");
+  }
+
+  /**
+   * Configures/Runs the Experiment from the command line.
+   *
+   * @param args command line arguments to the Experiment.
+   */
+  public static void main(String[] args) {
+
+    try {
+      RemoteExperiment exp = null;
+
+      // get options from XML?
+      String xmlOption = Utils.getOption("xml", args);
+      if (!xmlOption.equals(""))
+         args = new XMLOptions(xmlOption).toArray();
+      
+      Experiment base = null;
+      String expFile = Utils.getOption('l', args);
+      String saveFile = Utils.getOption('s', args);
+      boolean runExp = Utils.getFlag('r', args);
+      FastVector remoteHosts = new FastVector();
+      String runHost = " ";
+      while (runHost.length() != 0) {
+	runHost = Utils.getOption('h', args);
+	if (runHost.length() != 0) {
+	  remoteHosts.addElement(runHost);
+	}
+      }
+      if (expFile.length() == 0) {
+	base = new Experiment();
+	try {
+	  base.setOptions(args);
+	  Utils.checkForRemainingOptions(args);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	  String result = "Usage:\n\n"
+	    + "-l <exp file>\n"
+	    + "\tLoad experiment from file (default use cli options)\n"
+	    + "-s <exp file>\n"
+	    + "\tSave experiment to file after setting other options\n"
+	    + "\t(default don't save)\n"
+	    + "-h <remote host name>\n"
+	    + "\tHost to run experiment on (may be specified more than once\n"
+	    + "\tfor multiple remote hosts)\n"
+	    + "-r \n"
+	    + "\tRun experiment on (default don't run)\n"
+       + "-xml <filename | xml-string>\n"
+       + "\tget options from XML-Data instead from parameters\n"
+       + "\n";
+	  Enumeration enm = ((OptionHandler)base).listOptions();
+	  while (enm.hasMoreElements()) {
+	    Option option = (Option) enm.nextElement();
+	    result += option.synopsis() + "\n";
+	    result += option.description() + "\n";
+	  }
+	  throw new Exception(result + "\n" + ex.getMessage());
+	}
+      } else {
+         Object tmp;
+         
+         // KOML?
+         if ( (KOML.isPresent()) && (expFile.toLowerCase().endsWith(KOML.FILE_EXTENSION)) ) {
+            tmp = KOML.read(expFile);
+         }
+         else
+         // XML?
+         if (expFile.toLowerCase().endsWith(".xml")) {
+            XMLExperiment xml = new XMLExperiment(); 
+            tmp = xml.read(expFile);
+         }
+         // binary
+         else {
+            FileInputStream fi = new FileInputStream(expFile);
+            ObjectInputStream oi = new ObjectInputStream(
+                                   new BufferedInputStream(fi));
+            tmp = oi.readObject();
+            oi.close();
+         }
+	if (tmp instanceof RemoteExperiment) {
+	  exp = (RemoteExperiment)tmp;
+	} else {
+	  base = (Experiment)tmp;
+	}
+      }
+      if (base != null) {
+	exp = new RemoteExperiment(base);
+      }
+      for (int i=0;i<remoteHosts.size();i++) {
+	exp.addRemoteHost((String)remoteHosts.elementAt(i));
+      }
+      System.err.println("Experiment:\n" + exp.toString());
+
+      if (saveFile.length() != 0) {
+         // KOML?
+         if ( (KOML.isPresent()) && (saveFile.toLowerCase().endsWith(KOML.FILE_EXTENSION)) ) {
+            KOML.write(saveFile, exp);
+         }
+         else
+         // XML?
+         if (saveFile.toLowerCase().endsWith(".xml")) {
+            XMLExperiment xml = new XMLExperiment(); 
+            xml.write(saveFile, exp);
+         }
+         // binary
+         else {
+            FileOutputStream fo = new FileOutputStream(saveFile);
+            ObjectOutputStream oo = new ObjectOutputStream(
+                                    new BufferedOutputStream(fo));
+            oo.writeObject(exp);
+            oo.close();
+         }
+      }
+      
+      if (runExp) {
+	System.err.println("Initializing...");
+	exp.initialize();
+	System.err.println("Iterating...");
+	exp.runExperiment();
+	System.err.println("Postprocessing...");
+	exp.postProcess();
+      }      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentEvent.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoteExperimentEvent.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * Class encapsulating information on progress of a remote experiment
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public class RemoteExperimentEvent
+  implements Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7000867987391866451L;
+
+  /** A status type message */
+  public boolean m_statusMessage;
+
+  /** A log type message */
+  public boolean m_logMessage;
+
+  /** The message */
+  public String m_messageString;
+
+  /** True if a remote experiment has finished */
+  public boolean m_experimentFinished;
+
+  /**
+   * Constructor
+   * @param status true for status type messages
+   * @param log true for log type messages
+   * @param finished true if experiment has finished
+   * @param message the message
+   */
+  public RemoteExperimentEvent(boolean status, boolean log, boolean finished,
+			       String message) {
+    m_statusMessage = status;
+    m_logMessage = log;
+    m_experimentFinished = finished;
+    m_messageString = message;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoteExperimentListener.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+/**
+ * Interface for classes that want to listen for updates on RemoteExperiment
+ * progress
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface RemoteExperimentListener {
+
+  /**
+   * Called when progress has been made in a remote experiment
+   * @param e the event encapsulating what happened
+   */
+  void remoteExperimentStatus(RemoteExperimentEvent e);
+
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentSubTask.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentSubTask.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/RemoteExperimentSubTask.java	(revision 29)
@@ -0,0 +1,131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoteExperimentSubTask.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.File;
+
+/**
+ * Class to encapsulate an experiment as a task that can be executed on
+ * a remote host.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public class RemoteExperimentSubTask
+  implements Task, RevisionHandler {
+
+  /* Info on the task */
+  private TaskStatusInfo m_result = new TaskStatusInfo();
+  
+  /* The (sub) experiment to execute */
+  private Experiment m_experiment;
+  
+  public RemoteExperimentSubTask() {
+    m_result.setStatusMessage("Not running.");
+    m_result.setExecutionStatus(TaskStatusInfo.TO_BE_RUN);
+  }
+
+  /**
+   * Set the experiment for this sub task
+   * @param task the experiment
+   */
+  public void setExperiment(Experiment task) {
+    m_experiment = task;
+  }
+  
+  /**
+   * Get the experiment for this sub task
+   * @return this sub task's experiment
+   */
+  public Experiment getExperiment() {
+    return m_experiment;
+  }
+  
+  /**
+   * Run the experiment
+   */
+  public void execute() {
+    //      FastVector result = new FastVector();
+    m_result = new TaskStatusInfo();
+    m_result.setStatusMessage("Running...");
+    String goodResult = "(sub)experiment completed successfully";
+    String subTaskType;
+    if (m_experiment.getRunLower() != m_experiment.getRunUpper()) {
+      subTaskType = "(dataset "
+	+ ((File)m_experiment.getDatasets().elementAt(0)).getName();
+    } else {
+      subTaskType = "(exp run # "+
+	m_experiment.getRunLower();
+    }
+    try {	
+      System.err.println("Initializing " + subTaskType + ")...");
+      m_experiment.initialize();
+      System.err.println("Iterating " + subTaskType + ")...");
+      // Do not invoke runExperiment(): every exception will be lost
+      while (m_experiment.hasMoreIterations()) {
+        m_experiment.nextIteration();
+      }
+      System.err.println("Postprocessing " + subTaskType + ")...");
+      m_experiment.postProcess();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      String badResult =  "(sub)experiment " + subTaskType 
+	+ ") failed : "+ex.toString();
+      m_result.setExecutionStatus(TaskStatusInfo.FAILED);
+      //	m_result.addElement(new Integer(RemoteExperiment.FAILED));
+      //	m_result.addElement(badResult);
+      m_result.setStatusMessage(badResult);
+      m_result.setTaskResult("Failed");
+      //      return m_result;
+      return;
+    }            
+    //      m_result.addElement(new Integer(RemoteExperiment.FINISHED));
+    //      m_result.addElement(goodResult);
+    m_result.setExecutionStatus(TaskStatusInfo.FINISHED);
+    m_result.setStatusMessage(goodResult+" "+subTaskType+").");
+    m_result.setTaskResult("No errors");
+    //    return m_result;
+  }
+
+  public TaskStatusInfo getTaskStatus() {
+    return m_result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.10 $");
+  }
+}
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultListener.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResultListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+import java.io.Serializable;
+
+/**
+ * Interface for objects able to listen for results obtained
+ * by a ResultProducer
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+
+public interface ResultListener extends Serializable {
+
+  /**
+   * Determines if there are any constraints (imposed by the
+   * destination) on additional result columns to be produced by
+   * resultProducers. Null should be returned if there are NO
+   * constraints, otherwise a list of column names should be
+   * returned as an array of Strings.
+   * @param rp the ResultProducer to which the constraints will apply
+   * @return an array of column names to which resutltProducer's
+   * additional results will be restricted.
+   * @exception Exception if an error occurs
+   */
+  String [] determineColumnConstraints(ResultProducer rp) 
+    throws Exception;
+
+  /**
+   * Prepare for the results to be received.
+   *
+   * @param rp the ResultProducer that will generate the results
+   * @exception Exception if an error occurs during preprocessing.
+   */
+  void preProcess(ResultProducer rp) throws Exception;
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more results will be sent that need to be grouped together
+   * in any way.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @exception Exception if an error occurs
+   */
+  void postProcess(ResultProducer rp) throws Exception;
+  
+  /**
+   * Accepts results from a ResultProducer.
+   *
+   * @param rp the ResultProducer that generated the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @param result the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @exception Exception if the result could not be accepted.
+   */
+  void acceptResult(ResultProducer rp, Object [] key, Object [] result)
+    throws Exception;
+
+  /**
+   * Determines whether the results for a specified key must be
+   * generated.
+   *
+   * @param rp the ResultProducer wanting to generate the results
+   * @param key an array of Objects (Strings or Doubles) that uniquely
+   * identify a result for a given ResultProducer with given compatibilityState
+   * @return true if the result should be generated
+   * @exception Exception if it could not be determined if the result 
+   * is needed.
+   */
+  boolean isResultRequired(ResultProducer rp, Object [] key) 
+    throws Exception;
+
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrix.java	(revision 29)
@@ -0,0 +1,2268 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrix.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * This matrix is a container for the datasets and classifier setups and 
+ * their statistics. Derived classes output the data in different formats.
+ * Derived classes need to implement the following methods:
+ * <ul>
+ *   <li><code>toStringMatrix()</code></li>
+ *   <li><code>toStringKey()</code></li>
+ *   <li><code>toStringHeader()</code></li>
+ *   <li><code>toStringSummary()</code></li>
+ *   <li><code>toStringRanking()</code></li>
+ * </ul>
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ * @see #toStringMatrix()
+ * @see #toStringKey()
+ * @see #toStringHeader()
+ * @see #toStringSummary()
+ * @see #toStringRanking()
+ */
+public abstract class ResultMatrix
+  implements Serializable, RevisionHandler, OptionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4487179306428209739L;
+  
+  /** tie. */
+  public final static int SIGNIFICANCE_TIE = 0;
+
+  /** win. */
+  public final static int SIGNIFICANCE_WIN = 1;
+
+  /** loss. */
+  public final static int SIGNIFICANCE_LOSS = 2;
+
+  /** tie string. */
+  public String TIE_STRING = " ";
+
+  /** win string. */
+  public String WIN_STRING = "v";
+
+  /** loss string. */
+  public String LOSS_STRING = "*";
+
+  /** the left parentheses for enumerating cols/rows. */
+  public String LEFT_PARENTHESES = "(";
+
+  /** the right parentheses for enumerating cols/rows. */
+  public String RIGHT_PARENTHESES = ")";
+
+  /** the column names. */
+  protected String[] m_ColNames = null;
+
+  /** the row names. */
+  protected String[] m_RowNames = null;
+
+  /** whether a column is hidden. */
+  protected boolean[] m_ColHidden = null;
+  
+  /** whether a row is hidden. */
+  protected boolean[] m_RowHidden = null;
+  
+  /** the significance. */
+  protected int[][] m_Significance = null;
+
+  /** the values. */
+  protected double[][] m_Mean = null;
+
+  /** the standard deviation. */
+  protected double[][] m_StdDev = null;
+
+  /** the counts for the different datasets. */
+  protected double[] m_Counts = null;
+
+  /** the standard mean precision. */
+  protected int m_MeanPrec;
+
+  /** the standard std. deviation preicision. */
+  protected int m_StdDevPrec;
+
+  /** whether std. deviations are printed as well. */
+  protected boolean m_ShowStdDev;
+
+  /** whether the average for each column should be printed. */
+  protected boolean m_ShowAverage;
+  
+  /** whether the names or numbers are output as column declarations. */
+  protected boolean m_PrintColNames;
+
+  /** whether the names or numbers are output as row declarations. */
+  protected boolean m_PrintRowNames;
+
+  /** whether a "(x)" is printed before each column name with "x" as the
+   * index. */
+  protected boolean m_EnumerateColNames;
+
+  /** whether a "(x)" is printed before each row name with "x" as the index. */
+  protected boolean m_EnumerateRowNames;
+
+  /** the size of the names of the columns. */
+  protected int m_ColNameWidth;
+
+  /** the size of the names of the rows. */
+  protected int m_RowNameWidth;
+
+  /** the size of the mean columns. */
+  protected int m_MeanWidth;
+
+  /** the size of the std dev columns. */
+  protected int m_StdDevWidth;
+
+  /** the size of the significance columns. */
+  protected int m_SignificanceWidth;
+
+  /** the size of the counts. */
+  protected int m_CountWidth;
+
+  /** contains the keys for the header. */
+  protected Vector m_HeaderKeys = null;
+
+  /** contains the values for the header. */
+  protected Vector m_HeaderValues = null;
+
+  /** the non-significant wins. */
+  protected int[][] m_NonSigWins = null;
+
+  /** the significant wins. */
+  protected int[][] m_Wins = null;
+
+  /** the wins in ranking. */
+  protected int[] m_RankingWins = null;
+
+  /** the losses in ranking. */
+  protected int[] m_RankingLosses = null;
+
+  /** the difference between wins and losses. */
+  protected int[] m_RankingDiff = null;
+
+  /** the ordering of the rows. */
+  protected int[] m_RowOrder = null;
+
+  /** the ordering of the columns. */
+  protected int[] m_ColOrder = null;
+
+  /** whether to remove the filter name from the dataaset name. */
+  protected boolean m_RemoveFilterName = false;
+  
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrix() {
+    this(1, 1);
+  }
+  
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrix(int cols, int rows) {
+    setSize(cols, rows);
+    clear();
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrix(ResultMatrix matrix) {
+    assign(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public abstract String globalInfo();
+
+  /**
+   * Returns an enumeration of all the available options..
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    Vector<Option>	result;
+    
+    result = new Vector<Option>();
+    
+    result.addElement(new Option(
+        "\tThe number of decimals after the decimal point for the mean.\n"
+        + "\t(default: " + getDefaultMeanPrec() + ")",
+        "mean-prec", 1, "-mean-prec <int>"));
+    
+    result.addElement(new Option(
+        "\tThe number of decimals after the decimal point for the mean.\n"
+        + "\t(default: " + getDefaultStdDevPrec() + ")",
+        "stddev-prec", 1, "-stddev-prec <int>"));
+    
+    result.addElement(new Option(
+        "\tThe maximum width for the column names (0 = optimal).\n"
+        + "\t(default: " + getDefaultColNameWidth() + ")",
+        "col-name-width", 1, "-col-name-width <int>"));
+    
+    result.addElement(new Option(
+        "\tThe maximum width for the row names (0 = optimal).\n"
+        + "\t(default: " + getDefaultRowNameWidth() + ")",
+        "row-name-width", 1, "-row-name-width <int>"));
+    
+    result.addElement(new Option(
+        "\tThe width of the mean (0 = optimal).\n"
+        + "\t(default: " + getDefaultMeanWidth() + ")",
+        "mean-width", 1, "-mean-width <int>"));
+    
+    result.addElement(new Option(
+        "\tThe width of the standard deviation (0 = optimal).\n"
+        + "\t(default: " + getDefaultStdDevWidth() + ")",
+        "stddev-width", 1, "-stddev-width <int>"));
+    
+    result.addElement(new Option(
+        "\tThe width of the significance indicator (0 = optimal).\n"
+        + "\t(default: " + getDefaultSignificanceWidth() + ")",
+        "sig-width", 1, "-sig-width <int>"));
+    
+    result.addElement(new Option(
+        "\tThe width of the counts (0 = optimal).\n"
+        + "\t(default: " + getDefaultCountWidth() + ")",
+        "count-width", 1, "-count-width <int>"));
+    
+    result.addElement(new Option(
+        "\tWhether to display the standard deviation column.\n"
+        + "\t(default: no)",
+        "show-stddev", 0, "-show-stddev"));
+    
+    result.addElement(new Option(
+        "\tWhether to show the row with averages.\n"
+        + "\t(default: no)",
+        "show-avg", 0, "-show-avg"));
+    
+    result.addElement(new Option(
+        "\tWhether to remove the classname package prefixes from the\n"
+	+ "\tfilter names in datasets.\n"
+        + "\t(default: no)",
+        "remove-filter", 0, "-remove-filter"));
+    
+    result.addElement(new Option(
+        "\tWhether to output column names or just numbers representing them.\n"
+        + "\t(default: no)",
+        "print-col-names", 0, "-print-col-names"));
+    
+    result.addElement(new Option(
+        "\tWhether to output row names or just numbers representing them.\n"
+        + "\t(default: no)",
+        "print-row-names", 0, "-print-row-names"));
+    
+    result.addElement(new Option(
+        "\tWhether to enumerate the column names (prefixing them with \n"
+	+ "\t'(x)', with 'x' being the index).\n"
+        + "\t(default: no)",
+        "enum-col-names", 0, "-enum-col-names"));
+    
+    result.addElement(new Option(
+        "\tWhether to enumerate the row names (prefixing them with \n"
+	+ "\t'(x)', with 'x' being the index).\n"
+        + "\t(default: no)",
+        "enum-row-names", 0, "-enum-row-names"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption("mean-prec", options);
+    if (tmpStr.length() > 0)
+      setMeanPrec(Integer.parseInt(tmpStr));
+    else
+      setMeanPrec(getDefaultMeanPrec());
+    
+    tmpStr = Utils.getOption("stddev-prec", options);
+    if (tmpStr.length() > 0)
+      setStdDevPrec(Integer.parseInt(tmpStr));
+    else
+      setStdDevPrec(getDefaultStdDevPrec());
+    
+    tmpStr = Utils.getOption("col-name-width", options);
+    if (tmpStr.length() > 0)
+      setColNameWidth(Integer.parseInt(tmpStr));
+    else
+      setColNameWidth(getDefaultColNameWidth());
+    
+    tmpStr = Utils.getOption("row-name-width", options);
+    if (tmpStr.length() > 0)
+      setRowNameWidth(Integer.parseInt(tmpStr));
+    else
+      setRowNameWidth(getDefaultRowNameWidth());
+    
+    tmpStr = Utils.getOption("mean-width", options);
+    if (tmpStr.length() > 0)
+      setMeanWidth(Integer.parseInt(tmpStr));
+    else
+      setMeanWidth(getDefaultMeanWidth());
+    
+    tmpStr = Utils.getOption("stddev-width", options);
+    if (tmpStr.length() > 0)
+      setStdDevWidth(Integer.parseInt(tmpStr));
+    else
+      setStdDevWidth(getDefaultStdDevWidth());
+    
+    tmpStr = Utils.getOption("sig-width", options);
+    if (tmpStr.length() > 0)
+      setSignificanceWidth(Integer.parseInt(tmpStr));
+    else
+      setSignificanceWidth(getDefaultSignificanceWidth());
+    
+    tmpStr = Utils.getOption("count-width", options);
+    if (tmpStr.length() > 0)
+      setStdDevPrec(Integer.parseInt(tmpStr));
+    else
+      setStdDevPrec(getDefaultCountWidth());
+
+    setShowStdDev(Utils.getFlag("show-stddev", options));
+
+    setShowAverage(Utils.getFlag("show-avg", options));
+
+    setRemoveFilterName(Utils.getFlag("remove-filter", options));
+
+    setPrintColNames(Utils.getFlag("print-col-names", options));
+
+    setPrintRowNames(Utils.getFlag("print-row-names", options));
+
+    setEnumerateColNames(Utils.getFlag("enum-col-names", options));
+
+    setEnumerateRowNames(Utils.getFlag("enum-row-names", options));
+  }
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    result.add("-mean-prec");
+    result.add("" + getMeanPrec());
+    
+    result.add("-stddev-prec");
+    result.add("" + getStdDevPrec());
+    
+    result.add("-col-name-width");
+    result.add("" + getColNameWidth());
+    
+    result.add("-row-name-width");
+    result.add("" + getRowNameWidth());
+    
+    result.add("-mean-width");
+    result.add("" + getMeanWidth());
+    
+    result.add("-stddev-width");
+    result.add("" + getStdDevWidth());
+    
+    result.add("-sig-width");
+    result.add("" + getSignificanceWidth());
+    
+    result.add("-count-width");
+    result.add("" + getCountWidth());
+
+    if (getShowStdDev())
+      result.add("-show-stddev");
+
+    if (getShowAverage())
+      result.add("-show-avg");
+
+    if (getRemoveFilterName())
+      result.add("-remove-filter");
+
+    if (getPrintColNames())
+      result.add("-print-col-names");
+
+    if (getPrintRowNames())
+      result.add("-print-row-names");
+
+    if (getEnumerateColNames())
+      result.add("-enum-col-names");
+
+    if (getEnumerateRowNames())
+      result.add("-enum-row-names");
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public abstract String getDisplayName();
+
+  /**
+   * acquires the data from the given matrix.
+   * 
+   * @param matrix	the matrix to get the data from
+   */
+  public void assign(ResultMatrix matrix) {
+    int         i;
+    int         n;
+    
+    setSize(matrix.getColCount(), matrix.getRowCount());
+    
+    // output parameters
+    TIE_STRING          = matrix.TIE_STRING;
+    WIN_STRING          = matrix.WIN_STRING;
+    LOSS_STRING         = matrix.LOSS_STRING;
+    LEFT_PARENTHESES    = matrix.LEFT_PARENTHESES;
+    RIGHT_PARENTHESES   = matrix.RIGHT_PARENTHESES;
+    m_MeanPrec          = matrix.m_MeanPrec;
+    m_StdDevPrec        = matrix.m_StdDevPrec;
+    m_ShowStdDev        = matrix.m_ShowStdDev;
+    m_ShowAverage       = matrix.m_ShowAverage;
+    m_PrintColNames     = matrix.m_PrintColNames;
+    m_PrintRowNames     = matrix.m_PrintRowNames;
+    m_EnumerateColNames = matrix.m_EnumerateColNames;
+    m_EnumerateRowNames = matrix.m_EnumerateRowNames;
+    m_RowNameWidth      = matrix.m_RowNameWidth;
+    m_MeanWidth         = matrix.m_MeanWidth;
+    m_StdDevWidth       = matrix.m_StdDevWidth;
+    m_SignificanceWidth = matrix.m_SignificanceWidth;
+    m_CountWidth        = matrix.m_CountWidth;
+    m_RemoveFilterName  = matrix.m_RemoveFilterName;
+    
+    // header
+    m_HeaderKeys   = (Vector) matrix.m_HeaderKeys.clone();
+    m_HeaderValues = (Vector) matrix.m_HeaderValues.clone();
+
+    // matrix
+    for (i = 0; i < matrix.m_Mean.length; i++) {
+      for (n = 0; n < matrix.m_Mean[i].length; n++) {
+        m_Mean[i][n]         = matrix.m_Mean[i][n];
+        m_StdDev[i][n]       = matrix.m_StdDev[i][n];
+        m_Significance[i][n] = matrix.m_Significance[i][n];
+      }
+    }
+
+    for (i = 0; i < matrix.m_ColNames.length; i++) {
+      m_ColNames[i]  = matrix.m_ColNames[i];
+      m_ColHidden[i] = matrix.m_ColHidden[i];
+    }
+
+    for (i = 0; i < matrix.m_RowNames.length; i++) {
+      m_RowNames[i]  = matrix.m_RowNames[i];
+      m_RowHidden[i] = matrix.m_RowHidden[i];
+    }
+
+    for (i = 0; i < matrix.m_Counts.length; i++)
+      m_Counts[i] = matrix.m_Counts[i];
+
+    // summary
+    if (matrix.m_NonSigWins != null) {
+      m_NonSigWins = new int[matrix.m_NonSigWins.length][];
+      m_Wins       = new int[matrix.m_NonSigWins.length][];
+      for (i = 0; i < matrix.m_NonSigWins.length; i++) {
+        m_NonSigWins[i] = new int[matrix.m_NonSigWins[i].length];
+        m_Wins[i]       = new int[matrix.m_NonSigWins[i].length];
+
+        for (n = 0; n < matrix.m_NonSigWins[i].length; n++) {
+          m_NonSigWins[i][n] = matrix.m_NonSigWins[i][n];
+          m_Wins[i][n]       = matrix.m_Wins[i][n];
+        }
+      }
+    }
+
+    // ranking
+    if (matrix.m_RankingWins != null) {
+      m_RankingWins   = new int[matrix.m_RankingWins.length];
+      m_RankingLosses = new int[matrix.m_RankingWins.length];
+      m_RankingDiff   = new int[matrix.m_RankingWins.length];
+      for (i = 0; i < matrix.m_RankingWins.length; i++) {
+        m_RankingWins[i]   = matrix.m_RankingWins[i];
+        m_RankingLosses[i] = matrix.m_RankingLosses[i];
+        m_RankingDiff[i]   = matrix.m_RankingDiff[i];
+      }
+    }
+  }
+
+  /**
+   * removes the stored data and the ordering, but retains the dimensions of
+   * the matrix.
+   */
+  public void clear() {
+    m_MeanPrec          = getDefaultMeanPrec();
+    m_StdDevPrec        = getDefaultStdDevPrec();
+    m_ShowStdDev        = getDefaultShowStdDev();
+    m_ShowAverage       = getDefaultShowAverage();
+    m_RemoveFilterName  = getDefaultRemoveFilterName();
+    m_PrintColNames     = getDefaultPrintColNames();
+    m_PrintRowNames     = getDefaultPrintRowNames();
+    m_EnumerateColNames = getDefaultEnumerateColNames();
+    m_EnumerateRowNames = getDefaultEnumerateRowNames();
+    m_RowNameWidth      = getDefaultRowNameWidth();
+    m_ColNameWidth      = getDefaultColNameWidth();
+    m_MeanWidth         = getDefaultMeanWidth();
+    m_StdDevWidth       = getDefaultStdDevWidth();
+    m_SignificanceWidth = getDefaultSignificanceWidth();
+    m_CountWidth        = getDefaultCountWidth();
+
+    setSize(getColCount(), getRowCount());
+  }
+
+  /**
+   * clears the content of the matrix and sets the new size.
+   * 
+   * @param cols        the number of mean columns
+   * @param rows        the number of mean rows
+   */
+  public void setSize(int cols, int rows) {
+    int       i;
+    int       n;
+
+    m_ColNames     = new String[cols];
+    m_RowNames     = new String[rows];
+    m_Counts       = new double[rows];
+    m_ColHidden    = new boolean[cols];
+    m_RowHidden    = new boolean[rows];
+    m_Mean         = new double[rows][cols];
+    m_Significance = new int[rows][cols];
+    m_StdDev       = new double[rows][cols];
+    m_ColOrder     = null;
+    m_RowOrder     = null;
+
+    // NaN means that there exists no value! -> toArray()
+    for (i = 0; i < m_Mean.length; i++) {
+      for (n = 0; n < m_Mean[i].length; n++)
+        m_Mean[i][n]   = Double.NaN;
+    }
+
+    for (i = 0; i < m_ColNames.length; i++)
+      m_ColNames[i] = "col" + i;
+    for (i = 0; i < m_RowNames.length; i++)
+      m_RowNames[i] = "row" + i;
+
+    clearHeader();
+    clearSummary();
+    clearRanking();
+  }
+
+  /**
+   * sets the precision for the means.
+   * 
+   * @param prec	the number of decimals
+   */
+  public void setMeanPrec(int prec) {
+    if (prec >= 0)
+      m_MeanPrec = prec;
+  }
+
+  /**
+   * returns the current precision for the means.
+   * 
+   * @return		the number of decimals
+   */
+  public int getMeanPrec() {
+    return m_MeanPrec;
+  }
+
+  /**
+   * returns the default precision for the means.
+   * 
+   * @return		the number of decimals
+   */
+  public int getDefaultMeanPrec() {
+    return 2;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String meanPrecTipText() {
+    return "The number of decimals after the decimal point for the mean.";
+  }
+
+  /**
+   * sets the precision for the standard deviation.
+   * 
+   * @param prec	the number of decimals
+   */
+  public void setStdDevPrec(int prec) {
+    if (prec >= 0)
+      m_StdDevPrec = prec;
+  }
+
+  /**
+   * returns the current standard deviation precision.
+   * 
+   * @return		the number of decimals
+   */
+  public int getStdDevPrec() {
+    return m_StdDevPrec;
+  }
+
+  /**
+   * returns the default standard deviation precision.
+   * 
+   * @return		the number of decimals
+   */
+  public int getDefaultStdDevPrec() {
+    return 2;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String stdDevPrecTipText() {
+    return "The number of decimals after the decimal point for the standard deviation.";
+  }
+
+  /**
+   * sets the width for the column names (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setColNameWidth(int width) {
+    if (width >= 0)
+      m_ColNameWidth = width;
+  }
+
+  /**
+   * returns the current width for the column names.
+   * 
+   * @return		the width
+   */
+  public int getColNameWidth() {
+    return m_ColNameWidth;
+  }
+
+  /**
+   * returns the default width for the column names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultColNameWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String colNameWidthTipText() {
+    return "The maximum width of the column names (0 = optimal).";
+  }
+
+  /**
+   * sets the width for the row names (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setRowNameWidth(int width) {
+    if (width >= 0)
+      m_RowNameWidth = width;
+  }
+
+  /**
+   * returns the current width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getRowNameWidth() {
+    return m_RowNameWidth;
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String rowNameWidthTipText() {
+    return "The maximum width for the row names (0 = optimal).";
+  }
+  
+  /**
+   * sets the width for the mean (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setMeanWidth(int width) {
+    if (width >= 0)
+      m_MeanWidth = width;
+  }
+
+  /**
+   * returns the current width for the mean.
+   * 
+   * @return		the width
+   */
+  public int getMeanWidth() {
+    return m_MeanWidth;
+  }
+
+  /**
+   * returns the default width for the mean.
+   * 
+   * @return		the width
+   */
+  public int getDefaultMeanWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String meanWidthTipText() {
+    return "The width of the mean (0 = optimal).";
+  }
+
+  /**
+   * sets the width for the std dev (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setStdDevWidth(int width) {
+    if (width >= 0)
+      m_StdDevWidth = width;
+  }
+
+  /**
+   * returns the current width for the std dev.
+   * 
+   * @return		the width
+   */
+  public int getStdDevWidth() {
+    return m_StdDevWidth;
+  }
+
+  /**
+   * returns the default width for the std dev.
+   * 
+   * @return		the width
+   */
+  public int getDefaultStdDevWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String stdDevWidthTipText() {
+    return "The width of the standard deviation (0 = optimal).";
+  }
+
+  /**
+   * sets the width for the significance (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setSignificanceWidth(int width) {
+    if (width >= 0)
+      m_SignificanceWidth = width;
+  }
+
+  /**
+   * returns the current width for the significance.
+   * 
+   * @return		the width
+   */
+  public int getSignificanceWidth() {
+    return m_SignificanceWidth;
+  }
+
+  /**
+   * returns the default width for the significance.
+   * 
+   * @return		the width
+   */
+  public int getDefaultSignificanceWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String significanceWidthTipText() {
+    return "The width of the significance indicator (0 = optimal).";
+  }
+
+  /**
+   * sets the width for the counts (0 = optimal).
+   * 
+   * @param width	the width
+   */
+  public void setCountWidth(int width) {
+    if (width >= 0)
+      m_CountWidth = width;
+  }
+
+  /**
+   * returns the current width for the counts.
+   * 
+   * @return		the width
+   */
+  public int getCountWidth() {
+    return m_CountWidth;
+  }
+
+  /**
+   * returns the default width for the counts.
+   * 
+   * @return		the width
+   */
+  public int getDefaultCountWidth() {
+    return 0;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String countWidthTipText() {
+    return "The width of the counts (0 = optimal).";
+  }
+
+  /**
+   * sets whether to display the std deviations or not.
+   * 
+   * @param show	if true then the std deviations are displayed
+   */
+  public void setShowStdDev(boolean show) {
+    m_ShowStdDev = show;
+  }
+
+  /**
+   * returns whether std deviations are displayed or not.
+   * 
+   * @return		true if the std deviations are displayed
+   */
+  public boolean getShowStdDev() {
+    return m_ShowStdDev;
+  }
+
+  /**
+   * returns the default of whether std deviations are displayed or not.
+   * 
+   * @return		true if the std deviations are displayed
+   */
+  public boolean getDefaultShowStdDev() {
+    return false;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String showStdDevTipText() {
+    return "Whether to display the standard deviation column.";
+  }
+
+  /**
+   * sets whether to display the average per column or not.
+   * 
+   * @param show	if true then the average is displayed
+   */
+  public void setShowAverage(boolean show) {
+    m_ShowAverage = show;
+  }
+
+  /**
+   * returns whether average per column is displayed or not.
+   * 
+   * @return		true if the average is displayed
+   */
+  public boolean getShowAverage() {
+    return m_ShowAverage;
+  }
+
+  /**
+   * returns the default of whether average per column is displayed or not.
+   * 
+   * @return		true if the average is displayed
+   */
+  public boolean getDefaultShowAverage() {
+    return false;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String showAverageTipText() {
+    return "Whether to show the row with averages.";
+  }
+
+  /**
+   * sets whether to remove the filter classname from the dataset name.
+   * 
+   * @param remove	if true then the filter classnames are shortened
+   */
+  public void setRemoveFilterName(boolean remove) {
+    m_RemoveFilterName = remove;
+  }
+
+  /**
+   * returns whether the filter classname is removed from the dataset name.
+   * 
+   * @return		true if the filter classnames are shortened
+   */
+  public boolean getRemoveFilterName() {
+    return m_RemoveFilterName;
+  }
+
+  /**
+   * returns the default of whether the filter classname is removed from the 
+   * dataset name.
+   * 
+   * @return		true if the filter classnames are shortened
+   */
+  public boolean getDefaultRemoveFilterName() {
+    return false;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String removeFilterNameTipText() {
+    return "Whether to remove the classname package prefixes from the filter names in datasets.";
+  }
+
+  /**
+   * sets whether the column names or numbers instead are printed.
+   * deactivating automatically sets m_EnumerateColNames to TRUE.
+   * 
+   * @param print	if true then the names are printed instead of numbers
+   * @see 		#setEnumerateColNames(boolean)
+   */
+  public void setPrintColNames(boolean print) {
+    m_PrintColNames = print;
+    if (!print)
+      setEnumerateColNames(true);
+  }
+
+  /**
+   * returns whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getPrintColNames() {
+    return m_PrintColNames;
+  }
+
+  /**
+   * returns the default of whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintColNames() {
+    return true;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String printColNamesTipText() {
+    return "Whether to output column names or just numbers representing them.";
+  }
+
+  /**
+   * sets whether the row names or numbers instead are printed
+   * deactivating automatically sets m_EnumerateColNames to TRUE.
+   * 
+   * @param print	if true then names instead of numbers are printed
+   * @see 		#setEnumerateRowNames(boolean)
+   */
+  public void setPrintRowNames(boolean print) {
+    m_PrintRowNames = print;
+    if (!print)
+      setEnumerateRowNames(true);
+  }
+
+  /**
+   * returns whether row names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getPrintRowNames() {
+    return m_PrintRowNames;
+  }
+
+  /**
+   * returns the default of whether row names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintRowNames() {
+    return true;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String printRowNamesTipText() {
+    return "Whether to output row names or just numbers representing them.";
+  }
+
+  /**
+   * sets whether the column names are prefixed with "(x)" where "x" is
+   * the index.
+   * 
+   * @param enumerate	if true then the names are prefixed
+   */
+  public void setEnumerateColNames(boolean enumerate) {
+    m_EnumerateColNames = enumerate;
+  }
+
+  /**
+   * returns whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getEnumerateColNames() {
+    return m_EnumerateColNames;
+  }
+
+  /**
+   * returns the default of whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateColNames() {
+    return true;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String enumerateColNamesTipText() {
+    return "Whether to enumerate the column names (prefixing them with '(x)', with 'x' being the index).";
+  }
+
+  /**
+   * sets whether to the row names are prefixed with the index.
+   * 
+   * @param enumerate	if true then the names will be prefixed
+   */
+  public void setEnumerateRowNames(boolean enumerate) {
+    m_EnumerateRowNames = enumerate;
+  }
+
+  /**
+   * returns whether row names or prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getEnumerateRowNames() {
+    return m_EnumerateRowNames;
+  }
+
+  /**
+   * returns theh default of whether row names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateRowNames() {
+    return false;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String enumerateRowNamesTipText() {
+    return "Whether to enumerate the row names (prefixing them with '(x)', with 'x' being the index).";
+  }
+
+  /**
+   * returns the number of columns.
+   * 
+   * @return		the number of columns
+   */
+  public int getColCount() {
+    return m_ColNames.length;
+  }
+
+  /**
+   * returns the number of visible columns.
+   * 
+   * @return		the number of columns
+   */
+  public int getVisibleColCount() {
+    int         cols;
+    int         i;
+    
+    cols = 0;
+    for (i = 0; i < getColCount(); i++) {
+      if (!getColHidden(i))
+        cols++;
+    }
+
+    return cols;
+  }
+
+  /**
+   * returns the number of rows.
+   * 
+   * @return		the number of rows
+   */
+  public int getRowCount() {
+    return m_RowNames.length;
+  }
+
+  /**
+   * returns the number of visible rows.
+   * 
+   * @return		the number of rows
+   */
+  public int getVisibleRowCount() {
+    int         rows;
+    int         i;
+    
+    rows= 0;
+    for (i = 0; i < getRowCount(); i++) {
+      if (!getRowHidden(i))
+        rows++;
+    }
+
+    return rows;
+  }
+  
+  /**
+   * sets the name of the column (if the index is valid).
+   * 
+   * @param index	the index of the column
+   * @param name	the name of the column
+   */
+  public void setColName(int index, String name) {
+    if ( (index >= 0) && (index < getColCount()) )
+      m_ColNames[index] = name;
+  }
+
+  /**
+   * returns the name of the row, if the index is valid, otherwise null.
+   * if getPrintColNames() is FALSE then an empty string is returned or if
+   * getEnumerateColNames() is TRUE then the 1-based index surrounded by
+   * parentheses.
+   * 
+   * @param index	the index of the column
+   * @return		the name of the column
+   * @see 		#setPrintColNames(boolean)
+   * @see 		#getPrintColNames()
+   * @see 		#setEnumerateColNames(boolean)
+   * @see 		#getEnumerateColNames()
+   */
+  public String getColName(int index) {
+    String        result;
+    
+    result = null;
+    
+    if ( (index >= 0) && (index < getColCount()) ) {
+      if (getPrintColNames())
+        result = m_ColNames[index];
+      else
+        result = "";
+
+      if (getEnumerateColNames()) {
+        result =   LEFT_PARENTHESES 
+                 + Integer.toString(index + 1) 
+                 + RIGHT_PARENTHESES
+                 + " " + result;
+        result = result.trim();
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * sets the name of the row (if the index is valid).
+   * 
+   * @param index	the index of the row
+   * @param name	the name of the row
+   */
+  public void setRowName(int index, String name) {
+    if ( (index >= 0) && (index < getRowCount()) )
+      m_RowNames[index] = name;
+  }
+
+  /**
+   * returns the name of the row, if the index is valid, otherwise null.
+   * if getPrintRowNames() is FALSE then an empty string is returned or if
+   * getEnumerateRowNames() is TRUE then the 1-based index surrounded by
+   * parentheses.
+   * 
+   * @param index	the index of the row
+   * @return		the name of the row
+   * @see 		#setPrintRowNames(boolean)
+   * @see 		#getPrintRowNames()
+   * @see 		#setEnumerateRowNames(boolean)
+   * @see 		#getEnumerateRowNames()
+   */
+  public String getRowName(int index) {
+    String        result;
+    
+    result = null;
+    
+    if ( (index >= 0) && (index < getRowCount()) ) {
+      if (getPrintRowNames())
+        result = m_RowNames[index];
+      else
+        result = "";
+
+      if (getEnumerateRowNames()) {
+        result =   LEFT_PARENTHESES 
+                 + Integer.toString(index + 1) 
+                 + RIGHT_PARENTHESES
+                 + " " + result;
+        result = result.trim();
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * sets the hidden status of the column (if the index is valid).
+   * 
+   * @param index	the index of the column
+   * @param hidden	the hidden status of the column
+   */
+  public void setColHidden(int index, boolean hidden) {
+    if ( (index >= 0) && (index < getColCount()) )
+      m_ColHidden[index] = hidden;
+  }
+
+  /**
+   * returns the hidden status of the column, if the index is valid, otherwise
+   * false.
+   * 
+   * @param index	the index of the column
+   * @return		true if hidden
+   */
+  public boolean getColHidden(int index) {
+    if ( (index >= 0) && (index < getColCount()) )
+      return m_ColHidden[index];
+    else
+      return false;
+  }
+
+  /**
+   * sets the hidden status of the row (if the index is valid).
+   * 
+   * @param index	the index of the row
+   * @param hidden	the hidden status of the row
+   */
+  public void setRowHidden(int index, boolean hidden) {
+    if ( (index >= 0) && (index < getRowCount()) )
+      m_RowHidden[index] = hidden;
+  }
+
+  /**
+   * returns the hidden status of the row, if the index is valid, otherwise
+   * false.
+   * 
+   * @param index	the index of the row
+   * @return		true if hidden
+   */
+  public boolean getRowHidden(int index) {
+    if ( (index >= 0) && (index < getRowCount()) )
+      return m_RowHidden[index];
+    else
+      return false;
+  }
+
+  /**
+   * sets the count for the row (if the index is valid).
+   * 
+   * @param index	the index of the row
+   * @param count	the count for the row
+   */
+  public void setCount(int index, double count) {
+    if ( (index >= 0) && (index < getRowCount()) )
+      m_Counts[index] = count;
+  }
+
+  /**
+   * returns the count for the row. if the index is invalid then 0.
+   * 
+   * @param index	the index of the row
+   * @return		the count for the row
+   */
+  public double getCount(int index) {
+    if ( (index >= 0) && (index < getRowCount()) )
+      return m_Counts[index];
+    else
+      return 0;
+  }
+
+  /**
+   * sets the mean at the given position (if the position is valid).
+   * 
+   * @param col		the column of the mean
+   * @param row		the row of the mean
+   * @param value	the value of the mean
+   */
+  public void setMean(int col, int row, double value) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      m_Mean[row][col] = value;
+  }
+
+  /**
+   * returns the mean at the given position, if the position is valid,
+   * otherwise 0.
+   * 
+   * @param col		the column index
+   * @param row		the row index
+   * @return		the mean
+   */
+  public double getMean(int col, int row) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      return m_Mean[row][col];
+    else
+      return 0;
+  }
+
+  /**
+   * returns the average of the mean at the given position, if the position is
+   * valid, otherwise 0.
+   * 
+   * @param col		the column index
+   * @return		the average
+   */
+  public double getAverage(int col) {
+    int       i;
+    double    avg;
+    int       count;
+
+    if ( (col >= 0) && (col < getColCount()) ) {
+      avg   = 0;
+      count = 0;
+
+      for (i = 0; i < getRowCount(); i++) {
+        if (!Double.isNaN(getMean(col, i))) {
+          avg += getMean(col, i);
+          count++;
+        }
+      }
+      
+      return avg / (double) count;
+    }
+    else {
+      return 0;
+    }
+  }
+
+  /**
+   * sets the std deviation at the given position (if the position is valid).
+   * 
+   * @param col		the column of the std. deviation
+   * @param row		the row of the std deviation
+   * @param value	the value of the std deviation
+   */
+  public void setStdDev(int col, int row, double value) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      m_StdDev[row][col] = value;
+  }
+
+  /**
+   * returns the std deviation at the given position, if the position is valid,
+   * otherwise 0.
+   * 
+   * @param col		the column index
+   * @param row		the row index
+   * @return		the std deviation
+   */
+  public double getStdDev(int col, int row) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      return m_StdDev[row][col];
+    else
+      return 0;
+  }
+
+  /**
+   * sets the significance at the given position (if the position is valid).
+   * 
+   * @param col		the column of the significance
+   * @param row		the row of the significance
+   * @param value	the value of the significance
+   */
+  public void setSignificance(int col, int row, int value) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      m_Significance[row][col] = value;
+  }
+
+  /**
+   * returns the significance at the given position, if the position is valid,
+   * otherwise SIGNIFICANCE_ATIE.
+   * 
+   * @param col		the column index
+   * @param row		the row index
+   * @return		the indicator
+   */
+  public int getSignificance(int col, int row) {
+    if (    (col >= 0) && (col < getColCount()) 
+         && (row >= 0) && (row < getRowCount()) )
+      return m_Significance[row][col];
+    else
+      return SIGNIFICANCE_TIE;
+  }
+
+  /**
+   * counts the occurrences of the given significance type in the given
+   * column.
+   * 
+   * @param col		the columnn to gather the information from
+   * @param type	the significance type, WIN/TIE/LOSS
+   * @return		the count
+   */
+  public int getSignificanceCount(int col, int type) {
+    int       result;
+    int       i;
+
+    result = 0;
+
+    if ( (col >= 0) && (col < getColCount()) ) {
+      for (i = 0; i < getRowCount(); i++) {
+        if (getRowHidden(i))
+          continue;
+
+        // no value?
+        if (Double.isNaN(getMean(col, i)))
+          continue;
+
+        if (getSignificance(col, i) == type)
+          result++;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * sets the ordering of the rows, null means default.
+   * 
+   * @param order	the new order of the rows
+   */
+  public void setRowOrder(int[] order) {
+    int         i;
+    
+    // default order?
+    if (order == null) {
+      m_RowOrder = null;
+    }
+    else {
+      if (order.length == getRowCount()) {
+        m_RowOrder = new int[order.length];
+        for (i = 0; i < order.length; i++)
+          m_RowOrder[i] = order[i];
+      }
+      else {
+        System.err.println("setRowOrder: length does not match (" 
+            + order.length + " <> " + getRowCount() + ") - ignored!");
+      }
+    }
+  }
+
+  /**
+   * returns the current order of the rows, null means the default order.
+   * 
+   * @return		the current order of the rows
+   */
+  public int[] getRowOrder() {
+    return m_RowOrder;
+  }
+
+  /**
+   * returns the displayed index of the given row, depending on the order of
+   * rows, returns -1 if index out of bounds.
+   * 
+   * @param index	the row to get the displayed index for
+   * @return		the real index of the row
+   */
+  public int getDisplayRow(int index) {
+    if ( (index >= 0) && (index < getRowCount()) ) {
+      if (getRowOrder() == null)
+        return index;
+      else
+        return getRowOrder()[index];
+    }
+    else {
+      return -1;
+    }
+  }
+
+  /**
+   * sets the ordering of the columns, null means default.
+   * 
+   * @param order	the new order of the columns
+   */
+  public void setColOrder(int[] order) {
+    int         i;
+    
+    // default order?
+    if (order == null) {
+      m_ColOrder = null;
+    }
+    else {
+      if (order.length == getColCount()) {
+        m_ColOrder = new int[order.length];
+        for (i = 0; i < order.length; i++)
+          m_ColOrder[i] = order[i];
+      }
+      else {
+        System.err.println("setColOrder: length does not match (" 
+            + order.length + " <> " + getColCount() + ") - ignored!");
+      }
+    }
+  }
+
+  /**
+   * returns the current order of the columns, null means the default order.
+   * 
+   * @return		the current order of the columns
+   */
+  public int[] getColOrder() {
+    return m_ColOrder;
+  }
+
+  /**
+   * returns the displayed index of the given col, depending on the order of
+   * columns, returns -1 if index out of bounds.
+   * 
+   * @param index	the column to get the displayed index for
+   * @return		the real index of the column
+   */
+  public int getDisplayCol(int index) {
+    if ( (index >= 0) && (index < getColCount()) ) {
+      if (getColOrder() == null)
+        return index;
+      else
+        return getColOrder()[index];
+    }
+    else {
+      return -1;
+    }
+  }
+
+  /**
+   * returns the given number as string rounded to the given number of
+   * decimals. additional necessary 0's are added.
+   * 
+   * @param d		the number to format
+   * @param prec	the number of decimals after the point
+   * @return		the formatted number
+   */
+  protected String doubleToString(double d, int prec) {
+    String        result;
+    int           currentPrec;
+    int           i;
+
+    result = Utils.doubleToString(d, prec);
+
+    // decimal point?
+    if (result.indexOf(".") == -1)
+      result += ".";
+    
+    // precision so far?
+    currentPrec = result.length() - result.indexOf(".") - 1;
+    for (i = currentPrec; i < prec; i++)
+      result += "0";
+    
+    return result;
+  }
+
+  /**
+   * trims the given string down to the given length if longer, otherwise
+   * leaves it unchanged. a length of "0" leaves the string always 
+   * unchanged.
+   * 
+   * @param s		the string to trim (if too long)
+   * @param length	the max. length (0 means infinity)
+   * @return		the trimmed string
+   */
+  protected String trimString(String s, int length) {
+    if ( (length > 0) && (s.length() > length) )
+      return s.substring(0, length);
+    else
+      return s;
+  }
+
+  /**
+   * pads the given string on the right until it reaches the given length, if
+   * longer cuts it down. if length is 0 then nothing is done.
+   * 
+   * @param s		the string to pad
+   * @param length	the max. length of the string
+   * @return		the padded string
+   */
+  protected String padString(String s, int length) {
+    return padString(s, length, false);
+  }
+
+  /**
+   * pads the given string until it reaches the given length, if longer cuts
+   * it down. if length is 0 then nothing is done.
+   * 
+   * @param s		the string to pad
+   * @param length	the max. length of the string
+   * @param left	whether to pad left or right
+   * @return		the padded string
+   */
+  protected String padString(String s, int length, boolean left) {
+    String      result;
+    int         i;
+
+    result = s;
+
+    // pad with blanks
+    for (i = s.length(); i < length; i++) {
+      if (left)
+        result = " " + result;
+      else
+        result = result + " ";
+    }
+      
+    // too long?
+    if ( (length > 0) && (result.length() > length) )
+      result = result.substring(0, length);
+
+    return result;
+  }
+
+  /**
+   * returns the length of the longest cell in the given column.
+   * 
+   * @param data	the data to base the calculation on
+   * @param col		the column to check
+   * @return		the maximum length
+   */
+  protected int getColSize(String[][] data, int col) {
+    return getColSize(data, col, false, false);
+  }
+
+  /**
+   * returns the length of the longest cell in the given column.
+   * 
+   * @param data	the data to base the calculation on
+   * @param col		the column to check
+   * @param skipFirst	whether to skip the first row
+   * @param skipLast	whether to skip the last row
+   * @return		the maximum length
+   */
+  protected int getColSize( String[][] data, int col, 
+                            boolean skipFirst, boolean skipLast ) {
+    int       result;
+    int       i;
+
+    result = 0;
+
+    if ( (col >= 0) && (col < data[0].length) ) {
+      for (i = 0; i < data.length; i++) {
+        // skip first?
+        if ( (i == 0) && (skipFirst) )
+          continue;
+
+        // skip last?
+        if ( (i == data.length - 1) && (skipLast) )
+          continue;
+        
+        if (data[i][col].length() > result)
+          result = data[i][col].length();
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * removes the filter classname from the given string if it should be 
+   * removed, otherwise leaves the string alone.
+   * 
+   * @param s		the string to process
+   * @return		the processed string
+   * @see		#getRemoveFilterName()
+   */
+  protected String removeFilterName(String s) {
+    if (getRemoveFilterName())
+      return s.replaceAll("-weka\\.filters\\..*", "")
+              .replaceAll("-unsupervised\\..*",   "")
+              .replaceAll("-supervised\\..*",     "");
+    else
+      return s;
+  }
+
+  /**
+   * returns a 2-dimensional array with the prepared data. includes the column
+   * and row names. hidden cols/rows are already excluded. <br>
+   * first row: column names<br>
+   * last  row: wins/ties/losses<br>
+   * first col: row names<br>
+   * 
+   * @return		the generated array
+   */
+  protected String[][] toArray() {
+    int               i;
+    int               n;
+    int               ii;
+    int               nn;
+    int               x;
+    int               y;
+    String[][]        result;
+    String[][]        tmpResult;
+    int               cols;
+    int               rows;
+    boolean           valueExists;
+
+    // determine visible cols/rows
+    rows = getVisibleRowCount();
+    if (getShowAverage())
+      rows++;
+    cols = getVisibleColCount();
+    if (getShowStdDev())
+      cols = cols*3;   // mean + stddev + sign.
+    else
+      cols = cols*2;   // mean + stddev
+
+    result = new String[rows + 2][cols + 1];
+
+    // col names
+    result[0][0] = trimString("Dataset", getRowNameWidth());
+    x = 1;
+    for (ii = 0; ii < getColCount(); ii++) {
+      i = getDisplayCol(ii);
+      if (getColHidden(i))
+        continue;
+      
+      result[0][x] = trimString(
+          removeFilterName(getColName(i)), getColNameWidth());
+      x++;
+      // std dev
+      if (getShowStdDev()) {
+        result[0][x] = "";
+        x++;
+      }
+      // sign.
+      result[0][x] = "";
+      x++;
+    }
+
+    // row names
+    y = 1;
+    for (ii = 0; ii < getRowCount(); ii++) {
+      i = getDisplayRow(ii);
+      if (!getRowHidden(i)) {
+        result[y][0] = trimString(
+            removeFilterName(getRowName(i)), getRowNameWidth());
+        y++;
+      }
+    }
+
+    // fill in mean/std dev
+    y = 1;
+    for (ii = 0; ii < getRowCount(); ii++) {
+      i = getDisplayRow(ii);
+      if (getRowHidden(i))
+        continue;
+
+      x = 1;
+      for (nn = 0; nn < getColCount(); nn++) {
+        n = getDisplayCol(nn);
+        if (getColHidden(n))
+          continue;
+
+        // do we have a value in the matrix?
+        valueExists = (!Double.isNaN(getMean(n, i)));
+
+        // mean
+        if (!valueExists)
+          result[y][x] = "";
+        else
+          result[y][x] = doubleToString(getMean(n, i), getMeanPrec());
+        x++;
+        
+        // stddev
+        if (getShowStdDev()) {
+          if (!valueExists)
+            result[y][x] = "";
+          else if (Double.isInfinite(getStdDev(n, i)))
+            result[y][x] = "Inf";
+          else
+            result[y][x] = doubleToString(getStdDev(n, i), getStdDevPrec());
+          x++;
+        }
+        
+        // significance
+        if (!valueExists) {
+          result[y][x] = "";
+        }
+        else {
+          switch (getSignificance(n, i)) {
+            case SIGNIFICANCE_TIE:
+              result[y][x] = TIE_STRING;
+              break;
+            case SIGNIFICANCE_WIN:
+              result[y][x] = WIN_STRING;
+              break;
+            case SIGNIFICANCE_LOSS:
+              result[y][x] = LOSS_STRING;
+              break;
+          }
+        }
+        x++;
+      }
+
+      y++;
+    }
+
+    // the average
+    if (getShowAverage()) {
+      y = result.length - 2;
+      x = 0;
+      result[y][0] = "Average";
+      x++;
+      for (ii = 0; ii < getColCount(); ii++) {
+        i = getDisplayCol(ii);
+        if (getColHidden(i))
+          continue;
+
+        // mean-average
+        result[y][x] = doubleToString(getAverage(i), getMeanPrec());
+        x++;
+
+        // std dev.
+        if (getShowStdDev()) {
+          result[y][x] = "";
+          x++;
+        }
+
+        // significance
+        result[y][x] = "";
+        x++;
+      }
+    }
+
+    // wins/ties/losses
+    y = result.length - 1;
+    x = 0;
+    result[y][0] =   LEFT_PARENTHESES 
+                   + WIN_STRING + "/" 
+                   + TIE_STRING + "/" 
+                   + LOSS_STRING 
+                   + RIGHT_PARENTHESES;
+    x++;
+    for (ii = 0; ii < getColCount(); ii++) {
+      i = getDisplayCol(ii);
+      if (getColHidden(i))
+        continue;
+
+      // mean
+      result[y][x] = "";
+      x++;
+
+      // std dev.
+      if (getShowStdDev()) {
+        result[y][x] = "";
+        x++;
+      }
+
+      // significance
+      result[y][x] =   LEFT_PARENTHESES 
+                     + getSignificanceCount(i, SIGNIFICANCE_WIN) + "/" 
+                     + getSignificanceCount(i, SIGNIFICANCE_TIE) + "/" 
+                     + getSignificanceCount(i, SIGNIFICANCE_LOSS) 
+                     + RIGHT_PARENTHESES;
+      x++;
+    }
+
+    // base column has no significance -> remove these columns
+    tmpResult = new String[result.length][result[0].length - 1];
+
+    x = 0;
+    for (i = 0; i < result[0].length; i++) {
+      // significance
+      if (    ((i == 3) && ( getShowStdDev()))
+           || ((i == 2) && (!getShowStdDev())) )
+        continue;
+      
+      for (n = 0; n < result.length; n++)
+        tmpResult[n][x] = result[n][i];
+
+      x++;
+    }
+    result = tmpResult;
+
+    return result;
+  }
+
+  /**
+   * returns true if the index (in the array produced by toArray(boolean))
+   * is the row name.
+   * 
+   * @param index	the row index
+   * @return		true if index represents a row name
+   */
+  protected boolean isRowName(int index) {
+    return (index == 0);
+  }
+
+  /**
+   * returns true if the index (in the array produced by toArray(boolean))
+   * contains a mean.
+   * 
+   * @param index	the column index
+   * @return		true if mean column
+   */
+  protected boolean isMean(int index) {
+    index--;   // dataset
+    if (index == 0) {
+      return true;   // base column
+    }
+    else {
+      index--;   // base column
+
+      if (index < 0)
+        return false;
+      
+      if (getShowStdDev())
+        return (index % 3 == 1);
+      else
+        return (index % 2 == 0);
+    }
+  }
+
+  /**
+   * returns true if the row index (in the array produced by toArray(boolean))
+   * contains the average row.
+   * 
+   * @param rowIndex	the row index
+   * @return		true if average row
+   */
+  protected boolean isAverage(int rowIndex) {
+    if (getShowAverage())
+      return (getVisibleRowCount() + 1 == rowIndex);
+    else
+      return false;
+  }
+
+  /**
+   * returns true if the index (in the array produced by toArray(boolean))
+   * contains a std deviation.
+   * 
+   * @param index	the column index
+   * @return		true if std dev column
+   */
+  protected boolean isStdDev(int index) {
+    index--;   // dataset
+    index--;   // base column
+
+    if (getShowStdDev()) {
+      if (index == 0) {
+        return true;   // stddev of base column
+      }
+      else {
+        index--;   // stddev of base column
+
+        if (index < 0)
+          return false;
+      
+        return (index % 3 == 1);
+      }
+    }
+    else
+      return false;
+  }
+
+  /**
+   * returns true if the index (in the array produced by toArray(boolean))
+   * contains a significance column.
+   * 
+   * @param index	the column index
+   * @return		true if significance column
+   */
+  protected boolean isSignificance(int index) {
+    index--;   // dataset
+    index--;   // base column
+    if (getShowStdDev()) {
+      index--;   // stddev of base column
+
+      if (index < 0)
+        return false;
+      
+      return (index % 3 == 2);
+    }
+    else {
+      if (index < 0)
+        return false;
+      
+      return (index % 2 == 1);
+    }
+  }
+
+  /**
+   * returns the matrix as a string.
+   * 
+   * @return		the matrix as string
+   */
+  public abstract String toStringMatrix();
+
+  /**
+   * returns the matrix as a string.
+   * 
+   * @return		the matrix as string
+   * @see 		#toStringMatrix()
+   */
+  public String toString() {
+    return toStringMatrix();
+  }
+
+  /**
+   * removes all the header information.
+   */
+  public void clearHeader() {
+    m_HeaderKeys   = new Vector();
+    m_HeaderValues = new Vector();
+  }
+
+  /**
+   * adds the key-value pair to the header.
+   * 
+   * @param key		the name of the header value
+   * @param value	the value of the header value
+   */
+  public void addHeader(String key, String value) {
+    int         pos;
+    
+    pos = m_HeaderKeys.indexOf(key);
+    if (pos > -1) {
+      m_HeaderValues.set(pos, value);
+    }
+    else {
+      m_HeaderKeys.add(key);
+      m_HeaderValues.add(value);
+    }
+  }
+
+  /**
+   * returns the value associated with the given key, null if if cannot be
+   * found.
+   * 
+   * @param key		the key to retrieve the value for
+   * @return		the associated value
+   */
+  public String getHeader(String key) {
+    int       pos;
+
+    pos = m_HeaderKeys.indexOf(key);
+    if (pos == 0)
+      return null;
+    else
+      return (String) m_HeaderKeys.get(pos);
+  }
+
+  /**
+   * returns an enumeration of the header keys.
+   * 
+   * @return		all stored keys
+   */
+  public Enumeration headerKeys() {
+    return m_HeaderKeys.elements();
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header as string
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public abstract String toStringHeader();
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public abstract String toStringKey();
+
+  /**
+   * clears the current summary data.
+   */
+  public void clearSummary() {
+    m_NonSigWins = null;
+    m_Wins       = null;
+  }
+
+  /**
+   * sets the non-significant and significant wins of the resultsets.
+   * 
+   * @param nonSigWins      the non-significant wins
+   * @param wins         the significant wins
+   */
+  public void setSummary(int[][] nonSigWins, int[][] wins) {
+    int         i;
+    int         n;
+    
+    m_NonSigWins = new int[nonSigWins.length][nonSigWins[0].length];
+    m_Wins       = new int[wins.length][wins[0].length];
+
+    for (i = 0; i < m_NonSigWins.length; i++) {
+      for (n = 0; n < m_NonSigWins[i].length; n++) {
+        m_NonSigWins[i][n] = nonSigWins[i][n];
+        m_Wins[i][n]       = wins[i][n];
+      }
+    }
+  }
+
+  /**
+   * returns the character representation of the given column.
+   * 
+   * @param col		the column index
+   * @return		the title of the column
+   */
+  protected String getSummaryTitle(int col) {
+    return "" + (char) ((int) 'a' + col % 26);
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public abstract String toStringSummary();
+
+  /**
+   * clears the currently stored ranking data.
+   */
+  public void clearRanking() {
+    m_RankingWins   = null;
+    m_RankingLosses = null;
+    m_RankingDiff   = null;
+  }
+
+  /**
+   * sets the ranking data based on the wins.
+   * 
+   * @param wins      the wins 
+   */
+  public void setRanking(int[][] wins) {
+    int         i;
+    int         j;
+    
+    m_RankingWins   = new int[wins.length];
+    m_RankingLosses = new int[wins.length];
+    m_RankingDiff   = new int[wins.length];
+
+    for (i = 0; i < wins.length; i++) {
+      for (j = 0; j < wins[i].length; j++) {
+	m_RankingWins[j]   += wins[i][j];
+	m_RankingDiff[j]   += wins[i][j];
+	m_RankingLosses[i] += wins[i][j];
+	m_RankingDiff[i]   -= wins[i][j];
+      }
+    }
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public abstract String toStringRanking();
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixCSV.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixCSV.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixCSV.java	(revision 29)
@@ -0,0 +1,405 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixCSV.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates the matrix in CSV ('comma-separated values') format.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixCSV
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -171838863135042743L;
+  
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixCSV() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixCSV(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixCSV(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Generates the matrix in CSV ('comma-separated values') format.";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "CSV";
+  }
+
+  /**
+   * removes the stored data but retains the dimensions of the matrix.
+   */
+  public void clear() {
+    super.clear();
+    LEFT_PARENTHESES = "[";
+    RIGHT_PARENTHESES = "]";
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 25;
+  }
+
+  /**
+   * returns the default of whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintColNames() {
+    return false;
+  }
+
+  /**
+   * returns the default of whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateColNames() {
+    return true;
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    return new ResultMatrixPlainText(this).toStringHeader();
+  }
+
+  /**
+   * returns the matrix in CSV format.
+   * 
+   * @return		the matrix as string
+   */
+  public String toStringMatrix() {
+    StringBuffer        result;
+    String[][]          cells;
+    int                 i;
+    int                 n;
+
+    result = new StringBuffer();
+    cells  = toArray();
+
+    for (i = 0; i < cells.length; i++) {
+      for (n = 0; n < cells[i].length; n++) {
+        if (n > 0)
+          result.append(",");
+        result.append(Utils.quote(cells[i][n]));
+      }
+      result.append("\n");
+    }
+    
+    return result.toString();
+  }
+
+  /**
+   * returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    String          result;
+    int             i;
+
+    result = "Key,\n";
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      result +=   LEFT_PARENTHESES + (i+1) + RIGHT_PARENTHESES
+                + "," + Utils.quote(removeFilterName(m_ColNames[i])) + "\n";
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    String      result;
+    String      titles;
+    int         i;
+    int         j;
+    String      line;
+
+    if (m_NonSigWins == null)
+      return "-summary data not set-";
+    
+    result = "";
+    titles = "";
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+      if (!titles.equals(""))
+        titles += ",";
+      titles += getSummaryTitle(i);
+    }
+    result += titles + ",'(No. of datasets where [col] >> [row])'\n";
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      line = "";
+      for (j = 0; j < getColCount(); j++) {
+        if (getColHidden(j))
+          continue;
+
+        if (!line.equals(""))
+          line += ",";
+
+	if (j == i)
+	  line += "-";
+	else
+	  line += m_NonSigWins[i][j] 
+                    + " (" + m_Wins[i][j] + ")";
+      }
+
+      result += line + "," + getSummaryTitle(i) + " = " + removeFilterName(m_ColNames[i]) + '\n';
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public String toStringRanking() {
+    String        result;
+    int[]         ranking;
+    int           i;
+    int           curr;
+
+    if (m_RankingWins == null)
+      return "-ranking data not set-";
+
+    result = ">-<,>,<,Resultset\n";
+
+    ranking = Utils.sort(m_RankingDiff);
+
+    for (i = getColCount() - 1; i >= 0; i--) {
+      curr = ranking[i];
+
+      if (getColHidden(curr))
+        continue;
+
+      result += m_RankingDiff[curr] + ","
+        + m_RankingWins[curr] + ","
+        + m_RankingLosses[curr] + ","
+        + removeFilterName(m_ColNames[curr]) + "\n";
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixCSV(3, 3);
+
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixGnuPlot.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixGnuPlot.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixGnuPlot.java	(revision 29)
@@ -0,0 +1,438 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixGnuPlot.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Version;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates output for a data and script file for GnuPlot.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 50)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 50)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixGnuPlot
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -234648254944790097L;
+  
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixGnuPlot() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixGnuPlot(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixGnuPlot(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Generates output for a data and script file for GnuPlot.";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "GNUPlot";
+  }
+
+  /**
+   * removes the stored data but retains the dimensions of the matrix.
+   */
+  public void clear() {
+    super.clear();
+    LEFT_PARENTHESES = "";
+    RIGHT_PARENTHESES = "";
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 50;
+  }
+
+  /**
+   * returns the default width for the column names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultColNameWidth() {
+    return 50;
+  }
+
+  /**
+   * returns the default of whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateColNames() {
+    return false;
+  }
+
+  /**
+   * returns the default of whether row names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateRowNames() {
+    return false;
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    return new ResultMatrixPlainText(this).toStringHeader();
+  }
+
+  /**
+   * returns the matrix in CSV format.
+   * 
+   * @return		the matrix
+   */
+  public String toStringMatrix() {
+    StringBuffer        result;
+    String[][]          cells;
+    int                 i;
+    int                 n;
+    String              line;
+    String              title;
+    String              generated;
+
+    result = new StringBuffer();
+    cells  = toArray();
+
+    // generation comment
+    generated = "# generated by WEKA " + Version.VERSION + "\n";
+
+    // data
+    result.append("\n");
+    result.append("##################\n");
+    result.append("# file: plot.dat #\n");
+    result.append("##################\n");
+    result.append(generated);
+    result.append("# contains the data for the plot\n");
+    // key for x-axis
+    result.append("\n");
+    result.append("# key for the x-axis\n");
+    for (i = 1; i < cells.length - 1; i++)
+      result.append("# " + i + " - " + cells[i][0] + "\n");
+    // the data itself
+    result.append("\n");
+    result.append("# data for the plot\n");
+    for (i = 1; i < cells.length - 1; i++) {
+      result.append(Integer.toString(i));
+      for (n = 1; n < cells[i].length; n++) {
+        if (isSignificance(n))
+          continue;
+        result.append(" ");
+        result.append(Utils.quote(cells[i][n]));
+      }
+      result.append("\n");
+    }
+    result.append("#######\n");
+    result.append("# end #\n");
+    result.append("#######\n");
+
+    // script
+    result.append("\n");
+    result.append("##################\n");
+    result.append("# file: plot.scr #\n");
+    result.append("##################\n");
+    result.append(generated);
+    result.append("# script to plot the data\n");
+    result.append("\n");
+    result.append("# display it in a window:\n");
+    result.append("set terminal x11\n");
+    result.append("set output\n");
+    result.append("\n");
+    result.append("# to display all data rows:\n");
+    result.append("set xrange [0:" + ((cells.length - 2) + 1) + "]\n");
+    result.append("\n");
+    result.append("# axis labels, e.g.:\n");
+    result.append("#set xlabel \"Datasets\"\n");
+    result.append("#set ylabel \"Accuracy in %\"\n");
+    result.append("\n");
+    result.append("# the plot commands\n");
+    n = 1;
+    i = 0;
+    while (i < cells[0].length - 1) {
+      i++;
+
+      if (isSignificance(i))
+        continue;
+
+      n++;
+      
+      // plot
+      if (i == 1)
+        line = "plot";
+      else
+        line = "replot";
+      line += " \"plot.dat\"";
+
+      // title
+      title = "title \"" + cells[0][i] + "\"";
+      
+      // columns
+      line += " using 1:" + n;
+      if (getShowStdDev()) {
+        n++;
+        i++;
+        // errorbars
+        line += ":" + n;
+      }
+      
+      // options
+      line += " with";
+      if (getShowStdDev())
+        line += " yerrorbars";
+      else
+        line += " lines";
+      line += " " + title;
+      
+      result.append(line + "\n");
+    }
+    result.append("\n");
+    result.append("# generate ps:\n");
+    result.append("#set terminal postscript\n");
+    result.append("#set output \"plot.ps\"\n");
+    result.append("#replot\n");
+    result.append("\n");
+    result.append("# generate png:\n");
+    result.append("#set terminal png size 800,600\n");
+    result.append("#set output \"plot.png\"\n");
+    result.append("#replot\n");
+    result.append("\n");
+    result.append("# wait for user to hit <Return>\n");
+    result.append("pause -1\n");
+    result.append("#######\n");
+    result.append("# end #\n");
+    result.append("#######\n");
+    
+    return result.toString();
+  }
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    return new ResultMatrixPlainText(this).toStringKey();
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    return new ResultMatrixPlainText(this).toStringSummary();
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public String toStringRanking() {
+    return new ResultMatrixPlainText(this).toStringRanking();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixGnuPlot(3, 3);
+
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixHTML.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixHTML.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixHTML.java	(revision 29)
@@ -0,0 +1,456 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixHTML.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates the matrix output as HTML.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 25)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixHTML
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 6672380422544799990L;
+
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixHTML() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixHTML(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixHTML(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Generates the matrix output as HTML.";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "HTML";
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 25;
+  }
+
+  /**
+   * returns the default of whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintColNames() {
+    return false;
+  }
+
+  /**
+   * returns the default of whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateColNames() {
+    return true;
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    return new ResultMatrixPlainText(this).toStringHeader();
+  }
+
+  /**
+   * returns the matrix in an HTML table.
+   * 
+   * @return		the matrix
+   */
+  public String toStringMatrix() {
+    StringBuffer        result;
+    String[][]          cells;
+    int                 i;
+    int                 n;
+    int                 cols;
+
+    result = new StringBuffer();
+    cells  = toArray();
+
+    result.append("<table border=\"1\" cellpadding=\"3\" cellspacing=\"0\">\n");
+    
+    // headings
+    result.append("   <tr>");
+    for (n = 0; n < cells[0].length; n++) {
+      if (isRowName(n)) {
+        result.append("<td><b>" + cells[0][n] + "</b></td>");
+      }
+      else if (isMean(n)) {
+        if (n == 1)
+          cols = 1;
+        else
+          cols = 2;
+        if (getShowStdDev())
+          cols++;
+        result.append("<td align=\"center\" colspan=\"" + cols + "\">");
+        result.append("<b>" + cells[0][n] + "</b>");
+        result.append("</td>");
+      }
+    }
+    result.append("</tr>\n");
+      
+    // data
+    for (i = 1; i < cells.length; i++) {
+      result.append("   <tr>");
+      for (n = 0; n < cells[i].length; n++) {
+        if (isRowName(n))
+          result.append("<td>");
+        else if (isMean(n) || isStdDev(n))
+          result.append("<td align=\"right\">");
+        else if (isSignificance(n))
+          result.append("<td align=\"center\">");
+        else
+          result.append("<td>");
+        
+        // content
+        if (cells[i][n].trim().equals(""))
+          result.append("&nbsp;");
+        else if (isStdDev(n))
+          result.append("&plusmn;&nbsp;" + cells[i][n]);
+        else
+          result.append(cells[i][n]);
+        
+        result.append("</td>");
+      }
+      result.append("</tr>\n");
+    }
+    result.append("</table>\n");
+    
+    return result.toString();
+  }
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    String          result;
+    int             i;
+
+    result =   "<table border=\"1\" cellpadding=\"3\" cellspacing=\"0\">\n" 
+             + "   <tr><td colspan=\"2\"><b>Key</b></td></tr>\n";
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      result +=   "   <tr>"
+                + "<td><b>(" + (i+1) + ")</b></td>"
+                + "<td>" + removeFilterName(m_ColNames[i]) + "</td>" 
+                + "</tr>\n";
+    }
+
+    result += "</table>\n";
+
+    return result;
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    String      result;
+    String      titles;
+    int         resultsetLength;
+    int         i;
+    int         j;
+    String      content;
+
+    if (m_NonSigWins == null)
+      return "-summary data not set-";
+    
+    result = "<table border=\"1\" cellpadding=\"3\" cellspacing=\"0\">\n";
+    titles = "   <tr>";
+    resultsetLength = 1 + Math.max((int)(Math.log(getColCount())/Math.log(10)),
+                                   (int)(Math.log(getRowCount())/Math.log(10)));
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+      titles += "<td align=\"center\"><b>" + getSummaryTitle(i) + "</b></td>";
+    }
+    result +=   titles 
+              + "<td><b>(No. of datasets where [col] &gt;&gt; [row])</b></td></tr>\n";
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      result += "   <tr>";
+
+      for (j = 0; j < getColCount(); j++) {
+        if (getColHidden(j))
+          continue;
+
+	if (j == i)
+	  content = Utils.padLeft("-", resultsetLength * 2 + 3);
+	else
+	  content = Utils.padLeft("" + m_NonSigWins[i][j] 
+                                  + " (" + m_Wins[i][j] + ")",
+				  resultsetLength * 2 + 3);
+        result += "<td>" + content.replaceAll(" ", "&nbsp;") + "</td>";
+      }
+
+      result += "<td><b>" + getSummaryTitle(i) + "</b> = " + removeFilterName(m_ColNames[i]) + "</td></tr>\n";
+    }
+
+    result += "</table>\n";
+
+    return result;
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public String toStringRanking() {
+    String        result;
+    int[]         ranking;
+    int           i;
+    int           curr;
+
+    if (m_RankingWins == null)
+      return "-ranking data not set-";
+
+    result = "<table border=\"1\" cellpadding=\"3\" cellspacing=\"0\">\n";
+    result +=  "   <tr>" 
+             + "<td align=\"center\"><b>&gt;-&lt;</b></td>"
+             + "<td align=\"center\"><b>&gt;</b></td>"
+             + "<td align=\"center\"><b>&lt;</b></td>"
+             + "<td><b>Resultset</b></td>"
+             + "</tr>\n";
+
+    ranking = Utils.sort(m_RankingDiff);
+
+    for (i = getColCount() - 1; i >= 0; i--) {
+      curr = ranking[i];
+
+      if (getColHidden(curr))
+        continue;
+
+      result += "   <tr>"
+        + "<td align=\"right\">" + m_RankingDiff[curr] + "</td>"
+        + "<td align=\"right\">" + m_RankingWins[curr] + "</td>"
+        + "<td align=\"right\">" + m_RankingLosses[curr] + "</td>"
+        + "<td>" + removeFilterName(m_ColNames[curr]) + "</td>"
+        + "<tr>\n";
+    }
+
+    result += "</table>\n";
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixHTML(3, 3);
+    
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixLatex.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixLatex.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixLatex.java	(revision 29)
@@ -0,0 +1,534 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixLatex.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates the matrix output in LaTeX-syntax.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixLatex
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 777690788447600978L;
+  
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixLatex() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixLatex(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixLatex(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Generates the matrix output in LaTeX-syntax.";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "LaTeX";
+  }
+
+  /**
+   * removes the stored data but retains the dimensions of the matrix.
+   */
+  public void clear() {
+    super.clear();
+    TIE_STRING  = " ";
+    WIN_STRING  = "$\\circ$";
+    LOSS_STRING = "$\\bullet$";
+  }
+
+  /**
+   * returns the default of whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintColNames() {
+    return false;
+  }
+
+  /**
+   * returns the default of whether column names are prefixed with the index.
+   * 
+   * @return		true if the names are prefixed
+   */
+  public boolean getDefaultEnumerateColNames() {
+    return true;
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    return new ResultMatrixPlainText(this).toStringHeader();
+  }
+
+  /**
+   * returns the matrix as latex table.
+   * 
+   * @return		the matrix
+   */
+  public String toStringMatrix() {
+    StringBuffer    result;
+    String[][]      cells;
+    int             i;
+    int             j;
+    int             n;
+    int             size;
+
+    result  = new StringBuffer();
+    cells   = toArray();
+
+    result.append(  "\\begin{table}[thb]\n\\caption{\\label{labelname}"
+                  + "Table Caption}\n");
+    if (!getShowStdDev())
+      result.append("\\footnotesize\n");
+    else
+      result.append("\\scriptsize\n");
+
+    // output the column alignment characters
+    // one for the dataset name and one for the comparison column
+    if (!getShowStdDev()) {
+      result.append(  "{\\centering \\begin{tabular}{"
+                    + "l"                     // dataset
+                    + ""                      // separator
+                    + "r"                     // mean
+                    );
+    } else {
+      // dataset, mean, std dev
+      result.append(  "{\\centering \\begin{tabular}{" 
+                    + "l"                     // dataset
+                    + ""                      // separator
+                    + "r"                     // mean
+                    + "@{\\hspace{0cm}}"      // separator
+                    + "c"                     // +/-
+                    + "@{\\hspace{0cm}}"      // separator
+                    + "r"                     // stddev
+                    );
+    }
+
+    for (j = 1; j < getColCount(); j++) {
+      if (getColHidden(j))
+        continue;
+      if (!getShowStdDev())
+        result.append(  "r"                   // mean
+                      + "@{\\hspace{0.1cm}}"  // separator
+                      + "c"                   // significance
+                      );
+      else 
+        result.append(  "r"                   // mean
+                      + "@{\\hspace{0cm}}"    // separator
+                      + "c"                   // +/-
+                      + "@{\\hspace{0cm}}"    // separator
+                      + "r"                   // stddev
+                      + "@{\\hspace{0.1cm}}"  // separator
+                      + "c"                   // significance
+                      );
+    }
+    result.append("}\n\\\\\n\\hline\n");
+    if (!getShowStdDev())
+      result.append("Dataset & " + cells[0][1]);
+    else
+      result.append("Dataset & \\multicolumn{3}{c}{" + cells[0][1] + "}");
+
+    // now do the column names (numbers)
+    for (j = 2; j < cells[0].length; j++) {
+      if (!isMean(j))
+        continue;
+      if (!getShowStdDev())
+        result.append("& " + cells[0][j] + " & ");
+      else
+        result.append("& \\multicolumn{4}{c}{" + cells[0][j] + "} ");
+    }
+    result.append("\\\\\n\\hline\n");
+
+    // change "_" to "-" in names
+    for (i = 1; i < cells.length; i++)
+      cells[i][0] = cells[i][0].replace('_', '-');
+
+    // pad numbers
+    for (n = 1; n < cells[0].length; n++) {
+      size = getColSize(cells, n);
+      for (i = 1; i < cells.length; i++)
+        cells[i][n] = padString(cells[i][n], size, true);
+    }
+
+    // output data (w/o wins/ties/losses)
+    for (i = 1; i < cells.length - 1; i++) {
+      if (isAverage(i))
+        result.append("\\hline\n");
+      for (n = 0; n < cells[0].length; n++) {
+        if (n == 0) {
+          result.append(padString(cells[i][n], getRowNameWidth()));
+        }
+        else {
+          if (getShowStdDev()) {
+            if (isMean(n - 1)) {
+              if (!cells[i][n].trim().equals(""))
+                result.append(" & $\\pm$ & ");
+              else
+                result.append(" &       & ");
+            }
+            else
+              result.append(" & ");
+          }
+          else {
+            result.append(" & ");
+          }
+          result.append(cells[i][n]);
+        }
+      }
+      
+      result.append("\\\\\n");
+    }
+
+    result.append("\\hline\n\\multicolumn{" + cells[0].length + "}{c}{$\\circ$, $\\bullet$"
+		  +" statistically significant improvement or degradation}"
+		  +"\\\\\n\\end{tabular} ");
+    if (!getShowStdDev())     
+      result.append("\\footnotesize ");
+    else
+      result.append("\\scriptsize ");
+    
+    result.append("\\par}\n\\end{table}"
+		  +"\n");
+     
+    return result.toString();
+  }
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    String          result;
+    int             i;
+
+    result =   "\\begin{table}[thb]\n\\caption{\\label{labelname}"
+             + "Table Caption (Key)}\n";
+    result += "\\scriptsize\n";
+    result += "{\\centering\n";
+    result += "\\begin{tabular}{cl}\\\\\n";
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      result +=   LEFT_PARENTHESES + (i+1) + RIGHT_PARENTHESES 
+                + " & " + removeFilterName(m_ColNames[i]).replace('_', '-')
+                                       .replaceAll("\\\\", "\\\\textbackslash") 
+                + " \\\\\n";
+    }
+    result += "\\end{tabular}\n";
+    result += "}\n";
+    result += "\\end{table}\n";
+
+    return result;
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    int           resultsetLength;
+    String        result;
+    String        titles;
+    int           i;
+    int           j;
+
+    if (m_NonSigWins == null)
+      return "-summary data not set-";
+    
+    resultsetLength = 1 + Math.max((int)(Math.log(getColCount())/Math.log(10)),
+                                   (int)(Math.log(getRowCount())/Math.log(10)));
+    result = "";
+    titles = "";
+
+    result += "{\\centering\n";
+    result += "\\begin{table}[thb]\n\\caption{\\label{labelname}"
+                +"Table Caption}\n";
+    result += "\\footnotesize\n";
+    result += "\\begin{tabular}{l";
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      titles += " &";
+      result += "c";
+      titles += ' ' + Utils.padLeft("" + getSummaryTitle(i),
+				    resultsetLength * 2 + 3);
+    }
+    result += "}\\\\\n\\hline\n";
+    result += titles + " \\\\\n\\hline\n";
+    
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      for (j = 0; j < getColCount(); j++) {
+        if (getColHidden(j))
+          continue;
+
+	if (j == 0)
+	  result +=  (char)((int)'a' + i % 26);
+
+	if (j == i)
+	  result += " & - ";
+	else
+	  result += "& " + m_NonSigWins[i][j] + " (" + m_Wins[i][j] + ") ";
+      }
+      result += "\\\\\n";
+    }
+
+    result += "\\hline\n\\end{tabular} \\footnotesize \\par\n\\end{table}}";
+
+    return result;
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return 		the ranking
+   */
+  public String toStringRanking() {
+    int           biggest;
+    int           width;
+    String        result;
+    int[]         ranking;
+    int           i;
+    int           curr;
+
+    if (m_RankingWins == null)
+      return "-ranking data not set-";
+
+    biggest = Math.max(m_RankingWins[Utils.maxIndex(m_RankingWins)],
+                       m_RankingLosses[Utils.maxIndex(m_RankingLosses)]);
+    width = Math.max(2 + (int)(Math.log(biggest) / Math.log(10)),
+			 ">-<".length());
+    result = "\\begin{table}[thb]\n\\caption{\\label{labelname}Table Caption"
+      + "}\n\\footnotesize\n{\\centering \\begin{tabular}{rlll}\\\\\n\\hline\n";
+    result +=   "Resultset & Wins$-$ & Wins & Losses \\\\\n& Losses & & "
+              + "\\\\\n\\hline\n";
+
+    ranking = Utils.sort(m_RankingDiff);
+    for (i = getColCount() - 1; i >= 0; i--) {
+      curr = ranking[i];
+      
+      if (getColHidden(curr))
+        continue;
+
+      result +=   "(" + (curr + 1) + ") & " 
+                + Utils.padLeft("" + m_RankingDiff[curr], width) 
+                + " & " + Utils.padLeft("" + m_RankingWins[curr], width)
+                + " & " + Utils.padLeft("" + m_RankingLosses[curr], width)
+                + "\\\\\n";
+    }
+
+    result += "\\hline\n\\end{tabular} \\footnotesize \\par}\n\\end{table}";
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixLatex(3, 3);
+    
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixPlainText.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixPlainText.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixPlainText.java	(revision 29)
@@ -0,0 +1,556 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixPlainText.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+/**
+ <!-- globalinfo-start -->
+ * Generates the output as plain text (for fixed width fonts).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 25)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 5)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixPlainText
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1502934525382357937L;
+
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixPlainText() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixPlainText(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixPlainText(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Generates the output as plain text (for fixed width fonts).";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "Plain Text";
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 25;
+  }
+
+  /**
+   * returns the default width for the counts.
+   * 
+   * @return		the width
+   */
+  public int getDefaultCountWidth() {
+    return 5;
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    int         i;
+    int         size;
+    String[][]  data;
+    String      result;
+
+    result = "";
+    
+    // fill in data
+    data = new String[m_HeaderKeys.size()][2];
+    for (i = 0; i < m_HeaderKeys.size(); i++) {
+      data[i][0] = m_HeaderKeys.get(i).toString() + ":";
+      data[i][1] = m_HeaderValues.get(i).toString();
+    }
+
+    // pad
+    size = getColSize(data, 0);
+    for (i = 0; i < data.length; i++)
+      data[i][0] = padString(data[i][0], size);
+
+    // build result
+    for (i = 0; i < data.length; i++)
+      result += data[i][0] + " " + data[i][1] + "\n";
+
+    return result;
+  }
+
+  /**
+   * returns the matrix as plain text.
+   * 
+   * @return		the matrix
+   */
+  public String toStringMatrix() {
+    StringBuffer    result;
+    String[][]      cells;
+    int             i;
+    int             j;
+    int             n;
+    int             k;
+    int             size;
+    String          line;
+    int             indexBase;
+    int             indexSecond;
+    StringBuffer    head;
+    StringBuffer    body;
+    StringBuffer    foot;
+    int[]           startMeans;
+    int[]           startSigs;
+    int             maxLength;
+
+    result     = new StringBuffer();
+    head       = new StringBuffer();
+    body       = new StringBuffer();
+    foot       = new StringBuffer();
+    cells      = toArray();
+    startMeans = new int[getColCount()];
+    startSigs  = new int[getColCount() - 1];
+    maxLength  = 0;
+
+    // pad numbers
+    for (n = 1; n < cells[0].length; n++) {
+      size = getColSize(cells, n, true, true);
+      for (i = 1; i < cells.length - 1; i++)
+        cells[i][n] = padString(cells[i][n], size, true);
+    }
+
+    // index of base column in array
+    indexBase = 1;
+    if (getShowStdDev())
+      indexBase++;
+
+    // index of second column in array
+    indexSecond = indexBase + 1;
+    if (getShowStdDev())
+      indexSecond++;
+
+    // output data (without "(v/ /*)")
+    j = 0;
+    k = 0;
+    for (i = 1; i < cells.length - 1; i++) {
+      if (isAverage(i))
+        body.append(padString("", maxLength).replaceAll(".", "-") + "\n");
+      line = "";
+      
+      for (n = 0; n < cells[0].length; n++) {
+        // record starts
+        if (i == 1) {
+          if (isMean(n)) {
+            startMeans[j] = line.length();
+            j++;
+          }
+
+          if (isSignificance(n)) {
+            startSigs[k] = line.length();
+            k++;
+          }
+        }
+        
+        if (n == 0) {
+          line += padString(cells[i][n], getRowNameWidth());
+          if (!isAverage(i))
+            line += padString("(" +
+                Utils.doubleToString(getCount(getDisplayRow(i-1)), 0) + ")",
+                getCountWidth(), true);
+          else
+            line += padString("", getCountWidth(), true);
+        }
+        else {
+          // additional space before means
+          if (isMean(n))
+            line += "  ";
+
+          // print cell
+          if (getShowStdDev()) {
+            if (isMean(n - 1)) {
+              if (!cells[i][n].trim().equals(""))              
+                line += "(" + cells[i][n] + ")";
+              else
+                line += " " + cells[i][n] + " ";
+            }
+            else
+              line += " " + cells[i][n];
+          }
+          else {
+            line += " " + cells[i][n];
+          }
+        }
+
+        // add separator after base column
+        if (n == indexBase)
+          line += " |";
+      }
+
+      // record overall length
+      if (i == 1)
+        maxLength = line.length();
+      
+      body.append(line + "\n");
+    }
+
+    // column names
+    line = padString(cells[0][0], startMeans[0]);
+    i    = -1;
+    for (n = 1; n < cells[0].length; n++) {
+      if (isMean(n)) {
+        i++;
+
+        if (i == 0)
+          line = padString(line, startMeans[i] - getCountWidth());
+        else if (i == 1)
+          line = padString(line, startMeans[i] - " |".length());
+        else if (i > 1)
+          line = padString(line, startMeans[i]);
+        
+        if (i == 1)
+          line += " |";
+        
+        line += " " + cells[0][n];
+      }
+    }
+    line = padString(line, maxLength);
+    head.append(line + "\n");
+    head.append(line.replaceAll(".", "-") + "\n");
+    body.append(line.replaceAll(".", "-") + "\n");
+
+    // output wins/losses/ties
+    if (getColCount() > 1) {
+      line = padString(cells[cells.length - 1][0], startMeans[1]-2, true) + " |";
+      i    = 0;
+      for (n = 1; n < cells[cells.length - 1].length; n++) {
+        if (isSignificance(n)) {
+          line = padString(
+                  line, startSigs[i] + 1 - cells[cells.length - 1][n].length());
+          line += " " + cells[cells.length - 1][n];
+          i++;
+        }
+      }
+      line = padString(line, maxLength);
+    }
+    else {
+      line = padString(cells[cells.length - 1][0], line.length() - 2) + " |";
+    }
+    foot.append(line + "\n");
+    
+    // assemble output
+    result.append(head.toString());
+    result.append(body.toString());
+    result.append(foot.toString());
+
+    return result.toString();
+  }
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    String          result;
+    int             i;
+
+    result = "Key:\n";
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      result +=   LEFT_PARENTHESES + (i+1) + RIGHT_PARENTHESES 
+                + " " + removeFilterName(m_ColNames[i]) + "\n";
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    String      result;
+    String      titles;
+    int         resultsetLength;
+    int         i;
+    int         j;
+
+    if (m_NonSigWins == null)
+      return "-summary data not set-";
+    
+    result = "";
+    titles = "";
+    resultsetLength = 1 + Math.max((int)(Math.log(getColCount())/Math.log(10)),
+                                   (int)(Math.log(getRowCount())/Math.log(10)));
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+      titles += " " + Utils.padLeft("" + getSummaryTitle(i),
+				    resultsetLength * 2 + 3);
+    }
+    result += titles + "  (No. of datasets where [col] >> [row])\n";
+
+    for (i = 0; i < getColCount(); i++) {
+      if (getColHidden(i))
+        continue;
+
+      for (j = 0; j < getColCount(); j++) {
+        if (getColHidden(j))
+          continue;
+
+        result += " ";
+	if (j == i)
+	  result += Utils.padLeft("-", resultsetLength * 2 + 3);
+	else
+	  result += Utils.padLeft("" + m_NonSigWins[i][j] 
+                                  + " (" + m_Wins[i][j] + ")",
+				  resultsetLength * 2 + 3);
+      }
+
+      result += " | " + getSummaryTitle(i) + " = " + getColName(i) + '\n';
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public String toStringRanking() {
+    int           biggest;
+    int           width;
+    String        result;
+    int[]         ranking;
+    int           i;
+    int           curr;
+
+    if (m_RankingWins == null)
+      return "-ranking data not set-";
+
+    biggest = Math.max(m_RankingWins[Utils.maxIndex(m_RankingWins)],
+                       m_RankingLosses[Utils.maxIndex(m_RankingLosses)]);
+    width = Math.max(2 + (int)(Math.log(biggest) / Math.log(10)),
+			 ">-<".length());
+    result =   Utils.padLeft(">-<", width) + ' '
+             + Utils.padLeft(">", width) + ' '
+             + Utils.padLeft("<", width) + " Resultset\n";
+
+    ranking = Utils.sort(m_RankingDiff);
+
+    for (i = getColCount() - 1; i >= 0; i--) {
+      curr = ranking[i];
+
+      if (getColHidden(curr))
+        continue;
+
+      result += Utils.padLeft("" + m_RankingDiff[curr], width) + ' '
+        + Utils.padLeft("" + m_RankingWins[curr], width) + ' '
+        + Utils.padLeft("" + m_RankingLosses[curr], width) + ' '
+        + removeFilterName(m_ColNames[curr]) + '\n';
+    }
+
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixPlainText(3, 3);
+    
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixSignificance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixSignificance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultMatrixSignificance.java	(revision 29)
@@ -0,0 +1,373 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultMatrixSignificance.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionUtils;
+
+/**
+ <!-- globalinfo-start -->
+ * Only outputs the significance indicators. Can be used for spotting patterns.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -mean-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -stddev-prec &lt;int&gt;
+ *  The number of decimals after the decimal point for the mean.
+ *  (default: 2)</pre>
+ * 
+ * <pre> -col-name-width &lt;int&gt;
+ *  The maximum width for the column names (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -row-name-width &lt;int&gt;
+ *  The maximum width for the row names (0 = optimal).
+ *  (default: 40)</pre>
+ * 
+ * <pre> -mean-width &lt;int&gt;
+ *  The width of the mean (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -stddev-width &lt;int&gt;
+ *  The width of the standard deviation (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -sig-width &lt;int&gt;
+ *  The width of the significance indicator (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -count-width &lt;int&gt;
+ *  The width of the counts (0 = optimal).
+ *  (default: 0)</pre>
+ * 
+ * <pre> -show-stddev
+ *  Whether to display the standard deviation column.
+ *  (default: no)</pre>
+ * 
+ * <pre> -show-avg
+ *  Whether to show the row with averages.
+ *  (default: no)</pre>
+ * 
+ * <pre> -remove-filter
+ *  Whether to remove the classname package prefixes from the
+ *  filter names in datasets.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-col-names
+ *  Whether to output column names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -print-row-names
+ *  Whether to output row names or just numbers representing them.
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-col-names
+ *  Whether to enumerate the column names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ * <pre> -enum-row-names
+ *  Whether to enumerate the row names (prefixing them with 
+ *  '(x)', with 'x' being the index).
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class ResultMatrixSignificance
+  extends ResultMatrix {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -1280545644109764206L;
+  
+  /**
+   * initializes the matrix as 1x1 matrix.
+   */
+  public ResultMatrixSignificance() {
+    this(1, 1);
+  }
+
+  /**
+   * initializes the matrix with the given dimensions.
+   * 
+   * @param cols	the number of columns
+   * @param rows	the number of rows
+   */
+  public ResultMatrixSignificance(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  /**
+   * initializes the matrix with the values from the given matrix.
+   * 
+   * @param matrix      the matrix to get the values from
+   */
+  public ResultMatrixSignificance(ResultMatrix matrix) {
+    super(matrix);
+  }
+  
+  /**
+   * Returns a string describing the matrix.
+   * 
+   * @return 		a description suitable for
+   * 			displaying in the experimenter gui
+   */
+  public String globalInfo() {
+    return "Only outputs the significance indicators. Can be used for spotting patterns.";
+  }
+
+  /**
+   * returns the name of the output format.
+   * 
+   * @return		the display name
+   */
+  public String getDisplayName() {
+    return "Significance only";
+  }
+
+  /**
+   * returns the default of whether column names or numbers instead are printed.
+   * 
+   * @return		true if names instead of numbers are printed
+   */
+  public boolean getDefaultPrintColNames() {
+    return false;
+  }
+
+  /**
+   * returns the default width for the row names.
+   * 
+   * @return		the width
+   */
+  public int getDefaultRowNameWidth() {
+    return 40;
+  }
+
+  /**
+   * returns the default of whether std deviations are displayed or not.
+   * 
+   * @return		true if the std deviations are displayed
+   */
+  public boolean getDefaultShowStdDev() {
+    return false;
+  }
+
+  /**
+   * sets whether to display the std deviations or not - always false!
+   * 
+   * @param show	ignored
+   */
+  public void setShowStdDev(boolean show) {
+    // ignore
+  }
+
+  /**
+   * returns the matrix as plain text.
+   * 
+   * @return		the matrix
+   */
+  public String toStringMatrix() {
+    StringBuffer        result;
+    String[][]          cells;
+    int                 i;
+    int                 n;
+    int                 nameWidth;
+    String              line;
+    String              colStr;
+    int                 rows;
+
+    result = new StringBuffer();
+    cells  = toArray();
+
+    // pad names
+    nameWidth = getColSize(cells, 0);
+    for (i = 0; i < cells.length - 1; i++)
+      cells[i][0] = padString(cells[i][0], nameWidth);
+    
+    // determine number of displayed rows
+    rows = cells.length - 1;
+    if (getShowAverage())
+      rows--;
+    
+    for (i = 0; i < rows; i++) {
+      line   = "";
+      colStr = "";
+
+      for (n = 0; n < cells[i].length; n++) {
+        // the header of the column
+        if (isMean(n) || isRowName(n))
+          colStr = cells[0][n];
+        
+        if ( (n > 1) && (!isSignificance(n)) )
+          continue;
+        
+        // padding between cols 
+        if (n > 0)
+          line += " "; 
+        // padding for "(" below dataset line
+        if ( (i > 0) && (n > 1) )
+          line += " ";
+        
+        if (i == 0) {
+          line += colStr;
+        }
+        else {    
+          if (n == 0) {
+            line += cells[i][n];
+          }
+          else if (n == 1) {
+            line += colStr.replaceAll(".", " ");   // base column has no significance!
+          }
+          else {
+            line += cells[i][n];
+            // add blanks dep. on length of #
+            line += colStr.replaceAll(".", " ").substring(2);
+          }
+        }
+      }
+      result.append(line + "\n");
+      
+      // separator line
+      if (i == 0)
+        result.append(line.replaceAll(".", "-") + "\n");
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * returns the header of the matrix as a string.
+   * 
+   * @return		the header
+   * @see 		#m_HeaderKeys
+   * @see 		#m_HeaderValues
+   */
+  public String toStringHeader() {
+    return new ResultMatrixPlainText(this).toStringHeader();
+  }
+
+  /**
+   * returns returns a key for all the col names, for better readability if
+   * the names got cut off.
+   * 
+   * @return		the key
+   */
+  public String toStringKey() {
+    return new ResultMatrixPlainText(this).toStringKey();
+  }
+
+  /**
+   * returns the summary as string.
+   * 
+   * @return		the summary
+   */
+  public String toStringSummary() {
+    return new ResultMatrixPlainText(this).toStringSummary();
+  }
+
+  /**
+   * returns the ranking in a string representation.
+   * 
+   * @return		the ranking
+   */
+  public String toStringRanking() {
+    return new ResultMatrixPlainText(this).toStringRanking();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5346 $");
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    ResultMatrix        matrix;
+    int                 i;
+    int                 n;
+    
+    matrix = new ResultMatrixSignificance(3, 3);
+    
+    // set header
+    matrix.addHeader("header1", "value1");
+    matrix.addHeader("header2", "value2");
+    matrix.addHeader("header2", "value3");
+    
+    // set values
+    for (i = 0; i < matrix.getRowCount(); i++) {
+      for (n = 0; n < matrix.getColCount(); n++) {
+        matrix.setMean(n, i, (i+1)*n);
+        matrix.setStdDev(n, i, ((double) (i+1)*n) / 100);
+        if (i == n) {
+          if (i % 2 == 1)
+            matrix.setSignificance(n, i, SIGNIFICANCE_WIN);
+          else
+            matrix.setSignificance(n, i, SIGNIFICANCE_LOSS);
+        }
+      }
+    }
+
+    System.out.println("\n\n--> " + matrix.getDisplayName());
+    
+    System.out.println("\n1. complete\n");
+    System.out.println(matrix.toStringHeader() + "\n");
+    System.out.println(matrix.toStringMatrix() + "\n");
+    System.out.println(matrix.toStringKey());
+    
+    System.out.println("\n2. complete with std deviations\n");
+    matrix.setShowStdDev(true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n3. cols numbered\n");
+    matrix.setPrintColNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n4. second col missing\n");
+    matrix.setColHidden(1, true);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n5. last row missing, rows numbered too\n");
+    matrix.setRowHidden(2, true);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+    
+    System.out.println("\n6. mean prec to 3\n");
+    matrix.setMeanPrec(3);
+    matrix.setPrintRowNames(false);
+    System.out.println(matrix.toStringMatrix());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/ResultProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/ResultProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/ResultProducer.java	(revision 29)
@@ -0,0 +1,167 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResultProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Instances;
+import java.io.Serializable;
+
+/**
+ * This interface defines the methods required for an object 
+ * that produces results for different randomizations of a dataset. <p>
+ *
+ * Possible implementations of ResultProducer: <br>
+ * <ul>
+ *   <li>Random test/train splits
+ *   <li>CrossValidation splits
+ *   <li>LearningCurve splits (multiple results per run?)
+ *   <li>Averaging results of other result producers
+* </ul>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+
+public interface ResultProducer extends Serializable {
+  
+  /**
+   * Sets the dataset that results will be obtained for.
+   *
+   * @param instances a value of type 'Instances'.
+   */
+  void setInstances(Instances instances);
+
+  /**
+   * Sets the object to send results of each run to.
+   *
+   * @param listener a value of type 'ResultListener'
+   */
+  void setResultListener(ResultListener listener);
+
+  /**
+   * Sets a list of method names for additional measures to look for
+   * in SplitEvaluators.
+   * @param additionalMeasures a list of method names
+   */
+  void setAdditionalMeasures(String [] additionalMeasures);
+
+  /**
+   * Prepare to generate results. The ResultProducer should call
+   * preProcess(this) on the ResultListener it is to send results to.
+   *
+   * @exception Exception if an error occurs during preprocessing.
+   */
+  void preProcess() throws Exception;
+  
+  /**
+   * Perform any postprocessing. When this method is called, it indicates
+   * that no more requests to generate results for the current experiment
+   * will be sent. The ResultProducer should call
+   * preProcess(this) on the ResultListener it is to send results to.
+   *
+   * @exception Exception if an error occurs
+   */
+  void postProcess() throws Exception;
+  
+  /**
+   * Gets the results for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Results
+   * produced should be sent to the current ResultListener, but only
+   * if the ResultListener says the result is required (it may already
+   * have that result). A single run may produce multiple results.
+   *
+   * @param run the run number to generate results for.
+   * @exception Exception if a problem occurs while getting the results
+   */
+  void doRun(int run) throws Exception;
+  
+  /**
+   * Gets the keys for a specified run number. Different run
+   * numbers correspond to different randomizations of the data. Keys
+   * produced should be sent to the current ResultListener
+   *
+   * @param run the run number to get keys for.
+   * @exception Exception if a problem occurs while getting the keys
+   */
+  void doRunKeys(int run) throws Exception;
+
+  /**
+   * Gets the names of each of the key columns produced for a single run.
+   * The names should not contain spaces (use '_' instead for easy 
+   * translation.)
+   *
+   * @return an array containing the name of each key column
+   * @exception Exception if the key names could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  String [] getKeyNames() throws Exception;
+
+  /**
+   * Gets the data types of each of the key columns produced for a single run.
+   *
+   * @return an array containing objects of the type of each key column. The 
+   * objects should be Strings, or Doubles.
+   * @exception Exception if the key types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  Object [] getKeyTypes() throws Exception;
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The names should not contain spaces (use '_' instead for easy 
+   * translation.)
+   *
+   * @return an array containing the name of each result column
+   * @exception Exception if the result names could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  String [] getResultNames() throws Exception;
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   * @exception Exception if the result types could not be determined (perhaps
+   * because of a problem from a nested sub-resultproducer)
+   */
+  Object [] getResultTypes() throws Exception;
+
+  /**
+   * Gets a description of the internal settings of the result
+   * producer, sufficient for distinguishing a ResultProducer
+   * instance from another with different settings (ignoring
+   * those settings set through this interface). For example,
+   * a cross-validation ResultProducer may have a setting for the
+   * number of folds. For a given state, the results produced should
+   * be compatible. Typically if a ResultProducer is an OptionHandler,
+   * this string will represent those command line arguments required
+   * to set the ResultProducer to that state.
+   *
+   * @return the description of the ResultProducer state, or null
+   * if no state is defined
+   */
+  String getCompatibilityState();
+
+} // ResultProducer
Index: branches/MetisMQI/src/main/java/weka/experiment/SplitEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/SplitEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/SplitEvaluator.java	(revision 29)
@@ -0,0 +1,129 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SplitEvaluator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Instances;
+import java.io.Serializable;
+
+/**
+ * Interface to objects able to generate a fixed set of results for
+ * a particular split of a dataset. The set of results should contain
+ * fields related to any settings of the SplitEvaluator (not including
+ * the dataset name. For example, one field for the classifier used to
+ * get the results, another for the classifier options, etc). <p>
+ *
+ * Possible implementations of SplitEvaluator: <br>
+ * <ul>
+ *   <li>StdClassification results
+ *   <li>StdRegression results
+ * </ul>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public interface SplitEvaluator extends Serializable {
+  
+  /**
+   * Sets a list of method names for additional measures to look for
+   * in SplitEvaluators.
+   * @param additionalMeasures a list of method names
+   */
+  void setAdditionalMeasures(String [] additionalMeasures);
+
+  /**
+   * Gets the names of each of the key columns produced for a single run.
+   * The names should not contain spaces (use '_' instead for easy 
+   * translation.) The number of key fields must be constant for a given 
+   * SplitEvaluator.
+   *
+   * @return an array containing the name of each key column
+   */
+  String [] getKeyNames();
+
+  /**
+   * Gets the data types of each of the key columns produced for a single run.
+   * The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each key column. The 
+   * objects should be Strings, or Doubles.
+   */
+  Object [] getKeyTypes();
+
+  /**
+   * Gets the names of each of the result columns produced for a single run.
+   * The names should not contain spaces (use '_' instead for easy 
+   * translation.) The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing the name of each result column
+   */
+  String [] getResultNames();
+
+  /**
+   * Gets the data types of each of the result columns produced for a 
+   * single run. The number of result fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return an array containing objects of the type of each result column. 
+   * The objects should be Strings, or Doubles.
+   */
+  Object [] getResultTypes();
+
+  /**
+   * Gets the key describing the current SplitEvaluator. For example
+   * This may contain the name of the classifier used for classifier
+   * predictive evaluation. The number of key fields must be constant
+   * for a given SplitEvaluator.
+   *
+   * @return a value of type 'Object'
+   */
+  Object [] getKey();
+
+  /**
+   * Gets the results for the supplied train and test datasets.
+   *
+   * @param train the training Instances.
+   * @param test the testing Instances.
+   * @return the results stored in an array. The objects stored in
+   * the array may be Strings, Doubles, or null (for the missing value).
+   * @exception Exception if a problem occurs while getting the results
+   */
+  Object [] getResult(Instances train, Instances test) throws Exception;
+
+  /**
+   * Returns the raw output for the most recent call to getResult. Useful
+   * for debugging splitEvaluators.
+   * 
+   * @return the raw output corresponding to the most recent call
+   * to getResut
+   */
+  String getRawResultOutput();
+
+} // SplitEvaluator
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/experiment/Stats.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/Stats.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/Stats.java	(revision 29)
@@ -0,0 +1,214 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Stats.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.io.Serializable;
+
+/**
+ * A class to store simple statistics
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.12 $
+ */
+public class Stats
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8610544539090024102L;
+  
+  /** The number of values seen */
+  public double count = 0;
+
+  /** The sum of values seen */
+  public double sum = 0;
+
+  /** The sum of values squared seen */
+  public double sumSq = 0;
+
+  /** The std deviation of values at the last calculateDerived() call */    
+  public double stdDev = Double.NaN;
+
+  /** The mean of values at the last calculateDerived() call */    
+  public double mean = Double.NaN;
+
+  /** The minimum value seen, or Double.NaN if no values seen */
+  public double min = Double.NaN;
+
+  /** The maximum value seen, or Double.NaN if no values seen */
+  public double max = Double.NaN;
+    
+  /**
+   * Adds a value to the observed values
+   *
+   * @param value the observed value
+   */
+  public void add(double value) {
+
+    add(value, 1);
+  }
+
+  /**
+   * Adds a value that has been seen n times to the observed values
+   *
+   * @param value the observed value
+   * @param n the number of times to add value
+   */
+  public void add(double value, double n) {
+
+    sum += value * n;
+    sumSq += value * value * n;
+    count += n;
+    if (Double.isNaN(min)) {
+      min = max = value;
+    } else if (value < min) {
+      min = value;
+    } else if (value > max) {
+      max = value;
+    }
+  }
+
+  /**
+   * Removes a value to the observed values (no checking is done
+   * that the value being removed was actually added). 
+   *
+   * @param value the observed value
+   */
+  public void subtract(double value) {
+    subtract(value, 1);
+  }
+
+  /**
+   * Subtracts a value that has been seen n times from the observed values
+   *
+   * @param value the observed value
+   * @param n the number of times to subtract value
+   */
+  public void subtract(double value, double n) {
+    sum -= value * n;
+    sumSq -= value * value * n;
+    count -= n;
+  }
+
+  /**
+   * Tells the object to calculate any statistics that don't have their
+   * values automatically updated during add. Currently updates the mean
+   * and standard deviation.
+   */
+  public void calculateDerived() {
+
+    mean = Double.NaN;
+    stdDev = Double.NaN;
+    if (count > 0) {
+      mean = sum / count;
+      stdDev = Double.POSITIVE_INFINITY;
+      if (count > 1) {
+	stdDev = sumSq - (sum * sum) / count;
+	stdDev /= (count - 1);
+        if (stdDev < 0) {
+	  //          System.err.println("Warning: stdDev value = " + stdDev 
+	  //                             + " -- rounded to zero.");
+          stdDev = 0;
+        }
+	stdDev = Math.sqrt(stdDev);
+      }
+    }
+  }
+    
+  /**
+   * Returns a string summarising the stats so far.
+   *
+   * @return the summary string
+   */
+  public String toString() {
+
+    calculateDerived();
+    return
+      "Count   " + Utils.doubleToString(count, 8) + '\n'
+      + "Min     " + Utils.doubleToString(min, 8) + '\n'
+      + "Max     " + Utils.doubleToString(max, 8) + '\n'
+      + "Sum     " + Utils.doubleToString(sum, 8) + '\n'
+      + "SumSq   " + Utils.doubleToString(sumSq, 8) + '\n'
+      + "Mean    " + Utils.doubleToString(mean, 8) + '\n'
+      + "StdDev  " + Utils.doubleToString(stdDev, 8) + '\n';
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.12 $");
+  }
+
+  /**
+   * Tests the paired stats object from the command line.
+   * reads line from stdin, expecting two values per line.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    try {
+      Stats ps = new Stats();
+      java.io.LineNumberReader r = new java.io.LineNumberReader(
+				   new java.io.InputStreamReader(System.in));
+      String line;
+      while ((line = r.readLine()) != null) {
+        line = line.trim();
+        if (line.equals("") || line.startsWith("@") || line.startsWith("%")) {
+          continue;
+        }
+	java.util.StringTokenizer s 
+          = new java.util.StringTokenizer(line, " ,\t\n\r\f");
+	int count = 0;
+	double v1 = 0;
+	while (s.hasMoreTokens()) {
+	  double val = (new Double(s.nextToken())).doubleValue();
+	  if (count == 0) {
+	    v1 = val;
+	  } else {
+            System.err.println("MSG: Too many values in line \"" 
+                               + line + "\", skipped.");
+	    break;
+	  }
+	  count++;
+	}
+        if (count == 1) {
+          ps.add(v1);
+        }
+      }
+      ps.calculateDerived();
+      System.err.println(ps);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+
+} // Stats
+
Index: branches/MetisMQI/src/main/java/weka/experiment/Task.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/Task.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/Task.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Task.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import java.io.Serializable;
+
+/**
+ * Interface to something that can be remotely executed as a task.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public interface Task extends Serializable {
+  
+  /**
+   * Execute this task.
+   */
+  void execute();
+
+  /**
+   * Clients should be able to call this method at any time to obtain
+   * information on a current task.
+   *
+   * @return a TaskStatusInfo object holding info and result (if available) for
+   * this task
+   */
+  TaskStatusInfo getTaskStatus();
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/TaskStatusInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/TaskStatusInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/TaskStatusInfo.java	(revision 29)
@@ -0,0 +1,131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TaskStatusInfo.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.experiment;
+
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+
+import java.io.Serializable;
+
+/**
+ * A class holding information for tasks being executed
+ * on RemoteEngines. Also holds an object encapsulating any returnable result
+ * produced by the task (Note: result object must be serializable). Task
+ * objects execute methods return instances of this class. RemoteEngines also
+ * use this class for storing progress information for tasks that they
+ * execute.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ */
+public class TaskStatusInfo
+  implements Serializable, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6129343303703560015L;
+  
+  public static final int TO_BE_RUN = 0;
+  public static final int PROCESSING=1;
+  public static final int FAILED=2;
+  public static final int FINISHED=3;
+
+  /**
+   * Holds current execution status.
+   */
+  private int m_ExecutionStatus = TO_BE_RUN;
+
+  /**
+   * Holds current status message.
+   */
+  private String m_StatusMessage = "New Task";
+
+  /**
+   * Holds task result. Set to null for no returnable result.
+   */
+  private Object m_TaskResult = null;
+
+  /**
+   * Set the execution status of this Task.
+   *
+   * @param newStatus the new execution status code
+   */
+  public void setExecutionStatus(int newStatus) {
+    m_ExecutionStatus = newStatus;
+  }
+
+  /**
+   * Get the execution status of this Task.
+   * @return the execution status
+   */
+  public int getExecutionStatus() {
+    return m_ExecutionStatus;
+  }
+
+  /**
+   * Set the status message.
+   *
+   * @param newMessage the new status message
+   */
+  public void setStatusMessage(String newMessage) {
+    m_StatusMessage = newMessage;
+  }
+
+  /**
+   * Get the status message.
+   *
+   * @return the status message
+   */
+  public String getStatusMessage() {
+    return m_StatusMessage;
+  }
+
+  /**
+   * Set the returnable result for this task..
+   *
+   * @param taskResult the new returnable result for the task. null if no
+   * result is returnable.
+   */
+  public void setTaskResult(Object taskResult) {
+    m_TaskResult = taskResult;
+  }
+
+  /**
+   * Get the returnable result of this task.
+   *
+   * @return an object encapsulating the result of executing the task. May
+   * be null if the task has no returnable result (eg. a remote experiment
+   * task that sends its results to a data base).
+   */
+  public Object getTaskResult() {
+    return m_TaskResult;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.5 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/Tester.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/Tester.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/Tester.java	(revision 29)
@@ -0,0 +1,304 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Tester.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.experiment;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Range;
+
+import java.io.Serializable;
+
+/**
+ * Interface for different kinds of Testers in the Experimenter.
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public interface Tester
+  extends Serializable {
+  
+  /**
+   * returns the name of the testing algorithm
+   */
+  public String getDisplayName();
+
+  /**
+   * returns a string that is displayed as tooltip on the "perform test"
+   * button in the experimenter
+   */
+  public String getToolTipText();
+
+  /**
+   * retrieves all the settings from the given Tester
+   *
+   * @param tester      the Tester to get the settings from
+   */
+  public void assign(Tester tester);
+
+  /**
+   * Sets the matrix to use to produce the output.
+   * @param matrix the instance to use to produce the output
+   * @see ResultMatrix
+   */
+  public void setResultMatrix(ResultMatrix matrix);
+
+  /**
+   * Gets the instance that produces the output.
+   * @return the instance to produce the output
+   */
+  public ResultMatrix getResultMatrix();
+
+  /**
+   * Set whether standard deviations are displayed or not.
+   * @param s true if standard deviations are to be displayed
+   */
+  public void setShowStdDevs(boolean s);
+
+  /**
+   * Returns true if standard deviations have been requested.
+   * @return true if standard deviations are to be displayed.
+   */
+  public boolean getShowStdDevs();
+
+  /**
+   * Gets the number of datasets in the resultsets
+   *
+   * @return the number of datasets in the resultsets
+   */
+  public int getNumDatasets();
+
+  /**
+   * Gets the number of resultsets in the data.
+   *
+   * @return the number of resultsets in the data
+   */
+  public int getNumResultsets();
+
+  /**
+   * Gets a string descriptive of the specified resultset.
+   *
+   * @param index the index of the resultset
+   * @return a descriptive string for the resultset
+   */
+  public String getResultsetName(int index);
+  
+  /**
+   * Checks whether the resultset with the given index shall be displayed.
+   * 
+   * @param index the index of the resultset to check whether it shall be displayed 
+   * @return whether the specified resultset is displayed 
+   */
+  public boolean displayResultset(int index);
+  
+  /**
+   * Computes a paired t-test comparison for a specified dataset between
+   * two resultsets.
+   *
+   * @param datasetSpecifier the dataset specifier
+   * @param resultset1Index the index of the first resultset
+   * @param resultset2Index the index of the second resultset
+   * @param comparisonColumn the column containing values to compare
+   * @return the results of the paired comparison
+   * @exception Exception if an error occurs
+   */
+  public PairedStats calculateStatistics(Instance datasetSpecifier,
+					 int resultset1Index,
+					 int resultset2Index,
+					 int comparisonColumn) throws Exception;
+  
+  /**
+   * Creates a key that maps resultset numbers to their descriptions.
+   *
+   * @return a value of type 'String'
+   */
+  public String resultsetKey();
+  
+  /**
+   * Creates a "header" string describing the current resultsets.
+   *
+   * @param comparisonColumn a value of type 'int'
+   * @return a value of type 'String'
+   */
+  public String header(int comparisonColumn);
+
+  /**
+   * Carries out a comparison between all resultsets, counting the number
+   * of datsets where one resultset outperforms the other.
+   *
+   * @param comparisonColumn the index of the comparison column
+   * @return a 2d array where element [i][j] is the number of times resultset
+   * j performed significantly better than resultset i.
+   * @exception Exception if an error occurs
+   */
+  public int [][] multiResultsetWins(int comparisonColumn, int [][] nonSigWin)
+    throws Exception;
+  
+  /**
+   * Carries out a comparison between all resultsets, counting the number
+   * of datsets where one resultset outperforms the other. The results
+   * are summarized in a table.
+   *
+   * @param comparisonColumn the index of the comparison column
+   * @return the results in a string
+   * @exception Exception if an error occurs
+   */
+  public String multiResultsetSummary(int comparisonColumn)
+    throws Exception;
+
+  public String multiResultsetRanking(int comparisonColumn)
+    throws Exception;
+				    
+  /**
+   * Creates a comparison table where a base resultset is compared to the
+   * other resultsets. Results are presented for every dataset.
+   *
+   * @param baseResultset the index of the base resultset
+   * @param comparisonColumn the index of the column to compare over
+   * @return the comparison table string
+   * @exception Exception if an error occurs
+   */
+  public String multiResultsetFull(int baseResultset,
+				   int comparisonColumn) throws Exception;
+
+  /**
+   * Get the value of ResultsetKeyColumns.
+   *
+   * @return Value of ResultsetKeyColumns.
+   */
+  public Range getResultsetKeyColumns();
+  
+  /**
+   * Set the value of ResultsetKeyColumns.
+   *
+   * @param newResultsetKeyColumns Value to assign to ResultsetKeyColumns.
+   */
+  public void setResultsetKeyColumns(Range newResultsetKeyColumns);
+  
+  /**
+   * Gets the indices of the the datasets that are displayed (if <code>null</code>
+   * then all are displayed). The base is always displayed.
+   * 
+   * @return the indices of the datasets to display
+   */
+  public int[] getDisplayedResultsets();
+  
+  /**
+   * Sets the indicies of the datasets to display (<code>null</code> means all).
+   * The base is always displayed.
+   * 
+   * @param cols the indices of the datasets to display
+   */
+  public void setDisplayedResultsets(int[] cols);
+  
+  /**
+   * Get the value of SignificanceLevel.
+   *
+   * @return Value of SignificanceLevel.
+   */
+  public double getSignificanceLevel();
+  
+  /**
+   * Set the value of SignificanceLevel.
+   *
+   * @param newSignificanceLevel Value to assign to SignificanceLevel.
+   */
+  public void setSignificanceLevel(double newSignificanceLevel);
+
+  /**
+   * Get the value of DatasetKeyColumns.
+   *
+   * @return Value of DatasetKeyColumns.
+   */
+  public Range getDatasetKeyColumns();
+  
+  /**
+   * Set the value of DatasetKeyColumns.
+   *
+   * @param newDatasetKeyColumns Value to assign to DatasetKeyColumns.
+   */
+  public void setDatasetKeyColumns(Range newDatasetKeyColumns);
+  
+  /**
+   * Get the value of RunColumn.
+   *
+   * @return Value of RunColumn.
+   */
+  public int getRunColumn();
+  
+  /**
+   * Set the value of RunColumn.
+   *
+   * @param newRunColumn Value to assign to RunColumn.
+   */
+  public void setRunColumn(int newRunColumn);
+
+  /**
+   * Get the value of FoldColumn.
+   *
+   * @return Value of FoldColumn.
+   */
+  public int getFoldColumn();
+  
+  /**
+   * Set the value of FoldColumn.
+   *
+   * @param newFoldColumn Value to assign to FoldColumn.
+   */
+  public void setFoldColumn(int newFoldColumn);
+
+  /**
+   * Returns the name of the column to sort on.
+   *
+   * @return the name of the column to sort on.
+   */
+  public String getSortColumnName();
+
+  /**
+   * Returns the column to sort on, -1 means the default sorting.
+   *
+   * @return the column to sort on.
+   */
+  public int getSortColumn();
+  
+  /**
+   * Set the column to sort on, -1 means the default sorting.
+   *
+   * @param newSortColumn the new sort column.
+   */
+  public void setSortColumn(int newSortColumn);
+  
+  /**
+   * Get the value of Instances.
+   *
+   * @return Value of Instances.
+   */
+  public Instances getInstances();
+  
+  /**
+   * Set the value of Instances.
+   *
+   * @param newInstances Value to assign to Instances.
+   */
+  public void setInstances(Instances newInstances);
+}
Index: branches/MetisMQI/src/main/java/weka/experiment/xml/XMLExperiment.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/experiment/xml/XMLExperiment.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/experiment/xml/XMLExperiment.java	(revision 29)
@@ -0,0 +1,313 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLExperiment.java
+ * Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.experiment.xml;
+
+import java.beans.PropertyDescriptor;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Vector;
+
+import org.w3c.dom.Element;
+
+import weka.core.RevisionUtils;
+import weka.core.xml.XMLBasicSerialization;
+import weka.core.xml.XMLDocument;
+import weka.experiment.Experiment;
+import weka.experiment.PropertyNode;
+
+/**
+ * This class serializes and deserializes an Experiment instance to and
+ * fro XML.<br>
+ * It omits the <code>options</code> from the Experiment, since these are handled
+ * by the get/set-methods. For the <code>Classifier</code> class with all its 
+ * derivative classes it stores only <code>debug</code> and <code>options</code>.
+ * For <code>SplitEvaluator</code> and <code>ResultProducer</code> only the
+ * options are retrieved. The <code>PropertyNode</code> is done manually since
+ * it has no get/set-methods for its public fields.<br>
+ * Since there's no read-method for <code>m_ClassFirst</code> we always save it
+ * as <code>false</code>.
+ * 
+ * @see Experiment#m_ClassFirst
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.6 $ 
+ */
+public class XMLExperiment
+   extends XMLBasicSerialization {
+  
+   /** the name of the classFirst property */
+   public final static String NAME_CLASSFIRST = "classFirst";
+
+   /** PropertyNode member */
+   public final static String NAME_PROPERTYNODE_VALUE = "value";
+
+   /** PropertyNode member */
+   public final static String NAME_PROPERTYNODE_PARENTCLASS = "parentClass";
+
+   /** PropertyNode member */
+   public final static String NAME_PROPERTYNODE_PROPERTY = "property";
+   
+   /**
+    * initializes the serialization
+    * 
+    * @throws Exception if initialization fails
+    */
+   public XMLExperiment() throws Exception {
+      super();
+   }
+   
+   /**
+    * generates internally a new XML document and clears also the IgnoreList and
+    * the mappings for the Read/Write-Methods
+    * 
+    * @throws Exception if initializing fails
+    */
+   public void clear() throws Exception {
+      super.clear();
+
+      // ignore
+      m_Properties.addIgnored(VAL_ROOT + ".options");
+      m_Properties.addIgnored(Experiment.class, "options");
+      
+      // allow
+      m_Properties.addAllowed(weka.classifiers.Classifier.class, "debug");
+      m_Properties.addAllowed(weka.classifiers.Classifier.class, "options");
+      // we assume that classes implementing SplitEvaluator also implement OptionHandler
+      m_Properties.addAllowed(weka.experiment.SplitEvaluator.class, "options");
+      // we assume that classes implementing ResultProducer also implement OptionHandler
+      m_Properties.addAllowed(weka.experiment.ResultProducer.class, "options");
+      
+      // read/write methods
+      m_CustomMethods.register(this, PropertyNode.class, "PropertyNode");
+   }
+   
+   /**
+    * enables derived classes to add other properties to the DOM tree, e.g.
+    * ones that do not apply to the get/set convention of beans. 
+    * 
+    * @param o the object that is serialized into XML
+    * @throws Exception if post-processing fails
+    */
+   protected void writePostProcess(Object o) throws Exception {
+      Element              node;
+      Experiment           exp;
+
+      exp = (Experiment) o;
+      
+      // classfirst
+      node = addElement(m_Document.getDocument().getDocumentElement(), NAME_CLASSFIRST, Boolean.class.getName(), false);
+      node.appendChild(node.getOwnerDocument().createTextNode(new Boolean(false).toString()));   // TODO: get-Method for classFirst in Experiment???
+   }
+   
+   /**
+    * additional post-processing can happen in derived classes after reading 
+    * from XML. 
+    * 
+    * @param o the object to perform some additional processing on
+    * @return the processed object
+    * @throws Exception if post-processing fails
+    */
+   protected Object readPostProcess(Object o) throws Exception {
+      Element              node;
+      Experiment           exp;
+      int                  i;
+      Vector               children;
+
+      exp = (Experiment) o;
+      
+      // classfirst
+      children = XMLDocument.getChildTags(m_Document.getDocument().getDocumentElement());
+      for (i = 0; i < children.size(); i++) {
+         node = (Element) children.get(i);
+         if (node.getAttribute(ATT_NAME).equals(NAME_CLASSFIRST)) {
+            exp.classFirst(new Boolean(XMLDocument.getContent(node)).booleanValue());
+            break;
+         }
+      }
+      
+      return o;
+   }
+   
+   /**
+    * adds the given PropertyNode to a DOM structure. 
+    * 
+    * @param parent the parent of this object, e.g. the class this object is a member of
+    * @param o the Object to describe in XML
+    * @param name the name of the object
+    * @return the node that was created
+    * @throws Exception if the DOM creation fails
+    */
+   public Element writePropertyNode(Element parent, Object o, String name) throws Exception {
+      Element              node;
+      PropertyNode         pnode;
+      Vector               children;
+      int                  i;
+      Element              child;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), name);
+      
+      m_CurrentNode = parent;
+      
+      pnode = (PropertyNode) o;
+      node  = (Element) parent.appendChild(m_Document.getDocument().createElement(TAG_OBJECT));
+      node.setAttribute(ATT_NAME, name);
+      node.setAttribute(ATT_CLASS, pnode.getClass().getName());
+      node.setAttribute(ATT_PRIMITIVE, VAL_NO);
+      node.setAttribute(ATT_ARRAY, VAL_NO);
+      
+      if (pnode.value != null)
+         invokeWriteToXML(node, pnode.value, NAME_PROPERTYNODE_VALUE);
+      if (pnode.parentClass != null)
+         invokeWriteToXML(node, pnode.parentClass.getName(), NAME_PROPERTYNODE_PARENTCLASS);
+      if (pnode.property != null)
+         invokeWriteToXML(node, pnode.property.getDisplayName(), NAME_PROPERTYNODE_PROPERTY);
+      
+      // fix primitive values
+      if (    (pnode.value != null) 
+           && (pnode.property != null) 
+           && (pnode.property.getPropertyType().isPrimitive())) {
+         children = XMLDocument.getChildTags(node);
+         for (i = 0; i < children.size(); i++) {
+            child = (Element) children.get(i);
+            if (!child.getAttribute(ATT_NAME).equals(NAME_PROPERTYNODE_VALUE))
+               continue;
+            child.setAttribute(ATT_CLASS, pnode.property.getPropertyType().getName());
+            child.setAttribute(ATT_PRIMITIVE, VAL_YES);
+         }
+      }
+      
+      return node;
+   }
+
+   /**
+    * builds the PropertyNode from the given DOM node. 
+    * 
+    * @param node the associated XML node
+    * @return the instance created from the XML description
+    * @throws Exception if instantiation fails 
+    * @see javax.swing.DefaultListModel
+    */
+   public Object readPropertyNode(Element node) throws Exception {
+      Object               result;
+      Object               value;
+      String               parentClass;
+      String               property;
+      Vector               children;
+      Element              child;
+      int                  i;
+      Class                cls;
+
+      // for debugging only
+      if (DEBUG)
+         trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+      m_CurrentNode = node;
+      
+      result      = null;
+
+      children    = XMLDocument.getChildTags(node);
+      value       = null;
+      parentClass = null;
+      property    = null;
+      
+      for (i = 0; i < children.size(); i++) {
+         child = (Element) children.get(i);
+         
+         if (child.getAttribute(ATT_NAME).equals(NAME_PROPERTYNODE_VALUE)) {
+            if (stringToBoolean(child.getAttribute(ATT_PRIMITIVE)))
+               value = getPrimitive(child);
+            else
+               value = invokeReadFromXML(child);
+         }
+         if (child.getAttribute(ATT_NAME).equals(NAME_PROPERTYNODE_PARENTCLASS))
+            parentClass = XMLDocument.getContent(child);
+         if (child.getAttribute(ATT_NAME).equals(NAME_PROPERTYNODE_PROPERTY))
+            property = XMLDocument.getContent(child);
+      }
+      
+      if (parentClass != null)
+         cls = Class.forName(parentClass);
+      else
+         cls = null;
+      
+      if (cls != null)
+         result = new PropertyNode(value, new PropertyDescriptor(property, cls), cls);
+      else
+         result = new PropertyNode(value);
+      
+      return result;
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 1.6 $");
+   }
+
+   /**
+    * for testing only. if the first argument is a filename with ".xml"
+    * as extension it tries to generate an instance from the XML description
+    * and does a <code>toString()</code> of the generated object.
+    * Otherwise it loads the binary file, saves the XML representation in a 
+    * file with the original filename appended by ".xml" and once again in a
+    * binary file with the original filename appended by ".exp".
+    * 
+    * @param args 	the commandline arguments
+    * @throws Exception	if something goes wrong, e.g., file not found
+    */
+   public static void main(String[] args) throws Exception {
+      if (args.length > 0) {
+         // read xml and print
+         if (args[0].toLowerCase().endsWith(".xml")) {
+            System.out.println(new XMLExperiment().read(args[0]).toString());
+         }
+         // read binary and print generated XML
+         else {
+            // read
+            FileInputStream fi = new FileInputStream(args[0]);
+            ObjectInputStream oi = new ObjectInputStream(
+                                   new BufferedInputStream(fi));
+            Object o = oi.readObject();
+            oi.close();
+            // print to stdout
+            //new XMLExperiment().write(System.out, o);
+            // write to XML file
+            new XMLExperiment().write(new BufferedOutputStream(new FileOutputStream(args[0] + ".xml")), o);
+            // print to binary file
+            FileOutputStream fo = new FileOutputStream(args[0] + ".exp");
+            ObjectOutputStream oo = new ObjectOutputStream(
+                                   new BufferedOutputStream(fo));
+            oo.writeObject(o);
+            oo.close();
+         }
+      }
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/AllFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/AllFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/AllFilter.java	(revision 29)
@@ -0,0 +1,188 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AllFilter.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+
+/** 
+ * A simple instance filter that passes all instances directly
+ * through. Basically just for testing purposes.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5501 $
+ */
+public class AllFilter
+  extends Filter
+  implements Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = 5022109283147503266L;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An instance filter that passes all instances through unmodified."
+      + " Primarily for testing purposes.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the structure 
+   * 				is required).
+   * @return true 		if the outputFormat may be collected immediately
+   * @throws Exception 		if something goes wrong
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    push((Instance)instance.copy());
+    return true;
+  }
+  
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception {
+    StringBuffer        result;
+    
+    result = new StringBuffer();
+    
+    result.append("class " + className + " {\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters a single row\n");
+    result.append("   * \n");
+    result.append("   * @param i the row to process\n");
+    result.append("   * @return the processed row\n");
+    result.append("   */\n");
+    result.append("  public static Object[] filter(Object[] i) {\n");
+    result.append("    return i;\n");
+    result.append("  }\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters multiple rows\n");
+    result.append("   * \n");
+    result.append("   * @param i the rows to process\n");
+    result.append("   * @return the processed rows\n");
+    result.append("   */\n");
+    result.append("  public static Object[][] filter(Object[][] i) {\n");
+    result.append("    return i;\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5501 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new AllFilter(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/CheckSource.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/CheckSource.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/CheckSource.java	(revision 29)
@@ -0,0 +1,491 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckSource.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A simple class for checking the source generated from Filters
+ * implementing the <code>weka.filters.Sourcable</code> interface.
+ * It takes a filter, the classname of the generated source
+ * and the dataset the source was generated with as parameters and tests
+ * the output of the built filter against the output of the generated
+ * source. Use option '-h' to display all available commandline options.
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classname and options&gt;
+ *  The filter (incl. options) that was used to generate
+ *  the source code.</pre>
+ * 
+ * <pre> -S &lt;classname&gt;
+ *  The classname of the generated source code.</pre>
+ * 
+ * <pre> -t &lt;file&gt;
+ *  The training set with which the source code was generated.</pre>
+ * 
+ * <pre> -c &lt;index&gt;
+ *  The class index of the training set. 'first' and 'last' are
+ *  valid indices.
+ *  (default: none)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after -- are passed to the designated filter.<p>
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ * @see     weka.filters.Sourcable
+ */
+public class CheckSource
+  implements OptionHandler, RevisionHandler {
+
+  /** the classifier used for generating the source code */
+  protected Filter m_Filter = null;
+  
+  /** the generated source code */
+  protected Filter m_SourceCode = null;
+  
+  /** the dataset to use for testing */
+  protected File m_Dataset = null;
+  
+  /** the class index */
+  protected int m_ClassIndex = -1;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+        "\tThe filter (incl. options) that was used to generate\n"
+        + "\tthe source code.",
+        "W", 1, "-W <classname and options>"));
+    
+    result.addElement(new Option(
+        "\tThe classname of the generated source code.",
+        "S", 1, "-S <classname>"));
+    
+    result.addElement(new Option(
+        "\tThe training set with which the source code was generated.",
+        "t", 1, "-t <file>"));
+    
+    result.addElement(new Option(
+        "\tThe class index of the training set. 'first' and 'last' are\n"
+        + "\tvalid indices.\n"
+        + "\t(default: none)",
+        "c", 1, "-c <index>"));
+    
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;classname and options&gt;
+   *  The filter (incl. options) that was used to generate
+   *  the source code.</pre>
+   * 
+   * <pre> -S &lt;classname&gt;
+   *  The classname of the generated source code.</pre>
+   * 
+   * <pre> -t &lt;file&gt;
+   *  The training set with which the source code was generated.</pre>
+   * 
+   * <pre> -c &lt;index&gt;
+   *  The class index of the training set. 'first' and 'last' are
+   *  valid indices.
+   *  (default: none)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after -- are passed to the designated filter.<p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+    String[]    spec;
+    String      classname;
+
+    tmpStr = Utils.getOption('W', options);
+    if (tmpStr.length() > 0) {
+      spec = Utils.splitOptions(tmpStr);
+      if (spec.length == 0)
+        throw new IllegalArgumentException("Invalid filter specification string");
+      classname = spec[0];
+      spec[0]   = "";
+      setFilter((Filter) Utils.forName(Filter.class, classname, spec));
+    }
+    else {
+      throw new Exception("No filter (classname + options) provided!");
+    }
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() > 0) {
+      spec = Utils.splitOptions(tmpStr);
+      if (spec.length != 1)
+        throw new IllegalArgumentException("Invalid source code specification string");
+      classname = spec[0];
+      spec[0]   = "";
+      setSourceCode((Filter) Utils.forName(Filter.class, classname, spec));
+    }
+    else {
+      throw new Exception("No source code (classname) provided!");
+    }
+
+    tmpStr = Utils.getOption('t', options);
+    if (tmpStr.length() != 0)
+      setDataset(new File(tmpStr));
+    else
+      throw new Exception("No dataset provided!");
+
+    tmpStr = Utils.getOption('c', options);
+    if (tmpStr.length() != 0) {
+      if (tmpStr.equals("first"))
+        setClassIndex(0);
+      else if (tmpStr.equals("last"))
+        setClassIndex(-2);
+      else 
+        setClassIndex(Integer.parseInt(tmpStr) - 1);
+    }
+    else {
+      setClassIndex(-1);
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>      result;
+    
+    result  = new Vector<String>();
+
+    if (getFilter() != null) {
+      result.add("-W");
+      result.add(getFilter().getClass().getName() + " " 
+          + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
+    }
+
+    if (getSourceCode() != null) {
+      result.add("-S");
+      result.add(getSourceCode().getClass().getName());
+    }
+
+    if (getDataset() != null) {
+      result.add("-t");
+      result.add(m_Dataset.getAbsolutePath());
+    }
+
+    if (getClassIndex() != -1) {
+      result.add("-c");
+      if (getClassIndex() == -2)
+	result.add("last");
+      else if (getClassIndex() == 0)
+	result.add("first");
+      else 
+	result.add("" + (getClassIndex() + 1));
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the filter to use for the comparison.
+   * 
+   * @param value       the filter to use
+   */
+  public void setFilter(Filter value) {
+    m_Filter = value;
+  }
+  
+  /**
+   * Gets the filter being used for the tests, can be null.
+   * 
+   * @return            the currently set filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+  
+  /**
+   * Sets the class to test.
+   * 
+   * @param value       the class to test
+   */
+  public void setSourceCode(Filter value) {
+    m_SourceCode = value;
+  }
+  
+  /**
+   * Gets the class to test.
+   * 
+   * @return            the currently set class, can be null.
+   */
+  public Filter getSourceCode() {
+    return m_SourceCode;
+  }
+  
+  /**
+   * Sets the dataset to use for testing.
+   * 
+   * @param value       the dataset to use.
+   */
+  public void setDataset(File value) {
+    if (!value.exists())
+      throw new IllegalArgumentException(
+          "Dataset '" + value.getAbsolutePath() + "' does not exist!");
+    else
+      m_Dataset = value;
+  }
+  
+  /**
+   * Gets the dataset to use for testing, can be null.
+   * 
+   * @return            the dataset to use.
+   */
+  public File getDataset() {
+    return m_Dataset;
+  }
+
+  /**
+   * Sets the class index of the dataset.
+   * 
+   * @param value       the class index of the dataset.
+   */
+  public void setClassIndex(int value) {
+    m_ClassIndex = value;
+  }
+  
+  /**
+   * Gets the class index of the dataset.
+   * 
+   * @return            the current class index.
+   */
+  public int getClassIndex() {
+    return m_ClassIndex;
+  }
+
+  /**
+   * compares two Instance
+   * 
+   * @param inst1	the first Instance object to compare
+   * @param inst2	the second Instance object to compare
+   * @return		true if both are the same
+   */
+  protected boolean compare(Instance inst1, Instance inst2) {
+    boolean	result;
+    int		i;
+    
+    // check dimension
+    result = (inst1.numAttributes() == inst2.numAttributes());
+    
+    // check content
+    if (result) {
+      for (i = 0; i < inst1.numAttributes(); i++) {
+	if (Double.isNaN(inst1.value(i)) && (Double.isNaN(inst2.value(i))))
+	  continue;
+	
+	if (inst1.value(i) != inst2.value(i)) {
+	  result = false;
+	  System.out.println(
+	      "Values at position " + (i+1) + " differ (Filter/Source code): " 
+	      + inst1.value(i) + " != " + inst2.value(i));
+	  break;
+	}
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * compares the two Instances objects
+   * 
+   * @param inst1	the first Instances object to compare
+   * @param inst2	the second Instances object to compare
+   * @return		true if both are the same
+   */
+  protected boolean compare(Instances inst1, Instances inst2) {
+    boolean	result;
+    int		i;
+    
+    // check dimensions
+    result = (inst1.numInstances() == inst2.numInstances());
+
+    // check content
+    if (result) {
+      for (i = 0; i < inst1.numInstances(); i++) {
+	result = compare(inst1.instance(i), inst2.instance(i));
+	if (!result) {
+	  System.out.println(
+	      "Values in line " + (i+1) + " differ!");
+	  break;
+	}
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * performs the comparison test
+   * 
+   * @return            true if tests were successful
+   * @throws Exception  if tests fail
+   */
+  public boolean execute() throws Exception {
+    boolean     result;
+    Instances   data;
+    Instance	filteredInstance;
+    Instances	filteredInstances;
+    Instance	filteredInstanceSource;
+    Instances	filteredInstancesSource;
+    DataSource  source;
+    Filter	filter;
+    Filter	filterSource;
+    int		i;
+    
+    result = true;
+    
+    // a few checks
+    if (getFilter() == null)
+      throw new Exception("No filter set!");
+    if (getSourceCode() == null)
+      throw new Exception("No source code set!");
+    if (getDataset() == null)
+      throw new Exception("No dataset set!");
+    if (!getDataset().exists())
+      throw new Exception(
+          "Dataset '" + getDataset().getAbsolutePath() + "' does not exist!");
+    
+    // load data
+    source = new DataSource(getDataset().getAbsolutePath());
+    data   = source.getDataSet();
+    if (getClassIndex() == -2)
+      data.setClassIndex(data.numAttributes() - 1);
+    else
+      data.setClassIndex(getClassIndex());
+    
+    // compare output
+    // 1. batch filtering
+    filter = Filter.makeCopy(getFilter());
+    filter.setInputFormat(data);
+    filteredInstances = Filter.useFilter(data, filter);
+
+    filterSource = Filter.makeCopy(getSourceCode());
+    filterSource.setInputFormat(data);
+    filteredInstancesSource = Filter.useFilter(data, filterSource);
+
+    result = compare(filteredInstances, filteredInstancesSource);
+    
+    // 2. instance by instance
+    if (result) {
+      filter = Filter.makeCopy(getFilter());
+      filter.setInputFormat(data);
+      Filter.useFilter(data, filter);
+      
+      filterSource = Filter.makeCopy(getSourceCode());
+      filterSource.setInputFormat(data);
+
+      for (i = 0; i < data.numInstances(); i++) {
+	filter.input(data.instance(i));
+	filter.batchFinished();
+	filteredInstance = filter.output();
+	
+	filterSource.input(data.instance(i));
+	filterSource.batchFinished();
+	filteredInstanceSource = filterSource.output();
+	
+	if (!compare(filteredInstance, filteredInstanceSource))
+	  System.out.println(
+	      (i+1) + ". instance (Filter/Source code): " 
+	      + filteredInstance + " != " + filteredInstanceSource);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.3 $");
+  }
+  
+  /**
+   * Executes the tests, use "-h" to list the commandline options.
+   * 
+   * @param args        the commandline parameters
+   * @throws Exception  if something goes wrong
+   */
+  public static void main(String[] args) throws Exception{
+    CheckSource         check;
+    StringBuffer        text;
+    Enumeration         enm;
+    
+    check = new CheckSource();
+    if (Utils.getFlag('h', args)) {
+      text = new StringBuffer();   
+      text.append("\nHelp requested:\n\n");
+      enm = check.listOptions();
+      while (enm.hasMoreElements()) {
+        Option option = (Option) enm.nextElement();
+        text.append(option.synopsis() + "\n");
+        text.append(option.description() + "\n");
+      }
+      System.out.println("\n" + text + "\n");
+    }
+    else {
+      check.setOptions(args);
+      if (check.execute())
+        System.out.println("Tests OK!");
+      else
+        System.out.println("Tests failed!");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/Filter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/Filter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/Filter.java	(revision 29)
@@ -0,0 +1,1347 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Filter.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Queue;
+import weka.core.RelationalLocator;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SerializedObject;
+import weka.core.StringLocator;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.Capabilities.Capability;
+import weka.core.converters.ConverterUtils.DataSource;
+
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/** 
+ * An abstract class for instance filters: objects that take instances
+ * as input, carry out some transformation on the instance and then
+ * output the instance. The method implementations in this class
+ * assume that most of the work will be done in the methods overridden
+ * by subclasses.<p>
+ *
+ * A simple example of filter use. This example doesn't remove
+ * instances from the output queue until all instances have been
+ * input, so has higher memory consumption than an approach that
+ * uses output instances as they are made available:<p>
+ *
+ * <code> <pre>
+ *  Filter filter = ..some type of filter..
+ *  Instances instances = ..some instances..
+ *  for (int i = 0; i < data.numInstances(); i++) {
+ *    filter.input(data.instance(i));
+ *  }
+ *  filter.batchFinished();
+ *  Instances newData = filter.outputFormat();
+ *  Instance processed;
+ *  while ((processed = filter.output()) != null) {
+ *    newData.add(processed);
+ *  }
+ *  ..do something with newData..
+ * </pre> </code>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5500 $
+ */
+public abstract class Filter
+  implements Serializable, CapabilitiesHandler, RevisionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8835063755891851218L;
+
+  /** The output format for instances */
+  private Instances m_OutputFormat = null;
+
+  /** The output instance queue */
+  private Queue m_OutputQueue = null;
+
+  /** Indices of string attributes in the output format */
+  protected StringLocator m_OutputStringAtts = null;
+
+  /** Indices of string attributes in the input format */
+  protected StringLocator m_InputStringAtts = null;
+
+  /** Indices of relational attributes in the output format */
+  protected RelationalLocator m_OutputRelAtts = null;
+
+  /** Indices of relational attributes in the input format */
+  protected RelationalLocator m_InputRelAtts = null;
+
+  /** The input format for instances */
+  private Instances m_InputFormat = null;
+
+  /** Record whether the filter is at the start of a batch */
+  protected boolean m_NewBatch = true;
+
+  /** True if the first batch has been done */
+  protected boolean m_FirstBatchDone = false;
+
+  /**
+   * Returns true if the a new batch was started, either a new instance of the 
+   * filter was created or the batchFinished() method got called.
+   * 
+   * @return true if a new batch has been initiated
+   * @see #m_NewBatch
+   * @see #batchFinished()
+   */
+  public boolean isNewBatch() {
+    return m_NewBatch;
+  }
+  
+  /**
+   * Returns true if the first batch of instances got processed. Necessary for
+   * supervised filters, which "learn" from the first batch and then shouldn't
+   * get updated with subsequent calls of batchFinished().
+   * 
+   * @return true if the first batch has been processed
+   * @see #m_FirstBatchDone
+   * @see #batchFinished()
+   */
+  public boolean isFirstBatchDone() {
+    return m_FirstBatchDone;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter. Derived filters have to
+   * override this method to enable capabilities.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+
+    result = new Capabilities(this);
+    result.enableAll();
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return            the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5500 $");
+  }
+
+  /** 
+   * Returns the Capabilities of this filter, customized based on the data.
+   * I.e., if removes all class capabilities, in case there's not class
+   * attribute present or removes the NO_CLASS capability, in case that
+   * there's a class present.
+   *
+   * @param data	the data to use for customization
+   * @return            the capabilities of this object, based on the data
+   * @see               #getCapabilities()
+   */
+  public Capabilities getCapabilities(Instances data) {
+    Capabilities 	result;
+    Capabilities 	classes;
+    Iterator		iter;
+    Capability		cap;
+
+    result = getCapabilities();
+
+    // no class? -> remove all class capabilites apart from NO_CLASS
+    if (data.classIndex() == -1) {
+      classes = result.getClassCapabilities();
+      iter    = classes.capabilities();
+      while (iter.hasNext()) {
+	cap = (Capability) iter.next();
+	if (cap != Capability.NO_CLASS) {
+	  result.disable(cap);
+	  result.disableDependency(cap);
+	}
+      }
+    }
+    // class? -> remove NO_CLASS
+    else {
+      result.disable(Capability.NO_CLASS);
+      result.disableDependency(Capability.NO_CLASS);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of output instances. The derived class should use this
+   * method once it has determined the outputformat. The 
+   * output queue is cleared.
+   *
+   * @param outputFormat the new output format
+   */
+  protected void setOutputFormat(Instances outputFormat) {
+
+    if (outputFormat != null) {
+      m_OutputFormat = outputFormat.stringFreeStructure();
+      initOutputLocators(m_OutputFormat, null);
+
+      // Rename the relation
+      String relationName = outputFormat.relationName() 
+        + "-" + this.getClass().getName();
+      if (this instanceof OptionHandler) {
+        String [] options = ((OptionHandler)this).getOptions();
+        for (int i = 0; i < options.length; i++) {
+          relationName += options[i].trim();
+        }
+      }
+      m_OutputFormat.setRelationName(relationName);
+    } else {
+      m_OutputFormat = null;
+    }
+    m_OutputQueue = new Queue();
+  }
+
+  /**
+   * Gets the currently set inputformat instances. This dataset may contain
+   * buffered instances.
+   *
+   * @return the input Instances.
+   */
+  protected Instances getInputFormat() {
+
+    return m_InputFormat;
+  }
+
+  /**
+   * Returns a reference to the current input format without
+   * copying it.
+   *
+   * @return a reference to the current input format
+   */
+  protected Instances inputFormatPeek() {
+
+    return m_InputFormat;
+  }
+
+  /**
+   * Returns a reference to the current output format without
+   * copying it.
+   *
+   * @return a reference to the current output format
+   */
+  protected Instances outputFormatPeek() {
+
+    return m_OutputFormat;
+  }
+
+  /**
+   * Adds an output instance to the queue. The derived class should use this
+   * method for each output instance it makes available. 
+   *
+   * @param instance the instance to be added to the queue.
+   */
+  protected void push(Instance instance) {
+
+    if (instance != null) {
+      if (instance.dataset() != null)
+	copyValues(instance, false);
+      instance.setDataset(m_OutputFormat);
+      m_OutputQueue.push(instance);
+    }
+  }
+
+  /**
+   * Clears the output queue.
+   */
+  protected void resetQueue() {
+
+    m_OutputQueue = new Queue();
+  }
+
+  /**
+   * Adds the supplied input instance to the inputformat dataset for
+   * later processing.  Use this method rather than
+   * getInputFormat().add(instance). Or else. Note that the provided
+   * instance gets copied when buffered. 
+   *
+   * @param instance the <code>Instance</code> to buffer.  
+   */
+  protected void bufferInput(Instance instance) {
+
+    if (instance != null) {
+      copyValues(instance, true);
+      m_InputFormat.add(instance);
+    }
+  }
+
+  /**
+   * Initializes the input attribute locators. If indices is null then all 
+   * attributes of the data will be considered, otherwise only the ones
+   * that were provided.
+   * 
+   * @param data		the data to initialize the locators with
+   * @param indices		if not null, the indices to which to restrict
+   * 				the locating
+   */
+  protected void initInputLocators(Instances data, int[] indices) {
+    if (indices == null) {
+      m_InputStringAtts = new StringLocator(data);
+      m_InputRelAtts    = new RelationalLocator(data);
+    }
+    else {
+      m_InputStringAtts = new StringLocator(data, indices);
+      m_InputRelAtts    = new RelationalLocator(data, indices);
+    }
+  }
+
+  /**
+   * Initializes the output attribute locators. If indices is null then all 
+   * attributes of the data will be considered, otherwise only the ones
+   * that were provided.
+   * 
+   * @param data		the data to initialize the locators with
+   * @param indices		if not null, the indices to which to restrict
+   * 				the locating
+   */
+  protected void initOutputLocators(Instances data, int[] indices) {
+    if (indices == null) {
+      m_OutputStringAtts = new StringLocator(data);
+      m_OutputRelAtts    = new RelationalLocator(data);
+    }
+    else {
+      m_OutputStringAtts = new StringLocator(data, indices);
+      m_OutputRelAtts    = new RelationalLocator(data, indices);
+    }
+  }
+  
+  /**
+   * Copies string/relational values contained in the instance copied to a new
+   * dataset. The Instance must already be assigned to a dataset. This
+   * dataset and the destination dataset must have the same structure.
+   *
+   * @param instance		the Instance containing the string/relational 
+   * 				values to copy.
+   * @param isInput		if true the input format and input attribute 
+   * 				locators are used otherwise the output format 
+   * 				and output locators
+   */
+  protected void copyValues(Instance instance, boolean isInput) {
+
+    RelationalLocator.copyRelationalValues(
+	instance, 
+	(isInput) ? m_InputFormat : m_OutputFormat, 
+	(isInput) ? m_InputRelAtts : m_OutputRelAtts);
+
+    StringLocator.copyStringValues(
+	instance, 
+	(isInput) ? m_InputFormat : m_OutputFormat, 
+	(isInput) ? m_InputStringAtts : m_OutputStringAtts);
+  }
+
+  /**
+   * Takes string/relational values referenced by an Instance and copies them 
+   * from a source dataset to a destination dataset. The instance references are
+   * updated to be valid for the destination dataset. The instance may have the 
+   * structure (i.e. number and attribute position) of either dataset (this
+   * affects where references are obtained from). Only works if the number
+   * of string/relational attributes is the same in both indices (implicitly 
+   * these string/relational attributes should be semantically same but just 
+   * with shifted positions).
+   *
+   * @param instance 		the instance containing references to strings/
+   * 				relational values in the source dataset that 
+   * 				will have references updated to be valid for 
+   * 				the destination dataset.
+   * @param instSrcCompat 	true if the instance structure is the same as 
+   * 				the source, or false if it is the same as the 
+   * 				destination (i.e. which of the string/relational 
+   * 				attribute indices contains the correct locations 
+   * 				for this instance).
+   * @param srcDataset 		the dataset for which the current instance 
+   * 				string/relational value references are valid 
+   * 				(after any position mapping if needed)
+   * @param destDataset 	the dataset for which the current instance 
+   * 				string/relational value references need to be 
+   * 				inserted (after any position mapping if needed)
+   */
+  protected void copyValues(Instance instance, boolean instSrcCompat,
+                         Instances srcDataset, Instances destDataset) {
+
+    RelationalLocator.copyRelationalValues(
+	instance, instSrcCompat, 
+	srcDataset, m_InputRelAtts,
+	destDataset, m_OutputRelAtts);
+
+    StringLocator.copyStringValues(
+	instance, instSrcCompat, 
+	srcDataset, m_InputStringAtts,
+	getOutputFormat(), m_OutputStringAtts);
+  }
+
+  /**
+   * This will remove all buffered instances from the inputformat dataset.
+   * Use this method rather than getInputFormat().delete();
+   */
+  protected void flushInput() {
+
+    if (    (m_InputStringAtts.getAttributeIndices().length > 0) 
+	 || (m_InputRelAtts.getAttributeIndices().length > 0) ) {
+      m_InputFormat = m_InputFormat.stringFreeStructure();
+    } else {
+      // This more efficient than new Instances(m_InputFormat, 0);
+      m_InputFormat.delete();
+    }
+  }
+  
+  /**
+   * tests the data whether the filter can actually handle it
+   * 
+   * @param instanceInfo	the data to test
+   * @throws Exception		if the test fails
+   */
+  protected void testInputFormat(Instances instanceInfo) throws Exception {
+    getCapabilities(instanceInfo).testWithFail(instanceInfo);
+  }
+
+  /**
+   * Sets the format of the input instances. If the filter is able to
+   * determine the output format before seeing any input instances, it
+   * does so here. This default implementation clears the output format
+   * and output queue, and the new batch flag is set. Overriders should
+   * call <code>super.setInputFormat(Instances)</code>
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    testInputFormat(instanceInfo);
+    
+    m_InputFormat = instanceInfo.stringFreeStructure();
+    m_OutputFormat = null;
+    m_OutputQueue = new Queue();
+    m_NewBatch = true;
+    m_FirstBatchDone = false;
+    initInputLocators(m_InputFormat, null);
+    return false;
+  }
+
+  /**
+   * Gets the format of the output instances. This should only be called
+   * after input() or batchFinished() has returned true. The relation
+   * name of the output instances should be changed to reflect the
+   * action of the filter (eg: add the filter name and options).
+   *
+   * @return an Instances object containing the output instance
+   * structure only.
+   * @throws NullPointerException if no input structure has been
+   * defined (or the output format hasn't been determined yet) 
+   */
+  public Instances getOutputFormat() {
+
+    if (m_OutputFormat == null) {
+      throw new NullPointerException("No output format defined.");
+    }
+    return new Instances(m_OutputFormat, 0);
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is
+   * processed and made available for output immediately. Some filters
+   * require all instances be read before producing output, in which
+   * case output instances should be collected after calling
+   * batchFinished(). If the input marks the start of a new batch, the
+   * output queue is cleared. This default implementation assumes all
+   * instance conversion will occur when batchFinished() is called.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws NullPointerException if the input format has not been
+   * defined.
+   * @throws Exception if the input instance was not of the correct 
+   * format or if there was a problem with the filtering.  
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (m_InputFormat == null) {
+      throw new NullPointerException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      m_OutputQueue = new Queue();
+      m_NewBatch = false;
+    }
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If
+   * the filter requires all instances prior to filtering, output()
+   * may now be called to retrieve the filtered instances. Any
+   * subsequent instances filtered should be filtered based on setting
+   * obtained from the first batch (unless the inputFormat has been
+   * re-assigned or new options have been set). This default
+   * implementation assumes all instance processing occurs during
+   * inputFormat() and input().
+   *
+   * @return true if there are instances pending output
+   * @throws NullPointerException if no input structure has been defined,
+   * @throws Exception if there was a problem finishing the batch.
+   */
+  public boolean batchFinished() throws Exception {
+
+    if (m_InputFormat == null) {
+      throw new NullPointerException("No input instance format defined");
+    }
+    flushInput();
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+
+  /**
+   * Output an instance after filtering and remove from the output queue.
+   *
+   * @return the instance that has most recently been filtered (or null if
+   * the queue is empty).
+   * @throws NullPointerException if no output structure has been defined
+   */
+  public Instance output() {
+
+    if (m_OutputFormat == null) {
+      throw new NullPointerException("No output instance format defined");
+    }
+    if (m_OutputQueue.empty()) {
+      return null;
+    }
+    Instance result = (Instance)m_OutputQueue.pop();
+    
+    // Clear out references to old strings/relationals occasionally
+    /*if (m_OutputQueue.empty() && m_NewBatch) {
+      if (    (m_OutputStringAtts.getAttributeIndices().length > 0)
+	   || (m_OutputRelAtts.getAttributeIndices().length > 0) ) {
+        m_OutputFormat = m_OutputFormat.stringFreeStructure();
+      }
+    }*/
+    return result;
+  }
+  
+  /**
+   * Output an instance after filtering but do not remove from the
+   * output queue.
+   *
+   * @return the instance that has most recently been filtered (or null if
+   * the queue is empty).
+   * @throws NullPointerException if no input structure has been defined 
+   */
+  public Instance outputPeek() {
+
+    if (m_OutputFormat == null) {
+      throw new NullPointerException("No output instance format defined");
+    }
+    if (m_OutputQueue.empty()) {
+      return null;
+    }
+    Instance result = (Instance)m_OutputQueue.peek();
+    return result;
+  }
+
+  /**
+   * Returns the number of instances pending output
+   *
+   * @return the number of instances  pending output
+   * @throws NullPointerException if no input structure has been defined
+   */
+  public int numPendingOutput() {
+
+    if (m_OutputFormat == null) {
+      throw new NullPointerException("No output instance format defined");
+    }
+    return m_OutputQueue.size();
+  }
+
+  /**
+   * Returns whether the output format is ready to be collected
+   *
+   * @return true if the output format is set
+   */
+  public boolean isOutputFormatDefined() {
+
+    return (m_OutputFormat != null);
+  }
+
+  /**
+   * Creates a deep copy of the given filter using serialization.
+   *
+   * @param model 	the filter to copy
+   * @return 		a deep copy of the filter
+   * @throws Exception 	if an error occurs
+   */
+  public static Filter makeCopy(Filter model) throws Exception {
+    return (Filter)new SerializedObject(model).getObject();
+  }
+
+  /**
+   * Creates a given number of deep copies of the given filter using 
+   * serialization.
+   * 
+   * @param model 	the filter to copy
+   * @param num 	the number of filter copies to create.
+   * @return 		an array of filters.
+   * @throws Exception 	if an error occurs
+   */
+  public static Filter[] makeCopies(Filter model, int num) throws Exception {
+
+    if (model == null) {
+      throw new Exception("No model filter set");
+    }
+    Filter[] filters = new Filter[num];
+    SerializedObject so = new SerializedObject(model);
+    for (int i = 0; i < filters.length; i++) {
+      filters[i] = (Filter) so.getObject();
+    }
+    return filters;
+  }
+  
+  /**
+   * Filters an entire set of instances through a filter and returns
+   * the new set. 
+   *
+   * @param data the data to be filtered
+   * @param filter the filter to be used
+   * @return the filtered set of data
+   * @throws Exception if the filter can't be used successfully
+   */
+  public static Instances useFilter(Instances data,
+				    Filter filter) throws Exception {
+    /*
+    System.err.println(filter.getClass().getName() 
+                       + " in:" + data.numInstances());
+    */
+    for (int i = 0; i < data.numInstances(); i++) {
+      filter.input(data.instance(i));
+    }
+    filter.batchFinished();
+    Instances newData = filter.getOutputFormat();
+    Instance processed;
+    while ((processed = filter.output()) != null) {
+      newData.add(processed);
+    }
+
+    /*
+    System.err.println(filter.getClass().getName() 
+                       + " out:" + newData.numInstances());
+    */
+    return newData;
+  }
+
+  /**
+   * Returns a description of the filter, by default only the classname.
+   * 
+   * @return a string describing the filter
+   */
+  public String toString() {
+    return this.getClass().getName();
+  }
+  
+  /**
+   * generates source code from the filter
+   * 
+   * @param filter the filter to output as source
+   * @param className the name of the generated class
+   * @param input the input data the header is generated for
+   * @param output the output data the header is generated for
+   * @return the generated source code
+   * @throws Exception if source code cannot be generated
+   */
+  public static String wekaStaticWrapper(
+      Sourcable filter, String className, Instances input, Instances output) 
+    throws Exception {
+    
+    StringBuffer	result;
+    int			i;
+    int			n;
+    
+    result = new StringBuffer();
+    
+    result.append("// Generated with Weka " + Version.VERSION + "\n");
+    result.append("//\n");
+    result.append("// This code is public domain and comes with no warranty.\n");
+    result.append("//\n");
+    result.append("// Timestamp: " + new Date() + "\n");
+    result.append("// Relation: " + input.relationName() + "\n");
+    result.append("\n");
+    
+    result.append("package weka.filters;\n");
+    result.append("\n");
+    result.append("import weka.core.Attribute;\n");
+    result.append("import weka.core.Capabilities;\n");
+    result.append("import weka.core.Capabilities.Capability;\n");
+    result.append("import weka.core.FastVector;\n");
+    result.append("import weka.core.Instance;\n");
+    result.append("import weka.core.Instances;\n");
+    result.append("import weka.filters.Filter;\n");
+    result.append("\n");
+    result.append("public class WekaWrapper\n");
+    result.append("  extends Filter {\n");
+
+    // globalInfo
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns only the toString() method.\n");
+    result.append("   *\n");
+    result.append("   * @return a string describing the filter\n");
+    result.append("   */\n");
+    result.append("  public String globalInfo() {\n");
+    result.append("    return toString();\n");
+    result.append("  }\n");
+    
+    // getCapabilities
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns the capabilities of this filter.\n");
+    result.append("   *\n");
+    result.append("   * @return the capabilities\n");
+    result.append("   */\n");
+    result.append("  public Capabilities getCapabilities() {\n");
+    result.append(((Filter) filter).getCapabilities().toSource("result", 4));
+    result.append("    return result;\n");
+    result.append("  }\n");
+
+    // objectsToInstance
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * turns array of Objects into an Instance object\n");
+    result.append("   *\n");
+    result.append("   * @param obj	the Object array to turn into an Instance\n");
+    result.append("   * @param format	the data format to use\n");
+    result.append("   * @return		the generated Instance object\n");
+    result.append("   */\n");
+    result.append("  protected Instance objectsToInstance(Object[] obj, Instances format) {\n");
+    result.append("    Instance		result;\n");
+    result.append("    double[]		values;\n");
+    result.append("    int		i;\n");
+    result.append("\n");  
+    result.append("    values = new double[obj.length];\n");
+    result.append("\n");
+    result.append("    for (i = 0 ; i < obj.length; i++) {\n");
+    result.append("      if (obj[i] == null)\n");
+    result.append("        values[i] = Instance.missingValue();\n");
+    result.append("      else if (format.attribute(i).isNumeric())\n");
+    result.append("        values[i] = (Double) obj[i];\n");
+    result.append("      else if (format.attribute(i).isNominal())\n");
+    result.append("        values[i] = format.attribute(i).indexOfValue((String) obj[i]);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    // create new instance\n");
+    result.append("    result = new Instance(1.0, values);\n");
+    result.append("    result.setDataset(format);\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+
+    // instanceToObjects
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * turns the Instance object into an array of Objects\n");
+    result.append("   *\n");
+    result.append("   * @param inst	the instance to turn into an array\n");
+    result.append("   * @return		the Object array representing the instance\n");
+    result.append("   */\n");
+    result.append("  protected Object[] instanceToObjects(Instance inst) {\n");
+    result.append("    Object[]	result;\n");
+    result.append("    int		i;\n");
+    result.append("\n");  
+    result.append("    result = new Object[inst.numAttributes()];\n");
+    result.append("\n");
+    result.append("    for (i = 0 ; i < inst.numAttributes(); i++) {\n");
+    result.append("      if (inst.isMissing(i))\n");
+    result.append("  	result[i] = null;\n");
+    result.append("      else if (inst.attribute(i).isNumeric())\n");
+    result.append("  	result[i] = inst.value(i);\n");
+    result.append("      else\n");
+    result.append("  	result[i] = inst.stringValue(i);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+
+    // instancesToObjects
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * turns the Instances object into an array of Objects\n");
+    result.append("   *\n");
+    result.append("   * @param data	the instances to turn into an array\n");
+    result.append("   * @return		the Object array representing the instances\n");
+    result.append("   */\n");
+    result.append("  protected Object[][] instancesToObjects(Instances data) {\n");
+    result.append("    Object[][]	result;\n");
+    result.append("    int		i;\n");
+    result.append("\n");  
+    result.append("    result = new Object[data.numInstances()][];\n");
+    result.append("\n");  
+    result.append("    for (i = 0; i < data.numInstances(); i++)\n");
+    result.append("      result[i] = instanceToObjects(data.instance(i));\n");
+    result.append("\n");  
+    result.append("    return result;\n");
+    result.append("  }\n");
+    
+    // setInputFormat
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Only tests the input data.\n");
+    result.append("   *\n");
+    result.append("   * @param instanceInfo the format of the data to convert\n");
+    result.append("   * @return always true, to indicate that the output format can \n");
+    result.append("   *         be collected immediately.\n");
+    result.append("   */\n");
+    result.append("  public boolean setInputFormat(Instances instanceInfo) throws Exception {\n");
+    result.append("    super.setInputFormat(instanceInfo);\n");
+    result.append("    \n");
+    result.append("    // generate output format\n");
+    result.append("    FastVector atts = new FastVector();\n");
+    result.append("    FastVector attValues;\n");
+    for (i = 0; i < output.numAttributes(); i++) {
+      result.append("    // " + output.attribute(i).name() + "\n");
+      if (output.attribute(i).isNumeric()) {
+	result.append("    atts.addElement(new Attribute(\"" 
+	    + output.attribute(i).name() + "\"));\n");
+      }
+      else if (output.attribute(i).isNominal()) {
+	result.append("    attValues = new FastVector();\n");
+	for (n = 0; n < output.attribute(i).numValues(); n++) {
+	  result.append("    attValues.addElement(\"" + output.attribute(i).value(n) + "\");\n");
+	}
+	result.append("    atts.addElement(new Attribute(\"" 
+	    + output.attribute(i).name() + "\", attValues));\n");
+      }
+      else {
+	throw new UnsupportedAttributeTypeException(
+	    "Attribute type '" + output.attribute(i).type() + "' (position " 
+	    + (i+1) + ") is not supported!");
+      }
+    }
+    result.append("    \n");
+    result.append("    Instances format = new Instances(\"" + output.relationName() + "\", atts, 0);\n");
+    result.append("    format.setClassIndex(" + output.classIndex() + ");\n");
+    result.append("    setOutputFormat(format);\n");
+    result.append("    \n");
+    result.append("    return true;\n");
+    result.append("  }\n");
+    
+    // input
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Directly filters the instance.\n");
+    result.append("   *\n");
+    result.append("   * @param instance the instance to convert\n");
+    result.append("   * @return always true, to indicate that the output can \n");
+    result.append("   *         be collected immediately.\n");
+    result.append("   */\n");
+    result.append("  public boolean input(Instance instance) throws Exception {\n");
+    result.append("    Object[] filtered = " + className + ".filter(instanceToObjects(instance));\n");
+    result.append("    push(objectsToInstance(filtered, getOutputFormat()));\n");
+    result.append("    return true;\n");
+    result.append("  }\n");
+    
+    // batchFinished
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Performs a batch filtering of the buffered data, if any available.\n");
+    result.append("   *\n");
+    result.append("   * @return true if instances were filtered otherwise false\n");
+    result.append("   */\n");
+    result.append("  public boolean batchFinished() throws Exception {\n");
+    result.append("    if (getInputFormat() == null)\n");
+    result.append("      throw new NullPointerException(\"No input instance format defined\");;\n");
+    result.append("\n");
+    result.append("    Instances inst = getInputFormat();\n");
+    result.append("    if (inst.numInstances() > 0) {\n");
+    result.append("      Object[][] filtered = " + className + ".filter(instancesToObjects(inst));\n");
+    result.append("      for (int i = 0; i < filtered.length; i++) {\n");
+    result.append("        push(objectsToInstance(filtered[i], getOutputFormat()));\n");
+    result.append("      }\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    flushInput();\n");
+    result.append("    m_NewBatch = true;\n");
+    result.append("    m_FirstBatchDone = true;\n");
+    result.append("\n");
+    result.append("    return (inst.numInstances() > 0);\n");
+    result.append("  }\n");
+
+    // toString
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Returns only the classnames and what filter it is based on.\n");
+    result.append("   *\n");
+    result.append("   * @return a short description\n");
+    result.append("   */\n");
+    result.append("  public String toString() {\n");
+    result.append("    return \"Auto-generated filter wrapper, based on " 
+	+ filter.getClass().getName() + " (generated with Weka " + Version.VERSION + ").\\n" 
+	+ "\" + this.getClass().getName() + \"/" + className + "\";\n");
+    result.append("  }\n");
+    
+    // main
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * Runs the filter from commandline.\n");
+    result.append("   *\n");
+    result.append("   * @param args the commandline arguments\n");
+    result.append("   */\n");
+    result.append("  public static void main(String args[]) {\n");
+    result.append("    runFilter(new WekaWrapper(), args);\n");
+    result.append("  }\n");
+    result.append("}\n");
+
+    // actual filter code
+    result.append("\n");
+    result.append(filter.toSource(className, input));
+    
+    return result.toString();
+  }
+  
+  /**
+   * Method for testing filters.
+   *
+   * @param filter the filter to use
+   * @param options should contain the following arguments: <br/>
+   * -i input_file <br/>
+   * -o output_file <br/>
+   * -c class_index <br/>
+   * -z classname (for filters implementing weka.filters.Sourcable) <br/>
+   * or -h for help on options
+   * @throws Exception if something goes wrong or the user requests help on
+   * command options
+   */
+  public static void filterFile(Filter filter, String [] options) 
+    throws Exception {
+
+    boolean debug = false;
+    Instances data = null;
+    DataSource input = null;
+    PrintWriter output = null;
+    boolean helpRequest;
+    String sourceCode = "";
+
+    try {
+       helpRequest = Utils.getFlag('h', options);
+
+      if (Utils.getFlag('d', options)) {
+	debug = true;
+      }
+      String infileName = Utils.getOption('i', options);
+      String outfileName = Utils.getOption('o', options); 
+      String classIndex = Utils.getOption('c', options);
+      if (filter instanceof Sourcable)
+	sourceCode = Utils.getOption('z', options);
+      
+      if (filter instanceof OptionHandler) {
+	((OptionHandler)filter).setOptions(options);
+      }
+
+      Utils.checkForRemainingOptions(options);
+      if (helpRequest) {
+	throw new Exception("Help requested.\n");
+      }
+      if (infileName.length() != 0) {
+	input = new DataSource(infileName);
+      } else {
+	input = new DataSource(System.in);
+      }
+      if (outfileName.length() != 0) {
+	output = new PrintWriter(new FileOutputStream(outfileName));
+      } else { 
+	output = new PrintWriter(System.out);
+      }
+
+      data = input.getStructure();
+      if (classIndex.length() != 0) {
+	if (classIndex.equals("first")) {
+	  data.setClassIndex(0);
+	} else if (classIndex.equals("last")) {
+	  data.setClassIndex(data.numAttributes() - 1);
+	} else {
+	  data.setClassIndex(Integer.parseInt(classIndex) - 1);
+	}
+      }
+    } catch (Exception ex) {
+      String filterOptions = "";
+      // Output the error and also the valid options
+      if (filter instanceof OptionHandler) {
+	filterOptions += "\nFilter options:\n\n";
+	Enumeration enu = ((OptionHandler)filter).listOptions();
+	while (enu.hasMoreElements()) {
+	  Option option = (Option) enu.nextElement();
+	  filterOptions += option.synopsis() + '\n'
+	    + option.description() + "\n";
+	}
+      }
+
+      String genericOptions = "\nGeneral options:\n\n"
+	+ "-h\n"
+	+ "\tGet help on available options.\n"
+	+ "\t(use -b -h for help on batch mode.)\n"
+	+ "-i <file>\n"
+	+ "\tThe name of the file containing input instances.\n"
+	+ "\tIf not supplied then instances will be read from stdin.\n"
+	+ "-o <file>\n"
+	+ "\tThe name of the file output instances will be written to.\n"
+	+ "\tIf not supplied then instances will be written to stdout.\n"
+	+ "-c <class index>\n"
+	+ "\tThe number of the attribute to use as the class.\n"
+	+ "\t\"first\" and \"last\" are also valid entries.\n"
+	+ "\tIf not supplied then no class is assigned.\n";
+
+      if (filter instanceof Sourcable) {
+	genericOptions +=
+	  "-z <class name>\n"
+	  + "\tOutputs the source code representing the trained filter.\n";
+      }
+      
+      throw new Exception('\n' + ex.getMessage()
+			  + filterOptions+genericOptions);
+    }
+    
+    if (debug) {
+      System.err.println("Setting input format");
+    }
+    boolean printedHeader = false;
+    if (filter.setInputFormat(data)) {
+      if (debug) {
+	System.err.println("Getting output format");
+      }
+      output.println(filter.getOutputFormat().toString());
+      printedHeader = true;
+    }
+    
+    // Pass all the instances to the filter
+    Instance inst;
+    while (input.hasMoreElements(data)) {
+      inst = input.nextElement(data);
+      if (debug) {
+	System.err.println("Input instance to filter");
+      }
+      if (filter.input(inst)) {
+	if (debug) {
+	  System.err.println("Filter said collect immediately");
+	}
+	if (!printedHeader) {
+	  throw new Error("Filter didn't return true from setInputFormat() "
+			  + "earlier!");
+	}
+	if (debug) {
+	  System.err.println("Getting output instance");
+	}
+	output.println(filter.output().toString());
+      }
+    }
+
+    // Say that input has finished, and print any pending output instances
+    if (debug) {
+      System.err.println("Setting end of batch");
+    }
+    if (filter.batchFinished()) {
+      if (debug) {
+	System.err.println("Filter said collect output");
+      }
+      if (!printedHeader) {
+	if (debug) {
+	  System.err.println("Getting output format");
+	}
+	output.println(filter.getOutputFormat().toString());
+      }
+      if (debug) {
+	System.err.println("Getting output instance");
+      }
+      while (filter.numPendingOutput() > 0) {
+	output.println(filter.output().toString());
+	if (debug){
+	  System.err.println("Getting output instance");
+	}
+      }
+    }
+    if (debug) {
+      System.err.println("Done");
+    }
+    
+    if (output != null) {
+      output.close();
+    }
+    
+    if (sourceCode.length() != 0)
+      System.out.println(
+	  wekaStaticWrapper(
+	      (Sourcable) filter, sourceCode, data, filter.getOutputFormat()));
+  }
+
+  /**
+   * Method for testing filters ability to process multiple batches.
+   *
+   * @param filter the filter to use
+   * @param options should contain the following arguments: <br/>
+   * -i (first) input file <br/>
+   * -o (first) output file <br/>
+   * -r (second) input file <br/>
+   * -s (second) output file <br/>
+   * -c class_index <br/>
+   * -z classname (for filters implementing weka.filters.Sourcable) <br/>
+   * or -h for help on options
+   * @throws Exception if something goes wrong or the user requests help on
+   * command options
+   */
+  public static void batchFilterFile(Filter filter, String [] options) 
+    throws Exception {
+
+    Instances firstData = null;
+    Instances secondData = null;
+    DataSource firstInput = null;
+    DataSource secondInput = null;
+    PrintWriter firstOutput = null;
+    PrintWriter secondOutput = null;
+    boolean helpRequest;
+    String sourceCode = "";
+
+    try {
+      helpRequest = Utils.getFlag('h', options);
+
+      String fileName = Utils.getOption('i', options); 
+      if (fileName.length() != 0) {
+	firstInput = new DataSource(fileName);
+      } else {
+	throw new Exception("No first input file given.\n");
+      }
+
+      fileName = Utils.getOption('r', options); 
+      if (fileName.length() != 0) {
+	secondInput = new DataSource(fileName);
+      } else {
+	throw new Exception("No second input file given.\n");
+      }
+
+      fileName = Utils.getOption('o', options); 
+      if (fileName.length() != 0) {
+	firstOutput = new PrintWriter(new FileOutputStream(fileName));
+      } else {
+	firstOutput = new PrintWriter(System.out);
+      }
+      
+      fileName = Utils.getOption('s', options); 
+      if (fileName.length() != 0) {
+	secondOutput = new PrintWriter(new FileOutputStream(fileName));
+      } else {
+	secondOutput = new PrintWriter(System.out);
+      }
+      String classIndex = Utils.getOption('c', options);
+      if (filter instanceof Sourcable)
+	sourceCode = Utils.getOption('z', options);
+
+      if (filter instanceof OptionHandler) {
+	((OptionHandler)filter).setOptions(options);
+      }
+      Utils.checkForRemainingOptions(options);
+      
+      if (helpRequest) {
+	throw new Exception("Help requested.\n");
+      }
+      firstData = firstInput.getStructure();
+      secondData = secondInput.getStructure();
+      if (!secondData.equalHeaders(firstData)) {
+	throw new Exception("Input file formats differ.\n" + secondData.equalHeadersMsg(firstData) + "\n");
+      }
+      if (classIndex.length() != 0) {
+	if (classIndex.equals("first")) {
+	  firstData.setClassIndex(0);
+	  secondData.setClassIndex(0);
+	} else if (classIndex.equals("last")) {
+	  firstData.setClassIndex(firstData.numAttributes() - 1);
+	  secondData.setClassIndex(secondData.numAttributes() - 1);
+	} else {
+	  firstData.setClassIndex(Integer.parseInt(classIndex) - 1);
+	  secondData.setClassIndex(Integer.parseInt(classIndex) - 1);
+	}
+      }
+    } catch (Exception ex) {
+      String filterOptions = "";
+      // Output the error and also the valid options
+      if (filter instanceof OptionHandler) {
+	filterOptions += "\nFilter options:\n\n";
+	Enumeration enu = ((OptionHandler)filter).listOptions();
+	while (enu.hasMoreElements()) {
+	  Option option = (Option) enu.nextElement();
+	  filterOptions += option.synopsis() + '\n'
+	    + option.description() + "\n";
+	}
+      }
+
+      String genericOptions = "\nGeneral options:\n\n"
+	+ "-h\n"
+	+ "\tGet help on available options.\n"
+	+ "-i <filename>\n"
+	+ "\tThe file containing first input instances.\n"
+	+ "-o <filename>\n"
+	+ "\tThe file first output instances will be written to.\n"
+	+ "-r <filename>\n"
+	+ "\tThe file containing second input instances.\n"
+	+ "-s <filename>\n"
+	+ "\tThe file second output instances will be written to.\n"
+	+ "-c <class index>\n"
+	+ "\tThe number of the attribute to use as the class.\n"
+	+ "\t\"first\" and \"last\" are also valid entries.\n"
+	+ "\tIf not supplied then no class is assigned.\n";
+
+      if (filter instanceof Sourcable) {
+	genericOptions +=
+	  "-z <class name>\n"
+	  + "\tOutputs the source code representing the trained filter.\n";
+      }
+      
+      throw new Exception('\n' + ex.getMessage()
+			  + filterOptions+genericOptions);
+    }
+    boolean printedHeader = false;
+    if (filter.setInputFormat(firstData)) {
+      firstOutput.println(filter.getOutputFormat().toString());
+      printedHeader = true;
+    }
+    
+    // Pass all the instances to the filter
+    Instance inst;
+    while (firstInput.hasMoreElements(firstData)) {
+      inst = firstInput.nextElement(firstData);
+      if (filter.input(inst)) {
+	if (!printedHeader) {
+	  throw new Error("Filter didn't return true from setInputFormat() "
+			  + "earlier!");
+	}
+	firstOutput.println(filter.output().toString());
+      }
+    }
+    
+    // Say that input has finished, and print any pending output instances
+    if (filter.batchFinished()) {
+      if (!printedHeader) {
+	firstOutput.println(filter.getOutputFormat().toString());
+      }
+      while (filter.numPendingOutput() > 0) {
+	firstOutput.println(filter.output().toString());
+      }
+    }
+    
+    if (firstOutput != null) {
+      firstOutput.close();
+    }    
+    printedHeader = false;
+    if (filter.isOutputFormatDefined()) {
+      secondOutput.println(filter.getOutputFormat().toString());
+      printedHeader = true;
+    }
+    // Pass all the second instances to the filter
+    while (secondInput.hasMoreElements(secondData)) {
+      inst = secondInput.nextElement(secondData);
+      if (filter.input(inst)) {
+	if (!printedHeader) {
+	  throw new Error("Filter didn't return true from"
+			  + " isOutputFormatDefined() earlier!");
+	}
+	secondOutput.println(filter.output().toString());
+      }
+    }
+    
+    // Say that input has finished, and print any pending output instances
+    if (filter.batchFinished()) {
+      if (!printedHeader) {
+	secondOutput.println(filter.getOutputFormat().toString());
+      }
+      while (filter.numPendingOutput() > 0) {
+	secondOutput.println(filter.output().toString());
+      }
+    }
+    if (secondOutput != null) {
+      secondOutput.close();
+    }
+
+    if (sourceCode.length() != 0)
+      System.out.println(
+	  wekaStaticWrapper(
+	      (Sourcable) filter, sourceCode, firstData, filter.getOutputFormat()));
+  }
+
+  /**
+   * runs the filter instance with the given options.
+   * 
+   * @param filter	the filter to run
+   * @param options	the commandline options
+   */
+  protected static void runFilter(Filter filter, String[] options) {
+    try {
+      if (Utils.getFlag('b', options)) {
+	Filter.batchFilterFile(filter, options);
+      } else {
+	Filter.filterFile(filter, options);
+      }
+    } catch (Exception e) {
+      if (    (e.toString().indexOf("Help requested") == -1) 
+	   && (e.toString().indexOf("Filter options") == -1) )
+	e.printStackTrace();
+      else
+	System.err.println(e.getMessage());
+    }
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] args) {
+    
+    try {
+      if (args.length == 0) {
+        throw new Exception("First argument must be the class name of a Filter");
+      }
+      String fname = args[0];
+      Filter f = (Filter)Class.forName(fname).newInstance();
+      args[0] = "";
+      runFilter(f, args);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/MultiFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/MultiFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/MultiFilter.java	(revision 29)
@@ -0,0 +1,401 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MultiFilter.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Applies several filters successively. In case all supplied filters are StreamableFilters, it will act as a streamable one, too.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -F &lt;classname [options]&gt;
+ *  A filter to apply (can be specified multiple times).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5501 $
+ * @see     weka.filters.StreamableFilter
+ */
+public class MultiFilter
+  extends SimpleStreamFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6293720886005713120L;
+
+  /** The filters */
+  protected Filter m_Filters[] = {new AllFilter()};
+
+  /** caches the streamable state */
+  protected boolean m_Streamable = false;
+
+  /** whether we already checked the streamable state */
+  protected boolean m_StreamableChecked = false;
+  
+  /**
+   * Returns a string describing this filter
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Applies several filters successively. In case all supplied filters " 
+      + "are StreamableFilters, it will act as a streamable one, too.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+              "\tA filter to apply (can be specified multiple times).",
+              "F", 1, "-F <classname [options]>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -F &lt;classname [options]&gt;
+   *  A filter to apply (can be specified multiple times).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+    String        filter;
+    String[]      options2;
+    Vector        filters;
+
+    super.setOptions(options);
+    
+    filters = new Vector();
+    while ((tmpStr = Utils.getOption("F", options)).length() != 0) {
+      options2    = Utils.splitOptions(tmpStr);
+      filter      = options2[0];
+      options2[0] = "";
+      filters.add(Utils.forName(Filter.class, filter, options2));
+    }
+
+    // at least one filter
+    if (filters.size() == 0)
+      filters.add(new AllFilter());
+
+    setFilters((Filter[]) filters.toArray(new Filter[filters.size()]));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    for (i = 0; i < getFilters().length; i++) {
+      result.add("-F");
+      result.add(getFilterSpec(getFilter(i)));
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    if (getFilters().length == 0) {
+      Capabilities result = super.getCapabilities();
+      result.disableAll();
+      
+      return result;
+    } else {
+      return getFilters()[0].getCapabilities();
+    }
+  }
+
+  /**
+   * resets the filter, i.e., m_NewBatch to true and m_FirstBatchDone to
+   * false.
+   *
+   * @see #m_NewBatch
+   * @see #m_FirstBatchDone
+   */
+  protected void reset() {
+    super.reset();
+    m_StreamableChecked = false;
+  }
+
+  /**
+   * Sets the list of possible filters to choose from.
+   * Also resets the state of the filter (this reset doesn't affect the 
+   * options).
+   *
+   * @param filters	an array of filters with all options set.
+   * @see #reset()
+   */
+  public void setFilters(Filter[] filters) {
+    m_Filters           = filters;
+    reset();
+  }
+
+  /**
+   * Gets the list of possible filters to choose from.
+   *
+   * @return 	the array of Filters
+   */
+  public Filter[] getFilters() {
+    return m_Filters;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return    tip text for this property suitable for
+   *            displaying in the explorer/experimenter gui
+   */
+  public String filtersTipText() {
+    return "The base filters to be used.";
+  }
+  
+  /**
+   * Gets a single filter from the set of available filters.
+   *
+   * @param 	index the index of the filter wanted
+   * @return 	the Filter
+   */
+  public Filter getFilter(int index) {
+    return m_Filters[index];
+  }
+
+  /**
+   * returns the filter classname and the options as one string
+   * 
+   * @param filter	the filter to get the specs for
+   * @return		the classname plus options
+   */
+  protected String getFilterSpec(Filter filter) {
+    String        result;
+
+    if (filter == null) {
+      result = "";
+    }
+    else {
+      result  = filter.getClass().getName();
+      if (filter instanceof OptionHandler)
+        result += " " 
+          + Utils.joinOptions(((OptionHandler) filter).getOptions());
+    }
+
+    return result;
+  }
+
+  /**
+   * tests whether all the enclosed filters are streamable
+   *
+   * @return	true if all the enclosed filters are streamable
+   */
+  public boolean isStreamableFilter() {
+    int           i;
+
+    if (!m_StreamableChecked) {
+      m_Streamable        = true;
+      m_StreamableChecked = true;
+
+      for (i = 0; i < getFilters().length; i++) {
+        if (getFilter(i) instanceof MultiFilter)
+          m_Streamable = ((MultiFilter) getFilter(i)).isStreamableFilter();
+        else if (getFilter(i) instanceof StreamableFilter)
+          m_Streamable = true;
+        else
+          m_Streamable = false;
+
+        if (!m_Streamable)
+          break;
+      }
+
+      if (getDebug())
+        System.out.println("Streamable: " + m_Streamable);
+    }
+
+    return m_Streamable;
+  }
+
+  /**
+   * Returns true if the output format is immediately available after the
+   * input format has been set and not only after all the data has been
+   * seen (see batchFinished()). This method should normally return true
+   * for a stream filter, since the data will be processed in a batch
+   * manner instead (or at least for the second batch of files, see
+   * m_FirstBatchDone).
+   *
+   * @return      true if the output format is immediately available
+   * @see         #batchFinished()
+   * @see         #setInputFormat(Instances)
+   * @see         #m_FirstBatchDone
+   */
+  protected boolean hasImmediateOutputFormat() {
+    return isStreamableFilter();
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * hasImmediateOutputFormat() returns false, then this method will called
+   * from batchFinished() after the call of preprocess(Instances), in which,
+   * e.g., statistics for the actual processing step can be gathered.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see                   #hasImmediateOutputFormat()
+   * @see                   #batchFinished()
+   * @see                   #preprocess(Instances)
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Instances   result;
+    int         i;
+    
+    result = getInputFormat();
+    
+    for (i = 0; i < getFilters().length; i++) {
+      if (!isFirstBatchDone())
+	getFilter(i).setInputFormat(result);
+      result = getFilter(i).getOutputFormat();
+    }
+
+    return result;
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    Instance    result;
+    int         i;
+    
+    result = (Instance) instance.copy();
+
+    for (i = 0; i < getFilters().length; i++) {
+      getFilter(i).input(result);
+      result = getFilter(i).output();
+    }
+
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   * This implementation only calls process(Instance) for each instance
+   * in the given dataset.
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   * @see               #process(Instance)
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances     result;
+    int           i;
+
+    result = instances;
+    
+    for (i = 0; i < getFilters().length; i++) {
+      if (!isFirstBatchDone())
+	getFilter(i).setInputFormat(result);
+      result = Filter.useFilter(result, getFilter(i));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5501 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new MultiFilter(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/SimpleBatchFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/SimpleBatchFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/SimpleBatchFilter.java	(revision 29)
@@ -0,0 +1,251 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleBatchFilter.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/** 
+ * This filter is a superclass for simple batch filters. 
+ * <p/>
+ * 
+ * <b>General notes:</b><br/>
+ * <ul>
+ *   <li>After adding instances to the filter via input(Instance) one always
+ *   has to call batchFinished() to make them available via output(). </li>
+ *   <li>After the first call of batchFinished() the field m_FirstBatchDone is
+ *   set to <code>true</code>. </li>
+ * </ul>
+ * <p/>
+ * 
+ * <b>Example:</b><br/>
+ * The following code snippet uses the filter <code>SomeFilter</code> on a
+ * dataset that is loaded from <code>filename</code>.
+ * <pre>
+ * import weka.core.*;
+ * import weka.filters.*;
+ * import java.io.*;
+ * ...
+ * SomeFilter filter = new SomeFilter();
+ * // set necessary options for the filter
+ * Instances data = new Instances(
+ *                    new BufferedReader(
+ *                      new FileReader(filename)));
+ * Instances filteredData = Filter.useFilter(data, filter);
+ * </pre>
+ *
+ * <b>Implementation:</b><br/>
+ * Only the following abstract methods need to be implemented:
+ * <ul>
+ *   <li>globalInfo()</li>
+ *   <li>determineOutputFormat(Instances)</li>
+ *   <li>process(Instances)</li>
+ * </ul>
+ * <br/>
+ * And the <b>getCapabilities()</b> method must return what kind of
+ * attributes and classes the filter can handle.
+ * <p/>
+ * 
+ * If more options are necessary, then the following methods need to be
+ * overriden:
+ * <ul>
+ *   <li>listOptions()</li>
+ *   <li>setOptions(String[])</li>
+ *   <li>getOptions()</li>
+ * </ul>
+ * <p/>
+ *
+ * To make the filter available from commandline one must add the following
+ * main method for correct execution (&lt;Filtername&gt; must be replaced
+ * with the actual filter classname):
+ * <pre>
+ *  public static void main(String[] args) {
+ *    runFilter(new &lt;Filtername&gt;(), args);
+ *  }
+ * </pre>
+ * <p/>
+ *
+ * <b>Example implementation:</b><br/>
+ * <pre>
+ * import weka.core.*;
+ * import weka.core.Capabilities.*;
+ * import weka.filters.*;
+ *
+ * public class SimpleBatch
+ *   extends SimpleBatchFilter {
+ *   
+ *   public String globalInfo() {
+ *     return "A simple batch filter that adds an additional attribute 'bla' at the end containing the index of the processed instance.";
+ *   }
+ *     
+ *   public Capabilities getCapabilities() {
+ *     Capabilities result = super.getCapabilities();
+ *     result.enableAllAttributes();
+ *     result.enableAllClasses();
+ *     result.enable(Capability.NO_CLASS);  // filter doesn't need class to be set
+ *     return result;
+ *   }
+ * 
+ *   protected Instances determineOutputFormat(Instances inputFormat) {
+ *     Instances result = new Instances(inputFormat, 0);
+ *     result.insertAttributeAt(new Attribute("bla"), result.numAttributes());
+ *     return result;
+ *   }
+ * 
+ *   protected Instances process(Instances inst) {
+ *     Instances result = new Instances(determineOutputFormat(inst), 0);
+ *     for (int i = 0; i &lt; inst.numInstances(); i++) {
+ *       double[] values = new double[result.numAttributes()];
+ *       for (int n = 0; n &lt; inst.numAttributes(); n++)
+ *         values[n] = inst.instance(i).value(n);
+ *       values[values.length - 1] = i;
+ *       result.add(new Instance(1, values));
+ *     }
+ *     return result;
+ *   }
+ * 
+ *   public static void main(String[] args) {
+ *     runFilter(new SimpleBatch(), args);
+ *   }
+ * }
+ * </pre>
+ * <p/>
+ * 
+ * <b>Options:</b><br/>
+ * Valid filter-specific options are:<p/>
+ *
+ * -D <br/>
+ * Turns on output of debugging information.<p/>
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5047 $
+ * @see     SimpleStreamFilter 
+ * @see     #input(Instance)
+ * @see     #batchFinished()
+ * @see     #m_FirstBatchDone
+ */
+public abstract class SimpleBatchFilter
+  extends SimpleFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8102908673378055114L;
+
+  /**
+   * returns true if the output format is immediately available after the
+   * input format has been set and not only after all the data has been
+   * seen (see batchFinished())
+   *
+   * @return      true if the output format is immediately available
+   * @see         #batchFinished()
+   * @see         #setInputFormat(Instances)
+   */
+  protected boolean hasImmediateOutputFormat() {
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output (calling the method
+   * batchFinished() makes the data available). If this instance is part of
+   * a new batch, m_NewBatch is set to false.
+   *
+   * @param instance    the input instance
+   * @return            true if the filtered instance may now be
+   *                    collected with output().
+   * @throws  IllegalStateException if no input structure has been defined
+   * @throws Exception	if something goes wrong
+   * @see     		#batchFinished()
+   */
+  public boolean input(Instance instance) throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+    
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    bufferInput(instance);
+    
+    if (isFirstBatchDone()) {
+      Instances inst = new Instances(getInputFormat());
+      inst = process(inst);
+      for (int i = 0; i < inst.numInstances(); i++)
+	push(inst.instance(i));
+      flushInput();
+    }
+
+    return m_FirstBatchDone;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If
+   * the filter requires all instances prior to filtering, output()
+   * may now be called to retrieve the filtered instances. Any
+   * subsequent instances filtered should be filtered based on setting
+   * obtained from the first batch (unless the setInputFormat has been
+   * re-assigned or new options have been set). Sets m_FirstBatchDone
+   * and m_NewBatch to true.
+   *
+   * @return 		true if there are instances pending output
+   * @throws IllegalStateException 	if no input format has been set. 
+   * @throws Exception	if something goes wrong
+   * @see    		#m_NewBatch
+   * @see    		#m_FirstBatchDone 
+   */
+  public boolean batchFinished() throws Exception {
+    int         i;
+    Instances   inst;
+    
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    // get data
+    inst = new Instances(getInputFormat());
+
+    // if output format hasn't been set yet, do it now
+    if (!hasImmediateOutputFormat() && !isFirstBatchDone())
+      setOutputFormat(determineOutputFormat(new Instances(inst, 0)));
+
+    // don't do anything in case there are no instances pending.
+    // in case of second batch, they may have already been processed
+    // directly by the input method and added to the output queue
+    if (inst.numInstances() > 0) {
+      // process data
+      inst = process(inst);
+
+      // clear input queue
+      flushInput();
+
+      // move it to the output
+      for (i = 0; i < inst.numInstances(); i++)
+	push(inst.instance(i));
+    }
+    
+    m_NewBatch       = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/SimpleFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/SimpleFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/SimpleFilter.java	(revision 29)
@@ -0,0 +1,205 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleFilter.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+import weka.filters.Filter;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ * This filter contains common behavior of the SimpleBatchFilter and the
+ * SimpleStreamFilter.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ * @see     SimpleBatchFilter 
+ * @see     SimpleStreamFilter 
+ */
+public abstract class SimpleFilter
+  extends Filter 
+  implements OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5702974949137433141L;
+
+  /** Whether debugging is on */
+  protected boolean m_Debug = false;
+  
+  /**
+   * Returns a string describing this classifier.
+   *
+   * @return      a description of the classifier suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public abstract String globalInfo();
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+              "\tTurns on output of debugging information.",
+              "D", 0, "-D"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. 
+   * Also resets the state of the filter (this reset doesn't affect the 
+   * options).
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   * @see    #reset()
+   */
+  public void setOptions(String[] options) throws Exception {
+    reset();
+
+    setDebug(Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result = new Vector();
+
+    if (getDebug())
+      result.add("-D");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the debugging mode
+   *
+   * @param value     if true, debugging information is output
+   */
+  public void setDebug(boolean value) {
+    m_Debug = value;
+  }
+
+  /**
+   * Returns the current debugging mode state.
+   *
+   * @return      true if debugging mode is on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return    tip text for this property suitable for
+   *            displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Turns on output of debugging information.";
+  }
+
+  /**
+   * resets the filter, i.e., m_NewBatch to true and m_FirstBatchDone to
+   * false.
+   *
+   * @see #m_NewBatch
+   * @see #m_FirstBatchDone
+   */
+  protected void reset() {
+    m_NewBatch       = true;
+    m_FirstBatchDone = false;
+  }
+  
+  /**
+   * returns true if the output format is immediately available after the
+   * input format has been set and not only after all the data has been
+   * seen (see batchFinished())
+   *
+   * @return      true if the output format is immediately available
+   * @see         #batchFinished()
+   * @see         #setInputFormat(Instances)
+   */
+  protected abstract boolean hasImmediateOutputFormat();
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected abstract Instances determineOutputFormat(Instances inputFormat) throws Exception;
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected abstract Instances process(Instances instances) throws Exception;
+  
+  /**
+   * Sets the format of the input instances. 
+   * Also resets the state of the filter (this reset doesn't affect the 
+   * options).
+   *
+   * @param instanceInfo    an Instances object containing the input instance
+   *                        structure (any instances contained in the object 
+   *                        are ignored - only the structure is required).
+   * @return                true if the outputFormat may be collected 
+   *                        immediately
+   * @see                   #reset()
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+
+    reset();
+    
+    if (hasImmediateOutputFormat())
+      setOutputFormat(determineOutputFormat(instanceInfo));
+      
+    return hasImmediateOutputFormat();
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/SimpleStreamFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/SimpleStreamFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/SimpleStreamFilter.java	(revision 29)
@@ -0,0 +1,305 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleStreamFilter.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/** 
+ * This filter is a superclass for simple stream filters. <p/>
+ * <p/>
+ * 
+ * <b>General notes:</b><br/>
+ * <ul>
+ *   <li>After the first call of batchFinished() the field m_FirstBatchDone is
+ *   set to <code>true</code>. </li>
+ * </ul>
+ * <p/>
+ * 
+ * <b>Example:</b><br/>
+ * The following code snippet uses the filter <code>SomeFilter</code> on a
+ * dataset that is loaded from <code>filename</code>.
+ * <pre>
+ * import weka.core.*;
+ * import weka.filters.*;
+ * import java.io.*;
+ * ...
+ * SomeFilter filter = new SomeFilter();
+ * // set necessary options for the filter
+ * Instances data = new Instances(
+ *                    new BufferedReader(
+ *                      new FileReader(filename)));
+ * Instances filteredData = Filter.useFilter(data, filter);
+ * </pre>
+ *
+ * <b>Implementation:</b><br/>
+ * Only the following abstract methods need to be implemented:
+ * <ul>
+ *   <li>globalInfo()</li>
+ *   <li>determineOutputFormat(Instances)</li>
+ *   <li>process(Instance)</li>
+ * </ul>
+ * <br/>
+ * And the <b>getCapabilities()</b> method must return what kind of
+ * attributes and classes the filter can handle.
+ * <p/>
+ * 
+ * If more options are necessary, then the following methods need to be
+ * overriden:
+ * <ul>
+ *   <li>listOptions()</li>
+ *   <li>setOptions(String[])</li>
+ *   <li>getOptions()</li>
+ * </ul>
+ * <p/>
+ *
+ * To make the filter available from commandline one must add the following
+ * main method for correct execution (&lt;Filtername&gt; must be replaced
+ * with the actual filter classname):
+ * <pre>
+ *  public static void main(String[] args) {
+ *    runFilter(new &lt;Filtername&gt;(), args);
+ *  }
+ * </pre>
+ * <p/>
+ *
+ * <b>Example implementation:</b><br/>
+ * <pre>
+ * import weka.core.*;
+ * import weka.core.Capabilities.*;
+ * import weka.filters.*;
+ *
+ * import java.util.Random;
+ * 
+ * public class SimpleStream
+ *   extends SimpleStreamFilter {
+ *   
+ *   public String globalInfo() {
+ *     return "A simple stream filter that adds an attribute 'bla' at the end containing a random number.";
+ *   }
+ *     
+ *   public Capabilities getCapabilities() {
+ *     Capabilities result = super.getCapabilities();
+ *     result.enableAllAttributes();
+ *     result.enableAllClasses();
+ *     result.enable(Capability.NO_CLASS);  // filter doesn't need class to be set
+ *     return result;
+ *   }
+ * 
+ *   protected Instances determineOutputFormat(Instances inputFormat) {
+ *     Instances result = new Instances(inputFormat, 0);
+ *     result.insertAttributeAt(new Attribute("bla"), result.numAttributes());
+ *     return result;
+ *   }
+ * 
+ *   protected Instance process(Instance inst) {
+ *     double[] values = new double[inst.numAttributes() + 1];
+ *     for (int n = 0; n &lt; inst.numAttributes(); n++)
+ *       values[n] = inst.value(n);
+ *     values[values.length - 1] = new Random().nextInt();
+ *     Instance result = new Instance(1, values);
+ *     return result;
+ *   }
+ * 
+ *   public static void main(String[] args) {
+ *     runFilter(new SimpleStream(), args);
+ *   }
+ * }
+ * </pre>
+ * <p/>
+ *
+ * <b>Options:</b><br/>
+ * Valid filter-specific options are:<p/>
+ *
+ * -D <br/>
+ * Turns on output of debugging information.<p/>
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5047 $
+ * @see     SimpleBatchFilter 
+ * @see     #input(Instance)
+ * @see     #batchFinished()
+ * @see     #m_FirstBatchDone
+ */
+public abstract class SimpleStreamFilter
+  extends SimpleFilter 
+  implements StreamableFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2754882676192747091L;
+
+  /**
+   * Returns true if the output format is immediately available after the
+   * input format has been set and not only after all the data has been
+   * seen (see batchFinished()). This method should normally return true
+   * for a stream filter, since the data will be processed in a batch
+   * manner instead (or at least for the second batch of files, see
+   * m_FirstBatchDone).
+   *
+   * @return      true if the output format is immediately available
+   * @see         #batchFinished()
+   * @see         #setInputFormat(Instances)
+   * @see         #m_FirstBatchDone
+   */
+  protected boolean hasImmediateOutputFormat() {
+    return true;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * hasImmediateOutputFormat() returns false, then this method will called
+   * from batchFinished() after the call of preprocess(Instances), in which,
+   * e.g., statistics for the actual processing step can be gathered.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see                   #hasImmediateOutputFormat()
+   * @see                   #batchFinished()
+   * @see                   #preprocess(Instances)
+   */
+  protected abstract Instances determineOutputFormat(Instances inputFormat) throws Exception;
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected abstract Instance process(Instance instance) throws Exception;
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   * This implementation only calls process(Instance) for each instance
+   * in the given dataset.
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   * @see               #process(Instance)
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances     result;
+    int           i;
+
+    result = new Instances(getOutputFormat(), 0);
+    
+    for (i = 0; i < instances.numInstances(); i++)
+      result.add(process(instances.instance(i)));
+    
+    return result;
+  }
+
+  /**
+   * In case the output format cannot be returned immediately, this method
+   * is called before the actual processing of the instances. Derived classes
+   * can implement specific behavior here.
+   *
+   * @param instances   the instances to work on
+   * @see               #hasImmediateOutputFormat()
+   * @see               #determineOutputFormat(Instances)
+   */
+  protected void preprocess(Instances instances) {
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance    the input instance
+   * @return            true if the filtered instance may now be
+   *                    collected with output().
+   * @throws IllegalStateException 	if no input structure has been defined
+   * @throws Exception	if something goes wrong
+   */
+  public boolean input(Instance instance) throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    try {
+      if (hasImmediateOutputFormat() || isFirstBatchDone()) {
+        push(process((Instance) instance.copy()));
+        return true;
+      }
+      else {
+        bufferInput(instance);
+        return false;
+      }
+    }
+    catch (Exception e) {
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If
+   * the filter requires all instances prior to filtering, output()
+   * may now be called to retrieve the filtered instances. Any
+   * subsequent instances filtered should be filtered based on setting
+   * obtained from the first batch (unless the setInputFormat has been
+   * re-assigned or new options have been set).
+   *
+   * @return 		true if there are instances pending output
+   * @throws IllegalStateException 	if no input format has been set. 
+   */
+  public boolean batchFinished() throws Exception {
+    int         i;
+    Instances   inst;
+    
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    inst = new Instances(getInputFormat());
+    flushInput();
+
+    if (!hasImmediateOutputFormat())
+      preprocess(inst);
+
+    // process data
+    inst = process(inst);
+
+    // if output format hasn't been set yet, do it now
+    if (!hasImmediateOutputFormat() && !isFirstBatchDone())
+      setOutputFormat(inst);
+
+    // move data to the output
+    for (i = 0; i < inst.numInstances(); i++)
+      push(inst.instance(i));
+    
+    m_NewBatch       = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/Sourcable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/Sourcable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/Sourcable.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Sourcable.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+import weka.core.Instances;
+
+/** 
+ * Interface for filters that can be converted to Java source.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public interface Sourcable {
+
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/filters/StreamableFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/StreamableFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/StreamableFilter.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StreamableFilter.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+/**
+ * Interface for filters can work with a stream of instances.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.2 $
+ */
+
+public interface StreamableFilter {
+
+  /**
+   * Empty interface, to be used as a hint of the filters behaviour.
+   */
+
+}
Index: branches/MetisMQI/src/main/java/weka/filters/SupervisedFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/SupervisedFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/SupervisedFilter.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SupervisedFilter.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+/**
+ * Interface for filters that make use of a class attribute.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.2 $
+ */
+
+public interface SupervisedFilter {
+
+  /**
+   * Empty interface, to be used as a hint of the filters behaviour.
+   */
+
+}
Index: branches/MetisMQI/src/main/java/weka/filters/UnsupervisedFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/UnsupervisedFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/UnsupervisedFilter.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UnsupervisedFilter.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters;
+
+/**
+ * Interface for filters that do not need a class attribute.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.2 $
+ */
+
+public interface UnsupervisedFilter {
+
+  /**
+   * Empty interface, to be used as a hint of the filters behaviour.
+   */
+
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AddClassification.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AddClassification.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AddClassification.java	(revision 29)
@@ -0,0 +1,743 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AddClassification.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.filters.SimpleBatchFilter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.ObjectInputStream;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A filter for adding the classification, the class distribution and an error flag to a dataset with a classifier. The classifier is either trained on the data itself or provided as serialized model.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -W &lt;classifier specification&gt;
+ *  Full class name of classifier to use, followed
+ *  by scheme options. eg:
+ *   "weka.classifiers.bayes.NaiveBayes -D"
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> -serialized &lt;file&gt;
+ *  Instead of training a classifier on the data, one can also provide
+ *  a serialized model and use that for tagging the data.</pre>
+ * 
+ * <pre> -classification
+ *  Adds an attribute with the actual classification.
+ *  (default: off)</pre>
+ * 
+ * <pre> -remove-old-class
+ *  Removes the old class attribute.
+ *  (default: off)</pre>
+ * 
+ * <pre> -distribution
+ *  Adds attributes with the distribution for all classes 
+ *  (for numeric classes this will be identical to the attribute 
+ *  output with '-classification').
+ *  (default: off)</pre>
+ * 
+ * <pre> -error
+ *  Adds an attribute indicating whether the classifier output 
+ *  a wrong classification (for numeric classes this is the numeric 
+ *  difference).
+ *  (default: off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class AddClassification
+  extends SimpleBatchFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1931467132568441909L;
+
+  /** The classifier template used to do the classification */
+  protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
+
+  /** The file from which to load a serialized classifier */
+  protected File m_SerializedClassifierFile = new File(System.getProperty("user.dir"));
+  
+  /** The actual classifier used to do the classification */
+  protected Classifier m_ActualClassifier = null;
+
+  /** whether to output the classification */
+  protected boolean m_OutputClassification = false;
+
+  /** whether to remove the old class attribute */
+  protected boolean m_RemoveOldClass = false;
+  
+  /** whether to output the class distribution */
+  protected boolean m_OutputDistribution = false;
+  
+  /** whether to output the error flag */
+  protected boolean m_OutputErrorFlag = false;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter for adding the classification, the class distribution and "
+      + "an error flag to a dataset with a classifier. The classifier is "
+      + "either trained on the data itself or provided as serialized model.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        	result;
+    Enumeration   	en;
+
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tFull class name of classifier to use, followed\n"
+	+ "\tby scheme options. eg:\n"
+	+ "\t\t\"weka.classifiers.bayes.NaiveBayes -D\"\n"
+	+ "\t(default: weka.classifiers.rules.ZeroR)",
+	"W", 1, "-W <classifier specification>"));
+
+    result.addElement(new Option(
+	"\tInstead of training a classifier on the data, one can also provide\n"
+	+ "\ta serialized model and use that for tagging the data.",
+	"serialized", 1, "-serialized <file>"));
+
+    result.addElement(new Option(
+	"\tAdds an attribute with the actual classification.\n"
+	+ "\t(default: off)",
+	"classification", 0, "-classification"));
+
+    result.addElement(new Option(
+	"\tRemoves the old class attribute.\n"
+	+ "\t(default: off)",
+	"remove-old-class", 0, "-remove-old-class"));
+
+    result.addElement(new Option(
+	"\tAdds attributes with the distribution for all classes \n"
+        + "\t(for numeric classes this will be identical to the attribute \n"
+        + "\toutput with '-classification').\n"
+	+ "\t(default: off)",
+	"distribution", 0, "-distribution"));
+
+    result.addElement(new Option(
+	"\tAdds an attribute indicating whether the classifier output \n"
+        + "\ta wrong classification (for numeric classes this is the numeric \n"
+        + "\tdifference).\n"
+	+ "\t(default: off)",
+	"error", 0, "-error"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -W &lt;classifier specification&gt;
+   *  Full class name of classifier to use, followed
+   *  by scheme options. eg:
+   *   "weka.classifiers.bayes.NaiveBayes -D"
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> -serialized &lt;file&gt;
+   *  Instead of training a classifier on the data, one can also provide
+   *  a serialized model and use that for tagging the data.</pre>
+   * 
+   * <pre> -classification
+   *  Adds an attribute with the actual classification.
+   *  (default: off)</pre>
+   * 
+   * <pre> -remove-old-class
+   *  Removes the old class attribute.
+   *  (default: off)</pre>
+   * 
+   * <pre> -distribution
+   *  Adds attributes with the distribution for all classes 
+   *  (for numeric classes this will be identical to the attribute 
+   *  output with '-classification').
+   *  (default: off)</pre>
+   * 
+   * <pre> -error
+   *  Adds an attribute indicating whether the classifier output 
+   *  a wrong classification (for numeric classes this is the numeric 
+   *  difference).
+   *  (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[] 	tmpOptions;
+    File	file;
+    boolean 	serializedModel;
+
+    setOutputClassification(Utils.getFlag("classification", options));
+    
+    setRemoveOldClass(Utils.getFlag("remove-old-class", options));
+    
+    setOutputDistribution(Utils.getFlag("distribution", options));
+
+    setOutputErrorFlag(Utils.getFlag("error", options));
+    
+    serializedModel = false;
+    tmpStr = Utils.getOption("serialized", options);
+    if (tmpStr.length() != 0) {
+      file = new File(tmpStr);
+      if (!file.exists())
+	throw new FileNotFoundException(
+	    "File '" + file.getAbsolutePath() + "' not found!");
+      if (file.isDirectory())
+	throw new FileNotFoundException(
+	    "'" + file.getAbsolutePath() + "' points to a directory not a file!");
+      setSerializedClassifierFile(file);
+      serializedModel = true;
+    }
+    else {
+      setSerializedClassifierFile(null);
+    }
+    
+    if (!serializedModel) {
+      tmpStr = Utils.getOption('W', options);
+      if (tmpStr.length() == 0)
+	tmpStr = weka.classifiers.rules.ZeroR.class.getName();
+      tmpOptions = Utils.splitOptions(tmpStr);
+      if (tmpOptions.length == 0)
+	throw new Exception("Invalid classifier specification string");
+      tmpStr = tmpOptions[0];
+      tmpOptions[0] = "";
+      setClassifier(AbstractClassifier.forName(tmpStr, tmpOptions));
+    }
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int		i;
+    Vector	result;
+    String[]	options;
+    File	file;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getOutputClassification())
+      result.add("-classification");
+
+    if (getRemoveOldClass())
+      result.add("-remove-old-class");
+
+    if (getOutputDistribution())
+      result.add("-distribution");
+
+    if (getOutputErrorFlag())
+      result.add("-error");
+
+    file = getSerializedClassifierFile();
+    if ((file != null) && (!file.isDirectory())) {
+      result.add("-serialized");
+      result.add(file.getAbsolutePath());
+    }
+    else {
+      result.add("-W");
+      result.add(getClassifierSpec());
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    
+    if (getClassifier() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getClassifier().getCapabilities();
+    }
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+    return "The classifier to use for classification.";
+  }
+
+  /**
+   * Sets the classifier to classify instances with.
+   *
+   * @param value 	The classifier to be used (with its options set).
+   */
+  public void setClassifier(Classifier value) {
+    m_Classifier = value;
+  }
+  
+  /**
+   * Gets the classifier used by the filter.
+   *
+   * @return 		The classifier to be used.
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier.
+   *
+   * @return 		the classifier string.
+   */
+  protected String getClassifierSpec() {
+    String	result;
+    Classifier 	c;
+    
+    c      = getClassifier();
+    result = c.getClass().getName();
+    if (c instanceof OptionHandler)
+      result += " " + Utils.joinOptions(((OptionHandler) c).getOptions());
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String serializedClassifierFileTipText() {
+    return "A file containing the serialized model of a trained classifier.";
+  }
+
+  /**
+   * Gets the file pointing to a serialized, trained classifier. If it is
+   * null or pointing to a directory it will not be used.
+   * 
+   * @return		the file the serialized, trained classifier is located 
+   * 			in
+   */
+  public File getSerializedClassifierFile() {
+    return m_SerializedClassifierFile;
+  }
+
+  /**
+   * Sets the file pointing to a serialized, trained classifier. If the
+   * argument is null, doesn't exist or pointing to a directory, then the 
+   * value is ignored.
+   * 
+   * @param value	the file pointing to the serialized, trained classifier
+   */
+  public void setSerializedClassifierFile(File value) {
+    if ((value == null) || (!value.exists()))
+      value = new File(System.getProperty("user.dir"));
+
+    m_SerializedClassifierFile = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputClassificationTipText() {
+    return "Whether to add an attribute with the actual classification.";
+  }
+
+  /**
+   * Get whether the classifiction of the classifier is output.
+   *
+   * @return 		true if the classification of the classifier is output.
+   */
+  public boolean getOutputClassification() {
+    return m_OutputClassification;
+  }
+  
+  /**
+   * Set whether the classification of the classifier is output.
+   *
+   * @param value 	whether the classification of the classifier is output.
+   */
+  public void setOutputClassification(boolean value) {
+    m_OutputClassification = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String removeOldClassTipText() {
+    return "Whether to remove the old class attribute.";
+  }
+
+  /**
+   * Get whether the old class attribute is removed.
+   *
+   * @return 		true if the old class attribute is removed.
+   */
+  public boolean getRemoveOldClass() {
+    return m_RemoveOldClass;
+  }
+  
+  /**
+   * Set whether the old class attribute is removed.
+   *
+   * @param value 	whether the old class attribute is removed.
+   */
+  public void setRemoveOldClass(boolean value) {
+    m_RemoveOldClass = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputDistributionTipText() {
+    return 
+        "Whether to add attributes with the distribution for all classes "
+      + "(for numeric classes this will be identical to the attribute output "
+      + "with 'outputClassification').";
+  }
+
+  /**
+   * Get whether the classifiction of the classifier is output.
+   *
+   * @return 		true if the distribution of the classifier is output.
+   */
+  public boolean getOutputDistribution() {
+    return m_OutputDistribution;
+  }
+  
+  /**
+   * Set whether the Distribution of the classifier is output.
+   *
+   * @param value 	whether the distribution of the classifier is output.
+   */
+  public void setOutputDistribution(boolean value) {
+    m_OutputDistribution = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputErrorFlagTipText() {
+    return 
+        "Whether to add an attribute indicating whether the classifier output "
+      + "a wrong classification (for numeric classes this is the numeric "
+      + "difference).";
+  }
+
+  /**
+   * Get whether the classifiction of the classifier is output.
+   *
+   * @return 		true if the classification of the classifier is output.
+   */
+  public boolean getOutputErrorFlag() {
+    return m_OutputErrorFlag;
+  }
+  
+  /**
+   * Set whether the classification of the classifier is output.
+   *
+   * @param value 	whether the classification of the classifier is output.
+   */
+  public void setOutputErrorFlag(boolean value) {
+    m_OutputErrorFlag = value;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+    
+    Instances	result;
+    FastVector	atts;
+    int		i;
+    FastVector	values;
+    int		classindex;
+    
+    classindex = -1;
+    
+    // copy old attributes
+    atts = new FastVector();
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      // remove class?
+      if ((i == inputFormat.classIndex()) && (getRemoveOldClass()) )
+	continue;
+      // record class index
+      if (i == inputFormat.classIndex())
+	classindex = i;
+      atts.addElement(inputFormat.attribute(i).copy());
+    }
+    
+    // add new attributes
+    // 1. classification?
+    if (getOutputClassification()) {
+      // if old class got removed, use this one
+      if (classindex == -1)
+	classindex = atts.size();
+      atts.addElement(inputFormat.classAttribute().copy("classification"));
+    }
+    
+    // 2. distribution?
+    if (getOutputDistribution()) {
+      if (inputFormat.classAttribute().isNominal()) {
+	for (i = 0; i < inputFormat.classAttribute().numValues(); i++) {
+	  atts.addElement(new Attribute("distribution_" + inputFormat.classAttribute().value(i)));
+	}
+      }
+      else {
+	atts.addElement(new Attribute("distribution"));
+      }
+    }
+    
+    // 2. error flag?
+    if (getOutputErrorFlag()) {
+      if (inputFormat.classAttribute().isNominal()) {
+	values = new FastVector();
+	values.addElement("no");
+	values.addElement("yes");
+	atts.addElement(new Attribute("error", values));
+      }
+      else {
+	atts.addElement(new Attribute("error"));
+      }
+    }
+    
+    // generate new header
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(classindex);
+    
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances		result;
+    double[]		newValues;
+    double[]		oldValues;
+    int			i;
+    int			start;
+    int			n;
+    Instance		newInstance;
+    Instance		oldInstance;
+    Instances		header;
+    double[]		distribution;
+    File		file;
+    ObjectInputStream 	ois;
+    
+    // load or train classifier
+    if (!isFirstBatchDone()) {
+      file = getSerializedClassifierFile();
+      if (!file.isDirectory()) {
+	ois = new ObjectInputStream(new FileInputStream(file));
+	m_ActualClassifier = (Classifier) ois.readObject();
+	header = null;
+	// let's see whether there's an Instances header stored as well
+	try {
+	  header = (Instances) ois.readObject();
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+	ois.close();
+	// same dataset format?
+	if ((header != null) && (!header.equalHeaders(instances)))
+	  throw new WekaException(
+	      "Training header of classifier and filter dataset don't match:\n"
+	      + header.equalHeadersMsg(instances));
+      }
+      else {
+	m_ActualClassifier = AbstractClassifier.makeCopy(m_Classifier);
+	m_ActualClassifier.buildClassifier(instances);
+      }
+    }
+    
+    result = getOutputFormat();
+    
+    // traverse all instances
+    for (i = 0; i < instances.numInstances(); i++) {
+      oldInstance = instances.instance(i);
+      oldValues   = oldInstance.toDoubleArray();
+      newValues   = new double[result.numAttributes()];
+      
+      start = oldValues.length;
+      if (getRemoveOldClass())
+	start--;
+
+      // copy old values
+      System.arraycopy(oldValues, 0, newValues, 0, start);
+      
+      // add new values:
+      // 1. classification?
+      if (getOutputClassification()) {
+	newValues[start] = m_ActualClassifier.classifyInstance(oldInstance);
+	start++;
+      }
+      
+      // 2. distribution?
+      if (getOutputDistribution()) {
+	distribution = m_ActualClassifier.distributionForInstance(oldInstance);
+	for (n = 0; n < distribution.length; n++) {
+	  newValues[start] = distribution[n];
+	  start++;
+	}
+      }
+      
+      // 3. error flag?
+      if (getOutputErrorFlag()) {
+	if (result.classAttribute().isNominal()) {
+	  if (oldInstance.classValue() == m_ActualClassifier.classifyInstance(oldInstance))
+	    newValues[start] = 0;
+	  else
+	    newValues[start] = 1;
+	}
+	else {
+	  newValues[start] = m_ActualClassifier.classifyInstance(oldInstance) - oldInstance.classValue();
+	}
+	start++;
+      }
+      
+      // create new instance
+      if (oldInstance instanceof SparseInstance)
+	newInstance = new SparseInstance(oldInstance.weight(), newValues);
+      else
+	newInstance = new DenseInstance(oldInstance.weight(), newValues);
+
+      // copy string/relational values from input to output
+      copyValues(newInstance, false, oldInstance.dataset(), getOutputFormat());
+
+      result.add(newInstance);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new AddClassification(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AttributeSelection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AttributeSelection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/AttributeSelection.java	(revision 29)
@@ -0,0 +1,578 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSelection.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.attributeSelection.ASEvaluation;
+import weka.attributeSelection.ASSearch;
+import weka.attributeSelection.AttributeEvaluator;
+import weka.attributeSelection.AttributeTransformer;
+import weka.attributeSelection.BestFirst;
+import weka.attributeSelection.CfsSubsetEval;
+import weka.attributeSelection.Ranker;
+import weka.attributeSelection.UnsupervisedAttributeEvaluator;
+import weka.attributeSelection.UnsupervisedSubsetEvaluator;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A supervised attribute filter that can be used to select attributes. It is very flexible and allows various search and evaluation methods to be combined.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;"Name of search class [search options]"&gt;
+ *  Sets search method for subset evaluators.
+ *  eg. -S "weka.attributeSelection.BestFirst -S 8"</pre>
+ * 
+ * <pre> -E &lt;"Name of attribute/subset evaluation class [evaluator options]"&gt;
+ *  Sets attribute/subset evaluator.
+ *  eg. -E "weka.attributeSelection.CfsSubsetEval -L"</pre>
+ * 
+ * <pre> 
+ * Options specific to evaluator weka.attributeSelection.CfsSubsetEval:
+ * </pre>
+ * 
+ * <pre> -M
+ *  Treat missing values as a seperate value.</pre>
+ * 
+ * <pre> -L
+ *  Don't include locally predictive attributes.</pre>
+ * 
+ * <pre> 
+ * Options specific to search weka.attributeSelection.BestFirst:
+ * </pre>
+ * 
+ * <pre> -P &lt;start set&gt;
+ *  Specify a starting set of attributes.
+ *  Eg. 1,3,5-7.</pre>
+ * 
+ * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+ *  Direction of search. (default = 1).</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Number of non-improving nodes to
+ *  consider before terminating search.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Size of lookup cache for evaluated subsets.
+ *  Expressed as a multiple of the number of
+ *  attributes in the data set. (default = 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class AttributeSelection 
+  extends Filter
+  implements SupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -296211247688169716L;
+
+  /** the attribute selection evaluation object */
+  private weka.attributeSelection.AttributeSelection m_trainSelector;
+
+  /** the attribute evaluator to use */
+  private ASEvaluation m_ASEvaluator;
+
+  /** the search method if any */
+  private ASSearch m_ASSearch;
+
+  /** holds a copy of the full set of valid  options passed to the filter */
+  private String [] m_FilterOptions;
+
+  /** holds the selected attributes  */
+  private int [] m_SelectedAttributes;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A supervised attribute filter that can be used to select " 
+      + "attributes. It is very flexible and allows various search " 
+      + "and evaluation methods to be combined.";
+  }
+
+  /**
+   * Constructor
+   */
+  public AttributeSelection () {
+    
+    resetOptions();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	"\tSets search method for subset evaluators.\n"
+	+ "\teg. -S \"weka.attributeSelection.BestFirst -S 8\"", 
+	"S", 1,
+	"-S <\"Name of search class [search options]\">"));
+
+    newVector.addElement(new Option(
+	"\tSets attribute/subset evaluator.\n"
+	+ "\teg. -E \"weka.attributeSelection.CfsSubsetEval -L\"",
+	"E", 1,
+	"-E <\"Name of attribute/subset evaluation class [evaluator options]\">"));
+    
+    if ((m_ASEvaluator != null) && (m_ASEvaluator instanceof OptionHandler)) {
+      Enumeration enu = ((OptionHandler)m_ASEvaluator).listOptions();
+      
+      newVector.addElement(new Option("", "", 0, "\nOptions specific to "
+	   + "evaluator " + m_ASEvaluator.getClass().getName() + ":"));
+      while (enu.hasMoreElements()) {
+	newVector.addElement((Option)enu.nextElement());
+      }
+    }
+  
+    if ((m_ASSearch != null) && (m_ASSearch instanceof OptionHandler)) {
+      Enumeration enu = ((OptionHandler)m_ASSearch).listOptions();
+    
+      newVector.addElement(new Option("", "", 0, "\nOptions specific to "
+	      + "search " + m_ASSearch.getClass().getName() + ":"));
+      while (enu.hasMoreElements()) {
+	newVector.addElement((Option)enu.nextElement());
+      }
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;"Name of search class [search options]"&gt;
+   *  Sets search method for subset evaluators.
+   *  eg. -S "weka.attributeSelection.BestFirst -S 8"</pre>
+   * 
+   * <pre> -E &lt;"Name of attribute/subset evaluation class [evaluator options]"&gt;
+   *  Sets attribute/subset evaluator.
+   *  eg. -E "weka.attributeSelection.CfsSubsetEval -L"</pre>
+   * 
+   * <pre> 
+   * Options specific to evaluator weka.attributeSelection.CfsSubsetEval:
+   * </pre>
+   * 
+   * <pre> -M
+   *  Treat missing values as a seperate value.</pre>
+   * 
+   * <pre> -L
+   *  Don't include locally predictive attributes.</pre>
+   * 
+   * <pre> 
+   * Options specific to search weka.attributeSelection.BestFirst:
+   * </pre>
+   * 
+   * <pre> -P &lt;start set&gt;
+   *  Specify a starting set of attributes.
+   *  Eg. 1,3,5-7.</pre>
+   * 
+   * <pre> -D &lt;0 = backward | 1 = forward | 2 = bi-directional&gt;
+   *  Direction of search. (default = 1).</pre>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Number of non-improving nodes to
+   *  consider before terminating search.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Size of lookup cache for evaluated subsets.
+   *  Expressed as a multiple of the number of
+   *  attributes in the data set. (default = 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String optionString;
+    resetOptions();
+
+    if (Utils.getFlag('X',options)) {
+	throw new Exception("Cross validation is not a valid option"
+			    + " when using attribute selection as a Filter.");
+    }
+
+    optionString = Utils.getOption('E',options);
+    if (optionString.length() != 0) {
+      optionString = optionString.trim();
+      // split a quoted evaluator name from its options (if any)
+      int breakLoc = optionString.indexOf(' ');
+      String evalClassName = optionString;
+      String evalOptionsString = "";
+      String [] evalOptions=null;
+      if (breakLoc != -1) {
+	evalClassName = optionString.substring(0, breakLoc);
+	evalOptionsString = optionString.substring(breakLoc).trim();
+	evalOptions = Utils.splitOptions(evalOptionsString);
+      }
+      setEvaluator(ASEvaluation.forName(evalClassName, evalOptions));
+    }
+
+    if (m_ASEvaluator instanceof AttributeEvaluator) {
+      setSearch(new Ranker());
+    }
+
+    optionString = Utils.getOption('S',options);
+    if (optionString.length() != 0) {
+      optionString = optionString.trim();
+      int breakLoc = optionString.indexOf(' ');
+      String SearchClassName = optionString;
+      String SearchOptionsString = "";
+      String [] SearchOptions=null;
+      if (breakLoc != -1) {
+	SearchClassName = optionString.substring(0, breakLoc);
+	SearchOptionsString = optionString.substring(breakLoc).trim();
+	SearchOptions = Utils.splitOptions(SearchOptionsString);
+      }
+      setSearch(ASSearch.forName(SearchClassName, SearchOptions));
+    }
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+
+  /**
+   * Gets the current settings for the attribute selection (search, evaluator)
+   * etc.
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String [] getOptions() {
+    String [] EvaluatorOptions = new String[0];
+    String [] SearchOptions = new String[0];
+    int current = 0;
+
+    if (m_ASEvaluator instanceof OptionHandler) {
+      EvaluatorOptions = ((OptionHandler)m_ASEvaluator).getOptions();
+    }
+
+    if (m_ASSearch instanceof OptionHandler) {
+      SearchOptions = ((OptionHandler)m_ASSearch).getOptions();
+    }
+
+    String [] setOptions = new String [10];
+    setOptions[current++]="-E";
+    setOptions[current++]= getEvaluator().getClass().getName()
+      +" "+Utils.joinOptions(EvaluatorOptions);
+
+    setOptions[current++]="-S";
+    setOptions[current++]=getSearch().getClass().getName() 
+      + " "+Utils.joinOptions(SearchOptions);
+
+    while (current < setOptions.length) {
+      setOptions[current++] = "";
+    }
+    
+    return setOptions;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String evaluatorTipText() {
+
+    return "Determines how attributes/attribute subsets are evaluated.";
+  }
+
+  /**
+   * set attribute/subset evaluator
+   * 
+   * @param evaluator the evaluator to use
+   */
+  public void setEvaluator(ASEvaluation evaluator) {
+    m_ASEvaluator = evaluator;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String searchTipText() {
+
+    return "Determines the search method.";
+  }
+
+  /**
+   * Set search class
+   * 
+   * @param search the search class to use
+   */
+  public void setSearch(ASSearch search) {
+    m_ASSearch = search;
+  }
+
+  /**
+   * Get the name of the attribute/subset evaluator
+   *
+   * @return the name of the attribute/subset evaluator as a string
+   */
+  public ASEvaluation getEvaluator() {
+    
+      return m_ASEvaluator;
+  }
+
+  /**
+   * Get the name of the search method
+   *
+   * @return the name of the search method as a string
+   */
+  public ASSearch getSearch() {
+    
+      return m_ASSearch;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (m_ASEvaluator == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = m_ASEvaluator.getCapabilities();
+      // class index will be set if necessary, so we always allow the dataset
+      // to have no class attribute set. see the following method:
+      //   weka.attributeSelection.AttributeSelection.SelectAttributes(Instances)
+      result.enable(Capability.NO_CLASS);
+    }
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   * @throws Exception if the input instance was not of the correct format 
+   * or if there was a problem with the filtering.
+   */
+  public boolean input(Instance instance) throws Exception {
+    
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    if (isOutputFormatDefined()) {
+      convertInstance(instance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If the filter
+   * requires all instances prior to filtering, output() may now be called
+   * to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output.
+   * @throws IllegalStateException if no input structure has been defined.
+   * @throws Exception if there is a problem during the attribute selection.
+   */
+  public boolean batchFinished() throws Exception {
+    
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isOutputFormatDefined()) {
+      m_trainSelector.setEvaluator(m_ASEvaluator);
+      m_trainSelector.setSearch(m_ASSearch);
+      m_trainSelector.SelectAttributes(getInputFormat());
+      //      System.out.println(m_trainSelector.toResultsString());
+
+      m_SelectedAttributes = m_trainSelector.selectedAttributes();
+      if (m_SelectedAttributes == null) {
+	throw new Exception("No selected attributes\n");
+      }
+     
+      setOutputFormat();
+      
+      // Convert pending input instances
+      for (int i = 0; i < getInputFormat().numInstances(); i++) {
+	convertInstance(getInputFormat().instance(i));
+      }
+      flushInput();
+    }
+    
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Set the output format. Takes the currently defined attribute set 
+   * m_InputFormat and calls setOutputFormat(Instances) appropriately.
+   * 
+   * @throws Exception if something goes wrong
+   */
+  protected void setOutputFormat() throws Exception {
+    Instances informat;
+
+    if (m_SelectedAttributes == null) {
+      setOutputFormat(null);
+      return;
+    }
+
+    FastVector attributes = new FastVector(m_SelectedAttributes.length);
+
+    int i;
+    if (m_ASEvaluator instanceof AttributeTransformer) {
+      informat = ((AttributeTransformer)m_ASEvaluator).transformedHeader();
+    } else {
+      informat = getInputFormat();
+    }
+
+    for (i=0;i < m_SelectedAttributes.length;i++) {
+      attributes.
+	addElement(informat.attribute(m_SelectedAttributes[i]).copy());
+    }
+
+    Instances outputFormat = 
+      new Instances(getInputFormat().relationName(), attributes, 0);
+
+
+    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) &&
+	!(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) {
+      outputFormat.setClassIndex(m_SelectedAttributes.length - 1);
+    }
+    
+    setOutputFormat(outputFormat);  
+  }
+
+  /**
+   * Convert a single instance over. Selected attributes only are transfered.
+   * The converted instance is added to the end of
+   * the output queue.
+   *
+   * @param instance the instance to convert
+   * @throws Exception if something goes wrong
+   */
+  protected void convertInstance(Instance instance) throws Exception {
+    double[] newVals = new double[getOutputFormat().numAttributes()];
+
+    if (m_ASEvaluator instanceof AttributeTransformer) {
+      Instance tempInstance = ((AttributeTransformer)m_ASEvaluator).
+	convertInstance(instance);
+      for (int i = 0; i < m_SelectedAttributes.length; i++) {
+	int current = m_SelectedAttributes[i];
+	newVals[i] = tempInstance.value(current);
+      }
+    } else {
+      for (int i = 0; i < m_SelectedAttributes.length; i++) {
+	int current = m_SelectedAttributes[i];
+	newVals[i] = instance.value(current);
+      }
+    }
+    if (instance instanceof SparseInstance) {
+      push(new SparseInstance(instance.weight(), newVals));
+    } else {
+      push(new DenseInstance(instance.weight(), newVals));
+    }
+  }
+
+  /**
+   * set options to their default values
+   */
+  protected void resetOptions() {
+
+    m_trainSelector = new weka.attributeSelection.AttributeSelection();
+    setEvaluator(new CfsSubsetEval());
+    setSearch(new BestFirst());
+    m_SelectedAttributes = null;
+    m_FilterOptions = null;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new AttributeSelection(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/ClassOrder.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/ClassOrder.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/ClassOrder.java	(revision 29)
@@ -0,0 +1,521 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassOrder.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Changes the order of the classes so that the class values are no longer of in the order specified in the header. The values will be in the order specified by the user -- it could be either in ascending/descending order by the class frequency or in random order. Note that this filter currently does not change the header, only the class values of the instances, so there is not much point in using it in conjunction with the FilteredClassifier. The value can also be converted back using 'originalValue(double value)' procedure.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;seed&gt;
+ *  Specify the seed of randomization
+ *  used to randomize the class
+ *  order (default: 1)</pre>
+ * 
+ * <pre> -C &lt;order&gt;
+ *  Specify the class order to be
+ *  sorted, could be 0: ascending
+ *  1: descending and 2: random.(default: 0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5491 $
+ */
+public class ClassOrder 
+  extends Filter 
+  implements SupervisedFilter, OptionHandler {
+    
+  /** for serialization */
+  static final long serialVersionUID = -2116226838887628411L;
+  
+  /** The seed of randomization */
+  private long m_Seed = 1;
+    
+  /** The random object */
+  private Random m_Random = null;
+    
+  /** 
+   * The 1-1 converting table from the original class values
+   * to the new values
+   */
+  private int[] m_Converter = null;
+    
+  /** Class attribute of the data */
+  private Attribute m_ClassAttribute = null;
+
+  /** The class order to be sorted */
+  private int m_ClassOrder = 0;
+    
+  /** The class values are sorted in ascending order based on their frequencies */
+  public static final int FREQ_ASCEND = 0;
+
+  /** The class values are sorted in descending order based on their frequencies */
+  public static final int FREQ_DESCEND = 1;
+   
+  /** The class values are sorted in random order*/
+  public static final int RANDOM =2;
+
+  /** This class can provide the class distribution in the sorted order 
+   *  as side effect */
+  private double[] m_ClassCounts = null;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Changes the order of the classes so that the class values are " 
+      + "no longer of in the order specified in the header. "
+      + "The values will be in the order specified by the user "
+      + "-- it could be either in ascending/descending order by the class "
+      + "frequency or in random order. Note that this filter currently does not "
+      + "change the header, only the class values of the instances, "
+      + "so there is not much point in using it in conjunction with the "
+      + "FilteredClassifier. The value can also be converted back using "
+      + "'originalValue(double value)' procedure.";
+  }
+    
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+	
+    Vector newVector = new Vector(1);
+	
+    newVector.addElement(new Option("\tSpecify the seed of randomization\n"
+				    + "\tused to randomize the class\n"
+				    + "\torder (default: 1)",
+				    "R", 1, "-R <seed>"));
+	
+    newVector.addElement(new Option("\tSpecify the class order to be\n"
+				    + "\tsorted, could be 0: ascending\n"
+				    + "\t1: descending and 2: random.(default: 0)",
+				    "C", 1, "-C <order>"));
+	
+    return newVector.elements();
+  }
+    
+    
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;seed&gt;
+   *  Specify the seed of randomization
+   *  used to randomize the class
+   *  order (default: 1)</pre>
+   * 
+   * <pre> -C &lt;order&gt;
+   *  Specify the class order to be
+   *  sorted, could be 0: ascending
+   *  1: descending and 2: random.(default: 0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+	
+    String seedString = Utils.getOption('R', options);
+    if (seedString.length() != 0)
+      m_Seed = Long.parseLong(seedString);
+    else 
+      m_Seed = 1;  
+	
+    String orderString = Utils.getOption('C', options);
+    if (orderString.length() != 0)
+      m_ClassOrder = Integer.parseInt(orderString);
+    else 
+      m_ClassOrder = FREQ_ASCEND;   	
+	
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat()); 	
+	
+    m_Random = null;
+  }
+        
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+	
+    String [] options = new String [4];
+    int current = 0;
+	
+    options[current++] = "-R"; 
+    options[current++] = "" + m_Seed;
+    options[current++] = "-C"; 
+    options[current++] = "" + m_ClassOrder;
+	
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "Specify the seed of randomization of the class order";
+  }
+    
+  /**
+   * Get the current randomization seed
+   *
+   * @return a seed
+   */
+  public long getSeed() {	
+    return m_Seed;
+  }
+
+  /**
+   * Set randomization seed
+   *
+   * @param seed the set seed
+   */
+  public void setSeed(long seed){
+    m_Seed = seed;
+    m_Random = null;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classOrderTipText() {
+    return "Specify the class order after the filtering";
+  }
+    
+  /**
+   * Get the wanted class order
+   *
+   * @return class order
+   */
+  public int getClassOrder() {	
+    return m_ClassOrder;
+  }
+    
+  /**
+   * Set the wanted class order
+   *
+   * @param order the class order
+   */
+  public void setClassOrder(int order){
+    m_ClassOrder = order;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+    
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if no class index set or class not nominal
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {     
+
+    super.setInputFormat(new Instances(instanceInfo, 0));	
+
+    m_ClassAttribute = instanceInfo.classAttribute();	
+    m_Random = new Random(m_Seed);
+    m_Converter = null;
+    
+    int numClasses = instanceInfo.numClasses();
+    m_ClassCounts = new double[numClasses];	
+    return false;
+  }    
+    
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+	
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;     
+    }	
+    
+    // In case some one use this routine in testing, 
+    // although he/she should not do so
+    if(m_Converter != null){
+      Instance datum = (Instance)instance.copy();
+      if (!datum.isMissing(m_ClassAttribute)){
+	datum.setClassValue((double)m_Converter[(int)datum.classValue()]);
+      }
+      push(datum);
+      return true;
+    }
+    
+    if (!instance.isMissing(m_ClassAttribute)) {
+      m_ClassCounts[(int)instance.classValue()] += instance.weight();
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+  
+  /**
+   * Signify that this batch of input to the filter is finished. If
+   * the filter requires all instances prior to filtering, output()
+   * may now be called to retrieve the filtered instances. Any
+   * subsequent instances filtered should be filtered based on setting
+   * obtained from the first batch (unless the inputFormat has been
+   * re-assigned or new options have been set). This implementation 
+   * sorts the class values and provide class counts in the output format
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined,
+   * @throws Exception if there was a problem finishing the batch.
+   */
+  public boolean batchFinished() throws Exception {
+
+    Instances data = getInputFormat();
+    if (data == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_Converter == null) {
+
+      // Get randomized indices and class counts 
+      int[] randomIndices = new int[m_ClassCounts.length];
+      for (int i = 0; i < randomIndices.length; i++) {
+	randomIndices[i] = i;
+      }
+      for (int j = randomIndices.length - 1; j > 0; j--) {
+	int toSwap = m_Random.nextInt(j + 1);
+	int tmpIndex = randomIndices[j];
+	randomIndices[j] = randomIndices[toSwap];
+	randomIndices[toSwap] = tmpIndex;
+      }
+      
+      double[] randomizedCounts = new double[m_ClassCounts.length];
+      for (int i = 0; i < randomizedCounts.length; i++) {
+	randomizedCounts[i] = m_ClassCounts[randomIndices[i]];
+      } 
+
+      // Create new order. For the moment m_Converter converts new indices
+      // into old ones.
+      if (m_ClassOrder == RANDOM) {
+	m_Converter = randomIndices;
+	m_ClassCounts = randomizedCounts;
+      } else {
+	int[] sorted = Utils.sort(randomizedCounts);
+	m_Converter = new int[sorted.length];
+	if (m_ClassOrder == FREQ_ASCEND) {
+	  for (int i = 0; i < sorted.length; i++) {
+	    m_Converter[i] = randomIndices[sorted[i]];
+	  }
+	} else if (m_ClassOrder == FREQ_DESCEND) {
+	  for (int i = 0; i < sorted.length; i++) {
+	    m_Converter[i] = randomIndices[sorted[sorted.length - i - 1]];
+	  }
+	} else {
+	  throw new IllegalArgumentException("Class order not defined!");
+	}
+	
+	// Change class counts
+	double[] tmp2 = new double[m_ClassCounts.length];
+	for (int i = 0; i < m_Converter.length; i++) {
+	  tmp2[i] = m_ClassCounts[m_Converter[i]];
+	}
+	m_ClassCounts = tmp2;
+      }
+      
+      // Change the class values
+      FastVector values = new FastVector(data.classAttribute().numValues());
+      for (int i = 0; i < data.numClasses(); i++) {
+	values.addElement(data.classAttribute().value(m_Converter[i]));
+      }
+      FastVector newVec = new FastVector(data.numAttributes());
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (i == data.classIndex()) {
+	  newVec.addElement(new Attribute(data.classAttribute().name(), values, 
+					  data.classAttribute().getMetadata()));
+	} else {
+	  newVec.addElement(data.attribute(i));
+	}
+      }
+      Instances newInsts = new Instances(data.relationName(), newVec, 0);
+      newInsts.setClassIndex(data.classIndex());
+      setOutputFormat(newInsts);
+
+      // From now on we need m_Converter to convert old indices into new ones
+      int[] temp = new int[m_Converter.length];
+      for (int i = 0; i < temp.length; i++) {
+	temp[m_Converter[i]] = i;
+      }
+      m_Converter = temp;
+
+      // Process all instances
+      for(int xyz=0; xyz<data.numInstances(); xyz++){
+	Instance datum = data.instance(xyz);
+	if (!datum.isMissing(datum.classIndex())) {
+	  datum.setClassValue((double)m_Converter[(int)datum.classValue()]);
+	}
+	push(datum);
+      }
+    }
+    flushInput();
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+    
+  /**
+   * Get the class distribution of the sorted class values.  If class is numeric
+   * it returns null 
+   *
+   * @return the class counts
+   */
+  public double[] getClassCounts(){ 
+
+    if(m_ClassAttribute.isNominal())
+      return m_ClassCounts; 
+    else
+      return null;
+  }
+
+  /**
+   * Convert the given class distribution back to the distributions
+   * with the original internal class index
+   * 
+   * @param before the given class distribution
+   * @return the distribution converted back
+   */
+  public double[] distributionsByOriginalIndex (double[] before){
+
+    double[] after = new double[m_Converter.length];
+    for(int i=0; i < m_Converter.length; i++) 
+      after[i] = before[m_Converter[i]];
+    
+    return after;
+  }
+
+  /**
+   * Return the original internal class value given the randomized 
+   * class value, i.e. the string presentations of the two indices
+   * are the same.  It's useful when the filter is used within a classifier  
+   * so that the filtering procedure should be transparent to the 
+   * evaluation
+   *
+   * @param value the given value
+   * @return the original internal value, -1 if not found
+   * @throws Exception if the coverter table is not set yet
+   */
+  public double originalValue(double value)throws Exception{
+
+    if(m_Converter == null)
+      throw new IllegalStateException("Coverter table not defined yet!");
+	
+    for(int i=0; i < m_Converter.length; i++)
+      if((int)value == m_Converter[i])
+	return (double)i;
+
+    return -1;
+  }   
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5491 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new ClassOrder(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/Discretize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/Discretize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/Discretize.java	(revision 29)
@@ -0,0 +1,1030 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Discretize.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.ContingencyTables;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.SpecialFunctions;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that discretizes a range of numeric attributes in the dataset into nominal attributes. Discretization is by Fayyad &amp; Irani's MDL method (the default).<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Usama M. Fayyad, Keki B. Irani: Multi-interval discretization of continuousvalued attributes for classification learning. In: Thirteenth International Joint Conference on Articial Intelligence, 1022-1027, 1993.<br/>
+ * <br/>
+ * Igor Kononenko: On Biases in Estimating Multi-Valued Attributes. In: 14th International Joint Conference on Articial Intelligence, 1034-1040, 1995.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Fayyad1993,
+ *    author = {Usama M. Fayyad and Keki B. Irani},
+ *    booktitle = {Thirteenth International Joint Conference on Articial Intelligence},
+ *    pages = {1022-1027},
+ *    publisher = {Morgan Kaufmann Publishers},
+ *    title = {Multi-interval discretization of continuousvalued attributes for classification learning},
+ *    volume = {2},
+ *    year = {1993}
+ * }
+ * 
+ * &#64;inproceedings{Kononenko1995,
+ *    author = {Igor Kononenko},
+ *    booktitle = {14th International Joint Conference on Articial Intelligence},
+ *    pages = {1034-1040},
+ *    title = {On Biases in Estimating Multi-Valued Attributes},
+ *    year = {1995},
+ *    PS = {http://ai.fri.uni-lj.si/papers/kononenko95-ijcai.ps.gz}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to Discretize. First and last are valid indexes.
+ *  (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ * <pre> -D
+ *  Output binary attributes for discretized attributes.</pre>
+ * 
+ * <pre> -E
+ *  Use better encoding of split point for MDL.</pre>
+ * 
+ * <pre> -K
+ *  Use Kononenko's MDL criterion.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Discretize 
+  extends Filter 
+  implements SupervisedFilter, OptionHandler, WeightedInstancesHandler, 
+  	     TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3141006402280129097L;
+
+  /** Stores which columns to Discretize */
+  protected Range m_DiscretizeCols = new Range();
+
+  /** Store the current cutpoints */
+  protected double [][] m_CutPoints = null;
+
+  /** Output binary attributes for discretized attributes. */
+  protected boolean m_MakeBinary = false;
+
+  /** Use better encoding of split point for MDL. */
+  protected boolean m_UseBetterEncoding = false;
+
+  /** Use Kononenko's MDL criterion instead of Fayyad et al.'s */
+  protected boolean m_UseKononenko = false;
+
+  /** Constructor - initialises the filter */
+  public Discretize() {
+
+    setAttributeIndices("first-last");
+  }
+
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(7);
+
+    newVector.addElement(new Option(
+              "\tSpecifies list of columns to Discretize. First"
+	      + " and last are valid indexes.\n"
+	      + "\t(default none)",
+              "R", 1, "-R <col1,col2-col4,...>"));
+
+    newVector.addElement(new Option(
+              "\tInvert matching sense of column indexes.",
+              "V", 0, "-V"));
+
+    newVector.addElement(new Option(
+              "\tOutput binary attributes for discretized attributes.",
+              "D", 0, "-D"));
+
+    newVector.addElement(new Option(
+              "\tUse better encoding of split point for MDL.",
+              "E", 0, "-E"));
+
+    newVector.addElement(new Option(
+              "\tUse Kononenko's MDL criterion.",
+              "K", 0, "-K"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to Discretize. First and last are valid indexes.
+   *  (default none)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense of column indexes.</pre>
+   * 
+   * <pre> -D
+   *  Output binary attributes for discretized attributes.</pre>
+   * 
+   * <pre> -E
+   *  Use better encoding of split point for MDL.</pre>
+   * 
+   * <pre> -K
+   *  Use Kononenko's MDL criterion.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setMakeBinary(Utils.getFlag('D', options));
+    setUseBetterEncoding(Utils.getFlag('E', options));
+    setUseKononenko(Utils.getFlag('K', options));
+    setInvertSelection(Utils.getFlag('V', options));
+    
+    String convertList = Utils.getOption('R', options);
+    if (convertList.length() != 0) {
+      setAttributeIndices(convertList);
+    } else {
+      setAttributeIndices("first-last");
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [12];
+    int current = 0;
+
+    if (getMakeBinary()) {
+      options[current++] = "-D";
+    }
+    if (getUseBetterEncoding()) {
+      options[current++] = "-E";
+    }
+    if (getUseKononenko()) {
+      options[current++] = "-K";
+    }
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+
+    m_DiscretizeCols.setUpper(instanceInfo.numAttributes() - 1);
+    m_CutPoints = null;
+    
+    // If we implement loading cutfiles, then load 
+    //them here and set the output format
+    return false;
+  }
+
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (m_CutPoints != null) {
+      convertInstance(instance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+
+  /**
+   * Signifies that this batch of input to the filter is finished. If the 
+   * filter requires all instances prior to filtering, output() may now 
+   * be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_CutPoints == null) {
+      calculateCutPoints();
+
+      setOutputFormat();
+
+      // If we implement saving cutfiles, save the cuts here
+
+      // Convert pending input instances
+      for(int i = 0; i < getInputFormat().numInstances(); i++) {
+	convertInstance(getInputFormat().instance(i));
+      }
+    } 
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "An instance filter that discretizes a range of numeric"
+      + " attributes in the dataset into nominal attributes."
+      + " Discretization is by Fayyad & Irani's MDL method (the default).\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Usama M. Fayyad and Keki B. Irani");
+    result.setValue(Field.TITLE, "Multi-interval discretization of continuousvalued attributes for classification learning");
+    result.setValue(Field.BOOKTITLE, "Thirteenth International Joint Conference on Articial Intelligence");
+    result.setValue(Field.YEAR, "1993");
+    result.setValue(Field.VOLUME, "2");
+    result.setValue(Field.PAGES, "1022-1027");
+    result.setValue(Field.PUBLISHER, "Morgan Kaufmann Publishers");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "Igor Kononenko");
+    additional.setValue(Field.TITLE, "On Biases in Estimating Multi-Valued Attributes");
+    additional.setValue(Field.BOOKTITLE, "14th International Joint Conference on Articial Intelligence");
+    additional.setValue(Field.YEAR, "1995");
+    additional.setValue(Field.PAGES, "1034-1040");
+    additional.setValue(Field.PS, "http://ai.fri.uni-lj.si/papers/kononenko95-ijcai.ps.gz");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String makeBinaryTipText() {
+
+    return "Make resulting attributes binary.";
+  }
+
+  /**
+   * Gets whether binary attributes should be made for discretized ones.
+   *
+   * @return true if attributes will be binarized
+   */
+  public boolean getMakeBinary() {
+
+    return m_MakeBinary;
+  }
+
+  /** 
+   * Sets whether binary attributes should be made for discretized ones.
+   *
+   * @param makeBinary if binary attributes are to be made
+   */
+  public void setMakeBinary(boolean makeBinary) {
+
+    m_MakeBinary = makeBinary;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useKononenkoTipText() {
+
+    return "Use Kononenko's MDL criterion. If set to false"
+      + " uses the Fayyad & Irani criterion.";
+  }
+  
+  /**
+   * Gets whether Kononenko's MDL criterion is to be used.
+   *
+   * @return true if Kononenko's criterion will be used.
+   */
+  public boolean getUseKononenko() {
+
+    return m_UseKononenko;
+  }
+
+  /** 
+   * Sets whether Kononenko's MDL criterion is to be used.
+   *
+   * @param useKon true if Kononenko's one is to be used
+   */
+  public void setUseKononenko(boolean useKon) {
+
+    m_UseKononenko = useKon;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useBetterEncodingTipText() {
+
+    return "Uses a more efficient split point encoding.";
+  }
+
+  /**
+   * Gets whether better encoding is to be used for MDL.
+   *
+   * @return true if the better MDL encoding will be used
+   */
+  public boolean getUseBetterEncoding() {
+
+    return m_UseBetterEncoding;
+  }
+
+  /** 
+   * Sets whether better encoding is to be used for MDL.
+   *
+   * @param useBetterEncoding true if better encoding to be used.
+   */
+  public void setUseBetterEncoding(boolean useBetterEncoding) {
+
+    m_UseBetterEncoding = useBetterEncoding;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Set attribute selection mode. If false, only selected"
+      + " (numeric) attributes in the range will be discretized; if"
+      + " true, only non-selected attributes will be discretized.";
+  }
+
+  /**
+   * Gets whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_DiscretizeCols.getInvert();
+  }
+
+  /**
+   * Sets whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_DiscretizeCols.setInvert(invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_DiscretizeCols.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be Discretized (only numeric
+   * attributes among the selection will be Discretized).
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String rangeList) {
+
+    m_DiscretizeCols.setRanges(rangeList);
+  }
+
+  /**
+   * Sets which attributes are to be Discretized (only numeric
+   * attributes among the selection will be Discretized).
+   *
+   * @param attributes an array containing indexes of attributes to Discretize.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws IllegalArgumentException if an invalid set of ranges
+   * is supplied 
+   */
+  public void setAttributeIndicesArray(int [] attributes) {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+
+  /**
+   * Gets the cut points for an attribute
+   *
+   * @param attributeIndex the index (from 0) of the attribute to get the cut points of
+   * @return an array containing the cutpoints (or null if the
+   * attribute requested isn't being Discretized
+   */
+  public double [] getCutPoints(int attributeIndex) {
+
+    if (m_CutPoints == null) {
+      return null;
+    }
+    return m_CutPoints[attributeIndex];
+  }
+
+  /** Generate the cutpoints for each attribute */
+  protected void calculateCutPoints() {
+
+    Instances copy = null;
+
+    m_CutPoints = new double [getInputFormat().numAttributes()] [];
+    for(int i = getInputFormat().numAttributes() - 1; i >= 0; i--) {
+      if ((m_DiscretizeCols.isInRange(i)) && 
+	  (getInputFormat().attribute(i).isNumeric())) {
+
+	// Use copy to preserve order
+	if (copy == null) {
+	  copy = new Instances(getInputFormat());
+	}
+	calculateCutPointsByMDL(i, copy);
+      }
+    }
+  }
+
+  /**
+   * Set cutpoints for a single attribute using MDL.
+   *
+   * @param index the index of the attribute to set cutpoints for
+   * @param data the data to work with
+   */
+  protected void calculateCutPointsByMDL(int index,
+					 Instances data) {
+
+    // Sort instances
+    data.sort(data.attribute(index));
+
+    // Find first instances that's missing
+    int firstMissing = data.numInstances();
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (data.instance(i).isMissing(index)) {
+        firstMissing = i;
+        break;
+      }
+    }
+    m_CutPoints[index] = cutPointsForSubset(data, index, 0, firstMissing);
+  }
+
+  /** 
+   * Test using Kononenko's MDL criterion.
+   * 
+   * @param priorCounts
+   * @param bestCounts
+   * @param numInstances
+   * @param numCutPoints
+   * @return true if the split is acceptable
+   */
+  private boolean KononenkosMDL(double[] priorCounts,
+				double[][] bestCounts,
+				double numInstances,
+				int numCutPoints) {
+
+    double distPrior, instPrior, distAfter = 0, sum, instAfter = 0;
+    double before, after;
+    int numClassesTotal;
+
+    // Number of classes occuring in the set
+    numClassesTotal = 0;
+    for (int i = 0; i < priorCounts.length; i++) {
+      if (priorCounts[i] > 0) {
+	numClassesTotal++;
+      }
+    }
+
+    // Encode distribution prior to split
+    distPrior = SpecialFunctions.log2Binomial(numInstances 
+					      + numClassesTotal - 1,
+					      numClassesTotal - 1);
+
+    // Encode instances prior to split.
+    instPrior = SpecialFunctions.log2Multinomial(numInstances,
+						 priorCounts);
+
+    before = instPrior + distPrior;
+
+    // Encode distributions and instances after split.
+    for (int i = 0; i < bestCounts.length; i++) {
+      sum = Utils.sum(bestCounts[i]);
+      distAfter += SpecialFunctions.log2Binomial(sum + numClassesTotal - 1,
+						 numClassesTotal - 1);
+      instAfter += SpecialFunctions.log2Multinomial(sum,
+						    bestCounts[i]);
+    }
+
+    // Coding cost after split
+    after = Utils.log2(numCutPoints) + distAfter + instAfter;
+
+    // Check if split is to be accepted
+    return (before > after);
+  }
+
+
+  /** 
+   * Test using Fayyad and Irani's MDL criterion.
+   * 
+   * @param priorCounts
+   * @param bestCounts
+   * @param numInstances
+   * @param numCutPoints
+   * @return true if the splits is acceptable
+   */
+  private boolean FayyadAndIranisMDL(double[] priorCounts,
+				     double[][] bestCounts,
+				     double numInstances,
+				     int numCutPoints) {
+
+    double priorEntropy, entropy, gain; 
+    double entropyLeft, entropyRight, delta;
+    int numClassesTotal, numClassesRight, numClassesLeft;
+
+    // Compute entropy before split.
+    priorEntropy = ContingencyTables.entropy(priorCounts);
+
+    // Compute entropy after split.
+    entropy = ContingencyTables.entropyConditionedOnRows(bestCounts);
+
+    // Compute information gain.
+    gain = priorEntropy - entropy;
+
+    // Number of classes occuring in the set
+    numClassesTotal = 0;
+    for (int i = 0; i < priorCounts.length; i++) {
+      if (priorCounts[i] > 0) {
+	numClassesTotal++;
+      }
+    }
+
+    // Number of classes occuring in the left subset
+    numClassesLeft = 0;
+    for (int i = 0; i < bestCounts[0].length; i++) {
+      if (bestCounts[0][i] > 0) {
+	numClassesLeft++;
+      }
+    }
+
+    // Number of classes occuring in the right subset
+    numClassesRight = 0;
+    for (int i = 0; i < bestCounts[1].length; i++) {
+      if (bestCounts[1][i] > 0) {
+	numClassesRight++;
+      }
+    }
+
+    // Entropy of the left and the right subsets
+    entropyLeft = ContingencyTables.entropy(bestCounts[0]);
+    entropyRight = ContingencyTables.entropy(bestCounts[1]);
+
+    // Compute terms for MDL formula
+    delta = Utils.log2(Math.pow(3, numClassesTotal) - 2) - 
+      (((double) numClassesTotal * priorEntropy) - 
+       (numClassesRight * entropyRight) - 
+       (numClassesLeft * entropyLeft));
+
+    // Check if split is to be accepted
+    return (gain > (Utils.log2(numCutPoints) + delta) / (double)numInstances);
+  }
+    
+
+  /** 
+   * Selects cutpoints for sorted subset.
+   * 
+   * @param instances
+   * @param attIndex
+   * @param first
+   * @param lastPlusOne
+   * @return
+   */
+  private double[] cutPointsForSubset(Instances instances, int attIndex, 
+				      int first, int lastPlusOne) { 
+
+    double[][] counts, bestCounts;
+    double[] priorCounts, left, right, cutPoints;
+    double currentCutPoint = -Double.MAX_VALUE, bestCutPoint = -1, 
+      currentEntropy, bestEntropy, priorEntropy, gain;
+    int bestIndex = -1, numInstances = 0, numCutPoints = 0;
+
+    // Compute number of instances in set
+    if ((lastPlusOne - first) < 2) {
+      return null;
+    }
+
+    // Compute class counts.
+    counts = new double[2][instances.numClasses()];
+    for (int i = first; i < lastPlusOne; i++) {
+      numInstances += instances.instance(i).weight();
+      counts[1][(int)instances.instance(i).classValue()] +=
+	instances.instance(i).weight();
+    }
+
+    // Save prior counts
+    priorCounts = new double[instances.numClasses()];
+    System.arraycopy(counts[1], 0, priorCounts, 0, 
+		     instances.numClasses());
+
+    // Entropy of the full set
+    priorEntropy = ContingencyTables.entropy(priorCounts);
+    bestEntropy = priorEntropy;
+    
+    // Find best entropy.
+    bestCounts = new double[2][instances.numClasses()];
+    for (int i = first; i < (lastPlusOne - 1); i++) {
+      counts[0][(int)instances.instance(i).classValue()] +=
+	instances.instance(i).weight();
+      counts[1][(int)instances.instance(i).classValue()] -=
+	instances.instance(i).weight();
+      if (instances.instance(i).value(attIndex) < 
+	  instances.instance(i + 1).value(attIndex)) {
+	currentCutPoint = (instances.instance(i).value(attIndex) + 
+	  instances.instance(i + 1).value(attIndex)) / 2.0;
+	currentEntropy = ContingencyTables.entropyConditionedOnRows(counts);
+	if (currentEntropy < bestEntropy) {
+	  bestCutPoint = currentCutPoint;
+	  bestEntropy = currentEntropy;
+	  bestIndex = i;
+	  System.arraycopy(counts[0], 0, 
+			   bestCounts[0], 0, instances.numClasses());
+	  System.arraycopy(counts[1], 0, 
+			   bestCounts[1], 0, instances.numClasses()); 
+	}
+	numCutPoints++;
+      }
+    }
+
+    // Use worse encoding?
+    if (!m_UseBetterEncoding) {
+      numCutPoints = (lastPlusOne - first) - 1;
+    }
+
+    // Checks if gain is zero
+    gain = priorEntropy - bestEntropy;
+    if (gain <= 0) {
+      return null;
+    }
+
+    // Check if split is to be accepted
+    if ((m_UseKononenko && KononenkosMDL(priorCounts, bestCounts,
+					 numInstances, numCutPoints)) ||
+	(!m_UseKononenko && FayyadAndIranisMDL(priorCounts, bestCounts,
+					       numInstances, numCutPoints))) {
+      
+      // Select split points for the left and right subsets
+      left = cutPointsForSubset(instances, attIndex, first, bestIndex + 1);
+      right = cutPointsForSubset(instances, attIndex, 
+				 bestIndex + 1, lastPlusOne);
+      
+      // Merge cutpoints and return them
+      if ((left == null) && (right) == null) {
+	cutPoints = new double[1];
+	cutPoints[0] = bestCutPoint;
+      } else if (right == null) {
+	cutPoints = new double[left.length + 1];
+	System.arraycopy(left, 0, cutPoints, 0, left.length);
+	cutPoints[left.length] = bestCutPoint;
+      } else if (left == null) {
+	cutPoints = new double[1 + right.length];
+	cutPoints[0] = bestCutPoint;
+	System.arraycopy(right, 0, cutPoints, 1, right.length);
+      } else {
+	cutPoints = new double[left.length + right.length + 1];
+	System.arraycopy(left, 0, cutPoints, 0, left.length);
+	cutPoints[left.length] = bestCutPoint;
+	System.arraycopy(right, 0, cutPoints, left.length + 1, right.length);
+      }
+      
+      return cutPoints;
+    } else
+      return null;
+  }
+ 
+  /**
+   * Set the output format. Takes the currently defined cutpoints and 
+   * m_InputFormat and calls setOutputFormat(Instances) appropriately.
+   */
+  protected void setOutputFormat() {
+
+    if (m_CutPoints == null) {
+      setOutputFormat(null);
+      return;
+    }
+    FastVector attributes = new FastVector(getInputFormat().numAttributes());
+    int classIndex = getInputFormat().classIndex();
+    for(int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if ((m_DiscretizeCols.isInRange(i)) 
+	  && (getInputFormat().attribute(i).isNumeric())) {
+	if (!m_MakeBinary) {
+	  FastVector attribValues = new FastVector(1);
+	  if (m_CutPoints[i] == null) {
+	    attribValues.addElement("'All'");
+	  } else {
+	    for(int j = 0; j <= m_CutPoints[i].length; j++) {
+	      if (j == 0) {
+		attribValues.addElement("'(-inf-"
+			+ Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      } else if (j == m_CutPoints[i].length) {
+		attribValues.addElement("'("
+			+ Utils.doubleToString(m_CutPoints[i][j - 1], 6) 
+					+ "-inf)'");
+	      } else {
+		attribValues.addElement("'("
+			+ Utils.doubleToString(m_CutPoints[i][j - 1], 6) + "-"
+			+ Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      }
+	    }
+	  }
+	  attributes.addElement(new Attribute(getInputFormat().
+					      attribute(i).name(),
+					      attribValues));
+	} else {
+	  if (m_CutPoints[i] == null) {
+	    FastVector attribValues = new FastVector(1);
+	    attribValues.addElement("'All'");
+	    attributes.addElement(new Attribute(getInputFormat().
+						attribute(i).name(),
+						attribValues));
+	  } else {
+	    if (i < getInputFormat().classIndex()) {
+	      classIndex += m_CutPoints[i].length - 1;
+	    }
+	    for(int j = 0; j < m_CutPoints[i].length; j++) {
+	      FastVector attribValues = new FastVector(2);
+	      attribValues.addElement("'(-inf-"
+		      + Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      attribValues.addElement("'("
+		      + Utils.doubleToString(m_CutPoints[i][j], 6) + "-inf)'");
+	      attributes.addElement(new Attribute(getInputFormat().
+						  attribute(i).name(),
+						  attribValues));
+	    }
+	  }
+	}
+      } else {
+	attributes.addElement(getInputFormat().attribute(i).copy());
+      }
+    }
+    Instances outputFormat = 
+      new Instances(getInputFormat().relationName(), attributes, 0);
+    outputFormat.setClassIndex(classIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is added to 
+   * the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  protected void convertInstance(Instance instance) {
+
+    int index = 0;
+    double [] vals = new double [outputFormatPeek().numAttributes()];
+    // Copy and convert the values
+    for(int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if (m_DiscretizeCols.isInRange(i) && 
+	  getInputFormat().attribute(i).isNumeric()) {
+	int j;
+	double currentVal = instance.value(i);
+	if (m_CutPoints[i] == null) {
+	  if (instance.isMissing(i)) {
+	    vals[index] = Utils.missingValue();
+	  } else {
+	    vals[index] = 0;
+	  }
+	  index++;
+	} else {
+	  if (!m_MakeBinary) {
+	    if (instance.isMissing(i)) {
+	      vals[index] = Utils.missingValue();
+	    } else {
+	      for (j = 0; j < m_CutPoints[i].length; j++) {
+		if (currentVal <= m_CutPoints[i][j]) {
+		  break;
+		}
+	      }
+              vals[index] = j;
+	    }
+	    index++;
+	  } else {
+	    for (j = 0; j < m_CutPoints[i].length; j++) {
+	      if (instance.isMissing(i)) {
+                vals[index] = Utils.missingValue();
+	      } else if (currentVal <= m_CutPoints[i][j]) {
+                vals[index] = 0;
+	      } else {
+                vals[index] = 1;
+	      }
+	      index++;
+	    }
+	  }   
+	}
+      } else {
+        vals[index] = instance.value(i);
+	index++;
+      }
+    }
+    
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Discretize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/NominalToBinary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/NominalToBinary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/NominalToBinary.java	(revision 29)
@@ -0,0 +1,678 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NominalToBinary.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.UnassignedClassException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts all nominal attributes into binary numeric attributes. An attribute with k values is transformed into k binary attributes if the class is nominal (using the one-attribute-per-value approach). Binary attributes are left binary, if option '-A' is not given.If the class is numeric, k - 1 new binary attributes are generated in the manner described in "Classification and Regression Trees" by Breiman et al. (i.e. taking the average class value associated with each attribute value into account)<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * L. Breiman, J.H. Friedman, R.A. Olshen, C.J. Stone (1984). Classification and Regression Trees. Wadsworth Inc.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Breiman1984,
+ *    author = {L. Breiman and J.H. Friedman and R.A. Olshen and C.J. Stone},
+ *    publisher = {Wadsworth Inc},
+ *    title = {Classification and Regression Trees},
+ *    year = {1984},
+ *    ISBN = {0412048418}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Sets if binary attributes are to be coded as nominal ones.</pre>
+ * 
+ * <pre> -A
+ *  For each nominal value a new attribute is created, 
+ *  not only if there are more than 2 values.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $ 
+ */
+public class NominalToBinary 
+  extends Filter 
+  implements SupervisedFilter, OptionHandler, TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5004607029857673950L;
+
+  /** The sorted indices of the attribute values. */
+  private int[][] m_Indices = null;
+
+  /** Are the new attributes going to be nominal or numeric ones? */
+  private boolean m_Numeric = true;
+
+  /** Are all values transformed into new attributes? */
+  private boolean m_TransformAll = false;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Converts all nominal attributes into binary numeric attributes. An "
+      + "attribute with k values is transformed into k binary attributes if "
+      + "the class is nominal (using the one-attribute-per-value approach). "
+      + "Binary attributes are left binary, if option '-A' is not given."
+      + "If the class is numeric, k - 1 new binary attributes are generated "
+      + "in the manner described in \"Classification and Regression "
+      + "Trees\" by Breiman et al. (i.e. taking the average class value associated "
+      + "with each attribute value into account)\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "L. Breiman and J.H. Friedman and R.A. Olshen and C.J. Stone");
+    result.setValue(Field.TITLE, "Classification and Regression Trees");
+    result.setValue(Field.YEAR, "1984");
+    result.setValue(Field.PUBLISHER, "Wadsworth Inc");
+    result.setValue(Field.ISBN, "0412048418");
+    
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    if (instanceInfo.classIndex() < 0) {
+      throw new UnassignedClassException("No class has been assigned to the instances");
+    }
+    setOutputFormat();
+    m_Indices = null;
+    if (instanceInfo.classAttribute().isNominal()) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if ((m_Indices != null) || 
+	(getInputFormat().classAttribute().isNominal())) {
+      convertInstance(instance);
+      return true;
+    }
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if ((m_Indices == null) && 
+	(getInputFormat().classAttribute().isNumeric())) {
+      computeAverageClassValues();
+      setOutputFormat();
+
+      // Convert pending input instances
+
+      for(int i = 0; i < getInputFormat().numInstances(); i++) {
+	convertInstance(getInputFormat().instance(i));
+      }
+    } 
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+	"\tSets if binary attributes are to be coded as nominal ones.",
+	"N", 0, "-N"));
+    
+    newVector.addElement(new Option(
+	"\tFor each nominal value a new attribute is created, \n"
+	+ "\tnot only if there are more than 2 values.",
+	"A", 0, "-A"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Sets if binary attributes are to be coded as nominal ones.</pre>
+   * 
+   * <pre> -A
+   *  For each nominal value a new attribute is created, 
+   *  not only if there are more than 2 values.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setBinaryAttributesNominal(Utils.getFlag('N', options));
+
+    setTransformAllValues(Utils.getFlag('A', options));
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [1];
+    int current = 0;
+
+    if (getBinaryAttributesNominal()) {
+      options[current++] = "-N";
+    }
+
+    if (getTransformAllValues()) {
+      options[current++] = "-A";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binaryAttributesNominalTipText() {
+    return "Whether resulting binary attributes will be nominal.";
+  }
+
+  /**
+   * Gets if binary attributes are to be treated as nominal ones.
+   *
+   * @return true if binary attributes are to be treated as nominal ones
+   */
+  public boolean getBinaryAttributesNominal() {
+
+    return !m_Numeric;
+  }
+
+  /**
+   * Sets if binary attributes are to be treates as nominal ones.
+   *
+   * @param bool true if binary attributes are to be treated as nominal ones
+   */
+  public void setBinaryAttributesNominal(boolean bool) {
+
+    m_Numeric = !bool;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String transformAllValuesTipText() {
+    return "Whether all nominal values are turned into new attributes, not only if there are more than 2.";
+  }
+
+  /**
+   * Gets if all nominal values are turned into new attributes, not only if
+   * there are more than 2.
+   *
+   * @return true all nominal values are transformed into new attributes
+   */
+  public boolean getTransformAllValues() {
+
+    return m_TransformAll;
+  }
+
+  /**
+   * Sets whether all nominal values are transformed into new attributes, not
+   * just if there are more than 2.
+   *
+   * @param bool true if all nominal value are transformed into new attributes
+   */
+  public void setTransformAllValues(boolean bool) {
+
+    m_TransformAll = bool;
+  }
+
+  /** Computes average class values for each attribute and value */
+  private void computeAverageClassValues() {
+
+    double totalCounts, sum;
+    Instance instance;
+    double [] counts;
+
+    double [][] avgClassValues = new double[getInputFormat().numAttributes()][0];
+    m_Indices = new int[getInputFormat().numAttributes()][0];
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (att.isNominal()) {
+	avgClassValues[j] = new double [att.numValues()];
+	counts = new double [att.numValues()];
+	for (int i = 0; i < getInputFormat().numInstances(); i++) {
+	  instance = getInputFormat().instance(i);
+	  if (!instance.classIsMissing() && 
+	      (!instance.isMissing(j))) {
+	    counts[(int)instance.value(j)] += instance.weight();
+	    avgClassValues[j][(int)instance.value(j)] += 
+	      instance.weight() * instance.classValue();
+	  }
+	}
+	sum = Utils.sum(avgClassValues[j]);
+	totalCounts = Utils.sum(counts);
+	if (Utils.gr(totalCounts, 0)) {
+	  for (int k = 0; k < att.numValues(); k++) {
+	    if (Utils.gr(counts[k], 0)) {
+	      avgClassValues[j][k] /= (double)counts[k];
+	    } else {
+	      avgClassValues[j][k] = sum / (double)totalCounts;
+	    }
+	  }
+	}
+	m_Indices[j] = Utils.sort(avgClassValues[j]);
+      }
+    }
+  }
+
+  /** Set the output format. */
+  private void setOutputFormat() {
+
+    if (getInputFormat().classAttribute().isNominal()) {
+      setOutputFormatNominal();
+    } else {
+      setOutputFormatNumeric();
+    }
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstance(Instance inst) {
+
+    if (getInputFormat().classAttribute().isNominal()) {
+      convertInstanceNominal(inst);
+    } else {
+      convertInstanceNumeric(inst);
+    }
+  }
+
+  /**
+   * Set the output format if the class is nominal.
+   */
+  private void setOutputFormatNominal() {
+
+    FastVector newAtts;
+    int newClassIndex;
+    StringBuffer attributeName;
+    Instances outputFormat;
+    FastVector vals;
+
+    // Compute new attributes
+
+    newClassIndex = getInputFormat().classIndex();
+    newAtts = new FastVector();
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if ((!att.isNominal()) || 
+	  (j == getInputFormat().classIndex())) {
+	newAtts.addElement(att.copy());
+      } else {
+	if ( (att.numValues() <= 2) && (!m_TransformAll) ) {
+	  if (m_Numeric) {
+	    newAtts.addElement(new Attribute(att.name()));
+	  } else {
+	    newAtts.addElement(att.copy());
+	  }
+	} else {
+
+	  if (j < getInputFormat().classIndex()) {
+	    newClassIndex += att.numValues() - 1;
+	  }
+
+	  // Compute values for new attributes
+	  for (int k = 0; k < att.numValues(); k++) {
+	    attributeName = 
+	      new StringBuffer(att.name() + "=");
+	    attributeName.append(att.value(k));
+	    if (m_Numeric) {
+	      newAtts.
+		addElement(new Attribute(attributeName.toString()));
+	    } else {
+	      vals = new FastVector(2);
+	      vals.addElement("f"); vals.addElement("t");
+	      newAtts.
+		addElement(new Attribute(attributeName.toString(), vals));
+	    }
+	  }
+	}
+      }
+    }
+    outputFormat = new Instances(getInputFormat().relationName(),
+				 newAtts, 0);
+    outputFormat.setClassIndex(newClassIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Set the output format if the class is numeric.
+   */
+  private void setOutputFormatNumeric() {
+
+    if (m_Indices == null) {
+      setOutputFormat(null);
+      return;
+    }
+    FastVector newAtts;
+    int newClassIndex;
+    StringBuffer attributeName;
+    Instances outputFormat;
+    FastVector vals;
+
+    // Compute new attributes
+
+    newClassIndex = getInputFormat().classIndex();
+    newAtts = new FastVector();
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if ((!att.isNominal()) || 
+	  (j == getInputFormat().classIndex())) {
+	newAtts.addElement(att.copy());
+      } else {
+	if (j < getInputFormat().classIndex())
+	  newClassIndex += att.numValues() - 2;
+	  
+	// Compute values for new attributes
+	  
+	for (int k = 1; k < att.numValues(); k++) {
+	  attributeName = 
+	    new StringBuffer(att.name() + "=");
+	  for (int l = k; l < att.numValues(); l++) {
+	    if (l > k) {
+	      attributeName.append(',');
+	    }
+	    attributeName.append(att.value(m_Indices[j][l]));
+	  }
+	  if (m_Numeric) {
+	    newAtts.
+	      addElement(new Attribute(attributeName.toString()));
+	  } else {
+	    vals = new FastVector(2);
+	    vals.addElement("f"); vals.addElement("t");
+	    newAtts.
+	      addElement(new Attribute(attributeName.toString(), vals));
+	  }
+	}
+      }
+    }
+    outputFormat = new Instances(getInputFormat().relationName(),
+				 newAtts, 0);
+    outputFormat.setClassIndex(newClassIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Convert a single instance over if the class is nominal. The converted 
+   * instance is added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstanceNominal(Instance instance) {
+
+    double [] vals = new double [outputFormatPeek().numAttributes()];
+    int attSoFar = 0;
+
+    for(int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if ((!att.isNominal()) || (j == getInputFormat().classIndex())) {
+	vals[attSoFar] = instance.value(j);
+	attSoFar++;
+      } else {
+	if ( (att.numValues() <= 2) && (!m_TransformAll) ) {
+	  vals[attSoFar] = instance.value(j);
+	  attSoFar++;
+	} else {
+	  if (instance.isMissing(j)) {
+	    for (int k = 0; k < att.numValues(); k++) {
+              vals[attSoFar + k] = instance.value(j);
+	    }
+	  } else {
+	    for (int k = 0; k < att.numValues(); k++) {
+	      if (k == (int)instance.value(j)) {
+                vals[attSoFar + k] = 1;
+	      } else {
+                vals[attSoFar + k] = 0;
+	      }
+	    }
+	  }
+	  attSoFar += att.numValues();
+	}
+      }
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+  }
+
+  /**
+   * Convert a single instance over if the class is numeric. The converted 
+   * instance is added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstanceNumeric(Instance instance) {
+
+    double [] vals = new double [outputFormatPeek().numAttributes()];
+    int attSoFar = 0;
+
+    for(int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if ((!att.isNominal()) || (j == getInputFormat().classIndex())) {
+	vals[attSoFar] = instance.value(j);
+	attSoFar++;
+      } else {
+	if (instance.isMissing(j)) {
+	  for (int k = 0; k < att.numValues() - 1; k++) {
+            vals[attSoFar + k] = instance.value(j);
+	  }
+	} else {
+	  int k = 0;
+	  while ((int)instance.value(j) != m_Indices[j][k]) {
+            vals[attSoFar + k] = 1;
+	    k++;
+	  }
+	  while (k < att.numValues() - 1) {
+            vals[attSoFar + k] = 0;
+	    k++;
+	  }
+	}
+	attSoFar += att.numValues() - 1;
+      }
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new NominalToBinary(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/PLSFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/PLSFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/attribute/PLSFilter.java	(revision 29)
@@ -0,0 +1,1162 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PLSFilter.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.matrix.EigenvalueDecomposition;
+import weka.core.matrix.Matrix;
+import weka.filters.Filter;
+import weka.filters.SimpleBatchFilter;
+import weka.filters.SupervisedFilter;
+import weka.filters.unsupervised.attribute.Center;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+import weka.filters.unsupervised.attribute.Standardize;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Runs Partial Least Square Regression over the given instances and computes the resulting beta matrix for prediction.<br/>
+ * By default it replaces missing values and centers the data.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Tormod Naes, Tomas Isaksson, Tom Fearn, Tony Davies (2002). A User Friendly Guide to Multivariate Calibration and Classification. NIR Publications.<br/>
+ * <br/>
+ * StatSoft, Inc.. Partial Least Squares (PLS).<br/>
+ * <br/>
+ * Bent Jorgensen, Yuri Goegebeur. Module 7: Partial least squares regression I.<br/>
+ * <br/>
+ * S. de Jong (1993). SIMPLS: an alternative approach to partial least squares regression. Chemometrics and Intelligent Laboratory Systems. 18:251-263.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;book{Naes2002,
+ *    author = {Tormod Naes and Tomas Isaksson and Tom Fearn and Tony Davies},
+ *    publisher = {NIR Publications},
+ *    title = {A User Friendly Guide to Multivariate Calibration and Classification},
+ *    year = {2002},
+ *    ISBN = {0-9528666-2-5}
+ * }
+ * 
+ * &#64;misc{missing_id,
+ *    author = {StatSoft, Inc.},
+ *    booktitle = {Electronic Textbook StatSoft},
+ *    title = {Partial Least Squares (PLS)},
+ *    HTTP = {http://www.statsoft.com/textbook/stpls.html}
+ * }
+ * 
+ * &#64;misc{missing_id,
+ *    author = {Bent Jorgensen and Yuri Goegebeur},
+ *    booktitle = {ST02: Multivariate Data Analysis and Chemometrics},
+ *    title = {Module 7: Partial least squares regression I},
+ *    HTTP = {http://statmaster.sdu.dk/courses/ST02/module07/}
+ * }
+ * 
+ * &#64;article{Jong1993,
+ *    author = {S. de Jong},
+ *    journal = {Chemometrics and Intelligent Laboratory Systems},
+ *    pages = {251-263},
+ *    title = {SIMPLS: an alternative approach to partial least squares regression},
+ *    volume = {18},
+ *    year = {1993}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The number of components to compute.
+ *  (default: 20)</pre>
+ * 
+ * <pre> -U
+ *  Updates the class attribute as well.
+ *  (default: off)</pre>
+ * 
+ * <pre> -M
+ *  Turns replacing of missing values on.
+ *  (default: off)</pre>
+ * 
+ * <pre> -A &lt;SIMPLS|PLS1&gt;
+ *  The algorithm to use.
+ *  (default: PLS1)</pre>
+ * 
+ * <pre> -P &lt;none|center|standardize&gt;
+ *  The type of preprocessing that is applied to the data.
+ *  (default: center)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class PLSFilter
+  extends SimpleBatchFilter 
+  implements SupervisedFilter, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3335106965521265631L;
+
+  /** the type of algorithm: SIMPLS */
+  public static final int ALGORITHM_SIMPLS = 1;
+  /** the type of algorithm: PLS1 */
+  public static final int ALGORITHM_PLS1 = 2;
+  /** the types of algorithm */
+  public static final Tag[] TAGS_ALGORITHM = {
+    new Tag(ALGORITHM_SIMPLS, "SIMPLS"),
+    new Tag(ALGORITHM_PLS1, "PLS1")
+  };
+
+  /** the type of preprocessing: None */
+  public static final int PREPROCESSING_NONE = 0;
+  /** the type of preprocessing: Center */
+  public static final int PREPROCESSING_CENTER = 1;
+  /** the type of preprocessing: Standardize */
+  public static final int PREPROCESSING_STANDARDIZE = 2;
+  /** the types of preprocessing */
+  public static final Tag[] TAGS_PREPROCESSING = {
+    new Tag(PREPROCESSING_NONE, "none"),
+    new Tag(PREPROCESSING_CENTER, "center"),
+    new Tag(PREPROCESSING_STANDARDIZE, "standardize")
+  };
+
+  /** the maximum number of components to generate */
+  protected int m_NumComponents = 20;
+  
+  /** the type of algorithm */
+  protected int m_Algorithm = ALGORITHM_PLS1;
+
+  /** the regression vector "r-hat" for PLS1 */
+  protected Matrix m_PLS1_RegVector = null;
+
+  /** the P matrix for PLS1 */
+  protected Matrix m_PLS1_P = null;
+
+  /** the W matrix for PLS1 */
+  protected Matrix m_PLS1_W = null;
+
+  /** the b-hat vector for PLS1 */
+  protected Matrix m_PLS1_b_hat = null;
+  
+  /** the W matrix for SIMPLS */
+  protected Matrix m_SIMPLS_W = null;
+  
+  /** the B matrix for SIMPLS (used for prediction) */
+  protected Matrix m_SIMPLS_B = null;
+  
+  /** whether to include the prediction, i.e., modifying the class attribute */
+  protected boolean m_PerformPrediction = false;
+
+  /** for replacing missing values */
+  protected Filter m_Missing = null;
+  
+  /** whether to replace missing values */
+  protected boolean m_ReplaceMissing = true;
+  
+  /** for centering the data */
+  protected Filter m_Filter = null;
+  
+  /** the type of preprocessing */
+  protected int m_Preprocessing = PREPROCESSING_CENTER;
+
+  /** the mean of the class */
+  protected double m_ClassMean = 0;
+
+  /** the standard deviation of the class */
+  protected double m_ClassStdDev = 0;
+  
+  /**
+   * default constructor
+   */
+  public PLSFilter() {
+    super();
+    
+    // setup pre-processing
+    m_Missing = new ReplaceMissingValues();
+    m_Filter  = new Center();
+  }
+  
+  /**
+   * Returns a string describing this classifier.
+   *
+   * @return      a description of the classifier suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Runs Partial Least Square Regression over the given instances "
+      + "and computes the resulting beta matrix for prediction.\n"
+      + "By default it replaces missing values and centers the data.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.BOOK);
+    result.setValue(Field.AUTHOR, "Tormod Naes and Tomas Isaksson and Tom Fearn and Tony Davies");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.TITLE, "A User Friendly Guide to Multivariate Calibration and Classification");
+    result.setValue(Field.PUBLISHER, "NIR Publications");
+    result.setValue(Field.ISBN, "0-9528666-2-5");
+    
+    additional = result.add(Type.MISC);
+    additional.setValue(Field.AUTHOR, "StatSoft, Inc.");
+    additional.setValue(Field.TITLE, "Partial Least Squares (PLS)");
+    additional.setValue(Field.BOOKTITLE, "Electronic Textbook StatSoft");
+    additional.setValue(Field.HTTP, "http://www.statsoft.com/textbook/stpls.html");
+    
+    additional = result.add(Type.MISC);
+    additional.setValue(Field.AUTHOR, "Bent Jorgensen and Yuri Goegebeur");
+    additional.setValue(Field.TITLE, "Module 7: Partial least squares regression I");
+    additional.setValue(Field.BOOKTITLE, "ST02: Multivariate Data Analysis and Chemometrics");
+    additional.setValue(Field.HTTP, "http://statmaster.sdu.dk/courses/ST02/module07/");
+    
+    additional = result.add(Type.ARTICLE);
+    additional.setValue(Field.AUTHOR, "S. de Jong");
+    additional.setValue(Field.YEAR, "1993");
+    additional.setValue(Field.TITLE, "SIMPLS: an alternative approach to partial least squares regression");
+    additional.setValue(Field.JOURNAL, "Chemometrics and Intelligent Laboratory Systems");
+    additional.setValue(Field.VOLUME, "18");
+    additional.setValue(Field.PAGES, "251-263");
+    
+    return result;
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		enm;
+    String		param;
+    SelectedTag		tag;
+    int			i;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe number of components to compute.\n"
+	+ "\t(default: 20)",
+	"C", 1, "-C <num>"));
+
+    result.addElement(new Option(
+	"\tUpdates the class attribute as well.\n"
+	+ "\t(default: off)",
+	"U", 0, "-U"));
+
+    result.addElement(new Option(
+	"\tTurns replacing of missing values on.\n"
+	+ "\t(default: off)",
+	"M", 0, "-M"));
+
+    param = "";
+    for (i = 0; i < TAGS_ALGORITHM.length; i++) {
+      if (i > 0)
+	param += "|";
+      tag = new SelectedTag(TAGS_ALGORITHM[i].getID(), TAGS_ALGORITHM);
+      param += tag.getSelectedTag().getReadable();
+    }
+    result.addElement(new Option(
+	"\tThe algorithm to use.\n"
+	+ "\t(default: PLS1)",
+	"A", 1, "-A <" + param + ">"));
+
+    param = "";
+    for (i = 0; i < TAGS_PREPROCESSING.length; i++) {
+      if (i > 0)
+	param += "|";
+      tag = new SelectedTag(TAGS_PREPROCESSING[i].getID(), TAGS_PREPROCESSING);
+      param += tag.getSelectedTag().getReadable();
+    }
+    result.addElement(new Option(
+	"\tThe type of preprocessing that is applied to the data.\n"
+	+ "\t(default: center)",
+	"P", 1, "-P <" + param + ">"));
+
+    return result.elements();
+  }
+
+  /**
+   * returns the options of the current setup
+   *
+   * @return      the current options
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-C");
+    result.add("" + getNumComponents());
+
+    if (getPerformPrediction())
+      result.add("-U");
+    
+    if (getReplaceMissing())
+      result.add("-M");
+    
+    result.add("-A");
+    result.add("" + getAlgorithm().getSelectedTag().getReadable());
+
+    result.add("-P");
+    result.add("" + getPreprocessing().getSelectedTag().getReadable());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The number of components to compute.
+   *  (default: 20)</pre>
+   * 
+   * <pre> -U
+   *  Updates the class attribute as well.
+   *  (default: off)</pre>
+   * 
+   * <pre> -M
+   *  Turns replacing of missing values on.
+   *  (default: off)</pre>
+   * 
+   * <pre> -A &lt;SIMPLS|PLS1&gt;
+   *  The algorithm to use.
+   *  (default: PLS1)</pre>
+   * 
+   * <pre> -P &lt;none|center|standardize&gt;
+   *  The type of preprocessing that is applied to the data.
+   *  (default: center)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if the option setting fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("C", options);
+    if (tmpStr.length() != 0)
+      setNumComponents(Integer.parseInt(tmpStr));
+    else
+      setNumComponents(20);
+
+    setPerformPrediction(Utils.getFlag("U", options));
+    
+    setReplaceMissing(Utils.getFlag("M", options));
+    
+    tmpStr = Utils.getOption("A", options);
+    if (tmpStr.length() != 0)
+      setAlgorithm(new SelectedTag(tmpStr, TAGS_ALGORITHM));
+    else
+      setAlgorithm(new SelectedTag(ALGORITHM_PLS1, TAGS_ALGORITHM));
+    
+    tmpStr = Utils.getOption("P", options);
+    if (tmpStr.length() != 0)
+      setPreprocessing(new SelectedTag(tmpStr, TAGS_PREPROCESSING));
+    else
+      setPreprocessing(new SelectedTag(PREPROCESSING_CENTER, TAGS_PREPROCESSING));
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numComponentsTipText() {
+    return "The number of components to compute.";
+  }
+
+  /**
+   * sets the maximum number of attributes to use.
+   * 
+   * @param value	the maximum number of attributes
+   */
+  public void setNumComponents(int value) {
+    m_NumComponents = value;
+  }
+
+  /**
+   * returns the maximum number of attributes to use.
+   * 
+   * @return		the current maximum number of attributes
+   */
+  public int getNumComponents() {
+    return m_NumComponents;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String performPredictionTipText() {
+    return "Whether to update the class attribute with the predicted value.";
+  }
+
+  /**
+   * Sets whether to update the class attribute with the predicted value.
+   * 
+   * @param value	if true the class value will be replaced by the 
+   * 			predicted value.
+   */
+  public void setPerformPrediction(boolean value) {
+    m_PerformPrediction = value;
+  }
+
+  /**
+   * Gets whether the class attribute is updated with the predicted value.
+   * 
+   * @return		true if the class attribute is updated
+   */
+  public boolean getPerformPrediction() {
+    return m_PerformPrediction;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String algorithmTipText() {
+    return "Sets the type of algorithm to use.";
+  }
+
+  /**
+   * Sets the type of algorithm to use 
+   *
+   * @param value 	the algorithm type
+   */
+  public void setAlgorithm(SelectedTag value) {
+    if (value.getTags() == TAGS_ALGORITHM) {
+      m_Algorithm = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of algorithm to use 
+   *
+   * @return 		the current algorithm type.
+   */
+  public SelectedTag getAlgorithm() {
+    return new SelectedTag(m_Algorithm, TAGS_ALGORITHM);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String replaceMissingTipText() {
+    return "Whether to replace missing values.";
+  }
+
+  /**
+   * Sets whether to replace missing values.
+   * 
+   * @param value	if true missing values are replaced with the
+   * 			ReplaceMissingValues filter.
+   */
+  public void setReplaceMissing(boolean value) {
+    m_ReplaceMissing = value;
+  }
+
+  /**
+   * Gets whether missing values are replace.
+   * 
+   * @return		true if missing values are replaced with the 
+   * 			ReplaceMissingValues filter
+   */
+  public boolean getReplaceMissing() {
+    return m_ReplaceMissing;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String preprocessingTipText() {
+    return "Sets the type of preprocessing to use.";
+  }
+
+  /**
+   * Sets the type of preprocessing to use 
+   *
+   * @param value 	the preprocessing type
+   */
+  public void setPreprocessing(SelectedTag value) {
+    if (value.getTags() == TAGS_PREPROCESSING) {
+      m_Preprocessing = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of preprocessing to use 
+   *
+   * @return 		the current preprocessing type.
+   */
+  public SelectedTag getPreprocessing() {
+    return new SelectedTag(m_Preprocessing, TAGS_PREPROCESSING);
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) 
+    throws Exception {
+
+    // generate header
+    FastVector atts = new FastVector();
+    String prefix = getAlgorithm().getSelectedTag().getReadable();
+    for (int i = 0; i < getNumComponents(); i++)
+      atts.addElement(new Attribute(prefix + "_" + (i+1)));
+    atts.addElement(new Attribute("Class"));
+    Instances result = new Instances(prefix, atts, 0);
+    result.setClassIndex(result.numAttributes() - 1);
+    
+    return result;
+  }
+  
+  /**
+   * returns the data minus the class column as matrix
+   * 
+   * @param instances	the data to work on
+   * @return		the data without class attribute
+   */
+  protected Matrix getX(Instances instances) {
+    double[][]	x;
+    double[]	values;
+    Matrix	result;
+    int		i;
+    int		n;
+    int		j;
+    int		clsIndex;
+    
+    clsIndex = instances.classIndex();
+    x        = new double[instances.numInstances()][];
+    
+    for (i = 0; i < instances.numInstances(); i++) {
+      values = instances.instance(i).toDoubleArray();
+      x[i]   = new double[values.length - 1];
+      
+      j = 0;
+      for (n = 0; n < values.length; n++) {
+	if (n != clsIndex) {
+	  x[i][j] = values[n];
+	  j++;
+	}
+      }
+    }
+    
+    result = new Matrix(x);
+    
+    return result;
+  }
+  
+  /**
+   * returns the data minus the class column as matrix
+   * 
+   * @param instance	the instance to work on
+   * @return		the data without the class attribute
+   */
+  protected Matrix getX(Instance instance) {
+    double[][]	x;
+    double[]	values;
+    Matrix	result;
+    
+    x = new double[1][];
+    values = instance.toDoubleArray();
+    x[0] = new double[values.length - 1];
+    System.arraycopy(values, 0, x[0], 0, values.length - 1);
+    
+    result = new Matrix(x);
+    
+    return result;
+  }
+  
+  /**
+   * returns the data class column as matrix
+   * 
+   * @param instances	the data to work on
+   * @return		the class attribute
+   */
+  protected Matrix getY(Instances instances) {
+    double[][]	y;
+    Matrix	result;
+    int		i;
+    
+    y = new double[instances.numInstances()][1];
+    for (i = 0; i < instances.numInstances(); i++)
+      y[i][0] = instances.instance(i).classValue();
+    
+    result = new Matrix(y);
+    
+    return result;
+  }
+  
+  /**
+   * returns the data class column as matrix
+   * 
+   * @param instance	the instance to work on
+   * @return		the class attribute
+   */
+  protected Matrix getY(Instance instance) {
+    double[][]	y;
+    Matrix	result;
+    
+    y = new double[1][1];
+    y[0][0] = instance.classValue();
+    
+    result = new Matrix(y);
+    
+    return result;
+  }
+  
+  /**
+   * returns the X and Y matrix again as Instances object, based on the given
+   * header (must have a class attribute set).
+   * 
+   * @param header	the format of the instance object
+   * @param x		the X matrix (data)
+   * @param y		the Y matrix (class)
+   * @return		the assembled data
+   */
+  protected Instances toInstances(Instances header, Matrix x, Matrix y) {
+    double[]	values;
+    int		i;
+    int		n;
+    Instances	result;
+    int		rows;
+    int		cols;
+    int		offset;
+    int		clsIdx;
+    
+    result = new Instances(header, 0);
+    
+    rows   = x.getRowDimension();
+    cols   = x.getColumnDimension();
+    clsIdx = header.classIndex();
+    
+    for (i = 0; i < rows; i++) {
+      values = new double[cols + 1];
+      offset = 0;
+
+      for (n = 0; n < values.length; n++) {
+	if (n == clsIdx) {
+	  offset--;
+	  values[n] = y.get(i, 0);
+	}
+	else {
+	  values[n] = x.get(i, n + offset);
+	}
+      }
+      
+      result.add(new DenseInstance(1.0, values));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the given column as a vector (actually a n x 1 matrix)
+   * 
+   * @param m		the matrix to work on
+   * @param columnIndex	the column to return
+   * @return		the column as n x 1 matrix
+   */
+  protected Matrix columnAsVector(Matrix m, int columnIndex) {
+    Matrix	result;
+    int		i;
+    
+    result = new Matrix(m.getRowDimension(), 1);
+    
+    for (i = 0; i < m.getRowDimension(); i++)
+      result.set(i, 0, m.get(i, columnIndex));
+    
+    return result;
+  }
+  
+  /**
+   * stores the data from the (column) vector in the matrix at the specified 
+   * index
+   * 
+   * @param v		the vector to store in the matrix
+   * @param m		the receiving matrix
+   * @param columnIndex	the column to store the values in
+   */
+  protected void setVector(Matrix v, Matrix m, int columnIndex) {
+    m.setMatrix(0, m.getRowDimension() - 1, columnIndex, columnIndex, v);
+  }
+  
+  /**
+   * returns the (column) vector of the matrix at the specified index
+   * 
+   * @param m		the matrix to work on
+   * @param columnIndex	the column to get the values from
+   * @return		the column vector
+   */
+  protected Matrix getVector(Matrix m, int columnIndex) {
+    return m.getMatrix(0, m.getRowDimension() - 1, columnIndex, columnIndex);
+  }
+
+  /**
+   * determines the dominant eigenvector for the given matrix and returns it
+   * 
+   * @param m		the matrix to determine the dominant eigenvector for
+   * @return		the dominant eigenvector
+   */
+  protected Matrix getDominantEigenVector(Matrix m) {
+    EigenvalueDecomposition	eigendecomp;
+    double[]			eigenvalues;
+    int				index;
+    Matrix			result;
+    
+    eigendecomp = m.eig();
+    eigenvalues = eigendecomp.getRealEigenvalues();
+    index       = Utils.maxIndex(eigenvalues);
+    result	= columnAsVector(eigendecomp.getV(), index);
+    
+    return result;
+  }
+  
+  /**
+   * normalizes the given vector (inplace) 
+   * 
+   * @param v		the vector to normalize
+   */
+  protected void normalizeVector(Matrix v) {
+    double	sum;
+    int		i;
+    
+    // determine length
+    sum = 0;
+    for (i = 0; i < v.getRowDimension(); i++)
+      sum += v.get(i, 0) * v.get(i, 0);
+    sum = StrictMath.sqrt(sum);
+    
+    // normalize content
+    for (i = 0; i < v.getRowDimension(); i++)
+      v.set(i, 0, v.get(i, 0) / sum);
+  }
+
+  /**
+   * processes the instances using the PLS1 algorithm
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instances processPLS1(Instances instances) throws Exception {
+    Matrix	X, X_trans, x;
+    Matrix	y;
+    Matrix	W, w;
+    Matrix	T, t, t_trans;
+    Matrix	P, p, p_trans;
+    double	b;
+    Matrix	b_hat;
+    int		i;
+    int		j;
+    Matrix	X_new;
+    Matrix	tmp;
+    Instances	result;
+    Instances	tmpInst;
+
+    // initialization
+    if (!isFirstBatchDone()) {
+      // split up data
+      X       = getX(instances);
+      y       = getY(instances);
+      X_trans = X.transpose();
+      
+      // init
+      W     = new Matrix(instances.numAttributes() - 1, getNumComponents());
+      P     = new Matrix(instances.numAttributes() - 1, getNumComponents());
+      T     = new Matrix(instances.numInstances(), getNumComponents());
+      b_hat = new Matrix(getNumComponents(), 1);
+      
+      for (j = 0; j < getNumComponents(); j++) {
+	// 1. step: wj
+	w = X_trans.times(y);
+	normalizeVector(w);
+	setVector(w, W, j);
+	
+	// 2. step: tj
+	t       = X.times(w);
+	t_trans = t.transpose();
+	setVector(t, T, j);
+	
+	// 3. step: ^bj
+	b = t_trans.times(y).get(0, 0) / t_trans.times(t).get(0, 0);
+	b_hat.set(j, 0, b);
+	
+	// 4. step: pj
+	p       = X_trans.times(t).times((double) 1 / t_trans.times(t).get(0, 0));
+	p_trans = p.transpose();
+	setVector(p, P, j);
+	
+	// 5. step: Xj+1
+	X = X.minus(t.times(p_trans));
+	y = y.minus(t.times(b));
+      }
+      
+      // W*(P^T*W)^-1
+      tmp = W.times(((P.transpose()).times(W)).inverse());
+      
+      // X_new = X*W*(P^T*W)^-1
+      X_new = getX(instances).times(tmp);
+      
+      // factor = W*(P^T*W)^-1 * b_hat
+      m_PLS1_RegVector = tmp.times(b_hat);
+   
+      // save matrices
+      m_PLS1_P     = P;
+      m_PLS1_W     = W;
+      m_PLS1_b_hat = b_hat;
+      
+      if (getPerformPrediction())
+        result = toInstances(getOutputFormat(), X_new, y);
+      else
+        result = toInstances(getOutputFormat(), X_new, getY(instances));
+    }
+    // prediction
+    else {
+      result = new Instances(getOutputFormat());
+      
+      for (i = 0; i < instances.numInstances(); i++) {
+	// work on each instance
+	tmpInst = new Instances(instances, 0);
+	tmpInst.add((Instance) instances.instance(i).copy());
+	x = getX(tmpInst);
+	X = new Matrix(1, getNumComponents());
+	T = new Matrix(1, getNumComponents());
+	
+	for (j = 0; j < getNumComponents(); j++) {
+	  setVector(x, X, j);
+	  // 1. step: tj = xj * wj
+	  t = x.times(getVector(m_PLS1_W, j));
+	  setVector(t, T, j);
+	  // 2. step: xj+1 = xj - tj*pj^T (tj is 1x1 matrix!)
+	  x = x.minus(getVector(m_PLS1_P, j).transpose().times(t.get(0, 0)));
+	}
+	
+	if (getPerformPrediction())
+	  tmpInst = toInstances(getOutputFormat(), T, T.times(m_PLS1_b_hat));
+	else
+	  tmpInst = toInstances(getOutputFormat(), T, getY(tmpInst));
+	
+	result.add(tmpInst.instance(0));
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * processes the instances using the SIMPLS algorithm
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instances processSIMPLS(Instances instances) throws Exception {
+    Matrix	A, A_trans;
+    Matrix	M;
+    Matrix	X, X_trans;
+    Matrix	X_new;
+    Matrix	Y, y;
+    Matrix	C, c;
+    Matrix	Q, q;
+    Matrix	W, w;
+    Matrix	P, p, p_trans;
+    Matrix	v, v_trans;
+    Matrix	T;
+    Instances	result;
+    int		h;
+    
+    if (!isFirstBatchDone()) {
+      // init
+      X       = getX(instances);
+      X_trans = X.transpose();
+      Y       = getY(instances);
+      A       = X_trans.times(Y);
+      M       = X_trans.times(X);
+      C       = Matrix.identity(instances.numAttributes() - 1, instances.numAttributes() - 1);
+      W       = new Matrix(instances.numAttributes() - 1, getNumComponents());
+      P       = new Matrix(instances.numAttributes() - 1, getNumComponents());
+      Q       = new Matrix(1, getNumComponents());
+      
+      for (h = 0; h < getNumComponents(); h++) {
+	// 1. qh as dominant EigenVector of Ah'*Ah
+	A_trans = A.transpose();
+	q       = getDominantEigenVector(A_trans.times(A));
+	
+	// 2. wh=Ah*qh, ch=wh'*Mh*wh, wh=wh/sqrt(ch), store wh in W as column
+	w       = A.times(q);
+	c       = w.transpose().times(M).times(w);
+	w       = w.times(1.0 / StrictMath.sqrt(c.get(0, 0)));
+	setVector(w, W, h);
+	
+	// 3. ph=Mh*wh, store ph in P as column
+	p       = M.times(w);
+	p_trans = p.transpose();
+	setVector(p, P, h);
+	
+	// 4. qh=Ah'*wh, store qh in Q as column
+	q = A_trans.times(w);
+	setVector(q, Q, h);
+	
+	// 5. vh=Ch*ph, vh=vh/||vh||
+	v       = C.times(p);
+	normalizeVector(v);
+	v_trans = v.transpose();
+	
+	// 6. Ch+1=Ch-vh*vh', Mh+1=Mh-ph*ph'
+	C = C.minus(v.times(v_trans));
+	M = M.minus(p.times(p_trans));
+	
+	// 7. Ah+1=ChAh (actually Ch+1)
+	A = C.times(A);
+      }
+      
+      // finish
+      m_SIMPLS_W = W;
+      T          = X.times(m_SIMPLS_W);
+      X_new      = T;
+      m_SIMPLS_B = W.times(Q.transpose());
+      
+      if (getPerformPrediction())
+	y = T.times(P.transpose()).times(m_SIMPLS_B);
+      else
+	y = getY(instances);
+
+      result = toInstances(getOutputFormat(), X_new, y);
+    }
+    else {
+      result = new Instances(getOutputFormat());
+      
+      X     = getX(instances);
+      X_new = X.times(m_SIMPLS_W);
+      
+      if (getPerformPrediction())
+	y = X.times(m_SIMPLS_B);
+      else
+	y = getY(instances);
+      
+      result = toInstances(getOutputFormat(), X_new, y);
+    }
+    
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances	result;
+    int		i;
+    double	clsValue;
+    double[]	clsValues;
+    
+    result = null;
+
+    // save original class values if no prediction is performed
+    if (!getPerformPrediction())
+      clsValues = instances.attributeToDoubleArray(instances.classIndex());
+    else
+      clsValues = null;
+    
+    if (!isFirstBatchDone()) {
+      // init filters
+      if (m_ReplaceMissing)
+	m_Missing.setInputFormat(instances);
+      
+      switch (m_Preprocessing) {
+	case PREPROCESSING_CENTER:
+	  m_ClassMean   = instances.meanOrMode(instances.classIndex());
+	  m_ClassStdDev = 1;
+	  m_Filter      = new Center();
+	  ((Center) m_Filter).setIgnoreClass(true);
+      	  break;
+	case PREPROCESSING_STANDARDIZE:
+	  m_ClassMean   = instances.meanOrMode(instances.classIndex());
+	  m_ClassStdDev = StrictMath.sqrt(instances.variance(instances.classIndex()));
+	  m_Filter      = new Standardize();
+	  ((Standardize) m_Filter).setIgnoreClass(true);
+      	  break;
+	default:
+  	  m_ClassMean   = 0;
+	  m_ClassStdDev = 1;
+	  m_Filter      = null;
+      }
+      if (m_Filter != null)
+	m_Filter.setInputFormat(instances);
+    }
+    
+    // filter data
+    if (m_ReplaceMissing)
+      instances = Filter.useFilter(instances, m_Missing);
+    if (m_Filter != null)
+      instances = Filter.useFilter(instances, m_Filter);
+    
+    switch (m_Algorithm) {
+      case ALGORITHM_SIMPLS:
+	result = processSIMPLS(instances);
+	break;
+      case ALGORITHM_PLS1:
+	result = processPLS1(instances);
+	break;
+      default:
+	throw new IllegalStateException(
+	    "Algorithm type '" + m_Algorithm + "' is not recognized!");
+    }
+
+    // add the mean to the class again if predictions are to be performed,
+    // otherwise restore original class values
+    for (i = 0; i < result.numInstances(); i++) {
+      if (!getPerformPrediction()) {
+	result.instance(i).setClassValue(clsValues[i]);
+      }
+      else {
+	clsValue = result.instance(i).classValue();
+	result.instance(i).setClassValue(clsValue*m_ClassStdDev + m_ClassMean);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments.
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new PLSFilter(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/instance/Resample.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/instance/Resample.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/instance/Resample.java	(revision 29)
@@ -0,0 +1,682 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Resample.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Produces a random subsample of a dataset using either sampling with replacement or without replacement.<br/>
+ * The original dataset must fit entirely in memory. The number of instances in the generated dataset may be specified. The dataset must have a nominal class attribute. If not, use the unsupervised version. The filter can be made to maintain the class distribution in the subsample, or to bias the class distribution toward a uniform distribution. When used in batch mode (i.e. in the FilteredClassifier), subsequent batches are NOT resampled.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 1)</pre>
+ * 
+ * <pre> -Z &lt;num&gt;
+ *  The size of the output dataset, as a percentage of
+ *  the input dataset (default 100)</pre>
+ * 
+ * <pre> -B &lt;num&gt;
+ *  Bias factor towards uniform class distribution.
+ *  0 = distribution in input data -- 1 = uniform distribution.
+ *  (default 0)</pre>
+ * 
+ * <pre> -no-replacement
+ *  Disables replacement of instances
+ *  (default: with replacement)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the selection - only available with '-no-replacement'.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5492 $ 
+ */
+public class Resample
+  extends Filter 
+  implements SupervisedFilter, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = 7079064953548300681L;
+
+  /** The subsample size, percent of original set, default 100%. */
+  protected double m_SampleSizePercent = 100;
+  
+  /** The random number generator seed. */
+  protected int m_RandomSeed = 1;
+  
+  /** The degree of bias towards uniform (nominal) class distribution. */
+  protected double m_BiasToUniformClass = 0;
+
+  /** Whether to perform sampling with replacement or without. */
+  protected boolean m_NoReplacement = false;
+
+  /** Whether to invert the selection (only if instances are drawn WITHOUT 
+   * replacement).
+   * @see #m_NoReplacement */
+  protected boolean m_InvertSelection = false;
+
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Produces a random subsample of a dataset using either sampling "
+      + "with replacement or without replacement.\n"
+      + "The original dataset must "
+      + "fit entirely in memory. The number of instances in the generated "
+      + "dataset may be specified. The dataset must have a nominal class "
+      + "attribute. If not, use the unsupervised version. The filter can be "
+      + "made to maintain the class distribution in the subsample, or to bias "
+      + "the class distribution toward a uniform distribution. When used in batch "
+      + "mode (i.e. in the FilteredClassifier), subsequent batches are NOT resampled.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSpecify the random number seed (default 1)",
+	"S", 1, "-S <num>"));
+
+    result.addElement(new Option(
+	"\tThe size of the output dataset, as a percentage of\n"
+	+"\tthe input dataset (default 100)",
+	"Z", 1, "-Z <num>"));
+
+    result.addElement(new Option(
+	"\tBias factor towards uniform class distribution.\n"
+	+"\t0 = distribution in input data -- 1 = uniform distribution.\n"
+	+"\t(default 0)",
+	"B", 1, "-B <num>"));
+
+    result.addElement(new Option(
+	"\tDisables replacement of instances\n"
+	+"\t(default: with replacement)",
+	"no-replacement", 0, "-no-replacement"));
+
+    result.addElement(new Option(
+	"\tInverts the selection - only available with '-no-replacement'.",
+	"V", 0, "-V"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 1)</pre>
+   * 
+   * <pre> -Z &lt;num&gt;
+   *  The size of the output dataset, as a percentage of
+   *  the input dataset (default 100)</pre>
+   * 
+   * <pre> -B &lt;num&gt;
+   *  Bias factor towards uniform class distribution.
+   *  0 = distribution in input data -- 1 = uniform distribution.
+   *  (default 0)</pre>
+   * 
+   * <pre> -no-replacement
+   *  Disables replacement of instances
+   *  (default: with replacement)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the selection - only available with '-no-replacement'.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setRandomSeed(Integer.parseInt(tmpStr));
+    else
+      setRandomSeed(1);
+
+    tmpStr = Utils.getOption('B', options);
+    if (tmpStr.length() != 0)
+      setBiasToUniformClass(Double.parseDouble(tmpStr));
+    else
+      setBiasToUniformClass(0);
+
+    tmpStr = Utils.getOption('Z', options);
+    if (tmpStr.length() != 0)
+      setSampleSizePercent(Double.parseDouble(tmpStr));
+    else
+      setSampleSizePercent(100);
+
+    setNoReplacement(Utils.getFlag("no-replacement", options));
+
+    if (getNoReplacement())
+      setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+
+    result = new Vector<String>();
+
+    result.add("-B");
+    result.add("" + getBiasToUniformClass());
+
+    result.add("-S");
+    result.add("" + getRandomSeed());
+
+    result.add("-Z");
+    result.add("" + getSampleSizePercent());
+
+    if (getNoReplacement()) {
+      result.add("-no-replacement");
+      if (getInvertSelection())
+	result.add("-V");
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+    
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String biasToUniformClassTipText() {
+    return "Whether to use bias towards a uniform class. A value of 0 leaves the class "
+      + "distribution as-is, a value of 1 ensures the class distribution is "
+      + "uniform in the output data.";
+  }
+    
+  /**
+   * Gets the bias towards a uniform class. A value of 0 leaves the class
+   * distribution as-is, a value of 1 ensures the class distributions are
+   * uniform in the output data.
+   *
+   * @return the current bias
+   */
+  public double getBiasToUniformClass() {
+    return m_BiasToUniformClass;
+  }
+  
+  /**
+   * Sets the bias towards a uniform class. A value of 0 leaves the class
+   * distribution as-is, a value of 1 ensures the class distributions are
+   * uniform in the output data.
+   *
+   * @param newBiasToUniformClass the new bias value, between 0 and 1.
+   */
+  public void setBiasToUniformClass(double newBiasToUniformClass) {
+    m_BiasToUniformClass = newBiasToUniformClass;
+  }
+    
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Sets the random number seed for subsampling.";
+  }
+  
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getRandomSeed() {
+    return m_RandomSeed;
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setRandomSeed(int newSeed) {
+    m_RandomSeed = newSeed;
+  }
+    
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sampleSizePercentTipText() {
+    return "The subsample size as a percentage of the original set.";
+  }
+  
+  /**
+   * Gets the subsample size as a percentage of the original set.
+   *
+   * @return the subsample size
+   */
+  public double getSampleSizePercent() {
+    return m_SampleSizePercent;
+  }
+  
+  /**
+   * Sets the size of the subsample, as a percentage of the original set.
+   *
+   * @param newSampleSizePercent the subsample set size, between 0 and 100.
+   */
+  public void setSampleSizePercent(double newSampleSizePercent) {
+    m_SampleSizePercent = newSampleSizePercent;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String noReplacementTipText() {
+    return "Disables the replacement of instances.";
+  }
+
+  /**
+   * Gets whether instances are drawn with or without replacement.
+   * 
+   * @return true if the replacement is disabled
+   */
+  public boolean getNoReplacement() {
+    return m_NoReplacement;
+  }
+  
+  /**
+   * Sets whether instances are drawn with or with out replacement.
+   * 
+   * @param value if true then the replacement of instances is disabled
+   */
+  public void setNoReplacement(boolean value) {
+    m_NoReplacement = value;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Inverts the selection (only if instances are drawn WITHOUT replacement).";
+  }
+
+  /**
+   * Gets whether selection is inverted (only if instances are drawn WIHTOUT 
+   * replacement).
+   * 
+   * @return true if the replacement is disabled
+   * @see #m_NoReplacement
+   */
+  public boolean getInvertSelection() {
+    return m_InvertSelection;
+  }
+  
+  /**
+   * Sets whether the selection is inverted (only if instances are drawn WIHTOUT 
+   * replacement).
+   * 
+   * @param value if true then selection is inverted
+   */
+  public void setInvertSelection(boolean value) {
+    m_InvertSelection = value;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isFirstBatchDone()) {
+      // Do the subsample, and clear the input instances.
+      createSubsample();
+    }
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * creates the subsample with replacement.
+   * 
+   * @param random		the random number generator to use
+   * @param origSize		the original size of the dataset
+   * @param sampleSize		the size to generate
+   * @param actualClasses	the number of classes found in the data
+   * @param classIndices	the indices where classes start
+   */
+  public void createSubsampleWithReplacement(Random random, int origSize, 
+      int sampleSize, int actualClasses, int[] classIndices) {
+    
+    for (int i = 0; i < sampleSize; i++) {
+      int index = 0;
+      if (random.nextDouble() < m_BiasToUniformClass) {
+	// Pick a random class (of those classes that actually appear)
+	int cIndex = random.nextInt(actualClasses);
+	for (int j = 0, k = 0; j < classIndices.length - 1; j++) {
+	  if ((classIndices[j] != classIndices[j + 1]) && (k++ >= cIndex)) {
+	    // Pick a random instance of the designated class
+	    index =   classIndices[j] 
+	            + random.nextInt(classIndices[j + 1] - classIndices[j]);
+	    break;
+	  }
+	}
+      }
+      else {
+	index = random.nextInt(origSize);
+      }
+      push((Instance) getInputFormat().instance(index).copy());
+    }
+  }
+
+  /**
+   * creates the subsample without replacement.
+   * 
+   * @param random		the random number generator to use
+   * @param origSize		the original size of the dataset
+   * @param sampleSize		the size to generate
+   * @param actualClasses	the number of classes found in the data
+   * @param classIndices	the indices where classes start
+   */
+  public void createSubsampleWithoutReplacement(Random random, int origSize, 
+      int sampleSize, int actualClasses, int[] classIndices) {
+    
+    if (sampleSize > origSize) {
+      sampleSize = origSize;
+      System.err.println(
+	  "Resampling without replacement can only use percentage <=100% - "
+	  + "Using full dataset!");
+    }
+
+    Vector<Integer>[] indices = new Vector[classIndices.length - 1];
+    Vector<Integer>[] indicesNew = new Vector[classIndices.length - 1];
+
+    // generate list of all indices to draw from
+    for (int i = 0; i < classIndices.length - 1; i++) {
+      indices[i] = new Vector<Integer>(classIndices[i + 1] - classIndices[i]);
+      indicesNew[i] = new Vector<Integer>(indices[i].capacity());
+      for (int n = classIndices[i]; n < classIndices[i + 1]; n++)
+	indices[i].add(n);
+    }
+
+    // draw X samples
+    int currentSize = origSize;
+    for (int i = 0; i < sampleSize; i++) {
+      int index = 0;
+      if (random.nextDouble() < m_BiasToUniformClass) {
+	// Pick a random class (of those classes that actually appear)
+	int cIndex = random.nextInt(actualClasses);
+	for (int j = 0, k = 0; j < classIndices.length - 1; j++) {
+	  if ((classIndices[j] != classIndices[j + 1]) && (k++ >= cIndex)) {
+	    // no more indices for this class left, try again
+	    if (indices[j].size() == 0) {
+	      i--;
+	      break;
+	    }
+	    // Pick a random instance of the designated class
+	    index = random.nextInt(indices[j].size());
+	    indicesNew[j].add(indices[j].get(index));
+	    indices[j].remove(index);
+	    break;
+	  }
+	}
+      }
+      else {
+	index = random.nextInt(currentSize);
+	for (int n = 0; n < actualClasses; n++) {
+	  if (index < indices[n].size()) {
+	    indicesNew[n].add(indices[n].get(index));
+	    indices[n].remove(index);
+	    break;
+	  }
+	  else {
+	    index -= indices[n].size();
+	  }
+	}
+	currentSize--;
+      }
+    }
+
+    // sort indices
+    if (getInvertSelection()) {
+      indicesNew = indices;
+    }
+    else {
+      for (int i = 0; i < indicesNew.length; i++)
+	Collections.sort(indicesNew[i]);
+    }
+
+    // add to ouput
+    for (int i = 0; i < indicesNew.length; i++) {
+      for (int n = 0; n < indicesNew[i].size(); n++)
+	push((Instance) getInputFormat().instance(indicesNew[i].get(n)).copy());
+    }
+
+    // clean up
+    for (int i = 0; i < indices.length; i++) {
+      indices[i].clear();
+      indicesNew[i].clear();
+    }
+    indices = null;
+    indicesNew = null;
+  }
+
+  /**
+   * Creates a subsample of the current set of input instances. The output
+   * instances are pushed onto the output queue for collection.
+   */
+  protected void createSubsample() {
+    int origSize = getInputFormat().numInstances();
+    int sampleSize = (int) (origSize * m_SampleSizePercent / 100);
+
+    // Subsample that takes class distribution into consideration
+
+    // Sort according to class attribute.
+    getInputFormat().sort(getInputFormat().classIndex());
+    
+    // Create an index of where each class value starts
+    int[] classIndices = new int [getInputFormat().numClasses() + 1];
+    int currentClass = 0;
+    classIndices[currentClass] = 0;
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      Instance current = getInputFormat().instance(i);
+      if (current.classIsMissing()) {
+	for (int j = currentClass + 1; j < classIndices.length; j++) {
+	  classIndices[j] = i;
+	}
+	break;
+      } else if (current.classValue() != currentClass) {
+	for (int j = currentClass + 1; j <= current.classValue(); j++) {
+	  classIndices[j] = i;
+	}          
+	currentClass = (int) current.classValue();
+      }
+    }
+    if (currentClass <= getInputFormat().numClasses()) {
+      for (int j = currentClass + 1; j < classIndices.length; j++) {
+	classIndices[j] = getInputFormat().numInstances();
+      }
+    }
+    
+    int actualClasses = 0;
+    for (int i = 0; i < classIndices.length - 1; i++) {
+      if (classIndices[i] != classIndices[i + 1]) {
+	actualClasses++;
+      }
+    }
+
+    // Create the new sample
+    Random random = new Random(m_RandomSeed);
+
+    // Convert pending input instances
+    if (getNoReplacement())
+      createSubsampleWithoutReplacement(
+	  random, origSize, sampleSize, actualClasses, classIndices);
+    else
+      createSubsampleWithReplacement(
+	  random, origSize, sampleSize, actualClasses, classIndices);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5492 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Resample(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SMOTE.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SMOTE.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SMOTE.java	(revision 29)
@@ -0,0 +1,708 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SMOTE.java
+ * 
+ * Copyright (C) 2008 Ryan Lichtenwalter 
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Resamples a dataset by applying the Synthetic Minority Oversampling TEchnique (SMOTE). The original dataset must fit entirely in memory. The amount of SMOTE and number of nearest neighbors may be specified. For more information, see <br/>
+ * <br/>
+ * Nitesh V. Chawla et. al. (2002). Synthetic Minority Over-sampling Technique. Journal of Artificial Intelligence Research. 16:321-357.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{al.2002,
+ *    author = {Nitesh V. Chawla et. al.},
+ *    journal = {Journal of Artificial Intelligence Research},
+ *    pages = {321-357},
+ *    title = {Synthetic Minority Over-sampling Technique},
+ *    volume = {16},
+ *    year = {2002}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specifies the random number seed
+ *  (default 1)</pre>
+ * 
+ * <pre> -P &lt;percentage&gt;
+ *  Specifies percentage of SMOTE instances to create.
+ *  (default 100.0)
+ * </pre>
+ * 
+ * <pre> -K &lt;nearest-neighbors&gt;
+ *  Specifies the number of nearest neighbors to use.
+ *  (default 5)
+ * </pre>
+ * 
+ * <pre> -C &lt;value-index&gt;
+ *  Specifies the index of the nominal class value to SMOTE
+ *  (default 0: auto-detect non-empty minority class))
+ * </pre>
+ * 
+ <!-- options-end -->
+ *  
+ * @author Ryan Lichtenwalter (rlichtenwalter@gmail.com)
+ * @version $Revision: 5987 $
+ */
+public class SMOTE
+  extends Filter 
+  implements SupervisedFilter, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = -1653880819059250364L;
+
+  /** the number of neighbors to use. */
+  protected int m_NearestNeighbors = 5;
+  
+  /** the random seed to use. */
+  protected int m_RandomSeed = 1;
+  
+  /** the percentage of SMOTE instances to create. */
+  protected double m_Percentage = 100.0;
+  
+  /** the index of the class value. */
+  protected String m_ClassValueIndex = "0";
+  
+  /** whether to detect the minority class automatically. */
+  protected boolean m_DetectMinorityClass = true;
+
+  /**
+   * Returns a string describing this classifier.
+   * 
+   * @return 		a description of the classifier suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Resamples a dataset by applying the Synthetic Minority Oversampling TEchnique (SMOTE)." +
+    " The original dataset must fit entirely in memory." +
+    " The amount of SMOTE and number of nearest neighbors may be specified." +
+    " For more information, see \n\n" 
+    + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation result = new TechnicalInformation(Type.ARTICLE);
+
+    result.setValue(Field.AUTHOR, "Nitesh V. Chawla et. al.");
+    result.setValue(Field.TITLE, "Synthetic Minority Over-sampling Technique");
+    result.setValue(Field.JOURNAL, "Journal of Artificial Intelligence Research");
+    result.setValue(Field.YEAR, "2002");
+    result.setValue(Field.VOLUME, "16");
+    result.setValue(Field.PAGES, "321-357");
+
+    return result;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return 		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector();
+    
+    newVector.addElement(new Option(
+	"\tSpecifies the random number seed\n"
+	+ "\t(default 1)",
+	"S", 1, "-S <num>"));
+    
+    newVector.addElement(new Option(
+	"\tSpecifies percentage of SMOTE instances to create.\n"
+	+ "\t(default 100.0)\n",
+	"P", 1, "-P <percentage>"));
+    
+    newVector.addElement(new Option(
+	"\tSpecifies the number of nearest neighbors to use.\n"
+	+ "\t(default 5)\n",
+	"K", 1, "-K <nearest-neighbors>"));
+    
+    newVector.addElement(new Option(
+	"\tSpecifies the index of the nominal class value to SMOTE\n"
+	+"\t(default 0: auto-detect non-empty minority class))\n",
+	"C", 1, "-C <value-index>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options.
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specifies the random number seed
+   *  (default 1)</pre>
+   * 
+   * <pre> -P &lt;percentage&gt;
+   *  Specifies percentage of SMOTE instances to create.
+   *  (default 100.0)
+   * </pre>
+   * 
+   * <pre> -K &lt;nearest-neighbors&gt;
+   *  Specifies the number of nearest neighbors to use.
+   *  (default 5)
+   * </pre>
+   * 
+   * <pre> -C &lt;value-index&gt;
+   *  Specifies the index of the nominal class value to SMOTE
+   *  (default 0: auto-detect non-empty minority class))
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String seedStr = Utils.getOption('S', options);
+    if (seedStr.length() != 0) {
+      setRandomSeed(Integer.parseInt(seedStr));
+    } else {
+      setRandomSeed(1);
+    }
+
+    String percentageStr = Utils.getOption('P', options);
+    if (percentageStr.length() != 0) {
+      setPercentage(new Double(percentageStr).doubleValue());
+    } else {
+      setPercentage(100.0);
+    }
+
+    String nnStr = Utils.getOption('K', options);
+    if (nnStr.length() != 0) {
+      setNearestNeighbors(Integer.parseInt(nnStr));
+    } else {
+      setNearestNeighbors(5);
+    }
+
+    String classValueIndexStr = Utils.getOption( 'C', options);
+    if (classValueIndexStr.length() != 0) {
+      setClassValue(classValueIndexStr);
+    } else {
+      m_DetectMinorityClass = true;
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array 	of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    result.add("-C");
+    result.add(getClassValue());
+    
+    result.add("-K");
+    result.add("" + getNearestNeighbors());
+    
+    result.add("-P");
+    result.add("" + getPercentage());
+    
+    result.add("-S");
+    result.add("" + getRandomSeed());
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "The seed used for random sampling.";
+  }
+
+  /**
+   * Gets the random number seed.
+   *
+   * @return 		the random number seed.
+   */
+  public int getRandomSeed() {
+    return m_RandomSeed;
+  }
+
+  /**
+   * Sets the random number seed.
+   *
+   * @param value 	the new random number seed.
+   */
+  public void setRandomSeed(int value) {
+    m_RandomSeed = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String percentageTipText() {
+    return "The percentage of SMOTE instances to create.";
+  }
+
+  /**
+   * Sets the percentage of SMOTE instances to create.
+   * 
+   * @param value	the percentage to use
+   */
+  public void setPercentage(double value) {
+    if (value >= 0)
+      m_Percentage = value;
+    else
+      System.err.println("Percentage must be >= 0!");
+  }
+
+  /**
+   * Gets the percentage of SMOTE instances to create.
+   * 
+   * @return 		the percentage of SMOTE instances to create
+   */
+  public double getPercentage() {
+    return m_Percentage;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String nearestNeighborsTipText() {
+    return "The number of nearest neighbors to use.";
+  }
+
+  /**
+   * Sets the number of nearest neighbors to use.
+   * 
+   * @param value	the number of nearest neighbors to use
+   */
+  public void setNearestNeighbors(int value) {
+    if (value >= 1)
+      m_NearestNeighbors = value;
+    else
+      System.err.println("At least 1 neighbor necessary!");
+  }
+
+  /**
+   * Gets the number of nearest neighbors to use.
+   * 
+   * @return 		the number of nearest neighbors to use
+   */
+  public int getNearestNeighbors() {
+    return m_NearestNeighbors;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classValueTipText() {
+    return "The index of the class value to which SMOTE should be applied. " +
+    "Use a value of 0 to auto-detect the non-empty minority class.";
+  }
+
+  /**
+   * Sets the index of the class value to which SMOTE should be applied.
+   * 
+   * @param value	the class value index
+   */
+  public void setClassValue(String value) {
+    m_ClassValueIndex = value;
+    if (m_ClassValueIndex.equals("0")) {
+      m_DetectMinorityClass = true;
+    } else {
+      m_DetectMinorityClass = false;
+    }
+  }
+
+  /**
+   * Gets the index of the class value to which SMOTE should be applied.
+   * 
+   * @return 		the index of the clas value to which SMOTE should be applied
+   */
+  public String getClassValue() {
+    return m_ClassValueIndex;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained in 
+   * 				the object are ignored - only the structure is required).
+   * @return 			true if the outputFormat may be collected immediately
+   * @throws Exception 		if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+    super.setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance 		the input instance
+   * @return 			true if the filtered instance may now be
+   * 				collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_FirstBatchDone) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return 		true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   * @throws Exception 	if provided options cannot be executed 
+   * 			on input instances
+   */
+  public boolean batchFinished() throws Exception {
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!m_FirstBatchDone) {
+      // Do SMOTE, and clear the input instances.
+      doSMOTE();
+    }
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * The procedure implementing the SMOTE algorithm. The output
+   * instances are pushed onto the output queue for collection.
+   * 
+   * @throws Exception 	if provided options cannot be executed 
+   * 			on input instances
+   */
+  protected void doSMOTE() throws Exception {
+    int minIndex = 0;
+    int min = Integer.MAX_VALUE;
+    if (m_DetectMinorityClass) {
+      // find minority class
+      int[] classCounts = getInputFormat().attributeStats(getInputFormat().classIndex()).nominalCounts;
+      for (int i = 0; i < classCounts.length; i++) {
+	if (classCounts[i] != 0 && classCounts[i] < min) {
+	  min = classCounts[i];
+	  minIndex = i;
+	}
+      }
+    } else {
+      String classVal = getClassValue();
+      if (classVal.equalsIgnoreCase("first")) {
+	minIndex = 1;
+      } else if (classVal.equalsIgnoreCase("last")) {
+	minIndex = getInputFormat().numClasses();
+      } else {
+	minIndex = Integer.parseInt(classVal);
+      }
+      if (minIndex > getInputFormat().numClasses()) {
+	throw new Exception("value index must be <= the number of classes");
+      }
+      minIndex--; // make it an index
+    }
+
+    int nearestNeighbors;
+    if (min <= getNearestNeighbors()) {
+      nearestNeighbors = min - 1;
+    } else {
+      nearestNeighbors = getNearestNeighbors();
+    }
+    if (nearestNeighbors < 1)
+      throw new Exception("Cannot use 0 neighbors!");
+
+    // compose minority class dataset
+    // also push all dataset instances
+    Instances sample = getInputFormat().stringFreeStructure();
+    Enumeration instanceEnum = getInputFormat().enumerateInstances();
+    while(instanceEnum.hasMoreElements()) {
+      Instance instance = (Instance) instanceEnum.nextElement();
+      push((Instance) instance.copy());
+      if ((int) instance.classValue() == minIndex) {
+	sample.add(instance);
+      }
+    }
+
+    // compute Value Distance Metric matrices for nominal features
+    Map vdmMap = new HashMap();
+    Enumeration attrEnum = getInputFormat().enumerateAttributes();
+    while(attrEnum.hasMoreElements()) {
+      Attribute attr = (Attribute) attrEnum.nextElement();
+      if (!attr.equals(getInputFormat().classAttribute())) {
+	if (attr.isNominal() || attr.isString()) {
+	  double[][] vdm = new double[attr.numValues()][attr.numValues()];
+	  vdmMap.put(attr, vdm);
+	  int[] featureValueCounts = new int[attr.numValues()];
+	  int[][] featureValueCountsByClass = new int[getInputFormat().classAttribute().numValues()][attr.numValues()];
+	  instanceEnum = getInputFormat().enumerateInstances();
+	  while(instanceEnum.hasMoreElements()) {
+	    Instance instance = (Instance) instanceEnum.nextElement();
+	    int value = (int) instance.value(attr);
+	    int classValue = (int) instance.classValue();
+	    featureValueCounts[value]++;
+	    featureValueCountsByClass[classValue][value]++;
+	  }
+	  for (int valueIndex1 = 0; valueIndex1 < attr.numValues(); valueIndex1++) {
+	    for (int valueIndex2 = 0; valueIndex2 < attr.numValues(); valueIndex2++) {
+	      double sum = 0;
+	      for (int classValueIndex = 0; classValueIndex < getInputFormat().numClasses(); classValueIndex++) {
+		double c1i = (double) featureValueCountsByClass[classValueIndex][valueIndex1];
+		double c2i = (double) featureValueCountsByClass[classValueIndex][valueIndex2];
+		double c1 = (double) featureValueCounts[valueIndex1];
+		double c2 = (double) featureValueCounts[valueIndex2];
+		double term1 = c1i / c1;
+		double term2 = c2i / c2;
+		sum += Math.abs(term1 - term2);
+	      }
+	      vdm[valueIndex1][valueIndex2] = sum;
+	    }
+	  }
+	}
+      }
+    }
+
+    // use this random source for all required randomness
+    Random rand = new Random(getRandomSeed());
+
+    // find the set of extra indices to use if the percentage is not evenly divisible by 100
+    List extraIndices = new LinkedList();
+    double percentageRemainder = (getPercentage() / 100) - Math.floor(getPercentage() / 100.0);
+    int extraIndicesCount = (int) (percentageRemainder * sample.numInstances());
+    if (extraIndicesCount >= 1) {
+      for (int i = 0; i < sample.numInstances(); i++) {
+	extraIndices.add(i);
+      }
+    }
+    Collections.shuffle(extraIndices, rand);
+    extraIndices = extraIndices.subList(0, extraIndicesCount);
+    Set extraIndexSet = new HashSet(extraIndices);
+
+    // the main loop to handle computing nearest neighbors and generating SMOTE
+    // examples from each instance in the original minority class data
+    Instance[] nnArray = new Instance[nearestNeighbors];
+    for (int i = 0; i < sample.numInstances(); i++) {
+      Instance instanceI = sample.instance(i);
+      // find k nearest neighbors for each instance
+      List distanceToInstance = new LinkedList();
+      for (int j = 0; j < sample.numInstances(); j++) {
+	Instance instanceJ = sample.instance(j);
+	if (i != j) {
+	  double distance = 0;
+	  attrEnum = getInputFormat().enumerateAttributes();
+	  while(attrEnum.hasMoreElements()) {
+	    Attribute attr = (Attribute) attrEnum.nextElement();
+	    if (!attr.equals(getInputFormat().classAttribute())) {
+	      double iVal = instanceI.value(attr);
+	      double jVal = instanceJ.value(attr);
+	      if (attr.isNumeric()) {
+		distance += Math.pow(iVal - jVal, 2);
+	      } else {
+		distance += ((double[][]) vdmMap.get(attr))[(int) iVal][(int) jVal];
+	      }
+	    }
+	  }
+	  distance = Math.pow(distance, .5);
+	  distanceToInstance.add(new Object[] {distance, instanceJ});
+	}
+      }
+
+      // sort the neighbors according to distance
+      Collections.sort(distanceToInstance, new Comparator() {
+	public int compare(Object o1, Object o2) {
+	  double distance1 = (Double) ((Object[]) o1)[0];
+	  double distance2 = (Double) ((Object[]) o2)[0];
+	  return (int) Math.ceil(distance1 - distance2);
+	}
+      });
+
+      // populate the actual nearest neighbor instance array
+      Iterator entryIterator = distanceToInstance.iterator();
+      int j = 0;
+      while(entryIterator.hasNext() && j < nearestNeighbors) {
+	nnArray[j] = (Instance) ((Object[])entryIterator.next())[1];
+	j++;
+      }
+
+      // create synthetic examples
+      int n = (int) Math.floor(getPercentage() / 100);
+      while(n > 0 || extraIndexSet.remove(i)) {
+	double[] values = new double[sample.numAttributes()];
+	int nn = rand.nextInt(nearestNeighbors);
+	attrEnum = getInputFormat().enumerateAttributes();
+	while(attrEnum.hasMoreElements()) {
+	  Attribute attr = (Attribute) attrEnum.nextElement();
+	  if (!attr.equals(getInputFormat().classAttribute())) {
+	    if (attr.isNumeric()) {
+	      double dif = nnArray[nn].value(attr) - instanceI.value(attr);
+	      double gap = rand.nextDouble();
+	      values[attr.index()] = (double) (instanceI.value(attr) + gap * dif);
+	    } else if (attr.isDate()) {
+	      double dif = nnArray[nn].value(attr) - instanceI.value(attr);
+	      double gap = rand.nextDouble();
+	      values[attr.index()] = (long) (instanceI.value(attr) + gap * dif);
+	    } else {
+	      int[] valueCounts = new int[attr.numValues()];
+	      int iVal = (int) instanceI.value(attr);
+	      valueCounts[iVal]++;
+	      for (int nnEx = 0; nnEx < nearestNeighbors; nnEx++) {
+		int val = (int) nnArray[nnEx].value(attr);
+		valueCounts[val]++;
+	      }
+	      int maxIndex = 0;
+	      int max = Integer.MIN_VALUE;
+	      for (int index = 0; index < attr.numValues(); index++) {
+		if (valueCounts[index] > max) {
+		  max = valueCounts[index];
+		  maxIndex = index;
+		}
+	      }
+	      values[attr.index()] = maxIndex;
+	    }
+	  }
+	}
+	values[sample.classIndex()] = minIndex;
+	Instance synthetic = new DenseInstance(1.0, values);
+	push(synthetic);
+	n--;
+      }
+    }
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args 	should contain arguments to the filter: 
+   * 			use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new SMOTE(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SpreadSubsample.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SpreadSubsample.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/instance/SpreadSubsample.java	(revision 29)
@@ -0,0 +1,613 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SpreadSubsample.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.supervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.UnassignedClassException;
+import weka.core.UnsupportedClassTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Produces a random subsample of a dataset. The original dataset must fit entirely in memory. This filter allows you to specify the maximum "spread" between the rarest and most common class. For example, you may specify that there be at most a 2:1 difference in class frequencies. When used in batch mode, subsequent batches are NOT resampled.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 1)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  The maximum class distribution spread.
+ *  0 = no maximum spread, 1 = uniform distribution, 10 = allow at most
+ *  a 10:1 ratio between the classes (default 0)</pre>
+ * 
+ * <pre> -W
+ *  Adjust weights so that total weight per class is maintained.
+ *  Individual instance weighting is not preserved. (default no
+ *  weights adjustment</pre>
+ * 
+ * <pre> -X &lt;num&gt;
+ *  The maximum count for any class value (default 0 = unlimited).
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Stuart Inglis (stuart@reeltwo.com)
+ * @version $Revision: 5492 $ 
+ **/
+public class SpreadSubsample 
+  extends Filter 
+  implements SupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3947033795243930016L;
+
+  /** The random number generator seed */
+  private int m_RandomSeed = 1;
+
+  /** The maximum count of any class */
+  private int m_MaxCount;
+
+  /** True if the first batch has been done */
+  private double m_DistributionSpread = 0;
+
+  /**
+   * True if instance weights will be adjusted to maintain
+   * total weight per class.
+   */
+  private boolean m_AdjustWeights = false;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Produces a random subsample of a dataset. The original dataset must "
+      + "fit entirely in memory. This filter allows you to specify the maximum "
+      + "\"spread\" between the rarest and most common class. For example, you may "
+      + "specify that there be at most a 2:1 difference in class frequencies. "
+      + "When used in batch mode, subsequent batches are NOT resampled.";
+
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String adjustWeightsTipText() {
+    return "Wether instance weights will be adjusted to maintain total weight per "
+      + "class.";
+  }
+  
+  /**
+   * Returns true if instance  weights will be adjusted to maintain
+   * total weight per class.
+   *
+   * @return true if instance weights will be adjusted to maintain
+   * total weight per class.
+   */
+  public boolean getAdjustWeights() {
+
+    return m_AdjustWeights;
+  }
+  
+  /**
+   * Sets whether the instance weights will be adjusted to maintain
+   * total weight per class.
+   *
+   * @param newAdjustWeights whether to adjust weights
+   */
+  public void setAdjustWeights(boolean newAdjustWeights) {
+
+    m_AdjustWeights = newAdjustWeights;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+              "\tSpecify the random number seed (default 1)",
+              "S", 1, "-S <num>"));
+    newVector.addElement(new Option(
+              "\tThe maximum class distribution spread.\n"
+              +"\t0 = no maximum spread, 1 = uniform distribution, 10 = allow at most\n"
+	      +"\ta 10:1 ratio between the classes (default 0)",
+              "M", 1, "-M <num>"));
+    newVector.addElement(new Option(
+              "\tAdjust weights so that total weight per class is maintained.\n"
+              +"\tIndividual instance weighting is not preserved. (default no\n"
+              +"\tweights adjustment",
+              "W", 0, "-W"));
+    newVector.addElement(new Option(
+	      "\tThe maximum count for any class value (default 0 = unlimited).\n",
+              "X", 0, "-X <num>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 1)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  The maximum class distribution spread.
+   *  0 = no maximum spread, 1 = uniform distribution, 10 = allow at most
+   *  a 10:1 ratio between the classes (default 0)</pre>
+   * 
+   * <pre> -W
+   *  Adjust weights so that total weight per class is maintained.
+   *  Individual instance weighting is not preserved. (default no
+   *  weights adjustment</pre>
+   * 
+   * <pre> -X &lt;num&gt;
+   *  The maximum count for any class value (default 0 = unlimited).
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setRandomSeed(Integer.parseInt(seedString));
+    } else {
+      setRandomSeed(1);
+    }
+
+    String maxString = Utils.getOption('M', options);
+    if (maxString.length() != 0) {
+      setDistributionSpread(Double.valueOf(maxString).doubleValue());
+    } else {
+      setDistributionSpread(0);
+    }
+
+    String maxCount = Utils.getOption('X', options);
+    if (maxCount.length() != 0) {
+      setMaxCount(Double.valueOf(maxCount).doubleValue());
+    } else {
+      setMaxCount(0);
+    }
+
+    setAdjustWeights(Utils.getFlag('W', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [7];
+    int current = 0;
+
+    options[current++] = "-M"; 
+    options[current++] = "" + getDistributionSpread();
+
+    options[current++] = "-X"; 
+    options[current++] = "" + getMaxCount();
+
+    options[current++] = "-S"; 
+    options[current++] = "" + getRandomSeed();
+
+    if (getAdjustWeights()) {
+      options[current++] = "-W";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String distributionSpreadTipText() {
+    return "The maximum class distribution spread. "
+      + "(0 = no maximum spread, 1 = uniform distribution, 10 = allow at most a "
+      + "10:1 ratio between the classes).";
+  }
+  
+  /**
+   * Sets the value for the distribution spread
+   *
+   * @param spread the new distribution spread
+   */
+  public void setDistributionSpread(double spread) {
+
+    m_DistributionSpread = spread;
+  }
+
+  /**
+   * Gets the value for the distribution spread
+   *
+   * @return the distribution spread
+   */    
+  public double getDistributionSpread() {
+
+    return m_DistributionSpread;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxCountTipText() {
+    return "The maximum count for any class value (0 = unlimited).";
+  }
+  
+  /**
+   * Sets the value for the max count
+   *
+   * @param maxcount the new max count
+   */
+  public void setMaxCount(double maxcount) {
+
+    m_MaxCount = (int)maxcount;
+  }
+
+  /**
+   * Gets the value for the max count
+   *
+   * @return the max count
+   */    
+  public double getMaxCount() {
+
+    return m_MaxCount;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Sets the random number seed for subsampling.";
+  }
+  
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getRandomSeed() {
+
+    return m_RandomSeed;
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setRandomSeed(int newSeed) {
+
+    m_RandomSeed = newSeed;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnassignedClassException if no class attribute has been set.
+   * @throws UnsupportedClassTypeException if the class attribute
+   * is not nominal. 
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined 
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isFirstBatchDone()) {
+      // Do the subsample, and clear the input instances.
+      createSubsample();
+    }
+
+    flushInput();
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+
+  /**
+   * Creates a subsample of the current set of input instances. The output
+   * instances are pushed onto the output queue for collection.
+   */
+  private void createSubsample() {
+
+    int classI = getInputFormat().classIndex();
+    // Sort according to class attribute.
+    getInputFormat().sort(classI);
+    // Determine where each class starts in the sorted dataset
+    int [] classIndices = getClassIndices();
+
+    // Get the existing class distribution
+    int [] counts = new int [getInputFormat().numClasses()];
+    double [] weights = new double [getInputFormat().numClasses()];
+    int min = -1;
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      Instance current = getInputFormat().instance(i);
+      if (current.classIsMissing() == false) {
+        counts[(int)current.classValue()]++;
+        weights[(int)current.classValue()]+= current.weight();
+      }
+    }
+
+    // Convert from total weight to average weight
+    for (int i = 0; i < counts.length; i++) {
+      if (counts[i] > 0) {
+        weights[i] = weights[i] / counts[i];
+      }
+      /*
+      System.err.println("Class:" + i + " " + getInputFormat().classAttribute().value(i)
+                         + " Count:" + counts[i]
+                         + " Total:" + weights[i] * counts[i]
+                         + " Avg:" + weights[i]);
+      */
+    }
+    
+    // find the class with the minimum number of instances
+    int minIndex = -1;
+    for (int i = 0; i < counts.length; i++) {
+      if ( (min < 0) && (counts[i] > 0) ) {
+        min = counts[i];
+        minIndex = i;
+      } else if ((counts[i] < min) && (counts[i] > 0)) {
+        min = counts[i];
+        minIndex = i;
+      }
+    }
+
+    if (min < 0) { 
+	System.err.println("SpreadSubsample: *warning* none of the classes have any values in them.");
+	return;
+    }
+
+    // determine the new distribution 
+    int [] new_counts = new int [getInputFormat().numClasses()];
+    for (int i = 0; i < counts.length; i++) {
+      new_counts[i] = (int)Math.abs(Math.min(counts[i],
+                                             min * m_DistributionSpread));
+      if (i == minIndex) {
+        if (m_DistributionSpread > 0 && m_DistributionSpread < 1.0) {
+          // don't undersample the minority class!
+          new_counts[i] = counts[i];
+        }
+      }
+      if (m_DistributionSpread == 0) {
+        new_counts[i] = counts[i];
+      }
+
+      if (m_MaxCount > 0) {
+        new_counts[i] = Math.min(new_counts[i], m_MaxCount);
+      }
+    }
+
+    // Sample without replacement
+    Random random = new Random(m_RandomSeed);
+    Hashtable t = new Hashtable();
+    for (int j = 0; j < new_counts.length; j++) {
+      double newWeight = 1.0;
+      if (m_AdjustWeights && (new_counts[j] > 0)) {
+        newWeight = weights[j] * counts[j] / new_counts[j];
+        /*
+        System.err.println("Class:" + j + " " + getInputFormat().classAttribute().value(j) 
+                           + " Count:" + counts[j]
+                           + " Total:" + weights[j] * counts[j]
+                           + " Avg:" + weights[j]
+                           + " NewCount:" + new_counts[j]
+                           + " NewAvg:" + newWeight);
+        */
+      }
+      for (int k = 0; k < new_counts[j]; k++) {
+        boolean ok = false;
+        do {
+	  int index = classIndices[j] + (Math.abs(random.nextInt()) 
+                                         % (classIndices[j + 1] - classIndices[j])) ;
+	  // Have we used this instance before?
+          if (t.get("" + index) == null) {
+            // if not, add it to the hashtable and use it
+            t.put("" + index, "");
+            ok = true;
+	    if(index >= 0) {
+              Instance newInst = (Instance)getInputFormat().instance(index).copy();
+              if (m_AdjustWeights) {
+                newInst.setWeight(newWeight);
+              }
+              push(newInst);
+            }
+          }
+        } while (!ok);
+      }
+    }
+  }
+
+  /**
+   * Creates an index containing the position where each class starts in 
+   * the getInputFormat(). m_InputFormat must be sorted on the class attribute.
+   * 
+   * @return the positions
+   */
+  private int[] getClassIndices() {
+
+    // Create an index of where each class value starts
+    int [] classIndices = new int [getInputFormat().numClasses() + 1];
+    int currentClass = 0;
+    classIndices[currentClass] = 0;
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      Instance current = getInputFormat().instance(i);
+      if (current.classIsMissing()) {
+        for (int j = currentClass + 1; j < classIndices.length; j++) {
+          classIndices[j] = i;
+        }
+        break;
+      } else if (current.classValue() != currentClass) {
+        for (int j = currentClass + 1; j <= current.classValue(); j++) {
+          classIndices[j] = i;
+        }          
+        currentClass = (int) current.classValue();
+      }
+    }
+    if (currentClass <= getInputFormat().numClasses()) {
+      for (int j = currentClass + 1; j < classIndices.length; j++) {
+        classIndices[j] = getInputFormat().numInstances();
+      }
+    }
+    return classIndices;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5492 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new SpreadSubsample(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/supervised/instance/StratifiedRemoveFolds.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/supervised/instance/StratifiedRemoveFolds.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/supervised/instance/StratifiedRemoveFolds.java	(revision 29)
@@ -0,0 +1,473 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StratifiedRemoveFolds.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.supervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This filter takes a dataset and outputs a specified fold for cross validation. If you do not want the folds to be stratified use the unsupervised version.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -V
+ *  Specifies if inverse of selection is to be output.
+ * </pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Specifies number of folds dataset is split into. 
+ *  (default 10)
+ * </pre>
+ * 
+ * <pre> -F &lt;fold&gt;
+ *  Specifies which fold is selected. (default 1)
+ * </pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Specifies random number seed. (default 0, no randomizing)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5492 $ 
+ */
+public class StratifiedRemoveFolds 
+  extends Filter 
+  implements SupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7069148179905814324L;
+
+  /** Indicates if inverse of selection is to be output. */
+  private boolean m_Inverse = false;
+
+  /** Number of folds to split dataset into */
+  private int m_NumFolds = 10;
+
+  /** Fold to output */
+  private int m_Fold = 1;
+
+  /** Random number seed. */
+  private long m_Seed = 0;
+
+  /**
+   * Gets an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	      "\tSpecifies if inverse of selection is to be output.\n",
+	      "V", 0, "-V"));
+
+    newVector.addElement(new Option(
+              "\tSpecifies number of folds dataset is split into. \n"
+	      + "\t(default 10)\n",
+              "N", 1, "-N <number of folds>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies which fold is selected. (default 1)\n",
+	      "F", 1, "-F <fold>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies random number seed. (default 0, no randomizing)\n",
+	      "S", 1, "-S <seed>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -V
+   *  Specifies if inverse of selection is to be output.
+   * </pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Specifies number of folds dataset is split into. 
+   *  (default 10)
+   * </pre>
+   * 
+   * <pre> -F &lt;fold&gt;
+   *  Specifies which fold is selected. (default 1)
+   * </pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Specifies random number seed. (default 0, no randomizing)
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setInvertSelection(Utils.getFlag('V', options));
+    String numFolds = Utils.getOption('N', options);
+    if (numFolds.length() != 0) {
+      setNumFolds(Integer.parseInt(numFolds));
+    } else {
+      setNumFolds(10);
+    }
+    String fold = Utils.getOption('F', options);
+    if (fold.length() != 0) {
+      setFold(Integer.parseInt(fold));
+    } else {
+      setFold(1);
+    }
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(0);
+    }
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [8];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = "" + getSeed();
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    options[current++] = "-N"; options[current++] = "" + getNumFolds();
+    options[current++] = "-F"; options[current++] = "" + getFold();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "This filter takes a dataset and outputs a specified fold for "
+      + "cross validation. If you do not want the folds to be stratified "
+      + "use the unsupervised version.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Whether to invert the selection.";
+  }
+
+  /**
+   * Gets if selection is to be inverted.
+   *
+   * @return true if the selection is to be inverted
+   */
+  public boolean getInvertSelection() {
+
+    return m_Inverse;
+  }
+
+  /**
+   * Sets if selection is to be inverted.
+   *
+   * @param inverse true if inversion is to be performed
+   */
+  public void setInvertSelection(boolean inverse) {
+    
+    m_Inverse = inverse;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+
+    return "The number of folds to split the dataset into.";
+  }
+
+  /**
+   * Gets the number of folds in which dataset is to be split into.
+   * 
+   * @return the number of folds the dataset is to be split into.
+   */
+  public int getNumFolds() {
+
+    return m_NumFolds;
+  }
+
+  /**
+   * Sets the number of folds the dataset is split into. If the number
+   * of folds is zero, it won't split it into folds. 
+   *
+   * @param numFolds number of folds dataset is to be split into
+   * @throws IllegalArgumentException if number of folds is negative
+   */
+  public void setNumFolds(int numFolds) {
+
+    if (numFolds < 0) {
+      throw new IllegalArgumentException("Number of folds has to be positive or zero.");
+    }
+    m_NumFolds = numFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldTipText() {
+
+    return "The fold which is selected.";
+  }
+
+  /**
+   * Gets the fold which is selected.
+   *
+   * @return the fold which is selected
+   */
+  public int getFold() {
+
+    return m_Fold;
+  }
+
+  /**
+   * Selects a fold.
+   *
+   * @param fold the fold to be selected.
+   * @throws IllegalArgumentException if fold's index is smaller than 1
+   */
+  public void setFold(int fold) {
+
+    if (fold < 1) {
+      throw new IllegalArgumentException("Fold's index has to be greater than 0.");
+    }
+    m_Fold = fold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+
+    return "the random number seed for shuffling the dataset. If seed is negative, shuffling will not be performed.";
+  }
+
+  /**
+   * Gets the random number seed used for shuffling the dataset.
+   *
+   * @return the random number seed
+   */
+  public long getSeed() {
+
+    return m_Seed;
+  }
+
+  /**
+   * Sets the random number seed for shuffling the dataset. If seed
+   * is negative, shuffling won't be performed.
+   *
+   * @param seed the random number seed
+   */
+  public void setSeed(long seed) {
+    
+    m_Seed = seed;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true because outputFormat can be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */  
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    if ((m_NumFolds > 0) && (m_NumFolds < m_Fold)) {
+      throw new IllegalArgumentException("Fold has to be smaller or equal to "+
+                                         "number of folds.");
+    }
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is
+   * finished. Output() may now be called to retrieve the filtered
+   * instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    
+    Instances instances;
+
+    if (!isFirstBatchDone()) {
+      if (m_Seed > 0) {
+	// User has provided a random number seed.
+	getInputFormat().randomize(new Random(m_Seed));
+      }
+
+      // Select out a fold
+      getInputFormat().stratify(m_NumFolds);
+      if (!m_Inverse) {
+	instances = getInputFormat().testCV(m_NumFolds, m_Fold - 1);
+      } else {
+	instances = getInputFormat().trainCV(m_NumFolds, m_Fold - 1);
+      }
+    }
+    else {
+      instances = getInputFormat();
+    }
+    
+    flushInput();
+
+    for (int i = 0; i < instances.numInstances(); i++) {
+      push(instances.instance(i));
+    }
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5492 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new StratifiedRemoveFolds(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AbstractTimeSeries.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AbstractTimeSeries.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AbstractTimeSeries.java	(revision 29)
@@ -0,0 +1,463 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTimeSeries.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Queue;
+import weka.core.Range;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ * An abstract instance filter that assumes instances form time-series data and
+ * performs some merging of attribute values in the current instance with 
+ * attribute attribute values of some previous (or future) instance. For
+ * instances where the desired value is unknown either the instance may
+ * be dropped, or missing values used.<p>
+ *
+ * Valid filter-specific options are:<p>
+ *
+ * -R index1,index2-index4,...<br>
+ * Specify list of columns to calculate new values for.
+ * First and last are valid indexes.
+ * (default none)<p>
+ *
+ * -V <br>
+ * Invert matching sense (i.e. calculate for all non-specified columns)<p>
+ *
+ * -I num <br>
+ * The number of instances forward to merge values between.
+ * A negative number indicates taking values from a past instance.
+ * (default -1) <p>
+ *
+ * -M <br>
+ * For instances at the beginning or end of the dataset where the translated
+ * values are not known, remove those instances (default is to use missing 
+ * values). <p>
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public abstract class AbstractTimeSeries
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3795656792078022357L;
+
+  /** Stores which columns to copy */
+  protected Range m_SelectedCols = new Range();
+
+  /**
+   * True if missing values should be used rather than removing instances
+   * where the translated value is not known (due to border effects).
+   */
+  protected boolean m_FillWithMissing = true;
+
+  /**
+   * The number of instances forward to translate values between.
+   * A negative number indicates taking values from a past instance.
+   */
+  protected int m_InstanceRange = -1;
+
+  /** Stores the historical instances to copy values between */
+  protected Queue m_History;
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to translate in time. First and\n"
+	      + "\tlast are valid indexes. (default none)",
+              "R", 1, "-R <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tInvert matching sense (i.e. calculate for all non-specified columns)",
+              "V", 0, "-V"));
+    newVector.addElement(new Option(
+              "\tThe number of instances forward to translate values\n"
+	      + "\tbetween. A negative number indicates taking values from\n"
+	      + "\ta past instance. (default -1)",
+              "I", 1, "-I <num>"));
+    newVector.addElement(new Option(
+	      "\tFor instances at the beginning or end of the dataset where\n"
+	      + "\tthe translated values are not known, remove those instances\n"
+	      + "\t(default is to use missing values).",
+	      "M", 0, "-M"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options controlling the behaviour of this object.
+   * Valid options are:<p>
+   *
+   * -R index1,index2-index4,...<br>
+   * Specify list of columns to copy. First and last are valid indexes.
+   * (default none)<p>
+   *
+   * -V<br>
+   * Invert matching sense (i.e. calculate for all non-specified columns)<p>
+   *
+   * -I num <br>
+   * The number of instances forward to translate values between.
+   * A negative number indicates taking values from a past instance.
+   * (default -1) <p>
+   *
+   * -M <br>
+   * For instances at the beginning or end of the dataset where the translated
+   * values are not known, remove those instances (default is to use missing 
+   * values). <p>
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String copyList = Utils.getOption('R', options);
+    if (copyList.length() != 0) {
+      setAttributeIndices(copyList);
+    } else {
+      setAttributeIndices("");
+    }
+    
+    setInvertSelection(Utils.getFlag('V', options));
+
+    setFillWithMissing(!Utils.getFlag('M', options));
+    
+    String instanceRange = Utils.getOption('I', options);
+    if (instanceRange.length() != 0) {
+      setInstanceRange(Integer.parseInt(instanceRange));
+    } else {
+      setInstanceRange(-1);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [6];
+    int current = 0;
+
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    options[current++] = "-I"; options[current++] = "" + getInstanceRange();
+    if (!getFillWithMissing()) {
+      options[current++] = "-M";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the format couldn't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    resetHistory();
+    m_SelectedCols.setUpper(instanceInfo.numAttributes() - 1);
+    return false;
+  }
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws Exception if the input instance was not of the correct 
+   * format or if there was a problem with the filtering.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new NullPointerException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+      resetHistory();
+    }
+
+    Instance newInstance = historyInput(instance);
+    if (newInstance != null) {
+      push(newInstance);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Signifies that this batch of input to the filter is finished. If the 
+   * filter requires all instances prior to filtering, output() may now 
+   * be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (getFillWithMissing() && (m_InstanceRange > 0)) {
+      while (!m_History.empty()) {
+	push(mergeInstances(null, (Instance) m_History.pop()));
+      }
+    } 
+    flushInput();
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String fillWithMissingTipText() {
+    return "For instances at the beginning or end of the dataset where the translated "
+      + "values are not known, use missing values (default is to remove those "
+      + "instances)";
+  }
+
+  /**
+   * Gets whether missing values should be used rather than removing instances
+   * where the translated value is not known (due to border effects).
+   *
+   * @return true if so
+   */
+  public boolean getFillWithMissing() {
+    
+    return m_FillWithMissing;
+  }
+  
+  /**
+   * Sets whether missing values should be used rather than removing instances
+   * where the translated value is not known (due to border effects).
+   *
+   * @param newFillWithMissing true if so
+   */
+  public void setFillWithMissing(boolean newFillWithMissing) {
+    
+    m_FillWithMissing = newFillWithMissing;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String instanceRangeTipText() {
+    return "The number of instances forward/backward to merge values between. "
+      + "A negative number indicates taking values from a past instance.";
+  }
+
+  /**
+   * Gets the number of instances forward to translate values between.
+   * A negative number indicates taking values from a past instance.
+   *
+   * @return Value of InstanceRange.
+   */
+  public int getInstanceRange() {
+    
+    return m_InstanceRange;
+  }
+  
+  /**
+   * Sets the number of instances forward to translate values between.
+   * A negative number indicates taking values from a past instance.
+   *
+   * @param newInstanceRange Value to assign to InstanceRange.
+   */
+  public void setInstanceRange(int newInstanceRange) {
+    
+    m_InstanceRange = newInstanceRange;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Invert matching sense. ie calculate for all non-specified columns.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_SelectedCols.getInvert();
+  }
+
+  /**
+   * Set whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are copied. If false
+   * selected columns are copied and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_SelectedCols.setInvert(invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_SelectedCols.getRanges();
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param rangeList a string representing the list of attributes.  Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   */
+  public void setAttributeIndices(String rangeList) {
+
+    m_SelectedCols.setRanges(rangeList);
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   */
+  public void setAttributeIndicesArray(int [] attributes) {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+
+  /** Clears any instances from the history queue. */
+  protected void resetHistory() {
+
+    if (m_History == null) {
+      m_History = new Queue();
+    } else {
+      m_History.removeAllElements();
+    }
+  }
+
+  /**
+   * Adds an instance to the history buffer. If enough instances are in
+   * the buffer, a new instance may be output, with selected attribute
+   * values copied from one to another.
+   *
+   * @param instance the input instance
+   * @return a new instance with translated values, or null if no
+   * output instance is produced
+   */
+  protected Instance historyInput(Instance instance) {
+
+    m_History.push(instance);
+    if (m_History.size() <= Math.abs(m_InstanceRange)) {
+      if (getFillWithMissing() && (m_InstanceRange < 0)) {
+	return mergeInstances(null, instance);
+      } else {
+	return null;
+      }
+    }
+    if (m_InstanceRange < 0) {
+      return mergeInstances((Instance) m_History.pop(), instance);
+    } else {
+      return mergeInstances(instance, (Instance) m_History.pop());
+    }
+  }
+
+  /**
+   * Creates a new instance the same as one instance (the "destination")
+   * but with some attribute values copied from another instance
+   * (the "source")
+   *
+   * @param source the source instance
+   * @param dest the destination instance
+   * @return the new merged instance
+   */
+  protected abstract Instance mergeInstances(Instance source, Instance dest);
+  
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Add.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Add.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Add.java	(revision 29)
@@ -0,0 +1,596 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Add.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SingleIndex;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that adds a new attribute to the dataset. The new attribute will contain all missing values.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -T &lt;NUM|NOM|STR|DAT&gt;
+ *  The type of attribute to create:
+ *  NUM = Numeric attribute
+ *  NOM = Nominal attribute
+ *  STR = String attribute
+ *  DAT = Date attribute
+ *  (default: NUM)</pre>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  Specify where to insert the column. First and last
+ *  are valid indexes.(default: last)</pre>
+ * 
+ * <pre> -N &lt;name&gt;
+ *  Name of the new attribute.
+ *  (default: 'Unnamed')</pre>
+ * 
+ * <pre> -L &lt;label1,label2,...&gt;
+ *  Create nominal attribute with given labels
+ *  (default: numeric attribute)</pre>
+ * 
+ * <pre> -F &lt;format&gt;
+ *  The format of the date values (see ISO-8601)
+ *  (default: yyyy-MM-dd'T'HH:mm:ss)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Add 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = 761386447332932389L;
+
+  /** the attribute type. */
+  public static final Tag[] TAGS_TYPE = {
+    new Tag(Attribute.NUMERIC, "NUM", "Numeric attribute"),
+    new Tag(Attribute.NOMINAL, "NOM", "Nominal attribute"),
+    new Tag(Attribute.STRING,  "STR", "String attribute"),
+    new Tag(Attribute.DATE,    "DAT", "Date attribute")
+  };
+  
+  /** Record the type of attribute to insert. */
+  protected int m_AttributeType = Attribute.NUMERIC;
+
+  /** The name for the new attribute. */
+  protected String m_Name = "unnamed";
+
+  /** The location to insert the new attribute. */
+  private SingleIndex m_Insert = new SingleIndex("last"); 
+
+  /** The list of labels for nominal attribute. */
+  protected FastVector m_Labels = new FastVector();
+
+  /** The date format. */
+  protected String m_DateFormat = "yyyy-MM-dd'T'HH:mm:ss";
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "An instance filter that adds a new attribute to the dataset."
+      + " The new attribute will contain all missing values.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector 		newVector;
+    String		desc;
+    SelectedTag		tag;
+    int			i;
+
+    newVector = new Vector();
+
+    desc  = "";
+    for (i = 0; i < TAGS_TYPE.length; i++) {
+      tag = new SelectedTag(TAGS_TYPE[i].getID(), TAGS_TYPE);
+      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
+      	       + " = " + tag.getSelectedTag().getReadable()
+      	       + "\n";
+    }
+    newVector.addElement(new Option(
+	"\tThe type of attribute to create:\n"
+	+ desc
+	+"\t(default: " + new SelectedTag(Attribute.NUMERIC, TAGS_TYPE) + ")",
+	"T", 1, "-T " + Tag.toOptionList(TAGS_TYPE)));
+
+    newVector.addElement(new Option(
+	"\tSpecify where to insert the column. First and last\n"
+	+"\tare valid indexes.(default: last)",
+	"C", 1, "-C <index>"));
+
+    newVector.addElement(new Option(
+	"\tName of the new attribute.\n"
+	+"\t(default: 'Unnamed')",
+	"N", 1,"-N <name>"));
+    
+    newVector.addElement(new Option(
+	"\tCreate nominal attribute with given labels\n"
+	+"\t(default: numeric attribute)",
+	"L", 1, "-L <label1,label2,...>"));
+
+    newVector.addElement(new Option(
+	"\tThe format of the date values (see ISO-8601)\n"
+	+"\t(default: yyyy-MM-dd'T'HH:mm:ss)",
+	"F", 1, "-F <format>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -T &lt;NUM|NOM|STR|DAT&gt;
+   *  The type of attribute to create:
+   *  NUM = Numeric attribute
+   *  NOM = Nominal attribute
+   *  STR = String attribute
+   *  DAT = Date attribute
+   *  (default: NUM)</pre>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  Specify where to insert the column. First and last
+   *  are valid indexes.(default: last)</pre>
+   * 
+   * <pre> -N &lt;name&gt;
+   *  Name of the new attribute.
+   *  (default: 'Unnamed')</pre>
+   * 
+   * <pre> -L &lt;label1,label2,...&gt;
+   *  Create nominal attribute with given labels
+   *  (default: numeric attribute)</pre>
+   * 
+   * <pre> -F &lt;format&gt;
+   *  The format of the date values (see ISO-8601)
+   *  (default: yyyy-MM-dd'T'HH:mm:ss)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('T', options);
+    if (tmpStr.length() != 0)
+      setAttributeType(new SelectedTag(tmpStr, TAGS_TYPE));
+    else
+      setAttributeType(new SelectedTag(Attribute.NUMERIC, TAGS_TYPE));
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() == 0)
+      tmpStr = "last";
+    setAttributeIndex(tmpStr);
+    
+    setAttributeName(Utils.unbackQuoteChars(Utils.getOption('N', options)));
+    
+    if (m_AttributeType == Attribute.NOMINAL) {
+      tmpStr = Utils.getOption('L', options);
+      if (tmpStr.length() != 0)
+	setNominalLabels(tmpStr);
+    }
+    else if (m_AttributeType == Attribute.DATE) {
+      tmpStr = Utils.getOption('F', options);
+      if (tmpStr.length() != 0)
+	setDateFormat(tmpStr);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    if (m_AttributeType != Attribute.NUMERIC) {
+      result.add("-T");
+      result.add("" + getAttributeType());
+    }
+    
+    result.add("-N");
+    result.add(Utils.backQuoteChars(getAttributeName()));
+    
+    if (m_AttributeType == Attribute.NOMINAL) {
+      result.add("-L");
+      result.add(getNominalLabels());
+    }
+    else if (m_AttributeType == Attribute.NOMINAL) {
+      result.add("-F");
+      result.add(getDateFormat());
+    }
+    
+    result.add("-C");
+    result.add("" + getAttributeIndex());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the format couldn't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+
+    m_Insert.setUpper(instanceInfo.numAttributes());
+    Instances outputFormat = new Instances(instanceInfo, 0);
+    Attribute newAttribute = null;
+    switch (m_AttributeType) {
+      case Attribute.NUMERIC:
+	newAttribute = new Attribute(m_Name);
+	break;
+      case Attribute.NOMINAL:
+	newAttribute = new Attribute(m_Name, m_Labels);
+	break;
+      case Attribute.STRING:
+	newAttribute = new Attribute(m_Name, (FastVector) null);
+	break;
+      case Attribute.DATE:
+	newAttribute = new Attribute(m_Name, m_DateFormat);
+	break;
+      default:
+	throw new IllegalArgumentException("Unknown attribute type in Add");
+    }
+
+    if ((m_Insert.getIndex() < 0) || 
+	(m_Insert.getIndex() > getInputFormat().numAttributes())) {
+      throw new IllegalArgumentException("Index out of range");
+    }
+    outputFormat.insertAttributeAt(newAttribute, m_Insert.getIndex());
+    setOutputFormat(outputFormat);
+    
+    // all attributes, except index of added attribute
+    // (otherwise the length of the input/output indices differ)
+    Range atts = new Range(m_Insert.getSingleIndex());
+    atts.setInvert(true);
+    atts.setUpper(outputFormat.numAttributes() - 1);
+    initOutputLocators(outputFormat, atts.getSelection());
+    
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    Instance inst = (Instance)instance.copy();
+
+    // First copy string values from input to output
+    copyValues(inst, true, inst.dataset(), getOutputFormat());
+    
+    // Insert the new attribute and reassign to output
+    inst.setDataset(null);
+    inst.insertAttributeAt(m_Insert.getIndex());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+    return true;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeNameTipText() {
+
+    return "Set the new attribute's name.";
+  }
+
+  /**
+   * Get the name of the attribute to be created.
+   *
+   * @return the new attribute name
+   */
+  public String getAttributeName() {
+
+    return m_Name;
+  }
+
+  /** 
+   * Set the new attribute's name.
+   *
+   * @param name the new name
+   */
+  public void setAttributeName(String name) {
+    if (name.trim().equals(""))
+      m_Name = "unnamed";
+    else
+      m_Name = name;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "The position (starting from 1) where the attribute will be inserted "
+      + "(first and last are valid indices).";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_Insert.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_Insert.setSingleIndex(attIndex);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String nominalLabelsTipText() {
+    return "The list of value labels (nominal attribute creation only). "
+      + " The list must be comma-separated, eg: \"red,green,blue\"."
+      + " If this is empty, the created attribute will be numeric.";
+  }
+
+  /**
+   * Get the list of labels for nominal attribute creation.
+   *
+   * @return the list of labels for nominal attribute creation
+   */
+  public String getNominalLabels() {
+
+    String labelList = "";
+    for(int i = 0; i < m_Labels.size(); i++) {
+      if (i == 0) {
+	labelList = (String)m_Labels.elementAt(i);
+      } else {
+	labelList += "," + (String)m_Labels.elementAt(i); 
+      }
+    }
+    return labelList;
+  }
+
+  /**
+   * Set the labels for nominal attribute creation.
+   *
+   * @param labelList a comma separated list of labels
+   * @throws IllegalArgumentException if the labelList was invalid
+   */
+  public void setNominalLabels(String labelList) {
+
+    FastVector labels = new FastVector (10);
+
+    // Split the labelList up into the vector
+    int commaLoc;
+    while ((commaLoc = labelList.indexOf(',')) >= 0) {
+      String label = labelList.substring(0, commaLoc).trim();
+      if (!label.equals("")) {
+	labels.addElement(label);
+      } else {
+	throw new IllegalArgumentException("Invalid label list at "+
+                                           labelList.substring(commaLoc));
+      }
+      labelList = labelList.substring(commaLoc + 1);
+    }
+    String label = labelList.trim();
+    if (!label.equals("")) {
+      labels.addElement(label);
+    }
+
+    // If everything is OK, make the type change
+    m_Labels = labels;
+    if (labels.size() == 0) {
+      m_AttributeType = Attribute.NUMERIC;
+    } else {
+      m_AttributeType = Attribute.NOMINAL; 
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeTypeTipText() {
+    return "Defines the type of the attribute to generate.";
+  }
+
+  /**
+   * Sets the type of attribute to generate. 
+   *
+   * @param value 	the attribute type
+   */
+  public void setAttributeType(SelectedTag value) {
+    if (value.getTags() == TAGS_TYPE) {
+      m_AttributeType = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of attribute to generate. 
+   *
+   * @return 		the current attribute type.
+   */
+  public SelectedTag getAttributeType() {
+    return new SelectedTag(m_AttributeType, TAGS_TYPE);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String dateFormatTipText() {
+    return "The format of the date values (see ISO-8601).";
+  }
+
+  /**
+   * Get the date format, complying to ISO-8601.
+   *
+   * @return 		the date format
+   */
+  public String getDateFormat() {
+    return m_DateFormat;
+  }
+
+  /**
+   * Set the date format, complying to ISO-8601.
+   *
+   * @param value 	a comma separated list of labels
+   */
+  public void setDateFormat(String value) {
+    try {
+      new SimpleDateFormat(value);
+      m_DateFormat = value;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Add(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddCluster.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddCluster.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddCluster.java	(revision 29)
@@ -0,0 +1,603 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AddCluster.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.clusterers.AbstractClusterer;
+import weka.clusterers.Clusterer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.ObjectInputStream;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter that adds a new nominal attribute representing the cluster assigned to each instance by the specified clustering algorithm.<br/>
+ * Either the clustering algorithm gets built with the first batch of data or one specifies are serialized clusterer model file to use instead.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;clusterer specification&gt;
+ *  Full class name of clusterer to use, followed
+ *  by scheme options. eg:
+ *   "weka.clusterers.SimpleKMeans -N 3"
+ *  (default: weka.clusterers.SimpleKMeans)</pre>
+ * 
+ * <pre> -serialized &lt;file&gt;
+ *  Instead of building a clusterer on the data, one can also provide
+ *  a serialized model and use that for adding the clusters.</pre>
+ * 
+ * <pre> -I &lt;att1,att2-att4,...&gt;
+ *  The range of attributes the clusterer should ignore.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class AddCluster 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = 7414280611943807337L;
+
+  /** The clusterer used to do the cleansing. */
+  protected Clusterer m_Clusterer = new weka.clusterers.SimpleKMeans();
+
+  /** The file from which to load a serialized clusterer. */
+  protected File m_SerializedClustererFile = new File(System.getProperty("user.dir"));
+  
+  /** The actual clusterer used to do the clustering. */
+  protected Clusterer m_ActualClusterer = null;
+
+  /** Range of attributes to ignore. */
+  protected Range m_IgnoreAttributesRange = null;
+
+  /** Filter for removing attributes. */
+  protected Filter m_removeAttributes = new Remove();
+
+  /** 
+   * Returns the Capabilities of this filter, makes sure that the class is
+   * never set (for the clusterer).
+   *
+   * @param data	the data to use for customization
+   * @return            the capabilities of this object, based on the data
+   * @see               #getCapabilities()
+   */
+  public Capabilities getCapabilities(Instances data) {
+    Instances	newData;
+    
+    newData = new Instances(data, 0);
+    newData.setClassIndex(-1);
+    
+    return super.getCapabilities(newData);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = m_Clusterer.getCapabilities();
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+  
+  /**
+   * tests the data whether the filter can actually handle it.
+   * 
+   * @param instanceInfo	the data to test
+   * @throws Exception		if the test fails
+   */
+  protected void testInputFormat(Instances instanceInfo) throws Exception {
+    getCapabilities(instanceInfo).testWithFail(removeIgnored(instanceInfo));
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */ 
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+
+    m_removeAttributes = null;
+
+    return false;
+  }
+
+  /**
+   * filters all attributes that should be ignored.
+   * 
+   * @param data	the data to filter
+   * @return		the filtered data
+   * @throws Exception	if filtering fails
+   */
+  protected Instances removeIgnored(Instances data) throws Exception {
+    Instances result = data;
+    
+    if (m_IgnoreAttributesRange != null || data.classIndex() >= 0) {
+      m_removeAttributes = new Remove();
+      String rangeString = "";
+      if (m_IgnoreAttributesRange != null) {
+	rangeString += m_IgnoreAttributesRange.getRanges();
+      }
+      if (data.classIndex() >= 0) {
+	if (rangeString.length() > 0) {
+	  rangeString += "," + (data.classIndex() + 1);
+	} else {
+	  rangeString = "" + (data.classIndex() + 1);
+	}
+      }
+      ((Remove) m_removeAttributes).setAttributeIndices(rangeString);
+      ((Remove) m_removeAttributes).setInvertSelection(false);
+      m_removeAttributes.setInputFormat(data);
+      result = Filter.useFilter(data, m_removeAttributes);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */  
+  public boolean batchFinished() throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    Instances toFilter = getInputFormat();
+    
+    if (!isFirstBatchDone()) {
+      // filter out attributes if necessary
+      Instances toFilterIgnoringAttributes = removeIgnored(toFilter);
+
+      // serialized model or build clusterer from scratch?
+      File file = getSerializedClustererFile();
+      if (!file.isDirectory()) {
+	ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
+	m_ActualClusterer = (Clusterer) ois.readObject();
+	Instances header = null;
+	// let's see whether there's an Instances header stored as well
+	try {
+	  header = (Instances) ois.readObject();
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+	ois.close();
+	// same dataset format?
+	if ((header != null) && (!header.equalHeaders(toFilterIgnoringAttributes)))
+	  throw new WekaException(
+	      "Training header of clusterer and filter dataset don't match:\n"
+	      + header.equalHeadersMsg(toFilterIgnoringAttributes));
+      }
+      else {
+	m_ActualClusterer = AbstractClusterer.makeCopy(m_Clusterer);
+	m_ActualClusterer.buildClusterer(toFilterIgnoringAttributes);
+      }
+
+      // create output dataset with new attribute
+      Instances filtered = new Instances(toFilter, 0); 
+      FastVector nominal_values = new FastVector(m_ActualClusterer.numberOfClusters());
+      for (int i = 0; i < m_ActualClusterer.numberOfClusters(); i++) {
+	nominal_values.addElement("cluster" + (i+1)); 
+      }
+      filtered.insertAttributeAt(new Attribute("cluster", nominal_values),
+	  filtered.numAttributes());
+
+      setOutputFormat(filtered);
+    }
+
+    // build new dataset
+    for (int i=0; i<toFilter.numInstances(); i++) {
+      convertInstance(toFilter.instance(i));
+    }
+    
+    flushInput();
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (outputFormatPeek() != null) {
+      convertInstance(instance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is added to 
+   * the end of the output queue.
+   *
+   * @param instance the instance to convert
+   * @throws Exception if something goes wrong
+   */
+  protected void convertInstance(Instance instance) throws Exception {
+    Instance original, processed;
+    original = instance;
+
+    // copy values
+    double[] instanceVals = new double[instance.numAttributes()+1];
+    for(int j = 0; j < instance.numAttributes(); j++) {
+      instanceVals[j] = original.value(j);
+    }
+    Instance filteredI = null;
+    if (m_removeAttributes != null) {
+      m_removeAttributes.input(instance);
+      filteredI = m_removeAttributes.output();
+    } else {
+      filteredI = instance;
+    }
+
+    // add cluster to end
+    try {
+      instanceVals[instance.numAttributes()] = m_ActualClusterer.clusterInstance(filteredI);
+    }
+    catch (Exception e) {
+      // clusterer couldn't cluster instance -> missing
+      instanceVals[instance.numAttributes()] = Utils.missingValue();
+    }
+
+    // create new instance
+    if (original instanceof SparseInstance) {
+      processed = new SparseInstance(original.weight(), instanceVals);
+    } else {
+      processed = new DenseInstance(original.weight(), instanceVals);
+    }
+
+    processed.setDataset(instance.dataset());
+    copyValues(processed, false, instance.dataset(), getOutputFormat());
+    processed.setDataset(getOutputFormat());
+      
+    push(processed);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tFull class name of clusterer to use, followed\n"
+	+ "\tby scheme options. eg:\n"
+	+ "\t\t\"weka.clusterers.SimpleKMeans -N 3\"\n"
+	+ "\t(default: weka.clusterers.SimpleKMeans)",
+	"W", 1, "-W <clusterer specification>"));
+
+    result.addElement(new Option(
+	"\tInstead of building a clusterer on the data, one can also provide\n"
+	+ "\ta serialized model and use that for adding the clusters.",
+	"serialized", 1, "-serialized <file>"));
+    
+    result.addElement(new Option(
+	"\tThe range of attributes the clusterer should ignore.\n",
+	"I", 1,"-I <att1,att2-att4,...>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;clusterer specification&gt;
+   *  Full class name of clusterer to use, followed
+   *  by scheme options. eg:
+   *   "weka.clusterers.SimpleKMeans -N 3"
+   *  (default: weka.clusterers.SimpleKMeans)</pre>
+   * 
+   * <pre> -serialized &lt;file&gt;
+   *  Instead of building a clusterer on the data, one can also provide
+   *  a serialized model and use that for adding the clusters.</pre>
+   * 
+   * <pre> -I &lt;att1,att2-att4,...&gt;
+   *  The range of attributes the clusterer should ignore.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[] 	tmpOptions;
+    File	file;
+    boolean 	serializedModel;
+    
+    serializedModel = false;
+    tmpStr = Utils.getOption("serialized", options);
+    if (tmpStr.length() != 0) {
+      file = new File(tmpStr);
+      if (!file.exists())
+	throw new FileNotFoundException(
+	    "File '" + file.getAbsolutePath() + "' not found!");
+      if (file.isDirectory())
+	throw new FileNotFoundException(
+	    "'" + file.getAbsolutePath() + "' points to a directory not a file!");
+      setSerializedClustererFile(file);
+      serializedModel = true;
+    }
+    else {
+      setSerializedClustererFile(null);
+    }
+
+    if (!serializedModel) {
+      tmpStr = Utils.getOption('W', options);
+      if (tmpStr.length() == 0)
+	tmpStr = weka.clusterers.SimpleKMeans.class.getName();
+      tmpOptions = Utils.splitOptions(tmpStr);
+      if (tmpOptions.length == 0) {
+	throw new Exception("Invalid clusterer specification string");
+      }
+      tmpStr = tmpOptions[0];
+      tmpOptions[0] = "";
+      setClusterer(AbstractClusterer.forName(tmpStr, tmpOptions));
+    }
+        
+    setIgnoredAttributeIndices(Utils.getOption('I', options));
+
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    File		file;
+
+    result = new Vector<String>();
+
+    file = getSerializedClustererFile();
+    if ((file != null) && (!file.isDirectory())) {
+      result.add("-serialized");
+      result.add(file.getAbsolutePath());
+    }
+    else {
+      result.add("-W");
+      result.add(getClustererSpec());
+    }
+    
+    if (!getIgnoredAttributeIndices().equals("")) {
+      result.add("-I");
+      result.add(getIgnoredAttributeIndices());
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter that adds a new nominal attribute representing the cluster "
+      + "assigned to each instance by the specified clustering algorithm.\n"
+      + "Either the clustering algorithm gets built with the first batch of "
+      + "data or one specifies are serialized clusterer model file to use "
+      + "instead.";
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String clustererTipText() {
+    return "The clusterer to assign clusters with.";
+  }
+
+  /**
+   * Sets the clusterer to assign clusters with.
+   *
+   * @param clusterer The clusterer to be used (with its options set).
+   */
+  public void setClusterer(Clusterer clusterer) {
+    m_Clusterer = clusterer;
+  }
+  
+  /**
+   * Gets the clusterer used by the filter.
+   *
+   * @return The clusterer being used.
+   */
+  public Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+
+  /**
+   * Gets the clusterer specification string, which contains the class name of
+   * the clusterer and any options to the clusterer.
+   *
+   * @return the clusterer string.
+   */
+  protected String getClustererSpec() {
+    Clusterer c = getClusterer();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ignoredAttributeIndicesTipText() {
+    return "The range of attributes to be ignored by the clusterer. eg: first-3,5,9-last";
+  }
+
+  /**
+   * Gets ranges of attributes to be ignored.
+   *
+   * @return a string containing a comma-separated list of ranges
+   */
+  public String getIgnoredAttributeIndices() {
+    if (m_IgnoreAttributesRange == null)
+      return "";
+    else
+      return m_IgnoreAttributesRange.getRanges();
+  }
+
+  /**
+   * Sets the ranges of attributes to be ignored. If provided string
+   * is null, no attributes will be ignored.
+   *
+   * @param rangeList a string representing the list of attributes. 
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setIgnoredAttributeIndices(String rangeList) {
+    if ((rangeList == null) || (rangeList.length() == 0)) {
+      m_IgnoreAttributesRange = null;
+    } else {
+      m_IgnoreAttributesRange = new Range();
+      m_IgnoreAttributesRange.setRanges(rangeList);
+    }
+  }
+
+  /**
+   * Gets the file pointing to a serialized, built clusterer. If it is
+   * null or pointing to a directory it will not be used.
+   * 
+   * @return		the file the serialized, built clusterer is located in
+   */
+  public File getSerializedClustererFile() {
+    return m_SerializedClustererFile;
+  }
+
+  /**
+   * Sets the file pointing to a serialized, built clusterer. If the
+   * argument is null, doesn't exist or pointing to a directory, then the 
+   * value is ignored.
+   * 
+   * @param value	the file pointing to the serialized, built clusterer
+   */
+  public void setSerializedClustererFile(File value) {
+    if ((value == null) || (!value.exists()))
+      value = new File(System.getProperty("user.dir"));
+
+    m_SerializedClustererFile = value;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String serializedClustererFileTipText() {
+    return "A file containing the serialized model of a built clusterer.";
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String[] argv) {
+    runFilter(new AddCluster(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddExpression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddExpression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddExpression.java	(revision 29)
@@ -0,0 +1,402 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AddExpression.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.AttributeExpression;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * An instance filter that creates a new attribute by applying a mathematical expression to existing attributes. The expression can contain attribute references and numeric constants. Supported operators are :<br/>
+ * +, -, *, /, ^, log, abs, cos, exp, sqrt, floor, ceil, rint, tan, sin, (, )<br/>
+ * Attributes are specified by prefixing with 'a', eg. a7 is attribute number 7 (starting from 1).<br/>
+ * Example expression : a1^2*a5/log(a7*4.0).
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;expression&gt;
+ *  Specify the expression to apply. Eg a1^2*a5/log(a7*4.0).
+ *  Supported opperators: ,+, -, *, /, ^, log, abs, cos, 
+ *  exp, sqrt, floor, ceil, rint, tan, sin, (, )
+ *  (default: a1^2)</pre>
+ * 
+ * <pre> -N &lt;name&gt;
+ *  Specify the name for the new attribute. (default is the expression provided with -E)</pre>
+ * 
+ * <pre> -D
+ *  Debug. Names attribute with the postfix parse of the expression.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class AddExpression 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 402130384261736245L;
+  
+  /** The infix expression */
+  private String m_infixExpression = "a1^2";
+
+  /** Name of the new attribute. "expression"  length string will use the 
+      provided expression as the new attribute name */
+  private String m_attributeName="expression";
+
+  /** If true, makes the attribute name equal to the postfix parse of the
+      expression */
+  private boolean m_Debug = false;
+
+  private AttributeExpression m_attributeExpression = null;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "An instance filter that creates a new attribute by applying a "
+      + "mathematical expression to existing attributes. The expression "
+      + "can contain attribute references and numeric constants. Supported "
+      + "operators are :\n"
+      + "+, -, *, /, ^, log, abs, cos, exp, sqrt, floor, ceil, rint, tan, "
+      + "sin, (, )\n"
+      + "Attributes are specified by prefixing with 'a', eg. a7 is "
+      + "attribute number 7 (starting from 1).\n"
+      + "Example expression : a1^2*a5/log(a7*4.0).";
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3); 
+
+    newVector.addElement(new Option(
+	     "\tSpecify the expression to apply. Eg a1^2*a5/log(a7*4.0)."
+	     +"\n\tSupported opperators: ,+, -, *, /, ^, log, abs, cos, "
+	     +"\n\texp, sqrt, floor, ceil, rint, tan, sin, (, )"
+	     +"\n\t(default: a1^2)",
+	     "E",1,"-E <expression>"));
+
+    newVector.addElement(new Option(
+	     "\tSpecify the name for the new attribute. (default is the "
+	     +"expression provided with -E)",
+	     "N",1,"-N <name>"));
+
+    newVector.addElement(new Option(
+	     "\tDebug. Names attribute with the postfix parse of the "
+	     +"expression.","D",0,"-D"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -E &lt;expression&gt;
+   *  Specify the expression to apply. Eg a1^2*a5/log(a7*4.0).
+   *  Supported opperators: ,+, -, *, /, ^, log, abs, cos, 
+   *  exp, sqrt, floor, ceil, rint, tan, sin, (, )
+   *  (default: a1^2)</pre>
+   * 
+   * <pre> -N &lt;name&gt;
+   *  Specify the name for the new attribute. (default is the expression provided with -E)</pre>
+   * 
+   * <pre> -D
+   *  Debug. Names attribute with the postfix parse of the expression.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String expString = Utils.getOption('E', options);
+    if (expString.length() != 0) {
+      setExpression(expString);
+    } else {
+      setExpression("a1^2");
+    }
+
+    String name = Utils.getOption('N',options);
+    if (name.length() != 0) {
+      setName(name);
+    }
+
+    setDebug(Utils.getFlag('D', options));
+  }
+  
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [5];
+    int current = 0;
+    
+    options[current++] = "-E"; options[current++] = getExpression();
+    options[current++] = "-N"; options[current++] = getName();
+
+    if (getDebug()) {
+      options[current++] = "-D";
+    }
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String nameTipText() {
+    return "Set the name of the new attribute.";
+  }
+
+  /**
+   * Set the name for the new attribute. The string "expression" can
+   * be used to make the name of the new attribute equal to the expression
+   * provided.
+   * @param name the name of the new attribute
+   */
+  public void setName(String name) {
+    m_attributeName = name;
+  }
+
+  /**
+   * Returns the name of the new attribute
+   * @return the name of the new attribute
+   */
+  public String getName() {
+    return m_attributeName;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String debugTipText() {
+    return "Set debug mode. If true then the new attribute will be named with "
+      +"the postfix parse of the supplied expression.";
+  }
+  
+  /**
+   * Set debug mode. Causes the new attribute to be named with the postfix
+   * parse of the expression
+   * @param d true if debug mode is to be used
+   */
+  public void setDebug(boolean d) {
+    m_Debug = d;
+  }
+
+  /**
+   * Gets whether debug is set
+   * @return true if debug is set
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String expressionTipText() {
+    return "Set the math expression to apply. Eg. a1^2*a5/log(a7*4.0)";
+  }
+
+  /**
+   * Set the expression to apply
+   * @param expr a mathematical expression to apply
+   */
+  public void setExpression(String expr) {
+    m_infixExpression = expr;
+  }
+
+  /**
+   * Get the expression
+   * @return the expression
+   */
+  public String getExpression() {
+    return m_infixExpression;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the format couldn't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    m_attributeExpression = new AttributeExpression();
+    m_attributeExpression.
+      convertInfixToPostfix(new String(m_infixExpression));
+
+    super.setInputFormat(instanceInfo);
+
+    Instances outputFormat = new Instances(instanceInfo, 0);
+    Attribute newAttribute;
+    if (m_Debug) {
+      newAttribute = 
+        new Attribute(m_attributeExpression.getPostFixExpression());
+    } else if (m_attributeName.compareTo("expression") != 0) {
+      newAttribute = new Attribute(m_attributeName);
+    } else {
+      newAttribute = new Attribute(m_infixExpression);
+    }
+    outputFormat.insertAttributeAt(newAttribute, 
+				   instanceInfo.numAttributes());
+    setOutputFormat(outputFormat);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   * @throws Exception if there was a problem during the filtering.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    double[] vals = new double[instance.numAttributes()+1];
+    for(int i = 0; i < instance.numAttributes(); i++) {
+      if (instance.isMissing(i)) {
+	vals[i] = Utils.missingValue();
+      } else {
+	vals[i] = instance.value(i);
+      }
+    }
+
+    m_attributeExpression.evaluateExpression(vals);
+
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] args) {
+    runFilter(new AddExpression(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddID.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddID.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddID.java	(revision 29)
@@ -0,0 +1,388 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AddID.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * An instance filter that adds an ID attribute to the dataset. The new attribute contains a unique ID for each instance.<br/>
+ * Note: The ID is not reset for the second batch of files (using -b and -r and -s).
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;index&gt;
+ *  Specify where to insert the ID. First and last
+ *  are valid indexes.(default first)</pre>
+ * 
+ * <pre> -N &lt;name&gt;
+ *  Name of the new attribute.
+ *  (default = 'ID')</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class AddID
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4734383199819293390L;
+
+  /** the index of the attribute */
+  protected SingleIndex m_Index = new SingleIndex("first");
+
+  /** the name of the attribute */
+  protected String m_Name = "ID";
+  
+  /** the counter for the ID */
+  protected int m_Counter = -1;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return            a description of the filter suitable for
+   *                    displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "An instance filter that adds an ID attribute to the dataset. "
+      + "The new attribute contains a unique ID for each instance.\n"
+      + "Note: The ID is not reset for the second batch of files (using -b "
+      + "and -r and -s).";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+              "\tSpecify where to insert the ID. First and last\n"
+              +"\tare valid indexes.(default first)",
+              "C", 1, "-C <index>"));
+
+    result.addElement(new Option(
+              "\tName of the new attribute.\n"
+              +"\t(default = 'ID')",
+              "N", 1,"-N <name>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;index&gt;
+   *  Specify where to insert the ID. First and last
+   *  are valid indexes.(default first)</pre>
+   * 
+   * <pre> -N &lt;name&gt;
+   *  Name of the new attribute.
+   *  (default = 'ID')</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      m_Index.setSingleIndex(tmpStr);
+    else
+      m_Index.setSingleIndex("first");
+    
+    tmpStr = Utils.getOption('N', options);
+    if (tmpStr.length() != 0)
+      m_Name = tmpStr;
+    else
+      m_Name = "ID";
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector      result;
+    
+    result = new Vector();
+
+    result.add("-C");
+    result.add(getIDIndex());
+
+    result.add("-N");
+    result.add(getAttributeName());
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeNameTipText() {
+    return "Set the new attribute's name.";
+  }
+
+  /**
+   * Get the name of the attribute to be created
+   *
+   * @return the current attribute name
+   */
+  public String getAttributeName() {
+    return m_Name;
+  }
+
+  /** 
+   * Set the new attribute's name
+   *
+   * @param value the new name
+   */
+  public void setAttributeName(String value) {
+    m_Name = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String IDIndexTipText() {
+    return 
+        "The position (starting from 1) where the attribute will be inserted "
+      + "(first and last are valid indices).";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getIDIndex() {
+    return m_Index.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param value the index of the attribute
+   */
+  public void setIDIndex(String value) {
+    m_Index.setSingleIndex(value);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the format couldn't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    Instances           outputFormat;
+    Attribute           newAttribute;
+
+    super.setInputFormat(instanceInfo);
+
+    m_Counter = -1;
+    m_Index.setUpper(instanceInfo.numAttributes());
+    outputFormat = new Instances(instanceInfo, 0);
+    newAttribute = new Attribute(m_Name);
+
+    if ((m_Index.getIndex() < 0) || 
+        (m_Index.getIndex() > getInputFormat().numAttributes()))
+      throw new IllegalArgumentException("Index out of range");
+    
+    outputFormat.insertAttributeAt(newAttribute, m_Index.getIndex());
+    setOutputFormat(outputFormat);
+    
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (!isFirstBatchDone()) {
+      bufferInput(instance);
+      return false;
+    } 
+    else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (!isFirstBatchDone()) {
+      m_Counter = 0;
+      
+      // Convert pending input instances
+      for (int i = 0; i < getInputFormat().numInstances(); i++)
+        convertInstance(getInputFormat().instance(i));
+    } 
+    
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  protected void convertInstance(Instance instance) {
+    Instance            inst;
+    
+    m_Counter++;
+
+    // build instance
+    try {
+      inst = (Instance)instance.copy();
+
+      // First copy string values from input to output
+      copyValues(inst, true, inst.dataset(), getOutputFormat());
+
+      // Insert the new attribute and reassign to output
+      inst.setDataset(null);
+      inst.insertAttributeAt(m_Index.getIndex());
+      inst.setValue(m_Index.getIndex(), m_Counter);
+      inst.setDataset(getOutputFormat());
+
+      push(inst);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments to the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new AddID(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddNoise.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddNoise.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddNoise.java	(revision 29)
@@ -0,0 +1,645 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AddNoise.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that changes a percentage of a given attributes values. The attribute must be nominal. Missing value can be treated as value itself.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Index of the attribute to be changed 
+ *  (default last attribute)</pre>
+ * 
+ * <pre> -M
+ *  Treat missing values as an extra value 
+ * </pre>
+ * 
+ * <pre> -P &lt;num&gt;
+ *  Specify the percentage of noise introduced 
+ *  to the data (default 10)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ */
+public class AddNoise 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8499673222857299082L;
+
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** Flag if missing values are taken as value. */
+  private boolean m_UseMissing = false;
+
+  /** The subsample size, percent of original set, default 10% */
+  private int m_Percent = 10;
+  
+  /** The random number generator seed */
+  private int m_RandomSeed = 1;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "An instance filter that changes a percentage of a given"
+           + " attributes values. The attribute must be nominal."
+           + " Missing value can be treated as value itself.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+              "\tIndex of the attribute to be changed \n"
+              +"\t(default last attribute)",
+              "C", 1, "-C <col>"));
+    newVector.addElement(new Option(
+              "\tTreat missing values as an extra value \n",
+              "M", 1, "-M"));
+    newVector.addElement(new Option(
+              "\tSpecify the percentage of noise introduced \n"
+              +"\tto the data (default 10)",
+              "P", 1, "-P <num>"));
+    newVector.addElement(new Option(
+              "\tSpecify the random number seed (default 1)",
+              "S", 1, "-S <num>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Index of the attribute to be changed 
+   *  (default last attribute)</pre>
+   * 
+   * <pre> -M
+   *  Treat missing values as an extra value 
+   * </pre>
+   * 
+   * <pre> -P &lt;num&gt;
+   *  Specify the percentage of noise introduced 
+   *  to the data (default 10)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String indexString = Utils.getOption('C', options);
+    if (indexString.length() != 0) {
+      setAttributeIndex(indexString);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    if (Utils.getFlag('M', options)) {
+      setUseMissing(true);
+    }
+
+    String percentString = Utils.getOption('P', options);
+    if (percentString.length() != 0) {
+      setPercent((int) Double.valueOf(percentString).doubleValue());
+    } else {
+      setPercent(10);
+    }
+
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setRandomSeed(Integer.parseInt(seedString));
+    } else {
+      setRandomSeed(1);
+    }
+
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [7];
+    int current = 0;
+
+    options[current++] = "-C"; options[current++] = "" + getAttributeIndex();
+
+    if (getUseMissing()) {
+      options[current++] = "-M";
+    }
+
+    options[current++] = "-P"; options[current++] = "" + getPercent();
+
+    options[current++] = "-S"; options[current++] = "" + getRandomSeed();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+    
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useMissingTipText() {
+
+    return "Flag to set if missing values are used.";
+  }
+
+  /**
+   * Gets the flag if missing values are treated as extra values.
+   *
+   * @return the flag missing values.
+   */
+  public boolean getUseMissing() {
+
+    return m_UseMissing;
+  }
+
+  /**
+   * Sets the flag if missing values are treated as extra values.
+   *
+   * @param newUseMissing the new flag value.
+   */
+  public void setUseMissing(boolean newUseMissing) {
+
+    m_UseMissing = newUseMissing;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+
+    return "Random number seed.";
+  }
+
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getRandomSeed() {
+
+    return m_RandomSeed;
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setRandomSeed(int newSeed) {
+
+    m_RandomSeed = newSeed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String percentTipText() {
+
+    return "Percentage of introduced noise to data.";
+  }
+
+  /**
+   * Gets the size of noise data as a percentage of the original set.
+   *
+   * @return the noise data size
+   */
+  public int getPercent() {
+
+    return m_Percent;
+  }
+  
+  /**
+   * Sets the size of noise data, as a percentage of the original set.
+   *
+   * @param newPercent the subsample set size, between 0 and 100.
+   */
+  public void setPercent(int newPercent) {
+
+    m_Percent = newPercent;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "Index of the attribute that is to changed.";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    // set input format
+    //m_InputFormat = new Instances(instanceInfo, 0);
+    m_AttIndex.setUpper(getInputFormat().numAttributes() - 1);
+    // set index of attribute to be changed
+
+    // test if nominal 
+    if (!getInputFormat().attribute(m_AttIndex.getIndex()).isNominal()) {
+      throw new Exception("Adding noise is not possible:"
+                          + "Chosen attribute is numeric.");
+      }
+
+    // test if two values are given
+    if ((getInputFormat().attribute(m_AttIndex.getIndex()).numValues() < 2)
+        && (!m_UseMissing)) {
+      throw new Exception("Adding noise is not possible:"
+                          + "Chosen attribute has less than two values.");
+    }
+ 
+    setOutputFormat(getInputFormat());
+    m_NewBatch = true; 
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. 
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws Exception if the input format was not set
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    // check if input format is defined
+    if (getInputFormat() == null) {
+      throw new Exception("No input instance format defined");
+    }
+    
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws Exception if no input structure has been defined
+   */
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new Exception("No input instance format defined");
+    }
+
+    // Do the subsample, and clear the input instances.
+    addNoise (getInputFormat(), m_RandomSeed, m_Percent, m_AttIndex.getIndex(), 
+              m_UseMissing);
+
+    for(int i=0; i<getInputFormat().numInstances(); i++) {
+      push ((Instance)getInputFormat().instance(i).copy());
+    }
+
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * add noise to the dataset
+   * 
+   * a given percentage of the instances are changed in the  way, that 
+   * a set of instances are randomly selected using seed. The attribute 
+   * given by its index is changed from its current value to one of the
+   * other possibly ones, also randomly. This is done with leaving the
+   * apportion the same.  
+   * if m_UseMissing is true, missing value is  used as a value of its own
+   * @param instances is the dataset
+   * @param seed used for random function
+   * @param percent percentage of instances that are changed
+   * @param attIndex index of the attribute changed
+   * @param useMissing if true missing values are treated as extra value
+   */
+  public void addNoise (Instances instances, 
+                         int seed, 
+                         int percent,
+                         int attIndex,
+                         boolean useMissing) {
+    int indexList [];
+    int partition_count [];
+    int partition_max [];
+    double splitPercent = (double) percent; // percentage used for splits
+
+    // fill array with the indexes
+    indexList = new int [instances.numInstances()];
+    for (int i=0; i<instances.numInstances(); i++) {
+      indexList[i] = i;
+      }
+
+    // randomize list of indexes
+    Random random = new Random(seed);
+    for (int i=instances.numInstances()-1; i>=0; i--) {
+      int hValue = indexList[i];
+      int hIndex = (int)(random.nextDouble()*(double) i);
+      indexList[i] = indexList[hIndex];
+      indexList[hIndex] = hValue;
+      }
+ 
+    // initialize arrays that are used to count instances
+    // of each value and to keep the amount of instances of that value 
+    // that has to be changed
+    // this is done for the missing values in the two variables
+    // missing_count and missing_max
+    int numValues = instances.attribute(attIndex).numValues();
+
+    partition_count = new int[numValues];
+    partition_max = new int[numValues];
+    int missing_count = 0;;
+    int missing_max = 0;;
+
+    for (int i = 0; i < numValues; i++) {
+      partition_count[i] = 0;
+      partition_max[i] = 0;
+      }
+
+    // go through the dataset and count all occurrences of values 
+    // and all missing values using temporarily .._max arrays and
+    // variable missing_max
+    for (Enumeration e = instances.enumerateInstances();
+         e.hasMoreElements();) {
+      Instance instance = (Instance) e.nextElement(); 
+      if (instance.isMissing(attIndex)) {
+        missing_max++;
+      }
+      else {
+        int j = (int) instance.value(attIndex);
+        partition_max[(int) instance.value(attIndex)]++; 
+      }
+    }
+      
+    // use given percentage to calculate 
+    // how many have to be changed per split and
+    // how many of the missing values
+    if (!useMissing) {
+      missing_max = missing_count;
+    } else {
+      missing_max = (int) (((double)missing_max/100) * splitPercent + 0.5);
+    }
+    int sum_max = missing_max;
+    for (int i=0; i<numValues; i++) {
+      partition_max[i]=(int) (((double)partition_max[i]/100) * splitPercent 
+                              + 0.5);
+      sum_max = sum_max + partition_max[i];
+      }
+
+    // initialize sum_count to zero, use this variable to see if 
+    // everything is done already
+    int sum_count = 0;
+  
+    // add noise
+    // using the randomized index-array
+    // 
+    Random randomValue = new Random (seed);
+    int numOfValues = instances.attribute(attIndex).numValues();
+    for(int i=0; i<instances.numInstances(); i++) {
+       if (sum_count >= sum_max) { break; } // finished
+       Instance currInstance = instances.instance(indexList[i]);
+       // if value is missing then...
+       if (currInstance.isMissing(attIndex)) {
+         if (missing_count < missing_max) {
+           changeValueRandomly (randomValue, 
+                                numOfValues,
+                                attIndex, 
+                                currInstance,
+                                useMissing); 
+           missing_count++;
+           sum_count++;
+         }
+         
+       } else {
+         int vIndex = (int) currInstance.value(attIndex);
+         if (partition_count[vIndex] < partition_max[vIndex]) {
+           changeValueRandomly (randomValue,
+                                numOfValues,
+                                attIndex,     
+                                currInstance, 
+                                useMissing);           
+           partition_count[vIndex]++;
+           sum_count++;
+         }
+       }
+    }
+
+  }
+
+  /**
+   * method to set a new value
+   *
+   * @param r random function
+   * @param numOfValues 
+   * @param instance
+   * @param useMissing
+   */
+  private void changeValueRandomly(Random r, int numOfValues,
+                                   int indexOfAtt, 
+                                   Instance instance, 
+                                   boolean useMissing) {
+    int currValue;
+
+    // get current value 
+    // if value is missing set current value to number of values
+    // whiche is the highest possible value plus one 
+    if (instance.isMissing(indexOfAtt)) {
+      currValue = numOfValues;
+    } else {
+      currValue = (int) instance.value(indexOfAtt);
+    }
+
+    // with only two possible values it is easier
+    if ((numOfValues == 2) && (!instance.isMissing(indexOfAtt))) {
+	instance.setValue(indexOfAtt, (double) ((currValue+1)% 2));
+    } else {
+      // get randomly a new value not equal to the current value
+      // if missing values are used as values they must be treated
+      // in a special way
+      while (true) {
+	  int newValue;
+        if (useMissing) {
+          newValue = (int) (r.nextDouble() * (double) (numOfValues + 1));
+        } else {
+          newValue = (int) (r.nextDouble() * (double) numOfValues);
+        }
+        // have we found a new value?
+        if (newValue != currValue) { 
+          // the value 1 above the highest possible value (=numOfValues)
+          // is used as missing value
+          if (newValue == numOfValues) { instance.setMissing(indexOfAtt); }
+          else { instance.setValue(indexOfAtt, (double) newValue); }
+          break;
+        }
+      }
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new AddNoise(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/AddValues.java	(revision 29)
@@ -0,0 +1,476 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AddValues.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Adds the labels from the given list to an attribute if they are missing. The labels can also be sorted in an ascending manner. If no labels are provided then only the (optional) sorting applies.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index
+ *  (default last).</pre>
+ * 
+ * <pre> -L &lt;label1,label2,...&gt;
+ *  Comma-separated list of labels to add.
+ *  (default: none)</pre>
+ * 
+ * <pre> -S
+ *  Turns on the sorting of the labels.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Based on code from AddValues.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz) 
+ * @version $Revision: 5987 $
+ * @see     AddValues
+ */
+public class AddValues 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8100622241742393656L;
+
+  /** The attribute's index setting. */
+  protected SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The values to add. */
+  protected Vector m_Labels = new Vector();
+
+  /** Whether to sort the values. */
+  protected boolean m_Sort = false;
+
+  /** the array with the sorted label indices */
+  protected int[] m_SortedIndices;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Adds the labels from the given list to an attribute if they are "
+      + "missing. The labels can also be sorted in an ascending manner. "
+      + "If no labels are provided then only the (optional) sorting applies.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.addElement(new Option(
+	"\tSets the attribute index\n"
+	+ "\t(default last).",
+	"C", 1, "-C <col>"));
+
+    result.addElement(new Option(
+	"\tComma-separated list of labels to add.\n"
+	+ "\t(default: none)",
+	"L", 1, "-L <label1,label2,...>"));
+
+    result.addElement(new Option(
+	"\tTurns on the sorting of the labels.",
+	"S", 0, "-S"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index
+   *  (default last).</pre>
+   * 
+   * <pre> -L &lt;label1,label2,...&gt;
+   *  Comma-separated list of labels to add.
+   *  (default: none)</pre>
+   * 
+   * <pre> -S
+   *  Turns on the sorting of the labels.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setAttributeIndex(tmpStr);
+    else
+      setAttributeIndex("last");
+
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0)
+      setLabels(tmpStr);
+    else
+      setLabels("");
+
+    setSort(Utils.getFlag('S', options));
+   
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.add("-C");
+    result.add("" + getAttributeIndex());
+    
+    result.add("-L");
+    result.add("" + getLabels());
+    
+    if (getSort())
+      result.add("-S");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the 
+   * 				structure is required).
+   * @return 			true if the outputFormat may be collected 
+   * 				immediately
+   * @throws Exception 		if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    Attribute 	att;
+    Attribute 	attNew;
+    Vector 	allLabels;
+    Enumeration enm;
+    int 	i;
+    FastVector	values;
+    FastVector	atts;
+    Instances	instNew;
+
+    super.setInputFormat(instanceInfo);
+    
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    att = instanceInfo.attribute(m_AttIndex.getIndex());
+    if (!att.isNominal())
+      throw new UnsupportedAttributeTypeException("Chosen attribute not nominal.");
+    
+    // merge labels
+    allLabels = new Vector();
+    enm = att.enumerateValues();
+    while (enm.hasMoreElements())
+      allLabels.add(enm.nextElement());
+    for (i = 0; i < m_Labels.size(); i++) {
+      if (!allLabels.contains(m_Labels.get(i)))
+	allLabels.add(m_Labels.get(i));
+    }
+    
+    // generate index array
+    if (getSort())
+      Collections.sort(allLabels);
+    m_SortedIndices = new int[att.numValues()];
+    enm             = att.enumerateValues();
+    i               = 0;
+    while (enm.hasMoreElements()) {
+      m_SortedIndices[i] = allLabels.indexOf(enm.nextElement());
+      i++;
+    }
+    
+    // generate new header
+    values = new FastVector();
+    for (i = 0; i < allLabels.size(); i++)
+      values.addElement(allLabels.get(i));
+    attNew = new Attribute(att.name(), values);
+
+    atts = new FastVector();
+    for (i = 0; i < instanceInfo.numAttributes(); i++) {
+      if (i == m_AttIndex.getIndex())
+	atts.addElement(attNew);
+      else
+	atts.addElement(instanceInfo.attribute(i));
+    }
+    
+    instNew = new Instances(instanceInfo.relationName(), atts, 0);
+    instNew.setClassIndex(instanceInfo.classIndex());
+    
+    // set new format
+    setOutputFormat(instNew);
+    
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance 	the input instance
+   * @return 		true if the filtered instance may now be
+   * 			collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+    Instance 	newInstance;
+    double[]	values;
+
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    // generate new Instance
+    values = instance.toDoubleArray();
+    values[m_AttIndex.getIndex()] = m_SortedIndices[(int) values[m_AttIndex.getIndex()]];
+    newInstance = new DenseInstance(instance.weight(), values);
+
+    // copy string values etc. from input to output
+    copyValues(instance, false, instance.dataset(), getOutputFormat());
+    
+    push(newInstance);
+    
+    return true;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+    return "Sets which attribute to process. This "
+      + "attribute must be nominal (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return 		the index of the attribute
+   */
+  public String getAttributeIndex() {
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex 	the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String labelsTipText() {
+    return "Comma-separated list of lables to add.";
+  }
+
+  /**
+   * Get the comma-separated list of labels that are added.
+   *
+   * @return 		the list of labels
+   */
+  public String getLabels() {
+    String	result;
+    int		i;
+
+    result = "";
+    for (i = 0; i < m_Labels.size(); i++) {
+      if (i > 0)
+	result += ",";
+      result += Utils.quote((String) m_Labels.get(i));
+    }
+    
+    return result;
+  }
+
+  /**
+   * Sets the comma-separated list of labels.
+   *
+   * @param value	the list
+   */
+  public void setLabels(String value) {
+    int		i;
+    String	label;
+    boolean	quoted;
+    boolean	add;
+    
+    m_Labels.clear();
+    
+    label  = "";
+    quoted = false;
+    add    = false;
+    
+    for (i = 0; i < value.length(); i++) {
+      // quotes?
+      if (value.charAt(i) == '"') {
+	quoted = !quoted;
+	if (!quoted)
+	  add = true;
+      }
+      // comma
+      else if ( (value.charAt(i) == ',') && (!quoted) ) {
+	add = true;
+      }
+      // normal character
+      else {
+	label += value.charAt(i);
+	// last character?
+	if (i == value.length() - 1)
+	  add = true;
+      }
+      
+      if (add) {
+	if (label.length() != 0)
+	  m_Labels.add(label);
+	label = "";
+	add   = false;
+      }
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String sortTipText() {
+    return "Whether to sort the labels alphabetically.";
+  }
+
+  /**
+   * Gets whether the labels are sorted or not.
+   *
+   * @return 		true if the labels are sorted
+   */
+  public boolean getSort() {
+    return m_Sort;
+  }
+
+  /**
+   * Sets whether the labels are sorted.
+   *
+   * @param value	if true the labels are sorted
+   */
+  public void setSort(boolean value) {
+    m_Sort = value;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing and running this class.
+   *
+   * @param args 	should contain arguments to the filter: 
+   * 			use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new AddValues(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Center.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Center.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Center.java	(revision 29)
@@ -0,0 +1,338 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Center.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Sourcable;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Centers all numeric attributes in the given dataset to have zero mean (apart from the class attribute, if set).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @author FracPete (fracpete at waikato dot ac dot nz) 
+ * @version $Revision: 5987 $
+ */
+public class Center 
+  extends PotentialClassIgnorer 
+  implements UnsupervisedFilter, Sourcable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -9101338448900581023L;
+  
+  /** The means */
+  private double[] m_Means;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Centers all numeric attributes in the given dataset "
+      + "to have zero mean (apart from the class attribute, if set).";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the structure 
+   * 				is required).
+   * @return true 		if the outputFormat may be collected immediately
+   * @throws Exception 		if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_Means = null;
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance 			the input instance
+   * @return true 			if the filtered instance may now be 
+   * 					collected with output().
+   * @throws IllegalStateException 	if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (m_Means == null) {
+      bufferInput(instance);
+      return false;
+    } 
+    else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true 			if there are instances pending output
+   * @throws IllegalStateException 	if no input structure has been defined
+   */
+  public boolean batchFinished() {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+    
+    if (m_Means == null) {
+      Instances input = getInputFormat();
+      m_Means = new double[input.numAttributes()];
+      for (int i = 0; i < input.numAttributes(); i++) {
+	if (input.attribute(i).isNumeric() &&
+	    (input.classIndex() != i)) {
+	  m_Means[i] = input.meanOrMode(i);
+	}
+      }
+
+      // Convert pending input instances
+      for (int i = 0; i < input.numInstances(); i++)
+	convertInstance(input.instance(i));
+    }
+    
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance 	the instance to convert
+   */
+  private void convertInstance(Instance instance) {
+    Instance inst = null;
+    
+    if (instance instanceof SparseInstance) {
+      double[] newVals = new double[instance.numAttributes()];
+      int[] newIndices = new int[instance.numAttributes()];
+      double[] vals = instance.toDoubleArray();
+      int ind = 0;
+      for (int j = 0; j < instance.numAttributes(); j++) {
+	double value;
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  
+	  value = vals[j] - m_Means[j];
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	} else {
+	  value = vals[j];
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	}
+      }	
+      double[] tempVals = new double[ind];
+      int[] tempInd = new int[ind];
+      System.arraycopy(newVals, 0, tempVals, 0, ind);
+      System.arraycopy(newIndices, 0, tempInd, 0, ind);
+      inst = new SparseInstance(instance.weight(), tempVals, tempInd,
+                                instance.numAttributes());
+    } 
+    else {
+      double[] vals = instance.toDoubleArray();
+      for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  vals[j] = (vals[j] - m_Means[j]);
+	}
+      }	
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    
+    inst.setDataset(instance.dataset());
+    
+    push(inst);
+  }
+  
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception {
+    StringBuffer        result;
+    boolean[]		process;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // determine what attributes were processed
+    process = new boolean[data.numAttributes()];
+    for (i = 0; i < data.numAttributes(); i++) {
+      process[i] = (data.attribute(i).isNumeric() && (i != data.classIndex()));
+    }
+    
+    result.append("class " + className + " {\n");
+    result.append("\n");
+    result.append("  /** lists which attributes will be processed */\n");
+    result.append("  protected final static boolean[] PROCESS = new boolean[]{" + Utils.arrayToString(process) + "};\n");
+    result.append("\n");
+    result.append("  /** the computed means */\n");
+    result.append("  protected final static double[] MEANS = new double[]{" + Utils.arrayToString(m_Means) + "};\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters a single row\n");
+    result.append("   * \n");
+    result.append("   * @param i the row to process\n");
+    result.append("   * @return the processed row\n");
+    result.append("   */\n");
+    result.append("  public static Object[] filter(Object[] i) {\n");
+    result.append("    Object[] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      if (PROCESS[n] && (i[n] != null))\n");
+    result.append("        result[n] = ((Double) i[n]) - MEANS[n];\n");
+    result.append("      else\n");
+    result.append("        result[n] = i[n];\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters multiple rows\n");
+    result.append("   * \n");
+    result.append("   * @param i the rows to process\n");
+    result.append("   * @return the processed rows\n");
+    result.append("   */\n");
+    result.append("  public static Object[][] filter(Object[][] i) {\n");
+    result.append("    Object[][] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length][];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      result[n] = filter(i[n]);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args 	should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] args) {
+    runFilter(new Center(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ChangeDateFormat.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ChangeDateFormat.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ChangeDateFormat.java	(revision 29)
@@ -0,0 +1,374 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ChangeDateFormat.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Changes the date format used by a date attribute. This is most useful for converting to a format with less precision, for example, from an absolute date to day of year, etc. This changes the format string, and changes the date values to those that would be parsed by the new format.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index (default last).</pre>
+ * 
+ * <pre> -F &lt;value index&gt;
+ *  Sets the output date format string (default corresponds to ISO-8601).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 5987 $
+ */
+public class ChangeDateFormat 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -1609344074013448737L;
+
+  /** The default output date format. Corresponds to ISO-8601 format. */
+  private static final SimpleDateFormat DEFAULT_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The output date format. */
+  private SimpleDateFormat m_DateFormat = DEFAULT_FORMAT;
+
+  /** The output attribute. */
+  private Attribute m_OutputAttribute;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Changes the date format used by a date attribute. This is most "
+      + "useful for converting to a format with less precision, for example, "
+      + "from an absolute date to day of year, etc. This changes the format "
+      + "string, and changes the date values to those that would be parsed "
+      + "by the new format.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isDate()) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute not date.");
+    }
+
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. 
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws Exception if the input format was not set or the date format cannot
+   * be parsed
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance newInstance = (Instance)instance.copy();
+    int index = m_AttIndex.getIndex();
+    if (!newInstance.isMissing(index)) {
+      double value = instance.value(index);
+      try {
+        // Format and parse under the new format to force any required
+        // loss in precision.
+        value = m_OutputAttribute.parseDate(m_OutputAttribute.formatDate(value));
+      } catch (ParseException pe) {
+        throw new RuntimeException("Output date format couldn't parse its own output!!");
+      }
+      newInstance.setValue(index, value);
+    }
+    push(newInstance);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tSets the attribute index (default last).",
+              "C", 1, "-C <col>"));
+
+    newVector.addElement(new Option(
+              "\tSets the output date format string (default corresponds to ISO-8601).",
+              "F", 1, "-F <value index>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index (default last).</pre>
+   * 
+   * <pre> -F &lt;value index&gt;
+   *  Sets the output date format string (default corresponds to ISO-8601).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String attIndex = Utils.getOption('C', options);
+    if (attIndex.length() != 0) {
+      setAttributeIndex(attIndex);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    String formatString = Utils.getOption('F', options);
+    if (formatString.length() != 0) {
+      setDateFormat(formatString);
+    } else {
+      setDateFormat(DEFAULT_FORMAT);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+  
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [4];
+    int current = 0;
+
+    options[current++] = "-C";
+    options[current++] = "" + getAttributeIndex();
+    options[current++] = "-F"; 
+    options[current++] = "" + getDateFormat().toPattern();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "Sets which attribute to process. This "
+      + "attribute must be of type date (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Gets the index of the attribute converted.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets the index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String dateFormatTipText() {
+
+    return "The date format to change to. This should be a "
+      + "format understood by Java's SimpleDateFormat class.";
+  }
+
+  /**
+   * Get the date format used in output.
+   *
+   * @return the output date format.
+   */
+  public SimpleDateFormat getDateFormat() {
+
+    return m_DateFormat;
+  }
+
+  /**
+   * Sets the output date format.
+   *
+   * @param dateFormat the output date format.
+   */
+  public void setDateFormat(String dateFormat) {
+
+    setDateFormat(new SimpleDateFormat(dateFormat));
+  }
+
+  /**
+   * Sets the output date format.
+   *
+   * @param dateFormat the output date format.
+   */
+  public void setDateFormat(SimpleDateFormat dateFormat) {
+    if (dateFormat == null) {
+      throw new NullPointerException();
+    }
+    m_DateFormat = dateFormat;
+  }
+
+  /**
+   * Set the output format. Changes the format of the specified date
+   * attribute.
+   */
+  private void setOutputFormat() {
+    
+    // Create new attributes
+    FastVector newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (j == m_AttIndex.getIndex()) {
+	newAtts.addElement(new Attribute(att.name(), getDateFormat().toPattern()));  
+      } else {
+	newAtts.addElement(att.copy()); 
+      }
+    }
+      
+    // Create new header
+    Instances newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    m_OutputAttribute = newData.attribute(m_AttIndex.getIndex());
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new ChangeDateFormat(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClassAssigner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClassAssigner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClassAssigner.java	(revision 29)
@@ -0,0 +1,290 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClassAssigner.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleStreamFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Filter that can set and unset the class index.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -C &lt;num|first|last|0&gt;
+ *  The index of the class attribute. Index starts with 1, 'first'
+ *  and 'last' are accepted, '0' unsets the class index.
+ *  (default: last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class ClassAssigner
+  extends SimpleStreamFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1775780193887394115L;
+
+  /** use the first attribute as class. */
+  public final static int FIRST = 0;
+  
+  /** use the last attribute as class. */
+  public final static int LAST = -2;
+  
+  /** unset the class attribute. */
+  public final static int UNSET = -1;
+  
+  /** the class index. */
+  protected int m_ClassIndex = LAST;
+  
+  /**
+   * Returns a string describing this classifier.
+   * 
+   * @return 		a description of the classifier suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Filter that can set and unset the class index.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+	"\tThe index of the class attribute. Index starts with 1, 'first'\n"
+	+ "\tand 'last' are accepted, '0' unsets the class index.\n"
+	+ "\t(default: last)",
+	"C", 1, "-C <num|first|last|0>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -C &lt;num|first|last|0&gt;
+   *  The index of the class attribute. Index starts with 1, 'first'
+   *  and 'last' are accepted, '0' unsets the class index.
+   *  (default: last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    super.setOptions(options);
+    
+    tmpStr = Utils.getOption("C", options);
+    if (tmpStr.length() != 0)
+      setClassIndex(tmpStr);
+    else
+      setClassIndex("last");
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-C");
+    result.add(getClassIndex());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+    return 
+        "The index of the class attribute, starts with 1, 'first' and 'last' "
+      + "are accepted as well, '0' unsets the class index.";
+  }
+
+  /**
+   * sets the class index.
+   * 
+   * @param value	the class index
+   */
+  public void setClassIndex(String value) {
+    if (value.equalsIgnoreCase("first")) {
+      m_ClassIndex = FIRST;
+    }
+    else if (value.equalsIgnoreCase("last")) {
+      m_ClassIndex = LAST;
+    }
+    else if (value.equalsIgnoreCase("0")) {
+      m_ClassIndex = UNSET;
+    }
+    else {
+      try {
+	m_ClassIndex = Integer.parseInt(value) - 1;
+      }
+      catch (Exception e) {
+	System.err.println("Error parsing '" + value + "'!");
+      }
+    }
+  }
+
+  /**
+   * returns the class index.
+   * 
+   * @return		the class index
+   */
+  public String getClassIndex() {
+    if (m_ClassIndex == FIRST)
+      return "first";
+    else if (m_ClassIndex == LAST)
+      return "last";
+    else if (m_ClassIndex == UNSET)
+      return "0";
+    else
+      return "" + (m_ClassIndex + 1);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the class index is invalid
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+    
+    Instances result = new Instances(inputFormat, 0);
+    
+    if (m_ClassIndex == FIRST)
+      result.setClassIndex(0);
+    else if (m_ClassIndex == LAST)
+      result.setClassIndex(result.numAttributes() - 1);
+    else if (m_ClassIndex == UNSET)
+      result.setClassIndex(-1);
+    else
+      result.setClassIndex(m_ClassIndex);
+    
+    return result;
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    return instance;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new ClassAssigner(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClusterMembership.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClusterMembership.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ClusterMembership.java	(revision 29)
@@ -0,0 +1,560 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClusterMembership.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.clusterers.DensityBasedClusterer;
+import weka.clusterers.AbstractDensityBasedClusterer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter that uses a density-based clusterer to generate cluster membership values; filtered instances are composed of these values plus the class attribute (if set in the input data). If a (nominal) class attribute is set, the clusterer is run separately for each class. The class attribute (if set) and any user-specified attributes are ignored during the clustering operation
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;clusterer name&gt;
+ *  Full name of clusterer to use. eg:
+ *   weka.clusterers.EM
+ *  Additional options after the '--'.
+ *  (default: weka.clusterers.EM)</pre>
+ * 
+ * <pre> -I &lt;att1,att2-att4,...&gt;
+ *  The range of attributes the clusterer should ignore.
+ *  (the class attribute is automatically ignored)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * Options after the -- are passed on to the clusterer.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Eibe Frank
+ * @version $Revision: 5987 $
+ */
+public class ClusterMembership 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 6675702504667714026L;
+
+  /** The clusterer */
+  protected DensityBasedClusterer m_clusterer = new weka.clusterers.EM();
+
+  /** Array for storing the clusterers */
+  protected DensityBasedClusterer[] m_clusterers;
+
+  /** Range of attributes to ignore */
+  protected Range m_ignoreAttributesRange;
+
+  /** Filter for removing attributes */
+  protected Filter m_removeAttributes;
+
+  /** The prior probability for each class */
+  protected double[] m_priors;
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = m_clusterer.getCapabilities();
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter, makes sure that the class is
+   * never set (for the clusterer).
+   *
+   * @param data	the data to use for customization
+   * @return            the capabilities of this object, based on the data
+   * @see               #getCapabilities()
+   */
+  public Capabilities getCapabilities(Instances data) {
+    Instances	newData;
+    
+    newData = new Instances(data, 0);
+    newData.setClassIndex(-1);
+    
+    return super.getCapabilities(newData);
+  }
+  
+  /**
+   * tests the data whether the filter can actually handle it
+   * 
+   * @param instanceInfo	the data to test
+   * @throws Exception		if the test fails
+   */
+  protected void testInputFormat(Instances instanceInfo) throws Exception {
+    getCapabilities(instanceInfo).testWithFail(removeIgnored(instanceInfo));
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */ 
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    
+    super.setInputFormat(instanceInfo);
+    m_removeAttributes = null;
+    m_priors = null;
+
+    return false;
+  }
+
+  /**
+   * filters all attributes that should be ignored
+   * 
+   * @param data	the data to filter
+   * @return		the filtered data
+   * @throws Exception	if filtering fails
+   */
+  protected Instances removeIgnored(Instances data) throws Exception {
+    Instances result = data;
+    
+    if (m_ignoreAttributesRange != null || data.classIndex() >= 0) {
+      result = new Instances(data);
+      m_removeAttributes = new Remove();
+      String rangeString = "";
+      if (m_ignoreAttributesRange != null) {
+	rangeString += m_ignoreAttributesRange.getRanges();
+      }
+      if (data.classIndex() >= 0) {
+	if (rangeString.length() > 0) {
+	  rangeString += "," + (data.classIndex() + 1);
+	} else {
+	  rangeString = "" + (data.classIndex() + 1);
+	}
+      }
+      ((Remove) m_removeAttributes).setAttributeIndices(rangeString);
+      ((Remove) m_removeAttributes).setInvertSelection(false);
+      m_removeAttributes.setInputFormat(data);
+      result = Filter.useFilter(data, m_removeAttributes);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */  
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (outputFormatPeek() == null) {
+      Instances toFilter = getInputFormat();
+      Instances[] toFilterIgnoringAttributes;
+
+      // Make subsets if class is nominal
+      if ((toFilter.classIndex() >= 0) && toFilter.classAttribute().isNominal()) {
+	toFilterIgnoringAttributes = new Instances[toFilter.numClasses()];
+	for (int i = 0; i < toFilter.numClasses(); i++) {
+	  toFilterIgnoringAttributes[i] = new Instances(toFilter, toFilter.numInstances());
+	}
+	for (int i = 0; i < toFilter.numInstances(); i++) {
+	  toFilterIgnoringAttributes[(int)toFilter.instance(i).classValue()].add(toFilter.instance(i));
+	}
+	m_priors = new double[toFilter.numClasses()];
+	for (int i = 0; i < toFilter.numClasses(); i++) {
+	  toFilterIgnoringAttributes[i].compactify();
+	  m_priors[i] = toFilterIgnoringAttributes[i].sumOfWeights();
+	}
+	Utils.normalize(m_priors);
+      } else {
+	toFilterIgnoringAttributes = new Instances[1];
+	toFilterIgnoringAttributes[0] = toFilter;
+	m_priors = new double[1];
+	m_priors[0] = 1;
+      }
+
+      // filter out attributes if necessary
+      for (int i = 0; i < toFilterIgnoringAttributes.length; i++)
+	toFilterIgnoringAttributes[i] = removeIgnored(toFilterIgnoringAttributes[i]);
+
+      // build the clusterers
+      if ((toFilter.classIndex() <= 0) || !toFilter.classAttribute().isNominal()) {
+	m_clusterers = AbstractDensityBasedClusterer.makeCopies(m_clusterer, 1);
+	m_clusterers[0].buildClusterer(toFilterIgnoringAttributes[0]);
+      } else {
+	m_clusterers = AbstractDensityBasedClusterer.makeCopies(m_clusterer, toFilter.numClasses());
+	for (int i = 0; i < m_clusterers.length; i++) {
+	  if (toFilterIgnoringAttributes[i].numInstances() == 0) {
+	    m_clusterers[i] = null;
+	  } else {
+	    m_clusterers[i].buildClusterer(toFilterIgnoringAttributes[i]);
+	  }
+	}
+      }
+      
+      // create output dataset
+      FastVector attInfo = new FastVector();
+      for (int j = 0; j < m_clusterers.length; j++) {
+	if (m_clusterers[j] != null) {
+	  for (int i = 0; i < m_clusterers[j].numberOfClusters(); i++) {
+	    attInfo.addElement(new Attribute("pCluster_" + j + "_" + i));
+	  }
+	}
+      }
+      if (toFilter.classIndex() >= 0) {
+	attInfo.addElement(toFilter.classAttribute().copy());
+      }
+      attInfo.trimToSize();
+      Instances filtered = new Instances(toFilter.relationName()+"_clusterMembership",
+					 attInfo, 0);
+      if (toFilter.classIndex() >= 0) {
+	filtered.setClassIndex(filtered.numAttributes() - 1);
+      }
+      setOutputFormat(filtered);
+
+      // build new dataset
+      for (int i = 0; i < toFilter.numInstances(); i++) {
+	convertInstance(toFilter.instance(i));
+      }
+    }
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (outputFormatPeek() != null) {
+      convertInstance(instance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Converts logs back to density values.
+   * 
+   * @param j the index of the clusterer
+   * @param in the instance to convert the logs back
+   * @return the densities
+   * @throws Exception if something goes wrong
+   */
+  protected double[] logs2densities(int j, Instance in) throws Exception {
+
+    double[] logs = m_clusterers[j].logJointDensitiesForInstance(in);
+
+    for (int i = 0; i < logs.length; i++) {
+      logs[i] += Math.log(m_priors[j]);
+    }
+    return logs;
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is added to 
+   * the end of the output queue.
+   *
+   * @param instance the instance to convert
+   * @throws Exception if something goes wrong
+   */
+  protected void convertInstance(Instance instance) throws Exception {
+    
+    // set up values
+    double [] instanceVals = new double[outputFormatPeek().numAttributes()];
+    double [] tempvals;
+    if (instance.classIndex() >= 0) {
+      tempvals = new double[outputFormatPeek().numAttributes() - 1];
+    } else {
+      tempvals = new double[outputFormatPeek().numAttributes()];
+    }
+    int pos = 0;
+    for (int j = 0; j < m_clusterers.length; j++) {
+      if (m_clusterers[j] != null) {
+	double [] probs;
+	if (m_removeAttributes != null) {
+	  m_removeAttributes.input(instance);
+	  probs = logs2densities(j, m_removeAttributes.output());
+	} else {
+	  probs = logs2densities(j, instance);
+	}
+	System.arraycopy(probs, 0, tempvals, pos, probs.length);
+	pos += probs.length;
+      }
+    }
+    tempvals = Utils.logs2probs(tempvals);
+    System.arraycopy(tempvals, 0, instanceVals, 0, tempvals.length);
+    if (instance.classIndex() >= 0) {
+      instanceVals[instanceVals.length - 1] = instance.classValue();
+    }
+    
+    push(new DenseInstance(instance.weight(), instanceVals));
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(2);
+    
+    newVector.
+      addElement(new Option("\tFull name of clusterer to use. eg:\n"
+	                    + "\t\tweka.clusterers.EM\n"
+			    + "\tAdditional options after the '--'.\n"
+			    + "\t(default: weka.clusterers.EM)",
+			    "W", 1, "-W <clusterer name>"));
+
+    newVector.
+      addElement(new Option("\tThe range of attributes the clusterer should ignore."
+			    +"\n\t(the class attribute is automatically ignored)",
+			    "I", 1,"-I <att1,att2-att4,...>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;clusterer name&gt;
+   *  Full name of clusterer to use. eg:
+   *   weka.clusterers.EM
+   *  Additional options after the '--'.
+   *  (default: weka.clusterers.EM)</pre>
+   * 
+   * <pre> -I &lt;att1,att2-att4,...&gt;
+   *  The range of attributes the clusterer should ignore.
+   *  (the class attribute is automatically ignored)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * Options after the -- are passed on to the clusterer.
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String clustererString = Utils.getOption('W', options);
+    if (clustererString.length() == 0)
+      clustererString = weka.clusterers.EM.class.getName();
+    setDensityBasedClusterer((DensityBasedClusterer)Utils.
+			     forName(DensityBasedClusterer.class, clustererString,
+				     Utils.partitionOptions(options)));
+
+    setIgnoredAttributeIndices(Utils.getOption('I', options));
+    Utils.checkForRemainingOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] clustererOptions = new String [0];
+    if ((m_clusterer != null) &&
+	(m_clusterer instanceof OptionHandler)) {
+      clustererOptions = ((OptionHandler)m_clusterer).getOptions();
+    }
+    String [] options = new String [clustererOptions.length + 5];
+    int current = 0;
+
+    if (!getIgnoredAttributeIndices().equals("")) {
+      options[current++] = "-I";
+      options[current++] = getIgnoredAttributeIndices();
+    }
+    
+    if (m_clusterer != null) {
+      options[current++] = "-W"; 
+      options[current++] = getDensityBasedClusterer().getClass().getName();
+    }
+
+    options[current++] = "--";
+    System.arraycopy(clustererOptions, 0, options, current,
+		     clustererOptions.length);
+    current += clustererOptions.length;
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A filter that uses a density-based clusterer to generate cluster "
+      + "membership values; filtered instances are composed of these values "
+      + "plus the class attribute (if set in the input data). If a (nominal) "
+      + "class attribute is set, the clusterer is run separately for each "
+      + "class. The class attribute (if set) and any user-specified "
+      + "attributes are ignored during the clustering operation";
+  }
+  
+  /**
+   * Returns a description of this option suitable for display
+   * as a tip text in the gui.
+   *
+   * @return description of this option
+   */
+  public String densityBasedClustererTipText() {
+    return "The clusterer that will generate membership values for the instances.";
+  }
+
+  /**
+   * Set the clusterer for use in filtering
+   *
+   * @param newClusterer the clusterer to use
+   */
+  public void setDensityBasedClusterer(DensityBasedClusterer newClusterer) {
+    m_clusterer = newClusterer;
+  }
+
+  /**
+   * Get the clusterer used by this filter
+   *
+   * @return the clusterer used
+   */
+  public DensityBasedClusterer getDensityBasedClusterer() {
+    return m_clusterer;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ignoredAttributeIndicesTipText() {
+
+    return "The range of attributes to be ignored by the clusterer. eg: first-3,5,9-last";
+  }
+
+  /**
+   * Gets ranges of attributes to be ignored.
+   *
+   * @return a string containing a comma-separated list of ranges
+   */
+  public String getIgnoredAttributeIndices() {
+
+    if (m_ignoreAttributesRange == null) {
+      return "";
+    } else {
+      return m_ignoreAttributesRange.getRanges();
+    }
+  }
+
+  /**
+   * Sets the ranges of attributes to be ignored. If provided string
+   * is null, no attributes will be ignored.
+   *
+   * @param rangeList a string representing the list of attributes. 
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setIgnoredAttributeIndices(String rangeList) {
+
+    if ((rangeList == null) || (rangeList.length() == 0)) {
+      m_ignoreAttributesRange = null;
+    } else {
+      m_ignoreAttributesRange = new Range();
+      m_ignoreAttributesRange.setRanges(rangeList);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new ClusterMembership(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Copy.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Copy.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Copy.java	(revision 29)
@@ -0,0 +1,389 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Copy.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that copies a range of attributes in the dataset. This is used in conjunction with other filters that overwrite attribute values during the course of their operation -- this filter allows the original attributes to be kept as well as the new attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to copy. First and last are valid
+ *  indexes. (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. copy all non-specified columns)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Copy 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8543707493627441566L;
+
+  /** Stores which columns to copy */
+  protected Range m_CopyCols = new Range();
+
+  /**
+   * Stores the indexes of the selected attributes in order, once the
+   * dataset is seen
+   */
+  protected int [] m_SelectedAttributes;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to copy. First and last are valid\n"
+	      +"\tindexes. (default none)",
+              "R", 1, "-R <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tInvert matching sense (i.e. copy all non-specified columns)",
+              "V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to copy. First and last are valid
+   *  indexes. (default none)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense (i.e. copy all non-specified columns)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String copyList = Utils.getOption('R', options);
+    if (copyList.length() != 0) {
+      setAttributeIndices(copyList);
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+    
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [3];
+    int current = 0;
+
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if a problem occurs setting the input format
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    
+    m_CopyCols.setUpper(instanceInfo.numAttributes() - 1);
+
+    // Create the output buffer
+    Instances outputFormat = new Instances(instanceInfo, 0); 
+    m_SelectedAttributes = m_CopyCols.getSelection();
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      // Create a copy of the attribute with a different name
+      Attribute origAttribute = instanceInfo.attribute(current);
+      outputFormat.insertAttributeAt((Attribute)origAttribute.copy(),
+				     outputFormat.numAttributes());
+      outputFormat.renameAttribute(outputFormat.numAttributes() - 1,
+				   "Copy of " + origAttribute.name());
+
+    }
+
+    // adapt locators
+    int[] newIndices = new int[instanceInfo.numAttributes() + m_SelectedAttributes.length];
+    for (int i = 0; i < instanceInfo.numAttributes(); i++)
+      newIndices[i] = i;
+    for (int i = 0; i < m_SelectedAttributes.length; i++)
+      newIndices[instanceInfo.numAttributes() + i] = m_SelectedAttributes[i];
+    initInputLocators(instanceInfo, newIndices);
+
+    setOutputFormat(outputFormat);
+    
+    return true;
+  }
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    double[] vals = new double[outputFormatPeek().numAttributes()];
+    for(int i = 0; i < getInputFormat().numAttributes(); i++) {
+      vals[i] = instance.value(i);
+    }
+    int j = getInputFormat().numAttributes();
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      vals[i + j] = instance.value(current);
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+    return true;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "An instance filter that copies a range of attributes in the"
+      + " dataset. This is used in conjunction with other filters that"
+      + " overwrite attribute values during the course of their operation --"
+      + " this filter allows the original attributes to be kept as well"
+      + " as the new attributes.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Sets copy selected vs unselected action."
+      + " If set to false, only the specified attributes will be copied;"
+      + " If set to true, non-specified attributes will be copied.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_CopyCols.getInvert();
+  }
+
+  /**
+   * Set whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are copied. If false
+   * selected columns are copied and unselected columns are kept. <br>
+   * Note: use this method before you call 
+   * <code>setInputFormat(Instances)</code>, since the output format is
+   * determined in that method.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_CopyCols.setInvert(invert);
+  }
+
+  /**
+   * Get the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_CopyCols.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param rangeList a string representing the list of attributes.  Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last<br>
+   * Note: use this method before you call 
+   * <code>setInputFormat(Instances)</code>, since the output format is
+   * determined in that method.
+   * @throws Exception if an invalid range list is supplied
+   */
+  public void setAttributeIndices(String rangeList) throws Exception {
+
+    m_CopyCols.setRanges(rangeList);
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.<br>
+   * Note: use this method before you call 
+   * <code>setInputFormat(Instances)</code>, since the output format is
+   * determined in that method.
+   * @throws Exception if an invalid set of ranges is supplied
+   */
+  public void setAttributeIndicesArray(int [] attributes) throws Exception {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Copy(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Discretize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Discretize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Discretize.java	(revision 29)
@@ -0,0 +1,1081 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Discretize.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.WeightedInstancesHandler;
+import weka.core.Capabilities.Capability;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that discretizes a range of numeric attributes in the dataset into nominal attributes. Discretization is by simple binning. Skips the class attribute if set.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ * <pre> -B &lt;num&gt;
+ *  Specifies the (maximum) number of bins to divide numeric attributes into.
+ *  (default = 10)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Specifies the desired weight of instances per bin for
+ *  equal-frequency binning. If this is set to a positive
+ *  number then the -B option will be ignored.
+ *  (default = -1)</pre>
+ * 
+ * <pre> -F
+ *  Use equal-frequency instead of equal-width discretization.</pre>
+ * 
+ * <pre> -O
+ *  Optimize number of bins using leave-one-out estimate
+ *  of estimated entropy (for equal-width discretization).
+ *  If this is set then the -B option will be ignored.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to Discretize. First and last are valid indexes.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ * <pre> -D
+ *  Output binary attributes for discretized attributes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Discretize 
+  extends PotentialClassIgnorer 
+  implements UnsupervisedFilter, WeightedInstancesHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1358531742174527279L;
+
+  /** Stores which columns to Discretize */
+  protected Range m_DiscretizeCols = new Range();
+
+  /** The number of bins to divide the attribute into */
+  protected int m_NumBins = 10;
+
+  /** The desired weight of instances per bin */
+  protected double m_DesiredWeightOfInstancesPerInterval = -1;
+
+  /** Store the current cutpoints */
+  protected double [][] m_CutPoints = null;
+
+  /** Output binary attributes for discretized attributes. */
+  protected boolean m_MakeBinary = false;
+
+  /** Find the number of bins using cross-validated entropy. */
+  protected boolean m_FindNumBins = false;
+
+  /** Use equal-frequency binning if unsupervised discretization turned on */
+  protected boolean m_UseEqualFrequency = false;
+
+  /** The default columns to discretize */
+  protected String m_DefaultCols;
+
+  /** Constructor - initialises the filter */
+  public Discretize() {
+
+    m_DefaultCols = "first-last";
+    setAttributeIndices("first-last");
+  }
+
+  /** 
+   * Another constructor, sets the attribute indices immediately
+   * 
+   * @param cols the attribute indices
+   */
+  public Discretize(String cols) {
+
+    m_DefaultCols = cols;
+    setAttributeIndices(cols);
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+	"\tSpecifies the (maximum) number of bins to divide numeric"
+	+ " attributes into.\n"
+	+ "\t(default = 10)",
+	"B", 1, "-B <num>"));
+    
+    result.addElement(new Option(
+	"\tSpecifies the desired weight of instances per bin for\n"
+	+ "\tequal-frequency binning. If this is set to a positive\n"
+	+ "\tnumber then the -B option will be ignored.\n"
+	+ "\t(default = -1)",
+	"M", 1, "-M <num>"));
+    
+    result.addElement(new Option(
+	"\tUse equal-frequency instead of equal-width discretization.",
+	"F", 0, "-F"));
+    
+    result.addElement(new Option(
+	"\tOptimize number of bins using leave-one-out estimate\n"+
+	"\tof estimated entropy (for equal-width discretization).\n"+
+	"\tIf this is set then the -B option will be ignored.",
+	"O", 0, "-O"));
+    
+    result.addElement(new Option(
+	"\tSpecifies list of columns to Discretize. First"
+	+ " and last are valid indexes.\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+    
+    result.addElement(new Option(
+	"\tInvert matching sense of column indexes.",
+	"V", 0, "-V"));
+    
+    result.addElement(new Option(
+	"\tOutput binary attributes for discretized attributes.",
+	"D", 0, "-D"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -unset-class-temporarily
+   *  Unsets the class index temporarily before the filter is
+   *  applied to the data.
+   *  (default: no)</pre>
+   * 
+   * <pre> -B &lt;num&gt;
+   *  Specifies the (maximum) number of bins to divide numeric attributes into.
+   *  (default = 10)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Specifies the desired weight of instances per bin for
+   *  equal-frequency binning. If this is set to a positive
+   *  number then the -B option will be ignored.
+   *  (default = -1)</pre>
+   * 
+   * <pre> -F
+   *  Use equal-frequency instead of equal-width discretization.</pre>
+   * 
+   * <pre> -O
+   *  Optimize number of bins using leave-one-out estimate
+   *  of estimated entropy (for equal-width discretization).
+   *  If this is set then the -B option will be ignored.</pre>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to Discretize. First and last are valid indexes.
+   *  (default: first-last)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense of column indexes.</pre>
+   * 
+   * <pre> -D
+   *  Output binary attributes for discretized attributes.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    super.setOptions(options);
+
+    setMakeBinary(Utils.getFlag('D', options));
+    setUseEqualFrequency(Utils.getFlag('F', options));
+    setFindNumBins(Utils.getFlag('O', options));
+    setInvertSelection(Utils.getFlag('V', options));
+
+    String weight = Utils.getOption('M', options);
+    if (weight.length() != 0) {
+      setDesiredWeightOfInstancesPerInterval((new Double(weight)).doubleValue());
+    } else {
+      setDesiredWeightOfInstancesPerInterval(-1);
+    }
+
+    String numBins = Utils.getOption('B', options);
+    if (numBins.length() != 0) {
+      setBins(Integer.parseInt(numBins));
+    } else {
+      setBins(10);
+    }
+    
+    String convertList = Utils.getOption('R', options);
+    if (convertList.length() != 0) {
+      setAttributeIndices(convertList);
+    } else {
+      setAttributeIndices(m_DefaultCols);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (getMakeBinary())
+      result.add("-D");
+    
+    if (getUseEqualFrequency())
+      result.add("-F");
+    
+    if (getFindNumBins())
+      result.add("-O");
+    
+    if (getInvertSelection())
+      result.add("-V");
+    
+    result.add("-B");
+    result.add("" + getBins());
+    
+    result.add("-M");
+    result.add("" + getDesiredWeightOfInstancesPerInterval());
+    
+    if (!getAttributeIndices().equals("")) {
+      result.add("-R");
+      result.add(getAttributeIndices());
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    if (!getMakeBinary())
+      result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    if (m_MakeBinary && m_IgnoreClass) {
+      throw new IllegalArgumentException("Can't ignore class when " +
+					 "changing the number of attributes!");
+    }
+
+    super.setInputFormat(instanceInfo);
+
+    m_DiscretizeCols.setUpper(instanceInfo.numAttributes() - 1);
+    m_CutPoints = null;
+    
+    if (getFindNumBins() && getUseEqualFrequency()) {
+      throw new IllegalArgumentException("Bin number optimization in conjunction "+
+					 "with equal-frequency binning not implemented.");
+    }
+
+    // If we implement loading cutfiles, then load 
+    //them here and set the output format
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (m_CutPoints != null) {
+      convertInstance(instance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Signifies that this batch of input to the filter is finished. If the 
+   * filter requires all instances prior to filtering, output() may now 
+   * be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_CutPoints == null) {
+      calculateCutPoints();
+
+      setOutputFormat();
+
+      // If we implement saving cutfiles, save the cuts here
+
+      // Convert pending input instances
+      for(int i = 0; i < getInputFormat().numInstances(); i++) {
+	convertInstance(getInputFormat().instance(i));
+      }
+    } 
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "An instance filter that discretizes a range of numeric"
+      + " attributes in the dataset into nominal attributes."
+      + " Discretization is by simple binning. Skips the class"
+      + " attribute if set.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String findNumBinsTipText() {
+
+    return "Optimize number of equal-width bins using leave-one-out. Doesn't " +
+      "work for equal-frequency binning";
+  }
+
+  /**
+   * Get the value of FindNumBins.
+   *
+   * @return Value of FindNumBins.
+   */
+  public boolean getFindNumBins() {
+    
+    return m_FindNumBins;
+  }
+  
+  /**
+   * Set the value of FindNumBins.
+   *
+   * @param newFindNumBins Value to assign to FindNumBins.
+   */
+  public void setFindNumBins(boolean newFindNumBins) {
+    
+    m_FindNumBins = newFindNumBins;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String makeBinaryTipText() {
+
+    return "Make resulting attributes binary.";
+  }
+
+  /**
+   * Gets whether binary attributes should be made for discretized ones.
+   *
+   * @return true if attributes will be binarized
+   */
+  public boolean getMakeBinary() {
+
+    return m_MakeBinary;
+  }
+
+  /** 
+   * Sets whether binary attributes should be made for discretized ones.
+   *
+   * @param makeBinary if binary attributes are to be made
+   */
+  public void setMakeBinary(boolean makeBinary) {
+
+    m_MakeBinary = makeBinary;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String desiredWeightOfInstancesPerIntervalTipText() {
+
+    return "Sets the desired weight of instances per interval for " +
+      "equal-frequency binning.";
+  }
+  
+  /**
+   * Get the DesiredWeightOfInstancesPerInterval value.
+   * @return the DesiredWeightOfInstancesPerInterval value.
+   */
+  public double getDesiredWeightOfInstancesPerInterval() {
+
+    return m_DesiredWeightOfInstancesPerInterval;
+  }
+
+  /**
+   * Set the DesiredWeightOfInstancesPerInterval value.
+   * @param newDesiredNumber The new DesiredNumber value.
+   */
+  public void setDesiredWeightOfInstancesPerInterval(double newDesiredNumber) {
+    
+    m_DesiredWeightOfInstancesPerInterval = newDesiredNumber;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useEqualFrequencyTipText() {
+
+    return "If set to true, equal-frequency binning will be used instead of" +
+      " equal-width binning.";
+  }
+  
+  /**
+   * Get the value of UseEqualFrequency.
+   *
+   * @return Value of UseEqualFrequency.
+   */
+  public boolean getUseEqualFrequency() {
+    
+    return m_UseEqualFrequency;
+  }
+  
+  /**
+   * Set the value of UseEqualFrequency.
+   *
+   * @param newUseEqualFrequency Value to assign to UseEqualFrequency.
+   */
+  public void setUseEqualFrequency(boolean newUseEqualFrequency) {
+    
+    m_UseEqualFrequency = newUseEqualFrequency;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binsTipText() {
+
+    return "Number of bins.";
+  }
+
+  /**
+   * Gets the number of bins numeric attributes will be divided into
+   *
+   * @return the number of bins.
+   */
+  public int getBins() {
+
+    return m_NumBins;
+  }
+
+  /**
+   * Sets the number of bins to divide each selected numeric attribute into
+   *
+   * @param numBins the number of bins
+   */
+  public void setBins(int numBins) {
+
+    m_NumBins = numBins;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Set attribute selection mode. If false, only selected"
+      + " (numeric) attributes in the range will be discretized; if"
+      + " true, only non-selected attributes will be discretized.";
+  }
+
+  /**
+   * Gets whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_DiscretizeCols.getInvert();
+  }
+
+  /**
+   * Sets whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_DiscretizeCols.setInvert(invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_DiscretizeCols.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be Discretized (only numeric
+   * attributes among the selection will be Discretized).
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String rangeList) {
+
+    m_DiscretizeCols.setRanges(rangeList);
+  }
+
+  /**
+   * Sets which attributes are to be Discretized (only numeric
+   * attributes among the selection will be Discretized).
+   *
+   * @param attributes an array containing indexes of attributes to Discretize.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws IllegalArgumentException if an invalid set of ranges
+   * is supplied 
+   */
+  public void setAttributeIndicesArray(int [] attributes) {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+
+  /**
+   * Gets the cut points for an attribute
+   *
+   * @param attributeIndex the index (from 0) of the attribute to get the cut points of
+   * @return an array containing the cutpoints (or null if the
+   * attribute requested has been discretized into only one interval.)
+   */
+  public double [] getCutPoints(int attributeIndex) {
+
+    if (m_CutPoints == null) {
+      return null;
+    }
+    return m_CutPoints[attributeIndex];
+  }
+
+  /** Generate the cutpoints for each attribute */
+  protected void calculateCutPoints() {
+
+    m_CutPoints = new double [getInputFormat().numAttributes()] [];
+    for(int i = getInputFormat().numAttributes() - 1; i >= 0; i--) {
+      if ((m_DiscretizeCols.isInRange(i)) && 
+	  (getInputFormat().attribute(i).isNumeric()) &&
+	  (getInputFormat().classIndex() != i)) {
+	if (m_FindNumBins) {
+	  findNumBins(i);
+	} else if (!m_UseEqualFrequency) {
+	  calculateCutPointsByEqualWidthBinning(i);
+	} else {
+	  calculateCutPointsByEqualFrequencyBinning(i);
+	}
+      }
+    }
+  }
+ 
+  /**
+   * Set cutpoints for a single attribute.
+   *
+   * @param index the index of the attribute to set cutpoints for
+   */
+  protected void calculateCutPointsByEqualWidthBinning(int index) {
+
+    // Scan for max and min values
+    double max = 0, min = 1, currentVal;
+    Instance currentInstance;
+    for(int i = 0; i < getInputFormat().numInstances(); i++) {
+      currentInstance = getInputFormat().instance(i);
+      if (!currentInstance.isMissing(index)) {
+	currentVal = currentInstance.value(index);
+	if (max < min) {
+	  max = min = currentVal;
+	}
+	if (currentVal > max) {
+	  max = currentVal;
+	}
+	if (currentVal < min) {
+	  min = currentVal;
+	}
+      }
+    }
+    double binWidth = (max - min) / m_NumBins;
+    double [] cutPoints = null;
+    if ((m_NumBins > 1) && (binWidth > 0)) {
+      cutPoints = new double [m_NumBins - 1];
+      for(int i = 1; i < m_NumBins; i++) {
+	cutPoints[i - 1] = min + binWidth * i;
+      }
+    }
+    m_CutPoints[index] = cutPoints;
+  }
+ 
+  /**
+   * Set cutpoints for a single attribute.
+   *
+   * @param index the index of the attribute to set cutpoints for
+   */
+  protected void calculateCutPointsByEqualFrequencyBinning(int index) {
+
+    // Copy data so that it can be sorted
+    Instances data = new Instances(getInputFormat());
+
+    // Sort input data
+    data.sort(index);
+
+    // Compute weight of instances without missing values
+    double sumOfWeights = 0;
+    for (int i = 0; i < data.numInstances(); i++) {
+      if (data.instance(i).isMissing(index)) {
+	break;
+      } else {
+	sumOfWeights += data.instance(i).weight();
+      }
+    }
+    double freq;
+    double[] cutPoints = new double[m_NumBins - 1];
+    if (getDesiredWeightOfInstancesPerInterval() > 0) {
+      freq = getDesiredWeightOfInstancesPerInterval();
+      cutPoints = new double[(int)(sumOfWeights / freq)];
+    } else {
+      freq = sumOfWeights / m_NumBins;
+      cutPoints = new double[m_NumBins - 1];
+    }
+
+    // Compute break points
+    double counter = 0, last = 0;
+    int cpindex = 0, lastIndex = -1;
+    for (int i = 0; i < data.numInstances() - 1; i++) {
+
+      // Stop if value missing
+      if (data.instance(i).isMissing(index)) {
+	break;
+      }
+      counter += data.instance(i).weight();
+      sumOfWeights -= data.instance(i).weight();
+
+      // Do we have a potential breakpoint?
+      if (data.instance(i).value(index) < 
+	  data.instance(i + 1).value(index)) {
+
+	// Have we passed the ideal size?
+	if (counter >= freq) {
+
+	  // Is this break point worse than the last one?
+	  if (((freq - last) < (counter - freq)) && (lastIndex != -1)) {
+	    cutPoints[cpindex] = (data.instance(lastIndex).value(index) +
+				  data.instance(lastIndex + 1).value(index)) / 2;
+	    counter -= last;
+	    last = counter;
+	    lastIndex = i;
+	  } else {
+	    cutPoints[cpindex] = (data.instance(i).value(index) +
+				  data.instance(i + 1).value(index)) / 2;
+	    counter = 0;
+	    last = 0;
+	    lastIndex = -1;
+	  }
+	  cpindex++;
+	  freq = (sumOfWeights + counter) / ((cutPoints.length + 1) - cpindex);
+	} else {
+	  lastIndex = i;
+	  last = counter;
+	}
+      }
+    }
+
+    // Check whether there was another possibility for a cut point
+    if ((cpindex < cutPoints.length) && (lastIndex != -1)) {
+      cutPoints[cpindex] = (data.instance(lastIndex).value(index) +
+			    data.instance(lastIndex + 1).value(index)) / 2;      
+      cpindex++;
+    }
+
+    // Did we find any cutpoints?
+    if (cpindex == 0) {
+      m_CutPoints[index] = null;
+    } else {
+      double[] cp = new double[cpindex];
+      for (int i = 0; i < cpindex; i++) {
+	cp[i] = cutPoints[i];
+      }
+      m_CutPoints[index] = cp;
+    }
+  }
+
+  /**
+   * Optimizes the number of bins using leave-one-out cross-validation.
+   *
+   * @param index the attribute index
+   */
+  protected void findNumBins(int index) {
+
+    double min = Double.MAX_VALUE, max = -Double.MAX_VALUE, binWidth = 0, 
+      entropy, bestEntropy = Double.MAX_VALUE, currentVal;
+    double[] distribution;
+    int bestNumBins  = 1;
+    Instance currentInstance;
+
+    // Find minimum and maximum
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      currentInstance = getInputFormat().instance(i);
+      if (!currentInstance.isMissing(index)) {
+	currentVal = currentInstance.value(index);
+	if (currentVal > max) {
+	  max = currentVal;
+	}
+	if (currentVal < min) {
+	  min = currentVal;
+	}
+      }
+    }
+
+    // Find best number of bins
+    for (int i = 0; i < m_NumBins; i++) {
+      distribution = new double[i + 1];
+      binWidth = (max - min) / (i + 1);
+
+      // Compute distribution
+      for (int j = 0; j < getInputFormat().numInstances(); j++) {
+	currentInstance = getInputFormat().instance(j);
+	if (!currentInstance.isMissing(index)) {
+	  for (int k = 0; k < i + 1; k++) {
+	    if (currentInstance.value(index) <= 
+		(min + (((double)k + 1) * binWidth))) {
+	      distribution[k] += currentInstance.weight();
+	      break;
+	    }
+	  }
+	}
+      }
+
+      // Compute cross-validated entropy
+      entropy = 0;
+      for (int k = 0; k < i + 1; k++) {
+	if (distribution[k] < 2) {
+	  entropy = Double.MAX_VALUE;
+	  break;
+	}
+	entropy -= distribution[k] * Math.log((distribution[k] - 1) / 
+					      binWidth);
+      }
+
+      // Best entropy so far?
+      if (entropy < bestEntropy) {
+	bestEntropy = entropy;
+	bestNumBins = i + 1;
+      }
+    }
+
+    // Compute cut points
+    double [] cutPoints = null;
+    if ((bestNumBins > 1) && (binWidth > 0)) {
+      cutPoints = new double [bestNumBins - 1];
+      for(int i = 1; i < bestNumBins; i++) {
+	cutPoints[i - 1] = min + binWidth * i;
+      }
+    }
+    m_CutPoints[index] = cutPoints;
+   }
+
+  /**
+   * Set the output format. Takes the currently defined cutpoints and 
+   * m_InputFormat and calls setOutputFormat(Instances) appropriately.
+   */
+  protected void setOutputFormat() {
+
+    if (m_CutPoints == null) {
+      setOutputFormat(null);
+      return;
+    }
+    FastVector attributes = new FastVector(getInputFormat().numAttributes());
+    int classIndex = getInputFormat().classIndex();
+    for(int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if ((m_DiscretizeCols.isInRange(i)) 
+	  && (getInputFormat().attribute(i).isNumeric())
+	  && (getInputFormat().classIndex() != i)) {
+	if (!m_MakeBinary) {
+	  FastVector attribValues = new FastVector(1);
+	  if (m_CutPoints[i] == null) {
+	    attribValues.addElement("'All'");
+	  } else {
+	    for(int j = 0; j <= m_CutPoints[i].length; j++) {
+	      if (j == 0) {
+		attribValues.addElement("'(-inf-"
+			+ Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      } else if (j == m_CutPoints[i].length) {
+		attribValues.addElement("'("
+			+ Utils.doubleToString(m_CutPoints[i][j - 1], 6) 
+					+ "-inf)'");
+	      } else {
+		attribValues.addElement("'("
+			+ Utils.doubleToString(m_CutPoints[i][j - 1], 6) + "-"
+			+ Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      }
+	    }
+	  }
+	  attributes.addElement(new Attribute(getInputFormat().
+					      attribute(i).name(),
+					      attribValues));
+	} else {
+	  if (m_CutPoints[i] == null) {
+	    FastVector attribValues = new FastVector(1);
+	    attribValues.addElement("'All'");
+	    attributes.addElement(new Attribute(getInputFormat().
+						attribute(i).name(),
+						attribValues));
+	  } else {
+	    if (i < getInputFormat().classIndex()) {
+	      classIndex += m_CutPoints[i].length - 1;
+	    }
+	    for(int j = 0; j < m_CutPoints[i].length; j++) {
+	      FastVector attribValues = new FastVector(2);
+	      attribValues.addElement("'(-inf-"
+		      + Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
+	      attribValues.addElement("'("
+		      + Utils.doubleToString(m_CutPoints[i][j], 6) + "-inf)'");
+	      attributes.addElement(new Attribute(getInputFormat().
+						  attribute(i).name(),
+						  attribValues));
+	    }
+	  }
+	}
+      } else {
+	attributes.addElement(getInputFormat().attribute(i).copy());
+      }
+    }
+    Instances outputFormat = 
+      new Instances(getInputFormat().relationName(), attributes, 0);
+    outputFormat.setClassIndex(classIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is added to 
+   * the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  protected void convertInstance(Instance instance) {
+
+    int index = 0;
+    double [] vals = new double [outputFormatPeek().numAttributes()];
+    // Copy and convert the values
+    for(int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if (m_DiscretizeCols.isInRange(i) && 
+	  getInputFormat().attribute(i).isNumeric() &&
+	  (getInputFormat().classIndex() != i)) {
+	int j;
+	double currentVal = instance.value(i);
+	if (m_CutPoints[i] == null) {
+	  if (instance.isMissing(i)) {
+	    vals[index] = Utils.missingValue();
+	  } else {
+	    vals[index] = 0;
+	  }
+	  index++;
+	} else {
+	  if (!m_MakeBinary) {
+	    if (instance.isMissing(i)) {
+	      vals[index] = Utils.missingValue();
+	    } else {
+	      for (j = 0; j < m_CutPoints[i].length; j++) {
+		if (currentVal <= m_CutPoints[i][j]) {
+		  break;
+		}
+	      }
+              vals[index] = j;
+	    }
+	    index++;
+	  } else {
+	    for (j = 0; j < m_CutPoints[i].length; j++) {
+	      if (instance.isMissing(i)) {
+                vals[index] = Utils.missingValue();
+	      } else if (currentVal <= m_CutPoints[i][j]) {
+                vals[index] = 0;
+	      } else {
+                vals[index] = 1;
+	      }
+	      index++;
+	    }
+	  }   
+	}
+      } else {
+        vals[index] = instance.value(i);
+	index++;
+      }
+    }
+    
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Discretize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/EMImputation.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/EMImputation.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/EMImputation.java	(revision 29)
@@ -0,0 +1,1169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EMImputation.java
+ *    Copyright (C) 2009 Amri Napolitano
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleBatchFilter;
+import weka.filters.UnsupervisedFilter;
+import weka.core.matrix.Matrix;
+import weka.filters.Filter;
+import weka.experiment.Stats;
+
+/** 
+ <!-- globalinfo-start -->
+ * Replaces missing numeric values using Expectation Maximization with a multivariate normal model. Described in " Schafer, J.L. Analysis of Incomplete Multivariate Data, New York: Chapman and Hall, 1997."
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Maximum number of iterations for Expectation 
+ *  Maximization. (-1 = no maximum)</pre>
+ * 
+ * <pre> -E
+ *  Threshold for convergence in Expectation 
+ *  Maximization. If the change in the observed data 
+ *  log-likelihood (posterior density if a ridge prior 
+ *   is being used) across iterations is no more than 
+ *  this value, then convergence is considered to be 
+ *  achieved and the iterative process is ceased. 
+ *  (default = 0.0001)</pre>
+ * 
+ * <pre> -P
+ *  Use a ridge prior instead of the noninformative 
+ *  prior. This helps when the data has a singular 
+ *  covariance matrix.</pre>
+ * 
+ * <pre> -Q
+ *  The ridge parameter for when a ridge prior is 
+ *  used.</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Amri Napolitano 
+ * @version $Revision: 5987 $
+ */
+public class EMImputation 
+extends SimpleBatchFilter
+implements UnsupervisedFilter {
+  
+  /** for serialization */
+  static final long serialVersionUID = -2519262133734188184L;
+  
+  /** Number of EM iterations */
+  private int m_numIterations = -1;
+  
+  /** Threshold for convergence of EM */
+  private double m_LogLikelihoodThreshold = 1e-4;
+  
+  /** Whether to use a ridge prior */
+  private boolean m_ridgePrior = false;
+  
+  /** Ridge value for ridge prior */
+  private double m_ridge = 1.0e-8;
+  
+  /** Number of attributes to be involved in imputation */
+  private int m_numAttributes;
+  
+  /** Means of original data (for standardization) */
+  private double [] m_means = null;
+  
+  /** Standard deviations of original data (for standardization) */
+  private double [] m_stdDevs = null;
+  
+  /** Filter to remove unused attributes */
+  private Remove m_unusedAttributeRemover = null;
+  
+  /** Flags indicating if attributes won't be used for imputation */
+  private boolean [] m_unusedAtts = null;
+  
+  /** Parameters of data (for imputation of future instances) */
+  private Matrix m_theta = null;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    
+    return "Replaces missing numeric values using Expectation Maximization with a" +
+        " multivariate normal model. Described in \" Schafer, J.L. Analysis of" +
+        " Incomplete Multivariate Data, New York: Chapman and Hall, 1997.\"";
+  }
+  
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+    
+    // attributes
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options. <p>
+   *
+   * @return an enumeration of all the available options.
+   **/
+  public Enumeration listOptions () {
+    Vector options = new Vector(4);
+    options.addElement(new Option("\tMaximum number of iterations for Expectation \n" +
+                                  "\tMaximization. (-1 = no maximum)", "N", 1, "-N"));
+    
+    options.addElement(new Option("\tThreshold for convergence in Expectation \n" +
+                                  "\tMaximization. If the change in the observed data \n" +
+                                  "\tlog-likelihood (posterior density if a ridge prior \n" +
+                                  "\t is being used) across iterations is no more than \n" +
+                                  "\tthis value, then convergence is considered to be \n" +
+                                  "\tachieved and the iterative process is ceased. \n" +
+                                  "\t(default = 0.0001)", "E",1,"-E"));
+    
+    options.addElement(new Option("\tUse a ridge prior instead of the noninformative \n" +
+                                  "\tprior. This helps when the data has a singular \n" +
+                                  "\tcovariance matrix.", "P", 0, "-P"));
+    options.addElement(new Option("\tThe ridge parameter for when a ridge prior is \n" +
+                                  "\tused.", "Q", 1, "-Q"));
+    return  options.elements();
+  }
+  
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Maximum number of iterations for Expectation 
+   *  Maximization. (-1 = no maximum)</pre>
+   * 
+   * <pre> -E
+   *  Threshold for convergence in Expectation 
+   *  Maximization. If the change in the observed data 
+   *  log-likelihood (posterior density if a ridge prior 
+   *   is being used) across iterations is no more than 
+   *  this value, then convergence is considered to be 
+   *  achieved and the iterative process is ceased. 
+   *  (default = 0.0001)</pre>
+   * 
+   * <pre> -P
+   *  Use a ridge prior instead of the noninformative 
+   *  prior. This helps when the data has a singular 
+   *  covariance matrix.</pre>
+   * 
+   * <pre> -Q
+   *  The ridge parameter for when a ridge prior is 
+   *  used.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions (String[] options)
+  throws Exception {
+    String optionString;
+    
+    // set # EM iterations
+    optionString = Utils.getOption('N', options);
+    if (optionString.length() != 0) {
+      setNumIterations(Integer.parseInt(optionString));
+    }
+    // set log-likelihood EM convergence threshold
+    optionString = Utils.getOption('E', options);
+    if (optionString.length() != 0) {
+      setLogLikelihoodThreshold(Double.valueOf(optionString).doubleValue());
+    }
+    // set whether to use ridge prior
+    setUseRidgePrior(Utils.getFlag('P', options));
+    // set ridge parameter
+    optionString = Utils.getOption('Q', options);
+    if (optionString.length() != 0) {
+      setRidge(Double.valueOf(optionString));
+    }
+  }
+  
+  /**
+   * Gets the current settings of EMImputation
+   *
+   * @return an array of strings suitable for passing to setOptions()
+   */
+  public String[] getOptions () {
+    
+    String[] options = new String[7];
+    int current = 0;
+    
+    options[current++] = "-N";
+    options[current++] = "" + getNumIterations();
+    
+    options[current++] = "-E";
+    options[current++] = "" + getLogLikelihoodThreshold();
+    
+    options[current++] = "-Q";
+    options[current++] = "" + getRidge();
+    
+    if(getUseRidgePrior()) {
+      options[current++] = "-P";
+    }
+    
+    while(current < options.length) {
+      options[current++] = "";
+    }
+    
+    return  options;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numIterationsTipText() {
+    return "Maximum number of iterations for Expectation Maximization. " +
+            "EM is used to initialize the parameters of the multivariate normal " +
+            "distribution. (-1 = no maximum)";
+  }
+  
+  /**
+   * Sets the maximum number of EM iterations
+   * @param newIterations the maximum number of EM iterations
+   */
+  public void setNumIterations(int newIterations) {
+      m_numIterations = newIterations;
+  }
+  
+  /**
+   * Gets the maximum number of EM iterations
+   * @return the maximum number of EM iterations
+   */
+  public int getNumIterations() {
+    return m_numIterations;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String logLikelihoodThresholdTipText() {
+    return "Log-likelihood threshold for convergence in Expectation Maximization. " +
+            "If the change in the observed data log-likelihood across iterations " +
+            "is no more than this value, then convergence is considered to be " +
+            "achieved and the iterative process is ceased. (default = 0.0001)";
+  }
+  
+  /**
+   * Sets the EM log-likelihood convergence threshold
+   * @param newThreshold the EM log-likelihood convergence threshold
+   */
+  public void setLogLikelihoodThreshold(double newThreshold) {
+      m_LogLikelihoodThreshold = newThreshold;
+  }
+  
+  /**
+   * Gets the EM log-likelihood convergence threshold
+   * @return the EM log-likelihood convergence threshold
+   */
+  public double getLogLikelihoodThreshold() {
+    return m_LogLikelihoodThreshold;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return    tip text for this property suitable for
+   *      displaying in the explorer/experimenter gui
+   */
+  public String useRidgePriorTipText() {
+    return "Use a ridge prior instead of noninformative prior.";
+  }
+
+  /**
+   * Get whether to use a ridge prior.
+   *
+   * @return    whether to use a ridge prior.
+   */
+  public boolean getUseRidgePrior() {
+    return m_ridgePrior;
+  }
+
+  /**
+   * Set whether to use a ridge prior. 
+   *
+   * @param prior whether to use a ridge prior.
+   */
+  public void setUseRidgePrior(boolean prior) {
+    m_ridgePrior = prior;
+  }
+  
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return    tip text for this property suitable for
+   *      displaying in the explorer/experimenter gui
+   */
+  public String ridgeTipText() {
+    return "Ridge parameter for ridge prior.";
+  }
+
+  /**
+   * Get ridge parameter.
+   *
+   * @return   the ridge parameter
+   */
+  public double getRidge() {
+    return m_ridge;
+  }
+
+  /**
+   * Set ridge parameter 
+   *
+   * @param ridge new ridge parameter
+   */
+  public void setRidge(double ridge) {
+    m_ridge = ridge;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+  throws Exception {
+    
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) {
+    
+    return inputFormat;
+  }
+  
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the arguments are inappropriate or the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    
+    int numInstances = instances.numInstances();
+    int numAttributes = instances.numAttributes();
+    int classIndex = instances.classIndex();
+    // if only one numeric attribute, don't do procedure
+    if (((classIndex < 0 || instances.classAttribute().isNumeric()) && 
+        numAttributes < 2) || numAttributes < 3) {
+      throw new Exception("Must have 2 or more numeric attributes for EM Imputation");     
+    }
+    
+    // check for only numeric independent attributes
+    for (int i = 0; i < numAttributes; i++) {
+      if (instances.attribute(i).isNumeric() == false && instances.classIndex() != i) {
+        throw new Exception("EM Imputation can only handle numeric attributes");
+      }
+    }
+    
+    // check inputs for ok values
+    if (m_LogLikelihoodThreshold < 0) { // if loglikelihood threshold is negative, convergence would never occur
+      throw new Exception("Log-likelihood threshold must be non-negative.");
+    }
+    if (m_ridgePrior == true && m_ridge <= 0) {
+      throw new Exception("Ridge parameter should be positive.");
+    }
+    
+    // create datasets ready for processing
+    Instances preprocessed, dataWithClass;
+    Instances [] datasets = null;
+    if (isFirstBatchDone() == false) { // model hasn't been built yet
+      // check that n > p + 1
+      if (numInstances < numAttributes + 2) {
+        throw new Exception("EMImputation: Number of instances must be >= number of attributes + 2");
+      }
+      datasets = prepareData(instances, true);
+    } else { // model has been built
+      datasets = prepareData(instances, false);
+    }
+    preprocessed = datasets[0]; // holds data sorted by missing data pattern with attribute about pattern
+    dataWithClass = datasets[1]; // holds data in same sorted order without the class removed
+    
+    // build model if it hasn't been built yet
+    if (isFirstBatchDone() == false) {
+      // get matrix of complete-data sufficient statistics, T, for observed values
+      Matrix t_obs = getTObs(preprocessed);
+      
+      // perform expectation maximization to determine model parameters
+      m_theta = EM(preprocessed, t_obs);
+    }
+
+    // fill in missing values from model
+    impute(preprocessed, m_theta);
+    
+    // produce final output dataset
+    dataWithClass = postProcessData(preprocessed, dataWithClass);
+    
+    if (dataWithClass.numInstances() != 0) {
+      return dataWithClass;
+    } else {
+      return instances;
+    }
+  }
+  
+  /**
+   * Standardizes a (preprocessed) dataset using m_means and m_stdDevs.
+   * @param data        dataset to standardize
+   */
+  private void standardize(Instances data) {
+    
+    for (int i = 0; i < data.numInstances(); i++) {
+      for (int j = 1; j < m_numAttributes + 1; j++) {
+        if (data.instance(i).isMissing(j) == false) {
+          double value = data.instance(i).value(j);
+            data.instance(i).setValue(j, (value - m_means[j - 1]) / m_stdDevs[j - 1]);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Preprocesses data to be used for EM
+   * @param data            the data to be processed
+   * @return preprocessed   the prepared data
+   * @throws Exception      if processing goes wrong
+   */
+  private Instances prepareMissing(Instances data) throws Exception {
+    
+    // properties of data
+    int numInstances = data.numInstances();
+    int numAttributes = data.numAttributes();
+    
+    Instances preprocessed = new Instances(data, data.numInstances());
+    preprocessed.insertAttributeAt(new Attribute("NumInPattern"), 0);
+    // add instances to preprocessed sorted by missingness pattern
+    int currentPatternStart = 0; // index of 1st instance in current pattern
+    int numInCurrentPattern = 0; // number of instances with current pattern
+    int totalAdded = 0; // total instances added to new dataset
+    boolean [] addedAlready = new boolean[numInstances];
+    boolean [] missing = new boolean[numAttributes]; // missing columns
+    
+    while (totalAdded < numInstances) { // until all instances have been added
+      // set up pattern to find
+      numInCurrentPattern = 0;
+      int numMissing = 0;
+      int newPatternIndex = -1; // index of instance with new pattern
+      for (int i = 0; newPatternIndex == -1 && i < numInstances; i++) {
+        if (!addedAlready[i]) {
+          newPatternIndex = i;
+          addedAlready[i] = true;
+          numInCurrentPattern++;
+          totalAdded++;
+        }
+      }
+      // store new missing pattern
+      for (int j = 0; j < numAttributes; j++) {
+        if (m_unusedAtts[j] == true) {
+          numMissing++;
+          continue;
+        }
+        if (data.instance(newPatternIndex).isMissing(j)) {
+          missing[j] = true;
+          numMissing++;
+        } else {
+          missing[j] = false;
+        }
+      }
+      if (numMissing == numAttributes) { // all attributes are missing - don't use
+        continue;
+      }
+      
+      // create modified instance and add it to the new dataset
+      double [] attributes = new double[numAttributes + 1];
+      for (int j = 0; j < numAttributes; j++) {
+        attributes[j + 1] = data.instance(newPatternIndex).value(j);
+      }
+      Instance newInstance = new DenseInstance(data.instance(newPatternIndex).weight(), attributes);
+      preprocessed.add(newInstance);
+        
+      // go through all remaining instances to find those with same pattern
+      for (int i = 0; i < numInstances; i++) {
+        if (addedAlready[i]) {
+          continue;
+        }
+        // go through all the attributes to see if they match the pattern
+        boolean match = true; // if current instance matches the pattern
+        for (int j = 0; j < numAttributes && match == true; j++) {
+          if (m_unusedAtts[j] == true) {
+            continue;
+          }
+          if (data.instance(i).isMissing(j) != missing[j]) {
+            match = false; // not a matching pattern
+          }
+        }
+        if (match == true) { // if pattern matches
+          // create modified instance and add it to the new dataset
+          attributes = new double[numAttributes + 1];
+          for (int l = 0; l < numAttributes; l++) {
+            attributes[l + 1] = data.instance(i).value(l);
+          }
+          newInstance = new DenseInstance(data.instance(i).weight(), attributes);
+          preprocessed.add(newInstance);
+          
+          // mark current instance added
+          addedAlready[i] = true;
+          
+          // increment number of instances in current pattern and total
+          numInCurrentPattern++;
+          totalAdded++;
+        }
+      } // end iterating through instances remaining in original data
+      
+      // set number of iterations for fully found pattern
+      preprocessed.instance(currentPatternStart).setValue(0, numInCurrentPattern);
+      currentPatternStart += numInCurrentPattern;
+    } // end adding instances to new dataset
+    
+    return preprocessed;
+  }
+  
+  /**
+   * Sets up data for performing imputation. If firstTime is true, it sets some 
+   * initial global parameters (e.g. means/std devs for standardization).
+   * @param originalData    dataset preprocess
+   * @param firstTime       flag for whether or not to set data parameters
+   * @return data           array of preprocessed datasets necessary for other imputation functions
+   * @throws Exception      if processing goes wrong
+   */
+  private Instances [] prepareData(Instances originalData, boolean firstTime) throws Exception {
+    
+    int numAttributes = originalData.numAttributes();
+    int classIndex = originalData.classIndex();
+    
+    // determine attributes that won't be used in the imputation (all missing or all one value or nonnumeric class attribute)
+    if (firstTime == true) {
+      String unusedAttributeIndices = "";
+      m_unusedAtts = new boolean[numAttributes];
+      for (int j = 0; j < numAttributes; j++) {
+        AttributeStats currentStats = originalData.attributeStats(j);
+        if ((classIndex == j && originalData.classAttribute().isNumeric() == false) ||
+            currentStats.distinctCount < 2) {
+          m_unusedAtts[j] = true;
+          unusedAttributeIndices += (j + 2) + ",";
+        } else {
+          m_unusedAtts[j] = false;
+        }
+      }
+      if (unusedAttributeIndices.contentEquals("") == false) {
+        // set up remove filter
+        m_unusedAttributeRemover = new Remove();
+        m_unusedAttributeRemover.setInvertSelection(false);
+        m_unusedAttributeRemover.setAttributeIndices(unusedAttributeIndices);
+      }
+    }
+    
+    // preprocess for working with the missing values
+    Instances preprocessed = prepareMissing(originalData);
+    
+    // remove attributes that won't be used
+    Instances withClass = new Instances(preprocessed, 0, preprocessed.numInstances()); // to store class values
+    if (m_unusedAttributeRemover != null) {
+      m_unusedAttributeRemover.setInputFormat(preprocessed);
+      preprocessed = Filter.useFilter(preprocessed, m_unusedAttributeRemover);
+      preprocessed.setClassIndex(-1);
+    }
+    
+    // if preparing the first time data, set parameters
+    if (firstTime == true) {
+      m_numAttributes = preprocessed.numAttributes() - 1;
+      m_means = new double[m_numAttributes];
+      m_stdDevs = new double[m_numAttributes];
+      for (int i = 1; i < m_numAttributes + 1; i++) {
+        Stats columnStats = preprocessed.attributeStats(i).numericStats;
+        columnStats.calculateDerived();
+        m_means[i - 1] = columnStats.mean;
+        m_stdDevs[i - 1] = columnStats.stdDev;
+      }
+    }
+    
+    // standardize data (to avoid rounding errors)
+    standardize(preprocessed);
+    
+    // return array of datasets
+    Instances [] data = new Instances[2];
+    data[0] = preprocessed;
+    data[1] = withClass;
+    return data;
+  }
+  
+  /**
+   * Postprocesses data to make ready for output
+   * @param imputedData   dataset with final (standardized) imputed values
+   * @param withClass     dataset without class attribute removed
+   * @return withClass    the final imputed, postprocessed data
+   * @throws Exception    if processing goes wrong
+   */
+  private Instances postProcessData(Instances imputedData, Instances withClass) throws Exception {
+    
+    // remove missingPattern attribute from withClass
+    Remove patternRemover = new Remove();
+    patternRemover.setInvertSelection(false);
+    patternRemover.setAttributeIndices("1");
+    patternRemover.setInputFormat(withClass);
+    withClass = Filter.useFilter(withClass, patternRemover);
+    
+    // destandardize imputed values and place in proper spots in data with class
+    for (int i = 0; i < withClass.numInstances(); i++) {
+      int usedAttributes = 0;
+      for (int j = 0; j < withClass.numAttributes(); j++) {
+        if (m_unusedAtts[j] == true) {
+          continue;
+        }
+        if (withClass.instance(i).isMissing(j)) {
+          withClass.instance(i).setValue(j, imputedData.instance(i).value(usedAttributes + 1) 
+                                            * m_stdDevs[usedAttributes] + m_means[usedAttributes]);
+        }
+        usedAttributes++;
+      }
+    }
+    return withClass;
+  }
+  
+  /**
+   * Calculates T_obs (complete data sufficient statistics for the observed values). 
+   * @param data        preprocessed dataset with missing values
+   * @return t          T_obs
+   * @throws Exception  if processing goes wrong
+   */
+  private Matrix getTObs(Instances data) throws Exception {
+    
+    int p = m_numAttributes; // number of columns
+    
+    // matrix of complete-data sufficient statistics for observed values
+    Matrix t = new Matrix(p+1, p+1);
+    
+    // initialize T for observed data (T_obs)
+    int currentPatternStart = 0;
+    while (currentPatternStart < data.numInstances()) {
+      // number of instances in current missingness pattern is held in 1st attribute
+      int numInCurrentPattern = (int) data.instance(currentPatternStart).value(0);
+      t.set(0, 0, t.get(0,0) + numInCurrentPattern);
+      for (int i = 1; i < p + 1; i++) {
+        // if current column is not missing in this pattern
+        if (!data.instance(currentPatternStart).isMissing(i)) {
+          // add values in this column for this pattern
+          for (int k = 0; k < numInCurrentPattern; k++) {
+            t.set(0, i, t.get(0, i) + data.instance(currentPatternStart + k).value(i));
+          }
+          // iterate through columns to add appropriate squares and crossproducts
+          for (int j = i; j < p + 1; j++) {
+            // if this column is also not missing in the current missingness pattern
+            if (!data.instance(currentPatternStart).isMissing(j)) {
+              for (int k = 0; k < numInCurrentPattern; k++) {
+                t.set(i, j, t.get(i, j) + 
+                      data.instance(currentPatternStart + k).value(i) * 
+                      data.instance(currentPatternStart + k).value(j));
+              } // end iterating through instances in currrent missingness pattern
+            }
+          } // end iterating through column indices (inner loop) 
+        }
+      } // end iterating through column indices (outer loop)
+      
+      // move counter to start of next missingness pattern (or end of dataset)
+      currentPatternStart += numInCurrentPattern;
+    } // end iterating through missingness patterns
+    
+    // copy to symmetric lower triangular portion
+    for (int i = 0; i < p + 1; i++) {
+      for (int j = 1; j < p + 1; j++) {
+        t.set(j, i, t.get(i, j));
+      }
+    }
+    
+    return t;
+  }
+  
+  /**
+   * Performs the expectation maximization (EM) algorithm to find the maximum 
+   * likelihood estimate (or posterior mode if ridge prior is being used)
+   * for the multivariate normal parameters of a dataset with missing values. 
+   * @param data          preprocessed dataset with missing values
+   * @param t_obs         the complete data sufficient statistics for the observed values
+   * @return theta        the maximum likelihood estimate for the parameters of the multivariate normal distribution
+   * @throws Exception    if processing goes wrong
+   */
+  private Matrix EM(Instances data, Matrix t_obs) throws Exception {
+    
+    int p = m_numAttributes; // number of columns
+    Matrix theta = new Matrix(p+1, p+1); // parameter matrix
+    
+    // if numIterations is -1, change to largest int
+    int numIterations = m_numIterations;
+    if (numIterations < 0) {
+      numIterations = Integer.MAX_VALUE;
+    }
+    
+    // starting theta value (means and variances of each column, correlations left at zero)
+    // values are standardized so means are 0 and variances are 1
+    theta.set(0, 0, -1);
+    for (int i = 1; i < data.numAttributes(); i++) {
+      theta.set(0, i, 0); // mu_i
+      theta.set(i, 0, 0);
+      theta.set(i, i, 1); // sigma_ii
+    }
+    
+    double likelihood = logLikelihood(data, theta);
+    double deltaLikelihood = Double.MAX_VALUE;
+    for (int i = 0; i < numIterations && deltaLikelihood > m_LogLikelihoodThreshold; i++) {
+      theta = doEMIteration(data, theta, t_obs);
+      double newLikelihood = logLikelihood(data, theta);
+      deltaLikelihood = newLikelihood - likelihood;
+      likelihood = newLikelihood;
+    }
+    
+    return theta;
+  }
+  
+  /**
+   * Performs one iteration of expectation maximization (EM).
+   * @param data          preprocessed dataset with missing values
+   * @param theta         a matrix containing the starting estimate of the multivariate normal parameters
+   * @param t_obs         the complete data sufficient statistics for the observed values
+   * @return theta        the maximum likelihood estimate (or posterior mode for ridge prior) for the parameters of the multivariate normal distribution at the end of the iteration
+   * @throws Exception    if arguments are inappropriate or processing goes wrong
+   */
+  private Matrix doEMIteration(Instances data, Matrix theta, Matrix t_obs) throws Exception {
+    
+    int p = m_numAttributes; // number of columns
+    Matrix t = t_obs.copy();
+    
+    // go through each pattern
+    int currentPatternStart = 0;
+    while (currentPatternStart < data.numInstances()) {
+      // number of instances in current missingness pattern is held in 1st attribute
+      int numInCurrentPattern = (int) data.instance(currentPatternStart).value(0);
+      // set up binary flags for if column is observed in this pattern
+      boolean [] r = new boolean[p + 1];
+      int [] observedColumns = new int[p + 1]; // indices of observed columns
+      int [] missingColumns = new int[p + 1]; // indices of missing columns
+      int numMissing = 0; // number of missing columns
+      int numObserved = 0; // number of observed columns
+      for (int l = 1; l < p + 1; l++) {
+        if (data.instance(currentPatternStart).isMissing(l)) {
+          missingColumns[numMissing++] = l;
+        } else {
+          observedColumns[numObserved++] = l;
+          r[l] = true;
+        }
+      }
+      observedColumns[numObserved] = -1; // list end indicator
+      missingColumns[numMissing] = -1; // list end indicator
+      
+      for (int j = 1; j < p + 1; j++) {
+        if (r[j] == true && theta.get(j, j) > 0) {
+          theta = swp(theta, j);
+        } else if (r[j] == false && theta.get(j, j) < 0) {
+          theta = rsw(theta, j);
+        }
+      }
+      
+      double [] c = new double[p + 1]; // temporary workspace to hold y_ij^*
+      for (int i = 0; i < numInCurrentPattern; i++) {
+        
+        // calculate y_ij^* values
+        for (int jCounter = 0, j = missingColumns[jCounter]; j != -1; 
+             jCounter++, j = missingColumns[jCounter]) {
+          c[j] = theta.get(0, j);
+          
+          for (int kCounter = 0, k = observedColumns[kCounter]; k != -1;
+               kCounter++, k = observedColumns[kCounter]) {
+            c[j] += theta.get(k, j) * data.instance(currentPatternStart + i).value(k);
+          }
+        }
+        
+        // update t
+        for (int jCounter = 0, j = missingColumns[jCounter]; j != -1; 
+             jCounter++, j = missingColumns[jCounter]) {
+          t.set(0, j, t.get(0, j) + c[j]);
+          t.set(j, 0, t.get(0, j));
+          
+          for (int kCounter = 0, k = observedColumns[kCounter]; k != -1;
+               kCounter++, k = observedColumns[kCounter]) {
+            t.set(k, j, t.get(k, j) + c[j] * data.instance(currentPatternStart + i).value(k));
+            t.set(j, k, t.get(k, j));
+          }
+          
+          for (int kCounter = 0, k = missingColumns[kCounter]; k != -1;
+               kCounter++, k = missingColumns[kCounter]) {
+            if (k >= j) {
+              t.set(k, j, t.get(k, j) + theta.get(k, j) + c[k] * c[j]);
+              t.set(j, k, t.get(k, j));
+            }
+          }
+        }
+      }
+      
+      // move counter to start of next missingness pattern (or end of dataset)
+      currentPatternStart += numInCurrentPattern;
+    } // end iterating through missingness patterns
+    
+    // modify complete data sufficient statistics if using ridge prior
+    if (m_ridgePrior) {
+      double n = data.numInstances();
+      double m = m_ridge;
+      Matrix deltaInv = Matrix.identity(m_numAttributes, m_numAttributes).times(m_ridge); // delta^(-1)
+      // sufficient statistics t1 and t2
+      Matrix t1 = t.getMatrix(1, (int)p, 0, 0);
+      Matrix t2 = t.getMatrix(1, (int)p, 1, (int)p);
+      // modified sufficient statistics t1~ and t2~
+      Matrix t1Tilde, t2Tilde;
+      
+      t1Tilde = t1;
+      
+      t2Tilde = t2.minus(t1.times(t1.transpose()).times(1.0 / n));
+      t2Tilde = t2Tilde.plus(deltaInv).times(n / (n + m + p + 2));
+      t2Tilde.plusEquals(t1Tilde.times(t1Tilde.transpose()).times(1.0 / n));
+      
+      // replace t1 and t2 in t with t1~ and t2~
+      t.setMatrix(1, (int)p, 0, 0, t1Tilde); // replacing t1 in first row
+      t.setMatrix(0, 0, 1, (int)p, t1Tilde.transpose()); // replacing t1' in first column
+      t.setMatrix(1, (int)p, 1, (int)p, t2Tilde); // replacing t2
+    }
+    
+    return swp(t.times(1.0 / data.numInstances()), 0);
+  }
+  
+  /**
+   * Calculates the observed data log-likelihood for theta (or observed data log posterior 
+   * density if ridge prior is being used).
+   * @param data          numeric dataset with instances ordered by missingness pattern and an extra attribute added for keeping track of the patterns
+   * @param theta       a matrix containing the multivariate normal parameters
+   * @return likelihood the observed data log-likelihood for theta (or log posterior density if using a ridge prior)
+   * @throws Exception  if processing goes wrong
+   */
+  private double logLikelihood(Instances data, Matrix theta) throws Exception {
+    
+    int p = m_numAttributes; // number of columns
+    double likelihood = 0.0; // return value
+    double d = 0;
+    
+    double [] c = new double[p + 1]; // temporary storage for mu
+    for (int j = 1; j < p + 1; j++) {
+      c[j] = theta.get(0, j);
+    }
+    
+    // matrix for calculating the log posterior density if necessary
+    Matrix sigma = theta.getMatrix(1, p, 1, p); // covariance matrix from theta
+    
+    // go through each pattern
+    int currentPatternStart = 0;
+    while (currentPatternStart < data.numInstances()) {
+      // number of instances in current missingness pattern is held in 1st attribute
+      int numInCurrentPattern = (int) data.instance(currentPatternStart).value(0);
+      // set up binary flags for if column is observed in this pattern
+      boolean [] r = new boolean[p + 1];
+      int [] observedColumns = new int[p + 1]; // indices of observed columns
+      int [] missingColumns = new int[p + 1]; // indices of missing columns
+      int numMissing = 0; // number of missing columns
+      int numObserved = 0; // number of observed columns
+      for (int l = 1; l < p + 1; l++) {
+        if (data.instance(currentPatternStart).isMissing(l)) {
+          missingColumns[numMissing++] = l;
+        } else {
+          observedColumns[numObserved++] = l;
+          r[l] = true;
+        }
+      }
+      observedColumns[numObserved] = -1; // list end indicator
+      missingColumns[numMissing] = -1; // list end indicator
+      
+      for (int j = 1; j < p + 1; j++) {
+        if (r[j] == true && theta.get(j, j) > 0) {
+          d += Math.log(theta.get(j, j));
+          theta = swp(theta, j);
+        } else if (r[j] == false && theta.get(j, j) < 0) {
+          theta = rsw(theta, j);
+          d -= Math.log(theta.get(j, j));
+        }
+      }
+      
+      Matrix m = new Matrix(p + 1, p + 1);
+      for (int i = 0; i < numInCurrentPattern; i++) {
+        for (int jCounter = 0, j = observedColumns[jCounter]; j != -1; 
+             jCounter++, j = observedColumns[jCounter]) {
+          
+          for (int kCounter = jCounter, k = observedColumns[kCounter]; k != -1;
+               kCounter++, k = observedColumns[kCounter]) {
+            m.set(j, k, m.get(j, k) + (data.instance(currentPatternStart + i).value(j) -
+                  c[j]) * (data.instance(currentPatternStart + i).value(k) - c[k]));
+            m.set(k, j, m.get(j, k));
+          }
+        }
+      }
+      
+      double t = 0;
+      
+      for (int jCounter = 0, j = observedColumns[jCounter]; j != -1; 
+           jCounter++, j = observedColumns[jCounter]) {
+        
+        for (int kCounter = 0, k = observedColumns[kCounter]; k != -1;
+             kCounter++, k = observedColumns[kCounter]) {
+            t -= theta.get(j, k) * m.get(j, k);
+        }
+      }
+      
+      //update likelihood
+      likelihood -= ((double)numInCurrentPattern * d + t) / 2.0;
+      
+      // move counter to start of next missingness pattern (or end of dataset)
+      currentPatternStart += numInCurrentPattern;
+    } // end iterating through missingness patterns
+    
+    // modify to produce log posterior density if ridge prior is being used
+    if (m_ridgePrior) {
+      Matrix deltaInv = Matrix.identity(p, p).times(m_ridge); // delta^(-1)
+      double m = m_ridge;
+      double logPi = 0; // log pi(theta) term to be added to MLE to get log posterior density
+      
+      Matrix M0 = deltaInv;
+      
+      // compute log pi(theta)
+      logPi = Math.log(Math.abs(sigma.det()));
+      logPi *= -0.5 * (m + p + 2);
+      logPi -= 0.5 * sigma.inverse().times(M0).trace();
+      
+      // modify result to give log posterior density
+      likelihood += logPi;
+    }
+    
+    return likelihood;
+  }
+  
+  /**
+   * Performs the imputation. Draws Y_mis(i+1) as the expected value of (Y_mis | Y_obs, theta(i)).
+   * @param data          preprocessed dataset with missing values
+   * @param theta         a matrix containing the multivariate normal parameters
+   * @throws Exception    if processing goes wrong
+   */
+  private void impute(Instances data, Matrix theta) throws Exception {
+    
+    int p = m_numAttributes; // number of columns
+    
+    // go through each pattern
+    int currentPatternStart = 0;
+    while (currentPatternStart < data.numInstances()) {
+      // number of instances in current missingness pattern is held in 1st attribute
+      int numInCurrentPattern = (int) data.instance(currentPatternStart).value(0);
+      // set up binary flags for if column is observed in this pattern
+      boolean [] r = new boolean[p + 1];
+      int [] observedColumns = new int[p + 1]; // indices of observed columns
+      int [] missingColumns = new int[p + 1]; // indices of missing columns
+      int numMissing = 0; // number of missing columns
+      int numObserved = 0; // number of observed columns
+      for (int l = 1; l < p + 1; l++) {
+        if (data.instance(currentPatternStart).isMissing(l)) {
+          missingColumns[numMissing++] = l;
+        } else {
+          observedColumns[numObserved++] = l;
+          r[l] = true;
+        }
+      }
+      observedColumns[numObserved] = -1; // list end indicator
+      missingColumns[numMissing] = -1; // list end indicator
+      
+      for (int j = 1; j < p + 1; j++) {
+        if (r[j] == true && theta.get(j, j) > 0) {
+          theta = swp(theta, j);
+        } else if (r[j] == false && theta.get(j, j) < 0) {
+          theta = rsw(theta, j);
+        }
+      }
+      
+      for (int i = 0; i < numInCurrentPattern; i++) {
+        for (int jCounter = 0, j = missingColumns[jCounter]; j != -1; 
+             jCounter++, j = missingColumns[jCounter]) {
+          // impute the missing values
+          data.instance(currentPatternStart + i).setValue(j, theta.get(0, j));
+          for (int kCounter = 0, k = observedColumns[kCounter]; k != -1;
+               kCounter++, k = observedColumns[kCounter]) {
+            double y_ij = data.instance(currentPatternStart + i).value(j);
+            double y_ik = data.instance(currentPatternStart + i).value(k);
+            double theta_kj = theta.get(k, j);
+            data.instance(currentPatternStart + i).setValue(j, y_ij + theta_kj * y_ik);
+          }
+        }
+      } // end iterating through instances in pattern
+      // move counter to start of next missingness pattern (or end of dataset)
+      currentPatternStart += numInCurrentPattern;
+    } // end iterating through missingness patterns
+  }
+  
+  /**
+   * Performs the sweep operation on a matrix at the given position. 
+   * @param g           a matrix
+   * @param k           the pivot position
+   * @param dir         the direction to do the sweep in (1 = normal sweep, -1 = reverse sweep)
+   * @return h          the matrix after being swept on position k
+   * @throws Exception  if processing goes wrong
+   */
+  private static Matrix doSweep(Matrix g, int k, int dir) throws Exception {
+    
+    // number of rows/columns
+    int p = g.getRowDimension();
+    
+    // check if k is in range
+    if (k < 0 || k >= p) {
+      throw new Exception("Position to be swept on must be within range.");
+    }
+    
+    // check if dir is 1 or -1
+    if (dir != 1 && dir != -1) {
+      throw new Exception("Sweep direction must be 1 or -1.");
+    }
+    
+    // result matrix
+    Matrix h = g.copy();
+    
+    // check that pivot value is not zero
+    double kkValue = g.get(k, k);
+    if (kkValue == 0) {
+      throw new Exception("Sweep: Division by zero (pivot value).");
+    }
+    
+    // process elements
+    for (int i = 0; i < p; i++) {
+      for (int j = i; j < p; j++) {
+        if (i == k && j == k) { // pivot position
+          h.set(i, j, -1.0 / kkValue);
+        } else if (i == k || j == k) { // value in row or column k
+          h.set(i, j, (double)dir * g.get(i, j) / kkValue);
+          h.set(j, i, h.get(i, j)); // copy to symmetric value
+        } else {
+          h.set(i, j, g.get(i, j) - g.get(i, k) * g.get(k, j) / kkValue);
+          h.set(j, i, h.get(i, j)); // copy to symmetric value
+        }
+      }
+    }
+    
+    return h;
+  }
+  
+  /**
+   * Performs the normal sweep operation. 
+   * @param g           a matrix
+   * @param k           the pivot position
+   * @return h          the matrix after being swept on position k
+   * @throws Exception  if processing goes wrong
+   */
+  private static Matrix swp(Matrix g, int k) throws Exception {
+    
+    try {
+      return doSweep(g, k, 1); // call actual sweep function with proper parameters
+    } catch (Exception e) {
+      throw e;
+    }
+  }
+  
+  /**
+   * Performs the reverse sweep operation. 
+   * @param g           a matrix
+   * @param k           the pivot position
+   * @return h          the matrix after being swept on position k
+   * @throws Exception  if processing goes wrong
+   */
+  private static Matrix rsw(Matrix g, int k) throws Exception {
+    
+    try {
+      return doSweep(g, k, -1); // call actual sweep function with proper parameters
+    } catch (Exception e) {
+      throw e;
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    
+    runFilter(new EMImputation(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/FirstOrder.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/FirstOrder.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/FirstOrder.java	(revision 29)
@@ -0,0 +1,376 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FirstOrder.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This instance filter takes a range of N numeric attributes and replaces them with N-1 numeric attributes, the values of which are the difference between consecutive attribute values from the original instance. eg: <br/>
+ * <br/>
+ * Original attribute values<br/>
+ * <br/>
+ *    0.1, 0.2, 0.3, 0.1, 0.3<br/>
+ * <br/>
+ * New attribute values<br/>
+ * <br/>
+ *    0.1, 0.1, -0.2, 0.2<br/>
+ * <br/>
+ * The range of attributes used is taken in numeric order. That is, a range spec of 7-11,3-5 will use the attribute ordering 3,4,5,7,8,9,10,11 for the differences, NOT 7,8,9,10,11,3,4,5.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to take the differences between.
+ *  First and last are valid indexes.
+ *  (default none)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class FirstOrder 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -7500464545400454179L;
+  
+  /** Stores which columns to take differences between */
+  protected Range m_DeltaCols = new Range();
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "This instance filter takes a range of N numeric attributes and replaces "
+      + "them with N-1 numeric attributes, the values of which are the difference "
+      + "between consecutive attribute values from the original instance. eg: \n\n"
+      + "Original attribute values\n\n"
+      + "   0.1, 0.2, 0.3, 0.1, 0.3\n\n"
+      + "New attribute values\n\n"
+      + "   0.1, 0.1, -0.2, 0.2\n\n"
+      + "The range of attributes used is taken in numeric order. That is, a range "
+      + "spec of 7-11,3-5 will use the attribute ordering 3,4,5,7,8,9,10,11 for the "
+      + "differences, NOT 7,8,9,10,11,3,4,5.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to take the differences between.\n"
+	      + "\tFirst and last are valid indexes.\n"
+	      + "\t(default none)",
+              "R", 1, "-R <index1,index2-index4,...>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to take the differences between.
+   *  First and last are valid indexes.
+   *  (default none)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String deltaList = Utils.getOption('R', options);
+    if (deltaList.length() != 0) {
+      setAttributeIndices(deltaList);
+    } else {
+      setAttributeIndices("");
+    }
+    
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [2];
+    int current = 0;
+
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnsupportedAttributeTypeException if any of the
+   * selected attributes are not numeric 
+   * @throws Exception if only one attribute has been selected.
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+
+    m_DeltaCols.setUpper(getInputFormat().numAttributes() - 1);
+    int selectedCount = 0;
+    for (int i = getInputFormat().numAttributes() - 1; i >= 0; i--) {
+      if (m_DeltaCols.isInRange(i)) {
+        selectedCount++;
+        if (!getInputFormat().attribute(i).isNumeric()) {
+          throw new UnsupportedAttributeTypeException("Selected attributes must be all numeric");
+        }
+      }
+    }
+    if (selectedCount == 1) {
+      throw new Exception("Cannot select only one attribute.");
+    }
+
+    // Create the output buffer
+    FastVector newAtts = new FastVector();
+    boolean inRange = false;
+    String foName = null;
+    int clsIndex = -1;
+    for(int i = 0; i < instanceInfo.numAttributes(); i++) {
+      if (m_DeltaCols.isInRange(i) && (i != instanceInfo.classIndex())) {
+	if (inRange) {
+	  Attribute newAttrib = new Attribute(foName);
+          newAtts.addElement(newAttrib);
+	}
+        foName = instanceInfo.attribute(i).name();
+        foName = "'FO " + foName.replace('\'', ' ').trim() + '\'';
+        inRange = true;
+      } else {
+	newAtts.addElement((Attribute)instanceInfo.attribute(i).copy());
+	if ((i == instanceInfo.classIndex()))
+	  clsIndex = newAtts.size() - 1;
+      }      
+    }
+    Instances data = new Instances(instanceInfo.relationName(), newAtts, 0);
+    data.setClassIndex(clsIndex);
+    setOutputFormat(data);
+    return true;
+  }
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    Instances outputFormat = outputFormatPeek();
+    double[] vals = new double[outputFormat.numAttributes()];
+    boolean inRange = false;
+    double lastVal = Utils.missingValue();
+    int i, j;
+    for(i = 0, j = 0; j < outputFormat.numAttributes(); i++) {
+      if (m_DeltaCols.isInRange(i) && (i != instance.classIndex())) {
+	if (inRange) {
+	  if (Utils.isMissingValue(lastVal) || instance.isMissing(i)) {
+	    vals[j++] = Utils.missingValue();
+	  } else {
+	    vals[j++] = instance.value(i) - lastVal;
+	  }
+	} else {
+	  inRange = true;
+	}
+	lastVal = instance.value(i);
+      } else {
+	vals[j++] = instance.value(i);
+      }
+    }
+
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+    return true;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_DeltaCols.getRanges();
+  }
+
+  /**
+   * Set which attributes are to be deleted (or kept if invert is true)
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   * @throws Exception if an invalid range list is supplied
+   */
+  public void setAttributeIndices(String rangeList) throws Exception {
+
+    m_DeltaCols.setRanges(rangeList);
+  }
+
+  /**
+   * Set which attributes are to be deleted (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws Exception if an invalid set of ranges is supplied
+   */
+  public void setAttributeIndicesArray(int [] attributes) throws Exception {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new FirstOrder(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/InterquartileRange.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/InterquartileRange.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/InterquartileRange.java	(revision 29)
@@ -0,0 +1,960 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InterquartileRange.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleBatchFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A filter for detecting outliers and extreme values based on interquartile ranges. The filter skips the class attribute.<br/>
+ * <br/>
+ * Outliers:<br/>
+ *   Q3 + OF*IQR &lt; x &lt;= Q3 + EVF*IQR<br/>
+ *   or<br/>
+ *   Q1 - EVF*IQR &lt;= x &lt; Q1 - OF*IQR<br/>
+ * <br/>
+ * Extreme values:<br/>
+ *   x &gt; Q3 + EVF*IQR<br/>
+ *   or<br/>
+ *   x &lt; Q1 - EVF*IQR<br/>
+ * <br/>
+ * Key:<br/>
+ *   Q1  = 25% quartile<br/>
+ *   Q3  = 75% quartile<br/>
+ *   IQR = Interquartile Range, difference between Q1 and Q3<br/>
+ *   OF  = Outlier Factor<br/>
+ *   EVF = Extreme Value Factor
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to base outlier/extreme value detection
+ *  on. If an instance is considered in at least one of those
+ *  attributes an outlier/extreme value, it is tagged accordingly.
+ *  'first' and 'last' are valid indexes.
+ *  (default none)</pre>
+ * 
+ * <pre> -O &lt;num&gt;
+ *  The factor for outlier detection.
+ *  (default: 3)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The factor for extreme values detection.
+ *  (default: 2*Outlier Factor)</pre>
+ * 
+ * <pre> -E-as-O
+ *  Tags extreme values also as outliers.
+ *  (default: off)</pre>
+ * 
+ * <pre> -P
+ *  Generates Outlier/ExtremeValue pair for each numeric attribute in
+ *  the range, not just a single indicator pair for all the attributes.
+ *  (default: off)</pre>
+ * 
+ * <pre> -M
+ *  Generates an additional attribute 'Offset' per Outlier/ExtremeValue
+ *  pair that contains the multiplier that the value is off the median.
+ *     value = median + 'multiplier' * IQR
+ * Note: implicitely sets '-P'. (default: off)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * Thanks to Dale for a few brainstorming sessions.
+ *
+ * @author  Dale Fletcher (dale at cs dot waikato dot ac dot nz)
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class InterquartileRange
+  extends SimpleBatchFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -227879653639723030L;
+
+  /** indicator for non-numeric attributes */
+  public final static int NON_NUMERIC = -1;
+  
+  /** the attribute range to work on */
+  protected Range m_Attributes = new Range("first-last");
+  
+  /** the generated indices (only for performance reasons) */
+  protected int[] m_AttributeIndices = null;
+
+  /** the factor for detecting outliers */
+  protected double m_OutlierFactor = 3;
+  
+  /** the factor for detecting extreme values, by default 2*m_OutlierFactor */
+  protected double m_ExtremeValuesFactor = 2*m_OutlierFactor;
+  
+  /** whether extreme values are also tagged as outliers */
+  protected boolean m_ExtremeValuesAsOutliers = false;
+
+  /** the upper extreme value threshold (= Q3 + EVF*IQR) */
+  protected double[] m_UpperExtremeValue = null;
+
+  /** the upper outlier threshold (= Q3 + OF*IQR) */
+  protected double[] m_UpperOutlier = null;
+
+  /** the lower outlier threshold (= Q1 - OF*IQR) */
+  protected double[] m_LowerOutlier = null;
+
+  /** the interquartile range  */
+  protected double[] m_IQR = null;
+
+  /** the median  */
+  protected double[] m_Median = null;
+
+  /** the lower extreme value threshold (= Q1 - EVF*IQR) */
+  protected double[] m_LowerExtremeValue = null;
+  
+  /** whether to generate Outlier/ExtremeValue attributes for each attribute
+   * instead of a general one */
+  protected boolean m_DetectionPerAttribute = false;
+
+  /** the position of the outlier attribute */
+  protected int[] m_OutlierAttributePosition = null;
+
+  /** whether to add another attribute called "Offset", that lists the 
+   * 'multiplier' by which the outlier/extreme value is away from the median,
+   * i.e., value = median + 'multiplier' * IQR <br/>
+   * automatically enables m_DetectionPerAttribute!
+   */
+  protected boolean m_OutputOffsetMultiplier = false;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter for detecting outliers and extreme values based on "
+      + "interquartile ranges. The filter skips the class attribute.\n\n"
+      + "Outliers:\n"
+      + "  Q3 + OF*IQR < x <= Q3 + EVF*IQR\n"
+      + "  or\n"
+      + "  Q1 - EVF*IQR <= x < Q1 - OF*IQR\n"
+      + "\n"
+      + "Extreme values:\n"
+      + "  x > Q3 + EVF*IQR\n"
+      + "  or\n"
+      + "  x < Q1 - EVF*IQR\n"
+      + "\n"
+      + "Key:\n"
+      + "  Q1  = 25% quartile\n"
+      + "  Q3  = 75% quartile\n"
+      + "  IQR = Interquartile Range, difference between Q1 and Q3\n"
+      + "  OF  = Outlier Factor\n"
+      + "  EVF = Extreme Value Factor";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+	"\tSpecifies list of columns to base outlier/extreme value detection\n"
+	+ "\ton. If an instance is considered in at least one of those\n"
+	+ "\tattributes an outlier/extreme value, it is tagged accordingly.\n"
+	+ " 'first' and 'last' are valid indexes.\n"
+	+ "\t(default none)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+
+    result.addElement(new Option(
+        "\tThe factor for outlier detection.\n"
+	+ "\t(default: 3)",
+        "O", 1, "-O <num>"));
+
+    result.addElement(new Option(
+        "\tThe factor for extreme values detection.\n"
+	+ "\t(default: 2*Outlier Factor)",
+        "E", 1, "-E <num>"));
+
+    result.addElement(new Option(
+        "\tTags extreme values also as outliers.\n"
+	+ "\t(default: off)",
+        "E-as-O", 0, "-E-as-O"));
+
+    result.addElement(new Option(
+        "\tGenerates Outlier/ExtremeValue pair for each numeric attribute in\n"
+	+ "\tthe range, not just a single indicator pair for all the attributes.\n"
+	+ "\t(default: off)",
+        "P", 0, "-P"));
+
+    result.addElement(new Option(
+        "\tGenerates an additional attribute 'Offset' per Outlier/ExtremeValue\n"
+	+ "\tpair that contains the multiplier that the value is off the median.\n"
+	+ "\t   value = median + 'multiplier' * IQR\n"
+	+ "Note: implicitely sets '-P'."
+	+ "\t(default: off)",
+        "M", 0, "-M"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to base outlier/extreme value detection
+   *  on. If an instance is considered in at least one of those
+   *  attributes an outlier/extreme value, it is tagged accordingly.
+   *  'first' and 'last' are valid indexes.
+   *  (default none)</pre>
+   * 
+   * <pre> -O &lt;num&gt;
+   *  The factor for outlier detection.
+   *  (default: 3)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The factor for extreme values detection.
+   *  (default: 2*Outlier Factor)</pre>
+   * 
+   * <pre> -E-as-O
+   *  Tags extreme values also as outliers.
+   *  (default: off)</pre>
+   * 
+   * <pre> -P
+   *  Generates Outlier/ExtremeValue pair for each numeric attribute in
+   *  the range, not just a single indicator pair for all the attributes.
+   *  (default: off)</pre>
+   * 
+   * <pre> -M
+   *  Generates an additional attribute 'Offset' per Outlier/ExtremeValue
+   *  pair that contains the multiplier that the value is off the median.
+   *     value = median + 'multiplier' * IQR
+   * Note: implicitely sets '-P'. (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("R", options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices("first-last");
+
+    tmpStr = Utils.getOption("O", options);
+    if (tmpStr.length() != 0)
+      setOutlierFactor(Double.parseDouble(tmpStr));
+    else
+      setOutlierFactor(3);
+
+    tmpStr = Utils.getOption("E", options);
+    if (tmpStr.length() != 0)
+      setExtremeValuesFactor(Double.parseDouble(tmpStr));
+    else
+      setExtremeValuesFactor(2*getOutlierFactor());
+    
+    setExtremeValuesAsOutliers(Utils.getFlag("E-as-O", options));
+    
+    setDetectionPerAttribute(Utils.getFlag("P", options));
+
+    setOutputOffsetMultiplier(Utils.getFlag("M", options));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-R");
+    if (!getAttributeIndices().equals(""))
+      result.add(getAttributeIndices());
+    else
+      result.add("first-last");
+    
+    result.add("-O");
+    result.add("" + getOutlierFactor());
+
+    result.add("-E");
+    result.add("" + getExtremeValuesFactor());
+
+    if (getExtremeValuesAsOutliers())
+      result.add("-E-as-O");
+    
+    if (getDetectionPerAttribute())
+      result.add("-P");
+    
+    if (getOutputOffsetMultiplier())
+      result.add("-M");
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return 
+        "Specify range of attributes to act on; "
+      + " this is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values; specify an inclusive"
+      + " range with \"-\", eg: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection
+   *
+   * @return 		a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+    return m_Attributes.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be used for interquartile calculations and
+   * outlier/extreme value detection (only numeric attributes among the 
+   * selection will be used).
+   *
+   * @param value 	a string representing the list of attributes. Since
+   * 			the string will typically come from a user, attributes 
+   * 			are indexed from 1. <br> eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String value) {
+    m_Attributes.setRanges(value);
+  }
+
+  /**
+   * Sets which attributes are to be used for interquartile calculations and
+   * outlier/extreme value detection (only numeric attributes among the 
+   * selection will be used).
+   *
+   * @param value 	an array containing indexes of attributes to work on.
+   * 			Since the array will typically come from a program, 
+   * 			attributes are indexed from 0.
+   * @throws IllegalArgumentException if an invalid set of ranges is supplied 
+   */
+  public void setAttributeIndicesArray(int[] value) {
+    setAttributeIndices(Range.indicesToRangeList(value));
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outlierFactorTipText() {
+    return "The factor for determining the thresholds for outliers.";
+  }
+
+  /**
+   * Sets the factor for determining the thresholds for outliers.
+   *
+   * @param value 	the factor.
+   */
+  public void setOutlierFactor(double value) {
+    if (value >= getExtremeValuesFactor())
+      System.err.println("OutlierFactor must be smaller than ExtremeValueFactor");
+    else
+      m_OutlierFactor = value;
+  }
+
+  /**
+   * Gets the factor for determining the thresholds for outliers.
+   *
+   * @return 		the factor.
+   */
+  public double getOutlierFactor() {
+    return m_OutlierFactor;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String extremeValuesFactorTipText() {
+    return "The factor for determining the thresholds for extreme values.";
+  }
+
+  /**
+   * Sets the factor for determining the thresholds for extreme values.
+   *
+   * @param value 	the factor.
+   */
+  public void setExtremeValuesFactor(double value) {
+    if (value <= getOutlierFactor())
+      System.err.println("ExtremeValuesFactor must be greater than OutlierFactor!");
+    else
+      m_ExtremeValuesFactor = value;
+  }
+
+  /**
+   * Gets the factor for determining the thresholds for extreme values.
+   *
+   * @return 		the factor.
+   */
+  public double getExtremeValuesFactor() {
+    return m_ExtremeValuesFactor;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String extremeValuesAsOutliersTipText() {
+    return "Whether to tag extreme values also as outliers.";
+  }
+
+  /**
+   * Set whether extreme values are also tagged as outliers.
+   *
+   * @param value 	whether or not to tag extreme values also as outliers.
+   */
+  public void setExtremeValuesAsOutliers(boolean value) {
+    m_ExtremeValuesAsOutliers = value;
+  }
+
+  /**
+   * Get whether extreme values are also tagged as outliers.
+   *
+   * @return 		true if extreme values are also tagged as outliers.
+   */
+  public boolean getExtremeValuesAsOutliers() {
+    return m_ExtremeValuesAsOutliers;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String detectionPerAttributeTipText() {
+    return 
+        "Generates Outlier/ExtremeValue attribute pair for each numeric "
+      + "attribute, not just a single pair for all numeric attributes together.";
+  }
+
+  /**
+   * Set whether an Outlier/ExtremeValue attribute pair is generated for 
+   * each numeric attribute ("true") or just one pair for all numeric 
+   * attributes together ("false").
+   *
+   * @param value 	whether or not to generate indicator attribute pairs 
+   * 			for each numeric attribute.
+   */
+  public void setDetectionPerAttribute(boolean value) {
+    m_DetectionPerAttribute = value;
+    if (!m_DetectionPerAttribute)
+      m_OutputOffsetMultiplier = false;
+  }
+
+  /**
+   * Gets whether an Outlier/ExtremeValue attribute pair is generated for 
+   * each numeric attribute ("true") or just one pair for all numeric 
+   * attributes together ("false").
+   *
+   * @return 		true if indicator attribute pairs are generated for
+   * 			each numeric attribute.
+   */
+  public boolean getDetectionPerAttribute() {
+    return m_DetectionPerAttribute;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String outputOffsetMultiplierTipText() {
+    return 
+        "Generates an additional attribute 'Offset' that contains the "
+      + "multiplier the value is off the median: "
+      + "value = median + 'multiplier' * IQR";
+  }
+
+  /**
+   * Set whether an additional attribute "Offset" is generated per 
+   * Outlier/ExtremeValue attribute pair that lists the multiplier the value
+   * is off the median: value = median + 'multiplier' * IQR.
+   *
+   * @param value 	whether or not to generate the additional attribute.
+   */
+  public void setOutputOffsetMultiplier(boolean value) {
+    m_OutputOffsetMultiplier = value;
+    if (m_OutputOffsetMultiplier)
+      m_DetectionPerAttribute = true;
+  }
+
+  /**
+   * Gets whether an additional attribute "Offset" is generated per 
+   * Outlier/ExtremeValue attribute pair that lists the multiplier the value
+   * is off the median: value = median + 'multiplier' * IQR.
+   *
+   * @return 		true if the additional attribute is generated.
+   */
+  public boolean getOutputOffsetMultiplier() {
+    return m_OutputOffsetMultiplier;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * hasImmediateOutputFormat() returns false, then this method will called
+   * from batchFinished() after the call of preprocess(Instances), in which,
+   * e.g., statistics for the actual processing step can be gathered.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see                   #hasImmediateOutputFormat()
+   * @see                   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+    
+    FastVector		atts;
+    FastVector		values;
+    Instances		result;
+    int			i;
+
+    // attributes must be numeric
+    m_Attributes.setUpper(inputFormat.numAttributes() - 1);
+    m_AttributeIndices = m_Attributes.getSelection();
+    for (i = 0; i < m_AttributeIndices.length; i++) {
+      // ignore class
+      if (m_AttributeIndices[i] == inputFormat.classIndex()) {
+	m_AttributeIndices[i] = NON_NUMERIC;
+	continue;
+      }
+      // not numeric -> ignore it
+      if (!inputFormat.attribute(m_AttributeIndices[i]).isNumeric())
+	m_AttributeIndices[i] = NON_NUMERIC;
+    }
+    
+    // get old attributes
+    atts = new FastVector();
+    for (i = 0; i < inputFormat.numAttributes(); i++)
+      atts.addElement(inputFormat.attribute(i));
+    
+    if (!getDetectionPerAttribute()) {
+      m_OutlierAttributePosition    = new int[1];
+      m_OutlierAttributePosition[0] = atts.size();
+      
+      // add 2 new attributes
+      values = new FastVector();
+      values.addElement("no");
+      values.addElement("yes");
+      atts.addElement(new Attribute("Outlier", values));
+      
+      values = new FastVector();
+      values.addElement("no");
+      values.addElement("yes");
+      atts.addElement(new Attribute("ExtremeValue", values));
+    }
+    else {
+      m_OutlierAttributePosition = new int[m_AttributeIndices.length];
+      
+      for (i = 0; i < m_AttributeIndices.length; i++) {
+	if (m_AttributeIndices[i] == NON_NUMERIC)
+	  continue;
+	
+	m_OutlierAttributePosition[i] = atts.size();
+
+	// add new attributes
+	values = new FastVector();
+	values.addElement("no");
+	values.addElement("yes");
+	atts.addElement(
+	    new Attribute(
+		inputFormat.attribute(
+		    m_AttributeIndices[i]).name() + "_Outlier", values));
+	
+	values = new FastVector();
+	values.addElement("no");
+	values.addElement("yes");
+	atts.addElement(
+	    new Attribute(
+		inputFormat.attribute(
+		    m_AttributeIndices[i]).name() + "_ExtremeValue", values));
+
+	if (getOutputOffsetMultiplier())
+	  atts.addElement(
+	      new Attribute(
+		  inputFormat.attribute(
+		      m_AttributeIndices[i]).name() + "_Offset"));
+      }
+    }
+
+    // generate header
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(inputFormat.classIndex());
+    
+    return result;
+  }
+
+  /**
+   * computes the thresholds for outliers and extreme values
+   * 
+   * @param instances	the data to work on
+   */
+  protected void computeThresholds(Instances instances) {
+    int		i;
+    double[]	values;
+    int[]	sortedIndices;
+    int		half;
+    int		quarter;
+    double	q1;
+    double	q2;
+    double	q3;
+    
+    m_UpperExtremeValue = new double[m_AttributeIndices.length];
+    m_UpperOutlier      = new double[m_AttributeIndices.length];
+    m_LowerOutlier      = new double[m_AttributeIndices.length];
+    m_LowerExtremeValue = new double[m_AttributeIndices.length];
+    m_Median            = new double[m_AttributeIndices.length];
+    m_IQR               = new double[m_AttributeIndices.length];
+    
+    for (i = 0; i < m_AttributeIndices.length; i++) {
+      // non-numeric attribute?
+      if (m_AttributeIndices[i] == NON_NUMERIC)
+	continue;
+      
+      // sort attribute data
+      values        = instances.attributeToDoubleArray(m_AttributeIndices[i]);
+      sortedIndices = Utils.sort(values);
+      
+      // determine indices
+      half    = sortedIndices.length / 2;
+      quarter = half / 2;
+      
+      if (sortedIndices.length % 2 == 1) {
+	q2 = values[sortedIndices[half]];
+      }
+      else {
+	q2 = (values[sortedIndices[half]] + values[sortedIndices[half + 1]]) / 2;
+      }
+      
+      if (half % 2 == 1) {
+	q1 = values[sortedIndices[quarter]];
+	q3 = values[sortedIndices[sortedIndices.length - quarter - 1]];
+      }
+      else {
+	q1 = (values[sortedIndices[quarter]] + values[sortedIndices[quarter + 1]]) / 2;
+	q3 = (values[sortedIndices[sortedIndices.length - quarter - 1]] + values[sortedIndices[sortedIndices.length - quarter]]) / 2;
+      }
+      
+      // determine thresholds and other values
+      m_Median[i]            = q2;
+      m_IQR[i]               = q3 - q1;
+      m_UpperExtremeValue[i] = q3 + getExtremeValuesFactor() * m_IQR[i];
+      m_UpperOutlier[i]      = q3 + getOutlierFactor()       * m_IQR[i];
+      m_LowerOutlier[i]      = q1 - getOutlierFactor()       * m_IQR[i];
+      m_LowerExtremeValue[i] = q1 - getExtremeValuesFactor() * m_IQR[i];
+    }
+  }
+  
+  /**
+   * returns whether the instance has an outlier in the specified attribute 
+   * or not
+   * 
+   * @param inst	the instance to test
+   * @param index	the attribute index
+   * @return		true if the instance is an outlier
+   */
+  protected boolean isOutlier(Instance inst, int index) {
+    boolean	result;
+    double	value;
+
+    value  = inst.value(m_AttributeIndices[index]);
+    result =    ((m_UpperOutlier[index]      <  value) && (value <= m_UpperExtremeValue[index]))
+             || ((m_LowerExtremeValue[index] <= value) && (value <  m_LowerOutlier[index]));
+    
+    return result;
+  }
+  
+  /**
+   * returns whether the instance is an outlier or not
+   * 
+   * @param inst	the instance to test
+   * @return		true if the instance is an outlier
+   */
+  protected boolean isOutlier(Instance inst) {
+    boolean	result;
+    int		i;
+
+    result = false;
+    
+    for (i = 0; i < m_AttributeIndices.length; i++) {
+      // non-numeric attribute?
+      if (m_AttributeIndices[i] == NON_NUMERIC)
+	continue;
+
+      result = isOutlier(inst, i);
+      
+      if (result)
+	break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns whether the instance has an extreme value in the specified 
+   * attribute or not
+   * 
+   * @param inst	the instance to test
+   * @param index	the attribute index
+   * @return		true if the instance is an extreme value
+   */
+  protected boolean isExtremeValue(Instance inst, int index) {
+    boolean	result;
+    double	value;
+
+    value  = inst.value(m_AttributeIndices[index]);
+    result =    (value > m_UpperExtremeValue[index]) 
+             || (value < m_LowerExtremeValue[index]);
+      
+    return result;
+  }
+  
+  /**
+   * returns whether the instance is an extreme value or not
+   * 
+   * @param inst	the instance to test
+   * @return		true if the instance is an extreme value
+   */
+  protected boolean isExtremeValue(Instance inst) {
+    boolean	result;
+    int		i;
+
+    result = false;
+    
+    for (i = 0; i < m_AttributeIndices.length; i++) {
+      // non-numeric attribute?
+      if (m_AttributeIndices[i] == NON_NUMERIC)
+	continue;
+      
+      result = isExtremeValue(inst, i);
+      
+      if (result)
+	break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the mulitplier of the IQR the instance is off the median for this
+   * particular attribute.
+   * 
+   * @param inst	the instance to test
+   * @param index	the attribute index
+   * @return		the multiplier
+   */
+  protected double calculateMultiplier(Instance inst, int index) {
+    double	result;
+    double	value;
+
+    value  = inst.value(m_AttributeIndices[index]);
+    result = (value - m_Median[index]) / m_IQR[index];
+      
+    return result;
+  }
+  
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   * This implementation only calls process(Instance) for each instance
+   * in the given dataset.
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances	result;
+    Instance	instOld;
+    Instance	instNew;
+    int		i;
+    int		n;
+    double[]	values;
+    int		numAttNew;
+    int		numAttOld;
+    
+    if (!isFirstBatchDone())
+      computeThresholds(instances);
+    
+    result    = getOutputFormat();
+    numAttOld = instances.numAttributes();
+    numAttNew = result.numAttributes();
+    
+    for (n = 0; n < instances.numInstances(); n++) {
+      instOld = instances.instance(n);
+      values  = new double[numAttNew];
+      System.arraycopy(instOld.toDoubleArray(), 0, values, 0, numAttOld);
+      
+      // generate new instance
+      instNew = new DenseInstance(1.0, values);
+      instNew.setDataset(result);
+
+      // per attribute?
+      if (!getDetectionPerAttribute()) {
+	// outlier?
+	if (isOutlier(instOld))
+	  instNew.setValue(m_OutlierAttributePosition[0], 1);
+	// extreme value?
+	if (isExtremeValue(instOld)) {
+	  instNew.setValue(m_OutlierAttributePosition[0] + 1, 1);
+	  // tag extreme values also as outliers?
+	  if (getExtremeValuesAsOutliers())
+	    instNew.setValue(m_OutlierAttributePosition[0], 1);
+	}
+      }
+      else {
+	for (i = 0; i < m_AttributeIndices.length; i++) {
+	  // non-numeric attribute?
+	  if (m_AttributeIndices[i] == NON_NUMERIC)
+	    continue;
+	  
+	  // outlier?
+	  if (isOutlier(instOld, m_AttributeIndices[i]))
+	    instNew.setValue(m_OutlierAttributePosition[i], 1);
+	  // extreme value?
+	  if (isExtremeValue(instOld, m_AttributeIndices[i])) {
+	    instNew.setValue(m_OutlierAttributePosition[i] + 1, 1);
+	    // tag extreme values also as outliers?
+	    if (getExtremeValuesAsOutliers())
+	      instNew.setValue(m_OutlierAttributePosition[i], 1);
+	  }
+	  // add multiplier?
+	  if (getOutputOffsetMultiplier())
+	    instNew.setValue(
+		m_OutlierAttributePosition[i] + 2, 
+		calculateMultiplier(instOld, m_AttributeIndices[i]));
+	}
+      }
+      
+      // copy possible strings, relational values...
+      copyValues(instNew, false, instOld.dataset(), getOutputFormat());
+      
+      // add to output
+      result.add(instNew);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param args should contain arguments to the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new InterquartileRange(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/KernelFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/KernelFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/KernelFilter.java	(revision 29)
@@ -0,0 +1,889 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * KernelFilter.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.classifiers.functions.supportVector.PolyKernel;
+import weka.classifiers.functions.supportVector.RBFKernel;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MathematicalExpression;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.filters.AllFilter;
+import weka.filters.Filter;
+import weka.filters.SimpleBatchFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Converts the given set of predictor variables into a kernel matrix. The class value remains unchangedm, as long as the preprocessing filter doesn't change it.<br/>
+ * By default, the data is preprocessed with the Center filter, but the user can choose any filter (NB: one must be careful that the filter does not alter the class attribute unintentionally). With weka.filters.AllFilter the preprocessing gets disabled.<br/>
+ * <br/>
+ * For more information regarding preprocessing the data, see:<br/>
+ * <br/>
+ * K.P. Bennett, M.J. Embrechts: An Optimization Perspective on Kernel Partial Least Squares Regression. In: Advances in Learning Theory: Methods, Models and Applications, 227-249, 2003.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Bennett2003,
+ *    author = {K.P. Bennett and M.J. Embrechts},
+ *    booktitle = {Advances in Learning Theory: Methods, Models and Applications},
+ *    editor = {J. Suykens et al.},
+ *    pages = {227-249},
+ *    publisher = {IOS Press, Amsterdam, The Netherlands},
+ *    series = {NATO Science Series, Series III: Computer and System Sciences},
+ *    title = {An Optimization Perspective on Kernel Partial Least Squares Regression},
+ *    volume = {190},
+ *    year = {2003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  Turning them off assumes that data is purely numeric, doesn't
+ *  contain any missing values, and has a nominal class. Turning them
+ *  off also means that no header information will be stored if the
+ *  machine is linear. Finally, it also assumes that no instance has
+ *  a weight equal to 0.
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -F &lt;filename&gt;
+ *  The file to initialize the filter with (optional).</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The class index for the file to initialize with,
+ *  First and last are valid (optional, default: last).</pre>
+ * 
+ * <pre> -K &lt;classname and parameters&gt;
+ *  The Kernel to use.
+ *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+ * 
+ * <pre> -kernel-factor
+ *  Defines a factor for the kernel.
+ *   - RBFKernel: a factor for gamma
+ *    Standardize: 1/(2*N)
+ *    Normalize..: 6/N
+ *  Available parameters are:
+ *   N for # of instances, A for # of attributes
+ *  (default: 1)</pre>
+ * 
+ * <pre> -P &lt;classname and parameters&gt;
+ *  The Filter used for preprocessing (use weka.filters.AllFilter
+ *  to disable preprocessing).
+ *  (default: weka.filters.unsupervised.attribute.Center)</pre>
+ * 
+ * <pre> 
+ * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+ * </pre>
+ * 
+ * <pre> -D
+ *  Enables debugging output (if available) to be printed.
+ *  (default: off)</pre>
+ * 
+ * <pre> -no-checks
+ *  Turns off all checks - use with caution!
+ *  (default: checks on)</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  The size of the cache (a prime number), 0 for full cache and 
+ *  -1 to turn it off.
+ *  (default: 250007)</pre>
+ * 
+ * <pre> -E &lt;num&gt;
+ *  The Exponent to use.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -L
+ *  Use lower-order terms.
+ *  (default: no)</pre>
+ * 
+ * <pre> 
+ * Options specific to preprocessing filter weka.filters.unsupervised.attribute.Center:
+ * </pre>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Jonathan Miles (jdm18@cs.waikato.ac.nz) 
+ * @author FracPete (fracpete at waikato dot ac dot nz) 
+ * @version $Revision: 5987 $
+ */
+public class KernelFilter
+  extends SimpleBatchFilter 
+  implements UnsupervisedFilter, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 213800899640387499L;
+
+  /** The number of instances in the training data. */
+  protected int m_NumTrainInstances;
+
+  /** Kernel to use **/
+  protected Kernel m_Kernel = new PolyKernel();
+
+  /** the Kernel which is actually used for computation */
+  protected Kernel m_ActualKernel = null;
+
+  /** Turn off all checks and conversions? Turning them off assumes
+      that data is purely numeric, doesn't contain any missing values,
+      and has a nominal class. Turning them off also means that
+      no header information will be stored if the machine is linear. 
+      Finally, it also assumes that no instance has a weight equal to 0.*/
+  protected boolean m_checksTurnedOff;
+
+  /** The filter used to make attributes numeric. */
+  protected NominalToBinary m_NominalToBinary;
+
+  /** The filter used to get rid of missing values. */
+  protected ReplaceMissingValues m_Missing;
+
+  /** The dataset to initialize the filter with */
+  protected File m_InitFile = new File(System.getProperty("user.dir"));
+
+  /** the class index for the file to initialized with 
+   * @see #m_InitFile */
+  protected SingleIndex m_InitFileClassIndex = new SingleIndex("last");
+  
+  /** whether the filter was initialized */
+  protected boolean m_Initialized = false;
+
+  /** optimizes the kernel with this formula 
+   * (A = # of attributes, N = # of instances)*/
+  protected String m_KernelFactorExpression = "1";
+
+  /** the calculated kernel factor
+   * @see #m_KernelFactorExpression */
+  protected double m_KernelFactor = 1.0;
+  
+  /** for centering/standardizing the data */
+  protected Filter m_Filter = new Center();
+  
+  /** for centering/standardizing the data (the actual filter to use) */
+  protected Filter m_ActualFilter = null;
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return      a description of the filter suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Converts the given set of predictor variables into a kernel matrix. "
+      + "The class value remains unchangedm, as long as the preprocessing "
+      + "filter doesn't change it.\n"
+      + "By default, the data is preprocessed with the Center filter, but the "
+      + "user can choose any filter (NB: one must be careful that the filter "
+      + "does not alter the class attribute unintentionally). With "
+      + "weka.filters.AllFilter the preprocessing gets disabled.\n\n"
+      + "For more information regarding preprocessing the data, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "K.P. Bennett and M.J. Embrechts");
+    result.setValue(Field.TITLE, "An Optimization Perspective on Kernel Partial Least Squares Regression");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.EDITOR, "J. Suykens et al.");
+    result.setValue(Field.BOOKTITLE, "Advances in Learning Theory: Methods, Models and Applications");
+    result.setValue(Field.PAGES, "227-249");
+    result.setValue(Field.PUBLISHER, "IOS Press, Amsterdam, The Netherlands");
+    result.setValue(Field.SERIES, "NATO Science Series, Series III: Computer and System Sciences");
+    result.setValue(Field.VOLUME, "190");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        result;
+    Enumeration   enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+    
+    result.addElement(new Option(
+	"\tTurns off all checks - use with caution!\n"
+	+ "\tTurning them off assumes that data is purely numeric, doesn't\n"
+	+ "\tcontain any missing values, and has a nominal class. Turning them\n"
+	+ "\toff also means that no header information will be stored if the\n"
+	+ "\tmachine is linear. Finally, it also assumes that no instance has\n"
+	+ "\ta weight equal to 0.\n"
+	+ "\t(default: checks on)",
+	"no-checks", 0, "-no-checks"));
+
+    result.addElement(new Option(
+	"\tThe file to initialize the filter with (optional).",
+	"F", 1, "-F <filename>"));
+
+    result.addElement(new Option(
+	"\tThe class index for the file to initialize with,\n"
+	+ "\tFirst and last are valid (optional, default: last).",
+	"C", 1, "-C <num>"));
+
+    result.addElement(new Option(
+	"\tThe Kernel to use.\n"
+	+ "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
+	"K", 1, "-K <classname and parameters>"));
+
+    result.addElement(new Option(
+	"\tDefines a factor for the kernel.\n"
+	+ "\t\t- RBFKernel: a factor for gamma\n"
+	+ "\t\t\tStandardize: 1/(2*N)\n"
+	+ "\t\t\tNormalize..: 6/N\n"
+	+ "\tAvailable parameters are:\n"
+	+ "\t\tN for # of instances, A for # of attributes\n"
+	+ "\t(default: 1)",
+	"kernel-factor", 0, "-kernel-factor"));
+
+    result.addElement(new Option(
+	"\tThe Filter used for preprocessing (use weka.filters.AllFilter\n"
+	+ "\tto disable preprocessing).\n"
+	+ "\t(default: " + Center.class.getName() + ")",
+	"P", 1, "-P <classname and parameters>"));
+
+    // kernel options
+    result.addElement(new Option(
+	"",
+	"", 0, "\nOptions specific to kernel "
+	+ getKernel().getClass().getName() + ":"));
+    
+    enm = ((OptionHandler) getKernel()).listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    // filter options
+    if (getPreprocessing() instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to preprocessing filter "
+	  + getPreprocessing().getClass().getName() + ":"));
+
+      enm = ((OptionHandler) getPreprocessing()).listOptions();
+      while (enm.hasMoreElements())
+	result.addElement(enm.nextElement());
+    }
+    
+    return result.elements();
+  }	  
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int		i;
+    Vector	result;
+    String[]	options;
+    String	tmpStr;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getChecksTurnedOff())
+      result.add("-no-checks");
+
+    if ((getInitFile() != null) && getInitFile().isFile()) {
+      result.add("-F");
+      result.add("" + getInitFile().getAbsolutePath());
+
+      result.add("-C");
+      result.add("" + getInitFileClassIndex());
+    }
+
+    result.add("-K");
+    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
+
+    result.add("-kernel-factor");
+    result.add("" + getKernelFactorExpression());
+
+    result.add("-P");
+    tmpStr = getPreprocessing().getClass().getName();
+    if (getPreprocessing() instanceof OptionHandler)
+      tmpStr += " " + Utils.joinOptions(((OptionHandler) getPreprocessing()).getOptions());
+    result.add("" + tmpStr);
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }	  
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  Turning them off assumes that data is purely numeric, doesn't
+   *  contain any missing values, and has a nominal class. Turning them
+   *  off also means that no header information will be stored if the
+   *  machine is linear. Finally, it also assumes that no instance has
+   *  a weight equal to 0.
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -F &lt;filename&gt;
+   *  The file to initialize the filter with (optional).</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The class index for the file to initialize with,
+   *  First and last are valid (optional, default: last).</pre>
+   * 
+   * <pre> -K &lt;classname and parameters&gt;
+   *  The Kernel to use.
+   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
+   * 
+   * <pre> -kernel-factor
+   *  Defines a factor for the kernel.
+   *   - RBFKernel: a factor for gamma
+   *    Standardize: 1/(2*N)
+   *    Normalize..: 6/N
+   *  Available parameters are:
+   *   N for # of instances, A for # of attributes
+   *  (default: 1)</pre>
+   * 
+   * <pre> -P &lt;classname and parameters&gt;
+   *  The Filter used for preprocessing (use weka.filters.AllFilter
+   *  to disable preprocessing).
+   *  (default: weka.filters.unsupervised.attribute.Center)</pre>
+   * 
+   * <pre> 
+   * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
+   * </pre>
+   * 
+   * <pre> -D
+   *  Enables debugging output (if available) to be printed.
+   *  (default: off)</pre>
+   * 
+   * <pre> -no-checks
+   *  Turns off all checks - use with caution!
+   *  (default: checks on)</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  The size of the cache (a prime number), 0 for full cache and 
+   *  -1 to turn it off.
+   *  (default: 250007)</pre>
+   * 
+   * <pre> -E &lt;num&gt;
+   *  The Exponent to use.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -L
+   *  Use lower-order terms.
+   *  (default: no)</pre>
+   * 
+   * <pre> 
+   * Options specific to preprocessing filter weka.filters.unsupervised.attribute.Center:
+   * </pre>
+   * 
+   * <pre> -unset-class-temporarily
+   *  Unsets the class index temporarily before the filter is
+   *  applied to the data.
+   *  (default: no)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    setChecksTurnedOff(Utils.getFlag("no-checks", options));
+
+    tmpStr = Utils.getOption('F', options);
+    if (tmpStr.length() != 0)
+      setInitFile(new File(tmpStr));
+    else 
+      setInitFile(null);
+
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setInitFileClassIndex(tmpStr);
+    else 
+      setInitFileClassIndex("last");
+
+    tmpStr     = Utils.getOption('K', options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setKernel(Kernel.forName(tmpStr, tmpOptions));
+    }
+    
+    tmpStr = Utils.getOption("kernel-factor", options);
+    if (tmpStr.length() != 0)
+      setKernelFactorExpression(tmpStr);
+    else 
+      setKernelFactorExpression("1");
+    
+    tmpStr = Utils.getOption("P", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setPreprocessing((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
+    }
+    else {
+      setPreprocessing(new Center());
+    }
+
+    super.setOptions(options);
+  }	  
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String initFileTipText() {
+    return "The dataset to initialize the filter with.";
+  }
+
+  /**
+   * Gets the file to initialize the filter with, can be null.
+   *
+   * @return 		the file
+   */
+  public File getInitFile() {
+    return m_InitFile;
+  }
+    
+  /**
+   * Sets the file to initialize the filter with, can be null.
+   *
+   * @param value	the file
+   */
+  public void setInitFile(File value) {
+    m_InitFile = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String initFileClassIndexTipText() {
+    return "The class index of the dataset to initialize the filter with (first and last are valid).";
+  }
+
+  /**
+   * Gets the class index of the file to initialize the filter with.
+   *
+   * @return 		the class index
+   */
+  public String getInitFileClassIndex() {
+    return m_InitFileClassIndex.getSingleIndex();
+  }
+    
+  /**
+   * Sets class index of the file to initialize the filter with.
+   *
+   * @param value	the class index
+   */
+  public void setInitFileClassIndex(String value) {
+    m_InitFileClassIndex.setSingleIndex(value);
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelTipText() {
+    return "The kernel to use.";
+  }
+
+  /**
+   * Gets the kernel to use.
+   *
+   * @return 		the kernel
+   */
+  public Kernel getKernel() {
+    return m_Kernel;
+  }
+    
+  /**
+   * Sets the kernel to use.
+   *
+   * @param value	the kernel
+   */
+  public void setKernel(Kernel value) {
+    m_Kernel = value;
+  }
+
+  /**
+   * Disables or enables the checks (which could be time-consuming). Use with
+   * caution!
+   * 
+   * @param value	if true turns off all checks
+   */
+  public void setChecksTurnedOff(boolean value) {
+    m_checksTurnedOff = value;
+  }
+  
+  /**
+   * Returns whether the checks are turned off or not.
+   * 
+   * @return		true if the checks are turned off
+   */
+  public boolean getChecksTurnedOff() {
+    return m_checksTurnedOff;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String checksTurnedOffTipText() {
+    return "Turns time-consuming checks off - use with caution.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String kernelFactorExpressionTipText() {
+    return "The factor for the kernel, with A = # of attributes and N = # of instances.";
+  }
+
+  /**
+   * Gets the expression for the kernel.
+   *
+   * @return 		the expression
+   */
+  public String getKernelFactorExpression() {
+    return m_KernelFactorExpression;
+  }
+    
+  /**
+   * Sets the expression for the kernel.
+   *
+   * @param value	the file
+   */
+  public void setKernelFactorExpression(String value) {
+    m_KernelFactorExpression = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String preprocessingTipText() {
+    return "Sets the filter to use for preprocessing (use the AllFilter for no preprocessing).";
+  }
+
+  /**
+   * Sets the filter to use for preprocessing (use the AllFilter for no 
+   * preprocessing)
+   *
+   * @param value 	the preprocessing filter
+   */
+  public void setPreprocessing(Filter value) {
+    m_Filter       = value;
+    m_ActualFilter = null;
+  }
+
+  /**
+   * Gets the filter used for preprocessing
+   *
+   * @return 		the current preprocessing filter.
+   */
+  public Filter getPreprocessing() {
+    return m_Filter;
+  }
+
+  /**
+   * resets the filter, i.e., m_NewBatch to true and m_FirstBatchDone to
+   * false.
+   */
+  protected void reset() {
+    super.reset();
+    
+    m_Initialized = false;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    return new Instances(inputFormat);
+  }
+  
+  /**
+   * initializes the filter with the given dataset, i.e., the kernel gets
+   * built. Needs to be called before the first call of Filter.useFilter or
+   * batchFinished(), if not the -F option (or setInitFile(File) is used).
+   * 
+   * @param instances	the data to initialize with
+   * @throws Exception	if building of kernel fails
+   */
+  public void initFilter(Instances instances) throws Exception {
+    HashMap	symbols;
+    
+    // determine kernel factor
+    symbols = new HashMap();
+    symbols.put("A", new Double(instances.numAttributes()));
+    symbols.put("N", new Double(instances.numInstances()));
+    m_KernelFactor = MathematicalExpression.evaluate(getKernelFactorExpression(), symbols);
+    
+    // init filters
+    if (!m_checksTurnedOff) {
+      m_Missing = new ReplaceMissingValues();
+      m_Missing.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_Missing); 
+    } 
+    else {
+      m_Missing = null;
+    }
+
+    if (getKernel().getCapabilities().handles(Capability.NUMERIC_ATTRIBUTES)) {
+	boolean onlyNumeric = true;
+	if (!m_checksTurnedOff) {
+	  for (int i = 0; i < instances.numAttributes(); i++) {
+	    if (i != instances.classIndex()) {
+	      if (!instances.attribute(i).isNumeric()) {
+		onlyNumeric = false;
+		break;
+	      }
+	    }
+	  }
+	}
+	
+	if (!onlyNumeric) {
+	  m_NominalToBinary = new NominalToBinary();
+	  m_NominalToBinary.setInputFormat(instances);
+	  instances = Filter.useFilter(instances, m_NominalToBinary);
+	} 
+	else {
+	  m_NominalToBinary = null;
+	}
+    }
+    else {
+      m_NominalToBinary = null;
+    }
+
+    if ((m_Filter != null) && (m_Filter.getClass() != AllFilter.class)) {
+      m_ActualFilter = Filter.makeCopy(m_Filter);
+      m_ActualFilter.setInputFormat(instances);
+      instances = Filter.useFilter(instances, m_ActualFilter);
+    }
+    else {
+      m_ActualFilter = null;
+    }
+
+    m_NumTrainInstances = instances.numInstances();
+
+    // set factor for kernel
+    m_ActualKernel = Kernel.makeCopy(m_Kernel);
+    if (m_ActualKernel instanceof RBFKernel)
+      ((RBFKernel) m_ActualKernel).setGamma(
+	  m_KernelFactor * ((RBFKernel) m_ActualKernel).getGamma());
+    // build kernel
+    m_ActualKernel.buildKernel(instances);
+
+    m_Initialized = true;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    
+    if (getKernel() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getKernel().getCapabilities();
+    }
+
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    // initializing necessary?
+    if (!m_Initialized) {
+      // do we have a file to initialize with?
+      if ((getInitFile() != null) && getInitFile().isFile()) {
+	DataSource source = new DataSource(getInitFile().getAbsolutePath());
+	Instances data = source.getDataSet();
+	m_InitFileClassIndex.setUpper(data.numAttributes() - 1);
+	data.setClassIndex(m_InitFileClassIndex.getIndex());
+	initFilter(data);
+      }
+      else {
+	initFilter(instances);
+      }
+    }
+
+    // apply filters
+    if (m_Missing != null)
+      instances = Filter.useFilter(instances, m_Missing); 
+    if (m_NominalToBinary != null)
+      instances = Filter.useFilter(instances, m_NominalToBinary); 
+    if (m_ActualFilter != null)
+      instances = Filter.useFilter(instances, m_ActualFilter);
+
+    // backup class attribute and remove it
+    double[] classes = instances.attributeToDoubleArray(instances.classIndex());
+    int classIndex = instances.classIndex();
+    instances.setClassIndex(-1);
+    instances.deleteAttributeAt(classIndex);
+
+    // generate new header
+    FastVector atts = new FastVector();
+    for (int j = 0; j < m_NumTrainInstances; j++)
+      atts.addElement(new Attribute("Kernel " + j));
+    atts.addElement(new Attribute("Class"));
+    Instances result = new Instances("Kernel", atts, 0);
+    result.setClassIndex(result.numAttributes() - 1);
+
+    // compute matrix
+    for (int i = 0; i < instances.numInstances(); i++) {
+      double[] k = new double[m_NumTrainInstances + 1];
+      
+      for (int j = 0; j < m_NumTrainInstances; j++) {
+	double v = m_ActualKernel.eval(-1, j, instances.instance(i));
+	k[j] = v;
+      }
+      k[k.length - 1] = classes[i];
+
+      // create new instance
+      Instance in = new DenseInstance(1.0, k);
+      result.add(in);    
+    }
+
+    if (!isFirstBatchDone())
+      setOutputFormat(result);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new KernelFilter(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MILESFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MILESFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MILESFilter.java	(revision 29)
@@ -0,0 +1,362 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MILES.java
+ * Copyright (C) 2008-09 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Version;
+import weka.core.Capabilities.Capability;
+import weka.core.Capabilities;
+import weka.core.RevisionUtils;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.FastVector;
+import weka.core.Option;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import weka.filters.SimpleBatchFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.LinkedList;
+import java.util.Enumeration;
+
+/** 
+ <!-- globalinfo-start -->
+ * Implements the MILES transformation that maps multiple instance bags into a high-dimensional single-instance feature space.<br/>
+ * For more information see:<br/>
+ * <br/>
+ * Y. Chen, J. Bi, J.Z. Wang (2006). MILES: Multiple-instance learning via embedded instance selection. IEEE PAMI. 28(12):1931-1947.<br/>
+ * <br/>
+ * James Foulds, Eibe Frank: Revisiting multiple-instance learning via embedded instance selection. In: 21st Australasian Joint Conference on Artificial Intelligence, 300-310, 2008.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Chen2006,
+ *    author = {Y. Chen and J. Bi and J.Z. Wang},
+ *    journal = {IEEE PAMI},
+ *    number = {12},
+ *    pages = {1931-1947},
+ *    title = {MILES: Multiple-instance learning via embedded instance selection},
+ *    volume = {28},
+ *    year = {2006}
+ * }
+ * 
+ * &#64;inproceedings{Foulds2008,
+ *    author = {James Foulds and Eibe Frank},
+ *    booktitle = {21st Australasian Joint Conference on Artificial Intelligence},
+ *    pages = {300-310},
+ *    publisher = {Springer},
+ *    title = {Revisiting multiple-instance learning via embedded instance selection},
+ *    year = {2008}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the sigma parameter (default: sqrt(800000)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Jimmy Foulds
+ * @author Eibe Frank
+ * @version $Revision: 5987 $
+ */
+public class MILESFilter
+  extends SimpleBatchFilter implements UnsupervisedFilter, OptionHandler, TechnicalInformationHandler
+{
+
+  /** For serialization */
+  static final long serialVersionUID = 4694489111366063853L;
+  
+  /** Index of bag attribute */
+  public static final int BAG_ATTRIBUTE = 1;
+
+  /** Index of label attribute */
+  public static final int LABEL_ATTRIBUTE = 2; 
+
+  /** Sigma parameter (default: square root of 800000) */
+  private double m_sigma = Math.sqrt(800000);
+  
+  /** Linked list of all instances collected */
+  private LinkedList<Instance> m_allInsts = null;
+  
+  /**
+   * Returns the tip text for this property
+   */
+  public String sigmaTipText() {
+
+    return "The value of the sigma parameter.";
+  }
+
+  /**
+   * Sets the sigma parameter.
+   */
+  public void setSigma(double sigma)
+  {
+    m_sigma = sigma;
+  }
+  
+  /**
+   * Gets the sigma parameter.
+   */
+  public double getSigma()
+  {
+    return m_sigma;
+  }
+
+  /**
+   * Global info for the filter.
+   */
+  public String globalInfo() {
+    return   "Implements the MILES transformation that maps multiple instance bags into"
+      + " a high-dimensional single-instance feature space."
+      + "\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+      
+    result = new TechnicalInformation(Type.ARTICLE);
+    result.setValue(Field.AUTHOR, "Y. Chen and J. Bi and J.Z. Wang");
+    result.setValue(Field.TITLE, "MILES: Multiple-instance learning via embedded instance selection");
+    result.setValue(Field.JOURNAL, "IEEE PAMI");
+    result.setValue(Field.YEAR, "2006");
+    result.setValue(Field.VOLUME, "28");
+    result.setValue(Field.PAGES, "1931-1947");
+    result.setValue(Field.NUMBER, "12");
+    
+    additional = result.add(Type.INPROCEEDINGS);
+    additional.setValue(Field.AUTHOR, "James Foulds and Eibe Frank");
+    additional.setValue(Field.TITLE, "Revisiting multiple-instance learning via embedded instance selection");
+    additional.setValue(Field.BOOKTITLE, "21st Australasian Joint Conference on Artificial Intelligence");
+    additional.setValue(Field.YEAR, "2008");
+    additional.setValue(Field.PAGES, "300-310");
+    additional.setValue(Field.PUBLISHER, "Springer");
+    
+    return result;
+  }
+
+  /**
+   * Capabilities for the filter.
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    return result;
+  }
+
+  /**
+   * Determines the output format for the filter.
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) {
+
+    // Create attributes
+    FastVector atts = new FastVector();
+    m_allInsts = new LinkedList<Instance>();
+    for (int i = 0; i < getInputFormat().numInstances(); i++)
+      {
+        Instances bag = getInputFormat().instance(i).relationalValue(BAG_ATTRIBUTE);
+        for (int j = 0; j < bag.numInstances(); j++) 
+          {
+            m_allInsts.add(bag.instance(j));
+          }
+      }
+    for (int i = 0; i < m_allInsts.size(); i++)
+      {
+        atts.addElement(new Attribute("" + i));
+      }
+    atts.addElement(inputFormat.attribute(LABEL_ATTRIBUTE)); //class
+  
+    //TODO set relation name properly
+    Instances returner = new Instances("", atts, 0);
+    returner.setClassIndex(returner.numAttributes() - 1);
+
+    return returner;
+  }
+
+  /**
+   * Processes a set of instances.
+   */
+  protected Instances process(Instances inst)
+  {
+    
+    // Get instances object with correct output format
+    Instances result = getOutputFormat();
+    result.setClassIndex(result.numAttributes() - 1);
+
+    // Can't do much if bag is empty
+    if (inst.numInstances() == 0)
+      {
+        return result;
+      }
+    
+    // Go through all the instances in the bag to be transformed
+    for (int i = 0; i < inst.numInstances(); i++) //for every bag
+      {
+
+        // Allocate memory for instance
+        double[] outputInstance = new double[result.numAttributes()];
+	
+        // Get the bag
+        Instances bag = inst.instance(i).relationalValue(BAG_ATTRIBUTE);
+        int k = 0;
+        for (Instance x_k : m_allInsts) //for every instance in every bag
+          {
+            //TODO handle empty bags
+            double dSquared = Double.MAX_VALUE;
+            for (int j = 0; j < bag.numInstances(); j++) //for every instance in the current bag
+              {
+                // Compute sum of squared differences
+                double total = 0;
+                Instance x_ij = bag.instance(j);
+                double numMissingValues = 0;
+                for (int l = 0; l < x_k.numAttributes(); l++) //for every attribute
+                  {
+                    // Can skip missing values in reference instance
+                    if (x_k.isMissing(l)) {
+                      continue;
+                    }
+                    // Need to keep track of how many values in current instance are missing
+                    if (!x_ij.isMissing(l)) {
+                      total += (x_ij.value(l) - x_k.value(l)) * (x_ij.value(l) - x_k.value(l));
+                    } else {
+                      numMissingValues++;
+                    }
+                  }
+                // Adjust for missing values
+                total *= x_k.numAttributes() / (x_k.numAttributes() - numMissingValues);
+
+                // Update minimum
+                if (total < dSquared || dSquared == Double.MAX_VALUE) 
+                  {
+                    dSquared = total;
+                  }
+              }
+            if (dSquared == Double.MAX_VALUE)
+              outputInstance[k] = 0; //TODO is this ok?
+            else
+              outputInstance[k] = Math.exp(-1.0 * dSquared / (m_sigma * m_sigma));
+            k++;
+          }
+
+        // Set class label
+        double label = inst.instance(i).value(LABEL_ATTRIBUTE);
+        outputInstance[outputInstance.length - 1] = label;
+      
+        // Add instance to result
+        result.add(new DenseInstance(inst.instance(i).weight(), outputInstance));
+      }
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    FastVector newVector = new FastVector(1);
+    
+    newVector.addElement(new Option(
+                                    "\tSpecify the sigma parameter (default: sqrt(800000)",
+                                    "S", 1, "-S <num>"));
+    
+    return newVector.elements();
+  }
+  
+  
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the sigma parameter (default: sqrt(800000)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String sigmaString = Utils.getOption('S', options);
+    if (sigmaString.length() != 0) {
+      setSigma(Double.parseDouble(sigmaString));
+    } else {
+      setSigma(Math.sqrt(800000));
+    }
+  }
+  
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    
+    String [] options = new String [2];
+    int current = 0;
+    
+    options[current++] = "-S"; options[current++] = "" + getSigma();
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  public static void main(String[] args)
+  {
+    runFilter(new MILESFilter(), args);
+  }
+  
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MakeIndicator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MakeIndicator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MakeIndicator.java	(revision 29)
@@ -0,0 +1,466 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MakeIndicator.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter that creates a new dataset with a boolean attribute replacing a nominal attribute.  In the new dataset, a value of 1 is assigned to an instance that exhibits a particular range of attribute values, a 0 to an instance that doesn't. The boolean attribute is coded as numeric by default.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index.</pre>
+ * 
+ * <pre> -V &lt;index1,index2-index4,...&gt;
+ *  Specify the list of values to indicate. First and last are
+ *  valid indexes (default last)</pre>
+ * 
+ * <pre> -N &lt;index&gt;
+ *  Set if new boolean attribute nominal.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class MakeIndicator 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 766001176862773163L;
+  
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The value's index */
+  private Range m_ValIndex;
+  
+  /** Make boolean attribute numeric. */
+  private boolean m_Numeric = true;
+
+  /**
+   * Constructor
+   */
+  public MakeIndicator() {
+
+      m_ValIndex = new Range("last");
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnsupportedAttributeTypeException the selecte attribute is not nominal
+   * @throws UnsupportedAttributeTypeException the selecte attribute has fewer than two values.
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    m_ValIndex.setUpper(instanceInfo.attribute(m_AttIndex.
+					       getIndex()).numValues() - 1);
+    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isNominal()) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute not nominal.");
+    }
+    if (instanceInfo.attribute(m_AttIndex.getIndex()).numValues() < 2) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute has less " +
+						  "than two values.");
+    }
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance newInstance = (Instance)instance.copy();
+    if (!newInstance.isMissing(m_AttIndex.getIndex())) {
+      if (m_ValIndex.isInRange((int)newInstance.value(m_AttIndex.getIndex()))) {
+	newInstance.setValue(m_AttIndex.getIndex(), 1);
+      } else {
+	newInstance.setValue(m_AttIndex.getIndex(), 0);
+      }
+    }
+    push(newInstance);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+              "\tSets the attribute index.",
+              "C", 1, "-C <col>"));
+
+    newVector.addElement(new Option(
+              "\tSpecify the list of values to indicate. First and last are\n"+
+              "\tvalid indexes (default last)",
+              "V", 1, "-V <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+              "\tSet if new boolean attribute nominal.",
+              "N", 0, "-N <index>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index.</pre>
+   * 
+   * <pre> -V &lt;index1,index2-index4,...&gt;
+   *  Specify the list of values to indicate. First and last are
+   *  valid indexes (default last)</pre>
+   * 
+   * <pre> -N &lt;index&gt;
+   *  Set if new boolean attribute nominal.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String attIndex = Utils.getOption('C', options);
+    if (attIndex.length() != 0) {
+      setAttributeIndex(attIndex);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    String valIndex = Utils.getOption('V', options);
+    if (valIndex.length() != 0) {
+      setValueIndices(valIndex);
+    } else {
+      setValueIndices("last");
+    }
+
+    setNumeric(!Utils.getFlag('N', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [5];
+    int current = 0;
+
+    options[current++] = "-C";
+    options[current++] = "" + (getAttributeIndex());
+    options[current++] = "-V"; 
+    options[current++] = getValueIndices();
+    if (!getNumeric()) {
+      options[current++] = "-N"; 
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A filter that creates a new dataset with a boolean attribute "
+      + "replacing a nominal attribute.  In the new dataset, a value of 1 is "
+      + "assigned to an instance that exhibits a particular range of attribute "
+      + "values, a 0 to an instance that doesn't. The boolean attribute is "
+      + "coded as numeric by default.";
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "Sets which attribute should be replaced by the indicator. This "
+      + "attribute must be nominal.";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * Get the range containing the indicator values.
+   *
+   * @return the range containing the indicator values
+   */
+  public Range getValueRange() {
+
+    return m_ValIndex;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String valueIndicesTipText() {
+
+    return "Specify range of nominal values to act on."
+      + " This is a comma separated list of attribute indices (numbered from"
+      + " 1), with \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the indices of the indicator values.
+   *
+   * @return the indices of the indicator values
+   */
+  public String getValueIndices() {
+
+    return m_ValIndex.getRanges();
+  }
+
+  /**
+   * Sets indices of the indicator values.
+   *
+   * @param range the string representation of the indicator value indices
+   * @see Range
+   */
+  public void setValueIndices(String range) {
+    
+    m_ValIndex.setRanges(range);
+  }
+
+  /**
+   * Sets index of the indicator value.
+   *
+   * @param index the index of the indicator value
+   */
+  public void setValueIndex(int index) {
+
+    setValueIndices("" +  (index + 1));
+  }
+
+  /**
+   * Set which attributes are to be deleted (or kept if invert is true)
+   *
+   * @param indices an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws InvalidArgumentException if an invalid set of ranges is supplied
+   */
+  public void setValueIndicesArray(int [] indices) {
+    
+    setValueIndices(Range.indicesToRangeList(indices));
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numericTipText() {
+
+    return "Determines whether the output indicator attribute is numeric. If "
+      + "this is set to false, the output attribute will be nominal.";
+  }
+
+  /**
+   * Sets if the new Attribute is to be numeric.
+   *
+   * @param bool true if new Attribute is to be numeric
+   */
+  public void setNumeric(boolean bool) {
+
+    m_Numeric = bool;
+  }
+
+  /**
+   * Check if new attribute is to be numeric.
+   *
+   * @return true if new attribute is to be numeric
+   */
+  public boolean getNumeric() {
+
+    return m_Numeric;
+  }
+
+  /**
+   * Set the output format.
+   */
+  private void setOutputFormat() {
+    
+    Instances newData;
+    FastVector newAtts, newVals;
+      
+    // Compute new attributes
+    
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (j != m_AttIndex.getIndex()) {
+
+	// We don't have to copy the attribute because the
+	// attribute index remains unchanged.
+	newAtts.addElement(att);
+      } else {
+	if (m_Numeric) {
+	  newAtts.addElement(new Attribute(att.name()));
+	} else {
+          String vals;
+          int [] sel = m_ValIndex.getSelection();
+          if (sel.length == 1) {
+            vals = att.value(sel[0]);
+          } else {
+            vals = m_ValIndex.getRanges().replace(',','_');
+          }
+	  newVals = new FastVector(2);
+	  newVals.addElement("neg_" + vals);
+	  newVals.addElement("pos_" + vals);
+	  newAtts.addElement(new Attribute(att.name(), newVals));
+	}
+      }
+    }
+
+    // Construct new header
+    newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+ 
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new MakeIndicator(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MathExpression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MathExpression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MathExpression.java	(revision 29)
@@ -0,0 +1,569 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MathExpression.java
+ *    Copyright (C) 2004 Prados Julien
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MathematicalExpression;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.mathematicalexpression.Parser;
+import weka.core.mathematicalexpression.Scanner;
+import java_cup.runtime.DefaultSymbolFactory;
+import java_cup.runtime.SymbolFactory;
+import weka.filters.UnsupervisedFilter;
+
+import java.io.ByteArrayInputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Modify numeric attributes according to a given expression
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ * <pre> -E &lt;expression&gt;
+ *  Specify the expression to apply. Eg. pow(A,6)/(MEAN+MAX)
+ *  Supported operators are +, -, *, /, pow, log,
+ *  abs, cos, exp, sqrt, tan, sin, ceil, floor, rint, (, ), 
+ *  MEAN, MAX, MIN, SD, COUNT, SUM, SUMSQUARED, ifelse</pre>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to ignore. First and last are valid
+ *  indexes. (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. only modify specified columns)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @author Prados Julien (julien.prados@cui.unige.ch) 
+ * @version $Revision: 5987 $
+ * @see MathematicalExpression
+ */
+public class MathExpression 
+  extends PotentialClassIgnorer 
+  implements UnsupervisedFilter {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3713222714671997901L;
+  
+  /** Stores which columns to select as a funky range */
+  protected Range m_SelectCols = new Range();
+    
+  /** The default modification expression */
+  public static final String m_defaultExpression = "(A-MIN)/(MAX-MIN)";
+
+  /** The modification expression */
+  private String m_expression = m_defaultExpression;
+  
+  /** Attributes statistics */
+  private AttributeStats[] m_attStats;
+  
+  /**
+   * Constructor
+   */
+  public MathExpression() {
+    super();
+    setInvertSelection(false);
+  }  
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Modify numeric attributes according to a given expression ";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+    m_SelectCols.setUpper(instanceInfo.numAttributes() - 1);
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_attStats = null;
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_attStats == null) {
+      bufferInput(instance);
+      return false;
+    } else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_attStats == null) {
+      Instances input = getInputFormat();
+
+      m_attStats = new AttributeStats [input.numAttributes()];
+      
+      for (int i = 0; i < input.numAttributes(); i++) {
+	if (input.attribute(i).isNumeric() &&
+	    (input.classIndex() != i)) {
+	  m_attStats[i] = input.attributeStats(i);
+	}
+      }
+
+      // Convert pending input instances
+      for(int i = 0; i < input.numInstances(); i++) {
+	convertInstance(input.instance(i));
+      }
+    } 
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Evaluates the symbols.
+   * 
+   * @param symbols 	the symbols to use for evaluation
+   * @return		the calculated value, Double.NaN in case of an error
+   */
+  protected double eval(HashMap symbols) {
+    SymbolFactory 		sf;
+    ByteArrayInputStream 	parserInput;
+    Parser 			parser;
+    double			result;
+    
+    try {
+      sf          = new DefaultSymbolFactory();
+      parserInput = new ByteArrayInputStream(m_expression.getBytes());
+      parser      = new Parser(new Scanner(parserInput, sf), sf);
+      parser.setSymbols(symbols);
+      parser.parse();
+      result = parser.getResult();
+    }
+    catch (Exception e) {
+      result = Double.NaN;
+      e.printStackTrace();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   * @throws Exception if instance cannot be converted
+   */
+  private void convertInstance(Instance instance) throws Exception {
+  
+    Instance inst = null;
+    HashMap symbols = new HashMap(5);
+    if (instance instanceof SparseInstance) {
+      double[] newVals = new double[instance.numAttributes()];
+      int[] newIndices = new int[instance.numAttributes()];
+      double[] vals = instance.toDoubleArray();
+      int ind = 0;
+      double value;
+      for (int j = 0; j < instance.numAttributes(); j++) {
+        if (m_SelectCols.isInRange(j)) {          
+	  if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+              symbols.put("A", new Double(vals[j]));  
+              symbols.put("MAX", new Double(m_attStats[j].numericStats.max));
+              symbols.put("MIN", new Double(m_attStats[j].numericStats.min));
+              symbols.put("MEAN", new Double(m_attStats[j].numericStats.mean));
+              symbols.put("SD", new Double(m_attStats[j].numericStats.stdDev));
+              symbols.put("COUNT", new Double(m_attStats[j].numericStats.count));
+              symbols.put("SUM", new Double(m_attStats[j].numericStats.sum));
+              symbols.put("SUMSQUARED", new Double(m_attStats[j].numericStats.sumSq));
+              value = eval(symbols);
+              if (Double.isNaN(value) || Double.isInfinite(value)) {
+                  System.err.println("WARNING:Error in evaluating the expression: missing value set");
+                  value = Utils.missingValue();
+              }
+	      if (value != 0.0) {
+	        newVals[ind] = value;
+	        newIndices[ind] = j;
+	        ind++;
+	      }
+	      
+	  }
+        } else {
+          value = vals[j];
+          if (value != 0.0) {
+            newVals[ind] = value;
+            newIndices[ind] = j;
+            ind++;
+          }
+        }
+      }	
+      double[] tempVals = new double[ind];
+      int[] tempInd = new int[ind];
+      System.arraycopy(newVals, 0, tempVals, 0, ind);
+      System.arraycopy(newIndices, 0, tempInd, 0, ind);
+      inst = new SparseInstance(instance.weight(), tempVals, tempInd,
+                                instance.numAttributes());
+    } else {
+      double[] vals = instance.toDoubleArray();
+      for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+        if (m_SelectCols.isInRange(j)) {
+	  if (instance.attribute(j).isNumeric() &&
+	      (!Utils.isMissingValue(vals[j])) &&
+	      (getInputFormat().classIndex() != j)) {
+              symbols.put("A", new Double(vals[j]));  
+              symbols.put("MAX", new Double(m_attStats[j].numericStats.max));
+              symbols.put("MIN", new Double(m_attStats[j].numericStats.min));
+              symbols.put("MEAN", new Double(m_attStats[j].numericStats.mean));
+              symbols.put("SD", new Double(m_attStats[j].numericStats.stdDev));
+              symbols.put("COUNT", new Double(m_attStats[j].numericStats.count));
+              symbols.put("SUM", new Double(m_attStats[j].numericStats.sum));
+              symbols.put("SUMSQUARED", new Double(m_attStats[j].numericStats.sumSq));
+              vals[j] = eval(symbols);
+              if (Double.isNaN(vals[j]) || Double.isInfinite(vals[j])) {
+                  System.err.println("WARNING:Error in Evaluation the Expression: missing value set");
+                  vals[j] = Utils.missingValue();
+              }
+	  }
+        }
+      }
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(instance.dataset());
+    push(inst);
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -unset-class-temporarily
+   *  Unsets the class index temporarily before the filter is
+   *  applied to the data.
+   *  (default: no)</pre>
+   * 
+   * <pre> -E &lt;expression&gt;
+   *  Specify the expression to apply. Eg. pow(A,6)/(MEAN+MAX)
+   *  Supported operators are +, -, *, /, pow, log,
+   *  abs, cos, exp, sqrt, tan, sin, ceil, floor, rint, (, ), 
+   *  MEAN, MAX, MIN, SD, COUNT, SUM, SUMSQUARED, ifelse</pre>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to ignore. First and last are valid
+   *  indexes. (default none)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense (i.e. only modify specified columns)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    super.setOptions(options);
+
+    String expString = Utils.getOption('E', options);
+    if (expString.length() != 0) {
+      setExpression(expString);
+    } else {
+      setExpression(m_defaultExpression);
+    }
+    
+    String ignoreList = Utils.getOption('R', options);
+    if (ignoreList.length() != 0) {
+      setIgnoreRange(ignoreList);
+    }
+
+    setInvertSelection(Utils.getFlag('V', options));
+  }
+  
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector        result;
+    String[]      options;
+    int           i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-E");
+    result.add(getExpression());
+    
+    if (getInvertSelection())
+      result.add("-V");
+
+    if (!getIgnoreRange().equals("")) {
+      result.add("-R");
+      result.add(getIgnoreRange());
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+	"\tSpecify the expression to apply. Eg. pow(A,6)/(MEAN+MAX)"
+	+"\n\tSupported operators are +, -, *, /, pow, log,"
+	+"\n\tabs, cos, exp, sqrt, tan, sin, ceil, floor, rint, (, ), "
+	+"\n\tMEAN, MAX, MIN, SD, COUNT, SUM, SUMSQUARED, ifelse",
+	"E",1,"-E <expression>"));
+    
+    result.addElement(new Option(
+	"\tSpecify list of columns to ignore. First and last are valid\n"
+	+"\tindexes. (default none)",
+	"R", 1, "-R <index1,index2-index4,...>"));
+    
+    result.addElement(new Option(
+	"\tInvert matching sense (i.e. only modify specified columns)",
+	"V", 0, "-V"));
+    
+    return result.elements();
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String expressionTipText() {
+    return "Specify the expression to apply. The 'A' letter"
+             + "refers to the attribute value. MIN,MAX,MEAN,SD"
+             + "refer respectively to minimum, maximum, mean and"
+             + "standard deviation of the attribute."
+	     +"\n\tSupported operators are +, -, *, /, pow, log,"
+             +"abs, cos, exp, sqrt, tan, sin, ceil, floor, rint, (, ),"
+             +"A,MEAN, MAX, MIN, SD, COUNT, SUM, SUMSQUARED, ifelse"
+             +"\n\tEg. pow(A,6)/(MEAN+MAX)*ifelse(A<0,0,sqrt(A))+ifelse(![A>9 && A<15])";
+  }
+  
+  /**
+   * Set the expression to apply
+   * @param expr a mathematical expression to apply
+   */
+  public void setExpression(String expr) {
+    m_expression = expr;
+  }
+
+  /**
+   * Get the expression
+   * @return the expression
+   */
+  public String getExpression() {
+    return m_expression;
+  }
+  
+    /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Determines whether action is to select or unselect."
+      + " If set to true, only the specified attributes will be modified;"
+      + " If set to false, specified attributes will not be modified.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be select or unselect
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return !m_SelectCols.getInvert();
+  }
+
+  /**
+   * Set whether selected columns should be select or unselect. If true the 
+   * selected columns are modified. If false the selected columns are not
+   * modified.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_SelectCols.setInvert(!invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String ignoreRangeTipText() {
+
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the current range selection.
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getIgnoreRange() {
+
+    return m_SelectCols.getRanges();
+  }
+
+  /**
+   * Set which attributes are to be ignored
+   *
+   * @param rangeList a string representing the list of attributes.  Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br/>
+   * eg: first-3,5,6-last
+   */
+  public void setIgnoreRange(String rangeList) {
+
+    m_SelectCols.setRanges(rangeList);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new MathExpression(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeManyValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeManyValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeManyValues.java	(revision 29)
@@ -0,0 +1,457 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MergeManyValues.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ * 
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Merges many values of a nominal attribute into one value.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index
+ *  (default: last)</pre>
+ * 
+ * <pre> -M &lt;label&gt;
+ *  Sets the label of the newly merged classes
+ *  (default: 'merged')</pre>
+ * 
+ * <pre> -R &lt;range&gt;
+ *  Sets the merge range. 'first and 'last' are accepted as well.'
+ *  E.g.: first-5,7,9,20-last
+ *  (default: 1,2)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Kathryn Hempstalk (kah18 at cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class MergeManyValues 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4649332102154713625L;
+
+  /** The attribute's index setting. */
+  protected SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The first value's index setting. */
+  protected String m_Label = "merged";
+
+  /** The merge value's index setting. */
+  protected Range m_MergeRange = new Range("1,2");
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Merges many values of a nominal attribute into one value.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+	"\tSets the attribute index\n"
+	+ "\t(default: last)",
+	"C", 1, "-C <col>"));
+
+    newVector.addElement(new Option(
+	"\tSets the label of the newly merged classes\n"
+	+ "\t(default: 'merged')",
+	"M", 1, "-M <label>"));
+
+    newVector.addElement(new Option(
+	"\tSets the merge range. 'first and 'last' are accepted as well.'\n"
+	+ "\tE.g.: first-5,7,9,20-last\n"
+	+ "\t(default: 1,2)",
+	"R", 1, "-R <range>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index
+   *  (default: last)</pre>
+   * 
+   * <pre> -M &lt;label&gt;
+   *  Sets the label of the newly merged classes
+   *  (default: 'merged')</pre>
+   * 
+   * <pre> -R &lt;range&gt;
+   *  Sets the merge range. 'first and 'last' are accepted as well.'
+   *  E.g.: first-5,7,9,20-last
+   *  (default: 1,2)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0) {
+      setAttributeIndex(tmpStr);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    tmpStr = Utils.getOption('L', options);
+    if (tmpStr.length() != 0) {
+      setLabel(tmpStr);
+    } else {
+      setLabel("merged");
+    }
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0) {
+      setMergeValueRange(tmpStr);
+    } else {
+      setMergeValueRange("1,2");
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+    
+    result.add("-C");
+    result.add(getAttributeIndex());
+    
+    result.add("-L");
+    result.add(getLabel());
+
+    result.add("-R"); 
+    result.add(getMergeValueRange());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the structure 
+   * 				is required).
+   * @return 			true if the outputFormat may be collected immediately
+   * @throws Exception 		if the input format can't be set 
+   * 				successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+    
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+
+    m_MergeRange.setUpper(instanceInfo.attribute(m_AttIndex.getIndex()).numValues() - 1);
+    if ((instanceInfo.classIndex() > -1) && (instanceInfo.classIndex() == m_AttIndex.getIndex())) {
+      throw new Exception("Cannot process class attribute.");
+    }
+    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isNominal()) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute not nominal.");
+    }
+    if (instanceInfo.attribute(m_AttIndex.getIndex()).numValues() < 2) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute has less than " +
+      "two values.");
+    }
+
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Set the output format. Takes the current average class values
+   * and m_InputFormat and calls setOutputFormat(Instances) 
+   * appropriately.
+   */
+  private void setOutputFormat() {
+    Instances newData;
+    FastVector newAtts, newVals;
+
+    // Compute new attributes
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (j != m_AttIndex.getIndex()) {
+	newAtts.addElement(att.copy());
+      } else {
+
+	// Compute list of attribute values	  
+	newVals = new FastVector(att.numValues() - 1);
+	for (int i = 0; i < att.numValues(); i++) {
+	  boolean inMergeList = false;
+
+	  if(att.value(i).equalsIgnoreCase(m_Label)){
+	    //don't want to add this one.
+	    inMergeList = true;		
+	  }else{
+	    inMergeList = m_MergeRange.isInRange(i);
+	  }
+
+	  if(!inMergeList){
+	    //add it.
+	    newVals.addElement(att.value(i));
+	  }
+	}
+	newVals.addElement(m_Label);
+
+
+	newAtts.addElement(new Attribute(att.name(), newVals));
+      }
+    }
+
+    // Construct new header
+    newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    setOutputFormat(newData);
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance 	the input instance
+   * @return 		true if the filtered instance may now be
+   * 			collected with output().
+   * @throws IllegalStateException	if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    Attribute att = getInputFormat().attribute(m_AttIndex.getIndex());
+    FastVector newVals = new FastVector(att.numValues() - 1);
+    for (int i = 0; i < att.numValues(); i++) {
+      boolean inMergeList = false;
+
+      if(att.value(i).equalsIgnoreCase(m_Label)){
+	//don't want to add this one.
+	inMergeList = true;		
+      }else{
+	inMergeList = m_MergeRange.isInRange(i);
+      }
+
+      if(!inMergeList){
+	//add it.
+	newVals.addElement(att.value(i));
+      }
+    }
+    newVals.addElement(m_Label);
+
+    Attribute temp = new Attribute(att.name(), newVals);
+
+    Instance newInstance = (Instance)instance.copy();    
+    if (!newInstance.isMissing(m_AttIndex.getIndex())) {
+      String currValue = newInstance.stringValue(m_AttIndex.getIndex());
+      if(temp.indexOfValue(currValue) == -1)
+	newInstance.setValue(m_AttIndex.getIndex(), temp.indexOfValue(m_Label));
+      else
+	newInstance.setValue(m_AttIndex.getIndex(), temp.indexOfValue(currValue));
+    }
+
+    push(newInstance);
+    return true;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+    return "Sets which attribute to process. This "
+    + "attribute must be nominal (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String labelTipText() {
+    return "The new label for the merged values.";
+  }
+
+  /**
+   * Get the label for the new merged class.
+   *
+   * @return the label for the merged class.
+   */
+  public String getLabel() {
+    return m_Label;
+  }
+
+  /**
+   * Sets label of the merged class.
+   *
+   * @param alabel the new label.
+   */
+  public void setLabel(String alabel) {
+    m_Label = alabel;
+  }
+
+  /**
+   * Get the range of the merge values used.
+   *
+   * @return the range of the merge values
+   */
+  public String getMergeValueRange() {
+    return m_MergeRange.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String mergeValueRangeTipText() {
+    return "The range of values to merge.";
+  }
+
+  /**
+   * Sets range of the merge values used.
+   *
+   * @param range the range of the merged values
+   */
+  public void setMergeValueRange(String range) {
+    m_MergeRange.setRanges(range);
+  }
+
+  /**
+   * Returns the revision string.
+   *
+   * @return The revision string.
+   */
+  public String getRevision(){
+    return "$Revision: 5987 $";
+  }
+
+  /**
+   * Main method for executing this filter.
+   *
+   * @param args 	use -h to display all options
+   */
+  public static void main(String[] args) {
+    runFilter(new MergeManyValues(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeTwoValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeTwoValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MergeTwoValues.java	(revision 29)
@@ -0,0 +1,464 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MergeTwoValues.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Merges two values of a nominal attribute into one value.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index (default last).</pre>
+ * 
+ * <pre> -F &lt;value index&gt;
+ *  Sets the first value's index (default first).</pre>
+ * 
+ * <pre> -S &lt;value index&gt;
+ *  Sets the second value's index (default last).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class MergeTwoValues 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 2925048980504034018L;
+  
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The first value's index setting. */
+  private SingleIndex m_FirstIndex = new SingleIndex("first");
+
+  /** The second value's index setting. */
+  private SingleIndex m_SecondIndex = new SingleIndex("last");
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Merges two values of a nominal attribute into one value.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    m_FirstIndex.setUpper(instanceInfo.
+			  attribute(m_AttIndex.getIndex()).numValues() - 1);
+    m_SecondIndex.setUpper(instanceInfo.
+			   attribute(m_AttIndex.getIndex()).numValues() - 1);
+    if ((instanceInfo.classIndex() > -1) && (instanceInfo.classIndex() == m_AttIndex.getIndex())) {
+      throw new Exception("Cannot process class attribute.");
+    }
+    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isNominal()) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute not nominal.");
+    }
+    if (instanceInfo.attribute(m_AttIndex.getIndex()).numValues() < 2) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute has less than " +
+						  "two values.");
+    }
+    if (m_SecondIndex.getIndex() <= m_FirstIndex.getIndex()) {
+      // XXX Maybe we should just swap the values??
+      throw new Exception("The second index has to be greater "+
+			  "than the first.");
+    }
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance newInstance = (Instance)instance.copy();
+    if ((int)newInstance.value(m_AttIndex.getIndex()) == m_SecondIndex.getIndex()) {
+      newInstance.setValue(m_AttIndex.getIndex(), (double)m_FirstIndex.getIndex());
+    }
+    else if ((int)newInstance.value(m_AttIndex.getIndex()) > m_SecondIndex.getIndex()) {
+      newInstance.setValue(m_AttIndex.getIndex(),
+			   newInstance.value(m_AttIndex.getIndex()) - 1);
+    }
+    push(newInstance);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+              "\tSets the attribute index (default last).",
+              "C", 1, "-C <col>"));
+
+    newVector.addElement(new Option(
+              "\tSets the first value's index (default first).",
+              "F", 1, "-F <value index>"));
+
+    newVector.addElement(new Option(
+              "\tSets the second value's index (default last).",
+              "S", 1, "-S <value index>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index (default last).</pre>
+   * 
+   * <pre> -F &lt;value index&gt;
+   *  Sets the first value's index (default first).</pre>
+   * 
+   * <pre> -S &lt;value index&gt;
+   *  Sets the second value's index (default last).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String attIndex = Utils.getOption('C', options);
+    if (attIndex.length() != 0) {
+      setAttributeIndex(attIndex);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    String firstValIndex = Utils.getOption('F', options);
+    if (firstValIndex.length() != 0) {
+      setFirstValueIndex(firstValIndex);
+    } else {
+      setFirstValueIndex("first");
+    }
+
+    String secondValIndex = Utils.getOption('S', options);
+    if (secondValIndex.length() != 0) {
+      setSecondValueIndex(secondValIndex);
+    } else {
+      setSecondValueIndex("last");
+    }
+   
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [6];
+    int current = 0;
+
+    options[current++] = "-C";
+    options[current++] = "" + getAttributeIndex();
+    options[current++] = "-F"; 
+    options[current++] = "" + getFirstValueIndex();
+    options[current++] = "-S"; 
+    options[current++] = "" + getSecondValueIndex();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "Sets which attribute to process. This "
+      + "attribute must be nominal (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String firstValueIndexTipText() {
+
+    return "Sets the first value to be merged. "
+      + "(\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the first value used.
+   *
+   * @return the index of the first value
+   */
+  public String getFirstValueIndex() {
+
+    return m_FirstIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the first value used.
+   *
+   * @param firstIndex the index of the first value
+   */
+  public void setFirstValueIndex(String firstIndex) {
+    
+    m_FirstIndex.setSingleIndex(firstIndex);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String secondValueIndexTipText() {
+
+    return "Sets the second value to be merged. "
+      + "(\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the second value used.
+   *
+   * @return the index of the second value
+   */
+  public String getSecondValueIndex() {
+
+    return m_SecondIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the second value used.
+   *
+   * @param secondIndex the index of the second value
+   */
+  public void setSecondValueIndex(String secondIndex) {
+    
+    m_SecondIndex.setSingleIndex(secondIndex);
+  }
+
+  /**
+   * Set the output format. Takes the current average class values
+   * and m_InputFormat and calls setOutputFormat(Instances) 
+   * appropriately.
+   */
+  private void setOutputFormat() {
+    
+    Instances newData;
+    FastVector newAtts, newVals;
+    boolean firstEndsWithPrime = false, 
+      secondEndsWithPrime = false;
+    StringBuffer text = new StringBuffer();
+      
+    // Compute new attributes
+      
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (j != m_AttIndex.getIndex()) {
+	newAtts.addElement(att.copy());
+      } else {
+	  
+	// Compute new value
+	  
+	if (att.value(m_FirstIndex.getIndex()).endsWith("'")) {
+	  firstEndsWithPrime = true;
+	}
+	if (att.value(m_SecondIndex.getIndex()).endsWith("'")) {
+	  secondEndsWithPrime = true;
+	}
+	if (firstEndsWithPrime || secondEndsWithPrime) {
+	  text.append("'");
+	}
+	if (firstEndsWithPrime) {
+	  text.append(((String)att.value(m_FirstIndex.getIndex())).
+		      substring(1, ((String)att.value(m_FirstIndex.getIndex())).
+				length() - 1));
+	} else {
+	  text.append((String)att.value(m_FirstIndex.getIndex()));
+	}
+	text.append('_');
+	if (secondEndsWithPrime) {
+	  text.append(((String)att.value(m_SecondIndex.getIndex())).
+		      substring(1, ((String)att.value(m_SecondIndex.getIndex())).
+				length() - 1));
+	} else {
+	  text.append((String)att.value(m_SecondIndex.getIndex()));
+	}
+	if (firstEndsWithPrime || secondEndsWithPrime) {
+	  text.append("'");
+	}
+	  
+	// Compute list of attribute values
+	  
+	newVals = new FastVector(att.numValues() - 1);
+	for (int i = 0; i < att.numValues(); i++) {
+	  if (i == m_FirstIndex.getIndex()) {
+	    newVals.addElement(text.toString());
+	  } else if (i != m_SecondIndex.getIndex()) {
+	    newVals.addElement(att.value(i));
+	  }
+	}
+	newAtts.addElement(new Attribute(att.name(), newVals));
+      }
+    }
+      
+    // Construct new header
+      
+    newData = new Instances(getInputFormat().relationName(), newAtts,
+			    0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new MergeTwoValues(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositional.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositional.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositional.java	(revision 29)
@@ -0,0 +1,453 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MultiInstanceToPropositional.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.MultiInstanceCapabilitiesHandler;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RelationalLocator;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.StringLocator;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts the multi-instance dataset into single instance dataset so that the Nominalize, Standardize and other type of filters or transformation  can be applied to these data for the further preprocessing.<br/>
+ * Note: the first attribute of the converted dataset is a nominal attribute and refers to the bagId.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  The type of weight setting for each prop. instance:
+ *  0.weight = original single bag weight /Total number of
+ *  prop. instance in the corresponding bag;
+ *  1.weight = 1.0;
+ *  2.weight = 1.0/Total number of prop. instance in the 
+ *   corresponding bag; 
+ *  3. weight = Total number of prop. instance / (Total number 
+ *   of bags * Total number of prop. instance in the 
+ *   corresponding bag). 
+ *  (default:0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lin Dong (ld21@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ * @see PropositionalToMultiInstance
+ */
+public class MultiInstanceToPropositional 
+  extends Filter 
+  implements OptionHandler, UnsupervisedFilter, MultiInstanceCapabilitiesHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4102847628883002530L;
+
+  /** the total number of bags */
+  protected int m_NumBags;
+
+  /** Indices of string attributes in the bag */
+  protected StringLocator m_BagStringAtts = null;
+
+  /** Indices of relational attributes in the bag */
+  protected RelationalLocator m_BagRelAtts = null;
+  
+  /** the total number of the propositional instance in the dataset */
+  protected int m_NumInstances;
+  
+  /** weight method: keep the weight to be the same as the original value */
+  public static final int WEIGHTMETHOD_ORIGINAL = 0;
+  /** weight method: 1.0 */
+  public static final int WEIGHTMETHOD_1 = 1;
+  /** weight method: 1.0 / Total # of prop. instance in the corresp. bag */
+  public static final int WEIGHTMETHOD_INVERSE1 = 2;
+  /** weight method: Total # of prop. instance / (Total # of bags * Total # of prop. instance in the corresp. bag) */
+  public static final int WEIGHTMETHOD_INVERSE2 = 3;
+  /** weight methods */
+  public static final Tag[] TAGS_WEIGHTMETHOD = {
+    new Tag(WEIGHTMETHOD_ORIGINAL, 
+        "keep the weight to be the same as the original value"),
+    new Tag(WEIGHTMETHOD_1, 
+        "1.0"),
+    new Tag(WEIGHTMETHOD_INVERSE1, 
+        "1.0 / Total # of prop. instance in the corresp. bag"),
+    new Tag(WEIGHTMETHOD_INVERSE2, 
+        "Total # of prop. instance / (Total # of bags * Total # of prop. instance in the corresp. bag)")
+  };
+
+  /** the propositional instance weight setting method */
+  protected int m_WeightMethod = WEIGHTMETHOD_INVERSE2;
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+  
+    result.addElement(new Option(
+          "\tThe type of weight setting for each prop. instance:\n"
+          + "\t0.weight = original single bag weight /Total number of\n"
+          + "\tprop. instance in the corresponding bag;\n"
+          + "\t1.weight = 1.0;\n"
+          + "\t2.weight = 1.0/Total number of prop. instance in the \n"
+          + "\t\tcorresponding bag; \n"
+          + "\t3. weight = Total number of prop. instance / (Total number \n"
+          + "\t\tof bags * Total number of prop. instance in the \n"
+          + "\t\tcorresponding bag). \n"
+          + "\t(default:0)",
+          "A", 1, "-A <num>"));
+    
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  The type of weight setting for each prop. instance:
+   *  0.weight = original single bag weight /Total number of
+   *  prop. instance in the corresponding bag;
+   *  1.weight = 1.0;
+   *  2.weight = 1.0/Total number of prop. instance in the 
+   *   corresponding bag; 
+   *  3. weight = Total number of prop. instance / (Total number 
+   *   of bags * Total number of prop. instance in the 
+   *   corresponding bag). 
+   *  (default:0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String weightString = Utils.getOption('A', options);
+    if (weightString.length() != 0) {
+      setWeightMethod(
+          new SelectedTag(Integer.parseInt(weightString), TAGS_WEIGHTMETHOD));
+    } else {
+      setWeightMethod(
+          new SelectedTag(WEIGHTMETHOD_INVERSE2, TAGS_WEIGHTMETHOD));
+    }	
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+    
+    result.add("-A");
+    result.add("" + m_WeightMethod);
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String weightMethodTipText() {
+    return "The method used for weighting the instances.";
+  }
+
+  /**
+   * The new method for weighting the instances.
+   *
+   * @param method      the new method
+   */
+  public void setWeightMethod(SelectedTag method){
+    if (method.getTags() == TAGS_WEIGHTMETHOD)
+      m_WeightMethod = method.getSelectedTag().getID();
+  }
+
+  /**
+   * Returns the current weighting method for instances.
+   * 
+   * @return		the current weight method
+   */
+  public SelectedTag getWeightMethod(){
+    return new SelectedTag(m_WeightMethod, TAGS_WEIGHTMETHOD);
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return 
+        "Converts the multi-instance dataset into single instance dataset "
+      + "so that the Nominalize, Standardize and other type of filters or transformation "
+      + " can be applied to these data for the further preprocessing.\n"
+      + "Note: the first attribute of the converted dataset is a nominal "
+      + "attribute and refers to the bagId.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.disableAllAttributes();
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    // other
+    result.enable(Capability.ONLY_MULTIINSTANCE);
+    
+    return result;
+  }
+
+  /**
+   * Returns the capabilities of this multi-instance filter for the
+   * relational data (i.e., the bags).
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getMultiInstanceCapabilities() {
+    Capabilities result = new Capabilities(this);
+
+    // attributes
+    result.enableAllAttributes();
+    result.disable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    // other
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+    throws Exception {
+
+    if (instanceInfo.attribute(1).type()!=Attribute.RELATIONAL) {
+      throw new Exception("Can only handle relational-valued attribute!");
+    }  
+    super.setInputFormat(instanceInfo);   
+
+    m_NumBags = instanceInfo.numInstances();
+    m_NumInstances = 0;
+    for (int i=0; i<m_NumBags; i++)
+      m_NumInstances += instanceInfo.instance(i).relationalValue(1).numInstances();
+
+    Attribute classAttribute = (Attribute) instanceInfo.classAttribute().copy();
+    Attribute bagIndex = (Attribute) instanceInfo.attribute(0).copy();
+
+    /* create a new output format (propositional instance format) */
+    Instances newData = instanceInfo.attribute(1).relation().stringFreeStructure();
+    newData.insertAttributeAt(bagIndex, 0);
+    newData.insertAttributeAt(classAttribute, newData.numAttributes());
+    newData.setClassIndex(newData.numAttributes() - 1);
+
+    super.setOutputFormat(newData.stringFreeStructure());
+
+    m_BagStringAtts = new StringLocator(instanceInfo.attribute(1).relation().stringFreeStructure());
+    m_BagRelAtts    = new RelationalLocator(instanceInfo.attribute(1).relation().stringFreeStructure());
+
+    return true;
+  }
+
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    convertInstance(instance);
+    return true;
+
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    Instances input = getInputFormat();
+
+    // Convert pending input instances
+    for(int i = 0; i < input.numInstances(); i++) {
+      convertInstance(input.instance(i));
+    }
+
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single bag over. The converted instances is 
+   * added to the end of the output queue.
+   *
+   * @param bag the bag to convert
+   */
+  private void convertInstance(Instance bag) {
+
+    Instances data = bag.relationalValue(1);
+    int bagSize = data.numInstances();
+    double bagIndex = bag.value(0);
+    double classValue = bag.classValue();
+    double weight = 0.0; 
+    //the proper weight for each instance in a bag 
+    if (m_WeightMethod == WEIGHTMETHOD_1)
+      weight = 1.0;
+    else if (m_WeightMethod == WEIGHTMETHOD_INVERSE1)
+      weight = (double) 1.0 / bagSize;
+    else if (m_WeightMethod == WEIGHTMETHOD_INVERSE2)
+      weight=(double) m_NumInstances / (m_NumBags * bagSize);
+    else 
+      weight = (double) bag.weight() / bagSize;
+
+    Instance newInst;
+    Instances outputFormat = getOutputFormat().stringFreeStructure();
+
+    for (int i = 0; i < bagSize; i++) {
+      newInst = new DenseInstance(outputFormat.numAttributes());
+      newInst.setDataset(outputFormat);
+      newInst.setValue(0,bagIndex);
+      if (!bag.classIsMissing())
+        newInst.setClassValue(classValue);
+      // copy the attribute values to new instance
+      for (int j = 1; j < outputFormat.numAttributes() - 1; j++){
+        newInst.setValue(j,data.instance(i).value(j - 1));
+      }	
+
+      newInst.setWeight(weight);
+
+      // copy strings/relational values
+      StringLocator.copyStringValues(
+	  newInst, false, 
+	  data, m_BagStringAtts, 
+	  outputFormat, m_OutputStringAtts);
+
+      RelationalLocator.copyRelationalValues(
+	  newInst, false, 
+	  data, m_BagRelAtts, 
+	  outputFormat, m_OutputRelAtts);
+      
+      push(newInst);
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new MultiInstanceToPropositional(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToBinary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToBinary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToBinary.java	(revision 29)
@@ -0,0 +1,546 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NominalToBinary.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts all nominal attributes into binary numeric attributes. An attribute with k values is transformed into k binary attributes if the class is nominal (using the one-attribute-per-value approach). Binary attributes are left binary, if option '-A' is not given.If the class is numeric, you might want to use the supervised version of this filter.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N
+ *  Sets if binary attributes are to be coded as nominal ones.</pre>
+ * 
+ * <pre> -A
+ *  For each nominal value a new attribute is created, 
+ *  not only if there are more than 2 values.</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to act on. First and last are 
+ *  valid indexes.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $ 
+ */
+public class NominalToBinary 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1130642825710549138L;
+
+  /** Stores which columns to act on */
+  protected Range m_Columns = new Range();
+
+  /** Are the new attributes going to be nominal or numeric ones? */
+  private boolean m_Numeric = true;
+
+  /** Are all values transformed into new attributes? */
+  private boolean m_TransformAll = false;
+
+  /** Constructor - initialises the filter */
+  public NominalToBinary() {
+
+    setAttributeIndices("first-last");
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Converts all nominal attributes into binary numeric attributes. An "
+      + "attribute with k values is transformed into k binary attributes if "
+      + "the class is nominal (using the one-attribute-per-value approach). "
+      + "Binary attributes are left binary, if option '-A' is not given."
+      + "If the class is numeric, you might want to use the supervised version of "
+      + "this filter.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+
+    m_Columns.setUpper(instanceInfo.numAttributes() - 1);
+
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    convertInstance(instance);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+	"\tSets if binary attributes are to be coded as nominal ones.",
+	"N", 0, "-N"));
+
+    newVector.addElement(new Option(
+	"\tFor each nominal value a new attribute is created, \n"
+	+ "\tnot only if there are more than 2 values.",
+	"A", 0, "-A"));
+
+    newVector.addElement(new Option(
+	"\tSpecifies list of columns to act on. First and last are \n"
+	+ "\tvalid indexes.\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+
+    newVector.addElement(new Option(
+	"\tInvert matching sense of column indexes.",
+	"V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N
+   *  Sets if binary attributes are to be coded as nominal ones.</pre>
+   * 
+   * <pre> -A
+   *  For each nominal value a new attribute is created, 
+   *  not only if there are more than 2 values.</pre>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to act on. First and last are 
+   *  valid indexes.
+   *  (default: first-last)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense of column indexes.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setBinaryAttributesNominal(Utils.getFlag('N', options));
+
+    setTransformAllValues(Utils.getFlag('A', options));
+
+    String convertList = Utils.getOption('R', options);
+    if (convertList.length() != 0) {
+      setAttributeIndices(convertList);
+    } else {
+      setAttributeIndices("first-last");
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [4];
+    int current = 0;
+
+    if (getBinaryAttributesNominal()) {
+      options[current++] = "-N";
+    }
+
+    if (getTransformAllValues()) {
+      options[current++] = "-A";
+    }
+
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binaryAttributesNominalTipText() {
+    return "Whether resulting binary attributes will be nominal.";
+  }
+
+  /**
+   * Gets if binary attributes are to be treated as nominal ones.
+   *
+   * @return true if binary attributes are to be treated as nominal ones
+   */
+  public boolean getBinaryAttributesNominal() {
+
+    return !m_Numeric;
+  }
+
+  /**
+   * Sets if binary attributes are to be treates as nominal ones.
+   *
+   * @param bool true if binary attributes are to be treated as nominal ones
+   */
+  public void setBinaryAttributesNominal(boolean bool) {
+
+    m_Numeric = !bool;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String transformAllValuesTipText() {
+    return "Whether all nominal values are turned into new attributes, not only if there are more than 2.";
+  }
+
+  /**
+   * Gets if all nominal values are turned into new attributes, not only if
+   * there are more than 2.
+   *
+   * @return true all nominal values are transformed into new attributes
+   */
+  public boolean getTransformAllValues() {
+
+    return m_TransformAll;
+  }
+
+  /**
+   * Sets whether all nominal values are transformed into new attributes, not
+   * just if there are more than 2.
+   *
+   * @param bool true if all nominal value are transformed into new attributes
+   */
+  public void setTransformAllValues(boolean bool) {
+
+    m_TransformAll = bool;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Set attribute selection mode. If false, only selected"
+      + " (numeric) attributes in the range will be discretized; if"
+      + " true, only non-selected attributes will be discretized.";
+  }
+
+  /**
+   * Gets whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_Columns.getInvert();
+  }
+
+  /**
+   * Sets whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_Columns.setInvert(invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_Columns.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be acted on.
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String rangeList) {
+
+    m_Columns.setRanges(rangeList);
+  }
+
+  /**
+   * Set the output format if the class is nominal.
+   */
+  private void setOutputFormat() {
+
+    FastVector newAtts;
+    int newClassIndex;
+    StringBuffer attributeName;
+    Instances outputFormat;
+    FastVector vals;
+
+    // Compute new attributes
+
+    newClassIndex = getInputFormat().classIndex();
+    newAtts = new FastVector();
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (!att.isNominal() || (j == getInputFormat().classIndex()) ||
+	  !m_Columns.isInRange(j)) {
+	newAtts.addElement(att.copy());
+      } else {
+	if ( (att.numValues() <= 2) && (!m_TransformAll) ) {
+	  if (m_Numeric) {
+	    newAtts.addElement(new Attribute(att.name()));
+	  } else {
+	    newAtts.addElement(att.copy());
+	  }
+	} else {
+
+	  if (newClassIndex >= 0 && j < getInputFormat().classIndex()) {
+	    newClassIndex += att.numValues() - 1;
+	  }
+
+	  // Compute values for new attributes
+	  for (int k = 0; k < att.numValues(); k++) {
+	    attributeName = 
+	      new StringBuffer(att.name() + "=");
+	    attributeName.append(att.value(k));
+	    if (m_Numeric) {
+	      newAtts.
+		addElement(new Attribute(attributeName.toString()));
+	    } else {
+	      vals = new FastVector(2);
+	      vals.addElement("f"); vals.addElement("t");
+	      newAtts.
+		addElement(new Attribute(attributeName.toString(), vals));
+	    }
+	  }
+	}
+      }
+    }
+    outputFormat = new Instances(getInputFormat().relationName(),
+				 newAtts, 0);
+    outputFormat.setClassIndex(newClassIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Convert a single instance over if the class is nominal. The converted 
+   * instance is added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstance(Instance instance) {
+
+    double [] vals = new double [outputFormatPeek().numAttributes()];
+    int attSoFar = 0;
+
+    for(int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (!att.isNominal() || (j == getInputFormat().classIndex()) ||
+	  !m_Columns.isInRange(j)) {
+	vals[attSoFar] = instance.value(j);
+	attSoFar++;
+      } else {
+	if ( (att.numValues() <= 2) && (!m_TransformAll) ) {
+	  vals[attSoFar] = instance.value(j);
+	  attSoFar++;
+	} else {
+	  if (instance.isMissing(j)) {
+	    for (int k = 0; k < att.numValues(); k++) {
+              vals[attSoFar + k] = instance.value(j);
+	    }
+	  } else {
+	    for (int k = 0; k < att.numValues(); k++) {
+	      if (k == (int)instance.value(j)) {
+                vals[attSoFar + k] = 1;
+	      } else {
+                vals[attSoFar + k] = 0;
+	      }
+	    }
+	  }
+	  attSoFar += att.numValues();
+	}
+      }
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new NominalToBinary(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToString.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToString.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NominalToString.java	(revision 29)
@@ -0,0 +1,323 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NominalToString.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.Range;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts a nominal attribute (i.e. set number of values) to string (i.e. unspecified number of values).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the range of attributes to convert (default last).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class NominalToString
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8655492378380068939L;
+  
+  /** The attribute's index setting. */
+  private Range m_AttIndex = new Range("last");
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Converts a nominal attribute (i.e. set number of values) to string "
+      + "(i.e. unspecified number of values).";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the 
+   * 				structure is required).
+   * @return 			true if the outputFormat may be collected 
+   * 				immediately.
+   * @throws UnsupportedAttributeTypeException 	if the selected attribute
+   * 						a string attribute.
+   * @throws Exception 		if the input format can't be set 
+   * 				successfully.
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    
+    /*    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isNominal())
+      throw new UnsupportedAttributeTypeException("Chosen attribute is not of "
+      + "type nominal."); */
+
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance 		the input instance.
+   * @return 			true if the filtered instance may now be
+   * 				collected with output().
+   * @throws IllegalStateException 	if no input structure has been defined.
+   */
+  public boolean input(Instance instance) {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    if (isOutputFormatDefined()) {
+      Instance newInstance = (Instance) instance.copy();
+      push(newInstance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+
+  /**
+   * Signifies that this batch of input to the filter is finished. If the 
+   * filter requires all instances prior to filtering, output() may now 
+   * be called to retrieve the filtered instances.
+   *
+   * @return 		true if there are instances pending output.
+   * @throws IllegalStateException 	if no input structure has been defined.
+   */
+  public boolean batchFinished() {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (!isOutputFormatDefined()) {
+      setOutputFormat();
+
+      // Convert pending input instances
+      for(int i = 0; i < getInputFormat().numInstances(); i++)
+	push((Instance) getInputFormat().instance(i).copy());
+    } 
+
+    flushInput();
+    m_NewBatch = true;
+
+    return (numPendingOutput() != 0);
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSets the range of attributes to convert (default last).",
+	"C", 1, "-C <col>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the range of attributes to convert (default last).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setAttributeIndexes(tmpStr);
+    else
+      setAttributeIndexes("last");
+       
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector    result;
+
+    result = new Vector();
+
+    result.add("-C");
+    result.add("" + (getAttributeIndexes()));
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexesTipText() {
+    return "Sets a range attributes to process. Any non-nominal "
+      + "attributes in the range are left untouched (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return 		the index of the attribute
+   */
+  public String getAttributeIndexes() {
+    //    return m_AttIndex.getSingleIndex();
+    return m_AttIndex.getRanges();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex 	the index of the attribute
+   */
+  public void setAttributeIndexes(String attIndex) {
+    //    m_AttIndex.setSingleIndex(attIndex);
+    m_AttIndex.setRanges(attIndex);
+  }
+
+  /**
+   * Set the output format. Takes the current average class values
+   * and m_InputFormat and calls setOutputFormat(Instances) 
+   * appropriately.
+   */
+  private void setOutputFormat() {
+    Instances 	newData;
+    FastVector 	newAtts;
+      
+    // Compute new attributes
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+
+      if (!att.isNominal() || !m_AttIndex.isInRange(j))
+	newAtts.addElement(att); 
+      else
+	newAtts.addElement(new Attribute(att.name(), (FastVector) null));
+    }
+      
+    // Construct new header
+    newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param args 	should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] args) {
+    runFilter(new NominalToString(), args);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Normalize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Normalize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Normalize.java	(revision 29)
@@ -0,0 +1,581 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Normalize.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Sourcable;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Normalizes all numeric values in the given dataset (apart from the class attribute, if set). The resulting values are by default in [0,1] for the data used to compute the normalization intervals. But with the scale and translation parameters one can change that, e.g., with scale = 2.0 and translation = -1.0 you get values in the range [-1,+1].
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  The scaling factor for the output range.
+ *  (default: 1.0)</pre>
+ * 
+ * <pre> -T &lt;num&gt;
+ *  The translation of the output range.
+ *  (default: 0.0)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class Normalize 
+  extends PotentialClassIgnorer 
+  implements UnsupervisedFilter, Sourcable, OptionHandler {
+  
+  /** for serialization. */
+  static final long serialVersionUID = -8158531150984362898L;
+
+  /** The minimum values for numeric attributes. */
+  protected double[] m_MinArray;
+  
+  /** The maximum values for numeric attributes. */
+  protected double[] m_MaxArray;
+
+  /** The translation of the output range. */
+  protected double m_Translation = 0;
+  
+  /** The scaling factor of the output range. */
+  protected double m_Scale = 1.0;
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Normalizes all numeric values in the given dataset (apart from the "
+      + "class attribute, if set). The resulting values are by default "
+      + "in [0,1] for the data used to compute the normalization intervals. "
+      + "But with the scale and translation parameters one can change that, "
+      + "e.g., with scale = 2.0 and translation = -1.0 you get values in the "
+      + "range [-1,+1].";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    Enumeration en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tThe scaling factor for the output range.\n"
+	+ "\t(default: 1.0)",
+	"S", 1, "-S <num>"));
+
+    result.addElement(new Option(
+	"\tThe translation of the output range.\n"
+	+"\t(default: 0.0)",
+	"T", 1,"-T <num>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -unset-class-temporarily
+   *  Unsets the class index temporarily before the filter is
+   *  applied to the data.
+   *  (default: no)</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  The scaling factor for the output range.
+   *  (default: 1.0)</pre>
+   * 
+   * <pre> -T &lt;num&gt;
+   *  The translation of the output range.
+   *  (default: 0.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String      tmpStr;
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setScale(Double.parseDouble(tmpStr));
+    else
+      setScale(1.0);
+    
+    tmpStr = Utils.getOption('T', options);
+    if (tmpStr.length() != 0)
+      setTranslation(Double.parseDouble(tmpStr));
+    else
+      setTranslation(0.0);
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector<String>();
+
+    result.add("-S");
+    result.add("" + getScale());
+
+    result.add("-T");
+    result.add("" + getTranslation());
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained in 
+   * 				the object are ignored - only the structure is 
+   * 				required).
+   * @return 			true if the outputFormat may be collected 
+   * 				immediately
+   * @throws Exception 		if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_MinArray = m_MaxArray = null;
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance 	the input instance
+   * @return 		true if the filtered instance may now be
+   * 			collected with output().
+   * @throws Exception 	if an error occurs
+   * @throws IllegalStateException 	if no input format has been set.
+   */
+  public boolean input(Instance instance) throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+    
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_MinArray == null) {
+      bufferInput(instance);
+      return false;
+    }
+    else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return 		true if there are instances pending output
+   * @throws Exception 	if an error occurs
+   * @throws IllegalStateException 	if no input structure has been defined
+   */
+  public boolean batchFinished() throws Exception {
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (m_MinArray == null) {
+      Instances input = getInputFormat();
+      // Compute minimums and maximums
+      m_MinArray = new double[input.numAttributes()];
+      m_MaxArray = new double[input.numAttributes()];
+      for (int i = 0; i < input.numAttributes(); i++)
+	m_MinArray[i] = Double.NaN;
+
+      for (int j = 0; j < input.numInstances(); j++) {
+	double[] value = input.instance(j).toDoubleArray();
+	for (int i = 0; i < input.numAttributes(); i++) {
+	  if (input.attribute(i).isNumeric() &&
+	      (input.classIndex() != i)) {
+	    if (!Utils.isMissingValue(value[i])) {
+	      if (Double.isNaN(m_MinArray[i])) {
+		m_MinArray[i] = m_MaxArray[i] = value[i];
+	      }
+	      else {
+		if (value[i] < m_MinArray[i])
+		  m_MinArray[i] = value[i];
+		if (value[i] > m_MaxArray[i])
+		  m_MaxArray[i] = value[i];
+	      }
+	    }
+	  }
+	} 
+      }
+
+      // Convert pending input instances
+      for (int i = 0; i < input.numInstances(); i++)
+	convertInstance(input.instance(i));
+    } 
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance 	the instance to convert
+   * @throws Exception 	if conversion fails
+   */
+  protected void convertInstance(Instance instance) throws Exception {
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      double[] newVals = new double[instance.numAttributes()];
+      int[] newIndices = new int[instance.numAttributes()];
+      double[] vals = instance.toDoubleArray();
+      int ind = 0;
+      for (int j = 0; j < instance.numAttributes(); j++) {
+	double value;
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  if (Double.isNaN(m_MinArray[j]) ||
+	      (m_MaxArray[j] == m_MinArray[j])) {
+	    value = 0;
+	  }
+	  else {
+	    value = (vals[j] - m_MinArray[j]) / 
+	      (m_MaxArray[j] - m_MinArray[j]) * m_Scale + m_Translation;
+            if (Double.isNaN(value)) {
+              throw new Exception("A NaN value was generated "
+                                  + "while normalizing " 
+                                  + instance.attribute(j).name());
+            }
+	  }
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	}
+	else {
+	  value = vals[j];
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	}
+      }	
+      double[] tempVals = new double[ind];
+      int[] tempInd = new int[ind];
+      System.arraycopy(newVals, 0, tempVals, 0, ind);
+      System.arraycopy(newIndices, 0, tempInd, 0, ind);
+      inst = new SparseInstance(instance.weight(), tempVals, tempInd,
+                                instance.numAttributes());
+    }
+    else {
+      double[] vals = instance.toDoubleArray();
+      for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  if (Double.isNaN(m_MinArray[j]) ||
+	      (m_MaxArray[j] == m_MinArray[j])) {
+	    vals[j] = 0;
+	  }
+	  else {
+	    vals[j] = (vals[j] - m_MinArray[j]) / 
+	      (m_MaxArray[j] - m_MinArray[j]) * m_Scale + m_Translation;
+            if (Double.isNaN(vals[j])) {
+              throw new Exception("A NaN value was generated "
+                                  + "while normalizing " 
+                                  + instance.attribute(j).name());
+            }
+	  }
+	}
+      }	
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(instance.dataset());
+    push(inst);
+  }
+  
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception {
+    StringBuffer        result;
+    boolean[]		process;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // determine what attributes were processed
+    process = new boolean[data.numAttributes()];
+    for (i = 0; i < data.numAttributes(); i++) 
+      process[i] = (data.attribute(i).isNumeric() && (i != data.classIndex()));
+  
+    result.append("class " + className + " {\n");
+    result.append("\n");
+    result.append("  /** lists which attributes will be processed */\n");
+    result.append("  protected final static boolean[] PROCESS = new boolean[]{" + Utils.arrayToString(process) + "};\n");
+    result.append("\n");
+    result.append("  /** the minimum values for numeric values */\n");
+    result.append("  protected final static double[] MIN = new double[]{" + Utils.arrayToString(m_MinArray).replaceAll("NaN", "Double.NaN") + "};\n");
+    result.append("\n");
+    result.append("  /** the maximum values for numeric values */\n");
+    result.append("  protected final static double[] MAX = new double[]{" + Utils.arrayToString(m_MaxArray) + "};\n");
+    result.append("\n");
+    result.append("  /** the scale factor */\n");
+    result.append("  protected final static double SCALE = " + m_Scale + ";\n");
+    result.append("\n");
+    result.append("  /** the translation */\n");
+    result.append("  protected final static double TRANSLATION = " + m_Translation + ";\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters a single row\n");
+    result.append("   * \n");
+    result.append("   * @param i the row to process\n");
+    result.append("   * @return the processed row\n");
+    result.append("   */\n");
+    result.append("  public static Object[] filter(Object[] i) {\n");
+    result.append("    Object[] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      if (PROCESS[n] && (i[n] != null)) {\n");
+    result.append("        if (Double.isNaN(MIN[n]) || (MIN[n] == MAX[n]))\n");
+    result.append("          result[n] = 0;\n");
+    result.append("        else\n");
+    result.append("          result[n] = (((Double) i[n]) - MIN[n]) / (MAX[n] - MIN[n]) * SCALE + TRANSLATION;\n");
+    result.append("      }\n");
+    result.append("      else {\n");
+    result.append("        result[n] = i[n];\n");
+    result.append("      }\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters multiple rows\n");
+    result.append("   * \n");
+    result.append("   * @param i the rows to process\n");
+    result.append("   * @return the processed rows\n");
+    result.append("   */\n");
+    result.append("  public static Object[][] filter(Object[][] i) {\n");
+    result.append("    Object[][] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length][];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      result[n] = filter(i[n]);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+
+  /**
+   * Returns the calculated minimum values for the attributes in the data.
+   * 
+   * @return		the array with the minimum values
+   */
+  public double[] getMinArray() {
+    return m_MinArray;
+  }
+
+  /**
+   * Returns the calculated maximum values for the attributes in the data.
+   * 
+   * @return		the array with the maximum values
+   */
+  public double[] getMaxArray() {
+    return m_MaxArray;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String scaleTipText() {
+    return "The factor for scaling the output range (default: 1).";
+  }
+
+  /**
+   * Get the scaling factor.
+   *
+   * @return 		the factor
+   */
+  public double getScale() {
+    return m_Scale;
+  }
+
+  /**
+   * Sets the scaling factor.
+   *
+   * @param value 	the scaling factor
+   */
+  public void setScale(double value) {
+    m_Scale = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String translationTipText() {
+    return "The translation of the output range (default: 0).";
+  }
+
+  /**
+   * Get the translation.
+   *
+   * @return 		the translation
+   */
+  public double getTranslation() {
+    return m_Translation;
+  }
+
+  /**
+   * Sets the translation.
+   *
+   * @param value 	the translation
+   */
+  public void setTranslation(double value) {
+    m_Translation = value;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for running this filter.
+   *
+   * @param args 	should contain arguments to the filter, use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new Normalize(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericCleaner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericCleaner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericCleaner.java	(revision 29)
@@ -0,0 +1,822 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NumericCleaner.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleStreamFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/**
+ <!-- globalinfo-start -->
+ * A filter that 'cleanses' the numeric data from values that are too small, too big or very close to a certain value (e.g., 0) and sets these values to a pre-defined default.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -min &lt;double&gt;
+ *  The minimum threshold. (default -Double.MAX_VALUE)</pre>
+ * 
+ * <pre> -min-default &lt;double&gt;
+ *  The replacement for values smaller than the minimum threshold.
+ *  (default -Double.MAX_VALUE)</pre>
+ * 
+ * <pre> -max &lt;double&gt;
+ *  The maximum threshold. (default Double.MAX_VALUE)</pre>
+ * 
+ * <pre> -max-default &lt;double&gt;
+ *  The replacement for values larger than the maximum threshold.
+ *  (default Double.MAX_VALUE)</pre>
+ * 
+ * <pre> -closeto &lt;double&gt;
+ *  The number values are checked for closeness. (default 0)</pre>
+ * 
+ * <pre> -closeto-default &lt;double&gt;
+ *  The replacement for values that are close to '-closeto'.
+ *  (default 0)</pre>
+ * 
+ * <pre> -closeto-tolerance &lt;double&gt;
+ *  The tolerance below which numbers are considered being close to 
+ *  to each other. (default 1E-6)</pre>
+ * 
+ * <pre> -decimals &lt;int&gt;
+ *  The number of decimals to round to, -1 means no rounding at all.
+ *  (default -1)</pre>
+ * 
+ * <pre> -R &lt;col1,col2,...&gt;
+ *  The list of columns to cleanse, e.g., first-last or first-3,5-last.
+ *  (default first-last)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the matching sense.</pre>
+ * 
+ * <pre> -include-class
+ *  Whether to include the class in the cleansing.
+ *  The class column will always be skipped, if this flag is not
+ *  present. (default no)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class NumericCleaner
+  extends SimpleStreamFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -352890679895066592L;
+
+  /** the minimum threshold */
+  protected double m_MinThreshold = -Double.MAX_VALUE;
+
+  /** the minimum default replacement value */
+  protected double m_MinDefault = -Double.MAX_VALUE;
+
+  /** the maximum threshold */
+  protected double m_MaxThreshold = Double.MAX_VALUE;
+
+  /** the maximum default replacement value */
+  protected double m_MaxDefault = Double.MAX_VALUE;
+
+  /** the number the values are checked for closeness to */
+  protected double m_CloseTo = 0;
+
+  /** the default replacement value for numbers "close-to" */
+  protected double m_CloseToDefault = 0;
+
+  /** the tolerance distance, below which numbers are considered being "close-to" */
+  protected double m_CloseToTolerance = 1E-6;
+
+  /** Stores which columns to cleanse */
+  protected Range m_Cols = new Range("first-last");
+
+  /** whether to include the class attribute */
+  protected boolean m_IncludeClass = false;
+  
+  /** the number of decimals to round to (-1 means no rounding) */
+  protected int m_Decimals = -1;
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return      a description of the filter suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter that 'cleanses' the numeric data from values that are too "
+      + "small, too big or very close to a certain value (e.g., 0) and sets "
+      + "these values to a pre-defined default.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        result;
+    Enumeration   enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe minimum threshold. (default -Double.MAX_VALUE)",
+	"min", 1, "-min <double>"));
+    
+    result.addElement(new Option(
+	"\tThe replacement for values smaller than the minimum threshold.\n"
+	+ "\t(default -Double.MAX_VALUE)",
+	"min-default", 1, "-min-default <double>"));
+
+    result.addElement(new Option(
+	"\tThe maximum threshold. (default Double.MAX_VALUE)",
+	"max", 1, "-max <double>"));
+    
+    result.addElement(new Option(
+	"\tThe replacement for values larger than the maximum threshold.\n"
+	+ "\t(default Double.MAX_VALUE)",
+	"max-default", 1, "-max-default <double>"));
+
+    result.addElement(new Option(
+	"\tThe number values are checked for closeness. (default 0)",
+	"closeto", 1, "-closeto <double>"));
+    
+    result.addElement(new Option(
+	"\tThe replacement for values that are close to '-closeto'.\n"
+	+ "\t(default 0)",
+	"closeto-default", 1, "-closeto-default <double>"));
+    
+    result.addElement(new Option(
+	"\tThe tolerance below which numbers are considered being close to \n"
+	+ "\tto each other. (default 1E-6)",
+	"closeto-tolerance", 1, "-closeto-tolerance <double>"));
+
+    result.addElement(new Option(
+	"\tThe number of decimals to round to, -1 means no rounding at all.\n"
+	+ "\t(default -1)",
+	"decimals", 1, "-decimals <int>"));
+    
+    result.addElement(new Option(
+	"\tThe list of columns to cleanse, e.g., first-last or first-3,5-last.\n"
+	+ "\t(default first-last)",
+	"R", 1, "-R <col1,col2,...>"));
+
+    result.addElement(new Option(
+	"\tInverts the matching sense.",
+	"V", 0, "-V"));
+
+    result.addElement(new Option(
+	"\tWhether to include the class in the cleansing.\n"
+	+ "\tThe class column will always be skipped, if this flag is not\n"
+	+ "\tpresent. (default no)",
+	"include-class", 0, "-include-class"));
+
+    return result.elements();
+  }	  
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-min"); 
+    result.add("" + m_MinThreshold);
+
+    result.add("-min-default"); 
+    result.add("" + m_MinDefault);
+
+    result.add("-max"); 
+    result.add("" + m_MaxThreshold);
+
+    result.add("-max-default"); 
+    result.add("" + m_MaxDefault);
+
+    result.add("-closeto"); 
+    result.add("" + m_CloseTo);
+
+    result.add("-closeto-default"); 
+    result.add("" + m_CloseToDefault);
+    
+    result.add("-closeto-tolerance"); 
+    result.add("" + m_CloseToTolerance);
+
+    result.add("-R"); 
+    result.add("" + m_Cols.getRanges());
+
+    if (m_Cols.getInvert())
+      result.add("-V");
+    
+    if (m_IncludeClass)
+      result.add("-include-class"); 
+
+    result.add("-decimals"); 
+    result.add("" + getDecimals());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }	  
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -min &lt;double&gt;
+   *  The minimum threshold. (default -Double.MAX_VALUE)</pre>
+   * 
+   * <pre> -min-default &lt;double&gt;
+   *  The replacement for values smaller than the minimum threshold.
+   *  (default -Double.MAX_VALUE)</pre>
+   * 
+   * <pre> -max &lt;double&gt;
+   *  The maximum threshold. (default Double.MAX_VALUE)</pre>
+   * 
+   * <pre> -max-default &lt;double&gt;
+   *  The replacement for values larger than the maximum threshold.
+   *  (default Double.MAX_VALUE)</pre>
+   * 
+   * <pre> -closeto &lt;double&gt;
+   *  The number values are checked for closeness. (default 0)</pre>
+   * 
+   * <pre> -closeto-default &lt;double&gt;
+   *  The replacement for values that are close to '-closeto'.
+   *  (default 0)</pre>
+   * 
+   * <pre> -closeto-tolerance &lt;double&gt;
+   *  The tolerance below which numbers are considered being close to 
+   *  to each other. (default 1E-6)</pre>
+   * 
+   * <pre> -decimals &lt;int&gt;
+   *  The number of decimals to round to, -1 means no rounding at all.
+   *  (default -1)</pre>
+   * 
+   * <pre> -R &lt;col1,col2,...&gt;
+   *  The list of columns to cleanse, e.g., first-last or first-3,5-last.
+   *  (default first-last)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the matching sense.</pre>
+   * 
+   * <pre> -include-class
+   *  Whether to include the class in the cleansing.
+   *  The class column will always be skipped, if this flag is not
+   *  present. (default no)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption("min", options);
+    if (tmpStr.length() != 0)
+      setMinThreshold(Double.parseDouble(tmpStr));
+    else
+      setMinThreshold(-Double.MAX_VALUE);
+    
+    tmpStr = Utils.getOption("min-default", options);
+    if (tmpStr.length() != 0)
+      setMinDefault(Double.parseDouble(tmpStr));
+    else
+      setMinDefault(-Double.MAX_VALUE);
+    
+    tmpStr = Utils.getOption("max", options);
+    if (tmpStr.length() != 0)
+      setMaxThreshold(Double.parseDouble(tmpStr));
+    else
+      setMaxThreshold(Double.MAX_VALUE);
+    
+    tmpStr = Utils.getOption("max-default", options);
+    if (tmpStr.length() != 0)
+      setMaxDefault(Double.parseDouble(tmpStr));
+    else
+      setMaxDefault(Double.MAX_VALUE);
+    
+    tmpStr = Utils.getOption("closeto", options);
+    if (tmpStr.length() != 0)
+      setCloseTo(Double.parseDouble(tmpStr));
+    else
+      setCloseTo(0);
+    
+    tmpStr = Utils.getOption("closeto-default", options);
+    if (tmpStr.length() != 0)
+      setCloseToDefault(Double.parseDouble(tmpStr));
+    else
+      setCloseToDefault(0);
+    
+    tmpStr = Utils.getOption("closeto-tolerance", options);
+    if (tmpStr.length() != 0)
+      setCloseToTolerance(Double.parseDouble(tmpStr));
+    else
+      setCloseToTolerance(1E-6);
+    
+    tmpStr = Utils.getOption("R", options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices("first-last");
+    
+    setInvertSelection(Utils.getFlag("V", options));
+    
+    setIncludeClass(Utils.getFlag("include-class", options));
+
+    tmpStr = Utils.getOption("decimals", options);
+    if (tmpStr.length() != 0)
+      setDecimals(Integer.parseInt(tmpStr));
+    else
+      setDecimals(-1);
+    
+    super.setOptions(options);
+  }	  
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+
+    m_Cols.setUpper(inputFormat.numAttributes() - 1);
+    
+    return new Instances(inputFormat);
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    Instance		result;
+    int			i;
+    double		val;
+    double		factor;
+    
+    result = (Instance) instance.copy();
+    
+    if (m_Decimals > -1)
+      factor = StrictMath.pow(10, m_Decimals);
+    else
+      factor = 1;
+    
+    for (i = 0; i < result.numAttributes(); i++) {
+      // only numeric attributes
+      if (!result.attribute(i).isNumeric())
+	continue;
+
+      // out of range?
+      if (!m_Cols.isInRange(i))
+	continue;
+      
+      // skip class?
+      if ( (result.classIndex() == i) && (!m_IncludeClass) )
+	continue;
+      
+      // too small?
+      if (result.value(i) < m_MinThreshold) {
+	if (getDebug())
+	  System.out.println("Too small: " + result.value(i) + " -> " + m_MinDefault);
+	result.setValue(i, m_MinDefault);
+      }
+      // too big?
+      else if (result.value(i) > m_MaxThreshold) {
+	if (getDebug())
+	  System.out.println("Too big: " + result.value(i) + " -> " + m_MaxDefault);
+	result.setValue(i, m_MaxDefault);
+      }
+      // too close?
+      else if (    (result.value(i) - m_CloseTo < m_CloseToTolerance) 
+	        && (m_CloseTo - result.value(i) < m_CloseToTolerance) 
+	        && (result.value(i) != m_CloseTo) ) {
+	if (getDebug())
+	  System.out.println("Too close: " + result.value(i) + " -> " + m_CloseToDefault);
+	result.setValue(i, m_CloseToDefault);
+      }
+      
+      // decimals?
+      if (m_Decimals > -1) {
+	val = result.value(i);
+	val = StrictMath.round(val * factor) / factor;
+	result.setValue(i, val);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String minThresholdTipText() {
+    return "The minimum threshold below values are replaced by a default.";
+  }
+
+  /**
+   * Get the minimum threshold. 
+   *
+   * @return 		the minimum threshold.
+   */
+  public double getMinThreshold() {
+    return m_MinThreshold;
+  }
+
+  /**
+   * Set the minimum threshold. 
+   *
+   * @param value	the minimum threshold to use.
+   */
+  public void setMinThreshold(double value) {
+    m_MinThreshold = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String minDefaultTipText() {
+    return "The default value to replace values that are below the minimum threshold.";
+  }
+
+  /**
+   * Get the minimum default. 
+   *
+   * @return 		the minimum default.
+   */
+  public double getMinDefault() {
+    return m_MinDefault;
+  }
+
+  /**
+   * Set the minimum default. 
+   *
+   * @param value	the minimum default to use.
+   */
+  public void setMinDefault(double value) {
+    m_MinDefault = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxThresholdTipText() {
+    return "The maximum threshold above values are replaced by a default.";
+  }
+
+  /**
+   * Get the maximum threshold. 
+   *
+   * @return 		the maximum threshold.
+   */
+  public double getMaxThreshold() {
+    return m_MaxThreshold;
+  }
+
+  /**
+   * Set the maximum threshold. 
+   *
+   * @param value	the maximum threshold to use.
+   */
+  public void setMaxThreshold(double value) {
+    m_MaxThreshold = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxDefaultTipText() {
+    return "The default value to replace values that are above the maximum threshold.";
+  }
+
+  /**
+   * Get the maximum default. 
+   *
+   * @return 		the maximum default.
+   */
+  public double getMaxDefault() {
+    return m_MaxDefault;
+  }
+
+  /**
+   * Set the naximum default. 
+   *
+   * @param value	the maximum default to use.
+   */
+  public void setMaxDefault(double value) {
+    m_MaxDefault = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String closeToTipText() {
+    return 
+        "The number values are checked for whether they are too close to "
+      + "and get replaced by a default.";
+  }
+
+  /**
+   * Get the "close to" number.
+   *
+   * @return 		the "close to" number.
+   */
+  public double getCloseTo() {
+    return m_CloseTo;
+  }
+
+  /**
+   * Set the "close to" number.
+   *
+   * @param value	the number to use for checking closeness.
+   */
+  public void setCloseTo(double value) {
+    m_CloseTo = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String closeToDefaultTipText() {
+    return "The default value to replace values with that are too close.";
+  }
+
+  /**
+   * Get the "close to" default.
+   *
+   * @return 		the "close to" default.
+   */
+  public double getCloseToDefault() {
+    return m_CloseToDefault;
+  }
+
+  /**
+   * Set the "close to" default. 
+   *
+   * @param value	the "close to" default to use.
+   */
+  public void setCloseToDefault(double value) {
+    m_CloseToDefault = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String closeToToleranceTipText() {
+    return "The value below which values are considered close to.";
+  }
+
+  /**
+   * Get the "close to" Tolerance.
+   *
+   * @return 		the "close to" Tolerance.
+   */
+  public double getCloseToTolerance() {
+    return m_CloseToTolerance;
+  }
+
+  /**
+   * Set the "close to" Tolerance. 
+   *
+   * @param value	the "close to" Tolerance to use.
+   */
+  public void setCloseToTolerance(double value) {
+    m_CloseToTolerance = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "The selection of columns to use in the cleansing processs, first and last are valid indices.";
+  }
+
+  /**
+   * Gets the selection of the columns, e.g., first-last or first-3,5-last
+   *
+   * @return 		the selected indices
+   */
+  public String getAttributeIndices() {
+    return m_Cols.getRanges();
+  }
+
+  /**
+   * Sets the columns to use, e.g., first-last or first-3,5-last
+   *
+   * @param value 	the columns to use
+   */
+  public void setAttributeIndices(String value) {
+    m_Cols.setRanges(value);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "If enabled the selection of the columns is inverted.";
+  }
+
+  /**
+   * Gets whether the selection of the columns is inverted
+   *
+   * @return 		true if the selection is inverted
+   */
+  public boolean getInvertSelection() {
+    return m_Cols.getInvert();
+  }
+
+  /**
+   * Sets whether the selection of the indices is inverted or not
+   *
+   * @param value 	the new invert setting
+   */
+  public void setInvertSelection(boolean value) {
+    m_Cols.setInvert(value);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String includeClassTipText() {
+    return "If disabled, the class attribute will be always left out of the cleaning process.";
+  }
+
+  /**
+   * Gets whether the class is included in the cleaning process or always 
+   * skipped.
+   *
+   * @return 		true if the class can be considered for cleaning.
+   */
+  public boolean getIncludeClass() {
+    return m_IncludeClass;
+  }
+
+  /**
+   * Sets whether the class can be cleaned, too.
+   *
+   * @param value	true if the class can be cleansed, too
+   */
+  public void setIncludeClass(boolean value) {
+    m_IncludeClass = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String decimalsTipText() {
+    return "The number of decimals to round to, -1 means no rounding at all.";
+  }
+
+  /**
+   * Get the number of decimals to round to. 
+   *
+   * @return 		the number of decimals.
+   */
+  public int getDecimals() {
+    return m_Decimals;
+  }
+
+  /**
+   * Set the number of decimals to round to.
+   *
+   * @param value	the number of decimals.
+   */
+  public void setDecimals(int value) {
+    m_Decimals = value;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Runs the filter from commandline, use "-h" to see all options.
+   * 
+   * @param args the commandline options for the filter
+   */
+  public static void main(String[] args) {
+    runFilter(new NumericCleaner(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToBinary.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToBinary.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToBinary.java	(revision 29)
@@ -0,0 +1,234 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumericToBinary.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Capabilities.Capability;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts all numeric attributes into binary attributes (apart from the class attribute, if set): if the value of the numeric attribute is exactly zero, the value of the new attribute will be zero. If the value of the numeric attribute is missing, the value of the new attribute will be missing. Otherwise, the value of the new attribute will be one. The new attributes will be nominal.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $ 
+ */
+public class NumericToBinary 
+  extends PotentialClassIgnorer
+  implements UnsupervisedFilter, StreamableFilter {
+
+  /** for serialization */
+  static final long serialVersionUID = 2616879323359470802L;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Converts all numeric attributes into binary attributes (apart from "
+      + "the class attribute, if set): if the value of the numeric attribute is "
+      + "exactly zero, the value of the new attribute will be zero. If the "
+      + "value of the numeric attribute is missing, the value of the new "
+      + "attribute will be missing. Otherwise, the value of the new "
+      + "attribute will be one. The new attributes will be nominal.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    convertInstance(instance);
+    return true;
+  }
+
+  /** 
+   * Set the output format. 
+   */
+  private void setOutputFormat() {
+
+    FastVector newAtts;
+    int newClassIndex;
+    StringBuffer attributeName;
+    Instances outputFormat;
+    FastVector vals;
+
+    // Compute new attributes
+    newClassIndex = getInputFormat().classIndex();
+    newAtts = new FastVector();
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if ((j == newClassIndex) || (!att.isNumeric())) {
+	newAtts.addElement(att.copy());
+      } else {
+	attributeName = new StringBuffer(att.name() + "_binarized");
+	vals = new FastVector(2);
+	vals.addElement("0"); vals.addElement("1");
+	newAtts.addElement(new Attribute(attributeName.toString(), vals));
+      }
+    }
+    outputFormat = new Instances(getInputFormat().relationName(), newAtts, 0);
+    outputFormat.setClassIndex(newClassIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstance(Instance instance) {
+  
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      double[] vals = new double[instance.numValues()];
+      int[] newIndices = new int[instance.numValues()];
+      for (int j = 0; j < instance.numValues(); j++) {
+	Attribute att = getInputFormat().attribute(instance.index(j));
+	if ((!att.isNumeric()) || (instance.index(j) == getInputFormat().classIndex())) {
+	  vals[j] = instance.valueSparse(j);
+	} else {
+	  if (instance.isMissingSparse(j)) {
+	    vals[j] = instance.valueSparse(j);
+	  } else {
+	    vals[j] = 1;
+	  }
+	} 
+	newIndices[j] = instance.index(j);
+      }
+      inst = new SparseInstance(instance.weight(), vals, newIndices, 
+                                outputFormatPeek().numAttributes());
+    } else {
+      double[] vals = new double[outputFormatPeek().numAttributes()];
+      for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+	Attribute att = getInputFormat().attribute(j);
+	if ((!att.isNumeric()) || (j == getInputFormat().classIndex())) {
+	  vals[j] = instance.value(j);
+	} else {
+	  if (instance.isMissing(j) || (instance.value(j) == 0)) {
+	    vals[j] = instance.value(j);
+	  } else {
+	    vals[j] = 1;
+	  }
+	} 
+      }
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(instance.dataset());
+    push(inst);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new NumericToBinary(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToNominal.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToNominal.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericToNominal.java	(revision 29)
@@ -0,0 +1,431 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * NumericToNominal.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleBatchFilter;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A filter for turning numeric attributes into nominal ones. Unlike discretization, it just takes all numeric values and adds them to the list of nominal values of that attribute. Useful after CSV imports, to enforce certain attributes to become nominal, e.g., the class attribute, containing values from 1 to 5.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to Discretize. First and last are valid indexes.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class NumericToNominal
+  extends SimpleBatchFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6614630932899796239L;
+
+  /** the maximum number of decimals to use */
+  protected final static int MAX_DECIMALS = 6;
+  
+  /** Stores which columns to turn into nominals */
+  protected Range m_Cols = new Range("first-last");
+
+  /** The default columns to turn into nominals */
+  protected String m_DefaultCols = "first-last";
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter for turning numeric attributes into nominal ones. Unlike "
+      + "discretization, it just takes all numeric values and adds them to "
+      + "the list of nominal values of that attribute. Useful after CSV "
+      + "imports, to enforce certain attributes to become nominal, e.g., "
+      + "the class attribute, containing values from 1 to 5.";
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSpecifies list of columns to Discretize. First"
+	+ " and last are valid indexes.\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+
+    result.addElement(new Option(
+	"\tInvert matching sense of column indexes.",
+	"V", 0, "-V"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to Discretize. First and last are valid indexes.
+   *  (default: first-last)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense of column indexes.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    super.setOptions(options);
+    
+    setInvertSelection(Utils.getFlag('V', options));
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices(m_DefaultCols);
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    if (!getAttributeIndices().equals("")) {
+      result.add("-R");
+      result.add(getAttributeIndices());
+    }
+
+    if (getInvertSelection())
+      result.add("-V");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return 
+        "Set attribute selection mode. If false, only selected"
+      + " (numeric) attributes in the range will be 'nominalized'; if"
+      + " true, only non-selected attributes will be 'nominalized'.";
+  }
+
+  /**
+   * Gets whether the supplied columns are to be worked on or the others.
+   *
+   * @return 		true if the supplied columns will be worked on
+   */
+  public boolean getInvertSelection() {
+    return m_Cols.getInvert();
+  }
+
+  /**
+   * Sets whether selected columns should be worked on or all the others apart
+   * from these. If true all the other columns are considered for 
+   * "nominalization".
+   *
+   * @param value 	the new invert setting
+   */
+  public void setInvertSelection(boolean value) {
+    m_Cols.setInvert(value);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection
+   *
+   * @return 		a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+    return m_Cols.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be "nominalized" (only numeric
+   * attributes among the selection will be transformed).
+   *
+   * @param value 	a string representing the list of attributes. Since
+   * 			the string will typically come from a user, attributes 
+   * 			are indexed from 1. <br> eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String value) {
+    m_Cols.setRanges(value);
+  }
+
+  /**
+   * Sets which attributes are to be transoformed to nominal. (only numeric
+   * attributes among the selection will be transformed).
+   *
+   * @param value 	an array containing indexes of attributes to nominalize.
+   * 			Since the array will typically come from a program, 
+   * 			attributes are indexed from 0.
+   * @throws IllegalArgumentException if an invalid set of ranges is supplied 
+   */
+  public void setAttributeIndicesArray(int[] value) {
+    setAttributeIndices(Range.indicesToRangeList(value));
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+    
+    Instances 	data;
+    Instances	result;
+    FastVector	atts;
+    FastVector	values;
+    HashSet	hash;
+    int		i;
+    int		n;
+    boolean	isDate;
+    Instance	inst;
+    Vector	sorted;
+
+    m_Cols.setUpper(inputFormat.numAttributes() - 1);
+    data = new Instances(inputFormat);
+    atts = new FastVector();
+    for (i = 0; i < data.numAttributes(); i++) {
+      if (!m_Cols.isInRange(i) || !data.attribute(i).isNumeric()) {
+	atts.addElement(data.attribute(i));
+	continue;
+      }
+      
+      // date attribute?
+      isDate = (data.attribute(i).type() == Attribute.DATE);
+      
+      // determine all available attribtues in dataset
+      hash   = new HashSet();
+      for (n = 0; n < data.numInstances(); n++) {
+	inst = data.instance(n);
+	if (inst.isMissing(i))
+	  continue;
+	
+	if (isDate)
+	  hash.add(inst.stringValue(i));
+	else
+	  hash.add(new Double(inst.value(i)));
+      }
+      
+      // sort values
+      sorted = new Vector();
+      for (Object o: hash)
+	sorted.add(o);
+      Collections.sort(sorted);
+      
+      // create attribute from sorted values
+      values = new FastVector();
+      for (Object o: sorted) {
+	if (isDate)
+	  values.addElement(
+	      o.toString());
+	else
+	  values.addElement(
+	      Utils.doubleToString(((Double) o).doubleValue(), MAX_DECIMALS));
+      }
+      atts.addElement(new Attribute(data.attribute(i).name(), values));
+    }
+    
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(inputFormat.classIndex());
+    
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances	result;
+    int		i;
+    int		n;
+    double[]	values;
+    String	value;
+    Instance	inst;
+    Instance	newInst;
+    
+    // we need the complete input data!
+    if (!isFirstBatchDone())
+      setOutputFormat(determineOutputFormat(getInputFormat()));
+    
+    result = new Instances(getOutputFormat());
+    
+    for (i = 0; i < instances.numInstances(); i++) {
+      inst   = instances.instance(i);
+      values = inst.toDoubleArray();
+      
+      for (n = 0; n < values.length; n++) {
+	if (    !m_Cols.isInRange(n)
+	     || !instances.attribute(n).isNumeric() 
+	     || inst.isMissing(n) )
+	  continue;
+
+	// get index of value
+	if (instances.attribute(n).type() == Attribute.DATE)
+	  value = inst.stringValue(n);
+	else
+	  value = Utils.doubleToString(inst.value(n), MAX_DECIMALS);
+	
+	values[n] = result.attribute(n).indexOfValue(value);
+      }
+      
+      // generate new instance
+      if (inst instanceof SparseInstance)
+	newInst = new SparseInstance(inst.weight(), values);
+      else
+	newInst = new DenseInstance(inst.weight(), values);
+      
+      // copy possible string, relational values
+      newInst.setDataset(getOutputFormat());
+      copyValues(newInst, false, inst.dataset(), getOutputFormat());
+      
+      result.add(newInst);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Runs the filter with the given parameters. Use -h to list options.
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    runFilter(new NumericToNominal(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericTransform.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericTransform.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/NumericTransform.java	(revision 29)
@@ -0,0 +1,478 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumericTransform.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Transforms numeric attributes using a given transformation method.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to transform. First and last are
+ *  valid indexes (default none). Non-numeric columns are 
+ *  skipped.</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense.</pre>
+ * 
+ * <pre> -C &lt;string&gt;
+ *  Sets the class containing transformation method.
+ *  (default java.lang.Math)</pre>
+ * 
+ * <pre> -M &lt;string&gt;
+ *  Sets the method. (default abs)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class NumericTransform 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8561413333351366934L;
+
+  /** Stores which columns to transform. */
+  private Range m_Cols = new Range();
+
+  /** Class containing transformation method. */
+  private String m_Class;
+
+  /** Transformation method. */
+  private String m_Method;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Transforms numeric attributes using a given transformation method.";
+  }
+
+  /**
+   * Default constructor -- sets the default transform method
+   * to java.lang.Math.abs().
+   */
+  public NumericTransform() {
+
+    m_Class = "java.lang.Math";
+    m_Method = "abs";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    if (m_Class == null) {
+      throw new IllegalStateException("No class has been set.");
+    }
+    if (m_Method == null) {
+      throw new IllegalStateException("No method has been set.");
+    }
+    super.setInputFormat(instanceInfo);
+    m_Cols.setUpper(instanceInfo.numAttributes() - 1);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   * @throws InvocationTargetException if there is a problem applying
+   * the configured transform method.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    Method m = (Class.forName(m_Class)).getMethod(m_Method, new Class[] {Double.TYPE});
+
+    double []vals = new double[instance.numAttributes()];
+    Double []params = new Double[1];
+    Double newVal;
+    for(int i = 0; i < instance.numAttributes(); i++) {
+      if (instance.isMissing(i)) {
+	vals[i] = Utils.missingValue();
+      } else {
+	if (m_Cols.isInRange(i) &&
+	    instance.attribute(i).isNumeric()) {
+	  params[0] = new Double(instance.value(i));
+	  newVal = (Double) m.invoke(null, (Object[])params);
+	  if (newVal.isNaN() || newVal.isInfinite()) {
+	    vals[i] = Utils.missingValue();
+	  } else {
+	    vals[i] = newVal.doubleValue(); 
+	  }
+	} else {
+	  vals[i] = instance.value(i);
+	}
+      }
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(instance.dataset());
+    push(inst);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(4);
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to transform. First and last are\n"
+	      + "\tvalid indexes (default none). Non-numeric columns are \n"
+	      + "\tskipped.",
+              "R", 1, "-R <index1,index2-index4,...>"));
+
+    newVector.addElement(new Option(
+	      "\tInvert matching sense.",
+              "V", 0, "-V"));
+
+    newVector.addElement(new Option(
+              "\tSets the class containing transformation method.\n"+
+              "\t(default java.lang.Math)",
+              "C", 1, "-C <string>"));
+
+    newVector.addElement(new Option(
+              "\tSets the method. (default abs)",
+              "M", 1, "-M <string>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to transform. First and last are
+   *  valid indexes (default none). Non-numeric columns are 
+   *  skipped.</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense.</pre>
+   * 
+   * <pre> -C &lt;string&gt;
+   *  Sets the class containing transformation method.
+   *  (default java.lang.Math)</pre>
+   * 
+   * <pre> -M &lt;string&gt;
+   *  Sets the method. (default abs)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    setAttributeIndices(Utils.getOption('R', options));
+    setInvertSelection(Utils.getFlag('V', options));
+    String classString = Utils.getOption('C', options);
+    if (classString.length() != 0) {
+      setClassName(classString);
+    }
+    String methodString = Utils.getOption('M', options);
+    if (methodString.length() != 0) {
+      setMethodName(methodString);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [7];
+    int current = 0;
+
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+    if (m_Class != null) {
+      options[current++] = "-C"; options[current++] = getClassName();
+    }
+    if (m_Method != null) {
+      options[current++] = "-M"; options[current++] = getMethodName();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classNameTipText() {
+    return "Name of the class containing the method used for the transformation.";
+  }
+
+  /**
+   * Get the class containing the transformation method.
+   *
+   * @return string describing the class
+   */
+  public String getClassName() {
+
+    return m_Class;
+  }
+ 
+  /**
+   * Sets the class containing the transformation method.
+   *
+   * @param name the name of the class
+   * @throws ClassNotFoundException if class can't be found
+   */
+  public void setClassName(String name) throws ClassNotFoundException {
+  
+    m_Class = name;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String methodNameTipText() {
+    return "Name of the method used for the transformation.";
+  }
+ 
+  /**
+   * Get the transformation method.
+   *
+   * @return string describing the transformation method.
+   */
+  public String getMethodName() {
+
+    return m_Method;
+  }
+
+  /**
+   * Set the transformation method.
+   *
+   * @param name the name of the method
+   * @throws NoSuchMethodException if method can't be found in class
+   */
+  public void setMethodName(String name) throws NoSuchMethodException {
+
+    m_Method = name;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Whether to process the inverse of the given attribute ranges.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be transformed or not
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_Cols.getInvert();
+  }
+
+  /**
+   * Set whether selected columns should be transformed or not. 
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_Cols.setInvert(invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_Cols.getRanges();
+  }
+
+  /**
+   * Set which attributes are to be transformed (or kept if invert is true). 
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br> eg: 
+   * first-3,5,6-last
+   * @throws InvalidArgumentException if an invalid range list is supplied
+   */
+
+  public void setAttributeIndices(String rangeList) {
+
+    m_Cols.setRanges(rangeList);
+  }
+
+  /**
+   * Set which attributes are to be transformed (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws InvalidArgumentException if an invalid set of ranges is supplied
+   */
+  public void setAttributeIndicesArray(int [] attributes) {
+
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new NumericTransform(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Obfuscate.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Obfuscate.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Obfuscate.java	(revision 29)
@@ -0,0 +1,178 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Obfuscate.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * A simple instance filter that renames the relation, all attribute names and all nominal (and string) attribute values. For exchanging sensitive datasets. Currently doesn't like string or relational attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 6174 $
+ */
+public class Obfuscate 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter {
+  
+  /** for serialization */
+  static final long serialVersionUID = -343922772462971561L;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A simple instance filter that renames the relation, all attribute names "
+      + "and all nominal (and string) attribute values. For exchanging sensitive "
+      + "datasets. Currently doesn't like string or relational attributes.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if 
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    
+    // Make the obfuscated header
+    FastVector v = new FastVector();
+    for (int i = 0; i < instanceInfo.numAttributes(); i++) {
+      Attribute oldAtt = instanceInfo.attribute(i);
+      Attribute newAtt = null;
+      switch (oldAtt.type()) {
+	case Attribute.NUMERIC:
+	  newAtt = new Attribute("A" + (i + 1));
+	  break;
+	case Attribute.DATE:
+	  String format = oldAtt.getDateFormat();
+	  newAtt = new Attribute("A" + (i + 1), format);
+	  break;
+	case Attribute.NOMINAL:
+	  FastVector vals = new FastVector();
+	  for (int j = 0; j < oldAtt.numValues(); j++) {
+	    vals.addElement("V" + (j + 1));
+	  }
+	  newAtt = new Attribute("A" + (i + 1), vals);
+	  break;
+	case Attribute.STRING:
+	case Attribute.RELATIONAL:
+	default:
+	  newAtt = (Attribute) oldAtt.copy();
+  	  System.err.println("Not converting attribute: " + oldAtt.name());
+	  break;
+      }
+      v.addElement(newAtt);
+    }
+    Instances newHeader = new Instances("R", v, 10);
+    newHeader.setClassIndex(instanceInfo.classIndex());
+    setOutputFormat(newHeader);
+    return true;
+  }
+
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    push((Instance)instance.copy());
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6174 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Obfuscate(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PKIDiscretize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PKIDiscretize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PKIDiscretize.java	(revision 29)
@@ -0,0 +1,377 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PKIDiscretize.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Discretizes numeric attributes using equal frequency binning, where the number of bins is equal to the square root of the number of non-missing values.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Ying Yang, Geoffrey I. Webb: Proportional k-Interval Discretization for Naive-Bayes Classifiers. In: 12th European Conference on Machine Learning, 564-575, 2001.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Yang2001,
+ *    author = {Ying Yang and Geoffrey I. Webb},
+ *    booktitle = {12th European Conference on Machine Learning},
+ *    pages = {564-575},
+ *    publisher = {Springer},
+ *    series = {LNCS},
+ *    title = {Proportional k-Interval Discretization for Naive-Bayes Classifiers},
+ *    volume = {2167},
+ *    year = {2001}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ * <pre> -R &lt;col1,col2-col4,...&gt;
+ *  Specifies list of columns to Discretize. First and last are valid indexes.
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ * <pre> -D
+ *  Output binary attributes for discretized attributes.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public class PKIDiscretize 
+  extends Discretize
+  implements TechnicalInformationHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 6153101248977702675L;
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    // alter child behaviour to do what we want
+    m_FindNumBins = true;
+    return super.setInputFormat(instanceInfo);
+  }
+
+  /**
+   * Finds the number of bins to use and creates the cut points.
+   *
+   * @param index the attribute index
+   */
+  protected void findNumBins(int index) {
+
+    Instances toFilter = getInputFormat();
+
+    // Find number of instances for attribute where not missing
+    int numOfInstances = toFilter.numInstances();
+    for (int i = 0; i < toFilter.numInstances(); i++) {
+      if (toFilter.instance(i).isMissing(index))
+	numOfInstances--;
+    }
+
+    m_NumBins = (int)(Math.sqrt(numOfInstances));
+
+    if (m_NumBins > 0) {
+      calculateCutPointsByEqualFrequencyBinning(index);
+    }
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    
+    result.addElement(new Option(
+	"\tUnsets the class index temporarily before the filter is\n"
+	+ "\tapplied to the data.\n"
+	+ "\t(default: no)",
+	"unset-class-temporarily", 1, "-unset-class-temporarily"));
+    
+    result.addElement(new Option(
+	"\tSpecifies list of columns to Discretize. First"
+	+ " and last are valid indexes.\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <col1,col2-col4,...>"));
+    
+    result.addElement(new Option(
+	"\tInvert matching sense of column indexes.",
+	"V", 0, "-V"));
+    
+    result.addElement(new Option(
+	"\tOutput binary attributes for discretized attributes.",
+	"D", 0, "-D"));
+    
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -unset-class-temporarily
+   *  Unsets the class index temporarily before the filter is
+   *  applied to the data.
+   *  (default: no)</pre>
+   * 
+   * <pre> -R &lt;col1,col2-col4,...&gt;
+   *  Specifies list of columns to Discretize. First and last are valid indexes.
+   *  (default: first-last)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense of column indexes.</pre>
+   * 
+   * <pre> -D
+   *  Output binary attributes for discretized attributes.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setIgnoreClass(Utils.getFlag("unset-class-temporarily", options));
+    setMakeBinary(Utils.getFlag('D', options));
+    setInvertSelection(Utils.getFlag('V', options));
+    
+    String convertList = Utils.getOption('R', options);
+    if (convertList.length() != 0) {
+      setAttributeIndices(convertList);
+    } else {
+      setAttributeIndices("first-last");
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result = new Vector();
+
+    if (getMakeBinary())
+      result.add("-D");
+    
+    if (getInvertSelection())
+      result.add("-V");
+    
+    if (!getAttributeIndices().equals("")) {
+      result.add("-R");
+      result.add(getAttributeIndices());
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Discretizes numeric attributes using equal frequency binning,"
+      + " where the number of bins is equal to the square root of the"
+      + " number of non-missing values.\n\n"
+      + "For more information, see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Ying Yang and Geoffrey I. Webb");
+    result.setValue(Field.TITLE, "Proportional k-Interval Discretization for Naive-Bayes Classifiers");
+    result.setValue(Field.BOOKTITLE, "12th European Conference on Machine Learning");
+    result.setValue(Field.YEAR, "2001");
+    result.setValue(Field.PAGES, "564-575");
+    result.setValue(Field.PUBLISHER, "Springer");
+    result.setValue(Field.SERIES, "LNCS");
+    result.setValue(Field.VOLUME, "2167");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String findNumBinsTipText() {
+
+    return "Ignored.";
+  }
+
+  /**
+   * Get the value of FindNumBins.
+   *
+   * @return Value of FindNumBins.
+   */
+  public boolean getFindNumBins() {
+    
+    return false;
+  }
+  
+  /**
+   * Set the value of FindNumBins.
+   *
+   * @param newFindNumBins Value to assign to FindNumBins.
+   */
+  public void setFindNumBins(boolean newFindNumBins) {
+    
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useEqualFrequencyTipText() {
+
+    return "Always true.";
+  }
+
+  /**
+   * Get the value of UseEqualFrequency.
+   *
+   * @return Value of UseEqualFrequency.
+   */
+  public boolean getUseEqualFrequency() {
+    
+    return true;
+  }
+  
+  /**
+   * Set the value of UseEqualFrequency.
+   *
+   * @param newUseEqualFrequency Value to assign to UseEqualFrequency.
+   */
+  public void setUseEqualFrequency(boolean newUseEqualFrequency) {
+    
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String binsTipText() {
+
+    return "Ignored.";
+  }
+
+  /**
+   * Ignored
+   *
+   * @return the number of bins.
+   */
+  public int getBins() {
+
+    return 0;
+  }
+
+  /**
+   * Ignored
+   *
+   * @param numBins the number of bins
+   */
+  public void setBins(int numBins) {
+
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.9 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new PKIDiscretize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PartitionedMultiFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PartitionedMultiFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PartitionedMultiFilter.java	(revision 29)
@@ -0,0 +1,709 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PartitionedMultiFilter.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.AllFilter;
+import weka.filters.Filter;
+import weka.filters.SimpleBatchFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter that applies filters on subsets of attributes and assembles the output into a new dataset. Attributes that are not covered by any of the ranges can be either retained or removed from the output.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -F &lt;classname [options]&gt;
+ *  A filter to apply (can be specified multiple times).</pre>
+ * 
+ * <pre> -R &lt;range&gt;
+ *  An attribute range (can be specified multiple times).
+ *  For each filter a range must be supplied. 'first' and 'last'
+ *  are valid indices.</pre>
+ * 
+ * <pre> -U
+ *  Flag for leaving unused attributes out of the output, by default
+ *  these are included in the filter output.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ * @see     weka.filters.StreamableFilter
+ */
+public class PartitionedMultiFilter
+  extends SimpleBatchFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6293720886005713120L;
+
+  /** The filters */
+  protected Filter m_Filters[] = {new AllFilter()};
+  
+  /** The attribute ranges */
+  protected Range m_Ranges[] = {new Range("first-last")};
+  
+  /** Whether unused attributes are left out of the output */
+  protected boolean m_RemoveUnused = false;
+  
+  /** the indices of the unused attributes */
+  protected int[] m_IndicesUnused = new int[0];
+  
+  /**
+   * Returns a string describing this filter
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter that applies filters on subsets of attributes and "
+      + "assembles the output into a new dataset. Attributes that are "
+      + "not covered by any of the ranges can be either retained or removed "
+      + "from the output.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+    Enumeration enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.add(enm.nextElement());
+      
+    result.addElement(new Option(
+        "\tA filter to apply (can be specified multiple times).",
+        "F", 1, "-F <classname [options]>"));
+
+    result.addElement(new Option(
+        "\tAn attribute range (can be specified multiple times).\n"
+	+ "\tFor each filter a range must be supplied. 'first' and 'last'\n"
+	+ "\tare valid indices.",
+        "R", 1, "-R <range>"));
+
+    result.addElement(new Option(
+        "\tFlag for leaving unused attributes out of the output, by default\n"
+	+ "\tthese are included in the filter output.",
+        "U", 0, "-U"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -F &lt;classname [options]&gt;
+   *  A filter to apply (can be specified multiple times).</pre>
+   * 
+   * <pre> -R &lt;range&gt;
+   *  An attribute range (can be specified multiple times).
+   *  For each filter a range must be supplied. 'first' and 'last'
+   *  are valid indices.</pre>
+   * 
+   * <pre> -U
+   *  Flag for leaving unused attributes out of the output, by default
+   *  these are included in the filter output.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+    String        classname;
+    String[]      options2;
+    Vector        objects;
+
+    super.setOptions(options);
+    
+    setRemoveUnused(Utils.getFlag("U", options));
+    
+    objects = new Vector();
+    while ((tmpStr = Utils.getOption("F", options)).length() != 0) {
+      options2    = Utils.splitOptions(tmpStr);
+      classname      = options2[0];
+      options2[0] = "";
+      objects.add(Utils.forName(Filter.class, classname, options2));
+    }
+
+    // at least one filter
+    if (objects.size() == 0)
+      objects.add(new AllFilter());
+
+    setFilters((Filter[]) objects.toArray(new Filter[objects.size()]));
+    
+    objects = new Vector();
+    while ((tmpStr = Utils.getOption("R", options)).length() != 0) {
+      objects.add(new Range(tmpStr));
+    }
+
+    // at least one Range
+    if (objects.size() == 0)
+      objects.add(new Range("first-last"));
+
+    setRanges((Range[]) objects.toArray(new Range[objects.size()]));
+    
+    // is number of filters the same as ranges?
+    checkDimensions();
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector	result;
+    String[]	options;
+    int		i;
+
+    result = new Vector();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+    
+    if (getRemoveUnused())
+      result.add("-U");
+    
+    for (i = 0; i < getFilters().length; i++) {
+      result.add("-F");
+      result.add(getFilterSpec(getFilter(i)));
+    }
+
+    for (i = 0; i < getRanges().length; i++) {
+      result.add("-R");
+      result.add("" + getRange(i).getRanges());
+    }
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * checks whether the dimensions of filters and ranges fit together
+   * 
+   * @throws Exception	if dimensions differ
+   */
+  protected void checkDimensions() throws Exception {
+    if (getFilters().length != getRanges().length)
+      throw new IllegalArgumentException(
+	  "Number of filters (= " + getFilters().length + ") "
+	  + "and ranges (= " + getRanges().length + ") don't match!");
+  }
+  
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities	result;
+    
+    if (getFilters().length == 0) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getFilters()[0].getCapabilities();
+    }
+    
+    // disable attributes
+    result.disable(Capability.STRING_ATTRIBUTES);
+    result.disableDependency(Capability.STRING_ATTRIBUTES);
+    result.disable(Capability.RELATIONAL_ATTRIBUTES);
+    result.disableDependency(Capability.RELATIONAL_ATTRIBUTES);
+    
+    return result;
+  }
+
+  /**
+   * Sets whether unused attributes (ones that are not covered by any of the
+   * ranges) are removed from the output.
+   * 
+   * @param value	if true then the unused attributes get removed
+   */
+  public void setRemoveUnused(boolean value) {
+    m_RemoveUnused = value;
+  }
+  
+  /**
+   * Gets whether unused attributes (ones that are not covered by any of the
+   * ranges) are removed from the output.
+   * 
+   * @return		true if unused attributes are removed
+   */
+  public boolean getRemoveUnused() {
+    return m_RemoveUnused;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return    	tip text for this property suitable for
+   *            	displaying in the explorer/experimenter gui
+   */
+  public String removeUnusedTipText() {
+    return 
+        "If true then unused attributes (ones that are not covered by any "
+      + "of the ranges) will be removed from the output.";
+  }
+  
+  /**
+   * Sets the list of possible filters to choose from.
+   * Also resets the state of the filter (this reset doesn't affect the 
+   * options).
+   *
+   * @param filters	an array of filters with all options set.
+   * @see #reset()
+   */
+  public void setFilters(Filter[] filters) {
+    m_Filters = filters;
+    reset();
+  }
+
+  /**
+   * Gets the list of possible filters to choose from.
+   *
+   * @return 		the array of Filters
+   */
+  public Filter[] getFilters() {
+    return m_Filters;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return    	tip text for this property suitable for
+   *            	displaying in the explorer/experimenter gui
+   */
+  public String filtersTipText() {
+    return "The base filters to be used.";
+  }
+  
+  /**
+   * Gets a single filter from the set of available filters.
+   *
+   * @param index 	the index of the filter wanted
+   * @return 		the Filter
+   */
+  public Filter getFilter(int index) {
+    return m_Filters[index];
+  }
+
+  /**
+   * returns the filter classname and the options as one string
+   * 
+   * @param filter	the filter to get the specs for
+   * @return		the classname plus options
+   */
+  protected String getFilterSpec(Filter filter) {
+    String        result;
+
+    if (filter == null) {
+      result = "";
+    }
+    else {
+      result  = filter.getClass().getName();
+      if (filter instanceof OptionHandler)
+        result += " " 
+          + Utils.joinOptions(((OptionHandler) filter).getOptions());
+    }
+
+    return result;
+  }
+
+  /**
+   * Sets the list of possible Ranges to choose from.
+   * Also resets the state of the Range (this reset doesn't affect the 
+   * options).
+   *
+   * @param Ranges	an array of Ranges with all options set.
+   * @see #reset()
+   */
+  public void setRanges(Range[] Ranges) {
+    m_Ranges = Ranges;
+    reset();
+  }
+
+  /**
+   * Gets the list of possible Ranges to choose from.
+   *
+   * @return 		the array of Ranges
+   */
+  public Range[] getRanges() {
+    return m_Ranges;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return    	tip text for this property suitable for
+   *            	displaying in the explorer/experimenter gui
+   */
+  public String rangesTipText() {
+    return "The attribute ranges to be used.";
+  }
+  
+  /**
+   * Gets a single Range from the set of available Ranges.
+   *
+   * @param index 	the index of the Range wanted
+   * @return 		the Range
+   */
+  public Range getRange(int index) {
+    return m_Ranges[index];
+  }
+  
+  /**
+   * determines the indices of unused attributes (ones that are not covered
+   * by any of the range)
+   * 
+   * @param data	the data to base the determination on
+   * @see 		#m_IndicesUnused
+   */
+  protected void determineUnusedIndices(Instances data) {
+    Vector<Integer>	indices;
+    int			i;
+    int			n;
+    boolean		covered;
+    
+    // traverse all ranges
+    indices = new Vector<Integer>();
+    for (i = 0; i < data.numAttributes(); i++) {
+      if (i == data.classIndex())
+	continue;
+      
+      covered = false;
+      for (n = 0; n < getRanges().length; n++) {
+	if (getRanges()[n].isInRange(i)) {
+	  covered = true;
+	  break;
+	}
+      }
+      
+      if (!covered)
+	indices.add(new Integer(i));
+    }
+    
+    // create array
+    m_IndicesUnused = new int[indices.size()];
+    for (i = 0; i < indices.size(); i++)
+      m_IndicesUnused[i] = indices.get(i).intValue();
+    
+    if (getDebug())
+      System.out.println(
+	  "Unused indices: " + Utils.arrayToString(m_IndicesUnused));
+  }
+  
+  /**
+   * generates a subset of the dataset with only the attributes from the range
+   * (class is always added if present)
+   * 
+   * @param data	the data to work on
+   * @param range	the range of attribute to use
+   * @return		the generated subset
+   * @throws Exception	if creation fails
+   */
+  protected Instances generateSubset(Instances data, Range range) throws Exception {
+    Remove	filter;
+    String	atts;
+    Instances	result;
+ 
+    // determine attributes
+    atts = range.getRanges();
+    if ((data.classIndex() > -1) && (!range.isInRange(data.classIndex())))
+      atts += "," + (data.classIndex() + 1);
+    
+    // setup filter
+    filter = new Remove();
+    filter.setAttributeIndices(atts);
+    filter.setInvertSelection(true);
+    filter.setInputFormat(data);
+    
+    // generate output
+    result = Filter.useFilter(data, filter);
+    
+    return result;
+  }
+  
+  /**
+   * renames all the attributes in the dataset (excluding the class if present)
+   * by adding the prefix to the name.
+   * 
+   * @param data	the data to work on
+   * @param prefix	the prefix for the attributes
+   * @return		a copy of the data with the attributes renamed
+   * @throws Exception	if renaming fails
+   */
+  protected Instances renameAttributes(Instances data, String prefix) throws Exception {
+    Instances	result;
+    int		i;
+    FastVector	atts;
+    
+    // rename attributes
+    atts = new FastVector();
+    for (i = 0; i < data.numAttributes(); i++) {
+      if (i == data.classIndex())
+	atts.addElement(data.attribute(i).copy());
+      else
+	atts.addElement(data.attribute(i).copy(prefix + data.attribute(i).name()));
+    }
+    
+    // create new dataset
+    result = new Instances(data.relationName(), atts, data.numInstances());
+    for (i = 0; i < data.numInstances(); i++) {
+      result.add((Instance) data.instance(i).copy());
+    }
+    
+    // set class if present
+    if (data.classIndex() > -1)
+      result.setClassIndex(data.classIndex());
+    
+    return result;
+  }
+  
+  /**
+   * Determines the output format based only on the full input dataset and 
+   * returns this otherwise null is returned. In case the output format cannot 
+   * be returned immediately, i.e., immediateOutputFormat() returns false, 
+   * then this method will be called from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see                   #hasImmediateOutputFormat()
+   * @see                   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Instances   result;
+    Instances	processed;
+    int         i;
+    int		n;
+    FastVector	atts;
+    Attribute	att;
+    
+    if (!isFirstBatchDone()) {
+      // we need the full dataset here, see process(Instances)
+      if (inputFormat.numInstances() == 0)
+	return null;
+
+      checkDimensions();
+
+      // determine unused indices
+      determineUnusedIndices(inputFormat);
+
+      atts = new FastVector();
+      for (i = 0; i < getFilters().length; i++) {
+	if (!isFirstBatchDone()) {
+	  // generate subset
+	  processed = generateSubset(inputFormat, getRange(i));
+	  // set input format
+	  if (!getFilter(i).setInputFormat(processed))
+	    Filter.useFilter(processed, getFilter(i));
+	}
+
+	// get output format
+	processed = getFilter(i).getOutputFormat();
+
+	// rename attributes
+	processed = renameAttributes(processed, "filtered-" + i + "-");
+
+	// add attributes
+	for (n = 0; n < processed.numAttributes(); n++) {
+	  if (n == processed.classIndex())
+	    continue;
+	  atts.addElement(processed.attribute(n).copy());
+	}
+      }
+
+      // add unused attributes
+      if (!getRemoveUnused()) {
+	for (i = 0; i < m_IndicesUnused.length; i++) {
+	  att = inputFormat.attribute(m_IndicesUnused[i]);
+	  atts.addElement(att.copy("unfiltered-" + att.name()));
+	}
+      }
+
+      // add class if present
+      if (inputFormat.classIndex() > -1)
+	atts.addElement(inputFormat.classAttribute().copy());
+
+      // generate new dataset
+      result = new Instances(inputFormat.relationName(), atts, 0);
+      if (inputFormat.classIndex() > -1)
+	result.setClassIndex(result.numAttributes() - 1);
+    }
+    else {
+      result = getOutputFormat();
+    }
+    
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances		result;
+    int        		i;
+    int			n;
+    int			m;
+    int			index;
+    Instances[]		processed;
+    Instance		inst;
+    Instance		newInst;
+    double[]		values;
+    Vector		errors;
+
+    if (!isFirstBatchDone()) {
+      checkDimensions();
+
+      // set upper limits
+      for (i = 0; i < m_Ranges.length; i++)
+	m_Ranges[i].setUpper(instances.numAttributes() - 1);
+
+      // determine unused indices
+      determineUnusedIndices(instances);
+    }
+
+    // pass data through all filters
+    processed = new Instances[getFilters().length];
+    for (i = 0; i < getFilters().length; i++) {
+      processed[i] = generateSubset(instances, getRange(i));
+      if (!isFirstBatchDone())
+	getFilter(i).setInputFormat(processed[i]);
+      processed[i] = Filter.useFilter(processed[i], getFilter(i));
+    }
+
+    // set output format (can only be determined with full dataset, hence here)
+    if (!isFirstBatchDone()) {
+      result = determineOutputFormat(instances);
+      setOutputFormat(result);
+    }
+    else {
+      result = getOutputFormat();
+    }
+    
+    // check whether all filters didn't change the number of instances
+    errors = new Vector();
+    for (i = 0; i < processed.length; i++) {
+      if (processed[i].numInstances() != instances.numInstances())
+	errors.add(new Integer(i));
+    }
+    if (errors.size() > 0)
+      throw new IllegalStateException(
+	  "The following filter(s) changed the number of instances: " + errors);
+    
+    // assemble data
+    for (i = 0; i < instances.numInstances(); i++) {
+      inst   = instances.instance(i);
+      values = new double[result.numAttributes()];
+
+      // filtered data
+      index = 0;
+      for (n = 0; n < processed.length; n++) {
+	for (m = 0; m < processed[n].numAttributes(); m++) {
+	  if (m == processed[n].classIndex())
+	    continue;
+	  values[index] = processed[n].instance(i).value(m);
+	  index++;
+	}
+      }
+      
+      // unused attributes
+      if (!getRemoveUnused()) {
+	for (n = 0; n < m_IndicesUnused.length; n++) {
+	  values[index] = inst.value(m_IndicesUnused[n]);
+	  index++;
+	}
+      }
+      
+      // class
+      if (instances.classIndex() > -1)
+	values[values.length - 1] = inst.value(instances.classIndex());
+
+      // generate and add instance
+      if (inst instanceof SparseInstance)
+	newInst = new SparseInstance(instances.instance(i).weight(), values);
+      else
+	newInst = new DenseInstance(instances.instance(i).weight(), values);
+      result.add(newInst);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for executing this class.
+   *
+   * @param args should contain arguments for the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new PartitionedMultiFilter(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PotentialClassIgnorer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PotentialClassIgnorer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PotentialClassIgnorer.java	(revision 29)
@@ -0,0 +1,171 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PotentialClassIgnorer.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.filters.Filter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * This filter should be extended by other unsupervised attribute
+ * filters to allow processing of the class attribute if that's
+ * required. It the class is to be ignored it is essential that the
+ * extending filter does not change the position (i.e. index) of the
+ * attribute that is originally the class attribute !
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz), Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $ 
+ */
+public abstract class PotentialClassIgnorer
+  extends Filter
+  implements OptionHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8625371119276845454L;
+
+  /** True if the class is to be unset */
+  protected boolean m_IgnoreClass = false;
+
+  /** Storing the class index */
+  protected int m_ClassIndex = -1;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+      
+    result.addElement(new Option(
+	"\tUnsets the class index temporarily before the filter is\n"
+	+ "\tapplied to the data.\n"
+	+ "\t(default: no)",
+	"unset-class-temporarily", 1, "-unset-class-temporarily"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object.
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    setIgnoreClass(Utils.getFlag("unset-class-temporarily", options));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result = new Vector();
+
+    if (getIgnoreClass())
+      result.add("-unset-class-temporarily");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the format of the input instances. If the filter is able to
+   * determine the output format before seeing any input instances, it
+   * does so here. This default implementation clears the output format
+   * and output queue, and the new batch flag is set. Overriders should
+   * call <code>super.setInputFormat(Instances)</code>
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    boolean result = super.setInputFormat(instanceInfo);
+    if (m_IgnoreClass) {
+      m_ClassIndex = inputFormatPeek().classIndex();
+      inputFormatPeek().setClassIndex(-1);
+    }      
+    return result;
+  }
+
+  /**
+   * Gets the format of the output instances. This should only be called
+   * after input() or batchFinished() has returned true. The relation
+   * name of the output instances should be changed to reflect the
+   * action of the filter (eg: add the filter name and options).
+   *
+   * @return an Instances object containing the output instance
+   * structure only.
+   * @throws NullPointerException if no input structure has been
+   * defined (or the output format hasn't been determined yet) 
+   */
+  public Instances getOutputFormat() {
+
+    if (m_IgnoreClass) {
+      outputFormatPeek().setClassIndex(m_ClassIndex);
+    }
+    return super.getOutputFormat();
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String ignoreClassTipText() {
+    return "The class index will be unset temporarily before the filter is applied.";
+  }
+
+  /**
+   * Set the IgnoreClass value. Set this to true if the
+   * class index is to be unset before the filter is applied.
+   * 
+   * @param newIgnoreClass The new IgnoreClass value.
+   */
+  public void setIgnoreClass(boolean newIgnoreClass) {
+    m_IgnoreClass = newIgnoreClass;
+  }
+  
+  /**
+   * Gets the IgnoreClass value. If this to true then the
+   * class index is to unset before the filter is applied.
+   * 
+   * @return the current IgnoreClass value.
+   */
+  public boolean getIgnoreClass() {
+    return m_IgnoreClass;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PrincipalComponents.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PrincipalComponents.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PrincipalComponents.java	(revision 29)
@@ -0,0 +1,816 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PrincipalComponents.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.matrix.EigenvalueDecomposition;
+import weka.core.matrix.Matrix;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Performs a principal components analysis and transformation of the data.<br/>
+ * Dimensionality reduction is accomplished by choosing enough eigenvectors to account for some percentage of the variance in the original data -- default 0.95 (95%).<br/>
+ * Based on code of the attribute selection scheme 'PrincipalComponents' by Mark Hall and Gabi Schmidberger.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Don't normalize input data.</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  Retain enough PC attributes to account
+ *  for this proportion of variance in the original data.
+ *  (default: 0.95)</pre>
+ * 
+ * <pre> -A &lt;num&gt;
+ *  Maximum number of attributes to include in 
+ *  transformed attribute names.
+ *  (-1 = include all, default: 5)</pre>
+ * 
+ * <pre> -M &lt;num&gt;
+ *  Maximum number of PC attributes to retain.
+ *  (-1 = include all, default: -1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz) -- attribute selection code
+ * @author Gabi Schmidberger (gabi@cs.waikato.ac.nz) -- attribute selection code
+ * @author fracpete (fracpete at waikato dot ac dot nz) -- filter code
+ * @version $Revision: 5987 $
+ */
+public class PrincipalComponents
+  extends Filter
+  implements OptionHandler, UnsupervisedFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4626939780964387784L;
+
+  /** The data to transform analyse/transform. */
+  protected Instances m_TrainInstances;
+
+  /** Keep a copy for the class attribute (if set). */
+  protected Instances m_TrainCopy;
+
+  /** The header for the transformed data format. */
+  protected Instances m_TransformedFormat;
+
+  /** Data has a class set. */
+  protected boolean m_HasClass;
+
+  /** Class index. */
+  protected int m_ClassIndex;
+
+  /** Number of attributes. */
+  protected int m_NumAttribs;
+
+  /** Number of instances. */
+  protected int m_NumInstances;
+
+  /** Correlation matrix for the original data. */
+  protected double[][] m_Correlation;
+
+  /** Will hold the unordered linear transformations of the (normalized)
+      original data. */
+  protected double[][] m_Eigenvectors;
+
+  /** Eigenvalues for the corresponding eigenvectors. */
+  protected double[] m_Eigenvalues = null;
+
+  /** Sorted eigenvalues. */
+  protected int[] m_SortedEigens;
+
+  /** sum of the eigenvalues. */
+  protected double m_SumOfEigenValues = 0.0;
+
+  /** Filters for replacing missing values. */
+  protected ReplaceMissingValues m_ReplaceMissingFilter;
+  
+  /** Filter for normalizing the data. */
+  protected Normalize m_NormalizeFilter;
+  
+  /** Filter for turning nominal values into numeric ones. */
+  protected NominalToBinary m_NominalToBinaryFilter;
+  
+  /** Filter for removing class attribute, nominal attributes with 0 or 1 value. */
+  protected Remove m_AttributeFilter;
+
+  /** The number of attributes in the pc transformed data. */
+  protected int m_OutputNumAtts = -1;
+
+  /** normalize the input data? */
+  protected boolean m_Normalize = true;
+
+  /** the amount of varaince to cover in the original data when
+      retaining the best n PC's. */
+  protected double m_CoverVariance = 0.95;
+
+  /** maximum number of attributes in the transformed attribute name. */
+  protected int m_MaxAttrsInName = 5;
+
+  /** maximum number of attributes in the transformed data (-1 for all). */
+  protected int m_MaxAttributes = -1;
+
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Performs a principal components analysis and transformation of "
+      + "the data.\n"
+      + "Dimensionality reduction is accomplished by choosing enough eigenvectors "
+      + "to account for some percentage of the variance in the original data -- "
+      + "default 0.95 (95%).\n"
+      + "Based on code of the attribute selection scheme 'PrincipalComponents' "
+      + "by Mark Hall and Gabi Schmidberger.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tDon't normalize input data.", 
+	"D", 0, "-D"));
+
+    result.addElement(new Option(
+	"\tRetain enough PC attributes to account\n"
+	+"\tfor this proportion of variance in the original data.\n"
+	+ "\t(default: 0.95)",
+	"R", 1, "-R <num>"));
+
+    result.addElement(new Option(
+	"\tMaximum number of attributes to include in \n"
+	+ "\ttransformed attribute names.\n"
+	+ "\t(-1 = include all, default: 5)", 
+	"A", 1, "-A <num>"));
+
+    result.addElement(new Option(
+	"\tMaximum number of PC attributes to retain.\n"
+	+ "\t(-1 = include all, default: -1)", 
+	"M", 1, "-M <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a list of options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Don't normalize input data.</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  Retain enough PC attributes to account
+   *  for this proportion of variance in the original data.
+   *  (default: 0.95)</pre>
+   * 
+   * <pre> -A &lt;num&gt;
+   *  Maximum number of attributes to include in 
+   *  transformed attribute names.
+   *  (-1 = include all, default: 5)</pre>
+   * 
+   * <pre> -M &lt;num&gt;
+   *  Maximum number of PC attributes to retain.
+   *  (-1 = include all, default: -1)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setVarianceCovered(Double.parseDouble(tmpStr));
+    else
+      setVarianceCovered(0.95);
+
+    tmpStr = Utils.getOption('A', options);
+    if (tmpStr.length() != 0)
+      setMaximumAttributeNames(Integer.parseInt(tmpStr));
+    else
+      setMaximumAttributeNames(5);
+
+    tmpStr = Utils.getOption('M', options);
+    if (tmpStr.length() != 0)
+      setMaximumAttributes(Integer.parseInt(tmpStr));
+    else
+      setMaximumAttributes(-1);
+
+    setNormalize(!Utils.getFlag('D', options));
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+
+    result = new Vector<String>();
+
+    result.add("-R");
+    result.add("" + getVarianceCovered());
+
+    result.add("-A");
+    result.add("" + getMaximumAttributeNames());
+
+    result.add("-M");
+    result.add("" + getMaximumAttributes());
+
+    if (!getNormalize())
+      result.add("-D");
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String normalizeTipText() {
+    return "Normalize input data.";
+  }
+
+  /**
+   * Set whether input data will be normalized.
+   * 
+   * @param value 	true if input data is to be normalized
+   */
+  public void setNormalize(boolean value) {
+    m_Normalize = value;
+  }
+
+  /**
+   * Gets whether or not input data is to be normalized.
+   * 
+   * @return 		true if input data is to be normalized
+   */
+  public boolean getNormalize() {
+    return m_Normalize;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String varianceCoveredTipText() {
+    return "Retain enough PC attributes to account for this proportion of variance.";
+  }
+
+  /**
+   * Sets the amount of variance to account for when retaining
+   * principal components.
+   * 
+   * @param value 	the proportion of total variance to account for
+   */
+  public void setVarianceCovered(double value) {
+    m_CoverVariance = value;
+  }
+
+  /**
+   * Gets the proportion of total variance to account for when
+   * retaining principal components.
+   * 
+   * @return 		the proportion of variance to account for
+   */
+  public double getVarianceCovered() {
+    return m_CoverVariance;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maximumAttributeNamesTipText() {
+    return "The maximum number of attributes to include in transformed attribute names.";
+  }
+
+  /**
+   * Sets maximum number of attributes to include in
+   * transformed attribute names.
+   * 
+   * @param value 	the maximum number of attributes
+   */
+  public void setMaximumAttributeNames(int value) {
+    m_MaxAttrsInName = value;
+  }
+
+  /**
+   * Gets maximum number of attributes to include in
+   * transformed attribute names.
+   * 
+   * @return 		the maximum number of attributes
+   */
+  public int getMaximumAttributeNames() {
+    return m_MaxAttrsInName;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maximumAttributesTipText() {
+    return "The maximum number of PC attributes to retain.";
+  }
+
+  /**
+   * Sets maximum number of PC attributes to retain.
+   * 
+   * @param value 	the maximum number of attributes
+   */
+  public void setMaximumAttributes(int value) {
+    m_MaxAttributes = value;
+  }
+
+  /**
+   * Gets maximum number of PC attributes to retain.
+   * 
+   * @return 		the maximum number of attributes
+   */
+  public int getMaximumAttributes() {
+    return m_MaxAttributes;
+  }
+
+  /**
+   * Returns the capabilities of this evaluator.
+   *
+   * @return            the capabilities of this evaluator
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    double 		cumulative;
+    FastVector 		attributes;
+    int 		i;
+    int 		j;
+    StringBuffer 	attName;
+    double[] 		coeff_mags;
+    int 		num_attrs;
+    int[] 		coeff_inds;
+    double 		coeff_value;
+    int			numAttsLowerBound;
+    
+    if (m_Eigenvalues == null)
+      return inputFormat;
+
+    if (m_MaxAttributes > 0)
+      numAttsLowerBound = m_NumAttribs - m_MaxAttributes;
+    else
+      numAttsLowerBound = 0;
+    if (numAttsLowerBound < 0)
+      numAttsLowerBound = 0;
+    
+    cumulative = 0.0;
+    attributes = new FastVector();
+    for (i = m_NumAttribs - 1; i >= numAttsLowerBound; i--) {
+      attName = new StringBuffer();
+      // build array of coefficients
+      coeff_mags = new double[m_NumAttribs];
+      for (j = 0; j < m_NumAttribs; j++)
+	coeff_mags[j] = -Math.abs(m_Eigenvectors[j][m_SortedEigens[i]]);
+      num_attrs = (m_MaxAttrsInName > 0) ? Math.min(m_NumAttribs, m_MaxAttrsInName) : m_NumAttribs;
+
+      // this array contains the sorted indices of the coefficients
+      if (m_NumAttribs > 0) {
+	// if m_maxAttrsInName > 0, sort coefficients by decreasing magnitude
+	coeff_inds = Utils.sort(coeff_mags);
+      }
+      else {
+	// if  m_maxAttrsInName <= 0, use all coeffs in original order
+	coeff_inds = new int[m_NumAttribs];
+	for (j = 0; j < m_NumAttribs; j++)
+	  coeff_inds[j] = j;
+      }
+      // build final attName string
+      for (j = 0; j < num_attrs; j++) {
+	coeff_value = m_Eigenvectors[coeff_inds[j]][m_SortedEigens[i]];
+	if (j > 0 && coeff_value >= 0)
+	  attName.append("+");
+	attName.append(
+	    Utils.doubleToString(coeff_value,5,3) 
+	    + inputFormat.attribute(coeff_inds[j]).name());
+      }
+      if (num_attrs < m_NumAttribs)
+	attName.append("...");
+
+      attributes.addElement(new Attribute(attName.toString()));
+      cumulative += m_Eigenvalues[m_SortedEigens[i]];
+
+      if ((cumulative / m_SumOfEigenValues) >= m_CoverVariance)
+	break;
+    }
+
+    if (m_HasClass)
+      attributes.addElement(m_TrainCopy.classAttribute().copy());
+
+    Instances outputFormat = 
+      new Instances(
+	  m_TrainCopy.relationName() + "_principal components", attributes, 0);
+
+    // set the class to be the last attribute if necessary
+    if (m_HasClass)
+      outputFormat.setClassIndex(outputFormat.numAttributes() - 1);
+
+    m_OutputNumAtts = outputFormat.numAttributes();
+    
+    return outputFormat;
+  }
+
+  /**
+   * Fill the correlation matrix.
+   */
+  protected void fillCorrelation() {
+    int		i;
+    int		j;
+    int		k;
+    double[] 	att1;
+    double[] 	att2;
+    double 	corr;
+    
+    m_Correlation = new double[m_NumAttribs][m_NumAttribs];
+    att1          = new double [m_NumInstances];
+    att2          = new double [m_NumInstances];
+
+    for (i = 0; i < m_NumAttribs; i++) {
+      for (j = 0; j < m_NumAttribs; j++) {
+	if (i == j) {
+	  m_Correlation[i][j] = 1.0;
+	}
+	else {
+	  for (k = 0; k < m_NumInstances; k++) {
+	    att1[k] = m_TrainInstances.instance(k).value(i);
+	    att2[k] = m_TrainInstances.instance(k).value(j);
+	  }
+	  corr = Utils.correlation(att1,att2,m_NumInstances);
+	  m_Correlation[i][j] = corr;
+	  m_Correlation[j][i] = corr;
+	}
+      }
+    }
+  }
+
+  /**
+   * Transform an instance in original (unormalized) format.
+   * 
+   * @param instance 	an instance in the original (unormalized) format
+   * @return 		a transformed instance
+   * @throws Exception 	if instance can't be transformed
+   */
+  protected Instance convertInstance(Instance instance) throws Exception {
+    Instance	result;
+    double[] 	newVals;
+    Instance 	tempInst;
+    double 	cumulative;
+    int		i;
+    int		j;
+    double 	tempval;
+    int		numAttsLowerBound;
+    
+    newVals  = new double[m_OutputNumAtts];
+    tempInst = (Instance) instance.copy();
+
+    m_ReplaceMissingFilter.input(tempInst);
+    m_ReplaceMissingFilter.batchFinished();
+    tempInst = m_ReplaceMissingFilter.output();
+
+    if (m_Normalize) {
+      m_NormalizeFilter.input(tempInst);
+      m_NormalizeFilter.batchFinished();
+      tempInst = m_NormalizeFilter.output();
+    }
+
+    m_NominalToBinaryFilter.input(tempInst);
+    m_NominalToBinaryFilter.batchFinished();
+    tempInst = m_NominalToBinaryFilter.output();
+
+    if (m_AttributeFilter != null) {
+      m_AttributeFilter.input(tempInst);
+      m_AttributeFilter.batchFinished();
+      tempInst = m_AttributeFilter.output();
+    }
+
+    if (m_HasClass)
+      newVals[m_OutputNumAtts - 1] = instance.value(instance.classIndex());
+
+    if (m_MaxAttributes > 0)
+      numAttsLowerBound = m_NumAttribs - m_MaxAttributes;
+    else
+      numAttsLowerBound = 0;
+    if (numAttsLowerBound < 0)
+      numAttsLowerBound = 0;
+    
+    cumulative = 0;
+    for (i = m_NumAttribs - 1; i >= numAttsLowerBound; i--) {
+      tempval = 0.0;
+      for (j = 0; j < m_NumAttribs; j++)
+	tempval += m_Eigenvectors[j][m_SortedEigens[i]] * tempInst.value(j);
+
+      newVals[m_NumAttribs - i - 1] = tempval;
+      cumulative += m_Eigenvalues[m_SortedEigens[i]];
+      if ((cumulative / m_SumOfEigenValues) >= m_CoverVariance)
+	break;
+    }
+
+    // create instance
+    if (instance instanceof SparseInstance)
+      result = new SparseInstance(instance.weight(), newVals);
+    else
+      result = new DenseInstance(instance.weight(), newVals);
+    
+    return result;
+  }
+
+  /**
+   * Initializes the filter with the given input data.
+   *
+   * @param instances   the data to process
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected void setup(Instances instances) throws Exception {
+    int				i;
+    int				j;
+    Vector<Integer> 		deleteCols;
+    int[] 			todelete;
+    double[][] 			v;
+    Matrix 			corr;
+    EigenvalueDecomposition 	eig;
+    Matrix 			V;
+    
+    m_TrainInstances = new Instances(instances);
+
+    // make a copy of the training data so that we can get the class
+    // column to append to the transformed data (if necessary)
+    m_TrainCopy = new Instances(m_TrainInstances, 0);
+
+    m_ReplaceMissingFilter = new ReplaceMissingValues();
+    m_ReplaceMissingFilter.setInputFormat(m_TrainInstances);
+    m_TrainInstances = Filter.useFilter(m_TrainInstances, m_ReplaceMissingFilter);
+
+    if (m_Normalize) {
+      m_NormalizeFilter = new Normalize();
+      m_NormalizeFilter.setInputFormat(m_TrainInstances);
+      m_TrainInstances = Filter.useFilter(m_TrainInstances, m_NormalizeFilter);
+    }
+
+    m_NominalToBinaryFilter = new NominalToBinary();
+    m_NominalToBinaryFilter.setInputFormat(m_TrainInstances);
+    m_TrainInstances = Filter.useFilter(m_TrainInstances, m_NominalToBinaryFilter);
+
+    // delete any attributes with only one distinct value or are all missing
+    deleteCols = new Vector<Integer>();
+    for (i = 0; i < m_TrainInstances.numAttributes(); i++) {
+      if (m_TrainInstances.numDistinctValues(i) <= 1)
+	deleteCols.addElement(i);
+    }
+
+    if (m_TrainInstances.classIndex() >=0) {
+      // get rid of the class column
+      m_HasClass = true;
+      m_ClassIndex = m_TrainInstances.classIndex();
+      deleteCols.addElement(new Integer(m_ClassIndex));
+    }
+
+    // remove columns from the data if necessary
+    if (deleteCols.size() > 0) {
+      m_AttributeFilter = new Remove();
+      todelete = new int [deleteCols.size()];
+      for (i = 0; i < deleteCols.size(); i++)
+	todelete[i] = ((Integer)(deleteCols.elementAt(i))).intValue();
+      m_AttributeFilter.setAttributeIndicesArray(todelete);
+      m_AttributeFilter.setInvertSelection(false);
+      m_AttributeFilter.setInputFormat(m_TrainInstances);
+      m_TrainInstances = Filter.useFilter(m_TrainInstances, m_AttributeFilter);
+    }
+
+    // can evaluator handle the processed data ? e.g., enough attributes?
+    getCapabilities().testWithFail(m_TrainInstances);
+
+    m_NumInstances = m_TrainInstances.numInstances();
+    m_NumAttribs   = m_TrainInstances.numAttributes();
+
+    fillCorrelation();
+
+    // get eigen vectors/values
+    corr = new Matrix(m_Correlation);
+    eig  = corr.eig();
+    V    = eig.getV();
+    v    = new double[m_NumAttribs][m_NumAttribs];
+    for (i = 0; i < v.length; i++) {
+      for (j = 0; j < v[0].length; j++)
+        v[i][j] = V.get(i, j);
+    }
+    m_Eigenvectors = (double[][]) v.clone();
+    m_Eigenvalues  = (double[]) eig.getRealEigenvalues().clone();
+
+    // any eigenvalues less than 0 are not worth anything --- change to 0
+    for (i = 0; i < m_Eigenvalues.length; i++) {
+      if (m_Eigenvalues[i] < 0)
+	m_Eigenvalues[i] = 0.0;
+    }
+    m_SortedEigens     = Utils.sort(m_Eigenvalues);
+    m_SumOfEigenValues = Utils.sum(m_Eigenvalues);
+
+    m_TransformedFormat = determineOutputFormat(m_TrainInstances);
+    setOutputFormat(m_TransformedFormat);
+    
+    m_TrainInstances = null;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo 	an Instances object containing the input 
+   * 				instance structure (any instances contained 
+   * 				in the object are ignored - only the structure 
+   * 				is required).
+   * @return 			true if the outputFormat may be collected 
+   * 				immediately
+   * @throws Exception 		if the input format can't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+
+    m_Eigenvalues           = null;
+    m_OutputNumAtts         = -1;
+    m_AttributeFilter       = null;
+    m_NominalToBinaryFilter = null;
+    m_SumOfEigenValues      = 0.0;
+    
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance 			the input instance
+   * @return 				true if the filtered instance may now be
+   * 					collected with output().
+   * @throws IllegalStateException 	if no input format has been set
+   * @throws Exception 			if conversion fails
+   */
+  public boolean input(Instance instance) throws Exception {
+    Instance 	inst;
+    
+    if (getInputFormat() == null)
+      throw new IllegalStateException("No input instance format defined");
+
+    if (isNewBatch()) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (isFirstBatchDone()) {
+      inst = convertInstance(instance);
+      inst.setDataset(getOutputFormat());
+      push(inst);
+      return true;
+    }
+    else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true 			if there are instances pending output
+   * @throws NullPointerException 	if no input structure has been defined,
+   * @throws Exception 			if there was a problem finishing the batch.
+   */
+  public boolean batchFinished() throws Exception {
+    int		i;
+    Instances	insts;
+    Instance	inst;
+    
+    if (getInputFormat() == null)
+      throw new NullPointerException("No input instance format defined");
+
+    insts = getInputFormat();
+
+    if (!isFirstBatchDone())
+      setup(insts);
+    
+    for (i = 0; i < insts.numInstances(); i++) {
+      inst = convertInstance(insts.instance(i));
+      inst.setDataset(getOutputFormat());
+      push(inst);
+    }
+    
+    flushInput();
+    m_NewBatch       = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args 	should contain arguments to the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new PrincipalComponents(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstance.java	(revision 29)
@@ -0,0 +1,442 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PropositionalToMultiInstance.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RelationalLocator;
+import weka.core.RevisionUtils;
+import weka.core.StringLocator;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts the propositional instance dataset into multi-instance dataset (with relational attribute). When normalize or standardize a multi-instance dataset, a MIToSingleInstance filter can be applied first to convert the multi-instance dataset into propositional instance dataset. After normalization or standardization, may use this PropositionalToMultiInstance filter to convert the data back to multi-instance format.<br/>
+ * <br/>
+ * Note: the first attribute of the original propositional instance dataset must be a nominal attribute which is expected to be bagId attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  The seed for the randomization of the order of bags. (default 1)</pre>
+ * 
+ * <pre> -R
+ *  Randomizes the order of the produced bags after the generation. (default off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Lin Dong (ld21@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ * @see MultiInstanceToPropositional
+ */
+public class PropositionalToMultiInstance 
+  extends Filter
+  implements OptionHandler, UnsupervisedFilter {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5825873573912102482L;
+
+  /** the seed for randomizing, default is 1 */
+  protected int m_Seed = 1;
+  
+  /** whether to randomize the output data */
+  protected boolean m_Randomize = false;
+
+  /** Indices of string attributes in the bag */
+  protected StringLocator m_BagStringAtts = null;
+
+  /** Indices of relational attributes in the bag */
+  protected RelationalLocator m_BagRelAtts = null;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return  
+        "Converts the propositional instance dataset into multi-instance "
+      + "dataset (with relational attribute). When normalize or standardize a "
+      + "multi-instance dataset, a MIToSingleInstance filter can be applied "
+      + "first to convert the multi-instance dataset into propositional "
+      + "instance dataset. After normalization or standardization, may use "
+      + "this PropositionalToMultiInstance filter to convert the data back to "
+      + "multi-instance format.\n\n"
+      + "Note: the first attribute of the original propositional instance "
+      + "dataset must be a nominal attribute which is expected to be bagId "
+      + "attribute.";
+
+  }
+
+  /**
+   * Returns an enumeration describing the available options
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+  
+    result.addElement(new Option(
+        "\tThe seed for the randomization of the order of bags."
+        + "\t(default 1)",
+        "S", 1, "-S <num>"));
+  
+    result.addElement(new Option(
+        "\tRandomizes the order of the produced bags after the generation."
+        + "\t(default off)",
+        "R", 0, "-R"));
+  
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  The seed for the randomization of the order of bags. (default 1)</pre>
+   * 
+   * <pre> -R
+   *  Randomizes the order of the produced bags after the generation. (default off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String        tmpStr;
+    
+    setRandomize(Utils.getFlag('R', options));
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(1);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector        result;
+    
+    result = new Vector();
+    
+    result.add("-S");
+    result.add("" + getSeed());
+    
+    if (m_Randomize)
+      result.add("-R");
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The random seed used by the random number generator";
+  }
+
+  /**
+   * Sets the new seed for randomizing the order of the generated data
+   * 
+   * @param value     the new seed value
+   */
+  public void setSeed(int value) {
+    m_Seed = value;
+  }
+  
+  /**
+   * Returns the current seed value for randomizing the order of the generated
+   * data
+   * 
+   * @return          the current seed value
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+  
+  /**
+   * Sets whether the order of the generated data is randomized
+   * 
+   * @param value     whether to randomize or not
+   */
+  public void setRandomize(boolean value) {
+    m_Randomize = value;
+  }
+  
+  /**
+   * Gets whether the order of the generated is randomized
+   * 
+   * @return      true if the order is randomized
+   */
+  public boolean getRandomize() {
+    return m_Randomize;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomizeTipText() {
+    return "Whether the order of the generated data is randomized.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+    throws Exception {
+
+    if (instanceInfo.attribute(0).type()!= Attribute.NOMINAL) {
+      throw new Exception("The first attribute type of the original propositional instance dataset must be Nominal!");
+    }
+    super.setInputFormat(instanceInfo);
+
+    /* create a new output format (multi-instance format) */
+    Instances newData = instanceInfo.stringFreeStructure();
+    Attribute attBagIndex = (Attribute) newData.attribute(0).copy();
+    Attribute attClass = (Attribute) newData.classAttribute().copy();
+    // remove the bagIndex attribute
+    newData.deleteAttributeAt(0);
+    // remove the class attribute
+    newData.setClassIndex(-1);
+    newData.deleteAttributeAt(newData.numAttributes() - 1);
+
+    FastVector attInfo = new FastVector(3); 
+    attInfo.addElement(attBagIndex);
+    attInfo.addElement(new Attribute("bag", newData)); // relation-valued attribute
+    attInfo.addElement(attClass);
+    Instances data = new Instances("Multi-Instance-Dataset", attInfo, 0); 
+    data.setClassIndex(data.numAttributes() - 1);
+
+    super.setOutputFormat(data.stringFreeStructure());
+
+    m_BagStringAtts = new StringLocator(data.attribute(1).relation());
+    m_BagRelAtts    = new RelationalLocator(data.attribute(1).relation());
+    
+    return true;
+  }
+
+  /**
+   * adds a new bag out of the given data and adds it to the output
+   * 
+   * @param input       the intput dataset
+   * @param output      the dataset this bag is added to
+   * @param bagInsts    the instances in this bag
+   * @param bagIndex    the bagIndex of this bag
+   * @param classValue  the associated class value
+   * @param bagWeight   the weight of the bag
+   */
+  protected void addBag(
+      Instances input,
+      Instances output,
+      Instances bagInsts, 
+      int bagIndex, 
+      double classValue, 
+      double bagWeight) {
+    
+    // copy strings/relational values
+    for (int i = 0; i < bagInsts.numInstances(); i++) {
+      RelationalLocator.copyRelationalValues(
+	  bagInsts.instance(i), false, 
+	  input, m_InputRelAtts,
+	  bagInsts, m_BagRelAtts);
+
+      StringLocator.copyStringValues(
+	  bagInsts.instance(i), false, 
+	  input, m_InputStringAtts,
+	  bagInsts, m_BagStringAtts);
+    }
+    
+    int value = output.attribute(1).addRelation(bagInsts);
+    Instance newBag = new DenseInstance(output.numAttributes());        
+    newBag.setValue(0, bagIndex);
+    newBag.setValue(2, classValue);
+    newBag.setValue(1, value);
+    newBag.setWeight(bagWeight);
+    newBag.setDataset(output);
+    output.add(newBag);
+  }
+
+  /**
+   * Adds an output instance to the queue. The derived class should use this
+   * method for each output instance it makes available. 
+   *
+   * @param instance the instance to be added to the queue.
+   */
+  protected void push(Instance instance) {
+    if (instance != null) {
+      super.push(instance);
+      // set correct references
+    }
+  }
+  
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    Instances input = getInputFormat();
+    input.sort(0);   // make sure that bagID is sorted
+    Instances output = getOutputFormat();
+    Instances bagInsts = output.attribute(1).relation();
+    Instance inst = new DenseInstance(bagInsts.numAttributes());
+    inst.setDataset(bagInsts);
+
+    double bagIndex   = input.instance(0).value(0);
+    double classValue = input.instance(0).classValue(); 
+    double bagWeight  = 0.0;
+
+    // Convert pending input instances
+    for(int i = 0; i < input.numInstances(); i++) {
+      double currentBagIndex = input.instance(i).value(0);
+
+      // copy the propositional instance value, except the bagIndex and the class value
+      for (int j = 0; j < input.numAttributes() - 2; j++) 
+        inst.setValue(j, input.instance(i).value(j + 1));
+      inst.setWeight(input.instance(i).weight());
+
+      if (currentBagIndex == bagIndex){
+        bagInsts.add(inst);
+        bagWeight += inst.weight();
+      }
+      else{
+        addBag(input, output, bagInsts, (int) bagIndex, classValue, bagWeight);
+
+        bagInsts   = bagInsts.stringFreeStructure();  
+        bagInsts.add(inst);
+        bagIndex   = currentBagIndex;
+        classValue = input.instance(i).classValue();
+        bagWeight  = inst.weight();
+      }
+    }
+
+    // reach the last instance, create and add the last bag
+    addBag(input, output, bagInsts, (int) bagIndex, classValue, bagWeight);
+
+    if (getRandomize())
+      output.randomize(new Random(getSeed()));
+    
+    for (int i = 0; i < output.numInstances(); i++)
+      push(output.instance(i));
+    
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new PropositionalToMultiInstance(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RELAGGS.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RELAGGS.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RELAGGS.java	(revision 29)
@@ -0,0 +1,593 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RELAGGS.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.SimpleBatchFilter;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A propositionalization filter inspired by the RELAGGS algorithm.<br/>
+ * It processes all relational attributes that fall into the user defined range (all others are skipped, i.e., not added to the output). Currently, the filter only processes one level of nesting.<br/>
+ * The class attribute is not touched.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * M.-A. Krogel, S. Wrobel: Facets of Aggregation Approaches to Propositionalization. In: Work-in-Progress Track at the Thirteenth International Conference on Inductive Logic Programming (ILP), 2003.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Krogel2003,
+ *    author = {M.-A. Krogel and S. Wrobel},
+ *    booktitle = {Work-in-Progress Track at the Thirteenth International Conference on Inductive Logic Programming (ILP)},
+ *    editor = {T. Horvath and A. Yamamoto},
+ *    title = {Facets of Aggregation Approaches to Propositionalization},
+ *    year = {2003},
+ *    PDF = {http://kd.cs.uni-magdeburg.de/\~krogel/papers/aggs.pdf}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of string attributes to convert to words.
+ *  (default: select all relational attributes)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the matching sense of the selection.</pre>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  Max. cardinality of nominal attributes. If a nominal attribute
+ *  has more values than this upper limit, then it will be skipped.
+ *  (default: 20)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class RELAGGS
+  extends SimpleBatchFilter
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3333791375278589231L;
+  
+  /** the max. cardinality for nominal attributes */
+  protected int m_MaxCardinality = 20;
+
+  /** the range of attributes to process (only relational ones will be processed) */
+  protected Range m_SelectedRange = new Range("first-last");
+  
+  /** stores the attribute statistics 
+   * <code>att_index-att_index_in_rel_att &lt;-&gt; AttributeStats</code> */
+  protected Hashtable<String,AttributeStats> m_AttStats = new Hashtable<String,AttributeStats>();
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A propositionalization filter inspired by the RELAGGS algorithm.\n"
+      + "It processes all relational attributes that fall into the user defined "
+      + "range (all others are skipped, i.e., not added to the output). "
+      + "Currently, the filter only processes one level of nesting.\n"
+      + "The class attribute is not touched.\n"
+      + "\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return 		the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "M.-A. Krogel and S. Wrobel");
+    result.setValue(Field.TITLE, "Facets of Aggregation Approaches to Propositionalization");
+    result.setValue(Field.BOOKTITLE, "Work-in-Progress Track at the Thirteenth International Conference on Inductive Logic Programming (ILP)");
+    result.setValue(Field.EDITOR, "T. Horvath and A. Yamamoto");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.PDF, "http://kd.cs.uni-magdeburg.de/~krogel/papers/aggs.pdf");
+    
+    return result;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        	result;
+    Enumeration   	en;
+
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tSpecify list of string attributes to convert to words.\n"
+	+ "\t(default: select all relational attributes)",
+	"R", 1, "-R <index1,index2-index4,...>"));
+
+    result.addElement(new Option(
+	"\tInverts the matching sense of the selection.",
+	"V", 0, "-V"));
+
+    result.addElement(new Option(
+	"\tMax. cardinality of nominal attributes. If a nominal attribute\n"
+	+ "\thas more values than this upper limit, then it will be skipped.\n"
+	+ "\t(default: 20)",
+	"C", 1, "-C <num>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of string attributes to convert to words.
+   *  (default: select all relational attributes)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the matching sense of the selection.</pre>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  Max. cardinality of nominal attributes. If a nominal attribute
+   *  has more values than this upper limit, then it will be skipped.
+   *  (default: 20)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setSelectedRange(tmpStr);
+    else
+      setSelectedRange("first-last");
+
+    setInvertSelection(Utils.getFlag('V', options));
+    
+    tmpStr = Utils.getOption('C', options);
+    if (tmpStr.length() != 0)
+      setMaxCardinality(Integer.parseInt(tmpStr));
+    else
+      setMaxCardinality(20);
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int			i;
+    Vector<String>	result;
+    String[]		options;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-R"); 
+    result.add(getSelectedRange().getRanges());
+
+    if (getInvertSelection())
+      result.add("-V");
+    
+    result.add("-C");
+    result.add("" + getMaxCardinality());
+    
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String maxCardinalityTipText() {
+    return "The maximum number of values a nominal attribute can have before it's skipped.";
+  }
+
+  /**
+   * Sets the maximum number of values allowed for nominal attributes, before
+   * they're skipped.
+   *
+   * @param value 	the maximum value.
+   */
+  public void setMaxCardinality(int value) {
+    m_MaxCardinality = value;
+  }
+  
+  /**
+   * Gets the maximum number of values allowed for nominal attributes, before
+   * they're skipped.
+   *
+   * @return 		the maximum number.
+   */
+  public int getMaxCardinality() {
+    return m_MaxCardinality;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return 
+        "Specify range of attributes to act on; "
+      + "this is a comma separated list of attribute indices, with "
+      + "\"first\" and \"last\" valid values; Specify an inclusive "
+      + "range with \"-\"; eg: \"first-3,5,6-10,last\".";
+  }
+  
+  /**
+   * Set the range of attributes to process.
+   *
+   * @param value 	the new range.
+   */
+  public void setSelectedRange(String value) {
+    m_SelectedRange = new Range(value);
+  }
+
+  /**
+   * Gets the current range selection.
+   *
+   * @return 		current selection.
+   */
+  public Range getSelectedRange() {
+    return m_SelectedRange;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return 
+        "Set attribute selection mode. If false, only selected "
+      + "attributes in the range will be worked on; if "
+      + "true, only non-selected attributes will be processed.";
+  }
+
+  /**
+   * Sets whether selected columns should be processed or skipped.
+   *
+   * @param value 	the new invert setting
+   */
+  public void setInvertSelection(boolean value) {
+    m_SelectedRange.setInvert(value);
+  }
+
+  /**
+   * Gets whether the supplied columns are to be processed or skipped
+   *
+   * @return 		true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+    return m_SelectedRange.getInvert();
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+
+    Instances 	result;
+    Instances	relFormat;
+    FastVector	atts;
+    int		i;
+    int		n;
+    int		m;
+    int		clsIndex;
+    Attribute	att;
+    String	prefix;
+
+    m_SelectedRange.setUpper(inputFormat.numAttributes() - 1);
+    
+    atts     = new FastVector();
+    clsIndex = -1;
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      // we don't process the class
+      if (i == inputFormat.classIndex()) {
+	clsIndex = atts.size();
+	atts.addElement(inputFormat.attribute(i).copy());
+	continue;
+      }
+      
+      if (!inputFormat.attribute(i).isRelationValued()) {
+	atts.addElement(inputFormat.attribute(i).copy());
+	continue;
+      }
+      
+      if (!m_SelectedRange.isInRange(i)) {
+	if (getDebug())
+	  System.out.println(
+	      "Attribute " + (i+1) + " (" + inputFormat.attribute(i).name() 
+	      + ") skipped.");
+	continue;
+      }
+
+      // process relational attribute
+      prefix    = inputFormat.attribute(i).name() + "_";
+      relFormat = inputFormat.attribute(i).relation();
+      for (n = 0; n < relFormat.numAttributes(); n++) {
+	att = relFormat.attribute(n);
+	
+	if (att.isNumeric()) {
+	  atts.addElement(new Attribute(prefix + att.name() + "_MIN"));
+	  atts.addElement(new Attribute(prefix + att.name() + "_MAX"));
+	  atts.addElement(new Attribute(prefix + att.name() + "_AVG"));
+	  atts.addElement(new Attribute(prefix + att.name() + "_STDEV"));
+	  atts.addElement(new Attribute(prefix + att.name() + "_SUM"));
+	}
+	else if (att.isNominal()) {
+	  if (att.numValues() <= m_MaxCardinality) {
+	    for (m = 0; m < att.numValues(); m++)
+	      atts.addElement(new Attribute(prefix + att.name() + "_" + att.value(m) + "_CNT"));
+	  }
+	  else {
+	    if (getDebug())
+	      System.out.println(
+		  "Attribute " + (i+1) + "/" + (n+1) 
+		  + " (" + inputFormat.attribute(i).name() + "/" + att.name()
+		  + ") skipped, " + att.numValues() + " > " + m_MaxCardinality + ".");
+	  }
+	}
+	else {
+	  if (getDebug())
+	    System.out.println(
+		"Attribute " + (i+1) + "/" + (n+1) 
+		+ " (" + inputFormat.attribute(i).name() + "/" + att.name()
+		+ ") skipped.");
+	}
+      }
+    }
+    
+    // generate new format
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(clsIndex);
+    
+    // neither string nor relational attributes need to be copied to the 
+    // output:
+    initOutputLocators(result, new int[0]);
+    
+    return result;
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    Instances		result;
+    Instance		inst;
+    Instance		newInst;
+    Instances		relInstances;
+    int			k;
+    int			l;
+    int			i;
+    int			n;
+    int			m;
+    AttributeStats	stats;
+    Attribute		att;
+    
+    result = getOutputFormat();
+
+    // initialize attribute statistics
+    m_AttStats.clear();
+
+    // collect data for all relational attributes
+    for (i = 0; i < instances.numAttributes(); i++) {
+      if (i == instances.classIndex())
+	continue;
+
+      if (!instances.attribute(i).isRelationValued())
+	continue;
+
+      if (!m_SelectedRange.isInRange(i))
+	continue;
+
+      // compute statistics
+      for (k = 0; k < instances.numInstances(); k++) {
+	relInstances = instances.instance(k).relationalValue(i);
+
+	for (n = 0; n < relInstances.numAttributes(); n++) {
+	  att   = relInstances.attribute(n);
+	  stats = null;
+
+	  if (    att.isNumeric() 
+	      || (att.isNominal() && att.numValues() <= m_MaxCardinality) ) {
+	    stats = relInstances.attributeStats(n);
+	    m_AttStats.put(k + "-" + i + "-" + n, stats);
+	  }
+	}
+      }
+    }
+    
+    // convert data
+    for (k = 0; k < instances.numInstances(); k++) {
+      inst    = instances.instance(k);
+      newInst = new DenseInstance(result.numAttributes());
+      newInst.setWeight(inst.weight());
+
+      l = 0;
+      for (i = 0; i < instances.numAttributes(); i++) {
+	if (!instances.attribute(i).isRelationValued()) {
+	  newInst.setValue(l, inst.value(i));
+	  l++;
+	}
+	else {
+	  if (!m_SelectedRange.isInRange(i))
+	    continue;
+	  
+	  // replace relational data with statistics
+	  relInstances = inst.relationalValue(i);
+	  for (n = 0; n < relInstances.numAttributes(); n++) {
+	    att   = relInstances.attribute(n);
+	    stats = (AttributeStats) m_AttStats.get(k + "-" + i + "-" + n);
+	    
+	    if (att.isNumeric()) {
+	      newInst.setValue(l, stats.numericStats.min); l++;
+	      newInst.setValue(l, stats.numericStats.max); l++;
+	      newInst.setValue(l, stats.numericStats.mean); l++;
+	      newInst.setValue(l, stats.numericStats.stdDev); l++;
+	      newInst.setValue(l, stats.numericStats.sum); l++;
+	    }
+	    else if (att.isNominal() && att.numValues() <= m_MaxCardinality) {
+	      for (m = 0; m < att.numValues(); m++) {
+		newInst.setValue(l, stats.nominalCounts[m]);
+		l++;
+	      }
+	    }
+	  }
+	}
+      }
+      
+      result.add(newInst);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new RELAGGS(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomProjection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomProjection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomProjection.java	(revision 29)
@@ -0,0 +1,907 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RandomProjection.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Reduces the dimensionality of the data by projecting it onto a lower dimensional subspace using a random matrix with columns of unit length (i.e. It will reduce the number of attributes in the data while preserving much of its variation like PCA, but at a much less computational cost).<br/>
+ * It first applies the  NominalToBinary filter to convert all attributes to numeric before reducing the dimension. It preserves the class attribute.<br/>
+ * <br/>
+ * For more information, see:<br/>
+ * <br/>
+ * Dmitriy Fradkin, David Madigan: Experiments with random projections for machine learning. In: KDD '03: Proceedings of the ninth ACM SIGKDD international conference on Knowledge discovery and data mining, New York, NY, USA, 517-522, 003.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;inproceedings{Fradkin003,
+ *    address = {New York, NY, USA},
+ *    author = {Dmitriy Fradkin and David Madigan},
+ *    booktitle = {KDD '03: Proceedings of the ninth ACM SIGKDD international conference on Knowledge discovery and data mining},
+ *    pages = {517-522},
+ *    publisher = {ACM Press},
+ *    title = {Experiments with random projections for machine learning},
+ *    year = {003}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;number&gt;
+ *  The number of dimensions (attributes) the data should be reduced to
+ *  (default 10; exclusive of the class attribute, if it is set).</pre>
+ * 
+ * <pre> -D [SPARSE1|SPARSE2|GAUSSIAN]
+ *  The distribution to use for calculating the random matrix.
+ *  Sparse1 is:
+ *    sqrt(3)*{-1 with prob(1/6), 0 with prob(2/3), +1 with prob(1/6)}
+ *  Sparse2 is:
+ *    {-1 with prob(1/2), +1 with prob(1/2)}
+ * </pre>
+ * 
+ * <pre> -P &lt;percent&gt;
+ *  The percentage of dimensions (attributes) the data should
+ *  be reduced to (exclusive of the class attribute, if it is set). This -N
+ *  option is ignored if this option is present or is greater
+ *  than zero.</pre>
+ * 
+ * <pre> -M
+ *  Replace missing values using the ReplaceMissingValues filter</pre>
+ * 
+ * <pre> -R &lt;num&gt;
+ *  The random seed for the random number generator used for
+ *  calculating the random matrix (default 42).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $ [1.0 - 22 July 2003 - Initial version (Ashraf M. Kibriya)]
+ */
+public class RandomProjection 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler, TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4428905532728645880L;
+
+  /** Stores the number of dimensions to reduce the data to */
+  protected int m_k = 10;
+
+  /** Stores the dimensionality the data should be reduced to as percentage of the original dimension */
+  protected double m_percent = 0.0;
+
+  /** Is the random matrix will be computed using 
+      Gaussian distribution or not */
+  protected boolean m_useGaussian = false;
+
+  /** distribution type: sparse 1 */
+  public static final int SPARSE1 = 1;
+  /** distribution type: sparse 2 */
+  public static final int SPARSE2 = 2;
+  /** distribution type: gaussian */
+  public static final int GAUSSIAN = 3;
+
+  /** The types of distributions that can be used for 
+  calculating the random matrix */
+  public static final Tag [] TAGS_DSTRS_TYPE = {
+    new Tag(SPARSE1, "Sparse 1"),
+    new Tag(SPARSE2, "Sparse 2"),
+    new Tag(GAUSSIAN, "Gaussian"),
+  };
+
+  /** Stores the distribution to use for calculating the
+      random matrix */
+  protected int m_distribution = SPARSE1;
+ 
+  /** Should the missing values be replaced using 
+      unsupervised.ReplaceMissingValues filter */
+  protected boolean m_useReplaceMissing = false;
+
+  /** Keeps track of output format if it is defined or not */
+  protected boolean m_OutputFormatDefined = false;
+
+  /** The NominalToBinary filter applied to the data before this filter */
+  protected Filter m_ntob; // = new weka.filters.unsupervised.attribute.NominalToBinary();
+
+  /** The ReplaceMissingValues filter */
+  protected Filter m_replaceMissing;
+    
+  /** Stores the random seed used to generate the random matrix */
+  protected long m_rndmSeed = 42;
+
+  /** The random matrix */
+  protected double m_rmatrix[][];
+
+  /** The random number generator used for generating the random matrix */
+  protected Random m_random;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	      "\tThe number of dimensions (attributes) the data should be reduced to\n"
+             +"\t(default 10; exclusive of the class attribute, if it is set).",
+	      "N", 1, "-N <number>"));
+
+    newVector.addElement(new Option(
+	      "\tThe distribution to use for calculating the random matrix.\n"
+	     +"\tSparse1 is:\n"
+	     +"\t  sqrt(3)*{-1 with prob(1/6), 0 with prob(2/3), +1 with prob(1/6)}\n"
+	     +"\tSparse2 is:\n"
+	     +"\t  {-1 with prob(1/2), +1 with prob(1/2)}\n",
+	      "D", 1, "-D [SPARSE1|SPARSE2|GAUSSIAN]"));
+
+    //newVector.addElement(new Option(
+    //	      "\tUse Gaussian distribution for calculating the random matrix.",
+    //	      "G", 0, "-G"));
+
+    newVector.addElement(new Option(
+	      "\tThe percentage of dimensions (attributes) the data should\n"
+	     +"\tbe reduced to (exclusive of the class attribute, if it is set). This -N\n"
+	     +"\toption is ignored if this option is present or is greater\n"
+	     +"\tthan zero.",
+	      "P", 1, "-P <percent>"));
+
+    newVector.addElement(new Option(
+	      "\tReplace missing values using the ReplaceMissingValues filter",
+	      "M", 0, "-M"));
+
+    newVector.addElement(new Option(
+	      "\tThe random seed for the random number generator used for\n"
+	     +"\tcalculating the random matrix (default 42).",
+	      "R", 0, "-R <num>"));
+ 
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;number&gt;
+   *  The number of dimensions (attributes) the data should be reduced to
+   *  (default 10; exclusive of the class attribute, if it is set).</pre>
+   * 
+   * <pre> -D [SPARSE1|SPARSE2|GAUSSIAN]
+   *  The distribution to use for calculating the random matrix.
+   *  Sparse1 is:
+   *    sqrt(3)*{-1 with prob(1/6), 0 with prob(2/3), +1 with prob(1/6)}
+   *  Sparse2 is:
+   *    {-1 with prob(1/2), +1 with prob(1/2)}
+   * </pre>
+   * 
+   * <pre> -P &lt;percent&gt;
+   *  The percentage of dimensions (attributes) the data should
+   *  be reduced to (exclusive of the class attribute, if it is set). This -N
+   *  option is ignored if this option is present or is greater
+   *  than zero.</pre>
+   * 
+   * <pre> -M
+   *  Replace missing values using the ReplaceMissingValues filter</pre>
+   * 
+   * <pre> -R &lt;num&gt;
+   *  The random seed for the random number generator used for
+   *  calculating the random matrix (default 42).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+
+    String mString = Utils.getOption('P', options);
+    if (mString.length() != 0) {
+	setPercent((double) Double.parseDouble(mString)); //setNumberOfAttributes((int) Integer.parseInt(mString));
+    } else {
+        setPercent(0);
+	mString = Utils.getOption('N', options);
+	if (mString.length() != 0) 
+	    setNumberOfAttributes(Integer.parseInt(mString));	    
+	else	    
+	    setNumberOfAttributes(10);
+    }    
+    
+    mString = Utils.getOption('R', options);
+    if(mString.length()!=0) {
+	setRandomSeed( Long.parseLong(mString) );
+    }
+
+    mString = Utils.getOption('D', options);
+    if(mString.length()!=0) {
+	if(mString.equalsIgnoreCase("sparse1"))
+	   setDistribution( new SelectedTag(SPARSE1, TAGS_DSTRS_TYPE) );
+	else if(mString.equalsIgnoreCase("sparse2"))
+	   setDistribution( new SelectedTag(SPARSE2, TAGS_DSTRS_TYPE) );
+	else if(mString.equalsIgnoreCase("gaussian"))
+	   setDistribution( new SelectedTag(GAUSSIAN, TAGS_DSTRS_TYPE) );	   
+    }
+
+    if(Utils.getFlag('M', options))
+	setReplaceMissingValues(true);
+    else
+	setReplaceMissingValues(false);
+
+
+   //if(Utils.getFlag('G', options))
+   //    setUseGaussian(true);
+   //else
+   //    setUseGaussian(false);
+    
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [10];
+    int current = 0;
+
+    //if (getUseGaussian()) {
+    //  options[current++] = "-G";
+    //}
+
+    if (getReplaceMissingValues()) {
+      options[current++] = "-M";
+    }
+
+    if (getPercent() == 0) {
+      options[current++] = "-N";
+      options[current++] = "" + getNumberOfAttributes();
+    }
+    else {
+      options[current++] = "-P";
+      options[current++] = "" + getPercent();
+    }
+    
+    options[current++] = "-R";
+    options[current++] = "" + getRandomSeed();
+    
+    SelectedTag t = getDistribution();
+    options[current++] = "-D";
+    options[current++] = ""+t.getSelectedTag().getReadable();
+
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+
+    return options;
+  }
+    
+   
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Reduces the dimensionality of the data by projecting"
+	 + " it onto a lower dimensional subspace using a random"
+	 + " matrix with columns of unit length (i.e. It will reduce"
+	 + " the number of attributes in the data while preserving"
+	 + " much of its variation like PCA, but at a much less"
+	 + " computational cost).\n"
+	 + "It first applies the  NominalToBinary filter to" 
+	 + " convert all attributes to numeric before reducing the"
+	 + " dimension. It preserves the class attribute.\n\n"
+	 + "For more information, see:\n\n"
+	 + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Dmitriy Fradkin and David Madigan");
+    result.setValue(Field.TITLE, "Experiments with random projections for machine learning");
+    result.setValue(Field.BOOKTITLE, "KDD '03: Proceedings of the ninth ACM SIGKDD international conference on Knowledge discovery and data mining");
+    result.setValue(Field.YEAR, "003");
+    result.setValue(Field.PAGES, "517-522");
+    result.setValue(Field.PUBLISHER, "ACM Press");
+    result.setValue(Field.ADDRESS, "New York, NY, USA");
+    
+    return result;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numberOfAttributesTipText() {
+
+    return "The number of dimensions (attributes) the data should"
+         + " be reduced to.";
+  }
+
+  /** 
+   * Sets the number of attributes (dimensions) the data should be reduced to
+   * 
+   * @param newAttNum the goal for the dimensions
+   */
+  public void setNumberOfAttributes(int newAttNum) {
+      m_k = newAttNum;
+  }
+  
+  /** 
+   * Gets the current number of attributes (dimensionality) to which the data 
+   * will be reduced to.
+   *  
+   * @return the number of dimensions
+   */
+  public int getNumberOfAttributes() {
+      return m_k;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String percentTipText() {
+
+      return  " The percentage of dimensions (attributes) the data should"
+            + " be reduced to  (inclusive of the class attribute). This "
+	    + " NumberOfAttributes option is ignored if this option is"
+	    + " present or is greater than zero.";
+  }
+
+  /** 
+   * Sets the percent the attributes (dimensions) of the data should be reduced to
+   * 
+   * @param newPercent the percentage of attributes
+   */
+  public void setPercent(double newPercent) {
+      if(newPercent > 0)
+	  newPercent /= 100;
+      m_percent = newPercent;
+  }
+
+  /** 
+   * Gets the percent the attributes (dimensions) of the data will be reduced to
+   * 
+   * @return the percentage of attributes
+   */
+  public double getPercent() {
+      return m_percent * 100;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+      return  "The random seed used by the random"
+	     +" number generator used for generating"
+	     +" the random matrix ";
+  }
+
+  /** 
+   * Sets the random seed of the random number generator
+   * 
+   * @param seed the random seed value
+   */
+  public void setRandomSeed(long seed) {
+      m_rndmSeed = seed;
+  }
+
+  /** 
+   * Gets the random seed of the random number generator
+   * 
+   * @return the random seed value
+   */
+  public long getRandomSeed() {
+      return m_rndmSeed;
+  }
+
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String  distributionTipText() {
+      return "The distribution to use for calculating the random matrix.\n"
+	    +"Sparse1 is:\n"
+	    +" sqrt(3) * { -1 with prob(1/6), \n"
+	    +"               0 with prob(2/3),  \n"
+            +"              +1 with prob(1/6) } \n"
+	    +"Sparse2 is:\n"
+	    +" { -1 with prob(1/2), \n"
+	    +"   +1 with prob(1/2) } ";
+      
+  }
+  /** 
+   * Sets the distribution to use for calculating the random matrix
+   * 
+   * @param newDstr the distribution to use
+   */
+  public void setDistribution(SelectedTag newDstr) {
+
+      if (newDstr.getTags() == TAGS_DSTRS_TYPE) {
+	  m_distribution = newDstr.getSelectedTag().getID();
+      }
+  }
+
+  /** 
+   * Returns the current distribution that'll be used for calculating the 
+   * random matrix
+   * 
+   * @return the current distribution
+   */
+  public SelectedTag getDistribution() {
+      return new SelectedTag(m_distribution, TAGS_DSTRS_TYPE);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String replaceMissingValuesTipText() {
+
+    return "If set the filter uses weka.filters.unsupervised.attribute.ReplaceMissingValues"
+	 + " to replace the missing values";
+  }
+
+  /** 
+   * Sets either to use replace missing values filter or not
+   * 
+   * @param t if true then the replace missing values is used
+   */
+  public void setReplaceMissingValues(boolean t) {
+      m_useReplaceMissing = t;
+  }
+
+  /** 
+   * Gets the current setting for using ReplaceMissingValues filter
+   * 
+   * @return true if the replace missing values filter is used
+   */
+  public boolean getReplaceMissingValues() {
+      return m_useReplaceMissing;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {      
+    super.setInputFormat(instanceInfo);
+    /*
+    if (instanceInfo.classIndex() < 0) {
+      throw new UnassignedClassException("No class has been assigned to the instances");
+    }
+    */
+    
+    for(int i=0; i<instanceInfo.numAttributes(); i++) {        
+	if( i!=instanceInfo.classIndex() && instanceInfo.attribute(i).isNominal() ) {
+            if(instanceInfo.classIndex()>=0)
+                m_ntob = new weka.filters.supervised.attribute.NominalToBinary();
+            else
+                m_ntob = new weka.filters.unsupervised.attribute.NominalToBinary();
+            
+            break;
+	}
+    }
+
+    //r.setSeed(m_rndmSeed); //in case the setRandomSeed() is not
+                           //called we better set the seed to its 
+                           //default value of 42.
+    boolean temp=true;
+    if(m_replaceMissing!=null) {
+	m_replaceMissing = new weka.filters.unsupervised.attribute.ReplaceMissingValues();
+	if(m_replaceMissing.setInputFormat(instanceInfo))
+	    temp=true;
+	else
+	    temp=false;
+    }
+    
+    if(m_ntob!=null) {
+	if(m_ntob.setInputFormat(instanceInfo)) {
+	    setOutputFormat();
+	    return temp && true;
+	}
+	else { 
+	    return false;
+	}
+    }
+    else {
+	setOutputFormat();
+	return temp && true;
+    }
+  }
+
+   
+  /**
+   * Input an instance for filtering.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    Instance newInstance=null;
+
+    if (getInputFormat()==null) {
+	throw new IllegalStateException("No input instance format defined");
+    }
+    if(m_NewBatch) {
+      resetQueue();
+      //if(ntob!=null) 
+      //	  ntob.m_NewBatch=true;
+      m_NewBatch = false;
+    }
+    
+    boolean replaceDone=false;
+    if(m_replaceMissing!=null) {
+	if(m_replaceMissing.input(instance)) {
+	    if(m_OutputFormatDefined == false)
+		setOutputFormat();
+	    newInstance = m_replaceMissing.output();
+	    replaceDone = true;
+	}
+	else
+	    return false;;
+    }
+
+    if(m_ntob!=null) {
+	if(replaceDone==false)
+	    newInstance = instance;
+	if(m_ntob.input(newInstance)) {
+	    if(m_OutputFormatDefined == false) 
+		setOutputFormat();
+	    newInstance = m_ntob.output();
+	    newInstance = convertInstance(newInstance);
+	    push(newInstance);
+	    return true;	
+	}
+	else {
+	    return false;
+	}
+    }
+    else {
+	if(replaceDone==false)
+	    newInstance = instance;
+	newInstance = convertInstance(newInstance);
+	push(newInstance);
+	return true;
+    }
+  }
+
+
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws NullPointerException if no input structure has been defined,
+   * @throws Exception if there was a problem finishing the batch.
+   */
+  public boolean batchFinished() throws Exception {
+      if (getInputFormat() == null) {
+	  throw new NullPointerException("No input instance format defined");
+      }
+      
+      boolean conversionDone=false;
+      if(m_replaceMissing!=null) {
+	  if(m_replaceMissing.batchFinished()) {
+	      Instance newInstance, instance;
+	      
+	      while((instance=m_replaceMissing.output())!=null) {
+		  if(!m_OutputFormatDefined)
+		      setOutputFormat();
+		  if(m_ntob!=null) {
+		      m_ntob.input(instance);
+		  }
+		  else {
+		      newInstance = convertInstance(instance);
+		      push(newInstance);
+		  }
+	      }
+
+	      if(m_ntob!=null) {
+		  if(m_ntob.batchFinished()) {
+		      //Instance newInstance, instance;
+		      while((instance=m_ntob.output())!=null) {
+			  if(!m_OutputFormatDefined)
+			      setOutputFormat();
+			  newInstance = convertInstance(instance);
+			  push(newInstance);
+		      }
+		      m_ntob = null;		      
+		  }
+	      }
+	      m_replaceMissing = null;
+	      conversionDone=true;
+	  }
+      }
+
+      if(conversionDone==false && m_ntob!=null) {
+	  if(m_ntob.batchFinished()) {
+	      Instance newInstance, instance;
+	      while((instance=m_ntob.output())!=null) {
+		  if(!m_OutputFormatDefined)
+		      setOutputFormat();
+		  newInstance = convertInstance(instance);
+		  push(newInstance);
+	      }
+	      m_ntob = null;
+	  }
+      }
+      m_OutputFormatDefined=false;
+      return super.batchFinished();
+  }
+    
+
+  /** Sets the output format */  
+  protected void setOutputFormat() {
+      Instances currentFormat;
+      if(m_ntob!=null) {
+	  currentFormat = m_ntob.getOutputFormat();
+      }
+      else 
+	  currentFormat = getInputFormat();
+      
+      if(m_percent>0)
+	  { m_k = (int) ((getInputFormat().numAttributes()-1)*m_percent); 
+	  // System.out.print("numAtts: "+currentFormat.numAttributes());
+	  // System.out.print("percent: "+m_percent);
+	  // System.out.print("percent*numAtts: "+(currentFormat.numAttributes()*m_percent));
+	  // System.out.println("m_k: "+m_k);
+	  }
+
+      Instances newFormat;
+      int newClassIndex=-1;
+      FastVector attributes = new FastVector();
+      for(int i=0; i<m_k; i++) {
+	  attributes.addElement( new Attribute("K"+(i+1)) );
+      }
+      if(currentFormat.classIndex()!=-1)  {  //if classindex is set
+	  //attributes.removeElementAt(attributes.size()-1);
+	  attributes.addElement(currentFormat.attribute(currentFormat.classIndex()));
+	  newClassIndex = attributes.size()-1;
+      }
+
+      newFormat = new Instances(currentFormat.relationName(), attributes, 0);
+      if(newClassIndex!=-1)
+	  newFormat.setClassIndex(newClassIndex);
+      m_OutputFormatDefined=true;
+
+      m_random = new Random();
+      m_random.setSeed(m_rndmSeed);
+
+      m_rmatrix = new double[m_k][currentFormat.numAttributes()];
+      if(m_distribution==GAUSSIAN) {
+	  for(int i=0; i<m_rmatrix.length; i++) 
+	      for(int j=0; j<m_rmatrix[i].length; j++) 
+		  m_rmatrix[i][j] = m_random.nextGaussian();
+      }
+      else {
+	  boolean useDstrWithZero = (m_distribution==SPARSE1);
+	  for(int i=0; i<m_rmatrix.length; i++) 
+	      for(int j=0; j<m_rmatrix[i].length; j++) 
+		  m_rmatrix[i][j] = rndmNum(useDstrWithZero);
+      }
+
+      setOutputFormat(newFormat);
+  }
+
+  /**
+   * converts a single instance to the required format
+   *
+   * @param currentInstance     the instance to convert
+   * @return                    the converted instance
+   */
+  protected Instance convertInstance(Instance currentInstance) {
+
+      Instance newInstance;
+      double vals[] = new double[getOutputFormat().numAttributes()];
+      int classIndex = (m_ntob==null) ? getInputFormat().classIndex():m_ntob.getOutputFormat().classIndex();
+
+      for(int i = 0; i < m_k; i++) {
+        vals[i] = computeRandomProjection(i,classIndex,currentInstance);
+      }
+      if (classIndex != -1) {
+        vals[m_k] = currentInstance.value(classIndex);
+      }
+
+      newInstance = new DenseInstance(currentInstance.weight(), vals);
+      newInstance.setDataset(getOutputFormat());
+
+      return newInstance;
+  }
+
+
+  /**
+   * computes one random projection for a given instance (skip missing values)
+   *
+   * @param rpIndex     offset the new random projection attribute
+   * @param classIndex  classIndex of the input instance
+   * @param instance    the instance to convert
+   * @return    the random sum
+   */
+
+  protected double computeRandomProjection(int rpIndex, int classIndex, Instance instance) {
+
+    double sum = 0.0;
+    for(int i = 0; i < instance.numValues(); i++) {
+      int index = instance.index(i);
+      if (index != classIndex) {
+        double value = instance.valueSparse(i);
+        if (!Utils.isMissingValue(value)) {
+          sum += m_rmatrix[rpIndex][index] * value;
+        }
+      }
+    }
+    return sum;
+  }
+
+  private static final int weights[] = {1, 1, 4};
+  private static final int vals[] = {-1, 1, 0};
+  private static final int weights2[] = {1, 1};
+  private static final int vals2[] = {-1, 1};
+  private static final double sqrt3 = Math.sqrt(3);
+
+  /**
+   * returns a double x such that <br/>
+   *      x = sqrt(3) * { -1 with prob. 1/6, 0 with prob. 2/3, 1 with prob. 1/6 }
+   *      
+   * @param useDstrWithZero
+   * @return the generated number
+   */
+  protected double rndmNum(boolean useDstrWithZero) {
+      if(useDstrWithZero)
+	  return sqrt3 * vals[weightedDistribution(weights)];
+      else
+	  return vals2[weightedDistribution(weights2)];
+  }
+
+  /** 
+   * Calculates a weighted distribution
+   * 
+   * @param weights the weights to use
+   * @return
+   */
+  protected int weightedDistribution(int [] weights) {
+      int sum=0; 
+      
+      for(int i=0; i<weights.length; i++) 
+	  sum += weights[i];
+      
+      int val = (int)Math.floor(m_random.nextDouble()*sum);
+      
+      for(int i=0; i<weights.length; i++) {
+	  val -= weights[i];
+	  if(val<0)
+	      return i;
+      }
+      return -1;
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RandomProjection(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomSubset.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomSubset.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RandomSubset.java	(revision 29)
@@ -0,0 +1,381 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RandomSubset.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleStreamFilter;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Chooses a random subset of attributes, either an absolute number or a percentage. The class is always included in the output (as the last attribute).
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -N &lt;double&gt;
+ *  The number of attributes to randomly select.
+ *  If &lt; 1 then percentage, &gt;= 1 absolute number.
+ *  (default: 0.5)</pre>
+ * 
+ * <pre> -S &lt;int&gt;
+ *  The seed value.
+ *  (default: 1)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class RandomSubset
+  extends SimpleStreamFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 2911221724251628050L;
+
+  /** The number of attributes to randomly choose (&gt;= 1 absolute number of
+   * attributes, &lt; 1 percentage). */
+  protected double m_NumAttributes = 0.5;
+  
+  /** The seed value. */
+  protected int m_Seed = 1;
+  
+  /** The indices of the attributes that got selected. */
+  protected int[] m_Indices = null;
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Chooses a random subset of attributes, either an absolute number "
+      + "or a percentage. The class is always included in the output ("
+      + "as the last attribute).";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        result;
+    Enumeration   enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe number of attributes to randomly select.\n"
+	+ "\tIf < 1 then percentage, >= 1 absolute number.\n"
+	+ "\t(default: 0.5)",
+	"N", 1, "-N <double>"));
+    
+    result.addElement(new Option(
+	"\tThe seed value.\n"
+	+ "\t(default: 1)",
+	"S", 1, "-S <int>"));
+
+    return result.elements();
+  }	  
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int			i;
+    Vector<String>	result;
+    String[]		options;
+
+    result  = new Vector<String>();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-N"); 
+    result.add("" + m_NumAttributes);
+
+    result.add("-S"); 
+    result.add("" + m_Seed);
+
+    return result.toArray(new String[result.size()]);	  
+  }	  
+
+  /**
+   * Parses a given list of options. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -N &lt;double&gt;
+   *  The number of attributes to randomly select.
+   *  If &lt; 1 then percentage, &gt;= 1 absolute number.
+   *  (default: 0.5)</pre>
+   * 
+   * <pre> -S &lt;int&gt;
+   *  The seed value.
+   *  (default: 1)</pre>
+   * 
+   <!-- options-end -->
+   * 
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported 
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption("N", options);
+    if (tmpStr.length() != 0)
+      setNumAttributes(Double.parseDouble(tmpStr));
+    else
+      setNumAttributes(0.5);
+    
+    tmpStr = Utils.getOption("S", options);
+    if (tmpStr.length() != 0)
+      setSeed(Integer.parseInt(tmpStr));
+    else
+      setSeed(1);
+    
+    super.setOptions(options);
+  }	  
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String numAttributesTipText() {
+    return "The number of attributes to choose: < 1 percentage, >= 1 absolute number.";
+  }
+
+  /**
+   * Get the number of attributes (&lt; 1 percentage, &gt;= 1 absolute number).
+   *
+   * @return 		the number of attributes.
+   */
+  public double getNumAttributes() {
+    return m_NumAttributes;
+  }
+
+  /**
+   * Set the number of attributes. 
+   *
+   * @param value	the number of attributes to use.
+   */
+  public void setNumAttributes(double value) {
+    m_NumAttributes = value;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+    return "The seed value for the random number generator.";
+  }
+
+  /**
+   * Get the seed value for the random number generator.
+   *
+   * @return 		the seed value.
+   */
+  public int getSeed() {
+    return m_Seed;
+  }
+
+  /**
+   * Set the seed value for the random number generator. 
+   *
+   * @param value	the seed value.
+   */
+  public void setSeed(int value) {
+    m_Seed = value;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * hasImmediateOutputFormat() returns false, then this method will called
+   * from batchFinished() after the call of preprocess(Instances), in which,
+   * e.g., statistics for the actual processing step can be gathered.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Instances		result;
+    FastVector		atts;
+    int			i;
+    int			numAtts;
+    Vector<Integer>	indices;
+    Vector<Integer>	subset;
+    Random		rand;
+    int			index;
+ 
+    // determine the number of attributes
+    numAtts = inputFormat.numAttributes();
+    if (inputFormat.classIndex() > -1)
+      numAtts--;
+    
+    if (m_NumAttributes < 1) {
+      numAtts = (int) Math.round((double) numAtts * m_NumAttributes);
+    }
+    else {
+      if (m_NumAttributes < numAtts)
+	numAtts = (int) m_NumAttributes;
+    }
+    if (getDebug())
+      System.out.println("# of atts: " + numAtts);
+    
+    // determine random indices
+    indices = new Vector<Integer>();
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      if (i == inputFormat.classIndex())
+	continue;
+      indices.add(i);
+    }
+    
+    subset = new Vector<Integer>();
+    rand   = new Random(m_Seed);
+    for (i = 0; i < numAtts; i++) {
+      index = rand.nextInt(indices.size());
+      subset.add(indices.get(index));
+      indices.remove(index);
+    }
+    Collections.sort(subset);
+    if (inputFormat.classIndex() > -1)
+      subset.add(inputFormat.classIndex());
+    if (getDebug())
+      System.out.println("indices: " + subset);
+    
+    // generate output format
+    atts      = new FastVector();
+    m_Indices = new int[subset.size()];
+    for (i = 0; i < subset.size(); i++) {
+      atts.addElement(inputFormat.attribute(subset.get(i)));
+      m_Indices[i] = subset.get(i);
+    }
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    if (inputFormat.classIndex() > -1)
+      result.setClassIndex(result.numAttributes() - 1);
+
+    return result;
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    Instance	result;
+    double[]	values;
+    int		i;
+    
+    values = new double[m_Indices.length];
+    for (i = 0; i < m_Indices.length; i++)
+      values[i] = instance.value(m_Indices[i]);
+
+    result = new DenseInstance(instance.weight(), values);
+    result.setDataset(getOutputFormat());
+    
+    copyValues(result, false, instance.dataset(), getOutputFormat());
+    result.setDataset(getOutputFormat());
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Runs the filter with the given parameters. Use -h to list options.
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    runFilter(new RandomSubset(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Remove.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Remove.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Remove.java	(revision 29)
@@ -0,0 +1,378 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Remove.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An filter that removes a range of attributes from the dataset.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to delete. First and last are valid
+ *  indexes. (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. only keep specified columns)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6128 $
+ */
+public class Remove 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 5011337331921522847L;
+  
+  /** Stores which columns to select as a funky range */
+  protected Range m_SelectCols = new Range();
+
+  /**
+   * Stores the indexes of the selected attributes in order, once the
+   * dataset is seen
+   */
+  protected int [] m_SelectedAttributes;
+
+  /**
+   * Constructor so that we can initialize the Range variable properly.
+   */
+  public Remove() {
+	
+    m_SelectCols.setInvert(true);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to delete. First and last are valid\n"
+	      +"\tindexes. (default none)",
+              "R", 1, "-R <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tInvert matching sense (i.e. only keep specified columns)",
+              "V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to delete. First and last are valid
+   *  indexes. (default none)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense (i.e. only keep specified columns)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String deleteList = Utils.getOption('R', options);
+    if (deleteList.length() != 0) {
+      setAttributeIndices(deleteList);
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+    
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [3];
+    int current = 0;
+
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; options[current++] = getAttributeIndices();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the format couldn't be set successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    
+    m_SelectCols.setUpper(instanceInfo.numAttributes() - 1);
+
+    // Create the output buffer
+    FastVector attributes = new FastVector();
+    int outputClass = -1;
+    m_SelectedAttributes = m_SelectCols.getSelection();
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      if (instanceInfo.classIndex() == current) {
+	outputClass = attributes.size();
+      }
+      Attribute keep = (Attribute)instanceInfo.attribute(current).copy();
+      attributes.addElement(keep);
+    }
+    initInputLocators(instanceInfo, m_SelectedAttributes);
+    Instances outputFormat = new Instances(instanceInfo.relationName(),
+					   attributes, 0); 
+    outputFormat.setClassIndex(outputClass);
+    setOutputFormat(outputFormat);
+    return true;
+  }
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    if (getOutputFormat().numAttributes() == 0) {
+      return false;
+    }
+    double [] vals = new double[getOutputFormat().numAttributes()];
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      vals[i] = instance.value(current);
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new SparseInstance(instance.weight(), vals);
+    } else {
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    push(inst);
+    return true;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A filter that removes a range of"
+      + " attributes from the dataset.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Determines whether action is to select or delete."
+      + " If set to true, only the specified attributes will be kept;"
+      + " If set to false, specified attributes will be deleted.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return !m_SelectCols.getInvert();
+  }
+
+  /**
+   * Set whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_SelectCols.setInvert(!invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Get the current range selection.
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+
+    return m_SelectCols.getRanges();
+  }
+
+  /**
+   * Set which attributes are to be deleted (or kept if invert is true)
+   *
+   * @param rangeList a string representing the list of attributes.  Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   */
+  public void setAttributeIndices(String rangeList) {
+
+    m_SelectCols.setRanges(rangeList);
+  }
+
+  /**
+   * Set which attributes are to be deleted (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   */
+  public void setAttributeIndicesArray(int [] attributes) {
+    
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6128 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Remove(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveByName.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveByName.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveByName.java	(revision 29)
@@ -0,0 +1,323 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * RemoveByName.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.SimpleStreamFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Removes attributes based on a regular expression matched against their names.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -E &lt;regular expression&gt;
+ *  The regular expression to match the attribute names against.
+ *  (default: ^.*id$)</pre>
+ * 
+ * <pre> -V
+ *  Flag for inverting the matching sense. If set, attributes are kept
+ *  instead of deleted.
+ *  (default: off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6076 $
+ */
+public class RemoveByName
+  extends SimpleStreamFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -3335106965521265631L;
+
+  /** the default expression. */
+  public final static String DEFAULT_EXPRESSION = "^.*id$";
+  
+  /** the regular expression for selecting the attributes by name. */
+  protected String m_Expression = DEFAULT_EXPRESSION;
+  
+  /** whether to invert the matching sense. */
+  protected boolean m_InvertSelection;
+
+  /** the Remove filter used internally for removing the attributes. */
+  protected Remove m_Remove;
+  
+  /**
+   * Returns a string describing this classifier.
+   *
+   * @return      a description of the classifier suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Removes attributes based on a regular expression matched against "
+      + "their names.";
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    Enumeration	enm;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    result.addElement(new Option(
+	"\tThe regular expression to match the attribute names against.\n"
+	+ "\t(default: " + DEFAULT_EXPRESSION + ")",
+	"E", 1, "-E <regular expression>"));
+
+    result.addElement(new Option(
+	"\tFlag for inverting the matching sense. If set, attributes are kept\n"
+	+ "\tinstead of deleted.\n"
+	+ "\t(default: off)",
+	"V", 0, "-V"));
+
+    return result.elements();
+  }
+
+  /**
+   * returns the options of the current setup.
+   *
+   * @return      the current options
+   */
+  public String[] getOptions() {
+    int       		i;
+    Vector<String>	result;
+    String[]		options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-E");
+    result.add("" + getExpression());
+
+    if (getInvertSelection())
+      result.add("-V");
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -E &lt;regular expression&gt;
+   *  The regular expression to match the attribute names against.
+   *  (default: ^.*id$)</pre>
+   * 
+   * <pre> -V
+   *  Flag for inverting the matching sense. If set, attributes are kept
+   *  instead of deleted.
+   *  (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if the option setting fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("E", options);
+    if (tmpStr.length() != 0)
+      setExpression(tmpStr);
+    else
+      setExpression(DEFAULT_EXPRESSION);
+
+    setInvertSelection(Utils.getFlag("V", options));
+  }
+
+  /**
+   * Sets the regular expression to match the attribute names against.
+   *
+   * @param value 	the regular expression
+   */
+  public void setExpression(String value) {
+    m_Expression = value;
+  }
+
+  /**
+   * Returns the regular expression in use.
+   *
+   * @return 		the regular expression
+   */
+  public String getExpression() {
+    return m_Expression;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String expressionTipText() {
+    return "The regular expression to match the attribute names against.";
+  }
+
+  /**
+   * Set whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param value 	the new invert setting
+   */
+  public void setInvertSelection(boolean value) {
+    m_InvertSelection = value;
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept.
+   *
+   * @return 		true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+    return m_InvertSelection;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Determines whether action is to select or delete."
+      + " If set to true, only the specified attributes will be kept;"
+      + " If set to false, specified attributes will be deleted.";
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat	the input format to base the output format on
+   * @return		the output format
+   * @throws Exception	in case the determination goes wrong
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Vector<Integer>	indices;
+    int[]		attributes;
+    int			i;
+    
+    // determine indices
+    indices = new Vector<Integer>();
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      // skip class
+      if (i == inputFormat.classIndex())
+	continue;
+      if (inputFormat.attribute(i).name().matches(m_Expression))
+	indices.add(i);
+    }
+    attributes = new int[indices.size()];
+    for (i = 0; i < indices.size(); i++)
+      attributes[i] = indices.get(i);
+    
+    m_Remove = new Remove();
+    m_Remove.setAttributeIndicesArray(attributes);
+    m_Remove.setInvertSelection(getInvertSelection());
+    m_Remove.setInputFormat(inputFormat);
+    
+    return m_Remove.getOutputFormat();
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result;
+    
+    result = new Remove().getCapabilities();
+    result.setOwner(this);
+    
+    return result;
+  }
+  
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    m_Remove.input(instance);
+    return m_Remove.output();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6076 $");
+  }
+
+  /**
+   * runs the filter with the given arguments.
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new RemoveByName(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveType.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveType.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveType.java	(revision 29)
@@ -0,0 +1,422 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveType.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Removes attributes of a given type.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -T &lt;nominal|numeric|string|date|relational&gt;
+ *  Attribute type to delete. Valid options are "nominal", 
+ *  "numeric", "string", "date" and "relational".
+ *  (default "string")</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. only keep specified columns)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class RemoveType 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3563999462782486279L;
+  
+  /** The attribute filter used to do the filtering */
+  protected Remove m_attributeFilter = new Remove();
+
+  /** The type of attribute to delete */
+  protected int m_attTypeToDelete = Attribute.STRING;
+
+  /** Whether to invert selection */
+  protected boolean m_invert = false;
+  
+  /** Tag allowing selection of attribute type to delete */
+  public static final Tag [] TAGS_ATTRIBUTETYPE = {
+    new Tag(Attribute.NOMINAL, "Delete nominal attributes"),
+    new Tag(Attribute.NUMERIC, "Delete numeric attributes"),
+    new Tag(Attribute.STRING, "Delete string attributes"),
+    new Tag(Attribute.DATE, "Delete date attributes"),
+    new Tag(Attribute.RELATIONAL, "Delete relational attributes")
+  };
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.RELATIONAL_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */ 
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    
+    super.setInputFormat(instanceInfo);
+
+    int[] attsToDelete = new int[instanceInfo.numAttributes()];
+    int numToDelete = 0;
+    for (int i=0; i<instanceInfo.numAttributes(); i++) {
+      if ((i == instanceInfo.classIndex() && !m_invert)) {
+	continue; // skip class
+      }
+      if (instanceInfo.attribute(i).type() == m_attTypeToDelete)
+	attsToDelete[numToDelete++] = i;
+    }
+
+    int[] finalAttsToDelete = new int[numToDelete];
+    System.arraycopy(attsToDelete, 0, finalAttsToDelete, 0, numToDelete);
+    
+    m_attributeFilter.setAttributeIndicesArray(finalAttsToDelete);
+    m_attributeFilter.setInvertSelection(m_invert);
+    
+    boolean result = m_attributeFilter.setInputFormat(instanceInfo);
+    Instances afOutputFormat = m_attributeFilter.getOutputFormat();
+    
+    // restore old relation name to hide attribute filter stamp
+    afOutputFormat.setRelationName(instanceInfo.relationName());
+
+    setOutputFormat(afOutputFormat);
+    return result;
+  }
+
+  /**
+   * Input an instance for filtering.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   */
+  public boolean input(Instance instance) {
+    
+    return m_attributeFilter.input(instance);
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws Exception if something goes wrong
+   */  
+  public boolean batchFinished() throws Exception {
+
+    return m_attributeFilter.batchFinished();
+  }
+
+  /**
+   * Output an instance after filtering and remove from the output queue.
+   *
+   * @return the instance that has most recently been filtered (or null if
+   * the queue is empty).
+   */
+  public Instance output() {
+
+    return m_attributeFilter.output();
+  }
+
+  /**
+   * Output an instance after filtering but do not remove from the
+   * output queue.
+   *
+   * @return the instance that has most recently been filtered (or null if
+   * the queue is empty).
+   */
+  public Instance outputPeek() {
+
+    return m_attributeFilter.outputPeek();
+  }
+
+  /**
+   * Returns the number of instances pending output
+   *
+   * @return the number of instances  pending output
+   */  
+  public int numPendingOutput() {
+  
+    return m_attributeFilter.numPendingOutput();
+  }
+  
+  /**
+   * Returns whether the output format is ready to be collected
+   *
+   * @return true if the output format is set
+   */  
+  public boolean isOutputFormatDefined() {
+
+    return m_attributeFilter.isOutputFormatDefined();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+	"\tAttribute type to delete. Valid options are \"nominal\", \n"
+	+ "\t\"numeric\", \"string\", \"date\" and \"relational\".\n"
+	+ "\t(default \"string\")",
+	"T", 1, "-T <nominal|numeric|string|date|relational>"));
+
+    newVector.addElement(new Option(
+	"\tInvert matching sense (i.e. only keep specified columns)",
+	"V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -T &lt;nominal|numeric|string|date|relational&gt;
+   *  Attribute type to delete. Valid options are "nominal", 
+   *  "numeric", "string", "date" and "relational".
+   *  (default "string")</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense (i.e. only keep specified columns)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String tString = Utils.getOption('T', options);
+    if (tString.length() != 0) setAttributeTypeString(tString);
+    setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [3];
+    int current = 0;
+
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    options[current++] = "-T";
+    options[current++] = getAttributeTypeString();
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Removes attributes of a given type.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeTypeTipText() {
+
+    return "The type of attribute to remove.";
+  }
+
+  /**
+   * Sets the attribute type to be deleted by the filter.
+   *
+   * @param type a TAGS_ATTRIBUTETYPE of the new type the filter should delete
+   */
+  public void setAttributeType(SelectedTag type) {
+    
+    if (type.getTags() == TAGS_ATTRIBUTETYPE) {
+      m_attTypeToDelete = type.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the attribute type to be deleted by the filter.
+   *
+   * @return the attribute type as a selected tag TAGS_ATTRIBUTETYPE
+   */
+  public SelectedTag getAttributeType() {
+
+    return new SelectedTag(m_attTypeToDelete, TAGS_ATTRIBUTETYPE);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Determines whether action is to select or delete."
+      + " If set to true, only the specified attributes will be kept;"
+      + " If set to false, specified attributes will be deleted.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return m_invert;
+  }
+
+  /**
+   * Set whether selected columns should be removed or kept. If true the 
+   * selected columns are kept and unselected columns are deleted. If false
+   * selected columns are deleted and unselected columns are kept.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_invert = invert;
+  }
+
+  /**
+   * Gets the attribute type to be deleted by the filter as a string.
+   *
+   * @return the attribute type as a String
+   */
+  protected String getAttributeTypeString() {
+
+    if (m_attTypeToDelete == Attribute.NOMINAL) return "nominal";
+    else if (m_attTypeToDelete == Attribute.NUMERIC) return "numeric";
+    else if (m_attTypeToDelete == Attribute.STRING) return "string";
+    else if (m_attTypeToDelete == Attribute.DATE) return "date";
+    else if (m_attTypeToDelete == Attribute.RELATIONAL) return "relational";
+    else return "unknown";
+  }
+
+  /**
+   * Sets the attribute type to be deleted by the filter.
+   *
+   * @param typeString a String representing the new type the filter should delete
+   */
+  protected void setAttributeTypeString(String typeString) {
+
+    typeString = typeString.toLowerCase();
+    if (typeString.equals("nominal")) m_attTypeToDelete = Attribute.NOMINAL;
+    else if (typeString.equals("numeric")) m_attTypeToDelete = Attribute.NUMERIC;
+    else if (typeString.equals("string")) m_attTypeToDelete = Attribute.STRING;
+    else if (typeString.equals("date")) m_attTypeToDelete = Attribute.DATE;
+    else if (typeString.equals("relational")) m_attTypeToDelete = Attribute.RELATIONAL;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveType(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveUseless.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveUseless.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RemoveUseless.java	(revision 29)
@@ -0,0 +1,334 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveUseless.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * This filter removes attributes that do not vary at all or that vary too much. All constant attributes are deleted automatically, along with any that exceed the maximum percentage of variance parameter. The maximum variance test is only applied to nominal attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -M &lt;max variance %&gt;
+ *  Maximum variance percentage allowed (default 99)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class RemoveUseless 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8659417851407640038L;
+
+  /** The filter used to remove attributes */
+  protected Remove m_removeFilter = null;
+
+  /** The type of attribute to delete */
+  protected double m_maxVariancePercentage = 99.0;
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.STRING_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */ 
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_removeFilter = null;
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_removeFilter != null) {
+      m_removeFilter.input(instance);
+      Instance processed = m_removeFilter.output();
+      processed.setDataset(getOutputFormat());
+      copyValues(processed, false, instance.dataset(), getOutputFormat());
+      push(processed);
+      return true;
+    }
+    bufferInput(instance);
+    return false;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws Exception if no input format defined
+   */  
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_removeFilter == null) {
+
+      // establish attributes to remove from first batch
+
+      Instances toFilter = getInputFormat();
+      int[] attsToDelete = new int[toFilter.numAttributes()];
+      int numToDelete = 0;
+      for(int i = 0; i < toFilter.numAttributes(); i++) {
+	if (i==toFilter.classIndex()) continue; // skip class
+	AttributeStats stats = toFilter.attributeStats(i);
+	if (stats.distinctCount < 2) {
+	  // remove constant attributes
+	  attsToDelete[numToDelete++] = i;
+	} else if (toFilter.attribute(i).isNominal()) {
+	  // remove nominal attributes that vary too much
+	  double variancePercent = (double) stats.distinctCount
+	    / (double)(stats.totalCount - stats.missingCount) * 100.0;
+	  if (variancePercent > m_maxVariancePercentage) {
+	      attsToDelete[numToDelete++] = i;
+	  }
+	}
+      }
+      
+      int[] finalAttsToDelete = new int[numToDelete];
+      System.arraycopy(attsToDelete, 0, finalAttsToDelete, 0, numToDelete);
+      
+      m_removeFilter = new Remove();
+      m_removeFilter.setAttributeIndicesArray(finalAttsToDelete);
+      m_removeFilter.setInvertSelection(false);
+      m_removeFilter.setInputFormat(toFilter);
+      
+      for (int i = 0; i < toFilter.numInstances(); i++) {
+	m_removeFilter.input(toFilter.instance(i));
+      }
+      m_removeFilter.batchFinished();
+
+      Instance processed;
+      Instances outputDataset = m_removeFilter.getOutputFormat();
+    
+      // restore old relation name to hide attribute filter stamp
+      outputDataset.setRelationName(toFilter.relationName());
+    
+      setOutputFormat(outputDataset);
+      while ((processed = m_removeFilter.output()) != null) {
+	processed.setDataset(outputDataset);
+	push(processed);
+      }
+    }
+    flushInput();
+    
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+				    "\tMaximum variance percentage allowed (default 99)",
+				    "M", 1, "-M <max variance %>"));
+
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -M &lt;max variance %&gt;
+   *  Maximum variance percentage allowed (default 99)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String mString = Utils.getOption('M', options);
+    if (mString.length() != 0) {
+      setMaximumVariancePercentageAllowed((int) Double.valueOf(mString).doubleValue());
+    } else {
+      setMaximumVariancePercentageAllowed(99.0);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [2];
+    int current = 0;
+
+    options[current++] = "-M";
+    options[current++] = "" + getMaximumVariancePercentageAllowed();
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "This filter removes attributes that do not vary at all or that vary "
+      + "too much. All constant attributes are deleted automatically, along "
+      + "with any that exceed the maximum percentage of variance parameter. "
+      + "The maximum variance test is only applied to nominal attributes.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maximumVariancePercentageAllowedTipText() {
+
+    return "Set the threshold for the highest variance allowed before a nominal attribute will be deleted."
+      + "Specifically, if (number_of_distinct_values / total_number_of_values * 100)"
+      + " is greater than this value then the attribute will be removed.";
+  }
+
+  /**
+   * Sets the maximum variance attributes are allowed to have before they are
+   * deleted by the filter.
+   *
+   * @param maxVariance the maximum variance allowed, specified as a percentage
+   */
+  public void setMaximumVariancePercentageAllowed(double maxVariance) {
+    
+    m_maxVariancePercentage = maxVariance;
+  }
+
+  /**
+   * Gets the maximum variance attributes are allowed to have before they are
+   * deleted by the filter.
+   *
+   * @return the maximum variance allowed, specified as a percentage
+   */
+  public double getMaximumVariancePercentageAllowed() {
+
+    return m_maxVariancePercentage;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveUseless(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RenameAttribute.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RenameAttribute.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/RenameAttribute.java	(revision 29)
@@ -0,0 +1,489 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RenameAttribute.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleStreamFilter;
+
+/**
+ <!-- globalinfo-start -->
+ * This filter is used for renaming attribute names.<br/>
+ * Regular expressions can be used in the matching and replacing.<br/>
+ * See Javadoc of java.util.regex.Pattern class for more information:<br/>
+ * http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -find &lt;regexp&gt;
+ *  The regular expression that the attribute names must match.
+ *  (default: ([\s\S]+))</pre>
+ * 
+ * <pre> -replace &lt;regexp&gt;
+ *  The regular expression to replace matching attributes with.
+ *  (default: $0)</pre>
+ * 
+ * <pre> -all
+ *  Replaces all occurrences instead of just the first.
+ *  (default: only first occurrence)</pre>
+ * 
+ * <pre> -R &lt;range&gt;
+ *  The attribute range to work on.
+ * This is a comma separated list of attribute indices, with "first" and "last" valid values.
+ *  Specify an inclusive range with "-".
+ *  E.g: "first-3,5,6-10,last".
+ *  (default: first-last)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the attribute selection range.
+ *  (default: off)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6108 $
+ */
+public class RenameAttribute
+  extends SimpleStreamFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4216491776378279596L;
+
+  /** the regular expression that the attribute names have to match. */
+  protected String m_Find = "([\\s\\S]+)";
+  
+  /** the regular expression to replace the attribute name with. */
+  protected String m_Replace = "$0";
+  
+  /** the attribute range to work on. */
+  protected Range m_AttributeIndices = new Range("first-last");
+
+  /** whether to replace all occurrences or just the first. */
+  protected boolean m_ReplaceAll = false;
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "This filter is used for renaming attribute names.\n"
+      + "Regular expressions can be used in the matching and replacing.\n"
+      + "See Javadoc of java.util.regex.Pattern class for more information:\n"
+      + "http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.addElement(new Option(
+	"\tThe regular expression that the attribute names must match.\n"
+	+ "\t(default: ([\\s\\S]+))",
+	"find", 1, "-find <regexp>"));
+
+    result.addElement(new Option(
+	"\tThe regular expression to replace matching attributes with.\n"
+	+ "\t(default: $0)",
+	"replace", 1, "-replace <regexp>"));
+
+    result.addElement(new Option(
+	"\tReplaces all occurrences instead of just the first.\n"
+	+ "\t(default: only first occurrence)",
+	"all", 0, "-all"));
+
+    result.addElement(new Option(
+	"\tThe attribute range to work on.\n"
+	+ "This is a comma separated list of attribute indices, with "
+        + "\"first\" and \"last\" valid values.\n"
+        + "\tSpecify an inclusive range with \"-\".\n"
+        + "\tE.g: \"first-3,5,6-10,last\".\n"
+	+ "\t(default: first-last)",
+	"R", 1, "-R <range>"));
+
+    result.addElement(new Option(
+	"\tInverts the attribute selection range.\n"
+	+ "\t(default: off)",
+	"V", 0, "-V"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -find &lt;regexp&gt;
+   *  The regular expression that the attribute names must match.
+   *  (default: ([\s\S]+))</pre>
+   * 
+   * <pre> -replace &lt;regexp&gt;
+   *  The regular expression to replace matching attributes with.
+   *  (default: $0)</pre>
+   * 
+   * <pre> -all
+   *  Replaces all occurrences instead of just the first.
+   *  (default: only first occurrence)</pre>
+   * 
+   * <pre> -R &lt;range&gt;
+   *  The attribute range to work on.
+   * This is a comma separated list of attribute indices, with "first" and "last" valid values.
+   *  Specify an inclusive range with "-".
+   *  E.g: "first-3,5,6-10,last".
+   *  (default: first-last)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the attribute selection range.
+   *  (default: off)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption("find", options);
+    if (tmpStr.length() != 0)
+      setFind(tmpStr);
+    else
+      setFind("([\\s\\S]+)");
+    
+    tmpStr = Utils.getOption("replace", options);
+    if (tmpStr.length() != 0)
+      setReplace(tmpStr);
+    else
+      setReplace("$0");
+
+    setReplaceAll(Utils.getFlag("all", options));
+    
+    tmpStr = Utils.getOption("R", options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices("first-last");
+
+    setInvertSelection(Utils.getFlag("V", options));
+    
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+
+    result = new Vector<String>(Arrays.asList(super.getOptions()));
+    
+    result.add("-find");
+    result.add(getFind());
+    
+    result.add("-replace");
+    result.add(getReplace());
+    
+    if (getReplaceAll())
+      result.add("-all");
+    
+    result.add("-R");
+    result.add(getAttributeIndices());
+    
+    if (getInvertSelection())
+      result.add("-V");
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Sets the regular expression that the attribute names must match.
+   *
+   * @param value 	the regular expression
+   */
+  public void setFind(String value) {
+    m_Find = value;
+  }
+
+  /**
+   * Returns the current regular expression for .
+   *
+   * @return 		a string containing a comma separated list of ranges
+   */
+  public String getFind() {
+    return m_Find;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String findTipText() {
+    return "The regular expression that the attribute names must match.";
+  }
+
+  /**
+   * Sets the regular expression to replace matching attribute names with.
+   *
+   * @param value 	the regular expression
+   */
+  public void setReplace(String value) {
+    m_Replace = value;
+  }
+
+  /**
+   * Returns the regular expression to replace matching attribute names with.
+   *
+   * @return 		the regular expression
+   */
+  public String getReplace() {
+    return m_Replace;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String replaceTipText() {
+    return 
+        "The regular expression to use for replacing the matching attribute "
+      + "names with.";
+  }
+
+  /**
+   * Sets whether to replace all occurrences or just the first one.
+   *
+   * @param value 	if true then all occurrences are replace
+   */
+  public void setReplaceAll(boolean value) {
+    m_ReplaceAll = value;
+  }
+
+  /**
+   * Returns whether all occurrences are replaced or just the first one.
+   *
+   * @return 		true if all occurrences are replaced
+   */
+  public boolean getReplaceAll() {
+    return m_ReplaceAll;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String replaceAllTipText() {
+    return 
+        "If set to true, then all occurrences of the match will be replaced; "
+      + "otherwise only the first.";
+  }
+
+  /**
+   * Sets which attributes are to be acted on.
+   *
+   * @param value 	a string representing the list of attributes. Since
+   * 			the string will typically come from a user, attributes 
+   * 			are indexed from1. <br/>
+   * 			eg: first-3,5,6-last
+   */
+  public void setAttributeIndices(String value) {
+    m_AttributeIndices.setRanges(value);
+  }
+
+  /**
+   * Gets the current range selection.
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+    return m_AttributeIndices.getRanges();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return 
+        "Specify range of attributes to act on; "
+      + "this is a comma separated list of attribute indices, with "
+      + "\"first\" and \"last\" valid values; specify an inclusive "
+      + "range with \"-\"; eg: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Sets whether to invert the selection of the attributes.
+   *
+   * @param value 	if true then the selection is inverted
+   */
+  public void setInvertSelection(boolean value) {
+    m_AttributeIndices.setInvert(value);
+  }
+
+  /**
+   * Gets whether to invert the selection of the attributes.
+   *
+   * @return 		true if the selection is inverted
+   */
+  public boolean getInvertSelection() {
+    return m_AttributeIndices.getInvert();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return 
+        "If set to true, the selection will be inverted; eg: the attribute "
+      + "indices '2-4' then mean everything apart from '2-4'.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * hasImmediateOutputFormat() returns false, then this method will called
+   * from batchFinished() after the call of preprocess(Instances), in which,
+   * e.g., statistics for the actual processing step can be gathered.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Instances			result;
+    Attribute			att;
+    ArrayList<Attribute>	atts;
+    int				i;
+    
+    m_AttributeIndices.setUpper(inputFormat.numAttributes() - 1);
+    
+    // generate new header
+    atts = new ArrayList<Attribute>();
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      att = inputFormat.attribute(i);
+      if (m_AttributeIndices.isInRange(i)) {
+	if (m_ReplaceAll)
+	  atts.add(att.copy(att.name().replaceAll(m_Find, m_Replace)));
+	else
+	  atts.add(att.copy(att.name().replaceFirst(m_Find, m_Replace)));
+      }
+      else {
+	atts.add((Attribute) att.copy());
+      }
+    }
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(inputFormat.classIndex());
+    
+    return result;
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    return (Instance) instance.copy();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6108 $");
+  }
+
+  /**
+   * Main method for executing this filter.
+   *
+   * @param args 	the arguments to the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new RenameAttribute(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Reorder.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Reorder.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Reorder.java	(revision 29)
@@ -0,0 +1,425 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Reorder.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that generates output with a new order of the attributes. Useful if one wants to move an attribute to the end to use it as class attribute (e.g. with using "-R 2-last,1").<br/>
+ * But it's not only possible to change the order of all the attributes, but also to leave out attributes. E.g. if you have 10 attributes, you can generate the following output order: 1,3,5,7,9,10 or 10,1-5.<br/>
+ * You can also duplicate attributes, e.g. for further processing later on: e.g. 1,1,1,4,4,4,2,2,2 where the second and the third column of each attribute are processed differently and the first one, i.e. the original one is kept.<br/>
+ * One can simply inverse the order of the attributes via 'last-first'.<br/>
+ * After appyling the filter, the index of the class attribute is the last attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to copy. First and last are valid
+ *  indexes. (default first-last)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class Reorder 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -1135571321097202292L;
+
+  /** Stores which columns to reorder */
+  protected String m_NewOrderCols = "first-last";
+
+  /**
+   * Stores the indexes of the selected attributes in order, once the
+   * dataset is seen
+   */
+  protected int[] m_SelectedAttributes;
+
+  /** 
+   * Contains an index of string attributes in the input format
+   * that survive the filtering process -- some entries may be duplicated 
+   */
+  protected int[] m_InputStringIndex;
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector();
+
+    newVector.addElement(new Option(
+              "\tSpecify list of columns to copy. First and last are valid\n"
+	      +"\tindexes. (default first-last)",
+              "R", 1, "-R <index1,index2-index4,...>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of columns to copy. First and last are valid
+   *  indexes. (default first-last)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String orderList = Utils.getOption('R', options);
+    if (orderList.length() != 0) {
+      setAttributeIndices(orderList);
+    }
+    
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String [] options = new String [2];
+    int current = 0;
+
+    if (!getAttributeIndices().equals("")) {
+      options[current++] = "-R"; 
+      options[current++] = getAttributeIndices();
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+  
+  /**
+   * parses the index string and returns the corresponding int index
+   * 
+   * @param s			the index string to parse
+   * @param numAttributes	necessary for "last" and OutOfBounds checks
+   * @return			the int index determined form the index string
+   * @throws Exception		if index is not valid
+   */
+  protected int determineIndex(String s, int numAttributes) throws Exception {
+    int		result;
+    
+    if (s.equals("first"))
+      result = 0;
+    else if (s.equals("last"))
+      result = numAttributes - 1;
+    else
+      result = Integer.parseInt(s) - 1;
+    
+    // out of bounds?
+    if ( (result < 0) || (result > numAttributes - 1) )
+      throw new IllegalArgumentException(
+	  "'" + s + "' is not a valid index for the range '1-" + numAttributes + "'!");
+    
+    return result;
+  }
+  
+  /**
+   * parses the range string and returns an array with the indices
+   * 
+   * @param numAttributes	necessary for "last" and OutOfBounds checks
+   * @return			the indices determined form the range string
+   * @see			#m_NewOrderCols
+   * @throws Exception		if range is not valid
+   */
+  protected int[] determineIndices(int numAttributes) throws Exception {
+    int[]		result;
+    Vector<Integer>	list;
+    int			i;
+    StringTokenizer	tok;
+    String		token;
+    String[]		range;
+    int			from;
+    int			to;
+    
+    list = new Vector<Integer>();
+    
+    // parse range
+    tok = new StringTokenizer(m_NewOrderCols, ",");
+    while (tok.hasMoreTokens()) {
+      token = tok.nextToken();
+      if (token.indexOf("-") > -1) {
+	range = token.split("-");
+	if (range.length != 2)
+	  throw new IllegalArgumentException("'" + token + "' is not a valid range!");
+	from = determineIndex(range[0], numAttributes);
+	to   = determineIndex(range[1], numAttributes);
+
+	if (from <= to) {
+	  for (i = from; i <= to; i++)
+	    list.add(i);
+	}
+	else {
+	  for (i = from; i >= to; i--)
+	    list.add(i);
+	}
+      }
+      else {
+	list.add(determineIndex(token, numAttributes));
+      }
+    }
+    
+    // turn vector into int array
+    result = new int[list.size()];
+    for (i = 0; i < list.size(); i++)
+      result[i] = list.get(i);
+    
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attribute
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.NO_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if a problem occurs setting the input format
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    super.setInputFormat(instanceInfo);
+    
+    FastVector attributes = new FastVector();
+    int outputClass = -1;
+    m_SelectedAttributes = determineIndices(instanceInfo.numAttributes());
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      if (instanceInfo.classIndex() == current) {
+	outputClass = attributes.size();
+      }
+      Attribute keep = (Attribute)instanceInfo.attribute(current).copy();
+      attributes.addElement(keep);
+    }
+    
+    initInputLocators(instanceInfo, m_SelectedAttributes);
+
+    Instances outputFormat = new Instances(instanceInfo.relationName(),
+					   attributes, 0); 
+    outputFormat.setClassIndex(outputClass);
+    setOutputFormat(outputFormat);
+    
+    return true;
+  }
+  
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) {
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    double[] vals = new double[outputFormatPeek().numAttributes()];
+    for (int i = 0; i < m_SelectedAttributes.length; i++) {
+      int current = m_SelectedAttributes[i];
+      vals[i] = instance.value(current);
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance)
+      inst = new SparseInstance(instance.weight(), vals);
+    else
+      inst = new DenseInstance(instance.weight(), vals);
+
+    inst.setDataset(getOutputFormat());
+    copyValues(inst, false, instance.dataset(), getOutputFormat());
+    inst.setDataset(getOutputFormat());
+    
+    push(inst);
+    
+    return true;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "An instance filter that generates output with a new order of the "
+      + "attributes. Useful if one wants to move an attribute to the end to "
+      + "use it as class attribute (e.g. with using \"-R 2-last,1\").\n"
+      + "But it's not only possible to change the order of all the attributes, "
+      + "but also to leave out attributes. E.g. if you have 10 attributes, you "
+      + "can generate the following output order: 1,3,5,7,9,10 or 10,1-5.\n"
+      + "You can also duplicate attributes, e.g. for further processing later "
+      + "on: e.g. 1,1,1,4,4,4,2,2,2 where the second and the third column of "
+      + "each attribute are processed differently and the first one, i.e. the "
+      + "original one is kept.\n"
+      + "One can simply inverse the order of the attributes via 'last-first'.\n"
+      + "After appyling the filter, the index of the class attribute is the "
+      + "last attribute.";
+  }
+
+  /**
+   * Get the current range selection
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+    return m_NewOrderCols;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+      + " This is a comma separated list of attribute indices, with"
+      + " \"first\" and \"last\" valid values. Specify an inclusive"
+      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param rangeList a string representing the list of attributes.  Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last<br>
+   * Note: use this method before you call 
+   * <code>setInputFormat(Instances)</code>, since the output format is
+   * determined in that method.
+   * @throws Exception if an invalid range list is supplied
+   */
+  public void setAttributeIndices(String rangeList) throws Exception {
+    // simple test
+    if (rangeList.replaceAll("[afilrst0-9\\-,]*", "").length() != 0)
+      throw new IllegalArgumentException("Not a valid range string!");
+    
+    m_NewOrderCols = rangeList;
+  }
+
+  /**
+   * Set which attributes are to be copied (or kept if invert is true)
+   *
+   * @param attributes an array containing indexes of attributes to select.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.<br>
+   * Note: use this method before you call 
+   * <code>setInputFormat(Instances)</code>, since the output format is
+   * determined in that method.
+   * @throws Exception if an invalid set of ranges is supplied
+   */
+  public void setAttributeIndicesArray(int [] attributes) throws Exception {
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Reorder(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ReplaceMissingValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ReplaceMissingValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/ReplaceMissingValues.java	(revision 29)
@@ -0,0 +1,424 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ReplaceMissingValues.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Sourcable;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Replaces all missing values for nominal and numeric attributes in a dataset with the modes and means from the training data.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class ReplaceMissingValues 
+  extends PotentialClassIgnorer
+  implements UnsupervisedFilter, Sourcable {
+
+  /** for serialization */
+  static final long serialVersionUID = 8349568310991609867L;
+  
+  /** The modes and means */
+  private double[] m_ModesAndMeans = null;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Replaces all missing values for nominal and numeric attributes in a "
+      + "dataset with the modes and means from the training data.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_ModesAndMeans = null;
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_ModesAndMeans == null) {
+      bufferInput(instance);
+      return false;
+    } else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (m_ModesAndMeans == null) {
+      // Compute modes and means
+      double sumOfWeights =  getInputFormat().sumOfWeights();
+      double[][] counts = new double[getInputFormat().numAttributes()][];
+      for (int i = 0; i < getInputFormat().numAttributes(); i++) {
+	if (getInputFormat().attribute(i).isNominal()) {
+	  counts[i] = new double[getInputFormat().attribute(i).numValues()];
+          if (counts[i].length > 0)
+            counts[i][0] = sumOfWeights;
+	}
+      }
+      double[] sums = new double[getInputFormat().numAttributes()];
+      for (int i = 0; i < sums.length; i++) {
+	sums[i] = sumOfWeights;
+      }
+      double[] results = new double[getInputFormat().numAttributes()];
+      for (int j = 0; j < getInputFormat().numInstances(); j++) {
+	Instance inst = getInputFormat().instance(j);
+	for (int i = 0; i < inst.numValues(); i++) {
+	  if (!inst.isMissingSparse(i)) {
+	    double value = inst.valueSparse(i);
+	    if (inst.attributeSparse(i).isNominal()) {
+              if (counts[inst.index(i)].length > 0) {
+                counts[inst.index(i)][(int)value] += inst.weight();
+                counts[inst.index(i)][0] -= inst.weight();
+              }
+	    } else if (inst.attributeSparse(i).isNumeric()) {
+	      results[inst.index(i)] += inst.weight() * inst.valueSparse(i);
+	    }
+	  } else {
+	    if (inst.attributeSparse(i).isNominal()) {
+              if (counts[inst.index(i)].length > 0) {
+	        counts[inst.index(i)][0] -= inst.weight();
+              }
+	    } else if (inst.attributeSparse(i).isNumeric()) {
+	      sums[inst.index(i)] -= inst.weight();
+	    }
+	  }
+	}
+      }
+      m_ModesAndMeans = new double[getInputFormat().numAttributes()];
+      for (int i = 0; i < getInputFormat().numAttributes(); i++) {
+	if (getInputFormat().attribute(i).isNominal()) {
+          if (counts[i].length == 0)
+            m_ModesAndMeans[i] = Utils.missingValue();
+          else
+	    m_ModesAndMeans[i] = (double)Utils.maxIndex(counts[i]);
+	} else if (getInputFormat().attribute(i).isNumeric()) {
+	  if (Utils.gr(sums[i], 0)) {
+	    m_ModesAndMeans[i] = results[i] / sums[i];
+	  }
+	}
+      }
+
+      // Convert pending input instances
+      for(int i = 0; i < getInputFormat().numInstances(); i++) {
+	convertInstance(getInputFormat().instance(i));
+      }
+    } 
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   */
+  private void convertInstance(Instance instance) {
+  
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      double []vals = new double[instance.numValues()];
+      int []indices = new int[instance.numValues()];
+      int num = 0;
+      for (int j = 0; j < instance.numValues(); j++) {
+	if (instance.isMissingSparse(j) &&
+	    (getInputFormat().classIndex() != instance.index(j)) &&
+	    (instance.attributeSparse(j).isNominal() ||
+	     instance.attributeSparse(j).isNumeric())) {
+	  if (m_ModesAndMeans[instance.index(j)] != 0.0) {
+	    vals[num] = m_ModesAndMeans[instance.index(j)];
+	    indices[num] = instance.index(j);
+	    num++;
+	  } 
+	} else {
+	  vals[num] = instance.valueSparse(j);
+	  indices[num] = instance.index(j);
+	  num++;
+	}
+      } 
+      if (num == instance.numValues()) {
+	inst = new SparseInstance(instance.weight(), vals, indices,
+                                  instance.numAttributes());
+      } else {
+	double []tempVals = new double[num];
+	int []tempInd = new int[num];
+	System.arraycopy(vals, 0, tempVals, 0, num);
+	System.arraycopy(indices, 0, tempInd, 0, num);
+	inst = new SparseInstance(instance.weight(), tempVals, tempInd,
+                                  instance.numAttributes());
+      }
+    } else {
+      double []vals = new double[getInputFormat().numAttributes()];
+      for (int j = 0; j < instance.numAttributes(); j++) {
+	if (instance.isMissing(j) &&
+	    (getInputFormat().classIndex() != j) &&
+	    (getInputFormat().attribute(j).isNominal() ||
+	     getInputFormat().attribute(j).isNumeric())) {
+	  vals[j] = m_ModesAndMeans[j]; 
+	} else {
+	  vals[j] = instance.value(j);
+	}
+      } 
+      inst = new DenseInstance(instance.weight(), vals);
+    } 
+    inst.setDataset(instance.dataset());
+    push(inst);
+  }
+  
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception {
+    StringBuffer        result;
+    boolean[]		numeric;
+    boolean[]		nominal;
+    String[]		modes;
+    double[]		means;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // determine what attributes were processed
+    numeric = new boolean[data.numAttributes()];
+    nominal = new boolean[data.numAttributes()];
+    modes   = new String[data.numAttributes()];
+    means   = new double[data.numAttributes()];
+    for (i = 0; i < data.numAttributes(); i++) {
+      numeric[i] = (data.attribute(i).isNumeric() && (i != data.classIndex()));
+      nominal[i] = (data.attribute(i).isNominal() && (i != data.classIndex()));
+      
+      if (numeric[i])
+	means[i] = m_ModesAndMeans[i];
+      else
+	means[i] = Double.NaN;
+
+      if (nominal[i])
+	modes[i] = data.attribute(i).value((int) m_ModesAndMeans[i]);
+      else
+	modes[i] = null;
+    }
+    
+    result.append("class " + className + " {\n");
+    result.append("\n");
+    result.append("  /** lists which numeric attributes will be processed */\n");
+    result.append("  protected final static boolean[] NUMERIC = new boolean[]{" + Utils.arrayToString(numeric) + "};\n");
+    result.append("\n");
+    result.append("  /** lists which nominal attributes will be processed */\n");
+    result.append("  protected final static boolean[] NOMINAL = new boolean[]{" + Utils.arrayToString(nominal) + "};\n");
+    result.append("\n");
+    result.append("  /** the means */\n");
+    result.append("  protected final static double[] MEANS = new double[]{" + Utils.arrayToString(means).replaceAll("NaN", "Double.NaN") + "};\n");
+    result.append("\n");
+    result.append("  /** the modes */\n");
+    result.append("  protected final static String[] MODES = new String[]{");
+    for (i = 0; i < modes.length; i++) {
+      if (i > 0)
+	result.append(",");
+      if (nominal[i])
+	result.append("\"" + Utils.quote(modes[i]) + "\"");
+      else
+	result.append(modes[i]);
+    }
+    result.append("};\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters a single row\n");
+    result.append("   * \n");
+    result.append("   * @param i the row to process\n");
+    result.append("   * @return the processed row\n");
+    result.append("   */\n");
+    result.append("  public static Object[] filter(Object[] i) {\n");
+    result.append("    Object[] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      if (i[n] == null) {\n");
+    result.append("        if (NUMERIC[n])\n");
+    result.append("          result[n] = MEANS[n];\n");
+    result.append("        else if (NOMINAL[n])\n");
+    result.append("          result[n] = MODES[n];\n");
+    result.append("        else\n");
+    result.append("          result[n] = i[n];\n");
+    result.append("      }\n");
+    result.append("      else {\n");
+    result.append("        result[n] = i[n];\n");
+    result.append("      }\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters multiple rows\n");
+    result.append("   * \n");
+    result.append("   * @param i the rows to process\n");
+    result.append("   * @return the processed rows\n");
+    result.append("   */\n");
+    result.append("  public static Object[][] filter(Object[][] i) {\n");
+    result.append("    Object[][] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length][];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      result[n] = filter(i[n]);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new ReplaceMissingValues(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SortLabels.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SortLabels.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SortLabels.java	(revision 29)
@@ -0,0 +1,536 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SortLabels.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleStreamFilter;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A simple filter for sorting the labels of nominal attributes.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of string attributes to convert to words.
+ *  (default: select all relational attributes)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the matching sense of the selection.</pre>
+ * 
+ * <pre> -S &lt;CASE|NON-CASE&gt;
+ *  Determines the type of sorting:
+ *  CASE = Case-sensitive
+ *  NON-CASE = Case-insensitive
+ *  (default: CASE)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class SortLabels
+  extends SimpleStreamFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 7815204879694105691L;
+  
+  /**
+   * Represents a case-sensitive comparator for two strings.
+   *
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5987 $
+   */
+  public static class CaseSensitiveComparator
+    implements Comparator, Serializable {
+
+    /** for serialization. */
+    private static final long serialVersionUID = 7071450356783873277L;
+
+    /**
+     * compares the two strings, returns -1 if o1 is smaller than o2, 0
+     * if equal and +1 if greater.
+     * 
+     * @param o1	the first string to compare
+     * @param o2	the second string to compare
+     * @return		returns -1 if o1 is smaller than o2, 0 if equal and +1 
+     *			if greater
+     */
+    public int compare(Object o1, Object o2) {
+      String	s1;
+      String	s2;
+      
+      if ((o1 == null) && (o2 == null))
+	return 0;
+      else if (o1 == null)
+	return -1;
+      else if (o2 == null)
+	return +1;
+      
+      s1 = (String) o1;
+      s2 = (String) o2;
+      
+      return s1.compareTo(s2);
+    }
+  }
+  
+  /**
+   * Represents a case-insensitive comparator for two strings.
+   *
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5987 $
+   */
+  public static class CaseInsensitiveComparator
+    implements Comparator, Serializable {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -4515292733342486066L;
+
+    /**
+     * compares the two strings, returns -1 if o1 is smaller than o2, 0
+     * if equal and +1 if greater.
+     * 
+     * @param o1	the first string to compare
+     * @param o2	the second string to compare
+     * @return		returns -1 if o1 is smaller than o2, 0 if equal and +1 
+     *			if greater
+     */
+    public int compare(Object o1, Object o2) {
+      String	s1;
+      String	s2;
+      
+      if ((o1 == null) && (o2 == null))
+	return 0;
+      else if (o1 == null)
+	return -1;
+      else if (o2 == null)
+	return +1;
+      
+      s1 = (String) o1;
+      s2 = (String) o2;
+      
+      return s1.toLowerCase().compareTo(s2.toLowerCase());
+    }
+  }
+  
+  /** sorts the strings case-sensitive. */
+  public final static int SORT_CASESENSITIVE = 0;
+  
+  /** sorts the strings case-insensitive. */
+  public final static int SORT_CASEINSENSITIVE = 1;
+  
+  /** Tag allowing selection of sort type. */
+  public final static Tag[] TAGS_SORTTYPE = {
+    new Tag(SORT_CASESENSITIVE, "case", "Case-sensitive"),
+    new Tag(SORT_CASEINSENSITIVE, "non-case", "Case-insensitive")
+  };
+  
+  /** the range of attributes to process (only relational ones will be processed). */
+  protected Range m_AttributeIndices = new Range("first-last");
+
+  /** the new order for the labels. */
+  protected int[][] m_NewOrder = null;
+
+  /** the sort type. */
+  protected int m_SortType = SORT_CASEINSENSITIVE;
+  
+  /** the comparator to use for sorting. */
+  protected Comparator m_Comparator = new CaseSensitiveComparator();
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "A simple filter for sorting the labels of nominal attributes.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector        	result;
+    Enumeration   	en;
+    String		desc;
+    int			i;
+    SelectedTag		tag;
+
+    result = new Vector();
+
+    en = super.listOptions();
+    while (en.hasMoreElements())
+      result.addElement(en.nextElement());
+
+    result.addElement(new Option(
+	"\tSpecify list of string attributes to convert to words.\n"
+	+ "\t(default: select all relational attributes)",
+	"R", 1, "-R <index1,index2-index4,...>"));
+
+    result.addElement(new Option(
+	"\tInverts the matching sense of the selection.",
+	"V", 0, "-V"));
+
+    desc  = "";
+    for (i = 0; i < TAGS_SORTTYPE.length; i++) {
+      tag = new SelectedTag(TAGS_SORTTYPE[i].getID(), TAGS_SORTTYPE);
+      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
+      	       + " = " + tag.getSelectedTag().getReadable()
+      	       + "\n";
+    }
+    result.addElement(new Option(
+	"\tDetermines the type of sorting:\n"
+	+ desc
+	+ "\t(default: " + new SelectedTag(SORT_CASESENSITIVE, TAGS_SORTTYPE) + ")",
+	"S", 1, "-S " + Tag.toOptionList(TAGS_SORTTYPE)));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -R &lt;index1,index2-index4,...&gt;
+   *  Specify list of string attributes to convert to words.
+   *  (default: select all relational attributes)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the matching sense of the selection.</pre>
+   * 
+   * <pre> -S &lt;CASE|NON-CASE&gt;
+   *  Determines the type of sorting:
+   *  CASE = Case-sensitive
+   *  NON-CASE = Case-insensitive
+   *  (default: CASE)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption('R', options);
+    if (tmpStr.length() != 0)
+      setAttributeIndices(tmpStr);
+    else
+      setAttributeIndices("first-last");
+
+    setInvertSelection(Utils.getFlag('V', options));
+
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setSortType(new SelectedTag(tmpStr, TAGS_SORTTYPE));
+    else
+      setSortType(new SelectedTag(SORT_CASESENSITIVE, TAGS_SORTTYPE));
+
+    super.setOptions(options);
+  }
+
+  /**
+   * Gets the current settings of the classifier.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    int			i;
+    Vector<String>	result;
+    String[]		options;
+
+    result = new Vector<String>();
+
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-R"); 
+    result.add(getAttributeIndices().getRanges());
+
+    if (getInvertSelection())
+      result.add("-V");
+
+    result.add("-S");
+    result.add("" + getSortType());
+    
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return 
+        "Specify range of attributes to act on; "
+      + "this is a comma separated list of attribute indices, with "
+      + "\"first\" and \"last\" valid values; Specify an inclusive "
+      + "range with \"-\"; eg: \"first-3,5,6-10,last\".";
+  }
+  
+  /**
+   * Set the range of attributes to process.
+   *
+   * @param value 	the new range.
+   */
+  public void setAttributeIndices(String value) {
+    m_AttributeIndices = new Range(value);
+  }
+
+  /**
+   * Gets the current selected attributes.
+   *
+   * @return 		current selection.
+   */
+  public Range getAttributeIndices() {
+    return m_AttributeIndices;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return 
+        "Set attribute selection mode. If false, only selected "
+      + "attributes in the range will be worked on; if "
+      + "true, only non-selected attributes will be processed.";
+  }
+
+  /**
+   * Sets whether selected columns should be processed or skipped.
+   *
+   * @param value 	the new invert setting
+   */
+  public void setInvertSelection(boolean value) {
+    m_AttributeIndices.setInvert(value);
+  }
+
+  /**
+   * Gets whether the supplied columns are to be processed or skipped.
+   *
+   * @return 		true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+    return m_AttributeIndices.getInvert();
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String sortTypeTipText() {
+    return "The type of sorting to use.";
+  }
+
+  /**
+   * Sets the sort type to be used.
+   *
+   * @param type 	the type of sorting
+   */
+  public void setSortType(SelectedTag type) {
+    if (type.getTags() == TAGS_SORTTYPE) {
+      m_SortType = type.getSelectedTag().getID();
+      
+      if (m_SortType == SORT_CASESENSITIVE)
+	m_Comparator = new CaseSensitiveComparator();
+      else if (m_SortType == SORT_CASEINSENSITIVE)
+	m_Comparator = new CaseInsensitiveComparator();
+      else
+	throw new IllegalStateException("Unhandled sort type '" + type + "'!");
+    }
+  }
+
+  /**
+   * Gets the sort type to be used.
+   *
+   * @return 		the sort type
+   */
+  public SelectedTag getSortType() {
+    return new SelectedTag(m_SortType, TAGS_SORTTYPE);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. 
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) throws Exception {
+    Instances		result;
+    Attribute		att;
+    Attribute		attSorted;
+    FastVector		atts;
+    FastVector		values;
+    Vector<String>	sorted;
+    int			i;
+    int			n;
+    
+    m_AttributeIndices.setUpper(inputFormat.numAttributes() - 1);
+    
+    // determine sorted indices
+    atts       = new FastVector();
+    m_NewOrder = new int[inputFormat.numAttributes()][];
+    for (i = 0; i < inputFormat.numAttributes(); i++) {
+      att = inputFormat.attribute(i);
+      if (!att.isNominal() || !m_AttributeIndices.isInRange(i)) {
+	m_NewOrder[i] = new int[0];
+	atts.addElement(inputFormat.attribute(i).copy());
+	continue;
+      }
+
+      // sort labels
+      sorted = new Vector<String>();
+      for (n = 0; n < att.numValues(); n++)
+	sorted.add(att.value(n));
+      Collections.sort(sorted, m_Comparator);
+      
+      // determine new indices
+      m_NewOrder[i] = new int[att.numValues()];
+      values        = new FastVector();
+      for (n = 0; n < att.numValues(); n++) {
+	m_NewOrder[i][n] = sorted.indexOf(att.value(n));
+	values.addElement(sorted.get(n));
+      }
+      attSorted = new Attribute(att.name(), values);
+      attSorted.setWeight(att.weight());
+      atts.addElement(attSorted);
+    }
+    
+    // generate new header
+    result = new Instances(inputFormat.relationName(), atts, 0);
+    result.setClassIndex(inputFormat.classIndex());
+    
+    return result;
+  }
+
+  /**
+   * processes the given instance (may change the provided instance) and
+   * returns the modified version.
+   *
+   * @param instance    the instance to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instance process(Instance instance) throws Exception {
+    Instance	result;
+    Attribute	att;
+    double[]	values;
+    int		i;
+
+    // adjust indices
+    values = new double[instance.numAttributes()];
+    for (i = 0; i < instance.numAttributes(); i++) {
+      att = instance.attribute(i);
+      if (!att.isNominal() || !m_AttributeIndices.isInRange(i) || instance.isMissing(i))
+	values[i] = instance.value(i);
+      else
+	values[i] = m_NewOrder[i][(int) instance.value(i)];
+    }
+
+    // create new instance
+    result = new DenseInstance(instance.weight(), values);
+    
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments.
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new SortLabels(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Standardize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Standardize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Standardize.java	(revision 29)
@@ -0,0 +1,372 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Standardize.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Sourcable;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Standardizes all numeric attributes in the given dataset to have zero mean and unit variance (apart from the class attribute, if set).
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -unset-class-temporarily
+ *  Unsets the class index temporarily before the filter is
+ *  applied to the data.
+ *  (default: no)</pre>
+ * 
+ <!-- options-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class Standardize 
+  extends PotentialClassIgnorer 
+  implements UnsupervisedFilter, Sourcable {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6830769026855053281L;
+
+  /** The means */
+  private double [] m_Means;
+  
+  /** The variances */
+  private double [] m_StdDevs;
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Standardizes all numeric attributes in the given dataset "
+      + "to have zero mean and unit variance (apart from the class attribute, if set).";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_Means = m_StdDevs = null;
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_Means == null) {
+      bufferInput(instance);
+      return false;
+    } else {
+      convertInstance(instance);
+      return true;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @exception Exception if an error occurs
+   * @exception IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_Means == null) {
+      Instances input = getInputFormat();
+      m_Means = new double[input.numAttributes()];
+      m_StdDevs = new double[input.numAttributes()];
+      for (int i = 0; i < input.numAttributes(); i++) {
+	if (input.attribute(i).isNumeric() &&
+	    (input.classIndex() != i)) {
+	  m_Means[i] = input.meanOrMode(i);
+	  m_StdDevs[i] = Math.sqrt(input.variance(i));
+	}
+      }
+
+      // Convert pending input instances
+      for(int i = 0; i < input.numInstances(); i++) {
+	convertInstance(input.instance(i));
+      }
+    } 
+    // Free memory
+    flushInput();
+
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Convert a single instance over. The converted instance is 
+   * added to the end of the output queue.
+   *
+   * @param instance the instance to convert
+   * @exception Exception if an error occurs
+   */
+  private void convertInstance(Instance instance) throws Exception {
+  
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      double[] newVals = new double[instance.numAttributes()];
+      int[] newIndices = new int[instance.numAttributes()];
+      double[] vals = instance.toDoubleArray();
+      int ind = 0;
+      for (int j = 0; j < instance.numAttributes(); j++) {
+	double value;
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  
+	  // Just subtract the mean if the standard deviation is zero
+	  if (m_StdDevs[j] > 0) { 
+	    value = (vals[j] - m_Means[j]) / m_StdDevs[j];
+	  } else {
+	    value = vals[j] - m_Means[j];
+	  }
+          if (Double.isNaN(value)) {
+            throw new Exception("A NaN value was generated "
+                                + "while standardizing attribute " 
+                                + instance.attribute(j).name());
+          }
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	} else {
+	  value = vals[j];
+	  if (value != 0.0) {
+	    newVals[ind] = value;
+	    newIndices[ind] = j;
+	    ind++;
+	  }
+	}
+      }	
+      double[] tempVals = new double[ind];
+      int[] tempInd = new int[ind];
+      System.arraycopy(newVals, 0, tempVals, 0, ind);
+      System.arraycopy(newIndices, 0, tempInd, 0, ind);
+      inst = new SparseInstance(instance.weight(), tempVals, tempInd,
+                                instance.numAttributes());
+    } else {
+      double[] vals = instance.toDoubleArray();
+      for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+	if (instance.attribute(j).isNumeric() &&
+	    (!Utils.isMissingValue(vals[j])) &&
+	    (getInputFormat().classIndex() != j)) {
+	  
+	  // Just subtract the mean if the standard deviation is zero
+	  if (m_StdDevs[j] > 0) { 
+	    vals[j] = (vals[j] - m_Means[j]) / m_StdDevs[j];
+	  } else {
+	    vals[j] = (vals[j] - m_Means[j]);
+	  }
+          if (Double.isNaN(vals[j])) {
+            throw new Exception("A NaN value was generated "
+                                + "while standardizing attribute " 
+                                + instance.attribute(j).name());
+          }
+	}
+      }	
+      inst = new DenseInstance(instance.weight(), vals);
+    }
+    inst.setDataset(instance.dataset());
+    push(inst);
+  }
+  
+  /**
+   * Returns a string that describes the filter as source. The
+   * filter will be contained in a class with the given name (there may
+   * be auxiliary classes),
+   * and will contain two methods with these signatures:
+   * <pre><code>
+   * // converts one row
+   * public static Object[] filter(Object[] i);
+   * // converts a full dataset (first dimension is row index)
+   * public static Object[][] filter(Object[][] i);
+   * </code></pre>
+   * where the array <code>i</code> contains elements that are either
+   * Double, String, with missing values represented as null. The generated
+   * code is public domain and comes with no warranty.
+   *
+   * @param className   the name that should be given to the source class.
+   * @param data	the dataset used for initializing the filter
+   * @return            the object source described by a string
+   * @throws Exception  if the source can't be computed
+   */
+  public String toSource(String className, Instances data) throws Exception {
+    StringBuffer        result;
+    boolean[]		process;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    // determine what attributes were processed
+    process = new boolean[data.numAttributes()];
+    for (i = 0; i < data.numAttributes(); i++) {
+      process[i] = (data.attribute(i).isNumeric() && (i != data.classIndex()));
+    }
+    
+    result.append("class " + className + " {\n");
+    result.append("\n");
+    result.append("  /** lists which attributes will be processed */\n");
+    result.append("  protected final static boolean[] PROCESS = new boolean[]{" + Utils.arrayToString(process) + "};\n");
+    result.append("\n");
+    result.append("  /** the computed means */\n");
+    result.append("  protected final static double[] MEANS = new double[]{" + Utils.arrayToString(m_Means) + "};\n");
+    result.append("\n");
+    result.append("  /** the computed standard deviations */\n");
+    result.append("  protected final static double[] STDEVS = new double[]{" + Utils.arrayToString(m_StdDevs) + "};\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters a single row\n");
+    result.append("   * \n");
+    result.append("   * @param i the row to process\n");
+    result.append("   * @return the processed row\n");
+    result.append("   */\n");
+    result.append("  public static Object[] filter(Object[] i) {\n");
+    result.append("    Object[] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      if (PROCESS[n] && (i[n] != null)) {\n");
+    result.append("        if (STDEVS[n] > 0)\n");
+    result.append("          result[n] = (((Double) i[n]) - MEANS[n]) / STDEVS[n];\n");
+    result.append("        else\n");
+    result.append("          result[n] = ((Double) i[n]) - MEANS[n];\n");
+    result.append("      }\n");
+    result.append("      else {\n");
+    result.append("        result[n] = i[n];\n");
+    result.append("      }\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("\n");
+    result.append("  /**\n");
+    result.append("   * filters multiple rows\n");
+    result.append("   * \n");
+    result.append("   * @param i the rows to process\n");
+    result.append("   * @return the processed rows\n");
+    result.append("   */\n");
+    result.append("  public static Object[][] filter(Object[][] i) {\n");
+    result.append("    Object[][] result;\n");
+    result.append("\n");
+    result.append("    result = new Object[i.length][];\n");
+    result.append("    for (int n = 0; n < i.length; n++) {\n");
+    result.append("      result[n] = filter(i[n]);\n");
+    result.append("    }\n");
+    result.append("\n");
+    result.append("    return result;\n");
+    result.append("  }\n");
+    result.append("}\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Standardize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToNominal.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToNominal.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToNominal.java	(revision 29)
@@ -0,0 +1,356 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StringToNominal.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts a string attribute (i.e. unspecified number of values) to nominal (i.e. set number of values). You should ensure that all string values that will appear are represented in the first batch of the data.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;col&gt;
+ *  Sets the range of attribute indices (default last).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (len@reeltwo.com) 
+ * @version $Revision: 5987 $
+ */
+public class StringToNominal 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization */
+	private static final long serialVersionUID = 4864084427902797605L;
+	
+/** The attribute's range indices setting. */
+  private Range m_AttIndices = new Range("last"); 
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "Converts a range of string attributes (unspecified number of values) to nominal "
+      + "(set number of values). You should ensure that all string values that "
+      + "will appear are represented in the first batch of the data.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately.
+   * @throws UnsupportedAttributeTypeException if the selected attribute
+   * a string attribute.
+   * @throws Exception if the input format can't be set 
+   * successfully.
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_AttIndices.setUpper(instanceInfo.numAttributes() - 1);
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance the input instance.
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+
+    if (isOutputFormatDefined()) {
+      Instance newInstance = (Instance)instance.copy();
+      push(newInstance);
+      return true;
+    }
+
+    bufferInput(instance);
+    return false;
+  }
+
+
+  /**
+   * Signifies that this batch of input to the filter is finished. If the 
+   * filter requires all instances prior to filtering, output() may now 
+   * be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output.
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (!isOutputFormatDefined()) {
+
+      setOutputFormat();
+
+      // Convert pending input instances
+      for(int i = 0; i < getInputFormat().numInstances(); i++) {
+	push((Instance) getInputFormat().instance(i).copy());
+      }
+    } 
+
+    flushInput();
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration<Option> listOptions() {
+
+    Vector<Option> newVector = new Vector<Option>(1);
+
+    newVector.addElement(new Option(
+              "\tSets the range of attribute indices (default last).",
+              "R", 1, "-R <col>"));
+    
+    newVector.addElement(new Option(
+            "\tInvert the range specified by -R.",
+            "V", 1, "-V <col>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;col&gt;
+   *  Sets the range of attribute indices (default last).</pre>
+   *  
+   * <pre> -V &lt;col&gt;
+   *  Inverts the selection specified by -R.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String attIndices = Utils.getOption('R', options);
+    if (attIndices.length() != 0) {
+      setAttributeRange(attIndices);
+    } else {
+      setAttributeRange("last");
+    }
+    
+    String invertSelection = Utils.getOption('V', options);
+    if (invertSelection.length() != 0) {
+      m_AttIndices.setInvert(true);
+    } else {
+    	m_AttIndices.setInvert(false);
+    }
+       
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [this.m_AttIndices.getInvert() ? 7 : 6];
+    int current = 0;
+
+    options[current++] = "-R";
+    options[current++] = "" + (getAttributeRange());
+    
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    
+    if(this.m_AttIndices.getInvert()) {
+    	options[current++] = "-V";
+    }
+    
+    return options;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeRangeTipText() {
+
+    return "Sets which attributes to process. This attributes "
+      + "must be string attributes (\"first\" and \"last\" are valid values " +
+      		"as well as ranges and lists)";
+  }
+
+  /**
+   * Get the range of indices of the attributes used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeRange() {
+
+    return m_AttIndices.getRanges();
+  }
+
+  /**
+   * Sets range of indices of the attributes used.
+   *
+   * @param rangeList the list of attribute indices
+   */
+  public void setAttributeRange(String rangeList) {
+    
+    m_AttIndices.setRanges(rangeList);
+  }
+
+  /**
+   * Set the output format. Takes the current average class values
+   * and m_InputFormat and calls setOutputFormat(Instances) 
+   * appropriately.
+   */
+  private void setOutputFormat() {
+    
+    Instances newData;
+    FastVector newAtts, newVals;
+      
+    // Compute new attributes
+      
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if(!m_AttIndices.isInRange(j) || !att.isString()) {
+
+	// We don't have to copy the attribute because the
+	// attribute index remains unchanged.
+	newAtts.addElement(att); 
+      } else {
+	  
+	// Compute list of attribute values
+	newVals = new FastVector(att.numValues());
+	for (int i = 0; i < att.numValues(); i++) {
+          newVals.addElement(att.value(i)); 
+	}
+	newAtts.addElement(new Attribute(att.name(), newVals));
+      }
+    }
+      
+    // Construct new header
+    newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new StringToNominal(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToWordVector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToWordVector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/StringToWordVector.java	(revision 29)
@@ -0,0 +1,1750 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StringToWordVector.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.SparseInstance;
+import weka.core.Stopwords;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.stemmers.NullStemmer;
+import weka.core.stemmers.Stemmer;
+import weka.core.tokenizers.Tokenizer;
+import weka.core.tokenizers.WordTokenizer;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Converts String attributes into a set of attributes representing word occurrence (depending on the tokenizer) information from the text contained in the strings. The set of words (attributes) is determined by the first batch filtered (typically training data).
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C
+ *  Output word counts rather than boolean word presence.
+ * </pre>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of string attributes to convert to words (as weka Range).
+ *  (default: select all string attributes)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense of column indexes.</pre>
+ * 
+ * <pre> -P &lt;attribute name prefix&gt;
+ *  Specify a prefix for the created attribute names.
+ *  (default: "")</pre>
+ * 
+ * <pre> -W &lt;number of words to keep&gt;
+ *  Specify approximate number of word fields to create.
+ *  Surplus words will be discarded..
+ *  (default: 1000)</pre>
+ * 
+ * <pre> -prune-rate &lt;rate as a percentage of dataset&gt;
+ *  Specify the rate (e.g., every 10% of the input dataset) at which to periodically prune the dictionary.
+ *  -W prunes after creating a full dictionary. You may not have enough memory for this approach.
+ *  (default: no periodic pruning)</pre>
+ * 
+ * <pre> -T
+ *  Transform the word frequencies into log(1+fij)
+ *  where fij is the frequency of word i in jth document(instance).
+ * </pre>
+ * 
+ * <pre> -I
+ *  Transform each word frequency into:
+ *  fij*log(num of Documents/num of documents containing word i)
+ *    where fij if frequency of word i in jth document(instance)</pre>
+ * 
+ * <pre> -N
+ *  Whether to 0=not normalize/1=normalize all data/2=normalize test data only
+ *  to average length of training documents (default 0=don't normalize).</pre>
+ * 
+ * <pre> -L
+ *  Convert all tokens to lowercase before adding to the dictionary.</pre>
+ * 
+ * <pre> -S
+ *  Ignore words that are in the stoplist.</pre>
+ * 
+ * <pre> -stemmer &lt;spec&gt;
+ *  The stemmering algorihtm (classname plus parameters) to use.</pre>
+ * 
+ * <pre> -M &lt;int&gt;
+ *  The minimum term frequency (default = 1).</pre>
+ * 
+ * <pre> -O
+ *  If this is set, the maximum number of words and the 
+ *  minimum term frequency is not enforced on a per-class 
+ *  basis but based on the documents in all the classes 
+ *  (even if a class attribute is set).</pre>
+ * 
+ * <pre> -stopwords &lt;file&gt;
+ *  A file containing stopwords to override the default ones.
+ *  Using this option automatically sets the flag ('-S') to use the
+ *  stoplist if the file exists.
+ *  Format: one stopword per line, lines starting with '#'
+ *  are interpreted as comments and ignored.</pre>
+ * 
+ * <pre> -tokenizer &lt;spec&gt;
+ *  The tokenizing algorihtm (classname plus parameters) to use.
+ *  (default: weka.core.tokenizers.WordTokenizer)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author Stuart Inglis (stuart@reeltwo.com)
+ * @author Gordon Paynter (gordon.paynter@ucr.edu)
+ * @author Asrhaf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $ 
+ * @see Stopwords
+ */
+public class StringToWordVector 
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization. */
+  static final long serialVersionUID = 8249106275278565424L;
+
+  /** Range of columns to convert to word vectors. */
+  protected Range m_SelectedRange = new Range("first-last");
+
+  /** Contains a mapping of valid words to attribute indexes. */
+  private TreeMap m_Dictionary = new TreeMap();
+
+  /** True if output instances should contain word frequency rather than boolean 0 or 1. */
+  private boolean m_OutputCounts = false;
+
+  /** A String prefix for the attribute names. */
+  private String m_Prefix = "";
+
+  /** Contains the number of documents (instances) a particular word appears in.
+          The counts are stored with the same indexing as given by m_Dictionary.  */
+  private int [] m_DocsCounts;
+
+  /** Contains the number of documents (instances) in the input format from 
+          which the dictionary is created. It is used in IDF transform. */
+  private int m_NumInstances = -1;
+
+  /**
+   * Contains the average length of documents (among the first batch of 
+   * instances aka training data). This is used in length normalization of 
+   * documents which will be normalized to average document length.
+   */
+  private double m_AvgDocLength = -1;
+
+  /**
+   * The default number of words (per class if there is a class attribute
+   * assigned) to attempt to keep.
+   */
+  private int m_WordsToKeep = 1000;
+
+  /**
+   * The percentage at which to periodically prune the dictionary.
+   */
+  private double m_PeriodicPruningRate = -1;
+
+  /** True if word frequencies should be transformed into log(1+fi) 
+          where fi is the frequency of word i.
+   */
+  private boolean m_TFTransform;
+
+  /** The normalization to apply. */
+  protected int m_filterType = FILTER_NONE;
+
+  /** normalization: No normalization. */
+  public static final int FILTER_NONE = 0;
+  /** normalization: Normalize all data. */
+  public static final int FILTER_NORMALIZE_ALL = 1;
+  /** normalization: Normalize test data only. */
+  public static final int FILTER_NORMALIZE_TEST_ONLY = 2;
+
+  /** Specifies whether document's (instance's) word frequencies are
+   * to be normalized.  The are normalized to average length of
+   * documents specified as input format. */
+  public static final Tag [] TAGS_FILTER = {
+    new Tag(FILTER_NONE, "No normalization"),
+    new Tag(FILTER_NORMALIZE_ALL, "Normalize all data"),
+    new Tag(FILTER_NORMALIZE_TEST_ONLY, "Normalize test data only"),
+  };
+
+  /** True if word frequencies should be transformed into 
+          fij*log(numOfDocs/numOfDocsWithWordi). */
+  private boolean m_IDFTransform;
+
+  /** True if all tokens should be downcased. */
+  private boolean m_lowerCaseTokens;
+
+  /** True if tokens that are on a stoplist are to be ignored. */
+  private boolean m_useStoplist;  
+
+  /** the stemming algorithm. */
+  private Stemmer m_Stemmer = new NullStemmer();
+
+  /** the minimum (per-class) word frequency. */
+  private int m_minTermFreq = 1;
+
+  /** whether to operate on a per-class basis. */
+  private boolean m_doNotOperateOnPerClassBasis = false;
+
+  /** a file containing stopwords for using others than the default Rainbow 
+   * ones. */
+  private File m_Stopwords = new File(System.getProperty("user.dir"));
+
+  /** the tokenizer algorithm to use. */
+  private Tokenizer m_Tokenizer = new WordTokenizer();
+
+  /**
+   * Default constructor. Targets 1000 words in the output.
+   */
+  public StringToWordVector() {
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tOutput word counts rather than boolean word presence.\n",
+	"C", 0, "-C"));
+
+    result.addElement(new Option(
+	"\tSpecify list of string attributes to convert to words (as weka Range).\n"
+	+ "\t(default: select all string attributes)",
+	"R", 1, "-R <index1,index2-index4,...>"));
+
+    result.addElement(new Option(
+	"\tInvert matching sense of column indexes.",
+	"V", 0, "-V"));
+
+    result.addElement(new Option(
+	"\tSpecify a prefix for the created attribute names.\n"
+	+ "\t(default: \"\")",
+	"P", 1, "-P <attribute name prefix>"));
+
+    result.addElement(new Option(
+	"\tSpecify approximate number of word fields to create.\n"
+	+ "\tSurplus words will be discarded..\n"
+	+ "\t(default: 1000)",
+	"W", 1, "-W <number of words to keep>"));
+
+    result.addElement(new Option(
+	"\tSpecify the rate (e.g., every 10% of the input dataset) at which to periodically prune the dictionary.\n"
+	+ "\t-W prunes after creating a full dictionary. You may not have enough memory for this approach.\n"
+	+ "\t(default: no periodic pruning)",
+	"prune-rate", 1, "-prune-rate <rate as a percentage of dataset>"));
+
+    result.addElement(new Option(
+	"\tTransform the word frequencies into log(1+fij)\n"+
+	"\twhere fij is the frequency of word i in jth document(instance).\n",
+	"T", 0, "-T"));
+
+    result.addElement(new Option(
+	"\tTransform each word frequency into:\n"+
+	"\tfij*log(num of Documents/num of documents containing word i)\n"+
+	"\t  where fij if frequency of word i in jth document(instance)",
+	"I", 0, "-I"));
+
+    result.addElement(new Option(
+	"\tWhether to 0=not normalize/1=normalize all data/2=normalize test data only\n" 
+	+ "\tto average length of training documents "
+	+ "(default 0=don\'t normalize).",
+	"N", 1, "-N"));
+
+    result.addElement(new Option(
+	"\tConvert all tokens to lowercase before "+
+	"adding to the dictionary.",
+	"L", 0, "-L"));
+
+    result.addElement(new Option(
+	"\tIgnore words that are in the stoplist.",
+	"S", 0, "-S"));
+
+    result.addElement(new Option(
+	"\tThe stemmering algorihtm (classname plus parameters) to use.",
+	"stemmer", 1, "-stemmer <spec>"));
+
+    result.addElement(new Option(
+	"\tThe minimum term frequency (default = 1).",
+	"M", 1, "-M <int>"));
+
+    result.addElement(new Option(
+	"\tIf this is set, the maximum number of words and the \n"
+	+ "\tminimum term frequency is not enforced on a per-class \n"
+	+ "\tbasis but based on the documents in all the classes \n"
+	+ "\t(even if a class attribute is set).",
+	"O", 0, "-O"));
+
+    result.addElement(new Option(
+	"\tA file containing stopwords to override the default ones.\n"
+	+ "\tUsing this option automatically sets the flag ('-S') to use the\n"
+	+ "\tstoplist if the file exists.\n"
+	+ "\tFormat: one stopword per line, lines starting with '#'\n"
+	+ "\tare interpreted as comments and ignored.",
+	"stopwords", 1, "-stopwords <file>"));
+
+    result.addElement(new Option(
+	"\tThe tokenizing algorihtm (classname plus parameters) to use.\n"
+	+ "\t(default: " + WordTokenizer.class.getName() + ")",
+	"tokenizer", 1, "-tokenizer <spec>"));
+
+    return result.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+         <!-- options-start -->
+         * Valid options are: <p/>
+         * 
+         * <pre> -C
+         *  Output word counts rather than boolean word presence.
+         * </pre>
+         * 
+         * <pre> -R &lt;index1,index2-index4,...&gt;
+         *  Specify list of string attributes to convert to words (as weka Range).
+         *  (default: select all string attributes)</pre>
+         * 
+         * <pre> -V
+         *  Invert matching sense of column indexes.</pre>
+         * 
+         * <pre> -P &lt;attribute name prefix&gt;
+         *  Specify a prefix for the created attribute names.
+         *  (default: "")</pre>
+         * 
+         * <pre> -W &lt;number of words to keep&gt;
+         *  Specify approximate number of word fields to create.
+         *  Surplus words will be discarded..
+         *  (default: 1000)</pre>
+         * 
+         * <pre> -prune-rate &lt;rate as a percentage of dataset&gt;
+         *  Specify the rate (e.g., every 10% of the input dataset) at which to periodically prune the dictionary.
+         *  -W prunes after creating a full dictionary. You may not have enough memory for this approach.
+         *  (default: no periodic pruning)</pre>
+         * 
+         * <pre> -T
+         *  Transform the word frequencies into log(1+fij)
+         *  where fij is the frequency of word i in jth document(instance).
+         * </pre>
+         * 
+         * <pre> -I
+         *  Transform each word frequency into:
+         *  fij*log(num of Documents/num of documents containing word i)
+         *    where fij if frequency of word i in jth document(instance)</pre>
+         * 
+         * <pre> -N
+         *  Whether to 0=not normalize/1=normalize all data/2=normalize test data only
+         *  to average length of training documents (default 0=don't normalize).</pre>
+         * 
+         * <pre> -L
+         *  Convert all tokens to lowercase before adding to the dictionary.</pre>
+         * 
+         * <pre> -S
+         *  Ignore words that are in the stoplist.</pre>
+         * 
+         * <pre> -stemmer &lt;spec&gt;
+         *  The stemmering algorihtm (classname plus parameters) to use.</pre>
+         * 
+         * <pre> -M &lt;int&gt;
+         *  The minimum term frequency (default = 1).</pre>
+         * 
+         * <pre> -O
+         *  If this is set, the maximum number of words and the 
+         *  minimum term frequency is not enforced on a per-class 
+         *  basis but based on the documents in all the classes 
+         *  (even if a class attribute is set).</pre>
+         * 
+         * <pre> -stopwords &lt;file&gt;
+         *  A file containing stopwords to override the default ones.
+         *  Using this option automatically sets the flag ('-S') to use the
+         *  stoplist if the file exists.
+         *  Format: one stopword per line, lines starting with '#'
+         *  are interpreted as comments and ignored.</pre>
+         * 
+         * <pre> -tokenizer &lt;spec&gt;
+         *  The tokenizing algorihtm (classname plus parameters) to use.
+         *  (default: weka.core.tokenizers.WordTokenizer)</pre>
+         * 
+         <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String 	value;
+
+    value = Utils.getOption('R', options);
+    if (value.length() != 0)
+      setSelectedRange(value);
+    else
+      setSelectedRange("first-last");
+
+    setInvertSelection(Utils.getFlag('V', options));
+
+    value = Utils.getOption('P', options);
+    if (value.length() != 0)
+      setAttributeNamePrefix(value);
+    else
+      setAttributeNamePrefix("");
+
+    value = Utils.getOption('W', options);
+    if (value.length() != 0)
+      setWordsToKeep(Integer.valueOf(value).intValue());
+    else
+      setWordsToKeep(1000);
+
+    value = Utils.getOption("prune-rate", options);
+    if (value.length() > 0)
+      setPeriodicPruning(Double.parseDouble(value));
+    else
+      setPeriodicPruning(-1);
+
+    value = Utils.getOption('M', options);
+    if (value.length() != 0)
+      setMinTermFreq(Integer.valueOf(value).intValue());
+    else
+      setMinTermFreq(1);
+
+    setOutputWordCounts(Utils.getFlag('C', options));
+
+    setTFTransform(Utils.getFlag('T',  options));
+
+    setIDFTransform(Utils.getFlag('I',  options));
+
+    setDoNotOperateOnPerClassBasis(Utils.getFlag('O', options));
+
+    String nString = Utils.getOption('N', options);
+    if (nString.length() != 0)
+      setNormalizeDocLength(new SelectedTag(Integer.parseInt(nString), TAGS_FILTER));
+    else
+      setNormalizeDocLength(new SelectedTag(FILTER_NONE, TAGS_FILTER));
+
+    setLowerCaseTokens(Utils.getFlag('L', options));
+
+    setUseStoplist(Utils.getFlag('S', options));
+
+    String stemmerString = Utils.getOption("stemmer", options);
+    if (stemmerString.length() == 0) {
+      setStemmer(null);
+    }
+    else {
+      String[] stemmerSpec = Utils.splitOptions(stemmerString);
+      if (stemmerSpec.length == 0)
+	throw new Exception("Invalid stemmer specification string");
+      String stemmerName = stemmerSpec[0];
+      stemmerSpec[0] = "";
+      Stemmer stemmer = (Stemmer) Class.forName(stemmerName).newInstance();
+      if (stemmer instanceof OptionHandler)
+	((OptionHandler) stemmer).setOptions(stemmerSpec);
+      setStemmer(stemmer);
+    }
+
+    value = Utils.getOption("stopwords", options);
+    if (value.length() != 0)
+      setStopwords(new File(value));
+    else
+      setStopwords(null);
+
+    String tokenizerString = Utils.getOption("tokenizer", options);
+    if (tokenizerString.length() == 0) {
+      setTokenizer(new WordTokenizer());
+    }
+    else {
+      String[] tokenizerSpec = Utils.splitOptions(tokenizerString);
+      if (tokenizerSpec.length == 0)
+	throw new Exception("Invalid tokenizer specification string");
+      String tokenizerName = tokenizerSpec[0];
+      tokenizerSpec[0] = "";
+      Tokenizer tokenizer = (Tokenizer) Class.forName(tokenizerName).newInstance();
+      if (tokenizer instanceof OptionHandler)
+	((OptionHandler) tokenizer).setOptions(tokenizerSpec);
+      setTokenizer(tokenizer);
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector        result;
+
+    result = new Vector();
+
+    result.add("-R"); 
+    result.add(getSelectedRange().getRanges());
+
+    if (getInvertSelection())
+      result.add("-V");
+
+    if (!"".equals(getAttributeNamePrefix())) {
+      result.add("-P"); 
+      result.add(getAttributeNamePrefix());
+    }
+
+    result.add("-W"); 
+    result.add(String.valueOf(getWordsToKeep()));
+
+    result.add("-prune-rate"); 
+    result.add(String.valueOf(getPeriodicPruning()));
+
+    if (getOutputWordCounts())
+      result.add("-C");
+
+    if (getTFTransform())
+      result.add("-T");
+
+    if (getIDFTransform())
+      result.add("-I");
+
+    result.add("-N"); 
+    result.add("" + m_filterType);
+
+    if (getLowerCaseTokens())
+      result.add("-L");
+
+    if (getUseStoplist())
+      result.add("-S");
+
+    if (getStemmer() != null) {
+      result.add("-stemmer");
+      String spec = getStemmer().getClass().getName();
+      if (getStemmer() instanceof OptionHandler)
+	spec += " " + Utils.joinOptions(
+	    ((OptionHandler) getStemmer()).getOptions());
+      result.add(spec.trim());
+    }
+
+    result.add("-M"); 
+    result.add(String.valueOf(getMinTermFreq()));
+
+    if (getDoNotOperateOnPerClassBasis())
+      result.add("-O");
+
+    if (!getStopwords().isDirectory()) {
+      result.add("-stopwords");
+      result.add(getStopwords().getAbsolutePath());
+    }
+
+    result.add("-tokenizer");
+    String spec = getTokenizer().getClass().getName();
+    if (getTokenizer() instanceof OptionHandler)
+      spec += " " + Utils.joinOptions(
+	  ((OptionHandler) getTokenizer()).getOptions());
+    result.add(spec.trim());
+
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Constructor that allows specification of the target number of words
+   * in the output.
+   *
+   * @param wordsToKeep the number of words in the output vector (per class
+   * if assigned).
+   */
+  public StringToWordVector(int wordsToKeep) {
+    m_WordsToKeep = wordsToKeep;
+  }
+
+  /** 
+   * Used to store word counts for dictionary selection based on 
+   * a threshold.
+   */
+  private class Count 
+  implements Serializable, RevisionHandler {
+
+    /** for serialization. */
+    static final long serialVersionUID = 2157223818584474321L;
+
+    /** the counts. */
+    public int count, docCount;
+
+    /**
+     * the constructor.
+     * 
+     * @param c the count
+     */
+    public Count(int c) { 
+      count = c; 
+    }
+    
+    /**
+     * Returns the revision string.
+     * 
+     * @return		the revision
+     */
+    public String getRevision() {
+      return RevisionUtils.extract("$Revision: 5987 $");
+    }
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+  throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_SelectedRange.setUpper(instanceInfo.numAttributes() - 1);
+    m_AvgDocLength = -1;
+    m_NumInstances = -1;
+    return false;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance.
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      FastVector fv = new FastVector();
+      int firstCopy = convertInstancewoDocNorm(instance, fv);
+      Instance inst = (Instance)fv.elementAt(0);
+      if (m_filterType != FILTER_NONE) {
+	normalizeInstance(inst, firstCopy);
+      }
+      push(inst);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output.
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    // We only need to do something in this method
+    // if the first batch hasn't been processed. Otherwise
+    // input() has already done all the work.
+    if (!isFirstBatchDone()) {
+
+      // Determine the dictionary from the first batch (training data)
+      determineDictionary();
+
+      // Convert all instances w/o normalization
+      FastVector fv = new FastVector();
+      int firstCopy=0;
+      for(int i=0; i < m_NumInstances; i++) {
+	firstCopy = convertInstancewoDocNorm(getInputFormat().instance(i), fv);
+      }
+
+      // Need to compute average document length if necessary
+      if (m_filterType != FILTER_NONE) {
+	m_AvgDocLength = 0;
+	for(int i=0; i<fv.size(); i++) {
+	  Instance inst = (Instance) fv.elementAt(i);
+	  double docLength = 0;
+	  for(int j=0; j<inst.numValues(); j++) {
+	    if(inst.index(j)>=firstCopy) {
+	      docLength += inst.valueSparse(j) * inst.valueSparse(j);
+	    }
+	  }        
+	  m_AvgDocLength += Math.sqrt(docLength);
+	}
+	m_AvgDocLength /= m_NumInstances;
+      }
+
+      // Perform normalization if necessary.
+      if (m_filterType == FILTER_NORMALIZE_ALL) {
+	for(int i=0; i<fv.size(); i++) {
+	  normalizeInstance((Instance) fv.elementAt(i), firstCopy);
+	}
+      }
+
+      // Push all instances into the output queue
+      for(int i=0; i<fv.size(); i++) {
+	push((Instance) fv.elementAt(i));
+      }
+    }
+
+    // Flush the input
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns a string describing this filter.
+   * 
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */  
+  public String globalInfo() {
+    return 
+    "Converts String attributes into a set of attributes representing "
+    + "word occurrence (depending on the tokenizer) information from the "
+    + "text contained in the strings. The set of words (attributes) is "
+    + "determined by the first batch filtered (typically training data).";
+  }  
+
+  /**
+   * Gets whether output instances contain 0 or 1 indicating word
+   * presence, or word counts.
+   *
+   * @return true if word counts should be output.
+   */
+  public boolean getOutputWordCounts() {
+    return m_OutputCounts;
+  }
+
+  /**
+   * Sets whether output instances contain 0 or 1 indicating word
+   * presence, or word counts.
+   *
+   * @param outputWordCounts true if word counts should be output.
+   */
+  public void setOutputWordCounts(boolean outputWordCounts) {
+    m_OutputCounts = outputWordCounts;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String outputWordCountsTipText() {
+    return "Output word counts rather than boolean 0 or 1"+
+    "(indicating presence or absence of a word).";
+  }
+
+  /**
+   * Get the value of m_SelectedRange.
+   *
+   * @return Value of m_SelectedRange.
+   */
+  public Range getSelectedRange() {
+    return m_SelectedRange;
+  }
+
+  /**
+   * Set the value of m_SelectedRange.
+   *
+   * @param newSelectedRange Value to assign to m_SelectedRange.
+   */
+  public void setSelectedRange(String newSelectedRange) {
+    m_SelectedRange = new Range(newSelectedRange);
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndicesTipText() {
+    return "Specify range of attributes to act on."
+    + " This is a comma separated list of attribute indices, with"
+    + " \"first\" and \"last\" valid values. Specify an inclusive"
+    + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
+  }
+
+  /**
+   * Gets the current range selection.
+   *
+   * @return a string containing a comma separated list of ranges
+   */
+  public String getAttributeIndices() {
+    return m_SelectedRange.getRanges();
+  }
+
+  /**
+   * Sets which attributes are to be worked on.
+   *
+   * @param rangeList a string representing the list of attributes. Since
+   * the string will typically come from a user, attributes are indexed from
+   * 1. <br>
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setAttributeIndices(String rangeList) {
+    m_SelectedRange.setRanges(rangeList);
+  }
+
+  /**
+   * Sets which attributes are to be processed.
+   *
+   * @param attributes an array containing indexes of attributes to process.
+   * Since the array will typically come from a program, attributes are indexed
+   * from 0.
+   * @throws IllegalArgumentException if an invalid set of ranges
+   * is supplied 
+   */
+  public void setAttributeIndicesArray(int[] attributes) {
+    setAttributeIndices(Range.indicesToRangeList(attributes));
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Set attribute selection mode. If false, only selected"
+    + " attributes in the range will be worked on; if"
+    + " true, only non-selected attributes will be processed.";
+  }
+
+  /**
+   * Gets whether the supplied columns are to be processed or skipped.
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+    return m_SelectedRange.getInvert();
+  }
+
+  /**
+   * Sets whether selected columns should be processed or skipped.
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+    m_SelectedRange.setInvert(invert);
+  }
+
+  /**
+   * Get the attribute name prefix.
+   *
+   * @return The current attribute name prefix.
+   */
+  public String getAttributeNamePrefix() {
+    return m_Prefix;
+  }
+
+  /**
+   * Set the attribute name prefix.
+   *
+   * @param newPrefix String to use as the attribute name prefix.
+   */
+  public void setAttributeNamePrefix(String newPrefix) {
+    m_Prefix = newPrefix;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeNamePrefixTipText() {
+    return "Prefix for the created attribute names. "+
+    "(default: \"\")";
+  }
+
+  /**
+   * Gets the number of words (per class if there is a class attribute
+   * assigned) to attempt to keep.
+   *
+   * @return the target number of words in the output vector (per class if
+   * assigned).
+   */
+  public int getWordsToKeep() {
+    return m_WordsToKeep;
+  }
+
+  /**
+   * Sets the number of words (per class if there is a class attribute
+   * assigned) to attempt to keep.
+   *
+   * @param newWordsToKeep the target number of words in the output 
+   * vector (per class if assigned).
+   */
+  public void setWordsToKeep(int newWordsToKeep) {
+    m_WordsToKeep = newWordsToKeep;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String wordsToKeepTipText() {
+    return "The number of words (per class if there is a class attribute "+
+    "assigned) to attempt to keep.";
+  }
+
+  /**
+   * Gets the rate at which the dictionary is periodically pruned, as a 
+   * percentage of the dataset size.
+   *
+   * @return the rate at which the dictionary is periodically pruned
+   */
+  public double getPeriodicPruning() {
+    return m_PeriodicPruningRate;
+  }
+
+  /**
+   * Sets the rate at which the dictionary is periodically pruned, as a 
+   * percentage of the dataset size.
+   *
+   * @param newPeriodicPruning the rate at which the dictionary is periodically pruned
+   */
+  public void setPeriodicPruning(double newPeriodicPruning) {
+    m_PeriodicPruningRate = newPeriodicPruning;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String periodicPruningTipText() {
+    return "Specify the rate (x% of the input dataset) at which to periodically prune the dictionary. "
+    + "wordsToKeep prunes after creating a full dictionary. You may not have enough "
+    + "memory for this approach.";
+  }
+
+  /** Gets whether if the word frequencies should be transformed into
+   *  log(1+fij) where fij is the frequency of word i in document(instance) j.
+   *
+   * @return true if word frequencies are to be transformed.
+   */
+  public boolean getTFTransform() {
+    return this.m_TFTransform;
+  }
+
+  /** Sets whether if the word frequencies should be transformed into
+   *  log(1+fij) where fij is the frequency of word i in document(instance) j.
+   *
+   * @param TFTransform true if word frequencies are to be transformed.
+   */
+  public void setTFTransform(boolean TFTransform) {
+    this.m_TFTransform = TFTransform;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String TFTransformTipText() {
+    return "Sets whether if the word frequencies should be transformed into:\n "+
+    "   log(1+fij) \n"+
+    "       where fij is the frequency of word i in document (instance) j.";
+  }
+
+  /** Sets whether if the word frequencies in a document should be transformed
+   * into: <br>
+   * fij*log(num of Docs/num of Docs with word i) <br>
+   *      where fij is the frequency of word i in document(instance) j.
+   *
+   * @return true if the word frequencies are to be transformed.
+   */
+  public boolean getIDFTransform() {
+    return this.m_IDFTransform;
+  }
+
+  /** Sets whether if the word frequencies in a document should be transformed
+   * into: <br>
+   * fij*log(num of Docs/num of Docs with word i) <br>
+   *      where fij is the frequency of word i in document(instance) j.
+   *
+   * @param IDFTransform true if the word frequecies are to be transformed
+   */
+  public void setIDFTransform(boolean IDFTransform) {
+    this.m_IDFTransform = IDFTransform;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String IDFTransformTipText() {
+    return "Sets whether if the word frequencies in a document should be "+
+    "transformed into: \n"+
+    "   fij*log(num of Docs/num of Docs with word i) \n"+
+    "      where fij is the frequency of word i in document (instance) j.";
+  }
+
+
+  /** Gets whether if the word frequencies for a document (instance) should
+   *  be normalized or not.
+   *
+   * @return true if word frequencies are to be normalized.
+   */
+  public SelectedTag getNormalizeDocLength() {
+
+    return new SelectedTag(m_filterType, TAGS_FILTER);
+  }
+
+  /** Sets whether if the word frequencies for a document (instance) should
+   *  be normalized or not.
+   *
+   * @param newType the new type.
+   */
+  public void setNormalizeDocLength(SelectedTag newType) {
+
+    if (newType.getTags() == TAGS_FILTER) {
+      m_filterType = newType.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String normalizeDocLengthTipText() {
+    return "Sets whether if the word frequencies for a document (instance) "+
+    "should be normalized or not.";
+  }
+
+  /** Gets whether if the tokens are to be downcased or not.
+   *
+   * @return true if the tokens are to be downcased.
+   */
+  public boolean getLowerCaseTokens() {
+    return this.m_lowerCaseTokens;
+  }
+
+  /** Sets whether if the tokens are to be downcased or not. (Doesn't affect
+   * non-alphabetic characters in tokens).
+   *
+   * @param downCaseTokens should be true if only lower case tokens are 
+   * to be formed.
+   */
+  public void setLowerCaseTokens(boolean downCaseTokens) {
+    this.m_lowerCaseTokens = downCaseTokens;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String doNotOperateOnPerClassBasisTipText() {
+    return "If this is set, the maximum number of words and the "
+    + "minimum term frequency is not enforced on a per-class "
+    + "basis but based on the documents in all the classes "
+    +  "(even if a class attribute is set).";
+  }
+
+  /**
+   * Get the DoNotOperateOnPerClassBasis value.
+   * @return the DoNotOperateOnPerClassBasis value.
+   */
+  public boolean getDoNotOperateOnPerClassBasis() {
+    return m_doNotOperateOnPerClassBasis;
+  }
+
+  /**
+   * Set the DoNotOperateOnPerClassBasis value.
+   * @param newDoNotOperateOnPerClassBasis The new DoNotOperateOnPerClassBasis value.
+   */
+  public void setDoNotOperateOnPerClassBasis(boolean newDoNotOperateOnPerClassBasis) {
+    this.m_doNotOperateOnPerClassBasis = newDoNotOperateOnPerClassBasis;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String minTermFreqTipText() {
+    return "Sets the minimum term frequency. This is enforced "
+    + "on a per-class basis.";
+  }
+
+  /**
+   * Get the MinTermFreq value.
+   * @return the MinTermFreq value.
+   */
+  public int getMinTermFreq() {
+    return m_minTermFreq;
+  }
+
+  /**
+   * Set the MinTermFreq value.
+   * @param newMinTermFreq The new MinTermFreq value.
+   */
+  public void setMinTermFreq(int newMinTermFreq) {
+    this.m_minTermFreq = newMinTermFreq;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String lowerCaseTokensTipText() {
+    return "If set then all the word tokens are converted to lower case "+
+    "before being added to the dictionary.";
+  }
+
+  /** Gets whether if the words on the stoplist are to be ignored (The stoplist
+   *  is in weka.core.StopWords).
+   *
+   * @return true if the words on the stoplist are to be ignored.
+   */
+  public boolean getUseStoplist() {
+    return m_useStoplist;
+  }  
+
+  /** Sets whether if the words that are on a stoplist are to be ignored (The
+   * stop list is in weka.core.StopWords).
+   *
+   * @param useStoplist true if the tokens that are on a stoplist are to be 
+   * ignored.
+   */
+  public void setUseStoplist(boolean useStoplist) {
+    m_useStoplist = useStoplist;
+  }  
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String useStoplistTipText() {
+    return "Ignores all the words that are on the stoplist, if set to true.";
+  } 
+
+  /**
+   * the stemming algorithm to use, null means no stemming at all (i.e., the
+   * NullStemmer is used).
+   *
+   * @param value     the configured stemming algorithm, or null
+   * @see             NullStemmer
+   */
+  public void setStemmer(Stemmer value) {
+    if (value != null)
+      m_Stemmer = value;
+    else
+      m_Stemmer = new NullStemmer();
+  }
+
+  /**
+   * Returns the current stemming algorithm, null if none is used.
+   *
+   * @return          the current stemming algorithm, null if none set
+   */
+  public Stemmer getStemmer() {
+    return m_Stemmer;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String stemmerTipText() {
+    return "The stemming algorithm to use on the words.";
+  }
+
+  /**
+   * sets the file containing the stopwords, null or a directory unset the
+   * stopwords. If the file exists, it automatically turns on the flag to
+   * use the stoplist.
+   *
+   * @param value     the file containing the stopwords
+   */
+  public void setStopwords(File value) {
+    if (value == null)
+      value = new File(System.getProperty("user.dir"));
+
+    m_Stopwords = value;
+    if (value.exists() && value.isFile())
+      setUseStoplist(true);
+  }
+
+  /**
+   * returns the file used for obtaining the stopwords, if the file represents
+   * a directory then the default ones are used.
+   *
+   * @return          the file containing the stopwords
+   */
+  public File getStopwords() {
+    return m_Stopwords;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String stopwordsTipText() {
+    return "The file containing the stopwords (if this is a directory then the default ones are used).";
+  }
+
+  /**
+   * the tokenizer algorithm to use.
+   *
+   * @param value     the configured tokenizing algorithm
+   */
+  public void setTokenizer(Tokenizer value) {
+    m_Tokenizer = value;
+  }
+
+  /**
+   * Returns the current tokenizer algorithm.
+   *
+   * @return          the current tokenizer algorithm
+   */
+  public Tokenizer getTokenizer() {
+    return m_Tokenizer;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String tokenizerTipText() {
+    return "The tokenizing algorithm to use on the strings.";
+  }
+
+  /**
+   * sorts an array.
+   * 
+   * @param array the array to sort
+   */
+  private static void sortArray(int [] array) {
+
+    int i, j, h, N = array.length - 1;
+
+    for (h = 1; h <= N / 9; h = 3 * h + 1); 
+
+    for (; h > 0; h /= 3) {
+      for (i = h + 1; i <= N; i++) { 
+	int v = array[i]; 
+	j = i; 
+	while (j > h && array[j - h] > v ) { 
+	  array[j] = array[j - h]; 
+	  j -= h; 
+	} 
+	array[j] = v; 
+      } 
+    }
+  }
+
+  /**
+   * determines the selected range.
+   */
+  private void determineSelectedRange() {
+
+    Instances inputFormat = getInputFormat();
+
+    // Calculate the default set of fields to convert
+    if (m_SelectedRange == null) {
+      StringBuffer fields = new StringBuffer();
+      for (int j = 0; j < inputFormat.numAttributes(); j++) { 
+	if (inputFormat.attribute(j).type() == Attribute.STRING)
+	  fields.append((j + 1) + ",");
+      }
+      m_SelectedRange = new Range(fields.toString());
+    }
+    m_SelectedRange.setUpper(inputFormat.numAttributes() - 1);
+
+    // Prevent the user from converting non-string fields
+    StringBuffer fields = new StringBuffer();
+    for (int j = 0; j < inputFormat.numAttributes(); j++) { 
+      if (m_SelectedRange.isInRange(j) 
+	  && inputFormat.attribute(j).type() == Attribute.STRING)
+	fields.append((j + 1) + ",");
+    }
+    m_SelectedRange.setRanges(fields.toString());
+    m_SelectedRange.setUpper(inputFormat.numAttributes() - 1);
+
+    // System.err.println("Selected Range: " + getSelectedRange().getRanges()); 
+  }
+
+  /**
+   * determines the dictionary.
+   */
+  private void determineDictionary() {
+    // initialize stopwords
+    Stopwords stopwords = new Stopwords();
+    if (getUseStoplist()) {
+      try {
+	if (getStopwords().exists() && !getStopwords().isDirectory())
+	  stopwords.read(getStopwords());
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+
+    // Operate on a per-class basis if class attribute is set
+    int classInd = getInputFormat().classIndex();
+    int values = 1;
+    if (!m_doNotOperateOnPerClassBasis && (classInd != -1)) {
+      values = getInputFormat().attribute(classInd).numValues();
+    }
+
+    //TreeMap dictionaryArr [] = new TreeMap[values];
+    TreeMap [] dictionaryArr = new TreeMap[values];
+    for (int i = 0; i < values; i++) {
+      dictionaryArr[i] = new TreeMap();
+    }
+
+    // Make sure we know which fields to convert
+    determineSelectedRange();
+
+    // Tokenize all training text into an orderedMap of "words".
+    long pruneRate = 
+      Math.round((m_PeriodicPruningRate/100.0)*getInputFormat().numInstances());
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      Instance instance = getInputFormat().instance(i);
+      int vInd = 0;
+      if (!m_doNotOperateOnPerClassBasis && (classInd != -1)) {
+	vInd = (int)instance.classValue();
+      }
+
+      // Iterate through all relevant string attributes of the current instance
+      Hashtable h = new Hashtable();
+      for (int j = 0; j < instance.numAttributes(); j++) { 
+	if (m_SelectedRange.isInRange(j) && (instance.isMissing(j) == false)) {
+
+	  // Get tokenizer
+	  m_Tokenizer.tokenize(instance.stringValue(j));
+
+	  // Iterate through tokens, perform stemming, and remove stopwords
+	  // (if required)
+	  while (m_Tokenizer.hasMoreElements()) {
+	    String word = ((String)m_Tokenizer.nextElement()).intern();
+
+	    if(this.m_lowerCaseTokens==true)
+	      word = word.toLowerCase();
+
+	    word = m_Stemmer.stem(word);
+
+	    if(this.m_useStoplist==true)
+	      if(stopwords.is(word))
+		continue;
+
+	    if(!(h.contains(word)))
+	      h.put(word, new Integer(0));
+
+	    Count count = (Count)dictionaryArr[vInd].get(word);
+	    if (count == null) {
+	      dictionaryArr[vInd].put(word, new Count(1));
+	    } else {
+	      count.count++;                
+	    }
+	  }          
+	}
+      }
+
+      //updating the docCount for the words that have occurred in this
+      //instance(document).
+      Enumeration e = h.keys();
+      while(e.hasMoreElements()) {
+	String word = (String) e.nextElement();
+	Count c = (Count)dictionaryArr[vInd].get(word);
+	if(c!=null) {
+	  c.docCount++;
+	} else 
+	  System.err.println("Warning: A word should definitely be in the "+
+	      "dictionary.Please check the code");
+      }
+
+
+      if (pruneRate > 0) {
+	if (i % pruneRate == 0 && i > 0) {
+	  for (int z = 0; z < values; z++) {
+	    Vector d = new Vector(1000);
+	    Iterator it = dictionaryArr[z].keySet().iterator();
+	    while (it.hasNext()) {
+	      String word = (String)it.next();
+	      Count count = (Count)dictionaryArr[z].get(word);
+	      if (count.count <= 1) { d.add(word); }
+	    }
+	    Iterator iter = d.iterator();
+	    while(iter.hasNext()) {
+	      String word = (String)iter.next();
+	      dictionaryArr[z].remove(word);
+	    }
+	  }
+	}
+      }
+    }
+
+    // Figure out the minimum required word frequency
+    int totalsize = 0;
+    int prune[] = new int[values];
+    for (int z = 0; z < values; z++) {
+      totalsize += dictionaryArr[z].size();
+
+      int array[] = new int[dictionaryArr[z].size()];
+      int pos = 0;
+      Iterator it = dictionaryArr[z].keySet().iterator();
+      while (it.hasNext()) {
+	String word = (String)it.next();
+	Count count = (Count)dictionaryArr[z].get(word);
+	array[pos] = count.count;
+	pos++;
+      }
+
+      // sort the array
+      sortArray(array);
+      if (array.length < m_WordsToKeep) {
+	// if there aren't enough words, set the threshold to
+	// minFreq
+	prune[z] = m_minTermFreq;
+      } else {
+	// otherwise set it to be at least minFreq
+	prune[z] = Math.max(m_minTermFreq, 
+	    array[array.length - m_WordsToKeep]);
+      }
+    }
+
+    // Convert the dictionary into an attribute index
+    // and create one attribute per word
+    FastVector attributes = new FastVector(totalsize +
+	getInputFormat().numAttributes());
+
+    // Add the non-converted attributes 
+    int classIndex = -1;
+    for (int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if (!m_SelectedRange.isInRange(i)) { 
+	if (getInputFormat().classIndex() == i) {
+	  classIndex = attributes.size();
+	}
+	attributes.addElement(getInputFormat().attribute(i).copy());
+      }     
+    }
+
+    // Add the word vector attributes (eliminating duplicates
+	// that occur in multiple classes)
+    TreeMap newDictionary = new TreeMap();
+    int index = attributes.size();
+    for(int z = 0; z < values; z++) {
+      Iterator it = dictionaryArr[z].keySet().iterator();
+      while (it.hasNext()) {
+	String word = (String)it.next();
+	Count count = (Count)dictionaryArr[z].get(word);
+	if (count.count >= prune[z]) {
+	  if(newDictionary.get(word) == null) {
+	    newDictionary.put(word, new Integer(index++));
+	    attributes.addElement(new Attribute(m_Prefix + word));
+	  }
+	}
+      }
+    }
+
+    // Compute document frequencies
+    m_DocsCounts = new int[attributes.size()];
+    Iterator it = newDictionary.keySet().iterator();
+    while(it.hasNext()) {
+      String word = (String) it.next();
+      int idx = ((Integer)newDictionary.get(word)).intValue();
+      int docsCount=0;
+      for(int j=0; j<values; j++) {
+	Count c = (Count) dictionaryArr[j].get(word);
+	if(c!=null)
+	  docsCount += c.docCount;
+      }
+      m_DocsCounts[idx]=docsCount;
+    }
+
+    // Trim vector and set instance variables
+    attributes.trimToSize();
+    m_Dictionary = newDictionary;
+    m_NumInstances = getInputFormat().numInstances();
+
+    // Set the filter's output format
+    Instances outputFormat = new Instances(getInputFormat().relationName(), 
+	attributes, 0);
+    outputFormat.setClassIndex(classIndex);
+    setOutputFormat(outputFormat);
+  }
+
+  /**
+   * Converts the instance w/o normalization.
+   * 
+   * @oaram instance the instance to convert
+   * @param v
+   * @return the conerted instance
+   */
+  private int convertInstancewoDocNorm(Instance instance, FastVector v) {
+
+    // Convert the instance into a sorted set of indexes
+    TreeMap contained = new TreeMap();
+
+    // Copy all non-converted attributes from input to output
+    int firstCopy = 0;
+    for (int i = 0; i < getInputFormat().numAttributes(); i++) {
+      if (!m_SelectedRange.isInRange(i)) { 
+	if (getInputFormat().attribute(i).type() != Attribute.STRING) {
+	  // Add simple nominal and numeric attributes directly
+	  if (instance.value(i) != 0.0) {
+	    contained.put(new Integer(firstCopy), 
+		new Double(instance.value(i)));
+	  } 
+	} else {
+	  if (instance.isMissing(i)) {
+	    contained.put(new Integer(firstCopy),
+		new Double(Utils.missingValue()));
+	  } else {
+
+	    // If this is a string attribute, we have to first add
+	    // this value to the range of possible values, then add
+	    // its new internal index.
+	    if (outputFormatPeek().attribute(firstCopy).numValues() == 0) {
+	      // Note that the first string value in a
+	      // SparseInstance doesn't get printed.
+	      outputFormatPeek().attribute(firstCopy)
+	      .addStringValue("Hack to defeat SparseInstance bug");
+	    }
+	    int newIndex = outputFormatPeek().attribute(firstCopy)
+	    .addStringValue(instance.stringValue(i));
+	    contained.put(new Integer(firstCopy), 
+		new Double(newIndex));
+	  }
+	}
+	firstCopy++;
+      }     
+    }
+
+    for (int j = 0; j < instance.numAttributes(); j++) { 
+      //if ((getInputFormat().attribute(j).type() == Attribute.STRING) 
+      if (m_SelectedRange.isInRange(j)
+	  && (instance.isMissing(j) == false)) {          
+
+	m_Tokenizer.tokenize(instance.stringValue(j));
+
+	while (m_Tokenizer.hasMoreElements()) {
+	  String word = (String)m_Tokenizer.nextElement(); 
+	  if(this.m_lowerCaseTokens==true)
+	    word = word.toLowerCase();
+	  word = m_Stemmer.stem(word);
+	  Integer index = (Integer) m_Dictionary.get(word);
+	  if (index != null) {
+	    if (m_OutputCounts) { // Separate if here rather than two lines down to avoid hashtable lookup
+	      Double count = (Double)contained.get(index);
+	    if (count != null) {
+	      contained.put(index, new Double(count.doubleValue() + 1.0));
+	    } else {
+	      contained.put(index, new Double(1));
+	    }
+	    } else {
+	      contained.put(index, new Double(1));
+	    }                
+	  }
+	}
+      }
+    }
+
+    //Doing TFTransform
+    if(m_TFTransform==true) {
+      Iterator it = contained.keySet().iterator();
+      for(int i=0; it.hasNext(); i++) {
+	Integer index = (Integer)it.next();
+	if( index.intValue() >= firstCopy ) { 
+	  double val = ((Double)contained.get(index)).doubleValue();
+	  val = Math.log(val+1);
+	  contained.put(index, new Double(val));
+	}
+      }
+    }
+
+    //Doing IDFTransform
+    if(m_IDFTransform==true) {
+      Iterator it = contained.keySet().iterator();
+      for(int i=0; it.hasNext(); i++) {
+	Integer index = (Integer)it.next();
+	if( index.intValue() >= firstCopy ) {
+	  double val = ((Double)contained.get(index)).doubleValue();
+	  val = val*Math.log( m_NumInstances /
+	      (double) m_DocsCounts[index.intValue()] );
+	  contained.put(index, new Double(val));
+	}
+      }        
+    }
+
+    // Convert the set to structures needed to create a sparse instance.
+    double [] values = new double [contained.size()];
+    int [] indices = new int [contained.size()];
+    Iterator it = contained.keySet().iterator();
+    for (int i = 0; it.hasNext(); i++) {
+      Integer index = (Integer)it.next();
+      Double value = (Double)contained.get(index);
+      values[i] = value.doubleValue();
+      indices[i] = index.intValue();
+    }
+
+    Instance inst = new SparseInstance(instance.weight(), values, indices, 
+	outputFormatPeek().numAttributes());
+    inst.setDataset(outputFormatPeek());
+
+    v.addElement(inst);
+
+    return firstCopy;    
+  }
+
+  /**
+   * Normalizes given instance to average doc length (only the newly
+   * constructed attributes).
+   * 
+   * @param inst	the instance to normalize
+   * @param firstCopy
+   * @throws Exception if avg. doc length not set
+   */
+  private void normalizeInstance(Instance inst, int firstCopy) 
+  throws Exception {
+
+    double docLength = 0;
+
+    if (m_AvgDocLength < 0) {
+      throw new Exception("Average document length not set.");
+    }
+
+    // Compute length of document vector
+    for(int j=0; j<inst.numValues(); j++) {
+      if(inst.index(j)>=firstCopy) {
+	docLength += inst.valueSparse(j) * inst.valueSparse(j);
+      }
+    }        
+    docLength = Math.sqrt(docLength);
+
+    // Normalize document vector
+    for(int j=0; j<inst.numValues(); j++) {
+      if(inst.index(j)>=firstCopy) {
+	double val = inst.valueSparse(j) * m_AvgDocLength / docLength;
+	inst.setValueSparse(j, val);
+	if (val == 0){
+	  System.err.println("setting value "+inst.index(j)+" to zero.");
+	  j--;
+	}
+      }
+    }        
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new StringToWordVector(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SwapValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SwapValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/SwapValues.java	(revision 29)
@@ -0,0 +1,426 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SwapValues.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+
+/** 
+ <!-- globalinfo-start -->
+ * Swaps two values of a nominal attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;col&gt;
+ *  Sets the attribute index (default last).</pre>
+ * 
+ * <pre> -F &lt;value index&gt;
+ *  Sets the first value's index (default first).</pre>
+ * 
+ * <pre> -S &lt;value index&gt;
+ *  Sets the second value's index (default last).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz) 
+ * @version $Revision: 5987 $
+ */
+public class SwapValues 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 6155834679414275855L;
+
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+  /** The first value's index setting. */
+  private SingleIndex m_FirstIndex = new SingleIndex("first");
+
+  /** The second value's index setting. */
+  private SingleIndex m_SecondIndex = new SingleIndex("last");
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return  "Swaps two values of a nominal attribute.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnsupportedAttributeTypeException if the selected attribute
+   * is not nominal or if it only has one value.
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    m_FirstIndex.setUpper(instanceInfo.
+			  attribute(m_AttIndex.getIndex()).numValues() - 1);
+    m_SecondIndex.setUpper(instanceInfo.
+			   attribute(m_AttIndex.getIndex()).numValues() - 1);
+    if (!instanceInfo.attribute(m_AttIndex.getIndex()).isNominal()) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute not nominal.");
+    }
+    if (instanceInfo.attribute(m_AttIndex.getIndex()).numValues() < 2) {
+      throw new UnsupportedAttributeTypeException("Chosen attribute has less than " 
+						  + "two values.");
+    }
+    setOutputFormat();
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. The instance is processed
+   * and made available for output immediately.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance newInstance = (Instance)instance.copy();
+    if (!newInstance.isMissing(m_AttIndex.getIndex())) {
+      if ((int)newInstance.value(m_AttIndex.getIndex()) == m_SecondIndex.getIndex()) {
+        newInstance.setValue(m_AttIndex.getIndex(), (double)m_FirstIndex.getIndex());
+      } else if ((int)newInstance.value(m_AttIndex.getIndex()) == 
+		 m_FirstIndex.getIndex()) {
+        newInstance.setValue(m_AttIndex.getIndex(), (double)m_SecondIndex.getIndex());
+      }
+    }
+    push(newInstance);
+    return true;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(3);
+
+    newVector.addElement(new Option(
+              "\tSets the attribute index (default last).",
+              "C", 1, "-C <col>"));
+
+    newVector.addElement(new Option(
+              "\tSets the first value's index (default first).",
+              "F", 1, "-F <value index>"));
+
+    newVector.addElement(new Option(
+              "\tSets the second value's index (default last).",
+              "S", 1, "-S <value index>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;col&gt;
+   *  Sets the attribute index (default last).</pre>
+   * 
+   * <pre> -F &lt;value index&gt;
+   *  Sets the first value's index (default first).</pre>
+   * 
+   * <pre> -S &lt;value index&gt;
+   *  Sets the second value's index (default last).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String attIndex = Utils.getOption('C', options);
+    if (attIndex.length() != 0) {
+      setAttributeIndex(attIndex);
+    } else {
+      setAttributeIndex("last");
+    }
+
+    String firstValIndex = Utils.getOption('F', options);
+    if (firstValIndex.length() != 0) {
+      setFirstValueIndex(firstValIndex);
+    } else {
+      setFirstValueIndex("first");
+    }
+
+    String secondValIndex = Utils.getOption('S', options);
+    if (secondValIndex.length() != 0) {
+      setSecondValueIndex(secondValIndex);
+    } else {
+      setSecondValueIndex("last");
+    }
+   
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [6];
+    int current = 0;
+
+    options[current++] = "-C";
+    options[current++] = "" + (getAttributeIndex());
+    options[current++] = "-F"; 
+    options[current++] = "" + (getFirstValueIndex());
+    options[current++] = "-S"; 
+    options[current++] = "" + (getSecondValueIndex());
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+
+    return "Sets which attribute to process. This "
+      + "attribute must be nominal (\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String firstValueIndexTipText() {
+
+    return "The index of the first value."
+      + "(\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the first value used.
+   *
+   * @return the index of the first value
+   */
+  public String getFirstValueIndex() {
+
+    return m_FirstIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the first value used.
+   *
+   * @param firstIndex the index of the first value
+   */
+  public void setFirstValueIndex(String firstIndex) {
+    
+    m_FirstIndex.setSingleIndex(firstIndex);
+  }
+
+  /**
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String secondValueIndexTipText() {
+
+    return "The index of the second value."
+      + "(\"first\" and \"last\" are valid values)";
+  }
+
+  /**
+   * Get the index of the second value used.
+   *
+   * @return the index of the second value
+   */
+  public String getSecondValueIndex() {
+
+    return m_SecondIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the second value used.
+   *
+   * @param secondIndex the index of the second value
+   */
+  public void setSecondValueIndex(String secondIndex) {
+    
+    m_SecondIndex.setSingleIndex(secondIndex);
+  }
+
+  /**
+   * Set the output format. Swapss the desired nominal attribute values in
+   * the header and calls setOutputFormat(Instances) appropriately.
+   */
+  private void setOutputFormat() {
+    
+    Instances newData;
+    FastVector newAtts, newVals;
+      
+    // Compute new attributes
+      
+    newAtts = new FastVector(getInputFormat().numAttributes());
+    for (int j = 0; j < getInputFormat().numAttributes(); j++) {
+      Attribute att = getInputFormat().attribute(j);
+      if (j != m_AttIndex.getIndex()) {
+	newAtts.addElement(att.copy()); 
+      } else {
+	  
+	// Compute list of attribute values
+	  
+	newVals = new FastVector(att.numValues());
+	for (int i = 0; i < att.numValues(); i++) {
+	  if (i == m_FirstIndex.getIndex()) {
+	    newVals.addElement(att.value(m_SecondIndex.getIndex()));
+	  } else if (i == m_SecondIndex.getIndex()) {
+	    newVals.addElement(att.value(m_FirstIndex.getIndex()));
+	  } else {
+	    newVals.addElement(att.value(i)); 
+	  }
+	}
+	newAtts.addElement(new Attribute(att.name(), newVals));
+      }
+    }
+      
+    // Construct new header
+      
+    newData = new Instances(getInputFormat().relationName(), newAtts, 0);
+    newData.setClassIndex(getInputFormat().classIndex());
+    setOutputFormat(newData);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new SwapValues(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesDelta.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesDelta.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesDelta.java	(revision 29)
@@ -0,0 +1,200 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TimeSeriesDelta.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Capabilities.Capability;
+import weka.core.Utils;
+
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that assumes instances form time-series data and replaces attribute values in the current instance with the difference between the current value and the equivalent attribute attribute value of some previous (or future) instance. For instances where the time-shifted value is unknown either the instance may be dropped, or missing values used. Skips the class attribute if it is set.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to translate in time. First and
+ *  last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. calculate for all non-specified columns)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  The number of instances forward to translate values
+ *  between. A negative number indicates taking values from
+ *  a past instance. (default -1)</pre>
+ * 
+ * <pre> -M
+ *  For instances at the beginning or end of the dataset where
+ *  the translated values are not known, remove those instances
+ *  (default is to use missing values).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class TimeSeriesDelta 
+  extends TimeSeriesTranslate {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3101490081896634942L;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An instance filter that assumes instances form time-series data and "
+      + "replaces attribute values in the current instance with the difference "
+      + "between the current value and the equivalent attribute attribute value "
+      + "of some previous (or future) instance. For instances where the time-shifted "
+      + "value is unknown either the instance may be dropped, or missing values "
+      + "used. Skips the class attribute if it is set.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnsupportedAttributeTypeException if selected
+   * attributes are not numeric.  
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    if ((instanceInfo.classIndex() > 0) && (!getFillWithMissing())) {
+      throw new IllegalArgumentException("TimeSeriesDelta: Need to fill in missing values " +
+                                         "using appropriate option when class index is set.");
+    }
+    super.setInputFormat(instanceInfo);
+    // Create the output buffer
+    Instances outputFormat = new Instances(instanceInfo, 0); 
+    for(int i = 0; i < instanceInfo.numAttributes(); i++) {
+      if (i != instanceInfo.classIndex()) {
+        if (m_SelectedCols.isInRange(i)) {
+          if (outputFormat.attribute(i).isNumeric()) {
+            outputFormat.renameAttribute(i, outputFormat.attribute(i).name()
+                                         + " d"
+                                         + (m_InstanceRange < 0 ? '-' : '+')
+                                         + Math.abs(m_InstanceRange));
+          } else {
+            throw new UnsupportedAttributeTypeException("Time delta attributes must be numeric!");
+          }
+        }
+      }
+    }
+    outputFormat.setClassIndex(instanceInfo.classIndex());
+    setOutputFormat(outputFormat);
+    return true;
+  }
+  
+
+  /**
+   * Creates a new instance the same as one instance (the "destination")
+   * but with some attribute values copied from another instance
+   * (the "source")
+   *
+   * @param source the source instance
+   * @param dest the destination instance
+   * @return the new merged instance
+   */
+  protected Instance mergeInstances(Instance source, Instance dest) {
+
+    Instances outputFormat = outputFormatPeek();
+    double[] vals = new double[outputFormat.numAttributes()];
+    for(int i = 0; i < vals.length; i++) {
+      if ((i != outputFormat.classIndex()) && (m_SelectedCols.isInRange(i))) {
+        if ((source != null) && !source.isMissing(i) && !dest.isMissing(i)) {
+          vals[i] = dest.value(i) - source.value(i);
+        } else {
+          vals[i] = Utils.missingValue();
+        }
+      } else {
+        vals[i] = dest.value(i);
+      }
+    }
+    Instance inst = null;
+    if (dest instanceof SparseInstance) {
+      inst = new SparseInstance(dest.weight(), vals);
+    } else {
+      inst = new DenseInstance(dest.weight(), vals);
+    }
+    inst.setDataset(dest.dataset());
+    return inst;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new TimeSeriesDelta(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesTranslate.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesTranslate.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/TimeSeriesTranslate.java	(revision 29)
@@ -0,0 +1,199 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TimeSeriesTranslate.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Capabilities;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Capabilities.Capability;
+import weka.core.Utils;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that assumes instances form time-series data and replaces attribute values in the current instance with the equivalent attribute values of some previous (or future) instance. For instances where the desired value is unknown either the instance may be dropped, or missing values used. Skips the class attribute if it is set.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;index1,index2-index4,...&gt;
+ *  Specify list of columns to translate in time. First and
+ *  last are valid indexes. (default none)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense (i.e. calculate for all non-specified columns)</pre>
+ * 
+ * <pre> -I &lt;num&gt;
+ *  The number of instances forward to translate values
+ *  between. A negative number indicates taking values from
+ *  a past instance. (default -1)</pre>
+ * 
+ * <pre> -M
+ *  For instances at the beginning or end of the dataset where
+ *  the translated values are not known, remove those instances
+ *  (default is to use missing values).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class TimeSeriesTranslate 
+  extends AbstractTimeSeries {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8901621509691785705L;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return
+        "An instance filter that assumes instances form time-series data and "
+      + "replaces attribute values in the current instance with the equivalent "
+      + "attribute values of some previous (or future) instance. For "
+      + "instances where the desired value is unknown either the instance may "
+      + "be dropped, or missing values used. Skips the class attribute if it is set.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws UnsupportedAttributeTypeException if selected
+   * attributes are not numeric or nominal.
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    if ((instanceInfo.classIndex() > 0) && (!getFillWithMissing())) {
+      throw new IllegalArgumentException("TimeSeriesTranslate: Need to fill in missing values " +
+                                         "using appropriate option when class index is set.");
+    }
+    super.setInputFormat(instanceInfo);
+    // Create the output buffer
+    Instances outputFormat = new Instances(instanceInfo, 0); 
+    for(int i = 0; i < instanceInfo.numAttributes(); i++) {
+      if (i != instanceInfo.classIndex()) {
+        if (m_SelectedCols.isInRange(i)) {
+          if (outputFormat.attribute(i).isNominal()
+              || outputFormat.attribute(i).isNumeric()) {
+            outputFormat.renameAttribute(i, outputFormat.attribute(i).name()
+                                         + (m_InstanceRange < 0 ? '-' : '+')
+                                         + Math.abs(m_InstanceRange));
+          } else {
+            throw new UnsupportedAttributeTypeException("Only numeric and nominal attributes may be "
+                                                        + " manipulated in time series.");
+          }
+        }
+      }
+    }
+    outputFormat.setClassIndex(instanceInfo.classIndex());
+    setOutputFormat(outputFormat);
+    return true;
+  }
+  
+  /**
+   * Creates a new instance the same as one instance (the "destination")
+   * but with some attribute values copied from another instance
+   * (the "source")
+   *
+   * @param source the source instance
+   * @param dest the destination instance
+   * @return the new merged instance
+   */
+  protected Instance mergeInstances(Instance source, Instance dest) {
+
+    Instances outputFormat = outputFormatPeek();
+    double[] vals = new double[outputFormat.numAttributes()];
+    for(int i = 0; i < vals.length; i++) {
+      if ((i != outputFormat.classIndex()) && (m_SelectedCols.isInRange(i))) {
+        if (source != null) {
+          vals[i] = source.value(i);
+        } else {
+          vals[i] = Utils.missingValue();
+        }
+      } else {
+        vals[i] = dest.value(i);
+      }
+    }
+    Instance inst = null;
+    if (dest instanceof SparseInstance) {
+      inst = new SparseInstance(dest.weight(), vals);
+    } else {
+      inst = new DenseInstance(dest.weight(), vals);
+    }
+    inst.setDataset(dest.dataset());
+    return inst;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new TimeSeriesTranslate(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Wavelet.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Wavelet.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Wavelet.java	(revision 29)
@@ -0,0 +1,712 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Wavelet.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance; 
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SelectedTag;
+import weka.core.Tag;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.filters.Filter;
+import weka.filters.MultiFilter;
+import weka.filters.SimpleBatchFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter for wavelet transformation.<br/>
+ * <br/>
+ * For more information see:<br/>
+ * <br/>
+ * Wikipedia (2004). Discrete wavelet transform.<br/>
+ * <br/>
+ * Kristian Sandberg (2000). The Haar wavelet transform. University of Colorado at Boulder, USA.
+ * <p/>
+ <!-- globalinfo-end -->
+ *
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;misc{Wikipedia2004,
+ *    author = {Wikipedia},
+ *    title = {Discrete wavelet transform},
+ *    year = {2004},
+ *    HTTP = {http://en.wikipedia.org/wiki/Discrete_wavelet_transform}
+ * }
+ * 
+ * &#64;misc{Sandberg2000,
+ *    address = {University of Colorado at Boulder, USA},
+ *    author = {Kristian Sandberg},
+ *    institution = {Dept. of Applied Mathematics},
+ *    title = {The Haar wavelet transform},
+ *    year = {2000},
+ *    HTTP = {http://amath.colorado.edu/courses/5720/2000Spr/Labs/Haar/haar.html}
+ * }
+ * </pre>
+ * <p/>
+ <!-- technical-bibtex-end -->
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -A &lt;Haar&gt;
+ *  The algorithm to use.
+ *  (default: HAAR)</pre>
+ * 
+ * <pre> -P &lt;Zero&gt;
+ *  The padding to use.
+ *  (default: ZERO)</pre>
+ * 
+ * <pre> -F &lt;filter specification&gt;
+ *  The filter to use as preprocessing step (classname and options).
+ *  (default: MultiFilter with ReplaceMissingValues and Normalize)</pre>
+ * 
+ * <pre> 
+ * Options specific to filter weka.filters.MultiFilter ('-F'):
+ * </pre>
+ * 
+ * <pre> -D
+ *  Turns on output of debugging information.</pre>
+ * 
+ * <pre> -F &lt;classname [options]&gt;
+ *  A filter to apply (can be specified multiple times).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $
+ */
+public class Wavelet
+  extends SimpleBatchFilter 
+  implements TechnicalInformationHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -3335106965521265631L;
+
+  /** the type of algorithm: Haar wavelet */
+  public static final int ALGORITHM_HAAR = 0;
+  /** the types of algorithm */
+  public static final Tag[] TAGS_ALGORITHM = {
+    new Tag(ALGORITHM_HAAR, "Haar")
+  };
+
+  /** the type of padding: Zero padding */
+  public static final int PADDING_ZERO = 0;
+  /** the types of padding */
+  public static final Tag[] TAGS_PADDING = {
+    new Tag(PADDING_ZERO, "Zero")
+  };
+
+  /** an optional filter for preprocessing of the data */
+  protected Filter m_Filter = null;
+  
+  /** the type of algorithm */
+  protected int m_Algorithm = ALGORITHM_HAAR;
+  
+  /** the type of padding */
+  protected int m_Padding = PADDING_ZERO;
+  
+  /**
+   * default constructor
+   */
+  public Wavelet() {
+    super();
+    
+    m_Filter = new MultiFilter();
+    ((MultiFilter) m_Filter).setFilters(
+	new Filter[]{
+	    new weka.filters.unsupervised.attribute.ReplaceMissingValues(),
+	    new weka.filters.unsupervised.attribute.Normalize()
+	    });
+  }
+  
+  /**
+   * Returns a string describing this classifier.
+   *
+   * @return      a description of the classifier suitable for
+   *              displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter for wavelet transformation.\n\n"
+      + "For more information see:\n\n"
+      + getTechnicalInformation().toString();
+  }
+
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation 	result;
+    TechnicalInformation 	additional;
+    
+    result = new TechnicalInformation(Type.MISC);
+    result.setValue(Field.AUTHOR, "Wikipedia");
+    result.setValue(Field.YEAR, "2004");
+    result.setValue(Field.TITLE, "Discrete wavelet transform");
+    result.setValue(Field.HTTP, "http://en.wikipedia.org/wiki/Discrete_wavelet_transform");
+    
+    additional = result.add(Type.MISC);
+    additional.setValue(Field.AUTHOR, "Kristian Sandberg");
+    additional.setValue(Field.YEAR, "2000");
+    additional.setValue(Field.TITLE, "The Haar wavelet transform");
+    additional.setValue(Field.INSTITUTION, "Dept. of Applied Mathematics");
+    additional.setValue(Field.ADDRESS, "University of Colorado at Boulder, USA");
+    additional.setValue(Field.HTTP, "http://amath.colorado.edu/courses/5720/2000Spr/Labs/Haar/haar.html");
+    
+    return result;
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector		result;
+    Enumeration		enm;
+    String		param;
+    SelectedTag		tag;
+    int			i;
+
+    result = new Vector();
+
+    enm = super.listOptions();
+    while (enm.hasMoreElements())
+      result.addElement(enm.nextElement());
+
+    param = "";
+    for (i = 0; i < TAGS_ALGORITHM.length; i++) {
+      if (i > 0)
+	param += "|";
+      tag = new SelectedTag(TAGS_ALGORITHM[i].getID(), TAGS_ALGORITHM);
+      param += tag.getSelectedTag().getReadable();
+    }
+    result.addElement(new Option(
+	"\tThe algorithm to use.\n"
+	+ "\t(default: HAAR)",
+	"A", 1, "-A <" + param + ">"));
+
+    param = "";
+    for (i = 0; i < TAGS_PADDING.length; i++) {
+      if (i > 0)
+	param += "|";
+      tag = new SelectedTag(TAGS_PADDING[i].getID(), TAGS_PADDING);
+      param += tag.getSelectedTag().getReadable();
+    }
+    result.addElement(new Option(
+	"\tThe padding to use.\n"
+	+ "\t(default: ZERO)",
+	"P", 1, "-P <" + param + ">"));
+
+    result.addElement(new Option(
+	"\tThe filter to use as preprocessing step (classname and options).\n"
+	+ "\t(default: MultiFilter with ReplaceMissingValues and Normalize)",
+	"F", 1, "-F <filter specification>"));
+
+    if (getFilter() instanceof OptionHandler) {
+      result.addElement(new Option(
+	  "",
+	  "", 0, "\nOptions specific to filter "
+	  + getFilter().getClass().getName() + " ('-F'):"));
+      
+      enm = ((OptionHandler) getFilter()).listOptions();
+      while (enm.hasMoreElements())
+	result.addElement(enm.nextElement());
+    }
+
+    return result.elements();
+  }
+
+  /**
+   * returns the options of the current setup
+   *
+   * @return      the current options
+   */
+  public String[] getOptions() {
+    int       i;
+    Vector    result;
+    String[]  options;
+
+    result = new Vector();
+    options = super.getOptions();
+    for (i = 0; i < options.length; i++)
+      result.add(options[i]);
+
+    result.add("-A");
+    result.add("" + getAlgorithm().getSelectedTag().getReadable());
+
+    result.add("-P");
+    result.add("" + getPadding().getSelectedTag().getReadable());
+
+    result.add("-F");
+    if (getFilter() instanceof OptionHandler)
+      result.add(
+	  getFilter().getClass().getName() 
+	+ " " 
+	+ Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
+    else
+      result.add(
+	  getFilter().getClass().getName());
+
+    return (String[]) result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -A &lt;Haar&gt;
+   *  The algorithm to use.
+   *  (default: HAAR)</pre>
+   * 
+   * <pre> -P &lt;Zero&gt;
+   *  The padding to use.
+   *  (default: ZERO)</pre>
+   * 
+   * <pre> -F &lt;filter specification&gt;
+   *  The filter to use as preprocessing step (classname and options).
+   *  (default: MultiFilter with ReplaceMissingValues and Normalize)</pre>
+   * 
+   * <pre> 
+   * Options specific to filter weka.filters.MultiFilter ('-F'):
+   * </pre>
+   * 
+   * <pre> -D
+   *  Turns on output of debugging information.</pre>
+   * 
+   * <pre> -F &lt;classname [options]&gt;
+   *  A filter to apply (can be specified multiple times).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if the option setting fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    String[]	tmpOptions;
+    Filter	filter;
+
+    super.setOptions(options);
+
+    tmpStr = Utils.getOption("A", options);
+    if (tmpStr.length() != 0)
+      setAlgorithm(new SelectedTag(tmpStr, TAGS_ALGORITHM));
+    else
+      setAlgorithm(new SelectedTag(ALGORITHM_HAAR, TAGS_ALGORITHM));
+
+    tmpStr = Utils.getOption("P", options);
+    if (tmpStr.length() != 0)
+      setPadding(new SelectedTag(tmpStr, TAGS_PADDING));
+    else
+      setPadding(new SelectedTag(PADDING_ZERO, TAGS_PADDING));
+
+    tmpStr     = Utils.getOption("F", options);
+    tmpOptions = Utils.splitOptions(tmpStr);
+    if (tmpOptions.length != 0) {
+      tmpStr        = tmpOptions[0];
+      tmpOptions[0] = "";
+      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
+    }
+    else {
+      filter = new MultiFilter();
+      ((MultiFilter) filter).setFilters(
+	  new Filter[]{
+	      new weka.filters.unsupervised.attribute.ReplaceMissingValues(),
+	      new weka.filters.unsupervised.attribute.Normalize()
+	      });
+      setFilter(filter);
+    }
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String filterTipText() {
+    return "The preprocessing filter to use.";
+  }
+
+  /**
+   * Set the preprocessing filter (only used for setup).
+   *
+   * @param value	the preprocessing filter.
+   */
+  public void setFilter(Filter value) {
+    m_Filter = value;
+  }
+
+  /**
+   * Get the preprocessing filter.
+   *
+   * @return 		the preprocessing filter
+   */
+  public Filter getFilter() {
+    return m_Filter;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String algorithmTipText() {
+    return "Sets the type of algorithm to use.";
+  }
+
+  /**
+   * Sets the type of algorithm to use 
+   *
+   * @param value 	the algorithm type
+   */
+  public void setAlgorithm(SelectedTag value) {
+    if (value.getTags() == TAGS_ALGORITHM) {
+      m_Algorithm = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of algorithm to use 
+   *
+   * @return 		the current algorithm type.
+   */
+  public SelectedTag getAlgorithm() {
+    return new SelectedTag(m_Algorithm, TAGS_ALGORITHM);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String paddingTipText() {
+    return "Sets the type of padding to use.";
+  }
+
+  /**
+   * Sets the type of Padding to use 
+   *
+   * @param value 	the Padding type
+   */
+  public void setPadding(SelectedTag value) {
+    if (value.getTags() == TAGS_PADDING) {
+      m_Padding = value.getSelectedTag().getID();
+    }
+  }
+
+  /**
+   * Gets the type of Padding to use 
+   *
+   * @return 		the current Padding type.
+   */
+  public SelectedTag getPadding() {
+    return new SelectedTag(m_Padding, TAGS_PADDING);
+  }
+
+  /**
+   * returns the next bigger number that's a power of 2. If the number is 
+   * already a power of 2 then this will be returned. The number will be at
+   * least 2^2..
+   * 
+   * @param n		the number to start from
+   * @return		the next bigger number
+   */
+  protected static int nextPowerOf2(int n) {
+    int		exp;
+    
+    exp = (int) StrictMath.ceil(StrictMath.log(n) / StrictMath.log(2.0));
+    exp = StrictMath.max(2, exp);
+    
+    return (int) StrictMath.pow(2, exp);
+  }
+  
+  /**
+   * pads the data to conform to the necessary number of attributes
+   * 
+   * @param data	the data to pad
+   * @return		the padded data
+   */
+  protected Instances pad(Instances data) {
+    Instances 		result;
+    int 		i;
+    int			n;
+    String 		prefix;
+    int			numAtts;
+    boolean		isLast;
+    int			index;
+    Vector<Integer>	padded;
+    int[]		indices;
+    FastVector		atts;
+
+    // determine number of padding attributes
+    switch (m_Padding) {
+      case PADDING_ZERO:
+	if (data.classIndex() > -1)
+	  numAtts = (nextPowerOf2(data.numAttributes() - 1) + 1) - data.numAttributes();
+	else
+	  numAtts = nextPowerOf2(data.numAttributes()) - data.numAttributes();
+	break;
+	
+      default:
+	throw new IllegalStateException(
+	    "Padding " + new SelectedTag(m_Algorithm, TAGS_PADDING) 
+	    + " not implemented!");
+    }
+
+    result = new Instances(data);
+    prefix = getAlgorithm().getSelectedTag().getReadable();
+
+    // any padding necessary?
+    if (numAtts > 0) {
+      // add padding attributes
+      isLast = (data.classIndex() == data.numAttributes() - 1);
+      padded = new Vector<Integer>();
+      for (i = 0; i < numAtts; i++) {
+	if (isLast)
+	  index = result.numAttributes() - 1;
+	else
+	  index = result.numAttributes();
+	
+	result.insertAttributeAt(
+	    new Attribute(prefix + "_padding_" + (i+1)),
+	    index);
+	
+	// record index
+	padded.add(new Integer(index));
+      }
+      
+      // get padded indices
+      indices = new int[padded.size()];
+      for (i = 0; i < padded.size(); i++)
+	indices[i] = padded.get(i);
+      
+      // determine number of padding attributes
+      switch (m_Padding) {
+	case PADDING_ZERO:
+	  for (i = 0; i < result.numInstances(); i++) {
+	    for (n = 0; n < indices.length; n++)
+	      result.instance(i).setValue(indices[n], 0);
+	  }
+	  break;
+      }
+    }
+
+    // rename all attributes apart from class
+    data = result;
+    atts = new FastVector();
+    n = 0;
+    for (i = 0; i < data.numAttributes(); i++) {
+      n++;
+      if (i == data.classIndex())
+	atts.addElement((Attribute) data.attribute(i).copy());
+      else
+	atts.addElement(new Attribute(prefix + "_" + n));
+    }
+
+    // create new dataset
+    result = new Instances(data.relationName(), atts, data.numInstances());
+    result.setClassIndex(data.classIndex());
+    for (i = 0; i < data.numInstances(); i++)
+      result.add(new DenseInstance(1.0, data.instance(i).toDoubleArray()));
+    
+    return result;
+  }
+  
+  /**
+   * Determines the output format based on the input format and returns 
+   * this. In case the output format cannot be returned immediately, i.e.,
+   * immediateOutputFormat() returns false, then this method will be called
+   * from batchFinished().
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   * @see   #hasImmediateOutputFormat()
+   * @see   #batchFinished()
+   */
+  protected Instances determineOutputFormat(Instances inputFormat) 
+    throws Exception {
+
+    return pad(new Instances(inputFormat, 0));
+  }
+  
+  /**
+   * processes the instances using the HAAR algorithm
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   */
+  protected Instances processHAAR(Instances instances) throws Exception {
+    Instances	result;
+    int		i;
+    int		n;
+    int		j;
+    int		clsIdx;
+    double[]	oldVal;
+    double[]	newVal;
+    int		level;
+    int		length;
+    double[]	clsVal;
+    Attribute	clsAtt;
+
+    clsIdx  = instances.classIndex();
+    clsVal  = null;
+    clsAtt  = null;
+    if (clsIdx > -1) {
+      clsVal  = instances.attributeToDoubleArray(clsIdx);
+      clsAtt  = (Attribute) instances.classAttribute().copy();
+      instances.setClassIndex(-1);
+      instances.deleteAttributeAt(clsIdx);
+    }
+    result = new Instances(instances, 0);
+    level  = (int) StrictMath.ceil(
+			StrictMath.log(instances.numAttributes())
+			/ StrictMath.log(2.0));
+    
+    for (i = 0; i < instances.numInstances(); i++) {
+      oldVal = instances.instance(i).toDoubleArray();
+      newVal = new double[oldVal.length];
+      
+      for (n = level; n > 0; n--) {
+	length = (int) StrictMath.pow(2, n - 1);
+	
+	for (j = 0; j < length; j++) {
+	  newVal[j]          = (oldVal[j*2] + oldVal[j*2 + 1]) / StrictMath.sqrt(2);
+	  newVal[j + length] = (oldVal[j*2] - oldVal[j*2 + 1]) / StrictMath.sqrt(2);
+	}
+	
+	System.arraycopy(newVal, 0, oldVal, 0, newVal.length);
+      }
+      
+      // add new transformed instance
+      result.add(new DenseInstance(1, newVal));
+    }
+
+    // add class again
+    if (clsIdx > -1) {
+      result.insertAttributeAt(clsAtt, clsIdx);
+      result.setClassIndex(clsIdx);
+      for (i = 0; i < clsVal.length; i++)
+	result.instance(i).setClassValue(clsVal[i]);
+    }
+    
+    return result;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attribute
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    if (!isFirstBatchDone())
+      m_Filter.setInputFormat(instances);
+    instances = Filter.useFilter(instances, m_Filter);
+    
+    switch (m_Algorithm) {
+      case ALGORITHM_HAAR:
+	return processHAAR(pad(instances));
+      default:
+	throw new IllegalStateException(
+	    "Algorithm type '" + m_Algorithm + "' is not recognized!");
+    }
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * runs the filter with the given arguments
+   *
+   * @param args      the commandline arguments
+   */
+  public static void main(String[] args) {
+    runFilter(new Wavelet(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/NonSparseToSparse.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/NonSparseToSparse.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/NonSparseToSparse.java	(revision 29)
@@ -0,0 +1,319 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NonSparseToSparse.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that converts all incoming instances into sparse format.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class NonSparseToSparse 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4694489111366063852L;
+  
+  protected boolean m_encodeMissingAsZero = false;
+  
+  protected boolean m_insertDummyNominalFirstValue = false;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An instance filter that converts all incoming instances"
+      + " into sparse format.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result;
+    
+    result = new Vector();
+    result.add(new Option("\tTreat missing values as zero.",
+        "M", 0, "-M"));
+    result.add(new Option("\tAdd a dummy first value for nominal attributes.",
+        "F", 0, "-F"));
+    
+    return result.elements();
+  }
+  
+  public void setOptions(String[] options) throws Exception {
+    m_encodeMissingAsZero = Utils.getFlag('M', options);
+    m_insertDummyNominalFirstValue = Utils.getFlag('F', options);
+  }
+  
+  public String[] getOptions() {
+    Vector result = new Vector();
+    
+    if (m_encodeMissingAsZero) {
+      result.add("-M");      
+    }
+    
+    if (m_insertDummyNominalFirstValue) {
+      result.add("-F");
+    }
+    
+    return (String[]) result.toArray(new String[result.size()]);
+  }
+  
+  /**
+   * Set whether missing values should be treated in the same
+   * way as zeros
+   * 
+   * @param m true if missing values are to be treated the same
+   * as zeros
+   */
+  public void setTreatMissingValuesAsZero(boolean m) {
+    m_encodeMissingAsZero = m;
+  }
+  
+  /**
+   * Get whether missing values are to be treated in the same
+   * way as zeros
+   * 
+   * @return true if missing values are to be treated in the
+   * same way as zeros
+   */
+  public boolean getTreatMissingValuesAsZero() {
+    return m_encodeMissingAsZero;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return            tip text for this property suitable for
+   *                    displaying in the explorer/experimenter gui
+   */
+  public String treatMissingValuesAsZeroTipText() {
+    return "Treat missing values in the same way as zeros.";
+  }
+  
+  /**
+   * Set whether to insert a dummy first value in the definition
+   * for each nominal attribute or not.
+   * 
+   * @param d true if a dummy value is to be inserted for
+   * each nominal attribute.
+   */
+  public void setInsertDummyNominalFirstValue(boolean d) {
+    m_insertDummyNominalFirstValue = d;
+  }
+  
+  /**
+   * Get whether a dummy first value will be inserted in the definition
+   * of each nominal attribute.
+   * 
+   * @return true if a dummy first value will be inserted for each nominal
+   * attribute.
+   */
+  public boolean getInsertDummyNominalFirstValue() {
+    return m_insertDummyNominalFirstValue;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return            tip text for this property suitable for
+   *                    displaying in the explorer/experimenter gui
+   */
+  public String insertDummyNominalFirstValueTipText() {
+    return "Insert a dummy value before the first declared value "
+    + "for all nominal attributes. Useful when converting market "
+    + "basket data that has been encoded for Apriori to sparse format. "
+    + "Typically used in conjuction with treat missing values as zero.";
+    
+    		
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if format cannot be processed
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    Instances instNew = instanceInfo;
+    
+    if (m_insertDummyNominalFirstValue) {
+      FastVector atts = new FastVector();
+      for (int i = 0; i < instanceInfo.numAttributes(); i++) {
+        if (instanceInfo.attribute(i).isNominal()) {
+          FastVector labels = new FastVector();
+          labels.addElement("_d");
+          for (int j = 0; j < instanceInfo.attribute(j).numValues(); j++) {
+            labels.addElement(instanceInfo.attribute(i).value(j));
+          }
+          Attribute newAtt = new Attribute(instanceInfo.attribute(i).name(), 
+              labels);
+          atts.addElement(newAtt);
+        } else {
+          atts.addElement(instanceInfo.attribute(i));
+        }
+      }
+      instNew = new Instances(instanceInfo.relationName(), atts, 0);
+    }
+    
+    setOutputFormat(instNew);
+    return true;
+  }
+
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance.
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    Instance newInstance = null;
+    
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    
+    if (m_encodeMissingAsZero && !m_insertDummyNominalFirstValue) {
+      Instance tempInst = (Instance)instance.copy();
+      tempInst.setDataset(getInputFormat());
+      
+      for (int i = 0; i < tempInst.numAttributes(); i++) {
+        if (tempInst.isMissing(i)) {
+          tempInst.setValue(i, 0);
+        }
+      }
+      instance = tempInst;
+    }
+    
+    if (m_insertDummyNominalFirstValue) {
+      double[] values = instance.toDoubleArray();      
+      for (int i = 0; i < instance.numAttributes(); i++) {
+        if (instance.attribute(i).isNominal()) {
+          if (!Utils.isMissingValue(values[i])) {
+            values[i]++;
+          }
+        }
+        if (m_encodeMissingAsZero && Utils.isMissingValue(values[i])) {
+          values[i] = 0;
+        }
+      }
+      newInstance = new SparseInstance(instance.weight(), values);
+      newInstance.setDataset(getOutputFormat());
+      push(newInstance);
+    } else {
+      newInstance = new SparseInstance(instance);
+      newInstance.setDataset(instance.dataset());
+      push(newInstance);
+    }
+    
+    /*Instance inst = new SparseInstance(instance);
+    inst.setDataset(instance.dataset());
+    push(inst); */
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new NonSparseToSparse(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Normalize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Normalize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Normalize.java	(revision 29)
@@ -0,0 +1,322 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Normalize.java
+ *    Copyright (C) 2003 Prados Julien
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that normalize instances considering only numeric attributes and ignoring class index.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Specify the norm that each instance must have (default 1.0)</pre>
+ * 
+ * <pre> -L &lt;num&gt;
+ *  Specify L-norm to use (default 2.0)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Julien Prados
+ * @version $Revision: 5499 $
+ */
+public class Normalize 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7947971807522917395L;
+
+  /** The norm that each instance must have at the end */
+  protected double m_Norm = 1.0;
+
+  /** The L-norm to use */
+  protected double m_LNorm = 2.0;
+
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An instance filter that normalize instances considering only numeric "+
+           "attributes and ignoring class index.";
+  }  
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String LNormTipText() { 
+    return "The LNorm to use.";
+  }
+  
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String normTipText() { 
+    return "The norm of the instances after normalization.";
+  }
+  
+  
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector newVector = new Vector(2);
+    newVector.addElement(new Option(
+              "\tSpecify the norm that each instance must have (default 1.0)",
+              "N", 1, "-N <num>"));
+    newVector.addElement(new Option(
+              "\tSpecify L-norm to use (default 2.0)",
+              "L", 1, "-L <num>"));
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -N &lt;num&gt;
+   *  Specify the norm that each instance must have (default 1.0)</pre>
+   * 
+   * <pre> -L &lt;num&gt;
+   *  Specify L-norm to use (default 2.0)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String normString = Utils.getOption('N', options);
+    if (normString.length() != 0) {
+      setNorm(Double.parseDouble(normString));
+    } else {
+      setNorm(1.0);
+    }
+
+    String lNormString = Utils.getOption('L', options);
+    if (lNormString.length() != 0) {
+      setLNorm(Double.parseDouble(lNormString));
+    } else {
+      setLNorm(2.0);
+    }
+    
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    String [] options = new String [4];
+    int current = 0;
+
+    options[current++] = "-N"; 
+    options[current++] = "" + getNorm();
+
+    options[current++] = "-L"; 
+    options[current++] = "" + getLNorm();
+    
+    return options;
+  }
+
+  
+  /**
+   * Get the instance's Norm.
+   *
+   * @return the Norm
+   */
+  public double getNorm() {
+    return m_Norm;
+  }
+  
+  /**
+   * Set the norm of the instances
+   *
+   * @param newNorm the norm to wich the instances must be set
+   */
+  public void setNorm(double newNorm) {
+    m_Norm = newNorm;
+  }
+  
+  /**
+   * Get the L Norm used.
+   *
+   * @return the L-norm used
+   */
+  public double getLNorm() {
+    return m_LNorm;
+  }
+  
+  /**
+   * Set the L-norm to used
+   *
+   * @param newLNorm the L-norm
+   */
+  public void setLNorm(double newLNorm) {
+    m_LNorm = newLNorm;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if format cannot be processed
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    /* CHECK REMOVE, BECAUSE THE FILTER IS APPLIED ONLY TO NUMERIC ATTRIBUTE
+     * IN input()
+    //check if all attributes are numeric
+    for(int i=0;i<instanceInfo.numAttributes();i++){
+        if (!instanceInfo.attribute(i).isNumeric() 
+            && instanceInfo.classIndex() != i){
+            throw Exception("All the attributes must be numeric");
+        }
+    }
+    */
+      
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been defined.
+   */
+  public boolean input(Instance instance) throws Exception {
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance inst = (Instance) instance.copy();
+
+    //compute norm of inst
+    double iNorm = 0;
+    for(int i=0;i<getInputFormat().numAttributes();i++){
+        if (getInputFormat().classIndex() == i) continue;
+        if (!getInputFormat().attribute(i).isNumeric()) continue;
+        iNorm += Math.pow(Math.abs(inst.value(i)),getLNorm());
+    }
+    iNorm = Math.pow(iNorm,1.0/getLNorm());
+    
+    //normalize inst
+    for(int i=0;i<getInputFormat().numAttributes();i++){
+        if (getInputFormat().classIndex() == i) continue;
+        if (!getInputFormat().attribute(i).isNumeric()) continue;
+        inst.setValue(i,inst.value(i)/iNorm*getNorm());
+    }
+    
+    push(inst);
+    return true;
+  }  
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Normalize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Randomize.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Randomize.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Randomize.java	(revision 29)
@@ -0,0 +1,288 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Randomize.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Randomly shuffles the order of instances passed through it. The random number generator is reset with the seed value whenever a new set of instances is passed in.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 42)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5499 $
+ */
+public class Randomize 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 8854479785121877582L;
+
+  /** The random number seed */
+  protected int m_Seed = 42;
+
+  /** The current random number generator */
+  protected Random m_Random;
+  
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Randomly shuffles the order of instances passed through it. "
+      + "The random number generator is reset with the seed value whenever "
+      + "a new set of instances is passed in.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(1);
+
+    newVector.addElement(new Option(
+              "\tSpecify the random number seed (default 42)",
+              "S", 1, "-S <num>"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 42)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    
+    String seedString = Utils.getOption('S', options);
+    if (seedString.length() != 0) {
+      setRandomSeed(Integer.parseInt(seedString));
+    } else {
+      setRandomSeed(42);
+    }
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [2];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = "" + getRandomSeed();
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "Seed for the random number generator.";
+  }
+
+  /**
+   * Get the random number generator seed value.
+   *
+   * @return random number generator seed value.
+   */
+  public int getRandomSeed() {
+    
+    return m_Seed;
+  }
+  
+  /**
+   * Set the random number generator seed value.
+   *
+   * @param newRandomSeed value to use as the random number generator seed.
+   */
+  public void setRandomSeed(int newRandomSeed) {
+    
+    m_Seed = newRandomSeed;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+  
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if format cannot be processed
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_Random = new Random(m_Seed);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If
+   * the filter requires all instances prior to filtering, output()
+   * may now be called to retrieve the filtered instances. Any
+   * subsequent instances filtered should be filtered based on setting
+   * obtained from the first batch (unless the setInputFormat has been
+   * re-assigned or new options have been set). This 
+   * implementation randomizes all the instances received in the batch.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input format has been set. 
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isFirstBatchDone()) {
+      getInputFormat().randomize(m_Random);
+    }
+    for (int i = 0; i < getInputFormat().numInstances(); i++) {
+      push(getInputFormat().instance(i));
+    }
+    flushInput();
+    
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Randomize(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFolds.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFolds.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFolds.java	(revision 29)
@@ -0,0 +1,474 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveFolds.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * This filter takes a dataset and outputs a specified fold for cross validation. If you want the folds to be stratified use the supervised version.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -V
+ *  Specifies if inverse of selection is to be output.
+ * </pre>
+ * 
+ * <pre> -N &lt;number of folds&gt;
+ *  Specifies number of folds dataset is split into. 
+ *  (default 10)
+ * </pre>
+ * 
+ * <pre> -F &lt;fold&gt;
+ *  Specifies which fold is selected. (default 1)
+ * </pre>
+ * 
+ * <pre> -S &lt;seed&gt;
+ *  Specifies random number seed. (default 0, no randomizing)
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5499 $ 
+*/
+public class RemoveFolds 
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 8220373305559055700L;
+  
+  /** Indicates if inverse of selection is to be output. */
+  private boolean m_Inverse = false;
+
+  /** Number of folds to split dataset into */
+  private int m_NumFolds = 10;
+
+  /** Fold to output */
+  private int m_Fold = 1;
+
+  /** Random number seed. */
+  private long m_Seed = 0;
+
+  /**
+   * Gets an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+	      "\tSpecifies if inverse of selection is to be output.\n",
+	      "V", 0, "-V"));
+
+    newVector.addElement(new Option(
+              "\tSpecifies number of folds dataset is split into. \n"
+	      + "\t(default 10)\n",
+              "N", 1, "-N <number of folds>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies which fold is selected. (default 1)\n",
+	      "F", 1, "-F <fold>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies random number seed. (default 0, no randomizing)\n",
+	      "S", 1, "-S <seed>"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -V
+   *  Specifies if inverse of selection is to be output.
+   * </pre>
+   * 
+   * <pre> -N &lt;number of folds&gt;
+   *  Specifies number of folds dataset is split into. 
+   *  (default 10)
+   * </pre>
+   * 
+   * <pre> -F &lt;fold&gt;
+   *  Specifies which fold is selected. (default 1)
+   * </pre>
+   * 
+   * <pre> -S &lt;seed&gt;
+   *  Specifies random number seed. (default 0, no randomizing)
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    setInvertSelection(Utils.getFlag('V', options));
+    String numFolds = Utils.getOption('N', options);
+    if (numFolds.length() != 0) {
+      setNumFolds(Integer.parseInt(numFolds));
+    } else {
+      setNumFolds(10);
+    }
+    String fold = Utils.getOption('F', options);
+    if (fold.length() != 0) {
+      setFold(Integer.parseInt(fold));
+    } else {
+      setFold(1);
+    }
+    String seed = Utils.getOption('S', options);
+    if (seed.length() != 0) {
+      setSeed(Integer.parseInt(seed));
+    } else {
+      setSeed(0);
+    }
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [8];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = "" + getSeed();
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    options[current++] = "-N"; options[current++] = "" + getNumFolds();
+    options[current++] = "-F"; options[current++] = "" + getFold();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "This filter takes a dataset and outputs a specified fold for "
+      + "cross validation. If you want the folds to be stratified use the "
+      + "supervised version.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Whether to invert the selection.";
+  }
+
+  /**
+   * Gets if selection is to be inverted.
+   *
+   * @return true if the selection is to be inverted
+   */
+  public boolean getInvertSelection() {
+
+    return m_Inverse;
+  }
+
+  /**
+   * Sets if selection is to be inverted.
+   *
+   * @param inverse true if inversion is to be performed
+   */
+  public void setInvertSelection(boolean inverse) {
+    
+    m_Inverse = inverse;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+
+    return "The number of folds to split the dataset into.";
+  }
+
+  /**
+   * Gets the number of folds in which dataset is to be split into.
+   * 
+   * @return the number of folds the dataset is to be split into.
+   */
+  public int getNumFolds() {
+
+    return m_NumFolds;
+  }
+
+  /**
+   * Sets the number of folds the dataset is split into. If the number
+   * of folds is zero, it won't split it into folds. 
+   *
+   * @param numFolds number of folds dataset is to be split into
+   * @throws IllegalArgumentException if number of folds is negative
+   */
+  public void setNumFolds(int numFolds) {
+
+    if (numFolds < 0) {
+      throw new IllegalArgumentException("Number of folds has to be positive or zero.");
+    }
+    m_NumFolds = numFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String foldTipText() {
+
+    return "The fold which is selected.";
+  }
+
+  /**
+   * Gets the fold which is selected.
+   *
+   * @return the fold which is selected
+   */
+  public int getFold() {
+
+    return m_Fold;
+  }
+
+  /**
+   * Selects a fold.
+   *
+   * @param fold the fold to be selected.
+   * @throws IllegalArgumentException if fold's index is smaller than 1
+   */
+  public void setFold(int fold) {
+
+    if (fold < 1) {
+      throw new IllegalArgumentException("Fold's index has to be greater than 0.");
+    }
+    m_Fold = fold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String seedTipText() {
+
+    return "the random number seed for shuffling the dataset. If seed is negative, shuffling will not be performed.";
+  }
+
+  /**
+   * Gets the random number seed used for shuffling the dataset.
+   *
+   * @return the random number seed
+   */
+  public long getSeed() {
+
+    return m_Seed;
+  }
+
+  /**
+   * Sets the random number seed for shuffling the dataset. If seed
+   * is negative, shuffling won't be performed.
+   *
+   * @param seed the random number seed
+   */
+  public void setSeed(long seed) {
+    
+    m_Seed = seed;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true because outputFormat can be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */  
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    if ((m_NumFolds > 0) && (m_NumFolds < m_Fold)) {
+      throw new IllegalArgumentException("Fold has to be smaller or equal to "+
+                                         "number of folds.");
+    }
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is
+   * finished. Output() may now be called to retrieve the filtered
+   * instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    
+    Instances instances;
+
+    if (!isFirstBatchDone()) {
+      if (m_Seed > 0) {
+	// User has provided a random number seed.
+	getInputFormat().randomize(new Random(m_Seed));
+      }
+      // Push instances for output into output queue
+
+      // Select out a fold
+      if (!m_Inverse) {
+	instances = getInputFormat().testCV(m_NumFolds, m_Fold - 1);
+      } else {
+	instances = getInputFormat().trainCV(m_NumFolds, m_Fold - 1);
+      }
+    }
+    else {
+      instances = getInputFormat();
+    }
+    
+    flushInput();
+
+    for (int i = 0; i < instances.numInstances(); i++) {
+      push(instances.instance(i));
+    }
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveFolds(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFrequentValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFrequentValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveFrequentValues.java	(revision 29)
@@ -0,0 +1,646 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveFrequentValues.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Determines which values (frequent or infrequent ones) of an (nominal) attribute are retained and filters the instances accordingly. In case of values with the same frequency, they are kept in the way they appear in the original instances object. E.g. if you have the values "1,2,3,4" with the frequencies "10,5,5,3" and you chose to keep the 2 most common values, the values "1,2" would be returned, since the value "2" comes before "3", even though they have the same frequency.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  Choose attribute to be used for selection.</pre>
+ * 
+ * <pre> -N &lt;num&gt;
+ *  Number of values to retain for the sepcified attribute, 
+ *  i.e. the ones with the most instances (default 2).</pre>
+ * 
+ * <pre> -L
+ *  Instead of values with the most instances the ones with the 
+ *  least are retained.
+ * </pre>
+ * 
+ * <pre> -H
+ *  When selecting on nominal attributes, removes header
+ *  references to excluded values.</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5499 $
+ */
+public class RemoveFrequentValues 
+   extends Filter 
+   implements OptionHandler, UnsupervisedFilter {
+  
+   /** for serialization */
+   static final long serialVersionUID = -2447432930070059511L;
+
+   /** The attribute's index setting. */
+   private SingleIndex m_AttIndex = new SingleIndex("last"); 
+
+   /** the number of values to retain. */
+   protected int m_NumValues = 2; 
+   
+   /** whether to retain values with least instances instead of most. */
+   protected boolean m_LeastValues = false;
+   
+   /** whether to invert the matching sense. */
+   protected boolean m_Invert = false;
+   
+   /** Modify header for nominal attributes? */
+   protected boolean m_ModifyHeader = false;
+   
+   /** If m_ModifyHeader, stores a mapping from old to new indexes */
+   protected int [] m_NominalMapping;
+   
+   /** contains the values to retain */
+   protected HashSet m_Values = null;
+   
+   /**
+    * Returns a string describing this filter
+    * @return a description of the classifier suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String globalInfo() {
+     return 
+         "Determines which values (frequent or infrequent ones) of an "
+       + "(nominal) attribute are retained and filters the instances "
+       + "accordingly. In case of values with the same frequency, they are "
+       + "kept in the way they appear in the original instances object. E.g. "
+       + "if you have the values \"1,2,3,4\" with the frequencies \"10,5,5,3\" "
+       + "and you chose to keep the 2 most common values, the values \"1,2\" "
+       + "would be returned, since the value \"2\" comes before \"3\", even "
+       + "though they have the same frequency.";
+   }
+
+   /**
+    * Returns an enumeration describing the available options.
+    *
+    * @return an enumeration of all the available options.
+    */
+   public Enumeration listOptions() {
+      Vector newVector = new Vector(5);
+
+      newVector.addElement(new Option(
+                "\tChoose attribute to be used for selection.",
+                "C", 1, "-C <num>"));
+      newVector.addElement(new Option(
+                  "\tNumber of values to retain for the sepcified attribute, \n"
+                + "\ti.e. the ones with the most instances (default 2).",
+                "N", 1, "-N <num>"));
+      newVector.addElement(new Option(
+                  "\tInstead of values with the most instances the ones with the \n"
+                + "\tleast are retained.\n",
+                "L", 0, "-L"));
+      newVector.addElement(new Option(
+                  "\tWhen selecting on nominal attributes, removes header\n"
+            	 + "\treferences to excluded values.",
+            	 "H", 0, "-H"));
+      newVector.addElement(new Option(
+            	 "\tInvert matching sense.",
+                "V", 0, "-V"));
+
+      return newVector.elements();
+   }
+
+   /**
+    * Parses a given list of options. <p/>
+    * 
+    <!-- options-start -->
+    * Valid options are: <p/>
+    * 
+    * <pre> -C &lt;num&gt;
+    *  Choose attribute to be used for selection.</pre>
+    * 
+    * <pre> -N &lt;num&gt;
+    *  Number of values to retain for the sepcified attribute, 
+    *  i.e. the ones with the most instances (default 2).</pre>
+    * 
+    * <pre> -L
+    *  Instead of values with the most instances the ones with the 
+    *  least are retained.
+    * </pre>
+    * 
+    * <pre> -H
+    *  When selecting on nominal attributes, removes header
+    *  references to excluded values.</pre>
+    * 
+    * <pre> -V
+    *  Invert matching sense.</pre>
+    * 
+    <!-- options-end -->
+    *
+    * @param options the list of options as an array of strings
+    * @throws Exception if an option is not supported
+    */
+   public void setOptions(String[] options) throws Exception {
+      String attIndex = Utils.getOption('C', options);
+      if (attIndex.length() != 0) {
+         setAttributeIndex(attIndex);
+      } else {
+         setAttributeIndex("last");
+      }
+      
+      String numValues = Utils.getOption('N', options);
+      if (numValues.length() != 0) {
+         setNumValues(Integer.parseInt(numValues));
+      } else {
+         setNumValues(2);
+      }
+      
+      setUseLeastValues(Utils.getFlag('L', options));
+
+      setModifyHeader(Utils.getFlag('H', options));
+
+      setInvertSelection(Utils.getFlag('V', options));
+      
+      if (getInputFormat() != null) {
+         setInputFormat(getInputFormat());
+      }
+   }
+
+   /**
+    * Gets the current settings of the filter.
+    *
+    * @return an array of strings suitable for passing to setOptions
+    */
+   public String[] getOptions() {
+      String [] options = new String [7];
+      int current = 0;
+
+      options[current++] = "-C";
+      options[current++] = "" + (getAttributeIndex());
+      options[current++] = "-N";
+      options[current++] = "" + (getNumValues());
+      if (getUseLeastValues()) {
+        options[current++] = "-H";
+      }
+      if (getModifyHeader()) {
+         options[current++] = "-H";
+      }
+      if (getInvertSelection()) {
+         options[current++] = "-V";
+      }
+      while (current < options.length) {
+        options[current++] = "";
+      }
+      return options;
+   }
+
+   /**
+    * Returns the tip text for this property
+    * @return tip text for this property suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String attributeIndexTipText() {
+     return "Choose attribute to be used for selection (default last).";
+   }
+
+   /**
+    * Get the index of the attribute used.
+    *
+    * @return the index of the attribute
+    */
+   public String getAttributeIndex() {
+     return m_AttIndex.getSingleIndex();
+   }
+
+   /**
+    * Sets index of the attribute used.
+    *
+    * @param attIndex the index of the attribute
+    */
+   public void setAttributeIndex(String attIndex) {
+     m_AttIndex.setSingleIndex(attIndex);
+   }
+
+   /**
+    * Returns the tip text for this property
+    * 
+    * @return tip text for this property suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String numValuesTipText() {
+      return "The number of values to retain.";
+   }
+
+   /**
+    * Gets how many values are retained
+    *
+    * @return how many values are retained
+    */
+   public int getNumValues() {
+      return m_NumValues;
+   }
+
+   /**
+    * Sets how many values are retained  
+    *
+    * @param numValues the number of values to retain
+    */
+   public void setNumValues(int numValues) {
+      m_NumValues = numValues;
+   }
+
+   /**
+    * Returns the tip text for this property
+    * 
+    * @return tip text for this property suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String useLeastValuesTipText() {
+      return "Retains values with least instance instead of most.";
+   }
+
+   /**
+    * Gets whether to use values with least or most instances
+    *
+    * @return true if values with least instances are retained
+    */
+   public boolean getUseLeastValues() {
+      return m_LeastValues;
+   }
+
+   /**
+    * Sets whether to use values with least or most instances  
+    *
+    * @param leastValues whether values with least or most instances are retained
+    */
+   public void setUseLeastValues(boolean leastValues) {
+      m_LeastValues = leastValues;
+   }
+
+   /**
+    * Returns the tip text for this property
+    * @return tip text for this property suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String modifyHeaderTipText() {
+      return "When selecting on nominal attributes, removes header references to "
+      + "excluded values.";
+   }
+   
+   /**
+    * Gets whether the header will be modified when selecting on nominal
+    * attributes.
+    *
+    * @return true if so.
+    */
+   public boolean getModifyHeader() {
+      return m_ModifyHeader;
+   }
+   
+   /**
+    * Sets whether the header will be modified when selecting on nominal
+    * attributes.
+    *
+    * @param newModifyHeader true if so.
+    */
+   public void setModifyHeader(boolean newModifyHeader) {
+      m_ModifyHeader = newModifyHeader;
+   }
+   
+   /**
+    * Returns the tip text for this property
+    * 
+    * @return tip text for this property suitable for
+    * displaying in the explorer/experimenter gui
+    */
+   public String invertSelectionTipText() {
+      return "Invert matching sense.";
+   }
+
+   /**
+    * Get whether the supplied columns are to be removed or kept
+    *
+    * @return true if the supplied columns will be kept
+    */
+   public boolean getInvertSelection() {
+      return m_Invert;
+   }
+
+   /**
+    * Set whether selected values should be removed or kept. If true the 
+    * selected values are kept and unselected values are deleted. 
+    *
+    * @param invert the new invert setting
+    */
+   public void setInvertSelection(boolean invert) {
+      m_Invert = invert;
+   }
+
+   /** 
+    * Returns true if selection attribute is nominal.
+    *
+    * @return true if selection attribute is nominal
+    */
+   public boolean isNominal() {
+      if (getInputFormat() == null) {
+         return false;
+      } else {
+         return getInputFormat().attribute(m_AttIndex.getIndex()).isNominal();
+      }
+   }
+   
+   /**
+    * determines the values to retain, it is always at least 1
+    * and up to the maximum number of distinct values
+    * 
+    * @param inst the Instances to determine the values from which are kept  
+    */
+   public void determineValues(Instances inst) {
+      int					i;
+      AttributeStats		stats;
+      int					attIdx;
+      int					min;
+      int					max;
+      int					count;
+
+      m_AttIndex.setUpper(inst.numAttributes() - 1);
+      attIdx = m_AttIndex.getIndex();
+      
+      // init names
+      m_Values = new HashSet();
+      
+      if (inst == null)
+         return;
+      
+      // number of values to retain
+      stats = inst.attributeStats(attIdx);
+      if (m_Invert)
+         count = stats.nominalCounts.length - m_NumValues;
+      else
+         count = m_NumValues;
+      // out of bounds? -> fix
+      if (count < 1)
+         count = 1;  // at least one value!
+      if (count > stats.nominalCounts.length)
+         count = stats.nominalCounts.length;  // at max the existing values
+      
+      // determine min/max occurences
+      Arrays.sort(stats.nominalCounts);
+      if (m_LeastValues) {
+         min = stats.nominalCounts[0];
+         max = stats.nominalCounts[count - 1];
+      }
+      else {
+         min = stats.nominalCounts[(stats.nominalCounts.length - 1) - count + 1];
+         max = stats.nominalCounts[stats.nominalCounts.length - 1];
+      }
+      
+      // add values if they are inside min/max (incl. borders) and not more than count
+      stats = inst.attributeStats(attIdx);
+      for (i = 0; i < stats.nominalCounts.length; i++) {
+         if ( (stats.nominalCounts[i] >= min) && (stats.nominalCounts[i] <= max) && (m_Values.size() < count) )
+            m_Values.add(inst.attribute(attIdx).value(i));
+      }
+   }
+   
+   /**
+    * modifies the header of the Instances and returns the format w/o 
+    * any instances
+    * 
+    * @param instanceInfo the instances structure to modify
+    * @return the new structure (w/o instances!)
+    */
+   protected Instances modifyHeader(Instances instanceInfo) {
+      instanceInfo = new Instances(getInputFormat(), 0); // copy before modifying
+      Attribute oldAtt = instanceInfo.attribute(m_AttIndex.getIndex());
+      int [] selection = new int[m_Values.size()];
+      Iterator iter = m_Values.iterator();
+      int i = 0;
+      while (iter.hasNext()) {
+         selection[i] = oldAtt.indexOfValue(iter.next().toString());
+         i++;
+      }
+      FastVector newVals = new FastVector();
+      for (i = 0; i < selection.length; i++) {
+         newVals.addElement(oldAtt.value(selection[i]));
+      }
+      instanceInfo.deleteAttributeAt(m_AttIndex.getIndex());
+      instanceInfo.insertAttributeAt(new Attribute(oldAtt.name(), newVals),
+            m_AttIndex.getIndex());
+      m_NominalMapping = new int [oldAtt.numValues()];
+      for (i = 0; i < m_NominalMapping.length; i++) {
+         boolean found = false;
+         for (int j = 0; j < selection.length; j++) {
+            if (selection[j] == i) {
+               m_NominalMapping[i] = j;
+               found = true;
+               break;
+            }
+         }
+         if (!found) {
+            m_NominalMapping[i] = -1;
+         }
+      }
+      
+      return instanceInfo;
+   }
+
+   /** 
+    * Returns the Capabilities of this filter.
+    *
+    * @return            the capabilities of this object
+    * @see               Capabilities
+    */
+   public Capabilities getCapabilities() {
+     Capabilities result = super.getCapabilities();
+     result.disableAll();
+
+     // attributes
+     result.enableAllAttributes();
+     result.enable(Capability.MISSING_VALUES);
+     
+     // class
+     result.enableAllClasses();
+     result.enable(Capability.MISSING_CLASS_VALUES);
+     result.enable(Capability.NO_CLASS);
+     
+     return result;
+   }
+
+   /**
+    * Sets the format of the input instances.
+    *
+    * @param instanceInfo an Instances object containing the input instance
+    * structure (any instances contained in the object are ignored - only the
+    * structure is required).
+    * @return true if the outputFormat can be collected immediately
+    * @throws UnsupportedAttributeTypeException if the specified attribute
+    *         is not nominal.
+    */
+   public boolean setInputFormat(Instances instanceInfo) throws Exception {
+      super.setInputFormat(instanceInfo);
+      
+      m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+
+      if (!isNominal())
+         throw new UnsupportedAttributeTypeException("Can only handle nominal attributes.");
+      
+      m_Values = null;
+         
+      return false;
+   }
+   
+   /**
+    * Set the output format. Takes the currently defined Values to retain and 
+    * m_InputFormat and calls setOutputFormat(Instances) appropriately. 
+    * Those instances that have a value to retain are "push"ed to the output.
+    */
+   protected void setOutputFormat() {
+      Instances      instances;
+      int            i;
+      Instance       instance;
+      
+      if (m_Values == null) {
+         setOutputFormat(null);
+         return;
+      }
+      
+      // get structure
+      if (getModifyHeader())
+         instances = modifyHeader(getInputFormat());
+      else
+         instances = new Instances(getInputFormat(), 0);
+      setOutputFormat(instances);
+      
+      // remove instances with unwanted values, for the others change the values
+      // value if m_ModifyHeader is set
+      for (i = 0; i < getInputFormat().numInstances(); i++) {
+         instance = getInputFormat().instance(i);
+      
+         if (m_Values.contains(instance.stringValue(m_AttIndex.getIndex()))) {
+            if (getModifyHeader()) {
+               instance.setValue(m_AttIndex.getIndex(),
+                     m_NominalMapping[(int)instance.value(m_AttIndex.getIndex())]);
+            }
+            push(instance);
+         }
+      }
+   }
+   
+   /**
+    * Input an instance for filtering. Ordinarily the instance is processed
+    * and made available for output immediately. Some filters require all
+    * instances be read before producing output.
+    *
+    * @param instance the input instance
+    * @return true if the filtered instance may now be
+    * collected with output().
+    * @throws IllegalStateException if no input format has been set.
+    */
+   public boolean input(Instance instance) {
+      if (getInputFormat() == null) {
+         throw new IllegalStateException("No input instance format defined");
+      }
+      
+      if (m_NewBatch) {
+         resetQueue();
+         m_NewBatch = false;
+      }
+
+      if (isFirstBatchDone()) {
+	push(instance);
+	return true;
+      } 
+      else {
+	bufferInput(instance);
+	return false;
+      }
+   }
+
+   /**
+    * Signifies that this batch of input to the filter is finished. If the 
+    * filter requires all instances prior to filtering, output() may now 
+    * be called to retrieve the filtered instances.
+    *
+    * @return true if there are instances pending output
+    * @throws IllegalStateException if no input structure has been defined
+    */
+   public boolean batchFinished() {
+      if (getInputFormat() == null) {
+         throw new IllegalStateException("No input instance format defined");
+      }
+
+      // process input
+      if (m_Values == null) {
+         determineValues(getInputFormat());
+         setOutputFormat();
+      } 
+      flushInput();
+      
+      m_NewBatch = true;
+      m_FirstBatchDone = true;
+
+      return (numPendingOutput() != 0);
+   }
+   
+   /**
+    * Returns the revision string.
+    * 
+    * @return		the revision
+    */
+   public String getRevision() {
+     return RevisionUtils.extract("$Revision: 5499 $");
+   }
+   
+   /**
+    * Main method for testing this class.
+    *
+    * @param argv should contain arguments to the filter: 
+    * use -h for help
+    */
+   public static void main(String[] argv) {
+      runFilter(new RemoveFrequentValues(), argv);
+   }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveMisclassified.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveMisclassified.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveMisclassified.java	(revision 29)
@@ -0,0 +1,741 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveMisclassified.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * A filter that removes instances which are incorrectly classified. Useful for removing outliers.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -W &lt;classifier specification&gt;
+ *  Full class name of classifier to use, followed
+ *  by scheme options. eg:
+ *   "weka.classifiers.bayes.NaiveBayes -D"
+ *  (default: weka.classifiers.rules.ZeroR)</pre>
+ * 
+ * <pre> -C &lt;class index&gt;
+ *  Attribute on which misclassifications are based.
+ *  If &lt; 0 will use any current set class or default to the last attribute.</pre>
+ * 
+ * <pre> -F &lt;number of folds&gt;
+ *  The number of folds to use for cross-validation cleansing.
+ *  (&lt;2 = no cross-validation - default).</pre>
+ * 
+ * <pre> -T &lt;threshold&gt;
+ *  Threshold for the max error when predicting numeric class.
+ *  (Value should be &gt;= 0, default = 0.1).</pre>
+ * 
+ * <pre> -I
+ *  The maximum number of cleansing iterations to perform.
+ *  (&lt;1 = until fully cleansed - default)</pre>
+ * 
+ * <pre> -V
+ *  Invert the match so that correctly classified instances are discarded.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class RemoveMisclassified 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5469157004717663171L;
+
+  /** The classifier used to do the cleansing */
+  protected Classifier m_cleansingClassifier = new weka.classifiers.rules.ZeroR();
+
+  /** The attribute to treat as the class for purposes of cleansing. */
+  protected int m_classIndex = -1;
+
+  /** The number of cross validation folds to perform (&lt;2 = no cross validation)  */
+  protected int m_numOfCrossValidationFolds = 0;
+  
+  /** The maximum number of cleansing iterations to perform (&lt;1 = until fully cleansed)  */
+  protected int m_numOfCleansingIterations = 0;
+
+  /** The threshold for deciding when a numeric value is correctly classified */
+  protected double m_numericClassifyThreshold = 0.1;
+
+  /** Whether to invert the match so the correctly classified instances are discarded */
+  protected boolean m_invertMatching = false;
+
+  /** Have we processed the first batch (i.e. training data)? */
+  protected boolean m_firstBatchFinished = false;
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities 	result;
+    
+    if (getClassifier() == null) {
+      result = super.getCapabilities();
+      result.disableAll();
+    } else {
+      result = getClassifier().getCapabilities();
+    }
+    
+    result.setMinimumNumberInstances(0);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the inputFormat can't be set successfully 
+   */ 
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+    
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    m_firstBatchFinished = false;
+    return true;
+  }
+
+  /**
+   * Cleanses the data based on misclassifications when used training data.
+   *
+   * @param data the data to train with and cleanse
+   * @return the cleansed data
+   * @throws Exception if something goes wrong
+   */
+  private Instances cleanseTrain(Instances data) throws Exception {
+    
+    Instance inst;
+    Instances buildSet = new Instances(data);  
+    Instances temp = new Instances(data, data.numInstances());
+    Instances inverseSet = new Instances(data, data.numInstances()); 
+    int count = 0;
+    double ans;
+    int iterations = 0;
+    int classIndex = m_classIndex;
+    if (classIndex < 0) classIndex = data.classIndex();
+    if (classIndex < 0) classIndex = data.numAttributes()-1;
+
+    // loop until perfect
+    while(count != buildSet.numInstances()) {
+      
+      // check if hit maximum number of iterations
+      iterations++;
+      if (m_numOfCleansingIterations > 0 && iterations > m_numOfCleansingIterations) break;
+
+      // build classifier
+      count = buildSet.numInstances();
+      buildSet.setClassIndex(classIndex);
+      m_cleansingClassifier.buildClassifier(buildSet);
+
+      temp = new Instances(buildSet, buildSet.numInstances());
+
+      // test on training data
+      for (int i = 0; i < buildSet.numInstances(); i++) {
+	inst = buildSet.instance(i);
+	ans = m_cleansingClassifier.classifyInstance(inst);
+	if (buildSet.classAttribute().isNumeric()) {
+	  if (ans >= inst.classValue() - m_numericClassifyThreshold &&
+	      ans <= inst.classValue() + m_numericClassifyThreshold) {
+	    temp.add(inst);
+	  } else if (m_invertMatching) {
+	    inverseSet.add(inst);
+	  }
+	}
+	else { //class is nominal
+	  if (ans == inst.classValue()) {
+	    temp.add(inst);
+	  } else if (m_invertMatching) {
+	    inverseSet.add(inst);
+	  }
+	}
+      }
+      buildSet = temp;
+    }
+
+    if (m_invertMatching) {
+      inverseSet.setClassIndex(data.classIndex());
+      return inverseSet;
+    }
+    else {
+      buildSet.setClassIndex(data.classIndex());
+      return buildSet;
+    }
+  }
+
+  /**
+   * Cleanses the data based on misclassifications when performing cross-validation.
+   *
+   * @param data the data to train with and cleanse
+   * @return the cleansed data
+   * @throws Exception if something goes wrong
+   */
+  private Instances cleanseCross(Instances data) throws Exception {
+    
+    Instance inst;
+    Instances crossSet = new Instances(data);
+    Instances temp = new Instances(data, data.numInstances());    
+    Instances inverseSet = new Instances(data, data.numInstances()); 
+    int count = 0;
+    double ans;
+    int iterations = 0;
+    int classIndex = m_classIndex;
+    if (classIndex < 0) classIndex = data.classIndex();
+    if (classIndex < 0) classIndex = data.numAttributes()-1;
+
+    // loop until perfect
+    while (count != crossSet.numInstances() && 
+	   crossSet.numInstances() >= m_numOfCrossValidationFolds) {
+
+      count = crossSet.numInstances();
+      
+      // check if hit maximum number of iterations
+      iterations++;
+      if (m_numOfCleansingIterations > 0 && iterations > m_numOfCleansingIterations) break;
+
+      crossSet.setClassIndex(classIndex);
+
+      if (crossSet.classAttribute().isNominal()) {
+	crossSet.stratify(m_numOfCrossValidationFolds);
+      }
+      // do the folds
+      temp = new Instances(crossSet, crossSet.numInstances());
+      
+      for (int fold = 0; fold < m_numOfCrossValidationFolds; fold++) {
+	Instances train = crossSet.trainCV(m_numOfCrossValidationFolds, fold);
+	m_cleansingClassifier.buildClassifier(train);
+	Instances test = crossSet.testCV(m_numOfCrossValidationFolds, fold);
+	//now test
+	for (int i = 0; i < test.numInstances(); i++) {
+	  inst = test.instance(i);
+	  ans = m_cleansingClassifier.classifyInstance(inst);
+	  if (crossSet.classAttribute().isNumeric()) {
+	    if (ans >= inst.classValue() - m_numericClassifyThreshold &&
+		ans <= inst.classValue() + m_numericClassifyThreshold) {
+	      temp.add(inst);
+	    } else if (m_invertMatching) {
+	      inverseSet.add(inst);
+	    }
+	  }
+	  else { //class is nominal
+	    if (ans == inst.classValue()) {
+	      temp.add(inst);
+	    } else if (m_invertMatching) {
+	      inverseSet.add(inst);
+	    }
+	  }
+	}
+      }
+      crossSet = temp;
+    }
+
+    if (m_invertMatching) {
+      inverseSet.setClassIndex(data.classIndex());
+      return inverseSet;
+    }
+    else {
+      crossSet.setClassIndex(data.classIndex());
+      return crossSet;
+    }
+
+  }
+ 
+  /**
+   * Input an instance for filtering.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws NullPointerException if the input format has not been
+   * defined.
+   * @throws Exception if the input instance was not of the correct 
+   * format or if there was a problem with the filtering.  
+   */
+  public boolean input(Instance instance) throws Exception {
+
+    if (inputFormatPeek() == null) {
+      throw new NullPointerException("No input instance format defined");
+    }
+
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (m_firstBatchFinished) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+ 
+  /**
+   * Signify that this batch of input to the filter is finished.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */  
+  public boolean batchFinished() throws Exception {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!m_firstBatchFinished) {
+
+      Instances filtered;
+      if (m_numOfCrossValidationFolds < 2) {
+	filtered = cleanseTrain(getInputFormat());
+      } else {
+	filtered = cleanseCross(getInputFormat());
+      }
+      
+      for (int i=0; i<filtered.numInstances(); i++) {
+	push(filtered.instance(i));
+      }
+      
+      m_firstBatchFinished = true;
+      flushInput();
+    }
+    m_NewBatch = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    
+    Vector newVector = new Vector(6);
+    
+    newVector.addElement(new Option(
+	      "\tFull class name of classifier to use, followed\n"
+	      + "\tby scheme options. eg:\n"
+	      + "\t\t\"weka.classifiers.bayes.NaiveBayes -D\"\n"
+	      + "\t(default: weka.classifiers.rules.ZeroR)",
+	      "W", 1, "-W <classifier specification>"));
+    newVector.addElement(new Option(
+	      "\tAttribute on which misclassifications are based.\n"
+	      + "\tIf < 0 will use any current set class or default to the last attribute.",
+	      "C", 1, "-C <class index>"));
+    newVector.addElement(new Option(
+	      "\tThe number of folds to use for cross-validation cleansing.\n"
+	      +"\t(<2 = no cross-validation - default).",
+	      "F", 1, "-F <number of folds>"));
+    newVector.addElement(new Option(
+	      "\tThreshold for the max error when predicting numeric class.\n"
+	      +"\t(Value should be >= 0, default = 0.1).",
+	      "T", 1, "-T <threshold>"));
+    newVector.addElement(new Option(
+	      "\tThe maximum number of cleansing iterations to perform.\n"
+	      +"\t(<1 = until fully cleansed - default)",
+	      "I", 1,"-I"));
+    newVector.addElement(new Option(
+	      "\tInvert the match so that correctly classified instances are discarded.\n",
+	      "V", 0,"-V"));
+    
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -W &lt;classifier specification&gt;
+   *  Full class name of classifier to use, followed
+   *  by scheme options. eg:
+   *   "weka.classifiers.bayes.NaiveBayes -D"
+   *  (default: weka.classifiers.rules.ZeroR)</pre>
+   * 
+   * <pre> -C &lt;class index&gt;
+   *  Attribute on which misclassifications are based.
+   *  If &lt; 0 will use any current set class or default to the last attribute.</pre>
+   * 
+   * <pre> -F &lt;number of folds&gt;
+   *  The number of folds to use for cross-validation cleansing.
+   *  (&lt;2 = no cross-validation - default).</pre>
+   * 
+   * <pre> -T &lt;threshold&gt;
+   *  Threshold for the max error when predicting numeric class.
+   *  (Value should be &gt;= 0, default = 0.1).</pre>
+   * 
+   * <pre> -I
+   *  The maximum number of cleansing iterations to perform.
+   *  (&lt;1 = until fully cleansed - default)</pre>
+   * 
+   * <pre> -V
+   *  Invert the match so that correctly classified instances are discarded.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String classifierString = Utils.getOption('W', options);
+    if (classifierString.length() == 0)
+      classifierString = weka.classifiers.rules.ZeroR.class.getName();
+    String[] classifierSpec = Utils.splitOptions(classifierString);
+    if (classifierSpec.length == 0) {
+      throw new Exception("Invalid classifier specification string");
+    }
+    String classifierName = classifierSpec[0];
+    classifierSpec[0] = "";
+    setClassifier(AbstractClassifier.forName(classifierName, classifierSpec));
+
+    String cString = Utils.getOption('C', options);
+    if (cString.length() != 0) {
+      setClassIndex((new Double(cString)).intValue());
+    } else {
+      setClassIndex(-1);
+    }
+
+    String fString = Utils.getOption('F', options);
+    if (fString.length() != 0) {
+      setNumFolds((new Double(fString)).intValue());
+    } else {
+      setNumFolds(0);
+    }
+
+    String tString = Utils.getOption('T', options);
+    if (tString.length() != 0) {
+      setThreshold((new Double(tString)).doubleValue());
+    } else {
+      setThreshold(0.1);
+    }
+
+    String iString = Utils.getOption('I', options);
+    if (iString.length() != 0) {
+      setMaxIterations((new Double(iString)).intValue());
+    } else {
+      setMaxIterations(0);
+    }
+    
+    if (Utils.getFlag('V', options)) {
+      setInvert(true);
+    } else {
+      setInvert(false);
+    }
+        
+    Utils.checkForRemainingOptions(options);
+
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [15];
+    int current = 0;
+
+    options[current++] = "-W"; options[current++] = "" + getClassifierSpec();
+    options[current++] = "-C"; options[current++] = "" + getClassIndex();
+    options[current++] = "-F"; options[current++] = "" + getNumFolds();
+    options[current++] = "-T"; options[current++] = "" + getThreshold();
+    options[current++] = "-I"; options[current++] = "" + getMaxIterations();
+    if (getInvert()) {
+      options[current++] = "-V";
+    }
+    
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "A filter that removes instances which are incorrectly classified. "
+      + "Useful for removing outliers.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classifierTipText() {
+
+    return "The classifier upon which to base the misclassifications.";
+  }
+
+  /**
+   * Sets the classifier to classify instances with.
+   *
+   * @param classifier The classifier to be used (with its options set).
+   */
+  public void setClassifier(Classifier classifier) {
+
+    m_cleansingClassifier = classifier;
+  }
+  
+  /**
+   * Gets the classifier used by the filter.
+   *
+   * @return The classifier to be used.
+   */
+  public Classifier getClassifier() {
+
+    return m_cleansingClassifier;
+  }
+
+  /**
+   * Gets the classifier specification string, which contains the class name of
+   * the classifier and any options to the classifier.
+   *
+   * @return the classifier string.
+   */
+  protected String getClassifierSpec() {
+    
+    Classifier c = getClassifier();
+    if (c instanceof OptionHandler) {
+      return c.getClass().getName() + " "
+	+ Utils.joinOptions(((OptionHandler)c).getOptions());
+    }
+    return c.getClass().getName();
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String classIndexTipText() {
+
+    return "Index of the class upon which to base the misclassifications. "
+      + "If < 0 will use any current set class or default to the last attribute.";
+  }
+
+  /**
+   * Sets the attribute on which misclassifications are based.
+   * If &lt; 0 will use any current set class or default to the last attribute.
+   *
+   * @param classIndex the class index.
+   */
+  public void setClassIndex(int classIndex) {
+    
+    m_classIndex = classIndex;
+  }
+
+  /**
+   * Gets the attribute on which misclassifications are based.
+   *
+   * @return the class index.
+   */
+  public int getClassIndex() {
+
+    return m_classIndex;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String numFoldsTipText() {
+
+    return "The number of cross-validation folds to use. If < 2 then no cross-validation will be performed.";
+  }
+
+  /**
+   * Sets the number of cross-validation folds to use
+   * - &lt; 2 means no cross-validation.
+   *
+   * @param numOfFolds the number of folds.
+   */
+  public void setNumFolds(int numOfFolds) {
+    
+    m_numOfCrossValidationFolds = numOfFolds;
+  }
+
+  /**
+   * Gets the number of cross-validation folds used by the filter.
+   *
+   * @return the number of folds.
+   */
+  public int getNumFolds() {
+
+    return m_numOfCrossValidationFolds;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String thresholdTipText() {
+
+    return "Threshold for the max allowable error when predicting a numeric class. Should be >= 0.";
+  }
+
+  /**
+   * Sets the threshold for the max error when predicting a numeric class.
+   * The value should be &gt;= 0.
+   *
+   * @param threshold the numeric theshold.
+   */
+  public void setThreshold(double threshold) {
+    
+    m_numericClassifyThreshold = threshold;
+  }
+
+  /**
+   * Gets the threshold for the max error when predicting a numeric class.
+   *
+   * @return the numeric threshold.
+   */
+  public double getThreshold() {
+
+    return m_numericClassifyThreshold;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String maxIterationsTipText() {
+
+    return "The maximum number of iterations to perform. < 1 means filter will go until fully cleansed.";
+  }
+
+  /**
+   * Sets the maximum number of cleansing iterations to perform
+   * - &lt; 1 means go until fully cleansed
+   *
+   * @param iterations the maximum number of iterations.
+   */
+  public void setMaxIterations(int iterations) {
+    
+    m_numOfCleansingIterations = iterations;
+  }
+
+  /**
+   * Gets the maximum number of cleansing iterations performed
+   *
+   * @return the maximum number of iterations. 
+   */
+  public int getMaxIterations() {
+
+    return m_numOfCleansingIterations;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertTipText() {
+
+    return "Whether or not to invert the selection. If true, correctly classified instances will be discarded.";
+  }
+
+  /**
+   * Set whether selection is inverted.
+   *
+   * @param invert whether or not to invert selection.
+   */
+  public void setInvert(boolean invert) {
+    
+    m_invertMatching = invert;
+  }
+
+  /**
+   * Get whether selection is inverted.
+   *
+   * @return whether or not selection is inverted.
+   */
+  public boolean getInvert() {
+    
+    return m_invertMatching;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveMisclassified(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemovePercentage.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemovePercentage.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemovePercentage.java	(revision 29)
@@ -0,0 +1,348 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemovePercentage.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A filter that removes a given percentage of a dataset.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -P &lt;percentage&gt;
+ *  Specifies percentage of instances to select. (default 50)
+ * </pre>
+ * 
+ * <pre> -V
+ *  Specifies if inverse of selection is to be output.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Richard Kirkby (eibe@cs.waikato.ac.nz)
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5499 $ 
+*/
+public class RemovePercentage 
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 2150341191158533133L;
+  
+  /** Percentage of instances to select. */
+  private double m_Percentage = 50;
+
+  /** Indicates if inverse of selection is to be output. */
+  private boolean m_Inverse = false;
+
+  /**
+   * Gets an enumeration describing the available options..
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(2);
+
+    newVector.addElement(new Option(
+              "\tSpecifies percentage of instances to select. (default 50)\n",
+              "P", 1, "-P <percentage>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies if inverse of selection is to be output.\n",
+	      "V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -P &lt;percentage&gt;
+   *  Specifies percentage of instances to select. (default 50)
+   * </pre>
+   * 
+   * <pre> -V
+   *  Specifies if inverse of selection is to be output.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String percent = Utils.getOption('P', options);
+    if (percent.length() != 0) {
+      setPercentage(Double.parseDouble(percent));
+    } else {
+      setPercentage(50.0);
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [5];
+    int current = 0;
+
+    options[current++] = "-P"; options[current++] = "" + getPercentage();
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+
+    return "A filter that removes a given percentage of a dataset.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String percentageTipText() {
+
+    return "The percentage of the data to select.";
+  }
+
+  /**
+   * Gets the percentage of instances to select.
+   * 
+   * @return the percentage.
+   */
+  public double getPercentage() {
+
+    return m_Percentage;
+  }
+
+  /**
+   * Sets the percentage of intances to select.
+   *
+   * @param percent the percentage
+   * @throws IllegalArgumentException if percentage out of range
+   */
+  public void setPercentage(double percent) {
+
+    if (percent < 0 || percent > 100) {
+      throw new IllegalArgumentException("Percentage must be between 0 and 100.");
+    }
+    m_Percentage = percent;
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Whether to invert the selection.";
+  }
+
+  /**
+   * Gets if selection is to be inverted.
+   *
+   * @return true if the selection is to be inverted
+   */
+  public boolean getInvertSelection() {
+
+    return m_Inverse;
+  }
+
+  /**
+   * Sets if selection is to be inverted.
+   *
+   * @param inverse true if inversion is to be performed
+   */
+  public void setInvertSelection(boolean inverse) {
+    
+    m_Inverse = inverse;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true because outputFormat can be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */  
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+  
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+     if (getInputFormat() == null) {
+        throw new IllegalStateException("No input instance format defined");
+     }
+     
+     if (m_NewBatch) {
+        resetQueue();
+        m_NewBatch = false;
+     }
+
+     if (isFirstBatchDone()) {
+       push(instance);
+       return true;
+     } 
+     else {
+       bufferInput(instance);
+       return false;
+     }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is
+   * finished. Output() may now be called to retrieve the filtered
+   * instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    // Push instances for output into output queue
+    Instances toFilter = getInputFormat();
+    int cutOff = (int) Math.round(toFilter.numInstances() * m_Percentage / 100);
+    
+    if (m_Inverse) {
+      for (int i = 0; i < cutOff; i++) {
+	push(toFilter.instance(i));
+      }
+    } else {
+      for (int i = cutOff; i < toFilter.numInstances(); i++) {
+	push(toFilter.instance(i));
+      }
+    }
+    flushInput();
+    
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemovePercentage(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveRange.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveRange.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveRange.java	(revision 29)
@@ -0,0 +1,345 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveRange.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * A filter that removes a given range of instances of a dataset.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -R &lt;inst1,inst2-inst4,...&gt;
+ *  Specifies list of instances to select. First and last
+ *  are valid indexes. (required)
+ * </pre>
+ * 
+ * <pre> -V
+ *  Specifies if inverse of selection is to be output.
+ * </pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5499 $ 
+ */
+public class RemoveRange 
+  extends Filter
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -3064641215340828695L;
+
+  /** Range of instances requested by the user. */
+  private Range m_Range = new Range("first-last");
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(6);
+
+    newVector.addElement(new Option(
+              "\tSpecifies list of instances to select. First and last\n"
+	      +"\tare valid indexes. (required)\n",
+              "R", 1, "-R <inst1,inst2-inst4,...>"));
+
+    newVector.addElement(new Option(
+	      "\tSpecifies if inverse of selection is to be output.\n",
+	      "V", 0, "-V"));
+
+    return newVector.elements();
+  }
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -R &lt;inst1,inst2-inst4,...&gt;
+   *  Specifies list of instances to select. First and last
+   *  are valid indexes. (required)
+   * </pre>
+   * 
+   * <pre> -V
+   *  Specifies if inverse of selection is to be output.
+   * </pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of string.s
+   * @throws Exception if an option is not supported.
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String str = Utils.getOption('R', options);
+    if (str.length() != 0) {
+      setInstancesIndices(str);
+    } else {
+      setInstancesIndices("first-last");
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions.
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [8];
+    int current = 0;
+
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    options[current++] = "-R"; options[current++] = getInstancesIndices();
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the GUI.
+   */
+  public String globalInfo() {
+
+    return "A filter that removes a given range of instances of a dataset.";
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String instancesIndicesTipText() {
+
+    return "The range of instances to select. First and last are valid indexes.";
+  }
+
+  /**
+   * Gets ranges of instances selected.
+   *
+   * @return a string containing a comma-separated list of ranges
+   */
+  public String getInstancesIndices() {
+
+    return m_Range.getRanges();
+  }
+
+  /**
+   * Sets the ranges of instances to be selected. If provided string
+   * is null, ranges won't be used for selecting instances.
+   *
+   * @param rangeList a string representing the list of instances. 
+   * eg: first-3,5,6-last
+   * @throws IllegalArgumentException if an invalid range list is supplied 
+   */
+  public void setInstancesIndices(String rangeList) {
+
+    m_Range.setRanges(rangeList);
+  }
+
+  /**
+   * Returns the tip text for this property
+   *
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+
+    return "Whether to invert the selection.";
+  }
+
+  /**
+   * Gets if selection is to be inverted.
+   *
+   * @return true if the selection is to be inverted
+   */
+  public boolean getInvertSelection() {
+
+    return m_Range.getInvert();
+  }
+
+  /**
+   * Sets if selection is to be inverted.
+   *
+   * @param inverse true if inversion is to be performed
+   */
+  public void setInvertSelection(boolean inverse) {
+    
+    m_Range.setInvert(inverse);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true because outputFormat can be collected immediately
+   * @throws Exception if the input format can't be set successfully
+   */  
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is
+   * finished. Output() may now be called to retrieve the filtered
+   * instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined 
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    
+    if (!isFirstBatchDone()) {
+      // Push instances for output into output queue
+      m_Range.setUpper(getInputFormat().numInstances() - 1);
+      for (int i = 0; i < getInputFormat().numInstances(); i++) {
+	if (!m_Range.isInRange(i)) {
+	  push(getInputFormat().instance(i));
+	}
+      }
+    }
+    else {
+      for (int i = 0; i < getInputFormat().numInstances(); i++) {
+	push(getInputFormat().instance(i));
+      }
+    }
+    
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveRange(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveWithValues.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveWithValues.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/RemoveWithValues.java	(revision 29)
@@ -0,0 +1,643 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RemoveWithValues.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.RevisionUtils;
+import weka.core.SingleIndex;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Filters instances according to the value of an attribute.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -C &lt;num&gt;
+ *  Choose attribute to be used for selection.</pre>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Numeric value to be used for selection on numeric
+ *  attribute.
+ *  Instances with values smaller than given value will
+ *  be selected. (default 0)</pre>
+ * 
+ * <pre> -L &lt;index1,index2-index4,...&gt;
+ *  Range of label indices to be used for selection on
+ *  nominal attribute.
+ *  First and last are valid indexes. (default all values)</pre>
+ * 
+ * <pre> -M
+ *  Missing values count as a match. This setting is
+ *  independent of the -V option.
+ *  (default missing values don't match)</pre>
+ * 
+ * <pre> -V
+ *  Invert matching sense.</pre>
+ * 
+ * <pre> -H
+ *  When selecting on nominal attributes, removes header
+ *  references to excluded values.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5499 $
+ */
+public class RemoveWithValues 
+  extends Filter
+  implements UnsupervisedFilter, StreamableFilter, OptionHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = 4752870193679263361L;
+  
+  /** The attribute's index setting. */
+  private SingleIndex m_AttIndex = new SingleIndex("last"); 
+  
+  /** Stores which values of nominal attribute are to be used for filtering.*/
+  protected Range m_Values;
+
+  /** Stores which value of a numeric attribute is to be used for filtering.*/
+  protected double m_Value = 0;
+
+  /** True if missing values should count as a match */
+  protected boolean m_MatchMissingValues = false;
+
+  /** Modify header for nominal attributes? */
+  protected boolean m_ModifyHeader = false;
+
+  /** If m_ModifyHeader, stores a mapping from old to new indexes */
+  protected int [] m_NominalMapping;
+
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "Filters instances according to the value of an attribute.";
+  }
+
+  /** Default constructor */
+  public RemoveWithValues() {
+
+      m_Values = new Range("first-last");
+      m_Values.setInvert(true);
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+
+    Vector newVector = new Vector(5);
+
+    newVector.addElement(new Option(
+              "\tChoose attribute to be used for selection.",
+              "C", 1, "-C <num>"));
+    newVector.addElement(new Option(
+              "\tNumeric value to be used for selection on numeric\n"+
+	      "\tattribute.\n"+
+	      "\tInstances with values smaller than given value will\n"+
+              "\tbe selected. (default 0)",
+              "S", 1, "-S <num>"));
+    newVector.addElement(new Option(
+              "\tRange of label indices to be used for selection on\n"+
+	      "\tnominal attribute.\n"+
+	      "\tFirst and last are valid indexes. (default all values)",
+              "L", 1, "-L <index1,index2-index4,...>"));
+    newVector.addElement(new Option(
+	      "\tMissing values count as a match. This setting is\n"+
+              "\tindependent of the -V option.\n"+
+              "\t(default missing values don't match)",
+              "M", 0, "-M"));
+    newVector.addElement(new Option(
+	      "\tInvert matching sense.",
+              "V", 0, "-V"));
+    newVector.addElement(new Option(
+	      "\tWhen selecting on nominal attributes, removes header\n"
+	      + "\treferences to excluded values.",
+              "H", 0, "-H"));
+
+    return newVector.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -C &lt;num&gt;
+   *  Choose attribute to be used for selection.</pre>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Numeric value to be used for selection on numeric
+   *  attribute.
+   *  Instances with values smaller than given value will
+   *  be selected. (default 0)</pre>
+   * 
+   * <pre> -L &lt;index1,index2-index4,...&gt;
+   *  Range of label indices to be used for selection on
+   *  nominal attribute.
+   *  First and last are valid indexes. (default all values)</pre>
+   * 
+   * <pre> -M
+   *  Missing values count as a match. This setting is
+   *  independent of the -V option.
+   *  (default missing values don't match)</pre>
+   * 
+   * <pre> -V
+   *  Invert matching sense.</pre>
+   * 
+   * <pre> -H
+   *  When selecting on nominal attributes, removes header
+   *  references to excluded values.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+
+    String attIndex = Utils.getOption('C', options);
+    if (attIndex.length() != 0) {
+      setAttributeIndex(attIndex);
+    } else {
+      setAttributeIndex("last");
+    }
+    
+    String splitPoint = Utils.getOption('S', options);
+    if (splitPoint.length() != 0) {
+      setSplitPoint((new Double(splitPoint)).doubleValue());
+    } else {
+      setSplitPoint(0);
+    }
+
+    String convertList = Utils.getOption('L', options);
+    if (convertList.length() != 0) {
+      setNominalIndices(convertList);
+    } else {
+      setNominalIndices("first-last");
+    }
+    setInvertSelection(Utils.getFlag('V', options));
+    setMatchMissingValues(Utils.getFlag('M', options));
+    setModifyHeader(Utils.getFlag('H', options));
+    // Re-initialize output format according to new options
+    
+    if (getInputFormat() != null) {
+      setInputFormat(getInputFormat());
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+
+    String [] options = new String [9];
+    int current = 0;
+
+    options[current++] = "-S"; options[current++] = "" + getSplitPoint();
+    options[current++] = "-C";
+    options[current++] = "" + (getAttributeIndex());
+    if (!getNominalIndices().equals("")) {
+      options[current++] = "-L"; options[current++] = getNominalIndices();
+    }
+    if (getInvertSelection()) {
+      options[current++] = "-V";
+    }
+    if (getMatchMissingValues()) {
+      options[current++] = "-M";
+    }
+    if (getModifyHeader()) {
+      options[current++] = "-H";
+    }
+    while (current < options.length) {
+      options[current++] = "";
+    }
+    return options;
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @throws UnsupportedAttributeTypeException if the specified attribute
+   * is neither numeric or nominal.
+   * @return true because outputFormat can be collected immediately
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+
+    m_AttIndex.setUpper(instanceInfo.numAttributes() - 1);
+    if (!isNumeric() && !isNominal()) {
+      throw new UnsupportedAttributeTypeException("Can only handle numeric " +
+						  "or nominal attributes.");
+    }
+    m_Values.setUpper(instanceInfo.attribute(m_AttIndex.getIndex()).numValues() - 1);
+    if (isNominal() && m_ModifyHeader) {
+      instanceInfo = new Instances(instanceInfo, 0); // copy before modifying
+      Attribute oldAtt = instanceInfo.attribute(m_AttIndex.getIndex());
+      int [] selection = m_Values.getSelection();
+      FastVector newVals = new FastVector();
+      for (int i = 0; i < selection.length; i++) {
+	newVals.addElement(oldAtt.value(selection[i]));
+      }
+      instanceInfo.deleteAttributeAt(m_AttIndex.getIndex());
+      instanceInfo.insertAttributeAt(new Attribute(oldAtt.name(), newVals),
+				      m_AttIndex.getIndex());
+      m_NominalMapping = new int [oldAtt.numValues()];
+      for (int i = 0; i < m_NominalMapping.length; i++) {
+	boolean found = false;
+	for (int j = 0; j < selection.length; j++) {
+	  if (selection[j] == i) {
+	    m_NominalMapping[i] = j;
+	    found = true;
+	    break;
+	  }
+	}
+	if (!found) {
+	  m_NominalMapping[i] = -1;
+	}
+      }
+    }
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input format has been set.
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (instance.isMissing(m_AttIndex.getIndex())) {
+      if (!getMatchMissingValues()) {
+        push((Instance)instance.copy());
+        return true;
+      } else {
+        return false;
+      }
+    }
+    if (isNumeric()) {
+      if (!m_Values.getInvert()) {
+	if (instance.value(m_AttIndex.getIndex()) < m_Value) {
+	  push((Instance)instance.copy());
+	  return true;
+	} 
+      } else {
+	if (instance.value(m_AttIndex.getIndex()) >= m_Value) {
+	  push((Instance)instance.copy());
+	  return true;
+	} 
+      }
+    }
+    if (isNominal()) {
+      if (m_Values.isInRange((int)instance.value(m_AttIndex.getIndex()))) {
+	Instance temp = (Instance)instance.copy();
+	if (getModifyHeader()) {
+	  temp.setValue(m_AttIndex.getIndex(),
+			m_NominalMapping[(int)instance.value(m_AttIndex.getIndex())]);
+	}
+	push(temp);
+	return true;
+      }
+    }
+    return false;
+  }
+
+  /** 
+   * Returns true if selection attribute is nominal.
+   *
+   * @return true if selection attribute is nominal
+   */
+  public boolean isNominal() {
+    
+    if (getInputFormat() == null) {
+      return false;
+    } else {
+      return getInputFormat().attribute(m_AttIndex.getIndex()).isNominal();
+    }
+  }
+
+  /** 
+   * Returns true if selection attribute is numeric.
+   *
+   * @return true if selection attribute is numeric
+   */
+  public boolean isNumeric() {
+    
+    if (getInputFormat() == null) {
+      return false;
+    } else {
+      return getInputFormat().attribute(m_AttIndex.getIndex()).isNumeric();
+    }
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String modifyHeaderTipText() {
+    return "When selecting on nominal attributes, removes header references to "
+      + "excluded values.";
+  }
+
+  /**
+   * Gets whether the header will be modified when selecting on nominal
+   * attributes.
+   *
+   * @return true if so.
+   */
+  public boolean getModifyHeader() {
+    
+    return m_ModifyHeader;
+  }
+  
+  /**
+   * Sets whether the header will be modified when selecting on nominal
+   * attributes.
+   *
+   * @param newModifyHeader true if so.
+   */
+  public void setModifyHeader(boolean newModifyHeader) {
+    
+    m_ModifyHeader = newModifyHeader;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String attributeIndexTipText() {
+    return "Choose attribute to be used for selection (default last).";
+  }
+
+  /**
+   * Get the index of the attribute used.
+   *
+   * @return the index of the attribute
+   */
+  public String getAttributeIndex() {
+
+    return m_AttIndex.getSingleIndex();
+  }
+
+  /**
+   * Sets index of the attribute used.
+   *
+   * @param attIndex the index of the attribute
+   */
+  public void setAttributeIndex(String attIndex) {
+    
+    m_AttIndex.setSingleIndex(attIndex);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String splitPointTipText() {
+    return "Numeric value to be used for selection on numeric attribute. "
+     + "Instances with values smaller than given value will be selected.";
+  }
+
+  /**
+   * Get the split point used for numeric selection
+   *
+   * @return the numeric split point
+   */
+  public double getSplitPoint() {
+
+    return m_Value;
+  }
+
+  /**
+   * Split point to be used for selection on numeric attribute.
+   *
+   * @param value the split point
+   */
+  public void setSplitPoint(double value) {
+
+    m_Value = value;
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String matchMissingValuesTipText() {
+    return "Missing values count as a match. This setting is independent of "
+      + "the invertSelection option.";
+  }
+
+  /**
+   * Gets whether missing values are counted as a match.
+   *
+   * @return true if missing values are counted as a match.
+   */
+  public boolean getMatchMissingValues() {
+
+    return m_MatchMissingValues;
+  }
+  
+  /**
+   * Sets whether missing values are counted as a match.
+   *
+   * @param newMatchMissingValues true if missing values are counted as a match.
+   */
+  public void setMatchMissingValues(boolean newMatchMissingValues) {
+
+    m_MatchMissingValues = newMatchMissingValues;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Invert matching sense.";
+  }
+
+  /**
+   * Get whether the supplied columns are to be removed or kept
+   *
+   * @return true if the supplied columns will be kept
+   */
+  public boolean getInvertSelection() {
+
+    return !m_Values.getInvert();
+  }
+
+  /**
+   * Set whether selected values should be removed or kept. If true the 
+   * selected values are kept and unselected values are deleted. 
+   *
+   * @param invert the new invert setting
+   */
+  public void setInvertSelection(boolean invert) {
+
+    m_Values.setInvert(!invert);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String nominalIndicesTipText() {
+    return "Range of label indices to be used for selection on nominal attribute. "
+      +"First and last are valid indexes.";
+  }
+
+  /**
+   * Get the set of nominal value indices that will be used for selection
+   *
+   * @return rangeList a string representing the list of nominal indices.
+   */
+  public String getNominalIndices() {
+
+    return m_Values.getRanges();
+  }
+
+  /**
+   * Set which nominal labels are to be included in the selection.
+   *
+   * @param rangeList a string representing the list of nominal indices.
+   * eg: first-3,5,6-last
+   * @throws InvalidArgumentException if an invalid range list is supplied
+   */
+  public void setNominalIndices(String rangeList) {
+    
+    m_Values.setRanges(rangeList);
+  }
+
+  /**
+   * Set which values of a nominal attribute are to be used for
+   * selection.
+   *
+   * @param values an array containing indexes of values to be
+   * used for selection
+   * @throws InvalidArgumentException if an invalid set of ranges is supplied
+   */
+  public void setNominalIndicesArr(int [] values) {
+
+    String rangeList = "";
+    for(int i = 0; i < values.length; i++) {
+      if (i == 0) {
+	rangeList = "" + (values[i] + 1);
+      } else {
+	rangeList += "," + (values[i] + 1);
+      }
+    }
+    setNominalIndices(rangeList);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new RemoveWithValues(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Resample.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Resample.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/Resample.java	(revision 29)
@@ -0,0 +1,511 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Resample.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.UnsupervisedFilter;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/** 
+ <!-- globalinfo-start -->
+ * Produces a random subsample of a dataset using either sampling with replacement or without replacement. The original dataset must fit entirely in memory. The number of instances in the generated dataset may be specified. When used in batch mode, subsequent batches are NOT resampled.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 1)</pre>
+ * 
+ * <pre> -Z &lt;num&gt;
+ *  The size of the output dataset, as a percentage of
+ *  the input dataset (default 100)</pre>
+ * 
+ * <pre> -no-replacement
+ *  Disables replacement of instances
+ *  (default: with replacement)</pre>
+ * 
+ * <pre> -V
+ *  Inverts the selection - only available with '-no-replacement'.</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Len Trigg (len@reeltwo.com)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5499 $ 
+ */
+public class Resample 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3119607037607101160L;
+
+  /** The subsample size, percent of original set, default 100% */
+  protected double m_SampleSizePercent = 100;
+  
+  /** The random number generator seed */
+  protected int m_RandomSeed = 1;
+
+  /** Whether to perform sampling with replacement or without */
+  protected boolean m_NoReplacement = false;
+
+  /** Whether to invert the selection (only if instances are drawn WITHOUT 
+   * replacement)
+   * @see #m_NoReplacement */
+  protected boolean m_InvertSelection = false;
+  
+  /**
+   * Returns a string describing this classifier
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Produces a random subsample of a dataset using either sampling with "
+      + "replacement or without replacement. The original dataset must fit "
+      + "entirely in memory. The number of instances in the generated dataset "
+      + "may be specified. When used in batch mode, subsequent batches are "
+      + "NOT resampled.";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSpecify the random number seed (default 1)",
+	"S", 1, "-S <num>"));
+
+    result.addElement(new Option(
+	"\tThe size of the output dataset, as a percentage of\n"
+	+"\tthe input dataset (default 100)",
+	"Z", 1, "-Z <num>"));
+
+    result.addElement(new Option(
+	"\tDisables replacement of instances\n"
+	+"\t(default: with replacement)",
+	"no-replacement", 0, "-no-replacement"));
+
+    result.addElement(new Option(
+	"\tInverts the selection - only available with '-no-replacement'.",
+	"V", 0, "-V"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 1)</pre>
+   * 
+   * <pre> -Z &lt;num&gt;
+   *  The size of the output dataset, as a percentage of
+   *  the input dataset (default 100)</pre>
+   * 
+   * <pre> -no-replacement
+   *  Disables replacement of instances
+   *  (default: with replacement)</pre>
+   * 
+   * <pre> -V
+   *  Inverts the selection - only available with '-no-replacement'.</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0)
+      setRandomSeed(Integer.parseInt(tmpStr));
+    else
+      setRandomSeed(1);
+
+    tmpStr = Utils.getOption('Z', options);
+    if (tmpStr.length() != 0)
+      setSampleSizePercent(Double.parseDouble(tmpStr));
+    else
+      setSampleSizePercent(100);
+
+    setNoReplacement(Utils.getFlag("no-replacement", options));
+
+    if (getNoReplacement())
+      setInvertSelection(Utils.getFlag('V', options));
+
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+
+    result = new Vector<String>();
+
+    result.add("-S");
+    result.add("" + getRandomSeed());
+
+    result.add("-Z");
+    result.add("" + getSampleSizePercent());
+
+    if (getNoReplacement()) {
+      result.add("-no-replacement");
+      if (getInvertSelection())
+	result.add("-V");
+    }
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "The seed used for random sampling.";
+  }
+
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getRandomSeed() {
+    return m_RandomSeed;
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setRandomSeed(int newSeed) {
+    m_RandomSeed = newSeed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sampleSizePercentTipText() {
+    return "Size of the subsample as a percentage of the original dataset.";
+  }
+
+  /**
+   * Gets the subsample size as a percentage of the original set.
+   *
+   * @return the subsample size
+   */
+  public double getSampleSizePercent() {
+    return m_SampleSizePercent;
+  }
+  
+  /**
+   * Sets the size of the subsample, as a percentage of the original set.
+   *
+   * @param newSampleSizePercent the subsample set size, between 0 and 100.
+   */
+  public void setSampleSizePercent(double newSampleSizePercent) {
+    m_SampleSizePercent = newSampleSizePercent;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String noReplacementTipText() {
+    return "Disables the replacement of instances.";
+  }
+
+  /**
+   * Gets whether instances are drawn with or without replacement.
+   * 
+   * @return true if the replacement is disabled
+   */
+  public boolean getNoReplacement() {
+    return m_NoReplacement;
+  }
+  
+  /**
+   * Sets whether instances are drawn with or with out replacement.
+   * 
+   * @param value if true then the replacement of instances is disabled
+   */
+  public void setNoReplacement(boolean value) {
+    m_NoReplacement = value;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String invertSelectionTipText() {
+    return "Inverts the selection (only if instances are drawn WITHOUT replacement).";
+  }
+
+  /**
+   * Gets whether selection is inverted (only if instances are drawn WIHTOUT 
+   * replacement).
+   * 
+   * @return true if the replacement is disabled
+   * @see #m_NoReplacement
+   */
+  public boolean getInvertSelection() {
+    return m_InvertSelection;
+  }
+  
+  /**
+   * Sets whether the selection is inverted (only if instances are drawn WIHTOUT 
+   * replacement).
+   * 
+   * @param value if true then selection is inverted
+   */
+  public void setInvertSelection(boolean value) {
+    m_InvertSelection = value;
+  }
+  
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      bufferInput(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isFirstBatchDone()) {
+      // Do the subsample, and clear the input instances.
+      createSubsample();
+    }
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+
+  /**
+   * creates the subsample with replacement
+   * 
+   * @param random	the random number generator to use
+   * @param origSize	the original size of the dataset
+   * @param sampleSize	the size to generate
+   */
+  public void createSubsampleWithReplacement(Random random, int origSize, 
+      int sampleSize) {
+    
+    for (int i = 0; i < sampleSize; i++) {
+	int index = random.nextInt(origSize);
+	push((Instance) getInputFormat().instance(index).copy());
+    }
+  }
+
+  /**
+   * creates the subsample without replacement
+   * 
+   * @param random	the random number generator to use
+   * @param origSize	the original size of the dataset
+   * @param sampleSize	the size to generate
+   */
+  public void createSubsampleWithoutReplacement(Random random, int origSize, 
+      int sampleSize) {
+    
+    if (sampleSize > origSize) {
+      sampleSize = origSize;
+      System.err.println(
+	  "Resampling with replacement can only use percentage <=100% - "
+	  + "Using full dataset!");
+    }
+
+    Vector<Integer> indices = new Vector<Integer>(origSize);
+    Vector<Integer> indicesNew = new Vector<Integer>(sampleSize);
+
+    // generate list of all indices to draw from
+    for (int i = 0; i < origSize; i++)
+      indices.add(i);
+
+    // draw X random indices (selected ones get removed before next draw)
+    for (int i = 0; i < sampleSize; i++) {
+      int index = random.nextInt(indices.size());
+      indicesNew.add(indices.get(index));
+      indices.remove(index);
+    }
+
+    if (getInvertSelection())
+      indicesNew = indices;
+    else
+      Collections.sort(indicesNew);
+
+    for (int i = 0; i < indicesNew.size(); i++)
+      push((Instance) getInputFormat().instance(indicesNew.get(i)).copy());
+
+    // clean up
+    indices.clear();
+    indicesNew.clear();
+    indices = null;
+    indicesNew = null;
+  }
+  
+  /**
+   * Creates a subsample of the current set of input instances. The output
+   * instances are pushed onto the output queue for collection.
+   */
+  protected void createSubsample() {
+    int origSize = getInputFormat().numInstances();
+    int sampleSize = (int) (origSize * m_SampleSizePercent / 100);
+    Random random = new Random(m_RandomSeed);
+    
+    if (getNoReplacement())
+      createSubsampleWithoutReplacement(random, origSize, sampleSize);
+    else
+      createSubsampleWithReplacement(random, origSize, sampleSize);
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5499 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new Resample(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/ReservoirSample.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/ReservoirSample.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/ReservoirSample.java	(revision 29)
@@ -0,0 +1,400 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ReservoirSample.java
+ *    Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.filters.unsupervised.instance;
+
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * Produces a random subsample of a dataset using the reservoir sampling Algorithm "R" by Vitter. The original data set does not have to fit into main memory, but the reservoir does.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- technical-bibtex-start -->
+ * BibTeX:
+ * <pre>
+ * &#64;article{Vitter1985,
+ *    author = {J. S. Vitter},
+ *    journal = {ACM Transactions on Mathematical Software},
+ *    number = {1}
+ *    volume = {11}
+ *    pages = {37-57},
+ *    title = {Random Sampling with a Reservoir},
+ *    year = {1985}
+ * }
+ * </pre>
+ * </p>
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -S &lt;num&gt;
+ *  Specify the random number seed (default 1)</pre>
+ * 
+ * <pre> -Z &lt;num&gt;
+ *  The size of the output dataset - number of instances
+ *  (default 100)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5563 $ 
+ */
+public class ReservoirSample 
+  extends Filter 
+  implements UnsupervisedFilter, OptionHandler, StreamableFilter {
+  
+  /** for serialization */
+  static final long serialVersionUID = 3119607037607101160L;
+
+  /** The subsample size, number of instances% */
+  protected int m_SampleSize = 100;
+
+  /** Holds the sub-sample (reservoir) */
+  protected Instance[] m_subSample;
+
+  /** The current instance being processed */
+  protected int m_currentInst;
+  
+  /** The random number generator seed */
+  protected int m_RandomSeed = 1;
+
+  /** The random number generator */
+  protected Random m_random;
+  
+  /**
+   * Returns a string describing this filter
+   * @return a description of the classifier suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Produces a random subsample of a dataset using the reservoir sampling "
+      + "Algorithm \"R\" by Vitter. The original data set does not have to fit "
+      + "into main memory, but the reservoir does. ";
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector result = new Vector();
+
+    result.addElement(new Option(
+	"\tSpecify the random number seed (default 1)",
+	"S", 1, "-S <num>"));
+
+    result.addElement(new Option(
+	"\tThe size of the output dataset - number of instances\n"
+	+"\t(default 100)",
+	"Z", 1, "-Z <num>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -S &lt;num&gt;
+   *  Specify the random number seed (default 1)</pre>
+   * 
+   * <pre> -Z &lt;num&gt;
+   *  The size of the output dataset - number of instances
+   *  (default 100)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('S', options);
+    if (tmpStr.length() != 0) {
+      setRandomSeed(Integer.parseInt(tmpStr));
+    } else {
+      setRandomSeed(1);
+    }
+
+    tmpStr = Utils.getOption('Z', options);
+    if (tmpStr.length() != 0) {
+      setSampleSize(Integer.parseInt(tmpStr));
+    } else {
+      setSampleSize(100);
+    }
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String [] getOptions() {
+    Vector<String>	result;
+
+    result = new Vector<String>();
+
+    result.add("-S");
+    result.add("" + getRandomSeed());
+
+    result.add("-Z");
+    result.add("" + getSampleSize());
+    
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String randomSeedTipText() {
+    return "The seed used for random sampling.";
+  }
+
+  /**
+   * Gets the random number seed.
+   *
+   * @return the random number seed.
+   */
+  public int getRandomSeed() {
+    return m_RandomSeed;
+  }
+  
+  /**
+   * Sets the random number seed.
+   *
+   * @param newSeed the new random number seed.
+   */
+  public void setRandomSeed(int newSeed) {
+    m_RandomSeed = newSeed;
+  }
+  
+  /**
+   * Returns the tip text for this property
+   * 
+   * @return tip text for this property suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String sampleSizeTipText() {
+    return "Size of the subsample (reservoir). i.e. the number of instances.";
+  }
+
+  /**
+   * Gets the subsample size.
+   *
+   * @return the subsample size
+   */
+  public int getSampleSize() {
+    return m_SampleSize;
+  }
+  
+  /**
+   * Sets the size of the subsample.
+   *
+   * @param newSampleSize size of the subsample.
+   */
+  public void setSampleSize(int newSampleSize) {
+    m_SampleSize = newSampleSize;
+  }
+  
+  
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input 
+   * instance structure (any instances contained in the object are 
+   * ignored - only the structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if the input format can't be set 
+   * successfully
+   */
+  public boolean setInputFormat(Instances instanceInfo) 
+       throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+
+    m_subSample = new Instance[m_SampleSize];
+    m_currentInst = 0;
+    m_random = new Random(m_RandomSeed);
+
+    return true;
+  }
+
+  /**
+   * Decides whether the current instance gets retained in the
+   * reservoir.
+   *
+   * @param instance the Instance to potentially retain
+   */
+  protected void processInstance(Instance instance) {
+    if (m_currentInst < m_SampleSize) {
+      m_subSample[m_currentInst] = (Instance)instance.copy();
+    } else {
+      double r = m_random.nextDouble();
+      if (r < ((double)m_SampleSize / (double)m_currentInst)) {
+        r = m_random.nextDouble();
+        int replace = (int)((double)m_SampleSize * r);
+        m_subSample[replace] = (Instance)instance.copy();
+      }
+    }
+    m_currentInst++;
+  }
+
+  /**
+   * Input an instance for filtering. Filter requires all
+   * training instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    if (isFirstBatchDone()) {
+      push(instance);
+      return true;
+    } else {
+      //      bufferInput(instance);
+      copyValues(instance, false);
+      processInstance(instance);
+      return false;
+    }
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. 
+   * If the filter requires all instances prior to filtering,
+   * output() may now be called to retrieve the filtered instances.
+   *
+   * @return true if there are instances pending output
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean batchFinished() {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+
+    if (!isFirstBatchDone()) {
+      // Do the subsample, and clear the input instances.
+      createSubsample();
+    }
+    flushInput();
+
+    m_NewBatch = true;
+    m_FirstBatchDone = true;
+    return (numPendingOutput() != 0);
+  }
+  
+  /**
+   * Creates a subsample of the current set of input instances. The output
+   * instances are pushed onto the output queue for collection.
+   */
+  protected void createSubsample() {
+
+    for (int i = 0; i < m_SampleSize; i++) {
+      if (m_subSample[i] != null) {
+        Instance copy = (Instance) m_subSample[i].copy();
+        push(copy);
+      } else {
+        // less data in the original than was asked for
+        // as a sample.
+        break;
+      }
+    }
+    m_subSample = null;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5563 $");
+  }
+  
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: 
+   * use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new ReservoirSample(), argv);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SparseToNonSparse.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SparseToNonSparse.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SparseToNonSparse.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SparseToNonSparse.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.core.SparseInstance;
+import weka.core.Capabilities.Capability;
+import weka.filters.Filter;
+import weka.filters.StreamableFilter;
+import weka.filters.UnsupervisedFilter;
+
+/** 
+ <!-- globalinfo-start -->
+ * An instance filter that converts all incoming sparse instances into non-sparse format.
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ * @author Len Trigg (len@reeltwo.com)
+ * @version $Revision: 5987 $ 
+ */
+public class SparseToNonSparse 
+  extends Filter 
+  implements UnsupervisedFilter, StreamableFilter {
+
+  /** for serialization */
+  static final long serialVersionUID = 2481634184210236074L;
+  
+  /**
+   * Returns a string describing this filter
+   *
+   * @return a description of the filter suitable for
+   * displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return "An instance filter that converts all incoming sparse instances"
+      + " into non-sparse format.";
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enableAllAttributes();
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enableAllClasses();
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the format of the input instances.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   * @throws Exception if format cannot be processed
+   */
+  public boolean setInputFormat(Instances instanceInfo) throws Exception {
+
+    super.setInputFormat(instanceInfo);
+    setOutputFormat(instanceInfo);
+    return true;
+  }
+
+
+  /**
+   * Input an instance for filtering. Ordinarily the instance is processed
+   * and made available for output immediately. Some filters require all
+   * instances be read before producing output.
+   *
+   * @param instance the input instance
+   * @return true if the filtered instance may now be
+   * collected with output().
+   * @throws IllegalStateException if no input structure has been defined
+   */
+  public boolean input(Instance instance) {
+
+    if (getInputFormat() == null) {
+      throw new IllegalStateException("No input instance format defined");
+    }
+    if (m_NewBatch) {
+      resetQueue();
+      m_NewBatch = false;
+    }
+    Instance inst = null;
+    if (instance instanceof SparseInstance) {
+      inst = new DenseInstance(instance.weight(), instance.toDoubleArray());
+      inst.setDataset(instance.dataset());
+    } else {
+      inst = instance;
+    }
+    push(inst);
+    return true;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5987 $");
+  }
+
+  /**
+   * Main method for testing this class.
+   *
+   * @param argv should contain arguments to the filter: use -h for help
+   */
+  public static void main(String [] argv) {
+    runFilter(new SparseToNonSparse(), argv);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SubsetByExpression.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SubsetByExpression.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/SubsetByExpression.java	(revision 29)
@@ -0,0 +1,377 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SubsetByExpression.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.Option;
+import weka.core.RevisionUtils;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.filters.SimpleBatchFilter;
+import weka.filters.unsupervised.instance.subsetbyexpression.Parser;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ <!-- globalinfo-start -->
+ * Filters instances according to a user-specified expression.<br/>
+ * <br/>
+ * Grammar:<br/>
+ * <br/>
+ * boolexpr_list ::= boolexpr_list boolexpr_part | boolexpr_part;<br/>
+ * <br/>
+ * boolexpr_part ::= boolexpr:e {: parser.setResult(e); :} ;<br/>
+ * <br/>
+ * boolexpr ::=    BOOLEAN <br/>
+ *               | true<br/>
+ *               | false<br/>
+ *               | expr &lt; expr<br/>
+ *               | expr &lt;= expr<br/>
+ *               | expr &gt; expr<br/>
+ *               | expr &gt;= expr<br/>
+ *               | expr = expr<br/>
+ *               | ( boolexpr )<br/>
+ *               | not boolexpr<br/>
+ *               | boolexpr and boolexpr<br/>
+ *               | boolexpr or boolexpr<br/>
+ *               | ATTRIBUTE is STRING<br/>
+ *               ;<br/>
+ * <br/>
+ * expr      ::=   NUMBER<br/>
+ *               | ATTRIBUTE<br/>
+ *               | ( expr )<br/>
+ *               | opexpr<br/>
+ *               | funcexpr<br/>
+ *               ;<br/>
+ * <br/>
+ * opexpr    ::=   expr + expr<br/>
+ *               | expr - expr<br/>
+ *               | expr * expr<br/>
+ *               | expr / expr<br/>
+ *               ;<br/>
+ * <br/>
+ * funcexpr ::=    abs ( expr )<br/>
+ *               | sqrt ( expr )<br/>
+ *               | log ( expr )<br/>
+ *               | exp ( expr )<br/>
+ *               | sin ( expr )<br/>
+ *               | cos ( expr )<br/>
+ *               | tan ( expr )<br/>
+ *               | rint ( expr )<br/>
+ *               | floor ( expr )<br/>
+ *               | pow ( expr for base , expr for exponent )<br/>
+ *               | ceil ( expr )<br/>
+ *               ;<br/>
+ * <br/>
+ * Notes:<br/>
+ * - NUMBER<br/>
+ *   any integer or floating point number <br/>
+ *   (but not in scientific notation!)<br/>
+ * - STRING<br/>
+ *   any string surrounded by single quotes; <br/>
+ *   the string may not contain a single quote though.<br/>
+ * - ATTRIBUTE<br/>
+ *   the following placeholders are recognized for <br/>
+ *   attribute values:<br/>
+ *   - CLASS for the class value in case a class attribute is set.<br/>
+ *   - ATTxyz with xyz a number from 1 to # of attributes in the<br/>
+ *     dataset, representing the value of indexed attribute.<br/>
+ * <br/>
+ * Examples:<br/>
+ * - extracting only mammals and birds from the 'zoo' UCI dataset:<br/>
+ *   (CLASS is 'mammal') or (CLASS is 'bird')<br/>
+ * - extracting only animals with at least 2 legs from the 'zoo' UCI dataset:<br/>
+ *   (ATT14 &gt;= 2)<br/>
+ * - extracting only instances with non-missing 'wage-increase-second-year'<br/>
+ *   from the 'labor' UCI dataset:<br/>
+ *   not ismissing(ATT3)<br/>
+ * <p/>
+ <!-- globalinfo-end -->
+ * 
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -E &lt;expr&gt;
+ *  The expression to use for filtering
+ *  (default: true).</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6113 $
+ */
+public class SubsetByExpression
+  extends SimpleBatchFilter {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 5628686110979589602L;
+  
+  /** the expresion to use for filtering. */
+  protected String m_Expression = "true";
+  
+  /**
+   * Returns a string describing this filter.
+   *
+   * @return 		a description of the filter suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String globalInfo() {
+    return 
+        "Filters instances according to a user-specified expression.\n\n"
+      + "Grammar:\n\n"
+      + "boolexpr_list ::= boolexpr_list boolexpr_part | boolexpr_part;\n"
+      + "\n"
+      + "boolexpr_part ::= boolexpr:e {: parser.setResult(e); :} ;\n"
+      + "\n"
+      + "boolexpr ::=    BOOLEAN \n"
+      + "              | true\n"
+      + "              | false\n"
+      + "              | expr < expr\n"
+      + "              | expr <= expr\n"
+      + "              | expr > expr\n"
+      + "              | expr >= expr\n"
+      + "              | expr = expr\n"
+      + "              | ( boolexpr )\n"
+      + "              | not boolexpr\n"
+      + "              | boolexpr and boolexpr\n"
+      + "              | boolexpr or boolexpr\n"
+      + "              | ATTRIBUTE is STRING\n"
+      + "              ;\n"
+      + "\n"
+      + "expr      ::=   NUMBER\n"
+      + "              | ATTRIBUTE\n"
+      + "              | ( expr )\n"
+      + "              | opexpr\n"
+      + "              | funcexpr\n"
+      + "              ;\n"
+      + "\n"
+      + "opexpr    ::=   expr + expr\n"
+      + "              | expr - expr\n"
+      + "              | expr * expr\n"
+      + "              | expr / expr\n"
+      + "              ;\n"
+      + "\n"
+      + "funcexpr ::=    abs ( expr )\n"
+      + "              | sqrt ( expr )\n"
+      + "              | log ( expr )\n"
+      + "              | exp ( expr )\n"
+      + "              | sin ( expr )\n"
+      + "              | cos ( expr )\n"
+      + "              | tan ( expr )\n"
+      + "              | rint ( expr )\n"
+      + "              | floor ( expr )\n"
+      + "              | pow ( expr for base , expr for exponent )\n"
+      + "              | ceil ( expr )\n"
+      + "              ;\n"
+      + "\n"
+      + "Notes:\n"
+      + "- NUMBER\n"
+      + "  any integer or floating point number \n"
+      + "  (but not in scientific notation!)\n"
+      + "- STRING\n"
+      + "  any string surrounded by single quotes; \n"
+      + "  the string may not contain a single quote though.\n"
+      + "- ATTRIBUTE\n"
+      + "  the following placeholders are recognized for \n"
+      + "  attribute values:\n"
+      + "  - CLASS for the class value in case a class attribute is set.\n"
+      + "  - ATTxyz with xyz a number from 1 to # of attributes in the\n"
+      + "    dataset, representing the value of indexed attribute.\n"
+      + "\n"
+      + "Examples:\n"
+      + "- extracting only mammals and birds from the 'zoo' UCI dataset:\n"
+      + "  (CLASS is 'mammal') or (CLASS is 'bird')\n"
+      + "- extracting only animals with at least 2 legs from the 'zoo' UCI dataset:\n"
+      + "  (ATT14 >= 2)\n"
+      + "- extracting only instances with non-missing 'wage-increase-second-year'\n"
+      + "  from the 'labor' UCI dataset:\n"
+      + "  not ismissing(ATT3)\n"
+      ;
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options.
+   */
+  public Enumeration listOptions() {
+    Vector	result;
+    
+    result = new Vector();
+
+    result.addElement(new Option(
+	"\tThe expression to use for filtering\n"
+	+ "\t(default: true).",
+	"E", 1, "-E <expr>"));
+
+    return result.elements();
+  }
+
+
+  /**
+   * Parses a given list of options. <p/>
+   * 
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -E &lt;expr&gt;
+   *  The expression to use for filtering
+   *  (default: true).</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options the list of options as an array of strings
+   * @throws Exception if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+    
+    tmpStr = Utils.getOption('E', options);
+    if (tmpStr.length() != 0)
+      setExpression(tmpStr);
+    else
+      setExpression("true");
+   
+    if (getInputFormat() != null)
+      setInputFormat(getInputFormat());
+  }
+
+  /**
+   * Gets the current settings of the filter.
+   *
+   * @return an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    Vector<String>	result;
+    
+    result = new Vector();
+
+    result.add("-E");
+    result.add("" + getExpression());
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /** 
+   * Returns the Capabilities of this filter.
+   *
+   * @return            the capabilities of this object
+   * @see               Capabilities
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+    result.disableAll();
+
+    // attributes
+    result.enable(Capability.NOMINAL_ATTRIBUTES);
+    result.enable(Capability.NUMERIC_ATTRIBUTES);
+    result.enable(Capability.DATE_ATTRIBUTES);
+    result.enable(Capability.MISSING_VALUES);
+    
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    result.enable(Capability.NUMERIC_CLASS);
+    result.enable(Capability.DATE_CLASS);
+    result.enable(Capability.MISSING_CLASS_VALUES);
+    result.enable(Capability.NO_CLASS);
+    
+    return result;
+  }
+
+  /**
+   * Sets the expression used for filtering.
+   *
+   * @param value	the expression
+   */
+  public void setExpression(String value) {
+    m_Expression = value;
+  }
+
+  /**
+   * Returns the expression used for filtering.
+   *
+   * @return 		the expression
+   */
+  public String getExpression() {
+    return m_Expression;
+  }
+
+  /**
+   * Returns the tip text for this property.
+   * 
+   * @return 		tip text for this property suitable for
+   * 			displaying in the explorer/experimenter gui
+   */
+  public String expressionTipText() {
+    return "The expression to used for filtering the dataset.";
+  }
+
+  /**
+   * Determines the output format based on the input format and returns 
+   * this.
+   *
+   * @param inputFormat     the input format to base the output format on
+   * @return                the output format
+   * @throws Exception      in case the determination goes wrong
+   */
+  protected Instances determineOutputFormat(Instances inputFormat)
+      throws Exception {
+    
+    return new Instances(inputFormat, 0);
+  }
+
+  /**
+   * Processes the given data (may change the provided dataset) and returns
+   * the modified version. This method is called in batchFinished().
+   *
+   * @param instances   the data to process
+   * @return            the modified data
+   * @throws Exception  in case the processing goes wrong
+   * @see               #batchFinished()
+   */
+  protected Instances process(Instances instances) throws Exception {
+    if (!isFirstBatchDone())
+      return Parser.filter(m_Expression, instances);
+    else
+      return instances;
+  }
+
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 6113 $");
+  }
+
+  /**
+   * Main method for running this filter.
+   *
+   * @param args 	arguments for the filter: use -h for help
+   */
+  public static void main(String[] args) {
+    runFilter(new SubsetByExpression(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.cup
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.cup	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.cup	(revision 29)
@@ -0,0 +1,333 @@
+/*
+ * STANDARD ML OF NEW JERSEY COPYRIGHT NOTICE, LICENSE AND DISCLAIMER.
+ * 
+ * Copyright (c) 1989-1998 by Lucent Technologies
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both the
+ * copyright notice and this permission notice and warranty disclaimer appear
+ * in supporting documentation, and that the name of Lucent Technologies, Bell
+ * Labs or any Lucent entity not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior permission.
+ *
+ * Lucent disclaims all warranties with regard to this software, including all
+ * implied warranties of merchantability and fitness. In no event shall Lucent
+ * be liable for any special, indirect or consequential damages or any damages
+ * whatsoever resulting from loss of use, data or profits, whether in an action
+ * of contract, negligence or other tortious action, arising out of or in
+ * connection with the use or performance of this software. 
+ *
+ * Taken from this URL:
+ * http://www.smlnj.org/license.html
+ * 
+ * This license is compatible with the GNU GPL (see section "Standard ML of New
+ * Jersey Copyright License"):
+ * http://www.gnu.org/licenses/license-list.html#StandardMLofNJ
+ */
+
+/*
+ * Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian
+ */
+
+package weka.filters.unsupervised.instance.subsetbyexpression;
+
+import weka.core.*;
+import java_cup.runtime.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A parser for evaluating whether an Instance complies to a boolean expression 
+ * or not.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+
+parser code {:
+  /** variable - value relation. */
+  protected HashMap m_Symbols = new HashMap();
+
+  /** attribute - attribute-type (constants from weka.core.Attribute) relation. */
+  protected Hashtable<String,Integer> m_AttributeTypes = new Hashtable<String,Integer>();
+
+  /** for storing the result of the expression. */
+  protected Boolean m_Result = null;
+
+  /**
+   * Sets the variable - value relation to use.
+   * 
+   * @param value the variable-value relation
+   */
+  public void setSymbols(HashMap value) {
+    m_Symbols = value;
+  }
+
+  /**
+   * Returns the current variable - value relation in use.
+   * 
+   * @return the variable-value relation
+   */
+  public HashMap getSymbols() {
+    return m_Symbols;
+  }
+
+  /**
+   * Sets the attribute - attribute-type relation to use.
+   * 
+   * @param value the att - att-type relation
+   */
+  public void setAttributeTypes(Hashtable value) {
+    m_AttributeTypes = value;
+  }
+
+  /**
+   * Returns the current attribute - attribute-type relation in use.
+   * 
+   * @return the att - att-type relation
+   */
+  public Hashtable getAttributeTypes() {
+    return m_AttributeTypes;
+  }
+
+  /**
+   * Sets the result of the evaluation.
+   * 
+   * @param value the result
+   */
+  public void setResult(Boolean value) {
+    m_Result = value;
+  }
+
+  /**
+   * Returns the result of the evaluation.
+   * 
+   * @return the result
+   */
+  public Boolean getResult() {
+    return m_Result;
+  }
+
+  /**
+   * Returns either a String object for nominal attributes or a Double for numeric
+   * ones. For all other attribute types this method throws an Exception.
+   * It also returns a Double object in case of a missing value (for all
+   * attribute types!).
+   * 
+   * @param instance the instance to work on
+   * @param index the index of the attribute to return
+   * @return the converted value
+   */
+  public static Object getValue(Instance instance, int index) {
+    if (instance.isMissing(index))
+      return new Double(Instance.missingValue());
+    else if (instance.attribute(index).isNominal())
+      return new String(instance.stringValue(index));
+    else if (instance.attribute(index).isNumeric())
+      return new Double(instance.value(index));
+    else
+      throw new IllegalArgumentException(
+          "Unhandled attribute type '" + instance.attribute(index).type() + "'!");
+  }
+
+  /**
+   * Filters the input dataset against the provided expression.
+   *
+   * @param expression the expression used for filtering
+   * @param input the input data
+   * @return the filtered data
+   * @throws Exception if parsing fails
+   */
+  public static Instances filter(String expression, Instances input) throws Exception {
+    // setup output
+    Instances output = new Instances(input, 0);
+    
+    // setup attribute - attribute-type relation
+    Hashtable<String,Integer> attTypes = new Hashtable<String,Integer>();
+    for (int i = 0; i < input.numAttributes(); i++)
+       attTypes.put("ATT" + (i+1), input.attribute(i).type());
+    if (input.classIndex() > -1)
+      attTypes.put("CLASS", input.classAttribute().type());
+    
+    // filter dataset
+    SymbolFactory sf = new DefaultSymbolFactory();
+    HashMap symbols = new HashMap();
+    ByteArrayInputStream parserInput = new ByteArrayInputStream(expression.getBytes());
+    for (int i = 0; i < input.numInstances(); i++) {
+      Instance instance = input.instance(i);
+
+      // setup symbols
+      for (int n = 0; n < instance.numAttributes(); n++) {
+        if (n == instance.classIndex())
+          symbols.put("CLASS", getValue(instance, n));
+        symbols.put("ATT" + (n+1), getValue(instance, n));
+      }
+
+      // evaluate expression
+      parserInput.reset();
+      Parser parser = new Parser(new Scanner(parserInput,sf), sf);
+      parser.setSymbols(symbols);
+      parser.parse();
+      if (parser.getResult())
+        output.add((Instance) instance.copy());
+    }
+
+    return output;
+  }
+
+  /**
+   * Runs the parser from commandline. Takes the following arguments:
+   * <ol>
+   *   <li>expression</li>
+   *   <li>input file</li>
+   *   <li>class index (first|last|num), use 0 to ignore</li>
+   *   <li>output file</li>
+   * </ol>
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    // get expression
+    String expression = args[0];
+
+    // read input data
+    BufferedReader reader = new BufferedReader(new FileReader(args[1]));
+    Instances input = new Instances(reader);
+    reader.close();
+    if (args[2].equals("first"))
+      input.setClassIndex(0);
+    else if (args[2].equals("last"))
+      input.setClassIndex(input.numAttributes() - 1);
+    else
+      input.setClassIndex(Integer.parseInt(args[2]) - 1);
+
+    // process dataset
+    Instances output = filter(expression, input);
+
+    // save output file
+    BufferedWriter writer = new BufferedWriter(new FileWriter(args[3]));
+    writer.write(new Instances(output, 0).toString());
+    writer.newLine();
+    for (int i = 0; i < output.numInstances(); i++) {
+      writer.write(output.instance(i).toString());
+      writer.newLine();
+    }
+    writer.flush();
+    writer.close();
+  }
+:}
+
+terminal COMMA, LPAREN, RPAREN, ISMISSING;
+terminal MINUS, PLUS, TIMES, DIVISION;
+terminal ABS, SQRT, LOG, EXP, SIN, COS, TAN, RINT, FLOOR, POW, CEIL;
+terminal TRUE, FALSE, LT, LE, GT, GE, EQ, NOT, AND, OR, IS;
+terminal Double NUMBER;
+terminal Boolean BOOLEAN;
+terminal String ATTRIBUTE, STRING;
+
+non terminal boolexpr_list, boolexpr_part;
+non terminal Double expr;
+non terminal Double opexpr;
+non terminal Double funcexpr;
+non terminal Boolean boolexpr;
+
+precedence left PLUS, MINUS;
+precedence left TIMES, DIVISION;
+precedence left LPAREN, RPAREN;
+precedence left ABS, SQRT, LOG, EXP, SIN, COS, TAN, RINT, FLOOR, POW, CEIL;
+precedence left AND, OR;
+precedence left NOT;
+
+boolexpr_list ::= boolexpr_list boolexpr_part | boolexpr_part;
+boolexpr_part ::= boolexpr:e {: parser.setResult(e); :} ;
+boolexpr ::=    BOOLEAN:b 
+                {: RESULT = b; :}
+              | TRUE
+                {: RESULT = new Boolean(true); :}
+              | FALSE
+                {: RESULT = new Boolean(false); :}
+              | expr:l LT expr:r
+                {: RESULT = new Boolean(l.doubleValue() < r.doubleValue()); :}
+              | expr:l LE expr:r
+                {: RESULT = new Boolean(l.doubleValue() <= r.doubleValue()); :}
+              | expr:l GT expr:r
+                {: RESULT = new Boolean(l.doubleValue() > r.doubleValue()); :}
+              | expr:l GE expr:r
+                {: RESULT = new Boolean(l.doubleValue() >= r.doubleValue()); :}
+              | expr:l EQ expr:r
+                {: RESULT = new Boolean(l.doubleValue() == r.doubleValue()); :}
+              | LPAREN boolexpr:b RPAREN
+                {: RESULT = b; :}
+              | NOT boolexpr:b
+                {: RESULT = !b; :}
+              | boolexpr:l AND boolexpr:r
+                {: RESULT = l && r; :}
+              | boolexpr:l OR boolexpr:r
+                {: RESULT = l || r; :}
+              | ATTRIBUTE:a IS STRING:s
+                {: if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (parser.getSymbols().get(a) instanceof String) && ((String) parser.getSymbols().get(a)).equals(s);
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                :}
+              | ISMISSING LPAREN ATTRIBUTE:a RPAREN
+                {: if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (parser.getSymbols().get(a) instanceof Double) && Instance.isMissingValue((Double) parser.getSymbols().get(a));
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                :}
+              ;
+
+expr      ::=   NUMBER:n
+                {: RESULT = n; :}
+              | ATTRIBUTE:a
+                {: if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (Double) parser.getSymbols().get(a); 
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                :}
+              | LPAREN expr:e RPAREN
+                {: RESULT = e; :}
+              | opexpr:o
+                {: RESULT = o; :}
+              | funcexpr:f
+                {: RESULT = f; :}
+              ;
+
+opexpr    ::=   expr:l PLUS expr:r
+                {: RESULT = new Double(l.doubleValue() + r.doubleValue()); :}
+              | expr:l MINUS expr:r
+                {: RESULT = new Double(l.doubleValue() - r.doubleValue()); :}
+              | expr:l TIMES expr:r
+                {: RESULT = new Double(l.doubleValue() * r.doubleValue()); :}
+              | expr:l DIVISION expr:r
+                {: RESULT = new Double(l.doubleValue() / r.doubleValue()); :}
+              ;
+
+funcexpr ::=    ABS LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.abs(e)); :}
+              | SQRT LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.sqrt(e)); :}
+              | LOG LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.log(e)); :}
+              | EXP LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.exp(e)); :}
+              | SIN LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.sin(e)); :}
+              | COS LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.cos(e)); :}
+              | TAN LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.tan(e)); :}
+              | RINT LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.rint(e)); :}
+              | FLOOR LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.floor(e)); :}
+              | POW LPAREN expr:base COMMA expr:exponent RPAREN
+                {: RESULT = new Double(Math.pow(base, exponent)); :}
+              | CEIL LPAREN expr:e RPAREN
+                {: RESULT = new Double(Math.ceil(e)); :}
+              ;
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Parser.java	(revision 29)
@@ -0,0 +1,1119 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Sun Jan 11 11:14:12 NZDT 2009
+//----------------------------------------------------
+
+package weka.filters.unsupervised.instance.subsetbyexpression;
+
+import weka.core.*;
+import java_cup.runtime.*;
+import java.io.*;
+import java.util.*;
+
+/** CUP v0.11a beta 20060608 generated parser.
+  * @version Sun Jan 11 11:14:12 NZDT 2009
+  */
+public class Parser extends java_cup.runtime.lr_parser {
+
+  /** Default constructor. */
+  public Parser() {super();}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s) {super(s);}
+
+  /** Constructor which sets the default scanner. */
+  public Parser(java_cup.runtime.Scanner s, java_cup.runtime.SymbolFactory sf) {super(s,sf);}
+
+  /** Production table. */
+  protected static final short _production_table[][] = 
+    unpackFromStrings(new String[] {
+    "\000\046\000\002\002\004\000\002\002\004\000\002\002" +
+    "\003\000\002\003\003\000\002\007\003\000\002\007\003" +
+    "\000\002\007\003\000\002\007\005\000\002\007\005\000" +
+    "\002\007\005\000\002\007\005\000\002\007\005\000\002" +
+    "\007\005\000\002\007\004\000\002\007\005\000\002\007" +
+    "\005\000\002\007\005\000\002\007\006\000\002\004\003" +
+    "\000\002\004\003\000\002\004\005\000\002\004\003\000" +
+    "\002\004\003\000\002\005\005\000\002\005\005\000\002" +
+    "\005\005\000\002\005\005\000\002\006\006\000\002\006" +
+    "\006\000\002\006\006\000\002\006\006\000\002\006\006" +
+    "\000\002\006\006\000\002\006\006\000\002\006\006\000" +
+    "\002\006\006\000\002\006\010\000\002\006\006" });
+
+  /** Access to production table. */
+  public short[][] production_table() {return _production_table;}
+
+  /** Parse-action table. */
+  protected static final short[][] _action_table = 
+    unpackFromStrings(new String[] {
+    "\000\142\000\050\005\016\007\033\014\026\015\012\016" +
+    "\020\017\021\020\004\021\006\022\034\023\014\024\015" +
+    "\025\011\026\017\027\013\030\031\036\010\042\024\043" +
+    "\022\044\007\001\002\000\004\005\142\001\002\000\024" +
+    "\010\045\011\043\012\046\013\044\031\110\032\106\033" +
+    "\107\034\104\035\105\001\002\000\004\005\137\001\002" +
+    "\000\030\006\uffee\010\uffee\011\uffee\012\uffee\013\uffee\031" +
+    "\uffee\032\uffee\033\uffee\034\uffee\035\uffee\041\135\001\002" +
+    "\000\050\005\016\007\033\014\026\015\012\016\020\017" +
+    "\021\020\004\021\006\022\034\023\014\024\015\025\011" +
+    "\026\017\027\013\030\031\036\010\042\024\043\022\044" +
+    "\007\001\002\000\004\005\127\001\002\000\004\005\124" +
+    "\001\002\000\060\002\ufffc\005\ufffc\006\ufffc\007\ufffc\014" +
+    "\ufffc\015\ufffc\016\ufffc\017\ufffc\020\ufffc\021\ufffc\022\ufffc" +
+    "\023\ufffc\024\ufffc\025\ufffc\026\ufffc\027\ufffc\030\ufffc\036" +
+    "\ufffc\037\ufffc\040\ufffc\042\ufffc\043\ufffc\044\ufffc\001\002" +
+    "\000\004\005\121\001\002\000\004\005\116\001\002\000" +
+    "\050\005\016\007\033\014\026\015\012\016\020\017\021" +
+    "\020\004\021\006\022\034\023\014\024\015\025\011\026" +
+    "\017\027\013\030\031\036\010\042\024\043\022\044\007" +
+    "\001\002\000\004\005\076\001\002\000\004\005\073\001" +
+    "\002\000\004\005\070\001\002\000\060\002\ufffd\005\ufffd" +
+    "\006\ufffd\007\ufffd\014\ufffd\015\ufffd\016\ufffd\017\ufffd\020" +
+    "\ufffd\021\ufffd\022\ufffd\023\ufffd\024\ufffd\025\ufffd\026\ufffd" +
+    "\027\ufffd\030\ufffd\036\ufffd\037\ufffd\040\ufffd\042\ufffd\043" +
+    "\ufffd\044\ufffd\001\002\000\052\002\uffff\005\uffff\007\uffff" +
+    "\014\uffff\015\uffff\016\uffff\017\uffff\020\uffff\021\uffff\022" +
+    "\uffff\023\uffff\024\uffff\025\uffff\026\uffff\027\uffff\030\uffff" +
+    "\036\uffff\042\uffff\043\uffff\044\uffff\001\002\000\104\002" +
+    "\uffef\004\uffef\005\uffef\006\uffef\007\uffef\010\uffef\011\uffef" +
+    "\012\uffef\013\uffef\014\uffef\015\uffef\016\uffef\017\uffef\020" +
+    "\uffef\021\uffef\022\uffef\023\uffef\024\uffef\025\uffef\026\uffef" +
+    "\027\uffef\030\uffef\031\uffef\032\uffef\033\uffef\034\uffef\035" +
+    "\uffef\036\uffef\037\uffef\040\uffef\042\uffef\043\uffef\044\uffef" +
+    "\001\002\000\104\002\uffeb\004\uffeb\005\uffeb\006\uffeb\007" +
+    "\uffeb\010\uffeb\011\uffeb\012\uffeb\013\uffeb\014\uffeb\015\uffeb" +
+    "\016\uffeb\017\uffeb\020\uffeb\021\uffeb\022\uffeb\023\uffeb\024" +
+    "\uffeb\025\uffeb\026\uffeb\027\uffeb\030\uffeb\031\uffeb\032\uffeb" +
+    "\033\uffeb\034\uffeb\035\uffeb\036\uffeb\037\uffeb\040\uffeb\042" +
+    "\uffeb\043\uffeb\044\uffeb\001\002\000\004\005\065\001\002" +
+    "\000\052\002\064\005\016\007\033\014\026\015\012\016" +
+    "\020\017\021\020\004\021\006\022\034\023\014\024\015" +
+    "\025\011\026\017\027\013\030\031\036\010\042\024\043" +
+    "\022\044\007\001\002\000\056\002\ufffe\005\ufffe\007\ufffe" +
+    "\014\ufffe\015\ufffe\016\ufffe\017\ufffe\020\ufffe\021\ufffe\022" +
+    "\ufffe\023\ufffe\024\ufffe\025\ufffe\026\ufffe\027\ufffe\030\ufffe" +
+    "\036\ufffe\037\057\040\060\042\ufffe\043\ufffe\044\ufffe\001" +
+    "\002\000\060\002\ufffb\005\ufffb\006\ufffb\007\ufffb\014\ufffb" +
+    "\015\ufffb\016\ufffb\017\ufffb\020\ufffb\021\ufffb\022\ufffb\023" +
+    "\ufffb\024\ufffb\025\ufffb\026\ufffb\027\ufffb\030\ufffb\036\ufffb" +
+    "\037\ufffb\040\ufffb\042\ufffb\043\ufffb\044\ufffb\001\002\000" +
+    "\104\002\uffec\004\uffec\005\uffec\006\uffec\007\uffec\010\uffec" +
+    "\011\uffec\012\uffec\013\uffec\014\uffec\015\uffec\016\uffec\017" +
+    "\uffec\020\uffec\021\uffec\022\uffec\023\uffec\024\uffec\025\uffec" +
+    "\026\uffec\027\uffec\030\uffec\031\uffec\032\uffec\033\uffec\034" +
+    "\uffec\035\uffec\036\uffec\037\uffec\040\uffec\042\uffec\043\uffec" +
+    "\044\uffec\001\002\000\004\005\054\001\002\000\004\005" +
+    "\035\001\002\000\036\005\040\014\026\015\012\016\020" +
+    "\017\021\020\004\021\006\022\034\023\014\024\015\025" +
+    "\011\026\017\042\024\044\036\001\002\000\104\002\uffee" +
+    "\004\uffee\005\uffee\006\uffee\007\uffee\010\uffee\011\uffee\012" +
+    "\uffee\013\uffee\014\uffee\015\uffee\016\uffee\017\uffee\020\uffee" +
+    "\021\uffee\022\uffee\023\uffee\024\uffee\025\uffee\026\uffee\027" +
+    "\uffee\030\uffee\031\uffee\032\uffee\033\uffee\034\uffee\035\uffee" +
+    "\036\uffee\037\uffee\040\uffee\042\uffee\043\uffee\044\uffee\001" +
+    "\002\000\014\006\053\010\045\011\043\012\046\013\044" +
+    "\001\002\000\036\005\040\014\026\015\012\016\020\017" +
+    "\021\020\004\021\006\022\034\023\014\024\015\025\011" +
+    "\026\017\042\024\044\036\001\002\000\014\006\042\010" +
+    "\045\011\043\012\046\013\044\001\002\000\104\002\uffed" +
+    "\004\uffed\005\uffed\006\uffed\007\uffed\010\uffed\011\uffed\012" +
+    "\uffed\013\uffed\014\uffed\015\uffed\016\uffed\017\uffed\020\uffed" +
+    "\021\uffed\022\uffed\023\uffed\024\uffed\025\uffed\026\uffed\027" +
+    "\uffed\030\uffed\031\uffed\032\uffed\033\uffed\034\uffed\035\uffed" +
+    "\036\uffed\037\uffed\040\uffed\042\uffed\043\uffed\044\uffed\001" +
+    "\002\000\036\005\040\014\026\015\012\016\020\017\021" +
+    "\020\004\021\006\022\034\023\014\024\015\025\011\026" +
+    "\017\042\024\044\036\001\002\000\036\005\040\014\026" +
+    "\015\012\016\020\017\021\020\004\021\006\022\034\023" +
+    "\014\024\015\025\011\026\017\042\024\044\036\001\002" +
+    "\000\036\005\040\014\026\015\012\016\020\017\021\020" +
+    "\004\021\006\022\034\023\014\024\015\025\011\026\017" +
+    "\042\024\044\036\001\002\000\036\005\040\014\026\015" +
+    "\012\016\020\017\021\020\004\021\006\022\034\023\014" +
+    "\024\015\025\011\026\017\042\024\044\036\001\002\000" +
+    "\104\002\uffe8\004\uffe8\005\uffe8\006\uffe8\007\uffe8\010\uffe8" +
+    "\011\uffe8\012\uffe8\013\uffe8\014\uffe8\015\uffe8\016\uffe8\017" +
+    "\uffe8\020\uffe8\021\uffe8\022\uffe8\023\uffe8\024\uffe8\025\uffe8" +
+    "\026\uffe8\027\uffe8\030\uffe8\031\uffe8\032\uffe8\033\uffe8\034" +
+    "\uffe8\035\uffe8\036\uffe8\037\uffe8\040\uffe8\042\uffe8\043\uffe8" +
+    "\044\uffe8\001\002\000\104\002\uffe9\004\uffe9\005\uffe9\006" +
+    "\uffe9\007\uffe9\010\uffe9\011\uffe9\012\046\013\044\014\uffe9" +
+    "\015\uffe9\016\uffe9\017\uffe9\020\uffe9\021\uffe9\022\uffe9\023" +
+    "\uffe9\024\uffe9\025\uffe9\026\uffe9\027\uffe9\030\uffe9\031\uffe9" +
+    "\032\uffe9\033\uffe9\034\uffe9\035\uffe9\036\uffe9\037\uffe9\040" +
+    "\uffe9\042\uffe9\043\uffe9\044\uffe9\001\002\000\104\002\uffe7" +
+    "\004\uffe7\005\uffe7\006\uffe7\007\uffe7\010\uffe7\011\uffe7\012" +
+    "\uffe7\013\uffe7\014\uffe7\015\uffe7\016\uffe7\017\uffe7\020\uffe7" +
+    "\021\uffe7\022\uffe7\023\uffe7\024\uffe7\025\uffe7\026\uffe7\027" +
+    "\uffe7\030\uffe7\031\uffe7\032\uffe7\033\uffe7\034\uffe7\035\uffe7" +
+    "\036\uffe7\037\uffe7\040\uffe7\042\uffe7\043\uffe7\044\uffe7\001" +
+    "\002\000\104\002\uffea\004\uffea\005\uffea\006\uffea\007\uffea" +
+    "\010\uffea\011\uffea\012\046\013\044\014\uffea\015\uffea\016" +
+    "\uffea\017\uffea\020\uffea\021\uffea\022\uffea\023\uffea\024\uffea" +
+    "\025\uffea\026\uffea\027\uffea\030\uffea\031\uffea\032\uffea\033" +
+    "\uffea\034\uffea\035\uffea\036\uffea\037\uffea\040\uffea\042\uffea" +
+    "\043\uffea\044\uffea\001\002\000\104\002\uffe0\004\uffe0\005" +
+    "\uffe0\006\uffe0\007\uffe0\010\uffe0\011\uffe0\012\uffe0\013\uffe0" +
+    "\014\uffe0\015\uffe0\016\uffe0\017\uffe0\020\uffe0\021\uffe0\022" +
+    "\uffe0\023\uffe0\024\uffe0\025\uffe0\026\uffe0\027\uffe0\030\uffe0" +
+    "\031\uffe0\032\uffe0\033\uffe0\034\uffe0\035\uffe0\036\uffe0\037" +
+    "\uffe0\040\uffe0\042\uffe0\043\uffe0\044\uffe0\001\002\000\004" +
+    "\044\055\001\002\000\004\006\056\001\002\000\060\002" +
+    "\ufff0\005\ufff0\006\ufff0\007\ufff0\014\ufff0\015\ufff0\016\ufff0" +
+    "\017\ufff0\020\ufff0\021\ufff0\022\ufff0\023\ufff0\024\ufff0\025" +
+    "\ufff0\026\ufff0\027\ufff0\030\ufff0\036\ufff0\037\ufff0\040\ufff0" +
+    "\042\ufff0\043\ufff0\044\ufff0\001\002\000\050\005\016\007" +
+    "\033\014\026\015\012\016\020\017\021\020\004\021\006" +
+    "\022\034\023\014\024\015\025\011\026\017\027\013\030" +
+    "\031\036\010\042\024\043\022\044\007\001\002\000\050" +
+    "\005\016\007\033\014\026\015\012\016\020\017\021\020" +
+    "\004\021\006\022\034\023\014\024\015\025\011\026\017" +
+    "\027\013\030\031\036\010\042\024\043\022\044\007\001" +
+    "\002\000\060\002\ufff2\005\ufff2\006\ufff2\007\ufff2\014\ufff2" +
+    "\015\ufff2\016\ufff2\017\ufff2\020\ufff2\021\ufff2\022\ufff2\023" +
+    "\ufff2\024\ufff2\025\ufff2\026\ufff2\027\ufff2\030\ufff2\036\ufff2" +
+    "\037\ufff2\040\ufff2\042\ufff2\043\ufff2\044\ufff2\001\002\000" +
+    "\060\002\ufff3\005\ufff3\006\ufff3\007\ufff3\014\ufff3\015\ufff3" +
+    "\016\ufff3\017\ufff3\020\ufff3\021\ufff3\022\ufff3\023\ufff3\024" +
+    "\ufff3\025\ufff3\026\ufff3\027\ufff3\030\ufff3\036\ufff3\037\ufff3" +
+    "\040\ufff3\042\ufff3\043\ufff3\044\ufff3\001\002\000\052\002" +
+    "\001\005\001\007\001\014\001\015\001\016\001\017\001" +
+    "\020\001\021\001\022\001\023\001\024\001\025\001\026" +
+    "\001\027\001\030\001\036\001\042\001\043\001\044\001" +
+    "\001\002\000\004\002\000\001\002\000\036\005\040\014" +
+    "\026\015\012\016\020\017\021\020\004\021\006\022\034" +
+    "\023\014\024\015\025\011\026\017\042\024\044\036\001" +
+    "\002\000\014\006\067\010\045\011\043\012\046\013\044" +
+    "\001\002\000\104\002\uffe6\004\uffe6\005\uffe6\006\uffe6\007" +
+    "\uffe6\010\uffe6\011\uffe6\012\uffe6\013\uffe6\014\uffe6\015\uffe6" +
+    "\016\uffe6\017\uffe6\020\uffe6\021\uffe6\022\uffe6\023\uffe6\024" +
+    "\uffe6\025\uffe6\026\uffe6\027\uffe6\030\uffe6\031\uffe6\032\uffe6" +
+    "\033\uffe6\034\uffe6\035\uffe6\036\uffe6\037\uffe6\040\uffe6\042" +
+    "\uffe6\043\uffe6\044\uffe6\001\002\000\036\005\040\014\026" +
+    "\015\012\016\020\017\021\020\004\021\006\022\034\023" +
+    "\014\024\015\025\011\026\017\042\024\044\036\001\002" +
+    "\000\014\006\072\010\045\011\043\012\046\013\044\001" +
+    "\002\000\104\002\uffe3\004\uffe3\005\uffe3\006\uffe3\007\uffe3" +
+    "\010\uffe3\011\uffe3\012\uffe3\013\uffe3\014\uffe3\015\uffe3\016" +
+    "\uffe3\017\uffe3\020\uffe3\021\uffe3\022\uffe3\023\uffe3\024\uffe3" +
+    "\025\uffe3\026\uffe3\027\uffe3\030\uffe3\031\uffe3\032\uffe3\033" +
+    "\uffe3\034\uffe3\035\uffe3\036\uffe3\037\uffe3\040\uffe3\042\uffe3" +
+    "\043\uffe3\044\uffe3\001\002\000\036\005\040\014\026\015" +
+    "\012\016\020\017\021\020\004\021\006\022\034\023\014" +
+    "\024\015\025\011\026\017\042\024\044\036\001\002\000" +
+    "\014\006\075\010\045\011\043\012\046\013\044\001\002" +
+    "\000\104\002\uffe4\004\uffe4\005\uffe4\006\uffe4\007\uffe4\010" +
+    "\uffe4\011\uffe4\012\uffe4\013\uffe4\014\uffe4\015\uffe4\016\uffe4" +
+    "\017\uffe4\020\uffe4\021\uffe4\022\uffe4\023\uffe4\024\uffe4\025" +
+    "\uffe4\026\uffe4\027\uffe4\030\uffe4\031\uffe4\032\uffe4\033\uffe4" +
+    "\034\uffe4\035\uffe4\036\uffe4\037\uffe4\040\uffe4\042\uffe4\043" +
+    "\uffe4\044\uffe4\001\002\000\036\005\040\014\026\015\012" +
+    "\016\020\017\021\020\004\021\006\022\034\023\014\024" +
+    "\015\025\011\026\017\042\024\044\036\001\002\000\014" +
+    "\006\100\010\045\011\043\012\046\013\044\001\002\000" +
+    "\104\002\uffdc\004\uffdc\005\uffdc\006\uffdc\007\uffdc\010\uffdc" +
+    "\011\uffdc\012\uffdc\013\uffdc\014\uffdc\015\uffdc\016\uffdc\017" +
+    "\uffdc\020\uffdc\021\uffdc\022\uffdc\023\uffdc\024\uffdc\025\uffdc" +
+    "\026\uffdc\027\uffdc\030\uffdc\031\uffdc\032\uffdc\033\uffdc\034" +
+    "\uffdc\035\uffdc\036\uffdc\037\uffdc\040\uffdc\042\uffdc\043\uffdc" +
+    "\044\uffdc\001\002\000\026\006\042\010\045\011\043\012" +
+    "\046\013\044\031\110\032\106\033\107\034\104\035\105" +
+    "\001\002\000\010\006\103\037\057\040\060\001\002\000" +
+    "\060\002\ufff5\005\ufff5\006\ufff5\007\ufff5\014\ufff5\015\ufff5" +
+    "\016\ufff5\017\ufff5\020\ufff5\021\ufff5\022\ufff5\023\ufff5\024" +
+    "\ufff5\025\ufff5\026\ufff5\027\ufff5\030\ufff5\036\ufff5\037\ufff5" +
+    "\040\ufff5\042\ufff5\043\ufff5\044\ufff5\001\002\000\036\005" +
+    "\040\014\026\015\012\016\020\017\021\020\004\021\006" +
+    "\022\034\023\014\024\015\025\011\026\017\042\024\044" +
+    "\036\001\002\000\036\005\040\014\026\015\012\016\020" +
+    "\017\021\020\004\021\006\022\034\023\014\024\015\025" +
+    "\011\026\017\042\024\044\036\001\002\000\036\005\040" +
+    "\014\026\015\012\016\020\017\021\020\004\021\006\022" +
+    "\034\023\014\024\015\025\011\026\017\042\024\044\036" +
+    "\001\002\000\036\005\040\014\026\015\012\016\020\017" +
+    "\021\020\004\021\006\022\034\023\014\024\015\025\011" +
+    "\026\017\042\024\044\036\001\002\000\036\005\040\014" +
+    "\026\015\012\016\020\017\021\020\004\021\006\022\034" +
+    "\023\014\024\015\025\011\026\017\042\024\044\036\001" +
+    "\002\000\070\002\ufffa\005\ufffa\006\ufffa\007\ufffa\010\045" +
+    "\011\043\012\046\013\044\014\ufffa\015\ufffa\016\ufffa\017" +
+    "\ufffa\020\ufffa\021\ufffa\022\ufffa\023\ufffa\024\ufffa\025\ufffa" +
+    "\026\ufffa\027\ufffa\030\ufffa\036\ufffa\037\ufffa\040\ufffa\042" +
+    "\ufffa\043\ufffa\044\ufffa\001\002\000\070\002\ufff8\005\ufff8" +
+    "\006\ufff8\007\ufff8\010\045\011\043\012\046\013\044\014" +
+    "\ufff8\015\ufff8\016\ufff8\017\ufff8\020\ufff8\021\ufff8\022\ufff8" +
+    "\023\ufff8\024\ufff8\025\ufff8\026\ufff8\027\ufff8\030\ufff8\036" +
+    "\ufff8\037\ufff8\040\ufff8\042\ufff8\043\ufff8\044\ufff8\001\002" +
+    "\000\070\002\ufff9\005\ufff9\006\ufff9\007\ufff9\010\045\011" +
+    "\043\012\046\013\044\014\ufff9\015\ufff9\016\ufff9\017\ufff9" +
+    "\020\ufff9\021\ufff9\022\ufff9\023\ufff9\024\ufff9\025\ufff9\026" +
+    "\ufff9\027\ufff9\030\ufff9\036\ufff9\037\ufff9\040\ufff9\042\ufff9" +
+    "\043\ufff9\044\ufff9\001\002\000\070\002\ufff6\005\ufff6\006" +
+    "\ufff6\007\ufff6\010\045\011\043\012\046\013\044\014\ufff6" +
+    "\015\ufff6\016\ufff6\017\ufff6\020\ufff6\021\ufff6\022\ufff6\023" +
+    "\ufff6\024\ufff6\025\ufff6\026\ufff6\027\ufff6\030\ufff6\036\ufff6" +
+    "\037\ufff6\040\ufff6\042\ufff6\043\ufff6\044\ufff6\001\002\000" +
+    "\070\002\ufff7\005\ufff7\006\ufff7\007\ufff7\010\045\011\043" +
+    "\012\046\013\044\014\ufff7\015\ufff7\016\ufff7\017\ufff7\020" +
+    "\ufff7\021\ufff7\022\ufff7\023\ufff7\024\ufff7\025\ufff7\026\ufff7" +
+    "\027\ufff7\030\ufff7\036\ufff7\037\ufff7\040\ufff7\042\ufff7\043" +
+    "\ufff7\044\ufff7\001\002\000\036\005\040\014\026\015\012" +
+    "\016\020\017\021\020\004\021\006\022\034\023\014\024" +
+    "\015\025\011\026\017\042\024\044\036\001\002\000\014" +
+    "\006\120\010\045\011\043\012\046\013\044\001\002\000" +
+    "\104\002\uffde\004\uffde\005\uffde\006\uffde\007\uffde\010\uffde" +
+    "\011\uffde\012\uffde\013\uffde\014\uffde\015\uffde\016\uffde\017" +
+    "\uffde\020\uffde\021\uffde\022\uffde\023\uffde\024\uffde\025\uffde" +
+    "\026\uffde\027\uffde\030\uffde\031\uffde\032\uffde\033\uffde\034" +
+    "\uffde\035\uffde\036\uffde\037\uffde\040\uffde\042\uffde\043\uffde" +
+    "\044\uffde\001\002\000\036\005\040\014\026\015\012\016" +
+    "\020\017\021\020\004\021\006\022\034\023\014\024\015" +
+    "\025\011\026\017\042\024\044\036\001\002\000\014\006" +
+    "\123\010\045\011\043\012\046\013\044\001\002\000\104" +
+    "\002\uffdf\004\uffdf\005\uffdf\006\uffdf\007\uffdf\010\uffdf\011" +
+    "\uffdf\012\uffdf\013\uffdf\014\uffdf\015\uffdf\016\uffdf\017\uffdf" +
+    "\020\uffdf\021\uffdf\022\uffdf\023\uffdf\024\uffdf\025\uffdf\026" +
+    "\uffdf\027\uffdf\030\uffdf\031\uffdf\032\uffdf\033\uffdf\034\uffdf" +
+    "\035\uffdf\036\uffdf\037\uffdf\040\uffdf\042\uffdf\043\uffdf\044" +
+    "\uffdf\001\002\000\036\005\040\014\026\015\012\016\020" +
+    "\017\021\020\004\021\006\022\034\023\014\024\015\025" +
+    "\011\026\017\042\024\044\036\001\002\000\014\006\126" +
+    "\010\045\011\043\012\046\013\044\001\002\000\104\002" +
+    "\uffe5\004\uffe5\005\uffe5\006\uffe5\007\uffe5\010\uffe5\011\uffe5" +
+    "\012\uffe5\013\uffe5\014\uffe5\015\uffe5\016\uffe5\017\uffe5\020" +
+    "\uffe5\021\uffe5\022\uffe5\023\uffe5\024\uffe5\025\uffe5\026\uffe5" +
+    "\027\uffe5\030\uffe5\031\uffe5\032\uffe5\033\uffe5\034\uffe5\035" +
+    "\uffe5\036\uffe5\037\uffe5\040\uffe5\042\uffe5\043\uffe5\044\uffe5" +
+    "\001\002\000\036\005\040\014\026\015\012\016\020\017" +
+    "\021\020\004\021\006\022\034\023\014\024\015\025\011" +
+    "\026\017\042\024\044\036\001\002\000\014\004\131\010" +
+    "\045\011\043\012\046\013\044\001\002\000\036\005\040" +
+    "\014\026\015\012\016\020\017\021\020\004\021\006\022" +
+    "\034\023\014\024\015\025\011\026\017\042\024\044\036" +
+    "\001\002\000\014\006\133\010\045\011\043\012\046\013" +
+    "\044\001\002\000\104\002\uffdd\004\uffdd\005\uffdd\006\uffdd" +
+    "\007\uffdd\010\uffdd\011\uffdd\012\uffdd\013\uffdd\014\uffdd\015" +
+    "\uffdd\016\uffdd\017\uffdd\020\uffdd\021\uffdd\022\uffdd\023\uffdd" +
+    "\024\uffdd\025\uffdd\026\uffdd\027\uffdd\030\uffdd\031\uffdd\032" +
+    "\uffdd\033\uffdd\034\uffdd\035\uffdd\036\uffdd\037\uffdd\040\uffdd" +
+    "\042\uffdd\043\uffdd\044\uffdd\001\002\000\060\002\ufff4\005" +
+    "\ufff4\006\ufff4\007\ufff4\014\ufff4\015\ufff4\016\ufff4\017\ufff4" +
+    "\020\ufff4\021\ufff4\022\ufff4\023\ufff4\024\ufff4\025\ufff4\026" +
+    "\ufff4\027\ufff4\030\ufff4\036\ufff4\037\ufff4\040\ufff4\042\ufff4" +
+    "\043\ufff4\044\ufff4\001\002\000\004\045\136\001\002\000" +
+    "\060\002\ufff1\005\ufff1\006\ufff1\007\ufff1\014\ufff1\015\ufff1" +
+    "\016\ufff1\017\ufff1\020\ufff1\021\ufff1\022\ufff1\023\ufff1\024" +
+    "\ufff1\025\ufff1\026\ufff1\027\ufff1\030\ufff1\036\ufff1\037\ufff1" +
+    "\040\ufff1\042\ufff1\043\ufff1\044\ufff1\001\002\000\036\005" +
+    "\040\014\026\015\012\016\020\017\021\020\004\021\006" +
+    "\022\034\023\014\024\015\025\011\026\017\042\024\044" +
+    "\036\001\002\000\014\006\141\010\045\011\043\012\046" +
+    "\013\044\001\002\000\104\002\uffe1\004\uffe1\005\uffe1\006" +
+    "\uffe1\007\uffe1\010\uffe1\011\uffe1\012\uffe1\013\uffe1\014\uffe1" +
+    "\015\uffe1\016\uffe1\017\uffe1\020\uffe1\021\uffe1\022\uffe1\023" +
+    "\uffe1\024\uffe1\025\uffe1\026\uffe1\027\uffe1\030\uffe1\031\uffe1" +
+    "\032\uffe1\033\uffe1\034\uffe1\035\uffe1\036\uffe1\037\uffe1\040" +
+    "\uffe1\042\uffe1\043\uffe1\044\uffe1\001\002\000\036\005\040" +
+    "\014\026\015\012\016\020\017\021\020\004\021\006\022" +
+    "\034\023\014\024\015\025\011\026\017\042\024\044\036" +
+    "\001\002\000\014\006\144\010\045\011\043\012\046\013" +
+    "\044\001\002\000\104\002\uffe2\004\uffe2\005\uffe2\006\uffe2" +
+    "\007\uffe2\010\uffe2\011\uffe2\012\uffe2\013\uffe2\014\uffe2\015" +
+    "\uffe2\016\uffe2\017\uffe2\020\uffe2\021\uffe2\022\uffe2\023\uffe2" +
+    "\024\uffe2\025\uffe2\026\uffe2\027\uffe2\030\uffe2\031\uffe2\032" +
+    "\uffe2\033\uffe2\034\uffe2\035\uffe2\036\uffe2\037\uffe2\040\uffe2" +
+    "\042\uffe2\043\uffe2\044\uffe2\001\002" });
+
+  /** Access to parse-action table. */
+  public short[][] action_table() {return _action_table;}
+
+  /** <code>reduce_goto</code> table. */
+  protected static final short[][] _reduce_table = 
+    unpackFromStrings(new String[] {
+    "\000\142\000\016\002\026\003\022\004\004\005\031\006" +
+    "\024\007\027\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\012\004\004\005" +
+    "\031\006\024\007\133\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\002\001" +
+    "\001\000\012\004\100\005\031\006\024\007\101\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\014\003\062\004\004\005" +
+    "\031\006\024\007\027\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\002\001" +
+    "\001\000\010\004\036\005\031\006\024\001\001\000\002" +
+    "\001\001\000\002\001\001\000\010\004\040\005\031\006" +
+    "\024\001\001\000\002\001\001\000\002\001\001\000\010" +
+    "\004\051\005\031\006\024\001\001\000\010\004\050\005" +
+    "\031\006\024\001\001\000\010\004\047\005\031\006\024" +
+    "\001\001\000\010\004\046\005\031\006\024\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\002\001\001\000\002\001" +
+    "\001\000\002\001\001\000\012\004\004\005\031\006\024" +
+    "\007\061\001\001\000\012\004\004\005\031\006\024\007" +
+    "\060\001\001\000\002\001\001\000\002\001\001\000\002" +
+    "\001\001\000\002\001\001\000\010\004\065\005\031\006" +
+    "\024\001\001\000\002\001\001\000\002\001\001\000\010" +
+    "\004\070\005\031\006\024\001\001\000\002\001\001\000" +
+    "\002\001\001\000\010\004\073\005\031\006\024\001\001" +
+    "\000\002\001\001\000\002\001\001\000\010\004\076\005" +
+    "\031\006\024\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\002\001\001\000\002\001\001\000" +
+    "\010\004\114\005\031\006\024\001\001\000\010\004\113" +
+    "\005\031\006\024\001\001\000\010\004\112\005\031\006" +
+    "\024\001\001\000\010\004\111\005\031\006\024\001\001" +
+    "\000\010\004\110\005\031\006\024\001\001\000\002\001" +
+    "\001\000\002\001\001\000\002\001\001\000\002\001\001" +
+    "\000\002\001\001\000\010\004\116\005\031\006\024\001" +
+    "\001\000\002\001\001\000\002\001\001\000\010\004\121" +
+    "\005\031\006\024\001\001\000\002\001\001\000\002\001" +
+    "\001\000\010\004\124\005\031\006\024\001\001\000\002" +
+    "\001\001\000\002\001\001\000\010\004\127\005\031\006" +
+    "\024\001\001\000\002\001\001\000\010\004\131\005\031" +
+    "\006\024\001\001\000\002\001\001\000\002\001\001\000" +
+    "\002\001\001\000\002\001\001\000\002\001\001\000\010" +
+    "\004\137\005\031\006\024\001\001\000\002\001\001\000" +
+    "\002\001\001\000\010\004\142\005\031\006\024\001\001" +
+    "\000\002\001\001\000\002\001\001" });
+
+  /** Access to <code>reduce_goto</code> table. */
+  public short[][] reduce_table() {return _reduce_table;}
+
+  /** Instance of action encapsulation class. */
+  protected CUP$Parser$actions action_obj;
+
+  /** Action encapsulation object initializer. */
+  protected void init_actions()
+    {
+      action_obj = new CUP$Parser$actions(this);
+    }
+
+  /** Invoke a user supplied parse action. */
+  public java_cup.runtime.Symbol do_action(
+    int                        act_num,
+    java_cup.runtime.lr_parser parser,
+    java.util.Stack            stack,
+    int                        top)
+    throws java.lang.Exception
+  {
+    /* call code in generated class */
+    return action_obj.CUP$Parser$do_action(act_num, parser, stack, top);
+  }
+
+  /** Indicates start state. */
+  public int start_state() {return 0;}
+  /** Indicates start production. */
+  public int start_production() {return 1;}
+
+  /** <code>EOF</code> Symbol index. */
+  public int EOF_sym() {return 0;}
+
+  /** <code>error</code> Symbol index. */
+  public int error_sym() {return 1;}
+
+
+
+  /** variable - value relation. */
+  protected HashMap m_Symbols = new HashMap();
+
+  /** attribute - attribute-type (constants from weka.core.Attribute) relation. */
+  protected Hashtable<String,Integer> m_AttributeTypes = new Hashtable<String,Integer>();
+
+  /** for storing the result of the expression. */
+  protected Boolean m_Result = null;
+
+  /**
+   * Sets the variable - value relation to use.
+   * 
+   * @param value the variable-value relation
+   */
+  public void setSymbols(HashMap value) {
+    m_Symbols = value;
+  }
+
+  /**
+   * Returns the current variable - value relation in use.
+   * 
+   * @return the variable-value relation
+   */
+  public HashMap getSymbols() {
+    return m_Symbols;
+  }
+
+  /**
+   * Sets the attribute - attribute-type relation to use.
+   * 
+   * @param value the att - att-type relation
+   */
+  public void setAttributeTypes(Hashtable value) {
+    m_AttributeTypes = value;
+  }
+
+  /**
+   * Returns the current attribute - attribute-type relation in use.
+   * 
+   * @return the att - att-type relation
+   */
+  public Hashtable getAttributeTypes() {
+    return m_AttributeTypes;
+  }
+
+  /**
+   * Sets the result of the evaluation.
+   * 
+   * @param value the result
+   */
+  public void setResult(Boolean value) {
+    m_Result = value;
+  }
+
+  /**
+   * Returns the result of the evaluation.
+   * 
+   * @return the result
+   */
+  public Boolean getResult() {
+    return m_Result;
+  }
+
+  /**
+   * Returns either a String object for nominal attributes or a Double for numeric
+   * ones. For all other attribute types this method throws an Exception.
+   * It also returns a Double object in case of a missing value (for all
+   * attribute types!).
+   * 
+   * @param instance the instance to work on
+   * @param index the index of the attribute to return
+   * @return the converted value
+   */
+  public static Object getValue(Instance instance, int index) {
+    if (instance.isMissing(index))
+      return new Double(Utils.missingValue());
+    else if (instance.attribute(index).isNominal())
+      return new String(instance.stringValue(index));
+    else if (instance.attribute(index).isNumeric())
+      return new Double(instance.value(index));
+    else
+      throw new IllegalArgumentException(
+          "Unhandled attribute type '" + instance.attribute(index).type() + "'!");
+  }
+
+  /**
+   * Filters the input dataset against the provided expression.
+   *
+   * @param expression the expression used for filtering
+   * @param input the input data
+   * @return the filtered data
+   * @throws Exception if parsing fails
+   */
+  public static Instances filter(String expression, Instances input) throws Exception {
+    // setup output
+    Instances output = new Instances(input, 0);
+    
+    // setup attribute - attribute-type relation
+    Hashtable<String,Integer> attTypes = new Hashtable<String,Integer>();
+    for (int i = 0; i < input.numAttributes(); i++)
+       attTypes.put("ATT" + (i+1), input.attribute(i).type());
+    if (input.classIndex() > -1)
+      attTypes.put("CLASS", input.classAttribute().type());
+    
+    // filter dataset
+    SymbolFactory sf = new DefaultSymbolFactory();
+    HashMap symbols = new HashMap();
+    ByteArrayInputStream parserInput = new ByteArrayInputStream(expression.getBytes());
+    for (int i = 0; i < input.numInstances(); i++) {
+      Instance instance = input.instance(i);
+
+      // setup symbols
+      for (int n = 0; n < instance.numAttributes(); n++) {
+        if (n == instance.classIndex())
+          symbols.put("CLASS", getValue(instance, n));
+        symbols.put("ATT" + (n+1), getValue(instance, n));
+      }
+
+      // evaluate expression
+      parserInput.reset();
+      Parser parser = new Parser(new Scanner(parserInput,sf), sf);
+      parser.setSymbols(symbols);
+      parser.parse();
+      if (parser.getResult())
+        output.add((Instance) instance.copy());
+    }
+
+    return output;
+  }
+
+  /**
+   * Runs the parser from commandline. Takes the following arguments:
+   * <ol>
+   *   <li>expression</li>
+   *   <li>input file</li>
+   *   <li>class index (first|last|num), use 0 to ignore</li>
+   *   <li>output file</li>
+   * </ol>
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String args[]) throws Exception {
+    // get expression
+    String expression = args[0];
+
+    // read input data
+    BufferedReader reader = new BufferedReader(new FileReader(args[1]));
+    Instances input = new Instances(reader);
+    reader.close();
+    if (args[2].equals("first"))
+      input.setClassIndex(0);
+    else if (args[2].equals("last"))
+      input.setClassIndex(input.numAttributes() - 1);
+    else
+      input.setClassIndex(Integer.parseInt(args[2]) - 1);
+
+    // process dataset
+    Instances output = filter(expression, input);
+
+    // save output file
+    BufferedWriter writer = new BufferedWriter(new FileWriter(args[3]));
+    writer.write(new Instances(output, 0).toString());
+    writer.newLine();
+    for (int i = 0; i < output.numInstances(); i++) {
+      writer.write(output.instance(i).toString());
+      writer.newLine();
+    }
+    writer.flush();
+    writer.close();
+  }
+
+}
+
+/** Cup generated class to encapsulate user supplied action code.*/
+class CUP$Parser$actions {
+  private final Parser parser;
+
+  /** Constructor */
+  CUP$Parser$actions(Parser parser) {
+    this.parser = parser;
+  }
+
+  /** Method with the actual generated action code. */
+  public final java_cup.runtime.Symbol CUP$Parser$do_action(
+    int                        CUP$Parser$act_num,
+    java_cup.runtime.lr_parser CUP$Parser$parser,
+    java.util.Stack            CUP$Parser$stack,
+    int                        CUP$Parser$top)
+    throws java.lang.Exception
+    {
+      /* Symbol object for return from actions */
+      java_cup.runtime.Symbol CUP$Parser$result;
+
+      /* select the action based on the action number */
+      switch (CUP$Parser$act_num)
+        {
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 37: // funcexpr ::= CEIL LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.ceil(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 36: // funcexpr ::= POW LPAREN expr COMMA expr RPAREN 
+            {
+              Double RESULT =null;
+		int baseleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).left;
+		int baseright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)).right;
+		Double base = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-3)).value;
+		int exponentleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int exponentright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double exponent = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.pow(base, exponent)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-5)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 35: // funcexpr ::= FLOOR LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.floor(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 34: // funcexpr ::= RINT LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.rint(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 33: // funcexpr ::= TAN LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.tan(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 32: // funcexpr ::= COS LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.cos(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 31: // funcexpr ::= SIN LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.sin(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 30: // funcexpr ::= EXP LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.exp(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 29: // funcexpr ::= LOG LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.log(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 28: // funcexpr ::= SQRT LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.sqrt(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 27: // funcexpr ::= ABS LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = new Double(Math.abs(e)); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("funcexpr",4, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 26: // opexpr ::= expr DIVISION expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() / r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 25: // opexpr ::= expr TIMES expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() * r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 24: // opexpr ::= expr MINUS expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() - r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 23: // opexpr ::= expr PLUS expr 
+            {
+              Double RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Double(l.doubleValue() + r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("opexpr",3, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 22: // expr ::= funcexpr 
+            {
+              Double RESULT =null;
+		int fleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int fright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double f = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = f; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 21: // expr ::= opexpr 
+            {
+              Double RESULT =null;
+		int oleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int oright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double o = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = o; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 20: // expr ::= LPAREN expr RPAREN 
+            {
+              Double RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Double e = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = e; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 19: // expr ::= ATTRIBUTE 
+            {
+              Double RESULT =null;
+		int aleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int aright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		String a = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (Double) parser.getSymbols().get(a); 
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 18: // expr ::= NUMBER 
+            {
+              Double RESULT =null;
+		int nleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int nright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double n = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = n; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("expr",2, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 17: // boolexpr ::= ISMISSING LPAREN ATTRIBUTE RPAREN 
+            {
+              Boolean RESULT =null;
+		int aleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int aright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		String a = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (parser.getSymbols().get(a) instanceof Double) && Utils.isMissingValue((Double) parser.getSymbols().get(a));
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-3)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 16: // boolexpr ::= ATTRIBUTE IS STRING 
+            {
+              Boolean RESULT =null;
+		int aleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int aright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		String a = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int sleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int sright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		String s = (String)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 if (parser.getSymbols().containsKey(a)) 
+                     RESULT = (parser.getSymbols().get(a) instanceof String) && ((String) parser.getSymbols().get(a)).equals(s);
+                   else 
+                     throw new IllegalStateException("Unknown symbol '" + a + "'!"); 
+                
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 15: // boolexpr ::= boolexpr OR boolexpr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Boolean l = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean r = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = l || r; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 14: // boolexpr ::= boolexpr AND boolexpr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Boolean l = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean r = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = l && r; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 13: // boolexpr ::= NOT boolexpr 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = !b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 12: // boolexpr ::= LPAREN boolexpr RPAREN 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		 RESULT = b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 11: // boolexpr ::= expr EQ expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() == r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 10: // boolexpr ::= expr GE expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() >= r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 9: // boolexpr ::= expr GT expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() > r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 8: // boolexpr ::= expr LE expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() <= r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 7: // boolexpr ::= expr LT expr 
+            {
+              Boolean RESULT =null;
+		int lleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).left;
+		int lright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)).right;
+		Double l = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-2)).value;
+		int rleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int rright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Double r = (Double)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = new Boolean(l.doubleValue() < r.doubleValue()); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-2)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 6: // boolexpr ::= FALSE 
+            {
+              Boolean RESULT =null;
+		 RESULT = new Boolean(false); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 5: // boolexpr ::= TRUE 
+            {
+              Boolean RESULT =null;
+		 RESULT = new Boolean(true); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 4: // boolexpr ::= BOOLEAN 
+            {
+              Boolean RESULT =null;
+		int bleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int bright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean b = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 RESULT = b; 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr",5, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 3: // boolexpr_part ::= boolexpr 
+            {
+              Object RESULT =null;
+		int eleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).left;
+		int eright = ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()).right;
+		Boolean e = (Boolean)((java_cup.runtime.Symbol) CUP$Parser$stack.peek()).value;
+		 parser.setResult(e); 
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr_part",1, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 2: // boolexpr_list ::= boolexpr_part 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr_list",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 1: // $START ::= boolexpr_list EOF 
+            {
+              Object RESULT =null;
+		int start_valleft = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).left;
+		int start_valright = ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)).right;
+		Object start_val = (Object)((java_cup.runtime.Symbol) CUP$Parser$stack.elementAt(CUP$Parser$top-1)).value;
+		RESULT = start_val;
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("$START",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          /* ACCEPT */
+          CUP$Parser$parser.done_parsing();
+          return CUP$Parser$result;
+
+          /*. . . . . . . . . . . . . . . . . . . .*/
+          case 0: // boolexpr_list ::= boolexpr_list boolexpr_part 
+            {
+              Object RESULT =null;
+
+              CUP$Parser$result = parser.getSymbolFactory().newSymbol("boolexpr_list",0, ((java_cup.runtime.Symbol)CUP$Parser$stack.elementAt(CUP$Parser$top-1)), ((java_cup.runtime.Symbol)CUP$Parser$stack.peek()), RESULT);
+            }
+          return CUP$Parser$result;
+
+          /* . . . . . .*/
+          default:
+            throw new Exception(
+               "Invalid action number found in internal parse table");
+
+        }
+    }
+}
+
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.java	(revision 29)
@@ -0,0 +1,760 @@
+/* The following code was generated by JFlex 1.4.2 on 11/01/09 11:14 AM */
+
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance.subsetbyexpression;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for evaluating whether an Instance is to be included in a subset
+ * or not.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+
+public class Scanner implements java_cup.runtime.Scanner {
+
+  /** This character denotes the end of file */
+  public static final int YYEOF = -1;
+
+  /** initial size of the lookahead buffer */
+  private static final int ZZ_BUFFERSIZE = 16384;
+
+  /** lexical states */
+  public static final int STRING = 2;
+  public static final int YYINITIAL = 0;
+
+  /**
+   * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+   * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+   *                  at the beginning of a line
+   * l is of the form l = 2*k, k a non negative integer
+   */
+  private static final int ZZ_LEXSTATE[] = { 
+     0,  0,  1, 1
+  };
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final String ZZ_CMAP_PACKED = 
+    "\11\0\1\43\1\50\1\0\2\43\22\0\1\43\6\0\1\33\1\45"+
+    "\1\46\1\3\1\2\1\44\1\1\1\35\1\4\12\34\2\0\1\5"+
+    "\1\6\1\7\2\0\1\36\1\0\1\40\10\0\1\41\6\0\1\42"+
+    "\1\37\14\0\1\15\1\24\1\31\1\16\1\21\1\22\1\26\1\0"+
+    "\1\10\2\0\1\23\1\47\1\12\1\13\1\30\1\25\1\17\1\11"+
+    "\1\14\1\20\1\0\1\32\1\27\uff87\0";
+
+  /** 
+   * Translates characters to character classes
+   */
+  private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
+
+  /** 
+   * Translates DFA states to action switch labels.
+   */
+  private static final int [] ZZ_ACTION = zzUnpackAction();
+
+  private static final String ZZ_ACTION_PACKED_0 =
+    "\2\0\1\1\1\2\1\3\1\4\1\5\1\6\1\7"+
+    "\1\10\14\1\1\11\1\12\2\1\1\13\1\14\1\15"+
+    "\1\16\1\17\1\20\1\21\1\22\1\23\3\0\1\24"+
+    "\14\0\1\12\3\0\1\25\1\0\1\26\1\27\1\0"+
+    "\1\30\1\31\1\0\1\32\2\0\1\33\1\34\1\35"+
+    "\4\0\1\36\1\37\1\40\2\0\1\41\1\42\2\0"+
+    "\1\43\1\44\1\45\3\0\1\46";
+
+  private static int [] zzUnpackAction() {
+    int [] result = new int[89];
+    int offset = 0;
+    offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAction(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /** 
+   * Translates a state to a row index in the transition table
+   */
+  private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+  private static final String ZZ_ROWMAP_PACKED_0 =
+    "\0\0\0\51\0\122\0\173\0\122\0\122\0\122\0\244"+
+    "\0\122\0\315\0\366\0\u011f\0\u0148\0\u0171\0\u019a\0\u01c3"+
+    "\0\u01ec\0\u0215\0\u023e\0\u0267\0\u0290\0\u02b9\0\122\0\u02e2"+
+    "\0\u030b\0\u0334\0\122\0\122\0\122\0\122\0\122\0\122"+
+    "\0\122\0\122\0\u035d\0\u0386\0\u03af\0\u03d8\0\122\0\u0401"+
+    "\0\u042a\0\u0453\0\u047c\0\u04a5\0\u04ce\0\u04f7\0\u0520\0\u0549"+
+    "\0\u0572\0\u059b\0\u05c4\0\u05ed\0\u0616\0\u063f\0\u0668\0\122"+
+    "\0\u0691\0\122\0\122\0\u06ba\0\122\0\122\0\u06e3\0\122"+
+    "\0\u070c\0\u0735\0\122\0\122\0\122\0\u075e\0\u0787\0\u07b0"+
+    "\0\u07d9\0\122\0\122\0\122\0\u0802\0\u082b\0\122\0\u0787"+
+    "\0\u0854\0\u087d\0\122\0\122\0\122\0\u08a6\0\u08cf\0\u08f8"+
+    "\0\122";
+
+  private static int [] zzUnpackRowMap() {
+    int [] result = new int[89];
+    int offset = 0;
+    offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+    int i = 0;  /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int high = packed.charAt(i++) << 16;
+      result[j++] = high | packed.charAt(i++);
+    }
+    return j;
+  }
+
+  /** 
+   * The transition table of the DFA
+   */
+  private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+  private static final String ZZ_TRANS_PACKED_0 =
+    "\1\3\1\4\1\5\1\6\1\7\1\10\1\11\1\12"+
+    "\1\13\1\14\1\15\1\16\1\17\1\20\1\3\1\21"+
+    "\1\3\1\22\1\23\1\24\4\3\1\25\1\26\1\3"+
+    "\1\27\1\30\1\3\1\31\1\3\1\32\2\3\1\33"+
+    "\1\34\1\35\1\36\1\3\1\33\33\37\1\40\14\37"+
+    "\106\0\1\30\22\0\1\41\50\0\1\42\53\0\1\43"+
+    "\47\0\1\44\14\0\1\45\36\0\1\46\54\0\1\47"+
+    "\46\0\1\50\1\0\1\51\43\0\1\52\11\0\1\53"+
+    "\34\0\1\54\67\0\1\55\36\0\1\56\5\0\1\57"+
+    "\40\0\1\60\50\0\1\61\50\0\1\62\5\0\1\63"+
+    "\63\0\1\30\1\64\52\0\1\65\52\0\1\66\56\0"+
+    "\1\67\13\0\1\70\55\0\1\71\45\0\1\72\46\0"+
+    "\1\73\56\0\1\74\46\0\1\75\43\0\1\76\51\0"+
+    "\1\77\66\0\1\100\43\0\1\101\40\0\1\102\63\0"+
+    "\1\103\54\0\1\104\27\0\1\105\47\0\1\106\74\0"+
+    "\1\64\53\0\1\107\47\0\1\110\22\0\1\111\54\0"+
+    "\1\112\55\0\1\113\43\0\1\114\45\0\1\115\52\0"+
+    "\1\116\60\0\1\117\61\0\1\120\56\0\1\121\17\0"+
+    "\1\122\60\0\1\123\46\0\1\124\73\0\1\125\17\0"+
+    "\1\126\47\0\1\127\52\0\1\130\64\0\1\131\22\0";
+
+  private static int [] zzUnpackTrans() {
+    int [] result = new int[2337];
+    int offset = 0;
+    offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackTrans(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      value--;
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+
+  /* error codes */
+  private static final int ZZ_UNKNOWN_ERROR = 0;
+  private static final int ZZ_NO_MATCH = 1;
+  private static final int ZZ_PUSHBACK_2BIG = 2;
+
+  /* error messages for the codes above */
+  private static final String ZZ_ERROR_MSG[] = {
+    "Unkown internal scanner error",
+    "Error: could not match input",
+    "Error: pushback value was too large"
+  };
+
+  /**
+   * ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
+   */
+  private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+  private static final String ZZ_ATTRIBUTE_PACKED_0 =
+    "\2\0\1\11\1\1\3\11\1\1\1\11\15\1\1\11"+
+    "\3\1\10\11\1\1\3\0\1\11\14\0\1\1\3\0"+
+    "\1\11\1\0\2\11\1\0\2\11\1\0\1\11\2\0"+
+    "\3\11\4\0\3\11\2\0\1\11\1\1\2\0\3\11"+
+    "\3\0\1\11";
+
+  private static int [] zzUnpackAttribute() {
+    int [] result = new int[89];
+    int offset = 0;
+    offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+    return result;
+  }
+
+  private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+    int i = 0;       /* index in packed string  */
+    int j = offset;  /* index in unpacked array */
+    int l = packed.length();
+    while (i < l) {
+      int count = packed.charAt(i++);
+      int value = packed.charAt(i++);
+      do result[j++] = value; while (--count > 0);
+    }
+    return j;
+  }
+
+  /** the input device */
+  private java.io.Reader zzReader;
+
+  /** the current state of the DFA */
+  private int zzState;
+
+  /** the current lexical state */
+  private int zzLexicalState = YYINITIAL;
+
+  /** this buffer contains the current text to be matched and is
+      the source of the yytext() string */
+  private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
+
+  /** the textposition at the last accepting state */
+  private int zzMarkedPos;
+
+  /** the current text position in the buffer */
+  private int zzCurrentPos;
+
+  /** startRead marks the beginning of the yytext() string in the buffer */
+  private int zzStartRead;
+
+  /** endRead marks the last character in the buffer, that has been read
+      from input */
+  private int zzEndRead;
+
+  /** number of newlines encountered up to the start of the matched text */
+  private int yyline;
+
+  /** the number of characters up to the start of the matched text */
+  private int yychar;
+
+  /**
+   * the number of characters from the last newline up to the start of the 
+   * matched text
+   */
+  private int yycolumn;
+
+  /** 
+   * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+   */
+  private boolean zzAtBOL = true;
+
+  /** zzAtEOF == true <=> the scanner is at the EOF */
+  private boolean zzAtEOF;
+
+  /* user code: */
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 4939 $
+  protected SymbolFactory m_SymFactory;
+
+  protected StringBuffer m_String = new StringBuffer();
+
+  public Scanner(InputStream r, SymbolFactory sf) {
+    this(r);
+    m_SymFactory = sf;
+  }
+
+
+  /**
+   * Creates a new scanner
+   * There is also a java.io.InputStream version of this constructor.
+   *
+   * @param   in  the java.io.Reader to read input from.
+   */
+  public Scanner(java.io.Reader in) {
+    this.zzReader = in;
+  }
+
+  /**
+   * Creates a new scanner.
+   * There is also java.io.Reader version of this constructor.
+   *
+   * @param   in  the java.io.Inputstream to read input from.
+   */
+  public Scanner(java.io.InputStream in) {
+    this(new java.io.InputStreamReader(in));
+  }
+
+  /** 
+   * Unpacks the compressed character translation table.
+   *
+   * @param packed   the packed character translation table
+   * @return         the unpacked character translation table
+   */
+  private static char [] zzUnpackCMap(String packed) {
+    char [] map = new char[0x10000];
+    int i = 0;  /* index in packed string  */
+    int j = 0;  /* index in unpacked array */
+    while (i < 112) {
+      int  count = packed.charAt(i++);
+      char value = packed.charAt(i++);
+      do map[j++] = value; while (--count > 0);
+    }
+    return map;
+  }
+
+
+  /**
+   * Refills the input buffer.
+   *
+   * @return      <code>false</code>, iff there was new input.
+   * 
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  private boolean zzRefill() throws java.io.IOException {
+
+    /* first: make room (if you can) */
+    if (zzStartRead > 0) {
+      System.arraycopy(zzBuffer, zzStartRead,
+                       zzBuffer, 0,
+                       zzEndRead-zzStartRead);
+
+      /* translate stored positions */
+      zzEndRead-= zzStartRead;
+      zzCurrentPos-= zzStartRead;
+      zzMarkedPos-= zzStartRead;
+      zzStartRead = 0;
+    }
+
+    /* is the buffer big enough? */
+    if (zzCurrentPos >= zzBuffer.length) {
+      /* if not: blow it up */
+      char newBuffer[] = new char[zzCurrentPos*2];
+      System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
+      zzBuffer = newBuffer;
+    }
+
+    /* finally: fill the buffer with new input */
+    int numRead = zzReader.read(zzBuffer, zzEndRead,
+                                            zzBuffer.length-zzEndRead);
+
+    if (numRead > 0) {
+      zzEndRead+= numRead;
+      return false;
+    }
+    // unlikely but not impossible: read 0 characters, but not at end of stream    
+    if (numRead == 0) {
+      int c = zzReader.read();
+      if (c == -1) {
+        return true;
+      } else {
+        zzBuffer[zzEndRead++] = (char) c;
+        return false;
+      }     
+    }
+
+	// numRead < 0
+    return true;
+  }
+
+    
+  /**
+   * Closes the input stream.
+   */
+  public final void yyclose() throws java.io.IOException {
+    zzAtEOF = true;            /* indicate end of file */
+    zzEndRead = zzStartRead;  /* invalidate buffer    */
+
+    if (zzReader != null)
+      zzReader.close();
+  }
+
+
+  /**
+   * Resets the scanner to read from a new input stream.
+   * Does not close the old reader.
+   *
+   * All internal variables are reset, the old input stream 
+   * <b>cannot</b> be reused (internal buffer is discarded and lost).
+   * Lexical state is set to <tt>ZZ_INITIAL</tt>.
+   *
+   * @param reader   the new input stream 
+   */
+  public final void yyreset(java.io.Reader reader) {
+    zzReader = reader;
+    zzAtBOL  = true;
+    zzAtEOF  = false;
+    zzEndRead = zzStartRead = 0;
+    zzCurrentPos = zzMarkedPos = 0;
+    yyline = yychar = yycolumn = 0;
+    zzLexicalState = YYINITIAL;
+  }
+
+
+  /**
+   * Returns the current lexical state.
+   */
+  public final int yystate() {
+    return zzLexicalState;
+  }
+
+
+  /**
+   * Enters a new lexical state
+   *
+   * @param newState the new lexical state
+   */
+  public final void yybegin(int newState) {
+    zzLexicalState = newState;
+  }
+
+
+  /**
+   * Returns the text matched by the current regular expression.
+   */
+  public final String yytext() {
+    return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+  }
+
+
+  /**
+   * Returns the character at position <tt>pos</tt> from the 
+   * matched text. 
+   * 
+   * It is equivalent to yytext().charAt(pos), but faster
+   *
+   * @param pos the position of the character to fetch. 
+   *            A value from 0 to yylength()-1.
+   *
+   * @return the character at position pos
+   */
+  public final char yycharat(int pos) {
+    return zzBuffer[zzStartRead+pos];
+  }
+
+
+  /**
+   * Returns the length of the matched text region.
+   */
+  public final int yylength() {
+    return zzMarkedPos-zzStartRead;
+  }
+
+
+  /**
+   * Reports an error that occured while scanning.
+   *
+   * In a wellformed scanner (no or only correct usage of 
+   * yypushback(int) and a match-all fallback rule) this method 
+   * will only be called with things that "Can't Possibly Happen".
+   * If this method is called, something is seriously wrong
+   * (e.g. a JFlex bug producing a faulty scanner etc.).
+   *
+   * Usual syntax/scanner level error handling should be done
+   * in error fallback rules.
+   *
+   * @param   errorCode  the code of the errormessage to display
+   */
+  private void zzScanError(int errorCode) {
+    String message;
+    try {
+      message = ZZ_ERROR_MSG[errorCode];
+    }
+    catch (ArrayIndexOutOfBoundsException e) {
+      message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+    }
+
+    throw new Error(message);
+  } 
+
+
+  /**
+   * Pushes the specified amount of characters back into the input stream.
+   *
+   * They will be read again by then next call of the scanning method
+   *
+   * @param number  the number of characters to be read again.
+   *                This number must not be greater than yylength()!
+   */
+  public void yypushback(int number)  {
+    if ( number > yylength() )
+      zzScanError(ZZ_PUSHBACK_2BIG);
+
+    zzMarkedPos -= number;
+  }
+
+
+  /**
+   * Resumes scanning until the next regular expression is matched,
+   * the end of input is encountered or an I/O-Error occurs.
+   *
+   * @return      the next token
+   * @exception   java.io.IOException  if any I/O-Error occurs
+   */
+  public java_cup.runtime.Symbol next_token() throws java.io.IOException {
+    int zzInput;
+    int zzAction;
+
+    // cached fields:
+    int zzCurrentPosL;
+    int zzMarkedPosL;
+    int zzEndReadL = zzEndRead;
+    char [] zzBufferL = zzBuffer;
+    char [] zzCMapL = ZZ_CMAP;
+
+    int [] zzTransL = ZZ_TRANS;
+    int [] zzRowMapL = ZZ_ROWMAP;
+    int [] zzAttrL = ZZ_ATTRIBUTE;
+
+    while (true) {
+      zzMarkedPosL = zzMarkedPos;
+
+      yychar+= zzMarkedPosL-zzStartRead;
+
+      zzAction = -1;
+
+      zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+  
+      zzState = ZZ_LEXSTATE[zzLexicalState];
+
+
+      zzForAction: {
+        while (true) {
+    
+          if (zzCurrentPosL < zzEndReadL)
+            zzInput = zzBufferL[zzCurrentPosL++];
+          else if (zzAtEOF) {
+            zzInput = YYEOF;
+            break zzForAction;
+          }
+          else {
+            // store back cached positions
+            zzCurrentPos  = zzCurrentPosL;
+            zzMarkedPos   = zzMarkedPosL;
+            boolean eof = zzRefill();
+            // get translated positions and possibly new buffer
+            zzCurrentPosL  = zzCurrentPos;
+            zzMarkedPosL   = zzMarkedPos;
+            zzBufferL      = zzBuffer;
+            zzEndReadL     = zzEndRead;
+            if (eof) {
+              zzInput = YYEOF;
+              break zzForAction;
+            }
+            else {
+              zzInput = zzBufferL[zzCurrentPosL++];
+            }
+          }
+          int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+          if (zzNext == -1) break zzForAction;
+          zzState = zzNext;
+
+          int zzAttributes = zzAttrL[zzState];
+          if ( (zzAttributes & 1) == 1 ) {
+            zzAction = zzState;
+            zzMarkedPosL = zzCurrentPosL;
+            if ( (zzAttributes & 8) == 8 ) break zzForAction;
+          }
+
+        }
+      }
+
+      // store back cached position
+      zzMarkedPos = zzMarkedPosL;
+
+      switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+        case 9: 
+          { yybegin(STRING); m_String.setLength(0);
+          }
+        case 39: break;
+        case 25: 
+          { return m_SymFactory.newSymbol("Abs", sym.ABS);
+          }
+        case 40: break;
+        case 1: 
+          { System.err.println("Illegal character: " + yytext());
+          }
+        case 41: break;
+        case 15: 
+          { m_String.append(yytext());
+          }
+        case 42: break;
+        case 21: 
+          { return m_SymFactory.newSymbol("Sin", sym.SIN);
+          }
+        case 43: break;
+        case 14: 
+          { return m_SymFactory.newSymbol("Right Bracket", sym.RPAREN);
+          }
+        case 44: break;
+        case 11: 
+          { /* ignore white space. */
+          }
+        case 45: break;
+        case 17: 
+          { return m_SymFactory.newSymbol("Less or equal than", sym.LE);
+          }
+        case 46: break;
+        case 28: 
+          { return m_SymFactory.newSymbol("Pow", sym.POW);
+          }
+        case 47: break;
+        case 8: 
+          { return m_SymFactory.newSymbol("Greater than", sym.GT);
+          }
+        case 48: break;
+        case 16: 
+          { yybegin(YYINITIAL); return m_SymFactory.newSymbol("String", sym.STRING, new String(m_String.toString()));
+          }
+        case 49: break;
+        case 23: 
+          { return m_SymFactory.newSymbol("Tan", sym.TAN);
+          }
+        case 50: break;
+        case 12: 
+          { return m_SymFactory.newSymbol("Comma", sym.COMMA);
+          }
+        case 51: break;
+        case 19: 
+          { return m_SymFactory.newSymbol("Is", sym.IS);
+          }
+        case 52: break;
+        case 13: 
+          { return m_SymFactory.newSymbol("Left Bracket", sym.LPAREN);
+          }
+        case 53: break;
+        case 29: 
+          { return m_SymFactory.newSymbol("Cos", sym.COS);
+          }
+        case 54: break;
+        case 33: 
+          { return m_SymFactory.newSymbol("Ceil", sym.CEIL);
+          }
+        case 55: break;
+        case 6: 
+          { return m_SymFactory.newSymbol("Less than", sym.LT);
+          }
+        case 56: break;
+        case 4: 
+          { return m_SymFactory.newSymbol("Times", sym.TIMES);
+          }
+        case 57: break;
+        case 37: 
+          { return m_SymFactory.newSymbol("Class", sym.ATTRIBUTE, new String(yytext()));
+          }
+        case 58: break;
+        case 36: 
+          { return m_SymFactory.newSymbol("Floor", sym.FLOOR);
+          }
+        case 59: break;
+        case 27: 
+          { return m_SymFactory.newSymbol("Log", sym.LOG);
+          }
+        case 60: break;
+        case 35: 
+          { return m_SymFactory.newSymbol("False", sym.FALSE);
+          }
+        case 61: break;
+        case 31: 
+          { return m_SymFactory.newSymbol("True", sym.TRUE);
+          }
+        case 62: break;
+        case 7: 
+          { return m_SymFactory.newSymbol("Equals", sym.EQ);
+          }
+        case 63: break;
+        case 18: 
+          { return m_SymFactory.newSymbol("Greater or equal than", sym.GE);
+          }
+        case 64: break;
+        case 26: 
+          { return m_SymFactory.newSymbol("Exp", sym.EXP);
+          }
+        case 65: break;
+        case 20: 
+          { return m_SymFactory.newSymbol("Or", sym.OR);
+          }
+        case 66: break;
+        case 34: 
+          { return m_SymFactory.newSymbol("Attribute", sym.ATTRIBUTE, new String(yytext()));
+          }
+        case 67: break;
+        case 30: 
+          { return m_SymFactory.newSymbol("Sqrt", sym.SQRT);
+          }
+        case 68: break;
+        case 5: 
+          { return m_SymFactory.newSymbol("Division", sym.DIVISION);
+          }
+        case 69: break;
+        case 22: 
+          { return m_SymFactory.newSymbol("Not", sym.NOT);
+          }
+        case 70: break;
+        case 10: 
+          { return m_SymFactory.newSymbol("Number", sym.NUMBER, new Double(yytext()));
+          }
+        case 71: break;
+        case 3: 
+          { return m_SymFactory.newSymbol("Plus", sym.PLUS);
+          }
+        case 72: break;
+        case 38: 
+          { return m_SymFactory.newSymbol("Missing", sym.ISMISSING);
+          }
+        case 73: break;
+        case 32: 
+          { return m_SymFactory.newSymbol("Rint", sym.RINT);
+          }
+        case 74: break;
+        case 24: 
+          { return m_SymFactory.newSymbol("And", sym.AND);
+          }
+        case 75: break;
+        case 2: 
+          { return m_SymFactory.newSymbol("Minus", sym.MINUS);
+          }
+        case 76: break;
+        default: 
+          if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+            zzAtEOF = true;
+              {     return m_SymFactory.newSymbol("EOF",sym.EOF);
+ }
+          } 
+          else {
+            zzScanError(ZZ_NO_MATCH);
+          }
+      }
+    }
+  }
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.jflex
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.jflex	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/Scanner.jflex	(revision 29)
@@ -0,0 +1,115 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Scanner.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance.subsetbyexpression;
+
+import java_cup.runtime.SymbolFactory;
+import java.io.*;
+
+/**
+ * A scanner for evaluating whether an Instance is to be included in a subset
+ * or not.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4939 $
+ */
+%%
+%unicode
+%char
+%cup
+%public
+%class Scanner
+%{
+  // Author: FracPete (fracpete at waikato dot ac dot nz)
+  // Version: $Revision: 4939 $
+  protected SymbolFactory m_SymFactory;
+
+  protected StringBuffer m_String = new StringBuffer();
+
+  public Scanner(InputStream r, SymbolFactory sf) {
+    this(r);
+    m_SymFactory = sf;
+  }
+%}
+%eofval{
+    return m_SymFactory.newSymbol("EOF",sym.EOF);
+%eofval}
+
+%state STRING
+
+%%
+<YYINITIAL> {
+  // operands
+  "-" { return m_SymFactory.newSymbol("Minus", sym.MINUS); }
+  "+" { return m_SymFactory.newSymbol("Plus", sym.PLUS); }
+  "*" { return m_SymFactory.newSymbol("Times", sym.TIMES); }
+  "/" { return m_SymFactory.newSymbol("Division", sym.DIVISION); }
+
+  // boolean stuff
+  "<" { return m_SymFactory.newSymbol("Less than", sym.LT); }
+  "<=" { return m_SymFactory.newSymbol("Less or equal than", sym.LE); }
+  ">" { return m_SymFactory.newSymbol("Greater than", sym.GT); }
+  ">=" { return m_SymFactory.newSymbol("Greater or equal than", sym.GE); }
+  "=" { return m_SymFactory.newSymbol("Equals", sym.EQ); }
+  "is" { return m_SymFactory.newSymbol("Is", sym.IS); }
+  "not" { return m_SymFactory.newSymbol("Not", sym.NOT); }
+  "and" { return m_SymFactory.newSymbol("And", sym.AND); }
+  "or" { return m_SymFactory.newSymbol("Or", sym.OR); }
+  "true" { return m_SymFactory.newSymbol("True", sym.TRUE); }
+  "false" { return m_SymFactory.newSymbol("False", sym.FALSE); }
+
+  // functions
+  "abs" { return m_SymFactory.newSymbol("Abs", sym.ABS); }
+  "sqrt" { return m_SymFactory.newSymbol("Sqrt", sym.SQRT); }
+  "log" { return m_SymFactory.newSymbol("Log", sym.LOG); }
+  "exp" { return m_SymFactory.newSymbol("Exp", sym.EXP); }
+  "sin" { return m_SymFactory.newSymbol("Sin", sym.SIN); }
+  "cos" { return m_SymFactory.newSymbol("Cos", sym.COS); }
+  "tan" { return m_SymFactory.newSymbol("Tan", sym.TAN); }
+  "rint" { return m_SymFactory.newSymbol("Rint", sym.RINT); }
+  "floor" { return m_SymFactory.newSymbol("Floor", sym.FLOOR); }
+  "pow" { return m_SymFactory.newSymbol("Pow", sym.POW); }
+  "ceil" { return m_SymFactory.newSymbol("Ceil", sym.CEIL); }
+
+  // numbers and variables
+  "'" { yybegin(STRING); m_String.setLength(0); }
+  [0-9][0-9]*\.?[0-9]* { return m_SymFactory.newSymbol("Number", sym.NUMBER, new Double(yytext())); }
+  -[0-9][0-9]*\.?[0-9]* { return m_SymFactory.newSymbol("Number", sym.NUMBER, new Double(yytext())); }
+  [A][T][T][0-9][0-9]* { return m_SymFactory.newSymbol("Attribute", sym.ATTRIBUTE, new String(yytext())); }
+  "CLASS" { return m_SymFactory.newSymbol("Class", sym.ATTRIBUTE, new String(yytext())); }
+
+  // whitespaces
+  [ \r\n\t\f] { /* ignore white space. */ }
+
+  // various
+  "," { return m_SymFactory.newSymbol("Comma", sym.COMMA); }
+  "(" { return m_SymFactory.newSymbol("Left Bracket", sym.LPAREN); }
+  ")" { return m_SymFactory.newSymbol("Right Bracket", sym.RPAREN); }
+  "ismissing" { return m_SymFactory.newSymbol("Missing", sym.ISMISSING); }
+}
+
+<STRING> {
+  "'" { yybegin(YYINITIAL); return m_SymFactory.newSymbol("String", sym.STRING, new String(m_String.toString())); }
+  . { m_String.append(yytext()); }
+}
+
+// catch all
+. { System.err.println("Illegal character: " + yytext()); }
Index: branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/sym.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/sym.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/filters/unsupervised/instance/subsetbyexpression/sym.java	(revision 29)
@@ -0,0 +1,49 @@
+
+//----------------------------------------------------
+// The following code was generated by CUP v0.11a beta 20060608
+// Sun Jan 11 11:14:12 NZDT 2009
+//----------------------------------------------------
+
+package weka.filters.unsupervised.instance.subsetbyexpression;
+
+/** CUP generated interface containing symbol constants. */
+public interface sym {
+  /* terminals */
+  public static final int POW = 19;
+  public static final int SQRT = 11;
+  public static final int ABS = 10;
+  public static final int GE = 26;
+  public static final int ISMISSING = 5;
+  public static final int LPAREN = 3;
+  public static final int TAN = 16;
+  public static final int MINUS = 6;
+  public static final int FLOOR = 18;
+  public static final int RPAREN = 4;
+  public static final int NOT = 28;
+  public static final int IS = 31;
+  public static final int AND = 29;
+  public static final int LT = 23;
+  public static final int OR = 30;
+  public static final int COMMA = 2;
+  public static final int PLUS = 7;
+  public static final int EXP = 13;
+  public static final int LE = 24;
+  public static final int EOF = 0;
+  public static final int BOOLEAN = 33;
+  public static final int TRUE = 21;
+  public static final int error = 1;
+  public static final int NUMBER = 32;
+  public static final int COS = 15;
+  public static final int EQ = 27;
+  public static final int LOG = 12;
+  public static final int TIMES = 8;
+  public static final int SIN = 14;
+  public static final int STRING = 35;
+  public static final int RINT = 17;
+  public static final int CEIL = 20;
+  public static final int FALSE = 22;
+  public static final int DIVISION = 9;
+  public static final int ATTRIBUTE = 34;
+  public static final int GT = 25;
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/AttributeListPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/AttributeListPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/AttributeListPanel.java	(revision 29)
@@ -0,0 +1,246 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeListPanel.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * Creates a panel that displays the attributes contained in a set of
+ * instances, letting the user select a single attribute for inspection.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.3 $
+ */
+public class AttributeListPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2030706987910400362L;
+
+  /**
+   * A table model that looks at the names of attributes.
+   */
+  class AttributeTableModel
+    extends AbstractTableModel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -7345701953670327707L;
+
+    /** The instances who's attribute structure we are reporting */
+    protected Instances m_Instances;
+    
+    /**
+     * Creates the tablemodel with the given set of instances.
+     *
+     * @param instances the initial set of Instances
+     */
+    public AttributeTableModel(Instances instances) {
+
+      setInstances(instances);
+    }
+
+    /**
+     * Sets the tablemodel to look at a new set of instances.
+     *
+     * @param instances the new set of Instances.
+     */
+    public void setInstances(Instances instances) {
+
+      m_Instances = instances;
+    }
+    
+    /**
+     * Gets the number of attributes.
+     *
+     * @return the number of attributes.
+     */
+    public int getRowCount() {
+      
+      return m_Instances.numAttributes();
+    }
+    
+    /**
+     * Gets the number of columns: 2
+     *
+     * @return 2
+     */
+    public int getColumnCount() {
+      
+      return 2;
+    }
+    
+    /**
+     * Gets a table cell
+     *
+     * @param row the row index
+     * @param column the column index
+     * @return the value at row, column
+     */
+    public Object getValueAt(int row, int column) {
+      
+      switch (column) {
+      case 0:
+	return new Integer(row + 1);
+      case 1:
+	return m_Instances.attribute(row).name();
+      default:
+	return null;
+      }
+    }
+    
+    /**
+     * Gets the name for a column.
+     *
+     * @param column the column index.
+     * @return the name of the column.
+     */
+    public String getColumnName(int column) {
+      
+      switch (column) {
+      case 0:
+	return new String("No.");
+      case 1:
+	return new String("Name");
+      default:
+	return null;
+      }
+    }
+    
+    /**
+     * Gets the class of elements in a column.
+     *
+     * @param col the column index.
+     * @return the class of elements in the column.
+     */
+    public Class getColumnClass(int col) {
+      return getValueAt(0, col).getClass();
+    }
+
+    /**
+     * Returns false
+     *
+     * @param row ignored
+     * @param col ignored
+     * @return false
+     */
+    public boolean isCellEditable(int row, int col) {
+
+      return false;
+    }
+  }
+
+  /** The table displaying attribute names */
+  protected JTable m_Table = new JTable();
+
+  /** The table model containing attribute names */
+  protected AttributeTableModel m_Model;
+  
+  /**
+   * Creates the attribute selection panel with no initial instances.
+   */
+  public AttributeListPanel() {
+
+    m_Table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    m_Table.setColumnSelectionAllowed(false); 
+    m_Table.setPreferredScrollableViewportSize(new Dimension(250, 150));
+
+    setLayout(new BorderLayout());
+    add(new JScrollPane(m_Table), BorderLayout.CENTER);
+  }
+
+  /**
+   * Sets the instances who's attribute names will be displayed.
+   *
+   * @param newInstances the new set of instances
+   */
+  public void setInstances(Instances newInstances) {
+
+    if (m_Model == null) {
+      m_Model = new AttributeTableModel(newInstances);
+      m_Table.setModel(m_Model);
+      TableColumnModel tcm = m_Table.getColumnModel();
+      tcm.getColumn(0).setMaxWidth(60);
+      tcm.getColumn(1).setMinWidth(100);
+    } else {
+      m_Model.setInstances(newInstances);
+    }
+    m_Table.sizeColumnsToFit(-1);
+    m_Table.revalidate();
+    m_Table.repaint();
+  }
+  
+  /**
+   * Gets the selection model used by the table.
+   *
+   * @return a value of type 'ListSelectionModel'
+   */
+  public ListSelectionModel getSelectionModel() {
+
+    return m_Table.getSelectionModel();
+  }
+  
+  /**
+   * Tests the attribute list panel from the command line.
+   *
+   * @param args must contain the name of an arff file to load.
+   */
+  public static void main(String[] args) {
+
+    try {
+      if (args.length == 0) {
+	throw new Exception("supply the name of an arff file");
+      }
+      Instances i = new Instances(new java.io.BufferedReader(
+				  new java.io.FileReader(args[0])));
+      AttributeListPanel asp = new AttributeListPanel();
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Attribute List Panel");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(asp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      asp.setInstances(i);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+  
+} // AttributeListPanel
Index: branches/MetisMQI/src/main/java/weka/gui/AttributeSelectionPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/AttributeSelectionPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/AttributeSelectionPanel.java	(revision 29)
@@ -0,0 +1,439 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSelectionPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Instances;
+
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.BorderLayout;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.regex.Pattern;
+import javax.swing.JPanel;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.BorderFactory;
+
+/**
+ * Creates a panel that displays the attributes contained in a set of
+ * instances, letting the user toggle whether each attribute is selected
+ * or not (eg: so that unselected attributes can be removed before
+ * classification). <br>
+ * Besides the All, None and Invert button one can also choose attributes which
+ * names match a regular expression (Pattern button). E.g. for removing all
+ * attributes that contain an ID and therefore unwanted information, one can
+ * match all names that contain "id" in the name:<br> 
+ * <pre>   (.*_id_.*|.*_id$|^id$)</pre> 
+ * This does not match e.g. "humidity", which could be an attribute we would
+ * like to keep.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.9 $
+ */
+public class AttributeSelectionPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 627131485290359194L;
+
+  /**
+   * A table model that looks at the names of attributes and maintains
+   * a list of attributes that have been "selected".
+   */
+  class AttributeTableModel
+    extends AbstractTableModel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4152987434024338064L;
+
+    /** The instances who's attribute structure we are reporting */
+    protected Instances m_Instances;
+    
+    /** The flag for whether the instance will be included */
+    protected boolean [] m_Selected;
+
+    
+    /**
+     * Creates the tablemodel with the given set of instances.
+     *
+     * @param instances the initial set of Instances
+     */
+    public AttributeTableModel(Instances instances) {
+
+      setInstances(instances);
+    }
+
+    /**
+     * Sets the tablemodel to look at a new set of instances.
+     *
+     * @param instances the new set of Instances.
+     */
+    public void setInstances(Instances instances) {
+
+      m_Instances = instances;
+      m_Selected = new boolean [m_Instances.numAttributes()];
+    }
+    
+    /**
+     * Gets the number of attributes.
+     *
+     * @return the number of attributes.
+     */
+    public int getRowCount() {
+      
+      return m_Selected.length;
+    }
+    
+    /**
+     * Gets the number of columns: 3
+     *
+     * @return 3
+     */
+    public int getColumnCount() {
+      
+      return 3;
+    }
+    
+    /**
+     * Gets a table cell
+     *
+     * @param row the row index
+     * @param column the column index
+     * @return the value at row, column
+     */
+    public Object getValueAt(int row, int column) {
+      
+      switch (column) {
+      case 0:
+	return new Integer(row + 1);
+      case 1:
+	return new Boolean(m_Selected[row]);
+      case 2:
+	return m_Instances.attribute(row).name();
+      default:
+	return null;
+      }
+    }
+    
+    /**
+     * Gets the name for a column.
+     *
+     * @param column the column index.
+     * @return the name of the column.
+     */
+    public String getColumnName(int column) {
+      
+      switch (column) {
+      case 0:
+	return new String("No.");
+      case 1:
+	return new String("");
+      case 2:
+	return new String("Name");
+      default:
+	return null;
+      }
+    }
+    
+    /**
+     * Sets the value at a cell.
+     *
+     * @param value the new value.
+     * @param row the row index.
+     * @param col the column index.
+     */
+    public void setValueAt(Object value, int row, int col) {
+      
+      if (col == 1) {
+	m_Selected[row] = ((Boolean) value).booleanValue(); 
+      }
+    }
+    
+    /**
+     * Gets the class of elements in a column.
+     *
+     * @param col the column index.
+     * @return the class of elements in the column.
+     */
+    public Class getColumnClass(int col) {
+      return getValueAt(0, col).getClass();
+    }
+
+    /**
+     * Returns true if the column is the "selected" column.
+     *
+     * @param row ignored
+     * @param col the column index.
+     * @return true if col == 1.
+     */
+    public boolean isCellEditable(int row, int col) {
+
+      if (col == 1) { 
+	return true;
+      }
+      return false;
+    }
+    
+    /**
+     * Gets an array containing the indices of all selected attributes.
+     *
+     * @return the array of selected indices.
+     */
+    public int [] getSelectedAttributes() {
+      
+      int [] r1 = new int[getRowCount()];
+      int selCount = 0;
+      for (int i = 0; i < getRowCount(); i++) {
+	if (m_Selected[i]) {
+	  r1[selCount++] = i;
+	}
+      }
+      int [] result = new int[selCount];
+      System.arraycopy(r1, 0, result, 0, selCount);
+      return result;
+    }
+    
+    /**
+     * Sets the state of all attributes to selected.
+     */
+    public void includeAll() {
+      
+      for (int i = 0; i < m_Selected.length; i++) {
+	m_Selected[i] = true;
+      }
+      fireTableRowsUpdated(0, m_Selected.length);
+    }
+    
+    /**
+     * Deselects all attributes.
+     */
+    public void removeAll() {
+      
+      for (int i = 0; i < m_Selected.length; i++) {
+	m_Selected[i] = false;
+      }
+      fireTableRowsUpdated(0, m_Selected.length);
+    }
+
+    /**
+     * Inverts the selected status of each attribute.
+     */
+    public void invert() {
+
+      for (int i = 0; i < m_Selected.length; i++) {
+	m_Selected[i] = !m_Selected[i];
+      }
+      fireTableRowsUpdated(0, m_Selected.length);
+    }
+
+    /**
+     * applies the perl regular expression pattern to select the attribute
+     * names (expects a valid reg expression!)
+     * @param pattern     a perl reg. expression
+     */
+    public void pattern(String pattern) {
+      for (int i = 0; i < m_Selected.length; i++)
+        m_Selected[i] = Pattern.matches(
+                          pattern, m_Instances.attribute(i).name());
+      fireTableRowsUpdated(0, m_Selected.length);
+    }
+  }
+
+  /** Press to select all attributes */  
+  protected JButton m_IncludeAll = new JButton("All");
+
+  /** Press to deselect all attributes */
+  protected JButton m_RemoveAll = new JButton("None");
+
+  /** Press to invert the current selection */
+  protected JButton m_Invert = new JButton("Invert");
+
+  /** Press to enter a perl regular expression for selection */
+  protected JButton m_Pattern = new JButton("Pattern");
+
+  /** The table displaying attribute names and selection status */
+  protected JTable m_Table = new JTable();
+
+  /** The table model containingn attribute names and selection status */
+  protected AttributeTableModel m_Model;
+
+  /** The current regular expression. */
+  protected String m_PatternRegEx = "";
+  
+  /**
+   * Creates the attribute selection panel with no initial instances.
+   */
+  public AttributeSelectionPanel() {
+
+    m_IncludeAll.setToolTipText("Selects all attributes");
+    m_IncludeAll.setEnabled(false);
+    m_IncludeAll.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Model.includeAll();
+      }
+    });
+    m_RemoveAll.setToolTipText("Unselects all attributes");
+    m_RemoveAll.setEnabled(false);
+    m_RemoveAll.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Model.removeAll();
+      }
+    });
+    m_Invert.setToolTipText("Inverts the current attribute selection");
+    m_Invert.setEnabled(false);
+    m_Invert.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Model.invert();
+      }
+    });
+    m_Pattern.setToolTipText("Selects all attributes that match a reg. expression");
+    m_Pattern.setEnabled(false);
+    m_Pattern.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        String pattern = JOptionPane.showInputDialog(
+                            m_Pattern.getParent(),
+                            "Enter a Perl regular expression",
+                            m_PatternRegEx);
+        if (pattern != null) {
+          try {
+            Pattern.compile(pattern);
+            m_PatternRegEx = pattern;
+	    m_Model.pattern(pattern);
+          }
+          catch (Exception ex) {
+            JOptionPane.showMessageDialog(
+              m_Pattern.getParent(),
+              "'" + pattern + "' is not a valid Perl regular expression!\n" 
+              + "Error: " + ex, 
+              "Error in Pattern...", 
+              JOptionPane.ERROR_MESSAGE);
+          }
+        }
+      }
+    });
+    m_Table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    m_Table.setColumnSelectionAllowed(false); 
+    m_Table.setPreferredScrollableViewportSize(new Dimension(250, 150));
+
+    // Set up the layout
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    p1.setLayout(new GridLayout(1, 4, 5, 5));
+    p1.add(m_IncludeAll);
+    p1.add(m_RemoveAll);
+    p1.add(m_Invert);
+    p1.add(m_Pattern);
+
+    setLayout(new BorderLayout());
+    add(p1, BorderLayout.NORTH);
+    add(new JScrollPane(m_Table), BorderLayout.CENTER);
+  }
+
+  /**
+   * Sets the instances who's attribute names will be displayed.
+   *
+   * @param newInstances the new set of instances
+   */
+  public void setInstances(Instances newInstances) {
+
+    if (m_Model == null) {
+      m_Model = new AttributeTableModel(newInstances);
+      m_Table.setModel(m_Model);
+      TableColumnModel tcm = m_Table.getColumnModel();
+      tcm.getColumn(0).setMaxWidth(60);
+      tcm.getColumn(1).setMaxWidth(tcm.getColumn(1).getMinWidth());
+      tcm.getColumn(2).setMinWidth(100);
+    } else {
+      m_Model.setInstances(newInstances);
+      m_Table.clearSelection();
+    }
+    m_IncludeAll.setEnabled(true);
+    m_RemoveAll.setEnabled(true);
+    m_Invert.setEnabled(true);
+    m_Pattern.setEnabled(true);
+    m_Table.sizeColumnsToFit(2);
+    m_Table.revalidate();
+    m_Table.repaint();
+  }
+
+  /**
+   * Gets an array containing the indices of all selected attributes.
+   *
+   * @return the array of selected indices.
+   */
+  public int [] getSelectedAttributes() {
+    
+    return m_Model.getSelectedAttributes();
+  }
+  
+  /**
+   * Gets the selection model used by the table.
+   *
+   * @return a value of type 'ListSelectionModel'
+   */
+  public ListSelectionModel getSelectionModel() {
+
+    return m_Table.getSelectionModel();
+  }
+  
+  /**
+   * Tests the attribute selection panel from the command line.
+   *
+   * @param args must contain the name of an arff file to load.
+   */
+  public static void main(String[] args) {
+
+    try {
+      if (args.length == 0) {
+	throw new Exception("supply the name of an arff file");
+      }
+      Instances i = new Instances(new java.io.BufferedReader(
+				  new java.io.FileReader(args[0])));
+      AttributeSelectionPanel asp = new AttributeSelectionPanel();
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Attribute Selection Panel");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(asp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      asp.setInstances(i);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+  
+} // AttributeSelectionPanel
Index: branches/MetisMQI/src/main/java/weka/gui/AttributeSummaryPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/AttributeSummaryPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/AttributeSummaryPanel.java	(revision 29)
@@ -0,0 +1,399 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSummaryPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+
+/** 
+ * This panel displays summary statistics about an attribute: name, type
+ * number/% of missing/unique values, number of distinct values. For
+ * numeric attributes gives some other stats (mean/std dev), for nominal
+ * attributes gives counts for each attribute value.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5297 $
+ */
+public class AttributeSummaryPanel 
+  extends JPanel {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5434987925737735880L;
+
+  /** Message shown when no instances have been loaded and no attribute set */
+  protected static final String NO_SOURCE = "None";
+
+  /** Displays the name of the relation */
+  protected JLabel m_AttributeNameLab = new JLabel(NO_SOURCE);
+  
+  /** Displays the type of attribute */
+  protected JLabel m_AttributeTypeLab = new JLabel(NO_SOURCE);
+  
+  /** Displays the number of missing values */
+  protected JLabel m_MissingLab = new JLabel(NO_SOURCE);
+    
+  /** Displays the number of unique values */
+  protected JLabel m_UniqueLab = new JLabel(NO_SOURCE);
+    
+  /** Displays the number of distinct values */
+  protected JLabel m_DistinctLab = new JLabel(NO_SOURCE);
+
+  /** Displays other stats in a table */
+  protected JTable m_StatsTable = new JTable() {
+    /** for serialization */
+    private static final long serialVersionUID = 7165142874670048578L;
+
+    /**
+     * returns always false, since it's just information for the user
+     * 
+     * @param row	the row
+     * @param column	the column
+     * @return		always false, i.e., the whole table is not editable
+     */
+    public boolean isCellEditable(int row, int column) {
+      return false;
+    }
+  };
+  
+  /** The instances we're playing with */
+  protected Instances m_Instances;
+
+  /** Cached stats on the attributes we've summarized so far */
+  protected AttributeStats [] m_AttributeStats;
+  
+  /** Do all instances have the same weight */
+  protected boolean m_allEqualWeights = true;
+  
+  /**
+   * Creates the instances panel with no initial instances.
+   */
+  public AttributeSummaryPanel() {
+
+    JPanel simple = new JPanel();
+    GridBagLayout gbL = new GridBagLayout();
+    simple.setLayout(gbL);
+    JLabel lab = new JLabel("Name:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    GridBagConstraints gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(lab, gbC);
+    simple.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.weightx = 100; gbC.gridwidth = 3;
+    gbL.setConstraints(m_AttributeNameLab, gbC);
+    simple.add(m_AttributeNameLab);
+    m_AttributeNameLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
+    
+    lab = new JLabel("Type:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 4;
+    gbL.setConstraints(lab, gbC);
+    simple.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 5;
+    gbC.weightx = 100;
+    gbL.setConstraints(m_AttributeTypeLab, gbC);
+    simple.add(m_AttributeTypeLab);
+    m_AttributeTypeLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
+
+    // Put into a separate panel?
+    lab = new JLabel("Missing:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 5, 0));
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(lab, gbC);
+    simple.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 1;
+    gbC.weightx = 100;
+    gbL.setConstraints(m_MissingLab, gbC);
+    simple.add(m_MissingLab);
+    m_MissingLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 10));
+
+    lab = new JLabel("Distinct:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 5, 0));
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 2;
+    gbL.setConstraints(lab, gbC);
+    simple.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 3;
+    gbC.weightx = 100;
+    gbL.setConstraints(m_DistinctLab, gbC);
+    simple.add(m_DistinctLab);
+    m_DistinctLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 10));
+
+    lab = new JLabel("Unique:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 5, 0));
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 4;
+    gbL.setConstraints(lab, gbC);
+    simple.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 5;
+    gbC.weightx = 100;
+    gbL.setConstraints(m_UniqueLab, gbC);
+    simple.add(m_UniqueLab);
+    m_UniqueLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 10));
+    
+    setLayout(new BorderLayout());
+    add(simple, BorderLayout.NORTH);
+    add(new JScrollPane(m_StatsTable), BorderLayout.CENTER);
+    m_StatsTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+  }
+
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    
+    m_Instances = inst;
+    m_AttributeStats = new AttributeStats [inst.numAttributes()];
+    m_AttributeNameLab.setText(NO_SOURCE);
+    m_AttributeTypeLab.setText(NO_SOURCE);
+    m_MissingLab.setText(NO_SOURCE);
+    m_UniqueLab.setText(NO_SOURCE);
+    m_DistinctLab.setText(NO_SOURCE);
+    m_StatsTable.setModel(new DefaultTableModel());
+    
+    m_allEqualWeights = true;
+    double w = m_Instances.instance(0).weight();
+    for (int i = 1; i < m_Instances.numInstances(); i++) {
+      if (m_Instances.instance(i).weight() != w) {
+        m_allEqualWeights = false;
+        break;
+      }
+    }
+  }
+
+  /**
+   * Sets the attribute that statistics will be displayed for.
+   *
+   * @param index the index of the attribute to display
+   */
+  public void setAttribute(final int index) {
+
+    setHeader(index);
+    if (m_AttributeStats[index] == null) {
+      Thread t = new Thread() {
+	public void run() {
+	  m_AttributeStats[index] = m_Instances
+	  .attributeStats(index);
+	  SwingUtilities.invokeLater(new Runnable() {
+	    public void run() {
+	      setDerived(index);
+	      m_StatsTable.sizeColumnsToFit(-1);
+	      m_StatsTable.revalidate();
+	      m_StatsTable.repaint();
+	    }
+	  });
+	}
+      };
+      t.setPriority(Thread.MIN_PRIORITY);
+      t.start();
+    } else {
+      setDerived(index);
+    }
+  }
+  
+  /**
+   * Sets the gui elements for fields that are stored in the AttributeStats
+   * structure.
+   * 
+   * @param index	the index of the attribute
+   */
+  protected void setDerived(int index) {
+    
+    AttributeStats as = m_AttributeStats[index];
+    long percent = Math.round(100.0 * as.missingCount / as.totalCount);
+    m_MissingLab.setText("" + as.missingCount + " (" + percent + "%)");
+    percent = Math.round(100.0 * as.uniqueCount / as.totalCount);
+    m_UniqueLab.setText("" + as.uniqueCount + " (" + percent + "%)");
+    m_DistinctLab.setText("" + as.distinctCount);
+    setTable(as, index);
+  }
+
+  /**
+   * Creates a tablemodel for the attribute being displayed
+   * 
+   * @param as		the attribute statistics
+   * @param index	the index of the attribute
+   */
+  protected void setTable(AttributeStats as, int index) {
+
+    if (as.nominalCounts != null) {
+      Attribute att = m_Instances.attribute(index);
+      Object [] colNames = {"No.", "Label", "Count", "Weight"};
+      Object [][] data = new Object [as.nominalCounts.length][4];
+      for (int i = 0; i < as.nominalCounts.length; i++) {
+	data[i][0] = new Integer(i + 1);
+	data[i][1] = att.value(i);
+	data[i][2] = new Integer(as.nominalCounts[i]);
+	data[i][3] = new Double(Utils.doubleToString(as.nominalWeights[i], 3));
+      }
+      m_StatsTable.setModel(new DefaultTableModel(data, colNames));
+      m_StatsTable.getColumnModel().getColumn(0).setMaxWidth(60);
+      DefaultTableCellRenderer tempR = new DefaultTableCellRenderer();
+      tempR.setHorizontalAlignment(JLabel.RIGHT);
+      m_StatsTable.getColumnModel().getColumn(0).setCellRenderer(tempR);
+    } else if (as.numericStats != null) {
+      Object [] colNames = {"Statistic", "Value"};
+      Object [][] data = new Object [4][2];
+      data[0][0] = "Minimum"; data[0][1] = Utils.doubleToString(as.numericStats.min, 3);
+      data[1][0] = "Maximum"; data[1][1] = Utils.doubleToString(as.numericStats.max, 3);
+      data[2][0] = "Mean" + ((!m_allEqualWeights) ? " (weighted)" : "");    
+      data[2][1] = Utils.doubleToString(as.numericStats.mean, 3);
+      data[3][0] = "StdDev" + ((!m_allEqualWeights) ? " (weighted)" : "");  
+      data[3][1] = Utils.doubleToString(as.numericStats.stdDev, 3);
+      m_StatsTable.setModel(new DefaultTableModel(data, colNames));
+    } else {
+      m_StatsTable.setModel(new DefaultTableModel());
+    }
+    m_StatsTable.getColumnModel().setColumnMargin(4);
+  }
+  
+  /**
+   * Sets the labels for fields we can determine just from the instance
+   * header.
+   * 
+   * @param index	the index of the attribute
+   */
+  protected void setHeader(int index) {
+    
+    Attribute att = m_Instances.attribute(index);
+    m_AttributeNameLab.setText(att.name());
+    switch (att.type()) {
+    case Attribute.NOMINAL:
+      m_AttributeTypeLab.setText("Nominal");
+      break;
+    case Attribute.NUMERIC:
+      m_AttributeTypeLab.setText("Numeric");
+      break;
+    case Attribute.STRING:
+      m_AttributeTypeLab.setText("String");
+      break;
+    case Attribute.DATE:
+      m_AttributeTypeLab.setText("Date");
+      break;
+    case Attribute.RELATIONAL:
+      m_AttributeTypeLab.setText("Relational");
+      break;
+    default:
+      m_AttributeTypeLab.setText("Unknown");
+      break;
+    }
+    m_MissingLab.setText("...");
+    m_UniqueLab.setText("...");
+    m_DistinctLab.setText("...");
+  }
+
+  /**
+   * Tests out the attribute summary panel from the command line.
+   *
+   * @param args optional name of dataset to load
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame("Attribute Panel");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final AttributeSummaryPanel p = new AttributeSummaryPanel();
+      p.setBorder(BorderFactory.createTitledBorder("Attribute"));
+      jf.getContentPane().add(p, BorderLayout.CENTER);
+      final javax.swing.JComboBox j = new javax.swing.JComboBox();
+      j.setEnabled(false);
+      j.addActionListener(new java.awt.event.ActionListener() {
+	public void actionPerformed(java.awt.event.ActionEvent e) {
+	  p.setAttribute(j.getSelectedIndex());
+	}
+      });
+      jf.getContentPane().add(j, BorderLayout.NORTH);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      if (args.length == 1) {
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances inst = new Instances(r);
+	p.setInstances(inst);
+	p.setAttribute(0);
+	String [] names = new String [inst.numAttributes()];
+	for (int i = 0; i < names.length; i++) {
+	  names[i] = inst.attribute(i).name();
+	}
+	j.setModel(new javax.swing.DefaultComboBoxModel(names));
+	j.setEnabled(true);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/AttributeVisualizationPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/AttributeVisualizationPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/AttributeVisualizationPanel.java	(revision 29)
@@ -0,0 +1,1474 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeVisualizationPanel.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.gui.visualize.PrintableComponent;
+import weka.gui.visualize.PrintablePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.io.FileReader;
+
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+
+/**
+ * Creates a panel that shows a visualization of an
+ * attribute in a dataset. For nominal attribute it
+ * shows a bar plot, with each bar corresponding to
+ * each nominal value of the attribute with its height
+ * equal to the frequecy that value appears in the
+ * dataset. For numeric attributes, it displays a
+ * histogram. The width of an interval in the
+ * histogram is calculated using Scott's(1979)
+ * method: <br>
+ *    intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3))
+ * Then the number of intervals is calculated by: <br>
+ *   intervals = max(1, Math.round(Range/intervalWidth);
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 5721 $
+ */
+public class AttributeVisualizationPanel
+  extends PrintablePanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8650490488825371193L;
+  
+  /** This holds the current set of instances */
+  protected Instances m_data;
+  
+  /**
+   * This holds the attribute stats of the current attribute on display. It is
+   * calculated in setAttribute(int idx) when it is called to set a new
+   * attribute index.
+   */
+  protected AttributeStats m_as;
+  
+  /** This holds the index of the current attribute on display and should be
+   *  set through setAttribute(int idx).
+   */
+  protected int m_attribIndex;
+  
+  /**
+   * This holds the max value of the current attribute. In case of nominal
+   * attribute it is the highest count that a nominal value has in the
+   * attribute (given by m_as.nominalWeights[i]), otherwise in case of numeric
+   * attribute it is simply the maximum value present in the attribute (given by
+   * m_as.numericStats.max). It is used to calculate the ratio of the height of
+   * the bars with respect to the height of the display area.
+   */
+  protected double m_maxValue;
+  
+  /**
+   * This array holds the count (or height) for the each of the bars in a
+   * barplot or a histogram. In case of barplots (and current attribute being
+   * nominal) its length (and the number of bars) is equal to the number of
+   * nominal values in the current attribute, with each field of the array being
+   * equal to the count of each nominal that it represents (the count of ith
+   * nominal value of an attribute is given by m_as.nominalWeights[i]). Whereas,
+   * in case of histograms (and current attribute being numeric) the width of
+   * its intervals is calculated by Scott's(1979) method: <br>
+   *    intervalWidth = Max(1, 3.49*Std.Dev*numInstances^(1/3))
+   * And the number of intervals by: <br>
+   *   intervals = max(1, Math.round(Range/intervalWidth);
+   * Then each field of this array contains the number of values of the current
+   * attribute that fall in the histogram interval that it represents. <br>
+   * NOTE: The values of this array are only calculated if the class attribute
+   * is not set or if it is numeric.
+   */
+  protected double[] m_histBarCounts;
+  
+  /**
+   * This array holds the per class count (or per class height) of the each of
+   * the bars in a barplot or a histogram.
+   * For nominal attributes the format is: <br>
+   *    m_histBarClassCounts[nominalValue][classValue+1].
+   * For numeric attributes the format is: <br>
+   *    m_histBarClassCounts[interval][classValues+1], <br>
+   *      where the number of intervals is calculated by the Scott's method as
+   *            mentioned above.
+   * The array is initialized to have 1+numClasses to accomodate for instances
+   * with missing class value. The ones with missing class value are displayed
+   * as a black sub par in a histogram or a barplot.
+   *
+   * NOTE: The values of this array are only calculated if the class attribute
+   * is set and it is nominal.
+   */
+  SparseInstance m_histBarClassCounts[];
+  
+  /**
+   * Contains the range of each bar in a histogram. It is used to work out the
+   * range of bar the mouse pointer is on in getToolTipText().
+   */
+  protected double m_barRange;
+  
+  /** Contains the current class index. */
+  protected int m_classIndex;
+  
+  /** This stores the BarCalc or HistCalc thread while a new barplot or
+   * histogram is being calculated. */
+  private Thread m_hc;
+  
+  /** True if the thread m_hc above is running. */
+  private boolean m_threadRun=false;
+  
+  private boolean m_doneCurrentAttribute = false;
+  private boolean m_displayCurrentAttribute = false;
+  
+  /** This stores and lets the user select a class attribute. It also has
+   * an entry "No Class" if the user does not want to set a class attribute
+   * for colouring.
+   */
+  protected JComboBox m_colorAttrib;
+  
+  /**
+   * Fontmetrics used to get the font size which is required for calculating
+   * displayable area size, bar height ratio and width of strings that are
+   * displayed on top of bars indicating their count.
+   */
+  private FontMetrics m_fm;
+  
+  /**
+   * Lock variable to synchronize the different threads running currently in
+   * this class. There are two to three threads in this class, AWT paint thread
+   * which is handled differently in paintComponent() which checks on
+   * m_threadRun to determine if it can perform full paint or not, the second
+   * thread is the main execution thread and the third is the one represented by
+   * m_hc which we start when we want to calculate the internal fields for a bar
+   * plot or a histogram.
+   */
+  private Integer m_locker = new Integer(1);
+  
+  //Image img;
+  
+  /** Contains discrete colours for colouring of subbars of histograms and
+   * bar plots when the class attribute is set and is nominal
+   */
+  private FastVector m_colorList = new FastVector();
+  
+  /** default colour list */
+  private static final Color [] m_defaultColors = {Color.blue,
+  Color.red,
+  Color.cyan,
+  new Color(75, 123, 130),
+  Color.pink,
+  Color.green,
+  Color.orange,
+  new Color(255, 0, 255),
+  new Color(255, 0, 0),
+  new Color(0, 255, 0),
+  };
+  
+  /**
+   * Constructor - If used then the class will not show the class selection
+   * combo box.
+   */
+  public AttributeVisualizationPanel() {
+    this(false);
+  }
+  
+  /**
+   * Constructor.
+   * @param showColouringOption - should be true if the class selection combo
+   * box is to be displayed with the histogram/barplot, or false otherwise.
+   * P.S: the combo box is always created it just won't be shown if
+   * showColouringOption is false.
+   */
+  public AttributeVisualizationPanel(boolean showColouringOption) {
+    this.setFont( new Font("Default", Font.PLAIN, 9) );
+    m_fm = this.getFontMetrics( this.getFont() );
+    this.setToolTipText("");
+    FlowLayout fl= new FlowLayout(FlowLayout.LEFT);
+    this.setLayout(fl);
+    this.addComponentListener( new ComponentAdapter() {
+      public void componentResized(ComponentEvent ce) {
+        if(m_data!=null) {
+//          calcGraph();
+        }
+      }
+    });
+    
+    m_colorAttrib = new JComboBox();
+    m_colorAttrib.addItemListener( new ItemListener() {
+      public void itemStateChanged(ItemEvent ie) {
+        if(ie.getStateChange()==ItemEvent.SELECTED) {
+          m_classIndex = m_colorAttrib.getSelectedIndex() - 1;
+          if (m_as != null) {
+            setAttribute(m_attribIndex);
+          }
+        }
+      }
+    });
+    
+    if(showColouringOption) {
+      //m_colorAttrib.setVisible(false);
+      this.add(m_colorAttrib);
+      validate();
+    }
+  }
+  
+  /**
+   * Sets the instances for use
+   *
+   * @param newins a set of Instances
+   */
+  public void setInstances(Instances newins) {
+    m_attribIndex = 0;
+    m_as = null;
+    m_data = new Instances(newins);
+    if(m_colorAttrib!=null) {
+      m_colorAttrib.removeAllItems();
+      m_colorAttrib.addItem("No class");
+      for(int i=0; i<m_data.numAttributes(); i++) {
+	String type = "";
+	switch (m_data.attribute(i).type()) {
+	  case Attribute.NOMINAL:
+	    type = "(Nom) ";
+	    break;
+	  case Attribute.NUMERIC:
+	    type = "(Num) ";
+	    break;
+	  case Attribute.STRING:
+	    type = "(Str) ";
+	    break;
+	  case Attribute.DATE:
+	    type = "(Dat) ";
+	    break;
+	  case Attribute.RELATIONAL:
+	    type = "(Rel) ";
+	    break;
+	  default:
+	    type = "(???) ";
+	}
+        m_colorAttrib.addItem(new String("Class: "+m_data.attribute(i).name()+
+        " " + type));
+      }
+      if (m_data.classIndex() >= 0) {
+        m_colorAttrib.setSelectedIndex(m_data.classIndex() + 1);
+      } else {
+        m_colorAttrib.setSelectedIndex(m_data.numAttributes());
+      }
+      //if (m_data.classIndex() >= 0) {
+      //    m_colorAttrib.setSelectedIndex(m_data.classIndex());
+      //}
+    }
+    if (m_data.classIndex() >= 0) {
+      m_classIndex = m_data.classIndex();
+    } else {
+      m_classIndex = m_data.numAttributes()-1;
+    }
+    
+  }
+  
+  /**
+   * Returns the class selection combo box if the parent component wants to
+   * place it in itself or in some component other than this component.
+   */
+  public JComboBox getColorBox() {
+    return m_colorAttrib;
+  }
+  
+  /**
+   * Get the coloring (class) index for the plot
+   *
+   * @return an <code>int</code> value
+   */
+  public int getColoringIndex() {
+    return m_classIndex; //m_colorAttrib.getSelectedIndex();
+  }
+  
+  /**
+   * Set the coloring (class) index for the plot
+   *
+   * @param ci an <code>int</code> value
+   */
+  public void setColoringIndex(int ci) {
+    m_classIndex = ci;
+    if(m_colorAttrib!=null)
+      m_colorAttrib.setSelectedIndex(ci + 1);
+    else
+      setAttribute(m_attribIndex);
+  }
+  
+  /**
+   * Tells the panel which attribute to visualize.
+   *
+   * @param index The index of the attribute
+   */
+  public void setAttribute(int index) {
+    
+    synchronized (m_locker) {
+      //m_threadRun = true;
+      m_threadRun = false;
+      m_doneCurrentAttribute = false;
+      m_displayCurrentAttribute = true;
+      //if(m_hc!=null && m_hc.isAlive()) m_hc.stop();
+      m_attribIndex = index;
+      m_as = m_data.attributeStats(m_attribIndex);
+      //m_classIndex = m_colorAttrib.getSelectedIndex();
+    }
+    this.repaint();
+    // calcGraph();
+  }
+  
+  /**
+   * Recalculates the barplot or histogram to display, required usually when the
+   * attribute is changed or the component is resized.
+   */
+  public void calcGraph(int panelWidth, int panelHeight) {
+    
+    synchronized (m_locker) {
+      m_threadRun = true;
+      if(m_as.nominalWeights!=null) {
+        m_hc = new BarCalc(panelWidth, panelHeight);
+        m_hc.setPriority(m_hc.MIN_PRIORITY);
+        m_hc.start();
+      }
+      else if(m_as.numericStats!=null) {
+        m_hc = new HistCalc();
+        m_hc.setPriority(m_hc.MIN_PRIORITY);
+        m_hc.start();
+      } else {
+        m_histBarCounts = null;
+        m_histBarClassCounts = null;
+        m_doneCurrentAttribute = true;
+        m_threadRun = false;
+        this.repaint();
+      }
+    }
+  }
+  
+  /**
+   * Internal class that calculates the barplot to display, in a separate
+   * thread. In particular it initializes some of the crucial internal fields
+   * required by paintComponent() to display the histogram for the current
+   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
+   * m_maxValue and m_colorList.
+   */
+  private class BarCalc extends Thread {
+    private int m_panelWidth;
+    private int m_panelHeight;
+    
+    public BarCalc(int panelWidth, int panelHeight) {
+      m_panelWidth = panelWidth;
+      m_panelHeight = panelHeight;
+    }
+    
+    public void run() {
+      synchronized (m_locker) {
+        // there is no use doing/displaying anything if the resolution
+        // of the panel is less than the number of values for this attribute
+        if (m_data.attribute(m_attribIndex).numValues() > m_panelWidth) {
+          m_histBarClassCounts = null;
+          m_threadRun = false;
+          m_doneCurrentAttribute = true;
+          m_displayCurrentAttribute = false;
+          AttributeVisualizationPanel.this.repaint();
+          return;
+        }
+        
+        if((m_classIndex >= 0) &&
+        (m_data.attribute(m_classIndex).isNominal())) {
+          SparseInstance histClassCounts[];
+          histClassCounts = new SparseInstance[m_data.attribute(m_attribIndex).numValues()];
+                                  //[m_data.attribute(m_classIndex).numValues()+1];
+          
+          if (m_as.nominalWeights.length > 0) {
+            m_maxValue = m_as.nominalWeights[0];
+            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
+              if(m_as.nominalWeights[i]>m_maxValue)
+        	m_maxValue = m_as.nominalWeights[i];
+            }
+          }
+          else {
+            m_maxValue = 0;
+          }
+          
+          if(m_colorList.size()==0)
+            m_colorList.addElement(Color.black);
+          for(int i=m_colorList.size();
+          i < m_data.attribute(m_classIndex).numValues()+1; i++) {
+            Color pc = m_defaultColors[(i-1) % 10];
+            int ija =  (i-1) / 10;
+            ija *= 2;
+            
+            for (int j=0;j<ija;j++) {
+              pc = pc.darker();
+            }
+            
+            m_colorList.addElement(pc);
+          }
+          
+          // first sort data on attribute values
+          m_data.sort(m_attribIndex);
+          double[] tempClassCounts = null;
+          int tempAttValueIndex = -1;
+          
+          for(int k=0; k<m_data.numInstances(); k++) {
+            //System.out.println("attrib: "+
+            //                   m_data.instance(k).value(m_attribIndex)+
+            //                   " class: "+
+            //                   m_data.instance(k).value(m_classIndex));
+            if(!m_data.instance(k).isMissing(m_attribIndex)) {
+              // check to see if we need to allocate some space here
+              if (m_data.instance(k).value(m_attribIndex) != tempAttValueIndex) {
+                if (tempClassCounts != null) {
+                  // set up the sparse instance for the previous bar (if any)
+                  int numNonZero = 0;
+                  for (int z = 0; z < tempClassCounts.length; z++) {
+                    if (tempClassCounts[z] > 0) {
+                      numNonZero++;
+                    }
+                  }
+                  double[] nonZeroVals = new double[numNonZero];
+                  int[] nonZeroIndices = new int[numNonZero];
+                  int count = 0;
+                  for (int z = 0; z < tempClassCounts.length; z++) {
+                    if (tempClassCounts[z] > 0) {
+                      nonZeroVals[count] = tempClassCounts[z];
+                      nonZeroIndices[count++] = z;
+                    }
+                  }
+                  SparseInstance tempS = 
+                    new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length);
+                  histClassCounts[tempAttValueIndex] = tempS;
+                }
+                
+                tempClassCounts = new double[m_data.attribute(m_classIndex).numValues() + 1];
+                tempAttValueIndex = (int)m_data.instance(k).value(m_attribIndex);
+                
+                /* histClassCounts[(int)m_data.instance(k).value(m_attribIndex)] = 
+                  new double[m_data.attribute(m_classIndex).numValues()+1]; */ 
+              }
+              if(m_data.instance(k).isMissing(m_classIndex)) {
+                /* histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
+                               [0] += m_data.instance(k).weight(); */
+                tempClassCounts[0] += m_data.instance(k).weight();
+              } else {
+                tempClassCounts[(int)m_data.instance(k).value(m_classIndex)+1] 
+                                += m_data.instance(k).weight();
+                
+                /*histClassCounts[(int)m_data.instance(k).value(m_attribIndex)]
+                              [(int)m_data.instance(k).value(m_classIndex)+1] += m_data.instance(k).weight();*/
+              }
+            }
+          }
+          
+          // set up sparse instance for last bar?
+          if (tempClassCounts != null) {
+            // set up the sparse instance for the previous bar (if any)
+            int numNonZero = 0;
+            for (int z = 0; z < tempClassCounts.length; z++) {
+              if (tempClassCounts[z] > 0) {
+                numNonZero++;
+              }
+            }
+            double[] nonZeroVals = new double[numNonZero];
+            int[] nonZeroIndices = new int[numNonZero];
+            int count = 0;
+            for (int z = 0; z < tempClassCounts.length; z++) {
+              if (tempClassCounts[z] > 0) {
+                nonZeroVals[count] = tempClassCounts[z];
+                nonZeroIndices[count++] = z;
+              }
+            }
+            SparseInstance tempS = 
+              new SparseInstance(1.0, nonZeroVals, nonZeroIndices, tempClassCounts.length);
+            histClassCounts[tempAttValueIndex] = tempS;
+          }
+          
+          //for(int i=0; i<histClassCounts.length; i++) {
+          //int sum=0;
+          //for(int j=0; j<histClassCounts[i].length; j++) {
+          //    sum = sum+histClassCounts[i][j];
+          //}
+          //System.out.println("histCount: "+sum+" Actual: "+
+          //                   m_as.nominalWeights[i]);
+          //}
+          
+          m_threadRun=false;
+          m_doneCurrentAttribute = true;
+          m_displayCurrentAttribute = true;
+          m_histBarClassCounts = histClassCounts;
+          //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
+          //                                 BufferedImage.TYPE_INT_RGB);
+          //drawGraph( tmpImg.getGraphics() );
+          //img = tmpImg;
+          AttributeVisualizationPanel.this.repaint();
+        }
+        else {
+          double histCounts[];
+          histCounts  = new double[m_data.attribute(m_attribIndex).numValues()];
+          
+          if (m_as.nominalWeights.length > 0) {
+            m_maxValue = m_as.nominalWeights[0];
+            for(int i=0; i<m_data.attribute(m_attribIndex).numValues(); i++) {
+              if(m_as.nominalWeights[i]>m_maxValue)
+        	m_maxValue = m_as.nominalWeights[i];
+            }
+          }
+          else {
+            m_maxValue = 0;
+          }
+          
+          for(int k=0; k<m_data.numInstances(); k++) {
+            if(!m_data.instance(k).isMissing(m_attribIndex))
+              histCounts[(int)m_data.instance(k).value(m_attribIndex)] += 
+                m_data.instance(k).weight();
+          }
+          m_threadRun=false;
+          m_displayCurrentAttribute = true;
+          m_doneCurrentAttribute = true;
+          m_histBarCounts = histCounts;
+          //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
+          //                                 BufferedImage.TYPE_INT_RGB);
+          //drawGraph( tmpImg.getGraphics() );
+          //img = tmpImg;
+          AttributeVisualizationPanel.this.repaint();
+        }
+      } //end synchronized
+    }  //end run()
+  }
+  
+  /**
+   * Internal class that calculates the histogram to display, in a separate
+   * thread. In particular it initializes some of the crucial internal fields
+   * required by paintComponent() to display the histogram for the current
+   * attribute. These include: m_histBarCounts or m_histBarClassCounts,
+   * m_maxValue and m_colorList.
+   */
+  private class HistCalc extends Thread {
+    public void run() {
+      synchronized (m_locker) {
+        if((m_classIndex >= 0) &&
+           (m_data.attribute(m_classIndex).isNominal())) {
+          
+          int intervals; double intervalWidth=0.0;
+          
+          //This uses the M.P.Wand's method to calculate the histogram's
+          //interval width. See "Data-Based Choice of Histogram Bin Width", in
+          //The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
+          //intervalWidth = Math.pow(6D/( -psi(2, g21())*m_data.numInstances()),
+          //                          1/3D );
+          
+          //This uses the Scott's method to calculate the histogram's interval
+          //width. See "On optimal and data-based histograms".
+          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
+          intervalWidth =  3.49 * m_as.numericStats.stdDev *
+                           Math.pow(m_data.numInstances(), -1/3D);
+          //The Math.max is introduced to remove the possibility of
+          //intervals=0 and =NAN that can happen if respectively all the numeric
+          //values are the same or the interval width is evaluated to zero.
+          intervals = Math.max(1,
+          (int)Math.round( (m_as.numericStats.max - m_as.numericStats.min) /
+                           intervalWidth) );
+          
+          //System.out.println("Max: "+m_as.numericStats.max+
+          //                   " Min: "+m_as.numericStats.min+
+          //                   " stdDev: "+m_as.numericStats.stdDev+
+          //                   "intervalWidth: "+intervalWidth);
+          
+          //The number 4 below actually represents a padding of 3 pixels on
+          //each side of the histogram, and is also reflected in other parts of 
+          //the code in the shape of numerical constants like "6" here.
+          if(intervals > AttributeVisualizationPanel.this.getWidth()) {
+            intervals = AttributeVisualizationPanel.this.getWidth()-6;
+            if(intervals<1)//if width is too small then use 1 and forget padding
+              intervals = 1;
+          }
+          double histClassCounts[][]  =
+                          new double[intervals]
+                                 [m_data.attribute(m_classIndex).numValues()+1];
+          
+          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
+                              (double)histClassCounts.length;
+          
+          m_maxValue = 0;
+          
+          if(m_colorList.size()==0)
+            m_colorList.addElement(Color.black);
+          for(int i = m_colorList.size();
+          i < m_data.attribute(m_classIndex).numValues()+1; i++) {
+            Color pc = m_defaultColors[(i-1) % 10];
+            int ija =  (i-1) / 10;
+            ija *= 2;
+            for (int j=0;j<ija;j++) {
+              pc = pc.darker();
+            }
+            m_colorList.addElement(pc);
+          }
+          
+          for(int k=0; k<m_data.numInstances(); k++) {
+            int t=0; //This holds the interval that the attibute value of the
+                     //new instance belongs to.
+            try {
+              if(!m_data.instance(k).isMissing(m_attribIndex)) {
+                //1. see footnote at the end of this file
+                t = (int)Math.ceil( (float)(
+                (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
+                / barRange) );
+                if(t==0) {
+                  if(m_data.instance(k).isMissing(m_classIndex))
+                    histClassCounts[t][0] += m_data.instance(k).weight();
+                  else
+                    histClassCounts[t][(int)m_data.instance(k).value(m_classIndex)+1] +=
+                      m_data.instance(k).weight();
+                  //if(histCounts[t]>m_maxValue)
+                  //  m_maxValue = histCounts[t];
+                }
+                else {
+                  if(m_data.instance(k).isMissing(m_classIndex))
+                    histClassCounts[t-1][0] += m_data.instance(k).weight();
+                  else
+                    histClassCounts[t-1][(int)m_data.instance(k).value(m_classIndex)+1] +=
+                      m_data.instance(k).weight();
+                  //if(histCounts[t-1]>m_maxValue)
+                  //  m_maxValue = histCounts[t-1];
+                }
+              }
+            }
+            catch(ArrayIndexOutOfBoundsException ae) {
+              System.out.println("t:"+(t)+
+              " barRange:"+barRange+
+              " histLength:"+histClassCounts.length+
+              " value:"+m_data.instance(k).value(m_attribIndex)+
+              " min:"+m_as.numericStats.min+
+              " sumResult:"+
+              (m_data.instance(k).value(m_attribIndex) -
+              m_as.numericStats.min)+
+              " divideResult:"+
+              (float)((m_data.instance(k).value(m_attribIndex) -
+              m_as.numericStats.min) / barRange)+
+              " finalResult:"+
+              Math.ceil((float)((m_data.instance(k).value(m_attribIndex)-
+              m_as.numericStats.min) / barRange)) );
+            }
+          }
+          for(int i=0; i<histClassCounts.length; i++) {
+            double sum=0;
+            for(int j=0; j<histClassCounts[i].length; j++)
+              sum = sum+histClassCounts[i][j];
+            if(m_maxValue<sum)
+              m_maxValue = sum;
+          }
+          
+          // convert to sparse instances
+          SparseInstance[] histClassCountsSparse = 
+            new SparseInstance[histClassCounts.length];
+          
+          for (int i = 0; i < histClassCounts.length; i++) {
+            int numSparseValues = 0;
+            for (int j = 0; j < histClassCounts[i].length; j++) {
+              if (histClassCounts[i][j] > 0) {
+                numSparseValues++;
+              }
+            }
+            double[] sparseValues = new double[numSparseValues];
+            int[] sparseIndices = new int[numSparseValues];
+            int count = 0;
+            for (int j = 0; j < histClassCounts[i].length; j++) {
+              if (histClassCounts[i][j] > 0) {
+                sparseValues[count] = histClassCounts[i][j];
+                sparseIndices[count++] = j;
+              }
+            }
+            
+            SparseInstance tempS = 
+              new SparseInstance(1.0, sparseValues, sparseIndices, 
+                  histClassCounts[i].length);
+            histClassCountsSparse[i] = tempS;
+            
+          }
+          
+          m_histBarClassCounts = histClassCountsSparse;
+          m_barRange =  barRange;
+          
+        }
+        else { //else if the class attribute is numeric or the class is not set
+          
+          int intervals; double intervalWidth;
+          //At the time of this coding the
+          //possibility of datasets with zero instances
+          //was being dealt with in the
+          //PreProcessPanel of weka Explorer.
+          
+          //old method of calculating number of intervals
+          //intervals =  m_as.totalCount>10 ?
+          //                  (int)(m_as.totalCount*0.1):(int)m_as.totalCount;
+          
+          //This uses the M.P.Wand's method to calculate the histogram's
+          //interval width. See "Data-Based Choice of Histogram Bin Width", in
+          //The American Statistician, Vol. 51, No. 1, Feb., 1997, pp. 59-64.
+          //intervalWidth = Math.pow(6D/(-psi(2, g21())*m_data.numInstances() ),
+          //                          1/3D );
+          
+          //This uses the Scott's method to calculate the histogram's interval
+          //width. See "On optimal and data-based histograms".
+          // See Biometrika, 66, 605-610 OR see the same paper mentioned above.
+          intervalWidth =  3.49 * m_as.numericStats.stdDev *
+                           Math.pow(m_data.numInstances(), -1/3D);
+          //The Math.max is introduced to remove the possibility of
+          //intervals=0 and =NAN that can happen if respectively all the numeric
+          //values are the same or the interval width is evaluated to zero.
+          intervals = Math.max(1,
+          (int)Math.round( (m_as.numericStats.max - m_as.numericStats.min) /
+                           intervalWidth) );
+          
+          //The number 4 below actually represents a padding of 3 pixels on
+          //each side of the histogram, and is also reflected in other parts of 
+          //the code in the shape of numerical constants like "6" here.
+          if(intervals > AttributeVisualizationPanel.this.getWidth()) {
+            intervals = AttributeVisualizationPanel.this.getWidth()-6;
+            if(intervals<1)
+              intervals = 1;
+          }
+          
+          double[] histCounts  = new double[intervals];
+          double barRange   = (m_as.numericStats.max - m_as.numericStats.min) /
+                              (double)histCounts.length;
+          
+          m_maxValue = 0;
+          
+          for(int k=0; k<m_data.numInstances(); k++) {
+            int t=0; //This holds the interval to which the current attribute's
+                    //value of this particular instance k belongs to.
+            
+            if(m_data.instance(k).isMissing(m_attribIndex)) 
+              continue; //ignore missing values
+            
+            try {
+              //1. see footnote at the end of this file
+              t =(int) Math.ceil((
+              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
+              / barRange));
+              if(t==0) {
+                histCounts[t] += m_data.instance(k).weight();
+                if(histCounts[t]>m_maxValue)
+                  m_maxValue = histCounts[t];
+              }
+              else {
+                histCounts[t-1] += m_data.instance(k).weight();
+                if(histCounts[t-1]>m_maxValue)
+                  m_maxValue = histCounts[t-1];
+              }
+            }
+            catch(ArrayIndexOutOfBoundsException ae) {
+              ae.printStackTrace();
+              System.out.println("t:"+(t)+
+              " barRange:"+barRange+
+              " histLength:"+histCounts.length+
+              " value:"+m_data.instance(k).value(m_attribIndex)+
+              " min:"+m_as.numericStats.min+
+              " sumResult:"+
+              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)+
+              " divideResult:"+
+              (float)((m_data.instance(k).value(m_attribIndex) -
+              m_as.numericStats.min)/barRange)+
+              " finalResult:"+
+              Math.ceil( (float)((m_data.instance(k).value(m_attribIndex) -
+              m_as.numericStats.min) / barRange)) );
+            }
+          }
+          m_histBarCounts = histCounts;
+          m_barRange =  barRange;
+        }
+        
+        m_threadRun=false;
+        m_displayCurrentAttribute = true;
+        m_doneCurrentAttribute = true;
+        //Image tmpImg = new BufferedImage(getWidth(), getHeight(),
+        //                                 BufferedImage.TYPE_INT_RGB);
+        //drawGraph( tmpImg.getGraphics() );
+        //img = tmpImg;
+        AttributeVisualizationPanel.this.repaint();
+      }
+    }
+    
+    /****Code for M.P.Wand's method of histogram bin width selection.
+     *   There is some problem with it. It always comes up -ve value
+     *   which is raised to the power 1/3 and gives an NAN.
+     * private static final int M=400;
+     * private double psi(int r, double g) {
+     * double val;
+     *
+     * double sum=0.0;
+     * for(int i=0; i<M; i++) {
+     * double valCjKj=0.0;
+     * for(int j=0; j<M; j++) {
+     * valCjKj += c(j) * k(r, j-i, g);
+     * }
+     * sum += valCjKj*c(i);
+     * }
+     *
+     * val = Math.pow(m_data.numInstances(), -2) * sum;
+     * //System.out.println("psi returns: "+val);
+     * return val;
+     * }
+     * private double g21() {
+     * double val;
+     *
+     * val = Math.pow(2 / ( Math.sqrt(2D*Math.PI)*psi(4, g22()) * 
+     *                      m_data.numInstances() ), 1/5D)
+     *       * Math.sqrt(2) * m_as.numericStats.stdDev;
+     * //System.out.println("g21 returns: "+val);
+     * return val;
+     * }
+     * private double g22() {
+     * double val;
+     *
+     * val = Math.pow( 2D/(5*m_data.numInstances()), 1/7D) * 
+     *       Math.sqrt(2) * m_as.numericStats.stdDev;
+     * //System.out.println("g22 returns: "+val);
+     * return val;
+     * }
+     * private double c(int j) {
+     * double val=0.0;
+     * double sigma = (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
+     *
+     * //System.out.println("In c before doing the sum we have");
+     * //System.out.println("max: " +m_as.numericStats.max+" min: "+
+     * //                   m_as.numericStats.min+" sigma: "+sigma);
+     *
+     * for(int i=0; i<m_data.numInstances(); i++) {
+     * if(!m_data.instance(i).isMissing(m_attribIndex))
+     * val += Math.max( 0,
+     * ( 1 - Math.abs( Math.pow(sigma, -1)*(m_data.instance(i).value(m_attribIndex) - j) ) )
+     * );
+     * }
+     * //System.out.println("c returns: "+val);
+     * return val;
+     * }
+     * private double k(int r, int j, double g) {
+     * double val;
+     * double sigma = (m_as.numericStats.max - m_as.numericStats.min)/(M-1);
+     * //System.out.println("Before calling L we have");
+     * //System.out.println("Max: "+m_as.numericStats.max+" Min: "+m_as.numericStats.min+"\n"+
+     * //			 "r: "+r+" j: "+j+" g: "+g);
+     * val = Math.pow( g, -r-1) * L(sigma*j/g);
+     * //System.out.println("k returns: "+val);
+     * return val;
+     * }
+     * private double L(double x) {
+     * double val;
+     *
+     * val = Math.pow( 2*Math.PI, -1/2D ) * Math.exp( -(x*x)/2D );
+     * //System.out.println("L returns: "+val);
+     * return val;
+     * }
+     *******End of Wand's method
+     */
+  }
+  
+  
+  /**
+   * Returns "&lt;nominal value&gt; [&lt;nominal value count&gt;]"
+   * if displaying a bar plot and mouse is on some bar.
+   * If displaying histogram then it
+   *     <li>returns "count &lt;br&gt; [&lt;bars Range&gt;]" if mouse is
+   *     on the first bar. </li>
+   *     <li>returns "count &lt;br&gt; (&lt;bar's Range&gt;]" if mouse is
+   *     on some bar other than the first one. </li>
+   * Otherwise it returns ""
+   *
+   * @param ev The mouse event
+   */
+  public String getToolTipText(MouseEvent ev) {
+    
+    if(m_as!=null && m_as.nominalWeights!=null) { //if current attrib is nominal
+      
+      float intervalWidth = this.getWidth() / (float)m_as.nominalWeights.length;
+      double heightRatio;      
+      int barWidth, x=0, y=0;
+      
+      //if intervalWidth is at least six then bar width is 80% of intervalwidth
+      if(intervalWidth>5) //the rest is padding
+        barWidth = (int)Math.floor(intervalWidth*0.8F);
+      else
+        barWidth = 1;  //Otherwise barwidth is 1 & padding would be at least 1.
+      
+      //initializing x to maximum of 1 or 10% of interval width (i.e. half of 
+      //the padding which is 20% of interval width, as there is 10% on each 
+      //side of the bar) so that it points to the start of the 1st bar
+      x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 
+                     1:(Math.floor(intervalWidth*0.1F)) );
+
+      //Adding to x the appropriate value so that it points to the 1st bar of 
+      //our "centered" barplot. If subtracting barplots width from panel width 
+      //gives <=2 then the barplot is not centered.
+      if(this.getWidth() - (m_as.nominalWeights.length*barWidth+
+                           (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                                   1:(Math.floor(intervalWidth*0.2F)) ) * 
+                           m_as.nominalWeights.length) > 2 ) {
+        
+        //The following amounts to adding to x the half of the area left after
+        //subtracting from the components width the width of the whole barplot
+        //(i.e. width of all the bars plus the width of all the bar paddings, 
+        //thereby equaling to the whole barplot), since our barplot is centered.
+        x += ( this.getWidth() - (m_as.nominalWeights.length*barWidth + 
+                                 (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                                        1:(Math.floor(intervalWidth*0.2F)) ) * 
+                                 m_as.nominalWeights.length) ) / 2;
+      }
+        
+      for(int i=0; i<m_as.nominalWeights.length; i++) {        
+        heightRatio = (this.getHeight()-(double)m_fm.getHeight())/m_maxValue;
+        //initializing y to point to (from top) the start of the bar
+        y = (int) (this.getHeight()-Math.round(m_as.nominalWeights[i]*heightRatio));
+        
+        //if our mouse is on a bar then return the count of this bar in our
+        //barplot 
+        if(ev.getX() >= x && ev.getX()<=x+barWidth && 
+           ev.getY() >= this.getHeight() - 
+                        Math.round(m_as.nominalWeights[i]*heightRatio) )
+          return(m_data.attribute(m_attribIndex).value(i)+
+                 " ["+Utils.doubleToString(m_as.nominalWeights[i], 3)+"]");
+        //otherwise advance x to next bar and check that. Add barwidth to x 
+        //and padding which is max(1, 20% of interval width)
+        x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                              1:(Math.floor(intervalWidth*0.2F)) );
+      }
+    }
+    else if(m_threadRun==false &&     //if attrib is numeric
+            (m_histBarCounts!=null || m_histBarClassCounts!=null)) {
+
+      double heightRatio, intervalWidth;
+      int x=0, y=0,  barWidth;
+      double bar = m_as.numericStats.min;
+      
+      //if the class attribute is set and it is nominal 
+      if((m_classIndex >= 0) && (m_data.attribute(m_classIndex).isNominal())) {
+        //there is 3 pixels of padding on each side of the histogram
+        //the barwidth is 1 if after removing the padding its width is less
+        //then the displayable width
+        barWidth = ((this.getWidth()-6)/m_histBarClassCounts.length)<1 ? 
+                   1:((this.getWidth()-6)/m_histBarClassCounts.length);
+        
+        //initializing x to 3 adding appropriate value to make it point to the 
+        //start of the 1st bar of our "centered" histogram.
+        x = 3;
+        if( (this.getWidth() - (x + m_histBarClassCounts.length*barWidth)) > 5 )
+          x += (this.getWidth() - (x + m_histBarClassCounts.length*barWidth))/2;
+        
+        heightRatio = (this.getHeight()-(double)m_fm.getHeight())/m_maxValue;
+        
+        if( ev.getX()-x >= 0) {
+          //The temp holds the index of the current interval that we are looking
+          //at
+          int temp = (int)((ev.getX()-x)/(barWidth+0.0000000001));
+          if(temp == 0){  //handle the special case temp==0. see footnote 1
+            double sum=0;
+            for(int k=0; k<m_histBarClassCounts[0].numValues(); k++)
+              sum += m_histBarClassCounts[0].valueSparse(k);
+            //return the count of the interval mouse is pointing to plus 
+            //the range of values that fall into this interval
+            return ("<html><center><font face=Dialog size=-1>" + 
+                Utils.doubleToString(sum, 3) + "<br>"+
+                    "["+Utils.doubleToString(bar+m_barRange*temp,3)+
+                    ", "+Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
+                    "]"+"</font></center></html>");
+          }
+          else if( temp < m_histBarClassCounts.length ) { //handle case temp!=0
+            double sum=0;
+            for(int k=0; k<m_histBarClassCounts[temp].numValues(); k++)
+              sum+=m_histBarClassCounts[temp].valueSparse(k);
+            //return the count of the interval mouse is pointing to plus 
+            //the range of values that fall into this interval
+            return ("<html><center><font face=Dialog size=-1>" + 
+                Utils.doubleToString(sum, 3) + "<br>("+
+                    Utils.doubleToString(bar+m_barRange*temp,3)+", "+
+                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
+                    "]</font></center></html>");
+          }
+        }
+      }
+      else {  //else if the class attribute is not set or is numeric
+        barWidth = ((this.getWidth()-6)/m_histBarCounts.length) < 1 ? 
+                   1 : ((this.getWidth()-6)/m_histBarCounts.length);
+        
+        //initializing x to 3 adding appropriate value to make it point to the 
+        //start of the 1st bar of our "centered" histogram.
+        x = 3;
+        if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
+          x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
+        
+        heightRatio = (this.getHeight()-(float)m_fm.getHeight())/m_maxValue;
+        
+        if( ev.getX()-x >= 0) {
+          //Temp holds the index of the current bar we are looking at.
+          int temp = (int)((ev.getX()-x)/(barWidth+0.0000000001));
+          
+          //return interval count as well as its range
+          if(temp == 0) //handle special case temp==0. see footnote 1.
+            return ("<html><center><font face=Dialog size=-1>"+
+                    m_histBarCounts[0]+"<br>"+
+                    "["+Utils.doubleToString(bar+m_barRange*temp,3)+", "+
+                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
+                    "]"+
+                    "</font></center></html>");
+          else if(temp < m_histBarCounts.length) //handle case temp!=0
+            return ("<html><center><font face=Dialog size=-1>"+
+                    m_histBarCounts[temp]+"<br>"+
+                    "("+Utils.doubleToString(bar+m_barRange*temp,3)+", "+
+                    Utils.doubleToString((bar+m_barRange*(temp+1)),3)+
+                    "]"+
+                    "</font></center></html>");
+        }
+      }
+    }
+    return PrintableComponent.getToolTipText(m_Printer);
+  }
+  
+  
+  /**
+   * Paints this component
+   *
+   * @param g The graphics object for this component
+   */
+  public void paintComponent(Graphics g) {
+    g.clearRect(0,0,this.getWidth(), this.getHeight());
+    
+    if(m_as!=null) {    //If calculations have been done and histogram/barplot
+      if (!m_doneCurrentAttribute && !m_threadRun) {
+        calcGraph(this.getWidth(), this.getHeight());
+      }
+      if(m_threadRun==false && m_displayCurrentAttribute) {  //calculation thread is not running
+        int buttonHeight=0;
+        
+        if(m_colorAttrib!=null)
+          buttonHeight =m_colorAttrib.getHeight()+m_colorAttrib.getLocation().y;
+        
+        //if current attribute is nominal then draw barplot.
+        if(m_as.nominalWeights != null && 
+           (m_histBarClassCounts!=null || m_histBarCounts!=null) ) {
+          double heightRatio, intervalWidth;
+          int x=0, y=0, barHeight, barWidth;
+          
+          //if the class attribute is set and is nominal then draw coloured 
+          //subbars for each bar
+          if((m_classIndex >= 0) && 
+             (m_data.attribute(m_classIndex).isNominal())) {
+               
+            intervalWidth=(this.getWidth()/(float)m_histBarClassCounts.length);
+            
+            //Barwidth is 80% of interval width.The remaining 20% is padding,
+            //10% on each side of the bar. If interval width is less then 5 the
+            //20% of that value is less than 1, in that case we use bar width of
+            //1 and padding of 1 pixel on each side of the bar.
+            if(intervalWidth>5)
+              barWidth = (int)Math.floor(intervalWidth*0.8F);
+            else
+              barWidth = 1;
+
+            //initializing x to 10% of interval width or to 1 if 10% is <1. This
+            //is essentially the LHS padding of the 1st bar.
+            x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ?
+                           1 : (Math.floor(intervalWidth*0.1F)) );
+            
+            //Add appropriate value to x so that it starts at the 1st bar of 
+            //a "centered" barplot.
+            if(this.getWidth() - (m_histBarClassCounts.length*barWidth + 
+                                 (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                                        1 :(Math.floor(intervalWidth*0.2F))
+                                      ) * m_histBarClassCounts.length) > 2 ) {
+              //We take the width of all the bars and all the paddings (20%
+              //of interval width), and subtract it from the width of the panel
+              //to get the extra space that would be left after drawing. We 
+              //divide that space by 2 to get its mid-point and add that to our
+              //x, thus making the whole bar plot drawn centered in our 
+              //component.
+              x += (this.getWidth()-(m_histBarClassCounts.length*barWidth+
+                                    (int)( (Math.floor(intervalWidth*0.2F))<1 ?
+                                           1 : (Math.floor(intervalWidth*0.2F))
+                                         ) * m_histBarClassCounts.length))/2;
+            }
+            
+            //this holds the count of the bar and will be calculated by adding
+            //up the counts of individual subbars. It is displayed at the top
+            //of each bar.
+            double sum=0;
+            for(int i=0; i<m_histBarClassCounts.length; i++) {
+
+              //calculating the proportion of the components height compared to 
+              //the maxvalue in our attribute, also taking into account the 
+              //height of font to display bars count and the height of the class 
+              //ComboBox.
+              heightRatio = ( this.getHeight()-(double)m_fm.getHeight() - 
+                  buttonHeight ) / m_maxValue;              
+              y=this.getHeight();
+              if (m_histBarClassCounts[i] != null) {
+                for(int j=0; j<m_histBarClassCounts[i].numAttributes(); j++) {
+                  sum = sum + m_histBarClassCounts[i].value(j);
+                  y = (int) (y-Math.round(m_histBarClassCounts[i].value(j) * heightRatio));
+                  //selecting the colour corresponding to the current class.
+                  g.setColor( (Color)m_colorList.elementAt(j) );
+                  g.fillRect(x, y, barWidth, 
+                      (int) Math.round(m_histBarClassCounts[i].value(j) * heightRatio));
+                  g.setColor(Color.black);
+                }
+              }
+              //drawing the bar count at the top of the bar if it is less than
+              //interval width. draw it 1px up to avoid touching the bar.
+              if(m_fm.stringWidth(Utils.doubleToString(sum, 1))<intervalWidth)
+                g.drawString(Utils.doubleToString(sum, 1), x, y-1);
+              //advancing x to the next bar by adding bar width and padding
+              //of both the bars (i.e. RHS padding of the bar just drawn and LHS
+              //padding of the new bar).
+              x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                  1:(Math.floor(intervalWidth*0.2F)) );
+              //reseting sum for the next bar.
+              sum=0;
+
+            }
+          }
+          //else if class attribute is numeric or not set then draw black bars.
+          else {
+            intervalWidth =  (this.getWidth()/(float)m_histBarCounts.length);
+            
+            //same as in the case of nominal class (see inside of if stmt 
+            //corresponding to the current else above).
+            if(intervalWidth>5)
+              barWidth = (int)Math.floor(intervalWidth*0.8F);
+            else
+              barWidth = 1;
+            
+            //same as in the case of nominal class (see inside of if stmt 
+            //corresponding to the current else above).
+            x = x + (int)( (Math.floor(intervalWidth*0.1F))<1 ? 
+                           1:(Math.floor(intervalWidth*0.1F)) );
+            
+            //same as in the case of nominal class
+            if( this.getWidth() - (m_histBarCounts.length*barWidth+
+                                  (int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                                         1:(Math.floor(intervalWidth*0.2F)) ) * 
+                                  m_histBarCounts.length) > 2 ) {
+              x += (this.getWidth() -(m_histBarCounts.length*barWidth + 
+                                     (int)((Math.floor(intervalWidth*0.2F))<1 ? 
+                                           1:(Math.floor(intervalWidth*0.2F)))*
+                                     m_histBarCounts.length))/2;
+            }
+            
+            for(int i=0; i<m_histBarCounts.length; i++) {
+              //calculating the proportion of the height of the component 
+              //compared to the maxValue in our attribute.
+              heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
+                             buttonHeight) / m_maxValue;
+              y = (int) (this.getHeight()-Math.round(m_histBarCounts[i]*heightRatio));
+              g.fillRect(x, y, barWidth, 
+                         (int) Math.round(m_histBarCounts[i]*heightRatio));
+              //draw the bar count if it's width is smaller than intervalWidth.
+              //draw it 1px above to avoid touching the bar.
+              if(m_fm.stringWidth(Utils.doubleToString(m_histBarCounts[i], 1)) < 
+                                    intervalWidth)
+                g.drawString(Utils.doubleToString(m_histBarCounts[i], 1), x, y-1);
+              //Advance x to the next bar by adding bar-width and padding
+              //of the bars (RHS padding of current bar & LHS padding of next 
+              //bar).
+              x = x+barWidth+(int)( (Math.floor(intervalWidth*0.2F))<1 ? 
+                                     1:(Math.floor(intervalWidth*0.2F)) );
+            }
+          }
+          
+        } //<--end if m_as.nominalCount!=null
+        //if the current attribute is numeric then draw a histogram.
+        else if(m_as.numericStats != null && 
+                (m_histBarClassCounts!=null || m_histBarCounts!=null)) {
+
+          double heightRatio;
+          float intervalWidth;
+          int x=0, y=0,  barWidth;
+          
+          //If the class attribute is set and is not numeric then draw coloured 
+          //subbars for the histogram bars
+          if((m_classIndex >=0) && 
+             (m_data.attribute(m_classIndex).isNominal())) {
+            
+            //There is a padding of 3px on each side of the histogram.
+            barWidth = ((this.getWidth()-6)/m_histBarClassCounts.length)<1 ? 
+                       1 : ((this.getWidth()-6)/m_histBarClassCounts.length);
+            
+            //initializing x to start at the start of the 1st bar after padding.
+            x = 3;
+            //Adding appropriate value to x to account for a "centered" 
+            //histogram
+            if( (this.getWidth() - 
+                (x + m_histBarClassCounts.length*barWidth)) > 5 ) {
+              //We take the current value of x (histogram's RHS padding) and add
+              //the barWidths of all the bars to it to us the size of 
+              //our histogram. We subtract that from the width of the panel 
+              //giving us the extra space that would be left if the histogram is
+              //drawn and divide that by 2 to get the midpoint of that extra
+              //space. That space is then added to our x, hence making the 
+              //histogram centered.
+              x += ( this.getWidth() - 
+                    (x + m_histBarClassCounts.length*barWidth) ) / 2;
+            }
+            
+            for(int i=0; i<m_histBarClassCounts.length; i++) {
+              if (m_histBarClassCounts[i] != null) {
+                //Calculating height ratio. Leave space of 19 for an axis line at 
+                //the bottom
+                heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
+                    buttonHeight-19) / m_maxValue;
+                y = this.getHeight()-19;
+                //This would hold the count of the bar (sum of sub-bars).
+                double sum = 0;
+                for(int j=0; j<m_histBarClassCounts[i].numValues(); j++) {
+                  y = (int) (y-Math.round(m_histBarClassCounts[i].valueSparse(j) * heightRatio));
+                  //System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
+                  //                   " height:"+
+                  //                   (m_histBarClassCounts[i][j]*heightRatio));
+                  //selecting the color corresponding to our class
+                  g.setColor( (Color)m_colorList.elementAt(m_histBarClassCounts[i].index(j)) );
+                  //drawing the bar if its width is greater than 1
+                  if(barWidth>1)
+                    g.fillRect(x, y, 
+                        barWidth, 
+                        (int) Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio));
+                  //otherwise drawing a line
+                  else if((m_histBarClassCounts[i].valueSparse(j) * heightRatio)>0)
+                    g.drawLine(x, y, x, 
+                        (int) (y+Math.round(m_histBarClassCounts[i].valueSparse(j)*heightRatio)));
+                  g.setColor(Color.black);
+                  sum = sum + m_histBarClassCounts[i].valueSparse(j);
+                }
+                //Drawing bar count on the top of the bar if it is < barWidth
+                if(m_fm.stringWidth(" "+Utils.doubleToString(sum, 1))<barWidth)
+                  g.drawString(" "+Utils.doubleToString(sum, 1), x, y-1);
+                //Moving x to the next bar
+                x = x+barWidth;
+              }
+            }
+            
+            //Now drawing the axis line at the bottom of the histogram
+            //initializing x again to the start of the plot
+            x = 3;
+            if( (this.getWidth() - 
+                (x + m_histBarClassCounts.length*barWidth)) > 5 )
+              x += (this.getWidth() - 
+                   (x + m_histBarClassCounts.length*barWidth))/2;
+            
+            g.drawLine(x, this.getHeight()-17,
+                       (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 : 
+                                     x+barWidth*m_histBarClassCounts.length,
+                       this.getHeight()-17); //axis line -- see footnote 2.
+            g.drawLine(x, this.getHeight()-16, 
+                       x, this.getHeight()-12); //minimum line
+            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2),
+                         x,
+                         this.getHeight()-12+m_fm.getHeight()); //minimum value
+            g.drawLine(x+(barWidth*m_histBarClassCounts.length)/2,
+                       this.getHeight()-16,
+                       x+(barWidth*m_histBarClassCounts.length)/2,
+                       this.getHeight()-12); //median line
+            //Drawing median value. X position for drawing the value is: from 
+            //start of the plot take the mid point and subtract from it half
+            //of the width of the value to draw.
+            g.drawString(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2),
+                         x+(barWidth*m_histBarClassCounts.length)/2 - 
+                           m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2))/2,
+                         this.getHeight()-12+m_fm.getHeight()); //median value
+            g.drawLine((barWidth==1) ? x+barWidth*m_histBarClassCounts.length-1:
+                                       x+barWidth*m_histBarClassCounts.length,
+                       this.getHeight()-16,
+                       (barWidth==1) ? x+barWidth*m_histBarClassCounts.length-1:
+                                       x+barWidth*m_histBarClassCounts.length,
+                       this.getHeight()-12); //maximum line
+            g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
+                         (barWidth==1) ?
+              x+barWidth*m_histBarClassCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2))-1:
+              x+barWidth*m_histBarClassCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
+              this.getHeight()-12+m_fm.getHeight()); //maximum value -- see 2.
+          }
+          else {  //if class attribute is numeric
+            //There is a padding of 3px on each side of the histogram.
+            barWidth = ((this.getWidth()-6)/m_histBarCounts.length) < 1 ? 
+                        1:((this.getWidth()-6)/m_histBarCounts.length);
+
+            //Same as above. Pls inside of the if stmt.
+            x = 3;
+            if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
+              x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
+            
+            //Same as above
+            for(int i=0; i<m_histBarCounts.length; i++) {
+              //calculating the ration of the component's height compared to 
+              //the maxValue in our current attribute. Leaving 19 pixels to
+              //draw the axis at the bottom of the histogram.
+              heightRatio = (this.getHeight()-(float)m_fm.getHeight() - 
+                             buttonHeight-19) / m_maxValue;
+              y = (int) (this.getHeight() - 
+                  Math.round(m_histBarCounts[i]*heightRatio)-19);
+              //System.out.println("Filling x:"+x+" y:"+y+" width:"+barWidth+
+              //                   " height:"+(m_histBarCounts[i]*heightRatio));
+              //same as in the if stmt above
+              if(barWidth>1)
+                g.drawRect(x, y, barWidth, 
+                           (int) Math.round(m_histBarCounts[i]*heightRatio));
+              else if((m_histBarCounts[i]*heightRatio)>0)
+                g.drawLine(x, y, 
+                           x, (int) (y+Math.round(m_histBarCounts[i]*heightRatio)));
+              if(m_fm.stringWidth(" "+Utils.doubleToString(m_histBarCounts[i], 1)) < 
+                    barWidth)
+                g.drawString(" "+Utils.doubleToString(m_histBarCounts[i], 1), x, y-1);
+              
+              x = x+barWidth;
+            }
+            
+            //Now drawing the axis at the bottom of the histogram
+            x = 3;
+            if( (this.getWidth() - (x + m_histBarCounts.length*barWidth)) > 5 )
+              x += (this.getWidth() - (x + m_histBarCounts.length*barWidth))/2;
+            
+            //This is exact the same as in the if stmt above. See the inside of
+            //the stmt for details
+            g.drawLine(x, this.getHeight()-17,
+                       (barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
+                                       x+barWidth*m_histBarCounts.length,
+                       this.getHeight()-17); //axis line
+            g.drawLine(x, this.getHeight()-16, 
+                       x, this.getHeight()-12); //minimum line
+            g.drawString(Utils.doubleToString(m_as.numericStats.min, 2),
+                         x,
+                         this.getHeight()-12+m_fm.getHeight()); //minimum value
+            g.drawLine(x+(barWidth*m_histBarCounts.length)/2,
+                       this.getHeight()-16,
+                       x+(barWidth*m_histBarCounts.length)/2,
+                       this.getHeight()-12); //median line
+            g.drawString(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2),
+                         x+(barWidth*m_histBarCounts.length)/2 - 
+                           m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max/2+m_as.numericStats.min/2, 2))/2,
+                         this.getHeight()-12+m_fm.getHeight()); //median value
+            g.drawLine((barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
+                                        x+barWidth*m_histBarCounts.length,
+                       this.getHeight()-16,
+                       (barWidth==1) ? x+barWidth*m_histBarCounts.length-1 : 
+                                       x+barWidth*m_histBarCounts.length,
+                       this.getHeight()-12); //maximum line
+            g.drawString(Utils.doubleToString(m_as.numericStats.max, 2),
+                         (barWidth==1) ? 
+              x+barWidth*m_histBarCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2))-1 : 
+              x+barWidth*m_histBarCounts.length-m_fm.stringWidth(Utils.doubleToString(m_as.numericStats.max, 2)),
+              this.getHeight()-12+m_fm.getHeight()); //maximum value
+          }
+          //System.out.println("barWidth:"+barWidth+
+          //                   " histBarCount:"+m_histBarCounts.length);
+          
+        } else {
+          g.clearRect(0, 0, this.getWidth(), this.getHeight());
+          g.drawString("Attribute is neither numeric nor nominal.",
+          this.getWidth()/2 - m_fm.
+          stringWidth("Attribute is neither numeric nor nominal.")/2,
+          this.getHeight()/2-m_fm.getHeight()/2);
+        }
+      } //<--end if of calculation thread
+      else if (m_displayCurrentAttribute) {   //if still calculation thread is running plot
+        g.clearRect(0, 0, this.getWidth(), this.getHeight());
+        g.drawString("Calculating. Please Wait...",
+        this.getWidth()/2 - m_fm.stringWidth("Calculating. Please Wait...")/2,
+        this.getHeight()/2-m_fm.getHeight()/2);
+      } else if (!m_displayCurrentAttribute) {
+        g.clearRect(0, 0, this.getWidth(), this.getHeight());
+        g.drawString("Too many values to display.",
+        this.getWidth()/2 - m_fm.stringWidth("Too many values to display.")/2,
+        this.getHeight()/2-m_fm.getHeight()/2);
+      }
+    } //<--end if(m_as==null) this means 
+  }
+  
+  
+  /**
+   * Main method to test this class from command line
+   *
+   * @param args The arff file and the index of the attribute to use
+   */
+  public static void main(String [] args) {
+    if(args.length!=3) {
+      final JFrame jf = new JFrame("AttribVisualization");
+      AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
+      try {
+        Instances ins = new Instances( new FileReader(args[0]) );
+        ap.setInstances(ins);
+        System.out.println("Loaded: "+args[0]+
+                           "\nRelation: "+ap.m_data.relationName()+
+                           "\nAttributes: "+ap.m_data.numAttributes());
+        ap.setAttribute( Integer.parseInt(args[1]) );
+      }
+      catch(Exception ex) { ex.printStackTrace(); System.exit(-1); }
+      System.out.println("The attributes are: ");
+      for(int i=0; i<ap.m_data.numAttributes(); i++)
+        System.out.println(ap.m_data.attribute(i).name());
+      
+      jf.setSize(500, 300);
+      jf.getContentPane().setLayout( new BorderLayout() );
+      jf.getContentPane().add(ap, BorderLayout.CENTER );
+      jf.setDefaultCloseOperation( jf.EXIT_ON_CLOSE );
+      jf.setVisible(true);
+    }
+    else
+      System.out.println("Usage: java AttributeVisualizationPanel"+
+                         " [arff file] [index of attribute]");
+  }
+}
+
+
+/*
+ * t =(int) Math.ceil((float)(
+ *              (m_data.instance(k).value(m_attribIndex)-m_as.numericStats.min)
+ *                           / barRange));
+ * 1. 
+ * This equation gives a value between (i-1)+smallfraction and i if the 
+ * attribute m_attribIndex for the current instances lies in the ith
+ * interval. We then increment the value of our i-1th field of our 
+ * histogram/barplot array. 
+ * If, for example, barRange=3 then, apart from the 1st 
+ * interval, each interval i has values in the range 
+ * (minValue+3*i-1, minValue+3*i]. The 1st interval has range 
+ * [minValue, minValue+i]. Hence it can be seen in the code we specifically 
+ * handle t=0 separately.
+ *
+ */
+
+
+/**
+ * (barWidth==1)?x+barWidth*m_histBarClassCounts.length-1 : 
+ *                                    x+barWidth*m_histBarClassCounts.length
+ * 2. 
+ * In the case barWidth==1 we subtract 1 otherwise the line become one pixel
+ * longer than the actual size of the histogram
+ */
Index: branches/MetisMQI/src/main/java/weka/gui/BrowserHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/BrowserHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/BrowserHelper.java	(revision 29)
@@ -0,0 +1,151 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BrowserHelper.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.lang.reflect.Method;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+
+
+/**
+ * A little helper class for browser related stuff. <p/>
+ * 
+ * The <code>openURL</code> method is based on 
+ * <a href="http://www.centerkey.com/java/browser/" target="_blank">Bare Bones Browser Launch</a>,
+ * which is placed in the public domain.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5054 $
+ */
+public class BrowserHelper {
+
+  /** Linux/Unix binaries to look for */
+  public final static String[] LINUX_BROWSERS = 
+    {"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape"};
+  
+  /**
+   * opens the URL in a browser.
+   * 
+   * @param url		the URL to open
+   */
+  public static void openURL(String url) {
+    openURL(null, url);
+  }
+
+  /**
+   * opens the URL in a browser.
+   * 
+   * @param parent	the parent component
+   * @param url		the URL to open
+   */
+  public static void openURL(Component parent, String url) {
+    openURL(parent, url, true);
+  }
+
+  /**
+   * opens the URL in a browser.
+   * 
+   * @param parent	the parent component
+   * @param url		the URL to open
+   * @param showDialog	whether to display a dialog in case of an error or
+   * 			just print the error to the console
+   */
+  public static void openURL(Component parent, String url, boolean showDialog) {
+    String osName = System.getProperty("os.name"); 
+    try { 
+      // Mac OS
+      if (osName.startsWith("Mac OS")) { 
+	Class fileMgr = Class.forName("com.apple.eio.FileManager"); 
+	Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class}); 
+	openURL.invoke(null, new Object[] {url}); 
+      } 
+      // Windows
+      else if (osName.startsWith("Windows")) {
+	Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); 
+      }
+      // assume Unix or Linux
+      else {  
+	String browser = null; 
+	for (int count = 0; count < LINUX_BROWSERS.length && browser == null; count++) {
+	  // look for binaries and take first that's available
+	  if (Runtime.getRuntime().exec(new String[] {"which", LINUX_BROWSERS[count]}).waitFor() == 0) {
+	    browser = LINUX_BROWSERS[count];
+	    break;
+	  }
+	}
+	if (browser == null) 
+	  throw new Exception("Could not find web browser");
+	else
+	  Runtime.getRuntime().exec(new String[] {browser, url});
+      }
+    }
+    catch (Exception e) {
+      String errMsg = "Error attempting to launch web browser:\n" + e.getMessage();
+      
+      if (showDialog)
+	JOptionPane.showMessageDialog(
+	    parent, errMsg);
+      else
+	System.err.println(errMsg);
+    }
+  } 
+  
+  /**
+   * Generates a label with a clickable link.
+   * 
+   * @param url		the url of the link
+   * @param text	the text to display instead of URL. if null or of 
+   * 			length 0 then the URL is used
+   * @return		the generated label
+   */
+  public static JLabel createLink(String url, String text) {
+    final String urlF = url;
+    final JLabel result = new JLabel();
+    result.setText((text == null) || (text.length() == 0) ? url : text);
+    result.setToolTipText("Click to open link in browser");
+    result.setForeground(Color.BLUE);
+    result.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	if (e.getButton() == MouseEvent.BUTTON1) {
+	  BrowserHelper.openURL(urlF);
+	}
+	else {
+	  super.mouseClicked(e);
+	}
+      }
+      public void mouseEntered(MouseEvent e) {
+	result.setForeground(Color.RED);
+      }
+      public void mouseExited(MouseEvent e) {
+	result.setForeground(Color.BLUE);
+      }
+    });
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/CheckBoxList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/CheckBoxList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/CheckBoxList.java	(revision 29)
@@ -0,0 +1,614 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * CheckBoxList.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import java.awt.Component;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Vector;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JCheckBox;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+
+/**
+ * An extended JList that contains CheckBoxes. If necessary a CheckBoxListItem
+ * wrapper is added around the displayed object in any of the Model methods, 
+ * e.g., addElement. For methods returning objects the opposite takes place, 
+ * the wrapper is removed and only the payload object is returned.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class CheckBoxList
+  extends JList {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4359573373359270258L;
+  
+  /**
+   * represents an item in the CheckBoxListModel
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 1.3 $
+   */
+  protected class CheckBoxListItem {
+    
+    /** whether item is checked or not */
+    private boolean m_Checked = false;
+    
+    /** the actual object */
+    private Object m_Content = null;
+    
+    /**
+     * initializes the item with the given object and initially unchecked
+     * 
+     * @param o		the content object
+     */
+    public CheckBoxListItem(Object o) {
+      this(o, false);
+    }
+    
+    /**
+     * initializes the item with the given object and whether it's checked
+     * initially
+     * 
+     * @param o		the content object
+     * @param checked	whether the item should be checked initially
+     */
+    public CheckBoxListItem(Object o, boolean checked) {
+      m_Checked = checked;
+      m_Content = o;
+    }
+    
+    /**
+     * returns the content object
+     */
+    public Object getContent() {
+      return m_Content;
+    }
+    
+    /**
+     * sets the checked state of the item
+     */
+    public void setChecked(boolean value) {
+      m_Checked = value;
+    }
+    
+    /**
+     * returns the checked state of the item
+     */
+    public boolean getChecked() {
+      return m_Checked;
+    }
+    
+    /**
+     * returns the string representation of the content object
+     */
+    public String toString() {
+      return m_Content.toString();
+    }
+    
+    /**
+     * returns true if the "payload" objects of the current and the given
+     * CheckBoxListItem are the same.
+     * 
+     * @param o		the CheckBoxListItem to check
+     * @throws IllegalArgumentException if the provided object is not a CheckBoxListItem
+     */
+    public boolean equals(Object o) {
+      if (!(o instanceof CheckBoxListItem))
+	throw new IllegalArgumentException("Must be a CheckBoxListItem!");
+      
+      return getContent().equals(((CheckBoxListItem) o).getContent());
+    }
+  }
+  
+  /**
+   * A specialized model.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 1.3 $
+   */
+  public class CheckBoxListModel
+    extends DefaultListModel {
+
+    /** for serialization */
+    private static final long serialVersionUID = 7772455499540273507L;
+    
+    /**
+     * initializes the model with no data.
+     */
+    public CheckBoxListModel() {
+      super();
+    }
+    
+    /**
+     * Constructs a CheckBoxListModel from an array of objects and then applies 
+     * setModel to it.
+     * 
+     * @param listData	the data to use
+     */
+    public CheckBoxListModel(Object[] listData) {
+      for (int i = 0; i < listData.length; i++)
+        addElement(listData[i]);
+    }
+    
+    /**
+     * Constructs a CheckBoxListModel from a Vector and then applies setModel 
+     * to it.
+     */
+    public CheckBoxListModel(Vector listData) {
+      for (int i = 0; i < listData.size(); i++)
+        addElement(listData.get(i));
+    }
+    
+    /**
+     * Inserts the specified element at the specified position in this list.
+     * 
+     * @param index	index at which the specified element is to be inserted
+     * @param element	element to be inserted
+     */
+    public void add(int index, Object element) {
+      if (!(element instanceof CheckBoxListItem))
+	super.add(index, new CheckBoxListItem(element));
+      else
+	super.add(index, element);
+    }
+    
+    /**
+     * Adds the specified component to the end of this list.
+     * 
+     * @param obj 	the component to be added
+     */
+    public void addElement(Object obj) {
+      if (!(obj instanceof CheckBoxListItem))
+	super.addElement(new CheckBoxListItem(obj));
+      else
+	super.addElement(obj);
+    }
+    
+    /**
+     * Tests whether the specified object is a component in this list.
+     * 
+     * @param elem	the element to check
+     * @return		true if the element is in the list
+     */
+    public boolean contains(Object elem) {
+      if (!(elem instanceof CheckBoxListItem))
+	return super.contains(new CheckBoxListItem(elem));
+      else
+	return super.contains(elem);
+    }
+    
+    /**
+     * Copies the components of this list into the specified array.
+     * 
+     * @param anArray	the array into which the components get copied
+     * @throws IndexOutOfBoundsException if the array is not big enough
+     */
+    public void copyInto(Object[] anArray) {
+      if (anArray.length < getSize())
+	throw new IndexOutOfBoundsException("Array not big enough!");
+      
+      for (int i = 0; i < getSize(); i++)
+	anArray[i] = ((CheckBoxListItem) getElementAt(i)).getContent();
+    }
+    
+    /**
+     * Returns the component at the specified index. Throws an 
+     * ArrayIndexOutOfBoundsException if the index is negative or not less 
+     * than the size of the list.
+     * 
+     * @param index	an index into this list
+     * @return 		the component at the specified index
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public Object elementAt(int index) {
+      return ((CheckBoxListItem) super.elementAt(index)).getContent();
+    }
+    
+    /**
+     * Returns the first component of this list. Throws a 
+     * NoSuchElementException if this vector has no components.
+     * 
+     * @return		the first component of this list
+     * @throws NoSuchElementException
+     */
+    public Object firstElement() {
+      return ((CheckBoxListItem) super.firstElement()).getContent();
+    }
+    
+    /**
+     * Returns the element at the specified position in this list.
+     * 
+     * @param index of element to return
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public Object get(int index) {
+      return ((CheckBoxListItem) super.get(index)).getContent();
+    }
+    
+    /**
+     * Returns the component at the specified index.
+     * 
+     * @param index 	an index into this list
+     * @return 		the component at the specified index 
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public Object getElementAt(int index) {
+      return ((CheckBoxListItem) super.getElementAt(index)).getContent();
+    }
+    
+    /**
+     * Searches for the first occurrence of elem.
+     * 
+     * @param elem	an object
+     * @return 		the index of the first occurrence of the argument in this list; 
+     * 			returns -1 if the object is not found
+     */
+    public int indexOf(Object elem) {
+      if (!(elem instanceof CheckBoxListItem))
+	return super.indexOf(new CheckBoxListItem(elem));
+      else
+	return super.indexOf(elem);
+    }
+    
+    /**
+     * Searches for the first occurrence of elem, beginning the search at index.
+     * 
+     * @param elem 	the desired component
+     * @param index	the index from which to begin searching
+     * @return		the index where the first occurrence of elem  is found after index; 
+     * 			returns -1  if the elem is not found in the list
+     */
+    public int indexOf(Object elem, int index) {
+      if (!(elem instanceof CheckBoxListItem))
+	return super.indexOf(new CheckBoxListItem(elem), index);
+      else
+	return super.indexOf(elem, index);
+    }
+    
+    /**
+     * Inserts the specified object as a component in this list at the 
+     * specified index.
+     * 
+     * @param obj	the component to insert
+     * @param index	where to insert the new component
+     * @throws ArrayIndexOutOfBoundsException 
+     */
+    public void insertElementAt(Object obj, int index) {
+      if (!(obj instanceof CheckBoxListItem))
+	super.insertElementAt(new CheckBoxListItem(obj), index);
+      else
+	super.insertElementAt(obj, index);
+    }
+    
+    /**
+     * Returns the last component of the list. Throws a NoSuchElementException 
+     * if this vector has no components.
+     * 
+     * @return 		the last component of the list
+     * @throws NoSuchElementException
+     */
+    public Object lastElement() {
+      return ((CheckBoxListItem) super.lastElement()).getContent();
+    }
+    
+    /**
+     * Returns the index of the last occurrence of elem.
+     * 
+     * @param elem	the desired component
+     * @return		the index of the last occurrence of elem  in the list; 
+     * 			returns -1 if the object is not found
+     */
+    public int lastIndexOf(Object elem) {
+      if (!(elem instanceof CheckBoxListItem))
+	return super.lastIndexOf(new CheckBoxListItem(elem));
+      else
+	return super.lastIndexOf(elem);
+    }
+    
+    /**
+     * Searches backwards for elem, starting from the specified index, 
+     * and returns an index to it.
+     * 
+     * @param elem	the desired component
+     * @param index	the index to start searching from
+     * @return		the index of the last occurrence of the elem in this 
+     * 			list at position less than index; returns -1 if the 
+     * 			object is not found
+     */
+    public int lastIndexOf(Object elem, int index) {
+      if (!(elem instanceof CheckBoxListItem))
+	return super.lastIndexOf(new CheckBoxListItem(elem), index);
+      else
+	return super.lastIndexOf(elem, index);
+    }
+    
+    /**
+     * Removes the element at the specified position in this list. Returns the 
+     * element that was removed from the list.
+     * 
+     * @param index	the index of the element to removed
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public Object remove(int index) {
+      return ((CheckBoxListItem) super.remove(index)).getContent();
+    }
+    
+    /**
+     * Removes the first (lowest-indexed) occurrence of the argument from this 
+     * list.
+     * 
+     * @param obj	the component to be removed
+     * @return		true if the argument was a component of this list; 
+     * 			false otherwise
+     */
+    public boolean removeElement(Object obj) {
+      if (!(obj instanceof CheckBoxListItem))
+	return super.removeElement(new CheckBoxListItem(obj));
+      else
+	return super.removeElement(obj);
+    }
+    
+    /**
+     * Replaces the element at the specified position in this list with the 
+     * specified element.
+     * 
+     * @param index	index of element to replace
+     * @param element	element to be stored at the specified position
+     * @throws ArrayIndexOutOfBoundsException 
+     */
+    public Object set(int index, Object element) {
+      if (!(element instanceof CheckBoxListItem))
+	return ((CheckBoxListItem) super.set(index, new CheckBoxListItem(element))).getContent();
+      else
+	return ((CheckBoxListItem) super.set(index, element)).getContent();
+    }
+    
+    /**
+     * Sets the component at the specified index of this list to be the 
+     * specified object. The previous component at that position is discarded.
+     * 
+     * @param obj	what the component is to be set to
+     * @param index	the specified index
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public void setElementAt(Object obj, int index) {
+      if (!(obj instanceof CheckBoxListItem))
+	super.setElementAt(new CheckBoxListItem(obj), index);
+      else
+	super.setElementAt(obj, index);
+    }
+    
+    /**
+     * Returns an array containing all of the elements in this list in the 
+     * correct order.
+     * 
+     * @return 		an array containing the elements of the list
+     */
+    public Object[] toArray() {
+      Object[]		result;
+      Object[]		internal;
+      int		i;
+      
+      internal = super.toArray();
+      result   = new Object[internal.length];
+      
+      for (i = 0; i < internal.length; i++)
+	result[i] = ((CheckBoxListItem) internal[i]).getContent();
+      
+      return result;
+    }
+    
+    /**
+     * returns the checked state of the element at the given index
+     * 
+     * @param index	the index of the element to return the checked state for
+     * @return		the checked state of the specifed element
+     */
+    public boolean getChecked(int index) {
+      return ((CheckBoxListItem) super.getElementAt(index)).getChecked();
+    }
+    
+    /**
+     * sets the checked state of the element at the given index
+     * 
+     * @param index	the index of the element to set the checked state for
+     * @param checked	the new checked state
+     */
+    public void setChecked(int index, boolean checked) {
+      ((CheckBoxListItem) super.getElementAt(index)).setChecked(checked);
+    }
+  }
+  
+  /**
+   * A specialized CellRenderer for the CheckBoxList
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 1.3 $
+   * @see CheckBoxList
+   */
+  public class CheckBoxListRenderer 
+    extends JCheckBox 
+    implements ListCellRenderer {
+
+    /** for serialization */
+    private static final long serialVersionUID = 1059591605858524586L;
+  
+    /**
+     * Return a component that has been configured to display the specified 
+     * value.
+     * 
+     * @param list	The JList we're painting.
+     * @param value	The value returned by list.getModel().getElementAt(index).
+     * @param index	The cells index.
+     * @param isSelected	True if the specified cell was selected.
+     * @param cellHasFocus	True if the specified cell has the focus.
+     * @return 		A component whose paint() method will render the 
+     * 			specified value.
+     */
+    public Component getListCellRendererComponent(
+	JList list,
+        Object value,
+        int index,
+        boolean isSelected,
+        boolean cellHasFocus) {
+      
+      setText(value.toString());
+      setSelected(((CheckBoxList) list).getChecked(index));
+      setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
+      setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
+      setFocusPainted(false);
+      
+      return this;
+    }
+  }
+  
+  /**
+   * initializes the list with an empty CheckBoxListModel
+   */
+  public CheckBoxList() {
+    this(null);
+  }
+  
+  /**
+   * initializes the list with the given CheckBoxListModel
+   * 
+   * @param model	the model to initialize with
+   */
+  public CheckBoxList(CheckBoxListModel model) {
+    super();
+    
+    if (model == null)
+      model = this.new CheckBoxListModel();
+    
+    setModel(model);
+    setCellRenderer(new CheckBoxListRenderer());
+    
+    addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+	int index = locationToIndex(e.getPoint());
+	
+	if (index != -1) {
+	  setChecked(index, !getChecked(index));
+	  repaint();
+	}
+      }
+    });
+    
+    addKeyListener(new KeyAdapter() {
+      public void keyTyped(KeyEvent e) {
+        if ( (e.getKeyChar() == ' ') && (e.getModifiers() == 0) ) {
+          int index = getSelectedIndex();
+          setChecked(index, !getChecked(index));
+          e.consume();
+	  repaint();
+        }
+      }
+    });
+  }
+  
+  /**
+   * sets the model - must be an instance of CheckBoxListModel
+   * 
+   * @param model			the model to use
+   * @throws IllegalArgumentException 	if the model is not an instance of
+   * 					CheckBoxListModel
+   * @see CheckBoxListModel
+   */
+  public void setModel(ListModel model) {
+    if (!(model instanceof CheckBoxListModel))
+      throw new IllegalArgumentException("Model must be an instance of CheckBoxListModel!");
+    
+    super.setModel(model);
+  }
+  
+  /**
+   * Constructs a CheckBoxListModel from an array of objects and then applies 
+   * setModel to it.
+   * 
+   * @param listData	the data to use
+   */
+  public void setListData(Object[] listData) {
+    setModel(new CheckBoxListModel(listData));
+  }
+  
+  /**
+   * Constructs a CheckBoxListModel from a Vector and then applies setModel 
+   * to it.
+   */
+  public void setListData(Vector listData) {
+    setModel(new CheckBoxListModel(listData));
+  }
+  
+  /**
+   * returns the checked state of the element at the given index
+   * 
+   * @param index	the index of the element to return the checked state for
+   * @return		the checked state of the specifed element
+   */
+  public boolean getChecked(int index) {
+    return ((CheckBoxListModel) getModel()).getChecked(index);
+  }
+  
+  /**
+   * sets the checked state of the element at the given index
+   * 
+   * @param index	the index of the element to set the checked state for
+   * @param checked	the new checked state
+   */
+  public void setChecked(int index, boolean checked) {
+    ((CheckBoxListModel) getModel()).setChecked(index, checked);
+  }
+  
+  /**
+   * returns an array with the indices of all checked items
+   * 
+   * @return		the indices of all items that are currently checked
+   */
+  public int[] getCheckedIndices() {
+    Vector	list;
+    int[]	result;
+    int		i;
+    
+    // traverse over model
+    list = new Vector();
+    for (i = 0; i < getModel().getSize(); i++) {
+      if (getChecked(i))
+	list.add(new Integer(i));
+    }
+    
+    // generate result array
+    result = new int[list.size()];
+    for (i = 0; i < list.size(); i++) {
+      result[i] = ((Integer) list.get(i)).intValue();
+    }
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ComponentHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ComponentHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ComponentHelper.java	(revision 29)
@@ -0,0 +1,184 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ComponentHelper.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.Component;
+import java.awt.Image;
+import java.net.URL;
+
+import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
+
+
+/**
+ * A helper class for some common tasks with Dialogs, Icons, etc.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $ 
+ */
+
+public class ComponentHelper {
+  /** the default directories for images */
+  public final static String[] IMAGES = {"weka/gui/", "weka/gui/images/"};
+  
+  /**
+   * returns the ImageIcon for a given filename and directory, NULL if not successful
+   * 
+   * @param dir           the directory to look in for the file
+   * @param filename      the file to retrieve
+   * @return              the imageicon if found, otherwise null
+   */
+  public static ImageIcon getImageIcon(String dir, String filename) {
+    URL            url;
+    ImageIcon      result;
+    int            i;
+    
+    result = null;
+    url    = Loader.getURL(dir, filename);
+    
+    // try to find the image at default locations
+    if (url == null) {
+      for (i = 0; i < IMAGES.length; i++) {
+        url = Loader.getURL(IMAGES[i], filename);
+        if (url != null)
+          break;
+      }
+    }
+    
+    if (url != null)
+      result = new ImageIcon(url);
+    
+    return result;
+  }
+  
+  /**
+   * returns the ImageIcon for a given filename, NULL if not successful
+   *
+   * @param filename      the file to retrieve
+   * @return              the imageicon if found, otherwise null
+   */
+  public static ImageIcon getImageIcon(String filename) {
+    return getImageIcon("", filename);
+  }
+  
+  /**
+   * returns the Image for a given directory and filename, NULL if not successful
+   * 
+   * @param dir           the directory to look in for the file
+   * @param filename      the file to retrieve
+   * @return              the image if found, otherwise null
+   */
+  public static Image getImage(String dir, String filename) {
+    ImageIcon      img;
+    Image          result;
+    
+    result = null;
+    img    = getImageIcon(dir, filename);
+    
+    if (img != null)
+      result = img.getImage();
+    
+    return result;
+  }
+  
+  /**
+   * returns the Image for a given filename, NULL if not successful
+   * 
+   * @param filename      the file to retrieve
+   * @return              the image if found, otherwise null
+   */
+  public static Image getImage(String filename) {
+    ImageIcon      img;
+    Image          result;
+    
+    result = null;
+    img    = getImageIcon(filename);
+    
+    if (img != null)
+      result = img.getImage();
+    
+    return result;
+  }
+  
+  /**
+   * displays a message box with the given title, message, buttons and icon
+   * ant the dimension.
+   * it returns the pressed button.
+   * @param parent         the parent component 
+   * @param title          the title of the message box
+   * @param msg            the text to display
+   * @param buttons        the captions of the buttons to display
+   * @param messageType    the type of message like defined in <code>JOptionPane</code> (the icon is determined on this basis)
+   * @return               the button that was pressed
+   * @see JOptionPane
+   */
+  public static int showMessageBox(Component parent, String title, String msg, int buttons, int messageType) { 
+    String        icon;
+    
+    switch (messageType) {
+      case JOptionPane.ERROR_MESSAGE:
+      icon = "weka/gui/images/error.gif";
+      break;
+    case JOptionPane.INFORMATION_MESSAGE:
+      icon = "weka/gui/images/information.gif";
+      break;
+    case JOptionPane.WARNING_MESSAGE:
+      icon = "weka/gui/images/information.gif";
+      break;
+    case JOptionPane.QUESTION_MESSAGE:
+      icon = "weka/gui/images/question.gif";
+      break;
+    default:
+      icon = "weka/gui/images/information.gif";
+      break;
+    }
+    
+    return JOptionPane.showConfirmDialog(parent, msg, title, buttons, messageType, getImageIcon(icon));
+  }
+  
+  /**
+   * pops up an input dialog
+   * 
+   * @param parent        the parent of this dialog, can be <code>null</code>
+   * @param title         the title to display, can be <code>null</code>
+   * @param msg           the message to display
+   * @param initialValue  the initial value to display as input
+   * @return              the entered value, or if cancelled <code>null</code>  
+   */
+  public static String showInputBox(Component parent, String title, String msg, Object initialValue) {
+    Object        result;
+    
+    if (title == null)
+      title = "Input...";
+    
+    result = JOptionPane.showInputDialog(
+             parent, msg, title, JOptionPane.QUESTION_MESSAGE, getImageIcon("question.gif"), null, initialValue);
+    
+    if (result != null)
+      return result.toString();
+    else
+      return null;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/ConverterFileChooser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ConverterFileChooser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ConverterFileChooser.java	(revision 29)
@@ -0,0 +1,796 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ConverterFileChooser.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.AbstractFileSaver;
+import weka.core.converters.AbstractLoader;
+import weka.core.converters.AbstractSaver;
+import weka.core.converters.ConverterUtils;
+import weka.core.converters.FileSourcedConverter;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A specialized JFileChooser that lists all available file Loaders and Savers.
+ * To list only savers that can handle the data that is about to be saved, one
+ * can set a Capabilities filter.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4915 $
+ * @see	    #setCapabilitiesFilter(Capabilities)
+ */
+public class ConverterFileChooser
+  extends JFileChooser {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -5373058011025481738L;
+  
+  /** unhandled type of dialog. */
+  public final static int UNHANDLED_DIALOG = 0;
+  
+  /** the loader dialog. */
+  public final static int LOADER_DIALOG = 1;
+  
+  /** the saver dialog. */
+  public final static int SAVER_DIALOG = 2;
+  
+  /** the file chooser itself. */
+  protected ConverterFileChooser m_Self;
+
+  /** the file filters for the loaders. */
+  protected static Vector<ExtensionFileFilter> m_LoaderFileFilters;
+
+  /** the file filters for the savers. */
+  protected static Vector<ExtensionFileFilter> m_SaverFileFilters;
+  
+  /** the type of dialog to display. */
+  protected int m_DialogType;
+
+  /** the converter that was chosen by the user. */
+  protected Object m_CurrentConverter;
+  
+  /** the configure button. */
+  protected JButton m_ConfigureButton;
+
+  /** the propertychangelistener. */
+  protected PropertyChangeListener m_Listener;
+  
+  /** the last filter that was used for opening/saving. */
+  protected FileFilter m_LastFilter;
+  
+  /** the Capabilities filter for the savers. */
+  protected Capabilities m_CapabilitiesFilter;
+  
+  /** whether to popup a dialog in case the file already exists (only save
+   * dialog). */
+  protected boolean m_OverwriteWarning = true;
+
+  /** whether the file to be opened must exist (only open dialog). */
+  protected boolean m_FileMustExist = true;
+
+  /** the checkbox for bringing up the GenericObjectEditor. */
+  protected JCheckBox m_CheckBoxOptions;
+  
+  /** the note about the options dialog. */
+  protected JLabel m_LabelOptions;
+
+  /** the GOE for displaying the options of a loader/saver. */
+  protected GenericObjectEditor m_Editor = null;
+  
+  /** whether the GOE was OKed or Canceled. */
+  protected int m_EditorResult;
+  
+  /** whether to display only core converters (hardcoded in ConverterUtils).
+   * Necessary for RMI/Remote Experiments for instance.
+   * 
+   * @see ConverterUtils#CORE_FILE_LOADERS
+   * @see ConverterUtils#CORE_FILE_SAVERS */
+  protected boolean m_CoreConvertersOnly = false;
+  
+  static {
+    initFilters(true, ConverterUtils.getFileLoaders());
+    initFilters(false, ConverterUtils.getFileSavers());
+  }
+  
+  /**
+   * onstructs a FileChooser pointing to the user's default directory.
+   */
+  public ConverterFileChooser() {
+    super();
+    initialize();
+  }
+
+  /**
+   * Constructs a FileChooser using the given File as the path.
+   * 
+   * @param currentDirectory	the path to start in
+   */
+  public ConverterFileChooser(File currentDirectory) {
+    super(currentDirectory);
+    initialize();
+  }
+  
+  /**
+   * Constructs a FileChooser using the given path.
+   * 
+   * @param currentDirectory	the path to start in
+   */
+  public ConverterFileChooser(String currentDirectory) {
+    super(currentDirectory);
+    initialize();
+  }
+  
+  /**
+   * Further initializations.
+   */
+  protected void initialize() {
+    JPanel	panel;
+    JPanel	panel2;
+    
+    m_Self = this;
+    
+    m_CheckBoxOptions = new JCheckBox("Invoke options dialog");
+    m_CheckBoxOptions.setMnemonic('I');
+    m_LabelOptions = new JLabel("<html><br>Note:<br><br>Some file formats offer additional<br>options which can be customized<br>when invoking the options dialog.</html>");
+    panel = new JPanel(new BorderLayout());
+    panel.add(m_CheckBoxOptions, BorderLayout.NORTH);
+    panel2 = new JPanel(new BorderLayout());
+    panel2.add(m_LabelOptions, BorderLayout.NORTH);
+    panel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    panel.add(panel2, BorderLayout.CENTER);
+    setAccessory(panel);
+
+    m_Editor = new GenericObjectEditor(false);
+    ((GenericObjectEditor.GOEPanel) m_Editor.getCustomEditor()).addOkListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_EditorResult     = JFileChooser.APPROVE_OPTION;
+	m_CurrentConverter = m_Editor.getValue();
+	// thanks to serialization and transient readers/streams, we have
+	// to set the file again to initialize the converter again
+	try {
+	  ((FileSourcedConverter) m_CurrentConverter).setFile(((FileSourcedConverter) m_CurrentConverter).retrieveFile());
+	}
+	catch (Exception ex) {
+	  // ignored
+	}
+      }
+    });
+    ((GenericObjectEditor.GOEPanel) m_Editor.getCustomEditor()).addCancelListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_EditorResult = JFileChooser.CANCEL_OPTION;
+      }
+    });
+  }
+
+  /**
+   * filters out all non-core loaders if only those should be displayed.
+   * 
+   * @param list	the list of filters to check
+   * @return		the filtered list of filters
+   * @see		#m_CoreConvertersOnly
+   */
+  protected Vector<ExtensionFileFilter> filterNonCoreLoaderFileFilters(Vector<ExtensionFileFilter> list) {
+    Vector<ExtensionFileFilter> result;
+    int				i;
+    ExtensionFileFilter		filter;
+    AbstractLoader		loader;
+    
+    if (!getCoreConvertersOnly()) {
+      result = list;
+    }
+    else {
+      result = new Vector<ExtensionFileFilter>();
+      for (i = 0; i < list.size(); i++) {
+	filter = list.get(i);
+	loader = ConverterUtils.getLoaderForExtension(filter.getExtensions()[0]);
+	if (ConverterUtils.isCoreFileLoader(loader.getClass().getName()))
+	  result.add(filter);
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * filters out all non-core savers if only those should be displayed.
+   * 
+   * @param list	the list of filters to check
+   * @return		the filtered list of filters
+   * @see		#m_CoreConvertersOnly
+   */
+  protected Vector<ExtensionFileFilter> filterNonCoreSaverFileFilters(Vector<ExtensionFileFilter> list) {
+    Vector<ExtensionFileFilter> result;
+    int				i;
+    ExtensionFileFilter		filter;
+    AbstractSaver		saver;
+    
+    if (!getCoreConvertersOnly()) {
+      result = list;
+    }
+    else {
+      result = new Vector<ExtensionFileFilter>();
+      for (i = 0; i < list.size(); i++) {
+	filter = list.get(i);
+	saver = ConverterUtils.getSaverForExtension(filter.getExtensions()[0]);
+	if (ConverterUtils.isCoreFileSaver(saver.getClass().getName()))
+	  result.add(filter);
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * filters the list of file filters according to the currently set.
+   * Capabilities
+   * 
+   * @param list	the filters to check
+   * @return		the filtered list of filters
+   */
+  protected Vector<ExtensionFileFilter> filterSaverFileFilters(Vector<ExtensionFileFilter> list) {
+    Vector<ExtensionFileFilter>	result;
+    int				i;
+    ExtensionFileFilter		filter;
+    AbstractSaver		saver;
+    
+    if (m_CapabilitiesFilter == null) {
+      result = list;
+    }
+    else {
+      result = new Vector<ExtensionFileFilter>();
+      
+      for (i = 0; i < list.size(); i++) {
+	filter = list.get(i);
+	saver  = ConverterUtils.getSaverForExtension(filter.getExtensions()[0]);
+	if (saver.getCapabilities().supports(m_CapabilitiesFilter))
+	  result.add(filter);
+      }
+    }
+
+    return result;
+  }
+  
+  /**
+   * initializes the ExtensionFileFilters.
+   * 
+   * @param loader	if true then the loader filter are initialized
+   * @param classnames	the classnames of the converters
+   */
+  protected static void initFilters(boolean loader, Vector<String> classnames) {
+    int				i;
+    int				n;
+    String 			classname;
+    Class 			cls;
+    String[] 			ext;
+    String 			desc;
+    FileSourcedConverter 	converter;
+    ExtensionFileFilter 	filter;
+    
+    if (loader)
+      m_LoaderFileFilters = new Vector<ExtensionFileFilter>();
+    else
+      m_SaverFileFilters  = new Vector<ExtensionFileFilter>();
+
+    for (i = 0; i < classnames.size(); i++) {
+      classname = (String) classnames.get(i);
+      
+      // get data from converter
+      try {
+	cls       = Class.forName(classname);
+	converter = (FileSourcedConverter) cls.newInstance();
+	ext       = converter.getFileExtensions();
+	desc      = converter.getFileDescription();
+      }
+      catch (Exception e) {
+	cls       = null;
+	converter = null;
+	ext       = new String[0];
+	desc      = "";
+      }
+      
+      if (converter == null)
+	continue;
+
+      // loader?
+      if (loader) {
+	for (n = 0; n < ext.length; n++) {
+	  filter = new ExtensionFileFilter(ext[n], desc + " (*" + ext[n] + ")");
+	  m_LoaderFileFilters.add(filter);
+	}
+      }
+      else {
+	for (n = 0; n < ext.length; n++) {
+	  filter = new ExtensionFileFilter(ext[n], desc + " (*" + ext[n] + ")");
+	  m_SaverFileFilters.add(filter);
+	}
+      }
+    }
+  }
+  
+  /**
+   * initializes the GUI.
+   * 
+   * @param dialogType		the type of dialog to setup the GUI for
+   */
+  protected void initGUI(int dialogType) {
+    Vector<ExtensionFileFilter>	list;
+    int				i;
+    boolean 			acceptAll;
+
+    // backup current state
+    acceptAll = isAcceptAllFileFilterUsed();
+
+    // setup filters
+    resetChoosableFileFilters();
+    setAcceptAllFileFilterUsed(acceptAll);
+    if (dialogType == LOADER_DIALOG)
+      list = filterNonCoreLoaderFileFilters(m_LoaderFileFilters);
+    else
+      list = filterSaverFileFilters(filterNonCoreSaverFileFilters(m_SaverFileFilters));
+    for (i = 0; i < list.size(); i++) {
+      addChoosableFileFilter(list.get(i));
+    }
+    if (list.size() > 0) {
+      if ( (m_LastFilter == null) || (!list.contains(m_LastFilter)) )
+	setFileFilter(list.get(0));
+      else
+	setFileFilter(m_LastFilter);
+    }
+
+    // listener
+    if (m_Listener != null)
+      removePropertyChangeListener(m_Listener);
+    m_Listener = new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent evt) {
+	// filter changed
+	if (evt.getPropertyName().equals(FILE_FILTER_CHANGED_PROPERTY)) {
+	  updateCurrentConverter();
+	}
+      }
+    };
+    addPropertyChangeListener(m_Listener);
+    
+    // initial setup
+    if (dialogType == LOADER_DIALOG) {
+      m_Editor.setClassType(AbstractFileLoader.class);
+      m_Editor.setValue(new weka.core.converters.ArffLoader());
+    }
+    else {
+      m_Editor.setClassType(AbstractFileSaver.class);
+      m_Editor.setValue(new weka.core.converters.ArffSaver());
+    }
+    
+    updateCurrentConverter();
+  }
+
+  /**
+   * sets the capabilities that the savers must have. use null if all should be
+   * listed.
+   * 
+   * @param value	the minimum Capabilities the savers must have
+   */
+  public void setCapabilitiesFilter(Capabilities value) {
+    m_CapabilitiesFilter = (Capabilities) value.clone();
+  }
+  
+  /**
+   * returns the capabilities filter for the savers, can be null if all are
+   * listed.
+   * 
+   * @return		the minimum Capabilities the savers must have
+   */
+  public Capabilities getCapabilitiesFilter() {
+    if (m_CapabilitiesFilter != null)
+      return (Capabilities) m_CapabilitiesFilter.clone();
+    else
+      return null;
+  }
+
+  /**
+   * Whether a warning is popped up if the file that is to be saved already
+   * exists (only save dialog).
+   * 
+   * @param value	if true a warning will be popup
+   */
+  public void setOverwriteWarning(boolean value) {
+    m_OverwriteWarning = value;
+  }
+  
+  /**
+   * Returns whether a popup appears with a warning that the file already 
+   * exists (only save dialog).
+   * 
+   * @return		true if a warning pops up
+   */
+  public boolean getOverwriteWarning() {
+    return m_OverwriteWarning;
+  }
+
+  /**
+   * Whether the selected file must exst (only open dialog).
+   * 
+   * @param value	if true the file must exist
+   */
+  public void setFileMustExist(boolean value) {
+    m_FileMustExist = value;
+  }
+  
+  /**
+   * Returns whether the selected file must exist (only open dialog).
+   * 
+   * @return		true if the file must exist
+   */
+  public boolean getFileMustExist() {
+    return m_FileMustExist;
+  }
+
+  /**
+   * Whether to display only the hardocded core converters. Necessary for
+   * RMI/Remote Experiments (dynamic class discovery doesn't work there!).
+   * 
+   * @param value	if true only the core converters will be displayed
+   * @see 		#m_CoreConvertersOnly
+   */
+  public void setCoreConvertersOnly(boolean value) {
+    m_CoreConvertersOnly = value;
+  }
+  
+  /**
+   * Returns whether only the hardcoded core converters are displayed. 
+   * Necessary for RMI/REmote Experiments (dynamic class discovery doesn't 
+   * work there!).
+   * 
+   * @return		true if the file must exist
+   * @see 		#m_CoreConvertersOnly
+   */
+  public boolean getCoreConvertersOnly() {
+    return m_CoreConvertersOnly;
+  }
+  
+  /**
+   * Pops a custom file chooser dialog with a custom approve button. Throws
+   * an exception, if the dialog type is UNHANDLED_DIALOG.
+   * 
+   * @param parent		the parent of this dialog
+   * @param approveButtonText	the text for the OK button
+   * @return			the user's action
+   */
+  public int showDialog(Component parent, String approveButtonText) {
+    if (m_DialogType == UNHANDLED_DIALOG)
+      throw new IllegalStateException("Either use showOpenDialog or showSaveDialog!");
+    else
+      return super.showDialog(parent, approveButtonText);
+  }
+  
+  /**
+   * Pops up an "Open File" file chooser dialog.
+   * 
+   * @param parent		the parent of this file chooser
+   * @return			the result of the user's action
+   */
+  public int showOpenDialog(Component parent) {
+    m_DialogType       = LOADER_DIALOG;
+    m_CurrentConverter = null;
+    
+    initGUI(LOADER_DIALOG);
+    
+    int result = super.showOpenDialog(parent);
+    
+    m_DialogType = UNHANDLED_DIALOG;
+    removePropertyChangeListener(m_Listener);
+
+    // do we have to add the extension?
+    if ( (result == APPROVE_OPTION) && (getSelectedFile().isFile()) ) {
+      if (getFileFilter() instanceof ExtensionFileFilter) {
+	String filename = getSelectedFile().getAbsolutePath();
+	String[] extensions = ((ExtensionFileFilter) getFileFilter()).getExtensions();
+	if (!filename.endsWith(extensions[0])) {
+	  filename += extensions[0];
+	  setSelectedFile(new File(filename));
+	}
+      }
+    }
+    
+    // does file exist?
+    if (    (result == APPROVE_OPTION) 
+	 && (getFileMustExist()) 
+	 && (getSelectedFile().isFile())
+	 && (!getSelectedFile().exists()) ) {
+      int retVal = JOptionPane.showConfirmDialog(
+	  parent, 
+	  "The file '" 
+	  + getSelectedFile() 
+	  + "' does not exist - please select again!");
+      if (retVal == JOptionPane.OK_OPTION)
+	result = showOpenDialog(parent);
+      else
+	result = CANCEL_OPTION;
+    }
+
+    if (result == APPROVE_OPTION) {
+      m_LastFilter = getFileFilter();
+      configureCurrentConverter(LOADER_DIALOG);
+
+      // bring up options dialog?
+      if (m_CheckBoxOptions.isSelected()) {
+	m_EditorResult = JFileChooser.CANCEL_OPTION;
+	m_Editor.setValue(m_CurrentConverter);
+	PropertyDialog pd;
+	if (PropertyDialog.getParentDialog(this) != null)
+	  pd = new PropertyDialog(PropertyDialog.getParentDialog(this), m_Editor);
+	else
+	  pd = new PropertyDialog(PropertyDialog.getParentFrame(this), m_Editor);
+	pd.setVisible(true);
+	result = m_EditorResult;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Pops up an "Save File" file chooser dialog.
+   * 
+   * @param parent		the parent of this file chooser
+   * @return			the result of the user's action
+   */
+  public int showSaveDialog(Component parent) {
+    m_DialogType       = SAVER_DIALOG;
+    m_CurrentConverter = null;
+    
+    initGUI(SAVER_DIALOG);
+    
+    boolean acceptAll = isAcceptAllFileFilterUsed();
+
+    // using "setAcceptAllFileFilterUsed" messes up the currently selected 
+    // file filter/file, hence backup/restore of currently selected 
+    // file filter/file
+    FileFilter currentFilter = getFileFilter();
+    File currentFile = getSelectedFile();
+    setAcceptAllFileFilterUsed(false);
+    setFileFilter(currentFilter);
+    setSelectedFile(currentFile);
+    
+    int result = super.showSaveDialog(parent);
+    
+    // do we have to add the extension?
+    if (result == APPROVE_OPTION) {
+      if (getFileFilter() instanceof ExtensionFileFilter) {
+	String filename = getSelectedFile().getAbsolutePath();
+	String[] extensions = ((ExtensionFileFilter) getFileFilter()).getExtensions();
+	if (!filename.endsWith(extensions[0])) {
+	  filename += extensions[0];
+	  setSelectedFile(new File(filename));
+	}
+      }
+    }
+    
+    // using "setAcceptAllFileFilterUsed" messes up the currently selected 
+    // file filter/file, hence backup/restore of currently selected 
+    // file filter/file
+    currentFilter = getFileFilter();
+    currentFile = getSelectedFile();
+    setAcceptAllFileFilterUsed(acceptAll);
+    setFileFilter(currentFilter);
+    setSelectedFile(currentFile);
+    
+    m_DialogType = UNHANDLED_DIALOG;
+    removePropertyChangeListener(m_Listener);
+
+    // overwrite the file?
+    if (    (result == APPROVE_OPTION) 
+	 && (getOverwriteWarning()) 
+	 && (getSelectedFile().exists()) ) {
+      int retVal = JOptionPane.showConfirmDialog(
+	  	  parent, 
+	  	  "The file '" 
+	  	  + getSelectedFile() 
+	  	  + "' already exists - overwrite it?");
+      if (retVal == JOptionPane.OK_OPTION)
+	result = APPROVE_OPTION;
+      else if (retVal == JOptionPane.NO_OPTION)
+	result = showSaveDialog(parent);
+      else
+	result = CANCEL_OPTION;
+    }
+    
+    if (result == APPROVE_OPTION) {
+      m_LastFilter = getFileFilter();
+      configureCurrentConverter(SAVER_DIALOG);
+
+      // bring up options dialog?
+      if (m_CheckBoxOptions.isSelected()) {
+	m_EditorResult = JFileChooser.CANCEL_OPTION;
+	m_Editor.setValue(m_CurrentConverter);
+	PropertyDialog pd;
+	if (PropertyDialog.getParentDialog(this) != null)
+	  pd = new PropertyDialog(PropertyDialog.getParentDialog(this), m_Editor);
+	else
+	  pd = new PropertyDialog(PropertyDialog.getParentFrame(this), m_Editor);
+	pd.setVisible(true);
+	result = m_EditorResult;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the loader that was chosen by the user, can be null in case the
+   * user aborted the dialog or the save dialog was shown.
+   * 
+   * @return		the chosen loader, if any
+   */
+  public AbstractFileLoader getLoader() {
+    configureCurrentConverter(LOADER_DIALOG);
+    
+    if (m_CurrentConverter instanceof AbstractFileSaver)
+      return null;
+    else
+      return (AbstractFileLoader) m_CurrentConverter;
+  }
+  
+  /**
+   * returns the saver that was chosen by the user, can be null in case the
+   * user aborted the dialog or the open dialog was shown.
+   * 
+   * @return		the chosen saver, if any
+   */
+  public AbstractFileSaver getSaver() {
+    configureCurrentConverter(SAVER_DIALOG);
+    
+    if (m_CurrentConverter instanceof AbstractFileLoader)
+      return null;
+    else
+      return (AbstractFileSaver) m_CurrentConverter;
+  }
+  
+  /**
+   * sets the current converter according to the current filefilter.
+   */
+  protected void updateCurrentConverter() {
+    String[]	extensions;
+    Object	newConverter;
+    
+    if (getFileFilter() == null)
+      return;
+    
+    if (!isAcceptAllFileFilterUsed()) {
+      // determine current converter
+      extensions = ((ExtensionFileFilter) getFileFilter()).getExtensions();
+      if (m_DialogType == LOADER_DIALOG)
+	newConverter = ConverterUtils.getLoaderForExtension(extensions[0]);
+      else
+	newConverter = ConverterUtils.getSaverForExtension(extensions[0]);
+
+      try {
+	if (m_CurrentConverter == null) {
+	  m_CurrentConverter = newConverter;
+	}
+	else {
+	  if (!m_CurrentConverter.getClass().equals(newConverter.getClass()))
+	    m_CurrentConverter = newConverter;
+	}
+      }
+      catch (Exception e) {
+	m_CurrentConverter = null;
+	e.printStackTrace();
+      }
+    }
+    else {
+      m_CurrentConverter = null;
+    }
+  }
+  
+  /**
+   * configures the current converter.
+   * 
+   * @param dialogType		the type of dialog to configure for
+   */
+  protected void configureCurrentConverter(int dialogType) {
+    String	filename;
+    File	currFile;
+    
+    if ( (getSelectedFile() == null) || (getSelectedFile().isDirectory()) )
+      return;
+    
+    filename = getSelectedFile().getAbsolutePath();
+    
+    if (m_CurrentConverter == null) {
+      if (dialogType == LOADER_DIALOG)
+	m_CurrentConverter = ConverterUtils.getLoaderForFile(filename);
+      else if (dialogType == SAVER_DIALOG)
+	m_CurrentConverter = ConverterUtils.getSaverForFile(filename);
+      else
+	throw new IllegalStateException("Cannot determine loader/saver!");
+      
+      // none found?
+      if (m_CurrentConverter == null)
+	return;
+    }
+    
+    try {
+      currFile = ((FileSourcedConverter) m_CurrentConverter).retrieveFile();
+      if ((currFile == null) || (!currFile.getAbsolutePath().equals(filename)))
+	((FileSourcedConverter) m_CurrentConverter).setFile(new File(filename));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * For testing the file chooser.
+   * 
+   * @param args	the commandline options - ignored
+   * @throws Exception	if something goes wrong with loading/saving
+   */
+  public static void main(String[] args) throws Exception {
+    ConverterFileChooser	fc;
+    int				retVal;
+    AbstractFileLoader		loader;
+    AbstractFileSaver		saver;
+    Instances			data;
+    
+    fc     = new ConverterFileChooser();
+    retVal = fc.showOpenDialog(null);
+    
+    // load file
+    if (retVal == ConverterFileChooser.APPROVE_OPTION) {
+      loader = fc.getLoader();
+      data   = loader.getDataSet();
+      retVal = fc.showSaveDialog(null);
+
+      // save file
+      if (retVal == ConverterFileChooser.APPROVE_OPTION) {
+	saver = fc.getSaver();
+	saver.setInstances(data);
+	saver.writeBatch();
+      }
+      else {
+	System.out.println("Saving aborted!");
+      }
+    }
+    else {
+      System.out.println("Loading aborted!");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/CostMatrixEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/CostMatrixEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/CostMatrixEditor.java	(revision 29)
@@ -0,0 +1,542 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostMatrixEditor.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.classifiers.CostMatrix;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyEditor;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Class for editing CostMatrix objects. Brings up a custom editing panel
+ * with which the user can edit the matrix interactively, as well as save
+ * load cost matrices from files.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.11 $
+ */
+public class CostMatrixEditor 
+  implements PropertyEditor {
+
+  /** The cost matrix being edited */
+  private CostMatrix m_matrix;
+
+  /** A helper class for notifying listeners */
+  private PropertyChangeSupport m_propSupport;
+
+  /** An instance of the custom editor */
+  private CustomEditor m_customEditor;
+
+  /** The file chooser for the user to select cost files to save and load */
+  private JFileChooser m_fileChooser
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /**
+   * This class wraps around the cost matrix presenting it as a TableModel
+   * so that it can be displayed and edited in a JTable.
+   */
+  private class CostMatrixTableModel 
+    extends AbstractTableModel {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2762326138357037181L;
+
+    /**
+     * Gets the number of rows in the matrix. Cost matrices are square so it is the
+     * same as the column count, i.e. the size of the matrix.
+     *
+     * @return the row count
+     */
+    public int getRowCount() {
+
+      return m_matrix.size();
+    }
+
+    /**
+     * Gets the number of columns in the matrix. Cost matrices are square so it is
+     * the same as the row count, i.e. the size of the matrix.
+     *
+     * @return the row count
+     */
+    public int getColumnCount() {
+
+      return m_matrix.size();
+    }
+
+    /**
+     * Returns a value at the specified position in the cost matrix.
+     *
+     * @param row the row position
+     * @param column the column position
+     * @return the value
+     */
+    public Object getValueAt(int row, int column) {
+
+      //      return new Double(m_matrix.getElement(row, column));
+      try {
+        return m_matrix.getCell(row, column);
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+      return new Double(0.0);
+    }
+
+    /**
+     * Sets a value at a specified position in the cost matrix.
+     *
+     * @param aValue the new value (should be of type Double).
+     * @param rowIndex the row position
+     * @param columnIndex the column position
+     */
+    public void setValueAt(Object aValue,
+			   int rowIndex,
+			   int columnIndex) {
+
+      //      double value = ((Double) aValue).doubleValue();
+      //      m_matrix.setElement(rowIndex, columnIndex, value);
+      // try to parse it as a double first
+      Double val;
+      try {
+        val = new Double(((String)aValue));
+        double value = val.doubleValue();
+      } catch (Exception ex) {
+        val = null;
+      }
+      if (val == null) {
+        m_matrix.setCell(rowIndex, columnIndex, aValue);
+      } else {
+        m_matrix.setCell(rowIndex, columnIndex, val);
+      }
+      fireTableCellUpdated(rowIndex, columnIndex);
+    }
+
+    /**
+     * Indicates whether a cell in the table is editable. In this case all cells
+     * are editable so true is always returned.
+     *
+     * @param rowIndex the row position
+     * @param columnIndex the column position
+     * @return true
+     */    
+    public boolean isCellEditable(int rowIndex,
+				  int columnIndex) {
+
+      return true;
+    }
+
+    /**
+     * Indicates the class of the objects within a column of the table. In this
+     * case all columns in the cost matrix consist of double values so Double.class
+     * is always returned.
+     *
+     * @param columnIndex the column position
+     * @return Double.class
+     */    
+    public Class getColumnClass(int columnIndex) {
+
+      return Object.class;
+    }
+  }
+
+  /**
+   * This class presents a GUI for editing the cost matrix, and saving and 
+   * loading from files.
+   */
+  private class CustomEditor
+    extends JPanel 
+    implements ActionListener, TableModelListener {
+    
+    /** for serialization */
+    static final long serialVersionUID = -2931593489871197274L;
+
+    /** The table model of the cost matrix being edited */
+    private CostMatrixTableModel m_tableModel;
+
+    /** The button for setting default matrix values */
+    private JButton m_defaultButton;
+
+    /** The button for opening a cost matrix from a file */
+    private JButton m_openButton;
+
+    /** The button for saving a cost matrix to a file */
+    private JButton m_saveButton;
+
+    /** The field for changing the size of the cost matrix */
+    private JTextField m_classesField;
+
+    /** The button for resizing a matrix */
+    private JButton m_resizeButton;
+
+    /**
+     * Constructs a new CustomEditor.
+     *
+     */
+    public CustomEditor() {
+
+      // set up the file chooser
+      m_fileChooser.setFileFilter(
+	     new ExtensionFileFilter(CostMatrix.FILE_EXTENSION, 
+				     "Cost files")
+	       );
+      m_fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+      // create the buttons + field
+      m_defaultButton = new JButton("Defaults");
+      m_openButton = new JButton("Open...");
+      m_saveButton = new JButton("Save...");
+      m_resizeButton = new JButton("Resize");
+      m_classesField = new JTextField("" + m_matrix.size());
+
+      m_defaultButton.addActionListener(this);
+      m_openButton.addActionListener(this);
+      m_saveButton.addActionListener(this);
+      m_resizeButton.addActionListener(this);
+      m_classesField.addActionListener(this);
+
+      // lay out the GUI
+      JPanel classesPanel = new JPanel();
+      classesPanel.setLayout(new GridLayout(1, 2, 0, 0));
+      classesPanel.add(new JLabel("Classes:", SwingConstants.RIGHT));
+      classesPanel.add(m_classesField);
+
+      JPanel rightPanel = new JPanel();
+      
+      GridBagLayout gridBag = new GridBagLayout();
+      GridBagConstraints gbc = new GridBagConstraints();
+      rightPanel.setLayout(gridBag);
+      gbc.gridx = 0; gbc.gridy = GridBagConstraints.RELATIVE;
+      gbc.insets = new Insets(2, 10, 2, 10);
+      gbc.fill = GridBagConstraints.HORIZONTAL;
+      gridBag.setConstraints(m_defaultButton, gbc);
+      rightPanel.add(m_defaultButton);
+
+      gridBag.setConstraints(m_openButton, gbc);
+      rightPanel.add(m_openButton);
+      
+      gridBag.setConstraints(m_saveButton, gbc);
+      rightPanel.add(m_saveButton);
+
+      gridBag.setConstraints(classesPanel, gbc);
+      rightPanel.add(classesPanel);
+      
+      gridBag.setConstraints(m_resizeButton, gbc);
+      rightPanel.add(m_resizeButton);
+
+      JPanel fill = new JPanel();
+      gbc.weightx = 1.0; gbc.weighty = 1.0;
+      gbc.fill = GridBagConstraints.BOTH;
+      
+      gridBag.setConstraints(fill, gbc);
+      rightPanel.add(fill);
+
+      m_tableModel = new CostMatrixTableModel();
+      m_tableModel.addTableModelListener(this);
+      JTable matrixTable = new JTable(m_tableModel);
+      
+      setLayout(new BorderLayout());
+      add(matrixTable, BorderLayout.CENTER);
+      add(rightPanel, BorderLayout.EAST);
+    }
+
+    /**
+     * Responds to the user perfoming an action.
+     *
+     * @param e the action event that occured
+     */
+    public void actionPerformed(ActionEvent e) {
+      
+      if (e.getSource() == m_defaultButton) {
+	m_matrix.initialize();
+	matrixChanged();
+      } else if (e.getSource() == m_openButton) {
+	openMatrix();
+      } else if (e.getSource() == m_saveButton) {
+	saveMatrix();
+      } else if (    (e.getSource() == m_classesField) 
+	          || (e.getSource() == m_resizeButton) ) {
+	try {
+	  int newNumClasses = Integer.parseInt(m_classesField.getText());
+	  if (newNumClasses > 0 && newNumClasses != m_matrix.size()) {
+	    setValue(new CostMatrix(newNumClasses));
+	  }
+	} catch (Exception ex) {}
+      }
+    }
+
+    /**
+     * Responds to a change in the cost matrix table.
+     *
+     * @param e the tabel model event that occured
+     */
+    public void tableChanged(TableModelEvent e) {
+
+      m_propSupport.firePropertyChange(null, null, null);
+    }
+
+    /**
+     * Responds to a change in structure of the matrix being edited.
+     *
+     */
+    public void matrixChanged() {
+
+      m_tableModel.fireTableStructureChanged();
+      m_classesField.setText("" + m_matrix.size());
+    }
+
+    /**
+     * Prompts the user to open a matrix, and attemps to load it.
+     *
+     */
+    private void openMatrix() {
+
+      int returnVal = m_fileChooser.showOpenDialog(this);
+      if(returnVal == JFileChooser.APPROVE_OPTION) {
+	File selectedFile = m_fileChooser.getSelectedFile();
+	Reader reader = null;
+	try {
+	  reader = new BufferedReader(new FileReader(selectedFile));
+	  m_matrix = 
+	    new CostMatrix(reader);
+	  reader.close();
+	  matrixChanged();
+	} catch (Exception ex) {
+	  JOptionPane.showMessageDialog(this, 
+					"Error reading file '"
+					+ selectedFile.getName()
+					+ "':\n" + ex.getMessage(),
+					"Load failed",
+					JOptionPane.ERROR_MESSAGE);
+	  System.out.println(ex.getMessage());
+	}
+      }
+    }
+
+    /**
+     * Prompts the user to save a matrix, and attemps to save it.
+     *
+     */
+    private void saveMatrix() {
+      
+      int returnVal = m_fileChooser.showSaveDialog(this);
+      if(returnVal == JFileChooser.APPROVE_OPTION) {
+	File selectedFile = m_fileChooser.getSelectedFile();
+
+	// append extension if not already present
+	if (!selectedFile.getName().toLowerCase()
+            .endsWith(CostMatrix.FILE_EXTENSION)) {
+	  selectedFile = new File(selectedFile.getParent(), 
+				  selectedFile.getName() 
+				  + CostMatrix.FILE_EXTENSION);
+	}
+
+	Writer writer = null;
+	try {
+	  writer = new BufferedWriter(new FileWriter(selectedFile));
+	  m_matrix.write(writer);
+	  writer.close();
+	} catch (Exception ex) {
+	  JOptionPane.showMessageDialog(this, 
+					"Error writing file '"
+					+ selectedFile.getName()
+					+ "':\n" + ex.getMessage(),
+					"Save failed",
+					JOptionPane.ERROR_MESSAGE);
+	  System.out.println(ex.getMessage());
+	}
+      }
+    }
+  }
+
+  /**
+   * Constructs a new CostMatrixEditor.
+   *
+   */
+  public CostMatrixEditor() {
+
+    m_matrix = new CostMatrix(2);
+    m_propSupport = new PropertyChangeSupport(this);
+    m_customEditor = new CustomEditor();
+  }
+
+  /**
+   * Sets the value of the CostMatrix to be edited.
+   *
+   * @param value a CostMatrix object to be edited
+   */
+  public void setValue(Object value) {
+    
+    m_matrix = (CostMatrix) value;
+    m_customEditor.matrixChanged();
+  }
+
+  /**
+   * Gets the cost matrix that is being edited.
+   *
+   * @return the edited CostMatrix object
+   */  
+  public Object getValue() {
+
+    return m_matrix;
+  }
+
+  /**
+   * Indicates whether the object can be represented graphically. In this case
+   * it can.
+   *
+   * @return true
+   */  
+  public boolean isPaintable() {
+
+    return true;
+  }
+
+  /**
+   * Paints a graphical representation of the object. For the cost matrix it
+   * prints out the text "X x X matrix", where X is the size of the matrix.
+   *
+   * @param gfx the graphics context to draw the representation to
+   * @param box the bounds within which the representation should fit.
+   */    
+  public void paintValue(Graphics gfx,
+			 Rectangle box) {
+
+    gfx.drawString(m_matrix.size() + " x " + m_matrix.size() + " cost matrix",
+		   box.x, box.y + box.height);
+  }
+
+  /**
+   * Returns the Java code that generates an object the same as the one being edited.
+   * Unfortunately this can't be done in a single line of code, so the code returned
+   * will only build a default cost matrix of the same size.
+   *
+   * @return the initialization string
+   */   
+  public String getJavaInitializationString() {
+
+    return ("new CostMatrix(" + m_matrix.size() + ")");
+  }
+
+  /**
+   * Some objects can be represented as text, but a cost matrix cannot.
+   *
+   * @return null
+   */   
+  public String getAsText() {
+
+    return null;
+  }
+
+  /**
+   * Some objects can be represented as text, but a cost matrix cannot.
+   *
+   * @param text ignored
+   * @throws IllegalArgumentException always throws an IllegalArgumentException
+   */   
+  public void setAsText(String text) {
+    throw new IllegalArgumentException("CostMatrixEditor: "
+				       + "CostMatrix properties cannot be "
+				       + "expressed as text");
+  }
+
+  /**
+   * Some objects can return tags, but a cost matrix cannot.
+   *
+   * @return null
+   */  
+  public String[] getTags() {
+
+    return null;
+  }
+
+  /**
+   * Gets a GUI component with which the user can edit the cost matrix.
+   *
+   * @return an editor GUI component
+   */    
+  public Component getCustomEditor() {
+
+    return m_customEditor;
+  }
+
+  /**
+   * Indicates whether the cost matrix can be edited in a GUI, which it can.
+   *
+   * @return true
+   */     
+  public boolean supportsCustomEditor() {
+
+    return true;
+  }
+
+  /**
+   * Adds an object to the list of those that wish to be informed when the
+   * cost matrix changes.
+   *
+   * @param listener a new listener to add to the list
+   */   
+  public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+    m_propSupport.addPropertyChangeListener(listener);
+  }
+
+  /**
+   * Removes an object from the list of those that wish to be informed when the
+   * cost matrix changes.
+   *
+   * @param listener the listener to remove from the list
+   */  
+  public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+    m_propSupport.removePropertyChangeListener(listener);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/CustomPanelSupplier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/CustomPanelSupplier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/CustomPanelSupplier.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CustomPanelSupplier.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import javax.swing.JPanel;
+
+/**
+ * An interface for objects that are capable of supplying their own
+ * custom GUI components. The original purpose for this interface is
+ * to provide a mechanism allowing the GenericObjectEditor to override
+ * the standard PropertyPanel GUI.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.3 $
+ */
+public interface CustomPanelSupplier {
+
+  /**
+   * Gets the custom panel for the object.
+   *
+   * @return the custom JPanel
+   */
+  JPanel getCustomPanel();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/DatabaseConnectionDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/DatabaseConnectionDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/DatabaseConnectionDialog.java	(revision 29)
@@ -0,0 +1,274 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatabaseConnectionDialog.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+/** 
+ * A dialog to enter URL, username and password for a database connection.
+ *
+ * @author Dale Fletcher (dale@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class DatabaseConnectionDialog
+  extends JDialog {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1081946748666245054L;
+
+  /* URL field and label */
+  protected JTextField m_DbaseURLText;
+  protected JLabel m_DbaseURLLab;
+
+  /* Username field and label */
+  protected JTextField m_UserNameText; 
+  protected JLabel m_UserNameLab;
+
+  /* Password field and label */
+  protected JPasswordField m_PasswordText; 
+  protected JLabel m_PasswordLab;
+
+  /* Debug checkbox and label */
+  protected JCheckBox m_DebugCheckBox; 
+  protected JLabel m_DebugLab;
+  
+  /* whether dialog was cancel'ed or OK'ed */
+  protected int m_returnValue;
+
+  /**
+   * Create database connection dialog.
+   *
+   * @param parentFrame the parent frame of the dialog
+   */
+  public DatabaseConnectionDialog(Frame parentFrame) {
+    this(parentFrame, "", "");
+  }
+  
+  /**
+   * Create database connection dialog.
+   *
+   * @param parentFrame the parent frame of the dialog
+   * @param url initial text for URL field
+   * @param uname initial text for username field
+   */
+  public DatabaseConnectionDialog(Frame parentFrame, String url, String uname) {
+    this(parentFrame, url, uname, true);
+  }
+  
+  /**
+   * Create database connection dialog.
+   *
+   * @param parentFrame the parent frame of the dialog
+   * @param url initial text for URL field
+   * @param uname initial text for username field
+   * @param debug whether to display the debug checkbox
+   */
+  public DatabaseConnectionDialog(Frame parentFrame, String url, String uname, boolean debug) {
+    super(parentFrame,"Database Connection Parameters", true);
+    DbConnectionDialog(url, uname, debug);
+  }
+
+  /**
+   * Returns URL from dialog 
+   *
+   * @return URL string
+   */
+  public String getURL(){
+    return(m_DbaseURLText.getText());
+  }
+
+  /**
+   * Returns Username from dialog 
+   *
+   * @return Username string
+   */ 
+  public String getUsername(){
+    return(m_UserNameText.getText());
+  }
+
+  /**
+   * Returns password from dialog 
+   *
+   * @return Password string
+   */
+  public String getPassword(){
+    return(new String(m_PasswordText.getPassword()));
+  }
+  
+  /**
+   * Returns the debug flag
+   * 
+   * @return true if debugging should be enabled
+   */
+  public boolean getDebug() {
+    return m_DebugCheckBox.isSelected();
+  }
+
+  /**
+   * Returns which of OK or cancel was clicked from dialog 
+   *
+   * @return either JOptionPane.OK_OPTION or JOptionPane.CLOSED_OPTION
+   */
+  public int getReturnValue(){
+    return(m_returnValue);
+  }
+  
+  /**
+   * Display the database connection dialog
+   *
+   * @param url initial text for URL field
+   * @param uname initial text for username field
+   */
+  public void DbConnectionDialog(String url, String uname) {
+    DbConnectionDialog(url, uname, true);
+  }
+  
+  /**
+   * Display the database connection dialog
+   *
+   * @param url initial text for URL field
+   * @param uname initial text for username field
+   * @param debug whether to display the debug checkbox
+   */
+  public void DbConnectionDialog(String url, String uname, boolean debug) {
+
+    JPanel DbP = new JPanel();
+    if (debug)
+      DbP.setLayout(new GridLayout(5, 1));
+    else
+      DbP.setLayout(new GridLayout(4, 1));
+    
+    m_DbaseURLText = new JTextField(url,50); 
+    m_DbaseURLLab = new JLabel(" Database URL", SwingConstants.LEFT);
+    m_DbaseURLLab.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_DbaseURLLab.setDisplayedMnemonic('D');
+    m_DbaseURLLab.setLabelFor(m_DbaseURLText);
+
+    m_UserNameText = new JTextField(uname,25); 
+    m_UserNameLab = new JLabel(" Username    ", SwingConstants.LEFT);
+    m_UserNameLab.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_UserNameLab.setDisplayedMnemonic('U');
+    m_UserNameLab.setLabelFor(m_UserNameText);
+
+    m_PasswordText = new JPasswordField(25); 
+    m_PasswordLab = new JLabel(" Password    ", SwingConstants.LEFT);
+    m_PasswordLab.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_PasswordLab.setDisplayedMnemonic('P');
+    m_PasswordLab.setLabelFor(m_PasswordText);
+
+    m_DebugCheckBox = new JCheckBox(); 
+    m_DebugLab = new JLabel(" Debug       ", SwingConstants.LEFT);
+    m_DebugLab.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_DebugLab.setDisplayedMnemonic('P');
+    m_DebugLab.setLabelFor(m_DebugCheckBox);
+
+    JPanel urlP = new JPanel();   
+    urlP.setLayout(new FlowLayout(FlowLayout.LEFT));
+    urlP.add(m_DbaseURLLab);
+    urlP.add(m_DbaseURLText);
+    DbP.add(urlP);
+
+    JPanel usernameP = new JPanel();   
+    usernameP.setLayout(new FlowLayout(FlowLayout.LEFT));
+    usernameP.add(m_UserNameLab);
+    usernameP.add(m_UserNameText);
+    DbP.add(usernameP);
+
+    JPanel passwordP = new JPanel();   
+    passwordP.setLayout(new FlowLayout(FlowLayout.LEFT));
+    passwordP.add(m_PasswordLab);
+    passwordP.add(m_PasswordText);
+    DbP.add(passwordP);
+
+    if (debug) {
+      JPanel debugP = new JPanel();   
+      debugP.setLayout(new FlowLayout(FlowLayout.LEFT));
+      debugP.add(m_DebugLab);
+      debugP.add(m_DebugCheckBox);
+      DbP.add(debugP);
+    }
+
+    JPanel buttonsP = new JPanel();
+    buttonsP.setLayout(new FlowLayout());
+    JButton ok,cancel;
+    buttonsP.add(ok = new JButton("OK"));
+    buttonsP.add(cancel=new JButton("Cancel"));
+    ok.setMnemonic('O');
+    ok.addActionListener(new ActionListener(){
+	public void actionPerformed(ActionEvent evt){
+	  m_returnValue=JOptionPane.OK_OPTION;
+	  DatabaseConnectionDialog.this.dispose();
+      }
+    });
+    cancel.setMnemonic('C');
+    cancel.addActionListener(new ActionListener(){
+	public void actionPerformed(ActionEvent evt){
+	  m_returnValue=JOptionPane.CLOSED_OPTION;
+	  DatabaseConnectionDialog.this.dispose();
+      }
+    });
+    
+    // Listen for window close events
+    addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          System.err.println("Cancelled!!");
+          m_returnValue = JOptionPane.CLOSED_OPTION;
+        }
+      });
+   
+    DbP.add(buttonsP);
+    this.getContentPane().add(DbP,BorderLayout.CENTER);
+    this.pack();
+    getRootPane().setDefaultButton(ok);
+    setResizable(false);
+  }
+  
+  /**
+   * for testing only
+   */
+  public static void main(String[] args){
+    DatabaseConnectionDialog dbd= new DatabaseConnectionDialog(null,"URL","username");
+    dbd.setVisible(true);
+    System.out.println(dbd.getReturnValue()+":"+dbd.getUsername()+":"+dbd.getPassword()+":"+dbd.getURL());
+  }
+}
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/DocumentPrinting.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/DocumentPrinting.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/DocumentPrinting.java	(revision 29)
@@ -0,0 +1,276 @@
+/*
+ * DocumentPrinting.java (original classname: PrintMe)
+ * (C) Jan Michael Soan (http://it.toolbox.com/wiki/index.php/How_to_print_in_Java)
+ * (C) 2009 University of Waikato, Hamilton NewZealand
+ */
+
+package weka.gui;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import javax.swing.JTextPane;
+import javax.swing.text.Document;
+import javax.swing.text.View;
+
+/**
+ * DocumentPrinting is a class that lets you print documents on
+ * the fly for free ;) Printing in JDK 1.2 - 1.5 is hard.
+ * With this, you just simply call it in your application
+ * and add your text component like JTextPane:
+ * <pre>
+ * new DocumentPrinting().print(YourJTextComponent);
+ * </pre>
+ * Reminder: Just make sure there is a text on your component ;P
+ * <pre>
+ * Author : Jan Michael Soan
+ * WebSite: http://www.jmsoan.com
+ * Date   : 04/17/2004 
+ * Time   : 2:20 PM 
+ * </pre>
+ * 
+ * Found on <a href="http://it.toolbox.com/wiki/index.php/How_to_print_in_Java" target="_blank">Toolbox</a>
+ * (<a href="http://www.toolbox.com/TermsofUse.aspx" target="_blank">Terms of Use</a>).
+ * 
+ * @author Jan Michael Soan (<a href="http://it.toolbox.com/wiki/index.php/How_to_print_in_Java" target="_blank">original code</a>)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ */
+public class DocumentPrinting
+  implements Printable {
+
+  /** the current page. */
+  protected int m_CurrentPage = -1;
+  
+  /** the JTextPane which content is to be printed. */
+  protected JTextPane m_PrintPane; 
+  
+  /** the page end. */
+  protected double m_PageEndY = 0;
+  
+  /** the page start. */
+  protected double m_PageStartY = 0;
+  
+  /** whether to scale the width to fit. */
+  protected boolean m_ScaleWidthToFit = true; 
+  
+  /** the pageformat. */
+  protected PageFormat m_PageFormat;
+  
+  /** the printer job. */
+  protected PrinterJob m_PrinterJob;
+
+  /**
+   * Initializes the printing.
+   */
+  public DocumentPrinting() {
+    m_PrintPane  = new JTextPane();
+    m_PageFormat = new PageFormat();
+    m_PrinterJob = PrinterJob.getPrinterJob();
+  }
+
+  /**
+   * Brings up the page dialog.
+   */
+  protected void pageDialog() {
+    m_PageFormat = m_PrinterJob.pageDialog(m_PageFormat);
+  }
+
+  /**
+   * Prints the page.
+   * 
+   * @param graphics	the graphics context
+   * @param pageFormat	the format of the page
+   * @param pageIndex	the page index
+   * @return		either NO_SUCH_PAGE or PAGE_EXISTS
+   * @see		Printable#NO_SUCH_PAGE
+   * @see		Printable#PAGE_EXISTS
+   */
+  public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
+    double scale = 1.0;
+    Graphics2D graphics2D;
+    View rootView;
+
+    graphics2D = (Graphics2D) graphics;
+    m_PrintPane.setSize((int) pageFormat.getImageableWidth(),Integer.MAX_VALUE);
+    m_PrintPane.validate();
+
+    rootView = m_PrintPane.getUI().getRootView(m_PrintPane);
+
+    if ((m_ScaleWidthToFit) && (m_PrintPane.getMinimumSize().getWidth() > pageFormat.getImageableWidth())) {
+      scale = pageFormat.getImageableWidth()/
+      m_PrintPane.getMinimumSize().getWidth();
+      graphics2D.scale(scale,scale);
+    }
+
+    graphics2D.setClip(
+	(int) (pageFormat.getImageableX()/scale),
+	(int) (pageFormat.getImageableY()/scale),
+	(int) (pageFormat.getImageableWidth()/scale),
+	(int) (pageFormat.getImageableHeight()/scale));
+
+    if (pageIndex > m_CurrentPage) {
+      m_CurrentPage = pageIndex;
+      m_PageStartY += m_PageEndY;
+      m_PageEndY = graphics2D.getClipBounds().getHeight();
+    }
+
+    graphics2D.translate(
+	graphics2D.getClipBounds().getX(),
+	graphics2D.getClipBounds().getY());
+    Rectangle allocation = new Rectangle(
+	0,
+	(int) -m_PageStartY,
+	(int) (m_PrintPane.getMinimumSize().getWidth()),
+	(int) (m_PrintPane.getPreferredSize().getHeight()));
+
+    if (printView(graphics2D,allocation,rootView)) {
+      return Printable.PAGE_EXISTS;
+    }
+    else {
+      m_PageStartY = 0;
+      m_PageEndY = 0;
+      m_CurrentPage = -1;
+      return Printable.NO_SUCH_PAGE;
+    }
+  }
+
+  /**
+   * Prints the document in the JTextPane.
+   * 
+   * @param pane	the document to print
+   */
+  public void print(JTextPane pane) {
+    setDocument(pane);
+    printDialog();
+  }
+
+  /**
+   * Shows the print dialog.
+   */
+  public void printDialog() {
+    if (m_PrinterJob.printDialog()) {
+      m_PrinterJob.setPrintable(this,m_PageFormat);
+      try {
+	m_PrinterJob.print();
+      }
+      catch (PrinterException printerException) {
+	m_PageStartY = 0;
+	m_PageEndY = 0;
+	m_CurrentPage = -1;
+	System.out.println("Error Printing Document");
+      }
+    }
+  }
+
+  /**
+   * Shows a print view.
+   * 
+   * @param graphics2D	the graphics context
+   * @param allocation	the allocation
+   * @param view	the view
+   * @return		true if the page exists
+   */
+  protected boolean printView(Graphics2D graphics2D, Shape allocation, View view) {
+    boolean pageExists = false;
+    Rectangle clipRectangle = graphics2D.getClipBounds();
+    Shape childAllocation;
+    View childView;
+
+    if (view.getViewCount() > 0) {
+      for (int i = 0; i < view.getViewCount(); i++) {
+	childAllocation = view.getChildAllocation(i,allocation);
+	if (childAllocation != null) {
+	  childView = view.getView(i);
+	  if (printView(graphics2D,childAllocation,childView)) {
+	    pageExists = true;
+	  }
+	}
+      }
+    } else {
+      if (allocation.getBounds().getMaxY() >= clipRectangle.getY()) {
+	pageExists = true;
+	if ((allocation.getBounds().getHeight() > clipRectangle.getHeight()) &&
+	    (allocation.intersects(clipRectangle))) {
+	  view.paint(graphics2D,allocation);
+	} else {
+	  if (allocation.getBounds().getY() >= clipRectangle.getY()) {
+	    if (allocation.getBounds().getMaxY() <= clipRectangle.getMaxY()) {
+	      view.paint(graphics2D,allocation);
+	    } else {
+	      if (allocation.getBounds().getY() < m_PageEndY) {
+		m_PageEndY = allocation.getBounds().getY();
+	      }
+	    }
+	  }
+	}
+      }
+    }
+    return pageExists;
+  }
+
+  /**
+   * Sets the content type.
+   * 
+   * @param type	the content type
+   */
+  public void setContentType(String type) {
+    m_PrintPane.setContentType(type);
+  }
+
+  /**
+   * Returns the document to print.
+   * 
+   * @return		the document or null
+   */
+  public Document getDocument() {
+    if (m_PrintPane != null) 
+      return m_PrintPane.getDocument();
+    else 
+      return null;
+  }
+
+  /**
+   * Sets the document from the given JTextPane.
+   * 
+   * @param pane	the JTextPane to get the document from
+   */
+  public void setDocument(JTextPane pane) { 
+    m_PrintPane = new JTextPane();
+    setDocument(pane.getContentType(), pane.getDocument());
+  }
+
+  /**
+   * Sets the document and the according content type.
+   * 
+   * @param type	the content type
+   * @param document	the document to print
+   */
+  public void setDocument(String type, Document document) {
+    setContentType(type);
+    m_PrintPane.setDocument(document);
+  }
+
+  /**
+   * Sets whether to scale the width to fit.
+   * 
+   * @param scaleWidth	if true then the width will be scaled
+   */
+  public void setScaleWidthToFit(boolean scaleWidth) {
+    m_ScaleWidthToFit = scaleWidth;
+  }
+
+  /**
+   * Returns whether the width is to be scaled.
+   * 
+   * @return		true if scaled
+   */
+  public boolean getScaleWidthToFit() {
+    return m_ScaleWidthToFit;
+  }
+} 
Index: branches/MetisMQI/src/main/java/weka/gui/EnsembleLibraryEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/EnsembleLibraryEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/EnsembleLibraryEditor.java	(revision 29)
@@ -0,0 +1,354 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleLibraryEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui;
+
+import weka.classifiers.EnsembleLibrary;
+import weka.gui.ensembleLibraryEditor.AddModelsPanel;
+import weka.gui.ensembleLibraryEditor.ListModelsPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyEditor;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+/**
+ * Class for editing Library objects. Brings up a custom editing panel for the
+ * user to edit the library model list, as well as save load libraries from
+ * files.
+ * <p/>
+ * A model list file is simply a flat file with a single classifier on each
+ * line. Each of these classifier is represented by the command line string that
+ * would be used to create that specific model with the specified set of
+ * paramters.
+ * <p/>
+ * This code in class is based on other custom editors in weka.gui such as the
+ * CostMatrixEditor to try and maintain some consistency throughout the package.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class EnsembleLibraryEditor
+  implements PropertyEditor {
+  
+  /** The library being edited */
+  protected EnsembleLibrary m_Library;
+  
+  /** A helper class for notifying listeners */
+  protected transient PropertyChangeSupport m_PropSupport = new PropertyChangeSupport(
+      this);
+  
+  /** An instance of the custom editor */
+  protected transient CustomEditor m_CustomEditor = new CustomEditor();
+  
+  /**
+   * the main panel of the editor that will let the user switch between the
+   * different editing panels.
+   */
+  protected transient JTabbedPane m_ModelOptionsPane = new JTabbedPane();
+  
+  /**
+   * the main panel that will display the current set of models in the library
+   */
+  protected transient ListModelsPanel m_ListModelsPanel = new ListModelsPanel(
+      m_Library);
+  
+  /**
+   * This class presents a GUI for editing the library, and saving and loading
+   * model list from files.
+   */
+  private class CustomEditor
+    extends JPanel {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -1553490570313707008L;
+
+    /**
+     * Constructs a new editor.
+     */
+    public CustomEditor() {
+      
+      m_ModelOptionsPane = new JTabbedPane();
+      
+      m_ListModelsPanel = new ListModelsPanel(m_Library);
+      
+      m_ModelOptionsPane.addTab("Current Library", m_ListModelsPanel);
+      
+      m_ModelOptionsPane.addTab("Add Models", new AddModelsPanel(
+	  m_ListModelsPanel));
+      
+      // All added panels needs a reference to the main one so that
+      // they can add models to it.
+      setLayout(new BorderLayout());
+      add(m_ModelOptionsPane, BorderLayout.CENTER);
+    }
+    
+  }
+  
+  /**
+   * Constructs a new LibraryEditor.
+   */
+  public EnsembleLibraryEditor() {
+    m_PropSupport = new PropertyChangeSupport(this);
+    m_CustomEditor = new CustomEditor();
+  }
+  
+  /**
+   * Sets the value of the Library to be edited.
+   * 
+   * @param value
+   *            a Library object to be edited
+   */
+  public void setValue(Object value) {
+    m_Library = (EnsembleLibrary) value;
+    m_ListModelsPanel.setLibrary(m_Library);
+  }
+  
+  /**
+   * Gets the cost matrix that is being edited.
+   * 
+   * @return the edited CostMatrix object
+   */
+  public Object getValue() {
+    return m_Library;
+  }
+  
+  /**
+   * Indicates whether the object can be represented graphically. In this case
+   * it can.
+   * 
+   * @return true
+   */
+  public boolean isPaintable() {
+    return true;
+  }
+  
+  /**
+   * Paints a graphical representation of the Object. For the ensemble library
+   * it prints out the working directory as well as the number of models in
+   * the library
+   * 
+   * @param gfx
+   *            the graphics context to draw the representation to
+   * @param box
+   *            the bounds within which the representation should fit.
+   */
+  public void paintValue(Graphics gfx, Rectangle box) {
+    gfx.drawString(m_Library.size() + " models selected", box.x, box.y
+	+ box.height);
+  }
+  
+  /**
+   * Returns the Java code that generates an object the same as the one being
+   * edited. Unfortunately this can't be done in a single line of code, so the
+   * code returned will only build a default cost matrix of the same size.
+   * 
+   * @return the initialization string
+   */
+  public String getJavaInitializationString() {
+    return ("new Library(" + m_Library.size() + ")");
+  }
+  
+  /**
+   * Some objects can be represented as text, but a library cannot.
+   * 
+   * @return null
+   */
+  public String getAsText() {
+    return null;
+  }
+  
+  /**
+   * Some objects can be represented as text, but a library cannot.
+   * 
+   * @param text
+   *            ignored
+   * @throws always throws an IllegalArgumentException
+   */
+  public void setAsText(String text) {
+    throw new IllegalArgumentException("LibraryEditor: "
+	+ "Library properties cannot be " + "expressed as text");
+  }
+  
+  /**
+   * Some objects can return tags, but a cost matrix cannot.
+   * 
+   * @return null
+   */
+  public String[] getTags() {
+    return null;
+  }
+  
+  /**
+   * Gets a GUI component with which the user can edit the cost matrix.
+   * 
+   * @return an editor GUI component
+   */
+  public Component getCustomEditor() {
+    return m_CustomEditor;
+  }
+  
+  /**
+   * Indicates whether the library can be edited in a GUI, which it can.
+   * 
+   * @return true
+   */
+  public boolean supportsCustomEditor() {
+    return true;
+  }
+  
+  /**
+   * Adds an object to the list of those that wish to be informed when the
+   * library changes.
+   * 
+   * @param listener
+   *            a new listener to add to the list
+   */
+  public void addPropertyChangeListener(PropertyChangeListener listener) {
+    m_Library.addPropertyChangeListener(listener);
+  }
+  
+  /**
+   * Removes an object from the list of those that wish to be informed when
+   * the cost matrix changes.
+   * 
+   * @param listener
+   *            the listener to remove from the list
+   */
+  public void removePropertyChangeListener(PropertyChangeListener listener) {
+    m_PropSupport.removePropertyChangeListener(listener);
+  }
+  
+  // ***************************************************************
+  // The following three methods seem sort of out of place here.
+  // Basically they are helper methods for the various classes that
+  // constitute the AddModelsPanel. These static methods needed to
+  // go here because the objects (PropertyText, PropertyPanel, etc)
+  // involved are not visible from the weka.gui.libraryEditor
+  // package where the AddModelsPanel classes reside.  If these 
+  // objects ever became public then these three methods should be
+  // moved within the gui.LibraryEditor package
+  // ***************************************************************
+  
+  /**
+   * This method handles the different object editor types in weka to obtain
+   * their current values.
+   * 
+   * @param source	an Editor
+   * @return		the value of the editor
+   */
+  public static Object getEditorValue(Object source) {
+    
+    Object value = null;
+    
+    if (source instanceof GenericArrayEditor) {
+      value = ((GenericArrayEditor) source).getValue();
+    } else if (source instanceof CostMatrixEditor) {
+      value = ((CostMatrixEditor) source).getValue();
+    } else if (source instanceof PropertyText) {
+      value = ((PropertyText) source).getText();
+    } else if (source instanceof PropertyEditor) {
+      value = ((PropertyEditor) source).getValue();
+    }
+    
+    return value;
+  }
+  
+  /**
+   * This is a helper function that creates a renderer for Default Objects.
+   * These are basically objects that are not numeric, nominal, or generic
+   * objects. These are objects that we don't want to do anything special with
+   * and just display their values as normal. We simply create the editor the
+   * same way that they would have been created in the PropertySheetPanel
+   * class.
+   * 
+   * @param nodeEditor
+   *            the editor created for the defaultNode
+   * @return the Component to dispaly the defaultNode
+   */
+  public static Component getDefaultRenderer(PropertyEditor nodeEditor) {
+    
+    Component genericRenderer = null;
+    
+    if (nodeEditor.isPaintable() && nodeEditor.supportsCustomEditor()) {
+      genericRenderer = new PropertyPanel(nodeEditor);
+    } else if (nodeEditor.getTags() != null) {
+      genericRenderer = new PropertyValueSelector(nodeEditor);
+    } else if (nodeEditor.getAsText() != null) {
+      // String init = editor.getAsText();
+      genericRenderer = new PropertyText(nodeEditor);
+      ((PropertyText) genericRenderer).setColumns(20);
+    } else {
+      System.err.println("Warning: Property \""
+	  + nodeEditor.getClass().toString()
+	  + "\" has non-displayabale editor.  Skipping.");
+    }
+    
+    return genericRenderer;
+  }
+  
+  /**
+   * This is a helper function that creates a renderer for GenericObjects
+   * 
+   * @param classifierEditor
+   *            the editor created for this
+   * @return object renderer
+   */
+  public static Component createGenericObjectRenderer(
+      GenericObjectEditor classifierEditor) {
+    
+    PropertyPanel propertyPanel = new PropertyPanel(classifierEditor);
+    
+    return propertyPanel;
+  }
+  
+  
+  /**
+   * This is a simple main method that lets you run a LibraryEditor
+   * on its own without having to deal with the Explorer, etc... 
+   * This is useful only for building model lists.
+   * 
+   * @param args	the commandline arguments
+   */
+  public static void main(String[] args) {
+    
+    EnsembleLibrary value = new EnsembleLibrary();
+    EnsembleLibraryEditor libraryEditor = new EnsembleLibraryEditor();
+    libraryEditor.setValue(value);
+    
+    JPanel editor = (JPanel) libraryEditor.getCustomEditor();
+    
+    JFrame frame = new JFrame();
+    frame.getContentPane().add(editor);
+    
+    frame.pack();
+    frame.setVisible(true);
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/EnsembleSelectionLibraryEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/EnsembleSelectionLibraryEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/EnsembleSelectionLibraryEditor.java	(revision 29)
@@ -0,0 +1,145 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnsembleSelectionLibraryEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui;
+
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary;
+import weka.gui.ensembleLibraryEditor.AddModelsPanel;
+import weka.gui.ensembleLibraryEditor.DefaultModelsPanel;
+import weka.gui.ensembleLibraryEditor.ListModelsPanel;
+import weka.gui.ensembleLibraryEditor.LoadModelsPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+/**
+ * Class for editing Ensemble Library objects. This basically 
+ * does the same thing as its parent class except that it also
+ * adds two extra panels.  A "loadModelsPanel" to detect model 
+ * specifications from an ensemble working directory and a 
+ * "defaltModelsPanel" which lets you choose sets of model specs 
+ * from a few default lists.  Brings up a custom editing 
+ * panel with which the user can edit the library interactively, as well 
+ * as save load libraries from files.  
+ *
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class EnsembleSelectionLibraryEditor extends EnsembleLibraryEditor {
+  
+  /** An instance of the custom  editor */
+  protected CustomEditor m_CustomEditor = new CustomEditor();
+  
+  /** the panel showing models in a workingDirectory */
+  protected LoadModelsPanel m_LoadModelsPanel;
+  
+  /** The panel showing the defaults */
+  protected DefaultModelsPanel m_DefaultModelsPanel;
+  
+  /**
+   * This class presents a GUI for editing the library, and saving and 
+   * loading model list from files.
+   */
+  private class CustomEditor 
+    extends JPanel {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -3859973750432725901L;
+
+    /**
+     * Constructs a new CustomEditor.
+     *
+     */
+    public CustomEditor() {
+      
+      m_ModelOptionsPane = new JTabbedPane();
+      
+      m_ListModelsPanel = new ListModelsPanel(m_Library);
+      m_ModelOptionsPane.addTab("Library List", m_ListModelsPanel);
+      
+      //Each of the three other panels needs a reference to the main one so that 
+      //they can add models to it.
+      m_ModelOptionsPane.addTab("Add Models", new AddModelsPanel(
+	  m_ListModelsPanel));
+      
+      m_DefaultModelsPanel = new DefaultModelsPanel(m_ListModelsPanel);
+      m_ModelOptionsPane.addTab("Add Default Set", m_DefaultModelsPanel);
+      m_ModelOptionsPane.addChangeListener(m_DefaultModelsPanel);
+      
+      m_LoadModelsPanel = new LoadModelsPanel(m_ListModelsPanel,
+	  EnsembleSelectionLibraryEditor.this);
+      m_ModelOptionsPane.addTab("Load Models", m_LoadModelsPanel);
+      m_ModelOptionsPane.addChangeListener(m_LoadModelsPanel);
+      
+      setLayout(new BorderLayout());
+      add(m_ModelOptionsPane, BorderLayout.CENTER);
+      
+      setPreferredSize(new Dimension(420, 500));
+    }
+  }
+  
+  /**
+   * Constructs a new LibraryEditor.
+   *
+   */
+  public EnsembleSelectionLibraryEditor() {
+    super();
+    
+    m_CustomEditor = new CustomEditor();
+  }
+  
+  /**
+   * Sets the value of the Library to be edited.
+   *
+   * @param value a Library object to be edited
+   */
+  public void setValue(Object value) {
+    super.setValue(value);
+    m_LoadModelsPanel.setLibrary((EnsembleSelectionLibrary) m_Library);
+    
+    ((EnsembleSelectionLibrary) m_Library)
+    .addWorkingDirectoryListener(m_LoadModelsPanel);
+  }
+  
+  /**
+   * Gets a GUI component with which the user can edit the cost matrix.
+   *
+   * @return an editor GUI component
+   */
+  public Component getCustomEditor() {
+    return m_CustomEditor;
+  }
+  
+  /**
+   * returns whether or not the LoadModelsPanel is currently selected
+   * 
+   * @return true if selected
+   */
+  public boolean isLoadModelsPanelSelected() {
+    return m_ModelOptionsPane.getSelectedComponent().equals(
+	m_LoadModelsPanel);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ExtensionFileFilter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ExtensionFileFilter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ExtensionFileFilter.java	(revision 29)
@@ -0,0 +1,124 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ExtensionFileFilter.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import java.io.File;
+import java.io.Serializable;
+import java.io.FilenameFilter;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * Provides a file filter for FileChoosers that accepts or rejects files
+ * based on their extension. Compatible with both java.io.FilenameFilter and
+ * javax.swing.filechooser.FileFilter (why there are two I have no idea).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class ExtensionFileFilter
+  extends FileFilter
+  implements FilenameFilter, Serializable {
+
+  /** The text description of the types of files accepted */
+  protected String m_Description;
+
+  /** The filename extensions of accepted files */
+  protected String[] m_Extension;
+
+  /**
+   * Creates the ExtensionFileFilter
+   *
+   * @param extension the extension of accepted files.
+   * @param description a text description of accepted files.
+   */
+  public ExtensionFileFilter(String extension, String description) {
+    m_Extension = new String [1];
+    m_Extension[0] = extension;
+    m_Description = description;
+  }
+
+  /**
+   * Creates an ExtensionFileFilter that accepts files that have any of
+   * the extensions contained in the supplied array.
+   *
+   * @param extensions an array of acceptable file extensions (as Strings).
+   * @param description a text description of accepted files.
+   */
+  public ExtensionFileFilter(String [] extensions, String description) {
+    m_Extension = extensions;
+    m_Description = description;
+  }
+  
+  /**
+   * Gets the description of accepted files.
+   *
+   * @return the description.
+   */
+  public String getDescription() {
+    
+    return m_Description;
+  }
+  
+  /**
+   * Returns a copy of the acceptable extensions.
+   * 
+   * @return the accepted extensions
+   */
+  public String[] getExtensions() {
+    return (String[]) m_Extension.clone();
+  }
+  
+  /**
+   * Returns true if the supplied file should be accepted (i.e.: if it
+   * has the required extension or is a directory).
+   *
+   * @param file the file of interest.
+   * @return true if the file is accepted by the filter.
+   */
+  public boolean accept(File file) {
+    
+    String name = file.getName().toLowerCase();
+    if (file.isDirectory()) {
+      return true;
+    }
+    for (int i = 0; i < m_Extension.length; i++) {
+      if (name.endsWith(m_Extension[i])) {
+	return true;
+      }
+    }
+    return false;
+  }
+  
+  /**
+   * Returns true if the file in the given directory with the given name
+   * should be accepted.
+   *
+   * @param dir the directory where the file resides.
+   * @param name the name of the file.
+   * @return true if the file is accepted.
+   */
+  public boolean accept(File dir, String name) {
+    return accept(new File(dir, name));
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/FileEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/FileEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/FileEditor.java	(revision 29)
@@ -0,0 +1,143 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FileEditor.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.FontMetrics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyEditorSupport;
+import java.io.File;
+
+import javax.swing.JFileChooser;
+
+
+/** 
+ * A PropertyEditor for File objects that lets the user select a file.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5204 $
+ */
+public class FileEditor extends PropertyEditorSupport {
+
+  /** The file chooser used for selecting files. */
+  protected JFileChooser m_FileChooser;
+  
+  /**
+   * Returns a representation of the current property value as java source.
+   *
+   * @return a value of type 'String'
+   */
+  public String getJavaInitializationString() {
+
+    File f = (File) getValue();
+    if (f == null) {
+      return "null";
+    }
+    return "new File(\"" + f.getName() + "\")";
+  }
+
+  /**
+   * Returns true because we do support a custom editor.
+   *
+   * @return true
+   */
+  public boolean supportsCustomEditor() {
+    return true;
+  }
+  
+  /**
+   * Gets the custom editor component.
+   *
+   * @return a value of type 'java.awt.Component'
+   */
+  public java.awt.Component getCustomEditor() {
+
+    if (m_FileChooser == null) {
+      File currentFile = (File) getValue();
+      if (currentFile != null) {
+	m_FileChooser 
+	  = new JFileChooser();
+	m_FileChooser.setSelectedFile(currentFile);
+      } else {
+	m_FileChooser 
+	  = new JFileChooser(new File(System.getProperty("user.dir")));
+      }
+      m_FileChooser.setApproveButtonText("Select");
+      m_FileChooser.setApproveButtonMnemonic('S');
+      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+      m_FileChooser.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  String cmdString = e.getActionCommand();
+	  if (cmdString.equals(JFileChooser.APPROVE_SELECTION)) {
+	    File newVal = m_FileChooser.getSelectedFile();
+	    setValue(newVal);
+	  }
+	  closeDialog();
+	}
+      });
+    }
+    return m_FileChooser;
+  }
+
+  /**
+   * Returns true since this editor is paintable.
+   *
+   * @return true.
+   */
+  public boolean isPaintable() {
+    return true;
+  }
+
+  /**
+   * Paints a representation of the current Object.
+   *
+   * @param gfx the graphics context to use
+   * @param box the area we are allowed to paint into
+   */
+  public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
+
+    FontMetrics fm = gfx.getFontMetrics();
+    int vpad = (box.height - fm.getHeight()) / 2 ;
+    File f = (File) getValue();
+    String val = "No file";
+    if (f != null) {
+      val = f.getName();
+    }
+    gfx.drawString(val, 2, fm.getHeight() + vpad);
+  }  
+  
+  /**
+   * Closes the dialog.
+   */
+  protected void closeDialog() {
+    if (m_FileChooser instanceof Container) {
+      Dialog dlg = PropertyDialog.getParentDialog((Container) m_FileChooser);
+      if (dlg != null)
+	dlg.setVisible(false);
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/GUIChooser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GUIChooser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GUIChooser.java	(revision 29)
@@ -0,0 +1,1422 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GUIChooser.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.classifiers.bayes.net.GUI;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.Copyright;
+import weka.core.Instances;
+import weka.core.Memory;
+import weka.core.SystemInfo;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.scripting.Groovy;
+import weka.core.scripting.Jython;
+import weka.gui.arffviewer.ArffViewer;
+import weka.gui.beans.KnowledgeFlow;
+import weka.gui.beans.KnowledgeFlowApp;
+import weka.gui.boundaryvisualizer.BoundaryVisualizer;
+import weka.gui.experiment.Experimenter;
+import weka.gui.explorer.Explorer;
+import weka.gui.graphvisualizer.GraphVisualizer;
+import weka.gui.scripting.GroovyPanel;
+import weka.gui.scripting.JythonPanel;
+import weka.gui.sql.SqlViewer;
+import weka.gui.treevisualizer.Node;
+import weka.gui.treevisualizer.NodePlace;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeBuild;
+import weka.gui.treevisualizer.TreeVisualizer;
+import weka.gui.visualize.PlotData2D;
+import weka.gui.visualize.ThresholdVisualizePanel;
+import weka.gui.visualize.VisualizePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.LayoutManager;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+
+/** 
+ * The main class for the Weka GUIChooser. Lets the user choose
+ * which GUI they want to run.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5837 $
+ */
+public class GUIChooser
+  extends JFrame {
+
+  /** for serialization */
+  private static final long serialVersionUID = 9001529425230247914L;
+
+  /** the GUIChooser itself */
+  protected GUIChooser m_Self;
+  
+  // Menu stuff
+  private JMenuBar m_jMenuBar;
+  private JMenu m_jMenuProgram;
+  private JMenu m_jMenuVisualization;
+  private JMenu m_jMenuTools;
+  private JMenu m_jMenuHelp;
+  
+  // Applications
+
+  /** the panel for the application buttons */
+  protected JPanel m_PanelApplications = new JPanel();
+  
+  /** Click to open the Explorer */
+  protected JButton m_ExplorerBut = new JButton("Explorer");
+
+  /** The frame containing the explorer interface */
+  protected JFrame m_ExplorerFrame;
+
+  /** Click to open the Explorer */
+  protected JButton m_ExperimenterBut = new JButton("Experimenter");
+
+  /** The frame containing the experiment interface */
+  protected JFrame m_ExperimenterFrame;
+
+  /** Click to open the KnowledgeFlow */
+  protected JButton m_KnowledgeFlowBut = new JButton("KnowledgeFlow");
+
+  /** The frame containing the knowledge flow interface */
+  protected JFrame m_KnowledgeFlowFrame;
+
+  /** Click to open the simplecli */
+  protected JButton m_SimpleBut = new JButton("Simple CLI");
+  
+  /** The SimpleCLI */
+  protected SimpleCLI m_SimpleCLI;
+
+  /** The frame containing the Groovy console. */
+  protected JFrame m_GroovyConsoleFrame;
+
+  /** The frame containing the Jython console. */
+  protected JFrame m_JythonConsoleFrame;
+
+  /** keeps track of the opened ArffViewer instancs */
+  protected Vector m_ArffViewers = new Vector();
+
+  /** The frame containing the SqlViewer */
+  protected JFrame m_SqlViewerFrame;
+  
+  /** The frame containing the Bayes net GUI */
+  protected JFrame m_BayesNetGUIFrame;
+  
+  /** The frame containing the ensemble library interface */
+  protected JFrame m_EnsembleLibraryFrame;
+
+  // Visualization
+
+  /** keeps track of the opened plots */
+  protected Vector m_Plots = new Vector();
+
+  /** keeps track of the opened ROCs */
+  protected Vector m_ROCs = new Vector();
+
+  /** keeps track of the opened tree visualizer instancs */
+  protected Vector m_TreeVisualizers = new Vector();
+
+  /** keeps track of the opened graph visualizer instancs */
+  protected Vector m_GraphVisualizers = new Vector();
+
+  /** The frame containing the boundary visualizer */
+  protected JFrame m_BoundaryVisualizerFrame;
+  
+  // Help
+
+  /** The frame containing the system info */
+  protected JFrame m_SystemInfoFrame;
+  
+  // Other
+
+  /** The frame containing the memory usage */
+  protected JFrame m_MemoryUsageFrame;
+
+  /** The frame of the LogWindow */
+  protected static LogWindow m_LogWindow = new LogWindow();
+
+  /** The weka image */
+  Image m_weka = Toolkit.getDefaultToolkit().
+    getImage(ClassLoader.getSystemResource("weka/gui/images/weka_background.gif"));
+
+  /** filechooser for the TreeVisualizer */
+  protected JFileChooser m_FileChooserTreeVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for the GraphVisualizer */
+  protected JFileChooser m_FileChooserGraphVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for Plots */
+  protected JFileChooser m_FileChooserPlot = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for ROC curves */
+  protected JFileChooser m_FileChooserROC = new JFileChooser(new File(System.getProperty("user.dir")));
+  
+  /** the icon for the frames */
+  protected Image m_Icon;
+  
+  /** contains the child frames (title &lt;-&gt; object). */
+  protected HashSet<Container> m_ChildFrames = new HashSet<Container>();
+  
+  /**
+   * Creates the experiment environment gui with no initial experiment
+   */
+  public GUIChooser() {
+
+    super("Weka GUI Chooser");
+    
+    m_Self = this;
+
+    // filechoosers
+    m_FileChooserGraphVisualizer.addChoosableFileFilter(
+	  new ExtensionFileFilter(".bif", "BIF Files (*.bif)"));
+    m_FileChooserGraphVisualizer.addChoosableFileFilter(
+	  new ExtensionFileFilter(".xml", "XML Files (*.xml)"));
+
+    m_FileChooserPlot.addChoosableFileFilter(
+	  new ExtensionFileFilter(
+	      Instances.FILE_EXTENSION,
+	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));
+    m_FileChooserPlot.setMultiSelectionEnabled(true);
+    
+    m_FileChooserROC.addChoosableFileFilter(
+	  new ExtensionFileFilter(
+	      Instances.FILE_EXTENSION,
+	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));
+
+    // general layout
+    m_Icon = Toolkit.getDefaultToolkit().getImage(
+	ClassLoader.getSystemResource("weka/gui/weka_icon.gif"));
+    setIconImage(m_Icon);
+    this.getContentPane().setLayout(new BorderLayout());
+    
+    this.getContentPane().add(m_PanelApplications, BorderLayout.EAST);
+    
+    // applications
+    m_PanelApplications.setBorder(BorderFactory.createTitledBorder("Applications"));
+    m_PanelApplications.setLayout(new GridLayout(4, 1));
+    m_PanelApplications.add(m_ExplorerBut);
+    m_PanelApplications.add(m_ExperimenterBut);
+    m_PanelApplications.add(m_KnowledgeFlowBut);
+    m_PanelApplications.add(m_SimpleBut);
+    
+    // Weka image plus copyright info
+    JPanel wekaPan = new JPanel();
+    wekaPan.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    wekaPan.setLayout(new BorderLayout());
+    wekaPan.setToolTipText("Weka, a native bird of New Zealand");
+    ImageIcon wii = new ImageIcon(m_weka);
+    JLabel wekaLab = new JLabel(wii);
+    wekaPan.add(wekaLab, BorderLayout.CENTER);
+    String infoString = "<html>"
+      + "<font size=-2>"
+      + "Waikato Environment for Knowledge Analysis<br>"
+      + "Version " + Version.VERSION + "<br>"
+      + "(c) " + Copyright.getFromYear() + " - " + Copyright.getToYear() + "<br>"
+      + Copyright.getOwner() + "<br>"
+      + Copyright.getAddress()
+      + "</font>"
+      + "</html>";
+    JLabel infoLab = new JLabel(infoString);
+    infoLab.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    wekaPan.add(infoLab, BorderLayout.SOUTH);
+    
+    this.getContentPane().add(wekaPan, BorderLayout.CENTER);
+
+    // Menu bar
+    m_jMenuBar = new JMenuBar();
+    
+    // Program
+    m_jMenuProgram = new JMenu();
+    m_jMenuBar.add(m_jMenuProgram);
+    m_jMenuProgram.setText("Program");
+    m_jMenuProgram.setMnemonic('P');
+    
+    // Program/LogWindow
+    JMenuItem jMenuItemProgramLogWindow = new JMenuItem();
+    m_jMenuProgram.add(jMenuItemProgramLogWindow);
+    jMenuItemProgramLogWindow.setText("LogWindow");
+    //jMenuItemProgramLogWindow.setMnemonic('L');
+    jMenuItemProgramLogWindow.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK));
+    m_LogWindow.setIconImage(m_Icon);
+    jMenuItemProgramLogWindow.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_LogWindow.setVisible(true);
+      }
+    });
+    
+    final JMenuItem jMenuItemProgramMemUsage = new JMenuItem();
+    m_jMenuProgram.add(jMenuItemProgramMemUsage);
+    jMenuItemProgramMemUsage.setText("Memory usage");
+    //jMenuItemProgramMemUsage.setMnemonic('M');
+    jMenuItemProgramMemUsage.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_MASK));
+    
+    jMenuItemProgramMemUsage.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_MemoryUsageFrame == null) {
+          final MemoryUsagePanel panel = new MemoryUsagePanel(); 
+          jMenuItemProgramMemUsage.setEnabled(false);
+          m_MemoryUsageFrame = new JFrame("Memory usage");
+          m_MemoryUsageFrame.setIconImage(m_Icon);
+          m_MemoryUsageFrame.getContentPane().setLayout(new BorderLayout());
+          m_MemoryUsageFrame.getContentPane().add(panel, BorderLayout.CENTER);
+          m_MemoryUsageFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              panel.stopMonitoring();
+              m_MemoryUsageFrame.dispose();
+              m_MemoryUsageFrame = null;
+              jMenuItemProgramMemUsage.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_MemoryUsageFrame.pack();
+          m_MemoryUsageFrame.setSize(400, 50);
+          Point l = panel.getFrameLocation();
+          if ((l.x != -1) && (l.y != -1))
+            m_MemoryUsageFrame.setLocation(l);
+          m_MemoryUsageFrame.setVisible(true);
+          Dimension size = m_MemoryUsageFrame.getPreferredSize();
+          m_MemoryUsageFrame.setSize(new Dimension((int) size.getWidth(), (int) size.getHeight()));
+        }
+      }
+    });
+    
+    m_jMenuProgram.add(new JSeparator());
+    
+    // Program/Exit
+    JMenuItem jMenuItemProgramExit = new JMenuItem();
+    m_jMenuProgram.add(jMenuItemProgramExit);
+    jMenuItemProgramExit.setText("Exit");
+//    jMenuItemProgramExit.setMnemonic('E');
+    jMenuItemProgramExit.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_MASK));
+    jMenuItemProgramExit.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        dispose();
+        checkExit();
+      }
+    });
+    
+    // Visualization
+    m_jMenuVisualization = new JMenu();
+    m_jMenuBar.add(m_jMenuVisualization);
+    m_jMenuVisualization.setText("Visualization");
+    m_jMenuVisualization.setMnemonic('V');
+    
+    // Visualization/Plot
+    JMenuItem jMenuItemVisualizationPlot = new JMenuItem();
+    m_jMenuVisualization.add(jMenuItemVisualizationPlot);
+    jMenuItemVisualizationPlot.setText("Plot");
+    //jMenuItemVisualizationPlot.setMnemonic('P');
+    jMenuItemVisualizationPlot.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_MASK));
+    
+    jMenuItemVisualizationPlot.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // choose file
+        int retVal = m_FileChooserPlot.showOpenDialog(m_Self);
+        if (retVal != JFileChooser.APPROVE_OPTION)
+          return;
+
+        // build plot
+        VisualizePanel panel = new VisualizePanel();
+        String filenames = "";
+        File[] files = m_FileChooserPlot.getSelectedFiles();
+        for (int j = 0; j < files.length; j++) {
+          String filename = files[j].getAbsolutePath();
+          if (j > 0)
+            filenames += ", ";
+          filenames += filename;
+          System.err.println("Loading instances from " + filename);
+          try {
+            Reader r = new java.io.BufferedReader(new FileReader(filename));
+            Instances i = new Instances(r);
+            i.setClassIndex(i.numAttributes()-1);
+            PlotData2D pd1 = new PlotData2D(i);
+
+            if (j == 0) {
+              pd1.setPlotName("Master plot");
+              panel.setMasterPlot(pd1);
+            } else {
+              pd1.setPlotName("Plot "+(j+1));
+              pd1.m_useCustomColour = true;
+              pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; 
+              panel.addPlot(pd1);
+            }
+          }
+          catch (Exception ex) {
+            ex.printStackTrace();
+            JOptionPane.showMessageDialog(
+                m_Self, "Error loading file '" + files[j] + "':\n" + ex.getMessage());
+            return;
+          }
+        }
+
+        // create frame
+        final JFrame frame = new JFrame("Plot - " + filenames);
+        frame.setIconImage(m_Icon);
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(panel, BorderLayout.CENTER);
+        frame.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {
+            m_Plots.remove(frame);
+            frame.dispose();
+            checkExit();
+          }
+        });
+        frame.pack();
+        frame.setSize(800, 600);
+        frame.setVisible(true);
+        m_Plots.add(frame);
+      }
+    });
+    
+    // Visualization/ROC
+    JMenuItem jMenuItemVisualizationROC = new JMenuItem();
+    m_jMenuVisualization.add(jMenuItemVisualizationROC);
+    jMenuItemVisualizationROC.setText("ROC");
+    // jMenuItemVisualizationROC.setMnemonic('R');
+    jMenuItemVisualizationROC.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK));
+    
+    jMenuItemVisualizationROC.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // choose file
+        int retVal = m_FileChooserROC.showOpenDialog(m_Self);
+        if (retVal != JFileChooser.APPROVE_OPTION)
+          return;
+
+        // create plot
+        String filename  = m_FileChooserROC.getSelectedFile().getAbsolutePath();
+        Instances result = null;
+        try {
+          result = new Instances(new BufferedReader(new FileReader(filename)));
+        }
+        catch (Exception ex) {
+          ex.printStackTrace();
+          JOptionPane.showMessageDialog(
+              m_Self, "Error loading file '" + filename + "':\n" + ex.getMessage());
+          return;
+        }
+        result.setClassIndex(result.numAttributes() - 1);
+        ThresholdVisualizePanel vmc = new ThresholdVisualizePanel();
+        vmc.setROCString("(Area under ROC = " + 
+            Utils.doubleToString(ThresholdCurve.getROCArea(result), 4) + ")");
+        vmc.setName(result.relationName());
+        PlotData2D tempd = new PlotData2D(result);
+        tempd.setPlotName(result.relationName());
+        tempd.addInstanceNumberAttribute();
+        try {
+          vmc.addPlot(tempd);
+        }
+        catch (Exception ex) {
+          ex.printStackTrace();
+          JOptionPane.showMessageDialog(
+              m_Self, "Error adding plot:\n" + ex.getMessage());
+          return;
+        }
+
+        final JFrame frame = new JFrame("ROC - " + filename);
+        frame.setIconImage(m_Icon);
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(vmc, BorderLayout.CENTER);
+        frame.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {
+            m_ROCs.remove(frame);
+            frame.dispose();
+            checkExit();
+          }
+        });
+        frame.pack();
+        frame.setSize(800, 600);
+        frame.setVisible(true);
+        m_ROCs.add(frame);
+      }
+    });
+    
+    // Visualization/TreeVisualizer
+    JMenuItem jMenuItemVisualizationTree = new JMenuItem();
+    m_jMenuVisualization.add(jMenuItemVisualizationTree);
+    jMenuItemVisualizationTree.setText("TreeVisualizer");
+    // jMenuItemVisualizationTree.setMnemonic('T');
+    jMenuItemVisualizationTree.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_MASK));
+    
+    jMenuItemVisualizationTree.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // choose file
+        int retVal = m_FileChooserTreeVisualizer.showOpenDialog(m_Self);
+        if (retVal != JFileChooser.APPROVE_OPTION)
+          return;
+
+        // build tree
+        String filename = m_FileChooserTreeVisualizer.getSelectedFile().getAbsolutePath();
+        TreeBuild builder = new TreeBuild();
+        Node top = null;
+        NodePlace arrange = new PlaceNode2();
+        try {
+          top = builder.create(new FileReader(filename));
+        }
+        catch (Exception ex) {
+          ex.printStackTrace();
+          JOptionPane.showMessageDialog(
+              m_Self, "Error loading file '" + filename + "':\n" + ex.getMessage());
+          return;
+        }
+
+        // create frame
+        final JFrame frame = new JFrame("TreeVisualizer - " + filename);
+        frame.setIconImage(m_Icon);
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(new TreeVisualizer(null, top, arrange), BorderLayout.CENTER);
+        frame.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {
+            m_TreeVisualizers.remove(frame);
+            frame.dispose();
+            checkExit();
+          }
+        });
+        frame.pack();
+        frame.setSize(800, 600);
+        frame.setVisible(true);
+        m_TreeVisualizers.add(frame);
+      }
+    });
+    
+    // Visualization/GraphVisualizer
+    JMenuItem jMenuItemVisualizationGraph = new JMenuItem();
+    m_jMenuVisualization.add(jMenuItemVisualizationGraph);
+    jMenuItemVisualizationGraph.setText("GraphVisualizer");
+ //   jMenuItemVisualizationGraph.setMnemonic('G');
+    jMenuItemVisualizationGraph.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.CTRL_MASK));
+    
+    jMenuItemVisualizationGraph.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // choose file
+        int retVal = m_FileChooserGraphVisualizer.showOpenDialog(m_Self);
+        if (retVal != JFileChooser.APPROVE_OPTION)
+          return;
+
+        // build graph
+        String filename = m_FileChooserGraphVisualizer.getSelectedFile().getAbsolutePath();
+        GraphVisualizer panel = new GraphVisualizer();
+        try{
+          if (    filename.toLowerCase().endsWith(".xml") 
+              || filename.toLowerCase().endsWith(".bif") ) {
+            panel.readBIF(new FileInputStream(filename));
+          }
+          else {
+            panel.readDOT(new FileReader(filename));
+          }
+        }
+        catch (Exception ex) {
+          ex.printStackTrace();
+          JOptionPane.showMessageDialog(
+              m_Self, "Error loading file '" + filename + "':\n" + ex.getMessage());
+          return;
+        }
+
+        // create frame
+        final JFrame frame = new JFrame("GraphVisualizer - " + filename);
+        frame.setIconImage(m_Icon);
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(panel, BorderLayout.CENTER);
+        frame.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {
+            m_GraphVisualizers.remove(frame);
+            frame.dispose();
+            checkExit();
+          }
+        });
+        frame.pack();
+        frame.setSize(800, 600);
+        frame.setVisible(true);
+        m_GraphVisualizers.add(frame);
+      }
+    });
+    
+    // Visualization/BoundaryVisualizer
+    final JMenuItem jMenuItemVisualizationBoundary = new JMenuItem();
+    m_jMenuVisualization.add(jMenuItemVisualizationBoundary);
+    jMenuItemVisualizationBoundary.setText("BoundaryVisualizer");
+    // jMenuItemVisualizationBoundary.setMnemonic('B');
+    jMenuItemVisualizationBoundary.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK));
+    
+    jMenuItemVisualizationBoundary.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_BoundaryVisualizerFrame == null) {
+          jMenuItemVisualizationBoundary.setEnabled(false);
+          m_BoundaryVisualizerFrame = new JFrame("BoundaryVisualizer");
+          m_BoundaryVisualizerFrame.setIconImage(m_Icon);
+          m_BoundaryVisualizerFrame.getContentPane().setLayout(new BorderLayout());
+          final BoundaryVisualizer bv = new BoundaryVisualizer();
+          m_BoundaryVisualizerFrame.getContentPane().add(bv, BorderLayout.CENTER);
+          m_BoundaryVisualizerFrame.setSize(bv.getMinimumSize());
+          m_BoundaryVisualizerFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              bv.stopPlotting();
+              m_BoundaryVisualizerFrame.dispose();
+              m_BoundaryVisualizerFrame = null;
+              jMenuItemVisualizationBoundary.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_BoundaryVisualizerFrame.pack();
+          //m_BoundaryVisualizerFrame.setSize(800, 600);
+          m_BoundaryVisualizerFrame.setResizable(false);
+          m_BoundaryVisualizerFrame.setVisible(true);
+          // dont' do a System.exit after last window got closed!
+          BoundaryVisualizer.setExitIfNoWindowsOpen(false);
+        }
+      }
+    });
+    
+    // Extensions
+    JMenu jMenuExtensions = new JMenu("Extensions");
+    jMenuExtensions.setMnemonic(java.awt.event.KeyEvent.VK_E);
+    m_jMenuBar.add(jMenuExtensions);
+    jMenuExtensions.setVisible(false);
+    
+    String extensions = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
+        MainMenuExtension.class.getName(), "");
+
+    if (extensions.length() > 0) {
+      jMenuExtensions.setVisible(true);
+      String[] classnames = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
+          MainMenuExtension.class.getName(), "").split(",");
+      Hashtable<String,JMenu> submenus = new Hashtable<String,JMenu>();
+
+      // add all extensions
+      for (int i = 0; i < classnames.length; i++) {
+        String classname = classnames[i];
+        try {
+          MainMenuExtension ext = (MainMenuExtension) Class.forName(classname).newInstance();
+
+          // menuitem in a submenu?
+          JMenu submenu = null;
+          if (ext.getSubmenuTitle() != null) {
+            submenu = submenus.get(ext.getSubmenuTitle());
+            if (submenu == null) {
+              submenu = new JMenu(ext.getSubmenuTitle());
+              submenus.put(ext.getSubmenuTitle(), submenu);
+              insertMenuItem(jMenuExtensions, submenu);
+            }
+          }
+
+          // create menu item
+          JMenuItem menuitem = new JMenuItem();
+          menuitem.setText(ext.getMenuTitle());
+          // does the extension need a frame or does it have its own ActionListener?
+          ActionListener listener = ext.getActionListener(m_Self);
+          if (listener != null) {
+            menuitem.addActionListener(listener);
+          }
+          else {
+            final JMenuItem finalMenuitem = menuitem;
+            final MainMenuExtension finalExt = ext;
+            menuitem.addActionListener(new ActionListener() {
+              public void actionPerformed(ActionEvent e) {
+                Component frame = createFrame(
+                    m_Self, finalMenuitem.getText(), 
+                    null, null, null, -1, -1, null, false, false);
+                finalExt.fillFrame(frame);
+                frame.setVisible(true);
+              }
+            });
+          }
+
+          // sorted insert of menu item
+          if (submenu != null)
+            insertMenuItem(submenu, menuitem);
+          else
+            insertMenuItem(jMenuExtensions, menuitem);
+        }
+        catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    }
+    
+    // Tools
+    m_jMenuTools = new JMenu();
+    m_jMenuBar.add(m_jMenuTools);
+    m_jMenuTools.setText("Tools");
+    m_jMenuTools.setMnemonic('T');
+    
+    // Tools/ArffViewer
+    JMenuItem jMenuItemToolsArffViewer = new JMenuItem();
+    m_jMenuTools.add(jMenuItemToolsArffViewer);
+    jMenuItemToolsArffViewer.setText("ArffViewer");
+    // jMenuItemToolsArffViewer.setMnemonic('A');
+    jMenuItemToolsArffViewer.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK));
+    
+    jMenuItemToolsArffViewer.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        final ArffViewer av = new ArffViewer();
+        av.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent w) {
+            m_ArffViewers.remove(av);
+            checkExit();
+          }
+        });
+        av.setVisible(true);
+        m_ArffViewers.add(av);
+      }
+    });
+    
+    // Tools/SqlViewer
+    final JMenuItem jMenuItemToolsSql = new JMenuItem();
+    m_jMenuTools.add(jMenuItemToolsSql);
+    jMenuItemToolsSql.setText("SqlViewer");
+    // jMenuItemToolsSql.setMnemonic('S');
+    jMenuItemToolsSql.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
+    
+    jMenuItemToolsSql.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_SqlViewerFrame == null) {
+          jMenuItemToolsSql.setEnabled(false);
+          final SqlViewer sql = new SqlViewer(null);
+          m_SqlViewerFrame = new JFrame("SqlViewer");
+          m_SqlViewerFrame.setIconImage(m_Icon);
+          m_SqlViewerFrame.getContentPane().setLayout(new BorderLayout());
+          m_SqlViewerFrame.getContentPane().add(sql, BorderLayout.CENTER);
+          m_SqlViewerFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              sql.saveSize();
+              m_SqlViewerFrame.dispose();
+              m_SqlViewerFrame = null;
+              jMenuItemToolsSql.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_SqlViewerFrame.pack();
+          m_SqlViewerFrame.setVisible(true);
+        }
+      }
+    });
+    
+    // Tools/Bayes net editor
+    final JMenuItem jMenuItemBayesNet = new JMenuItem();
+    m_jMenuTools.add(jMenuItemBayesNet);
+    jMenuItemBayesNet.setText("Bayes net editor");
+    jMenuItemBayesNet.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));
+    jMenuItemBayesNet.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_BayesNetGUIFrame == null) {
+          jMenuItemBayesNet.setEnabled(false);
+          final GUI bayesNetGUI = new GUI();
+          JMenuBar bayesBar = bayesNetGUI.getMenuBar();
+          m_BayesNetGUIFrame = new JFrame("Bayes Network Editor");
+          m_BayesNetGUIFrame.setIconImage(m_Icon);
+          m_BayesNetGUIFrame.setJMenuBar(bayesBar);
+          m_BayesNetGUIFrame.getContentPane().add(bayesNetGUI, BorderLayout.CENTER);
+          m_BayesNetGUIFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              m_BayesNetGUIFrame.dispose();
+              m_BayesNetGUIFrame = null;
+              jMenuItemBayesNet.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_BayesNetGUIFrame.setSize(800, 600);
+          m_BayesNetGUIFrame.setVisible(true);
+        }
+      }
+    });
+    
+    // Tools/Groovy console
+    if (Groovy.isPresent()) {
+      final JMenuItem jMenuItemGroovyConsole = new JMenuItem();
+      m_jMenuTools.add(jMenuItemGroovyConsole);
+      jMenuItemGroovyConsole.setText("Groovy console");
+      jMenuItemGroovyConsole.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.CTRL_MASK));
+      jMenuItemGroovyConsole.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_BayesNetGUIFrame == null) {
+	    jMenuItemGroovyConsole.setEnabled(false);
+	    final GroovyPanel groovyPanel = new GroovyPanel();
+	    m_GroovyConsoleFrame = new JFrame(groovyPanel.getPlainTitle());
+	    m_GroovyConsoleFrame.setIconImage(m_Icon);
+	    m_GroovyConsoleFrame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+	    m_GroovyConsoleFrame.setJMenuBar(groovyPanel.getMenuBar());
+	    m_GroovyConsoleFrame.getContentPane().add(groovyPanel, BorderLayout.CENTER);
+	    m_GroovyConsoleFrame.addWindowListener(new WindowAdapter() {
+	      public void windowClosed(WindowEvent w) {
+		m_GroovyConsoleFrame = null;
+		jMenuItemGroovyConsole.setEnabled(true);
+		checkExit();
+	      }
+	    });
+	    m_GroovyConsoleFrame.setSize(800, 600);
+	    m_GroovyConsoleFrame.setVisible(true);
+	  }
+	}
+    });
+    }
+    
+    // Tools/Jython console
+    if (Jython.isPresent()) {
+      final JMenuItem jMenuItemJythonConsole = new JMenuItem();
+      m_jMenuTools.add(jMenuItemJythonConsole);
+      jMenuItemJythonConsole.setText("Jython console");
+      jMenuItemJythonConsole.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_J, KeyEvent.CTRL_MASK));
+      jMenuItemJythonConsole.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_BayesNetGUIFrame == null) {
+	    jMenuItemJythonConsole.setEnabled(false);
+	    final JythonPanel jythonPanel = new JythonPanel();
+	    m_JythonConsoleFrame = new JFrame(jythonPanel.getPlainTitle());
+	    m_JythonConsoleFrame.setIconImage(m_Icon);
+	    m_JythonConsoleFrame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+	    m_JythonConsoleFrame.setJMenuBar(jythonPanel.getMenuBar());
+	    m_JythonConsoleFrame.getContentPane().add(jythonPanel, BorderLayout.CENTER);
+	    m_JythonConsoleFrame.addWindowListener(new WindowAdapter() {
+	      public void windowClosed(WindowEvent w) {
+		m_JythonConsoleFrame = null;
+		jMenuItemJythonConsole.setEnabled(true);
+		checkExit();
+	      }
+	    });
+	    m_JythonConsoleFrame.setSize(800, 600);
+	    m_JythonConsoleFrame.setVisible(true);
+	  }
+	}
+    });
+    }
+    
+    // Help
+    m_jMenuHelp = new JMenu();
+    m_jMenuBar.add(m_jMenuHelp);
+    m_jMenuHelp.setText("Help");
+    m_jMenuHelp.setMnemonic('H');
+    
+    // Help/Homepage
+    JMenuItem jMenuItemHelpHomepage = new JMenuItem();
+    m_jMenuHelp.add(jMenuItemHelpHomepage);
+    jMenuItemHelpHomepage.setText("Weka homepage");
+    // jMenuItemHelpHomepage.setMnemonic('H');
+    jMenuItemHelpHomepage.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_MASK));
+    jMenuItemHelpHomepage.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        BrowserHelper.openURL("http://www.cs.waikato.ac.nz/~ml/weka/");
+      }
+    });
+    
+    m_jMenuHelp.add(new JSeparator());
+    
+    // Help/WekaWiki
+    JMenuItem jMenuItemHelpWekaWiki = new JMenuItem();
+    m_jMenuHelp.add(jMenuItemHelpWekaWiki);
+    jMenuItemHelpWekaWiki.setText("HOWTOs, code snippets, etc.");
+    // jMenuItemHelpWekaWiki.setMnemonic('W');
+    jMenuItemHelpWekaWiki.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_MASK));
+    
+    jMenuItemHelpWekaWiki.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        BrowserHelper.openURL("http://weka.wikispaces.com/");
+      }
+    });
+    
+    // Help/Sourceforge
+    JMenuItem jMenuItemHelpSourceforge = new JMenuItem();
+    m_jMenuHelp.add(jMenuItemHelpSourceforge);
+    jMenuItemHelpSourceforge.setText("Weka on Sourceforge");
+//    jMenuItemHelpSourceforge.setMnemonic('F');
+    jMenuItemHelpSourceforge.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK));
+    
+    jMenuItemHelpSourceforge.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        BrowserHelper.openURL("http://sourceforge.net/projects/weka/");
+      }
+    });
+    
+    // Help/SystemInfo
+    final JMenuItem jMenuItemHelpSysInfo = new JMenuItem();
+    m_jMenuHelp.add(jMenuItemHelpSysInfo);
+    jMenuItemHelpSysInfo.setText("SystemInfo");
+//    jMenuItemHelpSysInfo.setMnemonic('S');
+    jMenuItemHelpSysInfo.
+      setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_MASK));
+    
+    jMenuItemHelpSysInfo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_SystemInfoFrame == null) {
+          jMenuItemHelpSysInfo.setEnabled(false);
+          m_SystemInfoFrame = new JFrame("SystemInfo");
+          m_SystemInfoFrame.setIconImage(m_Icon);
+          m_SystemInfoFrame.getContentPane().setLayout(new BorderLayout());
+
+          // get info
+          Hashtable info = new SystemInfo().getSystemInfo();
+
+          // sort names
+          Vector names = new Vector();
+          Enumeration enm = info.keys();
+          while (enm.hasMoreElements())
+            names.add(enm.nextElement());
+          Collections.sort(names);
+
+          // generate table
+          String[][] data = new String[info.size()][2];
+          for (int i = 0; i < names.size(); i++) {
+            data[i][0] = names.get(i).toString();
+            data[i][1] = info.get(data[i][0]).toString();
+          }
+          String[] titles = new String[]{"Key", "Value"};
+          JTable table = new JTable(data, titles);
+
+          m_SystemInfoFrame.getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
+          m_SystemInfoFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              m_SystemInfoFrame.dispose();
+              m_SystemInfoFrame = null;
+              jMenuItemHelpSysInfo.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_SystemInfoFrame.pack();
+          m_SystemInfoFrame.setSize(800, 600);
+          m_SystemInfoFrame.setVisible(true);
+        }
+      }
+    });
+    
+    
+    // applications
+
+    m_ExplorerBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	if (m_ExplorerFrame == null) {
+	  m_ExplorerBut.setEnabled(false);
+	  m_ExplorerFrame = new JFrame("Weka Explorer");
+	  m_ExplorerFrame.setIconImage(m_Icon);
+	  m_ExplorerFrame.getContentPane().setLayout(new BorderLayout());
+	  m_ExplorerFrame.getContentPane().add(new Explorer(), BorderLayout.CENTER);
+	  m_ExplorerFrame.addWindowListener(new WindowAdapter() {
+	    public void windowClosing(WindowEvent w) {
+	      m_ExplorerFrame.dispose();
+	      m_ExplorerFrame = null;
+	      m_ExplorerBut.setEnabled(true);
+	      checkExit();
+	    }
+	  });
+	  m_ExplorerFrame.pack();
+	  m_ExplorerFrame.setSize(800, 600);
+	  m_ExplorerFrame.setVisible(true);
+	}
+      }
+    });
+
+    m_ExperimenterBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	if (m_ExperimenterFrame == null) {
+	  m_ExperimenterBut.setEnabled(false);
+	  m_ExperimenterFrame = new JFrame("Weka Experiment Environment");
+	  m_ExperimenterFrame.setIconImage(m_Icon);
+	  m_ExperimenterFrame.getContentPane().setLayout(new BorderLayout());
+	  m_ExperimenterFrame.getContentPane()
+	    .add(new Experimenter(false), BorderLayout.CENTER);
+	  m_ExperimenterFrame.addWindowListener(new WindowAdapter() {
+	    public void windowClosing(WindowEvent w) {
+	      m_ExperimenterFrame.dispose();
+	      m_ExperimenterFrame = null;
+	      m_ExperimenterBut.setEnabled(true);
+	      checkExit();
+	    }
+	  });
+	  m_ExperimenterFrame.pack();
+	  m_ExperimenterFrame.setSize(800, 600);
+	  m_ExperimenterFrame.setVisible(true);
+	}
+      }
+    });
+
+    KnowledgeFlowApp.addStartupListener(new weka.gui.beans.StartUpListener() {
+        public void startUpComplete() {
+          if (m_KnowledgeFlowFrame == null) {
+            final KnowledgeFlowApp kna = KnowledgeFlowApp.getSingleton();
+            m_KnowledgeFlowBut.setEnabled(false);
+            m_KnowledgeFlowFrame = new JFrame("Weka KnowledgeFlow Environment");
+            m_KnowledgeFlowFrame.setIconImage(m_Icon);
+            m_KnowledgeFlowFrame.getContentPane().setLayout(new BorderLayout());
+            m_KnowledgeFlowFrame.getContentPane()
+              .add(kna, BorderLayout.CENTER);
+            m_KnowledgeFlowFrame.addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent w) {
+                  kna.clearLayout();
+                  m_KnowledgeFlowFrame.dispose();
+                  m_KnowledgeFlowFrame = null;
+                  m_KnowledgeFlowBut.setEnabled(true);
+                  checkExit();
+                }
+              });
+            m_KnowledgeFlowFrame.pack();
+            m_KnowledgeFlowFrame.setSize(1000, 750);
+            m_KnowledgeFlowFrame.setVisible(true);
+          }
+        }
+      });
+
+    m_KnowledgeFlowBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        KnowledgeFlow.startApp();
+      }
+    });
+
+    m_SimpleBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_SimpleCLI == null) {
+          m_SimpleBut.setEnabled(false);
+          try {
+            m_SimpleCLI = new SimpleCLI();
+            m_SimpleCLI.setIconImage(m_Icon);
+          } catch (Exception ex) {
+            throw new Error("Couldn't start SimpleCLI!");
+          }
+          m_SimpleCLI.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent w) {
+              m_SimpleCLI.dispose();
+              m_SimpleCLI = null;
+              m_SimpleBut.setEnabled(true);
+              checkExit();
+            }
+          });
+          m_SimpleCLI.setVisible(true);
+        }
+      }
+    });
+
+    /*m_EnsembleLibraryBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	if (m_EnsembleLibraryFrame == null) {
+	  m_EnsembleLibraryBut.setEnabled(false);
+	  m_EnsembleLibraryFrame = new JFrame("EnsembleLibrary");
+	  m_EnsembleLibraryFrame.setIconImage(m_Icon);
+	  m_EnsembleLibraryFrame.getContentPane().setLayout(new BorderLayout());
+	  EnsembleLibrary value = new EnsembleLibrary();
+	  EnsembleLibraryEditor libraryEditor = new EnsembleLibraryEditor();
+	  libraryEditor.setValue(value);
+	  m_EnsembleLibraryFrame.getContentPane().add(libraryEditor.getCustomEditor(), BorderLayout.CENTER);
+	  m_EnsembleLibraryFrame.addWindowListener(new WindowAdapter() {
+	    public void windowClosing(WindowEvent w) {
+	      m_EnsembleLibraryFrame.dispose();
+	      m_EnsembleLibraryFrame = null;
+	      m_EnsembleLibraryBut.setEnabled(true);
+	      checkExit();
+	    }
+	  });
+	  m_EnsembleLibraryFrame.pack();
+	  m_EnsembleLibraryFrame.setSize(800, 600);
+	  m_EnsembleLibraryFrame.setVisible(true);
+	}
+      }
+    }); */
+    
+    setJMenuBar(m_jMenuBar);
+
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent w) {
+	dispose();
+	checkExit();
+      }
+    });
+    pack();
+  }
+  
+  /**
+   * insert the menu item in a sorted fashion.
+   * 
+   * @param menu        the menu to add the item to
+   * @param menuitem    the menu item to add
+   */
+  protected void insertMenuItem(JMenu menu, JMenuItem menuitem) {
+    insertMenuItem(menu, menuitem, 0);
+  }
+  
+  /**
+   * insert the menu item in a sorted fashion.
+   * 
+   * @param menu        the menu to add the item to
+   * @param menuitem    the menu item to add
+   * @param startIndex  the index in the menu to start with (0-based)
+   */
+  protected void insertMenuItem(JMenu menu, JMenuItem menuitem, int startIndex) {
+    boolean     inserted;
+    int         i;
+    JMenuItem   current;
+    String      currentStr;
+    String      newStr;
+    
+    inserted = false;
+    newStr   = menuitem.getText().toLowerCase();
+    
+    // try to find a spot inbetween
+    for (i = startIndex; i < menu.getMenuComponentCount(); i++) {
+      if (!(menu.getMenuComponent(i) instanceof JMenuItem))
+        continue;
+      
+      current    = (JMenuItem) menu.getMenuComponent(i);
+      currentStr = current.getText().toLowerCase();
+      if (currentStr.compareTo(newStr) > 0) {
+        inserted = true;
+        menu.insert(menuitem, i);
+        break;
+      }
+    }
+    
+    // add it at the end if not yet inserted
+    if (!inserted)
+      menu.add(menuitem);
+  }
+  
+  /**
+   * creates a frame and returns it.
+   * 
+   * @param parent              the parent of the generated frame
+   * @param title               the title of the frame
+   * @param c                   the component to place, can be null
+   * @param layout              the layout to use, e.g., BorderLayout
+   * @param layoutConstraints   the layout constraints, e.g., BorderLayout.CENTER
+   * @param width               the width of the frame, ignored if -1
+   * @param height              the height of the frame, ignored if -1
+   * @param menu                an optional menu
+   * @param listener            if true a default listener is added
+   * @param visible             if true then the frame is made visible immediately
+   * @return                    the generated frame
+   */
+  protected Container createFrame(
+      GUIChooser parent, String title, Component c, LayoutManager layout, 
+      Object layoutConstraints, int width, int height, JMenuBar menu,
+      boolean listener, boolean visible) {
+
+    Container result = null;
+
+
+    final ChildFrameSDI frame = new ChildFrameSDI(parent, title);
+
+    // layout
+    frame.setLayout(layout);
+    if (c != null)
+      frame.getContentPane().add(c, layoutConstraints);
+
+    // menu
+    frame.setJMenuBar(menu);
+
+    // size
+    frame.pack();
+    if ((width > -1) && (height > -1))
+      frame.setSize(width, height);
+    frame.validate();
+
+    // location
+    int screenHeight = getGraphicsConfiguration().getBounds().height;
+    int screenWidth  = getGraphicsConfiguration().getBounds().width;
+    frame.setLocation(
+        (screenWidth - frame.getBounds().width) / 2,
+        (screenHeight - frame.getBounds().height) / 2);
+
+    // listener?
+    if (listener) {
+      frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          frame.dispose();
+        }
+      });
+    }
+
+    // display frame
+    if (visible)
+      frame.setVisible(true);
+
+    result = frame;
+
+
+    return result;
+  }
+  
+  /**
+   * Specialized JFrame class.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5837 $
+   */
+  public static class ChildFrameSDI 
+    extends JFrame {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 8588293938686425618L;
+    
+    /** the parent frame. */
+    protected GUIChooser m_Parent;
+    
+    /**
+     * constructs a new internal frame that knows about its parent.
+     * 
+     * @param parent    the parent frame
+     * @param title     the title of the frame
+     */
+    public ChildFrameSDI(GUIChooser parent, String title) {
+      super(title);
+      
+      m_Parent = parent;
+
+      addWindowListener(new WindowAdapter() {
+        public void windowActivated(WindowEvent e) {
+          // update title of parent
+          if (getParentFrame() != null)
+            getParentFrame().createTitle(getTitle());
+        }
+      });
+      
+      // add to parent
+      if (getParentFrame() != null) {
+        getParentFrame().addChildFrame(this);
+        setIconImage(getParentFrame().getIconImage());
+      }
+    }
+    
+    /**
+     * returns the parent frame, can be null.
+     * 
+     * @return          the parent frame
+     */
+    public GUIChooser getParentFrame() {
+      return m_Parent;
+    }
+    
+    /**
+     * de-registers the child frame with the parent first.
+     */
+    public void dispose() {
+      if (getParentFrame() != null) {
+        getParentFrame().removeChildFrame(this);
+        getParentFrame().createTitle("");
+      }
+      
+      super.dispose();
+    }
+  }
+  
+  /**
+   * creates and displays the title.
+   * 
+   * @param title       the additional part of the title
+   */
+  protected void createTitle(String title) {
+    String      newTitle;
+    
+    newTitle = "Weka " + new Version();
+    if (title.length() != 0)
+      newTitle += " - " + title;
+    
+    setTitle(newTitle);
+  }
+  
+  /**
+   * adds the given child frame to the list of frames.
+   * 
+   * @param c           the child frame to add
+   */
+  public void addChildFrame(Container c) {
+    m_ChildFrames.add(c);
+  }
+  
+  /**
+   * tries to remove the child frame, it returns true if it could do such.
+   * 
+   * @param c           the child frame to remove
+   * @return            true if the child frame could be removed
+   */
+  public boolean removeChildFrame(Container c) {
+    boolean result = m_ChildFrames.remove(c);
+    return result;
+  }
+  
+  /**
+   * Kills the JVM if all windows have been closed.
+   */
+  private void checkExit() {
+
+    if (!isVisible()
+	// applications
+	&& (m_ExplorerFrame == null)
+	&& (m_ExperimenterFrame == null)
+	&& (m_KnowledgeFlowFrame == null)
+	&& (m_SimpleCLI == null)
+	// tools
+	&& (m_ArffViewers.size() == 0)
+	&& (m_SqlViewerFrame == null)
+	&& (m_GroovyConsoleFrame == null)
+	&& (m_JythonConsoleFrame == null)
+	&& (m_EnsembleLibraryFrame == null)
+	// visualization
+	&& (m_Plots.size() == 0)
+	&& (m_ROCs.size() == 0)
+	&& (m_TreeVisualizers.size() == 0)
+	&& (m_GraphVisualizers.size() == 0)
+	&& (m_BoundaryVisualizerFrame == null)
+	// help
+	&& (m_SystemInfoFrame == null) ) {
+      System.exit(0);
+    }
+  }
+
+  /** variable for the GUIChooser class which would be set to null by the memory 
+      monitoring thread to free up some memory if we running out of memory
+   */
+  private static GUIChooser m_chooser;
+
+  /** for monitoring the Memory consumption */
+  private static Memory m_Memory = new Memory(true);
+
+  /**
+   * Tests out the GUIChooser environment.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      m_chooser = new GUIChooser();
+      m_chooser.setVisible(true);
+
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while(true) {
+            try {
+              //System.out.println("before sleeping");
+              this.sleep(4000);
+              
+              System.gc();
+              
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                m_chooser.dispose();
+                if(m_chooser.m_ExperimenterFrame!=null) {
+                  m_chooser.m_ExperimenterFrame.dispose();
+                  m_chooser.m_ExperimenterFrame =null;
+                }
+                if(m_chooser.m_ExplorerFrame!=null) {
+                  m_chooser.m_ExplorerFrame.dispose();
+                  m_chooser.m_ExplorerFrame = null;
+                }
+                if(m_chooser.m_KnowledgeFlowFrame!=null) {
+                  m_chooser.m_KnowledgeFlowFrame.dispose();
+                  m_chooser.m_KnowledgeFlowFrame = null;
+                }
+                if(m_chooser.m_SimpleCLI!=null) {
+                  m_chooser.m_SimpleCLI.dispose();
+                  m_chooser.m_SimpleCLI = null;
+                }
+                if (m_chooser.m_ArffViewers.size() > 0) {
+                  for (int i = 0; i < m_chooser.m_ArffViewers.size(); i++) {
+                    ArffViewer av = (ArffViewer) 
+                                        m_chooser.m_ArffViewers.get(i);
+                    av.dispose();
+                  }
+                  m_chooser.m_ArffViewers.clear();
+                }
+                m_chooser = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+
+                // display error
+                m_chooser.m_LogWindow.setVisible(true);
+                m_chooser.m_LogWindow.toFront();
+                System.err.println("\ndisplayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nexiting...");
+                System.exit(-1);
+              }
+            } 
+            catch(InterruptedException ex) { 
+              ex.printStackTrace(); 
+            }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.NORM_PRIORITY);
+      memMonitor.start();    
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/GUIEditors.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GUIEditors.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GUIEditors.props	(revision 29)
@@ -0,0 +1,78 @@
+# Mappings for classes to be handled in the GenericObjectEditor
+#
+# Format:
+#   <class to be handled>=<class to handle it>
+#
+# Notes: 
+# - array classes have to be suffixed with "[]"
+# - full classname has to be provided
+#
+# author:  FracPete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 5813 $
+
+# general
+java.lang.Object[]=weka.gui.GenericArrayEditor
+java.io.File=weka.gui.FileEditor
+java.text.SimpleDateFormat=weka.gui.SimpleDateFormatEditor
+
+# core
+weka.core.SelectedTag=weka.gui.SelectedTagEditor
+weka.core.DistanceFunction=weka.gui.GenericObjectEditor
+weka.core.Range=weka.gui.GenericObjectEditor
+weka.core.Range[]=weka.gui.GenericArrayEditor
+weka.core.converters.Loader=weka.gui.GenericObjectEditor
+weka.core.converters.Saver=weka.gui.GenericObjectEditor
+weka.core.neighboursearch.NearestNeighbourSearch=weka.gui.GenericObjectEditor
+weka.core.neighboursearch.balltrees.BallTreeConstructor=weka.gui.GenericObjectEditor
+weka.core.neighboursearch.balltrees.BallSplitter=weka.gui.GenericObjectEditor
+weka.core.neighboursearch.kdtrees.KDTreeNodeSplitter=weka.gui.GenericObjectEditor
+weka.core.stemmers.Stemmer=weka.gui.GenericObjectEditor
+weka.core.tokenizers.Tokenizer=weka.gui.GenericObjectEditor
+
+# estimators
+weka.estimators.Estimator=weka.gui.GenericObjectEditor
+weka.estimators.Estimator[]=weka.gui.GenericArrayEditor
+
+# filters
+weka.filters.Filter=weka.gui.GenericObjectEditor
+weka.filters.Filter[]=weka.gui.GenericArrayEditor
+
+# classifiers
+weka.classifiers.Classifier=weka.gui.GenericObjectEditor
+weka.classifiers.Classifier[]=weka.gui.GenericArrayEditor
+weka.classifiers.CostMatrix=weka.gui.CostMatrixEditor
+weka.classifiers.bayes.net.search.SearchAlgorithm=weka.gui.GenericObjectEditor
+weka.classifiers.bayes.net.estimate.BayesNetEstimator=weka.gui.GenericObjectEditor
+weka.classifiers.evaluation.output.prediction.AbstractOutput=weka.gui.GenericObjectEditor
+weka.classifiers.functions.supportVector.Kernel=weka.gui.GenericObjectEditor
+weka.classifiers.functions.supportVector.RegOptimizer=weka.gui.GenericObjectEditor
+weka.classifiers.meta.generators.Generator=weka.gui.GenericObjectEditor
+weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary=weka.gui.EnsembleSelectionLibraryEditor
+
+# experiment
+weka.experiment.ResultListener=weka.gui.GenericObjectEditor
+weka.experiment.ResultProducer=weka.gui.GenericObjectEditor
+weka.experiment.SplitEvaluator=weka.gui.GenericObjectEditor
+weka.experiment.ResultMatrix=weka.gui.GenericObjectEditor
+weka.experiment.Tester=weka.gui.GenericObjectEditor
+
+# datagenerators
+weka.datagenerators.DataGenerator=weka.gui.GenericObjectEditor
+weka.datagenerators.ClusterDefinition=weka.gui.GenericObjectEditor
+weka.datagenerators.ClusterDefinition[]=weka.gui.GenericArrayEditor
+
+# associations
+weka.associations.Associator=weka.gui.GenericObjectEditor
+
+# clusterers
+weka.clusterers.Clusterer=weka.gui.GenericObjectEditor
+weka.clusterers.DensityBasedClusterer=weka.gui.GenericObjectEditor
+
+# attribute selection
+weka.attributeSelection.ASEvaluation=weka.gui.GenericObjectEditor
+weka.attributeSelection.ASSearch=weka.gui.GenericObjectEditor
+weka.attributeSelection.UnsupervisedSubsetEvaluator=weka.gui.GenericObjectEditor
+
+#generators
+weka.classifiers.meta.generators.NominalAttributeGenerator=weka.gui.GenericObjectEditor
+weka.classifiers.meta.generators.NumericAttributeGenerator=weka.gui.GenericObjectEditor
Index: branches/MetisMQI/src/main/java/weka/gui/GenericArrayEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericArrayEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericArrayEditor.java	(revision 29)
@@ -0,0 +1,643 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GenericArrayEditor.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.SerializedObject;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.lang.reflect.Array;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/** 
+ * A PropertyEditor for arrays of objects that themselves have
+ * property editors.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6125 $
+ */
+public class GenericArrayEditor
+  extends JPanel
+  implements PropertyEditor {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 3914616975334750480L;
+
+  /** Handles property change notification. */
+  private PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+
+  /** The label for when we can't edit that type. */
+  private JLabel m_Label = new JLabel("Can't edit", SwingConstants.CENTER);
+  
+  /** The list component displaying current values. */
+  private JList m_ElementList = new JList();
+
+  /** The class of objects allowed in the array. */
+  private Class m_ElementClass = String.class;
+
+  /** The defaultlistmodel holding our data. */
+  private DefaultListModel m_ListModel;
+
+  /** The property editor for the class we are editing. */
+  private PropertyEditor m_ElementEditor;
+
+  /** Click this to delete the selected array values. */
+  private JButton m_DeleteBut = new JButton("Delete");
+
+  /** Click this to edit the selected array value. */
+  private JButton m_EditBut = new JButton("Edit");
+
+  /** Click this to move the selected array value(s) one up. */
+  private JButton m_UpBut = new JButton("Up");
+
+  /** Click this to move the selected array value(s) one down. */
+  private JButton m_DownBut = new JButton("Down");
+
+  /** Click to add the current object configuration to the array. */
+  private JButton m_AddBut = new JButton("Add");
+
+  /** The property editor for editing existing elements. */
+  private PropertyEditor m_Editor = new GenericObjectEditor();
+
+  /** The currently displayed property dialog, if any. */
+  private PropertyDialog m_PD;
+
+  /** Listens to buttons being pressed and taking the appropriate action. */
+  private ActionListener m_InnerActionListener =
+    new ActionListener() {
+
+    public void actionPerformed(ActionEvent e) {
+
+      if (e.getSource() == m_DeleteBut) {
+	int [] selected = m_ElementList.getSelectedIndices();
+	if (selected != null) {
+	  for (int i = selected.length - 1; i >= 0; i--) {
+	    int current = selected[i];
+	    m_ListModel.removeElementAt(current);
+	    if (m_ListModel.size() > current) {
+	      m_ElementList.setSelectedIndex(current);
+	    }
+	  }
+	  m_Support.firePropertyChange("", null, null);
+	}
+      } else if (e.getSource() == m_EditBut) {
+        ((GenericObjectEditor) m_Editor).setClassType(m_ElementClass);
+        m_Editor.setValue(m_ElementList.getSelectedValue());
+        if (m_Editor.getValue() != null) {
+          if (m_PD == null) {
+            int x = getLocationOnScreen().x;
+            int y = getLocationOnScreen().y;
+            if (PropertyDialog.getParentDialog(GenericArrayEditor.this) != null)
+              m_PD = new PropertyDialog(
+        	  PropertyDialog.getParentDialog(GenericArrayEditor.this), 
+        	  m_Editor, x, y);
+            else
+              m_PD = new PropertyDialog(
+        	  PropertyDialog.getParentFrame(GenericArrayEditor.this), 
+        	  m_Editor, x, y);
+            m_PD.setVisible(true);
+          } 
+          else {
+            m_PD.setVisible(true);
+          }
+          m_Support.firePropertyChange("", null, null);
+        }
+      } else if (e.getSource() == m_UpBut) {
+        JListHelper.moveUp(m_ElementList);
+	m_Support.firePropertyChange("", null, null);
+      } else if (e.getSource() == m_DownBut) {
+        JListHelper.moveDown(m_ElementList);
+	m_Support.firePropertyChange("", null, null);
+      } else if (e.getSource() == m_AddBut) {
+	int selected = m_ElementList.getSelectedIndex();
+	Object addObj = m_ElementEditor.getValue();
+	
+	// Make a full copy of the object using serialization
+	try {
+          SerializedObject so = new SerializedObject(addObj);
+	  addObj = so.getObject();
+	  if (selected != -1) {
+	    m_ListModel.insertElementAt(addObj, selected);
+	  } else {
+	    m_ListModel.addElement(addObj);
+	  }
+	  m_Support.firePropertyChange("", null, null);
+	} catch (Exception ex) {
+	  JOptionPane.showMessageDialog(GenericArrayEditor.this,
+					"Could not create an object copy",
+					null,
+					JOptionPane.ERROR_MESSAGE);
+	}
+      } 
+    }
+  };
+
+  /** Listens to list items being selected and takes appropriate action. */
+  private ListSelectionListener m_InnerSelectionListener =
+    new ListSelectionListener() {
+
+      public void valueChanged(ListSelectionEvent e) {
+
+	if (e.getSource() == m_ElementList) {
+	  // Enable the delete/edit button
+	  if (m_ElementList.getSelectedIndex() != -1) {
+	    m_DeleteBut.setEnabled(true);
+	    m_EditBut.setEnabled(m_ElementList.getSelectedIndices().length == 1);
+	    m_UpBut.setEnabled(JListHelper.canMoveUp(m_ElementList));
+	    m_DownBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
+	  }
+          // disable delete/edit button
+          else {
+	    m_DeleteBut.setEnabled(false);
+	    m_EditBut.setEnabled(false);
+	    m_UpBut.setEnabled(false);
+	    m_DownBut.setEnabled(false);
+          }
+	}
+      }
+  };
+
+  /** Listens to mouse events and takes appropriate action. */
+  private MouseListener m_InnerMouseListener =
+    new MouseAdapter() {
+
+      public void mouseClicked(MouseEvent e) {
+        if (e.getSource() == m_ElementList) {
+          if (e.getClickCount() == 2) {
+            // unfortunately, locationToIndex only returns the nearest entry
+            // and not the exact one, i.e. if there's one item in the list and
+            // one doublelclicks somewhere in the list, this index will be
+            // returned
+            int index = m_ElementList.locationToIndex(e.getPoint());
+            if (index > -1)
+              m_InnerActionListener.actionPerformed(
+                  new ActionEvent(m_EditBut, 0, ""));
+          }
+        }
+      }
+  };
+    
+
+  /**
+   * Sets up the array editor.
+   */
+  public GenericArrayEditor() {
+
+    setLayout(new BorderLayout());
+    add(m_Label, BorderLayout.CENTER);
+    m_DeleteBut.addActionListener(m_InnerActionListener);
+    m_EditBut.addActionListener(m_InnerActionListener);
+    m_UpBut.addActionListener(m_InnerActionListener);
+    m_DownBut.addActionListener(m_InnerActionListener);
+    m_AddBut.addActionListener(m_InnerActionListener);
+    m_ElementList.addListSelectionListener(m_InnerSelectionListener);
+    m_ElementList.addMouseListener(m_InnerMouseListener);
+    m_AddBut.setToolTipText("Add the current item to the list");
+    m_DeleteBut.setToolTipText("Delete the selected list item");
+    m_EditBut.setToolTipText("Edit the selected list item");
+    m_UpBut.setToolTipText("Move the selected item(s) one up");
+    m_DownBut.setToolTipText("Move the selected item(s) one down");
+  }
+
+  /** This class handles the creation of list cell renderers from the 
+   * property editors.
+   */
+  private class EditorListCellRenderer implements ListCellRenderer {
+
+    /** The class of the property editor for array objects. */
+    private Class m_EditorClass;
+
+    /** The class of the array values. */
+    private Class m_ValueClass;
+
+    /**
+     * Creates the list cell renderer.
+     *
+     * @param editorClass The class of the property editor for array objects
+     * @param valueClass The class of the array values
+     */
+    public EditorListCellRenderer(Class editorClass, Class valueClass) {
+      m_EditorClass = editorClass;
+      m_ValueClass = valueClass;
+    }
+
+    /**
+     * Creates a cell rendering component.
+     *
+     * @param list the list that will be rendered in
+     * @param value the cell value
+     * @param index which element of the list to render
+     * @param isSelected true if the cell is selected
+     * @param cellHasFocus true if the cell has the focus
+     * @return the rendering component
+     */
+    public Component getListCellRendererComponent(final JList list,
+						  final Object value,
+						  final int index,
+						  final boolean isSelected,
+						  final boolean cellHasFocus) {
+      try {
+	final PropertyEditor e = (PropertyEditor)m_EditorClass.newInstance();
+	if (e instanceof GenericObjectEditor) {
+	  //	  ((GenericObjectEditor) e).setDisplayOnly(true);
+	  ((GenericObjectEditor) e).setClassType(m_ValueClass);
+	}
+	e.setValue(value);
+	return new JPanel() {
+	  
+	  private static final long serialVersionUID = -3124434678426673334L;
+
+	  public void paintComponent(Graphics g) {
+
+	    Insets i = this.getInsets();
+	    Rectangle box = new Rectangle(i.left, i.top,
+					  this.getWidth() - i.right,
+					  this.getHeight() - i.bottom );
+	    g.setColor(isSelected
+		       ? list.getSelectionBackground()
+		       : list.getBackground());
+	    g.fillRect(0, 0, this.getWidth(), this.getHeight());
+	    g.setColor(isSelected
+		       ? list.getSelectionForeground()
+		       : list.getForeground());
+	    e.paintValue(g, box);
+	  }
+	  
+	  public Dimension getPreferredSize() {
+
+	    Font f = this.getFont();
+	    FontMetrics fm = this.getFontMetrics(f);
+	    return new Dimension(0, fm.getHeight());
+	  }
+	};
+      } catch (Exception ex) {
+	return null;
+      }
+    }
+  }
+
+  /**
+   * Updates the type of object being edited, so attempts to find an
+   * appropriate propertyeditor.
+   *
+   * @param o a value of type 'Object'
+   */
+  private void updateEditorType(Object o) {
+
+    // Determine if the current object is an array
+    m_ElementEditor = null; m_ListModel = null;
+    removeAll();
+    if ((o != null) && (o.getClass().isArray())) {
+      Class elementClass = o.getClass().getComponentType();    
+      PropertyEditor editor = PropertyEditorManager.findEditor(elementClass);
+      Component view = null;
+      ListCellRenderer lcr = new DefaultListCellRenderer();
+      if (editor != null) {
+	if (editor instanceof GenericObjectEditor) {
+	  ((GenericObjectEditor) editor).setClassType(elementClass);
+	}
+
+        //setting the value in the editor so that
+        //we don't get a NullPointerException
+        //when we do getAsText() in the constructor of
+        //PropertyValueSelector()
+	if(Array.getLength(o) > 0) {
+	  editor.setValue(makeCopy(Array.get(o,0)));
+	} else {
+	  if (editor instanceof GenericObjectEditor) {
+	    ((GenericObjectEditor)editor).setDefaultValue();
+	  } else {   
+            try {
+	    editor.setValue(elementClass.newInstance());
+            } catch(Exception ex) {
+              m_ElementEditor=null;
+              System.err.println(ex.getMessage());
+              add(m_Label, BorderLayout.CENTER);
+              m_Support.firePropertyChange("", null, null);
+              validate();
+              return;
+            }
+	  }
+	}
+        
+	if (editor.isPaintable() && editor.supportsCustomEditor()) {
+	  view = new PropertyPanel(editor);
+	  lcr = new EditorListCellRenderer(editor.getClass(), elementClass);
+	} else if (editor.getTags() != null) {
+	  view = new PropertyValueSelector(editor);
+	} else if (editor.getAsText() != null) {
+	  view = new PropertyText(editor);
+	}
+      }
+      if (view == null) {
+	System.err.println("No property editor for class: "
+			   + elementClass.getName());
+      } else {
+	m_ElementEditor = editor;
+
+	// Create the ListModel and populate it
+	m_ListModel = new DefaultListModel();
+	m_ElementClass = elementClass;
+	for (int i = 0; i < Array.getLength(o); i++) {
+	  m_ListModel.addElement(Array.get(o,i));
+	}
+	m_ElementList.setCellRenderer(lcr);
+	m_ElementList.setModel(m_ListModel);
+	if (m_ListModel.getSize() > 0) {
+	  m_ElementList.setSelectedIndex(0);
+	} else {
+	  m_DeleteBut.setEnabled(false);
+	  m_EditBut.setEnabled(false);
+	}
+	m_UpBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
+	m_DownBut.setEnabled(JListHelper.canMoveDown(m_ElementList));
+
+        //have already set the value above in the editor
+	//try {
+	  //if (m_ListModel.getSize() > 0) {
+	  //  m_ElementEditor.setValue(m_ListModel.getElementAt(0));
+	  //} else {
+	  //  if (m_ElementEditor instanceof GenericObjectEditor) {
+	  //    ((GenericObjectEditor)m_ElementEditor).setDefaultValue();
+	  //  } else {
+	  //    m_ElementEditor.setValue(m_ElementClass.newInstance());
+	  //  }
+	  //}
+	  
+	  JPanel panel = new JPanel();
+	  panel.setLayout(new BorderLayout());
+	  panel.add(view, BorderLayout.CENTER);
+	  panel.add(m_AddBut, BorderLayout.EAST);
+	  add(panel, BorderLayout.NORTH);
+	  add(new JScrollPane(m_ElementList), BorderLayout.CENTER);
+          JPanel panel2 = new JPanel();
+          panel2.setLayout(new GridLayout(1, 4));
+          panel2.add(m_DeleteBut);
+          panel2.add(m_EditBut);
+          panel2.add(m_UpBut);
+          panel2.add(m_DownBut);
+          add(panel2, BorderLayout.SOUTH);
+	  m_ElementEditor
+	    .addPropertyChangeListener(new PropertyChangeListener() {
+	    public void propertyChange(PropertyChangeEvent e) {
+	      repaint();
+	    }
+	  });
+	//} catch (Exception ex) {
+	//  System.err.println(ex.getMessage());
+	//  m_ElementEditor = null;
+	//}
+      }
+    }
+    if (m_ElementEditor == null) {
+      add(m_Label, BorderLayout.CENTER);
+    }
+    m_Support.firePropertyChange("", null, null);
+    validate();
+  }
+
+  /**
+   * Sets the current object array.
+   *
+   * @param o an object that must be an array.
+   */
+  public void setValue(Object o) {
+
+    // Create a new list model, put it in the list and resize?
+    updateEditorType(o);
+  }
+
+  /**
+   * Gets the current object array.
+   *
+   * @return the current object array
+   */
+  public Object getValue() {
+
+    if (m_ListModel == null) {
+      return null;
+    }
+    // Convert the listmodel to an array of strings and return it.
+    int length = m_ListModel.getSize();
+    Object result = Array.newInstance(m_ElementClass, length);
+    for (int i = 0; i < length; i++) {
+      Array.set(result, i, m_ListModel.elementAt(i));
+    }
+    return result;
+  }
+  
+  /**
+   * Supposedly returns an initialization string to create a classifier
+   * identical to the current one, including it's state, but this doesn't
+   * appear possible given that the initialization string isn't supposed to
+   * contain multiple statements.
+   *
+   * @return the java source code initialisation string
+   */
+  public String getJavaInitializationString() {
+
+    return "null";
+  }
+
+  /**
+   * Returns true to indicate that we can paint a representation of the
+   * string array.
+   *
+   * @return true
+   */
+  public boolean isPaintable() {
+    return true;
+  }
+
+  /**
+   * Paints a representation of the current classifier.
+   *
+   * @param gfx the graphics context to use
+   * @param box the area we are allowed to paint into
+   */
+  public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
+
+    FontMetrics fm = gfx.getFontMetrics();
+    int vpad = (box.height - fm.getHeight()) / 2;
+    String rep = m_ListModel.getSize() + " " + m_ElementClass.getName();
+    gfx.drawString(rep, 2, fm.getAscent() + vpad + 2);
+  }
+
+  /**
+   * Returns null as we don't support getting/setting values as text.
+   *
+   * @return null
+   */
+  public String getAsText() {
+    return null;
+  }
+
+  /**
+   * Returns null as we don't support getting/setting values as text. 
+   *
+   * @param text the text value
+   * @exception IllegalArgumentException as we don't support
+   * getting/setting values as text.
+   */
+  public void setAsText(String text) {
+    throw new IllegalArgumentException(text);
+  }
+
+  /**
+   * Returns null as we don't support getting values as tags.
+   *
+   * @return null
+   */
+  public String[] getTags() {
+    return null;
+  }
+
+  /**
+   * Returns true because we do support a custom editor.
+   *
+   * @return true
+   */
+  public boolean supportsCustomEditor() {
+    return true;
+  }
+  
+  /**
+   * Returns the array editing component.
+   *
+   * @return a value of type 'java.awt.Component'
+   */
+  public java.awt.Component getCustomEditor() {
+    return this;
+  }
+
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    m_Support.removePropertyChangeListener(l);
+  }
+
+  /**
+   * Makes a copy of an object using serialization.
+   * 
+   * @param source the object to copy
+   * @return a copy of the source object, null if copying fails
+   */
+  public static Object makeCopy(Object source) {
+    Object	result;
+    
+    try {
+      result = GenericObjectEditor.makeCopy(source);
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tests out the array editor from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      GenericObjectEditor.registerEditors();
+
+      final GenericArrayEditor ce = new GenericArrayEditor();
+
+      final weka.filters.Filter [] initial = new weka.filters.Filter [0];
+	/*
+      {
+	new weka.filters.AddFilter()
+	};*/
+      /*
+      final String [] initial = {
+	"Hello",
+	"There",
+	"Bob"
+	};*/
+      PropertyDialog pd = new PropertyDialog((Frame) null, ce, 100, 100);
+      pd.setSize(200,200);
+      pd.addWindowListener(new WindowAdapter() {
+	private static final long serialVersionUID = -3124434678426673334L;
+	public void windowClosing(WindowEvent e) {
+	  System.exit(0);
+	}
+      });
+      ce.setValue(initial);
+      pd.setVisible(true);
+      //ce.validate();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.java	(revision 29)
@@ -0,0 +1,1785 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GenericObjectEditor.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.ClassDiscovery;
+import weka.core.OptionHandler;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.Capabilities.Capability;
+import weka.gui.CheckBoxList.CheckBoxListModel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Array;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+/**
+ * A PropertyEditor for objects. It can be used either in a static or a dynamic
+ * way. <br>
+ * <br>
+ * In the <b>static</b> way (<code>USE_DYNAMIC</code> is <code>false</code>) the
+ * objects have been defined as editable in the GenericObjectEditor
+ * configuration file, which lists possible values that can be selected from,
+ * and themselves configured. The configuration file is called
+ * "GenericObjectEditor.props" and may live in either the location given by
+ * "user.home" or the current directory (this last will take precedence), and a
+ * default properties file is read from the Weka distribution. For speed, the
+ * properties file is read only once when the class is first loaded -- this may
+ * need to be changed if we ever end up running in a Java OS ;-). <br>
+ * <br>
+ * If it is used in a <b>dynamic</b> way (the <code>UseDynamic</code> property 
+ * of the GenericPropertiesCreator props file is set to <code>true</code>) 
+ * then the classes to list are discovered by the 
+ * <code>GenericPropertiesCreator</code> class (it checks the complete classpath). 
+ * 
+ * @see GenericPropertiesCreator
+ * @see GenericPropertiesCreator#useDynamic()
+ * @see GenericPropertiesCreator#CREATOR_FILE
+ * @see weka.core.ClassDiscovery
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5841 $
+ */
+public class GenericObjectEditor implements PropertyEditor, CustomPanelSupplier {
+  
+  /** The object being configured. */
+  protected Object m_Object;
+  
+  /** Holds a copy of the current object that can be reverted to
+      if the user decides to cancel. */
+  protected Object m_Backup;
+    
+  /** Handles property change notification. */
+  protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+    
+  /** The Class of objects being edited. */
+  protected Class m_ClassType;
+    
+  /** The model containing the list of names to select from. */
+  protected Hashtable m_ObjectNames;
+
+  /** The GUI component for editing values, created when needed. */
+  protected GOEPanel m_EditorComponent;
+    
+  /** True if the GUI component is needed. */
+  protected boolean m_Enabled = true;
+    
+  /** The name of the properties file. */
+  protected static String PROPERTY_FILE = "weka/gui/GenericObjectEditor.props";
+    
+  /** Contains the editor properties. */
+  protected static Properties EDITOR_PROPERTIES;
+
+  /** the properties files containing the class/editor mappings. */
+  public static final String GUIEDITORS_PROPERTY_FILE = "weka/gui/GUIEditors.props";
+
+  /** The tree node of the current object so we can re-select it for the user. */
+  protected GOETreeNode m_treeNodeOfCurrentObject;
+
+  /** The property panel created for the objects. */
+  protected PropertyPanel m_ObjectPropertyPanel;
+    
+  /** whether the class can be changed. */
+  protected boolean m_canChangeClassInDialog;
+  
+  /** whether the Weka Editors were already registered. */
+  protected static boolean m_EditorsRegistered;
+
+  /** for filtering the tree based on the Capabilities of the leaves. */
+  protected Capabilities m_CapabilitiesFilter = null;
+  
+  /** 
+   * Loads the configuration property file (USE_DYNAMIC is FALSE) or determines
+   * the classes dynamically (USE_DYNAMIC is TRUE)
+   * @see #USE_DYNAMIC
+   * @see GenericPropertiesCreator
+   */
+  static {
+
+    try {
+      GenericPropertiesCreator creator = new GenericPropertiesCreator();
+
+      // dynamic approach?
+      if (creator.useDynamic()) {
+	try {
+	  creator.execute(false);
+	  EDITOR_PROPERTIES = creator.getOutputProperties();
+	}
+	catch (Exception e) {
+	  JOptionPane.showMessageDialog(
+	      null,
+	      "Could not determine the properties for the generic object\n"
+	      + "editor. This exception was produced:\n"
+	      + e.toString(),
+	      "GenericObjectEditor",
+	      JOptionPane.ERROR_MESSAGE);
+	}
+      }
+      else {
+	// Allow a properties file in the current directory to override
+	try {
+	  EDITOR_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+	  java.util.Enumeration keys = 
+	    (java.util.Enumeration)EDITOR_PROPERTIES.propertyNames();
+	  if (!keys.hasMoreElements()) {
+	    throw new Exception("Failed to read a property file for the "
+		+"generic object editor");
+	  }
+	}
+	catch (Exception ex) {
+	  JOptionPane.showMessageDialog(
+	      null,
+	      "Could not read a configuration file for the generic object\n"
+	      +"editor. An example file is included with the Weka distribution.\n"
+	      +"This file should be named \"" + PROPERTY_FILE + "\" and\n"
+	      +"should be placed either in your user home (which is set\n"
+	      + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+	      + "or the directory that java was started from\n",
+	      "GenericObjectEditor",
+	      JOptionPane.ERROR_MESSAGE);
+	}
+      }
+    }
+    catch (Exception e) {
+      JOptionPane.showMessageDialog(
+	  null,
+	  "Could not initialize the GenericPropertiesCreator. "
+	  + "This exception was produced:\n"
+	  + e.toString(),
+	  "GenericObjectEditor",
+	  JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * A specialized TreeNode for supporting filtering via Capabilities.
+   */
+  public class GOETreeNode
+    extends DefaultMutableTreeNode {
+    
+    /** for serialization. */
+    static final long serialVersionUID = -1707872446682150133L;
+    
+    /** color for "no support". */
+    public final static String NO_SUPPORT = "silver";
+    
+    /** color for "maybe support". */
+    public final static String MAYBE_SUPPORT = "blue";
+    
+    /** the Capabilities object to use for filtering. */
+    protected Capabilities m_Capabilities = null;
+    
+    /**
+     * Creates a tree node that has no parent and no children, but which 
+     * allows children.
+     */
+    public GOETreeNode() {
+      super();
+    }
+    
+    /**
+     * Creates a tree node with no parent, no children, but which allows 
+     * children, and initializes it with the specified user object.
+     * 
+     * @param userObject	an Object provided by the user that constitutes 
+     * 				the node's data
+     */
+    public GOETreeNode(Object userObject) {
+      super(userObject);
+    }
+    
+    /**
+     * Creates a tree node with no parent, no children, initialized with the 
+     * specified user object, and that allows children only if specified.
+
+     * @param userObject	an Object provided by the user that constitutes 
+     * 				the node's data
+     * @param allowsChildren	if true, the node is allowed to have child nodes 
+     * 				-- otherwise, it is always a leaf node
+     */
+    public GOETreeNode(Object userObject, boolean allowsChildren) {
+      super(userObject, allowsChildren);
+    }
+    
+    /**
+     * generates if necessary a Capabilities object for the given leaf.
+     */
+    protected void initCapabilities() {
+      String 	classname;
+      Class	cls;
+      Object	obj;
+      
+      if (m_Capabilities != null)
+	return;
+      if (!isLeaf())
+	return;
+      
+      classname = getClassnameFromPath(new TreePath(getPath()));
+      try {
+	cls = Class.forName(classname);
+	if (!ClassDiscovery.hasInterface(CapabilitiesHandler.class, cls))
+	  return;
+	
+	obj = cls.newInstance();
+	m_Capabilities = ((CapabilitiesHandler) obj).getCapabilities();
+      }
+      catch (Exception e) {
+	// ignore it
+      }
+    }
+    
+    /**
+     * returns a string representation of this treenode.
+     * 
+     * @return 		the text to display 
+     */
+    public String toString() {
+      String	result;
+      
+      result = super.toString();
+      
+      if (m_CapabilitiesFilter != null) {
+	initCapabilities();
+	if (m_Capabilities != null) {
+	  if (m_Capabilities.supportsMaybe(m_CapabilitiesFilter) && !m_Capabilities.supports(m_CapabilitiesFilter))
+	    result = "<html><font color=\"" + MAYBE_SUPPORT + "\">" + result + "</font></i><html>";
+	  else if (!m_Capabilities.supports(m_CapabilitiesFilter))
+	    result = "<html><font color=\"" + NO_SUPPORT + "\">" + result + "</font></i><html>";
+	}
+      }
+      
+      return result;
+    }
+  }
+  
+  /**
+   * A dialog for selecting Capabilities to look for in the GOE tree.
+   */
+  public class CapabilitiesFilterDialog 
+    extends JDialog {
+    
+    /** for serialization. */
+    static final long serialVersionUID = -7845503345689646266L;
+    
+    /** the dialog itself. */
+    protected JDialog m_Self;
+    
+    /** the popup to display again. */
+    protected JPopupMenu m_Popup = null;
+    
+    /** the capabilities used for initializing the dialog. */
+    protected Capabilities m_Capabilities = new Capabilities(null);
+
+    /** the label, listing the name of the superclass. */
+    protected JLabel m_InfoLabel = new JLabel();
+    
+    /** the list with all the capabilities. */
+    protected CheckBoxList m_List = new CheckBoxList();
+    
+    /** the OK button. */
+    protected JButton m_OkButton = new JButton("OK");
+    
+    /** the Cancel button. */
+    protected JButton m_CancelButton = new JButton("Cancel");
+    
+    /**
+     * creates a dialog to choose Capabilities from.
+     */
+    public CapabilitiesFilterDialog() {
+      super();
+
+      m_Self = this;
+      
+      initGUI();
+    }
+    
+    /**
+     * sets up the GUI.
+     */
+    protected void initGUI() {
+      JPanel			panel;
+      CheckBoxListModel		model;
+
+      setTitle("Filtering Capabilities...");
+      setLayout(new BorderLayout());
+      
+      panel = new JPanel(new BorderLayout());
+      panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+      getContentPane().add(panel, BorderLayout.NORTH);
+      m_InfoLabel.setText(
+	    "<html>"
+	  + m_ClassType.getName().replaceAll(".*\\.", "") + "s"
+	  + " have to support <i>at least</i> the following capabilities <br>"
+	  + "(the ones highlighted <font color=\"" + GOETreeNode.NO_SUPPORT + "\">" + GOETreeNode.NO_SUPPORT + "</font> don't meet these requirements <br>"
+	  + "the ones highlighted  <font color=\"" + GOETreeNode.MAYBE_SUPPORT + "\">" + GOETreeNode.MAYBE_SUPPORT + "</font> possibly meet them):"
+	  + "</html>");
+      panel.add(m_InfoLabel, BorderLayout.CENTER);
+      
+      // list
+      getContentPane().add(new JScrollPane(m_List), BorderLayout.CENTER);
+      model = (CheckBoxListModel) m_List.getModel();
+      for (Capability cap: Capability.values())
+	model.addElement(cap);
+      
+      // buttons
+      panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+      getContentPane().add(panel, BorderLayout.SOUTH);
+      
+      m_OkButton.setMnemonic('O');
+      m_OkButton.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          updateCapabilities();
+          if (m_CapabilitiesFilter == null)
+            m_CapabilitiesFilter = new Capabilities(null);
+          m_CapabilitiesFilter.assign(m_Capabilities);
+          m_Self.setVisible(false);
+          showPopup();
+        }
+      });
+      panel.add(m_OkButton);
+      
+      m_CancelButton.setMnemonic('C');
+      m_CancelButton.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          m_Self.setVisible(false);
+          showPopup();
+        }
+      });
+      panel.add(m_CancelButton);
+      pack();
+    }
+
+    /**
+     * transfers the Capabilities object to the JList.
+     * 
+     * @see #m_Capabilities
+     * @see #m_List
+     */
+    protected void updateList() {
+      CheckBoxListModel		model;
+      
+      model = (CheckBoxListModel) m_List.getModel();
+
+      for (Capability cap: Capability.values())
+	model.setChecked(model.indexOf(cap), m_Capabilities.handles(cap));
+    }
+    
+    /**
+     * transfers the selected Capabilities from the JList to the 
+     * Capabilities object.
+     * 
+     * @see #m_Capabilities
+     * @see #m_List
+     */
+    protected void updateCapabilities() {
+      CheckBoxListModel		model;
+      
+      model = (CheckBoxListModel) m_List.getModel();
+
+      for (Capability cap: Capability.values()) {
+	if (model.getChecked(model.indexOf(cap)))
+          m_Capabilities.enable(cap);
+	else
+	  m_Capabilities.disable(cap);
+      }
+    }
+    
+    /**
+     * sets the initial capabilities.
+     * 
+     * @param value the capabilities to use
+     */
+    public void setCapabilities(Capabilities value) {
+      if (value != null)
+	m_Capabilities.assign(value);
+      else
+	m_Capabilities = new Capabilities(null);
+      
+      updateList();
+    }
+    
+    /**
+     * returns the currently selected capabilities.
+     * 
+     * @return the currently selected capabilities
+     */
+    public Capabilities getCapabilities() {
+      return m_Capabilities;
+    }
+    
+    /**
+     * sets the JPopupMenu to display again after closing the dialog.
+     * 
+     * @param value the JPopupMenu to display again
+     */
+    public void setPopup(JPopupMenu value) {
+      m_Popup = value;
+    }
+    
+    /**
+     * returns the currently set JPopupMenu.
+     * 
+     * @return the current JPopupMenu
+     */
+    public JPopupMenu getPopup() {
+      return m_Popup;
+    }
+    
+    /**
+     * if a JPopupMenu is set, it is displayed again. Displaying this dialog
+     * closes any JPopupMenu automatically.
+     */
+    public void showPopup() {
+      if (getPopup() != null)
+	getPopup().setVisible(true);
+    }
+  }
+  
+  /**
+   * Creates a popup menu containing a tree that is aware
+   * of the screen dimensions.
+   */
+  public class JTreePopupMenu 
+    extends JPopupMenu {
+    
+    /** for serialization. */
+    static final long serialVersionUID = -3404546329655057387L;
+
+    /** the popup itself. */
+    private JPopupMenu m_Self;
+    
+    /** The tree. */
+    private JTree m_tree;
+
+    /** The scroller. */
+    private JScrollPane m_scroller;
+
+    /** The filter button in case of CapabilitiesHandlers. */
+    private JButton m_FilterButton = new JButton("Filter...");
+
+    /** The remove filter button in case of CapabilitiesHandlers. */
+    private JButton m_RemoveFilterButton = new JButton("Remove filter");
+    
+    /** The button for closing the popup again. */
+    private JButton m_CloseButton = new JButton("Close");
+    
+    /**
+     * Constructs a new popup menu.
+     *
+     * @param tree the tree to put in the menu
+     */
+    public JTreePopupMenu(JTree tree) {
+
+      m_Self = this;
+      
+      setLayout(new BorderLayout());
+      JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+      add(panel, BorderLayout.SOUTH);
+
+      if (ClassDiscovery.hasInterface(CapabilitiesHandler.class, m_ClassType)) {
+	// filter
+	m_FilterButton.setMnemonic('F');
+	m_FilterButton.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (e.getSource() == m_FilterButton) {
+	      CapabilitiesFilterDialog dialog = new CapabilitiesFilterDialog();
+	      dialog.setCapabilities(m_CapabilitiesFilter);
+	      dialog.setPopup(m_Self);
+	      dialog.setVisible(true);
+	      m_Support.firePropertyChange("", null, null);
+	      repaint();
+	    }
+	  }
+	});
+	panel.add(m_FilterButton);
+	
+	// remove
+	m_RemoveFilterButton.setMnemonic('R');
+	m_RemoveFilterButton.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (e.getSource() == m_RemoveFilterButton) {
+	      m_CapabilitiesFilter = null;
+	      m_Support.firePropertyChange("", null, null);
+	      repaint();
+	    }
+	  }
+	});
+	panel.add(m_RemoveFilterButton);
+      }
+
+      // close
+      m_CloseButton.setMnemonic('C');
+      m_CloseButton.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          if (e.getSource() == m_CloseButton) {
+            m_Self.setVisible(false);
+          }
+        }
+      });
+      panel.add(m_CloseButton);
+      
+      m_tree = tree;
+      
+      JPanel treeView = new JPanel();
+      treeView.setLayout(new BorderLayout());
+      treeView.add(m_tree, BorderLayout.NORTH);
+      
+      // make backgrounds look the same
+      treeView.setBackground(m_tree.getBackground());
+
+      m_scroller = new JScrollPane(treeView);
+      
+      m_scroller.setPreferredSize(new Dimension(300, 400));
+      m_scroller.getVerticalScrollBar().setUnitIncrement(20);
+
+      add(m_scroller);
+    }
+
+    /**
+     * Displays the menu, making sure it will fit on the screen.
+     *
+     * @param invoker the component thast invoked the menu
+     * @param x the x location of the popup
+     * @param y the y location of the popup
+     */
+    public void show(Component invoker, int x, int y) {
+
+      super.show(invoker, x, y);
+
+      // calculate available screen area for popup
+      java.awt.Point location = getLocationOnScreen();
+      java.awt.Dimension screenSize = getToolkit().getScreenSize();
+      int maxWidth = (int) (screenSize.getWidth() - location.getX());
+      int maxHeight = (int) (screenSize.getHeight() - location.getY());
+
+      // if the part of the popup goes off the screen then resize it
+      Dimension scrollerSize = m_scroller.getPreferredSize();
+      int height = (int) scrollerSize.getHeight();
+      int width = (int) scrollerSize.getWidth();
+      if (width > maxWidth) width = maxWidth;
+      if (height > maxHeight) height = maxHeight;
+      
+      // commit any size changes
+      m_scroller.setPreferredSize(new Dimension(width, height));
+      revalidate();
+      pack();
+    }
+  }
+
+  /**
+   * Handles the GUI side of editing values.
+   */
+  public class GOEPanel 
+    extends JPanel {
+    
+    /** for serialization. */
+    static final long serialVersionUID = 3656028520876011335L;
+    
+    /** The component that performs classifier customization. */
+    protected PropertySheetPanel m_ChildPropertySheet;
+    
+    /** The name of the current class. */
+    protected JLabel m_ClassNameLabel;
+
+    /** Open object from disk. */
+    protected JButton m_OpenBut;
+    
+    /** Save object to disk. */
+    protected JButton m_SaveBut;
+    
+    /** ok button. */
+    protected JButton m_okBut;
+    
+    /** cancel button. */
+    protected JButton m_cancelBut;
+    
+    /** The filechooser for opening and saving object files. */
+    protected JFileChooser m_FileChooser;
+    
+    /** Creates the GUI editor component. */
+    public GOEPanel() {
+    
+      m_Backup = copyObject(m_Object);
+      
+      m_ClassNameLabel = new JLabel("None");
+      m_ClassNameLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+      m_ChildPropertySheet = new PropertySheetPanel();
+      m_ChildPropertySheet.addPropertyChangeListener
+	(new PropertyChangeListener() {
+	    public void propertyChange(PropertyChangeEvent evt) {
+	      m_Support.firePropertyChange("", null, null);
+	    }
+	  });
+      
+      m_OpenBut = new JButton("Open...");
+      m_OpenBut.setToolTipText("Load a configured object");
+      m_OpenBut.setEnabled(true);
+      m_OpenBut.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    Object object = openObject();
+	    if (object != null) {
+	      // setValue takes care of: Making sure obj is of right type,
+	      // and firing property change.
+	      setValue(object);
+	      // Need a second setValue to get property values filled in OK.
+	      // Not sure why.
+	      setValue(object);
+	    }
+	  }
+	});
+      
+      m_SaveBut = new JButton("Save...");
+      m_SaveBut.setToolTipText("Save the current configured object");
+      m_SaveBut.setEnabled(true);
+      m_SaveBut.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveObject(m_Object);
+	  }
+	});
+      
+      m_okBut = new JButton("OK");
+      m_okBut.setEnabled(true);
+      m_okBut.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+
+	    m_Backup = copyObject(m_Object);
+	    if ((getTopLevelAncestor() != null)
+		&& (getTopLevelAncestor() instanceof Window)) {
+	      Window w = (Window) getTopLevelAncestor();
+	      w.dispose();
+	    }
+	  }
+	});
+      
+      m_cancelBut = new JButton("Cancel");
+      m_cancelBut.setEnabled(true);
+      m_cancelBut.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {		 
+	    if (m_Backup != null) {
+	
+	      m_Object = copyObject(m_Backup);
+	      
+	      // To fire property change
+	      m_Support.firePropertyChange("", null, null);
+	      m_ObjectNames = getClassesFromProperties();
+	      updateObjectNames();
+	      updateChildPropertySheet();
+	    }
+	    if ((getTopLevelAncestor() != null)
+		&& (getTopLevelAncestor() instanceof Window)) {
+	      Window w = (Window) getTopLevelAncestor();
+	      w.dispose();
+	    }
+	  }
+	});
+      
+      setLayout(new BorderLayout());
+
+      if (m_canChangeClassInDialog) {
+	JButton chooseButton = createChooseClassButton();
+	JPanel top = new JPanel();
+	top.setLayout(new BorderLayout());
+	top.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	top.add(chooseButton, BorderLayout.WEST);
+	top.add(m_ClassNameLabel, BorderLayout.CENTER);
+	add(top, BorderLayout.NORTH);
+      } else {
+	add(m_ClassNameLabel, BorderLayout.NORTH);
+      }
+
+      add(m_ChildPropertySheet, BorderLayout.CENTER);
+      // Since we resize to the size of the property sheet, a scrollpane isn't
+      // typically needed
+      // add(new JScrollPane(m_ChildPropertySheet), BorderLayout.CENTER);
+      
+      JPanel okcButs = new JPanel();
+      okcButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+      okcButs.setLayout(new GridLayout(1, 4, 5, 5));
+      okcButs.add(m_OpenBut);
+      okcButs.add(m_SaveBut);
+      okcButs.add(m_okBut);
+      okcButs.add(m_cancelBut);
+      add(okcButs, BorderLayout.SOUTH);
+
+      if (m_ClassType != null) {
+	m_ObjectNames = getClassesFromProperties();
+	if (m_Object != null) {
+	  updateObjectNames();
+	  updateChildPropertySheet();
+	}
+      }
+    }
+    
+    /**
+     * Enables/disables the cancel button.
+     *
+     * @param flag true to enable cancel button, false
+     * to disable it
+     */
+    protected void setCancelButton(boolean flag) {
+
+      if(m_cancelBut != null)
+	m_cancelBut.setEnabled(flag);
+    }
+    
+    /**
+     * Opens an object from a file selected by the user.
+     * 
+     * @return the loaded object, or null if the operation was cancelled
+     */
+    protected Object openObject() {
+      
+      if (m_FileChooser == null) {
+	createFileChooser();
+      }
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	File selected = m_FileChooser.getSelectedFile();
+	try {
+	  ObjectInputStream oi = new ObjectInputStream(new BufferedInputStream(new FileInputStream(selected)));
+	  Object obj = oi.readObject();
+	  oi.close();
+	  if (!m_ClassType.isAssignableFrom(obj.getClass())) {
+	    throw new Exception("Object not of type: " + m_ClassType.getName());
+	  }
+	  return obj;
+	} catch (Exception ex) {
+	  JOptionPane.showMessageDialog(this,
+					"Couldn't read object: "
+					+ selected.getName() 
+					+ "\n" + ex.getMessage(),
+					"Open object file",
+					JOptionPane.ERROR_MESSAGE);
+	}
+      }
+      return null;
+    }
+    
+    /**
+     * Saves an object to a file selected by the user.
+     * 
+     * @param object the object to save
+     */
+    protected void saveObject(Object object) {
+      
+      if (m_FileChooser == null) {
+	createFileChooser();
+      }
+      int returnVal = m_FileChooser.showSaveDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	File sFile = m_FileChooser.getSelectedFile();
+	try {
+	  ObjectOutputStream oo = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(sFile)));
+	  oo.writeObject(object);
+	  oo.close();
+	} catch (Exception ex) {
+	  JOptionPane.showMessageDialog(this,
+					"Couldn't write to file: "
+					+ sFile.getName() 
+					+ "\n" + ex.getMessage(),
+					"Save object",
+					JOptionPane.ERROR_MESSAGE);
+	}
+      }
+    }
+
+    /**
+     * Creates the file chooser the user will use to save/load files with.
+     */
+    protected void createFileChooser() {
+      
+      m_FileChooser = new JFileChooser(new File(System.getProperty("user.dir")));
+      m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    }
+    
+    /**
+     * Makes a copy of an object using serialization.
+     * 
+     * @param source the object to copy
+     * @return a copy of the source object
+     */
+    protected Object copyObject(Object source) {
+
+      Object result = null;
+      try {
+        result = GenericObjectEditor.this.makeCopy(source);
+	setCancelButton(true);
+	
+      } catch (Exception ex) {
+	setCancelButton(false);
+	System.err.println("GenericObjectEditor: Problem making backup object");
+	System.err.println(ex);
+      }
+      return result;
+    }
+    
+    /**
+     * Allows customization of the action label on the dialog.
+     * 
+     * @param newLabel the new string for the ok button
+     */
+    public void setOkButtonText(String newLabel) {
+
+      m_okBut.setText(newLabel);
+    }
+
+    /** 
+     * This is used to hook an action listener to the ok button.
+     * 
+     * @param a The action listener.
+     */
+    public void addOkListener(ActionListener a) {
+
+      m_okBut.addActionListener(a);
+    }
+    
+    /**
+     * This is used to hook an action listener to the cancel button.
+     * 
+     * @param a The action listener.
+     */
+    public void addCancelListener(ActionListener a) {
+
+      m_cancelBut.addActionListener(a);
+    }
+	
+    /**
+     * This is used to remove an action listener from the ok button.
+     * 
+     * @param a The action listener
+     */
+    public void removeOkListener(ActionListener a) {
+
+      m_okBut.removeActionListener(a);
+    }
+    
+    /**
+     * This is used to remove an action listener from the cancel button.
+     * 
+     * @param a The action listener
+     */
+    public void removeCancelListener(ActionListener a) {
+
+      m_cancelBut.removeActionListener(a);
+    }
+    
+    /**
+     * Updates the child property sheet, and creates if needed.
+     */
+    public void updateChildPropertySheet() {
+      
+      // Update the object name displayed
+      String className = "None";
+      if (m_Object != null) {
+	className = m_Object.getClass().getName();
+      }
+      m_ClassNameLabel.setText(className);
+
+      // Set the object as the target of the propertysheet
+      m_ChildPropertySheet.setTarget(m_Object);
+
+      // Adjust size of containing window if possible
+      if ((getTopLevelAncestor() != null)
+	  && (getTopLevelAncestor() instanceof Window)) {
+	((Window) getTopLevelAncestor()).pack();
+      }
+    }	
+  }
+
+  /**
+   * Default constructor.
+   */
+  public GenericObjectEditor() {
+
+    this(false);
+  }
+
+  /**
+   * Constructor that allows specifying whether it is possible
+   * to change the class within the editor dialog.
+   *
+   * @param canChangeClassInDialog whether the user can change the class
+   */
+  public GenericObjectEditor(boolean canChangeClassInDialog) {
+
+    m_canChangeClassInDialog = canChangeClassInDialog;
+  }
+  
+  /**
+   * registers all the editors in Weka.
+   */
+  public static void registerEditors() {
+    Properties 		props;
+    Enumeration 	enm;
+    String 		name;
+    String 		value;
+    Class 		baseCls;
+    Class		cls;
+
+    if (m_EditorsRegistered)
+      return;
+    
+    System.err.println("---Registering Weka Editors---");
+    m_EditorsRegistered = true;
+
+    // load properties
+    try {
+      props = Utils.readProperties(GUIEDITORS_PROPERTY_FILE);
+    }
+    catch (Exception e) {
+      props = new Properties();
+      e.printStackTrace();
+    }
+    
+    enm = props.propertyNames();
+    while (enm.hasMoreElements()) {
+      name  = enm.nextElement().toString();
+      value = props.getProperty(name, "");
+      try {
+	// array class?
+	if (name.endsWith("[]")) {
+	  baseCls = Class.forName(name.substring(0, name.indexOf("[]")));
+	  cls = Array.newInstance(baseCls, 1).getClass();
+	}
+	else {
+	  cls = Class.forName(name);
+	}
+	// register
+	PropertyEditorManager.registerEditor(cls, Class.forName(value));
+      }
+      catch (Exception e) {
+	System.err.println("Problem registering " + name + "/" + value + ": " + e);
+      }
+    }
+  }
+
+  /**
+   * Sets whether the user can change the class in the dialog.
+   * 
+   * @param value	if true then the user can change the class
+   */
+  public void setCanChangeClassInDialog(boolean value) {
+    m_canChangeClassInDialog = value;
+  }
+  
+  /**
+   * Returns whether the user can change the class in the dialog.
+   * 
+   * @return		true if the user can change the class
+   */
+  public boolean getCanChangeClassInDialog() {
+    return m_canChangeClassInDialog;
+  }
+  
+  /**
+   * Returns the backup object (may be null if there is no
+   * backup.
+   *
+   * @return the backup object
+   */
+  public Object getBackup() {
+    return m_Backup;
+  }
+  
+  /**
+   * returns the name of the root element of the given class name, 
+   * <code>null</code> if it doesn't contain the separator.
+   * 
+   * @param clsname the full classname
+   * @param separator the separator 
+   * @return string the root element
+   */
+  protected static String getRootFromClass(String clsname, String separator) {
+    if (clsname.indexOf(separator) > -1)
+      return clsname.substring(0, clsname.indexOf(separator));
+    else
+      return null;
+  }
+  
+  /**
+   * parses the given string of classes separated by ", " and returns the
+   * a hashtable with as many entries as there are different root elements in 
+   * the class names (the key is the root element). E.g. if there's only 
+   * "weka." as the prefix for all classes the a hashtable of size 1 is returned. 
+   * if NULL is the input, then NULL is also returned.
+   * 
+   * @param classes the classnames to work on
+   * @return for each distinct root element in the classnames, one entry in
+   * the hashtable (with the root element as key)
+   */
+  public static Hashtable sortClassesByRoot(String classes) {
+    Hashtable                 roots;
+    Hashtable                 result;
+    Enumeration               enm;
+    int                       i;
+    StringTokenizer           tok;
+    String                    clsname;
+    Vector                    list;
+    HierarchyPropertyParser   hpp;
+    String                    separator;
+    String                    root;
+    String                    tmpStr;
+    
+    if (classes == null)
+      return null;
+    
+    roots     = new Hashtable();
+    hpp       = new HierarchyPropertyParser();
+    separator = hpp.getSeperator();
+    
+    // go over all classnames and store them in the hashtable, with the
+    // root element as the key
+    tok   = new StringTokenizer(classes, ", ");
+    while (tok.hasMoreElements()) {
+      clsname = tok.nextToken();
+      root    = getRootFromClass(clsname, separator);
+      if (root == null)
+        continue;
+      
+      // already stored?
+      if (!roots.containsKey(root)) {
+        list = new Vector();
+        roots.put(root, list);
+      }
+      else {
+        list = (Vector) roots.get(root);
+      }
+      
+      list.add(clsname);
+    }
+    
+    // build result
+    result = new Hashtable();
+    enm    = roots.keys();
+    while (enm.hasMoreElements()) {
+      root = (String) enm.nextElement();
+      list = (Vector) roots.get(root);
+      tmpStr = "";
+      for (i = 0; i < list.size(); i++) {
+        if (i > 0)
+          tmpStr += ",";
+        tmpStr += (String) list.get(i);
+      }
+      result.put(root, tmpStr);
+    }
+      
+    return result;
+  }
+
+  /** 
+   * Called when the class of object being edited changes. 
+   * 
+   * @return the hashtable containing the HierarchyPropertyParsers for the root
+   *         elements
+   */
+  protected Hashtable getClassesFromProperties() {	    
+
+    Hashtable hpps = new Hashtable();
+    String className = m_ClassType.getName();
+    Hashtable typeOptions = sortClassesByRoot(EDITOR_PROPERTIES.getProperty(className));
+    if (typeOptions == null) {
+      /*
+      System.err.println("Warning: No configuration property found in\n"
+			 + PROPERTY_FILE + "\n"
+			 + "for " + className);
+      */
+    } else {		    
+      try {
+        Enumeration enm = typeOptions.keys();
+        while (enm.hasMoreElements()) {
+          String root = (String) enm.nextElement();
+          String typeOption = (String) typeOptions.get(root);
+          HierarchyPropertyParser hpp = new HierarchyPropertyParser();
+          hpp.build(typeOption, ", ");
+	  hpps.put(root, hpp);
+        }
+      } catch (Exception ex) {
+	System.err.println("Invalid property: " + typeOptions);
+      }	    
+    }
+    return hpps;
+  }
+  
+  /**
+   * Updates the list of selectable object names, adding any new names to the list.
+   */
+  protected void updateObjectNames() {
+    
+    if (m_ObjectNames == null) {
+      m_ObjectNames = getClassesFromProperties();
+    }
+    
+    if (m_Object != null) {
+      String className = m_Object.getClass().getName();
+      String root = getRootFromClass(className, new HierarchyPropertyParser().getSeperator());
+      HierarchyPropertyParser hpp = (HierarchyPropertyParser) m_ObjectNames.get(root);
+      if (hpp != null) {
+        if(!hpp.contains(className)){
+          hpp.add(className);
+        }
+      }
+    }
+  }
+  
+  /**
+   * Sets whether the editor is "enabled", meaning that the current
+   * values will be painted.
+   *
+   * @param newVal a value of type 'boolean'
+   */
+  public void setEnabled(boolean newVal) {
+    
+    if (newVal != m_Enabled) {
+      m_Enabled = newVal;
+    }
+  }
+  
+  /**
+   * Sets the class of values that can be edited.
+   *
+   * @param type a value of type 'Class'
+   */
+  public void setClassType(Class type) {
+    
+    m_ClassType = type;
+    m_ObjectNames = getClassesFromProperties();
+  }
+  
+  /**
+   * Sets the current object to be the default, taken as the first item in
+   * the chooser.
+   */
+  public void setDefaultValue() {
+    
+    if (m_ClassType == null) {
+      System.err.println("No ClassType set up for GenericObjectEditor!!");
+      return;
+    }	
+    
+    Hashtable hpps = getClassesFromProperties();
+    HierarchyPropertyParser hpp = null;
+    Enumeration enm = hpps.elements();
+    
+    try{
+      while (enm.hasMoreElements()) {
+        hpp = (HierarchyPropertyParser) enm.nextElement(); 
+        if(hpp.depth() > 0) {		
+          hpp.goToRoot();
+          while(!hpp.isLeafReached())
+            hpp.goToChild(0);
+          
+          String defaultValue = hpp.fullValue();
+          setValue(Class.forName(defaultValue).newInstance());
+        }
+      }
+    }catch(Exception ex){
+      System.err.println("Problem loading the first class: "+
+			 hpp.fullValue());
+      ex.printStackTrace();
+    }
+  }
+  
+  /**
+   * Sets the current Object. If the Object is in the
+   * Object chooser, this becomes the selected item (and added
+   * to the chooser if necessary).
+   *
+   * @param o an object that must be a Object.
+   */
+  public void setValue(Object o) {
+    
+    if (m_ClassType == null) {
+      System.err.println("No ClassType set up for GenericObjectEditor!!");
+      return;
+    }
+    if (!m_ClassType.isAssignableFrom(o.getClass())) {
+      System.err.println("setValue object not of correct type!");
+      return;
+    }
+    
+    setObject(o);
+
+    if (m_EditorComponent != null) m_EditorComponent.repaint();
+
+    updateObjectNames();
+  }
+  
+  /**
+   * Sets the current Object.
+   *
+   * @param c a value of type 'Object'
+   */
+  protected void setObject(Object c) {
+    
+    // This should really call equals() for comparison.
+    boolean trueChange ;
+    if (getValue() != null) {
+      trueChange = (!c.equals(getValue()));
+    }
+    else
+      trueChange = true;
+    
+    m_Backup = m_Object;
+    
+    m_Object = c;
+    
+    if (m_EditorComponent != null) {
+      m_EditorComponent.updateChildPropertySheet();
+    }
+    if (trueChange) {
+      m_Support.firePropertyChange("", null, null);
+    }
+  }
+  
+  /**
+   * Gets the current Object.
+   *
+   * @return the current Object
+   */
+  public Object getValue() {
+    
+    Object result = null;
+    try {
+      result = makeCopy(m_Object);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return result;
+  }
+  
+  /**
+   * Supposedly returns an initialization string to create a Object
+   * identical to the current one, including it's state, but this doesn't
+   * appear possible given that the initialization string isn't supposed to
+   * contain multiple statements.
+   *
+   * @return the java source code initialisation string
+   */
+  public String getJavaInitializationString() {
+
+    return "new " + m_Object.getClass().getName() + "()";
+  }
+
+  /**
+   * Returns true to indicate that we can paint a representation of the
+   * Object.
+   *
+   * @return true
+   */
+  public boolean isPaintable() {
+
+    return true;
+  }
+
+  /**
+   * Paints a representation of the current Object.
+   *
+   * @param gfx the graphics context to use
+   * @param box the area we are allowed to paint into
+   */
+  public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
+
+    if (m_Enabled) {
+      String rep;
+      if (m_Object != null) {
+	rep = m_Object.getClass().getName();
+      } else {
+	rep = "None";
+      }
+      int dotPos = rep.lastIndexOf('.');
+      if (dotPos != -1) {
+	rep = rep.substring(dotPos + 1);
+      }
+      /*
+      if (m_Object instanceof OptionHandler) {
+	rep += " " + Utils.joinOptions(((OptionHandler)m_Object)
+				       .getOptions());
+      }
+      */
+      java.awt.Font originalFont = gfx.getFont();
+      gfx.setFont(originalFont.deriveFont(java.awt.Font.BOLD));
+
+      FontMetrics fm = gfx.getFontMetrics();
+      int vpad = (box.height - fm.getHeight());
+      gfx.drawString(rep, 2, fm.getAscent() + vpad);
+      int repwidth = fm.stringWidth(rep);
+
+      gfx.setFont(originalFont);
+      if (m_Object instanceof OptionHandler) {
+	gfx.drawString(" " + Utils.joinOptions(((OptionHandler)m_Object).getOptions()),
+					       repwidth + 2, fm.getAscent() + vpad);
+      }
+    }
+  }
+
+  /**
+   * Returns null as we don't support getting/setting values as text.
+   *
+   * @return null
+   */
+  public String getAsText() {
+
+    return null;
+  }
+
+  /**
+   * Returns null as we don't support getting/setting values as text. 
+   *
+   * @param text the text value
+   * @throws IllegalArgumentException as we don't support
+   * getting/setting values as text.
+   */
+  public void setAsText(String text) {
+
+    throw new IllegalArgumentException(text);
+  }
+
+  /**
+   * Returns null as we don't support getting values as tags.
+   *
+   * @return null
+   */
+  public String[] getTags() {
+
+    return null;
+  }
+
+  /**
+   * Returns true because we do support a custom editor.
+   *
+   * @return true
+   */
+  public boolean supportsCustomEditor() {
+
+    return true;
+  }
+  
+  /**
+   * Returns the array editing component.
+   *
+   * @return a value of type 'java.awt.Component'
+   */
+  public java.awt.Component getCustomEditor() {
+
+    if (m_EditorComponent == null) {
+      m_EditorComponent = new GOEPanel();
+    }
+    return m_EditorComponent;
+  }
+
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+
+    m_Support.removePropertyChangeListener(l);
+  }
+
+  /**
+   * Gets the custom panel used for editing the object.
+   *
+   * @return the panel
+   */
+  public JPanel getCustomPanel() {
+    final JButton chooseButton = createChooseClassButton();    
+    m_ObjectPropertyPanel = new PropertyPanel(this, true);
+    
+    JPanel customPanel = new JPanel() {
+      public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        chooseButton.setEnabled(enabled);
+      }
+    };
+    customPanel.setLayout(new BorderLayout());
+    customPanel.add(chooseButton, BorderLayout.WEST);
+    customPanel.add(m_ObjectPropertyPanel, BorderLayout.CENTER);
+    return customPanel;
+  }
+
+  /**
+   * Creates a button that when clicked will enable the user to change
+   * the class of the object being edited.
+   * 
+   * @return the choose button
+   */
+  protected JButton createChooseClassButton() {
+
+    JButton setButton = new JButton("Choose");
+
+    // anonymous action listener shows a JTree popup and allows the user
+    // to choose the class they want
+    setButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+
+	  JPopupMenu popup = getChooseClassPopupMenu();
+
+	  // show the popup where the source component is
+	  if (e.getSource() instanceof Component) {
+	    Component comp = (Component) e.getSource();
+	    popup.show(comp, comp.getX(), comp.getY());
+	    popup.pack();
+	    popup.repaint();
+	  }
+	}
+      });
+
+    return setButton;
+  }
+
+  /**
+   * creates a classname from the given path.
+   * 
+   * @param path	the path to generate the classname from
+   * @return		the generated classname
+   */
+  protected String getClassnameFromPath(TreePath path) {
+    StringBuffer classname = new StringBuffer();
+    
+    // recreate class name from path
+    int start = 0;
+    if (m_ObjectNames.size() > 1)
+      start = 1;
+
+    for (int i = start; i < path.getPathCount(); i++) {
+      if (i>start) classname.append(".");
+      classname.append(
+	  (String) ((GOETreeNode) path.getPathComponent(i)).getUserObject());
+    }
+    
+    return classname.toString();
+  }
+  
+  /**
+   * Returns a popup menu that allows the user to change
+   * the class of object.
+   *
+   * @return a JPopupMenu that when shown will let the user choose the class
+   */
+  public JPopupMenu getChooseClassPopupMenu() {
+
+    updateObjectNames();
+
+    // create the tree, and find the path to the current class
+    m_treeNodeOfCurrentObject = null;
+    final JTree tree = createTree(m_ObjectNames);
+    if (m_treeNodeOfCurrentObject != null) {
+      tree.setSelectionPath(new TreePath(m_treeNodeOfCurrentObject.getPath()));
+    }
+    tree.getSelectionModel().setSelectionMode
+      (TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+    // create the popup
+    final JPopupMenu popup = new JTreePopupMenu(tree);
+
+    // respond when the user chooses a class
+    tree.addTreeSelectionListener(new TreeSelectionListener() {
+	public void valueChanged(TreeSelectionEvent e) {
+	  GOETreeNode node = (GOETreeNode) tree.getLastSelectedPathComponent();
+	  
+	  if (node == null) 
+	    return;
+	  
+	  if (node.isLeaf()) {
+	    /*if (node.m_Capabilities != null && m_CapabilitiesFilter != null) {
+	      if (!node.m_Capabilities.supportsMaybe(m_CapabilitiesFilter) && 
+	          !node.m_Capabilities.supports(m_CapabilitiesFilter)) {
+	        return;
+	      }
+	    } */
+	    classSelected(getClassnameFromPath(tree.getSelectionPath()));
+	    popup.setVisible(false);
+	  }
+	}
+      });
+    
+    return popup;
+  }
+
+  /**
+   * Creates a JTree from an object heirarchy.
+   *
+   * @param hpps the hierarchy of objects to mirror in the tree
+   * @return a JTree representation of the hierarchy
+   */
+  protected JTree createTree(Hashtable hpps) {
+    GOETreeNode             superRoot;
+    Enumeration             enm;
+    HierarchyPropertyParser hpp;
+    
+    if (hpps.size() > 1)
+      superRoot = new GOETreeNode("root");
+    else
+      superRoot = null;
+
+    enm = hpps.elements();
+    while (enm.hasMoreElements()) {
+      hpp = (HierarchyPropertyParser) enm.nextElement();
+      hpp.goToRoot();
+      GOETreeNode root = new GOETreeNode(hpp.getValue());
+      addChildrenToTree(root, hpp);
+      
+      if (superRoot == null)
+        superRoot = root;
+      else
+        superRoot.add(root);
+    }
+    
+    JTree tree = new JTree(superRoot);
+    
+    return tree;
+  }
+
+  /**
+   * Recursively builds a JTree from an object heirarchy.
+   * Also updates m_treeNodeOfCurrentObject if the current object
+   * is discovered during creation.
+   *
+   * @param tree the root of the tree to add children to
+   * @param hpp the hierarchy of objects to mirror in the tree
+   */
+  protected void addChildrenToTree(GOETreeNode tree,
+				   HierarchyPropertyParser hpp) {
+
+    try {
+      for (int i=0; i<hpp.numChildren(); i++) {
+	hpp.goToChild(i);
+	GOETreeNode child = new GOETreeNode(hpp.getValue());
+	if ((m_Object != null) &&
+	    m_Object.getClass().getName().equals(hpp.fullValue())) {
+	  m_treeNodeOfCurrentObject = child;
+	}
+	tree.add(child);
+	addChildrenToTree(child, hpp);
+	hpp.goToParent();
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Called when the user selects an class type to change to.
+   *
+   * @param className the name of the class that was selected
+   */
+  protected void classSelected(String className) {
+
+    try {		    
+      if ((m_Object != null) && m_Object.getClass().getName().equals(className)) {
+	return;
+      }
+      
+      setValue(Class.forName(className).newInstance());
+      //m_ObjectPropertyPanel.showPropertyDialog();
+      if (m_EditorComponent != null) {
+	m_EditorComponent.updateChildPropertySheet();
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+				    "Could not create an example of\n"
+				    + className + "\n"
+				    + "from the current classpath",
+				    "Class load failed",
+				    JOptionPane.ERROR_MESSAGE);
+      ex.printStackTrace();
+      try {
+	if(m_Backup != null)
+	  setValue(m_Backup);
+	else
+	  setDefaultValue();			
+      } catch(Exception e) {
+	System.err.println(ex.getMessage());
+	ex.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * Sets the capabilities to use for filtering.
+   * 
+   * @param value	the object to get the filter capabilities from
+   */
+  public void setCapabilitiesFilter(Capabilities value) {
+    m_CapabilitiesFilter = new Capabilities(null);
+    m_CapabilitiesFilter.assign(value);
+  }
+  
+  /**
+   * Returns the current Capabilities filter, can be null.
+   * 
+   * @return		the current Capabiliities used for filtering
+   */
+  public Capabilities getCapabilitiesFilter() {
+    return m_CapabilitiesFilter;
+  }
+  
+  /**
+   * Removes the current Capabilities filter.
+   */
+  public void removeCapabilitiesFilter() {
+    m_CapabilitiesFilter = null;
+  }
+
+  /**
+   * Makes a copy of an object using serialization.
+   * 
+   * @param source the object to copy
+   * @return a copy of the source object
+   * @exception Exception if the copy fails
+   */
+  public static Object makeCopy(Object source) throws Exception {
+    SerializedObject so = new SerializedObject(source);
+    Object result = so.getObject();
+    return result;
+  }
+  
+  /**
+   * Returns the available classnames for a certain property in the 
+   * props file.
+   * 
+   * @param property	the property to get the classnames for
+   * @return		the classnames
+   */
+  public static Vector<String> getClassnames(String property) {
+    Vector<String>	result;
+    String		value;
+    String[]		items;
+    int			i;
+    
+    result = new Vector<String>();
+    
+    value = EDITOR_PROPERTIES.getProperty(property, "").replaceAll(" ", "").trim();
+    if (value.length() > 0) {
+      items = value.split(",");
+      for (i = 0; i < items.length; i++)
+	result.add(items[i]);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tests out the Object editor from the command line.
+   *
+   * @param args may contain the class name of a Object to edit
+   */
+  public static void main(String [] args) {
+
+    try {
+      GenericObjectEditor.registerEditors();
+      GenericObjectEditor ce = new GenericObjectEditor(true);
+      ce.setClassType(weka.classifiers.Classifier.class);
+      Object initial = new weka.classifiers.rules.ZeroR();
+      if (args.length > 0){
+	ce.setClassType(Class.forName(args[0]));
+	if(args.length > 1){
+	  initial = (Object)Class.forName(args[1]).newInstance();
+	  ce.setValue(initial);
+	}
+	else
+	  ce.setDefaultValue();
+      }
+      else	  
+	ce.setValue(initial);
+      
+      PropertyDialog pd = new PropertyDialog((Frame) null, ce, 100, 100);
+      pd.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    PropertyEditor pe = ((PropertyDialog)e.getSource()).getEditor();
+	    Object c = (Object)pe.getValue();
+	    String options = "";
+	    if (c instanceof OptionHandler) {
+	      options = Utils.joinOptions(((OptionHandler)c).getOptions());
+	    }
+	    System.out.println(c.getClass().getName() + " " + options);
+	    System.exit(0);
+	  }
+	});
+      pd.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericObjectEditor.props	(revision 29)
@@ -0,0 +1,546 @@
+# Customises the list of options given by the GenericObjectEditor
+#
+# Thu Jun 19 02:47:27 GMT 2008
+ 
+# Lists the SearchAlgorithms I want to choose from
+weka.classifiers.bayes.net.search.SearchAlgorithm=\
+ weka.classifiers.bayes.net.search.local.GeneticSearch,\
+ weka.classifiers.bayes.net.search.local.HillClimber,\
+ weka.classifiers.bayes.net.search.local.K2,\
+ weka.classifiers.bayes.net.search.local.LAGDHillClimber,\
+ weka.classifiers.bayes.net.search.local.RepeatedHillClimber,\
+ weka.classifiers.bayes.net.search.local.SimulatedAnnealing,\
+ weka.classifiers.bayes.net.search.local.TabuSearch,\
+ weka.classifiers.bayes.net.search.local.TAN,\
+ weka.classifiers.bayes.net.search.ci.CISearchAlgorithm,\
+ weka.classifiers.bayes.net.search.ci.ICSSearchAlgorithm,\
+ weka.classifiers.bayes.net.search.global.GeneticSearch,\
+ weka.classifiers.bayes.net.search.global.HillClimber,\
+ weka.classifiers.bayes.net.search.global.K2,\
+ weka.classifiers.bayes.net.search.global.RepeatedHillClimber,\
+ weka.classifiers.bayes.net.search.global.SimulatedAnnealing,\
+ weka.classifiers.bayes.net.search.global.TabuSearch,\
+ weka.classifiers.bayes.net.search.global.TAN,\
+ weka.classifiers.bayes.net.search.fixed.FromFile,\
+ weka.classifiers.bayes.net.search.fixed.NaiveBayes
+ 
+# Lists the ResultListeners I want to choose from
+weka.experiment.ResultListener=\
+ weka.experiment.CSVResultListener,\
+ weka.experiment.DatabaseResultListener,\
+ weka.experiment.InstancesResultListener
+ 
+# Lists the Savers I want to choose from
+weka.core.converters.Saver=\
+ weka.core.converters.ArffSaver,\
+ weka.core.converters.C45Saver,\
+ weka.core.converters.CSVSaver,\
+ weka.core.converters.DatabaseSaver,\
+ weka.core.converters.LibSVMSaver,\
+ weka.core.converters.MatlabSaver,\
+ weka.core.converters.SerializedInstancesSaver,\
+ weka.core.converters.XRFFSaver
+ 
+# Lists the NearestNeighbourSearchs I want to choose from
+weka.core.neighboursearch.NearestNeighbourSearch=\
+ weka.core.neighboursearch.BallTree,\
+ weka.core.neighboursearch.CoverTree,\
+ weka.core.neighboursearch.KDTree,\
+ weka.core.neighboursearch.LinearNNSearch
+ 
+# Lists the ResultMatrixs I want to choose from
+weka.experiment.ResultMatrix=\
+ weka.experiment.ResultMatrixCSV,\
+ weka.experiment.ResultMatrixGnuPlot,\
+ weka.experiment.ResultMatrixHTML,\
+ weka.experiment.ResultMatrixLatex,\
+ weka.experiment.ResultMatrixPlainText,\
+ weka.experiment.ResultMatrixSignificance
+ 
+# Lists the ClusterDefinitions I want to choose from
+weka.datagenerators.ClusterDefinition=\
+ weka.datagenerators.clusterers.SubspaceClusterDefinition
+ 
+# Lists the BallTreeConstructors I want to choose from
+weka.core.neighboursearch.balltrees.BallTreeConstructor=\
+ weka.core.neighboursearch.balltrees.BottomUpConstructor,\
+ weka.core.neighboursearch.balltrees.MiddleOutConstructor,\
+ weka.core.neighboursearch.balltrees.TopDownConstructor
+ 
+# Lists the ASEvaluations I want to choose from
+weka.attributeSelection.ASEvaluation=\
+ weka.attributeSelection.CfsSubsetEval,\
+ weka.attributeSelection.ChiSquaredAttributeEval,\
+ weka.attributeSelection.ClassifierSubsetEval,\
+ weka.attributeSelection.ConsistencySubsetEval,\
+ weka.attributeSelection.CostSensitiveAttributeEval,\
+ weka.attributeSelection.CostSensitiveSubsetEval,\
+ weka.attributeSelection.FilteredAttributeEval,\
+ weka.attributeSelection.FilteredSubsetEval,\
+ weka.attributeSelection.GainRatioAttributeEval,\
+ weka.attributeSelection.InfoGainAttributeEval,\
+ weka.attributeSelection.LatentSemanticAnalysis,\
+ weka.attributeSelection.OneRAttributeEval,\
+ weka.attributeSelection.PrincipalComponents,\
+ weka.attributeSelection.ReliefFAttributeEval,\
+ weka.attributeSelection.SVMAttributeEval,\
+ weka.attributeSelection.SignificanceAttributeEval,\
+ weka.attributeSelection.SymmetricalUncertAttributeEval,\
+ weka.attributeSelection.SymmetricalUncertAttributeSetEval,\
+ weka.attributeSelection.WrapperSubsetEval
+ 
+# Lists the VisualizePlugins I want to choose from
+weka.gui.visualize.plugins.VisualizePlugin=\
+
+
+# Lists the TreeVisualizePlugins I want to choose from
+weka.gui.visualize.plugins.TreeVisualizePlugin=\
+
+
+# Lists the GraphVisualizePlugins I want to choose from
+weka.gui.visualize.plugins.GraphVisualizePlugin=\
+
+
+# Lists the ErrorVisualizePlugins I want to choose from
+weka.gui.visualize.plugins.ErrorVisualizePlugin=\
+ 
+ 
+# Lists the Loaders I want to choose from
+weka.core.converters.Loader=\
+ weka.core.converters.ArffLoader,\
+ weka.core.converters.C45Loader,\
+ weka.core.converters.CSVLoader,\
+ weka.core.converters.DatabaseLoader,\
+ weka.core.converters.LibSVMLoader,\
+ weka.core.converters.MatlabLoader,\
+ weka.core.converters.SerializedInstancesLoader,\
+ weka.core.converters.TextDirectoryLoader,\
+ weka.core.converters.XRFFLoader
+ 
+# Lists the Tokenizers I want to choose from
+weka.core.tokenizers.Tokenizer=\
+ weka.core.tokenizers.AlphabeticTokenizer,\
+ weka.core.tokenizers.NGramTokenizer,\
+ weka.core.tokenizers.WordTokenizer
+ 
+# Lists the Classifiers I want to choose from
+weka.classifiers.Classifier=\
+ weka.classifiers.bayes.AODE,\
+ weka.classifiers.bayes.AODEsr,\
+ weka.classifiers.bayes.BayesianLogisticRegression,\
+ weka.classifiers.bayes.BayesNet,\
+ weka.classifiers.bayes.ComplementNaiveBayes,\
+ weka.classifiers.bayes.DMNBtext,\
+ weka.classifiers.bayes.HNB,\
+ weka.classifiers.bayes.NaiveBayes,\
+ weka.classifiers.bayes.NaiveBayesMultinomial,\
+ weka.classifiers.bayes.NaiveBayesMultinomialUpdateable,\
+ weka.classifiers.bayes.NaiveBayesSimple,\
+ weka.classifiers.bayes.NaiveBayesUpdateable,\
+ weka.classifiers.bayes.WAODE,\
+ weka.classifiers.functions.GaussianProcesses,\
+ weka.classifiers.functions.IsotonicRegression,\
+ weka.classifiers.functions.LeastMedSq,\
+ weka.classifiers.functions.LibLINEAR,\
+ weka.classifiers.functions.LibSVM,\
+ weka.classifiers.functions.LinearRegression,\
+ weka.classifiers.functions.Logistic,\
+ weka.classifiers.functions.MultilayerPerceptron,\
+ weka.classifiers.functions.PaceRegression,\
+ weka.classifiers.functions.PLSClassifier,\
+ weka.classifiers.functions.RBFNetwork,\
+ weka.classifiers.functions.SimpleLinearRegression,\
+ weka.classifiers.functions.SimpleLogistic,\
+ weka.classifiers.functions.SMO,\
+ weka.classifiers.functions.SMOreg,\
+ weka.classifiers.functions.VotedPerceptron,\
+ weka.classifiers.functions.Winnow,\
+ weka.classifiers.lazy.IB1,\
+ weka.classifiers.lazy.IBk,\
+ weka.classifiers.lazy.KStar,\
+ weka.classifiers.lazy.LBR,\
+ weka.classifiers.lazy.LWL,\
+ weka.classifiers.meta.AdaBoostM1,\
+ weka.classifiers.meta.AdditiveRegression,\
+ weka.classifiers.meta.AttributeSelectedClassifier,\
+ weka.classifiers.meta.Bagging,\
+ weka.classifiers.meta.ClassificationViaClustering,\
+ weka.classifiers.meta.ClassificationViaRegression,\
+ weka.classifiers.meta.CostSensitiveClassifier,\
+ weka.classifiers.meta.CVParameterSelection,\
+ weka.classifiers.meta.Dagging,\
+ weka.classifiers.meta.Decorate,\
+ weka.classifiers.meta.END,\
+ weka.classifiers.meta.EnsembleSelection,\
+ weka.classifiers.meta.FilteredClassifier,\
+ weka.classifiers.meta.Grading,\
+ weka.classifiers.meta.GridSearch,\
+ weka.classifiers.meta.LogitBoost,\
+ weka.classifiers.meta.MetaCost,\
+ weka.classifiers.meta.MultiBoostAB,\
+ weka.classifiers.meta.MultiClassClassifier,\
+ weka.classifiers.meta.MultiScheme,\
+ weka.classifiers.meta.OrdinalClassClassifier,\
+ weka.classifiers.meta.RacedIncrementalLogitBoost,\
+ weka.classifiers.meta.RandomCommittee,\
+ weka.classifiers.meta.RandomSubSpace,\
+ weka.classifiers.meta.RegressionByDiscretization,\
+ weka.classifiers.meta.RotationForest,\
+ weka.classifiers.meta.Stacking,\
+ weka.classifiers.meta.StackingC,\
+ weka.classifiers.meta.ThresholdSelector,\
+ weka.classifiers.meta.Vote,\
+ weka.classifiers.meta.nestedDichotomies.ClassBalancedND,\
+ weka.classifiers.meta.nestedDichotomies.DataNearBalancedND,\
+ weka.classifiers.meta.nestedDichotomies.ND,\
+ weka.classifiers.mi.CitationKNN,\
+ weka.classifiers.mi.MDD,\
+ weka.classifiers.mi.MIBoost,\
+ weka.classifiers.mi.MIDD,\
+ weka.classifiers.mi.MIEMDD,\
+ weka.classifiers.mi.MILR,\
+ weka.classifiers.mi.MINND,\
+ weka.classifiers.mi.MIOptimalBall,\
+ weka.classifiers.mi.MISMO,\
+ weka.classifiers.mi.MISVM,\
+ weka.classifiers.mi.MIWrapper,\
+ weka.classifiers.mi.SimpleMI,\
+ weka.classifiers.mi.TLD,\
+ weka.classifiers.mi.TLDSimple,\
+ weka.classifiers.misc.FLR,\
+ weka.classifiers.misc.HyperPipes,\
+ weka.classifiers.misc.OSDL,\
+ weka.classifiers.misc.SerializedClassifier,\
+ weka.classifiers.misc.VFI,\
+ weka.classifiers.scripting.GroovyClassifier,\
+ weka.classifiers.scripting.JythonClassifier,\
+ weka.classifiers.trees.ADTree,\
+ weka.classifiers.trees.BFTree,\
+ weka.classifiers.trees.DecisionStump,\
+ weka.classifiers.trees.FT,\
+ weka.classifiers.trees.Id3,\
+ weka.classifiers.trees.J48,\
+ weka.classifiers.trees.J48graft,\
+ weka.classifiers.trees.LADTree,\
+ weka.classifiers.trees.LMT,\
+ weka.classifiers.trees.M5P,\
+ weka.classifiers.trees.NBTree,\
+ weka.classifiers.trees.RandomForest,\
+ weka.classifiers.trees.RandomTree,\
+ weka.classifiers.trees.REPTree,\
+ weka.classifiers.trees.SimpleCart,\
+ weka.classifiers.trees.UserClassifier,\
+ weka.classifiers.rules.ConjunctiveRule,\
+ weka.classifiers.rules.DecisionTable,\
+ weka.classifiers.rules.DTNB,\
+ weka.classifiers.rules.JRip,\
+ weka.classifiers.rules.M5Rules,\
+ weka.classifiers.rules.NNge,\
+ weka.classifiers.rules.OLM,\
+ weka.classifiers.rules.OneR,\
+ weka.classifiers.rules.PART,\
+ weka.classifiers.rules.Prism,\
+ weka.classifiers.rules.Ridor,\
+ weka.classifiers.rules.ZeroR
+
+# Lists the AbstractOutput classes I want to choose from
+weka.classifiers.evaluation.output.prediction.AbstractOutput=\
+  weka.classifiers.evaluation.output.prediction.CSV,\
+  weka.classifiers.evaluation.output.prediction.HTML,\
+  weka.classifiers.evaluation.output.prediction.Null,\
+  weka.classifiers.evaluation.output.prediction.PlainText
+ 
+# Lists the DistanceFunctions I want to choose from
+weka.core.DistanceFunction=\
+ weka.core.ChebyshevDistance,\
+ weka.core.EditDistance,\
+ weka.core.EuclideanDistance,\
+ weka.core.ManhattanDistance
+ 
+# Lists the Clusterers I want to choose from
+weka.clusterers.Clusterer=\
+ weka.clusterers.CLOPE,\
+ weka.clusterers.Cobweb,\
+ weka.clusterers.DBScan,\
+ weka.clusterers.EM,\
+ weka.clusterers.FarthestFirst,\
+ weka.clusterers.FilteredClusterer,\
+ weka.clusterers.MakeDensityBasedClusterer,\
+ weka.clusterers.OPTICS,\
+ weka.clusterers.sIB,\
+ weka.clusterers.SimpleKMeans,\
+ weka.clusterers.XMeans
+ 
+# Lists the UnsupervisedFilters I want to choose from
+weka.filters.UnsupervisedFilter=\
+ weka.filters.unsupervised.attribute.Add,\
+ weka.filters.unsupervised.attribute.AddCluster,\
+ weka.filters.unsupervised.attribute.AddExpression,\
+ weka.filters.unsupervised.attribute.AddID,\
+ weka.filters.unsupervised.attribute.AddNoise,\
+ weka.filters.unsupervised.attribute.AddValues,\
+ weka.filters.unsupervised.attribute.Center,\
+ weka.filters.unsupervised.attribute.ChangeDateFormat,\
+ weka.filters.unsupervised.attribute.ClusterMembership,\
+ weka.filters.unsupervised.attribute.Copy,\
+ weka.filters.unsupervised.attribute.Discretize,\
+ weka.filters.unsupervised.attribute.EMImputation,\
+ weka.filters.unsupervised.attribute.FirstOrder,\
+ weka.filters.unsupervised.attribute.KernelFilter,\
+ weka.filters.unsupervised.attribute.MakeIndicator,\
+ weka.filters.unsupervised.attribute.MathExpression,\
+ weka.filters.unsupervised.attribute.MergeTwoValues,\
+ weka.filters.unsupervised.attribute.MultiInstanceToPropositional,\
+ weka.filters.unsupervised.attribute.NominalToBinary,\
+ weka.filters.unsupervised.attribute.NominalToString,\
+ weka.filters.unsupervised.attribute.Normalize,\
+ weka.filters.unsupervised.attribute.NumericToBinary,\
+ weka.filters.unsupervised.attribute.NumericTransform,\
+ weka.filters.unsupervised.attribute.Obfuscate,\
+ weka.filters.unsupervised.attribute.PKIDiscretize,\
+ weka.filters.unsupervised.attribute.PrincipalComponents,\
+ weka.filters.unsupervised.attribute.PropositionalToMultiInstance,\
+ weka.filters.unsupervised.attribute.RandomProjection,\
+ weka.filters.unsupervised.attribute.Remove,\
+ weka.filters.unsupervised.attribute.RemoveType,\
+ weka.filters.unsupervised.attribute.RemoveUseless,\
+ weka.filters.unsupervised.attribute.Reorder,\
+ weka.filters.unsupervised.attribute.ReplaceMissingValues,\
+ weka.filters.unsupervised.attribute.Standardize,\
+ weka.filters.unsupervised.attribute.StringToNominal,\
+ weka.filters.unsupervised.attribute.StringToWordVector,\
+ weka.filters.unsupervised.attribute.SwapValues,\
+ weka.filters.unsupervised.attribute.TimeSeriesDelta,\
+ weka.filters.unsupervised.attribute.TimeSeriesTranslate,\
+ weka.filters.unsupervised.attribute.Wavelet,\
+ weka.filters.unsupervised.instance.NonSparseToSparse,\
+ weka.filters.unsupervised.instance.Normalize,\
+ weka.filters.unsupervised.instance.Randomize,\
+ weka.filters.unsupervised.instance.RemoveFolds,\
+ weka.filters.unsupervised.instance.RemoveFrequentValues,\
+ weka.filters.unsupervised.instance.RemoveMisclassified,\
+ weka.filters.unsupervised.instance.RemovePercentage,\
+ weka.filters.unsupervised.instance.RemoveRange,\
+ weka.filters.unsupervised.instance.RemoveWithValues,\
+ weka.filters.unsupervised.instance.Resample,\
+ weka.filters.unsupervised.instance.ReservoirSample,\
+ weka.filters.unsupervised.instance.SparseToNonSparse,\
+ weka.filters.unsupervised.instance.SubsetByExpression
+ 
+# Lists the RegOptimizers I want to choose from
+weka.classifiers.functions.supportVector.RegOptimizer=\
+ weka.classifiers.functions.supportVector.RegSMO,\
+ weka.classifiers.functions.supportVector.RegSMOImproved
+ 
+# Lists the JComponentWriters I want to choose from
+weka.gui.visualize.JComponentWriter=\
+ weka.gui.visualize.BMPWriter,\
+ weka.gui.visualize.JPEGWriter,\
+ weka.gui.visualize.PNGWriter,\
+ weka.gui.visualize.PostscriptWriter
+ 
+# Lists the Stemmers I want to choose from
+weka.core.stemmers.Stemmer=\
+ weka.core.stemmers.IteratedLovinsStemmer,\
+ weka.core.stemmers.LovinsStemmer,\
+ weka.core.stemmers.NullStemmer,\
+ weka.core.stemmers.SnowballStemmer
+ 
+# Lists the Kernels I want to choose from
+weka.classifiers.functions.supportVector.Kernel=\
+ weka.classifiers.functions.supportVector.NormalizedPolyKernel,\
+ weka.classifiers.functions.supportVector.PolyKernel,\
+ weka.classifiers.functions.supportVector.PrecomputedKernelMatrixKernel,\
+ weka.classifiers.functions.supportVector.Puk,\
+ weka.classifiers.functions.supportVector.RBFKernel,\
+ weka.classifiers.functions.supportVector.StringKernel,\
+ weka.classifiers.mi.supportVector.MIPolyKernel,\
+ weka.classifiers.mi.supportVector.MIRBFKernel
+ 
+# Lists the Testers I want to choose from
+weka.experiment.Tester=\
+ weka.experiment.PairedCorrectedTTester,\
+ weka.experiment.PairedTTester
+ 
+# Lists the ASSearchs I want to choose from
+weka.attributeSelection.ASSearch=\
+ weka.attributeSelection.BestFirst,\
+ weka.attributeSelection.ExhaustiveSearch,\
+ weka.attributeSelection.FCBFSearch,\
+ weka.attributeSelection.GeneticSearch,\
+ weka.attributeSelection.GreedyStepwise,\
+ weka.attributeSelection.LinearForwardSelection,\
+ weka.attributeSelection.RaceSearch,\
+ weka.attributeSelection.RandomSearch,\
+ weka.attributeSelection.Ranker,\
+ weka.attributeSelection.RankSearch,\
+ weka.attributeSelection.ScatterSearchV1,\
+ weka.attributeSelection.SubsetSizeForwardSelection
+ 
+# Lists the SplitEvaluators I want to choose from
+weka.experiment.SplitEvaluator=\
+ weka.experiment.ClassifierSplitEvaluator,\
+ weka.experiment.CostSensitiveClassifierSplitEvaluator,\
+ weka.experiment.DensityBasedClustererSplitEvaluator,\
+ weka.experiment.RegressionSplitEvaluator
+ 
+# Lists the DataGenerators I want to choose from
+weka.datagenerators.DataGenerator=\
+ weka.datagenerators.classifiers.classification.Agrawal,\
+ weka.datagenerators.classifiers.classification.BayesNet,\
+ weka.datagenerators.classifiers.classification.LED24,\
+ weka.datagenerators.classifiers.classification.RandomRBF,\
+ weka.datagenerators.classifiers.classification.RDG1,\
+ weka.datagenerators.classifiers.regression.Expression,\
+ weka.datagenerators.classifiers.regression.MexicanHat,\
+ weka.datagenerators.clusterers.BIRCHCluster,\
+ weka.datagenerators.clusterers.SubspaceCluster
+ 
+# Lists the AbstractFileLoaders I want to choose from
+weka.core.converters.AbstractFileLoader=\
+ weka.core.converters.ArffLoader,\
+ weka.core.converters.C45Loader,\
+ weka.core.converters.CSVLoader,\
+ weka.core.converters.LibSVMLoader,\
+ weka.core.converters.MatlabLoader,\
+ weka.core.converters.SerializedInstancesLoader,\
+ weka.core.converters.XRFFLoader
+ 
+# Lists the AbstractFileSavers I want to choose from
+weka.core.converters.AbstractFileSaver=\
+ weka.core.converters.ArffSaver,\
+ weka.core.converters.C45Saver,\
+ weka.core.converters.CSVSaver,\
+ weka.core.converters.LibSVMSaver,\
+ weka.core.converters.MatlabSaver,\
+ weka.core.converters.SerializedInstancesSaver,\
+ weka.core.converters.XRFFSaver
+ 
+# Lists the BayesNetEstimators I want to choose from
+weka.classifiers.bayes.net.estimate.BayesNetEstimator=\
+ weka.classifiers.bayes.net.estimate.BayesNetEstimator,\
+ weka.classifiers.bayes.net.estimate.BMAEstimator,\
+ weka.classifiers.bayes.net.estimate.MultiNomialBMAEstimator,\
+ weka.classifiers.bayes.net.estimate.SimpleEstimator
+ 
+# Lists the MainMenuExtensions I want to choose from
+weka.gui.MainMenuExtension=\
+ 
+ 
+# Lists the KDTreeNodeSplitters I want to choose from
+weka.core.neighboursearch.kdtrees.KDTreeNodeSplitter=\
+ weka.core.neighboursearch.kdtrees.KMeansInpiredMethod,\
+ weka.core.neighboursearch.kdtrees.MedianOfWidestDimension,\
+ weka.core.neighboursearch.kdtrees.MidPointOfWidestDimension,\
+ weka.core.neighboursearch.kdtrees.SlidingMidPointOfWidestSide
+ 
+# Lists the Associators I want to choose from
+weka.associations.Associator=\
+ weka.associations.Apriori,\
+ weka.associations.FilteredAssociator,\
+ weka.associations.GeneralizedSequentialPatterns,\
+ weka.associations.HotSpot,\
+ weka.associations.PredictiveApriori,\
+ weka.associations.Tertius
+ 
+# Lists the ResultProducers I want to choose from
+weka.experiment.ResultProducer=\
+ weka.experiment.AveragingResultProducer,\
+ weka.experiment.CrossValidationResultProducer,\
+ weka.experiment.DatabaseResultProducer,\
+ weka.experiment.LearningRateResultProducer,\
+ weka.experiment.RandomSplitResultProducer
+ 
+# Lists the DensityBasedClusterers I want to choose from
+weka.clusterers.DensityBasedClusterer=\
+ weka.clusterers.EM,\
+ weka.clusterers.MakeDensityBasedClusterer
+ 
+# Lists the Filters I want to choose from
+weka.filters.Filter=\
+ weka.filters.AllFilter,\
+ weka.filters.MultiFilter,\
+ weka.filters.supervised.attribute.AddClassification,\
+ weka.filters.supervised.attribute.AttributeSelection,\
+ weka.filters.supervised.attribute.ClassOrder,\
+ weka.filters.supervised.attribute.Discretize,\
+ weka.filters.supervised.attribute.NominalToBinary,\
+ weka.filters.supervised.attribute.PLSFilter,\
+ weka.filters.supervised.instance.Resample,\
+ weka.filters.supervised.instance.SpreadSubsample,\
+ weka.filters.supervised.instance.StratifiedRemoveFolds,\
+ weka.filters.supervised.instance.SMOTE,\
+ weka.filters.unsupervised.attribute.Add,\
+ weka.filters.unsupervised.attribute.AddCluster,\
+ weka.filters.unsupervised.attribute.AddExpression,\
+ weka.filters.unsupervised.attribute.AddID,\
+ weka.filters.unsupervised.attribute.AddNoise,\
+ weka.filters.unsupervised.attribute.AddValues,\
+ weka.filters.unsupervised.attribute.Center,\
+ weka.filters.unsupervised.attribute.ChangeDateFormat,\
+ weka.filters.unsupervised.attribute.ClassAssigner,\
+ weka.filters.unsupervised.attribute.ClusterMembership,\
+ weka.filters.unsupervised.attribute.Copy,\
+ weka.filters.unsupervised.attribute.Discretize,\
+ weka.filters.unsupervised.attribute.EMImputation,\
+ weka.filters.unsupervised.attribute.FirstOrder,\
+ weka.filters.unsupervised.attribute.InterquartileRange,\
+ weka.filters.unsupervised.attribute.KernelFilter,\
+ weka.filters.unsupervised.attribute.MakeIndicator,\
+ weka.filters.unsupervised.attribute.MathExpression,\
+ weka.filters.unsupervised.attribute.MergeTwoValues,\
+ weka.filters.unsupervised.attribute.MultiInstanceToPropositional,\
+ weka.filters.unsupervised.attribute.NominalToBinary,\
+ weka.filters.unsupervised.attribute.NominalToString,\
+ weka.filters.unsupervised.attribute.Normalize,\
+ weka.filters.unsupervised.attribute.NumericCleaner,\
+ weka.filters.unsupervised.attribute.NumericToBinary,\
+ weka.filters.unsupervised.attribute.NumericToNominal,\
+ weka.filters.unsupervised.attribute.NumericTransform,\
+ weka.filters.unsupervised.attribute.Obfuscate,\
+ weka.filters.unsupervised.attribute.PartitionedMultiFilter,\
+ weka.filters.unsupervised.attribute.PKIDiscretize,\
+ weka.filters.unsupervised.attribute.PrincipalComponents,\
+ weka.filters.unsupervised.attribute.PropositionalToMultiInstance,\
+ weka.filters.unsupervised.attribute.RandomProjection,\
+ weka.filters.unsupervised.attribute.RandomSubset,\
+ weka.filters.unsupervised.attribute.RELAGGS,\
+ weka.filters.unsupervised.attribute.Remove,\
+ weka.filters.unsupervised.attribute.RemoveType,\
+ weka.filters.unsupervised.attribute.RemoveUseless,\
+ weka.filters.unsupervised.attribute.Reorder,\
+ weka.filters.unsupervised.attribute.ReplaceMissingValues,\
+ weka.filters.unsupervised.attribute.Standardize,\
+ weka.filters.unsupervised.attribute.StringToNominal,\
+ weka.filters.unsupervised.attribute.StringToWordVector,\
+ weka.filters.unsupervised.attribute.SwapValues,\
+ weka.filters.unsupervised.attribute.TimeSeriesDelta,\
+ weka.filters.unsupervised.attribute.TimeSeriesTranslate,\
+ weka.filters.unsupervised.attribute.Wavelet,\
+ weka.filters.unsupervised.instance.NonSparseToSparse,\
+ weka.filters.unsupervised.instance.Normalize,\
+ weka.filters.unsupervised.instance.Randomize,\
+ weka.filters.unsupervised.instance.RemoveFolds,\
+ weka.filters.unsupervised.instance.RemoveFrequentValues,\
+ weka.filters.unsupervised.instance.RemoveMisclassified,\
+ weka.filters.unsupervised.instance.RemovePercentage,\
+ weka.filters.unsupervised.instance.RemoveRange,\
+ weka.filters.unsupervised.instance.RemoveWithValues,\
+ weka.filters.unsupervised.instance.Resample,\
+ weka.filters.unsupervised.instance.ReservoirSample,\
+ weka.filters.unsupervised.instance.SparseToNonSparse,\
+ weka.filters.unsupervised.instance.SubsetByExpression
+ 
+# Lists the Estimators I want to choose from
+weka.estimators.Estimator=\
+ weka.estimators.DiscreteEstimator,\
+ weka.estimators.KernelEstimator,\
+ weka.estimators.MahalanobisEstimator,\
+ weka.estimators.NormalEstimator,\
+ weka.estimators.PoissonEstimator
+ 
+# Lists the BallSplitters I want to choose from
+weka.core.neighboursearch.balltrees.BallSplitter=\
+ weka.core.neighboursearch.balltrees.MedianDistanceFromArbitraryPoint,\
+ weka.core.neighboursearch.balltrees.MedianOfWidestDimension,\
+ weka.core.neighboursearch.balltrees.PointsClosestToFurthestChildren
Index: branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.excludes
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.excludes	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.excludes	(revision 29)
@@ -0,0 +1,31 @@
+# Corresponds to GenericPropertiesCreator.props, but lists classes
+# that shouldn't appear in the GUI.
+#
+# Format:
+# <key>=<prefix>:<class>[,<prefix>:<class>]
+#
+# <key>     the key from GenericPropertiesCreator.props (class or interface)
+# <prefix>  S ("Superclass"): any class derived from this one will be
+#                             excluded
+#           I ("Interface"):  any class implementing this interface will be
+#                             excluded
+#           C ("Class"):      exactly this class will be excluded
+# <class>   the classname
+#
+# Author : fracpete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 1.3 $
+
+# ResultListeners
+weka.experiment.ResultListener=\
+  I:weka.experiment.ResultProducer
+
+# Search algorithms of the Bayes net package
+weka.classifiers.bayes.net.search.SearchAlgorithm=\
+  C:weka.classifiers.bayes.net.search.local.LocalScoreSearchAlgorithm,\
+  C:weka.classifiers.bayes.net.search.global.GlobalScoreSearchAlgorithm,\
+  C:weka.classifiers.bayes.net.search.ci.CIScoreSearchAlgorithm
+
+# SVMreg learning algorithms
+weka.classifiers.functions.supportVector.RegOptimizer=\
+ C:weka.classifiers.functions.supportVector.RegOptimizer
+
Index: branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.java	(revision 29)
@@ -0,0 +1,587 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GenericPropertiesCreator.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui;
+
+import weka.core.ClassDiscovery;
+import weka.core.Utils;
+import weka.core.ClassDiscovery.StringCompare;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class can generate the properties object that is normally loaded from
+ * the <code>GenericObjectEditor.props</code> file (= PROPERTY_FILE). It takes
+ * the <code>GenericPropertiesCreator.props</code> file as a template to
+ * determine all the derived classes by checking the classes in the given
+ * packages (a file with the same name in your home directory overrides the
+ * the one in the weka/gui directory/package). <br>
+ * E.g. if we want to have all the subclasses of the <code>Classifier</code>
+ * class then we specify the superclass ("weka.classifiers.Classifier") and the
+ * packages where to look for ("weka.classifiers.bayes" etc.):
+ * 
+ * <pre>
+ * 
+ *   weka.classifiers.Classifier=\
+ *     weka.classifiers.bayes,\
+ *     weka.classifiers.functions,\
+ *     weka.classifiers.lazy,\
+ *     weka.classifiers.meta,\
+ *     weka.classifiers.trees,\
+ *     weka.classifiers.rules
+ *  
+ * </pre>
+ * 
+ * This creates the same list as stored in the
+ * <code>GenericObjectEditor.props</code> file, but it will also add
+ * additional classes, that are not listed in the static list (e.g. a newly
+ * developed Classifier), but still in the classpath. <br>
+ * <br>
+ * For discovering the subclasses the whole classpath is inspected, which means
+ * that you can have several parallel directories with the same package
+ * structure (e.g. a release directory and a developer directory with additional
+ * classes). <br>
+ * <br>
+ * The dynamic discovery can be turned off via the <code>UseDyanmic</code>
+ * property in the props file (values: true|false).
+ * 
+ * @see #CREATOR_FILE
+ * @see #PROPERTY_FILE
+ * @see #USE_DYNAMIC
+ * @see GenericObjectEditor
+ * @see weka.core.ClassDiscovery
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5968 $
+ */
+public class GenericPropertiesCreator {
+  
+  /** whether to output some debug information */
+  public final static boolean VERBOSE = false;
+
+  /** name of property whether to use the dynamic approach or the old 
+   * GenericObjectEditor.props file */
+  public final static String USE_DYNAMIC = "UseDynamic";
+  
+  /** The name of the properties file to use as a template. Contains the 
+   * packages in which to look for derived classes. It has the same structure
+   * as the <code>PROPERTY_FILE</code>
+   * @see #PROPERTY_FILE
+   */
+  protected static String CREATOR_FILE = "weka/gui/GenericPropertiesCreator.props";
+  
+  /** The name of the properties file that lists classes/interfaces/superclasses
+   * to exclude from being shown in the GUI. See the file for more information.
+   */
+  protected static String EXCLUDE_FILE = "weka/gui/GenericPropertiesCreator.excludes";
+
+  /** the prefix for an interface exclusion */
+  protected static String EXCLUDE_INTERFACE = "I";
+
+  /** the prefix for an (exact) class exclusion */
+  protected static String EXCLUDE_CLASS = "C";
+
+  /** the prefix for a superclass exclusion */
+  protected static String EXCLUDE_SUPERCLASS = "S";
+  
+  /** The name of the properties file for the static GenericObjectEditor 
+   * (<code>USE_DYNAMIC</code> = <code>false</code>) 
+   * @see GenericObjectEditor 
+   * @see #USE_DYNAMIC 
+   */
+  protected static String PROPERTY_FILE = "weka/gui/GenericObjectEditor.props";
+  
+  /** the input file with the packages */
+  protected String m_InputFilename;
+  
+  /** the output props file for the GenericObjectEditor */
+  protected String m_OutputFilename;
+  
+  /** the "template" properties file with the layout and the packages */
+  protected Properties m_InputProperties;
+  
+  /** the output properties file with the filled in classes */
+  protected Properties m_OutputProperties;
+  
+  /** whether an explicit input file was given - if false, the Utils class
+   * is used to locate the props-file */
+  protected boolean m_ExplicitPropsFile;
+  
+  /** the hashtable that stores the excludes: 
+   * key -&gt; Hashtable(prefix -&gt; Vector of classnames) */
+  protected Hashtable m_Excludes;
+  
+  /**
+   * initializes the creator, locates the props file with the Utils class.
+   * 
+   * @throws Exception if loading of CREATOR_FILE fails
+   * @see #CREATOR_FILE
+   * @see Utils#readProperties(String)
+   * @see #loadInputProperties()
+   */
+  public GenericPropertiesCreator() throws Exception {
+    this(CREATOR_FILE);
+    m_ExplicitPropsFile = false;
+  }
+
+  /**
+   * initializes the creator, the given file overrides the props-file search
+   * of the Utils class
+   * 
+   * @param filename the file containing the packages to create a props file from
+   * @throws Exception if loading of the file fails
+   * @see #CREATOR_FILE
+   * @see Utils#readProperties(String)
+   * @see #loadInputProperties()
+   */
+  public GenericPropertiesCreator(String filename) throws Exception {
+    super();
+    m_InputFilename     = filename;
+    m_OutputFilename    = PROPERTY_FILE;
+    m_InputProperties   = null;
+    m_OutputProperties  = null;
+    m_ExplicitPropsFile = true;
+    m_Excludes          = new Hashtable();
+  }
+
+  /**
+   * if FALSE, the locating of a props-file of the Utils-class is used, 
+   * otherwise it's tried to load the specified file
+   * 
+   * @param value 	if true the specified file will be loaded not via the 
+   * 			Utils-class
+   * @see Utils#readProperties(String)
+   * @see #loadInputProperties()
+   */
+  public void setExplicitPropsFile(boolean value) {
+    m_ExplicitPropsFile = value;
+  }
+  
+  /**
+   * returns TRUE, if a file is loaded and not the Utils class used for 
+   * locating the props file.
+   * 
+   * @return	true if the specified file is used and not the one found by
+   * 		the Utils class
+   * @see Utils#readProperties(String)
+   * @see #loadInputProperties()
+   */
+  public boolean getExplicitPropsFile() {
+    return m_ExplicitPropsFile;
+  }
+  
+  /**
+   * returns the name of the output file
+   * 
+   * @return the name of the output file 
+   */
+  public String getOutputFilename() {
+    return m_OutputFilename;
+  }
+  
+  /**
+   * sets the file to output the properties for the GEO to
+   * 
+   * @param filename the filename for the output 
+   */
+  public void setOutputFilename(String filename) {
+    m_OutputFilename = filename;
+  }
+
+  /**
+   * returns the name of the input file
+   * 
+   * @return the name of the input file 
+   */
+  public String getInputFilename() {
+    return m_InputFilename;
+  }
+  
+  /**
+   * sets the file to get the information about the packages from. 
+   * automatically sets explicitPropsFile to TRUE.
+   * 
+   * @param filename the filename for the input 
+   * @see #setExplicitPropsFile(boolean)
+   */
+  public void setInputFilename(String filename) {
+    m_InputFilename = filename;
+    setExplicitPropsFile(true);
+  }
+  
+  /**
+   * returns the input properties object (template containing the packages)
+   * 
+   * @return		the input properties (the template)
+   */
+  public Properties getInputProperties() {
+    return m_InputProperties;
+  }
+  
+  /**
+   * returns the output properties object (structure like the template, but
+   * filled with classes instead of packages)
+   * 
+   * @return		the output properties (filled with classes)
+   */
+  public Properties getOutputProperties() {
+    return m_OutputProperties;
+  }
+  
+  /**
+   * loads the property file containing the layout and the packages of 
+   * the output-property-file. The exlcude property file is also read here.
+   * 
+   * @see #m_InputProperties
+   * @see #m_InputFilename
+   */
+  protected void loadInputProperties()  {
+    if (VERBOSE)
+      System.out.println("Loading '" + getInputFilename() + "'...");
+    m_InputProperties = new Properties();
+    try {
+      File f = new File(getInputFilename());
+      if (getExplicitPropsFile() && f.exists())
+        m_InputProperties.load(new FileInputStream(getInputFilename()));
+      else
+        m_InputProperties = Utils.readProperties(getInputFilename());
+      
+      // excludes
+      m_Excludes.clear();
+      Properties p = Utils.readProperties(EXCLUDE_FILE);
+      Enumeration enm = p.propertyNames();
+      while (enm.hasMoreElements()) {
+	String name = enm.nextElement().toString();
+	// new Hashtable for key
+	Hashtable t = new Hashtable();
+	m_Excludes.put(name, t);
+	t.put(EXCLUDE_INTERFACE, new Vector());
+	t.put(EXCLUDE_CLASS, new Vector());
+	t.put(EXCLUDE_SUPERCLASS, new Vector());
+	
+	// process entries
+	StringTokenizer tok = new StringTokenizer(p.getProperty(name), ",");
+	while (tok.hasMoreTokens()) {
+	  String item = tok.nextToken();
+	  // get list
+	  Vector list = new Vector();
+	  if (item.startsWith(EXCLUDE_INTERFACE + ":"))
+	    list = (Vector) t.get(EXCLUDE_INTERFACE);
+	  else if (item.startsWith(EXCLUDE_CLASS + ":"))
+	    list = (Vector) t.get(EXCLUDE_CLASS);
+	  else if (item.startsWith(EXCLUDE_SUPERCLASS))
+	    list = (Vector) t.get(EXCLUDE_SUPERCLASS);
+	  // add to list
+	  list.add(item.substring(item.indexOf(":") + 1));
+	}
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * gets whether the dynamic approach should be used or not
+   * 
+   * @return		true if the dynamic approach is to be used
+   */
+  public boolean useDynamic() {
+    if (getInputProperties() == null)
+      loadInputProperties();
+    
+    // check our classloader against the system one - if different then
+    // return false (as dynamic classloading only works for classes discoverable
+    // in the system classpath
+    /*if (!ClassLoader.getSystemClassLoader().equals(this.getClass().getClassLoader())) {
+      if (Boolean.parseBoolean(getInputProperties().getProperty(USE_DYNAMIC, "true")) == true) {
+        System.out.println("[GenericPropertiesCreator] classloader in use is not the system "
+            + "classloader: using static entries in weka/gui/GenericObjectEditor.props rather "
+            + "than dynamic class discovery.");
+      }
+      return false;
+    }*/
+    
+    return Boolean.parseBoolean(
+	getInputProperties().getProperty(USE_DYNAMIC, "true"));
+  }
+  
+  /**
+   * checks whether the classname is a valid one, i.e., from a public class
+   *
+   * @param classname   the classname to check
+   * @return            whether the classname is a valid one
+   */
+  protected boolean isValidClassname(String classname) {
+    return (classname.indexOf("$") == -1);
+  }
+
+  /**
+   * Checks whether the classname is a valid one for the given key. This is
+   * based on the settings in the Exclude file.
+   *
+   * @param key         the property key
+   * @param classname   the classname to check
+   * @return            whether the classname is a valid one
+   * @see		#EXCLUDE_FILE
+   */
+  protected boolean isValidClassname(String key, String classname) {
+    boolean	result;
+    Class	cls;
+    Class	clsCurrent;
+    Vector	list;
+    int		i;
+
+    result = true;
+
+    // are there excludes for this key?
+    if (m_Excludes.containsKey(key)) {
+      try {
+	clsCurrent = Class.forName(classname);
+      }
+      catch (Exception e) {
+	// we ignore this Exception
+	clsCurrent = null;
+      }
+      
+      // interface
+      if ((clsCurrent != null) && result) {
+	list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_INTERFACE);
+	for (i = 0; i < list.size(); i++) {
+	  try {
+	    cls = Class.forName(list.get(i).toString());
+	    if (ClassDiscovery.hasInterface(cls, clsCurrent)) {
+	      result = false;
+	      break;
+	    }
+	  }
+	  catch (Exception e) {
+	    // we ignore this Exception
+	  }
+	}
+      }
+      
+      // superclass
+      if ((clsCurrent != null) && result) {
+	list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_SUPERCLASS);
+	for (i = 0; i < list.size(); i++) {
+	  try {
+	    cls = Class.forName(list.get(i).toString());
+	    if (ClassDiscovery.isSubclass(cls, clsCurrent)) {
+	      result = false;
+	      break;
+	    }
+	  }
+	  catch (Exception e) {
+	    // we ignore this Exception
+	  }
+	}
+      }
+      
+      // class
+      if ((clsCurrent != null) && result) {
+	list = (Vector) ((Hashtable) m_Excludes.get(key)).get(EXCLUDE_CLASS);
+	for (i = 0; i < list.size(); i++) {
+	  try {
+	    cls = Class.forName(list.get(i).toString());
+	    if (cls.getName().equals(clsCurrent.getName()))
+	      result = false;
+	  }
+	  catch (Exception e) {
+	    // we ignore this Exception
+	  }
+	}
+      }
+    }
+
+    return result;
+  }
+  
+  /**
+   * fills in all the classes (based on the packages in the input properties 
+   * file) into the output properties file
+   *     
+   * @throws Exception 	if something goes wrong
+   * @see #m_OutputProperties
+   */
+  protected void generateOutputProperties() throws Exception {
+    Enumeration       keys;
+    String            key;
+    String            value;
+    String            pkg;
+    StringTokenizer   tok;
+    Vector            classes;
+    HashSet           names;
+    int               i;
+    
+    m_OutputProperties = new Properties();
+    keys               = m_InputProperties.propertyNames();
+    while (keys.hasMoreElements()) {
+      key   = keys.nextElement().toString();
+      if (key.equals(USE_DYNAMIC))
+	continue;
+      tok   = new StringTokenizer(m_InputProperties.getProperty(key), ",");
+      names = new HashSet();
+      
+      // get classes for all packages
+      while (tok.hasMoreTokens()) {
+        pkg = tok.nextToken().trim();
+        
+        try {
+          classes = ClassDiscovery.find(Class.forName(key), pkg);
+        }
+        catch (Exception e) {
+          System.out.println("Problem with '" + key + "': " + e);
+          classes = new Vector();
+        }
+        
+        for (i = 0; i < classes.size(); i++) {
+          // skip non-public classes
+          if (!isValidClassname(classes.get(i).toString()))
+            continue;
+          // some classes should not be listed for some keys
+          if (!isValidClassname(key, classes.get(i).toString()))
+            continue;
+          names.add(classes.get(i));
+        }
+      }
+      
+      // generate list
+      value   = "";
+      classes = new Vector();
+      classes.addAll(names);
+      Collections.sort(classes, new StringCompare());
+      for (i = 0; i < classes.size(); i++) {
+        if (!value.equals(""))
+          value += ",";
+        value += classes.get(i).toString();
+      }
+      if (VERBOSE)
+        System.out.println(pkg + " -> " + value);
+      
+      // set value
+      m_OutputProperties.setProperty(key, value);
+    }
+  }
+  
+  /**
+   * stores the generated output properties file
+   * 
+   * @throws Exception	if the saving fails
+   * @see #m_OutputProperties
+   * @see #m_OutputFilename 
+   */
+  protected void storeOutputProperties() throws Exception {
+    if (VERBOSE)
+      System.out.println("Saving '" + getOutputFilename() + "'...");
+    m_OutputProperties.store(
+        new FileOutputStream(getOutputFilename()), 
+        " Customises the list of options given by the GenericObjectEditor\n# for various superclasses.");
+  }
+  
+  /**
+   * generates the props-file for the GenericObjectEditor and stores it
+   * 
+   * @throws Exception	if something goes wrong
+   * @see #execute(boolean)
+   */
+  public void execute() throws Exception {
+    execute(true);
+  }
+  
+  /**
+   * generates the props-file for the GenericObjectEditor and stores it only
+   * if the the param <code>store</code> is TRUE. If it is FALSE then the
+   * generated properties file can be retrieved via the <code>getOutputProperties</code>
+   * method. 
+   * 
+   * @param store     	if TRUE then the properties file is stored to the stored 
+   *                  	filename
+   * @throws Exception	if something goes wrong
+   * @see #getOutputFilename()
+   * @see #setOutputFilename(String)
+   * @see #getOutputProperties()
+   */
+  public void execute(boolean store) throws Exception {
+    // read properties file
+    loadInputProperties();
+    
+    // generate the props file
+    generateOutputProperties();
+    
+    // write properties file
+    if (store)
+      storeOutputProperties();
+  }
+  
+  /**
+   * for generating props file:
+   * <ul>
+   *   <li>
+   *     no parameter: 
+   *     see default constructor
+   *   </li>
+   *   <li>
+   *     1 parameter (i.e., filename): 
+   *     see default constructor + setOutputFilename(String)
+   *   </li>
+   *   <li>
+   *     2 parameters (i.e, filenames): 
+   *     see constructor with String argument + setOutputFilename(String)
+   *   </li>
+   * </ul>
+   *
+   * @param args	the commandline arguments
+   * @throws Exception 	if something goes wrong
+   * @see #GenericPropertiesCreator()
+   * @see #GenericPropertiesCreator(String)
+   * @see #setOutputFilename(String)
+   */
+  public static void main(String[] args) throws Exception {
+    GenericPropertiesCreator   c = null;
+    
+    if (args.length == 0) {
+      c = new GenericPropertiesCreator();
+    }
+    else if (args.length == 1) {
+      c = new GenericPropertiesCreator();
+      c.setOutputFilename(args[0]);
+    }
+    else if (args.length == 2) {
+      c = new GenericPropertiesCreator(args[0]);
+      c.setOutputFilename(args[1]);
+    }
+    else {
+      System.out.println("usage: " + GenericPropertiesCreator.class.getName() + " [<input.props>] [<output.props>]");
+      System.exit(1);
+    }
+    
+    c.execute(true);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/GenericPropertiesCreator.props	(revision 29)
@@ -0,0 +1,199 @@
+# Customises the list of options given by the GenericObjectEditor
+# for various superclasses.
+#
+# Version: $Revision: 5837 $
+
+# Whether to use the dynamic approach or not (true|false). In case of
+# "false" the old GenericObjectEditor.props file is used:
+UseDynamic=true
+
+# Lists the ResultProducers-Packages I want to choose from
+weka.experiment.ResultProducer=\
+  weka.experiment
+
+# Lists the ResultListeners-Packages I want to choose from
+weka.experiment.ResultListener=\
+  weka.experiment
+
+# Lists the SplitEvaluators-Packages I want to choose from
+weka.experiment.SplitEvaluator=\
+  weka.experiment
+
+# Lists the Classifiers-Packages I want to choose from
+weka.classifiers.Classifier=\
+ weka.classifiers.bayes,\
+ weka.classifiers.functions,\
+ weka.classifiers.lazy,\
+ weka.classifiers.meta,\
+ weka.classifiers.meta.nestedDichotomies,\
+ weka.classifiers.mi,\
+ weka.classifiers.misc,\
+ weka.classifiers.scripting,\
+ weka.classifiers.trees,\
+ weka.classifiers.rules
+
+# Lists the AbstractClassificationOutput-packages I want to choose from
+weka.classifiers.evaluation.output.prediction.AbstractOutput=\
+  weka.classifiers.evaluation.output.prediction
+
+# Lists the Filters-Packages I want to choose from
+weka.filters.Filter= \
+ weka.filters, \
+ weka.filters.supervised.attribute, \
+ weka.filters.supervised.instance, \
+ weka.filters.unsupervised.attribute, \
+ weka.filters.unsupervised.instance
+
+# Lists the UnsupervisedFilters-Packages I want to choose from
+weka.filters.UnsupervisedFilter = \
+ weka.filters.unsupervised.attribute, \
+ weka.filters.unsupervised.instance
+
+# Lists the Attribute Selection Evaluators-Packages I want to choose from
+weka.attributeSelection.ASEvaluation = \
+ weka.attributeSelection
+
+# Lists the Attribute Selection Search methods-Packages I want to choose from
+weka.attributeSelection.ASSearch = \
+ weka.attributeSelection
+
+# Lists the Associators-Packages I want to choose from
+weka.associations.Associator=\
+ weka.associations
+
+# Lists the Clusterers-Packages I want to choose from
+weka.clusterers.Clusterer=\
+ weka.clusterers
+
+# Lists the DensityBasedClusterers I want to choose from
+weka.clusterers.DensityBasedClusterer=\
+ weka.clusterers
+
+# Lists the Loaders-Packages I want to choose from
+weka.core.converters.Loader=\
+ weka.core.converters
+
+# Lists the Savers-Packages I want to choose from
+weka.core.converters.Saver=\
+ weka.core.converters
+
+# Lists the search algorithms for nearest neighbour search
+weka.core.neighboursearch.NearestNeighbourSearch =\
+ weka.core.neighboursearch
+
+# Lists the ball tree constructors
+weka.core.neighboursearch.balltrees.BallTreeConstructor =\
+ weka.core.neighboursearch.balltrees
+
+# Lists the ball splitters
+weka.core.neighboursearch.balltrees.BallSplitter =\
+ weka.core.neighboursearch.balltrees
+ 
+# Lists the kd tree splitters
+weka.core.neighboursearch.kdtrees.KDTreeNodeSplitter =\
+ weka.core.neighboursearch.kdtrees
+
+# Lists the distance functions for use nearest neighbour search
+weka.core.DistanceFunction =\
+ weka.core 
+
+# Lists the Search algorithm for Bayes net structure learning-Packages I want to choose from
+weka.classifiers.bayes.net.search.SearchAlgorithm=\
+ weka.classifiers.bayes.net.search.local, \
+ weka.classifiers.bayes.net.search.ci, \
+ weka.classifiers.bayes.net.search.global, \
+ weka.classifiers.bayes.net.search.fixed
+
+weka.classifiers.bayes.net.estimate.BayesNetEstimator =\
+ weka.classifiers.bayes.net.estimate
+
+# Lists the DataGenerator Algorithms-Packages
+weka.datagenerators.DataGenerator=\
+ weka.datagenerators.classifiers.classification,\
+ weka.datagenerators.classifiers.regression,\
+ weka.datagenerators.clusterers
+
+# Lists the packages where to find ClusterDefinition
+weka.datagenerators.ClusterDefinition=\
+ weka.datagenerators,\
+ weka.datagenerators.clusterers
+
+# List of stemmers
+weka.core.stemmers.Stemmer=\
+ weka.core.stemmers
+
+# Lists the Estimator-Packages I want to choose from
+weka.estimators.Estimator= \
+ weka.estimators
+
+# Lists of Kernels I want to choose from
+weka.classifiers.functions.supportVector.Kernel=\
+ weka.classifiers.functions.supportVector,\
+ weka.classifiers.mi.supportVector
+
+# List of SVMreg learning algorithms
+weka.classifiers.functions.supportVector.RegOptimizer=\
+ weka.classifiers.functions.supportVector
+
+# List of tokenizers
+weka.core.tokenizers.Tokenizer=\
+ weka.core.tokenizers
+
+# List of extensions for the main GUI, package "weka.gui" is not allowed
+weka.gui.MainMenuExtension=
+
+# List of Explorer visualization plugins (classification panel)
+weka.gui.visualize.plugins.VisualizePlugin=\
+ weka.gui.visualize.plugins
+
+# List of Explorer tree visualization plugins (classification panel)
+weka.gui.visualize.plugins.TreeVisualizePlugin=\
+ weka.gui.visualize.plugins
+
+# List of Explorer graph visualization plugins (classification panel)
+weka.gui.visualize.plugins.GraphVisualizePlugin=\
+ weka.gui.visualize.plugins
+
+# List of Explorer error visualization plugins (classification panel)
+weka.gui.visualize.plugins.ErrorVisualizePlugin=\
+ weka.gui.visualize.plugins
+
+# List of writers for outputting graphics
+weka.gui.visualize.JComponentWriter=\
+ weka.gui.visualize
+
+# List of Experimenter result matrices
+weka.experiment.ResultMatrix=\
+ weka.experiment
+
+# List of Tester algorithms in the Experimenter
+weka.experiment.Tester=\
+ weka.experiment
+
+# List of file loaders
+weka.core.converters.AbstractFileLoader=\
+ weka.core.converters
+
+# List of file savers
+weka.core.converters.AbstractFileSaver=\
+ weka.core.converters
+
+# List of snowball stemmers.
+#
+# Note: the snowball jar needs to be in the CLASSPATH to make these stemmers
+#       available. See the following links for more information:
+#       http://weka.wikispaces.com/Stemmers
+#       http://snowball.tartarus.org/
+#
+#org.tartarus.snowball.SnowballProgram=\
+# org.tartarus.snowball.ext
+
+# List of generators
+weka.classifiers.meta.generators.Generator=\
+ weka.classifiers.meta.generators
+
+weka.classifiers.meta.generators.NumericAttributeGenerator=\
+ weka.classifiers.meta.generators
+
+weka.classifiers.meta.generators.NominalAttributeGenerator=\
+ weka.classifiers.meta.generators
Index: branches/MetisMQI/src/main/java/weka/gui/HierarchyPropertyParser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/HierarchyPropertyParser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/HierarchyPropertyParser.java	(revision 29)
@@ -0,0 +1,643 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HierarchyPropertyParser.java
+ *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.io.Serializable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class implements a parser to read properties that have
+ * a hierarchy(i.e. tree) structure.  Conceptually it's similar to 
+ * the XML DOM/SAX parser but of course is much simpler and 
+ * uses dot as the seperator of levels instead of back-slash.<br>
+ * It provides interfaces to both build a parser tree and traverse
+ * the tree. <br>
+ * Note that this implementation does not lock the tree when different
+ * threads are traversing it simultaneously, i.e. it's NOT synchronized
+ * and multi-thread safe.  It is recommended that later implementation
+ * extending this class provide a locking scheme and override the 
+ * functions with the "synchronized" modifier (most of them are 
+ * goToXXX() and information accessing functions).<p>
+ *
+ * @author Xin Xu (xx5@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class HierarchyPropertyParser
+    implements Serializable {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4151103338506077544L;
+
+    /** Keep track of the root of the tree */
+    private TreeNode m_Root;
+    
+    /** Keep track of the current node when traversing the tree */
+    private TreeNode m_Current;
+
+    /** The level separate in the path */
+    private String m_Seperator = ".";
+    
+    /** The depth of the tree */
+    private int m_Depth = 0;
+	
+    /** 
+     * The inner class implementing a single tree node.
+     * All fields are made public simply for convenient access,
+     * Although a severe violation of OO Design principle.
+     */
+    private class TreeNode{
+	/** The parent of this node */
+	public TreeNode parent = null;
+	
+	/** The value of this node.  Always String  */
+	public String value = null;
+
+	/** The children of this node */
+	public Vector children = null;	
+
+	/** The level of this node */
+	public int level = 0;
+
+	/** The context of this node */
+	public String context = null;
+    }
+    
+    /** Default constructor */
+    public HierarchyPropertyParser(){
+	m_Root = new TreeNode();
+	m_Root.parent = null;
+	m_Root.children = new Vector();
+	goToRoot();
+    }
+    
+    /**
+     * Constructor that builds a tree from the given property with
+     * the given delimitor
+     *
+     * @param p the given property string
+     * @param delim the given dilimitor
+     */
+    public HierarchyPropertyParser(String p, String delim) throws Exception {
+	this();		
+	build(p, delim);
+    }
+    
+    /**
+     * Set the seperator between levels.  Default is dot.
+     *
+     * @param s the seperator symbol
+     */
+    public void setSeperator(String s){
+	m_Seperator = s;
+    }
+
+    /**
+     * Get the seperator between levels.  Default is dot.
+     *
+     * @return the seperator symbol
+     */
+    public String getSeperator(){ return m_Seperator; }
+   
+    /** 
+     * Build a tree from the given property with the given delimitor
+     *
+     * @param p the given property
+     * @param delim the given delimitor
+     */
+    public void build(String p, String delim) throws Exception {
+	StringTokenizer st = new StringTokenizer(p, delim);
+	//System.err.println("delim: "+delim);
+	while(st.hasMoreTokens()){
+	    String property = st.nextToken().trim();
+	    if(!isHierachic(property))
+		throw new Exception("The given property is not in"+
+				    "hierachy structure with seperators!");
+	    add(property);	   
+	}
+	goToRoot();
+    }
+
+    /**
+     * Add the given item of property to the tree
+     *
+     * @param property the given item
+     */ 
+    public synchronized void add(String property){
+	String[] values = tokenize(property);
+	if(m_Root.value == null)
+	    m_Root.value = values[0];
+	
+	buildBranch(m_Root, values, 1);
+    }
+    
+    /** 
+     * Private function to build one branch of the tree
+     * based on one property 
+     * 
+     * @param parent the parent of the node to be built
+     * @param values the value of one property
+     * @param lvl the level of the node to be built in the tree
+     */
+    private void buildBranch(TreeNode parent, String[] values, int lvl){
+	// Precondition: children is not null
+	
+	if(lvl == values.length){  // Parent is leaf
+	    parent.children = null;
+	    return;
+	}
+	
+	if(lvl > (m_Depth-1))
+	    m_Depth = lvl+1;  // Depth starts from 1
+	
+	Vector kids = parent.children;
+	int index = search(kids, values[lvl]);
+	if(index != -1){
+	    TreeNode newParent = (TreeNode)kids.elementAt(index);
+	    if(newParent.children == null)
+		newParent.children = new Vector();
+	    buildBranch(newParent, values, lvl+1);
+	}
+	else{
+	    TreeNode added = new TreeNode();
+	    added.parent = parent;
+	    added.value = values[lvl];
+	    added.children = new Vector();
+	    added.level = lvl;
+	    if(parent != m_Root)
+		added.context = parent.context + m_Seperator + parent.value;
+	    else
+		added.context = parent.value;
+	    
+	    kids.addElement(added);
+	    buildBranch(added, values, lvl+1);
+	}
+    }
+    
+    /**
+     * Tokenize the given string based on the seperator and
+     * put the tokens into an array of strings
+     *
+     * @param rawString the given string
+     * @return an array of strings
+     */
+    public String[] tokenize(String rawString){
+	Vector result = new Vector();
+	StringTokenizer tk = new StringTokenizer(rawString, m_Seperator);
+	while(tk.hasMoreTokens())
+	    result.addElement(tk.nextToken());
+	
+	String[] newStrings = new String[result.size()];
+	for(int i=0; i < result.size(); i++)
+	    newStrings[i] = (String)result.elementAt(i);
+	
+	return newStrings;
+    }
+    
+    /**
+     * Whether the HierarchyPropertyParser contains the given
+     * string
+     *
+     * @param string the given string
+     * @return whether contains
+     */
+    public boolean contains(String string){
+	String[] item = tokenize(string);
+	if(!item[0].equals(m_Root.value))
+	    return false;
+	
+	return isContained(m_Root, item, 1);
+    }
+    
+    /** 
+     * Private function to decide whether one level of one branch 
+     * contains the relevant values
+     * 
+     * @param parent the parent of the node to be searched
+     * @param values the value of one property
+     * @param lvl the level of the node in question
+     * @return whether this branch contains the corresponding values
+     */
+    private boolean isContained(TreeNode parent, String[] values, int lvl){
+	if(lvl == values.length)  // Parent is leaf
+	    return true;
+	else if(lvl > values.length)
+	    return false;
+	else{
+	    Vector kids = parent.children;
+	    int index = search(kids, values[lvl]);
+	    if(index != -1){
+		TreeNode newParent = (TreeNode)kids.elementAt(index);
+		return isContained(newParent, values, lvl+1);
+	    }
+	    else
+		return false;
+	}
+    }
+    
+    /** 
+     * Whether the given string has a hierachy structure with
+     * the seperators
+     *
+     * @param string the given string
+     */
+    public boolean isHierachic(String string){
+	int index = string.indexOf(m_Seperator); 
+	// Seperator not occur or first occurance at the end
+	if((index == (string.length()-1)) || (index == -1))
+	    return false;
+	
+	return true;
+    }
+
+    /**
+     * Helper function to search for the given target string in a 
+     * given vector in which the elements' value may hopefully is equal
+     * to the target.  If such elements are found the first index
+     * is returned, otherwise -1
+     *
+     * @param vct the given vector
+     * @param target the given target string
+     * @return the index of the found element, -1 if not found
+     */
+    public int search(Vector vct, String target){
+	if(vct == null)
+	    return -1;
+	
+	for(int i=0; i < vct.size(); i++)
+	    if(target.equals(((TreeNode)vct.elementAt(i)).value))
+		return i;
+	
+	return -1;
+    }
+
+    /**
+     * Go to a certain node of the tree according to the specified path
+     * Note that the path must be absolute path from the root.  <br>
+     * For relative path, see goDown(String path).
+     *
+     * @param path the given absolute path
+     * @return whether the path exists, if false the current position does
+     * not move
+     */
+    public synchronized boolean goTo(String path){
+	if(!isHierachic(path)){
+	    if(m_Root.value.equals(path)){
+		goToRoot();
+		return true;
+	    }
+	    else
+		return false;
+	}
+	
+	TreeNode old = m_Current;
+	m_Current = new TreeNode(); 
+	goToRoot();
+	String[] nodes = tokenize(path);
+	if(!m_Current.value.equals(nodes[0]))
+	    return false;
+
+	for(int i=1; i < nodes.length; i++){
+	    int pos = search(m_Current.children, nodes[i]);
+	    if(pos == -1){
+		m_Current = old;
+		return false;
+	    }
+	    m_Current = (TreeNode)m_Current.children.elementAt(pos);
+	}
+	
+	return true;
+    }
+
+    /**
+     * Go to a certain node of the tree down from the current node 
+     * according to the specified relative path.  The path does not
+     * contain the value of current node
+     * 
+     * @param path the given relative path
+     * @return whether the path exists, if false the current position does
+     * not move
+     */
+    public synchronized boolean goDown(String path){
+	if(!isHierachic(path))
+	    return goToChild(path);
+
+	TreeNode old = m_Current;
+	m_Current = new TreeNode(); 
+	String[] nodes = tokenize(path);
+	int pos = search(old.children, nodes[0]);
+	if(pos == -1){
+	    m_Current = old;
+	    return false;
+	}
+      
+	m_Current = (TreeNode)old.children.elementAt(pos);
+	for(int i=1; i < nodes.length; i++){
+	    pos = search(m_Current.children, nodes[i]);
+	    if(pos == -1){
+		m_Current = old;
+		return false;
+	    }
+	    
+	    m_Current = (TreeNode)m_Current.children.elementAt(pos);
+	}
+	
+	return true;
+    }
+
+    /**
+     * Go to the root of the tree
+     */    
+    public synchronized void goToRoot(){
+	m_Current=m_Root;
+    }
+
+    /**
+     * Go to the parent from the current position in the tree
+     * If the current position is the root, it stays there and does 
+     * not move
+     */
+    public synchronized void goToParent(){
+	if(m_Current.parent != null)  // Not root
+	    m_Current = m_Current.parent;
+    }
+    
+    /**
+     * Go to one child node from the current position in the tree
+     * according to the given value <br>
+     * If the child node with the given value cannot be found it
+     * returns false, true otherwise.  If false, the current position
+     * does not change
+     * 
+     * @param value the value of the given child
+     * @return whether the child can be found
+     */
+    public synchronized boolean goToChild(String value){
+	if(m_Current.children == null) // Leaf
+	    return false;
+	
+	int pos = search(m_Current.children, value);
+	if(pos == -1)
+	    return false;
+	
+	m_Current = (TreeNode)m_Current.children.elementAt(pos);	
+	return true;
+    }
+    
+    /**
+     * Go to one child node from the current position in the tree
+     * according to the given position <br>
+     *
+     * @param pos the position of the given child
+     * @exception Exception if the position is out of range or leaf is reached
+     */
+    public synchronized void goToChild(int pos) throws Exception {
+	if((m_Current.children == null) || 
+	   (pos < 0) || (pos >= m_Current.children.size()))
+	    throw new Exception("Position out of range or leaf reached");
+	
+	m_Current = (TreeNode)m_Current.children.elementAt(pos);
+    }
+
+    /**
+     * The number of the children nodes. If current node is leaf, it
+     * returns 0.
+     * 
+     * @return the number of the children nodes of the current position
+     */    
+    public synchronized int numChildren(){
+	if(m_Current.children == null) // Leaf
+	    return 0;
+	
+	return m_Current.children.size(); 
+    }
+    
+    /**
+     * The value in the children nodes. If current node is leaf, it
+     * returns null.
+     * 
+     * @return the value in the children nodes
+     */    
+    public synchronized String[] childrenValues(){
+	if(m_Current.children == null) // Leaf
+	    return null;
+	else{
+	    Vector kids = m_Current.children;
+	    String[] values = new String[kids.size()];
+	    for(int i=0; i < kids.size(); i++)
+		values[i] = ((TreeNode)kids.elementAt(i)).value;
+	    return values;
+	}
+    }
+
+    /**
+     * The value in the parent node. If current node is root, it
+     * returns null.
+     * 
+     * @return the value in the parent node
+     */    
+    public synchronized String parentValue(){
+	if(m_Current.parent != null)  // Not root
+	    return m_Current.parent.value;
+	else return null;
+    }
+
+    /**
+     * Whether the current position is a leaf
+     *
+     * @return whether the current position is a leaf
+     */
+    public synchronized boolean isLeafReached(){
+	return (m_Current.children == null);
+    }
+    
+    /**
+     * Whether the current position is the root
+     *
+     * @return whether the current position is the root
+     */
+    public synchronized boolean isRootReached(){
+	return (m_Current.parent == null);
+    }
+    
+    /** 
+     * Get the value of current node
+     *
+     * @return value level
+     */ 
+    public synchronized String getValue(){ return m_Current.value; }
+    
+    /** 
+     * Get the level of current node.  Note the level starts from 0
+     *
+     * @return the level
+     */ 
+    public synchronized int getLevel(){ return m_Current.level; }
+
+    /** 
+     * Get the depth of the tree, i.e. (the largest level)+1
+     *
+     * @return the depth of the tree
+     */
+    public int depth(){ return m_Depth; }
+    
+    /**
+     * The context of the current node, i.e. the path from the
+     * root to the parent node of the current node, seperated by
+     * the seperator.  If root, it returns null
+     *
+     * @return the context path
+     */
+    public synchronized String context(){
+	return m_Current.context;
+    }
+    
+    /**
+     * The full value of the current node, i.e. its context + seperator
+     * + its value.  For root, only its value.
+     *
+     * @return the context path
+     */
+    public synchronized String fullValue(){
+	if(m_Current == m_Root)
+	    return m_Root.value;
+	else    
+	    return (m_Current.context + m_Seperator + m_Current.value);
+    }
+
+
+    /**
+     * Show the whole tree in text format
+     *
+     * @return the whole tree in text format
+     */
+    public String showTree(){
+	return showNode(m_Root, null);
+    }
+    
+    /**
+     * Show one node of the tree in text format
+     *
+     * @param node the node in question
+     * @return the node in text format
+     */
+    private String showNode(TreeNode node, boolean[] hasBar){
+	StringBuffer text =  new StringBuffer();	
+
+	for(int i=0; i < (node.level - 1); i++)
+	    if(hasBar[i])
+		text.append("  |       ");
+	    else
+		text.append("          ");
+
+	if(node.level != 0)
+	    text.append("  |------ ");
+	text.append(node.value+"("+node.level+")"+"["+node.context+"]\n");
+
+	if(node.children != null)
+	    for(int i=0; i < node.children.size(); i++){
+		boolean[] newBar = new boolean[node.level+1];
+		int lvl = node.level;
+
+		if(hasBar != null)
+		    for(int j=0; j < lvl; j++)
+			newBar[j] = hasBar[j];
+		
+		if((i == (node.children.size()-1)))
+		    newBar[lvl] = false;
+		else
+		    newBar[lvl] = true;
+
+		text.append(showNode((TreeNode)node.children.elementAt(i), newBar));		
+	    }
+	
+	return text.toString();
+    }
+    
+    /**
+     * Tests out the parser.
+     *
+     * @param args should contain nothing
+     */
+    public static void main(String args[]){
+	StringBuffer sb = new StringBuffer();
+	sb.append("node1.node1_1.node1_1_1.node1_1_1_1, ");
+	sb.append("node1.node1_1.node1_1_1.node1_1_1_2, ");
+	sb.append("node1.node1_1.node1_1_1.node1_1_1_3, ");
+	sb.append("node1.node1_1.node1_1_2.node1_1_2_1, ");
+	sb.append("node1.node1_1.node1_1_3.node1_1_3_1, ");
+	sb.append("node1.node1_2.node1_2_1.node1_2_1_1, ");
+	sb.append("node1.node1_2.node1_2_3.node1_2_3_1, ");
+	sb.append("node1.node1_3.node1_3_3.node1_3_3_1, ");
+	sb.append("node1.node1_3.node1_3_3.node1_3_3_2, ");
+
+	String p = sb.toString();
+	try{
+	    HierarchyPropertyParser hpp = new HierarchyPropertyParser(p, ", ");
+	    System.out.println("seperator: "+hpp.getSeperator());
+	    System.out.println("depth: "+hpp.depth());
+	    System.out.println("The tree:\n\n"+hpp.showTree());
+	    hpp.goToRoot();
+	    System.out.println("goto: "+hpp.goTo("node1.node1_2.node1_2_1")
+			       +": "+hpp.getValue()+" | "+hpp.fullValue()+
+			       " leaf? "+ hpp.isLeafReached());
+	    System.out.println("go down(wrong): "+hpp.goDown("node1"));
+	    System.out.println("Stay still? "+hpp.getValue());
+	    System.out.println("go to child: "+hpp.goToChild("node1_2_1_1")
+			       +": "+hpp.getValue()+" | "+hpp.fullValue()+
+			       " leaf? "+ hpp.isLeafReached()
+			       +" root? "+ hpp.isRootReached());
+	    System.out.println("parent: "+hpp.parentValue());
+	    System.out.println("level: "+hpp.getLevel());
+	    System.out.println("context: "+hpp.context());
+	    hpp.goToRoot();
+	    System.out.println("After gotoRoot. leaf? "+ hpp.isLeafReached()
+			       +" root? "+ hpp.isRootReached());
+	    System.out.println("Go down(correct): "+
+			       hpp.goDown("node1_1.node1_1_1")+
+			       " value: "+hpp.getValue()+" | "+hpp.fullValue()
+			       +" level: "+hpp.getLevel()
+			       +" leaf? "+ hpp.isLeafReached()
+			       +" root? "+ hpp.isRootReached());
+	    hpp.goToParent();
+	    System.out.println("value: "+hpp.getValue()+" | "+hpp.fullValue());
+	    System.out.println("level: "+hpp.getLevel());
+	    
+	    String[] chd = hpp.childrenValues();
+	    for(int i=0; i < chd.length; i++){
+		System.out.print("children "+i+": "+chd[i]);
+		hpp.goDown(chd[i]);
+		System.out.println("real value: "+hpp.getValue()+" | "+
+				   hpp.fullValue()+
+				   "(level: "+hpp.getLevel()+")");
+		hpp.goToParent();
+	    }
+	    
+	    System.out.println("Another way to go to root:"+hpp.goTo("node1")
+			       +": "+hpp.getValue()+" | "+hpp.fullValue()); 
+   	}catch(Exception e){
+	    System.out.println(e.getMessage());
+	    e.printStackTrace();
+	}
+    }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/InstancesSummaryPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/InstancesSummaryPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/InstancesSummaryPanel.java	(revision 29)
@@ -0,0 +1,218 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstancesSummaryPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+/** 
+ * This panel just displays relation name, number of instances, and number of
+ * attributes.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5297 $
+ */
+public class InstancesSummaryPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5243579535296681063L;
+
+  /** Message shown when no instances have been loaded */
+  protected static final String NO_SOURCE = "None";
+
+  /** Displays the name of the relation */
+  protected JLabel m_RelationNameLab = new JLabel(NO_SOURCE);
+  
+  /** Displays the number of instances */
+  protected JLabel m_NumInstancesLab = new JLabel(NO_SOURCE);
+  
+  /** Displays the number of attributes */
+  protected JLabel m_NumAttributesLab = new JLabel(NO_SOURCE);
+  
+  /** Displays the sum of instance weights */
+  protected JLabel m_sumOfWeightsLab = new JLabel(NO_SOURCE);
+    
+  /** The instances we're playing with */
+  protected Instances m_Instances;
+  
+  /** 
+   * Whether to display 0 or ? for the number of instances
+   * in cases where a dataset has only structure. Depending
+   * on where this panel is used from, the user may have
+   * loaded a dataset with no instances or a Loader that
+   * can read incrementally may be being used (in which case
+   * we don't know how many instances are in the dataset... 
+   * yet).
+   */
+  protected boolean m_showZeroInstancesAsUnknown = false;
+
+  /**
+   * Creates the instances panel with no initial instances.
+   */
+  public InstancesSummaryPanel() {
+
+    GridBagLayout gbLayout = new GridBagLayout();
+    setLayout(gbLayout);
+    JLabel lab = new JLabel("Relation:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0;     gbConstraints.gridx = 0;
+    gbLayout.setConstraints(lab, gbConstraints);
+    add(lab);
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.WEST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0;     gbConstraints.gridx = 1;
+    gbConstraints.weightx = 100; gbConstraints.gridwidth = 3;
+    gbLayout.setConstraints(m_RelationNameLab, gbConstraints);
+    add(m_RelationNameLab);
+    m_RelationNameLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
+    
+    lab = new JLabel("Instances:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1;     gbConstraints.gridx = 0;
+    gbLayout.setConstraints(lab, gbConstraints);
+    add(lab);
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.WEST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1;     gbConstraints.gridx = 1;
+    gbConstraints.weightx = 100;
+    gbLayout.setConstraints(m_NumInstancesLab, gbConstraints);
+    add(m_NumInstancesLab);
+    m_NumInstancesLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
+
+    lab = new JLabel("Attributes:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0;     gbConstraints.gridx = 2;
+    gbLayout.setConstraints(lab, gbConstraints);
+    add(lab);
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.WEST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0;     gbConstraints.gridx = 3;
+    gbConstraints.weightx = 100;
+    gbLayout.setConstraints(m_NumAttributesLab, gbConstraints);
+    add(m_NumAttributesLab);
+    m_NumAttributesLab.setBorder(BorderFactory.createEmptyBorder(0, 5,
+								 0, 10));
+    
+    lab = new JLabel("Sum of weights:", SwingConstants.RIGHT);
+    lab.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1;     gbConstraints.gridx = 2;
+    gbLayout.setConstraints(lab, gbConstraints);
+    add(lab);
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.WEST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1;     gbConstraints.gridx = 3;
+    gbConstraints.weightx = 100;
+    gbLayout.setConstraints(m_sumOfWeightsLab, gbConstraints);
+    add(m_sumOfWeightsLab);
+    m_sumOfWeightsLab.setBorder(BorderFactory.createEmptyBorder(0, 5,
+                                                                 0, 10));
+    
+  }
+  
+  public void setShowZeroInstancesAsUnknown(boolean zeroAsUnknown) {
+    m_showZeroInstancesAsUnknown = zeroAsUnknown;
+  }
+  
+  public boolean getShowZeroInstancesAsUnknown() {
+    return m_showZeroInstancesAsUnknown;
+  }
+
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    m_Instances = inst;
+    m_RelationNameLab.setText(m_Instances.relationName());
+    m_NumInstancesLab.setText("" + 
+        ((m_showZeroInstancesAsUnknown && m_Instances.numInstances() == 0) 
+            ? "?" 
+            : "" + m_Instances.numInstances()));
+    m_NumAttributesLab.setText("" + m_Instances.numAttributes());
+    m_sumOfWeightsLab.setText("" + 
+        ((m_showZeroInstancesAsUnknown && m_Instances.numInstances() == 0) 
+            ? "?" 
+            : "" + Utils.doubleToString(m_Instances.sumOfWeights(), 3)));
+  }
+
+  /**
+   * Tests out the instance summary panel from the command line.
+   *
+   * @param args optional name of dataset to load
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame("Instances Panel");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final InstancesSummaryPanel p = new InstancesSummaryPanel();
+      p.setBorder(BorderFactory.createTitledBorder("Relation"));
+      jf.getContentPane().add(p, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      if (args.length == 1) {
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	p.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/JListHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/JListHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/JListHelper.java	(revision 29)
@@ -0,0 +1,186 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JListHelper.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+
+/**
+ * A helper class for JList GUI elements with DefaultListModel or 
+ * derived models.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ * @see     JList
+ * @see     DefaultListModel
+ */
+public class JListHelper {
+  
+  /** moves items up */
+  public final static int MOVE_UP = 0;
+
+  /** moves items down */
+  public final static int MOVE_DOWN = 1;
+  
+  /**
+   * moves the selected items by a certain amount of items in a given direction
+   *
+   * @param list        the JList to work on
+   * @param moveby      the number of items to move by
+   * @param direction   the direction to move in
+   * @see               #MOVE_UP
+   * @see               #MOVE_DOWN
+   */
+  protected static void moveItems(JList list, int moveby, int direction) {
+    int[]               indices;
+    int                 i;
+    Object              o;
+    DefaultListModel    model;
+
+    model = (DefaultListModel) list.getModel();
+
+    switch (direction) {
+      case MOVE_UP:
+        indices = list.getSelectedIndices();
+        for (i = 0; i < indices.length; i++) {
+          if (indices[i] == 0)
+            continue;
+          o = model.remove(indices[i]);
+          indices[i] -= moveby;
+          model.insertElementAt(o, indices[i]);
+        }
+        list.setSelectedIndices(indices);
+        break;
+
+      case MOVE_DOWN:
+        indices = list.getSelectedIndices();
+        for (i = indices.length - 1; i >= 0; i--) {
+          if (indices[i] == model.getSize() - 1)
+            continue;
+          o = model.remove(indices[i]);
+          indices[i] += moveby;
+          model.insertElementAt(o, indices[i]);
+        }
+        list.setSelectedIndices(indices);
+        break;
+
+      default:
+        System.err.println(
+            JListHelper.class.getName() + ": direction '" 
+            + direction + "' is unknown!");
+    }
+  }
+
+  /**
+   * moves the selected items up by 1
+   *
+   * @param list        the JList to work on
+   */
+  public static void moveUp(JList list) {
+    if (canMoveUp(list))
+      moveItems(list, 1, MOVE_UP);
+  }
+
+  /**
+   * moves the selected item down by 1
+   *
+   * @param list        the JList to work on
+   */
+  public static void moveDown(JList list) {
+    if (canMoveDown(list))
+      moveItems(list, 1, MOVE_DOWN);
+  }
+
+  /**
+   * moves the selected items to the top
+   *
+   * @param list        the JList to work on
+   */
+  public static void moveTop(JList list) {
+    int[]     indices;
+    int       diff;
+
+    if (canMoveUp(list)) {
+      indices = list.getSelectedIndices();
+      diff    = indices[0];
+      moveItems(list, diff, MOVE_UP);
+    }
+  }
+
+  /**
+   * moves the selected items to the end
+   *
+   * @param list        the JList to work on
+   */
+  public static void moveBottom(JList list) {
+    int[]     indices;
+    int       diff;
+
+    if (canMoveDown(list)) {
+      indices = list.getSelectedIndices();
+      diff    = list.getModel().getSize() - 1 - indices[indices.length - 1];
+      moveItems(list, diff, MOVE_DOWN);
+    }
+  }
+
+  /**
+   * checks whether the selected items can be moved up
+   *
+   * @param list        the JList to work on
+   */
+  public static boolean canMoveUp(JList list) {
+    boolean   result;
+    int[]     indices;
+
+    result = false;
+    
+    indices = list.getSelectedIndices();
+    if (indices.length > 0) {
+      if (indices[0] > 0)
+        result = true;
+    }
+
+    return result;
+  }
+
+  /**
+   * checks whether the selected items can be moved down
+   *
+   * @param list        the JList to work on
+   */
+  public static boolean canMoveDown(JList list) {
+    boolean   result;
+    int[]     indices;
+
+    result = false;
+    
+    indices = list.getSelectedIndices();
+    if (indices.length > 0) {
+      if (indices[indices.length - 1] < list.getModel().getSize() - 1)
+        result = true;
+    }
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/JTableHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/JTableHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/JTableHelper.java	(revision 29)
@@ -0,0 +1,277 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JTableHelper.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.Rectangle;
+import javax.swing.JTable;
+import javax.swing.JViewport;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+
+/**
+ * A helper class for JTable, e.g. calculating the optimal colwidth.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $ 
+ */
+
+public class JTableHelper {
+  // the table to work with
+  private JTable          jtable;
+  
+  /**
+   * initializes the object
+   */
+  public JTableHelper(JTable jtable) {
+    this.jtable = jtable;
+  }
+  
+  /**
+   * returns the JTable
+   */
+  public JTable getJTable() {
+    return jtable;
+  }
+  
+  /**
+   * calcs the optimal column width of the given column
+   */
+  public int calcColumnWidth(int col) {
+    return calcColumnWidth(getJTable(), col);
+  }
+  
+  /**
+   *  Calculates the optimal width for the column of the given table. The
+   *  calculation is based on the preferred width of the header and cell
+   *  renderer.
+   *  <br>
+   *  Taken from the newsgoup de.comp.lang.java with some modifications.<br>
+   *  Taken from FOPPS/EnhancedTable - http://fopps.sourceforge.net/<br>
+   *
+   *  @param table    the table to calculate the column width
+   *  @param col      the column to calculate the widths
+   *  @return         the width, -1 if error
+   */
+  public static int calcColumnWidth(JTable table, int col) {
+    int width = calcHeaderWidth(table, col);
+    if (width == -1)
+      return width;
+    
+    TableColumnModel columns = table.getColumnModel();
+    TableModel data = table.getModel();
+    int rowCount = data.getRowCount();
+    TableColumn column = columns.getColumn(col);
+    try {
+      for (int row = rowCount - 1; row >= 0; --row) {
+        Component c = table.prepareRenderer(
+            table.getCellRenderer(row, col),
+            row, col);
+        width = Math.max(width, c.getPreferredSize().width + 10);
+      }
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return width;
+  }
+  
+  /**
+   * calcs the optimal header width of the given column
+   */
+  public int calcHeaderWidth(int col) {
+    return calcHeaderWidth(getJTable(), col);
+  }
+  
+  /**
+   *  Calculates the optimal width for the header of the given table. The
+   *  calculation is based on the preferred width of the header renderer.
+   *
+   *  @param table    the table to calculate the column width
+   *  @param col      the column to calculate the widths
+   *  @return         the width, -1 if error
+   */
+  public static int calcHeaderWidth(JTable table, int col) {
+    if (table == null) 
+      return -1;
+    
+    if (col < 0 || col > table.getColumnCount()) {
+      System.out.println("invalid col " + col);
+      return -1;
+    }
+    
+    JTableHeader header = table.getTableHeader();
+    TableCellRenderer defaultHeaderRenderer = null;
+    if (header != null) defaultHeaderRenderer = header.getDefaultRenderer();
+    TableColumnModel columns = table.getColumnModel();
+    TableModel data = table.getModel();
+    TableColumn column = columns.getColumn(col);
+    int width = -1;
+    TableCellRenderer h = column.getHeaderRenderer();
+    if (h == null) h = defaultHeaderRenderer;
+    if (h != null) {
+      // Not explicitly impossible
+      Component c = h.getTableCellRendererComponent(
+          table,
+          column.getHeaderValue(),
+          false, false, -1, col);
+      width = c.getPreferredSize().width + 5;
+    }
+    
+    return width;
+  }
+  
+  /**
+   * sets the optimal column width for the given column
+   */
+  public void setOptimalColumnWidth(int col) {
+    setOptimalColumnWidth(getJTable(), col);
+  }
+  
+  /**
+   * sets the optimal column width for the given column
+   
+   */
+  public static void setOptimalColumnWidth(JTable jtable, int col) {
+    int            width;
+    TableColumn    column;
+    JTableHeader   header;
+    
+    if ( (col >= 0) && (col < jtable.getColumnModel().getColumnCount()) ) {
+      width = calcColumnWidth(jtable, col);
+      
+      if (width >= 0) {
+        header = jtable.getTableHeader();
+        column = jtable.getColumnModel().getColumn(col);
+        column.setPreferredWidth(width);
+        jtable.sizeColumnsToFit(-1);
+        header.repaint();
+      }
+    }
+  }
+  
+  /**
+   * sets the optimal column width for all columns
+   */
+  public void setOptimalColumnWidth() {
+    setOptimalColumnWidth(getJTable());
+  }
+  
+  /**
+   * sets the optimal column width for alls column if the given table
+   */
+  public static void setOptimalColumnWidth(JTable jtable) {
+    int            i;
+    
+    for (i = 0; i < jtable.getColumnModel().getColumnCount(); i++)
+      setOptimalColumnWidth(jtable, i);
+  }
+  
+  /**
+   * sets the optimal header width for the given column
+   */
+  public void setOptimalHeaderWidth(int col) {
+    setOptimalHeaderWidth(getJTable(), col);
+  }
+  
+  /**
+   * sets the optimal header width for the given column
+   
+   */
+  public static void setOptimalHeaderWidth(JTable jtable, int col) {
+    int            width;
+    TableColumn    column;
+    JTableHeader   header;
+    
+    if ( (col >= 0) && (col < jtable.getColumnModel().getColumnCount()) ) {
+      width = calcHeaderWidth(jtable, col);
+      
+      if (width >= 0) {
+        header = jtable.getTableHeader();
+        column = jtable.getColumnModel().getColumn(col);
+        column.setPreferredWidth(width);
+        jtable.sizeColumnsToFit(-1);
+        header.repaint();
+      }
+    }
+  }
+  
+  /**
+   * sets the optimal header width for all columns
+   */
+  public void setOptimalHeaderWidth() {
+    setOptimalHeaderWidth(getJTable());
+  }
+  
+  /**
+   * sets the optimal header width for alls column if the given table
+   */
+  public static void setOptimalHeaderWidth(JTable jtable) {
+    int            i;
+    
+    for (i = 0; i < jtable.getColumnModel().getColumnCount(); i++)
+      setOptimalHeaderWidth(jtable, i);
+  }
+  
+  /** 
+   * Assumes table is contained in a JScrollPane.
+   * Scrolls the cell (rowIndex, vColIndex) so that it is visible
+   * within the viewport.
+   */
+  public void scrollToVisible(int row, int col) {
+    scrollToVisible(getJTable(), row, col);
+  }
+  
+  /** 
+   * Assumes table is contained in a JScrollPane.
+   * Scrolls the cell (rowIndex, vColIndex) so that it is visible
+   * within the viewport.
+   */
+  public static void scrollToVisible(JTable table, int row, int col) {
+    if (!(table.getParent() instanceof JViewport)) 
+      return;
+    
+    JViewport viewport = (JViewport) table.getParent();
+    
+    // This rectangle is relative to the table where the
+    // northwest corner of cell (0,0) is always (0,0).
+    Rectangle rect = table.getCellRect(row, col, true);
+    
+    // The location of the viewport relative to the table
+    Point pt = viewport.getViewPosition();
+    
+    // Translate the cell location so that it is relative
+    // to the view, assuming the northwest corner of the
+    // view is (0,0)
+    rect.setLocation(rect.x - pt.x, rect.y - pt.y);
+    
+    // Scroll the area into view
+    viewport.scrollRectToVisible(rect);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ListSelectorDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ListSelectorDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ListSelectorDialog.java	(revision 29)
@@ -0,0 +1,216 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ListSelectorDialog.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.regex.Pattern;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+
+/** 
+ * A dialog to present the user with a list of items, that the user can
+ * make a selection from, or cancel the selection.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public class ListSelectorDialog
+  extends JDialog {
+
+  /** for serialization */
+  private static final long serialVersionUID = 906147926840288895L;
+  
+  /** Click to choose the currently selected property */
+  protected JButton m_SelectBut = new JButton("Select");
+
+  /** Click to cancel the property selection */
+  protected JButton m_CancelBut = new JButton("Cancel");
+
+  /** Click to enter a regex pattern for selection */
+  protected JButton m_PatternBut = new JButton("Pattern");
+
+  /** The list component */
+  protected JList m_List;
+  
+  /** Whether the selection was made or cancelled */
+  protected int m_Result;
+
+  /** Signifies an OK property selection */
+  public static final int APPROVE_OPTION = 0;
+
+  /** Signifies a cancelled property selection */
+  public static final int CANCEL_OPTION = 1;
+
+  /** The current regular expression. */
+  protected String m_PatternRegEx = ".*";
+  
+  /**
+   * Create the list selection dialog.
+   *
+   * @param parentFrame the parent frame of the dialog
+   * @param userList the JList component the user will select from
+   */
+  public ListSelectorDialog(Frame parentFrame, JList userList) {
+    
+    super(parentFrame, "Select items", true);
+    m_List = userList;
+    m_CancelBut.setMnemonic('C');
+    m_CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Result = CANCEL_OPTION;
+	setVisible(false);
+      }
+    });
+    m_SelectBut.setMnemonic('S');
+    m_SelectBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Result = APPROVE_OPTION;
+	setVisible(false);
+      }
+    });
+    m_PatternBut.setMnemonic('P');
+    m_PatternBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        selectPattern();
+      }
+    });
+    
+    Container c = getContentPane();
+    c.setLayout(new BorderLayout());
+    //    setBorder(BorderFactory.createTitledBorder("Select a property"));
+    Box b1 = new Box(BoxLayout.X_AXIS);
+    b1.add(m_SelectBut);
+    b1.add(Box.createHorizontalStrut(10));
+    b1.add(m_PatternBut);
+    b1.add(Box.createHorizontalStrut(10));
+    b1.add(m_CancelBut);
+    c.add(b1, BorderLayout.SOUTH);
+    c.add(new JScrollPane(m_List), BorderLayout.CENTER);
+
+    getRootPane().setDefaultButton(m_SelectBut);
+    
+    pack();
+
+    // make sure, it's not bigger than the screen
+    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+    int width  = getWidth() > screen.getWidth() 
+                    ? (int) screen.getWidth() : getWidth();
+    int height = getHeight() > screen.getHeight() 
+                    ? (int) screen.getHeight() : getHeight();
+    setSize(width, height);
+  }
+
+  /**
+   * Pops up the modal dialog and waits for cancel or a selection.
+   *
+   * @return either APPROVE_OPTION, or CANCEL_OPTION
+   */
+  public int showDialog() {
+
+    m_Result = CANCEL_OPTION;
+    int [] origSelected = m_List.getSelectedIndices();
+    setVisible(true);
+    if (m_Result == CANCEL_OPTION) {
+      m_List.setSelectedIndices(origSelected);
+    }
+    return m_Result;
+  }
+
+  /**
+   * opens a separate dialog for entering a regex pattern for selecting
+   * elements from the provided list
+   */
+  protected void selectPattern() {
+    String pattern = JOptionPane.showInputDialog(
+                        m_PatternBut.getParent(),
+                        "Enter a Perl regular expression ('.*' for all)",
+                        m_PatternRegEx);
+    if (pattern != null) {
+      try {
+        Pattern.compile(pattern);
+        m_PatternRegEx = pattern;
+        m_List.clearSelection();
+        for (int i = 0; i < m_List.getModel().getSize(); i++) {
+          if (Pattern.matches(
+                pattern, m_List.getModel().getElementAt(i).toString()))
+            m_List.addSelectionInterval(i, i);
+        }
+      }
+      catch (Exception ex) {
+        JOptionPane.showMessageDialog(
+          m_PatternBut.getParent(),
+          "'" + pattern + "' is not a valid Perl regular expression!\n" 
+          + "Error: " + ex, 
+          "Error in Pattern...", 
+          JOptionPane.ERROR_MESSAGE);
+      }
+    }
+  }
+  
+  /**
+   * Tests out the list selector from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      DefaultListModel lm = new DefaultListModel();      
+      lm.addElement("one");
+      lm.addElement("two");
+      lm.addElement("three");
+      lm.addElement("four");
+      lm.addElement("five");
+      JList jl = new JList(lm);
+      final ListSelectorDialog jd = new ListSelectorDialog(null, jl);
+      int result = jd.showDialog();
+      if (result == ListSelectorDialog.APPROVE_OPTION) {
+	System.err.println("Fields Selected");
+	int [] selected = jl.getSelectedIndices();
+	for (int i = 0; i < selected.length; i++) {
+	  System.err.println("" + selected[i]
+			     + " " + lm.elementAt(selected[i]));
+	}
+      } else {
+	System.err.println("Cancelled");
+      }
+      System.exit(0);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/Loader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/Loader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/Loader.java	(revision 29)
@@ -0,0 +1,127 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Loader.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.io.Reader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+/**
+ * This class is for loading resources from a JAR archive.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $ 
+ */
+
+public class Loader {
+  /** the dir to use as prefix if filenames are w/o it, must have a slash
+   * at the end (the path separator is a slash!) */
+  private String          dir;
+  
+  /**
+   * initializes the object
+   */
+  public Loader(String dir) {
+    this.dir = dir;
+  }
+  
+  /**
+   * returns the dir prefix
+   */
+  public String getDir() {
+    return dir;
+  }
+  
+  /**
+   * returns the processed filename, i.e. with the dir-prefix if it's
+   * missing
+   */
+  public String processFilename(String filename) {
+    if (!filename.startsWith(getDir()))
+      filename = getDir() + filename;
+    
+    return filename;
+  }
+  
+  /**
+   * returns a URL for the given filename, can be NULL if it fails
+   */
+  public static URL getURL(String dir, String filename) {
+    Loader         loader;
+    
+    loader = new Loader(dir);
+    return loader.getURL(filename);
+  }
+  
+  /**
+   * returns a URL for the given filename, can be NULL if it fails
+   */
+  public URL getURL(String filename) {
+    filename = processFilename(filename);
+    return Loader.class.getClassLoader().getResource(filename);
+  }
+  
+  /**
+   * returns an InputStream for the given dir and filename, can be NULL if it 
+   * fails
+   */
+  public static InputStream getInputStream(String dir, String filename) {
+    Loader         loader;
+    
+    loader = new Loader(dir);
+    return loader.getInputStream(filename);
+  }
+  
+  /**
+   * returns an InputStream for the given filename, can be NULL if it fails
+   */
+  public InputStream getInputStream(String filename) {
+    filename = processFilename(filename);
+    return Loader.class.getResourceAsStream(filename);
+  }
+  
+  /**
+   * returns a Reader for the given filename and dir, can be NULL if it fails
+   */
+  public static Reader getReader(String dir, String filename) {
+    Loader            loader;
+    
+    loader = new Loader(dir);
+    return loader.getReader(filename);
+  }
+  
+  /**
+   * returns a Reader for the given filename, can be NULL if it fails
+   */
+  public Reader getReader(String filename) {
+    InputStream          in;
+    
+    in = getInputStream(filename);
+    
+    if (in == null)
+      return null;
+    else
+      return new InputStreamReader(in);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/LogPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/LogPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/LogPanel.java	(revision 29)
@@ -0,0 +1,372 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LogPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JViewport;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/** 
+ * This panel allows log and status messages to be posted. Log messages
+ * appear in a scrollable text area, and status messages appear as one-line
+ * transient messages.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 4789 $
+ */
+public class LogPanel
+  extends JPanel
+  implements Logger, TaskLogger {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4072464549112439484L;
+
+  /** Displays the current status */
+  protected JLabel m_StatusLab = new JLabel("OK");
+  
+  /** Displays the log messages */
+  protected JTextArea m_LogText = new JTextArea(4, 20);
+
+  /** The button for viewing the log */
+  protected JButton m_logButton = new JButton("Log");
+
+  /** An indicator for whether text has been output yet */
+  protected boolean m_First = true;
+
+  /** The panel for monitoring the number of running tasks (if supplied)*/
+  protected WekaTaskMonitor m_TaskMonitor=null;
+  
+  /**
+   * Creates the log panel with no task monitor and
+   * the log always visible.
+   */
+  public LogPanel() {
+
+    this(null, false, false, true);
+  }
+
+  /**
+   * Creates the log panel with a task monitor,
+   * where the log is hidden.
+   *
+   * @param tm the task monitor, or null for none
+   */
+  public LogPanel(WekaTaskMonitor tm) {
+
+    this(tm, true, false, true);
+  }
+
+  /**
+   * Creates the log panel, possibly with task monitor,
+   * where the log is optionally hidden.
+   *
+   * @param tm the task monitor, or null for none
+   * @param logHidden true if the log should be hidden and
+   *                  acessible via a button, or false if the
+   *                  log should always be visible.
+   */
+  public LogPanel(WekaTaskMonitor tm, boolean logHidden) {
+    this(tm, logHidden, false, true);
+  }
+
+  /**
+   * Creates the log panel, possibly with task monitor,
+   * where the either the log is optionally hidden or the status
+   * (having both hidden is not allowed).
+   * 
+   *
+   * @param tm the task monitor, or null for none
+   * @param logHidden true if the log should be hidden and
+   *                  acessible via a button, or false if the
+   *                  log should always be visible.
+   * @param statusHidden true if the status bar should be hidden (i.e.
+   * @param titledBorder true if the log should have a title
+   * you only want the log part).
+   */
+  public LogPanel(WekaTaskMonitor tm, boolean logHidden, 
+      boolean statusHidden, boolean titledBorder) {
+
+    m_TaskMonitor = tm;
+    m_LogText.setEditable(false);
+    m_LogText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_StatusLab.setBorder(BorderFactory.createCompoundBorder(
+			  BorderFactory.createTitledBorder("Status"),
+			  BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+
+    // create scrolling log
+    final JScrollPane js = new JScrollPane(m_LogText);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });
+
+    if (logHidden) {
+
+      // create log window
+      final JFrame jf = new JFrame("Log");
+      jf.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    jf.setVisible(false);
+	  }
+	});
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(js, BorderLayout.CENTER);
+      jf.pack();
+      jf.setSize(450, 350);
+      
+      // display log window on request
+      m_logButton.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    jf.setVisible(true);
+	  }
+	});
+      
+      // do layout
+      setLayout(new BorderLayout());
+      JPanel logButPanel = new JPanel();
+      logButPanel.setLayout(new BorderLayout());
+      logButPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+      logButPanel.add(m_logButton, BorderLayout.CENTER);
+      JPanel p1 = new JPanel();
+      p1.setLayout(new BorderLayout());
+      p1.add(m_StatusLab, BorderLayout.CENTER);
+      p1.add(logButPanel, BorderLayout.EAST);
+      
+      if (tm == null) {
+	add(p1, BorderLayout.SOUTH);
+      } else {
+	JPanel p2 = new JPanel();
+	p2.setLayout(new BorderLayout());
+	p2.add(p1, BorderLayout.CENTER);
+	p2.add((java.awt.Component)m_TaskMonitor, BorderLayout.EAST);
+	add(p2, BorderLayout.SOUTH);
+      }
+    } else {
+      // log always visible
+      
+      JPanel p1 = new JPanel();
+      if (titledBorder) {
+        p1.setBorder(BorderFactory.createTitledBorder("Log"));
+      }
+      p1.setLayout(new BorderLayout());
+      p1.add(js, BorderLayout.CENTER);
+      setLayout(new BorderLayout());
+      add(p1, BorderLayout.CENTER);
+
+      if (tm == null) {
+        if (!statusHidden) {
+          add(m_StatusLab, BorderLayout.SOUTH);
+        }
+      } else {
+        if (!statusHidden) {
+          JPanel p2 = new JPanel();
+          p2.setLayout(new BorderLayout());
+          p2.add(m_StatusLab,BorderLayout.CENTER);
+          p2.add((java.awt.Component)m_TaskMonitor, BorderLayout.EAST);
+          add(p2, BorderLayout.SOUTH);
+        }
+      }
+    }
+    addPopup();
+  }
+
+  /**
+   * adds thousand's-separators to the number
+   * @param l       the number to print
+   * @return        the number as string with separators
+   */
+  private String printLong(long l) {
+    String        result;
+    String        str;
+    int           i;
+    int           count;
+
+    str    = Long.toString(l);
+    result = "";
+    count  = 0;
+
+    for (i = str.length() - 1; i >= 0; i--) {
+      count++;
+      result = str.charAt(i) + result;
+      if ( (count == 3) && (i > 0) ) {
+        result = "," + result;
+        count = 0;
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Add a popup menu for displaying the amount of free memory
+   * and running the garbage collector
+   */
+  private void addPopup() {
+    addMouseListener(new MouseAdapter() {
+	public void mouseClicked(MouseEvent e) {
+	  if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	       != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+	    JPopupMenu gcMenu = new JPopupMenu();
+	    JMenuItem availMem = new JMenuItem("Memory information");
+	    availMem.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent ee) {
+		  System.gc();
+		  Runtime currR = Runtime.getRuntime();
+		  long freeM = currR.freeMemory();
+		  long totalM = currR.totalMemory();
+		  long maxM = currR.maxMemory();
+		  logMessage("Memory (free/total/max.) in bytes: " + printLong(freeM) + " / " + printLong(totalM) + " / " + printLong(maxM));
+		  statusMessage("Memory (free/total/max.) in bytes: " + printLong(freeM) + " / " + printLong(totalM) + " / " + printLong(maxM));
+		}
+	      });
+	    gcMenu.add(availMem);
+	    JMenuItem runGC = new JMenuItem("Run garbage collector");
+	    runGC.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent ee) {
+		  statusMessage("Running garbage collector");
+		  System.gc();
+		  statusMessage("OK");
+		}
+	      });
+	    gcMenu.add(runGC);
+	    gcMenu.show(LogPanel.this, e.getX(), e.getY());
+	  }
+	}
+      });
+  }
+
+  /**
+   * Record the starting of a new task
+   */
+  public void taskStarted() {
+    if (m_TaskMonitor != null) {
+      m_TaskMonitor.taskStarted();
+    }
+  }
+
+  /**
+   * Record a task ending
+   */
+  public void taskFinished() {
+    if (m_TaskMonitor != null) {
+      m_TaskMonitor.taskFinished();
+    }
+  }
+    
+  /**
+   * Gets a string containing current date and time.
+   *
+   * @return a string containing the date and time.
+   */
+  protected static String getTimestamp() {
+
+    return (new SimpleDateFormat("HH:mm:ss:")).format(new Date());
+  }
+
+  /**
+   * Sends the supplied message to the log area. The current timestamp will
+   * be prepended.
+   *
+   * @param message a value of type 'String'
+   */
+  public synchronized void logMessage(String message) {
+
+    if (m_First) {
+      m_First = false;
+    } else {
+      m_LogText.append("\n");
+    }
+    m_LogText.append(LogPanel.getTimestamp() + ' ' + message);
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, message);
+  }
+
+  /**
+   * Sends the supplied message to the status line.
+   *
+   * @param message the status message
+   */
+  public synchronized void statusMessage(String message) {
+
+    m_StatusLab.setText(message);
+  }
+
+  
+  /**
+   * Tests out the log panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame("Log Panel");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final LogPanel lp = new LogPanel();
+      jf.getContentPane().add(lp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      lp.logMessage("Welcome to the generic log panel!");
+      lp.statusMessage("Hi there");
+      lp.logMessage("Funky chickens");
+      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/LogWindow.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/LogWindow.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/LogWindow.java	(revision 29)
@@ -0,0 +1,526 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LogWindow.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Tee;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.PrintStream;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTextPane;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.StyledDocument;
+
+/** 
+ * Frame that shows the output from stdout and stderr.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4973 $
+ */
+public class LogWindow 
+  extends JFrame
+  implements CaretListener, ChangeListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5650947361381061112L;
+
+  /** the name of the style for stdout */
+  public final static String STYLE_STDOUT = "stdout";
+
+  /** the name of the style for stderr */
+  public final static String STYLE_STDERR = "stderr";
+
+  /** the color of the style for stdout */
+  public final static Color COLOR_STDOUT = Color.BLACK;
+
+  /** the Color of the style for stderr */
+  public final static Color COLOR_STDERR = Color.RED;
+
+  /** whether we're debugging - enables output on stdout */
+  public final static boolean DEBUG = false;
+
+  /** whether the JTextPane has wordwrap or not */
+  public boolean m_UseWordwrap = true;
+  
+  /** the output */
+  protected JTextPane m_Output = new JTextPane();
+
+  /** the clear button */
+  protected JButton m_ButtonClear = new JButton("Clear");
+
+  /** the close button */
+  protected JButton m_ButtonClose = new JButton("Close");
+
+  /** the current size */
+  protected JLabel m_LabelCurrentSize = new JLabel("currently: 0");
+
+  /** the spinner for the max number of chars */
+  protected JSpinner m_SpinnerMaxSize = new JSpinner();
+
+  /** whether to allow wordwrap or not */
+  protected JCheckBox m_CheckBoxWordwrap = new JCheckBox("Use wordwrap");
+
+  /** for redirecting stdout */
+  protected static Tee m_TeeOut = null;
+
+  /** for redirecting stderr */
+  protected static Tee m_TeeErr = null;
+
+  /** inner class for printing to the window, is used instead of standard
+   * System.out and System.err */
+  protected class LogWindowPrintStream extends PrintStream {
+    /** the parent */
+    protected LogWindow m_Parent = null;
+
+    /** the style of the printstream */
+    protected String m_Style = null;
+    
+    /**
+     * the constructor
+     * @param parent      the parent frame
+     * @param stream      the stream (used for constructor of superclass)
+     * @param style       the style name associated with this output
+     */
+    public LogWindowPrintStream( LogWindow parent, 
+                                 PrintStream stream, 
+                                 String style ) {
+      super(stream);
+
+      m_Parent = parent;
+      m_Style  = style;
+    }
+    
+    /**
+     * flushes the printstream
+     */
+    public synchronized void flush() {
+      // ignored
+    }
+
+    /**
+     * prints the given int
+     */
+    public synchronized void print(int x) {
+      print(new Integer(x).toString());
+    }
+
+    /**
+     * prints the given boolean 
+     */
+    public synchronized void print(boolean x) {
+      print(new Boolean(x).toString());
+    }
+
+    /**
+     * prints the given string 
+     */
+    public synchronized void print(String x) {
+      StyledDocument      doc;
+      int                 size;
+      int                 maxSize;
+      int                 pos;
+      
+      doc = m_Parent.m_Output.getStyledDocument();
+
+      try {
+        // insert text
+        doc.insertString(doc.getLength(), x, doc.getStyle(m_Style));
+        
+        // move cursor to end
+        m_Parent.m_Output.setCaretPosition(doc.getLength());
+
+        // trim size if necessary
+        m_Parent.trim();
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+
+    /**
+     * prints the given object
+     */
+    public synchronized void print(Object x) {
+      String                  line;
+      Throwable               t;
+      StackTraceElement[]     trace;
+      int                     i;
+
+      if (x instanceof Throwable) {
+        t     = (Throwable) x;
+        trace = t.getStackTrace();
+        line  = t.getMessage() + "\n";
+        for (i = 0; i < trace.length; i++)
+          line += "\t" + trace[i].toString() + "\n";
+        x = line;
+      }
+
+      if (x == null)
+	print("null");
+      else
+	print(x.toString());
+    }
+
+    /**
+     * prints a new line
+     */
+    public synchronized void println() {
+      print("\n");
+    }
+
+    /**
+     * prints the given int
+     */
+    public synchronized void println(int x) {
+      print(x);
+      println();
+    }
+
+    /**
+     * prints the given boolean
+     */
+    public synchronized void println(boolean x) {
+      print(x);
+      println();
+    }
+
+    /**
+     * prints the given string
+     */
+    public synchronized void println(String x) {
+      print(x);
+      println();
+    }
+
+    /**
+     * prints the given object (for Throwables we print the stack trace)
+     */
+    public synchronized void println(Object x) {
+      print(x);
+      println();
+    }
+  }
+  
+  /**
+   * creates the frame
+   */
+  public LogWindow() {
+    super("Weka - Log");
+
+    createFrame();
+
+    // styles
+    StyledDocument      doc;
+    Style               style;
+    boolean             teeDone;
+
+    doc   = m_Output.getStyledDocument();
+    style = StyleContext.getDefaultStyleContext()
+                        .getStyle(StyleContext.DEFAULT_STYLE);
+    style = doc.addStyle(STYLE_STDOUT, style);
+    StyleConstants.setFontFamily(style, "monospaced");
+    StyleConstants.setForeground(style, COLOR_STDOUT);
+    
+    style = StyleContext.getDefaultStyleContext()
+                        .getStyle(StyleContext.DEFAULT_STYLE);
+    style = doc.addStyle(STYLE_STDERR, style);
+    StyleConstants.setFontFamily(style, "monospaced");
+    StyleConstants.setForeground(style, COLOR_STDERR);
+
+    // print streams (instantiate only once!)
+    teeDone = !((m_TeeOut == null) && (m_TeeErr == null));
+    if (!DEBUG) {
+      if (!teeDone) {
+        m_TeeOut = new Tee(System.out);
+        System.setOut(m_TeeOut);
+      }
+      m_TeeOut.add(
+          new LogWindowPrintStream(this, m_TeeOut.getDefault(), STYLE_STDOUT));
+    }
+
+    if (!teeDone) {
+      m_TeeErr = new Tee(System.err);
+      System.setErr(m_TeeErr);
+    }
+    m_TeeErr.add(
+        new LogWindowPrintStream(this, m_TeeErr.getDefault(), STYLE_STDERR));
+  }
+
+  /**
+   * creates the frame and all its components
+   */
+  protected void createFrame() {
+    JPanel                panel;
+    JPanel                panel2;
+    JPanel                panel3;
+    JPanel                panel4;
+    SpinnerNumberModel    model;
+    int                   width;
+    JLabel                label;
+
+    // set layout
+    setSize(600, 400);
+    width = getBounds().width;
+    setLocation(
+        getGraphicsConfiguration().getBounds().width - width, getLocation().y);
+    getContentPane().setLayout(new BorderLayout());
+    
+    // output 
+    getContentPane().add(new JScrollPane(m_Output), BorderLayout.CENTER);
+    setWordwrap(m_UseWordwrap);
+    
+    // button(s)
+    panel = new JPanel(new BorderLayout());
+    getContentPane().add(panel, BorderLayout.SOUTH);
+    panel3 = new JPanel(new BorderLayout());
+    panel.add(panel3, BorderLayout.SOUTH);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+    panel3.add(panel2, BorderLayout.EAST);
+
+    m_ButtonClear.setMnemonic('C');
+    m_ButtonClear.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  clear();
+	}
+      });
+    panel2.add(m_ButtonClear);
+
+    m_ButtonClose.setMnemonic('l');
+    m_ButtonClose.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  close();
+	}
+      });
+    panel2.add(m_ButtonClose);
+
+    // size + current size + wordwrap
+    panel2 = new JPanel(new GridLayout(1, 3));
+    panel3.add(panel2, BorderLayout.WEST);
+    
+    // size
+    panel4 = new JPanel(new FlowLayout());
+    panel2.add(panel4);
+    model = (SpinnerNumberModel) m_SpinnerMaxSize.getModel();
+    model.setMinimum(new Integer(1));
+    model.setStepSize(new Integer(1000));
+    model.setValue(new Integer(100000));
+    model.addChangeListener(this);
+
+    label = new JLabel("max. Size");
+    label.setDisplayedMnemonic('m');
+    label.setLabelFor(m_SpinnerMaxSize);
+
+    panel4.add(label);
+    panel4.add(m_SpinnerMaxSize);
+
+    // current size
+    panel4 = new JPanel(new FlowLayout());
+    panel2.add(panel4);
+    panel4.add(m_LabelCurrentSize);
+
+    // wordwrap
+    panel4 = new JPanel(new FlowLayout());
+    panel2.add(panel4);
+    m_CheckBoxWordwrap.setSelected(m_UseWordwrap);
+    m_CheckBoxWordwrap.addItemListener(new ItemListener() {
+	public void itemStateChanged(ItemEvent e) {
+	  setWordwrap(m_CheckBoxWordwrap.isSelected());
+	}
+      });
+    panel4.add(m_CheckBoxWordwrap);
+  }
+
+  /**
+   * clears the output
+   */
+  public void clear() {
+    m_Output.setText("");
+  }
+
+  /**
+   * closes the frame
+   */
+  public void close() {
+    setVisible(false);
+  }
+
+  /**
+   * trims the JTextPane, if too big
+   */
+  public void trim() {
+    StyledDocument      doc;
+    int                 size;
+    int                 maxSize;
+    int                 pos;
+    
+    doc = m_Output.getStyledDocument();
+
+    // too large?
+    size    = doc.getLength();
+    maxSize = ((Integer) m_SpinnerMaxSize.getValue()).intValue();
+    if (size > maxSize) {
+      try {
+        // determine EOL after which to cut
+        pos = size - maxSize;
+        while (!doc.getText(pos, 1).equals("\n"))
+          pos++;
+        while (doc.getText(pos, 1).equals("\n")) 
+          pos++;
+        // delete text
+        doc.remove(0, pos);
+      }
+      catch (Exception ex) {
+        // don't print it, otherwise we get an endless loop!
+        if (DEBUG)
+          System.out.println(ex);
+      }
+    }
+    
+    // move cursor to end
+    m_Output.setCaretPosition(doc.getLength());
+  }
+
+  /**
+   * returns a string representation (#RGB) of the given color
+   */
+  protected String colorToString(Color c) {
+    String      result;
+    
+    result = "#" + Utils.padLeft(Integer.toHexString(c.getRed()),   2)
+                 + Utils.padLeft(Integer.toHexString(c.getGreen()), 2)
+                 + Utils.padLeft(Integer.toHexString(c.getBlue()),  2);
+
+    result = result.replaceAll("\\ ", "0").toUpperCase();
+    
+    return result;
+  }
+
+  /**
+   * toggles the wordwrap<br/>
+   * override wordwrap from: 
+   * http://forum.java.sun.com/thread.jspa?threadID=498535&messageID=2356174
+   */
+  public void setWordwrap(boolean wrap) {
+    Container   parent;
+    JTextPane   outputOld;
+    
+    m_UseWordwrap = wrap;
+    if (m_CheckBoxWordwrap.isSelected() != m_UseWordwrap)
+      m_CheckBoxWordwrap.setSelected(m_UseWordwrap);
+
+    // create new JTextPane
+    parent    = m_Output.getParent();
+    outputOld = m_Output;
+    if (m_UseWordwrap)
+      m_Output = new JTextPane();
+    else
+      m_Output = new JTextPane(){
+        private static final long serialVersionUID = -8275856175921425981L;
+        public void setSize(Dimension d) {    
+          if (d.width < getGraphicsConfiguration().getBounds().width) 
+            d.width = getGraphicsConfiguration().getBounds().width; 
+          super.setSize(d);
+        }
+
+        public boolean getScrollableTracksViewportWidth() { 
+          return false; 
+        }
+      };
+    m_Output.setEditable(false);
+    m_Output.addCaretListener(this);
+    m_Output.setDocument(outputOld.getDocument());
+    m_Output.setCaretPosition(m_Output.getDocument().getLength());
+    //m_Output.setToolTipText(
+    //      "stdout = " + colorToString(COLOR_STDOUT) + ", "
+    //    + "stderr = " + colorToString(COLOR_STDERR));
+    parent.add(m_Output);
+    parent.remove(outputOld);
+  }
+
+  /**
+   * Called when the caret position is updated.
+   */
+  public void caretUpdate(CaretEvent e) {
+    m_LabelCurrentSize.setText(
+        "currently: " + m_Output.getStyledDocument().getLength());
+
+    if (DEBUG)
+      System.out.println(e);
+  }
+
+  /**
+   * Invoked when the target of the listener has changed its state.
+   */
+  public void stateChanged(ChangeEvent e) {
+    // check max size if Spinner is changed
+    if (e.getSource() == m_SpinnerMaxSize.getModel()) {
+      trim();
+      validate();
+      caretUpdate(null);
+    }
+  }
+
+  /**
+   * for testing only
+   */
+  public static void main(String[] args) {
+    LogWindow       log;
+
+    LookAndFeel.setLookAndFeel();
+    
+    log = new LogWindow();
+    log.setVisible(true);
+    log.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+    // test output
+    System.out.print("a");
+    System.err.print("a");
+    System.out.print("a");
+    System.out.println();
+    System.err.println(new java.util.Date());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/Logger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/Logger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/Logger.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Logger.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+/** 
+ * Interface for objects that display log (permanent historical) and
+ * status (transient) messages.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface Logger {
+
+  /**
+   * Sends the supplied message to the log area. These message will typically
+   * have the current timestamp prepended, and be viewable as a history.
+   *
+   * @param message the log message
+   */
+  void logMessage(String message);
+  
+  /**
+   * Sends the supplied message to the status line. These messages are
+   * typically one-line status messages to inform the user of progress
+   * during processing (i.e. it doesn't matter if the user doesn't happen
+   * to look at each message)
+   *
+   * @param message the status message.
+   */
+  void statusMessage(String message);
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.java	(revision 29)
@@ -0,0 +1,156 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LookAndFeel.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import weka.core.Utils;
+
+import java.util.Properties;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+
+/**
+ * A little helper class for setting the Look and Feel of the user interface.
+ * Was necessary, since Java 1.5 sometimes crashed the WEKA GUI (e.g. under 
+ * Linux/Gnome). Running this class from the commandline will print all
+ * available Look and Feel themes.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class LookAndFeel {
+  
+  /** The name of the properties file */
+  public static String PROPERTY_FILE = "weka/gui/LookAndFeel.props";
+
+  /** Contains the look and feel properties */
+  protected static Properties LOOKANDFEEL_PROPERTIES;
+
+  static {
+    try {
+      LOOKANDFEEL_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+    } 
+    catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+       "LookAndFeel: Could not read a LookAndFeel configuration file.\n"
+       +"An example file is included in the Weka distribution.\n"
+       +"This file should be named \"" + PROPERTY_FILE + "\"  and\n"
+       +"should be placed either in your user home (which is set\n"
+       +"to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+       +"or the directory that java was started from\n",
+       "LookAndFeel",
+       JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * sets the look and feel to the specified class
+   * 
+   * @param classname      the look and feel to use
+   * @return               whether setting was successful   
+   */
+  public static boolean setLookAndFeel(String classname) {
+    boolean          result;
+
+    try {
+      UIManager.setLookAndFeel(classname);
+      result = true;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = false;
+    }
+
+    return result;
+  }
+
+  /**
+   * sets the look and feel to the one in the props-file or if not set the 
+   * default one of the system
+   * 
+   * @return               whether setting was successful
+   */
+  public static boolean setLookAndFeel() {
+    String           classname;
+
+    classname = LOOKANDFEEL_PROPERTIES.getProperty("Theme", "");
+    if (classname.equals("")) {
+      // Java 1.5 crashes under Gnome if one sets it to the GTKLookAndFeel 
+      // theme, hence we don't set any theme by default if we're on a Linux 
+      // box.
+      if (System.getProperty("os.name").equalsIgnoreCase("linux")) {
+	return true;
+      }
+      else {
+	classname = getSystemLookAndFeel();
+      }
+    }
+
+    return setLookAndFeel(classname);
+  }
+
+  /**
+   * returns the system LnF classname
+   * 
+   * @return               the name of the System LnF class
+   */
+  public static String getSystemLookAndFeel() {
+    return UIManager.getSystemLookAndFeelClassName();
+  }
+
+  /**
+   * returns an array with the classnames of all the installed LnFs
+   * 
+   * @return               the installed LnFs
+   */
+  public static String[] getInstalledLookAndFeels() {
+    String[]               result;
+    LookAndFeelInfo[]      laf;
+    int                    i;
+
+    laf    = UIManager.getInstalledLookAndFeels();
+    result = new String[laf.length];
+    for (i = 0; i < laf.length; i++)
+      result[i] = laf[i].getClassName();
+
+    return result;
+  }
+  
+  /**
+   * prints all the available LnFs to stdout
+   * 
+   * @param args	the commandline options
+   */
+  public static void main(String[] args) {
+    String[]	list;
+    int		i;
+    
+    System.out.println("\nInstalled Look and Feel themes:");
+    list = getInstalledLookAndFeels();
+    for (i = 0; i < list.length; i++)
+      System.out.println((i+1) + ". " + list[i]);
+
+    System.out.println("\nNote: a theme can be set in '" + PROPERTY_FILE + "'.");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/LookAndFeel.props	(revision 29)
@@ -0,0 +1,10 @@
+# Look'n'Feel configuration file
+# $Revision: 1.1 $
+
+# the theme to use, none specified or empty means the system default one
+#Theme=javax.swing.plaf.metal.MetalLookAndFeel
+#Theme=com.sun.java.swing.plaf.gtk.GTKLookAndFeel
+#Theme=com.sun.java.swing.plaf.motif.MotifLookAndFeel
+#Theme=com.sun.java.swing.plaf.windows.WindowsLookAndFeel
+#Theme=com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel
+
Index: branches/MetisMQI/src/main/java/weka/gui/Main.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/Main.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/Main.java	(revision 29)
@@ -0,0 +1,1950 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Main.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.classifiers.bayes.net.GUI;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.Copyright;
+import weka.core.Instances;
+import weka.core.Memory;
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.SelectedTag;
+import weka.core.SystemInfo;
+import weka.core.Tag;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.scripting.Groovy;
+import weka.core.scripting.Jython;
+import weka.gui.arffviewer.ArffViewerMainPanel;
+import weka.gui.beans.KnowledgeFlowApp;
+import weka.gui.beans.StartUpListener;
+import weka.gui.boundaryvisualizer.BoundaryVisualizer;
+import weka.gui.experiment.Experimenter;
+import weka.gui.explorer.Explorer;
+import weka.gui.graphvisualizer.GraphVisualizer;
+import weka.gui.scripting.GroovyPanel;
+import weka.gui.scripting.JythonPanel;
+import weka.gui.sql.SqlViewer;
+import weka.gui.treevisualizer.Node;
+import weka.gui.treevisualizer.NodePlace;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeBuild;
+import weka.gui.treevisualizer.TreeVisualizer;
+import weka.gui.visualize.PlotData2D;
+import weka.gui.visualize.ThresholdVisualizePanel;
+import weka.gui.visualize.VisualizePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.LayoutManager;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JDesktopPane;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.WindowConstants;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
+/**
+ * Menu-based GUI for Weka, replacement for the GUIChooser.
+ *
+ <!-- options-start -->
+ * Valid options are: <p/>
+ * 
+ * <pre> -gui &lt;MDI|SDI&gt;
+ *  Determines the layout of the GUI:
+ *  MDI = MDI Layout
+ *  SDI = SDI Layout
+ *  (default: MDI)</pre>
+ * 
+ <!-- options-end -->
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5837 $
+ */
+public class Main
+  extends JFrame
+  implements OptionHandler {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 1453813254824253849L;
+  
+  /**
+   * DesktopPane with background image.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5837 $
+   */
+  public static class BackgroundDesktopPane
+    extends JDesktopPane {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 2046713123452402745L;
+    
+    /** the actual background image. */
+    protected Image m_Background;
+    
+    /**
+     * intializes the desktop pane.
+     * 
+     * @param image	the image to use as background
+     */
+    public BackgroundDesktopPane(String image) {
+      super();
+      
+      try {
+	m_Background = Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource(image));
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+    
+    /**
+     * draws the background image.
+     * 
+     * @param g		the graphics context
+     */
+    public void paintComponent(Graphics g) {
+      super.paintComponent(g);
+     
+      if (m_Background != null) {
+	g.setColor(Color.WHITE);
+	g.clearRect(0, 0, getWidth(), getHeight());
+	
+	int width  = m_Background.getWidth(null);
+	int height = m_Background.getHeight(null);
+	int x = (getWidth() - width) / 2;
+	int y = (getHeight() - height) / 2;
+	g.drawImage(m_Background, x, y, width, height, this);
+      }
+    }
+  }
+  
+  /**
+   * Specialized JFrame class.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5837 $
+   */
+  public static class ChildFrameSDI 
+    extends JFrame {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 8588293938686425618L;
+    
+    /** the parent frame. */
+    protected Main m_Parent;
+    
+    /**
+     * constructs a new internal frame that knows about its parent.
+     * 
+     * @param parent	the parent frame
+     * @param title	the title of the frame
+     */
+    public ChildFrameSDI(Main parent, String title) {
+      super(title);
+      
+      m_Parent = parent;
+
+      addWindowListener(new WindowAdapter() {
+	public void windowActivated(WindowEvent e) {
+	  // update title of parent
+	  if (getParentFrame() != null)
+	    getParentFrame().createTitle(getTitle());
+	}
+      });
+      
+      // add to parent
+      if (getParentFrame() != null) {
+	getParentFrame().addChildFrame(this);
+	setIconImage(getParentFrame().getIconImage());
+      }
+    }
+    
+    /**
+     * returns the parent frame, can be null.
+     * 
+     * @return		the parent frame
+     */
+    public Main getParentFrame() {
+      return m_Parent;
+    }
+    
+    /**
+     * de-registers the child frame with the parent first.
+     */
+    public void dispose() {
+      if (getParentFrame() != null) {
+	getParentFrame().removeChildFrame(this);
+	getParentFrame().createTitle("");
+      }
+      
+      super.dispose();
+    }
+  }
+  
+  /**
+   * Specialized JInternalFrame class.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5837 $
+   */
+  public static class ChildFrameMDI
+    extends JInternalFrame {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 3772573515346899959L;
+    
+    /** the parent frame. */
+    protected Main m_Parent;
+    
+    /**
+     * constructs a new internal frame that knows about its parent.
+     * 
+     * @param parent	the parent frame
+     * @param title	the title of the frame
+     */
+    public ChildFrameMDI(Main parent, String title) {
+      super(title, true, true, true, true);
+      
+      m_Parent = parent;
+
+      addInternalFrameListener(new InternalFrameAdapter() {
+	public void internalFrameActivated(InternalFrameEvent e) {
+	  // update title of parent
+	  if (getParentFrame() != null)
+	    getParentFrame().createTitle(getTitle());
+	}
+      });
+      
+      // add to parent
+      if (getParentFrame() != null) {
+	getParentFrame().addChildFrame(this);
+	getParentFrame().jDesktopPane.add(this);
+      }
+    }
+    
+    /**
+     * returns the parent frame, can be null.
+     * 
+     * @return		the parent frame
+     */
+    public Main getParentFrame() {
+      return m_Parent;
+    }
+    
+    /**
+     * de-registers the child frame with the parent first.
+     */
+    public void dispose() {
+      if (getParentFrame() != null) {
+	getParentFrame().removeChildFrame(this);
+	getParentFrame().createTitle("");
+      }
+      
+      super.dispose();
+    }
+  }
+
+  /** displays the GUI as MDI. */
+  public final static int GUI_MDI = 0;
+  /** displays the GUI as SDI. */
+  public final static int GUI_SDI = 1;
+  /** GUI tags. */
+  public static final Tag[] TAGS_GUI = {
+    new Tag(GUI_MDI, "MDI", "MDI Layout"),
+    new Tag(GUI_SDI, "SDI", "SDI Layout")
+  };
+  
+  /** the frame itself. */
+  protected Main m_Self;
+  
+  /** the type of GUI to display. */
+  protected int m_GUIType = GUI_MDI;
+  
+  /** variable for the Main class which would be set to null by the memory
+   *  monitoring thread to free up some memory if we running out of memory. */
+  protected static Main m_MainCommandline;
+  
+  /** singleton instance of the GUI. */
+  protected static Main m_MainSingleton;
+
+  /** list of things to be notified when the startup process of
+   *  the KnowledgeFlow is complete. */
+  protected static Vector m_StartupListeners = new Vector();
+
+  /** for monitoring the Memory consumption. */
+  protected static Memory m_Memory = new Memory(true);
+  
+  /** contains the child frames (title &lt;-&gt; object). */
+  protected HashSet<Container> m_ChildFrames = new HashSet<Container>();
+
+  /** The frame of the LogWindow. */
+  protected static LogWindow m_LogWindow = new LogWindow();
+
+  /** filechooser for the TreeVisualizer. */
+  protected JFileChooser m_FileChooserTreeVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for the GraphVisualizer. */
+  protected JFileChooser m_FileChooserGraphVisualizer = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for Plots. */
+  protected JFileChooser m_FileChooserPlot = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** filechooser for ROC curves. */
+  protected JFileChooser m_FileChooserROC = new JFileChooser(new File(System.getProperty("user.dir")));
+  
+  // GUI components
+  private JMenu jMenuHelp;
+  private JMenu jMenuVisualization;
+  private JMenu jMenuTools;
+  private JDesktopPane jDesktopPane;
+  private JMenu jMenuApplications;
+  private JMenuItem jMenuItemHelpSystemInfo;
+  private JMenuItem jMenuItemHelpAbout;
+  private JMenuItem jMenuItemHelpHomepage;
+  private JMenuItem jMenuItemHelpWekaWiki;
+  private JMenuItem jMenuItemHelpWekaDoc;
+  private JMenuItem jMenuItemHelpSourceforge;
+  private JMenuItem jMenuItemVisualizationBoundaryVisualizer;
+  private JMenuItem jMenuItemVisualizationGraphVisualizer;
+  private JMenuItem jMenuItemVisualizationTreeVisualizer;
+  private JMenuItem jMenuItemVisualizationROC;
+  private JMenuItem jMenuItemVisualizationPlot;
+  private JMenuItem jMenuItemToolsEnsembleLibrary;
+  private JMenuItem jMenuItemToolsSqlViewer;
+  private JMenuItem jMenuItemToolsGroovyConsole;
+  private JMenuItem jMenuItemToolsJythonConsole;
+  private JMenuItem jMenuItemToolsArffViewer;
+  private JMenuItem jMenuItemApplicationsSimpleCLI;
+  private JMenuItem jMenuItemApplicationsKnowledgeFlow;
+  private JMenuItem jMenuItemApplicationsExperimenter;
+  private JMenuItem jMenuItemApplicationsExplorer;
+  private JMenuItem jMenuItemProgramExit;
+  private JMenuItem jMenuItemProgramLogWindow;
+  private JMenuItem jMenuItemProgramMemoryUsage;
+  private JMenuItem jMenuItemProgramPreferences;  // TODO: see below
+  private JMenu jMenuProgram;
+  private JMenu jMenuExtensions;
+  private JMenu jMenuWindows;
+  private JMenuBar jMenuBar;
+  
+  /**
+   * default constructor.
+   */
+  public Main() {
+    super();
+  }
+  
+  /**
+   * creates a frame (depending on m_GUIType) and returns it.
+   * 
+   * @param parent		the parent of the generated frame
+   * @param title		the title of the frame
+   * @param c			the component to place, can be null
+   * @param layout		the layout to use, e.g., BorderLayout
+   * @param layoutConstraints	the layout constraints, e.g., BorderLayout.CENTER
+   * @param width		the width of the frame, ignored if -1
+   * @param height		the height of the frame, ignored if -1
+   * @param menu		an optional menu
+   * @param listener		if true a default listener is added
+   * @param visible		if true then the frame is made visible immediately
+   * @return			the generated frame
+   * @see 			#m_GUIType
+   */
+  protected Container createFrame(
+      Main parent, String title, Component c, LayoutManager layout, 
+      Object layoutConstraints, int width, int height, JMenuBar menu,
+      boolean listener, boolean visible) {
+
+    Container result = null;
+    
+    if (m_GUIType == GUI_MDI) {
+      final ChildFrameMDI frame = new ChildFrameMDI(parent, title);
+      
+      // layout
+      frame.setLayout(layout);
+      if (c != null)
+	frame.getContentPane().add(c, layoutConstraints);
+      
+      // menu
+      frame.setJMenuBar(menu);
+      
+      // size
+      frame.pack();
+      if ((width > -1) && (height > -1))
+	frame.setSize(width, height);
+      frame.validate();
+
+      // listener?
+      if (listener) {
+	frame.addInternalFrameListener(new InternalFrameAdapter() {
+	  public void internalFrameClosing(InternalFrameEvent e) {
+	    frame.dispose();
+	  }
+	});
+      }
+      
+      // display frame
+      if (visible) {
+	frame.setVisible(true);
+	try {
+	  frame.setSelected(true);
+	}
+	catch (Exception e) {
+	  e.printStackTrace();
+	}
+      }
+      
+      result = frame;
+    }
+    else if (m_GUIType == GUI_SDI) {
+      final ChildFrameSDI frame = new ChildFrameSDI(parent, title);
+      
+      // layout
+      frame.setLayout(layout);
+      if (c != null)
+	frame.getContentPane().add(c, layoutConstraints);
+      
+      // menu
+      frame.setJMenuBar(menu);
+      
+      // size
+      frame.pack();
+      if ((width > -1) && (height > -1))
+	frame.setSize(width, height);
+      frame.validate();
+
+      // location
+      int screenHeight = getGraphicsConfiguration().getBounds().height;
+      int screenWidth  = getGraphicsConfiguration().getBounds().width;
+      frame.setLocation(
+	  (screenWidth - frame.getBounds().width) / 2,
+	  (screenHeight - frame.getBounds().height) / 2);
+      
+      // listener?
+      if (listener) {
+	frame.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    frame.dispose();
+	  }
+	});
+      }
+      
+      // display frame
+      if (visible)
+	frame.setVisible(true);
+
+      result = frame;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * insert the menu item in a sorted fashion.
+   * 
+   * @param menu	the menu to add the item to
+   * @param menuitem	the menu item to add
+   */
+  protected void insertMenuItem(JMenu menu, JMenuItem menuitem) {
+    insertMenuItem(menu, menuitem, 0);
+  }
+  
+  /**
+   * insert the menu item in a sorted fashion.
+   * 
+   * @param menu	the menu to add the item to
+   * @param menuitem	the menu item to add
+   * @param startIndex	the index in the menu to start with (0-based)
+   */
+  protected void insertMenuItem(JMenu menu, JMenuItem menuitem, int startIndex) {
+    boolean	inserted;
+    int		i;
+    JMenuItem	current;
+    String	currentStr;
+    String	newStr;
+    
+    inserted = false;
+    newStr   = menuitem.getText().toLowerCase();
+    
+    // try to find a spot inbetween
+    for (i = startIndex; i < menu.getMenuComponentCount(); i++) {
+      if (!(menu.getMenuComponent(i) instanceof JMenuItem))
+	continue;
+      
+      current    = (JMenuItem) menu.getMenuComponent(i);
+      currentStr = current.getText().toLowerCase();
+      if (currentStr.compareTo(newStr) > 0) {
+	inserted = true;
+	menu.insert(menuitem, i);
+	break;
+      }
+    }
+    
+    // add it at the end if not yet inserted
+    if (!inserted)
+      menu.add(menuitem);
+  }
+  
+  /**
+   * initializes the GUI.
+   */
+  protected void initGUI() {
+    m_Self = this;
+    
+    try {
+      // main window
+      createTitle("");
+      this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+      this.setIconImage(new ImageIcon(getClass().getClassLoader().getResource("weka/gui/weka_icon.gif")).getImage());
+
+      // bits and pieces
+      m_FileChooserGraphVisualizer.addChoosableFileFilter(
+	  new ExtensionFileFilter(".bif", "BIF Files (*.bif)"));
+      m_FileChooserGraphVisualizer.addChoosableFileFilter(
+	  new ExtensionFileFilter(".xml", "XML Files (*.xml)"));
+
+      m_FileChooserPlot.addChoosableFileFilter(
+	  new ExtensionFileFilter(
+	      Instances.FILE_EXTENSION,
+	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));
+      m_FileChooserPlot.setMultiSelectionEnabled(true);
+
+      m_FileChooserROC.addChoosableFileFilter(
+	  new ExtensionFileFilter(
+	      Instances.FILE_EXTENSION,
+	      "ARFF Files (*" + Instances.FILE_EXTENSION + ")"));
+
+      // Desktop
+      if (m_GUIType == GUI_MDI) {
+	jDesktopPane = new BackgroundDesktopPane("weka/gui/images/weka_background.gif");
+	jDesktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
+	setContentPane(jDesktopPane);
+      }
+      else {
+	jDesktopPane = null;
+      }
+
+      // Menu
+      jMenuBar = new JMenuBar();
+      setJMenuBar(jMenuBar);
+
+      // Program
+      jMenuProgram = new JMenu();
+      jMenuBar.add(jMenuProgram);
+      jMenuProgram.setText("Program");
+      jMenuProgram.setMnemonic('P');
+
+      // Program/Preferences
+      // TODO: read all properties from all props file and display them
+      /*
+        jMenuItemProgramPreferences = new JMenuItem();
+        jMenuProgram.add(jMenuItemProgramPreferences);
+        jMenuItemProgramPreferences.setText("Preferences");
+        jMenuItemProgramPreferences.setMnemonic('P');
+        jMenuItemProgramPreferences.addActionListener(new ActionListener() {
+  	  public void actionPerformed(ActionEvent evt) {
+	    System.out.println("jMenuItemProgramPreferences.actionPerformed, event="+evt);
+	    //TODO add your code for jMenuItemProgramPreferences.actionPerformed
+  	  }
+        });
+       */
+
+      // Program/LogWindow
+      jMenuItemProgramLogWindow = new JMenuItem();
+      jMenuProgram.add(jMenuItemProgramLogWindow);
+      jMenuItemProgramLogWindow.setText("LogWindow");
+      jMenuItemProgramLogWindow.setMnemonic('L');
+      jMenuItemProgramLogWindow.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  m_LogWindow.setVisible(true);
+	}
+      });
+      
+      jMenuItemProgramMemoryUsage = new JMenuItem();
+      jMenuProgram.add(jMenuItemProgramMemoryUsage);
+      jMenuItemProgramMemoryUsage.setText("Memory usage");
+      jMenuItemProgramMemoryUsage.setMnemonic('M');
+      jMenuItemProgramMemoryUsage.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemProgramMemoryUsage.getText();
+	  if (!containsWindow(title)) {
+	    final MemoryUsagePanel panel = new MemoryUsagePanel();
+	    Container c = createFrame(
+		m_Self, title, panel, new BorderLayout(), 
+		BorderLayout.CENTER, 400, 50, null, true, true);
+	    
+	    // optimize size
+	    Dimension size = c.getPreferredSize();
+	    c.setSize(new Dimension((int) size.getWidth(), (int) size.getHeight()));
+
+	    // stop threads
+	    if (m_GUIType == GUI_MDI) {
+	      final ChildFrameMDI frame = (ChildFrameMDI) c;
+	      Point l = panel.getFrameLocation();
+	      if ((l.x != -1) && (l.y != -1))
+		frame.setLocation(l);
+	      frame.addInternalFrameListener(new InternalFrameAdapter() {
+		public void internalFrameClosing(InternalFrameEvent e) {
+		  panel.stopMonitoring();
+		}
+	      });
+	    }
+	    else {
+	      final ChildFrameSDI frame = (ChildFrameSDI) c;
+	      Point l = panel.getFrameLocation();
+	      if ((l.x != -1) && (l.y != -1))
+		frame.setLocation(l);
+	      frame.addWindowListener(new WindowAdapter() {
+		public void windowClosing(WindowEvent e) {
+		  panel.stopMonitoring();
+		}
+	      });
+	    }
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      jMenuProgram.add(new JSeparator());
+
+      // Program/Exit
+      jMenuItemProgramExit = new JMenuItem();
+      jMenuProgram.add(jMenuItemProgramExit);
+      jMenuItemProgramExit.setText("Exit");
+      jMenuItemProgramExit.setMnemonic('E');
+      jMenuItemProgramExit.addActionListener(new ActionListener() {	
+	public void actionPerformed(ActionEvent evt) {
+	  // close all children
+	  Iterator iter = getWindowList();
+	  Vector<Container> list = new Vector<Container>();
+	  while (iter.hasNext())
+	    list.add((Container) iter.next());
+	  for (int i = 0; i < list.size(); i++) {
+	    Container c = list.get(i);
+	    if (c instanceof ChildFrameMDI)
+	      ((ChildFrameMDI) c).dispose();
+	    else if (c instanceof ChildFrameSDI)
+	      ((ChildFrameSDI) c).dispose();
+	  }
+	  // close logwindow
+	  m_LogWindow.dispose();
+	  // close main window
+	  m_Self.dispose();
+	  // make sure we stop
+	  System.exit(0);
+	}
+      });
+
+      // Applications
+      jMenuApplications = new JMenu();
+      jMenuBar.add(jMenuApplications);
+      jMenuApplications.setText("Applications");
+      jMenuApplications.setMnemonic('A');
+
+      // Applications/Explorer
+      jMenuItemApplicationsExplorer = new JMenuItem();
+      jMenuApplications.add(jMenuItemApplicationsExplorer);
+      jMenuItemApplicationsExplorer.setText("Explorer");
+      jMenuItemApplicationsExplorer.setMnemonic('E');
+      jMenuItemApplicationsExplorer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemApplicationsExplorer.getText();
+	  if (!containsWindow(title)) {
+	    createFrame(
+		m_Self, title, new Explorer(), new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, null, true, true);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Applications/Experimenter
+      jMenuItemApplicationsExperimenter = new JMenuItem();
+      jMenuApplications.add(jMenuItemApplicationsExperimenter);
+      jMenuItemApplicationsExperimenter.setText("Experimenter");
+      jMenuItemApplicationsExperimenter.setMnemonic('X');
+      jMenuItemApplicationsExperimenter.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemApplicationsExperimenter.getText();
+	  if (!containsWindow(title)) {
+	    createFrame(
+		m_Self, title, new Experimenter(false), new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, null, true, true);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Applications/KnowledgeFlow
+      jMenuItemApplicationsKnowledgeFlow = new JMenuItem();
+      jMenuApplications.add(jMenuItemApplicationsKnowledgeFlow);
+      jMenuItemApplicationsKnowledgeFlow.setText("KnowledgeFlow");
+      jMenuItemApplicationsKnowledgeFlow.setMnemonic('K');
+      jMenuItemApplicationsKnowledgeFlow.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemApplicationsKnowledgeFlow.getText();
+	  if (!containsWindow(title)) {
+	    KnowledgeFlowApp.createSingleton(new String[0]);
+	    createFrame(
+		m_Self, title, KnowledgeFlowApp.getSingleton(), new BorderLayout(), 
+		BorderLayout.CENTER, 900, 600, null, true, true);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Applications/SimpleCLI
+      jMenuItemApplicationsSimpleCLI = new JMenuItem();
+      jMenuApplications.add(jMenuItemApplicationsSimpleCLI);
+      jMenuItemApplicationsSimpleCLI.setText("SimpleCLI");
+      jMenuItemApplicationsSimpleCLI.setMnemonic('S');
+      jMenuItemApplicationsSimpleCLI.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemApplicationsSimpleCLI.getText();
+	  if (!containsWindow(title)) {
+	    try {
+	      createFrame(
+		  m_Self, title, new SimpleCLIPanel(), new BorderLayout(), 
+		  BorderLayout.CENTER, 600, 500, null, true, true);
+	    }
+	    catch (Exception e) {
+	      e.printStackTrace();
+	      JOptionPane.showMessageDialog(
+		  m_Self, "Error instantiating SimpleCLI:\n" + e.getMessage());
+	      return;
+	    }
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Tools
+      jMenuTools = new JMenu();
+      jMenuBar.add(jMenuTools);
+      jMenuTools.setText("Tools");
+      jMenuTools.setMnemonic('T');
+
+      // Tools/ArffViewer
+      jMenuItemToolsArffViewer = new JMenuItem();
+      jMenuTools.add(jMenuItemToolsArffViewer);
+      jMenuItemToolsArffViewer.setText("ArffViewer");
+      jMenuItemToolsArffViewer.setMnemonic('A');
+      jMenuItemToolsArffViewer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemToolsArffViewer.getText();
+	  if (!containsWindow(title)) {
+	    ArffViewerMainPanel panel = new ArffViewerMainPanel(null);
+	    panel.setConfirmExit(false);
+	    Container frame = createFrame(
+		m_Self, title, panel, new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, panel.getMenu(), true, true);
+	    panel.setParent(frame);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Tools/SqlViewer
+      jMenuItemToolsSqlViewer = new JMenuItem();
+      jMenuTools.add(jMenuItemToolsSqlViewer);
+      jMenuItemToolsSqlViewer.setText("SqlViewer");
+      jMenuItemToolsSqlViewer.setMnemonic('S');
+      jMenuItemToolsSqlViewer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemToolsSqlViewer.getText();
+	  if (!containsWindow(title)) {
+	    final SqlViewer sql = new SqlViewer(null);
+	    final Container frame = createFrame(
+		m_Self, title, sql, new BorderLayout(), 
+		BorderLayout.CENTER, -1, -1, null, false, true);
+
+	    // custom listener
+	    if (frame instanceof ChildFrameMDI) {
+	      ((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
+		public void internalFrameClosing(InternalFrameEvent e) {
+		  sql.saveSize();
+		  ((ChildFrameMDI) frame).dispose();
+		}
+	      });
+	    }
+	    else if (frame instanceof ChildFrameSDI) {
+	      ((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
+		public void windowClosing(WindowEvent e) {
+		  sql.saveSize();
+		  ((ChildFrameSDI) frame).dispose();
+		}
+	      });
+	    }
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+      
+      // Tools/Bayes net editor
+      // Tools/Bayes net editor
+      final JMenuItem jMenuItemBayesNet = new JMenuItem();
+      jMenuTools.add(jMenuItemBayesNet);
+      jMenuItemBayesNet.setText("Bayes net editor");
+      jMenuItemBayesNet.setMnemonic('N');
+
+      jMenuItemBayesNet.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          String title = jMenuItemBayesNet.getText();
+          
+          if (!containsWindow(title)) {
+            final GUI bayesNetGUI = new GUI();
+            final Container frame = createFrame(
+                m_Self, title, bayesNetGUI, new BorderLayout(), 
+                BorderLayout.CENTER, 800, 600, bayesNetGUI.getMenuBar(), false, true);
+          }
+          else {
+            showWindow(getWindow(title));
+          }          
+        }       
+      });
+
+      // Tools/Groovy console
+      if (Groovy.isPresent()) {
+	jMenuItemToolsGroovyConsole = new JMenuItem();
+	jMenuTools.add(jMenuItemToolsGroovyConsole);
+	jMenuItemToolsGroovyConsole.setText("Groovy console");
+	jMenuItemToolsGroovyConsole.setMnemonic('G');
+	jMenuItemToolsGroovyConsole.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent evt) {
+	    String title = jMenuItemToolsGroovyConsole.getText();
+	    if (!containsWindow(title)) {
+	      final GroovyPanel panel = new GroovyPanel();
+	      final Container frame = createFrame(
+		  m_Self, title, panel, new BorderLayout(), 
+		  BorderLayout.CENTER, 800, 600, panel.getMenuBar(), false, true);
+
+	      // custom listener
+	      if (frame instanceof ChildFrameMDI) {
+		((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
+		  public void internalFrameClosing(InternalFrameEvent e) {
+		    ((ChildFrameMDI) frame).dispose();
+		  }
+		});
+	      }
+	      else if (frame instanceof ChildFrameSDI) {
+		((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
+		  public void windowClosing(WindowEvent e) {
+		    ((ChildFrameSDI) frame).dispose();
+		  }
+		});
+	      }
+	    }
+	    else {
+	      showWindow(getWindow(title));
+	    }
+	  }
+	});
+      }
+
+      // Tools/Jython console
+      if (Jython.isPresent()) {
+	jMenuItemToolsJythonConsole = new JMenuItem();
+	jMenuTools.add(jMenuItemToolsJythonConsole);
+	jMenuItemToolsJythonConsole.setText("Jython console");
+	jMenuItemToolsJythonConsole.setMnemonic('J');
+	jMenuItemToolsJythonConsole.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent evt) {
+	    String title = jMenuItemToolsJythonConsole.getText();
+	    if (!containsWindow(title)) {
+	      final JythonPanel panel = new JythonPanel();
+	      final Container frame = createFrame(
+		  m_Self, title, panel, new BorderLayout(), 
+		  BorderLayout.CENTER, 800, 600, panel.getMenuBar(), false, true);
+
+	      // custom listener
+	      if (frame instanceof ChildFrameMDI) {
+		((ChildFrameMDI) frame).addInternalFrameListener(new InternalFrameAdapter() {
+		  public void internalFrameClosing(InternalFrameEvent e) {
+		    ((ChildFrameMDI) frame).dispose();
+		  }
+		});
+	      }
+	      else if (frame instanceof ChildFrameSDI) {
+		((ChildFrameSDI) frame).addWindowListener(new WindowAdapter() {
+		  public void windowClosing(WindowEvent e) {
+		    ((ChildFrameSDI) frame).dispose();
+		  }
+		});
+	      }
+	    }
+	    else {
+	      showWindow(getWindow(title));
+	    }
+	  }
+      });
+      }
+
+      // Tools/EnsembleLibrary
+      /* currently disabled due to bugs... FracPete
+      jMenuItemToolsEnsembleLibrary = new JMenuItem();
+      jMenuTools.add(jMenuItemToolsEnsembleLibrary);
+      jMenuItemToolsEnsembleLibrary.setText("EnsembleLibrary");
+      jMenuItemToolsEnsembleLibrary.setMnemonic('E');
+      jMenuItemToolsEnsembleLibrary.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemToolsEnsembleLibrary.getText();
+	  if (!containsWindow(title)) {
+	    EnsembleLibrary value = new EnsembleLibrary();
+	    EnsembleLibraryEditor libraryEditor = new EnsembleLibraryEditor();
+	    libraryEditor.setValue(value);
+	    createFrame(
+		m_Self, title, libraryEditor.getCustomEditor(), new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, null, true, true);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+      */
+
+      // Visualization
+      jMenuVisualization = new JMenu();
+      jMenuBar.add(jMenuVisualization);
+      jMenuVisualization.setText("Visualization");
+      jMenuVisualization.setMnemonic('V');
+
+      // Visualization/Plot
+      jMenuItemVisualizationPlot = new JMenuItem();
+      jMenuVisualization.add(jMenuItemVisualizationPlot);
+      jMenuItemVisualizationPlot.setText("Plot");
+      jMenuItemVisualizationPlot.setMnemonic('P');
+      jMenuItemVisualizationPlot.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  // choose file
+	  int retVal = m_FileChooserPlot.showOpenDialog(m_Self);
+	  if (retVal != JFileChooser.APPROVE_OPTION)
+	    return;
+
+	  // build plot
+	  VisualizePanel panel = new VisualizePanel();
+	  String filenames = "";
+	  File[] files = m_FileChooserPlot.getSelectedFiles();
+	  for (int j = 0; j < files.length; j++) {
+	    String filename = files[j].getAbsolutePath();
+	    if (j > 0)
+	      filenames += ", ";
+	    filenames += filename;
+	    System.err.println("Loading instances from " + filename);
+	    try {
+	      Reader r = new java.io.BufferedReader(new FileReader(filename));
+	      Instances i = new Instances(r);
+	      i.setClassIndex(i.numAttributes()-1);
+	      PlotData2D pd1 = new PlotData2D(i);
+
+	      if (j == 0) {
+		pd1.setPlotName("Master plot");
+		panel.setMasterPlot(pd1);
+	      } else {
+		pd1.setPlotName("Plot "+(j+1));
+		pd1.m_useCustomColour = true;
+		pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; 
+		panel.addPlot(pd1);
+	      }
+	    }
+	    catch (Exception e) {
+	      e.printStackTrace();
+	      JOptionPane.showMessageDialog(
+		  m_Self, "Error loading file '" + files[j] + "':\n" + e.getMessage());
+	      return;
+	    }
+	  }
+
+	  // create frame
+	  createFrame(
+	      m_Self, jMenuItemVisualizationPlot.getText() + " - " + filenames, 
+	      panel, new BorderLayout(), 
+	      BorderLayout.CENTER, 800, 600, null, true, true);
+	}
+      });
+
+      // Visualization/ROC
+      // based on this Wiki article:
+      // http://weka.sourceforge.net/wiki/index.php/Visualizing_ROC_curve
+      jMenuItemVisualizationROC = new JMenuItem();
+      jMenuVisualization.add(jMenuItemVisualizationROC);
+      jMenuItemVisualizationROC.setText("ROC");
+      jMenuItemVisualizationROC.setMnemonic('R');
+      jMenuItemVisualizationROC.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  // choose file
+	  int retVal = m_FileChooserROC.showOpenDialog(m_Self);
+	  if (retVal != JFileChooser.APPROVE_OPTION)
+	    return;
+
+	  // create plot
+	  String filename  = m_FileChooserROC.getSelectedFile().getAbsolutePath();
+	  Instances result = null;
+	  try {
+	    result = new Instances(new BufferedReader(new FileReader(filename)));
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    JOptionPane.showMessageDialog(
+		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
+	    return;
+	  }
+	  result.setClassIndex(result.numAttributes() - 1);
+	  ThresholdVisualizePanel vmc = new ThresholdVisualizePanel();
+	  vmc.setROCString("(Area under ROC = " + 
+	      Utils.doubleToString(ThresholdCurve.getROCArea(result), 4) + ")");
+	  vmc.setName(result.relationName());
+	  PlotData2D tempd = new PlotData2D(result);
+	  tempd.setPlotName(result.relationName());
+	  tempd.addInstanceNumberAttribute();
+	  try {
+	    vmc.addPlot(tempd);
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    JOptionPane.showMessageDialog(
+		m_Self, "Error adding plot:\n" + e.getMessage());
+	    return;
+	  }
+
+	  createFrame(
+	      m_Self, jMenuItemVisualizationROC.getText() + " - " + filename, 
+	      vmc, new BorderLayout(), 
+	      BorderLayout.CENTER, 800, 600, null, true, true);
+	}
+      });
+
+      // Visualization/TreeVisualizer
+      jMenuItemVisualizationTreeVisualizer = new JMenuItem();
+      jMenuVisualization.add(jMenuItemVisualizationTreeVisualizer);
+      jMenuItemVisualizationTreeVisualizer.setText("TreeVisualizer");
+      jMenuItemVisualizationTreeVisualizer.setMnemonic('T');
+      jMenuItemVisualizationTreeVisualizer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  // choose file
+	  int retVal = m_FileChooserTreeVisualizer.showOpenDialog(m_Self);
+	  if (retVal != JFileChooser.APPROVE_OPTION)
+	    return;
+
+	  // build tree
+	  String filename = m_FileChooserTreeVisualizer.getSelectedFile().getAbsolutePath();
+	  TreeBuild builder = new TreeBuild();
+	  Node top = null;
+	  NodePlace arrange = new PlaceNode2();
+	  try {
+	    top = builder.create(new FileReader(filename));
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    JOptionPane.showMessageDialog(
+		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
+	    return;
+	  }
+
+	  // create frame
+	  createFrame(
+	      m_Self, jMenuItemVisualizationTreeVisualizer.getText() + " - " + filename, 
+	      new TreeVisualizer(null, top, arrange), new BorderLayout(), 
+	      BorderLayout.CENTER, 800, 600, null, true, true);
+	}
+      });
+
+      // Visualization/GraphVisualizer
+      jMenuItemVisualizationGraphVisualizer = new JMenuItem();
+      jMenuVisualization.add(jMenuItemVisualizationGraphVisualizer);
+      jMenuItemVisualizationGraphVisualizer.setText("GraphVisualizer");
+      jMenuItemVisualizationGraphVisualizer.setMnemonic('G');
+      jMenuItemVisualizationGraphVisualizer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  // choose file
+	  int retVal = m_FileChooserGraphVisualizer.showOpenDialog(m_Self);
+	  if (retVal != JFileChooser.APPROVE_OPTION)
+	    return;
+
+	  // build graph
+	  String filename = m_FileChooserGraphVisualizer.getSelectedFile().getAbsolutePath();
+	  GraphVisualizer panel = new GraphVisualizer();
+	  try{
+	    if (    filename.toLowerCase().endsWith(".xml") 
+		|| filename.toLowerCase().endsWith(".bif") ) {
+	      panel.readBIF(new FileInputStream(filename));
+	    }
+	    else {
+	      panel.readDOT(new FileReader(filename));
+	    }
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    JOptionPane.showMessageDialog(
+		m_Self, "Error loading file '" + filename + "':\n" + e.getMessage());
+	    return;
+	  }
+
+	  // create frame
+	  createFrame(
+	      m_Self, jMenuItemVisualizationGraphVisualizer.getText() + " - " + filename, 
+	      panel, new BorderLayout(), 
+	      BorderLayout.CENTER, 800, 600, null, true, true);
+	}
+      });
+
+      // Visualization/BoundaryVisualizer
+      jMenuItemVisualizationBoundaryVisualizer = new JMenuItem();
+      jMenuVisualization.add(jMenuItemVisualizationBoundaryVisualizer);
+      jMenuItemVisualizationBoundaryVisualizer.setText("BoundaryVisualizer");
+      jMenuItemVisualizationBoundaryVisualizer.setMnemonic('B');
+      jMenuItemVisualizationBoundaryVisualizer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemVisualizationBoundaryVisualizer.getText();
+	  if (!containsWindow(title)) {
+	    createFrame(
+		m_Self, title, new BoundaryVisualizer(), new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, null, true, true);
+	    // dont' do a System.exit after last window got closed!
+	    BoundaryVisualizer.setExitIfNoWindowsOpen(false);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // Extensions
+      jMenuExtensions = new JMenu("Extensions");
+      jMenuExtensions.setMnemonic(java.awt.event.KeyEvent.VK_E);
+      jMenuBar.add(jMenuExtensions);
+      jMenuExtensions.setVisible(false);
+
+      String extensions = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
+	  MainMenuExtension.class.getName(), "");
+
+      if (extensions.length() > 0) {
+	jMenuExtensions.setVisible(true);
+	String[] classnames = GenericObjectEditor.EDITOR_PROPERTIES.getProperty(
+	    MainMenuExtension.class.getName(), "").split(",");
+	Hashtable<String,JMenu> submenus = new Hashtable<String,JMenu>();
+
+	// add all extensions
+	for (int i = 0; i < classnames.length; i++) {
+	  String classname = classnames[i];
+	  try {
+	    MainMenuExtension ext = (MainMenuExtension) Class.forName(classname).newInstance();
+
+	    // menuitem in a submenu?
+	    JMenu submenu = null;
+	    if (ext.getSubmenuTitle() != null) {
+	      submenu = submenus.get(ext.getSubmenuTitle());
+	      if (submenu == null) {
+		submenu = new JMenu(ext.getSubmenuTitle());
+		submenus.put(ext.getSubmenuTitle(), submenu);
+		insertMenuItem(jMenuExtensions, submenu);
+	      }
+	    }
+
+	    // create menu item
+	    JMenuItem menuitem = new JMenuItem();
+	    menuitem.setText(ext.getMenuTitle());
+	    // does the extension need a frame or does it have its own ActionListener?
+	    ActionListener listener = ext.getActionListener(m_Self);
+	    if (listener != null) {
+	      menuitem.addActionListener(listener);
+	    }
+	    else {
+	      final JMenuItem finalMenuitem = menuitem;
+	      final MainMenuExtension finalExt = ext;
+	      menuitem.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		  Component frame = createFrame(
+		      m_Self, finalMenuitem.getText(), 
+		      null, null, null, -1, -1, null, false, false);
+		  finalExt.fillFrame(frame);
+		  frame.setVisible(true);
+		}
+	      });
+	    }
+
+	    // sorted insert of menu item
+	    if (submenu != null)
+	      insertMenuItem(submenu, menuitem);
+	    else
+	      insertMenuItem(jMenuExtensions, menuitem);
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	  }
+	}
+      }
+
+      // Windows
+      jMenuWindows = new JMenu("Windows");
+      jMenuWindows.setMnemonic(java.awt.event.KeyEvent.VK_W);
+      jMenuBar.add(jMenuWindows);
+      jMenuWindows.setVisible(false);  // initially, there are no windows open
+
+      // Help
+      jMenuHelp = new JMenu();
+      jMenuBar.add(jMenuHelp);
+      jMenuHelp.setText("Help");
+      jMenuHelp.setMnemonic('H');
+
+      // Help/Homepage
+      jMenuItemHelpHomepage = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpHomepage);
+      jMenuItemHelpHomepage.setText("Weka homepage");
+      jMenuItemHelpHomepage.setMnemonic('H');
+      jMenuItemHelpHomepage.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  BrowserHelper.openURL(m_Self, "http://www.cs.waikato.ac.nz/~ml/weka/");
+	}
+      });
+
+      jMenuHelp.add(new JSeparator());
+
+/*      // Help/WekaDoc
+      jMenuItemHelpWekaDoc = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpWekaDoc);
+      jMenuItemHelpWekaDoc.setText("Online documentation");
+      jMenuItemHelpWekaDoc.setMnemonic('D');
+      jMenuItemHelpWekaDoc.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  BrowserHelper.openURL(m_Self, "http://weka.sourceforge.net/wekadoc/");
+	}
+      }); */
+
+      // Help/WekaWiki
+      jMenuItemHelpWekaWiki = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpWekaWiki);
+      jMenuItemHelpWekaWiki.setText("HOWTOs, code snippets, etc.");
+      jMenuItemHelpWekaWiki.setMnemonic('W');
+      jMenuItemHelpWekaWiki.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  BrowserHelper.openURL(m_Self, "http://weka.wikispaces.com/");
+	}
+      });
+
+      // Help/Sourceforge
+      jMenuItemHelpSourceforge = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpSourceforge);
+      jMenuItemHelpSourceforge.setText("Weka on SourceForge");
+      jMenuItemHelpSourceforge.setMnemonic('F');
+      jMenuItemHelpSourceforge.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  BrowserHelper.openURL(m_Self, "http://sourceforge.net/projects/weka/");
+	}
+      });
+
+      jMenuHelp.add(new JSeparator());
+
+      // Help/SystemInfo
+      jMenuItemHelpSystemInfo = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpSystemInfo);
+      jMenuItemHelpSystemInfo.setText("SystemInfo");
+      jMenuItemHelpHomepage.setMnemonic('S');
+      jMenuItemHelpSystemInfo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemHelpSystemInfo.getText();
+	  if (!containsWindow(title)) {
+	    // get info
+	    Hashtable info = new SystemInfo().getSystemInfo();
+
+	    // sort names
+	    Vector names = new Vector();
+	    Enumeration enm = info.keys();
+	    while (enm.hasMoreElements())
+	      names.add(enm.nextElement());
+	    Collections.sort(names);
+
+	    // generate table
+	    String[][] data = new String[info.size()][2];
+	    for (int i = 0; i < names.size(); i++) {
+	      data[i][0] = names.get(i).toString();
+	      data[i][1] = info.get(data[i][0]).toString();
+	    }
+	    String[] titles = new String[]{"Key", "Value"};
+	    JTable table = new JTable(data, titles);
+
+	    createFrame(
+		m_Self, title, new JScrollPane(table), new BorderLayout(), 
+		BorderLayout.CENTER, 800, 600, null, true, true);
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      jMenuHelp.add(new JSeparator());
+
+      // Help/About
+      jMenuItemHelpAbout = new JMenuItem();
+      jMenuHelp.add(jMenuItemHelpAbout);
+      jMenuItemHelpAbout.setText("About");
+      jMenuItemHelpAbout.setMnemonic('A');
+      jMenuItemHelpAbout.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent evt) {
+	  String title = jMenuItemHelpAbout.getText();
+	  if (!containsWindow(title)) {
+	    JPanel wekaPan = new JPanel();
+	    wekaPan.setToolTipText("Weka, a native bird of New Zealand");
+	    ImageIcon wii = new ImageIcon(Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("weka/gui/weka3.gif")));
+	    JLabel wekaLab = new JLabel(wii);
+	    wekaPan.add(wekaLab);
+	    Container frame = createFrame(
+		m_Self, title, wekaPan, new BorderLayout(), 
+		BorderLayout.CENTER, -1, -1, null, true, true);
+
+	    JPanel titlePan = new JPanel();
+	    titlePan.setLayout(new GridLayout(8,1));
+	    titlePan.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+	    titlePan.add(new JLabel("Waikato Environment for", SwingConstants.CENTER));
+	    titlePan.add(new JLabel("Knowledge Analysis", SwingConstants.CENTER));
+	    titlePan.add(new JLabel(""));
+	    titlePan.add(new JLabel("Version " + Version.VERSION, SwingConstants.CENTER));
+	    titlePan.add(new JLabel(""));
+	    titlePan.add(new JLabel("(c) " + Copyright.getFromYear() + " - " + Copyright.getToYear(), SwingConstants.CENTER));
+	    titlePan.add(new JLabel(Copyright.getOwner(), SwingConstants.CENTER));
+	    titlePan.add(new JLabel(Copyright.getAddress(), SwingConstants.CENTER));
+
+	    if (frame instanceof ChildFrameMDI) {
+	      ((ChildFrameMDI) frame).getContentPane().add(titlePan, BorderLayout.NORTH);
+	      ((ChildFrameMDI) frame).pack();
+	    }
+	    else if (frame instanceof ChildFrameSDI) {
+	      ((ChildFrameSDI) frame).getContentPane().add(titlePan, BorderLayout.NORTH);
+	      ((ChildFrameSDI) frame).pack();
+	    }
+	  }
+	  else {
+	    showWindow(getWindow(title));
+	  }
+	}
+      });
+
+      // size + position
+      int screenHeight = getGraphicsConfiguration().getBounds().height;
+      int screenWidth  = getGraphicsConfiguration().getBounds().width;
+      if (m_GUIType == GUI_MDI) {
+	int newHeight = (int) (((double) screenHeight) * 0.75);
+	int newWidth  = (int) (((double) screenWidth)  * 0.75);
+	setSize(
+	    1000 > newWidth  ? newWidth  : 1000,
+		800  > newHeight ? newHeight : 800);
+	setLocation(
+	    (screenWidth - getBounds().width) / 2,
+	    (screenHeight - getBounds().height) / 2);
+      }
+      else if (m_GUIType == GUI_SDI) {
+	pack();
+	setSize(screenWidth, getHeight());
+	setLocation(0, 0);
+      }
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * creates and displays the title.
+   * 
+   * @param title 	the additional part of the title
+   */
+  protected void createTitle(String title) {
+    String	newTitle;
+    
+    newTitle = "Weka " + new Version();
+    if (title.length() != 0)
+      newTitle += " - " + title;
+    
+    setTitle(newTitle);
+  }
+  
+  /**
+   * adds the given child frame to the list of frames.
+   * 
+   * @param c 		the child frame to add
+   */
+  public void addChildFrame(Container c) {
+    m_ChildFrames.add(c);
+    windowListChanged();
+  }
+  
+  /**
+   * tries to remove the child frame, it returns true if it could do such.
+   * 
+   * @param c 		the child frame to remove
+   * @return 		true if the child frame could be removed
+   */
+  public boolean removeChildFrame(Container c) {
+    boolean result = m_ChildFrames.remove(c);
+    windowListChanged();
+    return result;
+  }
+  
+  /**
+   * brings child frame to the top.
+   * 
+   * @param c 		the frame to activate
+   * @return 		true if frame was activated
+   */
+  public boolean showWindow(Container c) {
+    boolean        	result;
+    ChildFrameMDI	mdiFrame;
+    ChildFrameSDI	sdiFrame;
+    
+    if (c != null) {
+      try {
+	if (c instanceof ChildFrameMDI) {
+	  mdiFrame = (ChildFrameMDI) c;
+	  mdiFrame.setIcon(false);
+	  mdiFrame.toFront();
+	  createTitle(mdiFrame.getTitle());
+	}
+	else if (c instanceof ChildFrameSDI) {
+	  sdiFrame = (ChildFrameSDI) c;
+	  sdiFrame.setExtendedState(JFrame.NORMAL);
+	  sdiFrame.toFront();
+	  createTitle(sdiFrame.getTitle());
+	}
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+      result = true;
+    }
+    else {
+      result = false;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * brings the first frame to the top that is of the specified
+   * window class.
+   *  
+   * @param windowClass	the class to display the first child for
+   * @return		true, if a child was found and brought to front
+   */
+  public boolean showWindow(Class windowClass) {
+    return showWindow(getWindow(windowClass));
+  }
+  
+  /**
+   * returns all currently open frames.
+   * 
+   * @return 		an iterator over all currently open frame
+   */
+  public Iterator getWindowList() {
+    return m_ChildFrames.iterator();
+  }
+
+  /**
+   * returns the first instance of the given window class, null if none can be 
+   * found.
+   * 
+   * @param windowClass	the class to retrieve the first instance for
+   * @return		null, if no instance can be found
+   */
+  public Container getWindow(Class windowClass) {
+    Container	result;
+    Iterator	iter;
+    Container	current;
+    
+    result = null;
+    iter   = getWindowList();
+    while (iter.hasNext()) {
+      current = (Container) iter.next();
+      if (current.getClass() == windowClass) {
+        result = current;
+        break;
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns the first window with the given title, null if none can be 
+   * found.
+   * 
+   * @param title	the title to look for
+   * @return		null, if no instance can be found
+   */
+  public Container getWindow(String title) {
+    Container	result;
+    Iterator	iter;
+    Container	current;
+    boolean	found;
+    
+    result = null;
+    iter   = getWindowList();
+    while (iter.hasNext()) {
+      current = (Container) iter.next();
+      found   = false;
+      
+      if (current instanceof ChildFrameMDI)
+	found = ((ChildFrameMDI) current).getTitle().equals(title);
+      else if (current instanceof ChildFrameSDI)
+	found = ((ChildFrameSDI) current).getTitle().equals(title);
+	
+      if (found) {
+        result = current;
+        break;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * checks, whether an instance of the given window class is already in
+   * the Window list.
+   * 
+   * @param windowClass	the class to check for an instance in the current
+   * 			window list
+   * @return		true if the class is already listed in the Window list
+   */
+  public boolean containsWindow(Class windowClass) {
+    return (getWindow(windowClass) != null);
+  }
+  
+  /**
+   * checks, whether a window with the given title is already in
+   * the Window list.
+   * 
+   * @param title	the title to check for in the current window list
+   * @return		true if a window with the given title is already 
+   * 			listed in the Window list
+   */
+  public boolean containsWindow(String title) {
+    return (getWindow(title) != null);
+  }
+  
+  /**
+   * minimizes all windows.
+   */
+  public void minimizeWindows() {
+    Iterator	iter;
+    Container	frame;
+    
+    iter = getWindowList();
+    while (iter.hasNext()) {
+      frame = (Container) iter.next();
+      try {
+	if (frame instanceof ChildFrameMDI)
+	  ((ChildFrameMDI) frame).setIcon(true);
+	else if (frame instanceof ChildFrameSDI)
+	  ((ChildFrameSDI) frame).setExtendedState(JFrame.ICONIFIED);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * restores all windows.
+   */
+  public void restoreWindows() {
+    Iterator	iter;
+    Container	frame;
+    
+    iter = getWindowList();
+    while (iter.hasNext()) {
+      frame = (Container) iter.next();
+      try {
+	if (frame instanceof ChildFrameMDI)
+	  ((ChildFrameMDI) frame).setIcon(false);
+	else if (frame instanceof ChildFrameSDI)
+	  ((ChildFrameSDI) frame).setExtendedState(JFrame.NORMAL);
+    }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * is called when window list changed somehow (add or remove).
+   */
+  public void windowListChanged() {
+    createWindowMenu();
+  }
+  
+  /**
+   * creates the menu of currently open windows.
+   */
+  protected synchronized void createWindowMenu() {
+    Iterator          iter;
+    JMenuItem         menuItem;
+    int	              startIndex;
+    
+    // remove all existing entries
+    jMenuWindows.removeAll();
+    
+    // minimize + restore + separator
+    menuItem = new JMenuItem("Minimize");
+    menuItem.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        minimizeWindows();
+      }
+    });
+    jMenuWindows.add(menuItem);
+    
+    menuItem = new JMenuItem("Restore");
+    menuItem.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        restoreWindows();
+      }
+    });
+    jMenuWindows.add(menuItem);
+    
+    jMenuWindows.addSeparator();
+    
+    // windows
+    startIndex = jMenuWindows.getMenuComponentCount() - 1;
+    iter = getWindowList();
+    jMenuWindows.setVisible(iter.hasNext());
+    while (iter.hasNext()) {
+      Container frame = (Container) iter.next();
+      if (frame instanceof ChildFrameMDI)
+	menuItem = new JMenuItem(((ChildFrameMDI) frame).getTitle());
+      else if (frame instanceof ChildFrameSDI)
+	menuItem = new JMenuItem(((ChildFrameSDI) frame).getTitle());
+      insertMenuItem(jMenuWindows, menuItem, startIndex);
+      menuItem.setActionCommand(Integer.toString(frame.hashCode()));
+      menuItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent evt) {
+          Container frame = null;
+          Iterator iter = getWindowList();
+          while (iter.hasNext()) {
+            frame = (Container) iter.next();
+            String hashFrame = Integer.toString(frame.hashCode());
+            if (hashFrame.equals(evt.getActionCommand())) {
+              showWindow(frame);
+              break;
+            }
+          }
+          showWindow(frame);
+        }
+      });
+    }
+  }
+  
+  /**
+   * Shows or hides this component depending on the value of parameter b.
+   * 
+   * @param b		if true, shows this component; otherwise, hides this 
+   * 			component
+   */
+  public void setVisible(boolean b) {
+    super.setVisible(b);
+    
+    if (b)
+      paint(this.getGraphics());
+  }
+  
+  /**
+   * Create the singleton instance of the Main GUI.
+   * 
+   * @param args 	commandline options
+   */
+  public static void createSingleton(String[] args) {
+    if (m_MainSingleton == null)
+      m_MainSingleton = new Main();
+    
+    // set options
+    try {
+      m_MainSingleton.setOptions(args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    // notify listeners (if any)
+    for (int i = 0; i < m_StartupListeners.size(); i++)
+      ((StartUpListener) m_StartupListeners.elementAt(i)).startUpComplete();
+  }
+
+  /**
+   * Return the singleton instance of the Main GUI.
+   *
+   * @return the singleton instance
+   */
+  public static Main getSingleton() {
+    return m_MainSingleton;
+  }
+
+  /**
+   * Add a listener to be notified when startup is complete.
+   * 
+   * @param s 		a listener to add
+   */
+  public static void addStartupListener(StartUpListener s) {
+    m_StartupListeners.add(s);
+  }
+
+  /**
+   * Gets an enumeration describing the available options.
+   *
+   * @return 		an enumeration of all the available options.
+   */
+  public Enumeration listOptions(){
+    Vector        	result;
+    String		desc;
+    SelectedTag		tag;
+    int			i;
+
+    result = new Vector();
+
+    desc  = "";
+    for (i = 0; i < TAGS_GUI.length; i++) {
+      tag = new SelectedTag(TAGS_GUI[i].getID(), TAGS_GUI);
+      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
+      	       + " = " + tag.getSelectedTag().getReadable()
+      	       + "\n";
+    }
+    result.addElement(new Option(
+	"\tDetermines the layout of the GUI:\n"
+	+ desc
+	+ "\t(default: " + new SelectedTag(GUI_MDI, TAGS_GUI) + ")",
+	"gui", 1, "-gui " + Tag.toOptionList(TAGS_GUI)));
+
+    return result.elements();
+  }
+  
+  /**
+   * returns the options of the current setup.
+   *
+   * @return		the current options
+   */
+  public String[] getOptions(){
+    Vector<String>    	result;
+
+    result = new Vector();
+
+    result.add("-gui");
+    result.add("" + getGUIType());
+
+    return result.toArray(new String[result.size()]);	  
+  }
+
+  /**
+   * Parses the options for this object. <p/>
+   *
+   <!-- options-start -->
+   * Valid options are: <p/>
+   * 
+   * <pre> -gui &lt;MDI|SDI&gt;
+   *  Determines the layout of the GUI:
+   *  MDI = MDI Layout
+   *  SDI = SDI Layout
+   *  (default: MDI)</pre>
+   * 
+   <!-- options-end -->
+   *
+   * @param options	the options to use
+   * @throws Exception	if setting of options fails
+   */
+  public void setOptions(String[] options) throws Exception {
+    String	tmpStr;
+
+    tmpStr = Utils.getOption("gui", options);
+    if (tmpStr.length() != 0)
+      setGUIType(new SelectedTag(tmpStr, TAGS_GUI));
+    else
+      setGUIType(new SelectedTag(GUI_MDI, TAGS_GUI));
+  }
+
+  /**
+   * Sets the type of GUI to use. 
+   *
+   * @param value 	.the GUI type
+   */
+  public void setGUIType(SelectedTag value) {
+    if (value.getTags() == TAGS_GUI) {
+      m_GUIType = value.getSelectedTag().getID();
+      initGUI();
+    }
+  }
+
+  /**
+   * Gets the currently set type of GUI to display. 
+   * 
+   * @return 		the current GUI Type.
+   */
+  public SelectedTag getGUIType() {
+    return new SelectedTag(m_GUIType, TAGS_GUI);
+  }
+  
+  /**
+   * starts the application.
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String[] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment the following line to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      // help?
+      if (Utils.getFlag('h', args)) {
+	System.out.println();
+	System.out.println("Help requested.");
+	System.out.println();
+	System.out.println("General options:");
+	System.out.println();
+	System.out.println("-h");
+	System.out.println("\tprints this help screen");
+	System.out.println();
+
+	Enumeration enu = new Main().listOptions();
+	while (enu.hasMoreElements()) {
+	  Option option = (Option) enu.nextElement();
+	  System.out.println(option.synopsis());
+	  System.out.println(option.description());
+	}
+
+	System.out.println();
+	System.exit(0);
+      }
+      
+      // setup splash screen
+      Main.addStartupListener(new weka.gui.beans.StartUpListener() {
+        public void startUpComplete() {
+          m_MainCommandline = Main.getSingleton();
+          m_MainCommandline.setVisible(true);
+        }
+      });
+      Main.addStartupListener(new StartUpListener() {
+        public void startUpComplete() {
+          SplashWindow.disposeSplash();
+        }
+      });
+      SplashWindow.splash(ClassLoader.getSystemResource("weka/gui/images/weka_splash.gif"));
+
+      // start GUI
+      final String[] options = (String[]) args.clone();
+      Thread nt = new Thread() {
+	public void run() {
+	  weka.gui.SplashWindow.invokeMethod(
+	      Main.class.getName(), "createSingleton", options);
+	}
+      };
+      nt.start();
+      
+      Thread memMonitor = new Thread() {
+	public void run() {
+	  while(true) {
+	    try {
+	      Thread.sleep(4000);
+	      System.gc();
+	      
+	      if (m_Memory.isOutOfMemory()) {
+		// clean up
+		m_MainCommandline = null;
+		System.gc();
+		
+		// stop threads
+		m_Memory.stopThreads();
+		
+		// display error
+		System.err.println("\ndisplayed message:");
+		m_Memory.showOutOfMemory();
+		System.err.println("\nexiting");
+		System.exit(-1);
+	      }
+	      
+	    } catch(InterruptedException ex) { ex.printStackTrace(); }
+	  }
+	}
+      };
+      
+      memMonitor.setPriority(Thread.MAX_PRIORITY);
+      memMonitor.start();
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/MainMenuExtension.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/MainMenuExtension.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/MainMenuExtension.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MainMenuExtension.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import java.awt.Component;
+import java.awt.event.ActionListener;
+
+import javax.swing.JFrame;
+
+/**
+ * Classes implementing this interface will be displayed in the "Extensions"
+ * menu in the main GUI of Weka.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public interface MainMenuExtension {
+  
+  /**
+   * Returns the name of the submenu. If there is no submenu necessary then 
+   * the return value is null.
+   * 
+   * @return		the title of the submenu or null if no submenu
+   */
+  public String getSubmenuTitle();
+  
+  /**
+   * Returns the name of the menu item.
+   * 
+   * @return		the name of the menu item.
+   */
+  public String getMenuTitle();
+  
+  /**
+   * If the extension has a custom ActionListener for the menu item, then it
+   * must be returned here. Having a custom <code>ActionListener</code> also 
+   * means that the component handles any frame by itself.
+   * 
+   * @param owner 	the owner of potential dialogs
+   * @return		a custom ActionListener, can be null
+   * @see		#fillFrame(Component)
+   */
+  public ActionListener getActionListener(JFrame owner);
+  
+  /**
+   * Fills the frame with life, like adding components, window listeners,
+   * setting size, location, etc. The frame object can be either derived from 
+   * <code>JFrame</code> or from <code>JInternalFrame</code>. This method is 
+   * only called in case <code>getActionListener()</code> returns null.
+   * 
+   * @param frame	the frame object to embed components, etc.
+   * @see		#getActionListener(JFrame)
+   * @see		javax.swing.JFrame
+   * @see		javax.swing.JInternalFrame
+   */
+  public void fillFrame(Component frame);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/MemoryUsage.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/MemoryUsage.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/MemoryUsage.props	(revision 29)
@@ -0,0 +1,36 @@
+# Properties file for customizing the memory usage panel.
+#
+# author:  FracPete (fracpete at waikato dot ac dot nz)
+# version: $Revision: 1.1 $
+
+# The dimensions of the dialog, width and height.
+Width=400
+# Uncomment the following property if you don't want the height to be based on 
+# the height of the "garbage collector" button.
+#Height=20
+
+# The position of the frame. If at least one equals "-1", then the position 
+# will be ignored.
+Left=-1
+Top=-1
+
+# The background color for the graph.
+BackgroundColor=white
+
+# The refresh interval in milliseconds
+Interval=1000
+
+# The percentage thresholds at which to change color. comma-separated list.
+# Percentages listed here must have a corresponding color entry, i.e., 
+# if "70" is in the list, then an entry like "70=somecolor" must be listed in 
+# the properties file as well.
+Percentages=70,80,90
+
+# The default color, i.e., the color for memory usage below the smallest 
+# threshold listed in "Percentages".
+DefaultColor=green
+
+# The colors for the thresholds (can use R,G,B format)
+70=yellow
+80=orange
+90=red
Index: branches/MetisMQI/src/main/java/weka/gui/MemoryUsagePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/MemoryUsagePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/MemoryUsagePanel.java	(revision 29)
@@ -0,0 +1,428 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MemoryUsagePanel.java
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import weka.core.Memory;
+import weka.core.Utils;
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+/**
+ * A panel for displaying the memory usage.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class MemoryUsagePanel
+  extends JPanel {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4812319791687471721L;
+
+  /**
+   * Specialized thread for monitoring the memory usage.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 1.1 $
+   */
+  protected class MemoryMonitor
+    extends Thread {
+    
+    /** the refresh interval in msecs. */
+    protected int m_Interval;
+    
+    /** whether the thread is still running. */
+    protected boolean m_Monitoring;
+    
+    /**
+     * default constructor.
+     */
+    public MemoryMonitor() {
+      super();
+
+      setInterval(1000);  // TODO: via props file
+    }
+    
+    /**
+     * Returns the refresh interval in msecs.
+     * 
+     * @return		returns the refresh interval
+     */
+    public int getInterval() {
+      return m_Interval;
+    }
+    
+    /**
+     * Sets the refresh interval in msecs.
+     * 
+     * @param value	the refresh interval
+     */
+    public void setInterval(int value) {
+      m_Interval = value;
+    }
+    
+    /**
+     * Returns whether the thread is still running.
+     * 
+     * @return		true if the thread is still running
+     */
+    public boolean isMonitoring() {
+      return m_Monitoring;
+    }
+    
+    /**
+     * stops the monitoring thread.
+     */
+    public void stopMonitoring() {
+      m_Monitoring = false;
+    }
+
+    /**
+     * The run method.
+     */
+    public void run() {
+      m_Monitoring = true;
+      
+      while (m_Monitoring) {
+	try {
+	  Thread.sleep(m_Interval);
+	  
+	  // update GUI
+	  if (m_Monitoring) {
+	    Runnable doUpdate = new Runnable() {
+	      public void run() {
+		update();
+	      }
+	    };
+	    SwingUtilities.invokeLater(doUpdate);
+	  }
+	} 
+	catch(InterruptedException ex) { 
+	  ex.printStackTrace();
+	}
+      }
+    }
+    
+    /**
+     * Updates the GUI.
+     */
+    protected void update() {
+      double		perc;
+      Dimension		size;
+
+      // current usage
+      perc = (double) m_Memory.getCurrent() / (double) m_Memory.getMax();
+      perc = Math.round(perc * 1000) / 10;
+      
+      // tool tip
+      setToolTipText("" + perc + "% used");
+      
+      // update history
+      m_History.insertElementAt(perc, 0);
+      size = getSize();
+      while (m_History.size() > size.getWidth())
+	m_History.remove(m_History.size() - 1);
+      
+      // display history
+      repaint();
+    }
+  }
+  
+  /** The name of the properties file. */
+  protected static String PROPERTY_FILE = "weka/gui/MemoryUsage.props";
+    
+  /** Contains the properties. */
+  protected static Properties PROPERTIES;
+  
+  /** the memory usage over time. */
+  protected Vector<Double> m_History;
+
+  /** for monitoring the memory usage. */
+  protected Memory m_Memory;
+
+  /** the thread for monitoring the memory usage. */
+  protected MemoryMonitor m_Monitor;
+  
+  /** the button for running the garbage collector. */
+  protected JButton m_ButtonGC;
+
+  /** the threshold percentages to change color. */
+  protected Vector<Double> m_Percentages;
+  
+  /** the corresponding colors for the thresholds. */
+  protected Hashtable<Double,Color> m_Colors;
+  
+  /** the default color. */
+  protected Color m_DefaultColor;
+  
+  /** the background color. */
+  protected Color m_BackgroundColor;
+
+  /** the position for the dialog. */
+  protected Point m_FrameLocation;
+  
+  /** 
+   * Loads the configuration property file (USE_DYNAMIC is FALSE) or determines
+   * the classes dynamically (USE_DYNAMIC is TRUE)
+   * @see #USE_DYNAMIC
+   * @see GenericPropertiesCreator
+   */
+  static {
+    // Allow a properties file in the current directory to override
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+      Enumeration keys = PROPERTIES.propertyNames();
+      if (!keys.hasMoreElements())
+	throw new Exception("Failed to read a property file for the "
+	    +"memory usage panel");
+    }
+    catch (Exception ex) {
+      JOptionPane.showMessageDialog(
+	  null,
+	  "Could not read a configuration file for the memory usage\n"
+	  +"panel. An example file is included with the Weka distribution.\n"
+	  +"This file should be named \"" + PROPERTY_FILE + "\" and\n"
+	  +"should be placed either in your user home (which is set\n"
+	  + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+	  + "or the directory that java was started from\n",
+	  "MemoryUsagePanel",
+	  JOptionPane.ERROR_MESSAGE);
+    }
+  }
+  
+  /**
+   * default constructor.
+   */
+  public MemoryUsagePanel() {
+    super();
+
+    // initializes members
+    m_Memory      = new Memory();
+    m_History     = new Vector<Double>();
+    m_Percentages = new Vector<Double>();
+    m_Colors      = new Hashtable<Double,Color>();
+
+    // colors and percentages
+    m_BackgroundColor = parseColor("BackgroundColor", Color.WHITE);
+    m_DefaultColor    = parseColor("DefaultColor", Color.GREEN);
+    String[] percs    = PROPERTIES.getProperty("Percentages", "70,80,90").split(",");
+    for (int i = 0; i < percs.length; i++) {
+      // do we have a color associated with percentage?
+      if (PROPERTIES.getProperty(percs[i]) != null) {
+	double perc;
+	Color color;
+	
+	// try parsing the number
+	try {
+	  perc = Double.parseDouble(percs[i]);
+	}
+	catch (Exception e) {
+	  System.err.println(
+	      "MemoryUsagePanel: cannot parse percentage '" 
+	      + percs[i] + "' - ignored!");
+	  continue;
+	}
+
+	// try parsing the color
+	color = parseColor(percs[i], null);
+	if (color == null)
+	  continue;
+	
+	// store color and percentage
+	m_Percentages.add(perc);
+	m_Colors.put(perc, color);
+      }
+      else {
+	System.err.println(
+	    "MemoryUsagePanel: cannot find color for percentage '" 
+	    + percs[i] + "' - ignored!");
+      }
+    }
+    Collections.sort(m_Percentages);
+    
+    // layout
+    setLayout(new BorderLayout());
+
+    JPanel panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.EAST);
+    
+    m_ButtonGC = new JButton("GC");
+    m_ButtonGC.setToolTipText("Runs the garbage collector.");
+    m_ButtonGC.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+	System.gc();
+      }
+    });
+    panel.add(m_ButtonGC, BorderLayout.NORTH);
+
+    // dimensions
+    int height;
+    int width;
+    try {
+      height = Integer.parseInt(PROPERTIES.getProperty("Height", "" + (int) m_ButtonGC.getPreferredSize().getHeight()));
+      width  = Integer.parseInt(PROPERTIES.getProperty("Width", "400"));
+    }
+    catch (Exception e) {
+      System.err.println("MemoryUsagePanel: Problem parsing the dimensions - " + e);
+      height = (int) m_ButtonGC.getPreferredSize().getHeight();
+      width = 400;
+    }
+    setPreferredSize(new Dimension(width, height));
+
+    // position
+    int top;
+    int left;
+    try {
+      top  = Integer.parseInt(PROPERTIES.getProperty("Top", "0"));
+      left = Integer.parseInt(PROPERTIES.getProperty("Left", "0"));
+    }
+    catch (Exception e) {
+      System.err.println("MemoryUsagePanel: Problem parsing the position - " + e);
+      top  = 0;
+      left = 0;
+    }
+    m_FrameLocation = new Point(left, top);
+    
+    // monitoring thread
+    int interval;
+    try {
+      interval = Integer.parseInt(PROPERTIES.getProperty("Interval", "1000"));
+    }
+    catch (Exception e) {
+      System.err.println("MemoryUsagePanel: Problem parsing the refresh interval - " + e);
+      interval = 1000;
+    }
+    m_Monitor = new MemoryMonitor();
+    m_Monitor.setInterval(interval);
+    m_Monitor.setPriority(Thread.MAX_PRIORITY);
+    m_Monitor.start();
+  }
+
+  /**
+   * parses the color and returns the corresponding Color object.
+   * 
+   * @param prop	the color property to read and parse
+   * @param defValue	the default color
+   * @return		the parsed color or the default color of the 
+   */
+  protected Color parseColor(String prop, Color defValue) {
+    Color	result;
+    Color	color;
+    String 	colorStr;
+    
+    result = defValue;
+    
+    try {
+      colorStr = PROPERTIES.getProperty(prop);
+      color    = VisualizeUtils.processColour(colorStr, result);
+      if (color == null)
+	throw new Exception(colorStr);
+      result = color;
+    }
+    catch (Exception e) {
+      System.err.println(
+	  "MemoryUsagePanel: cannot parse color '" 
+	  + e.getMessage() + "' - ignored!");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns whether the thread is still running.
+   * 
+   * @return		true if the thread is still running
+   */
+  public boolean isMonitoring() {
+    return m_Monitor.isMonitoring();
+  }
+  
+  /**
+   * stops the monitoring thread.
+   */
+  public void stopMonitoring() {
+    m_Monitor.stopMonitoring();
+  }
+  
+  /**
+   * Returns the default position for the dialog.
+   * 
+   * @return		the default position
+   */
+  public Point getFrameLocation() {
+    return m_FrameLocation;
+  }
+  
+  /**
+   * draws the background image.
+   * 
+   * @param g		the graphics context
+   */
+  public void paintComponent(Graphics g) {
+    int		i;
+    int		n;
+    int		len;
+    double	scale;
+    double	perc;
+    Color	color;
+    
+    super.paintComponent(g);
+    
+    g.setColor(m_BackgroundColor);
+    g.fillRect(0, 0, getWidth(), getHeight());
+    scale = (double) getHeight() / 100.0;
+    for (i = 0; i < m_History.size(); i++) {
+      perc = m_History.get(i);
+      
+      // determine color
+      color = m_DefaultColor;
+      for (n = m_Percentages.size() - 1; n >= 0; n--) {
+	if (perc >= m_Percentages.get(n)) {
+	  color = m_Colors.get(m_Percentages.get(n));
+	  break;
+	}
+      }
+      
+      // paint line
+      g.setColor(color);
+      len = (int) Math.round(perc * scale);
+      g.drawLine(i, getHeight() - 1, i, getHeight() - len);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/PropertyDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertyDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertyDialog.java	(revision 29)
@@ -0,0 +1,238 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyDialog.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.Frame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyEditor;
+
+import javax.swing.JDialog;
+import javax.swing.JInternalFrame;
+
+/** 
+ * Support for PropertyEditors with custom editors: puts the editor into
+ * a separate frame.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5144 $
+ */
+public class PropertyDialog
+  extends JDialog {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -2314850859392433539L;
+
+  /** The property editor. */
+  private PropertyEditor m_Editor;
+
+  /** The custom editor component. */
+  private Component m_EditorComponent;
+  
+  /**
+   * Creates the editor frame - only kept for backward-compatibility.
+   *
+   * @param pe 		the PropertyEditor
+   * @param x 		initial x coord for the frame
+   * @param y 		initial y coord for the frame
+   * @deprecated 	instead of this constructor, one should use the constructors
+   * 			with an explicit owner (either derived from 
+   * 			<code>java.awt.Dialog</code> or from 
+   * 			<code>java.awt.Frame</code>) or, if none available,
+   * 			using <code>(Frame) null</code> as owner.
+   */
+  public PropertyDialog(PropertyEditor pe, int x, int y) {
+    this((Frame) null, pe, x, y);
+    setVisible(true);
+  }
+  
+  /**
+   * Creates the (screen-centered) editor dialog. The dialog is automatically
+   * modal in case the owner is non-null.
+   *
+   * @param owner	the dialog that opens this dialog
+   * @param pe 		the PropertyEditor
+   */
+  public PropertyDialog(Dialog owner, PropertyEditor pe) {
+    this(owner, pe, -1, -1);
+  }
+  
+  /**
+   * Creates the editor dialog at the given position. The dialog is automatically
+   * modal in case the owner is non-null.
+   *
+   * @param owner	the dialog that opens this dialog
+   * @param pe 		the PropertyEditor
+   * @param x 		initial x coord for the dialog
+   * @param y 		initial y coord for the dialog
+   */
+  public PropertyDialog(Dialog owner, PropertyEditor pe, int x, int y) {
+    super(owner, pe.getClass().getName(), true);
+    initialize(pe, x, y);
+  }
+  
+  /**
+   * Creates the (screen-centered) editor dialog. The dialog is automatically
+   * modal in case the owner is non-null.
+   *
+   * @param owner	the frame that opens this dialog
+   * @param pe 		the PropertyEditor
+   */
+  public PropertyDialog(Frame owner, PropertyEditor pe) {
+    this(owner, pe, -1, -1);
+  }
+  
+  /**
+   * Creates the editor dialog at the given position. The dialog is automatically
+   * modal in case the owner is non-null.
+   *
+   * @param owner	the frame that opens this dialog
+   * @param pe 		the PropertyEditor
+   * @param x 		initial x coord for the dialog
+   * @param y 		initial y coord for the dialog
+   */
+  public PropertyDialog(Frame owner, PropertyEditor pe, int x, int y) {
+    super(owner, pe.getClass().getName(), true);
+    
+    initialize(pe, x, y);
+  }
+  
+  /**
+   * Initializes the dialog.
+   *
+   * @param pe 		the PropertyEditor
+   * @param x 		initial x coord for the dialog
+   * @param y 		initial y coord for the dialog
+   */
+  protected void initialize(PropertyEditor pe, int x, int y) {
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+	e.getWindow().dispose();
+      }
+    });
+    getContentPane().setLayout(new BorderLayout());
+
+    m_Editor = pe;
+    m_EditorComponent = pe.getCustomEditor();
+    getContentPane().add(m_EditorComponent, BorderLayout.CENTER);
+
+    pack();
+    
+    if ((x == -1) && (y == -1))
+      setLocationRelativeTo(null);
+    else
+      setLocation(x, y);
+  }
+
+  /**
+   * Gets the current property editor.
+   *
+   * @return a value of type 'PropertyEditor'
+   */
+  public PropertyEditor getEditor() {
+    return m_Editor;
+  }
+
+  /**
+   * Tries to determine the frame this panel is part of.
+   * 
+   * @param c		the container to start with
+   * @return		the parent frame if one exists or null if not
+   */
+  public static Frame getParentFrame(Container c) {
+    Frame	result;
+    Container	parent;
+    
+    result = null;
+    
+    parent = c;
+    while (parent != null) {
+      if (parent instanceof Frame) {
+	result = (Frame) parent;
+	break;
+      }
+      else {
+	parent = parent.getParent();
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tries to determine the internal frame this panel is part of.
+   * 
+   * @param c		the container to start with
+   * @return		the parent internal frame if one exists or null if not
+   */
+  public static JInternalFrame getParentInternalFrame(Container c) {
+    JInternalFrame	result;
+    Container		parent;
+    
+    result = null;
+    
+    parent = c;
+    while (parent != null) {
+      if (parent instanceof JInternalFrame) {
+	result = (JInternalFrame) parent;
+	break;
+      }
+      else {
+	parent = parent.getParent();
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tries to determine the dialog this panel is part of.
+   * 
+   * @param c		the container to start with
+   * @return		the parent dialog if one exists or null if not
+   */
+  public static Dialog getParentDialog(Container c) {
+    Dialog	result;
+    Container	parent;
+    
+    result = null;
+    
+    parent = c;
+    while (parent != null) {
+      if (parent instanceof Dialog) {
+	result = (Dialog) parent;
+	break;
+      }
+      else {
+	parent = parent.getParent();
+      }
+    }
+    
+    return result;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/PropertyPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertyPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertyPanel.java	(revision 29)
@@ -0,0 +1,265 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyEditor;
+
+import javax.swing.BorderFactory;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+
+/** 
+ * Support for drawing a property value in a component.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5841 $
+ */
+public class PropertyPanel 
+  extends JPanel {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5370025273466728904L;
+
+  /** The property editor */
+  private PropertyEditor m_Editor;
+
+  /** The currently displayed property dialog, if any */
+  private PropertyDialog m_PD;
+
+  /** Whether the editor has provided its own panel */
+  private boolean m_HasCustomPanel = false;
+  
+  /** The custom panel (if any) */
+  private JPanel m_CustomPanel;
+  
+  /**
+   * Create the panel with the supplied property editor.
+   *
+   * @param pe the PropertyEditor
+   */
+  public PropertyPanel(PropertyEditor pe) {
+
+    this(pe, false);
+  }
+
+  /**
+   * Create the panel with the supplied property editor,
+   * optionally ignoring any custom panel the editor can provide.
+   *
+   * @param pe the PropertyEditor
+   * @param ignoreCustomPanel whether to make use of any available custom panel
+   */
+  public PropertyPanel(PropertyEditor pe, boolean ignoreCustomPanel) {
+
+    m_Editor = pe;
+    
+    if (!ignoreCustomPanel && m_Editor instanceof CustomPanelSupplier) {
+      setLayout(new BorderLayout());
+      m_CustomPanel = ((CustomPanelSupplier)m_Editor).getCustomPanel();
+      add(m_CustomPanel, BorderLayout.CENTER);
+      m_HasCustomPanel = true;
+    } else {
+      createDefaultPanel();
+    }
+  }
+
+  /**
+   * Creates the default style of panel for editors that do not
+   * supply their own.
+   */
+  protected void createDefaultPanel() {
+
+    setBorder(BorderFactory.createEtchedBorder());
+    setToolTipText("Left-click to edit properties for this object, right-click/Alt+Shift+left-click for menu");
+    setOpaque(true);
+    final Component comp = this;
+    addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent evt) {
+        if (evt.getClickCount() == 1) {
+          if (    (evt.getButton() == MouseEvent.BUTTON1) && !evt.isAltDown() && !evt.isShiftDown() ) {
+            showPropertyDialog();
+          }
+          else if (    (evt.getButton() == MouseEvent.BUTTON3) 
+              	    || ((evt.getButton() == MouseEvent.BUTTON1) && evt.isAltDown() && evt.isShiftDown()) ) {
+            JPopupMenu menu = new JPopupMenu();
+            JMenuItem item;
+
+            if (m_Editor.getValue() != null) {
+              item = new JMenuItem("Show properties...");
+              item.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                  showPropertyDialog();
+                }
+              });
+              menu.add(item);
+
+              item = new JMenuItem("Copy configuration to clipboard");
+              item.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                  String str = m_Editor.getValue().getClass().getName();
+                  if (m_Editor.getValue() instanceof OptionHandler)
+                    str += " " + Utils.joinOptions(((OptionHandler) m_Editor.getValue()).getOptions());
+                  StringSelection selection = new StringSelection(str.trim());
+                  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                  clipboard.setContents(selection, selection);
+                }
+              });
+              menu.add(item);
+            }
+            
+            item = new JMenuItem("Enter configuration...");
+            item.addActionListener(new ActionListener() {
+              public void actionPerformed(ActionEvent e) {
+        	String str = JOptionPane.showInputDialog(
+        	                 comp, 
+        	                 "Configuration (<classname> [<options>])");
+        	if (str != null) {
+        	  try {
+        	    String[] options = Utils.splitOptions(str);
+        	    String classname = options[0];
+        	    options[0] = "";
+        	    m_Editor.setValue(
+        		Utils.forName(
+        		    Object.class, classname, options));
+        	  }
+        	  catch (Exception ex) {
+        	    ex.printStackTrace();
+        	    JOptionPane.showMessageDialog(
+        		comp, 
+        		"Error parsing commandline:\n" + ex, 
+        		"Error...",
+        		JOptionPane.ERROR_MESSAGE);
+        	  }
+        	}
+              }
+            });
+            menu.add(item);
+            
+            menu.show(comp, evt.getX(), evt.getY());
+          }
+        }
+      }
+    });
+    Dimension newPref = getPreferredSize();
+    newPref.height = getFontMetrics(getFont()).getHeight() * 5 / 4;
+    newPref.width = newPref.height * 5;
+    setPreferredSize(newPref);
+
+    m_Editor.addPropertyChangeListener(new PropertyChangeListener () {
+	public void propertyChange(PropertyChangeEvent evt) {
+	  repaint();
+	}
+      });
+  }
+
+  /**
+   * Displays the property edit dialog for the panel.
+   */
+  public void showPropertyDialog() {
+
+    if (m_Editor.getValue() != null) {
+      if (m_PD == null) {
+	int x = getLocationOnScreen().x;
+	int y = getLocationOnScreen().y;
+	if (PropertyDialog.getParentDialog(this) != null)
+	  m_PD = new PropertyDialog(PropertyDialog.getParentDialog(this), m_Editor, x, y);
+	else
+	  m_PD = new PropertyDialog(PropertyDialog.getParentFrame(this), m_Editor, x, y);
+	m_PD.setVisible(true);
+      } else {
+	m_PD.setVisible(true);
+      }
+      // make sure that m_Backup is correctly initialized!
+      m_Editor.setValue(m_Editor.getValue());
+    }
+  }
+
+  /**
+   * Cleans up when the panel is destroyed.
+   */
+  public void removeNotify() {
+
+    super.removeNotify();
+    if (m_PD != null) {
+      m_PD.dispose();
+      m_PD = null;
+    }
+  }
+  
+  /**
+   * Passes on enabled/disabled status to the custom
+   * panel (if one is set).
+   * 
+   * @param enabled true if this panel (and the custom panel is enabled)
+   */
+  public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    if (m_HasCustomPanel) {
+      m_CustomPanel.setEnabled(enabled);
+    }
+    
+  }
+
+  /**
+   * Paints the component, using the property editor's paint method.
+   *
+   * @param g the current graphics context
+   */
+  public void paintComponent(Graphics g) {
+
+    if (!m_HasCustomPanel) {
+      Insets i = getInsets();
+      Rectangle box = new Rectangle(i.left, i.top,
+				    getSize().width - i.left - i.right - 1,
+				    getSize().height - i.top - i.bottom - 1);
+      
+      g.clearRect(i.left, i.top,
+		  getSize().width - i.right - i.left,
+		  getSize().height - i.bottom - i.top);
+      m_Editor.paintValue(g, box);
+    }
+  }
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/PropertySelectorDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertySelectorDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertySelectorDialog.java	(revision 29)
@@ -0,0 +1,275 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertySelectorDialog.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.experiment.PropertyNode;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+/** 
+ * Allows the user to select any (supported) property of an object, including
+ * properties that any of it's property values may have.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+public class PropertySelectorDialog
+  extends JDialog {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3155058124137930518L;
+  
+  /** Click to choose the currently selected property */
+  protected JButton m_SelectBut = new JButton("Select");
+
+  /** Click to cancel the property selection */
+  protected JButton m_CancelBut = new JButton("Cancel");
+
+  /** The root of the property tree */
+  protected DefaultMutableTreeNode m_Root;
+
+  /** The object at the root of the tree */
+  protected Object m_RootObject;
+
+  /** Whether the selection was made or cancelled */
+  protected int m_Result;
+
+  /** Stores the path to the selected property */
+  protected Object [] m_ResultPath;
+
+  /** The component displaying the property tree */
+  protected JTree m_Tree;
+
+  /** Signifies an OK property selection */
+  public static final int APPROVE_OPTION = 0;
+
+  /** Signifies a cancelled property selection */
+  public static final int CANCEL_OPTION = 1;
+  
+  /**
+   * Create the property selection dialog.
+   *
+   * @param parentFrame the parent frame of the dialog
+   * @param rootObject the object containing properties to select from
+   */
+  public PropertySelectorDialog(Frame parentFrame, Object rootObject) {
+    
+    super(parentFrame, "Select a property", true);
+    m_CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_Result = CANCEL_OPTION;
+	setVisible(false);
+      }
+    });
+    m_SelectBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	// value = path from root to selected;
+	TreePath tPath = m_Tree.getSelectionPath();
+	if (tPath == null) {
+	  m_Result = CANCEL_OPTION;
+	} else {
+	  m_ResultPath = tPath.getPath();
+	  if ((m_ResultPath == null) || (m_ResultPath.length < 2)) {
+	    m_Result = CANCEL_OPTION;
+	  } else {
+	    m_Result = APPROVE_OPTION;
+	  }
+	} 
+	setVisible(false);
+      }
+    });
+    m_RootObject = rootObject;
+    m_Root = new DefaultMutableTreeNode(
+	     new PropertyNode(m_RootObject));
+    createNodes(m_Root);
+    
+    Container c = getContentPane();
+    c.setLayout(new BorderLayout());
+    //    setBorder(BorderFactory.createTitledBorder("Select a property"));
+    Box b1 = new Box(BoxLayout.X_AXIS);
+    b1.add(m_SelectBut);
+    b1.add(Box.createHorizontalStrut(10));
+    b1.add(m_CancelBut);
+    c.add(b1, BorderLayout.SOUTH);
+    m_Tree = new JTree(m_Root);
+    m_Tree.getSelectionModel()
+      .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+    c.add(new JScrollPane(m_Tree), BorderLayout.CENTER);
+    pack();
+  }
+
+  /**
+   * Pops up the modal dialog and waits for cancel or a selection.
+   *
+   * @return either APPROVE_OPTION, or CANCEL_OPTION
+   */
+  public int showDialog() {
+
+    m_Result = CANCEL_OPTION;
+    setVisible(true);
+    return m_Result;
+  }
+
+  /**
+   * Gets the path of property nodes to the selected property.
+   *
+   * @return an array of PropertyNodes
+   */
+  public PropertyNode [] getPath() {
+
+    PropertyNode [] result = new PropertyNode [m_ResultPath.length - 1];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = (PropertyNode) ((DefaultMutableTreeNode) m_ResultPath[i + 1])
+	.getUserObject();
+    }
+    return result;
+  }
+
+  /**
+   * Creates the property tree below the current node.
+   *
+   * @param localNode a value of type 'DefaultMutableTreeNode'
+   */
+  protected void createNodes(DefaultMutableTreeNode localNode) {
+
+    PropertyNode pNode = (PropertyNode)localNode.getUserObject();
+    Object localObject = pNode.value;
+    // Find all the properties of the object in the root node
+    PropertyDescriptor localProperties[];
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(localObject.getClass());
+      localProperties = bi.getPropertyDescriptors();
+    } catch (IntrospectionException ex) {
+      System.err.println("PropertySelectorDialog: Couldn't introspect");
+      return;
+    }
+
+    // Put their values into child nodes.
+    for (int i = 0; i < localProperties.length; i++) {
+      // Don't display hidden or expert properties.
+      if (localProperties[i].isHidden() || localProperties[i].isExpert()) {
+	continue;
+      }
+      String name = localProperties[i].getDisplayName();
+      Class type = localProperties[i].getPropertyType();
+      Method getter = localProperties[i].getReadMethod();
+      Method setter = localProperties[i].getWriteMethod();
+      Object value = null;
+      // Only display read/write properties.
+      if (getter == null || setter == null) {
+	continue;
+      }
+      try {
+	Object args[] = { };
+	value = getter.invoke(localObject, args);
+	PropertyEditor editor = null;
+	Class pec = localProperties[i].getPropertyEditorClass();
+	if (pec != null) {
+	  try {
+	    editor = (PropertyEditor)pec.newInstance();
+	  } catch (Exception ex) {
+	  }
+	}
+	if (editor == null) {
+	  editor = PropertyEditorManager.findEditor(type);
+	}
+	if ((editor == null) || (value == null)) {
+	  continue;
+	}
+      } catch (InvocationTargetException ex) {
+	System.err.println("Skipping property " + name
+			   + " ; exception on target: "
+			   + ex.getTargetException());
+	ex.getTargetException().printStackTrace();
+	continue;
+      } catch (Exception ex) {
+	System.err.println("Skipping property " + name
+			   + " ; exception: " + ex);
+	ex.printStackTrace();
+	continue;
+      }
+      // Make a child node
+      DefaultMutableTreeNode child = new DefaultMutableTreeNode(
+				     new PropertyNode(value,
+						      localProperties[i],
+						      localObject.getClass()));
+      localNode.add(child);
+      createNodes(child);
+    }
+  }
+
+  
+  /**
+   * Tests out the property selector from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      GenericObjectEditor.registerEditors();
+
+      Object rp
+	= new weka.experiment.AveragingResultProducer();
+      final PropertySelectorDialog jd = new PropertySelectorDialog(null, rp);
+      int result = jd.showDialog();
+      if (result == PropertySelectorDialog.APPROVE_OPTION) {
+	System.err.println("Property Selected");
+	PropertyNode [] path = jd.getPath();
+	for (int i = 0; i < path.length; i++) {
+	  PropertyNode pn = path[i];
+	  System.err.println("" + (i + 1) + "  " + pn.toString()
+			     + " " + pn.value.toString());
+	}
+      } else {
+	System.err.println("Cancelled");
+      }
+      System.exit(0);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/PropertySheetPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertySheetPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertySheetPanel.java	(revision 29)
@@ -0,0 +1,847 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertySheet.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.MultiInstanceCapabilitiesHandler;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.BeanInfo;
+import java.beans.Beans;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyDescriptor;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.beans.PropertyVetoException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+
+
+/** 
+ * Displays a property sheet where (supported) properties of the target
+ * object may be edited.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6097 $
+ */
+public class PropertySheetPanel extends JPanel
+  implements PropertyChangeListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -8939835593429918345L;
+
+  /**
+   * A specialized dialog for displaying the capabilities.
+   */
+  protected class CapabilitiesHelpDialog
+    extends JDialog
+    implements PropertyChangeListener {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -1404770987103289858L;
+    
+    /** the dialog itself. */
+    private CapabilitiesHelpDialog m_Self;
+    
+    /**
+     * default constructor.
+     * 
+     * @param owner	the owning frame
+     */
+    public CapabilitiesHelpDialog(Frame owner) {
+      super(owner);
+      
+      initialize();
+    }
+    
+    /**
+     * default constructor.
+     * 
+     * @param owner	the owning dialog
+     */
+    public CapabilitiesHelpDialog(Dialog owner) {
+      super(owner);
+      
+      initialize();
+    }
+
+    /**
+     * Initializes the dialog.
+     */
+    protected void initialize() {
+      setTitle("Information about Capabilities");
+
+      m_Self = this;
+      
+      m_CapabilitiesText = new JTextArea();
+      m_CapabilitiesText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+      m_CapabilitiesText.setLineWrap(true);
+      m_CapabilitiesText.setWrapStyleWord(true);
+      m_CapabilitiesText.setEditable(false);
+      updateText();
+      addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  m_Self.dispose();
+	  if (m_CapabilitiesDialog == m_Self) {
+	    m_CapabilitiesBut.setEnabled(true);
+	  }
+	}
+      });
+      getContentPane().setLayout(new BorderLayout());
+      getContentPane().add(new JScrollPane(m_CapabilitiesText), BorderLayout.CENTER);
+      pack();
+    }
+
+    /**
+     * returns a comma-separated list of all the capabilities.
+     * 
+     * @param c		the capabilities to get a string representation from
+     * @return		the string describing the capabilities
+     */
+    protected String listCapabilities(Capabilities c) {
+      String	result;
+      Iterator	iter;
+      
+      result = "";
+      iter   = c.capabilities();
+      while (iter.hasNext()) {
+        if (result.length() != 0)
+  	result += ", ";
+        result += iter.next().toString();
+      }
+      
+      return result;
+    }
+    
+    /**
+     * generates a string from the capapbilities, suitable to add to the help 
+     * text.
+     * 
+     * @param title	the title for the capabilities
+     * @param c		the capabilities
+     * @return		a string describing the capabilities
+     */
+    protected String addCapabilities(String title, Capabilities c) {
+      String		result;
+      String		caps;
+      
+      result = title + "\n";
+      
+      // class
+      caps = listCapabilities(c.getClassCapabilities());
+      if (caps.length() != 0) {
+	result += "Class -- ";
+	result += caps;
+	result += "\n\n";
+      }
+      
+      // attribute
+      caps = listCapabilities(c.getAttributeCapabilities());
+      if (caps.length() != 0) {
+	result += "Attributes -- ";
+	result += caps;
+	result += "\n\n";
+      }
+      
+      // other capabilities
+      caps = listCapabilities(c.getOtherCapabilities());
+      if (caps.length() != 0) {
+	result += "Other -- ";
+	result += caps;
+	result += "\n\n";
+      }
+      
+      // additional stuff
+      result += "Additional\n";
+      result += "min # of instances: " + c.getMinimumNumberInstances() + "\n";
+      result += "\n";
+      
+      return result;
+    }  
+
+    /**
+     * updates the content of the capabilities help dialog.
+     */
+    protected void updateText() {
+      StringBuffer helpText = new StringBuffer();
+      
+      if (m_Target instanceof CapabilitiesHandler)
+        helpText.append(
+  	  addCapabilities(
+  	      "CAPABILITIES", 
+  	      ((CapabilitiesHandler) m_Target).getCapabilities()));
+      
+      if (m_Target instanceof MultiInstanceCapabilitiesHandler)
+        helpText.append(
+  	  addCapabilities(
+  	      "MI CAPABILITIES", 
+  	      ((MultiInstanceCapabilitiesHandler) m_Target).getMultiInstanceCapabilities()));
+      
+      m_CapabilitiesText.setText(helpText.toString());
+      m_CapabilitiesText.setCaretPosition(0);
+    }
+    
+    /**
+     * This method gets called when a bound property is changed.
+     *  
+     * @param evt	the change event
+     */
+    public void propertyChange(PropertyChangeEvent evt) {
+      updateText();
+    }
+  }
+  
+  /** The target object being edited. */
+  private Object m_Target;
+
+  /** Holds properties of the target. */
+  private PropertyDescriptor m_Properties[];
+
+  /** Holds the methods of the target. */
+  private MethodDescriptor m_Methods[];
+
+  /** Holds property editors of the object. */
+  private PropertyEditor m_Editors[];
+
+  /** Holds current object values for each property. */
+  private Object m_Values[];
+
+  /** Stores GUI components containing each editing component. */
+  private JComponent m_Views[];
+
+  /** The labels for each property. */
+  private JLabel m_Labels[];
+
+  /** The tool tip text for each property. */
+  private String m_TipTexts[];
+
+  /** StringBuffer containing help text for the object being edited. */
+  private StringBuffer m_HelpText;
+
+  /** Help dialog. */
+  private JDialog m_HelpDialog;
+
+  /** Capabilities Help dialog. */
+  private CapabilitiesHelpDialog m_CapabilitiesDialog;
+
+  /** Button to pop up the full help text in a separate dialog. */
+  private JButton m_HelpBut;
+
+  /** Button to pop up the capabilities in a separate dialog. */
+  private JButton m_CapabilitiesBut;
+  
+  /** the TextArea of the Capabilities help dialog. */
+  private JTextArea m_CapabilitiesText;
+
+  /** A count of the number of properties we have an editor for. */
+  private int m_NumEditable = 0;
+
+  /** The panel holding global info and help, if provided by
+      the object being editied. */
+  private JPanel m_aboutPanel;
+
+  /**
+   * Creates the property sheet panel.
+   */
+  public PropertySheetPanel() {
+
+    //    setBorder(BorderFactory.createLineBorder(Color.red));
+    setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
+  }
+
+  /**
+   * Return the panel containing global info and help for
+   * the object being edited. May return null if the edited
+   * object provides no global info or tip text.
+   *
+   * @return the about panel.
+   */
+  public JPanel getAboutPanel() {
+    return m_aboutPanel;
+  }
+
+  /** A support object for handling property change listeners. */ 
+  private PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+  /**
+   * Updates the property sheet panel with a changed property and also passed
+   * the event along.
+   *
+   * @param evt a value of type 'PropertyChangeEvent'
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    wasModified(evt); // Let our panel update before guys downstream
+    support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Adds a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    support.removePropertyChangeListener(l);
+  }
+  
+  /**
+   * Sets a new target object for customisation.
+   *
+   * @param targ a value of type 'Object'
+   */
+  public synchronized void setTarget(Object targ) {
+
+    // used to offset the components for the properties of targ
+    // if there happens to be globalInfo available in targ
+    int componentOffset = 0;
+
+    // Close any child windows at this point
+    removeAll();
+
+    setLayout(new BorderLayout());
+    JPanel scrollablePanel = new JPanel();
+    JScrollPane scrollPane = new JScrollPane(scrollablePanel);
+    scrollPane.setBorder(BorderFactory.createEmptyBorder());
+    add(scrollPane, BorderLayout.CENTER);
+    
+    GridBagLayout gbLayout = new GridBagLayout();
+
+    scrollablePanel.setLayout(gbLayout);
+    setVisible(false);
+    m_NumEditable = 0;
+    m_Target = targ;
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(m_Target.getClass());
+      m_Properties = bi.getPropertyDescriptors();
+      m_Methods = bi.getMethodDescriptors();
+    } catch (IntrospectionException ex) {
+      System.err.println("PropertySheet: Couldn't introspect");
+      return;
+    }
+
+    JTextArea jt = new JTextArea();
+    m_HelpText = null;
+    // Look for a globalInfo method that returns a string
+    // describing the target
+    for (int i = 0;i < m_Methods.length; i++) {
+      String name = m_Methods[i].getDisplayName();
+      Method meth = m_Methods[i].getMethod();
+      if (name.equals("globalInfo")) {
+	if (meth.getReturnType().equals(String.class)) {
+	  try {
+	    Object args[] = { };
+	    String globalInfo = (String)(meth.invoke(m_Target, args));
+            String summary = globalInfo;
+            int ci = globalInfo.indexOf('.');
+            if (ci != -1) {
+              summary = globalInfo.substring(0, ci + 1);
+            }
+	    final String className = targ.getClass().getName();
+            m_HelpText = new StringBuffer("NAME\n");
+            m_HelpText.append(className).append("\n\n");
+            m_HelpText.append("SYNOPSIS\n").append(globalInfo).append("\n\n");
+            m_HelpBut = new JButton("More");
+	    m_HelpBut.setToolTipText("More information about "
+                                     + className);
+	    
+	    m_HelpBut.addActionListener(new ActionListener() {
+              public void actionPerformed(ActionEvent a) {
+                openHelpFrame();
+                m_HelpBut.setEnabled(false);
+              }
+            });
+
+	    if (m_Target instanceof CapabilitiesHandler) {
+	      m_CapabilitiesBut = new JButton("Capabilities");
+	      m_CapabilitiesBut.setToolTipText("The capabilities of "
+		  + className);
+	      
+	      m_CapabilitiesBut.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent a) {
+		  openCapabilitiesHelpDialog();
+		  m_CapabilitiesBut.setEnabled(false);
+		}
+	      });
+	    }
+	    else {
+	      m_CapabilitiesBut = null;
+	    }
+
+	    jt.setColumns(30);
+	    jt.setFont(new Font("SansSerif", Font.PLAIN,12));
+	    jt.setEditable(false);
+	    jt.setLineWrap(true);
+	    jt.setWrapStyleWord(true);
+	    jt.setText(summary);
+            jt.setBackground(getBackground());
+	    JPanel jp = new JPanel();
+	    jp.setBorder(BorderFactory.createCompoundBorder(
+			 BorderFactory.createTitledBorder("About"),
+			 BorderFactory.createEmptyBorder(5, 5, 5, 5)
+		 ));
+	    jp.setLayout(new BorderLayout());
+	    jp.add(jt, BorderLayout.CENTER);
+            JPanel p2 = new JPanel();
+            p2.setLayout(new BorderLayout());
+            p2.add(m_HelpBut, BorderLayout.NORTH);
+            if (m_CapabilitiesBut != null) {
+              JPanel p3 = new JPanel();
+              p3.setLayout(new BorderLayout());
+              p3.add(m_CapabilitiesBut, BorderLayout.NORTH);
+              p2.add(p3, BorderLayout.CENTER);
+            }
+            jp.add(p2, BorderLayout.EAST);
+	    GridBagConstraints gbConstraints = new GridBagConstraints();
+	    //	    gbConstraints.anchor = GridBagConstraints.EAST;
+	    gbConstraints.fill = GridBagConstraints.BOTH;
+	    //	    gbConstraints.gridy = 0;     gbConstraints.gridx = 0;
+	    gbConstraints.gridwidth = 2;
+	    gbConstraints.insets = new Insets(0,5,0,5);
+	    gbLayout.setConstraints(jp, gbConstraints);
+	    m_aboutPanel = jp;
+	    scrollablePanel.add(m_aboutPanel);
+	    componentOffset = 1;
+	    break;
+	  } catch (Exception ex) {
+	    
+	  }
+	}
+      }
+    }
+
+    m_Editors = new PropertyEditor[m_Properties.length];
+    m_Values = new Object[m_Properties.length];
+    m_Views = new JComponent[m_Properties.length];
+    m_Labels = new JLabel[m_Properties.length];
+    m_TipTexts = new String[m_Properties.length];
+    boolean firstTip = true;
+    for (int i = 0; i < m_Properties.length; i++) {
+
+      // Don't display hidden or expert properties.
+      if (m_Properties[i].isHidden() || m_Properties[i].isExpert()) {
+	continue;
+      }
+
+      String name = m_Properties[i].getDisplayName();
+      Class type = m_Properties[i].getPropertyType();
+      Method getter = m_Properties[i].getReadMethod();
+      Method setter = m_Properties[i].getWriteMethod();
+
+      // Only display read/write properties.
+      if (getter == null || setter == null) {
+	continue;
+      }
+	
+      JComponent view = null;
+
+      try {
+	Object args[] = { };
+	Object value = getter.invoke(m_Target, args);
+	m_Values[i] = value;
+
+	PropertyEditor editor = null;
+	Class pec = m_Properties[i].getPropertyEditorClass();
+	if (pec != null) {
+	  try {
+	    editor = (PropertyEditor)pec.newInstance();
+	  } catch (Exception ex) {
+	    // Drop through.
+	  }
+	}
+	if (editor == null) {
+	  editor = PropertyEditorManager.findEditor(type);
+	}
+	m_Editors[i] = editor;
+
+	// If we can't edit this component, skip it.
+	if (editor == null) {
+	  // If it's a user-defined property we give a warning.
+	  String getterClass = m_Properties[i].getReadMethod()
+	    .getDeclaringClass().getName();
+	  /*
+	  if (getterClass.indexOf("java.") != 0) {
+	    System.err.println("Warning: Can't find public property editor"
+			       + " for property \"" + name + "\" (class \""
+			       + type.getName() + "\").  Skipping.");
+	  }
+	  */
+	  continue;
+	}
+	if (editor instanceof GenericObjectEditor) {
+	  ((GenericObjectEditor) editor).setClassType(type);
+	}
+
+	// Don't try to set null values:
+	if (value == null) {
+	  // If it's a user-defined property we give a warning.
+	  String getterClass = m_Properties[i].getReadMethod()
+	    .getDeclaringClass().getName();
+	  /*
+	  if (getterClass.indexOf("java.") != 0) {
+	    System.err.println("Warning: Property \"" + name 
+			       + "\" has null initial value.  Skipping.");
+	  }
+	  */
+	  continue;
+	}
+
+	editor.setValue(value);
+
+	// now look for a TipText method for this property
+        String tipName = name + "TipText";
+	for (int j = 0; j < m_Methods.length; j++) {
+	  String mname = m_Methods[j].getDisplayName();
+	  Method meth = m_Methods[j].getMethod();
+	  if (mname.equals(tipName)) {
+	    if (meth.getReturnType().equals(String.class)) {
+	      try {
+		String tempTip = (String)(meth.invoke(m_Target, args));
+		int ci = tempTip.indexOf('.');
+		if (ci < 0) {
+		  m_TipTexts[i] = tempTip;
+		} else {
+		  m_TipTexts[i] = tempTip.substring(0, ci);
+		}
+                if (m_HelpText != null) {
+                  if (firstTip) {
+                    m_HelpText.append("OPTIONS\n");
+                    firstTip = false;
+                  }
+                  m_HelpText.append(name).append(" -- ");
+                  m_HelpText.append(tempTip).append("\n\n");
+                  //jt.setText(m_HelpText.toString());
+                }
+	      } catch (Exception ex) {
+
+	      }
+	      break;
+	    }
+	  }
+	}	  
+
+	// Now figure out how to display it...
+	if (editor.isPaintable() && editor.supportsCustomEditor()) {
+	  view = new PropertyPanel(editor);
+	} else if (editor.supportsCustomEditor() && (editor.getCustomEditor() instanceof JComponent)) {
+	  view = (JComponent) editor.getCustomEditor();
+	} else if (editor.getTags() != null) {
+	  view = new PropertyValueSelector(editor);
+	} else if (editor.getAsText() != null) {
+	  view = new PropertyText(editor);
+	} else {
+	  System.err.println("Warning: Property \"" + name 
+			     + "\" has non-displayabale editor.  Skipping.");
+	  continue;
+	}
+	
+	editor.addPropertyChangeListener(this);
+
+      } catch (InvocationTargetException ex) {
+	System.err.println("Skipping property " + name
+			   + " ; exception on target: "
+			   + ex.getTargetException());
+	ex.getTargetException().printStackTrace();
+	continue;
+      } catch (Exception ex) {
+	System.err.println("Skipping property " + name
+			   + " ; exception: " + ex);
+	ex.printStackTrace();
+	continue;
+      }
+
+      m_Labels[i] = new JLabel(name, SwingConstants.RIGHT);
+      m_Labels[i].setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 5));
+      m_Views[i] = view;
+      GridBagConstraints gbConstraints = new GridBagConstraints();
+      gbConstraints.anchor = GridBagConstraints.EAST;
+      gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+      gbConstraints.gridy = i+componentOffset;     gbConstraints.gridx = 0;
+      gbLayout.setConstraints(m_Labels[i], gbConstraints);
+      scrollablePanel.add(m_Labels[i]);
+      JPanel newPanel = new JPanel();
+      if (m_TipTexts[i] != null) {
+	m_Views[i].setToolTipText(m_TipTexts[i]);
+      }
+      newPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 10));
+      newPanel.setLayout(new BorderLayout());
+      newPanel.add(m_Views[i], BorderLayout.CENTER);
+      gbConstraints = new GridBagConstraints();
+      gbConstraints.anchor = GridBagConstraints.WEST;
+      gbConstraints.fill = GridBagConstraints.BOTH;
+      gbConstraints.gridy = i+componentOffset;     gbConstraints.gridx = 1;
+      gbConstraints.weightx = 100;
+      gbLayout.setConstraints(newPanel, gbConstraints);
+      scrollablePanel.add(newPanel);
+      m_NumEditable ++;
+    }
+
+    if (m_NumEditable == 0) {
+      JLabel empty = new JLabel("No editable properties", 
+                                SwingConstants.CENTER);
+      Dimension d = empty.getPreferredSize();
+      empty.setPreferredSize(new Dimension(d.width * 2, d.height * 2));
+      empty.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 10));
+      GridBagConstraints gbConstraints = new GridBagConstraints();
+      gbConstraints.anchor = GridBagConstraints.CENTER;
+      gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+      gbConstraints.gridy = componentOffset;     gbConstraints.gridx = 0;
+      gbLayout.setConstraints(empty, gbConstraints);
+      scrollablePanel.add(empty);
+    }
+
+    validate();
+
+    // sometimes, the calculated dimensions seem to be too small and the
+    // scrollbars show up, though there is still plenty of space on the
+    // screen. hence we increase the dimensions a bit to fix this.
+    Dimension dim = scrollablePanel.getPreferredSize();
+    dim.height += 20;
+    dim.width  += 20;
+    scrollPane.setPreferredSize(dim);
+    validate();
+
+    setVisible(true);	
+  }
+
+  /**
+   * opens the help dialog.
+   */
+  protected void openHelpFrame() {
+
+    JTextArea ta = new JTextArea();
+    ta.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ta.setLineWrap(true);
+    ta.setWrapStyleWord(true);
+    //ta.setBackground(getBackground());
+    ta.setEditable(false);
+    ta.setText(m_HelpText.toString());
+    ta.setCaretPosition(0);
+    JDialog jdtmp;
+    if (PropertyDialog.getParentDialog(this) != null)
+      jdtmp = new JDialog(PropertyDialog.getParentDialog(this), "Information");
+    else
+      jdtmp = new JDialog(PropertyDialog.getParentFrame(this), "Information");
+    final JDialog jd = jdtmp;
+    jd.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        jd.dispose();
+        if (m_HelpDialog == jd) {
+          m_HelpBut.setEnabled(true);
+        }
+      }
+    });
+    jd.getContentPane().setLayout(new BorderLayout());
+    jd.getContentPane().add(new JScrollPane(ta), BorderLayout.CENTER);
+    jd.pack();
+    jd.setSize(400, 350);
+    jd.setLocation(m_aboutPanel.getTopLevelAncestor().getLocationOnScreen().x 
+                   + m_aboutPanel.getTopLevelAncestor().getSize().width,
+                   m_aboutPanel.getTopLevelAncestor().getLocationOnScreen().y);
+    jd.setVisible(true);
+    m_HelpDialog = jd;
+  }
+  
+  /**
+   * opens the help dialog for the capabilities.
+   */
+  protected void openCapabilitiesHelpDialog() {
+    if (PropertyDialog.getParentDialog(this) != null)
+      m_CapabilitiesDialog = new CapabilitiesHelpDialog(PropertyDialog.getParentDialog(this));
+    else
+      m_CapabilitiesDialog = new CapabilitiesHelpDialog(PropertyDialog.getParentFrame(this));
+    m_CapabilitiesDialog.setSize(400, 350);
+    m_CapabilitiesDialog.setLocation(m_aboutPanel.getTopLevelAncestor().getLocationOnScreen().x 
+                   + m_aboutPanel.getTopLevelAncestor().getSize().width,
+                   m_aboutPanel.getTopLevelAncestor().getLocationOnScreen().y);
+    m_CapabilitiesDialog.setVisible(true);
+    addPropertyChangeListener(m_CapabilitiesDialog);
+  }
+
+  /**
+   * Gets the number of editable properties for the current target.
+   *
+   * @return the number of editable properties.
+   */
+  public int editableProperties() {
+
+    return m_NumEditable;
+  }
+  
+  /**
+   * Updates the propertysheet when a value has been changed (from outside
+   * the propertysheet?).
+   *
+   * @param evt a value of type 'PropertyChangeEvent'
+   */
+  synchronized void wasModified(PropertyChangeEvent evt) {
+
+    //    System.err.println("wasModified");
+    if (evt.getSource() instanceof PropertyEditor) {
+      PropertyEditor editor = (PropertyEditor) evt.getSource();
+      for (int i = 0 ; i < m_Editors.length; i++) {
+	if (m_Editors[i] == editor) {
+	  PropertyDescriptor property = m_Properties[i];
+	  Object value = editor.getValue();
+	  m_Values[i] = value;
+	  Method setter = property.getWriteMethod();
+	  try {
+	    Object args[] = { value };
+	    args[0] = value;
+	    setter.invoke(m_Target, args);
+	  } catch (InvocationTargetException ex) {
+	    if (ex.getTargetException()
+		instanceof PropertyVetoException) {
+              String message = "WARNING: Vetoed; reason is: " 
+                               + ex.getTargetException().getMessage();
+	      System.err.println(message);
+              
+              Component jf;
+              if(evt.getSource() instanceof JPanel)
+                  jf = ((JPanel)evt.getSource()).getParent();
+              else
+                  jf = new JFrame();
+              JOptionPane.showMessageDialog(jf, message, 
+                                            "error", 
+                                            JOptionPane.WARNING_MESSAGE);
+              if(jf instanceof JFrame)
+                  ((JFrame)jf).dispose();
+
+            } else {
+	      System.err.println(ex.getTargetException().getClass().getName()+ 
+				 " while updating "+ property.getName() +": "+
+				 ex.getTargetException().getMessage());
+              Component jf;
+              if(evt.getSource() instanceof JPanel)
+                  jf = ((JPanel)evt.getSource()).getParent();
+              else
+                  jf = new JFrame();
+              JOptionPane.showMessageDialog(jf,
+                                            ex.getTargetException().getClass().getName()+ 
+                                            " while updating "+ property.getName()+
+                                            ":\n"+
+                                            ex.getTargetException().getMessage(), 
+                                            "error", 
+                                            JOptionPane.WARNING_MESSAGE);
+              if(jf instanceof JFrame)
+                  ((JFrame)jf).dispose();
+
+            }
+	  } catch (Exception ex) {
+	    System.err.println("Unexpected exception while updating " 
+		  + property.getName());
+	  }
+	  if (m_Views[i] != null && m_Views[i] instanceof PropertyPanel) {
+	    //System.err.println("Trying to repaint the property canvas");
+	    m_Views[i].repaint();
+	    revalidate();
+	  }
+	  break;
+	}
+      }
+    }
+
+    // Now re-read all the properties and update the editors
+    // for any other properties that have changed.
+    for (int i = 0; i < m_Properties.length; i++) {
+      Object o;
+      try {
+	Method getter = m_Properties[i].getReadMethod();
+	Method setter = m_Properties[i].getWriteMethod();
+	
+	if (getter == null || setter == null) {
+	  // ignore set/get only properties
+	  continue;
+	}
+
+	Object args[] = { };
+	o = getter.invoke(m_Target, args);
+      } catch (Exception ex) {
+	o = null;
+      }
+      if (o == m_Values[i] || (o != null && o.equals(m_Values[i]))) {
+	// The property is equal to its old value.
+	continue;
+      }
+      m_Values[i] = o;
+      // Make sure we have an editor for this property...
+      if (m_Editors[i] == null) {
+	continue;
+      }
+      // The property has changed!  Update the editor.
+      m_Editors[i].removePropertyChangeListener(this);
+      m_Editors[i].setValue(o);
+      m_Editors[i].addPropertyChangeListener(this);
+      if (m_Views[i] != null) {
+	//System.err.println("Trying to repaint " + (i + 1));
+	m_Views[i].repaint();
+      }
+    }
+
+    // Make sure the target bean gets repainted.
+    if (Beans.isInstanceOf(m_Target, Component.class)) {
+      ((Component)(Beans.getInstanceOf(m_Target, Component.class))).repaint();
+    }
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/PropertyText.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertyText.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertyText.java	(revision 29)
@@ -0,0 +1,101 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyText.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyEditor;
+
+import javax.swing.JTextField;
+
+/** 
+ * Support for a PropertyEditor that uses text.
+ * Isn't going to work well if the property gets changed
+ * somewhere other than this field simultaneously
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+class PropertyText
+  extends JTextField {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3915342928825822730L;
+
+  /** The property editor */
+  private PropertyEditor m_Editor;
+
+  /**
+   * Sets up the editing component with the supplied editor.
+   *
+   * @param pe the PropertyEditor
+   */
+  PropertyText(PropertyEditor pe) {
+ 
+    //super(pe.getAsText());
+    super((pe.getAsText().equals("null"))?"":pe.getAsText());
+    m_Editor = pe;
+    
+    /*    m_Editor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent evt) {
+	updateUs();
+      }
+      }); */
+    addKeyListener(new KeyAdapter() {
+      public void keyReleased(KeyEvent e) {
+	//	if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+	updateEditor();
+	//	}
+      }
+    });
+    addFocusListener(new FocusAdapter() {
+      public void focusLost(FocusEvent e) {
+	updateEditor();
+      }
+    });
+  }
+
+  /**
+   * Attempts to update the textfield value from the editor.
+   */
+  protected void updateUs() {
+    try {
+      setText(m_Editor.getAsText());
+    } catch (IllegalArgumentException ex) {
+      // Quietly ignore.
+    }
+  }
+
+  /**
+   * Attempts to update the editor value from the textfield.
+   */
+  protected void updateEditor() {
+    try {
+      m_Editor.setAsText(getText());
+    } catch (IllegalArgumentException ex) {
+      // Quietly ignore.
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/PropertyValueSelector.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/PropertyValueSelector.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/PropertyValueSelector.java	(revision 29)
@@ -0,0 +1,71 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyValueSelector.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.beans.PropertyEditor;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+
+/** 
+ * Support for any PropertyEditor that uses tags.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+class PropertyValueSelector
+  extends JComboBox {
+
+  /** for serialization */
+  private static final long serialVersionUID = 128041237745933212L;
+
+  /** The property editor */
+  PropertyEditor m_Editor;
+  
+  /**
+   * Sets up the editing component with the supplied editor.
+   *
+   * @param pe the PropertyEditor
+   */
+  public PropertyValueSelector(PropertyEditor pe) {
+      
+    m_Editor = pe;
+    Object value = m_Editor.getAsText();
+    String tags[] = m_Editor.getTags();
+    ComboBoxModel model = new DefaultComboBoxModel(tags) {
+      private static final long serialVersionUID = 7942587653040180213L;
+      
+      public Object getSelectedItem() {
+	return m_Editor.getAsText();
+      }
+      public void setSelectedItem(Object o) {
+	m_Editor.setAsText((String)o);
+      }
+    };
+    setModel(model);
+    setSelectedItem(value);
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/ReaderToTextPane.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ReaderToTextPane.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ReaderToTextPane.java	(revision 29)
@@ -0,0 +1,130 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ReaderToTextArea.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import java.awt.Color;
+import java.io.LineNumberReader;
+import java.io.Reader;
+
+import javax.swing.JTextPane;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.StyledDocument;
+
+/**
+ * A class that sends all lines from a reader to a JTextPane component.
+ * 
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class ReaderToTextPane
+  extends Thread {
+
+  /** The reader being monitored. */
+  protected LineNumberReader m_Input;
+
+  /** The output text component. */
+  protected JTextPane m_Output;
+  
+  /** the color to use. */
+  protected Color m_Color;
+
+  /**
+   * Sets up the thread. Using black as color for displaying the text.
+   *
+   * @param input 	the Reader to monitor
+   * @param output 	the TextArea to send output to
+   */
+  public ReaderToTextPane(Reader input, JTextPane output) {
+    this(input, output, Color.BLACK);
+  }
+
+  /**
+   * Sets up the thread.
+   *
+   * @param input 	the Reader to monitor
+   * @param output 	the TextArea to send output to
+   * @param color	the color to use
+   */
+  public ReaderToTextPane(Reader input, JTextPane output, Color color) {
+    StyledDocument      doc;
+    Style               style;
+
+    setDaemon(true);
+    
+    m_Color  = color;
+    m_Input  = new LineNumberReader(input);
+    m_Output = output;
+    
+    doc   = m_Output.getStyledDocument();
+    style = StyleContext.getDefaultStyleContext()
+                        .getStyle(StyleContext.DEFAULT_STYLE);
+    style = doc.addStyle(getStyleName(), style);
+    StyleConstants.setFontFamily(style, "monospaced");
+    StyleConstants.setForeground(style, m_Color);
+  }
+  
+  /**
+   * Returns the color in use.
+   * 
+   * @return		the color
+   */
+  public Color getColor() {
+    return m_Color;
+  }
+  
+  /**
+   * Returns the style name.
+   * 
+   * @return		the style name
+   */
+  protected String getStyleName() {
+    return "" + m_Color.hashCode();
+  }
+
+  /**
+   * Sit here listening for lines of input and appending them straight
+   * to the text component.
+   */
+  public void run() {
+    while (true) {
+      try {
+	StyledDocument doc = m_Output.getStyledDocument();
+	doc.insertString(
+	    doc.getLength(), 
+	    m_Input.readLine() + '\n', 
+	    doc.getStyle(getStyleName()));
+	m_Output.setCaretPosition(doc.getLength());
+      }
+      catch (Exception ex) {
+	try {
+	  sleep(100);
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ResultHistoryPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ResultHistoryPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ResultHistoryPanel.java	(revision 29)
@@ -0,0 +1,463 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResultHistoryPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import weka.gui.visualize.PrintableComponent;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.Serializable;
+import java.util.Hashtable;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JViewport;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.JTextComponent;
+
+/** 
+ * A component that accepts named stringbuffers and displays the name in a list
+ * box. When a name is right-clicked, a frame is popped up that contains
+ * the string held by the stringbuffer. Optionally a text component may be
+ * provided that will have it's text set to the named result text on a
+ * left-click.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.26 $
+ */
+public class ResultHistoryPanel
+  extends JPanel {
+  
+  /** for serialization */
+  static final long serialVersionUID = 4297069440135326829L;
+  
+  /** An optional component for single-click display */
+  protected JTextComponent m_SingleText;
+
+  /** The named result being viewed in the single-click display */
+  protected String m_SingleName;
+  
+  /** The list model */
+  protected DefaultListModel m_Model = new DefaultListModel();
+
+  /** The list component */
+  protected JList m_List = new JList(m_Model);
+  
+  /** A Hashtable mapping names to result buffers */
+  protected Hashtable m_Results = new Hashtable();
+
+  /** A Hashtable mapping names to output text components */
+  protected Hashtable m_FramedOutput = new Hashtable();
+
+  /** A hashtable mapping names to arbitrary objects */
+  protected Hashtable m_Objs = new Hashtable();
+
+  /** Let the result history list handle right clicks in the default
+      manner---ie, pop up a window displaying the buffer */
+  protected boolean m_HandleRightClicks = true;
+
+  /** for printing the output to files */
+  protected PrintableComponent m_Printer = null;
+  
+  /**
+   * Extension of MouseAdapter that implements Serializable.
+   */
+  public static class RMouseAdapter 
+    extends MouseAdapter implements Serializable {
+    
+    /** for serialization */
+    static final long serialVersionUID = -8991922650552358669L;    
+  }
+ 
+  
+  /**
+   * Extension of KeyAdapter that implements Serializable.
+   */
+  public static class RKeyAdapter 
+    extends KeyAdapter implements Serializable {
+    
+    /** for serialization */
+    static final long serialVersionUID = -8675332541861828079L;
+  }
+
+  /**
+   * Create the result history object
+   *
+   * @param text the optional text component for single-click display
+   */
+  public ResultHistoryPanel(JTextComponent text) {
+    m_SingleText = text;
+    if (text != null) {
+      m_Printer = new PrintableComponent(m_SingleText);
+    }
+    m_List.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    m_List.addMouseListener(new RMouseAdapter() {
+      private static final long serialVersionUID = -9015397020486290479L;
+      
+      public void mouseClicked(MouseEvent e) {
+	if ((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	    == InputEvent.BUTTON1_MASK) {
+            if (    ((e.getModifiers() & InputEvent.SHIFT_DOWN_MASK) == 0)
+                 && ((e.getModifiers() & InputEvent.CTRL_DOWN_MASK) == 0) ) {
+              int index = m_List.locationToIndex(e.getPoint());
+              if ((index != -1) && (m_SingleText != null)) {
+                setSingle((String)m_Model.elementAt(index));
+            }
+          }
+	} else {
+	  // if there are stored objects then assume that the storer
+	  // will handle popping up the text in a seperate frame
+	  if (m_HandleRightClicks) {
+	    int index = m_List.locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = (String)m_Model.elementAt(index);
+	      openFrame(name);
+	    }
+	  }
+	}
+      }
+    });
+
+    m_List.addKeyListener(new RKeyAdapter() {
+      private static final long serialVersionUID = 7910681776999302344L;
+      
+      public void keyReleased(KeyEvent e) {
+        if (e.getKeyCode() == KeyEvent.VK_DELETE) {
+          int selected = m_List.getSelectedIndex();
+          if (selected != -1) {
+            removeResult((String)m_Model.elementAt(selected));
+          }
+        }
+      }
+    });
+    m_List.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+      public void valueChanged(ListSelectionEvent e) {
+	if (!e.getValueIsAdjusting()) {
+	  ListSelectionModel lm = (ListSelectionModel) e.getSource();
+	  for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
+	    if (lm.isSelectedIndex(i)) {
+	      //m_AttSummaryPanel.setAttribute(i);
+	       if ((i != -1) && (m_SingleText != null)) {
+		 setSingle((String)m_Model.elementAt(i));
+	       }
+	      break;
+	    }
+	  }
+	}
+      }
+    });
+
+
+    setLayout(new BorderLayout());
+    //    setBorder(BorderFactory.createTitledBorder("Result history"));
+    final JScrollPane js = new JScrollPane(m_List);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });
+    add(js, BorderLayout.CENTER);
+  }
+
+  /**
+   * Adds a new result to the result list.
+   *
+   * @param name the name to associate with the result
+   * @param result the StringBuffer that contains the result text
+   */
+  public void addResult(String name, StringBuffer result) {
+    
+    m_Model.addElement(name);
+    m_Results.put(name, result);
+  }
+
+  /**
+   * Removes one of the result buffers from the history. Any windows currently
+   * displaying the contents of the buffer are not affected.
+   *
+   * @param name the name of the buffer to remove.
+   */
+  public void removeResult(String name) {
+
+    StringBuffer buff = (StringBuffer) m_Results.get(name);
+    if (buff != null) {
+      m_Results.remove(name);
+      m_Model.removeElement(name);
+      m_Objs.remove(name);
+      System.gc();
+    } 
+  }
+
+  /**
+   * Removes all of the result buffers from the history. Any windows currently
+   * displaying the contents of the buffer are not affected.
+   */
+  public void clearResults() {
+    m_Results.clear();
+    m_Model.clear();
+    m_Objs.clear();
+    System.gc();
+  }
+
+  /**
+   * Adds an object to the results list
+   * @param name the name to associate with the object
+   * @param o the object
+   */
+  public void addObject(String name, Object o) {
+    m_Objs.put(name, o);
+  }
+
+  /**
+   * Get the named object from the list
+   * @param name the name of the item to retrieve the stored object
+   * for
+   * @return the object or null if there is no object at this index
+   */
+  public Object getNamedObject(String name) {
+    Object v = null;
+    v = m_Objs.get(name);
+    return v;
+  }
+
+  /**
+   * Gets the object associated with the currently
+   * selected item in the list.
+   * @return the object or null if there is no
+   * object corresponding to the current selection in
+   * the list
+   */
+  public Object getSelectedObject() {
+    Object v = null;
+    int index = m_List.getSelectedIndex();
+    if (index != -1) {
+      String name = (String)(m_Model.elementAt(index));
+      v = m_Objs.get(name);
+    }
+    
+    return v;
+  }
+
+  /**
+   * Gets the named buffer
+   * @return the buffer or null if there are no items in
+   * the list
+   */
+  public StringBuffer getNamedBuffer(String name) {
+    StringBuffer b = null;
+    b = (StringBuffer)(m_Results.get(name));
+    return b;
+  }
+
+  /**
+   * Gets the buffer associated with the currently
+   * selected item in the list.
+   * @return the buffer or null if there are no items in
+   * the list
+   */
+  public StringBuffer getSelectedBuffer() {
+    StringBuffer b = null;
+    int index = m_List.getSelectedIndex();
+    if (index != -1) {
+      String name = (String)(m_Model.elementAt(index));
+      b = (StringBuffer)(m_Results.get(name));
+    }
+    return b;
+  }
+
+  /**
+   * Get the name of the currently selected item in the list
+   * @return the name of the currently selected item or null if no
+   * item selected
+   */
+  public String getSelectedName() {
+    int index = m_List.getSelectedIndex();
+    if (index != -1) {
+      return (String)(m_Model.elementAt(index));
+    }
+    return null;
+  }
+
+  /**
+   * Gets the name of theitem in the list at the specified index
+   * @return the name of item or null if there is no item at that index
+   */
+  public String getNameAtIndex(int index) {
+    if (index != -1) {
+      return (String)(m_Model.elementAt(index));
+    }
+    return null;
+  }
+
+  /**
+   * Sets the single-click display to view the named result.
+   *
+   * @param name the name of the result to display.
+   */
+  public void setSingle(String name) {
+
+    StringBuffer buff = (StringBuffer) m_Results.get(name);
+    if (buff != null) {
+      m_SingleName = name;
+      m_SingleText.setText(buff.toString());
+      m_List.setSelectedValue(name, true);
+    }
+  }
+  
+  /**
+   * Opens the named result in a separate frame.
+   *
+   * @param name the name of the result to open.
+   */
+  public void openFrame(String name) {
+
+    StringBuffer buff = (StringBuffer) m_Results.get(name);
+    JTextComponent currentText = (JTextComponent) m_FramedOutput.get(name);
+    if ((buff != null) && (currentText == null)) {
+      // Open the frame.
+      JTextArea ta = new JTextArea();
+      ta.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+      ta.setFont(new Font("Monospaced", Font.PLAIN, 12));
+      ta.setEditable(false);
+      ta.setText(buff.toString());
+      m_FramedOutput.put(name, ta);
+      final JFrame jf = new JFrame(name);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  m_FramedOutput.remove(jf.getTitle());
+	  jf.dispose();
+	}
+      });
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(new JScrollPane(ta), BorderLayout.CENTER);
+      jf.pack();
+      jf.setSize(450, 350);
+      jf.setVisible(true);
+    }
+  }
+
+  /**
+   * Tells any component currently displaying the named result that the
+   * contents of the result text in the StringBuffer have been updated.
+   *
+   * @param name the name of the result that has been updated.
+   */
+  public void updateResult(String name) {
+
+    StringBuffer buff = (StringBuffer) m_Results.get(name);
+    if (buff == null) {
+      return;
+    }
+    if (m_SingleName == name) {
+      m_SingleText.setText(buff.toString());
+    }
+    JTextComponent currentText = (JTextComponent) m_FramedOutput.get(name);
+    if (currentText != null) {
+      currentText.setText(buff.toString());
+    }
+  }
+
+  /**
+   * Gets the selection model used by the results list.
+   *
+   * @return a value of type 'ListSelectionModel'
+   */
+  public ListSelectionModel getSelectionModel() {
+    
+    return m_List.getSelectionModel();
+  }
+
+  /**
+   * Gets the JList used by the results list
+   * @return the JList
+   */
+  public JList getList() {
+    return m_List;
+  }
+
+  /**
+   * Set whether the result history list should handle right clicks
+   * or whether the parent object will handle them.
+   * @param tf false if parent object will handle right clicks
+   */
+  public void setHandleRightClicks(boolean tf) {
+    m_HandleRightClicks = tf;
+  }
+
+
+  /**
+   * Tests out the result history from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Classifier");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final ResultHistoryPanel jd = new ResultHistoryPanel(null);
+      jd.addResult("blah", new StringBuffer("Nothing to see here"));
+      jd.addResult("blah1", new StringBuffer("Nothing to see here1"));
+      jd.addResult("blah2", new StringBuffer("Nothing to see here2"));
+      jd.addResult("blah3", new StringBuffer("Nothing to see here3"));
+      jf.getContentPane().add(jd, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SaveBuffer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SaveBuffer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SaveBuffer.java	(revision 29)
@@ -0,0 +1,206 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SaveBuffer.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.gui.Logger;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Writer;
+import java.io.BufferedWriter;
+import java.io.PrintWriter;
+
+import java.awt.Component;
+import javax.swing.JFileChooser;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
+import javax.swing.JDialog;
+
+/**
+ * This class handles the saving of StringBuffers to files. It will pop
+ * up a file chooser allowing the user to select a destination file. If
+ * the file exists, the user is prompted for the correct course of action,
+ * ie. overwriting, appending, selecting a new filename or canceling.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision 1.0 $
+ */
+public class SaveBuffer {
+
+  /** The Logger to send messages to */
+  private Logger m_Log;
+
+  /** The parent component requesting the save */
+  private Component m_parentComponent;
+
+  /** Last directory selected from the file chooser */
+  private String m_lastvisitedDirectory=null;
+
+  /**
+   * Constructor
+   * @param log the logger to send messages to
+   * @param parent the parent component will be requesting a save
+   */
+  public SaveBuffer(Logger log, Component parent) {
+    m_Log = log;
+    m_parentComponent = parent;
+  }
+
+  /**
+   * Save a buffer
+   * @param buf the buffer to save
+   * @return true if the save is completed succesfully
+   */
+  public boolean save(StringBuffer buf) {
+    if (buf != null) {
+      JFileChooser fileChooser;
+      if (m_lastvisitedDirectory == null) {
+	fileChooser = new JFileChooser(
+		      new File(System.getProperty("user.dir")));
+      } else {
+	fileChooser = new JFileChooser(m_lastvisitedDirectory);
+      }
+
+      fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+      int returnVal = fileChooser.showSaveDialog(m_parentComponent);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	File sFile = fileChooser.getSelectedFile();
+	m_lastvisitedDirectory = sFile.getPath();
+
+	if (sFile.exists()) {
+	  Object [] options = new String[4];
+	  options[0] = "Append";
+	  options[1] = "Overwrite";
+	  options[2] = "Choose new name";
+	  options[3] = "Cancel";
+	
+	  JOptionPane jop = new JOptionPane("File exists",
+					     JOptionPane.QUESTION_MESSAGE,
+					    1,
+					    null,
+					    options);
+	  JDialog dialog = jop.createDialog(m_parentComponent, "File query");
+	  dialog.setVisible(true);
+	  Object selectedValue = jop.getValue();
+	  if (selectedValue == null) {
+	  } else {
+	    for(int i=0; i<4; i++) {
+	      if(options[i].equals(selectedValue)) {
+		switch (i) {
+		  // append
+		case 0:
+		  return saveOverwriteAppend(buf, sFile, true);
+		  // overwrite
+		case 1: 
+		  return saveOverwriteAppend(buf, sFile, false);
+		  // pick new name
+		case 2:
+		  return save(buf);
+		  // cancel
+		case 3: break;
+		}
+	      }
+	    }
+	  }
+	} else {
+	  saveOverwriteAppend(buf, sFile, false); // file does not exist
+	}
+      } else {
+	return false; // file save canceled
+      }
+    }
+    return false; // buffer null
+  }
+
+  /** 
+   * Saves the provided buffer to the specified file
+   * @param buf the buffer to save
+   * @param sFile the file to save to
+   * @param append true if buffer is to be appended to file
+   * @return true if save is succesful
+   */
+  private boolean saveOverwriteAppend(StringBuffer buf, 
+				      File sFile,
+				      boolean append) {
+
+    try {
+      String path = sFile.getPath();
+      if (m_Log != null) {
+	if (append) {
+	  m_Log.statusMessage("Appending to file...");
+	} else {
+	  m_Log.statusMessage("Saving to file...");
+	}
+      }
+      PrintWriter out
+	= new PrintWriter(new BufferedWriter(
+			  new FileWriter(path, append)));
+      out.write(buf.toString(),0,buf.toString().length());
+      out.close();
+      if (m_Log != null) {
+	m_Log.statusMessage("OK");
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      if (m_Log != null) {
+	m_Log.logMessage(ex.getMessage());
+      }
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Main method for testing this class
+   */
+  public static void main(String [] args) {
+     try {
+       final javax.swing.JFrame jf =
+	 new javax.swing.JFrame("SaveBuffer test");
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      javax.swing.JButton jb = new javax.swing.JButton("Save");
+      jf.getContentPane().add(jb,java.awt.BorderLayout.SOUTH);
+      jf.getContentPane().add(lp, java.awt.BorderLayout.CENTER);
+      final SaveBuffer svb = new SaveBuffer(lp, jf);
+      jb.addActionListener(new java.awt.event.ActionListener() {
+	  public void actionPerformed(java.awt.event.ActionEvent e) {
+	    svb.save(new StringBuffer("A bit of test text"));
+	  }
+	});
+
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+      jf.pack();
+      jf.setVisible(true);
+     } catch (Exception ex) {
+       ex.printStackTrace();
+       System.err.println(ex.getMessage());
+     }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SelectedTagEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SelectedTagEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SelectedTagEditor.java	(revision 29)
@@ -0,0 +1,157 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SelectedTagEditor.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+
+import weka.core.Tag;
+import weka.core.SelectedTag;
+
+import java.awt.BorderLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorSupport;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+/** 
+ * A PropertyEditor that uses tags, where the tags are obtained from a
+ * weka.core.SelectedTag object.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.8 $
+ */
+public class SelectedTagEditor extends PropertyEditorSupport {
+
+  /**
+   * Returns a description of the property value as java source.
+   *
+   * @return a value of type 'String'
+   */
+  public String getJavaInitializationString() {
+
+    SelectedTag s = (SelectedTag)getValue();
+    Tag [] tags = s.getTags();
+    String result = "new SelectedTag("
+      + s.getSelectedTag().getID()
+      + ", {\n";
+    for (int i = 0; i < tags.length; i++) {
+      result += "new Tag(" + tags[i].getID()
+	+ ",\"" + tags[i].getReadable()
+	+ "\")";
+      if (i < tags.length - 1) {
+	result += ',';
+      }
+      result += '\n';
+    }
+    return result + "})";
+  }
+
+  /**
+   * Gets the current value as text.
+   *
+   * @return a value of type 'String'
+   */
+  public String getAsText() {
+
+    SelectedTag s = (SelectedTag)getValue();
+    return s.getSelectedTag().getReadable();
+  }
+
+  /**
+   * Sets the current property value as text.
+   *
+   * @param text the text of the selected tag.
+   * @exception java.lang.IllegalArgumentException if an error occurs
+   */
+  public void setAsText(String text)
+    {
+
+    SelectedTag s = (SelectedTag)getValue();
+    Tag [] tags = s.getTags();
+    try {
+      for (int i = 0; i < tags.length; i++) {
+	if (text.equals(tags[i].getReadable())) {
+	  setValue(new SelectedTag(tags[i].getID(), tags));
+	  return;
+	}
+      }
+    } catch (Exception ex) {
+      throw new java.lang.IllegalArgumentException(text);
+    }
+  }
+
+  /**
+   * Gets the list of tags that can be selected from.
+   *
+   * @return an array of string tags.
+   */
+  public String[] getTags() {
+
+    SelectedTag s = (SelectedTag)getValue();
+    Tag [] tags = s.getTags();
+    String [] result = new String [tags.length];
+    for (int i = 0; i < tags.length; i++) {
+      result[i] = tags[i].getReadable();
+    }
+    return result;
+  }
+  
+  /**
+   * Tests out the selectedtag editor from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      GenericObjectEditor.registerEditors();
+      Tag [] tags =  {
+	new Tag(1, "First option"),
+	new Tag(2, "Second option"),
+	new Tag(3, "Third option"),
+	new Tag(4, "Fourth option"),
+	new Tag(5, "Fifth option"),
+      };
+      SelectedTag initial = new SelectedTag(1, tags);
+      SelectedTagEditor ce = new SelectedTagEditor();
+      ce.setValue(initial);
+      PropertyValueSelector ps = new PropertyValueSelector(ce);
+      JFrame f = new JFrame(); 
+      f.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  System.exit(0);
+	}
+      });
+      f.getContentPane().setLayout(new BorderLayout());
+      f.getContentPane().add(ps, BorderLayout.CENTER);
+      f.pack();
+      f.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/SetInstancesPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SetInstancesPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SetInstancesPanel.java	(revision 29)
@@ -0,0 +1,423 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SetInstancesPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.Instances;
+import weka.core.converters.ConverterUtils;
+import weka.core.converters.FileSourcedConverter;
+import weka.core.converters.IncrementalConverter;
+import weka.core.converters.URLSourcedLoader;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+/** 
+ * A panel that displays an instance summary for a set of instances and
+ * lets the user open a set of instances from either a file or URL.
+ *
+ * Instances may be obtained either in a batch or incremental fashion.
+ * If incremental reading is used, then
+ * the client should obtain the Loader object (by calling
+ * getLoader()) and read the instances one at a time. If
+ * batch loading is used, then SetInstancesPanel will load
+ * the data into memory inside of a separate thread and notify
+ * the client when the operation is complete. The client can
+ * then retrieve the instances by calling getInstances().
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5298 $
+ */
+public class SetInstancesPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -384804041420453735L;
+  
+  /** Click to open instances from a file */
+  protected JButton m_OpenFileBut = new JButton("Open file...");
+
+  /** Click to open instances from a URL */
+  protected JButton m_OpenURLBut = new JButton("Open URL...");
+
+  /** Click to close the dialog */
+  protected JButton m_CloseBut = new JButton("Close");
+
+  /** The instance summary component */
+  protected InstancesSummaryPanel m_Summary = new InstancesSummaryPanel();
+
+  /** The file chooser for selecting arff files */
+  protected ConverterFileChooser m_FileChooser
+    = new ConverterFileChooser(new File(System.getProperty("user.dir")));
+
+  /** Stores the last URL that instances were loaded from */
+  protected String m_LastURL = "http://";
+
+  /** The thread we do loading in */
+  protected Thread m_IOThread;
+
+  /**
+   * Manages sending notifications to people when we change the set of
+   * working instances.
+   */
+  protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+
+  /** The current set of instances loaded */
+  protected Instances m_Instances;
+
+  /** The current loader used to obtain the current instances */
+  protected weka.core.converters.Loader m_Loader;
+  
+  /** the parent frame. if one is provided, the close-button is displayed */
+  protected JFrame m_ParentFrame = null;
+
+  /** the panel the Close-Button is located in */
+  protected JPanel m_CloseButPanel = null;
+
+  protected boolean m_readIncrementally = true;
+  
+  protected boolean m_showZeroInstancesAsUnknown = false;
+  
+  public SetInstancesPanel() {
+    this(false);
+  }
+  
+  /**
+   * Create the panel.
+   */
+  public SetInstancesPanel(boolean showZeroInstancesAsUnknown) {
+    m_showZeroInstancesAsUnknown = showZeroInstancesAsUnknown;
+    
+    m_OpenFileBut.setToolTipText("Open a set of instances from a file");
+    m_OpenURLBut.setToolTipText("Open a set of instances from a URL");
+    m_CloseBut.setToolTipText("Closes the dialog");
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    m_OpenURLBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setInstancesFromURLQ();
+      }
+    });
+    m_OpenFileBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setInstancesFromFileQ();
+      }
+    });
+    m_CloseBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        closeFrame();
+      }
+    });
+    m_Summary.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new GridLayout(1, 2));
+    buttons.add(m_OpenFileBut);
+    buttons.add(m_OpenURLBut);
+    
+    m_CloseButPanel = new JPanel();
+    m_CloseButPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+    m_CloseButPanel.add(m_CloseBut);
+    m_CloseButPanel.setVisible(false);
+    
+    JPanel buttonsAll = new JPanel();
+    buttonsAll.setLayout(new BorderLayout());
+    buttonsAll.add(buttons, BorderLayout.CENTER);
+    buttonsAll.add(m_CloseButPanel, BorderLayout.SOUTH);
+    
+    setLayout(new BorderLayout());
+    add(m_Summary, BorderLayout.CENTER);
+    add(buttonsAll, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Sets the frame, this panel resides in. Used for displaying the close 
+   * button, i.e., the close-button is visible if the given frame is not null.
+   * @param parent        the parent frame
+   */
+  public void setParentFrame(JFrame parent) {
+    m_ParentFrame = parent;
+    m_CloseButPanel.setVisible(m_ParentFrame != null);
+  }
+  
+  /**
+   * Returns the current frame the panel knows of, that it resides in. Can be
+   * null.
+   * @return the current parent frame
+   */
+  public JFrame getParentFrame() {
+    return m_ParentFrame;
+  }
+
+  /**
+   * closes the frame, i.e., the visibility is set to false
+   */
+  public void closeFrame() {
+    if (m_ParentFrame != null)
+      m_ParentFrame.setVisible(false);
+  }
+
+  /**
+   * Queries the user for a file to load instances from, then loads the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void setInstancesFromFileQ() {
+    
+    if (m_IOThread == null) {
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	final File selected = m_FileChooser.getSelectedFile();
+	m_IOThread = new Thread() {
+	  public void run() {
+	    setInstancesFromFile(selected);
+	    m_IOThread = null;
+	  }
+	};
+	m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+	m_IOThread.start();
+      }
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+    
+  /**
+   * Queries the user for a URL to load instances from, then loads the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void setInstancesFromURLQ() {
+    
+    if (m_IOThread == null) {
+      try {
+	String urlName = (String) JOptionPane.showInputDialog(this,
+			"Enter the source URL",
+			"Load Instances",
+			JOptionPane.QUESTION_MESSAGE,
+			null,
+			null,
+			m_LastURL);
+	if (urlName != null) {
+	  m_LastURL = urlName;
+	  final URL url = new URL(urlName);
+	  m_IOThread = new Thread() {
+	    public void run() {
+	      setInstancesFromURL(url);
+	      m_IOThread = null;
+	    }
+	  };
+	  m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+	  m_IOThread.start();
+	}
+      } catch (Exception ex) {
+	JOptionPane.showMessageDialog(this,
+				      "Problem with URL:\n"
+				      + ex.getMessage(),
+				      "Load Instances",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+  
+
+  /**
+   * Loads results from a set of instances contained in the supplied
+   * file.
+   *
+   * @param f a value of type 'File'
+   */
+  protected void setInstancesFromFile(File f) {
+    boolean incremental = m_readIncrementally;
+    
+    try {
+      m_Loader = ConverterUtils.getLoaderForFile(f);
+      if (m_Loader == null)
+	throw new Exception("No suitable FileSourcedConverter found for file!\n" + f);
+      
+      // not an incremental loader?
+      if (!(m_Loader instanceof IncrementalConverter))
+	incremental = false;
+
+      // load
+      ((FileSourcedConverter) m_Loader).setFile(f);
+      if (incremental) {
+        m_Summary.setShowZeroInstancesAsUnknown(m_showZeroInstancesAsUnknown);
+	setInstances(m_Loader.getStructure());
+      } else {
+        // If we are batch loading then we will know for sure that
+        // the data has no instances
+        m_Summary.setShowZeroInstancesAsUnknown(false);
+	setInstances(m_Loader.getDataSet());
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(this,
+				    "Couldn't read from file:\n"
+				    + f.getName(),
+				    "Load Instances",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Loads instances from a URL.
+   *
+   * @param u the URL to load from.
+   */
+  protected void setInstancesFromURL(URL u) {
+    boolean incremental = m_readIncrementally;
+    
+    try {
+      m_Loader = ConverterUtils.getURLLoaderForFile(u.toString());
+      if (m_Loader == null)
+	throw new Exception("No suitable URLSourcedLoader found for URL!\n" + u);
+      
+      // not an incremental loader?
+      if (!(m_Loader instanceof IncrementalConverter))
+	incremental = false;
+
+      // load
+      ((URLSourcedLoader) m_Loader).setURL(u.toString());
+      if (incremental) {
+        m_Summary.setShowZeroInstancesAsUnknown(m_showZeroInstancesAsUnknown);
+	setInstances(m_Loader.getStructure());
+      } else {
+        m_Summary.setShowZeroInstancesAsUnknown(false);
+	setInstances(m_Loader.getDataSet());
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(this,
+				    "Couldn't read from URL:\n"
+				    + u,
+				    "Load Instances",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Updates the set of instances that is currently held by the panel
+   *
+   * @param i a value of type 'Instances'
+   */
+  public void setInstances(Instances i) {
+
+    m_Instances = i;
+    m_Summary.setInstances(m_Instances);
+    // Fire property change event for those interested.
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Gets the set of instances currently held by the panel
+   *
+   * @return a value of type 'Instances'
+   */
+  public Instances getInstances() {
+    
+    return m_Instances;
+  }
+
+  /**
+   * Gets the currently used Loader
+   *
+   * @return a value of type 'Loader'
+   */
+  public weka.core.converters.Loader getLoader() {
+    return m_Loader;
+  }
+
+  /**
+   * Gets the instances summary panel associated with
+   * this panel
+   * @return the instances summary panel
+   */
+  public InstancesSummaryPanel getSummary() {
+    return m_Summary;
+  }
+
+  /**
+   * Sets whether or not instances should be read incrementally
+   * by the Loader. If incremental reading is used, then
+   * the client should obtain the Loader object (by calling
+   * getLoader()) and read the instances one at a time. If
+   * batch loading is used, then SetInstancesPanel will load
+   * the data into memory inside of a separate thread and notify
+   * the client when the operation is complete. The client can
+   * then retrieve the instances by calling getInstances().
+   *
+   * @param incremental true if instances are to be read incrementally
+   * 
+   */
+  public void setReadIncrementally(boolean incremental) {
+    m_readIncrementally = incremental;
+  }
+
+  /**
+   * Gets whether instances are to be read incrementally or not
+   *
+   * @return true if instances are to be read incrementally
+   */
+  public boolean getReadIncrementally() {
+    return m_readIncrementally;
+  }
+  
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    m_Support.removePropertyChangeListener(l);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleCLI.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.gui.scripting.ScriptingPanel;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JFrame;
+
+/**
+ * Creates a very simple command line for invoking the main method of
+ * classes. System.out and System.err are redirected to an output area.
+ * Features a simple command history -- use up and down arrows to move
+ * through previous commmands. This gui uses only AWT (i.e. no Swing).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class SimpleCLI
+  extends JFrame {
+  
+  /** for serialization. */
+  static final long serialVersionUID = -50661410800566036L;
+  
+  /**
+   * Constructor.
+   *
+   * @throws Exception if an error occurs
+   */
+  public SimpleCLI() throws Exception {
+    SimpleCLIPanel	panel;
+
+    panel = new SimpleCLIPanel();
+    
+    setLayout(new BorderLayout());
+    setTitle(panel.getTitle());
+    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+    setIconImage(panel.getIcon().getImage());
+    add(panel);
+    pack();
+    setSize(600, 500);
+    setLocationRelativeTo(null);
+    setVisible(true);
+  }
+
+  /**
+   * Method to start up the simple cli.
+   *
+   * @param args 	Not used.
+   */
+  public static void main(String[] args) {
+    ScriptingPanel.showPanel(new SimpleCLIPanel(), args, 600, 500);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SimpleCLI.props	(revision 29)
@@ -0,0 +1,8 @@
+# Contains properties for the SimpleCLI
+#
+# Author:  FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 1.1 $
+
+# the maximum number of history commands to save (most recent ones)
+HistorySize=50
+
Index: branches/MetisMQI/src/main/java/weka/gui/SimpleCLIPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SimpleCLIPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SimpleCLIPanel.java	(revision 29)
@@ -0,0 +1,1096 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleCLIPanel.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.ClassDiscovery;
+import weka.core.OptionHandler;
+import weka.core.Trie;
+import weka.core.Utils;
+import weka.gui.scripting.ScriptingPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Vector;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.JTextPane;
+
+/**
+ * Creates a very simple command line for invoking the main method of
+ * classes. System.out and System.err are redirected to an output area.
+ * Features a simple command history -- use up and down arrows to move
+ * through previous commmands. This gui uses only AWT (i.e. no Swing).
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ */
+public class SimpleCLIPanel
+  extends ScriptingPanel
+  implements ActionListener {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 1089039734615114942L;
+
+  /** The filename of the properties file. */
+  protected static String FILENAME = "SimpleCLI.props";
+  
+  /** The default location of the properties file. */
+  protected static String PROPERTY_FILE = "weka/gui/" + FILENAME;
+  
+  /** Contains the SimpleCLI properties. */
+  protected static Properties PROPERTIES;
+
+  static {
+    // Allow a properties file in the current directory to override
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+      java.util.Enumeration keys = 
+	(java.util.Enumeration) PROPERTIES.propertyNames();
+      if (!keys.hasMoreElements()) {
+	throw new Exception(
+	    "Failed to read a property file for the SimpleCLI");
+      }
+    }
+    catch (Exception ex) {
+      JOptionPane.showMessageDialog(
+	  null,
+	  "Could not read a configuration file for the SimpleCLI.\n"
+	  + "An example file is included with the Weka distribution.\n"
+	  + "This file should be named \"" + PROPERTY_FILE + "\" and\n"
+	  + "should be placed either in your user home (which is set\n"
+	  + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+	  + "or the directory that java was started from\n",
+	  "SimpleCLI",
+	  JOptionPane.ERROR_MESSAGE);
+    }
+  }
+  
+  /** The output area canvas added to the frame. */
+  protected JTextPane m_OutputArea;
+
+  /** The command input area. */
+  protected JTextField m_Input;
+
+  /** The history of commands entered interactively. */
+  protected Vector m_CommandHistory;
+
+  /** The current position in the command history. */
+  protected int m_HistoryPos;
+
+  /** The thread currently running a class main method. */
+  protected Thread m_RunThread;
+
+  /** The commandline completion. */
+  protected CommandlineCompletion m_Completion;
+
+  /**
+   * A class that handles running the main method of the class
+   * in a separate thread.
+   * 
+   * @author Len Trigg (trigg@cs.waikato.ac.nz)
+   * @version $Revision: 5855 $
+   */
+  class ClassRunner extends Thread {
+
+    /** Stores the main method to call. */
+    protected Method m_MainMethod;
+
+    /** Stores the command line arguments to pass to the main method. */
+    String[] m_CommandArgs;
+    
+    /**
+     * Sets up the class runner thread.
+     *
+     * @param theClass the Class to call the main method of
+     * @param commandArgs an array of Strings to use as command line args
+     * @throws Exception if an error occurs
+     */
+    public ClassRunner(Class theClass, String [] commandArgs)
+      throws Exception {
+      
+      setDaemon(true);
+      Class[] argTemplate = {String[].class};
+      m_CommandArgs = commandArgs;
+      m_MainMethod = theClass.getMethod("main", argTemplate);
+      if (((m_MainMethod.getModifiers() & Modifier.STATIC) == 0)
+	  || (m_MainMethod.getModifiers() & Modifier.PUBLIC) == 0) {
+	throw new NoSuchMethodException("main(String[]) method of " +
+					theClass.getName() +
+					" is not public and static.");
+      }
+    }
+
+    /**
+     * Starts running the main method.
+     */
+    public void run() {
+      PrintStream outOld = null;
+      PrintStream outNew = null;
+      String outFilename = null;
+      
+      // is the output redirected?
+      if (m_CommandArgs.length > 2) {
+	String action = m_CommandArgs[m_CommandArgs.length - 2];
+	if (action.equals(">")) {
+	  outOld = System.out;
+	  try {
+	    outFilename = m_CommandArgs[m_CommandArgs.length - 1];
+	    // since file may not yet exist, command-line completion doesn't
+	    // work, hence replace "~" manually with home directory
+	    if (outFilename.startsWith("~"))
+	      outFilename = outFilename.replaceFirst("~", System.getProperty("user.home"));
+	    outNew = new PrintStream(new File(outFilename));
+	    System.setOut(outNew);
+	    m_CommandArgs[m_CommandArgs.length - 2] = "";
+	    m_CommandArgs[m_CommandArgs.length - 1] = "";
+	    // some main methods check the length of the "args" array
+	    // -> removed the two empty elements at the end
+	    String[] newArgs = new String[m_CommandArgs.length - 2];
+	    System.arraycopy(m_CommandArgs, 0, newArgs, 0, m_CommandArgs.length - 2);
+	    m_CommandArgs = newArgs;
+	  }
+	  catch (Exception e) {
+	    System.setOut(outOld);
+	    outOld = null;
+	  }
+	}
+      }
+      
+      try {
+	Object[] args = {m_CommandArgs};
+	m_MainMethod.invoke(null, args);
+	if (isInterrupted()) {
+	  System.err.println("[...Interrupted]");
+	}
+      } catch (Exception ex) {
+	if (ex.getMessage() == null) {
+	  System.err.println("[...Killed]");
+	} else {
+	  System.err.println("[Run exception] " + ex.getMessage());
+	}
+      } finally {
+	m_RunThread = null;
+      }
+      
+      // restore old System.out stream
+      if (outOld != null) {
+	outNew.flush();
+	outNew.close();
+	System.setOut(outOld);
+	System.out.println("Finished redirecting output to '" + outFilename + "'.");
+      }
+    }
+  }
+
+  /**
+   * A class for commandline completion of classnames.
+   * 
+   * @author  FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5855 $
+   */
+  public static class CommandlineCompletion {
+    
+    /** all the available packages. */
+    protected static Vector<String> m_Packages;
+
+    /** a trie for storing the packages. */
+    protected static Trie m_Trie;
+    
+    /** debug mode on/off. */
+    protected boolean m_Debug = false;
+    
+    /**
+     * default constructor.
+     */
+    public CommandlineCompletion() {
+      super();
+      
+      // build incremental list of packages
+      if (m_Packages == null) {
+	// get all packages
+	Vector list = ClassDiscovery.findPackages();
+	
+	// create incremental list
+	HashSet<String> set = new HashSet<String>();
+	for (int i = 0; i < list.size(); i++) {
+	  String[] parts = ((String) list.get(i)).split("\\.");
+	  for (int n = 1; n < parts.length; n++) {
+	    String pkg = "";
+	    for (int m = 0; m <= n; m++) {
+	      if (m > 0)
+		pkg += ".";
+	      pkg += parts[m];
+	    }
+	    set.add(pkg);
+	  }
+	}
+	
+	// init packages
+	m_Packages = new Vector<String>();
+	m_Packages.addAll(set);
+	Collections.sort(m_Packages);
+	
+	m_Trie = new Trie();
+	m_Trie.addAll(m_Packages);
+      }
+    }
+    
+    /**
+     * returns whether debug mode is on.
+     * 
+     * @return		true if debug is on
+     */
+    public boolean getDebug() {
+      return m_Debug;
+    }
+    
+    /**
+     * sets debug mode on/off.
+     * 
+     * @param value	if true then debug mode is on
+     */
+    public void setDebug(boolean value) {
+      m_Debug = value;
+    }
+    
+    /**
+     * tests whether the given partial string is the name of a class with
+     * classpath - it basically tests, whether the string consists only
+     * of alphanumeric literals, underscores and dots.
+     * 
+     * @param partial	the string to test
+     * @return		true if it looks like a classname
+     */
+    public boolean isClassname(String partial) {
+      return (partial.replaceAll("[a-zA-Z0-9\\-\\.]*", "").length() == 0);
+    }
+    
+    /**
+     * returns the packages part of the partial classname.
+     * 
+     * @param partial	the partial classname
+     * @return		the package part of the partial classname
+     */
+    public String getPackage(String partial) {
+      String	result;
+      int	i;
+      boolean	wasDot;
+      char	c;
+      
+      result = "";
+      wasDot = false;
+      for (i = 0; i < partial.length(); i++) {
+	c = partial.charAt(i);
+	
+	// start of classname?
+	if (wasDot && ((c >= 'A') && (c <= 'Z'))) {
+	  break;
+	}
+	// package/class separator
+	else if (c == '.') {
+	  wasDot = true;
+	  result += "" + c;
+	}
+	// regular char
+	else {
+	  wasDot = false;
+	  result += "" + c;
+	}
+      }
+
+      // remove trailing "."
+      if (result.endsWith("."))
+	result = result.substring(0, result.length() - 1);
+      
+      return result;
+    }
+    
+    /**
+     * returns the classname part of the partial classname.
+     * 
+     * @param partial	the partial classname
+     * @return		the class part of the classname
+     */
+    public String getClassname(String partial) {
+      String	result;
+      String	pkg;
+      
+      pkg = getPackage(partial);
+      if (pkg.length() + 1 < partial.length())
+	result = partial.substring(pkg.length() + 1);
+      else
+	result = "";
+      
+      return result;
+    }
+    
+    /**
+     * returns all the file/dir matches with the partial search string.
+     * 
+     * @param partial	the partial search string
+     * @return		all the matches
+     */
+    public Vector<String> getFileMatches(String partial) {
+      Vector<String>	result;
+      File		file;
+      File		dir;
+      File[]		files;
+      int		i;
+      String		prefix;
+      boolean		caseSensitive;
+      String		name;
+      boolean		match;
+      
+      result = new Vector<String>();
+
+      // is the OS case-sensitive?
+      caseSensitive = (File.separatorChar != '\\');
+      if (m_Debug)
+	System.out.println("case-sensitive=" + caseSensitive);
+      
+      // is "~" used for home directory? -> replace with actual home directory
+      if (partial.startsWith("~"))
+	partial = System.getProperty("user.home") + partial.substring(1);
+      
+      // determine dir and possible prefix
+      file   = new File(partial);
+      dir    = null;
+      prefix = null;
+      if (file.exists()) {
+	// determine dir to read
+	if (file.isDirectory()) {
+	  dir    = file;
+	  prefix = null;  // retrieve all
+	}
+	else {
+	  dir    = file.getParentFile();
+	  prefix = file.getName();
+	}
+      }
+      else {
+	dir    = file.getParentFile();
+	prefix = file.getName();
+      }
+
+      if (m_Debug)
+	System.out.println("search in dir=" + dir + ", prefix=" + prefix);
+      
+      // list all files in dir
+      if (dir != null) {
+	files = dir.listFiles();
+	if (files != null) {
+	  for (i = 0; i < files.length; i++) {
+	    name = files[i].getName();
+
+	    // does the name match?
+	    if ((prefix != null) && caseSensitive)
+	      match = name.startsWith(prefix);
+	    else if ((prefix != null) && !caseSensitive)
+	      match = name.toLowerCase().startsWith(prefix.toLowerCase());
+	    else
+	      match = true;
+
+	    if (match) {
+	      if (prefix != null) {
+		result.add(partial.substring(0, partial.length() - prefix.length()) + name);
+	      }
+	      else {
+		if (partial.endsWith("\\") || partial.endsWith("/"))
+		  result.add(partial + name);
+		else
+		  result.add(partial + File.separator + name);
+	      }
+	    }
+	  }
+	}
+	else {
+	  System.err.println("Invalid path: " + partial);
+	}
+      }
+      
+      // sort the result
+      if (result.size() > 1)
+	Collections.sort(result);
+      
+      // print results
+      if (m_Debug) {
+	System.out.println("file matches:");
+	for (i = 0; i < result.size(); i++)
+	  System.out.println(result.get(i));
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns all the class/package matches with the partial search string.
+     * 
+     * @param partial	the partial search string
+     * @return		all the matches
+     */
+    public Vector<String> getClassMatches(String partial) {
+      String		pkg;
+      String		cls;
+      Vector<String> 	result;
+      Vector<String> 	list;
+      int		i;
+      int		index;
+      Trie		tmpTrie;
+      HashSet		set;
+      String		tmpStr;
+      
+      pkg = getPackage(partial);
+      cls = getClassname(partial);
+      
+      if (getDebug())
+	System.out.println(
+	    "\nsearch for: '" + partial + "' => package=" + pkg + ", class=" + cls);
+
+      result = new Vector<String>();
+
+      // find all packages that start with that string
+      if (cls.length() == 0) {
+	list = m_Trie.getWithPrefix(pkg);
+	set  = new HashSet();
+	for (i = 0; i < list.size(); i++) {
+	  tmpStr = list.get(i);
+	  if (tmpStr.length() < partial.length())
+	    continue;
+	  if (tmpStr.equals(partial))
+	    continue;
+	  
+	  index  = tmpStr.indexOf('.', partial.length() + 1);
+	  if (index > -1)
+	    set.add(tmpStr.substring(0, index));
+	  else
+	    set.add(tmpStr);
+	}
+
+	result.addAll(set);
+	if (result.size() > 1)
+	  Collections.sort(result);
+      }
+
+      // find all classes that start with that string
+      list = ClassDiscovery.find(Object.class, pkg);
+      tmpTrie = new Trie();
+      tmpTrie.addAll(list);
+      list = tmpTrie.getWithPrefix(partial);
+      result.addAll(list);
+      
+      // sort the result
+      if (result.size() > 1)
+	Collections.sort(result);
+
+      // print results
+      if (m_Debug) {
+	System.out.println("class/package matches:");
+	for (i = 0; i < result.size(); i++)
+	  System.out.println(result.get(i));
+      }
+      
+      return result;
+    }
+    
+    /**
+     * returns all the matches with the partial search string, files or
+     * classes.
+     * 
+     * @param partial	the partial search string
+     * @return		all the matches
+     */
+    public Vector<String> getMatches(String partial) {
+      if (isClassname(partial))
+	return getClassMatches(partial);
+      else
+	return getFileMatches(partial);
+    }
+    
+    /**
+     * returns the common prefix for all the items in the list.
+     * 
+     * @param list	the list to return the common prefix for
+     * @return		the common prefix of all the items
+     */
+    public String getCommonPrefix(Vector<String> list) {
+      String	result;
+      Trie	trie;
+      
+      trie = new Trie();
+      trie.addAll(list);
+      result = trie.getCommonPrefix();
+      
+      if (m_Debug)
+	System.out.println(list + "\n  --> common prefix: '" + result + "'");
+      
+      return result;
+    }
+  }
+  
+  /**
+   * For initializing member variables.
+   */
+  protected void initialize() {
+    super.initialize();
+
+    m_CommandHistory = new Vector();
+    m_HistoryPos     = 0;
+    m_Completion     = new CommandlineCompletion();
+  }
+  
+  /**
+   * Sets up the GUI after initializing the members.
+   */
+  protected void initGUI() {
+    super.initGUI();
+
+    setLayout(new BorderLayout());
+
+    m_OutputArea = new JTextPane();
+    m_OutputArea.setEditable(false);
+    m_OutputArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    add(new JScrollPane(m_OutputArea), "Center");
+
+    m_Input = new JTextField();
+    m_Input.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_Input.addActionListener(this);
+    m_Input.setFocusTraversalKeysEnabled(false);
+    m_Input.addKeyListener(new KeyAdapter() {
+      public void keyPressed(KeyEvent e) {
+	doHistory(e);
+	doCommandlineCompletion(e);
+      }
+    });
+    add(m_Input, "South");
+  }
+  
+  /**
+   * Finishes up after initializing members and setting up the GUI.
+   */
+  protected void initFinish() {
+    super.initFinish();
+
+    System.out.println(
+	  "\nWelcome to the WEKA SimpleCLI\n\n"
+	+ "Enter commands in the textfield at the bottom of \n"
+	+ "the window. Use the up and down arrows to move \n"
+	+ "through previous commands.\n"
+	+ "Command completion for classnames and files is \n"
+	+ "initiated with <Tab>. In order to distinguish \n"
+	+ "between files and classnames, file names must \n"
+	+ "be either absolute or start with '." + File.separator + "' or '~/'\n"
+	+ "(the latter is a shortcut for the home directory).\n"
+	+ "<Alt+BackSpace> is used for deleting the text\n"
+	+ "in the commandline in chunks.\n");
+    try {
+      runCommand("help");
+    }
+    catch (Exception e) {
+      // ignored
+    }
+    
+    loadHistory();
+  }
+
+  /**
+   * Returns an icon to be used in a frame.
+   * 
+   * @return		the icon
+   */
+  public ImageIcon getIcon() {
+    return ComponentHelper.getImageIcon("weka_icon.gif");
+  }
+
+  /**
+   * Returns the current title for the frame/dialog.
+   * 
+   * @return		the title
+   */
+  public String getTitle() {
+    return "SimpleCLI";
+  }
+
+  /**
+   * Returns the text area that is used for displaying output on stdout
+   * and stderr.
+   * 
+   * @return		the JTextArea
+   */
+  public JTextPane getOutput() {
+    return m_OutputArea;
+  }
+
+  /**
+   * Not supported.
+   * 
+   * @return		always null
+   */
+  public JMenuBar getMenuBar() {
+    return null;
+  }
+
+  /**
+   * Executes a simple cli command.
+   *
+   * @param commands the command string
+   * @throws Exception if an error occurs
+   */
+  public void runCommand(String commands) throws Exception {
+
+    System.out.println("> " + commands + '\n');
+    System.out.flush();
+    String [] commandArgs = Utils.splitOptions(commands);
+    if (commandArgs.length == 0) {
+      return;
+    }
+    if (commandArgs[0].equals("java")) {
+      // Execute the main method of a class
+      commandArgs[0] = "";
+      try {
+	if (commandArgs.length == 1) {
+	  throw new Exception("No class name given");
+	}
+	String className = commandArgs[1];
+	commandArgs[1] = "";
+	if (m_RunThread != null) {
+	  throw new Exception("An object is already running, use \"break\""
+			      + " to interrupt it.");
+	}
+	Class theClass = Class.forName(className);
+
+	// some classes expect a fixed order of the args, i.e., they don't
+	// use Utils.getOption(...) => create new array without first two
+	// empty strings (former "java" and "<classname>")
+	Vector argv = new Vector();
+	for (int i = 2; i < commandArgs.length; i++)
+	  argv.add(commandArgs[i]);
+  
+	m_RunThread = new ClassRunner(theClass, (String[]) argv.toArray(new String[argv.size()]));
+	m_RunThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+	m_RunThread.start();	
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+
+    } else if (commandArgs[0].equals("capabilities")) {
+      try {
+	Object obj = Class.forName(commandArgs[1]).newInstance();
+	if (obj instanceof CapabilitiesHandler) {
+	  if (obj instanceof OptionHandler) {
+	    Vector<String> args = new Vector<String>();
+	    for (int i = 2; i < commandArgs.length; i++)
+	      args.add(commandArgs[i]);
+	    ((OptionHandler) obj).setOptions(args.toArray(new String[args.size()]));
+	  }
+	  Capabilities caps = ((CapabilitiesHandler) obj).getCapabilities();
+	  System.out.println(caps.toString().replace("[", "\n").replace("]", "\n"));
+	}
+	else {
+	  System.out.println("'" + commandArgs[1] + "' is not a " + CapabilitiesHandler.class.getName() + "!");
+	}
+      }
+      catch (Exception e) {
+	System.err.println(e.getMessage());
+      }
+    } else if (commandArgs[0].equals("cls")) {
+      // Clear the text area
+      m_OutputArea.setText("");
+    } else if (commandArgs[0].equals("history")) {
+      System.out.println("Command history:");
+      for (int i = 0; i < m_CommandHistory.size(); i++)
+	System.out.println(m_CommandHistory.get(i));
+      System.out.println();
+    } else if (commandArgs[0].equals("break")) {
+      if (m_RunThread == null) {
+	System.err.println("Nothing is currently running.");
+      } else {
+	System.out.println("[Interrupt...]");
+	m_RunThread.interrupt();
+      }
+    } else if (commandArgs[0].equals("kill")) {
+      if (m_RunThread == null) {
+	System.err.println("Nothing is currently running.");
+      } else {
+	System.out.println("[Kill...]");
+	m_RunThread.stop();
+	m_RunThread = null;
+      }
+    } else if (commandArgs[0].equals("exit")) {
+      // Shut down
+      // determine parent
+      Container parent = getParent();
+      Container frame = null;
+      boolean finished = false;
+      while (!finished) {
+	if (    (parent instanceof JFrame) 
+	     || (parent instanceof Frame) 
+	     || (parent instanceof JInternalFrame) ) {
+	  frame = parent;
+	  finished = true;
+	}
+	
+	if (!finished) {
+	  parent = parent.getParent();
+	  finished = (parent == null);
+	}
+      }
+      // fire the frame close event
+      if (frame != null) {
+	if (frame instanceof JInternalFrame)
+	  ((JInternalFrame) frame).doDefaultCloseAction();
+	else
+	  ((Window) frame).dispatchEvent(
+	      new WindowEvent(
+		  (Window) frame, WindowEvent.WINDOW_CLOSING));
+      }
+      
+    } else {
+      boolean help = ((commandArgs.length > 1)
+		      && commandArgs[0].equals("help"));
+      if (help && commandArgs[1].equals("java")) {
+	System.out.println(
+	    "java <classname> <args>\n\n"
+	    + "Starts the main method of <classname> with "
+	    + "the supplied command line arguments (if any).\n"
+	    + "The command is started in a separate thread, "
+	    + "and may be interrupted with the \"break\"\n"
+	    + "command (friendly), or killed with the \"kill\" "
+	    + "command (unfriendly).\n"
+	    + "Redirecting can be done with '>' followed by the "
+	    + "file to write to, e.g.:\n"
+	    + "  java some.Class > ." + File.separator + "some.txt");
+      } else if (help && commandArgs[1].equals("break")) {
+	System.out.println(
+	    "break\n\n"
+	    + "Attempts to nicely interrupt the running job, "
+	    + "if any. If this doesn't respond in an\n"
+	    + "acceptable time, use \"kill\".\n");
+      } else if (help && commandArgs[1].equals("kill")) {
+	System.out.println(
+	    "kill\n\n"
+	    + "Kills the running job, if any. You should only "
+	    + "use this if the job doesn't respond to\n"
+	    + "\"break\".\n");
+      } else if (help && commandArgs[1].equals("capabilities")) {
+	System.out.println(
+	    "capabilities <classname> <args>\n\n"
+	    + "Lists the capabilities of the specified class.\n"
+	    + "If the class is a " + OptionHandler.class.getName() + " then\n"
+	    + "trailing options after the classname will be\n"
+	    + "set as well.\n");
+      } else if (help && commandArgs[1].equals("cls")) {
+	System.out.println(
+	    "cls\n\n"
+	    + "Clears the output area.\n");
+      } else if (help && commandArgs[1].equals("history")) {
+	System.out.println(
+	    "history\n\n"
+	    + "Prints all issued commands.\n");
+      } else if (help && commandArgs[1].equals("exit")) {
+	System.out.println(
+	    "exit\n\n"
+	    + "Exits the SimpleCLI program.\n");
+      } else {
+	// Print a help message
+	System.out.println(
+	    "Command must be one of:\n"
+	    + "\tjava <classname> <args> [ > file]\n"
+	    + "\tbreak\n"
+	    + "\tkill\n"
+	    + "\tcapabilities <classname> <args>\n"
+	    + "\tcls\n"
+	    + "\thistory\n"
+	    + "\texit\n"
+	    + "\thelp <command>\n");
+      }
+    }
+  }
+
+  /**
+   * Changes the currently displayed command line when certain keys
+   * are pressed. The up arrow moves back through history entries
+   * and the down arrow moves forward through history entries.
+   *
+   * @param e a value of type 'KeyEvent'
+   */
+  public void doHistory(KeyEvent e) {
+    
+    if (e.getSource() == m_Input) {
+      switch (e.getKeyCode()) {
+      case KeyEvent.VK_UP:
+	if (m_HistoryPos > 0) {
+	  m_HistoryPos--;
+	  String command = (String) m_CommandHistory.elementAt(m_HistoryPos);
+	  m_Input.setText(command);
+	}
+	break;
+      case KeyEvent.VK_DOWN:
+	if (m_HistoryPos < m_CommandHistory.size()) {
+	  m_HistoryPos++;
+	  String command = "";
+	  if (m_HistoryPos < m_CommandHistory.size()) {
+	    command = (String) m_CommandHistory.elementAt(m_HistoryPos);
+	  }
+	  m_Input.setText(command);
+	}
+	break;
+      default:
+	break;
+      }
+    }
+  }
+
+  /**
+   * performs commandline completion on packages and classnames.
+   * 
+   * @param e a value of type 'KeyEvent'
+   */
+  public void doCommandlineCompletion(KeyEvent e) {
+    if (e.getSource() == m_Input) {
+      switch (e.getKeyCode()) {
+	// completion
+	case KeyEvent.VK_TAB:
+	  if (e.getModifiers() == 0) {
+	    // it might take a while before we determined all of the possible
+	    // matches (Java doesn't have an application wide cursor handling??)
+	    m_Input.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+	    m_OutputArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+	    try {
+	      String txt = m_Input.getText();
+
+	      // java call?
+	      if (txt.trim().startsWith("java ")) {
+		int pos = m_Input.getCaretPosition();
+		int nonNameCharPos = -1;
+		// find first character not part of a name, back from current position
+		// i.e., " or blank
+		for (int i = pos - 1; i >= 0; i--) {
+		  if (    (txt.charAt(i) == '"') 
+		      || (txt.charAt(i) == ' ') ) {
+		    nonNameCharPos = i;
+		    break;
+		  }
+		}
+
+		if (nonNameCharPos > -1) {
+		  String search = txt.substring(nonNameCharPos + 1, pos);
+
+		  // find matches and common prefix
+		  Vector<String> list = m_Completion.getMatches(search);
+		  String common = m_Completion.getCommonPrefix(list);
+
+		  // just extending by separator is not a real extension
+		  if ((search.toLowerCase() + File.separator).equals(common.toLowerCase()))
+		    common = search;
+
+		  // can we complete the string?
+		  if (common.length() > search.length()) {
+		    try {
+		      m_Input.getDocument().remove(nonNameCharPos + 1, search.length());
+		      m_Input.getDocument().insertString(nonNameCharPos + 1, common, null);
+		    }
+		    catch (Exception ex) {
+		      ex.printStackTrace();
+		    }
+		  }
+		  // ambigiuous? -> print matches
+		  else if (list.size() > 1) {
+		    System.out.println("\nPossible matches:");
+		    for (int i = 0; i < list.size(); i++)
+		      System.out.println("  " + list.get(i));
+		  }
+		  else {
+		    // nothing found, don't do anything
+		  }
+		}
+	      }
+	    }
+	    finally {
+	      // set cursor back to default
+	      m_Input.setCursor(null);
+	      m_OutputArea.setCursor(null);
+	    }
+	  }
+	  break;
+
+	// delete last part up to next blank or dot
+	case KeyEvent.VK_BACK_SPACE:
+	  if (e.getModifiers() == KeyEvent.ALT_MASK) {
+	    String txt = m_Input.getText();
+	    int pos    = m_Input.getCaretPosition();
+	    
+	    // skip whitespaces
+	    int start = pos;
+	    start--;
+	    while (start >= 0) {
+	      if (    (txt.charAt(start) == '.') 
+		   || (txt.charAt(start) == ' ') 
+		   || (txt.charAt(start) == '\\')
+		   || (txt.charAt(start) == '/') )
+		start--;
+	      else
+		break;
+	    }
+	    
+	    // find first blank or dot back from position
+	    int newPos = -1;
+	    for (int i = start; i >= 0; i--) {
+	      if (    (txt.charAt(i) == '.') 
+		   || (txt.charAt(i) == ' ')
+		   || (txt.charAt(i) == '\\') 
+		   || (txt.charAt(i) == '/') ) {
+		newPos = i;
+		break;
+	      }
+	    }
+
+	    // remove string
+	    try {
+	      m_Input.getDocument().remove(newPos + 1, pos - newPos - 1);
+	    }
+	    catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	  }
+	  break;
+      }
+    }
+  }
+  
+  /**
+   * Only gets called when return is pressed in the input area, which
+   * starts the command running.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+
+    try {
+      if (e.getSource() == m_Input) {
+	String command = m_Input.getText();
+	int last = m_CommandHistory.size() - 1;
+	if ((last < 0)
+	    || !command.equals((String)m_CommandHistory.elementAt(last))) {
+	  m_CommandHistory.addElement(command);
+	  saveHistory();
+	}
+	m_HistoryPos = m_CommandHistory.size();
+	runCommand(command);
+	
+	m_Input.setText("");
+      }
+    } catch (Exception ex) {
+      System.err.println(ex.getMessage());
+    }
+  }
+
+  /**
+   * loads the command history from the user's properties file.
+   */
+  protected void loadHistory() {
+    int 	size;
+    int		i;
+    String	cmd;
+    
+    size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
+
+    m_CommandHistory.clear();
+    for (i = 0; i < size; i++) {
+      cmd = PROPERTIES.getProperty("Command" + i, "");
+      if (cmd.length() != 0)
+	m_CommandHistory.add(cmd);
+      else 
+	break;
+    }
+    
+    m_HistoryPos = m_CommandHistory.size();
+  }
+  
+  /**
+   * saves the current command history in the user's home directory.
+   */
+  protected void saveHistory() {
+    int 			size;
+    int				from;
+    int				i;
+    String			filename;
+    BufferedOutputStream	stream;
+    
+    size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
+    
+    // determine first command to save
+    from = m_CommandHistory.size() - size;
+    if (from < 0)
+      from = 0;
+
+    // fill properties
+    PROPERTIES.setProperty("HistorySize", "" + size);
+    for (i = from; i < m_CommandHistory.size(); i++)
+      PROPERTIES.setProperty("Command" + (i-from), (String) m_CommandHistory.get(i));
+    
+    try {
+      filename = System.getProperties().getProperty("user.home") + File.separatorChar + FILENAME;
+      stream = new BufferedOutputStream(new FileOutputStream(filename));
+      PROPERTIES.store(stream, "SimpleCLI");
+      stream.close();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Displays the panel in a frame.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    showPanel(new SimpleCLIPanel(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SimpleDateFormatEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SimpleDateFormatEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SimpleDateFormatEditor.java	(revision 29)
@@ -0,0 +1,315 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SimpleDateFormatEditor.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyEditor;
+import java.text.SimpleDateFormat;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+/**
+ * Class for editing SimpleDateFormat strings. 
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ * @see SimpleDateFormat
+ */
+public class SimpleDateFormatEditor 
+  implements PropertyEditor {
+
+  /** the default format */
+  public final static String DEFAULT_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+  
+  /** The date format being edited */
+  private SimpleDateFormat m_Format;
+
+  /** A helper class for notifying listeners */
+  private PropertyChangeSupport m_propSupport;
+
+  /** An instance of the custom editor */
+  private CustomEditor m_customEditor;
+
+  /**
+   * This class presents a GUI for editing the cost matrix, and saving and 
+   * loading from files.
+   */
+  private class CustomEditor 
+    extends JPanel 
+    implements ActionListener, DocumentListener {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4018834274636309987L;
+    
+    /** The text field for the format */
+    private JTextField m_FormatText;
+    
+    /** The button for setting a default date format */
+    private JButton m_DefaultButton;
+
+    /** The button for applying the format */
+    private JButton m_ApplyButton;
+
+    /**
+     * Constructs a new CustomEditor.
+     */
+    public CustomEditor() {
+      m_FormatText    = new JTextField(20);
+      m_DefaultButton = new JButton("Default");
+      m_ApplyButton   = new JButton("Apply");
+
+      m_DefaultButton.setMnemonic('D');
+      m_ApplyButton.setMnemonic('A');
+
+      m_FormatText.getDocument().addDocumentListener(this);
+      m_DefaultButton.addActionListener(this);
+      m_ApplyButton.addActionListener(this);
+
+      setLayout(new FlowLayout());
+      add(new JLabel("ISO 8601 Date format"));
+      add(m_FormatText);
+      add(m_DefaultButton);
+      add(m_ApplyButton);
+    }
+
+    /**
+     * Responds to the user perfoming an action.
+     *
+     * @param e the action event that occured
+     */
+    public void actionPerformed(ActionEvent e) {
+      if (e.getSource() == m_DefaultButton)
+	defaultFormat();
+      else if (e.getSource() == m_ApplyButton)
+	applyFormat();
+    }
+
+    /**
+     * sets the format to default 
+     */
+    public void defaultFormat() {
+      m_FormatText.setText(DEFAULT_FORMAT);
+      formatChanged();
+    }
+
+    /**
+     * returns true if the current format is a valid format
+     */
+    protected boolean isValidFormat() {
+      boolean 	result;
+      
+      result = false;
+      
+      try {
+	new SimpleDateFormat(m_FormatText.getText());
+	result = true;
+      }
+      catch (Exception e) {
+	// we can ignore this exception
+      }
+      
+      return result;
+    }
+    
+    /**
+     * sets the format, but only if it's a valid one
+     */
+    public void applyFormat() {
+      if (isValidFormat()) {
+	m_Format = new SimpleDateFormat(m_FormatText.getText());
+	m_propSupport.firePropertyChange(null, null, null);
+      }
+      else {
+	throw new IllegalArgumentException(
+	    "Date format '" 
+	    + m_FormatText.getText() 
+	    + "' is invalid! Cannot execute applyFormat!");
+      }
+    }
+    
+    /**
+     * Responds to a change of the text field.
+     */
+    public void formatChanged() {
+      m_FormatText.setText(m_Format.toPattern());
+      m_propSupport.firePropertyChange(null, null, null);
+    }
+    
+    /**
+     * Gives notification that an attribute or set of attributes changed.
+     */
+    public void changedUpdate(DocumentEvent e) {
+      m_ApplyButton.setEnabled(isValidFormat());
+    }
+    
+    /**
+     * Gives notification that there was an insert into the document.
+     */
+    public void insertUpdate(DocumentEvent e) {
+      m_ApplyButton.setEnabled(isValidFormat());
+    }
+    
+    /**
+     * Gives notification that a portion of the document has been removed.
+     */
+    public void removeUpdate(DocumentEvent e) {
+      m_ApplyButton.setEnabled(isValidFormat());
+    }
+  }
+
+  /**
+   * Constructs a new SimpleDateFormatEditor.
+   *
+   */
+  public SimpleDateFormatEditor() {
+    m_propSupport = new PropertyChangeSupport(this);
+    m_customEditor = new CustomEditor();
+  }
+
+  /**
+   * Sets the value of the date format to be edited.
+   *
+   * @param value a SimpleDateFormat object to be edited
+   */
+  public void setValue(Object value) {
+    m_Format = (SimpleDateFormat) value;
+    m_customEditor.formatChanged();
+  }
+
+  /**
+   * Gets the date format that is being edited.
+   *
+   * @return the edited SimpleDateFormat object
+   */  
+  public Object getValue() {
+    return m_Format;
+  }
+
+  /**
+   * Indicates whether the object can be represented graphically. In this case
+   * it can.
+   *
+   * @return true
+   */  
+  public boolean isPaintable() {
+    return true;
+  }
+
+  /**
+   * Paints a graphical representation of the object. It just prints the 
+   * format.
+   *
+   * @param gfx the graphics context to draw the representation to
+   * @param box the bounds within which the representation should fit.
+   */    
+  public void paintValue(Graphics gfx,
+			 Rectangle box) {
+    gfx.drawString(m_Format.toPattern(), box.x, box.y + box.height);
+  }
+
+  /**
+   * Returns the Java code that generates an object the same as the one being edited.
+   *
+   * @return the initialization string
+   */   
+  public String getJavaInitializationString() {
+    return ("new SimpleDateFormat(" + m_Format.toPattern() + ")");
+  }
+
+  /**
+   * Returns the date format string.
+   *
+   * @return the date format string
+   */   
+  public String getAsText() {
+    return m_Format.toPattern();
+  }
+
+  /**
+   * Sets the date format string.
+   *
+   * @param text the date format string
+   */   
+  public void setAsText(String text) {
+    m_Format = new SimpleDateFormat(text);
+  }
+
+  /**
+   * Some objects can return tags, but a date format cannot.
+   *
+   * @return null
+   */  
+  public String[] getTags() {
+    return null;
+  }
+
+  /**
+   * Gets a GUI component with which the user can edit the date format.
+   *
+   * @return an editor GUI component
+   */    
+  public Component getCustomEditor() {
+    return m_customEditor;
+  }
+
+  /**
+   * Indicates whether the date format can be edited in a GUI, which it can.
+   *
+   * @return true
+   */     
+  public boolean supportsCustomEditor() {
+    return true;
+  }
+
+  /**
+   * Adds an object to the list of those that wish to be informed when the
+   * date format changes.
+   *
+   * @param listener a new listener to add to the list
+   */   
+  public void addPropertyChangeListener(PropertyChangeListener listener) {
+    m_propSupport.addPropertyChangeListener(listener);
+  }
+
+  /**
+   * Removes an object from the list of those that wish to be informed when the
+   * date format changes.
+   *
+   * @param listener the listener to remove from the list
+   */  
+  public void removePropertyChangeListener(PropertyChangeListener listener) {
+    m_propSupport.removePropertyChangeListener(listener);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SortedTableModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SortedTableModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SortedTableModel.java	(revision 29)
@@ -0,0 +1,417 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SortedTableModel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import weka.core.ClassDiscovery;
+
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Date;
+
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Represents a TableModel with sorting functionality.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+
+public class SortedTableModel
+  extends AbstractTableModel
+  implements TableModelListener {
+
+  /** for serialization */
+  static final long serialVersionUID = 4030907921461127548L;
+  
+  /** the actual table model */
+  protected TableModel mModel;
+
+  /** the mapping between displayed and actual index */
+  protected int[] mIndices;
+
+  /** the sort column */
+  protected int mSortColumn;
+
+  /** whether sorting is ascending or descending */
+  protected boolean mAscending;
+  
+  /**
+   * initializes with no model
+   */
+  public SortedTableModel() {
+    this(null);
+  }
+
+  /**
+   * initializes with the given model
+   *
+   * @param model       the model to initialize the sorted model with
+   */
+  public SortedTableModel(TableModel model) {
+    setModel(model);
+  }
+
+  /**
+   * sets the model to use
+   *
+   * @param value       the model to use
+   */
+  public void setModel(TableModel value) {
+    mModel = value;
+
+    // initialize indices
+    if (mModel == null) {
+      mIndices = null;
+    }
+    else {
+      initializeIndices();
+      mSortColumn = -1;
+      mAscending  = true;
+      mModel.addTableModelListener(this);
+    }
+  }
+
+  /**
+   * (re-)initializes the indices
+   */
+  protected void initializeIndices() {
+    int       i;
+
+    mIndices = new int[mModel.getRowCount()];
+    for (i = 0; i < mIndices.length; i++)
+      mIndices[i] = i;
+  }
+
+  /**
+   * returns the current model, can be null
+   *
+   * @return            the current model
+   */
+  public TableModel getModel() {
+    return mModel;
+  }
+
+  /**
+   * returns whether the table was sorted
+   *
+   * @return        true if the table was sorted
+   */
+  public boolean isSorted() {
+    return (mSortColumn > -1);
+  }
+
+  /**
+   * whether the model is initialized
+   *
+   * @return            true if the model is not null and the sort indices
+   *                    match the number of rows
+   */
+  protected boolean isInitialized() {
+    return (getModel() != null);
+  }
+
+  /**
+   * Returns the actual underlying row the given visible one represents. Useful
+   * for retrieving "non-visual" data that is also stored in a TableModel.
+   * 
+   * @param visibleRow	the displayed row to retrieve the original row for
+   * @return		the original row
+   */
+  public int getActualRow(int visibleRow) {
+    if (!isInitialized())
+      return -1;
+    else
+      return mIndices[visibleRow];
+  }
+  
+  /**
+   * Returns the most specific superclass for all the cell values in the
+   * column.
+   *
+   * @param columnIndex     the index of the column
+   * @return                the class of the specified column
+   */
+  public Class getColumnClass(int columnIndex) {
+    if (!isInitialized())
+      return null;
+    else
+      return getModel().getColumnClass(columnIndex);
+  }
+
+  /**
+   * Returns the number of columns in the model
+   *
+   * @return          the number of columns in the model
+   */
+  public int getColumnCount() {
+    if (!isInitialized())
+      return 0;
+    else
+      return getModel().getColumnCount();
+  }
+
+  /**
+   * Returns the name of the column at columnIndex
+   *
+   * @param columnIndex   the column to retrieve the name for
+   * @return              the name of the specified column
+   */
+  public String getColumnName(int columnIndex) {
+    if (!isInitialized())
+      return null;
+    else
+      return getModel().getColumnName(columnIndex);
+  }
+
+  /**
+   * Returns the number of rows in the model.
+   *
+   * @return              the number of rows in the model
+   */
+  public int getRowCount() {
+    if (!isInitialized())
+      return 0;
+    else
+      return getModel().getRowCount();
+  }
+
+  /**
+   * Returns the value for the cell at columnIndex and rowIndex.
+   *
+   * @param rowIndex      the row
+   * @param columnIndex   the column
+   * @return              the value of the sepcified cell
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    if (!isInitialized())
+      return null;
+    else
+      return getModel().getValueAt(mIndices[rowIndex], columnIndex);
+  }
+
+  /**
+   * Returns true if the cell at rowIndex and columnIndex is editable.
+   *
+   * @param rowIndex      the row
+   * @param columnIndex   the column
+   * @return              true if the cell is editable
+   */
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    if (!isInitialized())
+      return false;
+    else
+      return getModel().isCellEditable(mIndices[rowIndex], columnIndex);
+  }
+
+  /**
+   * Sets the value in the cell at columnIndex and rowIndex to aValue.
+   *
+   * @param aValue        the new value of the cell
+   * @param rowIndex      the row
+   * @param columnIndex   the column
+   */
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+    if (isInitialized())
+      getModel().setValueAt(aValue, mIndices[rowIndex], columnIndex);
+  }
+
+  /**
+   * sorts the table over the given column (ascending)
+   *
+   * @param columnIndex     the column to sort over
+   */
+  public void sort(int columnIndex) {
+    sort(columnIndex, true);
+  }
+
+  /**
+   * sorts the table over the given column, either ascending or descending
+   *
+   * @param columnIndex     the column to sort over
+   * @param ascending       ascending if true, otherwise descending
+   */
+  public void sort(int columnIndex, boolean ascending) {
+    int       columnType;
+    int       i;
+    int       n;
+    int       index;
+    int       backup;
+
+    // can we sort?
+    if (    (!isInitialized())
+         || (getModel().getRowCount() != mIndices.length) ) {
+
+      System.out.println(
+          this.getClass().getName() + ": Table model not initialized!");
+
+      return;
+    }
+
+    // init
+    mSortColumn = columnIndex;
+    mAscending  = ascending;
+    initializeIndices();
+    
+    // determine the column type: 0=string/other, 1=number, 2=date
+    if (ClassDiscovery.isSubclass(Number.class, getColumnClass(mSortColumn)))
+      columnType = 1;
+    else if (ClassDiscovery.isSubclass(Date.class, getColumnClass(mSortColumn)))
+      columnType = 2;
+    else
+      columnType = 0;
+
+    // sort ascending (descending is done below)
+    for (i = 0; i < getRowCount() - 1; i++) {
+      index = i;
+      for (n = i + 1; n < getRowCount(); n++) {
+        if (compare(mIndices[index], mIndices[n], mSortColumn, columnType) > 0)
+          index = n;
+      }
+
+      // found smaller one?
+      if (index != i) {
+        backup          = mIndices[i];
+        mIndices[i]     = mIndices[index];
+        mIndices[index] = backup;
+      }
+    }
+
+    // reverse sorting?
+    if (!mAscending) {
+      for (i = 0; i < getRowCount() / 2; i++) {
+        backup                          = mIndices[i];
+        mIndices[i]                     = mIndices[getRowCount() - i - 1];
+        mIndices[getRowCount() - i - 1] = backup;
+      }
+    }
+  }
+
+  /**
+   * compares two cells, returns -1 if cell1 is less than cell2, 0 if equal
+   * and +1 if greater.
+   *
+   * @param row1        row index of cell1
+   * @param row2        row index of cell2
+   * @param col         colunm index
+   * @param type        the class type: 0=string/other, 1=number, 2=date
+   * @return            -1 if cell1&lt;cell2, 0 if cell1=cell2, +1 if 
+   *                    cell1&gt;cell2
+   */
+  protected int compare(int row1, int row2, int col, int type) {
+    int           result;
+    Object        o1;
+    Object        o2;
+    Double        d1;
+    Double        d2;
+
+    o1 = getModel().getValueAt(row1, col);
+    o2 = getModel().getValueAt(row2, col);
+
+    // null is always smaller than non-null values
+    if ( (o1 == null) &&  (o2 == null) ) {
+      result = 0;
+    }
+    else if (o1 == null) {
+      result = -1;
+    }
+    else if (o2 == null) {
+      result = 1;
+    }
+    else {
+      switch (type) {
+        // number
+        case 1:
+          d1 = new Double(((Number) o1).doubleValue());
+          d2 = new Double(((Number) o2).doubleValue());
+          result = d1.compareTo(d2);
+          break;
+          
+        // date
+        case 2:
+          result = ((Date) o1).compareTo((Date) o2);
+          break;
+          
+        // string
+        default:
+          result = o1.toString().compareTo(o2.toString());
+          break;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * This fine grain notification tells listeners the exact range of cells,
+   * rows, or columns that changed.
+   *
+   * @param e       the event
+   */
+  public void tableChanged(TableModelEvent e) {
+    initializeIndices();
+    if (isSorted())
+      sort(mSortColumn, mAscending);
+    
+    fireTableChanged(e);
+  }
+  
+  /**
+   * adds a mouselistener to the header
+   *
+   * @param table       the table to add the listener to
+   */
+  public void addMouseListenerToHeader(JTable table) {
+    final SortedTableModel modelFinal = this;
+    final JTable tableFinal = table;
+    tableFinal.setColumnSelectionAllowed(false);
+    JTableHeader header = tableFinal.getTableHeader();
+
+    if (header != null) {
+      MouseAdapter listMouseListener = new MouseAdapter() {
+        public void mouseClicked(MouseEvent e) {
+          TableColumnModel columnModel = tableFinal.getColumnModel();
+          int viewColumn = columnModel.getColumnIndexAtX(e.getX());
+          int column = tableFinal.convertColumnIndexToModel(viewColumn);
+          if (    e.getButton() == MouseEvent.BUTTON1 
+               && e.getClickCount() == 1 
+               && !e.isAltDown()
+               && column != -1 ) {
+            int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK;
+            boolean ascending = (shiftPressed == 0);
+            modelFinal.sort(column, ascending);
+          }
+        }
+      };
+      
+      header.addMouseListener(listMouseListener);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SplashWindow.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SplashWindow.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SplashWindow.java	(revision 29)
@@ -0,0 +1,258 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * @(#)SplashWindow.java  2.2  2005-04-03
+ *
+ * Copyright (c) 2003-2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is in the public domain.
+ *
+ */
+
+package weka.gui;
+
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.net.URL;
+
+/**
+ * A Splash window.
+ *  <p>
+ * Usage: MyApplication is your application class. Create a Splasher class which
+ * opens the splash window, invokes the main method of your Application class,
+ * and disposes the splash window afterwards.
+ * Please note that we want to keep the Splasher class and the SplashWindow class
+ * as small as possible. The less code and the less classes must be loaded into
+ * the JVM to open the splash screen, the faster it will appear.
+ * <pre>
+ * class Splasher {
+ *    public static void main(String[] args) {
+ *         SplashWindow.splash(Startup.class.getResource("splash.gif"));
+ *         MyApplication.main(args);
+ *         SplashWindow.disposeSplash();
+ *    }
+ * }
+ * </pre>
+ *
+ * @author  Werner Randelshofer
+ * @author  Mark Hall
+ * @version $Revision: 1.3 $ 
+ */
+public class SplashWindow
+  extends Window {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2685134277041307795L;
+  
+  /**
+   * The current instance of the splash window.
+   * (Singleton design pattern).
+   */
+  private static SplashWindow m_instance;
+    
+  /**
+   * The splash image which is displayed on the splash window.
+   */
+  private Image image;
+    
+  /**
+   * This attribute indicates whether the method
+   * paint(Graphics) has been called at least once since the
+   * construction of this window.<br>
+   * This attribute is used to notify method splash(Image)
+   * that the window has been drawn at least once
+   * by the AWT event dispatcher thread.<br>
+   * This attribute acts like a latch. Once set to true,
+   * it will never be changed back to false again.
+   *
+   * @see #paint
+   * @see #splash
+   */
+  private boolean paintCalled = false;
+    
+  /**
+   * Creates a new instance.
+   * @param parent the parent of the window.
+   * @param image the splash image.
+   */
+  private SplashWindow(Frame parent, Image image) {
+    super(parent);
+    this.image = image;
+
+    // Load the image
+    MediaTracker mt = new MediaTracker(this);
+    mt.addImage(image,0);
+    try {
+      mt.waitForID(0);
+    } catch(InterruptedException ie){
+    }
+        
+    // Center the window on the screen
+    int imgWidth = image.getWidth(this);
+    int imgHeight = image.getHeight(this);
+    setSize(imgWidth, imgHeight);
+    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
+    setLocation(
+                (screenDim.width - imgWidth) / 2,
+                (screenDim.height - imgHeight) / 2
+                );
+
+        
+    // Users shall be able to close the splash window by
+    // clicking on its display area. This mouse listener
+    // listens for mouse clicks and disposes the splash window.
+    MouseAdapter disposeOnClick = new MouseAdapter() {
+        public void mouseClicked(MouseEvent evt) {
+          // Note: To avoid that method splash hangs, we
+          // must set paintCalled to true and call notifyAll.
+          // This is necessary because the mouse click may
+          // occur before the contents of the window
+          // has been painted.
+          synchronized(SplashWindow.this) {
+            SplashWindow.this.paintCalled = true;
+            SplashWindow.this.notifyAll();
+          }
+          dispose();
+        }
+      };
+    addMouseListener(disposeOnClick);
+  }
+    
+  /**
+   * Updates the display area of the window.
+   */
+  public void update(Graphics g) {
+    // Note: Since the paint method is going to draw an
+    // image that covers the complete area of the component we
+    // do not fill the component with its background color
+    // here. This avoids flickering.
+
+    paint(g);
+  } 
+
+  /**
+   * Paints the image on the window.
+   */
+  public void paint(Graphics g) {
+    g.drawImage(image, 0, 0, this);
+    // Notify method splash that the window
+    // has been painted.
+    // Note: To improve performance we do not enter
+    // the synchronized block unless we have to.
+    if (! paintCalled) {
+      paintCalled = true;
+      synchronized (this) { notifyAll(); }
+    }
+  }
+    
+  /**
+   * Open's a splash window using the specified image.
+   * @param image The splash image.
+   */
+  public static void splash(Image image) {
+    if (m_instance == null && image != null) {
+      Frame f = new Frame();
+            
+      // Create the splash image
+      m_instance = new SplashWindow(f, image);
+            
+      // Show the window.
+      m_instance.show();
+            
+      // Note: To make sure the user gets a chance to see the
+      // splash window we wait until its paint method has been
+      // called at least once by the AWT event dispatcher thread.
+      // If more than one processor is available, we don't wait,
+      // and maximize CPU throughput instead.
+      if (!EventQueue.isDispatchThread() 
+            &&  Runtime.getRuntime().availableProcessors() == 1) {
+        synchronized (m_instance) {
+          while (! m_instance.paintCalled) {
+            try { m_instance.wait(); } catch (InterruptedException e) {}
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Open's a splash window using the specified image.
+   * @param imageURL The url of the splash image.
+   */
+  public static void splash(URL imageURL) {
+    if (imageURL != null) {
+      splash(Toolkit.getDefaultToolkit().createImage(imageURL));
+    }
+  }
+    
+  /**
+   * Closes the splash window.
+   */
+  public static void disposeSplash() {
+    if (m_instance != null) {
+      m_instance.getOwner().dispose();
+      m_instance = null;
+    }
+  }
+    
+  /**
+   * Invokes the named method of the provided class name.
+   * @param className the name of the class
+   * @param methodName the name of the method to invoke
+   * @param args the command line arguments
+   */
+  public static void invokeMethod(String className, String methodName, 
+                                  String [] args) {
+    try {
+      Class.forName(className)
+        .getMethod(methodName, new Class[] {String[].class})
+        .invoke(null, new Object[] {args});
+    } catch (Exception e) {
+      InternalError error = new InternalError("Failed to invoke method: "
+                                              +methodName);
+      error.initCause(e);
+      throw error;
+    }
+  }
+
+  /**
+   * Invokes the main method of the provided class name.
+   * @param className the name of the class
+   * @param args the command line arguments
+   */
+  public static void invokeMain(String className, String[] args) {
+    try {
+      Class.forName(className)
+        .getMethod("main", new Class[] {String[].class})
+        .invoke(null, new Object[] {args});
+    } catch (Exception e) {
+      InternalError error = new InternalError("Failed to invoke main method");
+      error.initCause(e);
+      throw error;
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/SysErrLog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/SysErrLog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/SysErrLog.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SysErrLog.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/** 
+ * This Logger just sends messages to System.err.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class SysErrLog implements Logger {
+
+  /**
+   * Gets a string containing current date and time.
+   *
+   * @return a string containing the date and time.
+   */
+  protected static String getTimestamp() {
+
+    return (new SimpleDateFormat("yyyy.MM.dd hh:mm:ss")).format(new Date());
+  }
+
+  /**
+   * Sends the supplied message to the log area. The current timestamp will
+   * be prepended.
+   *
+   * @param message a value of type 'String'
+   */
+  public void logMessage(String message) {
+    
+    System.err.println("LOG " + SysErrLog.getTimestamp() + ": "
+		       + message);
+  }
+
+  /**
+   * Sends the supplied message to the status line.
+   *
+   * @param message the status message
+   */
+  public void statusMessage(String message) {
+
+    System.err.println("STATUS: " + message);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/TaskLogger.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/TaskLogger.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/TaskLogger.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TaskLogger.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+/** 
+ * Interface for objects that display log and display information on
+ * running tasks.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface TaskLogger {
+  
+  /**
+   * Tells the task logger that a new task has been started
+   */
+  void taskStarted();
+
+  /**
+   * Tells the task logger that a task has completed
+   */
+  void taskFinished();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ViewerDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ViewerDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ViewerDialog.java	(revision 29)
@@ -0,0 +1,196 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    ViewerDialog.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui;
+
+import weka.core.Instances;
+import weka.gui.arffviewer.ArffPanel;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * A downsized version of the ArffViewer, displaying only one Instances-Object.
+ *
+ *
+ * @see weka.gui.arffviewer.ArffViewer
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $ 
+ */
+public class ViewerDialog 
+  extends JDialog 
+  implements ChangeListener { 
+
+  /** for serialization */
+  private static final long serialVersionUID = 6747718484736047752L;
+  
+  /** Signifies an OK property selection */
+  public static final int APPROVE_OPTION = 0;
+
+  /** Signifies a cancelled property selection */
+  public static final int CANCEL_OPTION = 1;
+
+  /** the result of the user's action, either OK or CANCEL */
+  protected int m_Result = CANCEL_OPTION;
+  
+  /** Click to activate the current set parameters */
+  protected JButton m_OkButton = new JButton("OK");
+
+  /** Click to cancel the dialog */
+  protected JButton m_CancelButton = new JButton("Cancel");
+
+  /** Click to undo the last action */
+  protected JButton m_UndoButton = new JButton("Undo");
+  
+  /** the panel to display the Instances-object */
+  protected ArffPanel m_ArffPanel = new ArffPanel();
+  
+  /**
+   * initializes the dialog with the given parent
+   * 
+   * @param parent the parent for this dialog
+   */
+  public ViewerDialog(Frame parent) {
+    super(parent, true);
+    createDialog();
+  }
+
+  /**
+   * creates all the elements of the dialog
+   */
+  protected void createDialog() {
+    JPanel              panel;
+
+    setTitle("Viewer");
+    
+    getContentPane().setLayout(new BorderLayout());
+    
+    // ArffPanel
+    m_ArffPanel.addChangeListener(this);
+    getContentPane().add(m_ArffPanel, BorderLayout.CENTER);
+    
+    // Buttons
+    panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+    getContentPane().add(panel, BorderLayout.SOUTH);
+    m_UndoButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        undo();
+      }
+    });
+    getContentPane().add(panel, BorderLayout.SOUTH);
+    m_CancelButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_Result = CANCEL_OPTION;
+        setVisible(false);
+      }
+    });
+    m_OkButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_Result = APPROVE_OPTION;
+        setVisible(false);
+      }
+    });
+    panel.add(m_UndoButton);
+    panel.add(m_OkButton);
+    panel.add(m_CancelButton);
+
+    pack();
+  }
+  
+  /**
+   * sets the instances to display
+   */
+  public void setInstances(Instances inst) {
+    m_ArffPanel.setInstances(new Instances(inst));
+  }
+  
+  /**
+   * returns the currently displayed instances
+   */
+  public Instances getInstances() {
+    return m_ArffPanel.getInstances();
+  }
+  
+  /**
+   * sets the state of the buttons 
+   */
+  protected void setButtons() {
+    m_OkButton.setEnabled(true); 
+    m_CancelButton.setEnabled(true); 
+    m_UndoButton.setEnabled(m_ArffPanel.canUndo()); 
+  }
+  
+  /**
+   * returns whether the data has been changed
+   * 
+   * @return true if the data has been changed
+   */
+  public boolean isChanged() {
+    return m_ArffPanel.isChanged();
+  }
+  
+  /**
+   * undoes the last action 
+   */
+  private void undo() {
+    m_ArffPanel.undo();
+  }
+
+  /**
+   * Invoked when the target of the listener has changed its state.
+   */
+  public void stateChanged(ChangeEvent e) {
+    setButtons();
+  }
+  
+  /**
+   * Pops up the modal dialog and waits for Cancel or OK.
+   *
+   * @return either APPROVE_OPTION, or CANCEL_OPTION
+   */
+  public int showDialog() {
+    m_Result = CANCEL_OPTION;
+    setVisible(true);
+    setButtons();
+    return m_Result;
+  }
+
+  /**
+   * Pops up the modal dialog and waits for Cancel or OK.
+   *
+   * @param inst the instances to display
+   * @return either APPROVE_OPTION, or CANCEL_OPTION
+   */
+  public int showDialog(Instances inst) {
+    setInstances(inst);
+    return showDialog();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/WekaTaskMonitor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/WekaTaskMonitor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/WekaTaskMonitor.java	(revision 29)
@@ -0,0 +1,167 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WekaTaskMonitor.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Toolkit;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+/** 
+ * This panel records the number of weka tasks running and displays a
+ * simple bird animation while their are active tasks
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5198 $
+ */
+public class WekaTaskMonitor
+  extends JPanel
+  implements TaskLogger {
+
+  /** for serialization */
+  private static final long serialVersionUID = 508309816292197578L;
+
+  /** The number of running weka threads */
+  private int m_ActiveTasks = 0;
+
+  /** The label for displaying info */
+  private JLabel m_MonitorLabel;
+
+  /** The icon for the stationary bird */
+  private ImageIcon m_iconStationary;
+
+  /** The icon for the animated bird */
+  private ImageIcon m_iconAnimated;
+
+  /** True if their are active tasks */
+  private boolean m_animating = false;
+  
+  /**
+   * Constructor
+   */
+  public WekaTaskMonitor() {
+    java.net.URL imageURL = 
+      this.getClass().getClassLoader().getResource("weka/gui/weka_stationary.gif");
+
+    if (imageURL != null) {
+      Image pic = Toolkit.getDefaultToolkit().getImage(imageURL);
+      imageURL = 
+        this.getClass().getClassLoader().getResource("weka/gui/weka_animated.gif");
+      Image pic2 = Toolkit.getDefaultToolkit().getImage(imageURL); 
+    
+      /*    Image pic = Toolkit.getDefaultToolkit().
+            getImage(ClassLoader.getSystemResource("weka/gui/weka_stationary.gif"));
+            Image pic2 = Toolkit.getDefaultToolkit().
+            getImage(ClassLoader.getSystemResource("weka/gui/weka_animated.gif")); */
+
+      m_iconStationary = new ImageIcon(pic); 
+      m_iconAnimated = new ImageIcon(pic2);
+    }
+    
+    m_MonitorLabel = new JLabel(" x "+m_ActiveTasks,m_iconStationary,SwingConstants.CENTER);
+    /*
+    setBorder(BorderFactory.createCompoundBorder(
+  	      BorderFactory.createTitledBorder("Weka Tasks"),
+  	      BorderFactory.createEmptyBorder(0, 5, 5, 5)
+  	      ));
+    */
+    setLayout(new BorderLayout());
+    Dimension d = m_MonitorLabel.getPreferredSize();
+    m_MonitorLabel.setPreferredSize(new Dimension(d.width+15,d.height));
+    m_MonitorLabel.setMinimumSize(new Dimension(d.width+15,d.height));
+    add(m_MonitorLabel, BorderLayout.CENTER);
+    
+
+  }
+
+  /**
+   * Tells the panel that a new task has been started
+   */
+  public synchronized void taskStarted() {
+    m_ActiveTasks++;
+    updateMonitor();
+  }
+
+  /**
+   * Tells the panel that a task has completed
+   */
+  public synchronized void taskFinished() {
+    m_ActiveTasks--;
+    if (m_ActiveTasks < 0) {
+      m_ActiveTasks = 0;
+    }
+    updateMonitor();
+  }
+
+  /**
+   * Updates the number of running tasks an the status of the bird
+   * image
+   */
+  private void updateMonitor() {
+    m_MonitorLabel.setText(" x "+m_ActiveTasks);
+    if (m_ActiveTasks > 0 && !m_animating) {
+      m_MonitorLabel.setIcon(m_iconAnimated);
+      m_animating = true;
+    }
+
+    if (m_ActiveTasks == 0 && m_animating) {
+      m_MonitorLabel.setIcon(m_iconStationary);
+      m_animating = false;
+    }
+  }
+
+  /**
+   * Main method for testing this class
+   */
+  public static void main(String [] args) {
+    
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new BorderLayout());
+      final WekaTaskMonitor tm = new WekaTaskMonitor();
+      tm.setBorder(BorderFactory.createCompoundBorder(
+  	           BorderFactory.createTitledBorder("Weka Tasks"),
+  	           BorderFactory.createEmptyBorder(0, 5, 5, 5)
+  	           ));
+      jf.getContentPane().add(tm, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      tm.taskStarted();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffPanel.java	(revision 29)
@@ -0,0 +1,1040 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Instances;
+import weka.core.Undoable;
+import weka.core.Utils;
+import weka.gui.ComponentHelper;
+import weka.gui.JTableHelper;
+import weka.gui.ListSelectorDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.TableModelEvent;
+
+/**
+ * A Panel representing an ARFF-Table and the associated filename.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4769 $ 
+ */
+
+public class ArffPanel 
+  extends JPanel
+  implements ActionListener, ChangeListener, MouseListener, Undoable {
+  
+  /** for serialization */
+  static final long serialVersionUID = -4697041150989513939L;
+  
+  /** the name of the tab for instances that were set directly */
+  public final static String TAB_INSTANCES = "Instances";
+
+  /** the underlying table */
+  private ArffTable m_TableArff;
+  /** the popup menu for the header row */
+  private JPopupMenu m_PopupHeader;
+  /** the popup menu for the data rows */
+  private JPopupMenu m_PopupRows;
+  /** displays the relation name */
+  private JLabel m_LabelName;
+  
+  // menu items
+  private JMenuItem             menuItemMean;
+  private JMenuItem             menuItemSetAllValues;
+  private JMenuItem             menuItemSetMissingValues;
+  private JMenuItem             menuItemReplaceValues;
+  private JMenuItem             menuItemRenameAttribute;
+  private JMenuItem             menuItemAttributeAsClass;
+  private JMenuItem             menuItemDeleteAttribute;
+  private JMenuItem             menuItemDeleteAttributes;
+  private JMenuItem             menuItemSortInstances;
+  private JMenuItem             menuItemDeleteSelectedInstance;
+  private JMenuItem             menuItemDeleteAllSelectedInstances;
+  private JMenuItem             menuItemSearch;
+  private JMenuItem             menuItemClearSearch;
+  private JMenuItem             menuItemUndo;
+  private JMenuItem             menuItemCopy;
+  private JMenuItem             menuItemOptimalColWidth;
+  private JMenuItem             menuItemOptimalColWidths;
+  
+  /** the filename used in the title */
+  private String m_Filename;
+  /** the title prefix */
+  private String m_Title;
+  /** the currently selected column */
+  private int m_CurrentCol;
+  /** flag for whether data got changed */
+  private boolean m_Changed;
+  /** the listeners that listen for modifications */
+  private HashSet m_ChangeListeners;
+  /** the string used in the last search */
+  private String m_LastSearch;
+  /** the string used in the last replace */
+  private String m_LastReplace;
+  
+  /**
+   * initializes the panel with no data
+   */
+  public ArffPanel() {
+    super();
+    
+    initialize();
+    createPanel();
+  }
+  
+  /**
+   * initializes the panel and loads the specified file
+   * 
+   * @param filename	the file to load
+   */
+  public ArffPanel(String filename) {
+    this();
+    
+    loadFile(filename);
+  }
+  
+  /**
+   * initializes the panel with the given data
+   * 
+   * @param data	the data to use
+   */
+  public ArffPanel(Instances data) {
+    this();
+    
+    m_Filename = "";
+    
+    setInstances(data);
+  }
+  
+  /**
+   * any member variables are initialized here
+   */
+  protected void initialize() {
+    m_Filename        = "";
+    m_Title           = "";
+    m_CurrentCol      = -1;
+    m_LastSearch      = "";
+    m_LastReplace     = "";
+    m_Changed         = false;
+    m_ChangeListeners = new HashSet();
+  }
+  
+  /**
+   * creates all the components in the frame
+   */
+  protected void createPanel() {
+    JScrollPane                pane;
+    
+    setLayout(new BorderLayout());
+    
+    menuItemMean = new JMenuItem("Get mean...");
+    menuItemMean.addActionListener(this);
+    menuItemSetAllValues = new JMenuItem("Set all values to...");
+    menuItemSetAllValues.addActionListener(this);
+    menuItemSetMissingValues = new JMenuItem("Set missing values to...");
+    menuItemSetMissingValues.addActionListener(this);
+    menuItemReplaceValues = new JMenuItem("Replace values with...");
+    menuItemReplaceValues.addActionListener(this);
+    menuItemRenameAttribute = new JMenuItem("Rename attribute...");
+    menuItemRenameAttribute.addActionListener(this);
+    menuItemAttributeAsClass = new JMenuItem("Attribute as class");
+    menuItemAttributeAsClass.addActionListener(this);
+    menuItemDeleteAttribute = new JMenuItem("Delete attribute");
+    menuItemDeleteAttribute.addActionListener(this);
+    menuItemDeleteAttributes = new JMenuItem("Delete attributes...");
+    menuItemDeleteAttributes.addActionListener(this);
+    menuItemSortInstances = new JMenuItem("Sort data (ascending)");
+    menuItemSortInstances.addActionListener(this);
+    menuItemOptimalColWidth = new JMenuItem("Optimal column width (current)");
+    menuItemOptimalColWidth.addActionListener(this);
+    menuItemOptimalColWidths = new JMenuItem("Optimal column width (all)");
+    menuItemOptimalColWidths.addActionListener(this);
+
+    // row popup
+    menuItemUndo = new JMenuItem("Undo");
+    menuItemUndo.addActionListener(this);
+    menuItemCopy = new JMenuItem("Copy");
+    menuItemCopy.addActionListener(this);
+    menuItemSearch = new JMenuItem("Search...");
+    menuItemSearch.addActionListener(this);
+    menuItemClearSearch = new JMenuItem("Clear search");
+    menuItemClearSearch.addActionListener(this);
+    menuItemDeleteSelectedInstance = new JMenuItem("Delete selected instance");
+    menuItemDeleteSelectedInstance.addActionListener(this);
+    menuItemDeleteAllSelectedInstances = new JMenuItem("Delete ALL selected instances");
+    menuItemDeleteAllSelectedInstances.addActionListener(this);
+    
+    // table
+    m_TableArff = new ArffTable();
+    m_TableArff.setToolTipText("Right click (or left+alt) for context menu");
+    m_TableArff.getTableHeader().addMouseListener(this);
+    m_TableArff.getTableHeader().setToolTipText("<html><b>Sort view:</b> left click = ascending / Shift + left click = descending<br><b>Menu:</b> right click (or left+alt)</html>");
+    m_TableArff.getTableHeader().setDefaultRenderer(new ArffTableCellRenderer());
+    m_TableArff.addChangeListener(this);
+    m_TableArff.addMouseListener(this);
+    pane = new JScrollPane(m_TableArff);
+    add(pane, BorderLayout.CENTER);
+    
+    // relation name
+    m_LabelName   = new JLabel();
+    add(m_LabelName, BorderLayout.NORTH);
+  }
+
+  /**
+   * initializes the popup menus
+   */
+  private void initPopupMenus() {
+    // header popup
+    m_PopupHeader  = new JPopupMenu();
+    m_PopupHeader.addMouseListener(this);
+    m_PopupHeader.add(menuItemMean);
+    if (!isReadOnly()) {
+      m_PopupHeader.addSeparator();
+      m_PopupHeader.add(menuItemSetAllValues);
+      m_PopupHeader.add(menuItemSetMissingValues);
+      m_PopupHeader.add(menuItemReplaceValues);
+      m_PopupHeader.addSeparator();
+      m_PopupHeader.add(menuItemRenameAttribute);
+      m_PopupHeader.add(menuItemAttributeAsClass);
+      m_PopupHeader.add(menuItemDeleteAttribute);
+      m_PopupHeader.add(menuItemDeleteAttributes);
+      m_PopupHeader.add(menuItemSortInstances);
+    }
+    m_PopupHeader.addSeparator();
+    m_PopupHeader.add(menuItemOptimalColWidth);
+    m_PopupHeader.add(menuItemOptimalColWidths);
+    
+    // row popup
+    m_PopupRows = new JPopupMenu();
+    m_PopupRows.addMouseListener(this);
+    if (!isReadOnly()) {
+      m_PopupRows.add(menuItemUndo);
+      m_PopupRows.addSeparator();
+    }
+    m_PopupRows.add(menuItemCopy);
+    m_PopupRows.addSeparator();
+    m_PopupRows.add(menuItemSearch);
+    m_PopupRows.add(menuItemClearSearch);
+    if (!isReadOnly()) {
+      m_PopupRows.addSeparator();
+      m_PopupRows.add(menuItemDeleteSelectedInstance);
+      m_PopupRows.add(menuItemDeleteAllSelectedInstances);
+    }
+  }
+  
+  /**
+   * sets the enabled/disabled state of the menu items 
+   */
+  private void setMenu() {
+    boolean			isNumeric;
+    boolean			hasColumns;
+    boolean			hasRows;
+    boolean			attSelected;
+    ArffSortedTableModel	model;
+    boolean			isNull;
+    
+    model       = (ArffSortedTableModel) m_TableArff.getModel();
+    isNull      = (model.getInstances() == null);
+    hasColumns  = !isNull && (model.getInstances().numAttributes() > 0);
+    hasRows     = !isNull && (model.getInstances().numInstances() > 0);
+    attSelected = hasColumns && (m_CurrentCol > 0);
+    isNumeric   = attSelected && (model.getAttributeAt(m_CurrentCol).isNumeric());
+    
+    menuItemUndo.setEnabled(canUndo());
+    menuItemCopy.setEnabled(true);
+    menuItemSearch.setEnabled(true);
+    menuItemClearSearch.setEnabled(true);
+    menuItemMean.setEnabled(isNumeric);
+    menuItemSetAllValues.setEnabled(attSelected);
+    menuItemSetMissingValues.setEnabled(attSelected);
+    menuItemReplaceValues.setEnabled(attSelected);
+    menuItemRenameAttribute.setEnabled(attSelected);
+    menuItemDeleteAttribute.setEnabled(attSelected);
+    menuItemDeleteAttributes.setEnabled(attSelected);
+    menuItemSortInstances.setEnabled(hasRows && attSelected);
+    menuItemDeleteSelectedInstance.setEnabled(hasRows && m_TableArff.getSelectedRow() > -1);
+    menuItemDeleteAllSelectedInstances.setEnabled(hasRows && (m_TableArff.getSelectedRows().length > 0));
+  }
+  
+  /**
+   * returns the table component
+   * 
+   * @return 		the table
+   */
+  public ArffTable getTable() {
+    return m_TableArff;
+  }
+  
+  /**
+   * returns the title for the Tab, i.e. the filename
+   * 
+   * @return 		the title for the tab
+   */
+  public String getTitle() {
+    return m_Title;
+  }
+  
+  /**
+   * returns the filename
+   * 
+   * @return		the filename
+   */
+  public String getFilename() {
+    return m_Filename;
+  }
+  
+  /**
+   * sets the filename
+   * 
+   * @param filename	the new filename
+   */
+  public void setFilename(String filename) {
+    m_Filename = filename;
+    createTitle();
+  }
+  
+  /**
+   * returns the instances of the panel, if none then NULL
+   * 
+   * @return		the instances of the panel
+   */
+  public Instances getInstances() {
+    Instances            result;
+    
+    result = null;
+    
+    if (m_TableArff.getModel() != null)
+      result = ((ArffSortedTableModel) m_TableArff.getModel()).getInstances();
+    
+    return result;
+  }
+  
+  /**
+   * displays the given instances, i.e. creates a tab with the title 
+   * TAB_INSTANCES. if one already exists it closes it.<br>
+   * if a different instances object is used here, don't forget to clear
+   * the undo-history by calling <code>clearUndo()</code>
+   * 
+   * @param data	the instances to display
+   * @see               #TAB_INSTANCES
+   * @see               #clearUndo()
+   */
+  public void setInstances(Instances data) {
+    ArffSortedTableModel         model;
+    
+    m_Filename = TAB_INSTANCES;
+    
+    createTitle();
+    model = new ArffSortedTableModel(data);
+    
+    m_TableArff.setModel(model);
+    clearUndo();
+    setChanged(false);
+    createName();
+  }
+  
+  /**
+   * returns a list with the attributes
+   * 
+   * @return		a list of the attributes
+   */
+  public Vector getAttributes() {
+    Vector               result;
+    int                  i;
+    
+    result = new Vector();
+    for (i = 0; i < getInstances().numAttributes(); i++)
+      result.add(getInstances().attribute(i).name());
+    Collections.sort(result);
+    
+    return result;
+  }
+  
+  /**
+   * can only reset the changed state to FALSE
+   * 
+   * @param changed		if false, resets the changed state
+   */
+  public void setChanged(boolean changed) {
+    if (!changed) {
+      this.m_Changed = changed;
+      createTitle();
+    }
+  }
+  
+  /**
+   * returns whether the content of the panel was changed
+   * 
+   * @return		true if the content was changed
+   */
+  public boolean isChanged() {
+    return m_Changed;
+  }
+
+  /**
+   * returns whether the model is read-only
+   * 
+   * @return 		true if model is read-only
+   */
+  public boolean isReadOnly() {
+    if (m_TableArff == null)
+      return true;
+    else
+      return ((ArffSortedTableModel) m_TableArff.getModel()).isReadOnly();
+  }
+  
+  /**
+   * sets whether the model is read-only
+   * 
+   * @param value	if true the model is set to read-only
+   */
+  public void setReadOnly(boolean value) {
+    if (m_TableArff != null)
+      ((ArffSortedTableModel) m_TableArff.getModel()).setReadOnly(value);
+  }
+
+  /**
+   * returns whether undo support is enabled
+   * 
+   * @return 		true if undo is enabled
+   */
+  public boolean isUndoEnabled() {
+    return ((ArffSortedTableModel) m_TableArff.getModel()).isUndoEnabled();
+  }
+  
+  /**
+   * sets whether undo support is enabled
+   * 
+   * @param enabled		whether to enable/disable undo support
+   */
+  public void setUndoEnabled(boolean enabled) {
+    ((ArffSortedTableModel) m_TableArff.getModel()).setUndoEnabled(enabled);
+  }
+  
+  /**
+   * removes the undo history
+   */
+  public void clearUndo() {
+    ((ArffSortedTableModel) m_TableArff.getModel()).clearUndo();
+  }
+  
+  /**
+   * returns whether an undo is possible 
+   * 
+   * @return		true if undo is possible
+   */
+  public boolean canUndo() {
+    return ((ArffSortedTableModel) m_TableArff.getModel()).canUndo();
+  }
+  
+  /**
+   * performs an undo action
+   */
+  public void undo() {
+    if (canUndo()) {
+      ((ArffSortedTableModel) m_TableArff.getModel()).undo();
+      
+      // notify about update
+      notifyListener();
+    }
+  }
+  
+  /**
+   * adds the current state of the instances to the undolist 
+   */
+  public void addUndoPoint() {
+    ((ArffSortedTableModel) m_TableArff.getModel()).addUndoPoint();
+        
+    // update menu
+    setMenu();
+  }
+  
+  /**
+   * sets the title (i.e. filename)
+   */
+  private void createTitle() {
+    File              file;
+    
+    if (m_Filename.equals("")) {
+      m_Title = "-none-";
+    }
+    else if (m_Filename.equals(TAB_INSTANCES)) {
+      m_Title = TAB_INSTANCES;
+    }
+    else {
+      try {
+        file  = new File(m_Filename);
+        m_Title = file.getName();
+      }
+      catch (Exception e) {
+        m_Title = "-none-";
+      }
+    }
+    
+    if (isChanged())
+      m_Title += " *";
+  }
+  
+  /**
+   * sets the relation name
+   */
+  private void createName() {
+    ArffSortedTableModel         model;
+    
+    model = (ArffSortedTableModel) m_TableArff.getModel();
+    if ((model != null) && (model.getInstances() != null))
+      m_LabelName.setText("Relation: " + model.getInstances().relationName());
+    else
+      m_LabelName.setText("");
+  }
+  
+  /**
+   * loads the specified file into the table
+   * 
+   * @param filename		the file to load
+   */
+  private void loadFile(String filename) {
+    ArffSortedTableModel         model;
+    
+    this.m_Filename = filename;
+    
+    createTitle();
+    
+    if (filename.equals(""))   
+      model = null;
+    else
+      model = new ArffSortedTableModel(filename);
+    
+    m_TableArff.setModel(model);
+    setChanged(false);
+    createName();
+  }
+  
+  /**
+   * calculates the mean of the given numeric column
+   */
+  private void calcMean() {
+    ArffSortedTableModel   model;
+    int               i;
+    double            mean;
+    
+    // no column selected?
+    if (m_CurrentCol == -1)
+      return;
+    
+    model = (ArffSortedTableModel) m_TableArff.getModel();
+    
+    // not numeric?
+    if (!model.getAttributeAt(m_CurrentCol).isNumeric())
+      return;
+    
+    mean = 0;
+    for (i = 0; i < model.getRowCount(); i++)
+      mean += model.getInstances().instance(i).value(m_CurrentCol - 1);
+    mean = mean / model.getRowCount();
+    
+    // show result
+    ComponentHelper.showMessageBox(
+        getParent(), 
+        "Mean for attribute...",
+        "Mean for attribute '" 
+        + m_TableArff.getPlainColumnName(m_CurrentCol) 
+        + "':\n\t" + Utils.doubleToString(mean, 3),
+        JOptionPane.OK_CANCEL_OPTION,
+        JOptionPane.PLAIN_MESSAGE);
+  }
+  
+  /**
+   * sets the specified values in a column to a new value
+   * 
+   * @param o		the menu item
+   */
+  private void setValues(Object o) {
+    String                     msg;
+    String                     title;
+    String                     value;
+    String                     valueNew;
+    int                        i;
+    ArffSortedTableModel      model;
+    
+    value    = "";
+    valueNew = "";
+    
+    if (o == menuItemSetMissingValues) {
+      title = "Replace missing values..."; 
+      msg   = "New value for MISSING values";
+    }
+    else if (o == menuItemSetAllValues) {
+      title = "Set all values..."; 
+      msg   = "New value for ALL values";
+    }
+    else if (o == menuItemReplaceValues) {
+      title = "Replace values..."; 
+      msg   = "Old value";
+    }
+    else
+      return;
+    
+    value = ComponentHelper.showInputBox(m_TableArff.getParent(), title, msg, m_LastSearch);
+    
+    // cancelled?
+    if (value == null)
+      return;
+
+    m_LastSearch = value;
+    
+    // replacement
+    if (o == menuItemReplaceValues) {
+      valueNew = ComponentHelper.showInputBox(m_TableArff.getParent(), title, "New value", m_LastReplace);
+      if (valueNew == null)
+        return;
+      m_LastReplace = valueNew;
+    }
+    
+    model = (ArffSortedTableModel) m_TableArff.getModel();
+    model.setNotificationEnabled(false);
+
+    // undo
+    addUndoPoint();
+    model.setUndoEnabled(false);
+    String valueCopy = value;
+    // set value
+    for (i = 0; i < m_TableArff.getRowCount(); i++) {
+      if (o == menuItemSetAllValues) {
+        if (valueCopy.equals("NaN") || valueCopy.equals("?")) {
+          value = null;
+        }
+        model.setValueAt(value, i, m_CurrentCol);
+      }
+      else
+        if ( (o == menuItemSetMissingValues) 
+            && model.isMissingAt(i, m_CurrentCol) )
+          model.setValueAt(value, i, m_CurrentCol);
+        else if ( (o == menuItemReplaceValues) 
+            && model.getValueAt(i, m_CurrentCol).toString().equals(value) )
+          model.setValueAt(valueNew, i, m_CurrentCol);
+    }
+    model.setUndoEnabled(true);
+    model.setNotificationEnabled(true);
+    model.notifyListener(new TableModelEvent(model, 0, model.getRowCount(), m_CurrentCol, TableModelEvent.UPDATE));
+    
+    // refresh
+    m_TableArff.repaint();
+  }
+  
+  /**
+   * deletes the currently selected attribute
+   */
+  public void deleteAttribute() {
+    ArffSortedTableModel   model;
+    
+    // no column selected?
+    if (m_CurrentCol == -1)
+      return;
+    
+    model = (ArffSortedTableModel) m_TableArff.getModel();
+
+    // really an attribute column?
+    if (model.getAttributeAt(m_CurrentCol) == null)
+      return;
+    
+    // really?
+    if (ComponentHelper.showMessageBox(
+        getParent(), 
+        "Confirm...",
+        "Do you really want to delete the attribute '" 
+        + model.getAttributeAt(m_CurrentCol).name() + "'?",
+        JOptionPane.YES_NO_OPTION,
+        JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
+      return;
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+    model.deleteAttributeAt(m_CurrentCol);
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+  }
+  
+  /**
+   * deletes the chosen attributes
+   */
+  public void deleteAttributes() {
+    ListSelectorDialog    dialog;
+    ArffSortedTableModel       model;
+    Object[]              atts;
+    int[]                 indices;
+    int                   i;
+    JList                 list;
+    int                   result;
+    
+    list   = new JList(getAttributes());
+    dialog = new ListSelectorDialog(null, list);
+    result = dialog.showDialog();
+    
+    if (result != ListSelectorDialog.APPROVE_OPTION)
+      return;
+    
+    atts = list.getSelectedValues();
+    
+    // really?
+    if (ComponentHelper.showMessageBox(
+        getParent(), 
+        "Confirm...",
+        "Do you really want to delete these " 
+        + atts.length + " attributes?",
+        JOptionPane.YES_NO_OPTION,
+        JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION)
+      return;
+    
+    model   = (ArffSortedTableModel) m_TableArff.getModel();
+    indices = new int[atts.length];
+    for (i = 0; i < atts.length; i++)
+      indices[i] = model.getAttributeColumn(atts[i].toString());
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+    model.deleteAttributes(indices);
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+  }
+  
+  /**
+   * sets the current attribute as class attribute, i.e. it moves it to the end
+   * of the attributes
+   */
+  public void attributeAsClass() {
+    ArffSortedTableModel   model;
+    
+    // no column selected?
+    if (m_CurrentCol == -1)
+      return;
+    
+    model   = (ArffSortedTableModel) m_TableArff.getModel();
+
+    // really an attribute column?
+    if (model.getAttributeAt(m_CurrentCol) == null)
+      return;
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+    model.attributeAsClassAt(m_CurrentCol);
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+  }
+  
+  /**
+   * renames the current attribute
+   */
+  public void renameAttribute() {
+    ArffSortedTableModel   model;
+    String            newName;
+    
+    // no column selected?
+    if (m_CurrentCol == -1)
+      return;
+    
+    model   = (ArffSortedTableModel) m_TableArff.getModel();
+
+    // really an attribute column?
+    if (model.getAttributeAt(m_CurrentCol) == null)
+      return;
+    
+    newName = ComponentHelper.showInputBox(getParent(), "Rename attribute...", "Enter new Attribute name", model.getAttributeAt(m_CurrentCol).name());
+    if (newName == null)
+      return;
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+    model.renameAttributeAt(m_CurrentCol, newName);
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+  }
+  
+  /**
+   * deletes the currently selected instance
+   */
+  public void deleteInstance() {
+    int               index;
+    
+    index = m_TableArff.getSelectedRow();
+    if (index == -1)
+      return;
+    
+    ((ArffSortedTableModel) m_TableArff.getModel()).deleteInstanceAt(index);
+  }
+  
+  /**
+   * deletes all the currently selected instances
+   */
+  public void deleteInstances() {
+    int[]             indices;
+    
+    if (m_TableArff.getSelectedRow() == -1)
+      return;
+    
+    indices = m_TableArff.getSelectedRows();
+    ((ArffSortedTableModel) m_TableArff.getModel()).deleteInstances(indices);
+  }
+  
+  /**
+   * sorts the instances via the currently selected column
+   */
+  public void sortInstances() {
+    if (m_CurrentCol == -1)
+      return;
+    
+    ((ArffSortedTableModel) m_TableArff.getModel()).sortInstances(m_CurrentCol);
+  }
+  
+  /**
+   * copies the content of the selection to the clipboard
+   */
+  public void copyContent() {
+    StringSelection      selection;
+    Clipboard            clipboard;
+    
+    selection = getTable().getStringSelection();
+    if (selection == null)
+      return;
+    
+    clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+    clipboard.setContents(selection, selection);
+  }
+  
+  /**
+   * searches for a string in the cells
+   */
+  public void search() {
+    String              searchString;
+    
+    // display dialog
+    searchString = ComponentHelper.showInputBox(getParent(), "Search...", "Enter the string to search for", m_LastSearch);
+    if (searchString != null)
+      m_LastSearch = searchString;
+    
+    getTable().setSearchString(searchString);
+  }
+  
+  /**
+   * clears the search, i.e. resets the found cells
+   */
+  public void clearSearch() {
+    getTable().setSearchString("");
+  }
+  
+  /**
+   * calculates the optimal column width for the current column
+   */
+  public void setOptimalColWidth() {
+    // no column selected?
+    if (m_CurrentCol == -1)
+      return;
+
+    JTableHelper.setOptimalColumnWidth(getTable(), m_CurrentCol);
+  }
+  
+  /**
+   * calculates the optimal column widths for all columns
+   */
+  public void setOptimalColWidths() {
+    JTableHelper.setOptimalColumnWidth(getTable());
+  }
+  
+  /**
+   * invoked when an action occurs
+   * 
+   * @param e		the action event
+   */
+  public void actionPerformed(ActionEvent e) {
+    Object          o;
+    
+    o = e.getSource();
+    
+    if (o == menuItemMean)
+      calcMean();
+    else if (o == menuItemSetAllValues)
+      setValues(menuItemSetAllValues);
+    else if (o == menuItemSetMissingValues)
+      setValues(menuItemSetMissingValues);
+    else if (o == menuItemReplaceValues)
+      setValues(menuItemReplaceValues);
+    else if (o == menuItemRenameAttribute)
+      renameAttribute();
+    else if (o == menuItemAttributeAsClass)
+      attributeAsClass();
+    else if (o == menuItemDeleteAttribute)
+      deleteAttribute();
+    else if (o == menuItemDeleteAttributes)
+      deleteAttributes();
+    else if (o == menuItemDeleteSelectedInstance)
+      deleteInstance();
+    else if (o == menuItemDeleteAllSelectedInstances)
+      deleteInstances();
+    else if (o == menuItemSortInstances)
+      sortInstances();
+    else if (o == menuItemSearch)
+      search();
+    else if (o == menuItemClearSearch)
+      clearSearch();
+    else if (o == menuItemUndo)
+      undo();
+    else if (o == menuItemCopy)
+      copyContent();
+    else if (o == menuItemOptimalColWidth)
+      setOptimalColWidth();
+    else if (o == menuItemOptimalColWidths)
+      setOptimalColWidths();
+  }
+  
+  /**
+   * Invoked when a mouse button has been pressed and released on a component
+   * 
+   * @param e		the mouse event
+   */
+  public void mouseClicked(MouseEvent e) {
+    int		col;
+    boolean	popup;
+    
+    col   = m_TableArff.columnAtPoint(e.getPoint());
+    popup =    ((e.getButton() == MouseEvent.BUTTON3) && (e.getClickCount() == 1))
+            || ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 1) && e.isAltDown() && !e.isControlDown() && !e.isShiftDown());
+    popup = popup && (getInstances() != null);
+    
+    if (e.getSource() == m_TableArff.getTableHeader()) {
+      m_CurrentCol = col;
+      
+      // Popup-Menu
+      if (popup) {
+        e.consume();
+        setMenu();
+        initPopupMenus();
+        m_PopupHeader.show(e.getComponent(), e.getX(), e.getY());
+      }
+    }
+    else if (e.getSource() == m_TableArff) {
+      // Popup-Menu
+      if (popup) {
+        e.consume();
+        setMenu();
+        initPopupMenus();
+        m_PopupRows.show(e.getComponent(), e.getX(), e.getY());
+      }
+    }
+    
+    // highlihgt column
+    if (    (e.getButton() == MouseEvent.BUTTON1)  
+         && (e.getClickCount() == 1) 
+         && (!e.isAltDown())
+         && (col > -1) ) {
+      m_TableArff.setSelectedColumn(col);
+    }
+  }
+  
+  /**
+   * Invoked when the mouse enters a component.
+   * 
+   * @param e		the mouse event
+   */
+  public void mouseEntered(MouseEvent e) {
+  }
+  
+  /**
+   * Invoked when the mouse exits a component
+   * 
+   * @param e		the mouse event
+   */
+  public void mouseExited(MouseEvent e) {
+  }
+  
+  /**
+   * Invoked when a mouse button has been pressed on a component
+   * 
+   * @param e		the mouse event
+   */
+  public void mousePressed(MouseEvent e) {
+  }
+  
+  /**
+   * Invoked when a mouse button has been released on a component.
+   * 
+   * @param e		the mouse event
+   */
+  public void mouseReleased(MouseEvent e) {
+  }
+  
+  /**
+   * Invoked when the target of the listener has changed its state.
+   * 
+   * @param e		the change event
+   */
+  public void stateChanged(ChangeEvent e) {
+    m_Changed = true;
+    createTitle();
+    notifyListener();
+  }
+  
+  /**
+   * notfies all listener of the change
+   */
+  public void notifyListener() {
+    Iterator                iter;
+    
+    iter = m_ChangeListeners.iterator();
+    while (iter.hasNext())
+      ((ChangeListener) iter.next()).stateChanged(new ChangeEvent(this));
+  }
+  
+  /**
+   * Adds a ChangeListener to the panel
+   * 
+   * @param l		the listener to add
+   */
+  public void addChangeListener(ChangeListener l) {
+    m_ChangeListeners.add(l);
+  }
+  
+  /**
+   * Removes a ChangeListener from the panel
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeChangeListener(ChangeListener l) {
+    m_ChangeListeners.remove(l);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffSortedTableModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffSortedTableModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffSortedTableModel.java	(revision 29)
@@ -0,0 +1,364 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffSortedTableModel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.gui.SortedTableModel;
+import weka.core.Instances;
+import weka.core.Attribute;
+import weka.core.Undoable;
+import javax.swing.table.TableModel;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+/**
+ * A sorter for the ARFF-Viewer - necessary because of the custom CellRenderer.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $ 
+ */
+
+public class ArffSortedTableModel 
+  extends SortedTableModel 
+  implements Undoable {
+  
+  /** for serialization */
+  static final long serialVersionUID = -5733148376354254030L;
+  
+  /**
+   * initializes the sorter w/o a model, but loads the given file and creates
+   * from that a model
+   * 
+   * @param filename	the file to load
+   */
+  public ArffSortedTableModel(String filename) {
+    this(new ArffTableModel(filename));
+  }
+  
+  /**
+   * initializes the sorter w/o a model, but uses the given data to create
+   * a model from that
+   * 
+   * @param data 	the data to use
+   */
+  public ArffSortedTableModel(Instances data) {
+    this(new ArffTableModel(data));
+  }
+  
+  /**
+   * initializes the sorter with the given model
+   * 
+   * @param model	the model to use
+   */
+  public ArffSortedTableModel(TableModel model) {
+    super(model);
+  }
+  
+  /**
+   * returns whether the notification of changes is enabled
+   * 
+   * @return 		true if notification of changes is enabled
+   */
+  public boolean isNotificationEnabled() {
+    return ((ArffTableModel) getModel()).isNotificationEnabled();
+  }
+  
+  /**
+   * sets whether the notification of changes is enabled
+   * 
+   * @param enabled	enables/disables the notification
+   */
+  public void setNotificationEnabled(boolean enabled) {
+    ((ArffTableModel) getModel()).setNotificationEnabled(enabled);
+  }
+  
+  /**
+   * returns whether undo support is enabled
+   * 
+   * @return 		true if undo support is enabled
+   */
+  public boolean isUndoEnabled() {
+    return ((ArffTableModel) getModel()).isUndoEnabled();
+  }
+  
+  /**
+   * sets whether undo support is enabled
+   * 
+   * @param enabled	whether to enable/disable undo support
+   */
+  public void setUndoEnabled(boolean enabled) {
+    ((ArffTableModel) getModel()).setUndoEnabled(enabled);
+  }
+
+  /**
+   * returns whether the model is read-only
+   * 
+   * @return 		true if model is read-only
+   */
+  public boolean isReadOnly() {
+    return ((ArffTableModel) getModel()).isReadOnly();
+  }
+  
+  /**
+   * sets whether the model is read-only
+   * 
+   * @param value	if true the model is set to read-only
+   */
+  public void setReadOnly(boolean value) {
+    ((ArffTableModel) getModel()).setReadOnly(value);
+  }
+  
+  /**
+   * returns the double value of the underlying Instances object at the
+   * given position, -1 if out of bounds
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return			the underlying value in the Instances object
+   */
+  public double getInstancesValueAt(int rowIndex, int columnIndex) {
+    return ((ArffTableModel) getModel()).getInstancesValueAt(mIndices[rowIndex], columnIndex);
+  }
+  
+  /**
+   * returns the value at the given position
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return			the value of the model at the given  position
+   */
+  public Object getModelValueAt(int rowIndex, int columnIndex) {
+    Object            result;
+    
+    result = super.getModel().getValueAt(rowIndex, columnIndex);
+    // since this is called in the super-class we have to use the original
+    // index!
+    if (((ArffTableModel) getModel()).isMissingAt(rowIndex, columnIndex))
+      result = null;
+    
+    return result;
+  }
+  
+  /**
+   * returns the TYPE of the attribute at the given position
+   * 
+   * @param columnIndex		the index of the column
+   * @return			the attribute type
+   */
+  public int getType(int columnIndex) {
+    return ((ArffTableModel) getModel()).getType(mIndices[0], columnIndex);
+  }
+  
+  /**
+   * returns the TYPE of the attribute at the given position
+   * 
+   * @param rowIndex		the index of the row
+   * @param columnIndex		the index of the column
+   * @return			the attribute type
+   */
+  public int getType(int rowIndex, int columnIndex) {
+    return ((ArffTableModel) getModel()).getType(mIndices[rowIndex], columnIndex);
+  }
+  
+  /**
+   * deletes the attribute at the given col index
+   * 
+   * @param columnIndex     the index of the attribute to delete
+   */
+  public void deleteAttributeAt(int columnIndex) {
+    ((ArffTableModel) getModel()).deleteAttributeAt(columnIndex);
+  }
+  
+  /**
+   * deletes the attributes at the given indices
+   * 
+   * @param columnIndices	the column indices
+   */
+  public void deleteAttributes(int[] columnIndices) {
+    ((ArffTableModel) getModel()).deleteAttributes(columnIndices);
+  }
+  
+  /**
+   * renames the attribute at the given col index
+   * 
+   * @param columnIndex		the index of the column
+   * @param newName		the new name of the attribute
+   */
+  public void renameAttributeAt(int columnIndex, String newName) {
+    ((ArffTableModel) getModel()).renameAttributeAt(columnIndex, newName);
+  }
+  
+  /**
+   * sets the attribute at the given col index as the new class attribute
+   * 
+   * @param columnIndex		the index of the column
+   */
+  public void attributeAsClassAt(int columnIndex) {
+    ((ArffTableModel) getModel()).attributeAsClassAt(columnIndex);
+  }
+  
+  /**
+   * deletes the instance at the given index
+   * 
+   * @param rowIndex		the index of the row
+   */
+  public void deleteInstanceAt(int rowIndex) {
+    ((ArffTableModel) getModel()).deleteInstanceAt(mIndices[rowIndex]);
+  }
+  
+  /**
+   * deletes the instances at the given positions
+   * 
+   * @param rowIndices		the indices to delete
+   */
+  public void deleteInstances(int[] rowIndices) {
+    int[]               realIndices;
+    int                 i;
+    
+    realIndices = new int[rowIndices.length];
+    for (i = 0; i < rowIndices.length; i++)
+      realIndices[i] = mIndices[rowIndices[i]];
+   
+    ((ArffTableModel) getModel()).deleteInstances(realIndices);
+  }
+  
+  /**
+   * sorts the instances via the given attribute
+   * 
+   * @param columnIndex		the index of the column
+   */
+  public void sortInstances(int columnIndex) {
+    ((ArffTableModel) getModel()).sortInstances(columnIndex);
+  }
+  
+  /**
+   * returns the column of the given attribute name, -1 if not found
+   * 
+   * @param name		the name of the attribute
+   * @return			the column index or -1 if not found
+   */
+  public int getAttributeColumn(String name) {
+    return ((ArffTableModel) getModel()).getAttributeColumn(name);
+  }
+  
+  /**
+   * checks whether the value at the given position is missing
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return			true if the value at the position is missing
+   */
+  public boolean isMissingAt(int rowIndex, int columnIndex) {
+    return ((ArffTableModel) getModel()).isMissingAt(mIndices[rowIndex], columnIndex);
+  }
+  
+  /**
+   * sets the data
+   * 
+   * @param data	the data to use
+   */
+  public void setInstances(Instances data) {
+    ((ArffTableModel) getModel()).setInstances(data);
+  }
+  
+  /**
+   * returns the data
+   * 
+   * @return		the current data
+   */
+  public Instances getInstances() {
+    return ((ArffTableModel) getModel()).getInstances();
+  }
+  
+  /**
+   * returns the attribute at the given index, can be NULL if not an attribute
+   * column
+   * 
+   * @param columnIndex		the index of the column
+   * @return			the attribute at the position
+   */
+  public Attribute getAttributeAt(int columnIndex) {
+    return ((ArffTableModel) getModel()).getAttributeAt(columnIndex);
+  }
+  
+  /**
+   * adds a listener to the list that is notified each time a change to data 
+   * model occurs
+   * 
+   * @param l		the listener to add
+   */
+  public void addTableModelListener(TableModelListener l) {
+    if (getModel() != null)
+      ((ArffTableModel) getModel()).addTableModelListener(l);
+  }
+  
+  /**
+   * removes a listener from the list that is notified each time a change to
+   * the data model occurs
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeTableModelListener(TableModelListener l) {
+    if (getModel() != null)
+      ((ArffTableModel) getModel()).removeTableModelListener(l);
+  }
+  
+  /**
+   * notfies all listener of the change of the model
+   * 
+   * @param e		the event to send to the listeners
+   */
+  public void notifyListener(TableModelEvent e) {
+    ((ArffTableModel) getModel()).notifyListener(e);
+  }
+
+  /**
+   * removes the undo history
+   */
+  public void clearUndo() {
+    ((ArffTableModel) getModel()).clearUndo();
+  }
+  
+  /**
+   * returns whether an undo is possible, i.e. whether there are any undo points
+   * saved so far
+   * 
+   * @return returns TRUE if there is an undo possible 
+   */
+  public boolean canUndo() {
+    return ((ArffTableModel) getModel()).canUndo();
+  }
+  
+  /**
+   * undoes the last action
+   */
+  public void undo() {
+    ((ArffTableModel) getModel()).undo();
+  }
+  
+  /**
+   * adds an undo point to the undo history 
+   */
+  public void addUndoPoint() {
+    ((ArffTableModel) getModel()).addUndoPoint();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTable.java	(revision 29)
@@ -0,0 +1,466 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffTable.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.gui.ComponentHelper;
+import weka.gui.JTableHelper;
+import weka.gui.ViewerDialog;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.datatransfer.StringSelection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableModel;
+
+/**
+ * A specialized JTable for the Arff-Viewer.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.8 $ 
+ */
+public class ArffTable
+  extends JTable {
+  
+  /** for serialization */
+  static final long serialVersionUID = -2016200506908637967L;
+
+  /**
+   * a special Editor for editing the relation attribute.
+   */
+  protected class RelationalCellEditor
+    extends AbstractCellEditor
+    implements TableCellEditor {
+
+    /** for serialization */
+    private static final long serialVersionUID = 657969163293205963L;
+    
+    /** the button for opening the dialog */
+    protected JButton m_Button;
+    
+    /** the current instances */
+    protected Instances m_CurrentInst;
+    
+    /** the row index this editor is for */
+    protected int m_RowIndex;
+    
+    /** the column index this editor is for */
+    protected int m_ColumnIndex;
+    
+    /**
+     * initializes the editor
+     * 
+     * @param rowIndex		the row index
+     * @param columnIndex	the column index
+     */
+    public RelationalCellEditor(int rowIndex, int columnIndex) {
+      super();
+
+      m_CurrentInst = getInstancesAt(rowIndex, columnIndex);
+      m_RowIndex    = rowIndex;
+      m_ColumnIndex = columnIndex;
+      
+      m_Button = new JButton("...");
+      m_Button.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent evt) {
+          ViewerDialog        dialog;
+          int                 result;
+          
+          dialog = new ViewerDialog(null);
+          dialog.setTitle(
+              "Relational attribute Viewer - " 
+              + ((ArffSortedTableModel) getModel()).getInstances().attribute(m_ColumnIndex - 1).name());
+          result = dialog.showDialog(m_CurrentInst);
+          if (result == ViewerDialog.APPROVE_OPTION) {
+            m_CurrentInst = dialog.getInstances();
+            fireEditingStopped();
+          }
+          else {
+            fireEditingCanceled();
+          }
+        }
+      });
+    }
+
+    /**
+     * returns the underlying instances at the given position
+     * 
+     * @param rowIndex		the row index
+     * @param columnIndex	the column index
+     * @return 			the corresponding instances
+     */
+    protected Instances getInstancesAt(int rowIndex, int columnIndex) {
+      Instances			result;
+      ArffSortedTableModel	model;
+      double			value;
+      
+      model = (ArffSortedTableModel) getModel();
+      value = model.getInstancesValueAt(rowIndex, columnIndex);
+      result = model.getInstances().attribute(columnIndex - 1).relation((int) value);
+      
+      return result;
+    }
+    
+    /**
+     * Sets an initial value for the editor. This will cause the editor to 
+     * stopEditing and lose any partially edited value if the editor is 
+     * editing when this method is called.
+     * 
+     * @param table		the table this editor belongs to
+     * @param value		the value to edit
+     * @param isSelected	whether the cell is selected
+     * @param row		the row index
+     * @param column		the column index
+     * @return			the 
+     */
+    public Component getTableCellEditorComponent(JTable table,
+                                                 Object value,
+                                                 boolean isSelected,
+                                                 int row,
+                                                 int column) {
+      return m_Button;
+    }
+
+    /**
+     * Returns the value contained in the editor.
+     * 
+     * @return		the value contained in the editor
+     */
+    public Object getCellEditorValue() {
+      return m_CurrentInst;
+    }
+  }
+  
+  /** the search string */
+  private String m_SearchString;
+  /** the listeners for changes */
+  private HashSet m_ChangeListeners;
+  
+  /**
+   * initializes with no model
+   */
+  public ArffTable() {
+    this(new ArffSortedTableModel(""));
+  }
+  
+  /**
+   * initializes with the given model
+   * 
+   * @param model		the model to use
+   */
+  public ArffTable(TableModel model) {
+    super(model);
+    
+    setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+  }
+  
+  /**
+   * sets the new model
+   * 
+   * @param model		the model to use
+   */
+  public void setModel(TableModel model) {
+    ArffSortedTableModel      arffModel;
+    
+    // initialize the search
+    m_SearchString = null;
+    
+    // init the listeners
+    if (m_ChangeListeners == null)
+      m_ChangeListeners = new HashSet();
+    
+    super.setModel(model);
+    
+    if (model == null)
+      return;
+    
+    if (!(model instanceof ArffSortedTableModel))
+      return;
+    
+    arffModel = (ArffSortedTableModel) model;
+    arffModel.addMouseListenerToHeader(this);
+    arffModel.addTableModelListener(this);
+    arffModel.sort(0);
+    setLayout();
+    setSelectedColumn(0);
+    
+    // disable column moving
+    if (getTableHeader() != null)
+      getTableHeader().setReorderingAllowed(false);
+  }
+
+  /**
+   * returns the cell editor for the given cell
+   * 
+   * @param row		the row index
+   * @param column	the column index
+   * @return		the cell editor
+   */
+  public TableCellEditor getCellEditor(int row, int column) {
+    TableCellEditor		result;
+    
+    // relational attribute?
+    if (    (getModel() instanceof ArffSortedTableModel) 
+	 && (((ArffSortedTableModel) getModel()).getType(column) == Attribute.RELATIONAL) )
+      result = new RelationalCellEditor(row, column);
+    // default
+    else
+      result = super.getCellEditor(row, column);
+    
+    return result;
+  }
+
+  /**
+   * returns whether the model is read-only
+   * 
+   * @return 		true if model is read-only
+   */
+  public boolean isReadOnly() {
+    return ((ArffSortedTableModel) getModel()).isReadOnly();
+  }
+  
+  /**
+   * sets whether the model is read-only
+   * 
+   * @param value	if true the model is set to read-only
+   */
+  public void setReadOnly(boolean value) {
+    ((ArffSortedTableModel) getModel()).setReadOnly(value);
+  }
+  
+  /**
+   * sets the cell renderer and calcs the optimal column width
+   */
+  private void setLayout() {
+    ArffSortedTableModel      arffModel;
+    int                  i;
+    JComboBox            combo;
+    Enumeration          enm;
+    
+    arffModel = (ArffSortedTableModel) getModel();
+    
+    for (i = 0; i < getColumnCount(); i++) {
+      // optimal colwidths (only according to header!)
+      JTableHelper.setOptimalHeaderWidth(this, i);
+      
+      // CellRenderer
+      getColumnModel().getColumn(i).setCellRenderer(
+          new ArffTableCellRenderer());
+      
+      // CellEditor
+      if (i > 0) {
+        if (arffModel.getType(i) == Attribute.NOMINAL) {
+          combo = new JComboBox();
+          combo.addItem(null);
+          enm  = arffModel.getInstances().attribute(i - 1).enumerateValues();
+          while (enm.hasMoreElements())
+            combo.addItem(enm.nextElement());
+          getColumnModel().getColumn(i).setCellEditor(new DefaultCellEditor(combo));
+        }
+        else {
+          getColumnModel().getColumn(i).setCellEditor(null);
+        }
+      }
+    }
+  }
+  
+  /**
+   * returns the basically the attribute name of the column and not the
+   * HTML column name via getColumnName(int)
+   * 
+   * @param columnIndex		the column index
+   * @return 			the plain name
+   */
+  public String getPlainColumnName(int columnIndex) {
+    ArffSortedTableModel      arffModel;
+    String               result;
+    
+    result = "";
+    
+    if (getModel() == null)
+      return result;
+    if (!(getModel() instanceof ArffSortedTableModel))  
+      return result;
+    
+    arffModel = (ArffSortedTableModel) getModel();
+    
+    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0)
+        result = "No.";
+      else
+        result = arffModel.getAttributeAt(columnIndex).name();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the selected content in a StringSelection that can be copied to
+   * the clipboard and used in Excel, if nothing is selected the whole table
+   * is copied to the clipboard
+   * 
+   * @return			the current selection
+   */
+  public StringSelection getStringSelection() {
+    StringSelection         result;
+    int[]                   indices;
+    int                     i;
+    int                     n;
+    StringBuffer            tmp;
+    
+    result = null;
+    
+    // nothing selected? -> all
+    if (getSelectedRow() == -1) {
+      // really?
+      if (ComponentHelper.showMessageBox(
+            getParent(),
+            "Question...",
+            "Do you really want to copy the whole table?",
+            JOptionPane.YES_NO_OPTION,
+            JOptionPane.QUESTION_MESSAGE ) != JOptionPane.YES_OPTION)
+        return result;
+      
+      indices = new int[getRowCount()];
+      for (i = 0; i < indices.length; i++)
+        indices[i] = i;
+    }
+    else {
+      indices = getSelectedRows();
+    }
+    
+    // get header
+    tmp = new StringBuffer();
+    for (i = 0; i < getColumnCount(); i++) {
+      if (i > 0)
+        tmp.append("\t");
+      tmp.append(getPlainColumnName(i));
+    }
+    tmp.append("\n");
+    
+    // get content
+    for (i = 0; i < indices.length; i++) {
+      for (n = 0; n < getColumnCount(); n++) {
+        if (n > 0)
+          tmp.append("\t");
+        tmp.append(getValueAt(indices[i], n).toString());
+      }
+      tmp.append("\n");
+    }
+    
+    result = new StringSelection(tmp.toString());
+    
+    return result;
+  }
+  
+  /**
+   * sets the search string to look for in the table, NULL or "" disables
+   * the search
+   * 
+   * @param searchString	the search string to use
+   */
+  public void setSearchString(String searchString) {
+    this.m_SearchString = searchString;
+    repaint();
+  }
+  
+  /**
+   * returns the search string, can be NULL if no search string is set
+   * 
+   * @return			the current search string
+   */
+  public String getSearchString() {
+    return m_SearchString;
+  }
+  
+  /**
+   * sets the selected column
+   * 
+   * @param index		the column to select
+   */
+  public void setSelectedColumn(int index) {
+    getColumnModel().getSelectionModel().clearSelection();
+    getColumnModel().getSelectionModel().setSelectionInterval(index, index);
+    resizeAndRepaint();
+    if (getTableHeader() != null)
+      getTableHeader().resizeAndRepaint();
+  }
+  
+  /**
+   * This fine grain notification tells listeners the exact range of cells, 
+   * rows, or columns that changed.
+   * 
+   * @param e		the table event
+   */
+  public void tableChanged(TableModelEvent e) {
+    super.tableChanged(e);
+    
+    setLayout();
+    notifyListener();
+  }
+  
+  /**
+   * notfies all listener of the change
+   */
+  private void notifyListener() {
+    Iterator                iter;
+    
+    iter = m_ChangeListeners.iterator();
+    while (iter.hasNext())
+      ((ChangeListener) iter.next()).stateChanged(new ChangeEvent(this));
+  }
+  
+  /**
+   * Adds a ChangeListener to the panel
+   * 
+   * @param l			the listener to add
+   */
+  public void addChangeListener(ChangeListener l) {
+    m_ChangeListeners.add(l);
+  }
+  
+  /**
+   * Removes a ChangeListener from the panel
+   * 
+   * @param l			the listener to remove
+   */
+  public void removeChangeListener(ChangeListener l) {
+    m_ChangeListeners.remove(l);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableCellRenderer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableCellRenderer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableCellRenderer.java	(revision 29)
@@ -0,0 +1,187 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffTableCellRenderer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Attribute;
+import java.awt.Color;
+import java.awt.Component;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Handles the background colors for missing values differently than the
+ * DefaultTableCellRenderer.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $ 
+ */
+
+public class ArffTableCellRenderer 
+  extends DefaultTableCellRenderer {
+  
+  /** for serialization */
+  static final long serialVersionUID = 9195794493301191171L;
+  
+  /** the color for missing values */
+  private Color           missingColor;
+  /** the color for selected missing values */
+  private Color           missingColorSelected;
+  /** the color for highlighted values */
+  private Color           highlightColor;
+  /** the color for selected highlighted values */
+  private Color           highlightColorSelected;
+  
+  /**
+   * initializes the Renderer with a standard color
+   */
+  public ArffTableCellRenderer() {
+    this( new Color(223, 223, 223), 
+        new Color(192, 192, 192) );
+  }
+  
+  /**
+   * initializes the Renderer with the given colors
+   * 
+   * @param missingColor		the color for missing values
+   * @param missingColorSelected	the color selected missing values
+   */
+  public ArffTableCellRenderer( Color missingColor, 
+      Color missingColorSelected ) {
+    this( missingColor,
+        missingColorSelected,
+        Color.RED,
+        Color.RED.darker() );
+  }
+  
+  /**
+   * initializes the Renderer with the given colors
+   * 
+   * @param missingColor		the color for missing values
+   * @param missingColorSelected	the color selected missing values
+   * @param highlightColor		the color for highlighted values
+   * @param highlightColorSelected	the color selected highlighted values
+   */
+  public ArffTableCellRenderer( Color missingColor, 
+      Color missingColorSelected,
+      Color highlightColor,
+      Color highlightColorSelected ) {
+    super();
+    
+    this.missingColor           = missingColor;
+    this.missingColorSelected   = missingColorSelected;
+    this.highlightColor         = highlightColor;
+    this.highlightColorSelected = highlightColorSelected;
+  }
+  
+  /**
+   * Returns the default table cell renderer.
+   * stuff for the header is taken from <a href="http://www.chka.de/swing/table/faq.html">here</a>
+   * 
+   * @param table		the table this object belongs to
+   * @param value		the actual cell value
+   * @param isSelected		whether the cell is selected
+   * @param hasFocus		whether the cell has the focus
+   * @param row			the row in the table
+   * @param column		the column in the table
+   * @return			the rendering component
+   */
+  public Component getTableCellRendererComponent(
+      JTable table, Object value, boolean isSelected, 
+      boolean hasFocus, int row, int column ) {
+    ArffSortedTableModel            model;
+    Component                  result;
+    String                     searchString;
+    boolean                    found;
+    
+    result = super.getTableCellRendererComponent(
+        table, value, isSelected, hasFocus, row, column);
+    
+    // search
+    if (table instanceof ArffTable)
+      searchString = ((ArffTable) table).getSearchString();
+    else
+      searchString = null;
+    if ( (searchString != null) && (!searchString.equals("")) )
+      found = (searchString.equals(value.toString()));
+    else
+      found = false;
+    
+    if (table.getModel() instanceof ArffSortedTableModel) {
+      model = (ArffSortedTableModel) table.getModel();
+      // normal cell
+      if (row >= 0) {
+        if (model.isMissingAt(row, column)) {
+          setToolTipText("missing");
+          if (found) {
+            if (isSelected)
+              result.setBackground(highlightColorSelected);
+            else
+              result.setBackground(highlightColor);
+          }
+          else {
+            if (isSelected)
+              result.setBackground(missingColorSelected);
+            else
+              result.setBackground(missingColor);
+          }
+        }
+        else {
+          setToolTipText(null);
+          if (found) {
+            if (isSelected)
+              result.setBackground(highlightColorSelected);
+            else
+              result.setBackground(highlightColor);
+          }
+          else {
+            if (isSelected)
+              result.setBackground(table.getSelectionBackground());
+            else
+              result.setBackground(Color.WHITE);
+          }
+        }
+        
+        // alignment
+        if (model.getType(row, column) == Attribute.NUMERIC)
+          setHorizontalAlignment(SwingConstants.RIGHT);
+        else
+          setHorizontalAlignment(SwingConstants.LEFT);
+      }
+      // header
+      else {
+        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+        setHorizontalAlignment(SwingConstants.CENTER);
+        if (table.getColumnModel().getSelectionModel().isSelectedIndex(column))
+          result.setBackground(UIManager.getColor("TableHeader.background").darker());
+        else
+          result.setBackground(UIManager.getColor("TableHeader.background"));
+      }
+    }
+    
+    return result;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffTableModel.java	(revision 29)
@@ -0,0 +1,899 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffTableModel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Undoable;
+import weka.core.Utils;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.ConverterUtils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Reorder;
+import weka.gui.ComponentHelper;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+
+/**
+ * The model for the Arff-Viewer.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5987 $ 
+ */
+public class ArffTableModel 
+  implements TableModel, Undoable {
+  
+  /** the listeners */
+  private HashSet m_Listeners;
+  /** the data */
+  private Instances m_Data;
+  /** whether notfication is enabled */
+  private boolean m_NotificationEnabled;
+  /** whether undo is active */
+  private boolean m_UndoEnabled;
+  /** whether to ignore changes, i.e. not adding to undo history */
+  private boolean m_IgnoreChanges;
+  /** the undo list (contains temp. filenames) */
+  private Vector m_UndoList;
+  /** whether the table is read-only */
+  private boolean m_ReadOnly;
+  
+  /**
+   * performs some initialization
+   */
+  private ArffTableModel() {
+    super();
+    
+    m_Listeners           = new HashSet();
+    m_Data                = null;
+    m_NotificationEnabled = true;
+    m_UndoList            = new Vector();
+    m_IgnoreChanges       = false;
+    m_UndoEnabled         = true;
+    m_ReadOnly            = false;
+  }
+  
+  /**
+   * initializes the object and loads the given file
+   * 
+   * @param filename	the file to load
+   */
+  public ArffTableModel(String filename) {
+    this();
+    
+    if ( (filename != null) && (!filename.equals("")) )
+      loadFile(filename);
+  }
+  
+  /**
+   * initializes the model with the given data
+   * 
+   * @param data 	the data to use
+   */
+  public ArffTableModel(Instances data) {
+    this();
+    
+    this.m_Data = data;
+  }
+
+  /**
+   * returns whether the notification of changes is enabled
+   * 
+   * @return 		true if notification of changes is enabled
+   */
+  public boolean isNotificationEnabled() {
+    return m_NotificationEnabled;
+  }
+  
+  /**
+   * sets whether the notification of changes is enabled
+   * 
+   * @param enabled	enables/disables the notification
+   */
+  public void setNotificationEnabled(boolean enabled) {
+    m_NotificationEnabled = enabled;
+  }
+
+  /**
+   * returns whether undo support is enabled
+   * 
+   * @return 		true if undo support is enabled
+   */
+  public boolean isUndoEnabled() {
+    return m_UndoEnabled;
+  }
+  
+  /**
+   * sets whether undo support is enabled
+   * 
+   * @param enabled	whether to enable/disable undo support
+   */
+  public void setUndoEnabled(boolean enabled) {
+    m_UndoEnabled = enabled;
+  }
+
+  /**
+   * returns whether the model is read-only
+   * 
+   * @return 		true if model is read-only
+   */
+  public boolean isReadOnly() {
+    return m_ReadOnly;
+  }
+  
+  /**
+   * sets whether the model is read-only
+   * 
+   * @param value	if true the model is set to read-only
+   */
+  public void setReadOnly(boolean value) {
+    m_ReadOnly = value;
+  }
+  
+  /**
+   * loads the specified ARFF file
+   * 
+   * @param filename	the file to load
+   */
+  private void loadFile(String filename) {
+    AbstractFileLoader          loader;
+    
+    loader = ConverterUtils.getLoaderForFile(filename);
+    
+    if (loader != null) {
+      try {
+        loader.setFile(new File(filename));
+        m_Data = loader.getDataSet();
+      }
+      catch (Exception e) {
+        ComponentHelper.showMessageBox(
+            null, 
+            "Error loading file...", 
+            e.toString(), 
+            JOptionPane.OK_CANCEL_OPTION,
+            JOptionPane.ERROR_MESSAGE );
+        System.out.println(e);
+        m_Data = null;
+      }
+    }
+  }
+  
+  /**
+   * sets the data
+   * 
+   * @param data	the data to use
+   */
+  public void setInstances(Instances data) {
+    m_Data = data;
+  }
+  
+  /**
+   * returns the data
+   * 
+   * @return		the current data
+   */
+  public Instances getInstances() {
+    return m_Data;
+  }
+  
+  /**
+   * returns the attribute at the given index, can be NULL if not an attribute
+   * column
+   * 
+   * @param columnIndex		the index of the column
+   * @return			the attribute at the position
+   */
+  public Attribute getAttributeAt(int columnIndex) {
+    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) )
+      return m_Data.attribute(columnIndex - 1);
+    else
+      return null;
+  }
+  
+  /**
+   * returns the TYPE of the attribute at the given position
+   * 
+   * @param columnIndex		the index of the column
+   * @return			the attribute type
+   */
+  public int getType(int columnIndex) {
+    return getType(0, columnIndex);
+  }
+  
+  /**
+   * returns the TYPE of the attribute at the given position
+   * 
+   * @param rowIndex		the index of the row
+   * @param columnIndex		the index of the column
+   * @return			the attribute type
+   */
+  public int getType(int rowIndex, int columnIndex) {
+    int            result;
+    
+    result = Attribute.STRING;
+    
+    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
+         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
+      result = m_Data.instance(rowIndex).attribute(columnIndex - 1).type();
+    
+    return result;
+  }
+  
+  /**
+   * deletes the attribute at the given col index. notifies the listeners.
+   * 
+   * @param columnIndex     the index of the attribute to delete
+   */
+  public void deleteAttributeAt(int columnIndex) {
+    deleteAttributeAt(columnIndex, true);
+  }
+  
+  /**
+   * deletes the attribute at the given col index
+   * 
+   * @param columnIndex     the index of the attribute to delete
+   * @param notify          whether to notify the listeners
+   */
+  public void deleteAttributeAt(int columnIndex, boolean notify) {
+    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
+      if (!m_IgnoreChanges)
+        addUndoPoint();
+      m_Data.deleteAttributeAt(columnIndex - 1);
+      if (notify) 
+        notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
+    }
+  }
+  
+  /**
+   * deletes the attributes at the given indices
+   * 
+   * @param columnIndices	the column indices
+   */
+  public void deleteAttributes(int[] columnIndices) {
+    int            i;
+    
+    Arrays.sort(columnIndices);
+    
+    addUndoPoint();
+
+    m_IgnoreChanges = true;
+    for (i = columnIndices.length - 1; i >= 0; i--)
+      deleteAttributeAt(columnIndices[i], false);
+    m_IgnoreChanges = false;
+
+    notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
+  }
+  
+  /**
+   * renames the attribute at the given col index
+   * 
+   * @param columnIndex		the index of the column
+   * @param newName		the new name of the attribute
+   */
+  public void renameAttributeAt(int columnIndex, String newName) {
+    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
+      addUndoPoint();
+      m_Data.renameAttribute(columnIndex - 1, newName);
+      notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
+    }
+  }
+  
+  /**
+   * sets the attribute at the given col index as the new class attribute, i.e.
+   * it moves it to the end of the attributes
+   * 
+   * @param columnIndex		the index of the column
+   */
+  public void attributeAsClassAt(int columnIndex) {
+    Reorder     reorder;
+    String      order;
+    int         i;
+    
+    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
+      addUndoPoint();
+      
+      try {
+        // build order string (1-based!)
+        order = "";
+        for (i = 1; i < m_Data.numAttributes() + 1; i++) {
+          // skip new class
+          if (i == columnIndex)
+            continue;
+          
+          if (!order.equals(""))
+            order += ",";
+          order += Integer.toString(i);
+        }
+        if (!order.equals(""))
+          order += ",";
+        order += Integer.toString(columnIndex);
+        
+        // process data
+        reorder = new Reorder();
+        reorder.setAttributeIndices(order);
+        reorder.setInputFormat(m_Data);
+        m_Data = Filter.useFilter(m_Data, reorder);
+        
+        // set class index
+        m_Data.setClassIndex(m_Data.numAttributes() - 1);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+        undo();
+      }
+      
+      notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
+    }
+  }
+  
+  /**
+   * deletes the instance at the given index
+   * 
+   * @param rowIndex		the index of the row
+   */
+  public void deleteInstanceAt(int rowIndex) {
+    deleteInstanceAt(rowIndex, true);
+  }
+  
+  /**
+   * deletes the instance at the given index
+   * 
+   * @param rowIndex		the index of the row
+   * @param notify		whether to notify the listeners
+   */
+  public void deleteInstanceAt(int rowIndex, boolean notify) {
+    if ( (rowIndex >= 0) && (rowIndex < getRowCount()) ) {
+      if (!m_IgnoreChanges)
+        addUndoPoint();
+      m_Data.delete(rowIndex);
+      if (notify)
+        notifyListener(
+            new TableModelEvent(
+                this, rowIndex, rowIndex, 
+                TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
+    }
+  }
+  
+  /**
+   * deletes the instances at the given positions
+   * 
+   * @param rowIndices		the indices to delete
+   */
+  public void deleteInstances(int[] rowIndices) {
+    int               i;
+    
+    Arrays.sort(rowIndices);
+    
+    addUndoPoint();
+    
+    m_IgnoreChanges = true;
+    for (i = rowIndices.length - 1; i >= 0; i--)
+      deleteInstanceAt(rowIndices[i], false);
+    m_IgnoreChanges = false;
+
+    notifyListener(
+        new TableModelEvent(
+            this, rowIndices[0], rowIndices[rowIndices.length - 1], 
+            TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
+  }
+  
+  /**
+   * sorts the instances via the given attribute
+   * 
+   * @param columnIndex		the index of the column
+   */
+  public void sortInstances(int columnIndex) {
+    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
+      addUndoPoint();
+      m_Data.sort(columnIndex - 1);
+      notifyListener(new TableModelEvent(this));
+    }
+  }
+  
+  /**
+   * returns the column of the given attribute name, -1 if not found
+   * 
+   * @param name		the name of the attribute
+   * @return			the column index or -1 if not found
+   */
+  public int getAttributeColumn(String name) {
+    int            i;
+    int            result;
+    
+    result = -1;
+    
+    for (i = 0; i < m_Data.numAttributes(); i++) {
+      if (m_Data.attribute(i).name().equals(name)) {
+        result = i + 1;
+        break;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the most specific superclass for all the cell values in the 
+   * column (always String)
+   * 
+   * @param columnIndex		the column index
+   * @return			the class of the column
+   */
+  public Class getColumnClass(int columnIndex) {
+    Class       result;
+    
+    result = null;
+    
+    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0)
+        result = Integer.class;
+      else if (getType(columnIndex) == Attribute.NUMERIC)
+        result = Double.class;
+      else
+        result = String.class;   // otherwise no input of "?"!!!
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the number of columns in the model
+   * 
+   * @return		the number of columns
+   */
+  public int getColumnCount() {
+    int         result;
+    
+    result = 1;
+    if (m_Data != null)
+      result += m_Data.numAttributes();
+    
+    return result;
+  }
+  
+  /**
+   * checks whether the column represents the class or not
+   * 
+   * @param columnIndex		the index of the column
+   * @return			true if the column is the class attribute
+   */
+  private boolean isClassIndex(int columnIndex) {
+    boolean        result;
+    int            index;
+    
+    index  = m_Data.classIndex();
+    result =    ((index == - 1) && (m_Data.numAttributes() == columnIndex))
+             || (index == columnIndex - 1);
+    
+    return result;
+  }
+  
+  /**
+   * returns the name of the column at columnIndex
+   * 
+   * @param columnIndex		the index of the column
+   * @return			the name of the column
+   */
+  public String getColumnName(int columnIndex) {
+    String      result;
+    
+    result = "";
+    
+    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0) {
+        result = "<html><center>No.<br><font size=\"-2\">&nbsp;</font></center></html>";
+      }
+      else {
+        if (m_Data != null) {
+          if ( (columnIndex - 1 < m_Data.numAttributes()) ) {
+            result = "<html><center>";
+            // name
+            if (isClassIndex(columnIndex))
+              result +=   "<b>" 
+                + m_Data.attribute(columnIndex - 1).name() 
+                + "</b>";
+            else
+              result += m_Data.attribute(columnIndex - 1).name();
+            
+            // attribute type
+            switch (getType(columnIndex)) {
+              case Attribute.DATE: 
+                result += "<br><font size=\"-2\">Date</font>";
+                break;
+              case Attribute.NOMINAL:
+                result += "<br><font size=\"-2\">Nominal</font>";
+                break;
+              case Attribute.STRING:
+                result += "<br><font size=\"-2\">String</font>";
+                break;
+              case Attribute.NUMERIC:
+                result += "<br><font size=\"-2\">Numeric</font>";
+                break;
+              case Attribute.RELATIONAL:
+                result += "<br><font size=\"-2\">Relational</font>";
+                break;
+              default:
+                result += "<br><font size=\"-2\">???</font>";
+            }
+            
+            result += "</center></html>";
+          }
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the number of rows in the model
+   * 
+   * @return		the number of rows
+   */
+  public int getRowCount() {
+    if (m_Data == null)
+      return 0;
+    else
+      return m_Data.numInstances(); 
+  }
+  
+  /**
+   * checks whether the value at the given position is missing
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return			true if the value at the position is missing
+   */
+  public boolean isMissingAt(int rowIndex, int columnIndex) {
+    boolean           result;
+    
+    result = false;
+    
+    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
+         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
+      result = (m_Data.instance(rowIndex).isMissing(columnIndex - 1));
+    
+    return result;
+  }
+  
+  /**
+   * returns the double value of the underlying Instances object at the
+   * given position, -1 if out of bounds
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return			the underlying value in the Instances object
+   */
+  public double getInstancesValueAt(int rowIndex, int columnIndex) {
+    double	result;
+    
+    result = -1;
+    
+    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
+         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
+      result = m_Data.instance(rowIndex).value(columnIndex - 1);
+    
+    return result;
+  }
+  
+  /**
+   * returns the value for the cell at columnindex and rowIndex
+   * 
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @return 			the value at the position
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    Object            result;
+    String            tmp;
+    
+    result = null;
+    
+    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
+        && (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0) {
+        result = new Integer(rowIndex + 1);
+      }
+      else {
+        if (isMissingAt(rowIndex, columnIndex)) {
+          result = null;
+        }
+        else {
+          switch (getType(columnIndex)) {
+            case Attribute.DATE: 
+            case Attribute.NOMINAL:
+            case Attribute.STRING:
+            case Attribute.RELATIONAL:
+              result = m_Data.instance(rowIndex).stringValue(columnIndex - 1);
+              break;
+            case Attribute.NUMERIC:
+              result = new Double(m_Data.instance(rowIndex).value(columnIndex - 1));
+              break;
+            default:
+              result = "-can't display-";
+          }
+        }
+      }
+    }
+    
+    if (getType(columnIndex) != Attribute.NUMERIC) {
+      if (result != null) {
+        // does it contain "\n" or "\r"? -> replace with red html tag
+        tmp = result.toString();
+        if ( (tmp.indexOf("\n") > -1) || (tmp.indexOf("\r") > -1) ) {
+          tmp    = tmp.replaceAll("\\r\\n", "<font color=\"red\"><b>\\\\r\\\\n</b></font>");
+          tmp    = tmp.replaceAll("\\r", "<font color=\"red\"><b>\\\\r</b></font>");
+          tmp    = tmp.replaceAll("\\n", "<font color=\"red\"><b>\\\\n</b></font>");
+          result = "<html>" + tmp + "</html>";
+        }
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns true if the cell at rowindex and columnindexis editable
+   * 
+   * @param rowIndex		the index of the row
+   * @param columnIndex		the index of the column
+   * @return			true if the cell is editable
+   */
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    return (columnIndex > 0) && !isReadOnly();
+  }
+  
+  /**
+   * sets the value in the cell at columnIndex and rowIndex to aValue.
+   * but only the value and the value can be changed
+   * 
+   * @param aValue		the new value
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   */
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+    setValueAt(aValue, rowIndex, columnIndex, true);
+  }
+  
+  /**
+   * sets the value in the cell at columnIndex and rowIndex to aValue.
+   * but only the value and the value can be changed
+   * 
+   * @param aValue		the new value
+   * @param rowIndex		the row index
+   * @param columnIndex		the column index
+   * @param notify		whether to notify the listeners
+   */
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex, boolean notify) {
+    int            type;
+    int            index;
+    String         tmp;
+    Instance       inst;
+    Attribute      att;
+    Object         oldValue;
+    
+    if (!m_IgnoreChanges)
+      addUndoPoint();
+    
+    oldValue = getValueAt(rowIndex, columnIndex);
+    type     = getType(rowIndex, columnIndex);
+    index    = columnIndex - 1;
+    inst     = m_Data.instance(rowIndex);
+    att      = inst.attribute(index);
+    
+    // missing?
+    if (aValue == null) {
+      inst.setValue(index, Utils.missingValue());
+    }
+    else {
+      tmp = aValue.toString();
+      
+      switch (type) {
+        case Attribute.DATE:
+          try {
+            att.parseDate(tmp);
+            inst.setValue(index, att.parseDate(tmp));
+          }
+          catch (Exception e) {
+            // ignore
+          }
+          break;
+      
+        case Attribute.NOMINAL:
+          if (att.indexOfValue(tmp) > -1)
+            inst.setValue(index, att.indexOfValue(tmp));
+          break;
+      
+        case Attribute.STRING:
+          inst.setValue(index, tmp);
+          break;
+      
+        case Attribute.NUMERIC:
+          try {
+            Double.parseDouble(tmp);
+            inst.setValue(index, Double.parseDouble(tmp));
+          }
+          catch (Exception e) {
+            // ignore
+          }
+          break;
+          
+        case Attribute.RELATIONAL:
+          try {
+            inst.setValue(index, inst.attribute(index).addRelation((Instances) aValue));
+          }
+          catch (Exception e) {
+            // ignore
+          }
+          break;
+          
+        default:
+          throw new IllegalArgumentException("Unsupported Attribute type: " + type + "!");
+      }
+    }
+    
+    // notify only if the value has changed!
+    if (notify && (!("" + oldValue).equals("" + aValue)) )
+        notifyListener(new TableModelEvent(this, rowIndex, columnIndex));
+  }
+  
+  /**
+   * adds a listener to the list that is notified each time a change to data 
+   * model occurs
+   * 
+   * @param l		the listener to add
+   */
+  public void addTableModelListener(TableModelListener l) {
+    m_Listeners.add(l);
+  }
+  
+  /**
+   * removes a listener from the list that is notified each time a change to
+   * the data model occurs
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeTableModelListener(TableModelListener l) {
+    m_Listeners.remove(l);
+  }
+  
+  /**
+   * notfies all listener of the change of the model
+   * 
+   * @param e		the event to send to the listeners
+   */
+  public void notifyListener(TableModelEvent e) {
+    Iterator                iter;
+    TableModelListener      l;
+
+    // is notification enabled?
+    if (!isNotificationEnabled())
+      return;
+    
+    iter = m_Listeners.iterator();
+    while (iter.hasNext()) {
+      l = (TableModelListener) iter.next();
+      l.tableChanged(e);
+    }
+  }
+
+  /**
+   * removes the undo history
+   */
+  public void clearUndo() {
+    m_UndoList = new Vector();
+  }
+  
+  /**
+   * returns whether an undo is possible, i.e. whether there are any undo points
+   * saved so far
+   * 
+   * @return returns TRUE if there is an undo possible 
+   */
+  public boolean canUndo() {
+    return !m_UndoList.isEmpty();
+  }
+  
+  /**
+   * undoes the last action
+   */
+  public void undo() {
+    File                  tempFile;
+    Instances             inst;
+    ObjectInputStream     ooi;
+    
+    if (canUndo()) {
+      // load file
+      tempFile = (File) m_UndoList.get(m_UndoList.size() - 1);
+      try {
+        // read serialized data
+        ooi = new ObjectInputStream(new BufferedInputStream(new FileInputStream(tempFile)));
+        inst = (Instances) ooi.readObject();
+        ooi.close();
+        
+        // set instances
+        setInstances(inst);
+        notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
+        notifyListener(new TableModelEvent(this));
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+      tempFile.delete();
+      
+      // remove from undo
+      m_UndoList.remove(m_UndoList.size() - 1);
+    }
+  }
+  
+  /**
+   * adds an undo point to the undo history, if the undo support is enabled
+   * @see #isUndoEnabled()
+   * @see #setUndoEnabled(boolean)
+   */
+  public void addUndoPoint() {
+    File                  tempFile;
+    ObjectOutputStream    oos;
+
+    // undo support currently on?
+    if (!isUndoEnabled())
+      return;
+    
+    if (getInstances() != null) {
+      try {
+        // temp. filename
+        tempFile = File.createTempFile("arffviewer", null);
+        tempFile.deleteOnExit();
+        
+        // serialize instances
+        oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)));
+        oos.writeObject(getInstances());
+        oos.flush();
+        oos.close();
+        
+        // add to undo list
+        m_UndoList.add(tempFile);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewer.java	(revision 29)
@@ -0,0 +1,359 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffViewer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Memory;
+import weka.gui.ComponentHelper;
+import weka.gui.LookAndFeel;
+
+import java.awt.BorderLayout;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+/**
+ * A little tool for viewing ARFF files.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4719 $ 
+ */
+
+public class ArffViewer 
+  extends JFrame
+  implements WindowListener {
+  
+  /** for serialization */
+  static final long serialVersionUID = -7455845566922685175L;
+
+  /** the main panel */
+  private ArffViewerMainPanel m_MainPanel;
+  
+  /** for monitoring the Memory consumption */
+  private static Memory m_Memory = new Memory(true);
+  
+  /** the viewer if started from command line */
+  private static ArffViewer m_Viewer;
+
+  /** whether the files were already loaded */
+  private static boolean m_FilesLoaded;
+
+  /** the command line arguments */
+  private static String[] m_Args;
+  
+  /**
+   * initializes the object
+   */
+  public ArffViewer() {
+    super("ARFF-Viewer");
+    createFrame();
+  }
+  
+  /**
+   * creates all the components in the frame
+   */
+  protected void createFrame() {
+    // basic setup
+    setIconImage(ComponentHelper.getImage("weka_icon.gif"));
+    setSize(ArffViewerMainPanel.WIDTH, ArffViewerMainPanel.HEIGHT);
+    setCenteredLocation();
+    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+    // remove the listener - otherwise we get the strange behavior that one
+    // frame receives a window-event for every single open frame!
+    removeWindowListener(this);
+    // add listener anew
+    addWindowListener(this);
+    
+    getContentPane().setLayout(new BorderLayout());
+    
+    m_MainPanel = new ArffViewerMainPanel(this);
+    m_MainPanel.setConfirmExit(false);
+    getContentPane().add(m_MainPanel, BorderLayout.CENTER);
+    
+    setJMenuBar(m_MainPanel.getMenu());
+  }
+  
+  /**
+   * returns the left coordinate if the frame would be centered
+   * 
+   * @return 		the left coordinate
+   */
+  protected int getCenteredLeft() {
+    int            width;
+    int            x;
+    
+    width  = getBounds().width;
+    x      = (getGraphicsConfiguration().getBounds().width  - width) / 2;
+    
+    if (x < 0) 
+      x = 0;
+    
+    return x;
+  }
+  
+  /**
+   * returns the top coordinate if the frame would be centered
+   * 
+   * @return		the top coordinate
+   */
+  protected int getCenteredTop() {
+    int            height;
+    int            y;
+    
+    height = getBounds().height;
+    y      = (getGraphicsConfiguration().getBounds().height - height) / 2;
+    
+    if (y < 0) 
+      y = 0;
+    
+    return y;
+  }
+  
+  /**
+   * positions the window at the center of the screen
+   */
+  public void setCenteredLocation() { 
+    setLocation(getCenteredLeft(), getCenteredTop());
+  }
+  
+  /**
+   * whether to present a MessageBox on Exit or not
+   * @param confirm           whether a MessageBox pops up or not to confirm
+   *                          exit
+   */
+  public void setConfirmExit(boolean confirm) {
+    m_MainPanel.setConfirmExit(confirm);
+  }
+  
+  /**
+   * returns the setting of whether to display a confirm messagebox or not
+   * on exit
+   * @return                  whether a messagebox is displayed or not
+   */
+  public boolean getConfirmExit() {
+    return m_MainPanel.getConfirmExit();
+  }
+
+  /**
+   * whether to do a System.exit(0) on close
+   * 
+   * @param value	enables/disables the System.exit(0)
+   */
+  public void setExitOnClose(boolean value) {
+    m_MainPanel.setExitOnClose(value);
+  }
+
+  /**
+   * returns TRUE if a System.exit(0) is done on a close
+   * 
+   * @return		true if System.exit(0) is done
+   */
+  public boolean getExitOnClose() {
+    return m_MainPanel.getExitOnClose();
+  }
+  
+  /**
+   * returns the main panel
+   * 
+   * @return		the main panel
+   */
+  public ArffViewerMainPanel getMainPanel() {
+    return m_MainPanel;
+  }
+  
+  /**
+   * validates and repaints the frame
+   */
+  public void refresh() {
+    validate();
+    repaint();
+  }
+
+  /**
+   * invoked when a window is activated
+   * 
+   * @param e		the window event
+   */
+  public void windowActivated(WindowEvent e) {
+  }
+  
+  /**
+   * invoked when a window is closed
+   * 
+   * @param e		the window event
+   */
+  public void windowClosed(WindowEvent e) {
+  }
+  
+  /**
+   * invoked when a window is in the process of closing
+   * 
+   * @param e		the window event
+   */
+  public void windowClosing(WindowEvent e) {
+    int         button;
+    
+    while (getMainPanel().getTabbedPane().getTabCount() > 0)
+      getMainPanel().closeFile(false);
+    
+    if (getConfirmExit()) {
+      button = ComponentHelper.showMessageBox(
+          this,
+          "Quit - " + getTitle(),
+          "Do you really want to quit?",
+          JOptionPane.YES_NO_OPTION,
+          JOptionPane.QUESTION_MESSAGE);
+      if (button == JOptionPane.YES_OPTION)
+        dispose();
+    } 
+    else {
+      dispose();
+    }
+
+    if (getExitOnClose())
+      System.exit(0);
+  }
+  
+  /**
+   * invoked when a window is deactivated
+   * 
+   * @param e		the window event
+   */
+  public void windowDeactivated(WindowEvent e) {
+  }
+  
+  /**
+   * invoked when a window is deiconified
+   * 
+   * @param e		the window event
+   */
+  public void windowDeiconified(WindowEvent e) {
+  }
+  
+  /**
+   * invoked when a window is iconified
+   * 
+   * @param e		the window event
+   */
+  public void windowIconified(WindowEvent e) {
+  }
+  
+  /**
+   * invoked when a window is has been opened
+   * 
+   * @param e		the window event
+   */
+  public void windowOpened(WindowEvent e) {
+  }
+  
+  /**
+   * returns only the classname
+   * 
+   * @return 		the classname
+   */
+  public String toString() {
+    return this.getClass().getName();
+  }
+  
+  /**
+   * shows the frame and it tries to load all the arff files that were
+   * provided as arguments.
+   * 
+   * @param args	the commandline parameters
+   * @throws Exception	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      m_Viewer      = new ArffViewer();
+      m_Viewer.setExitOnClose(true);
+      m_Viewer.setVisible(true);
+      m_FilesLoaded = false;
+      m_Args        = args;
+
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while(true) {
+            try {
+              if ( (m_Args.length > 0) && (!m_FilesLoaded) ) {
+                for (int i = 0; i < m_Args.length; i++) {
+                  System.out.println("Loading " + (i+1) + "/" 
+                      + m_Args.length +  ": '" + m_Args[i] + "'...");
+                  m_Viewer.getMainPanel().loadFile(m_Args[i]);
+                }
+                m_Viewer.getMainPanel().getTabbedPane().setSelectedIndex(0);
+                System.out.println("Finished!");
+                m_FilesLoaded = true;
+              }
+
+              //System.out.println("before sleeping");
+              Thread.sleep(4000);
+              
+              System.gc();
+              
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                m_Viewer.dispose();
+                m_Viewer = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+
+                // display error
+                System.err.println("\ndisplayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nrestarting...");
+
+                // restart GUI
+                System.gc();
+                m_Viewer = new ArffViewer();
+                m_Viewer.setExitOnClose(true);
+                m_Viewer.setVisible(true);
+                // Note: no re-loading of datasets, otherwise we could end up
+                //       in an endless loop!
+              }
+            }
+            catch(InterruptedException ex) { 
+              ex.printStackTrace(); 
+            }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.NORM_PRIORITY);
+      memMonitor.start();    
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewerMainPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewerMainPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/arffviewer/ArffViewerMainPanel.java	(revision 29)
@@ -0,0 +1,1058 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ArffViewerMainPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.arffviewer;
+
+import weka.core.Capabilities;
+import weka.core.Instances;
+import weka.core.converters.AbstractSaver;
+import weka.gui.ComponentHelper;
+import weka.gui.ConverterFileChooser;
+import weka.gui.JTableHelper;
+import weka.gui.ListSelectorDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JList;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * The main panel of the ArffViewer. It has a reference to the menu, that an
+ * implementing JFrame only needs to add via the setJMenuBar(JMenuBar) method.
+ *
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.7 $ 
+ */
+
+public class ArffViewerMainPanel 
+  extends JPanel 
+  implements ActionListener, ChangeListener {
+  
+  /** for serialization */
+  static final long serialVersionUID = -8763161167586738753L;
+  
+  /** the default for width */
+  public final static int    DEFAULT_WIDTH     = -1;
+  /** the default for height */
+  public final static int    DEFAULT_HEIGHT    = -1;
+  /** the default for left */
+  public final static int    DEFAULT_LEFT      = -1;
+  /** the default for top */
+  public final static int    DEFAULT_TOP       = -1;
+  /** default width */
+  public final static int    WIDTH             = 800;
+  /** default height */
+  public final static int    HEIGHT            = 600;
+
+  protected Container             parent;
+  protected JTabbedPane           tabbedPane;
+  protected JMenuBar              menuBar;
+  protected JMenu                 menuFile;
+  protected JMenuItem             menuFileOpen;
+  protected JMenuItem             menuFileSave;
+  protected JMenuItem             menuFileSaveAs;
+  protected JMenuItem             menuFileClose;
+  protected JMenuItem             menuFileCloseAll;
+  protected JMenuItem             menuFileProperties;
+  protected JMenuItem             menuFileExit;
+  protected JMenu                 menuEdit;
+  protected JMenuItem             menuEditUndo;
+  protected JMenuItem             menuEditCopy;
+  protected JMenuItem             menuEditSearch;
+  protected JMenuItem             menuEditClearSearch;
+  protected JMenuItem             menuEditDeleteAttribute;
+  protected JMenuItem             menuEditDeleteAttributes;
+  protected JMenuItem             menuEditRenameAttribute;
+  protected JMenuItem             menuEditAttributeAsClass;
+  protected JMenuItem             menuEditDeleteInstance;
+  protected JMenuItem             menuEditDeleteInstances;
+  protected JMenuItem             menuEditSortInstances;
+  protected JMenu                 menuView;
+  protected JMenuItem             menuViewAttributes;
+  protected JMenuItem             menuViewValues;
+  protected JMenuItem             menuViewOptimalColWidths;
+  
+  protected ConverterFileChooser  fileChooser;
+  protected String                frameTitle;
+  protected boolean               confirmExit;
+  protected int                   width;
+  protected int                   height;
+  protected int                   top;
+  protected int                   left;
+  protected boolean               exitOnClose;
+  
+  /**
+   * initializes the object
+   * 
+   * @param parentFrame		the parent frame (JFrame or JInternalFrame)
+   */
+  public ArffViewerMainPanel(Container parentFrame) {
+    parent     = parentFrame;
+    frameTitle = "ARFF-Viewer"; 
+    createPanel();
+  }
+  
+  /**
+   * creates all the components in the panel
+   */
+  protected void createPanel() {
+    // basic setup
+    setSize(WIDTH, HEIGHT);
+    
+    setConfirmExit(false);
+    setLayout(new BorderLayout());
+    
+    // file dialog
+    fileChooser = new ConverterFileChooser(new File(System.getProperty("user.dir")));
+    fileChooser.setMultiSelectionEnabled(true);
+    
+    // menu
+    menuBar        = new JMenuBar();
+    menuFile       = new JMenu("File");
+    menuFileOpen   = new JMenuItem("Open...", ComponentHelper.getImageIcon("open.gif"));
+    menuFileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK));
+    menuFileOpen.addActionListener(this);
+    menuFileSave   = new JMenuItem("Save", ComponentHelper.getImageIcon("save.gif"));
+    menuFileSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
+    menuFileSave.addActionListener(this);
+    menuFileSaveAs = new JMenuItem("Save as...", ComponentHelper.getImageIcon("empty.gif"));
+    menuFileSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK));
+    menuFileSaveAs.addActionListener(this);
+    menuFileClose  = new JMenuItem("Close", ComponentHelper.getImageIcon("empty.gif"));
+    menuFileClose.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.CTRL_MASK));
+    menuFileClose.addActionListener(this);
+    menuFileCloseAll = new JMenuItem("Close all", ComponentHelper.getImageIcon("empty.gif"));
+    menuFileCloseAll.addActionListener(this);
+    menuFileProperties  = new JMenuItem("Properties", ComponentHelper.getImageIcon("empty.gif"));
+    menuFileProperties.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK));
+    menuFileProperties.addActionListener(this);
+    menuFileExit   = new JMenuItem("Exit", ComponentHelper.getImageIcon("forward.gif"));
+    menuFileExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.ALT_MASK));
+    menuFileExit.addActionListener(this);
+    menuFile.add(menuFileOpen);
+    menuFile.add(menuFileSave);
+    menuFile.add(menuFileSaveAs);
+    menuFile.add(menuFileClose);
+    menuFile.add(menuFileCloseAll);
+    menuFile.addSeparator();
+    menuFile.add(menuFileProperties);
+    menuFile.addSeparator();
+    menuFile.add(menuFileExit);
+    menuBar.add(menuFile);
+    
+    menuEdit       = new JMenu("Edit");
+    menuEditUndo   = new JMenuItem("Undo", ComponentHelper.getImageIcon("undo.gif"));
+    menuEditUndo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_MASK));
+    menuEditUndo.addActionListener(this);
+    menuEditCopy   = new JMenuItem("Copy", ComponentHelper.getImageIcon("copy.gif"));
+    menuEditCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.CTRL_MASK));
+    menuEditCopy.addActionListener(this);
+    menuEditSearch   = new JMenuItem("Search...", ComponentHelper.getImageIcon("find.gif"));
+    menuEditSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK));
+    menuEditSearch.addActionListener(this);
+    menuEditClearSearch   = new JMenuItem("Clear search", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditClearSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK));
+    menuEditClearSearch.addActionListener(this);
+    menuEditRenameAttribute = new JMenuItem("Rename attribute", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditRenameAttribute.addActionListener(this);
+    menuEditAttributeAsClass = new JMenuItem("Attribute as class", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditAttributeAsClass.addActionListener(this);
+    menuEditDeleteAttribute = new JMenuItem("Delete attribute", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditDeleteAttribute.addActionListener(this);
+    menuEditDeleteAttributes = new JMenuItem("Delete attributes", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditDeleteAttributes.addActionListener(this);
+    menuEditDeleteInstance = new JMenuItem("Delete instance", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditDeleteInstance.addActionListener(this);
+    menuEditDeleteInstances = new JMenuItem("Delete instances", ComponentHelper.getImageIcon("empty.gif"));
+    menuEditDeleteInstances.addActionListener(this);
+    menuEditSortInstances = new JMenuItem("Sort data (ascending)", ComponentHelper.getImageIcon("sort.gif"));
+    menuEditSortInstances.addActionListener(this);
+    menuEdit.add(menuEditUndo);
+    menuEdit.addSeparator();
+    menuEdit.add(menuEditCopy);
+    menuEdit.addSeparator();
+    menuEdit.add(menuEditSearch);
+    menuEdit.add(menuEditClearSearch);
+    menuEdit.addSeparator();
+    menuEdit.add(menuEditRenameAttribute);
+    menuEdit.add(menuEditAttributeAsClass);
+    menuEdit.add(menuEditDeleteAttribute);
+    menuEdit.add(menuEditDeleteAttributes);
+    menuEdit.addSeparator();
+    menuEdit.add(menuEditDeleteInstance);
+    menuEdit.add(menuEditDeleteInstances);
+    menuEdit.add(menuEditSortInstances);
+    menuBar.add(menuEdit);
+    
+    menuView       = new JMenu("View");
+    menuViewAttributes   = new JMenuItem("Attributes...", ComponentHelper.getImageIcon("objects.gif"));
+    menuViewAttributes.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK));
+    menuViewAttributes.addActionListener(this);
+    menuViewValues   = new JMenuItem("Values...", ComponentHelper.getImageIcon("properties.gif"));
+    menuViewValues.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_MASK + KeyEvent.SHIFT_MASK));
+    menuViewValues.addActionListener(this);
+    menuViewOptimalColWidths = new JMenuItem("Optimal column width (all)", ComponentHelper.getImageIcon("resize.gif"));
+    menuViewOptimalColWidths.addActionListener(this);
+    menuView.add(menuViewAttributes);
+    menuView.add(menuViewValues);
+    menuView.addSeparator();
+    menuView.add(menuViewOptimalColWidths);
+    menuBar.add(menuView);
+    
+    // tabbed pane
+    tabbedPane  = new JTabbedPane();
+    tabbedPane.addChangeListener(this);
+    add(tabbedPane, BorderLayout.CENTER);
+    
+    updateMenu();
+    updateFrameTitle();
+  }
+  
+  /**
+   * returns the parent frame, if it's a JFrame, otherwise null
+   * 
+   * @return		the parent frame
+   */
+  public JFrame getParentFrame() {
+    if (parent instanceof JFrame)
+      return (JFrame) parent;
+    else
+      return null;
+  }
+  
+  /**
+   * returns the parent frame, if it's a JInternalFrame, otherwise null
+   * 
+   * @return		the parent frame
+   */
+  public JInternalFrame getParentInternalFrame() {
+    if (parent instanceof JInternalFrame)
+      return (JInternalFrame) parent;
+    else
+      return null;
+  }
+  
+  /**
+   * sets the new parent frame
+   * 
+   * @param value	the parent frame
+   */
+  public void setParent(Container value) {
+    parent = value;
+  }
+  
+  /**
+   * returns the menu bar to be added in a frame
+   * 
+   * @return		the menu bar
+   */
+  public JMenuBar getMenu() {
+    return menuBar;
+  }
+  
+  /**
+   * returns the tabbedpane instance
+   * 
+   * @return		the tabbed pane
+   */
+  public JTabbedPane getTabbedPane() {
+    return tabbedPane;
+  }
+  
+  /**
+   * whether to present a MessageBox on Exit or not
+   * @param confirm           whether a MessageBox pops up or not to confirm
+   *                          exit
+   */
+  public void setConfirmExit(boolean confirm) {
+    confirmExit = confirm;
+  }
+  
+  /**
+   * returns the setting of whether to display a confirm messagebox or not
+   * on exit
+   * @return                  whether a messagebox is displayed or not
+   */
+  public boolean getConfirmExit() {
+    return confirmExit;
+  }
+
+  /**
+   * whether to do a System.exit(0) on close
+   * 
+   * @param value	enables/disables a System.exit(0) on close
+   */
+  public void setExitOnClose(boolean value) {
+    exitOnClose = value;
+  }
+
+  /**
+   * returns TRUE if a System.exit(0) is done on a close
+   * 
+   * @return		true if a System.exit(0) is done on close
+   */
+  public boolean getExitOnClose() {
+    return exitOnClose;
+  }
+  
+  /**
+   * validates and repaints the frame
+   */
+  public void refresh() {
+    validate();
+    repaint();
+  }
+  
+  /**
+   * returns the title (incl. filename) for the frame
+   * 
+   * @return		the frame title
+   */
+  public String getFrameTitle() {
+    if (getCurrentFilename().equals(""))
+      return frameTitle;
+    else
+      return frameTitle + " - " + getCurrentFilename();
+  }
+  
+  /**
+   * sets the title of the parent frame, if one was provided
+   */
+  public void updateFrameTitle() {
+    if (getParentFrame() != null)
+      getParentFrame().setTitle(getFrameTitle());
+    if (getParentInternalFrame() != null)
+      getParentInternalFrame().setTitle(getFrameTitle());
+  }
+  
+  /**
+   * sets the enabled/disabled state of the menu 
+   */
+  protected void updateMenu() {
+    boolean       fileOpen;
+    boolean       isChanged;
+    boolean       canUndo;
+    
+    fileOpen  = (getCurrentPanel() != null);
+    isChanged = fileOpen && (getCurrentPanel().isChanged());
+    canUndo   = fileOpen && (getCurrentPanel().canUndo());
+    
+    // File
+    menuFileOpen.setEnabled(true);
+    menuFileSave.setEnabled(isChanged);
+    menuFileSaveAs.setEnabled(fileOpen);
+    menuFileClose.setEnabled(fileOpen);
+    menuFileCloseAll.setEnabled(fileOpen);
+    menuFileProperties.setEnabled(fileOpen);
+    menuFileExit.setEnabled(true);
+    // Edit
+    menuEditUndo.setEnabled(canUndo);
+    menuEditCopy.setEnabled(fileOpen);
+    menuEditSearch.setEnabled(fileOpen);
+    menuEditClearSearch.setEnabled(fileOpen);
+    menuEditAttributeAsClass.setEnabled(fileOpen);
+    menuEditRenameAttribute.setEnabled(fileOpen);
+    menuEditDeleteAttribute.setEnabled(fileOpen);
+    menuEditDeleteAttributes.setEnabled(fileOpen);
+    menuEditDeleteInstance.setEnabled(fileOpen);
+    menuEditDeleteInstances.setEnabled(fileOpen);
+    menuEditSortInstances.setEnabled(fileOpen);
+    // View
+    menuViewAttributes.setEnabled(fileOpen);
+    menuViewValues.setEnabled(fileOpen);
+    menuViewOptimalColWidths.setEnabled(fileOpen);
+  }
+  
+  /**
+   * sets the title of the tab that contains the given component
+   * 
+   * @param component		the component to set the title for
+   */
+  protected void setTabTitle(JComponent component) {
+    int            index;
+    
+    if (!(component instanceof ArffPanel))
+      return;
+    
+    index = tabbedPane.indexOfComponent(component);
+    if (index == -1)
+      return;
+    
+    tabbedPane.setTitleAt(index, ((ArffPanel) component).getTitle());
+    updateFrameTitle();
+  }
+  
+  /**
+   * returns the number of panels currently open
+   * 
+   * @return		the number of open panels
+   */
+  public int getPanelCount() {
+    return tabbedPane.getTabCount();
+  }
+  
+  /**
+   * returns the specified panel, <code>null</code> if index is out of bounds  
+   * 
+   * @param index	the index of the panel
+   * @return		the panel
+   */
+  public ArffPanel getPanel(int index) {
+    if ((index >= 0) && (index < getPanelCount()))
+      return (ArffPanel) tabbedPane.getComponentAt(index);
+    else
+      return null;
+  }
+  
+  /**
+   * returns the currently selected tab index
+   * 
+   * @return		the index of the currently selected tab
+   */
+  public int getCurrentIndex() {
+    return tabbedPane.getSelectedIndex();
+  }
+  
+  /**
+   * returns the currently selected panel
+   * 
+   * @return		the currently selected panel
+   */
+  public ArffPanel getCurrentPanel() {
+    return getPanel(getCurrentIndex());
+  }
+  
+  /**
+   * checks whether a panel is currently selected
+   * 
+   * @return		true if a panel is currently selected
+   */
+  public boolean isPanelSelected() {
+    return (getCurrentPanel() != null);
+  }
+  
+  /**
+   * returns the filename of the specified panel 
+   * 
+   * @param index	the index of the panel
+   * @return		the filename for the panel
+   */
+  public String getFilename(int index) {
+    String            result;
+    ArffPanel         panel;
+    
+    result = "";
+    panel  = getPanel(index);
+    
+    if (panel != null)
+      result = panel.getFilename();
+    
+    return result;
+  }
+  
+  /**
+   * returns the filename of the current tab
+   * 
+   * @return		the current filename
+   */
+  public String getCurrentFilename() {
+    return getFilename(getCurrentIndex());
+  }
+  
+  /**
+   * sets the filename of the specified panel
+   * 
+   * @param index	the index of the panel
+   * @param filename	the new filename
+   */
+  public void setFilename(int index, String filename) {
+    ArffPanel         panel;
+    
+    panel = getPanel(index);
+    
+    if (panel != null) {
+      panel.setFilename(filename);
+      setTabTitle(panel);
+    }
+  }
+  
+  /**
+   * sets the filename of the current tab
+   * 
+   * @param filename	the new filename
+   */
+  public void setCurrentFilename(String filename) {
+    setFilename(getCurrentIndex(), filename);
+  }
+  
+  /**
+   * if the file is changed it pops up a dialog whether to change the
+   * settings. if the project wasn't changed or saved it returns TRUE
+   * 
+   * @return 		true if project wasn't changed or saved
+   */
+  protected boolean saveChanges() {
+    return saveChanges(true);
+  }
+  
+  /**
+   * if the file is changed it pops up a dialog whether to change the
+   * settings. if the project wasn't changed or saved it returns TRUE
+   * 
+   * @param showCancel	whether we have YES/NO/CANCEL or only YES/NO
+   * @return 		true if project wasn't changed or saved
+   */
+  protected boolean saveChanges(boolean showCancel) {
+    int            button;
+    boolean        result;
+    
+    if (!isPanelSelected())
+      return true;
+    
+    result = !getCurrentPanel().isChanged();
+    
+    if (getCurrentPanel().isChanged()) {
+      try {
+        if (showCancel)
+          button = ComponentHelper.showMessageBox(
+              this,
+              "Changed",
+              "The file is not saved - Do you want to save it?",
+              JOptionPane.YES_NO_CANCEL_OPTION,
+              JOptionPane.QUESTION_MESSAGE );
+        else
+          button = ComponentHelper.showMessageBox(
+              this,
+              "Changed",
+              "The file is not saved - Do you want to save it?",
+              JOptionPane.YES_NO_OPTION,
+              JOptionPane.QUESTION_MESSAGE );
+      }
+      catch (Exception e) {
+        button = JOptionPane.CANCEL_OPTION; 
+      }
+      
+      switch (button) {
+        case JOptionPane.YES_OPTION:
+          saveFile();
+          result = !getCurrentPanel().isChanged();
+          break;
+        case JOptionPane.NO_OPTION:
+          result = true;
+          break;
+        case JOptionPane.CANCEL_OPTION: 
+          result = false;
+          break;
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * loads the specified file
+   * 
+   * @param filename	the file to load
+   */
+  public void loadFile(String filename) {
+    ArffPanel         panel;
+
+    panel    = new ArffPanel(filename);
+    panel.addChangeListener(this);
+    tabbedPane.addTab(panel.getTitle(), panel);
+    tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
+  }
+  
+  /**
+   * loads the specified file into the table
+   */
+  public void loadFile() {
+    int               retVal;
+    int               i;
+    String            filename;
+    
+    retVal = fileChooser.showOpenDialog(this);
+    if (retVal != ConverterFileChooser.APPROVE_OPTION)
+      return;
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+    
+    for (i = 0; i< fileChooser.getSelectedFiles().length; i++) {
+      filename = fileChooser.getSelectedFiles()[i].getAbsolutePath();
+      loadFile(filename);
+    }
+    
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+  }
+  
+  /**
+   * saves the current data into a file
+   */
+  public void saveFile() {
+    ArffPanel           panel;
+    String              filename;
+    AbstractSaver       saver;
+    
+    // no panel? -> exit
+    panel = getCurrentPanel();
+    if (panel == null)
+      return;
+    
+    filename = panel.getFilename();
+    if (filename.equals(ArffPanel.TAB_INSTANCES)) {
+      saveFileAs();
+    }
+    else {
+      saver = fileChooser.getSaver();
+      try {
+	saver.setInstances(panel.getInstances());
+	saver.writeBatch();
+	panel.setChanged(false);
+	setCurrentFilename(filename);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * saves the current data into a new file
+   */
+  public void saveFileAs() {
+    int                  retVal;
+    ArffPanel            panel;
+    
+    // no panel? -> exit
+    panel = getCurrentPanel();
+    if (panel == null) {
+      System.out.println("nothing selected!");
+      return;
+    }
+    
+    if (!getCurrentFilename().equals("")) {
+      try {
+        fileChooser.setSelectedFile(new File(getCurrentFilename()));
+      }
+      catch (Exception e) {
+        // ignore
+      }
+    }
+    
+    // set filter for savers
+    try {
+      fileChooser.setCapabilitiesFilter(Capabilities.forInstances(panel.getInstances()));
+    }
+    catch (Exception e) {
+      fileChooser.setCapabilitiesFilter(null);
+    }
+
+    retVal = fileChooser.showSaveDialog(this);
+    if (retVal != ConverterFileChooser.APPROVE_OPTION)
+      return;
+    
+    panel.setChanged(false);
+    setCurrentFilename(fileChooser.getSelectedFile().getAbsolutePath());
+    saveFile();
+  }
+  
+  /**
+   * closes the current tab
+   */
+  public void closeFile() {
+    closeFile(true);
+  }
+  
+  /**
+   * closes the current tab
+   * @param showCancel           whether to show an additional CANCEL button
+   *                             in the "Want to save changes"-dialog
+   * @see                        #saveChanges(boolean)
+   */
+  public void closeFile(boolean showCancel) {
+    if (getCurrentIndex() == -1)
+      return;
+    
+    if (!saveChanges(showCancel))
+      return;
+    
+    tabbedPane.removeTabAt(getCurrentIndex());
+    updateFrameTitle();
+    System.gc();
+  }
+  
+  /**
+   * closes all open files
+   */
+  public void closeAllFiles() {
+    while (tabbedPane.getTabCount() > 0) {
+      if (!saveChanges(true))
+        return;
+      
+      tabbedPane.removeTabAt(getCurrentIndex());
+      updateFrameTitle();
+      System.gc();
+    }
+  }
+  
+  /**
+   * displays some properties of the instances
+   */
+  public void showProperties() {
+    ArffPanel             panel;
+    ListSelectorDialog    dialog;
+    Vector                props;
+    Instances             inst;
+    
+    panel = getCurrentPanel();
+    if (panel == null)
+      return;
+    
+    inst  = panel.getInstances();
+    if (inst == null)
+      return;
+    if (inst.classIndex() < 0)
+      inst.setClassIndex(inst.numAttributes() - 1);
+    
+    // get some data
+    props = new Vector();
+    props.add("Filename: " + panel.getFilename());
+    props.add("Relation name: " + inst.relationName());
+    props.add("# of instances: " + inst.numInstances());
+    props.add("# of attributes: " + inst.numAttributes());
+    props.add("Class attribute: " + inst.classAttribute().name());
+    props.add("# of class labels: " + inst.numClasses());
+    
+    dialog = new ListSelectorDialog(getParentFrame(), new JList(props));
+    dialog.showDialog();
+  }
+  
+  /**
+   * closes the window, i.e., if the parent is not null and implements
+   * the WindowListener interface it calls the windowClosing method
+   */
+  public void close() {
+    if (getParentInternalFrame() != null)
+      getParentInternalFrame().doDefaultCloseAction();
+    else if (getParentFrame() != null)
+      ((Window) getParentFrame()).dispatchEvent(
+	  new WindowEvent(
+	      (Window) getParentFrame(), WindowEvent.WINDOW_CLOSING));
+  }
+  
+  /**
+   * undoes the last action 
+   */
+  public void undo() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().undo();
+  }
+  
+  /**
+   * copies the content of the selection to the clipboard
+   */
+  public void copyContent() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().copyContent();
+  }
+  
+  /**
+   * searches for a string in the cells
+   */
+  public void search() {
+    if (!isPanelSelected())
+      return;
+
+    getCurrentPanel().search();
+  }
+  
+  /**
+   * clears the search, i.e. resets the found cells
+   */
+  public void clearSearch() {
+    if (!isPanelSelected())
+      return;
+
+    getCurrentPanel().clearSearch();
+  }
+  
+  /**
+   * renames the current selected Attribute
+   */
+  public void renameAttribute() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().renameAttribute();
+  }
+  
+  /**
+   * sets the current selected Attribute as class attribute, i.e. it moves it
+   * to the end of the attributes
+   */
+  public void attributeAsClass() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().attributeAsClass();
+  }
+  
+  /**
+   * deletes the current selected Attribute or several chosen ones
+   * 
+   * @param multiple	whether to delete myultiple attributes
+   */
+  public void deleteAttribute(boolean multiple) {
+    if (!isPanelSelected())
+      return;
+    
+    if (multiple)
+      getCurrentPanel().deleteAttributes();
+    else
+      getCurrentPanel().deleteAttribute();
+  }
+  
+  /**
+   * deletes the current selected Instance or several chosen ones
+   * 
+   * @param multiple		whether to delete multiple instances
+   */
+  public void deleteInstance(boolean multiple) {
+    if (!isPanelSelected())
+      return;
+    
+    if (multiple)
+      getCurrentPanel().deleteInstances();
+    else
+      getCurrentPanel().deleteInstance();
+  }
+  
+  /**
+   * sorts the current selected attribute
+   */
+  public void sortInstances() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().sortInstances();
+  }
+  
+  /**
+   * displays all the attributes, returns the selected item or NULL if canceled
+   * 
+   * @return		the name of the selected attribute
+   */
+  public String showAttributes() {
+    ArffSortedTableModel     model;
+    ListSelectorDialog  dialog;
+    int                 i;
+    JList               list;
+    String              name;
+    int                 result;
+    
+    if (!isPanelSelected())
+      return null;
+    
+    list   = new JList(getCurrentPanel().getAttributes());
+    dialog = new ListSelectorDialog(getParentFrame(), list);
+    result = dialog.showDialog();
+    
+    if (result == ListSelectorDialog.APPROVE_OPTION) {
+      model = (ArffSortedTableModel) getCurrentPanel().getTable().getModel();
+      name  = list.getSelectedValue().toString();
+      i     = model.getAttributeColumn(name);
+      JTableHelper.scrollToVisible(getCurrentPanel().getTable(), 0, i);
+      getCurrentPanel().getTable().setSelectedColumn(i);
+      return name;
+    }
+    else {
+      return null;
+    }
+  }
+  
+  /**
+   * displays all the distinct values for an attribute
+   */
+  public void showValues() {
+    String                attribute;
+    ArffSortedTableModel       model;
+    ArffTable             table;
+    HashSet               values;
+    Vector                items;
+    Iterator              iter;
+    ListSelectorDialog    dialog;
+    int                   i;
+    int                   col;
+    
+    // choose attribute to retrieve values for
+    attribute = showAttributes();
+    if (attribute == null)
+      return;
+    
+    table  = (ArffTable) getCurrentPanel().getTable();
+    model  = (ArffSortedTableModel) table.getModel();
+    
+    // get column index
+    col    = -1;
+    for (i = 0; i < table.getColumnCount(); i++) {
+      if (table.getPlainColumnName(i).equals(attribute)) {
+        col = i;
+        break;
+      }
+    }
+    // not found?
+    if (col == -1)
+      return;
+    
+    // get values
+    values = new HashSet();
+    items  = new Vector();
+    for (i = 0; i < model.getRowCount(); i++)
+      values.add(model.getValueAt(i, col).toString());
+    if (values.isEmpty())
+      return;
+    iter = values.iterator();
+    while (iter.hasNext())
+      items.add(iter.next());
+    Collections.sort(items);
+    
+    dialog = new ListSelectorDialog(getParentFrame(), new JList(items));
+    dialog.showDialog();
+  }
+  
+  /**
+   * sets the optimal column width for all columns
+   */
+  public void setOptimalColWidths() {
+    if (!isPanelSelected())
+      return;
+    
+    getCurrentPanel().setOptimalColWidths();
+  }
+  
+  /**
+   * invoked when an action occurs
+   * 
+   * @param e		the action event
+   */
+  public void actionPerformed(ActionEvent e) {
+    Object          o;
+    
+    o = e.getSource();
+    
+    if (o == menuFileOpen)
+      loadFile();
+    else if (o == menuFileSave)
+      saveFile();
+    else if (o == menuFileSaveAs)
+      saveFileAs();
+    else if (o == menuFileClose)
+      closeFile();
+    else if (o == menuFileCloseAll)
+      closeAllFiles();
+    else if (o == menuFileProperties)
+      showProperties();
+    else if (o == menuFileExit)
+      close();
+    else if (o == menuEditUndo)
+      undo();
+    else if (o == menuEditCopy)
+      copyContent();
+    else if (o == menuEditSearch)
+      search();
+    else if (o == menuEditClearSearch)
+      clearSearch();
+    else if (o == menuEditDeleteAttribute)
+      deleteAttribute(false);
+    else if (o == menuEditDeleteAttributes)
+      deleteAttribute(true);
+    else if (o == menuEditRenameAttribute)
+      renameAttribute();
+    else if (o == menuEditAttributeAsClass)
+      attributeAsClass();
+    else if (o == menuEditDeleteInstance)
+      deleteInstance(false);
+    else if (o == menuEditDeleteInstances)
+      deleteInstance(true);
+    else if (o == menuEditSortInstances)
+      sortInstances();
+    else if (o == menuViewAttributes)
+      showAttributes();
+    else if (o == menuViewValues)
+      showValues();
+    else if (o == menuViewOptimalColWidths)
+      setOptimalColWidths();
+    
+    updateMenu();
+  }
+  
+  /**
+   * Invoked when the target of the listener has changed its state.
+   * 
+   * @param e		the change event
+   */
+  public void stateChanged(ChangeEvent e) {
+    updateFrameTitle();
+    updateMenu();
+    
+    // did the content of panel change? -> change title of tab
+    if (e.getSource() instanceof JComponent)
+      setTabTitle((JComponent) e.getSource());
+  }
+  
+  /**
+   * returns only the classname
+   * 
+   * @return		the classname
+   */
+  public String toString() {
+    return this.getClass().getName();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSink.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSink.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSink.java	(revision 29)
@@ -0,0 +1,204 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractDataSink.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract class for objects that store instances to some destination.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5753 $
+ * @since 1.0
+ * @see JPanel
+ * @see Serializable
+ */
+
+public abstract class AbstractDataSink
+  extends JPanel
+  implements DataSink, BeanCommon, Visible, 
+	     DataSourceListener, TrainingSetListener,
+	     TestSetListener, InstanceListener, ThresholdDataListener,
+	     Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3956528599473814287L;
+
+  /**
+   * Default visual for data sources
+   */
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractDataSink", 
+		   BeanVisual.ICON_PATH+"DefaultDataSink.gif",
+		   BeanVisual.ICON_PATH+"DefaultDataSink_animated.gif");
+
+  /**
+   * Non null if this object is a target for any events.
+   * Provides for the simplest case when only one incomming connection
+   * is allowed. Subclasses can overide the appropriate BeanCommon methods
+   * to change this behaviour and allow multiple connections if desired
+   */
+  protected Object m_listenee = null;
+
+  protected transient weka.gui.Logger m_logger = null;
+
+  public AbstractDataSink() {
+    useDefaultVisual();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public abstract void acceptTrainingSet(TrainingSetEvent e);
+
+  /**
+   * Accept a test set
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public abstract void acceptTestSet(TestSetEvent e);
+
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public abstract void acceptDataSet(DataSetEvent e);
+  
+  /**
+   * Accept a threshold data set
+   *
+   * @param e a <code>ThresholdDataEvent</code> value
+   */
+  public abstract void acceptDataSet(ThresholdDataEvent e);
+  
+  /**
+   * Accept an instance
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  public abstract void acceptInstance(InstanceEvent e);
+
+  /**
+   * Set the visual for this data source
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual being used by this data source.
+   *
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default images for a data source
+   *
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataSink.gif",
+		       BeanVisual.ICON_PATH+"DefaultDataSink_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+  
+  /**
+   * Set a log for this bean
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   * Subclass must implement
+   */
+  public abstract void stop();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSinkBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSinkBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSinkBeanInfo.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractDataSinkBeanInfo.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the AbstractDataSink
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class AbstractDataSinkBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSource.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSource.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSource.java	(revision 29)
@@ -0,0 +1,224 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractDataSource.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract class for objects that can provide instances from some source
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ * @since 1.0
+ * @see JPanel
+ * @see DataSource
+ * @see Serializable
+ */
+public abstract class AbstractDataSource
+  extends JPanel
+  implements DataSource, Visible, Serializable, BeanContextChild {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4127257701890044793L;
+
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+  
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+
+  /**
+   * Default visual for data sources
+   */
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractDataSource", 
+		   BeanVisual.ICON_PATH+"DefaultDataSource.gif",
+		   BeanVisual.ICON_PATH+"DefaultDataSource_animated.gif");
+  
+  /**
+   * Objects listening for events from data sources
+   */
+  protected Vector m_listeners;
+
+  /**
+   * Creates a new <code>AbstractDataSource</code> instance.
+   *
+   */
+  public AbstractDataSource() {    
+    useDefaultVisual();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    m_listeners = new Vector();
+  }
+
+  /**
+   * Add a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void addDataSourceListener(DataSourceListener dsl) {
+    m_listeners.addElement(dsl);
+  }
+  
+  /**
+   * Remove a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void removeDataSourceListener(DataSourceListener dsl) {
+    m_listeners.remove(dsl);
+  }
+
+  /**
+   * Add an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void addInstanceListener(InstanceListener dsl) {
+    m_listeners.addElement(dsl);
+  }
+  
+  /**
+   * Remove an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void removeInstanceListener(InstanceListener dsl) {
+    m_listeners.remove(dsl);
+  }
+
+  /**
+   * Set the visual for this data source
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual being used by this data source.
+   *
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default images for a data source
+   *
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataSource.gif",
+		       BeanVisual.ICON_PATH+"DefaultDataSource_animated.gif");
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+  }
+
+  /**
+   * Return the bean context (if any) that this bean is embedded in
+   *
+   * @return a <code>BeanContext</code> value
+   */
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  /**
+   * Add a property change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(String name,
+					PropertyChangeListener pcl) {
+    m_bcSupport.addPropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Remove a property change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(String name,
+					   PropertyChangeListener pcl) {
+    m_bcSupport.removePropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Add a vetoable change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void addVetoableChangeListener(String name,
+				       VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+  
+  /**
+   * Remove a vetoable change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void removeVetoableChangeListener(String name,
+					   VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+}
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSourceBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSourceBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractDataSourceBeanInfo.java	(revision 29)
@@ -0,0 +1,61 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractDataSourceBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for AbstractDataSource. All beans that extend
+ * AbstractDataSource might want to extend this class
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see SimpleBeanInfo
+ */
+public class AbstractDataSourceBeanInfo extends SimpleBeanInfo {
+
+  /**
+   * Get the event set descriptors pertinent to data sources
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds =  
+      { new EventSetDescriptor(DataSource.class, 
+			       "dataSet",
+			       DataSourceListener.class,
+			       "acceptDataSet"),
+	new EventSetDescriptor(DataSource.class, 
+			       "instance",
+			       InstanceListener.class,
+			       "acceptInstance")
+         };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractEvaluator.java	(revision 29)
@@ -0,0 +1,167 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractEvaluator.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract class for objects that can provide some kind of evaluation for
+ * classifier, clusterers etc.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ * @since 1.0
+ * @see JPanel
+ * @see Visible
+ * @see Serializable
+ */
+public abstract class AbstractEvaluator
+  extends JPanel
+  implements Visible, BeanCommon, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3983303541814121632L;
+  
+  /**
+   * Default visual for evaluators
+   */
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractEvaluator", 
+		   BeanVisual.ICON_PATH+"DefaultEvaluator.gif",
+		   BeanVisual.ICON_PATH+"DefaultEvaluator_animated.gif");
+
+  protected Object m_listenee = null;
+
+  protected transient Logger m_logger = null;
+
+  /**
+   * Constructor
+   */
+  public AbstractEvaluator() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Set the visual
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  /**
+   * Use the default images for an evaluator
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultEvaluator.gif",
+		       BeanVisual.ICON_PATH+"DefaultEvaluator_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event name
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event named
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+  
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   * Subclass must implement
+   */
+  public abstract void stop();
+
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducer.java	(revision 29)
@@ -0,0 +1,192 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTestSetProducer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract class for TestSetProducers that contains default
+ * implementations of add/remove listener methods and defualt
+ * visual representation.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ * @since 1.0
+ * @see TestSetProducer
+ */
+public abstract class AbstractTestSetProducer
+  extends JPanel
+  implements TestSetProducer, Visible, 
+	     BeanCommon, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7905764845789349839L;
+
+  /**
+   * Objects listening to us
+   */
+  protected Vector m_listeners = new Vector();
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractTestSetProducer", 
+		    BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		    BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+
+  /**
+   * non null if this object is a target for any events.
+   */
+  protected Object m_listenee = null;
+
+  /**
+   * Logger
+   */
+  protected transient weka.gui.Logger m_logger = null;
+
+  /**
+   * Creates a new <code>AbstractTestSetProducer</code> instance.
+   */
+  public AbstractTestSetProducer() {
+
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Add a listener for test sets
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void addTestSetListener(TestSetListener tsl) {
+    m_listeners.addElement(tsl);
+  }
+
+  /**
+   * Remove a listener for test sets
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void removeTestSetListener(TestSetListener tsl) {
+    m_listeners.removeElement(tsl);
+  }
+
+  /**
+   * Set the visual for this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+  
+  /**
+   * Get the visual for this bean
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  /**
+   * Use the default visual for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		       BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event name
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+      
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   * Subclass must implement
+   */
+  public abstract void stop();
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTestSetProducerBeanInfo.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTestSetProducerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for AbstractTestSetProducer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class AbstractTestSetProducerBeanInfo extends SimpleBeanInfo {
+
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(TestSetProducer.class, 
+			       "testSet", 
+			       TestSetListener.class, 
+			       "acceptTestSet") 
+	  };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducer.java	(revision 29)
@@ -0,0 +1,216 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTrainAndTestSetProducer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract base class for TrainAndTestSetProducers that contains default
+ * implementations of add/remove listener methods and defualt
+ * visual representation.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ */
+public abstract class AbstractTrainAndTestSetProducer 
+  extends JPanel
+  implements Visible, TrainingSetProducer, TestSetProducer, 
+	     BeanCommon, Serializable, DataSourceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1809339823613492037L;
+
+  /**
+   * Objects listening for trainin set events
+   */
+  protected Vector m_trainingListeners = new Vector();
+
+  /**
+   * Objects listening for test set events
+   */
+  protected Vector m_testListeners = new Vector();
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractTrainingSetProducer", 
+		   BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		   BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+
+  /**
+   * non null if this object is a target for any events.
+   */
+  protected Object m_listenee = null;
+
+  protected transient weka.gui.Logger m_logger = null;
+
+  /**
+   * Creates a new <code>AbstractTrainAndTestSetProducer</code> instance.
+   */
+  public AbstractTrainAndTestSetProducer() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Subclass must implement
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public abstract void acceptDataSet(DataSetEvent e);
+
+  /**
+   * Add a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
+    m_trainingListeners.addElement(tsl);
+  }
+
+  /**
+   * Remove a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
+    m_trainingListeners.removeElement(tsl);
+  }
+
+  /**
+   * Add a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void addTestSetListener(TestSetListener tsl) {
+    m_testListeners.addElement(tsl);
+  }
+
+  /**
+   * Remove a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void removeTestSetListener(TestSetListener tsl) {
+    m_testListeners.removeElement(tsl);
+  }
+
+  /**
+   * Set the visual for this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual for this bean
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  /**
+   * Use the default visual for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		       BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+  
+  /**
+   * Set a log for this bean
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   * Subclass must implement
+   */
+  public abstract void stop();
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainAndTestSetProducerBeanInfo.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTrainAndTestSetProducerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for AbstractTrainAndTestSetProducers
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class AbstractTrainAndTestSetProducerBeanInfo extends SimpleBeanInfo {
+
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(TrainingSetProducer.class, 
+			       "trainingSet", 
+			       TrainingSetListener.class, 
+			       "acceptTrainingSet"),
+	new EventSetDescriptor(TestSetProducer.class, 
+			       "testSet", 
+			       TestSetListener.class, 
+			       "acceptTestSet")  };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducer.java	(revision 29)
@@ -0,0 +1,192 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTrainingSetProducer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Abstract class for TrainingSetProducers that contains default
+ * implementations of add/remove listener methods and default
+ * visual representation
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.7 $
+ * @since 1.0
+ * @see TrainingSetProducer
+ */
+public abstract class AbstractTrainingSetProducer
+  extends JPanel
+  implements TrainingSetProducer, Visible, 
+	     BeanCommon, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7842746199524591125L;
+
+  /**
+   * Objects listening for training set events
+   */
+  protected Vector m_listeners = new Vector();
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractTraingSetProducer", 
+		   BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		   BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+
+  
+  /**
+   * non null if this object is a target for any events.
+   */
+  protected Object m_listenee = null;
+
+  protected transient weka.gui.Logger m_logger = null;
+
+  /**
+   * Creates a new <code>AbstractTrainingSetProducer</code> instance.
+   */
+  public AbstractTrainingSetProducer() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Add a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
+    m_listeners.addElement(tsl);
+  }
+
+  /**
+   * Remove a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
+    m_listeners.removeElement(tsl);
+  }
+
+  /**
+   * Set the visual for this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual for this bean
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  /**
+   * Use the default visual for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultTrainTest.gif",
+		       BeanVisual.ICON_PATH+"DefaultTrainTest_animated.gif");
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   * Subclass must implement
+   */
+  public abstract void stop();
+
+}
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AbstractTrainingSetProducerBeanInfo.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AbstractTrainingSetProducerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for AbstractTrainingSetProducer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class AbstractTrainingSetProducerBeanInfo extends SimpleBeanInfo {
+
+  /**
+   * Returns event set descriptors for this type of bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(TrainingSetProducer.class, 
+			       "trainingSet", 
+			       TrainingSetListener.class, 
+			       "acceptTrainingSet") 
+	  };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Associator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Associator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Associator.java	(revision 29)
@@ -0,0 +1,648 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Associator.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.associations.Apriori;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Bean that wraps around weka.associations
+ *
+ * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5247 $
+ * @since 1.0
+ * @see JPanel
+ * @see BeanCommon
+ * @see Visible
+ * @see WekaWrapper
+ * @see Serializable
+ * @see UserRequestAcceptor
+ * @see TrainingSetListener
+ * @see DataSourceListener
+ */
+public class Associator
+  extends JPanel
+  implements BeanCommon, Visible, 
+	     WekaWrapper, EventConstraints,
+	     Serializable, UserRequestAcceptor,
+             DataSourceListener,
+	     TrainingSetListener, ConfigurationProducer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7843500322130210057L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("Associator",
+		   BeanVisual.ICON_PATH+"DefaultAssociator.gif",
+		   BeanVisual.ICON_PATH+"DefaultAssociator_animated.gif");
+
+  private static int IDLE = 0;
+  private static int BUILDING_MODEL = 1;
+
+  private int m_state = IDLE;
+
+  private Thread m_buildThread = null;
+
+  /**
+   * Global info for the wrapped associator (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Objects talking to us
+   */
+  private Hashtable m_listenees = new Hashtable();
+
+  /**
+   * Objects listening for text events
+   */
+  private Vector m_textListeners = new Vector();
+
+  /**
+   * Objects listening for graph events
+   */
+  private Vector m_graphListeners = new Vector();
+
+  private weka.associations.Associator m_Associator = new Apriori();
+
+  private transient Logger m_log = null;
+
+  /**
+   * Global info (if it exists) for the wrapped classifier
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  /**
+   * Creates a new <code>Associator</code> instance.
+   */
+  public Associator() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    setAssociator(m_Associator);
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Set the associator for this wrapper
+   *
+   * @param c a <code>weka.associations.Associator</code> value
+   */
+  public void setAssociator(weka.associations.Associator c) {
+    boolean loadImages = true;
+    if (c.getClass().getName().
+	compareTo(m_Associator.getClass().getName()) == 0) {
+      loadImages = false;
+    } 
+    m_Associator = c;
+    String associatorName = c.getClass().toString();
+    associatorName = associatorName.substring(associatorName.
+					      lastIndexOf('.')+1, 
+					      associatorName.length());
+    if (loadImages) {
+      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+associatorName+".gif",
+		       BeanVisual.ICON_PATH+associatorName+"_animated.gif")) {
+	useDefaultVisual();
+      }
+    }
+    m_visual.setText(associatorName);
+
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Associator);
+  }
+  
+  /**
+   * Get the associator currently set for this wrapper
+   *
+   * @return a <code>weka.associations.Associator</code> value
+   */
+  public weka.associations.Associator getAssociator() {
+    return m_Associator;
+  }
+
+  /**
+   * Sets the algorithm (associator) for this bean
+   *
+   * @param algorithm an <code>Object</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void setWrappedAlgorithm(Object algorithm) {
+
+    if (!(algorithm instanceof weka.associations.Associator)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Associator)");
+    }
+    setAssociator((weka.associations.Associator)algorithm);
+  }
+
+  /**
+   * Returns the wrapped associator
+   *
+   * @return an <code>Object</code> value
+   */
+  public Object getWrappedAlgorithm() {
+    return getAssociator();
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    // construct and pass on a DataSetEvent
+    Instances trainingSet = e.getTrainingSet();
+    DataSetEvent dse = new DataSetEvent(this, trainingSet);
+    acceptDataSet(dse);
+  }
+
+  public void acceptDataSet(final DataSetEvent e) {
+    if (e.isStructureOnly()) {
+      // no need to build an associator, just absorb and return
+      return;
+    }
+
+
+    if (m_buildThread == null) {
+      try {
+	if (m_state == IDLE) {
+	  synchronized (this) {
+	    m_state = BUILDING_MODEL;
+	  }
+	  final Instances trainingData = e.getDataSet();
+//	  final String oldText = m_visual.getText();
+	  m_buildThread = new Thread() {
+	      public void run() {
+		try {
+		  if (trainingData != null) {
+		    m_visual.setAnimated();
+//		    m_visual.setText("Building model...");
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() 
+		          + "Building model...");
+		    }
+		    buildAssociations(trainingData);
+
+		    if (m_textListeners.size() > 0) {
+		      String modelString = m_Associator.toString();
+		      String titleString = m_Associator.getClass().getName();
+		      
+		      titleString = titleString.
+			substring(titleString.lastIndexOf('.') + 1,
+				  titleString.length());
+		      modelString = "=== Associator model ===\n\n" +
+			"Scheme:   " +titleString+"\n" +
+			"Relation: "  + trainingData.relationName() + 
+                        "\n\n"
+			+ modelString;
+		      titleString = "Model: " + titleString;
+
+		      TextEvent nt = new TextEvent(Associator.this,
+						   modelString,
+						   titleString);
+		      notifyTextListeners(nt);
+		    }
+
+                    if (m_Associator instanceof weka.core.Drawable && 
+			m_graphListeners.size() > 0) {
+		      String grphString = 
+			((weka.core.Drawable)m_Associator).graph();
+                      int grphType = ((weka.core.Drawable)m_Associator).graphType();
+		      String grphTitle = m_Associator.getClass().getName();
+		      grphTitle = grphTitle.substring(grphTitle.
+						      lastIndexOf('.')+1, 
+						      grphTitle.length());
+		      grphTitle = " ("
+			+e.getDataSet().relationName() + ") "
+			+grphTitle;
+		      
+		      GraphEvent ge = new GraphEvent(Associator.this, 
+						     grphString, 
+						     grphTitle,
+                                                     grphType);
+		      notifyGraphListeners(ge);
+		    }
+		  }
+		} catch (Exception ex) {
+		  Associator.this.stop();
+		  if (m_log != null) {
+		    m_log.statusMessage(statusMessagePrefix()
+		        + "ERROR (See log for details)");
+		    m_log.logMessage("[Associator] " + statusMessagePrefix()
+		        + " problem training associator. " + ex.getMessage());
+		  }
+		  ex.printStackTrace();
+		} finally {
+//		  m_visual.setText(oldText);
+		  m_visual.setStatic();
+		  m_state = IDLE;
+		  if (isInterrupted()) {
+		    if (m_log != null) {
+                      String titleString = m_Associator.getClass().getName();		      
+		      titleString = titleString.
+			substring(titleString.lastIndexOf('.') + 1,
+				  titleString.length());
+		      m_log.logMessage("[Associator] " + statusMessagePrefix() 
+		          + " Build associator interrupted!");
+		      m_log.statusMessage(statusMessagePrefix() + "INTERRUPTED");
+		    }
+		  } else {
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() + "Finished.");
+		    }
+		  }
+		  block(false);
+		}
+	      }	
+	    };
+          m_buildThread.setPriority(Thread.MIN_PRIORITY);
+	  m_buildThread.start();
+	  // make sure the thread is still running before we block
+	  //	  if (m_buildThread.isAlive()) {
+	  block(true);
+	    //	  }
+	  m_buildThread = null;
+	  m_state = IDLE;
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+  }
+
+
+  private void buildAssociations(Instances data) 
+    throws Exception {
+    m_Associator.buildAssociations(data);
+  }
+
+  /**
+   * Sets the visual appearance of this wrapper bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Gets the visual appearance of this wrapper bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultAssociator.gif",
+		       BeanVisual.ICON_PATH+"DefaultAssociator_animated.gif");
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+
+  /**
+   * Add a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void addGraphListener(GraphListener cl) {
+    m_graphListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void removeGraphListener(GraphListener cl) {
+    m_graphListeners.remove(cl);
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void addConfigurationListener(ConfigurationListener cl) {
+    
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void removeConfigurationListener(ConfigurationListener cl) {
+    
+  }
+
+  /**
+   * Notify all text listeners of a text event
+   *
+   * @param ge a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TextListener)l.elementAt(i)).acceptText(ge);
+      }
+    }
+  }
+
+  /**
+   * Notify all graph listeners of a graph event
+   *
+   * @param ge a <code>GraphEvent</code> value
+   */
+  private void notifyGraphListeners(GraphEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_graphListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((GraphListener)l.elementAt(i)).acceptGraph(ge);
+      }
+    }
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the named event
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    if (m_listenees.containsKey(eventName)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the named event
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+
+    if (connectionAllowed(eventName)) {
+      m_listenees.put(eventName, source);
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    m_listenees.remove(eventName);
+  }
+
+  /**
+   * Function used to stop code that calls acceptTrainingSet. This is 
+   * needed as classifier construction is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+
+    if (tf) {
+      try {
+	  // only block if thread is still doing something useful!
+	if (m_buildThread.isAlive() && m_state != IDLE) {
+	  wait();
+	  }
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }  
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_buildThread != null);
+  }
+
+  /**
+   * Stop any associator action
+   */
+  public void stop() {
+    // tell all listenees (upstream beans) to stop
+    Enumeration en = m_listenees.keys();
+    while (en.hasMoreElements()) {
+      Object tempO = m_listenees.get(en.nextElement());
+      if (tempO instanceof BeanCommon) {
+	((BeanCommon)tempO).stop();
+      }
+    }
+
+    // stop the build thread
+    if (m_buildThread != null) {
+      m_buildThread.interrupt();
+      m_buildThread.stop();
+      m_buildThread = null;
+      m_visual.setStatic();
+    }
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+
+  /**
+   * Return an enumeration of requests that can be made by the user
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_buildThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform a particular request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Associator)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the event described by the
+   * supplied event descriptor could be generated.
+   *
+   * @param esd an <code>EventSetDescriptor</code> value
+   * @return a <code>boolean</code> value
+   */
+  public boolean eventGeneratable(EventSetDescriptor esd) {
+    String eventName = esd.getName();
+    return eventGeneratable(eventName);
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.compareTo("text") == 0 ||
+        eventName.compareTo("graph") == 0) {
+      if (!m_listenees.containsKey("dataSet") &&
+	  !m_listenees.containsKey("trainingSet")) {
+	return false;
+      }
+      Object source = m_listenees.get("trainingSet");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+      source = m_listenees.get("dataSet");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("dataSet")) {
+	  return false;
+	}
+      }
+
+      if (eventName.compareTo("graph") == 0 &&
+          !(m_Associator instanceof weka.core.Drawable)) {
+        return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Associator instanceof OptionHandler && 
+        Utils.joinOptions(((OptionHandler)m_Associator).getOptions()).length() > 0) 
+        ? Utils.joinOptions(((OptionHandler)m_Associator).getOptions()) + "|"
+            : "");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorBeanInfo.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AssociatorBeanInfo.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the Associator wrapper bean
+ *
+ * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5247 $
+ */
+public class AssociatorBeanInfo extends SimpleBeanInfo {
+ 
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(Associator.class,
+			       "text",
+			       TextListener.class,
+			       "acceptText"),
+        new EventSetDescriptor(Associator.class,
+			       "graph",
+			       GraphListener.class,
+			       "acceptGraph"),
+	new EventSetDescriptor(Associator.class,
+	                       "configuration",
+	                       ConfigurationListener.class,
+	                       "acceptConfiguration")
+      };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Associator.class, 
+			      AssociatorCustomizer.class);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AssociatorCustomizer.java	(revision 29)
@@ -0,0 +1,140 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AssociatorCustomizer.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * GUI customizer for the associator wrapper bean
+ *
+ * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
+ * @version $Revision: 5340 $
+ */
+public class AssociatorCustomizer
+  extends JPanel
+  implements Customizer, CustomizerCloseRequester {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5767664969353495974L;
+
+  static {
+    GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+  
+  private weka.gui.beans.Associator m_dsAssociator;
+  /*  private GenericObjectEditor m_ClassifierEditor = 
+      new GenericObjectEditor(true); */
+  private PropertySheetPanel m_AssociatorEditor = 
+    new PropertySheetPanel();
+  
+  protected JFrame m_parentFrame;
+  
+  /** Backup is user presses cancel */
+  private weka.associations.Associator m_backup;
+
+  public AssociatorCustomizer() {
+    setLayout(new BorderLayout());
+    add(m_AssociatorEditor, BorderLayout.CENTER);
+    
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new GridLayout(1,2));
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // cancel requested, so revert to backup and then
+        // close the dialog
+        if (m_backup != null) {
+          m_dsAssociator.setAssociator(m_backup);
+        }
+        m_parentFrame.dispose();
+      }
+    });
+    
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    add(butHolder, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Set the classifier object to be edited
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    m_dsAssociator = (weka.gui.beans.Associator)object;
+    //    System.err.println(Utils.joinOptions(((OptionHandler)m_dsClassifier.getClassifier()).getOptions()));
+    try {
+      m_backup = 
+        (weka.associations.Associator)GenericObjectEditor.makeCopy(m_dsAssociator.getAssociator());
+    } catch (Exception ex) {
+      // ignore
+    }
+    
+    m_AssociatorEditor.setTarget(m_dsAssociator.getAssociator());
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizer.java	(revision 29)
@@ -0,0 +1,347 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSummarizer.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+import weka.gui.AttributeVisualizationPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.beans.beancontext.BeanContext;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+/**
+ * Bean that encapsulates displays bar graph summaries for attributes in
+ * a data set.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.11 $
+ */
+public class AttributeSummarizer
+  extends DataVisualizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -294354961169372758L;
+
+  /**
+   * The number of plots horizontally in the display
+   */
+  protected int m_gridWidth = 4;
+  
+  /**
+   * The maximum number of plots to show
+   */
+  protected int m_maxPlots = 100;
+
+  /**
+   * Index on which to color the plots.
+   */
+  protected int m_coloringIndex = -1;
+
+  /**
+   * Creates a new <code>AttributeSummarizer</code> instance.
+   */
+  public AttributeSummarizer() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Plot summary bar charts for incoming data/training/test sets.";
+  }
+
+  /**
+   * Set the coloring index for the attribute summary plots
+   *
+   * @param ci an <code>int</code> value
+   */
+  public void setColoringIndex(int ci) {
+    m_coloringIndex = ci;
+  }
+
+  /**
+   * Return the coloring index for the attribute summary plots
+   *
+   * @return an <code>int</code> value
+   */
+  public int getColoringIndex() {
+    return m_coloringIndex;
+  }
+
+  /**
+   * Set the width of the grid of plots
+   *
+   * @param gw the width of the grid
+   */
+  public void setGridWidth(int gw) {
+    if (gw > 0) {
+      m_bcSupport.firePropertyChange("gridWidth", new Integer(m_gridWidth),
+				     new Integer(gw));
+      m_gridWidth = gw;
+    }
+  }
+
+  /**
+   * Get the width of the grid of plots
+   *
+   * @return the grid width
+   */
+  public int getGridWidth() {
+    return m_gridWidth;
+  }
+  
+  /**
+   * Set the maximum number of plots to display
+   *
+   * @param mp the number of plots to display
+   */
+  public void setMaxPlots(int mp) {
+    if (mp > 0) {
+      m_bcSupport.firePropertyChange("maxPlots", new Integer(m_maxPlots),
+				     new Integer(mp));
+      m_maxPlots = mp;
+    }
+  }
+
+  /**
+   * Get the number of plots to display
+   *
+   * @return the number of plots to display
+   */
+  public int getMaxPlots() {
+    return m_maxPlots;
+  }
+
+  /**
+   * Set whether the appearance of this bean should be design or
+   * application
+   *
+   * @param design true if bean should appear in design mode
+   */
+  public void setDesign(boolean design) {
+    m_design = true;
+    appearanceDesign();
+  }
+
+  protected void appearanceDesign() {
+    removeAll();
+    m_visual = 
+      new BeanVisual("AttributeSummarizer", 
+		     BeanVisual.ICON_PATH+"AttributeSummarizer.gif",
+		     BeanVisual.ICON_PATH+"AttributeSummarizer_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+  }
+
+  protected void setUpFinal() {
+    removeAll();
+    JScrollPane hp = makePanel();
+    add(hp, BorderLayout.CENTER);
+  }
+
+  /**
+   * Use the default appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
+		       BeanVisual.ICON_PATH+"DefaultDataVisualizer_animated.gif");
+  }
+
+  /**
+   * Return an enumeration of actions that the user can ask this bean to
+   * perform
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_visualizeDataSet != null) {
+      newVector.addElement("Show summaries");
+    }
+    return newVector.elements();
+  }
+
+  private JScrollPane makePanel() {
+    String fontFamily = this.getFont().getFamily();
+    Font newFont = new Font(fontFamily, Font.PLAIN, 10);
+    JPanel hp = new JPanel();
+    hp.setFont(newFont);
+    int numPlots = Math.min(m_visualizeDataSet.numAttributes(), m_maxPlots);
+    int gridHeight = numPlots / m_gridWidth;
+    
+    if (numPlots % m_gridWidth != 0) {
+      gridHeight++;
+    }
+    hp.setLayout(new GridLayout(gridHeight, 4));
+    for (int i = 0; i < numPlots; i++) {
+      JPanel temp = new JPanel();
+      temp.setLayout(new BorderLayout());
+      temp.setBorder(BorderFactory.createTitledBorder(m_visualizeDataSet.
+						      attribute(i).name()));
+
+      AttributeVisualizationPanel ap = new AttributeVisualizationPanel();
+      ap.setInstances(m_visualizeDataSet);
+      if (m_coloringIndex < 0 && m_visualizeDataSet.classIndex() >= 0) {
+        ap.setColoringIndex(m_visualizeDataSet.classIndex());
+      } else {
+        ap.setColoringIndex(m_coloringIndex);
+      }
+      temp.add(ap, BorderLayout.CENTER);
+      ap.setAttribute(i);
+      hp.add(temp);
+    }
+    
+    Dimension d = new Dimension(830, gridHeight * 100);
+    hp.setMinimumSize(d);
+    hp.setMaximumSize(d);
+    hp.setPreferredSize(d);
+    
+    JScrollPane scroller = new JScrollPane(hp);
+
+    return scroller;
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  /*  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } 
+    } */
+
+  /**
+   * Set instances for this bean. This method is a convenience method
+   * for clients who use this component programatically
+   *
+   * @param inst an <code>Instances</code> value
+   * @exception Exception if an error occurs
+   */
+  public void setInstances(Instances inst) throws Exception {
+    if (m_design) {
+      throw new Exception("This method is not to be used during design "
+			  +"time. It is meant to be used if this "
+			  +"bean is being used programatically as as "
+			  +"stand alone component.");
+    }
+    m_visualizeDataSet = inst;
+    setUpFinal();
+  }
+
+  /**
+   * Perform a named user request
+   *
+   * @param request a string containing the name of the request to perform
+   * @exception IllegalArgumentException if request is not supported
+   */
+  public void performRequest(String request) {
+    if (m_design == false) {
+      setUpFinal();
+      return;
+    }
+    if (request.compareTo("Show summaries") == 0) {
+      try {
+	// popup matrix panel
+	if (!m_framePoppedUp) {
+	  m_framePoppedUp = true;
+	  final JScrollPane holderP = makePanel();
+
+	  final javax.swing.JFrame jf = 
+	    new javax.swing.JFrame("Visualize");
+	  jf.setSize(800,600);
+	  jf.getContentPane().setLayout(new BorderLayout());
+	  jf.getContentPane().add(holderP, BorderLayout.CENTER);
+	  jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	      public void windowClosing(java.awt.event.WindowEvent e) {
+		jf.dispose();
+		m_framePoppedUp = false;
+	      }
+	    });
+	  jf.setVisible(true);
+	  m_popupFrame = jf;
+	} else {
+	  m_popupFrame.toFront();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	m_framePoppedUp = false;
+      }
+    } else {
+      throw new IllegalArgumentException(request
+		+ " not supported (AttributeSummarizer)");
+    }
+  }
+
+  public static void main(String [] args) {
+    try {
+      if (args.length != 1) {
+	System.err.println("Usage: AttributeSummarizer <dataset>");
+	System.exit(1);
+      }
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      Instances inst = new Instances(r);
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      final AttributeSummarizer as = new AttributeSummarizer();
+      as.setInstances(inst);
+      
+      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(830,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/AttributeSummarizerBeanInfo.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSummarizerBeanInfo.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the attribute summarizer bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class AttributeSummarizerBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierEvent.java	(revision 29)
@@ -0,0 +1,241 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BatchClassifierEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+
+import java.util.EventObject;
+
+/**
+ * Class encapsulating a built classifier and a batch of instances to
+ * test on.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5928 $
+ * @since 1.0
+ * @see EventObject
+ */
+public class BatchClassifierEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 878097199815991084L;
+
+  /**
+   * The classifier
+   */
+  protected Classifier m_classifier;
+  //  protected Instances m_trainingSet;
+
+  /**
+   * Instances that can be used for testing the classifier
+   */
+  //  protected Instances m_testSet;
+  protected DataSetEvent m_testSet;
+  
+  /**
+   * Instances that were used to train the classifier (may be null if not available)
+   */
+  protected DataSetEvent m_trainSet;
+  
+  /**
+   * The run number that this classifier was generated for 
+   */
+  protected int m_runNumber = 1;
+    
+  /**
+   * The maximum number of runs 
+   */
+  protected int m_maxRunNumber = 1;
+
+  /**
+   * The set number for the test set
+   */
+  protected int m_setNumber;
+
+  /**
+   * The last set number for this series
+   */
+  protected int m_maxSetNumber;
+  
+  /**
+   * An identifier that can be used to group all related runs/sets
+   * together.
+   */
+  protected long m_groupIdentifier = Long.MAX_VALUE;
+
+  /**
+   * Creates a new <code>BatchClassifierEvent</code> instance.
+   *
+   * @param source the source object
+   * @param scheme a Classifier
+   * @param trsI the training instances used to train the classifier
+   * @param tstI the test instances
+   * @param setNum the set number of the test instances
+   * @param maxSetNum the last set number in the series
+   */
+  public BatchClassifierEvent(Object source, Classifier scheme,
+			 DataSetEvent trsI, DataSetEvent tstI, int setNum,
+			 int maxSetNum) {
+    super(source);
+    //    m_trainingSet = trnI;
+    m_classifier = scheme;
+    m_testSet = tstI;
+    m_trainSet = trsI;
+    m_setNumber = setNum;
+    m_maxSetNumber = maxSetNum;
+  }
+  
+  /**
+   * Creates a new <code>BatchClassifierEvent</code> instance.
+   *
+   * @param source the source object
+   * @param scheme a Classifier
+   * @param trsI the training instances used to train the classifier
+   * @param tstI the test instances
+   * @param runNum the run number
+   * @param maxRunNum the maximum run number
+   * @param setNum the set number of the test instances
+   * @param maxSetNum the last set number in the series
+   */
+  public BatchClassifierEvent(Object source, Classifier scheme,
+    DataSetEvent trsI, DataSetEvent tstI, int runNum, int maxRunNum,
+    int setNum, int maxSetNum) {
+    
+    this(source, scheme, trsI, tstI, setNum, maxSetNum);
+    
+    m_runNumber = runNum;
+    m_maxRunNumber = maxRunNum;
+  }
+
+//    /**
+//     * Get the training instances
+//     *
+//     * @return the training instances
+//     */
+//    public Instances getTrainingSet() {
+//      return m_trainingSet;
+//    }
+
+  /**
+   * Get the classifier
+   *
+   * @return the classifier
+   */
+  public Classifier getClassifier() {
+    return m_classifier;
+  }
+  
+  /**
+   * Set the classifier
+   * 
+   * @param classifier the classifier
+   */
+  public void setClassifier(Classifier classifier) {
+    m_classifier = classifier;
+  }
+  
+  /**
+   * Set the test set
+   * 
+   * @param tse the test set
+   */
+  public void setTestSet(DataSetEvent tse) {
+    m_testSet = tse;
+  }
+
+  /**
+   * Get the test set
+   *
+   * @return the test set
+   */
+  public DataSetEvent getTestSet() {
+    return m_testSet;
+  }
+  
+  /**
+   * Set the training set
+   * 
+   * @param tse the training set
+   */
+  public void setTrainSet(DataSetEvent tse) {
+    m_trainSet = tse;
+  }
+  
+  /**
+   * Get the train set
+   *
+   * @return the training set
+   */
+  public DataSetEvent getTrainSet() {
+    return m_trainSet;
+  }
+  
+  /**
+   * Get the run number.
+   * 
+   * @return the run number
+   */
+  public int getRunNumber() {
+    return m_runNumber;
+  }
+  
+  /**
+   * Get the maximum run number
+   * 
+   * @return the maximum run number
+   */
+  public int getMaxRunNumber() {
+    return m_maxRunNumber;
+  }
+
+  /**
+   * Get the set number (ie which fold this is)
+   *
+   * @return the set number for the training and testing data sets
+   * encapsulated in this event
+   */
+  public int getSetNumber() {
+    return m_setNumber;
+  }
+
+  /**
+   * Get the maximum set number (ie the total number of training
+   * and testing sets in the series).
+   *
+   * @return the maximum set number
+   */
+  public int getMaxSetNumber() {
+    return m_maxSetNumber;
+  }
+  
+  public void setGroupIdentifier(long identifier) {
+    m_groupIdentifier = identifier;
+  }
+  
+  public long getGroupIdentifier() {
+    return m_groupIdentifier;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BatchClassifierListener.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BatchClassifierListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can process a BatchClassifierEvent
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface BatchClassifierListener extends EventListener {
+  
+  /**
+   * Accept a BatchClassifierEvent
+   *
+   * @param e a <code>BatchClassifierEvent</code> value
+   */
+  void acceptClassifier(BatchClassifierEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererEvent.java	(revision 29)
@@ -0,0 +1,145 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BatchClustererEvent.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.clusterers.Clusterer;
+
+import java.util.EventObject;
+
+/**
+ * Class encapsulating a built clusterer and a batch of instances to
+ * test on.
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 1.4 $
+ * @since 1.0
+ * @see EventObject
+ */
+public class BatchClustererEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7268777944939129714L;
+
+  /**
+   * The clusterer
+   */
+  protected Clusterer m_clusterer;
+  //  protected Instances m_trainingSet;
+
+  /**
+   * Training or Test Instances
+   */
+  //  protected Instances m_testSet;
+  protected DataSetEvent m_testSet;
+
+  /**
+   * The set number for the test set
+   */
+  protected int m_setNumber;
+
+  /**
+   * Indicates if m_testSet is a training or a test set. 0 for test, >0 for training
+   */
+  protected int m_testOrTrain;
+
+  /**
+   * The last set number for this series
+   */
+  protected int m_maxSetNumber;
+  
+  public static int TEST = 0;
+  public static int TRAINING = 1;
+
+  /**
+   * Creates a new <code>BatchClustererEvent</code> instance.
+   *
+   * @param source the source object
+   * @param scheme a Clusterer
+   * @param tstI the training/test instances
+   * @param setNum the set number of the training/testinstances
+   * @param maxSetNum the last set number in the series
+   * @param testOrTrain 0 if the set is a test set, >0 if it is a training set
+   */
+  public BatchClustererEvent(Object source, Clusterer scheme, DataSetEvent tstI, int setNum, int maxSetNum, int testOrTrain) {
+    super(source);
+    //    m_trainingSet = trnI;
+    m_clusterer = scheme;
+    m_testSet = tstI;
+    m_setNumber = setNum;
+    m_maxSetNumber = maxSetNum;
+    if(testOrTrain == 0)
+        m_testOrTrain = TEST;
+    else
+        m_testOrTrain = TRAINING;
+  }
+
+
+  /**
+   * Get the clusterer
+   *
+   * @return the clusterer
+   */
+  public Clusterer getClusterer() {
+    return m_clusterer;
+  }
+
+  /**
+   * Get the training/test set
+   *
+   * @return the training/testing instances
+   */
+  public DataSetEvent getTestSet() {
+    return m_testSet;
+  }
+
+  /**
+   * Get the set number (ie which fold this is)
+   *
+   * @return the set number for the training and testing data sets
+   * encapsulated in this event
+   */
+  public int getSetNumber() {
+    return m_setNumber;
+  }
+
+  /**
+   * Get the maximum set number (ie the total number of training
+   * and testing sets in the series).
+   *
+   * @return the maximum set number
+   */
+  public int getMaxSetNumber() {
+    return m_maxSetNumber;
+  }
+  
+  /**
+   * Get whether the set of instances is a test or a training set
+   *
+   * @return 0 for test set, >0 fro training set
+   */
+  public int getTestOrTrain(){
+    return m_testOrTrain;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BatchClustererListener.java	(revision 29)
@@ -0,0 +1,42 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BatchClustererListener.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can process a BatchClustererEvent
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">MStefan Mutter</a>
+ * @version $Revision: 1.2 $
+ * @see EventListener
+ */
+public interface BatchClustererListener extends EventListener {
+  
+  /**
+   * Accept a BatchClustererEvent
+   *
+   * @param e a <code>BatchClustererEvent</code> value
+   */
+  void acceptClusterer(BatchClustererEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BeanCommon.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BeanCommon.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BeanCommon.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BeanCommon.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.EventSetDescriptor;
+
+/**
+ * Interface specifying routines that all weka beans should implement
+ * in order to allow the bean environment to exercise some control over the
+ * bean and also to allow the bean to exercise some control over connections.
+ *
+ * Beans may want to  impose constraints in terms of
+ * the number of connections they will allow via a particular 
+ * listener interface. Some beans may only want to be registered
+ * as a listener for a particular event type with only one source, or
+ * perhaps a limited number of sources.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5820 $
+ * @since 1.0
+ */
+public interface BeanCommon {
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  void setCustomName(String name);
+  
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  String getCustomName();
+
+  /**
+   * Stop any processing that the bean might be doing.
+   */
+  void stop();
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  boolean isBusy();
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  void setLog(weka.gui.Logger logger);
+
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the named event
+   *
+   * @param esd the EventSetDescriptor for the event in question
+   * @return true if the object will accept a connection
+   */
+  boolean connectionAllowed(EventSetDescriptor esd);
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the named event
+   *
+   * @param eventName the name of the event
+   * @return true if the object will accept a connection
+   */
+  boolean connectionAllowed(String eventName);
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source for recieving events described by the named event
+   * This object is responsible for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  void connectionNotification(String eventName, Object source);
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source for named event. This object is responsible
+   * for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  void disconnectionNotification(String eventName, Object source);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BeanConnection.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BeanConnection.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BeanConnection.java	(revision 29)
@@ -0,0 +1,713 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BeanConnection.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.BeanInfo;
+import java.beans.EventSetDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingConstants;
+
+/**
+ * Class for encapsulating a connection between two beans. Also
+ * maintains a list of all connections
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5611 $
+ */
+public class BeanConnection
+  implements Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8804264241791332064L;
+
+  /**
+   * The list of connections
+   */
+  public static Vector CONNECTIONS = new Vector();
+
+  // details for this connection
+  private BeanInstance m_source;
+  private BeanInstance m_target;
+
+  /**
+   * The name of the event for this connection
+   */
+  private String m_eventName;
+
+  // Should the connection be painted?
+  private boolean m_hidden = false;
+
+  /**
+   * Reset the list of connections
+   */
+  public static void reset() {
+    CONNECTIONS = new Vector();
+  }
+
+  /**
+   * Returns the list of connections
+   *
+   * @return the list of connections
+   */
+  public static Vector getConnections() {
+    return CONNECTIONS;
+  }
+
+  /**
+   * Describe <code>setConnections</code> method here.
+   *
+   * @param connections a <code>Vector</code> value
+   */
+  public static void setConnections(Vector connections) {
+    CONNECTIONS = connections;
+  }
+
+  /**
+   * Returns true if there is a link between the supplied source and
+   * target BeanInstances at an earlier index than the supplied index
+   *
+   * @param source the source BeanInstance
+   * @param target the target BeanInstance
+   * @param index the index to compare to
+   * @return true if there is already a link at an earlier index
+   */
+  private static boolean previousLink(BeanInstance source, BeanInstance target,
+				      int index) {
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      BeanInstance compSource = bc.getSource();
+      BeanInstance compTarget = bc.getTarget();
+
+      if (compSource == source && compTarget == target && index < i) {
+	return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A candidate BeanInstance can be an input if it is in the listToCheck
+   * and it is the source of a connection to a target that is in the
+   * listToCheck
+   */
+  private static boolean checkForSource(BeanInstance candidate,
+                                        Vector listToCheck) {
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      if (bc.getSource() != candidate) {
+        continue;
+      }
+
+      // check to see if target is in list
+      for (int j = 0; j < listToCheck.size(); j++) {
+        BeanInstance tempTarget = (BeanInstance)listToCheck.elementAt(j);
+        if (bc.getTarget() == tempTarget) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+  
+  /**
+   * A candidate BeanInstance can't be an input if it is the target
+   * of a connection from a source that is in the listToCheck
+   */
+  private static boolean checkTargetConstraint(BeanInstance candidate,
+                                               Vector listToCheck) {
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      if (bc.getTarget() == candidate) {
+        for (int j = 0; j < listToCheck.size(); j++) {
+          BeanInstance tempSource = (BeanInstance)listToCheck.elementAt(j);
+          if (bc.getSource() == tempSource) {
+            return false;
+          }          
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns a vector of BeanConnections associated with
+   * the supplied vector of BeanInstances, i.e. all connections
+   * that exist between those BeanInstances in the subFlow.
+   * 
+   * @param subFlow a Vector of BeanInstances
+   * @return a Vector of BeanConnections
+   */
+  public static Vector associatedConnections(Vector subFlow) {
+    Vector associatedConnections = new Vector();
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      BeanInstance tempSource = bc.getSource();
+      BeanInstance tempTarget = bc.getTarget();
+      boolean sourceInSubFlow = false;
+      boolean targetInSubFlow = false;
+      for (int j = 0; j < subFlow.size(); j++) {
+        BeanInstance toCheck = (BeanInstance)subFlow.elementAt(j);
+        if (toCheck == tempSource) {
+          sourceInSubFlow = true;
+        }
+        if (toCheck == tempTarget) {
+          targetInSubFlow = true;
+        }
+        if (sourceInSubFlow && targetInSubFlow) {
+          associatedConnections.add(bc);
+          break;
+        }
+      }
+    }
+    return associatedConnections;
+  }
+
+  /**
+   * Returns a vector of BeanInstances that can be considered
+   * as inputs (or the left-hand side of a sub-flow)
+   *
+   * @param subset the sub-flow to examine
+   * @return a Vector of inputs to the sub-flow
+   */
+  public static Vector inputs(Vector subset) {
+    Vector result = new Vector();
+    for (int i = 0; i < subset.size(); i++) {
+      BeanInstance temp = (BeanInstance)subset.elementAt(i);
+      //      if (checkForSource(temp, subset)) {
+        // now check target constraint
+        if (checkTargetConstraint(temp, subset)) {
+          result.add(temp);
+        }
+        //      }
+    }
+    return result;
+  }
+
+
+  /**
+   * A candidate BeanInstance can be an output if it is in the listToCheck
+   * and it is the target of a connection from a source that is in the
+   * listToCheck
+   */
+  private static boolean checkForTarget(BeanInstance candidate,
+                                        Vector listToCheck) {
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      if (bc.getTarget() != candidate) {
+        continue;
+      }
+      
+      // check to see if source is in list
+      for (int j = 0; j < listToCheck.size(); j++) {
+        BeanInstance tempSource = (BeanInstance)listToCheck.elementAt(j);
+        if (bc.getSource() == tempSource) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private static boolean isInList(BeanInstance candidate,
+                                  Vector listToCheck) {
+    for (int i = 0; i < listToCheck.size(); i++) {
+      BeanInstance temp = (BeanInstance)listToCheck.elementAt(i);
+      if (candidate == temp) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A candidate BeanInstance can't be an output if it is the source
+   * of a connection only to target(s) that are in the listToCheck
+   */
+  private static boolean checkSourceConstraint(BeanInstance candidate,
+                                               Vector listToCheck) {
+    boolean result = true;
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      if (bc.getSource() == candidate) {
+        BeanInstance cTarget = bc.getTarget();
+        // is the target of this connection external to the list to check?
+        if (!isInList(cTarget, listToCheck)) {
+          return true;
+        }
+        for (int j = 0; j < listToCheck.size(); j++) {
+          BeanInstance tempTarget = (BeanInstance)listToCheck.elementAt(j);
+          if (bc.getTarget() == tempTarget) {
+            result = false;
+          }          
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns a vector of BeanInstances that can be considered
+   * as outputs (or the right-hand side of a sub-flow)
+   *
+   * @param subset the sub-flow to examine
+   * @return a Vector of outputs of the sub-flow
+   */
+  public static Vector outputs(Vector subset) {
+    Vector result = new Vector();
+    for (int i = 0; i < subset.size(); i++) {
+      BeanInstance temp = (BeanInstance)subset.elementAt(i);
+      if (checkForTarget(temp, subset)) {
+        // now check source constraint
+        if (checkSourceConstraint(temp, subset)) {
+          // now check that this bean can actually produce some events
+          try {
+            BeanInfo bi = Introspector.getBeanInfo(temp.getBean().getClass());
+            EventSetDescriptor [] esd = bi.getEventSetDescriptors();
+            if (esd != null && esd.length > 0) {
+              result.add(temp);
+            }
+          } catch (IntrospectionException ex) {
+            // quietly ignore
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Renders the connections and their names on the supplied graphics
+   * context
+   *
+   * @param gx a <code>Graphics</code> value
+   */
+  public static void paintConnections(Graphics gx) {
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      if (!bc.isHidden()) {
+        BeanInstance source = bc.getSource();
+        BeanInstance target = bc.getTarget();
+        EventSetDescriptor srcEsd = bc.getSourceEventSetDescriptor();
+        BeanVisual sourceVisual = (source.getBean() instanceof Visible) ?
+          ((Visible)source.getBean()).getVisual() :
+          null;
+        BeanVisual targetVisual = (target.getBean() instanceof Visible) ?
+          ((Visible)target.getBean()).getVisual() :
+          null;
+        if (sourceVisual != null && targetVisual != null) {
+          Point bestSourcePt = 
+            sourceVisual.getClosestConnectorPoint(
+                         new Point((target.getX()+(target.getWidth()/2)), 
+                                   (target.getY() + (target.getHeight() / 2))));
+          Point bestTargetPt = 
+            targetVisual.getClosestConnectorPoint(
+                         new Point((source.getX()+(source.getWidth()/2)), 
+                                   (source.getY() + (source.getHeight() / 2))));
+          gx.setColor(Color.red);
+          boolean active = true;
+          if (source.getBean() instanceof EventConstraints) {
+            if (!((EventConstraints) source.getBean()).
+                eventGeneratable(srcEsd.getName())) {
+              gx.setColor(Color.gray); // link not active at this time
+              active = false;
+            }
+          }
+          gx.drawLine((int)bestSourcePt.getX(), (int)bestSourcePt.getY(),
+                      (int)bestTargetPt.getX(), (int)bestTargetPt.getY());
+
+          // paint an arrow head
+          double angle;
+          try {
+            double a = 
+              (double)(bestSourcePt.getY() - 
+                       bestTargetPt.getY()) / 
+              (double)(bestSourcePt.getX() - bestTargetPt.getX());
+            angle = Math.atan(a);
+          } catch(Exception ex) {
+            angle = Math.PI / 2;
+          }
+          //	Point arrowstart = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+          Point arrowstart = new Point(bestTargetPt.x,
+                                       bestTargetPt.y);
+          Point arrowoffset = new Point((int)(7 * Math.cos(angle)), 
+                                        (int)(7 * Math.sin(angle)));
+          Point arrowend;
+          if (bestSourcePt.getX() >= bestTargetPt.getX()) {
+	  
+            arrowend = new Point(arrowstart.x + arrowoffset.x, 
+                                 arrowstart.y + arrowoffset.y);
+          } else {
+            arrowend = new Point(arrowstart.x - arrowoffset.x, 
+                                 arrowstart.y - arrowoffset.y);
+          }
+          int xs[] = { arrowstart.x,
+                       arrowend.x + (int)(7 * Math.cos(angle + (Math.PI / 2))),
+                       arrowend.x + (int)(7 * Math.cos(angle - (Math.PI / 2)))};
+          int ys[] = { arrowstart.y,
+                       arrowend.y + (int)(7 * Math.sin(angle + (Math.PI / 2))),
+                       arrowend.y + (int)(7 * Math.sin(angle - (Math.PI / 2)))};
+          gx.fillPolygon(xs, ys, 3);
+          // ----
+
+          // paint the connection name
+          int midx = (int)bestSourcePt.getX();
+          midx += (int)((bestTargetPt.getX() - bestSourcePt.getX()) / 2);
+          int midy = (int)bestSourcePt.getY();
+          midy += (int)((bestTargetPt.getY() - bestSourcePt.getY()) / 2) - 2 ;
+          gx.setColor((active) ? Color.blue : Color.gray);
+          if (previousLink(source, target, i)) {
+            midy -= 15;
+          }
+          gx.drawString(srcEsd.getName(), midx, midy);
+        }
+      }
+    }
+  }
+
+  /**
+   * Return a list of connections within some delta of a point
+   *
+   * @param pt the point at which to look for connections
+   * @param delta connections have to be within this delta of the point
+   * @return a list of connections
+   */
+  public static Vector getClosestConnections(Point pt, int delta) {
+    Vector closestConnections = new Vector();
+    
+    for (int i = 0; i < CONNECTIONS.size(); i++) {
+      BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+      BeanInstance source = bc.getSource();
+      BeanInstance target = bc.getTarget();
+      EventSetDescriptor srcEsd = bc.getSourceEventSetDescriptor();
+      BeanVisual sourceVisual = (source.getBean() instanceof Visible) ?
+	((Visible)source.getBean()).getVisual() :
+	null;
+      BeanVisual targetVisual = (target.getBean() instanceof Visible) ?
+	((Visible)target.getBean()).getVisual() :
+	null;
+      if (sourceVisual != null && targetVisual != null) {
+	Point bestSourcePt = 
+	  sourceVisual.getClosestConnectorPoint(
+		       new Point((target.getX()+(target.getWidth()/2)), 
+				 (target.getY() + (target.getHeight() / 2))));
+	Point bestTargetPt = 
+	  targetVisual.getClosestConnectorPoint(
+		       new Point((source.getX()+(source.getWidth()/2)), 
+				 (source.getY() + (source.getHeight() / 2))));
+
+	int minx = (int) Math.min(bestSourcePt.getX(), bestTargetPt.getX());
+	int maxx = (int) Math.max(bestSourcePt.getX(), bestTargetPt.getX());
+	int miny = (int) Math.min(bestSourcePt.getY(), bestTargetPt.getY());
+	int maxy = (int) Math.max(bestSourcePt.getY(), bestTargetPt.getY());
+	// check to see if supplied pt is inside bounding box
+	if (pt.getX() >= minx-delta && pt.getX() <= maxx+delta && 
+	    pt.getY() >= miny-delta && pt.getY() <= maxy+delta) {
+	  // now see if the point is within delta of the line
+	  // formulate ax + by + c = 0
+	  double a = bestSourcePt.getY() - bestTargetPt.getY();
+	  double b = bestTargetPt.getX() - bestSourcePt.getX();
+	  double c = (bestSourcePt.getX() * bestTargetPt.getY()) -
+	    (bestTargetPt.getX() * bestSourcePt.getY());
+	  
+	  double distance = Math.abs((a * pt.getX()) + (b * pt.getY()) + c);
+	  distance /= Math.abs(Math.sqrt((a*a) + (b*b)));
+
+	  if (distance <= delta) {
+	    closestConnections.addElement(bc);
+	  }
+	}
+      }
+    }
+    return closestConnections;
+  }
+
+  /**
+   * Remove all connections for a bean. If the bean is a target for
+   * receiving events then it gets deregistered from the corresonding
+   * source bean. If the bean is a source of events then all targets 
+   * implementing BeanCommon are notified via their
+   * disconnectionNotification methods that the source (and hence the
+   * connection) is going away.
+   *
+   * @param instance the bean to remove connections to/from
+   */
+  public static void removeConnections(BeanInstance instance) {
+    
+    Vector instancesToRemoveFor = new Vector();
+    if (instance.getBean() instanceof MetaBean) {
+      instancesToRemoveFor = 
+        ((MetaBean)instance.getBean()).getBeansInSubFlow();
+    } else {
+      instancesToRemoveFor.add(instance);
+    }
+    Vector removeVector = new Vector();
+    for (int j = 0; j < instancesToRemoveFor.size(); j++) {
+      BeanInstance tempInstance = 
+        (BeanInstance)instancesToRemoveFor.elementAt(j);
+      for (int i = 0; i < CONNECTIONS.size(); i++) {
+        // In cases where this instance is the target, deregister it
+        // as a listener for the source
+        BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
+        BeanInstance tempTarget = bc.getTarget();
+        BeanInstance tempSource = bc.getSource();
+
+        EventSetDescriptor tempEsd = bc.getSourceEventSetDescriptor();
+        if (tempInstance == tempTarget) {
+          // try to deregister the target as a listener for the source
+          try {
+            Method deregisterMethod = tempEsd.getRemoveListenerMethod();
+            Object targetBean = tempTarget.getBean();
+            Object [] args = new Object[1];
+            args[0] = targetBean;
+            deregisterMethod.invoke(tempSource.getBean(), args);
+            //            System.err.println("Deregistering listener");
+            removeVector.addElement(bc);
+          } catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        } else if (tempInstance == tempSource) {
+          removeVector.addElement(bc);
+          if (tempTarget.getBean() instanceof BeanCommon) {
+            // tell the target that the source is going away, therefore
+            // this type of connection is as well
+            ((BeanCommon)tempTarget.getBean()).
+              disconnectionNotification(tempEsd.getName(),
+                                        tempSource.getBean());
+          }
+        }
+      }
+    }
+    for (int i = 0; i < removeVector.size(); i++) {
+      //      System.err.println("removing connection");
+      CONNECTIONS.removeElement((BeanConnection)removeVector.elementAt(i));
+    }
+  }
+
+  public static void doMetaConnection(BeanInstance source, BeanInstance target,
+                                      final EventSetDescriptor esd,
+                                      final JComponent displayComponent) {
+
+    Object targetBean = target.getBean();
+    BeanInstance realTarget = null;
+    final BeanInstance realSource = source;
+    if (targetBean instanceof MetaBean) {
+      Vector receivers = ((MetaBean)targetBean).getSuitableTargets(esd);
+      if (receivers.size() == 1) {
+        realTarget = (BeanInstance)receivers.elementAt(0);
+        BeanConnection bc = new BeanConnection(realSource, realTarget,
+                                               esd);
+        //        m_target = (BeanInstance)receivers.elementAt(0);
+      } else {
+        // have to do the popup thing here
+        int menuItemCount = 0;
+        JPopupMenu targetConnectionMenu = new JPopupMenu();
+        targetConnectionMenu.insert(new JLabel("Select target",
+                                               SwingConstants.CENTER),
+                                    menuItemCount++);
+        for (int i = 0; i < receivers.size(); i++) {
+          final BeanInstance tempTarget = 
+            (BeanInstance)receivers.elementAt(i);
+          String tName = ""+(i+1)+": " 
+            + ((tempTarget.getBean() instanceof BeanCommon) 
+                ? ((BeanCommon)tempTarget.getBean()).getCustomName() 
+                : tempTarget.getBean().getClass().getName());
+          JMenuItem targetItem = new JMenuItem(tName);
+          targetItem.addActionListener(new ActionListener() {
+              public void actionPerformed(ActionEvent e) {
+                //    finalTarget.add(tempTarget);
+                BeanConnection bc = 
+                  new BeanConnection(realSource, tempTarget,
+                                     esd);
+                displayComponent.repaint();
+              }
+            });
+          targetConnectionMenu.add(targetItem);
+          menuItemCount++;
+        }
+        targetConnectionMenu.show(displayComponent, target.getX(),
+                                  target.getY());
+        //        m_target = (BeanInstance)finalTarget.elementAt(0);
+      }
+    }
+  }
+
+  /**
+   * Creates a new <code>BeanConnection</code> instance.
+   *
+   * @param source the source bean
+   * @param target the target bean
+   * @param esd the EventSetDescriptor for the connection
+   * be displayed
+   */
+  public BeanConnection(BeanInstance source, BeanInstance target,
+			EventSetDescriptor esd) {
+    m_source = source;
+    m_target = target;
+    //    m_sourceEsd = sourceEsd;
+    m_eventName = esd.getName();
+    //    System.err.println(m_eventName);
+
+    // attempt to connect source and target beans
+    Method registrationMethod = 
+      //      m_sourceEsd.getAddListenerMethod();
+      //      getSourceEventSetDescriptor().getAddListenerMethod();
+      esd.getAddListenerMethod();
+    Object targetBean = m_target.getBean();
+
+    Object [] args = new Object[1];
+    args[0] = targetBean;
+    Class listenerClass = esd.getListenerType();
+    if (listenerClass.isInstance(targetBean)) {
+      try {
+	registrationMethod.invoke(m_source.getBean(), args);
+	// if the target implements BeanCommon, then inform
+	// it that it has been registered as a listener with a source via
+	// the named listener interface
+	if (targetBean instanceof BeanCommon) {
+	  ((BeanCommon)targetBean).
+	    connectionNotification(esd.getName(), m_source.getBean());
+	}
+	CONNECTIONS.addElement(this);
+      } catch (Exception ex) {
+	System.err.println("[BeanConnection] Unable to connect beans");
+	ex.printStackTrace();
+      }
+    } else {
+      System.err.println("[BeanConnection] Unable to connect beans");
+    }
+  }
+
+  /**
+   * Make this connection invisible on the display
+   *
+   * @param hidden true to make the connection invisible
+   */
+  public void setHidden(boolean hidden) {
+    m_hidden = hidden;
+  }
+
+  /**
+   * Returns true if this connection is invisible
+   *
+   * @return true if connection is invisible
+   */
+  public boolean isHidden() {
+    return m_hidden;
+  }
+
+  /**
+   * Remove this connection
+   */
+  public void remove() {
+    EventSetDescriptor tempEsd = getSourceEventSetDescriptor();
+    // try to deregister the target as a listener for the source
+    try {
+      Method deregisterMethod = tempEsd.getRemoveListenerMethod();
+      Object targetBean = getTarget().getBean();
+      Object [] args = new Object[1];
+      args[0] = targetBean;
+      deregisterMethod.invoke(getSource().getBean(), args);
+      //      System.err.println("Deregistering listener");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    if (getTarget().getBean() instanceof BeanCommon) {
+      // tell the target that this connection is going away
+      ((BeanCommon)getTarget().getBean()).
+	disconnectionNotification(tempEsd.getName(),
+				  getSource().getBean());
+    }
+
+    CONNECTIONS.remove(this);
+  }
+
+  /**
+   * returns the source BeanInstance for this connection
+   *
+   * @return a <code>BeanInstance</code> value
+   */
+  public BeanInstance getSource() {
+    return m_source;
+  }
+
+  /**
+   * Returns the target BeanInstance for this connection
+   *
+   * @return a <code>BeanInstance</code> value
+   */
+  public BeanInstance getTarget() {
+    return m_target;
+  }
+  
+  /**
+   * Returns the name of the event for this conncetion
+   * 
+   * @return the name of the event for this connection
+   */
+  public String getEventName() {
+    return m_eventName;
+  }
+
+  /**
+   * Returns the event set descriptor for the event generated by the source
+   * for this connection
+   *
+   * @return an <code>EventSetDescriptor</code> value
+   */
+  protected EventSetDescriptor getSourceEventSetDescriptor() {
+    JComponent bc = (JComponent)m_source.getBean();
+     try {
+       BeanInfo sourceInfo = Introspector.getBeanInfo(bc.getClass());
+       if (sourceInfo == null) {
+       System.err.println("[BeanConnection] Error getting bean info, source info is null.");
+       } else {
+	 EventSetDescriptor [] esds = sourceInfo.getEventSetDescriptors();
+	 for (int i = 0; i < esds.length; i++) {
+	   if (esds[i].getName().compareTo(m_eventName) == 0) {
+	     return esds[i];
+	   }
+	 }
+       }
+     } catch (Exception ex) {
+       System.err.println("[BeanConnection] Problem retrieving event set descriptor");
+     }
+     return null;
+     
+     //    return m_sourceEsd;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BeanInstance.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BeanInstance.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BeanInstance.java	(revision 29)
@@ -0,0 +1,488 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BeanInstance.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.beans.Beans;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+
+/**
+ * Class that manages a set of beans.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version  $Revision: 4829 $
+ * @since 1.0
+ */
+public class BeanInstance
+  implements Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7575653109025406342L;
+
+  /**
+   * class variable holding all the beans
+   */
+  private static Vector COMPONENTS = new Vector();
+
+  public static final int IDLE = 0;
+  public static final int BEAN_EXECUTING = 1;
+  
+  /**
+   * Holds the bean encapsulated in this instance
+   */
+  private Object m_bean;
+  private int m_x;
+  private int m_y;
+
+
+  /**
+   * Reset the list of beans
+   */
+  public static void reset(JComponent container) {
+    // remove beans from container if necessary
+    removeAllBeansFromContainer(container);
+    COMPONENTS = new Vector();
+  }
+
+  /**
+   * Removes all beans from containing component
+   *
+   * @param container a <code>JComponent</code> value
+   */
+  public static void removeAllBeansFromContainer(JComponent container) {
+    if (container != null) {
+      if (COMPONENTS != null) {
+	for (int i = 0; i < COMPONENTS.size(); i++) {
+	  BeanInstance tempInstance = (BeanInstance)COMPONENTS.elementAt(i);
+	  Object tempBean = tempInstance.getBean();
+	  if (Beans.isInstanceOf(tempBean, JComponent.class)) {
+	    container.remove((JComponent)tempBean);
+	  }
+	}
+      }
+      container.revalidate();
+    }
+  }
+
+  /**
+   * Adds all beans to the supplied component
+   *
+   * @param container a <code>JComponent</code> value
+   */
+  public static void addAllBeansToContainer(JComponent container) {
+    if (container != null) {
+      if (COMPONENTS != null) {
+	for (int i = 0; i < COMPONENTS.size(); i++) {
+	  BeanInstance tempInstance = (BeanInstance)COMPONENTS.elementAt(i);
+	  Object tempBean = tempInstance.getBean();
+	  if (Beans.isInstanceOf(tempBean, JComponent.class)) {
+	    container.add((JComponent)tempBean);
+	  }
+	}
+      }
+      container.revalidate();
+    }
+  }
+
+  /**
+   * Return the list of displayed beans
+   *
+   * @return a vector of beans
+   */
+  public static Vector getBeanInstances() {
+    return COMPONENTS;
+  }
+
+  /**
+   * Describe <code>setBeanInstances</code> method here.
+   *
+   * @param beanInstances a <code>Vector</code> value
+   * @param container a <code>JComponent</code> value
+   */
+  public static void setBeanInstances(Vector beanInstances, 
+				      JComponent container) {
+    reset(container);
+    
+    if (container != null) {
+      for (int i = 0; i < beanInstances.size(); i++) {
+	Object bean = ((BeanInstance)beanInstances.elementAt(i)).getBean();
+	if (Beans.isInstanceOf(bean, JComponent.class)) {
+	  container.add((JComponent)bean);
+	}
+      }
+      container.revalidate();
+      container.repaint();
+    }
+    COMPONENTS = beanInstances;
+  }
+
+  /**
+   * Renders the textual labels for the beans.
+   *
+   * @param gx a <code>Graphics</code> object on which to render
+   * the labels
+   */
+  public static void paintLabels(Graphics gx) {
+    gx.setFont(new Font(null, Font.PLAIN, 9));
+    FontMetrics fm = gx.getFontMetrics();
+    int hf = fm.getAscent();
+    for (int i = 0; i < COMPONENTS.size(); i++) {
+      BeanInstance bi = (BeanInstance)COMPONENTS.elementAt(i);
+      if (!(bi.getBean() instanceof Visible)) {
+	continue;
+      }
+      int cx = bi.getX(); int cy = bi.getY();
+      int width = ((JComponent)bi.getBean()).getWidth();
+      int height = ((JComponent)bi.getBean()).getHeight();
+      String label = ((Visible)bi.getBean()).getVisual().getText();
+      int labelwidth = fm.stringWidth(label);
+      if (labelwidth < width) {
+	gx.drawString(label, (cx+(width/2)) - (labelwidth / 2), cy+height+hf+2);
+      } else {
+	// split label
+
+	// find mid point
+	int mid = label.length() / 2;
+	// look for split point closest to the mid
+	int closest = label.length();
+	int closestI = -1;
+	for (int z = 0; z < label.length(); z++) {
+	  if (label.charAt(z) < 'a') {
+	    if (Math.abs(mid - z) < closest) {
+	      closest = Math.abs(mid - z);
+	      closestI = z;
+	    }
+	  }
+	}
+	if (closestI != -1) {
+	  String left = label.substring(0, closestI);
+	  String right = label.substring(closestI, label.length());
+	  if (left.length() > 1 && right.length() > 1) {
+	    gx.drawString(left, (cx+(width/2)) - (fm.stringWidth(left) / 2), 
+			  cy+height+(hf * 1)+2);
+	    gx.drawString(right, (cx+(width/2)) - (fm.stringWidth(right) / 2), 
+			  cy+height+(hf * 2)+2);
+	  } else {
+	    gx.drawString(label, (cx+(width/2)) - (fm.stringWidth(label) / 2), 
+			  cy+height+(hf * 1)+2);
+	  }
+	} else {
+	  gx.drawString(label, (cx+(width/2)) - (fm.stringWidth(label) / 2), 
+			cy+height+(hf * 1)+2);
+	}
+      }
+    }
+  }
+
+  /**
+   * Looks for a bean (if any) whose bounds contain the supplied point
+   *
+   * @param p a point
+   * @return a bean that contains the supplied point or null if no bean
+   * contains the point
+   */
+  public static BeanInstance findInstance(Point p) {
+    Rectangle tempBounds = new Rectangle();
+    for (int i=0; i < COMPONENTS.size(); i++) {
+      
+      BeanInstance t = (BeanInstance)COMPONENTS.elementAt(i);
+      JComponent temp = (JComponent)t.getBean();
+				      
+      tempBounds = temp.getBounds(tempBounds);
+      if (tempBounds.contains(p)) {
+	return t;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Looks for all beans (if any) located within
+   * the supplied bounding box. Also adjusts the
+   * bounding box to be a tight fit around all
+   * contained beans
+   *
+   * @param boundingBox the bounding rectangle
+   * @return a Vector of BeanInstances
+   */
+  public static Vector findInstances(Rectangle boundingBox) {
+    Graphics gx = null;
+    FontMetrics fm = null;
+    
+    int centerX, centerY;
+    int startX, startY, endX, endY;
+    startX = (int)boundingBox.getX();
+    startY = (int)boundingBox.getY();
+    endX = (int)boundingBox.getMaxX();
+    endY = (int)boundingBox.getMaxY();
+    int minX = Integer.MAX_VALUE;
+    int minY = Integer.MAX_VALUE;
+    int maxX = Integer.MIN_VALUE;
+    int maxY = Integer.MIN_VALUE;
+    Vector result = new Vector();
+    for (int i = 0; i < COMPONENTS.size(); i++) {
+      BeanInstance t = (BeanInstance)COMPONENTS.elementAt(i);
+      centerX = t.getX() + (t.getWidth()/2);
+      centerY = t.getY() + (t.getHeight()/2);
+      if (boundingBox.contains(centerX, centerY)) {
+	result.addElement(t);
+
+
+	// adjust bounding box stuff
+//	int hf = 0;
+	if (gx == null) {
+	  gx = ((JComponent)t.getBean()).getGraphics();
+	  gx.setFont(new Font(null, Font.PLAIN, 9));
+	  fm = gx.getFontMetrics();
+//	  hf = fm.getAscent();
+	}
+	String label = "";
+	if (t.getBean() instanceof Visible) {
+	  label = ((Visible)t.getBean()).getVisual().getText();
+	}
+	int labelwidth = fm.stringWidth(label);
+	int heightMultiplier = (labelwidth > t.getWidth())
+	? 2
+	: 1;
+	/*if (label.length() == 0) {
+	  heightMultiplier = 0;
+	}*/
+	int brx = 0;
+	int blx = 0;
+	if (centerX - (labelwidth / 2) - 2 < t.getX()) {
+	  blx = (centerX - (labelwidth / 2) - 2);
+	  brx = centerX + (labelwidth / 2) + 2;
+	} else {
+	  blx = t.getX() - 2;
+	  brx = t.getX() + t.getWidth() + 2;
+	}
+
+	if (blx < minX) {
+	  minX = blx;
+	}
+	if (brx > maxX) {
+	  maxX = brx;
+	}
+	if (t.getY() - 2 < minY) {
+	  minY = t.getY() - 2;
+	}
+	if (t.getY() + t.getHeight() + 2 > maxY) {
+	  maxY = t.getY() + t.getHeight()  + 2;
+	}
+      }
+    }
+    boundingBox.setBounds(minX, minY, maxX - minX, maxY - minY);
+    
+    return result;
+  }
+
+  /**
+   * Creates a new <code>BeanInstance</code> instance.
+   *
+   * @param container a <code>JComponent</code> to add the bean to
+   * @param bean the bean to add
+   * @param x the x coordinate of the bean
+   * @param y the y coordinate of the bean
+   */
+  public BeanInstance(JComponent container, Object bean, int x, int y) {
+    m_bean = bean;
+    m_x = x;
+    m_y = y;
+    addBean(container);
+  }
+
+  /**
+   * Creates a new <code>BeanInstance</code> instance given the fully
+   * qualified name of the bean
+   *
+   * @param container a <code>JComponent</code> to add the bean to
+   * @param beanName the fully qualified name of the bean
+   * @param x the x coordinate of the bean
+   * @param y th y coordinate of the bean
+   */
+  public BeanInstance(JComponent container, String beanName, int x, int y) {
+    m_x = x;
+    m_y = y;
+    
+    // try and instantiate the named component
+    try {
+      m_bean = Beans.instantiate(null, beanName);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      return;
+    }
+
+    addBean(container);
+  }
+
+  /**
+   * Remove this bean from the list of beans and from the containing component
+   *
+   * @param container the <code>JComponent</code> that holds the bean
+   */
+  public void removeBean(JComponent container) {
+    for (int i = 0; i < COMPONENTS.size(); i++) {
+      if ((BeanInstance)COMPONENTS.elementAt(i) == this) {
+	System.err.println("Removing bean");
+	COMPONENTS.removeElementAt(i);
+      }
+    }
+    if (container != null) {
+      container.remove((JComponent)m_bean);
+      container.revalidate();
+      container.repaint();
+    }
+  }
+
+  /**
+   * Adds this bean to the global list of beans and
+   * to the supplied container. The constructor
+   * calls this method, so a client should not need
+   * to unless they have called removeBean and then
+   * wish to have it added again.
+   *
+   * @param container the Component on which this
+   * BeanInstance will be displayed
+   */
+  public void addBean(JComponent container) {
+
+    // do nothing if we are already in the list
+    if (COMPONENTS.contains(this)) {
+      return;
+    }
+
+    // Ignore invisible components
+    if (!Beans.isInstanceOf(m_bean, JComponent.class)) {
+      System.err.println("Component is invisible!");
+      return;
+    }
+    
+    COMPONENTS.addElement(this);
+    
+    // Position and layout the component
+    JComponent c = (JComponent)m_bean;
+    Dimension d = c.getPreferredSize();
+    int dx = (int)(d.getWidth() / 2);
+    int dy = (int)(d.getHeight() / 2);
+    m_x -= dx;
+    m_y -= dy;
+    c.setLocation(m_x, m_y);
+    //    c.doLayout();
+    c.validate();
+    //    bp.addBean(c);
+    //    c.repaint();
+    if (container != null) {
+      container.add(c);
+      container.revalidate();
+    }
+  }
+
+  /**
+   * Gets the bean encapsulated in this instance
+   *
+   * @return an <code>Object</code> value
+   */
+  public Object getBean() {
+    return m_bean;
+  }
+
+  /**
+   * Gets the x coordinate of this bean
+   *
+   * @return an <code>int</code> value
+   */
+  public int getX() {
+    return m_x;
+  }
+
+  /**
+   * Gets the y coordinate of this bean
+   *
+   * @return an <code>int</code> value
+   */
+  public int getY() {
+    return m_y;
+  }
+
+  /**
+   * Gets the width of this bean
+   *
+   * @return an <code>int</code> value
+   */
+  public int getWidth() {
+    return ((JComponent)m_bean).getWidth();
+  }
+
+  /**
+   * Gets the height of this bean
+   *
+   * @return an <code>int</code> value
+   */
+  public int getHeight() {
+    return ((JComponent)m_bean).getHeight();
+  }
+ 
+  /**
+   * Set the x and y coordinates of this bean
+   *
+   * @param newX the x coordinate
+   * @param newY the y coordinate
+   */
+  public void setXY(int newX, int newY) {
+    setX(newX);
+    setY(newY);
+    if (getBean() instanceof MetaBean) {
+      ((MetaBean)getBean()).shiftBeans(this, false);
+    }
+  }
+
+  /**
+   * Sets the x coordinate of this bean
+   *
+   * @param newX an <code>int</code> value
+   */
+  public void setX(int newX) {
+    m_x = newX;
+    ((JComponent)m_bean).setLocation(m_x, m_y);
+    ((JComponent)m_bean).validate();
+  }
+
+  /**
+   * Sets the y coordinate of this bean
+   *
+   * @param newY an <code>int</code> value
+   */
+  public void setY(int newY) {
+    m_y = newY;
+    ((JComponent)m_bean).setLocation(m_x, m_y);
+    ((JComponent)m_bean).validate();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/BeanVisual.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/BeanVisual.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/BeanVisual.java	(revision 29)
@@ -0,0 +1,413 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BeanVisual.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * BeanVisual encapsulates icons and label for a given bean. Has methods
+ * to load icons, set label text and toggle between static and animated
+ * versions of a bean's icon.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.10 $
+ * @since 1.0
+ * @see JPanel
+ * @see Serializable
+ */
+public class BeanVisual
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6677473561687129614L;
+
+  public static final String ICON_PATH="weka/gui/beans/icons/";
+
+  public static final int NORTH_CONNECTOR = 0;
+  public static final int SOUTH_CONNECTOR = 1;
+  public static final int EAST_CONNECTOR = 2;
+  public static final int WEST_CONNECTOR = 3;
+
+  /**
+   * Holds name (including path) of the static icon
+   */
+  protected String m_iconPath;
+
+  /**
+   * Holds name (including path) of the animated icon
+   */
+  protected String m_animatedIconPath;
+
+  /**
+   * ImageIcons for the icons. Is transient because for some reason
+   * animated gifs cease to be animated after restoring from serialization.
+   * Icons are re-loaded from source after deserialization
+   */
+  protected transient ImageIcon m_icon;
+  protected transient ImageIcon m_animatedIcon;
+
+  /**
+   * Name for the bean
+   */
+  protected String m_visualName;
+
+  protected JLabel m_visualLabel;
+  
+  /**
+   * Container for the icon
+   */
+  //  protected IconHolder m_visualHolder;
+
+  //  protected JLabel m_textLabel;
+  private boolean m_stationary = true;
+
+  private PropertyChangeSupport m_pcs = new PropertyChangeSupport(this);
+  
+  private boolean m_displayConnectors = false;
+  private Color m_connectorColor = Color.blue;
+
+  /**
+   * Constructor
+   *
+   * @param visualName name for the bean
+   * @param iconPath path to the icon file
+   * @param animatedIconPath path to the animated icon file
+   */
+  public BeanVisual(String visualName, String iconPath, 
+		    String animatedIconPath) {
+
+    loadIcons(iconPath, animatedIconPath);
+    m_visualName = visualName;
+    //    m_textLabel = new JLabel(m_visualName, JLabel.CENTER);
+    m_visualLabel = new JLabel(m_icon);
+
+    setLayout(new BorderLayout());
+   
+    //    m_visualHolder = new IconHolder(m_visualLabel);
+    
+    add(m_visualLabel, BorderLayout.CENTER);
+    Dimension d = m_visualLabel.getPreferredSize();
+    //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
+    Dimension d2 = new Dimension((int)d.getWidth() + 10, 
+				 (int)d.getHeight() + 10);
+    setMinimumSize(d2);
+    setPreferredSize(d2);
+    setMaximumSize(d2);
+  }
+
+  /**
+   * Reduce this BeanVisual's icon size by the given factor
+   *
+   * @param factor the factor by which to reduce the icon size by
+   */
+  public void scale(int factor) {
+    if (m_icon != null) {
+      removeAll();
+      Image pic = m_icon.getImage();
+      int width = m_icon.getIconWidth();
+      int height = m_icon.getIconHeight();
+      int reduction = width / factor;
+      width -= reduction;
+      height -= reduction;
+      pic = pic.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+      m_icon = new ImageIcon(pic);
+      m_visualLabel = new JLabel(m_icon);
+      add(m_visualLabel, BorderLayout.CENTER);
+      Dimension d = m_visualLabel.getPreferredSize();
+      //      this.setSize((int)d.getWidth()+50, (int)d.getHeight()+50);
+      Dimension d2 = new Dimension((int)d.getWidth() + 10, 
+				   (int)d.getHeight() + 10);
+      setMinimumSize(d2);
+      setPreferredSize(d2);
+      setMaximumSize(d2);   
+    }
+  }
+
+  /**
+   * Loads static and animated versions of a beans icons. These are
+   * assumed to be defined in the system resource location (i.e. in the
+   * CLASSPATH). If the named icons do not exist, no changes to the
+   * visual appearance is made. Since default icons for generic
+   * types of beans (eg. DataSource, Classifier etc)
+   * are assumed to exist, it allows developers to add custom icons for
+   * for specific instantiations of these beans 
+   * (eg. J48, DiscretizeFilter etc) at their leisure.
+   *
+   * @param iconPath path to
+   * @param animatedIconPath a <code>String</code> value
+   */
+  public boolean loadIcons(String iconPath, String animatedIconPath) {
+    boolean success = true;
+    //    java.net.URL imageURL = ClassLoader.getSystemResource(iconPath);
+    java.net.URL imageURL = this.getClass().getClassLoader().getResource(iconPath);
+    if (imageURL == null) {
+      //      System.err.println("Warning: unable to load "+iconPath);
+    } else {
+      Image pic = Toolkit.getDefaultToolkit().
+	getImage(imageURL);
+
+      m_icon = new ImageIcon(pic);
+      if (m_visualLabel != null) {
+	m_visualLabel.setIcon(m_icon);
+      }
+    }
+    
+    //    imageURL = ClassLoader.getSystemResource(animatedIconPath);
+    imageURL = this.getClass().getClassLoader().getResource(animatedIconPath);
+    if (imageURL == null) {
+      //      System.err.println("Warning: unable to load "+animatedIconPath);
+      success = false;
+    } else {
+      Image pic2 = Toolkit.getDefaultToolkit().
+	getImage(imageURL);
+      m_animatedIcon = new ImageIcon(pic2);
+    }
+    m_iconPath = iconPath;
+    m_animatedIconPath = animatedIconPath;
+    return success;
+  }
+
+  /**
+   * Set the label for the visual. Informs any property change listeners
+   *
+   * @param text the label
+   */
+  public void setText(String text) {
+    m_visualName = text;
+    //    m_textLabel.setText(m_visualName);
+    m_pcs.firePropertyChange("label",null,null);
+  }
+
+  /**
+   * Get the visual's label
+   *
+   * @return a <code>String</code> value
+   */
+  public String getText() {
+    return m_visualName;
+  }
+
+  /**
+   * Set the static version of the icon
+   *
+   */
+  public void setStatic() {
+    m_visualLabel.setIcon(m_icon);
+  }
+
+  /**
+   * Set the animated version of the icon
+   *
+   */
+  public void setAnimated() {
+    m_visualLabel.setIcon(m_animatedIcon);
+  }
+
+  /**
+   * Returns the coordinates of the closest "connector" point to the
+   * supplied point. Coordinates are in the parent containers coordinate
+   * space.
+   *
+   * @param pt the reference point
+   * @return the closest connector point
+   */
+  public Point getClosestConnectorPoint(Point pt) {
+    int sourceX = getParent().getX();
+    int sourceY = getParent().getY();
+    int sourceWidth = getWidth();
+    int sourceHeight = getHeight();
+    int sourceMidX = sourceX + (sourceWidth / 2);
+    int sourceMidY = sourceY + (sourceHeight / 2);
+    int x = (int)pt.getX();
+    int y = (int)pt.getY();
+    
+    Point closest = new Point();
+    int cx = (Math.abs(x - sourceMidX) < Math.abs(y - sourceMidY)) ? 
+      sourceMidX :
+      ((x < sourceMidX) ? sourceX : sourceX + sourceWidth);
+    int cy = (Math.abs(y - sourceMidY) < Math.abs(x - sourceMidX)) ? 
+      sourceMidY :
+      ((y < sourceMidY) ? sourceY : sourceY + sourceHeight) ;
+    closest.setLocation(cx, cy);
+    return closest;
+  }
+
+  /**
+   * Returns the coordinates of the connector point given a compass point
+   *
+   * @param compassPoint a compass point
+   * @return a <code>Point</code> value
+   */
+  public Point getConnectorPoint(int compassPoint) {
+    int sourceX = getParent().getX();
+    int sourceY = getParent().getY();
+    int sourceWidth = getWidth();
+    int sourceHeight = getHeight();
+    int sourceMidX = sourceX + (sourceWidth / 2);
+    int sourceMidY = sourceY + (sourceHeight / 2);
+
+    switch (compassPoint) {
+    case NORTH_CONNECTOR : return new Point(sourceMidX, sourceY);
+    case SOUTH_CONNECTOR : return new Point(sourceMidX, sourceY+sourceHeight);
+    case WEST_CONNECTOR :  return new Point(sourceX, sourceMidY);
+    case EAST_CONNECTOR :  return new Point(sourceX+sourceWidth, sourceMidY);
+    default : System.err.println("Unrecognised connectorPoint (BeanVisual)");
+    }
+    return new Point(sourceX, sourceY);
+  }
+
+  /**
+   * Returns the static icon
+   *
+   * @return an <code>ImageIcon</code> value
+   */
+  public ImageIcon getStaticIcon() {
+    return m_icon;
+  }
+
+  /**
+   * Returns the animated icon
+   *
+   * @return an <code>ImageIcon</code> value
+   */
+  public ImageIcon getAnimatedIcon() {
+    return m_animatedIcon;
+  }
+
+  /**
+   * returns the path for the icon
+   * 
+   * @return the path for the icon
+   */
+  public String getIconPath() {
+    return m_iconPath;
+  }
+
+  /**
+   * returns the path for the animated icon
+   * 
+   * @return the path for the animated icon
+   */
+  public String getAnimatedIconPath() {
+    return m_animatedIconPath;
+  }
+
+  /**
+   * Turn on/off the connector points
+   *
+   * @param dc a <code>boolean</code> value
+   */
+  public void setDisplayConnectors(boolean dc) {
+    //    m_visualHolder.setDisplayConnectors(dc);
+    m_displayConnectors = dc;
+    m_connectorColor = Color.blue;
+    repaint();
+  }
+
+  /**
+   * Turn on/off the connector points
+   *
+   * @param dc a <code>boolean</code> value
+   * @param c the Color to use
+   */
+  public void setDisplayConnectors(boolean dc,
+                                   Color c) {
+    setDisplayConnectors(dc);
+    m_connectorColor = c;
+  }
+
+  /**
+   * Add a listener for property change events
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcs.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcs.removePropertyChangeListener(pcl);
+  }
+
+  public void paintComponent(Graphics gx) {
+    super.paintComponent(gx);
+    if (m_displayConnectors) {
+      gx.setColor(m_connectorColor);
+      
+      int midx = (int)(this.getWidth() / 2.0);
+      int midy = (int)(this.getHeight() / 2.0);
+      gx.fillOval(midx-2, 0, 5, 5);
+      gx.fillOval(midx-2, this.getHeight()-5, 5, 5);
+      gx.fillOval(0, midy-2, 5, 5);
+      gx.fillOval(this.getWidth()-5, midy-2, 5, 5);
+    }
+  }
+
+  /**
+   * Overides default read object in order to reload icons.
+   * This is necessary because for some strange reason animated
+   * gifs stop being animated after being serialized/deserialized.
+   *
+   * @param ois an <code>ObjectInputStream</code> value
+   * @exception IOException if an error occurs
+   * @exception ClassNotFoundException if an error occurs
+   */
+  private void readObject(ObjectInputStream ois) 
+    throws IOException, ClassNotFoundException {
+    try {
+      ois.defaultReadObject();
+      remove(m_visualLabel);
+      m_visualLabel = new JLabel(m_icon);
+      loadIcons(m_iconPath, m_animatedIconPath);
+      add(m_visualLabel, BorderLayout.CENTER);
+      Dimension d = m_visualLabel.getPreferredSize();
+      Dimension d2 = new Dimension((int)d.getWidth() + 10, 
+				   (int)d.getHeight() + 10);
+      setMinimumSize(d2);
+      setPreferredSize(d2);
+      setMaximumSize(d2);   
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Beans.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Beans.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Beans.props	(revision 29)
@@ -0,0 +1,69 @@
+# list of standard toolbars (containing bean tools that do not wrap weka
+# base class types)
+weka.gui.beans.KnowledgeFlow.standardToolBars=\
+ Evaluation,\
+ Visualization
+
+# Specifies the tools for each standard toolbar
+weka.gui.beans.KnowledgeFlow.Evaluation=weka.gui.beans.TrainingSetMaker,\
+ weka.gui.beans.TestSetMaker,\
+ weka.gui.beans.CrossValidationFoldMaker,\
+ weka.gui.beans.TrainTestSplitMaker,\
+ weka.gui.beans.InstanceStreamToBatchMaker,\
+ weka.gui.beans.ClassAssigner,\
+ weka.gui.beans.ClassValuePicker,\
+ weka.gui.beans.ClassifierPerformanceEvaluator,\
+ weka.gui.beans.IncrementalClassifierEvaluator,\
+ weka.gui.beans.ClustererPerformanceEvaluator,\
+ weka.gui.beans.PredictionAppender,\
+ weka.gui.beans.SerializedModelSaver
+weka.gui.beans.KnowledgeFlow.Visualization=weka.gui.beans.DataVisualizer,\
+ weka.gui.beans.ScatterPlotMatrix,\
+ weka.gui.beans.AttributeSummarizer,\
+ weka.gui.beans.ModelPerformanceChart,\
+  weka.gui.beans.CostBenefitAnalysis,\
+ weka.gui.beans.TextViewer,\
+ weka.gui.beans.GraphViewer,\
+ weka.gui.beans.StripChart
+
+# the bean panel processes keys in the generic object editor properties file.
+# If there is a corresponding entry here, then a toolbar is constructed using
+# the specified wrapper bean
+weka.core.converters.Loader=weka.gui.beans.Loader
+weka.core.converters.Saver=weka.gui.beans.Saver
+weka.classifiers.Classifier=weka.gui.beans.Classifier
+weka.filters.Filter=weka.gui.beans.Filter
+weka.clusterers.Clusterer=weka.gui.beans.Clusterer
+weka.associations.Associator=weka.gui.beans.Associator
+# weka.attributeSelection.ASEvaluation=weka.gui.beans.AttributeSelector
+
+# toolbar ordering information for wrapper types
+weka.core.converters.Loader.order=0
+weka.core.converters.Saver.order=1
+weka.filters.Filter.order=3
+weka.classifiers.Classifier.order=4
+weka.clusterers.Clusterer.order=5
+weka.associations.Associator.order=6
+# weka.attributeSelection.ASEvaluation.order=6
+
+# toolbar naming aliases for weka algorithm classes
+weka.core.converters.Loader.alias=DataSources
+weka.core.converters.Saver.alias=DataSinks
+weka.classifiers.Classifier.alias=Classifiers
+weka.clusterers.Clusterer.alias=Clusterers
+weka.filters.Filter.alias=Filters
+weka.associations.Associator.alias=Associations
+
+# GUI behaviour
+ScrollBarIncrementLayout=20
+ScrollBarIncrementComponents=50
+FlowWidth=1024
+FlowHeight=768
+PreferredExtension=.kf
+UserComponentsInXML=false
+
+# colours
+weka.gui.beans.StripChart.backgroundColour=black
+weka.gui.beans.StripChart$LegendPanel.borderColour=blue
+#weka.gui.beans.StripChart.backgroundColour=white
+#weka.gui.beans.StripChart$LegendPanel.borderColour=black
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ChartEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ChartEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ChartEvent.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ChartEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventObject;
+import java.util.Vector;
+
+/**
+ * Event encapsulating info for plotting a data point on the StripChart
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public class ChartEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7812460715499569390L;
+
+  private Vector m_legendText;
+  private double m_max;
+  private double m_min;
+
+  private boolean m_reset;
+
+  /**
+   * Y values of the data points
+   */
+  private double [] m_dataPoint;
+
+  /**
+   * Creates a new <code>ChartEvent</code> instance.
+   *
+   * @param source the source of the event
+   * @param legendText a vector of strings to display in the legend
+   * @param min minimum y value
+   * @param max maximum y value
+   * @param dataPoint an array of y values to plot
+   * @param reset true if display is to be reset
+   */
+  public ChartEvent(Object source, Vector legendText, double min, double max,
+		    double [] dataPoint, boolean reset) {
+    super(source);
+    m_legendText = legendText;
+    m_max = max;
+    m_min = min;
+    m_dataPoint = dataPoint;
+    m_reset = reset;
+  }
+
+  /**
+   * Creates a new <code>ChartEvent</code> instance.
+   *
+   * @param source the source of the event
+   */
+  public ChartEvent(Object source) {
+    super(source);
+  }
+
+  /**
+   * Get the legend text vector
+   *
+   * @return a <code>Vector</code> value
+   */
+  public Vector getLegendText() {
+    return m_legendText;
+  }
+
+  /**
+   * Set the legend text vector
+   *
+   * @param lt a <code>Vector</code> value
+   */
+  public void setLegendText(Vector lt) {
+    m_legendText = lt;
+  }
+
+  /**
+   * Get the min y value
+   *
+   * @return a <code>double</code> value
+   */
+  public double getMin() {
+    return m_min;
+  }
+
+  /**
+   * Set the min y value
+   *
+   * @param m a <code>double</code> value
+   */
+  public void setMin(double m) {
+    m_min = m;
+  }
+
+  /**
+   * Get the max y value
+   *
+   * @return a <code>double</code> value
+   */
+  public double getMax() {
+    return m_max;
+  }
+
+  /**
+   * Set the max y value
+   *
+   * @param m a <code>double</code> value
+   */
+  public void setMax(double m) {
+    m_max = m;
+  }
+
+  /**
+   * Get the data point
+   *
+   * @return a <code>double[]</code> value
+   */
+  public double [] getDataPoint() {
+    return m_dataPoint;
+  }
+
+  /**
+   * Set the data point
+   *
+   * @param dp a <code>double[]</code> value
+   */
+  public void setDataPoint(double [] dp) {
+    m_dataPoint = dp;
+  }
+
+  /**
+   * Set the reset flag
+   *
+   * @param reset a <code>boolean</code> value
+   */
+  public void setReset(boolean reset) {
+    m_reset = reset;
+  }
+
+  /**
+   * get the value of the reset flag
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean getReset() {
+    return m_reset;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ChartListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ChartListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ChartListener.java	(revision 29)
@@ -0,0 +1,36 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ChartListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can process a ChartEvent
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface ChartListener extends EventListener {
+
+  void acceptDataPoint(ChartEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssigner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssigner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssigner.java	(revision 29)
@@ -0,0 +1,557 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassAssigner.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Bean that assigns a class attribute to a data set.
+ *
+ * @author Mark Hall
+ * @version $Revision: 5667 $
+ */
+public class ClassAssigner
+  extends JPanel
+  implements Visible, DataSourceListener, TrainingSetListener, TestSetListener,
+	     DataSource, TrainingSetProducer, TestSetProducer,
+	     BeanCommon, EventConstraints, Serializable,
+	     InstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4011131665025817924L;
+  
+  private String m_classColumn = "last";
+
+  /** format of instances for current incoming connection (if any) */
+  private Instances m_connectedFormat;
+
+  private Object m_trainingProvider;
+  private Object m_testProvider;
+  private Object m_dataProvider;
+  private Object m_instanceProvider;
+
+  private Vector m_trainingListeners = new Vector();
+  private Vector m_testListeners = new Vector();
+  private Vector m_dataListeners = new Vector();
+  private Vector m_instanceListeners = new Vector();
+
+  private Vector m_dataFormatListeners = new Vector();
+
+  protected transient weka.gui.Logger m_logger = null;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("ClassAssigner", 
+		   BeanVisual.ICON_PATH+"ClassAssigner.gif",
+		   BeanVisual.ICON_PATH+"ClassAssigner_animated.gif");
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Designate which column is to be considered the class column "
+      +"in incoming data.";
+  }
+
+  public ClassAssigner() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);    
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Tool tip text for this property
+   *
+   * @return a <code>String</code> value
+   */
+  public String classColumnTipText() {
+    return "Specify the number of the column that contains the class attribute";
+  }
+
+  /**
+   * Returns the structure of the incoming instances (if any)
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getConnectedFormat() {
+    return m_connectedFormat;
+  }
+
+  public void setClassColumn(String col) {
+    m_classColumn = col;
+    if (m_connectedFormat != null) {
+      assignClass(m_connectedFormat);
+    }
+  }
+
+  public String getClassColumn() {
+    return m_classColumn;
+  }
+
+  public void acceptDataSet(DataSetEvent e) {
+    Instances dataSet = e.getDataSet();
+    assignClass(dataSet);
+    notifyDataListeners(e);
+    if (e.isStructureOnly()) {
+      m_connectedFormat = e.getDataSet();
+      // tell any listening customizers (or other
+      notifyDataFormatListeners();
+    }
+  }
+
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    Instances trainingSet = e.getTrainingSet();
+    assignClass(trainingSet);
+    notifyTrainingListeners(e);
+    
+    if (e.isStructureOnly()) {
+      m_connectedFormat = e.getTrainingSet();
+      // tell any listening customizers (or other
+      notifyDataFormatListeners();
+    }
+  }
+
+  public void acceptTestSet(TestSetEvent e) {
+    Instances testSet = e.getTestSet();
+    assignClass(testSet);
+    notifyTestListeners(e);
+    if (e.isStructureOnly()) {
+      m_connectedFormat = e.getTestSet();
+      // tell any listening customizers (or other
+      notifyDataFormatListeners();
+    }
+  }
+
+  public void acceptInstance(InstanceEvent e) {
+    if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+      //      Instances dataSet = e.getInstance().dataset();
+      m_connectedFormat = e.getStructure();
+      
+      //      System.err.println("Assigning class column...");
+      assignClass(m_connectedFormat);
+      notifyInstanceListeners(e);
+
+      // tell any listening customizers (or other interested parties)
+      System.err.println("Notifying customizer...");
+      notifyDataFormatListeners();
+    } else {
+      //      Instances dataSet = e.getInstance().dataset();
+      //      assignClass(dataSet);
+      notifyInstanceListeners(e);
+    }
+  }
+
+  private void assignClass(Instances dataSet) {
+    int classCol = -1;
+    if (m_classColumn.toLowerCase().compareTo("last") == 0) {
+      dataSet.setClassIndex(dataSet.numAttributes()-1);
+    } else if (m_classColumn.toLowerCase().compareTo("first") == 0) {
+      dataSet.setClassIndex(0);
+    } else {
+      classCol = Integer.parseInt(m_classColumn) - 1;
+      if (/*classCol < 0 ||*/ classCol > dataSet.numAttributes()-1) {
+	if (m_logger != null) {
+	  m_logger.logMessage("Class column outside range of data "
+			      +"(ClassAssigner)");
+	}
+      } else {
+	dataSet.setClassIndex(classCol);
+      }
+    }
+  }
+
+  protected void notifyTestListeners(TestSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_testListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	System.err.println("Notifying test listeners "
+			   +"(ClassAssigner)");
+	((TestSetListener)l.elementAt(i)).acceptTestSet(tse);
+      }
+    }
+  }
+
+  protected void notifyTrainingListeners(TrainingSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_trainingListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	System.err.println("Notifying training listeners "
+			   +"(ClassAssigner)");
+	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(tse);
+      }
+    }
+  }
+
+  protected void notifyDataListeners(DataSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	System.err.println("Notifying data listeners "
+			   +"(ClassAssigner)");
+	((DataSourceListener)l.elementAt(i)).acceptDataSet(tse);
+      }
+    }
+  }
+
+  protected void notifyInstanceListeners(InstanceEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_instanceListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying instance listeners "
+	//			   +"(ClassAssigner)");
+	
+	((InstanceListener)l.elementAt(i)).acceptInstance(tse);
+      }
+    }
+  }
+
+  protected void notifyDataFormatListeners() {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataFormatListeners.clone();
+    }
+    if (l.size() > 0) {
+      DataSetEvent dse = new DataSetEvent(this, m_connectedFormat);
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying instance listeners "
+	//			   +"(ClassAssigner)");
+	((DataFormatListener)l.elementAt(i)).newDataFormat(dse);
+      }
+    }
+  }
+
+  public synchronized void addInstanceListener(InstanceListener tsl) {
+    m_instanceListeners.addElement(tsl);
+    if (m_connectedFormat != null) {
+      InstanceEvent e = new InstanceEvent(this, m_connectedFormat);
+      tsl.acceptInstance(e);
+    }
+  }
+
+  public synchronized void removeInstanceListener(InstanceListener tsl) {
+    m_instanceListeners.removeElement(tsl);
+  }
+
+  public synchronized void addDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.addElement(tsl);
+    // pass on any format that we might know about
+    if (m_connectedFormat != null) {
+      DataSetEvent e = new DataSetEvent(this, m_connectedFormat);
+      tsl.acceptDataSet(e);
+    }
+  }
+
+  public synchronized void removeDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.removeElement(tsl);
+  }
+
+  public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
+    m_trainingListeners.addElement(tsl);
+    // pass on any format that we might know about
+    if (m_connectedFormat != null) {
+      TrainingSetEvent e = new TrainingSetEvent(this, m_connectedFormat);
+      tsl.acceptTrainingSet(e);
+    }
+  }
+
+  public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
+    m_trainingListeners.removeElement(tsl);
+  }
+
+  public synchronized void addTestSetListener(TestSetListener tsl) {
+    m_testListeners.addElement(tsl);
+    // pass on any format that we might know about
+    if (m_connectedFormat != null) {
+      TestSetEvent e = new TestSetEvent(this, m_connectedFormat);
+      tsl.acceptTestSet(e);
+    }
+  }
+
+  public synchronized void removeTestSetListener(TestSetListener tsl) {
+    m_testListeners.removeElement(tsl);
+  }
+
+  public synchronized void addDataFormatListener(DataFormatListener dfl) {
+    m_dataFormatListeners.addElement(dfl);
+  }
+
+  public synchronized void removeDataFormatListener(DataFormatListener dfl) {
+    m_dataFormatListeners.removeElement(dfl);
+  }
+
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"ClassAssigner.gif",
+		       BeanVisual.ICON_PATH+"ClassAssigner_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    if (eventName.compareTo("trainingSet") == 0 && 
+	(m_trainingProvider != null || m_dataProvider != null ||
+	 m_instanceProvider != null)) { 
+      return false;
+    }
+    
+    if (eventName.compareTo("testSet") == 0 && 
+	m_testProvider != null) { 
+      return false;
+    }
+
+     if (eventName.compareTo("instance") == 0 &&
+	m_instanceProvider != null || m_trainingProvider != null ||
+	 m_dataProvider != null) {
+       return false;
+     } 
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      if (eventName.compareTo("trainingSet") == 0) {
+	m_trainingProvider = source;
+      } else if (eventName.compareTo("testSet") == 0) {
+	m_testProvider = source;
+      } else if (eventName.compareTo("dataSet") == 0) {
+	m_dataProvider = source;
+      } else if (eventName.compareTo("instance") == 0) {
+	m_instanceProvider = source;
+      }
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+
+    if (eventName.compareTo("trainingSet") == 0) {
+      if (m_trainingProvider == source) {
+	m_trainingProvider = null;
+      }
+    }
+    if (eventName.compareTo("testSet") == 0) {
+      if (m_testProvider == source) {
+	m_testProvider = null;
+      }
+    }
+    if (eventName.compareTo("dataSet") == 0) {
+      if (m_dataProvider == source) {
+	m_dataProvider = null;
+      }
+    }
+
+    if (eventName.compareTo("instance") == 0) {
+      if (m_instanceProvider == source) {
+	m_instanceProvider = null;
+      }
+    }
+  }
+  
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  public void stop() {
+    // Pass on to upstream beans
+    if (m_trainingProvider != null && m_trainingProvider instanceof BeanCommon) {
+      ((BeanCommon)m_trainingProvider).stop();
+    }
+    
+    if (m_testProvider != null && m_testProvider instanceof BeanCommon) {
+      ((BeanCommon)m_testProvider).stop();
+    }
+    
+    if (m_dataProvider != null && m_dataProvider instanceof BeanCommon) {
+      ((BeanCommon)m_dataProvider).stop();
+    }
+    
+    if (m_instanceProvider != null && m_instanceProvider instanceof BeanCommon) {
+      ((BeanCommon)m_instanceProvider).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+  
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.compareTo("trainingSet") == 0) { 
+      if (m_trainingProvider == null) {
+	return false;
+      } else {
+	if (m_trainingProvider instanceof EventConstraints) {
+	  if (!((EventConstraints)m_trainingProvider).
+	      eventGeneratable("trainingSet")) {
+	    return false;
+	  }
+	}
+      }
+    }
+
+    if (eventName.compareTo("dataSet") == 0) { 
+      if (m_dataProvider == null) {
+	if (m_instanceProvider == null) {
+	  m_connectedFormat = null;
+	  notifyDataFormatListeners();
+	}
+	return false;
+      } else {
+	if (m_dataProvider instanceof EventConstraints) {
+	  if (!((EventConstraints)m_dataProvider).
+	      eventGeneratable("dataSet")) {
+	    m_connectedFormat = null;
+	    notifyDataFormatListeners();
+	    return false;
+	  }
+	}
+      }
+    }
+
+    if (eventName.compareTo("instance") == 0) { 
+      if (m_instanceProvider == null) {
+	if (m_dataProvider == null) {
+	  m_connectedFormat = null;
+	  notifyDataFormatListeners();
+	}
+	return false;
+      } else {
+	if (m_instanceProvider instanceof EventConstraints) {
+	  if (!((EventConstraints)m_instanceProvider).
+	      eventGeneratable("instance")) {
+	    m_connectedFormat = null;
+	    notifyDataFormatListeners();
+	    return false;
+	  }
+	}
+      }
+    }
+
+    if (eventName.compareTo("testSet") == 0) {
+      if (m_testProvider == null) {
+	return false;
+      } else {
+	if (m_testProvider instanceof EventConstraints) {
+	  if (!((EventConstraints)m_testProvider).
+	      eventGeneratable("testSet")) {
+	    return false;
+	  }
+	}
+      }
+    }
+    return true;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerBeanInfo.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassAssignerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the class assigner bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class ClassAssignerBeanInfo 
+  extends SimpleBeanInfo {
+
+  /**
+   * Returns the event set descriptors
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = 
+      { new EventSetDescriptor(DataSource.class, 
+			       "dataSet", 
+			       DataSourceListener.class, 
+			       "acceptDataSet"),
+	new EventSetDescriptor(DataSource.class, 
+			       "instance", 
+			       InstanceListener.class, 
+			       "acceptInstance"),
+	new EventSetDescriptor(TrainingSetProducer.class, 
+			       "trainingSet", 
+			       TrainingSetListener.class, 
+			       "acceptTrainingSet"),
+	new EventSetDescriptor(TestSetProducer.class, 
+			       "testSet", 
+			       TestSetListener.class, 
+			       "acceptTestSet")  };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+  
+  /**
+   * Returns the property descriptors
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      p1 = new PropertyDescriptor("classColumn", ClassAssigner.class);
+      PropertyDescriptor [] pds = { p1 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.ClassAssigner.class,
+			      ClassAssignerCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassAssignerCustomizer.java	(revision 29)
@@ -0,0 +1,195 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassAssignerCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+
+/**
+ * GUI customizer for the class assigner bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.7 $
+ */
+public class ClassAssignerCustomizer
+  extends JPanel
+  implements Customizer, CustomizerClosingListener, DataFormatListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 476539385765301907L;
+
+  private boolean m_displayColNames = false;
+
+  private ClassAssigner m_classAssigner;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private PropertySheetPanel m_caEditor = 
+    new PropertySheetPanel();
+
+  private JComboBox m_ClassCombo = new JComboBox();
+  private JPanel m_holderP = new JPanel();
+
+  public ClassAssignerCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+
+    setLayout(new BorderLayout());
+    add(new javax.swing.JLabel("ClassAssignerCustomizer"), 
+	BorderLayout.NORTH);
+    m_holderP.setLayout(new BorderLayout());
+    m_holderP.setBorder(BorderFactory.createTitledBorder("Choose class attribute"));
+    m_holderP.add(m_ClassCombo, BorderLayout.CENTER);
+    m_ClassCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_classAssigner != null && m_displayColNames == true) {
+	    m_classAssigner.setClassColumn(""+(m_ClassCombo.getSelectedIndex()));
+	  }
+	}
+      });
+    add(m_caEditor, BorderLayout.CENTER);
+  }
+
+  private void setUpStandardSelection() {
+    if (m_displayColNames == true) {
+      remove(m_holderP);
+      m_caEditor.setTarget(m_classAssigner);
+      add(m_caEditor, BorderLayout.CENTER);
+      m_displayColNames = false;
+    }
+    validate(); repaint();
+  }
+  
+  private void setUpColumnSelection(Instances format) {
+    if (m_displayColNames == false) {
+      remove(m_caEditor);
+    }
+    int existingClassCol = format.classIndex();
+    if (existingClassCol < 0) {
+      existingClassCol = 0;
+    }
+    String [] attribNames = new String [format.numAttributes()+1];
+    attribNames[0] = "NO CLASS";
+    for (int i = 1; i < attribNames.length; i++) {
+      String type = "";
+      switch (format.attribute(i-1).type()) {
+      case Attribute.NOMINAL:
+	type = "(Nom) ";
+	break;
+      case Attribute.NUMERIC:
+	type = "(Num) ";
+	break;
+      case Attribute.STRING:
+	type = "(Str) ";
+	break;
+      case Attribute.DATE:
+	type = "(Dat) ";
+	break;
+      case Attribute.RELATIONAL:
+	type = "(Rel) ";
+	break;
+      default:
+	type = "(???) ";
+      }
+      attribNames[i] = type + format.attribute(i-1).name();
+    }
+    m_ClassCombo.setModel(new DefaultComboBoxModel(attribNames));
+    if (attribNames.length > 0) {
+      m_ClassCombo.setSelectedIndex(existingClassCol+1);
+    }
+    if (m_displayColNames == false) {
+      add(m_holderP, BorderLayout.CENTER);
+      m_displayColNames = true;
+    }
+    validate(); repaint();
+  }
+
+  /**
+   * Set the bean to be edited
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    if (m_classAssigner != (ClassAssigner)object) {
+      // remove ourselves as a listener from the old ClassAssigner (if necessary)
+      if (m_classAssigner != null) {
+	m_classAssigner.removeDataFormatListener(this);
+      }
+      m_classAssigner = (ClassAssigner)object;
+      // add ourselves as a data format listener
+      m_classAssigner.addDataFormatListener(this);
+      m_caEditor.setTarget(m_classAssigner);
+      if (m_classAssigner.getConnectedFormat() != null) {
+	setUpColumnSelection(m_classAssigner.getConnectedFormat());
+      }
+    }
+  }
+
+  public void customizerClosing() {
+    // remove ourselves as a listener from the ClassAssigner (if necessary)
+    if (m_classAssigner != null) {
+      System.err.println("Customizer deregistering with class assigner");
+      m_classAssigner.removeDataFormatListener(this);
+    }
+  }
+
+  public void newDataFormat(DataSetEvent dse) {
+    if (dse.getDataSet() != null) {
+      //      System.err.println("Setting up column selection.........");
+      setUpColumnSelection(m_classAssigner.getConnectedFormat());
+    } else {
+      setUpStandardSelection();
+    }
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePicker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePicker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePicker.java	(revision 29)
@@ -0,0 +1,377 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassValuePicker.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.SwapValues;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * @author Mark Hall
+ * @version $Revision: 5731 $
+ */
+public class ClassValuePicker
+  extends JPanel
+  implements Visible, DataSourceListener, BeanCommon,
+	     EventConstraints, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1196143276710882989L;
+
+  /** the class value index considered to be the positive class */
+  private int m_classValueIndex = 0;
+
+  /** format of instances for the current incoming connection (if any) */
+  private Instances m_connectedFormat;
+
+  private Object m_dataProvider;
+
+  private Vector m_dataListeners = new Vector();
+  private Vector m_dataFormatListeners = new Vector();
+
+  protected transient weka.gui.Logger m_logger = null;
+  
+  protected BeanVisual m_visual = 
+    new BeanVisual("ClassValuePicker", 
+		   BeanVisual.ICON_PATH+"ClassValuePicker.gif",
+		   BeanVisual.ICON_PATH+"ClassValuePicker_animated.gif");
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Designate which class value is to be considered the \"positive\" "
+      +"class value (useful for ROC style curves).";
+  }
+
+  public ClassValuePicker() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);    
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Returns the structure of the incoming instances (if any)
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getConnectedFormat() {
+    if (m_connectedFormat ==null) {
+      System.err.println("Is null!!!!!!");
+    }
+    return m_connectedFormat;
+  }
+
+  /**
+   * Set the class value index considered to be the "positive"
+   * class value.
+   *
+   * @param index the class value index to use
+   */
+  public void setClassValueIndex(int index) {
+    m_classValueIndex = index;
+    if (m_connectedFormat != null) {
+      notifyDataFormatListeners();
+    }
+  }
+
+  /**
+   * Gets the class value index considered to be the "positive"
+   * class value.
+   *
+   * @return the class value index
+   */
+  public int getClassValueIndex() {
+    return m_classValueIndex;
+  }
+
+  public void acceptDataSet(DataSetEvent e) {
+    if (e.isStructureOnly()) {
+      if (m_connectedFormat == null ||
+	  !m_connectedFormat.equalHeaders(e.getDataSet())) { 
+	m_connectedFormat = new Instances(e.getDataSet(), 0);
+	// tell any listening customizers (or other
+	notifyDataFormatListeners();
+      }
+    }
+    Instances dataSet = e.getDataSet();
+    Instances newDataSet = assignClassValue(dataSet);
+    e = new DataSetEvent(this, newDataSet);
+    notifyDataListeners(e);
+  }
+
+  private Instances assignClassValue(Instances dataSet) {
+    if (dataSet.classIndex() < 0) {
+      if (m_logger != null) {
+	m_logger.
+	  logMessage("[ClassValuePicker] " 
+	      + statusMessagePrefix() 
+	      + " No class attribute defined in data set.");
+	m_logger.statusMessage(statusMessagePrefix()
+	    + "WARNING: No class attribute defined in data set.");
+      }
+      return dataSet;
+    }
+    
+    if (dataSet.classAttribute().isNumeric()) {
+      if (m_logger != null) {
+	m_logger.
+	  logMessage("[ClassValuePicker] "
+	      + statusMessagePrefix()
+	      + " Class attribute must be nominal (ClassValuePicker)");
+	m_logger.statusMessage(statusMessagePrefix()
+	    + "WARNING: Class attribute must be nominal.");
+      }
+      return dataSet;
+    } else {
+      if (m_logger != null) {
+        m_logger.statusMessage(statusMessagePrefix() + "remove");
+      }
+    }
+
+    if (m_classValueIndex != 0) { // nothing to do if == 0
+      // swap selected index with index 0
+      try {
+	SwapValues sv = new SwapValues();
+	sv.setAttributeIndex(""+(dataSet.classIndex()+1));
+	sv.setFirstValueIndex("first");
+	sv.setSecondValueIndex(""+(m_classValueIndex+1));
+	sv.setInputFormat(dataSet);
+	Instances newDataSet = Filter.useFilter(dataSet, sv);
+	newDataSet.setRelationName(dataSet.relationName());
+	return newDataSet;
+      } catch (Exception ex) {
+	if (m_logger != null) {
+	  m_logger.
+	    logMessage("[ClassValuePicker] "
+	        +statusMessagePrefix()
+	        + " Unable to swap class attibute values.");
+	  m_logger.statusMessage(statusMessagePrefix()
+	      + "ERROR (See log for details)");
+	}
+      }
+    }
+    return dataSet;
+  }
+
+  protected void notifyDataListeners(DataSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	System.err.println("Notifying data listeners "
+			   +"(ClassValuePicker)");
+	((DataSourceListener)l.elementAt(i)).acceptDataSet(tse);
+      }
+    }
+  }
+
+  protected void notifyDataFormatListeners() {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataFormatListeners.clone();
+    }
+    if (l.size() > 0) {
+      DataSetEvent dse = new DataSetEvent(this, m_connectedFormat);
+      for(int i = 0; i < l.size(); i++) {
+	((DataFormatListener)l.elementAt(i)).newDataFormat(dse);
+      }
+    }
+  }
+
+  public synchronized void addDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.addElement(tsl);
+  }
+
+  public synchronized void removeDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.removeElement(tsl);
+  }
+
+  public synchronized void addDataFormatListener(DataFormatListener dfl) {
+    m_dataFormatListeners.addElement(dfl);
+  }
+
+  public synchronized void removeDataFormatListener(DataFormatListener dfl) {
+    m_dataFormatListeners.removeElement(dfl);
+  }
+
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"ClassValuePicker.gif",
+		       BeanVisual.ICON_PATH+"ClassValuePicker_animated.gif");
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    if (eventName.compareTo("dataSet") == 0 && 
+	(m_dataProvider != null)) { 
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      if (eventName.compareTo("dataSet") == 0) {
+	m_dataProvider = source;
+      }
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+
+    if (eventName.compareTo("dataSet") == 0) {
+      if (m_dataProvider == source) {
+	m_dataProvider = null;
+      }
+    }
+  }
+
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  public void stop() {
+    // nothing to do
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.compareTo("dataSet") != 0) {
+      return false;
+    }
+
+    if (eventName.compareTo("dataSet") == 0) { 
+      if (m_dataProvider == null) {
+	m_connectedFormat = null;
+	notifyDataFormatListeners();
+	return false;
+      } else {
+	if (m_dataProvider instanceof EventConstraints) {
+	  if (!((EventConstraints)m_dataProvider).
+	      eventGeneratable("dataSet")) {
+	    m_connectedFormat = null;
+	    notifyDataFormatListeners();
+	    return false;
+	  }
+	}
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerBeanInfo.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassValuePickerBeanInfo.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the class value picker bean
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.3 $
+ */
+public class ClassValuePickerBeanInfo 
+  extends SimpleBeanInfo {
+
+  /**
+   * Returns the event set descriptors
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = 
+      { new EventSetDescriptor(ClassValuePicker.class, 
+			       "dataSet", 
+			       DataSourceListener.class, 
+			       "acceptDataSet") };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+  
+  /**
+   * Returns the property descriptors
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      p1 = new PropertyDescriptor("classValueIndex", ClassValuePicker.class);
+      PropertyDescriptor [] pds = { p1 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.ClassValuePicker.class,
+			      ClassValuePickerCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassValuePickerCustomizer.java	(revision 29)
@@ -0,0 +1,172 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassValuePickerCustomizer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * @author Mark Hall
+ * @version $Revision: 1.6 $
+ */
+public class ClassValuePickerCustomizer
+  extends JPanel
+  implements Customizer, CustomizerClosingListener, DataFormatListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8213423053861600469L;
+
+  private boolean m_displayValNames = false;
+
+  private ClassValuePicker m_classValuePicker;
+  
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private JComboBox m_ClassValueCombo = new JComboBox();
+  private JPanel m_holderP = new JPanel();
+
+  private JLabel m_messageLabel = new JLabel("No customization possible at present.");
+
+  public ClassValuePickerCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+    
+    setLayout(new BorderLayout());
+    add(new javax.swing.JLabel("ClassValuePickerCustomizer"), 
+	BorderLayout.NORTH);
+    m_holderP.setLayout(new BorderLayout());
+    m_holderP.setBorder(BorderFactory.createTitledBorder("Choose class value"));
+    m_holderP.add(m_ClassValueCombo, BorderLayout.CENTER);
+    m_ClassValueCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_classValuePicker != null) {
+	    m_classValuePicker.setClassValueIndex(m_ClassValueCombo.getSelectedIndex());
+	  }
+	}
+      });
+
+    add(m_messageLabel, BorderLayout.CENTER);
+  }
+
+  private void setUpNoCustPossible() {
+    if (m_displayValNames == true) {
+      remove(m_holderP);
+      add(m_messageLabel, BorderLayout.CENTER);
+      m_displayValNames = false;
+    }
+    validate(); repaint();
+  }
+
+  private void setUpValueSelection(Instances format) {
+    if (format.classIndex() < 0 || format.classAttribute().isNumeric()) {
+      // cant do anything in this case
+      return;
+    }
+    if (m_displayValNames == false) {
+      remove(m_messageLabel);
+    }
+    
+    int existingClassVal = m_classValuePicker.getClassValueIndex();
+    String [] attribValNames = new String [format.classAttribute().numValues()];
+    for (int i = 0; i < attribValNames.length; i++) {
+      attribValNames[i] = format.classAttribute().value(i);
+    }
+    m_ClassValueCombo.setModel(new DefaultComboBoxModel(attribValNames));
+    if (attribValNames.length > 0) {
+      if (existingClassVal < attribValNames.length) {
+	m_ClassValueCombo.setSelectedIndex(existingClassVal);
+      }
+    }
+    if (m_displayValNames == false) {
+      add(m_holderP, BorderLayout.CENTER);
+      m_displayValNames = true;
+    }
+    validate(); repaint();
+  }
+
+  /**
+   * Set the bean to be edited
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    if (m_classValuePicker != (ClassValuePicker)object) {
+      // remove ourselves as a listener from the old ClassvaluePicker (if necessary)
+      if (m_classValuePicker != null) {
+	m_classValuePicker.removeDataFormatListener(this);
+      }
+      m_classValuePicker = (ClassValuePicker)object;
+      // add ourselves as a data format listener
+      m_classValuePicker.addDataFormatListener(this);
+      if (m_classValuePicker.getConnectedFormat() != null) {
+	setUpValueSelection(m_classValuePicker.getConnectedFormat());	
+      }
+    }
+  }
+  
+  public void customizerClosing() {
+    // remove ourselves as a listener from the ClassValuePicker (if necessary)
+    if (m_classValuePicker != null) {
+      System.err.println("Customizer deregistering with class value picker");
+      m_classValuePicker.removeDataFormatListener(this);
+    }
+  }
+
+  public void newDataFormat(DataSetEvent dse) {
+    if (dse.getDataSet() != null) {
+      setUpValueSelection(m_classValuePicker.getConnectedFormat());
+    } else {
+      setUpNoCustPossible();
+    }
+  }
+  
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Classifier.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Classifier.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Classifier.java	(revision 29)
@@ -0,0 +1,2018 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Classifier.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.filechooser.FileFilter;
+
+import weka.classifiers.rules.ZeroR;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.xml.KOML;
+import weka.core.xml.XStream;
+import weka.experiment.Task;
+import weka.experiment.TaskStatusInfo;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.Logger;
+
+/**
+ * Bean that wraps around weka.classifiers
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 6197 $
+ * @since 1.0
+ * @see JPanel
+ * @see BeanCommon
+ * @see Visible
+ * @see WekaWrapper
+ * @see Serializable
+ * @see UserRequestAcceptor
+ * @see TrainingSetListener
+ * @see TestSetListener
+ */
+public class Classifier
+  extends JPanel
+  implements BeanCommon, Visible, 
+	     WekaWrapper, EventConstraints,
+	     Serializable, UserRequestAcceptor,
+	     TrainingSetListener, TestSetListener,
+	     InstanceListener, ConfigurationProducer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 659603893917736008L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("Classifier",
+		   BeanVisual.ICON_PATH+"DefaultClassifier.gif",
+		   BeanVisual.ICON_PATH+"DefaultClassifier_animated.gif");
+
+  private static int IDLE = 0;
+  private static int BUILDING_MODEL = 1;
+  private static int CLASSIFYING = 2;
+
+  private int m_state = IDLE;
+
+  //private Thread m_buildThread = null;
+
+  /**
+   * Global info for the wrapped classifier (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Objects talking to us
+   */
+  private Hashtable m_listenees = new Hashtable();
+
+  /**
+   * Objects listening for batch classifier events
+   */
+  private Vector m_batchClassifierListeners = new Vector();
+
+  /**
+   * Objects listening for incremental classifier events
+   */
+  private Vector m_incrementalClassifierListeners = new Vector();
+
+  /**
+   * Objects listening for graph events
+   */
+  private Vector m_graphListeners = new Vector();
+
+  /**
+   * Objects listening for text events
+   */
+  private Vector m_textListeners = new Vector();
+
+  /**
+   * Holds training instances for batch training. Not transient because
+   * header is retained for validating any instance events that this
+   * classifier might be asked to predict in the future.
+   */
+  private Instances m_trainingSet;
+  private transient Instances m_testingSet;
+  private weka.classifiers.Classifier m_Classifier = new ZeroR();
+  /** Template used for creating copies when building in parallel */
+  private weka.classifiers.Classifier m_ClassifierTemplate = m_Classifier;
+  
+  private IncrementalClassifierEvent m_ie = 
+    new IncrementalClassifierEvent(this);
+
+  /** the extension for serialized models (binary Java serialization) */
+  public final static String FILE_EXTENSION = "model";
+
+  private transient JFileChooser m_fileChooser = null; 
+
+  protected FileFilter m_binaryFilter =
+    new ExtensionFileFilter("."+FILE_EXTENSION, "Binary serialized model file (*"
+                            + FILE_EXTENSION + ")");
+
+  protected FileFilter m_KOMLFilter =
+    new ExtensionFileFilter(KOML.FILE_EXTENSION + FILE_EXTENSION,
+                            "XML serialized model file (*"
+                            + KOML.FILE_EXTENSION + FILE_EXTENSION + ")");
+
+  protected FileFilter m_XStreamFilter =
+    new ExtensionFileFilter(XStream.FILE_EXTENSION + FILE_EXTENSION,
+                            "XML serialized model file (*"
+                            + XStream.FILE_EXTENSION + FILE_EXTENSION + ")");
+
+  /**
+   * If the classifier is an incremental classifier, should we
+   * update it (ie train it on incoming instances). This makes it
+   * possible incrementally test on a separate stream of instances
+   * without updating the classifier, or mix batch training/testing
+   * with incremental training/testing
+   */
+  private boolean m_updateIncrementalClassifier = true;
+
+  private transient Logger m_log = null;
+
+  /**
+   * Event to handle when processing incremental updates
+   */
+  private InstanceEvent m_incrementalEvent;
+  
+  /**
+   * Number of threads to use to train models with
+   */
+  protected int m_executionSlots = 2;
+  
+//  protected int m_queueSize = 5;
+  
+  /**
+   * Pool of threads to train models on incoming data 
+   */
+  protected transient ThreadPoolExecutor m_executorPool;  
+  
+  /**
+   * Stores completed models and associated data sets.
+   */
+  protected transient BatchClassifierEvent[][] m_outputQueues;
+
+  /**
+   * Stores which sets from which runs have been completed.
+   */
+  protected transient boolean[][] m_completedSets;
+  
+  /**
+   * Identifier for the current batch. A batch is a group
+   * of related runs/sets.
+   */
+  protected transient Date m_currentBatchIdentifier;
+  
+  /**
+   * Holds original icon label text
+   */
+  protected String m_oldText = "";
+  
+  /**
+   * true if we should reject any further training 
+   * data sets, until all processing has been finished,
+   *  once we've received the last fold of
+   * the last run.
+   */
+  protected boolean m_reject = false;
+  
+  /** 
+   * True if we should block rather reject until
+   * all processing has been completed.
+   */
+  protected boolean m_block = false;
+
+  /**
+   * Global info (if it exists) for the wrapped classifier
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  /**
+   * Creates a new <code>Classifier</code> instance.
+   */
+  public Classifier() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    setClassifierTemplate(m_ClassifierTemplate);
+    
+    //setupFileChooser();
+  }
+  
+  private void startExecutorPool() {
+    
+    if (m_executorPool != null) {
+      m_executorPool.shutdownNow();
+    }
+    
+    m_executorPool = new ThreadPoolExecutor(m_executionSlots, m_executionSlots,
+        120, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  protected void setupFileChooser() {
+    if (m_fileChooser == null) {
+      m_fileChooser = 
+        new JFileChooser(new File(System.getProperty("user.dir")));
+    }
+
+    m_fileChooser.addChoosableFileFilter(m_binaryFilter);
+    if (KOML.isPresent()) {
+      m_fileChooser.addChoosableFileFilter(m_KOMLFilter);
+    }
+    if (XStream.isPresent()) {
+      m_fileChooser.addChoosableFileFilter(m_XStreamFilter);
+    }
+    m_fileChooser.setFileFilter(m_binaryFilter);
+  }
+  
+  /**
+   * Get the number of execution slots (threads) used
+   * to train models.
+   * 
+   * @return the number of execution slots.
+   */
+  public int getExecutionSlots() {
+    return m_executionSlots;
+  }
+  
+  /**
+   * Set the number of execution slots (threads) to use to
+   * train models with.
+   * 
+   * @param slots the number of execution slots to use.
+   */
+  public void setExecutionSlots(int slots) {
+    m_executionSlots = slots;
+  }
+  
+  /**
+   * Set whether to block on receiving the last fold
+   * of the last run rather than rejecting any further
+   * data until all processing is complete.
+   * 
+   * @param block true if we should block on the
+   * last fold of the last run.
+   */
+  public void setBlockOnLastFold(boolean block) {
+    m_block = block;
+  }
+  
+  /**
+   * Gets whether we are blocking on the last fold of the
+   * last run rather than rejecting any further data until
+   * all processing has been completed.
+   * 
+   * @return true if we are blocking on the last fold
+   * of the last run
+   */
+  public boolean getBlockOnLastFold() {
+    return m_block;
+  }
+
+  /**
+   * Set the template classifier for this wrapper
+   *
+   * @param c a <code>weka.classifiers.Classifier</code> value
+   */
+  public void setClassifierTemplate(weka.classifiers.Classifier c) {
+    boolean loadImages = true;
+    if (c.getClass().getName().
+	compareTo(m_ClassifierTemplate.getClass().getName()) == 0) {
+      loadImages = false;
+    } else {
+      // classifier has changed so any batch training status is now
+      // invalid
+      m_trainingSet = null;
+    }
+    m_ClassifierTemplate = c;
+    String classifierName = c.getClass().toString();
+    classifierName = classifierName.substring(classifierName.
+					      lastIndexOf('.')+1, 
+					      classifierName.length());
+    if (loadImages) {
+      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+classifierName+".gif",
+		       BeanVisual.ICON_PATH+classifierName+"_animated.gif")) {
+	useDefaultVisual();
+      }
+    }
+    m_visual.setText(classifierName);
+
+    if (!(m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier) &&
+	(m_listenees.containsKey("instance"))) {
+      if (m_log != null) {
+	m_log.logMessage("[Classifier] " + statusMessagePrefix() + " WARNING : "
+	    + getCustomName() +" is not an incremental classifier");
+      }
+    }
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_ClassifierTemplate);
+  }
+  
+  /**
+   * Return the classifier template currently in use.
+   * 
+   * @return the classifier template currently in use.
+   */
+  public weka.classifiers.Classifier getClassifierTemplate() {
+    return m_ClassifierTemplate;
+  }
+  
+  private void setTrainedClassifier(weka.classifiers.Classifier tc) {
+    m_Classifier = tc;
+    
+    // set the template
+    weka.classifiers.Classifier newTemplate = null;
+    try {
+      String[] options = ((OptionHandler)tc).getOptions();
+      newTemplate = weka.classifiers.AbstractClassifier.forName(tc.getClass().getName(), options);
+      setClassifierTemplate(newTemplate);
+    } catch (Exception ex) {
+      if (m_log != null) {
+        m_log.logMessage("[Classifier] " + statusMessagePrefix() + ex.getMessage());
+        String errorMessage = statusMessagePrefix()
+        + "ERROR: see log for details.";
+        m_log.statusMessage(errorMessage);        
+      } else {
+        ex.printStackTrace();
+      }
+    }    
+  }
+
+  /**
+   * Returns true if this classifier has an incoming connection that is
+   * an instance stream
+   *
+   * @return true if has an incoming connection that is an instance stream
+   */
+  public boolean hasIncomingStreamInstances() {
+    if (m_listenees.size() == 0) {
+      return false;
+    }
+    if (m_listenees.containsKey("instance")) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if this classifier has an incoming connection that is
+   * a batch set of instances
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean hasIncomingBatchInstances() {
+    if (m_listenees.size() == 0) {
+      return false;
+    }
+    if (m_listenees.containsKey("trainingSet") ||
+	m_listenees.containsKey("testSet")) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Get the currently trained classifier.
+   *
+   * @return a <code>weka.classifiers.Classifier</code> value
+   */
+  public weka.classifiers.Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Sets the algorithm (classifier) for this bean
+   *
+   * @param algorithm an <code>Object</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void setWrappedAlgorithm(Object algorithm) 
+    {
+
+    if (!(algorithm instanceof weka.classifiers.Classifier)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Classifier)");
+    }
+    setClassifierTemplate((weka.classifiers.Classifier)algorithm);
+  }
+
+  /**
+   * Returns the wrapped classifier
+   *
+   * @return an <code>Object</code> value
+   */
+  public Object getWrappedAlgorithm() {
+    return getClassifierTemplate();
+  }
+
+  /**
+   * Get whether an incremental classifier will be updated on the
+   * incoming instance stream.
+   * 
+   * @return true if an incremental classifier is to be updated.
+   */
+  public boolean getUpdateIncrementalClassifier() {
+    return m_updateIncrementalClassifier;
+  }
+
+  /**
+   * Set whether an incremental classifier will be updated on the
+   * incoming instance stream.
+   * 
+   * @param update true if an incremental classifier is to be updated.
+   */
+  public void setUpdateIncrementalClassifier(boolean update) {
+    m_updateIncrementalClassifier = update;
+  }
+
+  /**
+   * Accepts an instance for incremental processing.
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  public void acceptInstance(InstanceEvent e) {
+    m_incrementalEvent = e;
+    handleIncrementalEvent();
+  }
+
+  /**
+   * Handles initializing and updating an incremental classifier
+   */
+  private void handleIncrementalEvent() {
+    if (m_executorPool != null && 
+        (m_executorPool.getQueue().size() > 0 || 
+            m_executorPool.getActiveCount() > 0)) {
+      
+      String messg = "[Classifier] " + statusMessagePrefix() 
+        + " is currently batch training!";
+      if (m_log != null) {
+	m_log.logMessage(messg);
+	m_log.statusMessage(statusMessagePrefix() + "WARNING: "
+	    + "Can't accept instance - batch training in progress.");
+      } else {
+	System.err.println(messg);
+      }
+      return;
+    }
+
+    if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+      // clear any warnings/errors from the log
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "remove");
+      }
+      
+      //      Instances dataset = m_incrementalEvent.getInstance().dataset();
+      Instances dataset = m_incrementalEvent.getStructure();
+      // default to the last column if no class is set
+      if (dataset.classIndex() < 0) {
+        stop();
+        String errorMessage = statusMessagePrefix()
+            + "ERROR: no class attribute set in incoming stream!";
+        if (m_log != null) {
+          m_log.statusMessage(errorMessage);
+          m_log.logMessage("[" + getCustomName() + "] " + errorMessage);
+        } else {
+          System.err.println("[" + getCustomName() + "] " + errorMessage);
+        }
+        return;
+        
+	// System.err.println("Classifier : setting class index...");
+	//dataset.setClassIndex(dataset.numAttributes()-1);
+      }
+      try {
+	// initialize classifier if m_trainingSet is null
+	// otherwise assume that classifier has been pre-trained in batch
+	// mode, *if* headers match
+	if (m_trainingSet == null || !m_trainingSet.equalHeaders(dataset)) {
+	  if (!(m_ClassifierTemplate instanceof 
+		weka.classifiers.UpdateableClassifier)) {
+	    stop(); // stop all processing
+	    if (m_log != null) {
+	      String msg = (m_trainingSet == null)
+		? statusMessagePrefix()
+		+ "ERROR: classifier has not been batch "
+		+"trained; can't process instance events."
+		: statusMessagePrefix() 
+		  + "ERROR: instance event's structure is different from "
+		  +"the data that "
+		  + "was used to batch train this classifier; can't continue.";
+	      m_log.logMessage("[Classifier] " + msg);
+	      m_log.statusMessage(msg);
+	    }
+	    return;
+	  }
+	  
+	  if (m_trainingSet != null && 
+	      (!dataset.equalHeaders(m_trainingSet))) {
+	    if (m_log != null) {
+	      String msg = statusMessagePrefix() 
+              + " WARNING : structure of instance events differ "
+              +"from data used in batch training this "
+              +"classifier. Resetting classifier...";
+	      m_log.logMessage("[Classifier] " + msg);
+	      m_log.statusMessage(msg);
+	    }
+	    m_trainingSet = null;
+	  }
+	  if (m_trainingSet == null) {
+	    // initialize the classifier if it hasn't been trained yet
+	    m_trainingSet = new Instances(dataset, 0);
+	    m_Classifier = weka.classifiers.AbstractClassifier.makeCopy(m_ClassifierTemplate);
+	    m_Classifier.buildClassifier(m_trainingSet);
+	  }
+	}
+      } catch (Exception ex) {
+        stop();
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details)");
+          m_log.logMessage("[Classifier] " + statusMessagePrefix()
+              + " problem during incremental processing. " 
+              + ex.getMessage());
+        }
+	ex.printStackTrace();
+      }
+      // Notify incremental classifier listeners of new batch
+      System.err.println("NOTIFYING NEW BATCH");
+      m_ie.setStructure(dataset); 
+      m_ie.setClassifier(m_Classifier);
+
+      notifyIncrementalClassifierListeners(m_ie);
+      return;
+    } else {
+      if (m_trainingSet == null) {
+	// simply return. If the training set is still null after
+	// the first instance then the classifier must not be updateable
+	// and hasn't been previously batch trained - therefore we can't
+	// do anything meaningful
+	return;
+      }
+    }
+
+    try {
+      // test on this instance
+      if (m_incrementalEvent.getInstance().dataset().classIndex() < 0) {
+        // System.err.println("Classifier : setting class index...");
+        m_incrementalEvent.getInstance().dataset().setClassIndex(
+            m_incrementalEvent.getInstance().dataset().numAttributes()-1);
+      }
+      
+      int status = IncrementalClassifierEvent.WITHIN_BATCH;
+      /*      if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+	      status = IncrementalClassifierEvent.NEW_BATCH; */
+      /* } else */ if (m_incrementalEvent.getStatus() ==
+		       InstanceEvent.BATCH_FINISHED) {
+	status = IncrementalClassifierEvent.BATCH_FINISHED;
+      }
+
+      m_ie.setStatus(status); m_ie.setClassifier(m_Classifier);
+      m_ie.setCurrentInstance(m_incrementalEvent.getInstance());
+
+      notifyIncrementalClassifierListeners(m_ie);
+
+      // now update on this instance (if class is not missing and classifier
+      // is updateable and user has specified that classifier is to be
+      // updated)
+      if (m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier &&
+	  m_updateIncrementalClassifier == true &&
+	  !(m_incrementalEvent.getInstance().
+	    isMissing(m_incrementalEvent.getInstance().
+		      dataset().classIndex()))) {
+	((weka.classifiers.UpdateableClassifier)m_Classifier).
+	  updateClassifier(m_incrementalEvent.getInstance());
+      }
+      if (m_incrementalEvent.getStatus() == 
+	  InstanceEvent.BATCH_FINISHED) {
+	if (m_textListeners.size() > 0) {
+	  String modelString = m_Classifier.toString();
+	  String titleString = m_Classifier.getClass().getName();
+
+	  titleString = titleString.
+	    substring(titleString.lastIndexOf('.') + 1,
+		      titleString.length());
+	  modelString = "=== Classifier model ===\n\n" +
+	    "Scheme:   " +titleString+"\n" +
+	    "Relation: "  + m_trainingSet.relationName() + "\n\n"
+	    + modelString;
+	  titleString = "Model: " + titleString;
+	  TextEvent nt = new TextEvent(this,
+				       modelString,
+				       titleString);
+	  notifyTextListeners(nt);
+	}
+      }
+    } catch (Exception ex) {
+      stop();
+      if (m_log != null) {
+	m_log.logMessage("[Classifier] " + statusMessagePrefix()
+	    + ex.getMessage());
+	m_log.statusMessage(statusMessagePrefix()
+	    + "ERROR (see log for details)");
+	ex.printStackTrace();
+      } else {
+        ex.printStackTrace();
+      }
+    }
+  }
+  
+  protected class TrainingTask implements Runnable, Task {
+    private int m_runNum;
+    private int m_maxRunNum;
+    private int m_setNum;
+    private int m_maxSetNum;
+    private Instances m_train = null;
+    private TaskStatusInfo m_taskInfo = new TaskStatusInfo();
+
+    public TrainingTask(int runNum, int maxRunNum, 
+        int setNum, int maxSetNum, Instances train) {
+      m_runNum = runNum;
+      m_maxRunNum = maxRunNum;
+      m_setNum = setNum;
+      m_maxSetNum = maxSetNum;
+      m_train = train;
+      m_taskInfo.setExecutionStatus(TaskStatusInfo.TO_BE_RUN);
+    }
+
+    public void run() {
+      execute();
+    }
+
+    public void execute() {
+      try {
+        if (m_train != null) {
+          if (m_train.classIndex() < 0) {
+            // stop all processing
+            stop();
+            String errorMessage = statusMessagePrefix()
+                + "ERROR: no class attribute set in test data!";
+            if (m_log != null) {
+              m_log.statusMessage(errorMessage);
+              m_log.logMessage("[Classifier] " + errorMessage);
+            } else {
+              System.err.println("[Classifier] " + errorMessage);
+            }
+            return;
+            
+            // assume last column is the class
+/*            m_train.setClassIndex(m_train.numAttributes()-1);
+            if (m_log != null) {
+              m_log.logMessage("[Classifier] " + statusMessagePrefix() 
+                  + " : assuming last "
+                  +"column is the class");
+            } */
+          }
+          if (m_runNum == 1 && m_setNum == 1) {
+            // set this back to idle once the last fold
+            // of the last run has completed
+            m_state = BUILDING_MODEL; // global state
+            
+            // local status of this runnable
+            m_taskInfo.setExecutionStatus(TaskStatusInfo.PROCESSING);
+          }
+          
+          //m_visual.setAnimated();
+          //m_visual.setText("Building model...");
+          String msg = statusMessagePrefix()
+            + "Building model for run " + m_runNum + " fold " + m_setNum;
+          if (m_log != null) {
+            m_log.statusMessage(msg);
+          } else {
+            System.err.println(msg);
+          }
+          // buildClassifier();
+          
+          // copy the classifier configuration
+          weka.classifiers.Classifier classifierCopy = 
+            weka.classifiers.AbstractClassifier.makeCopy(m_ClassifierTemplate);
+          
+          // build this model
+          classifierCopy.buildClassifier(m_train);
+          if (m_runNum == m_maxRunNum && m_setNum == m_maxSetNum) {
+            // Save the last classifier (might be used later on for
+            // classifying further test sets.
+            m_Classifier = classifierCopy;
+            m_trainingSet = m_train;
+          }
+                              
+          //if (m_batchClassifierListeners.size() > 0) {
+            // notify anyone who might be interested in just the model
+            // and training set.
+            BatchClassifierEvent ce = 
+              new BatchClassifierEvent(Classifier.this, classifierCopy, 
+                  new DataSetEvent(this, m_train),
+                  null, // no test set (yet)
+                  m_setNum, m_maxSetNum);
+            ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
+            notifyBatchClassifierListeners(ce);
+                        
+            // store in the output queue (if we have incoming test set events)
+            ce = 
+              new BatchClassifierEvent(Classifier.this, classifierCopy, 
+                  new DataSetEvent(this, m_train),
+                  null, // no test set (yet)
+                  m_setNum, m_maxSetNum);
+            ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
+            classifierTrainingComplete(ce);
+          //}
+
+          if (classifierCopy instanceof weka.core.Drawable && 
+              m_graphListeners.size() > 0) {
+            String grphString = 
+              ((weka.core.Drawable)classifierCopy).graph();
+            int grphType = ((weka.core.Drawable)classifierCopy).graphType();
+            String grphTitle = classifierCopy.getClass().getName();
+            grphTitle = grphTitle.substring(grphTitle.
+                lastIndexOf('.')+1, 
+                grphTitle.length());
+            grphTitle = "Set " + m_setNum + " ("
+            + m_train.relationName() + ") "
+            + grphTitle;
+
+            GraphEvent ge = new GraphEvent(Classifier.this, 
+                grphString, 
+                grphTitle,
+                grphType);
+            notifyGraphListeners(ge);
+          }
+
+          if (m_textListeners.size() > 0) {
+            String modelString = classifierCopy.toString();
+            String titleString = classifierCopy.getClass().getName();
+
+            titleString = titleString.
+            substring(titleString.lastIndexOf('.') + 1,
+                titleString.length());
+            modelString = "=== Classifier model ===\n\n" +
+            "Scheme:   " +titleString+"\n" +
+            "Relation: "  + m_train.relationName() + 
+            ((m_maxSetNum > 1) 
+                ? "\nTraining Fold: " + m_setNum
+                    :"")
+                    + "\n\n"
+                    + modelString;
+            titleString = "Model: " + titleString;
+
+            TextEvent nt = new TextEvent(Classifier.this,
+                modelString,
+                titleString);
+            notifyTextListeners(nt);
+          }
+        }
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        if (m_log != null) {
+          String titleString = "[Classifier] " + statusMessagePrefix();
+
+          titleString += " run " + m_runNum + " fold " + m_setNum
+          + " failed to complete.";
+          m_log.logMessage(titleString + " (build classifier). " 
+              + ex.getMessage());
+          m_log.statusMessage(statusMessagePrefix() 
+              + "ERROR (see log for details)");
+          ex.printStackTrace();
+        }
+        m_taskInfo.setExecutionStatus(TaskStatusInfo.FAILED);
+        // Stop all processing
+        stop();
+      } finally {
+        m_visual.setStatic();
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Finished.");
+        }
+        m_state = IDLE;
+        if (Thread.currentThread().isInterrupted()) {
+          // prevent any classifier events from being fired
+          m_trainingSet = null;
+          if (m_log != null) {
+            String titleString = "[Classifier] " + statusMessagePrefix();                 
+         
+            m_log.logMessage(titleString + " ("
+               + " run " + m_runNum + " fold " + m_setNum + ") interrupted!");
+            m_log.statusMessage(statusMessagePrefix() + "INTERRUPTED");
+            
+            /* // are we the last active thread?
+            if (m_executorPool.getActiveCount() == 1) {
+              String msg = "[Classifier] " + statusMessagePrefix() 
+              + " last classifier unblocking...";
+              System.err.println(msg + " (interrupted)");
+              m_log.logMessage(msg + " (interrupted)");
+//              m_log.statusMessage(statusMessagePrefix() + "finished.");
+              m_block = false;
+              m_state = IDLE;
+              block(false);
+            } */
+          }
+          /*System.err.println("Queue size: " + m_executorPool.getQueue().size() +
+              " Active count: " + m_executorPool.getActiveCount()); */
+        } /* else {
+          // check to see if we are the last active thread
+          if (m_executorPool == null || 
+              (m_executorPool.getQueue().size() == 0 && 
+                  m_executorPool.getActiveCount() == 1)) {
+
+            String msg = "[Classifier] " + statusMessagePrefix() 
+            + " last classifier unblocking...";
+            System.err.println(msg);
+            if (m_log != null) {
+              m_log.logMessage(msg);
+            } else {
+              System.err.println(msg);
+            }
+            //m_visual.setText(m_oldText);
+
+            if (m_log != null) {
+              m_log.statusMessage(statusMessagePrefix() + "Finished.");
+            }
+            // m_outputQueues = null; // free memory
+            m_block = false;
+            block(false);
+          }
+        } */
+      }
+    }
+  
+    public TaskStatusInfo getTaskStatus() {
+      // TODO
+      return null;
+    }     
+  }     
+  
+  /**
+   * Accepts a training set and builds batch classifier
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(final TrainingSetEvent e) {
+    
+    if (e.isStructureOnly()) {
+      // no need to build a classifier, instead just generate a dummy
+      // BatchClassifierEvent in order to pass on instance structure to
+      // any listeners - eg. PredictionAppender can use it to determine
+      // the final structure of instances with predictions appended
+      BatchClassifierEvent ce = 
+        new BatchClassifierEvent(this, m_Classifier, 
+                                 new DataSetEvent(this, e.getTrainingSet()),
+                                 new DataSetEvent(this, e.getTrainingSet()),
+                                 e.getSetNumber(), e.getMaxSetNumber());
+
+      notifyBatchClassifierListeners(ce);
+      return;
+    }
+    
+    if (m_reject) {
+      //block(true);
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "BUSY. Can't accept data "
+            + "at this time.");
+        m_log.logMessage("[Classifier] " + statusMessagePrefix()
+            + " BUSY. Can't accept data at this time.");
+      }
+      return;
+    }
+    
+    // Do some initialization if this is the first set of the first run
+    if (e.getRunNumber() == 1 && e.getSetNumber() == 1) {
+//      m_oldText = m_visual.getText();
+      // store the training header
+      m_trainingSet = new Instances(e.getTrainingSet(), 0);
+      m_state = BUILDING_MODEL;
+      
+      String msg = "[Classifier] " + statusMessagePrefix() 
+        + " starting executor pool ("
+        + getExecutionSlots() + " slots)...";
+      if (m_log != null) {
+        m_log.logMessage(msg);
+      } else {
+        System.err.println(msg);
+      }
+      // start the execution pool
+      if (m_executorPool == null) {
+        startExecutorPool();
+      }
+            
+      // setup output queues
+      msg = "[Classifier] " + statusMessagePrefix() + " setup output queues.";
+      if (m_log != null) {
+        m_log.logMessage(msg);
+      } else {
+        System.err.println(msg);
+      }
+      
+      m_outputQueues = 
+        new BatchClassifierEvent[e.getMaxRunNumber()][e.getMaxSetNumber()];
+      m_completedSets = new boolean[e.getMaxRunNumber()][e.getMaxSetNumber()];
+      m_currentBatchIdentifier = new Date();
+    }
+    
+    // create a new task and schedule for execution
+    TrainingTask newTask = new TrainingTask(e.getRunNumber(), e.getMaxRunNumber(),
+        e.getSetNumber(), e.getMaxSetNumber(), e.getTrainingSet());
+    String msg = "[Classifier] " + statusMessagePrefix() + " scheduling run " 
+    + e.getRunNumber() +" fold " + e.getSetNumber() + " for execution...";
+    if (m_log != null) {
+      m_log.logMessage(msg);
+    } else {
+      System.err.println(msg);
+    }
+    
+    // delay just a little bit
+    /*try {
+      Thread.sleep(10);
+    } catch (Exception ex){} */
+    m_executorPool.execute(newTask);
+  }
+
+  /**
+   * Accepts a test set for a batch trained classifier
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */    
+  public synchronized void acceptTestSet(TestSetEvent e) {
+    if (m_reject) {
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "BUSY. Can't accept data "
+            + "at this time.");
+        m_log.logMessage("[Classifier] " + statusMessagePrefix()
+            + " BUSY. Can't accept data at this time.");
+      }
+      return;
+    }
+    
+    Instances testSet = e.getTestSet();
+    if (testSet != null) {
+      if (testSet.classIndex() < 0) {
+  //        testSet.setClassIndex(testSet.numAttributes() - 1);
+        // stop all processing
+        stop();
+        String errorMessage = statusMessagePrefix()
+            + "ERROR: no class attribute set in test data!";
+        if (m_log != null) {
+          m_log.statusMessage(errorMessage);
+          m_log.logMessage("[Classifier] " + errorMessage);
+        } else {
+          System.err.println("[Classifier] " + errorMessage);
+        }
+        return;
+      }
+    }
+    
+    // If we just have a test set connection or
+    // there is just one run involving one set (and we are not
+    // currently building a model), then use the
+    // last saved model
+    if (m_Classifier != null && m_state == IDLE && 
+        (!m_listenees.containsKey("trainingSet") || 
+        (e.getMaxRunNumber() == 1 && e.getMaxSetNumber() == 1))) {
+      // if this is structure only then just return at this point
+      if (e.getTestSet() != null && e.isStructureOnly()) {
+        return;
+      }
+      
+      // check that we have a training set/header (if we don't,
+      // then it means that no model has been loaded
+      if (m_trainingSet == null) {
+        stop();
+        String errorMessage = statusMessagePrefix()
+            + "ERROR: no trained/loaded classifier to use for prediction!";
+        if (m_log != null) {
+          m_log.statusMessage(errorMessage);
+          m_log.logMessage("[Classifier] " + errorMessage);
+        } else {
+          System.err.println("[Classifier] " + errorMessage);
+        }
+        return;
+      }
+      
+      testSet = e.getTestSet();
+      if (e.getRunNumber() == 1 && e.getSetNumber() == 1) {
+        m_currentBatchIdentifier = new Date();
+      }
+      
+      if (testSet != null) {        
+        if (m_trainingSet.equalHeaders(testSet)) {
+          BatchClassifierEvent ce =
+            new BatchClassifierEvent(this, m_Classifier,                                       
+                new DataSetEvent(this, m_trainingSet),
+                new DataSetEvent(this, e.getTestSet()),
+           e.getRunNumber(), e.getMaxRunNumber(), 
+           e.getSetNumber(), e.getMaxSetNumber());
+          ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
+          
+          if (m_log != null && !e.isStructureOnly()) {
+            m_log.statusMessage(statusMessagePrefix() + "Finished.");
+          }
+          notifyBatchClassifierListeners(ce);          
+        } else {
+          // if headers do not match check to see if it's
+          // just the class that is different and that
+          // all class values are missing
+          if (testSet.numInstances() > 0) {
+            if (testSet.classIndex() == m_trainingSet.classIndex() && 
+                testSet.attributeStats(testSet.classIndex()).missingCount ==
+                testSet.numInstances()) {
+              // now check the other attributes against the training
+              // structure
+              boolean ok = true;
+              for (int i = 0; i < testSet.numAttributes(); i++) {
+                if (i != testSet.classIndex()) {
+                  ok = testSet.attribute(i).equals(m_trainingSet.attribute(i));
+                  if (!ok) {
+                    break;
+                  }
+                }
+              }
+              
+              if (ok) {
+                BatchClassifierEvent ce =
+                  new BatchClassifierEvent(this, m_Classifier,                                       
+                      new DataSetEvent(this, m_trainingSet),
+                      new DataSetEvent(this, e.getTestSet()),
+                 e.getRunNumber(), e.getMaxRunNumber(), 
+                 e.getSetNumber(), e.getMaxSetNumber());
+                ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
+                
+                if (m_log != null && !e.isStructureOnly()) {
+                  m_log.statusMessage(statusMessagePrefix() + "Finished.");
+                }
+                notifyBatchClassifierListeners(ce);
+              } else {
+                stop();
+                String errorMessage = statusMessagePrefix()
+                + "ERROR: structure of training and test sets is not compatible!";
+                if (m_log != null) {
+                  m_log.statusMessage(errorMessage);
+                  m_log.logMessage("[Classifier] " + errorMessage);
+                } else {
+                  System.err.println("[Classifier] " + errorMessage);
+                }
+              }
+            }
+          }
+        }
+      }
+    } else {
+/*      System.err.println("[Classifier] accepting test set: run " 
+          + e.getRunNumber() + " fold " + e.getSetNumber()); */
+      
+      if (m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1] == null) {
+        // store an event with a null model and training set (to be filled in later)
+        m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1] =
+          new BatchClassifierEvent(this, null, null, 
+              new DataSetEvent(this, e.getTestSet()),
+              e.getRunNumber(), e.getMaxRunNumber(),
+              e.getSetNumber(), e.getMaxSetNumber());
+        if (e.getRunNumber() == e.getMaxRunNumber() && 
+            e.getSetNumber() == e.getMaxSetNumber()) {
+          
+          // block on the last fold of the last run
+          /* System.err.println("[Classifier] blocking on last fold of last run...");
+          block(true); */
+          m_reject = true;
+          if (m_block) {
+            block(true);
+          }
+        }
+      } else {
+        // Otherwise, there is a model here waiting for a test set...
+        m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1].
+          setTestSet(new DataSetEvent(this, e.getTestSet()));
+        checkCompletedRun(e.getRunNumber(), e.getMaxRunNumber(), e.getMaxSetNumber());
+      }
+    }
+  }
+  
+  private synchronized void classifierTrainingComplete(BatchClassifierEvent ce) {
+    // check the output queues if we have an incoming test set connection
+    if (m_listenees.containsKey("testSet")) {
+      String msg = "[Classifier] " + statusMessagePrefix() 
+      + " storing model for run " + ce.getRunNumber() 
+      + " fold " + ce.getSetNumber();
+      if (m_log != null) {
+        m_log.logMessage(msg);
+      } else {
+        System.err.println(msg);
+      }
+      
+      if (m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1] == null) {
+        // store the event - test data filled in later
+        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1] = ce;
+      } else {
+        // there is a test set here waiting for a model and training set
+        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1].
+          setClassifier(ce.getClassifier());
+        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1].
+        setTrainSet(ce.getTrainSet());
+        
+      }
+      checkCompletedRun(ce.getRunNumber(), ce.getMaxRunNumber(), ce.getMaxSetNumber());
+    }
+  }
+  
+  private synchronized void checkCompletedRun(int runNum, int maxRunNum, int maxSets) {
+    // look to see if there are any completed classifiers that we can pass
+    // on for evaluation
+    for (int i = 0; i < maxSets; i++) {
+      if (m_outputQueues[runNum - 1][i] != null) {
+        if (m_outputQueues[runNum - 1][i].getClassifier() != null &&
+            m_outputQueues[runNum - 1][i].getTestSet() != null) {
+          String msg = "[Classifier] " + statusMessagePrefix() 
+          + " dispatching run/set " + runNum + "/" + (i+1) + " to listeners.";
+          if (m_log != null) {
+            m_log.logMessage(msg);
+          } else {
+            System.err.println(msg);
+          }
+          
+          // dispatch this one
+          m_outputQueues[runNum - 1][i].setGroupIdentifier(m_currentBatchIdentifier.getTime());
+          notifyBatchClassifierListeners(m_outputQueues[runNum - 1][i]);
+          // save memory
+          m_outputQueues[runNum - 1][i] = null;
+          // mark as done
+          m_completedSets[runNum - 1][i] = true;
+        }
+      }
+    }
+    
+    // scan for completion
+    boolean done = true;
+    for (int i = 0; i < maxRunNum; i++) {
+      for (int j = 0; j < maxSets; j++) {
+        if (!m_completedSets[i][j]) {
+          done = false;
+          break;
+        }
+      }
+      if (!done) {
+        break;
+      }
+    }
+    
+    if (done) {
+      String msg = "[Classifier] " + statusMessagePrefix() 
+      + " last classifier unblocking...";
+
+      if (m_log != null) {
+        m_log.logMessage(msg);
+      } else {
+        System.err.println(msg);
+      }
+      //m_visual.setText(m_oldText);
+
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "Finished.");
+      }
+      // m_outputQueues = null; // free memory
+      m_reject = false;
+      block(false);
+      m_state = IDLE;
+    }
+  }
+
+  /*private synchronized void checkCompletedRun(int runNum, int maxRunNum, int  maxSets) {
+    boolean runOK = true;
+    for (int i = 0; i < maxSets; i++) {
+      if (m_outputQueues[runNum - 1][i] == null) {
+        runOK = false;
+        break;
+      } else if (m_outputQueues[runNum - 1][i].getClassifier() == null ||
+          m_outputQueues[runNum - 1][i].getTestSet() == null) {
+        runOK = false;
+        break;       
+      }
+    }
+    
+    if (runOK) {
+      String msg = "[Classifier] " + statusMessagePrefix() 
+        + " dispatching run " + runNum + " to listeners.";
+      if (m_log != null) {
+        m_log.logMessage(msg);
+      } else {
+        System.err.println(msg);
+      }
+      // dispatch this run to listeners
+      for (int i = 0; i < maxSets; i++) {
+        notifyBatchClassifierListeners(m_outputQueues[runNum - 1][i]);
+        // save memory
+        m_outputQueues[runNum - 1][i] = null;
+      }
+      
+      if (runNum == maxRunNum) {
+        // unblock
+        msg = "[Classifier] " + statusMessagePrefix() 
+        + " last classifier unblocking...";
+
+        if (m_log != null) {
+          m_log.logMessage(msg);
+        } else {
+          System.err.println(msg);
+        }
+        //m_visual.setText(m_oldText);
+
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Finished.");
+        }
+        // m_outputQueues = null; // free memory
+        m_reject = false;
+        block(false);
+        m_state = IDLE;
+      }
+    }
+  } */
+
+  /**
+   * Sets the visual appearance of this wrapper bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Gets the visual appearance of this wrapper bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    // try to get a default for this package of classifiers
+    String name = m_ClassifierTemplate.getClass().toString();
+    String packageName = name.substring(0, name.lastIndexOf('.'));
+    packageName = 
+      packageName.substring(packageName.lastIndexOf('.')+1,
+                            packageName.length());
+    if (!m_visual.loadIcons(BeanVisual.ICON_PATH+"Default_"+packageName
+                            +"Classifier.gif",
+                            BeanVisual.ICON_PATH+"Default_"+packageName
+                            +"Classifier_animated.gif")) {
+      m_visual.loadIcons(BeanVisual.
+                         ICON_PATH+"DefaultClassifier.gif",
+                         BeanVisual.
+                         ICON_PATH+"DefaultClassifier_animated.gif");
+    }
+  }
+
+  /**
+   * Add a batch classifier listener
+   *
+   * @param cl a <code>BatchClassifierListener</code> value
+   */
+  public synchronized void 
+    addBatchClassifierListener(BatchClassifierListener cl) {
+    m_batchClassifierListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a batch classifier listener
+   *
+   * @param cl a <code>BatchClassifierListener</code> value
+   */
+  public synchronized void 
+    removeBatchClassifierListener(BatchClassifierListener cl) {
+    m_batchClassifierListeners.remove(cl);
+  }
+
+  /**
+   * Notify all batch classifier listeners of a batch classifier event
+   *
+   * @param ce a <code>BatchClassifierEvent</code> value
+   */
+  private synchronized void notifyBatchClassifierListeners(BatchClassifierEvent ce) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_batchClassifierListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((BatchClassifierListener)l.elementAt(i)).acceptClassifier(ce);
+      }
+    }
+  }
+
+  /**
+   * Add a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void addGraphListener(GraphListener cl) {
+    m_graphListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void removeGraphListener(GraphListener cl) {
+    m_graphListeners.remove(cl);
+  }
+
+  /**
+   * Notify all graph listeners of a graph event
+   *
+   * @param ge a <code>GraphEvent</code> value
+   */
+  private void notifyGraphListeners(GraphEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_graphListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((GraphListener)l.elementAt(i)).acceptGraph(ge);
+      }
+    }
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void addConfigurationListener(ConfigurationListener cl) {
+    
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void removeConfigurationListener(ConfigurationListener cl) {
+    
+  }
+
+  /**
+   * Notify all text listeners of a text event
+   *
+   * @param ge a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TextListener)l.elementAt(i)).acceptText(ge);
+      }
+    }
+  }
+
+  /**
+   * Add an incremental classifier listener
+   *
+   * @param cl an <code>IncrementalClassifierListener</code> value
+   */
+  public synchronized void 
+    addIncrementalClassifierListener(IncrementalClassifierListener cl) {
+    m_incrementalClassifierListeners.add(cl);
+  }
+
+  /**
+   * Remove an incremental classifier listener
+   *
+   * @param cl an <code>IncrementalClassifierListener</code> value
+   */
+  public synchronized void 
+    removeIncrementalClassifierListener(IncrementalClassifierListener cl) {
+    m_incrementalClassifierListeners.remove(cl);
+  }
+
+  /**
+   * Notify all incremental classifier listeners of an incremental classifier
+   * event
+   *
+   * @param ce an <code>IncrementalClassifierEvent</code> value
+   */
+  private void 
+    notifyIncrementalClassifierListeners(IncrementalClassifierEvent ce) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_incrementalClassifierListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((IncrementalClassifierListener)l.elementAt(i)).acceptClassifier(ce);
+      }
+    }
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the named event
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    /*    if (eventName.compareTo("instance") == 0) {
+      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
+	return false;
+      }
+      } */
+    if (m_listenees.containsKey(eventName)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the named event
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (eventName.compareTo("instance") == 0) {
+      if (!(m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier)) {
+	if (m_log != null) {
+	  String msg = statusMessagePrefix() + "WARNING: "
+          + m_ClassifierTemplate.getClass().getName() 
+          + " Is not an updateable classifier. This "
+          +"classifier will only be evaluated on incoming "
+          +"instance events and not trained on them.";
+	  m_log.logMessage("[Classifier] " + msg);
+	  m_log.statusMessage(msg);
+	}
+      }
+    }
+
+    if (connectionAllowed(eventName)) {
+      m_listenees.put(eventName, source);
+      /*      if (eventName.compareTo("instance") == 0) {
+	startIncrementalHandler();
+	} */
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    m_listenees.remove(eventName);
+    if (eventName.compareTo("instance") == 0) {
+      stop(); // kill the incremental handler thread if it is running
+    }
+  }
+
+  /**
+   * Function used to stop code that calls acceptTrainingSet. This is 
+   * needed as classifier construction is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+
+    if (tf) {
+      try {
+	  // only block if thread is still doing something useful!
+//	if (m_state != IDLE) {
+	  wait();
+	  //}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+
+  /**
+   * Stop any classifier action
+   */
+  public void stop() {
+    // tell all listenees (upstream beans) to stop
+    Enumeration en = m_listenees.keys();
+    while (en.hasMoreElements()) {
+      Object tempO = m_listenees.get(en.nextElement());
+      if (tempO instanceof BeanCommon) {
+	((BeanCommon)tempO).stop();
+      }
+    }
+    
+    // shutdown the executor pool and reclaim storage
+    if (m_executorPool != null) {
+      m_executorPool.shutdownNow();
+      m_executorPool.purge();
+      m_executorPool = null;
+    }
+    m_reject = false;
+    block(false);
+    m_visual.setStatic();
+    if (m_oldText.length() > 0) {
+      //m_visual.setText(m_oldText);
+    }
+
+    // stop the build thread
+    /*if (m_buildThread != null) {
+      m_buildThread.interrupt();
+      m_buildThread.stop();
+      m_buildThread = null;
+      m_visual.setStatic();
+    } */
+  }
+
+  public void loadModel() {
+    try {
+      if (m_fileChooser == null) {
+        // i.e. after de-serialization
+        setupFileChooser();
+      }
+      int returnVal = m_fileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+        File loadFrom = m_fileChooser.getSelectedFile();
+
+        // add extension if necessary
+        if (m_fileChooser.getFileFilter() == m_binaryFilter) {
+          if (!loadFrom.getName().toLowerCase().endsWith("." + FILE_EXTENSION)) {
+            loadFrom = new File(loadFrom.getParent(),
+                                loadFrom.getName() + "." + FILE_EXTENSION);
+          }
+        } else if (m_fileChooser.getFileFilter() == m_KOMLFilter) {
+          if (!loadFrom.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION 
+                                                         + FILE_EXTENSION)) {
+            loadFrom = new File(loadFrom.getParent(),
+                                loadFrom.getName() + KOML.FILE_EXTENSION 
+                                + FILE_EXTENSION);
+          }
+        } else if (m_fileChooser.getFileFilter() == m_XStreamFilter) {
+          if (!loadFrom.getName().toLowerCase().endsWith(XStream.FILE_EXTENSION 
+                                                        + FILE_EXTENSION)) {
+            loadFrom = new File(loadFrom.getParent(),
+                                loadFrom.getName() + XStream.FILE_EXTENSION 
+                                + FILE_EXTENSION);
+          }
+        }
+
+        weka.classifiers.Classifier temp = null;
+        Instances tempHeader = null;
+        // KOML ?
+        if ((KOML.isPresent()) &&
+            (loadFrom.getAbsolutePath().toLowerCase().
+             endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION))) {
+          Vector v = (Vector) KOML.read(loadFrom.getAbsolutePath());
+          temp = (weka.classifiers.Classifier) v.elementAt(0);
+          if (v.size() == 2) {
+            // try and grab the header
+            tempHeader = (Instances) v.elementAt(1);
+          }
+        } /* XStream */ else if ((XStream.isPresent()) &&
+                                 (loadFrom.getAbsolutePath().toLowerCase().
+                                  endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION))) {
+          Vector v = (Vector) XStream.read(loadFrom.getAbsolutePath());
+          temp = (weka.classifiers.Classifier) v.elementAt(0);
+          if (v.size() == 2) {
+            // try and grab the header
+            tempHeader = (Instances) v.elementAt(1);
+          } 
+        } /* binary */ else {
+
+          ObjectInputStream is = 
+            new ObjectInputStream(new BufferedInputStream(
+                                                          new FileInputStream(loadFrom)));
+          // try and read the model
+          temp = (weka.classifiers.Classifier)is.readObject();
+          // try and read the header (if present)
+          try {
+            tempHeader = (Instances)is.readObject();
+          } catch (Exception ex) {
+            //            System.err.println("No header...");
+            // quietly ignore
+          }
+          is.close();
+        }
+
+        // Update name and icon
+        setTrainedClassifier(temp);
+        // restore header
+        m_trainingSet = tempHeader;
+
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Loaded model.");
+          m_log.logMessage("[Classifier] " + statusMessagePrefix() 
+              + "Loaded classifier: "
+              + m_Classifier.getClass().toString());
+        }
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(Classifier.this,
+                                    "Problem loading classifier.\n",
+                                    "Load Model",
+                                    JOptionPane.ERROR_MESSAGE);
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "ERROR: unable to load " +
+        		"model (see log).");
+        m_log.logMessage("[Classifier] " + statusMessagePrefix() 
+            + "Problem loading classifier. " 
+            + ex.getMessage());
+      }
+    }
+  }
+
+  public void saveModel() {
+    try {
+      if (m_fileChooser == null) {
+        // i.e. after de-serialization
+        setupFileChooser();
+      }
+      int returnVal = m_fileChooser.showSaveDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+        File saveTo = m_fileChooser.getSelectedFile();
+        String fn = saveTo.getAbsolutePath();
+        if (m_fileChooser.getFileFilter() == m_binaryFilter) {
+          if (!fn.toLowerCase().endsWith("." + FILE_EXTENSION)) {
+            fn += "." + FILE_EXTENSION;
+          }
+        } else if (m_fileChooser.getFileFilter() == m_KOMLFilter) {
+          if (!fn.toLowerCase().endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION)) {
+            fn += KOML.FILE_EXTENSION + FILE_EXTENSION;
+          }
+        } else if (m_fileChooser.getFileFilter() == m_XStreamFilter) {
+          if (!fn.toLowerCase().endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION)) {
+            fn += XStream.FILE_EXTENSION + FILE_EXTENSION;
+          }
+        }
+        saveTo = new File(fn);
+
+        // now serialize model
+        // KOML?
+        if ((KOML.isPresent()) &&
+            saveTo.getAbsolutePath().toLowerCase().
+            endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION)) {
+          SerializedModelSaver.saveKOML(saveTo,
+                                        m_Classifier,
+                                        (m_trainingSet != null)
+                                        ? new Instances(m_trainingSet, 0)
+                                        : null);
+          /*          Vector v = new Vector();
+          v.add(m_Classifier);
+          if (m_trainingSet != null) {
+            v.add(new Instances(m_trainingSet, 0));
+          }
+          v.trimToSize();
+          KOML.write(saveTo.getAbsolutePath(), v); */
+        } /* XStream */ else if ((XStream.isPresent()) &&
+                                 saveTo.getAbsolutePath().toLowerCase().
+            endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION)) {
+
+          SerializedModelSaver.saveXStream(saveTo,
+                                           m_Classifier,
+                                           (m_trainingSet != null)
+                                           ? new Instances(m_trainingSet, 0)
+                                           : null);
+          /*          Vector v = new Vector();
+          v.add(m_Classifier);
+          if (m_trainingSet != null) {
+            v.add(new Instances(m_trainingSet, 0));
+          }
+          v.trimToSize();
+          XStream.write(saveTo.getAbsolutePath(), v); */
+        } else /* binary */ {
+          ObjectOutputStream os = 
+            new ObjectOutputStream(new BufferedOutputStream(
+                                   new FileOutputStream(saveTo)));
+          os.writeObject(m_Classifier);
+          if (m_trainingSet != null) {
+            Instances header = new Instances(m_trainingSet, 0);
+            os.writeObject(header);
+          }
+          os.close();
+        }
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Model saved.");
+          m_log.logMessage("[Classifier] " + statusMessagePrefix() 
+              + " Saved classifier " + getCustomName());
+        }
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(Classifier.this,
+                                    "Problem saving classifier.\n",
+                                    "Save Model",
+                                    JOptionPane.ERROR_MESSAGE);
+      if (m_log != null) {
+        m_log.statusMessage(statusMessagePrefix() + "ERROR: unable to" +
+        		" save model (see log).");
+        m_log.logMessage("[Classifier] " + statusMessagePrefix() 
+            + " Problem saving classifier " + getCustomName() 
+            + ex.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+
+  /**
+   * Return an enumeration of requests that can be made by the user
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_executorPool != null && 
+        (m_executorPool.getQueue().size() > 0 || 
+            m_executorPool.getActiveCount() > 0)) {
+      newVector.addElement("Stop");
+    }
+
+    if ((m_executorPool == null || 
+        (m_executorPool.getQueue().size() == 0 && 
+            m_executorPool.getActiveCount() == 0)) && 
+        m_Classifier != null) {
+      newVector.addElement("Save model");
+    }
+
+    if (m_executorPool == null || 
+        (m_executorPool.getQueue().size() == 0 && 
+            m_executorPool.getActiveCount() == 0)) {
+      newVector.addElement("Load model");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform a particular request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else if (request.compareTo("Save model") == 0) {
+      saveModel();
+    } else if (request.compareTo("Load model") == 0) {
+      loadModel();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Classifier)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the event described by the
+   * supplied event descriptor could be generated.
+   *
+   * @param esd an <code>EventSetDescriptor</code> value
+   * @return a <code>boolean</code> value
+   */
+  public boolean eventGeneratable(EventSetDescriptor esd) {
+    String eventName = esd.getName();
+    return eventGeneratable(eventName);
+  }
+  
+  /**
+   * @param name of the event to check
+   * @return true if eventName is one of the possible events
+   * that this component can generate
+   */
+  private boolean generatableEvent(String eventName) {
+    if (eventName.compareTo("graph") == 0
+	|| eventName.compareTo("text") == 0
+	|| eventName.compareTo("batchClassifier") == 0
+	|| eventName.compareTo("incrementalClassifier") == 0
+	|| eventName.compareTo("configuration") == 0) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (!generatableEvent(eventName)) {
+      return false;
+    }
+    if (eventName.compareTo("graph") == 0) {
+      // can't generate a GraphEvent if classifier is not drawable
+      if (!(m_Classifier instanceof weka.core.Drawable)) {
+	return false;
+      }
+      // need to have a training set before the classifier
+      // can generate a graph!
+      if (!m_listenees.containsKey("trainingSet")) {
+	return false;
+      }
+      // Source needs to be able to generate a trainingSet
+      // before we can generate a graph
+      Object source = m_listenees.get("trainingSet");
+       if (source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+    }
+
+    if (eventName.compareTo("batchClassifier") == 0) {
+      /*      if (!m_listenees.containsKey("testSet")) {
+        return false;
+      }
+      if (!m_listenees.containsKey("trainingSet") && 
+          m_trainingSet == null) {
+	return false;
+        } */
+      if (!m_listenees.containsKey("testSet") && 
+          !m_listenees.containsKey("trainingSet")) {
+        return false;
+      }
+      Object source = m_listenees.get("testSet");
+      if (source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("testSet")) {
+	  return false;
+	}
+      }
+      /*      source = m_listenees.get("trainingSet");
+      if (source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+        } */
+    }
+
+    if (eventName.compareTo("text") == 0) {
+      if (!m_listenees.containsKey("trainingSet") &&
+	  !m_listenees.containsKey("instance")) {
+	return false;
+      }
+      Object source = m_listenees.get("trainingSet");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+      source = m_listenees.get("instance");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("instance")) {
+	  return false;
+	}
+      }
+    }
+
+    if (eventName.compareTo("incrementalClassifier") == 0) {
+      /*      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
+	return false;
+	} */
+      if (!m_listenees.containsKey("instance")) {
+	return false;
+      }
+      Object source = m_listenees.get("instance");
+      if (source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("instance")) {
+	  return false;
+	}
+      }
+    }
+    
+    if (eventName.equals("configuration") && m_Classifier == null) {
+      return false;
+    }
+    
+    return true;
+  }
+    
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    if (m_executorPool == null || 
+        (m_executorPool.getQueue().size() == 0 && 
+            m_executorPool.getActiveCount() == 0) && m_state == IDLE) {
+      return false;
+    }
+    /* System.err.println("isBusy() Q:" + m_executorPool.getQueue().size()
+        +" A:" + m_executorPool.getActiveCount()); */
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Classifier instanceof OptionHandler &&
+        Utils.joinOptions(((OptionHandler)m_Classifier).getOptions()).length() > 0) 
+        ? Utils.joinOptions(((OptionHandler)m_Classifier).getOptions()) + "|"
+            : "");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierBeanInfo.java	(revision 29)
@@ -0,0 +1,77 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the Classifier wrapper bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5247 $
+ */
+public class ClassifierBeanInfo extends SimpleBeanInfo {
+ 
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(Classifier.class, 
+			       "batchClassifier", 
+			       BatchClassifierListener.class, 
+			       "acceptClassifier"),
+	new EventSetDescriptor(Classifier.class,
+			       "graph",
+			       GraphListener.class,
+			       "acceptGraph"),
+	new EventSetDescriptor(Classifier.class,
+			       "text",
+			       TextListener.class,
+			       "acceptText"),
+	new EventSetDescriptor(Classifier.class,
+			       "incrementalClassifier",
+			       IncrementalClassifierListener.class,
+			       "acceptClassifier"),
+	new EventSetDescriptor(Classifier.class,
+	                       "configuration",
+	                        ConfigurationListener.class,
+	                        "acceptConfiguration")
+			       
+	};
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Classifier.class, 
+			      ClassifierCustomizer.class);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierCustomizer.java	(revision 29)
@@ -0,0 +1,259 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+/**
+ * GUI customizer for the classifier wrapper bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 6077 $
+ */
+public class ClassifierCustomizer
+  extends JPanel
+  implements Customizer, CustomizerClosingListener, 
+  CustomizerCloseRequester {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6688000820160821429L;
+
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+  
+  private weka.gui.beans.Classifier m_dsClassifier;
+  /*  private GenericObjectEditor m_ClassifierEditor = 
+      new GenericObjectEditor(true); */
+  private PropertySheetPanel m_ClassifierEditor = 
+    new PropertySheetPanel();
+
+  private JPanel m_incrementalPanel = new JPanel();
+  private JCheckBox m_updateIncrementalClassifier 
+    = new JCheckBox("Update classifier on incoming instance stream");
+  private boolean m_panelVisible = false;
+  
+  private JPanel m_holderPanel = new JPanel();
+  private JTextField m_executionSlotsText = new JTextField();
+  
+  private JCheckBox m_blockOnLastFold = new JCheckBox("Block on last fold of last run");
+  
+  private JFrame m_parentFrame;
+  
+  /** Copy of the current classifier in case cancel is selected */
+  protected weka.classifiers.Classifier m_backup;
+
+  public ClassifierCustomizer() {
+    
+    m_ClassifierEditor.
+      setBorder(BorderFactory.createTitledBorder("Classifier options"));
+    
+    m_updateIncrementalClassifier.
+      setToolTipText("Train the classifier on "
+		     +"each individual incoming streamed instance.");
+    m_updateIncrementalClassifier.
+      addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (m_dsClassifier != null) {
+	      m_dsClassifier.
+		setUpdateIncrementalClassifier(m_updateIncrementalClassifier.
+					       isSelected());
+	    }
+	  }
+	});
+    m_incrementalPanel.add(m_updateIncrementalClassifier);
+    
+    m_executionSlotsText.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_dsClassifier != null &&
+            m_executionSlotsText.getText().length() > 0) {
+          int newSlots = Integer.parseInt(m_executionSlotsText.getText());
+          m_dsClassifier.setExecutionSlots(newSlots);
+        }
+      }
+    });
+    
+    m_executionSlotsText.addFocusListener(new FocusListener() {
+      public void focusGained(FocusEvent e) {}
+      
+      public void focusLost(FocusEvent e) {
+        if (m_dsClassifier != null && 
+            m_executionSlotsText.getText().length() > 0) {
+          int newSlots = Integer.parseInt(m_executionSlotsText.getText());
+          m_dsClassifier.setExecutionSlots(newSlots);
+        }
+      }
+    });
+    
+    m_blockOnLastFold.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_dsClassifier != null) {
+          m_dsClassifier.setBlockOnLastFold(m_blockOnLastFold.isSelected());
+        }
+      }
+    });
+    
+    JPanel executionSlotsPanel = new JPanel();
+    executionSlotsPanel.
+      setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+    JLabel executionSlotsLabel = new JLabel("Execution slots");
+    executionSlotsPanel.setLayout(new BorderLayout());
+    executionSlotsPanel.add(executionSlotsLabel, BorderLayout.WEST);
+    executionSlotsPanel.add(m_executionSlotsText, BorderLayout.CENTER);
+    m_holderPanel.
+      setBorder(BorderFactory.createTitledBorder("More options"));
+    m_holderPanel.setLayout(new BorderLayout());
+    m_holderPanel.add(executionSlotsPanel, BorderLayout.NORTH);
+//    m_blockOnLastFold.setHorizontalTextPosition(SwingConstants.RIGHT);
+    m_holderPanel.add(m_blockOnLastFold, BorderLayout.SOUTH);
+    
+    JPanel holder2 = new JPanel();
+    holder2.setLayout(new BorderLayout());
+    holder2.add(m_holderPanel, BorderLayout.NORTH);
+    JButton OKBut = new JButton("OK");
+    JButton CancelBut = new JButton("Cancel");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+    
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // cancel requested, so revert to backup and then
+        // close the dialog
+        if (m_backup != null) {
+          m_dsClassifier.setClassifierTemplate(m_backup);
+        }
+        m_parentFrame.dispose();
+      }
+    });
+    
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new GridLayout(1,2));
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    holder2.add(butHolder, BorderLayout.SOUTH);
+    
+    setLayout(new BorderLayout());
+    add(m_ClassifierEditor, BorderLayout.CENTER);
+    add(holder2, BorderLayout.SOUTH);
+  }
+  
+  private void checkOnClassifierType() {
+    Classifier editedC = m_dsClassifier.getClassifierTemplate();
+    if (editedC instanceof weka.classifiers.UpdateableClassifier && 
+	m_dsClassifier.hasIncomingStreamInstances()) {
+      if (!m_panelVisible) {
+	m_holderPanel.add(m_incrementalPanel, BorderLayout.SOUTH);
+	m_panelVisible = true;
+	m_executionSlotsText.setEnabled(false);
+      }
+    } else {
+      if (m_panelVisible) {
+	m_holderPanel.remove(m_incrementalPanel);
+	m_executionSlotsText.setEnabled(true);
+	m_panelVisible = false;
+      }
+    }
+  }
+
+  /**
+   * Set the classifier object to be edited
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    m_dsClassifier = (weka.gui.beans.Classifier)object;
+    //    System.err.println(Utils.joinOptions(((OptionHandler)m_dsClassifier.getClassifier()).getOptions()));
+    try {
+      m_backup = 
+        (weka.classifiers.Classifier)GenericObjectEditor.makeCopy(m_dsClassifier.getClassifierTemplate());
+    } catch (Exception ex) {
+      // ignore
+    }
+    m_ClassifierEditor.setTarget(m_dsClassifier.getClassifierTemplate());
+    m_updateIncrementalClassifier.
+      setSelected(m_dsClassifier.getUpdateIncrementalClassifier());
+    m_executionSlotsText.setText(""+m_dsClassifier.getExecutionSlots());
+    m_blockOnLastFold.setSelected(m_dsClassifier.getBlockOnLastFold());
+    checkOnClassifierType();
+  }
+  
+  /* (non-Javadoc)
+   * @see weka.gui.beans.CustomizerClosingListener#customizerClosing()
+   */
+  public void customizerClosing() {
+    if (m_executionSlotsText.getText().length() > 0) {
+      int newSlots = Integer.parseInt(m_executionSlotsText.getText());
+      m_dsClassifier.setExecutionSlots(newSlots);
+    }
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;    
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluator.java	(revision 29)
@@ -0,0 +1,540 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierPerformanceEvaluator.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.gui.explorer.ClassifierErrorsPlotInstances;
+import weka.gui.explorer.ExplorerDefaults;
+import weka.gui.visualize.PlotData2D;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A bean that evaluates the performance of batch trained classifiers
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5928 $
+ */
+public class ClassifierPerformanceEvaluator 
+  extends AbstractEvaluator
+  implements BatchClassifierListener, 
+	     Serializable, UserRequestAcceptor, EventConstraints {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3511801418192148690L;
+
+  /**
+   * Evaluation object used for evaluating a classifier
+   */
+  private transient Evaluation m_eval;
+
+  private transient Thread m_evaluateThread = null;
+  
+  private transient long m_currentBatchIdentifier;
+  private transient int m_setsComplete;
+  
+  private Vector m_textListeners = new Vector();
+  private Vector m_thresholdListeners = new Vector();
+  private Vector m_visualizableErrorListeners = new Vector();
+
+  public ClassifierPerformanceEvaluator() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"ClassifierPerformanceEvaluator.gif",
+		       BeanVisual.ICON_PATH
+		       +"ClassifierPerformanceEvaluator_animated.gif");
+    m_visual.setText("ClassifierPerformanceEvaluator");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+  
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Evaluate the performance of batch trained classifiers.";
+  }
+
+  // ----- Stuff for ROC curves
+  private boolean m_rocListenersConnected = false;
+  
+  /** for generating plottable instance with predictions appended. */
+  private transient ClassifierErrorsPlotInstances m_PlotInstances = null;
+
+  /**
+   * Accept a classifier to be evaluated.
+   *
+   * @param ce a <code>BatchClassifierEvent</code> value
+   */
+  public void acceptClassifier(final BatchClassifierEvent ce) {
+    if (ce.getTestSet() == null || ce.getTestSet().isStructureOnly()) {
+      return; // cant evaluate empty/non-existent test instances
+    }
+    try {
+      if (m_evaluateThread == null) {
+	m_evaluateThread = new Thread() {
+	    public void run() {
+	      boolean errorOccurred = false;
+//	      final String oldText = m_visual.getText();
+	      Classifier classifier = ce.getClassifier();
+	      try {
+		// if (ce.getSetNumber() == 1) {
+	        if (ce.getGroupIdentifier() != m_currentBatchIdentifier) {
+		  
+		  if (ce.getTrainSet().getDataSet() == null ||
+		      ce.getTrainSet().getDataSet().numInstances() == 0) {
+		    // we have no training set to estimate majority class
+		    // or mean of target from
+		    m_eval = new Evaluation(ce.getTestSet().getDataSet());
+		    m_eval.useNoPriors();
+		  } else {
+		    m_eval = new Evaluation(ce.getTrainSet().getDataSet());
+		  }
+//		  m_classifier = ce.getClassifier();
+		  m_PlotInstances = ExplorerDefaults.getClassifierErrorsPlotInstances();
+		  m_PlotInstances.setInstances(ce.getTestSet().getDataSet());
+		  m_PlotInstances.setClassifier(ce.getClassifier());
+		  m_PlotInstances.setClassIndex(ce.getTestSet().getDataSet().classIndex());
+		  m_PlotInstances.setEvaluation(m_eval);
+		  m_PlotInstances.setUp();
+		  
+		  m_currentBatchIdentifier = ce.getGroupIdentifier();
+		  m_setsComplete = 0;
+		}
+//		if (ce.getSetNumber() <= ce.getMaxSetNumber()) {
+	        if (m_setsComplete < ce.getMaxSetNumber()) {
+		  
+		  if (ce.getTrainSet().getDataSet() != null &&
+		      ce.getTrainSet().getDataSet().numInstances() > 0) {
+		    // set the priors
+		    m_eval.setPriors(ce.getTrainSet().getDataSet());
+		  }
+		  
+//		  m_visual.setText("Evaluating ("+ce.getSetNumber()+")...");
+		  if (m_logger != null) {
+		    m_logger.statusMessage(statusMessagePrefix()
+					   +"Evaluating ("+ce.getSetNumber()
+					   +")...");
+		  }
+		  m_visual.setAnimated();
+		  /*
+		  m_eval.evaluateModel(ce.getClassifier(), 
+		  ce.getTestSet().getDataSet()); */
+		  for (int i = 0; i < ce.getTestSet().getDataSet().numInstances(); i++) {
+		    Instance temp = ce.getTestSet().getDataSet().instance(i);
+		    m_PlotInstances.process(temp, ce.getClassifier(), m_eval);
+		  }
+		  
+		  m_setsComplete++;
+		}
+		
+//		if (ce.getSetNumber() == ce.getMaxSetNumber()) {
+	        if (m_setsComplete == ce.getMaxSetNumber()) {
+                  //		  System.err.println(m_eval.toSummaryString());
+		  // m_resultsString.append(m_eval.toSummaryString());
+		  // m_outText.setText(m_resultsString.toString());
+		  String textTitle = classifier.getClass().getName();
+		  String textOptions = "";
+		  if (classifier instanceof OptionHandler) {
+	             textOptions = 
+	               Utils.joinOptions(((OptionHandler)classifier).getOptions()); 
+		  }
+		  textTitle = 
+		    textTitle.substring(textTitle.lastIndexOf('.')+1,
+					textTitle.length());
+		  String resultT = "=== Evaluation result ===\n\n"
+		    + "Scheme: " + textTitle + "\n"
+		    + ((textOptions.length() > 0) ? "Options: " + textOptions + "\n": "")
+		    + "Relation: " + ce.getTestSet().getDataSet().relationName()
+		    + "\n\n" + m_eval.toSummaryString();
+                  
+                  if (ce.getTestSet().getDataSet().
+                      classAttribute().isNominal()) {
+                    resultT += "\n" + m_eval.toClassDetailsString()
+                      + "\n" + m_eval.toMatrixString();
+                  }
+                  
+		  TextEvent te = 
+		    new TextEvent(ClassifierPerformanceEvaluator.this, 
+				  resultT,
+				  textTitle);
+		  notifyTextListeners(te);
+
+                  // set up visualizable errors
+                  if (m_visualizableErrorListeners.size() > 0) {
+                    PlotData2D errorD = m_PlotInstances.getPlotData(
+                	textTitle + " " + textOptions);
+                    VisualizableErrorEvent vel = 
+                      new VisualizableErrorEvent(ClassifierPerformanceEvaluator.this, errorD);
+                    notifyVisualizableErrorListeners(vel);
+                    m_PlotInstances.cleanUp();
+                  }
+                  
+
+		  if (ce.getTestSet().getDataSet().classAttribute().isNominal() &&
+		      m_thresholdListeners.size() > 0) {
+		    ThresholdCurve tc = new ThresholdCurve();
+		    Instances result = tc.getCurve(m_eval.predictions(), 0);
+		    result.
+		      setRelationName(ce.getTestSet().getDataSet().relationName());
+		    PlotData2D pd = new PlotData2D(result);
+		    String htmlTitle = "<html><font size=-2>"
+		      + textTitle;
+		    String newOptions = "";
+		    if (classifier instanceof OptionHandler) {
+		      String[] options = 
+		        ((OptionHandler) classifier).getOptions();
+		      if (options.length > 0) {
+		        for (int ii = 0; ii < options.length; ii++) {
+		          if (options[ii].length() == 0) {
+		            continue;
+		          }
+		          if (options[ii].charAt(0) == '-' && 
+		              !(options[ii].charAt(1) >= '0' &&
+		                  options[ii].charAt(1)<= '9')) {
+		            newOptions += "<br>";
+		          }
+		          newOptions += options[ii];
+		        }
+		      }
+		    }
+		    
+		   htmlTitle += " " + newOptions + "<br>" 
+		      + " (class: "
+                      +ce.getTestSet().getDataSet().
+                        classAttribute().value(0) + ")" 
+                      + "</font></html>";
+		    pd.setPlotName(textTitle + " (class: "
+	                      +ce.getTestSet().getDataSet().
+	                        classAttribute().value(0) + ")");
+		    pd.setPlotNameHTML(htmlTitle);
+		    boolean [] connectPoints = 
+		      new boolean [result.numInstances()];
+		    for (int jj = 1; jj < connectPoints.length; jj++) {
+		      connectPoints[jj] = true;
+		    }
+		    pd.setConnectPoints(connectPoints);
+		    ThresholdDataEvent rde = 
+		      new ThresholdDataEvent(ClassifierPerformanceEvaluator.this,
+				       pd, ce.getTestSet().getDataSet().classAttribute());
+		    notifyThresholdListeners(rde);
+		    /*te = new TextEvent(ClassifierPerformanceEvaluator.this,
+				       result.toString(),
+				       "ThresholdCurveInst");
+				       notifyTextListeners(te); */
+		  }
+		  if (m_logger != null) {
+		    m_logger.statusMessage(statusMessagePrefix() + "Finished.");
+		  }
+
+		  // save memory
+		  m_PlotInstances = null;
+		}
+	      } catch (Exception ex) {
+	        errorOccurred = true;
+	        ClassifierPerformanceEvaluator.this.stop(); // stop all processing
+	        if (m_logger != null) {
+	          m_logger.logMessage("[ClassifierPerformanceEvaluator] "
+	              + statusMessagePrefix() 
+	              + " problem evaluating classifier. " 
+	              + ex.getMessage());
+	        }
+		ex.printStackTrace();
+	      } finally {
+//		m_visual.setText(oldText);
+		m_visual.setStatic();
+		m_evaluateThread = null;
+						
+		if (m_logger != null) {
+		  if (errorOccurred) {
+		    m_logger.statusMessage(statusMessagePrefix() 
+		        + "ERROR (See log for details)");
+		  } else if (isInterrupted()) {
+		    m_logger.logMessage("[" + getCustomName() +"] Evaluation interrupted!");
+		    m_logger.statusMessage(statusMessagePrefix() 
+		        + "INTERRUPTED");
+		  }
+		}
+		block(false);
+	      }
+	    }
+	  };
+	m_evaluateThread.setPriority(Thread.MIN_PRIORITY);
+	m_evaluateThread.start();
+
+	// make sure the thread is still running before we block
+	//	if (m_evaluateThread.isAlive()) {
+	block(true);
+	  //	}
+	m_evaluateThread = null;
+      }
+    }  catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_evaluateThread != null);
+  }
+    
+  /**
+   * Try and stop any action
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+
+    // stop the evaluate thread
+    if (m_evaluateThread != null) {
+      m_evaluateThread.interrupt();
+      m_evaluateThread.stop();
+      m_evaluateThread = null;
+      m_visual.setStatic();
+    }
+  }
+  
+  /**
+   * Function used to stop code that calls acceptClassifier. This is 
+   * needed as classifier evaluation is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+	// only block if thread is still doing something useful!
+	if (m_evaluateThread != null && m_evaluateThread.isAlive()) {
+	  wait();
+	}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Return an enumeration of user activated requests for this bean
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_evaluateThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new 
+	IllegalArgumentException(request
+
+		    + " not supported (ClassifierPerformanceEvaluator)");
+    }
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+  
+  /**
+   * Add a threshold data listener
+   *
+   * @param cl a <code>ThresholdDataListener</code> value
+   */
+  public synchronized void addThresholdDataListener(ThresholdDataListener cl) {
+    m_thresholdListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a Threshold data listener
+   *
+   * @param cl a <code>ThresholdDataListener</code> value
+   */
+  public synchronized void removeThresholdDataListener(ThresholdDataListener cl) {
+    m_thresholdListeners.remove(cl);
+  }
+
+  /**
+   * Add a visualizable error listener
+   *
+   * @param vel a <code>VisualizableErrorListener</code> value
+   */
+  public synchronized void addVisualizableErrorListener(VisualizableErrorListener vel) {
+    m_visualizableErrorListeners.add(vel);
+  }
+
+  /**
+   * Remove a visualizable error listener
+   *
+   * @param vel a <code>VisualizableErrorListener</code> value
+   */
+  public synchronized void removeVisualizableErrorListener(VisualizableErrorListener vel) {
+    m_visualizableErrorListeners.remove(vel);
+  }
+
+  /**
+   * Notify all text listeners of a TextEvent
+   *
+   * @param te a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent te) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying text listeners "
+	//			   +"(ClassifierPerformanceEvaluator)");
+	((TextListener)l.elementAt(i)).acceptText(te);
+      }
+    }
+  }
+
+  /**
+   * Notify all ThresholdDataListeners of a ThresholdDataEvent
+   *
+   * @param te a <code>ThresholdDataEvent</code> value
+   */
+  private void notifyThresholdListeners(ThresholdDataEvent re) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_thresholdListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying text listeners "
+	//			   +"(ClassifierPerformanceEvaluator)");
+	((ThresholdDataListener)l.elementAt(i)).acceptDataSet(re);
+      }
+    }
+  }
+
+  /**
+   * Notify all VisualizableErrorListeners of a VisualizableErrorEvent
+   *
+   * @param te a <code>VisualizableErrorEvent</code> value
+   */
+  private void notifyVisualizableErrorListeners(VisualizableErrorEvent re) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_visualizableErrorListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying text listeners "
+	//			   +"(ClassifierPerformanceEvaluator)");
+	((VisualizableErrorListener)l.elementAt(i)).acceptDataSet(re);
+      }
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).
+	  eventGeneratable("batchClassifier")) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluatorBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluatorBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClassifierPerformanceEvaluatorBeanInfo.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierPerformanceEvaluatorBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the classifier performance evaluator
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ */
+public class ClassifierPerformanceEvaluatorBeanInfo extends SimpleBeanInfo {
+  
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(ClassifierPerformanceEvaluator.class, 
+			       "text", 
+			       TextListener.class, 
+			       "acceptText"),
+	new EventSetDescriptor(ClassifierPerformanceEvaluator.class, 
+			       "thresholdData", 
+			       ThresholdDataListener.class, 
+			       "acceptDataSet"),
+      	new EventSetDescriptor(ClassifierPerformanceEvaluator.class, 
+			       "visualizableError", 
+			       VisualizableErrorListener.class, 
+			       "acceptDataSet")};
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Clusterer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Clusterer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Clusterer.java	(revision 29)
@@ -0,0 +1,923 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Clusterer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.clusterers.EM;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.gui.Logger;
+import weka.gui.ExtensionFileFilter;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+import javax.swing.JOptionPane;
+import javax.swing.JFileChooser;
+
+import javax.swing.JPanel;
+
+/**
+ * Bean that wraps around weka.clusterers
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 5247 $
+ * @see JPanel
+ * @see BeanCommon
+ * @see Visible
+ * @see WekaWrapper
+ * @see Serializable
+ * @see UserRequestAcceptor
+ * @see TrainingSetListener
+ * @see TestSetListener
+ */
+public class Clusterer
+  extends JPanel
+  implements BeanCommon, Visible, WekaWrapper, EventConstraints, 
+             UserRequestAcceptor, TrainingSetListener, 
+             TestSetListener, ConfigurationProducer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7729795159836843810L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("Clusterer",
+		   BeanVisual.ICON_PATH+"EM.gif",
+		   BeanVisual.ICON_PATH+"EM_animated.gif");
+
+  private static int IDLE = 0;
+  private static int BUILDING_MODEL = 1;
+  private static int CLUSTERING = 2;
+
+  private int m_state = IDLE;
+
+  private Thread m_buildThread = null;
+
+  /**
+   * Global info for the wrapped classifier (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Objects talking to us
+   */
+  private Hashtable m_listenees = new Hashtable();
+
+  /**
+   * Objects listening for batch clusterer events
+   */
+  private Vector m_batchClustererListeners = new Vector();
+
+
+  /**
+   * Objects listening for graph events
+   */
+  private Vector m_graphListeners = new Vector();
+
+  /**
+   * Objects listening for text events
+   */
+  private Vector m_textListeners = new Vector();
+
+  /**
+   * Holds training instances for batch training. 
+   */
+  private Instances m_trainingSet;
+  private transient Instances m_testingSet;
+  private weka.clusterers.Clusterer m_Clusterer = new EM();
+  
+
+  private transient Logger m_log = null;
+
+  private Double m_dummy = new Double(0.0);
+
+  private transient JFileChooser m_fileChooser = null;
+
+  /**
+   * Global info (if it exists) for the wrapped classifier
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  /**
+   * Creates a new <code>Clusterer</code> instance.
+   */
+  public Clusterer() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    setClusterer(m_Clusterer);
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Set the clusterer for this wrapper
+   *
+   * @param c a <code>weka.clusterers.Clusterer</code> value
+   */
+  public void setClusterer(weka.clusterers.Clusterer c) {
+    boolean loadImages = true;
+    if (c.getClass().getName().
+	compareTo(m_Clusterer.getClass().getName()) == 0) {
+      loadImages = false;
+    } else {
+      // clusterer has changed so any batch training status is now
+      // invalid
+      m_trainingSet = null;
+    }
+    m_Clusterer = c;
+    String clustererName = c.getClass().toString();
+    clustererName = clustererName.substring(clustererName.
+					      lastIndexOf('.')+1, 
+					      clustererName.length());
+    if (loadImages) {
+      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+clustererName+".gif",
+		       BeanVisual.ICON_PATH+clustererName+"_animated.gif")) {
+	useDefaultVisual();
+      }
+    }
+    m_visual.setText(clustererName);
+
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Clusterer);
+  }
+
+
+  /**
+   * Returns true if this clusterer has an incoming connection that is
+   * a batch set of instances
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean hasIncomingBatchInstances() {
+    if (m_listenees.size() == 0) {
+      return false;
+    }
+    if (m_listenees.containsKey("trainingSet") ||
+	m_listenees.containsKey("testSet") ||
+        m_listenees.containsKey("dataSet")) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Get the clusterer currently set for this wrapper
+   *
+   * @return a <code>weka.clusterers.Clusterer</code> value
+   */
+  public weka.clusterers.Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+
+  /**
+   * Sets the algorithm (clusterer) for this bean
+   *
+   * @param algorithm an <code>Object</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void setWrappedAlgorithm(Object algorithm) 
+    {
+
+    if (!(algorithm instanceof weka.clusterers.Clusterer)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Clusterer)");
+    }
+    setClusterer((weka.clusterers.Clusterer)algorithm);
+  }
+
+  /**
+   * Returns the wrapped clusterer
+   *
+   * @return an <code>Object</code> value
+   */
+  public Object getWrappedAlgorithm() {
+    return getClusterer();
+  }
+
+
+  /**
+   * Accepts a training set and builds batch clusterer
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(final TrainingSetEvent e) {
+    if (e.isStructureOnly()) {
+      // no need to build a clusterer, instead just generate a dummy
+      // BatchClustererEvent in order to pass on instance structure to
+      // any listeners 
+      BatchClustererEvent ce = 
+	new BatchClustererEvent(this, m_Clusterer, 
+				 new DataSetEvent(this, e.getTrainingSet()),
+				 e.getSetNumber(), e.getMaxSetNumber(),1);
+
+      notifyBatchClustererListeners(ce);
+      return;
+    }
+    if (m_buildThread == null) {
+      try {
+	if (m_state == IDLE) {
+	  synchronized (this) {
+	    m_state = BUILDING_MODEL;
+	  }
+	  m_trainingSet = e.getTrainingSet();
+//	  final String oldText = m_visual.getText();
+	  m_buildThread = new Thread() {
+	      public void run() {
+		try {
+		  if (m_trainingSet != null) {  
+		    m_visual.setAnimated();
+//		    m_visual.setText("Building clusters...");
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() 
+		          + "Building clusters...");
+		    }
+		    buildClusterer();
+                    if(m_batchClustererListeners.size() > 0){
+                        BatchClustererEvent ce = 
+                            new BatchClustererEvent(this, m_Clusterer, 
+				 new DataSetEvent(this, e.getTrainingSet()),
+				 e.getSetNumber(), e.getMaxSetNumber(),1);
+                        notifyBatchClustererListeners(ce);
+                    }
+		    if (m_Clusterer instanceof weka.core.Drawable && 
+			m_graphListeners.size() > 0) {
+		      String grphString = 
+			((weka.core.Drawable)m_Clusterer).graph();
+                      int grphType = ((weka.core.Drawable)m_Clusterer).graphType();
+		      String grphTitle = m_Clusterer.getClass().getName();
+		      grphTitle = grphTitle.substring(grphTitle.
+						      lastIndexOf('.')+1, 
+						      grphTitle.length());
+		      grphTitle = "Set " + e.getSetNumber() + " ("
+			+e.getTrainingSet().relationName() + ") "
+			+grphTitle;
+		      
+		      GraphEvent ge = new GraphEvent(Clusterer.this, 
+						     grphString, 
+						     grphTitle,
+                                                     grphType);
+		      notifyGraphListeners(ge);
+		    }
+
+		    if (m_textListeners.size() > 0) {
+		      String modelString = m_Clusterer.toString();
+		      String titleString = m_Clusterer.getClass().getName();
+		      
+		      titleString = titleString.
+			substring(titleString.lastIndexOf('.') + 1,
+				  titleString.length());
+		      modelString = "=== Clusterer model ===\n\n" +
+			"Scheme:   " +titleString+"\n" +
+			"Relation: "  + m_trainingSet.relationName() + 
+			((e.getMaxSetNumber() > 1) 
+			 ? "\nTraining Fold: "+e.getSetNumber()
+			 :"")
+			+ "\n\n"
+			+ modelString;
+		      titleString = "Model: " + titleString;
+
+		      TextEvent nt = new TextEvent(Clusterer.this,
+						   modelString,
+						   titleString);
+		      notifyTextListeners(nt);
+		    }
+		  }
+		} catch (Exception ex) {
+		  Clusterer.this.stop(); // stop processing
+		  if (m_log != null) {
+		    m_log.statusMessage(statusMessagePrefix()
+		        + "ERROR (See log for details");
+		    m_log.logMessage("[Clusterer] " + statusMessagePrefix()
+		        + " problem training clusterer. " + ex.getMessage());
+		  }
+		  ex.printStackTrace();
+		} finally {
+//		  m_visual.setText(oldText);
+		  m_visual.setStatic();
+		  m_state = IDLE;
+		  if (isInterrupted()) {
+		    // prevent any clusterer events from being fired
+		    m_trainingSet = null;
+		    if (m_log != null) {
+		      m_log.logMessage("[Clusterer]" + statusMessagePrefix() 
+		          + " Build clusterer interrupted!");
+		      m_log.statusMessage(statusMessagePrefix() 
+		          + "INTERRUPTED");
+		    }
+		  } else {
+		    // save header
+		    m_trainingSet = new Instances(m_trainingSet, 0);
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() + "Finished.");
+		    }
+		  }
+		  block(false);
+		}
+	      }	
+	    };
+	  m_buildThread.setPriority(Thread.MIN_PRIORITY);
+	  m_buildThread.start();
+	  // make sure the thread is still running before we block
+	  //	  if (m_buildThread.isAlive()) {
+	  block(true);
+	    //	  }
+	  m_buildThread = null;
+	  m_state = IDLE;
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Accepts a test set for a batch trained clusterer
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public void acceptTestSet(TestSetEvent e) {
+
+    if (m_trainingSet != null) {
+      try {
+	if (m_state == IDLE) {
+	  synchronized(this) {
+	    m_state = CLUSTERING;
+	  }
+          m_testingSet = e.getTestSet();
+	  if (m_trainingSet.equalHeaders(m_testingSet)) {
+	    BatchClustererEvent ce = 
+	      new BatchClustererEvent(this, m_Clusterer, 
+				       new DataSetEvent(this, e.getTestSet()),
+				  e.getSetNumber(), e.getMaxSetNumber(),0);
+
+	    notifyBatchClustererListeners(ce);
+	    
+	  }
+	  m_state = IDLE;
+	}
+      } catch (Exception ex) {
+        stop(); // stop any processing
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix()
+              + "ERROR (see log for details");
+          m_log.logMessage("[Clusterer] " + statusMessagePrefix()
+              + " problem during testing. " + ex.getMessage());
+        }
+	ex.printStackTrace();
+      }
+    }
+  }
+
+/**
+   * Builds the clusters
+   */
+  private void buildClusterer() throws Exception {
+      if(m_trainingSet.classIndex() < 0)  
+        m_Clusterer.buildClusterer(m_trainingSet);
+      else{ //class based evaluation if class attribute is set
+        Remove removeClass = new Remove();
+	removeClass.setAttributeIndices(""+(m_trainingSet.classIndex()+1));
+	removeClass.setInvertSelection(false);
+	removeClass.setInputFormat(m_trainingSet);
+	Instances clusterTrain = Filter.useFilter(m_trainingSet, removeClass);
+	m_Clusterer.buildClusterer(clusterTrain);
+      }
+  }
+
+  /**
+   * Sets the visual appearance of this wrapper bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Gets the visual appearance of this wrapper bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultClusterer.gif",
+		       BeanVisual.ICON_PATH+"DefaultClusterer_animated.gif");
+  }
+
+  /**
+   * Add a batch clusterer listener
+   *
+   * @param cl a <code>BatchClustererListener</code> value
+   */
+  public synchronized void 
+    addBatchClustererListener(BatchClustererListener cl) {
+    m_batchClustererListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a batch clusterer listener
+   *
+   * @param cl a <code>BatchClustererListener</code> value
+   */
+  public synchronized void 
+    removeBatchClustererListener(BatchClustererListener cl) {
+    m_batchClustererListeners.remove(cl);
+  }
+
+  /**
+   * Notify all batch clusterer listeners of a batch clusterer event
+   *
+   * @param ce a <code>BatchClustererEvent</code> value
+   */
+  private void notifyBatchClustererListeners(BatchClustererEvent ce) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_batchClustererListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((BatchClustererListener)l.elementAt(i)).acceptClusterer(ce);
+      }
+    }
+  }
+
+  /**
+   * Add a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void addGraphListener(GraphListener cl) {
+    m_graphListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a graph listener
+   *
+   * @param cl a <code>GraphListener</code> value
+   */
+  public synchronized void removeGraphListener(GraphListener cl) {
+    m_graphListeners.remove(cl);
+  }
+
+  /**
+   * Notify all graph listeners of a graph event
+   *
+   * @param ge a <code>GraphEvent</code> value
+   */
+  private void notifyGraphListeners(GraphEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_graphListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((GraphListener)l.elementAt(i)).acceptGraph(ge);
+      }
+    }
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+
+  /**
+   * Notify all text listeners of a text event
+   *
+   * @param ge a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TextListener)l.elementAt(i)).acceptText(ge);
+      }
+    }
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void addConfigurationListener(ConfigurationListener cl) {
+    
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void removeConfigurationListener(ConfigurationListener cl) {
+    
+  }
+
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the named event
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    /*    if (eventName.compareTo("instance") == 0) {
+      if (!(m_Clusterer instanceof weka.classifiers.UpdateableClassifier)) {
+	return false;
+      }
+      } */
+    if (m_listenees.containsKey(eventName)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the named event
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+
+    if (connectionAllowed(eventName)) {
+      m_listenees.put(eventName, source);
+      /*      if (eventName.compareTo("instance") == 0) {
+	startIncrementalHandler();
+	} */
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    m_listenees.remove(eventName);
+  }
+
+  /**
+   * Function used to stop code that calls acceptTrainingSet. This is 
+   * needed as clusterer construction is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+
+    if (tf) {
+      try {
+	  // only block if thread is still doing something useful!
+	if (m_buildThread.isAlive() && m_state != IDLE) {
+	  wait();
+	  }
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_buildThread != null);
+  }
+  
+  /**
+   * Stop any clusterer action
+   */
+  public void stop() {
+    // tell all listenees (upstream beans) to stop
+    Enumeration en = m_listenees.keys();
+    while (en.hasMoreElements()) {
+      Object tempO = m_listenees.get(en.nextElement());
+      if (tempO instanceof BeanCommon) {
+	((BeanCommon)tempO).stop();
+      }
+    }
+
+    // stop the build thread
+    if (m_buildThread != null) {
+      m_buildThread.interrupt();
+      m_buildThread.stop();
+      m_buildThread = null;
+      m_visual.setStatic();
+    }
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+
+  public void saveModel() {
+    try {
+      if (m_fileChooser == null) {
+        // i.e. after de-serialization
+        m_fileChooser = 
+          new JFileChooser(new File(System.getProperty("user.dir")));
+        ExtensionFileFilter ef = new ExtensionFileFilter("model", "Serialized weka clusterer");
+      m_fileChooser.setFileFilter(ef);
+      }
+      int returnVal = m_fileChooser.showSaveDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+        File saveTo = m_fileChooser.getSelectedFile();
+        String fn = saveTo.getAbsolutePath();
+        if (!fn.endsWith(".model")) {
+          fn += ".model";
+          saveTo = new File(fn);
+        }
+        ObjectOutputStream os = 
+          new ObjectOutputStream(new BufferedOutputStream(
+                                                          new FileOutputStream(saveTo)));
+        os.writeObject(m_Clusterer);
+        if (m_trainingSet != null) {
+          Instances header = new Instances(m_trainingSet, 0);
+          os.writeObject(header);
+        }
+        os.close();
+        if (m_log != null) {
+          m_log.logMessage("[Clusterer] Saved clusterer " + getCustomName());
+        }
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(Clusterer.this,
+                                    "Problem saving clusterer.\n",
+                                    "Save Model",
+                                    JOptionPane.ERROR_MESSAGE);
+      if (m_log != null) {
+        m_log.logMessage("[Clusterer] Problem saving clusterer. " 
+            + getCustomName() + ex.getMessage());
+      }
+    }
+  }
+
+  public void loadModel() {
+    try {
+      if (m_fileChooser == null) {
+        // i.e. after de-serialization
+        m_fileChooser = 
+          new JFileChooser(new File(System.getProperty("user.dir")));
+        ExtensionFileFilter ef = new ExtensionFileFilter("model", "Serialized weka clusterer");
+        m_fileChooser.setFileFilter(ef);
+      }
+      int returnVal = m_fileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+        File loadFrom = m_fileChooser.getSelectedFile();
+        ObjectInputStream is = 
+          new ObjectInputStream(new BufferedInputStream(
+                                new FileInputStream(loadFrom)));
+        // try and read the model
+        weka.clusterers.Clusterer temp = (weka.clusterers.Clusterer)is.readObject();
+
+        // Update name and icon
+        setClusterer(temp);
+        
+        // try and read the header (if present)
+        try {
+          m_trainingSet = (Instances)is.readObject();
+        } catch (Exception ex) {
+          // quietly ignore
+        }
+        is.close();
+        if (m_log != null) {
+          m_log.logMessage("[Clusterer] Loaded clusterer: "
+                           + m_Clusterer.getClass().toString());
+        }
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(Clusterer.this,
+                                    "Problem loading classifier.\n",
+                                    "Load Model",
+                                    JOptionPane.ERROR_MESSAGE);
+      if (m_log != null) {
+        m_log.logMessage("[Clusterer] Problem loading classifier. " 
+            + ex.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Return an enumeration of requests that can be made by the user
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_buildThread != null) {
+      newVector.addElement("Stop");
+    }
+
+    if (m_buildThread == null &&
+        m_Clusterer != null) {
+      newVector.addElement("Save model");
+    }
+
+    if (m_buildThread == null) {
+      newVector.addElement("Load model");
+    }
+    
+    return newVector.elements();
+  }
+
+  /**
+   * Perform a particular request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    }  else if (request.compareTo("Save model") == 0) {
+      saveModel();
+    } else if (request.compareTo("Load model") == 0) {
+      loadModel();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Clusterer)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the event described by the
+   * supplied event descriptor could be generated.
+   *
+   * @param esd an <code>EventSetDescriptor</code> value
+   * @return a <code>boolean</code> value
+   */
+  public boolean eventGeneratable(EventSetDescriptor esd) {
+    String eventName = esd.getName();
+    return eventGeneratable(eventName);
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.compareTo("graph") == 0) {
+      // can't generate a GraphEvent if clusterer is not drawable
+      if (!(m_Clusterer instanceof weka.core.Drawable)) {
+	return false;
+      }
+      // need to have a training set before the clusterer
+      // can generate a graph!
+      if (!m_listenees.containsKey("trainingSet")) {
+	return false;
+      }
+      // Source needs to be able to generate a trainingSet
+      // before we can generate a graph
+      Object source = m_listenees.get("trainingSet");
+       if (source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+    }
+
+    if (eventName.compareTo("batchClusterer") == 0) {
+      if (!m_listenees.containsKey("trainingSet")) {
+	return false;
+      }
+     
+      Object source = m_listenees.get("trainingSet");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+    }
+
+    if (eventName.compareTo("text") == 0) {
+      if (!m_listenees.containsKey("trainingSet")){ 
+	return false;
+      }
+      Object source = m_listenees.get("trainingSet");
+      if (source != null && source instanceof EventConstraints) {
+	if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
+	  return false;
+	}
+      }
+    }
+
+    if (eventName.compareTo("batchClassifier") == 0)
+        return false;
+    if (eventName.compareTo("incrementalClassifier") == 0)
+        return false;
+    
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Clusterer instanceof OptionHandler &&
+        Utils.joinOptions(((OptionHandler)m_Clusterer).getOptions()).length() > 0)
+        ? Utils.joinOptions(((OptionHandler)m_Clusterer).getOptions()) + "|"
+            : "");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClustererBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClustererBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClustererBeanInfo.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClustererBeanInfo.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the Clusterer wrapper bean
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 5247 $
+ */
+public class ClustererBeanInfo extends SimpleBeanInfo {
+ 
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(Clusterer.class, 
+			       "batchClusterer", 
+			       BatchClustererListener.class, 
+			       "acceptClusterer"),
+        new EventSetDescriptor(Clusterer.class,
+			       "graph",
+			       GraphListener.class,
+			       "acceptGraph"),                       
+	new EventSetDescriptor(Clusterer.class,
+			       "text",
+			       TextListener.class,
+			       "acceptText"),
+	new EventSetDescriptor(Clusterer.class,
+	                       "configuration",
+	                       ConfigurationListener.class,
+	                       "acceptConfiguration")
+	};
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Clusterer.class, 
+			      ClustererCustomizer.class);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClustererCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClustererCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClustererCustomizer.java	(revision 29)
@@ -0,0 +1,141 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClustererCustomizer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * GUI customizer for the Clusterer wrapper bean
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 5340 $
+ */
+public class ClustererCustomizer
+  extends JPanel
+  implements Customizer, CustomizerCloseRequester {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2035688458149534161L;
+
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+  
+  private weka.gui.beans.Clusterer m_dsClusterer;
+  
+  private PropertySheetPanel m_ClustererEditor = 
+    new PropertySheetPanel();
+  
+  private JFrame m_parentFrame;
+  
+  /** Backup if the user presses cancel */
+  private weka.clusterers.Clusterer m_backup;
+
+  
+  public ClustererCustomizer() {
+    
+    setLayout(new BorderLayout());
+    add(m_ClustererEditor, BorderLayout.CENTER);
+    
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new GridLayout(1,2));
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // cancel requested, so revert to backup and then
+        // close the dialog
+        if (m_backup != null) {
+          m_dsClusterer.setClusterer(m_backup);
+        }
+        m_parentFrame.dispose();
+      }
+    });
+    
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    add(butHolder, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * Set the Clusterer object to be edited
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    m_dsClusterer = (weka.gui.beans.Clusterer)object;
+    try {
+      m_backup = 
+        (weka.clusterers.Clusterer)GenericObjectEditor.makeCopy(m_dsClusterer.getClusterer());
+    } catch (Exception ex) {
+      // ignore
+    }
+    
+    m_ClustererEditor.setTarget(m_dsClusterer.getClusterer());
+    
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluator.java	(revision 29)
@@ -0,0 +1,344 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClustererPerformanceEvaluator.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.clusterers.ClusterEvaluation;
+import weka.clusterers.Clusterer;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A bean that evaluates the performance of batch trained clusterers
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 4813 $
+ */
+public class ClustererPerformanceEvaluator
+  extends AbstractEvaluator
+  implements BatchClustererListener, Serializable, UserRequestAcceptor, 
+             EventConstraints {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8041163601333978584L;
+
+  /**
+   * Evaluation object used for evaluating a clusterer
+   */
+  private transient ClusterEvaluation m_eval;
+
+  /**
+   * Holds the clusterer to be evaluated
+   */
+  private transient Clusterer m_clusterer;
+
+  private transient Thread m_evaluateThread = null;
+  
+  private Vector m_textListeners = new Vector();
+
+  public ClustererPerformanceEvaluator() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"ClustererPerformanceEvaluator.gif",
+		       BeanVisual.ICON_PATH
+		       +"ClustererPerformanceEvaluator_animated.gif");
+    m_visual.setText("ClustererPerformanceEvaluator");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+  
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Evaluate the performance of batch trained clusterers.";
+  }
+
+  /**
+   * Accept a clusterer to be evaluated
+   *
+   * @param ce a <code>BatchClustererEvent</code> value
+   */
+  public void acceptClusterer(final BatchClustererEvent ce) {
+    
+    if (ce.getTestSet().isStructureOnly()) {
+      return; // cant evaluate empty instances
+    }
+    try {
+      if (m_evaluateThread == null) {
+	m_evaluateThread = new Thread() {
+	    public void run() {
+              boolean numericClass = false;  
+//	      final String oldText = m_visual.getText();
+	      try {
+		if (ce.getSetNumber() == 1 /*|| 
+		    ce.getClusterer() != m_clusterer */) {
+		  m_eval = new ClusterEvaluation();
+		  m_clusterer = ce.getClusterer();
+                  m_eval.setClusterer(m_clusterer);
+		}
+		
+		if (ce.getSetNumber() <= ce.getMaxSetNumber()) {
+//		  m_visual.setText("Evaluating ("+ce.getSetNumber()+")...");
+		  if (m_logger != null) {
+		    m_logger.statusMessage(statusMessagePrefix()
+					   +"Evaluating ("+ce.getSetNumber()
+					   +")...");
+		  }
+		  m_visual.setAnimated();
+                  if(ce.getTestSet().getDataSet().classIndex() != -1 && ce.getTestSet().getDataSet().classAttribute().isNumeric()){
+                    numericClass = true;
+                    ce.getTestSet().getDataSet().setClassIndex(-1);
+                  } 
+                  m_eval.evaluateClusterer(ce.getTestSet().getDataSet());
+		}
+		
+		if (ce.getSetNumber() == ce.getMaxSetNumber()) {
+		  String textTitle = m_clusterer.getClass().getName();
+		  textTitle = 
+		    textTitle.substring(textTitle.lastIndexOf('.')+1,
+					textTitle.length());
+                  String test;
+                  if(ce.getTestOrTrain() == 0)
+                      test = "test";
+                  else
+                      test = "training";
+		  String resultT = "=== Evaluation result for "+test+" instances ===\n\n"
+		    + "Scheme: " + textTitle + "\n"
+		    + "Relation: " + ce.getTestSet().getDataSet().relationName()
+		    + "\n\n" + m_eval.clusterResultsToString();
+                  if(numericClass)
+                      resultT = resultT + "\n\nNo class based evaluation possible. Class attribute has to be nominal.";
+		  TextEvent te = 
+		    new TextEvent(ClustererPerformanceEvaluator.this, 
+				  resultT,
+				  textTitle);
+		  notifyTextListeners(te);
+		  if (m_logger != null) {
+		    m_logger.statusMessage(statusMessagePrefix() + "Finished.");
+		  }
+		}
+	      } catch (Exception ex) {
+	        // stop all processing
+	        ClustererPerformanceEvaluator.this.stop();
+	        if (m_logger != null) {
+	          m_logger.statusMessage(statusMessagePrefix()
+	              + "ERROR (see log for details");
+	          m_logger.logMessage("[ClustererPerformanceEvaluator] " 
+	              + statusMessagePrefix()
+	              + " problem while evaluating clusterer. " + ex.getMessage());
+	        }
+		ex.printStackTrace();
+	      } finally {
+//		m_visual.setText(oldText);
+		m_visual.setStatic();
+		m_evaluateThread = null;
+		if (isInterrupted()) {
+		  if (m_logger != null) {
+		    m_logger.logMessage("[" + getCustomName() 
+		        + "] Evaluation interrupted!");
+		    m_logger.statusMessage(statusMessagePrefix() 
+		        + "INTERRUPTED");
+		  }
+		}
+		block(false);
+	      }
+	    }
+	  };
+	m_evaluateThread.setPriority(Thread.MIN_PRIORITY);
+	m_evaluateThread.start();
+
+	// make sure the thread is still running before we block
+	//	if (m_evaluateThread.isAlive()) {
+	block(true);
+	  //	}
+	m_evaluateThread = null;
+      }
+    }  catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_evaluateThread != null);
+  }
+
+  /**
+   * Try and stop any action
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+
+    // stop the evaluate thread
+    if (m_evaluateThread != null) {
+      m_evaluateThread.interrupt();
+      m_evaluateThread.stop();
+      m_evaluateThread = null;
+      m_visual.setStatic();
+    }
+  }
+  
+  /**
+   * Function used to stop code that calls acceptClusterer. This is 
+   * needed as clusterer evaluation is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+	// only block if thread is still doing something useful!
+	if (m_evaluateThread != null && m_evaluateThread.isAlive()) {
+	  wait();
+	}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Return an enumeration of user activated requests for this bean
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_evaluateThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new 
+	IllegalArgumentException(request
+
+		    + " not supported (ClustererPerformanceEvaluator)");
+    }
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+  
+  /**
+   * Notify all text listeners of a TextEvent
+   *
+   * @param te a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent te) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying text listeners "
+	//			   +"(ClustererPerformanceEvaluator)");
+	((TextListener)l.elementAt(i)).acceptText(te);
+      }
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).
+	  eventGeneratable("batchClusterer")) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluatorBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluatorBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ClustererPerformanceEvaluatorBeanInfo.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClustererPerformanceEvaluatorBeanInfo.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the clusterer performance evaluator
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 1.2 $
+ */
+public class ClustererPerformanceEvaluatorBeanInfo extends SimpleBeanInfo {
+  
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(ClustererPerformanceEvaluator.class, 
+			       "text", 
+			       TextListener.class, 
+			       "acceptText")};
+	
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationEvent.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConfigurationEvent.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventObject;
+
+/**
+ * Matching event for ConfigurationListener. Implementers of
+ * ConfigurationProducer do not actually have to generate this
+ * event (nor will listeners ever need to process ConfigurationEvent).
+ * Configurations will be pulled (rather than pushed) by
+ * ConfigurationListeners. It is a listener's responsibility (if
+ * they are interested in utilizing configurations) to implement
+ * BeanCommon and store/delete reference(s) to ConfigurationProducers 
+ * when connectionNotification() and disconnectionNotification() are
+ * called on them. 
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision $
+ */
+public class ConfigurationEvent extends EventObject {
+
+  /** For serialization */
+  private static final long serialVersionUID = 5433562112093780868L;
+
+  public ConfigurationEvent(Object source) {
+    super(source);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationListener.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConfigurationListener.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Matching listener for ConfigurationEvent. Implementers of
+ * ConfigurationProducer do not actually have to generate the
+ * event (nor will listeners ever need to process ConfigurationEvent).
+ * Configurations will be pulled (rather than pushed) by
+ * ConfigurationListeners. It is a listener's responsibility (if
+ * they are interested in utilizing configurations) to implement
+ * BeanCommon and store/delete reference(s) to ConfigurationProducers 
+ * when connectionNotification() and disconnectionNotification() are
+ * called on them.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision $
+ */
+public interface ConfigurationListener {
+  
+  /**
+   * Implementers do not have to do anything in this
+   * method (see the above documentation).
+   * 
+   * @param e a ConfigurationEvent
+   */
+  void acceptConfiguration(ConfigurationEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ConfigurationProducer.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConfigurationProducer.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Marker interface for components that can share their configuration.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision $
+ */
+public interface ConfigurationProducer {
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  void addConfigurationListener(ConfigurationListener cl);
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  void removeConfigurationListener(ConfigurationListener cl);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ConnectionNotificationConsumer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ConnectionNotificationConsumer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ConnectionNotificationConsumer.java	(revision 29)
@@ -0,0 +1,62 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ConnectionNotificationConsumer.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.beans;
+
+/**
+ * Interface for Beans that can receive (dis-)connection events generated when
+ * (dis-)connecting data processing nodes in the Weka KnowledgeFlow.
+ * 
+ * This is useful, for example, for "intelligent" filters that are able to share
+ * configuration information with preceding nodes in the processing chain.
+ * 
+ * @author Carsten Pohle (cp AT cpohle de)
+ * @version $Revision: 1.2 $
+ */
+public interface ConnectionNotificationConsumer {
+  /**
+   * Notify this object that it has been registered as a listener with a source
+   * with respect to the supplied event name.
+   * 
+   * This method should be implemented <emph>synchronized</emph>.
+   * 
+   * @param eventName
+   * @param source
+   *          the source with which this object has been registered as a
+   *          listener
+   */
+  public void connectionNotification(String eventName, Object source);
+
+  /**
+   * Notify this object that it has been deregistered as a listener with a
+   * source with respect to the supplied event name
+   * 
+   * This method should be implemented <emph>synchronized</emph>.
+   * 
+   * @param eventName
+   *          the event
+   * @param source
+   *          the source with which this object has been registered as a
+   *          listener
+   */
+  public void disconnectionNotification(String eventName, Object source);
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CostBenefitAnalysis.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CostBenefitAnalysis.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CostBenefitAnalysis.java	(revision 29)
@@ -0,0 +1,1253 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CostBenefitAnalysis.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.beans.EventSetDescriptor;
+import java.beans.PropertyVetoException;
+import java.beans.VetoableChangeListener;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.Logger;
+import weka.gui.visualize.VisualizePanel;
+import weka.gui.visualize.Plot2D;
+import weka.gui.visualize.PlotData2D;
+
+
+/**
+ * Bean that aids in analyzing cost/benefit tradeoffs.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 6137 $
+ */
+public class CostBenefitAnalysis extends JPanel 
+  implements BeanCommon, ThresholdDataListener, Visible, UserRequestAcceptor,
+  Serializable, BeanContextChild {
+  
+  /** For serialization */
+  private static final long serialVersionUID = 8647471654613320469L;
+
+  protected BeanVisual m_visual;
+  
+  protected transient JFrame m_popupFrame;
+
+  protected boolean m_framePoppedUp = false;
+  
+  private transient AnalysisPanel m_analysisPanel;
+  
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+  
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+  
+  /**
+   * The object sending us data (we allow only one connection at any one time)
+   */
+  protected Object m_listenee;
+  
+  /**
+   * Inner class for displaying the plots and all control widgets.
+   * 
+   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+   */
+  protected static class AnalysisPanel extends JPanel {
+    
+    /** For serialization */
+    private static final long serialVersionUID = 5364871945448769003L;
+
+    /** Displays the performance graphs(s) */
+    protected VisualizePanel m_performancePanel = new VisualizePanel();
+    
+    /** Displays the cost/benefit (profit/loss) graph */
+    protected VisualizePanel m_costBenefitPanel = new VisualizePanel();
+    
+    /** 
+     * The class attribute from the data that was used to generate
+     * the threshold curve
+     */
+    protected Attribute m_classAttribute;
+    
+    /** Data for the threshold curve */
+    protected PlotData2D m_masterPlot;
+    
+    /** Data for the cost/benefit curve */
+    protected PlotData2D m_costBenefit;
+    
+    /** The size of the points being plotted */
+    protected int[] m_shapeSizes;
+    
+    /** The index of the previous plotted point that was highlighted */
+    protected int m_previousShapeIndex = -1;
+        
+    /** The slider for adjusting the threshold */
+    protected JSlider m_thresholdSlider = new JSlider(0,100,0);
+    
+    protected JRadioButton m_percPop = new JRadioButton("% of Population");
+    protected JRadioButton m_percOfTarget = new JRadioButton("% of Target (recall)");
+    protected JRadioButton m_threshold = new JRadioButton("Score Threshold");
+    
+    protected JLabel m_percPopLab = new JLabel();
+    protected JLabel m_percOfTargetLab = new JLabel();
+    protected JLabel m_thresholdLab = new JLabel();
+    
+    // Confusion matrix stuff
+    protected JLabel m_conf_predictedA = new JLabel("Predicted (a)", SwingConstants.RIGHT);
+    protected JLabel m_conf_predictedB = new JLabel("Predicted (b)", SwingConstants.RIGHT);
+    protected JLabel m_conf_actualA = new JLabel(" Actual (a):");
+    protected JLabel m_conf_actualB = new JLabel(" Actual (b):");
+    protected ConfusionCell m_conf_aa = new ConfusionCell();
+    protected ConfusionCell m_conf_ab = new ConfusionCell();
+    protected ConfusionCell m_conf_ba = new ConfusionCell();
+    protected ConfusionCell m_conf_bb = new ConfusionCell();
+    
+    // Cost matrix stuff
+    protected JLabel m_cost_predictedA = new JLabel("Predicted (a)", SwingConstants.RIGHT);
+    protected JLabel m_cost_predictedB = new JLabel("Predicted (b)", SwingConstants.RIGHT);
+    protected JLabel m_cost_actualA = new JLabel(" Actual (a)");
+    protected JLabel m_cost_actualB = new JLabel(" Actual (b)");
+    protected JTextField m_cost_aa = new JTextField("0.0", 5);
+    protected JTextField m_cost_ab = new JTextField("1.0", 5);
+    protected JTextField m_cost_ba = new JTextField("1.0", 5);
+    protected JTextField m_cost_bb = new JTextField("0.0" ,5);
+    protected JButton m_maximizeCB = new JButton("Maximize Cost/Benefit");
+    protected JButton m_minimizeCB = new JButton("Minimize Cost/Benefit");
+    protected JRadioButton m_costR = new JRadioButton("Cost");
+    protected JRadioButton m_benefitR = new JRadioButton("Benefit");
+    protected JLabel m_costBenefitL = new JLabel("Cost: ", SwingConstants.RIGHT);
+    protected JLabel m_costBenefitV = new JLabel("0");
+    protected JLabel m_randomV = new JLabel("0");
+    protected JLabel m_gainV = new JLabel("0");
+    
+    protected int m_originalPopSize;
+    
+    /** Population text field */
+    protected JTextField m_totalPopField = new JTextField(6);
+    protected int m_totalPopPrevious;
+    
+    /** Classification accuracy */
+    protected JLabel m_classificationAccV = new JLabel("-");
+    
+    // Only update curve & stats if values in cost matrix have changed
+    protected double m_tpPrevious;
+    protected double m_fpPrevious;
+    protected double m_tnPrevious;
+    protected double m_fnPrevious;
+    
+    /**
+     * Inner class for handling a single cell in the confusion matrix.
+     * Displays the value, value as a percentage of total population and
+     * graphical depiction of percentage.
+     * 
+     * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+     */
+    protected static class ConfusionCell extends JPanel {
+
+      /** For serialization */
+      private static final long serialVersionUID = 6148640235434494767L;
+      
+      private JLabel m_conf_cell = new JLabel("-", SwingConstants.RIGHT);
+      JLabel m_conf_perc = new JLabel("-", SwingConstants.RIGHT);
+      
+      private JPanel m_percentageP;
+      
+      protected double m_percentage = 0;
+      
+      public ConfusionCell() {
+        setLayout(new BorderLayout());
+        setBorder(BorderFactory.createEtchedBorder());
+        
+        add(m_conf_cell, BorderLayout.NORTH);
+        
+        m_percentageP = new JPanel() {
+          public void paintComponent(Graphics gx) {
+            super.paintComponent(gx);
+            
+            if (m_percentage > 0) {
+              gx.setColor(Color.BLUE);
+              int height = this.getHeight();
+              double width = this.getWidth();
+              int barWidth = (int)(m_percentage * width); 
+              gx.fillRect(0, 0, barWidth, height);
+            }
+          }
+        };
+        
+        Dimension d = new Dimension(30,5);
+        m_percentageP.setMinimumSize(d);
+        m_percentageP.setPreferredSize(d);
+        JPanel percHolder = new JPanel();
+        percHolder.setLayout(new BorderLayout());
+        percHolder.add(m_percentageP, BorderLayout.CENTER);
+        percHolder.add(m_conf_perc, BorderLayout.EAST);
+        
+        add(percHolder, BorderLayout.SOUTH);
+      }
+      
+      /**
+       * Set the value of a cell.
+       * 
+       * @param cellValue the value of the cell
+       * @param max the max (for setting value as a percentage)
+       * @param scaleFactor scale the value by this amount
+       * @param precision precision for the percentage value
+       */
+      public void setCellValue(double cellValue, double max, double scaleFactor, int precision) {
+        if (!Utils.isMissingValue(cellValue)) {
+          m_percentage = cellValue / max;
+        } else {
+          m_percentage = 0;
+        }
+        
+        m_conf_cell.setText(Utils.doubleToString((cellValue * scaleFactor), 0));
+        m_conf_perc.setText(Utils.doubleToString(m_percentage * 100.0, precision) + "%");
+        
+        // refresh the percentage bar
+        m_percentageP.repaint();
+      }
+    }
+    
+    public AnalysisPanel() {
+      setLayout(new BorderLayout());
+      m_performancePanel.setShowAttBars(false);
+      m_performancePanel.setShowClassPanel(false);
+      m_costBenefitPanel.setShowAttBars(false);
+      m_costBenefitPanel.setShowClassPanel(false);
+      
+      Dimension size = new Dimension(500, 400);
+      m_performancePanel.setPreferredSize(size);
+      m_performancePanel.setMinimumSize(size);
+      
+      size = new Dimension(500, 400);
+      m_costBenefitPanel.setMinimumSize(size);
+      m_costBenefitPanel.setPreferredSize(size);
+      
+      m_thresholdSlider.addChangeListener(new ChangeListener() {
+        public void stateChanged(ChangeEvent e) {
+          updateInfoForSliderValue((double)m_thresholdSlider.getValue() / 100.0);
+        }
+      });
+      
+      JPanel plotHolder = new JPanel();
+      plotHolder.setLayout(new GridLayout(1,2));      
+      plotHolder.add(m_performancePanel);
+      plotHolder.add(m_costBenefitPanel);
+      add(plotHolder, BorderLayout.CENTER);
+      
+      JPanel lowerPanel = new JPanel();
+      lowerPanel.setLayout(new BorderLayout());
+      
+      ButtonGroup bGroup = new ButtonGroup();
+      bGroup.add(m_percPop);
+      bGroup.add(m_percOfTarget);
+      bGroup.add(m_threshold);
+      
+      ButtonGroup bGroup2 = new ButtonGroup();
+      bGroup2.add(m_costR);
+      bGroup2.add(m_benefitR);
+      ActionListener rl = new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          if (m_costR.isSelected()) {
+            m_costBenefitL.setText("Cost: ");
+          } else {
+            m_costBenefitL.setText("Benefit: ");
+          }
+
+          double gain = Double.parseDouble(m_gainV.getText());
+          gain = -gain;
+          m_gainV.setText(Utils.doubleToString(gain, 2));
+        }
+      };
+      m_costR.addActionListener(rl);
+      m_benefitR.addActionListener(rl);
+      m_costR.setSelected(true);
+      
+      m_percPop.setSelected(true);
+      JPanel threshPanel = new JPanel();
+      threshPanel.setLayout(new BorderLayout());
+      JPanel radioHolder = new JPanel();
+      radioHolder.setLayout(new FlowLayout());
+      radioHolder.add(m_percPop);
+      radioHolder.add(m_percOfTarget);
+      radioHolder.add(m_threshold);
+      threshPanel.add(radioHolder, BorderLayout.NORTH);
+      threshPanel.add(m_thresholdSlider, BorderLayout.SOUTH);
+      
+      JPanel threshInfoPanel = new JPanel();
+      threshInfoPanel.setLayout(new GridLayout(3,2));
+      threshInfoPanel.add(new JLabel("% of Population: ", SwingConstants.RIGHT));
+      threshInfoPanel.add(m_percPopLab);
+      threshInfoPanel.add(new JLabel("% of Target: ", SwingConstants.RIGHT));
+      threshInfoPanel.add(m_percOfTargetLab);
+      threshInfoPanel.add(new JLabel("Score Threshold: ", SwingConstants.RIGHT));
+      threshInfoPanel.add(m_thresholdLab);
+      
+      JPanel threshHolder = new JPanel();
+      threshHolder.setBorder(BorderFactory.createTitledBorder("Threshold"));
+      threshHolder.setLayout(new BorderLayout());
+      threshHolder.add(threshPanel, BorderLayout.CENTER);
+      threshHolder.add(threshInfoPanel, BorderLayout.EAST);
+      
+      lowerPanel.add(threshHolder, BorderLayout.NORTH);
+      
+      // holder for the two matrixes
+      JPanel matrixHolder = new JPanel();
+      matrixHolder.setLayout(new GridLayout(1,2));
+      
+      // confusion matrix
+      JPanel confusionPanel = new JPanel();
+      confusionPanel.setLayout(new GridLayout(3,3));
+      confusionPanel.add(m_conf_predictedA);
+      confusionPanel.add(m_conf_predictedB);
+      confusionPanel.add(new JLabel()); // dummy
+      confusionPanel.add(m_conf_aa);
+      confusionPanel.add(m_conf_ab);
+      confusionPanel.add(m_conf_actualA);
+      confusionPanel.add(m_conf_ba);
+      confusionPanel.add(m_conf_bb);
+      confusionPanel.add(m_conf_actualB);
+      JPanel tempHolderCA = new JPanel();
+      tempHolderCA.setLayout(new BorderLayout());
+      tempHolderCA.setBorder(BorderFactory.createTitledBorder("Confusion Matrix"));
+      tempHolderCA.add(confusionPanel, BorderLayout.CENTER);
+      
+      JPanel accHolder = new JPanel();
+      accHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
+      accHolder.add(new JLabel("Classification Accuracy: "));
+      accHolder.add(m_classificationAccV);
+      tempHolderCA.add(accHolder, BorderLayout.SOUTH);
+      
+      matrixHolder.add(tempHolderCA);
+      
+      // cost matrix
+      JPanel costPanel = new JPanel();
+      costPanel.setBorder(BorderFactory.createTitledBorder("Cost Matrix"));
+      costPanel.setLayout(new BorderLayout());
+      
+      JPanel cmHolder = new JPanel();
+      cmHolder.setLayout(new GridLayout(3, 3));
+      cmHolder.add(m_cost_predictedA);      
+      cmHolder.add(m_cost_predictedB);
+      cmHolder.add(new JLabel()); // dummy
+      cmHolder.add(m_cost_aa);
+      cmHolder.add(m_cost_ab);
+      cmHolder.add(m_cost_actualA);
+      cmHolder.add(m_cost_ba);
+      cmHolder.add(m_cost_bb);
+      cmHolder.add(m_cost_actualB);
+      costPanel.add(cmHolder, BorderLayout.CENTER);
+      
+      FocusListener fl = new FocusListener() {
+        public void focusGained(FocusEvent e) {
+          
+        }
+        
+        public void focusLost(FocusEvent e) {
+          if (constructCostBenefitData()) {
+            try {
+              m_costBenefitPanel.setMasterPlot(m_costBenefit);
+              m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
+            } catch (Exception ex) {
+              ex.printStackTrace();
+            }
+            updateCostBenefit();
+          }
+        }
+      };
+      
+      ActionListener al = new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          if (constructCostBenefitData()) {
+            try {
+              m_costBenefitPanel.setMasterPlot(m_costBenefit);
+              m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
+            } catch (Exception ex) {
+              ex.printStackTrace();
+            }
+            updateCostBenefit();
+          }
+        }
+      };
+            
+      m_cost_aa.addFocusListener(fl);
+      m_cost_aa.addActionListener(al);
+      m_cost_ab.addFocusListener(fl);
+      m_cost_ab.addActionListener(al);
+      m_cost_ba.addFocusListener(fl);
+      m_cost_ba.addActionListener(al);
+      m_cost_bb.addFocusListener(fl);
+      m_cost_bb.addActionListener(al);
+      
+      m_totalPopField.addFocusListener(fl);
+      m_totalPopField.addActionListener(al);
+      
+      JPanel cbHolder = new JPanel();
+      cbHolder.setLayout(new BorderLayout());
+      JPanel tempP = new JPanel();
+      tempP.setLayout(new GridLayout(3, 2));
+      tempP.add(m_costBenefitL);
+      tempP.add(m_costBenefitV);
+      tempP.add(new JLabel("Random: ", SwingConstants.RIGHT));
+      tempP.add(m_randomV);
+      tempP.add(new JLabel("Gain: ", SwingConstants.RIGHT));
+      tempP.add(m_gainV);
+      cbHolder.add(tempP, BorderLayout.NORTH);
+      JPanel butHolder = new JPanel();
+      butHolder.setLayout(new GridLayout(2, 1));
+      butHolder.add(m_maximizeCB);
+      butHolder.add(m_minimizeCB);
+      m_maximizeCB.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          findMaxMinCB(true);
+        }
+      });
+      
+      m_minimizeCB.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          findMaxMinCB(false);
+        }
+      });
+      
+      cbHolder.add(butHolder, BorderLayout.SOUTH);
+      costPanel.add(cbHolder, BorderLayout.EAST);
+      
+      JPanel popCBR = new JPanel();
+      popCBR.setLayout(new GridLayout(1, 2));
+      JPanel popHolder = new JPanel();
+      popHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
+      popHolder.add(new JLabel("Total Population: "));
+      popHolder.add(m_totalPopField);
+      
+      JPanel radioHolder2 = new JPanel();
+      radioHolder2.setLayout(new FlowLayout(FlowLayout.RIGHT));
+      radioHolder2.add(m_costR);
+      radioHolder2.add(m_benefitR);
+      popCBR.add(popHolder);
+      popCBR.add(radioHolder2);
+      
+      costPanel.add(popCBR, BorderLayout.SOUTH);
+      
+      matrixHolder.add(costPanel);
+      
+      
+      lowerPanel.add(matrixHolder, BorderLayout.SOUTH);
+      
+
+
+//      popAccHolder.add(popHolder);
+      
+      //popAccHolder.add(accHolder);
+      
+      /*JPanel lowerPanel2 = new JPanel();
+      lowerPanel2.setLayout(new BorderLayout());
+      lowerPanel2.add(lowerPanel, BorderLayout.NORTH);
+      lowerPanel2.add(popAccHolder, BorderLayout.SOUTH); */
+      
+      add(lowerPanel, BorderLayout.SOUTH);
+      
+    }
+    
+    private void findMaxMinCB(boolean max) {
+      double maxMin = (max) 
+      ? Double.NEGATIVE_INFINITY 
+          : Double.POSITIVE_INFINITY;
+      
+      Instances cBCurve = m_costBenefit.getPlotInstances();
+      int maxMinIndex = 0;
+      
+      for (int i = 0; i < cBCurve.numInstances(); i++) {
+        Instance current = cBCurve.instance(i);
+        if (max) {
+          if (current.value(1) > maxMin) {
+            maxMin = current.value(1);
+            maxMinIndex = i;
+          }
+        } else {
+          if (current.value(1) < maxMin) {
+            maxMin = current.value(1);
+            maxMinIndex = i;
+          }
+        }
+      }
+      
+      
+      // set the slider to the correct position
+      int indexOfSampleSize = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
+      int indexOfPercOfTarget = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
+      int indexOfThreshold =
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
+      int indexOfMetric;
+      
+      if (m_percPop.isSelected()) {
+        indexOfMetric = indexOfSampleSize;           
+      } else if (m_percOfTarget.isSelected()) {
+        indexOfMetric = indexOfPercOfTarget;
+      } else {
+        indexOfMetric = indexOfThreshold;
+      }
+      
+      double valueOfMetric = m_masterPlot.getPlotInstances().instance(maxMinIndex).value(indexOfMetric);
+      valueOfMetric *= 100.0;
+      
+      // set the approximate location of the slider
+      m_thresholdSlider.setValue((int)valueOfMetric);
+      
+      // make sure the actual values relate to the true min/max rather
+      // than being off due to slider location error.
+      updateInfoGivenIndex(maxMinIndex);
+    }
+    
+    private void updateCostBenefit() {
+      double value = (double)m_thresholdSlider.getValue() / 100.0;
+      Instances plotInstances = m_masterPlot.getPlotInstances();
+      int indexOfSampleSize = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
+      int indexOfPercOfTarget = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
+      int indexOfThreshold =
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
+      int indexOfMetric;
+      
+      if (m_percPop.isSelected()) {
+        indexOfMetric = indexOfSampleSize;           
+      } else if (m_percOfTarget.isSelected()) {
+        indexOfMetric = indexOfPercOfTarget;
+      } else {
+        indexOfMetric = indexOfThreshold;
+      }
+      
+      int index = findIndexForValue(value, plotInstances, indexOfMetric);
+      updateCBRandomGainInfo(index);
+    }
+    
+    private void updateCBRandomGainInfo(int index) {
+      double requestedPopSize = m_originalPopSize;
+      try {
+        requestedPopSize = Double.parseDouble(m_totalPopField.getText());
+      } catch (NumberFormatException e) {}
+      double scaleFactor = requestedPopSize / m_originalPopSize;
+      
+      double CB = m_costBenefit.
+        getPlotInstances().instance(index).value(1);
+      m_costBenefitV.setText(Utils.doubleToString(CB,2));
+      
+      double totalRandomCB = 0.0;
+      Instance first = m_masterPlot.getPlotInstances().instance(0);
+      double totalPos = first.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.TRUE_POS_NAME).index()) * scaleFactor;
+      double totalNeg = first.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.FALSE_POS_NAME)) * scaleFactor;
+
+      double posInSample = (totalPos * (Double.parseDouble(m_percPopLab.getText()) / 100.0));
+      double negInSample = (totalNeg * (Double.parseDouble(m_percPopLab.getText()) / 100.0));
+      double posOutSample = totalPos - posInSample;
+      double negOutSample = totalNeg - negInSample;
+      
+      double tpCost = 0.0;
+      try {
+        tpCost = Double.parseDouble(m_cost_aa.getText());
+      } catch (NumberFormatException n) {}
+      double fpCost = 0.0;
+      try {
+        fpCost = Double.parseDouble(m_cost_ba.getText());
+      } catch (NumberFormatException n) {}
+      double tnCost = 0.0;
+      try {
+        tnCost = Double.parseDouble(m_cost_bb.getText());
+      } catch (NumberFormatException n) {}
+      double fnCost = 0.0;
+      try {
+        fnCost = Double.parseDouble(m_cost_ab.getText());
+      } catch (NumberFormatException n) {}
+            
+      totalRandomCB += posInSample * tpCost;
+      totalRandomCB += negInSample * fpCost;
+      totalRandomCB += posOutSample * fnCost;
+      totalRandomCB += negOutSample * tnCost;
+      
+      m_randomV.setText(Utils.doubleToString(totalRandomCB, 2));
+      double gain = (m_costR.isSelected()) 
+      ? totalRandomCB - CB
+          : CB - totalRandomCB;
+      m_gainV.setText(Utils.doubleToString(gain, 2));
+      
+      // update classification rate
+      Instance currentInst = m_masterPlot.getPlotInstances().instance(index);
+      double tp = currentInst.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.TRUE_POS_NAME).index());
+      double tn = currentInst.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.TRUE_NEG_NAME).index());
+      m_classificationAccV.
+        setText(Utils.doubleToString((tp + tn) / (totalPos + totalNeg) * 100.0, 4) + "%");      
+    }
+    
+    private void updateInfoGivenIndex(int index) {
+      Instances plotInstances = m_masterPlot.getPlotInstances();
+      int indexOfSampleSize = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
+      int indexOfPercOfTarget = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
+      int indexOfThreshold =
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
+      
+      // update labels
+      m_percPopLab.setText(Utils.
+          doubleToString(100.0 * plotInstances.instance(index).value(indexOfSampleSize), 4));
+      m_percOfTargetLab.setText(Utils.doubleToString(
+          100.0 * plotInstances.instance(index).value(indexOfPercOfTarget), 4));
+      m_thresholdLab.setText(Utils.doubleToString(plotInstances.instance(index).value(indexOfThreshold), 4));
+      /*if (m_percPop.isSelected()) {
+        m_percPopLab.setText(Utils.doubleToString(100.0 * value, 4));
+      } else if (m_percOfTarget.isSelected()) {
+        m_percOfTargetLab.setText(Utils.doubleToString(100.0 * value, 4));
+      } else {
+        m_thresholdLab.setText(Utils.doubleToString(value, 4));
+      }*/
+      
+      // Update the highlighted point on the graphs */
+      if (m_previousShapeIndex >= 0) {
+        m_shapeSizes[m_previousShapeIndex] = 1;
+      }
+     
+      m_shapeSizes[index] = 10;
+      m_previousShapeIndex = index;
+      
+      // Update the confusion matrix
+//      double totalInstances = 
+      int tp = plotInstances.attribute(ThresholdCurve.TRUE_POS_NAME).index();
+      int fp = plotInstances.attribute(ThresholdCurve.FALSE_POS_NAME).index();
+      int tn = plotInstances.attribute(ThresholdCurve.TRUE_NEG_NAME).index();
+      int fn = plotInstances.attribute(ThresholdCurve.FALSE_NEG_NAME).index();
+      Instance temp = plotInstances.instance(index);
+      double totalInstances = temp.value(tp) + temp.value(fp) + temp.value(tn) + temp.value(fn);
+      // get the value out of the total pop field (if possible)
+      double requestedPopSize = totalInstances;
+      try {
+        requestedPopSize = Double.parseDouble(m_totalPopField.getText());
+      } catch (NumberFormatException e) {}
+      
+      m_conf_aa.setCellValue(temp.value(tp), totalInstances, 
+          requestedPopSize / totalInstances, 2);
+      m_conf_ab.setCellValue(temp.value(fn), totalInstances, 
+          requestedPopSize / totalInstances, 2);
+      m_conf_ba.setCellValue(temp.value(fp), totalInstances, 
+          requestedPopSize / totalInstances, 2);
+      m_conf_bb.setCellValue(temp.value(tn), totalInstances, 
+            requestedPopSize / totalInstances, 2);
+      
+      updateCBRandomGainInfo(index);
+      
+      repaint();
+    }
+    
+    private void updateInfoForSliderValue(double value) {
+      int indexOfSampleSize = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
+      int indexOfPercOfTarget = 
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
+      int indexOfThreshold =
+        m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
+      int indexOfMetric;
+      
+      if (m_percPop.isSelected()) {
+        indexOfMetric = indexOfSampleSize;           
+      } else if (m_percOfTarget.isSelected()) {
+        indexOfMetric = indexOfPercOfTarget;
+      } else {
+        indexOfMetric = indexOfThreshold;
+      }
+      
+      Instances plotInstances = m_masterPlot.getPlotInstances();
+      int index = findIndexForValue(value, plotInstances, indexOfMetric);
+      updateInfoGivenIndex(index);
+    }
+    
+    private int findIndexForValue(double value, Instances plotInstances, int indexOfMetric) {
+      // binary search
+      // threshold curve is sorted ascending in the threshold (thus
+      // descending for recall and pop size)
+      int index = -1;
+      int lower = 0;
+      int upper = plotInstances.numInstances() - 1;
+      int mid = (upper - lower) / 2;
+      boolean done = false;
+      while (!done) {
+        if (upper - lower <= 1) {
+          
+          // choose the one closest to the value
+          double comp1 = plotInstances.instance(upper).value(indexOfMetric);
+          double comp2 = plotInstances.instance(lower).value(indexOfMetric);
+          if (Math.abs(comp1 - value) < Math.abs(comp2 - value)) {
+            index = upper;
+          } else {
+            index = lower;
+          }
+          
+          break;
+        }
+        double comparisonVal = plotInstances.instance(mid).value(indexOfMetric);
+        if (value > comparisonVal) {
+          if (m_threshold.isSelected()) {
+            lower = mid;
+            mid += (upper - lower) / 2;
+          } else {
+            upper = mid;
+            mid -= (upper - lower) / 2;
+          }
+        } else if (value < comparisonVal) {
+          if (m_threshold.isSelected()) {
+            upper = mid;
+            mid -= (upper - lower) / 2;
+          } else {
+            lower = mid;
+            mid += (upper - lower) / 2;
+          }
+        } else {
+          index = mid;
+          done = true;
+        }
+      }
+      
+      // now check for ties in the appropriate direction
+      if (!m_threshold.isSelected()) {
+        while (index + 1 < plotInstances.numInstances()) {
+          if (plotInstances.instance(index + 1).value(indexOfMetric) == 
+            plotInstances.instance(index).value(indexOfMetric)) {
+            index++;
+          } else {
+            break;
+          }
+        }
+      } else {
+        while (index - 1 >= 0) {
+          if (plotInstances.instance(index - 1).value(indexOfMetric) == 
+            plotInstances.instance(index).value(indexOfMetric)) {
+            index--;
+          } else {
+            break;
+          } 
+        }
+      }
+      return index;
+    }
+    
+    /**
+     * Set the threshold data for the panel to use.
+     * 
+     * @param data PlotData2D object encapsulating the threshold data.
+     * @param classAtt the class attribute from the original data used to generate
+     * the threshold data.
+     * @throws Exception if something goes wrong.
+     */
+    public synchronized void setDataSet(PlotData2D data, Attribute classAtt) throws Exception {      
+      // make a copy of the PlotData2D object
+      m_masterPlot = new PlotData2D(data.getPlotInstances());
+      boolean[] connectPoints = new boolean[m_masterPlot.getPlotInstances().numInstances()];
+      for (int i = 1; i < connectPoints.length; i++) {
+        connectPoints[i] = true;
+      }
+      m_masterPlot.setConnectPoints(connectPoints);
+
+      m_masterPlot.m_alwaysDisplayPointsOfThisSize = 10;
+      setClassForConfusionMatrix(classAtt);
+      m_performancePanel.setMasterPlot(m_masterPlot);
+      m_performancePanel.validate(); m_performancePanel.repaint();
+
+      m_shapeSizes = new int[m_masterPlot.getPlotInstances().numInstances()];
+      for (int i = 0; i < m_shapeSizes.length; i++) {
+        m_shapeSizes[i] = 1;
+      }
+      m_masterPlot.setShapeSize(m_shapeSizes);
+      constructCostBenefitData();
+      m_costBenefitPanel.setMasterPlot(m_costBenefit);
+      m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
+
+      m_totalPopPrevious = 0;
+      m_fpPrevious = 0;
+      m_tpPrevious = 0;
+      m_tnPrevious = 0;
+      m_fnPrevious = 0;
+      m_previousShapeIndex = -1;
+
+      // set the total population size
+      Instance first = m_masterPlot.getPlotInstances().instance(0);
+      double totalPos = first.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.TRUE_POS_NAME).index());
+      double totalNeg = first.value(m_masterPlot.getPlotInstances().
+          attribute(ThresholdCurve.FALSE_POS_NAME));
+      m_originalPopSize = (int)(totalPos + totalNeg);
+      m_totalPopField.setText("" + m_originalPopSize);
+
+      m_performancePanel.setYIndex(5);
+      m_performancePanel.setXIndex(10);
+      m_costBenefitPanel.setXIndex(0);
+      m_costBenefitPanel.setYIndex(1);
+      //      System.err.println(m_masterPlot.getPlotInstances());
+      updateInfoForSliderValue((double)m_thresholdSlider.getValue() / 100.0);
+    }
+    
+    private void setClassForConfusionMatrix(Attribute classAtt) {
+      m_classAttribute = classAtt;
+      m_conf_actualA.setText(" Actual (a): " + classAtt.value(0));
+      m_conf_actualA.setToolTipText(classAtt.value(0));
+      String negClasses = "";
+      for (int i = 1; i < classAtt.numValues(); i++) {
+        negClasses += classAtt.value(i);
+        if (i < classAtt.numValues() - 1) {
+          negClasses += ",";
+        }
+      }
+      m_conf_actualB.setText(" Actual (b): " + negClasses);
+      m_conf_actualB.setToolTipText(negClasses);
+    }
+    
+    private boolean constructCostBenefitData() {
+      double tpCost = 0.0;
+      try {
+        tpCost = Double.parseDouble(m_cost_aa.getText());
+      } catch (NumberFormatException n) {}
+      double fpCost = 0.0;
+      try {
+        fpCost = Double.parseDouble(m_cost_ba.getText());
+      } catch (NumberFormatException n) {}
+      double tnCost = 0.0;
+      try {
+        tnCost = Double.parseDouble(m_cost_bb.getText());
+      } catch (NumberFormatException n) {}
+      double fnCost = 0.0;
+      try {
+        fnCost = Double.parseDouble(m_cost_ab.getText());
+      } catch (NumberFormatException n) {}
+      
+      double requestedPopSize = m_originalPopSize;
+      try {
+        requestedPopSize = Double.parseDouble(m_totalPopField.getText());
+      } catch (NumberFormatException e) {}
+      
+      double scaleFactor = 1.0;
+      if (m_originalPopSize != 0) {
+        scaleFactor = requestedPopSize / m_originalPopSize;
+      }
+      
+      if (tpCost == m_tpPrevious && fpCost == m_fpPrevious &&
+          tnCost == m_tnPrevious && fnCost == m_fnPrevious &&
+          requestedPopSize == m_totalPopPrevious) {
+        return false;
+      }
+      
+      // First construct some Instances for the curve
+      FastVector fv = new FastVector();
+      fv.addElement(new Attribute("Sample Size"));
+      fv.addElement(new Attribute("Cost/Benefit"));
+      Instances costBenefitI = new Instances("Cost/Benefit Curve", fv, 100);
+      
+      // process the performance data to make this curve
+      Instances performanceI = m_masterPlot.getPlotInstances();
+      
+      for (int i = 0; i < performanceI.numInstances(); i++) {
+        Instance current = performanceI.instance(i);
+        
+        double[] vals = new double[2];
+        vals[0] = current.value(10); // sample size
+        vals[1] = (current.value(0) * tpCost
+            + current.value(1) * fnCost
+            + current.value(2) * fpCost
+            + current.value(3) * tnCost) * scaleFactor;
+        Instance newInst = new DenseInstance(1.0, vals);
+        costBenefitI.add(newInst);
+      }
+      
+      costBenefitI.compactify();
+      
+      // now set up the plot data
+      m_costBenefit = new PlotData2D(costBenefitI);
+      m_costBenefit.m_alwaysDisplayPointsOfThisSize = 10;
+      m_costBenefit.setPlotName("Cost/benefit curve");
+      boolean[] connectPoints = new boolean[costBenefitI.numInstances()];
+      
+      for (int i = 0; i < connectPoints.length; i++) {
+        connectPoints[i] = true;
+      }
+      try {
+        m_costBenefit.setConnectPoints(connectPoints);
+        m_costBenefit.setShapeSize(m_shapeSizes);
+      } catch (Exception ex) {
+        // ignore
+      }
+      
+      m_tpPrevious = tpCost;
+      m_fpPrevious = fpCost;
+      m_tnPrevious = tnCost;
+      m_fnPrevious = fnCost;
+      
+      return true;
+    }
+  }
+  
+  /**
+   * Constructor.
+   */
+  public CostBenefitAnalysis() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+  
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Visualize performance charts (such as ROC).";
+  }
+
+  /**
+   * Accept a threshold data event and set up the visualization.
+   * @param e a threshold data event
+   */
+  public void acceptDataSet(ThresholdDataEvent e) {
+    try {
+      setCurveData(e.getDataSet(), e.getClassAttribute());
+    } catch (Exception ex) {
+      System.err.println("[CostBenefitAnalysis] Problem setting up visualization.");
+      ex.printStackTrace();
+    }
+  }
+  
+  /**
+   * Set the threshold curve data to use.
+   * 
+   * @param curveData a PlotData2D object set up with the curve data.
+   * @param origClassAtt the class attribute from the original data used to
+   * generate the curve.
+   * @throws Exception if somthing goes wrong during the setup process.
+   */
+  public void setCurveData(PlotData2D curveData, Attribute origClassAtt) 
+    throws Exception {
+    if (m_analysisPanel == null) {
+      m_analysisPanel = new AnalysisPanel();
+    }
+    m_analysisPanel.setDataSet(curveData, origClassAtt);
+  }
+
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
+        BeanVisual.ICON_PATH+"DefaultDataVisualizer_animated.gif");
+  }
+
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_analysisPanel != null) {
+      if (m_analysisPanel.m_masterPlot != null) {
+        newVector.addElement("Show analysis");
+      }
+    }
+    return newVector.elements();
+  }
+
+  public void performRequest(String request) {
+    if (request.compareTo("Show analysis") == 0) {
+      try {
+        // popup visualize panel
+        if (!m_framePoppedUp) {
+          m_framePoppedUp = true;
+
+          final javax.swing.JFrame jf = 
+            new javax.swing.JFrame("Cost/Benefit Analysis");
+          jf.setSize(1000,600);
+          jf.getContentPane().setLayout(new BorderLayout());
+          jf.getContentPane().add(m_analysisPanel, BorderLayout.CENTER);
+          jf.addWindowListener(new java.awt.event.WindowAdapter() {
+              public void windowClosing(java.awt.event.WindowEvent e) {
+                jf.dispose();
+                m_framePoppedUp = false;
+              }
+            });
+          jf.setVisible(true);
+          m_popupFrame = jf;
+        } else {
+          m_popupFrame.toFront();
+        }
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        m_framePoppedUp = false;
+      }
+    } else {
+      throw new IllegalArgumentException(request
+          + " not supported (Cost/Benefit Analysis");
+    }
+  }
+
+  public void addVetoableChangeListener(String name, VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  public void removeVetoableChangeListener(String name,
+      VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+  
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    setUpFinal();
+  }
+  
+  protected void setUpFinal() {
+    if (m_analysisPanel == null) {
+      m_analysisPanel = new AnalysisPanel();
+    }
+    add(m_analysisPanel, BorderLayout.CENTER);
+  }
+  
+  protected void appearanceDesign() {
+    removeAll();
+    m_visual = new BeanVisual("CostBenefitAnalysis", 
+                              BeanVisual.ICON_PATH+"ModelPerformanceChart.gif",
+                              BeanVisual.ICON_PATH
+                              +"ModelPerformanceChart_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  public void setBeanContext(BeanContext bc) throws PropertyVetoException {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      java.awt.GraphicsEnvironment ge = 
+        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+      if (!ge.isHeadless()) {
+        appearanceFinal();
+      }
+    }
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the named event
+   *
+   * @param eventName the name of the event in question
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source for recieving events described by the named event
+   * This object is responsible for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void connectionNotification(String eventName, Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source for named event. This object is responsible
+   * for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void disconnectionNotification(String eventName, Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+    
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    // we don't need to do any logging    
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   */
+  public void stop() {
+    // nothing to do here
+  }
+    
+  public static void main(String[] args) {
+    try {
+      Instances train = new Instances(new java.io.BufferedReader(new java.io.FileReader(args[0])));
+      train.setClassIndex(train.numAttributes() - 1);
+      weka.classifiers.evaluation.ThresholdCurve tc = 
+        new weka.classifiers.evaluation.ThresholdCurve();
+      weka.classifiers.evaluation.EvaluationUtils eu = 
+        new weka.classifiers.evaluation.EvaluationUtils();
+      //weka.classifiers.Classifier classifier = new weka.classifiers.functions.Logistic();
+      weka.classifiers.Classifier classifier = new weka.classifiers.bayes.NaiveBayes();
+      FastVector predictions = new FastVector();
+      eu.setSeed(1);
+      predictions.appendElements(eu.getCVPredictions(classifier, train, 10));
+      Instances result = tc.getCurve(predictions, 0);
+      PlotData2D pd = new PlotData2D(result);
+      pd.m_alwaysDisplayPointsOfThisSize = 10;
+
+      boolean[] connectPoints = new boolean[result.numInstances()];
+      for (int i = 1; i < connectPoints.length; i++) {
+        connectPoints[i] = true;
+      }
+      pd.setConnectPoints(connectPoints);
+      final javax.swing.JFrame jf = 
+        new javax.swing.JFrame("CostBenefitTest");
+      jf.setSize(1000,600);
+      //jf.pack();
+      jf.getContentPane().setLayout(new BorderLayout());
+      final CostBenefitAnalysis.AnalysisPanel analysisPanel = 
+        new CostBenefitAnalysis.AnalysisPanel();
+      
+      jf.getContentPane().add(analysisPanel, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      
+      jf.setVisible(true);
+      
+      analysisPanel.setDataSet(pd, train.classAttribute());
+      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+ 
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMaker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMaker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMaker.java	(revision 29)
@@ -0,0 +1,462 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CrossValidationFoldMaker.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Bean for splitting instances into training ant test sets according to
+ * a cross validation
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 6003 $
+ */
+public class CrossValidationFoldMaker 
+  extends AbstractTrainAndTestSetProducer
+  implements DataSourceListener, TrainingSetListener, TestSetListener, 
+	     UserRequestAcceptor, EventConstraints, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6350179298851891512L;
+
+  private int m_numFolds = 10;
+  private int m_randomSeed = 1;
+  
+  private boolean m_preserveOrder = false;
+
+  private transient Thread m_foldThread = null;
+
+  public CrossValidationFoldMaker() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"CrossValidationFoldMaker.gif",
+		       BeanVisual.ICON_PATH
+		       +"CrossValidationFoldMaker_animated.gif");
+    m_visual.setText("CrossValidationFoldMaker");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Split an incoming data set into cross validation folds. "
+      +"Separate train and test sets are produced for each of the k folds.";
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    Instances trainingSet = e.getTrainingSet();
+    DataSetEvent dse = new DataSetEvent(this, trainingSet);
+    acceptDataSet(dse);
+  }
+
+  /**
+   * Accept a test set
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public void acceptTestSet(TestSetEvent e) {
+    Instances testSet = e.getTestSet();
+    DataSetEvent dse = new DataSetEvent(this, testSet);
+    acceptDataSet(dse);
+  }
+  
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public void acceptDataSet(DataSetEvent e) {
+    if (e.isStructureOnly()) {
+      // Pass on structure to training and test set listeners
+      TrainingSetEvent tse = new TrainingSetEvent(this, e.getDataSet());
+      TestSetEvent tsee = new TestSetEvent(this, e.getDataSet());
+      notifyTrainingSetProduced(tse);
+      notifyTestSetProduced(tsee);
+      return;
+    }
+    if (m_foldThread == null) {
+      final Instances dataSet = new Instances(e.getDataSet());
+      m_foldThread = new Thread() {
+	  public void run() {
+	    boolean errorOccurred = false;
+	    try {
+	      Random random = new Random(getSeed());
+	      if (!m_preserveOrder) {
+	        dataSet.randomize(random);
+	      }
+	      if (dataSet.classIndex() >= 0 && 
+		  dataSet.attribute(dataSet.classIndex()).isNominal() &&
+		  !m_preserveOrder) {
+		dataSet.stratify(getFolds());
+		if (m_logger != null) {
+		  m_logger.logMessage("[" + getCustomName() + "] "
+				      +"stratifying data");
+		}
+	      }
+	      
+	      for (int i = 0; i < getFolds(); i++) {
+		if (m_foldThread == null) {
+		  if (m_logger != null) {
+		    m_logger.logMessage("[" + getCustomName() + "] Cross validation has been canceled!");
+		  }
+		  // exit gracefully
+		  break;
+		}
+		Instances train = (!m_preserveOrder) 
+		  ? dataSet.trainCV(getFolds(), i, random)
+		  : dataSet.trainCV(getFolds(), i); 
+		Instances test  = dataSet.testCV(getFolds(), i);
+
+		// inform all training set listeners
+		TrainingSetEvent tse = new TrainingSetEvent(this, train);
+		tse.m_setNumber = i+1; tse.m_maxSetNumber = getFolds();
+		String msg = getCustomName() + "$" 
+		  + CrossValidationFoldMaker.this.hashCode() + "|";
+		if (m_logger != null) {
+		  m_logger.statusMessage(msg + "seed: " + getSeed() + " folds: "
+		      + getFolds() + "|Training fold " + (i+1));
+		}
+		if (m_foldThread != null) {
+		  //		  System.err.println("--Just before notify training set");
+		  notifyTrainingSetProduced(tse);
+		  //		  System.err.println("---Just after notify");
+		}
+	      
+		// inform all test set listeners
+		TestSetEvent teste = new TestSetEvent(this, test);
+		teste.m_setNumber = i+1; teste.m_maxSetNumber = getFolds();
+		
+		if (m_logger != null) {
+		  m_logger.statusMessage(msg + "seed: " + getSeed() + " folds: "
+		      + getFolds() + "|Test fold " + (i+1));
+		}
+		if (m_foldThread != null) {
+		  notifyTestSetProduced(teste);
+		}
+	      }
+	    } catch (Exception ex) {
+	      // stop all processing
+	      errorOccurred = true;
+	      if (m_logger != null) {
+	        m_logger.logMessage("[" + getCustomName() 
+	            + "] problem during fold creation. "
+	            + ex.getMessage());
+	      }
+	      ex.printStackTrace();
+	      CrossValidationFoldMaker.this.stop();
+	    } finally {
+	      m_foldThread = null;
+	      
+	      if (errorOccurred) {
+	        if (m_logger != null) {
+	          m_logger.statusMessage(getCustomName() 
+	              + "$" + CrossValidationFoldMaker.this.hashCode()
+	              + "|"
+	              + "ERROR (See log for details).");
+	        }
+	      } else if (isInterrupted()) {
+	        String msg = "[" + getCustomName() + "] Cross validation interrupted";
+	        if (m_logger != null) {
+	          m_logger.logMessage("[" + getCustomName() + "] Cross validation interrupted");
+	          m_logger.statusMessage(getCustomName() + "$"
+	              + CrossValidationFoldMaker.this.hashCode() + "|"
+	              + "INTERRUPTED");
+	        } else {
+	          System.err.println(msg);
+	        }
+	      } else {
+	        String msg = getCustomName() + "$" 
+	        + CrossValidationFoldMaker.this.hashCode() + "|";
+	        if (m_logger != null) {
+	          m_logger.statusMessage(msg + "Finished.");
+	        }
+	      }
+	      block(false);
+	    }
+	  }
+	};
+      m_foldThread.setPriority(Thread.MIN_PRIORITY);
+      m_foldThread.start();
+
+      //      if (m_foldThread.isAlive()) {
+      block(true);
+	//      }
+      m_foldThread = null;
+    }
+  }
+
+
+  /**
+   * Notify all test set listeners of a TestSet event
+   *
+   * @param tse a <code>TestSetEvent</code> value
+   */
+  private void notifyTestSetProduced(TestSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_testListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_foldThread == null) {
+          break;
+        }
+	//	System.err.println("Notifying test listeners "
+	//			   +"(cross validation fold maker)");
+	((TestSetListener)l.elementAt(i)).acceptTestSet(tse);
+      }
+    }
+  }
+
+  /**
+   * Notify all listeners of a TrainingSet event
+   *
+   * @param tse a <code>TrainingSetEvent</code> value
+   */
+  protected void notifyTrainingSetProduced(TrainingSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_trainingListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_foldThread == null) {
+          break;
+        }
+	//	System.err.println("Notifying training listeners "
+	//			   +"(cross validation fold maker)");
+	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(tse);
+      }
+    }
+  }
+
+  /**
+   * Set the number of folds for the cross validation
+   *
+   * @param numFolds an <code>int</code> value
+   */
+  public void setFolds(int numFolds) {
+    m_numFolds = numFolds;
+  }
+  
+  /**
+   * Get the currently set number of folds
+   *
+   * @return an <code>int</code> value
+   */
+  public int getFolds() {
+    return m_numFolds;
+  }
+
+  /**
+   * Tip text for this property
+   *
+   * @return a <code>String</code> value
+   */
+  public String foldsTipText() {
+    return "The number of train and test splits to produce";
+  }
+    
+  /**
+   * Set the seed
+   *
+   * @param randomSeed an <code>int</code> value
+   */
+  public void setSeed(int randomSeed) {
+    m_randomSeed = randomSeed;
+  }
+  
+  /**
+   * Get the currently set seed
+   *
+   * @return an <code>int</code> value
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+  
+  /**
+   * Tip text for this property
+   *
+   * @return a <code>String</code> value
+   */
+  public String seedTipText() {
+    return "The randomization seed";
+  }
+  
+  /**
+   * Returns true if the order of the incoming instances is to
+   * be preserved under cross-validation (no randomization or 
+   * stratification is done in this case).
+   * 
+   * @return true if the order of the incoming instances is to
+   * be preserved.
+   */
+  public boolean getPreserveOrder() {
+    return m_preserveOrder;
+  }
+  
+  /**
+   * Sets whether the order of the incoming instances is to be
+   * preserved under cross-validation (no randomization or 
+   * stratification is done in this case).
+   *  
+   * @param p true if the order is to be preserved.
+   */
+  public void setPreserveOrder(boolean p) {
+    m_preserveOrder = p;
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_foldThread != null);
+  }
+
+  /**
+   * Stop any action
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+
+    // stop the fold thread
+    if (m_foldThread != null) {
+      Thread temp = m_foldThread;
+      m_foldThread = null;
+      temp.interrupt();
+      temp.stop();
+    }
+  }
+
+  /**
+   * Function used to stop code that calls acceptDataSet. This is 
+   * needed as cross validation is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+	// make sure the thread is still running before we block
+	if (m_foldThread != null && m_foldThread.isAlive()) {
+	  wait();
+	}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Return an enumeration of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_foldThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (CrossValidation)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+    
+    if (m_listenee instanceof EventConstraints) {
+      if (((EventConstraints)m_listenee).eventGeneratable("dataSet") ||
+	  ((EventConstraints)m_listenee).eventGeneratable("trainingSet") ||
+	  ((EventConstraints)m_listenee).eventGeneratable("testSet")) {
+	return true;
+      } else {
+	return false;
+      }
+    }
+    return true;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerBeanInfo.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CrossValidationFoldMakerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the cross validation fold maker bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5017 $
+ */
+public class CrossValidationFoldMakerBeanInfo 
+  extends AbstractTrainAndTestSetProducerBeanInfo {
+  
+  /**
+   * Return the property descriptors for this bean
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      PropertyDescriptor p2;
+      PropertyDescriptor p3;
+      p1 = new PropertyDescriptor("folds", CrossValidationFoldMaker.class);
+      p2 = new PropertyDescriptor("seed", CrossValidationFoldMaker.class);
+      p3 = new PropertyDescriptor("preserveOrder", CrossValidationFoldMaker.class);
+      PropertyDescriptor [] pds = { p1, p2, p3 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Return the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.CrossValidationFoldMaker.class,
+			      CrossValidationFoldMakerCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CrossValidationFoldMakerCustomizer.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CrossValidationFoldMakerCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JPanel;
+
+/**
+ * GUI Customizer for the cross validation fold maker bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public class CrossValidationFoldMakerCustomizer
+  extends JPanel
+  implements Customizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1229878140258668581L;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private PropertySheetPanel m_cvEditor = 
+    new PropertySheetPanel();
+
+  public CrossValidationFoldMakerCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+
+    setLayout(new BorderLayout());
+    add(m_cvEditor, BorderLayout.CENTER);
+    add(new javax.swing.JLabel("CrossValidationFoldMakerCustomizer"), 
+	BorderLayout.NORTH);
+  }
+  
+  /**
+   * Set the object to be edited
+   *
+   * @param object a CrossValidationFoldMaker object
+   */
+  public void setObject(Object object) {
+    m_cvEditor.setTarget((CrossValidationFoldMaker)object);
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerCloseRequester.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerCloseRequester.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerCloseRequester.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CustomizerCloseRequester.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import javax.swing.JFrame;
+
+/**
+ * Customizers who want to be able to close the customizer window
+ * themselves can implement this window. The KnowledgeFlow will
+ * pass in the reference to the parent JFrame when constructing
+ * the customizer. The customizer can then call dispose() the
+ * Frame whenever it suits them.
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.2 $
+ */
+public interface CustomizerCloseRequester {
+
+  /**
+   * A reference to the parent is passed in
+   *
+   * @param parent the parent frame
+   */
+  void setParentFrame(JFrame parent);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerClosingListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerClosingListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/CustomizerClosingListener.java	(revision 29)
@@ -0,0 +1,36 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CustomizerClosingListener.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui.beans;
+
+/**
+ * @author Mark Hall
+ * @version $Revision: 1.4 $
+ */
+public interface CustomizerClosingListener {
+
+  /**
+   * Customizer classes that want to know when they are being
+   * disposed of can implement this method.
+   */
+  void customizerClosing();
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataFormatListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataFormatListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataFormatListener.java	(revision 29)
@@ -0,0 +1,37 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package weka.gui.beans;
+
+/**
+ * Listener interface that customizer classes that are interested
+ * in data format changes can implement.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface DataFormatListener {
+  
+  /**
+   * Recieve a DataSetEvent that encapsulates a new data format. The
+   * DataSetEvent may contain null for the encapsulated format. This indicates
+   * that there is no data format available (ie. user may have disconnected
+   * an input source of data in the KnowledgeFlow).
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  void newDataFormat(DataSetEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataSetEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataSetEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataSetEvent.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataSetEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.util.EventObject;
+
+/**
+ * Event encapsulating a data set
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ * @see EventObject
+ */
+public class DataSetEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5111218447577318057L;
+
+  private Instances m_dataSet;
+  private boolean m_structureOnly;
+
+  public DataSetEvent(Object source, Instances dataSet) {
+    super(source);
+    m_dataSet = dataSet;
+    if (m_dataSet != null && m_dataSet.numInstances() == 0) {
+      m_structureOnly = true;
+    }
+  }
+  
+  /**
+   * Return the instances of the data set
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getDataSet() {
+    return m_dataSet;
+  }
+
+  /**
+   * Returns true if the encapsulated instances
+   * contain just header information
+   *
+   * @return true if only header information is
+   * available in this DataSetEvent
+   */
+  public boolean isStructureOnly() {
+    return m_structureOnly;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataSink.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataSink.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataSink.java	(revision 29)
@@ -0,0 +1,35 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataSink.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Indicator interface to something that can store instances to some
+ * destination
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+
+public interface DataSink {
+  // empty at present
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataSource.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataSource.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataSource.java	(revision 29)
@@ -0,0 +1,65 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataSource.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/**
+ * Interface to something that is capable of being a source for data - 
+ * either batch or incremental data
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ */
+public interface DataSource {
+
+  /**
+   * Add a data source listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  void addDataSourceListener(DataSourceListener dsl);
+
+  /**
+   * Remove a data source listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  void removeDataSourceListener(DataSourceListener dsl);
+
+  /**
+   * Add an instance listener
+   *
+   * @param dsl an <code>InstanceListener</code> value
+   */
+  void addInstanceListener(InstanceListener dsl);
+
+  /**
+   * Remove an instance listener
+   *
+   * @param dsl an <code>InstanceListener</code> value
+   */
+  void removeInstanceListener(InstanceListener dsl);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataSourceListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataSourceListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataSourceListener.java	(revision 29)
@@ -0,0 +1,37 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataSourceListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accept DataSetEvents
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface DataSourceListener extends EventListener {
+  void acceptDataSet(DataSetEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizer.java	(revision 29)
@@ -0,0 +1,438 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataVisualizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+import weka.gui.visualize.PlotData2D;
+import weka.gui.visualize.VisualizePanel;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * Bean that encapsulates weka.gui.visualize.VisualizePanel
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5284 $
+ */
+public class DataVisualizer extends JPanel
+  implements DataSourceListener, TrainingSetListener,
+	     TestSetListener, Visible, UserRequestAcceptor, Serializable,
+	     BeanContextChild {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1949062132560159028L;
+
+  protected BeanVisual m_visual;
+
+  protected transient Instances m_visualizeDataSet;
+
+  protected transient JFrame m_popupFrame;
+
+  protected boolean m_framePoppedUp = false;
+
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+
+  private VisualizePanel m_visPanel;
+
+  /**
+   * Objects listening for data set events
+   */
+  private Vector m_dataSetListeners = new Vector();
+  
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+
+  public DataVisualizer() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Visualize incoming data/training/test sets in a 2D scatter plot.";
+  }
+
+  protected void appearanceDesign() {
+    m_visPanel = null;
+    removeAll();
+    m_visual = new BeanVisual("DataVisualizer", 
+			      BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
+			      BeanVisual.ICON_PATH
+			      +"DefaultDataVisualizer_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    
+    removeAll();
+    if (!ge.isHeadless()) {
+      setLayout(new BorderLayout());
+      setUpFinal();
+    }
+  }
+
+  protected void setUpFinal() {
+    if (m_visPanel == null) {
+      m_visPanel = new VisualizePanel();
+    }
+    add(m_visPanel, BorderLayout.CENTER);
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    Instances trainingSet = e.getTrainingSet();
+    DataSetEvent dse = new DataSetEvent(this, trainingSet);
+    acceptDataSet(dse);
+  }
+
+  /**
+   * Accept a test set
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public void acceptTestSet(TestSetEvent e) {
+    Instances testSet = e.getTestSet();
+    DataSetEvent dse = new DataSetEvent(this, testSet);
+    acceptDataSet(dse);
+  }
+
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public synchronized void acceptDataSet(DataSetEvent e) {
+    // ignore structure only events
+    if (e.isStructureOnly()) {
+      return;
+    }
+    m_visualizeDataSet = new Instances(e.getDataSet());
+    if (m_visualizeDataSet.classIndex() < 0) {
+      m_visualizeDataSet.setClassIndex(m_visualizeDataSet.numAttributes()-1);
+    }
+    if (!m_design) {
+      try {
+	setInstances(m_visualizeDataSet);
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+
+    // pass on the event to any listeners
+    notifyDataSetListeners(e);
+  }
+
+  /**
+   * Set the visual appearance of this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Return the visual appearance of this bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
+		       BeanVisual.ICON_PATH+"DefaultDataVisualizer_animated.gif");
+  }
+
+  /**
+   * Describe <code>enumerateRequests</code> method here.
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_visualizeDataSet != null) {
+      newVector.addElement("Show plot");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Add a property change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(String name,
+					PropertyChangeListener pcl) {
+    m_bcSupport.addPropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Remove a property change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(String name,
+					   PropertyChangeListener pcl) {
+    m_bcSupport.removePropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Add a vetoable change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void addVetoableChangeListener(String name,
+				       VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+  
+  /**
+   * Remove a vetoable change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void removeVetoableChangeListener(String name,
+					   VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      java.awt.GraphicsEnvironment ge = 
+        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+      if (!ge.isHeadless()) {
+        appearanceFinal();
+      }
+    }
+  }
+
+  /**
+   * Return the bean context (if any) that this bean is embedded in
+   *
+   * @return a <code>BeanContext</code> value
+   */
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  /**
+   * Set instances for this bean. This method is a convenience method
+   * for clients who use this component programatically
+   *
+   * @param inst an <code>Instances</code> value
+   * @exception Exception if an error occurs
+   */
+  public void setInstances(Instances inst) throws Exception {
+    if (m_design) {
+      throw new Exception("This method is not to be used during design "
+			  +"time. It is meant to be used if this "
+			  +"bean is being used programatically as as "
+			  +"stand alone component.");
+    }
+    m_visualizeDataSet = inst;
+    PlotData2D pd1 = new PlotData2D(m_visualizeDataSet);
+    String relationName = m_visualizeDataSet.relationName();
+    pd1.setPlotName(relationName);
+    try {
+      m_visPanel.setMasterPlot(pd1);
+    } catch (Exception ex) {
+      System.err.println("Problem setting up "
+			 +"visualization (DataVisualizer)");
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Notify all data set listeners of a data set event
+   *
+   * @param ge a <code>DataSetEvent</code> value
+   */
+  private void notifyDataSetListeners(DataSetEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataSetListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((DataSourceListener)l.elementAt(i)).acceptDataSet(ge);
+      }
+    }
+  }
+  
+  /**
+   * Describe <code>performRequest</code> method here.
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show plot") == 0) {
+      try {
+	// popup visualize panel
+	if (!m_framePoppedUp) {
+	  m_framePoppedUp = true;
+	  final VisualizePanel vis = new VisualizePanel();
+	  PlotData2D pd1 = new PlotData2D(m_visualizeDataSet);
+	  
+	  String relationName = m_visualizeDataSet.relationName();
+	  
+	  // A bit of a nasty hack. Allows producers of instances-based
+	  // events to specify that the points should be connected
+	  if (relationName.startsWith("__")) {
+	    boolean[] connect = new boolean[m_visualizeDataSet.numInstances()];
+	    for (int i = 1; i < connect.length; i++) { connect[i] = true; }
+	    pd1.setConnectPoints(connect);
+	    relationName = relationName.substring(2);
+	  }
+	  pd1.setPlotName(relationName);
+	  try {
+	    vis.setMasterPlot(pd1);
+	  } catch (Exception ex) {
+	    System.err.println("Problem setting up "
+			       +"visualization (DataVisualizer)");
+	    ex.printStackTrace();
+	  }
+	  final JFrame jf = new JFrame("Visualize");
+	  jf.setSize(800,600);
+	  jf.getContentPane().setLayout(new BorderLayout());
+	  jf.getContentPane().add(vis, BorderLayout.CENTER);
+	  jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	      public void windowClosing(java.awt.event.WindowEvent e) {
+		jf.dispose();
+		m_framePoppedUp = false;
+	      }
+	    });
+	  jf.setVisible(true);
+	  m_popupFrame = jf;
+	} else {
+	  m_popupFrame.toFront();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	m_framePoppedUp = false;
+      }
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (DataVisualizer)");
+    }
+  }
+
+  /**
+   * Add a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void addDataSourceListener(DataSourceListener dsl) {
+    m_dataSetListeners.addElement(dsl);
+  }
+
+  /**
+   * Remove a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void removeDataSourceListener(DataSourceListener dsl) {
+    m_dataSetListeners.remove(dsl);
+  }
+
+  public static void main(String [] args) {
+    try {
+      if (args.length != 1) {
+	System.err.println("Usage: DataVisualizer <dataset>");
+	System.exit(1);
+      }
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      Instances inst = new Instances(r);
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      final DataVisualizer as = new DataVisualizer();
+      as.setInstances(inst);
+      
+      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/DataVisualizerBeanInfo.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DataVisualizerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the data visualizer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public class DataVisualizerBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+        new EventSetDescriptor(DataVisualizer.class,
+                               "dataSet",
+                               DataSourceListener.class,
+                               "acceptDataSet")
+      };      
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/EnvironmentField.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/EnvironmentField.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/EnvironmentField.java	(revision 29)
@@ -0,0 +1,289 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EnvironmentField.java
+ *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+import weka.core.Environment;
+
+/**
+ * Widget that displays a label and a combo box for selecting
+ * environment variables. The enter arbitrary text, select an
+ * environment variable or a combination of both. Any variables
+ * are resolved (if possible) and resolved values are displayed
+ * in a tip-text.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class EnvironmentField extends JPanel {
+  
+  /** For serialization */
+  private static final long serialVersionUID = -3125404573324734121L;
+
+  /** The label for the widget */
+  protected JLabel m_label;
+  
+  /** The combo box */
+  protected JComboBox m_combo;
+  
+  /** The current environment variables */
+  protected Environment m_env;
+  
+  protected String m_currentContents = "";
+  protected int m_firstCaretPos = 0;
+  protected int m_previousCaretPos = 0;
+  protected int m_currentCaretPos = 0;
+  
+  /**
+   * Construct an EnvironmentField with no label.
+   */
+  public EnvironmentField() {
+    this("");
+  }
+  
+  /**
+   * Construct an EnvironmentField with no label.
+   * @param env the environment variables to display in
+   * the drop-down box
+   */
+  public EnvironmentField(Environment env) {
+    this("");
+    setEnvironment(env);    
+  }
+  
+  /**
+   * Constructor.
+   * 
+   * @param label the label to use
+   */
+  public EnvironmentField(String label) {
+    setLayout(new BorderLayout());
+    m_label = new JLabel(label);
+    if (label.length() > 0) {
+      m_label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
+    }
+    add(m_label, BorderLayout.WEST);
+    
+    m_combo = new JComboBox();
+    m_combo.setEditable(true);
+    //m_combo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    
+    java.awt.Component theEditor = m_combo.getEditor().getEditorComponent();
+    if (theEditor instanceof JTextField) {
+      ((JTextField)m_combo.getEditor().getEditorComponent()).addCaretListener(new CaretListener() {
+        
+        public void caretUpdate(CaretEvent e) {
+          m_firstCaretPos = m_previousCaretPos;
+          m_previousCaretPos = m_currentCaretPos;
+          m_currentCaretPos = e.getDot();
+        }
+      });
+    }
+    add(m_combo, BorderLayout.CENTER);
+    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+  }
+  
+  /**
+   * Constructor.
+   * 
+   * @param label the label to use
+   * @param env the environment variables to display in
+   * the drop-down box
+   */
+  public EnvironmentField(String label, Environment env) {
+    this(label);
+    setEnvironment(env);
+  }
+  
+  /**
+   * Set the label for this widget.
+   * 
+   * @param label the label to use
+   */
+  public void setLabel(String label) {
+    m_label.setText(label);
+  }
+  
+  /**
+   * Set the text to display in the editable combo box.
+   * 
+   * @param text the text to display
+   */
+  public void setText(String text) {
+    m_currentContents = text;
+    m_combo.setSelectedItem(m_currentContents);
+  }
+  
+  /**
+   * Return the text from the combo box.
+   * 
+   * @return the text from the combo box
+   */
+  public String getText() {
+    return (String)m_combo.getSelectedItem();
+  }
+  
+  private String processSelected(String selected) {
+    if (selected.equals(m_currentContents)) {
+      // don't do anything if the user has just pressed return
+      // without adding anything new
+      return selected;
+    }
+    if (m_firstCaretPos == 0) {
+      m_currentContents = selected + m_currentContents;
+    } else if (m_firstCaretPos >= m_currentContents.length()){
+      m_currentContents = m_currentContents + selected;
+    } else {
+      String left = m_currentContents.substring(0, m_firstCaretPos);
+      String right = m_currentContents.substring(m_firstCaretPos, m_currentContents.length());
+      
+      m_currentContents = left + selected + right;
+    }
+    
+    /* java.awt.Component theEditor = m_combo.getEditor().getEditorComponent();
+    if (theEditor instanceof JTextField) {
+      System.err.println("Setting current contents..." + m_currentContents);
+      ((JTextField)theEditor).setText(m_currentContents);
+    } */
+    m_combo.setSelectedItem(m_currentContents);
+    
+    return m_currentContents;
+  }
+  
+  /**
+   * Set the environment variables to display in the drop
+   * down list.
+   * 
+   * @param env the environment variables to display
+   */
+  public void setEnvironment(final Environment env) {
+    m_env = env;
+    Vector<String> varKeys = new Vector<String>(env.getVariableNames());
+    
+    DefaultComboBoxModel dm = new DefaultComboBoxModel(varKeys) {
+      public Object getSelectedItem() {
+        Object item = super.getSelectedItem();
+        if (item instanceof String) {
+          if (env.getVariableValue((String)item) != null) {
+            String newS = "${" + (String)item + "}";
+            item = newS;
+          }
+        }
+        return item;
+      }
+    };
+    m_combo.setModel(dm);
+    m_combo.setSelectedItem("");
+    m_combo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        String selected = (String)m_combo.getSelectedItem();
+        try {
+          selected = processSelected(selected);
+          
+          selected = m_env.substitute(selected);
+        } catch (Exception ex) {
+          // quietly ignore unresolved variables
+        }
+        m_combo.setToolTipText(selected);
+      }
+    });
+        
+    m_combo.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
+      public void keyReleased(KeyEvent e) {
+        java.awt.Component theEditor = m_combo.getEditor().getEditorComponent();
+        if (theEditor instanceof JTextField) {
+          String selected = ((JTextField)theEditor).getText();
+          m_currentContents = selected;
+          if (m_env != null) {
+            try {
+              selected = m_env.substitute(selected);
+            } catch (Exception ex) {
+              // quietly ignore unresolved variables
+            }
+          }
+          m_combo.setToolTipText(selected);
+        }
+      }
+    });
+  }
+  
+  /**
+   * Set the enabled status of the combo box.
+   * 
+   * @param enabled true if the combo box is enabled
+   */
+  public void setEnabled(boolean enabled) {
+    m_combo.setEnabled(enabled);
+  }
+  
+  /**
+   * Set the editable status of the combo box.
+   * 
+   * @param editable true if the combo box is editable
+   */
+  public void setEditable(boolean editable) {
+    m_combo.setEditable(editable);
+  }
+  
+  /**
+   * Main method for testing this class
+   * 
+   * @param args command line args (ignored)
+   */
+  public static void main(String[] args) {
+    try {
+      final javax.swing.JFrame jf =
+        new javax.swing.JFrame("EnvironmentField");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final EnvironmentField f = new EnvironmentField("A label here");
+      jf.getContentPane().add(f, BorderLayout.CENTER);
+      Environment env = Environment.getSystemWide();
+      f.setEnvironment(env);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/EventConstraints.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/EventConstraints.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/EventConstraints.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    EventConstraints.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+
+/**
+ * Interface for objects that want to be able to specify at any given
+ * time whether their current configuration allows a particular event
+ * to be generated.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface EventConstraints {
+  
+  /**
+   * Returns true if, at the current time, the named event could be
+   * generated.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated
+   */
+  boolean eventGeneratable(String eventName);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Filter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Filter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Filter.java	(revision 29)
@@ -0,0 +1,1066 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Filter.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.filters.AllFilter;
+import weka.filters.StreamableFilter;
+import weka.filters.SupervisedFilter;
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.EventObject;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * A wrapper bean for Weka filters
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5247 $
+ */
+/**
+ * @author mhall
+ *
+ */
+/**
+ * @author mhall
+ *
+ */
+public class Filter
+  extends JPanel
+  implements BeanCommon, Visible, WekaWrapper,
+	     Serializable, UserRequestAcceptor,
+	     TrainingSetListener, TestSetListener,
+	     TrainingSetProducer, TestSetProducer,
+	     DataSource, DataSourceListener, 
+	     InstanceListener, EventConstraints,
+	     ConfigurationProducer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8249759470189439321L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("Filter",
+		   BeanVisual.ICON_PATH+"DefaultFilter.gif",
+		   BeanVisual.ICON_PATH+"DefaultFilter_animated.gif");
+
+  private static int IDLE = 0;
+  private static int FILTERING_TRAINING = 1;
+  private static int FILTERING_TEST = 2;
+  private int m_state = IDLE;
+
+  protected Thread m_filterThread = null;
+
+  private transient Instances m_trainingSet;
+  private transient Instances m_testingSet;
+
+  /**
+   * Global info for the wrapped filter (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Objects talking to us
+   */
+  private Hashtable m_listenees = new Hashtable();
+
+  /**
+   * Objects listening for training set events
+   */
+  private Vector m_trainingListeners = new Vector();
+
+  /**
+   * Objects listening for test set events
+   */
+  private Vector m_testListeners = new Vector();
+
+  /**
+   * Objects listening for instance events
+   */
+  private Vector m_instanceListeners = new Vector();
+
+  /**
+   * Objects listening for data set events
+   */
+  private Vector m_dataListeners = new Vector();
+
+  /**
+   * The filter to use.
+   */
+  private weka.filters.Filter m_Filter = new AllFilter();
+
+  /**
+   * Instance event object for passing on filtered instance streams
+   */
+  private InstanceEvent m_ie = new InstanceEvent(this);
+
+  /**
+   * Logging.
+   */
+  private transient Logger m_log = null;
+  
+  /**
+   * Counts incoming streamed instances.
+   */
+  private transient int m_instanceCount;
+  
+  /**
+   * Global info (if it exists) for the wrapped filter
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  public Filter() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    setFilter(m_Filter);
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Set the filter to be wrapped by this bean
+   *
+   * @param c a <code>weka.filters.Filter</code> value
+   */
+  public void setFilter(weka.filters.Filter c) {
+    boolean loadImages = true;
+    if (c.getClass().getName().
+	compareTo(m_Filter.getClass().getName()) == 0) {
+      loadImages = false;
+    }
+    m_Filter = c;
+    String filterName = c.getClass().toString();
+    filterName = filterName.substring(filterName.
+				      indexOf('.')+1, 
+				      filterName.length());
+    if (loadImages) {
+      if (m_Filter instanceof Visible) {
+        m_visual = ((Visible) m_Filter).getVisual();
+      } else {
+        if (!m_visual.loadIcons(BeanVisual.ICON_PATH+filterName+".gif",
+                                BeanVisual.ICON_PATH+filterName+"_animated.gif")) {
+          useDefaultVisual();
+        }
+      }
+    }
+    m_visual.setText(filterName.substring(filterName.lastIndexOf('.')+1,
+					  filterName.length()));
+
+    if (m_Filter instanceof LogWriter && m_log != null) {
+      ((LogWriter) m_Filter).setLog(m_log);
+    }
+
+    if (!(m_Filter instanceof StreamableFilter) &&
+	(m_listenees.containsKey("instance"))) {
+      if (m_log != null) {
+	m_log.logMessage("[Filter] " + 
+	    statusMessagePrefix() + " WARNING : "
+	    + m_Filter.getClass().getName()
+	    + " is not an incremental filter");
+	m_log.statusMessage(statusMessagePrefix()
+	    + "WARNING: Not an incremental filter.");
+      }
+    }
+    
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Filter);
+  }
+
+  public weka.filters.Filter getFilter() {
+    return m_Filter;
+  }
+
+  /**
+   * Set the filter to be wrapped by this bean
+   *
+   * @param algorithm a weka.filters.Filter
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void setWrappedAlgorithm(Object algorithm) {
+    
+    if (!(algorithm instanceof weka.filters.Filter)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Filter)");
+    }
+    setFilter((weka.filters.Filter)algorithm);
+  }
+
+  /**
+   * Get the filter wrapped by this bean
+   *
+   * @return an <code>Object</code> value
+   */
+  public Object getWrappedAlgorithm() {
+    return getFilter();
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    processTrainingOrDataSourceEvents(e);
+  }
+
+  private boolean m_structurePassedOn = false;
+  /**
+   * Accept an instance for processing by StreamableFilters only
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  public void acceptInstance(InstanceEvent e) {
+    // to do!
+    if (m_filterThread != null) {
+      String messg = "[Filter] " + statusMessagePrefix() 
+        + " is currently batch processing!";
+      if (m_log != null) {
+	m_log.logMessage(messg);
+	m_log.statusMessage(statusMessagePrefix()
+	    + "WARNING: Filter is currently batch processing.");
+      } else {
+	System.err.println(messg);
+      }
+      return;
+    }
+    if (!(m_Filter instanceof StreamableFilter)) {
+      stop(); // stop all processing
+      if (m_log != null) {
+	m_log.logMessage("[Filter] " + statusMessagePrefix() 
+	    + " ERROR : "+m_Filter.getClass().getName()
+	    +"can't process streamed instances; can't continue");
+	m_log.statusMessage(statusMessagePrefix()
+	    + "ERROR: Can't process streamed instances; can't continue.");
+      }
+      return;
+    }
+    if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+      try {
+        m_instanceCount = 0;
+        //notifyInstanceListeners(e);
+	//	Instances dataset = e.getInstance().dataset();
+	Instances dataset = e.getStructure();
+	if (m_Filter instanceof SupervisedFilter) {
+	  // defualt to last column if no class is set
+	  if (dataset.classIndex() < 0) {
+	    dataset.setClassIndex(dataset.numAttributes()-1);
+	  }
+	}
+	// initialize filter
+	m_Filter.setInputFormat(dataset);
+	// attempt to determine post-filtering
+	// structure. If successful this can be passed on to instance
+	// listeners as a new FORMAT_AVAILABLE event.
+	m_structurePassedOn = false;
+	try {
+	  if (m_Filter.isOutputFormatDefined()) {
+//	    System.err.println("Filter - passing on output format...");
+	    //	    System.err.println(m_Filter.getOutputFormat());
+	    m_ie.setStructure(m_Filter.getOutputFormat());
+	    notifyInstanceListeners(m_ie);
+	    m_structurePassedOn = true;
+	  }
+	} catch (Exception ex) {
+	  stop(); // stop all processing
+	  if (m_log != null) {
+	    m_log.logMessage("[Filter] " + statusMessagePrefix() 
+                + " Error in obtaining post-filter structure. " 
+                + ex.getMessage());
+	    m_log.statusMessage(statusMessagePrefix()
+	        +"ERROR (See log for details).");
+	  } else {
+	    System.err.println("[Filter] " + statusMessagePrefix() 
+	        + " Error in obtaining post-filter structure");
+	  }
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+      return;
+    }
+   
+    if (e.getStatus() == InstanceEvent.BATCH_FINISHED) {
+      // get the last instance (if available)
+      try {
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() 
+              + "Stream finished.");
+        }
+        if (m_Filter.input(e.getInstance())) {
+          Instance filteredInstance = m_Filter.output();
+          if (filteredInstance != null) {
+            if (!m_structurePassedOn) {
+              // pass on the new structure first
+              m_ie.setStructure(new Instances(filteredInstance.dataset(), 0));
+              notifyInstanceListeners(m_ie);
+              m_structurePassedOn = true;
+            }
+
+            m_ie.setInstance(filteredInstance);
+            
+            // if there are instances pending for output don't want to send
+            // a batch finisehd at this point...
+            //System.err.println("Filter - in batch finisehd...");
+            if (m_Filter.batchFinished() && m_Filter.numPendingOutput() > 0) {
+              m_ie.setStatus(InstanceEvent.INSTANCE_AVAILABLE);
+            } else {
+              m_ie.setStatus(e.getStatus());
+            }
+            notifyInstanceListeners(m_ie);
+          }
+        }
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Done.");
+        }
+      } catch (Exception ex) {
+        stop(); // stop all processing
+        if (m_log != null) {
+          m_log.logMessage("[Filter] " 
+              + statusMessagePrefix() + ex.getMessage());
+          m_log.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details).");
+        }
+        ex.printStackTrace();
+      }
+      
+      // check for any pending instances that we might need to pass on
+      try {
+        if (m_Filter.batchFinished() && m_Filter.numPendingOutput() > 0) {
+          if (m_log != null) {
+            m_log.statusMessage(statusMessagePrefix() 
+                + "Passing on pending instances...");
+          }
+          Instance filteredInstance = m_Filter.output();
+          if (filteredInstance != null) {
+            if (!m_structurePassedOn) {
+              // pass on the new structure first
+              m_ie.setStructure(new Instances(filteredInstance.dataset(), 0));
+              notifyInstanceListeners(m_ie);
+              m_structurePassedOn = true;
+            }
+
+            m_ie.setInstance(filteredInstance);
+            
+            //TODO here is the problem I think
+            m_ie.setStatus(InstanceEvent.INSTANCE_AVAILABLE);
+            notifyInstanceListeners(m_ie);
+          }
+          while (m_Filter.numPendingOutput() > 0) {
+            filteredInstance = m_Filter.output();
+            m_ie.setInstance(filteredInstance);
+//            System.err.println("Filter - sending pending...");
+            if (m_Filter.numPendingOutput() == 0) {
+              m_ie.setStatus(InstanceEvent.BATCH_FINISHED);
+            } else {
+              m_ie.setStatus(InstanceEvent.INSTANCE_AVAILABLE);
+            }
+            notifyInstanceListeners(m_ie);
+          }
+          if (m_log != null) {
+            m_log.statusMessage(statusMessagePrefix() + "Finished.");
+          }
+        }
+      } catch (Exception ex) {
+        stop(); // stop all processing
+        if (m_log != null) {
+          m_log.logMessage("[Filter] " 
+              + statusMessagePrefix() 
+              + ex.toString());
+          m_log.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details.");
+        }
+        ex.printStackTrace();
+      }
+    } else {
+      // pass instance through the filter
+      try {
+        if (!m_Filter.input(e.getInstance())) {
+//          System.err.println("Filter - inputing instance into filter...");
+          /* if (m_log != null) {
+            m_log.logMessage("ERROR : filter not ready to output instance");
+          } */
+          
+          // quietly return. Filter might be able to output some instances
+          // once the batch is finished.
+          return;
+        }
+
+        // collect output instance.
+        Instance filteredInstance = m_Filter.output();
+        if (filteredInstance == null) {
+          return;
+        }
+        m_instanceCount++;
+        
+        if (!m_structurePassedOn) {
+          // pass on the new structure first
+          m_ie.setStructure(new Instances(filteredInstance.dataset(), 0));
+          notifyInstanceListeners(m_ie);
+          m_structurePassedOn = true;
+        }
+
+        m_ie.setInstance(filteredInstance);
+        m_ie.setStatus(e.getStatus());
+        
+        if (m_log != null && (m_instanceCount % 10000 == 0)) {
+          m_log.statusMessage(statusMessagePrefix()
+              + "Received " + m_instanceCount + " instances.");
+        }
+        notifyInstanceListeners(m_ie);
+      } catch (Exception ex) {
+        stop(); // stop all processing
+        if (m_log != null) {
+          m_log.logMessage("[Filter] " + statusMessagePrefix() 
+              + ex.toString());
+          m_log.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details).");
+        }
+        ex.printStackTrace();
+      }
+    }
+  }
+
+  private void processTrainingOrDataSourceEvents(final EventObject e) {
+    boolean structureOnly = false;
+    if (e instanceof DataSetEvent) {
+      structureOnly = ((DataSetEvent)e).isStructureOnly();
+      if(structureOnly){
+       notifyDataOrTrainingListeners(e);  
+      }
+    }
+    if (e instanceof TrainingSetEvent) {
+      structureOnly = ((TrainingSetEvent)e).isStructureOnly();
+      if(structureOnly){
+       notifyDataOrTrainingListeners(e);  
+      }
+    }
+    if (structureOnly && !(m_Filter instanceof StreamableFilter)) {
+      return; // nothing can be done
+    }
+    
+    if (m_filterThread == null) {
+      try {
+	if (m_state == IDLE) {
+	  synchronized (this) {
+	    m_state = FILTERING_TRAINING;
+	  }
+	  m_trainingSet = (e instanceof TrainingSetEvent) 
+	    ? ((TrainingSetEvent)e).getTrainingSet()
+	    : ((DataSetEvent)e).getDataSet();
+
+//	  final String oldText = m_visual.getText();
+	  m_filterThread = new Thread() {
+	      public void run() {
+		try {
+		  if (m_trainingSet != null) {
+		    m_visual.setAnimated();
+//		    m_visual.setText("Filtering training data...");
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() 
+		          + "Filtering training data ("
+		          + m_trainingSet.relationName() + ")");
+		    }
+		    m_Filter.setInputFormat(m_trainingSet);
+		    Instances filteredData = 
+		      weka.filters.Filter.useFilter(m_trainingSet, m_Filter);
+//		    m_visual.setText(oldText);
+		    m_visual.setStatic();
+		    EventObject ne;
+		    if (e instanceof TrainingSetEvent) {
+		      ne = new TrainingSetEvent(weka.gui.beans.Filter.this, 
+						filteredData);
+		      ((TrainingSetEvent)ne).m_setNumber =
+			((TrainingSetEvent)e).m_setNumber;
+		      ((TrainingSetEvent)ne).m_maxSetNumber = 
+			((TrainingSetEvent)e).m_maxSetNumber;
+		    } else {
+		      ne = new DataSetEvent(weka.gui.beans.Filter.this,
+					    filteredData);
+		    }
+
+		    notifyDataOrTrainingListeners(ne);
+		  }
+		} catch (Exception ex) {
+		  Filter.this.stop(); // stop all processing
+		  ex.printStackTrace();
+                  if (m_log != null) {
+                    m_log.logMessage("[Filter] " + statusMessagePrefix() 
+                        + ex.getMessage());
+                    m_log.statusMessage(statusMessagePrefix()
+                        + "ERROR (See log for details).");
+//                    m_log.statusMessage("Problem filtering: see log for details.");
+                  }
+		} finally {
+//		  m_visual.setText(oldText);
+		  m_visual.setStatic();
+		  m_state = IDLE;
+		  if (isInterrupted()) {
+		    m_trainingSet = null;
+		    if (m_log != null) {
+		      m_log.logMessage("[Filter] " + statusMessagePrefix()
+                                       + " training set interrupted!");
+		      m_log.statusMessage(statusMessagePrefix()
+		          + "INTERRUPTED");
+		    }		    
+		  } else {
+		    if (m_log != null) {
+		      m_log.statusMessage(statusMessagePrefix() + "Finished.");
+		    }
+		  }
+		  block(false);
+		}
+	      }
+	    };
+	  m_filterThread.setPriority(Thread.MIN_PRIORITY);
+	  m_filterThread.start();
+	  block(true);
+	  m_filterThread = null;
+	  m_state = IDLE;
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Accept a test set
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public void acceptTestSet(final TestSetEvent e) {
+      if(e.isStructureOnly())
+            notifyTestListeners(e);
+      if (m_trainingSet != null && 
+	m_trainingSet.equalHeaders(e.getTestSet()) && 
+	m_filterThread == null) {
+      try {
+	if (m_state == IDLE) {
+	  m_state = FILTERING_TEST;
+	}
+	m_testingSet = e.getTestSet();
+        //	final String oldText = m_visual.getText();
+	m_filterThread = new Thread() {
+	    public void run() {
+	      try {
+		if (m_testingSet != null) {
+		  m_visual.setAnimated();
+                  //		  m_visual.setText("Filtering test data...");
+		  if (m_log != null) {
+		    m_log.statusMessage(statusMessagePrefix() 
+		        + "Filtering test data ("
+			+ m_testingSet.relationName() + ")");
+		  }
+		  Instances filteredTest = 
+		    weka.filters.Filter.useFilter(m_testingSet, m_Filter);
+                  //		  m_visual.setText(oldText);
+		  m_visual.setStatic();
+		  TestSetEvent ne =
+		    new TestSetEvent(weka.gui.beans.Filter.this,
+				     filteredTest);
+		  ne.m_setNumber = e.m_setNumber;
+		  ne.m_maxSetNumber = e.m_maxSetNumber;
+		  notifyTestListeners(ne);
+		}
+	      } catch (Exception ex) {
+	        Filter.this.stop();
+		ex.printStackTrace();
+                if (m_log != null) {
+                  m_log.logMessage("[Filter] " + statusMessagePrefix() 
+                      + ex.getMessage());
+                  m_log.statusMessage(statusMessagePrefix() 
+                      + "ERROR (See log for details).");
+                }
+	      } finally {
+                //		m_visual.setText(oldText);
+		m_visual.setStatic();
+		m_state = IDLE;
+		if (isInterrupted()) {
+		  m_trainingSet = null;
+		  if (m_log != null) {
+		      m_log.logMessage("[Filter] " + statusMessagePrefix()
+                                       + " test set interrupted!");
+		      m_log.statusMessage(statusMessagePrefix()
+		          + "INTERRUPTED");
+//		    m_log.statusMessage("OK");
+		  }
+		} else {
+		  if (m_log != null) {
+		    m_log.statusMessage(statusMessagePrefix() + "Finished.");
+		  }
+		}
+		block(false);
+	      }
+	    }
+	  };
+	m_filterThread.setPriority(Thread.MIN_PRIORITY);
+	m_filterThread.start();
+	block(true);
+	m_filterThread = null;
+	m_state = IDLE;
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public void acceptDataSet(DataSetEvent e) {
+    processTrainingOrDataSourceEvents(e);
+  }
+
+  /**
+   * Set the visual appearance of this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual appearance of this bean
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultFilter.gif",
+		       BeanVisual.ICON_PATH+"DefaultFilter_animated.gif");
+  }
+
+  /**
+   * Add a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
+    m_trainingListeners.addElement(tsl);
+  }
+  
+  /**
+   * Remove a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
+     m_trainingListeners.removeElement(tsl);
+  }
+
+  /**
+   * Add a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void addTestSetListener(TestSetListener tsl) {
+    m_testListeners.addElement(tsl);
+  }
+  
+  /**
+   * Remove a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public synchronized void removeTestSetListener(TestSetListener tsl) {
+    m_testListeners.removeElement(tsl);
+  }
+
+  /**
+   * Add a data source listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void addDataSourceListener(DataSourceListener dsl) {
+    m_dataListeners.addElement(dsl);
+  }
+
+  /**
+   * Remove a data source listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void removeDataSourceListener(DataSourceListener dsl) {
+    m_dataListeners.remove(dsl);
+  }
+
+  /**
+   * Add an instance listener
+   *
+   * @param tsl an <code>InstanceListener</code> value
+   */
+  public synchronized void addInstanceListener(InstanceListener tsl) {
+    m_instanceListeners.addElement(tsl);
+  }
+
+  /**
+   * Remove an instance listener
+   *
+   * @param tsl an <code>InstanceListener</code> value
+   */
+  public synchronized void removeInstanceListener(InstanceListener tsl) {
+    m_instanceListeners.removeElement(tsl);
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void addConfigurationListener(ConfigurationListener cl) {
+    
+  }
+  
+  /**
+   * We don't have to keep track of configuration listeners (see the
+   * documentation for ConfigurationListener/ConfigurationEvent).
+   * 
+   * @param cl a ConfigurationListener.
+   */
+  public synchronized void removeConfigurationListener(ConfigurationListener cl) {
+    
+  }
+
+  private void notifyDataOrTrainingListeners(EventObject ce) {
+    Vector l;
+    synchronized (this) {
+      l = (ce instanceof TrainingSetEvent)
+	? (Vector)m_trainingListeners.clone()
+	: (Vector)m_dataListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	if (ce instanceof TrainingSetEvent) {
+	  ((TrainingSetListener)l.elementAt(i)).
+	    acceptTrainingSet((TrainingSetEvent)ce);
+	} else {
+	  ((DataSourceListener)l.elementAt(i)).acceptDataSet((DataSetEvent)ce);
+	}
+      }
+    }
+  }
+
+  private void notifyTestListeners(TestSetEvent ce) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_testListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TestSetListener)l.elementAt(i)).acceptTestSet(ce);
+      }
+    }
+  }
+
+  protected void notifyInstanceListeners(InstanceEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_instanceListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying instance listeners "
+	//			   +"(Filter)");
+	((InstanceListener)l.elementAt(i)).acceptInstance(tse);
+      }
+    }
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+
+    if (m_listenees.containsKey(eventName)) {
+      return false;
+    }
+
+    /* reject a test event if we don't have a training or data set event
+    if (eventName.compareTo("testSet") == 0) {
+      if (!m_listenees.containsKey("trainingSet") &&
+	  !m_listenees.containsKey("dataSet")) {
+	return false;
+      }
+      } */
+    
+    // will need to reject train/test listener if we have a
+    // data source listener and vis versa
+    if (m_listenees.containsKey("dataSet") &&
+	(eventName.compareTo("trainingSet") == 0 ||
+	 eventName.compareTo("testSet") == 0 ||
+	eventName.compareTo("instance") == 0)) {
+      return false;
+    }
+
+    if ((m_listenees.containsKey("trainingSet") ||
+	 m_listenees.containsKey("testSet")) &&
+	(eventName.compareTo("dataSet") == 0 || 
+	eventName.compareTo("instance") == 0)) {
+      return false;
+    }
+
+    if (m_listenees.containsKey("instance") &&
+	(eventName.compareTo("trainingSet") == 0 ||
+	 eventName.compareTo("testSet") == 0 ||
+	 eventName.compareTo("dataSet") == 0)) {
+      return false;
+    }
+
+    // reject an instance event connection if our filter isn't
+    // streamable
+    if (eventName.compareTo("instance") == 0 &&
+	!(m_Filter instanceof StreamableFilter)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenees.put(eventName, source);
+      if (m_Filter instanceof ConnectionNotificationConsumer) {
+        ((ConnectionNotificationConsumer) m_Filter).
+          connectionNotification(eventName, source);
+      }
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_Filter instanceof ConnectionNotificationConsumer) {
+      ((ConnectionNotificationConsumer) m_Filter).
+        disconnectionNotification(eventName, source);
+    }
+    m_listenees.remove(eventName);
+  }
+
+  /**
+   * Function used to stop code that calls acceptTrainingSet, acceptTestSet,
+   * or acceptDataSet. This is 
+   * needed as filtering is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+	// only block if thread is still doing something useful!
+	if (m_filterThread.isAlive() && m_state != IDLE) {
+	  wait();
+	}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Stop all action if possible
+   */
+  public void stop() {
+    // tell all listenees (upstream beans) to stop
+    Enumeration en = m_listenees.keys();
+    while (en.hasMoreElements()) {
+      Object tempO = m_listenees.get(en.nextElement());
+      if (tempO instanceof BeanCommon) {
+	((BeanCommon)tempO).stop();
+      }
+    }
+    
+    // stop the filter thread
+    if (m_filterThread != null) {
+      m_filterThread.interrupt();
+      m_filterThread.stop();
+      m_filterThread = null;
+      m_visual.setStatic();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_filterThread != null);
+  }
+  
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+
+    if (m_Filter != null && m_Filter instanceof LogWriter) {
+      ((LogWriter) m_Filter).setLog(m_log);
+    }
+  }
+
+  /**
+   * Return an enumeration of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_filterThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Filter)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    
+    if (eventName.equals("configuration") && m_Filter != null) {
+      return true;
+    }
+    
+    // can't generate the named even if we are not receiving it as an
+    // input!
+    if (!m_listenees.containsKey(eventName)) {
+      return false;
+    }
+    Object source = m_listenees.get(eventName);
+    if (source instanceof EventConstraints) {
+      if (!((EventConstraints)source).eventGeneratable(eventName)) {
+	return false;
+      }
+    }
+    if (eventName.compareTo("instance") == 0) {
+      if (!(m_Filter instanceof StreamableFilter)) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Filter instanceof OptionHandler &&
+        Utils.joinOptions(((OptionHandler)m_Filter).getOptions()).length() > 0) 
+        ? Utils.joinOptions(((OptionHandler)m_Filter).getOptions()) + "|"
+            : "");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/FilterBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/FilterBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/FilterBeanInfo.java	(revision 29)
@@ -0,0 +1,80 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilterBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the Filter bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5247 $
+ */
+public class FilterBeanInfo extends SimpleBeanInfo {
+   
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(TrainingSetProducer.class, 
+			       "trainingSet", 
+			       TrainingSetListener.class, 
+			       "acceptTrainingSet"),
+	new EventSetDescriptor(TestSetProducer.class, 
+			       "testSet", 
+			       TestSetListener.class, 
+			       "acceptTestSet"),
+	new EventSetDescriptor(DataSource.class,
+			       "dataSet",
+			       DataSourceListener.class,
+			       "acceptDataSet"),
+	new EventSetDescriptor(DataSource.class, 
+			       "instance", 
+			       InstanceListener.class, 
+			       "acceptInstance"),
+	new EventSetDescriptor(Filter.class,
+	                       "configuration",
+	                       ConfigurationListener.class,
+	                       "acceptConfiguration")
+	  };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Filter.class,
+			      FilterCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/FilterCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/FilterCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/FilterCustomizer.java	(revision 29)
@@ -0,0 +1,148 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FilterCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.filters.Filter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * GUI customizer for the filter bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5340 $
+ */
+public class FilterCustomizer
+  extends JPanel
+  implements Customizer, CustomizerCloseRequester {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2049895469240109738L;
+  
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private weka.gui.beans.Filter m_filter;
+/*  private GenericObjectEditor m_filterEditor = 
+    new GenericObjectEditor(true); */
+  
+  /** Backup if user presses cancel */
+  private weka.filters.Filter m_backup;
+  
+  private PropertySheetPanel m_filterEditor = 
+    new PropertySheetPanel();
+  
+  private JFrame m_parentFrame;
+ 
+  public FilterCustomizer() {
+    m_filterEditor.
+    setBorder(BorderFactory.createTitledBorder("Filter options"));
+
+
+
+    setLayout(new BorderLayout());
+    add(m_filterEditor, BorderLayout.CENTER);
+
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new GridLayout(1,2));
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // cancel requested, so revert to backup and then
+        // close the dialog
+        if (m_backup != null) {
+          m_filter.setFilter(m_backup);
+        }
+        m_parentFrame.dispose();
+      }
+    });
+    
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    add(butHolder, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * Set the filter bean to be edited
+   *
+   * @param object a Filter bean
+   */
+  public void setObject(Object object) {
+    m_filter = (weka.gui.beans.Filter)object;
+    try {
+      m_backup = 
+        (weka.filters.Filter)GenericObjectEditor.makeCopy(m_filter.getFilter());
+    } catch (Exception ex) {
+      // ignore
+    }
+    m_filterEditor.setTarget(m_filter.getFilter());
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/FlowRunner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/FlowRunner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/FlowRunner.java	(revision 29)
@@ -0,0 +1,491 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    FlowRunner.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.Vector;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.RevisionHandler;
+import weka.core.RevisionUtils;
+import weka.gui.Logger;
+import weka.gui.beans.xml.*;
+
+/**
+ * Small utility class for executing KnowledgeFlow
+ * flows outside of the KnowledgeFlow application
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5928 $
+ */
+public class FlowRunner implements RevisionHandler {
+
+  /** The potential flow(s) to execute */
+  protected Vector m_beans;
+
+  protected int m_runningCount = 0;
+
+  protected transient Logger m_log = null;
+  
+  protected transient Environment m_env;
+  
+  /** run each Startable bean sequentially? (default in parallel) */
+  protected boolean m_startSequentially = false;
+  
+  public static class SimpleLogger implements weka.gui.Logger {
+    SimpleDateFormat m_DateFormat = 
+      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    
+    public void logMessage(String lm) {
+      System.out.println(m_DateFormat.format(new Date()) + ": " + lm);
+    }
+    
+    public void statusMessage(String lm) {
+      System.out.println(m_DateFormat.format(new Date()) + ": " + lm);  
+    }
+  }
+
+  /**
+   * Constructor
+   */
+  public FlowRunner() {
+    // make sure that properties and plugins are loaded
+    KnowledgeFlowApp.loadProperties();
+  }
+
+  public void setLog(Logger log) {
+    m_log = log;
+  }
+  
+  protected void runSequentially(TreeMap<Integer, Startable> startables) {
+    Set<Integer> s = startables.keySet();
+    for (Integer i : s) {
+      try {
+        Startable startPoint = startables.get(i);
+        startPoint.start();
+        Thread.sleep(200);
+        waitUntilFinished();
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        if (m_log != null) {
+          m_log.logMessage(ex.getMessage());
+          m_log.logMessage("Aborting...");
+        } else {
+          System.err.println(ex.getMessage());
+          System.err.println("Aborting...");
+        }
+        break;
+      }
+    }
+  }
+
+  protected synchronized void launchThread(final Startable s, final int flowNum) {
+    Thread t = new Thread() {
+        private int m_num = flowNum;
+        public void run() {
+          try {
+            s.start();
+          } catch (Exception ex) {
+            ex.printStackTrace();
+            if (m_log != null) {
+              m_log.logMessage(ex.getMessage());
+            } else {
+              System.err.println(ex.getMessage());
+            }
+          } finally {
+            /*
+            if (m_log != null) { 
+              m_log.logMessage("[FlowRunner] flow " + m_num + " finished.");
+            } else {
+              System.out.println("[FlowRunner] Flow " + m_num + " finished.");
+            }
+            */
+            decreaseCount();
+          }
+        }
+      };
+    m_runningCount++;
+    t.setPriority(Thread.MIN_PRIORITY);
+    t.start();
+  }
+
+  protected synchronized void decreaseCount() {
+    m_runningCount--;
+  }
+
+  public synchronized void stopAllFlows() {
+    for (int i = 0; i < m_beans.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_beans.elementAt(i);
+      if (temp.getBean() instanceof BeanCommon) {
+        // try to stop any execution
+        ((BeanCommon)temp.getBean()).stop();
+      }
+    }
+  }
+
+  /**
+   * Waits until all flows have finished executing before returning
+   *
+   */
+  public void waitUntilFinished() {
+    try {
+      while (m_runningCount > 0) {
+        Thread.sleep(200);
+      }
+      
+      // now poll beans to see if there are any that are still busy
+      // (i.e. any multi-threaded ones that queue data instead of blocking)
+      while (true) {
+        boolean busy = false;
+        for (int i = 0; i < m_beans.size(); i++) {
+          BeanInstance temp = (BeanInstance)m_beans.elementAt(i);
+          if (temp.getBean() instanceof BeanCommon) {
+            if (((BeanCommon)temp.getBean()).isBusy()) {
+              busy = true;
+              break; // for
+            }            
+          }
+        }
+        if (busy) {
+          Thread.sleep(3000);
+        } else {
+          break; // while
+        }
+      }
+    } catch (Exception ex) {
+      if (m_log != null) {
+        m_log.logMessage("[FlowRunner] Attempting to stop all flows...");
+      } else {
+        System.err.println("[FlowRunner] Attempting to stop all flows...");
+      }
+      stopAllFlows();
+      //      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Load a serialized KnowledgeFlow (either binary or xml)
+   *
+   * @param fileName the name of the file to load from
+   * @throws Exception if something goes wrong
+   */
+  public void load(String fileName) throws Exception {
+    if (!fileName.endsWith(".kf") && !fileName.endsWith(".kfml")) {
+      throw new Exception("Can only load and run binary or xml serialized KnowledgeFlows "
+                          + "(*.kf | *.kfml)");
+    }
+    
+    if (fileName.endsWith(".kf")) {
+      loadBinary(fileName);
+    } else if (fileName.endsWith(".kfml")) {
+      loadXML(fileName);
+    }
+  }
+
+  /**
+   * Load a binary serialized KnowledgeFlow
+   *
+   * @param fileName the name of the file to load from
+   * @throws Exception if something goes wrong
+   */
+  public void loadBinary(String fileName) throws Exception {
+    if (!fileName.endsWith(".kf")) {
+      throw new Exception("File must be a binary flow (*.kf)");
+    }
+
+    InputStream is = new FileInputStream(fileName);
+    ObjectInputStream ois = new ObjectInputStream(is);
+    m_beans = (Vector)ois.readObject();
+    
+    // don't need the graphical connections
+    ois.close();
+    
+    if (m_env != null) {
+      String parentDir = (new File(fileName)).getParent();
+      if (parentDir == null) {
+        parentDir = "./";
+      }
+      m_env.addVariable("Internal.knowledgeflow.directory", 
+          parentDir);
+    }
+  }
+
+  /**
+   * Load an XML serialized KnowledgeFlow
+   *
+   * @param fileName the name of the file to load from
+   * @throws Exception if something goes wrong
+   */
+  public void loadXML(String fileName) throws Exception {
+    if (!fileName.endsWith(".kfml")) {
+      throw new Exception("File must be an XML flow (*.kfml)");
+    }
+
+    XMLBeans xml = new XMLBeans(null, null);
+    Vector v = (Vector) xml.read(new File(fileName));
+    m_beans = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+
+    if (m_env != null) {
+      String parentDir = (new File(fileName)).getParent();
+      if (parentDir == null) {
+        parentDir = "./";
+      }
+      m_env.addVariable("Internal.knowledgeflow.directory", 
+          parentDir);
+    } else {
+      System.err.println("++++++++++++ Environment variables null!!...");
+    }
+  }
+  
+  /**
+   * Get the vector holding the flow(s)
+   *
+   * @return the Vector holding the flow(s)
+   */
+  public Vector getFlows() {
+    return m_beans;
+  }
+
+  /**
+   * Set the vector holding the flows(s) to run
+   *
+   * @param beans the Vector holding the flows to run
+   */
+  public void setFlows(Vector beans) {
+    m_beans = beans;
+  }
+  
+  /**
+   * Set the environment variables to use. NOTE: this needs
+   * to be called BEFORE a load method is invoked to ensure
+   * that the ${Internal.knowledgeflow.directory} variable get
+   * set in the supplied Environment object.
+   * 
+   * @param env the environment variables to use.
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+  
+  /**
+   * Get the environment variables that are in use.
+   * 
+   * @return the environment variables that are in ues.
+   */
+  public Environment getEnvironment() {
+    return m_env;
+  }
+  
+  /**
+   * Set whether to launch Startable beans one after the other
+   * or all in parallel.
+   * 
+   * @param s true if Startable beans are to be launched sequentially
+   */
+  public void setStartSequentially(boolean s) {
+    m_startSequentially = s;
+  }
+  
+  /**
+   * Gets whether Startable beans will be launched sequentially
+   * or all in parallel.
+   * 
+   * @return true if Startable beans will be launched sequentially
+   */
+  public boolean getStartSequentially() {
+    return m_startSequentially;
+  }
+
+  /**
+   * Launch all loaded KnowledgeFlow
+   *
+   * @throws Exception if something goes wrong during execution
+   */
+  public void run() throws Exception {
+    if (m_beans == null) {
+      throw new Exception("Don't seem to have any beans I can execute.");
+    }
+    
+    // register the log (if set) with the beans
+    for (int i = 0; i < m_beans.size(); i++) {
+      BeanInstance tempB = (BeanInstance)m_beans.elementAt(i);
+      if (m_log != null) {
+        if (tempB.getBean() instanceof BeanCommon) {
+          ((BeanCommon)tempB.getBean()).setLog(m_log);
+        }
+      }
+        
+      if (tempB.getBean() instanceof EnvironmentHandler) {
+        ((EnvironmentHandler)tempB.getBean()).setEnvironment(m_env);
+      }
+    }
+    
+    int numFlows = 1;
+
+    if (m_log != null) {
+      if (m_startSequentially) {
+        m_log.logMessage("[FlowRunner] launching flow start points sequentially...");
+      } else {
+        m_log.logMessage("[FlowRunner] launching flow start points in parallel...");
+      }
+    }
+    TreeMap<Integer, Startable> startables = new TreeMap<Integer, Startable>();
+    // look for a Startable bean...
+    for (int i = 0; i < m_beans.size(); i++) {
+      BeanInstance tempB = (BeanInstance)m_beans.elementAt(i);
+      if (tempB.getBean() instanceof Startable) {
+        Startable s = (Startable)tempB.getBean();
+        // start that sucker (if it's happy to be started)...
+        if (!m_startSequentially) {
+          if (s.getStartMessage().charAt(0) != '$') {
+            if (m_log != null) {
+              m_log.logMessage("[FlowRunner] Launching flow "+numFlows+"...");
+            } else {
+              System.out.println("[FlowRunner] Launching flow "+numFlows+"...");
+            }
+            launchThread(s, numFlows);
+            numFlows++;
+          } else {
+            String beanName = s.getClass().getName();
+            if (s instanceof BeanCommon) {
+              String customName = ((BeanCommon)s).getCustomName();
+              beanName = customName;
+            }
+            if (m_log != null) {
+              m_log.logMessage("[FlowRunner] WARNING: Can't start " + beanName + " at this time.");
+            } else {
+              System.out.println("[FlowRunner] WARNING: Can't start " + beanName + " at this time.");
+            }
+          }
+        } else {
+          boolean ok = false;
+          Integer position = null;
+          String beanName = s.getClass().getName();
+          if (s instanceof BeanCommon) {
+            String customName = ((BeanCommon)s).getCustomName();
+            beanName = customName;
+            // see if we have a parseable integer at the start of the name
+            if (customName.indexOf(':') > 0) {
+              String startPos = customName.substring(0, customName.indexOf(':'));
+              try {
+                position = new Integer(startPos);
+                ok = true;
+              } catch (NumberFormatException n) {
+              }
+            }            
+          }
+          
+          if (!ok) {
+            if (startables.size() == 0) {
+              position = new Integer(0);
+            } else {
+              int newPos = startables.lastKey().intValue();
+              newPos++;
+              position = new Integer(newPos);
+            }
+          }
+          
+          if (s.getStartMessage().charAt(0) != '$') {
+            if (m_log != null) {
+              m_log.logMessage("[FlowRunner] adding start point " + beanName
+                  + " to the execution list (position " + position + ")");
+            } else {
+              System.out.println("[FlowRunner] adding start point " + beanName
+                  + " to the execution list (position " + position + ")");
+            }
+            startables.put(position, s);
+          } else {
+            if (m_log != null) {
+              m_log.logMessage("[FlowRunner] WARNING: Can't start " + beanName + " at this time.");
+            } else {
+              System.out.println("[FlowRunner] WARNING: Can't start " + beanName + " at this time.");
+            }
+          }
+        }
+      }
+    }
+    
+    if (m_startSequentially) {
+      runSequentially(startables);
+    }
+  }
+
+  /**
+   * Main method for testing this class. <p>
+   * <br>Usage:<br><br>
+   * <pre>Usage:\n\nFlowRunner <serialized kf file></pre>
+   *
+   * @param args command line arguments
+   */
+  public static void main(String[] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    if (args.length < 1) {
+      System.err.println("Usage:\n\nFlowRunner <serialized kf file> [-s]\n\n" 
+          + "\tUse -s to launch start points sequentially (default launches "
+          + "in parallel).");
+    } else {
+      try {
+        FlowRunner fr = new FlowRunner();
+        FlowRunner.SimpleLogger sl = new FlowRunner.SimpleLogger();
+        String fileName = args[0];
+        
+        if (args.length == 2 && args[1].equals("-s")) {
+          fr.setStartSequentially(true);
+        }
+        
+        // start with the system-wide vars
+        Environment env = Environment.getSystemWide();
+
+        fr.setLog(sl);
+        fr.setEnvironment(env);
+        
+        fr.load(fileName);
+        fr.run();
+        fr.waitUntilFinished();
+        System.out.println("Finished all flows.");
+        System.exit(1);
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        System.err.println(ex.getMessage());
+      }
+    }                         
+  }
+
+  public String getRevision() {
+    return "$Revision: 5928 $";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/GraphEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/GraphEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/GraphEvent.java	(revision 29)
@@ -0,0 +1,85 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventObject;
+
+/**
+ * Event for graphs
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ */
+public class GraphEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 2099494034652519986L;
+
+  protected String m_graphString;
+  protected String m_graphTitle;
+  protected int m_graphType;
+
+  /**
+   * Creates a new <code>GraphEvent</code> instance.
+   *
+   * @param source the source of the event
+   * @param graphString a string describing the graph in "dot" format
+   * @param graphTitle the title for the graph
+   * @param graphType the type for the graph
+   */
+  public GraphEvent(Object source, String graphString,
+		    String graphTitle, int graphType) {
+    super(source);
+    m_graphString = graphString;
+    m_graphTitle = graphTitle;
+    m_graphType = graphType;
+  }
+
+  /**
+   * Return the dot string for the graph
+   *
+   * @return a <code>String</code> value
+   */
+  public String getGraphString() {
+    return m_graphString;
+  }
+
+  /**
+   * Return the graph title
+   *
+   * @return a <code>String</code> value
+   */
+  public String getGraphTitle() {
+    return m_graphTitle;
+  }
+
+  /**
+   * Return the graph type
+   *
+   * @return a <code>int</code> value
+   */
+  public int getGraphType() {
+    return m_graphType;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/GraphListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/GraphListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/GraphListener.java	(revision 29)
@@ -0,0 +1,42 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Describe interface <code>TextListener</code> here.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public interface GraphListener extends EventListener {
+
+  /**
+   * Describe <code>acceptGraph</code> method here.
+   *
+   * @param e a <code>GraphEvent</code> value
+   */
+  void acceptGraph(GraphEvent e);
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewer.java	(revision 29)
@@ -0,0 +1,354 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphViewer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.graphvisualizer.BIFFormatException;
+import weka.gui.graphvisualizer.GraphVisualizer;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeVisualizer;
+
+import java.awt.BorderLayout;
+import java.awt.event.MouseEvent;
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * A bean encapsulating weka.gui.treevisualize.TreeVisualizer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.9 $
+ */
+public class GraphViewer 
+  extends JPanel
+  implements Visible, GraphListener,
+	     UserRequestAcceptor, 
+             Serializable, BeanContextChild {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5183121972114900617L;
+
+  protected BeanVisual m_visual;
+
+  private transient JFrame m_resultsFrame = null;
+
+  protected transient ResultHistoryPanel m_history;
+
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+
+  public GraphViewer() {
+    /*    setUpResultHistory();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER); */
+
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+  protected void appearanceDesign() {
+    setUpResultHistory();
+    removeAll();
+    m_visual = 
+      new BeanVisual("GraphViewer", 
+                     BeanVisual.ICON_PATH+"DefaultGraph.gif",
+		   BeanVisual.ICON_PATH+"DefaultGraph_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    setUpFinal();
+  }
+
+  protected void setUpFinal() {
+    setUpResultHistory();
+    add(m_history, BorderLayout.CENTER);
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Graphically visualize trees or graphs produced by classifiers/clusterers.";
+  }
+
+  private void setUpResultHistory() {
+    if (m_history == null) {
+      m_history = new ResultHistoryPanel(null);
+    }
+    m_history.setBorder(BorderFactory.createTitledBorder("Graph list"));
+    m_history.setHandleRightClicks(false);
+    m_history.getList().
+      addMouseListener(new ResultHistoryPanel.RMouseAdapter() {
+	  /** for serialization */
+	  private static final long serialVersionUID = -4984130887963944249L;
+
+	  public void mouseClicked(MouseEvent e) {
+	    int index = m_history.getList().locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = m_history.getNameAtIndex(index);
+	      doPopup(name);
+	    }
+	  }
+	});
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      java.awt.GraphicsEnvironment ge = 
+        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+      if (!ge.isHeadless()){
+        appearanceFinal();
+      }
+    }
+  }
+
+  /**
+   * Return the bean context (if any) that this bean is embedded in
+   *
+   * @return a <code>BeanContext</code> value
+   */
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  /**
+   * Add a vetoable change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void addVetoableChangeListener(String name,
+				       VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+  
+  /**
+   * Remove a vetoable change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void removeVetoableChangeListener(String name,
+					   VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+
+  /**
+   * Accept a graph
+   *
+   * @param e a <code>GraphEvent</code> value
+   */
+  public synchronized void acceptGraph(GraphEvent e) {
+
+    FastVector graphInfo = new FastVector();
+
+    if (m_history == null) {
+      setUpResultHistory();
+    }
+    String name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
+
+    name += e.getGraphTitle();
+    graphInfo.addElement(new Integer(e.getGraphType()));
+    graphInfo.addElement(e.getGraphString());
+    m_history.addResult(name, new StringBuffer());
+    m_history.addObject(name, graphInfo);
+  }
+
+  /**
+   * Set the visual appearance of this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual appearance of this bean
+   *
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance
+   *
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultGraph.gif",
+		       BeanVisual.ICON_PATH+"DefaultGraph_animated.gif");
+  }
+
+  /**
+   * Popup a result list from which the user can select a graph to view
+   *
+   */
+  public void showResults() {
+    if (m_resultsFrame == null) {
+      if (m_history == null) {
+	setUpResultHistory();
+      }
+      m_resultsFrame = new JFrame("Graph Viewer");
+      m_resultsFrame.getContentPane().setLayout(new BorderLayout());
+      m_resultsFrame.getContentPane().add(m_history, BorderLayout.CENTER);
+      m_resultsFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    m_resultsFrame.dispose();
+	    m_resultsFrame = null;
+	  }
+	});
+      m_resultsFrame.pack();
+      m_resultsFrame.setVisible(true);
+    } else {
+      m_resultsFrame.toFront();
+    }
+  }
+
+  private void doPopup(String name) {
+
+    FastVector graph;  
+    String grphString;
+    int grphType;
+
+    graph = (FastVector)m_history.getNamedObject(name);
+    grphType = ((Integer)graph.firstElement()).intValue();
+    grphString = (String)graph.lastElement();
+
+    if(grphType == Drawable.TREE){
+        final javax.swing.JFrame jf = 
+            new javax.swing.JFrame("Weka Classifier Tree Visualizer: "+name);
+        jf.setSize(500,400);
+        jf.getContentPane().setLayout(new BorderLayout());
+        TreeVisualizer tv = 
+            new TreeVisualizer(null,
+			 grphString,
+			 new PlaceNode2());
+        jf.getContentPane().add(tv, BorderLayout.CENTER);
+        jf.addWindowListener(new java.awt.event.WindowAdapter() {
+            public void windowClosing(java.awt.event.WindowEvent e) {
+            jf.dispose();
+            }
+        });
+
+        jf.setVisible(true);
+    }
+    if(grphType == Drawable.BayesNet) {
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Classifier Graph Visualizer: "+name);
+      jf.setSize(500,400);
+      jf.getContentPane().setLayout(new BorderLayout());
+      GraphVisualizer gv = 
+	new GraphVisualizer();
+      try { 
+	gv.readBIF(grphString);
+      }
+      catch (BIFFormatException be) { 
+	System.err.println("unable to visualize BayesNet"); be.printStackTrace(); 
+      }
+      gv.layoutGraph();
+      jf.getContentPane().add(gv, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+            jf.dispose();
+	  }
+        });
+      
+      jf.setVisible(true);
+    }
+  }
+
+  /**
+   * Return an enumeration of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    newVector.addElement("Show results");
+
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) 
+    {
+    if (request.compareTo("Show results") == 0) {
+      showResults();
+    } else {
+      throw new 
+	IllegalArgumentException(request
+		    + " not supported (GraphViewer)");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/GraphViewerBeanInfo.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphViewerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the graph viewer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class GraphViewerBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluator.java	(revision 29)
@@ -0,0 +1,468 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalClassifierEvaluator.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.Evaluation;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.util.Vector;
+
+/**
+ * Bean that evaluates incremental classifiers
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5928 $
+ */
+public class IncrementalClassifierEvaluator
+  extends AbstractEvaluator
+  implements IncrementalClassifierListener,
+	     EventConstraints {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3105419818939541291L;
+
+  private transient Evaluation m_eval;
+
+  private transient Classifier m_classifier;
+  
+  private Vector m_listeners = new Vector();
+  private Vector m_textListeners = new Vector();
+
+  private Vector m_dataLegend = new Vector();
+
+  private ChartEvent m_ce = new ChartEvent(this);
+  private double [] m_dataPoint = new double[1];
+  private boolean m_reset = false;
+
+  private double m_min = Double.MAX_VALUE;
+  private double m_max = Double.MIN_VALUE;
+
+  // how often to report # instances processed to the log
+  private int m_statusFrequency = 100;
+  private int m_instanceCount = 0;
+
+  // output info retrieval and auc stats for each class (if class is nominal)
+  private boolean m_outputInfoRetrievalStats = false;
+
+  public IncrementalClassifierEvaluator() {
+     m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"IncrementalClassifierEvaluator.gif",
+		       BeanVisual.ICON_PATH
+		       +"IncrementalClassifierEvaluator_animated.gif");
+    m_visual.setText("IncrementalClassifierEvaluator");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Evaluate the performance of incrementally trained classifiers.";
+  }
+
+  /**
+   * Accepts and processes a classifier encapsulated in an incremental
+   * classifier event
+   *
+   * @param ce an <code>IncrementalClassifierEvent</code> value
+   */
+  public void acceptClassifier(final IncrementalClassifierEvent ce) {
+    try {
+      if (ce.getStatus() == IncrementalClassifierEvent.NEW_BATCH) {
+	//	m_eval = new Evaluation(ce.getCurrentInstance().dataset());
+	m_eval = new Evaluation(ce.getStructure());
+	m_eval.useNoPriors();
+	
+	m_dataLegend = new Vector();
+	m_reset = true;
+	m_dataPoint = new double[0];
+	Instances inst = ce.getStructure();
+	System.err.println("NEW BATCH");
+        m_instanceCount = 0;
+        if (m_logger != null) {
+          m_logger.statusMessage(statusMessagePrefix() 
+              + "IncrementalClassifierEvaluator: started processing...");
+          m_logger.logMessage("[IncrementalClassifierEvaluator]" +
+              statusMessagePrefix() + " started processing...");
+        }
+	/* if (inst.classIndex() >= 0) {
+	  if (inst.attribute(inst.classIndex()).isNominal()) {
+	    if (inst.isMissing(inst.classIndex())) {
+	      m_dataLegend.addElement("Confidence");
+	    } else {
+	      m_dataLegend.addElement("Accuracy");
+	    }
+	  } else {
+	    if (inst.isMissing(inst.classIndex())) {
+	      m_dataLegend.addElement("Prediction");
+	    } else {
+	      m_dataLegend.addElement("RRSE");
+	    }
+	  }
+	} */
+      } else {
+        if (m_instanceCount > 0 && m_instanceCount % m_statusFrequency == 0) {
+          if (m_logger != null) {
+            m_logger.statusMessage(statusMessagePrefix() + "Processed "
+                                   + m_instanceCount + " instances.");
+          }
+        }
+        m_instanceCount++;
+	Instance inst = ce.getCurrentInstance();
+	//	if (inst.attribute(inst.classIndex()).isNominal()) {
+	double [] dist = ce.getClassifier().distributionForInstance(inst);
+	double pred = 0;
+	if (!inst.isMissing(inst.classIndex())) {
+          if (m_outputInfoRetrievalStats) {
+            // store predictions so AUC etc can be output.
+            m_eval.evaluateModelOnceAndRecordPrediction(dist, inst);
+          } else {
+            m_eval.evaluateModelOnce(dist, inst);
+          }
+	} else {
+	  pred = ce.getClassifier().classifyInstance(inst);
+	}
+	if (inst.classIndex() >= 0) {
+	  // need to check that the class is not missing
+	  if (inst.attribute(inst.classIndex()).isNominal()) {
+	    if (!inst.isMissing(inst.classIndex())) {
+	      if (m_dataPoint.length < 2) {
+		m_dataPoint = new double[2];
+		m_dataLegend.addElement("Accuracy");
+		m_dataLegend.addElement("RMSE (prob)");
+	      }
+	      //		int classV = (int) inst.value(inst.classIndex());
+	      m_dataPoint[1] = m_eval.rootMeanSquaredError();
+	      //  		int maxO = Utils.maxIndex(dist);
+	      //  		if (maxO == classV) {
+	      //  		  dist[classV] = -1;
+	      //  		  maxO = Utils.maxIndex(dist);
+	      //  		}
+	      //  		m_dataPoint[1] -= dist[maxO];
+	    } else {
+	      if (m_dataPoint.length < 1) {
+		m_dataPoint = new double[1];
+		m_dataLegend.addElement("Confidence");
+	      }
+	    }
+	    double primaryMeasure = 0;
+	    if (!inst.isMissing(inst.classIndex())) {
+	      primaryMeasure = 1.0 - m_eval.errorRate();
+	    } else {
+	      // record confidence as the primary measure
+	      // (another possibility would be entropy of
+	      // the distribution, or perhaps average
+	      // confidence)
+	      primaryMeasure = dist[Utils.maxIndex(dist)];
+	    }
+	    //	    double [] dataPoint = new double[1];
+	    m_dataPoint[0] = primaryMeasure;
+	    //	    double min = 0; double max = 100;
+	    /*	    ChartEvent e = 
+		    new ChartEvent(IncrementalClassifierEvaluator.this, 
+		    m_dataLegend, min, max, dataPoint); */
+	    m_ce.setLegendText(m_dataLegend);
+	    m_ce.setMin(0); m_ce.setMax(1);
+	    m_ce.setDataPoint(m_dataPoint);
+	    m_ce.setReset(m_reset);
+	    m_reset = false;
+	  } else {
+	    // numeric class
+	    if (m_dataPoint.length < 1) {
+	      m_dataPoint = new double[1];
+	      if (inst.isMissing(inst.classIndex())) {
+		m_dataLegend.addElement("Prediction");
+	      } else {
+		m_dataLegend.addElement("RMSE");
+	      }
+	    }
+	    if (!inst.isMissing(inst.classIndex())) {
+	      double update;
+	      if (!inst.isMissing(inst.classIndex())) {
+		update = m_eval.rootMeanSquaredError();
+	      } else {
+		update = pred;
+	      }
+	      m_dataPoint[0] = update;
+	      if (update > m_max) {
+		  m_max = update;
+	      }
+	      if (update < m_min) {
+		m_min = update;
+	      }
+	    }
+	    
+	    m_ce.setLegendText(m_dataLegend);
+	    m_ce.setMin((inst.isMissing(inst.classIndex()) 
+			 ? m_min
+			 : 0)); 
+	    m_ce.setMax(m_max);
+	    m_ce.setDataPoint(m_dataPoint);
+	    m_ce.setReset(m_reset);
+	    m_reset = false;
+	  }
+	  notifyChartListeners(m_ce);
+
+	  if (ce.getStatus() == IncrementalClassifierEvent.BATCH_FINISHED) {
+            if (m_logger != null) {
+              m_logger.logMessage("[IncrementalClassifierEvaluator]"
+                  + statusMessagePrefix() + " Finished processing.");
+              m_logger.statusMessage(statusMessagePrefix() + "Done.");
+            }
+	    if (m_textListeners.size() > 0) {
+	      String textTitle = ce.getClassifier().getClass().getName();
+	      textTitle = 
+		textTitle.substring(textTitle.lastIndexOf('.')+1,
+				    textTitle.length());
+	      String results = "=== Performance information ===\n\n"
+		+  "Scheme:   " + textTitle + "\n"
+		+  "Relation: "+ inst.dataset().relationName() + "\n\n"
+		+ m_eval.toSummaryString();
+              if (inst.classIndex() >= 0 && 
+                  inst.classAttribute().isNominal() &&
+                  (m_outputInfoRetrievalStats)) {
+                results += "\n" + m_eval.toClassDetailsString();
+              }
+
+              if (inst.classIndex() >= 0 && 
+                  inst.classAttribute().isNominal()) {
+                results += "\n" + m_eval.toMatrixString();
+              }
+	      textTitle = "Results: " + textTitle;
+	      TextEvent te = 
+		new TextEvent(this, 
+			      results,
+			    textTitle);
+	      notifyTextListeners(te);
+	    }
+	  }
+	}
+      }
+    } catch (Exception ex) {
+      if (m_logger != null) {
+        m_logger.logMessage("[IncrementalClassifierEvaluator]"
+            + statusMessagePrefix() + " Error processing prediction " 
+            + ex.getMessage());
+        m_logger.statusMessage(statusMessagePrefix() 
+            + "ERROR: problem processing prediction (see log for details)");
+      }
+      ex.printStackTrace();
+      stop();
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).
+	  eventGeneratable("incrementalClassifier")) {
+	return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Stop all action
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  private void notifyChartListeners(ChartEvent ce) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_listeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((ChartListener)l.elementAt(i)).acceptDataPoint(ce);
+      }
+    }
+  }
+
+  /**
+   * Notify all text listeners of a TextEvent
+   *
+   * @param te a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent te) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	//	System.err.println("Notifying text listeners "
+	//			   +"(ClassifierPerformanceEvaluator)");
+	((TextListener)l.elementAt(i)).acceptText(te);
+      }
+    }
+  }
+
+  /**
+   * Set how often progress is reported to the status bar.
+   * 
+   * @param s report progress every s instances
+   */
+  public void setStatusFrequency(int s) {
+    m_statusFrequency = s;
+  }
+
+  /**
+   * Get how often progress is reported to the status bar.
+   * 
+   * @return after how many instances, progress is reported to the
+   * status bar
+   */
+  public int getStatusFrequency() {
+    return m_statusFrequency;
+  }
+
+  /**
+   * Return a tip text string for this property
+   * 
+   * @return a string for the tip text
+   */
+  public String statusFrequencyTipText() {
+    return "How often to report progress to the status bar.";
+  }
+
+  /**
+   * Set whether to output per-class information retrieval
+   * statistics (nominal class only).
+   * 
+   * @param i true if info retrieval stats are to be output
+   */
+  public void setOutputPerClassInfoRetrievalStats(boolean i) {
+    m_outputInfoRetrievalStats = i;
+  }
+
+  /**
+   * Get whether per-class information retrieval stats are to be output.
+   * 
+   * @return true if info retrieval stats are to be output
+   */
+  public boolean getOutputPerClassInfoRetrievalStats() {
+    return m_outputInfoRetrievalStats;
+  }
+
+  /**
+   * Return a tip text string for this property
+   * 
+   * @return a string for the tip text
+   */
+  public String outputPerClassInfoRetrievalStatsTipText() {
+    return "Output per-class info retrieval stats. If set to true, predictions get "
+      +"stored so that stats such as AUC can be computed. Note: this consumes some memory.";
+  }
+
+  /**
+   * Add a chart listener
+   *
+   * @param cl a <code>ChartListener</code> value
+   */
+  public synchronized void addChartListener(ChartListener cl) {
+    m_listeners.addElement(cl);
+  }
+
+  /**
+   * Remove a chart listener
+   *
+   * @param cl a <code>ChartListener</code> value
+   */
+  public synchronized void removeChartListener(ChartListener cl) {
+    m_listeners.remove(cl);
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorBeanInfo.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalClassifierEvaluatorBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the incremental classifier evaluator bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public class IncrementalClassifierEvaluatorBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Return the property descriptors for this bean
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      PropertyDescriptor p2;
+      p1 = new PropertyDescriptor("statusFrequency", IncrementalClassifierEvaluator.class);
+      p2 = new PropertyDescriptor("outputPerClassInfoRetrievalStats", 
+                                  IncrementalClassifierEvaluator.class);
+      PropertyDescriptor [] pds = { p1, p2 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+	new EventSetDescriptor(IncrementalClassifierEvaluator.class, 
+			       "chart", 
+			       ChartListener.class, 
+			       "acceptDataPoint"),
+	new EventSetDescriptor(IncrementalClassifierEvaluator.class,
+			       "text",
+			       TextListener.class,
+			       "acceptText") 
+      };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Return the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.IncrementalClassifierEvaluator.class,
+			      IncrementalClassifierEvaluatorCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvaluatorCustomizer.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalClassifierEvaluatorCustomizer.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JPanel;
+
+/**
+ * GUI Customizer for the incremental classifier evaluator bean
+ *
+ * @author Mark Hall (mhall{[at}]pentaho{[dot]}org
+ * @version $Revision: 5563 $
+ */
+public class IncrementalClassifierEvaluatorCustomizer
+  extends JPanel
+  implements Customizer {
+
+  /** for serialization */
+  //  private static final long serialVersionUID = 1229878140258668581L;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private PropertySheetPanel m_ieEditor = 
+    new PropertySheetPanel();
+
+  public IncrementalClassifierEvaluatorCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+
+    setLayout(new BorderLayout());
+    add(m_ieEditor, BorderLayout.CENTER);
+    add(new javax.swing.JLabel("IncrementalClassifierEvaluatorCustomizer"), 
+	BorderLayout.NORTH);
+  }
+  
+  /**
+   * Set the object to be edited
+   *
+   * @param object a IncrementalClassifierEvaluator object
+   */
+  public void setObject(Object object) {
+    m_ieEditor.setTarget((IncrementalClassifierEvaluator)object);
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierEvent.java	(revision 29)
@@ -0,0 +1,162 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalClassifierEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.util.EventObject;
+
+/**
+ * Class encapsulating an incrementally built classifier and current instance
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5928 $
+ * @since 1.0
+ * @see EventObject
+ */
+public class IncrementalClassifierEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 28979464317643232L;
+
+  public static final int NEW_BATCH = 0;
+  public static final int WITHIN_BATCH = 1;
+  public static final int BATCH_FINISHED = 2;
+
+  private Instances m_structure;
+  private int m_status;
+  protected Classifier m_classifier;
+  protected Instance m_currentInstance;
+
+  /**
+   * Creates a new <code>IncrementalClassifierEvent</code> instance.
+   *
+   * @param source the source of the event
+   * @param scheme the classifier
+   * @param currentI the current instance
+   * @param status the status
+   */
+  public IncrementalClassifierEvent(Object source, Classifier scheme,
+			 Instance currentI, int status) {
+    super(source);
+    //    m_trainingSet = trnI;
+    m_classifier = scheme;
+    m_currentInstance = currentI;
+    m_status = status;
+  }
+
+  /**
+   * Creates a new incremental classifier event that encapsulates
+   * header information and classifier.
+   *
+   * @param source an <code>Object</code> value
+   * @param scheme a <code>Classifier</code> value
+   * @param structure an <code>Instances</code> value
+   */
+  public IncrementalClassifierEvent(Object source, Classifier scheme,
+				    Instances structure) {
+    super(source);
+    m_structure = structure;
+    m_status = NEW_BATCH;
+    m_classifier = scheme;
+  }
+
+  public IncrementalClassifierEvent(Object source) {
+    super(source);
+  }
+
+  /**
+   * Get the classifier
+   *
+   * @return the classifier
+   */
+  public Classifier getClassifier() {
+    return m_classifier;
+  }
+  
+  public void setClassifier(Classifier c) {
+    m_classifier = c;
+  }
+
+  /**
+   * Get the current instance
+   *
+   * @return the current instance
+   */
+  public Instance getCurrentInstance() {
+    return m_currentInstance;
+  }
+
+  /**
+   * Set the current instance for this event
+   *
+   * @param i an <code>Instance</code> value
+   */
+  public void setCurrentInstance(Instance i) {
+    m_currentInstance = i;
+  }
+
+  /**
+   * Get the status
+   *
+   * @return an <code>int</code> value
+   */
+  public int getStatus() {
+    return m_status;
+  }
+
+  /**
+   * Set the status
+   *
+   * @param s an <code>int</code> value
+   */
+  public void setStatus(int s) {
+    m_status = s;
+  }
+
+  /**
+   * Set the instances structure
+   *
+   * @param structure an <code>Instances</code> value
+   */
+  public void setStructure(Instances structure) {
+    m_structure = structure;
+    m_currentInstance = null;
+    m_status = NEW_BATCH;
+  }
+
+  /**
+   * Get the instances structure (may be null if this is not
+   * a NEW_BATCH event)
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getStructure() {
+    return m_structure;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/IncrementalClassifierListener.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    IncrementalClassifierListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can process a IncrementalClassifierEvent
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface IncrementalClassifierListener extends EventListener {
+  
+  /**
+   * Accept the event
+   *
+   * @param e a <code>ClassifierEvent</code> value
+   */
+  void acceptClassifier(IncrementalClassifierEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/InstanceEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/InstanceEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/InstanceEvent.java	(revision 29)
@@ -0,0 +1,138 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.util.EventObject;
+
+/**
+ * Event that encapsulates a single instance or header information only
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.5 $
+ * @see EventObject
+ */
+public class InstanceEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6104920894559423946L;
+  
+  public static final int FORMAT_AVAILABLE = 0;
+  public static final int INSTANCE_AVAILABLE = 1;
+  public static final int BATCH_FINISHED = 2;
+  
+  private Instances m_structure;
+  private Instance m_instance;
+  private int m_status;
+
+  /**
+   * Creates a new <code>InstanceEvent</code> instance that encapsulates
+   * a single instance only.
+   *
+   * @param source the source of the event
+   * @param instance the instance
+   * @param status status code (either INSTANCE_AVAILABLE or BATCH_FINISHED)
+   */
+  public InstanceEvent(Object source, Instance instance, int status) {
+    super(source);
+    m_instance = instance;
+    m_status = status;
+  }
+
+  /**
+   * Creates a new <code>InstanceEvent</code> instance which encapsulates
+   * header information only.
+   *
+   * @param source an <code>Object</code> value
+   * @param structure an <code>Instances</code> value
+   */
+  public InstanceEvent(Object source, Instances structure) {
+    super(source);
+    m_structure = structure;
+    m_status = FORMAT_AVAILABLE;
+  }
+
+  public InstanceEvent(Object source) {
+    super(source);
+  }
+  
+  /**
+   * Get the instance
+   *
+   * @return an <code>Instance</code> value
+   */
+  public Instance getInstance() {
+    return m_instance;
+  }
+  
+  /**
+   * Set the instance
+   *
+   * @param i an <code>Instance</code> value
+   */
+  public void setInstance(Instance i) {
+    m_instance = i;
+  }
+
+  /**
+   * Get the status
+   *
+   * @return an <code>int</code> value
+   */
+  public int getStatus() {
+    return m_status;
+  }
+
+  /**
+   * Set the status
+   *
+   * @param s an <code>int</code> value
+   */
+  public void setStatus(int s) {
+    m_status = s;
+  }
+
+  /**
+   * Set the instances structure
+   *
+   * @param structure an <code>Instances</code> value
+   */
+  public void setStructure(Instances structure) {
+    m_structure = structure;
+    m_instance = null;
+    m_status = FORMAT_AVAILABLE;
+  }
+
+  /**
+   * Get the instances structure (may be null if this is not
+   * a FORMAT_AVAILABLE event)
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getStructure() {
+    return m_structure;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/InstanceListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/InstanceListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/InstanceListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accept instance events
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface InstanceListener extends EventListener {
+  
+  /**
+   * Accept and process an instance event
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  void acceptInstance(InstanceEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMaker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMaker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMaker.java	(revision 29)
@@ -0,0 +1,307 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceStreamToBatchMaker.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.util.ArrayList;
+
+import javax.swing.JPanel;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.gui.Logger;
+
+/**
+ * Bean that converts an instance stream into a (batch) data set.
+ * Useful in conjunction with the Reservoir sampling filter.
+ * 
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class InstanceStreamToBatchMaker extends JPanel 
+  implements BeanCommon, Visible, InstanceListener, 
+  EventConstraints, DataSource {
+  
+  /**
+   * For serialization
+   */
+  private static final long serialVersionUID = -7037141087208627799L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("InstanceStreamToBatchMaker",
+                   BeanVisual.ICON_PATH+"InstanceStreamToBatchMaker.gif",
+                   BeanVisual.ICON_PATH+"InstanceStreamToBatchMaker_animated.gif");
+  
+  /**
+   * The log.
+   */
+  private transient Logger m_log;
+  
+  /**
+   * Component connected to us.
+   */
+  private Object m_listenee;
+  
+  private ArrayList<DataSourceListener> m_dataListeners = 
+    new ArrayList<DataSourceListener>();
+  
+  /**
+   * Collects up the instances. 
+   */
+  private ArrayList<Instance> m_batch;
+  
+  private Instances m_structure;
+  
+  public InstanceStreamToBatchMaker() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+  
+  /**
+   * Accept an instance to add to the batch.
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  public void acceptInstance(InstanceEvent e) {
+    if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+      m_batch = new ArrayList<Instance>();
+      m_structure = e.getStructure();
+      
+      // notify dataset listeners of structure available
+      if (m_log != null) {
+        m_log.logMessage("[InstanceStreamToBatch] passing on structure.");
+      }
+      DataSetEvent dse = new DataSetEvent(this, m_structure);
+      notifyDataListeners(dse);
+    } else if (e.getStatus() == InstanceEvent.INSTANCE_AVAILABLE) {
+      m_batch.add(e.getInstance());
+    } else {
+      // batch finished
+      
+      // add the last instance
+      m_batch.add(e.getInstance());
+      
+      // create the new Instances
+      Instances dataSet = new Instances(m_structure, m_batch.size());
+      for (Instance i : m_batch) {
+        dataSet.add(i);
+      }
+      dataSet.compactify();
+      
+      // save memory
+      m_batch = null;
+      
+      if (m_log != null) {
+        m_log.logMessage("[InstanceStreamToBatch] sending batch to listeners.");
+      }
+      
+      // notify dataset listeners
+      DataSetEvent dse = new DataSetEvent(this, dataSet);
+      notifyDataListeners(dse);
+    }
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the named event
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    if (m_listenee != null || !eventName.equals("instance")) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the named event
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void connectionNotification(String eventName, Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void disconnectionNotification(String eventName, Object source) {
+    m_listenee = null;
+  }
+  
+  /**
+   * Returns true if, at the current time, the named event could be
+   * generated.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (!eventName.equals("dataSet")) {
+      return false;
+    }
+    
+    if (m_listenee == null) {
+      return false;
+    }
+    
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).eventGeneratable("instance")) {
+        return false;
+      }
+    }
+    
+    return true;
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Stop any action (if possible).
+   */
+  public void stop() {
+    // not much we can do. Stopping depends on upstream components.
+  }
+
+  /**
+   * Gets the visual appearance of this wrapper bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Sets the visual appearance of this wrapper bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"InstanceStreamToBatchMaker.gif",
+        BeanVisual.ICON_PATH+"InstanceStreamToBatchMaker_animated.gif");
+  }
+  
+  /**
+   * Notify all data source listeners.
+   * 
+   * @param tse the DataSetEvent to pass on.
+   */
+  protected void notifyDataListeners(DataSetEvent tse) {
+    ArrayList<DataSourceListener> l;
+    synchronized (this) {
+      l = (ArrayList<DataSourceListener>)m_dataListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        l.get(i).acceptDataSet(tse);
+      }
+    }
+  }
+
+  public synchronized void addDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.add(tsl);
+    // pass on any format that we might know about
+    if (m_structure != null) {
+      DataSetEvent e = new DataSetEvent(this, m_structure);
+      tsl.acceptDataSet(e);
+    }
+  }
+
+  public synchronized void removeDataSourceListener(DataSourceListener tsl) {
+    m_dataListeners.remove(tsl);
+  }
+  
+  public synchronized void addInstanceListener(InstanceListener il) {
+    // we don't produce instance events
+  }
+  
+  public synchronized void removeInstanceListener(InstanceListener il) {
+    // we don't produce instance events
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMakerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMakerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/InstanceStreamToBatchMakerBeanInfo.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceStreamToBatchMakerBeanInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * BeanInfo class for the InstanceStreamToBatchMaker bean
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class InstanceStreamToBatchMakerBeanInfo
+  extends SimpleBeanInfo {
+
+  /**
+   * Returns the event set descriptors
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = 
+      { new EventSetDescriptor(DataSource.class, 
+			       "dataSet", 
+			       DataSourceListener.class, 
+			       "acceptDataSet")};
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlow.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlow.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlow.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KnowledgeFlow.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Startup class for the KnowledgeFlow. Displays a splash screen.
+ *
+ * @author Mark Hall
+ * @version  $Revision: 4717 $
+ */
+public class KnowledgeFlow {
+
+  /**
+   * Static method that can be called from a running program
+   * to launch the KnowledgeFlow
+   */
+  public static void startApp() {
+    KnowledgeFlowApp.addStartupListener(new StartUpListener() {
+        public void startUpComplete() {
+          weka.gui.SplashWindow.disposeSplash();
+        }
+      });
+                                        
+    weka.gui.SplashWindow.splash(ClassLoader.
+                                 getSystemResource("weka/gui/beans/icons/splash.jpg"));
+
+    Thread nt = new Thread() {
+        public void run() {
+          weka.gui.SplashWindow.invokeMethod("weka.gui.beans.KnowledgeFlowApp", 
+                                             "createSingleton", null);
+        }};
+      nt.start();
+  }
+
+    /**
+     * Shows the splash screen, launches the application and then disposes
+     * the splash screen.
+     * @param args the command line arguments
+     */
+    public static void main(String[] args) {
+      weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+      weka.gui.SplashWindow.splash(ClassLoader.
+                                   getSystemResource("weka/gui/beans/icons/splash.jpg"));
+      weka.gui.SplashWindow.invokeMain("weka.gui.beans.KnowledgeFlowApp", args);
+      weka.gui.SplashWindow.disposeSplash();
+    }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlowApp.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlowApp.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/KnowledgeFlowApp.java	(revision 29)
@@ -0,0 +1,3169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    KnowledgeFlowApp.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.ClassloaderUtil;
+import weka.core.Copyright;
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Memory;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.xml.KOML;
+import weka.core.xml.XStream;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.GenericPropertiesCreator;
+import weka.gui.HierarchyPropertyParser;
+import weka.gui.LookAndFeel;
+import weka.gui.beans.xml.XMLBeans;
+import weka.gui.visualize.PrintablePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.MenuItem;
+import java.awt.Point;
+import java.awt.PopupMenu;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.image.BufferedImage;
+import java.beans.BeanInfo;
+import java.beans.Beans;
+import java.beans.Customizer;
+import java.beans.EventSetDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.Vector;
+
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+import javax.swing.JWindow;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * Main GUI class for the KnowledgeFlow. Modifications to allow interoperability
+ * with swt provided by Davide Zerbetto (davide dot zerbetto at eng dot it).
+ *
+ * @author Mark Hall
+ * @version  $Revision: 6140 $
+ * @since 1.0
+ * @see JPanel
+ * @see PropertyChangeListener
+ */
+public class KnowledgeFlowApp
+  extends JPanel
+  implements PropertyChangeListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7064906770289728431L;
+
+  /**
+   * Location of the property file for the KnowledgeFlowApp
+   */
+  protected static String PROPERTY_FILE = "weka/gui/beans/Beans.props";
+
+  /** Contains the editor properties */
+  protected static Properties BEAN_PROPERTIES;
+
+  private static ArrayList<Properties> BEAN_PLUGINS_PROPERTIES;
+
+  /**
+   * Holds the details needed to construct button bars for various supported
+   * classes of weka algorithms/tools 
+   */
+  private static Vector TOOLBARS = new Vector();
+
+  /**
+   * Loads KnowledgeFlow properties and any plugins (adds jars to
+   * the classpath)
+   */
+  public static void loadProperties() {
+    if (BEAN_PROPERTIES == null) {
+      System.out.println("[KnowledgeFlow] Loading properties and plugins...");
+      /** Loads the configuration property file */
+      //  static {
+      // Allow a properties file in the current directory to override
+      try {
+        BEAN_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+        java.util.Enumeration keys =
+          (java.util.Enumeration)BEAN_PROPERTIES.propertyNames();
+        if (!keys.hasMoreElements()) {
+          throw new Exception( "Could not read a configuration file for the bean\n"
+                               +"panel. An example file is included with the Weka distribution.\n"
+                               +"This file should be named \"" + PROPERTY_FILE + "\" and\n"
+                               +"should be placed either in your user home (which is set\n"
+                               + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+                               + "or the directory that java was started from\n");
+        }
+      } catch (Exception ex) {
+        JOptionPane.showMessageDialog(null,
+                                      ex.getMessage(),
+                                      "KnowledgeFlow",
+                                      JOptionPane.ERROR_MESSAGE);
+      }
+
+
+      // try and load any plugin beans properties
+      File pluginDir = new File(System.getProperty("user.home")
+                                +File.separator+".knowledgeFlow"
+                                +File.separator+"plugins");
+      if (pluginDir.exists() && pluginDir.isDirectory()) {
+        BEAN_PLUGINS_PROPERTIES = new ArrayList<Properties>();
+        // How many sub-dirs are there?
+        File[] contents = pluginDir.listFiles();
+        for (int i = 0; i < contents.length; i++) {
+          if (contents[i].isDirectory() && 
+              contents[i].listFiles().length > 0) {
+            try {      
+              Properties tempP = new Properties();
+              File propFile = new File(contents[i].getPath()
+                                       + File.separator
+                                       + "Beans.props");
+              tempP.load(new FileInputStream(propFile));
+              BEAN_PLUGINS_PROPERTIES.add(tempP);
+
+              // Now try and add all jar files in this directory to the classpath
+              File anyJars[] = contents[i].listFiles();
+              for (int j = 0; j < anyJars.length; j++) {
+                if (anyJars[j].getPath().endsWith(".jar")) {
+                  System.out.println("[KnowledgeFlow] Plugins: adding "+anyJars[j].getPath()
+                                     +" to classpath...");
+                  ClassloaderUtil.addFile(anyJars[j].getPath());
+                }
+              }
+            } catch (Exception ex) {
+              // Don't make a fuss
+              System.err.println("[KnowledgeFlow] Warning: Unable to load bean properties for plugin "
+                                 +"directory: " + contents[i].getPath());
+            }
+          }
+          //        BEAN_PLUGINS_PROPERTIES = new Properties();
+          //        BEAN_PLUGINS_PROPERTIES.load(new FileInputStream(pluginDir));
+        }
+      } else {
+        // make the plugin directory for the user
+        pluginDir.mkdir();
+      }
+    }
+  }
+
+  /**
+   * Initializes the temporary files necessary to construct the toolbars
+   * from.
+   */
+  private static void init() {
+    System.out.println("[KnowledgeFlow] Initializing KF...");
+
+    try {
+      TreeMap wrapList = new TreeMap();
+      GenericPropertiesCreator creator = new GenericPropertiesCreator();
+      Properties GEOProps = null;
+
+      if (creator.useDynamic()) {
+        creator.execute(false);
+        /* now process the keys in the GenericObjectEditor.props. For each
+           key that has an entry in the Beans.props associating it with a
+           bean component a button tool bar will be created */
+        GEOProps = creator.getOutputProperties();
+      } else {
+        // Read the static information from the GenericObjectEditor.props
+        GEOProps = Utils.readProperties("weka/gui/GenericObjectEditor.props");
+      }
+      Enumeration en = GEOProps.propertyNames();
+      while (en.hasMoreElements()) {
+	String geoKey = (String)en.nextElement();
+
+	// try to match this key with one in the Beans.props file
+	String beanCompName = BEAN_PROPERTIES.getProperty(geoKey);
+	if (beanCompName != null) {
+	  // add details necessary to construct a button bar for this class
+	  // of algorithms
+	  Vector newV = new Vector();
+	  // check for a naming alias for this toolbar
+	  String toolBarNameAlias = 
+	    BEAN_PROPERTIES.getProperty(geoKey+".alias");
+	  String toolBarName = (toolBarNameAlias != null) ?
+	    toolBarNameAlias :
+	    geoKey.substring(geoKey.lastIndexOf('.')+1, geoKey.length());
+
+          // look for toolbar ordering information for this wrapper type
+          String order = 
+            BEAN_PROPERTIES.getProperty(geoKey+".order");
+          Integer intOrder = (order != null) ?
+            new Integer(order) :
+            new Integer(0);
+            
+	  // Name for the toolbar (name of weka algorithm class)
+	  newV.addElement(toolBarName);
+	  // Name of bean capable of handling this class of algorithm
+	  newV.addElement(beanCompName);
+
+	  // add the root package for this key
+	  String rootPackage = geoKey.substring(0, geoKey.lastIndexOf('.'));
+
+	  newV.addElement(rootPackage);
+
+	  // All the weka algorithms of this class of algorithm
+	  String wekaAlgs = GEOProps.getProperty(geoKey);
+
+          Hashtable roots = GenericObjectEditor.sortClassesByRoot(wekaAlgs);
+          Hashtable hpps = new Hashtable();
+          Enumeration enm = roots.keys();
+          while (enm.hasMoreElements()) {
+            String root = (String) enm.nextElement();
+            String classes = (String) roots.get(root);
+            weka.gui.HierarchyPropertyParser hpp = 
+              new weka.gui.HierarchyPropertyParser();
+            hpp.build(classes, ", ");
+            //            System.err.println(hpp.showTree());
+            hpps.put(root, hpp);
+          }
+
+	  //------ test the HierarchyPropertyParser
+          /*  weka.gui.HierarchyPropertyParser hpp = 
+	    new weka.gui.HierarchyPropertyParser();
+	  hpp.build(wekaAlgs, ", ");
+
+	  System.err.println(hpp.showTree()); */
+	  // ----- end test the HierarchyPropertyParser
+          //	  newV.addElement(hpp); // add the hierarchical property parser
+	  newV.addElement(hpps); // add the hierarchical property parser
+
+	  StringTokenizer st = new StringTokenizer(wekaAlgs, ", ");
+	  while (st.hasMoreTokens()) {
+	    String current = st.nextToken().trim();
+	    newV.addElement(current);
+	  }
+          wrapList.put(intOrder, newV);
+          //	  TOOLBARS.addElement(newV);
+	}
+      }
+      Iterator keysetIt = wrapList.keySet().iterator();
+      while (keysetIt.hasNext()) {
+        Integer key = (Integer)keysetIt.next();
+        Vector newV = (Vector)wrapList.get(key);
+        if (newV != null) {
+          TOOLBARS.addElement(newV);
+        }
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+          "Could not read a configuration file for the generic objecte editor"
+         +". An example file is included with the Weka distribution.\n"
+         +"This file should be named \"GenericObjectEditor.props\" and\n"
+         +"should be placed either in your user home (which is set\n"
+         + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+         + "or the directory that java was started from\n",
+         "KnowledgeFlow",
+         JOptionPane.ERROR_MESSAGE);
+    }
+
+    try {
+      String standardToolBarNames = 
+	BEAN_PROPERTIES.
+	getProperty("weka.gui.beans.KnowledgeFlow.standardToolBars");
+      StringTokenizer st = new StringTokenizer(standardToolBarNames, ", ");
+       while (st.hasMoreTokens()) {
+	 String tempBarName = st.nextToken().trim();
+	 // construct details for this toolbar
+	 Vector newV = new Vector();
+	 // add the name of the toolbar
+	 newV.addElement(tempBarName);
+
+	 // indicate that this is a standard toolbar (no wrapper bean)
+	 newV.addElement("null");
+	 String toolBarContents = 
+	   BEAN_PROPERTIES.
+	   getProperty("weka.gui.beans.KnowledgeFlow."+tempBarName);
+	 StringTokenizer st2 = new StringTokenizer(toolBarContents, ", ");
+	 while (st2.hasMoreTokens()) {
+	   String tempBeanName = st2.nextToken().trim();
+	   newV.addElement(tempBeanName);
+	 }
+	 TOOLBARS.addElement(newV);
+       }       
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+				    ex.getMessage(),
+				    "KnowledgeFlow",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  } 
+  
+  /**
+   * Used for displaying the bean components and their visible
+   * connections
+   *
+   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+   * @version $Revision: 6140 $
+   * @since 1.0
+   * @see PrintablePanel
+   */
+  protected class BeanLayout
+    extends PrintablePanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -146377012429662757L;
+
+    public void paintComponent(Graphics gx) {
+      super.paintComponent(gx);
+      BeanInstance.paintLabels(gx);
+      BeanConnection.paintConnections(gx);
+      //      BeanInstance.paintConnections(gx);
+      if (m_mode == CONNECTING) {
+	gx.drawLine(m_startX, m_startY, m_oldX, m_oldY);
+      } else if (m_mode == SELECTING) {
+        gx.drawRect((m_startX < m_oldX) ? m_startX : m_oldX, 
+                    (m_startY < m_oldY) ? m_startY : m_oldY, 
+                    Math.abs(m_oldX-m_startX), Math.abs(m_oldY-m_startY));
+      }
+    }
+
+    public void doLayout() {
+      super.doLayout();
+      Vector comps = BeanInstance.getBeanInstances();
+      for (int i = 0; i < comps.size(); i++) {
+	BeanInstance bi = (BeanInstance)comps.elementAt(i);
+	JComponent c = (JComponent)bi.getBean();
+	Dimension d = c.getPreferredSize();
+	c.setBounds(bi.getX(), bi.getY(), d.width, d.height);
+	c.revalidate();
+      }
+    }
+  }
+
+  // Used for measuring and splitting icon labels
+  // over multiple lines
+  FontMetrics m_fontM;
+
+  // constants for operations in progress
+  protected static final int NONE = 0;
+  protected static final int MOVING = 1;
+  protected static final int CONNECTING = 2;
+  protected static final int ADDING = 3;
+  protected static final int SELECTING = 4;
+
+  // which operation is in progress
+  private int m_mode = NONE;
+
+  /** the extension for the user components, when serialized to XML */
+  protected final static String USERCOMPONENTS_XML_EXTENSION = ".xml";
+  
+  /**
+   * Button group to manage all toolbar buttons
+   */
+  private ButtonGroup m_toolBarGroup = new ButtonGroup();
+
+  /**
+   * Holds the selected toolbar bean
+   */
+  private Object m_toolBarBean;
+
+  /**
+   * The layout area
+   */
+  private BeanLayout m_beanLayout = new BeanLayout();
+
+  /**
+   * Tabbed pane to hold tool bars
+   */
+  private JTabbedPane m_toolBars = new JTabbedPane();
+
+  /**
+   * Stuff relating to plugin beans
+   */
+  private JToolBar m_pluginsToolBar = null;
+  private Box m_pluginsBoxPanel = null;
+  
+  /**
+   * Stuff relating to user created meta beans
+   */
+  private JToolBar m_userToolBar = null;
+  private Box m_userBoxPanel = null;
+  private Vector m_userComponents = new Vector();
+  private boolean m_firstUserComponentOpp = true;
+
+  private JToggleButton m_pointerB;
+  private JButton m_saveB;
+  private JButton m_loadB;
+  private JButton m_stopB;
+  private JButton m_helpB;
+  private JButton m_newB;
+
+  /**
+   * Reference to bean being manipulated
+   */
+  private BeanInstance m_editElement;
+
+  /**
+   * Event set descriptor for the bean being manipulated
+   */
+  private EventSetDescriptor m_sourceEventSetDescriptor;
+
+  /**
+   * Used to record screen coordinates during move, select and connect
+   * operations
+   */
+  private int m_oldX, m_oldY;
+  private int m_startX, m_startY;
+  
+  /** The file chooser for selecting layout files */
+  protected JFileChooser m_FileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  protected LogPanel m_logPanel = new LogPanel();//new LogPanel(null, true);
+
+  protected BeanContextSupport m_bcSupport = new BeanContextSupport();
+
+  /** the extension for the serialized setups (Java serialization) */
+  public final static String FILE_EXTENSION = ".kf";
+
+  /** the extension for the serialized setups (Java serialization) */
+  public final static String FILE_EXTENSION_XML = ".kfml";
+  
+  /** A filter to ensure only KnowledgeFlow files in binary format get shown in
+      the chooser */
+  protected FileFilter m_KfFilter = 
+    new ExtensionFileFilter(FILE_EXTENSION, 
+                            "Binary KnowledgeFlow configuration files (*" 
+                            + FILE_EXTENSION + ")");
+
+  /** A filter to ensure only KnowledgeFlow files in KOML format 
+      get shown in the chooser */
+  protected FileFilter m_KOMLFilter = 
+    new ExtensionFileFilter(KOML.FILE_EXTENSION + "kf", 
+                            "XML KnowledgeFlow configuration files (*" 
+                            + KOML.FILE_EXTENSION + "kf)");
+
+  /** A filter to ensure only KnowledgeFlow files in XStream format 
+      get shown in the chooser */
+  protected FileFilter m_XStreamFilter = 
+    new ExtensionFileFilter(XStream.FILE_EXTENSION + "kf", 
+                            "XML KnowledgeFlow configuration files (*" 
+                            + XStream.FILE_EXTENSION + "kf)");
+
+  /** A filter to ensure only KnowledgeFlow layout files in XML format get 
+      shown in the chooser */
+  protected FileFilter m_XMLFilter = 
+    new ExtensionFileFilter(FILE_EXTENSION_XML, 
+                            "XML KnowledgeFlow layout files (*" 
+                            + FILE_EXTENSION_XML + ")");
+
+  /** the scrollbar increment of the layout scrollpane */
+  protected int m_ScrollBarIncrementLayout = 20;
+
+  /** the scrollbar increment of the components scrollpane */
+  protected int m_ScrollBarIncrementComponents = 50;
+
+  /** the flow layout width */
+  protected int m_FlowWidth = 1024;
+
+  /** the flow layout height */
+  protected int m_FlowHeight = 768;
+
+  /** the preferred file extension */
+  protected String m_PreferredExtension = FILE_EXTENSION;
+  
+  /** whether to store the user components in XML or in binary format */
+  protected boolean m_UserComponentsInXML = false;
+  
+  /** Environment variables for the current flow */
+  protected Environment m_flowEnvironment = new Environment();
+  
+  /**
+   * Set the environment variables to use. NOTE: loading a new layout
+   * resets back to the default set of variables
+   * 
+   * @param env
+   */
+  public void setEnvironment(Environment env) {
+    m_flowEnvironment = env;
+    setEnvironment();
+  }
+  
+  private void setEnvironment() {
+    // pass m_flowEnvironment to all components
+    // that implement EnvironmentHandler
+    Vector beans = BeanInstance.getBeanInstances();
+    for (int i = 0; i < beans.size(); i++) {
+      Object temp = ((BeanInstance) beans.elementAt(i)).getBean();
+
+      if (temp instanceof EnvironmentHandler) {
+        ((EnvironmentHandler) temp).setEnvironment(m_flowEnvironment);
+      }
+    }
+  }
+  
+  /**
+   * Creates a new <code>KnowledgeFlowApp</code> instance.
+   */
+  // modifications by Zerbetto
+  //public KnowledgeFlowApp() {
+  public KnowledgeFlowApp(boolean showFileMenu) {
+    if (BEAN_PROPERTIES == null) {
+      loadProperties();
+      init();
+    }
+
+    m_showFileMenu = showFileMenu;
+
+    // end modifications by Zerbetto
+    // Grab a fontmetrics object
+    JWindow temp = new JWindow();
+    temp.setVisible(true);
+    temp.getGraphics().setFont(new Font(null, Font.PLAIN, 9));
+    m_fontM = temp.getGraphics().getFontMetrics();
+    temp.setVisible(false);
+
+    // some GUI defaults
+    try {
+      m_ScrollBarIncrementLayout = Integer.parseInt(
+          BEAN_PROPERTIES.getProperty(
+            "ScrollBarIncrementLayout", "" + m_ScrollBarIncrementLayout));
+      m_ScrollBarIncrementComponents = Integer.parseInt(
+          BEAN_PROPERTIES.getProperty(
+            "ScrollBarIncrementComponents", "" + m_ScrollBarIncrementComponents));
+      m_FlowWidth = Integer.parseInt(
+          BEAN_PROPERTIES.getProperty(
+            "FlowWidth", "" + m_FlowWidth));
+      m_FlowHeight = Integer.parseInt(
+          BEAN_PROPERTIES.getProperty(
+            "FlowHeight", "" + m_FlowHeight));
+      m_PreferredExtension = BEAN_PROPERTIES.getProperty(
+          "PreferredExtension", m_PreferredExtension);
+      m_UserComponentsInXML = Boolean.valueOf(
+          BEAN_PROPERTIES.getProperty(
+            "UserComponentsInXML", "" + m_UserComponentsInXML)).booleanValue();
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    // FileChooser
+    m_FileChooser.addChoosableFileFilter(m_KfFilter);
+    if (KOML.isPresent()) {
+      m_FileChooser.addChoosableFileFilter(m_KOMLFilter);
+    }
+    if (XStream.isPresent()) {
+      m_FileChooser.addChoosableFileFilter(m_XStreamFilter);
+    }
+
+    m_FileChooser.addChoosableFileFilter(m_XMLFilter);
+
+    if (m_PreferredExtension.equals(FILE_EXTENSION_XML)) {
+      m_FileChooser.setFileFilter(m_XMLFilter);
+    } else if (KOML.isPresent() && m_PreferredExtension.equals(KOML.FILE_EXTENSION + "kf")) {
+      m_FileChooser.setFileFilter(m_KOMLFilter);
+    } else if (XStream.isPresent() && m_PreferredExtension.equals(XStream.FILE_EXTENSION + "kf")) {
+      m_FileChooser.setFileFilter(m_XStreamFilter);
+    } else {
+      m_FileChooser.setFileFilter(m_KfFilter);
+    }
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    m_bcSupport.setDesignTime(true);
+    m_beanLayout.setLayout(null);
+    
+    // handle mouse events
+    m_beanLayout.addMouseListener(new MouseAdapter() {
+
+	public void mousePressed(MouseEvent me) {
+	  if (m_toolBarBean == null) {
+	    if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
+		 == InputEvent.BUTTON1_MASK) && m_mode == NONE) {
+	      BeanInstance bi = BeanInstance.findInstance(me.getPoint());
+	      JComponent bc = null;
+	      if (bi != null) {
+		bc = (JComponent)(bi.getBean());
+	      }
+	      if (bc != null && (bc instanceof Visible)) {
+		m_editElement = bi;
+		m_oldX = me.getX();
+		m_oldY = me.getY();
+		m_mode = MOVING;
+	      }
+              if (m_mode != MOVING) {
+                m_mode = SELECTING;
+                m_oldX = me.getX();
+                m_oldY = me.getY();
+                m_startX = m_oldX;
+                m_startY = m_oldY;
+                Graphics2D gx = (Graphics2D)m_beanLayout.getGraphics();
+                gx.setXORMode(java.awt.Color.white);
+                //                gx.drawRect(m_oldX, m_oldY, m_oldX, m_oldY);
+                //                gx.drawLine(m_startX, m_startY, m_startX, m_startY);
+                gx.dispose();
+                m_mode = SELECTING;
+              }
+	    }
+	  }
+	}
+
+	public void mouseReleased(MouseEvent me) {
+	  if (m_editElement != null && m_mode == MOVING) {
+	    m_editElement = null;
+	    revalidate();
+	    m_beanLayout.repaint();
+	    m_mode = NONE;
+	  }
+          if (m_mode == SELECTING) {
+            revalidate();
+            m_beanLayout.repaint();
+            m_mode = NONE;
+                        
+            checkSubFlow(m_startX, m_startY, me.getX(), me.getY());
+          }
+	}
+
+	public void mouseClicked(MouseEvent me) {
+	  BeanInstance bi = BeanInstance.findInstance(me.getPoint());
+	  if (m_mode == ADDING || m_mode == NONE) {
+	    // try and popup a context sensitive menu if we have
+	    // been clicked over a bean.
+	    if (bi != null) {
+	      JComponent bc = (JComponent)bi.getBean();
+              // if we've been double clicked, then popup customizer
+              // as long as we're not a meta bean
+              if (me.getClickCount() == 2 && !(bc instanceof MetaBean)) {
+                try {
+                  Class custClass = 
+                    Introspector.getBeanInfo(bc.getClass()).getBeanDescriptor().getCustomizerClass();
+                  if (custClass != null) {
+                    if (bc instanceof BeanCommon) {
+                      if (!((BeanCommon)bc).
+                          isBusy()) {
+                        popupCustomizer(custClass, bc);
+                      }
+                    } else {
+                      popupCustomizer(custClass, bc);
+                    }
+                  }
+                } catch (IntrospectionException ex) {
+                  ex.printStackTrace();
+                }
+              } else if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
+                          != InputEvent.BUTTON1_MASK) || me.isAltDown()) {
+		doPopup(me.getPoint(), bi, me.getX(), me.getY());
+	      }
+	    } else {
+	      if (((me.getModifiers() & InputEvent.BUTTON1_MASK)
+		   != InputEvent.BUTTON1_MASK) || me.isAltDown()) {
+		// find connections if any close to this point
+		int delta = 10;
+		deleteConnectionPopup(BeanConnection.
+		      getClosestConnections(new Point(me.getX(), me.getY()), 
+					    delta), me.getX(), me.getY());
+	      } else if (m_toolBarBean != null) {
+		// otherwise, if a toolbar button is active then 
+		// add the component
+		addComponent(me.getX(), me.getY());
+	      }
+	    }
+	  }
+	
+	  if (m_mode == CONNECTING) {
+	    // turn off connecting points and remove connecting line
+	    m_beanLayout.repaint();
+	    Vector beanInstances = BeanInstance.getBeanInstances();
+	    for (int i = 0; i < beanInstances.size(); i++) {
+	      JComponent bean = 
+		(JComponent)((BeanInstance)beanInstances.elementAt(i)).
+		getBean();
+	      if (bean instanceof Visible) {
+		((Visible)bean).getVisual().setDisplayConnectors(false);
+	      }
+	    }
+
+	    if (bi != null) {
+	      boolean doConnection = false;
+	      if (!(bi.getBean() instanceof BeanCommon)) {
+		doConnection = true;
+	      } else {
+		// Give the target bean a chance to veto the proposed
+		// connection
+		if (((BeanCommon)bi.getBean()).
+		    //connectionAllowed(m_sourceEventSetDescriptor.getName())) {
+		    connectionAllowed(m_sourceEventSetDescriptor)) {
+		  doConnection = true;
+		}
+	      }
+	      if (doConnection) {
+		// attempt to connect source and target beans
+
+                if (bi.getBean() instanceof MetaBean) {
+                  BeanConnection.doMetaConnection(m_editElement, bi,
+                                                  m_sourceEventSetDescriptor,
+                                                  m_beanLayout);
+                } else {
+                  BeanConnection bc = 
+                    new BeanConnection(m_editElement, bi, 
+                                       m_sourceEventSetDescriptor);
+                }
+	      }
+	      m_beanLayout.repaint();
+	    }
+	    m_mode = NONE;
+	    m_editElement = null;
+	    m_sourceEventSetDescriptor = null;
+	  }
+	}
+      });
+    
+     m_beanLayout.addMouseMotionListener(new MouseMotionAdapter() {
+
+	public void mouseDragged(MouseEvent me) {
+	  if (m_editElement != null && m_mode == MOVING) {
+	    ImageIcon ic = ((Visible)m_editElement.getBean()).
+	      getVisual().getStaticIcon();
+	    int width = ic.getIconWidth() / 2;
+	    int height = ic.getIconHeight() / 2;
+
+            /*	    m_editElement.setX(m_oldX-width);
+                    m_editElement.setY(m_oldY-height); */
+
+            m_editElement.setXY(m_oldX-width,
+                                m_oldY-height);
+	    m_beanLayout.repaint();
+	    
+	    // note the new points
+	    m_oldX = me.getX(); m_oldY = me.getY();
+	  }
+          if (m_mode == SELECTING) {
+            m_beanLayout.repaint();
+            m_oldX = me.getX(); m_oldY = me.getY();
+          }
+	}
+
+	 public void mouseMoved(MouseEvent e) {
+	   if (m_mode == CONNECTING) {
+	     m_beanLayout.repaint();
+	     // note the new coordinates
+	     m_oldX = e.getX(); m_oldY = e.getY();
+	   }
+	 }
+       });
+     
+     String date = (new SimpleDateFormat("EEEE, d MMMM yyyy"))
+       .format(new Date());
+     m_logPanel.logMessage("Weka Knowledge Flow was written by Mark Hall");
+     m_logPanel.logMessage("Weka Knowledge Flow");
+     m_logPanel.logMessage("(c) 2002-" + Copyright.getToYear() + " " 
+	 + Copyright.getOwner() + ", " + Copyright.getAddress());
+     m_logPanel.logMessage("web: " + Copyright.getURL());
+     m_logPanel.logMessage( date);
+     m_logPanel.statusMessage("[KnowledgeFlow]|Welcome to the Weka Knowledge Flow");
+     m_logPanel.getStatusTable().addMouseListener(new MouseAdapter() {
+       public void mouseClicked(MouseEvent e) {
+         if (m_logPanel.getStatusTable().rowAtPoint(e.getPoint()) == 0) {
+           if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+               != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+             System.gc();
+             Runtime currR = Runtime.getRuntime();
+             long freeM = currR.freeMemory();
+             long totalM = currR.totalMemory();
+             long maxM = currR.maxMemory();
+             m_logPanel.
+             logMessage("[KnowledgeFlow] Memory (free/total/max.) in bytes: " 
+                 + String.format("%,d", freeM) + " / " 
+                 + String.format("%,d", totalM) + " / " 
+                 + String.format("%,d", maxM));
+             m_logPanel.statusMessage("[KnowledgeFlow]|Memory (free/total/max.) in bytes: " 
+                 + String.format("%,d", freeM) + " / " 
+                 + String.format("%,d", totalM) + " / " 
+                 + String.format("%,d", maxM)); 
+           }
+         }
+       }
+     });
+    
+     JPanel p1 = new JPanel();
+     p1.setLayout(new BorderLayout());
+     p1.setBorder(javax.swing.BorderFactory.createCompoundBorder(
+			    javax.swing.BorderFactory.
+			    createTitledBorder("Knowledge Flow Layout"),
+                   javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5)
+                   ));
+     final JScrollPane js = new JScrollPane(m_beanLayout);
+     p1.add(js, BorderLayout.CENTER);
+     js.getVerticalScrollBar().setUnitIncrement(m_ScrollBarIncrementLayout);
+     js.getHorizontalScrollBar().setUnitIncrement(m_ScrollBarIncrementLayout);
+
+     setLayout(new BorderLayout());
+     
+     add(p1, BorderLayout.CENTER);
+     m_beanLayout.setSize(m_FlowWidth, m_FlowHeight);
+     Dimension d = m_beanLayout.getPreferredSize();
+     m_beanLayout.setMinimumSize(d);
+     m_beanLayout.setMaximumSize(d);
+     m_beanLayout.setPreferredSize(d);
+
+     Dimension d2 = new Dimension(100, 170);
+     m_logPanel.setPreferredSize(d2);
+     m_logPanel.setMinimumSize(d2);
+     add(m_logPanel, BorderLayout.SOUTH);
+     
+     setUpToolBars();
+     loadUserComponents();
+  }
+  
+  private Image loadImage(String path) {
+    Image pic = null;
+    // Modified by Zerbetto
+    //java.net.URL imageURL = ClassLoader.getSystemResource(path);
+    java.net.URL imageURL = this.getClass().getClassLoader().getResource(path);
+
+    // end modifications
+    if (imageURL == null) {
+      //      System.err.println("Warning: unable to load "+path);
+    } else {
+      pic = Toolkit.getDefaultToolkit().
+	getImage(imageURL);
+    }
+    return pic;
+  }
+
+  /**
+   * Describe <code>setUpToolBars</code> method here.
+   */
+  private void setUpToolBars() {
+    JPanel toolBarPanel = new JPanel();
+    toolBarPanel.setLayout(new BorderLayout());
+
+    // modifications by Zerbetto
+    // first construct the toolbar for saving, loading etc
+    if (m_showFileMenu) {
+      JToolBar fixedTools = new JToolBar();
+      fixedTools.setOrientation(JToolBar.VERTICAL);
+      m_saveB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
+              "Save24.gif")));
+      m_saveB.setToolTipText("Save layout");
+      m_loadB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
+              "Open24.gif")));
+      m_loadB.setToolTipText("Load layout");
+      m_newB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
+              "New24.gif")));
+      m_newB.setToolTipText("Clear the layout");
+      fixedTools.add(m_newB);
+      fixedTools.add(m_saveB);
+      fixedTools.add(m_loadB);
+
+      m_saveB.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            saveLayout();
+          }
+        });
+
+      m_loadB.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            m_flowEnvironment = new Environment();
+            loadLayout();
+          }
+        });
+
+      m_newB.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent ae) {
+            clearLayout();
+          }
+        });
+
+      fixedTools.setFloatable(false);
+      toolBarPanel.add(fixedTools, BorderLayout.WEST);
+    }
+
+    m_stopB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
+            "Stop24.gif")));
+    m_helpB = new JButton(new ImageIcon(loadImage(BeanVisual.ICON_PATH +
+            "Help24.gif")));
+    m_stopB.setToolTipText("Stop all execution");
+    m_helpB.setToolTipText("Display help");
+
+    Image tempI = loadImage(BeanVisual.ICON_PATH + "Pointer.gif");
+    m_pointerB = new JToggleButton(new ImageIcon(tempI));
+    m_pointerB.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          m_toolBarBean = null;
+          m_mode = NONE;
+          setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }
+      });
+
+    //    Dimension dP = m_saveB.getPreferredSize();
+    //    Dimension dM = m_saveB.getMaximumSize();
+    //    Dimension dP = m_stopB.getPreferredSize();
+    //    Dimension dM = m_stopB.getMaximumSize();
+    //    m_pointerB.setPreferredSize(dP);
+    //    m_pointerB.setMaximumSize(dM);
+    m_toolBarGroup.add(m_pointerB);
+
+    JToolBar fixedTools2 = new JToolBar();
+    fixedTools2.setOrientation(JToolBar.VERTICAL);
+    fixedTools2.setFloatable(false);
+    fixedTools2.add(m_pointerB);
+    fixedTools2.add(m_helpB);
+    fixedTools2.add(m_stopB);
+    //    m_helpB.setPreferredSize(dP);
+    //    m_helpB.setMaximumSize(dP);
+    m_helpB.setSize(m_pointerB.getSize().width, m_pointerB.getSize().height);
+    toolBarPanel.add(fixedTools2, BorderLayout.EAST);
+    // end modifications by Zerbetto
+    m_stopB.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          m_logPanel.statusMessage("[KnowledgeFlow]|Attempting to stop all components...");
+          stopFlow();
+          m_logPanel.statusMessage("[KnowledgeFlow]|OK.");
+        }
+      });
+
+    m_helpB.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent ae) {
+          popupHelp();
+        }
+      });
+
+    final int STANDARD_TOOLBAR = 0;
+    final int WEKAWRAPPER_TOOLBAR = 1;
+
+    int toolBarType = STANDARD_TOOLBAR;
+
+    // set up wrapper toolbars
+    for (int i = 0; i < TOOLBARS.size(); i++) {
+      Vector tempBarSpecs = (Vector) TOOLBARS.elementAt(i);
+
+      // name for the tool bar
+      String tempBarName = (String) tempBarSpecs.elementAt(0);
+
+      // Used for weka leaf packages 
+      Box singletonHolderPanel = null;
+
+      // name of the bean component to handle this class of weka algorithms
+      String tempBeanCompName = (String) tempBarSpecs.elementAt(1);
+
+      // a JPanel holding an instantiated bean + label ready to be added
+      // to the current toolbar
+      JPanel tempBean;
+
+      // the root package for weka algorithms
+      String rootPackage = "";
+      weka.gui.HierarchyPropertyParser hpp = null;
+      Hashtable hpps = null;
+
+      // Is this a wrapper toolbar?
+      if (tempBeanCompName.compareTo("null") != 0) {
+        tempBean = null;
+        toolBarType = WEKAWRAPPER_TOOLBAR;
+        rootPackage = (String) tempBarSpecs.elementAt(2);
+        //	hpp = (weka.gui.HierarchyPropertyParser)tempBarSpecs.elementAt(3);
+        hpps = (Hashtable) tempBarSpecs.elementAt(3);
+
+        try {
+          // modifications by Zerbetto
+          // Beans.instantiate(null, tempBeanCompName);
+          Beans.instantiate(this.getClass().getClassLoader(), tempBeanCompName);
+
+          // end modifications by Zerbetto
+        } catch (Exception ex) {
+          // ignore
+          System.err.println("[KnowledgeFlow] Failed to instantiate: " + tempBeanCompName);
+
+          break;
+        }
+      } else {
+        toolBarType = STANDARD_TOOLBAR;
+      }
+
+      // a toolbar to hold buttons---one for each algorithm
+      JToolBar tempToolBar = new JToolBar();
+
+      //      System.err.println(tempToolBar.getLayout());
+      //      tempToolBar.setLayout(new FlowLayout());
+      int z = 2;
+
+      if (toolBarType == WEKAWRAPPER_TOOLBAR) {
+        Enumeration enm = hpps.keys();
+
+        while (enm.hasMoreElements()) {
+          String root = (String) enm.nextElement();
+          String userPrefix = "";
+          hpp = (HierarchyPropertyParser) hpps.get(root);
+
+          if (!hpp.goTo(rootPackage)) {
+            System.out.println("[KnowledgeFlow] Processing user package... ");
+            //            System.exit(1);
+            userPrefix = root + ".";
+          }
+
+          String[] primaryPackages = hpp.childrenValues();
+
+          for (int kk = 0; kk < primaryPackages.length; kk++) {
+            hpp.goToChild(primaryPackages[kk]);
+
+            // check to see if this is a leaf - if so then there are no
+            // sub packages
+            if (hpp.isLeafReached()) {
+              if (singletonHolderPanel == null) {
+                singletonHolderPanel = Box.createHorizontalBox();
+                singletonHolderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
+                    tempBarName));
+              }
+
+              String algName = hpp.fullValue();
+              tempBean = instantiateToolBarBean(true, tempBeanCompName, algName);
+
+              if (tempBean != null) {
+                // tempToolBar.add(tempBean);
+                singletonHolderPanel.add(tempBean);
+              }
+
+              hpp.goToParent();
+            } else {
+              // make a titledborder JPanel to hold all the schemes in this
+              // package
+              //	    JPanel holderPanel = new JPanel();
+              Box holderPanel = Box.createHorizontalBox();
+              holderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(userPrefix +
+                  primaryPackages[kk]));
+              processPackage(holderPanel, tempBeanCompName, hpp);
+              tempToolBar.add(holderPanel);
+            }
+          }
+
+          if (singletonHolderPanel != null) {
+            tempToolBar.add(singletonHolderPanel);
+            singletonHolderPanel = null;
+          }
+        }
+      } else {
+        Box holderPanel = Box.createHorizontalBox();
+        holderPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
+            tempBarName));
+
+        for (int j = z; j < tempBarSpecs.size(); j++) {
+          tempBean = null;
+          tempBeanCompName = (String) tempBarSpecs.elementAt(j);
+          tempBean = instantiateToolBarBean((toolBarType == WEKAWRAPPER_TOOLBAR),
+              tempBeanCompName, "");
+
+          if (tempBean != null) {
+            // set tool tip text (if any)
+            // setToolTipText(tempBean)
+            holderPanel.add(tempBean);
+          }
+        }
+
+        tempToolBar.add(holderPanel);
+      }
+
+      JScrollPane tempJScrollPane = createScrollPaneForToolBar(tempToolBar);
+      // ok, now create tabbed pane to hold this toolbar
+      m_toolBars.addTab(tempBarName, null, tempJScrollPane, tempBarName);
+    }
+
+    // Any plugin components to process?
+    if (BEAN_PLUGINS_PROPERTIES != null && 
+        BEAN_PLUGINS_PROPERTIES.size() > 0) {
+      for (int i = 0; i < BEAN_PLUGINS_PROPERTIES.size(); i++) {
+        Properties tempP = BEAN_PLUGINS_PROPERTIES.get(i);
+        JPanel tempBean = null;
+        String components = 
+        tempP.getProperty("weka.gui.beans.KnowledgeFlow.Plugins");
+        StringTokenizer st2 = new StringTokenizer(components, ", ");
+
+        while (st2.hasMoreTokens()) {
+          String tempBeanCompName = st2.nextToken().trim();
+          tempBean = instantiateToolBarBean(false, tempBeanCompName, "");
+          if (m_pluginsToolBar == null) {
+            // need to create the plugins tab and toolbar
+            setUpPluginsToolBar();
+          }
+          m_pluginsBoxPanel.add(tempBean);
+        }
+      }
+    }
+
+    toolBarPanel.add(m_toolBars, BorderLayout.CENTER);
+
+    //    add(m_toolBars, BorderLayout.NORTH);
+    add(toolBarPanel, BorderLayout.NORTH);
+  }
+  
+  private void stopFlow() {
+    Vector components = BeanInstance.getBeanInstances();
+
+    for (int i = 0; i < components.size(); i++) {
+      Object temp = ((BeanInstance) components.elementAt(i)).getBean();
+
+      if (temp instanceof BeanCommon) {
+        ((BeanCommon) temp).stop();
+      }
+    }
+  }
+
+
+  private JScrollPane createScrollPaneForToolBar(JToolBar tb) {
+    JScrollPane tempJScrollPane = 
+      new JScrollPane(tb, 
+                      JScrollPane.VERTICAL_SCROLLBAR_NEVER,
+                      JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+    
+    Dimension d = tb.getPreferredSize();
+    tempJScrollPane.setMinimumSize(new Dimension((int)d.getWidth(),
+                                                 (int)(d.getHeight()+15)));
+    tempJScrollPane.setPreferredSize(new Dimension((int)d.getWidth(),
+                                                   (int)(d.getHeight()+15)));
+    tempJScrollPane.getHorizontalScrollBar().setUnitIncrement(
+        m_ScrollBarIncrementComponents);
+
+    return tempJScrollPane;
+  }
+
+  private void processPackage(JComponent holderPanel,
+			      String tempBeanCompName,
+			      weka.gui.HierarchyPropertyParser hpp) {
+    if (hpp.isLeafReached()) {
+      // instantiate a bean and add it to the holderPanel
+      //      System.err.println("Would add "+hpp.fullValue());
+      String algName = hpp.fullValue();
+      JPanel tempBean = 
+	instantiateToolBarBean(true, tempBeanCompName, algName);
+      if (tempBean != null) {
+	holderPanel.add(tempBean);
+      }
+      hpp.goToParent();
+      return;
+    }
+    String [] children = hpp.childrenValues();
+    for (int i = 0; i < children.length; i++) {
+      hpp.goToChild(children[i]);
+      processPackage(holderPanel, tempBeanCompName, hpp);
+    }
+    hpp.goToParent();
+  }
+
+  /**
+   * Instantiates a bean for display in the toolbars
+   *
+   * @param wekawrapper true if the bean to be instantiated is a wekawrapper
+   * @param tempBeanCompName the name of the bean to instantiate
+   * @param algName holds the name of a weka algorithm to configure the
+   * bean with if it is a wekawrapper bean
+   * @return a JPanel holding the instantiated (and configured bean)
+   */
+  private JPanel instantiateToolBarBean(boolean wekawrapper, 
+					String tempBeanCompName,
+					String algName) {
+    Object tempBean;
+    if (wekawrapper) {
+      try {
+        // modifications by Zerbetto
+        //tempBean = Beans.instantiate(null, tempBeanCompName);
+        tempBean = Beans.instantiate(this.getClass().getClassLoader(),
+                                     tempBeanCompName);
+        
+        // end modifications by Zerbetto
+      } catch (Exception ex) {
+	System.err.println("[KnowledgeFlow] Failed to instantiate :"+tempBeanCompName
+			   +"KnowledgeFlowApp.instantiateToolBarBean()");
+	return null;
+      }
+      if (tempBean instanceof WekaWrapper) {
+	//	algName = (String)tempBarSpecs.elementAt(j);
+	Class c = null;
+	try {
+	  c = Class.forName(algName);
+	} catch (Exception ex) {
+	  System.err.println("[KnowledgeFlow] Can't find class called: "+algName);
+	  return null;
+	}
+	try {
+	  Object o = c.newInstance();
+	  ((WekaWrapper)tempBean).setWrappedAlgorithm(o);
+	} catch (Exception ex) {
+	  System.err.println("[KnowledgeFlow] Failed to configure "+tempBeanCompName
+			     +" with "+algName);
+	  return null;
+	}
+      }
+    } else {
+      try {
+        // modifications by Zerbetto
+        //tempBean = Beans.instantiate(null, tempBeanCompName);
+        tempBean = Beans.instantiate(this.getClass().getClassLoader(),
+            tempBeanCompName);
+
+        // end modifications
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	System.err.println("[KnowledgeFlow] Failed to instantiate :"+tempBeanCompName
+			   +"KnowledgeFlowApp.setUpToolBars()");
+	return null;
+      }
+    }
+    
+    if (tempBean instanceof BeanContextChild) {
+      m_bcSupport.add(tempBean);
+    }
+    if (tempBean instanceof Visible) {
+      ((Visible)tempBean).getVisual().scale(3);
+    }
+
+    return makeHolderPanelForToolBarBean(tempBeanCompName, tempBean, 
+                                         wekawrapper, algName, false);
+  }
+
+  /**
+   * Instantiates (by making a serialized copy) the supplied
+   * template meta bean for display in the user tool bar
+   * 
+   * @param bean the prototype MetaBean to display in the toolbar
+   */
+  private JPanel instantiateToolBarMetaBean(MetaBean bean) {
+    // copy the bean via serialization
+    ((Visible)bean).getVisual().removePropertyChangeListener(this);
+    bean.removePropertyChangeListenersSubFlow(this);
+    Object copy = null;
+    try {
+      SerializedObject so = new SerializedObject(bean);
+      copy = (MetaBean)so.getObject();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      return null;
+    }
+    ((Visible)bean).getVisual().addPropertyChangeListener(this);
+    bean.addPropertyChangeListenersSubFlow(this);
+
+    String displayName ="";
+    //
+    if (copy instanceof Visible) {
+      ((Visible)copy).getVisual().scale(3);
+      displayName = ((Visible)copy).getVisual().getText();
+    }
+    return makeHolderPanelForToolBarBean(displayName,
+                                         copy,
+                                         false,
+                                         null,
+                                         true);
+  }
+
+  private JPanel makeHolderPanelForToolBarBean(final String tempName,
+                                               Object tempBean,
+                                               boolean wekawrapper,
+                                               String algName,
+                                               final boolean metabean) {
+    // ---------------------------------------
+    JToggleButton tempButton;
+    final JPanel tempP = new JPanel();
+    JLabel tempL = new JLabel();
+    tempL.setFont(new Font(null, Font.PLAIN, 9));
+
+    String labelName = (wekawrapper == true) 
+      ? algName 
+      : tempName;
+    labelName = labelName.substring(labelName.lastIndexOf('.')+1, 
+				    labelName.length());
+    tempL.setText(" "+labelName+" ");
+    tempL.setHorizontalAlignment(JLabel.CENTER);
+    tempP.setLayout(new BorderLayout());
+
+    if (tempBean instanceof Visible) {
+      BeanVisual bv = ((Visible)tempBean).getVisual();
+
+      tempButton = 
+	new JToggleButton(bv.getStaticIcon());
+      int width = bv.getStaticIcon().getIconWidth();
+      int height = bv.getStaticIcon().getIconHeight();
+      
+      JPanel labelPanel = 
+	multiLineLabelPanel(labelName, width);
+      tempP.add(labelPanel, BorderLayout.SOUTH);
+    } else {
+      tempButton = new JToggleButton();
+      tempP.add(tempL, BorderLayout.SOUTH);
+    }
+    tempP.add(tempButton, BorderLayout.NORTH);
+    //    tempP.add(tempL, BorderLayout.SOUTH);
+    
+    //  holderPanel.add(tempP);
+    //    tempToolBar.add(tempP);
+    m_toolBarGroup.add(tempButton);
+    
+    // add an action listener for the button here
+    final Object tempBN = tempBean;
+    final JToggleButton fButton = tempButton;
+    //	  final JToggleButton tempButton2 = tempButton;
+    tempButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+          boolean changeCursor = true;
+	  try {
+	    m_toolBarBean = null;
+            if (metabean) {
+              if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
+                changeCursor = false;
+                m_toolBarGroup.remove(fButton);
+                m_userBoxPanel.remove(tempP);
+                m_userBoxPanel.revalidate();
+                m_userComponents.remove(tempBN);
+                if (m_firstUserComponentOpp) {
+                  installWindowListenerForSavingUserBeans();
+                  m_firstUserComponentOpp = false;
+                }
+                if (m_userComponents.size() == 0) {
+                  m_toolBars.removeTabAt(m_toolBars.getTabCount() - 1);
+                  m_userToolBar = null;
+                  notifyIsDirty();
+                }
+              } else {
+                SerializedObject so = new SerializedObject(tempBN);
+                MetaBean copy = (MetaBean)so.getObject();
+                /*((Visible)copy).getVisual().
+                  addPropertyChangeListener(KnowledgeFlowApp.this); */
+                copy.addPropertyChangeListenersSubFlow(KnowledgeFlowApp.this);
+                m_toolBarBean = copy;
+              }
+            } else {
+              // modifications by Zerbetto
+              //m_toolBarBean = Beans.instantiate(null, tempName);
+              m_toolBarBean = Beans.instantiate(this.getClass().getClassLoader(),
+                  tempName);
+
+              // end modifications
+            }
+            if (m_toolBarBean instanceof WekaWrapper) {
+	      Object wrappedAlg = 
+		((WekaWrapper)tempBN).getWrappedAlgorithm();
+	      
+	      ((WekaWrapper)m_toolBarBean).
+                setWrappedAlgorithm(wrappedAlg.getClass().newInstance());
+	      //		    tempButton2.setSelected(false);
+	    }
+            if (changeCursor) {
+              setCursor(Cursor.
+                        getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+              m_mode = ADDING;
+            }
+	  } catch (Exception ex) {
+	    System.err.
+	      println("[KnowledgeFlow] Problem adding bean to data flow layout");
+            ex.printStackTrace();
+	  }
+          notifyIsDirty();
+	}
+      });
+    
+    if (tempBean instanceof MetaBean) {
+      tempButton.setToolTipText("Hold down shift and click to remove");
+      m_userComponents.add(tempBean);
+    } else {
+      // set tool tip text from global info if supplied
+      String summary = getGlobalInfo(tempBean);
+      if (summary != null) {
+        int ci = summary.indexOf('.');
+        if (ci != -1) {
+          summary = summary.substring(0, ci + 1);
+        }
+        tempButton.setToolTipText(summary);
+      }
+    }
+
+    //return tempBean;
+    return tempP;
+  }
+
+  private JPanel multiLineLabelPanel(String sourceL,
+				     int splitWidth) {
+    JPanel jp = new JPanel();
+    Vector v = new Vector();
+
+    int labelWidth = m_fontM.stringWidth(sourceL);
+
+    if (labelWidth < splitWidth) {
+      v.addElement(sourceL);
+    } else {
+      // find mid point
+      int mid = sourceL.length() / 2;
+      
+      // look for split point closest to the mid
+      int closest = sourceL.length();
+      int closestI = -1;
+      for (int i = 0; i < sourceL.length(); i++) {
+	if (sourceL.charAt(i) < 'a') {
+	  if (Math.abs(mid - i) < closest) {
+	    closest = Math.abs(mid - i);
+	    closestI = i;
+	  }
+	}
+      }
+      if (closestI != -1) {
+	String left = sourceL.substring(0, closestI);
+	String right = sourceL.substring(closestI, sourceL.length());
+	if (left.length() > 1 && right.length() > 1) {
+	  v.addElement(left);
+	  v.addElement(right);
+	} else {
+	  v.addElement(sourceL);
+	}
+      } else {
+	v.addElement(sourceL);
+      }
+    }
+
+    jp.setLayout(new GridLayout(v.size(), 1));
+    for (int i = 0; i < v.size(); i++) {
+      JLabel temp = new JLabel();
+      temp.setFont(new Font(null, Font.PLAIN, 9));
+      temp.setText(" "+((String)v.elementAt(i))+" ");
+      temp.setHorizontalAlignment(JLabel.CENTER);
+      jp.add(temp);
+    }
+    return jp;
+  }
+
+  private void setUpUserToolBar() {
+    m_userBoxPanel = Box.createHorizontalBox();
+    m_userBoxPanel.setBorder(javax.swing.BorderFactory.
+                             createTitledBorder("User"));
+    m_userToolBar = new JToolBar();
+    m_userToolBar.add(m_userBoxPanel);
+    JScrollPane tempJScrollPane = 
+      createScrollPaneForToolBar(m_userToolBar);
+    // ok, now create tabbed pane to hold this toolbar
+    
+    m_toolBars.addTab("User", null, 
+                      tempJScrollPane,
+                      "User created components");
+  }
+
+  private void setUpPluginsToolBar() {
+    m_pluginsBoxPanel = Box.createHorizontalBox();
+    m_pluginsBoxPanel.setBorder(javax.swing.BorderFactory.
+                                createTitledBorder("Plugins"));
+    m_pluginsToolBar = new JToolBar();
+    m_pluginsToolBar.add(m_pluginsBoxPanel);
+    JScrollPane tempJScrollPane = 
+      createScrollPaneForToolBar(m_pluginsToolBar);
+    // ok, now create tabbed pane to hold this toolbar
+    
+    m_toolBars.addTab("Plugins", null, 
+                      tempJScrollPane,
+                      "Plugin components");
+  }
+
+  /**
+   * Pop up a help window
+   */
+  private void popupHelp() {
+    final JButton tempB = m_helpB;
+    try {
+      tempB.setEnabled(false);
+      // Modified by Zerbetto
+      //InputStream inR = 
+      //	ClassLoader.
+      //        getSystemResourceAsStream("weka/gui/beans/README_KnowledgeFlow");
+      InputStream inR = this.getClass().getClassLoader()
+                            .getResourceAsStream("weka/gui/beans/README_KnowledgeFlow");
+
+      // end modifications
+      StringBuffer helpHolder = new StringBuffer();
+      LineNumberReader lnr = new LineNumberReader(new InputStreamReader(inR));
+      
+      String line;
+      
+      while ((line = lnr.readLine()) != null) {
+	helpHolder.append(line+"\n");
+      }
+      
+      lnr.close();
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      final JTextArea ta = new JTextArea(helpHolder.toString());
+      ta.setFont(new Font("Monospaced", Font.PLAIN, 12));
+      ta.setEditable(false);
+      final JScrollPane sp = new JScrollPane(ta);
+      jf.getContentPane().add(sp, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+	  tempB.setEnabled(true);
+          jf.dispose();
+        }
+      });
+      jf.setSize(600,600);
+      jf.setVisible(true);
+      
+    } catch (Exception ex) {
+      tempB.setEnabled(true);
+    }
+  }
+
+  public void clearLayout() {
+    stopFlow(); // try and stop any running components
+    BeanInstance.reset(m_beanLayout);
+    BeanConnection.reset();
+    m_beanLayout.revalidate();
+    m_beanLayout.repaint();
+    m_logPanel.clearStatus();
+    m_logPanel.statusMessage("[KnowledgeFlow]|Welcome to the Weka Knowledge Flow");
+  }
+  
+  /**
+   * Popup a context sensitive menu for the bean component
+   *
+   * @param pt holds the panel coordinates for the component
+   * @param bi the bean component over which the user right clicked the mouse
+   * @param x the x coordinate at which to popup the menu
+   * @param y the y coordinate at which to popup the menu
+   *
+   * Modified by Zerbetto: javax.swing.JPopupMenu transformed into java.awt.PopupMenu
+   *
+   */
+  private void doPopup(Point pt, final BeanInstance bi, int x, int y) {
+    final JComponent bc = (JComponent) bi.getBean();
+    final int xx = x;
+    final int yy = y;
+    int menuItemCount = 0;
+
+    // modifications by Zerbetto
+    PopupMenu beanContextMenu = new PopupMenu();
+
+    //JPopupMenu beanContextMenu = new JPopupMenu();
+
+    //    beanContextMenu.insert(new JLabel("Edit", 
+    //				      SwingConstants.CENTER), 
+    //			   menuItemCount);
+    MenuItem edit = new MenuItem("Edit:");
+    edit.setEnabled(false);
+    beanContextMenu.insert(edit, menuItemCount);
+    menuItemCount++;
+
+    if (bc instanceof MetaBean) {
+      //JMenuItem ungroupItem = new JMenuItem("Ungroup");
+      MenuItem ungroupItem = new MenuItem("Ungroup");
+      ungroupItem.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            // ungroup
+            bi.removeBean(m_beanLayout);
+
+            Vector group = ((MetaBean) bc).getBeansInSubFlow();
+            Vector associatedConnections = ((MetaBean) bc).getAssociatedConnections();
+            ((MetaBean) bc).restoreBeans();
+
+            for (int i = 0; i < group.size(); i++) {
+              BeanInstance tbi = (BeanInstance) group.elementAt(i);
+              addComponent(tbi, false);
+              tbi.addBean(m_beanLayout);
+            }
+
+            for (int i = 0; i < associatedConnections.size(); i++) {
+              BeanConnection tbc = (BeanConnection) associatedConnections.elementAt(i);
+              tbc.setHidden(false);
+            }
+
+            m_beanLayout.repaint();
+            notifyIsDirty();
+          }
+        });
+      beanContextMenu.add(ungroupItem);
+      menuItemCount++;
+
+      // Add to user tab
+      //JMenuItem addToUserTabItem = new JMenuItem("Add to user tab");
+      MenuItem addToUserTabItem = new MenuItem("Add to user tab");
+      addToUserTabItem.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            addToUserToolBar((MetaBean) bi.getBean(), true);
+            notifyIsDirty();
+          }
+        });
+      beanContextMenu.add(addToUserTabItem);
+      menuItemCount++;
+    }
+
+    //JMenuItem deleteItem = new JMenuItem("Delete");
+    MenuItem deleteItem = new MenuItem("Delete");
+    deleteItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          BeanConnection.removeConnections(bi);
+          bi.removeBean(m_beanLayout);
+          if (bc instanceof BeanCommon) {            
+            String key = ((BeanCommon)bc).getCustomName()
+              + "$" + bc.hashCode();
+            m_logPanel.statusMessage(key + "|remove");
+          }
+          revalidate();
+          notifyIsDirty();
+        }
+      });
+    if (bc instanceof BeanCommon) {
+      if (((BeanCommon)bc).isBusy()) {
+        deleteItem.setEnabled(false);
+      }
+    }
+    beanContextMenu.add(deleteItem);
+    menuItemCount++;
+
+    if (bc instanceof BeanCommon) {
+      MenuItem nameItem = new MenuItem("Set name");
+      nameItem.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            String oldName = ((BeanCommon)bc).getCustomName();
+            String name = JOptionPane.showInputDialog(KnowledgeFlowApp.this,
+                                                      "Enter a name for this component",
+                                                      oldName);
+            if (name != null) {
+              ((BeanCommon)bc).setCustomName(name);
+            }
+          }
+        });
+      if (bc instanceof BeanCommon) {
+        if (((BeanCommon)bc).isBusy()) {
+          nameItem.setEnabled(false);
+        }
+      }
+      beanContextMenu.add(nameItem);
+      menuItemCount++;
+    }
+
+    try {
+      //BeanInfo [] compInfo = null;
+      //JComponent [] associatedBeans = null;
+      Vector compInfo = new Vector(1);
+      Vector associatedBeans = null;
+      Vector outputBeans = null;
+      Vector compInfoOutputs = null;
+
+      if (bc instanceof MetaBean) {
+        compInfo = ((MetaBean) bc).getBeanInfoSubFlow();
+        associatedBeans = ((MetaBean) bc).getBeansInSubFlow();
+
+        outputBeans = ((MetaBean) bc).getBeansInOutputs();
+        compInfoOutputs = ((MetaBean) bc).getBeanInfoOutputs();
+      } else {
+        compInfo.add(Introspector.getBeanInfo(bc.getClass()));
+        compInfoOutputs = compInfo;
+      }
+
+      final Vector tempAssociatedBeans = associatedBeans;
+
+      if (compInfo == null) {
+        System.err.println("[KnowledgeFlow] Error in doPopup()");
+      } else {
+        //	System.err.println("Got bean info");
+        for (int zz = 0; zz < compInfo.size(); zz++) {
+          final int tt = zz;
+          final Class custClass = ((BeanInfo) compInfo.elementAt(zz)).getBeanDescriptor()
+            .getCustomizerClass();
+
+          if (custClass != null) {
+            //	  System.err.println("Got customizer class");
+            //	  popupCustomizer(custClass, bc);
+            //JMenuItem custItem = null;
+            MenuItem custItem = null;
+            boolean customizationEnabled = true;
+
+            if (!(bc instanceof MetaBean)) {
+              //custItem = new JMenuItem("Configure...");
+              custItem = new MenuItem("Configure...");
+              if (bc instanceof BeanCommon) {
+                customizationEnabled = 
+                  !((BeanCommon)bc).isBusy();
+              }
+            } else {
+              String custName = custClass.getName();
+              BeanInstance tbi = (BeanInstance) associatedBeans.elementAt(zz);
+              if (tbi.getBean() instanceof BeanCommon) {
+                custName = ((BeanCommon)tbi.getBean()).getCustomName();
+              } else {
+                if (tbi.getBean() instanceof WekaWrapper) {
+                  custName = ((WekaWrapper) tbi.getBean()).getWrappedAlgorithm()
+                  .getClass().getName();
+                } else {
+                  custName = custName.substring(0, custName.indexOf("Customizer"));
+                }
+
+                custName = custName.substring(custName.lastIndexOf('.') + 1,              
+                                            custName.length());
+              }
+              //custItem = new JMenuItem("Configure: "+ custName);
+              custItem = new MenuItem("Configure: " + custName);
+              if (tbi.getBean() instanceof BeanCommon) {
+                customizationEnabled = 
+                  !((BeanCommon)tbi.getBean()).isBusy();
+              }
+            }
+
+            custItem.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                  if (bc instanceof MetaBean) {
+                    popupCustomizer(custClass,
+                      (JComponent) ((BeanInstance) tempAssociatedBeans.
+                                    elementAt(tt)).getBean());
+                  } else {
+                    popupCustomizer(custClass, bc);
+                  }
+
+                  notifyIsDirty();
+                }
+              });
+            custItem.setEnabled(customizationEnabled);
+            beanContextMenu.add(custItem);
+            menuItemCount++;
+          } else {
+            System.err.println("[KnowledgeFlow] No customizer class");
+          }
+        }
+
+        Vector esdV = new Vector();
+
+        //for (int i = 0; i < compInfoOutputs.size(); i++) {
+        for (int i = 0; i < compInfo.size(); i++) {
+          EventSetDescriptor[] temp = 
+          //  ((BeanInfo) compInfoOutputs.elementAt(i)).getEventSetDescriptors();
+          ((BeanInfo) compInfo.elementAt(i)).getEventSetDescriptors();
+
+          if ((temp != null) && (temp.length > 0)) {
+            esdV.add(temp);
+          }
+        }
+
+        //        EventSetDescriptor [] esds = compInfo.getEventSetDescriptors();
+        //        if (esds != null && esds.length > 0) {
+        if (esdV.size() > 0) {
+          //          beanContextMenu.insert(new JLabel("Connections", 
+          //                                            SwingConstants.CENTER), 
+          //                                 menuItemCount);
+          MenuItem connections = new MenuItem("Connections:");
+          connections.setEnabled(false);
+          beanContextMenu.insert(connections, menuItemCount);
+          menuItemCount++;
+        }
+
+        //final Vector finalOutputs = outputBeans;
+        final Vector finalOutputs = associatedBeans;
+
+        for (int j = 0; j < esdV.size(); j++) {
+          final int fj = j;
+          String sourceBeanName = "";
+
+          if (bc instanceof MetaBean) {
+            //Object sourceBean = ((BeanInstance) outputBeans.elementAt(j)).getBean();
+            Object sourceBean = ((BeanInstance) associatedBeans.elementAt(j)).getBean();
+            if (sourceBean instanceof BeanCommon) {
+              sourceBeanName = ((BeanCommon)sourceBean).getCustomName();
+            } else {
+              if (sourceBean instanceof WekaWrapper) {
+                sourceBeanName = ((WekaWrapper) sourceBean).getWrappedAlgorithm()
+                .getClass().getName();
+              } else {
+                sourceBeanName = sourceBean.getClass().getName();
+              }
+
+              sourceBeanName = 
+                sourceBeanName.substring(sourceBeanName.lastIndexOf('.') + 1, 
+                    sourceBeanName.length());
+            }
+            sourceBeanName += ": ";
+          }
+
+          EventSetDescriptor[] esds = (EventSetDescriptor[]) esdV.elementAt(j);
+
+          for (int i = 0; i < esds.length; i++) {
+            //	  System.err.println(esds[i].getName());
+            // add each event name to the menu
+            //            JMenuItem evntItem = new JMenuItem(sourceBeanName
+            //                                               +esds[i].getName());
+            MenuItem evntItem = new MenuItem(sourceBeanName +
+                                             esds[i].getName());
+            final EventSetDescriptor esd = esds[i];
+
+            // Check EventConstraints (if any) here
+            boolean ok = true;
+
+            if (bc instanceof EventConstraints) {
+              ok = ((EventConstraints) bc).eventGeneratable(esd.getName());
+            }
+
+            if (ok) {
+              evntItem.addActionListener(new ActionListener() {
+                  public void actionPerformed(ActionEvent e) {
+                    connectComponents(esd,
+                                      (bc instanceof MetaBean)
+                                      ? ((BeanInstance) finalOutputs.elementAt(fj)) : bi, xx, yy);
+                    notifyIsDirty();
+                  }
+                });
+            } else {
+              evntItem.setEnabled(false);
+            }
+
+            beanContextMenu.add(evntItem);
+            menuItemCount++;
+          }
+        }
+      }
+    } catch (IntrospectionException ie) {
+      ie.printStackTrace();
+    }
+
+    //    System.err.println("Just before look for other options");
+    // now look for other options for this bean
+    if (bc instanceof UserRequestAcceptor || bc instanceof Startable) {
+      Enumeration req = null;
+      
+      if (bc instanceof UserRequestAcceptor) {
+        req = ((UserRequestAcceptor) bc).enumerateRequests();
+      }
+
+      if ((bc instanceof Startable) || (req !=null && req.hasMoreElements())) {
+        //	beanContextMenu.insert(new JLabel("Actions", 
+        //					  SwingConstants.CENTER), 
+        //			       menuItemCount);
+        MenuItem actions = new MenuItem("Actions:");
+        actions.setEnabled(false);
+        beanContextMenu.insert(actions, menuItemCount);
+        menuItemCount++;
+      }
+
+      if (bc instanceof Startable) {
+        String tempS = ((Startable)bc).getStartMessage();
+        insertUserOrStartableMenuItem(bc, true, tempS, beanContextMenu);
+      }
+      
+      while (req != null && req.hasMoreElements()) {
+        String tempS = (String) req.nextElement();
+        insertUserOrStartableMenuItem(bc, false, tempS, beanContextMenu);
+        menuItemCount++;
+      }
+    }
+
+    //    System.err.println("Just before showing menu");
+    // popup the menu
+    if (menuItemCount > 0) {
+      //beanContextMenu.show(m_beanLayout, x, y);
+      m_beanLayout.add(beanContextMenu);
+      beanContextMenu.show(m_beanLayout, x, y);
+    }
+  }
+  
+  private void insertUserOrStartableMenuItem(final JComponent bc, 
+      final boolean startable, String tempS, PopupMenu beanContextMenu) {
+
+    boolean disabled = false;
+    boolean confirmRequest = false;
+
+    // check to see if this item is currently disabled
+    if (tempS.charAt(0) == '$') {
+      tempS = tempS.substring(1, tempS.length());
+      disabled = true;
+    }
+    
+    // check to see if this item requires confirmation
+    if (tempS.charAt(0) == '?') {
+      tempS = tempS.substring(1, tempS.length());
+      confirmRequest = true;
+    }
+
+    final String tempS2 = tempS;
+
+    //      JMenuItem custItem = new JMenuItem(tempS2);
+    MenuItem custItem = new MenuItem(tempS2);
+    if (confirmRequest) {
+      custItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          // 
+          int result = JOptionPane.showConfirmDialog(KnowledgeFlowApp.this,
+              tempS2,
+              "Confirm action",
+              JOptionPane.YES_NO_OPTION);
+          if (result == JOptionPane.YES_OPTION) {
+            Thread startPointThread = new Thread() {
+              public void run() {
+                try {
+                  if (startable) {
+                    ((Startable)bc).start();                    
+                  } else if (bc instanceof UserRequestAcceptor) {
+                    ((UserRequestAcceptor) bc).performRequest(tempS2);
+                  }
+                  notifyIsDirty();
+                } catch (Exception ex) {
+                  ex.printStackTrace();
+                }
+              }
+            };
+            startPointThread.setPriority(Thread.MIN_PRIORITY);
+            startPointThread.start();
+          }
+        }
+      });
+    } else {
+      custItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          Thread startPointThread = new Thread() {
+            public void run() {
+              try {
+                if (startable) {
+                  ((Startable)bc).start();                  
+                } else if (bc instanceof UserRequestAcceptor) {
+                  ((UserRequestAcceptor) bc).performRequest(tempS2);
+                }
+                notifyIsDirty();
+              } catch (Exception ex) {
+                ex.printStackTrace();
+              }
+            }
+          };
+          startPointThread.setPriority(Thread.MIN_PRIORITY);
+          startPointThread.start();
+        }
+      });
+    }
+
+    if (disabled) {
+      custItem.setEnabled(false);
+    }
+
+    beanContextMenu.add(custItem); 
+  }
+
+  /**
+   * Popup the customizer for this bean
+   *
+   * @param custClass the class of the customizer
+   * @param bc the bean to be customized
+   */
+  private void popupCustomizer(Class custClass, JComponent bc) {
+    try {
+      // instantiate
+      final Object customizer = custClass.newInstance();
+      // set environment **before** setting object!!
+      if (customizer instanceof EnvironmentHandler) {
+        ((EnvironmentHandler)customizer).setEnvironment(m_flowEnvironment);
+      }
+      ((Customizer)customizer).setObject(bc);
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add((JComponent)customizer, BorderLayout.CENTER);
+      if (customizer instanceof CustomizerCloseRequester) {
+	((CustomizerCloseRequester)customizer).setParentFrame(jf);
+      }
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    if (customizer instanceof CustomizerClosingListener) {
+	      ((CustomizerClosingListener)customizer).customizerClosing();
+	    }
+	    jf.dispose();
+	  }
+	});
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+                                
+  /**
+   * Handles adding a custom MetaBean to the user toolbar
+   *
+   * @param bean the MetaBean
+   * @param installListener install a listener for window close
+   * events so as to save the user components
+   */
+  private void addToUserToolBar(MetaBean bean, 
+                                boolean installListener) {
+
+    if (m_userToolBar == null) {
+      // need to create the user tab and toolbar
+      setUpUserToolBar();
+    }
+
+    // Disconnect any beans connected to the inputs or outputs
+    // of this MetaBean (prevents serialization of the entire
+    // KnowledgeFlow!!)
+    Vector tempRemovedConnections = new Vector();
+    Vector allConnections = BeanConnection.getConnections();
+    Vector inputs = bean.getInputs();
+    Vector outputs = bean.getOutputs();
+    Vector allComps = bean.getSubFlow();
+        
+    for (int i = 0; i < inputs.size(); i++) {
+      BeanInstance temp = (BeanInstance)inputs.elementAt(i);
+      // is this input a target for some event?
+      for (int j = 0; j < allConnections.size(); j++) {
+        BeanConnection tempC = (BeanConnection)allConnections.elementAt(j);
+        if (tempC.getTarget() == temp) {
+          tempRemovedConnections.add(tempC);
+        }
+        
+        // also check to see if this input is a source for
+        // some target that is *not* in the subFlow
+        if (tempC.getSource() == temp && !bean.subFlowContains(tempC.getTarget())) {
+          tempRemovedConnections.add(tempC);
+        }
+      }
+    }
+
+    for (int i = 0; i < outputs.size(); i++) {
+      BeanInstance temp = (BeanInstance)outputs.elementAt(i);
+      // is this output a source for some target?
+      for (int j = 0; j < allConnections.size(); j++) {
+        BeanConnection tempC = (BeanConnection)allConnections.elementAt(j);
+        if (tempC.getSource() == temp) {
+          tempRemovedConnections.add(tempC);
+        }
+      }
+    }
+    
+    
+    for (int i = 0; i < tempRemovedConnections.size(); i++) {
+      BeanConnection temp = 
+        (BeanConnection)tempRemovedConnections.elementAt(i);
+      temp.remove();
+    }
+    
+    // now add to user tool bar
+    JPanel tempUser = instantiateToolBarMetaBean(bean);
+    m_userBoxPanel.add(tempUser);
+    if (installListener && m_firstUserComponentOpp) {
+      try {
+        installWindowListenerForSavingUserBeans();
+        m_firstUserComponentOpp = false;
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+
+    // Now reinstate any deleted connections to the original MetaBean
+    for (int i = 0; i < tempRemovedConnections.size(); i++) {
+      BeanConnection temp = 
+        (BeanConnection)tempRemovedConnections.elementAt(i);
+      BeanConnection newC = 
+        new BeanConnection(temp.getSource(), temp.getTarget(),
+                           temp.getSourceEventSetDescriptor());
+    }    
+  }
+
+  /**
+   * Popup a menu giving choices for connections to delete (if any)
+   *
+   * @param closestConnections a vector containing 0 or more BeanConnections
+   * @param x the x coordinate at which to popup the menu
+   * @param y the y coordinate at which to popup the menu
+   *
+   * Modified by Zerbetto: javax.swing.JPopupMenu transformed into java.awt.PopupMenu
+   */
+  private void deleteConnectionPopup(Vector closestConnections, int x, int y) {
+    if (closestConnections.size() > 0) {
+      int menuItemCount = 0;
+
+      // modifications by Zerbetto
+      //JPopupMenu deleteConnectionMenu = new JPopupMenu();
+      PopupMenu deleteConnectionMenu = new PopupMenu();
+
+      //      deleteConnectionMenu.insert(new JLabel("Delete Connection", 
+      //					     SwingConstants.CENTER), 
+      //				  menuItemCount);
+      MenuItem deleteConnection = new MenuItem("Delete Connection:");
+      deleteConnection.setEnabled(false);
+      deleteConnectionMenu.insert(deleteConnection, menuItemCount);
+      menuItemCount++;
+
+      for (int i = 0; i < closestConnections.size(); i++) {
+        final BeanConnection bc = (BeanConnection) closestConnections.elementAt(i);
+        String connName = bc.getSourceEventSetDescriptor().getName();
+
+        //JMenuItem deleteItem = new JMenuItem(connName);
+        String targetName = "";
+        if (bc.getTarget().getBean() instanceof BeanCommon) {
+          targetName = ((BeanCommon)bc.getTarget().getBean()).getCustomName();
+        } else {
+          targetName = bc.getTarget().getBean().getClass().getName();
+          targetName = targetName.substring(targetName.lastIndexOf('.')+1, targetName.length());
+        }
+        MenuItem deleteItem = new MenuItem(connName + "-->" + targetName);
+        deleteItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+              bc.remove();
+              m_beanLayout.revalidate();
+              m_beanLayout.repaint();
+              notifyIsDirty();
+            }
+          });
+        deleteConnectionMenu.add(deleteItem);
+        menuItemCount++;
+      }
+
+      //deleteConnectionMenu.show(m_beanLayout, x, y);
+      m_beanLayout.add(deleteConnectionMenu);
+      deleteConnectionMenu.show(m_beanLayout, x, y);
+    }
+  }
+
+  /**
+   * Initiates the connection process for two beans
+   *
+   * @param esd the EventSetDescriptor for the source bean
+   * @param bi the source bean
+   * @param x the x coordinate to start connecting from
+   * @param y the y coordinate to start connecting from
+   */
+  private void connectComponents(EventSetDescriptor esd, 
+				 BeanInstance bi,
+				 int x,
+				 int y) {
+    // record the event set descriptior for this event
+    m_sourceEventSetDescriptor = esd;
+
+    Class listenerClass = esd.getListenerType(); // class of the listener
+    JComponent source = (JComponent)bi.getBean();
+    // now determine which (if any) of the other beans implement this
+    // listener
+    int targetCount = 0;
+    Vector beanInstances = BeanInstance.getBeanInstances();
+    for (int i = 0; i < beanInstances.size(); i++) {
+      JComponent bean = 
+	(JComponent)((BeanInstance)beanInstances.elementAt(i)).getBean();
+      boolean connectable = false;
+      boolean canContinue = false;
+      if (bean != source) {
+        if (bean instanceof MetaBean) {
+          if (((MetaBean)bean).canAcceptConnection(listenerClass)) {
+            canContinue = true;
+          }
+        } else if (listenerClass.isInstance(bean) && bean != source) {
+          canContinue = true;
+        }
+      }
+      if (canContinue) {
+	if (!(bean instanceof BeanCommon)) {
+	  connectable = true; // assume this bean is happy to receive a connection
+	} else {
+	  // give this bean a chance to veto any proposed connection via
+	  // the listener interface
+	  if (((BeanCommon)bean).
+	      //connectionAllowed(esd.getName())) {
+	      connectionAllowed(esd)) {
+	    connectable = true;
+	  }
+	}
+	if (connectable) {
+	  if (bean instanceof Visible) {
+	    targetCount++;
+	    ((Visible)bean).getVisual().setDisplayConnectors(true);
+	  }
+	}
+      }
+    }
+    
+    // have some possible beans to connect to?
+    if (targetCount > 0) {
+      //      System.err.println("target count "+targetCount);
+      if (source instanceof Visible) {
+	((Visible)source).getVisual().setDisplayConnectors(true);
+      }
+
+      m_editElement = bi;
+      Point closest = ((Visible)source).getVisual().
+	getClosestConnectorPoint(new Point(x, y));
+
+      m_startX = (int)closest.getX();
+      m_startY = (int)closest.getY();
+      m_oldX = m_startX;
+      m_oldY = m_startY;
+
+      Graphics2D gx = (Graphics2D)m_beanLayout.getGraphics();
+      gx.setXORMode(java.awt.Color.white);
+      gx.drawLine(m_startX, m_startY, m_startX, m_startY);
+      gx.dispose();
+      m_mode = CONNECTING;
+    }
+  }
+
+  private void addComponent(BeanInstance comp, boolean repaint) {
+    if (comp.getBean() instanceof Visible) {
+      ((Visible)comp.getBean()).getVisual().addPropertyChangeListener(this);
+    }
+    if (comp.getBean() instanceof BeanCommon) {
+      ((BeanCommon)comp.getBean()).setLog(m_logPanel);
+    }
+    if (comp.getBean() instanceof MetaBean) {
+      // re-align sub-beans
+      Vector list;
+      
+      list = ((MetaBean) comp.getBean()).getInputs();
+      for (int i = 0; i < list.size(); i++) {
+        ((BeanInstance) list.get(i)).setX(comp.getX());
+        ((BeanInstance) list.get(i)).setY(comp.getY());
+      }
+
+      list = ((MetaBean) comp.getBean()).getOutputs();
+      for (int i = 0; i < list.size(); i++) {
+        ((BeanInstance) list.get(i)).setX(comp.getX());
+        ((BeanInstance) list.get(i)).setY(comp.getY());
+      }
+    }
+    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    if (repaint) {
+      m_beanLayout.repaint();
+    }
+    m_pointerB.setSelected(true);
+    m_mode = NONE;
+  }
+
+  private void addComponent(int x, int y) {
+    if (m_toolBarBean instanceof MetaBean) {
+      // need to add the MetaBean's internal connections
+      // to BeanConnection's vector
+      Vector associatedConnections = 
+        ((MetaBean)m_toolBarBean).getAssociatedConnections();
+      BeanConnection.getConnections().addAll(associatedConnections);
+    }
+
+    if (m_toolBarBean instanceof BeanContextChild) {
+      m_bcSupport.add(m_toolBarBean);
+    }
+    BeanInstance bi = new BeanInstance(m_beanLayout, m_toolBarBean, x, y);
+    //    addBean((JComponent)bi.getBean());
+    m_toolBarBean = null;
+    addComponent(bi, true);
+  }
+
+  /**
+   * Handles the checking of a selected set of components
+   * for suitability for grouping. If suitable the user
+   * is prompted for a name and then a MetaBean is used
+   * group the components.
+   */
+  private void checkSubFlow(int startX, int startY,
+                            int endX, int endY) {
+
+    java.awt.Rectangle r = 
+      new java.awt.Rectangle((startX < endX) ? startX : endX,
+                             (startY < endY) ? startY: endY,
+                             Math.abs(startX - endX),
+                             Math.abs(startY - endY));
+    //    System.err.println(r);
+    Vector selected = BeanInstance.findInstances(r);
+    //    System.err.println(r);
+    // check if sub flow is valid
+    Vector inputs = BeanConnection.inputs(selected);
+    Vector outputs = BeanConnection.outputs(selected);
+    
+    // screen the inputs and outputs
+    if (inputs.size() == 0 || outputs.size() == 0) {
+      return;
+    }
+
+    // dissallow MetaBeans in the selected set (for the
+    // time being).
+    for (int i = 0; i < selected.size(); i++) {
+      BeanInstance temp = (BeanInstance)selected.elementAt(i);
+      if (temp.getBean() instanceof MetaBean) {
+        return;
+      }
+    }
+
+    // show connector dots for selected beans
+    for (int i = 0; i < selected.size(); i++) {
+      BeanInstance temp = (BeanInstance)selected.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)temp.getBean()).getVisual().setDisplayConnectors(true);
+      }
+    }
+
+    // show connector dots for input beans
+    for (int i = 0; i < inputs.size(); i++) {
+      BeanInstance temp = (BeanInstance)inputs.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)temp.getBean()).getVisual().
+          setDisplayConnectors(true, java.awt.Color.red);
+      }
+    }
+
+    // show connector dots for output beans
+    for (int i = 0; i < outputs.size(); i++) {
+      BeanInstance temp = (BeanInstance)outputs.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)temp.getBean()).getVisual().
+          setDisplayConnectors(true, java.awt.Color.green);
+      }
+    }
+    
+    BufferedImage subFlowPreview = null; 
+    try {
+      	subFlowPreview = createImage(m_beanLayout, r);              
+    } catch (IOException ex) {
+      ex.printStackTrace();
+      // drop through quietly
+    }
+
+    // Confirmation pop-up
+    int result = JOptionPane.showConfirmDialog(KnowledgeFlowApp.this,
+                                               "Group this sub-flow?",
+                                               "Group Components",
+                                               JOptionPane.YES_NO_OPTION);
+    if (result == JOptionPane.YES_OPTION) {
+      Vector associatedConnections = 
+        BeanConnection.associatedConnections(selected);
+
+      String name = JOptionPane.showInputDialog(KnowledgeFlowApp.this,
+                                                "Enter a name for this group",
+                                                "MyGroup");
+      if (name != null) {       
+        MetaBean group = new MetaBean();
+        group.setSubFlow(selected);
+        group.setAssociatedConnections(associatedConnections);
+        group.setInputs(inputs);
+        group.setOutputs(outputs);
+        group.setSubFlowPreview(new ImageIcon(subFlowPreview));
+        if (name.length() > 0) {
+          //          group.getVisual().setText(name);
+          group.setCustomName(name);
+        }
+        
+        if (group instanceof BeanContextChild) {
+          m_bcSupport.add(group);
+        }
+        BeanInstance bi = new BeanInstance(m_beanLayout, group, 
+                                           (int)r.getX()+(int)(r.getWidth()/2),
+                                           (int)r.getY()+(int)(r.getHeight()/2));
+        for (int i = 0; i < selected.size(); i++) {
+          BeanInstance temp = (BeanInstance)selected.elementAt(i);
+          temp.removeBean(m_beanLayout);
+          if (temp.getBean() instanceof Visible) {
+            ((Visible)temp.getBean()).getVisual().removePropertyChangeListener(this);
+          }
+        }
+        for (int i = 0; i < associatedConnections.size(); i++) {
+          BeanConnection temp = (BeanConnection)associatedConnections.elementAt(i);
+          temp.setHidden(true);
+        }
+        group.shiftBeans(bi, true);
+        
+        addComponent(bi, true);
+      }
+    }
+
+    // hide connector dots
+    for (int i = 0; i < selected.size(); i++) {
+      BeanInstance temp = (BeanInstance)selected.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)temp.getBean()).getVisual().setDisplayConnectors(false);
+      }
+    }    
+  }
+
+  /**
+   * Accept property change events
+   *
+   * @param e a <code>PropertyChangeEvent</code> value
+   */
+  public void propertyChange(PropertyChangeEvent e) {
+    revalidate();
+    m_beanLayout.repaint();
+  }
+  
+  /**
+   * Load a pre-saved layout
+   */
+  private void loadLayout() {
+    m_loadB.setEnabled(false);
+    m_saveB.setEnabled(false);
+    int returnVal = m_FileChooser.showOpenDialog(this);
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      stopFlow();
+
+      // determine filename
+      File oFile = m_FileChooser.getSelectedFile();
+      // set internal flow directory environment variable
+      m_flowEnvironment.addVariable("Internal.knowledgeflow.directory", oFile.getParent());
+
+      // add extension if necessary
+      if (m_FileChooser.getFileFilter() == m_KfFilter) {
+        if (!oFile.getName().toLowerCase().endsWith(FILE_EXTENSION)) {
+          oFile = new File(oFile.getParent(), 
+                           oFile.getName() + FILE_EXTENSION);
+        }
+      } else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+        if (!oFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION + "kf")) {
+          oFile = new File(oFile.getParent(), 
+                           oFile.getName() + KOML.FILE_EXTENSION + "kf");
+        }
+      } else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+        if (!oFile.getName().toLowerCase().endsWith(FILE_EXTENSION_XML)) {
+          oFile = new File(oFile.getParent(), 
+                           oFile.getName() + FILE_EXTENSION_XML);
+        }
+      } else if (m_FileChooser.getFileFilter() == m_XStreamFilter) {
+        if (!oFile.getName().toLowerCase().endsWith(XStream.FILE_EXTENSION +"kf")) {
+          oFile = new File(oFile.getParent(), 
+                           oFile.getName() + XStream.FILE_EXTENSION + "kf");
+        }
+      }
+    
+      try {
+        Vector beans       = new Vector();
+        Vector connections = new Vector();
+
+        // KOML?
+        if ( (KOML.isPresent()) && 
+             (oFile.getAbsolutePath().toLowerCase().
+              endsWith(KOML.FILE_EXTENSION + "kf")) ) {
+          Vector v     = (Vector) KOML.read(oFile.getAbsolutePath());
+          beans        = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+          connections  = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
+        } /* XStream */ else if ( (XStream.isPresent()) && 
+             (oFile.getAbsolutePath().toLowerCase().
+              endsWith(XStream.FILE_EXTENSION + "kf")) ) {
+          Vector v     = (Vector) XStream.read(oFile.getAbsolutePath());
+          beans        = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+          connections  = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
+        } /* XML? */ else if (oFile.getAbsolutePath().toLowerCase().
+                              endsWith(FILE_EXTENSION_XML)) {
+          XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport); 
+          Vector v     = (Vector) xml.read(oFile);
+          beans        = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+          connections  = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
+          //connections  = new Vector();
+        } /* binary */ else {
+          InputStream is = new FileInputStream(oFile);
+          ObjectInputStream ois = new ObjectInputStream(is);
+          beans = (Vector) ois.readObject();
+          connections = (Vector) ois.readObject();
+          ois.close();
+        }
+
+        integrateFlow(beans, connections);
+        setEnvironment();
+        m_logPanel.clearStatus();
+        m_logPanel.statusMessage("[KnowledgeFlow]|Flow loaded.");
+      } catch (Exception ex) {
+        m_logPanel.statusMessage("[KnowledgeFlow]|Unable to load flow (see log).");
+        m_logPanel.logMessage("[KnowledgeFlow] Unable to load flow ("
+            + ex.getMessage() + ").");
+	ex.printStackTrace();
+      }
+    }
+    m_loadB.setEnabled(true);
+    m_saveB.setEnabled(true);
+  }
+
+  // Link the supplied beans into the KnowledgeFlow gui
+  private void integrateFlow(Vector beans, Vector connections) {
+    java.awt.Color bckC = getBackground();
+    m_bcSupport = new BeanContextSupport();
+    m_bcSupport.setDesignTime(true);
+
+    // register this panel as a property change listener with each
+    // bean
+    for (int i = 0; i < beans.size(); i++) {
+      BeanInstance tempB = (BeanInstance)beans.elementAt(i);
+      if (tempB.getBean() instanceof Visible) {
+        ((Visible)(tempB.getBean())).getVisual().
+          addPropertyChangeListener(this);
+
+        // A workaround to account for JPanel's with their default
+        // background colour not being serializable in Apple's JRE
+        ((Visible)(tempB.getBean())).getVisual().
+          setBackground(bckC);
+        ((JComponent)(tempB.getBean())).setBackground(bckC);
+      }
+      if (tempB.getBean() instanceof BeanCommon) {
+        ((BeanCommon)(tempB.getBean())).setLog(m_logPanel);
+      }
+      if (tempB.getBean() instanceof BeanContextChild) {
+        m_bcSupport.add(tempB.getBean());
+      }
+    }
+    BeanInstance.setBeanInstances(beans, m_beanLayout);
+    BeanConnection.setConnections(connections);
+    m_beanLayout.revalidate();
+    m_beanLayout.repaint();
+  }
+
+  /**
+   * Set the flow for the KnowledgeFlow to edit. Assumes that client
+   * has loaded a Vector of beans and a Vector of connections. the supplied
+   * beans and connections are deep-copied via serialization before being
+   * set in the layout.
+   *
+   * @param v a Vector containing a Vector of beans and a Vector of connections
+   * @exception Exception if something goes wrong
+   */
+  public void setFlow(Vector v) throws Exception {
+    //    Vector beansCopy = null, connectionsCopy = null;
+    clearLayout();
+    SerializedObject so = new SerializedObject(v);
+    Vector copy = (Vector)so.getObject();
+    
+    Vector beans = (Vector)copy.elementAt(0);
+    Vector connections = (Vector)copy.elementAt(1);
+    
+    // reset environment variables
+    m_flowEnvironment = new Environment();
+    integrateFlow(beans, connections);
+  }
+
+  /**
+   * Gets the current flow being edited. The flow is returned as a single
+   * Vector containing two other Vectors: the beans and the connections.
+   * These two vectors are deep-copied via serialization before being
+   * returned.
+   *
+   * @return the current flow being edited
+   */
+  public Vector getFlow() throws Exception {
+    Vector v = new Vector();
+    Vector beans = BeanInstance.getBeanInstances();
+    Vector connections = BeanConnection.getConnections();
+    detachFromLayout(beans);
+    v.add(beans);
+    v.add(connections);
+
+    SerializedObject so = new SerializedObject(v);
+    Vector copy = (Vector)so.getObject();
+
+    //    tempWrite(beans, connections);
+    
+    integrateFlow(beans, connections);
+    return copy;
+  }
+  
+  /**
+   * Utility method to create an image of a region of the given component
+   * @param component the component to create an image of
+   * @param region the region of the component to put into the image
+   * @return the image
+   * @throws IOException
+   */
+  protected static BufferedImage createImage(JComponent component, Rectangle region)
+  throws IOException {
+    boolean opaqueValue = component.isOpaque();
+    component.setOpaque( true );
+    BufferedImage image = new BufferedImage(region.width, 
+	region.height, BufferedImage.TYPE_INT_RGB);
+    Graphics2D g2d = image.createGraphics();
+    g2d.translate(-region.getX(), -region.getY());
+    //g2d.setClip( region );
+    component.paint( g2d );
+    g2d.dispose();
+    component.setOpaque( opaqueValue );
+    
+    return image;
+  }
+
+  // Remove this panel as a property changle listener from
+  // each bean
+  private void detachFromLayout(Vector beans) {
+    for (int i = 0; i < beans.size(); i++) {
+      BeanInstance tempB = (BeanInstance)beans.elementAt(i);
+      if (tempB.getBean() instanceof Visible) {
+        ((Visible)(tempB.getBean())).getVisual().
+          removePropertyChangeListener(this);
+          
+        if (tempB.getBean() instanceof MetaBean) {
+          ((MetaBean)tempB.getBean()).
+            removePropertyChangeListenersSubFlow(this);
+        }
+
+        // A workaround to account for JPanel's with their default
+        // background colour not being serializable in Apple's JRE.
+        // JComponents are rendered with a funky stripy background
+        // under OS X using java.awt.TexturePaint - unfortunately
+        // TexturePaint doesn't implement Serializable.
+        ((Visible)(tempB.getBean())).getVisual().
+          setBackground(java.awt.Color.white);
+        ((JComponent)(tempB.getBean())).setBackground(java.awt.Color.white);
+      }
+    }
+  }
+
+  /**
+   * Serialize the layout to a file
+   */
+  private void saveLayout() {
+    //    m_loadB.setEnabled(false);
+    //    m_saveB.setEnabled(false);
+    int returnVal = m_FileChooser.showSaveDialog(this);
+    java.awt.Color bckC = getBackground();
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      // temporarily remove this panel as a property changle listener from
+      // each bean
+
+      Vector beans = BeanInstance.getBeanInstances();
+      detachFromLayout(beans);
+
+      // determine filename
+      File sFile = m_FileChooser.getSelectedFile();
+
+      // add extension if necessary
+      if (m_FileChooser.getFileFilter() == m_KfFilter) {
+        if (!sFile.getName().toLowerCase().endsWith(FILE_EXTENSION)) {
+          sFile = new File(sFile.getParent(), 
+                           sFile.getName() + FILE_EXTENSION);
+        }
+      } else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+        if (!sFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION + "kf")) {
+          sFile = new File(sFile.getParent(), 
+                           sFile.getName() + KOML.FILE_EXTENSION + "kf");
+        }
+      } else if (m_FileChooser.getFileFilter() == m_XStreamFilter) {
+        if (!sFile.getName().toLowerCase().endsWith(XStream.FILE_EXTENSION + "kf")) {
+          sFile = new File(sFile.getParent(), 
+                           sFile.getName() + XStream.FILE_EXTENSION + "kf");
+        }
+      } else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+        if (!sFile.getName().toLowerCase().endsWith(FILE_EXTENSION_XML)) {
+          sFile = new File(sFile.getParent(), 
+                           sFile.getName() + FILE_EXTENSION_XML);
+        }
+      }
+    
+      // now serialize components vector and connections vector
+      try {
+        // KOML?
+        if ((KOML.isPresent()) && 
+            (sFile.getAbsolutePath().toLowerCase().
+             endsWith(KOML.FILE_EXTENSION + "kf")) ) {
+          Vector v = new Vector();
+          v.setSize(2);
+          v.set(XMLBeans.INDEX_BEANINSTANCES, beans);
+          v.set(XMLBeans.INDEX_BEANCONNECTIONS, BeanConnection.getConnections());
+          KOML.write(sFile.getAbsolutePath(), v);
+        } /* XStream */ else if ((XStream.isPresent()) && 
+            (sFile.getAbsolutePath().toLowerCase().
+             endsWith(XStream.FILE_EXTENSION + "kf")) ) {
+          Vector v = new Vector();
+          v.setSize(2);
+          v.set(XMLBeans.INDEX_BEANINSTANCES, beans);
+          v.set(XMLBeans.INDEX_BEANCONNECTIONS, BeanConnection.getConnections());
+          XStream.write(sFile.getAbsolutePath(), v);
+        } /* XML? */ else if (sFile.getAbsolutePath().
+                              toLowerCase().endsWith(FILE_EXTENSION_XML)) {
+          Vector v = new Vector();
+          v.setSize(2);
+          v.set(XMLBeans.INDEX_BEANINSTANCES, beans);
+          v.set(XMLBeans.INDEX_BEANCONNECTIONS, BeanConnection.getConnections());
+          XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport); 
+          xml.write(sFile, v);
+        } /* binary */ else {
+          OutputStream os = new FileOutputStream(sFile);
+          ObjectOutputStream oos = new ObjectOutputStream(os);
+          oos.writeObject(beans);
+          oos.writeObject(BeanConnection.getConnections());
+          oos.flush();
+          oos.close();
+        }
+        m_logPanel.statusMessage("[KnowledgeFlow]|Flow saved.");
+        
+        // set the internal knowledgeflow directory environment var for this flow
+        m_flowEnvironment.addVariable("Internal.knowledgeflow.directory", sFile.getParent());
+        setEnvironment();
+      } catch (Exception ex) {
+        m_logPanel.statusMessage("[KnowledgeFlow]|Unable to save flow (see log).");
+        m_logPanel.logMessage("[KnowledgeFlow] Unable to save flow ("
+            + ex.getMessage() + ").");
+	ex.printStackTrace();
+      } finally {
+	// restore this panel as a property change listener in the beans
+	for (int i = 0; i < beans.size(); i++) {
+	  BeanInstance tempB = (BeanInstance)beans.elementAt(i);
+	  if (tempB.getBean() instanceof Visible) {
+	    ((Visible)(tempB.getBean())).getVisual().
+	      addPropertyChangeListener(this);
+
+            if (tempB.getBean() instanceof MetaBean) {
+              ((MetaBean)tempB.getBean()).
+                addPropertyChangeListenersSubFlow(this);
+            }
+	    // Restore the default background colour
+	    ((Visible)(tempB.getBean())).getVisual().
+	      setBackground(bckC);
+	    ((JComponent)(tempB.getBean())).setBackground(bckC);
+	  }
+	}
+      }
+    }
+    //    m_saveB.setEnabled(true);
+    //    m_loadB.setEnabled(true);
+  }
+
+  /**
+   * Save the knowledge flow into the OutputStream passed at input. Only
+   * supports saving the layout data (no trained models) to XML.
+   *
+   * @param out		the output stream to save the layout in
+   */
+  public void saveLayout(OutputStream out) {
+    // temporarily remove this panel as a property changle listener from
+    // each bean
+    Vector beans = BeanInstance.getBeanInstances();
+
+    for (int i = 0; i < beans.size(); i++) {
+      BeanInstance tempB = (BeanInstance) beans.elementAt(i);
+
+      if (tempB.getBean() instanceof Visible) {
+        ((Visible) (tempB.getBean())).getVisual()
+         .removePropertyChangeListener(this);
+
+        if (tempB.getBean() instanceof MetaBean) {
+          ((MetaBean) tempB.getBean()).removePropertyChangeListenersSubFlow(this);
+        }
+      }
+    }
+
+    // now serialize components vector and connections vector
+    try {
+      Vector v = new Vector();
+      v.setSize(2);
+      v.set(XMLBeans.INDEX_BEANINSTANCES, beans);
+      v.set(XMLBeans.INDEX_BEANCONNECTIONS, BeanConnection.getConnections());
+
+      XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport);
+      xml.write(out, v);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    } finally {
+      // restore this panel as a property change listener in the beans
+      for (int i = 0; i < beans.size(); i++) {
+        BeanInstance tempB = (BeanInstance) beans.elementAt(i);
+
+        if (tempB.getBean() instanceof Visible) {
+          ((Visible) (tempB.getBean())).getVisual()
+           .addPropertyChangeListener(this);
+
+          if (tempB.getBean() instanceof MetaBean) {
+            ((MetaBean) tempB.getBean()).addPropertyChangeListenersSubFlow(this);
+          }
+        }
+      }
+    }
+  }
+
+  private void loadUserComponents() {
+    Vector tempV = null;
+    String ext = "";
+    if (m_UserComponentsInXML)
+      ext = USERCOMPONENTS_XML_EXTENSION;
+    File sFile = 
+      new File(System.getProperty("user.home")
+               +File.separator + ".knowledgeFlow"
+               +File.separator + "userComponents"
+               +ext);
+    if (sFile.exists()) {
+      try {
+        if (m_UserComponentsInXML) {
+          XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport, XMLBeans.DATATYPE_USERCOMPONENTS);
+          tempV = (Vector) xml.read(sFile);
+        }
+        else {
+          InputStream is = new FileInputStream(sFile);
+          ObjectInputStream ois = new ObjectInputStream(is);
+          tempV = (Vector)ois.readObject();
+          ois.close();
+        }
+      } catch (Exception ex) {
+        System.err.println("[KnowledgeFlow] Problem reading user components.");
+        ex.printStackTrace();
+        return;
+      }
+      if (tempV.size() > 0) {
+        // create the user tab and add the components
+        for (int i = 0; i < tempV.size(); i++) {
+          MetaBean tempB = (MetaBean)tempV.elementAt(i);
+          addToUserToolBar(tempB, false);
+        }
+      }
+    }
+  }
+
+  private void installWindowListenerForSavingUserBeans() {
+    ((java.awt.Window)getTopLevelAncestor()).
+      addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent e) {
+            System.out.println("[KnowledgeFlow] Saving user components....");
+            File sFile = 
+              new File(System.getProperty("user.home")
+                       +File.separator+".knowledgeFlow");
+            if (!sFile.exists()) {
+              if (!sFile.mkdir()) {
+                System.err.println("[KnowledgeFlow] Unable to create .knowledgeFlow "
+                                   +"directory in your HOME.");
+              } else {
+                // make the plugins subdirectory for the user
+                sFile = new File(sFile.toString() + File.separator 
+                    + "plugins");
+                sFile.mkdir();
+              }
+            }
+            try {
+              String ext = "";
+              if (m_UserComponentsInXML)
+                ext = USERCOMPONENTS_XML_EXTENSION;
+              File sFile2 = new File(sFile.getAbsolutePath()
+                                     +File.separator
+                                     +"userComponents"
+                                     +ext);
+                
+              if (m_UserComponentsInXML) {
+                XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport, XMLBeans.DATATYPE_USERCOMPONENTS);
+                xml.write(sFile2, m_userComponents);
+              }
+              else {
+                OutputStream os = new FileOutputStream(sFile2);
+                ObjectOutputStream oos = new ObjectOutputStream(os);
+                oos.writeObject(m_userComponents);
+                oos.flush();
+                oos.close();
+              }
+            } catch (Exception ex) {
+              System.err.println("[KnowledgeFlow] Unable to save user components");
+              ex.printStackTrace();
+            } 
+
+          }
+        });
+  }
+  
+  /**
+   * Utility method for grabbing the global info help (if it exists) from
+   * an arbitrary object
+   *
+   * @param tempBean the object to grab global info from 
+   * @return the global help info or null if global info does not exist
+   */
+  public static String getGlobalInfo(Object tempBean) {
+    // set tool tip text from global info if supplied
+    String gi = null;
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(tempBean.getClass());
+      MethodDescriptor [] methods = bi.getMethodDescriptors();
+      for (int i = 0; i < methods.length; i++) {
+	String name = methods[i].getDisplayName();
+	Method meth = methods[i].getMethod();
+	if (name.equals("globalInfo")) {
+	  if (meth.getReturnType().equals(String.class)) {
+	    Object args[] = { };
+	    String globalInfo = (String)(meth.invoke(tempBean, args));
+	    gi = globalInfo;
+	    break;
+	  }
+	}
+      }
+    } catch (Exception ex) {
+      
+    }
+    return gi;
+  }
+
+  /** variable for the KnowLedgeFlow class which would be set to null by the 
+      memory monitoring thread to free up some memory if we running out of 
+      memory.
+   */
+  private static KnowledgeFlowApp m_knowledgeFlow;
+
+  /** for monitoring the Memory consumption */
+  private static Memory m_Memory = new Memory(true);
+
+  // list of things to be notified when the startup process of
+  // the KnowledgeFlow is complete
+  public static Vector s_startupListeners = new Vector();
+
+  // modifications by Zerbetto
+  // If showFileMenu is true, the file menu (open file, new file, save file buttons) is showed
+  private boolean m_showFileMenu = true;
+  
+  /**
+   * Create the singleton instance of the KnowledgeFlow
+   * @param args can contain a file argument for loading a flow layout 
+   * (format: "file=[path to layout file]")
+   * Modified by Zerbetto: you can specify the path of a knowledge flow layout file at input
+   */
+  public static void createSingleton(String[] args) {
+    //modifications by Zerbetto 05-12-2007
+    String fileName = null;
+    boolean showFileMenu = true;
+
+    if ((args != null) && (args.length > 0)) {
+      for (int i = 0; i < args.length; i++) {
+        String arg = args[i];
+
+        if (arg.startsWith("file=")) {
+          fileName = arg.substring("file=".length());
+        } else if (arg.startsWith("showFileMenu=")) {
+          showFileMenu = Boolean.parseBoolean(arg.substring(
+                "showFileMenu=".length()));
+        }
+      }
+    }
+
+    if (m_knowledgeFlow == null) {
+      m_knowledgeFlow = new KnowledgeFlowApp(showFileMenu);
+    }
+
+    // end modifications by Zerbetto
+
+    // notify listeners (if any)
+    for (int i = 0; i < s_startupListeners.size(); i++) {
+      ((StartUpListener) s_startupListeners.elementAt(i)).startUpComplete();
+    }
+
+    //modifications by Zerbetto 05-12-2007
+    if (fileName != null) {
+      m_knowledgeFlow.loadInitialLayout(fileName);
+    }
+
+    // end modifications 
+  }
+
+  /**
+   * Return the singleton instance of the KnowledgeFlow
+   *
+   * @return the singleton instance
+   */
+  public static KnowledgeFlowApp getSingleton() {
+    return m_knowledgeFlow;
+  }
+
+  /**
+   * Add a listener to be notified when startup is complete
+   * 
+   * @param s a listener to add
+   */
+  public static void addStartupListener(StartUpListener s) {
+    s_startupListeners.add(s);
+  }
+
+  /**
+   * Loads the specified file at input
+   *
+   * Added by Zerbetto
+   */
+  //modifications by Zerbetto 05-12-2007
+  private void loadInitialLayout(String fileName) {
+    File oFile = new File(fileName);
+
+    if (oFile.exists() && oFile.isFile()) {
+      m_FileChooser.setSelectedFile(oFile);
+
+      int index = fileName.lastIndexOf('.');
+
+      if (index != -1) {
+        String extension = fileName.substring(index);
+
+        if (FILE_EXTENSION_XML.equalsIgnoreCase(extension)) {
+          m_FileChooser.setFileFilter(m_knowledgeFlow.m_XMLFilter);
+        } else if (FILE_EXTENSION.equalsIgnoreCase(extension)) {
+          m_FileChooser.setFileFilter(m_knowledgeFlow.m_KfFilter);
+        }
+      }
+    } else {
+      System.err.println("[KnowledgeFlow] File '" + fileName + "' does not exists.");
+    }
+
+    try {
+      Vector beans = new Vector();
+      Vector connections = new Vector();
+
+      // KOML?
+      if ((KOML.isPresent()) &&
+            (oFile.getAbsolutePath().toLowerCase().endsWith(KOML.FILE_EXTENSION))) {
+        Vector v = (Vector) KOML.read(oFile.getAbsolutePath());
+        beans = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+        connections = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
+      } /* XML? */ else if (oFile.getAbsolutePath().toLowerCase()
+                                     .endsWith(FILE_EXTENSION_XML)) {
+        XMLBeans xml = new XMLBeans(m_beanLayout, m_bcSupport);
+        Vector v = (Vector) xml.read(oFile);
+        beans = (Vector) v.get(XMLBeans.INDEX_BEANINSTANCES);
+        connections = (Vector) v.get(XMLBeans.INDEX_BEANCONNECTIONS);
+
+        //connections  = new Vector();
+      } /* binary */ else {
+        InputStream is = new FileInputStream(oFile);
+        ObjectInputStream ois = new ObjectInputStream(is);
+        beans = (Vector) ois.readObject();
+        connections = (Vector) ois.readObject();
+        ois.close();
+      }
+
+      java.awt.Color bckC = getBackground();
+      m_bcSupport = new BeanContextSupport();
+      m_bcSupport.setDesignTime(true);
+
+      // register this panel as a property change listener with each
+      // bean
+      for (int i = 0; i < beans.size(); i++) {
+        BeanInstance tempB = (BeanInstance) beans.elementAt(i);
+
+        if (tempB.getBean() instanceof Visible) {
+          ((Visible) (tempB.getBean())).getVisual()
+           .addPropertyChangeListener(this);
+
+          // A workaround to account for JPanel's with their default
+          // background colour not being serializable in Apple's JRE
+          ((Visible) (tempB.getBean())).getVisual().setBackground(bckC);
+          ((JComponent) (tempB.getBean())).setBackground(bckC);
+        }
+
+        if (tempB.getBean() instanceof BeanCommon) {
+          ((BeanCommon) (tempB.getBean())).setLog(m_logPanel);
+        }
+
+        if (tempB.getBean() instanceof BeanContextChild) {
+          m_bcSupport.add(tempB.getBean());
+        }
+      }
+
+      BeanInstance.setBeanInstances(beans, m_beanLayout);
+      BeanConnection.setConnections(connections);
+      m_beanLayout.revalidate();
+      m_beanLayout.repaint();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  //end modifications
+
+  /**
+   * Notifies to the parent swt that the layout is dirty
+   *
+   * Added by Zerbetto
+   */
+  private void notifyIsDirty() {
+    //this.firePropertyChange(new Integer(IEditorPart.PROP_DIRTY).toString(), null, null);
+    this.firePropertyChange("PROP_DIRTY", null, null);
+  }
+
+  /**
+   * Main method.
+   *
+   * @param args a <code>String[]</code> value
+   */
+  public static void main(String [] args) {
+
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      //final KnowledgeFlowApp tm = new KnowledgeFlowApp();
+      m_knowledgeFlow = new KnowledgeFlowApp(true);
+
+      jf.getContentPane().add(m_knowledgeFlow, java.awt.BorderLayout.CENTER);
+      jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+      jf.setSize(1000,750);
+      jf.setVisible(true);     
+
+      
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while(true) {
+            try {
+              //System.out.println("Before sleeping");
+              this.sleep(4000);
+              
+              System.gc();
+
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                jf.dispose();
+                m_knowledgeFlow = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+               
+                // display error
+                System.err.println("\n[KnowledgeFlow] displayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nexiting");
+                System.exit(-1);
+              }
+
+            } catch(InterruptedException ex) { ex.printStackTrace(); }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.NORM_PRIORITY);
+      memMonitor.start();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Loader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Loader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Loader.java	(revision 29)
@@ -0,0 +1,833 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Loader.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.converters.ArffLoader;
+import weka.core.converters.DatabaseLoader;
+import weka.core.converters.FileSourcedConverter;
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.EventSetDescriptor;
+import java.beans.beancontext.BeanContext;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.JButton;
+
+/**
+ * Loads data sets using weka.core.converter classes
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5396 $
+ * @since 1.0
+ * @see AbstractDataSource
+ * @see UserRequestAcceptor
+ */
+public class Loader
+  extends AbstractDataSource 
+  implements Startable, /*UserRequestAcceptor,*/ WekaWrapper,
+	     EventConstraints, BeanCommon, EnvironmentHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1993738191961163027L;
+
+  /**
+   * Holds the instances loaded
+   */
+  private transient Instances m_dataSet;
+
+  /**
+   * Holds the format of the last loaded data set
+   */
+  private transient Instances m_dataFormat;
+
+  /**
+   * Global info for the wrapped loader (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Thread for doing IO in
+   */
+  private LoadThread m_ioThread;
+
+  private static int IDLE = 0;
+  private static int BATCH_LOADING = 1;
+  private static int INCREMENTAL_LOADING = 2;
+  private int m_state = IDLE;
+
+  /**
+   * Loader
+   */
+  private weka.core.converters.Loader m_Loader = new ArffLoader();
+
+  private InstanceEvent m_ie = new InstanceEvent(this);
+
+  /**
+   * Keep track of how many listeners for different types of events there are.
+   */
+  private int m_instanceEventTargets = 0;
+  private int m_dataSetEventTargets = 0;
+  
+  /** Flag indicating that a database has already been configured*/
+  private boolean m_dbSet = false;
+  
+  /**
+   * Logging
+   */
+  protected transient Logger m_log;
+  
+  /**
+   * The environment variables.
+   */
+  protected transient Environment m_env;
+  
+  /**
+   * Asked to stop?
+   */
+  protected boolean m_stopped = false;
+  
+  private class LoadThread extends Thread {
+    private DataSource m_DP;
+
+    public LoadThread(DataSource dp) {
+      m_DP = dp;
+    }
+
+    public void run() {
+      try {
+	m_visual.setAnimated();
+//        m_visual.setText("Loading...");
+        
+	boolean instanceGeneration = true;
+	// determine if we are going to produce data set or instance events
+	/*	for (int i = 0; i < m_listeners.size(); i++) {
+	  if (m_listeners.elementAt(i) instanceof DataSourceListener) {
+	    instanceGeneration = false;
+	    break;
+	  }
+	  } */
+	if (m_dataSetEventTargets > 0) {
+	  instanceGeneration = false;
+          m_state = BATCH_LOADING;
+	}
+	
+	// Set environment variables
+	if (m_Loader instanceof EnvironmentHandler && m_env != null) {
+	  ((EnvironmentHandler)m_Loader).setEnvironment(m_env);
+	}
+	
+	String msg = statusMessagePrefix();
+	if (m_Loader instanceof FileSourcedConverter) {
+	  msg += "Loading " + ((FileSourcedConverter)m_Loader).retrieveFile().getName();
+	} else {
+	  msg += "Loading...";
+	}
+	if (m_log != null) {
+	  m_log.statusMessage(msg);
+	}
+
+	if (instanceGeneration) {
+          m_state = INCREMENTAL_LOADING;
+	  //	  boolean start = true;
+	  Instance nextInstance = null;
+	  // load and pass on the structure first
+	  Instances structure = null;
+	  try {
+            m_Loader.reset();
+            //	    System.err.println("NOTIFYING STRUCTURE AVAIL");
+	    structure = m_Loader.getStructure();
+	    notifyStructureAvailable(structure);
+	  } catch (IOException e) {
+	    if (m_log != null) {
+	      m_log.statusMessage(statusMessagePrefix()
+	          +"ERROR (See log for details");
+	      m_log.logMessage("[Loader] " + statusMessagePrefix()
+	          + " " + e.getMessage());
+	    }
+	    e.printStackTrace();
+	  }
+	  try {
+	    nextInstance = m_Loader.getNextInstance(structure);
+	  } catch (IOException e) {
+	    if (m_log != null) {
+	      m_log.statusMessage(statusMessagePrefix()
+	          +"ERROR (See log for details");
+	      m_log.logMessage("[Loader] " + statusMessagePrefix()
+	          + " " + e.getMessage());
+	    }
+	    e.printStackTrace();
+	  }
+	  int z = 0;
+	  while (nextInstance != null) {
+	    if (m_stopped) {
+	      break;
+	    }
+	    nextInstance.setDataset(structure);
+	    //	    format.add(nextInstance);
+	    /*	    InstanceEvent ie = (start)
+	      ? new InstanceEvent(m_DP, nextInstance, 
+				  InstanceEvent.FORMAT_AVAILABLE)
+		: new InstanceEvent(m_DP, nextInstance, 
+		InstanceEvent.INSTANCE_AVAILABLE); */
+	    //	    if (start) {
+	    //	      m_ie.setStatus(InstanceEvent.FORMAT_AVAILABLE);
+	      //	    } else {
+	    m_ie.setStatus(InstanceEvent.INSTANCE_AVAILABLE);
+	      //	    }
+	    m_ie.setInstance(nextInstance);
+	    //	    start = false;
+	    //	    System.err.println(z);
+	    nextInstance = m_Loader.getNextInstance(structure);
+	    if (nextInstance == null) {
+	      m_ie.setStatus(InstanceEvent.BATCH_FINISHED);
+	    }
+	    notifyInstanceLoaded(m_ie);
+	    z++;
+            if (z % 10000 == 0) {
+//              m_visual.setText("" + z + " instances...");
+              if (m_log != null) {
+                m_log.statusMessage(statusMessagePrefix() 
+                    + "Loaded " + z + " instances");
+              }
+            }
+	  }
+	  m_visual.setStatic();
+//	  m_visual.setText(structure.relationName());
+	} else {
+          m_Loader.reset();
+	  m_dataSet = m_Loader.getDataSet();
+	  m_visual.setStatic();
+	  if (m_log != null) {
+	    m_log.logMessage("[Loader] " + statusMessagePrefix() 
+	        + " loaded " + m_dataSet.relationName());
+	  }
+//	  m_visual.setText(m_dataSet.relationName());
+	  notifyDataSetLoaded(new DataSetEvent(m_DP, m_dataSet));
+	}
+      } catch (Exception ex) {
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix()
+              +"ERROR (See log for details");
+          m_log.logMessage("[Loader] " + statusMessagePrefix()
+              + " " + ex.getMessage());
+        }
+	ex.printStackTrace();
+      } finally {
+        if (Thread.currentThread().isInterrupted()) {
+          if (m_log != null) {
+            m_log.logMessage("[Loader] " + statusMessagePrefix() 
+                + " loading interrupted!");
+          }
+        }
+	m_ioThread = null;
+	//	m_visual.setText("Finished");
+	//	m_visual.setIcon(m_inactive.getVisual());
+	m_visual.setStatic();
+        m_state = IDLE;
+        m_stopped = false;
+        if (m_log != null) {
+          m_log.statusMessage(statusMessagePrefix() + "Finished.");
+        }
+        block(false);
+      }
+    }
+  }
+
+  /**
+   * Global info (if it exists) for the wrapped loader
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  public Loader() {
+    super();
+    setLoader(m_Loader);
+    appearanceFinal();
+  }
+  
+  public void setDB(boolean flag){
+  
+      m_dbSet = flag;
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    JButton goButton = new JButton("Start...");
+    add(goButton, BorderLayout.CENTER);
+    goButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  startLoading();
+	}
+      });
+  }
+
+  protected void appearanceDesign() {
+    removeAll();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    super.setBeanContext(bc);
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      appearanceFinal();
+    }
+  }
+
+  /**
+   * Set the loader to use
+   *
+   * @param loader a <code>weka.core.converters.Loader</code> value
+   */
+  public void setLoader(weka.core.converters.Loader loader) {
+    boolean loadImages = true;
+    if (loader.getClass().getName().
+	compareTo(m_Loader.getClass().getName()) == 0) {
+      loadImages = false;
+    }
+    m_Loader = loader;
+    String loaderName = loader.getClass().toString();
+    loaderName = loaderName.substring(loaderName.
+				      lastIndexOf('.')+1, 
+				      loaderName.length());
+    if (loadImages) {
+      if (m_Loader instanceof Visible) {
+        m_visual = ((Visible) m_Loader).getVisual();
+      } else {
+
+        if (!m_visual.loadIcons(BeanVisual.ICON_PATH+loaderName+".gif",
+                                BeanVisual.ICON_PATH+loaderName+"_animated.gif")) {
+          useDefaultVisual();
+        }
+      }
+    }
+    m_visual.setText(loaderName);
+    
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Loader);
+  }
+  
+  protected void newFileSelected() {
+    if(! (m_Loader instanceof DatabaseLoader)) {
+      // try to load structure (if possible) and notify any listeners
+      try {
+        // Set environment variables
+        if (m_Loader instanceof EnvironmentHandler && m_env != null) {
+          ((EnvironmentHandler)m_Loader).setEnvironment(m_env);
+        }
+        m_dataFormat = m_Loader.getStructure();
+        //      System.err.println(m_dataFormat);
+        System.out.println("[Loader] Notifying listeners of instance structure avail.");
+        notifyStructureAvailable(m_dataFormat);
+      }catch (Exception ex) {
+      }
+    }
+  }
+
+  /**
+   * Get the loader
+   *
+   * @return a <code>weka.core.converters.Loader</code> value
+   */
+  public weka.core.converters.Loader getLoader() {
+    return m_Loader;
+  }
+
+  /**
+   * Set the loader
+   *
+   * @param algorithm a Loader
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void setWrappedAlgorithm(Object algorithm) 
+    {
+
+    if (!(algorithm instanceof weka.core.converters.Loader)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Loader)");
+    }
+    setLoader((weka.core.converters.Loader)algorithm);
+  }
+
+  /**
+   * Get the loader
+   *
+   * @return a Loader
+   */
+  public Object getWrappedAlgorithm() {
+    return getLoader();
+  }
+
+  /**
+   * Notify all listeners that the structure of a data set
+   * is available.
+   *
+   * @param structure an <code>Instances</code> value
+   */
+  protected void notifyStructureAvailable(Instances structure) {
+    if (m_dataSetEventTargets > 0 && structure != null) {
+      DataSetEvent dse = new DataSetEvent(this, structure);
+      notifyDataSetLoaded(dse);
+    } else if (m_instanceEventTargets > 0 && structure != null) {
+      m_ie.setStructure(structure);
+      notifyInstanceLoaded(m_ie);
+    }
+  }
+
+  /**
+   * Notify all Data source listeners that a data set has been loaded
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  protected void notifyDataSetLoaded(DataSetEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_listeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((DataSourceListener)l.elementAt(i)).acceptDataSet(e);
+      }
+      m_dataSet = null;
+    }
+  }
+
+  /**
+   * Notify all instance listeners that a new instance is available
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  protected void notifyInstanceLoaded(InstanceEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_listeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((InstanceListener)l.elementAt(i)).acceptInstance(e);
+      }
+      m_dataSet = null;
+    }
+  }
+
+ 
+  /**
+   * Start loading data
+   */
+  public void startLoading() {
+    if (m_ioThread == null) {
+      //      m_visual.setText(m_dataSetFile.getName());
+      m_state = BATCH_LOADING;
+      m_ioThread = new LoadThread(Loader.this);
+      m_ioThread.setPriority(Thread.MIN_PRIORITY);
+      m_ioThread.start();
+    } else {
+      m_ioThread = null;
+      m_state = IDLE;
+    }
+  }
+
+  /**
+   * Get a list of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  /*public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    boolean ok = true;
+    if (m_ioThread == null) {
+      if (m_Loader instanceof FileSourcedConverter) {
+        String temp = ((FileSourcedConverter) m_Loader).retrieveFile().getPath();
+        Environment env = (m_env == null) ? Environment.getSystemWide() : m_env;
+        try {
+          temp = env.substitute(temp);
+        } catch (Exception ex) {}
+        File tempF = new File(temp);
+	if (!tempF.isFile()) {
+	  ok = false;
+	}
+      }
+      String entry = "Start loading";
+      if (!ok) {
+	entry = "$"+entry;
+      }
+      newVector.addElement(entry);
+    }
+    return newVector.elements();
+  } */
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  /*public void performRequest(String request) {
+    if (request.compareTo("Start loading") == 0) {
+      startLoading();
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Loader)");
+    }
+  } */
+
+  /**
+   * Start loading
+   *
+   * @exception Exception if something goes wrong
+   */
+  public void start() throws Exception {
+    startLoading();
+    block(true);
+  }
+  
+  /**
+   * Gets a string that describes the start action. The
+   * KnowledgeFlow uses this in the popup contextual menu
+   * for the component. The string can be proceeded by
+   * a '$' character to indicate that the component can't
+   * be started at present.
+   * 
+   * @return a string describing the start action.
+   */
+  public String getStartMessage() {
+    boolean ok = true;
+    String entry = "Start loading";
+    if (m_ioThread == null) {
+      if (m_Loader instanceof FileSourcedConverter) {
+        String temp = ((FileSourcedConverter) m_Loader).retrieveFile().getPath();
+        Environment env = (m_env == null) ? Environment.getSystemWide() : m_env;
+        try {
+          temp = env.substitute(temp);
+        } catch (Exception ex) {}
+        File tempF = new File(temp);
+        if (!tempF.isFile()) {
+          ok = false;
+        }
+      }
+      if (!ok) {
+        entry = "$"+entry;
+      }
+    }
+    
+    return entry;
+  }
+  
+  /**
+   * Function used to stop code that calls acceptTrainingSet. This is 
+   * needed as classifier construction is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+
+    if (tf) {
+      try {
+	  // only block if thread is still doing something useful!
+	if (m_ioThread.isAlive() && m_state != IDLE) {
+	  wait();
+        }
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Returns true if the named event can be generated at this time
+   *
+   * @param eventName the event
+   * @return a <code>boolean</code> value
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.compareTo("instance") == 0) {
+      if (!(m_Loader instanceof weka.core.converters.IncrementalConverter)) {
+	return false;
+      }
+      if (m_dataSetEventTargets > 0) {
+	return false;
+      }
+      /*      for (int i = 0; i < m_listeners.size(); i++) {
+	if (m_listeners.elementAt(i) instanceof DataSourceListener) {
+	  return false;
+	}
+	} */
+    }
+
+    if (eventName.compareTo("dataSet") == 0) {
+      if (!(m_Loader instanceof weka.core.converters.BatchConverter)) {
+	return false;
+      }
+      if (m_instanceEventTargets > 0) {
+	return false;
+      }
+      /*      for (int i = 0; i < m_listeners.size(); i++) {
+	if (m_listeners.elementAt(i) instanceof InstanceListener) {
+	  return false;
+	}
+	} */
+    }
+    return true;
+  }
+
+  /**
+   * Add a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void addDataSourceListener(DataSourceListener dsl) {
+    super.addDataSourceListener(dsl);
+    m_dataSetEventTargets ++;
+    // pass on any current instance format
+    try{
+      if((m_Loader instanceof DatabaseLoader && m_dbSet && m_dataFormat == null) || 
+         (!(m_Loader instanceof DatabaseLoader) && m_dataFormat == null)) {
+        m_dataFormat = m_Loader.getStructure();
+        m_dbSet = false;
+      }
+    }catch(Exception ex){
+    }
+    notifyStructureAvailable(m_dataFormat);
+  }
+  
+  /**
+   * Remove a listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void removeDataSourceListener(DataSourceListener dsl) {
+    super.removeDataSourceListener(dsl);
+    m_dataSetEventTargets --;
+  }
+
+  /**
+   * Add an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void addInstanceListener(InstanceListener dsl) {
+    super.addInstanceListener(dsl);
+    m_instanceEventTargets ++;
+    try{
+      if((m_Loader instanceof DatabaseLoader && m_dbSet && m_dataFormat == null) || 
+         (!(m_Loader instanceof DatabaseLoader) && m_dataFormat == null)) {
+        m_dataFormat = m_Loader.getStructure();
+        m_dbSet = false;
+      }
+    }catch(Exception ex){
+    }
+    // pass on any current instance format      
+    notifyStructureAvailable(m_dataFormat);
+  }
+  
+  /**
+   * Remove an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void removeInstanceListener(InstanceListener dsl) {
+    super.removeInstanceListener(dsl);
+    m_instanceEventTargets --;
+  }
+  
+  public static void main(String [] args) {
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+
+      final Loader tv = new Loader();
+
+      jf.getContentPane().add(tv, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+  
+  private Object readResolve() throws ObjectStreamException {
+    // try and reset the Loader
+    if (m_Loader != null) {
+      try {
+        m_Loader.reset();
+      } catch (Exception ex) {
+      }
+    }
+    return this;
+  }
+  
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+  
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+  
+  /**
+   * Set environment variables to use.
+   * 
+   * @param env the environment variables to
+   * use
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the supplied
+   * EventSetDescriptor. Always returns false for loader.
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return false;
+  }
+  
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the named event
+   *
+   * @param eventName the name of the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return false;
+  }
+  
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source for receiving events described by the named event
+   * This object is responsible for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void connectionNotification(String eventName, Object source) {
+    // this should never get called for us.
+  }
+  
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source for named event. This object is responsible
+   * for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void disconnectionNotification(String eventName, Object source) {
+    // this should never get called for us.
+  }
+  
+  /**
+   * Stop any loading action.
+   */
+  public void stop() {
+    m_stopped = true;
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_ioThread != null);
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Loader instanceof OptionHandler) 
+        ? Utils.joinOptions(((OptionHandler)m_Loader).getOptions()) + "|"
+            : "");
+  }
+  
+  // Custom de-serialization in order to set default
+  // environment variables on de-serialization
+  private void readObject(ObjectInputStream aStream) 
+    throws IOException, ClassNotFoundException {
+    aStream.defaultReadObject();
+    
+    // set a default environment to use
+    m_env = Environment.getSystemWide();
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/LoaderBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/LoaderBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/LoaderBeanInfo.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LoaderBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the loader bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class LoaderBeanInfo extends AbstractDataSourceBeanInfo {
+  
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Loader.class,
+			      LoaderCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/LoaderCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/LoaderCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/LoaderCustomizer.java	(revision 29)
@@ -0,0 +1,556 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LoaderCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.converters.DatabaseConverter;
+import weka.core.converters.DatabaseLoader;
+import weka.core.converters.FileSourcedConverter;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.JCheckBox;
+
+/**
+ * GUI Customizer for the loader bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5406 $
+ */
+public class LoaderCustomizer
+  extends JPanel
+  implements Customizer, CustomizerCloseRequester, EnvironmentHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6990446313118930298L;
+
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private weka.gui.beans.Loader m_dsLoader;
+
+  private PropertySheetPanel m_LoaderEditor = 
+    new PropertySheetPanel();
+
+  private JFileChooser m_fileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+  /*  private JDialog m_chooserDialog = 
+    new JDialog((JFrame)getTopLevelAncestor(),
+    true); */
+
+  private JFrame m_parentFrame;
+  private JFrame m_fileChooserFrame;
+  
+  private EnvironmentField m_dbaseURLText;
+  
+  private EnvironmentField m_userNameText;
+  
+  private EnvironmentField m_queryText;
+   
+  private EnvironmentField m_keyText;
+  
+  private JPasswordField m_passwordText;
+
+  private JCheckBox m_relativeFilePath;
+  
+  private EnvironmentField m_fileText;
+  
+  private Environment m_env = Environment.getSystemWide();
+
+  public LoaderCustomizer() {
+    /*    m_fileEditor.addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+	  if (m_dsLoader != null) {
+	    m_dsLoader.setDataSetFile((File)m_fileEditor.getValue());
+	  }
+	}
+	}); */
+
+    try {
+      /*      m_LoaderEditor.setClassType(weka.core.converters.Loader.class);
+	      m_LoaderEditor.setValue(new weka.core.converters.ArffLoader()); */
+      m_LoaderEditor.addPropertyChangeListener(
+	  new PropertyChangeListener() {
+	      public void propertyChange(PropertyChangeEvent e) {
+		repaint();
+		if (m_dsLoader != null) {
+		  System.err.println("Property change!!");
+		  m_dsLoader.setLoader(m_dsLoader.getLoader());
+		}
+	      }
+	    });
+      repaint();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+
+    setLayout(new BorderLayout());
+    //    add(m_fileEditor.getCustomEditor(), BorderLayout.CENTER);
+    //    add(m_LoaderEditor, BorderLayout.CENTER);
+    m_fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
+    m_fileChooser.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
+	    try {
+              File selectedFile = m_fileChooser.getSelectedFile();
+/*              EnvironmentField ef = m_environmentFields.get(0);
+              ef.setText(selectedFile.toString()); */
+              m_fileText.setText(selectedFile.toString());
+              
+/*	      ((FileSourcedConverter)m_dsLoader.getLoader()).
+		setFile(selectedFile);
+	      // tell the loader that a new file has been selected so
+	      // that it can attempt to load the header
+	      //m_dsLoader.setLoader(m_dsLoader.getLoader());
+	      m_dsLoader.newFileSelected(); */
+	    } catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	  }
+	  // closing
+	  if (m_fileChooserFrame != null) {
+	    // m_parentFrame.dispose();
+	    m_fileChooserFrame.dispose();
+	  }
+	}
+      });   
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+  
+  private void setUpOther() {
+    removeAll();
+    add(m_LoaderEditor, BorderLayout.CENTER);
+    validate();
+    repaint();
+  }
+  
+  
+  /** Sets up a customizer window for a Database Connection*/
+  private void setUpDatabase() {
+
+    removeAll();
+
+    JPanel db = new JPanel();
+    GridBagLayout gbLayout = new GridBagLayout();
+    //db.setLayout(new GridLayout(6, 1));
+    db.setLayout(gbLayout);
+
+    JLabel urlLab = new JLabel("Database URL", SwingConstants.RIGHT);
+    urlLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(urlLab, gbConstraints);
+    db.add(urlLab);
+
+    m_dbaseURLText = new EnvironmentField();
+    m_dbaseURLText.setEnvironment(m_env);
+/*    int width = m_dbaseURLText.getPreferredSize().width;
+    int height = m_dbaseURLText.getPreferredSize().height;
+    m_dbaseURLText.setMinimumSize(new Dimension(width * 2, height));
+    m_dbaseURLText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_dbaseURLText.setText(((DatabaseConverter)m_dsLoader.getLoader()).getUrl());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 1;
+    gbConstraints.weightx = 5;
+    gbLayout.setConstraints(m_dbaseURLText, gbConstraints);
+    db.add(m_dbaseURLText);
+    
+    JLabel userLab = new JLabel("Username", SwingConstants.RIGHT);
+    userLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(userLab, gbConstraints);
+    db.add(userLab);
+    
+    m_userNameText = new EnvironmentField();
+    m_userNameText.setEnvironment(m_env);
+/*    m_userNameText.setMinimumSize(new Dimension(width * 2, height));
+    m_userNameText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_userNameText.setText(((DatabaseConverter)m_dsLoader.getLoader()).getUser()); 
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_userNameText, gbConstraints);
+    db.add(m_userNameText);
+
+    JLabel passwordLab = new JLabel("Password ", SwingConstants.RIGHT);
+    passwordLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(passwordLab, gbConstraints);
+    db.add(passwordLab);
+    
+    m_passwordText = new JPasswordField();     
+    JPanel passwordHolder = new JPanel();
+    passwordHolder.setLayout(new BorderLayout());
+    passwordHolder.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+//    passwordHolder.add(passwordLab, BorderLayout.WEST);
+    passwordHolder.add(m_passwordText, BorderLayout.CENTER);
+/*    passwordHolder.setMinimumSize(new Dimension(width * 2, height));
+    passwordHolder.setPreferredSize(new Dimension(width * 2, height)); */
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(passwordHolder, gbConstraints);
+    db.add(passwordHolder);
+
+    JLabel queryLab = new JLabel("Query", SwingConstants.RIGHT);
+    queryLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(queryLab, gbConstraints);
+    db.add(queryLab);
+    
+    m_queryText = new EnvironmentField();
+    m_queryText.setEnvironment(m_env);
+/*    m_queryText.setMinimumSize(new Dimension(width * 2, height));
+    m_queryText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_queryText.setText(((DatabaseLoader)m_dsLoader.getLoader()).getQuery());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_queryText, gbConstraints);
+    db.add(m_queryText);
+    
+    JLabel keyLab = new JLabel("Key columns", SwingConstants.RIGHT);
+    keyLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(keyLab, gbConstraints);
+    db.add(keyLab);
+    
+    m_keyText = new EnvironmentField();
+    m_keyText.setEnvironment(m_env);
+    /*m_keyText.setMinimumSize(new Dimension(width * 2, height));
+    m_keyText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_keyText.setText(((DatabaseLoader)m_dsLoader.getLoader()).getKeys());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_keyText, gbConstraints);
+    db.add(m_keyText);
+
+    JPanel buttonsP = new JPanel();
+    buttonsP.setLayout(new FlowLayout());
+    JButton ok,cancel;
+    buttonsP.add(ok = new JButton("OK"));
+    buttonsP.add(cancel=new JButton("Cancel"));
+    ok.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        ((DatabaseLoader)m_dsLoader.getLoader()).resetStructure();  
+        ((DatabaseConverter)m_dsLoader.getLoader()).setUrl(m_dbaseURLText.getText());
+        ((DatabaseConverter)m_dsLoader.getLoader()).setUser(m_userNameText.getText());
+        ((DatabaseConverter)m_dsLoader.getLoader()).setPassword(new String(m_passwordText.getPassword()));
+        ((DatabaseLoader)m_dsLoader.getLoader()).setQuery(m_queryText.getText());
+        ((DatabaseLoader)m_dsLoader.getLoader()).setKeys(m_keyText.getText());
+        try{
+          m_dsLoader.notifyStructureAvailable(((DatabaseLoader)m_dsLoader.getLoader()).getStructure());
+          //database connection has been configured
+          m_dsLoader.setDB(true);
+        }catch (Exception ex){
+        }
+        if (m_parentFrame != null) {
+          m_parentFrame.dispose();
+        }
+      }
+    });
+    cancel.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        if (m_parentFrame != null) {
+          m_parentFrame.dispose();
+        }
+      }
+    });
+    
+    JPanel holderP = new JPanel();
+    holderP.setLayout(new BorderLayout());
+    holderP.add(db, BorderLayout.NORTH);
+    holderP.add(buttonsP, BorderLayout.SOUTH);
+    
+    //db.add(buttonsP);
+    JPanel about = m_LoaderEditor.getAboutPanel();
+    if (about != null) {
+      add(about, BorderLayout.NORTH);
+    }
+    add(holderP, BorderLayout.SOUTH);
+  }
+
+  public void setUpFile() {
+    removeAll();
+
+    boolean currentFileIsDir = false;
+    File tmp = ((FileSourcedConverter)m_dsLoader.getLoader()).retrieveFile();
+    String tmpString = tmp.toString();
+    if (Environment.containsEnvVariables(tmpString)) {
+      try {
+        tmpString = m_env.substitute(tmpString);
+      } catch (Exception ex) {
+        // ignore
+      }
+    }
+    File tmp2 = new File((new File(tmpString)).getAbsolutePath());
+
+    if (tmp2.isDirectory()) {
+      m_fileChooser.setCurrentDirectory(tmp2);
+      currentFileIsDir = true;
+    } else {
+      m_fileChooser.setSelectedFile(tmp2);
+    }
+    
+    FileSourcedConverter loader = (FileSourcedConverter) m_dsLoader.getLoader();
+    String[] ext = loader.getFileExtensions();
+    ExtensionFileFilter firstFilter = null;
+    for (int i = 0; i < ext.length; i++) {
+      ExtensionFileFilter ff =
+	new ExtensionFileFilter(
+	    ext[i], loader.getFileDescription() + " (*" + ext[i] + ")");
+      if (i == 0)
+	firstFilter = ff;
+      m_fileChooser.addChoosableFileFilter(ff);
+    }
+    if (firstFilter != null)
+      m_fileChooser.setFileFilter(firstFilter);
+    JPanel about = m_LoaderEditor.getAboutPanel();
+    JPanel northPanel = new JPanel();
+    northPanel.setLayout(new BorderLayout());
+    if (about != null) {
+      northPanel.add(about, BorderLayout.NORTH);
+    }
+    add(northPanel, BorderLayout.NORTH);
+    final EnvironmentField ef = new EnvironmentField();
+    JPanel efHolder = new JPanel();
+    efHolder.setLayout(new BorderLayout());
+
+    ef.setEnvironment(m_env);
+    /*int width = ef.getPreferredSize().width;
+    int height = ef.getPreferredSize().height;
+//    ef.setMinimumSize(new Dimension(width * 2, height));
+    ef.setPreferredSize(new Dimension(width * 2, height)); */
+    m_fileText = ef;
+    
+    // only set the text on the EnvironmentField if the current file is not a directory
+    if (!currentFileIsDir) {
+      ef.setText(tmp.toString());
+    }
+    
+    efHolder.add(ef, BorderLayout.CENTER);
+    JButton browseBut = new JButton("Browse...");
+    browseBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+          final JFrame jf = new JFrame("Choose file");
+          jf.getContentPane().setLayout(new BorderLayout());
+          jf.getContentPane().add(m_fileChooser, BorderLayout.CENTER);
+          jf.pack();
+          jf.setVisible(true);
+          m_fileChooserFrame = jf;
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+      }
+    });
+    
+    JPanel bP = new JPanel(); bP.setLayout(new BorderLayout());
+    bP.setBorder(BorderFactory.createEmptyBorder(5,0,5,5));
+    bP.add(browseBut, BorderLayout.CENTER);
+    efHolder.add(bP, BorderLayout.EAST);
+    JPanel alignedP = new JPanel();
+    GridBagLayout gbLayout = new GridBagLayout();
+    alignedP.setLayout(gbLayout);
+    JLabel efLab = new JLabel("Filename", SwingConstants.RIGHT);
+    efLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(efLab, gbConstraints);    
+    alignedP.add(efLab);
+    
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 1;
+    gbConstraints.weightx = 5;
+    gbLayout.setConstraints(efHolder, gbConstraints);
+    alignedP.add(efHolder);
+    
+    
+    northPanel.add(alignedP, BorderLayout.SOUTH);
+    
+    // add(m_fileChooser, BorderLayout.CENTER);
+
+    // m_relativeFilePath = new JCheckBox("Use relative file paths");
+    m_relativeFilePath = new JCheckBox();
+    m_relativeFilePath.
+      setSelected(((FileSourcedConverter)m_dsLoader.getLoader()).getUseRelativePath());
+
+    m_relativeFilePath.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          ((FileSourcedConverter)m_dsLoader.getLoader()).
+            setUseRelativePath(m_relativeFilePath.isSelected());
+        }
+      });
+    
+    JLabel relativeLab = new JLabel("Use relative file paths", SwingConstants.RIGHT);
+    relativeLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(relativeLab, gbConstraints);
+    alignedP.add(relativeLab);
+    
+    
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_relativeFilePath, gbConstraints);
+    alignedP.add(m_relativeFilePath);
+        
+    JPanel butHolder = new JPanel();
+    //butHolder.setLayout(new GridLayout(1,2));
+    butHolder.setLayout(new FlowLayout());
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+          ((FileSourcedConverter)m_dsLoader.getLoader()).
+          setFile(new File(ef.getText()));
+          // tell the loader that a new file has been selected so
+          // that it can attempt to load the header
+          m_dsLoader.setLoader(m_dsLoader.getLoader());
+          m_dsLoader.newFileSelected();
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+        
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+    
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    
+    add(butHolder, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Set the loader to be customized
+   *
+   * @param object a weka.gui.beans.Loader
+   */
+  public void setObject(Object object) {
+    m_dsLoader = (weka.gui.beans.Loader)object;
+    m_LoaderEditor.setTarget(m_dsLoader.getLoader());
+    //    m_fileEditor.setValue(m_dsLoader.getDataSetFile());
+    if (m_dsLoader.getLoader() instanceof FileSourcedConverter) {
+      setUpFile();
+    } else{ 
+        if(m_dsLoader.getLoader() instanceof DatabaseConverter) {
+            setUpDatabase();
+        }
+        else
+      setUpOther();
+    }
+  }
+  
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/LogPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/LogPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/LogPanel.java	(revision 29)
@@ -0,0 +1,422 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LogPanel
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.EventQueue;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableCellRenderer;
+
+import weka.gui.Logger;
+
+/**
+ * Class for displaying a status area (made up of a variable number of
+ * lines) and a log area.
+ * 
+ * @author mhall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class LogPanel extends JPanel implements Logger {
+  
+  /**
+   * Holds the index (line number) in the JTable of each component
+   * being tracked. 
+   */
+  private HashMap<String,Integer> m_tableIndexes = 
+    new HashMap<String, Integer>();
+  
+  /**
+   * Holds the timers associated with each component being tracked.
+   */
+  private HashMap<String, Timer> m_timers =
+    new HashMap<String, Timer>();
+  
+  /**
+   * The table model for the JTable used in the status area
+   */
+  private final DefaultTableModel m_tableModel;
+  
+  /**
+   * The table for the status area
+   */
+  private JTable m_table;
+  
+  /**
+   * Tabbed pane to hold both the status and the log
+   */
+  private JTabbedPane m_tabs = new JTabbedPane();
+      
+  /**
+   * The log panel to delegate log messages to. 
+   */
+  private weka.gui.LogPanel m_logPanel = 
+    new weka.gui.LogPanel(null, false, true, false);
+    
+  public LogPanel() {
+    
+    String[] columnNames = {"Component", "Parameters", "Time", "Status"};
+    m_tableModel = new DefaultTableModel(columnNames, 0);
+    
+    // JTable with error/warning indication for rows.
+    m_table = new JTable() {
+      public Class getColumnClass(int column) {
+        return getValueAt(0, column).getClass();
+      }
+
+      public Component prepareRenderer(TableCellRenderer renderer, 
+          int row, int column) {
+        Component c = super.prepareRenderer(renderer, row, column);
+        if (!c.getBackground().equals(getSelectionBackground()))
+        {
+          String type = (String)getModel().getValueAt(row, 3);
+          Color backgroundIndicator = null;
+          if (type.startsWith("ERROR")) {
+            backgroundIndicator = Color.RED;
+          } else if (type.startsWith("WARNING")) {
+            backgroundIndicator = Color.YELLOW;
+          } else if (type.startsWith("INTERRUPTED")) {
+            backgroundIndicator = Color.MAGENTA;
+          }
+          c.setBackground(backgroundIndicator);
+        }
+        return c;
+      }
+    };
+    
+    m_table.setModel(m_tableModel);
+    m_table.getColumnModel().getColumn(0).setPreferredWidth(100);
+    m_table.getColumnModel().getColumn(1).setPreferredWidth(150);
+    m_table.getColumnModel().getColumn(2).setPreferredWidth(2);
+    m_table.getColumnModel().getColumn(3).setPreferredWidth(500);
+    m_table.setShowVerticalLines(true);
+    
+    JPanel statusPan = new JPanel();
+    statusPan.setLayout(new BorderLayout());
+    statusPan.add(new JScrollPane(m_table), BorderLayout.CENTER);
+    m_tabs.addTab("Status", statusPan);
+    m_tabs.addTab("Log", m_logPanel);
+    
+    setLayout(new BorderLayout());
+    add(m_tabs, BorderLayout.CENTER);
+    
+  }
+  
+  /**
+   * Clear the status area.
+   */
+  public void clearStatus() {
+    // stop any running timers
+    Iterator<Timer> i = m_timers.values().iterator();
+    while (i.hasNext()) {
+      i.next().stop();
+    }
+    
+    // clear the map entries
+    m_timers.clear();
+    m_tableIndexes.clear();
+    
+    // clear the rows from the table
+    while (m_tableModel.getRowCount() > 0) {
+      m_tableModel.removeRow(0);
+    }
+  }
+  
+  /**
+   * The JTable used for the status messages (in case clients
+   * want to attach listeners etc.)
+   * 
+   * @return the JTable used for the status messages.
+   */
+  public JTable getStatusTable() {
+    return m_table;
+  }
+  
+  /**
+   * Sends the supplied message to the log area. These message will typically
+   * have the current timestamp prepended, and be viewable as a history.
+   *
+   * @param message the log message
+   */
+  public synchronized void logMessage(String message) {
+    // delegate to the weka.gui.LogPanel
+    m_logPanel.logMessage(message);
+  }
+  
+  /**
+   * Sends the supplied message to the status area. These messages are
+   * typically one-line status messages to inform the user of progress
+   * during processing (i.e. it doesn't matter if the user doesn't happen
+   * to look at each message). These messages have the following format:
+   * 
+   * <Component name (needs to be unique)>|<Parameter string (optional)|<Status message>
+   *
+   * @param message the status message.
+   */
+  public synchronized void statusMessage(String message) {
+
+    boolean hasDelimiters = (message.indexOf('|') > 0);
+    String stepName = "";
+    String stepHash = "";
+    String stepParameters = "";
+    String stepStatus = "";
+    
+    if (!hasDelimiters) {
+      stepName = "Unknown";
+      stepHash = "Unknown";
+      stepStatus = message;
+    } else {
+      // Extract the fields of the status message
+      stepHash = message.substring(0, message.indexOf('|'));
+      message = message.substring(message.indexOf('|') + 1,
+          message.length());
+      // See if there is a unique object ID in the stepHash string
+      if (stepHash.indexOf('$') > 0) {
+        // Extract the step name
+        stepName = stepHash.substring(0, stepHash.indexOf('$'));
+      } else {
+        stepName = stepHash;
+      }
+      
+      // See if there are any step parameters to extract
+      if (message.indexOf('|') > 0) {
+        stepParameters = message.substring(0, message.indexOf('|'));
+        stepStatus = message.substring(message.indexOf('|') + 1, 
+            message.length());
+      } else {
+        // set the status message to the remainder
+        stepStatus = message;
+      }
+    }
+    
+    // Now see if this step is in the hashmap
+    if (m_tableIndexes.containsKey(stepHash)) {
+      // Get the row number and update the table model...
+      final Integer rowNum = m_tableIndexes.get(stepHash);
+      if (stepStatus.equalsIgnoreCase("remove") ||
+          stepStatus.equalsIgnoreCase("remove.")) {
+        
+        //m_tableModel.fireTableDataChanged();
+        m_tableIndexes.remove(stepHash);
+        m_timers.get(stepHash).stop();
+        m_timers.remove(stepHash);
+        
+        // now need to decrement all the row indexes of
+        // any rows greater than this one
+        Iterator<String> i = m_tableIndexes.keySet().iterator();
+        while (i.hasNext()) {
+          String nextKey = i.next();
+          int index = m_tableIndexes.get(nextKey).intValue();
+          if (index > rowNum.intValue()) {
+            index--;
+            //System.err.println("*** " + nextKey + " needs decrementing to " + index);
+            m_tableIndexes.put(nextKey, index);
+//            System.err.println("new index " + m_rows.get(nextKey).intValue());
+          }
+        }
+        
+        // Remove the entry...
+        if (!SwingUtilities.isEventDispatchThread()) {
+          try {
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                m_tableModel.removeRow(rowNum);
+              }
+            });
+          } catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        } else {
+          m_tableModel.removeRow(rowNum);
+        }
+      } else {
+        final String stepNameCopy = stepName;
+        final String stepStatusCopy = stepStatus;
+        final String stepParametersCopy = stepParameters;
+
+        if (!SwingUtilities.isEventDispatchThread()) {
+          try {
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                // ERROR overrides INTERRUPTED
+                if (!(stepStatusCopy.startsWith("INTERRUPTED") &&
+                    ((String)m_tableModel.getValueAt(rowNum.intValue(), 3)).startsWith("ERROR"))) {
+                  m_tableModel.setValueAt(stepNameCopy, rowNum.intValue(), 0);
+                  m_tableModel.setValueAt(stepParametersCopy, rowNum.intValue(), 1);
+                  m_tableModel.setValueAt(m_table.getValueAt(rowNum.intValue(), 2), rowNum.intValue(), 2);
+                  m_tableModel.setValueAt(stepStatusCopy, rowNum.intValue(), 3);
+                }
+              }
+            });
+          } catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        } else {
+          if (!(stepStatusCopy.startsWith("INTERRUPTED") &&
+              ((String)m_tableModel.getValueAt(rowNum.intValue(), 3)).startsWith("ERROR"))) {
+            m_tableModel.setValueAt(stepNameCopy, rowNum.intValue(), 0);
+            m_tableModel.setValueAt(stepParametersCopy, rowNum.intValue(), 1);
+            m_tableModel.setValueAt(m_table.getValueAt(rowNum.intValue(), 2), rowNum.intValue(), 2);
+            m_tableModel.setValueAt(stepStatusCopy, rowNum.intValue(), 3);
+          }
+        }
+        if (stepStatus.startsWith("ERROR") ||
+            stepStatus.startsWith("INTERRUPTED") ||
+            stepStatus.equalsIgnoreCase("finished") ||
+            stepStatus.equalsIgnoreCase("finished.") ||
+            stepStatus.equalsIgnoreCase("done") ||
+            stepStatus.equalsIgnoreCase("done.")) {
+          // stop the timer.
+          m_timers.get(stepHash).stop();
+        } else if (!m_timers.get(stepHash).isRunning()) {
+          // need to create a new one in order to reset the
+          // elapsed time.
+          installTimer(stepHash);
+        }
+      //  m_tableModel.fireTableCellUpdated(rowNum.intValue(), 3);
+      }
+    } else if (!stepStatus.equalsIgnoreCase("Remove") &&
+        !stepStatus.equalsIgnoreCase("Remove.")) {
+      // Add this one to the hash map
+      int numKeys = m_tableIndexes.keySet().size();
+      m_tableIndexes.put(stepHash, numKeys);
+      
+      // Now add a row to the table model
+      final Object[] newRow = new Object[4];
+      newRow[0] = stepName;
+      newRow[1] = stepParameters;
+      newRow[2] = "-";
+      newRow[3] = stepStatus;
+      final String stepHashCopy = stepHash;
+      try {
+        if (!SwingUtilities.isEventDispatchThread()) {
+          SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+              m_tableModel.addRow(newRow);
+              //m_tableModel.fireTableDataChanged();
+            }
+          });
+        } else {
+          m_tableModel.addRow(newRow);
+        }
+        
+        installTimer(stepHashCopy);
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+  
+  private void installTimer(final String stepHash) {
+    final long startTime = System.currentTimeMillis();
+    Timer newTimer = new Timer(1000, new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        synchronized(LogPanel.this) {
+          if (m_tableIndexes.containsKey(stepHash)) {
+            final Integer rn = m_tableIndexes.get(stepHash);
+            long elapsed = System.currentTimeMillis() - startTime;
+            long seconds = elapsed / 1000;
+            long minutes = seconds / 60;
+            final long hours = minutes / 60;
+            seconds = seconds - (minutes * 60);
+            minutes = minutes - (hours * 60);
+            final long seconds2 = seconds;
+            final long minutes2 = minutes;
+            if (!SwingUtilities.isEventDispatchThread()) {
+              try {
+              SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                  m_tableModel.
+                    setValueAt("" + hours + ":" + minutes2 + ":" + seconds2, rn.intValue(), 2);
+                }
+              });
+              } catch (Exception ex) {
+                ex.printStackTrace();
+              }
+            } else {
+              m_tableModel.
+                setValueAt("" + hours + ":" + minutes2 + ":" + seconds2, rn.intValue(), 2);
+            }
+          }
+        }
+      }
+    });
+    m_timers.put(stepHash, newTimer);
+    newTimer.start();
+  }
+
+  /**
+   * Main method to test this class.
+   * 
+   * @param args any arguments (unused)
+   */
+  public static void main(String[] args) {
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame("Status/Log Panel");
+      
+      jf.getContentPane().setLayout(new BorderLayout());
+      final LogPanel lp = new LogPanel();
+      jf.getContentPane().add(lp, BorderLayout.CENTER);
+      
+      jf.getContentPane().add(lp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.pack();
+      jf.setVisible(true);
+      lp.statusMessage("Step 1|Some options here|A status message");
+      lp.statusMessage("Step 2$hashkey|Status message: no options");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 2$hashkey|Funky Chickens!!!");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 1|Some options here|finished");
+      //lp.statusMessage("Step 1|Some options here|back again!");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 2$hashkey|ERROR! More Funky Chickens!!!");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 2$hashkey|WARNING - now a warning...");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 2$hashkey|Back to normal.");
+      Thread.sleep(3000);
+      lp.statusMessage("Step 2$hashkey|INTERRUPTED.");
+      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/LogWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/LogWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/LogWriter.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LogWriter.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.beans;
+
+/**
+ * Interface to be implemented by classes that should be able to write their
+ * own output to the Weka logger. This is useful, for example, for filters
+ * that provide detailed processing instructions.
+ * 
+ * @author Carsten Pohle (cp AT cpohle de)
+ * @version $Revision: 1.2 $
+ */
+public interface LogWriter {
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  void setLog(weka.gui.Logger logger);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/MetaBean.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/MetaBean.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/MetaBean.java	(revision 29)
@@ -0,0 +1,674 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MetaBean.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.EventSetDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyChangeListener;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JWindow;
+
+/**
+ * A meta bean that encapsulates several other regular beans, useful for 
+ * grouping large KnowledgeFlows.
+ *
+ *
+ * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
+ * @version $Revision: 6014 $
+ */
+public class MetaBean
+  extends JPanel 
+  implements BeanCommon, Visible, EventConstraints,
+             Serializable, UserRequestAcceptor {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6582768902038027077L;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("Group",
+		   BeanVisual.ICON_PATH+"DiamondPlain.gif",
+		   BeanVisual.ICON_PATH+"DiamondPlain.gif");
+
+  private transient Logger m_log = null;
+  private transient JWindow m_previewWindow = null;
+  private transient javax.swing.Timer m_previewTimer = null;
+
+  protected Vector m_subFlow = new Vector();
+  protected Vector m_inputs = new Vector();
+  protected Vector m_outputs = new Vector();
+
+  // the internal connections for the grouping
+  protected Vector m_associatedConnections = new Vector();
+  
+  // Holds a preview image of the encapsulated sub-flow
+  protected ImageIcon m_subFlowPreview = null;
+
+  public MetaBean() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  public void setAssociatedConnections(Vector ac) {
+    m_associatedConnections = ac;
+  }
+
+  public Vector getAssociatedConnections() {
+    return m_associatedConnections;
+  }
+
+  public void setSubFlow(Vector sub) {
+    m_subFlow = sub;
+  }
+
+  public Vector getSubFlow() {
+    return m_subFlow;
+  }
+
+  public void setInputs(Vector inputs) {
+    m_inputs = inputs;
+  }
+
+  public Vector getInputs() {
+    return m_inputs;
+  }
+
+  public void setOutputs(Vector outputs) {
+    m_outputs = outputs;
+  }
+
+  public Vector getOutputs() {
+    return m_outputs;
+  }
+
+  private Vector getBeans(Vector beans, int type) {
+    Vector comps = new Vector();
+    for (int i = 0; i < beans.size(); i++) {
+      BeanInstance temp = (BeanInstance)beans.elementAt(i);
+      // need to check for sub MetaBean!
+      if (temp.getBean() instanceof MetaBean) {
+        switch (type) {
+        case 0 : 
+          comps.addAll(((MetaBean)temp.getBean()).getBeansInSubFlow());
+          break;
+        case 1 : 
+          comps.addAll(((MetaBean)temp.getBean()).getBeansInInputs());
+          break;
+        case 2:
+          comps.addAll(((MetaBean)temp.getBean()).getBeansInOutputs());
+          break;
+        }
+      } else {
+        comps.add(temp);
+      }
+    }
+    return comps;
+  }
+  
+  private boolean beanSetContains(Vector set, BeanInstance toCheck) {
+    boolean ok = false;
+    
+    for (int i = 0; i < set.size(); i++) {
+      BeanInstance temp = (BeanInstance)set.elementAt(i);
+      if (toCheck == temp) {
+        ok = true;
+        break;
+      }
+    }
+    return ok;
+  }
+  
+  public boolean subFlowContains(BeanInstance toCheck) {
+    return beanSetContains(m_subFlow, toCheck);
+  }
+  
+  public boolean inputsContains(BeanInstance toCheck) {
+    return beanSetContains(m_inputs, toCheck);
+  }
+  
+  public boolean outputsContains(BeanInstance toCheck) {
+    return beanSetContains(m_outputs, toCheck);
+  }
+
+  /**
+   * Return all the beans in the sub flow
+   *
+   * @return a Vector of all the beans in the sub flow
+   */
+  public Vector getBeansInSubFlow() {
+    return getBeans(m_subFlow, 0);
+  }
+
+  /**
+   * Return all the beans in the inputs
+   *
+   * @return a Vector of all the beans in the inputs
+   */
+  public Vector getBeansInInputs() {
+    return getBeans(m_inputs, 1);
+  }
+
+  /**
+   * Return all the beans in the outputs
+   *
+   * @return a Vector of all the beans in the outputs
+   */
+  public Vector getBeansInOutputs() {
+    return getBeans(m_outputs, 2);
+  }
+
+  private Vector getBeanInfos(Vector beans, int type) {
+    Vector infos = new Vector();
+    for (int i = 0; i < beans.size(); i++) {
+      BeanInstance temp = (BeanInstance)beans.elementAt(i);
+      if (temp.getBean() instanceof MetaBean) {
+        switch (type) {
+        case 0: 
+          infos.addAll(((MetaBean)temp.getBean()).getBeanInfoSubFlow());
+          break;
+        case 1: 
+          infos.addAll(((MetaBean)temp.getBean()).getBeanInfoInputs());
+          break;
+        case 2:
+          infos.addAll(((MetaBean)temp.getBean()).getBeanInfoOutputs());
+        }
+      } else {
+        try {
+          infos.add(Introspector.getBeanInfo(temp.getBean().getClass()));
+        } catch (IntrospectionException ex) {
+          ex.printStackTrace();
+        }
+      }
+    }
+    return infos;
+  }
+
+  public Vector getBeanInfoSubFlow() {
+    return getBeanInfos(m_subFlow, 0);
+  }
+
+  public Vector getBeanInfoInputs() {
+    return getBeanInfos(m_inputs, 1);
+  }
+
+  public Vector getBeanInfoOutputs() {
+    return getBeanInfos(m_outputs, 2);
+  }
+
+  // stores the original position of the beans 
+  // when this group is created. Used
+  // to restore their locations if the group is ungrouped.
+  private Vector m_originalCoords;
+
+  /**
+   * returns the vector containing the original coordinates (instances of class
+   * Point) for the inputs
+   * @return the containing the coord Points of the original inputs
+   */
+  public Vector getOriginalCoords() {
+    return m_originalCoords;
+  }
+  
+  /**
+   * sets the vector containing the original coordinates (instances of class
+   * Point) for the inputs
+   * @param value the vector containing the points of the coords of the original inputs
+   */
+  public void setOriginalCoords(Vector value) {
+    m_originalCoords = value;
+  }
+
+  /**
+   * Move coords of all inputs and outputs of this meta bean
+   * to the coords of the supplied BeanInstance. Typically
+   * the supplied BeanInstance is the BeanInstance that encapsulates
+   * this meta bean; the result in this case is that all inputs
+   * and outputs are shifted so that their coords coincide with
+   * the meta bean and all connections to them appear (visually) to
+   * go to/from the meta bean.
+   *
+   * @param toShiftTo the BeanInstance whos coordinates will
+   * be used.
+   * @param save true if coordinates are to be saved.
+   */
+  public void shiftBeans(BeanInstance toShiftTo, 
+                         boolean save) {
+    if (save) {
+      m_originalCoords = new Vector();
+    }
+    int targetX = toShiftTo.getX();
+    int targetY = toShiftTo.getY();
+
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
+      if (save) {
+        Point p = new Point(temp.getX(), temp.getY());
+        m_originalCoords.add(p);
+      }
+      temp.setX(targetX); temp.setY(targetY);
+    }
+  }
+
+  public void restoreBeans() {
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
+      Point p = (Point)m_originalCoords.elementAt(i);
+      JComponent c = (JComponent)temp.getBean();
+      Dimension d = c.getPreferredSize();
+      int dx = (int)(d.getWidth() / 2);
+      int dy = (int)(d.getHeight() / 2);
+      temp.setX((int)p.getX()+dx);
+      temp.setY((int)p.getY()+dy);
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the event described by the
+   * supplied event descriptor could be generated.
+   *
+   * @param esd an <code>EventSetDescriptor</code> value
+   * @return a <code>boolean</code> value
+   */
+  public boolean eventGeneratable(EventSetDescriptor esd) {
+    String eventName = esd.getName();
+    return eventGeneratable(eventName);
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance output = (BeanInstance)m_subFlow.elementAt(i);
+      if (output.getBean() instanceof EventConstraints) {
+        if (((EventConstraints)output.getBean()).eventGeneratable(eventName)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection with respect to the
+   * supplied EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    Vector targets = getSuitableTargets(esd);
+    for (int i = 0; i < targets.size(); i++) {
+      BeanInstance input = (BeanInstance)targets.elementAt(i);
+      if (input.getBean() instanceof BeanCommon) {
+        //if (((BeanCommon)input.getBean()).connectionAllowed(esd.getName())) {
+        if (((BeanCommon)input.getBean()).connectionAllowed(esd)) {
+          return true;
+        }
+      } else {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public boolean connectionAllowed(String eventName) {
+    return false;
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the named event. This is just a dummy
+   * method in this class to satisfy the interface. Specific code
+   * in BeanConnection takes care of this method for MetaBeans
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+  }
+  
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name. This is just a dummy
+   * method in this class to satisfy the interface. Specific code
+   * in BeanConnection takes care of this method for MetaBeans
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+
+  }
+
+  /**
+   * Stop all encapsulated beans
+   */
+  public void stop() {
+    for (int i = 0; i < m_inputs.size(); i++) {
+      Object temp = m_inputs.elementAt(i);
+      if (temp instanceof BeanCommon) {
+        ((BeanCommon)temp).stop();
+      }
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    boolean result = false;
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      Object temp = m_subFlow.elementAt(i);
+      if (temp instanceof BeanCommon) {
+        if (((BeanCommon)temp).isBusy()) {
+          result = true;
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Sets the visual appearance of this wrapper bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Gets the visual appearance of this wrapper bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DiamondPlain.gif",
+		       BeanVisual.ICON_PATH+"DiamondPlain.gif");
+  }
+
+  /**
+   * Return an enumeration of requests that can be made by the user
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector();
+    if (m_subFlowPreview != null) {
+      String text = "Show preview";
+      if (m_previewWindow != null) {
+	text = "$"+text;
+      }
+      newVector.addElement(text);
+    }
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
+      if (temp.getBean() instanceof UserRequestAcceptor) {
+        String prefix = "";
+        if ((temp.getBean() instanceof BeanCommon)) {
+          prefix = ((BeanCommon)temp.getBean()).getCustomName();
+        } else {
+          prefix = temp.getBean().getClass().getName();
+          prefix = prefix.substring(prefix.lastIndexOf('.')+1, prefix.length());
+        }
+        prefix = ""+(i+1)+": ("+prefix+")";
+        Enumeration en = ((UserRequestAcceptor)temp.getBean()).enumerateRequests();
+        while (en.hasMoreElements()) {
+          String req = (String)en.nextElement();
+          if (req.charAt(0) == '$') {
+            prefix = '$'+prefix;
+            req = req.substring(1, req.length());
+          }
+          
+          if (req.charAt(0) == '?') {
+            prefix = '?' + prefix;
+            req = req.substring(1, req.length());
+          }
+          newVector.add(prefix+" "+req);
+        }          
+      } else if (temp.getBean() instanceof Startable) {
+        String prefix = "";
+        if ((temp.getBean() instanceof BeanCommon)) {
+          prefix = ((BeanCommon)temp.getBean()).getCustomName();
+        } else {
+          prefix = temp.getBean().getClass().getName();
+          prefix = prefix.substring(prefix.lastIndexOf('.')+1, prefix.length());
+        }
+        prefix = ""+(i+1)+": ("+prefix+")";
+        String startMessage = ((Startable)temp.getBean()).getStartMessage();
+        if (startMessage.charAt(0) == '$') {
+          prefix = '$'+prefix;
+          startMessage = startMessage.substring(1, startMessage.length());
+        }
+        newVector.add(prefix + " " + startMessage);
+      }
+    }
+    
+    return newVector.elements();
+  }
+  
+  public void setSubFlowPreview(ImageIcon sfp) {
+    m_subFlowPreview = sfp;
+  }
+  
+  private void showPreview() {
+    if (m_previewWindow == null) {
+      
+      JLabel jl = new JLabel(m_subFlowPreview);
+      //Dimension d = jl.getPreferredSize();
+      jl.setLocation(0,0);
+      m_previewWindow = new JWindow();
+      //popup.getContentPane().setLayout(null);
+      m_previewWindow.getContentPane().add(jl);
+      m_previewWindow.validate();
+      m_previewWindow.setSize(m_subFlowPreview.getIconWidth(), m_subFlowPreview.getIconHeight());
+      
+      m_previewWindow.addMouseListener(new MouseAdapter() {
+	  public void mouseClicked(MouseEvent e) {
+	    m_previewWindow.dispose();
+	    m_previewWindow = null;
+	  }
+	});
+      
+      m_previewWindow.setLocation(
+	  getParent().getLocationOnScreen().x + getX() + getWidth() / 2 - 
+	  m_subFlowPreview.getIconWidth() / 2, 
+	  getParent().getLocationOnScreen().y + getY() + getHeight() / 2 - 
+	  m_subFlowPreview.getIconHeight() / 2);
+      //popup.pack();
+      m_previewWindow.setVisible(true);
+      m_previewTimer = 
+	new javax.swing.Timer(8000, new java.awt.event.ActionListener() {
+	  public void actionPerformed(java.awt.event.ActionEvent e) {
+	    if (m_previewWindow != null) {
+	      m_previewWindow.dispose();
+	      m_previewWindow = null;
+	      m_previewTimer = null;
+	    }
+	  }
+	});
+      m_previewTimer.setRepeats(false);
+      m_previewTimer.start();
+    }
+  }
+
+  /**
+   * Perform a particular request
+   *
+   * @param request the request to perform
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show preview") == 0) {
+      showPreview();
+      return;
+    }
+    // first grab the index if any
+    if (request.indexOf(":") < 0) {
+      return;
+    }
+    String tempI = request.substring(0, request.indexOf(':'));
+    int index = Integer.parseInt(tempI);
+    index--;
+    String req = request.substring(request.indexOf(')')+1, 
+                                   request.length()).trim();
+    
+    Object target = (((BeanInstance)m_subFlow.elementAt(index)).getBean());
+    if (target instanceof Startable && req.equals(((Startable)target).getStartMessage())) {
+      try {
+        ((Startable)target).start();
+      } catch (Exception ex) {
+        if (m_log != null) {
+          String compName = (target instanceof BeanCommon) ? ((BeanCommon)target).getCustomName() : "";
+          m_log.logMessage("Problem starting subcomponent " + compName);
+        }
+      }
+    } else {    
+      ((UserRequestAcceptor)target).performRequest(req);
+    }                                   
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+
+  public void removePropertyChangeListenersSubFlow(PropertyChangeListener pcl) {
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)(temp.getBean())).getVisual().
+          removePropertyChangeListener(pcl);
+      }
+      if (temp.getBean() instanceof MetaBean) {
+        ((MetaBean)temp.getBean()).removePropertyChangeListenersSubFlow(pcl);
+      }
+    }
+  }
+
+  public void addPropertyChangeListenersSubFlow(PropertyChangeListener pcl) {
+    for (int i = 0; i < m_subFlow.size(); i++) {
+      BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
+      if (temp.getBean() instanceof Visible) {
+        ((Visible)(temp.getBean())).getVisual().
+          addPropertyChangeListener(pcl);
+      }
+      if (temp.getBean() instanceof MetaBean) {
+        ((MetaBean)temp.getBean()).addPropertyChangeListenersSubFlow(pcl);
+      }
+    }
+  }
+
+  /**
+   * Checks to see if any of the inputs to this group implements
+   * the supplied listener class
+   *
+   * @param listenerClass the listener to check for
+   */
+  public boolean canAcceptConnection(Class listenerClass) {
+    for (int i = 0; i < m_inputs.size(); i++) {
+      BeanInstance input = (BeanInstance)m_inputs.elementAt(i);
+      if (listenerClass.isInstance(input.getBean())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Return a list of input beans capable of receiving the 
+   * supplied event
+   *
+   * @param esd the event in question
+   * @return a vector of beans capable of handling the event
+   */
+  public Vector getSuitableTargets(EventSetDescriptor esd) {
+    Class listenerClass = esd.getListenerType(); // class of the listener
+    Vector targets = new Vector();
+    for (int i = 0; i < m_inputs.size(); i++) {
+      BeanInstance input = (BeanInstance)m_inputs.elementAt(i);
+      if (listenerClass.isInstance(input.getBean())) {
+        targets.add(input);
+      }
+    }
+    return targets;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChart.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChart.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChart.java	(revision 29)
@@ -0,0 +1,364 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelPerformanceChart.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+import weka.gui.visualize.PlotData2D;
+import weka.gui.visualize.VisualizePanel;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * Bean that can be used for displaying threshold curves (e.g. ROC
+ * curves) and scheme error plots
+ *
+ * @author Mark Hall
+ * @version $Revision: 4762 $
+ */
+public class ModelPerformanceChart
+  extends JPanel
+  implements ThresholdDataListener, VisualizableErrorListener, 
+             Visible, UserRequestAcceptor,
+	     Serializable, BeanContextChild {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4602034200071195924L;
+  
+  protected BeanVisual m_visual;
+
+  protected transient PlotData2D m_masterPlot;
+  
+  protected transient JFrame m_popupFrame;
+
+  protected boolean m_framePoppedUp = false;
+
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+
+  private transient VisualizePanel m_visPanel;
+  
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+
+  public ModelPerformanceChart() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+    /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Visualize performance charts (such as ROC).";
+  }
+
+  protected void appearanceDesign() {
+    removeAll();
+    m_visual = new BeanVisual("ModelPerformanceChart", 
+			      BeanVisual.ICON_PATH+"ModelPerformanceChart.gif",
+			      BeanVisual.ICON_PATH
+			      +"ModelPerformanceChart_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    setUpFinal();
+  }
+
+  protected void setUpFinal() {
+    if (m_visPanel == null) {
+      m_visPanel = new VisualizePanel();
+    }
+    add(m_visPanel, BorderLayout.CENTER);
+  }
+
+  /**
+   * Display a threshold curve.
+   *
+   * @param e a ThresholdDataEvent
+   */
+  public synchronized void acceptDataSet(ThresholdDataEvent e) {
+    if (m_visPanel == null) {
+      m_visPanel = new VisualizePanel();
+    }
+    if (m_masterPlot == null) {
+      m_masterPlot = e.getDataSet();
+    }
+    try {
+    // check for compatable data sets
+      if (!m_masterPlot.getPlotInstances().relationName().
+	  equals(e.getDataSet().getPlotInstances().relationName())) {
+	
+	// if not equal then remove all plots and set as new master plot
+	m_masterPlot = e.getDataSet();
+	m_visPanel.setMasterPlot(m_masterPlot);
+	m_visPanel.validate(); m_visPanel.repaint();
+      } else {
+	// add as new plot
+	m_visPanel.addPlot(e.getDataSet());
+	m_visPanel.validate(); m_visPanel.repaint();
+      }
+      m_visPanel.setXIndex(4); m_visPanel.setYIndex(5);
+    } catch (Exception ex) {
+      System.err.println("Problem setting up visualization (ModelPerformanceChart)");
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Display a scheme error plot.
+   *
+   * @param e a VisualizableErrorEvent
+   */
+  public synchronized void acceptDataSet(VisualizableErrorEvent e) {
+    if (m_visPanel == null) {
+      m_visPanel = new VisualizePanel();
+    }
+    if (m_masterPlot == null) {
+      m_masterPlot = e.getDataSet();
+    }
+    try {
+      m_visPanel.setMasterPlot(m_masterPlot);
+    } catch (Exception ex) {
+      System.err.println("Problem setting up visualization (ModelPerformanceChart)");
+      ex.printStackTrace();
+    }
+    m_visPanel.validate();
+    m_visPanel.repaint();
+  }
+
+  /**
+   * Set the visual appearance of this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Return the visual appearance of this bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
+		       BeanVisual.ICON_PATH+"DefaultDataVisualizer_animated.gif");
+  }
+
+  /**
+   * Describe <code>enumerateRequests</code> method here.
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_masterPlot != null) {
+      newVector.addElement("Show chart");
+      newVector.addElement("?Clear all plots");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Add a property change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(String name,
+					PropertyChangeListener pcl) {
+    m_bcSupport.addPropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Remove a property change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(String name,
+					   PropertyChangeListener pcl) {
+    m_bcSupport.removePropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Add a vetoable change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void addVetoableChangeListener(String name,
+				       VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+  
+  /**
+   * Remove a vetoable change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void removeVetoableChangeListener(String name,
+					   VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      java.awt.GraphicsEnvironment ge = 
+        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+      if (!ge.isHeadless()) {
+        appearanceFinal();
+      }
+    }
+  }
+
+  /**
+   * Return the bean context (if any) that this bean is embedded in
+   *
+   * @return a <code>BeanContext</code> value
+   */
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  /**
+   * Describe <code>performRequest</code> method here.
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show chart") == 0) {
+      try {
+	// popup visualize panel
+	if (!m_framePoppedUp) {
+	  m_framePoppedUp = true;
+
+	  final javax.swing.JFrame jf = 
+	    new javax.swing.JFrame("Model Performance Chart");
+	  jf.setSize(800,600);
+	  jf.getContentPane().setLayout(new BorderLayout());
+	  jf.getContentPane().add(m_visPanel, BorderLayout.CENTER);
+	  jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	      public void windowClosing(java.awt.event.WindowEvent e) {
+		jf.dispose();
+		m_framePoppedUp = false;
+	      }
+	    });
+	  jf.setVisible(true);
+	  m_popupFrame = jf;
+	} else {
+	  m_popupFrame.toFront();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	m_framePoppedUp = false;
+      }
+    } else if (request.equals("Clear all plots")) {
+        m_visPanel.removeAllPlots();
+        m_visPanel.validate(); m_visPanel.repaint();
+        m_visPanel = null;
+        m_masterPlot = null;
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (Model Performance Chart)");
+    }
+  }
+  
+  public static void main(String [] args) {
+    try {
+      if (args.length != 1) {
+	System.err.println("Usage: ModelPerformanceChart <dataset>");
+	System.exit(1);
+      }
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      Instances inst = new Instances(r);
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      final ModelPerformanceChart as = new ModelPerformanceChart();
+      PlotData2D pd = new PlotData2D(inst);
+      pd.setPlotName(inst.relationName());
+      ThresholdDataEvent roc = new ThresholdDataEvent(as, pd);
+      as.acceptDataSet(roc);      
+
+      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChartBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChartBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ModelPerformanceChartBeanInfo.java	(revision 29)
@@ -0,0 +1,46 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelPerformanceChartBeanInfo.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the model performance chart
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.2 $
+ */
+public class ModelPerformanceChartBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppender.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppender.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppender.java	(revision 29)
@@ -0,0 +1,970 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PredictionAppender.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.clusterers.DensityBasedClusterer;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * Bean that can can accept batch or incremental classifier events
+ * and produce dataset or instance events which contain instances with
+ * predictions appended.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5987 $
+ */
+public class PredictionAppender
+  extends JPanel
+  implements DataSource, TrainingSetProducer, TestSetProducer, Visible, BeanCommon,
+	     EventConstraints, BatchClassifierListener,
+	     IncrementalClassifierListener, BatchClustererListener, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2987740065058976673L;
+
+  /**
+   * Objects listenening for dataset events
+   */
+  protected Vector m_dataSourceListeners = new Vector();
+
+  /**
+   * Objects listening for instances events
+   */
+  protected Vector m_instanceListeners = new Vector();
+  
+  /**
+   * Objects listening for training set events
+   */
+  protected Vector m_trainingSetListeners = new Vector();;
+  
+  /**
+   * Objects listening for test set events
+   */
+  protected Vector m_testSetListeners = new Vector();
+
+  /**
+   * Non null if this object is a target for any events.
+   */
+  protected Object m_listenee = null;
+
+  /**
+   * Format of instances to be produced.
+   */
+  protected Instances m_format;
+
+  protected BeanVisual m_visual = 
+    new BeanVisual("PredictionAppender", 
+		   BeanVisual.ICON_PATH+"PredictionAppender.gif",
+		   BeanVisual.ICON_PATH+"PredictionAppender_animated.gif");
+
+  /**
+   * Append classifier's predicted probabilities (if the class is discrete
+   * and the classifier is a distribution classifier)
+   */
+  protected boolean m_appendProbabilities;
+
+  protected transient weka.gui.Logger m_logger;
+
+  /**
+   * Global description of this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Accepts batch or incremental classifier events and "
+      +"produces a new data set with classifier predictions appended.";
+  }
+
+  /**
+   * Creates a new <code>PredictionAppender</code> instance.
+   */
+  public PredictionAppender() {
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Return a tip text suitable for displaying in a GUI
+   *
+   * @return a <code>String</code> value
+   */
+  public String appendPredictedProbabilitiesTipText() {
+    return "append probabilities rather than labels for discrete class "
+      +"predictions";
+  }
+
+  /**
+   * Return true if predicted probabilities are to be appended rather
+   * than class value
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean getAppendPredictedProbabilities() {
+    return m_appendProbabilities;
+  }
+
+  /**
+   * Set whether to append predicted probabilities rather than
+   * class value (for discrete class data sets)
+   *
+   * @param ap a <code>boolean</code> value
+   */
+  public void setAppendPredictedProbabilities(boolean ap) {
+    m_appendProbabilities = ap;
+  }
+
+  /**
+   * Add a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public void addTrainingSetListener(TrainingSetListener tsl) {
+    // TODO Auto-generated method stub
+    m_trainingSetListeners.addElement(tsl);
+    // pass on any format that we might have determined so far
+    if (m_format != null) {
+      TrainingSetEvent e = new TrainingSetEvent(this, m_format);
+      tsl.acceptTrainingSet(e);
+    }
+  }
+
+  /**
+   * Remove a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  public void removeTrainingSetListener(TrainingSetListener tsl) {   
+    m_trainingSetListeners.removeElement(tsl);
+  }
+
+  /**
+   * Add a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public void addTestSetListener(TestSetListener tsl) {
+    m_testSetListeners.addElement(tsl);
+//  pass on any format that we might have determined so far
+    if (m_format != null) {
+      TestSetEvent e = new TestSetEvent(this, m_format);
+      tsl.acceptTestSet(e);
+    }
+  }
+
+  /**
+   * Remove a test set listener
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  public void removeTestSetListener(TestSetListener tsl) {
+    m_testSetListeners.removeElement(tsl);
+  }
+  
+  /**
+   * Add a datasource listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void addDataSourceListener(DataSourceListener dsl) {
+    m_dataSourceListeners.addElement(dsl);
+    // pass on any format that we might have determined so far
+    if (m_format != null) {
+      DataSetEvent e = new DataSetEvent(this, m_format);
+      dsl.acceptDataSet(e);
+    }
+  }
+  
+  /**
+   * Remove a datasource listener
+   *
+   * @param dsl a <code>DataSourceListener</code> value
+   */
+  public synchronized void removeDataSourceListener(DataSourceListener dsl) {
+    m_dataSourceListeners.remove(dsl);
+  }
+
+  /**
+   * Add an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void addInstanceListener(InstanceListener dsl) {
+    m_instanceListeners.addElement(dsl);
+    // pass on any format that we might have determined so far
+    if (m_format != null) {
+      InstanceEvent e = new InstanceEvent(this, m_format);
+      dsl.acceptInstance(e);
+    }
+  }
+  
+  /**
+   * Remove an instance listener
+   *
+   * @param dsl a <code>InstanceListener</code> value
+   */
+  public synchronized void removeInstanceListener(InstanceListener dsl) {
+    m_instanceListeners.remove(dsl);
+  }
+
+  /**
+   * Set the visual for this data source
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual being used by this data source.
+   *
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default images for a data source
+   *
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"PredictionAppender.gif",
+		       BeanVisual.ICON_PATH+"PredictionAppender_animated.gif");
+  }
+
+  protected InstanceEvent m_instanceEvent;
+
+  
+  /**
+   * Accept and process an incremental classifier event
+   *
+   * @param e an <code>IncrementalClassifierEvent</code> value
+   */
+  public void acceptClassifier(IncrementalClassifierEvent e) {
+    weka.classifiers.Classifier classifier = e.getClassifier();
+    Instance currentI = e.getCurrentInstance();
+    int status = e.getStatus();
+    int oldNumAtts = 0;
+    if (status == IncrementalClassifierEvent.NEW_BATCH) {
+      oldNumAtts = e.getStructure().numAttributes();
+    } else {
+      oldNumAtts = currentI.dataset().numAttributes();
+    }
+    if (status == IncrementalClassifierEvent.NEW_BATCH) {
+      m_instanceEvent = new InstanceEvent(this, null, 0);
+      // create new header structure
+      Instances oldStructure = new Instances(e.getStructure(), 0);
+      //String relationNameModifier = oldStructure.relationName()
+	//+"_with predictions";
+      String relationNameModifier = "_with predictions";
+	//+"_with predictions";
+       if (!m_appendProbabilities 
+	   || oldStructure.classAttribute().isNumeric()) {
+	 try {
+	   m_format = makeDataSetClass(oldStructure, oldStructure, classifier,
+						     relationNameModifier);
+	 } catch (Exception ex) {
+	   ex.printStackTrace();
+	   return;
+	 }
+       } else if (m_appendProbabilities) {
+	 try {
+	   m_format = 
+	     makeDataSetProbabilities(oldStructure, oldStructure, classifier,
+				      relationNameModifier);
+
+	 } catch (Exception ex) {
+	   ex.printStackTrace();
+	   return;
+	 }
+       }
+       // Pass on the structure
+       m_instanceEvent.setStructure(m_format);
+       notifyInstanceAvailable(m_instanceEvent);
+       return;
+    }
+
+    double[] instanceVals = new double [m_format.numAttributes()];
+    Instance newInst = null;
+    try {
+      // process the actual instance
+      for (int i = 0; i < oldNumAtts; i++) {
+	instanceVals[i] = currentI.value(i);
+      }
+      if (!m_appendProbabilities 
+	  || currentI.dataset().classAttribute().isNumeric()) {
+	double predClass = 
+	  classifier.classifyInstance(currentI);
+	instanceVals[instanceVals.length - 1] = predClass;
+      } else if (m_appendProbabilities) {
+	double [] preds = classifier.distributionForInstance(currentI);
+	for (int i = oldNumAtts; i < instanceVals.length; i++) {
+	  instanceVals[i] = preds[i-oldNumAtts];
+	}      
+      }      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      return;
+    } finally {
+      newInst = new DenseInstance(currentI.weight(), instanceVals);
+      newInst.setDataset(m_format);
+      m_instanceEvent.setInstance(newInst);
+      m_instanceEvent.setStatus(status);
+      // notify listeners
+      notifyInstanceAvailable(m_instanceEvent);
+    }
+
+    if (status == IncrementalClassifierEvent.BATCH_FINISHED) {
+      // clean up
+      //      m_incrementalStructure = null;
+      m_instanceEvent = null;
+    }
+  }
+
+  /**
+   * Accept and process a batch classifier event
+   *
+   * @param e a <code>BatchClassifierEvent</code> value
+   */
+  public void acceptClassifier(BatchClassifierEvent e) {
+    if (m_dataSourceListeners.size() > 0 
+	|| m_trainingSetListeners.size() > 0
+	|| m_testSetListeners.size() > 0) {
+
+      if (e.getTestSet() == null) {
+        // can't append predictions
+        return;
+      }
+
+      Instances testSet = e.getTestSet().getDataSet();
+      Instances trainSet = e.getTrainSet().getDataSet();
+      int setNum = e.getSetNumber();
+      int maxNum = e.getMaxSetNumber();
+
+      weka.classifiers.Classifier classifier = e.getClassifier();
+      String relationNameModifier = "_set_"+e.getSetNumber()+"_of_"
+	+e.getMaxSetNumber();
+      if (!m_appendProbabilities || testSet.classAttribute().isNumeric()) {
+	try {
+	  Instances newTestSetInstances = makeDataSetClass(testSet, trainSet, 
+	      classifier, relationNameModifier);
+	  Instances newTrainingSetInstances = makeDataSetClass(trainSet, trainSet, 
+	      classifier, relationNameModifier);
+	  
+	  if (m_trainingSetListeners.size() > 0) {
+	    TrainingSetEvent tse = new TrainingSetEvent(this,
+		new Instances(newTrainingSetInstances, 0));
+	    tse.m_setNumber = setNum;
+	    tse.m_maxSetNumber = maxNum;
+	    notifyTrainingSetAvailable(tse);
+	    // fill in predicted values
+            for (int i = 0; i < trainSet.numInstances(); i++) {
+              double predClass = 
+        	classifier.classifyInstance(trainSet.instance(i));
+              newTrainingSetInstances.instance(i).setValue(newTrainingSetInstances.numAttributes()-1,
+        	  predClass);
+            }
+            tse = new TrainingSetEvent(this,
+        	newTrainingSetInstances);
+            tse.m_setNumber = setNum;
+            tse.m_maxSetNumber = maxNum;
+            notifyTrainingSetAvailable(tse);
+	  }
+	  
+	  if (m_testSetListeners.size() > 0) {
+	    TestSetEvent tse = new TestSetEvent(this,
+		new Instances(newTestSetInstances, 0));
+	    tse.m_setNumber = setNum;
+	    tse.m_maxSetNumber = maxNum;
+	    notifyTestSetAvailable(tse);
+	  }
+	  if (m_dataSourceListeners.size() > 0) {
+	    notifyDataSetAvailable(new DataSetEvent(this, new Instances(newTestSetInstances,0)));
+	  }
+          if (e.getTestSet().isStructureOnly()) {
+	    m_format = newTestSetInstances;
+	  }
+          if (m_dataSourceListeners.size() > 0 || m_testSetListeners.size() > 0) {
+            // fill in predicted values
+            for (int i = 0; i < testSet.numInstances(); i++) {
+              Instance tempInst = testSet.instance(i);
+              
+              // if the class value is missing, then copy the instance
+              // and set the data set to the training data. This is
+              // just in case this test data was loaded from a CSV file
+              // with all missing values for a nominal class (in this
+              // case we have no information on the legal class values
+              // in the test data)
+              if (tempInst.isMissing(tempInst.classIndex())) {
+                tempInst = (Instance)testSet.instance(i).copy();
+                tempInst.setDataset(trainSet);
+              }
+              double predClass = 
+        	classifier.classifyInstance(tempInst);
+              newTestSetInstances.instance(i).setValue(newTestSetInstances.numAttributes()-1,
+        	  predClass);
+            }
+          }
+	  // notify listeners
+          if (m_testSetListeners.size() > 0) {
+            TestSetEvent tse = new TestSetEvent(this, newTestSetInstances);
+            tse.m_setNumber = setNum;
+            tse.m_maxSetNumber = maxNum;
+            notifyTestSetAvailable(tse);
+          }
+          if (m_dataSourceListeners.size() > 0) {
+            notifyDataSetAvailable(new DataSetEvent(this, newTestSetInstances));            
+          }
+	  return;
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+      if (m_appendProbabilities) {
+	try {
+	  Instances newTestSetInstances = 
+	    makeDataSetProbabilities(testSet, trainSet,
+				     classifier,relationNameModifier);
+	  Instances newTrainingSetInstances = 
+	    makeDataSetProbabilities(trainSet, trainSet,
+				     classifier,relationNameModifier);
+	  if (m_trainingSetListeners.size() > 0) {
+	    TrainingSetEvent tse = new TrainingSetEvent(this,
+		new Instances(newTrainingSetInstances, 0));
+	    tse.m_setNumber = setNum;
+	    tse.m_maxSetNumber = maxNum;
+	    notifyTrainingSetAvailable(tse);
+//	    fill in predicted probabilities
+	    for (int i = 0; i < trainSet.numInstances(); i++) {
+	      double [] preds = classifier.
+	      distributionForInstance(trainSet.instance(i));
+	      for (int j = 0; j < trainSet.classAttribute().numValues(); j++) {
+		newTrainingSetInstances.instance(i).setValue(trainSet.numAttributes()+j,
+		    preds[j]);
+	      }
+	    }
+	    tse = new TrainingSetEvent(this,
+        	newTrainingSetInstances);
+            tse.m_setNumber = setNum;
+            tse.m_maxSetNumber = maxNum;
+            notifyTrainingSetAvailable(tse);
+	  }
+	  if (m_testSetListeners.size() > 0) {
+	    TestSetEvent tse = new TestSetEvent(this,
+		new Instances(newTestSetInstances, 0));
+	    tse.m_setNumber = setNum;
+	    tse.m_maxSetNumber = maxNum;
+	    notifyTestSetAvailable(tse);
+	  }
+	  if (m_dataSourceListeners.size() > 0) {
+	    notifyDataSetAvailable(new DataSetEvent(this, new Instances(newTestSetInstances,0)));
+	  }
+          if (e.getTestSet().isStructureOnly()) {
+	    m_format = newTestSetInstances;
+	  }
+          if (m_dataSourceListeners.size() > 0 || m_testSetListeners.size() > 0) {
+            // fill in predicted probabilities
+            for (int i = 0; i < testSet.numInstances(); i++) {
+              Instance tempInst = testSet.instance(i);
+              
+              // if the class value is missing, then copy the instance
+              // and set the data set to the training data. This is
+              // just in case this test data was loaded from a CSV file
+              // with all missing values for a nominal class (in this
+              // case we have no information on the legal class values
+              // in the test data)
+              if (tempInst.isMissing(tempInst.classIndex())) {
+                tempInst = (Instance)testSet.instance(i).copy();
+                tempInst.setDataset(trainSet);
+              }
+              
+              double [] preds = classifier.
+              distributionForInstance(tempInst);
+              for (int j = 0; j < tempInst.classAttribute().numValues(); j++) {
+        	newTestSetInstances.instance(i).setValue(testSet.numAttributes()+j,
+        	    preds[j]);
+              }
+            }
+          }
+          
+          // notify listeners
+          if (m_testSetListeners.size() > 0) {
+            TestSetEvent tse = new TestSetEvent(this, newTestSetInstances);
+            tse.m_setNumber = setNum;
+            tse.m_maxSetNumber = maxNum;
+            notifyTestSetAvailable(tse);
+          }
+          if (m_dataSourceListeners.size() > 0) {
+            notifyDataSetAvailable(new DataSetEvent(this, newTestSetInstances));
+          }
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+    }
+  }
+  
+  
+  /**
+   * Accept and process a batch clusterer event
+   *
+   * @param e a <code>BatchClassifierEvent</code> value
+   */
+  public void acceptClusterer(BatchClustererEvent e) {
+    if (m_dataSourceListeners.size() > 0
+        || m_trainingSetListeners.size() > 0
+	|| m_testSetListeners.size() > 0) {
+
+      if(e.getTestSet().isStructureOnly()) {
+        return;
+      }
+      Instances testSet = e.getTestSet().getDataSet();
+
+      weka.clusterers.Clusterer clusterer = e.getClusterer();
+      String test;
+      if(e.getTestOrTrain() == 0) {
+        test = "test";
+      } else {
+        test = "training";
+      }
+      String relationNameModifier = "_"+test+"_"+e.getSetNumber()+"_of_"
+	+e.getMaxSetNumber();
+      if (!m_appendProbabilities || !(clusterer instanceof DensityBasedClusterer)) {
+	if(m_appendProbabilities && !(clusterer instanceof DensityBasedClusterer)){
+          System.err.println("Only density based clusterers can append probabilities. Instead cluster will be assigned for each instance.");
+          if (m_logger != null) {
+            m_logger.logMessage("[PredictionAppender] "
+                + statusMessagePrefix() + " Only density based clusterers can "
+                +"append probabilities. Instead cluster will be assigned for each "
+                +"instance.");
+            m_logger.statusMessage(statusMessagePrefix()
+                +"WARNING: Only density based clusterers can append probabilities. "
+                +"Instead cluster will be assigned for each instance.");
+          }
+        }
+        try {
+	  Instances newInstances = makeClusterDataSetClass(testSet, clusterer,
+                                                           relationNameModifier);
+
+          // data source listeners get both train and test sets
+          if (m_dataSourceListeners.size() > 0) {
+            notifyDataSetAvailable(new DataSetEvent(this, new Instances(newInstances,0)));
+          }
+
+          if (m_trainingSetListeners.size() > 0 && e.getTestOrTrain() > 0) {
+             TrainingSetEvent tse = 
+               new TrainingSetEvent(this, new Instances(newInstances, 0));
+             tse.m_setNumber = e.getSetNumber();
+             tse.m_maxSetNumber = e.getMaxSetNumber();
+	    notifyTrainingSetAvailable(tse);
+          }
+
+          if (m_testSetListeners.size() > 0 && e.getTestOrTrain() == 0) {
+             TestSetEvent tse = 
+               new TestSetEvent(this, new Instances(newInstances, 0));
+             tse.m_setNumber = e.getSetNumber();
+             tse.m_maxSetNumber = e.getMaxSetNumber();
+	    notifyTestSetAvailable(tse);
+          }
+          
+	  // fill in predicted values
+	  for (int i = 0; i < testSet.numInstances(); i++) {
+	    double predCluster = 
+	      clusterer.clusterInstance(testSet.instance(i));
+	    newInstances.instance(i).setValue(newInstances.numAttributes()-1,
+					      predCluster);
+	  }
+	  // notify listeners
+          if (m_dataSourceListeners.size() > 0) {
+            notifyDataSetAvailable(new DataSetEvent(this, newInstances));
+          }
+          if (m_trainingSetListeners.size() > 0 && e.getTestOrTrain() > 0) {
+             TrainingSetEvent tse = 
+               new TrainingSetEvent(this, newInstances);
+             tse.m_setNumber = e.getSetNumber();
+             tse.m_maxSetNumber = e.getMaxSetNumber();
+	    notifyTrainingSetAvailable(tse);
+          }
+          if (m_testSetListeners.size() > 0 && e.getTestOrTrain() == 0) {
+             TestSetEvent tse = 
+               new TestSetEvent(this, newInstances);
+             tse.m_setNumber = e.getSetNumber();
+             tse.m_maxSetNumber = e.getMaxSetNumber();
+	    notifyTestSetAvailable(tse);
+          }
+
+	  return;
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+      else{
+	try {
+	  Instances newInstances = 
+	    makeClusterDataSetProbabilities(testSet,
+                                            clusterer,relationNameModifier);
+	  notifyDataSetAvailable(new DataSetEvent(this, new Instances(newInstances,0)));
+          
+	  // fill in predicted probabilities
+	  for (int i = 0; i < testSet.numInstances(); i++) {
+	    double [] probs = clusterer.
+	      distributionForInstance(testSet.instance(i));
+	    for (int j = 0; j < clusterer.numberOfClusters(); j++) {
+	      newInstances.instance(i).setValue(testSet.numAttributes()+j,
+						probs[j]);
+	    }
+	  }
+	  // notify listeners
+	  notifyDataSetAvailable(new DataSetEvent(this, newInstances));
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+    }
+  }
+
+  private Instances 
+    makeDataSetProbabilities(Instances insts, Instances format,
+			     weka.classifiers.Classifier classifier,
+			     String relationNameModifier) 
+  throws Exception {
+    String classifierName = classifier.getClass().getName();
+    classifierName = classifierName.
+      substring(classifierName.lastIndexOf('.')+1, classifierName.length());
+    int numOrigAtts = insts.numAttributes();
+    Instances newInstances = new Instances(insts);
+    for (int i = 0; i < format.classAttribute().numValues(); i++) {
+      weka.filters.unsupervised.attribute.Add addF = new
+	weka.filters.unsupervised.attribute.Add();
+      addF.setAttributeIndex("last");
+      addF.setAttributeName(classifierName+"_prob_"+format.classAttribute().value(i));
+      addF.setInputFormat(newInstances);
+      newInstances = weka.filters.Filter.useFilter(newInstances, addF);
+    }
+    newInstances.setRelationName(insts.relationName()+relationNameModifier);
+    return newInstances;
+  }
+
+  private Instances makeDataSetClass(Instances insts, Instances structure,
+				     weka.classifiers.Classifier classifier,
+				     String relationNameModifier) 
+  throws Exception {
+    
+    weka.filters.unsupervised.attribute.Add addF = new
+      weka.filters.unsupervised.attribute.Add();
+    addF.setAttributeIndex("last");
+    String classifierName = classifier.getClass().getName();
+    classifierName = classifierName.
+      substring(classifierName.lastIndexOf('.')+1, classifierName.length());
+    addF.setAttributeName("class_predicted_by: "+classifierName);
+    if (structure.classAttribute().isNominal()) {
+      String classLabels = "";
+      Enumeration enu = structure.classAttribute().enumerateValues();
+      classLabels += (String)enu.nextElement();
+      while (enu.hasMoreElements()) {
+	classLabels += ","+(String)enu.nextElement();
+      }
+      addF.setNominalLabels(classLabels);
+    }
+    addF.setInputFormat(insts);
+
+
+    Instances newInstances = 
+      weka.filters.Filter.useFilter(insts, addF);
+    newInstances.setRelationName(insts.relationName()+relationNameModifier);
+    return newInstances;
+  }
+  
+  private Instances 
+    makeClusterDataSetProbabilities(Instances format,
+			     weka.clusterers.Clusterer clusterer,
+			     String relationNameModifier) 
+  throws Exception {
+    int numOrigAtts = format.numAttributes();
+    Instances newInstances = new Instances(format);
+    for (int i = 0; i < clusterer.numberOfClusters(); i++) {
+      weka.filters.unsupervised.attribute.Add addF = new
+	weka.filters.unsupervised.attribute.Add();
+      addF.setAttributeIndex("last");
+      addF.setAttributeName("prob_cluster"+i);
+      addF.setInputFormat(newInstances);
+      newInstances = weka.filters.Filter.useFilter(newInstances, addF);
+    }
+    newInstances.setRelationName(format.relationName()+relationNameModifier);
+    return newInstances;
+  }
+
+  private Instances makeClusterDataSetClass(Instances format,
+				     weka.clusterers.Clusterer clusterer,
+				     String relationNameModifier) 
+  throws Exception {
+    
+    weka.filters.unsupervised.attribute.Add addF = new
+      weka.filters.unsupervised.attribute.Add();
+    addF.setAttributeIndex("last");
+    String clustererName = clusterer.getClass().getName();
+    clustererName = clustererName.
+      substring(clustererName.lastIndexOf('.')+1, clustererName.length());
+    addF.setAttributeName("assigned_cluster: "+clustererName);
+    //if (format.classAttribute().isNominal()) {
+    String clusterLabels = "0";
+      /*Enumeration enu = format.classAttribute().enumerateValues();
+      clusterLabels += (String)enu.nextElement();
+      while (enu.hasMoreElements()) {
+	clusterLabels += ","+(String)enu.nextElement();
+      }*/
+    for(int i = 1; i <= clusterer.numberOfClusters()-1; i++)
+        clusterLabels += ","+i;
+    addF.setNominalLabels(clusterLabels);
+    //}
+    addF.setInputFormat(format);
+
+
+    Instances newInstances = 
+      weka.filters.Filter.useFilter(format, addF);
+    newInstances.setRelationName(format.relationName()+relationNameModifier);
+    return newInstances;
+  }
+
+  /**
+   * Notify all instance listeners that an instance is available
+   *
+   * @param e an <code>InstanceEvent</code> value
+   */
+  protected void notifyInstanceAvailable(InstanceEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_instanceListeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((InstanceListener)l.elementAt(i)).acceptInstance(e);
+      }
+    }
+  }
+
+  /**
+   * Notify all Data source listeners that a data set is available
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  protected void notifyDataSetAvailable(DataSetEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_dataSourceListeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((DataSourceListener)l.elementAt(i)).acceptDataSet(e);
+      }
+    }
+  }
+  
+  /**
+   * Notify all test set listeners that a test set is available
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  protected void notifyTestSetAvailable(TestSetEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_testSetListeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TestSetListener)l.elementAt(i)).acceptTestSet(e);
+      }
+    }
+  }
+  
+  /**
+   * Notify all test set listeners that a test set is available
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  protected void notifyTrainingSetAvailable(TrainingSetEvent e) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_trainingSetListeners.clone();
+    }
+    
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(e);
+      }
+    }
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name
+   *
+   * @param eventName the event name
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+      m_format = null; // assume any calculated instance format if now invalid
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (eventName.equals("instance")) {
+	if (!((EventConstraints)m_listenee).
+	    eventGeneratable("incrementalClassifier")) {
+	  return false;
+	}
+      }
+      if (eventName.equals("dataSet") 
+	  || eventName.equals("trainingSet") 
+	  || eventName.equals("testSet")) {
+	if (((EventConstraints)m_listenee).
+	    eventGeneratable("batchClassifier")) {
+	  return true;
+	}
+	if (((EventConstraints)m_listenee).eventGeneratable("batchClusterer")) {
+	  return true;
+	}
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderBeanInfo.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PredictionAppenderBeanInfo.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for PredictionAppender.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see SimpleBeanInfo
+ */
+public class PredictionAppenderBeanInfo extends SimpleBeanInfo {
+
+  /**
+   * Get the event set descriptors pertinent to data sources
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds =  
+      { new EventSetDescriptor(PredictionAppender.class, 
+	  "dataSet",
+	  DataSourceListener.class,
+      "acceptDataSet"),
+      new EventSetDescriptor(PredictionAppender.class, 
+	  "instance",
+	  InstanceListener.class,
+      "acceptInstance"),
+      new EventSetDescriptor(PredictionAppender.class, 
+	  "trainingSet",
+	  TrainingSetListener.class,
+      "acceptTrainingSet"),
+      new EventSetDescriptor(PredictionAppender.class, 
+	  "testSet",
+	  TestSetListener.class,
+      "acceptTestSet")
+      };
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Return the property descriptors for this bean
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      p1 = new PropertyDescriptor("appendPredictedProbabilities", 
+				  PredictionAppender.class);
+      PropertyDescriptor [] pds = { p1 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Return the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.PredictionAppender.class,
+			      PredictionAppenderCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/PredictionAppenderCustomizer.java	(revision 29)
@@ -0,0 +1,89 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PredictionAppenderCustomizer.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JPanel;
+
+/**
+ * GUI Customizer for the prediction appender bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+
+public class PredictionAppenderCustomizer
+  extends JPanel
+  implements Customizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6884933202506331888L;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+  
+  private PropertySheetPanel m_paEditor = 
+    new PropertySheetPanel();
+
+  public PredictionAppenderCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+
+    setLayout(new BorderLayout());
+    add(m_paEditor, BorderLayout.CENTER);
+    add(new javax.swing.JLabel("PredcitionAppenderCustomizer"), 
+	BorderLayout.NORTH);
+  }
+
+  /**
+   * Set the object to be edited
+   *
+   * @param object a PredictionAppender object
+   */
+  public void setObject(Object object) {
+    m_paEditor.setTarget((PredictionAppender)object);
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/README_KnowledgeFlow
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/README_KnowledgeFlow	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/README_KnowledgeFlow	(revision 29)
@@ -0,0 +1,201 @@
+===============================================================
+KnowledgeFlow GUI Quick Primer
+===============================================================
+
+What's new in the KnowledgeFlow:
+
+Components can now be grouped together under a "meta" component. Start
+by placing some components on the layout and connecting them
+together. Then select a subset of components with the mouse by holding
+down the left button and dragging the resulting rectangle. You will
+then be asked whether you wish to group the selected components and
+for a name to give the group. The selected components will then be
+replaced by a single icon on the layout. All grouped beans can still
+be configured and connections made by right-clicking on the icon to
+display a pop-up menu. At the moment meta components can't form part
+of another group (this functionality will be added in a later
+release). Eventually, functionality will be added to allow the user
+to store custom groups in a user toolbar for reuse.
+
+Introduction:
+
+The KnowledgeFlow provides an alternative to the Explorer as a
+graphical front end to Weka's core algorithms. The KnowledgeFlow is a
+work in progress so some of the functionality from the Explorer is not
+yet available. On the other hand, there are things that can be done in
+the KnowledgeFlow but not in the Explorer.
+
+The KnowledgeFlow presents a "data-flow" inspired interface to
+Weka. The user can select Weka components from a tool bar, place them
+on a layout canvas and connect them together in order to form a
+"knowledge flow" for processing and analyzing data. At present, all of
+Weka's classifiers, filters, clusterers, loaders and savers are
+available in the KnowledgeFlow along with some extra tools.
+
+The KnowledgeFlow can handle data either incrementally or in batches
+(the Explorer handles batch data only). Of course learning from data
+incrementally requires a classifier that can be updated on an instance
+by instance basis. Currently in Weka there are five classifiers that
+can handle data incrementally: NaiveBayesUpdateable, IB1, IBk, LWR
+(locally weighted regression). There is also one meta classifier -
+RacedIncrementalLogitBoost - that can use of any regression base
+learner to learn from discrete class data incrementally.
+
+Features of the KnowledgeFlow:
+
+* intuitive data flow style layout
+* process data in batches or incrementally 
+* process multiple batches or streams in parallel! (each separate flow 
+  executes in its own thread)
+* chain filters together
+* view models produced by classifiers for each fold in a cross validation
+* visualize performance of incremental classifiers during 
+  processing (scrolling plots of classification accuracy, RMS error, 
+  predictions etc)
+
+Components available in the KnowledgeFlow:
+
+DataSources:
+  All of Weka's loaders are available
+
+DataSinks:
+  All of Weka's savers are available
+
+Filters:
+  All of Weka's filters are available
+
+Classifiers:
+  All of Weka's classifiers are available
+
+Clusterers:
+  All of Weka's clusterers are available
+
+Evaluation:
+  TrainingSetMaker - make a data set into a training set
+  TestSetMaker - make a data set into a test set
+  CrossValidationFoldMaker - split any data set, training set or test set
+    into folds
+  TrainTestSplitMaker - split any data set, training set or test set into
+    a training set and a test set
+  ClassAssigner - assign a column to be the class for any data set, training
+    set or test set
+  ClassValuePicker - choose a class value to be considered as the "positive"
+    class. This is useful when generating data for ROC style curves (see
+    below)
+  ClassifierPerformanceEvaluator - evaluate the performance of batch
+    trained/tested classifiers
+  IncrementalClassifierEvaluator - evaluate the performance of incrementally
+    trained classifiers
+  ClustererPerformanceEvaluator - evaluate the performance of batch
+    trained/tested clusterers
+  PredictionAppender - append classifier predictions to a test set. For
+    discrete class problems, can either append predicted class labels or
+    probability distributions
+
+Visualization:
+  DataVisualizer - component that can pop up a panel for visualizing data in
+    a single large 2D scatter plot
+  ScatterPlotMatrix - component that can pop up a panel containing a matrix of
+    small scatter plots (clicking on a small plot pops up a large scatter 
+    plot)
+  AttributeSummarizer - component that can pop up a panel containing a matrix
+    of histogram plots - one for each of the attributes in the input data
+  ModelPerformanceChart - component that can pop up a panel for visualizing
+    threshold (i.e. ROC style) curves.
+  TextViewer - component for showing textual data. Can show data sets, 
+    classification performance statistics etc.
+  GraphViewer - component that can pop up a panel for visualizing tree based
+    models
+  StripChart - component that can pop up a panel that displays a scrolling
+    plot of data (used for viewing the online performance of incremental
+    classifiers)
+
+
+---------------
+
+Launching the KnowledgeFlow:
+
+The Weka GUI Chooser window is used to launch Weka's graphical
+environments. Select the button labeled "KnowledgeFlow" to start the
+KnowledgeFlow. Alternatively, you can launch the KnowledgeFlow from a
+terminal window by typing "java weka.gui.beans.KnowledgeFlow".
+
+At the top of the KnowledgeFlow window is are seven tabs: DataSources,
+DataSinks, Filters, Classifiers, Clusterers, Evaluation and
+Visualization. The names are pretty much self explanatory.
+
+EXAMPLE:
+-----------------
+Setting up a flow to load an arff file (batch mode) and
+perform a cross validation using J48 (Weka's C4.5 implementation).
+
+First start the KnowlegeFlow.
+
+Next click on the DataSources tab and choose "ArffLoader" from the
+toolbar (the mouse pointer will change to a "cross hairs").
+
+Next place the ArffLoader component on the layout area by clicking
+somewhere on the layout (A copy of the ArffLoader icon will appear on
+the layout area).
+
+Next specify an arff file to load by first right clicking the mouse
+over the ArffLoader icon on the layout. A pop-up menu will
+appear. Select "Configure" under "Edit" in the list from this menu and
+browse to the location of your arff file. Alternatively, you can
+double-click on the icon to bring up the configuration dialog (if
+the component in question has one).
+
+Next click the "Evaluation" tab at the top of the window and choose the
+"ClassAssigner" (allows you to choose which column to be the class)
+component from the toolbar. Place this on the layout.
+
+Now connect the ArffLoader to the ClassAssigner: first right click
+over the ArffLoader and select the "dataSet" under "Connections" in
+the menu. A "rubber band" line will appear. Move the mouse over the
+ClassAssigner component and left click - a red line labeled "dataSet"
+will connect the two components.
+
+Next right click over the ClassAssigner and choose "Configure" from
+the menu. This will pop up a window from which you can specify which
+column is the class in your data (last is the default).
+
+Next grab a "CrossValidationFoldMaker" component from the Evaluation
+toolbar and place it on the layout. Connect the ClassAssigner to the
+CrossValidationFoldMaker by right clicking over "ClassAssigner" and
+selecting "dataSet" from under "Connections" in the menu.
+
+Next click on the "Classifiers" tab at the top of the window and
+scroll along the toolbar until you reach the "J48" component in the
+"trees" section. Place a J48 component on the layout.
+
+Connect the CrossValidationFoldMaker to J48 TWICE by first choosing
+"trainingSet" and then "testSet" from the pop-up menu for the
+CrossValidationFoldMaker.
+
+Next go back to the "Evaluation" tab and place a
+"ClassifierPerformanceEvaluator" component on the layout. Connect J48
+to this component by selecting the "batchClassifier" entry from the
+pop-up menu for J48.
+
+Next go to the "Visualization" toolbar and place a "TextViewer"
+component on the layout. Connect the ClassifierPerformanceEvaluator to
+the TextViewer by selecting the "text" entry from the pop-up menu for
+ClassifierPerformanceEvaluator.
+
+Now start the flow executing by selecting "Start loading" from the
+pop-up menu for ArffLoader. Depending on how big the data set is and
+how long cross validation takes you will see some animation from some
+of the icons in the layout (J48's tree will "grow" in the icon and the
+ticks will animate on the ClassifierPerformanceEvaluator). You will
+also see some progress information in the "Status" bar and "Log" at
+the bottom of the window.
+
+When finished you can view the results by choosing show results from
+the pop-up menu for the TextViewer component.
+
+Other cool things to add to this flow: connect a TextViewer and/or a
+GraphViewer to J48 in order to view the textual or graphical
+representations of the trees produced for each fold of the cross
+validation (this is something that is not possible in the Explorer).
+-----------------------------
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Saver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Saver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Saver.java	(revision 29)
@@ -0,0 +1,604 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Saver.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.converters.ArffSaver;
+import weka.core.converters.DatabaseConverter;
+import weka.core.converters.DatabaseSaver;
+
+/**
+ * Saves data sets using weka.core.converter classes
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 5738 $
+ *
+ */
+public class Saver
+  extends AbstractDataSink
+  implements WekaWrapper, EnvironmentHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5371716690308950755L;
+
+  /**
+   * Holds the instances to be saved
+   */
+  private Instances m_dataSet;
+
+  /**
+   * Holds the structure
+   */
+  private Instances m_structure;
+
+  /**
+   * Global info for the wrapped loader (if it exists).
+   */
+  protected String m_globalInfo;
+
+  /**
+   * Thread for doing IO in
+   */
+  private transient SaveBatchThread m_ioThread;
+
+  /**
+   * Saver
+   */
+  private weka.core.converters.Saver m_Saver= new ArffSaver();
+
+  /**
+   * The relation name that becomes part of the file name
+   */
+  private String m_fileName;
+  
+  /** Flag indicating that instances will be saved to database. Used because structure information can only be sent after a database has been configured.*/
+  private boolean m_isDBSaver;
+  
+  /** 
+   * For file-based savers - if true (default), relation name is used
+   * as the primary part of the filename. If false, then the prefix is
+   * used as the filename. Useful for preventing filenames from getting
+   * too long when there are many filters in a flow. 
+   */
+  private boolean m_relationNameForFilename = true;
+ 
+  /**
+   * Count for structure available messages
+   */
+  private int m_count;
+  
+  /**
+   * The environment variables.
+   */
+  protected transient Environment m_env;
+  
+  private class SaveBatchThread extends Thread {
+    private DataSink m_DS;
+
+    public SaveBatchThread(DataSink ds) {
+      m_DS= ds;
+    }
+
+    public void run() {
+      try {
+        m_visual.setAnimated();
+                
+        m_Saver.setInstances(m_dataSet);
+        if (m_logger != null) {
+          m_logger.statusMessage(statusMessagePrefix() + "Saving "
+              + m_dataSet.relationName() + "...");
+        }
+        m_Saver.writeBatch();
+        if (m_logger != null) {
+          m_logger.logMessage("[Saver] " + statusMessagePrefix() 
+              + "Save successful.");
+        }
+	
+      } catch (Exception ex) {
+        if (m_logger != null) {
+          m_logger.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details)");
+          m_logger.logMessage("[Saver] " + statusMessagePrefix()
+              + " problem saving. " 
+              + ex.getMessage());
+        }
+	ex.printStackTrace();
+      } finally {
+        if (Thread.currentThread().isInterrupted()) {
+          if (m_logger != null) {
+            m_logger.logMessage("[Saver] " + statusMessagePrefix()
+                + " Saving interrupted!!");
+          }
+        }
+        if (m_logger != null) {
+          m_logger.statusMessage(statusMessagePrefix() + "Finished.");
+        }
+        block(false);
+	m_visual.setStatic();
+	m_ioThread = null;
+      }
+    }
+  }
+  
+  /**
+   * Function used to stop code that calls acceptTrainingSet. This is 
+   * needed as classifier construction is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+
+    if (tf) {
+      try {
+	if (m_ioThread.isAlive()) {
+	  wait();
+	  }
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_ioThread != null);
+  }
+
+  /**
+   * Global info (if it exists) for the wrapped loader
+   *
+   * @return the global info
+   */
+  public String globalInfo() {
+    return m_globalInfo;
+  }
+
+  /** Contsructor */  
+  public Saver() {
+    super();
+    setSaver(m_Saver);
+    m_fileName = "";
+    m_dataSet = null;
+    m_count = 0;
+    
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }  
+  
+  /**
+   * Set environment variables to use.
+   * 
+   * @param env the environment variables to
+   * use
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+  
+  /**
+   * Pass the environment variables on the the wrapped saver
+   */
+  private void passEnvOnToSaver() {
+    // set environment variables
+    if (m_Saver instanceof EnvironmentHandler && m_env != null) {
+      ((EnvironmentHandler)m_Saver).setEnvironment(m_env);
+    }
+  }
+
+  /** Set the loader to use
+   * @param saver a Saver
+   */
+  public void setSaver(weka.core.converters.Saver saver) {
+    boolean loadImages = true;
+    if (saver.getClass().getName().
+	compareTo(m_Saver.getClass().getName()) == 0) {
+      loadImages = false;
+    }
+    m_Saver = saver;
+    String saverName = saver.getClass().toString();
+    saverName = saverName.substring(saverName.
+				      lastIndexOf('.')+1, 
+				      saverName.length());
+    if (loadImages) {
+
+      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+saverName+".gif",
+			    BeanVisual.ICON_PATH+saverName+"_animated.gif")) {
+	useDefaultVisual();
+      }
+    }
+    m_visual.setText(saverName);
+
+    
+    // get global info
+    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Saver);
+    if(m_Saver instanceof DatabaseConverter)
+        m_isDBSaver = true;
+    else
+        m_isDBSaver = false;
+  }
+
+  /**
+   * makes sure that the filename is valid, i.e., replaces slashes,
+   * backslashes and colons with underscores ("_"). Also try to prevent
+   * filename from becoming insanely long by removing package part
+   * of class names.
+   * 
+   * @param filename	the filename to cleanse
+   * @return		the cleansed filename
+   */
+  protected String sanitizeFilename(String filename) {
+    filename = filename.replaceAll("\\\\", "_").replaceAll(":", "_").replaceAll("/", "_");
+    filename = Utils.removeSubstring(filename, "weka.filters.supervised.instance.");
+    filename = Utils.removeSubstring(filename, "weka.filters.supervised.attribute.");
+    filename = Utils.removeSubstring(filename, "weka.filters.unsupervised.instance.");
+    filename = Utils.removeSubstring(filename, "weka.filters.unsupervised.attribute.");
+    filename = Utils.removeSubstring(filename, "weka.clusterers.");
+    filename = Utils.removeSubstring(filename, "weka.associations.");
+    filename = Utils.removeSubstring(filename, "weka.attributeSelection.");
+    filename = Utils.removeSubstring(filename, "weka.estimators.");
+    filename = Utils.removeSubstring(filename, "weka.datagenerators.");
+    
+    if (!m_isDBSaver && !m_relationNameForFilename) {
+      filename = "";
+      try {
+        if (m_Saver.filePrefix().equals("")) {
+          m_Saver.setFilePrefix("no-name");
+        }
+      } catch (Exception ex) {
+        System.err.println(ex);
+      }
+    }
+
+    return filename;
+  }
+  
+  /** Method reacts to a dataset event and starts the writing process in batch mode
+   * @param e a dataset event
+   */  
+  public synchronized void acceptDataSet(DataSetEvent e) {
+  
+      passEnvOnToSaver();
+      m_fileName = sanitizeFilename(e.getDataSet().relationName());
+      m_dataSet = e.getDataSet();
+      if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_Saver).getRelationForTableName()){//
+          ((DatabaseSaver)m_Saver).setTableName(m_fileName);
+      }
+      if(!e.isStructureOnly()){
+          if(!m_isDBSaver){
+            try{
+                m_Saver.setDirAndPrefix(m_fileName,"");
+            }catch (Exception ex){
+                System.out.println(ex);
+            }
+          }
+          saveBatch();
+          System.out.println("...relation "+ m_fileName +" saved.");
+      }
+  }
+  
+  /**
+   * Method reacts to a threshold data event ans starts the writing process
+   * in batch mode.
+   * 
+   * @param e threshold data event.
+   */
+  public synchronized void acceptDataSet(ThresholdDataEvent e) {
+    passEnvOnToSaver();
+    m_fileName = sanitizeFilename(e.getDataSet().getPlotInstances().relationName());
+    m_dataSet = e.getDataSet().getPlotInstances();
+    
+    if(m_isDBSaver && ((DatabaseSaver)m_Saver).getRelationForTableName()){//
+      ((DatabaseSaver)m_Saver).setTableName(m_fileName);
+    }
+
+    if(!m_isDBSaver){
+      try{
+        m_Saver.setDirAndPrefix(m_fileName,"");
+      }catch (Exception ex){
+        System.out.println(ex);
+      }
+    }
+    saveBatch();
+    System.out.println("...relation "+ m_fileName +" saved.");
+  }
+  
+  /** Method reacts to a test set event and starts the writing process in batch mode
+   * @param e test set event
+   */  
+  public synchronized void acceptTestSet(TestSetEvent e) {
+  
+      passEnvOnToSaver();
+      m_fileName = sanitizeFilename(e.getTestSet().relationName());
+      m_dataSet = e.getTestSet();
+      if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_Saver).getRelationForTableName()){
+          ((DatabaseSaver)m_Saver).setTableName(m_fileName);
+      }
+      if(!e.isStructureOnly()){
+          if(!m_isDBSaver){
+            try{
+                m_Saver.setDirAndPrefix(m_fileName,"_test_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
+            }catch (Exception ex){
+                System.out.println(ex);
+            }
+          }
+          else{
+              String setName = ((DatabaseSaver)m_Saver).getTableName();
+              setName = setName.replaceFirst("_[tT][eE][sS][tT]_[0-9]+_[oO][fF]_[0-9]+","");
+              ((DatabaseSaver)m_Saver).setTableName(setName+"_test_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
+          }
+          saveBatch();
+          System.out.println("... test set "+e.getSetNumber()+" of "+e.getMaxSetNumber()+" for relation "+ m_fileName +" saved.");
+      }
+  }
+  
+  /** Method reacts to a training set event and starts the writing process in batch
+   * mode
+   * @param e a training set event
+   */  
+  public synchronized void acceptTrainingSet(TrainingSetEvent e) {
+  
+      passEnvOnToSaver();
+      m_fileName = sanitizeFilename(e.getTrainingSet().relationName());
+      m_dataSet = e.getTrainingSet();
+      if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_Saver).getRelationForTableName()){
+           ((DatabaseSaver)m_Saver).setTableName(m_fileName);
+      }
+      if(!e.isStructureOnly()){
+          if(!m_isDBSaver){
+            try{
+                m_Saver.setDirAndPrefix(m_fileName,"_training_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
+            }catch (Exception ex){
+                System.out.println(ex);
+            }
+          }
+          else{
+              String setName = ((DatabaseSaver)m_Saver).getTableName();
+              setName = setName.replaceFirst("_[tT][rR][aA][iI][nN][iI][nN][gG]_[0-9]+_[oO][fF]_[0-9]+","");
+              ((DatabaseSaver)m_Saver).setTableName(setName+"_training_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
+          }
+          saveBatch();
+          System.out.println("... training set "+e.getSetNumber()+" of "+e.getMaxSetNumber()+" for relation "+ m_fileName +" saved.");
+      }
+  }
+  
+  /** Saves instances in batch mode */  
+  public synchronized void saveBatch(){
+  
+      m_Saver.setRetrieval(m_Saver.BATCH);
+/*      String visText = this.getName();
+      try {
+        visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
+      } catch (Exception ex) {        
+      }
+      m_visual.setText(visText); */
+      m_ioThread = new SaveBatchThread(Saver.this);
+      m_ioThread.setPriority(Thread.MIN_PRIORITY);
+      m_ioThread.start();
+      block(true);
+  }
+  
+  /** Methods reacts to instance events and saves instances incrementally.
+   * If the instance to save is null, the file is closed and the saving process is
+   * ended.
+   * @param e instance event
+   */  
+  public synchronized void acceptInstance(InstanceEvent e) {
+      
+      
+      if(e.getStatus() == e.FORMAT_AVAILABLE){
+        m_Saver.setRetrieval(m_Saver.INCREMENTAL);
+        m_structure = e.getStructure();
+        m_fileName = sanitizeFilename(m_structure.relationName());
+        m_Saver.setInstances(m_structure);
+        if(m_isDBSaver)
+            if(((DatabaseSaver)m_Saver).getRelationForTableName())
+                ((DatabaseSaver)m_Saver).setTableName(m_fileName);
+      }
+      if(e.getStatus() == e.INSTANCE_AVAILABLE){
+        m_visual.setAnimated();
+        if(m_count == 0){
+            passEnvOnToSaver();
+            if(!m_isDBSaver){
+                try{
+                    m_Saver.setDirAndPrefix(m_fileName,"");
+                }catch (Exception ex){
+                    System.out.println(ex);
+                    m_visual.setStatic();
+                }
+            }
+            m_count ++;
+        }
+        try{
+/*          String visText = this.getName();
+          visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
+            m_visual.setText(m_fileName); */
+            m_Saver.writeIncremental(e.getInstance());
+        } catch (Exception ex) {
+            m_visual.setStatic();
+            System.err.println("Instance "+e.getInstance() +" could not been saved");
+            ex.printStackTrace();
+        }
+      }
+      if(e.getStatus() == e.BATCH_FINISHED){
+        try{  
+            m_Saver.writeIncremental(e.getInstance());
+            m_Saver.writeIncremental(null);
+            //m_firstNotice = true;
+            m_visual.setStatic();
+            System.out.println("...relation "+ m_fileName +" saved.");
+/*            String visText = this.getName();
+            visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
+            m_visual.setText(visText); */     
+            m_count = 0;
+        } catch (Exception ex) {
+            m_visual.setStatic();
+            System.err.println("File could not have been closed.");
+            ex.printStackTrace();
+        }
+      }
+  }
+  
+  
+
+  /**
+   * Get the saver
+   *
+   * @return a <code>weka.core.converters.Saver</code> value
+   */
+  public weka.core.converters.Saver getSaver() {
+    return m_Saver;
+  }
+
+  /**
+   * Set the saver
+   *
+   * @param algorithm a Saver
+   */
+  public void setWrappedAlgorithm(Object algorithm) 
+    {
+
+    if (!(algorithm instanceof weka.core.converters.Saver)) { 
+      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+					 +"type of algorithm (Loader)");
+    }
+    setSaver((weka.core.converters.Saver)algorithm);
+  }
+
+  /**
+   * Get the saver
+   *
+   * @return a Saver
+   */
+  public Object getWrappedAlgorithm() {
+    return getSaver();
+  }
+  
+  /**
+   * Set whether to use the relation name as the primary part
+   * of the filename. If false, then the prefix becomes the filename.
+   * 
+   * @param r true if the relation name is to be part of the filename.
+   */
+  public void setRelationNameForFilename(boolean r) {
+    m_relationNameForFilename = r;
+  }
+  
+  /**
+   * Get whether the relation name is the primary part of the filename.
+   * 
+   * @return true if the relation name is part of the filename.
+   */
+  public boolean getRelationNameForFilename() {
+    return m_relationNameForFilename;
+  }
+
+  /** Stops the bean */  
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      ((BeanCommon)m_listenee).stop();
+    }
+    
+    // stop the io thread
+    if (m_ioThread != null) {
+      m_ioThread.interrupt();
+      m_ioThread.stop();
+      m_ioThread = null;
+      m_visual.setStatic();
+    }
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|"
+    + ((m_Saver instanceof OptionHandler) 
+        ? Utils.joinOptions(((OptionHandler)m_Saver).getOptions()) + "|"
+            : "");
+  }
+  
+  // Custom de-serialization in order to set default
+  // environment variables on de-serialization
+  private void readObject(ObjectInputStream aStream) 
+    throws IOException, ClassNotFoundException {
+    aStream.defaultReadObject();
+    
+    // set a default environment to use
+    m_env = Environment.getSystemWide();
+  }
+  
+  
+  /** The main method for testing
+   * @param args
+   */  
+  public static void main(String [] args) {
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+
+      final Saver tv = new Saver();
+
+      jf.getContentPane().add(tv, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+  
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/SaverBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/SaverBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/SaverBeanInfo.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SaverBeanInfo.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the saver bean
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 1.2 $
+ */
+public class SaverBeanInfo extends AbstractDataSinkBeanInfo {
+  
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.Saver.class,
+			      SaverCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/SaverCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/SaverCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/SaverCustomizer.java	(revision 29)
@@ -0,0 +1,627 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SaverCustomizer.java
+ *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.SwingConstants;
+import javax.swing.filechooser.FileFilter;
+
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.converters.DatabaseConverter;
+import weka.core.converters.DatabaseSaver;
+import weka.core.converters.FileSourcedConverter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+
+/**
+ * GUI Customizer for the saver bean
+ *
+ * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
+ * @version $Revision: 5423 $
+ */
+public class SaverCustomizer
+extends JPanel
+implements Customizer, CustomizerCloseRequester, EnvironmentHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4874208115942078471L;
+
+  static {
+    GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private weka.gui.beans.Saver m_dsSaver;
+
+  private PropertySheetPanel m_SaverEditor = 
+    new PropertySheetPanel();
+
+  private JFileChooser m_fileChooser 
+  = new JFileChooser(new File(System.getProperty("user.dir")));
+
+
+  private JFrame m_parentFrame;
+  
+  private JFrame m_fileChooserFrame;
+
+  private EnvironmentField m_dbaseURLText;
+
+  private EnvironmentField m_userNameText;
+
+  private JPasswordField m_passwordText;
+
+  private EnvironmentField m_tableText;
+
+  private JCheckBox m_idBox;
+
+  private JCheckBox m_tabBox;
+
+  private EnvironmentField m_prefixText;
+
+  private JCheckBox m_relativeFilePath;
+
+  private JCheckBox m_relationNameForFilename;
+
+  private Environment m_env = Environment.getSystemWide();
+  
+  private EnvironmentField m_directoryText;
+
+
+  /** Constructor */  
+  public SaverCustomizer() {
+
+    try {
+      m_SaverEditor.addPropertyChangeListener(
+          new PropertyChangeListener() {
+            public void propertyChange(PropertyChangeEvent e) {
+              repaint();
+              if (m_dsSaver != null) {
+                System.err.println("Property change!!");
+                m_dsSaver.setSaver(m_dsSaver.getSaver());
+              }
+            }
+          });
+      repaint();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    setLayout(new BorderLayout());
+    m_fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+    m_fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+    m_fileChooser.setApproveButtonText("Select directory");
+    m_fileChooser.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
+          try {
+            File selectedFile = m_fileChooser.getSelectedFile();
+            m_directoryText.setText(selectedFile.toString());
+
+            /* (m_dsSaver.getSaver()).setFilePrefix(m_prefixText.getText());
+                (m_dsSaver.getSaver()).setDir(m_fileChooser.getSelectedFile().getPath());
+                m_dsSaver.
+                  setRelationNameForFilename(m_relationNameForFilename.isSelected()); */
+
+          } catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        }
+        // closing
+        if (m_fileChooserFrame != null) {
+          m_fileChooserFrame.dispose();
+        }
+      }
+    });   
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+
+  /** Sets up dialog for saving instances in other data sinks then files
+   * To be extended.
+   */ 
+  private void setUpOther() {
+    removeAll();
+    add(m_SaverEditor, BorderLayout.CENTER);
+    validate();
+    repaint();
+  }
+
+  /** Sets up the dialog for saving to a database*/
+  private void setUpDatabase() {
+
+    removeAll();
+    JPanel db = new JPanel();
+    GridBagLayout gbLayout = new GridBagLayout();
+    db.setLayout(gbLayout);
+    
+    JLabel dbaseURLLab = new JLabel(" Database URL", SwingConstants.RIGHT);
+    dbaseURLLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(dbaseURLLab, gbConstraints);
+    db.add(dbaseURLLab);
+    
+    m_dbaseURLText = new EnvironmentField();
+    m_dbaseURLText.setEnvironment(m_env);
+/*    int width = m_dbaseURLText.getPreferredSize().width;
+    int height = m_dbaseURLText.getPreferredSize().height;
+    m_dbaseURLText.setMinimumSize(new Dimension(width * 2, height));
+    m_dbaseURLText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_dbaseURLText.setText(((DatabaseConverter)m_dsSaver.getSaver()).getUrl());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 1;
+    gbConstraints.weightx = 5;
+    gbLayout.setConstraints(m_dbaseURLText, gbConstraints);
+    db.add(m_dbaseURLText);    
+    
+    JLabel userLab = new JLabel("Username", SwingConstants.RIGHT);
+    userLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(userLab, gbConstraints);
+    db.add(userLab);
+
+    m_userNameText = new EnvironmentField();
+    m_userNameText.setEnvironment(m_env);
+/*    m_userNameText.setMinimumSize(new Dimension(width * 2, height));
+    m_userNameText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_userNameText.setText(((DatabaseConverter)m_dsSaver.getSaver()).getUser());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_userNameText, gbConstraints);
+    db.add(m_userNameText);
+    
+    JLabel passwordLab = new JLabel("Password ", SwingConstants.RIGHT);
+    passwordLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(passwordLab, gbConstraints);
+    db.add(passwordLab);
+
+    m_passwordText = new JPasswordField();
+    JPanel passwordHolder = new JPanel();
+    passwordHolder.setLayout(new BorderLayout());
+    passwordHolder.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    passwordHolder.add(m_passwordText, BorderLayout.CENTER);
+    /*passwordHolder.setMinimumSize(new Dimension(width * 2, height));
+    passwordHolder.setPreferredSize(new Dimension(width * 2, height)); */
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(passwordHolder, gbConstraints);
+    db.add(passwordHolder);
+
+    JLabel tableLab = new JLabel("Table Name", SwingConstants.RIGHT);
+    tableLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(tableLab, gbConstraints);
+    db.add(tableLab);
+    
+    m_tableText = new EnvironmentField();
+    m_tableText.setEnvironment(m_env);
+/*    m_tableText.setMinimumSize(new Dimension(width * 2, height));
+    m_tableText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_tableText.setEnabled(!((DatabaseSaver)m_dsSaver.getSaver()).getRelationForTableName());
+    m_tableText.setText(((DatabaseSaver)m_dsSaver.getSaver()).getTableName());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_tableText, gbConstraints);
+    db.add(m_tableText);
+        
+    JLabel tabLab = new JLabel("Use relation name", SwingConstants.RIGHT);
+    tabLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(tabLab, gbConstraints);
+    db.add(tabLab);
+    
+    m_tabBox = new JCheckBox();
+    m_tabBox.setSelected(((DatabaseSaver)m_dsSaver.getSaver()).getRelationForTableName()); 
+    m_tabBox.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent e){
+        m_tableText.setEnabled(!m_tabBox.isSelected());
+      }
+    });
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_tabBox, gbConstraints);
+    db.add(m_tabBox);
+
+    JLabel idLab = new JLabel("Automatic primary key", SwingConstants.RIGHT);
+    idLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 5; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(idLab, gbConstraints);
+    db.add(idLab);
+    
+    m_idBox = new JCheckBox();
+    m_idBox.setSelected(((DatabaseSaver)m_dsSaver.getSaver()).getAutoKeyGeneration());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 5; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_idBox, gbConstraints);
+    db.add(m_idBox);
+
+    JPanel buttonsP = new JPanel();
+    buttonsP.setLayout(new FlowLayout());
+    JButton ok,cancel;
+    buttonsP.add(ok = new JButton("OK"));
+    buttonsP.add(cancel=new JButton("Cancel"));
+    ok.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        ((DatabaseSaver)m_dsSaver.getSaver()).resetStructure();  
+        ((DatabaseConverter)m_dsSaver.getSaver()).setUrl(m_dbaseURLText.getText());
+        ((DatabaseConverter)m_dsSaver.getSaver()).setUser(m_userNameText.getText());
+        ((DatabaseConverter)m_dsSaver.getSaver()).setPassword(new String(m_passwordText.getPassword()));
+        if(!m_tabBox.isSelected()) {
+          ((DatabaseSaver)m_dsSaver.getSaver()).setTableName(m_tableText.getText());
+        }
+        ((DatabaseSaver)m_dsSaver.getSaver()).setAutoKeyGeneration(m_idBox.isSelected());
+        ((DatabaseSaver)m_dsSaver.getSaver()).setRelationForTableName(m_tabBox.isSelected());
+        if (m_parentFrame != null) {
+          m_parentFrame.dispose();
+        }
+      }
+    });
+    cancel.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        if (m_parentFrame != null) {
+          m_parentFrame.dispose();
+        }
+      }
+    });
+    
+    JPanel holderP = new JPanel();
+    holderP.setLayout(new BorderLayout());
+    holderP.add(db, BorderLayout.NORTH);
+    holderP.add(buttonsP, BorderLayout.SOUTH);
+
+//    db.add(buttonsP);
+    JPanel about = m_SaverEditor.getAboutPanel();
+    if (about != null) {
+      add(about, BorderLayout.NORTH);
+    }
+    add(holderP,BorderLayout.SOUTH);
+  }
+
+  /** Sets up dialog for saving instances in a file */  
+  public void setUpFile() {
+    removeAll();
+    
+    m_fileChooser.setFileFilter(new FileFilter() { 
+      public boolean accept(File f) { 
+        return f.isDirectory();
+      }
+      public String getDescription() {
+        return "Directory";
+      }
+    });
+    
+    m_fileChooser.setAcceptAllFileFilterUsed(false);
+    
+    try{
+      if(!(((m_dsSaver.getSaver()).retrieveDir()).equals(""))) {
+        String dirStr = m_dsSaver.getSaver().retrieveDir();
+        if (Environment.containsEnvVariables(dirStr)) {
+          try {
+            dirStr = m_env.substitute(dirStr);
+          } catch (Exception ex) {
+            // ignore
+          }          
+        }
+        File tmp = new File(dirStr);
+        tmp = new File(tmp.getAbsolutePath());
+        m_fileChooser.setCurrentDirectory(tmp);
+      }
+    } catch(Exception ex) {
+      System.out.println(ex);
+    }
+    
+    JPanel innerPanel = new JPanel();
+    innerPanel.setLayout(new BorderLayout());
+    
+    JPanel alignedP = new JPanel();
+    GridBagLayout gbLayout = new GridBagLayout();
+    alignedP.setLayout(gbLayout);
+    
+    JLabel prefixLab = new JLabel("Prefix for file name", SwingConstants.RIGHT);
+    prefixLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(prefixLab, gbConstraints);
+    alignedP.add(prefixLab);
+    
+    m_prefixText = new EnvironmentField();
+    m_prefixText.setEnvironment(m_env);
+    m_prefixText.setToolTipText("Prefix for file name "
+        + "(or filename itself if relation name is not used)");
+/*    int width = m_prefixText.getPreferredSize().width;
+    int height = m_prefixText.getPreferredSize().height;
+    m_prefixText.setMinimumSize(new Dimension(width * 2, height));
+    m_prefixText.setPreferredSize(new Dimension(width * 2, height)); */
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_prefixText, gbConstraints);
+    alignedP.add(m_prefixText);
+    
+    try{
+//      m_prefixText = new JTextField(m_dsSaver.getSaver().filePrefix(),25);
+
+      m_prefixText.setText(m_dsSaver.getSaver().filePrefix());
+      
+/*      final JLabel prefixLab = 
+        new JLabel(" Prefix for file name:", SwingConstants.LEFT); */
+      
+      JLabel relationLab = new JLabel("Relation name for filename", SwingConstants.RIGHT);
+      relationLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+      gbConstraints = new GridBagConstraints();
+      gbConstraints.anchor = GridBagConstraints.EAST;
+      gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+      gbConstraints.gridy = 1; gbConstraints.gridx = 0;
+      gbLayout.setConstraints(relationLab, gbConstraints);
+      alignedP.add(relationLab);
+      
+      m_relationNameForFilename = new JCheckBox();
+      m_relationNameForFilename.setSelected(m_dsSaver.getRelationNameForFilename());
+      m_relationNameForFilename.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          if (m_relationNameForFilename.isSelected()) {
+            m_prefixText.setLabel("Prefix for file name");
+            m_fileChooser.setApproveButtonText("Select directory and prefix");
+          } else {
+            m_prefixText.setLabel("File name");
+            m_fileChooser.setApproveButtonText("Select directory and filename");
+          }
+        }
+      });
+      
+      gbConstraints = new GridBagConstraints();
+      gbConstraints.anchor = GridBagConstraints.EAST;
+      gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+      gbConstraints.gridy = 1; gbConstraints.gridx = 1;
+      gbConstraints.weightx = 5;
+      gbLayout.setConstraints(m_relationNameForFilename, gbConstraints);
+      alignedP.add(m_relationNameForFilename);
+    } catch(Exception ex){
+    }
+    //innerPanel.add(m_SaverEditor, BorderLayout.SOUTH);
+    JPanel about = m_SaverEditor.getAboutPanel();
+    if (about != null) {
+      innerPanel.add(about, BorderLayout.NORTH);
+    }
+    add(innerPanel, BorderLayout.NORTH);
+//    add(m_fileChooser, BorderLayout.CENTER);
+    
+    JLabel directoryLab = new JLabel("Directory", SwingConstants.RIGHT);
+    directoryLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(directoryLab, gbConstraints);
+    alignedP.add(directoryLab);
+    
+    m_directoryText = new EnvironmentField();
+//    m_directoryText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_directoryText.setEnvironment(m_env);  
+/*    width = m_directoryText.getPreferredSize().width;
+    height = m_directoryText.getPreferredSize().height;
+    m_directoryText.setMinimumSize(new Dimension(width * 2, height));
+    m_directoryText.setPreferredSize(new Dimension(width * 2, height)); */
+    
+    try {
+      m_directoryText.setText(m_dsSaver.getSaver().retrieveDir());
+    } catch (IOException ex) {
+      // ignore
+    }
+    
+    JButton browseBut = new JButton("Browse...");
+    browseBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+          final JFrame jf = new JFrame("Choose directory");
+          jf.getContentPane().setLayout(new BorderLayout());
+          jf.getContentPane().add(m_fileChooser, BorderLayout.CENTER);
+          jf.pack();
+          jf.setVisible(true);
+          m_fileChooserFrame = jf;
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+      }
+    });
+    
+    JPanel efHolder = new JPanel();
+    efHolder.setLayout(new BorderLayout());
+    JPanel bP = new JPanel(); bP.setLayout(new BorderLayout());
+    bP.setBorder(BorderFactory.createEmptyBorder(5,0,5,5));
+    bP.add(browseBut, BorderLayout.CENTER);
+    efHolder.add(m_directoryText, BorderLayout.CENTER);
+    efHolder.add(bP, BorderLayout.EAST);
+    //efHolder.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(efHolder, gbConstraints);
+    alignedP.add(efHolder);
+    
+
+    JLabel relativeLab = new JLabel("Use relative file paths", SwingConstants.RIGHT);
+    relativeLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(relativeLab, gbConstraints);
+    alignedP.add(relativeLab);
+    
+    m_relativeFilePath = new JCheckBox();
+    m_relativeFilePath.
+    setSelected(((FileSourcedConverter)m_dsSaver.getSaver()).getUseRelativePath());
+
+    m_relativeFilePath.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        ((FileSourcedConverter)m_dsSaver.getSaver()).
+        setUseRelativePath(m_relativeFilePath.isSelected());
+      }
+    });
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_relativeFilePath, gbConstraints);
+    alignedP.add(m_relativeFilePath);
+        
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {          
+          (m_dsSaver.getSaver()).setFilePrefix(m_prefixText.getText());
+          (m_dsSaver.getSaver()).setDir(m_directoryText.getText());
+          m_dsSaver.
+            setRelationNameForFilename(m_relationNameForFilename.isSelected());
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+        
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+    
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new FlowLayout());
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+    JPanel holder2 = new JPanel();
+    holder2.setLayout(new BorderLayout());
+    holder2.add(alignedP, BorderLayout.NORTH);
+    holder2.add(butHolder, BorderLayout.SOUTH);
+    
+    add(holder2, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Set the saver to be customized
+   *
+   * @param object a weka.gui.beans.Saver
+   */
+  public void setObject(Object object) {
+    m_dsSaver = (weka.gui.beans.Saver)object;
+    m_SaverEditor.setTarget(m_dsSaver.getSaver());
+    if(m_dsSaver.getSaver() instanceof DatabaseConverter){
+      setUpDatabase();
+    }
+    else{
+      if (m_dsSaver.getSaver() instanceof FileSourcedConverter) {
+        setUpFile();
+      } else {
+        setUpOther();
+      }
+    }
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  /* (non-Javadoc)
+   * @see weka.core.EnvironmentHandler#setEnvironment(weka.core.Environment)
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrix.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrix.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrix.java	(revision 29)
@@ -0,0 +1,174 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ScatterPlotMatrix.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+import weka.gui.visualize.MatrixPanel;
+
+import java.awt.BorderLayout;
+
+/**
+ * Bean that encapsulates weka.gui.visualize.MatrixPanel for displaying a
+ * scatter plot matrix.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.9 $
+ */
+public class ScatterPlotMatrix
+  extends DataVisualizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -657856527563507491L;
+
+  protected MatrixPanel m_matrixPanel;
+
+  public ScatterPlotMatrix() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Visualize incoming data/training/test sets in a scatter "
+      +"plot matrix.";
+  }
+
+  protected void appearanceDesign() {
+    m_matrixPanel = null;
+    removeAll();
+    m_visual = 
+      new BeanVisual("ScatterPlotMatrix", 
+		     BeanVisual.ICON_PATH+"ScatterPlotMatrix.gif",
+		     BeanVisual.ICON_PATH+"ScatterPlotMatrix_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    setUpFinal();
+  }
+
+  protected void setUpFinal() {
+    if (m_matrixPanel == null) {
+      m_matrixPanel = new MatrixPanel();
+    }
+    add(m_matrixPanel, BorderLayout.CENTER);
+  }
+
+  /**
+   * Set instances for this bean. This method is a convenience method
+   * for clients who use this component programatically
+   *
+   * @param inst an <code>Instances</code> value
+   * @exception Exception if an error occurs
+   */
+  public void setInstances(Instances inst) throws Exception {
+    if (m_design) {
+      throw new Exception("This method is not to be used during design "
+			  +"time. It is meant to be used if this "
+			  +"bean is being used programatically as as "
+			  +"stand alone component.");
+    }
+    m_visualizeDataSet = inst;
+    m_matrixPanel.setInstances(m_visualizeDataSet);
+  }
+
+  /**
+   * Perform a named user request
+   *
+   * @param request a string containing the name of the request to perform
+   * @exception IllegalArgumentException if request is not supported
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show plot") == 0) {
+      try {
+	// popup matrix panel
+	if (!m_framePoppedUp) {
+	  m_framePoppedUp = true;
+	  final MatrixPanel vis = new MatrixPanel();
+	  vis.setInstances(m_visualizeDataSet);
+
+	  final javax.swing.JFrame jf = 
+	    new javax.swing.JFrame("Visualize");
+	  jf.setSize(800,600);
+	  jf.getContentPane().setLayout(new BorderLayout());
+	  jf.getContentPane().add(vis, BorderLayout.CENTER);
+	  jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	      public void windowClosing(java.awt.event.WindowEvent e) {
+		jf.dispose();
+		m_framePoppedUp = false;
+	      }
+	    });
+	  jf.setVisible(true);
+	  m_popupFrame = jf;
+	} else {
+	  m_popupFrame.toFront();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	m_framePoppedUp = false;
+      }
+    } else {
+      throw new IllegalArgumentException(request
+					 + " not supported (ScatterPlotMatrix)");
+    }
+  }
+
+  public static void main(String [] args) {
+    try {
+      if (args.length != 1) {
+	System.err.println("Usage: ScatterPlotMatrix <dataset>");
+	System.exit(1);
+      }
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      Instances inst = new Instances(r);
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+      final ScatterPlotMatrix as = new ScatterPlotMatrix();
+      as.setInstances(inst);
+      
+      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrixBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrixBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ScatterPlotMatrixBeanInfo.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ScatterPlotMatrixBeanInfo.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the scatter plot matrix bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class ScatterPlotMatrixBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaver.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaver.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaver.java	(revision 29)
@@ -0,0 +1,748 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedModelSaver.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.io.File;
+import java.io.ObjectOutputStream;
+import java.io.FileOutputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.awt.BorderLayout;
+import java.beans.EventSetDescriptor;
+import java.util.ArrayList;
+import java.util.Vector;
+import javax.swing.JPanel;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instances;
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.xml.KOML;
+import weka.core.xml.XStream;
+import weka.core.Tag;
+import weka.core.Utils;
+
+/**
+ * A bean that saves serialized models
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 5928 $
+ */
+public class SerializedModelSaver
+  extends JPanel
+  implements BeanCommon, Visible, BatchClassifierListener, 
+             IncrementalClassifierListener, BatchClustererListener,
+	     EnvironmentHandler, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3956528599473814287L;
+
+  /**
+   * Default visual for data sources
+   */
+  protected BeanVisual m_visual = 
+    new BeanVisual("AbstractDataSink", 
+		   BeanVisual.ICON_PATH+"SerializedModelSaver.gif",
+		   BeanVisual.ICON_PATH+"SerializedModelSaver_animated.gif");
+
+  /**
+   * Non null if this object is a target for any events.
+   * Provides for the simplest case when only one incomming connection
+   * is allowed.
+   */
+  protected Object m_listenee = null;
+
+  /**
+   * The log for this bean
+   */
+  protected transient weka.gui.Logger m_logger = null;
+
+  /**
+   * The prefix for the file name (model + training set info will be appended)
+   */
+  private String m_filenamePrefix = "";
+
+  /**
+   * The directory to hold the saved model(s)
+   */
+  private File m_directory = new File(System.getProperty("user.dir"));
+
+  /**
+   * File format stuff
+   */
+  private Tag m_fileFormat;
+
+  public final static int BINARY = 0;
+  public final static int KOMLV = 1;
+  public final static int XSTREAM = 2;
+
+  /** the extension for serialized models (binary Java serialization) */
+  public final static String FILE_EXTENSION = "model";
+
+  /** relative path for the directory (relative to the user.dir (startup directory))? */
+  private boolean m_useRelativePath = false;
+  
+  /** include relation name in filename */
+  private boolean m_includeRelationName = false;
+
+  /**
+   * Available file formats. Reflection is used to check if classes
+   * are available for deep object serialization to XML
+   */
+  public static ArrayList<Tag> s_fileFormatsAvailable;
+  static {
+    s_fileFormatsAvailable = new ArrayList<Tag>();
+    s_fileFormatsAvailable.add(new Tag(BINARY, "Binary serialized model file (*"
+                                       + FILE_EXTENSION + ")", "", false));
+    if (KOML.isPresent()) {
+      s_fileFormatsAvailable.add(new Tag(KOMLV,
+                                         "XML serialized model file (*"
+                                         + KOML.FILE_EXTENSION + FILE_EXTENSION + ")", "", false));
+    }
+
+    if (XStream.isPresent()) {
+      s_fileFormatsAvailable.add(new Tag(XSTREAM,
+                                         "XML serialized model file (*"
+                                         + XStream.FILE_EXTENSION + FILE_EXTENSION + ")", "", false));
+    }
+  }
+  
+  /**
+   * The environment variables.
+   */
+  protected transient Environment m_env;
+
+  /**
+   * Constructor.
+   */
+  public SerializedModelSaver() {
+    useDefaultVisual();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+    m_fileFormat = s_fileFormatsAvailable.get(0);
+    
+    m_env = Environment.getSystemWide();
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Use the default images for this bean.
+   *
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"SerializedModelSaver.gif",
+		       BeanVisual.ICON_PATH+"SerializedModelSaver_animated.gif");
+    m_visual.setText("SerializedModelSaver");
+  }
+
+  /**
+   * Set the visual for this data source.
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual being used by this data source.
+   *
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor.
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection according to the supplied
+   * event name.
+   *
+   * @param eventName the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return (m_listenee == null);
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source with respect to the supplied event name.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void connectionNotification(String eventName,
+						  Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source with respect to the supplied event name.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public synchronized void disconnectionNotification(String eventName,
+						     Object source) {
+    if (m_listenee == source) {
+      m_listenee = null;
+    }
+  }
+  
+  /**
+   * Set a log for this bean.
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_logger = logger;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * makes sure that the filename is valid, i.e., replaces slashes,
+   * backslashes and colons with underscores ("_").
+   * 
+   * @param filename	the filename to cleanse
+   * @return		the cleansed filename
+   */
+  protected String sanitizeFilename(String filename) {
+    return filename.replaceAll("\\\\", "_").replaceAll(":", "_").replaceAll("/", "_");
+  }
+
+  /**
+   * Accept and save a batch trained clusterer.
+   *
+   * @param ce a <code>BatchClassifierEvent</code> value
+   */
+  public void acceptClusterer(BatchClustererEvent ce) {
+    if (ce.getTestSet() == null || 
+        ce.getTestOrTrain() == BatchClustererEvent.TEST ||
+        ce.getTestSet().isStructureOnly()) {
+      return;
+    }
+
+    Instances trainHeader = new Instances(ce.getTestSet().getDataSet(), 0);
+    String titleString = ce.getClusterer().getClass().getName();		      
+    titleString = titleString.
+      substring(titleString.lastIndexOf('.') + 1,
+                titleString.length());
+
+    String prefix = "";
+    String relationName = (m_includeRelationName)
+    ? trainHeader.relationName()
+    : "";
+    try {
+      prefix = m_env.substitute(m_filenamePrefix);
+    } catch (Exception ex) {
+      stop(); // stop all processing
+      String message = "[SerializedModelSaver] " 
+        + statusMessagePrefix() 
+        + " Can't save model. Reason: " 
+        + ex.getMessage();
+      if (m_logger != null) {
+        m_logger.logMessage(message);
+        m_logger.statusMessage(statusMessagePrefix()
+            + "ERROR (See log for details)");
+      } else {
+        System.err.println(message);
+      }
+      return;
+    }
+    String fileName = "" 
+      + prefix
+      + relationName
+      + titleString
+      + "_"
+      + ce.getSetNumber() 
+      + "_" + ce.getMaxSetNumber();
+    fileName = sanitizeFilename(fileName);
+    
+    String dirName = m_directory.getPath();
+    try {
+      dirName = m_env.substitute(dirName);
+    } catch (Exception ex) {
+      stop(); // stop all processing
+      String message = "[SerializedModelSaver] "
+        + statusMessagePrefix() + " Can't save model. Reason: " 
+                           + ex.getMessage();
+      if (m_logger != null) {
+        m_logger.logMessage(message);
+        m_logger.statusMessage(statusMessagePrefix()
+            + "ERROR (See log for details)");
+      } else {
+        System.err.println(message);
+      }
+      return;
+    }
+    File tempFile = new File(dirName);
+    fileName = tempFile.getAbsolutePath() 
+      + File.separator
+      + fileName;
+
+    saveModel(fileName, trainHeader, ce.getClusterer());
+  }
+
+  /**
+   * Accept and save an incrementally trained classifier.
+   *
+   * @param ce the BatchClassifierEvent containing the classifier
+   */
+  public void acceptClassifier(final IncrementalClassifierEvent ce) {
+    if (ce.getStatus() == IncrementalClassifierEvent.BATCH_FINISHED) {
+      // Only save model when the end of the stream is reached
+      Instances header = ce.getStructure();
+      String titleString = ce.getClassifier().getClass().getName();		      
+      titleString = titleString.
+        substring(titleString.lastIndexOf('.') + 1,
+                  titleString.length());
+
+      String prefix = "";
+      String relationName = (m_includeRelationName)
+        ? header.relationName()
+        : "";
+        
+      try {
+        prefix = m_env.substitute(m_filenamePrefix);
+      } catch (Exception ex) {
+        stop(); // stop processing
+        String message = "[SerializedModelSaver] "
+          + statusMessagePrefix() + " Can't save model. Reason: " 
+          + ex.getMessage();
+        if (m_logger != null) {
+          m_logger.logMessage(message);
+          m_logger.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details)");
+        } else {
+          System.err.println(message);
+        }
+        return;
+      }
+      
+      String fileName = "" + prefix + relationName + titleString;
+      fileName = sanitizeFilename(fileName);
+
+      String dirName = m_directory.getPath();
+      try {
+        dirName = m_env.substitute(dirName);
+      } catch (Exception ex) {
+        stop(); // stop processing
+        String message = "[SerializedModelSaver] "
+          + statusMessagePrefix() + " Can't save model. Reason: " 
+          + ex.getMessage();
+        if (m_logger != null) {
+          m_logger.logMessage(message);
+          m_logger.statusMessage(statusMessagePrefix()
+              + "ERROR (See log for details)");
+        } else {
+          System.err.println(message);
+        }
+        return;
+      }
+      File tempFile = new File(dirName);
+
+      fileName = tempFile.getAbsolutePath() 
+        + File.separator
+        + fileName;
+      
+      saveModel(fileName, header, ce.getClassifier());
+    }
+  }
+  
+  /**
+   * Accept and save a batch trained classifier.
+   *
+   * @param ce the BatchClassifierEvent containing the classifier
+   */
+  public void acceptClassifier(final BatchClassifierEvent ce) {
+    if (ce.getTrainSet() == null || 
+        ce.getTrainSet().isStructureOnly()) {
+      return;
+    }
+    Instances trainHeader = new Instances(ce.getTrainSet().getDataSet(), 0);
+    String titleString = ce.getClassifier().getClass().getName();		      
+    titleString = titleString.
+      substring(titleString.lastIndexOf('.') + 1,
+                titleString.length());
+
+    String prefix = "";
+    String relationName = (m_includeRelationName)
+    ? trainHeader.relationName()
+    : "";
+    try {
+      prefix = m_env.substitute(m_filenamePrefix);
+    } catch (Exception ex) {
+      stop(); // stop processing
+      String message = "[SerializedModelSaver] "
+        + statusMessagePrefix() + " Can't save model. Reason: " 
+        + ex.getMessage();
+      if (m_logger != null) {
+        m_logger.logMessage(message);
+        m_logger.statusMessage(statusMessagePrefix()
+            + "ERROR (See log for details)");
+      } else {
+        System.err.println(message);
+      }
+      return;
+    }
+
+    String fileName = "" 
+      + prefix
+      + relationName
+      + titleString
+      + "_"
+      + ce.getSetNumber() 
+      + "_" + ce.getMaxSetNumber();
+    fileName = sanitizeFilename(fileName);
+    
+    String dirName = m_directory.getPath();
+    try {
+      dirName = m_env.substitute(dirName);
+    } catch (Exception ex) {
+      stop(); // stop processing
+      String message = "[SerializedModelSaver] "
+        + statusMessagePrefix() + " Can't save model. Reason: " 
+                           + ex.getMessage();
+      if (m_logger != null) {
+        m_logger.logMessage(message);
+        m_logger.statusMessage(statusMessagePrefix()
+            + "ERROR (See log for details)");
+      } else {
+        System.err.println(message);
+      }
+      return;
+    }
+    File tempFile = new File(dirName);
+
+    fileName = tempFile.getAbsolutePath() 
+      + File.separator
+      + fileName;
+
+    saveModel(fileName, trainHeader, ce.getClassifier());
+  }
+
+  /**
+   * Helper routine to actually save the models.
+   */
+  private void saveModel(String fileName, Instances trainHeader, Object model) {
+    m_fileFormat = validateFileFormat(m_fileFormat);
+    if (m_fileFormat == null) {
+      // default to binary if validation fails
+      m_fileFormat = s_fileFormatsAvailable.get(0);
+    }
+    try {
+      switch (m_fileFormat.getID()) {
+      case KOMLV:
+        fileName = fileName + KOML.FILE_EXTENSION + FILE_EXTENSION;
+        saveKOML(new File(fileName), model, trainHeader);
+        break;
+      case XSTREAM:
+        fileName = fileName + XStream.FILE_EXTENSION + FILE_EXTENSION;
+        saveXStream(new File(fileName), model, trainHeader);
+        break;
+      default:
+        fileName = fileName + "." + FILE_EXTENSION;
+        saveBinary(new File(fileName), model, trainHeader);
+        break;
+      }        
+    } catch (Exception ex) {
+      stop(); // stop all processing
+      System.err.println("[SerializedModelSaver] Problem saving model");
+      if (m_logger != null) {
+        m_logger.logMessage("[SerializedModelSaver] "
+            + statusMessagePrefix() + " Problem saving model");
+        m_logger.statusMessage(statusMessagePrefix()
+            + "ERROR (See log for details)");
+      }
+    }
+  }
+
+  /**
+   * Save a model in binary form.
+   *
+   * @param saveTo the file name to save to
+   * @param model the model to save
+   * @param header the header of the data that was used to train the model (optional)
+   */
+  public static void saveBinary(File saveTo, Object model, Instances header) throws IOException {
+    ObjectOutputStream os =
+      new ObjectOutputStream(new BufferedOutputStream(
+                             new FileOutputStream(saveTo)));
+    os.writeObject(model);
+    // now the header
+    if (header != null) {
+      os.writeObject(header);
+    }
+    os.close();
+  }
+
+  /**
+   * Save a model in KOML deep object serialized XML form.
+   *
+   * @param saveTo the file name to save to
+   * @param model the model to save
+   * @param header the header of the data that was used to train the model (optional)
+   */
+  public static void saveKOML(File saveTo, Object model, Instances header) throws Exception {
+    Vector v = new Vector();
+    v.add(model);
+    if (header != null) {
+      v.add(header);
+    }
+    v.trimToSize();
+    KOML.write(saveTo.getAbsolutePath(), v);
+  }
+
+  /**
+   * Save a model in XStream deep object serialized XML form.
+   *
+   * @param saveTo the file name to save to
+   * @param model the model to save
+   * @param header the header of the data that was used to train the model (optional)
+   */
+  public static void saveXStream(File saveTo, Object model, Instances header) throws Exception {
+    Vector v = new Vector();
+    v.add(model);
+    if (header != null) {
+      v.add(header);
+    }
+    v.trimToSize();
+    XStream.write(saveTo.getAbsolutePath(), v);
+  }
+
+  /**
+   * Get the directory that the model(s) will be saved into
+   *
+   * @return the directory to save to
+   */
+  public File getDirectory() {
+    return m_directory;
+  }
+  
+  /**
+   * Set the directory that the model(s) will be saved into.
+   *
+   * @param d the directory to save to
+   */
+  public void setDirectory(File d) {
+    m_directory = d;
+    if (m_useRelativePath) {
+      try {
+        m_directory = Utils.convertToRelativePath(m_directory);
+      } catch (Exception ex) {
+      }
+    }
+  }
+
+  /**
+   * Set whether to use relative paths for the directory.
+   * I.e. relative to the startup (user.dir) directory
+   *
+   * @param rp true if relative paths are to be used
+   */
+  public void setUseRelativePath(boolean rp) {
+    m_useRelativePath = rp;
+  }
+  
+  /**
+   * Get whether to use relative paths for the directory.
+   * I.e. relative to the startup (user.dir) directory
+   *
+   * @return true if relative paths are to be used
+   */
+  public boolean getUseRelativePath() {
+    return m_useRelativePath;
+  }
+  
+  /**
+   * Set whether the relation name of the training data
+   * used to create the model should be included as part
+   * of the filename for the serialized model.
+   * 
+   * @param rn true if the relation name should be included
+   * in the file name
+   */
+  public void setIncludeRelationName(boolean rn) {
+    m_includeRelationName = rn;
+  }
+  
+  /**
+   * Get whether the relation name of the training
+   * data used to create the model is to be included
+   * in the filename of the serialized model.
+   * 
+   * @return true if the relation name is to be included
+   * in the file name
+   */
+  public boolean getIncludeRelationName() {
+    return m_includeRelationName;
+  }
+
+  /**
+   * Get the prefix to prepend to the model file names.
+   *
+   * @return the prefix to prepend
+   */
+  public String getPrefix() {
+    return m_filenamePrefix;
+  }
+
+  /**
+   * Set the prefix to prepend to the model file names.
+   *
+   * @param p the prefix to prepend
+   */
+  public void setPrefix(String p) {
+    m_filenamePrefix = p;
+  }
+
+  /**
+   * Global info for this bean. Gets displayed in the GUI.
+   *
+   * @return information about this bean.
+   */
+  public String globalInfo() {
+    return "Save trained models to serialized object files.";
+  }
+
+  /**
+   * Set the file format to use for saving.
+   *
+   * @param ff the file format to use
+   */
+  public void setFileFormat(Tag ff) {
+    m_fileFormat = ff;
+  }
+
+  /**
+   * Get the file format to use for saving.
+   *
+   * @return the file format to use
+   */
+  public Tag getFileFormat() {
+    return m_fileFormat;
+  }
+
+  /**
+   * Validate the file format. After this bean is deserialized, classes for
+   * XML serialization may not be in the classpath any more.
+   *
+   * @param ff the current file format to validate
+   */
+  public Tag validateFileFormat(Tag ff) {
+    Tag r = ff;
+    if (ff.getID() == BINARY) {
+      return ff;
+    }
+
+    if (ff.getID() == KOMLV && !KOML.isPresent()) {
+      r = null;
+    }
+
+    if (ff.getID() == XSTREAM && !XStream.isPresent()) {
+      r = null;
+    }
+
+    return r;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+  
+  /**
+   * Set environment variables to use.
+   * 
+   * @param env the environment variables to
+   * use
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+  
+  // Custom de-serialization in order to set default
+  // environment variables on de-serialization
+  private void readObject(ObjectInputStream aStream) 
+    throws IOException, ClassNotFoundException {
+    aStream.defaultReadObject();
+    
+    // set a default environment to use
+    m_env = Environment.getSystemWide();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverBeanInfo.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedModelSaverBeanInfo.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the serialized model saver bean
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 5563 $
+ */
+public class SerializedModelSaverBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.SerializedModelSaver.class,
+			      SerializedModelSaverCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/SerializedModelSaverCustomizer.java	(revision 29)
@@ -0,0 +1,446 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerializedModelSaverCustomizer.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertySheetPanel;
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.core.Tag;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.beans.Customizer;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.BorderFactory;
+
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * GUI Customizer for the SerializedModelSaver bean
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 5563 $
+ */
+public class SerializedModelSaverCustomizer
+  extends JPanel
+  implements Customizer, CustomizerCloseRequester, EnvironmentHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4874208115942078471L;
+
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private weka.gui.beans.SerializedModelSaver m_smSaver;
+
+  private PropertySheetPanel m_SaverEditor = 
+    new PropertySheetPanel();
+
+  private JFileChooser m_fileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+  
+
+  private JFrame m_parentFrame;
+  
+  private JFrame m_fileChooserFrame;
+  
+  //private JTextField m_prefixText;
+  private EnvironmentField m_prefixText;
+
+  private JComboBox m_fileFormatBox;
+
+  private JCheckBox m_relativeFilePath;
+  
+  private JCheckBox m_includeRelationName;
+  
+  private Environment m_env = Environment.getSystemWide();
+  
+  private EnvironmentField m_directoryText;
+  
+
+  /** Constructor */  
+  public SerializedModelSaverCustomizer() {
+
+    try {
+      m_SaverEditor.addPropertyChangeListener(
+	  new PropertyChangeListener() {
+	      public void propertyChange(PropertyChangeEvent e) {
+		repaint();
+		if (m_smSaver != null) {
+		  System.err.println("Property change!!");
+                  //		  m_smSaver.setSaver(m_smSaver.getSaver());
+		}
+	      }
+	    });
+      repaint();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    setLayout(new BorderLayout());
+
+    m_fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+    m_fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+    m_fileChooser.setApproveButtonText("Select directory and prefix");
+
+    m_fileChooser.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
+	    try {
+              m_smSaver.setPrefix(m_prefixText.getText());
+//              m_smSaver.setDirectory(m_fileChooser.getSelectedFile());
+              
+              File selectedFile = m_fileChooser.getSelectedFile();
+              m_directoryText.setText(selectedFile.toString());
+              
+	    } catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	  }
+	  // closing
+	  if (m_parentFrame != null) {
+	    m_fileChooserFrame.dispose();
+	  }
+	}
+      });   
+  }
+
+  public void setParentFrame(JFrame parent) {
+    m_parentFrame = parent;
+  }
+  
+  private void setUpOther() {
+    removeAll();
+    add(m_SaverEditor, BorderLayout.CENTER);
+    validate();
+    repaint();
+  }
+  
+  /** Sets up dialog for saving models to a file */  
+  public void setUpFile() {
+    removeAll();
+    m_fileChooser.setFileFilter(new FileFilter() { 
+      public boolean accept(File f) { 
+        return f.isDirectory();
+      }
+      public String getDescription() {
+        return "Directory";
+      }
+    });
+
+    m_fileChooser.setAcceptAllFileFilterUsed(false);
+
+    try{
+      if (!m_smSaver.getDirectory().getPath().equals("")) {
+       // File tmp = m_smSaver.getDirectory();
+        String dirStr = m_smSaver.getDirectory().toString();
+        if (Environment.containsEnvVariables(dirStr)) {
+          try {
+            dirStr = m_env.substitute(dirStr);
+          } catch (Exception ex) {
+            // ignore
+          }
+        }
+        File tmp = new File(dirStr);;
+        tmp = new File (tmp.getAbsolutePath());
+        m_fileChooser.setCurrentDirectory(tmp);
+      }
+    } catch(Exception ex) {
+      System.out.println(ex);
+    }
+
+    JPanel innerPanel = new JPanel();
+    innerPanel.setLayout(new BorderLayout());
+    
+    JPanel alignedP = new JPanel();
+    GridBagLayout gbLayout = new GridBagLayout();
+    alignedP.setLayout(gbLayout);
+    
+    JLabel prefixLab = new JLabel("Prefix for file name", SwingConstants.RIGHT);
+    prefixLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    GridBagConstraints gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(prefixLab, gbConstraints);
+    alignedP.add(prefixLab);
+    
+//    m_prefixText = new JTextField(m_smSaver.getPrefix(), 25);
+    m_prefixText = new EnvironmentField();
+    m_prefixText.setEnvironment(m_env);
+/*    int width = m_prefixText.getPreferredSize().width;
+    int height = m_prefixText.getPreferredSize().height;
+    m_prefixText.setMinimumSize(new Dimension(width * 2, height));
+    m_prefixText.setPreferredSize(new Dimension(width * 2, height)); */
+    m_prefixText.setText(m_smSaver.getPrefix());
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 0; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_prefixText, gbConstraints);
+    alignedP.add(m_prefixText);
+    
+    JLabel ffLab = new JLabel("File format", SwingConstants.RIGHT);
+    ffLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(ffLab, gbConstraints);
+    alignedP.add(ffLab);
+    
+    setUpFileFormatComboBox();
+    m_fileFormatBox.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 1; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_fileFormatBox, gbConstraints);
+    alignedP.add(m_fileFormatBox);
+
+    JPanel about = m_SaverEditor.getAboutPanel();
+    if (about != null) {
+      innerPanel.add(about, BorderLayout.NORTH);
+    }
+    add(innerPanel, BorderLayout.NORTH);
+    
+    JLabel directoryLab = new JLabel("Directory", SwingConstants.RIGHT);
+    directoryLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(directoryLab, gbConstraints);
+    alignedP.add(directoryLab);
+    
+    m_directoryText = new EnvironmentField();
+    m_directoryText.setEnvironment(m_env);  
+/*    width = m_directoryText.getPreferredSize().width;
+    height = m_directoryText.getPreferredSize().height;
+    m_directoryText.setMinimumSize(new Dimension(width * 2, height));
+    m_directoryText.setPreferredSize(new Dimension(width * 2, height)); */
+    
+    m_directoryText.setText(m_smSaver.getDirectory().toString());
+    
+    JButton browseBut = new JButton("Browse...");
+    browseBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+          final JFrame jf = new JFrame("Choose directory");
+          jf.getContentPane().setLayout(new BorderLayout());
+          jf.getContentPane().add(m_fileChooser, BorderLayout.CENTER);
+          jf.pack();
+          jf.setVisible(true);
+          m_fileChooserFrame = jf;
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+      }
+    });
+    
+    JPanel efHolder = new JPanel();
+    efHolder.setLayout(new BorderLayout());
+    JPanel bP = new JPanel(); bP.setLayout(new BorderLayout());
+    bP.setBorder(BorderFactory.createEmptyBorder(5,0,5,5));
+    bP.add(browseBut, BorderLayout.CENTER);
+    efHolder.add(bP, BorderLayout.EAST);
+    efHolder.add(m_directoryText, BorderLayout.CENTER);
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 2; gbConstraints.gridx = 1;
+    gbConstraints.weightx = 5; // make sure that extra horizontal space gets allocated to this column
+    gbLayout.setConstraints(efHolder, gbConstraints);
+    alignedP.add(efHolder);
+
+    
+    JLabel relativeLab = new JLabel("Use relative file paths", SwingConstants.RIGHT);
+    relativeLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(relativeLab, gbConstraints);
+    alignedP.add(relativeLab);
+    
+    m_relativeFilePath = new JCheckBox();
+    m_relativeFilePath.
+      setSelected(m_smSaver.getUseRelativePath());
+
+    m_relativeFilePath.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          m_smSaver.setUseRelativePath(m_relativeFilePath.isSelected());
+        }
+      });
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 3; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_relativeFilePath, gbConstraints);
+    alignedP.add(m_relativeFilePath);
+    
+    
+    JLabel relationLab = new JLabel("Include relation name in file name", SwingConstants.RIGHT);
+    relationLab.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 0;
+    gbLayout.setConstraints(relationLab, gbConstraints);
+    alignedP.add(relationLab);
+    
+    m_includeRelationName = new JCheckBox();
+    m_includeRelationName.setToolTipText("Include the relation name of the training data used "
+        + "to create the model in the file name.");
+    m_includeRelationName.setSelected(m_smSaver.getIncludeRelationName());
+        
+    gbConstraints = new GridBagConstraints();
+    gbConstraints.anchor = GridBagConstraints.EAST;
+    gbConstraints.fill = GridBagConstraints.HORIZONTAL;
+    gbConstraints.gridy = 4; gbConstraints.gridx = 1;
+    gbLayout.setConstraints(m_includeRelationName, gbConstraints);
+    alignedP.add(m_includeRelationName);
+    
+    JButton OKBut = new JButton("OK");
+    OKBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {          
+          m_smSaver.setPrefix(m_prefixText.getText());
+          m_smSaver.setDirectory(new File(m_directoryText.getText()));
+          m_smSaver.
+            setIncludeRelationName(m_includeRelationName.isSelected());
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+        
+        m_parentFrame.dispose();
+      }
+    });
+
+    JButton CancelBut = new JButton("Cancel");
+    CancelBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_parentFrame.dispose();
+      }
+    });
+    
+    JPanel butHolder = new JPanel();
+    butHolder.setLayout(new FlowLayout());
+    butHolder.add(OKBut);
+    butHolder.add(CancelBut);
+  
+    JPanel holderPanel = new JPanel();
+    holderPanel.setLayout(new BorderLayout());
+    holderPanel.add(alignedP, BorderLayout.NORTH);
+    holderPanel.add(butHolder, BorderLayout.SOUTH);
+    add(holderPanel, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Set the model saver to be customized
+   *
+   * @param object a weka.gui.beans.SerializedModelSaver
+   */
+  public void setObject(Object object) {
+    m_smSaver = (weka.gui.beans.SerializedModelSaver)object;
+    m_SaverEditor.setTarget(m_smSaver);
+
+    setUpFile();    
+  }
+
+  private void setUpFileFormatComboBox() {
+    m_fileFormatBox = new JComboBox();
+    for (int i = 0; i < SerializedModelSaver.s_fileFormatsAvailable.size(); i++) {
+      Tag temp = SerializedModelSaver.s_fileFormatsAvailable.get(i);
+      m_fileFormatBox.addItem(temp);
+    }
+
+    Tag result = m_smSaver.validateFileFormat(m_smSaver.getFileFormat());
+    if (result == null) {
+      m_fileFormatBox.setSelectedIndex(0);
+    } else {
+      m_fileFormatBox.setSelectedItem(result);
+    }
+
+    m_fileFormatBox.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          Tag selected = (Tag)m_fileFormatBox.getSelectedItem();
+          if (selected != null) {
+            m_smSaver.setFileFormat(selected);
+          }
+        }
+      });
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+
+  /* (non-Javadoc)
+   * @see weka.core.EnvironmentHandler#setEnvironment(weka.core.Environment)
+   */
+  public void setEnvironment(Environment env) {
+    m_env = env;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/StartUpListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/StartUpListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/StartUpListener.java	(revision 29)
@@ -0,0 +1,36 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StartUpListener.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can be notified of a successful startup
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.2 $
+ */
+public interface StartUpListener extends EventListener {
+
+  void startUpComplete();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Startable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Startable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Startable.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Startable.java
+ *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Interface to something that is a start point for a flow and
+ * can be launched programatically.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5928 $
+ * @since 1.0
+ */
+public interface Startable {
+  
+  /**
+   * Start the flow running
+   *
+   * @exception Exception if something goes wrong
+   */
+  void start() throws Exception;
+  
+  /**
+   * Gets a string that describes the start action. The
+   * KnowledgeFlow uses this in the popup contextual menu
+   * for the component. The string can be proceeded by
+   * a '$' character to indicate that the component can't
+   * be started at present.
+   * 
+   * @return a string describing the start action.
+   */
+  String getStartMessage();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/StripChart.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/StripChart.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/StripChart.java	(revision 29)
@@ -0,0 +1,896 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StripChart.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.gui.visualize.PrintableComponent;
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.beans.EventSetDescriptor;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.border.TitledBorder;
+
+/**
+ * Bean that can display a horizontally scrolling strip chart. Can
+ * display multiple plots simultaneously
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4806 $
+ */
+public class StripChart
+  extends JPanel
+  implements ChartListener, InstanceListener, Visible,
+	     BeanCommon, UserRequestAcceptor {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1483649041577695019L;
+
+  /** default colours for colouring lines */
+  protected Color [] m_colorList = {Color.green,
+				    Color.red,
+				    Color.blue,
+				    Color.cyan,
+				    Color.pink,
+				    new Color(255, 0, 255),
+				    Color.orange,
+				    new Color(255, 0, 0),
+				    new Color(0, 255, 0),
+				    Color.white};
+
+  /** the background color. */
+  protected Color m_BackgroundColor;
+  
+  /** the color of the legend panel's border. */
+  protected Color m_LegendPanelBorderColor;
+  
+  /**
+   * Class providing a panel for the plot.
+   */
+  private class StripPlotter
+    extends JPanel {
+
+    /** for serialization. */
+    private static final long serialVersionUID = -7056271598761675879L;
+
+    public void paintComponent(Graphics g) {
+      super.paintComponent(g);
+      if (m_osi != null) {
+	g.drawImage(m_osi,0,0,this);
+      }
+    }
+  }
+
+  private transient JFrame m_outputFrame = null;
+  private transient StripPlotter m_plotPanel = null;
+
+  /**
+   * The off screen image for rendering to.
+   */
+  private transient Image m_osi = null;
+
+  /**
+   * Width and height of the off screen image.
+   */
+  private int m_iheight;
+  private int m_iwidth;
+
+  /**
+   * Max value for the y axis.
+   */
+  private double m_max = 1;
+
+  /**
+   * Min value for the y axis.
+   */
+  private double m_min = 0;
+
+  /**
+   * Scale update requested.
+   */
+  private boolean m_yScaleUpdate = false;
+  private double m_oldMax;
+  private double m_oldMin;
+
+  private Font m_labelFont = new Font("Monospaced", Font.PLAIN, 10);
+  private FontMetrics m_labelMetrics;
+
+  //  private int m_plotCount = 0;
+
+  private Vector m_legendText = new Vector();
+
+  /**
+   * Class providing a panel for displaying the y axis.
+   */
+  private class ScalePanel 
+    extends JPanel {
+
+    /** for serialization. */
+    private static final long serialVersionUID = 6416998474984829434L;
+
+    public void paintComponent(Graphics gx) {
+      super.paintComponent(gx);
+      if (m_labelMetrics == null) {
+	m_labelMetrics = gx.getFontMetrics(m_labelFont);
+      }
+      gx.setFont(m_labelFont);
+      int hf = m_labelMetrics.getAscent();
+      String temp = ""+m_max;
+      gx.setColor(m_colorList[m_colorList.length-1]);
+      gx.drawString(temp, 1, hf-2);
+      temp = ""+(m_min + ((m_max - m_min)/2.0));
+      gx.drawString(temp, 1, (this.getHeight() / 2)+(hf / 2));
+      temp = ""+m_min;
+      gx.drawString(temp, 1, this.getHeight()-1);
+    }
+  };
+  
+  /** the scale. */
+  private ScalePanel m_scalePanel = new ScalePanel();
+
+  /**
+   * Class providing a panel for the legend.
+   */
+  private class LegendPanel 
+    extends JPanel {
+
+    /** for serialization. */
+    private static final long serialVersionUID = 7713986576833797583L;
+
+    public void paintComponent(Graphics gx) {
+      super.paintComponent(gx);
+
+      if (m_labelMetrics == null) {
+	m_labelMetrics = gx.getFontMetrics(m_labelFont);
+      }
+      int hf = m_labelMetrics.getAscent();
+      int x = 10; int y = hf+15;
+      gx.setFont(m_labelFont);
+      for (int i = 0; i < m_legendText.size(); i++) {
+	String temp = (String)m_legendText.elementAt(i);
+	gx.setColor(m_colorList[(i % m_colorList.length)]);
+	gx.drawString(temp,x,y);
+	y+=hf;
+      }
+      StripChart.this.revalidate();
+    }
+  };
+  
+  /** the legend. */
+  private LegendPanel m_legendPanel = new LegendPanel();
+
+  /**
+   * Holds the data to be plotted. Entries in the list are arrays of
+   * y points.
+   */
+  private LinkedList m_dataList = new LinkedList();
+  //  private double [] m_dataPoint = new double[1];
+  private double [] m_previousY = new double[1];
+
+  private transient Thread m_updateHandler;
+
+  protected BeanVisual m_visual =
+    new BeanVisual("StripChart",
+		   BeanVisual.ICON_PATH+"StripChart.gif",
+		   BeanVisual.ICON_PATH+"StripChart_animated.gif");
+
+  private Object m_listenee = null;
+  private transient weka.gui.Logger m_log = null;
+
+  /**
+   * Print x axis labels every m_xValFreq points
+   */
+  private int m_xValFreq = 500;
+  private int m_xCount = 0;
+
+  /**
+   * Shift the plot by this many pixels every time a point is plotted
+   */
+  private int m_refreshWidth = 1;
+
+  /**
+   * Plot every m_refreshFrequency'th point
+   */
+  private int m_refreshFrequency = 5;
+
+  /** the class responsible for printing */
+  protected PrintableComponent m_Printer = null;
+
+  public StripChart() {
+
+    //    m_plotPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+
+    // start a thread to handle plot updates
+    initPlot();
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   *
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   *
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Visualize incremental classifier performance as a scrolling plot.";
+  }
+
+  /**
+   * GUI Tip text
+   *
+   * @return a <code>String</code> value
+   */
+  public String xLabelFreqTipText() {
+    return "Show x axis labels this often";
+  }
+
+  /**
+   * Set the frequency for printing x label values
+   *
+   * @param freq an <code>int</code> value
+   */
+  public void setXLabelFreq(int freq) {
+    m_xValFreq = freq;
+    if (getGraphics() != null)
+      setRefreshWidth();
+  }
+
+  /**
+   * Get the frequency by which x axis values are printed
+   *
+   * @return an <code>int</code> value
+   */
+  public int getXLabelFreq() {
+    return m_xValFreq;
+  }
+
+  /**
+   * GUI Tip text
+   *
+   * @return a <code>String</code> value
+   */
+  public String refreshFreqTipText() {
+    return "Plot every x'th data point";
+  }
+
+  /**
+   * Set how often (in x axis points) to refresh the display
+   *
+   * @param freq an <code>int</code> value
+   */
+  public void setRefreshFreq(int freq) {
+    m_refreshFrequency = freq;
+    if (getGraphics() != null)
+      setRefreshWidth();
+  }
+
+  /**
+   * Get the refresh frequency
+   *
+   * @return an <code>int</code> value
+   */
+  public int getRefreshFreq() {
+    return m_refreshFrequency;
+  }
+
+  private void setRefreshWidth() {
+    m_refreshWidth = 1;
+    if (m_labelMetrics == null) {
+      getGraphics().setFont(m_labelFont);
+      m_labelMetrics = getGraphics().getFontMetrics(m_labelFont);
+    }
+
+    int refWidth = m_labelMetrics.stringWidth("99000");
+    // compute how often x label will be rendered
+    int z = (getXLabelFreq() / getRefreshFreq());
+    if (z < 1) {
+      z = 1;
+    }
+
+    if (z * m_refreshWidth < refWidth+5) {
+      m_refreshWidth *= (((refWidth+5) / z) + 1) ;
+    }
+  }
+
+  /**
+   * Provide some necessary initialization after object has
+   * been deserialized.
+   *
+   * @param ois an <code>ObjectInputStream</code> value
+   * @exception IOException if an error occurs
+   * @exception ClassNotFoundException if an error occurs
+   */
+  private void readObject(ObjectInputStream ois)
+    throws IOException, ClassNotFoundException {
+    try {
+      ois.defaultReadObject();
+      initPlot();
+      //      startHandler();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Loads properties from properties file.
+   * 
+   * @see	KnowledgeFlowApp#BEAN_PROPERTIES
+   */
+  private void setProperties() {
+    String 	key;
+    String 	color;
+
+    // background color
+    key   = this.getClass().getName() + ".backgroundColour";
+    color = KnowledgeFlowApp.BEAN_PROPERTIES.getProperty(key);
+    m_BackgroundColor = Color.BLACK;
+    if (color != null)
+      m_BackgroundColor = VisualizeUtils.processColour(color, m_BackgroundColor);
+
+    // legend color (border)
+    key   = m_legendPanel.getClass().getName() + ".borderColour";
+    color = KnowledgeFlowApp.BEAN_PROPERTIES.getProperty(key);
+    m_LegendPanelBorderColor = Color.BLUE;
+    if (color != null)
+      m_LegendPanelBorderColor = VisualizeUtils.processColour(color, m_LegendPanelBorderColor);
+  }
+
+  private void initPlot() {
+    setProperties();
+    m_plotPanel = new StripPlotter();
+    m_plotPanel.setBackground(m_BackgroundColor);
+    m_scalePanel.setBackground(m_BackgroundColor);
+    m_legendPanel.setBackground(m_BackgroundColor);
+    m_xCount = 0;
+  }
+
+  private void startHandler() {
+    if (m_updateHandler == null) {
+      m_updateHandler = new Thread() {
+	  private double [] dataPoint;
+	  public void run() {
+	    while (true) {
+	      if (m_outputFrame != null) {
+		synchronized(m_dataList) {
+		  while(m_dataList.isEmpty()) {
+		  //		  while (m_dataList.empty()) {
+		    try {
+		      m_dataList.wait();
+		    } catch (InterruptedException ex) {
+		      return;
+		    }
+		  }
+		  dataPoint = (double [])m_dataList.remove(0);
+		  //dataPoint  = (double [])m_dataList.pop();
+		}
+		if (m_outputFrame != null) {
+		  StripChart.this.updateChart(dataPoint);
+		}
+	      }
+	    }
+	  }
+	};
+      //      m_updateHandler.setPriority(Thread.MIN_PRIORITY);
+      m_updateHandler.start();
+    }
+  }
+
+  /**
+   * Popup the chart panel
+   */
+  public void showChart() {
+    if (m_outputFrame == null) {
+      m_outputFrame = new JFrame("Strip Chart");
+      m_outputFrame.getContentPane().setLayout(new BorderLayout());
+      JPanel panel = new JPanel(new BorderLayout());
+      new PrintableComponent(panel);
+      m_outputFrame.getContentPane().add(panel, BorderLayout.CENTER);
+      panel.add(m_legendPanel, BorderLayout.WEST);
+      panel.add(m_plotPanel, BorderLayout.CENTER);
+      panel.add(m_scalePanel, BorderLayout.EAST);
+      m_legendPanel.setMinimumSize(new Dimension(100,getHeight()));
+      m_legendPanel.setPreferredSize(new Dimension(100,getHeight()));
+      m_scalePanel.setMinimumSize(new Dimension(30, getHeight()));
+      m_scalePanel.setPreferredSize(new Dimension(30, getHeight()));
+      Font lf = new Font("Monospaced", Font.PLAIN, 12);
+      m_legendPanel.setBorder(BorderFactory.
+			      createTitledBorder(BorderFactory.
+				    createEtchedBorder(Color.gray,
+						       Color.darkGray),
+				    "Legend" ,
+				    TitledBorder.CENTER,
+				    TitledBorder.DEFAULT_POSITION, lf,
+				    m_LegendPanelBorderColor));
+      m_outputFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    if (m_updateHandler != null) {
+	      System.err.println("Interrupting");
+	      m_updateHandler.interrupt();
+	      m_updateHandler = null;
+	    }
+	    synchronized (m_dataList) {
+	      m_dataList = new LinkedList();
+	    }
+	    m_outputFrame.dispose();
+	    m_outputFrame = null;
+	  }
+	});
+      m_outputFrame.pack();
+      m_outputFrame.setSize(600,150);
+      m_outputFrame.setResizable(false);
+      m_outputFrame.setVisible(true);
+      int iwidth = m_plotPanel.getWidth();
+      int iheight = m_plotPanel.getHeight();
+      m_osi = m_plotPanel.createImage(iwidth, iheight);
+      Graphics m = m_osi.getGraphics();
+      m.setColor(m_BackgroundColor);
+      m.fillRect(0,0,iwidth,iheight);
+      m_previousY[0] = -1;
+      setRefreshWidth();
+      if (m_updateHandler == null) {
+	System.err.println("Starting handler");
+	startHandler();
+      }
+    } else {
+      m_outputFrame.toFront();
+    }
+  }
+
+  private int convertToPanelY(double yval) {
+    int height = m_plotPanel.getHeight();
+    double temp = (yval - m_min) / (m_max - m_min);
+    temp = temp * height;
+    temp = height - temp;
+    return (int)temp;
+  }
+
+  /**
+   * Update the plot
+   *
+   * @param dataPoint contains y values to plot
+   */
+  protected void updateChart(double [] dataPoint) {
+    if (m_previousY[0] == -1) {
+      int iw = m_plotPanel.getWidth();
+      int ih = m_plotPanel.getHeight();
+      m_osi = m_plotPanel.createImage(iw, ih);
+      Graphics m = m_osi.getGraphics();
+      m.setColor(m_BackgroundColor);
+      m.fillRect(0,0,iw,ih);
+      m_previousY[0] = convertToPanelY(0);
+      m_iheight = ih; m_iwidth = iw;
+    }
+
+    if (dataPoint.length-1 != m_previousY.length) {
+      m_previousY = new double [dataPoint.length-1];
+      //      m_plotCount = 0;
+      for (int i = 0; i < dataPoint.length-1; i++) {
+	m_previousY[i] = convertToPanelY(0);
+      }
+    }
+
+    Graphics osg = m_osi.getGraphics();
+    Graphics g = m_plotPanel.getGraphics();
+
+    osg.copyArea(m_refreshWidth,0,m_iwidth-m_refreshWidth,
+		 m_iheight,-m_refreshWidth,0);
+    osg.setColor(m_BackgroundColor);
+    osg.fillRect(m_iwidth-m_refreshWidth,0, m_iwidth, m_iheight);
+
+    // paint the old scale onto the plot if a scale update has occured
+    if (m_yScaleUpdate) {
+      String maxVal = numToString(m_oldMax);
+      String minVal = numToString(m_oldMin);
+      String midVal = numToString((m_oldMax - m_oldMin) / 2.0);
+      if (m_labelMetrics == null) {
+	m_labelMetrics = g.getFontMetrics(m_labelFont);
+      }
+      osg.setFont(m_labelFont);
+      int wmx = m_labelMetrics.stringWidth(maxVal);
+      int wmn = m_labelMetrics.stringWidth(minVal);
+      int wmd = m_labelMetrics.stringWidth(midVal);
+
+      int hf = m_labelMetrics.getAscent();
+      osg.setColor(m_colorList[m_colorList.length-1]);
+      osg.drawString(maxVal, m_iwidth-wmx, hf-2);
+      osg.drawString(midVal, m_iwidth-wmd, (m_iheight / 2)+(hf / 2));
+      osg.drawString(minVal, m_iwidth-wmn, m_iheight-1);
+      m_yScaleUpdate = false;
+    }
+
+    double pos;
+    for (int i = 0; i < dataPoint.length-1; i++) {
+      osg.setColor(m_colorList[(i % m_colorList.length)]);
+      pos = convertToPanelY(dataPoint[i]);
+      osg.drawLine(m_iwidth-m_refreshWidth, (int)m_previousY[i],
+		   m_iwidth-1, (int)pos);
+      m_previousY[i] = pos;
+      if (dataPoint[dataPoint.length-1] % m_xValFreq == 0) {
+	// draw the actual y value onto the plot for this curve
+	String val = numToString(dataPoint[i]);
+	if (m_labelMetrics == null) {
+	  m_labelMetrics = g.getFontMetrics(m_labelFont);
+	}
+	int hf = m_labelMetrics.getAscent();
+	if (pos - hf < 0) {
+	  pos += hf;
+	}
+	int w = m_labelMetrics.stringWidth(val);
+	osg.setFont(m_labelFont);
+	osg.drawString(val, m_iwidth-w, (int)pos);
+      }
+    }
+
+    // last element in the data point array contains the data point number
+    if (dataPoint[dataPoint.length-1] % m_xValFreq == 0) {
+
+      String xVal = ""+(int)dataPoint[dataPoint.length-1];
+      osg.setColor(m_colorList[m_colorList.length-1]);
+      int w = m_labelMetrics.stringWidth(xVal);
+      osg.setFont(m_labelFont);
+      osg.drawString(xVal, m_iwidth-w, m_iheight - 1);
+    }
+    g.drawImage(m_osi,0,0,m_plotPanel);
+    //    System.err.println("Finished");
+    //    m_plotCount++;
+  }
+
+  private static String numToString(double num) {
+    int precision = 1;
+    int whole = (int)Math.abs(num);
+    double decimal = Math.abs(num) - whole;
+    int nondecimal;
+    nondecimal = (whole > 0)
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+
+    precision = (decimal > 0)
+      ? (int)Math.abs(((Math.log(Math.abs(num)) /
+				      Math.log(10))))+2
+      : 1;
+    if (precision > 5) {
+      precision = 1;
+    }
+
+    String numString = weka.core.Utils.doubleToString(num,
+						      nondecimal+1+precision
+						      ,precision);
+
+    return numString;
+  }
+
+  ChartEvent m_ce = new ChartEvent(this);
+  double [] m_dataPoint = null;
+  public void acceptInstance(InstanceEvent e) {
+    if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
+      Instances structure = e.getStructure();
+      m_legendText = new Vector();
+      m_max = 1.0;
+      m_min = 0;
+      int i = 0;
+      for (i =0; i < structure.numAttributes(); i++) {
+	if (i > 10) {
+	  i--;
+	  break;
+	}
+	m_legendText.addElement(structure.attribute(i).name());
+	m_legendPanel.repaint();
+	m_scalePanel.repaint();
+      }
+      m_dataPoint = new double[i];
+      m_xCount = 0;
+      return;
+    }
+
+    // process data point
+    Instance inst = e.getInstance();
+    for (int i = 0; i < m_dataPoint.length; i++) {
+      if (!inst.isMissing(i)) {
+	m_dataPoint[i] = inst.value(i);
+      }
+    }
+    acceptDataPoint(m_dataPoint);
+    m_xCount++;
+  }
+
+  /**
+   * Accept a data point (encapsulated in a chart event) to plot
+   *
+   * @param e a <code>ChartEvent</code> value
+   */
+  public void acceptDataPoint(ChartEvent e) {
+    if (e.getReset()) {
+      m_xCount = 0;
+      m_max = 1;
+      m_min = 0;
+    }
+    if (m_outputFrame != null) {
+      boolean refresh = false;
+      if (e.getLegendText() != null & e.getLegendText() != m_legendText) {
+	m_legendText = e.getLegendText();
+	refresh = true;
+      }
+
+      if (e.getMin() != m_min || e.getMax() != m_max) {
+	m_oldMax = m_max; m_oldMin = m_min;
+	m_max = e.getMax();
+	m_min = e.getMin();
+	refresh = true;
+	m_yScaleUpdate = true;
+      }
+
+      if (refresh) {
+	m_legendPanel.repaint();
+	m_scalePanel.repaint();
+      }
+
+      acceptDataPoint(e.getDataPoint());
+    }
+    m_xCount++;
+  }
+
+  /**
+   * Accept a data point to plot
+   *
+   * @param dataPoint a <code>double[]</code> value
+   */
+  public void acceptDataPoint(double [] dataPoint) {
+
+    if (m_outputFrame != null && (m_xCount % m_refreshFrequency == 0 )) {
+      double [] dp = new double[dataPoint.length+1];
+      dp[dp.length-1] = m_xCount;
+      System.arraycopy(dataPoint, 0, dp, 0, dataPoint.length);
+      // check for out of scale values
+      for (int i = 0; i < dataPoint.length; i++) {
+	if (dataPoint[i] < m_min) {
+	  m_oldMin = m_min; m_min = dataPoint[i];
+	  m_yScaleUpdate = true;
+	}
+
+	if (dataPoint[i] > m_max) {
+	  m_oldMax = m_max; m_max = dataPoint[i];
+	  m_yScaleUpdate = true;
+	}
+      }
+      if (m_yScaleUpdate) {
+	m_scalePanel.repaint();
+	m_yScaleUpdate = false;
+      }
+      synchronized(m_dataList) {
+	m_dataList.add(m_dataList.size(), dp);
+	//	m_dataList.push(dp);
+	m_dataList.notifyAll();
+	/*	if (m_dataList.size() != 0) {
+	  System.err.println("***** "+m_dataList.size());
+	  } */
+
+	//      System.err.println(m_xCount);
+      }
+    }
+  }
+
+  /**
+   * Set the visual appearance of this bean
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual appearance of this bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"StripChart.gif",
+		       BeanVisual.ICON_PATH+"StripChart_animated.gif");
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_updateHandler != null);
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>weka.gui.Logger</code> value
+   */
+  public void setLog(weka.gui.Logger logger) {
+    m_log = logger;
+  }
+
+  /**
+   * Returns true if, at this time,
+   * the object will accept a connection via the named event
+   *
+   * @param eventName the name of the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    if (m_listenee == null) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if, at this time,
+   * the object will accept a connection according to the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source for recieving events described by the named event
+   * This object is responsible for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void connectionNotification(String eventName, Object source) {
+    if (connectionAllowed(eventName)) {
+      m_listenee = source;
+    }
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source for named event. This object is responsible
+   * for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void disconnectionNotification(String eventName, Object source) {
+    m_listenee = null;
+  }
+
+  /**
+   * Describe <code>enumerateRequests</code> method here.
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    newVector.addElement("Show chart");
+    return newVector.elements();
+  }
+
+  /**
+   * Describe <code>performRequest</code> method here.
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show chart") == 0) {
+      showChart();
+    } else {
+      throw new
+	IllegalArgumentException(request
+				 + " not supported (StripChart)");
+    }
+  }
+
+  /**
+   * Tests out the StripChart from the command line
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Knowledge Flow : StipChart");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final StripChart jd = new StripChart();
+      jf.getContentPane().add(jd, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      jd.showChart();
+      Random r = new Random(1);
+      for (int i = 0; i < 1020; i++) {
+	double [] pos = new double[1];
+	pos[0] = r.nextDouble();
+	jd.acceptDataPoint(pos);
+      }
+      System.err.println("Done sending data");
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/StripChartBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/StripChartBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/StripChartBeanInfo.java	(revision 29)
@@ -0,0 +1,74 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StripChartBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the strip chart bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class StripChartBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    // hide all gui events
+    EventSetDescriptor [] esds = { };
+    return esds;
+  }
+
+  /**
+   * Get the property descriptors for this bean
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      PropertyDescriptor p2;
+      p1 = new PropertyDescriptor("xLabelFreq", StripChart.class);
+      p2 = new PropertyDescriptor("refreshFreq", StripChart.class);
+      PropertyDescriptor [] pds = { p1, p2 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.StripChart.class,
+			      StripChartCustomizer.class);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/StripChartCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/StripChartCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/StripChartCustomizer.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    StripChartCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JPanel;
+
+/**
+ * GUI Customizer for the strip chart bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public class StripChartCustomizer
+  extends JPanel
+  implements Customizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7441741530975984608L;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private PropertySheetPanel m_cvEditor = 
+    new PropertySheetPanel();
+
+  public StripChartCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+
+    setLayout(new BorderLayout());
+    add(m_cvEditor, BorderLayout.CENTER);
+    add(new javax.swing.JLabel("StripChartCustomizer"), 
+	BorderLayout.NORTH);
+  }
+  
+  /**
+   * Set the StripChart object to be customized
+   *
+   * @param object a StripChart
+   */
+  public void setObject(Object object) {
+    m_cvEditor.setTarget((StripChart)object);
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TestSetEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TestSetEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TestSetEvent.java	(revision 29)
@@ -0,0 +1,170 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TestSetEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.util.EventObject;
+
+/**
+ * Event encapsulating a test set
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4761 $
+ */
+public class TestSetEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 8780718708498854231L;
+  
+  /**
+   * The test set instances
+   */
+  protected Instances m_testSet;
+  private boolean m_structureOnly;
+  
+  /**
+   * What run number is this training set from. 
+   */
+  protected int m_runNumber = 1;
+  
+  
+  /**
+   * Maximum number of runs. 
+   */
+  protected int m_maxRunNumber = 1;
+
+  /**
+   * what number is this test set (ie fold 2 of 10 folds)
+   */
+  protected int m_setNumber;
+
+  /**
+   * Maximum number of sets (ie 10 in a 10 fold)
+   */
+  protected int m_maxSetNumber;
+
+  /**
+   * Creates a new <code>TestSetEvent</code>
+   *
+   * @param source the source of the event
+   * @param testSet the test instances
+   */
+  public TestSetEvent(Object source, Instances testSet) {
+    super(source);
+    m_testSet = testSet;
+    if (m_testSet != null && m_testSet.numInstances() == 0) {
+      m_structureOnly = true;
+    }
+  }
+
+  /**
+   * Creates a new <code>TestSetEvent</code>
+   *
+   * @param source the source of the event
+   * @param testSet the test instances
+   * @param setNum the number of the test set
+   * @param maxSetNum the maximum number of sets
+   */
+  public TestSetEvent(Object source, Instances testSet, 
+                      int setNum, int maxSetNum) {
+    this(source, testSet);
+    m_setNumber = setNum;
+    m_maxSetNumber = maxSetNum;
+  }
+  
+  /**
+   * Creates a new <code>TestSetEvent</code>
+   * 
+   * @param source the source of the event
+   * @param testSet the test instances
+   * @param runNum the run number that the test set belongs to
+   * @param maxRunNum the maximum run number
+   * @param setNum the number of the test set
+   * @param maxSetNum the maximum number of sets
+   */
+  public TestSetEvent(Object source, Instances testSet,
+      int runNum, int maxRunNum, int setNum, int maxSetNum) {
+    this(source, testSet, setNum, maxSetNum);
+    
+    m_runNumber = runNum;
+    m_maxRunNumber = maxRunNum;
+  }
+
+  /**
+   * Get the test set instances
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getTestSet() {
+    return m_testSet;
+  }
+  
+  /**
+   * Get the run number that this training set belongs to.
+   * 
+   * @return the run number for this training set.
+   */
+  public int getRunNumber() {
+    return m_runNumber;
+  }
+  
+  /**
+   * Get the maximum number of runs.
+   * 
+   * @return return the maximum number of runs.
+   */
+  public int getMaxRunNumber() {
+    return m_maxRunNumber;
+  }
+
+  /**
+   * Get the test set number (eg. fold 2 of a 10 fold split)
+   *
+   * @return an <code>int</code> value
+   */
+  public int getSetNumber() {
+    return m_setNumber;
+  }
+
+  /**
+   * Get the maximum set number
+   *
+   * @return an <code>int</code> value
+   */
+  public int getMaxSetNumber() {
+    return m_maxSetNumber;
+  }
+
+  /**
+   * Returns true if the encapsulated instances
+   * contain just header information
+   *
+   * @return true if only header information is
+   * available in this DataSetEvent
+   */
+  public boolean isStructureOnly() {
+    return m_structureOnly;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TestSetListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TestSetListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TestSetListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TestSetListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accpet test set events
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface TestSetListener extends EventListener {
+
+  /**
+   * Accept and process a test set event
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  void acceptTestSet(TestSetEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMaker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMaker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMaker.java	(revision 29)
@@ -0,0 +1,163 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TestSetMaker.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.io.Serializable;
+import java.util.Vector;
+
+/**
+ * Bean that accepts data sets and produces test sets
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5097 $
+ */
+public class TestSetMaker
+  extends AbstractTestSetProducer 
+  implements DataSourceListener, EventConstraints, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8473882857628061841L;
+
+  protected boolean m_receivedStopNotification = false;
+
+  public TestSetMaker() {
+     m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"TestSetMaker.gif",
+		       BeanVisual.ICON_PATH
+		       +"TestSetMaker_animated.gif");
+    m_visual.setText("TestSetMaker");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Designate an incoming data set as a test set.";
+  }
+
+  /**
+   * Accepts and processes a data set event
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public void acceptDataSet(DataSetEvent e) {
+    m_receivedStopNotification = false;
+    TestSetEvent tse = new TestSetEvent(this, e.getDataSet());
+    tse.m_setNumber = 1;
+    tse.m_maxSetNumber = 1;
+    notifyTestSetProduced(tse);
+  }
+
+  /**
+   * Tells all listeners that a test set is available
+   *
+   * @param tse a <code>TestSetEvent</code> value
+   */
+  protected void notifyTestSetProduced(TestSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_listeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_receivedStopNotification) {
+          if (m_logger != null) {
+            m_logger.logMessage("[TestSetMaker] "
+                + statusMessagePrefix() + " stopping.");
+            m_logger.statusMessage(statusMessagePrefix()
+                + "INTERRUPTED");
+          }
+          m_receivedStopNotification = false;
+          break;
+        }
+	((TestSetListener)l.elementAt(i)).acceptTestSet(tse);
+      }
+    }
+  }
+
+  public void stop() {
+    // do something
+    m_receivedStopNotification = true;
+
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).eventGeneratable("dataSet")) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMakerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMakerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TestSetMakerBeanInfo.java	(revision 29)
@@ -0,0 +1,35 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TestSetMakerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the test set maker bean. Essentially just hides
+ * gui related properties
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ */
+public class TestSetMakerBeanInfo 
+  extends AbstractTestSetProducerBeanInfo { }
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TestSetProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TestSetProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TestSetProducer.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TestSetProducer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/**
+ * Interface to something that can produce test sets
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface TestSetProducer {
+
+  /**
+   * Add a listener for test set events
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  void addTestSetListener(TestSetListener tsl);
+
+  /**
+   * Remove a listener for test set events
+   *
+   * @param tsl a <code>TestSetListener</code> value
+   */
+  void removeTestSetListener(TestSetListener tsl);
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TextEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TextEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TextEvent.java	(revision 29)
@@ -0,0 +1,79 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TextEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventObject;
+
+/**
+ * Event that encapsulates some textual information
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public class TextEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 4196810607402973744L;
+  
+  /**
+   * The text
+   */
+  protected String m_text;
+
+  /**
+   * The title for the text. Could be used in a list component
+   */
+  protected String m_textTitle;
+
+  /**
+   * Creates a new <code>TextEvent</code> instance.
+   *
+   * @param source an <code>Object</code> value
+   * @param text a <code>String</code> value
+   */
+  public TextEvent(Object source, String text, String textTitle) {
+    super(source);
+    
+    m_text = text;
+    m_textTitle = textTitle;
+  }
+
+  /**
+   * Describe <code>getText</code> method here.
+   *
+   * @return a <code>String</code> value
+   */
+  public String getText() {
+    return m_text;
+  }
+
+  /**
+   * Describe <code>getTextTitle</code> method here.
+   *
+   * @return a <code>String</code> value
+   */
+  public String getTextTitle() {
+    return m_textTitle;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TextListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TextListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TextListener.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TextListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can process a TextEvent
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface TextListener extends EventListener {
+
+  /**
+   * Accept and process a text event
+   *
+   * @param e a <code>TextEvent</code> value
+   */
+  void acceptText(TextEvent e);
+}
+
+
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TextViewer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TextViewer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TextViewer.java	(revision 29)
@@ -0,0 +1,650 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TextViewer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.EventSetDescriptor;
+import java.beans.PropertyChangeListener;
+import java.beans.VetoableChangeListener;
+import java.beans.beancontext.BeanContext;
+import java.beans.beancontext.BeanContextChild;
+import java.beans.beancontext.BeanContextChildSupport;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * Bean that collects and displays pieces of text
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4806 $
+ */
+public class TextViewer 
+  extends JPanel
+  implements TextListener, DataSourceListener, 
+	     TrainingSetListener, TestSetListener,
+	     Visible, UserRequestAcceptor, 
+	     BeanContextChild,
+             BeanCommon,
+             EventConstraints {
+
+  /** for serialization */
+  private static final long serialVersionUID = 104838186352536832L;
+
+  protected BeanVisual m_visual;
+
+
+  private transient JFrame m_resultsFrame = null;
+
+  /**
+   * Output area for a piece of text
+   */
+  private transient JTextArea m_outText = null;// = new JTextArea(20, 80);
+
+  /**
+   * List of text revieved so far
+   */
+  protected transient ResultHistoryPanel m_history;
+
+  /**
+   * True if this bean's appearance is the design mode appearance
+   */
+  protected boolean m_design;
+  
+  /**
+   * BeanContex that this bean might be contained within
+   */
+  protected transient BeanContext m_beanContext = null;
+
+  /**
+   * BeanContextChild support
+   */
+  protected BeanContextChildSupport m_bcSupport = 
+    new BeanContextChildSupport(this);
+
+  /**
+   * Objects listening for text events
+   */
+  private Vector m_textListeners = new Vector();
+
+  private transient Logger m_log = null;
+
+  
+  public TextViewer() {
+    /*    setUpResultHistory();
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER); */
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    if (!ge.isHeadless()) {
+      appearanceFinal();
+    }
+  }
+
+  protected void appearanceDesign() {
+    setUpResultHistory();
+    removeAll();
+    m_visual =  new BeanVisual("TextViewer", 
+			       BeanVisual.ICON_PATH+"DefaultText.gif",
+			       BeanVisual.ICON_PATH+"DefaultText_animated.gif");
+    setLayout(new BorderLayout());
+    add(m_visual, BorderLayout.CENTER);
+  }
+
+  protected void appearanceFinal() {
+    removeAll();
+    setLayout(new BorderLayout());
+    setUpFinal();
+  }
+
+  protected void setUpFinal() {
+    setUpResultHistory();
+    JPanel holder = new JPanel();
+    holder.setLayout(new BorderLayout());
+    JScrollPane js = new JScrollPane(m_outText);
+    js.setBorder(BorderFactory.createTitledBorder("Text"));
+    holder.add(js, BorderLayout.CENTER);
+    holder.add(m_history, BorderLayout.WEST);
+
+    add(holder, BorderLayout.CENTER);
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "General purpose text display.";
+  }
+
+  private void setUpResultHistory() {
+    java.awt.GraphicsEnvironment ge = 
+      java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+    if(!ge.isHeadless()) {
+      if (m_outText == null) {
+        m_outText = new JTextArea(20, 80);
+        m_history = new ResultHistoryPanel(m_outText);
+      }
+      m_outText.setEditable(false);
+      m_outText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+      m_outText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+      m_history.setBorder(BorderFactory.createTitledBorder("Result list"));
+      m_history.setHandleRightClicks(false);
+      m_history.getList().addMouseListener(new MouseAdapter() {
+          public void mouseClicked(MouseEvent e) {
+            if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+                 != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+              int index = m_history.getList().locationToIndex(e.getPoint());
+              if (index != -1) {
+                String name = m_history.getNameAtIndex(index);
+                visualize(name, e.getX(), e.getY());
+              } else {
+                visualize(null, e.getX(), e.getY());
+              }
+            }
+          }
+        });
+    }
+  }
+
+  /**
+   * Handles constructing a popup menu with visualization options.
+   * @param name the name of the result history list entry clicked on by
+   * the user
+   * @param x the x coordinate for popping up the menu
+   * @param y the y coordinate for popping up the menu
+   */
+  protected void visualize(String name, int x, int y) {
+    final JPanel panel = this;
+    final String selectedName = name;
+    JPopupMenu resultListMenu = new JPopupMenu();
+    
+    JMenuItem visMainBuffer = new JMenuItem("View in main window");
+    if (selectedName != null) {
+      visMainBuffer.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_history.setSingle(selectedName);
+	  }
+	});
+    } else {
+      visMainBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visMainBuffer);
+    
+    JMenuItem visSepBuffer = new JMenuItem("View in separate window");
+    if (selectedName != null) {
+      visSepBuffer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_history.openFrame(selectedName);
+	}
+      });
+    } else {
+      visSepBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visSepBuffer);
+    
+    JMenuItem saveOutput = new JMenuItem("Save result buffer");
+    if (selectedName != null) {
+      saveOutput.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    SaveBuffer m_SaveOut = new SaveBuffer(null, panel);
+	    StringBuffer sb = m_history.getNamedBuffer(selectedName);
+	    if (sb != null) {
+	      m_SaveOut.save(sb);
+	    }
+	  }
+	});
+    } else {
+      saveOutput.setEnabled(false);
+    }
+    resultListMenu.add(saveOutput);
+    
+    JMenuItem deleteOutput = new JMenuItem("Delete result buffer");
+    if (selectedName != null) {
+      deleteOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_history.removeResult(selectedName);
+	}
+      });
+    } else {
+      deleteOutput.setEnabled(false);
+    }
+    resultListMenu.add(deleteOutput);
+
+    resultListMenu.show(m_history.getList(), x, y);
+  }
+
+  /**
+   * Accept a data set for displaying as text
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public synchronized void acceptDataSet(DataSetEvent e) {
+    TextEvent nt = new TextEvent(e.getSource(), 
+				 e.getDataSet().toString(),
+				 e.getDataSet().relationName());
+    acceptText(nt);
+  }
+
+  /**
+   * Accept a training set for displaying as text
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public synchronized void acceptTrainingSet(TrainingSetEvent e) {
+    TextEvent nt = new TextEvent(e.getSource(), 
+				 e.getTrainingSet().toString(),
+				 e.getTrainingSet().relationName());
+    acceptText(nt);
+  }
+
+  /**
+   * Accept a test set for displaying as text
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public synchronized void acceptTestSet(TestSetEvent e) {
+    TextEvent nt = new TextEvent(e.getSource(), 
+				 e.getTestSet().toString(),
+				 e.getTestSet().relationName());
+    acceptText(nt);
+  }
+
+  /**
+   * Accept some text
+   *
+   * @param e a <code>TextEvent</code> value
+   */
+  public synchronized void acceptText(TextEvent e) {
+    if (m_outText == null) {
+      setUpResultHistory();
+    }
+    StringBuffer result = new StringBuffer();
+    result.append(e.getText());
+    //    m_resultsString.append(e.getText());
+    //    m_outText.setText(m_resultsString.toString());
+    String name = (new SimpleDateFormat("HH:mm:ss - "))
+      .format(new Date());
+    name += e.getTextTitle();
+    //    System.err.println(name);
+    if (name.length() > 30) {
+      name = name.substring(0, 30);
+    }
+
+    if (m_outText != null) {
+      // see if there is an entry with this name already in the list -
+      // could happen if two items with the same name arrive at the same second
+      int mod = 2;
+      String nameOrig = new String(name);
+      while (m_history.getNamedBuffer(name) != null) {
+        name = new String(nameOrig+""+mod);
+        mod++;
+      }
+      m_history.addResult(name, result);
+      m_history.setSingle(name);
+    }
+
+    // pass on the event to any listeners
+    notifyTextListeners(e);
+  }
+
+  /**
+   * Describe <code>setVisual</code> method here.
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  public void setVisual(BeanVisual newVisual) {
+    m_visual = newVisual;
+  }
+
+  /**
+   * Get the visual appearance of this bean
+   */
+  public BeanVisual getVisual() {
+    return m_visual;
+  }
+  
+  /**
+   * Use the default visual appearance for this bean
+   */
+  public void useDefaultVisual() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultText.gif",
+		       BeanVisual.ICON_PATH+"DefaultText_animated.gif");
+  }
+
+  /**
+   * Popup a component to display the selected text
+   */
+  public void showResults() {
+    if (m_resultsFrame == null) {
+      if (m_outText == null) {
+	setUpResultHistory();
+      }
+      m_resultsFrame = new JFrame("Text Viewer");
+      m_resultsFrame.getContentPane().setLayout(new BorderLayout());
+      final JScrollPane js = new JScrollPane(m_outText);
+      js.setBorder(BorderFactory.createTitledBorder("Text"));
+      m_resultsFrame.getContentPane().add(js, BorderLayout.CENTER);
+      m_resultsFrame.getContentPane().add(m_history, BorderLayout.WEST);
+      m_resultsFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    m_resultsFrame.dispose();
+	    m_resultsFrame = null;
+	  }
+	});
+      m_resultsFrame.pack();
+      m_resultsFrame.setVisible(true);
+    } else {
+      m_resultsFrame.toFront();
+    }
+  }
+
+  /**
+   * Get a list of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+
+    newVector.addElement("Show results");
+
+    newVector.addElement("?Clear results");
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Show results") == 0) {
+      showResults();
+    } else if (request.compareTo("Clear results") == 0) {
+      m_outText.setText("");
+      m_history.clearResults();
+    } else {
+      throw new 
+	IllegalArgumentException(request
+		    + " not supported (TextViewer)");
+    }
+  }
+
+  /**
+   * Add a property change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(String name,
+					PropertyChangeListener pcl) {
+    m_bcSupport.addPropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Remove a property change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(String name,
+					   PropertyChangeListener pcl) {
+    m_bcSupport.removePropertyChangeListener(name, pcl);
+  }
+
+  /**
+   * Add a vetoable change listener to this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void addVetoableChangeListener(String name,
+				       VetoableChangeListener vcl) {
+    m_bcSupport.addVetoableChangeListener(name, vcl);
+  }
+  
+  /**
+   * Remove a vetoable change listener from this bean
+   *
+   * @param name the name of the property of interest
+   * @param vcl a <code>VetoableChangeListener</code> value
+   */
+  public void removeVetoableChangeListener(String name,
+					   VetoableChangeListener vcl) {
+    m_bcSupport.removeVetoableChangeListener(name, vcl);
+  }
+
+  /**
+   * Set a bean context for this bean
+   *
+   * @param bc a <code>BeanContext</code> value
+   */
+  public void setBeanContext(BeanContext bc) {
+    m_beanContext = bc;
+    m_design = m_beanContext.isDesignTime();
+    if (m_design) {
+      appearanceDesign();
+    } else {
+      java.awt.GraphicsEnvironment ge = 
+        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment(); 
+      if (!ge.isHeadless()){
+        appearanceFinal();
+      }
+    }
+  }
+
+  /**
+   * Notify all text listeners of a text event
+   *
+   * @param ge a <code>TextEvent</code> value
+   */
+  private void notifyTextListeners(TextEvent ge) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_textListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((TextListener)l.elementAt(i)).acceptText(ge);
+      }
+    }
+  }
+
+  /**
+   * Return the bean context (if any) that this bean is embedded in
+   *
+   * @return a <code>BeanContext</code> value
+   */
+  public BeanContext getBeanContext() {
+    return m_beanContext;
+  }
+
+  /**
+   * Stop any processing that the bean might be doing.
+   */
+  public void stop() {
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Set a logger
+   *
+   * @param logger a <code>Logger</code> value
+   */
+  public void setLog(Logger logger) {
+    m_log = logger;
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the supplied
+   * EventSetDescriptor
+   *
+   * @param esd the EventSetDescriptor
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(EventSetDescriptor esd) {
+    return connectionAllowed(esd.getName());
+  }
+
+  /**
+   * Returns true if, at this time, 
+   * the object will accept a connection via the named event
+   *
+   * @param eventName the name of the event
+   * @return true if the object will accept a connection
+   */
+  public boolean connectionAllowed(String eventName) {
+    return true;
+  }
+
+  /**
+   * Notify this object that it has been registered as a listener with
+   * a source for recieving events described by the named event
+   * This object is responsible for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void connectionNotification(String eventName, Object source) {
+  }
+
+  /**
+   * Notify this object that it has been deregistered as a listener with
+   * a source for named event. This object is responsible
+   * for recording this fact.
+   *
+   * @param eventName the event
+   * @param source the source with which this object has been registered as
+   * a listener
+   */
+  public void disconnectionNotification(String eventName, Object source) {
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (eventName.equals("text")) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Add a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void addTextListener(TextListener cl) {
+    m_textListeners.addElement(cl);
+  }
+
+  /**
+   * Remove a text listener
+   *
+   * @param cl a <code>TextListener</code> value
+   */
+  public synchronized void removeTextListener(TextListener cl) {
+    m_textListeners.remove(cl);
+  }
+
+  public static void main(String [] args) {
+    try {
+      final javax.swing.JFrame jf = new javax.swing.JFrame();
+      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+
+      final TextViewer tv = new TextViewer();
+
+      tv.acceptText(new TextEvent(tv, "Here is some test text from the main "
+				  +"method of this class.", "The Title"));
+      jf.getContentPane().add(tv, java.awt.BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+        public void windowClosing(java.awt.event.WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TextViewerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TextViewerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TextViewerBeanInfo.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TextViewerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the text viewer
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public class TextViewerBeanInfo extends SimpleBeanInfo {
+  
+  /**
+   * Get the event set descriptors for this bean
+   *
+   * @return an <code>EventSetDescriptor[]</code> value
+   */
+  public EventSetDescriptor [] getEventSetDescriptors() {
+    try {
+      EventSetDescriptor [] esds = { 
+        new EventSetDescriptor(TextViewer.class,
+                               "text",
+                               TextListener.class,
+                               "acceptText")
+      };      
+      return esds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataEvent.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ThresholdDataEvent.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Attribute;
+import weka.gui.visualize.PlotData2D;
+
+import java.util.EventObject;
+
+/**
+ * Event encapsulating classifier performance data based on
+ * varying a threshold over the classifier's predicted probabilities
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5750 $
+ * @see EventObject
+ */
+public class ThresholdDataEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8309334224492439644L;
+
+  private PlotData2D m_dataSet;
+  
+  private Attribute m_classAttribute;
+
+  public ThresholdDataEvent(Object source, PlotData2D dataSet) {
+    this(source, dataSet, null);
+  }
+  
+  public ThresholdDataEvent(Object source, PlotData2D dataSet, Attribute classAtt) {
+    super(source);
+    m_dataSet = dataSet;
+    m_classAttribute = classAtt;
+  }
+  
+  /**
+   * Return the instances of the data set
+   *
+   * @return an <code>Instances</code> value
+   */
+  public PlotData2D getDataSet() {
+    return m_dataSet;
+  }
+  
+  /**
+   * Return the class attribute for which the threshold data was generated
+   * for.
+   * 
+   * @return the class attribute for the threshold data or null if not set.
+   */
+  public Attribute getClassAttribute() {
+    return m_classAttribute;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/ThresholdDataListener.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ThresholdDataListener.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accept ThresholdDataEvents
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface ThresholdDataListener extends EventListener {
+  void acceptDataSet(ThresholdDataEvent e);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMaker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMaker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMaker.java	(revision 29)
@@ -0,0 +1,389 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainTestSplitMaker.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+/**
+ * Bean that accepts data sets, training sets, test sets and produces
+ * both a training and test set by randomly spliting the data
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4813 $
+ */
+public class TrainTestSplitMaker
+  extends AbstractTrainAndTestSetProducer
+  implements DataSourceListener, TrainingSetListener, TestSetListener,
+	     UserRequestAcceptor, EventConstraints, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7390064039444605943L;
+
+  private double m_trainPercentage = 66;
+  private int m_randomSeed = 1;
+  
+  private Thread m_splitThread = null;
+
+  public TrainTestSplitMaker() {
+         m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"TrainTestSplitMaker.gif",
+		       BeanVisual.ICON_PATH
+		       +"TrainTestSplittMaker_animated.gif");
+    m_visual.setText("TrainTestSplitMaker");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Split an incoming data set into separate train and test sets." ;
+  }
+
+  /**
+   * Tip text info for this property
+   *
+   * @return a <code>String</code> value
+   */
+  public String trainPercentTipText() {
+    return "The percentage of data to go into the training set";
+  }
+
+  /**
+   * Set the percentage of data to be in the training portion of the split
+   *
+   * @param newTrainPercent an <code>int</code> value
+   */
+  public void setTrainPercent(double newTrainPercent) {
+    m_trainPercentage = newTrainPercent;
+  }
+
+  /**
+   * Get the percentage of the data that will be in the training portion of
+   * the split
+   *
+   * @return an <code>int</code> value
+   */
+  public double getTrainPercent() {
+    return m_trainPercentage;
+  }
+
+  /**
+   * Tip text for this property
+   *
+   * @return a <code>String</code> value
+   */
+  public String seedTipText() {
+    return "The randomization seed";
+  }
+
+  /**
+   * Set the random seed
+   *
+   * @param newSeed an <code>int</code> value
+   */
+  public void setSeed(int newSeed) {
+    m_randomSeed = newSeed;
+  }
+
+  /**
+   * Get the value of the random seed
+   *
+   * @return an <code>int</code> value
+   */
+  public int getSeed() {
+    return m_randomSeed;
+  }
+
+  /**
+   * Accept a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  public void acceptTrainingSet(TrainingSetEvent e) {
+    Instances trainingSet = e.getTrainingSet();
+    DataSetEvent dse = new DataSetEvent(this, trainingSet);
+    acceptDataSet(dse);
+  }
+
+  /**
+   * Accept a test set
+   *
+   * @param e a <code>TestSetEvent</code> value
+   */
+  public void acceptTestSet(TestSetEvent e) {
+    Instances testSet = e.getTestSet();
+    DataSetEvent dse = new DataSetEvent(this, testSet);
+    acceptDataSet(dse);
+  }
+
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public void acceptDataSet(DataSetEvent e) {
+    if (m_splitThread == null) {
+      final Instances dataSet = new Instances(e.getDataSet());
+      m_splitThread = new Thread() {
+	  public void run() {
+	    try {
+	      dataSet.randomize(new Random(m_randomSeed));
+	      int trainSize = 
+                (int)Math.round(dataSet.numInstances() * m_trainPercentage / 100);
+	      int testSize = dataSet.numInstances() - trainSize;
+      
+	      Instances train = new Instances(dataSet, 0, trainSize);
+	      Instances test = new Instances(dataSet, trainSize, testSize);
+      
+	      TrainingSetEvent tse =
+		new TrainingSetEvent(TrainTestSplitMaker.this, train);
+	      tse.m_setNumber = 1; tse.m_maxSetNumber = 1;
+	      if (m_splitThread != null) {
+		notifyTrainingSetProduced(tse);
+	      }
+    
+	      // inform all test set listeners
+	      TestSetEvent teste = 
+		new TestSetEvent(TrainTestSplitMaker.this, test);
+	      teste.m_setNumber = 1; teste.m_maxSetNumber = 1;
+	      if (m_splitThread != null) {
+		notifyTestSetProduced(teste);
+	      } else {
+		if (m_logger != null) {
+		  m_logger.logMessage("[TrainTestSplitMaker] "
+		      + statusMessagePrefix() + " Split has been canceled!");
+		  m_logger.statusMessage(statusMessagePrefix()
+		      + "INTERRUPTED");
+		}
+	      }
+	    } catch (Exception ex) {
+	      stop(); // stop all processing
+	      if (m_logger != null) {
+	          m_logger.statusMessage(statusMessagePrefix()
+	              + "ERROR (See log for details)");
+	          m_logger.logMessage("[TrainTestSplitMaker] " 
+	              + statusMessagePrefix()
+	              + " problem during split creation. " 
+	              + ex.getMessage());
+	      }
+	      ex.printStackTrace();
+	    } finally {
+	      if (isInterrupted()) {
+	        if (m_logger != null) {
+	          m_logger.logMessage("[TrainTestSplitMaker] "
+	              + statusMessagePrefix() + " Split has been canceled!");
+	          m_logger.statusMessage(statusMessagePrefix()
+	              + "INTERRUPTED");
+	        }
+	      }
+	      block(false);
+	    }
+	  }
+	};
+      m_splitThread.setPriority(Thread.MIN_PRIORITY);
+      m_splitThread.start();
+
+      //      if (m_splitThread.isAlive()) {
+      block(true);
+      //      }
+      m_splitThread = null;
+    }
+  }
+
+  /**
+   * Notify test set listeners that a test set is available
+   *
+   * @param tse a <code>TestSetEvent</code> value
+   */
+  protected void notifyTestSetProduced(TestSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_testListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_splitThread == null) {
+          break;
+        }
+        //	System.err.println("Notifying test listeners "
+        //			   +"(Train - test split maker)");
+	((TestSetListener)l.elementAt(i)).acceptTestSet(tse);
+      }
+    }
+  }
+
+  /**
+   * Notify training set listeners that a training set is available
+   *
+   * @param tse a <code>TrainingSetEvent</code> value
+   */
+  protected void notifyTrainingSetProduced(TrainingSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_trainingListeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_splitThread == null) {
+          break;
+        }
+        //	System.err.println("Notifying training listeners "
+        //			   +"(Train - test split fold maker)");
+	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(tse);
+      }
+    }
+  }
+
+  /**
+   * Function used to stop code that calls acceptDataSet. This is 
+   * needed as split is performed inside a separate
+   * thread of execution.
+   *
+   * @param tf a <code>boolean</code> value
+   */
+  private synchronized void block(boolean tf) {
+    if (tf) {
+      try {
+	// make sure that the thread is still alive before blocking
+	if (m_splitThread.isAlive()) {
+	  wait();
+	}
+      } catch (InterruptedException ex) {
+      }
+    } else {
+      notifyAll();
+    }
+  }
+
+  /**
+   * Stop processing
+   */
+  public void stop() {
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+
+    // stop the split thread
+    if (m_splitThread != null) {
+      Thread temp = m_splitThread;
+      m_splitThread = null;
+      temp.interrupt();
+      temp.stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return (m_splitThread != null);
+  }
+
+  /**
+   * Get list of user requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  public Enumeration enumerateRequests() {
+    Vector newVector = new Vector(0);
+    if (m_splitThread != null) {
+      newVector.addElement("Stop");
+    }
+    return newVector.elements();
+  }
+
+  /**
+   * Perform the named request
+   *
+   * @param request a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  public void performRequest(String request) {
+    if (request.compareTo("Stop") == 0) {
+      stop();
+    } else {
+      throw new IllegalArgumentException(request
+			 + " not supported (TrainTestSplitMaker)");
+    }
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that the supplied event name is
+   * an event that could be generated by this bean
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+    
+    if (m_listenee instanceof EventConstraints) {
+      if (((EventConstraints)m_listenee).eventGeneratable("dataSet") ||
+	  ((EventConstraints)m_listenee).eventGeneratable("trainingSet") ||
+	  ((EventConstraints)m_listenee).eventGeneratable("testSet")) {
+	return true;
+      } else {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerBeanInfo.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainTestSplitMakerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the train test split maker bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ * @see AbstractTrainAndTestSetProducerBeanInfo
+ */
+public class TrainTestSplitMakerBeanInfo 
+  extends AbstractTrainAndTestSetProducerBeanInfo {
+  
+  /**
+   * Get the property descriptors for this bean
+   *
+   * @return a <code>PropertyDescriptor[]</code> value
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      PropertyDescriptor p1;
+      PropertyDescriptor p2;
+      p1 = new PropertyDescriptor("trainPercent", TrainTestSplitMaker.class);
+      p2 = new PropertyDescriptor("seed", TrainTestSplitMaker.class);
+      PropertyDescriptor [] pds = { p1, p2 };
+      return pds;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Get the bean descriptor for this bean
+   *
+   * @return a <code>BeanDescriptor</code> value
+   */
+  public BeanDescriptor getBeanDescriptor() {
+    return new BeanDescriptor(weka.gui.beans.TrainTestSplitMaker.class,
+			      TrainTestSplitMakerCustomizer.class);
+  }
+ }
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerCustomizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerCustomizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainTestSplitMakerCustomizer.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainTestSplitMakerCustomizer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.PropertySheetPanel;
+
+import java.awt.BorderLayout;
+import java.beans.Customizer;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.JPanel;
+
+/**
+ * GUI customizer for the train test split maker bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public class TrainTestSplitMakerCustomizer
+  extends JPanel
+  implements Customizer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1684662340241807260L;
+
+  private PropertyChangeSupport m_pcSupport = 
+    new PropertyChangeSupport(this);
+
+  private PropertySheetPanel m_splitEditor = 
+    new PropertySheetPanel();
+
+  public TrainTestSplitMakerCustomizer() {
+    setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 5, 5));
+    
+    setLayout(new BorderLayout());
+    add(m_splitEditor, BorderLayout.CENTER);
+    add(new javax.swing.JLabel("TrainTestSplitMakerCustomizer"), 
+	BorderLayout.NORTH);
+  }
+  
+  /**
+   * Set the TrainTestSplitMaker to be customized
+   *
+   * @param object an <code>Object</code> value
+   */
+  public void setObject(Object object) {
+    m_splitEditor.setTarget((TrainTestSplitMaker)object);
+  }
+
+  /**
+   * Add a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void addPropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.addPropertyChangeListener(pcl);
+  }
+  
+  /**
+   * Remove a property change listener
+   *
+   * @param pcl a <code>PropertyChangeListener</code> value
+   */
+  public void removePropertyChangeListener(PropertyChangeListener pcl) {
+    m_pcSupport.removePropertyChangeListener(pcl);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetEvent.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainingSetEvent.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instances;
+
+import java.util.EventObject;
+
+/**
+ * Event encapsulating a training set
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4761 $
+ */
+public class TrainingSetEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5872343811810872662L;
+  
+  /**
+   * The training instances
+   */
+  protected Instances m_trainingSet;
+  private boolean m_structureOnly;
+  
+  /**
+   * What run number is this training set from. 
+   */
+  protected int m_runNumber = 1;
+  
+  
+  /**
+   * Maximum number of runs. 
+   */
+  protected int m_maxRunNumber = 1;
+
+  /**
+   * what number is this training set (ie fold 2 of 10 folds)
+   */
+  protected int m_setNumber;
+
+  /**
+   * Maximum number of sets (ie 10 in a 10 fold)
+   */
+  protected int m_maxSetNumber;
+
+  /**
+   * Creates a new <code>TrainingSetEvent</code>
+   *
+   * @param source the source of the event
+   * @param trainSet the training instances
+   */
+  public TrainingSetEvent(Object source, Instances trainSet) {
+    super(source);
+    m_trainingSet = trainSet;
+    if (m_trainingSet != null && m_trainingSet.numInstances() == 0) {
+      m_structureOnly = true;
+    }
+  }
+
+  /**
+   * Creates a new <code>TrainingSetEvent</code>
+   *
+   * @param source the source of the event
+   * @param trainSet the training instances
+   * @param setNum the number of the training set
+   * @param maxSetNum the maximum number of sets
+   */
+  public TrainingSetEvent(Object source, Instances trainSet, int setNum, int maxSetNum) {
+    this(source, trainSet);
+    m_setNumber = setNum;
+    m_maxSetNumber = maxSetNum;
+  }
+  
+  /**
+   * Creates a new <code>TrainingSetEvent</code>
+   * 
+   * @param source the source of the event
+   * @param trainSet the training instances
+   * @param runNum the run number that the training set belongs to
+   * @param maxRunNum the maximum run number
+   * @param setNum the number of the training set
+   * @param maxSetNum the maximum number of sets
+   */
+  public TrainingSetEvent(Object source, Instances trainSet, int runNum,
+      int maxRunNum, int setNum, int maxSetNum) {
+    this(source, trainSet, setNum, maxSetNum);
+    
+    m_runNumber = runNum;
+    m_maxRunNumber = maxRunNum; 
+  }
+
+  /**
+   * Get the training instances
+   *
+   * @return an <code>Instances</code> value
+   */
+  public Instances getTrainingSet() {
+    return m_trainingSet;
+  }
+  
+  /**
+   * Get the run number that this training set belongs to.
+   * 
+   * @return the run number for this training set.
+   */
+  public int getRunNumber() {
+    return m_runNumber;
+  }
+  
+  /**
+   * Get the maximum number of runs.
+   * 
+   * @return return the maximum number of runs.
+   */
+  public int getMaxRunNumber() {
+    return m_maxRunNumber;
+  }
+
+  /**
+   * Get the set number (eg. fold 2 of a 10 fold split)
+   *
+   * @return an <code>int</code> value
+   */
+  public int getSetNumber() {
+    return m_setNumber;
+  }
+
+  /**
+   * Get the maximum set number
+   *
+   * @return an <code>int</code> value
+   */
+  public int getMaxSetNumber() {
+    return m_maxSetNumber;
+  }
+
+  /**
+   * Returns true if the encapsulated instances
+   * contain just header information
+   *
+   * @return true if only header information is
+   * available in this DataSetEvent
+   */
+  public boolean isStructureOnly() {
+    return m_structureOnly;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainingSetListener.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accept and process training set events
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ */
+public interface TrainingSetListener extends EventListener {
+
+  /**
+   * Accept and process a training set
+   *
+   * @param e a <code>TrainingSetEvent</code> value
+   */
+  void acceptTrainingSet(TrainingSetEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMaker.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMaker.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMaker.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainingSetMaker.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.io.Serializable;
+import java.util.Vector;
+
+/**
+ * Bean that accepts a data sets and produces a training set
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 4999 $
+ */
+public class TrainingSetMaker 
+  extends AbstractTrainingSetProducer 
+  implements DataSourceListener, EventConstraints, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6152577265471535786L;
+
+  protected boolean m_receivedStopNotification = false;
+
+  public TrainingSetMaker() {
+    m_visual.loadIcons(BeanVisual.ICON_PATH
+		       +"TrainingSetMaker.gif",
+		       BeanVisual.ICON_PATH
+		       +"TrainingSetMaker_animated.gif");
+    m_visual.setText("TrainingSetMaker");
+  }
+
+  /**
+   * Set a custom (descriptive) name for this bean
+   * 
+   * @param name the name to use
+   */
+  public void setCustomName(String name) {
+    m_visual.setText(name);
+  }
+
+  /**
+   * Get the custom (descriptive) name for this bean (if one has been set)
+   * 
+   * @return the custom name (or the default name)
+   */
+  public String getCustomName() {
+    return m_visual.getText();
+  }
+
+  /**
+   * Global info for this bean
+   *
+   * @return a <code>String</code> value
+   */
+  public String globalInfo() {
+    return "Designate an incoming data set as a training set.";
+  }
+
+  /**
+   * Accept a data set
+   *
+   * @param e a <code>DataSetEvent</code> value
+   */
+  public void acceptDataSet(DataSetEvent e) {
+    m_receivedStopNotification = false;
+    System.err.println("In accept data set");
+    TrainingSetEvent tse = new TrainingSetEvent(this, e.getDataSet());
+    tse.m_setNumber = 1;
+    tse.m_maxSetNumber = 1;
+    notifyTrainingSetProduced(tse);
+  }
+
+  /**
+   * Inform training set listeners that a training set is available
+   *
+   * @param tse a <code>TrainingSetEvent</code> value
+   */
+  protected void notifyTrainingSetProduced(TrainingSetEvent tse) {
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_listeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+        if (m_receivedStopNotification) {
+          if (m_logger != null) {
+            m_logger.logMessage("T[rainingSetMaker] "
+                +statusMessagePrefix() + " stopping.");
+            m_logger.statusMessage(statusMessagePrefix()
+                + "INTERRUPTED");
+          }
+          m_receivedStopNotification = false;
+          break;
+        }
+	System.err.println("Notifying listeners (training set maker)");
+	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(tse);
+      }
+    }
+  }
+
+  /**
+   * Stop any action
+   */
+  public void stop() {
+    m_receivedStopNotification = true;
+
+    // tell the listenee (upstream bean) to stop
+    if (m_listenee instanceof BeanCommon) {
+      //      System.err.println("Listener is BeanCommon");
+      ((BeanCommon)m_listenee).stop();
+    }
+  }
+  
+  /**
+   * Returns true if. at this time, the bean is busy with some
+   * (i.e. perhaps a worker thread is performing some calculation).
+   * 
+   * @return true if the bean is busy.
+   */
+  public boolean isBusy() {
+    return false;
+  }
+
+  /**
+   * Returns true, if at the current time, the named event could
+   * be generated. Assumes that supplied event names are names of
+   * events that could be generated by this bean.
+   *
+   * @param eventName the name of the event in question
+   * @return true if the named event could be generated at this point in
+   * time
+   */
+  public boolean eventGeneratable(String eventName) {
+    if (m_listenee == null) {
+      return false;
+    }
+
+    if (m_listenee instanceof EventConstraints) {
+      if (!((EventConstraints)m_listenee).eventGeneratable("dataSet")) {
+	return false;
+      }
+    }
+    return true;
+  }
+  
+  private String statusMessagePrefix() {
+    return getCustomName() + "$" + hashCode() + "|";
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMakerBeanInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMakerBeanInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetMakerBeanInfo.java	(revision 29)
@@ -0,0 +1,34 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainingSetMakerBeanInfo.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.beans.*;
+
+/**
+ * Bean info class for the training set maker bean
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public class TrainingSetMakerBeanInfo 
+  extends AbstractTrainingSetProducerBeanInfo { }
Index: branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/TrainingSetProducer.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TrainingSetProducer.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/**
+ * Interface to something that can produce a training set
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ */
+public interface TrainingSetProducer {
+
+  /**
+   * Add a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  void addTrainingSetListener(TrainingSetListener tsl);
+
+  /**
+   * Remove a training set listener
+   *
+   * @param tsl a <code>TrainingSetListener</code> value
+   */
+  void removeTrainingSetListener(TrainingSetListener tsl);
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/UserRequestAcceptor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/UserRequestAcceptor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/UserRequestAcceptor.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    UserRequestAcceptor.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.Enumeration;
+
+/**
+ * Interface to something that can accept requests from a user to perform
+ * some action
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ */
+public interface UserRequestAcceptor {
+
+  /**
+   * Get a list of performable requests
+   *
+   * @return an <code>Enumeration</code> value
+   */
+  Enumeration enumerateRequests();
+
+  /**
+   * Perform the named request
+   *
+   * @param requestName a <code>String</code> value
+   * @exception IllegalArgumentException if an error occurs
+   */
+  void performRequest(String requestName);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/Visible.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/Visible.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/Visible.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Visible.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Interface to something that has a visible (via BeanVisual) reprentation
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ */
+public interface Visible {
+
+  /**
+   * Use the default visual representation
+   */
+  void useDefaultVisual();
+
+  /**
+   * Set a new visual representation
+   *
+   * @param newVisual a <code>BeanVisual</code> value
+   */
+  void setVisual(BeanVisual newVisual);
+
+  /**
+   * Get the visual representation
+   *
+   * @return a <code>BeanVisual</code> value
+   */
+  BeanVisual getVisual();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorEvent.java	(revision 29)
@@ -0,0 +1,58 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizableErrorEvent.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import weka.gui.visualize.PlotData2D;
+
+import java.util.EventObject;
+
+/**
+ * Event encapsulating error information for a learning scheme
+ * that can be visualized in the DataVisualizer
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.3 $
+ * @see EventObject
+ */
+public class VisualizableErrorEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5811819270887223400L;
+
+  private PlotData2D m_dataSet;
+
+  public VisualizableErrorEvent(Object source, PlotData2D dataSet) {
+    super(source);
+    m_dataSet = dataSet;
+  }
+  
+  /**
+   * Return the instances of the data set
+   *
+   * @return an <code>Instances</code> value
+   */
+  public PlotData2D getDataSet() {
+    return m_dataSet;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/VisualizableErrorListener.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizableErrorListener.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+import java.util.EventListener;
+
+/**
+ * Interface to something that can accept VisualizableErrorEvents
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.2 $
+ * @since 1.0
+ * @see EventListener
+ */
+public interface VisualizableErrorListener extends EventListener {
+  void acceptDataSet(VisualizableErrorEvent e);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/beans/WekaWrapper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/WekaWrapper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/WekaWrapper.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    WekaWrapper.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.beans;
+
+/**
+ * Interface to something that can wrap around a class of Weka
+ * algorithms (classifiers, filters etc). Typically implemented
+ * by a bean for handling classes of Weka algorithms. 
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ * @since 1.0
+ */
+public interface WekaWrapper {
+
+  /**
+   * Set the algorithm.
+   *
+   * @param algorithm an <code>Object</code> value
+   * @exception IllegalArgumentException if the supplied object is
+   * not of the class of algorithms handled by this wrapper.
+   */
+  void setWrappedAlgorithm(Object algorithm);
+
+  /**
+   * Get the algorithm
+   *
+   * @return an <code>Object</code> value
+   */
+  Object getWrappedAlgorithm();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/beans/xml/XMLBeans.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/beans/xml/XMLBeans.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/beans/xml/XMLBeans.java	(revision 29)
@@ -0,0 +1,2055 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * XMLBeans.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.beans.xml;
+
+import weka.core.converters.ConverterUtils;
+import weka.core.xml.XMLBasicSerialization;
+import weka.core.xml.XMLDocument;
+import weka.core.Environment;
+import weka.core.EnvironmentHandler;
+import weka.gui.beans.BeanConnection;
+import weka.gui.beans.BeanInstance;
+import weka.gui.beans.BeanVisual;
+import weka.gui.beans.MetaBean;
+import weka.gui.beans.Visible;
+import weka.gui.beans.BeanCommon;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Point;
+import java.beans.BeanInfo;
+import java.beans.EventSetDescriptor;
+import java.beans.Introspector;
+import java.beans.beancontext.BeanContextSupport;
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.FontUIResource;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class serializes and deserializes a KnowledgeFlow setup to and fro XML.
+ * <br>
+ * 
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5235 $
+ */
+public class XMLBeans 
+  extends XMLBasicSerialization {
+
+  /** the value of the id property */
+  public final static String VAL_ID = "id";
+
+  /** the value of the x property */
+  public final static String VAL_X = "x";
+
+  /** the value of the y property */
+  public final static String VAL_Y = "y";
+
+  /** the value of the bean property */
+  public final static String VAL_BEAN = "bean";
+
+  /** the value of the customName property */
+  public final static String VAL_CUSTOM_NAME = "custom_name";
+ 
+  /** the value of the source property */
+  public final static String VAL_SOURCEID = "source_id";
+ 
+  /** the value of the target property */
+  public final static String VAL_TARGETID = "target_id";
+  
+  /** the value of the eventname property */
+  public final static String VAL_EVENTNAME = "eventname";
+  
+  /** the value of the hidden property */
+  public final static String VAL_HIDDEN = "hidden";
+  
+  /** the value of the file property */
+  public final static String VAL_FILE = "file";
+  
+  /** the value of the dir property */
+  public final static String VAL_DIR = "dir";
+  
+  /** the value of the prefix property */
+  public final static String VAL_PREFIX = "prefix";
+
+  public final static String VAL_RELATIVE_PATH = "useRelativePath";
+  
+  /** the value of the options property */
+  public final static String VAL_OPTIONS = "options";
+  
+  /** the value of the saver property */
+  public final static String VAL_SAVER = "saver";
+  
+  /** the value of the loader property */
+  public final static String VAL_LOADER = "loader";
+  
+  /** the value of the text property */
+  public final static String VAL_TEXT = "text";
+  
+  /** the value of the beanContext property */
+  public final static String VAL_BEANCONTEXT = "beanContext";
+
+  /** the value of the width property */
+  public final static String VAL_WIDTH = "width";
+
+  /** the value of the height property */
+  public final static String VAL_HEIGHT = "height";
+
+  /** the value of the red property */
+  public final static String VAL_RED = "red";
+
+  /** the value of the green property */
+  public final static String VAL_GREEN = "green";
+  
+  /** the value of the blue property */
+  public final static String VAL_BLUE = "blue";
+
+  /** the value of the value property */
+  public final static String VAL_NAME = "name";
+
+  /** the value of the style property */
+  public final static String VAL_STYLE = "style";
+ 
+  /** the value of the location property */
+  public final static String VAL_LOCATION = "location";
+ 
+  /** the value of the size property */
+  public final static String VAL_SIZE = "size";
+ 
+  /** the value of the color property */
+  public final static String VAL_COLOR = "color";
+  
+  /** the value of the font property */
+  public final static String VAL_FONT = "font";
+  
+  /** the value of the iconpath property */
+  public final static String VAL_ICONPATH = "iconPath";
+  
+  /** the value of the animatedIconPath property */
+  public final static String VAL_ANIMATEDICONPATH = "animatedIconPath";
+  
+  /** the value of the associatedConnections property */
+  public final static String VAL_ASSOCIATEDCONNECTIONS = "associatedConnections";
+  
+  /** the value of the input property */
+  public final static String VAL_INPUTS = "inputs";
+  
+  /** the value of the input id property */
+  public final static String VAL_INPUTSID = "inputs_id";
+  
+  /** the value of the outputs id property */
+  public final static String VAL_OUTPUTS = "outputs";
+  
+  /** the value of the outputs property */
+  public final static String VAL_OUTPUTSID = "outputs_id";
+  
+  /** the value of the subFlow property */
+  public final static String VAL_SUBFLOW = "subFlow";
+  
+  /** the value of the originalCoords property */
+  public final static String VAL_ORIGINALCOORDS = "originalCoords";
+  
+  /** the value of the relationNameForFilename property (Saver) */
+  public final static String VAL_RELATIONNAMEFORFILENAME = "relationNameForFilename";
+
+  /** the index in the Vector, where the BeanInstances are stored 
+   * (Instances and Connections are stored in a Vector and then serialized) */
+  public final static int INDEX_BEANINSTANCES = 0;
+
+  /** the index in the Vector, where the BeanConnections are stored
+  * (Instances and Connections are stored in a Vector and then serialized) */
+  public final static int INDEX_BEANCONNECTIONS = 1;
+  
+  /** the component that manages the layout of the beans */
+  protected JComponent m_BeanLayout;
+  
+  /** keeps track of the BeanInstances read so far, used for the BeanConnections */
+  protected Vector m_BeanInstances;
+  
+  /** keeps track of the BeanInstances read so far, used for the BeanConnections */
+  protected Vector m_BeanInstancesID;
+
+  /** whether to ignore the BeanConnection */
+  protected boolean m_IgnoreBeanConnections;
+  
+  /** the current MetaBean (for the BeanConnections) */
+  protected MetaBean m_CurrentMetaBean;
+
+  /** the identifier for regular BeanConnections */
+  protected final static String REGULAR_CONNECTION = "regular_connection";
+  
+  /** the relation between Bean and connection, MetaBean BeanConnections
+   * are stored under the reference of the MetaBean, regular connections 
+   * are stored under REGULAR_CONNECTION. The relation has the following 
+   * format (is a string): sourcePos,targetPos,event,hidden
+   * @see #REGULAR_CONNECTION */
+  protected Hashtable m_BeanConnectionRelation;
+  
+  /** the data that is about to be read/written contains a complete layout 
+   * @see #m_DataType */
+  public final static int DATATYPE_LAYOUT = 0;
+  
+  /** the data that is about to be read/written contains user-components, i.e., 
+   * Metabeans 
+   * @see #m_DataType */
+  public final static int DATATYPE_USERCOMPONENTS = 1;
+  
+  /** the type of data that is be read/written
+   * @see #DATATYPE_LAYOUT
+   * @see #DATATYPE_USERCOMPONENTS */
+  protected int m_DataType = DATATYPE_LAYOUT;
+  
+  /** the beancontext to use for loading from XML and the beancontext is
+   * null in the bean */
+  protected BeanContextSupport m_BeanContextSupport = null;
+  
+  /**
+   * initializes the serialization for layouts
+   * 
+   * @param layout      the component that manages the layout
+   * @param context     the bean context support to use
+   * @throws Exception  if initialization fails
+   */
+  public XMLBeans(JComponent layout, BeanContextSupport context) throws Exception {
+    this(layout, context, DATATYPE_LAYOUT);
+  }
+  
+  /**
+   * initializes the serialization for different types of data
+   * 
+   * @param layout      the component that manages the layout
+   * @param context     the bean context support to use
+   * @param datatype    the type of data to read/write
+   * @throws Exception  if initialization fails
+   */
+  public XMLBeans(JComponent layout, BeanContextSupport context, int datatype) throws Exception {
+    super();
+    
+    m_BeanLayout = layout;
+    m_BeanContextSupport = context;
+    setDataType(datatype);
+  }
+  
+  /**
+   * sets what kind of data is to be read/written
+   * @param value       the type of data
+   * @see #m_DataType
+   */
+  public void setDataType(int value) {
+    if (value == DATATYPE_LAYOUT)
+      m_DataType = value;
+    else if (value == DATATYPE_USERCOMPONENTS)
+      m_DataType = value;
+    else
+      System.out.println("DataType '" + value + "' is unknown!");
+  }
+  
+  /**
+   * returns the type of data that is to be read/written
+   * @return the type of data
+   * @see #m_DataType
+   */
+  public int getDataType() {
+    return m_DataType;
+  }
+
+  /**
+   * generates internally a new XML document and clears also the IgnoreList and
+   * the mappings for the Read/Write-Methods
+   * 
+   * @throws Exception if something goes wrong
+   */
+  public void clear() throws Exception {
+    Vector<String>	classnames;
+    int			i;
+    
+    super.clear();
+    
+    // ignore: suppress unnecessary GUI stuff 
+    // needs to be checked for new Java versions (might introduce new properties) 
+    // - works with Java 1.5
+    m_Properties.addIgnored("UI");
+    m_Properties.addIgnored("actionMap");
+    m_Properties.addIgnored("alignmentX");
+    m_Properties.addIgnored("alignmentY");
+    m_Properties.addIgnored("autoscrolls");
+    m_Properties.addIgnored("background");
+    m_Properties.addIgnored("border");
+    m_Properties.addIgnored("componentPopupMenu");
+    m_Properties.addIgnored("debugGraphicsOptions");
+    m_Properties.addIgnored("doubleBuffered");
+    m_Properties.addIgnored("enabled");
+    m_Properties.addIgnored("focusCycleRoot");
+    m_Properties.addIgnored("focusTraversalPolicy");
+    m_Properties.addIgnored("focusTraversalPolicyProvider");
+    m_Properties.addIgnored("focusable");
+    m_Properties.addIgnored("font");
+    m_Properties.addIgnored("foreground");
+    m_Properties.addIgnored("inheritsPopupMenu");
+    m_Properties.addIgnored("inputVerifier");
+    m_Properties.addIgnored("layout");
+    m_Properties.addIgnored("locale");
+    m_Properties.addIgnored("maximumSize");
+    m_Properties.addIgnored("minimumSize");
+    m_Properties.addIgnored("nextFocusableComponent");
+    m_Properties.addIgnored("opaque");
+    m_Properties.addIgnored("preferredSize");
+    m_Properties.addIgnored("requestFocusEnabled");
+    m_Properties.addIgnored("toolTipText");
+    m_Properties.addIgnored("transferHandler");
+    m_Properties.addIgnored("verifyInputWhenFocusTarget");
+    m_Properties.addIgnored("visible");
+
+    // special ignore
+    m_Properties.addIgnored("size");  // otherwise you get an endless loop with Dimension!
+    m_Properties.addIgnored("location");  // otherwise you get an endless loop with Point!
+
+    // allow
+    m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "x");
+    m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "y");
+    m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "bean");
+    m_Properties.addAllowed(weka.gui.beans.Saver.class, "saver");
+    m_Properties.addAllowed(weka.gui.beans.Loader.class, "loader");
+    m_Properties.addAllowed(weka.gui.beans.Saver.class, "relationNameForFilename");
+    if (getDataType() == DATATYPE_LAYOUT)
+      m_Properties.addAllowed(weka.gui.beans.Loader.class, "beanContext");
+    else
+      m_Properties.addIgnored(weka.gui.beans.Loader.class, "beanContext");   // TODO: more classes???
+    m_Properties.addAllowed(weka.gui.beans.Filter.class, "filter");
+    m_Properties.addAllowed(weka.gui.beans.Classifier.class, "wrappedAlgorithm");
+    m_Properties.addAllowed(weka.gui.beans.Clusterer.class, "wrappedAlgorithm");
+    m_Properties.addAllowed(weka.gui.beans.Classifier.class, "executionSlots");
+    m_Properties.addAllowed(weka.gui.beans.Classifier.class, "blockOnLastFold");
+
+    m_Properties.addAllowed(weka.classifiers.Classifier.class, "debug");
+    m_Properties.addAllowed(weka.classifiers.Classifier.class, "options");
+    m_Properties.addAllowed(weka.filters.Filter.class, "options");
+    
+    m_Properties.addAllowed(weka.core.converters.DatabaseSaver.class, "options");
+    m_Properties.addAllowed(weka.core.converters.DatabaseLoader.class, "options");
+    m_Properties.addAllowed(weka.core.converters.TextDirectoryLoader.class, "options");
+
+    // we assume that classes implementing SplitEvaluator also implement OptionHandler
+    m_Properties.addAllowed(weka.experiment.SplitEvaluator.class, "options");
+    // we assume that classes implementing ResultProducer also implement OptionHandler
+    m_Properties.addAllowed(weka.experiment.ResultProducer.class, "options");
+
+    // read/write methods
+    m_CustomMethods.register(this, Color.class, "Color");
+    m_CustomMethods.register(this, Dimension.class, "Dimension");
+    m_CustomMethods.register(this, Font.class, "Font");
+    m_CustomMethods.register(this, Point.class, "Point");
+    m_CustomMethods.register(this, ColorUIResource.class, "ColorUIResource");
+    m_CustomMethods.register(this, FontUIResource.class, "FontUIResource");
+
+    m_CustomMethods.register(this, weka.gui.beans.BeanInstance.class, "BeanInstance");
+    m_CustomMethods.register(this, weka.gui.beans.BeanConnection.class, "BeanConnection");
+    m_CustomMethods.register(this, weka.gui.beans.BeanVisual.class, "BeanVisual");
+    m_CustomMethods.register(this, weka.gui.beans.Saver.class, "BeanSaver");
+    m_CustomMethods.register(this, weka.gui.beans.MetaBean.class, "MetaBean");
+
+    classnames = ConverterUtils.getFileLoaders();
+    for (i = 0; i < classnames.size(); i++)
+      m_CustomMethods.register(this, Class.forName(classnames.get(i)), "Loader");
+    classnames = ConverterUtils.getFileSavers();
+    for (i = 0; i < classnames.size(); i++)
+      m_CustomMethods.register(this, Class.forName(classnames.get(i)), "Saver");
+    
+    // other variables
+    m_BeanInstances          = null;
+    m_BeanInstancesID        = null;
+    m_CurrentMetaBean        = null;
+    m_IgnoreBeanConnections  = true;
+    m_BeanConnectionRelation = null;
+  }
+  
+  /**
+   * traverses over all BeanInstances (or MetaBeans) and stores them in a vector 
+   * (recurses into MetaBeans, since the sub-BeanInstances are not visible)
+   * @param list       the BeanInstances/MetaBeans to traverse
+   */
+  protected void addBeanInstances(Vector list) {
+    int             i;
+    BeanInstance    beaninst;
+    
+    for (i = 0; i < list.size(); i++) {
+      if (list.get(i) instanceof BeanInstance) {
+        beaninst = (BeanInstance) list.get(i);
+        
+        m_BeanInstancesID.add(new Integer(m_BeanInstances.size()));
+        m_BeanInstances.add(beaninst);
+        
+        if (beaninst.getBean() instanceof MetaBean)
+          addBeanInstances(((MetaBean) beaninst.getBean()).getBeansInSubFlow());
+      }
+      else if (list.get(i) instanceof MetaBean) {
+        addBeanInstances(((MetaBean) list.get(i)).getBeansInSubFlow());
+      }
+      else {
+        System.out.println("addBeanInstances does not support Vectors of class '" + list.get(i) + "'!");
+      }
+    }
+  }
+  
+  /**
+   * enables derived classes to due some pre-processing on the objects, that's
+   * about to be serialized. Right now it only returns the object.
+   * 
+   * @param o the object that is serialized into XML
+   * @return the possibly altered object
+   * @throws Exception if post-processing fails
+   */
+  protected Object writePreProcess(Object o) throws Exception {
+    o = super.writePreProcess(o);
+    
+    // gather all BeanInstances, also the ones in MetaBeans
+    m_BeanInstances   = new Vector();
+    m_BeanInstancesID = new Vector();
+    
+    switch (getDataType()) {
+      case DATATYPE_LAYOUT:
+        addBeanInstances(BeanInstance.getBeanInstances());
+        break;
+
+      case DATATYPE_USERCOMPONENTS:
+        addBeanInstances((Vector) o);
+        break;
+        
+      default:
+        System.out.println("writePreProcess: data type '" + getDataType() + "' is not recognized!");
+        break;
+    }
+    
+    return o;
+  }
+  
+  /**
+   * enables derived classes to add other properties to the DOM tree, e.g.
+   * ones that do not apply to the get/set convention of beans. only implemented
+   * with empty method body.
+   * 
+   * @param o the object that is serialized into XML
+   * @throws Exception if post-processing fails
+   */
+  protected void writePostProcess(Object o) throws Exception {
+    Element         root;
+    NodeList        list;
+    Element         conns;
+    Element         child;
+    int             i;
+
+    // since not all BeanConnections get saved to XML (e.g., MetaBeans in the 
+    // UserToolBar) if one saves a layout, the numbering in the Vector of the 
+    // BeanConnections is not correct. The "name" attribute of the nodes has
+    // to be modified
+    if (getDataType() == DATATYPE_LAYOUT) {
+      root  = m_Document.getDocument().getDocumentElement();
+      conns = (Element) root.getChildNodes().item(INDEX_BEANCONNECTIONS);
+      list  = conns.getChildNodes();
+      for (i = 0; i < list.getLength(); i++) {
+        child = (Element) list.item(i);
+        child.setAttribute(ATT_NAME, "" + i);
+      }
+    }
+  }
+  
+  /**
+   * additional pre-processing can happen in derived classes before the 
+   * actual reading from XML (working on the raw XML). right now it does 
+   * nothing with the document, only empties the help-vector for the
+   * BeanInstances and reads the IDs for the BeanInstances, s.t. the correct
+   * references can be set again
+   * 
+   * @param document the document to pre-process
+   * @return the processed object
+   * @throws Exception if post-processing fails
+   * @see #m_BeanInstances
+   */
+  protected Document readPreProcess(Document document) throws Exception {
+    NodeList        list;
+    int             i;
+    Element         node;
+    String          clsName;
+    Vector          children;
+    int             id;
+    int             n;
+    Element         child;
+    
+    m_BeanInstances   = new Vector();
+    m_BeanInstancesID = new Vector();
+    
+    // get all BeanInstance nodes
+    list    = document.getElementsByTagName("*");
+    clsName = BeanInstance.class.getName();
+    for (i = 0; i < list.getLength(); i++) {
+      node = (Element) list.item(i);
+      
+      // is it a BeanInstance?
+      if (node.getAttribute(ATT_CLASS).equals(clsName)) {
+        children = XMLDocument.getChildTags(node);
+        id       = m_BeanInstancesID.size();
+        
+        // get id-tag (if available)
+        for (n = 0; n < children.size(); n++) {
+          child = (Element) children.get(n);
+          if (child.getAttribute(ATT_NAME).equals(VAL_ID))
+            id = readIntFromXML((Element) child);
+        }
+        
+        m_BeanInstancesID.add(new Integer(id));
+      }
+    }
+    
+    m_BeanInstances.setSize(m_BeanInstancesID.size());
+    
+    // set MetaBean to null
+    m_CurrentMetaBean = null;
+
+    // no BeanConnections -> see readPostProcess(Object)
+    m_IgnoreBeanConnections = true;
+    
+    // reset BeanConnection-Relations
+    m_BeanConnectionRelation = new Hashtable();
+    
+    return document;
+  }
+  
+  /**
+   * puts the given BeanConnection onto the next null in the given Vector,
+   * or at the end of the list, if no null is found.
+   * (during the de-serializing, no BeanConnections are set, only nulls)
+   * @param conn      the connection to add to the list
+   * @param list      the list to add the BeanConnection to
+   */
+  protected void setBeanConnection(BeanConnection conn, Vector list) {
+    int         i;
+    boolean     added;
+    
+    added = false;
+    for (i = 0; i < list.size(); i++) {
+      if (list.get(i) == null) {
+        list.set(i, conn);
+        added = true;
+        break;
+      }
+    }
+    
+    if (!added)
+      list.add(conn);
+  }
+  
+  /**
+   * generates a connection based on the given parameters
+   * @param sourcePos the source position in the m_BeanInstances vector
+   * @param targetPos the target position in the m_BeanInstances vector
+   * @param event the name of the event, i.e., the connection
+   * @param hidden true if the connection is hidden
+   * @return the generated BeanConnection
+   * @throws Exception if something goes wrong
+   */
+  protected BeanConnection createBeanConnection(int sourcePos, int targetPos, String event, boolean hidden) throws Exception {
+    BeanConnection          result;
+    BeanInfo                compInfo;
+    EventSetDescriptor[]    esds;
+    int                     i;
+    BeanInstance            instSource;
+    BeanInstance            instTarget;
+
+    result = null;
+    
+    // was there a connection?
+    if ( (sourcePos == -1) || (targetPos == -1) )
+      return result;
+    
+    instSource = (BeanInstance) m_BeanInstances.get(sourcePos);
+    instTarget = (BeanInstance) m_BeanInstances.get(targetPos);
+    
+    compInfo = Introspector.getBeanInfo(((BeanInstance) m_BeanInstances.get(sourcePos)).getBean().getClass());
+    esds     = compInfo.getEventSetDescriptors();
+
+    for (i = 0; i < esds.length; i++) {
+      if (esds[i].getName().equals(event)) {
+        result = new BeanConnection(instSource, instTarget, esds[i]);
+        ((BeanConnection) result).setHidden(hidden);
+        break;
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * rebuilds all the connections for a certain key in the hashtable.
+   * for the ones being part of a MetaBean, no new instance is built, but only
+   * the reference to the actual BeanConnection set.
+   * @param deserialized    the deserialized knowledgeflow
+   * @param key             the key of the hashtable to rebuild all connections for
+   * @throws Exception if something goes wrong
+   */
+  protected void rebuildBeanConnections(Vector deserialized, Object key) throws Exception {
+    int                     i;
+    int                     n;
+    int                     sourcePos;
+    int                     targetPos;
+    String                  event;
+    boolean                 hidden;
+    Vector                  conns;
+    BeanConnection          conn;
+    StringTokenizer         tok;
+    Vector                  beanconns;
+
+    conns = (Vector) m_BeanConnectionRelation.get(key);
+    
+    // no connections?
+    if (conns == null)
+      return;
+    
+    for (n = 0; n < conns.size(); n++) {
+      tok       = new StringTokenizer(conns.get(n).toString(), ",");
+      conn      = null;
+      sourcePos = Integer.parseInt(tok.nextToken());
+      targetPos = Integer.parseInt(tok.nextToken());
+      event     = tok.nextToken();
+      hidden    = stringToBoolean(tok.nextToken());
+
+      // regular connection? -> new instance
+      // or MetaBean from user toolbar
+      if ( (!(key instanceof MetaBean)) || (getDataType() == DATATYPE_USERCOMPONENTS)) {
+        conn = createBeanConnection(sourcePos, targetPos, event, hidden);
+      }
+      // MetaBean? -> find BeanConnection 
+      else {
+        beanconns = BeanConnection.getConnections();
+        
+        for (i = 0; i < beanconns.size(); i++) {
+          conn = (BeanConnection) beanconns.get(i);
+          if (    (conn.getSource() == (BeanInstance) m_BeanInstances.get(sourcePos))
+               && (conn.getTarget() == (BeanInstance) m_BeanInstances.get(targetPos))
+               && (conn.getEventName().equals(event)) ) {
+            break;
+          }
+          conn = null;
+        }
+      }
+      
+      // add the connection to the corresponding list/MetaBean
+      if (key instanceof MetaBean)
+        setBeanConnection(conn, ((MetaBean) key).getAssociatedConnections());
+      else
+        setBeanConnection(conn, (Vector) deserialized.get(INDEX_BEANCONNECTIONS));
+    }
+  }
+  
+  /**
+   * removes the given meta beans from the layout, since they're only listed
+   * in the user toolbar
+   * 
+   * @param metabeans         the list of MetaBeans in the user toolbar
+   */
+  protected void removeUserToolBarBeans(Vector metabeans) {
+    int           i;
+    int           n;
+    MetaBean      meta;
+    Vector        subflow;
+    BeanInstance  beaninst;
+    
+    for (i = 0; i < metabeans.size(); i++) {
+      meta    = (MetaBean) metabeans.get(i);
+      subflow = meta.getSubFlow();
+      
+      for (n = 0; n < subflow.size(); n++) {
+        beaninst = (BeanInstance) subflow.get(n);
+        beaninst.removeBean(m_BeanLayout);
+      }
+    }
+  }
+  
+  /**
+   * additional post-processing can happen in derived classes after reading 
+   * from XML. re-builds the BeanConnections.
+   * 
+   * @param o the object to perform some additional processing on
+   * @return the processed object
+   * @throws Exception if post-processing fails
+   */
+  protected Object readPostProcess(Object o) throws Exception {
+    Enumeration             enm;
+    Vector                  deserialized;
+    Object                  key;
+
+    deserialized = (Vector) super.readPostProcess(o);
+    
+    // rebuild the actual connections
+    rebuildBeanConnections(deserialized, REGULAR_CONNECTION);
+
+    // rebuild the references in the MetaBeans
+    enm = m_BeanConnectionRelation.keys();
+    while (enm.hasMoreElements()) {
+      key = enm.nextElement();
+      
+      // skip the regular connections
+      if (!(key instanceof MetaBean))
+        continue;
+      
+      rebuildBeanConnections(deserialized, key);
+    }
+
+    // remove MetaBean and subflow from BeanInstance (not part of the flow!)
+    if (getDataType() == DATATYPE_USERCOMPONENTS)
+      removeUserToolBarBeans(deserialized);
+    
+    return deserialized;
+  }
+
+  /**
+   * returns the relation for the given MetaBean, for the regular connections,
+   * null has to be used
+   * @param meta      the MetaBean (or null for regular connections) to retrieve
+   *                  the connections for
+   * @return          the associated connections
+   * @see #REGULAR_CONNECTION
+   */
+  protected Vector getBeanConnectionRelation(MetaBean meta) {
+    Vector      result;
+    Object      key;
+    
+    if (meta == null)
+      key = REGULAR_CONNECTION;
+    else
+      key = meta;
+    
+    // not yet in there?
+    if (!m_BeanConnectionRelation.containsKey(key)) {
+      m_BeanConnectionRelation.put(key, new Vector());
+    }
+
+    result = (Vector) m_BeanConnectionRelation.get(key);
+    
+    return result;
+  }
+  
+  /**
+   * adds the given connection-relation for the specified MetaBean (or null in 
+   * case of regular connections)
+   * @param meta        the MetaBean (or null for regular connections) to add
+   *                    the relationship for
+   * @param connection  the connection relation to add
+   */
+  protected void addBeanConnectionRelation(MetaBean meta, String connection) {
+    Vector      relations;
+    Object      key;
+    
+    relations = getBeanConnectionRelation(meta);
+    
+    // add relation
+    relations.add(connection);
+    
+    // update
+    if (meta == null)
+      key = REGULAR_CONNECTION;
+    else
+      key = meta;
+    m_BeanConnectionRelation.put(key, relations);
+  }
+  
+  /**
+   * adds the given Color to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeColor(Element parent, Object o, String name)
+    throws Exception {
+    
+    Element     node;
+    Color       color;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    color = (Color) o;
+    node  = addElement(parent, name, color.getClass().getName(), false);
+
+    writeIntToXML(node, color.getRed(), VAL_RED);
+    writeIntToXML(node, color.getGreen(), VAL_GREEN);
+    writeIntToXML(node, color.getBlue(), VAL_BLUE);
+    
+    return node;
+  }
+
+  /**
+   * builds the Color from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readColor(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    int         red;
+    int         green;
+    int         blue;
+    String      name;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    red      = 0;
+    green    = 0;
+    blue     = 0;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_RED))
+        red = readIntFromXML(child);
+      else if (name.equals(VAL_GREEN))
+        green = readIntFromXML(child);
+      else if (name.equals(VAL_BLUE))
+        blue = readIntFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new Color(red, green, blue);
+
+    return result;
+  }
+
+  /**
+   * adds the given Dimension to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeDimension(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element     node;
+    Dimension   dim;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    dim = (Dimension) o;
+    node = addElement(parent, name, dim.getClass().getName(), false);
+
+    writeDoubleToXML(node, dim.getWidth(), VAL_WIDTH);
+    writeDoubleToXML(node, dim.getHeight(), VAL_HEIGHT);
+    
+    return node;
+  }
+
+  /**
+   * builds the Dimension from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readDimension(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    double      width;
+    double      height;
+    String      name;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    width    = 0;
+    height   = 0;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_WIDTH))
+        width = readDoubleFromXML(child);
+      else if (name.equals(VAL_HEIGHT))
+        height = readDoubleFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new Dimension();
+    ((Dimension) result).setSize(width, height);
+
+    return result;
+  }
+
+  /**
+   * adds the given Font to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeFont(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element     node;
+    Font        font;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    font = (Font) o;
+    node = addElement(parent, name, font.getClass().getName(), false);
+
+    invokeWriteToXML(node, font.getName(), VAL_NAME);
+    writeIntToXML(node, font.getStyle(), VAL_STYLE);
+    writeIntToXML(node, font.getSize(), VAL_SIZE);
+    
+    return node;
+  }
+
+  /**
+   * builds the Font from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readFont(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    int         style;
+    int         size;
+    String      name;
+    String      fontname;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    fontname = "";
+    style    = 0;
+    size     = 0;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_NAME))
+        name = (String) invokeReadFromXML(child);
+      else if (name.equals(VAL_STYLE))
+        style = readIntFromXML(child);
+      else if (name.equals(VAL_SIZE))
+        size = readIntFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new Font(fontname, style, size);
+
+    return result;
+  }
+
+  /**
+   * adds the given Point to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writePoint(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element     node;
+    Point       p;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    p    = (Point) o;
+    node = addElement(parent, name, p.getClass().getName(), false);
+
+    writeDoubleToXML(node, p.getX(), VAL_X);
+    writeDoubleToXML(node, p.getY(), VAL_Y);
+    
+    return node;
+  }
+
+  /**
+   * builds the Point from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readPoint(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    double      x;
+    double      y;
+    String      name;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    x        = 0;
+    y        = 0;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_X))
+        x = readDoubleFromXML(child);
+      else if (name.equals(VAL_Y))
+        y = readDoubleFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new Point();
+    ((Point) result).setLocation(x, y);
+
+    return result;
+  }
+
+  /**
+   * adds the given ColorUIResource to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeColorUIResource(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element           node;
+    ColorUIResource   resource;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    resource = (ColorUIResource) o;
+    node     = addElement(parent, name, resource.getClass().getName(), false);
+    invokeWriteToXML(node, new Color(resource.getRGB()), VAL_COLOR);
+    
+    return node;
+  }
+
+  /**
+   * builds the ColorUIResource from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readColorUIResource(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    String      name;
+    Color       color;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    color    = null;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_COLOR))
+        color = (Color) invokeReadFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new ColorUIResource(color);
+
+    return result;
+  }
+
+  /**
+   * adds the given FontUIResource to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeFontUIResource(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element           node;
+    FontUIResource    resource;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    resource = (FontUIResource) o;
+    node     = addElement(parent, name, resource.getClass().getName(), false);
+    invokeWriteToXML(node, new Font(resource.getName(), resource.getStyle(), resource.getSize()), VAL_COLOR);
+    
+    return node;
+  }
+
+  /**
+   * builds the FontUIResource from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readFontUIResource(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    String      name;
+    Font        font;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    font     = null;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_FONT))
+        font = (Font) invokeReadFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+    
+    result = new FontUIResource(font);
+
+    return result;
+  }
+
+  /**
+   * adds the given BeanInstance to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeBeanInstance(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element         node;
+    BeanInstance    beaninst;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    beaninst = (BeanInstance) o;
+    node     = addElement(parent, name, beaninst.getClass().getName(), false);
+
+    writeIntToXML(node, m_BeanInstances.indexOf(beaninst), VAL_ID);
+    writeIntToXML(node, beaninst.getX() + beaninst.getWidth()  / 2, VAL_X);   // x is thought to be in the center?
+    writeIntToXML(node, beaninst.getY() + beaninst.getHeight() / 2, VAL_Y);   // y is thought to be in the center?
+    if (beaninst.getBean() instanceof BeanCommon) {
+      // write the custom name of this bean
+      String custName = ((BeanCommon)beaninst.getBean()).getCustomName();
+      invokeWriteToXML(node, custName, VAL_CUSTOM_NAME);
+    }
+    invokeWriteToXML(node, beaninst.getBean(), VAL_BEAN);
+    
+    return node;
+  }
+
+  /**
+   * builds the BeanInstance from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readBeanInstance(Element node) throws Exception {
+    Object          result;
+    Vector          children;
+    Element         child;
+    String          name;
+    int             i;
+    int             x;
+    int             y;
+    int             id;
+    Object          bean;
+    BeanVisual      visual;
+    BeanInstance    beaninst;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    id       = -1;
+    x        = 0;
+    y        = 0;
+    bean     = null;
+    String customName = null;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_ID)) {
+        id = readIntFromXML(child);
+      } else if (name.equals(VAL_X)) {
+        x = readIntFromXML(child);
+      } else if (name.equals(VAL_Y)) {
+        y = readIntFromXML(child);
+      } else if (name.equals(VAL_CUSTOM_NAME)) {
+        customName = (String)invokeReadFromXML(child);
+      } else if (name.equals(VAL_BEAN)) {
+        bean = invokeReadFromXML(child);
+      } else {
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+      }
+    }
+    
+    result   = new BeanInstance(m_BeanLayout, bean, x, y);
+    beaninst = (BeanInstance) result;
+    
+    // set parent of BeanVisual
+    if (beaninst.getBean() instanceof weka.gui.beans.Visible) {
+      visual = ((Visible) beaninst.getBean()).getVisual();
+      visual.setSize(visual.getPreferredSize());
+      if (visual.getParent() == null) {
+        ((JPanel) beaninst.getBean()).add(visual);
+      }
+    }
+
+    if (beaninst.getBean() instanceof BeanCommon &&
+        customName != null) {
+      ((BeanCommon)beaninst.getBean()).setCustomName(customName);
+    }
+    
+    // no IDs -> get next null position
+    if (id == -1) {
+      for (i = 0; i < m_BeanInstances.size(); i++) {
+        if (m_BeanInstances.get(i) == null) {
+          id = ((Integer) m_BeanInstancesID.get(i)).intValue();
+          break;
+        }
+      }
+    }
+    // get position for id
+    i = m_BeanInstancesID.indexOf(new Integer(id));
+
+    // keep track of the BeanInstances for reading the connections later on
+    m_BeanInstances.set(i, result);
+
+    // no current MetaBean
+    m_CurrentMetaBean = null;
+    
+    return result;
+  }
+
+  /**
+   * adds the given BeanConncetion to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeBeanConnection(Element parent, Object o, String name)
+    throws Exception {
+    
+    Element           node;
+    BeanConnection    beanconn;
+    int               source;
+    int               target;
+    int               sourcePos;
+    int               targetPos;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    beanconn = (BeanConnection) o;
+    node     = null;
+
+    // get position
+    sourcePos = m_BeanInstances.indexOf(beanconn.getSource());
+    targetPos = m_BeanInstances.indexOf(beanconn.getTarget());
+    
+    // get id (if Connection is from a Bean in the UserToolBar, it's not listed! -> ignore it)
+    if ( (sourcePos > -1) && (targetPos > -1) ) {
+      source = ((Integer) m_BeanInstancesID.get(sourcePos)).intValue();
+      target = ((Integer) m_BeanInstancesID.get(targetPos)).intValue();
+    }
+    else {
+       source = -1;
+       target = -1;
+    }
+    
+    // connection exists in the layout?
+    if ( (source > -1) && (target > -1) ) {
+      node = addElement(parent, name, beanconn.getClass().getName(), false);
+  
+      writeIntToXML(node, source, VAL_SOURCEID);
+      writeIntToXML(node, target, VAL_TARGETID);
+      invokeWriteToXML(node, beanconn.getEventName(), VAL_EVENTNAME);
+      writeBooleanToXML(node, beanconn.isHidden(), VAL_HIDDEN);
+    }
+    
+    return node;
+  }
+
+  /**
+   * builds the BeanConnection from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readBeanConnection(Element node) throws Exception {
+    Object                  result;
+    Vector                  children;
+    Element                 child;
+    String                  name;
+    int                     i;
+    int                     source;
+    int                     target;
+    int                     sourcePos;
+    int                     targetPos;
+    String                  event;
+    boolean                 hidden;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = null;
+    children = XMLDocument.getChildTags(node);
+    source   = 0;
+    target   = 0;
+    event    = "";
+    hidden   = false;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_SOURCEID))
+        source = readIntFromXML(child);
+      else if (name.equals(VAL_TARGETID))
+        target = readIntFromXML(child);
+      else if (name.equals(VAL_EVENTNAME))
+        event = (String) invokeReadFromXML(child);
+      else if (name.equals(VAL_HIDDEN))
+        hidden = readBooleanFromXML(child);
+      else
+        System.out.println("WARNING: '" + name
+            + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
+    }
+
+    // get position of id
+    sourcePos = m_BeanInstancesID.indexOf(new Integer(source));
+    targetPos = m_BeanInstancesID.indexOf(new Integer(target));
+    
+    // do we currently ignore the connections?
+    // Note: necessary because of the MetaBeans
+    if (m_IgnoreBeanConnections) {
+      addBeanConnectionRelation(m_CurrentMetaBean, sourcePos + "," + targetPos + "," + event + "," + hidden);
+      return result;
+    }
+
+    // generate it normally
+    result = createBeanConnection(sourcePos, targetPos, event, hidden);
+
+    return result;
+  }
+  
+  /**
+   * adds the given Loader (a bean) to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeBeanLoader(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element                 node;
+    weka.gui.beans.Loader   loader;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    loader = (weka.gui.beans.Loader) o;
+    node   = addElement(parent, name, loader.getClass().getName(), false);
+
+    invokeWriteToXML(node, loader.getLoader(), VAL_LOADER);
+    invokeWriteToXML(node, loader.getBeanContext(), VAL_BEANCONTEXT);
+    
+    return node;
+  }
+
+  /**
+   * adds the given Saver (a bean) to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeBeanSaver(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element                 node;
+    weka.gui.beans.Saver    saver;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    saver = (weka.gui.beans.Saver) o;
+    node   = addElement(parent, name, saver.getClass().getName(), false);
+    invokeWriteToXML(node, saver.getRelationNameForFilename(), VAL_RELATIONNAMEFORFILENAME);
+
+    invokeWriteToXML(node, saver.getSaver(), VAL_SAVER);
+    
+    return node;
+  }
+  
+  /**
+   * adds the given Loader to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeLoader(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element                       node;
+    weka.core.converters.Loader   loader;
+    File                          file;
+    boolean                       known;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    loader = (weka.core.converters.Loader) o;
+    node   = addElement(parent, name, loader.getClass().getName(), false);
+    known  = true;
+    file   = null;
+
+    // file
+    if (loader instanceof weka.core.converters.AbstractFileLoader)
+      file = ((weka.core.converters.AbstractFileLoader) loader).retrieveFile();
+    else
+      known = false;
+
+    if (!known)
+      System.out.println("WARNING: unknown loader class '" + loader.getClass().getName() + "' - cannot retrieve file!");
+
+    Boolean relativeB = null;
+    if (loader instanceof weka.core.converters.FileSourcedConverter) {
+      boolean relative = ((weka.core.converters.FileSourcedConverter)loader).getUseRelativePath();
+      relativeB = new Boolean(relative);
+    }
+    
+    // only save it, if it's a real file!
+    if ( (file == null) || (file.isDirectory()) ) {
+      invokeWriteToXML(node, "", VAL_FILE);
+    } else {
+      boolean notAbsolute = 
+        (((weka.core.converters.AbstractFileLoader) loader).getUseRelativePath() ||
+        (loader instanceof EnvironmentHandler 
+            && Environment.containsEnvVariables(file.getPath())));
+      
+      String path = (notAbsolute)
+        ? file.getPath()
+        : file.getAbsolutePath();
+      // Replace any windows file separators with forward slashes (Java under windows can
+      // read paths with forward slashes (apparantly)
+      path = path.replace('\\', '/');
+      invokeWriteToXML(node, path, VAL_FILE);
+    }
+    if (relativeB != null) {
+      invokeWriteToXML(node, relativeB.toString(), VAL_RELATIVE_PATH);
+    }
+    
+    return node;
+  }
+
+  /**
+   * builds the Loader from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readLoader(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    String      name;
+    String      file;
+    File        fl;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = Class.forName(node.getAttribute(ATT_CLASS)).newInstance();
+    children = XMLDocument.getChildTags(node);
+    file     = "";
+    Object relativeB = null;
+    boolean relative = false;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_FILE)) {
+        file = (String) invokeReadFromXML(child);
+      } else if (name.equals(VAL_RELATIVE_PATH)) {
+        relativeB = readFromXML(child);
+        if (relativeB instanceof Boolean) {
+          relative = ((Boolean)relativeB).booleanValue();
+        }
+      } else {
+        readFromXML(result, name, child);
+      }
+    }
+
+    if (result instanceof weka.core.converters.FileSourcedConverter) {
+      ((weka.core.converters.FileSourcedConverter)result).setUseRelativePath(relative);
+    }
+
+    if (file.equals(""))
+      file = null;
+
+    // set file only, if it exists
+    if (file != null) {
+      String tempFile = file;
+
+      boolean containsEnv = false;
+      containsEnv = Environment.containsEnvVariables(file);
+      
+      fl = new File(file);      
+      // only test for existence if the path does not contain environment vars
+      // (trust that after they are resolved that everything is hunky dory)
+      if (containsEnv || fl.exists()) {
+        ((weka.core.converters.AbstractFileLoader) result).setSource(new File(file));
+      } else {
+        System.out.println("WARNING: The file '" + tempFile + "' does not exist!");
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * adds the given Saver to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeSaver(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element                     node;
+    weka.core.converters.Saver  saver;
+    File                        file;
+    String			prefix;
+    String			dir;
+    boolean                     known;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    saver  = (weka.core.converters.Saver) o;
+    node   = addElement(parent, name, saver.getClass().getName(), false);
+    known  = true;
+    file   = null;
+    prefix = "";
+    dir    = "";
+
+    // file
+    if (saver instanceof weka.core.converters.AbstractFileSaver) {
+      file   = ((weka.core.converters.AbstractFileSaver) saver).retrieveFile();
+      prefix = ((weka.core.converters.AbstractFileSaver) saver).filePrefix();
+      dir    = ((weka.core.converters.AbstractFileSaver) saver).retrieveDir();
+      // Replace any windows file separators with forward slashes (Java under windows can
+      // read paths with forward slashes (apparantly)
+      dir = dir.replace('\\', '/');
+    }
+    else {
+      known = false;
+    }
+    
+    if (!known)
+      System.out.println("WARNING: unknown saver class '" + saver.getClass().getName() + "' - cannot retrieve file!");
+
+    Boolean relativeB = null;
+    if (saver instanceof weka.core.converters.FileSourcedConverter) {
+      boolean relative = ((weka.core.converters.FileSourcedConverter)saver).getUseRelativePath();
+      relativeB = new Boolean(relative);
+    }
+    
+
+//    if ( (file == null) || (file.isDirectory()) ) {
+      invokeWriteToXML(node, "",     VAL_FILE);
+      invokeWriteToXML(node, dir,    VAL_DIR);
+      invokeWriteToXML(node, prefix, VAL_PREFIX);
+/*    }
+    else {
+      String path = (((weka.core.converters.FileSourcedConverter) saver).getUseRelativePath())
+        ? file.getPath()
+        : file.getAbsolutePath();
+      // Replace any windows file separators with forward slashes (Java under windows can
+      // read paths with forward slashes (apparantly)
+      path = path.replace('\\', '/');
+      invokeWriteToXML(node, path, VAL_FILE);
+      invokeWriteToXML(node, dir, VAL_DIR);
+      invokeWriteToXML(node, prefix, VAL_PREFIX);
+    }*/
+
+    if (relativeB != null) {
+      invokeWriteToXML(node, relativeB.toString(), VAL_RELATIVE_PATH);
+    }
+    
+    return node;
+  }
+
+  /**
+   * builds the Saver from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readSaver(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    String      name;
+    String      file;
+    String	dir;
+    String	prefix;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = Class.forName(node.getAttribute(ATT_CLASS)).newInstance();
+    children = XMLDocument.getChildTags(node);
+    file     = null;
+    dir      = null;
+    prefix   = null;
+
+    Object relativeB = null;
+    boolean relative = false;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_FILE)) {
+        file = (String) invokeReadFromXML(child);
+      } else if (name.equals(VAL_DIR)) {
+        dir = (String) invokeReadFromXML(child);
+      } else if (name.equals(VAL_PREFIX)) {
+        prefix = (String) invokeReadFromXML(child);
+      } else if (name.equals(VAL_RELATIVE_PATH)) {
+        relativeB = readFromXML(child);
+        if (relativeB instanceof Boolean) {
+          relative = ((Boolean)relativeB).booleanValue();
+        }
+      } else {
+        readFromXML(result, name, child);
+      }
+    }
+
+    if ( (file != null) && (file.length() == 0) )
+      file = null;
+
+    // savers only get directory and prefix, not file (KnowledgeFlow sets the
+    // file/destination based on the relation, dir and prefix)
+    if ( (dir != null) && (prefix != null) ) {
+      ((weka.core.converters.AbstractFileSaver) result).setDir(dir);
+      ((weka.core.converters.AbstractFileSaver) result).setFilePrefix(prefix);
+    }
+
+    if (result instanceof weka.core.converters.FileSourcedConverter) {
+      ((weka.core.converters.FileSourcedConverter)result).setUseRelativePath(relative);
+    }
+    
+    return result;
+  }
+
+  /**
+   * adds the given BeanVisual to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeBeanVisual(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element         node;
+    BeanVisual      visual;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    visual = (BeanVisual) o;
+    node   = writeToXML(parent, o, name);
+
+    // add icon paths
+    invokeWriteToXML(node, visual.getIconPath(),         VAL_ICONPATH);
+    invokeWriteToXML(node, visual.getAnimatedIconPath(), VAL_ANIMATEDICONPATH);
+    
+    return node;
+  }
+
+  /**
+   * builds the BeanVisual from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readBeanVisual(Element node) throws Exception {
+    Object      result;
+    Vector      children;
+    Element     child;
+    int         i;
+    String      name;
+    String      text;
+    String      iconPath;
+    String      animIconPath;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result       = null;
+    children     = XMLDocument.getChildTags(node);
+    text         = "";
+    iconPath     = "";
+    animIconPath = "";
+
+    // find text
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_TEXT))
+        text = (String) invokeReadFromXML(child);
+      else if (name.equals(VAL_ICONPATH))
+        iconPath = (String) invokeReadFromXML(child);
+      else if (name.equals(VAL_ANIMATEDICONPATH))
+        animIconPath = (String) invokeReadFromXML(child);
+    }
+
+    result = new BeanVisual(text, iconPath, animIconPath);
+    
+    // set rest of properties
+    for (i = 0; i < children.size(); i++)
+      readFromXML(result, node.getAttribute(ATT_NAME), (Element) children.get(i));
+    
+    return result;
+  }
+  
+  /**
+   * returns the IDs for the given BeanInstances, i.e., the stored IDs
+   * in m_BeanInstancesID, based on m_BeanInstances
+   * 
+   * @param beans       the beans to retrieve the IDs for
+   * @return            the IDs for the given BeanInstances
+   * @see #m_BeanInstances
+   * @see #m_BeanInstancesID
+   */
+  protected Vector getIDsForBeanInstances(Vector beans) {
+    Vector        result;
+    int           i;
+    int           pos;
+    
+    result = new Vector();
+
+    for (i = 0; i < beans.size(); i++) {
+      pos = m_BeanInstances.indexOf(beans.get(i));
+      result.add(m_BeanInstancesID.get(pos));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * adds the given MetaBean to a DOM structure.
+   * 
+   * @param parent the parent of this object, e.g. the class this object is a member of
+   * @param o the Object to describe in XML
+   * @param name the name of the object
+   * @return the node that was created
+   * @throws Exception if the DOM creation fails
+   */
+  public Element writeMetaBean(Element parent, Object o, String name)
+      throws Exception {
+    
+    Element         node;
+    MetaBean        meta;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), name);
+    
+    m_CurrentNode = parent;
+    
+    meta = (MetaBean) o;
+    node = writeToXML(parent, o, name);
+
+    invokeWriteToXML(node, getIDsForBeanInstances(meta.getBeansInInputs()), VAL_INPUTSID);
+    invokeWriteToXML(node, getIDsForBeanInstances(meta.getBeansInOutputs()), VAL_OUTPUTSID);
+    
+    return node;
+  }
+  
+  /**
+   * returns a vector with references to BeanInstances according to the IDs
+   * in the given Vector.
+   * @param ids       contains the IDs of the BeanInstances
+   * @return          the corresponding BeanInstances
+   * @see #m_BeanInstances
+   * @see #m_BeanInstancesID
+   */
+  protected Vector getBeanInstancesForIDs(Vector ids) {
+    Vector        result;
+    int           i;
+    int           pos;
+    
+    result = new Vector();
+    
+    for (i = 0; i < ids.size(); i++) {
+      pos = m_BeanInstancesID.indexOf(ids.get(i));
+      result.add(m_BeanInstances.get(pos));
+    }
+    
+    return result;
+  }
+
+  /**
+   * builds the MetaBean from the given DOM node.
+   * 
+   * @param node the associated XML node
+   * @return the instance created from the XML description
+   * @throws Exception if instantiation fails
+   */
+  public Object readMetaBean(Element node) throws Exception {
+    Object          result;
+    Vector          children;
+    Element         child;
+    int             i;
+    String          name;
+    Vector          inputs;
+    Vector          outputs;
+    Vector          coords;
+    MetaBean        bean;
+
+    // for debugging only
+    if (DEBUG)
+       trace(new Throwable(), node.getAttribute(ATT_NAME));
+
+    m_CurrentNode = node;
+    
+    result   = new MetaBean();
+    children = XMLDocument.getChildTags(node);
+    inputs   = new Vector();
+    outputs  = new Vector();
+    coords   = new Vector();
+    
+    // the current MetaBean
+    m_CurrentMetaBean = (MetaBean) result;
+
+    for (i = 0; i < children.size(); i++) {
+      child = (Element) children.get(i);
+      name  = child.getAttribute(ATT_NAME);
+
+      if (name.equals(VAL_ASSOCIATEDCONNECTIONS))
+        ((MetaBean) result).setAssociatedConnections((Vector) invokeReadFromXML(child));
+      else if (name.equals(VAL_INPUTSID))
+        inputs = (Vector) invokeReadFromXML(child);
+      else if (name.equals(VAL_OUTPUTSID))
+        outputs = (Vector) invokeReadFromXML(child);
+      else if (name.equals(VAL_SUBFLOW))
+        ((MetaBean) result).setSubFlow((Vector) invokeReadFromXML(child));
+      else if (name.equals(VAL_ORIGINALCOORDS))
+        coords = (Vector) invokeReadFromXML(child);
+      else if (name.equals(VAL_INPUTS))
+        System.out.println("INFO: '" + name + "' will be restored later.");
+      else if (name.equals(VAL_OUTPUTS))
+        System.out.println("INFO: '" + name + "' will be restored later.");
+      else
+        readFromXML(result, name, child);
+    }
+
+    bean = (MetaBean) result;
+    
+    // set inputs and outputs, after the beans have been instantiated
+    bean.setInputs(getBeanInstancesForIDs(inputs));
+    bean.setOutputs(getBeanInstancesForIDs(outputs));
+    bean.setOriginalCoords(coords);
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanel.java	(revision 29)
@@ -0,0 +1,1250 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   BoundaryPanel.java
+ *   Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.visualize.JPEGWriter;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.ToolTipManager;
+
+/**
+ * BoundaryPanel. A class to handle the plotting operations
+ * associated with generating a 2D picture of a classifier's decision
+ * boundaries.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5987 $
+ * @since 1.0
+ * @see JPanel
+ */
+public class BoundaryPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8499445518744770458L;
+  
+  /** default colours for classes */
+  public static final Color [] DEFAULT_COLORS = {
+    Color.red,
+    Color.green,
+    Color.blue,
+    new Color(0, 255, 255), // cyan
+    new Color(255, 0, 255), // pink
+    new Color(255, 255, 0), // yellow
+    new Color(255, 255, 255), //white
+    new Color(0, 0, 0)};
+    
+  /** The distance we can click away from a point in the GUI and still remove it. */
+  public static final double REMOVE_POINT_RADIUS = 7.0;
+
+  protected FastVector m_Colors = new FastVector();
+
+  /** training data */
+  protected Instances m_trainingData;
+
+  /** distribution classifier to use */
+  protected Classifier m_classifier;
+
+  /** data generator to use */
+  protected DataGenerator m_dataGenerator;
+
+  /** index of the class attribute */
+  private int m_classIndex = -1;
+
+  // attributes for visualizing on
+  protected int m_xAttribute;
+  protected int m_yAttribute;
+
+  // min, max and ranges of these attributes
+  protected double m_minX;
+  protected double m_minY;
+  protected double m_maxX;
+  protected double m_maxY;
+  private double m_rangeX;
+  private double m_rangeY;
+
+  // pixel width and height in terms of attribute values
+  protected double m_pixHeight;
+  protected double m_pixWidth;
+
+  /** used for offscreen drawing */
+  protected Image m_osi = null;
+
+  // width and height of the display area
+  protected int m_panelWidth;
+  protected int m_panelHeight;
+
+  // number of samples to take from each region in the fixed dimensions
+  protected int m_numOfSamplesPerRegion = 2;
+
+  // number of samples per kernel = base ^ (# non-fixed dimensions)
+  protected int m_numOfSamplesPerGenerator;
+  protected double m_samplesBase = 2.0;
+
+  /** listeners to be notified when plot is complete */
+  private Vector m_listeners = new Vector();
+
+  /**
+   * small inner class for rendering the bitmap on to
+   */
+  private class PlotPanel
+    extends JPanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = 743629498352235060L;
+    
+    public PlotPanel() {
+      this.setToolTipText("");
+    }
+    
+    public void paintComponent(Graphics g) {
+      super.paintComponent(g);
+      if (m_osi != null) {
+	g.drawImage(m_osi,0,0,this);
+      }
+    }
+    
+    public String getToolTipText(MouseEvent event) {
+      if (m_probabilityCache == null) {
+	return null;
+      }
+      
+      if (m_probabilityCache[event.getY()][event.getX()] == null) {
+	return null;
+      }
+      
+      String pVec = "(X: "
+	+Utils.doubleToString(convertFromPanelX((double)event.getX()), 2)
+	+" Y: "
+	+Utils.doubleToString(convertFromPanelY((double)event.getY()), 2)+") ";
+      // construct a string holding the probability vector
+      for (int i = 0; i < m_trainingData.classAttribute().numValues(); i++) {
+	pVec += 
+	  Utils.
+	  doubleToString(m_probabilityCache[event.getY()][event.getX()][i],
+			 3)+" ";
+      }
+      return pVec;
+    }
+  }
+
+  /** the actual plotting area */
+  private PlotPanel m_plotPanel = new PlotPanel();
+
+  /** thread for running the plotting operation in */
+  private Thread m_plotThread = null;
+
+  /** Stop the plotting thread */
+  protected boolean m_stopPlotting = false;
+
+  /** Stop any replotting threads */
+  protected boolean m_stopReplotting = false;
+
+  // Used by replotting threads to pause and resume the main plot thread
+  private Double m_dummy = new Double(1.0);
+  private boolean m_pausePlotting = false;
+  /** what size of tile is currently being plotted */
+  private int m_size = 1;
+  /** is the main plot thread performing the initial coarse tiling */
+  private boolean m_initialTiling;
+
+  /** A random number generator  */
+  private Random m_random = null;
+
+  /** cache of probabilities for fast replotting */
+  protected double [][][] m_probabilityCache;
+
+  /** plot the training data */
+  protected boolean m_plotTrainingData = true;
+
+  /**
+   * Creates a new <code>BoundaryPanel</code> instance.
+   *
+   * @param panelWidth the width in pixels of the panel
+   * @param panelHeight the height in pixels of the panel
+   */
+  public BoundaryPanel(int panelWidth, int panelHeight) {
+    ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
+    m_panelWidth = panelWidth;
+    m_panelHeight = panelHeight;
+    setLayout(new BorderLayout());
+    m_plotPanel.setMinimumSize(new Dimension(m_panelWidth, m_panelHeight));
+    m_plotPanel.setPreferredSize(new Dimension(m_panelWidth, m_panelHeight));
+    m_plotPanel.setMaximumSize(new Dimension(m_panelWidth, m_panelHeight));
+    add(m_plotPanel, BorderLayout.CENTER);
+    setPreferredSize(m_plotPanel.getPreferredSize());
+    setMaximumSize(m_plotPanel.getMaximumSize());
+    setMinimumSize(m_plotPanel.getMinimumSize());
+
+    m_random = new Random(1);
+    for (int i = 0; i < DEFAULT_COLORS.length; i++) {
+      m_Colors.addElement(new Color(DEFAULT_COLORS[i].getRed(),
+				    DEFAULT_COLORS[i].getGreen(),
+				    DEFAULT_COLORS[i].getBlue()));
+    }
+    m_probabilityCache = new double[m_panelHeight][m_panelWidth][];
+    
+  }
+
+  /**
+   * Set the number of points to uniformly sample from a region (fixed
+   * dimensions).
+   *
+   * @param num an <code>int</code> value
+   */
+  public void setNumSamplesPerRegion(int num) {
+    m_numOfSamplesPerRegion = num;
+  }
+
+  /**
+   * Get the number of points to sample from a region (fixed dimensions).
+   *
+   * @return an <code>int</code> value
+   */
+  public int getNumSamplesPerRegion() {
+    return m_numOfSamplesPerRegion;
+  }
+
+  /**
+   * Set the base for computing the number of samples to obtain from each
+   * generator. number of samples = base ^ (# non fixed dimensions)
+   *
+   * @param ksb a <code>double</code> value
+   */
+  public void setGeneratorSamplesBase(double ksb) {
+    m_samplesBase = ksb;
+  }
+
+  /**
+   * Get the base used for computing the number of samples to obtain from
+   * each generator
+   *
+   * @return a <code>double</code> value
+   */
+  public double getGeneratorSamplesBase() {
+    return m_samplesBase;
+  }
+
+  /**
+   * Set up the off screen bitmap for rendering to
+   */
+  protected void initialize() {
+    int iwidth = m_plotPanel.getWidth();
+    int iheight = m_plotPanel.getHeight();
+    //    System.err.println(iwidth+" "+iheight);
+    m_osi = m_plotPanel.createImage(iwidth, iheight);
+    Graphics m = m_osi.getGraphics();
+    m.fillRect(0,0,iwidth,iheight);
+  }
+
+  /**
+   * Stop the plotting thread
+   */
+  public void stopPlotting() {
+    m_stopPlotting = true;
+    try {
+    	m_plotThread.join(100);
+    } catch (Exception e){};
+  }
+
+  /** Set up the bounds of our graphic based by finding the smallest reasonable
+      area in the instance space to surround our data points.
+  */
+  public void computeMinMaxAtts() {
+    m_minX = Double.MAX_VALUE;
+    m_minY = Double.MAX_VALUE;
+    m_maxX = Double.MIN_VALUE;
+    m_maxY = Double.MIN_VALUE;
+    
+    boolean allPointsLessThanOne = true;
+    
+    if (m_trainingData.numInstances() == 0) {
+      m_minX = m_minY = 0.0;
+      m_maxX = m_maxY = 1.0;
+    }
+    else
+    {
+	for (int i = 0; i < m_trainingData.numInstances(); i++) {
+		Instance inst = m_trainingData.instance(i);
+		double x = inst.value(m_xAttribute);
+		double y = inst.value(m_yAttribute);
+		if (!Utils.isMissingValue(x) && !Utils.isMissingValue(y)) {
+			if (x < m_minX) {
+			m_minX = x;
+			}
+			if (x > m_maxX) {
+			m_maxX = x;
+			}
+		
+			if (y < m_minY) {
+			m_minY = y;
+			}
+			if (y > m_maxY) {
+			m_maxY = y;
+			}
+			if (x > 1.0 || y > 1.0)
+				allPointsLessThanOne = false;
+		}
+	}
+    }
+    
+    if (m_minX == m_maxX)
+    	m_minX = 0;
+    if (m_minY == m_maxY)
+    	m_minY = 0;
+    if (m_minX == Double.MAX_VALUE)
+    	m_minX = 0;
+    if (m_minY == Double.MAX_VALUE)
+    	m_minY = 0;
+    if (m_maxX == Double.MIN_VALUE)
+    	m_maxX = 1;
+    if (m_maxY == Double.MIN_VALUE)
+    	m_maxY = 1;
+    if (allPointsLessThanOne) {
+    	m_minX = m_minY = 0.0;
+    	m_maxX = m_maxY = 1.0;
+    }
+    
+    
+    
+    m_rangeX = (m_maxX - m_minX);
+    m_rangeY = (m_maxY - m_minY);
+    
+    m_pixWidth = m_rangeX / (double)m_panelWidth;
+    m_pixHeight = m_rangeY / (double) m_panelHeight;
+  }
+
+  /**
+   * Return a random x attribute value contained within
+   * the pix'th horizontal pixel
+   *
+   * @param pix the horizontal pixel number
+   * @return a value in attribute space
+   */
+  private double getRandomX(int pix) {
+
+    double minPix =  m_minX + (pix * m_pixWidth);
+
+    return minPix + m_random.nextDouble() * m_pixWidth;
+  }
+
+  /**
+   * Return a random y attribute value contained within
+   * the pix'th vertical pixel
+   *
+   * @param pix the vertical pixel number
+   * @return a value in attribute space
+   */
+  private double getRandomY(int pix) {
+    
+    double minPix = m_minY + (pix * m_pixHeight);
+    
+    return minPix +  m_random.nextDouble() * m_pixHeight;
+  }
+  
+  /**
+   * Start the plotting thread
+   *
+   * @exception Exception if an error occurs
+   */
+  public void start() throws Exception {
+    m_numOfSamplesPerGenerator = 
+      (int)Math.pow(m_samplesBase, m_trainingData.numAttributes()-3);
+
+    m_stopReplotting = true;
+    if (m_trainingData == null) {
+      throw new Exception("No training data set (BoundaryPanel)");
+    }
+    if (m_classifier == null) {
+      throw new Exception("No classifier set (BoundaryPanel)");
+    }
+    if (m_dataGenerator == null) {
+      throw new Exception("No data generator set (BoundaryPanel)");
+    }
+    if (m_trainingData.attribute(m_xAttribute).isNominal() || 
+	m_trainingData.attribute(m_yAttribute).isNominal()) {
+      throw new Exception("Visualization dimensions must be numeric "
+			  +"(BoundaryPanel)");
+    }
+    
+    computeMinMaxAtts();
+    
+    startPlotThread();
+    /*if (m_plotThread == null) {
+      m_plotThread = new PlotThread();
+      m_plotThread.setPriority(Thread.MIN_PRIORITY);
+      m_plotThread.start();
+    }*/
+  }
+  
+  // Thread for main plotting operation
+  protected class PlotThread extends Thread {
+    double [] m_weightingAttsValues;
+    boolean [] m_attsToWeightOn;
+    double [] m_vals;
+    double [] m_dist;
+    Instance m_predInst;
+    public void run() {
+
+      m_stopPlotting = false;
+      try {
+        initialize();
+        repaint();
+        
+        // train the classifier
+        m_probabilityCache = new double[m_panelHeight][m_panelWidth][];
+        m_classifier.buildClassifier(m_trainingData);
+        
+        // build DataGenerator
+        m_attsToWeightOn = new boolean[m_trainingData.numAttributes()];
+        m_attsToWeightOn[m_xAttribute] = true;
+        m_attsToWeightOn[m_yAttribute] = true;
+	      
+        m_dataGenerator.setWeightingDimensions(m_attsToWeightOn);
+	      
+        m_dataGenerator.buildGenerator(m_trainingData);
+
+        // generate samples
+        m_weightingAttsValues = new double [m_attsToWeightOn.length];
+        m_vals = new double[m_trainingData.numAttributes()];
+        m_predInst = new DenseInstance(1.0, m_vals);
+        m_predInst.setDataset(m_trainingData);
+
+	
+        m_size = 1 << 4;  // Current sample region size
+	
+	m_initialTiling = true;
+        // Display the initial coarse image tiling.
+      abortInitial:
+        for (int i = 0; i <= m_panelHeight; i += m_size) {   
+          for (int j = 0; j <= m_panelWidth; j += m_size) {   
+	    if (m_stopPlotting) {
+	      break abortInitial;
+	    }
+	    if (m_pausePlotting) {
+	      synchronized (m_dummy) {
+		try {
+		  m_dummy.wait();
+		} catch (InterruptedException ex) {
+		  m_pausePlotting = false;
+		}
+	      }
+	    }
+            plotPoint(j, i, m_size, m_size, 
+		      calculateRegionProbs(j, i), (j == 0));
+          }
+        }
+	if (!m_stopPlotting) {
+	  m_initialTiling = false;
+	}
+        
+        // Sampling and gridding loop
+        int size2 = m_size / 2;
+        abortPlot: 
+        while (m_size > 1) { // Subdivide down to the pixel level
+          for (int i = 0; i <= m_panelHeight; i += m_size) {
+            for (int j = 0; j <= m_panelWidth; j += m_size) {
+              if (m_stopPlotting) {
+                break abortPlot;
+              }
+	      if (m_pausePlotting) {
+		synchronized (m_dummy) {
+		  try {
+		    m_dummy.wait();
+		  } catch (InterruptedException ex) {
+		    m_pausePlotting = false;
+		  }
+		}
+	      }
+              boolean update = (j == 0 && i % 2 == 0);
+              // Draw the three new subpixel regions
+              plotPoint(j, i + size2, size2, size2, 
+			calculateRegionProbs(j, i + size2), update);
+              plotPoint(j + size2, i + size2, size2, size2, 
+			calculateRegionProbs(j + size2, i + size2), update);
+              plotPoint(j + size2, i, size2, size2, 
+			calculateRegionProbs(j + size2, i), update);
+            }
+          }
+          // The new region edge length is half the old edge length
+          m_size = size2;
+          size2 = size2 / 2;
+        }
+	update();
+	
+
+	/*
+        // Old method without sampling.
+        abortPlot: 
+        for (int i = 0; i < m_panelHeight; i++) {
+          for (int j = 0; j < m_panelWidth; j++) {
+            if (m_stopPlotting) {
+              break abortPlot;
+            }
+            plotPoint(j, i, calculateRegionProbs(j, i), (j == 0));
+          }
+        }
+        */
+
+
+        if (m_plotTrainingData) {
+          plotTrainingData();
+        }
+	      
+      } catch (Exception ex) {
+        ex.printStackTrace();
+	JOptionPane.showMessageDialog(null,"Error while plotting: \"" + ex.getMessage() + "\"");
+      } finally {
+        m_plotThread = null;
+        // notify any listeners that we are finished
+        Vector l;
+        ActionEvent e = new ActionEvent(this, 0, "");
+        synchronized(this) {
+          l = (Vector)m_listeners.clone();
+        }
+        for (int i = 0; i < l.size(); i++) {
+          ActionListener al = (ActionListener)l.elementAt(i);
+          al.actionPerformed(e);
+        }
+      }
+    }
+    
+    private double [] calculateRegionProbs(int j, int i) throws Exception {
+      double [] sumOfProbsForRegion = 
+	new double [m_trainingData.classAttribute().numValues()];
+
+      for (int u = 0; u < m_numOfSamplesPerRegion; u++) {
+      
+        double [] sumOfProbsForLocation = 
+	  new double [m_trainingData.classAttribute().numValues()];
+      
+        m_weightingAttsValues[m_xAttribute] = getRandomX(j);
+        m_weightingAttsValues[m_yAttribute] = getRandomY(m_panelHeight-i-1);
+      
+        m_dataGenerator.setWeightingValues(m_weightingAttsValues);
+      
+        double [] weights = m_dataGenerator.getWeights();
+        double sumOfWeights = Utils.sum(weights);
+        int [] indices = Utils.sort(weights);
+      
+        // Prune 1% of weight mass
+        int [] newIndices = new int[indices.length];
+        double sumSoFar = 0; 
+        double criticalMass = 0.99 * sumOfWeights;
+        int index = weights.length - 1; int counter = 0;
+        for (int z = weights.length - 1; z >= 0; z--) {
+          newIndices[index--] = indices[z];
+          sumSoFar += weights[indices[z]];
+          counter++;
+          if (sumSoFar > criticalMass) {
+            break;
+          }
+        }
+        indices = new int[counter];
+        System.arraycopy(newIndices, index + 1, indices, 0, counter);
+      
+        for (int z = 0; z < m_numOfSamplesPerGenerator; z++) {
+        
+          m_dataGenerator.setWeightingValues(m_weightingAttsValues);
+          double [][] values = m_dataGenerator.generateInstances(indices);
+        
+          for (int q = 0; q < values.length; q++) {
+            if (values[q] != null) {
+              System.arraycopy(values[q], 0, m_vals, 0, m_vals.length);
+              m_vals[m_xAttribute] = m_weightingAttsValues[m_xAttribute];
+              m_vals[m_yAttribute] = m_weightingAttsValues[m_yAttribute];
+            
+              // classify the instance
+              m_dist = m_classifier.distributionForInstance(m_predInst);
+              for (int k = 0; k < sumOfProbsForLocation.length; k++) {
+                sumOfProbsForLocation[k] += (m_dist[k] * weights[q]); 
+              }
+            }
+          }
+        }
+      
+        for (int k = 0; k < sumOfProbsForRegion.length; k++) {
+          sumOfProbsForRegion[k] += (sumOfProbsForLocation[k] * 
+				     sumOfWeights); 
+        }
+      }
+    
+      // average
+      Utils.normalize(sumOfProbsForRegion);
+
+      // cache
+      if ((i < m_panelHeight) && (j < m_panelWidth)) {
+        m_probabilityCache[i][j] = new double[sumOfProbsForRegion.length];
+        System.arraycopy(sumOfProbsForRegion, 0, m_probabilityCache[i][j], 
+			 0, sumOfProbsForRegion.length);
+      }
+		
+      return sumOfProbsForRegion;
+    }
+  }
+
+  /** Render the training points on-screen.
+  */
+  public void plotTrainingData() {
+    
+    Graphics2D osg = (Graphics2D)m_osi.getGraphics();
+    Graphics g = m_plotPanel.getGraphics();
+    osg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                         RenderingHints.VALUE_ANTIALIAS_ON);
+    double xval = 0; double yval = 0;
+    
+    for (int i = 0; i < m_trainingData.numInstances(); i++) {
+      if (!m_trainingData.instance(i).isMissing(m_xAttribute) &&
+          !m_trainingData.instance(i).isMissing(m_yAttribute)) {
+	  
+	if (m_trainingData.instance(i).isMissing(m_classIndex)) //jimmy.
+		continue; //don't plot if class is missing. TODO could we plot it differently instead?
+	
+        xval = m_trainingData.instance(i).value(m_xAttribute);
+        yval = m_trainingData.instance(i).value(m_yAttribute);
+       
+        int panelX = convertToPanelX(xval);
+        int panelY = convertToPanelY(yval);
+        Color ColorToPlotWith = 
+          ((Color)m_Colors.elementAt((int)m_trainingData.instance(i).
+                                     value(m_classIndex) % m_Colors.size()));
+	
+        if (ColorToPlotWith.equals(Color.white)) {
+          osg.setColor(Color.black);
+        } else {
+          osg.setColor(Color.white);
+        }
+        osg.fillOval(panelX-3, panelY-3, 7, 7);
+        osg.setColor(ColorToPlotWith);
+        osg.fillOval(panelX-2, panelY-2, 5, 5);
+      }
+    }
+    g.drawImage(m_osi,0,0,m_plotPanel);
+  }
+  
+  /** Convert an X coordinate from the instance space to the panel space.
+  */
+  private int convertToPanelX(double xval) {
+    double temp = (xval - m_minX) / m_rangeX;
+    temp = temp * (double) m_panelWidth;
+
+    return (int)temp;
+  }
+
+  /** Convert a Y coordinate from the instance space to the panel space.
+  */
+  private int convertToPanelY(double yval) {
+    double temp = (yval - m_minY) / m_rangeY;
+    temp = temp * (double) m_panelHeight;
+    temp = m_panelHeight - temp;
+    
+    return (int)temp;
+  }
+  
+  /** Convert an X coordinate from the panel space to the instance space.
+  */
+  private double convertFromPanelX(double pX) {
+    pX /= (double) m_panelWidth;
+    pX *= m_rangeX;
+    return pX + m_minX;
+  }
+
+  /** Convert a Y coordinate from the panel space to the instance space.
+  */
+  private double convertFromPanelY(double pY) {
+    pY  = m_panelHeight - pY;
+    pY /= (double) m_panelHeight;
+    pY *= m_rangeY;
+    
+    return pY + m_minY;
+  }
+
+
+  /** Plot a point in our visualization on-screen.
+  */
+  protected  void plotPoint(int x, int y, double [] probs, boolean update) {
+    plotPoint(x, y, 1, 1, probs, update);
+  }
+  
+  /** Plot a point in our visualization on-screen.
+  */
+  private void plotPoint(int x, int y, int width, int height, 
+			 double [] probs, boolean update) {
+
+    // draw a progress line
+    Graphics osg = m_osi.getGraphics();
+    if (update) {
+      osg.setXORMode(Color.white);
+      osg.drawLine(0, y, m_panelWidth-1, y);
+      update();
+      osg.drawLine(0, y, m_panelWidth-1, y);
+    }
+
+    // plot the point
+    osg.setPaintMode();
+    float [] colVal = new float[3];
+    
+    float [] tempCols = new float[3];
+    for (int k = 0; k < probs.length; k++) {
+      Color curr = (Color)m_Colors.elementAt(k % m_Colors.size());
+
+      curr.getRGBColorComponents(tempCols);
+      for (int z = 0 ; z < 3; z++) {
+        colVal[z] += probs[k] * tempCols[z];
+      }
+    }
+
+    for (int z = 0; z < 3; z++) {
+      if (colVal[z] < 0) {
+	colVal[z] = 0;
+      } else if (colVal[z] > 1) {
+	colVal[z] = 1;
+      }
+    }
+    
+    osg.setColor(new Color(colVal[0], 
+                           colVal[1], 
+                           colVal[2]));
+    osg.fillRect(x, y, width, height);
+  }
+  
+  /** Update the rendered image.
+  */
+  private void update() {
+    Graphics g = m_plotPanel.getGraphics();
+    g.drawImage(m_osi, 0, 0, m_plotPanel);
+  }
+
+  /**
+   * Set the training data to use
+   *
+   * @param trainingData the training data
+   * @exception Exception if an error occurs
+   */
+  public void setTrainingData(Instances trainingData) throws Exception {
+
+    m_trainingData = trainingData;
+    if (m_trainingData.classIndex() < 0) {
+      throw new Exception("No class attribute set (BoundaryPanel)");
+    }
+    m_classIndex = m_trainingData.classIndex();
+  }
+  
+  /** Adds a training instance to the visualization dataset.
+  */
+  public void addTrainingInstance(Instance instance) {
+  	
+  	if (m_trainingData == null) {
+		//TODO
+		System.err.println("Trying to add to a null training set (BoundaryPanel)");
+	}
+  	
+  	m_trainingData.add(instance);
+  }
+
+  /**
+   * Register a listener to be notified when plotting completes
+   *
+   * @param newListener the listener to add
+   */
+  public void addActionListener(ActionListener newListener) {
+    m_listeners.add(newListener);
+  }
+  
+  /**
+   * Remove a listener
+   *
+   * @param removeListener the listener to remove
+   */
+  public void removeActionListener(ActionListener removeListener) {
+    m_listeners.removeElement(removeListener);
+  }
+  
+  /**
+   * Set the classifier to use.
+   *
+   * @param classifier the classifier to use
+   */
+  public void setClassifier(Classifier classifier) {
+    m_classifier = classifier;
+  }
+  
+  /**
+   * Set the data generator to use for generating new instances
+   *
+   * @param dataGenerator the data generator to use
+   */
+  public void setDataGenerator(DataGenerator dataGenerator) {
+    m_dataGenerator = dataGenerator;
+  }
+  
+  /**
+   * Set the x attribute index
+   *
+   * @param xatt index of the attribute to use on the x axis
+   * @exception Exception if an error occurs
+   */
+  public void setXAttribute(int xatt) throws Exception {
+    if (m_trainingData == null) {
+      throw new Exception("No training data set (BoundaryPanel)");
+    }
+    if (xatt < 0 || 
+        xatt > m_trainingData.numAttributes()) {
+      throw new Exception("X attribute out of range (BoundaryPanel)");
+    }
+    if (m_trainingData.attribute(xatt).isNominal()) {
+      throw new Exception("Visualization dimensions must be numeric "
+                          +"(BoundaryPanel)");
+    }
+    /*if (m_trainingData.numDistinctValues(xatt) < 2) {
+      throw new Exception("Too few distinct values for X attribute "
+                          +"(BoundaryPanel)");
+    }*/ //removed by jimmy. TESTING!
+    m_xAttribute = xatt;
+  }
+
+  /**
+   * Set the y attribute index
+   *
+   * @param yatt index of the attribute to use on the y axis
+   * @exception Exception if an error occurs
+   */
+  public void setYAttribute(int yatt) throws Exception {
+    if (m_trainingData == null) {
+      throw new Exception("No training data set (BoundaryPanel)");
+    }
+    if (yatt < 0 || 
+        yatt > m_trainingData.numAttributes()) {
+      throw new Exception("X attribute out of range (BoundaryPanel)");
+    }
+    if (m_trainingData.attribute(yatt).isNominal()) {
+      throw new Exception("Visualization dimensions must be numeric "
+                          +"(BoundaryPanel)");
+    }
+    /*if (m_trainingData.numDistinctValues(yatt) < 2) {
+      throw new Exception("Too few distinct values for Y attribute "
+                          +"(BoundaryPanel)");
+    }*/ //removed by jimmy. TESTING!
+    m_yAttribute = yatt;
+  }
+  
+  /**
+   * Set a vector of Color objects for the classes
+   *
+   * @param colors a <code>FastVector</code> value
+   */
+  public void setColors(FastVector colors) {
+    synchronized (m_Colors) {
+      m_Colors = colors;
+    }
+    //replot(); //commented by jimmy
+    update(); //added by jimmy
+  }
+
+  /**
+   * Set whether to superimpose the training data
+   * plot
+   *
+   * @param pg a <code>boolean</code> value
+   */
+  public void setPlotTrainingData(boolean pg) {
+    m_plotTrainingData = pg;
+  }
+
+  /**
+   * Returns true if training data is to be superimposed
+   *
+   * @return a <code>boolean</code> value
+   */
+  public boolean getPlotTrainingData() {
+    return m_plotTrainingData;
+  }
+  
+  /**
+   * Get the current vector of Color objects used for the classes
+   *
+   * @return a <code>FastVector</code> value
+   */
+  public FastVector getColors() {
+    return m_Colors;
+  }
+  
+  /**
+   * Quickly replot the display using cached probability estimates
+   */
+  public void replot() {
+    if (m_probabilityCache[0][0] == null) {
+      return;
+    }
+    m_stopReplotting = true;
+    m_pausePlotting = true;
+    // wait 300 ms to give any other replot threads a chance to halt
+    try {
+      Thread.sleep(300);
+    } catch (Exception ex) {}
+
+    final Thread replotThread = new Thread() {
+        public void run() {
+          m_stopReplotting = false;
+	  int size2 = m_size / 2;
+          finishedReplot: for (int i = 0; i < m_panelHeight; i += m_size) {
+            for (int j = 0; j < m_panelWidth; j += m_size) {
+              if (m_probabilityCache[i][j] == null || m_stopReplotting) {
+                break finishedReplot;
+              }
+
+	      boolean update = (j == 0 && i % 2 == 0);
+	      if (i < m_panelHeight && j < m_panelWidth) {
+		// Draw the three new subpixel regions or single course tiling
+		if (m_initialTiling || m_size == 1) {
+		  if (m_probabilityCache[i][j] == null) {
+		    break finishedReplot;
+		  }
+		  plotPoint(j, i, m_size, m_size, 
+			    m_probabilityCache[i][j], update);
+		} else {
+		  if (m_probabilityCache[i+size2][j] == null) {
+		    break finishedReplot;
+		  }
+		  plotPoint(j, i + size2, size2, size2, 
+			    m_probabilityCache[i + size2][j], update);
+		  if (m_probabilityCache[i+size2][j+size2] == null) {
+		    break finishedReplot;
+		  }
+		  plotPoint(j + size2, i + size2, size2, size2, 
+			    m_probabilityCache[i + size2][j + size2], update);
+		  if (m_probabilityCache[i][j+size2] == null) {
+		    break finishedReplot;
+		  }
+		  plotPoint(j + size2, i, size2, size2, 
+			    m_probabilityCache[i + size2][j], update);
+		}
+	      }
+	    }
+          }
+	  update();
+          if (m_plotTrainingData) {
+            plotTrainingData();
+          }
+	  m_pausePlotting = false;
+	  if (!m_stopPlotting) {
+	    synchronized (m_dummy) {
+	      m_dummy.notifyAll();
+	    }
+	  }
+        }
+      };
+    
+    replotThread.start();      
+  }
+
+  protected void saveImage(String fileName) {
+    BufferedImage	bi;
+    Graphics2D 		gr2;
+    ImageWriter 	writer;
+    Iterator 		iter;
+    ImageOutputStream 	ios;
+    ImageWriteParam 	param;
+
+    try {
+      // render image
+      bi  = new BufferedImage(m_panelWidth, m_panelHeight, BufferedImage.TYPE_INT_RGB);
+      gr2 = bi.createGraphics();
+      gr2.drawImage(m_osi, 0, 0, m_panelWidth, m_panelHeight, null);
+
+      // get jpeg writer
+      writer = null;
+      iter   = ImageIO.getImageWritersByFormatName("jpg");
+      if (iter.hasNext())
+	writer = (ImageWriter) iter.next();
+      else
+	throw new Exception("No JPEG writer available!");
+
+      // prepare output file
+      ios = ImageIO.createImageOutputStream(new File(fileName));
+      writer.setOutput(ios);
+
+      // set the quality
+      param = new JPEGImageWriteParam(Locale.getDefault());
+      param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
+      param.setCompressionQuality(1.0f);
+
+      // write the image
+      writer.write(null, new IIOImage(bi, null, null), param);
+
+      // cleanup
+      ios.flush();
+      writer.dispose();
+      ios.close();    
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /** Adds a training instance to our dataset, based on the coordinates of the mouse on the panel.
+      This method sets the x and y attributes and the class (as defined by classAttIndex), and sets
+      all other values as Missing.
+   *  @param mouseX the x coordinate of the mouse, in pixels.
+   *  @param mouseY the y coordinate of the mouse, in pixels.
+   *  @param classAttIndex the index of the attribute that is currently selected as the class attribute.
+   *  @param classValue the value to set the class to in our new point.
+   */
+  public void addTrainingInstanceFromMouseLocation(int mouseX, int mouseY, int classAttIndex, double classValue) {
+  	//convert to coordinates in the training instance space.
+	double x = convertFromPanelX(mouseX);
+	double y = convertFromPanelY(mouseY);
+	
+	//build the training instance
+	Instance newInstance = new DenseInstance(m_trainingData.numAttributes());
+	for (int i = 0; i < newInstance.numAttributes(); i++) {
+		if (i == classAttIndex) {
+			newInstance.setValue(i,classValue);
+		}
+		else if (i == m_xAttribute)
+			newInstance.setValue(i,x);
+		else if (i == m_yAttribute)
+			newInstance.setValue(i,y);
+		else newInstance.setMissing(i);
+	}
+	
+	//add it to our data set.
+	addTrainingInstance(newInstance);
+  }
+  
+  /** Deletes all training instances from our dataset.
+  */
+  public void removeAllInstances() {
+  	if (m_trainingData != null)
+	{
+  		m_trainingData.delete();
+		try { initialize();} catch (Exception e) {};
+	}
+	
+  }
+  
+  /** Removes a single training instance from our dataset, if there is one that is close enough
+      to the specified mouse location.
+  */
+  public void removeTrainingInstanceFromMouseLocation(int mouseX, int mouseY) {
+  	
+	//convert to coordinates in the training instance space.
+	double x = convertFromPanelX(mouseX);
+	double y = convertFromPanelY(mouseY);
+	
+	int bestIndex = -1;
+	double bestDistanceBetween = Integer.MAX_VALUE;
+	
+	//find the closest point.
+	for (int i = 0; i < m_trainingData.numInstances(); i++) {
+		Instance current = m_trainingData.instance(i);
+		double distanceBetween = (current.value(m_xAttribute) - x) * (current.value(m_xAttribute) - x) + (current.value(m_yAttribute) - y) * (current.value(m_yAttribute) - y); // won't bother to sqrt, just used square values.
+		
+		if (distanceBetween < bestDistanceBetween)
+		{
+			bestIndex = i;
+			bestDistanceBetween = distanceBetween;
+		}
+	}
+	if (bestIndex == -1)
+		return;
+	Instance best = m_trainingData.instance(bestIndex);
+	double panelDistance = (convertToPanelX(best.value(m_xAttribute)) - mouseX) * (convertToPanelX(best.value(m_xAttribute)) - mouseX)
+		+ (convertToPanelY(best.value(m_yAttribute)) - mouseY) * (convertToPanelY(best.value(m_yAttribute)) - mouseY);
+	if (panelDistance < REMOVE_POINT_RADIUS * REMOVE_POINT_RADIUS) {//the best point is close enough. (using squared distances)
+		m_trainingData.delete(bestIndex);
+	}
+  }
+  
+  /** Starts the plotting thread.  Will also create it if necessary.
+  */
+  public void startPlotThread() {
+  	if (m_plotThread == null) { //jimmy
+      		m_plotThread = new PlotThread();
+      		m_plotThread.setPriority(Thread.MIN_PRIORITY);
+      		m_plotThread.start();
+    	}
+  }
+  
+  /** Adds a mouse listener.
+  */
+  public void addMouseListener(MouseListener l) {
+  	m_plotPanel.addMouseListener(l);
+  }
+  
+  /** Gets the minimum x-coordinate bound, in training-instance units (not mouse coordinates).
+  */
+  public double getMinXBound() {
+  	return m_minX;
+  }
+  
+  /** Gets the minimum y-coordinate bound, in training-instance units (not mouse coordinates).
+  */
+  public double getMinYBound() {
+  	return m_minY;
+  }
+  
+  /** Gets the maximum x-coordinate bound, in training-instance units (not mouse coordinates).
+  */
+  public double getMaxXBound() {
+  	return m_maxX;
+  }
+  
+  /** Gets the maximum x-coordinate bound, in training-instance units (not mouse coordinates).
+  */
+  public double getMaxYBound() {
+  	return m_maxY;
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param args a <code>String[]</code> value
+   */
+  public static void main (String [] args) {
+    try {
+      if (args.length < 8) {
+	System.err.println("Usage : BoundaryPanel <dataset> "
+			   +"<class col> <xAtt> <yAtt> "
+			   +"<base> <# loc/pixel> <kernel bandwidth> "
+			   +"<display width> "
+			   +"<display height> <classifier "
+			   +"[classifier options]>");
+	System.exit(1);
+      }
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka classification boundary visualizer");
+      jf.getContentPane().setLayout(new BorderLayout());
+
+      System.err.println("Loading instances from : "+args[0]);
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      final Instances i = new Instances(r);
+      i.setClassIndex(Integer.parseInt(args[1]));
+
+      //      bv.setClassifier(new Logistic());
+      final int xatt = Integer.parseInt(args[2]);
+      final int yatt = Integer.parseInt(args[3]);
+      int base = Integer.parseInt(args[4]);
+      int loc = Integer.parseInt(args[5]);
+
+      int bandWidth = Integer.parseInt(args[6]);
+      int panelWidth = Integer.parseInt(args[7]);
+      int panelHeight = Integer.parseInt(args[8]);
+
+      final String classifierName = args[9];
+      final BoundaryPanel bv = new BoundaryPanel(panelWidth,panelHeight);
+      bv.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    String classifierNameNew = 
+	      classifierName.substring(classifierName.lastIndexOf('.')+1, 
+				       classifierName.length());
+	    bv.saveImage(classifierNameNew+"_"+i.relationName()
+			 +"_X"+xatt+"_Y"+yatt+".jpg");
+	  }
+	});
+
+      jf.getContentPane().add(bv, BorderLayout.CENTER);
+      jf.setSize(bv.getMinimumSize());
+      //      jf.setSize(200,200);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+
+      jf.pack();
+      jf.setVisible(true);
+      //      bv.initialize();
+      bv.repaint();
+      
+
+      String [] argsR = null;
+      if (args.length > 10) {
+	argsR = new String [args.length-10];
+	for (int j = 10; j < args.length; j++) {
+	  argsR[j-10] = args[j];
+	}
+      }
+      Classifier c = AbstractClassifier.forName(args[9], argsR);
+      KDDataGenerator dataGen = new KDDataGenerator();
+      dataGen.setKernelBandwidth(bandWidth);
+      bv.setDataGenerator(dataGen);
+      bv.setNumSamplesPerRegion(loc);
+      bv.setGeneratorSamplesBase(base);
+      bv.setClassifier(c);
+      bv.setTrainingData(i);
+      bv.setXAttribute(xatt);
+      bv.setYAttribute(yatt);
+
+      try {
+	// try and load a color map if one exists
+	FileInputStream fis = new FileInputStream("colors.ser");
+	ObjectInputStream ois = new ObjectInputStream(fis);
+	FastVector colors = (FastVector)ois.readObject();
+	bv.setColors(colors);	
+      } catch (Exception ex) {
+	System.err.println("No color map file");
+      }
+      bv.start();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanelDistributed.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanelDistributed.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryPanelDistributed.java	(revision 29)
@@ -0,0 +1,659 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   BoundaryPanelDistrubuted.java
+ *   Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.experiment.Compute;
+import weka.experiment.RemoteExperimentEvent;
+import weka.experiment.RemoteExperimentListener;
+import weka.experiment.TaskStatusInfo;
+
+import java.awt.BorderLayout;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.ObjectInputStream;
+import java.rmi.Naming;
+import java.util.Vector;
+
+/**
+ * This class extends BoundaryPanel with code for distributing the
+ * processing necessary to create a visualization among a list of
+ * remote machines. Specifically, a visualization is broken down and
+ * processed row by row using the available remote computers.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5928 $
+ * @since 1.0
+ * @see BoundaryPanel
+ */
+public class BoundaryPanelDistributed
+  extends BoundaryPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1743284397893937776L;
+
+  /** a list of RemoteExperimentListeners */
+  protected Vector m_listeners = new Vector();
+
+  /** Holds the names of machines with remoteEngine servers running */
+  protected Vector m_remoteHosts = new Vector();
+  
+  /** The queue of available hosts */
+  private weka.core.Queue m_remoteHostsQueue = new weka.core.Queue();
+
+  /** The status of each of the remote hosts */
+  private int [] m_remoteHostsStatus;
+
+  /** The number of times tasks have failed on each remote host */
+  private int [] m_remoteHostFailureCounts;
+
+  protected static final int AVAILABLE=0;
+  protected static final int IN_USE=1;
+  protected static final int CONNECTION_FAILED=2;
+  protected static final int SOME_OTHER_FAILURE=3;
+
+  protected static final int MAX_FAILURES=3;
+
+  /** Set to true if MAX_FAILURES exceeded on all hosts or connections fail 
+      on all hosts or user aborts plotting */
+  private boolean m_plottingAborted = false;
+
+  /** The number of hosts removed due to exceeding max failures */
+  private int m_removedHosts;
+
+  /** The count of failed sub-tasks */
+  private int m_failedCount;
+
+  /** The count of successfully completed sub-tasks */
+  private int m_finishedCount;
+
+  /** The queue of sub-tasks waiting to be processed */
+  private weka.core.Queue m_subExpQueue = new weka.core.Queue();
+
+  /** number of seconds between polling server */
+  private int m_minTaskPollTime = 1000;
+
+  private int [] m_hostPollingTime;
+
+  /**
+   * Creates a new <code>BoundaryPanelDistributed</code> instance.
+   *
+   * @param panelWidth width of the display
+   * @param panelHeight height of the display
+   */
+  public BoundaryPanelDistributed(int panelWidth, int panelHeight) {
+    super(panelWidth, panelHeight);
+  }
+
+  /**
+   * Set a list of host names of machines to distribute processing to
+   *
+   * @param remHosts a Vector of host names (Strings)
+   */
+  public void setRemoteHosts(Vector remHosts) {
+    m_remoteHosts = remHosts;
+  }
+
+  /**
+   * Add an object to the list of those interested in recieving update
+   * information from the RemoteExperiment
+   * @param r a listener
+   */
+  public void addRemoteExperimentListener(RemoteExperimentListener r) {
+    m_listeners.addElement(r);
+  }
+
+  protected void initialize() {
+    super.initialize();
+
+    m_plottingAborted = false;
+    m_finishedCount = 0;
+    m_failedCount = 0;
+
+    // initialize all remote hosts to available
+    m_remoteHostsStatus = new int [m_remoteHosts.size()];    
+    m_remoteHostFailureCounts = new int [m_remoteHosts.size()];
+
+    m_remoteHostsQueue = new weka.core.Queue();
+
+    if (m_remoteHosts.size() == 0) {
+      System.err.println("No hosts specified!");
+      System.exit(1);
+    }
+
+    // prime the hosts queue
+    m_hostPollingTime = new int [m_remoteHosts.size()];
+    for (int i=0;i<m_remoteHosts.size();i++) {
+      m_remoteHostsQueue.push(new Integer(i));
+      m_hostPollingTime[i] = m_minTaskPollTime;
+    }
+
+    // set up sub taskss (just holds the row numbers to be processed
+    m_subExpQueue = new weka.core.Queue();
+    for (int i = 0; i < m_panelHeight; i++) {
+      m_subExpQueue.push(new Integer(i));
+    }
+    
+    try {
+      // need to build classifier and data generator
+      m_classifier.buildClassifier(m_trainingData);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.exit(1);
+    }
+    
+    boolean [] attsToWeightOn;
+    // build DataGenerator
+    attsToWeightOn = new boolean[m_trainingData.numAttributes()];
+    attsToWeightOn[m_xAttribute] = true;
+    attsToWeightOn[m_yAttribute] = true;
+    
+    m_dataGenerator.setWeightingDimensions(attsToWeightOn);    
+    try {
+      m_dataGenerator.buildGenerator(m_trainingData);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  /**
+   * Start processing
+   *
+   * @exception Exception if an error occurs
+   */
+  public void start() throws Exception {
+    // done in the sub task
+    /*     m_numOfSamplesPerGenerator = 
+	   (int)Math.pow(m_samplesBase, m_trainingData.numAttributes()-3); */
+
+    m_stopReplotting = true;
+    if (m_trainingData == null) {
+      throw new Exception("No training data set (BoundaryPanel)");
+    }
+    if (m_classifier == null) {
+      throw new Exception("No classifier set (BoundaryPanel)");
+    }
+    if (m_dataGenerator == null) {
+      throw new Exception("No data generator set (BoundaryPanel)");
+    }
+    if (m_trainingData.attribute(m_xAttribute).isNominal() || 
+	m_trainingData.attribute(m_yAttribute).isNominal()) {
+      throw new Exception("Visualization dimensions must be numeric "
+			  +"(BoundaryPanel)");
+    }
+    
+    computeMinMaxAtts();
+    initialize();
+
+    // launch tasks on all available hosts
+    int totalHosts = m_remoteHostsQueue.size();
+    for (int i = 0; i < totalHosts; i++) {
+      availableHost(-1);
+      Thread.sleep(70);
+    }
+  }
+
+  /**
+   * Push a host back onto the list of available hosts and launch a waiting
+   * Task (if any).
+   *
+   * @param hostNum the number of the host to return to the queue. -1
+   * if no host to return.
+   */
+  protected synchronized void availableHost(int hostNum) {
+    if (hostNum >= 0) { 
+      if (m_remoteHostFailureCounts[hostNum] < MAX_FAILURES) {
+	m_remoteHostsQueue.push(new Integer(hostNum));
+      } else {
+	notifyListeners(false,true,false,"Max failures exceeded for host "
+			+((String)m_remoteHosts.elementAt(hostNum))
+			+". Removed from host list.");
+	m_removedHosts++;
+      }
+    }
+
+    // check for all sub exp complete or all hosts failed or failed count
+    // exceeded
+    if (m_failedCount == (MAX_FAILURES * m_remoteHosts.size())) {
+      m_plottingAborted = true;
+      notifyListeners(false,true,true,"Plotting aborted! Max failures "
+		      +"exceeded on all remote hosts.");
+      return;
+    }
+
+    /*    System.err.println("--------------");
+    System.err.println("exp q :"+m_subExpQueue.size());
+    System.err.println("host list size "+m_remoteHosts.size());
+    System.err.println("actual host list size "+m_remoteHostsQueue.size());
+    System.err.println("removed hosts "+m_removedHosts); */
+    if (m_subExpQueue.size() == 0 && 
+	(m_remoteHosts.size() == 
+	 (m_remoteHostsQueue.size() + m_removedHosts))) {
+      if (m_plotTrainingData) {
+	plotTrainingData();
+      }
+      notifyListeners(false,true,true,"Plotting completed successfully.");
+
+      return;
+    }
+
+
+    if (checkForAllFailedHosts()) {
+      return;
+    }
+
+    if (m_plottingAborted && 
+	(m_remoteHostsQueue.size() + m_removedHosts) == 
+	m_remoteHosts.size()) {
+      notifyListeners(false,true,true,"Plotting aborted. All remote tasks "
+		      +"finished.");
+    }
+
+    if (!m_subExpQueue.empty() && !m_plottingAborted) {
+      if (!m_remoteHostsQueue.empty()) {
+	int availHost, waitingTask;
+	try {
+	  availHost = ((Integer)m_remoteHostsQueue.pop()).intValue();
+	  waitingTask = ((Integer)m_subExpQueue.pop()).intValue();
+	  launchNext(waitingTask, availHost);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	}
+      }
+    }    
+  }
+
+  /**
+   * Inform all listeners of progress
+   * @param status true if this is a status type of message
+   * @param log true if this is a log type of message
+   * @param finished true if the remote task has finished
+   * @param message the message.
+   */
+  private synchronized void notifyListeners(boolean status, 
+					    boolean log, 
+					    boolean finished,
+					    String message) {
+    if (m_listeners.size() > 0) {
+      for (int i=0;i<m_listeners.size();i++) {
+	RemoteExperimentListener r = 
+	  (RemoteExperimentListener)(m_listeners.elementAt(i));
+	r.remoteExperimentStatus(new RemoteExperimentEvent(status,
+							   log,
+							   finished,
+							   message));
+      }
+    } else {
+      System.err.println(message);
+    }
+  }
+
+  /**
+   * Check to see if we have failed to connect to all hosts
+   */
+  private boolean checkForAllFailedHosts() {
+    boolean allbad = true;
+    for (int i = 0; i < m_remoteHostsStatus.length; i++) {
+      if (m_remoteHostsStatus[i] != CONNECTION_FAILED) {
+	allbad = false;
+	break;
+      }
+    }
+    if (allbad) {
+      m_plottingAborted = true;
+      notifyListeners(false,true,true,"Plotting aborted! All connections "
+		      +"to remote hosts failed.");
+    }
+    return allbad;
+  }
+
+  /**
+   * Increment the number of successfully completed sub experiments
+   */
+  protected synchronized void incrementFinished() {
+    m_finishedCount++;
+  }
+
+  /**
+   * Increment the overall number of failures and the number of failures for
+   * a particular host
+   * @param hostNum the index of the host to increment failure count
+   */
+  protected synchronized void incrementFailed(int hostNum) {
+    m_failedCount++;
+    m_remoteHostFailureCounts[hostNum]++;
+  }
+
+  /**
+   * Push an experiment back on the queue of waiting experiments
+   * @param expNum the index of the experiment to push onto the queue
+   */
+  protected synchronized void waitingTask(int expNum) {
+    m_subExpQueue.push(new Integer(expNum));
+  }
+
+  protected void launchNext(final int wtask, final int ah) {
+    Thread subTaskThread;
+    subTaskThread = new Thread() {
+	public void run() {
+	  m_remoteHostsStatus[ah] = IN_USE;
+	  //	  m_subExpComplete[wtask] = TaskStatusInfo.PROCESSING;
+	  RemoteBoundaryVisualizerSubTask vSubTask = 
+	    new RemoteBoundaryVisualizerSubTask();
+	  vSubTask.setXAttribute(m_xAttribute);
+	  vSubTask.setYAttribute(m_yAttribute);
+	  vSubTask.setRowNumber(wtask);
+	  vSubTask.setPanelWidth(m_panelWidth);
+	  vSubTask.setPanelHeight(m_panelHeight);
+	  vSubTask.setPixHeight(m_pixHeight);
+	  vSubTask.setPixWidth(m_pixWidth);
+	  vSubTask.setClassifier(m_classifier);
+	  vSubTask.setDataGenerator(m_dataGenerator);
+	  vSubTask.setInstances(m_trainingData);
+	  vSubTask.setMinMaxX(m_minX, m_maxX);
+	  vSubTask.setMinMaxY(m_minY, m_maxY);
+	  vSubTask.setNumSamplesPerRegion(m_numOfSamplesPerRegion);
+	  vSubTask.setGeneratorSamplesBase(m_samplesBase);
+	  try {
+	    String name = "//"
+	      +((String)m_remoteHosts.elementAt(ah))
+	      +"/RemoteEngine";
+	    Compute comp = (Compute) Naming.lookup(name);
+	    // assess the status of the sub-exp
+	    notifyListeners(false,true,false,"Starting row "
+			    +wtask
+			    +" on host "
+			    +((String)m_remoteHosts.elementAt(ah)));
+	    Object subTaskId = comp.executeTask(vSubTask);
+	    boolean finished = false;
+	    TaskStatusInfo is = null;
+	    long startTime = System.currentTimeMillis();
+	    while (!finished) {
+	      try {
+		Thread.sleep(Math.max(m_minTaskPollTime, 
+				      m_hostPollingTime[ah]));
+		
+		TaskStatusInfo cs = (TaskStatusInfo)comp.
+		  checkStatus(subTaskId);
+		if (cs.getExecutionStatus() == TaskStatusInfo.FINISHED) {
+		  // push host back onto queue and try launching any waiting 
+		  // sub-experiments
+		  long runTime = System.currentTimeMillis() - startTime;
+		  runTime /= 4;
+		  if (runTime < 1000) {
+		    runTime = 1000;
+		  }
+		  m_hostPollingTime[ah] = (int)runTime;
+
+		  // Extract the row from the result
+		  RemoteResult rr =  (RemoteResult)cs.getTaskResult();
+		  double [][] probs = rr.getProbabilities();
+		  
+		  for (int i = 0; i < m_panelWidth; i++) {
+		    m_probabilityCache[wtask][i] = probs[i];
+		    if (i < m_panelWidth-1) {
+		      plotPoint(i, wtask, probs[i], false);
+		    } else {
+		      plotPoint(i, wtask, probs[i], true);
+		    }
+		  }
+		  notifyListeners(false, true, false,  cs.getStatusMessage());
+		  m_remoteHostsStatus[ah] = AVAILABLE;
+		  incrementFinished();
+		  availableHost(ah);
+		  finished = true;
+		} else if (cs.getExecutionStatus() == 
+			   TaskStatusInfo.FAILED) {
+		  // a non connection related error---possibly host doesn't have
+		  // access to data sets or security policy is not set up
+		  // correctly or classifier(s) failed for some reason
+		  notifyListeners(false, true, false,  
+				  cs.getStatusMessage());
+		  m_remoteHostsStatus[ah] = SOME_OTHER_FAILURE;
+		  //		  m_subExpComplete[wexp] = TaskStatusInfo.FAILED;
+		  notifyListeners(false,true,false,"Row "+wtask
+				  +" "+cs.getStatusMessage()
+				  +". Scheduling for execution on another host.");
+		  incrementFailed(ah);
+		  // push experiment back onto queue
+		  waitingTask(wtask);	
+		  // push host back onto queue and try launching any waiting 
+		  // Tasks. Host is pushed back on the queue as the
+		  // failure may be temporary.
+		  availableHost(ah);
+		  finished = true;
+		} else {
+		  if (is == null) {
+		    is = cs;
+		    notifyListeners(false, true, false, cs.getStatusMessage());
+		  } else {
+		    RemoteResult rr = (RemoteResult)cs.getTaskResult();
+		    if (rr != null) {
+		      int percentComplete = rr.getPercentCompleted();
+		      String timeRemaining = "";
+		      if (percentComplete > 0 && percentComplete < 100) {
+			double timeSoFar = (double)System.currentTimeMillis() -
+			  (double)startTime;
+			double timeToGo = 
+			  ((100.0 - percentComplete) 
+			   / (double)percentComplete) * timeSoFar;
+			if (timeToGo < m_hostPollingTime[ah]) {
+			  m_hostPollingTime[ah] = (int)timeToGo;
+			}
+			String units = "seconds";
+			timeToGo /= 1000.0;
+			if (timeToGo > 60) {
+			  units = "minutes";
+			  timeToGo /= 60.0;
+			}
+			if (timeToGo > 60) {
+			  units = "hours";
+			  timeToGo /= 60.0;
+			}
+			timeRemaining = " (approx. time remaining "
+			  +Utils.doubleToString(timeToGo, 1)+" "+units+")";
+		      }
+		      if (percentComplete < 25 
+			  /*&& minTaskPollTime < 30000*/) {		
+			if (percentComplete > 0) {
+			  m_hostPollingTime[ah] = 
+			    (int)((25.0 / (double)percentComplete) * 
+				  m_hostPollingTime[ah]);
+			} else {
+			  m_hostPollingTime[ah] *= 2;
+			}
+			if (m_hostPollingTime[ah] > 60000) {
+			  m_hostPollingTime[ah] = 60000;
+			}
+		      }
+		      notifyListeners(false, true, false,
+				      "Row "+wtask+" "+percentComplete
+				      +"% complete"+timeRemaining+".");
+		    } else {
+		      notifyListeners(false, true, false,
+				      "Row "+wtask+" queued on "
+				      +((String)m_remoteHosts.
+					elementAt(ah)));
+		      if (m_hostPollingTime[ah] < 60000) {
+			m_hostPollingTime[ah] *= 2;
+		      }
+		    }
+
+		    is = cs;
+		  }
+		}
+	      } catch (InterruptedException ie) {
+		ie.printStackTrace();
+	      }
+	    }
+	  } catch (Exception ce) {
+	    m_remoteHostsStatus[ah] = CONNECTION_FAILED;
+	    m_removedHosts++;
+	    System.err.println(ce);
+	    ce.printStackTrace();
+	    notifyListeners(false,true,false,"Connection to "
+			    +((String)m_remoteHosts.elementAt(ah))
+			    +" failed. Scheduling row "
+			    +wtask
+			    +" for execution on another host.");
+	    checkForAllFailedHosts();
+	    waitingTask(wtask);
+	  } finally {
+	    if (isInterrupted()) {
+	      System.err.println("Sub exp Interupted!");
+	    }
+	  }
+	}
+      };
+    subTaskThread.setPriority(Thread.MIN_PRIORITY);
+    subTaskThread.start();
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param args a <code>String[]</code> value
+   */
+  public static void main (String [] args) {
+    try {
+      if (args.length < 8) {
+	System.err.println("Usage : BoundaryPanelDistributed <dataset> "
+			   +"<class col> <xAtt> <yAtt> "
+			   +"<base> <# loc/pixel> <kernel bandwidth> "
+			   +"<display width> "
+			   +"<display height> <classifier "
+			   +"[classifier options]>");
+	System.exit(1);
+      }
+      
+      Vector hostNames = new Vector();
+      // try loading hosts file
+      try {
+	BufferedReader br = new BufferedReader(new FileReader("hosts.vis"));
+	String hostName = br.readLine();
+	while (hostName != null) {
+	  System.out.println("Adding host "+hostName);
+	  hostNames.add(hostName);
+	  hostName = br.readLine();
+	}
+	br.close();
+      } catch (Exception ex) {
+	System.err.println("No hosts.vis file - create this file in "
+			   +"the current directory with one host name "
+			   +"per line, or use BoundaryPanel instead.");
+	System.exit(1);
+      }
+
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka classification boundary visualizer");
+      jf.getContentPane().setLayout(new BorderLayout());
+
+      System.err.println("Loading instances from : "+args[0]);
+      java.io.Reader r = new java.io.BufferedReader(
+			 new java.io.FileReader(args[0]));
+      final Instances i = new Instances(r);
+      i.setClassIndex(Integer.parseInt(args[1]));
+
+      //      bv.setClassifier(new Logistic());
+      final int xatt = Integer.parseInt(args[2]);
+      final int yatt = Integer.parseInt(args[3]);
+      int base = Integer.parseInt(args[4]);
+      int loc = Integer.parseInt(args[5]);
+
+      int bandWidth = Integer.parseInt(args[6]);
+      int panelWidth = Integer.parseInt(args[7]);
+      int panelHeight = Integer.parseInt(args[8]);
+
+      final String classifierName = args[9];
+      final BoundaryPanelDistributed bv = 
+	new BoundaryPanelDistributed(panelWidth,panelHeight);
+      bv.addRemoteExperimentListener(new RemoteExperimentListener() {
+	  public void remoteExperimentStatus(RemoteExperimentEvent e) {
+	    if (e.m_experimentFinished) {
+	      String classifierNameNew = 
+		classifierName.substring(classifierName.lastIndexOf('.')+1, 
+					 classifierName.length());
+	      bv.saveImage(classifierNameNew+"_"+i.relationName()
+			   +"_X"+xatt+"_Y"+yatt+".jpg");
+	    } else {
+	      System.err.println(e.m_messageString);
+	    }
+	  }
+	});
+      bv.setRemoteHosts(hostNames);
+
+      jf.getContentPane().add(bv, BorderLayout.CENTER);
+      jf.setSize(bv.getMinimumSize());
+      //      jf.setSize(200,200);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+
+      jf.pack();
+      jf.setVisible(true);
+      //      bv.initialize();
+      bv.repaint();
+      
+
+      String [] argsR = null;
+      if (args.length > 10) {
+	argsR = new String [args.length-10];
+	for (int j = 10; j < args.length; j++) {
+	  argsR[j-10] = args[j];
+	}
+      }
+      Classifier c = AbstractClassifier.forName(args[9], argsR);
+      KDDataGenerator dataGen = new KDDataGenerator();
+      dataGen.setKernelBandwidth(bandWidth);
+      bv.setDataGenerator(dataGen);
+      bv.setNumSamplesPerRegion(loc);
+      bv.setGeneratorSamplesBase(base);
+      bv.setClassifier(c);
+      bv.setTrainingData(i);
+      bv.setXAttribute(xatt);
+      bv.setYAttribute(yatt);
+
+      try {
+	// try and load a color map if one exists
+	FileInputStream fis = new FileInputStream("colors.ser");
+	ObjectInputStream ois = new ObjectInputStream(fis);
+	FastVector colors = (FastVector)ois.readObject();
+	bv.setColors(colors);	
+      } catch (Exception ex) {
+	System.err.println("No color map file");
+      }
+      bv.start();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryVisualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryVisualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/BoundaryVisualizer.java	(revision 29)
@@ -0,0 +1,1242 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   BoundaryVisualizer.java
+ *   Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.DenseInstance;
+import weka.core.TechnicalInformation;
+import weka.core.TechnicalInformationHandler;
+import weka.core.Utils;
+import weka.core.TechnicalInformation.Field;
+import weka.core.TechnicalInformation.Type;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertyPanel;
+import weka.gui.visualize.ClassPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.ObjectOutputStream;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+
+/**
+ * BoundaryVisualizer. Allows the visualization of classifier decision
+ * boundaries in two dimensions. A supplied classifier is first
+ * trained on supplied training data, then a data generator (currently
+ * using kernels) is used to generate new instances at points fixed in
+ * the two visualization dimensions but random in the other
+ * dimensions. These instances are classified by the classifier and
+ * plotted as points with colour corresponding to the probability
+ * distribution predicted by the classifier. At present, 2 * 2^(#
+ * non-fixed dimensions) points are generated from each kernel per
+ * pixel in the display. In practice, fewer points than this are
+ * actually classified because kernels are weighted (on a per-pixel
+ * basis) according to the fixexd dimensions and kernels corresponding
+ * to the lowest 1% of the weight mass are discarded. Predicted
+ * probability distributions are weighted (acording to the fixed
+ * visualization dimensions) and averaged to produce an RGB value for
+ * the pixel. For more information, see<p>
+ * 
+ * Eibe Frank and Mark Hall (2003). Visualizing Class Probability
+ * Estimators. Working Paper 02/03, Department of Computer Science,
+ * University of Waikato.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5987 $
+ * @since 1.0
+ * @see JPanel 
+ */
+public class BoundaryVisualizer
+  extends JPanel implements TechnicalInformationHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3933877580074013208L;
+
+  /**
+   * Inner class to handle rendering the axis
+   *
+   * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+   * @version $Revision: 5987 $
+   * @since 1.0
+   * @see JPanel
+   */
+  private class AxisPanel
+    extends JPanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -7421022416674492712L;
+    
+    private static final int MAX_PRECISION = 10;
+    private boolean m_vertical = false;
+    private final int PAD = 5;
+    private FontMetrics m_fontMetrics;
+    private int m_fontHeight;
+    
+    public AxisPanel(boolean vertical) {
+      m_vertical = vertical;
+      this.setBackground(Color.black);
+      //      Graphics g = this.getGraphics();
+      String fontFamily = this.getFont().getFamily();
+      Font newFont = new Font(fontFamily, Font.PLAIN, 10);
+      this.setFont(newFont);
+    }
+
+    public Dimension getPreferredSize() {
+      if (m_fontMetrics == null) {
+	Graphics g = this.getGraphics();
+	m_fontMetrics = g.getFontMetrics();
+	m_fontHeight = m_fontMetrics.getHeight();
+      }
+      if (!m_vertical) {
+	return new Dimension(this.getSize().width, PAD+2+m_fontHeight);
+      }
+      return new Dimension(50, this.getSize().height);
+    }
+
+    public void paintComponent(Graphics g) {
+      super.paintComponent(g);
+      this.setBackground(Color.black);
+      if (m_fontMetrics == null) {
+	m_fontMetrics = g.getFontMetrics();
+	m_fontHeight = m_fontMetrics.getHeight();
+      }
+
+      Dimension d = this.getSize();
+      Dimension d2 = m_boundaryPanel.getSize();
+      g.setColor(Color.gray);
+      int hf = m_fontMetrics.getAscent();
+      if (!m_vertical) {
+	g.drawLine(d.width, PAD, d.width-d2.width, PAD);
+	// try and draw some scale values
+	if (getInstances() != null) {
+	  int precisionXmax = 1;
+	  int precisionXmin = 1;
+	  int whole = (int)Math.abs(m_maxX);
+	  double decimal = Math.abs(m_maxX) - whole;
+	  int nondecimal;
+	  nondecimal = (whole > 0) 
+	    ? (int)(Math.log(whole) / Math.log(10))
+	    : 1;
+	  
+	  precisionXmax = (decimal > 0) 
+	    ? (int)Math.abs(((Math.log(Math.abs(m_maxX)) / 
+			      Math.log(10))))+2
+	    : 1;
+	  if (precisionXmax > MAX_PRECISION) {
+	    precisionXmax = 1;
+	  }
+	  String maxStringX = Utils.doubleToString(m_maxX,
+						   nondecimal+1+precisionXmax
+						   ,precisionXmax);
+	  
+	  whole = (int)Math.abs(m_minX);
+	  decimal = Math.abs(m_minX) - whole;
+	  nondecimal = (whole > 0) 
+	    ? (int)(Math.log(whole) / Math.log(10))
+	    : 1;
+	  precisionXmin = (decimal > 0) 
+	    ? (int)Math.abs(((Math.log(Math.abs(m_minX)) / 
+			      Math.log(10))))+2
+	    : 1;
+	  if (precisionXmin > MAX_PRECISION) {
+	    precisionXmin = 1;
+	  }
+	  
+	  String minStringX = Utils.doubleToString(m_minX,
+						   nondecimal+1+precisionXmin,
+						   precisionXmin);
+	  g.drawString(minStringX,  d.width-d2.width, PAD+hf+2);
+	  int maxWidth = m_fontMetrics.stringWidth(maxStringX);
+	  g.drawString(maxStringX, d.width-maxWidth, PAD+hf+2);
+	}
+      } else {
+	g.drawLine(d.width-PAD, 0, d.width-PAD, d2.height);
+	// try and draw some scale values
+	if (getInstances() != null) {
+	  int precisionYmax = 1;
+	  int precisionYmin = 1;
+	  int whole = (int)Math.abs(m_maxY);
+	  double decimal = Math.abs(m_maxY) - whole;
+	  int nondecimal;
+	  nondecimal = (whole > 0) 
+	    ? (int)(Math.log(whole) / Math.log(10))
+	    : 1;
+	  
+	  precisionYmax = (decimal > 0) 
+	    ? (int)Math.abs(((Math.log(Math.abs(m_maxY)) / 
+			      Math.log(10))))+2
+	    : 1;
+	  if (precisionYmax > MAX_PRECISION) {
+	    precisionYmax = 1;
+	  }
+	  String maxStringY = Utils.doubleToString(m_maxY,
+						   nondecimal+1+precisionYmax
+						   ,precisionYmax);
+	  
+	  whole = (int)Math.abs(m_minY);
+	  decimal = Math.abs(m_minY) - whole;
+	  nondecimal = (whole > 0) 
+	    ? (int)(Math.log(whole) / Math.log(10))
+	    : 1;
+	  precisionYmin = (decimal > 0) 
+	    ? (int)Math.abs(((Math.log(Math.abs(m_minY)) / 
+			      Math.log(10))))+2
+	    : 1;
+	  if (precisionYmin > MAX_PRECISION) {
+	    precisionYmin = 1;
+	  }
+	  
+	  String minStringY = Utils.doubleToString(m_minY,
+						   nondecimal+1+precisionYmin,
+						   precisionYmin);
+	  int maxWidth = m_fontMetrics.stringWidth(minStringY);
+	  g.drawString(minStringY,  d.width-PAD-maxWidth-2, d2.height);
+	  maxWidth = m_fontMetrics.stringWidth(maxStringY);
+	  g.drawString(maxStringY, d.width-PAD-maxWidth-2, hf);
+	}
+      }
+    }
+  }
+  
+  /** the number of visualizer windows we have open. */
+  protected static int m_WindowCount = 0; 
+  
+  /** whether the exit if there are no more windows open */
+  protected static boolean m_ExitIfNoWindowsOpen = true;
+
+  /** the training instances */
+  private Instances m_trainingInstances;
+
+  /** the classifier to use */
+  private Classifier m_classifier;
+
+  // plot area dimensions
+  protected int m_plotAreaWidth = 384;
+  //protected int m_plotAreaHeight = 384;
+  protected int m_plotAreaHeight = 384;
+
+  /** the plotting panel */
+  protected BoundaryPanel m_boundaryPanel;
+
+  // combo boxes for selecting the class attribute, class values (for
+  // colouring pixels), and visualization attributes
+  protected JComboBox m_classAttBox = new JComboBox();
+  protected JComboBox m_xAttBox = new JComboBox();
+  protected JComboBox m_yAttBox = new JComboBox();
+
+  protected Dimension COMBO_SIZE = 
+    new Dimension((int)(m_plotAreaWidth * 0.75),
+		  m_classAttBox.getPreferredSize().height);
+
+  protected JButton m_startBut = new JButton("Start");
+
+  protected JCheckBox m_plotTrainingData = new JCheckBox("Plot training data");
+
+  protected JPanel m_controlPanel;
+
+  protected ClassPanel m_classPanel = new ClassPanel();
+
+  // separate panels for rendering axis information
+  private AxisPanel m_xAxisPanel;
+  private AxisPanel m_yAxisPanel;
+
+  // min and max values for visualization dimensions
+  private double m_maxX;
+  private double m_maxY;
+  private double m_minX;
+  private double m_minY;
+
+  private int m_xIndex;
+  private int m_yIndex;
+
+  /* Kernel density estimator/generator */
+  private KDDataGenerator m_dataGenerator;
+
+  /* number of samples per pixel (fixed dimensions only) */
+  private int m_numberOfSamplesFromEachRegion;
+
+  /** base for sampling in the non-fixed dimensions */
+  private int m_generatorSamplesBase;
+
+  /** Set the kernel bandwidth to cover this many nearest neighbours */
+  private int m_kernelBandwidth;
+  
+  private JTextField m_regionSamplesText = 
+    new JTextField(""+0);
+
+  private JTextField m_generatorSamplesText = 
+    new JTextField(""+0);
+
+  private JTextField m_kernelBandwidthText = 
+    new JTextField(""+3+"  ");
+
+  //jimmy
+  protected GenericObjectEditor m_classifierEditor = new GenericObjectEditor(); //the widget to select the classifier
+  protected PropertyPanel m_ClassifierPanel = new PropertyPanel(m_classifierEditor);
+  /** The file chooser for selecting arff files */
+  protected JFileChooser m_FileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+  protected ExtensionFileFilter m_arffFileFilter = 
+    new ExtensionFileFilter(Instances.FILE_EXTENSION,
+			    "Arff data files");
+  protected JLabel dataFileLabel = new JLabel(); //stores the name of the data file (currently stores relation name rather than filename)
+  protected JPanel m_addRemovePointsPanel = new JPanel(); //a panel which contains the controls to add and remove points
+  protected JComboBox m_classValueSelector = new JComboBox(); //a widget to select the class attribute.
+  protected JRadioButton m_addPointsButton = new JRadioButton(); //when this is selected, clicking on the BoundaryPanel will add points.
+  protected JRadioButton m_removePointsButton = new JRadioButton(); //when this is selected, clicking on the BoundaryPanel will remove points.
+  protected ButtonGroup m_addRemovePointsButtonGroup = new ButtonGroup();
+  protected JButton removeAllButton = new JButton ("Remove all"); //button to remove all points
+  protected JButton chooseButton = new JButton("Open File"); //button to choose a data file
+  
+  /* Register the property editors we need */
+  static {
+    GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Returns a string describing this tool
+   * @return a description of the tool suitable for
+   * displaying in various Weka GUIs
+   */
+  public String globalInfo() {
+    return "Class for visualizing class probability estimates.\n\n"
+    + "For more information, see\n\n"
+    + getTechnicalInformation().toString();
+  }
+  
+  /**
+   * Returns an instance of a TechnicalInformation object, containing 
+   * detailed information about the technical background of this class,
+   * e.g., paper reference or book this class is based on.
+   * 
+   * @return the technical information about this class
+   */
+  public TechnicalInformation getTechnicalInformation() {
+    TechnicalInformation        result;
+
+    result = new TechnicalInformation(Type.INPROCEEDINGS);
+    result.setValue(Field.AUTHOR, "Eibe Frank and Mark Hall");
+    result.setValue(Field.TITLE, "Visualizing class probability estimators");
+    result.setValue(Field.BOOKTITLE, "European Conference on Principles and Practice of " +
+    		"Knowledge Discovery in Databases");
+    result.setValue(Field.YEAR, "2003");
+    result.setValue(Field.PAGES, "168-169");
+    result.setValue(Field.PUBLISHER, "Springer-Verlag");
+    result.setValue(Field.ADDRESS, "Cavtat-Dubrovnik");
+
+    return result;
+  }
+
+
+  /**
+   * Creates a new <code>BoundaryVisualizer</code> instance.
+   */
+  public BoundaryVisualizer() {
+    
+    setLayout(new BorderLayout());
+    m_classAttBox.setMinimumSize(COMBO_SIZE);
+    m_classAttBox.setPreferredSize(COMBO_SIZE);
+    m_classAttBox.setMaximumSize(COMBO_SIZE);
+    m_classAttBox.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        if (m_classAttBox.getItemCount() != 0)
+	{
+		try { 
+			m_classPanel.setCindex(m_classAttBox.getSelectedIndex());
+			plotTrainingData();
+			System.err.println("Here in class att box listener");
+		} catch (Exception ex) {ex.printStackTrace();}
+		
+		//set up the add points selector combo box. -jimmy
+		setUpClassValueSelectorCB();
+	}
+      }
+      });
+	    
+
+    m_xAttBox.setMinimumSize(COMBO_SIZE);
+    m_xAttBox.setPreferredSize(COMBO_SIZE);
+    m_xAttBox.setMaximumSize(COMBO_SIZE);
+
+    m_yAttBox.setMinimumSize(COMBO_SIZE);
+    m_yAttBox.setPreferredSize(COMBO_SIZE);
+    m_yAttBox.setMaximumSize(COMBO_SIZE);
+
+    m_classPanel.setMinimumSize(new 
+      Dimension((int)COMBO_SIZE.getWidth()*2, 
+		(int)COMBO_SIZE.getHeight()*2));
+    m_classPanel.setPreferredSize(new 
+      Dimension((int)COMBO_SIZE.getWidth()*2, 
+		(int)COMBO_SIZE.getHeight()*2));
+
+
+    m_controlPanel = new JPanel();
+    m_controlPanel.setLayout(new BorderLayout());
+    
+    //jimmy
+    JPanel dataChooseHolder = new JPanel(new BorderLayout());
+    dataChooseHolder.setBorder(BorderFactory.createTitledBorder("Dataset"));
+    dataChooseHolder.add(dataFileLabel, BorderLayout.WEST);
+    
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    m_FileChooser.addChoosableFileFilter(m_arffFileFilter);
+    chooseButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+		setInstancesFromFileQ();
+		int classIndex = m_classAttBox.getSelectedIndex();
+		if (m_trainingInstances != null && m_classifier != null && (m_trainingInstances.attribute(classIndex).isNominal())) {
+			m_startBut.setEnabled(true);
+//			plotTrainingData();
+		}
+		
+		
+	} catch (Exception ex) {
+		ex.printStackTrace(System.out);
+		System.err.println("exception");
+	}
+	
+      }
+    });
+    dataChooseHolder.add(chooseButton, BorderLayout.EAST);
+    
+    JPanel classifierHolder = new JPanel();
+    classifierHolder.setBorder(BorderFactory.createTitledBorder("Classifier"));
+    classifierHolder.setLayout(new BorderLayout());
+    m_classifierEditor.setClassType(weka.classifiers.Classifier.class);
+    
+    m_classifierEditor.addPropertyChangeListener(new PropertyChangeListener() {
+    	public void propertyChange(PropertyChangeEvent evt) {
+		m_classifier = (Classifier)m_classifierEditor.getValue();
+		try {
+			int classIndex = m_classAttBox.getSelectedIndex();
+			if (m_trainingInstances != null && m_classifier != null && (m_trainingInstances.attribute(classIndex).isNominal())) {
+				m_startBut.setEnabled(true);
+			}
+		} catch (Exception ex) {};
+	}
+    });
+    classifierHolder.add(m_ClassifierPanel, BorderLayout.CENTER);
+    
+        
+
+    JPanel cHolder = new JPanel();
+    cHolder.setBorder(BorderFactory.createTitledBorder("Class Attribute"));
+    cHolder.add(m_classAttBox);
+
+    JPanel vAttHolder = new JPanel();
+    vAttHolder.setLayout(new GridLayout(2,1));
+    vAttHolder.setBorder(BorderFactory.
+			 createTitledBorder("Visualization Attributes"));
+    vAttHolder.add(m_xAttBox);
+    vAttHolder.add(m_yAttBox);
+
+    JPanel colOne = new JPanel();
+    colOne.setLayout(new BorderLayout());
+    colOne.add(dataChooseHolder, BorderLayout.NORTH); //jimmy
+    colOne.add(cHolder, BorderLayout.CENTER);
+    //colOne.add(vAttHolder, BorderLayout.SOUTH);
+
+    JPanel tempPanel = new JPanel();
+    tempPanel.setBorder(BorderFactory.
+			createTitledBorder("Sampling control"));
+    tempPanel.setLayout(new GridLayout(3,1));
+
+    JPanel colTwo = new JPanel();
+    colTwo.setLayout(new BorderLayout());
+    JPanel gsP = new JPanel(); gsP.setLayout(new BorderLayout());
+    gsP.add(new JLabel(" Base for sampling (r)"), BorderLayout.CENTER);
+    gsP.add(m_generatorSamplesText, BorderLayout.WEST);
+    tempPanel.add(gsP);
+
+    JPanel rsP = new JPanel(); rsP.setLayout(new BorderLayout());
+    rsP.add(new JLabel(" Num. locations per pixel"), BorderLayout.CENTER);
+    rsP.add(m_regionSamplesText, BorderLayout.WEST);
+    tempPanel.add(rsP);
+
+    JPanel ksP = new JPanel(); ksP.setLayout(new BorderLayout());
+    ksP.add(new JLabel(" Kernel bandwidth (k)"), BorderLayout.CENTER);
+    ksP.add(m_kernelBandwidthText, BorderLayout.WEST);
+    tempPanel.add(ksP);
+    
+    colTwo.add(classifierHolder,BorderLayout.NORTH);//jimmy
+    //colTwo.add(tempPanel, BorderLayout.CENTER);
+    colTwo.add(vAttHolder, BorderLayout.CENTER);
+
+    JPanel startPanel = new JPanel();
+    startPanel.setBorder(BorderFactory.
+			 createTitledBorder("Plotting"));
+    startPanel.setLayout(new BorderLayout());
+    startPanel.add(m_startBut, BorderLayout.CENTER);
+    startPanel.add(m_plotTrainingData, BorderLayout.WEST);
+
+    //colTwo.add(startPanel, BorderLayout.SOUTH);
+
+    m_controlPanel.add(colOne, BorderLayout.WEST);
+    m_controlPanel.add(colTwo, BorderLayout.CENTER);
+    JPanel classHolder = new JPanel();
+    classHolder.setLayout(new BorderLayout()); //jimmy
+    classHolder.setBorder(BorderFactory.createTitledBorder("Class color"));
+    classHolder.add(m_classPanel, BorderLayout.CENTER);
+    m_controlPanel.add(classHolder, BorderLayout.SOUTH);
+    
+    JPanel aboutAndControlP = new JPanel();
+    aboutAndControlP.setLayout(new BorderLayout());
+    aboutAndControlP.add(m_controlPanel, BorderLayout.SOUTH);
+    
+    weka.gui.PropertySheetPanel psp = new weka.gui.PropertySheetPanel();
+    psp.setTarget(BoundaryVisualizer.this);
+    JPanel aboutPanel = psp.getAboutPanel();
+    
+    aboutAndControlP.add(aboutPanel, BorderLayout.NORTH);
+
+    add(aboutAndControlP, BorderLayout.NORTH);
+    
+    //classHolder.add(newWindowButton, BorderLayout.EAST);
+   
+    // set up the add-remove points widgets
+    m_addRemovePointsPanel.setBorder(BorderFactory.createTitledBorder("Add / remove data points"));
+    m_addRemovePointsPanel.setLayout(new GridBagLayout());
+    GridBagConstraints constraints = new GridBagConstraints();
+    constraints.weightx = 1.0;
+    constraints.weighty = 1.0;
+    constraints.gridx = 0;
+    constraints.gridy = 0;
+    constraints.fill = GridBagConstraints.BOTH;
+    m_addRemovePointsPanel.add(m_addPointsButton);
+    constraints.gridx = 1;
+    m_addRemovePointsPanel.add(new JLabel("Add points"), constraints);
+    constraints.gridx = 2;
+    m_addRemovePointsPanel.add(m_classValueSelector);
+    constraints.gridx = 0;
+    constraints.gridy = 1;
+    m_addRemovePointsPanel.add(m_removePointsButton, constraints);
+    constraints.gridx = 1;
+    m_addRemovePointsPanel.add(new JLabel("Remove points"),constraints);
+    
+    
+    	removeAllButton.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+			if (m_trainingInstances != null)
+			{
+				if (m_startBut.getText().equals("Stop")) //we are plotting
+					return;
+				m_boundaryPanel.removeAllInstances();
+				computeBounds();
+				m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
+				m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
+	
+				try {m_boundaryPanel.plotTrainingData(); } catch (Exception ex) {}
+			}
+		}	
+	});
+    constraints.gridx = 2;
+    m_addRemovePointsPanel.add(removeAllButton, constraints);
+    
+//     m_addRemovePointsPanel.add(addPointsFrame, BorderLayout.NORTH);
+//     m_addRemovePointsPanel.add(removePointsFrame, BorderLayout.CENTER);
+    //m_addRemovePointsPanel.add(removeAllButton, BorderLayout.SOUTH);
+    
+    
+    m_addRemovePointsButtonGroup.add(m_addPointsButton);
+    m_addRemovePointsButtonGroup.add(m_removePointsButton);
+    m_addPointsButton.setSelected(true);
+        
+    //classHolder.add(m_addRemovePointsPanel, BorderLayout.SOUTH);
+    
+
+    m_boundaryPanel = new BoundaryPanel(m_plotAreaWidth, m_plotAreaHeight);
+    m_numberOfSamplesFromEachRegion = m_boundaryPanel.getNumSamplesPerRegion();
+    m_regionSamplesText.setText(""+m_numberOfSamplesFromEachRegion+"  ");
+    m_generatorSamplesBase = (int)m_boundaryPanel.getGeneratorSamplesBase();
+    m_generatorSamplesText.setText(""+m_generatorSamplesBase+"  ");
+
+    m_dataGenerator = new KDDataGenerator();
+    m_kernelBandwidth = m_dataGenerator.getKernelBandwidth();
+    m_kernelBandwidthText.setText(""+m_kernelBandwidth+"  ");
+    m_boundaryPanel.setDataGenerator(m_dataGenerator);
+    
+     
+    JPanel gfxPanel = new JPanel();
+    gfxPanel.setLayout(new BorderLayout());
+    gfxPanel.setBorder(BorderFactory.createEtchedBorder());
+    //add(gfxPanel, BorderLayout.CENTER);
+        
+   // gfxPanel.add(m_addRemovePointsPanel, BorderLayout.NORTH);
+    gfxPanel.add(m_boundaryPanel, BorderLayout.CENTER);
+    m_xAxisPanel = new AxisPanel(false);
+    gfxPanel.add(m_xAxisPanel, BorderLayout.SOUTH);
+    m_yAxisPanel = new AxisPanel(true);
+    gfxPanel.add(m_yAxisPanel, BorderLayout.WEST);
+    
+    JPanel containerPanel = new JPanel();
+    containerPanel.setLayout(new BorderLayout());
+    containerPanel.add(gfxPanel, BorderLayout.CENTER);
+    add(containerPanel, BorderLayout.WEST);
+    
+    JPanel rightHandToolsPanel = new JPanel(); //this panel contains the widgets to the right of the BoundaryPanel.
+    rightHandToolsPanel.setLayout(new BoxLayout(rightHandToolsPanel, BoxLayout.PAGE_AXIS));
+
+    rightHandToolsPanel.add(m_addRemovePointsPanel);
+    
+    JButton newWindowButton = new JButton("Open a new window"); //the button for spawning a new window for the program.
+    //newWindowButton.setMaximumSize(new Dimension(100, 100));
+    //newWindowButton.setPreferredSize(new Dimension(120, m_addRemovePointsPanel.getHeight()));
+    newWindowButton.addActionListener(new ActionListener() {
+    	public void actionPerformed(ActionEvent e) {
+		try {
+			Instances newTrainingData = null;
+			Classifier newClassifier = null;
+			if (m_trainingInstances != null)
+				newTrainingData = new Instances(m_trainingInstances);
+			if (m_classifier != null)
+				newClassifier = AbstractClassifier.makeCopy(m_classifier);
+			createNewVisualizerWindow(newClassifier, newTrainingData);
+		} catch (Exception ex) {  ex.printStackTrace();}
+	}
+    });
+    JPanel newWindowHolder = new JPanel();
+    newWindowHolder.add(newWindowButton);
+    rightHandToolsPanel.add(newWindowHolder);
+    rightHandToolsPanel.add(tempPanel);
+    rightHandToolsPanel.add(startPanel);
+    
+    containerPanel.add(rightHandToolsPanel, BorderLayout.EAST);
+        
+    /*add(m_boundaryPanel, BorderLayout.CENTER);
+
+    m_xAxisPanel = new AxisPanel(false);
+    add(m_xAxisPanel, BorderLayout.SOUTH);
+    m_yAxisPanel = new AxisPanel(true);
+    add(m_yAxisPanel, BorderLayout.WEST);*/
+
+    m_startBut.setEnabled(false);
+    m_startBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_startBut.getText().equals("Start")) {
+	    if (m_trainingInstances != null && m_classifier != null) {
+		try {
+			
+			int BPSuccessCode = setUpBoundaryPanel(); //set up the boundary panel, find out if it was successful or not.
+			
+			if (BPSuccessCode == 1)
+				JOptionPane.showMessageDialog(null,"Error: Kernel Bandwidth can't be less than zero!");
+			else if (BPSuccessCode == 2) {
+				JOptionPane.showMessageDialog(null,"Error: Kernel Bandwidth must be less than the number of training instances!");
+			} else {
+				m_boundaryPanel.start();
+				m_startBut.setText("Stop");
+				setControlEnabledStatus(false);
+			}
+		} catch (Exception ex) {
+			ex.printStackTrace();
+		}
+	    }
+	  } else {
+	    m_boundaryPanel.stopPlotting();
+	    m_startBut.setText("Start");
+	    setControlEnabledStatus(true);
+	  }
+	}
+      });
+
+    m_boundaryPanel.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_startBut.setText("Start");
+	  setControlEnabledStatus(true);
+	}
+      });
+
+    m_classPanel.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+
+	  try {
+	    // save color vector to a file
+	    FastVector colors = m_boundaryPanel.getColors();
+	    FileOutputStream fos = new FileOutputStream("colors.ser");
+	    ObjectOutputStream oos = new ObjectOutputStream(fos);
+	    oos.writeObject(colors);
+	    oos.flush();
+	    oos.close();
+	  } catch (Exception ex) {}
+
+	  m_boundaryPanel.replot();
+	  
+	}
+      });
+      
+    //set up a mouse listener for the boundary panel.
+    m_boundaryPanel.addMouseListener(new MouseAdapter() {
+    	public void mouseClicked(MouseEvent e) {
+// 		System.err.println("boundary panel mouseClick " + e.getX() + " " + e.getY());
+		if (m_trainingInstances != null) {
+			if (m_startBut.getText().equals("Stop")) //we are plotting
+				return;
+		
+			if (m_addPointsButton.isSelected()) {//we are in add mode
+				double classVal = 0;
+				boolean validInput = true;
+				if (m_trainingInstances.attribute(m_classAttBox.getSelectedIndex()).isNominal()) //class is nominal
+					classVal = (double)m_classValueSelector.getSelectedIndex();
+				else {
+					String indexStr = "";
+					try {					
+						indexStr = (String)m_classValueSelector.getSelectedItem();
+						classVal = Double.parseDouble(indexStr);
+					} catch (Exception ex) {
+						if (indexStr == null) indexStr = "";
+						JOptionPane.showMessageDialog(null,"Error adding a point: \"" + indexStr + "\""
+							+ " is not a valid class value.");
+						validInput = false;
+					}
+				}
+				//System.err.println("classVal is " + classVal);
+				if (validInput)
+					m_boundaryPanel.addTrainingInstanceFromMouseLocation(e.getX(), e.getY(), m_classAttBox.getSelectedIndex(), classVal);
+			}
+			else { //remove mode
+				m_boundaryPanel.removeTrainingInstanceFromMouseLocation(e.getX(), e.getY());
+			}
+			try{ plotTrainingData(); } catch (Exception ex) {} //jimmy
+			m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
+    			m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
+		}
+	}
+    });
+  }
+    
+  /**
+   * Set the enabled status of the controls
+   *
+   * @param status a <code>boolean</code> value
+   */
+  private void setControlEnabledStatus(boolean status) {
+    m_classAttBox.setEnabled(status);
+    m_xAttBox.setEnabled(status);
+    m_yAttBox.setEnabled(status);
+    m_regionSamplesText.setEnabled(status);
+    m_generatorSamplesText.setEnabled(status);
+    m_kernelBandwidthText.setEnabled(status);
+    m_plotTrainingData.setEnabled(status);
+    removeAllButton.setEnabled(status);
+    m_classValueSelector.setEnabled(status);
+    m_addPointsButton.setEnabled(status);
+    m_removePointsButton.setEnabled(status);
+    m_FileChooser.setEnabled(status);
+    chooseButton.setEnabled(status);
+  }
+
+  /**
+   * Set a classifier to use
+   *
+   * @param newClassifier the classifier to use
+   * @exception Exception if an error occurs
+   */
+  public void setClassifier(Classifier newClassifier) throws Exception {
+
+    m_classifier = newClassifier;
+    
+    try {
+	int classIndex = m_classAttBox.getSelectedIndex();
+	
+	if ((m_classifier != null) && (m_trainingInstances != null) &&
+		(m_trainingInstances.attribute(classIndex).isNominal())) {
+		m_startBut.setEnabled(true);
+	}
+	else
+		m_startBut.setEnabled(false);
+    } catch (Exception e) {}
+    
+  }
+  
+  /** Sets up the bounds on our x and y axes to fit the dataset.
+      Also repaints the x and y axes.
+  */
+  private void computeBounds() {
+  
+    m_boundaryPanel.computeMinMaxAtts(); //delegate to the BoundaryPanel
+  
+    String xName = (String)m_xAttBox.getSelectedItem();
+    if (xName == null) {
+      return;
+    }
+    xName = Utils.removeSubstring(xName, "X: ");
+    xName = Utils.removeSubstring(xName, " (Num)");
+    String yName = (String)m_yAttBox.getSelectedItem();
+    yName = Utils.removeSubstring(yName, "Y: ");
+    yName = Utils.removeSubstring(yName, " (Num)");
+
+    m_xIndex = -1;
+    m_yIndex = -1;
+    for (int i = 0; i < m_trainingInstances.numAttributes(); i++) {
+      if (m_trainingInstances.attribute(i).name().equals(xName)) {
+	m_xIndex = i;
+      } 
+      if (m_trainingInstances.attribute(i).name().equals(yName)) {
+	m_yIndex = i;
+      }
+    }
+    
+    m_minX = m_boundaryPanel.getMinXBound();
+    m_minY = m_boundaryPanel.getMinYBound();
+    m_maxX = m_boundaryPanel.getMaxXBound();
+    m_maxY = m_boundaryPanel.getMaxYBound();
+    //System.err.println("setting bounds to " + m_minX + " " + m_minY + " " + m_maxX + " " + m_maxY);
+    m_xAxisPanel.repaint(0,0,0,m_xAxisPanel.getWidth(), m_xAxisPanel.getHeight());
+    m_yAxisPanel.repaint(0,0,0,m_yAxisPanel.getWidth(), m_yAxisPanel.getHeight());
+  }
+
+  /**
+   * Get the training instances
+   *
+   * @return the training instances
+   */
+  public Instances getInstances() {
+    return m_trainingInstances;
+  }
+
+  /**
+   * Set the training instances
+   *
+   * @param inst the instances to use
+   */
+  public void setInstances(Instances inst) throws Exception {
+    if (inst == null) {
+    	m_trainingInstances = inst;
+    	m_classPanel.setInstances(m_trainingInstances);
+	return;
+    }
+    
+    // count the number of numeric attributes
+    int numCount = 0;
+    for (int i = 0; i < inst.numAttributes(); i++) {
+      if (inst.attribute(i).isNumeric()) {
+        numCount++;
+      }
+    }
+    
+    if (numCount < 2) {
+      JOptionPane.showMessageDialog(null,"We need at least two numeric " +
+      		"attributes in order to visualize!");
+      return;
+    }
+        
+    m_trainingInstances = inst;
+    m_classPanel.setInstances(m_trainingInstances);
+    // setup combo boxes
+    String [] classAttNames = new String [m_trainingInstances.numAttributes()];
+    final Vector xAttNames = new Vector();
+    Vector yAttNames = new Vector();
+
+    for (int i = 0; i < m_trainingInstances.numAttributes(); i++) {
+      classAttNames[i] = m_trainingInstances.attribute(i).name();
+      String type = "";
+      switch (m_trainingInstances.attribute(i).type()) {
+	case Attribute.NOMINAL:
+	  type = " (Nom)";
+	  break;
+	case Attribute.NUMERIC:
+	  type = " (Num)";
+	  break;
+	case Attribute.STRING:
+	  type = " (Str)";
+	  break;
+	case Attribute.DATE:
+	  type = " (Dat)";
+	  break;
+	case Attribute.RELATIONAL:
+	  type = " (Rel)";
+	  break;
+	default:
+	  type = " (???)";
+      }
+      classAttNames[i] += type;
+      if (m_trainingInstances.attribute(i).isNumeric()) {
+	xAttNames.addElement("X: "+classAttNames[i]);
+	yAttNames.addElement("Y: "+classAttNames[i]);
+      }
+    }
+
+    m_classAttBox.setModel(new DefaultComboBoxModel(classAttNames));
+    m_xAttBox.setModel(new DefaultComboBoxModel(xAttNames));
+    m_yAttBox.setModel(new DefaultComboBoxModel(yAttNames));
+    if (xAttNames.size() > 1) {
+      m_yAttBox.setSelectedIndex(1);
+    }
+    
+    m_classAttBox.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  configureForClassAttribute();
+	}
+      });
+
+    m_xAttBox.addItemListener(new ItemListener() {
+	public void itemStateChanged(ItemEvent e) {
+	  if (e.getStateChange() == ItemEvent.SELECTED) {
+/*	    if (xAttNames.size() > 1) {
+	      if (m_xAttBox.getSelectedIndex() == 
+		  m_yAttBox.getSelectedIndex()) {
+		m_xAttBox.setSelectedIndex((m_xAttBox.getSelectedIndex() + 1) %
+					   xAttNames.size());
+	      }
+	    } */
+	    computeBounds();
+	    repaint();
+	    try{ plotTrainingData(); } catch (Exception ex) {ex.printStackTrace();} //jimmy	    
+	  }
+	}
+      });
+
+    m_yAttBox.addItemListener(new ItemListener() {
+	public void itemStateChanged(ItemEvent e) {
+	  if (e.getStateChange() == ItemEvent.SELECTED) {
+/*	    if (xAttNames.size() > 1) {
+	      if (m_yAttBox.getSelectedIndex() == 
+		  m_xAttBox.getSelectedIndex()) {
+		m_yAttBox.setSelectedIndex((m_yAttBox.getSelectedIndex() + 1) %
+					   xAttNames.size());
+	      }
+	    } */
+	    computeBounds();
+	    repaint();
+	    try{ plotTrainingData(); } catch (Exception ex) {ex.printStackTrace();}
+	  }
+	}
+      });
+    
+    if (classAttNames.length > 0)
+      m_classAttBox.setSelectedIndex(classAttNames.length - 1); //select last attribute as class by default.  -jimmy
+      
+    //set up the add points selector combo box
+    setUpClassValueSelectorCB();
+    
+    configureForClassAttribute();
+    
+    m_classPanel.setCindex(m_classAttBox.getSelectedIndex());
+    plotTrainingData();      
+    computeBounds();
+    revalidate();
+    repaint();
+    
+    if (getTopLevelAncestor() instanceof java.awt.Window) {
+      ((java.awt.Window)getTopLevelAncestor()).pack();
+    }
+  }
+  
+  /** Set up the combo box that chooses which class values to use when adding data points.
+  */
+  private void setUpClassValueSelectorCB() {
+    m_classValueSelector.removeAllItems();
+    int classAttribute = m_classAttBox.getSelectedIndex();
+    //System.err.println(m_trainingInstances.numClasses() + " classes");
+    m_trainingInstances.setClassIndex(classAttribute);
+    if (m_trainingInstances.attribute(classAttribute).isNominal()) {
+    	m_classValueSelector.setEditable(false);
+    	for (int i = 0; i < /*m_trainingInstances.numDistinctValues(classAttribute)*/m_trainingInstances.numClasses(); i++)
+    		m_classValueSelector.insertItemAt(m_trainingInstances.attribute(classAttribute).value(i) , i);
+	m_classValueSelector.setSelectedIndex(0);
+    }
+    else {
+    	m_classValueSelector.setEditable(true);
+    }
+  }
+  
+  /**
+   * Set up the class values combo boxes
+   */
+  private void configureForClassAttribute() {
+    int classIndex = m_classAttBox.getSelectedIndex();
+    if (classIndex >= 0) {
+      // see if this is a nominal attribute
+      if (!m_trainingInstances.attribute(classIndex).isNominal() || m_classifier == null) {
+	m_startBut.setEnabled(false);
+      } else {
+	m_startBut.setEnabled(true);
+      }
+      // set up class colours
+	FastVector colors = new FastVector();
+	if (!m_trainingInstances.attribute(m_classAttBox.getSelectedIndex()).isNominal()) //this if by jimmy
+	{
+		for (int i = 0; i < BoundaryPanel.DEFAULT_COLORS.length; i++)
+			colors.addElement(BoundaryPanel.DEFAULT_COLORS[i]);
+	}
+	else {
+		for (int i = 0; i < 
+		m_trainingInstances.attribute(classIndex).numValues(); i++) {
+			colors.addElement(BoundaryPanel.
+				DEFAULT_COLORS[i % BoundaryPanel.DEFAULT_COLORS.length]);
+// 			m_classPanel.setColours(colors);	  
+// 			m_boundaryPanel.setColors(colors);
+		}
+	}
+	m_classPanel.setColours(colors); //jimmy
+	m_boundaryPanel.setColors(colors);
+   }
+  }
+  
+    
+  /**
+   * Queries the user for a file to load instances from, then loads the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void setInstancesFromFileQ() {
+    
+//     if (m_IOThread == null) {
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	File selected = m_FileChooser.getSelectedFile();
+	
+	try
+	{
+	java.io.Reader r = new java.io.BufferedReader(
+				new java.io.FileReader(selected));
+	Instances i = new Instances(r);
+	setInstances(i);
+	
+	//dataFileLabel.setText(selected.getName());
+	dataFileLabel.setText(i.relationName());
+	} catch (Exception e)
+	{
+		JOptionPane.showMessageDialog(this,"Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+		    e.printStackTrace();
+	
+	}
+      }
+  }
+  
+  /** Sets up the BoundaryPanel object so that it is ready for plotting.
+   * @return an error code:<br/>
+   *		0 - SUCCESS<br/>
+   *		1 - ERROR - Kernel bandwidth < 0<br/>
+   *		2 - ERROR - Kernel bandwidth >= number of training instances.
+   */
+  public int setUpBoundaryPanel() throws Exception {
+  	int returner = 0; //OK code.
+  	int tempSamples = m_numberOfSamplesFromEachRegion;
+		try {
+		  tempSamples = 
+		    Integer.parseInt(m_regionSamplesText.getText().trim());
+		} catch (Exception ex) {
+		  m_regionSamplesText.setText(""+tempSamples);
+		}
+		m_numberOfSamplesFromEachRegion = tempSamples;
+		m_boundaryPanel.
+		  setNumSamplesPerRegion(tempSamples);
+
+		tempSamples = m_generatorSamplesBase;
+		try {
+		  tempSamples = 
+		    Integer.parseInt(m_generatorSamplesText.getText().trim());
+		} catch (Exception ex) {
+		  m_generatorSamplesText.setText(""+tempSamples);
+		}
+		m_generatorSamplesBase = tempSamples;
+		m_boundaryPanel.setGeneratorSamplesBase((double)tempSamples);
+
+		tempSamples = m_kernelBandwidth;
+		try {
+		  tempSamples = 
+		    Integer.parseInt(m_kernelBandwidthText.getText().trim());
+		} catch (Exception ex) {
+		  m_kernelBandwidthText.setText(""+tempSamples);
+		}
+		m_kernelBandwidth = tempSamples;
+		m_dataGenerator.setKernelBandwidth(tempSamples);
+		
+		if (m_kernelBandwidth < 0)	returner = 1;
+		if (m_kernelBandwidth >= m_trainingInstances.numInstances())	returner = 2;
+
+		m_trainingInstances.
+		  setClassIndex(m_classAttBox.getSelectedIndex());
+		m_boundaryPanel.setClassifier(m_classifier);
+		m_boundaryPanel.setTrainingData(m_trainingInstances);
+		m_boundaryPanel.setXAttribute(m_xIndex);
+		m_boundaryPanel.setYAttribute(m_yIndex);
+		m_boundaryPanel.
+		  setPlotTrainingData(m_plotTrainingData.isSelected());
+
+	return returner;
+  }
+  
+  /** Plots the training data on-screen.  Also does all of the setup required 
+   *  for this to work.
+  */
+  public void plotTrainingData() throws Exception {
+  	m_boundaryPanel.initialize();
+ 	setUpBoundaryPanel();
+	computeBounds();
+	m_boundaryPanel.plotTrainingData();
+  }
+  
+  /** Stops the plotting thread.
+  */
+  public void stopPlotting() {
+  	m_boundaryPanel.stopPlotting();
+  }
+  
+  /**
+   * Sets whether System.exit gets called when no more windows are open.
+   * 
+   * @param value	if TRUE then a System.exit call is ossued after the 
+   * 			last window gets closed.
+   */
+  public static void setExitIfNoWindowsOpen(boolean value) {
+    m_ExitIfNoWindowsOpen = value;
+  }
+  
+  /**
+   * Gets whether System.exit gets called after the last window gets closed
+   * 
+   * @return		TRUE if System.exit gets called after last window
+   * 			got closed.
+   */
+  public static boolean getExitIfNoWindowsOpen() {
+    return m_ExitIfNoWindowsOpen;
+  }
+  
+  /** Creates a new GUI window with all of the BoundaryVisualizer trappings,
+   *  @param classifier The classifier to use in the new window.  May be null.
+   *  @param instances  The dataset to visualize on in the new window.  May be null.
+   */
+  public static void createNewVisualizerWindow(Classifier classifier, Instances instances) throws Exception {
+      m_WindowCount++;
+  
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka classification boundary visualizer");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final BoundaryVisualizer bv = new BoundaryVisualizer();
+      jf.getContentPane().add(bv, BorderLayout.CENTER);
+      jf.setSize(bv.getMinimumSize());
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    m_WindowCount--;
+	    bv.stopPlotting();
+	    jf.dispose();
+	    if ((m_WindowCount == 0) && m_ExitIfNoWindowsOpen) {
+		System.exit(0);
+	    }
+	  }
+	});
+
+      jf.pack();
+      jf.setVisible(true);
+      jf.setResizable(false);
+      
+      if (classifier == null)
+      	bv.setClassifier(null);
+      else {
+	bv.setClassifier(classifier);
+	bv.m_classifierEditor.setValue(classifier);
+      }
+      
+      if (instances == null)
+      	bv.setInstances(null);
+      else
+      {
+	bv.setInstances(instances);
+	
+	try{
+		bv.dataFileLabel.setText(instances.relationName());
+		bv.plotTrainingData();
+		bv.m_classPanel.setCindex(bv.m_classAttBox.getSelectedIndex());
+		bv.repaint(0,0,0,bv.getWidth(), bv.getHeight());
+	} catch (Exception ex) {}
+      }
+  
+  }
+
+  /**
+   * Main method for testing this class
+   *
+   * @param args a <code>String[]</code> value
+   */
+  public static void main(String [] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    try {
+    	if (args.length < 2) {
+		createNewVisualizerWindow(null, null);
+	}
+	else {
+		String [] argsR = null;
+		if (args.length > 2) {
+			argsR = new String [args.length-2];
+			for (int j = 2; j < args.length; j++) {
+			argsR[j-2] = args[j];
+			}
+		}
+		Classifier c = AbstractClassifier.forName(args[1], argsR);
+		
+		System.err.println("Loading instances from : "+args[0]);
+		java.io.Reader r = new java.io.BufferedReader(
+					new java.io.FileReader(args[0]));
+		Instances i = new Instances(r);
+	
+		createNewVisualizerWindow(c, i);
+	}
+      
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/DataGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/DataGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/DataGenerator.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   DataGenerator.java
+ *   Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.core.*;
+
+/**
+ * Interface to something that can generate new instances based on
+ * a set of input instances
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.4 $
+ * @since 1.0
+ */
+public interface DataGenerator {
+
+  /**
+   * Build the data generator
+   *
+   * @param inputInstances Instances to build the generator from
+   * @exception Exception if an error occurs
+   */
+  void buildGenerator(Instances inputInstances) throws Exception;
+
+  /**
+   * Generate an instance. Should return a new Instance object
+   *
+   * @return an <code>Instance</code> value
+   * @exception Exception if an error occurs
+   */
+  double [][] generateInstances(int [] indices) throws Exception;
+
+  /**
+   * Get weights
+   */
+  double [] getWeights() throws Exception;
+
+  /**
+   * Set the dimensions to be used in computing a weight for
+   * each instance generated
+   *
+   * @param dimensions an array of booleans specifying the dimensions to
+   * be used when computing instance weights
+   */
+  void setWeightingDimensions(boolean [] dimensions);
+
+  /**
+   * Set the values of the dimensions (chosen via setWeightingDimensions)
+   * to be used when computing instance weights
+   *
+   * @param vals a <code>double[]</code> value
+   */
+  void setWeightingValues(double [] vals);
+
+  /**
+   * Returns the number of generating models used by this DataGenerator
+   *
+   * @return an <code>int</code> value
+   */
+  int getNumGeneratingModels();
+
+  /**
+   * Set a seed for random number generation (if needed).
+   *
+   * @param seed an <code>int</code> value
+   */
+  void setSeed(int seed);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/KDDataGenerator.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/KDDataGenerator.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/KDDataGenerator.java	(revision 29)
@@ -0,0 +1,440 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   KDDataGenerator.java
+ *   Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Random;
+
+/**
+ * KDDataGenerator. Class that uses kernels to generate new random
+ * instances based on a supplied set of instances.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.7 $
+ * @since 1.0
+ * @see DataGenerator
+ * @see Serializable
+ */
+public class KDDataGenerator
+  implements DataGenerator, Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -958573275606402792L;
+
+  /** the instances to use */
+  private Instances m_instances;
+
+  /** standard deviations of the normal distributions for numeric attributes in
+   * each KD estimator */
+  private double [] m_standardDeviations;
+
+  /** global means or modes to use for missing values */
+  private double [] m_globalMeansOrModes;
+
+  /** minimum standard deviation for numeric attributes */
+  private double m_minStdDev = 1e-5;
+
+  /** Laplace correction for discrete distributions */
+  private double m_laplaceConst = 1.0;
+
+  /** random number seed */
+  private int m_seed = 1;
+
+  /** random number generator */
+  private Random m_random;
+
+  /** which dimensions to use for computing a weight for each generated
+   * instance */
+  private boolean [] m_weightingDimensions;
+  
+  /** the values for the weighting dimensions to use for computing the weight 
+   * for the next instance to be generated */
+  private double [] m_weightingValues;
+
+  private static double m_normConst = Math.sqrt(2*Math.PI);
+
+  /** Number of neighbours to use for kernel bandwidth */
+  private int m_kernelBandwidth = 3;
+
+  /** standard deviations for numeric attributes computed from the 
+   * m_kernelBandwidth nearest neighbours for each kernel. */
+  private double [][] m_kernelParams;
+
+  /** The minimum values for numeric attributes. */
+  protected double [] m_Min;
+  
+  /** The maximum values for numeric attributes. */
+  protected double [] m_Max;
+
+  /**
+   * Initialize the generator using the supplied instances
+   *
+   * @param inputInstances the instances to use as the basis of the kernels
+   * @throws Exception if an error occurs
+   */
+  public void buildGenerator(Instances inputInstances) throws Exception {
+    m_random = new Random(m_seed);
+    
+    m_instances = inputInstances;
+    m_standardDeviations = new double [m_instances.numAttributes()];
+    m_globalMeansOrModes = new double [m_instances.numAttributes()];
+    if (m_weightingDimensions == null) {
+      m_weightingDimensions = new boolean[m_instances.numAttributes()];
+    }
+    /*    for (int i = 0; i < m_instances.numAttributes(); i++) {
+      if (i != m_instances.classIndex()) {
+	if (m_instances.attribute(i).isNumeric()) {
+	  // global standard deviations
+	  double var = m_instances.variance(i);
+	  if (var == 0) {
+	    var = m_minStdDev;
+	  } else {
+	    var = Math.sqrt(var);
+	    //  heuristic to take into account # instances and dimensions
+	    double adjust = Math.pow((double) m_instances.numInstances(), 
+				     1.0 / m_instances.numAttributes());
+	    //	  double adjust = m_instances.numInstances();
+	    var /= adjust;
+	  }
+	  m_standardDeviations[i] = var;
+	} else {
+	  m_globalMeansOrModes[i] = m_instances.meanOrMode(i);
+	}
+      }
+      } */
+    for (int i = 0; i < m_instances.numAttributes(); i++) {
+      if (i != m_instances.classIndex()) {
+	m_globalMeansOrModes[i] = m_instances.meanOrMode(i);
+      }
+    }
+
+    m_kernelParams = 
+      new double [m_instances.numInstances()][m_instances.numAttributes()];
+    computeParams();
+  }
+
+  public double [] getWeights() {
+
+    double [] weights = new double[m_instances.numInstances()];
+
+    for (int k = 0; k < m_instances.numInstances(); k++) {
+      double weight = 1;
+      for (int i = 0; i < m_instances.numAttributes(); i++) {
+	if (m_weightingDimensions[i]) {
+	  double mean = 0;
+	  if (!m_instances.instance(k).isMissing(i)) {
+	    mean = m_instances.instance(k).value(i);
+	  } else {
+	    mean = m_globalMeansOrModes[i];
+	  }
+	  double wm = 1.0;
+	  
+	  //	    wm = normalDens(m_weightingValues[i], mean, m_standardDeviations[i]);
+	  wm = normalDens(m_weightingValues[i], mean, 
+			  m_kernelParams[k][i]);
+	  
+	  weight *= wm;
+	}
+      }
+      weights[k] = weight;
+    }
+    return weights;
+  }
+
+  /**
+   * Return a cumulative distribution from a discrete distribution
+   *
+   * @param dist the distribution to use
+   * @return the cumulative distribution
+   */
+  private double [] computeCumulativeDistribution(double [] dist) {
+
+    double [] cumDist = new double[dist.length];
+    double sum = 0;
+    for (int i = 0; i < dist.length; i++) {
+      sum += dist[i];
+      cumDist[i] = sum;
+    }
+    
+    return cumDist;
+  }
+
+  /**
+   * Generates a new instance using one kernel estimator. Each successive
+   * call to this method incremets the index of the kernel to use.
+   *
+   * @return the new random instance
+   * @throws Exception if an error occurs
+   */
+  public double [][] generateInstances(int [] indices) throws Exception {
+    
+    double [][] values = new double[m_instances.numInstances()][];
+
+    for (int k = 0; k < indices.length; k++) {
+      values[indices[k]] = new double[m_instances.numAttributes()];
+      for (int i = 0; i < m_instances.numAttributes(); i++) {
+	if ((!m_weightingDimensions[i]) && (i != m_instances.classIndex())) {
+	  if (m_instances.attribute(i).isNumeric()) {
+	    double mean = 0;
+	    double val = m_random.nextGaussian();
+	    if (!m_instances.instance(indices[k]).isMissing(i)) {
+	      mean = m_instances.instance(indices[k]).value(i);
+	    } else {
+	      mean = m_globalMeansOrModes[i];
+	    }
+	    
+	    val *= m_kernelParams[indices[k]][i];
+	    val += mean;
+
+	    values[indices[k]][i] = val;
+	  } else {
+	    // nominal attribute
+	    double [] dist = new double[m_instances.attribute(i).numValues()];
+	    for (int j = 0; j < dist.length; j++) {
+	      dist[j] = m_laplaceConst;
+	    }
+	    if (!m_instances.instance(indices[k]).isMissing(i)) {
+	      dist[(int)m_instances.instance(indices[k]).value(i)]++;
+	    } else {
+	      dist[(int)m_globalMeansOrModes[i]]++;
+	    }
+	    Utils.normalize(dist);
+	    double [] cumDist = computeCumulativeDistribution(dist);
+	    double randomVal = m_random.nextDouble();
+	    int instVal = 0;
+	    for (int j = 0; j < cumDist.length; j++) {
+	      if (randomVal <= cumDist[j]) {
+		instVal = j;
+		break;
+	      }
+	    }
+	    values[indices[k]][i] = (double)instVal;
+	  }
+	}
+      }
+    }
+    return values;
+  }
+
+  /**
+   * Density function of normal distribution.
+   * @param x input value
+   * @param mean mean of distribution
+   * @param stdDev standard deviation of distribution
+   */
+  private double normalDens (double x, double mean, double stdDev) {
+    double diff = x - mean;
+   
+    return  (1/(m_normConst*stdDev))*Math.exp(-(diff*diff/(2*stdDev*stdDev)));
+  }
+
+  /**
+   * Set which dimensions to use when computing a weight for the next
+   * instance to generate
+   *
+   * @param dims an array of booleans indicating which dimensions to use
+   */
+  public void setWeightingDimensions(boolean [] dims) {
+    m_weightingDimensions = dims;
+  }
+
+  /**
+   * Set the values for the weighting dimensions to be used when computing
+   * the weight for the next instance to be generated
+   *
+   * @param vals an array of doubles containing the values of the
+   * weighting dimensions (corresponding to the entries that are set to
+   * true throw setWeightingDimensions)
+   */
+  public void setWeightingValues(double [] vals) {
+    m_weightingValues = vals;
+  }
+
+  /**
+   * Return the number of kernels (there is one per training instance)
+   *
+   * @return the number of kernels
+   */
+  public int getNumGeneratingModels() {
+    if (m_instances != null) {
+      return m_instances.numInstances();
+    }
+    return 0;
+  }
+
+  /**
+   * Set the kernel bandwidth (number of nearest neighbours to cover)
+   *
+   * @param kb an <code>int</code> value
+   */
+  public void setKernelBandwidth(int kb) {
+    m_kernelBandwidth = kb;
+  }
+
+  /**
+   * Get the kernel bandwidth
+   *
+   * @return an <code>int</code> value
+   */
+  public int getKernelBandwidth() {
+    return m_kernelBandwidth;
+  } 
+
+  /**
+   * Initializes a new random number generator using the
+   * supplied seed.
+   *
+   * @param seed an <code>int</code> value
+   */
+  public void setSeed(int seed) {
+    m_seed = seed;
+    m_random = new Random(m_seed);
+  }
+
+  /**
+   * Calculates the distance between two instances
+   *
+   * @param test the first instance
+   * @param train the second instance
+   * @return the distance between the two given instances, between 0 and 1
+   */          
+  private double distance(Instance first, Instance second) {  
+
+    double diff, distance = 0;
+
+    for(int i = 0; i < m_instances.numAttributes(); i++) { 
+      if (i == m_instances.classIndex()) {
+	continue;
+      }
+      double firstVal = m_globalMeansOrModes[i];
+      double secondVal = m_globalMeansOrModes[i];
+
+      switch (m_instances.attribute(i).type()) {
+      case Attribute.NUMERIC:
+	// If attribute is numeric
+	if (!first.isMissing(i)) {
+	  firstVal = first.value(i);
+	}
+	
+	if (!second.isMissing(i)) {
+	  secondVal = second.value(i);
+	}
+
+	diff = norm(firstVal,i) - norm(secondVal,i);
+
+	break;
+      default:
+	diff = 0;
+	break;
+      }
+      distance += diff * diff;
+    }
+    return Math.sqrt(distance);
+  }
+
+  /**
+   * Normalizes a given value of a numeric attribute.
+   *
+   * @param x the value to be normalized
+   * @param i the attribute's index
+   */
+  private double norm(double x,int i) {
+    
+    if (Double.isNaN(m_Min[i]) || Utils.eq(m_Max[i], m_Min[i])) {
+      return 0;
+    } else {
+      return (x - m_Min[i]) / (m_Max[i] - m_Min[i]);
+    }
+  }
+
+  /**
+   * Updates the minimum and maximum values for all the attributes
+   * based on a new instance.
+   *
+   * @param instance the new instance
+   */
+  private void updateMinMax(Instance instance) {  
+
+    for (int j = 0; j < m_instances.numAttributes(); j++) {
+      if (!instance.isMissing(j)) {
+	if (Double.isNaN(m_Min[j])) {
+	  m_Min[j] = instance.value(j);
+	  m_Max[j] = instance.value(j);
+	} else if (instance.value(j) < m_Min[j]) {
+	  m_Min[j] = instance.value(j);
+	} else if (instance.value(j) > m_Max[j]) {
+	  m_Max[j] = instance.value(j);
+	}
+      }
+    }
+  }
+
+  private void computeParams() throws Exception {
+    // Calculate the minimum and maximum values
+    m_Min = new double [m_instances.numAttributes()];
+    m_Max = new double [m_instances.numAttributes()];
+    for (int i = 0; i < m_instances.numAttributes(); i++) {
+      m_Min[i] = m_Max[i] = Double.NaN;
+    }
+    for (int i = 0; i < m_instances.numInstances(); i++) {
+      updateMinMax(m_instances.instance(i));
+    }
+
+    double [] distances = new double[m_instances.numInstances()];
+    for (int i = 0; i < m_instances.numInstances(); i++) {
+      Instance current = m_instances.instance(i);
+      for (int j = 0; j < m_instances.numInstances(); j++) {
+	distances[j] = distance(current, m_instances.instance(j));
+      }
+      int [] sorted = Utils.sort(distances);
+      int k = m_kernelBandwidth;
+      double bandwidth = distances[sorted[k]];
+
+      // Check for bandwidth zero
+      if (bandwidth <= 0) {
+	for (int j = k + 1; j < sorted.length; j++) {
+	  if (distances[sorted[j]] > bandwidth) {
+	    bandwidth = distances[sorted[j]];
+	    break;
+	  }
+	}
+	if (bandwidth <= 0) {
+	  throw new Exception("All training instances coincide with "
+			      +"test instance!");
+	}
+      }
+      for (int j = 0; j < m_instances.numAttributes(); j++) {
+	if ((m_Max[j] - m_Min[j]) > 0) {
+	  m_kernelParams[i][j] = bandwidth * (m_Max[j] - m_Min[j]);
+	}
+      }
+    }
+  }
+}
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteBoundaryVisualizerSubTask.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteBoundaryVisualizerSubTask.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteBoundaryVisualizerSubTask.java	(revision 29)
@@ -0,0 +1,396 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   RemoteBoundaryVisualizerSubTask.java
+ *   Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.experiment.Task;
+import weka.experiment.TaskStatusInfo;
+
+import java.util.Random;
+
+/**
+ * Class that encapsulates a sub task for distributed boundary
+ * visualization. Produces probability distributions for each pixel
+ * in one row of the visualization.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 5987 $
+ * @since 1.0
+ * @see Task
+ */
+public class RemoteBoundaryVisualizerSubTask implements Task {
+
+  // status information for this sub task
+  private TaskStatusInfo m_status = new TaskStatusInfo();
+
+  // the result of this sub task
+  private RemoteResult m_result;
+
+  // which row are we doing
+  private int m_rowNumber;
+
+  // width and height of the visualization
+  private int m_panelHeight;
+  private int m_panelWidth;
+
+  // the classifier to use
+  private Classifier m_classifier;
+
+  // the kernel density estimator
+  private DataGenerator m_dataGenerator;
+
+  // the training data
+  private Instances m_trainingData;
+
+  // attributes for visualizing on (fixed dimensions)
+  private int m_xAttribute;
+  private int m_yAttribute;
+
+  // pixel width and height in terms of attribute values
+  private double m_pixHeight;
+  private double m_pixWidth;
+
+  // min, max of these attributes
+  private double m_minX;
+  private double m_minY;
+  private double m_maxX;
+  private double m_maxY;
+
+  // number of samples to take from each region in the fixed dimensions
+  private int m_numOfSamplesPerRegion = 2;
+
+  // number of samples per kernel = base ^ (# non-fixed dimensions)
+  private int m_numOfSamplesPerGenerator;
+  private double m_samplesBase = 2.0;
+
+  // A random number generator 
+  private Random m_random;
+
+  private double [] m_weightingAttsValues;
+  private boolean [] m_attsToWeightOn;
+  private double [] m_vals;
+  private double [] m_dist;
+  private Instance m_predInst;
+  
+  /**
+   * Set the row number for this sub task
+   *
+   * @param rn the row number
+   */
+  public void setRowNumber(int rn) {
+    m_rowNumber = rn;
+  }
+
+  /**
+   * Set the width of the visualization
+   *
+   * @param pw the width
+   */
+  public void setPanelWidth(int pw) {
+    m_panelWidth = pw;
+  }
+
+  /**
+   * Set the height of the visualization
+   *
+   * @param ph the height
+   */
+  public void setPanelHeight(int ph) {
+    m_panelHeight = ph;
+  }
+
+  /**
+   * Set the height of a pixel
+   *
+   * @param ph the height of a pixel
+   */
+  public void setPixHeight(double ph) {
+    m_pixHeight = ph;
+  }
+
+  /**
+   * Set the width of a pixel
+   *
+   * @param pw the width of a pixel
+   */
+  public void setPixWidth(double pw) {
+    m_pixWidth = pw;
+  }
+
+  /**
+   * Set the classifier to use
+   *
+   * @param dc the classifier
+   */
+  public void setClassifier(Classifier dc) {
+    m_classifier = dc;
+  }
+
+  /**
+   * Set the density estimator to use
+   *
+   * @param dg the density estimator
+   */
+  public void setDataGenerator(DataGenerator dg) {
+    m_dataGenerator = dg;
+  }
+
+  /**
+   * Set the training data
+   *
+   * @param i the training data
+   */
+  public void setInstances(Instances i) {
+    m_trainingData = i;
+  }
+
+  /**
+   * Set the minimum and maximum values of the x axis fixed dimension
+   *
+   * @param minx a <code>double</code> value
+   * @param maxx a <code>double</code> value
+   */
+  public void setMinMaxX(double minx, double maxx) {
+    m_minX = minx; m_maxX = maxx;
+  }
+
+  /**
+   * Set the minimum and maximum values of the y axis fixed dimension
+   *
+   * @param miny a <code>double</code> value
+   * @param maxy a <code>double</code> value
+   */
+  public void setMinMaxY(double miny, double maxy) {
+    m_minY = miny; m_maxY = maxy;
+  }
+
+  /**
+   * Set the x axis fixed dimension
+   *
+   * @param xatt an <code>int</code> value
+   */
+  public void setXAttribute(int xatt) {
+    m_xAttribute = xatt;
+  }
+
+  /**
+   * Set the y axis fixed dimension
+   *
+   * @param yatt an <code>int</code> value
+   */
+  public void setYAttribute(int yatt) {
+    m_yAttribute = yatt;
+  }
+
+  /**
+   * Set the number of points to uniformly sample from a region (fixed
+   * dimensions).
+   *
+   * @param num an <code>int</code> value
+   */
+  public void setNumSamplesPerRegion(int num) {
+    m_numOfSamplesPerRegion = num;
+  }
+
+  /**
+   * Set the base for computing the number of samples to obtain from each
+   * generator. number of samples = base ^ (# non fixed dimensions)
+   *
+   * @param ksb a <code>double</code> value
+   */
+  public void setGeneratorSamplesBase(double ksb) {
+    m_samplesBase = ksb;
+  }
+
+  /**
+   * Perform the sub task
+   */
+  public void execute() {
+
+    m_random = new Random(m_rowNumber * 11);
+    m_dataGenerator.setSeed(m_rowNumber * 11);
+    m_result = new RemoteResult(m_rowNumber, m_panelWidth);
+    m_status.setTaskResult(m_result);
+    m_status.setExecutionStatus(TaskStatusInfo.PROCESSING);
+
+    try {
+      m_numOfSamplesPerGenerator = 
+	(int)Math.pow(m_samplesBase, m_trainingData.numAttributes()-3);
+      if (m_trainingData == null) {
+	throw new Exception("No training data set (BoundaryPanel)");
+      }
+      if (m_classifier == null) {
+	throw new Exception("No classifier set (BoundaryPanel)");
+      }
+      if (m_dataGenerator == null) {
+	throw new Exception("No data generator set (BoundaryPanel)");
+      }
+      if (m_trainingData.attribute(m_xAttribute).isNominal() || 
+	m_trainingData.attribute(m_yAttribute).isNominal()) {
+	throw new Exception("Visualization dimensions must be numeric "
+			    +"(RemoteBoundaryVisualizerSubTask)");
+      }
+      
+      m_attsToWeightOn = new boolean[m_trainingData.numAttributes()];
+      m_attsToWeightOn[m_xAttribute] = true;
+      m_attsToWeightOn[m_yAttribute] = true;
+      
+      // generate samples
+      m_weightingAttsValues = new double [m_attsToWeightOn.length];
+      m_vals = new double[m_trainingData.numAttributes()];
+      m_predInst = new DenseInstance(1.0, m_vals);
+      m_predInst.setDataset(m_trainingData);
+
+      System.err.println("Executing row number "+m_rowNumber);
+      for (int j = 0; j < m_panelWidth; j++) {
+	double [] preds = calculateRegionProbs(j, m_rowNumber);
+	m_result.setLocationProbs(j, preds);
+	m_result.
+	  setPercentCompleted((int)(100 * ((double)j / (double)m_panelWidth)));
+      }
+    } catch (Exception ex) {
+      m_status.setExecutionStatus(TaskStatusInfo.FAILED);
+      m_status.setStatusMessage("Row "+m_rowNumber+" failed.");
+      System.err.print(ex);
+      return;
+    }
+
+    // finished
+    m_status.setExecutionStatus(TaskStatusInfo.FINISHED);
+    m_status.setStatusMessage("Row "+m_rowNumber+" completed successfully.");
+  }
+
+
+  private double [] calculateRegionProbs(int j, int i) throws Exception {
+    double [] sumOfProbsForRegion = 
+      new double [m_trainingData.classAttribute().numValues()];
+
+    for (int u = 0; u < m_numOfSamplesPerRegion; u++) {
+      
+      double [] sumOfProbsForLocation = 
+	new double [m_trainingData.classAttribute().numValues()];
+      
+      m_weightingAttsValues[m_xAttribute] = getRandomX(j);
+      m_weightingAttsValues[m_yAttribute] = getRandomY(m_panelHeight-i-1);
+      
+      m_dataGenerator.setWeightingValues(m_weightingAttsValues);
+      
+      double [] weights = m_dataGenerator.getWeights();
+      double sumOfWeights = Utils.sum(weights);
+      int [] indices = Utils.sort(weights);
+      
+      // Prune 1% of weight mass
+      int [] newIndices = new int[indices.length];
+      double sumSoFar = 0; 
+      double criticalMass = 0.99 * sumOfWeights;
+      int index = weights.length - 1; int counter = 0;
+      for (int z = weights.length - 1; z >= 0; z--) {
+	newIndices[index--] = indices[z];
+	sumSoFar += weights[indices[z]];
+	counter++;
+	if (sumSoFar > criticalMass) {
+	  break;
+	}
+      }
+      indices = new int[counter];
+      System.arraycopy(newIndices, index + 1, indices, 0, counter);
+      
+      for (int z = 0; z < m_numOfSamplesPerGenerator; z++) {
+        
+	m_dataGenerator.setWeightingValues(m_weightingAttsValues);
+	double [][] values = m_dataGenerator.generateInstances(indices);
+        
+	for (int q = 0; q < values.length; q++) {
+	  if (values[q] != null) {
+	    System.arraycopy(values[q], 0, m_vals, 0, m_vals.length);
+	    m_vals[m_xAttribute] = m_weightingAttsValues[m_xAttribute];
+	    m_vals[m_yAttribute] = m_weightingAttsValues[m_yAttribute];
+            
+	    // classify the instance
+	    m_dist = m_classifier.distributionForInstance(m_predInst);
+
+	    for (int k = 0; k < sumOfProbsForLocation.length; k++) {
+	      sumOfProbsForLocation[k] += (m_dist[k] * weights[q]); 
+	    }
+	  }
+	}
+      }
+      
+      for (int k = 0; k < sumOfProbsForRegion.length; k++) {
+	sumOfProbsForRegion[k] += (sumOfProbsForLocation[k] * sumOfWeights); 
+      }
+    }
+    
+    // average
+    Utils.normalize(sumOfProbsForRegion);
+
+    // cache
+    double [] tempDist = new double[sumOfProbsForRegion.length];
+    System.arraycopy(sumOfProbsForRegion, 0, tempDist, 
+		     0, sumOfProbsForRegion.length);
+		
+    return tempDist;
+  }
+
+  /**
+   * Return a random x attribute value contained within
+   * the pix'th horizontal pixel
+   *
+   * @param pix the horizontal pixel number
+   * @return a value in attribute space
+   */
+  private double getRandomX(int pix) {
+
+    double minPix =  m_minX + (pix * m_pixWidth);
+
+    return minPix + m_random.nextDouble() * m_pixWidth;
+  }
+
+  /**
+   * Return a random y attribute value contained within
+   * the pix'th vertical pixel
+   *
+   * @param pix the vertical pixel number
+   * @return a value in attribute space
+   */
+  private double getRandomY(int pix) {
+    
+    double minPix = m_minY + (pix * m_pixHeight);
+    
+    return minPix +  m_random.nextDouble() * m_pixHeight;
+  }
+  
+  /**
+   * Return status information for this sub task
+   *
+   * @return a <code>TaskStatusInfo</code> value
+   */
+  public TaskStatusInfo getTaskStatus() {
+    return m_status;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteResult.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteResult.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/boundaryvisualizer/RemoteResult.java	(revision 29)
@@ -0,0 +1,104 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *   RemoteResult.java
+ *   Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.boundaryvisualizer;
+
+import java.io.Serializable;
+
+/**
+ * Class that encapsulates a result (and progress info) for part
+ * of a distributed boundary visualization. The result of a sub-task
+ * is the probabilities necessary to display one row of the final 
+ * visualization.
+ *
+ * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
+ * @version $Revision: 1.3 $
+ * @since 1.0
+ * @see Serializable
+ */
+public class RemoteResult
+  implements Serializable {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1873271280044633808L;
+
+  /** the row number that this result corresponds to */
+  private int m_rowNumber;
+
+  /** how many pixels in a row */
+  private int m_rowLength;
+
+  /** the result - ie. the probability distributions produced by the
+   * classifier for this row in the visualization */
+  private double [][] m_probabilities;
+
+  /** progress on computing this row */
+  private int m_percentCompleted;
+
+  /**
+   * Creates a new <code>RemoteResult</code> instance.
+   *
+   * @param rowNum the row number
+   * @param rowLength the number of pixels in the row
+   */
+  public RemoteResult(int rowNum, int rowLength) {
+    m_probabilities = new double[rowLength][0];
+  }
+  
+  /**
+   * Store the classifier's distribution for a particular pixel in the
+   * visualization
+   *
+   * @param index the pixel
+   * @param distribution the probability distribution from the classifier
+   */
+  public void setLocationProbs(int index, double [] distribution) {
+    m_probabilities[index] = distribution;
+  }
+
+  /**
+   * Return the probability distributions  for this row in the visualization
+   *
+   * @return the probability distributions
+   */
+  public double [][] getProbabilities() {
+    return m_probabilities;
+  }
+
+  /**
+   * Set the progress for this row so far
+   *
+   * @param pc a percent completed value
+   */
+  public void setPercentCompleted(int pc) {
+    m_percentCompleted = pc;
+  }
+
+  /**
+   * Return the progress for this row
+   *
+   * @return a percent completed value
+   */
+  public int getPercentCompleted() {
+    return m_percentCompleted;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/AddModelsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/AddModelsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/AddModelsPanel.java	(revision 29)
@@ -0,0 +1,495 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AddModelsPanel.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.EnsembleLibraryModel;
+import weka.classifiers.trees.J48;
+import weka.gui.GenericObjectEditor;
+import weka.gui.ensembleLibraryEditor.tree.GenericObjectNode;
+import weka.gui.ensembleLibraryEditor.tree.ModelTreeNodeEditor;
+import weka.gui.ensembleLibraryEditor.tree.ModelTreeNodeRenderer;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.ToolTipManager;
+import javax.swing.tree.DefaultTreeModel;
+
+/**
+ * The purpose of this class is to create a user interface that will
+ * provide an intuitive method of building "playlists" of weka 
+ * classifiers to be trained.  The main gui consists of two parts: the
+ * top and bottom. The top will use a JTree to show all of the options
+ * available for a particular classifier. The intent of showing these 
+ * options in a tree is to allow users to collapse/expand tree nodes 
+ * to quickly access all of the available options at different levels.
+ * The bottom half of the gui will show the current "playlist" of 
+ * chosen models that the user can opt to add to the current library.  
+ * <p/>
+ * The overall concept is that users will use the tree to specify 
+ * combinations of options to the currently selected classifier type.
+ * then they will use the "generate models" button to generate a set
+ * models from the selected options. This can be done many times with
+ * different sets of options for different model types to generate a
+ * list in the bottom half of the gui.  Once the user is satisfied
+ * with their list of models they are provided a button to "add all 
+ * models" to the model library displayed by the ListModelsPanel.
+ * <p/>
+ * Note that there are currently 9 different classes that implement
+ * tree nodes and tree node editors created to support this class
+ * in modelling/rendering weka classifier parameters. see 
+ * appropriate classes for details.  They currently reside in the 
+ * weka.gui.libraryEditor.tree package.
+ * <p/>
+ * To instantiate the treeModel:
+ * <ul>
+ *   <li>ModelNodeEditor</li>
+ *   <li>ModelNodeRenderer</li>
+ * </ul>
+ * 
+ * To render/model weka objects:
+ * <ul>
+ *   <li>PropertyNode</li>
+ *   <li>GenericObjectNode</li>
+ *   <li>GenericObjectNodeEditor</li>
+ *   <li>CheckBoxNode</li>
+ *   <li>CheckBoxNodeEditor</li>
+ *   <li>NumberNode</li>
+ *   <li>NumberNodeEditor</li>
+ *   <li>DefaultNode</li>
+ * </ul>
+ * 
+ * These classes are responsible for 
+ * representing the different kinds of tree nodes that will be 
+ * contained in the JTree object, as well as the renderers and editors
+ * that will be responsible for displaying their properties in the
+ * user interface.  
+ * <p/>
+ * Code for this class was inspired and partly borrowed from the 
+ * following excellent tutorial on creating custom JTree renderers 
+ * and editors authored by John Zukowski: <br/>
+ * <a href="http://www.java2s.com/ExampleCode/Swing-JFC/CheckBoxNodeTreeSample.htm" target="_blank">http://www.java2s.com/ExampleCode/Swing-JFC/CheckBoxNodeTreeSample.htm</a>
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 5928 $
+ */
+public class AddModelsPanel 
+  extends JPanel
+  implements ActionListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 4874639416371962573L;
+
+  /**
+   * This is a reference to the main gui object that is responsible 
+   * for displaying the model library.  This panel will add models
+   * to the main panel through methods in this object.
+   */
+  private ListModelsPanel m_ListModelsPanel;
+  
+  /**
+   * The JTree that will display the classifier options available in
+   * the currently select3ed model type
+   */
+  private JTree m_Tree;
+  
+  /**
+   * The tree model that will be used to add and remove nodes from 
+   * the currently selected model type
+   */
+  private DefaultTreeModel m_TreeModel;
+  
+  /**
+   * This button will allow users to generate a group of models from
+   * the currently selected classifer options in the m_Tree object.
+   */
+  private JButton m_GenerateButton;
+  
+  /**
+   * This will display messages associated with model generation.
+   * Currently the number of models generated and the number of
+   * them that had errors.
+   */
+  private JLabel m_GenerateLabel;
+  
+  /**
+   * This button will allow users to remove all of the models 
+   * currently selected in the m_ModeList object
+   */
+  private JButton m_RemoveSelectedButton;
+  
+  /**
+   * This button will remove all of the models that had errors 
+   * during model generation.
+   */
+  private JButton m_RemoveInvalidButton;
+  
+  /**
+   * This button will add all of the models that had are 
+   * currently selected in the model list.
+   */
+  private JButton m_AddSelectedButton;
+  
+  /**
+   * This button will allow users to add all models currently in 
+   * the model list to the model library in the ListModelsPanel. 
+   * Note that this operation will exclude any models that had 
+   * errors
+   */
+  private JButton m_AddAllButton;
+  
+  /**
+   * This object will store all of the model sets generated from the
+   * m_Tree.  The ModelList class is a custom class in weka.gui that
+   * knows how to display library model objects in a JList
+   */
+  private ModelList m_ModelList;
+  
+  /** the scroll pane holding our classifer parameters */
+  JScrollPane m_TreeView;
+  
+  /**
+   * This constructor simply stores the reference to the 
+   * ListModelsPanel and builf the user interface.
+   * 
+   * @param listModelsPanel	the reference to the panel
+   */
+  public AddModelsPanel(ListModelsPanel listModelsPanel) {
+    m_ListModelsPanel = listModelsPanel;
+    
+    createAddModelsPanel();
+  }
+  
+  /**
+   * This method is responsible for building the use interface.
+   */
+  private void createAddModelsPanel() {
+    GridBagConstraints gbc = new GridBagConstraints();
+    setLayout(new GridBagLayout());
+    
+    m_TreeView = new JScrollPane();
+    m_TreeView.setPreferredSize(new Dimension(150, 50));
+    
+    buildClassifierTree(new J48());
+    
+    ToolTipManager.sharedInstance().registerComponent(m_Tree);
+    
+    gbc.weightx = 1;
+    gbc.weighty = 1.5;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.gridwidth = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(m_TreeView, gbc);
+    
+    m_GenerateButton = new JButton("Generate Models");
+    m_GenerateButton.setToolTipText(
+	"Generate a set of models from options specified in options tree");
+    m_GenerateButton.addActionListener(this);
+    gbc.weightx = 0;
+    gbc.weighty = 0;
+    gbc.fill = GridBagConstraints.NONE;
+    gbc.gridx = 0;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_GenerateButton, gbc);
+    
+    m_GenerateLabel = new JLabel("");
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 2;
+    add(m_GenerateLabel, gbc);
+    
+    m_RemoveInvalidButton = new JButton("Remove Invalid");
+    m_RemoveInvalidButton.setToolTipText(
+	"Remove all invalid (red) models from the above list");
+    m_RemoveInvalidButton.addActionListener(this);
+    gbc.weightx = 0;
+    gbc.fill = GridBagConstraints.NONE;
+    gbc.gridx = 2;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    //OK, this button was removed because we thought it was a waste
+    //of space.  Instead of removing invalid models, we just explicitly
+    //prevent the user from adding them to the main list.  I'm going to
+    //leave the code in with this final "add" statement commented out
+    //because we are still on the fence as to whether this is a good 
+    //idea
+    //add(m_RemoveInvalidButton, gbc);
+    
+    m_ModelList = new ModelList();
+    
+    m_ModelList.getInputMap().put(
+	KeyStroke.getKeyStroke("released DELETE"), "deleteSelected");
+    m_ModelList.getActionMap().put("deleteSelected",
+	new AbstractAction("deleteSelected") {
+      /** for serialization */
+      private static final long serialVersionUID = -3351194234735560372L;
+      
+      public void actionPerformed(ActionEvent evt) {
+	
+	Object[] currentModels = m_ModelList.getSelectedValues();
+	
+	ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+	
+	for (int i = 0; i < currentModels.length; i++) {
+	  dataModel.removeElement((EnsembleLibraryModel) currentModels[i]);
+	}
+	
+	//Shrink the selected range to the first index that was selected
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+	
+      }
+    });
+    
+    m_ModelList
+    .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+    m_ModelList.setLayoutOrientation(JList.VERTICAL);
+    m_ModelList.setVisibleRowCount(-1);
+    
+    JPanel modelListPanel = new JPanel();
+    modelListPanel.setBorder(
+	BorderFactory.createTitledBorder("Working Set of Newly Generated Models"));
+    
+    JScrollPane listView = new JScrollPane(m_ModelList);
+    //listView.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+    listView.setPreferredSize(new Dimension(150, 50));
+    
+    modelListPanel.setLayout(new BorderLayout());
+    modelListPanel.add(listView, BorderLayout.CENTER);
+    
+    gbc.weightx = 1;
+    gbc.weighty = 1;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.gridx = 0;
+    gbc.gridy = 2;
+    gbc.gridwidth = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(modelListPanel, gbc);
+    
+    m_RemoveSelectedButton = new JButton("Remove Selected");
+    m_RemoveSelectedButton.setToolTipText("Remove all currently selected models from the above list");
+    m_RemoveSelectedButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.weighty = 0;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_RemoveSelectedButton, gbc);
+    
+    m_AddSelectedButton = new JButton("Add Selected");
+    m_AddSelectedButton.setToolTipText(
+	"Add selected models in the above list to the model library");
+    m_AddSelectedButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_AddSelectedButton, gbc);
+    
+    m_AddAllButton = new JButton("Add All");
+    m_AddAllButton.setToolTipText(
+	"Add all models in the above list to the model library");
+    m_AddAllButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_AddAllButton, gbc);
+  }
+  
+  /**
+   * This method necessarily seperates the process of building the
+   * tree object from the rest of the GUI construction.  In order to
+   * prevent all kinds of strange garbage collection problems, we take
+   * the conservative approach of gutting and rebuilding the JTree 
+   * every time a new classifier is chosen for the root node.
+   * 
+   * @param classifier	the classifier to build the tree for
+   */
+  public void buildClassifierTree(Classifier classifier) {
+    
+    //This block sets up the root node of the tree.  Note that 
+    //the constructor for the GenericObjectNode will take care
+    //of creating all of the child nodes containing the node
+    //properties
+    GenericObjectEditor classifierEditor = new GenericObjectEditor();
+    classifierEditor.setClassType(Classifier.class);
+    classifierEditor.setValue(classifier);
+    
+    GenericObjectNode rootNode = new GenericObjectNode(this, classifier,
+	classifierEditor, "Current Classifier");
+    
+    m_TreeModel = new DefaultTreeModel(rootNode);
+    m_Tree = new JTree(m_TreeModel);
+    rootNode.setTree(m_Tree);
+    rootNode.updateTree();
+    
+    m_Tree.setRootVisible(true);
+    
+    ModelTreeNodeRenderer renderer = new ModelTreeNodeRenderer();
+    m_Tree.setCellRenderer(renderer);
+    m_Tree.setCellEditor(new ModelTreeNodeEditor(m_Tree));
+    m_Tree.setEditable(true);
+    m_Tree.setVisibleRowCount(8);
+    //ToolTipManager.sharedInstance().registerComponent(m_Tree);
+    
+    //This "tentatively seems to work better:
+    m_Tree.setRowHeight(0);
+    
+    m_TreeView.setViewportView(m_Tree);
+  }
+  
+  /**
+   * This will support the button triggered events for this panel.
+   * 
+   * @param e	the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+    
+    if (e.getSource() == m_GenerateButton) {
+      
+      //here we want to generate all permutations of the 
+      //options specified and then add each of them to the 
+      //model list panel
+      
+      Vector models = ((GenericObjectNode) m_TreeModel.getRoot()).getValues();
+      
+      int total = models.size();
+      int invalid = 0;
+      
+      for (int i = 0; i < models.size(); i++) {
+	Classifier classifier = (Classifier) models.get(i);
+	
+	//This method will invoke the classifier's setOptions
+	//method to see if the current set of options was 
+	//valid.  
+	
+	EnsembleLibraryModel model = m_ListModelsPanel.getLibrary().createModel(classifier);
+	
+	model.testOptions();
+	
+	if (!model.getOptionsWereValid())
+	  invalid++;
+	
+	dataModel.add(model);
+      }
+      
+      //update the message text with model generation info
+      String generateString = new String("  " + total
+	  + " models generated");
+      generateString += ", " + invalid + " had errors";
+      m_GenerateLabel.setText(generateString);
+      
+    } else if (e.getSource() == m_RemoveSelectedButton) {
+      
+      //here we simply get the list of models that are 
+      //currently selected and ten remove them from the list
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	dataModel.removeElement(currentModels[i]);
+      }
+      
+      //Shrink the selected range to the first index that was selected
+      if (m_ModelList.getSelectedIndices().length > 0) {
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+      }
+      
+    } else if (e.getSource() == m_RemoveInvalidButton) {
+      
+      //here we simply remove all the models that were not
+      //valid
+      
+      Vector toRemove = new Vector();
+      
+      for (int i = 0; i < dataModel.getSize(); i++) {
+	
+	EnsembleLibraryModel currentModel = (EnsembleLibraryModel) dataModel.getElementAt(i);
+	if (!currentModel.getOptionsWereValid()) {
+	  toRemove.add(currentModel);
+	}
+      }
+      
+      for (int i = 0; i < toRemove.size(); i++)
+	dataModel.removeElement(toRemove.get(i));
+      
+    } else if (e.getSource() == m_AddAllButton) {
+      
+      //here we just need to add all of the models to the 
+      //ListModelsPanel object
+      
+      Iterator it = dataModel.iterator();
+      
+      while (it.hasNext()) {
+	EnsembleLibraryModel currentModel = (EnsembleLibraryModel) it.next();
+	if (currentModel.getOptionsWereValid()) {
+	  m_ListModelsPanel.addModel(currentModel);
+	}
+      }
+      
+      int size = dataModel.getSize();
+      
+      for (int i = 0; i < size; i++) {
+	dataModel.removeElement(dataModel.getElementAt(0));
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/DefaultModelsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/DefaultModelsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/DefaultModelsPanel.java	(revision 29)
@@ -0,0 +1,618 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DefaultModelsPanel.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.EnsembleLibraryModel;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.ToolTipManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * This class is intended to build a panel that contains as interface 
+ * that will let users choose default lists of models to add to the 
+ * library. There will be default a list of models provided by the 
+ * EnsembleLibrary class.  In addition, the user will be able to prune
+ * the list of defaults to remove models that have either high training
+ * times, high testing times, or high file sizes on disk.  Finally, users
+ * will be able to also prune the size of the current working default set
+ * to a specific number of models with a slider bar.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 5928 $
+ */
+public class DefaultModelsPanel 
+  extends JPanel 
+  implements ActionListener, ChangeListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -6123488873592563339L;
+
+  /** the name of the property file */
+  public static final String PROPERTY_FILE = "DefaultModels.props";
+  
+  /** Contains the editor properties */
+  protected static Properties DEFAULT_PROPERTIES;
+  
+  /** options to exclude */
+  public String EXCLUDE_OPTIONS[] = { "Train Time", "Test Time", "File Size" };
+  
+  /** an array of libray files to be used in populating
+   * the default list comboBox*/
+  private String[] m_DefaultFileNames;
+  
+  /** an array of model Strings that should be excluded 
+   * if the file size option is selected*/
+  private String[] m_LargeFileSizeModels;
+  
+  /** an array of model Strings that should be excluded 
+   * if the train time option is selected*/
+  private String[] m_LargeTrainTimeModels;
+  
+  /** an array of model Strings that should be excluded 
+   * if the test time option is selected*/
+  private String[] m_LargeTestTimeModels;
+  
+  /** this is a combo box that will allow the user to select
+   * which set of models to remove from the list */
+  private JComboBox m_ExcludeModelsComboBox;
+  
+  /** this is a button that will allow the user to select
+   * which set of models to remove from the list */
+  private JButton m_ExcludeModelsButton;
+  
+  /** this is a combo box that will allow the user to select
+   * the default model file */
+  private JComboBox m_DefaultFilesComboBox;
+  
+  /** allows the user to reload the default set */
+  private JButton m_RefreshButton;
+  
+  /**
+   * This object will store all of the models that can be selected
+   * from the default list.  The ModelList class is 
+   * a custom class in weka.gui that knows how to display library 
+   * model objects in a JList
+   */
+  private ModelList m_ModelList;
+  
+  /** This button allows the user to remove all the models currently
+   * selected in the model List from those that will be added*/
+  private JButton m_RemoveSelectedButton;
+  
+  /** This button allows the user to add all the in the current 
+   * working default set to the library */
+  private JButton m_AddAllButton;
+  
+  /** This button allows the user to add all the models currently
+   * selected to the current set of models in this library, after 
+   * this is selected it should also send the user back to the 
+   * main interface*/
+  private JButton m_AddSelectedButton;
+  
+  /**
+   * This is a reference to the main gui object that is responsible 
+   * for displaying the model library.  This panel will add models
+   * to the main panel through methods in this object.
+   */
+  private ListModelsPanel m_ListModelsPanel;
+  
+  /** whether an update is pending */
+  private boolean m_ListUpdatePending = false;
+  
+  /** 
+   * Loads the configuration property file 
+   */
+  static {
+    
+    try {
+      System.out.println("package name: " + getPackageName());
+      
+      DEFAULT_PROPERTIES = Utils.readProperties(getPackageName()
+	  + PROPERTY_FILE);
+      java.util.Enumeration keys = (java.util.Enumeration) DEFAULT_PROPERTIES
+      .propertyNames();
+      if (!keys.hasMoreElements()) {
+	throw new Exception("Failed to read a property file for the "
+	    + "generic object editor");
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+	  "Could not read a configuration file for the default models\n"
+	  + "panel.\n", "DefaultProperties",
+	  JOptionPane.ERROR_MESSAGE);
+    }
+    
+  }
+  
+  /**
+   * Constructor to initialize the GUI
+   * 
+   * @param listModelsPanel	the panel to use
+   */
+  public DefaultModelsPanel(ListModelsPanel listModelsPanel) {
+    m_ListModelsPanel = listModelsPanel;
+    
+    readProperties();
+    
+    createDefaultModelsPanel();
+  }
+  
+  /**
+   * This grabs the relevant properties from the Default model
+   * properties file.
+   *
+   */
+  private void readProperties() {
+    
+    m_DefaultFileNames = DEFAULT_PROPERTIES.getProperty("files").split(", ");
+    
+    m_LargeTrainTimeModels = DEFAULT_PROPERTIES.getProperty("train_time").split(", ");
+    
+    m_LargeTestTimeModels = DEFAULT_PROPERTIES.getProperty("test_time").split(", ");
+    
+    m_LargeFileSizeModels = DEFAULT_PROPERTIES.getProperty("file_size").split(", ");
+  }
+  
+  /**
+   * Initializes the GUI
+   *
+   */
+  private void createDefaultModelsPanel() {
+    
+    setLayout(new GridBagLayout());
+    GridBagConstraints gbc = new GridBagConstraints();
+    
+    JLabel defaultFileLabel = new JLabel("Select default set: ");
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(defaultFileLabel, gbc);
+    
+    m_DefaultFilesComboBox = new JComboBox(m_DefaultFileNames);
+    m_DefaultFilesComboBox.setSelectedItem(m_DefaultFileNames[0]);
+    m_DefaultFilesComboBox.addActionListener(this);
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 0;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(m_DefaultFilesComboBox, gbc);
+    
+    m_RefreshButton = new JButton("Reload set");
+    m_RefreshButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 0;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(m_RefreshButton, gbc);
+    
+    JLabel excludeModelsLabel = new JLabel("Exclude models w/ large");
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 1;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(excludeModelsLabel, gbc);
+    
+    m_ExcludeModelsComboBox = new JComboBox(EXCLUDE_OPTIONS);
+    m_ExcludeModelsComboBox.setSelectedItem(EXCLUDE_OPTIONS[0]);
+    //m_ExcludeModelsComboBox.addActionListener(this);
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 1;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(m_ExcludeModelsComboBox, gbc);
+    
+    m_ExcludeModelsButton = new JButton("Exclude");
+    m_ExcludeModelsButton.setToolTipText(
+	"Exclude this type of models from the current working list");
+    m_ExcludeModelsButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 1;
+    gbc.gridwidth = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(m_ExcludeModelsButton, gbc);
+    
+    JPanel modelListPanel = new JPanel();
+    modelListPanel.setBorder(BorderFactory
+	.createTitledBorder("Working set of Default Library Models"));
+    
+    m_ModelList = new ModelList();
+    
+    m_ModelList.getInputMap().put(
+	KeyStroke.getKeyStroke("released DELETE"), "deleteSelected");
+    m_ModelList.getActionMap().put("deleteSelected",
+	new AbstractAction("deleteSelected") {
+      
+      private static final long serialVersionUID = 4601977182190493654L;
+      
+      public void actionPerformed(ActionEvent evt) {
+	
+	Object[] currentModels = m_ModelList.getSelectedValues();
+	
+	ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+	
+	for (int i = 0; i < currentModels.length; i++) {
+	  dataModel.removeElement((EnsembleLibraryModel) currentModels[i]);
+	}
+	
+	//Shrink the selected range to the first index that was selected
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+	
+      }
+    });
+    
+    m_ModelList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+    m_ModelList.setLayoutOrientation(JList.VERTICAL);
+    //m_ModelList.setVisibleRowCount(12);
+    
+    ToolTipManager.sharedInstance().registerComponent(m_ModelList);
+    
+    JScrollPane listView = new JScrollPane(m_ModelList);
+    listView
+    .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+    listView.setPreferredSize(new Dimension(150, 50));
+    
+    modelListPanel.setLayout(new BorderLayout());
+    modelListPanel.add(listView, BorderLayout.CENTER);
+    
+    gbc.weightx = 1;
+    gbc.weighty = 1;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.gridx = 0;
+    gbc.gridy = 2;
+    gbc.gridwidth = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(modelListPanel, gbc);
+    
+    m_RemoveSelectedButton = new JButton("Remove Selected");
+    m_RemoveSelectedButton.addActionListener(this);
+    m_RemoveSelectedButton.setToolTipText(
+	"Remove the selected models from the current default set");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weighty = 0;
+    gbc.gridx = 0;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.weightx = 1;
+    gbc.gridwidth = 1;
+    add(m_RemoveSelectedButton, gbc);
+    
+    m_AddSelectedButton = new JButton("Add Selected");
+    m_AddSelectedButton.addActionListener(this);
+    m_AddSelectedButton.setToolTipText("Add selected models in "
+	+ "above list to the library");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    gbc.weightx = 1;
+    add(m_AddSelectedButton, gbc);
+    
+    m_AddAllButton = new JButton("Add All");
+    m_AddAllButton.addActionListener(this);
+    m_AddAllButton.setToolTipText("Add all models in the above"
+	+ "list to the Libray");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    gbc.weightx = 1;
+    add(m_AddAllButton, gbc);
+    
+    m_ListUpdatePending = true;
+    
+  }
+  
+  /**
+   * This method is called in response to user actions prompting
+   * us to reload the model list: when they select a new list, 
+   * or hit reload.
+   *
+   */
+  public void updateDefaultList() {
+    
+    ((ModelList.SortedListModel) m_ModelList.getModel()).clear();
+    
+    String ensemblePackageString = getPackageName();
+    
+    int index = m_DefaultFilesComboBox.getSelectedIndex();
+    
+    Vector classifiers = null;
+    
+    LibrarySerialization serialization;
+    try {
+      
+      serialization = new LibrarySerialization();
+      
+      String defaultFileString = ensemblePackageString
+      + m_DefaultFileNames[index].trim() + ".model.xml";
+      
+      //System.out.println(defaultFileString);
+      
+      InputStream is = ClassLoader.getSystemResourceAsStream(defaultFileString);
+      
+      if (is == null) {
+	File f = new File(defaultFileString);
+	if (f.exists()) {
+	  System.out.println("file existed: " + f.getPath());
+	} else {
+	  System.out.println("file didn't exist: " + f.getPath());
+	}
+	
+      }
+      
+      classifiers = (Vector) serialization.read(ClassLoader.getSystemResourceAsStream(defaultFileString));
+      
+      for (Iterator it = classifiers.iterator(); it.hasNext();) {
+	EnsembleLibraryModel model = m_ListModelsPanel.getLibrary().createModel((Classifier) it.next());
+	model.testOptions();
+	((ModelList.SortedListModel) m_ModelList.getModel()).add(model);
+      }
+      
+    } catch (Exception e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Deals with user input to the various buttons in this GUI
+   * 
+   * @param e		the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList
+	.getModel());
+    
+    if (e.getSource() == m_RefreshButton) {
+      updateDefaultList();
+      
+    } else if (e.getSource() == m_DefaultFilesComboBox) {
+      updateDefaultList();
+      
+    } else if (e.getSource() == m_RemoveSelectedButton) {
+      
+      //here we simply get the list of models that are 
+      //currently selected and ten remove them from the list
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	dataModel.removeElement(currentModels[i]);
+      }
+      
+      //Shrink the selected range to the first index that was selected
+      if (m_ModelList.getSelectedIndices().length > 0) {
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+      }
+      
+    } else if (e.getSource() == m_AddAllButton) {
+      
+      //here we just need to add all of the models to the 
+      //ListModelsPanel object
+      
+      Iterator it = dataModel.iterator();
+      
+      while (it.hasNext()) {
+	EnsembleLibraryModel currentModel = (EnsembleLibraryModel) it.next();
+	m_ListModelsPanel.addModel(currentModel);
+      }
+      
+      dataModel.clear();
+      
+    } else if (e.getSource() == m_AddSelectedButton) {
+      
+      //here we simply get the list of models that are 
+      //currently selected and ten remove them from the list
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	
+	m_ListModelsPanel.addModel((EnsembleLibraryModel) currentModels[i]);
+	dataModel.removeElement(currentModels[i]);
+      }
+    } else if (e.getSource() == m_ExcludeModelsButton) {
+      
+      if (m_ExcludeModelsComboBox.getSelectedIndex() == 0) {
+	applyTrainTimeFilters();
+      } else if (m_ExcludeModelsComboBox.getSelectedIndex() == 1) {
+	applyTestTimeFilters();
+      } else if (m_ExcludeModelsComboBox.getSelectedIndex() == 2) {
+	applyFileSizeFilters();
+      }
+    }
+  }
+  
+  /**
+   * this listener event fires when the use switches back to this panel
+   * it checks to se if the working directory has changed since they were
+   * here last.  If so then it updates the model list.
+   * 
+   * @param e		the event
+   */
+  public void stateChanged(ChangeEvent e) {
+    JTabbedPane pane = (JTabbedPane) e.getSource();
+    
+    if (pane.getSelectedComponent().equals(this) && m_ListUpdatePending) {
+      
+      updateDefaultList();
+      m_ListUpdatePending = false;
+    }
+  }
+  
+  /**
+   * Removes models from the list that fit the regular expressions
+   * defining models that have large train times
+   */
+  public void applyTrainTimeFilters() {
+    
+    //create an array of patterns and then pass them to the apply 
+    //filters method
+    Pattern patterns[] = new Pattern[m_LargeTrainTimeModels.length];
+    
+    for (int i = 0; i < m_LargeTrainTimeModels.length; i++) {
+      patterns[i] = Pattern.compile(m_LargeTrainTimeModels[i]);
+    }
+    
+    applyFilters(patterns);
+  }
+  
+  /**
+   * Removes models from the list that fit the regular expressions
+   * defining models that have large test times
+   */
+  public void applyTestTimeFilters() {
+    
+    //create an array of patterns and then pass them to the apply 
+    //filters method
+    Pattern patterns[] = new Pattern[m_LargeTestTimeModels.length];
+    
+    for (int i = 0; i < m_LargeTestTimeModels.length; i++) {
+      patterns[i] = Pattern.compile(m_LargeTestTimeModels[i]);
+    }
+    
+    applyFilters(patterns);
+  }
+  
+  /**
+   * Removes models from the list that fit the regular expressions
+   * defining models that have large file sizes
+   */
+  public void applyFileSizeFilters() {
+    
+    //create an array of patterns and then pass them to the apply 
+    //filters method
+    Pattern patterns[] = new Pattern[m_LargeFileSizeModels.length];
+    
+    for (int i = 0; i < m_LargeFileSizeModels.length; i++) {
+      patterns[i] = Pattern.compile(m_LargeFileSizeModels[i]);
+    }
+    
+    applyFilters(patterns);
+  }
+  
+  /**
+   * This is the code common to the previous three methods.  It basically 
+   * takes a Java regexp pattern and applies it to the currently selected 
+   * list of models, removing those that match as it goes.  
+   *
+   * @param patterns	the regexp patterns
+   */
+  public void applyFilters(Pattern[] patterns) {
+    
+    ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList
+	.getModel());
+    
+    //this will track all of the models that we need to remove
+    //from our list
+    Vector toRemove = new Vector();
+    
+    //here we simply get the list of models that are 
+    //currently selected and ten remove them from the list
+    //if they match one of the patterns
+    
+    Iterator it = dataModel.iterator();
+    
+    while (it.hasNext()) {
+      EnsembleLibraryModel currentModel = (EnsembleLibraryModel) it.next();
+      
+      for (int i = 0; i < patterns.length; i++) {
+	if (patterns[i].matcher(currentModel.getStringRepresentation()).matches()) {
+	  toRemove.add(currentModel);
+	  break;
+	}
+      }
+    }
+    
+    it = toRemove.iterator();
+    
+    while (it.hasNext()) {
+      dataModel.removeElement(it.next());
+    }
+  }
+  
+  /**
+   * this bit of code grabs all of the .model.xml files located in
+   * the ensemble selection package directory.  I decided to detect this *
+   * directory in case we change the package name which I think we planned
+   * on doing.  
+   * 
+   * @return		the package name
+   */
+  public static String getPackageName() {
+    return 
+       EnsembleSelectionLibrary.class.toString()
+      .replaceAll("class ", "")
+      .replaceAll("EnsembleSelectionLibrary", "")
+      .replaceAll("\\.", "/");
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LibrarySerialization.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LibrarySerialization.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LibrarySerialization.java	(revision 29)
@@ -0,0 +1,62 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * LibrarySerialization.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.core.xml.XMLBasicSerialization;
+
+/**
+ * For serializing LibraryModels.  This class uses the existing weka 
+ * infrastructure to let us easily serialize classifier specifications
+ * in a nice solid and stable XML format.  This class is responsible for 
+ * creating the .model.xml files.  Note that this truly was a life saver
+ * when we found out that saving the string representations , e.g. 
+ * "weka.classifiers.meta.foobar -X 1 -Y 2"  in a flat file simply did 
+ * not work for lots of different kinds of classifiers.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ */
+public class LibrarySerialization 
+  extends XMLBasicSerialization {
+  
+  /**
+   * initializes the serialization
+   * 
+   * @throws Exception if initialization fails
+   */
+  public LibrarySerialization() throws Exception {
+    super();
+  }
+  
+  /**
+   * generates internally a new XML document and clears also the IgnoreList and
+   * the mappings for the Read/Write-Methods
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  public void clear() throws Exception {
+    super.clear();
+    
+    // allow
+    m_Properties.addAllowed(weka.classifiers.Classifier.class, "options");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ListModelsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ListModelsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ListModelsPanel.java	(revision 29)
@@ -0,0 +1,402 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ListModelsPanel.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.classifiers.EnsembleLibrary;
+import weka.classifiers.EnsembleLibraryModel;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * This class is responsible for creating the main panel in the library editor
+ * gui. It is responsible for displaying the entire list of models currently in
+ * the list. It is also responsible for allowing the user to save/load this list
+ * as a flat file and choosing the working directory for the library.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.2 $
+ */
+public class ListModelsPanel
+  extends JPanel 
+  implements ActionListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -1986253077944432252L;
+
+  /** The library being edited */
+  private EnsembleLibrary m_Library;
+  
+  /** The button for removing selected models */
+  private JButton m_RemoveSelectedButton;
+  
+  /** The button for opening a model list from a file */
+  private JButton m_OpenModelFileButton;
+  
+  /** The button for saving a model list to a file */
+  private JButton m_SaveModelFileButton;
+  
+  /** The ModelList object that displays all currently selected models */
+  private ModelList m_ModelList;
+  
+  /** The file chooser for the user to select model list files to save and load */
+  private JFileChooser m_modelListChooser = new JFileChooser(new File(
+      System.getProperty("user.dir")));
+  
+  /** 
+   * Constructor to initialize library object and GUI
+   * 
+   * @param library 	the library to use
+   */
+  public ListModelsPanel(EnsembleLibrary library) {
+    m_Library = library;
+    createListModelsPanel();
+  }
+  
+  /** 
+   * this is necessay to set the Library object after initiialization
+   * 
+   * @param library 	the library to use
+   */
+  public void setLibrary(EnsembleLibrary library) {
+    
+    m_Library = library;
+  }
+  
+  /**
+   * Builds the GUI.
+   */
+  private void createListModelsPanel() {
+    GridBagConstraints gbc = new GridBagConstraints();
+    setLayout(new GridBagLayout());
+    
+    JPanel modelListPanel = new JPanel();
+    modelListPanel.setBorder(
+	BorderFactory.createTitledBorder("Currently Chosen Library Models"));
+    
+    m_ModelList = new ModelList();
+    
+    m_ModelList.getInputMap().put(
+	KeyStroke.getKeyStroke("released DELETE"), "deleteSelected");
+    m_ModelList.getActionMap().put("deleteSelected",
+	new AbstractAction("deleteSelected") {
+
+      private static final long serialVersionUID = 8178827388328307805L;
+      
+      public void actionPerformed(ActionEvent evt) {
+	
+	Object[] currentModels = m_ModelList.getSelectedValues();
+	
+	for (int i = 0; i < currentModels.length; i++) {
+	  removeModel((EnsembleLibraryModel) currentModels[i]);
+	}
+	
+	// Shrink the selected range to the first index that was
+	// selected
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+      }
+    });
+    
+    m_ModelList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+    m_ModelList.setVisibleRowCount(-1);
+    m_ModelList.setLayoutOrientation(JList.VERTICAL);
+    JScrollPane listScroller = new JScrollPane(m_ModelList);
+    listScroller.setPreferredSize(new Dimension(150, 50));
+    
+    modelListPanel.setLayout(new BorderLayout());
+    modelListPanel.add(listScroller, BorderLayout.CENTER);
+    
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weightx = 1;
+    gbc.weighty = 1;
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 3;
+    add(modelListPanel, gbc);
+    
+    m_RemoveSelectedButton = new JButton("Remove Selected");
+    m_RemoveSelectedButton.addActionListener(this);
+    m_RemoveSelectedButton.setToolTipText(
+	"Remove all currently selected models from the above list");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.weightx = 1;
+    gbc.weighty = 0;
+    gbc.gridwidth = 1;
+    add(m_RemoveSelectedButton, gbc);
+    
+    m_modelListChooser.setAcceptAllFileFilterUsed(false);
+    
+    XMLModelFileFilter xmlFilter = new XMLModelFileFilter();
+    m_modelListChooser.addChoosableFileFilter(xmlFilter);
+    m_modelListChooser.addChoosableFileFilter(new FlatModelFileFilter());
+    // set up the file chooser
+    m_modelListChooser.setFileFilter(xmlFilter);
+    m_modelListChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    // create the buttons + field
+    m_OpenModelFileButton = new JButton("Open...");
+    m_OpenModelFileButton.addActionListener(this);
+    m_OpenModelFileButton.setToolTipText(
+	"Import a model list file from the file system");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    gbc.weightx = 1;
+    gbc.weighty = 0;
+    add(m_OpenModelFileButton, gbc);
+    
+    m_SaveModelFileButton = new JButton("Save...");
+    m_SaveModelFileButton.addActionListener(this);
+    m_SaveModelFileButton.setToolTipText(
+	"Save the current list of models to a file");
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    gbc.weightx = 1;
+    gbc.weighty = 0;
+    add(m_SaveModelFileButton, gbc);
+  }
+  
+  /**
+   * returns the current library
+   * 
+   * @return		the current library
+   */
+  public EnsembleLibrary getLibrary() {
+    return m_Library;
+  }
+  
+  /**
+   * This handles all of the button events. Specifically the buttons
+   * associated with saving/loading model lists as well as removing models
+   * from the currently displayed list.
+   * 
+   * @param e
+   *            the action event that occured
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    if (e.getSource() == m_OpenModelFileButton) {
+      
+      int returnVal = m_modelListChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	
+	clearAll();
+	
+	File selectedFile = m_modelListChooser.getSelectedFile();
+	
+	// add .mlf extension if that was the file filter used
+	if ((m_modelListChooser.getFileFilter() instanceof FlatModelFileFilter)) {
+	  if (!selectedFile.getName().endsWith(
+	      EnsembleLibrary.FLAT_FILE_EXTENSION)) {
+	    selectedFile = new File(selectedFile.getPath()
+		+ EnsembleLibrary.FLAT_FILE_EXTENSION);
+	  }
+	  
+	  // otherwise use .model.xml file extension by default
+	} else {
+	  if (!selectedFile.getName().endsWith(
+	      EnsembleLibrary.XML_FILE_EXTENSION)) {
+	    selectedFile = new File(selectedFile.getPath()
+		+ EnsembleLibrary.XML_FILE_EXTENSION);
+	  }
+	}
+	
+	EnsembleLibrary.loadLibrary(selectedFile, this, m_Library);
+	
+	ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+	
+	TreeSet models = m_Library.getModels();
+	for (Iterator it = models.iterator(); it.hasNext();) {
+	  addModel((EnsembleLibraryModel) it.next());
+	  // dataModel.add(it.next());
+	}
+	
+      }
+      
+    } else if (e.getSource() == m_SaveModelFileButton) {
+      
+      int returnVal = m_modelListChooser.showSaveDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	
+	File selectedFile = m_modelListChooser.getSelectedFile();
+	
+	// add .mlf extension if that was the file filter used
+	if ((m_modelListChooser.getFileFilter() instanceof FlatModelFileFilter)) {
+	  if (!selectedFile.getName().endsWith(
+	      EnsembleLibrary.FLAT_FILE_EXTENSION)) {
+	    selectedFile = new File(selectedFile.getPath()
+		+ EnsembleLibrary.FLAT_FILE_EXTENSION);
+	  }
+	  
+	  // otherwise use .model.xml file extension by default
+	} else {
+	  if (!selectedFile.getName().endsWith(
+	      EnsembleLibrary.XML_FILE_EXTENSION)) {
+	    selectedFile = new File(selectedFile.getPath()
+		+ EnsembleLibrary.XML_FILE_EXTENSION);
+	  }
+	}
+	
+	EnsembleLibrary.saveLibrary(selectedFile, m_Library, this);
+      }
+      
+    } else if (e.getSource() == m_RemoveSelectedButton) {
+      
+      // here we simply get the list of models that are
+      // currently selected and ten remove them from the list
+      
+      // ModelList.SortedListModel dataModel =
+      // ((ModelList.SortedListModel)m_ModelList.getModel());
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	removeModel((EnsembleLibraryModel) currentModels[i]);
+      }
+      
+      // Shrink the selected range to the first index that was selected
+      if (m_ModelList.getSelectedIndices().length > 0) {
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+      }
+    }
+  }
+  
+  /**
+   * This removes all the models from the current list in the GUI
+   */
+  public void clearAll() {
+    ((ModelList.SortedListModel) m_ModelList.getModel()).clear();
+    m_Library.clearModels();
+  }
+  
+  /**
+   * Adds a model to the current library
+   * 
+   * @param model	the model to add
+   */
+  public void addModel(EnsembleLibraryModel model) {
+    
+    ((ModelList.SortedListModel) m_ModelList.getModel()).add(model);
+    m_Library.addModel(model);
+    
+  }
+  
+  /**
+   * Removes a model to the current library
+   * 
+   * @param model	the model to remove
+   */
+  public void removeModel(EnsembleLibraryModel model) {
+    ((ModelList.SortedListModel) m_ModelList.getModel())
+    .removeElement(model);
+    m_Library.removeModel(model);
+  }
+  
+  /**
+   * A helper class for filtering xml model files
+   */
+  class XMLModelFileFilter 
+    extends FileFilter {
+    
+    /**
+     * Whether the given file is accepted by this filter.
+     * 
+     * @param file	the file to check
+     * @return		true if the file got accepted
+     */
+    public boolean accept(File file) {
+      String filename = file.getName();
+      return (filename.endsWith(EnsembleLibrary.XML_FILE_EXTENSION) || file
+	  .isDirectory());
+    }
+    
+    /**
+     * The description of this filter.
+     * 
+     * @return		the description
+     */
+    public String getDescription() {
+      return "XML Library Files (*.model.xml)";
+    }
+  }
+  
+  /**
+   * A helper class for filtering flat model files
+   * 
+   */
+  class FlatModelFileFilter 
+    extends FileFilter {
+    
+    /**
+     * Whether the given file is accepted by this filter.
+     * 
+     * @param file	the file to check
+     * @return		true if the file got accepted
+     */
+    public boolean accept(File file) {
+      String filename = file.getName();
+      return (filename.endsWith(EnsembleLibrary.FLAT_FILE_EXTENSION) || file
+	  .isDirectory());
+    }
+    
+    /**
+     * The description of this filter.
+     * 
+     * @return		the description
+     */
+    public String getDescription() {
+      return "Model List Files (*.mlf)";
+    }
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LoadModelsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LoadModelsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/LoadModelsPanel.java	(revision 29)
@@ -0,0 +1,469 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LoadModelsPanel.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.classifiers.EnsembleLibraryModel;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibraryModel;
+import weka.gui.EnsembleSelectionLibraryEditor;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.util.Iterator;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.ToolTipManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * 
+ * @author  Robert Jung
+ * @version $Revision: 1.2 $
+ */
+public class LoadModelsPanel
+  extends JPanel
+  implements ActionListener, PropertyChangeListener, ChangeListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -6961209378227736515L;
+
+  /**
+   * This is a reference to the main gui object that is responsible for
+   * displaying the model library. This panel will add models to the main
+   * panel through methods in this object.
+   */
+  private ListModelsPanel m_ListModelsPanel;
+  
+  /**
+   * This will display messages associated with model loading. Currently the
+   * number of models found and the number of data sets.
+   */
+  private JLabel m_LoadingLabel;
+  
+  /**
+   * This will display the current working Directory of the Ensemble Library.
+   */
+  private JLabel m_DirectoryLabel;
+  
+  /**
+   * This button will refresh the model list currently displayed in the case
+   * that either the working directory changed or the models stored in it
+   * changed.
+   */
+  private JButton m_RefreshListButton;
+  
+  /**
+   * This button will allow users to remove all of the models currently
+   * selected in the m_ModeList object
+   */
+  private JButton m_RemoveSelectedButton;
+  
+  /**
+   * This button will allow users to add all models currently in the model
+   * list to the model library in the ListModelsPanel. Note that this
+   * operation will exclude any models that had errors
+   */
+  private JButton m_AddAllButton;
+  
+  /**
+   * This button will add all of the models that had are currently selected in
+   * the model list.
+   */
+  private JButton m_AddSelectedButton;
+  
+  /**
+   * This object will store all of the models that were found in the library's
+   * current working directory. The ModelList class is JList list = new
+   * JList(listModel); a custom class in weka.gui that knows how to display
+   * library model objects in a JList
+   */
+  private ModelList m_ModelList;
+  
+  /**
+   * A reference to the main library object so that we can get the current
+   * working Directory.
+   */
+  private EnsembleSelectionLibrary m_Library;
+  
+  /**
+   * A reference to the libary editor that contains this panel so that we can
+   * see if we're selected or not.
+   */
+  private EnsembleSelectionLibraryEditor m_EnsembleLibraryEditor;
+  
+  /**
+   * 
+   */
+  private boolean m_workingDirectoryChanged = false;
+  
+  /**
+   * This constructor simply stores the reference to the ListModelsPanel and
+   * build the user interface.
+   * 
+   * @param listModelsPanel	the panel
+   * @param editor		the editor 
+   */
+  public LoadModelsPanel(ListModelsPanel listModelsPanel,
+      EnsembleSelectionLibraryEditor editor) {
+    
+    m_ListModelsPanel = listModelsPanel;
+    m_EnsembleLibraryEditor = editor;
+    createLoadModelsPanel();
+  }
+  
+  /**
+   * This method is responsible for building the user interface.
+   */
+  private void createLoadModelsPanel() {
+    GridBagConstraints gbc = new GridBagConstraints();
+    setLayout(new GridBagLayout());
+    
+    m_RefreshListButton = new JButton("Reload List");
+    m_RefreshListButton.setToolTipText(
+	"Discover the set of models existing in the current working directory");
+    m_RefreshListButton.addActionListener(this);
+    gbc.weightx = 0;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_RefreshListButton, gbc);
+    
+    m_DirectoryLabel = new JLabel("");
+    updateDirectoryLabel();
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 1;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 3;
+    add(m_DirectoryLabel, gbc);
+    
+    m_LoadingLabel = new JLabel("");
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 2;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 3;
+    add(m_LoadingLabel, gbc);
+    
+    JPanel modelListPanel = new JPanel();
+    modelListPanel.setBorder(
+	BorderFactory.createTitledBorder("Working Set of Loadable Models"));
+    
+    m_ModelList = new ModelList();
+    
+    m_ModelList.getInputMap().put(
+	KeyStroke.getKeyStroke("released DELETE"), "deleteSelected");
+    m_ModelList.getActionMap().put("deleteSelected",
+	new AbstractAction("deleteSelected") {
+      
+      private static final long serialVersionUID = 4557836979081722983L;
+      
+      public void actionPerformed(ActionEvent evt) {
+	
+	Object[] currentModels = m_ModelList.getSelectedValues();
+	
+	ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+	
+	for (int i = 0; i < currentModels.length; i++) {
+	  dataModel.removeElement((EnsembleLibraryModel) currentModels[i]);
+	}
+	
+	// Shrink the selected range to the first index that was
+	// selected
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+	
+      }
+    });
+    
+    m_ModelList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+    m_ModelList.setLayoutOrientation(JList.VERTICAL);
+    // m_ModelList.setVisibleRowCount(12);
+    
+    ToolTipManager.sharedInstance().registerComponent(m_ModelList);
+    
+    JScrollPane listView = new JScrollPane(m_ModelList);
+    listView
+    .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+    listView.setPreferredSize(new Dimension(150, 50));
+    
+    modelListPanel.setLayout(new BorderLayout());
+    modelListPanel.add(listView, BorderLayout.CENTER);
+    
+    gbc.weightx = 1;
+    gbc.weighty = 1;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.gridx = 0;
+    gbc.gridy = 3;
+    gbc.gridwidth = 3;
+    gbc.anchor = GridBagConstraints.WEST;
+    add(modelListPanel, gbc);
+    
+    m_RemoveSelectedButton = new JButton("Remove Selected");
+    m_RemoveSelectedButton.setToolTipText(
+	"Remove all selected models from the above list");
+    m_RemoveSelectedButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.weighty = 0;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 0;
+    gbc.gridy = 4;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_RemoveSelectedButton, gbc);
+    
+    m_AddSelectedButton = new JButton("Add Selected");
+    m_AddSelectedButton.setToolTipText(
+	"Add selected models in the above list to the model library");
+    m_AddSelectedButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 1;
+    gbc.gridy = 4;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_AddSelectedButton, gbc);
+    
+    m_AddAllButton = new JButton("Add All");
+    m_AddAllButton.setToolTipText(
+	"Add all models in the above list to the model library");
+    m_AddAllButton.addActionListener(this);
+    gbc.weightx = 1;
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.gridx = 2;
+    gbc.gridy = 4;
+    gbc.anchor = GridBagConstraints.WEST;
+    gbc.gridwidth = 1;
+    add(m_AddAllButton, gbc);
+  }
+  
+  /**
+   * Sets the library to use
+   * 
+   * @param library 	the library to use
+   */
+  public void setLibrary(EnsembleSelectionLibrary library) {
+    m_Library = library;
+    updateDirectoryLabel();
+    loadModels();
+  }
+  
+  /**
+   * This method will load all of the models found in the current working
+   * directory into the ModelList object
+   */
+  public void loadModels() {
+    
+    ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+    
+    int directoryCount = 0;
+    int modelCount = 0;
+    
+    dataModel.clear();
+    
+    File directory = m_Library.getWorkingDirectory();
+    File subDirectories[] = directory.listFiles();
+    
+    if (subDirectories != null) {
+      
+      for (int i = 0; i < subDirectories.length; i++) {
+	
+	if (subDirectories[i].isDirectory()
+	    && subDirectories[i].getName().matches(
+	    ".*_instances_.*")) {
+	  
+	  directoryCount++;
+	  
+	  File[] subDirectoryFiles = subDirectories[i].listFiles();
+	  
+	  for (int j = 0; j < subDirectoryFiles.length; j++) {
+	    
+	    if (subDirectoryFiles[j].getName().matches(".*.elm")) {
+	      
+	      EnsembleSelectionLibraryModel model = EnsembleSelectionLibraryModel
+	      .loadModel(subDirectoryFiles[j].getPath());
+	      
+	      // get those Classifier[] objects garbage collected!
+	      model.releaseModel();
+	      
+	      if (!dataModel.contains(model)) {
+		
+		modelCount++;
+		dataModel.add(model);
+	      }
+	    }
+	  }
+	}
+      }
+    }
+    
+    updateLoadingLabel(modelCount, directoryCount);
+  }
+  
+  /**
+   * updates the "directory" label
+   */
+  public void updateDirectoryLabel() {
+    if (m_Library != null) {
+      m_DirectoryLabel.setText("Directory: "
+	  + m_Library.getWorkingDirectory());
+    }
+  }
+  
+  /**
+   * updates the "loading" label
+   * 
+   * @param modelCount		the number of models found
+   * @param directoryCount	the number of directories found
+   */
+  public void updateLoadingLabel(int modelCount, int directoryCount) {
+    m_LoadingLabel.setText(" " + modelCount
+	+ " unique model descriptions found in " + directoryCount
+	+ " directories");
+  }
+  
+  /**
+   * This will support the button triggered events for this panel.
+   * 
+   * @param e		the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    ModelList.SortedListModel dataModel = ((ModelList.SortedListModel) m_ModelList.getModel());
+    
+    if (e.getSource() == m_RefreshListButton) {
+      
+      updateDirectoryLabel();
+      loadModels();
+      
+    } else if (e.getSource() == m_RemoveSelectedButton) {
+      
+      // here we simply get the list of models that are
+      // currently selected and ten remove them from the list
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	dataModel.removeElement(currentModels[i]);
+      }
+      
+      // Shrink the selected range to the first index that was selected
+      if (m_ModelList.getSelectedIndices().length > 0) {
+	int selected[] = new int[1];
+	selected[0] = m_ModelList.getSelectedIndices()[0];
+	m_ModelList.setSelectedIndices(selected);
+      }
+      
+    } else if (e.getSource() == m_AddAllButton) {
+      
+      // here we just need to add all of the models to the
+      // ListModelsPanel object
+      
+      Iterator it = dataModel.iterator();
+      
+      while (it.hasNext()) {
+	EnsembleLibraryModel currentModel = (EnsembleLibraryModel) it
+	.next();
+	
+	m_ListModelsPanel.addModel(currentModel);
+      }
+      
+      dataModel.clear();
+      
+    } else if (e.getSource() == m_AddSelectedButton) {
+      
+      // here we simply get the list of models that are
+      // currently selected and ten remove them from the list
+      
+      Object[] currentModels = m_ModelList.getSelectedValues();
+      
+      for (int i = 0; i < currentModels.length; i++) {
+	
+	m_ListModelsPanel
+	.addModel((EnsembleLibraryModel) currentModels[i]);
+	dataModel.removeElement(currentModels[i]);
+      }
+    }
+  }
+  
+  /**
+   * This property change listener gets fired whenever the library's default
+   * working directory changes. Here we check to see if the user has currently
+   * selected the load models panel. If so then we immediately update the
+   * list. Otherwise, we cache this event in the workingDirectoryChanged field
+   * and reload the model list when the use switches back to this panel. This
+   * is taken care of by the next state changed listener
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    
+    if (m_EnsembleLibraryEditor.isLoadModelsPanelSelected()) {
+      updateDirectoryLabel();
+      loadModels();
+    } else {
+      m_workingDirectoryChanged = true;
+    }
+  }
+  
+  /**
+   * this listener event fires when the use switches back to this panel it
+   * checks to se if the working directory has changed since they were here
+   * last. If so then it updates the model list.
+   * 
+   * @param e		the event
+   */
+  public void stateChanged(ChangeEvent e) {
+    JTabbedPane pane = (JTabbedPane) e.getSource();
+    
+    if (pane.getSelectedComponent().equals(this)
+	&& m_workingDirectoryChanged) {
+      
+      updateDirectoryLabel();
+      loadModels();
+      m_workingDirectoryChanged = false;
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ModelList.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ModelList.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/ModelList.java	(revision 29)
@@ -0,0 +1,203 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelList.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor;
+
+import weka.classifiers.EnsembleLibraryModel;
+import weka.classifiers.EnsembleLibraryModelComparator;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.swing.AbstractListModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JComponent;
+import javax.swing.JList;
+
+/**
+ * This class is basically a customization of the JList class to allow it
+ * to display LibraryModel objects.  It has two nested helper classes that
+ * respectively take care of rendering and modelling the List of models.
+ * 
+ * @author Robert Jung (mrbobjung@gmail.com)
+ */
+public class ModelList 
+  extends JList {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -421567241792939539L;
+
+  /**
+   * The constructor simply initializes the model and the renderer.
+   */
+  public ModelList() {
+    super();
+    this.setModel(new SortedListModel());
+    this.setCellRenderer(new ModelListRenderer());
+  }
+  
+  /**
+   * This nested helper class is responsible for rendering each Library
+   * Model object.  
+   */
+  public class ModelListRenderer 
+    extends DefaultListCellRenderer {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -7061163240718897794L;
+
+    /**
+     * This is the only method necessary to overload.  All we have to 
+     * do is print the String value of the model along with its index 
+     * in the ModelList data structure.
+     * 
+     * @param list		the JList
+     * @param value		the value
+     * @param index		the index of the value
+     * @param isSelected	if true the item is selected
+     * @param cellHasFocus	whether it has the focus
+     * @return			the rendering component
+     */
+    public Component getListCellRendererComponent(JList list, Object value,
+	int index, boolean isSelected, boolean cellHasFocus) {
+      
+      Component modelComponent = null;
+      
+      if (value instanceof EnsembleLibraryModel) {
+	
+	EnsembleLibraryModel model = ((EnsembleLibraryModel) value);
+	
+	String modelString = index
+	+ ": "
+	+ model.getStringRepresentation().replaceAll(
+	    "weka.classifiers.", "");
+	
+	modelComponent = super.getListCellRendererComponent(list,
+	    modelString, index, isSelected, cellHasFocus);
+	
+	if (!model.getOptionsWereValid()) {
+	  modelComponent.setBackground(Color.pink);
+	}
+	
+	((JComponent) modelComponent).setToolTipText(model
+	    .getDescriptionText());
+	
+      }
+      
+      return modelComponent;
+    }
+  }
+  
+  /**
+   * 
+   * This is a helper class that creates a custom list model for the ModelList class.
+   * It basically ensures that all model entries are 1) unique - so that no duplicate
+   * entries can find their way in, and 2) sorted alphabetically.  It also numbers 
+   * them.
+   * <p/>
+   * This nested class was adapted from code found in a freely available tutorial on
+   * sorting JList entries by John Zukowski - wait a sec, he's the guy who wrote the
+   * other tutorial I cited in the AddModelsPanel. wow, different web site even. 
+   * This guy is really in to writing tutorials.  Anyway, it was very helpful, if 
+   * you would like to know more about implementing swing MVC stuff.
+   * <p/>
+   * Anyway, John Zukowski's tutorial can be found at: <br/>
+   * <a href="http://www.jguru.com/faq/view.jsp?EID=15245" target="_blank">http://www.jguru.com/faq/view.jsp?EID=15245</a>
+   */
+  public class SortedListModel extends AbstractListModel {
+    
+    /** for serialization */
+    private static final long serialVersionUID = -8334675481243839371L;
+    
+    /** Define a SortedSet */
+    SortedSet m_Models;
+    
+    /**
+     * default constructor
+     */
+    public SortedListModel() {
+      // Create a TreeSet
+      // Store it in SortedSet variable
+      m_Models = new TreeSet(new EnsembleLibraryModelComparator());
+    }
+    
+    // ListModel methods
+    public int getSize() {
+      // Return the model size
+      return m_Models.size();
+    }
+    
+    public Object getElementAt(int index) {
+      // Return the appropriate element
+      return m_Models.toArray()[index];
+    }
+    
+    // Other methods
+    public void add(Object element) {
+      if (m_Models.add(element)) {
+	fireContentsChanged(this, 0, getSize());
+      }
+    }
+    
+    public void addAll(Object elements[]) {
+      Collection c = Arrays.asList(elements);
+      m_Models.addAll(c);
+      fireContentsChanged(this, 0, getSize());
+    }
+    
+    public void clear() {
+      m_Models.clear();
+      fireContentsChanged(this, 0, getSize());
+    }
+    
+    public boolean contains(Object element) {
+      return m_Models.contains(element);
+    }
+    
+    public Object firstElement() {
+      // Return the appropriate element
+      return m_Models.first();
+    }
+    
+    public Iterator iterator() {
+      return m_Models.iterator();
+    }
+    
+    public Object lastElement() {
+      // Return the appropriate element
+      return m_Models.last();
+    }
+    
+    public boolean removeElement(Object element) {
+      boolean removed = m_Models.remove(element);
+      if (removed) {
+	fireContentsChanged(this, 0, getSize());
+      }
+      return removed;
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNode.java	(revision 29)
@@ -0,0 +1,160 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckBoxNode.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * This class is responsible for implementing the underlying logic of 
+ * tree nodes representing a single nominal value.  This is either going to
+ * be true/false values or an enumeration of values defined by the model.
+ * Check box nodes are relatively simple in that they are simply toggled
+ * on or off by the user indicating whether or not they are to be used.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class CheckBoxNode
+  extends DefaultMutableTreeNode
+  implements ItemListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 727140674668443817L;
+
+  /** tracks whether this node is currently selected as a model parameter */
+  private boolean m_Selected;
+  
+  /** the tip text for our node editor to display */
+  private String m_ToolTipText;
+  
+  /**
+   * The constructor initializes the members of this node.  Note that
+   * the text String is stored as the userObject.
+   * 
+   * @param name the name of this attribute
+   * @param selected the initial value of this node
+   * @param toolTipText the toolTipText to be displayed
+   */
+  public CheckBoxNode(String name, boolean selected, String toolTipText) {
+    super(name);
+    setName(name);
+    this.m_Selected = selected;
+    this.m_ToolTipText = toolTipText;
+  }
+  
+  /**
+   * getter for the node state
+   * 
+   * @return whether or not this node is selected
+   */
+  public boolean getSelected() {
+    return m_Selected;
+  }
+  
+  /**
+   * setter for the node state
+   * 
+   * @param newValue the new selected state
+   */
+  public void setSelected(boolean newValue) {
+    m_Selected = newValue;
+  }
+  
+  /**
+   * sets whether the box is selected
+   * 
+   * @param newValue	if true the box will be selected
+   */
+  public void setBoxSelected(boolean newValue) {
+    m_Selected = newValue;
+  }
+  
+  /**
+   * gets the name of the parameter value represented by this node 
+   * which is stored as the node's user object
+   * 
+   * @return the name of this parameter
+   */
+  public String getName() {
+    return (String) getUserObject().toString();
+  }
+  
+  /**
+   * sets the name of the parameter value represented by this node 
+   * and stores it as the node's user object
+   * 
+   * @param newValue	the new name
+   */
+  public void setName(String newValue) {
+    setUserObject(newValue);
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public String getToolTipText() {
+    return m_ToolTipText;
+  }
+  
+  /**
+   * this is a simple filter for the setUserObject method.  We basically
+   * don't want null values to be passed in.
+   * 
+   * @param o		the user object
+   */
+  public void setUserObject(Object o) {
+    if (o != null)
+      super.setUserObject(o);
+  }
+  
+  /**
+   * ToString methods prints out the toString method of this nodes user 
+   * object
+   * 
+   * @return		a string representation
+   */
+  public String toString() {
+    return getClass().getName() + "[" + getUserObject() + "/" + m_Selected
+    + "]";
+  }
+  
+  /**
+   * This is the listener that fires when the check box is actually toggled.
+   * Here we just want to change the selected state accordingly.
+   * 
+   * @param e		the event
+   */
+  public void itemStateChanged(ItemEvent e) {
+    if (e.getStateChange() == ItemEvent.SELECTED) {
+      setBoxSelected(true);
+      
+    } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+      setBoxSelected(false);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNodeEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNodeEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/CheckBoxNodeEditor.java	(revision 29)
@@ -0,0 +1,138 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    CheckBoxNodeEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+/** 
+ * This class is responsible for creating a simple checkBox editor for the 
+ * CheckBoxNode class.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class CheckBoxNodeEditor
+  extends JPanel
+  implements ItemListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -1506685976284982111L;
+
+  /** the checkbox the user will interact with */
+  private JCheckBox m_SelectedCheckBox;
+  
+  /** the label that will display the name of this parameter value */
+  private JLabel m_Label;
+  
+  /** colors we'll use  */
+  private Color textForeground, textBackground;
+  
+  /** a reference to the node this editor represents */
+  private CheckBoxNode m_Node;
+  
+  /**
+   * The constructor builds the simple CheckBox GUI based on the information 
+   * in the node that is passed to it.  Specifically, the GUI consists of only
+   * a JLabel showing the name and a CheckBox allowing the user to toggle it. 
+   * 
+   * @param node a reference to the node this editor represents
+   */
+  public CheckBoxNodeEditor(CheckBoxNode node) {
+    
+    this.m_Node = node;
+    
+    String name = node.getName();
+    boolean selected = node.getSelected();
+    
+    textForeground = UIManager.getColor("Tree.textForeground");
+    textBackground = UIManager.getColor("Tree.textBackground");
+    
+    setForeground(textForeground);
+    setBackground(textBackground);
+    
+    Font fontValue;
+    fontValue = UIManager.getFont("Tree.font");
+    
+    m_SelectedCheckBox = new JCheckBox();
+    m_SelectedCheckBox.setSelected(selected);
+    m_SelectedCheckBox.setForeground(textForeground);
+    m_SelectedCheckBox.setBackground(textBackground);
+    m_SelectedCheckBox.addItemListener(this);
+    add(m_SelectedCheckBox);
+    
+    m_Label = new JLabel(name);
+    if (fontValue != null) {
+      m_Label.setFont(fontValue);
+    }
+    m_Label.setForeground(textForeground);
+    m_Label.setBackground(textBackground);
+    add(m_Label);
+  }
+  
+  /**
+   * This method provides a way for the ModelTreeNodeEditor
+   * to register an itemListener with this editor.  This way, 
+   * after the user has done something, the tree can update its
+   * editing state as well as the tree node structure as necessary 
+   * 
+   * @param itemListener	the listener to use
+   */
+  public void setItemListener(ItemListener itemListener) {
+    if (m_SelectedCheckBox != null)
+      m_SelectedCheckBox.addItemListener(itemListener);
+  }
+  
+  /**
+   * This is the implementation of the itemListener for the CheckBoxNode.
+   * Note that when we are deselcting a CheckBox we have to ask our parent
+   * node if this is OK.  This is because we need to ensure that at least a
+   * single CheckBox underneath the parent remains checked because we need at 
+   * least one value for each classifier parameter.
+   * 
+   * @param e		the event
+   */
+  public void itemStateChanged(ItemEvent e) {
+    if (e.getStateChange() == ItemEvent.SELECTED) {
+      m_Node.setSelected(true);
+      m_Label.setEnabled(true);
+      
+    } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+      
+      PropertyNode parent = (PropertyNode) m_Node.getParent();
+      if (parent.canDeselect(m_Node)) {
+	m_Node.setSelected(false);
+	m_Label.setEnabled(false);
+      } else {
+	m_SelectedCheckBox.setSelected(true);
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/DefaultNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/DefaultNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/DefaultNode.java	(revision 29)
@@ -0,0 +1,161 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DefaultNode.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.gui.EnsembleLibraryEditor;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyEditor;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * This class is responsible for representing objects that we haven't explicitly
+ * written custom tree node editors for. In other words - Objects that are
+ * "weird". It didn't make sense for us to try to come up with a nice tree
+ * representation for absolutely everything, e.g. CostMatrixes or ArrayLists.
+ * This class is responsible for representing Classifier parameter values that
+ * are not numbers, an enumeration of values (e.g. true/false), or Objects that
+ * have their own GenericObjectEditors (like other Classifiers). So in these
+ * cases we can just use the default editor that came with the object.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class DefaultNode
+  extends DefaultMutableTreeNode
+  implements PropertyChangeListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -2182147677358461880L;
+
+  /** the name of this node */
+  private String m_Name;
+  
+  /** the tip text for our node editor to display */
+  private String m_ToolTipText;
+  
+  /** The default PropertyEditor that was supplied for this node */
+  private PropertyEditor m_PropertyEditor;
+  
+  /**
+   * The constructor initializes the members of this node.
+   * 
+   * @param name
+   *            the name of the value represented by this node
+   * @param toolTipText
+   *            the tool tip text to be displayed
+   * @param value
+   *            the intial value
+   * @param propertyEditor
+   *            the editor provided for this node
+   */
+  public DefaultNode(String name, String toolTipText, Object value,
+      PropertyEditor propertyEditor) {
+    
+    super(value);
+    
+    this.m_Name = name;
+    this.m_ToolTipText = toolTipText;
+    this.m_PropertyEditor = propertyEditor;
+    
+    m_PropertyEditor.addPropertyChangeListener(this);
+    
+  }
+  
+  /**
+   * this returns the property editor that was provided for this object. This
+   * propertyEditor object is initially chosen inside of the GenericObjectNode
+   * updateTree() method if you are interested in where it comes from.
+   * 
+   * @return the default editor for this node
+   */
+  public PropertyEditor getEditor() {
+    m_PropertyEditor.setValue(this.getUserObject());
+    return m_PropertyEditor;
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public String getToolTipText() {
+    return m_ToolTipText;
+  }
+  
+  /**
+   * gets the name of the parameter value represented by this node
+   * 
+   * @return the name of this parameter
+   */
+  public String getName() {
+    return m_Name;
+  }
+  
+  /**
+   * this is a simple filter for the setUserObject method. We basically don't
+   * want null values to be passed in.
+   * 
+   * @param o		the user object
+   */
+  public void setUserObject(Object o) {
+    if (o != null)
+      super.setUserObject(o);
+  }
+  
+  /**
+   * ToString method simply prints out the user object toString for this node
+   * 
+   * @return		a string representation
+   */
+  public String toString() {
+    return getClass().getName() + "[" + getUserObject().toString() + "]";
+  }
+  
+  /**
+   * This implements the PropertyChangeListener for this node that gets
+   * registered with its Editor. All we really have to do is change the Object
+   * value stored internally at this node when its editor says the value
+   * changed.
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    
+    Object source = evt.getSource();
+    
+    Object value = EnsembleLibraryEditor.getEditorValue(source);
+    
+    /*
+     * //was useful for debugging when we encountered some strange value
+     * types //these printouts tell you the classes that the editor is
+     * supplying System.out.println("prop name: " + evt.getPropertyName() +
+     * "new value: " + evt.getNewValue() + "old value: " +
+     * evt.getOldValue()); System.out.println("prop val: " +
+     * source.toString() + " expected val: " + value);
+     */
+    setUserObject(value);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNode.java	(revision 29)
@@ -0,0 +1,694 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GenericObjectNode.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.classifiers.Classifier;
+import weka.gui.GenericObjectEditor;
+import weka.gui.ensembleLibraryEditor.AddModelsPanel;
+
+import java.awt.Component;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.beans.PropertyVetoException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+/**
+ * This class is responsible for allowing users to choose an object that
+ * was provided with a GenericObjectEditor.  Just about every one of these 
+ * Objects is a Weka Classifier.  There are two important things that these
+ * nodes are responsible for beyond the other parameter node types.  First,
+ * they must discover all of the parameters that need to be added in the 
+ * model as child nodes.  This is done through a loop of introspection that
+ * was copied and adapted from the weka.gui.PropertySheetPanel class.  
+ * Second, this class is also responsible for discovering all possible 
+ * combinations of GenericObject parameters that are stored in its child 
+ * nodes.  This is accomplished by first discovering all of the child node 
+ * parameters in the getValues method and then finding all combinations of 
+ * these values with the combinAllValues method.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class GenericObjectNode
+  extends DefaultMutableTreeNode
+  implements PropertyChangeListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 688096727663132485L;
+  
+  //The following 8 arrays hold the accumulated information about the
+  //Classifier parameters that we discover through introspection.  This
+  //is very similar to the approach within PropertySheetPanel.
+
+  /** Holds properties of the target */
+  private PropertyDescriptor m_Properties[];
+  
+  /** this tracks which indexes of the m_Properties */
+  private Vector m_UsedPropertyIndexes;
+  
+  /** Holds the methods of the target */
+  private MethodDescriptor m_Methods[];
+  
+  /** Holds property editors of the object */
+  private PropertyEditor m_Editors[];
+  
+  /** Holds current object values for each property */
+  private Object m_Values[];
+  
+  /** The labels for each property */
+  private String m_Names[];
+  
+  /** The tool tip text for each property */
+  private String m_TipTexts[];
+  
+  /** StringBuffer containing help text for the object being edited */
+  private StringBuffer m_HelpText;
+  
+  /** the GenericObjectEditor that was supplied for this node */
+  private GenericObjectEditor m_GenericObjectEditor;
+  
+  /** this Vector stores all of the possible combinations of parameters 
+   * that it obtains from its child nodes.  These combinations are 
+   * created by the recursive combineAllValues method*/
+  private Vector m_WorkingSetCombinations;
+  
+  /** the tip text for our node editor to display */
+  private String m_ToolTipText;
+  
+  /** a reference to the tree model is necessary to be able to add and 
+   * remove nodes in the tree */
+  private DefaultTreeModel m_TreeModel;
+  
+  /** this is a reference to the Tree object that this node is 
+   * contained within. Its required for this node to be able to 
+   * add/remove nodes from the JTree*/
+  private JTree m_Tree;
+  
+  /** This is a reference to the parent panel of the JTree so that we can 
+   * supply it as the required argument when supplying warning JDialog 
+   * messages*/
+  private final AddModelsPanel m_ParentPanel;
+  
+  /** 
+   * The constructor initialiazes the member variables of this node, 
+   * Note that the "value" of this generic object is stored as the treeNode
+   * user object. 
+   * 
+   * @param panel	the reference to the parent panel for calls to JDialog
+   * @param value the value stored at this tree node
+   * @param genericObjectEditor the GenericObjectEditor for this object
+   * @param toolTipText the tipText to be displayed for this object
+   */
+  public GenericObjectNode(AddModelsPanel panel, Object value,
+      GenericObjectEditor genericObjectEditor, String toolTipText) {
+    
+    super(value);
+    //setObject(value);
+    m_ParentPanel = panel;
+    this.m_GenericObjectEditor = genericObjectEditor;
+    this.m_ToolTipText = toolTipText;
+    
+  }
+  
+  /** 
+   * It seems kind of dumb that the reference to the tree model is passed in
+   * seperately - but know that this is actually necessary.  There is a really 
+   * weird chicken before the egg problem.  You cannot create a TreeModel without
+   * giving it its root node.  However, the nodes in your tree can't update the 
+   * structure of the tree unless they have a reference to the TreeModel.  So in 
+   * the end this was the only compromise that I could get to work well
+   * 
+   * @param tree	the tree to use
+   */
+  public void setTree(JTree tree) {
+    this.m_Tree = tree;
+    this.m_TreeModel = (DefaultTreeModel) m_Tree.getModel();
+    
+  }
+  
+  /**
+   * returns the current tree
+   * 
+   * @return		the current tree
+   */
+  public JTree getTree() {
+    return m_Tree;
+  }
+  
+  /**
+   * A getter for the GenericObjectEditor for this node
+   * 
+   * @return		the editor
+   */
+  public GenericObjectEditor getEditor() {
+    return m_GenericObjectEditor;
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public StringBuffer getHelpText() {
+    return m_HelpText;
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public String getToolTipText() {
+    return m_ToolTipText;
+  }
+  
+  /**
+   * getter for this node's object
+   * 
+   * @return	the node's object
+   */
+  public Object getObject() {
+    return getUserObject();
+  }
+  
+  /**
+   * setter for this nodes object
+   * 
+   * @param newValue	sets the new object
+   */
+  public void setObject(Object newValue) {
+    setUserObject(newValue);
+  }
+  
+  /**
+   * this is a simple filter for the setUserObject method.  We basically
+   * don't want null values to be passed in.
+   * 
+   * @param o		the object to set
+   */
+  public void setUserObject(Object o) {
+    if (o != null)
+      super.setUserObject(o);
+  }
+  
+  /**
+   * getter for the parent panel
+   * 
+   * @return		the parent panel
+   */
+  public JPanel getParentPanel() {
+    return m_ParentPanel;
+  }
+  
+  /**
+   * returns always null
+   * 
+   * @return		always null
+   */
+  public String toString() {
+    return null;
+    //return getClass().getName() + "[" + getUserObject().toString() + "]";
+  }
+  
+  /**
+   * This implements the PropertyChangeListener for this node that gets 
+   * registered with its Editor.  All we really have to do is change the 
+   * Object value stored internally at this node when its editor says the 
+   * value changed.
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    
+    Object newValue = ((GenericObjectEditor) evt.getSource()).getValue();
+    
+    if (!newValue.getClass().equals(getObject().getClass())) {
+      
+      if (m_TreeModel.getRoot() == this) {
+	
+	try {
+	  m_ParentPanel.buildClassifierTree((Classifier) newValue
+	      .getClass().newInstance());
+	} catch (InstantiationException e) {
+	  e.printStackTrace();
+	} catch (IllegalAccessException e) {
+	  e.printStackTrace();
+	}
+	m_ParentPanel.update(m_ParentPanel.getGraphics());
+	m_ParentPanel.repaint();
+	
+	//System.out.println("Changed root");
+	
+      } else {
+	setObject(newValue);
+	updateTree();
+	updateTree();
+	m_TreeModel.nodeChanged(this);
+      }
+    }
+  }
+  
+  /**
+   * This method uses introspection to programatically discover all of 
+   * the parameters for this generic object.  For each one of them it
+   * uses the TreeModel reference to create a new subtree to represent
+   * that parameter and its value ranges.  Note that all of these nodes
+   * are PropertyNodes which themselves hold the logic of figuring out
+   * what type of parameter it is they are representing and thus what 
+   * type of subtree to build.
+   * <p/> 
+   * We need to be careful because this was molded from the code inside of 
+   * the PropertySheetPanel class.  Which means that we are wide open
+   * to copy/paste problems.  In the future, when that code changes to 
+   * adapt to other changes in Weka then this could easily become broken.
+   */
+  public void updateTree() {
+    
+    int childCount = m_TreeModel.getChildCount(this);
+    
+    for (int i = 0; i < childCount; i++) {
+      DefaultMutableTreeNode child = (DefaultMutableTreeNode) m_TreeModel.getChild(this, 0);
+      
+      m_TreeModel.removeNodeFromParent(child);
+    }
+    
+    //removeAllChildren();
+    
+    Object classifier = this.getUserObject();
+    
+    try {
+      BeanInfo bi = Introspector.getBeanInfo(classifier.getClass());
+      m_Properties = bi.getPropertyDescriptors();
+      m_Methods = bi.getMethodDescriptors();
+    } catch (IntrospectionException ex) {
+      System.err.println("PropertySheet: Couldn't introspect");
+      return;
+    }
+    
+    //		 Look for a globalInfo method that returns a string
+    // describing the target
+    for (int i = 0; i < m_Methods.length; i++) {
+      String name = m_Methods[i].getDisplayName();
+      Method meth = m_Methods[i].getMethod();
+      if (name.equals("globalInfo")) {
+	if (meth.getReturnType().equals(String.class)) {
+	  try {
+	    Object args[] = {};
+	    String globalInfo = (String) (meth.invoke(getObject(),
+		args));
+	    String summary = globalInfo;
+	    int ci = globalInfo.indexOf('.');
+	    if (ci != -1) {
+	      summary = globalInfo.substring(0, ci + 1);
+	    }
+	    final String className = getObject().getClass().getName();
+	    m_HelpText = new StringBuffer("NAME\n");
+	    m_HelpText.append(className).append("\n\n");
+	    m_HelpText.append("SYNOPSIS\n").append(globalInfo).append("\n\n");
+	    
+	  } catch (Exception ex) {
+	    // ignored
+	  }
+	}
+      }
+    }
+    
+    m_UsedPropertyIndexes = new Vector();
+    
+    m_Editors = new PropertyEditor[m_Properties.length];
+    
+    m_Values = new Object[m_Properties.length];
+    m_Names = new String[m_Properties.length];
+    m_TipTexts = new String[m_Properties.length];
+    boolean firstTip = true;
+    
+    for (int i = 0; i < m_Properties.length; i++) {
+      
+      // Don't display hidden or expert properties.
+      if (m_Properties[i].isHidden() || m_Properties[i].isExpert()) {
+	continue;
+      }
+      
+      m_Names[i] = m_Properties[i].getDisplayName();
+      Class type = m_Properties[i].getPropertyType();
+      Method getter = m_Properties[i].getReadMethod();
+      Method setter = m_Properties[i].getWriteMethod();
+      
+      // Only display read/write properties.
+      if (getter == null || setter == null) {
+	continue;
+      }
+      
+      try {
+	Object args[] = {};
+	Object value = getter.invoke(classifier, args);
+	m_Values[i] = value;
+	
+	PropertyEditor editor = null;
+	Class pec = m_Properties[i].getPropertyEditorClass();
+	if (pec != null) {
+	  try {
+	    editor = (PropertyEditor) pec.newInstance();
+	  } catch (Exception ex) {
+	    // Drop through.
+	  }
+	}
+	if (editor == null) {
+	  editor = PropertyEditorManager.findEditor(type);
+	}
+	m_Editors[i] = editor;
+	
+	// If we can't edit this component, skip it.
+	if (editor == null) {
+	  continue;
+	}
+	if (editor instanceof GenericObjectEditor) {
+	  ((GenericObjectEditor) editor).setClassType(type);
+	}
+	
+	// Don't try to set null values:
+	if (value == null) {
+	  continue;
+	}
+	
+	editor.setValue(value);
+	
+	// now look for a TipText method for this property
+	String tipName = m_Names[i] + "TipText";
+	for (int j = 0; j < m_Methods.length; j++) {
+	  String mname = m_Methods[j].getDisplayName();
+	  Method meth = m_Methods[j].getMethod();
+	  if (mname.equals(tipName)) {
+	    if (meth.getReturnType().equals(String.class)) {
+	      try {
+		String tempTip = (String) (meth.invoke(
+		    classifier, args));
+		int ci = tempTip.indexOf('.');
+		if (ci < 0) {
+		  m_TipTexts[i] = tempTip;
+		} else {
+		  m_TipTexts[i] = tempTip.substring(0, ci);
+		}
+		
+		if (m_HelpText != null) {
+		  if (firstTip) {
+		    m_HelpText.append("OPTIONS\n");
+		    firstTip = false;
+		  }
+		  m_HelpText.append(m_Names[i]).append(" -- ");
+		  m_HelpText.append(tempTip).append("\n\n");
+		  
+		}
+		
+	      } catch (Exception ex) {
+		
+	      }
+	      break;
+	    }
+	  }
+	}
+	
+	//Here we update the usedPropertyIndexes variable so that
+	//later on we will know which ones to look at.
+	m_UsedPropertyIndexes.add(new Integer(i));
+	
+	int currentCount = m_TreeModel.getChildCount(this);
+	
+	//Now we make a child node and add it to the tree underneath 
+	//this one
+	PropertyNode newNode = new PropertyNode(m_Tree, m_ParentPanel,
+	    m_Names[i], m_TipTexts[i], m_Values[i], m_Editors[i]);
+	
+	m_TreeModel.insertNodeInto(newNode, this, currentCount);
+	
+      } catch (InvocationTargetException ex) {
+	System.err.println("Skipping property " + m_Names[i]
+	                                                  + " ; exception on target: " + ex.getTargetException());
+	ex.getTargetException().printStackTrace();
+	continue;
+      } catch (Exception ex) {
+	System.err.println("Skipping property " + m_Names[i]
+	                                                  + " ; exception: " + ex);
+	ex.printStackTrace();
+	continue;
+      }
+      
+    }
+    
+    //Finally we tell the TreeModel to update itself so the changes
+    //will be visible
+    m_TreeModel.nodeStructureChanged(this);
+  }
+  
+  /**
+   * This method iterates over all of the child nodes of this 
+   * GenericObjectNode and requests the verious sets of values that the
+   * user has presumably specified.  Once these sets of values are 
+   * 
+   * @return a Vector consisting of all parameter combinations
+   */
+  public Vector getValues() {
+    
+    Vector valuesVector = new Vector();
+    
+    int childCount = m_TreeModel.getChildCount(this);
+    
+    //poll all child nodes for their values.
+    for (int i = 0; i < childCount; i++) {
+      
+      PropertyNode currentChild = (PropertyNode) m_TreeModel.getChild(
+	  this, i);
+      
+      Vector v = currentChild.getAllValues();
+      valuesVector.add(v);
+      
+    }
+    
+    //Need to initialize the working set of paramter combinations
+    m_WorkingSetCombinations = new Vector();
+    
+    //obtain all combinations of the paremeters
+    combineAllValues(new Vector(), valuesVector);
+    
+    /*
+     //nice for initially debugging this - and there was a WHOLE lot of
+      //that going on till this crazy idea finally worked.
+       for (int i = 0; i < m_WorkingSetCombinations.size(); i++) {
+       
+       System.out.print("Combo "+i+": ");
+       
+       Vector current = (Vector)m_WorkingSetCombinations.get(i);
+       for (int j = 0; j < current.size(); j++) {
+       
+       System.out.print(current.get(j)+"\t");
+       
+       }
+       
+       System.out.print("\n");
+       }
+       */
+    
+    //Now the real work begins.  Here we need to translate all of the values
+    //received from the editors back into the actual class types that the 
+    //Weka classifiers will understand.  for example, String values for 
+    //enumerated values need to be turned back into the SelectedTag objects 
+    //that classifiers understand. 
+    //This vector will hold all of the actual generic objects that are being 
+    //instantiated
+    Vector newGenericObjects = new Vector();
+    
+    for (int i = 0; i < m_WorkingSetCombinations.size(); i++) {
+      
+      Vector current = (Vector) m_WorkingSetCombinations.get(i);
+      
+      //create a new copy of this class.  We will use this copy to test whether
+      //the current set of parameters is valid.
+      Object o = this.getUserObject();
+      Class c = o.getClass();
+      Object copy = null;
+      
+      try {
+	copy = c.newInstance();
+      } catch (InstantiationException e) {
+	e.printStackTrace();
+      } catch (IllegalAccessException e) {
+	e.printStackTrace();
+      }
+      
+      for (int j = 0; j < current.size(); j++) {
+	
+	Object[] args = new Object[1];
+	
+	int index = ((Integer) m_UsedPropertyIndexes.get(j)).intValue();
+	
+	PropertyDescriptor property = (PropertyDescriptor) m_Properties[index];
+	Method setter = property.getWriteMethod();
+	Class[] params = setter.getParameterTypes();
+	
+	Object currentVal = current.get(j);
+	
+	//System.out.println(currentVal.getClass().toString());
+	
+	//we gotta turn strings back into booleans 
+	if (params.length == 1
+	    && params[0].toString().equals("boolean")
+	    && currentVal.getClass().toString().equals(
+	    "class java.lang.String")) {
+	  
+	  currentVal = new Boolean((String) current.get(j));
+	}
+	
+	//we gotta turn strings back into "Tags"
+	if (params.length == 1
+	    && params[0].toString().equals(
+	    "class weka.core.SelectedTag")
+	    && currentVal.getClass().toString().equals(
+	    "class java.lang.String")) {
+	  
+	  String tagString = (String) current.get(j);
+	  
+	  m_Editors[index].setAsText(tagString);
+	  currentVal = m_Editors[index].getValue();
+	  
+	}
+	
+	args[0] = currentVal;
+	
+	/*
+	 System.out.print("setterName: "+setter.getName()+
+	 " editor class: "+m_Editors[index].getClass()+
+	 " params: ");
+	 
+	 
+	 for (int k = 0; k < params.length; k++) 
+	 System.out.print(params[k].toString()+" ");
+	 
+	 System.out.println(" value class: "+args[0].getClass().toString());
+	 */
+	
+	try {
+	  
+	  //we tell the setter for the current parameter to update the copy
+	  //with the current parameter value
+	  setter.invoke(copy, args);
+	  
+	} catch (InvocationTargetException ex) {
+	  if (ex.getTargetException() instanceof PropertyVetoException) {
+	    String message = "WARNING: Vetoed; reason is: "
+	      + ex.getTargetException().getMessage();
+	    System.err.println(message);
+	    
+	    Component jf;
+	    jf = m_ParentPanel.getRootPane();
+	    JOptionPane.showMessageDialog(jf, message, "error",
+		JOptionPane.WARNING_MESSAGE);
+	    if (jf instanceof JFrame)
+	      ((JFrame) jf).dispose();
+	    
+	  } else {
+	    System.err.println(ex.getTargetException().getClass()
+		.getName()
+		+ " while updating "
+		+ property.getName()
+		+ ": " + ex.getTargetException().getMessage());
+	    Component jf;
+	    jf = m_ParentPanel.getRootPane();
+	    JOptionPane.showMessageDialog(jf, ex
+		.getTargetException().getClass().getName()
+		+ " while updating "
+		+ property.getName()
+		+ ":\n" + ex.getTargetException().getMessage(),
+		"error", JOptionPane.WARNING_MESSAGE);
+	    if (jf instanceof JFrame)
+	      ((JFrame) jf).dispose();
+	    
+	  }
+	  
+	} catch (IllegalArgumentException e) {
+	  e.printStackTrace();
+	} catch (IllegalAccessException e) {
+	  e.printStackTrace();
+	}
+	
+      }
+      
+      //At this point we have set all the parameters for this GenericObject
+      //with a single combination that was generated from the 
+      //m_WorkingSetCombinations Vector and can add it to the collection that
+      //will be returned
+      newGenericObjects.add(copy);
+      
+    }
+    
+    return newGenericObjects;
+  }
+  
+  /** This method is responsible for returning all possible values through 
+   * a recursive loop.
+   * 
+   * When the recursion terminates that means that there are no more parameter
+   * sets to branch out through so all we have to do is save the current Vector
+   * in the working set of combinations.  Otherwise we iterate through all 
+   * possible values left in the next set of parameter values and recursively
+   * call this function.
+   * 
+   * @param previouslySelected stores the values chosen in this branch of the recursion 
+   * @param remainingValues the sets of values left 
+   */
+  public void combineAllValues(Vector previouslySelected,
+      Vector remainingValues) {
+    
+    if (remainingValues.isEmpty()) {
+      m_WorkingSetCombinations.add(previouslySelected);
+      return;
+    }
+    
+    Vector currentSet = new Vector((Vector) remainingValues.get(0));
+    Vector tmpRemaining = new Vector(remainingValues);
+    tmpRemaining.removeElementAt(0);
+    
+    for (int i = 0; i < currentSet.size(); i++) {
+      Vector tmpPreviouslySelected = new Vector(previouslySelected);
+      tmpPreviouslySelected.add(currentSet.get(i));
+      combineAllValues(tmpPreviouslySelected, tmpRemaining);
+    }
+    
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNodeEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNodeEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNodeEditor.java	(revision 29)
@@ -0,0 +1,286 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GenericObjectNodeEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.gui.GenericObjectEditor;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.UIManager;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+/** 
+ * This class creates a simple user interface for selecting the Generic
+ * Object Type. It consists of a button that brings up a popupMenu 
+ * allowing the user to select the type of Generic Object to be edited.  
+ * It also contains a JLabel that just prints out the type. Finally, we
+ * also added the button from the PropertySheetPanel that brings up more
+ * info about the Object
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class GenericObjectNodeEditor
+  extends JPanel
+  implements PropertyChangeListener, ActionListener, PopupMenuListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -2382339640932830323L;
+
+  /**  a reference to the GenericObjectEditor for this node*/
+  private GenericObjectEditor m_GenericObjectEditor;
+  
+  /** the label that will display the node object type */
+  private JLabel m_Label;
+  
+  /** the button that will create the JPopupMenu to choose the object type */
+  private JButton m_ChooseClassButton;
+  
+  /** Button to pop up the full help text in a separate frame */
+  private JButton m_MoreInfoButton;
+  
+  /** Help frame */
+  private JFrame m_HelpFrame;
+  
+  /** a propertyChangeSupportListener to inform the TreeNodeEditor
+   * when we stop editing - needs to be done manually for that popup*/
+  private PropertyChangeSupport m_propertyChangeSupport = new PropertyChangeSupport(
+      this);
+  
+  /** the colors */
+  private Color textForeground, textBackground;
+  
+  /** A reference to the node */
+  private GenericObjectNode m_Node;
+  
+  /** the popup menu to show the node parameter GUI */
+  private JPopupMenu m_popup;
+  
+  /**
+   * The constructor builds initializes the various member values from the
+   * node
+   * 
+   * @param node	the node to base the node editor on
+   */
+  public GenericObjectNodeEditor(GenericObjectNode node) {
+    m_GenericObjectEditor = node.getEditor();
+    m_GenericObjectEditor.setValue(node.getObject());
+    m_GenericObjectEditor.addPropertyChangeListener(node);
+    m_GenericObjectEditor.addPropertyChangeListener(this);
+    
+    m_Node = node;
+    updateEditor();
+  }
+  
+  /**
+   * This method updates the editor by creating the two buttons and the
+   * JPanel that constiture the generic object editor GUI.
+   */
+  public void updateEditor() {
+    
+    textForeground = UIManager.getColor("Tree.textForeground");
+    textBackground = UIManager.getColor("Tree.textBackground");
+    
+    setForeground(textForeground);
+    setBackground(textBackground);
+    
+    m_ChooseClassButton = new JButton("Choose");
+    m_ChooseClassButton.addActionListener(this);
+    m_ChooseClassButton.setForeground(textForeground);
+    m_ChooseClassButton.setBackground(textBackground);
+    
+    Font fontValue;
+    fontValue = UIManager.getFont("Tree.font");
+    
+    String labelString = null;
+    
+    //This bit just prunes of the weka.classifier part of the lass name
+    //for feng shui purposes
+    
+    boolean replaceName = false;
+    String className = m_Node.getObject().getClass().toString();
+    if (className.length() > 23) {
+      if (className.substring(0, 23).equals("class weka.classifiers.")) {
+	replaceName = true;
+      }
+    }
+    
+    if (replaceName) {
+      labelString = new String(m_Node.getObject().getClass().toString()
+	  .replaceAll("class weka.classifiers.", ""));
+    } else {
+      labelString = new String(m_Node.getObject().getClass().toString());
+    }
+    
+    m_Label = new JLabel(labelString);
+    
+    if (fontValue != null) {
+      m_Label.setFont(fontValue);
+    }
+    m_Label.setForeground(textForeground);
+    m_Label.setBackground(textBackground);
+    
+    m_MoreInfoButton = new JButton("More Info");
+    
+    // anonymous action listener shows a JTree popup and allows the user
+    // to choose the class they want
+    m_MoreInfoButton.addActionListener(this);
+    m_MoreInfoButton.setForeground(textForeground);
+    m_MoreInfoButton.setBackground(textBackground);
+    
+    add(m_ChooseClassButton);
+    add(m_Label);
+    add(m_MoreInfoButton);
+  }
+  
+  /**
+   * Sets the prop change listener
+   * 
+   * @param al		the listener
+   */
+  public void setPropertyChangeListener(PropertyChangeListener al) {
+    if (m_GenericObjectEditor != null)
+      m_GenericObjectEditor.addPropertyChangeListener(al);
+    m_propertyChangeSupport.addPropertyChangeListener(al);
+  }
+  
+  /**
+   * This method implements the property change listener for this node. 
+   * When this happens, we just need to update the node value
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    updateEditor();
+  }
+  
+  /**
+   * This implements the action listener for the buttons in this editor,  If
+   * they hit the choose class button then the popup menu for choosing the 
+   * generic object ype is created.  Otherwise if its the more info button 
+   * then the information frame is popped up
+   * 
+   * @param e		the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    if (e.getSource() == m_ChooseClassButton) {
+      
+      m_popup = m_GenericObjectEditor.getChooseClassPopupMenu();
+      
+      m_popup.addPopupMenuListener(this);
+      m_popup.show(m_ChooseClassButton, m_ChooseClassButton.getX(),
+	  m_ChooseClassButton.getY());
+      
+      m_popup.pack();
+      
+    } else if (e.getSource() == m_MoreInfoButton) {
+      
+      openHelpFrame();
+      
+    }
+  }
+  
+  /**
+   * This method was copied and modified from the ProeprtySheetPanel
+   * class.
+   */
+  protected void openHelpFrame() {
+    
+    JTextArea ta = new JTextArea();
+    ta.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ta.setLineWrap(true);
+    ta.setWrapStyleWord(true);
+    //ta.setBackground(getBackground());
+    ta.setEditable(false);
+    ta.setText(m_Node.getHelpText().toString());
+    ta.setCaretPosition(0);
+    final JFrame jf = new JFrame("Information");
+    jf.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+	jf.dispose();
+	if (m_HelpFrame == jf) {
+	  m_MoreInfoButton.setEnabled(true);
+	}
+      }
+    });
+    jf.getContentPane().setLayout(new BorderLayout());
+    jf.getContentPane().add(new JScrollPane(ta), BorderLayout.CENTER);
+    jf.pack();
+    jf.setSize(400, 350);
+    jf.setLocation(this.getTopLevelAncestor().getLocationOnScreen().x
+	+ this.getTopLevelAncestor().getSize().width, this
+	.getTopLevelAncestor().getLocationOnScreen().y);
+    jf.setVisible(true);
+    m_HelpFrame = jf;
+  }
+  
+  /**
+   * in the case that the popup menu is cancelled we need to manually 
+   * inform the ModelTreeNodeEditor that we are done editing. If we
+   * don't then we get stuck in a strange limbo state where the node
+   * represented by this editor is completely frozen and the user 
+   * can't interact with it.
+   * 
+   * @param e		the event
+   */
+  public void popupMenuCanceled(PopupMenuEvent e) {
+    m_propertyChangeSupport.firePropertyChange(null, null, null);
+  }
+  
+  /**
+   * Necessary for popup listener interface.  Does nothing.
+   * 
+   * @param e		the event
+   */
+  public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+    //do nothing
+  }
+  
+  /**
+   * Necessary for popup listener interface.  Does nothing.
+   * 
+   * @param e		the event
+   */
+  public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+    //do nothing
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/InvalidInputException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/InvalidInputException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/InvalidInputException.java	(revision 29)
@@ -0,0 +1,48 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InvalidInputException.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+/**
+ * A custom Exception that is thrown when a user specifies an invalid
+ * set of parameters to the LibraryEditor Tree GUI.  This covers things
+ * like specifying a range of numerical values with the min being > than
+ * the max, etc...
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class InvalidInputException
+  extends Exception {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 9192136737177003882L;
+
+  /**
+   * initializes the exception
+   * 
+   * @param s		the message of the exception
+   */
+  public InvalidInputException(String s) {
+    super(s);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeEditor.java	(revision 29)
@@ -0,0 +1,235 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelTreeNodeEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.gui.EnsembleLibraryEditor;
+import weka.gui.GenericObjectEditor;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyEditor;
+import java.util.EventObject;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeCellEditor;
+import javax.swing.tree.TreePath;
+
+/**
+ * This class is in charge of dynamically creating editor GUI objects on
+ * demand for the main JTree class that will display our Classifier tree
+ * model of parameters.  This is in fact the CellEditor class that is
+ * registered with our tree.
+ * <p/>
+ * Basically it delegates much of the work to the various NodeEditor 
+ * classes found in this package. All it really has to do is detect 
+ * what of node it is and then instantiate an editor of the appropriate
+ * type.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class ModelTreeNodeEditor
+  extends AbstractCellEditor
+  implements TreeCellEditor, ItemListener, PropertyChangeListener, ActionListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 7057924814405386358L;
+  
+  /**
+   * This is the underlying tree holding all our parameter nodes in the GUI.
+   */
+  private JTree m_Tree;
+  
+  /**
+   * default Constructor
+   * 
+   * @param tree	the tree to use
+   */
+  public ModelTreeNodeEditor(JTree tree) {
+    m_Tree = tree;
+  }
+  
+  /**
+   * I'm supposed to implemnent this as part of the TreeCellEDitor
+   * interface. however, it's not necessary for this class so it
+   * returns null.
+   * 
+   * @return		always null
+   */
+  public Object getCellEditorValue() {
+    return null;
+  }
+  
+  /**
+   * This tells the JTree whether or not to let nodes in the tree be
+   * edited.  Basically all this does is return true for all nodes that
+   * aren't PropertyNodes - which don't have any interactive widgets
+   * 
+   * @param event	the event
+   * @return		true if editable
+   */
+  public boolean isCellEditable(EventObject event) {
+    boolean returnValue = false;
+    if (event instanceof MouseEvent) {
+      MouseEvent mouseEvent = (MouseEvent) event;
+      TreePath path = m_Tree.getPathForLocation(mouseEvent.getX(),
+	  mouseEvent.getY());
+      if (path != null) {
+	Object node = path.getLastPathComponent();
+	if ((node != null) && (node instanceof DefaultMutableTreeNode)) {
+	  DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
+	  returnValue = (!(treeNode instanceof PropertyNode));
+	}
+      }
+    }
+    return returnValue;
+  }
+  
+  /**
+   * This method uses the ModelTreeNodeRenderer class to get the individual
+   * editors and then registers this classes editing event listeners with 
+   * them
+   * 
+   * @param tree	the associated tree
+   * @param value	the value
+   * @param selected	true if item is selected
+   * @param expanded	true if it is expanded
+   * @param leaf	true if it is a leaf
+   * @param row		the row in the tree
+   * @return		the rendering component
+   */
+  public Component getTreeCellEditorComponent(JTree tree, Object value,
+      boolean selected, boolean expanded, boolean leaf, int row) {
+    
+    //make the renderer to all the "painting" work, then add all of the
+    //necessary action listeners to the objects.
+    //Component customEditor = m_Renderer.getTreeCellRendererComponent(tree,
+    //	value,selected,expanded,leaf,row,false);
+    
+    JComponent customRenderer = null;
+    
+    if (value instanceof GenericObjectNode) {
+      GenericObjectNode node = (GenericObjectNode) value;
+      customRenderer = new GenericObjectNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      ((GenericObjectNodeEditor) customRenderer)
+      .setPropertyChangeListener(this);
+      
+    } else if (value instanceof PropertyNode) {
+      PropertyNode node = (PropertyNode) value;
+      JLabel label = new JLabel(node.getName());
+      label.setToolTipText(node.getToolTipText());
+      customRenderer = label;
+      customRenderer.setToolTipText(node.getToolTipText());
+      //do nothing, these nodes aren't editable
+      
+    } else if (value instanceof CheckBoxNode) {
+      CheckBoxNode node = (CheckBoxNode) value;
+      customRenderer = new CheckBoxNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      ((CheckBoxNodeEditor) customRenderer).setItemListener(this);
+      
+    } else if (value instanceof NumberNode) {
+      NumberNode node = (NumberNode) value;
+      customRenderer = new NumberNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      ((NumberNodeEditor) customRenderer).setPropertyChangeListener(this);
+      ((NumberNodeEditor) customRenderer).setItemListener(this);
+      ((NumberNodeEditor) customRenderer).setActionListener(this);
+      
+    } else if (value instanceof DefaultNode) {
+      
+      DefaultNode node = (DefaultNode) value;
+      PropertyEditor nodeEditor = node.getEditor();
+      customRenderer = (JComponent) EnsembleLibraryEditor
+      .getDefaultRenderer(nodeEditor);
+      ((JComponent) customRenderer).setToolTipText(node.getToolTipText());
+      nodeEditor.addPropertyChangeListener(this);
+      
+    }
+    
+    return customRenderer;
+  }
+  
+  /**
+   * The item Listener that gets registered with all node editors that
+   * have a widget that had itemStateChangeg events.  It just fires the
+   * editing stopped event.
+   * 
+   * @param e		the event
+   */
+  public void itemStateChanged(ItemEvent e) {
+    if (stopCellEditing()) {
+      fireEditingStopped();
+    }
+  }
+  
+  /**
+   * The prtopertyListener that gets registered with all node editors that
+   * have a widget that had propertyStateChangeg events.  It just fires the
+   * editing stopped event.
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    
+    if (evt.getSource() instanceof GenericObjectEditor
+	|| evt.getSource() instanceof GenericObjectNodeEditor) {
+      
+      if (stopCellEditing()) {
+	fireEditingStopped();
+      }
+      
+    } else if (evt.getPropertyName() != null) {
+      if (evt.getPropertyName().equals("value")) {
+	
+	if (stopCellEditing()) {
+	  fireEditingStopped();
+	}
+      }
+    }
+  }
+  
+  /**
+   * The item Listener that gets registered with all node editors that
+   * have a widget that had actionPerformed events.  It just fires the
+   * editing stopped event.
+   * 
+   * @param e		the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    if (stopCellEditing()) {
+      fireEditingStopped();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeRenderer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeRenderer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/ModelTreeNodeRenderer.java	(revision 29)
@@ -0,0 +1,112 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ModelTreeNodeRenderer.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.gui.EnsembleLibraryEditor;
+
+import java.awt.Component;
+import java.beans.PropertyEditor;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.tree.TreeCellRenderer;
+
+/**
+ * This class renders a tree nodes. It determines which type of node the m_Tree
+ * is trying to render and then returns the approapriate gui widget. It is
+ * basically the same thing as the ModelTreeNodeEditor class except it does not
+ * have to.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class ModelTreeNodeRenderer
+  implements TreeCellRenderer {
+  
+  /**
+   * empty Constructor
+   */
+  public ModelTreeNodeRenderer() {
+    super();
+  }
+  
+  /**
+   * This is the method of this class that is responsible for figuring out how
+   * to display each of the tree nodes. All it does is figure out the type of
+   * the node and then return a new instance of the apropriate editor type for
+   * the node.
+   * 
+   * @param tree	the associated tree
+   * @param value	the value
+   * @param selected	true if item is selected
+   * @param expanded	true if node is expanded
+   * @param leaf	true if node is leaf
+   * @param row		the row in the tree
+   * @param hasFocus	true if item has the focus
+   * @return		the rendering component
+   */
+  public Component getTreeCellRendererComponent(JTree tree, Object value,
+      boolean selected, boolean expanded, boolean leaf, int row,
+      boolean hasFocus) {
+    
+    JComponent customRenderer = null;
+    
+    if (value instanceof GenericObjectNode) {
+      
+      GenericObjectNode node = (GenericObjectNode) value;
+      customRenderer = new GenericObjectNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      
+    } else if (value instanceof PropertyNode) {
+      PropertyNode node = (PropertyNode) value;
+      JLabel label = new JLabel(node.getName());
+      label.setToolTipText(node.getToolTipText());
+      customRenderer = label;
+      customRenderer.setToolTipText(node.getToolTipText());
+      
+    } else if (value instanceof CheckBoxNode) {
+      
+      CheckBoxNode node = (CheckBoxNode) value;
+      customRenderer = new CheckBoxNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      
+    } else if (value instanceof NumberNode) {
+      
+      NumberNode node = (NumberNode) value;
+      customRenderer = new NumberNodeEditor(node);
+      customRenderer.setToolTipText(node.getToolTipText());
+      
+    } else if (value instanceof DefaultNode) {
+      
+      DefaultNode node = (DefaultNode) value;
+      PropertyEditor nodeEditor = node.getEditor();
+      customRenderer = (JComponent) EnsembleLibraryEditor
+      .getDefaultRenderer(nodeEditor);
+      customRenderer.setToolTipText(node.getToolTipText());
+      
+    }
+    
+    return customRenderer;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberClassNotFoundException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberClassNotFoundException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberClassNotFoundException.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumberClassNotFoundException.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+/**
+ * This is a custom exception that gets thrown when the NumberNode class
+ * or its editor cannot determine the correct child class of 
+ * java.lang.Number for a numeric parameter.
+ *  
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class NumberClassNotFoundException
+  extends Exception {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -4896867049872120453L;
+
+  /**
+   * initializes the exception
+   * 
+   * @param s		the message of the exception
+   */
+  public NumberClassNotFoundException(String s) {
+    super(s);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNode.java	(revision 29)
@@ -0,0 +1,486 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumberNode.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import java.math.BigDecimal;
+import java.text.NumberFormat;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+/**
+ * This subclass is responsible for allowing users to specify either a minimum,
+ * maximum, or iterator value for Integer attributes. It stores a value that is
+ * of type java.lang.Number to accomodate the many different number types used
+ * by Weka classifiers.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class NumberNode
+  extends DefaultMutableTreeNode {
+  
+  /** for serialization */
+  private static final long serialVersionUID = -2505599954089243851L;
+
+  /** the enumerated value indicating a node is not an iterator */
+  public static final int NOT_ITERATOR = 0;
+  
+  /** the enumerated value indicating a node is a *= iterator */
+  public static final int TIMES_EQUAL = 1;
+  
+  /** the enumerated value indicating a node is a += iterator */
+  public static final int PLUS_EQUAL = 2;
+  
+  /** the name of the node to be displayed */
+  private String m_Name;
+  
+  /** the iterator type, NOT_ITERATOR, TIMES_EQUAL, or PLUS_EQUAL */
+  private int m_IteratorType;
+  
+  /** this stores whether or not this node should have a checkbox */
+  private boolean m_Checkable;
+  
+  /** this stores the node's selected state */
+  private boolean m_Selected;
+  
+  /** the node's tipText */
+  private String m_ToolTipText;
+  
+  /**
+   * This method rounds a double to the number of decimal places defined by
+   * scale
+   * 
+   * @param a		the value to round
+   * @return		the rounded value
+   */
+  public static double roundDouble(double a) {
+    return new BigDecimal("" + a).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
+  }
+  
+  /**
+   * This method rounds a float to the number of decimal places defined by
+   * scale
+   * 
+   * @param a		the value to round
+   * @return		the rounded value
+   */
+  public static float roundFloat(float a) {
+    return new BigDecimal("" + a).setScale(scale, BigDecimal.ROUND_HALF_UP).floatValue();
+  }
+  
+  /**
+   * this defines the number of decimal places we care about, we arbitrarily
+   * chose 7 thinking that anything beyond this is overkill
+   */
+  public static final int scale = 7;
+  
+  /**
+   * This is the maximum floating point value that we care about when testing
+   * for equality.
+   */
+  public static final double epsilon = 0.000001;
+  
+  /**
+   * The constructor simply initializes all of the member variables
+   * 
+   * @param text		the name
+   * @param value		the actual value
+   * @param iteratorType	the iterator type
+   * @param checkable		true if it's checkable
+   * @param toolTipText		the tooltip to use
+   */
+  public NumberNode(String text, Number value, int iteratorType,
+      boolean checkable, String toolTipText) {
+    
+    this.m_Name = text;
+    setValue(value);
+    this.m_IteratorType = iteratorType;
+    this.m_Checkable = checkable;
+    this.m_Selected = false;
+    this.m_ToolTipText = toolTipText;
+  }
+  
+  /**
+   * getter for the node selected state
+   * 
+   * @return whether or not this node is selected
+   */
+  public boolean getSelected() {
+    return m_Selected;
+  }
+  
+  /**
+   * setter for the node selected state
+   * 
+   * @param newValue
+   *            the new selected state
+   */
+  public void setSelected(boolean newValue) {
+    m_Selected = newValue;
+  }
+  
+  /**
+   * getter for this node's object
+   * 
+   * @return	the current value
+   */
+  public Number getValue() {
+    return (Number) getUserObject();
+  }
+  
+  /**
+   * setter for this nodes object
+   * 
+   * @param newValue	the new value to use
+   */
+  public void setValue(Number newValue) {
+    userObject = newValue;
+  }
+  
+  /**
+   * getter for this node's iteratorType which will be one of the three
+   * enumerated values
+   * 
+   * @return		the iterator type
+   */
+  public int getIteratorType() {
+    return m_IteratorType;
+  }
+  
+  /**
+   * setter for this nodes iteratorType which should be one of the three
+   * enumerated values
+   * 
+   * @param newValue	the new iterator type to use
+   */
+  public void setIteratorType(int newValue) {
+    m_IteratorType = newValue;
+  }
+  
+  /**
+   * returns whether or not this node can be toggled on and off
+   * 
+   * @return		true if it's checkable
+   */
+  public boolean getCheckable() {
+    return m_Checkable;
+  }
+  
+  /**
+   * returns the text to be displayed for this node
+   * 
+   * @return		the name
+   */
+  public String getText() {
+    return m_Name;
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public String getToolTipText() {
+    return m_ToolTipText;
+  }
+  
+  /**
+   * this is a simple filter for the setUserObject method. We basically don't
+   * want null values to be passed in.
+   * 
+   * @param o		the user object
+   */
+  public void setUserObject(Object o) {
+    if (o != null)
+      super.setUserObject(o);
+  }
+  
+  /**
+   * returns a string representation
+   * 
+   * @return		a string representation
+   */
+  public String toString() {
+    return getClass().getName() + "[" + m_Name + ": "
+    + getUserObject().toString() + "]";
+  }
+  
+  // *************** IMPORTANT!!! ***********************
+  // I could not figure out a graceful way to deal with this!
+  // we have a requirement here to add, multiply, and test for
+  // equality various subclasses of java.lang.number the
+  // following eight methods are extremely redundant and are
+  // a horrible example cutting/pasting. However, this
+  // is what I've ended up with and its very clunky looking.
+  // If anyone knows a better way to do this then please be my
+  // guest by all means!
+  
+  // I really can't beleive there's not some slick way of handling
+  // this stuff built into the language
+  
+  /**
+   * figures out the class of this node's object and returns a new instance of
+   * it initialized with the value of "0".
+   * 
+   * @return					0 as object
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public Number getZeroValue() throws NumberClassNotFoundException {
+    
+    Number value = getValue();
+    Number zero = null;
+    
+    if (value instanceof Double)
+      zero = new Double(0.0);
+    else if (value instanceof Integer)
+      zero = new Integer(0);
+    else if (value instanceof Float)
+      zero = new Float(0.0);
+    else if (value instanceof Long)
+      zero = new Long(0);
+    else {
+      throw new NumberClassNotFoundException(value.getClass()
+	  + " not currently supported.");
+    }
+    
+    return zero;
+  }
+  
+  /**
+   * figures out the class of this node's object and returns a new instance of
+   * it initialized with the value of "1".
+   * 
+   * @return					1 as object
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public Number getOneValue() throws NumberClassNotFoundException {
+    
+    Number value = getValue();
+    Number one = null;
+    
+    if (value instanceof Double)
+      one = new Double(1.0);
+    else if (value instanceof Integer)
+      one = new Integer(1);
+    else if (value instanceof Float)
+      one = new Float(1.0);
+    else if (value instanceof Long)
+      one = new Long(1);
+    else {
+      throw new NumberClassNotFoundException(value.getClass()
+	  + " not currently supported.");
+    }
+    return one;
+  }
+  
+  /**
+   * figures out the class of this node's object and returns a new instance of
+   * it initialized with the value of "2".
+   * 
+   * @return					2 as object
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public Number getTwoValue() throws NumberClassNotFoundException {
+    
+    Number value = getValue();
+    Number two = null;
+    
+    if (value instanceof Double)
+      two = new Double(2.0);
+    else if (value instanceof Integer)
+      two = new Integer(2);
+    else if (value instanceof Float)
+      two = new Float(2.0);
+    else if (value instanceof Long)
+      two = new Long(2);
+    else {
+      throw new NumberClassNotFoundException(value.getClass()
+	  + " not currently supported.");
+    }
+    return two;
+  }
+  
+  /**
+   * adds two objects that are instances of one of the child classes of
+   * java.lang.Number
+   * 
+   * @param a	the first number
+   * @param b	the second number
+   * @return 	the sum: a+b
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public Number addNumbers(Number a, Number b)
+    throws NumberClassNotFoundException {
+    
+    Number sum = null;
+    
+    if (a instanceof Double && b instanceof Double) {
+      sum = new Double(roundDouble(a.doubleValue() + b.doubleValue()));
+      // trimNumber(sum);
+      
+    } else if (a instanceof Integer && b instanceof Integer) {
+      sum = new Integer(a.intValue() + b.intValue());
+    } else if (a instanceof Float && b instanceof Float) {
+      sum = new Float(roundFloat(a.floatValue() + b.floatValue()));
+      
+      // trimNumber(sum);
+      
+    } else if (a instanceof Long && b instanceof Long) {
+      sum = new Long(a.longValue() + b.longValue());
+    } else {
+      throw new NumberClassNotFoundException(a.getClass() + " and "
+	  + b.getClass() + " not currently supported.");
+    }
+    return sum;
+  }
+  
+  /**
+   * multiplies two objects that are instances of one of the child classes of
+   * java.lang.Number
+   * 
+   * @param a	the first number
+   * @param b	the second number
+   * @return 	the product: a*b
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public Number multiplyNumbers(Number a, Number b)
+    throws NumberClassNotFoundException {
+    
+    Number product = null;
+    
+    if (a instanceof Double && b instanceof Double) {
+      product = new Double(roundDouble(a.doubleValue() * b.doubleValue()));
+      
+    } else if (a instanceof Integer && b instanceof Integer) {
+      product = new Integer(a.intValue() * b.intValue());
+    } else if (a instanceof Float && b instanceof Float) {
+      product = new Float(roundFloat(a.floatValue() * b.floatValue()));
+      
+    } else if (a instanceof Long && b instanceof Long) {
+      product = new Long(a.longValue() * b.longValue());
+    } else {
+      throw new NumberClassNotFoundException(a.getClass() + " and "
+	  + b.getClass() + " not currently supported.");
+    }
+    return product;
+  }
+  
+  /**
+   * tests if the first argument is greater than the second among two objects
+   * that are instances of one of the child classes of java.lang.Number
+   * 
+   * @param a	the first number
+   * @param b	the second number
+   * @return 	true if a is less than b
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public boolean lessThan(Number a, Number b)
+    throws NumberClassNotFoundException {
+    
+    boolean greater = false;
+    
+    if (a instanceof Double && b instanceof Double) {
+      if (a.doubleValue() < b.doubleValue())
+	greater = true;
+    } else if (a instanceof Integer && b instanceof Integer) {
+      if (a.intValue() < b.intValue())
+	greater = true;
+    } else if (a instanceof Float && b instanceof Float) {
+      if (a.floatValue() < b.floatValue())
+	greater = true;
+    } else if (a instanceof Long && b instanceof Long) {
+      if (a.longValue() < b.longValue())
+	greater = true;
+    } else {
+      throw new NumberClassNotFoundException(a.getClass() + " and "
+	  + b.getClass() + " not currently supported.");
+    }
+    
+    return greater;
+    
+  }
+  
+  /**
+   * tests for equality among two objects that are instances of one of the
+   * child classes of java.lang.Number
+   * 
+   * @param a	the first number
+   * @param b	the second number
+   * @return 	true if the two values are equal
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public boolean equals(Number a, Number b)
+    throws NumberClassNotFoundException {
+    
+    boolean equals = false;
+    
+    if (a instanceof Double && b instanceof Double) {
+      if (Math.abs(a.doubleValue() - b.doubleValue()) < epsilon)
+	equals = true;
+    } else if (a instanceof Integer && b instanceof Integer) {
+      if (a.intValue() == b.intValue())
+	equals = true;
+    } else if (a instanceof Float && b instanceof Float) {
+      if (Math.abs(a.floatValue() - b.floatValue()) < epsilon)
+	equals = true;
+    } else if (a instanceof Long && b instanceof Long) {
+      if (a.longValue() == b.longValue())
+	equals = true;
+    } else {
+      throw new NumberClassNotFoundException(a.getClass() + " and "
+	  + b.getClass() + " not currently supported.");
+    }
+    
+    return equals;
+  }
+  
+  /**
+   * A helper method to figure out what number format should be used to
+   * display the numbers value in a formatted text box.
+   * 
+   * @return the number format
+   * @throws NumberClassNotFoundException	if number class not supported
+   */
+  public NumberFormat getNumberFormat() throws NumberClassNotFoundException {
+    NumberFormat numberFormat = null;
+    
+    Number value = getValue();
+    
+    if (value instanceof Double) {
+      numberFormat = NumberFormat.getInstance();
+      numberFormat.setMaximumFractionDigits(7);
+    } else if (value instanceof Integer) {
+      numberFormat = NumberFormat.getIntegerInstance();
+    } else if (value instanceof Float) {
+      numberFormat = NumberFormat.getInstance();
+      numberFormat.setMaximumFractionDigits(7);
+    } else if (value instanceof Long) {
+      numberFormat = NumberFormat.getIntegerInstance();
+    } else {
+      throw new NumberClassNotFoundException(value.getClass()
+	  + " not currently supported.");
+    }
+    
+    return numberFormat;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNodeEditor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNodeEditor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/NumberNodeEditor.java	(revision 29)
@@ -0,0 +1,339 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NumberNodeEditor.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.NumberFormat;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+/**
+ * This class is responsible for creating the number editor GUI to allow users
+ * to specify ranges of numerical values.
+ * 
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class NumberNodeEditor
+  extends JPanel
+  implements ActionListener, ItemListener, PropertyChangeListener {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 3486848815982334460L;
+
+  /**
+   * the button that toggles the type of iterator that this node represents
+   */
+  private JButton m_IteratorButton;
+  
+  /** the textField that allows editing of the number value */
+  private JFormattedTextField m_NumberField;
+  
+  /** the checkBox that allows thi snode to be selected/unselected */
+  private JCheckBox m_SelectedCheckBox;
+  
+  /** the label that prints the name of this node */
+  private JLabel m_Label;
+  
+  /** the colors to use */
+  private Color textForeground, textBackground;
+  
+  /** a reference to the node this editor is rendering */
+  private NumberNode m_Node;
+  
+  /**
+   * The constructor builds a user interface based on the information queried
+   * from the node passed in. Specifically the iteratorType, whether the node
+   * is checkable, whether it is currently selected, it's current value, and
+   * the name to print on the label.
+   * 
+   * @param node	the node the editor is for
+   */
+  public NumberNodeEditor(NumberNode node) {
+    
+    this.m_Node = node;
+    String name = node.getText();
+    Number value = node.getValue();
+    int iteratorType = node.getIteratorType();
+    boolean showCheckBox = node.getCheckable();
+    boolean selected = node.getSelected();
+    
+    textForeground = UIManager.getColor("Tree.textForeground");
+    textBackground = UIManager.getColor("Tree.textBackground");
+    
+    setForeground(textForeground);
+    setBackground(textBackground);
+    
+    Font fontValue;
+    fontValue = UIManager.getFont("Tree.font");
+    
+    if (showCheckBox) {
+      m_SelectedCheckBox = new JCheckBox();
+      m_SelectedCheckBox.setSelected(selected);
+      m_SelectedCheckBox.setForeground(textForeground);
+      m_SelectedCheckBox.setBackground(textBackground);
+      m_SelectedCheckBox.addItemListener(this);
+      add(m_SelectedCheckBox);
+    }
+    
+    m_Label = new JLabel(name);
+    if (fontValue != null) {
+      m_Label.setFont(fontValue);
+    }
+    m_Label.setForeground(textForeground);
+    m_Label.setBackground(textBackground);
+    add(m_Label);
+    
+    if (iteratorType != NumberNode.NOT_ITERATOR) {
+      updateIteratorButton();
+      m_IteratorButton.setForeground(textForeground);
+      m_IteratorButton.setBackground(textBackground);
+      add(m_IteratorButton);
+    }
+    
+    NumberFormat numberFormat = null;
+    try {
+      numberFormat = node.getNumberFormat();
+    } catch (NumberClassNotFoundException e) {
+      e.printStackTrace();
+    }
+    
+    m_NumberField = new JFormattedTextField(numberFormat);
+    m_NumberField.setValue(value);
+    m_NumberField.setColumns(10);
+    m_NumberField.setForeground(textForeground);
+    m_NumberField.setBackground(textBackground);
+    
+    m_NumberField.addPropertyChangeListener(this);
+    
+    add(m_NumberField);
+    
+    if (!selected && showCheckBox) {
+      m_Label.setEnabled(false);
+      if (m_IteratorButton != null)
+	m_IteratorButton.setEnabled(false);
+      m_NumberField.setEnabled(false);
+    }
+  }
+  
+  /**
+   * This method provides a way for the ModelTreeNodeEditor to register an
+   * actionListener with this editor. This way, after the user has done
+   * something, the tree can update its editing state as well as the tree node
+   * structure as necessary
+   * 
+   * @param actionListener	the listener to use
+   */
+  public void setActionListener(ActionListener actionListener) {
+    if (m_IteratorButton != null)
+      m_IteratorButton.addActionListener(actionListener);
+  }
+  
+  /**
+   * This method provides a way for the ModelTreeNodeEditor to register an
+   * itemListener with this editor. This way, after the user has done
+   * something, the tree can update its editing state as well as the tree node
+   * structure as necessary
+   * 
+   * @param itemListener	the listener to use
+   */
+  public void setItemListener(ItemListener itemListener) {
+    if (m_SelectedCheckBox != null)
+      m_SelectedCheckBox.addItemListener(itemListener);
+  }
+  
+  /**
+   * This method provides a way for the ModelTreeNodeEditor to register a
+   * PropertyListener with this editor. This way, after the user has done
+   * something, the tree can update its editing state as well as the tree node
+   * structure as necessary
+   * 
+   * @param propertyChangeListener	the listener to use
+   */
+  public void setPropertyChangeListener(
+      PropertyChangeListener propertyChangeListener) {
+    if (m_NumberField != null)
+      m_NumberField.addPropertyChangeListener(propertyChangeListener);
+  }
+  
+  /**
+   * This is a helper function that repaints the iterator toggling button
+   * after an event.
+   * 
+   */
+  private void updateIteratorButton() {
+    
+    if (m_Node.getIteratorType() == NumberNode.PLUS_EQUAL) {
+      m_IteratorButton = new JButton("+=");
+      m_IteratorButton.addActionListener(this);
+      
+    } else if (m_Node.getIteratorType() == NumberNode.TIMES_EQUAL) {
+      m_IteratorButton = new JButton("*=");
+      m_IteratorButton.addActionListener(this);
+      
+    }
+    
+    m_IteratorButton.repaint();
+    repaint();
+  }
+  
+  /**
+   * This is the actionListener that while handle events from the JButton that
+   * specifies the type of iterator. It simply updates both the label string
+   * for the button and the iteratorType of the NumberNode
+   * 
+   * @param e		the event
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    if (m_Node.getIteratorType() == NumberNode.PLUS_EQUAL) {
+      m_Node.setIteratorType(NumberNode.TIMES_EQUAL);
+      
+      try {
+	if ((m_Node.getValue()).equals(m_Node.getOneValue())) {
+	  m_Node.setValue(m_Node.getTwoValue());
+	  updateIteratorButton();
+	}
+	
+      } catch (NumberClassNotFoundException e1) {
+	e1.printStackTrace();
+      }
+      
+    } else if (m_Node.getIteratorType() == NumberNode.TIMES_EQUAL) {
+      m_Node.setIteratorType(NumberNode.PLUS_EQUAL);
+      try {
+	if ((m_Node.getValue()).equals(m_Node.getTwoValue())) {
+	  m_Node.setValue(m_Node.getOneValue());
+	  updateIteratorButton();
+	}
+	
+      } catch (NumberClassNotFoundException e1) {
+	e1.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * This is the Listener that while handle events from the checkBox ,if this
+   * node has one. Note that before we can select or deselect this node we
+   * have to ask permission from this node's parent. (wow, what fitting
+   * terminology). Anyway, the reason is that we cannot have an iterator node
+   * selected if there is no max node selected - conversely we cannot have a
+   * max node be deselected if the iterator node is selected.
+   * 
+   * @param e		the event
+   */
+  public void itemStateChanged(ItemEvent e) {
+    if (e.getStateChange() == ItemEvent.SELECTED) {
+      
+      PropertyNode parent = (PropertyNode) m_Node.getParent();
+      
+      if (parent.canSelect(m_Node)) {
+	
+	m_Node.setSelected(true);
+	
+	m_Label.setEnabled(true);
+	if (m_IteratorButton != null)
+	  m_IteratorButton.setEnabled(true);
+	m_NumberField.setEnabled(true);
+      } else {
+	m_SelectedCheckBox.setSelected(false);
+      }
+      
+    } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+      
+      PropertyNode parent = (PropertyNode) m_Node.getParent();
+      
+      if (parent.canDeselect(m_Node)) {
+	
+	m_Node.setSelected(false);
+	
+	m_Label.setEnabled(false);
+	if (m_IteratorButton != null)
+	  m_IteratorButton.setEnabled(false);
+	m_NumberField.setEnabled(false);
+	
+      } else {
+	m_SelectedCheckBox.setSelected(true);
+      }
+    }
+  }
+  
+  /**
+   * This is the Listener that while handle events from the text box for this
+   * node. It basically grabs the value from the text field and then casts it
+   * to the correct type and sets the value of the node.
+   * 
+   * @param evt		the event
+   */
+  public void propertyChange(PropertyChangeEvent evt) {
+    
+    Object source = evt.getSource();
+    
+    if (source instanceof JFormattedTextField
+	&& evt.getPropertyName().equals("value")
+	&& (((JFormattedTextField) evt.getSource()).getValue() != null)) {
+      
+      Number newValue = null;
+      
+      Number value = m_Node.getValue();
+      
+      JFormattedTextField field = (JFormattedTextField) evt.getSource();
+      
+      Number fieldValue = (Number) field.getValue();
+      
+      if (value instanceof Double)
+	newValue = new Double(NumberNode.roundDouble((fieldValue.doubleValue())));
+      else if (value instanceof Integer)
+	newValue = new Integer((fieldValue).intValue());
+      else if (value instanceof Float)
+	newValue = new Float(NumberNode.roundFloat((fieldValue.floatValue())));
+      else if (value instanceof Long)
+	newValue = new Long((fieldValue).longValue());
+      else {
+	try {
+	  throw new NumberClassNotFoundException(value.getClass()
+	      + " not currently supported.");
+	} catch (NumberClassNotFoundException e) {
+	  e.printStackTrace();
+	}
+      }
+      
+      field.setValue(newValue);
+      
+      m_Node.setValue(newValue);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/PropertyNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/PropertyNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/ensembleLibraryEditor/tree/PropertyNode.java	(revision 29)
@@ -0,0 +1,446 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PropertyNode.java
+ *    Copyright (C) 2006 Robert Jung
+ *
+ */
+
+package weka.gui.ensembleLibraryEditor.tree;
+
+import weka.gui.GenericObjectEditor;
+import weka.gui.ensembleLibraryEditor.AddModelsPanel;
+
+import java.beans.PropertyEditor;
+import java.util.Vector;
+
+import javax.swing.JOptionPane;
+import javax.swing.JRootPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+/**
+ * This node class represents individual parameters of generic objects
+ * (in practice this means classifiers).  So all direct children of a
+ * classifier or other generic objects in the tree are going to be 
+ * property nodes.  Note that these nodes do not themselves have editors
+ * all editing in the user interface actaully happens in the child
+ * nodes of this class that it controls.  On top of creating these 
+ * child nodes and initializing them with the correct editing 
+ * configuration, this class is also responsible for obtaining all of 
+ * the possible values from the child nodes.
+ *  
+ * @author  Robert Jung (mrbobjung@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class PropertyNode 
+  extends DefaultMutableTreeNode {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 8179038568780212829L;
+
+  /** this is a reference to the parent panel of the JTree which is
+   * needed to display correctly anchored JDialogs*/
+  private final AddModelsPanel m_ParentPanel;
+  
+  /** the name of the node to be displayed */
+  private String m_Name;
+  
+  /** the node's tip text*/
+  private String m_ToolTipText;
+  
+  /** The propertyEditor created for the node, this is very useful in
+   * figuring out exactly waht kind of child editor nodes to create */
+  private PropertyEditor m_PropertyEditor;
+  
+  /** a reference to the tree model is necessary to be able to add and 
+   * remove nodes in the tree */
+  private DefaultTreeModel m_TreeModel;
+  
+  /** a reference to the tree */
+  private JTree m_Tree;
+  
+  /**
+   * The constructor initialiazes the member variables of this node, 
+   * Note that the "value" of this generic object is stored as the treeNode
+   * user object. After the values are initialized the constructor calls
+   * the addEditorNodes to create all the child nodes necessary to allow
+   * users to specify ranges of parameter values meaningful for the 
+   * parameter that this node represents.
+   * 
+   * @param tree		the tree to use
+   * @param panel		the pabel
+   * @param name		the name
+   * @param toolTipText		the tooltip
+   * @param value		the actual value
+   * @param pe			the property editor
+   */
+  public PropertyNode(JTree tree, AddModelsPanel panel, String name,
+      String toolTipText, Object value, PropertyEditor pe) {
+    
+    super(value);
+    
+    m_Tree = tree;
+    m_TreeModel = (DefaultTreeModel) m_Tree.getModel();
+    m_ParentPanel = panel;
+    m_Name = name;
+    m_ToolTipText = toolTipText;
+    m_PropertyEditor = pe;
+    
+    addEditorNodes(name, toolTipText);
+  }
+  
+  /**
+   * getter for the tooltip text
+   * 
+   * @return tooltip text
+   */
+  public String getToolTipText() {
+    return m_ToolTipText;
+  }
+  
+  /**
+   * getter for the name to be displayed for this node
+   * 
+   * @return		the name
+   */
+  public String getName() {
+    return m_Name;
+  }
+  
+  /**
+   * this returns the property editor that was provided for this object. This
+   * propertyEditor object is initially chosen inside of the GenericObjectNode
+   * updateTree() method if you are interested in where it comes from.
+   * 
+   * @return the default editor for this node
+   */
+  public PropertyEditor getPropertyEditor() {
+    return m_PropertyEditor;
+  }
+  
+  /**
+   * returns a string representation
+   * 
+   * @return		a string representation
+   */
+  public String toString() {
+    return getClass().getName() + "[" + getUserObject().toString() + "]";
+  }
+  
+  /**
+   * This method figures out what kind of parameter type this node 
+   * represents and then creates the appropriate set of child nodes 
+   * for editing.
+   * 
+   * @param name	the name
+   * @param toolTipText	the tooltip
+   */
+  public void addEditorNodes(String name, String toolTipText) {
+    
+    Object value = getUserObject();
+    
+    if (value instanceof Number) {
+      
+      NumberNode minNode = new NumberNode("min: ", (Number) value,
+	  NumberNode.NOT_ITERATOR, false, toolTipText);
+      
+      m_TreeModel.insertNodeInto(minNode, this, 0);
+      
+      Number one = null;
+      try {
+	one = minNode.getOneValue();
+      } catch (NumberClassNotFoundException e) {
+	e.printStackTrace();
+      }
+      
+      NumberNode iteratorNode = new NumberNode("iterator: ", one,
+	  NumberNode.PLUS_EQUAL, true, toolTipText);
+      m_TreeModel.insertNodeInto(iteratorNode, this, 1);
+      
+      NumberNode maxNode = new NumberNode("max: ", (Number) value,
+	  NumberNode.NOT_ITERATOR, true, toolTipText);
+      m_TreeModel.insertNodeInto(maxNode, this, 2);
+      
+    } else if (m_PropertyEditor instanceof GenericObjectEditor) {
+      
+      GenericObjectNode classifierNode = new GenericObjectNode(
+	  m_ParentPanel, value,
+	  (GenericObjectEditor) m_PropertyEditor, toolTipText);
+      
+      m_TreeModel.insertNodeInto(classifierNode, this, 0);
+      classifierNode.setTree(m_Tree);
+      classifierNode.updateTree();
+      
+    } else if (m_PropertyEditor.getTags() != null) {
+      
+      String selected = m_PropertyEditor.getAsText();
+      String tags[] = m_PropertyEditor.getTags();
+      if (tags != null)
+	for (int i = 0; i < tags.length; i++) {
+	  
+	  CheckBoxNode checkBoxNode = new CheckBoxNode(tags[i],
+	      selected.equals(tags[i]), toolTipText);
+	  m_TreeModel.insertNodeInto(checkBoxNode, this, i);
+	  
+	}
+      
+    } else {
+      
+      DefaultNode defaultNode = new DefaultNode(name, toolTipText, value,
+	  m_PropertyEditor);
+      
+      m_TreeModel.insertNodeInto(defaultNode, this, 0);
+      
+    }
+  }
+  
+  /** 
+   * This method gets the range of values as specified by the 
+   * child editor nodes.
+   * 
+   * @return	all values
+   */
+  public Vector getAllValues() {
+    
+    Vector values = new Vector();
+    
+    //OK, there are four type of nodes that can branch off of a propertyNode
+    
+    DefaultMutableTreeNode child = (DefaultMutableTreeNode) m_TreeModel.getChild(this, 0);
+    
+    if (child instanceof GenericObjectNode) {
+      //Here we let the generic object class handles this for us
+      values = ((GenericObjectNode) child).getValues();
+      
+    } else if (child instanceof DefaultNode) {
+      //This is perhaps the easiest case.  GenericNodes are only responsible
+      //for a single value 				
+      values.add(((DefaultNode) child).getUserObject());
+      
+    } else if (child instanceof CheckBoxNode) {
+      //Iterate through all of the children add their
+      //value if they're selected
+      
+      int childCount = m_TreeModel.getChildCount(this);
+      
+      for (int i = 0; i < childCount; i++) {
+	
+	CheckBoxNode currentChild = (CheckBoxNode) m_TreeModel
+	.getChild(this, i);
+	
+	if (currentChild.getSelected())
+	  values.add(currentChild.getUserObject());
+	
+      }
+      
+    } else if (child instanceof NumberNode) {
+      //here we need to handle some weird cases for inpout validation
+      
+      NumberNode minChild = (NumberNode) m_TreeModel.getChild(this, 0);
+      NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this, 1);
+      NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2);
+      
+      boolean ignoreIterator = false;
+      
+      try {
+	
+	if (iteratorChild.getSelected()) {
+	  
+	  //first we check to see if the min value is greater than the max value
+	  //if so then we gotta problem
+	  
+	  if (maxChild.lessThan(maxChild.getValue(), minChild.getValue())) {
+	    
+	    ignoreIterator = true;
+	    throw new InvalidInputException(
+		"Invalid numeric input for node " + getName()
+		+ ": min > max. ");
+	  }
+	  
+	  //Make sure that the iterator value will actually "iterate" between the
+	  //min and max values
+	  if ((iteratorChild.getIteratorType() == NumberNode.PLUS_EQUAL)
+	      && (iteratorChild.lessThan(
+		  iteratorChild.getValue(), iteratorChild
+		  .getZeroValue()) || (iteratorChild
+		      .equals(iteratorChild.getValue(),
+			  iteratorChild.getZeroValue())))) {
+	    
+	    ignoreIterator = true;
+	    throw new InvalidInputException(
+		"Invalid numeric input for node " + getName()
+		+ ": += iterator <= 0. ");
+	    
+	  } else if ((iteratorChild.getIteratorType() == NumberNode.TIMES_EQUAL)
+	      && (iteratorChild.lessThan(
+		  iteratorChild.getValue(), iteratorChild
+		  .getOneValue()) || (iteratorChild
+		      .equals(iteratorChild.getValue(),
+			  iteratorChild.getOneValue())))) {
+	    
+	    ignoreIterator = true;
+	    throw new InvalidInputException(
+		"Invalid numeric input for node " + getName()
+		+ ": *= iterator <= 1. ");
+	    
+	  }
+	  
+	}
+	
+      } catch (InvalidInputException e) {
+	
+	JRootPane parent = m_ParentPanel.getRootPane();
+	
+	JOptionPane.showMessageDialog(parent, "Invalid Input: "
+	    + e.getMessage(), "Input error",
+	    JOptionPane.ERROR_MESSAGE);
+	
+	e.printStackTrace();
+      } catch (NumberClassNotFoundException e) {
+	e.printStackTrace();
+      }
+      
+      if (!iteratorChild.getSelected() || ignoreIterator) {
+	//easiest case - if we don't care about the Iterator then we just throw
+	//in the min value along with the max value(if its selected)  
+	values.add(minChild.getUserObject());
+	
+	if (maxChild.getSelected()
+	    && (!maxChild.getValue().equals(minChild.getValue())))
+	  values.add(maxChild.getUserObject());
+	
+      } else {
+	//here we need to cycle through all of the values from min to max in
+	//increments specified by the inrement value.
+	
+	Number current = minChild.getValue();
+	
+	try {
+	  
+	  values.add(minChild.getValue());
+	  
+	  do {
+	    
+	    Number newNumber = null;
+	    
+	    if (iteratorChild.getIteratorType() == NumberNode.PLUS_EQUAL) {
+	      newNumber = iteratorChild.addNumbers(iteratorChild.getValue(), current);
+	    } else if (iteratorChild.getIteratorType() == NumberNode.TIMES_EQUAL) {
+	      newNumber = iteratorChild.multiplyNumbers(
+		  iteratorChild.getValue(), current);
+	    }
+	    
+	    current = newNumber;
+	    
+	    if (iteratorChild
+		.lessThan(current, maxChild.getValue())
+		&& (!iteratorChild.equals(current, maxChild.getValue()))) {
+	      values.add(newNumber);
+	    }
+	    
+	  } while (iteratorChild.lessThan(current, maxChild.getValue())
+	      && (!iteratorChild.equals(current, maxChild.getValue())));
+	  
+	  if (maxChild.getSelected()
+	      && (!maxChild.getValue().equals(minChild.getValue())))
+	    values.add(maxChild.getUserObject());
+	  
+	} catch (Exception e) {
+	  e.printStackTrace();
+	}
+	
+      }
+    }
+    
+    return values;
+  }
+  
+  /**
+   * This method informs a child number node whether or not it is 
+   * allowed to be selected. NumberNodes are the only ones that need
+   * to ask permission first.  This simply makes sure that iterator
+   * nodes can't be selected when the max node is not selected.
+   * 
+   * @param node	the node to check
+   * @return		true of the node can be selected
+   */
+  public boolean canSelect(NumberNode node) {
+    
+    boolean permission = true;
+    
+    NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this, 1);
+    NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2);
+    
+    //the one case where we want to say no: you can not have an iterator
+    //without a maximum value
+    if (node == iteratorChild && (maxChild.getSelected() == false))
+      permission = false;
+    
+    return permission;
+  }
+  
+  /**
+   * informs a requesting child node whether or not it has permission
+   * to be deselected. Note that only NumberNodes and CheckBoxNodes
+   * are the only one's that have any notion of being deselected and
+   * therefore should be the only one's calling this method.
+   * 
+   * @param node	the node to check
+   * @return		true if it can be de-selected
+   */
+  public boolean canDeselect(DefaultMutableTreeNode node) {
+    
+    boolean permission = true;
+    
+    if (node instanceof NumberNode) {
+      
+      NumberNode iteratorChild = (NumberNode) m_TreeModel.getChild(this,
+	  1);
+      NumberNode maxChild = (NumberNode) m_TreeModel.getChild(this, 2);
+      //the one case where we want to say no for number nodes: you can 
+      //not have an iterator without a maximum value
+      if (node == maxChild && (iteratorChild.getSelected() == true))
+	permission = false;
+      
+    } else if (node instanceof CheckBoxNode) {
+      
+      //For check box nodes, we only want to say no if there's only one
+      //box currently selected - because at least one box needs to be
+      //checked.
+      int totalSelected = 0;
+      int childCount = m_TreeModel.getChildCount(this);
+      
+      for (int i = 0; i < childCount; i++) {
+	
+	CheckBoxNode currentChild = (CheckBoxNode) m_TreeModel
+	.getChild(this, i);
+	
+	if (currentChild.getSelected())
+	  totalSelected++;
+	
+      }
+      
+      if (totalSelected == 1)
+	permission = false;
+      
+    }
+    
+    return permission;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/AlgorithmListPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/AlgorithmListPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/AlgorithmListPanel.java	(revision 29)
@@ -0,0 +1,630 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AlgorithmListPanel.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.xml.XMLClassifier;
+import weka.core.OptionHandler;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.experiment.Experiment;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.JListHelper;
+import weka.gui.PropertyDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel controls setting a list of algorithms for an experiment to
+ * iterate over.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5928 $
+ */
+public class AlgorithmListPanel
+  extends JPanel
+  implements ActionListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7204528834764898671L;
+
+  /**
+   * Class required to show the Classifiers nicely in the list
+   */
+  public class ObjectCellRenderer
+    extends DefaultListCellRenderer {
+
+    /** for serialization */
+    private static final long serialVersionUID = -5067138526587433808L;
+    
+    /**
+     * Return a component that has been configured to display the specified 
+     * value. That component's paint method is then called to "render" the 
+     * cell. If it is necessary to compute the dimensions of a list because 
+     * the list cells do not have a fixed size, this method is called to 
+     * generate a component on which getPreferredSize can be invoked.
+     * 
+     * @param list 		The JList we're painting.
+     * @param value		The value returned by 
+     * 				list.getModel().getElementAt(index).
+     * @param index		The cells index.
+     * @param isSelected	True if the specified cell was selected.
+     * @param cellHasFocus	True if the specified cell has the focus.
+     * @return			A component whose paint() method will render 
+     * 				the specified value.
+     */
+    public Component getListCellRendererComponent(JList list,
+						  Object value,
+						  int index,
+						  boolean isSelected,
+						  boolean cellHasFocus) {
+
+      Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+      String rep = value.getClass().getName();
+      int dotPos = rep.lastIndexOf('.');
+      if (dotPos != -1) {
+	rep = rep.substring(dotPos + 1);
+      }
+      if (value instanceof OptionHandler) {
+	rep += " " + Utils.joinOptions(((OptionHandler)value)
+				       .getOptions());
+      }
+      setText(rep);
+      return c;
+    }
+  }
+  
+  /** The experiment to set the algorithm list of */
+  protected Experiment m_Exp;
+
+  /** The component displaying the algorithm list */
+  protected JList m_List;
+
+  /** Click to add an algorithm */
+  protected JButton m_AddBut = new JButton("Add new...");
+  
+  /** Click to edit the selected algorithm */
+  protected JButton m_EditBut = new JButton("Edit selected...");
+
+  /** Click to remove the selected dataset from the list */
+  protected JButton m_DeleteBut = new JButton("Delete selected");
+  
+  /** Click to edit the load the options for athe selected algorithm */
+  protected JButton m_LoadOptionsBut = new JButton("Load options...");
+  
+  /** Click to edit the save the options from selected algorithm */
+  protected JButton m_SaveOptionsBut = new JButton("Save options...");
+  
+  /** Click to move the selected algorithm(s) one up */
+  protected JButton m_UpBut = new JButton("Up");
+  
+  /** Click to move the selected algorithm(s) one down */
+  protected JButton m_DownBut = new JButton("Down");
+  
+  /** The file chooser for selecting experiments */
+  protected JFileChooser m_FileChooser =
+    new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** A filter to ensure only experiment (in XML format) files get shown in the chooser */
+  protected FileFilter m_XMLFilter = 
+    new ExtensionFileFilter(".xml", 
+                            "Classifier options (*.xml)");
+
+  /** Whether an algorithm is added or only edited  */
+  protected boolean m_Editing = false;
+  
+  /** Lets the user configure the classifier */
+  protected GenericObjectEditor m_ClassifierEditor =
+    new GenericObjectEditor(true);
+
+  /** The currently displayed property dialog, if any */
+  protected PropertyDialog m_PD;
+
+  /** The list model used */
+  protected DefaultListModel m_AlgorithmListModel = new DefaultListModel();
+
+  /* Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+
+  /**
+   * Creates the algorithm list panel with the given experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public AlgorithmListPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Create the algorithm list panel initially disabled.
+   */
+  public AlgorithmListPanel() {
+    final AlgorithmListPanel self = this;
+    m_List = new JList();
+    MouseListener mouseListener = new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	final int index = m_List.locationToIndex(e.getPoint());
+
+	if ((e.getClickCount() == 2) && (e.getButton() == MouseEvent.BUTTON1)) {
+	  // unfortunately, locationToIndex only returns the nearest entry
+	  // and not the exact one, i.e. if there's one item in the list and
+	  // one doublelclicks somewhere in the list, this index will be
+	  // returned
+	  if (index > -1)
+	    actionPerformed(new ActionEvent(m_EditBut, 0, ""));
+	}
+	else if (e.getClickCount() == 1) {
+	  if (    (e.getButton() == MouseEvent.BUTTON3) 
+	      || ((e.getButton() == MouseEvent.BUTTON1) && e.isAltDown() && e.isShiftDown()) ) {
+	    JPopupMenu menu = new JPopupMenu();
+	    JMenuItem item;
+
+	    item = new JMenuItem("Add configuration...");
+	    item.addActionListener(new ActionListener() {
+	      public void actionPerformed(ActionEvent e) {
+		String str = JOptionPane.showInputDialog(
+		    self, 
+		    "Configuration (<classname> [<options>])");
+		if (str != null) {
+		  try {
+		    String[] options = Utils.splitOptions(str);
+		    String classname = options[0];
+		    options[0] = "";
+		    Object obj = Utils.forName(Object.class, classname, options);
+		    m_AlgorithmListModel.addElement(obj);
+		    updateExperiment();
+		  }
+		  catch (Exception ex) {
+		    ex.printStackTrace();
+		    JOptionPane.showMessageDialog(
+			self, 
+			"Error parsing commandline:\n" + ex, 
+			"Error...",
+			JOptionPane.ERROR_MESSAGE);
+		  }
+		}
+	      }
+	    });
+	    menu.add(item);
+
+	    if (m_List.getSelectedValue() != null) {
+	      menu.addSeparator();
+
+	      item = new JMenuItem("Show properties...");
+	      item.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		  self.actionPerformed(new ActionEvent(m_EditBut, 0, ""));
+		}
+	      });
+	      menu.add(item);
+
+	      item = new JMenuItem("Copy configuration to clipboard");
+	      item.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		  String str = m_List.getSelectedValue().getClass().getName();
+		  if (m_List.getSelectedValue() instanceof OptionHandler)
+		    str += " " + Utils.joinOptions(((OptionHandler) m_List.getSelectedValue()).getOptions());
+		  StringSelection selection = new StringSelection(str.trim());
+		  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+		  clipboard.setContents(selection, selection);
+		}
+	      });
+	      menu.add(item);
+
+	      item = new JMenuItem("Enter configuration...");
+	      item.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		  String str = JOptionPane.showInputDialog(
+		      self, 
+		      "Configuration (<classname> [<options>])");
+		  if (str != null) {
+		    try {
+		      String[] options = Utils.splitOptions(str);
+		      String classname = options[0];
+		      options[0] = "";
+		      Object obj = Utils.forName(Object.class, classname, options);
+		      m_AlgorithmListModel.setElementAt(obj, index);
+		      updateExperiment();
+		    }
+		    catch (Exception ex) {
+		      ex.printStackTrace();
+		      JOptionPane.showMessageDialog(
+			  self, 
+			  "Error parsing commandline:\n" + ex, 
+			  "Error...",
+			  JOptionPane.ERROR_MESSAGE);
+		    }
+		  }
+		}
+	      });
+	      menu.add(item);
+	    }
+
+	    menu.show(m_List, e.getX(), e.getY());
+	  }
+	}
+      }
+    };
+    m_List.addMouseListener(mouseListener);
+  
+    m_ClassifierEditor.setClassType(Classifier.class);
+    m_ClassifierEditor.setValue(new weka.classifiers.rules.ZeroR());
+    m_ClassifierEditor.addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+	  repaint();
+	}
+      });
+    ((GenericObjectEditor.GOEPanel) m_ClassifierEditor.getCustomEditor()).addOkListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  Classifier newCopy =
+	    (Classifier) copyObject(m_ClassifierEditor.getValue());
+	  addNewAlgorithm(newCopy);
+	}
+      });
+    
+    m_DeleteBut.setEnabled(false);
+    m_DeleteBut.addActionListener(this);
+    m_AddBut.setEnabled(false);
+    m_AddBut.addActionListener(this);
+    m_EditBut.setEnabled(false);
+    m_EditBut.addActionListener(this);
+    m_LoadOptionsBut.setEnabled(false);
+    m_LoadOptionsBut.addActionListener(this);
+    m_SaveOptionsBut.setEnabled(false);
+    m_SaveOptionsBut.addActionListener(this);
+    m_UpBut.setEnabled(false);
+    m_UpBut.addActionListener(this);
+    m_DownBut.setEnabled(false);
+    m_DownBut.addActionListener(this);
+    
+    m_List.addListSelectionListener(new ListSelectionListener() {
+        public void valueChanged(ListSelectionEvent e) {
+          setButtons(e);
+        }
+      });
+    
+    m_FileChooser.addChoosableFileFilter(m_XMLFilter);
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    setLayout(new BorderLayout());
+    setBorder(BorderFactory.createTitledBorder("Algorithms"));
+    JPanel topLab = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    topLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    topLab.setLayout(gb);
+   
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    topLab.add(m_AddBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    topLab.add(m_EditBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    topLab.add(m_DeleteBut,constraints);
+
+    JPanel bottomLab = new JPanel();
+    gb = new GridBagLayout();
+    constraints = new GridBagConstraints();
+    bottomLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    bottomLab.setLayout(gb);
+    
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    bottomLab.add(m_LoadOptionsBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    bottomLab.add(m_SaveOptionsBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    bottomLab.add(m_UpBut,constraints);
+    constraints.gridx=3;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    bottomLab.add(m_DownBut,constraints);
+
+    add(topLab, BorderLayout.NORTH);
+    add(new JScrollPane(m_List), BorderLayout.CENTER);
+    add(bottomLab, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Tells the panel to act on a new experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+
+    m_Exp = exp;
+    m_AddBut.setEnabled(true);
+    m_List.setModel(m_AlgorithmListModel);
+    m_List.setCellRenderer(new ObjectCellRenderer());
+    m_AlgorithmListModel.removeAllElements();
+    if (m_Exp.getPropertyArray() instanceof Classifier[]) {
+      Classifier[] algorithms = (Classifier[]) m_Exp.getPropertyArray();
+      for (int i=0; i<algorithms.length; i++) {
+	m_AlgorithmListModel.addElement(algorithms[i]);
+      }
+    }
+    m_EditBut.setEnabled((m_AlgorithmListModel.size() > 0));
+    m_DeleteBut.setEnabled((m_AlgorithmListModel.size() > 0));
+    m_LoadOptionsBut.setEnabled((m_AlgorithmListModel.size() > 0));
+    m_SaveOptionsBut.setEnabled((m_AlgorithmListModel.size() > 0));
+    m_UpBut.setEnabled(JListHelper.canMoveUp(m_List));
+    m_DownBut.setEnabled(JListHelper.canMoveDown(m_List));
+  }
+
+  /**
+   * Add a new algorithm to the list.
+   * 
+   * @param newScheme	the new scheme to add
+   */
+  private void addNewAlgorithm(Classifier newScheme) {
+    if (!m_Editing)
+      m_AlgorithmListModel.addElement(newScheme);
+    else
+      m_AlgorithmListModel.setElementAt(newScheme, m_List.getSelectedIndex());
+    
+    updateExperiment();
+    
+    m_Editing = false;
+  }
+  
+  /**
+   * updates the classifiers in the experiment
+   */
+  private void updateExperiment() {
+    Classifier[] cArray = new Classifier[m_AlgorithmListModel.size()];
+    for (int i=0; i<cArray.length; i++) {
+      cArray[i] = (Classifier) m_AlgorithmListModel.elementAt(i);
+    }
+    m_Exp.setPropertyArray(cArray); 
+  }
+  
+  /**
+   * sets the state of the buttons according to the selection state of the
+   * JList
+   * 
+   * @param e		the event
+   */
+  private void setButtons(ListSelectionEvent e) {
+    if (e.getSource() == m_List) {
+      m_DeleteBut.setEnabled(m_List.getSelectedIndex() > -1);
+      m_AddBut.setEnabled(true);
+      m_EditBut.setEnabled(m_List.getSelectedIndices().length == 1);
+      m_LoadOptionsBut.setEnabled(m_List.getSelectedIndices().length == 1);
+      m_SaveOptionsBut.setEnabled(m_List.getSelectedIndices().length == 1);
+      m_UpBut.setEnabled(JListHelper.canMoveUp(m_List));
+      m_DownBut.setEnabled(JListHelper.canMoveDown(m_List));
+    }
+  }
+
+  /**
+   * Handle actions when buttons get pressed.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+
+    if (e.getSource() == m_AddBut) {
+      m_Editing = false;
+      if (m_PD == null) {
+	int x = getLocationOnScreen().x;
+	int y = getLocationOnScreen().y;
+	if (PropertyDialog.getParentDialog(this) != null)
+	  m_PD = new PropertyDialog(
+	      PropertyDialog.getParentDialog(this), 
+	      m_ClassifierEditor, x, y);
+	else
+	  m_PD = new PropertyDialog(
+	      PropertyDialog.getParentFrame(this), 
+	      m_ClassifierEditor, x, y);
+	m_PD.setVisible(true);
+      } else {
+	m_PD.setVisible(true);
+      }
+      
+    } else if (e.getSource() == m_EditBut) {
+      if (m_List.getSelectedValue() != null) {
+        m_ClassifierEditor.setClassType(weka.classifiers.Classifier.class);
+        // m_PD.getEditor().setValue(m_List.getSelectedValue());
+        m_ClassifierEditor.setValue(m_List.getSelectedValue());
+         m_Editing = true;
+         if (m_PD == null) {
+            int x = getLocationOnScreen().x;
+            int y = getLocationOnScreen().y;
+            if (PropertyDialog.getParentDialog(this) != null)
+              m_PD = new PropertyDialog(
+        	  PropertyDialog.getParentDialog(this), 
+        	  m_ClassifierEditor, x, y);
+            else
+              m_PD = new PropertyDialog(
+        	  PropertyDialog.getParentFrame(this), 
+        	  m_ClassifierEditor, x, y);
+            m_PD.setVisible(true);
+         } else {
+            m_PD.setVisible(true);
+         }
+      }
+
+    } else if (e.getSource() == m_DeleteBut) {
+
+      int [] selected = m_List.getSelectedIndices();
+      if (selected != null) {
+	for (int i = selected.length - 1; i >= 0; i--) {
+	  int current = selected[i];
+	  m_AlgorithmListModel.removeElementAt(current);
+	  if (m_Exp.getDatasets().size() > current) {
+	    m_List.setSelectedIndex(current);
+	  } else {
+	    m_List.setSelectedIndex(current - 1);
+	  }
+	}
+      }
+      if (m_List.getSelectedIndex() == -1) {
+        m_EditBut.setEnabled(false);
+        m_DeleteBut.setEnabled(false);
+        m_LoadOptionsBut.setEnabled(false);
+        m_SaveOptionsBut.setEnabled(false);
+        m_UpBut.setEnabled(false);
+        m_DownBut.setEnabled(false);
+      }
+
+      updateExperiment();
+    } else if (e.getSource() == m_LoadOptionsBut) {
+      if (m_List.getSelectedValue() != null) {
+        int returnVal = m_FileChooser.showOpenDialog(this);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+          try {
+            File file = m_FileChooser.getSelectedFile();
+            if (!file.getAbsolutePath().toLowerCase().endsWith(".xml"))
+              file = new File(file.getAbsolutePath() + ".xml");
+            XMLClassifier xmlcls = new XMLClassifier();
+            Classifier c = (Classifier) xmlcls.read(file);
+            m_AlgorithmListModel.setElementAt(c, m_List.getSelectedIndex());
+            updateExperiment();
+          }
+          catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        }
+      }
+   } else if (e.getSource() == m_SaveOptionsBut) {
+      if (m_List.getSelectedValue() != null) {
+        int returnVal = m_FileChooser.showSaveDialog(this);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+          try {
+            File file = m_FileChooser.getSelectedFile();
+            if (!file.getAbsolutePath().toLowerCase().endsWith(".xml"))
+              file = new File(file.getAbsolutePath() + ".xml");
+            XMLClassifier xmlcls = new XMLClassifier();
+            xmlcls.write(file, m_List.getSelectedValue());
+          }
+          catch (Exception ex) {
+            ex.printStackTrace();
+          }
+        }
+      }
+    } 
+    else if (e.getSource() == m_UpBut) {
+      JListHelper.moveUp(m_List);
+      updateExperiment();
+    }
+    else if (e.getSource() == m_DownBut) {
+      JListHelper.moveDown(m_List);
+      updateExperiment();
+    }
+  }
+
+  /**
+   * Makes a copy of an object using serialization
+   * @param source the object to copy
+   * @return a copy of the source object
+   */
+  protected Object copyObject(Object source) {
+    
+    Object result = null;
+    try {
+      SerializedObject so = new SerializedObject(source);
+      result = so.getObject();
+    } catch (Exception ex) {
+      System.err.println("AlgorithmListPanel: Problem copying object");
+      System.err.println(ex);
+    }
+    return result;
+  }
+  
+  /**
+   * Tests out the algorithm list panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Algorithm List Editor");
+      jf.getContentPane().setLayout(new BorderLayout());
+      AlgorithmListPanel dp = new AlgorithmListPanel();
+      jf.getContentPane().add(dp,
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      System.err.println("Short nap");
+      Thread.currentThread().sleep(3000);
+      System.err.println("Done");
+      dp.setExperiment(new Experiment());
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/DatasetListPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/DatasetListPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/DatasetListPanel.java	(revision 29)
@@ -0,0 +1,434 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DatasetListPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.ClassDiscovery.StringCompare;
+import weka.core.converters.ConverterUtils;
+import weka.core.converters.Saver;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.core.Utils;
+import weka.experiment.Experiment;
+import weka.gui.ConverterFileChooser;
+import weka.gui.JListHelper;
+import weka.gui.ViewerDialog;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.util.Collections;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/** 
+ * This panel controls setting a list of datasets for an experiment to
+ * iterate over.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.27 $
+ */
+public class DatasetListPanel
+  extends JPanel
+  implements ActionListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 7068857852794405769L;
+
+  /** The experiment to set the dataset list of. */
+  protected Experiment m_Exp;
+
+  /** The component displaying the dataset list. */
+  protected JList m_List;
+
+  /** Click to add a dataset. */
+  protected JButton m_AddBut = new JButton("Add new...");
+  
+  /** Click to edit the selected algorithm. */
+  protected JButton m_EditBut = new JButton("Edit selected...");
+
+  /** Click to remove the selected dataset from the list. */
+  protected JButton m_DeleteBut = new JButton("Delete selected");
+  
+  /** Click to move the selected dataset(s) one up. */
+  protected JButton m_UpBut = new JButton("Up");
+  
+  /** Click to move the selected dataset(s) one down. */
+  protected JButton m_DownBut = new JButton("Down");
+
+  /** Make file paths relative to the user (start) directory. */
+  protected JCheckBox m_relativeCheck = new JCheckBox("Use relative paths");
+
+  /** The user (start) directory. */
+  //  protected File m_UserDir = new File(System.getProperty("user.dir"));
+
+  /** The file chooser component. */
+  protected ConverterFileChooser m_FileChooser = 
+    new ConverterFileChooser(ExperimenterDefaults.getInitialDatasetsDirectory());
+
+  
+  /**
+   * Creates the dataset list panel with the given experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public DatasetListPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Create the dataset list panel initially disabled.
+   */
+  public DatasetListPanel() {
+    
+    m_List = new JList();
+    m_List.addListSelectionListener(new ListSelectionListener() {
+        public void valueChanged(ListSelectionEvent e) {
+          setButtons(e);
+        }
+      });
+    MouseListener mouseListener = new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+        if (e.getClickCount() == 2) {
+          // unfortunately, locationToIndex only returns the nearest entry
+          // and not the exact one, i.e. if there's one item in the list and
+          // one doublelclicks somewhere in the list, this index will be
+          // returned
+          int index = m_List.locationToIndex(e.getPoint());
+          if (index > -1)
+            actionPerformed(new ActionEvent(m_EditBut, 0, ""));
+        }
+      }
+    };
+    m_List.addMouseListener(mouseListener);
+    
+    //m_FileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+    m_FileChooser.setCoreConvertersOnly(true);
+    m_FileChooser.setMultiSelectionEnabled(true);
+    m_FileChooser.setFileSelectionMode(ConverterFileChooser.FILES_AND_DIRECTORIES);
+    m_FileChooser.setAcceptAllFileFilterUsed(false);
+    m_DeleteBut.setEnabled(false);
+    m_DeleteBut.addActionListener(this);
+    m_AddBut.setEnabled(false);
+    m_AddBut.addActionListener(this);
+    m_EditBut.setEnabled(false);
+    m_EditBut.addActionListener(this);
+    m_UpBut.setEnabled(false);
+    m_UpBut.addActionListener(this);
+    m_DownBut.setEnabled(false);
+    m_DownBut.addActionListener(this);
+    m_relativeCheck.setSelected(ExperimenterDefaults.getUseRelativePaths());
+    m_relativeCheck.setToolTipText("Store file paths relative to "
+				   +"the start directory");
+    setLayout(new BorderLayout());
+    setBorder(BorderFactory.createTitledBorder("Datasets"));
+    JPanel topLab = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    topLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    //    topLab.setLayout(new GridLayout(1,2,5,5));
+    topLab.setLayout(gb);
+   
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    topLab.add(m_AddBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    topLab.add(m_EditBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    topLab.add(m_DeleteBut,constraints);
+
+    constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    topLab.add(m_relativeCheck,constraints);
+
+    JPanel bottomLab = new JPanel();
+    gb = new GridBagLayout();
+    constraints = new GridBagConstraints();
+    bottomLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    bottomLab.setLayout(gb);
+   
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    bottomLab.add(m_UpBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    bottomLab.add(m_DownBut,constraints);
+
+    add(topLab, BorderLayout.NORTH);
+    add(new JScrollPane(m_List), BorderLayout.CENTER);
+    add(bottomLab, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * sets the state of the buttons according to the selection state of the
+   * JList.
+   * 
+   * @param e the event
+   */
+  private void setButtons(ListSelectionEvent e) {
+    if ( (e == null) || (e.getSource() == m_List) ) {
+      m_DeleteBut.setEnabled(m_List.getSelectedIndex() > -1);
+      m_EditBut.setEnabled(m_List.getSelectedIndices().length == 1);
+      m_UpBut.setEnabled(JListHelper.canMoveUp(m_List));
+      m_DownBut.setEnabled(JListHelper.canMoveDown(m_List));
+    }
+  }
+
+  /**
+   * Tells the panel to act on a new experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+
+    m_Exp = exp;
+    m_List.setModel(m_Exp.getDatasets());
+    m_AddBut.setEnabled(true);
+    setButtons(null);
+  }
+  
+  /**
+   * Gets all the files in the given directory
+   * that match the currently selected extension.
+   * 
+   * @param directory the directory to get the files for
+   * @param files the list to add the files to
+   */
+  protected void getFilesRecursively(File directory, Vector files) {
+
+    try {
+      String[] currentDirFiles = directory.list();
+      for (int i = 0; i < currentDirFiles.length; i++) {
+	currentDirFiles[i] = directory.getCanonicalPath() + File.separator + 
+	  currentDirFiles[i];
+	File current = new File(currentDirFiles[i]);
+	if (m_FileChooser.getFileFilter().accept(current)) {
+	  if (current.isDirectory()) {
+	    getFilesRecursively(current, files);
+	  } else {
+	    files.addElement(current);
+	  }
+	}
+      }
+    } catch (Exception e) {
+      System.err.println("IOError occured when reading list of files");
+    }
+  }
+  
+  /**
+   * Handle actions when buttons get pressed.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+    boolean useRelativePaths = m_relativeCheck.isSelected();
+
+    if (e.getSource() == m_AddBut) {
+      // Let the user select an arff file from a file chooser
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if(returnVal == JFileChooser.APPROVE_OPTION) {
+	if (m_FileChooser.isMultiSelectionEnabled()) {
+	  File [] selected = m_FileChooser.getSelectedFiles();
+	  for (int i = 0; i < selected.length; i++) {
+	    if (selected[i].isDirectory()) {
+	      Vector files = new Vector();
+	      getFilesRecursively(selected[i], files);
+    
+	      // sort the result
+	      Collections.sort(files, new StringCompare());
+
+	      for (int j = 0; j < files.size(); j++) {
+		File temp = (File)files.elementAt(j);
+		if (useRelativePaths) {
+		  try {
+		    temp = Utils.convertToRelativePath(temp);
+		  } catch (Exception ex) {
+		    ex.printStackTrace();
+		  }
+		}
+		m_Exp.getDatasets().addElement(temp);
+	      }
+	    } else {
+	      File temp = selected[i];
+	      if (useRelativePaths) {
+		try {
+		  temp = Utils.convertToRelativePath(temp);
+		} catch (Exception ex) {
+		  ex.printStackTrace();
+		}
+	      }
+	      m_Exp.getDatasets().addElement(temp);
+	    }
+	  }
+          setButtons(null);
+	} else {
+	  if (m_FileChooser.getSelectedFile().isDirectory()) {
+	    Vector files = new Vector();
+	    getFilesRecursively(m_FileChooser.getSelectedFile(), files);
+    
+	    // sort the result
+	    Collections.sort(files, new StringCompare());
+
+	    for (int j = 0; j < files.size(); j++) {
+	      File temp = (File)files.elementAt(j);
+	      if (useRelativePaths) {
+		try {
+		  temp = Utils.convertToRelativePath(temp);
+		} catch (Exception ex) {
+		  ex.printStackTrace();
+		}
+	      }
+	      m_Exp.getDatasets().addElement(temp);
+	    }
+	  } else {
+	    File temp = m_FileChooser.getSelectedFile();
+	    if (useRelativePaths) {
+	      try {
+		temp = Utils.convertToRelativePath(temp);
+	      } catch (Exception ex) {
+		ex.printStackTrace();
+	      }
+	    }
+	    m_Exp.getDatasets().addElement(temp);
+	  }
+          setButtons(null);
+	}
+      }
+    } else if (e.getSource() == m_DeleteBut) {
+      // Delete the selected files
+      int [] selected = m_List.getSelectedIndices();
+      if (selected != null) {
+	for (int i = selected.length - 1; i >= 0; i--) {
+	  int current = selected[i];
+	  m_Exp.getDatasets().removeElementAt(current);
+	  if (m_Exp.getDatasets().size() > current) {
+	    m_List.setSelectedIndex(current);
+	  } else {
+	    m_List.setSelectedIndex(current - 1);
+	  }
+	}
+      }
+      setButtons(null);
+    } else if (e.getSource() == m_EditBut) {
+      // Delete the selected files
+      int selected = m_List.getSelectedIndex();
+      if (selected != -1) {
+	ViewerDialog dialog = new ViewerDialog(null);
+	String filename = m_List.getSelectedValue().toString();
+	int result;
+	try {
+	  DataSource source = new DataSource(filename);
+	  result = dialog.showDialog(source.getDataSet());
+	  // nasty workaround for Windows regarding locked files:
+	  // if file Reader in Loader is not closed explicitly, we cannot
+	  // overwrite the file.
+	  source = null;
+	  System.gc();
+	  // workaround end
+	  if ((result == ViewerDialog.APPROVE_OPTION) && (dialog.isChanged())) {
+	    result = JOptionPane.showConfirmDialog(
+			this,
+			"File was modified - save changes?");
+	    if (result == JOptionPane.YES_OPTION) {
+	      Saver saver = ConverterUtils.getSaverForFile(filename);
+	      saver.setFile(new File(filename));
+	      saver.setInstances(dialog.getInstances());
+	      saver.writeBatch();
+	    }
+	  }
+	}
+	catch (Exception ex) {
+	  JOptionPane.showMessageDialog(
+	      this,
+	      "Error loading file '" + filename + "':\n" + ex.toString(),
+	      "Error loading file",
+	      JOptionPane.INFORMATION_MESSAGE);
+	}
+      }
+      setButtons(null);
+    } else if (e.getSource() == m_UpBut) {
+      JListHelper.moveUp(m_List);
+    } else if (e.getSource() == m_DownBut) {
+      JListHelper.moveDown(m_List);
+    }
+  }
+
+  /**
+   * Tests out the dataset list panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Dataset List Editor");
+      jf.getContentPane().setLayout(new BorderLayout());
+      DatasetListPanel dp = new DatasetListPanel();
+      jf.getContentPane().add(dp,
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      System.err.println("Short nap");
+      Thread.currentThread().sleep(3000);
+      System.err.println("Done");
+      dp.setExperiment(new Experiment());
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/DistributeExperimentPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/DistributeExperimentPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/DistributeExperimentPanel.java	(revision 29)
@@ -0,0 +1,244 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DistributeExperimentPanel.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.experiment.Experiment;
+import weka.experiment.RemoteExperiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+/** 
+ * This panel enables an experiment to be distributed to multiple hosts;
+ * it also allows remote host names to be specified.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public class DistributeExperimentPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5206721431754800278L;
+
+  /**
+   * The experiment to configure.
+   */
+  RemoteExperiment m_Exp = null;
+
+  /** Distribute the current experiment to remote hosts */
+  protected JCheckBox m_enableDistributedExperiment = 
+    new JCheckBox();
+
+  /** Popup the HostListPanel */
+  protected JButton m_configureHostNames = new JButton("Hosts");
+
+  /** The host list panel */
+  protected HostListPanel m_hostList = new HostListPanel();
+
+  /**
+   * Split experiment up by data set.
+   */
+  protected JRadioButton m_splitByDataSet = new JRadioButton("By data set");
+
+  /**
+   * Split experiment up by run number.
+   */
+  protected JRadioButton m_splitByRun = new JRadioButton("By run");
+
+  /** Handle radio buttons */
+  ActionListener m_radioListener = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	updateRadioLinks();
+      }
+    };
+  
+  /**
+   * Constructor
+   */
+  public DistributeExperimentPanel() {
+    m_enableDistributedExperiment.setSelected(false);
+    m_enableDistributedExperiment.
+      setToolTipText("Allow this experiment to be distributed to remote hosts");
+    m_enableDistributedExperiment.setEnabled(false);
+    m_configureHostNames.setEnabled(false);
+    m_configureHostNames.setToolTipText("Edit the list of remote hosts");
+    
+    m_enableDistributedExperiment.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_configureHostNames.setEnabled(m_enableDistributedExperiment.
+					  isSelected());
+	  m_splitByDataSet.setEnabled(m_enableDistributedExperiment.
+					  isSelected());
+	  m_splitByRun.setEnabled(m_enableDistributedExperiment.
+					  isSelected());
+	  
+	}
+      });
+
+    m_configureHostNames.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  popupHostPanel();
+	}
+      });
+
+    m_splitByDataSet.setToolTipText("Distribute experiment by data set");
+    m_splitByRun.setToolTipText("Distribute experiment by run number");
+    m_splitByDataSet.setSelected(true);
+    m_splitByDataSet.setEnabled(false);
+    m_splitByRun.setEnabled(false);
+    m_splitByDataSet.addActionListener(m_radioListener);
+    m_splitByRun.addActionListener(m_radioListener);
+
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_splitByDataSet);
+    bg.add(m_splitByRun);
+
+    JPanel rbuts = new JPanel();
+    rbuts.setLayout(new GridLayout(1, 2));
+    rbuts.add(m_splitByDataSet);
+    rbuts.add(m_splitByRun);
+
+    setLayout(new BorderLayout());
+    setBorder(BorderFactory.createTitledBorder("Distribute experiment"));
+    add(m_enableDistributedExperiment, BorderLayout.WEST);
+    add(m_configureHostNames, BorderLayout.CENTER);
+    add(rbuts, BorderLayout.SOUTH);
+  }
+
+  /**
+   * Creates the panel with the supplied initial experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public DistributeExperimentPanel(Experiment exp) {
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Sets the experiment to be configured.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+    
+    m_enableDistributedExperiment.setEnabled(true);
+    m_Exp = null;
+    if (exp instanceof RemoteExperiment) {
+      m_Exp = (RemoteExperiment)exp;
+      m_enableDistributedExperiment.setSelected(true);
+      m_configureHostNames.setEnabled(true);
+      m_hostList.setExperiment(m_Exp);
+      m_splitByDataSet.setEnabled(true);
+      m_splitByRun.setEnabled(true);
+      m_splitByDataSet.setSelected(m_Exp.getSplitByDataSet());
+      m_splitByRun.setSelected(!m_Exp.getSplitByDataSet());
+    }
+  }
+
+  /**
+   * Pop up the host list panel
+   */
+  private void popupHostPanel() {
+    try {
+      final JFrame jf = new JFrame("Edit host names");
+      
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(m_hostList,
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	  public void windowClosing(WindowEvent e) {
+	    jf.dispose();
+	  }
+	});
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+
+  /**
+   * Returns true if the distribute experiment checkbox is selected
+   * @return true if the user has opted for distributing the experiment
+   */
+  public boolean distributedExperimentSelected() {
+    return m_enableDistributedExperiment.isSelected();
+  }
+
+  /**
+   * Enable objects to listen for changes to the check box
+   * @param al an ActionListener
+   */
+  public void addCheckBoxActionListener(ActionListener al) {
+    m_enableDistributedExperiment.addActionListener(al);
+  }
+
+  /**
+   * Updates the remote experiment when a radio button is clicked
+   */
+  private void updateRadioLinks() {
+    if (m_Exp != null) {
+      m_Exp.setSplitByDataSet(m_splitByDataSet.isSelected());
+    }
+  }
+
+  /**
+   * Tests out the panel from the command line.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+    try {
+      final JFrame jf = new JFrame("DistributeExperiment");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(new DistributeExperimentPanel(new Experiment()),
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.java	(revision 29)
@@ -0,0 +1,176 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Experimenter.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.Memory;
+import weka.experiment.Experiment;
+import weka.gui.LookAndFeel;
+
+import java.awt.BorderLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+/** 
+ * The main class for the experiment environment. Lets the user create,
+ * open, save, configure, run experiments, and analyse experimental results.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 4721 $
+ */
+public class Experimenter
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5751617505738193788L;
+
+  /** The panel for configuring the experiment */
+  protected SetupModePanel m_SetupPanel;
+
+  /** The panel for running the experiment */
+  protected RunPanel m_RunPanel;
+
+  /** The panel for analysing experimental results */
+  protected ResultsPanel m_ResultsPanel;
+
+  /** The tabbed pane that controls which sub-pane we are working with */
+  protected JTabbedPane m_TabbedPane = new JTabbedPane();
+
+  /** True if the class attribute is the first attribute for all
+      datasets involved in this experiment. */
+  protected boolean m_ClassFirst = false;
+
+  /**
+   * Creates the experiment environment gui with no initial experiment
+   */
+  public Experimenter(boolean classFirst) {
+
+    m_SetupPanel = new SetupModePanel();
+    m_ResultsPanel = new ResultsPanel();
+    m_RunPanel = new RunPanel();
+    m_RunPanel.setResultsPanel(m_ResultsPanel);
+    
+    m_ClassFirst = classFirst;
+
+    m_TabbedPane.addTab("Setup", null, m_SetupPanel, "Set up the experiment");
+    m_TabbedPane.addTab("Run", null, m_RunPanel, "Run the experiment");
+    m_TabbedPane.addTab("Analyse", null, m_ResultsPanel,
+			"Analyse experiment results");
+    m_TabbedPane.setSelectedIndex(0);
+    m_TabbedPane.setEnabledAt(1, false);
+    m_SetupPanel.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	//System.err.println("Updated experiment");
+	Experiment exp = m_SetupPanel.getExperiment();
+	exp.classFirst(m_ClassFirst);
+	m_RunPanel.setExperiment(exp);
+	//m_ResultsPanel.setExperiment(exp);
+	m_TabbedPane.setEnabledAt(1, true);
+      }
+    });
+    setLayout(new BorderLayout());
+    add(m_TabbedPane, BorderLayout.CENTER);
+  }
+
+
+  /** variable for the Experimenter class which would be set to null by the memory 
+      monitoring thread to free up some memory if we running out of memory
+   */
+  private static Experimenter m_experimenter;
+
+  /** for monitoring the Memory consumption */
+  private static Memory m_Memory = new Memory(true);
+
+  /**
+   * Tests out the experiment environment.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");    
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      boolean classFirst = false;
+      if (args.length > 0) {
+	classFirst = args[0].equals("CLASS_FIRST");
+      }
+      m_experimenter = new Experimenter(classFirst);
+      final JFrame jf = new JFrame("Weka Experiment Environment");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(m_experimenter, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while(true) {
+            try {
+              this.sleep(4000);
+              
+              System.gc();
+
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                jf.dispose();
+                m_experimenter = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+
+                // display error
+                System.err.println("\ndisplayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nexiting");
+                System.exit(-1);
+              }
+
+            } catch(InterruptedException ex) { ex.printStackTrace(); }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.NORM_PRIORITY);
+      memMonitor.start();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/Experimenter.props	(revision 29)
@@ -0,0 +1,92 @@
+# This props file contains default values for the Weka Experimenter.
+# "simple" indicates that this property is only available for simple
+# experiments.
+#
+# $Revision: 5367 $
+
+# the default extension in the file-dialog
+# possible values: 
+#   .exp 
+#   .xml 
+#   .koml (if available)
+Extension=.exp
+
+# the default destination ("simple")
+# possible values:
+#   ARFF file
+#   CSV file
+#   JDBC database
+Destination=ARFF file
+
+# the experiment type ("simple")
+# possible values:
+#   Cross-validation
+#   Train/Test Percentage Split (data randomized)
+#   Train/Test Percentage Split (order preserved)
+ExperimentType=Cross-validation
+
+# whether classification is the default ("simple")
+UseClassification=true
+
+# the default number of CV folds ("simple")
+Folds=10
+
+# the default percentage for training ("simple")
+TrainPercentage=66
+
+# the default number of repetitions ("simple")
+Repetitions=10
+
+# whether datasets are first iterated ("simple")
+DatasetsFirst=true
+
+# the initial datasets directory
+# Note for Win32: the path backslashes have to written as "\\"
+InitialDatasetsDirectory=
+
+# whether to use relative paths
+UseRelativePaths=false
+
+# the default tester to use
+# possible values (at least):
+#   Paired T-Tester (corrected)
+#   Paired T-Tester
+Tester=Paired T-Tester (corrected)
+
+# the row selection
+Row=Key_Dataset
+
+# the column selection
+Column=Key_Scheme,Key_Scheme_options,Key_Scheme_version_ID
+
+# the default comparison field (lower case!)
+# see ComboBox in Experimenter
+ComparisonField=percent_correct
+
+# the default significance
+Significance=0.05
+
+# the default sorting
+Sorting=
+
+# whether stddevs are displayed by default
+ShowStdDev=false
+
+# whether the Average is displayed by default
+ShowAverage=false
+
+# the default precision for the mean
+MeanPrecision=2
+
+# the default precision for the stdev
+StdDevPrecision=2
+
+# the classname and options of the ResultMatrix, reponsible for the default 
+# output format.
+# NB: The other defaults listed in this props file override the "advanced" 
+#     options specified in this property.
+OutputFormat=weka.experiment.ResultMatrixPlainText -col-name-width 0 -row-name-width 25 -mean-width 0 -stddev-width 0 -sig-width 0 -count-width 5 -print-col-names -print-row-names -enum-col-names
+
+# whether filter classnames are removed by default
+RemoveFilterClassnames=false
+
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/ExperimenterDefaults.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/ExperimenterDefaults.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/ExperimenterDefaults.java	(revision 29)
@@ -0,0 +1,347 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ExperimenterDefaults.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.Utils;
+import weka.experiment.PairedCorrectedTTester;
+import weka.experiment.ResultMatrix;
+import weka.experiment.ResultMatrixPlainText;
+import weka.experiment.Tester;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * This class offers get methods for the default Experimenter settings in 
+ * the props file <code>weka/gui/experiment/Experimenter.props</code>.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5367 $
+ * @see #PROPERTY_FILE
+ */
+public class ExperimenterDefaults
+  implements Serializable {
+  
+  /** for serialization. */
+  static final long serialVersionUID = -2835933184632147981L;
+  
+  /** The name of the properties file. */
+  public final static String PROPERTY_FILE = "weka/gui/experiment/Experimenter.props";
+
+  /** Properties associated with the experimenter options. */
+  protected static Properties PROPERTIES;
+  static {
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+    }
+    catch (Exception e) {
+      System.err.println("Problem reading properties. Fix before continuing.");
+      e.printStackTrace();
+      PROPERTIES = new Properties();
+    }
+  }
+
+  /**
+   * returns the value for the specified property, if non-existent then the
+   * default value.
+   *
+   * @param property      the property to retrieve the value for
+   * @param defaultValue  the default value for the property
+   * @return              the value of the specified property
+   */
+  public static String get(String property, String defaultValue) {
+    return PROPERTIES.getProperty(property, defaultValue);
+  }
+
+  /**
+   * returns the associated properties file.
+   * 
+   * @return              the props file
+   */
+  public final static Properties getProperties() {
+    return PROPERTIES;
+  }
+
+  /**
+   * returns the default experiment extension.
+   * 
+   * @return              the extension (incl. dot)
+   */
+  public final static String getExtension() {
+    return get("Extension", ".exp");
+  }
+
+  /**
+   * returns the default destination.
+   * 
+   * @return              the destination
+   */
+  public final static String getDestination() {
+    return get("Destination", "ARFF file");
+  }
+
+  /**
+   * returns the default experiment type.
+   * 
+   * @return              the type
+   */
+  public final static String getExperimentType() {
+    return get("ExperimentType", "Cross-validation");
+  }
+
+  /**
+   * whether classification or regression is used.
+   * 
+   * @return              true if classification
+   */
+  public final static boolean getUseClassification() {
+    return Boolean.valueOf(get("UseClassification", "true")).booleanValue();
+  }
+
+  /**
+   * returns the number of folds used for cross-validation.
+   * 
+   * @return              the number of folds
+   */
+  public final static int getFolds() {
+    return Integer.parseInt(get("Folds", "10"));
+  }
+
+  /**
+   * returns the training percentage in case of splits.
+   * 
+   * @return              the percentage (0-100)
+   */
+  public final static double getTrainPercentage() {
+    return Integer.parseInt(get("TrainPercentage", "66"));
+  }
+
+  /**
+   * returns the number of repetitions to use.
+   * 
+   * @return              the repetitions/runs
+   */
+  public final static int getRepetitions() {
+    return Integer.parseInt(get("Repetitions", "10"));
+  }
+
+  /**
+   * whether datasets or algorithms are iterated first.
+   * 
+   * @return              true if datasets are iterated first
+   */
+  public final static boolean getDatasetsFirst() {
+    return Boolean.valueOf(get("DatasetsFirst", "true")).booleanValue();
+  }
+
+  /**
+   * returns the initial directory for the datasets (if empty, it returns
+   * the user's home directory).
+   * 
+   * @return              the directory
+   */
+  public final static File getInitialDatasetsDirectory() {
+    String    dir;
+    
+    dir = get("InitialDatasetsDirectory", "");
+    if (dir.equals(""))
+      dir = System.getProperty("user.dir");
+
+    return new File(dir);
+  }
+
+  /**
+   * whether relative paths are used by default.
+   * 
+   * @return              true if relative paths are used
+   */
+  public final static boolean getUseRelativePaths() {
+    return Boolean.valueOf(get("UseRelativePaths", "false")).booleanValue();
+  }
+
+  /**
+   * returns the display name of the preferred Tester algorithm.
+   * 
+   * @return              the display name
+   * @see Tester
+   * @see PairedCorrectedTTester
+   */
+  public final static String getTester() {
+    return get("Tester", new PairedCorrectedTTester().getDisplayName());
+  }
+
+  /**
+   * the comma-separated list of attribute names that identify a row.
+   * 
+   * @return              the attribute list
+   */
+  public final static String getRow() {
+    return get("Row", "Key_Dataset");
+  }
+
+  /**
+   * the comma-separated list of attribute names that identify a column.
+   * 
+   * @return              the attribute list
+   */
+  public final static String getColumn() {
+    return get("Column", "Key_Scheme,Key_Scheme_options,Key_Scheme_version_ID");
+  }
+
+  /**
+   * returns the name of the field used for comparison.
+   * 
+   * @return              the comparison field
+   */
+  public final static String getComparisonField() {
+    return get("ComparisonField", "percent_correct");
+  }
+
+  /**
+   * returns the default significance.
+   * 
+   * @return              the significance (0.0-1.0)
+   */
+  public final static double getSignificance() {
+    return Double.parseDouble(get("Significance", "0.05"));
+  }
+
+  /**
+   * returns the default sorting (empty string means none).
+   * 
+   * @return              the sorting field
+   */
+  public final static String getSorting() {
+    return get("Sorting", "");
+  }
+
+  /**
+   * returns whether StdDevs are shown by default.
+   * 
+   * @return              true if stddevs are shown
+   */
+  public final static boolean getShowStdDevs() {
+    return Boolean.valueOf(get("ShowStdDev", "false")).booleanValue();
+  }
+
+  /**
+   * returns whether the Average is shown by default.
+   * 
+   * @return              true if the average is shown
+   */
+  public final static boolean getShowAverage() {
+    return Boolean.valueOf(get("ShowAverage", "false")).booleanValue();
+  }
+
+  /**
+   * returns the default precision for the mean.
+   * 
+   * @return              the decimals of the mean
+   */
+  public final static int getMeanPrecision() {
+    return Integer.parseInt(get("MeanPrecision", "2"));
+  }
+
+  /**
+   * returns the default precision for the stddevs.
+   * 
+   * @return              the decimals of the stddevs
+   */
+  public final static int getStdDevPrecision() {
+    return Integer.parseInt(get("StdDevPrecision", "2"));
+  }
+
+  /**
+   * returns the classname (and optional options) of the ResultMatrix class, 
+   * responsible for the output format.
+   * 
+   * @return              the classname and options
+   * @see ResultMatrix
+   * @see ResultMatrixPlainText
+   */
+  public final static ResultMatrix getOutputFormat() {
+    ResultMatrix	result;
+    
+    try {
+      String[] options = Utils.splitOptions(get("OutputFormat", ResultMatrix.class.getName() + " -col-name-width 0 -row-name-width 25 -mean-width 0 -stddev-width 0 -sig-width 0 -count-width 5 -print-col-names -print-row-names -enum-col-names"));
+      String classname = options[0];
+      options[0]       = "";
+      result           = (ResultMatrix) Utils.forName(ResultMatrix.class, classname, options);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = new ResultMatrixPlainText();
+    }
+    
+    // override with other default properties
+    result.setMeanPrec(getMeanPrecision());
+    result.setStdDevPrec(getStdDevPrecision());
+    result.setShowAverage(getShowAverage());
+    result.setShowStdDev(getShowStdDevs());
+    result.setRemoveFilterName(getRemoveFilterClassnames());
+
+    return result;
+  }
+
+  /**
+   * whether the filter classnames in the dataset names are removed by default.
+   * 
+   * @return              true if filter names are removed
+   */
+  public final static boolean getRemoveFilterClassnames() {
+    return Boolean.valueOf(get("RemoveFilterClassnames", "false")).booleanValue();
+  }
+  
+  /**
+   * only for testing - prints the content of the props file.
+   * 
+   * @param args	commandline parameters - ignored
+   */
+  public static void main(String[] args) {
+    Enumeration		names;
+    String		name;
+    Vector		sorted;
+    
+    System.out.println("\nExperimenter defaults:");
+    names = PROPERTIES.propertyNames();
+
+    // sort names
+    sorted = new Vector();
+    while (names.hasMoreElements())
+      sorted.add(names.nextElement());
+    Collections.sort(sorted);
+    names = sorted.elements();
+    
+    // output
+    while (names.hasMoreElements()) {
+      name = names.nextElement().toString();
+      System.out.println("- " + name + ": " + PROPERTIES.getProperty(name, ""));
+    }
+    System.out.println();
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/GeneratorPropertyIteratorPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/GeneratorPropertyIteratorPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/GeneratorPropertyIteratorPanel.java	(revision 29)
@@ -0,0 +1,280 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GeneratorPropertyIteratorPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.FastVector;
+import weka.experiment.Experiment;
+import weka.experiment.PropertyNode;
+import weka.gui.GenericArrayEditor;
+import weka.gui.PropertySelectorDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+
+import javax.swing.BorderFactory;
+import javax.swing.ComboBoxModel;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/** 
+ * This panel controls setting a list of values for an arbitrary
+ * resultgenerator property for an experiment to iterate over.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.10 $
+ */
+public class GeneratorPropertyIteratorPanel
+  extends JPanel
+  implements ActionListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6026938995241632139L;
+
+  /** Click to select the property to iterate over */
+  protected JButton m_ConfigureBut = new JButton("Select property...");
+
+  /** Controls whether the custom iterator is used or not */
+  protected JComboBox m_StatusBox = new JComboBox();
+
+  /** Allows editing of the custom property values */
+  protected GenericArrayEditor m_ArrayEditor = new GenericArrayEditor();
+
+  /** The experiment this all applies to */
+  protected Experiment m_Exp;
+
+  /** Listeners who want to be notified about editing status of this
+      panel */
+  protected FastVector m_Listeners = new FastVector();
+  
+  /**
+   * Creates the property iterator panel initially disabled.
+   */
+  public GeneratorPropertyIteratorPanel() {
+
+    String [] options = {"Disabled", "Enabled"};
+    ComboBoxModel cbm = new DefaultComboBoxModel(options);
+    m_StatusBox.setModel(cbm);
+    m_StatusBox.setSelectedIndex(0);
+    m_StatusBox.addActionListener(this);
+    m_StatusBox.setEnabled(false);
+    m_ConfigureBut.setEnabled(false);
+    m_ConfigureBut.addActionListener(this);
+    JPanel buttons = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    buttons.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    //    buttons.setLayout(new GridLayout(1, 2));
+    buttons.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    buttons.add(m_StatusBox,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    buttons.add(m_ConfigureBut,constraints);
+    buttons.setMaximumSize(new Dimension(buttons.getMaximumSize().width,
+					   buttons.getMinimumSize().height));
+    setBorder(BorderFactory.createTitledBorder("Generator properties"));
+    setLayout(new BorderLayout());
+    add(buttons, BorderLayout.NORTH);
+    //    add(Box.createHorizontalGlue());
+    m_ArrayEditor.setBorder(BorderFactory.createEtchedBorder());
+    m_ArrayEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	System.err.println("Updating experiment property iterator array");
+	m_Exp.setPropertyArray(m_ArrayEditor.getValue());
+      }
+    });
+    add(m_ArrayEditor, BorderLayout.CENTER);
+  }
+
+  /**
+   * Creates the property iterator panel and sets the experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public GeneratorPropertyIteratorPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Returns true if the editor is currently in an active status---that
+   * is the array is active and able to be edited.
+   * @return true if editor is active
+   */
+  public boolean getEditorActive() {
+    if (m_StatusBox.getSelectedIndex() == 0) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Sets the experiment which will have the custom properties edited.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+
+    m_Exp = exp;
+    m_StatusBox.setEnabled(true);
+    m_ArrayEditor.setValue(m_Exp.getPropertyArray());
+    if (m_Exp.getPropertyArray() == null) {
+      m_StatusBox.setSelectedIndex(0);
+      m_ConfigureBut.setEnabled(false);
+    } else {
+      m_StatusBox.setSelectedIndex(m_Exp.getUsePropertyIterator() ? 1 : 0);
+      m_ConfigureBut.setEnabled(m_Exp.getUsePropertyIterator());
+    }
+    validate();
+  }
+
+  /**
+   * Gets the user to select a property of the current resultproducer.
+   *
+   * @return APPROVE_OPTION if the selection went OK, otherwise the selection
+   * was cancelled.
+   */
+  protected int selectProperty() {
+    
+    final PropertySelectorDialog jd = new PropertySelectorDialog(null,
+					  m_Exp.getResultProducer());
+    jd.setLocationRelativeTo(this);
+    int result = jd.showDialog();
+    if (result == PropertySelectorDialog.APPROVE_OPTION) {
+      System.err.println("Property Selected");
+      PropertyNode [] path = jd.getPath();
+      Object value = path[path.length - 1].value;
+      PropertyDescriptor property = path[path.length - 1].property;
+      // Make an array containing the propertyValue
+      Class propertyClass = property.getPropertyType();
+      m_Exp.setPropertyPath(path);
+      m_Exp.setPropertyArray(Array.newInstance(propertyClass, 1));
+      Array.set(m_Exp.getPropertyArray(), 0, value);	
+      // Pass it to the arrayeditor
+      m_ArrayEditor.setValue(m_Exp.getPropertyArray());
+      m_ArrayEditor.repaint();
+      System.err.println("Set new array to array editor");
+    } else {
+      System.err.println("Cancelled");
+    }
+    return result;
+  }
+
+  /**
+   * Handles the various button clicking type activities.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+
+    if (e.getSource() == m_ConfigureBut) {
+      selectProperty();
+    } else if (e.getSource() == m_StatusBox) {
+      // notify any listeners
+      for (int i = 0; i < m_Listeners.size(); i++) {
+	ActionListener temp = ((ActionListener)m_Listeners.elementAt(i));
+	temp.actionPerformed(new ActionEvent(this, 
+					     ActionEvent.ACTION_PERFORMED, 
+					     "Editor status change"));
+      }
+
+      // Toggles whether the custom property is used
+      if (m_StatusBox.getSelectedIndex() == 0) {
+	m_Exp.setUsePropertyIterator(false);
+	m_ConfigureBut.setEnabled(false);
+	m_ArrayEditor.setEnabled(false);
+	m_ArrayEditor.setValue(null);
+	validate();
+      } else {
+	if (m_Exp.getPropertyArray() == null) {
+	  selectProperty();
+	}
+	if (m_Exp.getPropertyArray() == null) {
+	  m_StatusBox.setSelectedIndex(0);
+	} else {
+	  m_Exp.setUsePropertyIterator(true);
+	  m_ConfigureBut.setEnabled(true);
+	  m_ArrayEditor.setEnabled(true);
+	}
+	validate();
+      }
+    }
+  }
+
+  /**
+   * Add a listener interested in kowing about editor status changes
+   * @param newA an listener to add
+   */
+  public void addActionListener(ActionListener newA) {
+    m_Listeners.addElement(newA);
+  }
+
+  /**
+   * Tests out the panel from the command line.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Generator Property Iterator");
+      jf.getContentPane().setLayout(new BorderLayout());
+      GeneratorPropertyIteratorPanel gp = new GeneratorPropertyIteratorPanel();
+      jf.getContentPane().add(gp, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      System.err.println("Short nap");
+      Thread.currentThread().sleep(3000);
+      System.err.println("Done");
+      gp.setExperiment(new Experiment());
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/HostListPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/HostListPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/HostListPanel.java	(revision 29)
@@ -0,0 +1,186 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HostListPanel.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.experiment.RemoteExperiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
+/** 
+ * This panel controls setting a list of hosts for a RemoteExperiment to
+ * use.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class HostListPanel
+  extends JPanel
+  implements ActionListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7182791134585882197L;
+  
+  /** The remote experiment to set the host list of */
+  protected RemoteExperiment m_Exp;
+
+ /** The component displaying the host list */
+  protected JList m_List;
+
+  /** Click to remove the selected host from the list */
+  protected JButton m_DeleteBut = new JButton("Delete selected");
+
+  /** The field with which to enter host names */
+  protected JTextField m_HostField = new JTextField(25);
+
+  /**
+   * Creates the host list panel with the given experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public HostListPanel(RemoteExperiment exp) {
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Create the host list panel initially disabled.
+   */
+  public HostListPanel() {
+    m_List = new JList();
+    m_List.setModel(new DefaultListModel());
+    m_DeleteBut.setEnabled(false);
+    m_DeleteBut.addActionListener(this);
+    m_HostField.addActionListener(this);
+    setLayout(new BorderLayout());
+    setBorder(BorderFactory.createTitledBorder("Hosts"));
+
+    JPanel topLab = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    topLab.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    //    topLab.setLayout(new GridLayout(1,2,5,5));
+    topLab.setLayout(gb);
+   
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    topLab.add(m_DeleteBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    topLab.add(m_HostField,constraints);
+    
+    add(topLab, BorderLayout.NORTH);
+    add(new JScrollPane(m_List), BorderLayout.CENTER);
+  }
+
+  /**
+   * Tells the panel to act on a new experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(RemoteExperiment exp) {
+    m_Exp = exp;
+    m_List.setModel(m_Exp.getRemoteHosts());
+    if (((DefaultListModel)m_List.getModel()).size() > 0) {
+      m_DeleteBut.setEnabled(true);
+    }
+  }
+
+  /**
+   * Handle actions when text is entered into the host field or the
+   * delete button is pressed.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+    if (e.getSource() == m_HostField) {
+      ((DefaultListModel)m_List.getModel())
+	.addElement(m_HostField.getText());
+      m_DeleteBut.setEnabled(true);
+    } else if (e.getSource() == m_DeleteBut) {
+      int [] selected = m_List.getSelectedIndices();
+      if (selected != null) {
+	for (int i = selected.length - 1; i >= 0; i--) {
+	  int current = selected[i];
+	  ((DefaultListModel)m_List.getModel()).removeElementAt(current);
+	  if (((DefaultListModel)m_List.getModel()).size() > current) {
+	    m_List.setSelectedIndex(current);
+	  } else {
+	    m_List.setSelectedIndex(current - 1);
+	  }
+	}
+      }
+      if (((DefaultListModel)m_List.getModel()).size() == 0) {
+	m_DeleteBut.setEnabled(false);
+      }
+    }
+  }
+
+  /**
+   * Tests out the host list panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Host List Editor");
+      jf.getContentPane().setLayout(new BorderLayout());
+      HostListPanel dp = new HostListPanel();
+      jf.getContentPane().add(dp,
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      /* System.err.println("Short nap");
+      Thread.currentThread().sleep(3000);
+      System.err.println("Done"); */
+      //      dp.setExperiment(new Experiment());
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/OutputFormatDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/OutputFormatDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/OutputFormatDialog.java	(revision 29)
@@ -0,0 +1,486 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    OutputFormatDialog.java
+ *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.experiment.ResultMatrix;
+import weka.experiment.ResultMatrixPlainText;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertyPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/** 
+ * A dialog for setting various output format parameters.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5346 $
+ */
+public class OutputFormatDialog
+  extends JDialog {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 2169792738187807378L;
+
+  /** Signifies an OK property selection. */
+  public static final int APPROVE_OPTION = 0;
+
+  /** Signifies a cancelled property selection. */
+  public static final int CANCEL_OPTION = 1;
+
+  /** the result of the user's action, either OK or CANCEL. */
+  protected int m_Result;
+  
+  /** the different classes for outputting the comparison tables. */
+  protected static Vector<Class> m_OutputFormatClasses;
+  
+  /** the different names of matrices for outputting the comparison tables. */
+  protected static Vector<String> m_OutputFormatNames;
+  
+  /** Lets the user configure the result matrix. */
+  protected GenericObjectEditor m_ResultMatrixEditor;
+
+  /** the panel for the GOE. */
+  protected PropertyPanel m_ResultMatrixPanel;
+
+  /** the label for the GOE. */
+  protected JLabel m_ResultMatrixLabel;
+  
+  /** the current result matrix. */
+  protected ResultMatrix m_ResultMatrix;
+
+  /** lets the user choose the format for the output. */
+  protected JComboBox m_OutputFormatComboBox;
+
+  /** the label for the format. */
+  protected JLabel m_OutputFormatLabel;
+
+  /** the spinner to choose the precision for the mean from. */
+  protected JSpinner m_MeanPrecSpinner;
+
+  /** the label for the mean precision. */
+  protected JLabel m_MeanPrecLabel;
+
+  /** the spinner to choose the precision for the std. deviation from */
+  protected JSpinner m_StdDevPrecSpinner;
+
+  /** the label for the std dev precision. */
+  protected JLabel m_StdDevPrecLabel;
+
+  /** the checkbox for outputting the average. */
+  protected JCheckBox m_ShowAverageCheckBox;
+
+  /** the label for showing the average. */
+  protected JLabel m_ShowAverageLabel;
+
+  /** the checkbox for the removing of filter classnames. */
+  protected JCheckBox m_RemoveFilterNameCheckBox;
+
+  /** the label for the removing the filter classnames. */
+  protected JLabel m_RemoveFilterNameLabel;
+  
+  /** Click to activate the current set parameters. */
+  protected JButton m_OkButton;
+
+  /** Click to cancel the dialog. */
+  protected JButton m_CancelButton;
+
+  /** whether to ignore updates in the GUI momentarily. */
+  protected boolean m_IgnoreChanges;
+  
+  /**
+   * initializes the dialog with the given parent frame.
+   * 
+   * @param parent the parent of this dialog
+   */
+  public OutputFormatDialog(Frame parent) {
+    super(parent, "Output Format...", true);
+
+    m_IgnoreChanges = true;
+    
+    initialize();
+    initGUI();
+
+    m_IgnoreChanges = false;
+  }
+  
+  /**
+   * initializes the member variables.
+   */
+  protected void initialize() {
+    Vector 		classes;
+    int			i;
+    Class 		cls;
+    ResultMatrix 	matrix;
+    
+    m_Result = CANCEL_OPTION;
+
+    if (m_OutputFormatClasses == null) {
+      classes = GenericObjectEditor.getClassnames(ResultMatrix.class.getName());
+
+      // set names and classes
+      m_OutputFormatClasses = new Vector<Class>();
+      m_OutputFormatNames   = new Vector<String>();
+      for (i = 0; i < classes.size(); i++) {
+        try {
+          cls    = Class.forName(classes.get(i).toString());
+          matrix = (ResultMatrix) cls.newInstance();
+          m_OutputFormatClasses.add(cls);
+          m_OutputFormatNames.add(matrix.getDisplayName());
+        }
+        catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+  
+  /**
+   * performs the creation of the dialog and all its components.
+   */
+  protected void initGUI() {
+    JPanel              panel;
+    SpinnerNumberModel  model;
+    JPanel		panel2;
+    
+    getContentPane().setLayout(new BorderLayout());
+    
+    panel = new JPanel(new GridLayout(6, 1));
+    panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    getContentPane().add(panel, BorderLayout.CENTER);
+    
+    // mean precision
+    m_MeanPrecSpinner = new JSpinner();
+    m_MeanPrecSpinner.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+        getData();
+      }
+    });
+    model = (SpinnerNumberModel) m_MeanPrecSpinner.getModel();
+    model.setMaximum(new Integer(20));
+    model.setMinimum(new Integer(0));
+    m_MeanPrecLabel = new JLabel("Mean Precision");
+    m_MeanPrecLabel.setDisplayedMnemonic('M');
+    m_MeanPrecLabel.setLabelFor(m_MeanPrecSpinner);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_MeanPrecLabel);
+    panel2.add(m_MeanPrecSpinner);
+    panel.add(panel2);
+    
+    // stddev precision
+    m_StdDevPrecSpinner = new JSpinner();
+    m_StdDevPrecSpinner.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+        getData();
+      }
+    });
+    model = (SpinnerNumberModel) m_StdDevPrecSpinner.getModel();
+    model.setMaximum(new Integer(20));
+    model.setMinimum(new Integer(0));
+    m_StdDevPrecLabel = new JLabel("StdDev. Precision");
+    m_StdDevPrecLabel.setDisplayedMnemonic('S');
+    m_StdDevPrecLabel.setLabelFor(m_StdDevPrecSpinner);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_StdDevPrecLabel);
+    panel2.add(m_StdDevPrecSpinner);
+    panel.add(panel2);
+    
+    // Format
+    m_OutputFormatComboBox = new JComboBox(m_OutputFormatNames);
+    m_OutputFormatComboBox.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	getData();
+      }
+    });
+    m_OutputFormatLabel = new JLabel("Output Format");
+    m_OutputFormatLabel.setDisplayedMnemonic('F');
+    m_OutputFormatLabel.setLabelFor(m_OutputFormatComboBox);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_OutputFormatLabel);
+    panel2.add(m_OutputFormatComboBox);
+    panel.add(panel2);
+
+    // Average
+    m_ShowAverageCheckBox = new JCheckBox("");
+    m_ShowAverageCheckBox.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+	getData();
+      }
+    });
+    m_ShowAverageLabel = new JLabel("Show Average");
+    m_ShowAverageLabel.setDisplayedMnemonic('A');
+    m_ShowAverageLabel.setLabelFor(m_ShowAverageCheckBox);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_ShowAverageLabel);
+    panel2.add(m_ShowAverageCheckBox);
+    panel.add(panel2);
+
+    // Remove filter classname
+    m_RemoveFilterNameCheckBox = new JCheckBox("");
+    m_RemoveFilterNameCheckBox.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+	getData();
+      }
+    });
+    m_RemoveFilterNameLabel = new JLabel("Remove filter classnames");
+    m_RemoveFilterNameLabel.setDisplayedMnemonic('R');
+    m_RemoveFilterNameLabel.setLabelFor(m_RemoveFilterNameCheckBox);
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_RemoveFilterNameLabel);
+    panel2.add(m_RemoveFilterNameCheckBox);
+    panel.add(panel2);
+
+    // Advanced setup
+    m_ResultMatrix       = ExperimenterDefaults.getOutputFormat();
+    m_ResultMatrixEditor = new GenericObjectEditor(true);
+    m_ResultMatrixEditor.setClassType(ResultMatrix.class);
+    m_ResultMatrixEditor.setValue(m_ResultMatrix);
+    m_ResultMatrixEditor.addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+	  // user selected different class?
+	  if (!m_ResultMatrix.getClass().equals(m_ResultMatrixEditor.getValue().getClass())) {
+	    // if it's the preferred class, then automaticallly use the Experimenter defaults
+	    if (m_ResultMatrixEditor.getValue().getClass().equals(ExperimenterDefaults.getOutputFormat().getClass())) {
+	      m_ResultMatrix = ExperimenterDefaults.getOutputFormat();
+	      m_ResultMatrixEditor.setValue(ExperimenterDefaults.getOutputFormat());
+	    }
+	    else {
+	      m_ResultMatrix = (ResultMatrix) m_ResultMatrixEditor.getValue();
+	    }
+	    setData();
+	  }
+	  repaint();
+	}
+      });
+    ((GenericObjectEditor.GOEPanel) m_ResultMatrixEditor.getCustomEditor()).addOkListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+          m_ResultMatrix = (ResultMatrix) m_ResultMatrixEditor.getValue();
+          setData();
+	}
+      });
+    m_ResultMatrixPanel = new PropertyPanel(m_ResultMatrixEditor, false);
+    m_ResultMatrixLabel = new JLabel("Advanced setup");
+    panel2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(m_ResultMatrixLabel);
+    panel2.add(m_ResultMatrixPanel);
+    panel.add(panel2);
+    
+    // Buttons
+    panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+    getContentPane().add(panel, BorderLayout.SOUTH);
+    m_CancelButton = new JButton("Cancel");
+    m_CancelButton.setMnemonic('C');
+    m_CancelButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_Result = CANCEL_OPTION;
+        setVisible(false);
+      }
+    });
+    m_OkButton = new JButton("OK");
+    m_OkButton.setMnemonic('O');
+    m_OkButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        getData();
+        m_Result = APPROVE_OPTION;
+        setVisible(false);
+      }
+    });
+    panel.add(m_OkButton);
+    panel.add(m_CancelButton);
+
+    // default button
+    getRootPane().setDefaultButton(m_OkButton);
+    
+    // initial layout (to get widths and heights)
+    pack();
+    
+    // adjust dimensions
+    m_MeanPrecLabel.setPreferredSize(new Dimension(m_RemoveFilterNameLabel.getWidth(), m_MeanPrecLabel.getHeight()));
+    m_MeanPrecSpinner.setPreferredSize(new Dimension(m_MeanPrecSpinner.getWidth() * 3, m_MeanPrecSpinner.getHeight()));
+    m_StdDevPrecLabel.setPreferredSize(new Dimension(m_RemoveFilterNameLabel.getWidth(), m_StdDevPrecLabel.getHeight()));
+    m_StdDevPrecSpinner.setPreferredSize(new Dimension(m_StdDevPrecSpinner.getWidth() * 3, m_StdDevPrecSpinner.getHeight()));
+    m_OutputFormatLabel.setPreferredSize(new Dimension(m_RemoveFilterNameLabel.getWidth(), m_OutputFormatLabel.getHeight()));
+    m_ShowAverageLabel.setPreferredSize(new Dimension(m_RemoveFilterNameLabel.getWidth(), m_ShowAverageLabel.getHeight()));
+    m_ResultMatrixLabel.setPreferredSize(new Dimension(m_RemoveFilterNameLabel.getWidth(), m_ResultMatrixLabel.getHeight()));
+    m_ResultMatrixPanel.setPreferredSize(new Dimension((int) (m_ResultMatrixPanel.getWidth() * 1.5), m_ResultMatrixPanel.getHeight()));
+    
+    // final layout
+    pack();
+  }
+  
+  /**
+   * initializes the GUI components with the data.
+   */
+  private void setData() {
+    m_IgnoreChanges = true;
+    
+    // Precision
+    m_MeanPrecSpinner.setValue(m_ResultMatrix.getMeanPrec());
+    m_StdDevPrecSpinner.setValue(m_ResultMatrix.getStdDevPrec());
+    
+    // format
+    for (int i = 0; i < m_OutputFormatClasses.size(); i++) {
+      if (m_OutputFormatClasses.get(i).equals(m_ResultMatrix.getClass())) {
+	m_OutputFormatComboBox.setSelectedItem(m_OutputFormatNames.get(i));
+        break;
+      }
+    }
+
+    // average
+    m_ShowAverageCheckBox.setSelected(m_ResultMatrix.getShowAverage());
+
+    // filter names
+    m_RemoveFilterNameCheckBox.setSelected(m_ResultMatrix.getRemoveFilterName());
+
+    // GOE
+    m_ResultMatrixEditor.setValue(m_ResultMatrix);
+    
+    m_IgnoreChanges = false;
+  }    
+  
+  /**
+   *  gets the data from GUI components.
+   */
+  private void getData() {
+    if (m_IgnoreChanges)
+      return;
+    
+    // format
+    try {
+      if (!m_ResultMatrix.getClass().equals(m_OutputFormatClasses.get(m_OutputFormatComboBox.getSelectedIndex()))) {
+	if (m_OutputFormatClasses.get(m_OutputFormatComboBox.getSelectedIndex()).equals(ExperimenterDefaults.getOutputFormat().getClass()))
+	  m_ResultMatrix = ExperimenterDefaults.getOutputFormat();
+	else
+	  m_ResultMatrix = (ResultMatrix) ((Class) m_OutputFormatClasses.get(m_OutputFormatComboBox.getSelectedIndex())).newInstance();
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      m_ResultMatrix = new ResultMatrixPlainText();
+    }
+    
+    // Precision
+    m_ResultMatrix.setMeanPrec(Integer.parseInt(m_MeanPrecSpinner.getValue().toString()));
+    m_ResultMatrix.setStdDevPrec(Integer.parseInt(m_StdDevPrecSpinner.getValue().toString()));
+
+    // average
+    m_ResultMatrix.setShowAverage(m_ShowAverageCheckBox.isSelected());
+
+    // filter names
+    m_ResultMatrix.setRemoveFilterName(m_RemoveFilterNameCheckBox.isSelected());
+    
+    // update GOE
+    m_ResultMatrixEditor.setValue(m_ResultMatrix);
+  }
+
+  /**
+   * Sets the matrix to use as initial selected output format.
+   * 
+   * @param matrix the matrix to use as initial selected output format
+   */
+  public void setResultMatrix(ResultMatrix matrix) {
+    m_ResultMatrix = matrix;
+    setData();
+  }
+
+  /**
+   * Gets the currently selected output format result matrix.
+   * 
+   * @return the currently selected matrix to use as output
+   */
+  public ResultMatrix getResultMatrix() {
+    return m_ResultMatrix;
+  }
+
+  /**
+   * sets the class of the chosen result matrix.
+   */
+  protected void setFormat() {
+    for (int i = 0; i < m_OutputFormatClasses.size(); i++) {
+      if (m_OutputFormatNames.get(i).equals(
+            m_OutputFormatComboBox.getItemAt(i).toString())) {
+        m_OutputFormatComboBox.setSelectedIndex(i);
+        break;
+      }
+    }
+  }
+  
+  /**
+   * the result from the last display of the dialog, the same is returned
+   * from <code>showDialog</code>.
+   * 
+   * @return the result from the last display of the dialog; 
+   *         either APPROVE_OPTION, or CANCEL_OPTION
+   * @see #showDialog()
+   */
+  public int getResult() {
+    return m_Result;
+  }
+
+  /**
+   * Pops up the modal dialog and waits for cancel or a selection.
+   *
+   * @return either APPROVE_OPTION, or CANCEL_OPTION
+   */
+  public int showDialog() {
+    m_Result = CANCEL_OPTION;
+    setData();
+    setVisible(true);
+    return m_Result;
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args ignored
+   */
+  public static void main(String[] args) {
+    OutputFormatDialog      dialog;
+    
+    dialog = new OutputFormatDialog(null);
+    if (dialog.showDialog() == APPROVE_OPTION)
+      System.out.println("Accepted");
+    else
+      System.out.println("Aborted");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/ResultsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/ResultsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/ResultsPanel.java	(revision 29)
@@ -0,0 +1,1355 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ResultsPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import weka.core.Attribute;
+import weka.core.ClassDiscovery;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Range;
+import weka.core.converters.CSVLoader;
+import weka.experiment.CSVResultListener;
+import weka.experiment.DatabaseResultListener;
+import weka.experiment.Experiment;
+import weka.experiment.InstanceQuery;
+import weka.experiment.PairedCorrectedTTester;
+import weka.experiment.ResultMatrix;
+import weka.experiment.ResultMatrixPlainText;
+import weka.experiment.Tester;
+import weka.gui.DatabaseConnectionDialog;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.ListSelectorDialog;
+import weka.gui.PropertyDialog;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+
+/** 
+ * This panel controls simple analysis of experimental results.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 6087 $
+ */
+public class ResultsPanel
+  extends JPanel {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4913007978534178569L;
+
+  /** Message shown when no experimental results have been loaded. */
+  protected static final String NO_SOURCE = "No source";
+
+  /** Click to load results from a file. */
+  protected JButton m_FromFileBut = new JButton("File...");
+
+  /** Click to load results from a database. */
+  protected JButton m_FromDBaseBut = new JButton("Database...");
+
+  /** Click to get results from the destination given in the experiment. */
+  protected JButton m_FromExpBut = new JButton("Experiment");
+
+  /** Displays a message about the current result set. */
+  protected JLabel m_FromLab = new JLabel(NO_SOURCE);
+
+  /**
+   * This is needed to get around a bug in Swing <= 1.1 -- Once everyone
+   * is using Swing 1.1.1 or higher just remove this variable and use the
+   * no-arg constructor to DefaultComboBoxModel.
+   */
+  private static String [] FOR_JFC_1_1_DCBM_BUG = {""};
+
+  /** The model embedded in m_DatasetCombo. */
+  protected DefaultComboBoxModel m_DatasetModel =
+    new DefaultComboBoxModel(FOR_JFC_1_1_DCBM_BUG);
+  
+  /** The model embedded in m_CompareCombo. */
+  protected DefaultComboBoxModel m_CompareModel = 
+    new DefaultComboBoxModel(FOR_JFC_1_1_DCBM_BUG);
+  
+  /** The model embedded in m_SortCombo. */
+  protected DefaultComboBoxModel m_SortModel = 
+    new DefaultComboBoxModel(FOR_JFC_1_1_DCBM_BUG);
+  
+  /** The model embedded in m_TestsList. */
+  protected DefaultListModel m_TestsModel = new DefaultListModel();
+  
+  /** The model embedded in m_DisplayedList. */
+  protected DefaultListModel m_DisplayedModel = new DefaultListModel();
+
+  /** Displays the currently selected Tester-Class. */
+  protected JLabel m_TesterClassesLabel = new JLabel("Testing with",
+						 SwingConstants.RIGHT);
+  
+  /** Contains all the available classes implementing the Tester-Interface 
+   * (the display names).
+   * @see Tester */
+  protected static DefaultComboBoxModel m_TesterClassesModel = 
+    new DefaultComboBoxModel(FOR_JFC_1_1_DCBM_BUG);
+
+  /** Contains all the available classes implementing the Tester-Interface
+   * (the actual Classes).
+   * @see Tester */
+  protected static Vector m_Testers = null;
+  
+  /** determine all classes implementing the Tester interface (in the same
+   * package!).
+   * @see Tester
+   * @see ClassDiscovery */
+  static {
+    Vector classes = GenericObjectEditor.getClassnames(Tester.class.getName());
+
+    // set names and classes
+    m_Testers            = new Vector();
+    m_TesterClassesModel = new DefaultComboBoxModel();
+    for (int i = 0; i < classes.size(); i++) {
+      try {
+        Class cls = Class.forName(classes.get(i).toString());
+        Tester tester = (Tester) cls.newInstance();
+        m_Testers.add(cls);
+        m_TesterClassesModel.addElement(tester.getDisplayName());
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  /** Lists all the available classes implementing the Tester-Interface.
+   * @see Tester */
+  protected JComboBox m_TesterClasses = 
+    new JComboBox(m_TesterClassesModel);
+
+  /** Label for the dataset and result key buttons. */
+  protected JLabel m_DatasetAndResultKeysLabel = new JLabel("Select rows and cols",
+						 SwingConstants.RIGHT);
+
+  /** the panel encapsulating the Rows/Columns/Swap buttons. */
+  protected JPanel m_PanelDatasetResultKeys = new JPanel(new GridLayout(1, 3));
+  
+  /** Click to edit the columns used to determine the scheme. */
+  protected JButton m_DatasetKeyBut = new JButton("Rows");
+
+  /** Stores the list of attributes for selecting the scheme columns. */
+  protected DefaultListModel m_DatasetKeyModel = new DefaultListModel();
+
+  /** Displays the list of selected columns determining the scheme. */
+  protected JList m_DatasetKeyList = new JList(m_DatasetKeyModel);
+
+  /** Click to edit the columns used to determine the scheme. */
+  protected JButton m_ResultKeyBut = new JButton("Cols");
+
+  /** For swapping rows and columns. */
+  protected JButton m_SwapDatasetKeyAndResultKeyBut = new JButton("Swap");
+
+  /** Stores the list of attributes for selecting the scheme columns. */
+  protected DefaultListModel m_ResultKeyModel = new DefaultListModel();
+
+  /** Displays the list of selected columns determining the scheme. */
+  protected JList m_ResultKeyList = new JList(m_ResultKeyModel);
+
+  /** Lets the user select which scheme to base comparisons against. */
+  protected JButton m_TestsButton = new JButton("Select");
+
+  /** Lets the user select which schemes are compared to base. */
+  protected JButton m_DisplayedButton = new JButton("Select");
+
+  /** Holds the list of schemes to base the test against. */
+  protected JList m_TestsList = new JList(m_TestsModel);
+
+  /** Holds the list of schemes to display. */
+  protected JList m_DisplayedList = new JList(m_DisplayedModel);
+
+  /** Lets the user select which performance measure to analyze. */
+  protected JComboBox m_CompareCombo = new JComboBox(m_CompareModel);
+
+  /** Lets the user select which column to use for sorting. */
+  protected JComboBox m_SortCombo = new JComboBox(m_SortModel);
+
+  /** Lets the user edit the test significance. */
+  protected JTextField m_SigTex = new JTextField(
+      "" + ExperimenterDefaults.getSignificance());
+
+  /** Lets the user select whether standard deviations are to be output
+      or not. */
+  protected JCheckBox m_ShowStdDevs = 
+    new JCheckBox("");
+  
+  /** lets the user choose the format for the output. */
+  protected JButton m_OutputFormatButton = new JButton("Select");
+
+  /** Click to start the test. */
+  protected JButton m_PerformBut = new JButton("Perform test");
+  
+  /** Click to save test output to a file. */
+  protected JButton m_SaveOutBut = new JButton("Save output");
+
+  /** The buffer saving object for saving output. */
+  SaveBuffer m_SaveOut = new SaveBuffer(null, this);
+
+  /** Displays the output of tests. */
+  protected JTextArea m_OutText = new JTextArea();
+
+  /** A panel controlling results viewing. */
+  protected ResultHistoryPanel m_History = new ResultHistoryPanel(m_OutText);
+  
+  /** The file chooser for selecting result files. */
+  protected JFileChooser m_FileChooser = new JFileChooser(new File(System.getProperty("user.dir")));
+ 
+  // File filters for various file types.
+  /** CSV file filter. */
+  protected ExtensionFileFilter m_csvFileFilter = 
+    new ExtensionFileFilter(CSVLoader.FILE_EXTENSION, "CSV data files");
+
+  /** ARFF file filter. */
+  protected ExtensionFileFilter m_arffFileFilter = 
+    new ExtensionFileFilter(Instances.FILE_EXTENSION, "Arff data files");
+
+  /** The PairedTTester object. */
+  protected Tester m_TTester = new PairedCorrectedTTester();
+  
+  /** The instances we're extracting results from. */
+  protected Instances m_Instances;
+
+  /** Does any database querying for us. */
+  protected InstanceQuery m_InstanceQuery;
+
+  /** A thread to load results instances from a file or database. */
+  protected Thread m_LoadThread;
+  
+  /** An experiment (used for identifying a result source) -- optional. */
+  protected Experiment m_Exp;
+
+  /** the size for a combobox. */
+  private Dimension COMBO_SIZE = new Dimension(210, m_ResultKeyBut.getPreferredSize().height);
+
+  /** the initial result matrix. */
+  protected ResultMatrix m_ResultMatrix = new ResultMatrixPlainText();
+  
+  /**
+   * Creates the results panel with no initial experiment.
+   */
+  public ResultsPanel() {
+
+    // defaults
+    m_TTester.setSignificanceLevel(ExperimenterDefaults.getSignificance());
+    m_TTester.setShowStdDevs(ExperimenterDefaults.getShowStdDevs());
+    m_ResultMatrix = ExperimenterDefaults.getOutputFormat();
+    m_ResultMatrix.setShowStdDev(ExperimenterDefaults.getShowStdDevs());
+    m_ResultMatrix.setMeanPrec(ExperimenterDefaults.getMeanPrecision());
+    m_ResultMatrix.setStdDevPrec(ExperimenterDefaults.getStdDevPrecision());
+    m_ResultMatrix.setRemoveFilterName(ExperimenterDefaults.getRemoveFilterClassnames());
+    m_ResultMatrix.setShowAverage(ExperimenterDefaults.getShowAverage());
+
+    // Create/Configure/Connect components
+    
+    m_FileChooser.
+      addChoosableFileFilter(m_csvFileFilter);
+    m_FileChooser.
+      addChoosableFileFilter(m_arffFileFilter);
+
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    m_FromExpBut.setEnabled(false);
+    m_FromExpBut.setMnemonic('E');
+    m_FromExpBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	if (m_LoadThread == null) {
+	  m_LoadThread = new Thread() {
+	    public void run() {
+	      setInstancesFromExp(m_Exp);
+	      m_LoadThread = null;
+	    }
+	  };
+	  m_LoadThread.start();
+	}
+      }
+    });
+    m_FromDBaseBut.setMnemonic('D');
+    m_FromDBaseBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	if (m_LoadThread == null) {
+	  m_LoadThread = new Thread() {
+	    public void run() {
+	      setInstancesFromDBaseQuery();
+	    m_LoadThread = null;
+	    }
+	  };
+	  m_LoadThread.start();
+	}
+      }
+    });
+    m_FromFileBut.setMnemonic('F');
+    m_FromFileBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	int returnVal = m_FileChooser.showOpenDialog(ResultsPanel.this);
+	if (returnVal == JFileChooser.APPROVE_OPTION) {
+	  final File selected = m_FileChooser.getSelectedFile();
+	  if (m_LoadThread == null) {
+	    m_LoadThread = new Thread() {
+	      public void run() {
+		setInstancesFromFile(selected);
+		m_LoadThread = null;
+	      }
+	    };
+	    m_LoadThread.start();
+	  }
+	}
+      }
+    });
+    setComboSizes();
+    m_TesterClasses.setEnabled(false);
+    m_DatasetKeyBut.setEnabled(false);
+    m_DatasetKeyBut.setToolTipText("For selecting the keys that are shown as rows.");
+    m_DatasetKeyBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setDatasetKeyFromDialog();
+      }
+    });
+    m_DatasetKeyList.setSelectionMode(ListSelectionModel
+				      .MULTIPLE_INTERVAL_SELECTION);
+    m_ResultKeyBut.setEnabled(false);
+    m_ResultKeyBut.setToolTipText("For selecting the keys that are shown as columns.");
+    m_ResultKeyBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setResultKeyFromDialog();
+      }
+    });
+    m_ResultKeyList.setSelectionMode(ListSelectionModel
+				     .MULTIPLE_INTERVAL_SELECTION);
+    m_SwapDatasetKeyAndResultKeyBut.setEnabled(false);
+    m_SwapDatasetKeyAndResultKeyBut.setToolTipText("Swaps the keys for selecting rows and columns.");
+    m_SwapDatasetKeyAndResultKeyBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	swapDatasetKeyAndResultKey();
+      }
+    });
+    m_CompareCombo.setEnabled(false);
+    m_SortCombo.setEnabled(false);
+
+    m_SigTex.setEnabled(false);
+    m_TestsButton.setEnabled(false);
+    m_TestsButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  setTestBaseFromDialog();
+	}
+      });
+
+    m_DisplayedButton.setEnabled(false);
+    m_DisplayedButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        setDisplayedFromDialog();
+      }
+      });
+
+    m_ShowStdDevs.setEnabled(false);
+    m_ShowStdDevs.setSelected(ExperimenterDefaults.getShowStdDevs());
+    m_OutputFormatButton.setEnabled(false);
+    m_OutputFormatButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        setOutputFormatFromDialog();
+      }
+      });
+    
+    m_PerformBut.setEnabled(false);
+    m_PerformBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	performTest();
+	m_SaveOutBut.setEnabled(true);
+      }
+    });
+
+    m_PerformBut.setToolTipText(m_TTester.getToolTipText());
+
+    m_SaveOutBut.setEnabled(false);
+    m_SaveOutBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  saveBuffer();
+	}
+      });
+    m_OutText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_OutText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_OutText.setEditable(false);
+    m_History.setBorder(BorderFactory.createTitledBorder("Result list"));
+
+
+    // Set up the GUI layout
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createTitledBorder("Source"));
+    JPanel p2 = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    p2.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
+    //    p2.setLayout(new GridLayout(1, 3));
+    p2.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    p2.add(m_FromFileBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    p2.add(m_FromDBaseBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    p2.add(m_FromExpBut,constraints);
+    p1.setLayout(new BorderLayout());
+    p1.add(m_FromLab, BorderLayout.CENTER);
+    p1.add(p2, BorderLayout.EAST);
+
+    JPanel p3 = new JPanel();
+    p3.setBorder(BorderFactory.createTitledBorder("Configure test"));
+    GridBagLayout gbL = new GridBagLayout();
+    p3.setLayout(gbL);
+
+    int y = 0;
+    GridBagConstraints gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_TesterClassesLabel,gbC);
+    m_TesterClassesLabel.setDisplayedMnemonic('w');
+    m_TesterClassesLabel.setLabelFor(m_TesterClasses);
+    p3.add(m_TesterClassesLabel);
+    gbC = new GridBagConstraints();
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbL.setConstraints(m_TesterClasses, gbC);
+    p3.add(m_TesterClasses);
+    m_TesterClasses.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  setTester();
+	}
+      });
+    setSelectedItem(m_TesterClasses, ExperimenterDefaults.getTester());
+    
+    y++;
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_DatasetAndResultKeysLabel,gbC);
+    m_DatasetAndResultKeysLabel.setDisplayedMnemonic('R');
+    m_DatasetAndResultKeysLabel.setLabelFor(m_DatasetKeyBut);
+    p3.add(m_DatasetAndResultKeysLabel);
+    
+    m_PanelDatasetResultKeys.add(m_DatasetKeyBut);
+    m_PanelDatasetResultKeys.add(m_ResultKeyBut);
+    m_PanelDatasetResultKeys.add(m_SwapDatasetKeyAndResultKeyBut);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_PanelDatasetResultKeys, gbC);
+    p3.add(m_PanelDatasetResultKeys);
+    
+    y++;
+    JLabel lab = new JLabel("Comparison field", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('m');
+    lab.setLabelFor(m_CompareCombo);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbL.setConstraints(m_CompareCombo, gbC);
+    p3.add(m_CompareCombo);
+    
+    y++;
+    lab = new JLabel("Significance", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('g');
+    lab.setLabelFor(m_SigTex);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbL.setConstraints(m_SigTex, gbC);
+    p3.add(m_SigTex);
+    
+    y++;
+    lab = new JLabel("Sorting (asc.) by", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('S');
+    lab.setLabelFor(m_SortCombo);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_SortCombo, gbC);
+    p3.add(m_SortCombo);
+    
+    y++;
+    lab = new JLabel("Test base", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('b');
+    lab.setLabelFor(m_TestsButton);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_TestsButton, gbC);
+    p3.add(m_TestsButton);
+
+    y++;
+    lab = new JLabel("Displayed Columns", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('i');
+    lab.setLabelFor(m_DisplayedButton);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_DisplayedButton, gbC);
+    p3.add(m_DisplayedButton);
+
+    y++;
+    lab = new JLabel("Show std. deviations", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('a');
+    lab.setLabelFor(m_ShowStdDevs);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_ShowStdDevs, gbC);
+    p3.add(m_ShowStdDevs);
+
+    y++;
+    lab = new JLabel("Output Format", SwingConstants.RIGHT);
+    lab.setDisplayedMnemonic('O');
+    lab.setLabelFor(m_OutputFormatButton);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.gridy = y;     gbC.gridx = 0;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(lab, gbC);
+    p3.add(lab);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = y;     gbC.gridx = 1;  gbC.weightx = 100;
+    gbC.insets = new Insets(5,0,5,0);
+    gbL.setConstraints(m_OutputFormatButton, gbC);
+    p3.add(m_OutputFormatButton);
+    
+    JPanel output = new JPanel();
+    output.setLayout(new BorderLayout());
+    output.setBorder(BorderFactory.createTitledBorder("Test output"));
+    output.add(new JScrollPane(m_OutText), BorderLayout.CENTER);
+
+    JPanel mondo = new JPanel();
+    gbL = new GridBagLayout();
+    mondo.setLayout(gbL);
+    gbC = new GridBagConstraints();
+    //    gbC.anchor = GridBagConstraints.WEST;
+    //    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(p3, gbC);
+    mondo.add(p3);
+
+    JPanel bts = new JPanel();
+    m_PerformBut.setMnemonic('t');
+    m_SaveOutBut.setMnemonic('S');
+    bts.setLayout(new GridLayout(1,2,5,5));
+    bts.add(m_PerformBut);
+    bts.add(m_SaveOutBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbC.insets = new Insets(5,5,5,5);
+    gbL.setConstraints(bts, gbC);
+    mondo.add(bts);
+    gbC = new GridBagConstraints();
+    //gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 2;     gbC.gridx = 0; gbC.weightx = 0;
+    gbC.weighty = 100;
+    gbL.setConstraints(m_History, gbC);
+    mondo.add(m_History);
+    /*gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.gridheight = 3;
+    gbC.weightx = 100; gbC.weighty = 100;
+    gbL.setConstraints(output, gbC);*/
+    //mondo.add(output);
+    JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+					  mondo, output);
+    splitPane.setOneTouchExpandable(true);
+    //splitPane.setDividerLocation(100);
+
+    setLayout(new BorderLayout());
+    add(p1, BorderLayout.NORTH);
+    //add(mondo , BorderLayout.CENTER);
+    add(splitPane , BorderLayout.CENTER);
+  }
+
+  /**
+   * Sets the combo-boxes to a fixed size so they don't take up too much room
+   * that would be better devoted to the test output box.
+   */
+  protected void setComboSizes() {
+    
+    m_TesterClasses.setPreferredSize(COMBO_SIZE);
+    m_PanelDatasetResultKeys.setPreferredSize(COMBO_SIZE);
+    m_CompareCombo.setPreferredSize(COMBO_SIZE);
+    m_SigTex.setPreferredSize(COMBO_SIZE);
+    m_SortCombo.setPreferredSize(COMBO_SIZE);
+
+    m_TesterClasses.setMaximumSize(COMBO_SIZE);
+    m_PanelDatasetResultKeys.setMaximumSize(COMBO_SIZE);
+    m_CompareCombo.setMaximumSize(COMBO_SIZE);
+    m_SigTex.setMaximumSize(COMBO_SIZE);
+    m_SortCombo.setMaximumSize(COMBO_SIZE);
+
+    m_TesterClasses.setMinimumSize(COMBO_SIZE);
+    m_PanelDatasetResultKeys.setMinimumSize(COMBO_SIZE);
+    m_CompareCombo.setMinimumSize(COMBO_SIZE);
+    m_SigTex.setMinimumSize(COMBO_SIZE);
+    m_SortCombo.setMinimumSize(COMBO_SIZE);
+  }
+  
+  /**
+   * Tells the panel to use a new experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+    
+    m_Exp = exp;
+    m_FromExpBut.setEnabled(exp != null);
+  }
+
+  /**
+   * Queries the user enough to make a database query to retrieve experiment
+   * results.
+   */
+  protected void setInstancesFromDBaseQuery() {
+
+    try {
+      if (m_InstanceQuery == null) {
+	m_InstanceQuery = new InstanceQuery();
+      }
+      String dbaseURL = m_InstanceQuery.getDatabaseURL();
+      String username = m_InstanceQuery.getUsername();
+      String passwd = m_InstanceQuery.getPassword();
+      /*dbaseURL = (String) JOptionPane.showInputDialog(this,
+					     "Enter the database URL",
+					     "Query Database",
+					     JOptionPane.PLAIN_MESSAGE,
+					     null,
+					     null,
+					     dbaseURL);*/
+     
+      
+      
+      DatabaseConnectionDialog dbd= new DatabaseConnectionDialog(null,dbaseURL,username);
+      dbd.setVisible(true);
+      
+      //if (dbaseURL == null) {
+      if (dbd.getReturnValue()==JOptionPane.CLOSED_OPTION) {
+	m_FromLab.setText("Cancelled");
+	return;
+      }
+      dbaseURL=dbd.getURL();
+      username=dbd.getUsername();
+      passwd=dbd.getPassword();
+      m_InstanceQuery.setDatabaseURL(dbaseURL);
+      m_InstanceQuery.setUsername(username);
+      m_InstanceQuery.setPassword(passwd);
+      m_InstanceQuery.setDebug(dbd.getDebug());
+      
+      m_InstanceQuery.connectToDatabase();
+      if (!m_InstanceQuery.experimentIndexExists()) {
+	System.err.println("not found");
+	m_FromLab.setText("No experiment index");
+        m_InstanceQuery.disconnectFromDatabase();
+	return;
+      }
+      System.err.println("found");
+      m_FromLab.setText("Getting experiment index");
+      Instances index = m_InstanceQuery.retrieveInstances("SELECT * FROM "
+				       + InstanceQuery.EXP_INDEX_TABLE);
+      if (index.numInstances() == 0) {
+	m_FromLab.setText("No experiments available");
+        m_InstanceQuery.disconnectFromDatabase();
+	return;	
+      }
+      m_FromLab.setText("Got experiment index");
+
+      DefaultListModel lm = new DefaultListModel();
+      for (int i = 0; i < index.numInstances(); i++) {
+	lm.addElement(index.instance(i).toString());
+      }
+      JList jl = new JList(lm);
+      jl.setSelectedIndex(0);
+      int result;
+      // display dialog only if there's not just one result!
+      if (jl.getModel().getSize() != 1) {
+        ListSelectorDialog jd = new ListSelectorDialog(null, jl);
+        result = jd.showDialog();
+      }
+      else {
+        result = ListSelectorDialog.APPROVE_OPTION;
+      }
+      if (result != ListSelectorDialog.APPROVE_OPTION) {
+	m_FromLab.setText("Cancelled");
+        m_InstanceQuery.disconnectFromDatabase();
+	return;
+      }
+      Instance selInst = index.instance(jl.getSelectedIndex());
+      Attribute tableAttr = index.attribute(InstanceQuery.EXP_RESULT_COL);
+      String table = InstanceQuery.EXP_RESULT_PREFIX
+	+ selInst.toString(tableAttr);
+      setInstancesFromDatabaseTable(table);
+      
+    } catch (Exception ex) {
+       // 1. print complete stacktrace
+       ex.printStackTrace();
+       // 2. print message in panel
+       m_FromLab.setText("Problem reading database: '" + ex.getMessage() + "'");
+    }
+  }
+  
+  /**
+   * Examines the supplied experiment to determine the results destination
+   * and attempts to load the results.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  protected void setInstancesFromExp(Experiment exp) {
+
+    if ((exp.getResultListener() instanceof CSVResultListener)) { 
+      File resultFile = ((CSVResultListener) exp.getResultListener())
+	.getOutputFile();
+      if ((resultFile == null)) {
+	m_FromLab.setText("No result file");
+      } else {
+	setInstancesFromFile(resultFile);
+      }
+    } else if (exp.getResultListener() instanceof DatabaseResultListener) {
+      String dbaseURL = ((DatabaseResultListener) exp.getResultListener())
+	.getDatabaseURL();
+      try {
+	if (m_InstanceQuery == null) {
+	  m_InstanceQuery = new InstanceQuery();
+	}
+	m_InstanceQuery.setDatabaseURL(dbaseURL);
+	m_InstanceQuery.connectToDatabase();
+	String tableName = m_InstanceQuery
+	  .getResultsTableName(exp.getResultProducer());
+	setInstancesFromDatabaseTable(tableName);
+      } catch (Exception ex) {
+	m_FromLab.setText("Problem reading database");
+      }
+    } else {
+      m_FromLab.setText("Can't get results from experiment");
+    }
+  }
+
+  
+  /**
+   * Queries a database to load results from the specified table name. The
+   * database connection must have already made by m_InstanceQuery.
+   *
+   * @param tableName the name of the table containing results to retrieve.
+   */
+  protected void setInstancesFromDatabaseTable(String tableName) {
+
+    try {
+      m_FromLab.setText("Reading from database, please wait...");
+      final Instances i = m_InstanceQuery.retrieveInstances("SELECT * FROM "
+						      + tableName);
+      SwingUtilities.invokeAndWait(new Runnable() {
+	public void run() {
+	  setInstances(i);
+	}
+      });
+      m_InstanceQuery.disconnectFromDatabase();
+    } catch (Exception ex) {
+      m_FromLab.setText(ex.getMessage());
+    }
+  }
+  
+  /**
+   * Loads results from a set of instances contained in the supplied
+   * file.
+   *
+   * @param f a value of type 'File'
+   */
+  protected void setInstancesFromFile(File f) {
+
+    String fileType = f.getName();
+    try {
+      m_FromLab.setText("Reading from file...");
+      if (f.getName().toLowerCase().endsWith(Instances.FILE_EXTENSION)) {	    
+	fileType = "arff";
+	Reader r = new BufferedReader(new FileReader(f));
+	setInstances(new Instances(r));
+	r.close();
+      } else if (f.getName().toLowerCase().endsWith(CSVLoader.FILE_EXTENSION)) {
+	fileType = "csv";
+	CSVLoader cnv = new CSVLoader();
+	cnv.setSource(f);
+	Instances inst = cnv.getDataSet();
+	setInstances(inst);
+      } else {
+	throw new Exception("Unrecognized file type");
+      }
+    } catch (Exception ex) {
+      m_FromLab.setText("File '" + f.getName() + "' not recognised as an "
+			  +fileType+" file.");
+      if (JOptionPane.showOptionDialog(ResultsPanel.this,
+				       "File '" + f.getName()
+				       + "' not recognised as an "
+				       +fileType+" file.\n"
+				       + "Reason:\n" + ex.getMessage(),
+				       "Load Instances",
+				       0,
+				       JOptionPane.ERROR_MESSAGE,
+				       null,
+				       new String[] {"OK"},
+				       null) == 1) {
+	
+      }
+    }  
+  }
+  
+  /**
+   * Returns a vector with column names of the dataset, listed in "list". If
+   * a column cannot be found or the list is empty the ones from the default 
+   * list are returned. 
+   * 
+   * @param list           comma-separated list of attribute names
+   * @param defaultList    the default list of attribute names
+   * @param inst           the instances to get the attribute names from
+   * @return               a vector containing attribute names
+   */
+  protected Vector determineColumnNames(String list, String defaultList, Instances inst) {
+    Vector              result;
+    Vector              atts;
+    StringTokenizer     tok;
+    int                 i;
+    String              item;
+    
+    // get attribute names
+    atts = new Vector();
+    for (i = 0; i < inst.numAttributes(); i++)
+      atts.add(inst.attribute(i).name().toLowerCase());
+
+    // process list
+    result = new Vector();
+    tok = new StringTokenizer(list, ",");
+    while (tok.hasMoreTokens()) {
+      item = tok.nextToken().toLowerCase();
+      if (atts.contains(item)) {
+        result.add(item);
+      }
+      else {
+        result.clear();
+        break;
+      }
+    }
+    
+    // do we have to return defaults?
+    if (result.size() == 0) {
+      tok = new StringTokenizer(defaultList, ",");
+      while (tok.hasMoreTokens())
+        result.add(tok.nextToken().toLowerCase());
+    }
+    
+    return result;
+  }
+
+  /**
+   * Sets up the panel with a new set of instances, attempting
+   * to guess the correct settings for various columns.
+   *
+   * @param newInstances the new set of results.
+   */
+  public void setInstances(Instances newInstances) {
+
+    m_Instances = newInstances;
+    m_TTester.setInstances(m_Instances);
+    m_FromLab.setText("Got " + m_Instances.numInstances() + " results");
+    
+    // setup row and column names
+    Vector rows = determineColumnNames(
+        ExperimenterDefaults.getRow(), "Key_Dataset", m_Instances);
+    Vector cols = determineColumnNames(
+        ExperimenterDefaults.getColumn(), "Key_Scheme,Key_Scheme_options,Key_Scheme_version_ID", m_Instances);
+    
+    // Do other stuff
+    m_DatasetKeyModel.removeAllElements();
+    m_ResultKeyModel.removeAllElements();
+    m_CompareModel.removeAllElements();
+    m_SortModel.removeAllElements();
+    m_SortModel.addElement("<default>");
+    m_TTester.setSortColumn(-1);
+    String selectedList = "";
+    String selectedListDataset = "";
+    boolean comparisonFieldSet = false; 
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      String name = m_Instances.attribute(i).name();
+      if (name.toLowerCase().startsWith("key_", 0)) {
+	m_DatasetKeyModel.addElement(name.substring(4));
+	m_ResultKeyModel.addElement(name.substring(4));
+	m_CompareModel.addElement(name.substring(4));
+      } else {
+	m_DatasetKeyModel.addElement(name);
+	m_ResultKeyModel.addElement(name);
+	m_CompareModel.addElement(name);
+        if (m_Instances.attribute(i).isNumeric())
+	  m_SortModel.addElement(name);
+      }
+
+      if (rows.contains(name.toLowerCase())) {
+	m_DatasetKeyList.addSelectionInterval(i, i);
+	selectedListDataset += "," + (i + 1);
+      } else if (name.toLowerCase().equals("key_run")) {
+	m_TTester.setRunColumn(i);
+      } else if (name.toLowerCase().equals("key_fold")) {
+	m_TTester.setFoldColumn(i);
+      } else if (cols.contains(name.toLowerCase())) {
+	m_ResultKeyList.addSelectionInterval(i, i);
+	selectedList += "," + (i + 1);
+      } else if (name.toLowerCase().indexOf(ExperimenterDefaults.getComparisonField()) != -1) {
+	m_CompareCombo.setSelectedIndex(i);
+	comparisonFieldSet = true;
+	//	break;
+      } else if ((name.toLowerCase().indexOf("root_relative_squared_error") != -1) &&
+          (!comparisonFieldSet)) {
+	m_CompareCombo.setSelectedIndex(i);
+	comparisonFieldSet = true;
+      }
+    }
+    m_TesterClasses.setEnabled(true);
+    m_DatasetKeyBut.setEnabled(true);
+    m_ResultKeyBut.setEnabled(true);
+    m_SwapDatasetKeyAndResultKeyBut.setEnabled(true);
+    m_CompareCombo.setEnabled(true);
+    m_SortCombo.setEnabled(true);
+    if (ExperimenterDefaults.getSorting().length() != 0)
+      setSelectedItem(m_SortCombo, ExperimenterDefaults.getSorting());
+
+    Range generatorRange = new Range();
+    if (selectedList.length() != 0) {
+      try {
+	generatorRange.setRanges(selectedList);
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	System.err.println(ex.getMessage());
+      }
+    }
+    m_TTester.setResultsetKeyColumns(generatorRange);
+
+    generatorRange = new Range();
+    if (selectedListDataset.length() != 0) {
+      try {
+	generatorRange.setRanges(selectedListDataset);
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	System.err.println(ex.getMessage());
+      }
+    }
+    m_TTester.setDatasetKeyColumns(generatorRange);
+
+    m_SigTex.setEnabled(true);
+
+    setTTester();
+  }
+
+  /**
+   * Sets the selected item of an combobox, since using setSelectedItem(...)
+   * doesn't work, if one checks object references!
+   *
+   * @param cb      the combobox to set the item for
+   * @param item    the item to set active
+   */
+  protected void setSelectedItem(JComboBox cb, String item) {
+    int       i;
+
+    for (i = 0; i < cb.getItemCount(); i++) {
+      if (cb.getItemAt(i).toString().equals(item)) {
+        cb.setSelectedIndex(i);
+        break;
+      }
+    }
+  }
+
+  /**
+   * Updates the test chooser with possible tests.
+   */
+  protected void setTTester() {
+    
+    // default is to display all columns
+    m_TTester.setDisplayedResultsets(null);       
+
+    String name = (new SimpleDateFormat("HH:mm:ss - "))
+      .format(new Date())
+      + "Available resultsets";
+    StringBuffer outBuff = new StringBuffer();
+    outBuff.append("Available resultsets\n"
+		   + m_TTester.resultsetKey() + "\n\n");
+    m_History.addResult(name, outBuff);
+    m_History.setSingle(name);
+
+    m_TestsModel.removeAllElements();
+    for (int i = 0; i < m_TTester.getNumResultsets(); i++) {
+      String tname = m_TTester.getResultsetName(i);
+      /*      if (tname.length() > 20) {
+	tname = tname.substring(0, 20);
+	} */
+      m_TestsModel.addElement(tname);
+    }
+    
+    m_DisplayedModel.removeAllElements();
+    for (int i = 0; i < m_TestsModel.size(); i++)
+      m_DisplayedModel.addElement(m_TestsModel.elementAt(i));
+    
+    m_TestsModel.addElement("Summary");
+    m_TestsModel.addElement("Ranking");
+
+    m_TestsList.setSelectedIndex(0);
+    m_DisplayedList.setSelectionInterval(0, m_DisplayedModel.size() - 1);
+    
+    m_TestsButton.setEnabled(true);
+    m_DisplayedButton.setEnabled(true);
+    m_ShowStdDevs.setEnabled(true);
+    m_OutputFormatButton.setEnabled(true);
+    m_PerformBut.setEnabled(true);
+    
+  }
+
+  
+  /**
+   * Carries out a t-test using the current configuration.
+   */
+  protected void performTest() {
+
+    String sigStr = m_SigTex.getText();
+    if (sigStr.length() != 0) {
+      m_TTester.setSignificanceLevel((new Double(sigStr)).doubleValue());
+    } else {
+      m_TTester.setSignificanceLevel(ExperimenterDefaults.getSignificance());
+    }
+
+    // Carry out the test chosen and biff the results to the output area
+    m_TTester.setShowStdDevs(m_ShowStdDevs.isSelected());
+    if (m_Instances.attribute(m_SortCombo.getSelectedItem().toString()) != null)
+      m_TTester.setSortColumn(
+          m_Instances.attribute(
+            m_SortCombo.getSelectedItem().toString()).index());
+    else
+      m_TTester.setSortColumn(-1);
+    int compareCol = m_CompareCombo.getSelectedIndex();
+    int tType = m_TestsList.getSelectedIndex();
+
+    String name = (new SimpleDateFormat("HH:mm:ss - "))
+      .format(new Date())
+      + (String) m_CompareCombo.getSelectedItem() + " - "
+      + (String) m_TestsList.getSelectedValue();
+    StringBuffer outBuff = new StringBuffer();
+    outBuff.append(m_TTester.header(compareCol));
+    outBuff.append("\n");
+    m_History.addResult(name, outBuff);
+    m_History.setSingle(name);
+    m_TTester.setDisplayedResultsets(m_DisplayedList.getSelectedIndices());
+    m_TTester.setResultMatrix(m_ResultMatrix);
+    try {
+      if (tType < m_TTester.getNumResultsets()) {
+	outBuff.append(m_TTester.multiResultsetFull(tType, compareCol));
+      } else if (tType == m_TTester.getNumResultsets()) {
+	outBuff.append(m_TTester.multiResultsetSummary(compareCol));
+      } else {
+	outBuff.append(m_TTester.multiResultsetRanking(compareCol));
+      }
+      outBuff.append("\n");
+    } catch (Exception ex) {
+      outBuff.append(ex.getMessage() + "\n");
+    }
+    m_History.updateResult(name);
+  }
+
+  
+  public void setResultKeyFromDialog() {
+
+    ListSelectorDialog jd = new ListSelectorDialog(null, m_ResultKeyList);
+
+    // Open the dialog
+    int result = jd.showDialog();
+  
+    // If accepted, update the ttester
+    if (result == ListSelectorDialog.APPROVE_OPTION) {
+      int [] selected = m_ResultKeyList.getSelectedIndices();
+      String selectedList = "";
+      for (int i = 0; i < selected.length; i++) {
+	selectedList += "," + (selected[i] + 1);
+      }
+      Range generatorRange = new Range();
+      if (selectedList.length() != 0) {
+	try {
+	  generatorRange.setRanges(selectedList);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	  System.err.println(ex.getMessage());
+	}
+      }
+      m_TTester.setResultsetKeyColumns(generatorRange);
+      setTTester();
+    }
+  }
+  
+  public void setDatasetKeyFromDialog() {
+
+    ListSelectorDialog jd = new ListSelectorDialog(null, m_DatasetKeyList);
+
+    // Open the dialog
+    int result = jd.showDialog();
+    
+    // If accepted, update the ttester
+    if (result == ListSelectorDialog.APPROVE_OPTION) {
+      int [] selected = m_DatasetKeyList.getSelectedIndices();
+      String selectedList = "";
+      for (int i = 0; i < selected.length; i++) {
+	selectedList += "," + (selected[i] + 1);
+      }
+      Range generatorRange = new Range();
+      if (selectedList.length() != 0) {
+	try {
+	  generatorRange.setRanges(selectedList);
+	} catch (Exception ex) {
+	  ex.printStackTrace();
+	  System.err.println(ex.getMessage());
+	}
+      }
+      m_TTester.setDatasetKeyColumns(generatorRange);
+      setTTester();
+    }
+  }
+
+  /**
+   * Swaps the keys for dataset and result.
+   */
+  protected void swapDatasetKeyAndResultKey() {
+    int[] 	tmpSelected;
+    Range	tmpRange;
+    
+    // lists
+    tmpSelected = m_DatasetKeyList.getSelectedIndices();
+    m_DatasetKeyList.setSelectedIndices(m_ResultKeyList.getSelectedIndices());
+    m_ResultKeyList.setSelectedIndices(tmpSelected);
+    
+    // tester
+    tmpRange = m_TTester.getDatasetKeyColumns();
+    m_TTester.setDatasetKeyColumns(m_TTester.getResultsetKeyColumns());
+    m_TTester.setResultsetKeyColumns(tmpRange);
+    setTTester();
+  }
+  
+  public void setTestBaseFromDialog() {
+    ListSelectorDialog jd = new ListSelectorDialog(null, m_TestsList);
+
+    // Open the dialog
+    jd.showDialog();
+  }
+
+  public void setDisplayedFromDialog() {
+    ListSelectorDialog jd = new ListSelectorDialog(null, m_DisplayedList);
+
+    // Open the dialog
+    jd.showDialog();
+  }
+  
+  /**
+   * displays the Dialog for the output format and sets the chosen settings, 
+   * if the user approves.
+   */
+  public void setOutputFormatFromDialog() {
+    OutputFormatDialog dialog = new OutputFormatDialog(PropertyDialog.getParentFrame(this));
+
+    m_ResultMatrix.setShowStdDev(m_ShowStdDevs.isSelected());
+    dialog.setResultMatrix(m_ResultMatrix);
+    dialog.setLocationRelativeTo(this);
+    
+    if (dialog.showDialog() == OutputFormatDialog.APPROVE_OPTION) {
+      m_ResultMatrix = dialog.getResultMatrix();
+      m_ShowStdDevs.setSelected(m_ResultMatrix.getShowStdDev());
+    }
+  }
+
+  /**
+   * Save the currently selected result buffer to a file.
+   */
+  protected void saveBuffer() {
+    StringBuffer sb = m_History.getSelectedBuffer();
+    if (sb != null) {
+      if (m_SaveOut.save(sb)) {
+	JOptionPane.showMessageDialog(this,
+				      "File saved",
+				      "Results",
+				      JOptionPane.INFORMATION_MESSAGE);
+      }
+    } else {
+      m_SaveOutBut.setEnabled(false);
+    }
+  }
+
+  /**
+   * sets the currently selected Tester-Class.
+   */
+  protected void setTester() {
+    Tester        tester;
+    Tester        t;
+    int           i;
+    
+    if (m_TesterClasses.getSelectedItem() == null)
+      return;
+
+    tester = null;
+    
+    // find display name
+    try {
+      for (i = 0; i < m_Testers.size(); i++) {
+        t = (Tester) ((Class) m_Testers.get(i)).newInstance();
+        if (t.getDisplayName().equals(m_TesterClasses.getSelectedItem())) {
+          tester = t;
+          break;
+        }
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    if (tester == null) {
+      tester  = new PairedCorrectedTTester();  // default
+      m_TesterClasses.setSelectedItem(tester.getDisplayName());
+    }
+
+    tester.assign(m_TTester);
+    m_TTester = tester;
+    m_PerformBut.setToolTipText(m_TTester.getToolTipText());
+    System.out.println("Tester set to: " + m_TTester.getClass().getName());
+  }
+  
+  /**
+   * Tests out the results panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Weka Experiment: Results Analysis");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final ResultsPanel sp = new ResultsPanel();
+      //sp.setBorder(BorderFactory.createTitledBorder("Setup"));
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(700, 550);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/RunNumberPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/RunNumberPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/RunNumberPanel.java	(revision 29)
@@ -0,0 +1,197 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RunNumberPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.experiment;
+
+import weka.experiment.Experiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+
+
+/** 
+ * This panel controls configuration of lower and upper run numbers
+ * in an experiment.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $
+ */
+public class RunNumberPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1644336658426067852L;
+
+  /** Configures the lower run number */
+  protected JTextField m_LowerText = new JTextField("1");
+
+  /** Configures the upper run number */
+  protected JTextField m_UpperText = new JTextField("10");
+
+  /** The experiment being configured */
+  protected Experiment m_Exp;
+  
+  /**
+   * Creates the panel with no initial experiment.
+   */
+  public RunNumberPanel() {
+    
+    // Updates occur to the values in exp whenever enter is pressed
+    // or the component loses focus
+    m_LowerText.addKeyListener(new KeyAdapter() {
+      public void keyReleased(KeyEvent e) {
+	m_Exp.setRunLower(getLower());
+      }
+    });
+    m_LowerText.addFocusListener(new FocusAdapter() {
+      public void focusLost(FocusEvent e) {
+	m_Exp.setRunLower(getLower());
+      }
+    });
+    m_UpperText.addKeyListener(new KeyAdapter() {
+      public void keyReleased(KeyEvent e) {
+	m_Exp.setRunUpper(getUpper());
+      }
+    });
+    m_UpperText.addFocusListener(new FocusAdapter() {
+      public void focusLost(FocusEvent e) {
+	m_Exp.setRunUpper(getUpper());
+      }
+    });
+    m_LowerText.setEnabled(false);
+    m_UpperText.setEnabled(false);
+
+    // Set the GUI layout
+    setLayout(new GridLayout(1,2));
+    setBorder(BorderFactory.createTitledBorder("Runs"));
+    Box b1 = new Box(BoxLayout.X_AXIS);
+    b1.add(Box.createHorizontalStrut(10));
+    b1.add(new JLabel("From:", SwingConstants.RIGHT));
+    b1.add(Box.createHorizontalStrut(5));
+    b1.add(m_LowerText);
+    add(b1);
+    Box b2 = new Box(BoxLayout.X_AXIS);
+    b2.add(Box.createHorizontalStrut(10));
+    b2.add(new JLabel("To:", SwingConstants.RIGHT));
+    b2.add(Box.createHorizontalStrut(5));
+    b2.add(m_UpperText);
+    add(b2);
+  }
+
+  /**
+   * Creates the panel with the supplied initial experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public RunNumberPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Sets the experiment to be configured.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+    
+    m_Exp = exp;
+    m_LowerText.setText("" + m_Exp.getRunLower());
+    m_UpperText.setText("" + m_Exp.getRunUpper());
+    m_LowerText.setEnabled(true);
+    m_UpperText.setEnabled(true);
+  }
+  
+  /**
+   * Gets the current lower run number.
+   *
+   * @return the lower run number.
+   */
+  public int getLower() {
+
+    int result = 1;
+    try {
+      result = Integer.parseInt(m_LowerText.getText());
+    } catch (Exception ex) {
+    }
+    return Math.max(1, result);
+  }
+  
+  /**
+   * Gets the current upper run number.
+   *
+   * @return the upper run number.
+   */
+  public int getUpper() {
+
+    int result = 1;
+    try {
+      result = Integer.parseInt(m_UpperText.getText());
+    } catch (Exception ex) {
+    }
+    return Math.max(1, result);
+  }
+  
+  /**
+   * Tests out the panel from the command line.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Dataset List Editor");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(new RunNumberPanel(new Experiment()),
+			      BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/RunPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/RunPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/RunPanel.java	(revision 29)
@@ -0,0 +1,394 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    RunPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.experiment.Experiment;
+import weka.experiment.RemoteExperiment;
+import weka.experiment.RemoteExperimentEvent;
+import weka.experiment.RemoteExperimentListener;
+import weka.gui.LogPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/** 
+ * This panel controls the running of an experiment.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.21 $
+ */
+public class RunPanel
+  extends JPanel
+  implements ActionListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 1691868018596872051L;
+
+  /** The message displayed when no experiment is running */
+  protected static final String NOT_RUNNING = "Not running";
+
+  /** Click to start running the experiment */
+  protected JButton m_StartBut = new JButton("Start");
+
+  /** Click to signal the running experiment to halt */
+  protected JButton m_StopBut = new JButton("Stop");
+
+  protected LogPanel m_Log = new LogPanel();
+
+  /** The experiment to run */
+  protected Experiment m_Exp;
+
+  /** The thread running the experiment */
+  protected Thread m_RunThread = null;
+
+  /** A pointer to the results panel */
+  protected ResultsPanel m_ResultsPanel = null;
+
+  /*
+   * A class that handles running a copy of the experiment
+   * in a separate thread
+   */
+  class ExperimentRunner
+    extends Thread
+    implements Serializable {
+
+    /** for serialization */
+    private static final long serialVersionUID = -5591889874714150118L;
+
+    Experiment m_ExpCopy;
+    
+    public ExperimentRunner(final Experiment exp) throws Exception {
+
+      // Create a full copy using serialization
+      if (exp == null) {
+	System.err.println("Null experiment!!!");
+      } else {
+	System.err.println("Running experiment: " + exp.toString());
+      }
+      System.err.println("Writing experiment copy");
+      SerializedObject so = new SerializedObject(exp);
+      System.err.println("Reading experiment copy");
+      m_ExpCopy = (Experiment) so.getObject();
+      System.err.println("Made experiment copy");
+    }
+
+    public void abortExperiment() {
+      if (m_ExpCopy instanceof RemoteExperiment) {
+	((RemoteExperiment)m_ExpCopy).abortExperiment();
+	//	m_StartBut.setEnabled(true);
+	m_StopBut.setEnabled(false);
+	//	statusMessage(NOT_RUNNING);
+      }
+    }
+
+    /**
+     * Starts running the experiment.
+     */
+    public void run() {
+      
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(true);
+      if (m_ResultsPanel != null) {
+	m_ResultsPanel.setExperiment(null);
+      }
+      try {
+	if (m_ExpCopy instanceof RemoteExperiment) {
+	  // add a listener
+	  System.err.println("Adding a listener");
+	  ((RemoteExperiment)m_ExpCopy).
+	    addRemoteExperimentListener(new RemoteExperimentListener() {
+		public void remoteExperimentStatus(RemoteExperimentEvent e) {
+		  if (e.m_statusMessage) {
+		    statusMessage(e.m_messageString);
+		  }
+		  if (e.m_logMessage) {
+		    logMessage(e.m_messageString);
+		  }
+		  if (e.m_experimentFinished) {
+		    m_RunThread = null;
+		    m_StartBut.setEnabled(true);
+		    m_StopBut.setEnabled(false);
+		    statusMessage(NOT_RUNNING);
+		  }
+		}
+	      });
+	}
+	logMessage("Started");
+	statusMessage("Initializing...");
+	m_ExpCopy.initialize();
+	int errors = 0;
+	if (!(m_ExpCopy instanceof RemoteExperiment)) {
+	  statusMessage("Iterating...");
+	  while (m_RunThread != null && m_ExpCopy.hasMoreIterations()) {
+	    try {
+	      String current = "Iteration:";
+	      if (m_ExpCopy.getUsePropertyIterator()) {
+		int cnum = m_ExpCopy.getCurrentPropertyNumber();
+		String ctype = m_ExpCopy.getPropertyArray().getClass().getComponentType().getName();
+		int lastDot = ctype.lastIndexOf('.');
+		if (lastDot != -1) {
+		  ctype = ctype.substring(lastDot + 1);
+		}
+		String cname = " " + ctype + "="
+		  + (cnum + 1) + ":"
+		  + m_ExpCopy.getPropertyArrayValue(cnum).getClass().getName();
+		current += cname;
+	      }
+	      String dname = ((File) m_ExpCopy.getDatasets()
+			      .elementAt(m_ExpCopy.getCurrentDatasetNumber()))
+		.getName();
+	      current += " Dataset=" + dname
+		+ " Run=" + (m_ExpCopy.getCurrentRunNumber());
+	      statusMessage(current);
+	      m_ExpCopy.nextIteration();
+	    } catch (Exception ex) {
+	      errors ++;
+	      logMessage(ex.getMessage());
+	      ex.printStackTrace();
+	      boolean continueAfterError = false;
+	      if (continueAfterError) {
+		m_ExpCopy.advanceCounters(); // Try to keep plowing through
+	      } else {
+		m_RunThread = null;
+	      }
+	    }
+	  }
+	  statusMessage("Postprocessing...");
+	  m_ExpCopy.postProcess();
+	  if (m_RunThread == null) {
+	    logMessage("Interrupted");
+	  } else {
+	    logMessage("Finished");
+	  }
+	  if (errors == 1) {
+	    logMessage("There was " + errors + " error");
+	  } else {
+	    logMessage("There were " + errors + " errors");
+	  }
+	  statusMessage(NOT_RUNNING);
+	} else {
+	  statusMessage("Remote experiment running...");
+	  ((RemoteExperiment)m_ExpCopy).runExperiment();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	System.err.println(ex.getMessage());
+	statusMessage(ex.getMessage());
+      } finally {
+	if (m_ResultsPanel != null) {
+	  m_ResultsPanel.setExperiment(m_ExpCopy);
+	}
+	if (!(m_ExpCopy instanceof RemoteExperiment)) {
+	  m_RunThread = null;
+	  m_StartBut.setEnabled(true);
+	  m_StopBut.setEnabled(false);
+	  System.err.println("Done...");
+	}
+      }
+    }
+  }
+
+  /**
+   * Sets the pointer to the results panel.
+   */
+  public void setResultsPanel(ResultsPanel rp) {
+
+    m_ResultsPanel = rp;
+  }
+  
+  /**
+   * Creates the run panel with no initial experiment.
+   */
+  public RunPanel() {
+
+    m_StartBut.addActionListener(this);
+    m_StopBut.addActionListener(this);
+    m_StartBut.setEnabled(false);
+    m_StopBut.setEnabled(false);
+    m_StartBut.setMnemonic('S');
+    m_StopBut.setMnemonic('t');
+    m_Log.statusMessage(NOT_RUNNING);
+
+    // Set the GUI layout
+    JPanel controls = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    controls.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    //    controls.setLayout(new GridLayout(1,2));
+    controls.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    controls.add(m_StartBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    controls.add(m_StopBut,constraints);
+    setLayout(new BorderLayout());
+    add(controls, BorderLayout.NORTH);
+    add(m_Log, BorderLayout.CENTER);
+  }
+    
+  /**
+   * Creates the panel with the supplied initial experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public RunPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+
+  /**
+   * Sets the experiment the panel operates on.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+    
+    m_Exp = exp;
+    m_StartBut.setEnabled(m_RunThread == null);
+    m_StopBut.setEnabled(m_RunThread != null);
+  }
+  
+  /**
+   * Controls starting and stopping the experiment.
+   *
+   * @param e a value of type 'ActionEvent'
+   */
+  public void actionPerformed(ActionEvent e) {
+
+    if (e.getSource() == m_StartBut) {
+      if (m_RunThread == null) {
+	try {
+	  m_RunThread = new ExperimentRunner(m_Exp);
+	  m_RunThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+	  m_RunThread.start();
+	} catch (Exception ex) {
+          ex.printStackTrace();
+	  logMessage("Problem creating experiment copy to run: "
+		     + ex.getMessage());
+	}
+      }
+    } else if (e.getSource() == m_StopBut) {
+      m_StopBut.setEnabled(false);
+      logMessage("User aborting experiment. ");
+      if (m_Exp instanceof RemoteExperiment) {
+	logMessage("Waiting for remote tasks to "
+		   +"complete...");
+      }
+      ((ExperimentRunner)m_RunThread).abortExperiment();
+      // m_RunThread.stop() ??
+      m_RunThread = null;
+    }
+  }
+  
+  /**
+   * Sends the supplied message to the log panel log area.
+   *
+   * @param message the message to log
+   */
+  protected void logMessage(String message) {
+
+    m_Log.logMessage(message);
+  }
+
+  /**
+   * Sends the supplied message to the log panel status line.
+   *
+   * @param message the status message
+   */
+  protected void statusMessage(String message) {
+    
+    m_Log.statusMessage(message);
+  }
+  
+  /**
+   * Tests out the run panel from the command line.
+   *
+   * @param args may contain options specifying an experiment to run.
+   */
+  public static void main(String [] args) {
+
+    try {
+      boolean readExp = Utils.getFlag('l', args);
+      final String expFile = Utils.getOption('f', args);
+      if (readExp && (expFile.length() == 0)) {
+	throw new Exception("A filename must be given with the -f option");
+      }
+      Experiment exp = null;
+      if (readExp) {
+	FileInputStream fi = new FileInputStream(expFile);
+	ObjectInputStream oi = new ObjectInputStream(
+			       new BufferedInputStream(fi));
+	Object to = oi.readObject();
+	if (to instanceof RemoteExperiment) {
+	  exp = (RemoteExperiment)to;
+	} else {
+	  exp = (Experiment)to;
+	}
+	oi.close();
+      } else {
+	exp = new Experiment();
+      }
+      System.err.println("Initial Experiment:\n" + exp.toString());
+      final JFrame jf = new JFrame("Run Weka Experiment");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final RunPanel sp = new RunPanel(exp);
+      //sp.setBorder(BorderFactory.createTitledBorder("Setup"));
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  System.err.println("\nExperiment Configuration\n"
+			     + sp.m_Exp.toString());
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/SetupModePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/SetupModePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/SetupModePanel.java	(revision 29)
@@ -0,0 +1,169 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SetupModePanel.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.experiment.Experiment;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+/** 
+ * This panel switches between simple and advanced experiment setup panels.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class SetupModePanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3758035565520727822L;
+
+  /** The button for choosing simple setup mode */
+  protected JRadioButton m_SimpleSetupRBut = 
+    new JRadioButton("Simple");
+
+  /** The button for choosing advanced setup mode */
+  protected JRadioButton m_AdvancedSetupRBut = 
+    new JRadioButton("Advanced");  
+
+  /** The simple setup panel */
+  protected SimpleSetupPanel m_simplePanel = new SimpleSetupPanel();
+
+  /** The advanced setup panel */
+  protected SetupPanel m_advancedPanel = new SetupPanel();
+
+  /**
+   * Creates the setup panel with no initial experiment.
+   */
+  public SetupModePanel() {
+
+    m_simplePanel.setModePanel(this);
+
+    m_SimpleSetupRBut.setMnemonic('S');
+    m_SimpleSetupRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  switchToSimple(null);
+	}
+      });
+
+    m_AdvancedSetupRBut.setMnemonic('A');
+    m_AdvancedSetupRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  switchToAdvanced(null);
+	}
+      });
+
+    ButtonGroup modeBG = new ButtonGroup();
+    modeBG.add(m_SimpleSetupRBut);
+    modeBG.add(m_AdvancedSetupRBut);
+    m_SimpleSetupRBut.setSelected(true);
+
+    JPanel modeButtons = new JPanel();
+    modeButtons.setLayout(new GridLayout(1,0));
+    modeButtons.add(m_SimpleSetupRBut);
+    modeButtons.add(m_AdvancedSetupRBut);
+
+    JPanel switchPanel = new JPanel();
+    switchPanel.setLayout(new GridLayout(1,0));
+    switchPanel.add(new JLabel("Experiment Configuration Mode:"));
+    switchPanel.add(modeButtons);
+
+    setLayout(new BorderLayout());
+    add(switchPanel, BorderLayout.NORTH);
+    add(m_simplePanel, BorderLayout.CENTER);
+  }
+
+  /**
+   * Switches to the advanced setup mode.
+   *
+   * @param exp the experiment to configure
+   */
+  public void switchToAdvanced(Experiment exp) {
+ 
+    if (exp == null) {
+      exp = m_simplePanel.getExperiment();
+    }
+    if (exp != null) {
+      m_AdvancedSetupRBut.setSelected(true);
+      m_advancedPanel.setExperiment(exp);
+    }
+    remove(m_simplePanel);
+    m_simplePanel.removeNotesFrame();
+    add(m_advancedPanel, BorderLayout.CENTER);
+    validate();
+    repaint();
+  }
+  
+  /**
+   * Switches to the simple setup mode only if allowed to.
+   *
+   * @param exp the experiment to configure
+   */
+  public void switchToSimple(Experiment exp) {
+    
+    if (exp == null) {
+      exp = m_advancedPanel.getExperiment();
+    }
+    if (exp != null && !m_simplePanel.setExperiment(exp)) {
+      m_AdvancedSetupRBut.setSelected(true);
+      switchToAdvanced(exp);
+    } else {
+      remove(m_advancedPanel);
+      m_advancedPanel.removeNotesFrame();
+      add(m_simplePanel, BorderLayout.CENTER);
+      validate();
+      repaint();
+    }
+  }
+
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+
+    m_simplePanel.addPropertyChangeListener(l);
+    m_advancedPanel.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Gets the currently configured experiment.
+   *
+   * @return the currently configured experiment.
+   */
+  public Experiment getExperiment() {
+
+    if (m_SimpleSetupRBut.isSelected()) return m_simplePanel.getExperiment();
+    else return m_advancedPanel.getExperiment();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/SetupPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/SetupPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/SetupPanel.java	(revision 29)
@@ -0,0 +1,659 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SetupPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.core.Utils;
+import weka.core.xml.KOML;
+import weka.experiment.Experiment;
+import weka.experiment.PropertyNode;
+import weka.experiment.RemoteExperiment;
+import weka.experiment.ResultListener;
+import weka.experiment.ResultProducer;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.PropertyPanel;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel controls the configuration of an experiment.
+ * <p>
+ * If <a href="http://koala.ilog.fr/XML/serialization/" target="_blank">KOML</a>
+ * is in the classpath the experiments can also be saved to XML instead of a
+ * binary format.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz) 
+ * @version $Revision: 5841 $
+ */
+public class SetupPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6552671886903170033L;
+
+  /** The experiment being configured */
+  protected Experiment m_Exp;
+
+  /** Click to load an experiment */
+  protected JButton m_OpenBut = new JButton("Open...");
+
+  /** Click to save an experiment */
+  protected JButton m_SaveBut = new JButton("Save...");
+
+  /** Click to create a new experiment with default settings */
+  protected JButton m_NewBut = new JButton("New");
+
+  /** A filter to ensure only experiment files get shown in the chooser */
+  protected FileFilter m_ExpFilter = 
+    new ExtensionFileFilter(Experiment.FILE_EXTENSION, 
+                            "Experiment configuration files (*" + Experiment.FILE_EXTENSION + ")");
+
+  /** A filter to ensure only experiment (in KOML format) files get shown in the chooser */
+  protected FileFilter m_KOMLFilter = 
+    new ExtensionFileFilter(KOML.FILE_EXTENSION, 
+                            "Experiment configuration files (*" + KOML.FILE_EXTENSION + ")");
+
+  /** A filter to ensure only experiment (in XML format) files get shown in the chooser */
+  protected FileFilter m_XMLFilter = 
+    new ExtensionFileFilter(".xml", 
+                            "Experiment configuration files (*.xml)");
+
+  /** The file chooser for selecting experiments */
+  protected JFileChooser m_FileChooser = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** The ResultProducer editor */
+  protected GenericObjectEditor m_RPEditor = new GenericObjectEditor();
+
+  /** The panel to contain the ResultProducer editor */
+  protected PropertyPanel m_RPEditorPanel = new PropertyPanel(m_RPEditor);
+
+  /** The ResultListener editor */
+  protected GenericObjectEditor m_RLEditor = new GenericObjectEditor();
+
+  /** The panel to contain the ResultListener editor */
+  protected PropertyPanel m_RLEditorPanel = new PropertyPanel(m_RLEditor);
+
+  /** The panel that configures iteration on custom resultproducer property */
+  protected GeneratorPropertyIteratorPanel m_GeneratorPropertyPanel
+    = new GeneratorPropertyIteratorPanel();
+
+  /** The panel for configuring run numbers */
+  protected RunNumberPanel m_RunNumberPanel = new RunNumberPanel();
+
+  /** The panel for enabling a distributed experiment */
+  protected DistributeExperimentPanel m_DistributeExperimentPanel = 
+    new DistributeExperimentPanel();
+
+  /** The panel for configuring selected datasets */
+  protected DatasetListPanel m_DatasetListPanel = new DatasetListPanel();
+
+  /** A button for bringing up the notes */
+  protected JButton m_NotesButton =  new JButton("Notes");
+
+  /** Frame for the notes */
+  protected JFrame m_NotesFrame = new JFrame("Notes");
+
+  /** Area for user notes Default of 10 rows */
+  protected JTextArea m_NotesText = new JTextArea(null, 10, 0);
+
+  /**
+   * Manages sending notifications to people when we change the experiment,
+   * at this stage, only the resultlistener so the resultpanel can update.
+   */
+  protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+
+  /** Click to advacne data set before custom generator */
+  protected JRadioButton m_advanceDataSetFirst = 
+    new JRadioButton("Data sets first");
+
+  /** Click to advance custom generator before data set */
+  protected JRadioButton m_advanceIteratorFirst = 
+    new JRadioButton("Custom generator first");
+
+  /** Handle radio buttons */
+  ActionListener m_RadioListener = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	updateRadioLinks();
+      }
+    };
+  
+  // Registers the appropriate property editors
+  static {
+    GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the setup panel with the supplied initial experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public SetupPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+  
+  /**
+   * Creates the setup panel with no initial experiment.
+   */
+  public SetupPanel() {
+
+    m_DistributeExperimentPanel.
+      addCheckBoxActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    if (m_DistributeExperimentPanel.distributedExperimentSelected()) {
+	      if (!(m_Exp instanceof RemoteExperiment)) {
+		try {
+		  RemoteExperiment re = new RemoteExperiment(m_Exp);
+		  setExperiment(re);
+		} catch (Exception ex) {
+		  ex.printStackTrace();
+		}
+	      }
+	    } else {
+	      if (m_Exp instanceof RemoteExperiment) {
+		setExperiment(((RemoteExperiment)m_Exp).getBaseExperiment());
+	      }
+	    }
+	  }
+	});	      
+
+    m_NewBut.setMnemonic('N');
+    m_NewBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setExperiment(new Experiment());
+      }
+    });
+    m_SaveBut.setMnemonic('S');
+    m_SaveBut.setEnabled(false);
+    m_SaveBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	saveExperiment();
+      }
+    });
+    m_OpenBut.setMnemonic('O');
+    m_OpenBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	openExperiment();
+      }
+    });
+    m_FileChooser.addChoosableFileFilter(m_ExpFilter);
+    if (KOML.isPresent())
+       m_FileChooser.addChoosableFileFilter(m_KOMLFilter);
+    m_FileChooser.addChoosableFileFilter(m_XMLFilter);
+    m_FileChooser.setFileFilter(m_ExpFilter);
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    
+    m_GeneratorPropertyPanel.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  updateRadioLinks();
+	}
+      });
+
+    m_RPEditor.setClassType(ResultProducer.class);
+    m_RPEditor.setEnabled(false);
+    m_RPEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	m_Exp.setResultProducer((ResultProducer) m_RPEditor.getValue());
+	m_Exp.setUsePropertyIterator(false);
+	m_Exp.setPropertyArray(null);
+	m_Exp.setPropertyPath(null);
+	m_GeneratorPropertyPanel.setExperiment(m_Exp);
+	repaint();
+      }
+    });
+
+    m_RLEditor.setClassType(ResultListener.class);
+    m_RLEditor.setEnabled(false);
+    m_RLEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	m_Exp.setResultListener((ResultListener) m_RLEditor.getValue());
+	m_Support.firePropertyChange("", null, null);
+	repaint();
+      }
+    });
+
+    m_NotesFrame.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  m_NotesButton.setEnabled(true);
+	}
+      });
+    m_NotesFrame.getContentPane().add(new JScrollPane(m_NotesText));
+    m_NotesFrame.setSize(600, 400);
+
+    m_NotesButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_NotesButton.setEnabled(false);
+	  m_NotesFrame.setVisible(true);
+	}
+      });
+    m_NotesButton.setEnabled(false);
+
+    m_NotesText.setEditable(true);
+    //m_NotesText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_NotesText.addKeyListener(new KeyAdapter() {
+	public void keyReleased(KeyEvent e) {
+	  m_Exp.setNotes(m_NotesText.getText());
+	}
+      });
+    m_NotesText.addFocusListener(new FocusAdapter() {
+	public void focusLost(FocusEvent e) {
+	  m_Exp.setNotes(m_NotesText.getText());
+	}
+      });
+
+    // Set up the GUI layout
+    JPanel buttons = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    buttons.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    //    buttons.setLayout(new GridLayout(1, 3, 5, 5));
+    buttons.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    buttons.add(m_OpenBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    buttons.add(m_SaveBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    buttons.add(m_NewBut,constraints);
+    
+    JPanel src = new JPanel();
+    src.setLayout(new BorderLayout());
+    src.setBorder(BorderFactory.createCompoundBorder(
+		  BorderFactory.createTitledBorder("Result generator"),
+		  BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		  ));
+    src.add(m_RPEditorPanel, BorderLayout.NORTH);
+    m_RPEditorPanel.setEnabled(false);
+
+    JPanel dest = new JPanel();
+    dest.setLayout(new BorderLayout());
+    dest.setBorder(BorderFactory.createCompoundBorder(
+		   BorderFactory.createTitledBorder("Destination"),
+		   BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		   ));
+    dest.add(m_RLEditorPanel, BorderLayout.NORTH);
+    m_RLEditorPanel.setEnabled(false);
+
+    m_advanceDataSetFirst.setEnabled(false);
+    m_advanceIteratorFirst.setEnabled(false);
+    m_advanceDataSetFirst.
+      setToolTipText("Advance data set before custom generator");
+    m_advanceIteratorFirst.
+      setToolTipText("Advance custom generator before data set");
+    m_advanceDataSetFirst.setSelected(true);
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_advanceDataSetFirst);
+    bg.add(m_advanceIteratorFirst);
+    m_advanceDataSetFirst.addActionListener(m_RadioListener);
+    m_advanceIteratorFirst.addActionListener(m_RadioListener);
+
+    JPanel radioButs = new JPanel();
+    radioButs.setBorder(BorderFactory.
+			createTitledBorder("Iteration control"));
+    radioButs.setLayout(new GridLayout(1, 2));
+    radioButs.add(m_advanceDataSetFirst);
+    radioButs.add(m_advanceIteratorFirst);
+
+    JPanel simpleIterators = new JPanel();
+    simpleIterators.setLayout(new BorderLayout());
+    
+    JPanel tmp = new JPanel();
+    tmp.setLayout(new GridBagLayout());
+    //    tmp.setLayout(new GridLayout(1, 2));
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    tmp.add(m_RunNumberPanel,constraints);
+    
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=2;
+    tmp.add(m_DistributeExperimentPanel, constraints);
+
+    JPanel tmp2 = new JPanel();
+    //    tmp2.setLayout(new GridLayout(2, 1));
+    tmp2.setLayout(new GridBagLayout());
+
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    tmp2.add(tmp,constraints);
+
+    constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    tmp2.add(radioButs,constraints);
+		   
+
+    simpleIterators.add(tmp2, BorderLayout.NORTH);
+    simpleIterators.add(m_DatasetListPanel, BorderLayout.CENTER);
+    JPanel iterators = new JPanel();
+    iterators.setLayout(new GridLayout(1, 2));
+    iterators.add(simpleIterators);
+    iterators.add(m_GeneratorPropertyPanel);
+
+    JPanel top = new JPanel();
+    top.setLayout(new GridLayout(2, 1));
+    top.add(dest);
+    top.add(src);
+
+    JPanel notes = new JPanel();
+    notes.setLayout(new BorderLayout());
+    notes.add(m_NotesButton, BorderLayout.CENTER);
+    
+    JPanel p2 = new JPanel();
+    //    p2.setLayout(new GridLayout(2, 1));
+    p2.setLayout(new BorderLayout());
+    p2.add(iterators, BorderLayout.CENTER);
+    p2.add(notes, BorderLayout.SOUTH);
+
+    JPanel p3 = new JPanel();
+    p3.setLayout(new BorderLayout());
+    p3.add(buttons, BorderLayout.NORTH);
+    p3.add(top, BorderLayout.SOUTH);
+    setLayout(new BorderLayout());
+    add(p3, BorderLayout.NORTH);
+    add(p2, BorderLayout.CENTER);
+  }
+    
+  /**
+   * Deletes the notes frame.
+   */
+  protected void removeNotesFrame() {
+    m_NotesFrame.setVisible(false);
+  }
+
+  /**
+   * Sets the experiment to configure.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public void setExperiment(Experiment exp) {
+
+    boolean iteratorOn = exp.getUsePropertyIterator();
+    Object propArray = exp.getPropertyArray();
+    PropertyNode [] propPath = exp.getPropertyPath();
+
+    m_Exp = exp;
+    m_SaveBut.setEnabled(true);
+    m_RPEditor.setValue(m_Exp.getResultProducer());
+    m_RPEditor.setEnabled(true);
+    m_RPEditorPanel.setEnabled(true);
+    m_RPEditorPanel.repaint();
+    m_RLEditor.setValue(m_Exp.getResultListener());
+    m_RLEditor.setEnabled(true);
+    m_RLEditorPanel.setEnabled(true);
+    m_RLEditorPanel.repaint();
+
+    m_NotesText.setText(exp.getNotes());
+    m_NotesButton.setEnabled(true);
+
+    m_advanceDataSetFirst.setSelected(m_Exp.getAdvanceDataSetFirst());
+    m_advanceIteratorFirst.setSelected(!m_Exp.getAdvanceDataSetFirst());
+    m_advanceDataSetFirst.setEnabled(true);
+    m_advanceIteratorFirst.setEnabled(true);
+
+    exp.setPropertyPath(propPath);
+    exp.setPropertyArray(propArray);
+    exp.setUsePropertyIterator(iteratorOn);
+
+    m_GeneratorPropertyPanel.setExperiment(m_Exp);   
+    m_RunNumberPanel.setExperiment(m_Exp);
+    m_DatasetListPanel.setExperiment(m_Exp);
+    m_DistributeExperimentPanel.setExperiment(m_Exp);
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Gets the currently configured experiment.
+   *
+   * @return the currently configured experiment.
+   */
+  public Experiment getExperiment() {
+
+    return m_Exp;
+  }
+  
+  /**
+   * Prompts the user to select an experiment file and loads it.
+   */
+  private void openExperiment() {
+    
+    int returnVal = m_FileChooser.showOpenDialog(this);
+    if (returnVal != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    File expFile = m_FileChooser.getSelectedFile();
+    
+    // add extension if necessary
+    if (m_FileChooser.getFileFilter() == m_ExpFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(Experiment.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + Experiment.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + KOML.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(".xml"))
+        expFile = new File(expFile.getParent(), expFile.getName() + ".xml");
+    }
+    
+    try {
+      Experiment exp = Experiment.read(expFile.getAbsolutePath());
+      setExperiment(exp);
+      System.err.println("Opened experiment:\n" + m_Exp);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      JOptionPane.showMessageDialog(this, "Couldn't open experiment file:\n"
+				    + expFile
+				    + "\nReason:\n" + ex.getMessage(),
+				    "Open Experiment",
+				    JOptionPane.ERROR_MESSAGE);
+      // Pop up error dialog
+    }
+  }
+
+  /**
+   * Prompts the user for a filename to save the experiment to, then saves
+   * the experiment.
+   */
+  private void saveExperiment() {
+
+    int returnVal = m_FileChooser.showSaveDialog(this);
+    if (returnVal != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    File expFile = m_FileChooser.getSelectedFile();
+    
+    // add extension if necessary
+    if (m_FileChooser.getFileFilter() == m_ExpFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(Experiment.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + Experiment.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + KOML.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(".xml"))
+        expFile = new File(expFile.getParent(), expFile.getName() + ".xml");
+    }
+    
+    try {
+      Experiment.write(expFile.getAbsolutePath(), m_Exp);
+      System.err.println("Saved experiment:\n" + m_Exp);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      JOptionPane.showMessageDialog(this, "Couldn't save experiment file:\n"
+				    + expFile
+				    + "\nReason:\n" + ex.getMessage(),
+				    "Save Experiment",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    m_Support.removePropertyChangeListener(l);
+  }
+
+  /**
+   * Updates the primary loop iteration control of the experiment
+   */
+  private void updateRadioLinks() {
+    
+    m_advanceDataSetFirst.
+      setEnabled(m_GeneratorPropertyPanel.getEditorActive());
+    m_advanceIteratorFirst.
+      setEnabled(m_GeneratorPropertyPanel.getEditorActive());
+
+    if (m_Exp != null) {
+      if (!m_GeneratorPropertyPanel.getEditorActive()) {
+	m_Exp.setAdvanceDataSetFirst(true);
+      } else {
+	m_Exp.setAdvanceDataSetFirst(m_advanceDataSetFirst.isSelected());
+      }
+    }
+  }
+
+  /**
+   * Tests out the experiment setup from the command line.
+   *
+   * @param args arguments to the program.
+   */
+  public static void main(String [] args) {
+
+    try {
+      boolean readExp = Utils.getFlag('l', args);
+      final boolean writeExp = Utils.getFlag('s', args);
+      final String expFile = Utils.getOption('f', args);
+      if ((readExp || writeExp) && (expFile.length() == 0)) {
+	throw new Exception("A filename must be given with the -f option");
+      }
+      Experiment exp = null;
+      if (readExp) {
+	FileInputStream fi = new FileInputStream(expFile);
+	ObjectInputStream oi = new ObjectInputStream(
+			       new BufferedInputStream(fi));
+	exp = (Experiment)oi.readObject();
+	oi.close();
+      } else {
+	exp = new Experiment();
+      }
+      System.err.println("Initial Experiment:\n" + exp.toString());
+      final JFrame jf = new JFrame("Weka Experiment Setup");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final SetupPanel sp = new SetupPanel();
+      //sp.setBorder(BorderFactory.createTitledBorder("Setup"));
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  System.err.println("\nFinal Experiment:\n"
+			     + sp.m_Exp.toString());
+	  // Save the experiment to a file
+	  if (writeExp) {
+	    try {
+	      FileOutputStream fo = new FileOutputStream(expFile);
+	      ObjectOutputStream oo = new ObjectOutputStream(
+				      new BufferedOutputStream(fo));
+	      oo.writeObject(sp.m_Exp);
+	      oo.close();
+	    } catch (Exception ex) {
+              ex.printStackTrace();
+	      System.err.println("Couldn't write experiment to: " + expFile
+				 + '\n' + ex.getMessage());
+	    }
+	  }
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      System.err.println("Short nap");
+      Thread.currentThread().sleep(3000);
+      System.err.println("Done");
+      sp.setExperiment(exp);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/experiment/SimpleSetupPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/experiment/SimpleSetupPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/experiment/SimpleSetupPanel.java	(revision 29)
@@ -0,0 +1,1218 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SimpleSetupPanel.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.experiment;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.core.xml.KOML;
+import weka.experiment.CSVResultListener;
+import weka.experiment.ClassifierSplitEvaluator;
+import weka.experiment.CrossValidationResultProducer;
+import weka.experiment.DatabaseResultListener;
+import weka.experiment.Experiment;
+import weka.experiment.InstancesResultListener;
+import weka.experiment.PropertyNode;
+import weka.experiment.RandomSplitResultProducer;
+import weka.experiment.RegressionSplitEvaluator;
+import weka.experiment.SplitEvaluator;
+import weka.gui.DatabaseConnectionDialog;
+import weka.gui.ExtensionFileFilter;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.IntrospectionException;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.beans.PropertyDescriptor;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel controls the configuration of an experiment.
+  * <p>
+ * If <a href="http://koala.ilog.fr/XML/serialization/" target="_blank">KOML</a>
+ * is in the classpath the experiments can also be serialized to XML instead of a
+ * binary format.
+*
+ * @author Richard kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz) 
+ * @version $Revision: 5928 $
+ */
+public class SimpleSetupPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5257424515609176509L;
+
+  /** The experiment being configured */
+  protected Experiment m_Exp;
+
+  /** The panel which switched between simple and advanced setup modes */
+  protected SetupModePanel m_modePanel = null;
+
+  /** The database destination URL to store results into */
+  protected String m_destinationDatabaseURL;
+
+  /** The filename to store results into */
+  protected String m_destinationFilename = "";
+
+  /** The number of folds for a cross-validation experiment */
+  protected int m_numFolds = 10;
+
+  /** The training percentage for a train/test split experiment */
+  protected double m_trainPercent = 66;
+
+  /** The number of times to repeat the sub-experiment */
+  protected int m_numRepetitions = 10;
+
+  /** Whether or not the user has consented for the experiment to be simplified */
+  protected boolean m_userHasBeenAskedAboutConversion;
+
+  /** Filter for choosing CSV files */
+  protected ExtensionFileFilter m_csvFileFilter =
+    new ExtensionFileFilter(".csv", "Comma separated value files");
+
+  /** FIlter for choosing ARFF files */
+  protected ExtensionFileFilter m_arffFileFilter =
+    new ExtensionFileFilter(".arff", "ARFF files");
+
+  /** Click to load an experiment */
+  protected JButton m_OpenBut = new JButton("Open...");
+
+  /** Click to save an experiment */
+  protected JButton m_SaveBut = new JButton("Save...");
+
+  /** Click to create a new experiment with default settings */
+  protected JButton m_NewBut = new JButton("New");
+
+  /** A filter to ensure only experiment files get shown in the chooser */
+  protected FileFilter m_ExpFilter = 
+    new ExtensionFileFilter(Experiment.FILE_EXTENSION, 
+                            "Experiment configuration files (*" + Experiment.FILE_EXTENSION + ")");
+
+  /** A filter to ensure only experiment (in KOML format) files get shown in the chooser */
+  protected FileFilter m_KOMLFilter = 
+    new ExtensionFileFilter(KOML.FILE_EXTENSION, 
+                            "Experiment configuration files (*" + KOML.FILE_EXTENSION + ")");
+
+  /** A filter to ensure only experiment (in XML format) files get shown in the chooser */
+  protected FileFilter m_XMLFilter = 
+    new ExtensionFileFilter(".xml", 
+                            "Experiment configuration files (*.xml)");
+
+  /** The file chooser for selecting experiments */
+  protected JFileChooser m_FileChooser =
+    new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** The file chooser for selecting result destinations */
+  protected JFileChooser m_DestFileChooser =
+    new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** Combo box for choosing experiment destination type */
+  protected JComboBox m_ResultsDestinationCBox = new JComboBox();
+
+  /** Label for destination field */
+  protected JLabel m_ResultsDestinationPathLabel = new JLabel("Filename:");
+
+  /** Input field for result destination path */ 
+  protected JTextField m_ResultsDestinationPathTField = new JTextField();
+
+  /** Button for browsing destination files */
+  protected JButton m_BrowseDestinationButton = new JButton("Browse...");
+
+  /** Combo box for choosing experiment type */
+  protected JComboBox m_ExperimentTypeCBox = new JComboBox();
+
+  /** Label for parameter field */
+  protected JLabel m_ExperimentParameterLabel = new JLabel("Number of folds:");
+
+  /** Input field for experiment parameter */
+  protected JTextField m_ExperimentParameterTField = new JTextField(); 
+
+  /** Radio button for choosing classification experiment */
+  protected JRadioButton m_ExpClassificationRBut = 
+    new JRadioButton("Classification");
+
+  /** Radio button for choosing regression experiment */
+  protected JRadioButton m_ExpRegressionRBut = 
+    new JRadioButton("Regression");
+
+  /** Input field for number of repetitions */
+  protected JTextField m_NumberOfRepetitionsTField = new JTextField();  
+
+  /** Radio button for choosing datasets first in order of execution */ 
+  protected JRadioButton m_OrderDatasetsFirstRBut = 
+    new JRadioButton("Data sets first");
+
+  /** Radio button for choosing algorithms first in order of execution */ 
+  protected JRadioButton m_OrderAlgorithmsFirstRBut = 
+    new JRadioButton("Algorithms first");
+
+  /** The strings used to identify the combo box choices */
+  protected static String DEST_DATABASE_TEXT = ("JDBC database");
+  protected static String DEST_ARFF_TEXT = ("ARFF file");
+  protected static String DEST_CSV_TEXT = ("CSV file");
+  protected static String TYPE_CROSSVALIDATION_TEXT = ("Cross-validation");
+  protected static String TYPE_RANDOMSPLIT_TEXT = ("Train/Test Percentage Split (data randomized)");
+  protected static String TYPE_FIXEDSPLIT_TEXT = ("Train/Test Percentage Split (order preserved)");
+
+  /** The panel for configuring selected datasets */
+  protected DatasetListPanel m_DatasetListPanel = new DatasetListPanel();
+
+  /** The panel for configuring selected algorithms */
+  protected AlgorithmListPanel m_AlgorithmListPanel = new AlgorithmListPanel();
+
+  /** A button for bringing up the notes */
+  protected JButton m_NotesButton =  new JButton("Notes");
+
+  /** Frame for the notes */
+  protected JFrame m_NotesFrame = new JFrame("Notes");
+
+  /** Area for user notes Default of 10 rows */
+  protected JTextArea m_NotesText = new JTextArea(null, 10, 0);
+
+  /**
+   * Manages sending notifications to people when we change the experiment,
+   * at this stage, only the resultlistener so the resultpanel can update.
+   */
+  protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+  
+  /**
+   * Creates the setup panel with the supplied initial experiment.
+   *
+   * @param exp a value of type 'Experiment'
+   */
+  public SimpleSetupPanel(Experiment exp) {
+
+    this();
+    setExperiment(exp);
+  }
+  
+  /**
+   * Creates the setup panel with no initial experiment.
+   */
+  public SimpleSetupPanel() {
+
+    // everything disabled on startup
+    m_ResultsDestinationCBox.setEnabled(false);
+    m_ResultsDestinationPathLabel.setEnabled(false);
+    m_ResultsDestinationPathTField.setEnabled(false);
+    m_BrowseDestinationButton.setEnabled(false); 
+    m_ExperimentTypeCBox.setEnabled(false);
+    m_ExperimentParameterLabel.setEnabled(false);
+    m_ExperimentParameterTField.setEnabled(false);
+    m_ExpClassificationRBut.setEnabled(false);
+    m_ExpRegressionRBut.setEnabled(false);
+    m_NumberOfRepetitionsTField.setEnabled(false);
+    m_OrderDatasetsFirstRBut.setEnabled(false);
+    m_OrderAlgorithmsFirstRBut.setEnabled(false);
+
+    // get sensible default database address
+    try {
+      m_destinationDatabaseURL = (new DatabaseResultListener()).getDatabaseURL();
+    } catch (Exception e) {}
+
+    // create action listeners
+    m_NewBut.setMnemonic('N');
+    m_NewBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  Experiment newExp = new Experiment();
+	  CrossValidationResultProducer cvrp = new CrossValidationResultProducer();
+	  cvrp.setNumFolds(10);
+	  cvrp.setSplitEvaluator(new ClassifierSplitEvaluator());
+	  newExp.setResultProducer(cvrp);
+	  newExp.setPropertyArray(new Classifier[0]);
+	  newExp.setUsePropertyIterator(true);
+	  setExperiment(newExp);
+
+          // defaults
+          if (ExperimenterDefaults.getUseClassification())
+            m_ExpClassificationRBut.setSelected(true);
+          else
+            m_ExpRegressionRBut.setSelected(true);
+          
+          setSelectedItem(
+              m_ResultsDestinationCBox, ExperimenterDefaults.getDestination());
+          destinationTypeChanged();
+          
+          setSelectedItem(
+              m_ExperimentTypeCBox, ExperimenterDefaults.getExperimentType());
+          
+          m_numRepetitions = ExperimenterDefaults.getRepetitions();
+          m_NumberOfRepetitionsTField.setText(
+              "" + m_numRepetitions);
+          
+          if (ExperimenterDefaults.getExperimentType().equals(
+                TYPE_CROSSVALIDATION_TEXT)) {
+            m_numFolds = ExperimenterDefaults.getFolds();
+            m_ExperimentParameterTField.setText(
+                "" + m_numFolds);
+          }
+          else {
+            m_trainPercent = ExperimenterDefaults.getTrainPercentage();
+            m_ExperimentParameterTField.setText(
+                "" + m_trainPercent);
+          }
+          
+          if (ExperimenterDefaults.getDatasetsFirst())
+            m_OrderDatasetsFirstRBut.setSelected(true);
+          else
+            m_OrderAlgorithmsFirstRBut.setSelected(true);
+
+          expTypeChanged();
+	}
+      });
+    m_SaveBut.setEnabled(false);
+    m_SaveBut.setMnemonic('S');
+    m_SaveBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  saveExperiment();
+	}
+      });
+    m_OpenBut.setMnemonic('O');
+    m_OpenBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  openExperiment();
+	}
+      });
+    m_FileChooser.addChoosableFileFilter(m_ExpFilter);
+    if (KOML.isPresent())
+       m_FileChooser.addChoosableFileFilter(m_KOMLFilter);
+    m_FileChooser.addChoosableFileFilter(m_XMLFilter);
+    if (ExperimenterDefaults.getExtension().equals(".xml"))
+      m_FileChooser.setFileFilter(m_XMLFilter);
+    else if (KOML.isPresent() && ExperimenterDefaults.getExtension().equals(KOML.FILE_EXTENSION))
+      m_FileChooser.setFileFilter(m_KOMLFilter);
+    else
+      m_FileChooser.setFileFilter(m_ExpFilter);
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    m_DestFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    m_BrowseDestinationButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  //using this button for both browsing file & setting username/password
+	  if (m_ResultsDestinationCBox.getSelectedItem() == DEST_DATABASE_TEXT){
+	    chooseURLUsername();
+	  } else {
+	    chooseDestinationFile();
+	  }
+	}
+      });
+
+    m_ExpClassificationRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  expTypeChanged();
+	}
+      });
+  
+    m_ExpRegressionRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  expTypeChanged();
+	}
+      });
+
+    m_OrderDatasetsFirstRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_Exp != null) {
+	    m_Exp.setAdvanceDataSetFirst(true);
+	    m_Support.firePropertyChange("", null, null);
+	  }
+	}
+      });
+    
+    m_OrderAlgorithmsFirstRBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  if (m_Exp != null) {
+	    m_Exp.setAdvanceDataSetFirst(false);
+	    m_Support.firePropertyChange("", null, null);
+	  }
+	}
+      });
+
+    m_ResultsDestinationPathTField.getDocument().addDocumentListener(new DocumentListener() {
+	public void insertUpdate(DocumentEvent e) {destinationAddressChanged();}
+	public void removeUpdate(DocumentEvent e) {destinationAddressChanged();}
+	public void changedUpdate(DocumentEvent e) {destinationAddressChanged();}
+      });
+
+    m_ExperimentParameterTField.getDocument().addDocumentListener(new DocumentListener() {
+	public void insertUpdate(DocumentEvent e) {expParamChanged();}
+	public void removeUpdate(DocumentEvent e) {expParamChanged();}
+	public void changedUpdate(DocumentEvent e) {expParamChanged();}
+      });
+
+    m_NumberOfRepetitionsTField.getDocument().addDocumentListener(new DocumentListener() {
+	public void insertUpdate(DocumentEvent e) {numRepetitionsChanged();}
+	public void removeUpdate(DocumentEvent e) {numRepetitionsChanged();}
+	public void changedUpdate(DocumentEvent e) {numRepetitionsChanged();}
+      });
+
+    m_NotesFrame.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  m_NotesButton.setEnabled(true);
+	}
+      });
+    m_NotesFrame.getContentPane().add(new JScrollPane(m_NotesText));
+    m_NotesFrame.setSize(600, 400);
+
+    m_NotesButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_NotesButton.setEnabled(false);
+	  m_NotesFrame.setVisible(true);
+	}
+      });
+    m_NotesButton.setEnabled(false);
+
+    m_NotesText.setEditable(true);
+    //m_NotesText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_NotesText.addKeyListener(new KeyAdapter() {
+	public void keyReleased(KeyEvent e) {
+	  m_Exp.setNotes(m_NotesText.getText());
+	}
+      });
+    m_NotesText.addFocusListener(new FocusAdapter() {
+	public void focusLost(FocusEvent e) {
+	  m_Exp.setNotes(m_NotesText.getText());
+	}
+      });
+    
+    // Set up the GUI layout
+    JPanel buttons = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+    buttons.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    buttons.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    buttons.add(m_OpenBut,constraints);
+    constraints.gridx=1;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    buttons.add(m_SaveBut,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    buttons.add(m_NewBut,constraints);
+
+    JPanel destName = new JPanel();
+    destName.setLayout(new BorderLayout(5, 5));
+    destName.add(m_ResultsDestinationPathLabel, BorderLayout.WEST);
+    destName.add(m_ResultsDestinationPathTField, BorderLayout.CENTER);
+    
+    m_ResultsDestinationCBox.addItem(DEST_ARFF_TEXT);
+    m_ResultsDestinationCBox.addItem(DEST_CSV_TEXT);
+    m_ResultsDestinationCBox.addItem(DEST_DATABASE_TEXT);
+    
+    m_ResultsDestinationCBox.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  destinationTypeChanged();
+	}
+      });
+
+    JPanel destInner = new JPanel();
+    destInner.setLayout(new BorderLayout(5, 5));
+    destInner.add(m_ResultsDestinationCBox, BorderLayout.WEST);
+    destInner.add(destName, BorderLayout.CENTER);
+    destInner.add(m_BrowseDestinationButton, BorderLayout.EAST);
+
+    JPanel dest = new JPanel();
+    dest.setLayout(new BorderLayout());
+    dest.setBorder(BorderFactory.createCompoundBorder(
+		  BorderFactory.createTitledBorder("Results Destination"),
+		  BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		  ));
+    dest.add(destInner, BorderLayout.NORTH);
+
+    JPanel expParam = new JPanel();
+    expParam.setLayout(new BorderLayout(5, 5));
+    expParam.add(m_ExperimentParameterLabel, BorderLayout.WEST);
+    expParam.add(m_ExperimentParameterTField, BorderLayout.CENTER);
+
+    ButtonGroup typeBG = new ButtonGroup();
+    typeBG.add(m_ExpClassificationRBut);
+    typeBG.add(m_ExpRegressionRBut);
+    m_ExpClassificationRBut.setSelected(true);
+
+    JPanel typeRButtons = new JPanel();
+    typeRButtons.setLayout(new GridLayout(1,0));
+    typeRButtons.add(m_ExpClassificationRBut);
+    typeRButtons.add(m_ExpRegressionRBut);
+
+    m_ExperimentTypeCBox.addItem(TYPE_CROSSVALIDATION_TEXT);
+    m_ExperimentTypeCBox.addItem(TYPE_RANDOMSPLIT_TEXT);
+    m_ExperimentTypeCBox.addItem(TYPE_FIXEDSPLIT_TEXT);
+
+    m_ExperimentTypeCBox.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  expTypeChanged();
+	}
+      });
+
+    JPanel typeInner = new JPanel();
+    typeInner.setLayout(new GridLayout(0,1));
+    typeInner.add(m_ExperimentTypeCBox);
+    typeInner.add(expParam);
+    typeInner.add(typeRButtons);
+
+    JPanel type = new JPanel();
+    type.setLayout(new BorderLayout());
+    type.setBorder(BorderFactory.createCompoundBorder(
+		  BorderFactory.createTitledBorder("Experiment Type"),
+		  BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		  ));
+    type.add(typeInner, BorderLayout.NORTH);
+
+    ButtonGroup iterBG = new ButtonGroup();
+    iterBG.add(m_OrderDatasetsFirstRBut);
+    iterBG.add(m_OrderAlgorithmsFirstRBut);
+    m_OrderDatasetsFirstRBut.setSelected(true);
+
+    JPanel numIter = new JPanel();
+    numIter.setLayout(new BorderLayout(5, 5));
+    numIter.add(new JLabel("Number of repetitions:"), BorderLayout.WEST);
+    numIter.add(m_NumberOfRepetitionsTField, BorderLayout.CENTER);
+
+    JPanel controlInner = new JPanel();
+    controlInner.setLayout(new GridLayout(0,1));
+    controlInner.add(numIter);
+    controlInner.add(m_OrderDatasetsFirstRBut);
+    controlInner.add(m_OrderAlgorithmsFirstRBut);
+
+    JPanel control = new JPanel();
+    control.setLayout(new BorderLayout());
+    control.setBorder(BorderFactory.createCompoundBorder(
+		  BorderFactory.createTitledBorder("Iteration Control"),
+		  BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		  ));
+    control.add(controlInner, BorderLayout.NORTH);
+
+    JPanel type_control = new JPanel();
+    type_control.setLayout(new GridLayout(1,0));
+    type_control.add(type);
+    type_control.add(control);
+
+    JPanel notes = new JPanel();
+    notes.setLayout(new BorderLayout());
+    notes.add(m_NotesButton, BorderLayout.CENTER);
+
+    JPanel top1 = new JPanel();
+    top1.setLayout(new BorderLayout());
+    top1.add(dest, BorderLayout.NORTH);
+    top1.add(type_control, BorderLayout.CENTER);
+
+    JPanel top = new JPanel();
+    top.setLayout(new BorderLayout());
+    top.add(buttons, BorderLayout.NORTH);
+    top.add(top1, BorderLayout.CENTER);  
+
+    JPanel datasets = new JPanel();
+    datasets.setLayout(new BorderLayout());
+    datasets.add(m_DatasetListPanel, BorderLayout.CENTER);
+
+    JPanel algorithms = new JPanel();
+    algorithms.setLayout(new BorderLayout());
+    algorithms.add(m_AlgorithmListPanel, BorderLayout.CENTER);
+
+    JPanel schemes = new JPanel();
+    schemes.setLayout(new GridLayout(1,0));
+    schemes.add(datasets);
+    schemes.add(algorithms);
+
+    setLayout(new BorderLayout());
+    add(top, BorderLayout.NORTH);
+    add(schemes, BorderLayout.CENTER);
+    add(notes, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * Sets the selected item of an combobox, since using setSelectedItem(...)
+   * doesn't work, if one checks object references!
+   *
+   * @param cb      the combobox to set the item for
+   * @param item    the item to set active
+   */
+  protected void setSelectedItem(JComboBox cb, String item) {
+    int       i;
+
+    for (i = 0; i < cb.getItemCount(); i++) {
+      if (cb.getItemAt(i).toString().equals(item)) {
+        cb.setSelectedIndex(i);
+        break;
+      }
+    }
+  }
+  
+  /**
+   * Deletes the notes frame.
+   */
+  protected void removeNotesFrame() {
+    m_NotesFrame.setVisible(false);
+  }
+
+  /**
+   * Gets te users consent for converting the experiment to a simpler form.
+   *
+   * @return true if the user has given consent, false otherwise
+   */  
+  private boolean userWantsToConvert() {
+    
+    if (m_userHasBeenAskedAboutConversion) return true;
+    m_userHasBeenAskedAboutConversion = true;
+    return (JOptionPane.showConfirmDialog(this,
+					  "This experiment has settings that are too advanced\n" +
+					  "to be represented in the simple setup mode.\n" +
+					  "Do you want the experiment to be converted,\n" +
+					  "losing some of the advanced settings?\n",
+					  "Confirm conversion",
+					  JOptionPane.YES_NO_OPTION,
+					  JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION);
+  }
+
+  /**
+   * Sets the panel used to switch between simple and advanced modes.
+   *
+   * @param modePanel the panel
+   */
+  public void setModePanel(SetupModePanel modePanel) {
+
+    m_modePanel = modePanel;
+  }
+
+  /**
+   * Sets the experiment to configure.
+   *
+   * @param exp a value of type 'Experiment'
+   * @return true if experiment could be configured, false otherwise
+   */
+  public boolean setExperiment(Experiment exp) {
+    
+    m_userHasBeenAskedAboutConversion = false;
+    m_Exp = null; // hold off until we are sure we want conversion
+    m_SaveBut.setEnabled(true);
+
+    if (exp.getResultListener() instanceof DatabaseResultListener) {
+      m_ResultsDestinationCBox.setSelectedItem(DEST_DATABASE_TEXT);
+      m_ResultsDestinationPathLabel.setText("URL:");
+      m_destinationDatabaseURL = ((DatabaseResultListener)exp.getResultListener()).getDatabaseURL();
+      m_ResultsDestinationPathTField.setText(m_destinationDatabaseURL);
+      m_BrowseDestinationButton.setEnabled(true);
+    } else if (exp.getResultListener() instanceof InstancesResultListener) {
+      m_ResultsDestinationCBox.setSelectedItem(DEST_ARFF_TEXT);
+      m_ResultsDestinationPathLabel.setText("Filename:");
+      m_destinationFilename = ((InstancesResultListener)exp.getResultListener()).outputFileName();
+      m_ResultsDestinationPathTField.setText(m_destinationFilename);
+      m_BrowseDestinationButton.setEnabled(true);
+    } else if (exp.getResultListener() instanceof CSVResultListener) {
+      m_ResultsDestinationCBox.setSelectedItem(DEST_CSV_TEXT);
+      m_ResultsDestinationPathLabel.setText("Filename:");
+      m_destinationFilename = ((CSVResultListener)exp.getResultListener()).outputFileName();
+      m_ResultsDestinationPathTField.setText(m_destinationFilename);
+      m_BrowseDestinationButton.setEnabled(true);
+    } else {
+      // unrecognised result listener 
+      System.out.println("SimpleSetup incompatibility: unrecognised result destination");
+      if (userWantsToConvert()) {
+	m_ResultsDestinationCBox.setSelectedItem(DEST_ARFF_TEXT);
+	m_ResultsDestinationPathLabel.setText("Filename:");
+	m_destinationFilename = "";
+	m_ResultsDestinationPathTField.setText(m_destinationFilename);
+	m_BrowseDestinationButton.setEnabled(true);
+      } else {
+	return false;
+      }
+    }
+    m_ResultsDestinationCBox.setEnabled(true);
+    m_ResultsDestinationPathLabel.setEnabled(true);
+    m_ResultsDestinationPathTField.setEnabled(true);
+
+    if (exp.getResultProducer() instanceof CrossValidationResultProducer) {
+      CrossValidationResultProducer cvrp = (CrossValidationResultProducer) exp.getResultProducer();
+      m_numFolds = cvrp.getNumFolds();
+      m_ExperimentParameterTField.setText("" + m_numFolds);
+      
+      if (cvrp.getSplitEvaluator() instanceof ClassifierSplitEvaluator) {
+	m_ExpClassificationRBut.setSelected(true);
+	m_ExpRegressionRBut.setSelected(false);
+      } else if (cvrp.getSplitEvaluator() instanceof RegressionSplitEvaluator) {
+	m_ExpClassificationRBut.setSelected(false);
+	m_ExpRegressionRBut.setSelected(true);
+      } else {
+	// unknown split evaluator
+	System.out.println("SimpleSetup incompatibility: unrecognised split evaluator");
+	if (userWantsToConvert()) {
+	  m_ExpClassificationRBut.setSelected(true);
+	  m_ExpRegressionRBut.setSelected(false);
+	} else {
+	  return false;
+	}
+      }
+      m_ExperimentTypeCBox.setSelectedItem(TYPE_CROSSVALIDATION_TEXT);
+    } else if (exp.getResultProducer() instanceof RandomSplitResultProducer) {
+      RandomSplitResultProducer rsrp = (RandomSplitResultProducer) exp.getResultProducer();
+      if (rsrp.getRandomizeData()) {
+	m_ExperimentTypeCBox.setSelectedItem(TYPE_RANDOMSPLIT_TEXT);
+      } else {
+	m_ExperimentTypeCBox.setSelectedItem(TYPE_FIXEDSPLIT_TEXT);
+      }
+      if (rsrp.getSplitEvaluator() instanceof ClassifierSplitEvaluator) {
+	m_ExpClassificationRBut.setSelected(true);
+	m_ExpRegressionRBut.setSelected(false);
+      } else if (rsrp.getSplitEvaluator() instanceof RegressionSplitEvaluator) {
+	m_ExpClassificationRBut.setSelected(false);
+	m_ExpRegressionRBut.setSelected(true);
+      } else {
+	// unknown split evaluator
+	System.out.println("SimpleSetup incompatibility: unrecognised split evaluator");
+	if (userWantsToConvert()) {
+	  m_ExpClassificationRBut.setSelected(true);
+	  m_ExpRegressionRBut.setSelected(false);
+	} else {
+	  return false;
+	}
+      }
+      m_trainPercent = rsrp.getTrainPercent();
+      m_ExperimentParameterTField.setText("" + m_trainPercent);
+      
+    } else {
+      // unknown experiment type
+      System.out.println("SimpleSetup incompatibility: unrecognised resultProducer");
+      if (userWantsToConvert()) {
+	m_ExperimentTypeCBox.setSelectedItem(TYPE_CROSSVALIDATION_TEXT);
+	m_ExpClassificationRBut.setSelected(true);
+	m_ExpRegressionRBut.setSelected(false);
+      } else {
+	return false;
+      }
+    }
+
+    m_ExperimentTypeCBox.setEnabled(true);
+    m_ExperimentParameterLabel.setEnabled(true);
+    m_ExperimentParameterTField.setEnabled(true);
+    m_ExpClassificationRBut.setEnabled(true);
+    m_ExpRegressionRBut.setEnabled(true);
+    
+    if (exp.getRunLower() == 1) {
+      m_numRepetitions = exp.getRunUpper();
+      m_NumberOfRepetitionsTField.setText("" + m_numRepetitions);
+    } else {
+      // unsupported iterations
+      System.out.println("SimpleSetup incompatibility: runLower is not 1");
+      if (userWantsToConvert()) {
+	exp.setRunLower(1);
+	if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_FIXEDSPLIT_TEXT) {
+	  exp.setRunUpper(1);
+	  m_NumberOfRepetitionsTField.setEnabled(false);
+	  m_NumberOfRepetitionsTField.setText("1");
+	} else {
+	  exp.setRunUpper(10);
+	  m_numRepetitions = 10;
+	  m_NumberOfRepetitionsTField.setText("" + m_numRepetitions);
+	}
+	
+      } else {
+	return false;
+      }
+    }
+    m_NumberOfRepetitionsTField.setEnabled(true);
+
+    m_OrderDatasetsFirstRBut.setSelected(exp.getAdvanceDataSetFirst());
+    m_OrderAlgorithmsFirstRBut.setSelected(!exp.getAdvanceDataSetFirst());
+    m_OrderDatasetsFirstRBut.setEnabled(true);
+    m_OrderAlgorithmsFirstRBut.setEnabled(true);
+
+    m_NotesText.setText(exp.getNotes());
+    m_NotesButton.setEnabled(true);
+
+    if (!exp.getUsePropertyIterator() || !(exp.getPropertyArray() instanceof Classifier[])) {
+      // unknown property iteration
+      System.out.println("SimpleSetup incompatibility: unrecognised property iteration");
+      if (userWantsToConvert()) {
+	exp.setPropertyArray(new Classifier[0]);
+	exp.setUsePropertyIterator(true);
+      } else {
+	return false;
+      }
+    }
+
+    m_DatasetListPanel.setExperiment(exp);
+    m_AlgorithmListPanel.setExperiment(exp);
+    
+    m_Exp = exp;
+    expTypeChanged(); // recreate experiment
+    
+    m_Support.firePropertyChange("", null, null);
+    
+    return true;
+  }
+
+  /**
+   * Gets the currently configured experiment.
+   *
+   * @return the currently configured experiment.
+   */
+  public Experiment getExperiment() {
+
+    return m_Exp;
+  }
+  
+  /**
+   * Prompts the user to select an experiment file and loads it.
+   */
+  private void openExperiment() {
+    
+    int returnVal = m_FileChooser.showOpenDialog(this);
+    if (returnVal != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    File expFile = m_FileChooser.getSelectedFile();
+    
+    // add extension if necessary
+    if (m_FileChooser.getFileFilter() == m_ExpFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(Experiment.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + Experiment.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + KOML.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(".xml"))
+        expFile = new File(expFile.getParent(), expFile.getName() + ".xml");
+    }
+    
+    try {
+      Experiment exp = Experiment.read(expFile.getAbsolutePath());
+      if (!setExperiment(exp)) {
+	if (m_modePanel != null) m_modePanel.switchToAdvanced(exp);
+      }
+      System.err.println("Opened experiment:\n" + exp);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      JOptionPane.showMessageDialog(this, "Couldn't open experiment file:\n"
+				    + expFile
+				    + "\nReason:\n" + ex.getMessage(),
+				    "Open Experiment",
+				    JOptionPane.ERROR_MESSAGE);
+      // Pop up error dialog
+    }
+  }
+
+  /**
+   * Prompts the user for a filename to save the experiment to, then saves
+   * the experiment.
+   */
+  private void saveExperiment() {
+
+    int returnVal = m_FileChooser.showSaveDialog(this);
+    if (returnVal != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    File expFile = m_FileChooser.getSelectedFile();
+    
+    // add extension if necessary
+    if (m_FileChooser.getFileFilter() == m_ExpFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(Experiment.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + Experiment.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_KOMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION))
+        expFile = new File(expFile.getParent(), expFile.getName() + KOML.FILE_EXTENSION);
+    }
+    else if (m_FileChooser.getFileFilter() == m_XMLFilter) {
+      if (!expFile.getName().toLowerCase().endsWith(".xml"))
+        expFile = new File(expFile.getParent(), expFile.getName() + ".xml");
+    }
+    
+    try {
+      Experiment.write(expFile.getAbsolutePath(), m_Exp);
+      System.err.println("Saved experiment:\n" + m_Exp);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      JOptionPane.showMessageDialog(this, "Couldn't save experiment file:\n"
+				    + expFile
+				    + "\nReason:\n" + ex.getMessage(),
+				    "Save Experiment",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    m_Support.removePropertyChangeListener(l);
+  }
+
+  /**
+   * Responds to a change in the destination type.
+   */
+  private void destinationTypeChanged() {
+
+    if (m_Exp == null) return;
+
+    String str = "";
+
+    if (m_ResultsDestinationCBox.getSelectedItem() == DEST_DATABASE_TEXT) {
+      m_ResultsDestinationPathLabel.setText("URL:");
+      str = m_destinationDatabaseURL;
+      m_BrowseDestinationButton.setEnabled(true); //!!!
+      m_BrowseDestinationButton.setText("User...");
+    } else {
+      m_ResultsDestinationPathLabel.setText("Filename:");
+      if (m_ResultsDestinationCBox.getSelectedItem() == DEST_ARFF_TEXT) {
+	int ind = m_destinationFilename.lastIndexOf(".csv");
+	if (ind > -1) {
+	  m_destinationFilename = m_destinationFilename.substring(0, ind) + ".arff";
+	}
+      }
+      if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+	int ind = m_destinationFilename.lastIndexOf(".arff");
+	if (ind > -1) {
+	  m_destinationFilename = m_destinationFilename.substring(0, ind) + ".csv";
+	}
+      }
+      str = m_destinationFilename;
+      if (m_ResultsDestinationCBox.getSelectedItem() == DEST_ARFF_TEXT) {
+	int ind = str.lastIndexOf(".csv");
+	if (ind > -1) {
+	  str = str.substring(0, ind) + ".arff";
+	}
+      }
+      if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+	int ind = str.lastIndexOf(".arff");
+	if (ind > -1) {
+	  str = str.substring(0, ind) + ".csv";
+	}
+      }
+      m_BrowseDestinationButton.setEnabled(true);
+      m_BrowseDestinationButton.setText("Browse...");
+    }
+
+    if (m_ResultsDestinationCBox.getSelectedItem() == DEST_DATABASE_TEXT) {
+      DatabaseResultListener drl = null;
+      try {
+	drl = new DatabaseResultListener();
+      } catch (Exception e) {
+	e.printStackTrace();
+      }
+      drl.setDatabaseURL(m_destinationDatabaseURL);
+      m_Exp.setResultListener(drl);
+    } else {
+      if (m_ResultsDestinationCBox.getSelectedItem() == DEST_ARFF_TEXT) {
+	InstancesResultListener irl = new InstancesResultListener();
+	if (!m_destinationFilename.equals("")) {
+	  irl.setOutputFile(new File(m_destinationFilename));
+	}
+	m_Exp.setResultListener(irl);
+      } else if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+	CSVResultListener crl = new CSVResultListener();
+	if (!m_destinationFilename.equals("")) {
+	  crl.setOutputFile(new File(m_destinationFilename));
+	}
+	m_Exp.setResultListener(crl);
+      }
+    }
+
+    m_ResultsDestinationPathTField.setText(str);
+
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Responds to a change in the destination address.
+   */
+  private void destinationAddressChanged() {
+
+    if (m_Exp == null) return;
+
+    if (m_ResultsDestinationCBox.getSelectedItem() == DEST_DATABASE_TEXT) {
+      m_destinationDatabaseURL = m_ResultsDestinationPathTField.getText();
+      if (m_Exp.getResultListener() instanceof DatabaseResultListener) {
+	((DatabaseResultListener)m_Exp.getResultListener()).setDatabaseURL(m_destinationDatabaseURL);
+      }
+    } else {
+      File resultsFile = null;
+      m_destinationFilename = m_ResultsDestinationPathTField.getText();
+
+      // Use temporary file if no file name is provided
+      if (m_destinationFilename.equals("")) {
+	try {
+	  if (m_ResultsDestinationCBox.getSelectedItem() == DEST_ARFF_TEXT) {
+	    resultsFile = File.createTempFile("weka_experiment", ".arff");
+	  }
+	  if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+	    resultsFile = File.createTempFile("weka_experiment", ".csv");
+	  }
+	  resultsFile.deleteOnExit();
+	} catch (Exception e) {
+	  System.err.println("Cannot create temp file, writing to standard out.");
+	  resultsFile = new File("-");
+	}
+      } else {
+	if (m_ResultsDestinationCBox.getSelectedItem() == DEST_ARFF_TEXT) {
+	  if (!m_destinationFilename.endsWith(".arff")) {
+	    m_destinationFilename += ".arff";
+	  }
+	}
+	if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+	  if (!m_destinationFilename.endsWith(".csv")) {
+	    m_destinationFilename += ".csv";
+	  }
+	}
+	resultsFile = new File(m_destinationFilename);
+      }
+      ((CSVResultListener)m_Exp.getResultListener()).setOutputFile(resultsFile);
+      ((CSVResultListener)m_Exp.getResultListener()).setOutputFileName(m_destinationFilename);
+    }
+
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Responds to a change in the experiment type.
+   */
+  private void expTypeChanged() {
+
+    if (m_Exp == null) return;
+
+    // update parameter ui
+    if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_CROSSVALIDATION_TEXT) {
+      m_ExperimentParameterLabel.setText("Number of folds:");
+      m_ExperimentParameterTField.setText("" + m_numFolds);
+    } else {
+      m_ExperimentParameterLabel.setText("Train percentage:");
+      m_ExperimentParameterTField.setText("" + m_trainPercent);
+    }
+
+    // update iteration ui
+    if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_FIXEDSPLIT_TEXT) {
+      m_NumberOfRepetitionsTField.setEnabled(false);
+      m_NumberOfRepetitionsTField.setText("1");
+      m_Exp.setRunLower(1);
+      m_Exp.setRunUpper(1);
+    } else {
+      m_NumberOfRepetitionsTField.setText("" + m_numRepetitions);
+      m_NumberOfRepetitionsTField.setEnabled(true);
+      m_Exp.setRunLower(1);
+      m_Exp.setRunUpper(m_numRepetitions);
+    }
+
+    SplitEvaluator se = null;
+    Classifier sec = null;
+    if (m_ExpClassificationRBut.isSelected()) {
+      se = new ClassifierSplitEvaluator();
+      sec = ((ClassifierSplitEvaluator)se).getClassifier();
+    } else {
+      se = new RegressionSplitEvaluator();
+      sec = ((RegressionSplitEvaluator)se).getClassifier();
+    }
+    
+    // build new ResultProducer
+    if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_CROSSVALIDATION_TEXT) {
+      CrossValidationResultProducer cvrp = new CrossValidationResultProducer();
+      cvrp.setNumFolds(m_numFolds);
+      cvrp.setSplitEvaluator(se);
+      
+      PropertyNode[] propertyPath = new PropertyNode[2];
+      try {
+	propertyPath[0] = new PropertyNode(se, new PropertyDescriptor("splitEvaluator",
+								      CrossValidationResultProducer.class),
+					   CrossValidationResultProducer.class);
+	propertyPath[1] = new PropertyNode(sec, new PropertyDescriptor("classifier",
+								       se.getClass()),
+					   se.getClass());
+      } catch (IntrospectionException e) {
+	e.printStackTrace();
+      }
+      
+      m_Exp.setResultProducer(cvrp);
+      m_Exp.setPropertyPath(propertyPath);
+
+    } else {
+      RandomSplitResultProducer rsrp = new RandomSplitResultProducer();
+      rsrp.setRandomizeData(m_ExperimentTypeCBox.getSelectedItem() == TYPE_RANDOMSPLIT_TEXT);
+      rsrp.setTrainPercent(m_trainPercent);
+      rsrp.setSplitEvaluator(se);
+
+      PropertyNode[] propertyPath = new PropertyNode[2];
+      try {
+	propertyPath[0] = new PropertyNode(se, new PropertyDescriptor("splitEvaluator",
+								      RandomSplitResultProducer.class),
+					   RandomSplitResultProducer.class);
+	propertyPath[1] = new PropertyNode(sec, new PropertyDescriptor("classifier",
+								       se.getClass()),
+					   se.getClass());
+      } catch (IntrospectionException e) {
+	e.printStackTrace();
+      }
+
+      m_Exp.setResultProducer(rsrp);
+      m_Exp.setPropertyPath(propertyPath);
+
+    }
+
+    m_Exp.setUsePropertyIterator(true);
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Responds to a change in the experiment parameter.
+   */
+  private void expParamChanged() {
+
+    if (m_Exp == null) return;
+
+    if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_CROSSVALIDATION_TEXT) {
+      try {
+	m_numFolds = Integer.parseInt(m_ExperimentParameterTField.getText());
+      } catch (NumberFormatException e) {
+	return;
+      }
+    } else {
+      try {
+	m_trainPercent = Double.parseDouble(m_ExperimentParameterTField.getText());
+      } catch (NumberFormatException e) {
+	return;
+      }
+    }
+
+    if (m_ExperimentTypeCBox.getSelectedItem() == TYPE_CROSSVALIDATION_TEXT) {
+
+      if (m_Exp.getResultProducer() instanceof CrossValidationResultProducer) {
+	CrossValidationResultProducer cvrp = (CrossValidationResultProducer) m_Exp.getResultProducer();
+	cvrp.setNumFolds(m_numFolds);
+      } else {
+	return;
+      }
+
+    } else {
+      
+      if (m_Exp.getResultProducer() instanceof RandomSplitResultProducer) {
+	RandomSplitResultProducer rsrp = (RandomSplitResultProducer) m_Exp.getResultProducer();
+	rsrp.setRandomizeData(m_ExperimentTypeCBox.getSelectedItem() == TYPE_RANDOMSPLIT_TEXT);
+	rsrp.setTrainPercent(m_trainPercent);
+      } else {
+	//System.err.println("not rsrp");
+	return;
+      }
+    }
+
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Responds to a change in the number of repetitions.
+   */
+  private void numRepetitionsChanged() {
+
+    if (m_Exp == null || !m_NumberOfRepetitionsTField.isEnabled()) return;
+
+    try {
+      m_numRepetitions = Integer.parseInt(m_NumberOfRepetitionsTField.getText());
+    } catch (NumberFormatException e) {
+      return;
+    }
+
+    m_Exp.setRunLower(1);
+    m_Exp.setRunUpper(m_numRepetitions);
+
+    m_Support.firePropertyChange("", null, null);
+  }
+
+  /**
+   * Lets user enter username/password/URL.
+   */
+  private void chooseURLUsername() {
+    String dbaseURL=((DatabaseResultListener)m_Exp.getResultListener()).getDatabaseURL();
+    String username=((DatabaseResultListener)m_Exp.getResultListener()).getUsername();
+    DatabaseConnectionDialog dbd= new DatabaseConnectionDialog(null,dbaseURL,username);
+    dbd.setVisible(true);
+      
+    //if (dbaseURL == null) {
+    if (dbd.getReturnValue()==JOptionPane.CLOSED_OPTION) {
+      return;
+    }
+
+    ((DatabaseResultListener)m_Exp.getResultListener()).setUsername(dbd.getUsername());
+    ((DatabaseResultListener)m_Exp.getResultListener()).setPassword(dbd.getPassword());
+    ((DatabaseResultListener)m_Exp.getResultListener()).setDatabaseURL(dbd.getURL());
+    ((DatabaseResultListener)m_Exp.getResultListener()).setDebug(dbd.getDebug());
+    m_ResultsDestinationPathTField.setText(dbd.getURL());
+  }
+  /**
+   * Lets user browse for a destination file..
+   */
+  private void chooseDestinationFile() {
+
+    FileFilter fileFilter = null;
+    if (m_ResultsDestinationCBox.getSelectedItem() == DEST_CSV_TEXT) {
+      fileFilter = m_csvFileFilter;
+    } else {
+      fileFilter = m_arffFileFilter;
+    }
+    m_DestFileChooser.setFileFilter(fileFilter);
+    int returnVal = m_DestFileChooser.showSaveDialog(this);
+    if (returnVal != JFileChooser.APPROVE_OPTION) {
+      return;
+    }
+    m_ResultsDestinationPathTField.setText(m_DestFileChooser.getSelectedFile().toString());
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/AbstractPlotInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/AbstractPlotInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/AbstractPlotInstances.java	(revision 29)
@@ -0,0 +1,190 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * AbstractPlotInstances.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.gui.visualize.PlotData2D;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Abstract superclass for generating plottable instances.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5714 $
+ */
+public abstract class AbstractPlotInstances
+  implements Serializable, OptionHandler {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 2375155184845160908L;
+
+  /** the full dataset. */
+  protected Instances m_Instances;
+  
+  /** the plotable instances. */
+  protected Instances m_PlotInstances;
+
+  /** whether processing has been finished up already. */
+  protected boolean m_FinishUpCalled;
+  
+  /**
+   * Initializes the container.
+   */
+  public AbstractPlotInstances() {
+    initialize();
+  }
+  
+  /**
+   * Initializes the member variables.
+   */
+  protected void initialize() {
+    m_Instances      = null;
+    m_PlotInstances  = null;
+    m_FinishUpCalled = false;
+  }
+
+  /**
+   * Returns an enumeration of all the available options.
+   *
+   * @return 		an enumeration of all available options.
+   */
+  public Enumeration listOptions() {
+    return new Vector().elements();
+  }
+
+  /**
+   * Sets the OptionHandler's options using the given list. All options
+   * will be set (or reset) during this call (i.e. incremental setting
+   * of options is not possible).
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+  }
+
+  /**
+   * Gets the current option settings for the OptionHandler.
+   *
+   * @return the list of current option settings as an array of strings
+   */
+  public String[] getOptions() {
+    return new String[0];
+  }
+  
+  /**
+   * Sets up the structure for the plot instances.
+   */
+  protected abstract void determineFormat();  
+  
+  /**
+   * Sets the instances that are the basis for the plot instances.
+   * 
+   * @param value	the training data to initialize with
+   */
+  public void setInstances(Instances value) {
+    m_Instances = value;
+  }
+  
+  /**
+   * Returns the training data.
+   * 
+   * @return		the training data
+   */
+  public Instances getInstances() {
+    return m_Instances;
+  }
+  
+  /**
+   * Default implementation only ensures that a dataset has been set.
+   */
+  protected void check() {
+    if (m_Instances == null)
+      throw new IllegalStateException("No instances set!");
+  }
+  
+  /**
+   * Performs checks, sets up the structure for the plot instances.
+   */
+  public void setUp() {
+    m_FinishUpCalled = false;
+    
+    check();
+    determineFormat();
+  }
+  
+  /**
+   * Performs optional post-processing.
+   */
+  protected void finishUp() {
+    m_FinishUpCalled = true;
+  }
+  
+  /**
+   * Returns the generated plot instances.
+   * 
+   * @return		the instances to plot
+   */
+  public Instances getPlotInstances() {
+    if (!m_FinishUpCalled)
+      finishUp();
+    
+    return m_PlotInstances;
+  }
+  /**
+   * Assembles and returns the plot. The relation name of the dataset gets
+   * added automatically.
+   * 
+   * @param name	the name of the plot
+   * @return		the plot
+   * @throws Exception	if plot generation fails
+   */
+  protected abstract PlotData2D createPlotData(String name) throws Exception;
+  
+  /**
+   * Assembles and returns the plot. The relation name of the dataset gets
+   * added automatically.
+   * 
+   * @param name	the name of the plot
+   * @return		the plot
+   * @throws Exception	if plot generation fails
+   */
+  public PlotData2D getPlotData(String name) throws Exception {
+    if (!m_FinishUpCalled)
+      finishUp();
+    
+    return createPlotData(name);
+  }
+  
+  /**
+   * For freeing up memory. Plot data cannot be generated after this call!
+   */
+  public void cleanUp() {
+    m_Instances      = null;
+    m_PlotInstances  = null;
+    m_FinishUpCalled = false;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/AssociationsPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/AssociationsPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/AssociationsPanel.java	(revision 29)
@@ -0,0 +1,618 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AssociationsPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.associations.Associator;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.gui.GenericObjectEditor;
+import weka.gui.Logger;
+import weka.gui.PropertyPanel;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+import weka.gui.SysErrLog;
+import weka.gui.TaskLogger;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.explorer.Explorer.LogHandler;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JViewport;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/** 
+ * This panel allows the user to select, configure, and run a scheme
+ * that learns associations.
+ *
+ * @author Eibe Frank (eibe@cs.waikato.ac.nz)
+ * @version $Revision: 5704 $
+ */
+public class AssociationsPanel 
+  extends JPanel
+  implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = -6867871711865476971L;
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+
+  /** Lets the user configure the associator */
+  protected GenericObjectEditor m_AssociatorEditor =
+    new GenericObjectEditor();
+
+  /** The panel showing the current associator selection */
+  protected PropertyPanel m_CEPanel = new PropertyPanel(m_AssociatorEditor);
+  
+  /** The output area for associations */
+  protected JTextArea m_OutText = new JTextArea(20, 40);
+
+  /** The destination for log/status messages */
+  protected Logger m_Log = new SysErrLog();
+
+  /** The buffer saving object for saving output */
+  protected SaveBuffer m_SaveOut = new SaveBuffer(m_Log, this);
+
+  /** A panel controlling results viewing */
+  protected ResultHistoryPanel m_History = new ResultHistoryPanel(m_OutText);
+
+  /** Click to start running the associator */
+  protected JButton m_StartBut = new JButton("Start");
+
+  /** Click to stop a running associator */
+  protected JButton m_StopBut = new JButton("Stop");
+  
+  /** The main set of instances we're playing with */
+  protected Instances m_Instances;
+
+  /** The user-supplied test set (if any) */
+  protected Instances m_TestInstances;
+  
+  /** A thread that associator runs in */
+  protected Thread m_RunThread;
+
+  /* Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the associator panel
+   */
+  public AssociationsPanel() {
+
+    // Connect / configure the components
+    m_OutText.setEditable(false);
+    m_OutText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_OutText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_OutText.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	if ((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	    != InputEvent.BUTTON1_MASK) {
+	  m_OutText.selectAll();
+	}
+      }
+    });
+    m_History.setBorder(BorderFactory.createTitledBorder("Result list (right-click for options)"));
+    m_History.setHandleRightClicks(false);
+    // see if we can popup a menu for the selected result
+    m_History.getList().addMouseListener(new MouseAdapter() {
+	public void mouseClicked(MouseEvent e) {
+	  if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	       != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+	    int index = m_History.getList().locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = m_History.getNameAtIndex(index);
+	      historyRightClickPopup(name, e.getX(), e.getY());
+	    } else {
+	      historyRightClickPopup(null, e.getX(), e.getY());
+	    }
+	  }
+	}
+      });
+
+    m_AssociatorEditor.setClassType(Associator.class);
+    m_AssociatorEditor.setValue(ExplorerDefaults.getAssociator());
+    m_AssociatorEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        m_StartBut.setEnabled(true);
+        // Check capabilities
+        Capabilities currentFilter = m_AssociatorEditor.getCapabilitiesFilter();
+        Associator associator = (Associator) m_AssociatorEditor.getValue();
+        Capabilities currentSchemeCapabilities =  null;
+        if (associator != null && currentFilter != null && 
+            (associator instanceof CapabilitiesHandler)) {
+          currentSchemeCapabilities = ((CapabilitiesHandler)associator).getCapabilities();
+          
+          if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+              !currentSchemeCapabilities.supports(currentFilter)) {
+            m_StartBut.setEnabled(false);
+          }
+        }
+	repaint();
+      }
+    });
+
+    m_StartBut.setToolTipText("Starts the associator");
+    m_StopBut.setToolTipText("Stops the associator");
+    m_StartBut.setEnabled(false);
+    m_StopBut.setEnabled(false);
+    m_StartBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	startAssociator();
+      }
+    });
+    m_StopBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	stopAssociator();
+      }
+    });
+
+    // Layout the GUI
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Associator"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    p1.setLayout(new BorderLayout());
+    p1.add(m_CEPanel, BorderLayout.NORTH);
+
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new GridLayout(1,2));
+    JPanel ssButs = new JPanel();
+    ssButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ssButs.setLayout(new GridLayout(1, 2, 5, 5));
+    ssButs.add(m_StartBut);
+    ssButs.add(m_StopBut);
+    buttons.add(ssButs);
+
+    JPanel p3 = new JPanel();
+    p3.setBorder(BorderFactory.createTitledBorder("Associator output"));
+    p3.setLayout(new BorderLayout());
+    final JScrollPane js = new JScrollPane(m_OutText);
+    p3.add(js, BorderLayout.CENTER);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });
+    
+    GridBagLayout gbL = new GridBagLayout();
+    GridBagConstraints gbC = new GridBagConstraints();
+    JPanel mondo = new JPanel();
+    gbL = new GridBagLayout();
+    mondo.setLayout(gbL);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(buttons, gbC);
+    mondo.add(buttons);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 2;     gbC.gridx = 0; gbC.weightx = 0;
+    gbL.setConstraints(m_History, gbC);
+    mondo.add(m_History);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.gridheight = 3;
+    gbC.weightx = 100; gbC.weighty = 100;
+    gbL.setConstraints(p3, gbC);
+    mondo.add(p3);
+
+    setLayout(new BorderLayout());
+    add(p1, BorderLayout.NORTH);
+    add(mondo, BorderLayout.CENTER);
+  }
+  
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+
+    m_Log = newLog;
+  }
+  
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    
+    m_Instances = inst;
+    String [] attribNames = new String [m_Instances.numAttributes()];
+    for (int i = 0; i < attribNames.length; i++) {
+      String type = "";
+      switch (m_Instances.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = "(Nom) ";
+	break;
+      case Attribute.NUMERIC:
+	type = "(Num) ";
+	break;
+      case Attribute.STRING:
+	type = "(Str) ";
+	break;
+      case Attribute.DATE:
+	type = "(Dat) ";
+	break;
+      case Attribute.RELATIONAL:
+	type = "(Rel) ";
+	break;
+      default:
+	type = "(???) ";
+      }
+      attribNames[i] = type + m_Instances.attribute(i).name();
+    }
+    m_StartBut.setEnabled(m_RunThread == null);
+    m_StopBut.setEnabled(m_RunThread != null);
+  }
+  
+  /**
+   * Starts running the currently configured associator with the current
+   * settings. This is run in a separate thread, and will only start if
+   * there is no associator already running. The associator output is sent
+   * to the results history panel.
+   */
+  protected void startAssociator() {
+
+    if (m_RunThread == null) {
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(true);
+      m_RunThread = new Thread() {
+	public void run() {
+	  // Copy the current state of things
+	  m_Log.statusMessage("Setting up...");
+	  Instances inst = new Instances(m_Instances);
+
+	  Associator associator = (Associator) m_AssociatorEditor.getValue();
+	  StringBuffer outBuff = new StringBuffer();
+	  String name = (new SimpleDateFormat("HH:mm:ss - "))
+	  .format(new Date());
+	  String cname = associator.getClass().getName();
+	  if (cname.startsWith("weka.associations.")) {
+	    name += cname.substring("weka.associations.".length());
+	  } else {
+	    name += cname;
+	  }
+          String cmd = m_AssociatorEditor.getValue().getClass().getName();
+          if (m_AssociatorEditor.getValue() instanceof OptionHandler)
+            cmd += " " + Utils.joinOptions(((OptionHandler) m_AssociatorEditor.getValue()).getOptions());
+	  try {
+
+	    // Output some header information
+	    m_Log.logMessage("Started " + cname);
+	    m_Log.logMessage("Command: " + cmd);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskStarted();
+	    }
+	    outBuff.append("=== Run information ===\n\n");
+	    outBuff.append("Scheme:       " + cname);
+	    if (associator instanceof OptionHandler) {
+	      String [] o = ((OptionHandler) associator).getOptions();
+	      outBuff.append(" " + Utils.joinOptions(o));
+	    }
+	    outBuff.append("\n");
+	    outBuff.append("Relation:     " + inst.relationName() + '\n');
+	    outBuff.append("Instances:    " + inst.numInstances() + '\n');
+	    outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
+	    if (inst.numAttributes() < 100) {
+	      for (int i = 0; i < inst.numAttributes(); i++) {
+		outBuff.append("              " + inst.attribute(i).name()
+			       + '\n');
+	      }
+	    } else {
+	      outBuff.append("              [list of attributes omitted]\n");
+	    }
+	    m_History.addResult(name, outBuff);
+	    m_History.setSingle(name);
+	    
+	    // Build the model and output it.
+	    m_Log.statusMessage("Building model on training data...");
+	    associator.buildAssociations(inst);
+	    outBuff.append("=== Associator model (full training set) ===\n\n");
+	    outBuff.append(associator.toString() + '\n');
+	    m_History.updateResult(name);
+	    m_Log.logMessage("Finished " + cname);
+	    m_Log.statusMessage("OK");
+	  } catch (Exception ex) {
+	    m_Log.logMessage(ex.getMessage());
+	    m_Log.statusMessage("See error log");
+	  } finally {
+	    if (isInterrupted()) {
+	      m_Log.logMessage("Interrupted " + cname);
+	      m_Log.statusMessage("See error log");
+	    }
+	    m_RunThread = null;
+	    m_StartBut.setEnabled(true);
+	    m_StopBut.setEnabled(false);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskFinished();
+	    }
+	  }
+	}
+      };
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+  
+  /**
+   * Stops the currently running Associator (if any).
+   */
+  protected void stopAssociator() {
+
+    if (m_RunThread != null) {
+      m_RunThread.interrupt();
+      
+      // This is deprecated (and theoretically the interrupt should do).
+      m_RunThread.stop();
+      
+    }
+  }
+
+  /**
+   * Save the currently selected associator output to a file.
+   * @param name the name of the buffer to save
+   */
+  protected void saveBuffer(String name) {
+    StringBuffer sb = m_History.getNamedBuffer(name);
+    if (sb != null) {
+      if (m_SaveOut.save(sb)) {
+	m_Log.logMessage("Save successful.");
+      }
+    }
+  }
+    
+  /**
+   * Handles constructing a popup menu with visualization options.
+   * @param name the name of the result history list entry clicked on by
+   * the user
+   * @param x the x coordinate for popping up the menu
+   * @param y the y coordinate for popping up the menu
+   */
+  protected void historyRightClickPopup(String name, int x, int y) {
+    final String selectedName = name;
+    JPopupMenu resultListMenu = new JPopupMenu();
+    
+    JMenuItem visMainBuffer = new JMenuItem("View in main window");
+    if (selectedName != null) {
+      visMainBuffer.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_History.setSingle(selectedName);
+	  }
+	});
+    } else {
+      visMainBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visMainBuffer);
+    
+    JMenuItem visSepBuffer = new JMenuItem("View in separate window");
+    if (selectedName != null) {
+      visSepBuffer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.openFrame(selectedName);
+	}
+      });
+    } else {
+      visSepBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visSepBuffer);
+    
+    JMenuItem saveOutput = new JMenuItem("Save result buffer");
+    if (selectedName != null) {
+      saveOutput.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveBuffer(selectedName);
+	  }
+	});
+    } else {
+      saveOutput.setEnabled(false);
+    }
+    resultListMenu.add(saveOutput);
+
+    JMenuItem deleteOutput = new JMenuItem("Delete result buffer");
+    if (selectedName != null) {
+      deleteOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.removeResult(selectedName);
+	}
+      });
+    } else {
+      deleteOutput.setEnabled(false);
+    }
+    resultListMenu.add(deleteOutput);
+
+    resultListMenu.show(m_History.getList(), x, y);
+  }
+  
+  /**
+   * updates the capabilities filter of the GOE
+   * 
+   * @param filter	the new filter to use
+   */
+  protected void updateCapabilitiesFilter(Capabilities filter) {
+    Instances           tempInst;
+    Capabilities        filterClass;
+    
+    if (filter == null) {
+      m_AssociatorEditor.setCapabilitiesFilter(new Capabilities(null));
+      return;
+    }
+    
+    if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
+      tempInst = new Instances(m_Instances, 0);
+    else
+      tempInst = new Instances(m_Instances);
+    tempInst.setClassIndex(-1);
+    
+    try {
+      filterClass = Capabilities.forInstances(tempInst);
+    }
+    catch (Exception e) {
+      filterClass = new Capabilities(null);
+    }
+    
+    m_AssociatorEditor.setCapabilitiesFilter(filterClass);
+    
+    m_StartBut.setEnabled(true);
+    // Check capabilities
+    Capabilities currentFilter = m_AssociatorEditor.getCapabilitiesFilter();
+    Associator associator = (Associator) m_AssociatorEditor.getValue();
+    Capabilities currentSchemeCapabilities =  null;
+    if (associator != null && currentFilter != null && 
+        (associator instanceof CapabilitiesHandler)) {
+      currentSchemeCapabilities = ((CapabilitiesHandler)associator).getCapabilities();
+      
+      if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+          !currentSchemeCapabilities.supports(currentFilter)) {
+        m_StartBut.setEnabled(false);
+      }
+    }
+  }
+  
+  /**
+   * method gets called in case of a change event
+   * 
+   * @param e		the associated change event
+   */
+  public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
+    if (e.getFilter() == null)
+      updateCapabilitiesFilter(null);
+    else
+      updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
+  }
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Associate";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Discover association rules";
+  }
+
+  /**
+   * Tests out the Associator panel from the command line.
+   *
+   * @param args may optionally contain the name of a dataset to load.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Associator");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final AssociationsPanel sp = new AssociationsPanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      sp.setLog(lp);
+      jf.getContentPane().add(lp, BorderLayout.SOUTH);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      if (args.length == 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	sp.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/AttributeSelectionPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/AttributeSelectionPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/AttributeSelectionPanel.java	(revision 29)
@@ -0,0 +1,1186 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributeSelectionPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.attributeSelection.ASEvaluation;
+import weka.attributeSelection.ASSearch;
+import weka.attributeSelection.AttributeEvaluator;
+import weka.attributeSelection.AttributeSelection;
+import weka.attributeSelection.AttributeTransformer;
+import weka.attributeSelection.Ranker;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.Logger;
+import weka.gui.PropertyPanel;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+import weka.gui.SysErrLog;
+import weka.gui.TaskLogger;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.explorer.Explorer.LogHandler;
+import weka.gui.visualize.MatrixPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JViewport;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/** 
+ * This panel allows the user to select and configure an attribute
+ * evaluator and a search method, set the
+ * attribute of the current dataset to be used as the class, and perform
+ * attribute selection using one of two  selection modes (select using all the
+ * training data or perform a n-fold cross validation---on each trial
+ * selecting features using n-1 folds of the data).
+ * The results of attribute selection runs are stored in a results history
+ * so that previous results are accessible.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5385 $
+ */
+public class AttributeSelectionPanel 
+  extends JPanel
+  implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
+  
+  /** for serialization */
+  static final long serialVersionUID = 5627185966993476142L;
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+
+  /** Lets the user configure the attribute evaluator */
+  protected GenericObjectEditor m_AttributeEvaluatorEditor =
+    new GenericObjectEditor();
+
+  /** Lets the user configure the search method */
+  protected GenericObjectEditor m_AttributeSearchEditor = 
+    new GenericObjectEditor();
+
+  /** The panel showing the current attribute evaluation method */
+  protected PropertyPanel m_AEEPanel = 
+    new PropertyPanel(m_AttributeEvaluatorEditor);
+
+  /** The panel showing the current search method */
+  protected PropertyPanel m_ASEPanel = 
+    new PropertyPanel(m_AttributeSearchEditor);
+  
+  /** The output area for attribute selection results */
+  protected JTextArea m_OutText = new JTextArea(20, 40);
+
+  /** The destination for log/status messages */
+  protected Logger m_Log = new SysErrLog();
+
+  /** The buffer saving object for saving output */
+  SaveBuffer m_SaveOut = new SaveBuffer(m_Log, this);
+
+  /** A panel controlling results viewing */
+  protected ResultHistoryPanel m_History = new ResultHistoryPanel(m_OutText);
+
+  /** Lets the user select the class column */
+  protected JComboBox m_ClassCombo = new JComboBox();
+
+  /** Click to set evaluation mode to cross-validation */
+  protected JRadioButton m_CVBut = new JRadioButton("Cross-validation");
+
+  /** Click to set test mode to test on training data */
+  protected JRadioButton m_TrainBut = 
+    new JRadioButton("Use full training set");
+
+  /** Label by where the cv folds are entered */
+  protected JLabel m_CVLab = new JLabel("Folds", SwingConstants.RIGHT);
+
+  /** The field where the cv folds are entered */
+  protected JTextField m_CVText = new JTextField("10");
+
+  /** Label by where cv random seed is entered */
+  protected JLabel m_SeedLab = new JLabel("Seed",SwingConstants.RIGHT);
+
+  /** The field where the seed value is entered */
+  protected JTextField m_SeedText = new JTextField("1");
+
+  /**
+   * Alters the enabled/disabled status of elements associated with each
+   * radio button
+   */
+  ActionListener m_RadioListener = new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      updateRadioLinks();
+    }
+  };
+
+  /** Click to start running the attribute selector */
+  protected JButton m_StartBut = new JButton("Start");
+
+  /** Click to stop a running classifier */
+  protected JButton m_StopBut = new JButton("Stop");
+
+ /** Stop the class combo from taking up to much space */
+  private Dimension COMBO_SIZE = new Dimension(150, m_StartBut
+					       .getPreferredSize().height);
+  
+  /** The main set of instances we're playing with */
+  protected Instances m_Instances;
+
+  /** A thread that attribute selection runs in */
+  protected Thread m_RunThread;
+
+  /* Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the classifier panel
+   */
+  public AttributeSelectionPanel() {
+
+    // Connect / configure the components
+    m_OutText.setEditable(false);
+    m_OutText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_OutText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_OutText.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	if ((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	    != InputEvent.BUTTON1_MASK) {
+	  m_OutText.selectAll();
+	}
+      }
+    });
+    m_History.setBorder(BorderFactory.createTitledBorder("Result list (right-click for options)"));
+    m_AttributeEvaluatorEditor.setClassType(ASEvaluation.class);
+    m_AttributeEvaluatorEditor.setValue(ExplorerDefaults.getASEvaluator());
+    m_AttributeEvaluatorEditor.
+      addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        if (m_AttributeEvaluatorEditor.getValue() instanceof AttributeEvaluator) {
+          if (!(m_AttributeSearchEditor.getValue() instanceof Ranker)) {
+            Object backup = m_AttributeEvaluatorEditor.getBackup();
+            int result = 
+              JOptionPane.showConfirmDialog(null, "You must use use the Ranker search method "
+                                            +"in order to use\n"
+                                            +m_AttributeEvaluatorEditor.getValue().getClass().getName()
+                                            +".\nShould I select the Ranker search method for you?",
+                                            "Alert!", JOptionPane.YES_NO_OPTION);
+            if (result == JOptionPane.YES_OPTION) {
+              m_AttributeSearchEditor.setValue(new Ranker());
+            } else {
+              // restore to what was there previously (if possible)
+              if (backup != null) {
+                m_AttributeEvaluatorEditor.setValue(backup);
+              }             
+            }
+          }
+        } else {
+          if (m_AttributeSearchEditor.getValue() instanceof Ranker) {
+            Object backup = m_AttributeEvaluatorEditor.getBackup();
+            int result = 
+              JOptionPane.showConfirmDialog(null, "You must use use a search method that explores \n"
+                                            +"the space of attribute subsets (such as GreedyStepwise) in "
+                                            +"order to use\n"
+                                            +m_AttributeEvaluatorEditor.getValue().getClass().getName()
+                                            +".\nShould I select the GreedyStepwise search method for "
+                                            +"you?\n(you can always switch to a different method afterwards)",
+                                            "Alert!", JOptionPane.YES_NO_OPTION);
+            if (result == JOptionPane.YES_OPTION) {
+              m_AttributeSearchEditor.setValue(new weka.attributeSelection.GreedyStepwise());
+            } else {
+              // restore to what was there previously (if possible)
+              if (backup != null) {
+                m_AttributeEvaluatorEditor.setValue(backup);
+              } 
+            }
+          }
+        }
+	updateRadioLinks();
+	
+	m_StartBut.setEnabled(true);
+	// check capabilities...
+        Capabilities currentFilter = m_AttributeEvaluatorEditor.getCapabilitiesFilter();
+        ASEvaluation evaluator = (ASEvaluation) m_AttributeEvaluatorEditor.getValue();
+        Capabilities currentSchemeCapabilities =  null;
+        if (evaluator != null && currentFilter != null && 
+            (evaluator instanceof CapabilitiesHandler)) {
+          currentSchemeCapabilities = ((CapabilitiesHandler)evaluator).getCapabilities();
+          
+          if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+              !currentSchemeCapabilities.supports(currentFilter)) {
+            m_StartBut.setEnabled(false);
+          }
+        }
+	repaint();
+      }
+    });
+
+    m_AttributeSearchEditor.setClassType(ASSearch.class);
+    m_AttributeSearchEditor.setValue(ExplorerDefaults.getASSearch());
+    m_AttributeSearchEditor.
+      addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+          if (m_AttributeSearchEditor.getValue() instanceof Ranker) {
+            if (!(m_AttributeEvaluatorEditor.getValue() instanceof AttributeEvaluator)) {
+              Object backup = m_AttributeSearchEditor.getBackup();
+              int result = 
+                JOptionPane.showConfirmDialog(null, "You must use use an evaluator that evaluates\n"
+                                              +"single attributes (such as InfoGain) in order to use\n"
+                                              +"the Ranker. Should I select the InfoGain evaluator "
+                                              +"for you?\n"
+                                              +"(You can always switch to a different method afterwards)" ,
+                                              "Alert!", JOptionPane.YES_NO_OPTION);
+              if (result == JOptionPane.YES_OPTION) {
+                m_AttributeEvaluatorEditor.setValue(new weka.attributeSelection.InfoGainAttributeEval());
+              } else {
+                // restore to what was there previously (if possible)
+                if (backup != null) {
+                  m_AttributeSearchEditor.setValue(backup);
+                }             
+              }
+            }
+          } else {
+            if (m_AttributeEvaluatorEditor.getValue() instanceof AttributeEvaluator) {
+              Object backup = m_AttributeSearchEditor.getBackup();
+              int result = 
+                JOptionPane.showConfirmDialog(null, "You must use use an evaluator that evaluates\n"
+                                              +"subsets of attributes (such as CFS) in order to use\n"
+                                              +m_AttributeEvaluatorEditor.getValue().getClass().getName()
+                                              +".\nShould I select the CFS subset evaluator for you?"
+                                              +"\n(you can always switch to a different method afterwards)",
+                                              "Alert!", JOptionPane.YES_NO_OPTION);
+
+              if (result == JOptionPane.YES_OPTION) {
+                m_AttributeEvaluatorEditor.setValue(new weka.attributeSelection.CfsSubsetEval());
+              } else {
+                // restore to what was there previously (if possible)
+                if (backup != null) {
+                  m_AttributeSearchEditor.setValue(backup);
+                }              
+              }
+            }
+          }
+	  repaint();
+	}
+      });
+    
+    m_ClassCombo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	updateCapabilitiesFilter(m_AttributeEvaluatorEditor.getCapabilitiesFilter());
+      }
+    });
+
+    m_ClassCombo.setToolTipText("Select the attribute to use as the class");
+    m_TrainBut.setToolTipText("select attributes using the full training "
+			      + "dataset");
+    m_CVBut.setToolTipText("Perform a n-fold cross-validation");
+
+    m_StartBut.setToolTipText("Starts attribute selection");
+    m_StopBut.setToolTipText("Stops a attribute selection task");
+    
+    m_ClassCombo.setPreferredSize(COMBO_SIZE);
+    m_ClassCombo.setMaximumSize(COMBO_SIZE);
+    m_ClassCombo.setMinimumSize(COMBO_SIZE);
+    m_History.setPreferredSize(COMBO_SIZE);
+    m_History.setMaximumSize(COMBO_SIZE);
+    m_History.setMinimumSize(COMBO_SIZE);
+    
+    m_ClassCombo.setEnabled(false);
+    m_TrainBut.setSelected(ExplorerDefaults.getASTestMode() == 0);
+    m_CVBut.setSelected(ExplorerDefaults.getASTestMode() == 1);
+    updateRadioLinks();
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_TrainBut);
+    bg.add(m_CVBut);
+
+    m_TrainBut.addActionListener(m_RadioListener);
+    m_CVBut.addActionListener(m_RadioListener);
+
+    m_CVText.setText("" + ExplorerDefaults.getASCrossvalidationFolds());
+    m_SeedText.setText("" + ExplorerDefaults.getASRandomSeed());
+    
+    m_StartBut.setEnabled(false);
+    m_StopBut.setEnabled(false);
+
+    m_StartBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	startAttributeSelection();
+      }
+    });
+    m_StopBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	stopAttributeSelection();
+      }
+    });
+    
+    m_History.setHandleRightClicks(false);
+    // see if we can popup a menu for the selected result
+    m_History.getList().addMouseListener(new MouseAdapter() {
+	public void mouseClicked(MouseEvent e) {
+	  if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	       != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+	    int index = m_History.getList().locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = m_History.getNameAtIndex(index);
+	      visualize(name, e.getX(), e.getY());
+	    } else {
+	      visualize(null, e.getX(), e.getY());
+	    }
+	  }
+	}
+      });
+
+    // Layout the GUI
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Attribute Evaluator"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    p1.setLayout(new BorderLayout());
+    p1.add(m_AEEPanel, BorderLayout.NORTH);
+
+    JPanel p1_1 = new JPanel();
+    p1_1.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Search Method"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    p1_1.setLayout(new BorderLayout());
+    p1_1.add(m_ASEPanel, BorderLayout.NORTH);
+
+    JPanel p_new = new JPanel();
+    p_new.setLayout(new BorderLayout());
+    p_new.add(p1, BorderLayout.NORTH);
+    p_new.add(p1_1, BorderLayout.CENTER);
+
+    JPanel p2 = new JPanel();
+    GridBagLayout gbL = new GridBagLayout();
+    p2.setLayout(gbL);
+    p2.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Attribute Selection Mode"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    GridBagConstraints gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 2;     gbC.gridx = 0;
+    gbL.setConstraints(m_TrainBut, gbC);
+    p2.add(m_TrainBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 4;     gbC.gridx = 0;
+    gbL.setConstraints(m_CVBut, gbC);
+    p2.add(m_CVBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 4;     gbC.gridx = 1;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_CVLab, gbC);
+    p2.add(m_CVLab);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 4;     gbC.gridx = 2;  gbC.weightx = 100;
+    gbC.ipadx = 20;
+    gbL.setConstraints(m_CVText, gbC);
+    p2.add(m_CVText);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 6;     gbC.gridx = 1;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_SeedLab, gbC);
+    p2.add(m_SeedLab);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 6;     gbC.gridx = 2;  gbC.weightx = 100;
+    gbC.ipadx = 20;
+    gbL.setConstraints(m_SeedText, gbC);
+    p2.add(m_SeedText);
+
+
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new GridLayout(2, 2));
+    buttons.add(m_ClassCombo);
+    m_ClassCombo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    JPanel ssButs = new JPanel();
+    ssButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ssButs.setLayout(new GridLayout(1, 2, 5, 5));
+    ssButs.add(m_StartBut);
+    ssButs.add(m_StopBut);
+    buttons.add(ssButs);
+    
+    JPanel p3 = new JPanel();
+    p3.setBorder(BorderFactory.
+		 createTitledBorder("Attribute selection output"));
+    p3.setLayout(new BorderLayout());
+    final JScrollPane js = new JScrollPane(m_OutText);
+    p3.add(js, BorderLayout.CENTER);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });
+
+    JPanel mondo = new JPanel();
+    gbL = new GridBagLayout();
+    mondo.setLayout(gbL);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 0; gbC.weightx = 0;
+    gbL.setConstraints(p2, gbC);
+    mondo.add(p2);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0; gbC.weightx = 0;
+    gbL.setConstraints(buttons, gbC);
+    mondo.add(buttons);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 2;     gbC.gridx = 0; gbC.weightx = 0; gbC.weighty = 100;
+    gbL.setConstraints(m_History, gbC);
+    mondo.add(m_History);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.gridheight = 3;
+    gbC.weightx = 100; gbC.weighty = 100;
+    gbL.setConstraints(p3, gbC);
+    mondo.add(p3);
+
+    setLayout(new BorderLayout());
+    add(p_new, BorderLayout.NORTH);
+    add(mondo, BorderLayout.CENTER);
+  }
+
+  
+  /**
+   * Updates the enabled status of the input fields and labels.
+   */
+  protected void updateRadioLinks() {
+    m_CVBut.setEnabled(true);
+    m_CVText.setEnabled(m_CVBut.isSelected());
+    m_CVLab.setEnabled(m_CVBut.isSelected());
+    m_SeedText.setEnabled(m_CVBut.isSelected());
+    m_SeedLab.setEnabled(m_CVBut.isSelected());
+    
+    if (m_AttributeEvaluatorEditor.getValue() 
+	instanceof AttributeTransformer) {
+      m_CVBut.setSelected(false);
+      m_CVBut.setEnabled(false);
+      m_CVText.setEnabled(false);
+      m_CVLab.setEnabled(false);
+      m_SeedText.setEnabled(false);
+      m_SeedLab.setEnabled(false);
+      m_TrainBut.setSelected(true);
+    }
+  }
+  
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+
+    m_Log = newLog;
+  }
+  
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    
+    m_Instances = inst;
+    String [] attribNames = new String [m_Instances.numAttributes()];
+    for (int i = 0; i < attribNames.length; i++) {
+      String type = "";
+      switch (m_Instances.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = "(Nom) ";
+	break;
+      case Attribute.NUMERIC:
+	type = "(Num) ";
+	break;
+      case Attribute.STRING:
+	type = "(Str) ";
+	break;
+      case Attribute.DATE:
+	type = "(Dat) ";
+	break;
+      case Attribute.RELATIONAL:
+	type = "(Rel) ";
+	break;
+      default:
+	type = "(???) ";
+      }
+      String attnm = m_Instances.attribute(i).name();
+     
+      attribNames[i] = type + attnm;
+    }
+    m_StartBut.setEnabled(m_RunThread == null);
+    m_StopBut.setEnabled(m_RunThread != null);
+    m_ClassCombo.setModel(new DefaultComboBoxModel(attribNames));
+    if (inst.classIndex() == -1)
+      m_ClassCombo.setSelectedIndex(attribNames.length - 1);
+    else
+      m_ClassCombo.setSelectedIndex(inst.classIndex());
+    m_ClassCombo.setEnabled(true);
+  }
+  
+  /**
+   * Starts running the currently configured attribute evaluator and
+   * search method. This is run in a separate thread, and will only start if
+   * there is no attribute selection  already running. The attribute selection
+   * output is sent to the results history panel.
+   */
+  protected void startAttributeSelection() {
+
+    if (m_RunThread == null) {
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(true);
+      m_RunThread = new Thread() {
+	public void run() {
+	  // Copy the current state of things
+	  m_Log.statusMessage("Setting up...");
+	  Instances inst = new Instances(m_Instances);
+
+	  int testMode = 0;
+	  int numFolds = 10;
+	  int seed = 1;
+	  int classIndex = m_ClassCombo.getSelectedIndex();
+	  ASEvaluation evaluator = 
+	     (ASEvaluation) m_AttributeEvaluatorEditor.getValue();
+
+	  ASSearch search = (ASSearch) m_AttributeSearchEditor.getValue();
+
+	  StringBuffer outBuff = new StringBuffer();
+	  String name = (new SimpleDateFormat("HH:mm:ss - "))
+	  .format(new Date());
+	  String sname = search.getClass().getName();
+	  if (sname.startsWith("weka.attributeSelection.")) {
+	    name += sname.substring("weka.attributeSelection.".length());
+	  } else {
+	    name += sname;
+	  }
+	  String ename = evaluator.getClass().getName();
+	  if (ename.startsWith("weka.attributeSelection.")) {
+	    name += (" + "
+		     +ename.substring("weka.attributeSelection.".length()));
+	  } else {
+	    name += (" + "+ename);
+	  }
+          
+	  // assemble commands
+          String cmd;
+	  String cmdFilter;
+          String cmdClassifier;
+          
+          // 1. attribute selection command
+          Vector<String> list = new Vector<String>();
+          list.add("-s");
+          if (search instanceof OptionHandler)
+            list.add(sname + " " + Utils.joinOptions(((OptionHandler) search).getOptions()));
+          else
+            list.add(sname);
+          if (evaluator instanceof OptionHandler) {
+            String[] opt = ((OptionHandler) evaluator).getOptions();
+            for (int i = 0; i < opt.length; i++)
+              list.add(opt[i]);
+          }
+          cmd =   ename + " " 
+                + Utils.joinOptions(list.toArray(new String[list.size()]));
+
+          // 2. filter command
+          weka.filters.supervised.attribute.AttributeSelection filter = 
+            new weka.filters.supervised.attribute.AttributeSelection();
+          filter.setEvaluator((ASEvaluation) m_AttributeEvaluatorEditor.getValue());
+          filter.setSearch((ASSearch) m_AttributeSearchEditor.getValue());
+	  cmdFilter =   filter.getClass().getName() + " " 
+                      + Utils.joinOptions(((OptionHandler) filter).getOptions());
+
+          // 3. meta-classifier command
+          weka.classifiers.meta.AttributeSelectedClassifier cls = 
+            new weka.classifiers.meta.AttributeSelectedClassifier();
+          cls.setEvaluator((ASEvaluation) m_AttributeEvaluatorEditor.getValue());
+          cls.setSearch((ASSearch) m_AttributeSearchEditor.getValue());
+          cmdClassifier =   cls.getClass().getName() + " " 
+                          + Utils.joinOptions(cls.getOptions());
+          
+	  AttributeSelection eval = null;
+
+	  try {
+	    if (m_CVBut.isSelected()) {
+	      testMode = 1;
+	      numFolds = Integer.parseInt(m_CVText.getText());
+	      seed = Integer.parseInt(m_SeedText.getText());
+	      if (numFolds <= 1) {
+		throw new Exception("Number of folds must be greater than 1");
+	      }
+	    } 
+	    inst.setClassIndex(classIndex);
+
+	    // Output some header information
+	    m_Log.logMessage("Started " + ename);
+	    m_Log.logMessage("Command: " + cmd);
+            m_Log.logMessage("Filter command: " + cmdFilter);
+            m_Log.logMessage("Meta-classifier command: " + cmdClassifier);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskStarted();
+	    }
+	    outBuff.append("=== Run information ===\n\n");
+	    outBuff.append("Evaluator:    " + ename);
+	    if (evaluator instanceof OptionHandler) {
+	      String [] o = ((OptionHandler) evaluator).getOptions();
+	      outBuff.append(" " + Utils.joinOptions(o));
+	    }
+	    outBuff.append("\nSearch:       " + sname);
+	    if (search instanceof OptionHandler) {
+	      String [] o = ((OptionHandler) search).getOptions();
+	      outBuff.append(" " + Utils.joinOptions(o));
+	    }
+	    outBuff.append("\n");
+	    outBuff.append("Relation:     " + inst.relationName() + '\n');
+	    outBuff.append("Instances:    " + inst.numInstances() + '\n');
+	    outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
+	    if (inst.numAttributes() < 100) {
+	      for (int i = 0; i < inst.numAttributes(); i++) {
+		outBuff.append("              " + inst.attribute(i).name()
+			       + '\n');
+	      }
+	    } else {
+	      outBuff.append("              [list of attributes omitted]\n");
+	    }
+	    outBuff.append("Evaluation mode:    ");
+	    switch (testMode) {
+	      case 0: // select using all training
+	      outBuff.append("evaluate on all training data\n");
+	      break;
+	      case 1: // CV mode
+	      outBuff.append("" + numFolds + "-fold cross-validation\n");
+	      break;
+	    }
+	    outBuff.append("\n");
+	    m_History.addResult(name, outBuff);
+	    m_History.setSingle(name);
+	    
+	    // Do the feature selection and output the results.
+	    m_Log.statusMessage("Doing feature selection...");
+	    m_History.updateResult(name);
+	    
+	    eval = new AttributeSelection();
+	    eval.setEvaluator(evaluator);
+	    eval.setSearch(search);
+	    eval.setFolds(numFolds);
+	    eval.setSeed(seed);
+	    if (testMode == 1) {
+	      eval.setXval(true);
+	    }
+	    	    
+	    switch (testMode) {
+	      case 0: // select using training
+	      m_Log.statusMessage("Evaluating on training data...");
+	      eval.SelectAttributes(inst);
+	      break;
+
+	      case 1: // CV mode
+	      m_Log.statusMessage("Randomizing instances...");
+	      Random random = new Random(seed);
+	      inst.randomize(random);
+	      if (inst.attribute(classIndex).isNominal()) {
+		m_Log.statusMessage("Stratifying instances...");
+		inst.stratify(numFolds);
+	      }
+	      for (int fold = 0; fold < numFolds;fold++) {
+		m_Log.statusMessage("Creating splits for fold "
+				    + (fold + 1) + "...");
+		Instances train = inst.trainCV(numFolds, fold, random);
+		m_Log.statusMessage("Selecting attributes using all but fold "
+				    + (fold + 1) + "...");
+		
+		eval.selectAttributesCVSplit(train);
+	      }
+	      break;
+	      default:
+	      throw new Exception("Test mode not implemented");
+	    }
+
+	    if (testMode == 0) {
+	      outBuff.append(eval.toResultsString());
+	    } else {
+	      outBuff.append(eval.CVResultsString());
+	    }
+	  
+	    outBuff.append("\n");
+	    m_History.updateResult(name);
+	    m_Log.logMessage("Finished " + ename+" "+sname);
+	    m_Log.statusMessage("OK");
+	  } catch (Exception ex) {
+	    m_Log.logMessage(ex.getMessage());
+	    m_Log.statusMessage("See error log");
+	  } finally {
+	    if (evaluator instanceof AttributeTransformer) {
+	      try {
+		Instances transformed = 
+		  ((AttributeTransformer)evaluator).transformedData(inst);
+		transformed.
+		  setRelationName("AT: "+transformed.relationName());
+
+		FastVector vv = new FastVector();
+		vv.addElement(transformed);
+		m_History.addObject(name, vv);
+	      } catch (Exception ex) {
+		System.err.println(ex);
+		ex.printStackTrace();
+	      }
+	    } else if (testMode == 0) {
+	      try {
+		Instances reducedInst = eval.reduceDimensionality(inst);
+		FastVector vv = new FastVector();
+		vv.addElement(reducedInst);
+		m_History.addObject(name, vv);
+	      } catch (Exception ex) {
+		ex.printStackTrace();
+	      }
+	    }
+	    if (isInterrupted()) {
+	      m_Log.logMessage("Interrupted " + ename+" "+sname);
+	      m_Log.statusMessage("See error log");
+	    }
+	    m_RunThread = null;
+	    m_StartBut.setEnabled(true);
+	    m_StopBut.setEnabled(false);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskFinished();
+	    }
+	  }
+	}
+      };
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+  
+  /**
+   * Stops the currently running attribute selection (if any).
+   */
+  protected void stopAttributeSelection() {
+
+    if (m_RunThread != null) {
+      m_RunThread.interrupt();
+      
+      // This is deprecated (and theoretically the interrupt should do).
+      m_RunThread.stop();
+      
+    }
+  }
+  
+  /**
+   * Save the named buffer to a file.
+   * @param name the name of the buffer to be saved.
+   */
+  protected void saveBuffer(String name) {
+    StringBuffer sb = m_History.getNamedBuffer(name);
+    if (sb != null) {
+      if (m_SaveOut.save(sb)) {
+	m_Log.logMessage("Save succesful.");
+      }
+    }
+  }
+
+  /**
+   * Popup a visualize panel for viewing transformed data
+   * 
+   * @param ti          the Instances to display
+   */
+  protected void visualizeTransformedData(Instances ti) {
+    if (ti != null) {
+      MatrixPanel mp = new MatrixPanel();
+      mp.setInstances(ti);
+      String plotName = ti.relationName();
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Attribute Selection Visualize: "
+			       +plotName);
+      jf.setSize(800,600);
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(mp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	  }
+	});
+
+      jf.setVisible(true);
+    }
+  }
+
+  /**
+   * Popup a SaveDialog for saving the transformed data
+   * 
+   * @param ti          the Instances to display
+   */
+  protected void saveTransformedData(Instances ti) {
+    JFileChooser        fc;
+    int                 retVal;
+    BufferedWriter      writer;
+    ExtensionFileFilter filter;
+
+    fc     = new JFileChooser();
+    filter = new ExtensionFileFilter(".arff", "ARFF data files");
+    fc.setFileFilter(filter);
+    retVal = fc.showSaveDialog(this);
+
+    if (retVal == JFileChooser.APPROVE_OPTION) {
+      try {
+        writer = new BufferedWriter(new FileWriter(fc.getSelectedFile()));
+        writer.write(ti.toString());
+        writer.flush();
+        writer.close();
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+        m_Log.logMessage("Problem saving data: " + e.getMessage());
+        JOptionPane.showMessageDialog(
+            this, 
+            "Problem saving data:\n" + e.getMessage(), 
+            "Error", 
+            JOptionPane.ERROR_MESSAGE);
+      }
+    }
+  }
+
+  /**
+   * Handles constructing a popup menu with visualization options
+   * @param name the name of the result history list entry clicked on by
+   * the user.
+   * @param x the x coordinate for popping up the menu
+   * @param y the y coordinate for popping up the menu
+   */
+  protected void visualize(String name, int x, int y) {
+    final String selectedName = name;
+    JPopupMenu resultListMenu = new JPopupMenu();
+
+    JMenuItem visMainBuffer = new JMenuItem("View in main window");
+    if (selectedName != null) {
+      visMainBuffer.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_History.setSingle(selectedName);
+	  }
+	});
+    } else {
+      visMainBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visMainBuffer);
+
+    JMenuItem visSepBuffer = new JMenuItem("View in separate window");
+    if (selectedName != null) {
+    visSepBuffer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.openFrame(selectedName);
+	}
+      });
+    } else {
+      visSepBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visSepBuffer);
+
+    JMenuItem saveOutput = new JMenuItem("Save result buffer");
+    if (selectedName != null) {
+    saveOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  saveBuffer(selectedName);
+	}
+      });
+    } else {
+      saveOutput.setEnabled(false);
+    }
+    resultListMenu.add(saveOutput);
+    
+    JMenuItem deleteOutput = new JMenuItem("Delete result buffer");
+    if (selectedName != null) {
+      deleteOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.removeResult(selectedName);
+	}
+      });
+    } else {
+      deleteOutput.setEnabled(false);
+    }
+    resultListMenu.add(deleteOutput);
+
+
+    FastVector o = null;
+    if (selectedName != null) {
+      o = (FastVector)m_History.getNamedObject(selectedName);
+    }    
+
+    //    VisualizePanel temp_vp = null;
+    Instances tempTransformed = null;
+
+    if (o != null) {
+      for (int i = 0; i < o.size(); i++) {
+	Object temp = o.elementAt(i);
+	//	if (temp instanceof VisualizePanel) {
+	if (temp instanceof Instances) {
+	  //	  temp_vp = (VisualizePanel)temp;
+	  tempTransformed = (Instances) temp;
+	} 
+      }
+    }
+    
+    //    final VisualizePanel vp = temp_vp;
+    final Instances ti = tempTransformed;
+    JMenuItem visTrans = null;
+    
+    if (ti != null) {
+      if (ti.relationName().startsWith("AT:")) {
+	visTrans = new JMenuItem("Visualize transformed data");
+      } else {
+	visTrans = new JMenuItem("Visualize reduced data");
+      }
+      resultListMenu.addSeparator();
+    }
+
+    //    JMenuItem visTrans = new JMenuItem("Visualize transformed data");
+    if (ti != null && visTrans != null) {
+      visTrans.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    visualizeTransformedData(ti);
+	  }
+	});
+    }
+     
+    if (visTrans != null) {
+      resultListMenu.add(visTrans);
+    }
+    
+    JMenuItem saveTrans = null;
+    if (ti != null) {
+      if (ti.relationName().startsWith("AT:"))
+        saveTrans = new JMenuItem("Save transformed data...");
+      else
+        saveTrans = new JMenuItem("Save reduced data...");
+    }
+    if (saveTrans != null) {
+      saveTrans.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            saveTransformedData(ti);
+          }
+      });
+      resultListMenu.add(saveTrans);
+    }
+    
+    resultListMenu.show(m_History.getList(), x, y);
+  }
+  
+  /**
+   * updates the capabilities filter of the GOE
+   * 
+   * @param filter	the new filter to use
+   */
+  protected void updateCapabilitiesFilter(Capabilities filter) {
+    Instances 		tempInst;
+    Capabilities 	filterClass;
+
+    if (filter == null) {
+      m_AttributeEvaluatorEditor.setCapabilitiesFilter(new Capabilities(null));
+      m_AttributeSearchEditor.setCapabilitiesFilter(new Capabilities(null));
+      return;
+    }
+    
+    if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
+      tempInst = new Instances(m_Instances, 0);
+    else
+      tempInst = new Instances(m_Instances);
+    tempInst.setClassIndex(m_ClassCombo.getSelectedIndex());
+
+    try {
+      filterClass = Capabilities.forInstances(tempInst);
+    }
+    catch (Exception e) {
+      filterClass = new Capabilities(null);
+    }
+    
+    // set new filter
+    m_AttributeEvaluatorEditor.setCapabilitiesFilter(filterClass);
+    m_AttributeSearchEditor.setCapabilitiesFilter(filterClass);
+    
+    m_StartBut.setEnabled(true);
+    // check capabilities...
+    Capabilities currentFilter = m_AttributeEvaluatorEditor.getCapabilitiesFilter();
+    ASEvaluation evaluator = (ASEvaluation) m_AttributeEvaluatorEditor.getValue();
+    Capabilities currentSchemeCapabilities =  null;
+    if (evaluator != null && currentFilter != null && 
+        (evaluator instanceof CapabilitiesHandler)) {
+      currentSchemeCapabilities = ((CapabilitiesHandler)evaluator).getCapabilities();
+      
+      if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+          !currentSchemeCapabilities.supports(currentFilter)) {
+        m_StartBut.setEnabled(false);
+      }
+    }
+  }
+  
+  /**
+   * method gets called in case of a change event
+   * 
+   * @param e		the associated change event
+   */
+  public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
+    if (e.getFilter() == null)
+      updateCapabilitiesFilter(null);
+    else
+      updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
+  }
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Select attributes";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Determine relevance of attributes";
+  }
+
+  /**
+   * Tests out the attribute selection panel from the command line.
+   *
+   * @param args may optionally contain the name of a dataset to load.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Select attributes");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final AttributeSelectionPanel sp = new AttributeSelectionPanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      sp.setLog(lp);
+      jf.getContentPane().add(lp, BorderLayout.SOUTH);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setVisible(true);
+      if (args.length == 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	sp.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierErrorsPlotInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierErrorsPlotInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierErrorsPlotInstances.java	(revision 29)
@@ -0,0 +1,491 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClassifierErrorsPlotInstances.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.explorer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.Evaluation;
+import weka.classifiers.IntervalEstimator;
+import weka.classifiers.evaluation.NumericPrediction;
+import weka.core.Attribute;
+import weka.core.DenseInstance;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.visualize.Plot2D;
+import weka.gui.visualize.PlotData2D;
+
+/**
+ * A class for generating plottable visualization errors.
+ * <p/>
+ * Example usage:
+ * <pre>
+ * Instances train = ... // from somewhere
+ * Instances test = ... // from somewhere
+ * Classifier cls = ... // from somewhere
+ * // build classifier
+ * cls.buildClassifier(train);
+ * // evaluate classifier and generate plot instances
+ * ClassifierPlotInstances plotInstances = new ClassifierPlotInstances();
+ * plotInstances.setClassifier(cls);
+ * plotInstances.setInstances(train);
+ * plotInstances.setClassIndex(train.classIndex());
+ * plotInstances.setUp();
+ * Evaluation eval = new Evaluation(train);
+ * for (int i = 0; i &lt; test.numInstances(); i++)
+ *   plotInstances.process(test.instance(i), cls, eval);
+ * // generate visualization
+ * VisualizePanel visPanel = new VisualizePanel();
+ * visPanel.addPlot(plotInstances.getPlotData("plot name"));
+ * visPanel.setColourIndex(plotInstances.getPlotInstances().classIndex()+1);
+ * // clean up
+ * plotInstances.cleanUp();
+ * </pre>
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6103 $
+ */
+public class ClassifierErrorsPlotInstances
+  extends AbstractPlotInstances {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -3941976365792013279L;
+
+  /** the minimum plot size for numeric errors. */
+  protected int m_MinimumPlotSizeNumeric;
+
+  /** the maximum plot size for numeric errors. */
+  protected int m_MaximumPlotSizeNumeric;
+  
+  /** whether to save the instances for visualization or just evaluate the 
+   * instance. */
+  protected boolean m_SaveForVisualization;
+  
+  /** for storing the plot shapes. */
+  protected FastVector m_PlotShapes;
+  
+  /** for storing the plot sizes. */
+  protected FastVector m_PlotSizes;
+  
+  /** the classifier being used. */
+  protected Classifier m_Classifier;
+
+  /** the class index. */
+  protected int m_ClassIndex;
+  
+  /** the Evaluation object to use. */
+  protected Evaluation m_Evaluation;
+  
+  /**
+   * Initializes the members.
+   */
+  protected void initialize() {
+    super.initialize();
+    
+    m_PlotShapes             = new FastVector();
+    m_PlotSizes              = new FastVector();
+    m_Classifier             = null;
+    m_ClassIndex             = -1;
+    m_Evaluation             = null;
+    m_SaveForVisualization   = true;
+    m_MinimumPlotSizeNumeric = ExplorerDefaults.getClassifierErrorsMinimumPlotSizeNumeric();
+    m_MaximumPlotSizeNumeric = ExplorerDefaults.getClassifierErrorsMaximumPlotSizeNumeric();
+  }
+  
+  /**
+   * Sets the classifier used for making the predictions.
+   * 
+   * @param value	the classifier to use
+   */
+  public void setClassifier(Classifier value) {
+    m_Classifier = value;
+  }
+  
+  /**
+   * Returns the currently set classifier.
+   * 
+   * @return		the classifier in use
+   */
+  public Classifier getClassifier() {
+    return m_Classifier;
+  }
+
+  /**
+   * Sets the 0-based class index.
+   * 
+   * @param index	the class index
+   */
+  public void setClassIndex(int index) {
+    m_ClassIndex = index;
+  }
+  
+  /**
+   * Returns the 0-based class index.
+   * 
+   * @return		the class index
+   */
+  public int getClassIndex() {
+    return m_ClassIndex;
+  }
+
+  /**
+   * Sets the Evaluation object to use.
+   * 
+   * @param value	the evaluation to use
+   */
+  public void setEvaluation(Evaluation value) {
+    m_Evaluation = value;
+  }
+  
+  /**
+   * Returns the Evaluation object in use.
+   * 
+   * @return		the evaluation object
+   */
+  public Evaluation getEvaluation() {
+    return m_Evaluation;
+  }
+  
+  /**
+   * Sets whether the instances are saved for visualization or only evaluation
+   * of the prediction is to happen.
+   * 
+   * @param value	if true then the instances will be saved
+   */
+  public void setSaveForVisualization(boolean value) {
+    m_SaveForVisualization = value;
+  }
+  
+  /**
+   * Returns whether the instances are saved for visualization for only
+   * evaluation of the prediction is to happen.
+   * 
+   * @return		true if the instances are saved
+   */
+  public boolean getSaveForVisualization() {
+    return m_SaveForVisualization;
+  }
+  
+  /**
+   * Checks whether classifier, class index and evaluation are provided.
+   */
+  protected void check() {
+    super.check();
+    
+    if (m_Classifier == null)
+      throw new IllegalStateException("No classifier set!");
+    
+    if (m_ClassIndex == -1)
+      throw new IllegalStateException("No class index set!");
+    
+    if (m_Evaluation == null)
+      throw new IllegalStateException("No evaluation set");
+  }
+  
+  /**
+   * Sets up the structure for the plot instances. Sets m_PlotInstances to null
+   * if instances are not saved for visualization.
+   * 
+   * @see #getSaveForVisualization()
+   */
+  protected void determineFormat() {
+    FastVector 	hv;
+    Attribute 	predictedClass;
+    Attribute 	classAt;
+    FastVector 	attVals;
+    int		i;
+    
+    if (!m_SaveForVisualization) {
+      m_PlotInstances = null;
+      return;
+    }
+    
+    hv = new FastVector();
+
+    classAt = m_Instances.attribute(m_ClassIndex);
+    if (classAt.isNominal()) {
+      attVals = new FastVector();
+      for (i = 0; i < classAt.numValues(); i++)
+	attVals.addElement(classAt.value(i));
+      predictedClass = new Attribute("predicted" + classAt.name(), attVals);
+    }
+    else {
+      predictedClass = new Attribute("predicted" + classAt.name());
+    }
+
+    for (i = 0; i < m_Instances.numAttributes(); i++) {
+      if (i == m_Instances.classIndex())
+	hv.addElement(predictedClass);
+      hv.addElement(m_Instances.attribute(i).copy());
+    }
+    
+    m_PlotInstances = new Instances(
+	m_Instances.relationName() + "_predicted", hv, m_Instances.numInstances());
+    m_PlotInstances.setClassIndex(m_ClassIndex + 1);
+  }
+  
+  /**
+   * Process a classifier's prediction for an instance and update a
+   * set of plotting instances and additional plotting info. m_PlotShape
+   * for nominal class datasets holds shape types (actual data points have
+   * automatic shape type assignment; classifier error data points have
+   * box shape type). For numeric class datasets, the actual data points
+   * are stored in m_PlotInstances and m_PlotSize stores the error (which is
+   * later converted to shape size values).
+   * 
+   * @param toPredict 	the actual data point
+   * @param classifier 	the classifier
+   * @param eval 	the evaluation object to use for evaluating the classifier on
+   * 			the instance to predict
+   * @see		#m_PlotShapes
+   * @see		#m_PlotSizes
+   * @see		#m_PlotInstances
+   */
+  public void process(Instance toPredict, Classifier classifier, Evaluation eval) {
+    double 	pred;
+    double[] 	values;
+    int		i;
+    
+    try {
+      pred = eval.evaluateModelOnceAndRecordPrediction(classifier, toPredict);
+      
+      if (!m_SaveForVisualization)
+	return;
+
+      if (m_PlotInstances != null) {
+        values = new double[m_PlotInstances.numAttributes()];
+        for (i = 0; i < m_PlotInstances.numAttributes(); i++) {
+          if (i < toPredict.classIndex()) {
+            values[i] = toPredict.value(i);
+          }
+          else if (i == toPredict.classIndex()) {
+            values[i]   = pred;
+            values[i+1] = toPredict.value(i);
+            i++;
+          }
+          else {
+            values[i] = toPredict.value(i-1);
+          }
+        }
+
+        m_PlotInstances.add(new DenseInstance(1.0, values));
+        
+        if (toPredict.classAttribute().isNominal()) {
+          if (toPredict.isMissing(toPredict.classIndex()) || Utils.isMissingValue(pred)) {
+            m_PlotShapes.addElement(new Integer(Plot2D.MISSING_SHAPE));
+          }
+          else if (pred != toPredict.classValue()) {
+            // set to default error point shape
+            m_PlotShapes.addElement(new Integer(Plot2D.ERROR_SHAPE));
+          }
+          else {
+            // otherwise set to constant (automatically assigned) point shape
+            m_PlotShapes.addElement(new Integer(Plot2D.CONST_AUTOMATIC_SHAPE));
+          }
+          m_PlotSizes.addElement(new Integer(Plot2D.DEFAULT_SHAPE_SIZE));
+        }
+        else {
+          // store the error (to be converted to a point size later)
+          Double errd = null;
+          if (!toPredict.isMissing(toPredict.classIndex()) && !Utils.isMissingValue(pred)) {
+            errd = new Double(pred - toPredict.classValue());
+            m_PlotShapes.addElement(new Integer(Plot2D.CONST_AUTOMATIC_SHAPE));
+          }
+          else {
+            // missing shape if actual class not present or prediction is missing
+            m_PlotShapes.addElement(new Integer(Plot2D.MISSING_SHAPE));
+          }
+          m_PlotSizes.addElement(errd);
+        }
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Scales numeric class predictions into shape sizes for plotting
+   * in the visualize panel.
+   */
+  protected void scaleNumericPredictions() {
+    double 	maxErr;
+    double 	minErr;
+    double 	err;
+    int		i;
+    Double 	errd;
+    double 	temp;
+    
+    maxErr = Double.NEGATIVE_INFINITY;
+    minErr = Double.POSITIVE_INFINITY;
+
+    // find min/max errors
+    for (i = 0; i < m_PlotSizes.size(); i++) {
+      errd = (Double) m_PlotSizes.elementAt(i);
+      if (errd != null) {
+	err = Math.abs(errd.doubleValue());
+        if (err < minErr)
+	  minErr = err;
+	if (err > maxErr)
+	  maxErr = err;
+      }
+    }
+    
+    // scale errors
+    for (i = 0; i < m_PlotSizes.size(); i++) {
+      errd = (Double) m_PlotSizes.elementAt(i);
+      if (errd != null) {
+	err = Math.abs(errd.doubleValue());
+	if (maxErr - minErr > 0) {
+	  temp = (((err - minErr) / (maxErr - minErr)) * (m_MaximumPlotSizeNumeric - m_MinimumPlotSizeNumeric + 1));
+	  m_PlotSizes.setElementAt(new Integer((int) temp) + m_MinimumPlotSizeNumeric, i);
+	}
+	else {
+	  m_PlotSizes.setElementAt(new Integer(m_MinimumPlotSizeNumeric), i);
+	}
+      }
+      else {
+	m_PlotSizes.setElementAt(new Integer(m_MinimumPlotSizeNumeric), i);
+      }
+    }
+  }
+  
+  /**
+   * Adds the prediction intervals as additional attributes at the end.
+   * Since classifiers can returns varying number of intervals per instance,
+   * the dataset is filled with missing values for non-existing intervals.
+   */
+  protected void addPredictionIntervals() {
+    int		maxNum;
+    int		num;
+    int		i;
+    int		n;
+    FastVector	preds;
+    FastVector	atts;
+    Instances	data;
+    Instance	inst;
+    Instance	newInst;
+    double[]	values;
+    double[][]	predInt;
+    
+    // determine the maximum number of intervals
+    maxNum = 0;
+    preds  = m_Evaluation.predictions();
+    for (i = 0; i < preds.size(); i++) {
+      num = ((NumericPrediction) preds.elementAt(i)).predictionIntervals().length;
+      if (num > maxNum)
+	maxNum = num;
+    }
+    
+    // create new header
+    atts = new FastVector();
+    for (i = 0; i < m_PlotInstances.numAttributes(); i++)
+      atts.addElement(m_PlotInstances.attribute(i));
+    for (i = 0; i < maxNum; i++) {
+      atts.addElement(new Attribute("predictionInterval_" + (i+1) + "-lowerBoundary"));
+      atts.addElement(new Attribute("predictionInterval_" + (i+1) + "-upperBoundary"));
+      atts.addElement(new Attribute("predictionInterval_" + (i+1) + "-width"));
+    }
+    data = new Instances(m_PlotInstances.relationName(), atts, m_PlotInstances.numInstances());
+    data.setClassIndex(m_PlotInstances.classIndex());
+    
+    // update data
+    for (i = 0; i < m_PlotInstances.numInstances(); i++) {
+      inst = m_PlotInstances.instance(i);
+      // copy old values
+      values = new double[data.numAttributes()];
+      System.arraycopy(inst.toDoubleArray(), 0, values, 0, inst.numAttributes());
+      // add interval data
+      predInt = ((NumericPrediction) preds.elementAt(i)).predictionIntervals();
+      for (n = 0; n < maxNum; n++) {
+	if (n < predInt.length){
+	  values[m_PlotInstances.numAttributes() + n*3 + 0] = predInt[n][0];
+	  values[m_PlotInstances.numAttributes() + n*3 + 1] = predInt[n][1];
+	  values[m_PlotInstances.numAttributes() + n*3 + 2] = predInt[n][1] - predInt[n][0];
+	}
+	else {
+	  values[m_PlotInstances.numAttributes() + n*3 + 0] = Utils.missingValue();
+	  values[m_PlotInstances.numAttributes() + n*3 + 1] = Utils.missingValue();
+	  values[m_PlotInstances.numAttributes() + n*3 + 2] = Utils.missingValue();
+	}
+      }
+      // create new Instance
+      newInst = new DenseInstance(inst.weight(), values);
+      data.add(newInst);
+    }
+    
+    m_PlotInstances = data;
+  }
+  
+  /**
+   * Performs optional post-processing.
+   * 
+   * @see #scaleNumericPredictions()
+   * @see #addPredictionIntervals()
+   */
+  protected void finishUp() {
+    super.finishUp();
+    
+    if (!m_SaveForVisualization)
+      return;
+    
+    if (m_Instances.attribute(m_ClassIndex).isNumeric())
+      scaleNumericPredictions();
+    if (m_Classifier instanceof IntervalEstimator)
+      addPredictionIntervals();
+  }
+  
+  /**
+   * Assembles and returns the plot. The relation name of the dataset gets
+   * added automatically.
+   * 
+   * @param name	the name of the plot
+   * @return		the plot or null if plot instances weren't saved for visualization
+   * @throws Exception	if plot generation fails
+   */
+  protected PlotData2D createPlotData(String name) throws Exception {
+    PlotData2D 	result;
+    
+    if (!m_SaveForVisualization)
+      return null;
+    
+    result = new PlotData2D(m_PlotInstances);
+    result.setShapeSize(m_PlotSizes);
+    result.setShapeType(m_PlotShapes);
+    result.setPlotName(name + " (" + m_Instances.relationName() + ")");
+    result.addInstanceNumberAttribute();
+
+    return result;
+  }
+  
+  /**
+   * For freeing up memory. Plot data cannot be generated after this call!
+   */
+  public void cleanUp() {
+    super.cleanUp();
+    
+    m_Classifier = null;
+    m_PlotShapes = null;
+    m_PlotSizes  = null;
+    m_Evaluation = null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/ClassifierPanel.java	(revision 29)
@@ -0,0 +1,2478 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassifierPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.CostMatrix;
+import weka.classifiers.Evaluation;
+import weka.classifiers.Sourcable;
+import weka.classifiers.evaluation.CostCurve;
+import weka.classifiers.evaluation.MarginCurve;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.classifiers.evaluation.output.prediction.AbstractOutput;
+import weka.classifiers.evaluation.output.prediction.Null;
+import weka.classifiers.pmml.consumer.PMMLClassifier;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Range;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.core.converters.IncrementalConverter;
+import weka.core.converters.Loader;
+import weka.core.converters.ConverterUtils.DataSource;
+import weka.core.pmml.PMMLFactory;
+import weka.core.pmml.PMMLModel;
+import weka.gui.CostMatrixEditor;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.Logger;
+import weka.gui.PropertyDialog;
+import weka.gui.PropertyPanel;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+import weka.gui.SetInstancesPanel;
+import weka.gui.SysErrLog;
+import weka.gui.TaskLogger;
+import weka.gui.beans.CostBenefitAnalysis;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.explorer.Explorer.LogHandler;
+import weka.gui.graphvisualizer.BIFFormatException;
+import weka.gui.graphvisualizer.GraphVisualizer;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeVisualizer;
+import weka.gui.visualize.PlotData2D;
+import weka.gui.visualize.ThresholdVisualizePanel;
+import weka.gui.visualize.VisualizePanel;
+import weka.gui.visualize.plugins.ErrorVisualizePlugin;
+import weka.gui.visualize.plugins.GraphVisualizePlugin;
+import weka.gui.visualize.plugins.TreeVisualizePlugin;
+import weka.gui.visualize.plugins.VisualizePlugin;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JViewport;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel allows the user to select and configure a classifier, set the
+ * attribute of the current dataset to be used as the class, and evaluate
+ * the classifier using a number of testing modes (test on the training data,
+ * train/test on a percentage split, n-fold cross-validation, test on a
+ * separate split). The results of classification runs are stored in a result
+ * history so that previous results are accessible.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5958 $
+ */
+public class ClassifierPanel 
+  extends JPanel
+  implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
+   
+  /** for serialization */
+  static final long serialVersionUID = 6959973704963624003L;
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+
+  /** The filename extension that should be used for model files */
+  public static String MODEL_FILE_EXTENSION = ".model";
+  
+  /** The filename extension that should be used for PMML xml files */
+  public static String PMML_FILE_EXTENSION = ".xml";
+
+  /** Lets the user configure the classifier */
+  protected GenericObjectEditor m_ClassifierEditor =
+    new GenericObjectEditor();
+
+  /** The panel showing the current classifier selection */
+  protected PropertyPanel m_CEPanel = new PropertyPanel(m_ClassifierEditor);
+  
+  /** The output area for classification results */
+  protected JTextArea m_OutText = new JTextArea(20, 40);
+
+  /** The destination for log/status messages */
+  protected Logger m_Log = new SysErrLog();
+
+  /** The buffer saving object for saving output */
+  SaveBuffer m_SaveOut = new SaveBuffer(m_Log, this);
+
+  /** A panel controlling results viewing */
+  protected ResultHistoryPanel m_History = new ResultHistoryPanel(m_OutText);
+
+  /** Lets the user select the class column */
+  protected JComboBox m_ClassCombo = new JComboBox();
+
+  /** Click to set test mode to cross-validation */
+  protected JRadioButton m_CVBut = new JRadioButton("Cross-validation");
+
+  /** Click to set test mode to generate a % split */
+  protected JRadioButton m_PercentBut = new JRadioButton("Percentage split");
+
+  /** Click to set test mode to test on training data */
+  protected JRadioButton m_TrainBut = new JRadioButton("Use training set");
+
+  /** Click to set test mode to a user-specified test set */
+  protected JRadioButton m_TestSplitBut =
+    new JRadioButton("Supplied test set");
+
+  /** Check to save the predictions in the results list for visualizing
+      later on */
+  protected JCheckBox m_StorePredictionsBut = 
+    new JCheckBox("Store predictions for visualization");
+
+  /** Check to output the model built from the training data */
+  protected JCheckBox m_OutputModelBut = new JCheckBox("Output model");
+
+  /** Check to output true/false positives, precision/recall for each class */
+  protected JCheckBox m_OutputPerClassBut =
+    new JCheckBox("Output per-class stats");
+
+  /** Check to output a confusion matrix */
+  protected JCheckBox m_OutputConfusionBut =
+    new JCheckBox("Output confusion matrix");
+
+  /** Check to output entropy statistics */
+  protected JCheckBox m_OutputEntropyBut =
+    new JCheckBox("Output entropy evaluation measures");
+
+  /** Lets the user configure the ClassificationOutput. */
+  protected GenericObjectEditor m_ClassificationOutputEditor = new GenericObjectEditor(true);
+
+  /** ClassificationOutput configuration. */
+  protected PropertyPanel m_ClassificationOutputPanel = new PropertyPanel(m_ClassificationOutputEditor);
+  
+  /** the range of attributes to output */
+  protected Range m_OutputAdditionalAttributesRange = null;
+  
+  /** Check to evaluate w.r.t a cost matrix */
+  protected JCheckBox m_EvalWRTCostsBut =
+    new JCheckBox("Cost-sensitive evaluation");
+
+  /** for the cost matrix */
+  protected JButton m_SetCostsBut = new JButton("Set...");
+
+  /** Label by where the cv folds are entered */
+  protected JLabel m_CVLab = new JLabel("Folds", SwingConstants.RIGHT);
+
+  /** The field where the cv folds are entered */
+  protected JTextField m_CVText = new JTextField("10", 3);
+
+  /** Label by where the % split is entered */
+  protected JLabel m_PercentLab = new JLabel("%", SwingConstants.RIGHT);
+
+  /** The field where the % split is entered */
+  protected JTextField m_PercentText = new JTextField("66", 3);
+
+  /** The button used to open a separate test dataset */
+  protected JButton m_SetTestBut = new JButton("Set...");
+
+  /** The frame used to show the test set selection panel */
+  protected JFrame m_SetTestFrame;
+
+  /** The frame used to show the cost matrix editing panel */
+  protected PropertyDialog m_SetCostsFrame;
+
+  /**
+   * Alters the enabled/disabled status of elements associated with each
+   * radio button
+   */
+  ActionListener m_RadioListener = new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      updateRadioLinks();
+    }
+  };
+
+  /** Button for further output/visualize options */
+  JButton m_MoreOptions = new JButton("More options...");
+
+  /** User specified random seed for cross validation or % split */
+  protected JTextField m_RandomSeedText = new JTextField("1", 3);
+  
+  /** the label for the random seed textfield */
+  protected JLabel m_RandomLab = new JLabel("Random seed for XVal / % Split", 
+					    SwingConstants.RIGHT);
+
+  /** Whether randomization is turned off to preserve order */
+  protected JCheckBox m_PreserveOrderBut = new JCheckBox("Preserve order for % Split");
+
+  /** Whether to output the source code (only for classifiers importing Sourcable) */
+  protected JCheckBox m_OutputSourceCode = new JCheckBox("Output source code");
+
+  /** The name of the generated class (only applicable to Sourcable schemes) */
+  protected JTextField m_SourceCodeClass = new JTextField("WekaClassifier", 10);
+  
+  /** Click to start running the classifier */
+  protected JButton m_StartBut = new JButton("Start");
+
+  /** Click to stop a running classifier */
+  protected JButton m_StopBut = new JButton("Stop");
+
+  /** Stop the class combo from taking up to much space */
+  private Dimension COMBO_SIZE = new Dimension(150, m_StartBut
+					       .getPreferredSize().height);
+
+  /** The cost matrix editor for evaluation costs */
+  protected CostMatrixEditor m_CostMatrixEditor = new CostMatrixEditor();
+
+  /** The main set of instances we're playing with */
+  protected Instances m_Instances;
+
+  /** The loader used to load the user-supplied test set (if any) */
+  protected Loader m_TestLoader;
+  
+  /** A thread that classification runs in */
+  protected Thread m_RunThread;
+
+  /** The current visualization object */
+  protected VisualizePanel m_CurrentVis = null;
+
+  /** Filter to ensure only model files are selected */  
+  protected FileFilter m_ModelFilter =
+    new ExtensionFileFilter(MODEL_FILE_EXTENSION, "Model object files");
+  
+  protected FileFilter m_PMMLModelFilter =
+    new ExtensionFileFilter(PMML_FILE_EXTENSION, "PMML model files");
+
+  /** The file chooser for selecting model files */
+  protected JFileChooser m_FileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /* Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the classifier panel
+   */
+  public ClassifierPanel() {
+
+    // Connect / configure the components
+    m_OutText.setEditable(false);
+    m_OutText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_OutText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_OutText.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	if ((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	    != InputEvent.BUTTON1_MASK) {
+	  m_OutText.selectAll();
+	}
+      }
+    });
+    m_History.setBorder(BorderFactory.createTitledBorder("Result list (right-click for options)"));
+    m_ClassifierEditor.setClassType(Classifier.class);
+    m_ClassifierEditor.setValue(ExplorerDefaults.getClassifier());
+    m_ClassifierEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        m_StartBut.setEnabled(true);
+        // Check capabilities
+        Capabilities currentFilter = m_ClassifierEditor.getCapabilitiesFilter();
+        Classifier classifier = (Classifier) m_ClassifierEditor.getValue();
+        Capabilities currentSchemeCapabilities =  null;
+        if (classifier != null && currentFilter != null && 
+            (classifier instanceof CapabilitiesHandler)) {
+          currentSchemeCapabilities = ((CapabilitiesHandler)classifier).getCapabilities();
+          
+          if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+              !currentSchemeCapabilities.supports(currentFilter)) {
+            m_StartBut.setEnabled(false);
+          }
+        }
+	repaint();
+      }
+    });
+
+    m_ClassCombo.setToolTipText("Select the attribute to use as the class");
+    m_TrainBut.setToolTipText("Test on the same set that the classifier"
+			      + " is trained on");
+    m_CVBut.setToolTipText("Perform a n-fold cross-validation");
+    m_PercentBut.setToolTipText("Train on a percentage of the data and"
+				+ " test on the remainder");
+    m_TestSplitBut.setToolTipText("Test on a user-specified dataset");
+    m_StartBut.setToolTipText("Starts the classification");
+    m_StopBut.setToolTipText("Stops a running classification");
+    m_StorePredictionsBut.
+      setToolTipText("Store predictions in the result list for later "
+		     +"visualization");
+    m_OutputModelBut
+      .setToolTipText("Output the model obtained from the full training set");
+    m_OutputPerClassBut.setToolTipText("Output precision/recall & true/false"
+				    + " positives for each class");
+    m_OutputConfusionBut
+      .setToolTipText("Output the matrix displaying class confusions");
+    m_OutputEntropyBut
+      .setToolTipText("Output entropy-based evaluation measures");
+    m_EvalWRTCostsBut
+      .setToolTipText("Evaluate errors with respect to a cost matrix");
+    m_RandomLab.setToolTipText("The seed value for randomization");
+    m_RandomSeedText.setToolTipText(m_RandomLab.getToolTipText());
+    m_PreserveOrderBut.setToolTipText("Preserves the order in a percentage split");
+    m_OutputSourceCode.setToolTipText(
+      "Whether to output the built classifier as Java source code");
+    m_SourceCodeClass.setToolTipText("The classname of the built classifier");
+
+    m_FileChooser.addChoosableFileFilter(m_PMMLModelFilter);
+    m_FileChooser.setFileFilter(m_ModelFilter);
+    
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    m_ClassificationOutputEditor.setClassType(AbstractOutput.class);
+    m_ClassificationOutputEditor.setValue(new Null());
+    
+    m_StorePredictionsBut.setSelected(ExplorerDefaults.getClassifierStorePredictionsForVis());
+    m_OutputModelBut.setSelected(ExplorerDefaults.getClassifierOutputModel());
+    m_OutputPerClassBut.setSelected(ExplorerDefaults.getClassifierOutputPerClassStats());
+    m_OutputConfusionBut.setSelected(ExplorerDefaults.getClassifierOutputConfusionMatrix());
+    m_EvalWRTCostsBut.setSelected(ExplorerDefaults.getClassifierCostSensitiveEval());
+    m_OutputEntropyBut.setSelected(ExplorerDefaults.getClassifierOutputEntropyEvalMeasures());
+    m_RandomSeedText.setText("" + ExplorerDefaults.getClassifierRandomSeed());
+    m_PreserveOrderBut.setSelected(ExplorerDefaults.getClassifierPreserveOrder());
+    m_OutputSourceCode.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        m_SourceCodeClass.setEnabled(m_OutputSourceCode.isSelected());
+      }
+    });
+    m_OutputSourceCode.setSelected(ExplorerDefaults.getClassifierOutputSourceCode());
+    m_SourceCodeClass.setText(ExplorerDefaults.getClassifierSourceCodeClass());
+    m_SourceCodeClass.setEnabled(m_OutputSourceCode.isSelected());
+    m_ClassCombo.setEnabled(false);
+    m_ClassCombo.setPreferredSize(COMBO_SIZE);
+    m_ClassCombo.setMaximumSize(COMBO_SIZE);
+    m_ClassCombo.setMinimumSize(COMBO_SIZE);
+
+    m_CVBut.setSelected(true);
+    // see "testMode" variable in startClassifier
+    m_CVBut.setSelected(ExplorerDefaults.getClassifierTestMode() == 1);
+    m_PercentBut.setSelected(ExplorerDefaults.getClassifierTestMode() == 2);
+    m_TrainBut.setSelected(ExplorerDefaults.getClassifierTestMode() == 3);
+    m_TestSplitBut.setSelected(ExplorerDefaults.getClassifierTestMode() == 4);
+    m_PercentText.setText("" + ExplorerDefaults.getClassifierPercentageSplit());
+    m_CVText.setText("" + ExplorerDefaults.getClassifierCrossvalidationFolds());
+    updateRadioLinks();
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_TrainBut);
+    bg.add(m_CVBut);
+    bg.add(m_PercentBut);
+    bg.add(m_TestSplitBut);
+    m_TrainBut.addActionListener(m_RadioListener);
+    m_CVBut.addActionListener(m_RadioListener);
+    m_PercentBut.addActionListener(m_RadioListener);
+    m_TestSplitBut.addActionListener(m_RadioListener);
+    m_SetTestBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setTestSet();
+      }
+    });
+    m_EvalWRTCostsBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_SetCostsBut.setEnabled(m_EvalWRTCostsBut.isSelected());
+	if ((m_SetCostsFrame != null) 
+	    && (!m_EvalWRTCostsBut.isSelected())) {
+	  m_SetCostsFrame.setVisible(false);
+	}
+      }
+    });
+    m_CostMatrixEditor.setValue(new CostMatrix(1));
+    m_SetCostsBut.setEnabled(m_EvalWRTCostsBut.isSelected());
+    m_SetCostsBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_SetCostsBut.setEnabled(false);
+	if (m_SetCostsFrame == null) {
+	  if (PropertyDialog.getParentDialog(ClassifierPanel.this) != null)
+	    m_SetCostsFrame = new PropertyDialog(
+		PropertyDialog.getParentDialog(ClassifierPanel.this), 
+		m_CostMatrixEditor, 100, 100);
+	  else
+	    m_SetCostsFrame = new PropertyDialog(
+		PropertyDialog.getParentFrame(ClassifierPanel.this), 
+		m_CostMatrixEditor, 100, 100);
+	  m_SetCostsFrame.setTitle("Cost Matrix Editor");
+	  //	pd.setSize(250,150);
+	  m_SetCostsFrame.addWindowListener(new java.awt.event.WindowAdapter() {
+	    public void windowClosing(java.awt.event.WindowEvent p) {
+	      m_SetCostsBut.setEnabled(m_EvalWRTCostsBut.isSelected());
+	      if ((m_SetCostsFrame != null) 
+		  && (!m_EvalWRTCostsBut.isSelected())) {
+		m_SetCostsFrame.setVisible(false);
+	      }
+	    }
+	  });
+	  m_SetCostsFrame.setVisible(true);
+	}
+	
+	// do we need to change the size of the matrix?
+	int classIndex = m_ClassCombo.getSelectedIndex();
+	int numClasses = m_Instances.attribute(classIndex).numValues();
+	if (numClasses != ((CostMatrix) m_CostMatrixEditor.getValue()).numColumns())
+	  m_CostMatrixEditor.setValue(new CostMatrix(numClasses));
+	
+	m_SetCostsFrame.setVisible(true);
+      }
+    });
+
+    m_StartBut.setEnabled(false);
+    m_StopBut.setEnabled(false);
+    m_StartBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	startClassifier();
+      }
+    });
+    m_StopBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	stopClassifier();
+      }
+    });
+   
+    m_ClassCombo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	int selected = m_ClassCombo.getSelectedIndex();
+	if (selected != -1) {
+	  boolean isNominal = m_Instances.attribute(selected).isNominal();
+	  m_OutputPerClassBut.setEnabled(isNominal);
+	  m_OutputConfusionBut.setEnabled(isNominal);	
+	}
+	updateCapabilitiesFilter(m_ClassifierEditor.getCapabilitiesFilter());
+      }
+    });
+
+    m_History.setHandleRightClicks(false);
+    // see if we can popup a menu for the selected result
+    m_History.getList().addMouseListener(new MouseAdapter() {
+	public void mouseClicked(MouseEvent e) {
+	  if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	       != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+	    int index = m_History.getList().locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = m_History.getNameAtIndex(index);
+	      visualize(name, e.getX(), e.getY());
+	    } else {
+	      visualize(null, e.getX(), e.getY());
+	    }
+	  }
+	}
+      });
+
+    m_MoreOptions.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	m_MoreOptions.setEnabled(false);
+	JPanel moreOptionsPanel = new JPanel();
+	moreOptionsPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
+	moreOptionsPanel.setLayout(new GridLayout(10, 1));
+	moreOptionsPanel.add(m_OutputModelBut);
+	moreOptionsPanel.add(m_OutputPerClassBut);	  
+	moreOptionsPanel.add(m_OutputEntropyBut);	  
+	moreOptionsPanel.add(m_OutputConfusionBut);	  
+	moreOptionsPanel.add(m_StorePredictionsBut);
+	JPanel classOutPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+	classOutPanel.add(new JLabel("Output predictions"));
+	classOutPanel.add(m_ClassificationOutputPanel);
+	moreOptionsPanel.add(classOutPanel);
+	JPanel costMatrixOption = new JPanel(new FlowLayout(FlowLayout.LEFT));
+	costMatrixOption.add(m_EvalWRTCostsBut);
+	costMatrixOption.add(m_SetCostsBut);
+	moreOptionsPanel.add(costMatrixOption);
+	JPanel seedPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+	seedPanel.add(m_RandomLab);
+	seedPanel.add(m_RandomSeedText);
+	moreOptionsPanel.add(seedPanel);
+	moreOptionsPanel.add(m_PreserveOrderBut);
+        JPanel sourcePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        m_OutputSourceCode.setEnabled(m_ClassifierEditor.getValue() instanceof Sourcable);
+        m_SourceCodeClass.setEnabled(m_OutputSourceCode.isEnabled() && m_OutputSourceCode.isSelected());
+        sourcePanel.add(m_OutputSourceCode);
+        sourcePanel.add(m_SourceCodeClass);
+        moreOptionsPanel.add(sourcePanel);
+
+	JPanel all = new JPanel();
+	all.setLayout(new BorderLayout());	
+
+	JButton oK = new JButton("OK");
+	JPanel okP = new JPanel();
+	okP.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+	okP.setLayout(new GridLayout(1,1,5,5));
+	okP.add(oK);
+
+	all.add(moreOptionsPanel, BorderLayout.CENTER);
+	all.add(okP, BorderLayout.SOUTH);
+	
+	final JDialog jd = 
+	  new JDialog(PropertyDialog.getParentFrame(ClassifierPanel.this), "Classifier evaluation options");
+	jd.getContentPane().setLayout(new BorderLayout());
+	jd.getContentPane().add(all, BorderLayout.CENTER);
+	jd.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent w) {
+	    jd.dispose();
+	    m_MoreOptions.setEnabled(true);
+	  }
+	});
+	oK.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent a) {
+	    m_MoreOptions.setEnabled(true);
+	    jd.dispose();
+	  }
+	});
+	jd.pack();
+	
+	// panel height is only available now
+	m_ClassificationOutputPanel.setPreferredSize(new Dimension(300, m_ClassificationOutputPanel.getHeight()));
+	jd.pack();
+	
+	jd.setLocation(m_MoreOptions.getLocationOnScreen());
+	jd.setVisible(true);
+      }
+    });
+
+    // Layout the GUI
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Classifier"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    p1.setLayout(new BorderLayout());
+    p1.add(m_CEPanel, BorderLayout.NORTH);
+
+    JPanel p2 = new JPanel();
+    GridBagLayout gbL = new GridBagLayout();
+    p2.setLayout(gbL);
+    p2.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Test options"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    GridBagConstraints gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(m_TrainBut, gbC);
+    p2.add(m_TrainBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(m_TestSplitBut, gbC);
+    p2.add(m_TestSplitBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 1;    gbC.gridwidth = 2;
+    gbC.insets = new Insets(2, 10, 2, 0);
+    gbL.setConstraints(m_SetTestBut, gbC);
+    p2.add(m_SetTestBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 2;     gbC.gridx = 0;
+    gbL.setConstraints(m_CVBut, gbC);
+    p2.add(m_CVBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 2;     gbC.gridx = 1;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_CVLab, gbC);
+    p2.add(m_CVLab);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 2;     gbC.gridx = 2;  gbC.weightx = 100;
+    gbC.ipadx = 20;
+    gbL.setConstraints(m_CVText, gbC);
+    p2.add(m_CVText);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 3;     gbC.gridx = 0;
+    gbL.setConstraints(m_PercentBut, gbC);
+    p2.add(m_PercentBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 3;     gbC.gridx = 1;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_PercentLab, gbC);
+    p2.add(m_PercentLab);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 3;     gbC.gridx = 2;  gbC.weightx = 100;
+    gbC.ipadx = 20;
+    gbL.setConstraints(m_PercentText, gbC);
+    p2.add(m_PercentText);
+
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 4;     gbC.gridx = 0;  gbC.weightx = 100;
+    gbC.gridwidth = 3;
+
+    gbC.insets = new Insets(3, 0, 1, 0);
+    gbL.setConstraints(m_MoreOptions, gbC);
+    p2.add(m_MoreOptions);
+
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new GridLayout(2, 2));
+    buttons.add(m_ClassCombo);
+    m_ClassCombo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    JPanel ssButs = new JPanel();
+    ssButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ssButs.setLayout(new GridLayout(1, 2, 5, 5));
+    ssButs.add(m_StartBut);
+    ssButs.add(m_StopBut);
+
+    buttons.add(ssButs);
+    
+    JPanel p3 = new JPanel();
+    p3.setBorder(BorderFactory.createTitledBorder("Classifier output"));
+    p3.setLayout(new BorderLayout());
+    final JScrollPane js = new JScrollPane(m_OutText);
+    p3.add(js, BorderLayout.CENTER);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });
+    
+    JPanel mondo = new JPanel();
+    gbL = new GridBagLayout();
+    mondo.setLayout(gbL);
+    gbC = new GridBagConstraints();
+    //    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(p2, gbC);
+    mondo.add(p2);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(buttons, gbC);
+    mondo.add(buttons);
+    gbC = new GridBagConstraints();
+    //gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 2;     gbC.gridx = 0; gbC.weightx = 0;
+    gbL.setConstraints(m_History, gbC);
+    mondo.add(m_History);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.gridheight = 3;
+    gbC.weightx = 100; gbC.weighty = 100;
+    gbL.setConstraints(p3, gbC);
+    mondo.add(p3);
+
+    setLayout(new BorderLayout());
+    add(p1, BorderLayout.NORTH);
+    add(mondo, BorderLayout.CENTER);
+  }
+
+  
+  /**
+   * Updates the enabled status of the input fields and labels.
+   */
+  protected void updateRadioLinks() {
+    
+    m_SetTestBut.setEnabled(m_TestSplitBut.isSelected());
+    if ((m_SetTestFrame != null) && (!m_TestSplitBut.isSelected())) {
+      m_SetTestFrame.setVisible(false);
+    }
+    m_CVText.setEnabled(m_CVBut.isSelected());
+    m_CVLab.setEnabled(m_CVBut.isSelected());
+    m_PercentText.setEnabled(m_PercentBut.isSelected());
+    m_PercentLab.setEnabled(m_PercentBut.isSelected());
+  }
+
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+
+    m_Log = newLog;
+  }
+
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    m_Instances = inst;
+
+    String [] attribNames = new String [m_Instances.numAttributes()];
+    for (int i = 0; i < attribNames.length; i++) {
+      String type = "";
+      switch (m_Instances.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = "(Nom) ";
+	break;
+      case Attribute.NUMERIC:
+	type = "(Num) ";
+	break;
+      case Attribute.STRING:
+	type = "(Str) ";
+	break;
+      case Attribute.DATE:
+	type = "(Dat) ";
+	break;
+      case Attribute.RELATIONAL:
+	type = "(Rel) ";
+	break;
+      default:
+	type = "(???) ";
+      }
+      attribNames[i] = type + m_Instances.attribute(i).name();
+    }
+    m_ClassCombo.setModel(new DefaultComboBoxModel(attribNames));
+    if (attribNames.length > 0) {
+      if (inst.classIndex() == -1)
+	m_ClassCombo.setSelectedIndex(attribNames.length - 1);
+      else
+	m_ClassCombo.setSelectedIndex(inst.classIndex());
+      m_ClassCombo.setEnabled(true);
+      m_StartBut.setEnabled(m_RunThread == null);
+      m_StopBut.setEnabled(m_RunThread != null);
+    } else {
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(false);
+    }
+  }
+
+  /**
+   * Sets the user test set. Information about the current test set
+   * is displayed in an InstanceSummaryPanel and the user is given the
+   * ability to load another set from a file or url.
+   *
+   */
+  protected void setTestSet() {
+
+    if (m_SetTestFrame == null) {
+      final SetInstancesPanel sp = new SetInstancesPanel(true);
+
+      if (m_TestLoader != null) {
+        try {
+          if (m_TestLoader.getStructure() != null) {
+            sp.setInstances(m_TestLoader.getStructure());
+          }
+        } catch (Exception ex) {
+          ex.printStackTrace();
+        }
+      }
+      sp.addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+	  m_TestLoader = sp.getLoader();
+	}
+      });
+      // Add propertychangelistener to update m_TestLoader whenever
+      // it changes in the settestframe
+      m_SetTestFrame = new JFrame("Test Instances");
+      sp.setParentFrame(m_SetTestFrame);   // enable Close-Button
+      m_SetTestFrame.getContentPane().setLayout(new BorderLayout());
+      m_SetTestFrame.getContentPane().add(sp, BorderLayout.CENTER);
+      m_SetTestFrame.pack();
+    }
+    m_SetTestFrame.setVisible(true);
+  }
+
+  /**
+   * outputs the header for the predictions on the data.
+   * 
+   * @param outBuff			the buffer to add the output to
+   * @param classificationOutput	for generating the classification output
+   * @param title			the title to print
+   */
+  protected void printPredictionsHeader(StringBuffer outBuff, AbstractOutput classificationOutput, String title) {
+    if (classificationOutput.generatesOutput())
+      outBuff.append("=== Predictions on " + title + " ===\n\n");
+    classificationOutput.printHeader();
+  }
+  
+  /**
+   * Starts running the currently configured classifier with the current
+   * settings. This is run in a separate thread, and will only start if
+   * there is no classifier already running. The classifier output is sent
+   * to the results history panel.
+   */
+  protected void startClassifier() {
+
+    if (m_RunThread == null) {
+      synchronized (this) {
+	m_StartBut.setEnabled(false);
+	m_StopBut.setEnabled(true);
+      }
+      m_RunThread = new Thread() {
+	public void run() {
+	  // Copy the current state of things
+	  m_Log.statusMessage("Setting up...");
+	  CostMatrix costMatrix = null;
+	  Instances inst = new Instances(m_Instances);
+	  DataSource source = null;
+          Instances userTestStructure = null;
+	  ClassifierErrorsPlotInstances plotInstances = null;
+	  
+	  // for timing
+	  long trainTimeStart = 0, trainTimeElapsed = 0;
+
+          try {
+            if (m_TestLoader != null && m_TestLoader.getStructure() != null) {
+              m_TestLoader.reset();
+              source = new DataSource(m_TestLoader);
+              userTestStructure = source.getStructure();
+            }
+          } catch (Exception ex) {
+            ex.printStackTrace();
+          }
+	  if (m_EvalWRTCostsBut.isSelected()) {
+	    costMatrix = new CostMatrix((CostMatrix) m_CostMatrixEditor
+					.getValue());
+	  }
+	  boolean outputModel = m_OutputModelBut.isSelected();
+	  boolean outputConfusion = m_OutputConfusionBut.isSelected();
+	  boolean outputPerClass = m_OutputPerClassBut.isSelected();
+	  boolean outputSummary = true;
+          boolean outputEntropy = m_OutputEntropyBut.isSelected();
+	  boolean saveVis = m_StorePredictionsBut.isSelected();
+	  boolean outputPredictionsText = (m_ClassificationOutputEditor.getValue().getClass() != Null.class);
+
+	  String grph = null;
+
+	  int testMode = 0;
+	  int numFolds = 10;
+          double percent = 66;
+	  int classIndex = m_ClassCombo.getSelectedIndex();
+	  Classifier classifier = (Classifier) m_ClassifierEditor.getValue();
+	  Classifier template = null;
+	  try {
+	    template = AbstractClassifier.makeCopy(classifier);
+	  } catch (Exception ex) {
+	    m_Log.logMessage("Problem copying classifier: " + ex.getMessage());
+	  }
+	  Classifier fullClassifier = null;
+	  StringBuffer outBuff = new StringBuffer();
+	  AbstractOutput classificationOutput = null; 
+	  if (outputPredictionsText) {
+	    classificationOutput = (AbstractOutput) m_ClassificationOutputEditor.getValue();
+	    Instances header = new Instances(inst, 0);
+	    header.setClassIndex(classIndex);
+	    classificationOutput.setHeader(header);
+	    classificationOutput.setBuffer(outBuff);
+	  }
+	  String name = (new SimpleDateFormat("HH:mm:ss - "))
+	  .format(new Date());
+	  String cname = classifier.getClass().getName();
+	  if (cname.startsWith("weka.classifiers.")) {
+	    name += cname.substring("weka.classifiers.".length());
+	  } else {
+	    name += cname;
+	  }
+          String cmd = m_ClassifierEditor.getValue().getClass().getName();
+          if (m_ClassifierEditor.getValue() instanceof OptionHandler)
+            cmd += " " + Utils.joinOptions(((OptionHandler) m_ClassifierEditor.getValue()).getOptions());
+	  Evaluation eval = null;
+	  try {
+	    if (m_CVBut.isSelected()) {
+	      testMode = 1;
+	      numFolds = Integer.parseInt(m_CVText.getText());
+	      if (numFolds <= 1) {
+		throw new Exception("Number of folds must be greater than 1");
+	      }
+	    } else if (m_PercentBut.isSelected()) {
+	      testMode = 2;
+	      percent = Double.parseDouble(m_PercentText.getText());
+	      if ((percent <= 0) || (percent >= 100)) {
+		throw new Exception("Percentage must be between 0 and 100");
+	      }
+	    } else if (m_TrainBut.isSelected()) {
+	      testMode = 3;
+	    } else if (m_TestSplitBut.isSelected()) {
+	      testMode = 4;
+	      // Check the test instance compatibility
+	      if (source == null) {
+		throw new Exception("No user test set has been specified");
+	      }
+	      if (!inst.equalHeaders(userTestStructure)) {
+		throw new Exception("Train and test set are not compatible\n" + inst.equalHeadersMsg(userTestStructure));
+	      }
+              userTestStructure.setClassIndex(classIndex);
+	    } else {
+	      throw new Exception("Unknown test mode");
+	    }
+	    inst.setClassIndex(classIndex);
+
+	    // set up the structure of the plottable instances for 
+	    // visualization
+	    plotInstances = ExplorerDefaults.getClassifierErrorsPlotInstances();
+	    plotInstances.setInstances(inst);
+	    plotInstances.setClassifier(classifier);
+	    plotInstances.setClassIndex(inst.classIndex());
+	    plotInstances.setSaveForVisualization(saveVis);
+
+	    // Output some header information
+	    m_Log.logMessage("Started " + cname);
+	    m_Log.logMessage("Command: " + cmd);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskStarted();
+	    }
+	    outBuff.append("=== Run information ===\n\n");
+	    outBuff.append("Scheme:       " + cname);
+	    if (classifier instanceof OptionHandler) {
+	      String [] o = ((OptionHandler) classifier).getOptions();
+	      outBuff.append(" " + Utils.joinOptions(o));
+	    }
+	    outBuff.append("\n");
+	    outBuff.append("Relation:     " + inst.relationName() + '\n');
+	    outBuff.append("Instances:    " + inst.numInstances() + '\n');
+	    outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
+	    if (inst.numAttributes() < 100) {
+	      for (int i = 0; i < inst.numAttributes(); i++) {
+		outBuff.append("              " + inst.attribute(i).name()
+			       + '\n');
+	      }
+	    } else {
+	      outBuff.append("              [list of attributes omitted]\n");
+	    }
+
+	    outBuff.append("Test mode:    ");
+	    switch (testMode) {
+	      case 3: // Test on training
+		outBuff.append("evaluate on training data\n");
+		break;
+	      case 1: // CV mode
+		outBuff.append("" + numFolds + "-fold cross-validation\n");
+		break;
+	      case 2: // Percent split
+		outBuff.append("split " + percent
+		    + "% train, remainder test\n");
+		break;
+	      case 4: // Test on user split
+		if (source.isIncremental())
+		  outBuff.append("user supplied test set: "
+		      + " size unknown (reading incrementally)\n");
+		else
+		  outBuff.append("user supplied test set: "
+		      + source.getDataSet().numInstances() + " instances\n");
+		break;
+	    }
+            if (costMatrix != null) {
+               outBuff.append("Evaluation cost matrix:\n")
+               .append(costMatrix.toString()).append("\n");
+            }
+	    outBuff.append("\n");
+	    m_History.addResult(name, outBuff);
+	    m_History.setSingle(name);
+	    
+	    // Build the model and output it.
+	    if (outputModel || (testMode == 3) || (testMode == 4)) {
+	      m_Log.statusMessage("Building model on training data...");
+
+	      trainTimeStart = System.currentTimeMillis();
+	      classifier.buildClassifier(inst);
+	      trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
+	    }
+
+	    if (outputModel) {
+	      outBuff.append("=== Classifier model (full training set) ===\n\n");
+	      outBuff.append(classifier.toString() + "\n");
+	      outBuff.append("\nTime taken to build model: " +
+			     Utils.doubleToString(trainTimeElapsed / 1000.0,2)
+			     + " seconds\n\n");
+	      m_History.updateResult(name);
+	      if (classifier instanceof Drawable) {
+		grph = null;
+		try {
+		  grph = ((Drawable)classifier).graph();
+		} catch (Exception ex) {
+		}
+	      }
+	      // copy full model for output
+	      SerializedObject so = new SerializedObject(classifier);
+	      fullClassifier = (Classifier) so.getObject();
+	    }
+	    
+	    switch (testMode) {
+	      case 3: // Test on training
+	      m_Log.statusMessage("Evaluating on training data...");
+	      eval = new Evaluation(inst, costMatrix);
+	      plotInstances.setEvaluation(eval);
+              plotInstances.setUp();
+	      
+	      if (outputPredictionsText) {
+		printPredictionsHeader(outBuff, classificationOutput, "training set");
+	      }
+
+	      for (int jj=0;jj<inst.numInstances();jj++) {
+		plotInstances.process(inst.instance(jj), classifier, eval);
+		
+		if (outputPredictionsText) {
+		  classificationOutput.printClassification(classifier, inst.instance(jj), jj);
+		}
+		if ((jj % 100) == 0) {
+		  m_Log.statusMessage("Evaluating on training data. Processed "
+				      +jj+" instances...");
+		}
+	      }
+	      if (outputPredictionsText)
+		classificationOutput.printFooter();
+	      if (outputPredictionsText && classificationOutput.generatesOutput()) {
+		outBuff.append("\n");
+	      } 
+	      outBuff.append("=== Evaluation on training set ===\n");
+	      break;
+
+	      case 1: // CV mode
+	      m_Log.statusMessage("Randomizing instances...");
+	      int rnd = 1;
+	      try {
+		rnd = Integer.parseInt(m_RandomSeedText.getText().trim());
+		// System.err.println("Using random seed "+rnd);
+	      } catch (Exception ex) {
+		m_Log.logMessage("Trouble parsing random seed value");
+		rnd = 1;
+	      }
+	      Random random = new Random(rnd);
+	      inst.randomize(random);
+	      if (inst.attribute(classIndex).isNominal()) {
+		m_Log.statusMessage("Stratifying instances...");
+		inst.stratify(numFolds);
+	      }
+	      eval = new Evaluation(inst, costMatrix);
+	      plotInstances.setEvaluation(eval);
+              plotInstances.setUp();
+      
+	      if (outputPredictionsText) {
+		printPredictionsHeader(outBuff, classificationOutput, "test data");
+	      }
+
+	      // Make some splits and do a CV
+	      for (int fold = 0; fold < numFolds; fold++) {
+		m_Log.statusMessage("Creating splits for fold "
+				    + (fold + 1) + "...");
+		Instances train = inst.trainCV(numFolds, fold, random);
+		eval.setPriors(train);
+		m_Log.statusMessage("Building model for fold "
+				    + (fold + 1) + "...");
+		Classifier current = null;
+		try {
+		  current = AbstractClassifier.makeCopy(template);
+		} catch (Exception ex) {
+		  m_Log.logMessage("Problem copying classifier: " + ex.getMessage());
+		}
+		current.buildClassifier(train);
+		Instances test = inst.testCV(numFolds, fold);
+		m_Log.statusMessage("Evaluating model for fold "
+				    + (fold + 1) + "...");
+		for (int jj=0;jj<test.numInstances();jj++) {
+		  plotInstances.process(test.instance(jj), current, eval);
+		  if (outputPredictionsText) {
+		    classificationOutput.printClassification(current, test.instance(jj), jj);
+		  }
+		}
+	      }
+	      if (outputPredictionsText)
+		classificationOutput.printFooter();
+	      if (outputPredictionsText) {
+		outBuff.append("\n");
+	      } 
+	      if (inst.attribute(classIndex).isNominal()) {
+		outBuff.append("=== Stratified cross-validation ===\n");
+	      } else {
+		outBuff.append("=== Cross-validation ===\n");
+	      }
+	      break;
+		
+	      case 2: // Percent split
+	      if (!m_PreserveOrderBut.isSelected()) {
+		m_Log.statusMessage("Randomizing instances...");
+		try {
+		  rnd = Integer.parseInt(m_RandomSeedText.getText().trim());
+		} catch (Exception ex) {
+		  m_Log.logMessage("Trouble parsing random seed value");
+		  rnd = 1;
+		}
+		inst.randomize(new Random(rnd));
+	      }
+	      int trainSize = (int) Math.round(inst.numInstances() * percent / 100);
+	      int testSize = inst.numInstances() - trainSize;
+	      Instances train = new Instances(inst, 0, trainSize);
+	      Instances test = new Instances(inst, trainSize, testSize);
+	      m_Log.statusMessage("Building model on training split ("+trainSize+" instances)...");
+	      Classifier current = null;
+	      try {
+		current = AbstractClassifier.makeCopy(template);
+	      } catch (Exception ex) {
+		m_Log.logMessage("Problem copying classifier: " + ex.getMessage());
+	      }
+	      current.buildClassifier(train);
+	      eval = new Evaluation(train, costMatrix);
+	      plotInstances.setEvaluation(eval);
+              plotInstances.setUp();
+	      m_Log.statusMessage("Evaluating on test split...");
+	     
+	      if (outputPredictionsText) {
+		printPredictionsHeader(outBuff, classificationOutput, "test split");
+	      }
+     
+	      for (int jj=0;jj<test.numInstances();jj++) {
+		plotInstances.process(test.instance(jj), current, eval);
+		if (outputPredictionsText) { 
+		  classificationOutput.printClassification(current, test.instance(jj), jj);
+		}
+		if ((jj % 100) == 0) {
+		  m_Log.statusMessage("Evaluating on test split. Processed "
+				      +jj+" instances...");
+		}
+	      }
+	      if (outputPredictionsText)
+		classificationOutput.printFooter();
+	      if (outputPredictionsText) {
+		outBuff.append("\n");
+	      } 
+	      outBuff.append("=== Evaluation on test split ===\n");
+	      break;
+		
+	      case 4: // Test on user split
+	      m_Log.statusMessage("Evaluating on test data...");
+	      eval = new Evaluation(inst, costMatrix);
+	      plotInstances.setEvaluation(eval);
+              plotInstances.setUp();
+	      
+	      if (outputPredictionsText) {
+		printPredictionsHeader(outBuff, classificationOutput, "test set");
+	      }
+
+	      Instance instance;
+	      int jj = 0;
+	      while (source.hasMoreElements(userTestStructure)) {
+		instance = source.nextElement(userTestStructure);
+		plotInstances.process(instance, classifier, eval);
+		if (outputPredictionsText) {
+		  classificationOutput.printClassification(classifier, instance, jj);
+		}
+		if ((++jj % 100) == 0) {
+		  m_Log.statusMessage("Evaluating on test data. Processed "
+		      +jj+" instances...");
+		}
+	      }
+
+	      if (outputPredictionsText)
+		classificationOutput.printFooter();
+	      if (outputPredictionsText) {
+		outBuff.append("\n");
+	      } 
+	      outBuff.append("=== Evaluation on test set ===\n");
+	      break;
+
+	      default:
+	      throw new Exception("Test mode not implemented");
+	    }
+	    
+	    if (outputSummary) {
+	      outBuff.append(eval.toSummaryString(outputEntropy) + "\n");
+	    }
+
+	    if (inst.attribute(classIndex).isNominal()) {
+
+	      if (outputPerClass) {
+		outBuff.append(eval.toClassDetailsString() + "\n");
+	      }
+
+	      if (outputConfusion) {
+		outBuff.append(eval.toMatrixString() + "\n");
+	      }
+	    }
+
+            if (   (fullClassifier instanceof Sourcable) 
+                 && m_OutputSourceCode.isSelected()) {
+              outBuff.append("=== Source code ===\n\n");
+              outBuff.append(
+                Evaluation.wekaStaticWrapper(
+                    ((Sourcable) fullClassifier),
+                    m_SourceCodeClass.getText()));
+            }
+
+	    m_History.updateResult(name);
+	    m_Log.logMessage("Finished " + cname);
+	    m_Log.statusMessage("OK");
+	  } catch (Exception ex) {
+	    ex.printStackTrace();
+	    m_Log.logMessage(ex.getMessage());
+	    JOptionPane.showMessageDialog(ClassifierPanel.this,
+					  "Problem evaluating classifier:\n"
+					  + ex.getMessage(),
+					  "Evaluate classifier",
+					  JOptionPane.ERROR_MESSAGE);
+	    m_Log.statusMessage("Problem evaluating classifier");
+	  } finally {
+	    try {
+              if (!saveVis && outputModel) {
+		  FastVector vv = new FastVector();
+		  vv.addElement(fullClassifier);
+		  Instances trainHeader = new Instances(m_Instances, 0);
+		  trainHeader.setClassIndex(classIndex);
+		  vv.addElement(trainHeader);
+                  if (grph != null) {
+		    vv.addElement(grph);
+		  }
+		  m_History.addObject(name, vv);
+              } else if (saveVis && plotInstances != null && plotInstances.getPlotInstances().numInstances() > 0) {
+		m_CurrentVis = new VisualizePanel();
+		m_CurrentVis.setName(name+" ("+inst.relationName()+")");
+		m_CurrentVis.setLog(m_Log);
+		m_CurrentVis.addPlot(plotInstances.getPlotData(cname));
+		m_CurrentVis.setColourIndex(plotInstances.getPlotInstances().classIndex()+1);
+		plotInstances.cleanUp();
+	    
+                FastVector vv = new FastVector();
+                if (outputModel) {
+                  vv.addElement(fullClassifier);
+                  Instances trainHeader = new Instances(m_Instances, 0);
+                  trainHeader.setClassIndex(classIndex);
+                  vv.addElement(trainHeader);
+                  if (grph != null) {
+                    vv.addElement(grph);
+                  }
+                }
+                vv.addElement(m_CurrentVis);
+                
+                if ((eval != null) && (eval.predictions() != null)) {
+                  vv.addElement(eval.predictions());
+                  vv.addElement(inst.classAttribute());
+                }
+                m_History.addObject(name, vv);
+	      }
+	    } catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	    
+	    if (isInterrupted()) {
+	      m_Log.logMessage("Interrupted " + cname);
+	      m_Log.statusMessage("Interrupted");
+	    }
+
+	    synchronized (this) {
+	      m_StartBut.setEnabled(true);
+	      m_StopBut.setEnabled(false);
+	      m_RunThread = null;
+	    }
+	    if (m_Log instanceof TaskLogger) {
+              ((TaskLogger)m_Log).taskFinished();
+            }
+          }
+	}
+      };
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+
+  /**
+   * Handles constructing a popup menu with visualization options.
+   * @param name the name of the result history list entry clicked on by
+   * the user
+   * @param x the x coordinate for popping up the menu
+   * @param y the y coordinate for popping up the menu
+   */
+  protected void visualize(String name, int x, int y) {
+    final String selectedName = name;
+    JPopupMenu resultListMenu = new JPopupMenu();
+    
+    JMenuItem visMainBuffer = new JMenuItem("View in main window");
+    if (selectedName != null) {
+      visMainBuffer.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_History.setSingle(selectedName);
+	  }
+	});
+    } else {
+      visMainBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visMainBuffer);
+    
+    JMenuItem visSepBuffer = new JMenuItem("View in separate window");
+    if (selectedName != null) {
+      visSepBuffer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.openFrame(selectedName);
+	}
+      });
+    } else {
+      visSepBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visSepBuffer);
+    
+    JMenuItem saveOutput = new JMenuItem("Save result buffer");
+    if (selectedName != null) {
+      saveOutput.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveBuffer(selectedName);
+	  }
+	});
+    } else {
+      saveOutput.setEnabled(false);
+    }
+    resultListMenu.add(saveOutput);
+    
+    JMenuItem deleteOutput = new JMenuItem("Delete result buffer");
+    if (selectedName != null) {
+      deleteOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.removeResult(selectedName);
+	}
+      });
+    } else {
+      deleteOutput.setEnabled(false);
+    }
+    resultListMenu.add(deleteOutput);
+
+    resultListMenu.addSeparator();
+    
+    JMenuItem loadModel = new JMenuItem("Load model");
+    loadModel.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  loadClassifier();
+	}
+      });
+    resultListMenu.add(loadModel);
+
+    FastVector o = null;
+    if (selectedName != null) {
+      o = (FastVector)m_History.getNamedObject(selectedName);
+    }
+
+    VisualizePanel temp_vp = null;
+    String temp_grph = null;
+    FastVector temp_preds = null;
+    Attribute temp_classAtt = null;
+    Classifier temp_classifier = null;
+    Instances temp_trainHeader = null;
+      
+    if (o != null) { 
+      for (int i = 0; i < o.size(); i++) {
+	Object temp = o.elementAt(i);
+	if (temp instanceof Classifier) {
+	  temp_classifier = (Classifier)temp;
+	} else if (temp instanceof Instances) { // training header
+	  temp_trainHeader = (Instances)temp;
+	} else if (temp instanceof VisualizePanel) { // normal errors
+	  temp_vp = (VisualizePanel)temp;
+	} else if (temp instanceof String) { // graphable output
+	  temp_grph = (String)temp;
+	} else if (temp instanceof FastVector) { // predictions
+	  temp_preds = (FastVector)temp;
+	} else if (temp instanceof Attribute) { // class attribute
+	  temp_classAtt = (Attribute)temp;
+	}
+      }
+    }
+
+    final VisualizePanel vp = temp_vp;
+    final String grph = temp_grph;
+    final FastVector preds = temp_preds;
+    final Attribute classAtt = temp_classAtt;
+    final Classifier classifier = temp_classifier;
+    final Instances trainHeader = temp_trainHeader;
+    
+    JMenuItem saveModel = new JMenuItem("Save model");
+    if (classifier != null) {
+      saveModel.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveClassifier(selectedName, classifier, trainHeader);
+	  }
+	});
+    } else {
+      saveModel.setEnabled(false);
+    }
+    resultListMenu.add(saveModel);
+
+    JMenuItem reEvaluate =
+      new JMenuItem("Re-evaluate model on current test set");
+    if (classifier != null && m_TestLoader != null) {
+      reEvaluate.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    reevaluateModel(selectedName, classifier, trainHeader);
+	  }
+	});
+    } else {
+      reEvaluate.setEnabled(false);
+    }
+    resultListMenu.add(reEvaluate);
+    
+    resultListMenu.addSeparator();
+    
+    JMenuItem visErrors = new JMenuItem("Visualize classifier errors");
+    if (vp != null) {
+      if ((vp.getXIndex() == 0) && (vp.getYIndex() == 1)) {
+	try {
+	  vp.setXIndex(vp.getInstances().classIndex());  // class
+	  vp.setYIndex(vp.getInstances().classIndex() - 1);  // predicted class
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+      }
+      visErrors.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    visualizeClassifierErrors(vp);
+	  }
+	});
+    } else {
+      visErrors.setEnabled(false);
+    }
+    resultListMenu.add(visErrors);
+
+    JMenuItem visGrph = new JMenuItem("Visualize tree");
+    if (grph != null) {
+	if(((Drawable)temp_classifier).graphType()==Drawable.TREE) {
+	    visGrph.addActionListener(new ActionListener() {
+		    public void actionPerformed(ActionEvent e) {
+			String title;
+			if (vp != null) title = vp.getName();
+			else title = selectedName;
+			visualizeTree(grph, title);
+		    }
+		});
+	}
+	else if(((Drawable)temp_classifier).graphType()==Drawable.BayesNet) {
+	    visGrph.setText("Visualize graph");
+	    visGrph.addActionListener(new ActionListener() {
+		    public void actionPerformed(ActionEvent e) {
+			Thread th = new Thread() {
+				public void run() {
+				visualizeBayesNet(grph, selectedName);
+				}
+			    };
+			th.start();
+		    }
+		});
+	}
+	else
+	    visGrph.setEnabled(false);
+    } else {
+      visGrph.setEnabled(false);
+    }
+    resultListMenu.add(visGrph);
+
+    JMenuItem visMargin = new JMenuItem("Visualize margin curve");
+    if ((preds != null) && (classAtt != null) && (classAtt.isNominal())) {
+      visMargin.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    try {
+	      MarginCurve tc = new MarginCurve();
+	      Instances result = tc.getCurve(preds);
+	      VisualizePanel vmc = new VisualizePanel();
+	      vmc.setName(result.relationName());
+	      vmc.setLog(m_Log);
+	      PlotData2D tempd = new PlotData2D(result);
+	      tempd.setPlotName(result.relationName());
+	      tempd.addInstanceNumberAttribute();
+	      vmc.addPlot(tempd);
+	      visualizeClassifierErrors(vmc);
+	    } catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	  }
+	});
+    } else {
+      visMargin.setEnabled(false);
+    }
+    resultListMenu.add(visMargin);
+
+    JMenu visThreshold = new JMenu("Visualize threshold curve");
+    if ((preds != null) && (classAtt != null) && (classAtt.isNominal())) {
+      for (int i = 0; i < classAtt.numValues(); i++) {
+	JMenuItem clv = new JMenuItem(classAtt.value(i));
+	final int classValue = i;
+	clv.addActionListener(new ActionListener() {
+	    public void actionPerformed(ActionEvent e) {
+	      try {
+		ThresholdCurve tc = new ThresholdCurve();
+		Instances result = tc.getCurve(preds, classValue);
+		//VisualizePanel vmc = new VisualizePanel();
+		ThresholdVisualizePanel vmc = new ThresholdVisualizePanel();
+		vmc.setROCString("(Area under ROC = " + 
+				 Utils.doubleToString(ThresholdCurve.getROCArea(result), 4) + ")");
+		vmc.setLog(m_Log);
+		vmc.setName(result.relationName()+". (Class value "+
+			    classAtt.value(classValue)+")");
+		PlotData2D tempd = new PlotData2D(result);
+		tempd.setPlotName(result.relationName());
+		tempd.addInstanceNumberAttribute();
+		// specify which points are connected
+		boolean[] cp = new boolean[result.numInstances()];
+		for (int n = 1; n < cp.length; n++)
+		  cp[n] = true;
+		tempd.setConnectPoints(cp);
+		// add plot
+		vmc.addPlot(tempd);
+		visualizeClassifierErrors(vmc);
+	      } catch (Exception ex) {
+		ex.printStackTrace();
+	      }
+	      }
+	  });
+	  visThreshold.add(clv);
+      }
+    } else {
+      visThreshold.setEnabled(false);
+    }
+    resultListMenu.add(visThreshold);
+    
+    JMenu visCostBenefit = new JMenu("Cost/Benefit analysis");
+    if ((preds != null) && (classAtt != null) && (classAtt.isNominal())) {
+      for (int i = 0; i < classAtt.numValues(); i++) {
+        JMenuItem clv = new JMenuItem(classAtt.value(i));
+        final int classValue = i;
+        clv.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+              try {
+                ThresholdCurve tc = new ThresholdCurve();
+                Instances result = tc.getCurve(preds, classValue);
+
+                // Create a dummy class attribute with the chosen
+                // class value as index 0 (if necessary).
+                Attribute classAttToUse = classAtt;
+                if (classValue != 0) {
+                  FastVector newNames = new FastVector();
+                  newNames.addElement(classAtt.value(classValue));
+                  for (int k = 0; k < classAtt.numValues(); k++) {
+                    if (k != classValue) {
+                      newNames.addElement(classAtt.value(k));
+                    }
+                  }
+                  classAttToUse = new Attribute(classAtt.name(), newNames);
+                }
+                
+                CostBenefitAnalysis cbAnalysis = new CostBenefitAnalysis();
+                
+                PlotData2D tempd = new PlotData2D(result);
+                tempd.setPlotName(result.relationName());
+                tempd.m_alwaysDisplayPointsOfThisSize = 10;
+                // specify which points are connected
+                boolean[] cp = new boolean[result.numInstances()];
+                for (int n = 1; n < cp.length; n++)
+                  cp[n] = true;
+                tempd.setConnectPoints(cp);
+                
+                String windowTitle = "";
+                if (classifier != null) {
+                  String cname = classifier.getClass().getName();
+                  if (cname.startsWith("weka.classifiers.")) {
+                    windowTitle = "" + cname.substring("weka.classifiers.".length()) + " ";
+                  }
+                }
+                windowTitle += " (class = " + classAttToUse.value(0) + ")";                
+                
+                // add plot
+                cbAnalysis.setCurveData(tempd, classAttToUse);
+                visualizeCostBenefitAnalysis(cbAnalysis, windowTitle);
+              } catch (Exception ex) {
+                ex.printStackTrace();
+              }
+              }
+          });
+          visCostBenefit.add(clv);
+      }
+    } else {
+      visCostBenefit.setEnabled(false);
+    }
+    resultListMenu.add(visCostBenefit);
+
+    JMenu visCost = new JMenu("Visualize cost curve");
+    if ((preds != null) && (classAtt != null) && (classAtt.isNominal())) {
+      for (int i = 0; i < classAtt.numValues(); i++) {
+	JMenuItem clv = new JMenuItem(classAtt.value(i));
+	final int classValue = i;
+	clv.addActionListener(new ActionListener() {
+	    public void actionPerformed(ActionEvent e) {
+	      try {
+		CostCurve cc = new CostCurve();
+		Instances result = cc.getCurve(preds, classValue);
+		VisualizePanel vmc = new VisualizePanel();
+		vmc.setLog(m_Log);
+		vmc.setName(result.relationName()+". (Class value "+
+			    classAtt.value(classValue)+")");
+		PlotData2D tempd = new PlotData2D(result);
+		tempd.m_displayAllPoints = true;
+		tempd.setPlotName(result.relationName());
+		boolean [] connectPoints = 
+		  new boolean [result.numInstances()];
+		for (int jj = 1; jj < connectPoints.length; jj+=2) {
+		  connectPoints[jj] = true;
+		}
+		tempd.setConnectPoints(connectPoints);
+		//		  tempd.addInstanceNumberAttribute();
+		vmc.addPlot(tempd);
+		visualizeClassifierErrors(vmc);
+	      } catch (Exception ex) {
+		ex.printStackTrace();
+	      }
+	    }
+	  });
+	visCost.add(clv);
+      }
+    } else {
+      visCost.setEnabled(false);
+    }
+    resultListMenu.add(visCost);
+    
+    // visualization plugins
+    JMenu visPlugins = new JMenu("Plugins");
+    boolean availablePlugins = false;
+    
+    // predictions
+    Vector pluginsVector = GenericObjectEditor.getClassnames(VisualizePlugin.class.getName());
+    for (int i = 0; i < pluginsVector.size(); i++) {
+      String className = (String) (pluginsVector.elementAt(i));
+      try {
+        VisualizePlugin plugin = (VisualizePlugin) Class.forName(className).newInstance();
+        if (plugin == null)
+          continue;
+        availablePlugins = true;
+        JMenuItem pluginMenuItem = plugin.getVisualizeMenuItem(preds, classAtt);
+        Version version = new Version();
+        if (pluginMenuItem != null) {
+          if (version.compareTo(plugin.getMinVersion()) < 0)
+            pluginMenuItem.setText(pluginMenuItem.getText() + " (weka outdated)");
+          if (version.compareTo(plugin.getMaxVersion()) >= 0)
+            pluginMenuItem.setText(pluginMenuItem.getText() + " (plugin outdated)");
+          visPlugins.add(pluginMenuItem);
+        }
+      }
+      catch (Exception e) {
+	  //e.printStackTrace();
+      }
+    }
+    
+    // errros
+    pluginsVector = GenericObjectEditor.getClassnames(ErrorVisualizePlugin.class.getName());
+    for (int i = 0; i < pluginsVector.size(); i++) {
+      String className = (String) (pluginsVector.elementAt(i));
+      try {
+        ErrorVisualizePlugin plugin = (ErrorVisualizePlugin) Class.forName(className).newInstance();
+        if (plugin == null)
+          continue;
+        availablePlugins = true;
+        JMenuItem pluginMenuItem = plugin.getVisualizeMenuItem(vp.getInstances());
+        Version version = new Version();
+        if (pluginMenuItem != null) {
+          if (version.compareTo(plugin.getMinVersion()) < 0)
+            pluginMenuItem.setText(pluginMenuItem.getText() + " (weka outdated)");
+          if (version.compareTo(plugin.getMaxVersion()) >= 0)
+            pluginMenuItem.setText(pluginMenuItem.getText() + " (plugin outdated)");
+          visPlugins.add(pluginMenuItem);
+        }
+      }
+      catch (Exception e) {
+	  //e.printStackTrace();
+      }
+    }
+    
+    // graphs+trees
+    if (grph != null) {
+      // trees
+      if (((Drawable) temp_classifier).graphType() == Drawable.TREE) {
+	pluginsVector = GenericObjectEditor.getClassnames(TreeVisualizePlugin.class.getName());
+	for (int i = 0; i < pluginsVector.size(); i++) {
+	  String className = (String) (pluginsVector.elementAt(i));
+	  try {
+	    TreeVisualizePlugin plugin = (TreeVisualizePlugin) Class.forName(className).newInstance();
+	    if (plugin == null)
+	      continue;
+	    availablePlugins = true;
+	    JMenuItem pluginMenuItem = plugin.getVisualizeMenuItem(grph, selectedName);
+	    Version version = new Version();
+	    if (pluginMenuItem != null) {
+	      if (version.compareTo(plugin.getMinVersion()) < 0)
+		pluginMenuItem.setText(pluginMenuItem.getText() + " (weka outdated)");
+	      if (version.compareTo(plugin.getMaxVersion()) >= 0)
+		pluginMenuItem.setText(pluginMenuItem.getText() + " (plugin outdated)");
+	      visPlugins.add(pluginMenuItem);
+	    }
+	  }
+	  catch (Exception e) {
+	    //e.printStackTrace();
+	  }
+	}
+      }
+      // graphs
+      else {
+	pluginsVector = GenericObjectEditor.getClassnames(GraphVisualizePlugin.class.getName());
+	for (int i = 0; i < pluginsVector.size(); i++) {
+	  String className = (String) (pluginsVector.elementAt(i));
+	  try {
+	    GraphVisualizePlugin plugin = (GraphVisualizePlugin) Class.forName(className).newInstance();
+	    if (plugin == null)
+	      continue;
+	    availablePlugins = true;
+	    JMenuItem pluginMenuItem = plugin.getVisualizeMenuItem(grph, selectedName);
+	    Version version = new Version();
+	    if (pluginMenuItem != null) {
+	      if (version.compareTo(plugin.getMinVersion()) < 0)
+		pluginMenuItem.setText(pluginMenuItem.getText() + " (weka outdated)");
+	      if (version.compareTo(plugin.getMaxVersion()) >= 0)
+		pluginMenuItem.setText(pluginMenuItem.getText() + " (plugin outdated)");
+	      visPlugins.add(pluginMenuItem);
+	    }
+	  }
+	  catch (Exception e) {
+	    //e.printStackTrace();
+	  }
+	}
+      }
+    }
+
+    if (availablePlugins)
+      resultListMenu.add(visPlugins);
+
+    resultListMenu.show(m_History.getList(), x, y);
+  }
+
+  /**
+   * Pops up a TreeVisualizer for the classifier from the currently
+   * selected item in the results list
+   * @param dottyString the description of the tree in dotty format
+   * @param treeName the title to assign to the display
+   */
+  protected void visualizeTree(String dottyString, String treeName) {
+    final javax.swing.JFrame jf = 
+      new javax.swing.JFrame("Weka Classifier Tree Visualizer: "+treeName);
+    jf.setSize(500,400);
+    jf.getContentPane().setLayout(new BorderLayout());
+    TreeVisualizer tv = new TreeVisualizer(null,
+					   dottyString,
+					   new PlaceNode2());
+    jf.getContentPane().add(tv, BorderLayout.CENTER);
+    jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	}
+      });
+    
+    jf.setVisible(true);
+    tv.fitToScreen();
+  }
+
+  /**
+   * Pops up a GraphVisualizer for the BayesNet classifier from the currently
+   * selected item in the results list
+   * 
+   * @param XMLBIF the description of the graph in XMLBIF ver. 0.3
+   * @param graphName the name of the graph
+   */
+  protected void visualizeBayesNet(String XMLBIF, String graphName) {
+    final javax.swing.JFrame jf = 
+      new javax.swing.JFrame("Weka Classifier Graph Visualizer: "+graphName);
+    jf.setSize(500,400);
+    jf.getContentPane().setLayout(new BorderLayout());
+    GraphVisualizer gv = new GraphVisualizer();
+    try { gv.readBIF(XMLBIF);
+    }
+    catch(BIFFormatException be) { System.err.println("unable to visualize BayesNet"); be.printStackTrace(); }
+    gv.layoutGraph();
+
+    jf.getContentPane().add(gv, BorderLayout.CENTER);
+    jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	}
+      });
+    
+    jf.setVisible(true);
+  }
+  
+  /**
+   * Pops up the Cost/Benefit analysis panel.
+   * 
+   * @param cb the CostBenefitAnalysis panel to pop up
+   */
+  protected void visualizeCostBenefitAnalysis(CostBenefitAnalysis cb, 
+      String classifierAndRelationName) {
+    if (cb != null) {
+      String windowTitle = "Weka Classifier: Cost/Benefit Analysis ";
+      if (classifierAndRelationName != null) {
+        windowTitle += "- " + classifierAndRelationName;
+      }
+      final javax.swing.JFrame jf = 
+        new javax.swing.JFrame(windowTitle);
+        jf.setSize(1000,600);
+        jf.getContentPane().setLayout(new BorderLayout());
+
+        jf.getContentPane().add(cb, BorderLayout.CENTER);
+        jf.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent e) {
+            jf.dispose();
+          }
+        });
+
+    jf.setVisible(true);
+    }
+  }
+
+
+  /**
+   * Pops up a VisualizePanel for visualizing the data and errors for 
+   * the classifier from the currently selected item in the results list
+   * @param sp the VisualizePanel to pop up.
+   */
+  protected void visualizeClassifierErrors(VisualizePanel sp) {
+   
+    if (sp != null) {
+      String plotName = sp.getName(); 
+	final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Classifier Visualize: "+plotName);
+	jf.setSize(600,400);
+	jf.getContentPane().setLayout(new BorderLayout());
+
+	jf.getContentPane().add(sp, BorderLayout.CENTER);
+	jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	  }
+	});
+
+    jf.setVisible(true);
+    }
+  }
+
+  /**
+   * Save the currently selected classifier output to a file.
+   * @param name the name of the buffer to save
+   */
+  protected void saveBuffer(String name) {
+    StringBuffer sb = m_History.getNamedBuffer(name);
+    if (sb != null) {
+      if (m_SaveOut.save(sb)) {
+	m_Log.logMessage("Save successful.");
+      }
+    }
+  }
+  
+
+  /**
+   * Stops the currently running classifier (if any).
+   */
+  protected void stopClassifier() {
+
+    if (m_RunThread != null) {
+      m_RunThread.interrupt();
+      
+      // This is deprecated (and theoretically the interrupt should do).
+      m_RunThread.stop();
+    }
+  }
+
+  /**
+   * Saves the currently selected classifier
+   * 
+   * @param name the name of the run
+   * @param classifier the classifier to save
+   * @param trainHeader the header of the training instances
+   */
+  protected void saveClassifier(String name, Classifier classifier,
+				Instances trainHeader) {
+
+    File sFile = null;
+    boolean saveOK = true;
+ 
+    int returnVal = m_FileChooser.showSaveDialog(this);
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      sFile = m_FileChooser.getSelectedFile();
+      if (!sFile.getName().toLowerCase().endsWith(MODEL_FILE_EXTENSION)) {
+	sFile = new File(sFile.getParent(), sFile.getName() 
+			 + MODEL_FILE_EXTENSION);
+      }
+      m_Log.statusMessage("Saving model to file...");
+      
+      try {
+	OutputStream os = new FileOutputStream(sFile);
+	if (sFile.getName().endsWith(".gz")) {
+	  os = new GZIPOutputStream(os);
+	}
+	ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
+	objectOutputStream.writeObject(classifier);
+	if (trainHeader != null) objectOutputStream.writeObject(trainHeader);
+	objectOutputStream.flush();
+	objectOutputStream.close();
+      } catch (Exception e) {
+	
+	JOptionPane.showMessageDialog(null, e, "Save Failed",
+				      JOptionPane.ERROR_MESSAGE);
+	saveOK = false;
+      }
+      if (saveOK)
+	m_Log.logMessage("Saved model (" + name
+			 + ") to file '" + sFile.getName() + "'");
+      m_Log.statusMessage("OK");
+    }
+  }
+
+  /**
+   * Loads a classifier
+   */
+  protected void loadClassifier() {
+
+    int returnVal = m_FileChooser.showOpenDialog(this);
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      File selected = m_FileChooser.getSelectedFile();
+      Classifier classifier = null;
+      Instances trainHeader = null;
+
+      m_Log.statusMessage("Loading model from file...");
+
+      try {
+	InputStream is = new FileInputStream(selected);
+	if (selected.getName().endsWith(PMML_FILE_EXTENSION)) {
+	  PMMLModel model = PMMLFactory.getPMMLModel(is, m_Log);
+	  if (model instanceof PMMLClassifier) {
+	    classifier = (PMMLClassifier)model;
+	    /*trainHeader = 
+	      ((PMMLClassifier)classifier).getMiningSchema().getMiningSchemaAsInstances(); */
+	  } else {
+	    throw new Exception("PMML model is not a classification/regression model!");
+	  }
+	} else {
+	if (selected.getName().endsWith(".gz")) {
+	  is = new GZIPInputStream(is);
+	}
+	ObjectInputStream objectInputStream = new ObjectInputStream(is);
+	classifier = (Classifier) objectInputStream.readObject();
+	try { // see if we can load the header
+	  trainHeader = (Instances) objectInputStream.readObject();
+	} catch (Exception e) {} // don't fuss if we can't
+	objectInputStream.close();
+	}
+      } catch (Exception e) {
+	
+	JOptionPane.showMessageDialog(null, e, "Load Failed",
+				      JOptionPane.ERROR_MESSAGE);
+      }	
+
+      m_Log.statusMessage("OK");
+      
+      if (classifier != null) {
+	m_Log.logMessage("Loaded model from file '" + selected.getName()+ "'");
+	String name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
+	String cname = classifier.getClass().getName();
+	if (cname.startsWith("weka.classifiers."))
+	  cname = cname.substring("weka.classifiers.".length());
+	name += cname + " from file '" + selected.getName() + "'";
+	StringBuffer outBuff = new StringBuffer();
+
+	outBuff.append("=== Model information ===\n\n");
+	outBuff.append("Filename:     " + selected.getName() + "\n");
+	outBuff.append("Scheme:       " + classifier.getClass().getName());
+	if (classifier instanceof OptionHandler) {
+	  String [] o = ((OptionHandler) classifier).getOptions();
+	  outBuff.append(" " + Utils.joinOptions(o));
+	}
+	outBuff.append("\n");
+	if (trainHeader != null) {
+	  outBuff.append("Relation:     " + trainHeader.relationName() + '\n');
+	  outBuff.append("Attributes:   " + trainHeader.numAttributes() + '\n');
+	  if (trainHeader.numAttributes() < 100) {
+	    for (int i = 0; i < trainHeader.numAttributes(); i++) {
+	      outBuff.append("              " + trainHeader.attribute(i).name()
+			     + '\n');
+	    }
+	  } else {
+	    outBuff.append("              [list of attributes omitted]\n");
+	  }
+	} else {
+	  outBuff.append("\nTraining data unknown\n");
+	} 
+
+	outBuff.append("\n=== Classifier model ===\n\n");
+	outBuff.append(classifier.toString() + "\n");
+	
+	m_History.addResult(name, outBuff);
+	m_History.setSingle(name);
+	FastVector vv = new FastVector();
+	vv.addElement(classifier);
+	if (trainHeader != null) vv.addElement(trainHeader);
+	// allow visualization of graphable classifiers
+	String grph = null;
+	if (classifier instanceof Drawable) {
+	  try {
+	    grph = ((Drawable)classifier).graph();
+	  } catch (Exception ex) {
+	  }
+	}
+	if (grph != null) vv.addElement(grph);
+	
+	m_History.addObject(name, vv);
+      }
+    }
+  }
+  
+  /**
+   * Re-evaluates the named classifier with the current test set. Unpredictable
+   * things will happen if the data set is not compatible with the classifier.
+   *
+   * @param name the name of the classifier entry
+   * @param classifier the classifier to evaluate
+   * @param trainHeader the header of the training set
+   */
+  protected void reevaluateModel(final String name, 
+                                 final Classifier classifier, 
+                                 final Instances trainHeader) {
+
+    if (m_RunThread == null) {
+      synchronized (this) {
+	m_StartBut.setEnabled(false);
+	m_StopBut.setEnabled(true);
+      }
+      m_RunThread = new Thread() {
+          public void run() {
+            // Copy the current state of things
+            m_Log.statusMessage("Setting up...");
+
+            StringBuffer outBuff = m_History.getNamedBuffer(name);
+            DataSource source = null;
+            Instances userTestStructure = null;
+            ClassifierErrorsPlotInstances plotInstances = null;
+
+            CostMatrix costMatrix = null;
+            if (m_EvalWRTCostsBut.isSelected()) {
+              costMatrix = new CostMatrix((CostMatrix) m_CostMatrixEditor
+                                          .getValue());
+            }    
+            boolean outputConfusion = m_OutputConfusionBut.isSelected();
+            boolean outputPerClass = m_OutputPerClassBut.isSelected();
+            boolean outputSummary = true;
+            boolean outputEntropy = m_OutputEntropyBut.isSelected();
+            boolean saveVis = m_StorePredictionsBut.isSelected();
+            boolean outputPredictionsText = (m_ClassificationOutputEditor.getValue().getClass() != Null.class);
+            String grph = null;    
+            Evaluation eval = null;
+
+            try {
+
+              boolean incrementalLoader = (m_TestLoader instanceof IncrementalConverter);
+              if (m_TestLoader != null && m_TestLoader.getStructure() != null) {
+                m_TestLoader.reset();
+                source = new DataSource(m_TestLoader);
+                userTestStructure = source.getStructure();
+              }
+              // Check the test instance compatibility
+              if (source == null) {
+                throw new Exception("No user test set has been specified");
+              }
+              if (trainHeader != null) {
+                if (trainHeader.classIndex() > 
+                    userTestStructure.numAttributes()-1)
+                  throw new Exception("Train and test set are not compatible");
+                userTestStructure.setClassIndex(trainHeader.classIndex());
+                if (!trainHeader.equalHeaders(userTestStructure)) {
+                  throw new Exception("Train and test set are not compatible:\n" + trainHeader.equalHeadersMsg(userTestStructure));
+                }
+              } else {
+        	if (classifier instanceof PMMLClassifier) {
+        	  // set the class based on information in the mining schema
+        	  Instances miningSchemaStructure = 
+        	    ((PMMLClassifier)classifier).getMiningSchema().getMiningSchemaAsInstances();
+        	  String className = miningSchemaStructure.classAttribute().name();
+        	  Attribute classMatch = userTestStructure.attribute(className);
+        	  if (classMatch == null) {
+        	    throw new Exception("Can't find a match for the PMML target field " 
+        		+ className + " in the "
+        		+ "test instances!");
+        	  }
+        	  userTestStructure.setClass(classMatch);
+        	} else {
+        	  userTestStructure.
+        	    setClassIndex(userTestStructure.numAttributes()-1);
+        	}
+              }
+              if (m_Log instanceof TaskLogger) {
+                ((TaskLogger)m_Log).taskStarted();
+              }
+              m_Log.statusMessage("Evaluating on test data...");
+              m_Log.logMessage("Re-evaluating classifier (" + name 
+                               + ") on test set");
+              eval = new Evaluation(userTestStructure, costMatrix);
+              eval.useNoPriors();
+      
+              // set up the structure of the plottable instances for 
+              // visualization if selected
+              if (saveVis) {
+        	plotInstances = new ClassifierErrorsPlotInstances();
+        	plotInstances.setInstances(userTestStructure);
+        	plotInstances.setClassifier(classifier);
+        	plotInstances.setClassIndex(userTestStructure.classIndex());
+        	plotInstances.setUp();
+              }
+      
+              outBuff.append("\n=== Re-evaluation on test set ===\n\n");
+              outBuff.append("User supplied test set\n");  
+              outBuff.append("Relation:     " 
+                             + userTestStructure.relationName() + '\n');
+              if (incrementalLoader)
+        	outBuff.append("Instances:     unknown (yet). Reading incrementally\n");
+              else
+        	outBuff.append("Instances:    " + source.getDataSet().numInstances() + "\n");
+              outBuff.append("Attributes:   " 
+        	  + userTestStructure.numAttributes() 
+        	  + "\n\n");
+              if (trainHeader == null)
+                outBuff.append("NOTE - if test set is not compatible then results are "
+                               + "unpredictable\n\n");
+
+              AbstractOutput classificationOutput = null;
+              if (outputPredictionsText) {
+        	classificationOutput = (AbstractOutput) m_ClassificationOutputEditor.getValue();
+        	classificationOutput.setHeader(userTestStructure);
+        	classificationOutput.setBuffer(outBuff);
+        	classificationOutput.setAttributes("");
+        	classificationOutput.setOutputDistribution(false);
+        	classificationOutput.printHeader();
+              }
+
+	      Instance instance;
+	      int jj = 0;
+	      while (source.hasMoreElements(userTestStructure)) {
+		instance = source.nextElement(userTestStructure);
+		plotInstances.process(instance, classifier, eval);
+		if (outputPredictionsText) {
+		  classificationOutput.printClassification(classifier, instance, jj);
+		}
+		if ((++jj % 100) == 0) {
+		  m_Log.statusMessage("Evaluating on test data. Processed "
+		      +jj+" instances...");
+		}
+	      }
+
+	      if (outputPredictionsText)
+		classificationOutput.printFooter();
+              if (outputPredictionsText && classificationOutput.generatesOutput()) {
+                outBuff.append("\n");
+              } 
+      
+              if (outputSummary) {
+                outBuff.append(eval.toSummaryString(outputEntropy) + "\n");
+              }
+      
+              if (userTestStructure.classAttribute().isNominal()) {
+	
+                if (outputPerClass) {
+                  outBuff.append(eval.toClassDetailsString() + "\n");
+                }
+	
+                if (outputConfusion) {
+                  outBuff.append(eval.toMatrixString() + "\n");
+                }
+              }
+      
+              m_History.updateResult(name);
+              m_Log.logMessage("Finished re-evaluation");
+              m_Log.statusMessage("OK");
+            } catch (Exception ex) {
+              ex.printStackTrace();
+              m_Log.logMessage(ex.getMessage());
+              m_Log.statusMessage("See error log");
+
+              ex.printStackTrace();
+              m_Log.logMessage(ex.getMessage());
+              JOptionPane.showMessageDialog(ClassifierPanel.this,
+                                            "Problem evaluationg classifier:\n"
+                                            + ex.getMessage(),
+                                            "Evaluate classifier",
+                                            JOptionPane.ERROR_MESSAGE);
+              m_Log.statusMessage("Problem evaluating classifier");
+            } finally {
+              try {
+        	if (classifier instanceof PMMLClassifier) {
+        	  // signal the end of the scoring run so
+        	  // that the initialized state can be reset
+        	  // (forces the field mapping to be recomputed
+        	  // for the next scoring run).
+        	  ((PMMLClassifier)classifier).done();
+        	}
+        	
+                if (plotInstances != null && plotInstances.getPlotInstances().numInstances() > 0) {
+                  m_CurrentVis = new VisualizePanel();
+                  m_CurrentVis.setName(name + " (" + userTestStructure.relationName() + ")");
+                  m_CurrentVis.setLog(m_Log);
+                  m_CurrentVis.addPlot(plotInstances.getPlotData(name));
+                  m_CurrentVis.setColourIndex(plotInstances.getPlotInstances().classIndex()+1);
+                  plotInstances.cleanUp();
+	  
+                  if (classifier instanceof Drawable) {
+                    try {
+                      grph = ((Drawable)classifier).graph();
+                    } catch (Exception ex) {
+                    }
+                  }
+
+                  if (saveVis) {
+                    FastVector vv = new FastVector();
+                    vv.addElement(classifier);
+                    if (trainHeader != null) vv.addElement(trainHeader);
+                    vv.addElement(m_CurrentVis);
+                    if (grph != null) {
+                      vv.addElement(grph);
+                    }
+                    if ((eval != null) && (eval.predictions() != null)) {
+                      vv.addElement(eval.predictions());
+                      vv.addElement(userTestStructure.classAttribute());
+                    }
+                    m_History.addObject(name, vv);
+                  } else {
+                    FastVector vv = new FastVector();
+                    vv.addElement(classifier);
+                    if (trainHeader != null) vv.addElement(trainHeader);
+                    m_History.addObject(name, vv);
+                  }
+                }
+              } catch (Exception ex) {
+                ex.printStackTrace();
+              }
+              if (isInterrupted()) {
+                m_Log.logMessage("Interrupted reevaluate model");
+                m_Log.statusMessage("Interrupted");
+              }
+
+              synchronized (this) {
+                m_StartBut.setEnabled(true);
+                m_StopBut.setEnabled(false);
+                m_RunThread = null;
+              }
+
+              if (m_Log instanceof TaskLogger) {
+                ((TaskLogger)m_Log).taskFinished();
+              }
+            }
+          }
+        };
+
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+  
+  /**
+   * updates the capabilities filter of the GOE
+   * 
+   * @param filter	the new filter to use
+   */
+  protected void updateCapabilitiesFilter(Capabilities filter) {
+    Instances 		tempInst;
+    Capabilities 	filterClass;
+
+    if (filter == null) {
+      m_ClassifierEditor.setCapabilitiesFilter(new Capabilities(null));
+      return;
+    }
+    
+    if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
+      tempInst = new Instances(m_Instances, 0);
+    else
+      tempInst = new Instances(m_Instances);
+    tempInst.setClassIndex(m_ClassCombo.getSelectedIndex());
+
+    try {
+      filterClass = Capabilities.forInstances(tempInst);
+    }
+    catch (Exception e) {
+      filterClass = new Capabilities(null);
+    }
+    
+    // set new filter
+    m_ClassifierEditor.setCapabilitiesFilter(filterClass);
+    
+    // Check capabilities
+    m_StartBut.setEnabled(true);
+    Capabilities currentFilter = m_ClassifierEditor.getCapabilitiesFilter();
+    Classifier classifier = (Classifier) m_ClassifierEditor.getValue();
+    Capabilities currentSchemeCapabilities =  null;
+    if (classifier != null && currentFilter != null && 
+        (classifier instanceof CapabilitiesHandler)) {
+      currentSchemeCapabilities = ((CapabilitiesHandler)classifier).getCapabilities();
+      
+      if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+          !currentSchemeCapabilities.supports(currentFilter)) {
+        m_StartBut.setEnabled(false);
+      }
+    }
+  }
+  
+  /**
+   * method gets called in case of a change event
+   * 
+   * @param e		the associated change event
+   */
+  public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
+    if (e.getFilter() == null)
+      updateCapabilitiesFilter(null);
+    else
+      updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
+  }
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Classify";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Classify instances";
+  }
+  
+  /**
+   * Tests out the classifier panel from the command line.
+   *
+   * @param args may optionally contain the name of a dataset to load.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Classifier");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final ClassifierPanel sp = new ClassifierPanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      sp.setLog(lp);
+      jf.getContentPane().add(lp, BorderLayout.SOUTH);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+      if (args.length == 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	sp.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererAssignmentsPlotInstances.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererAssignmentsPlotInstances.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererAssignmentsPlotInstances.java	(revision 29)
@@ -0,0 +1,249 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ClustererAssignmentsPlotInstances.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.explorer;
+
+import weka.clusterers.ClusterEvaluation;
+import weka.clusterers.Clusterer;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.DenseInstance;
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.visualize.Plot2D;
+import weka.gui.visualize.PlotData2D;
+
+/**
+ * A class for generating plottable cluster assignments.
+ * <p/>
+ * Example usage:
+ * <pre>
+ * Instances train = ... // from somewhere
+ * Instances test = ... // from somewhere
+ * Clusterer cls = ... // from somewhere
+ * // build and evaluate clusterer
+ * cls.buildClusterer(train);
+ * ClusterEvaluation eval = new ClusterEvaluation();
+ * eval.setClusterer(cls);
+ * eval.evaluateClusterer(test);
+ * // generate plot instances
+ * ClustererPlotInstances plotInstances = new ClustererPlotInstances();
+ * plotInstances.setClusterer(cls);
+ * plotInstances.setInstances(test);
+ * plotInstances.setClusterer(cls);
+ * plotInstances.setClusterEvaluation(eval);
+ * plotInstances.setUp();
+ * // generate visualization
+ * VisualizePanel visPanel = new VisualizePanel();
+ * visPanel.addPlot(plotInstances.getPlotData("plot name"));
+ * // clean up
+ * plotInstances.cleanUp();
+ * </pre>
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6021 $
+ */
+public class ClustererAssignmentsPlotInstances
+  extends AbstractPlotInstances {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4748134272046520423L;
+
+  /** for storing the plot shapes. */
+  protected int[] m_PlotShapes;
+  
+  /** the clusterer being used. */
+  protected Clusterer m_Clusterer;
+  
+  /** the cluster evaluation to use. */
+  protected ClusterEvaluation m_Evaluation;
+  
+  /**
+   * Initializes the members.
+   */
+  protected void initialize() {
+    super.initialize();
+    
+    m_PlotShapes = null;
+    m_Clusterer  = null;
+    m_Evaluation = null;
+  }
+  
+  /**
+   * Sets the classifier used for making the predictions.
+   * 
+   * @param value	the clusterer to use
+   */
+  public void setClusterer(Clusterer value) {
+    m_Clusterer = value;
+  }
+  
+  /**
+   * Returns the currently set clusterer.
+   * 
+   * @return		the clusterer in use
+   */
+  public Clusterer getClusterer() {
+    return m_Clusterer;
+  }
+
+  /**
+   * Sets the cluster evaluation object to use.
+   * 
+   * @param value	the evaluation object
+   */
+  public void setClusterEvaluation(ClusterEvaluation value) {
+    m_Evaluation = value;
+  }
+  
+  /**
+   * Returns the cluster evaluation object in use.
+   * 
+   * @return		the evaluation object
+   */
+  public ClusterEvaluation getClusterEvaluation() {
+    return m_Evaluation;
+  }
+  
+  /**
+   * Checks whether clusterer and evaluation are provided.
+   */
+  protected void check() {
+    super.check();
+
+    if (m_Clusterer == null)
+      throw new IllegalStateException("No clusterer set!");
+  
+    if (m_Evaluation == null)
+      throw new IllegalStateException("No cluster evaluation set!");
+  }
+  
+  /**
+   * Sets up the structure for the plot instances.
+   */
+  protected void determineFormat() {
+    int 	numClusters;
+    FastVector 	hv;
+    Attribute 	predictedCluster;
+    FastVector 	clustVals;
+    int		i;
+    
+    numClusters = m_Evaluation.getNumClusters();
+    hv          = new FastVector();
+    clustVals   = new FastVector();
+
+    for (i = 0; i < numClusters; i++)
+      clustVals.addElement("cluster" + (i+1));
+    predictedCluster = new Attribute("Cluster", clustVals);
+    for (i = 0; i < m_Instances.numAttributes(); i++)
+      hv.addElement(m_Instances.attribute(i).copy());
+    hv.addElement(predictedCluster);
+    
+    m_PlotInstances = new Instances(
+	m_Instances.relationName() + "_clustered", hv, m_Instances.numInstances());
+  }
+  
+  /**
+   * Generates the cluster assignments.
+   * 
+   * @see		#m_PlotShapes
+   * @see		#m_PlotSizes
+   * @see		#m_PlotInstances
+   */
+  protected void process() {
+    double[] 	clusterAssignments;
+    int		i;
+    double[] 	values;
+    int 	j;
+    int[] 	classAssignments;
+    
+    clusterAssignments = m_Evaluation.getClusterAssignments();
+    
+    classAssignments   = null;
+    if (m_Instances.classIndex() >= 0) {
+      classAssignments = m_Evaluation.getClassesToClusters();
+      m_PlotShapes = new int[m_Instances.numInstances()];
+      for (i = 0; i < m_Instances.numInstances(); i++)
+	m_PlotShapes[i] = Plot2D.CONST_AUTOMATIC_SHAPE;
+    }
+
+    for (i = 0; i < m_Instances.numInstances(); i++) {
+      values = new double[m_PlotInstances.numAttributes()];
+      for (j = 0; j < m_Instances.numAttributes(); j++)
+	values[j] = m_Instances.instance(i).value(j);
+      if (clusterAssignments[i] < 0) {
+        values[j] = Utils.missingValue();
+      } else {
+        values[j] = clusterAssignments[i];
+      }
+      m_PlotInstances.add(new DenseInstance(1.0, values));
+      if (m_PlotShapes != null) {
+        if (clusterAssignments[i] >= 0) {
+          if ((int) m_Instances.instance(i).classValue() != classAssignments[(int) clusterAssignments[i]])
+            m_PlotShapes[i] = Plot2D.ERROR_SHAPE;
+        } else {
+          m_PlotShapes[i] = Plot2D.MISSING_SHAPE;
+        }
+      }
+    }
+  }
+  
+  /**
+   * Performs optional post-processing.
+   */
+  protected void finishUp() {
+    super.finishUp();
+    
+    process();
+  }
+  
+  /**
+   * Assembles and returns the plot. The relation name of the dataset gets
+   * added automatically.
+   * 
+   * @param name	the name of the plot
+   * @return		the plot
+   * @throws Exception	if plot generation fails
+   */
+  protected PlotData2D createPlotData(String name) throws Exception {
+    PlotData2D 	result;
+    
+    result = new PlotData2D(m_PlotInstances);
+    if (m_PlotShapes != null)
+      result.setShapeType(m_PlotShapes);
+    result.addInstanceNumberAttribute();
+    result.setPlotName(name + " (" + m_Instances.relationName() + ")");
+
+    return result;
+  }
+  
+  /**
+   * For freeing up memory. Plot data cannot be generated after this call!
+   */
+  public void cleanUp() {
+    super.cleanUp();
+    
+    m_Clusterer  = null;
+    m_Evaluation = null;
+    m_PlotShapes = null;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/ClustererPanel.java	(revision 29)
@@ -0,0 +1,1701 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClustererPanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.clusterers.ClusterEvaluation;
+import weka.clusterers.Clusterer;
+import weka.core.Attribute;
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Drawable;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.Version;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+import weka.gui.InstancesSummaryPanel;
+import weka.gui.ListSelectorDialog;
+import weka.gui.Logger;
+import weka.gui.PropertyPanel;
+import weka.gui.ResultHistoryPanel;
+import weka.gui.SaveBuffer;
+import weka.gui.SetInstancesPanel;
+import weka.gui.SysErrLog;
+import weka.gui.TaskLogger;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.explorer.Explorer.LogHandler;
+import weka.gui.treevisualizer.PlaceNode2;
+import weka.gui.treevisualizer.TreeVisualizer;
+import weka.gui.visualize.VisualizePanel;
+import weka.gui.visualize.plugins.TreeVisualizePlugin;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.Vector;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JViewport;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.filechooser.FileFilter;
+
+import weka.gui.hierarchyvisualizer.HierarchyVisualizer;
+
+/** 
+ * This panel allows the user to select and configure a clusterer, and evaluate
+ * the clusterer using a number of testing modes (test on the training data,
+ * train/test on a percentage split, test on a
+ * separate split). The results of clustering runs are stored in a result
+ * history so that previous results are accessible.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @version $Revision: 5961 $
+ */
+public class ClustererPanel
+  extends JPanel
+  implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
+
+  /** for serialization */
+  static final long serialVersionUID = -2474932792950820990L;
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+  
+  /** The filename extension that should be used for model files */
+  public static String MODEL_FILE_EXTENSION = ".model";
+
+  /** Lets the user configure the clusterer */
+  protected GenericObjectEditor m_ClustererEditor =
+    new GenericObjectEditor();
+
+  /** The panel showing the current clusterer selection */
+  protected PropertyPanel m_CLPanel = new PropertyPanel(m_ClustererEditor);
+  
+  /** The output area for classification results */
+  protected JTextArea m_OutText = new JTextArea(20, 40);
+
+  /** The destination for log/status messages */
+  protected Logger m_Log = new SysErrLog();
+
+  /** The buffer saving object for saving output */
+  SaveBuffer m_SaveOut = new SaveBuffer(m_Log, this);
+
+  /** A panel controlling results viewing */
+  protected ResultHistoryPanel m_History = new ResultHistoryPanel(m_OutText);
+
+  /** Click to set test mode to generate a % split */
+  protected JRadioButton m_PercentBut = new JRadioButton("Percentage split");
+
+  /** Click to set test mode to test on training data */
+  protected JRadioButton m_TrainBut = new JRadioButton("Use training set");
+
+  /** Click to set test mode to a user-specified test set */
+  protected JRadioButton m_TestSplitBut =
+    new JRadioButton("Supplied test set");
+
+  /** Click to set test mode to classes to clusters based evaluation */
+  protected JRadioButton m_ClassesToClustersBut = 
+    new JRadioButton("Classes to clusters evaluation");
+
+  /** Lets the user select the class column for classes to clusters based
+      evaluation */
+  protected JComboBox m_ClassCombo = new JComboBox();
+
+  /** Label by where the % split is entered */
+  protected JLabel m_PercentLab = new JLabel("%", SwingConstants.RIGHT);
+
+  /** The field where the % split is entered */
+  protected JTextField m_PercentText = new JTextField("66");
+
+  /** The button used to open a separate test dataset */
+  protected JButton m_SetTestBut = new JButton("Set...");
+
+  /** The frame used to show the test set selection panel */
+  protected JFrame m_SetTestFrame;
+
+  /** The button used to popup a list for choosing attributes to ignore while
+      clustering */
+  protected JButton m_ignoreBut = new JButton("Ignore attributes");
+
+  protected DefaultListModel m_ignoreKeyModel = new DefaultListModel();
+  protected JList m_ignoreKeyList = new JList(m_ignoreKeyModel);
+
+  //  protected Remove m_ignoreFilter = null;
+  
+  /**
+   * Alters the enabled/disabled status of elements associated with each
+   * radio button
+   */
+  ActionListener m_RadioListener = new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      updateRadioLinks();
+    }
+  };
+
+  /** Click to start running the clusterer */
+  protected JButton m_StartBut = new JButton("Start");
+
+  /** Stop the class combo from taking up to much space */
+  private Dimension COMBO_SIZE = new Dimension(250, m_StartBut
+					       .getPreferredSize().height);
+
+  /** Click to stop a running clusterer */
+  protected JButton m_StopBut = new JButton("Stop");
+
+  /** The main set of instances we're playing with */
+  protected Instances m_Instances;
+
+  /** The user-supplied test set (if any) */
+  protected Instances m_TestInstances;
+
+  /** The current visualization object */
+  protected VisualizePanel m_CurrentVis = null;
+
+  /** Check to save the predictions in the results list for visualizing
+      later on */
+  protected JCheckBox m_StorePredictionsBut = 
+    new JCheckBox("Store clusters for visualization");
+  
+  /** A thread that clustering runs in */
+  protected Thread m_RunThread;
+  
+  /** The instances summary panel displayed by m_SetTestFrame */
+  protected InstancesSummaryPanel m_Summary;
+
+  /** Filter to ensure only model files are selected */  
+  protected FileFilter m_ModelFilter =
+    new ExtensionFileFilter(MODEL_FILE_EXTENSION, "Model object files");
+
+  /** The file chooser for selecting model files */
+  protected JFileChooser m_FileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /* Register the property editors we need */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the clusterer panel
+   */
+  public ClustererPanel() {
+
+    // Connect / configure the components
+    m_OutText.setEditable(false);
+    m_OutText.setFont(new Font("Monospaced", Font.PLAIN, 12));
+    m_OutText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    m_OutText.addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+	if ((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	    != InputEvent.BUTTON1_MASK) {
+	  m_OutText.selectAll();
+	}
+      }
+    });
+    m_History.setBorder(BorderFactory.createTitledBorder("Result list (right-click for options)"));
+    m_ClustererEditor.setClassType(Clusterer.class);
+    m_ClustererEditor.setValue(ExplorerDefaults.getClusterer());
+    m_ClustererEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        m_StartBut.setEnabled(true);
+        Capabilities currentFilter = m_ClustererEditor.getCapabilitiesFilter();
+        Clusterer clusterer = (Clusterer) m_ClustererEditor.getValue();
+        Capabilities currentSchemeCapabilities =  null;
+        if (clusterer != null && currentFilter != null && 
+            (clusterer instanceof CapabilitiesHandler)) {
+          currentSchemeCapabilities = ((CapabilitiesHandler)clusterer).getCapabilities();
+          
+          if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+              !currentSchemeCapabilities.supports(currentFilter)) {
+            m_StartBut.setEnabled(false);
+          }
+        }
+	repaint();
+      }
+    });
+
+    m_TrainBut.setToolTipText("Cluster the same set that the clusterer"
+			      + " is trained on");
+    m_PercentBut.setToolTipText("Train on a percentage of the data and"
+				+ " cluster the remainder");
+    m_TestSplitBut.setToolTipText("Cluster a user-specified dataset");
+    m_ClassesToClustersBut.setToolTipText("Evaluate clusters with respect to a"
+					  +" class");
+    m_ClassCombo.setToolTipText("Select the class attribute for class based"
+				+" evaluation");
+    m_StartBut.setToolTipText("Starts the clustering");
+    m_StopBut.setToolTipText("Stops a running clusterer");
+    m_StorePredictionsBut.
+      setToolTipText("Store predictions in the result list for later "
+		     +"visualization");
+    m_ignoreBut.setToolTipText("Ignore attributes during clustering");
+
+    m_FileChooser.setFileFilter(m_ModelFilter);
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    m_ClassCombo.setPreferredSize(COMBO_SIZE);
+    m_ClassCombo.setMaximumSize(COMBO_SIZE);
+    m_ClassCombo.setMinimumSize(COMBO_SIZE);
+    m_ClassCombo.setEnabled(false);
+
+    m_PercentBut.setSelected(ExplorerDefaults.getClustererTestMode() == 2);
+    m_TrainBut.setSelected(ExplorerDefaults.getClustererTestMode() == 3);
+    m_TestSplitBut.setSelected(ExplorerDefaults.getClustererTestMode() == 4);
+    m_ClassesToClustersBut.setSelected(ExplorerDefaults.getClustererTestMode() == 5);
+    m_StorePredictionsBut.setSelected(ExplorerDefaults.getClustererStoreClustersForVis());
+    updateRadioLinks();
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_TrainBut);
+    bg.add(m_PercentBut);
+    bg.add(m_TestSplitBut);
+    bg.add(m_ClassesToClustersBut);
+    m_TrainBut.addActionListener(m_RadioListener);
+    m_PercentBut.addActionListener(m_RadioListener);
+    m_TestSplitBut.addActionListener(m_RadioListener);
+    m_ClassesToClustersBut.addActionListener(m_RadioListener);
+    m_SetTestBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setTestSet();
+      }
+    });
+
+    m_StartBut.setEnabled(false);
+    m_StopBut.setEnabled(false);
+    m_ignoreBut.setEnabled(false);
+    m_StartBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	startClusterer();
+      }
+    });
+    m_StopBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	stopClusterer();
+      }
+    });
+
+    m_ignoreBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  setIgnoreColumns();
+	}
+      });
+   
+    m_History.setHandleRightClicks(false);
+    // see if we can popup a menu for the selected result
+    m_History.getList().addMouseListener(new MouseAdapter() {
+	public void mouseClicked(MouseEvent e) {
+	  if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
+	       != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
+	    int index = m_History.getList().locationToIndex(e.getPoint());
+	    if (index != -1) {
+	      String name = m_History.getNameAtIndex(index);
+	      visualizeClusterer(name, e.getX(), e.getY());
+	    } else {
+	      visualizeClusterer(null, e.getX(), e.getY());
+	    }
+	  }
+	}
+      });
+    
+    m_ClassCombo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	updateCapabilitiesFilter(m_ClustererEditor.getCapabilitiesFilter());
+      }
+    });
+
+    // Layout the GUI
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Clusterer"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    p1.setLayout(new BorderLayout());
+    p1.add(m_CLPanel, BorderLayout.NORTH);
+
+    JPanel p2 = new JPanel();
+    GridBagLayout gbL = new GridBagLayout();
+    p2.setLayout(gbL);
+    p2.setBorder(BorderFactory.createCompoundBorder(
+		 BorderFactory.createTitledBorder("Cluster mode"),
+		 BorderFactory.createEmptyBorder(0, 5, 5, 5)
+		 ));
+    GridBagConstraints gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(m_TrainBut, gbC);
+    p2.add(m_TrainBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(m_TestSplitBut, gbC);
+    p2.add(m_TestSplitBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 1;    gbC.gridwidth = 2;
+    gbC.insets = new Insets(2, 10, 2, 0);
+    gbL.setConstraints(m_SetTestBut, gbC);
+    p2.add(m_SetTestBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 2;     gbC.gridx = 0;
+    gbL.setConstraints(m_PercentBut, gbC);
+    p2.add(m_PercentBut);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 2;     gbC.gridx = 1;
+    gbC.insets = new Insets(2, 10, 2, 10);
+    gbL.setConstraints(m_PercentLab, gbC);
+    p2.add(m_PercentLab);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.EAST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 2;     gbC.gridx = 2;  gbC.weightx = 100;
+    gbC.ipadx = 20;
+    gbL.setConstraints(m_PercentText, gbC);
+    p2.add(m_PercentText);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 3;     gbC.gridx = 0;  gbC.gridwidth = 2;
+    gbL.setConstraints(m_ClassesToClustersBut, gbC);
+    p2.add(m_ClassesToClustersBut);
+
+    m_ClassCombo.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0));
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 4;     gbC.gridx = 0;  gbC.gridwidth = 2;
+    gbL.setConstraints(m_ClassCombo, gbC);
+    p2.add(m_ClassCombo);
+
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.WEST;
+    gbC.gridy = 5;     gbC.gridx = 0;  gbC.gridwidth = 2;
+    gbL.setConstraints(m_StorePredictionsBut, gbC);
+    p2.add(m_StorePredictionsBut);
+
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new GridLayout(2, 1));
+    JPanel ssButs = new JPanel();
+    ssButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ssButs.setLayout(new GridLayout(1, 2, 5, 5));
+    ssButs.add(m_StartBut);
+    ssButs.add(m_StopBut);
+
+    JPanel ib = new JPanel();
+    ib.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+    ib.setLayout(new GridLayout(1, 1, 5, 5));
+    ib.add(m_ignoreBut);
+    buttons.add(ib);
+    buttons.add(ssButs);
+    
+    JPanel p3 = new JPanel();
+    p3.setBorder(BorderFactory.createTitledBorder("Clusterer output"));
+    p3.setLayout(new BorderLayout());
+    final JScrollPane js = new JScrollPane(m_OutText);
+    p3.add(js, BorderLayout.CENTER);
+    js.getViewport().addChangeListener(new ChangeListener() {
+      private int lastHeight;
+      public void stateChanged(ChangeEvent e) {
+	JViewport vp = (JViewport)e.getSource();
+	int h = vp.getViewSize().height; 
+	if (h != lastHeight) { // i.e. an addition not just a user scrolling
+	  lastHeight = h;
+	  int x = h - vp.getExtentSize().height;
+	  vp.setViewPosition(new Point(0, x));
+	}
+      }
+    });    
+
+    JPanel mondo = new JPanel();
+    gbL = new GridBagLayout();
+    mondo.setLayout(gbL);
+    gbC = new GridBagConstraints();
+    //    gbC.anchor = GridBagConstraints.WEST;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 0;     gbC.gridx = 0;
+    gbL.setConstraints(p2, gbC);
+    mondo.add(p2);
+    gbC = new GridBagConstraints();
+    gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.HORIZONTAL;
+    gbC.gridy = 1;     gbC.gridx = 0;
+    gbL.setConstraints(buttons, gbC);
+    mondo.add(buttons);
+    gbC = new GridBagConstraints();
+    //gbC.anchor = GridBagConstraints.NORTH;
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 2;     gbC.gridx = 0; gbC.weightx = 0;
+    gbL.setConstraints(m_History, gbC);
+    mondo.add(m_History);
+    gbC = new GridBagConstraints();
+    gbC.fill = GridBagConstraints.BOTH;
+    gbC.gridy = 0;     gbC.gridx = 1;
+    gbC.gridheight = 3;
+    gbC.weightx = 100; gbC.weighty = 100;
+    gbL.setConstraints(p3, gbC);
+    mondo.add(p3);
+
+    setLayout(new BorderLayout());
+    add(p1, BorderLayout.NORTH);
+    add(mondo, BorderLayout.CENTER);
+  }
+  
+  /**
+   * Updates the enabled status of the input fields and labels.
+   */
+  protected void updateRadioLinks() {
+    
+    m_SetTestBut.setEnabled(m_TestSplitBut.isSelected());
+    if ((m_SetTestFrame != null) && (!m_TestSplitBut.isSelected())) {
+      m_SetTestFrame.setVisible(false);
+    }
+    m_PercentText.setEnabled(m_PercentBut.isSelected());
+    m_PercentLab.setEnabled(m_PercentBut.isSelected());
+    m_ClassCombo.setEnabled(m_ClassesToClustersBut.isSelected());
+  }
+
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+
+    m_Log = newLog;
+  }
+
+  /**
+   * Tells the panel to use a new set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+
+    m_Instances = inst;
+   
+    m_ignoreKeyModel.removeAllElements();
+    
+    String [] attribNames = new String [m_Instances.numAttributes()];
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      String name = m_Instances.attribute(i).name();
+      m_ignoreKeyModel.addElement(name);
+
+       String type = "";
+      switch (m_Instances.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = "(Nom) ";
+	break;
+      case Attribute.NUMERIC:
+	type = "(Num) ";
+	break;
+      case Attribute.STRING:
+	type = "(Str) ";
+	break;
+      case Attribute.DATE:
+	type = "(Dat) ";
+	break;
+      case Attribute.RELATIONAL:
+	type = "(Rel) ";
+	break;
+      default:
+	type = "(???) ";
+      }
+      String attnm = m_Instances.attribute(i).name();
+     
+      attribNames[i] = type + attnm;
+    }
+
+    
+    m_StartBut.setEnabled(m_RunThread == null);
+    m_StopBut.setEnabled(m_RunThread != null);
+    m_ignoreBut.setEnabled(true);
+    m_ClassCombo.setModel(new DefaultComboBoxModel(attribNames));
+    if (inst.classIndex() == -1)
+      m_ClassCombo.setSelectedIndex(attribNames.length - 1);
+    else
+      m_ClassCombo.setSelectedIndex(inst.classIndex());
+    updateRadioLinks();
+  }
+
+  /**
+   * Sets the user test set. Information about the current test set
+   * is displayed in an InstanceSummaryPanel and the user is given the
+   * ability to load another set from a file or url.
+   *
+   */
+  protected void setTestSet() {
+
+    if (m_SetTestFrame == null) {
+      final SetInstancesPanel sp = new SetInstancesPanel();
+      sp.setReadIncrementally(false);
+      m_Summary = sp.getSummary();
+      if (m_TestInstances != null) {
+	sp.setInstances(m_TestInstances);
+      }
+      sp.addPropertyChangeListener(new PropertyChangeListener() {
+	public void propertyChange(PropertyChangeEvent e) {
+	  m_TestInstances = sp.getInstances();
+	  m_TestInstances.setClassIndex(-1);  // make sure that no class attribute is set!
+	}
+      });
+      // Add propertychangelistener to update m_TestInstances whenever
+      // it changes in the settestframe
+      m_SetTestFrame = new JFrame("Test Instances");
+      sp.setParentFrame(m_SetTestFrame);   // enable Close-Button
+      m_SetTestFrame.getContentPane().setLayout(new BorderLayout());
+      m_SetTestFrame.getContentPane().add(sp, BorderLayout.CENTER);
+      m_SetTestFrame.pack();
+    }
+    m_SetTestFrame.setVisible(true);
+  }
+  
+  /**
+   * Starts running the currently configured clusterer with the current
+   * settings. This is run in a separate thread, and will only start if
+   * there is no clusterer already running. The clusterer output is sent
+   * to the results history panel.
+   */
+  protected void startClusterer() {
+
+    if (m_RunThread == null) {
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(true);
+      m_ignoreBut.setEnabled(false);
+      m_RunThread = new Thread() {
+	public void run() {
+	  // Copy the current state of things
+	  m_Log.statusMessage("Setting up...");
+	  Instances inst = new Instances(m_Instances);
+	  inst.setClassIndex(-1);
+	  Instances userTest = null;
+	  ClustererAssignmentsPlotInstances plotInstances = ExplorerDefaults.getClustererAssignmentsPlotInstances();
+	  plotInstances.setClusterer((Clusterer) m_ClustererEditor.getValue());
+	  if (m_TestInstances != null) {
+	    userTest = new Instances(m_TestInstances);
+	  }
+	  
+	  boolean saveVis = m_StorePredictionsBut.isSelected();
+	  String grph = null;
+	  int[] ignoredAtts = null;
+
+	  int testMode = 0;
+	  int percent = 66;
+	  Clusterer clusterer = (Clusterer) m_ClustererEditor.getValue();
+	  Clusterer fullClusterer = null;
+	  StringBuffer outBuff = new StringBuffer();
+	  String name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
+	  String cname = clusterer.getClass().getName();
+	  if (cname.startsWith("weka.clusterers.")) {
+	    name += cname.substring("weka.clusterers.".length());
+	  } else {
+	    name += cname;
+	  }
+          String cmd = m_ClustererEditor.getValue().getClass().getName();
+          if (m_ClustererEditor.getValue() instanceof OptionHandler)
+            cmd += " " + Utils.joinOptions(((OptionHandler) m_ClustererEditor.getValue()).getOptions());
+	  try {
+	    m_Log.logMessage("Started " + cname);
+	    m_Log.logMessage("Command: " + cmd);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskStarted();
+	    }
+	    if (m_PercentBut.isSelected()) {
+	      testMode = 2;
+	      percent = Integer.parseInt(m_PercentText.getText());
+	      if ((percent <= 0) || (percent >= 100)) {
+		throw new Exception("Percentage must be between 0 and 100");
+	      }
+	    } else if (m_TrainBut.isSelected()) {
+	      testMode = 3;
+	    } else if (m_TestSplitBut.isSelected()) {
+	      testMode = 4;
+	      // Check the test instance compatibility
+	      if (userTest == null) {
+		throw new Exception("No user test set has been opened");
+	      }
+	      if (!inst.equalHeaders(userTest)) {
+		throw new Exception("Train and test set are not compatible\n" + inst.equalHeadersMsg(userTest));
+	      }
+	    } else if (m_ClassesToClustersBut.isSelected()) {
+	      testMode = 5;
+	    } else {
+	      throw new Exception("Unknown test mode");
+	    }
+
+	    Instances trainInst = new Instances(inst);
+	    if (m_ClassesToClustersBut.isSelected()) {
+	      trainInst.setClassIndex(m_ClassCombo.getSelectedIndex());
+	      inst.setClassIndex(m_ClassCombo.getSelectedIndex());
+	      if (inst.classAttribute().isNumeric()) {
+		throw new Exception("Class must be nominal for class based "
+				    +"evaluation!");
+	      }
+	    }
+	    if (!m_ignoreKeyList.isSelectionEmpty()) {
+	      trainInst = removeIgnoreCols(trainInst);
+	    }
+
+	    // Output some header information
+	    outBuff.append("=== Run information ===\n\n");
+	    outBuff.append("Scheme:       " + cname);
+	    if (clusterer instanceof OptionHandler) {
+	      String [] o = ((OptionHandler) clusterer).getOptions();
+	      outBuff.append(" " + Utils.joinOptions(o));
+	    }
+	    outBuff.append("\n");
+	    outBuff.append("Relation:     " + inst.relationName() + '\n');
+	    outBuff.append("Instances:    " + inst.numInstances() + '\n');
+	    outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
+	    if (inst.numAttributes() < 100) {
+	      boolean [] selected = new boolean [inst.numAttributes()];
+	      for (int i = 0; i < inst.numAttributes(); i++) {
+		selected[i] = true;
+	      }
+	      if (!m_ignoreKeyList.isSelectionEmpty()) {
+		int [] indices = m_ignoreKeyList.getSelectedIndices();
+		for (int i = 0; i < indices.length; i++) {
+		  selected[indices[i]] = false;
+		}
+	      }
+	      if (m_ClassesToClustersBut.isSelected()) {
+		selected[m_ClassCombo.getSelectedIndex()] = false;
+	      }
+	      for (int i = 0; i < inst.numAttributes(); i++) {
+		if (selected[i]) {
+		  outBuff.append("              " + inst.attribute(i).name()
+				 + '\n');
+		}
+	      }
+	      if (!m_ignoreKeyList.isSelectionEmpty() 
+		  || m_ClassesToClustersBut.isSelected()) {
+		outBuff.append("Ignored:\n");
+		for (int i = 0; i < inst.numAttributes(); i++) {
+		  if (!selected[i]) {
+		    outBuff.append("              " + inst.attribute(i).name()
+				   + '\n');
+		  }
+		}
+	      }
+	    } else {
+	      outBuff.append("              [list of attributes omitted]\n");
+	    }
+
+	    if (!m_ignoreKeyList.isSelectionEmpty()) {
+	      ignoredAtts = m_ignoreKeyList.getSelectedIndices();
+	    }
+
+	    if (m_ClassesToClustersBut.isSelected()) {
+	      // add class to ignored list
+	      if (ignoredAtts == null) {
+		ignoredAtts = new int[1];
+		ignoredAtts[0] = m_ClassCombo.getSelectedIndex();
+	      } else {
+		int[] newIgnoredAtts = new int[ignoredAtts.length+1];
+		System.arraycopy(ignoredAtts, 0, newIgnoredAtts, 0, ignoredAtts.length);
+		newIgnoredAtts[ignoredAtts.length] = m_ClassCombo.getSelectedIndex();
+		ignoredAtts = newIgnoredAtts;
+	      }
+	    }
+
+
+	    outBuff.append("Test mode:    ");
+	    switch (testMode) {
+	      case 3: // Test on training
+	      outBuff.append("evaluate on training data\n");
+	      break;
+	      case 2: // Percent split
+	      outBuff.append("split " + percent
+			       + "% train, remainder test\n");
+	      break;
+	      case 4: // Test on user split
+	      outBuff.append("user supplied test set: "
+			     + userTest.numInstances() + " instances\n");
+	      break;
+	    case 5: // Classes to clusters evaluation on training
+	      outBuff.append("Classes to clusters evaluation on training data");
+	      
+	      break;
+	    }
+	    outBuff.append("\n");
+	    m_History.addResult(name, outBuff);
+	    m_History.setSingle(name);
+	    
+	    // Build the model and output it.
+	    m_Log.statusMessage("Building model on training data...");
+
+	    // remove the class attribute (if set) and build the clusterer
+	    clusterer.buildClusterer(removeClass(trainInst));
+	    
+	    if (testMode == 2) {
+	      outBuff.append("\n=== Clustering model (full training set) ===\n\n");
+	    
+	      outBuff.append(clusterer.toString() + '\n');
+	    }
+	    m_History.updateResult(name);
+	    if (clusterer instanceof Drawable) {
+	      try {
+		grph = ((Drawable)clusterer).graph();
+	      } catch (Exception ex) {
+	      }
+	    }
+	    // copy full model for output
+	    SerializedObject so = new SerializedObject(clusterer);
+	    fullClusterer = (Clusterer) so.getObject();
+	    
+	    ClusterEvaluation eval = new ClusterEvaluation();
+	    eval.setClusterer(clusterer);
+	    switch (testMode) {
+	      case 3: case 5: // Test on training
+	      m_Log.statusMessage("Clustering training data...");
+	      eval.evaluateClusterer(trainInst);
+	      plotInstances.setInstances(inst);
+	      plotInstances.setClusterEvaluation(eval);
+	      outBuff.append("=== Model and evaluation on training set ===\n\n");
+	      break;
+
+	      case 2: // Percent split
+	      m_Log.statusMessage("Randomizing instances...");
+	      inst.randomize(new Random(1));
+	      trainInst.randomize(new Random(1));
+	      int trainSize = trainInst.numInstances() * percent / 100;
+	      int testSize = trainInst.numInstances() - trainSize;
+	      Instances train = new Instances(trainInst, 0, trainSize);
+	      Instances test = new Instances(trainInst, trainSize, testSize);
+	      Instances testVis = new Instances(inst, trainSize, testSize);
+	      m_Log.statusMessage("Building model on training split...");
+	      clusterer.buildClusterer(train);
+	      m_Log.statusMessage("Evaluating on test split...");
+	      eval.evaluateClusterer(test);
+	      plotInstances.setInstances(testVis);
+	      plotInstances.setClusterEvaluation(eval);
+	      outBuff.append("=== Model and evaluation on test split ===\n");
+	      break;
+		
+	      case 4: // Test on user split
+	      m_Log.statusMessage("Evaluating on test data...");
+	      Instances userTestT = new Instances(userTest);
+	      if (!m_ignoreKeyList.isSelectionEmpty()) {
+		userTestT = removeIgnoreCols(userTestT);
+	      }
+	      eval.evaluateClusterer(userTestT);
+	      plotInstances.setInstances(userTest);
+	      plotInstances.setClusterEvaluation(eval);
+	      outBuff.append("=== Model and evaluation on test set ===\n");
+	      break;
+
+	      default:
+	      throw new Exception("Test mode not implemented");
+	    }
+	    outBuff.append(eval.clusterResultsToString());
+	    outBuff.append("\n");
+	    m_History.updateResult(name);
+	    m_Log.logMessage("Finished " + cname);
+	    m_Log.statusMessage("OK");
+	  } catch (Exception ex) {
+	    ex.printStackTrace();
+	    m_Log.logMessage(ex.getMessage());
+	    JOptionPane.showMessageDialog(ClustererPanel.this,
+					  "Problem evaluating clusterer:\n"
+					  + ex.getMessage(),
+					  "Evaluate clusterer",
+					  JOptionPane.ERROR_MESSAGE);
+	    m_Log.statusMessage("Problem evaluating clusterer");
+	  } finally {
+	    if (plotInstances != null) {
+	      plotInstances.setUp();
+	      m_CurrentVis = new VisualizePanel();
+	      m_CurrentVis.setName(name+" ("+inst.relationName()+")");
+	      m_CurrentVis.setLog(m_Log);
+	      try {
+		m_CurrentVis.addPlot(plotInstances.getPlotData(name));
+	      } catch (Exception ex) {
+		System.err.println(ex);
+	      }
+	      plotInstances.cleanUp();
+
+	      FastVector vv = new FastVector();
+	      vv.addElement(fullClusterer);
+	      Instances trainHeader = new Instances(m_Instances, 0);
+	      vv.addElement(trainHeader);
+	      if (ignoredAtts != null) vv.addElement(ignoredAtts);
+	      if (saveVis) {
+		vv.addElement(m_CurrentVis);
+		if (grph != null) {
+		  vv.addElement(grph);
+		}
+		
+	      }
+	      m_History.addObject(name, vv);
+	    }
+	    if (isInterrupted()) {
+	      m_Log.logMessage("Interrupted " + cname);
+	      m_Log.statusMessage("See error log");
+	    }
+	    m_RunThread = null;
+	    m_StartBut.setEnabled(true);
+	    m_StopBut.setEnabled(false);
+	    m_ignoreBut.setEnabled(true);
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskFinished();
+	    }
+	  }
+	}
+      };
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+
+  private Instances removeClass(Instances inst) {
+    Remove af = new Remove();
+    Instances retI = null;
+    
+    try {
+      if (inst.classIndex() < 0) {
+	retI = inst;
+      } else {
+	af.setAttributeIndices(""+(inst.classIndex()+1));
+	af.setInvertSelection(false);
+	af.setInputFormat(inst);
+	retI = Filter.useFilter(inst, af);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    return retI;
+  }
+
+  private Instances removeIgnoreCols(Instances inst) {
+    
+    // If the user is doing classes to clusters evaluation and
+    // they have opted to ignore the class, then unselect the class in
+    // the ignore list
+    if (m_ClassesToClustersBut.isSelected()) {
+      int classIndex = m_ClassCombo.getSelectedIndex();
+      if (m_ignoreKeyList.isSelectedIndex(classIndex)) {
+	m_ignoreKeyList.removeSelectionInterval(classIndex, classIndex);
+      }
+    }
+    int [] selected = m_ignoreKeyList.getSelectedIndices();
+    Remove af = new Remove();
+    Instances retI = null;
+
+    try {
+      af.setAttributeIndicesArray(selected);
+      af.setInvertSelection(false);
+      af.setInputFormat(inst);
+      retI = Filter.useFilter(inst, af);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return retI;
+  }
+
+  private Instances removeIgnoreCols(Instances inst, int[] toIgnore) {
+
+    Remove af = new Remove();
+    Instances retI = null;
+
+    try {
+      af.setAttributeIndicesArray(toIgnore);
+      af.setInvertSelection(false);
+      af.setInputFormat(inst);
+      retI = Filter.useFilter(inst, af);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return retI;
+  }
+
+  /**
+   * Stops the currently running clusterer (if any).
+   */
+  protected void stopClusterer() {
+
+    if (m_RunThread != null) {
+      m_RunThread.interrupt();
+      
+      // This is deprecated (and theoretically the interrupt should do).
+      m_RunThread.stop();
+      
+    }
+  }
+
+  /**
+   * Pops up a TreeVisualizer for the clusterer from the currently
+   * selected item in the results list
+   * @param graphString the description of the tree in dotty format
+   * @param treeName the title to assign to the display
+   */
+  protected void visualizeTree(String graphString, String treeName) {
+    final javax.swing.JFrame jf = 
+      new javax.swing.JFrame("Weka Classifier Tree Visualizer: "+treeName);
+    jf.setSize(500,400);
+    jf.getContentPane().setLayout(new BorderLayout());
+    if (graphString.contains("digraph")) {
+	    TreeVisualizer tv = new TreeVisualizer(null,
+						   graphString,
+						   new PlaceNode2());
+	    jf.getContentPane().add(tv, BorderLayout.CENTER);
+	    jf.addWindowListener(new java.awt.event.WindowAdapter() {
+		public void windowClosing(java.awt.event.WindowEvent e) {
+		  jf.dispose();
+		}
+	      });
+	    jf.setVisible(true);
+	    tv.fitToScreen();
+    } else if (graphString.startsWith("Newick:")) {
+	    HierarchyVisualizer tv = new HierarchyVisualizer(graphString.substring(7));
+	    jf.getContentPane().add(tv, BorderLayout.CENTER);
+	    jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	    	public void windowClosing(java.awt.event.WindowEvent e) {
+	    		jf.dispose();
+	    	}
+	    });
+	    jf.setVisible(true);
+	    tv.fitToScreen();
+    }
+  }
+
+  /**
+   * Pops up a visualize panel to display cluster assignments
+   * @param sp the visualize panel to display
+   */
+  protected void visualizeClusterAssignments(VisualizePanel sp) {
+    if (sp != null) {
+      String plotName = sp.getName();
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Clusterer Visualize: "+plotName);
+      jf.setSize(500,400);
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	  }
+	});
+
+      jf.setVisible(true);
+    }
+  }
+
+  /**
+   * Handles constructing a popup menu with visualization options
+   * @param name the name of the result history list entry clicked on by
+   * the user
+   * @param x the x coordinate for popping up the menu
+   * @param y the y coordinate for popping up the menu
+   */
+  protected void visualizeClusterer(String name, int x, int y) {
+    final String selectedName = name;
+    JPopupMenu resultListMenu = new JPopupMenu();
+
+    JMenuItem visMainBuffer = new JMenuItem("View in main window");
+    if (selectedName != null) {
+      visMainBuffer.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    m_History.setSingle(selectedName);
+	  }
+	});
+    } else {
+      visMainBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visMainBuffer);
+
+    JMenuItem visSepBuffer = new JMenuItem("View in separate window");
+    if (selectedName != null) {
+    visSepBuffer.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.openFrame(selectedName);
+	}
+      });
+    } else {
+      visSepBuffer.setEnabled(false);
+    }
+    resultListMenu.add(visSepBuffer);
+
+    JMenuItem saveOutput = new JMenuItem("Save result buffer");
+    if (selectedName != null) {
+      saveOutput.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveBuffer(selectedName);
+	  }
+	});
+    } else {
+      saveOutput.setEnabled(false);
+    }
+    resultListMenu.add(saveOutput);
+    
+    JMenuItem deleteOutput = new JMenuItem("Delete result buffer");
+    if (selectedName != null) {
+      deleteOutput.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  m_History.removeResult(selectedName);
+	}
+      });
+    } else {
+      deleteOutput.setEnabled(false);
+    }
+    resultListMenu.add(deleteOutput);
+
+    resultListMenu.addSeparator();
+
+    JMenuItem loadModel = new JMenuItem("Load model");
+    loadModel.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  loadClusterer();
+	}
+      });
+    resultListMenu.add(loadModel);
+
+    FastVector o = null;
+    if (selectedName != null) {
+      o = (FastVector)m_History.getNamedObject(selectedName);
+    }
+
+    VisualizePanel temp_vp = null;
+    String temp_grph = null;
+    Clusterer temp_clusterer = null;
+    Instances temp_trainHeader = null;
+    int[] temp_ignoreAtts = null;
+    
+    if (o != null) {
+      for (int i = 0; i < o.size(); i++) {
+	Object temp = o.elementAt(i);
+	if (temp instanceof Clusterer) {
+	  temp_clusterer = (Clusterer)temp;
+	} else if (temp instanceof Instances) { // training header
+	  temp_trainHeader = (Instances)temp;
+	} else if (temp instanceof int[]) { // ignored attributes
+	  temp_ignoreAtts = (int[])temp;
+	} else if (temp instanceof VisualizePanel) { // normal errors
+	  temp_vp = (VisualizePanel)temp;
+	} else if (temp instanceof String) { // graphable output
+	  temp_grph = (String)temp;
+	}
+      } 
+    }
+      
+    final VisualizePanel vp = temp_vp;
+    final String grph = temp_grph;
+    final Clusterer clusterer = temp_clusterer;
+    final Instances trainHeader = temp_trainHeader;
+    final int[] ignoreAtts = temp_ignoreAtts;
+    
+    JMenuItem saveModel = new JMenuItem("Save model");
+    if (clusterer != null) {
+      saveModel.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    saveClusterer(selectedName, clusterer, trainHeader, ignoreAtts);
+	  }
+	});
+    } else {
+      saveModel.setEnabled(false);
+    }
+    resultListMenu.add(saveModel);
+    
+    JMenuItem reEvaluate =
+      new JMenuItem("Re-evaluate model on current test set");
+    if (clusterer != null && m_TestInstances != null) {
+      reEvaluate.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    reevaluateModel(selectedName, clusterer, trainHeader, ignoreAtts);
+	  }
+	}); 
+    } else {
+      reEvaluate.setEnabled(false);
+    }
+    resultListMenu.add(reEvaluate);
+    
+    resultListMenu.addSeparator();
+    
+    JMenuItem visClusts = new JMenuItem("Visualize cluster assignments");
+    if (vp != null) {
+      visClusts.addActionListener(new ActionListener() {
+	    public void actionPerformed(ActionEvent e) {
+	      visualizeClusterAssignments(vp);
+	    }
+	  });
+      
+    } else {
+      visClusts.setEnabled(false);
+    }
+    resultListMenu.add(visClusts);
+
+    JMenuItem visTree = new JMenuItem("Visualize tree");
+    if (grph != null) {
+      visTree.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    String title;
+	    if (vp != null) title = vp.getName();
+	    else title = selectedName;
+	    visualizeTree(grph, title);
+	  }
+	});
+    } else {
+      visTree.setEnabled(false);
+    }
+    resultListMenu.add(visTree);
+
+    
+    // visualization plugins
+    JMenu visPlugins = new JMenu("Plugins");
+    boolean availablePlugins = false;
+    
+    // trees
+    if (grph != null) {
+      // trees
+      Vector pluginsVector = GenericObjectEditor.getClassnames(TreeVisualizePlugin.class.getName());
+      for (int i = 0; i < pluginsVector.size(); i++) {
+	String className = (String) (pluginsVector.elementAt(i));
+	try {
+	  TreeVisualizePlugin plugin = (TreeVisualizePlugin) Class.forName(className).newInstance();
+	  if (plugin == null)
+	    continue;
+	  availablePlugins = true;
+	  JMenuItem pluginMenuItem = plugin.getVisualizeMenuItem(grph, selectedName);
+	  Version version = new Version();
+	  if (pluginMenuItem != null) {
+	    if (version.compareTo(plugin.getMinVersion()) < 0)
+	      pluginMenuItem.setText(pluginMenuItem.getText() + " (weka outdated)");
+	    if (version.compareTo(plugin.getMaxVersion()) >= 0)
+	      pluginMenuItem.setText(pluginMenuItem.getText() + " (plugin outdated)");
+	    visPlugins.add(pluginMenuItem);
+	  }
+	}
+	catch (Exception e) {
+	  //e.printStackTrace();
+	}
+      }
+    }
+
+    if (availablePlugins)
+      resultListMenu.add(visPlugins);
+    
+    resultListMenu.show(m_History.getList(), x, y);
+  }
+  
+  /**
+   * Save the currently selected clusterer output to a file.
+   * @param name the name of the buffer to save
+   */
+  protected void saveBuffer(String name) {
+    StringBuffer sb = m_History.getNamedBuffer(name);
+    if (sb != null) {
+      if (m_SaveOut.save(sb)) {
+	m_Log.logMessage("Save successful.");
+      }
+    }
+  }
+
+  private void setIgnoreColumns() {
+    ListSelectorDialog jd = new ListSelectorDialog(null, m_ignoreKeyList);
+
+    // Open the dialog
+    int result = jd.showDialog();
+    
+    if (result != ListSelectorDialog.APPROVE_OPTION) {
+      // clear selected indices
+      m_ignoreKeyList.clearSelection();
+    }
+  }
+
+  /**
+   * Saves the currently selected clusterer
+   */
+  protected void saveClusterer(String name, Clusterer clusterer,
+			       Instances trainHeader, int[] ignoredAtts) {
+
+    File sFile = null;
+    boolean saveOK = true;
+
+    int returnVal = m_FileChooser.showSaveDialog(this);
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      sFile = m_FileChooser.getSelectedFile();
+      if (!sFile.getName().toLowerCase().endsWith(MODEL_FILE_EXTENSION)) {
+	sFile = new File(sFile.getParent(), sFile.getName() 
+			 + MODEL_FILE_EXTENSION);
+      }
+      m_Log.statusMessage("Saving model to file...");
+      
+      try {
+	OutputStream os = new FileOutputStream(sFile);
+	if (sFile.getName().endsWith(".gz")) {
+	  os = new GZIPOutputStream(os);
+	}
+	ObjectOutputStream objectOutputStream = new ObjectOutputStream(os);
+	objectOutputStream.writeObject(clusterer);
+	if (trainHeader != null) objectOutputStream.writeObject(trainHeader);
+	if (ignoredAtts != null) objectOutputStream.writeObject(ignoredAtts);
+	objectOutputStream.flush();
+	objectOutputStream.close();
+      } catch (Exception e) {
+	
+	JOptionPane.showMessageDialog(null, e, "Save Failed",
+				      JOptionPane.ERROR_MESSAGE);
+	saveOK = false;
+      }
+      if (saveOK)
+	m_Log.logMessage("Saved model (" + name
+			 + ") to file '" + sFile.getName() + "'");
+      m_Log.statusMessage("OK");
+    }
+  }
+
+  /**
+   * Loads a clusterer
+   */
+  protected void loadClusterer() {
+
+    int returnVal = m_FileChooser.showOpenDialog(this);
+    if (returnVal == JFileChooser.APPROVE_OPTION) {
+      File selected = m_FileChooser.getSelectedFile();
+      Clusterer clusterer = null;
+      Instances trainHeader = null;
+      int[] ignoredAtts = null;
+
+      m_Log.statusMessage("Loading model from file...");
+
+      try {
+	InputStream is = new FileInputStream(selected);
+	if (selected.getName().endsWith(".gz")) {
+	  is = new GZIPInputStream(is);
+	}
+	ObjectInputStream objectInputStream = new ObjectInputStream(is);
+	clusterer = (Clusterer) objectInputStream.readObject();
+	try { // see if we can load the header & ignored attribute info
+	  trainHeader = (Instances) objectInputStream.readObject();
+	  ignoredAtts = (int[]) objectInputStream.readObject();
+	} catch (Exception e) {} // don't fuss if we can't
+	objectInputStream.close();
+      } catch (Exception e) {
+	
+	JOptionPane.showMessageDialog(null, e, "Load Failed",
+				      JOptionPane.ERROR_MESSAGE);
+      }	
+
+      m_Log.statusMessage("OK");
+      
+      if (clusterer != null) {
+	m_Log.logMessage("Loaded model from file '" + selected.getName()+ "'");
+	String name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
+	String cname = clusterer.getClass().getName();
+	if (cname.startsWith("weka.clusterers."))
+	  cname = cname.substring("weka.clusterers.".length());
+	name += cname + " from file '" + selected.getName() + "'";
+	StringBuffer outBuff = new StringBuffer();
+
+	outBuff.append("=== Model information ===\n\n");
+	outBuff.append("Filename:     " + selected.getName() + "\n");
+	outBuff.append("Scheme:       " + clusterer.getClass().getName());
+	if (clusterer instanceof OptionHandler) {
+	  String [] o = ((OptionHandler) clusterer).getOptions();
+	  outBuff.append(" " + Utils.joinOptions(o));
+	}
+	outBuff.append("\n");
+
+	if (trainHeader != null) {
+
+	  outBuff.append("Relation:     " + trainHeader.relationName() + '\n');
+	  outBuff.append("Attributes:   " + trainHeader.numAttributes() + '\n');
+	  if (trainHeader.numAttributes() < 100) {
+	    boolean [] selectedAtts = new boolean [trainHeader.numAttributes()];
+	    for (int i = 0; i < trainHeader.numAttributes(); i++) {
+	      selectedAtts[i] = true;
+	    }
+	    
+	    if (ignoredAtts != null)
+	      for (int i=0; i<ignoredAtts.length; i++)
+		selectedAtts[ignoredAtts[i]] = false;
+	   
+	    for (int i = 0; i < trainHeader.numAttributes(); i++) {
+	      if (selectedAtts[i]) {
+		outBuff.append("              " + trainHeader.attribute(i).name()
+			       + '\n');
+	      }
+	    }
+	    if (ignoredAtts != null) {
+	      outBuff.append("Ignored:\n");
+	      for (int i=0; i<ignoredAtts.length; i++)
+		outBuff.append("              "
+			       + trainHeader.attribute(ignoredAtts[i]).name()
+			       + '\n');
+	    }
+	  } else {
+	    outBuff.append("              [list of attributes omitted]\n");
+	  }
+	} else {
+	  outBuff.append("\nTraining data unknown\n");
+	} 
+	
+	outBuff.append("\n=== Clustering model ===\n\n");
+	outBuff.append(clusterer.toString() + "\n");
+
+	m_History.addResult(name, outBuff);
+	m_History.setSingle(name);
+	FastVector vv = new FastVector();
+	vv.addElement(clusterer);
+	if (trainHeader != null) vv.addElement(trainHeader);
+	if (ignoredAtts != null) vv.addElement(ignoredAtts);
+	// allow visualization of graphable classifiers
+	String grph = null;
+	if (clusterer instanceof Drawable) {
+	  try {
+	    grph = ((Drawable)clusterer).graph();
+	  } catch (Exception ex) {
+	  }
+	}
+	if (grph != null) vv.addElement(grph);
+	
+	m_History.addObject(name, vv);
+	
+      }
+    }
+  }
+
+  /**
+   * Re-evaluates the named clusterer with the current test set. Unpredictable
+   * things will happen if the data set is not compatible with the clusterer.
+   *
+   * @param name the name of the clusterer entry
+   * @param clusterer the clusterer to evaluate
+   * @param trainHeader the header of the training set
+   * @param ignoredAtts ignored attributes
+   */
+  protected void reevaluateModel(final String name, 
+                                 final Clusterer clusterer,
+				 final Instances trainHeader, 
+                                 final int[] ignoredAtts) {
+
+    if (m_RunThread == null) {
+      m_StartBut.setEnabled(false);
+      m_StopBut.setEnabled(true);
+      m_ignoreBut.setEnabled(false);
+      m_RunThread = new Thread() {
+          public void run() {
+            // Copy the current state of things
+            m_Log.statusMessage("Setting up...");
+
+            StringBuffer outBuff = m_History.getNamedBuffer(name);
+            Instances userTest = null;
+
+            ClustererAssignmentsPlotInstances plotInstances = ExplorerDefaults.getClustererAssignmentsPlotInstances();
+            plotInstances.setClusterer(clusterer);
+            if (m_TestInstances != null) {
+              userTest = new Instances(m_TestInstances);
+            }
+    
+            boolean saveVis = m_StorePredictionsBut.isSelected();
+            String grph = null;
+
+            try {
+              if (userTest == null) {
+                throw new Exception("No user test set has been opened");
+              }
+              if (trainHeader != null && !trainHeader.equalHeaders(userTest)) {
+                throw new Exception("Train and test set are not compatible\n" + trainHeader.equalHeadersMsg(userTest));
+              }
+
+              m_Log.statusMessage("Evaluating on test data...");
+              m_Log.logMessage("Re-evaluating clusterer (" + name + ") on test set");
+
+              m_Log.logMessage("Started reevaluate model");
+              if (m_Log instanceof TaskLogger) {
+                ((TaskLogger)m_Log).taskStarted();
+              }
+              ClusterEvaluation eval = new ClusterEvaluation();
+              eval.setClusterer(clusterer);
+    
+              Instances userTestT = new Instances(userTest);
+              if (ignoredAtts != null) {
+                userTestT = removeIgnoreCols(userTestT, ignoredAtts);
+              }
+
+              eval.evaluateClusterer(userTestT);
+      
+              plotInstances.setClusterEvaluation(eval);
+              plotInstances.setInstances(userTest);
+              plotInstances.setUp();
+
+              outBuff.append("\n=== Re-evaluation on test set ===\n\n");
+              outBuff.append("User supplied test set\n");  
+              outBuff.append("Relation:     " + userTest.relationName() + '\n');
+              outBuff.append("Instances:    " + userTest.numInstances() + '\n');
+              outBuff.append("Attributes:   " + userTest.numAttributes() + "\n\n");
+              if (trainHeader == null)
+                outBuff.append("NOTE - if test set is not compatible then results are "
+                               + "unpredictable\n\n");
+      
+              outBuff.append(eval.clusterResultsToString());
+              outBuff.append("\n");
+              m_History.updateResult(name);
+              m_Log.logMessage("Finished re-evaluation");
+              m_Log.statusMessage("OK");
+            } catch (Exception ex) {
+              ex.printStackTrace();
+              m_Log.logMessage(ex.getMessage());
+              JOptionPane.showMessageDialog(ClustererPanel.this,
+                                            "Problem evaluating clusterer:\n"
+                                            + ex.getMessage(),
+                                            "Evaluate clusterer",
+                                            JOptionPane.ERROR_MESSAGE);
+              m_Log.statusMessage("Problem evaluating clusterer");
+
+            } finally {
+              if (plotInstances != null) {
+                m_CurrentVis = new VisualizePanel();
+                m_CurrentVis.setName(name+" ("+userTest.relationName()+")");
+                m_CurrentVis.setLog(m_Log);
+                try {
+                  m_CurrentVis.addPlot(plotInstances.getPlotData(name));
+                } catch (Exception ex) {
+                  System.err.println(ex);
+                }
+	
+                FastVector vv = new FastVector();
+                vv.addElement(clusterer);
+                if (trainHeader != null) vv.addElement(trainHeader);
+                if (ignoredAtts != null) vv.addElement(ignoredAtts);
+                if (saveVis) {
+                  vv.addElement(m_CurrentVis);
+                  if (grph != null) {
+                    vv.addElement(grph);
+                  }
+	  
+                }
+                m_History.addObject(name, vv);
+
+              }
+              if (isInterrupted()) {
+                m_Log.logMessage("Interrupted reevaluate model");
+                m_Log.statusMessage("See error log");
+              }
+              m_RunThread = null;
+              m_StartBut.setEnabled(true);
+              m_StopBut.setEnabled(false);
+              m_ignoreBut.setEnabled(true);
+              if (m_Log instanceof TaskLogger) {
+                ((TaskLogger)m_Log).taskFinished();
+              }
+            }
+          }
+      
+        };
+      m_RunThread.setPriority(Thread.MIN_PRIORITY);
+      m_RunThread.start();
+    }
+  }
+  
+  /**
+   * updates the capabilities filter of the GOE
+   * 
+   * @param filter	the new filter to use
+   */
+  protected void updateCapabilitiesFilter(Capabilities filter) {
+    Instances 		tempInst;
+    Capabilities 	filterClass;
+
+    if (filter == null) {
+      m_ClustererEditor.setCapabilitiesFilter(new Capabilities(null));
+      return;
+    }
+    
+    if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
+      tempInst = new Instances(m_Instances, 0);
+    else
+      tempInst = new Instances(m_Instances);
+    tempInst.setClassIndex(-1);
+
+    try {
+      filterClass = Capabilities.forInstances(tempInst);
+    }
+    catch (Exception e) {
+      filterClass = new Capabilities(null);
+    }
+    
+    m_ClustererEditor.setCapabilitiesFilter(filterClass);
+    
+    // check capabilities
+    m_StartBut.setEnabled(true);
+    Capabilities currentFilter = m_ClustererEditor.getCapabilitiesFilter();
+    Clusterer clusterer = (Clusterer) m_ClustererEditor.getValue();
+    Capabilities currentSchemeCapabilities =  null;
+    if (clusterer != null && currentFilter != null && 
+        (clusterer instanceof CapabilitiesHandler)) {
+      currentSchemeCapabilities = ((CapabilitiesHandler)clusterer).getCapabilities();
+      
+      if (!currentSchemeCapabilities.supportsMaybe(currentFilter) &&
+          !currentSchemeCapabilities.supports(currentFilter)) {
+        m_StartBut.setEnabled(false);
+      }
+    }
+  }
+  
+  /**
+   * method gets called in case of a change event
+   * 
+   * @param e		the associated change event
+   */
+  public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
+    if (e.getFilter() == null)
+      updateCapabilitiesFilter(null);
+    else
+      updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
+  }
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Cluster";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Identify instance clusters";
+  }
+
+  /**
+   * Tests out the clusterer panel from the command line.
+   *
+   * @param args may optionally contain the name of a dataset to load.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Cluster");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final ClustererPanel sp = new ClustererPanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      sp.setLog(lp);
+      jf.getContentPane().add(lp, BorderLayout.SOUTH);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+      if (args.length == 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	sp.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/DataGeneratorPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/DataGeneratorPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/DataGeneratorPanel.java	(revision 29)
@@ -0,0 +1,187 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DataGeneratorPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.datagenerators.DataGenerator;
+import weka.gui.GenericObjectEditor;
+import weka.gui.Logger;
+import weka.gui.PropertyPanel;
+import weka.gui.SysErrLog;
+
+import java.awt.BorderLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+/** 
+ * A panel for generating artificial data via DataGenerators.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public class DataGeneratorPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2520408165350629380L;
+
+  /** the GOE for the generators */
+  protected GenericObjectEditor m_GeneratorEditor = new GenericObjectEditor();
+
+  /** the generated Instances */
+  protected Instances m_Instances = null;
+
+  /** the generated output (as text) */
+  protected StringWriter m_Output = new StringWriter();
+
+  /** The destination for log/status messages */
+  protected Logger m_Log = new SysErrLog();
+
+  /** register the classes */
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * creates the panel
+   */
+  public DataGeneratorPanel() {
+    setLayout(new BorderLayout());
+   
+    add(new PropertyPanel(m_GeneratorEditor), BorderLayout.CENTER);
+
+    // editor
+    m_GeneratorEditor.setClassType(DataGenerator.class);
+    m_GeneratorEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	repaint();
+      }
+    });
+    
+    // set default generator
+    setGenerator(null);
+  }
+
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param value 	the Logger that will now get info messages
+   */
+  public void setLog(Logger value) {
+    m_Log = value;
+  }
+
+  /**
+   * returns the generated instances, null if the process was cancelled.
+   *
+   * @return the generated Instances
+   */
+  public Instances getInstances() {
+    return m_Instances;
+  }
+
+  /**
+   * returns the generated output as text
+   * 
+   * @return		the generated output
+   */
+  public String getOutput() {
+    return m_Output.toString();
+  }
+
+  /**
+   * sets the generator to use initially
+   * 
+   * @param value	the data generator to use
+   */
+  public void setGenerator(DataGenerator value) {
+    if (value != null)
+      m_GeneratorEditor.setValue(value);
+    else
+      m_GeneratorEditor.setValue(
+          new weka.datagenerators.classifiers.classification.RDG1());
+  }
+
+  /**
+   * returns the currently selected DataGenerator
+   * 
+   * @return		the current data generator
+   */
+  public DataGenerator getGenerator() {
+    return (DataGenerator) m_GeneratorEditor.getValue();
+  }
+
+  /**
+   * generates the instances, returns TRUE if successful
+   * 
+   * @return		TRUE if successful
+   * @see #getInstances()
+   */
+  public boolean execute() {
+    DataGenerator     generator;
+    boolean           result;
+    String            relName;
+    String            cname;
+    String            cmd;
+    
+    result    = true;
+    generator = (DataGenerator) m_GeneratorEditor.getValue();
+    relName   = generator.getRelationName();
+
+    cname = generator.getClass().getName().replaceAll(".*\\.", "");
+    cmd = generator.getClass().getName();
+    if (generator instanceof OptionHandler)
+      cmd += " " + Utils.joinOptions(((OptionHandler) generator).getOptions());
+    
+    try {
+      m_Log.logMessage("Started " + cname);
+      m_Log.logMessage("Command: " + cmd);
+      m_Output = new StringWriter();
+      generator.setOutput(new PrintWriter(m_Output));
+      DataGenerator.makeData(generator, generator.getOptions());
+      m_Instances = new Instances(new StringReader(getOutput()));
+      m_Log.logMessage("Finished " + cname);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      JOptionPane.showMessageDialog(
+          this, "Error generating data:\n" + e.getMessage(), 
+          "Error", JOptionPane.ERROR_MESSAGE);
+      m_Instances = null;
+      m_Output    = new StringWriter();
+      result      = false;
+    }
+
+    generator.setRelationName(relName);
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.java	(revision 29)
@@ -0,0 +1,423 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Explorer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Capabilities;
+import weka.core.Copyright;
+import weka.core.Instances;
+import weka.core.Memory;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.ConverterUtils;
+import weka.gui.LogPanel;
+import weka.gui.Logger;
+import weka.gui.LookAndFeel;
+import weka.gui.WekaTaskMonitor;
+
+import java.awt.BorderLayout;
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.event.ChangeEvent;
+
+/** 
+ * The main class for the Weka explorer. Lets the user create,
+ * open, save, configure, datasets, and perform ML analysis.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 4722 $
+ */
+public class Explorer
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7674003708867909578L;
+
+  /**
+   * Interface for classes that listen for filter changes.
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4722 $
+   */
+  public static interface CapabilitiesFilterChangeListener 
+    extends EventListener {
+    
+    /**
+     * method gets called in case of a change event
+     * 
+     * @param e		the associated change event
+     */
+    public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e);
+  }
+
+  /**
+   * This event can be fired in case the capabilities filter got changed 
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4722 $
+   */
+  public static class CapabilitiesFilterChangeEvent
+    extends ChangeEvent {
+
+    /** for serialization */
+    private static final long serialVersionUID = 1194260517270385559L;
+    
+    /** the capabilities filter */
+    protected Capabilities m_Filter;
+    
+    /**
+     * Constructs a GOECapabilitiesFilterChangeEvent object.
+     * 
+     * @param source	the Object that is the source of the event
+     * @param filter	the responsible capabilities filter
+     */
+    public CapabilitiesFilterChangeEvent(Object source, Capabilities filter) {
+      super(source);
+      m_Filter = filter;
+    }
+    
+    /**
+     * returns the associated Capabilities filter
+     * 
+     * @return		the filter
+     */
+    public Capabilities getFilter() {
+      return m_Filter;
+    }
+  }
+
+  /**
+   * A common interface for panels to be displayed in the Explorer
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4722 $
+   */
+  public static interface ExplorerPanel {
+
+    /**
+     * Sets the Explorer to use as parent frame (used for sending notifications
+     * about changes in the data)
+     * 
+     * @param parent	the parent frame
+     */
+    public void setExplorer(Explorer parent);
+    
+    /**
+     * returns the parent Explorer frame
+     * 
+     * @return		the parent
+     */
+    public Explorer getExplorer();
+    
+    /**
+     * Tells the panel to use a new set of instances.
+     *
+     * @param inst a set of Instances
+     */
+    public void setInstances(Instances inst);
+    
+    /**
+     * Returns the title for the tab in the Explorer
+     * 
+     * @return the title of this tab
+     */
+    public String getTabTitle();
+    
+    /**
+     * Returns the tooltip for the tab in the Explorer
+     * 
+     * @return the tooltip of this tab
+     */
+    public String getTabTitleToolTip();
+  }
+
+  /**
+   * A common interface for panels in the explorer that can handle logs
+   * 
+   * @author FracPete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 4722 $
+   */
+  public static interface LogHandler {
+    
+    /**
+     * Sets the Logger to receive informational messages
+     *
+     * @param newLog the Logger that will now get info messages
+     */
+    public void setLog(Logger newLog);
+  }
+
+  /** The panel for preprocessing instances */
+  protected PreprocessPanel m_PreprocessPanel = new PreprocessPanel();
+  
+  /** Contains all the additional panels apart from the pre-processing panel */
+  protected Vector<ExplorerPanel> m_Panels = new Vector<ExplorerPanel>();
+  
+  /** The tabbed pane that controls which sub-pane we are working with */
+  protected JTabbedPane m_TabbedPane = new JTabbedPane();
+  
+  /** The panel for log and status messages */
+  protected LogPanel m_LogPanel = new LogPanel(new WekaTaskMonitor());
+  
+  /** the listeners that listen to filter changes */
+  protected HashSet<CapabilitiesFilterChangeListener> m_CapabilitiesFilterChangeListeners = new HashSet<CapabilitiesFilterChangeListener>();
+  
+  /**
+   * Creates the experiment environment gui with no initial experiment
+   */
+  public Explorer() {
+    
+    String date = (new SimpleDateFormat("EEEE, d MMMM yyyy")).format(new Date());
+    m_LogPanel.logMessage("Weka Explorer");
+    m_LogPanel.logMessage("(c) " + Copyright.getFromYear() + "-" + Copyright.getToYear() 
+	+ " " + Copyright.getOwner() + ", " + Copyright.getAddress());
+    m_LogPanel.logMessage("web: " + Copyright.getURL());
+    m_LogPanel.logMessage("Started on " + date);
+    m_LogPanel.statusMessage("Welcome to the Weka Explorer");
+
+    // intialize pre-processpanel
+    m_PreprocessPanel.setLog(m_LogPanel);
+    m_TabbedPane.addTab(
+	m_PreprocessPanel.getTabTitle(),
+	null,
+	m_PreprocessPanel,
+	m_PreprocessPanel.getTabTitleToolTip());
+    
+    // initialize additional panels
+    String[] tabs = ExplorerDefaults.getTabs();
+    Hashtable<String, HashSet> tabOptions = new Hashtable<String, HashSet>();
+    for (int i = 0; i < tabs.length; i++) {
+      try {
+	// determine classname and additional options
+	String[] optionsStr = tabs[i].split(":");
+	String classname = optionsStr[0];
+	HashSet options = new HashSet();
+	tabOptions.put(classname, options);
+	for (int n = 1; n < optionsStr.length; n++)
+	  options.add(optionsStr[n]);
+	  
+	// setup panel
+	ExplorerPanel panel = (ExplorerPanel) Class.forName(classname).newInstance();
+	panel.setExplorer(this);
+	m_Panels.add(panel);
+	if (panel instanceof LogHandler)
+	  ((LogHandler) panel).setLog(m_LogPanel);
+	m_TabbedPane.addTab(
+	    panel.getTabTitle(), null, (JPanel) panel, panel.getTabTitleToolTip());
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+
+    // setup tabbed pane
+    m_TabbedPane.setSelectedIndex(0);
+    for (int i = 0; i < m_Panels.size(); i++) {
+      HashSet options = tabOptions.get(m_Panels.get(i).getClass().getName());
+      m_TabbedPane.setEnabledAt(i + 1, options.contains("standalone"));
+    }
+
+    // setup notification for dataset changes
+    m_PreprocessPanel.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+	for (int i = 0; i < m_Panels.size(); i++) {
+	   m_Panels.get(i).setInstances(m_PreprocessPanel.getInstances());
+	   m_TabbedPane.setEnabledAt(i + 1, true);
+	}
+      }
+    });
+
+    // add listeners for changes in the capabilities
+    m_PreprocessPanel.setExplorer(this);
+    addCapabilitiesFilterListener(m_PreprocessPanel);
+    for (int i = 0; i < m_Panels.size(); i++) {
+      if (m_Panels.get(i) instanceof CapabilitiesFilterChangeListener)
+	addCapabilitiesFilterListener((CapabilitiesFilterChangeListener) m_Panels.get(i));
+    }
+
+    // add components to layout
+    setLayout(new BorderLayout());
+    add(m_TabbedPane, BorderLayout.CENTER);
+    add(m_LogPanel, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * returns all the panels, apart from the PreprocessPanel
+   * 
+   * @return		the currently displayed panels w/o PreprocessPanel
+   */
+  public Vector<ExplorerPanel> getPanels() {
+    return m_Panels;
+  }
+  
+  /**
+   * returns the instance of the PreprocessPanel being used in this instance
+   * of the Explorer
+   * 
+   * @return		the panel
+   */
+  public PreprocessPanel getPreprocessPanel() {
+    return m_PreprocessPanel;
+  }
+  
+  /**
+   * returns the tabbed pane of the Explorer
+   * 
+   * @return		the tabbed pane
+   */
+  public JTabbedPane getTabbedPane() {
+    return m_TabbedPane;
+  }
+  
+  /**
+   * adds the listener to the list of objects that listen for changes of the
+   * CapabilitiesFilter
+   * 
+   * @param l		the listener to add
+   * @see		#m_CapabilitiesFilterChangeListeners
+   */
+  public void addCapabilitiesFilterListener(CapabilitiesFilterChangeListener l) {
+    m_CapabilitiesFilterChangeListeners.add(l);
+  }
+
+  /**
+   * Removes the specified listener from the set of listeners if it is present.
+   * 
+   * @param l		the listener to remove
+   * @return		true if the listener was registered
+   */
+  public boolean removeCapabilitiesFilterListener(CapabilitiesFilterChangeListener l) {
+    return m_CapabilitiesFilterChangeListeners.remove(l);
+  }
+  
+  /**
+   * notifies all the listeners of a change
+   * 
+   * @param filter	the affected filter
+   */
+  public void notifyCapabilitiesFilterListener(Capabilities filter) {
+    for (CapabilitiesFilterChangeListener l: m_CapabilitiesFilterChangeListeners) {
+      if (l == this)
+	continue;
+      l.capabilitiesFilterChanged(new CapabilitiesFilterChangeEvent(this, filter));
+    }
+  }
+  
+  /** variable for the Explorer class which would be set to null by the memory 
+      monitoring thread to free up some memory if we running out of memory
+   */
+  private static Explorer m_explorer;
+
+  /** for monitoring the Memory consumption */
+  private static Memory m_Memory = new Memory(true);
+
+  /**
+   * Tests out the explorer environment.
+   *
+   * @param args ignored.
+   */
+  public static void main(String [] args) {
+
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      m_explorer = new Explorer();
+      final JFrame jf = new JFrame("Weka Explorer");
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(m_explorer, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+      Image icon = Toolkit.getDefaultToolkit().
+      getImage(ClassLoader.getSystemResource("weka/gui/weka_icon.gif"));
+      jf.setIconImage(icon);
+
+      if (args.length == 1) {
+        System.err.println("Loading instances from " + args[0]);
+        AbstractFileLoader loader = ConverterUtils.getLoaderForFile(args[0]);
+	loader.setFile(new File(args[0]));
+        m_explorer.m_PreprocessPanel.setInstancesFromFile(loader);
+      }
+
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while(true) {
+            try {
+              //System.out.println("Before sleeping.");
+              this.sleep(4000);
+
+              System.gc();
+
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                jf.dispose();
+                m_explorer = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+
+                // display error
+                System.err.println("\ndisplayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nexiting");
+                System.exit(-1);
+              }
+
+            } catch(InterruptedException ex) { ex.printStackTrace(); }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.MAX_PRIORITY);
+      memMonitor.start();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/Explorer.props	(revision 29)
@@ -0,0 +1,182 @@
+# This props file contains default values for the Weka Explorer.
+#
+# Notes:
+# - backslashes within options, e.g., for the default "Classifier", need
+#   to be doubled (the backslashes get interpreted already when a property
+#   is read).
+#
+# Author  FracPete (fracpete at waikato dot ac dot nz)
+# Version $Revision: 6103 $
+
+# if set to true the Capabilities filters in the GOE will be initialized
+# based on the full dataset that has been loaded into the Explorer 
+# otherwise only the header (true|false)
+# Note: The tabs in the Explorer have their own class combobox, which means 
+#       that the data has to be inspected several times (changing the class
+#       combobox only leads to an inspection of the data in the current tab), 
+#       which can be slow on big datasets.
+InitGenericObjectEditorFilter=True
+
+# The tabs to display apart from the PreprocessPanel.
+#
+# The classes listed here must import the weka.gui.explorer.Explorer.ExplorerPanel
+# interface. Optionally, they can also import the 
+# weka.gui.explorer.Explorer.LogHandler interface if they want to use the logging
+# functionality of the Explorer and the 
+# weka.gui.exporer.Explorer.CapabilitiesFilterChangeListener interface 
+# in case they need to know when the Capabilities have changed, e.g., when a 
+# new dataset has been loaded into the Explorer.
+#
+# Additional options follow the classname after a colon.
+# Currently supported options are:
+#   standalone - the tab does not depend on the PreprocessPanel to load the data first
+#
+Tabs=weka.gui.explorer.ClassifierPanel,\
+     weka.gui.explorer.ClustererPanel,\
+     weka.gui.explorer.AssociationsPanel,\
+     weka.gui.explorer.AttributeSelectionPanel,\
+     weka.gui.explorer.VisualizePanel
+
+# the initial directory for opening datasets.
+# the following placeholders are recognized
+#   %t - the temp directory
+#   %h - the user's home directory
+#   %c - the current directory
+#   %% - gets replaced by a single percentage sign
+InitialDirectory=%c
+
+# the default filter, including options (can be left empty)
+Filter=
+
+# the default classifier in the classify tab, including options
+# (default is ZeroR)
+Classifier=weka.classifiers.rules.ZeroR
+
+# the default test mode in the classify tab 
+# (according to "testMode" variable in startClassifier method)
+# 1 - cross-validation
+# 2 - percentage split
+# 3 - use training set
+# 4 - supplied test set
+# (default is 1 - CV)
+ClassifierTestMode=1
+
+# the default number of folds for CV in the classify tab 
+# (default is 10)
+ClassifierCrossvalidationFolds=10
+
+# the default percentage split % in the classify tab (integer: 1-99)
+# (default is 66)
+ClassifierPercentageSplit=66
+
+# whether the classifier model is output (true|false)
+# (default is true)
+ClassifierOutputModel=true
+
+# whether additional per-class stats of the classifier model are 
+# output (true|false)
+# (default is true)
+ClassifierOutputPerClassStats=true
+
+# whether the entropy based evaluation measures of the classifier model are 
+# output (true|false)
+# (default is false)
+ClassifierOutputEntropyEvalMeasures=false
+
+# whether the confusion matrix is output for the classifier (true|false)
+# (default is true)
+ClassifierOutputConfusionMatrix=true
+
+# whether the predictions of the classifier are stored for visulization 
+# purposes (true|false)
+# (default is true)
+ClassifierStorePredictionsForVis=true
+
+# whether the predictions of the classifier output as well (true|false)
+# (default is false)
+ClassifierOutputPredictions=false
+
+# lists the attributes indices to output in addition to the predictions
+# (default is "")
+ClassifierOutputAdditionalAttributes=
+
+# whether the evaluation of the classifier is done cost-sensitively (true|false)
+# (default is false)
+# Note: a cost matrix still has to be provided!
+ClassifierCostSensitiveEval=false
+
+# the default random seed in the classify tab
+# (default is 1)
+ClassifierRandomSeed=1
+
+# whether the order is preserved in case of percentage split in the classifier 
+# tab 
+# (default is false)
+ClassifierPreserveOrder=false
+
+# whether the source code of a Sourcable classifier is output as well in the 
+# classifier tab 
+# (default is false)
+ClassifierOutputSourceCode=false
+
+# the default classname of a Sourcable classifier in the classifier tab 
+# (default is Foobar)
+ClassifierSourceCodeClass=WekaClassifier
+
+# the class (incl. options) for collecting the predictions and turning them 
+# into plotable instances for displaying the classifier errors.
+ClassifierErrorsPlotInstances=weka.gui.explorer.ClassifierErrorsPlotInstances
+
+# The minimum plot size for numeric attributes (when visualizing classifier errors)
+ClassifierErrorsMinimumPlotSizeNumeric=1
+
+# The maximum plot size for numeric attributes (when visualizing classifier errors)
+ClassifierErrorsMaximumPlotSizeNumeric=20
+
+# the default clusterer, including options
+# (default is EM)
+Clusterer=weka.clusterers.EM
+
+# the default test mode in the cluster tab 
+# (according to "testMode" variable in startClusterer method)
+# 2 - percentage split
+# 3 - use training set
+# 4 - supplied test set
+# 5 - classes to clusters evaluation
+# (default is 3 - training set)
+ClustererTestMode=3
+
+# whether the clusters are stored for visualization purposes (true|false)
+# (default is true)
+ClustererStoreClustersForVis=true
+
+# the class (incl. options) for collecting the predictions and turning them 
+# into plotable instances for displaying the cluster assignments.
+ClustererAssignmentsPlotInstances=weka.gui.explorer.ClustererAssignmentsPlotInstances
+
+# the default associator, including options
+# (default is Apriori)
+Associator=weka.associations.Apriori
+
+# the default attribute evaluator, including options
+# (default is CfsSubsetEval)
+ASEvaluation=weka.attributeSelection.CfsSubsetEval
+
+# the default attribute selection search scheme, including options
+# (default is BestFirst)
+ASSearch=weka.attributeSelection.BestFirst
+
+# the default test mode in the attribute selection tab 
+# (according to "testMode" variable in startAttributeSelection method)
+# 0 - use full training set
+# 1 - cross-validation
+# (default is 0 - full training set)
+ASTestMode=0
+
+# the default number of folds for CV in the attribute selection tab 
+# (default is 10)
+ASCrossvalidationFolds=10
+
+# the default random seed in the attribute selection tab
+# (default is 1)
+ASRandomSeed=1
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/ExplorerDefaults.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/ExplorerDefaults.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/ExplorerDefaults.java	(revision 29)
@@ -0,0 +1,570 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ExplorerDefaults.java
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Utils;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * This class offers get methods for the default Explorer settings in 
+ * the props file <code>weka/gui/explorer/Explorer.props</code>.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6103 $
+ * @see #PROPERTY_FILE
+ */
+public class ExplorerDefaults
+  implements Serializable {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 4954795757927524225L;
+
+  /** The name of the properties file. */
+  public final static String PROPERTY_FILE = "weka/gui/explorer/Explorer.props";
+
+  /** Properties associated with the explorer options. */
+  protected static Properties PROPERTIES;
+  static {
+    try {
+      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+    }
+    catch (Exception e) {
+      System.err.println("Problem reading properties. Fix before continuing.");
+      e.printStackTrace();
+      PROPERTIES = new Properties();
+    }
+  }
+
+  /**
+   * returns the value for the specified property, if non-existent then the
+   * default value.
+   *
+   * @param property      the property to retrieve the value for
+   * @param defaultValue  the default value for the property
+   * @return              the value of the specified property
+   */
+  public static String get(String property, String defaultValue) {
+    return PROPERTIES.getProperty(property, defaultValue);
+  }
+
+  /**
+   * returns the associated properties file.
+   * 
+   * @return              the props file
+   */
+  public final static Properties getProperties() {
+    return PROPERTIES;
+  }
+  
+  /**
+   * Tries to instantiate the class stored for this property, optional 
+   * options will be set as well. Returns null if unsuccessful.
+   * 
+   * @param property      the property to get the object for
+   * @param defaultValue  the default object spec string
+   * @return              if successful the fully configured object, null
+   *                      otherwise
+   */
+  protected static Object getObject(String property, String defaultValue) {
+    return getObject(property, defaultValue, Object.class);
+  }
+  
+  /**
+   * Tries to instantiate the class stored for this property, optional 
+   * options will be set as well. Returns null if unsuccessful.
+   * 
+   * @param property      the property to get the object for
+   * @param defaultValue  the default object spec string
+   * @param cls           the class the object must be derived from
+   * @return              if successful the fully configured object, null
+   *                      otherwise
+   */
+  protected static Object getObject(String property, String defaultValue, Class cls) {
+    Object	result;
+    String	tmpStr;
+    String[]	tmpOptions;
+    
+    result = null;
+    
+    try {
+      tmpStr     = get(property, defaultValue);
+      tmpOptions = Utils.splitOptions(tmpStr);
+      if (tmpOptions.length != 0) {
+	tmpStr        = tmpOptions[0];
+	tmpOptions[0] = "";
+	result        = Utils.forName(cls, tmpStr, tmpOptions);
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns if the GOEs in the Explorer will be initialized based on the
+   * data that is loaded into the Explorer.
+   * 
+   * @return		true if the GOEs get initialized
+   */
+  public static boolean getInitGenericObjectEditorFilter() {
+    return Boolean.parseBoolean(get("InitGenericObjectEditorFilter", "false"));
+  }
+  
+  /**
+   * returns an array with the classnames of all the additional panels to 
+   * display as tabs in the Explorer.
+   * 
+   * @return		the classnames
+   */
+  public static String[] getTabs() {
+    String[]	result;
+    String	tabs;
+    
+    // read and split on comma
+    tabs   = get("Tabs", "weka.gui.explorer.ClassifierPanel,weka.gui.explorer.ClustererPanel,weka.gui.explorer.AssociationsPanel,weka.gui.explorer.AttributeSelectionPanel,weka.gui.explorer.VisualizePanel");
+    result = tabs.split(",");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the initial directory for the file chooser used for opening
+   * datasets. 
+   * <p/>
+   * The following placeholders are recognized:
+   * <pre>
+   *   %t - the temp directory
+   *   %h - the user's home directory
+   *   %c - the current directory
+   *   %% - gets replaced by a single percentage sign
+   * </pre>
+   * 
+   * @return		the default directory
+   */
+  public static String getInitialDirectory() {
+    String	result;
+    
+    result = get("InitialDirectory", "%c");
+    result = result.replaceAll("%t", System.getProperty("java.io.tmpdir"));
+    result = result.replaceAll("%h", System.getProperty("user.home"));
+    result = result.replaceAll("%c", System.getProperty("user.dir"));
+    result = result.replaceAll("%%", System.getProperty("%"));
+    
+    return result;
+  }
+  
+  /**
+   * returns the default filter (fully configured) for the preprocess panel.
+   * 
+   * @return		the default filter, null if none
+   */
+  public static Object getFilter() {
+    return getObject("Filter", "", weka.filters.Filter.class);
+  }
+  
+  /**
+   * returns the default classifier (fully configured) for the classify panel.
+   * 
+   * @return		the default classifier, ZeroR by default
+   */
+  public static Object getClassifier() {
+    Object	result;
+    
+    result = getObject(
+		"Classifier", 
+		weka.classifiers.rules.ZeroR.class.getName(), 
+		weka.classifiers.Classifier.class);
+    if (result == null)
+      result = new weka.classifiers.rules.ZeroR();
+    
+    return result;
+  }
+  
+  /**
+   * returns the default classifier test mode for the classify panel.
+   * 
+   * @return		the default test mode
+   */
+  public static int getClassifierTestMode() {
+    return Integer.parseInt(get("ClassifierTestMode", "1"));
+  }
+  
+  /**
+   * returns the default number of folds of the CV in the classify panel.
+   * 
+   * @return		the default number of folds
+   */
+  public static int getClassifierCrossvalidationFolds() {
+    return Integer.parseInt(get("ClassifierCrossvalidationFolds", "10"));
+  }
+  
+  /**
+   * returns the default classifier test mode for the classify panel (0-99).
+   * 
+   * @return		the default precentage split
+   */
+  public static int getClassifierPercentageSplit() {
+    return Integer.parseInt(get("ClassifierPercentageSplit", "66"));
+  }
+
+  /**
+   * returns whether the built model is output.
+   * 
+   * @return		true if the built model is output
+   */
+  public static boolean getClassifierOutputModel() {
+    return Boolean.parseBoolean(get("ClassifierOutputModel", "true"));
+  }
+
+  /**
+   * returns whether additional per-class stats of the classifier are output.
+   * 
+   * @return		true if stats are output
+   */
+  public static boolean getClassifierOutputPerClassStats() {
+    return Boolean.parseBoolean(get("ClassifierOutputPerClassStats", "true"));
+  }
+
+  /**
+   * returns whether entropy-based evaluation meastures of the classifier 
+   * are output.
+   * 
+   * @return		true if output
+   */
+  public static boolean getClassifierOutputEntropyEvalMeasures() {
+    return Boolean.parseBoolean(get("ClassifierOutputEntropyEvalMeasures", "false"));
+  }
+
+  /**
+   * returns whether the confusion matrix for the classifier is output.
+   * 
+   * @return		true if matrix is output
+   */
+  public static boolean getClassifierOutputConfusionMatrix() {
+    return Boolean.parseBoolean(get("ClassifierOutputConfusionMatrix", "true"));
+  }
+
+  /**
+   * returns whether the predictions of the classifier are output as well.
+   * 
+   * @return		true if predictions are output as well
+   */
+  public static boolean getClassifierOutputPredictions() {
+    return Boolean.parseBoolean(get("ClassifierOutputPredictions", "false"));
+  }
+
+  /**
+   * returns the string with the additional indices to output alongside the
+   * predictions. 
+   * 
+   * @return		the indices, 0 if none are output
+   */
+  public static String getClassifierOutputAdditionalAttributes() {
+    return get("ClassifierOutputAdditionalAttributes", "");
+  }
+
+  /**
+   * returns whether the predictions of the classifier are stored for 
+   * visualization.
+   * 
+   * @return		true if predictions are stored
+   */
+  public static boolean getClassifierStorePredictionsForVis() {
+    return Boolean.parseBoolean(get("ClassifierStorePredictionsForVis", "true"));
+  }
+
+  /**
+   * returns whether the evaluation of the classifier is done cost-sensitively.
+   * 
+   * @return		true if cost-sensitively done
+   */
+  public static boolean getClassifierCostSensitiveEval() {
+    return Boolean.parseBoolean(get("ClassifierCostSensitiveEval", "false"));
+  }
+  
+  /**
+   * returns the default random seed value for the classifier for the 
+   * classify panel.
+   * 
+   * @return		the default random seed
+   */
+  public static int getClassifierRandomSeed() {
+    return Integer.parseInt(get("ClassifierRandomSeed", "1"));
+  }
+
+  /**
+   * returns whether the order is preserved in case of the percentage split 
+   * in the classify tab.
+   * 
+   * @return		true if order is preserved
+   */
+  public static boolean getClassifierPreserveOrder() {
+    return Boolean.parseBoolean(get("ClassifierPreserveOrder", "false"));
+  }
+
+  /**
+   * returns whether the source of a sourcable Classifier is output
+   * in the classify tab.
+   * 
+   * @return		true if the source code is output
+   */
+  public static boolean getClassifierOutputSourceCode() {
+    return Boolean.parseBoolean(get("ClassifierOutputSourceCode", "false"));
+  }
+
+  /**
+   * returns the default classname for a sourcable Classifier in the classify tab.
+   * 
+   * @return		the default classname
+   */
+  public static String getClassifierSourceCodeClass() {
+    return get("ClassifierSourceCodeClass", "Foobar");
+  }
+  
+  /**
+   * Returns an instance of the class used for generating plot instances
+   * for displaying the classifier errors.
+   * 
+   * @return		an instance of the class
+   */
+  public static ClassifierErrorsPlotInstances getClassifierErrorsPlotInstances() {
+    ClassifierErrorsPlotInstances	result;
+    String			classname;
+    String[]			options;
+    
+    try {
+      options = Utils.splitOptions(get("ClassifierErrorsPlotInstances", "weka.gui.explorer.ClassifierErrorsPlotInstances"));
+      classname  = options[0];
+      options[0] = "";
+      result     = (ClassifierErrorsPlotInstances) Utils.forName(ClassifierErrorsPlotInstances.class, classname, options);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = new ClassifierErrorsPlotInstances();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns the minimum size in pixels for plots of plotting classifier errors 
+   * of numeric attributes.
+   * 
+   * @return		the size
+   */
+  public static int getClassifierErrorsMinimumPlotSizeNumeric() {
+    return Integer.parseInt(get("ClassifierErrorsMinimumPlotSizeNumeric", "1"));
+  }
+  
+  /**
+   * Returns the maximum size in pixels for plots of plotting classifier errors 
+   * of numeric attributes.
+   * 
+   * @return		the size
+   */
+  public static int getClassifierErrorsMaximumPlotSizeNumeric() {
+    return Integer.parseInt(get("ClassifierErrorsMaximumPlotSizeNumeric", "20"));
+  }
+  
+  /**
+   * returns the default clusterer (fully configured) for the clusterer panel.
+   * 
+   * @return		the default clusterer, EM by default
+   */
+  public static Object getClusterer() {
+    Object	result;
+    
+    result = getObject(
+		"Clusterer", 
+		weka.clusterers.EM.class.getName(), 
+		weka.clusterers.Clusterer.class);
+    if (result == null)
+      result = new weka.clusterers.EM();
+    
+    return result;
+  }
+  
+  /**
+   * returns the default cluster test mode for the cluster panel.
+   * 
+   * @return		the default test mode
+   */
+  public static int getClustererTestMode() {
+    return Integer.parseInt(get("ClustererTestMode", "3"));
+  }
+
+  /**
+   * returns whether the clusters are storeed for visualization purposes
+   * in the cluster panel.
+   * 
+   * @return		true if clusters are stored
+   */
+  public static boolean getClustererStoreClustersForVis() {
+    return Boolean.parseBoolean(get("ClustererStoreClustersForVis", "true"));
+  }
+  
+  /**
+   * Returns an instance of the class used for generating plot instances
+   * for displaying the cluster assignments.
+   * 
+   * @return		an instance of the class
+   */
+  public static ClustererAssignmentsPlotInstances getClustererAssignmentsPlotInstances() {
+    ClustererAssignmentsPlotInstances	result;
+    String			classname;
+    String[]			options;
+    
+    try {
+      options = Utils.splitOptions(get("ClustererAssignmentsPlotInstances", "weka.gui.explorer.ClustererAssignmentsPlotInstances"));
+      classname  = options[0];
+      options[0] = "";
+      result     = (ClustererAssignmentsPlotInstances) Utils.forName(ClustererAssignmentsPlotInstances.class, classname, options);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = new ClustererAssignmentsPlotInstances();
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the default associator (fully configured) for the associations panel.
+   * 
+   * @return		the default associator, Apriori by default
+   */
+  public static Object getAssociator() {
+    Object	result;
+    
+    result = getObject(
+		"Associator", 
+		weka.associations.Apriori.class.getName(), 
+		weka.associations.Associator.class);
+    if (result == null)
+      result = new weka.associations.Apriori();
+    
+    return result;
+  }
+  
+  /**
+   * returns the default attribute evalautor (fully configured) for the 
+   * attribute selection panel.
+   * 
+   * @return		the default attribute evaluator, CfsSubsetEval by default
+   */
+  public static Object getASEvaluator() {
+    Object	result;
+    
+    result = getObject(
+		"ASEvaluation", 
+		weka.attributeSelection.CfsSubsetEval.class.getName(), 
+		weka.attributeSelection.ASEvaluation.class);
+    if (result == null)
+      result = new weka.attributeSelection.CfsSubsetEval();
+    
+    return result;
+  }
+  
+  /**
+   * returns the default attribute selection search scheme (fully configured) 
+   * for the attribute selection panel.
+   * 
+   * @return		the default search scheme, BestFirst by default
+   */
+  public static Object getASSearch() {
+    Object	result;
+    
+    result = getObject(
+		"ASSearch", 
+		weka.attributeSelection.BestFirst.class.getName(), 
+		weka.attributeSelection.ASSearch.class);
+    if (result == null)
+      result = new weka.attributeSelection.BestFirst();
+    
+    return result;
+  }
+  
+  /**
+   * returns the default attribute selection test mode for the attribute
+   * selection panel.
+   * 
+   * @return		the default test mode
+   */
+  public static int getASTestMode() {
+    return Integer.parseInt(get("ASTestMode", "0"));
+  }
+  
+  /**
+   * returns the default number of folds of the CV in the attribute selection 
+   * panel.
+   * 
+   * @return		the default number of folds
+   */
+  public static int getASCrossvalidationFolds() {
+    return Integer.parseInt(get("ASCrossvalidationFolds", "10"));
+  }
+  
+  /**
+   * returns the default random seed value in the attribute selection panel.
+   * 
+   * @return		the default random seed
+   */
+  public static int getASRandomSeed() {
+    return Integer.parseInt(get("ASRandomSeed", "1"));
+  }
+  
+  /**
+   * only for testing - prints the content of the props file.
+   * 
+   * @param args	commandline parameters - ignored
+   */
+  public static void main(String[] args) {
+    Enumeration		names;
+    String		name;
+    Vector		sorted;
+    
+    System.out.println("\nExplorer defaults:");
+    names = PROPERTIES.propertyNames();
+
+    // sort names
+    sorted = new Vector();
+    while (names.hasMoreElements())
+      sorted.add(names.nextElement());
+    Collections.sort(sorted);
+    names = sorted.elements();
+    
+    // output
+    while (names.hasMoreElements()) {
+      name = names.nextElement().toString();
+      System.out.println("- " + name + ": " + PROPERTIES.getProperty(name, ""));
+    }
+    System.out.println();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/PreprocessPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/PreprocessPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/PreprocessPanel.java	(revision 29)
@@ -0,0 +1,1433 @@
+ /*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PreprocessPanel.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Capabilities;
+import weka.core.CapabilitiesHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+import weka.core.converters.AbstractFileLoader;
+import weka.core.converters.AbstractFileSaver;
+import weka.core.converters.ConverterUtils;
+import weka.core.converters.Loader;
+import weka.core.converters.SerializedInstancesLoader;
+import weka.core.converters.URLSourcedLoader;
+import weka.datagenerators.DataGenerator;
+import weka.experiment.InstanceQuery;
+import weka.filters.Filter;
+import weka.filters.SupervisedFilter;
+import weka.filters.unsupervised.attribute.Remove;
+import weka.gui.AttributeSelectionPanel;
+import weka.gui.AttributeSummaryPanel;
+import weka.gui.AttributeVisualizationPanel;
+import weka.gui.ConverterFileChooser;
+import weka.gui.GenericObjectEditor;
+import weka.gui.InstancesSummaryPanel;
+import weka.gui.Logger;
+import weka.gui.PropertyDialog;
+import weka.gui.PropertyPanel;
+import weka.gui.SysErrLog;
+import weka.gui.TaskLogger;
+import weka.gui.ViewerDialog;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeEvent;
+import weka.gui.explorer.Explorer.CapabilitiesFilterChangeListener;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.explorer.Explorer.LogHandler;
+import weka.gui.sql.SqlViewerDialog;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel controls simple preprocessing of instances. Summary
+ * information on instances and attributes is shown. Filters may be
+ * configured to alter the set of instances. Altered instances may
+ * also be saved.
+ *
+ * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 5508 $
+ */
+public class PreprocessPanel
+  extends JPanel 
+  implements CapabilitiesFilterChangeListener, ExplorerPanel, LogHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6764850273874813049L;
+  
+  /** Displays simple stats on the working instances */
+  protected InstancesSummaryPanel m_InstSummaryPanel =
+    new InstancesSummaryPanel();
+
+  /** Click to load base instances from a file */
+  protected JButton m_OpenFileBut = new JButton("Open file...");
+
+  /** Click to load base instances from a URL */
+  protected JButton m_OpenURLBut = new JButton("Open URL...");
+
+  /** Click to load base instances from a Database */
+  protected JButton m_OpenDBBut = new JButton("Open DB...");
+
+  /** Click to generate artificial data */
+  protected JButton m_GenerateBut = new JButton("Generate...");
+
+  /** Click to revert back to the last saved point */
+  protected JButton m_UndoBut = new JButton("Undo");
+
+  /** Click to open the current instances in a viewer */
+  protected JButton m_EditBut = new JButton("Edit...");
+
+  /** Click to apply filters and save the results */
+  protected JButton m_SaveBut = new JButton("Save...");
+  
+  /** Panel to let the user toggle attributes */
+  protected AttributeSelectionPanel m_AttPanel = new AttributeSelectionPanel();
+
+  /** Button for removing attributes */
+  protected JButton m_RemoveButton = new JButton("Remove");
+
+  /** Displays summary stats on the selected attribute */
+  protected AttributeSummaryPanel m_AttSummaryPanel =
+    new AttributeSummaryPanel();
+
+  /** Lets the user configure the filter */
+  protected GenericObjectEditor m_FilterEditor =
+    new GenericObjectEditor();
+
+  /** Filter configuration */
+  protected PropertyPanel m_FilterPanel = new PropertyPanel(m_FilterEditor);
+
+  /** Click to apply filters and save the results */
+  protected JButton m_ApplyFilterBut = new JButton("Apply");
+
+  /** The file chooser for selecting data files */
+  protected ConverterFileChooser m_FileChooser 
+    = new ConverterFileChooser(new File(ExplorerDefaults.getInitialDirectory()));
+
+  /** Stores the last URL that instances were loaded from */
+  protected String m_LastURL = "http://";
+  
+  /** Stores the last sql query executed */
+  protected String m_SQLQ = new String("SELECT * FROM ?");
+ 
+  /** The working instances */
+  protected Instances m_Instances;
+
+  /** The last generator that was selected */
+  protected DataGenerator m_DataGenerator = null;
+
+  /** The visualization of the attribute values */
+  protected AttributeVisualizationPanel m_AttVisualizePanel = 
+    new AttributeVisualizationPanel();
+
+  /** Keeps track of undo points */
+  protected File[] m_tempUndoFiles = new File[20]; // set number of undo ops here
+
+  /** The next available slot for an undo point */
+  protected int m_tempUndoIndex = 0;
+  
+  /**
+   * Manages sending notifications to people when we change the set of
+   * working instances.
+   */
+  protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
+
+  /** A thread for loading/saving instances from a file or URL */
+  protected Thread m_IOThread;
+
+  /** The message logger */
+  protected Logger m_Log = new SysErrLog();
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+  
+  static {
+     GenericObjectEditor.registerEditors();
+  }
+  
+  /**
+   * Creates the instances panel with no initial instances.
+   */
+  public PreprocessPanel() {
+
+    // Create/Configure/Connect components
+    m_FilterEditor.setClassType(weka.filters.Filter.class);
+    if (ExplorerDefaults.getFilter() != null)
+      m_FilterEditor.setValue(ExplorerDefaults.getFilter());
+    
+    m_FilterEditor.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent e) {
+        m_ApplyFilterBut.setEnabled(true);
+        Capabilities currentCapabilitiesFilter = m_FilterEditor.getCapabilitiesFilter();
+        Filter filter = (Filter) m_FilterEditor.getValue();
+        Capabilities currentFilterCapabilities = null;
+        if (filter != null && currentCapabilitiesFilter != null &&
+            (filter instanceof CapabilitiesHandler)) {
+          currentFilterCapabilities = ((CapabilitiesHandler)filter).getCapabilities();
+          
+          if (!currentFilterCapabilities.supportsMaybe(currentCapabilitiesFilter) &&
+              !currentFilterCapabilities.supports(currentCapabilitiesFilter)) {
+            m_ApplyFilterBut.setEnabled(false);
+          }
+        }
+      }
+    });
+    m_OpenFileBut.setToolTipText("Open a set of instances from a file");
+    m_OpenURLBut.setToolTipText("Open a set of instances from a URL");
+    m_OpenDBBut.setToolTipText("Open a set of instances from a database");
+    m_GenerateBut.setToolTipText("Generates artificial data");
+    m_UndoBut.setToolTipText("Undo the last change to the dataset");
+    m_EditBut.setToolTipText("Open the current dataset in a Viewer for editing");
+    m_SaveBut.setToolTipText("Save the working relation to a file");
+    m_ApplyFilterBut.setToolTipText("Apply the current filter to the data");
+
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+    m_OpenURLBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setInstancesFromURLQ();
+      }
+    });
+    m_OpenDBBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        SqlViewerDialog dialog = new SqlViewerDialog(null);
+        dialog.setVisible(true);
+        if (dialog.getReturnValue() == JOptionPane.OK_OPTION)
+          setInstancesFromDBQ(dialog.getURL(), dialog.getUser(),
+                              dialog.getPassword(), dialog.getQuery(),
+                              dialog.getGenerateSparseData());
+      }
+    });
+    m_OpenFileBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	setInstancesFromFileQ();
+      }
+    });
+    m_GenerateBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	generateInstances();
+      }
+    });
+    m_UndoBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	undo();
+      }
+    });
+    m_EditBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        edit();
+      }
+    });
+    m_SaveBut.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+	saveWorkingInstancesToFileQ();
+      }
+    });
+    m_ApplyFilterBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  applyFilter((Filter) m_FilterEditor.getValue());
+	}
+      });
+    m_AttPanel.getSelectionModel()
+      .addListSelectionListener(new ListSelectionListener() {
+	public void valueChanged(ListSelectionEvent e) {
+	  if (!e.getValueIsAdjusting()) {	  
+	    ListSelectionModel lm = (ListSelectionModel) e.getSource();
+	    for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
+	      if (lm.isSelectedIndex(i)) {
+		m_AttSummaryPanel.setAttribute(i);
+		m_AttVisualizePanel.setAttribute(i);
+		break;
+	      }
+	    }
+	  }
+	}
+    });
+
+
+    m_InstSummaryPanel.setBorder(BorderFactory
+				 .createTitledBorder("Current relation"));
+    JPanel attStuffHolderPanel = new JPanel();
+    attStuffHolderPanel.setBorder(BorderFactory
+				  .createTitledBorder("Attributes"));
+    attStuffHolderPanel.setLayout(new BorderLayout());
+    attStuffHolderPanel.add(m_AttPanel, BorderLayout.CENTER);
+    m_RemoveButton.setEnabled(false);
+    m_RemoveButton.setToolTipText("Remove selected attributes.");
+    m_RemoveButton.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  try {
+	    Remove r = new Remove();
+	    int [] selected = m_AttPanel.getSelectedAttributes();
+	    if (selected.length == 0) {
+	      return;
+	    }
+	    if (selected.length == m_Instances.numAttributes()) {
+	      // Pop up an error optionpane
+	      JOptionPane.showMessageDialog(PreprocessPanel.this,
+					    "Can't remove all attributes from data!\n",
+					    "Remove Attributes",
+					    JOptionPane.ERROR_MESSAGE);
+	      m_Log.logMessage("Can't remove all attributes from data!");
+	      m_Log.statusMessage("Problem removing attributes");
+	      return;
+	    }
+	    r.setAttributeIndicesArray(selected);
+	    applyFilter(r);
+	  } catch (Exception ex) {
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskFinished();
+	    }
+	    // Pop up an error optionpane
+	    JOptionPane.showMessageDialog(PreprocessPanel.this,
+					  "Problem filtering instances:\n"
+					  + ex.getMessage(),
+					  "Remove Attributes",
+					  JOptionPane.ERROR_MESSAGE);
+	    m_Log.logMessage("Problem removing attributes: " + ex.getMessage());
+	    m_Log.statusMessage("Problem removing attributes");
+	  }
+	}
+      });
+
+    JPanel p1 = new JPanel();
+    p1.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    p1.setLayout(new BorderLayout());
+    p1.add(m_RemoveButton, BorderLayout.CENTER);
+    attStuffHolderPanel.add(p1, BorderLayout.SOUTH);
+    m_AttSummaryPanel.setBorder(BorderFactory
+		    .createTitledBorder("Selected attribute"));
+    m_UndoBut.setEnabled(false);
+    m_EditBut.setEnabled(false);
+    m_SaveBut.setEnabled(false);
+    m_ApplyFilterBut.setEnabled(false);
+    
+    // Set up the GUI layout
+    JPanel buttons = new JPanel();
+    buttons.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    buttons.setLayout(new GridLayout(1, 6, 5, 5));
+    buttons.add(m_OpenFileBut);
+    buttons.add(m_OpenURLBut);
+    buttons.add(m_OpenDBBut);
+    buttons.add(m_GenerateBut);
+    buttons.add(m_UndoBut);
+    buttons.add(m_EditBut);
+    buttons.add(m_SaveBut);
+
+    JPanel attInfo = new JPanel();
+
+    attInfo.setLayout(new BorderLayout());
+    attInfo.add(attStuffHolderPanel, BorderLayout.CENTER);
+
+    JPanel filter = new JPanel();
+    filter.setBorder(BorderFactory
+		    .createTitledBorder("Filter"));
+    filter.setLayout(new BorderLayout());
+    filter.add(m_FilterPanel, BorderLayout.CENTER);
+    filter.add(m_ApplyFilterBut, BorderLayout.EAST); 
+
+    JPanel attVis = new JPanel();
+    attVis.setLayout( new GridLayout(2,1) );
+    attVis.add(m_AttSummaryPanel);
+
+    JComboBox colorBox = m_AttVisualizePanel.getColorBox();
+    colorBox.setToolTipText("The chosen attribute will also be used as the " +
+			    "class attribute when a filter is applied.");
+    colorBox.addItemListener(new ItemListener() {
+      public void itemStateChanged(ItemEvent ie) {
+	if (ie.getStateChange() == ItemEvent.SELECTED) {
+	  updateCapabilitiesFilter(m_FilterEditor.getCapabilitiesFilter());
+	}
+      }
+    });
+    final JButton visAllBut = new JButton("Visualize All");
+    visAllBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent ae) {
+	  if (m_Instances != null) {
+	    try {
+	      final weka.gui.beans.AttributeSummarizer as = 
+		new weka.gui.beans.AttributeSummarizer();
+	      as.setColoringIndex(m_AttVisualizePanel.getColoringIndex());
+	      as.setInstances(m_Instances);
+	      
+	      final javax.swing.JFrame jf = new javax.swing.JFrame();
+	      jf.getContentPane().setLayout(new java.awt.BorderLayout());
+	      
+	      jf.getContentPane().add(as, java.awt.BorderLayout.CENTER);
+	      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+		  public void windowClosing(java.awt.event.WindowEvent e) {
+		    visAllBut.setEnabled(true);
+		    jf.dispose();
+		  }
+		});
+	      jf.setSize(830,600);
+	      jf.setVisible(true);
+	    } catch (Exception ex) {
+	      ex.printStackTrace();
+	    }
+	  }
+	}
+      });
+    JPanel histoHolder = new JPanel();
+    histoHolder.setLayout(new BorderLayout());
+    histoHolder.add(m_AttVisualizePanel, BorderLayout.CENTER);
+    JPanel histoControls = new JPanel();
+    histoControls.setLayout(new BorderLayout());
+    histoControls.add(colorBox, BorderLayout.CENTER);
+    histoControls.add(visAllBut, BorderLayout.EAST);
+    histoHolder.add(histoControls, BorderLayout.NORTH);
+    attVis.add(histoHolder);
+
+    JPanel lhs = new JPanel();
+    lhs.setLayout(new BorderLayout());
+    lhs.add(m_InstSummaryPanel, BorderLayout.NORTH);
+    lhs.add(attInfo, BorderLayout.CENTER);
+
+    JPanel rhs = new JPanel();
+    rhs.setLayout(new BorderLayout());
+    rhs.add(attVis, BorderLayout.CENTER);
+
+    JPanel relation = new JPanel();
+    relation.setLayout(new GridLayout(1, 2));
+    relation.add(lhs);
+    relation.add(rhs);
+
+    JPanel middle = new JPanel();
+    middle.setLayout(new BorderLayout());
+    middle.add(filter, BorderLayout.NORTH);
+    middle.add(relation, BorderLayout.CENTER);
+
+    setLayout(new BorderLayout());
+    add(buttons, BorderLayout.NORTH);
+    add(middle, BorderLayout.CENTER);
+  }
+
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+
+    m_Log = newLog;
+  }
+  
+  /**
+   * Tells the panel to use a new base set of instances.
+   *
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+
+    m_Instances = inst;
+    try {
+      Runnable r = new Runnable() {
+	public void run() {
+	  m_InstSummaryPanel.setInstances(m_Instances);
+	  m_AttPanel.setInstances(m_Instances);
+	  m_RemoveButton.setEnabled(true);
+	  m_AttSummaryPanel.setInstances(m_Instances);
+	  m_AttVisualizePanel.setInstances(m_Instances);
+
+	  // select the first attribute in the list
+	  m_AttPanel.getSelectionModel().setSelectionInterval(0, 0);
+	  m_AttSummaryPanel.setAttribute(0);
+	  m_AttVisualizePanel.setAttribute(0);
+
+	  m_ApplyFilterBut.setEnabled(true);
+
+	  m_Log.logMessage("Base relation is now "
+			   + m_Instances.relationName()
+			   + " (" + m_Instances.numInstances()
+			   + " instances)");
+	  m_SaveBut.setEnabled(true);
+	  m_EditBut.setEnabled(true);
+	  m_Log.statusMessage("OK");
+	  // Fire a propertychange event
+	  m_Support.firePropertyChange("", null, null);
+	  
+	  // notify GOEs about change
+	  try {
+	    // get rid of old filter settings
+	    getExplorer().notifyCapabilitiesFilterListener(null);
+
+	    int oldIndex = m_Instances.classIndex();
+	    m_Instances.setClassIndex(m_AttVisualizePanel.getColorBox().getSelectedIndex() - 1);
+	    
+	    // send new ones
+	    if (ExplorerDefaults.getInitGenericObjectEditorFilter())
+	      getExplorer().notifyCapabilitiesFilterListener(
+		  Capabilities.forInstances(m_Instances));
+	    else
+	      getExplorer().notifyCapabilitiesFilterListener(
+		  Capabilities.forInstances(new Instances(m_Instances, 0)));
+
+	    m_Instances.setClassIndex(oldIndex);
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	    m_Log.logMessage(e.toString());
+	  }
+	}
+      };
+      if (SwingUtilities.isEventDispatchThread()) {
+	r.run();
+      } else {
+	SwingUtilities.invokeAndWait(r);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      JOptionPane.showMessageDialog(this,
+				    "Problem setting base instances:\n"
+				    + ex,
+				    "Instances",
+				    JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Gets the working set of instances.
+   *
+   * @return the working instances
+   */
+  public Instances getInstances() {
+
+    return m_Instances;
+  }
+  
+  /**
+   * Adds a PropertyChangeListener who will be notified of value changes.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+
+    m_Support.addPropertyChangeListener(l);
+  }
+
+  /**
+   * Removes a PropertyChangeListener.
+   *
+   * @param l a value of type 'PropertyChangeListener'
+   */
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+
+    m_Support.removePropertyChangeListener(l);
+  }
+  
+  /**
+   * Passes the dataset through the filter that has been configured for use.
+   * 
+   * @param filter	the filter to apply
+   */
+  protected void applyFilter(final Filter filter) {
+
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	public void run() {
+	  try {
+
+	    if (filter != null) {
+	    
+	      if (m_Log instanceof TaskLogger) {
+		((TaskLogger)m_Log).taskStarted();
+	      }
+	      m_Log.statusMessage("Passing dataset through filter "
+		  + filter.getClass().getName());
+	      String cmd = filter.getClass().getName();
+	      if (filter instanceof OptionHandler)
+		cmd += " " + Utils.joinOptions(((OptionHandler) filter).getOptions());
+	      m_Log.logMessage("Command: " + cmd);
+	      int classIndex = m_AttVisualizePanel.getColoringIndex();
+	      if ((classIndex < 0) && (filter instanceof SupervisedFilter)) {
+		throw new IllegalArgumentException("Class (colour) needs to " +
+						   "be set for supervised " +
+						   "filter.");
+	      }
+	      Instances copy = new Instances(m_Instances);
+	      copy.setClassIndex(classIndex);
+	      filter.setInputFormat(copy);
+	      Instances newInstances = Filter.useFilter(copy, filter);
+	      if (newInstances == null || newInstances.numAttributes() < 1) {
+		throw new Exception("Dataset is empty.");
+	      }
+	      m_Log.statusMessage("Saving undo information");
+	      addUndoPoint();
+	      m_AttVisualizePanel.setColoringIndex(copy.classIndex());
+	      // if class was not set before, reset it again after use of filter
+	      if (m_Instances.classIndex() < 0)
+		newInstances.setClassIndex(-1);
+	      m_Instances = newInstances;
+	      setInstances(m_Instances);
+	      if (m_Log instanceof TaskLogger) {
+		((TaskLogger)m_Log).taskFinished();
+	      }
+	    }
+	    
+	  } catch (Exception ex) {
+	
+	    if (m_Log instanceof TaskLogger) {
+	      ((TaskLogger)m_Log).taskFinished();
+	    }
+	    // Pop up an error optionpane
+	    JOptionPane.showMessageDialog(PreprocessPanel.this,
+					  "Problem filtering instances:\n"
+					  + ex.getMessage(),
+					  "Apply Filter",
+					  JOptionPane.ERROR_MESSAGE);
+	    m_Log.logMessage("Problem filtering instances: " + ex.getMessage());
+	    m_Log.statusMessage("Problem filtering instances");
+	  }
+	  m_IOThread = null;
+	}
+      };
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't apply filter at this time,\n"
+				    + "currently busy with other IO",
+				    "Apply Filter",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+
+  /**
+   * Queries the user for a file to save instances as, then saves the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void saveWorkingInstancesToFileQ() {
+    
+    if (m_IOThread == null) {
+      m_FileChooser.setCapabilitiesFilter(m_FilterEditor.getCapabilitiesFilter());
+      m_FileChooser.setAcceptAllFileFilterUsed(false);
+      int returnVal = m_FileChooser.showSaveDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	Instances inst = new Instances(m_Instances);
+	inst.setClassIndex(m_AttVisualizePanel.getColoringIndex());
+	saveInstancesToFile(m_FileChooser.getSaver(), inst);
+      }
+      FileFilter temp = m_FileChooser.getFileFilter();
+      m_FileChooser.setAcceptAllFileFilterUsed(true);
+      m_FileChooser.setFileFilter(temp);
+    }
+    else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't save at this time,\n"
+				    + "currently busy with other IO",
+				    "Save Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+  
+  /**
+   * saves the data with the specified saver
+   * 
+   * @param saver	the saver to use for storing the data
+   * @param inst	the data to save
+   */
+  public void saveInstancesToFile(final AbstractFileSaver saver, final Instances inst) {
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	  public void run() {
+	    try {
+	      m_Log.statusMessage("Saving to file...");
+
+	      saver.setInstances(inst);
+	      saver.writeBatch();
+	      
+	      m_Log.statusMessage("OK");
+	    }
+	    catch (Exception ex) {
+	      ex.printStackTrace();
+	      m_Log.logMessage(ex.getMessage());
+	    }
+	    m_IOThread = null;
+	  }
+	};
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    }
+    else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't save at this time,\n"
+				    + "currently busy with other IO",
+				    "Saving instances",
+				    JOptionPane.WARNING_MESSAGE);
+    } 
+  }
+  
+  /**
+   * Queries the user for a file to load instances from, then loads the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void setInstancesFromFileQ() {
+    
+    if (m_IOThread == null) {
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	try {
+	  addUndoPoint();
+	}
+	catch (Exception ignored) {
+	  // ignored
+	}
+
+	if (m_FileChooser.getLoader() == null) {
+	  JOptionPane.showMessageDialog(this,
+	      "Cannot determine file loader automatically, please choose one.",
+	      "Load Instances",
+	      JOptionPane.ERROR_MESSAGE);
+	  converterQuery(m_FileChooser.getSelectedFile());
+	}
+	else {
+	  setInstancesFromFile(m_FileChooser.getLoader());
+	}
+	    
+      }
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+
+  /**
+   * Loads (non-sparse) instances from an SQL query the user provided with the
+   * SqlViewerDialog, then loads the instances in a background process. This is
+   * done in the IO thread, and an error message is popped up if the IO thread
+   * is busy.
+   * 
+   * @param url           the database URL
+   * @param user          the user to connect as
+   * @param pw            the password of the user
+   * @param query         the query for retrieving instances from
+   */
+  public void setInstancesFromDBQ(String url, String user, 
+                                  String pw, String query) {
+    setInstancesFromDBQ(url, user, pw, query, false);
+  }
+
+  /**
+   * Loads instances from an SQL query the user provided with the
+   * SqlViewerDialog, then loads the instances in a background process. This is
+   * done in the IO thread, and an error message is popped up if the IO thread
+   * is busy.
+   * 
+   * @param url		the database URL
+   * @param user	the user to connect as
+   * @param pw		the password of the user
+   * @param query	the query for retrieving instances from
+   * @param sparse	whether to create sparse or non-sparse instances
+   */
+  public void setInstancesFromDBQ(String url, String user, 
+                                  String pw, String query,
+                                  boolean sparse) {
+    if (m_IOThread == null) {
+      try {
+	InstanceQuery InstQ = new InstanceQuery();
+        InstQ.setDatabaseURL(url);
+        InstQ.setUsername(user);
+        InstQ.setPassword(pw);
+        InstQ.setQuery(query);
+        InstQ.setSparseData(sparse);
+	
+        // we have to disconnect, otherwise we can't change the DB!
+        if (InstQ.isConnected())
+          InstQ.disconnectFromDatabase();
+
+	InstQ.connectToDatabase();      
+	try {
+	  addUndoPoint();
+	} catch (Exception ignored) {}
+	setInstancesFromDB(InstQ);
+      } catch (Exception ex) {
+	JOptionPane.showMessageDialog(this,
+				      "Problem connecting to database:\n"
+				      + ex.getMessage(),
+				      "Load Instances",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+      
+    } else {
+      JOptionPane.showMessageDialog(this,
+				     "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+    
+  /**
+   * Queries the user for a URL to load instances from, then loads the
+   * instances in a background process. This is done in the IO
+   * thread, and an error message is popped up if the IO thread is busy.
+   */
+  public void setInstancesFromURLQ() {
+    
+    if (m_IOThread == null) {
+      try {
+	String urlName = (String) JOptionPane.showInputDialog(this,
+			"Enter the source URL",
+			"Load Instances",
+			JOptionPane.QUESTION_MESSAGE,
+			null,
+			null,
+			m_LastURL);
+	if (urlName != null) {
+	  m_LastURL = urlName;
+	  URL url = new URL(urlName);
+	  try {
+	    addUndoPoint();
+	  } catch (Exception ignored) {}
+	  setInstancesFromURL(url);
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+	JOptionPane.showMessageDialog(this,
+				      "Problem with URL:\n"
+				      + ex.getMessage(),
+				      "Load Instances",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+  
+  /**
+   * sets Instances generated via DataGenerators (pops up a Dialog)
+   */
+  public void generateInstances() {
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	  public void run() {
+	    try {
+              // create dialog
+              final DataGeneratorPanel generatorPanel = new DataGeneratorPanel();
+              final JDialog dialog = new JDialog();
+              final JButton generateButton = new JButton("Generate");
+              final JCheckBox showOutputCheckBox = 
+                                  new JCheckBox("Show generated data as text, incl. comments");
+
+              showOutputCheckBox.setMnemonic('S');
+              generatorPanel.setLog(m_Log);
+              generatorPanel.setGenerator(m_DataGenerator);
+              generatorPanel.setPreferredSize(
+                  new Dimension(
+                        300, 
+                        (int) generatorPanel.getPreferredSize().getHeight()));
+              generateButton.setMnemonic('G');
+              generateButton.setToolTipText("Generates the dataset according the settings.");
+              generateButton.addActionListener(new ActionListener(){
+                  public void actionPerformed(ActionEvent evt){
+                    // generate
+                    generatorPanel.execute();
+                    boolean generated = (generatorPanel.getInstances() != null);
+                    if (generated)
+                      setInstances(generatorPanel.getInstances());
+
+                    // close dialog
+                    dialog.dispose();
+
+                    // get last generator
+                    m_DataGenerator = generatorPanel.getGenerator();
+
+                    // display output?
+                    if ( (generated) && (showOutputCheckBox.isSelected()) )
+                      showGeneratedInstances(generatorPanel.getOutput());
+                }
+              });
+              dialog.setTitle("DataGenerator");
+              dialog.getContentPane().add(generatorPanel, BorderLayout.CENTER);
+              dialog.getContentPane().add(generateButton, BorderLayout.EAST);
+              dialog.getContentPane().add(showOutputCheckBox, BorderLayout.SOUTH);
+              dialog.pack();
+              
+              // display dialog
+              dialog.setVisible(true);
+	    } 
+            catch (Exception ex) {
+	      ex.printStackTrace();
+	      m_Log.logMessage(ex.getMessage());
+	    }
+	    m_IOThread = null;
+	  }
+	};
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    } 
+    else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't generate data at this time,\n"
+				    + "currently busy with other IO",
+				    "Generate Data",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+  
+  /**
+   * displays a dialog with the generated instances from the DataGenerator
+   * 
+   * @param data	the data to display
+   */
+  protected void showGeneratedInstances(String data) {
+    final JDialog dialog = new JDialog();
+    final JButton saveButton = new JButton("Save");
+    final JButton closeButton = new JButton("Close");
+    final JTextArea textData = new JTextArea(data);
+    final JPanel panel = new JPanel();
+    panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+    textData.setEditable(false);
+    textData.setFont(
+        new Font("Monospaced", Font.PLAIN, textData.getFont().getSize()));
+
+    saveButton.setMnemonic('S');
+    saveButton.setToolTipText("Saves the output to a file");
+    saveButton.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        JFileChooser filechooser = new JFileChooser();
+        int result = filechooser.showSaveDialog(dialog);
+        if (result == JFileChooser.APPROVE_OPTION) {
+          try {
+            BufferedWriter writer = new BufferedWriter(
+                                      new FileWriter(
+                                        filechooser.getSelectedFile()));
+            writer.write(textData.getText());
+            writer.flush();
+            writer.close();
+            JOptionPane.showMessageDialog(
+              dialog, 
+              "Output successfully saved to file '" 
+              + filechooser.getSelectedFile() + "'!",
+              "Information",
+              JOptionPane.INFORMATION_MESSAGE);
+          }
+          catch (Exception e) {
+            e.printStackTrace();
+          }
+          dialog.dispose();
+        }
+      }
+    });
+    closeButton.setMnemonic('C');
+    closeButton.setToolTipText("Closes the dialog");
+    closeButton.addActionListener(new ActionListener(){
+      public void actionPerformed(ActionEvent evt){
+        dialog.dispose();
+      }
+    });
+    panel.add(saveButton);
+    panel.add(closeButton);
+    dialog.setTitle("Generated Instances (incl. comments)");
+    dialog.getContentPane().add(new JScrollPane(textData), BorderLayout.CENTER);
+    dialog.getContentPane().add(panel, BorderLayout.SOUTH);
+    dialog.pack();
+
+    // make sure, it's not bigger than 80% of the screen
+    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+    int width  = dialog.getWidth() > screen.getWidth()*0.8
+                    ? (int) (screen.getWidth()*0.8) : dialog.getWidth();
+    int height = dialog.getHeight() > screen.getHeight()*0.8 
+                    ? (int) (screen.getHeight()*0.8) : dialog.getHeight();
+    dialog.setSize(width, height);
+    
+    // display dialog
+    dialog.setVisible(true);
+  }
+
+  /**
+   * Pops up generic object editor with list of conversion filters
+   *
+   * @param f the File
+   */
+  private void converterQuery(final File f) {
+    final GenericObjectEditor convEd = new GenericObjectEditor(true);
+
+    try {
+      convEd.setClassType(weka.core.converters.Loader.class);
+      convEd.setValue(new weka.core.converters.CSVLoader());
+      ((GenericObjectEditor.GOEPanel)convEd.getCustomEditor())
+	.addOkListener(new ActionListener() {
+	    public void actionPerformed(ActionEvent e) {
+	      tryConverter((Loader)convEd.getValue(), f);
+	    }
+	  });
+    } catch (Exception ex) {
+    }
+
+    PropertyDialog pd;
+    if (PropertyDialog.getParentDialog(this) != null)
+      pd = new PropertyDialog(PropertyDialog.getParentDialog(this), convEd, 100, 100);
+    else
+      pd = new PropertyDialog(PropertyDialog.getParentFrame(this), convEd, 100, 100);
+    pd.setVisible(true);
+  }
+
+  /**
+   * Applies the selected converter
+   *
+   * @param cnv the converter to apply to the input file
+   * @param f the input file
+   */
+  private void tryConverter(final Loader cnv, final File f) {
+
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	  public void run() {
+	    try {
+	      cnv.setSource(f);
+	      Instances inst = cnv.getDataSet();
+	      setInstances(inst);
+	    } catch (Exception ex) {
+	      m_Log.statusMessage(cnv.getClass().getName()+" failed to load "
+				 +f.getName());
+	      JOptionPane.showMessageDialog(PreprocessPanel.this,
+					    cnv.getClass().getName()+" failed to load '"
+					    + f.getName() + "'.\n"
+					    + "Reason:\n" + ex.getMessage(),
+					    "Convert File",
+					    JOptionPane.ERROR_MESSAGE);
+	      m_IOThread = null;
+	      converterQuery(f);
+	    }
+	    m_IOThread = null;
+	  }
+	};
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    }
+  }
+
+  /**
+   * Loads results from a set of instances retrieved with the supplied loader. 
+   * This is started in the IO thread, and a dialog is popped up
+   * if there's a problem.
+   *
+   * @param loader	the loader to use
+   */
+  public void setInstancesFromFile(final AbstractFileLoader loader) {
+      
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	public void run() {
+	  try {
+	    m_Log.statusMessage("Reading from file...");
+	    Instances inst = loader.getDataSet();
+	    setInstances(inst);
+	  }
+	  catch (Exception ex) {
+	    m_Log.statusMessage(
+		"File '" + loader.retrieveFile() + "' not recognised as an '"
+		+ loader.getFileDescription() + "' file.");
+	    m_IOThread = null;
+	    if (JOptionPane.showOptionDialog(PreprocessPanel.this,
+					     "File '" + loader.retrieveFile()
+					     + "' not recognised as an '"
+					     + loader.getFileDescription() 
+					     + "' file.\n"
+					     + "Reason:\n" + ex.getMessage(),
+					     "Load Instances",
+					     0,
+					     JOptionPane.ERROR_MESSAGE,
+					     null,
+					     new String[] {"OK", "Use Converter"},
+					     null) == 1) {
+	    
+	      converterQuery(loader.retrieveFile());
+	    }
+	  }
+	  m_IOThread = null;
+	}
+      };
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+  
+  /**
+   * Loads instances from a database
+   *
+   * @param iq the InstanceQuery object to load from (this is assumed
+   * to have been already connected to a valid database).
+   */
+  public void setInstancesFromDB(final InstanceQuery iq) {
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	public void run() {
+	  
+	  try {
+	    m_Log.statusMessage("Reading from database...");
+	    final Instances i = iq.retrieveInstances();
+	    SwingUtilities.invokeAndWait(new Runnable() {
+	      public void run() {
+		setInstances(new Instances(i));
+	      }
+	    });
+	    iq.disconnectFromDatabase();
+	  } catch (Exception ex) {
+	    m_Log.statusMessage("Problem executing DB query "+m_SQLQ);
+	    JOptionPane.showMessageDialog(PreprocessPanel.this,
+					  "Couldn't read from database:\n"
+					  + ex.getMessage(),
+					  "Load Instances",
+					  JOptionPane.ERROR_MESSAGE);
+	  }
+
+	   m_IOThread = null;
+	}
+      };
+
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    } else {
+       JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+
+  /**
+   * Loads instances from a URL.
+   *
+   * @param u the URL to load from.
+   */
+  public void setInstancesFromURL(final URL u) {
+
+    if (m_IOThread == null) {
+      m_IOThread = new Thread() {
+	public void run() {
+
+	  try {
+	    m_Log.statusMessage("Reading from URL...");
+	    AbstractFileLoader loader = ConverterUtils.getURLLoaderForFile(u.toString());
+	    if (loader == null)
+	      throw new Exception("No suitable URLSourcedLoader found for URL!\n" + u);
+	    ((URLSourcedLoader) loader).setURL(u.toString());
+	    setInstances(loader.getDataSet());
+	  } catch (Exception ex) {
+	    ex.printStackTrace();
+	    m_Log.statusMessage("Problem reading " + u);
+	    JOptionPane.showMessageDialog(PreprocessPanel.this,
+					  "Couldn't read from URL:\n"
+					  + u + "\n"
+					  + ex.getMessage(),
+					  "Load Instances",
+					  JOptionPane.ERROR_MESSAGE);
+	  }
+
+	  m_IOThread = null;
+	}
+      };
+      m_IOThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
+      m_IOThread.start();
+    } else {
+      JOptionPane.showMessageDialog(this,
+				    "Can't load at this time,\n"
+				    + "currently busy with other IO",
+				    "Load Instances",
+				    JOptionPane.WARNING_MESSAGE);
+    }
+  }
+
+  /**
+   * Backs up the current state of the dataset, so the changes can be undone.
+   * 
+   * @throws Exception 	if an error occurs
+   */
+  public void addUndoPoint() throws Exception {
+    
+    if (m_Instances != null) {
+      // create temporary file
+      File tempFile = File.createTempFile("weka", SerializedInstancesLoader.FILE_EXTENSION);
+      tempFile.deleteOnExit();
+
+      ObjectOutputStream oos = 
+	new ObjectOutputStream(
+	new BufferedOutputStream(
+	new FileOutputStream(tempFile)));
+    
+      oos.writeObject(m_Instances);
+      oos.flush();
+      oos.close();
+
+      // update undo file list
+      if (m_tempUndoFiles[m_tempUndoIndex] != null) {
+	// remove undo points that are too old
+	m_tempUndoFiles[m_tempUndoIndex].delete();
+      }
+      m_tempUndoFiles[m_tempUndoIndex] = tempFile;
+      if (++m_tempUndoIndex >= m_tempUndoFiles.length) {
+	// wrap pointer around
+	m_tempUndoIndex = 0;
+      }
+
+      m_UndoBut.setEnabled(true);
+    }
+  }
+
+  /**
+   * Reverts to the last backed up version of the dataset.
+   */
+  public void undo() {
+
+    if (--m_tempUndoIndex < 0) {
+      // wrap pointer around
+      m_tempUndoIndex = m_tempUndoFiles.length-1;
+    }
+    
+    if (m_tempUndoFiles[m_tempUndoIndex] != null) {
+      // load instances from the temporary file
+      AbstractFileLoader loader = ConverterUtils.getLoaderForFile(m_tempUndoFiles[m_tempUndoIndex]);
+      try {
+	loader.setFile(m_tempUndoFiles[m_tempUndoIndex]);
+	setInstancesFromFile(loader);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	m_Log.logMessage(e.toString());
+	JOptionPane.showMessageDialog(PreprocessPanel.this,
+	    "Cannot perform undo operation!\n" + e.toString(),
+	    "Undo",
+	    JOptionPane.ERROR_MESSAGE);
+      }
+
+      // update undo file list
+      m_tempUndoFiles[m_tempUndoIndex] = null;
+    }
+    
+    // update undo button
+    int temp = m_tempUndoIndex-1;
+    if (temp < 0) {
+      temp = m_tempUndoFiles.length-1;
+    }
+    m_UndoBut.setEnabled(m_tempUndoFiles[temp] != null);
+  }
+  
+  /**
+   * edits the current instances object in the viewer 
+   */
+  public void edit() {
+    ViewerDialog        dialog;
+    int                 result;
+    Instances           copy;
+    Instances           newInstances;
+    
+    final int classIndex = m_AttVisualizePanel.getColoringIndex();
+    copy   = new Instances(m_Instances);
+    copy.setClassIndex(classIndex);
+    dialog = new ViewerDialog(null);
+    result = dialog.showDialog(copy);
+    if (result == ViewerDialog.APPROVE_OPTION) {
+      try {
+        addUndoPoint();
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+      // if class was not set before, reset it again after use of filter
+      newInstances = dialog.getInstances();
+      if (m_Instances.classIndex() < 0)
+        newInstances.setClassIndex(-1);
+      setInstances(newInstances);
+    }
+  }
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * updates the capabilities filter of the GOE
+   * 
+   * @param filter	the new filter to use
+   */
+  protected void updateCapabilitiesFilter(Capabilities filter) {
+    Instances 		tempInst;
+    Capabilities 	filterClass;
+
+    if (filter == null) {
+      m_FilterEditor.setCapabilitiesFilter(new Capabilities(null));
+      return;
+    }
+    
+    if (!ExplorerDefaults.getInitGenericObjectEditorFilter())
+      tempInst = new Instances(m_Instances, 0);
+    else
+      tempInst = new Instances(m_Instances);
+    tempInst.setClassIndex(m_AttVisualizePanel.getColorBox().getSelectedIndex() - 1);
+
+    try {
+      filterClass = Capabilities.forInstances(tempInst);
+    }
+    catch (Exception e) {
+      filterClass = new Capabilities(null);
+    }
+    
+    // set new filter
+    m_FilterEditor.setCapabilitiesFilter(filterClass);
+    
+    // check capabilities
+    m_ApplyFilterBut.setEnabled(true);
+    Capabilities currentCapabilitiesFilter = m_FilterEditor.getCapabilitiesFilter();
+    Filter currentFilter = (Filter) m_FilterEditor.getValue();
+    Capabilities currentFilterCapabilities = null;
+    if (currentFilter != null && currentCapabilitiesFilter != null &&
+        (currentFilter instanceof CapabilitiesHandler)) {
+      currentFilterCapabilities = ((CapabilitiesHandler)currentFilter).getCapabilities();
+      
+      if (!currentFilterCapabilities.supportsMaybe(currentCapabilitiesFilter) &&
+          !currentFilterCapabilities.supports(currentCapabilitiesFilter)) {
+        m_ApplyFilterBut.setEnabled(false);
+      }
+    }
+  }
+  
+  /**
+   * method gets called in case of a change event
+   * 
+   * @param e		the associated change event
+   */
+  public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e) {
+    if (e.getFilter() == null)
+      updateCapabilitiesFilter(null);
+    else
+      updateCapabilitiesFilter((Capabilities) e.getFilter().clone());
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Preprocess";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Open/Edit/Save instances";
+  }
+  
+  /**
+   * Tests out the instance-preprocessing panel from the command line.
+   *
+   * @param args ignored
+   */
+  public static void main(String [] args) {
+
+    try {
+      final JFrame jf = new JFrame("Weka Explorer: Preprocess");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final PreprocessPanel sp = new PreprocessPanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      weka.gui.LogPanel lp = new weka.gui.LogPanel();
+      sp.setLog(lp);
+      jf.getContentPane().add(lp, BorderLayout.SOUTH);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/explorer/VisualizePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/explorer/VisualizePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/explorer/VisualizePanel.java	(revision 29)
@@ -0,0 +1,118 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * VisualizePanel.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.explorer;
+
+import weka.core.Instances;
+import weka.gui.explorer.Explorer.ExplorerPanel;
+import weka.gui.visualize.MatrixPanel;
+
+import java.awt.BorderLayout;
+
+/**
+ * A slightly extended MatrixPanel for better support in the Explorer.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.15 $
+ * @see MatrixPanel
+ */
+public class VisualizePanel
+  extends MatrixPanel
+  implements ExplorerPanel {
+  
+  /** for serialization */
+  private static final long serialVersionUID = 6084015036853918846L;
+
+  /** the parent frame */
+  protected Explorer m_Explorer = null;
+
+  /**
+   * Sets the Explorer to use as parent frame (used for sending notifications
+   * about changes in the data)
+   * 
+   * @param parent	the parent frame
+   */
+  public void setExplorer(Explorer parent) {
+    m_Explorer = parent;
+  }
+  
+  /**
+   * returns the parent Explorer frame
+   * 
+   * @return		the parent
+   */
+  public Explorer getExplorer() {
+    return m_Explorer;
+  }
+  
+  /**
+   * Returns the title for the tab in the Explorer
+   * 
+   * @return 		the title of this tab
+   */
+  public String getTabTitle() {
+    return "Visualize";
+  }
+  
+  /**
+   * Returns the tooltip for the tab in the Explorer
+   * 
+   * @return 		the tooltip of this tab
+   */
+  public String getTabTitleToolTip() {
+    return "Explore the data";
+  }
+
+  /**
+   * Tests out the visualize panel from the command line.
+   *
+   * @param args may optionally contain the name of a dataset to load.
+   */
+  public static void main(String [] args) {
+
+    try {
+      final javax.swing.JFrame jf =
+	new javax.swing.JFrame("Weka Explorer: Visualize");
+      jf.getContentPane().setLayout(new BorderLayout());
+      final VisualizePanel sp = new VisualizePanel();
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+      if (args.length == 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	sp.setInstances(i);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFFormatException.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFFormatException.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFFormatException.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BIFFormatException.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+/**
+ * This is the Exception thrown by BIFParser, if there
+ * was an error in parsing the XMLBIF string or input
+ * stream.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public class BIFFormatException
+  extends Exception {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4102518086411708140L;
+  
+  public BIFFormatException(String s) {
+    super(s);
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFParser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFParser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/BIFParser.java	(revision 29)
@@ -0,0 +1,401 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    BIFParser.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui.graphvisualizer;
+
+import java.io.InputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.StringTokenizer;
+
+import weka.core.FastVector;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Element;
+
+
+/**
+ * This class parses an inputstream or a string in
+ * XMLBIF ver. 0.3 format, and builds the datastructures
+ * that are passed to it through the constructor.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public class BIFParser implements GraphConstants {
+  
+  /** These holds the nodes and edges of the graph */
+  protected FastVector m_nodes, m_edges;
+  /**  This holds the name of the graph (i.e. the name of network tag in XMLBIF
+   * input)
+   */
+  protected String graphName;
+  /** This holds the string to be parsed */
+  protected String inString;
+  /** This holds the InputStream to be parsed */
+  protected InputStream inStream;
+  
+  
+  /**
+   * Constructor (if our input is a String)
+   *
+   * @param input the string to be parsed (should not be null)
+   * @param nodes vector containing GraphNode objects (should be empty)
+   * @param edges vector containing GraphEdge objects (should be empty)
+   */
+  public BIFParser(String input, FastVector nodes, FastVector edges) {
+    m_nodes = nodes; m_edges = edges; inString = input;
+  }
+  
+  
+  /**
+   * Constructor (if our input is an InputStream)
+   *
+   * @param instream the InputStream to be parsed (should not be null)
+   * @param nodes vector containing GraphNode objects (should be empty)
+   * @param edges vector containing GraphEdge objects (should be empty)
+   */
+  public BIFParser(InputStream instream, FastVector nodes, FastVector edges) {
+    m_nodes = nodes; m_edges = edges; inStream = instream;
+  }
+  
+  
+  /**
+   * This method parses the string or the InputStream that we
+   * passed in through the constructor and builds up the
+   * m_nodes and m_edges vectors
+   * @exception Exception if both the inString and inStream are
+   *              null, i.e. no input has been provided
+   * @exception BIFFormatException if there is format of the
+   *              input is not correct. The format should conform to
+   *              XMLBIF version 0.3
+   * @exception NumberFormatException if there is an invalid
+   *              char in the probability table of a node.
+   * @return    returns the name of the graph
+   */
+  public String parse() throws Exception {
+    Document dc=null;
+    
+    javax.xml.parsers.DocumentBuilderFactory dbf =
+    javax.xml.parsers.DocumentBuilderFactory.newInstance();
+    dbf.setIgnoringElementContentWhitespace(true);
+    javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
+    
+    if(inStream!=null)
+      dc = db.parse(inStream);
+    else if(inString!=null)
+      dc = db.parse(new org.xml.sax.InputSource(new StringReader(inString)));
+    else
+    { throw new Exception("No input given"); }
+    
+    NodeList nl = dc.getElementsByTagName( "NETWORK" );
+    
+    if(nl.getLength()==0) {
+      throw new BIFFormatException( "NETWORK tag not found" );
+    }
+    
+    //take only the first network node
+    NodeList templist = ((Element)nl.item(0)).getElementsByTagName( "NAME" );
+    graphName = templist.item(0).getFirstChild().getNodeValue();
+    //System.out.println("The name of the network is "+
+    //templist.item(0).getFirstChild().getNodeValue());
+    
+    //Get all the variables
+    nl = dc.getElementsByTagName("VARIABLE");
+    for(int i=0; i<nl.getLength(); i++) {
+      
+      templist = ((Element)nl.item(i)).getElementsByTagName("NAME");
+      if(templist.getLength()>1)
+        throw new BIFFormatException("More than one name tags found for "+
+        "variable no. "+(i+1));
+      
+      String nodename =
+      ((org.w3c.dom.Node)templist.item(0)).getFirstChild().getNodeValue();
+      GraphNode n = new GraphNode( nodename, nodename, GraphNode.NORMAL );
+      m_nodes.addElement(n);
+      //getting nodes position
+      templist = ((Element)nl.item(i)).getElementsByTagName("PROPERTY");
+      for(int j=0; j<templist.getLength(); j++) {
+        if( ((org.w3c.dom.Node)templist.item(j)).getFirstChild()
+        .getNodeValue().startsWith("position") ) {
+          String xy = templist.item(j).getFirstChild().getNodeValue();
+          //System.out.println("x: "+
+          //                   xy.substring(xy.indexOf('(')+1, xy.indexOf(','))+
+          //                   " y: "+
+          //                   xy.substring(xy.indexOf(',')+1, xy.indexOf(')'))
+          //                  );
+          n.x = Integer.parseInt( xy.substring(xy.indexOf('(')+
+          1, xy.indexOf(',')).trim() );
+          n.y = Integer.parseInt( xy.substring(xy.indexOf(',')+
+          1, xy.indexOf(')')).trim() );
+          break;
+        }
+      }
+      //getting all the outcomes of the node
+      templist = ((Element)nl.item(i)).getElementsByTagName("OUTCOME");
+      n.outcomes = new String[templist.getLength()];
+      for(int j=0; j<templist.getLength(); j++) {
+        n.outcomes[j] = templist.item(j).getFirstChild().getNodeValue();
+        //System.out.println("Outcome["+j+"]: "+n.outcomes[j]);
+      }
+    } //end for (for variables)
+    
+    //Get all the edges and probability tables by getting all the definitions
+    nl = dc.getElementsByTagName("DEFINITION");
+    for(int i=0; i<nl.getLength(); i++) {
+      
+      templist = ((Element)nl.item(i)).getElementsByTagName("FOR");
+      //the Label of the node the edges are coming into
+      String nid = templist.item(0).getFirstChild().getNodeValue();
+      
+      //getting the GraphNode object with the above label
+      GraphNode n = (GraphNode)m_nodes.elementAt(0);
+      for(int j=1; j<m_nodes.size() && !n.ID.equals(nid); j++)
+        n = (GraphNode)m_nodes.elementAt(j);
+      
+      
+      templist = ((Element)nl.item(i)).getElementsByTagName("GIVEN");
+      int parntOutcomes = 1; //for creating the probability table later on
+      //creating all the edges coming into the node
+      for(int j=0; j<templist.getLength(); j++) {
+        nid = templist.item(j).getFirstChild().getNodeValue();
+        
+        GraphNode n2 = (GraphNode)m_nodes.elementAt(0);
+        for(int k=1; k<m_nodes.size() && !n2.ID.equals(nid); k++)
+          n2 = (GraphNode)m_nodes.elementAt(k);
+        m_edges.addElement( new GraphEdge(m_nodes.indexOf(n2),
+        m_nodes.indexOf(n), 1) );
+        
+        parntOutcomes *= n2.outcomes.length;
+      }
+      
+      //creating the probability table for the node
+      templist = ((Element)nl.item(i)).getElementsByTagName("TABLE");
+      if(templist.getLength()>1)
+        throw new BIFFormatException("More than one Probability Table for "+
+        n.ID);
+      
+      String probs = templist.item(0).getFirstChild().getNodeValue();
+      StringTokenizer tk = new StringTokenizer(probs, " \n\t");
+      
+      if(parntOutcomes*n.outcomes.length > tk.countTokens())
+        throw new BIFFormatException("Probability Table for "+n.ID+
+        " contains more values than it should");
+      else if(parntOutcomes*n.outcomes.length < tk.countTokens())
+        throw new BIFFormatException("Probability Table for "+n.ID+
+        " contains less values than it should");
+      else {
+        n.probs = new double[parntOutcomes][n.outcomes.length];
+        for(int r=0; r<parntOutcomes; r++)     //row
+          for(int c=0; c<n.outcomes.length; c++) //column
+            try {
+              n.probs[r][c] =  Double.parseDouble( tk.nextToken() );
+            }
+            catch(NumberFormatException ne) { throw ne; }
+      } // end of creating probability table
+    } //endfor (for edges)
+    
+    
+    //int tmpMatrix[][] = new int[m_nodes.size()][m_nodes.size()];
+    //for(int i=0; i<m_edges.size(); i++)
+    //    tmpMatrix[((GraphEdge)m_edges.elementAt(i)).src]
+    //	       [((GraphEdge)m_edges.elementAt(i)).dest] =
+    //                                   ((GraphEdge)m_edges.elementAt(i)).type;
+    //for(int i=0; i<m_nodes.size(); i++) {
+    //    GraphNode n = (GraphNode)m_nodes.elementAt(i);
+    //    n.edges = tmpMatrix[i];
+    //}
+    
+    //Adding parents, and those edges to a node which are coming out from it
+    int tmpEdges[], noOfEdgesOfNode[]=new int[m_nodes.size()];
+    int noOfPrntsOfNode[]=new int[m_nodes.size()];
+    for(int i=0; i<m_edges.size(); i++) {
+      GraphEdge e = (GraphEdge)m_edges.elementAt(i);
+      noOfEdgesOfNode[e.src]++;
+      noOfPrntsOfNode[e.dest]++;
+    }
+    
+    for(int i=0; i<m_edges.size(); i++) {
+      GraphEdge e  = (GraphEdge)m_edges.elementAt(i);
+      GraphNode n  = (GraphNode)m_nodes.elementAt(e.src);
+      GraphNode n2 = (GraphNode)m_nodes.elementAt(e.dest);
+      if(n.edges==null) {
+        n.edges = new int[noOfEdgesOfNode[e.src]][2];
+        for(int k=0; k<n.edges.length; k++)
+          n.edges[k][0]=-1;
+      }
+      if(n2.prnts==null) {
+        n2.prnts = new int[noOfPrntsOfNode[e.dest]];
+        for(int k=0; k<n2.prnts.length; k++)
+          n2.prnts[k]=-1;
+      }
+      
+      int k=0;
+      while(n.edges[k][0]!=-1) k++;
+      n.edges[k][0] = e.dest;
+      n.edges[k][1] = e.type;
+      
+      k=0;
+      while(n2.prnts[k]!=-1) k++;
+      n2.prnts[k] = e.src;
+    }
+    
+    //processGraph();
+    //setAppropriateSize();
+    return graphName;
+  } //end readBIF
+  
+  
+  /**
+   * This method writes a graph in XMLBIF ver. 0.3 format to a file.
+   * However, if is reloaded in GraphVisualizer we would need to layout
+   * the graph again to display it correctly.
+   *
+   * @param filename  The name of the file to write in. (will overwrite)
+   * @param graphName The name of the graph. (will be the name of network
+   * tag in XMLBIF)
+   * @param nodes     Vector containing all the nodes
+   * @param edges     Vector containing all the edges
+   */
+  public static void writeXMLBIF03(String filename, String graphName,
+  FastVector nodes, FastVector edges) {
+    try {
+      FileWriter outfile = new FileWriter(filename);
+      
+      StringBuffer text = new StringBuffer();
+      
+      text.append("<?xml version=\"1.0\"?>\n");
+      text.append("<!-- DTD for the XMLBIF 0.3 format -->\n");
+      text.append("<!DOCTYPE BIF [\n");
+      text.append("	<!ELEMENT BIF ( NETWORK )*>\n");
+      text.append("	      <!ATTLIST BIF VERSION CDATA #REQUIRED>\n");
+      text.append("	<!ELEMENT NETWORK ( NAME, ( PROPERTY | VARIABLE | DEFI"+
+      "NITION )* )>\n");
+      text.append("	<!ELEMENT NAME (#PCDATA)>\n");
+      text.append("	<!ELEMENT VARIABLE ( NAME, ( OUTCOME |  PROPERTY )* )"+
+      " >\n");
+      text.append("	      <!ATTLIST VARIABLE TYPE (nature|decision|utility"+
+      ") \"nature\">\n");
+      text.append("	<!ELEMENT OUTCOME (#PCDATA)>\n");
+      text.append("	<!ELEMENT DEFINITION ( FOR | GIVEN | TABLE | PROPERTY"+
+      " )* >\n");
+      text.append("	<!ELEMENT FOR (#PCDATA)>\n");
+      text.append("	<!ELEMENT GIVEN (#PCDATA)>\n");
+      text.append("	<!ELEMENT TABLE (#PCDATA)>\n");
+      text.append("	<!ELEMENT PROPERTY (#PCDATA)>\n");
+      text.append("]>\n");
+      text.append("\n");
+      text.append("\n");
+      text.append("<BIF VERSION=\"0.3\">\n");
+      text.append("<NETWORK>\n");
+      text.append("<NAME>" + XMLNormalize(graphName)  + "</NAME>\n");
+      
+      //Writing all the node names and their outcomes
+      //If outcome is null(ie if the graph was loaded from DOT file) then
+      //simply write TRUE
+      for(int nodeidx=0; nodeidx<nodes.size(); nodeidx++) {
+        GraphNode n = (GraphNode)nodes.elementAt(nodeidx);
+        if(n.nodeType!=GraphNode.NORMAL)
+          continue;
+        
+        text.append("<VARIABLE TYPE=\"nature\">\n");
+        text.append("\t<NAME>" + XMLNormalize(n.ID) + "</NAME>\n");
+        
+        if(n.outcomes!=null)
+          for(int outidx=0; outidx<n.outcomes.length; outidx++)
+            text.append("\t<OUTCOME>" + XMLNormalize(n.outcomes[outidx])+
+            "</OUTCOME>\n");
+        else
+          text.append("\t<OUTCOME>true</OUTCOME>\n");
+        
+        text.append("\t<PROPERTY>position = ("+n.x+","+n.y+")</PROPERTY>\n");
+        text.append("</VARIABLE>\n");
+      }
+      
+      //Writing all the nodes definitions and their probability tables
+      //If probability table is null then simply write 1 for all
+      //the posible outcomes of the parents
+      for (int nodeidx=0; nodeidx<nodes.size(); nodeidx++) {
+        GraphNode n = (GraphNode) nodes.elementAt(nodeidx);
+        if(n.nodeType!=GraphNode.NORMAL)
+          continue;
+        
+        text.append("<DEFINITION>\n");
+        text.append("<FOR>" + XMLNormalize(n.ID) + "</FOR>\n");
+        int parntOutcomes = 1;
+        if(n.prnts!=null)
+          for(int pidx=0; pidx<n.prnts.length; pidx++) {
+            GraphNode prnt = (GraphNode)nodes.elementAt(n.prnts[pidx]);
+            text.append("\t<GIVEN>" + XMLNormalize(prnt.ID) + "</GIVEN>\n");
+            if(prnt.outcomes!=null)
+              parntOutcomes *= prnt.outcomes.length;
+          }
+        
+        text.append("<TABLE>\n");
+        for(int i=0; i<parntOutcomes; i++) {
+          if(n.outcomes!=null)
+            for(int outidx=0; outidx<n.outcomes.length; outidx++){
+              text.append(n.probs[i][outidx]+" ");
+            }
+          else
+            text.append("1");
+          text.append('\n');
+        }
+        text.append("</TABLE>\n");
+        text.append("</DEFINITION>\n");
+      }
+      
+      text.append("</NETWORK>\n");
+      text.append("</BIF>\n");
+      
+      outfile.write(text.toString());
+      outfile.close();
+    }
+    catch(IOException ex) { ex.printStackTrace(); }
+  } // writeXMLBIF
+  
+  /** XMLNormalize converts the five standard XML entities in a string
+   * g.e. the string V&D's is returned as V&amp;D&apos;s
+   * @author Remco Bouckaert (rrb@xm.co.nz)
+   * @param sStr string to normalize
+   * @return normalized string
+   */
+  private static String XMLNormalize(String sStr) {
+    StringBuffer sStr2 = new StringBuffer();
+    for (int iStr = 0; iStr < sStr.length(); iStr++) {
+      char c = sStr.charAt(iStr);
+      switch (c) {
+        case '&': sStr2.append("&amp;"); break;
+        case '\'': sStr2.append("&apos;"); break;
+        case '\"': sStr2.append("&quot;"); break;
+        case '<': sStr2.append("&lt;"); break;
+        case '>': sStr2.append("&gt;"); break;
+        default:
+          sStr2.append(c);
+      }
+    }
+    return sStr2.toString();
+  } // XMLNormalize
+  
+  
+} // BIFParser
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/DotParser.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/DotParser.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/DotParser.java	(revision 29)
@@ -0,0 +1,491 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    DotParser.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui.graphvisualizer;
+
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.FileWriter;
+
+import weka.core.FastVector;
+import weka.gui.graphvisualizer.GraphNode;
+import weka.gui.graphvisualizer.GraphEdge;
+
+
+
+/**
+ * This class parses input in DOT format, and
+ * builds the datastructures that are passed to it.
+ * It is NOT 100% compatible with the DOT format. The
+ * GraphNode and GraphEdge classes do not have any provision
+ * for dealing with different colours or  shapes of nodes,
+ * there can however, be a different label and ID for a
+ * node. It  also does not do anything for labels for
+ * edges. However, this class won't crash or throw an
+ * exception if it encounters any of the above
+ * attributes of an edge or a node. This class however,
+ * won't be able to deal with things like subgraphs and
+ * grouping of nodes.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $ - 23 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public class DotParser implements GraphConstants  {
+  
+  /** These holds the nodes and edges of the graph */
+  protected FastVector m_nodes, m_edges;
+  /** This is the input containing DOT stream  to be parsed */
+  protected Reader m_input;
+  /**  This holds the name of the graph if there is any otherwise it is null */
+  protected String m_graphName;
+  
+  /**
+   *
+   *  Dot parser Constructor
+   *
+   * @param input - The input, if passing in a string then
+   *                encapsulate that in String reader object
+   * @param nodes - Vector to put in GraphNode objects,
+   *                corresponding to the nodes parsed in from
+   *                the input
+   * @param edges - Vector to put in GraphEdge objects,
+   *                corresponding to the edges parsed in from
+   *                the input
+   */
+  public DotParser(Reader input, FastVector nodes, FastVector edges) {
+    m_nodes = nodes; m_edges = edges;
+    m_input = input;
+  }
+  
+  
+  /**
+   * This method parses the string or the InputStream that we
+   * passed in through the constructor and builds up the
+   * m_nodes and m_edges vectors
+   *
+   * @return - returns the name of the graph
+   */
+  public String parse() {
+    StreamTokenizer tk = new StreamTokenizer(new BufferedReader(m_input));
+    setSyntax(tk);
+    
+    graph(tk);
+    
+    return m_graphName;
+  }
+  
+  
+  /**
+   * This method sets the syntax of the StreamTokenizer.
+   * i.e. set the whitespace, comment and delimit chars.
+   *
+   */
+  protected void setSyntax(StreamTokenizer tk) {
+    tk.resetSyntax();
+    tk.eolIsSignificant(false);
+    tk.slashStarComments(true);
+    tk.slashSlashComments(true);
+    tk.whitespaceChars(0,' ');
+    tk.wordChars(' '+1,'\u00ff');
+    tk.ordinaryChar('[');
+    tk.ordinaryChar(']');
+    tk.ordinaryChar('{');
+    tk.ordinaryChar('}');
+    tk.ordinaryChar('-');
+    tk.ordinaryChar('>');
+    tk.ordinaryChar('/');
+    tk.ordinaryChar('*');
+    tk.quoteChar('"');
+    tk.whitespaceChars(';',';');
+    tk.ordinaryChar('=');
+  }
+  
+  /*************************************************************
+   *
+   * Following methods parse the DOT input and mimic the DOT
+   * language's grammar in their structure
+   *
+   *************************************************************
+   */
+  protected void graph(StreamTokenizer tk) {
+    try {
+      tk.nextToken();
+      
+      if(tk.ttype==tk.TT_WORD) {
+        if(tk.sval.equalsIgnoreCase("digraph")) {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD) {
+            m_graphName = tk.sval;
+            tk.nextToken();
+          }
+          
+          while(tk.ttype!='{') {
+            System.err.println("Error at line "+tk.lineno()+" ignoring token "+
+            tk.sval);
+            tk.nextToken();
+            if(tk.ttype==tk.TT_EOF)
+              return;
+          }
+          stmtList(tk);
+        }
+        else if(tk.sval.equalsIgnoreCase("graph"))
+          System.err.println("Error. Undirected graphs cannot be used");
+        else
+          System.err.println("Error. Expected graph or digraph at line "+
+          tk.lineno());
+      }
+    }
+    catch(Exception ex) { ex.printStackTrace(); }
+    
+    
+    //int tmpMatrix[][] = new int[m_nodes.size()][m_nodes.size()];
+    //for(int i=0; i<m_edges.size(); i++)
+    //    tmpMatrix[((GraphEdge)m_edges.elementAt(i)).src]
+    //	     [((GraphEdge)m_edges.elementAt(i)).dest] =
+    //                                   ((GraphEdge)m_edges.elementAt(i)).type;
+    //for(int i=0; i<m_nodes.size(); i++) {
+    //    GraphNode n = (GraphNode)m_nodes.elementAt(i);
+    //    n.edges = tmpMatrix[i];
+    //}
+    
+    //Adding parents, and those edges to a node which are coming out from it
+    int tmpEdges[], noOfEdgesOfNode[]=new int[m_nodes.size()];
+    int noOfPrntsOfNode[]=new int[m_nodes.size()];
+    for(int i=0; i<m_edges.size(); i++) {
+      GraphEdge e = (GraphEdge)m_edges.elementAt(i);
+      noOfEdgesOfNode[e.src]++;
+      noOfPrntsOfNode[e.dest]++;
+    }
+    for(int i=0; i<m_edges.size(); i++) {
+      GraphEdge e  = (GraphEdge)m_edges.elementAt(i);
+      GraphNode n  = (GraphNode)m_nodes.elementAt(e.src);
+      GraphNode n2 = (GraphNode)m_nodes.elementAt(e.dest);
+      if(n.edges==null) {
+        n.edges = new int[noOfEdgesOfNode[e.src]][2];
+        for(int k=0; k<n.edges.length; k++)
+          n.edges[k][1]=0;
+      }
+      if(n2.prnts==null) {
+        n2.prnts = new int[noOfPrntsOfNode[e.dest]];
+        for(int k=0; k<n2.prnts.length; k++)
+          n2.prnts[k]=-1;
+      }
+      int k=0;
+      while(n.edges[k][1]!=0) k++;
+      n.edges[k][0] = e.dest;
+      n.edges[k][1] = e.type;
+      
+      k=0;
+      while(n2.prnts[k]!=-1) k++;
+      n2.prnts[k] = e.src;
+    }
+  }
+  
+  
+  protected void stmtList(StreamTokenizer tk) throws Exception{
+    tk.nextToken();
+    if(tk.ttype=='}' || tk.ttype==tk.TT_EOF)
+      return;
+    else {
+      stmt(tk);
+      stmtList(tk);
+    }
+  }
+  
+  
+  protected void stmt(StreamTokenizer tk) {
+    //tk.nextToken();
+    
+    if(tk.sval.equalsIgnoreCase("graph") || tk.sval.equalsIgnoreCase("node") ||
+    tk.sval.equalsIgnoreCase("edge") )
+      ; //attribStmt(k);
+    else {
+      try {
+        nodeID(tk);
+        int nodeindex= m_nodes.indexOf(new GraphNode(tk.sval, null));
+        tk.nextToken();
+        
+        if(tk.ttype == '[')
+          nodeStmt(tk, nodeindex);
+        else if(tk.ttype == '-')
+          edgeStmt(tk, nodeindex);
+        else
+          System.err.println("error at lineno "+tk.lineno()+" in stmt");
+      }
+      catch(Exception ex) {
+        System.err.println("error at lineno "+tk.lineno()+" in stmtException");
+        ex.printStackTrace();
+      }
+    }
+  }
+  
+  
+  protected void nodeID(StreamTokenizer tk) throws Exception{
+    
+    if(tk.ttype=='"' || tk.ttype==tk.TT_WORD || (tk.ttype>='a' && tk.ttype<='z')
+    || (tk.ttype>='A' && tk.ttype<='Z')) {
+      if(m_nodes!=null && !(m_nodes.contains( new GraphNode(tk.sval, null))) ) {
+        m_nodes.addElement( new GraphNode(tk.sval, tk.sval) );
+        //System.out.println("Added node >"+tk.sval+"<");
+      }
+    }
+    else
+    { throw new Exception(); }
+    
+    //tk.nextToken();
+  }
+  
+  
+  protected void nodeStmt(StreamTokenizer tk, final int nindex)
+  throws Exception {
+    tk.nextToken();
+    
+    GraphNode temp = (GraphNode) m_nodes.elementAt(nindex);
+    
+    if(tk.ttype==']' || tk.ttype==tk.TT_EOF)
+      return;
+    else if(tk.ttype==tk.TT_WORD) {
+      
+      if(tk.sval.equalsIgnoreCase("label")) {
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            temp.lbl = tk.sval;
+          else {
+            System.err.println("couldn't find label at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find label at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+      
+      else if(tk.sval.equalsIgnoreCase("color")){
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            ;
+          else {
+            System.err.println("couldn't find color at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find color at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+      
+      else if(tk.sval.equalsIgnoreCase("style")) {
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            ;
+          else {
+            System.err.println("couldn't find style at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find style at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+    }
+    nodeStmt(tk, nindex);
+  }
+  
+  
+  protected void edgeStmt(StreamTokenizer tk, final int nindex)
+  throws Exception {
+    tk.nextToken();
+    
+    GraphEdge e=null;
+    if(tk.ttype=='>') {
+      tk.nextToken();
+      if(tk.ttype=='{') {
+        while(true) {
+          tk.nextToken();
+          if(tk.ttype=='}')
+            break;
+          else {
+            nodeID(tk);
+            e = new GraphEdge(nindex,
+            m_nodes.indexOf( new GraphNode(tk.sval, null) ),
+            DIRECTED);
+            if( m_edges!=null && !(m_edges.contains(e)) ) {
+              m_edges.addElement( e );
+              //System.out.println("Added edge from "+
+              //                  ((GraphNode)(m_nodes.elementAt(nindex))).ID+
+              //                  " to "+
+              //	        ((GraphNode)(m_nodes.elementAt(e.dest))).ID);
+            }
+          }
+        }
+      }
+      else {
+        nodeID(tk);
+        e = new GraphEdge(nindex,
+        m_nodes.indexOf( new GraphNode(tk.sval, null) ),
+        DIRECTED);
+        if( m_edges!=null && !(m_edges.contains(e)) ) {
+          m_edges.addElement( e );
+          //System.out.println("Added edge from "+
+          //                 ((GraphNode)(m_nodes.elementAt(nindex))).ID+" to "+
+          //		     ((GraphNode)(m_nodes.elementAt(e.dest))).ID);
+        }
+      }
+    }
+    else if(tk.ttype=='-') {
+      System.err.println("Error at line "+tk.lineno()+
+      ". Cannot deal with undirected edges");
+      if(tk.ttype==tk.TT_WORD)
+        tk.pushBack();
+      return;
+    }
+    else {
+      System.err.println("Error at line "+tk.lineno()+" in edgeStmt");
+      if(tk.ttype==tk.TT_WORD)
+        tk.pushBack();
+      return;
+    }
+    
+    tk.nextToken();
+    
+    if(tk.ttype=='[')
+      edgeAttrib(tk, e);
+    else
+      tk.pushBack();
+  }
+  
+  
+  protected void edgeAttrib(StreamTokenizer tk, final GraphEdge e)
+  throws Exception {
+    tk.nextToken();
+    
+    if(tk.ttype==']' || tk.ttype==tk.TT_EOF)
+      return;
+    else if(tk.ttype==tk.TT_WORD) {
+      
+      if(tk.sval.equalsIgnoreCase("label")) {
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            System.err.println("found label "+tk.sval);//e.lbl = tk.sval;
+          else {
+            System.err.println("couldn't find label at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find label at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+      else if(tk.sval.equalsIgnoreCase("color")) {
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            ;
+          else {
+            System.err.println("couldn't find color at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find color at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+      
+      else if(tk.sval.equalsIgnoreCase("style")) {
+        
+        tk.nextToken();
+        if(tk.ttype=='=') {
+          tk.nextToken();
+          if(tk.ttype==tk.TT_WORD || tk.ttype=='"')
+            ;
+          else {
+            System.err.println("couldn't find style at line "+tk.lineno());
+            tk.pushBack();
+          }
+        }
+        else {
+          System.err.println("couldn't find style at line "+tk.lineno());
+          tk.pushBack();
+        }
+      }
+    }
+    edgeAttrib(tk, e);
+  }
+  
+  
+  /**
+   *
+   * This method saves a graph in a file in DOT format.
+   * However, if reloaded in GraphVisualizer we would need
+   * to layout the graph again.
+   *
+   * @param filename  - The name of the file to write in. (will overwrite)
+   * @param graphName - The name of the graph
+   * @param nodes     - Vector containing all the nodes
+   * @param edges     - Vector containing all the edges
+   */
+  public static void writeDOT(String filename, String graphName,
+  FastVector nodes, FastVector edges) {
+    try {
+      FileWriter os = new FileWriter(filename);
+      os.write("digraph ", 0, ("digraph ").length());
+      if(graphName!=null)
+        os.write(graphName+" ", 0, graphName.length()+1);
+      os.write("{\n", 0, ("{\n").length());
+      
+      GraphEdge e;
+      for(int i=0; i<edges.size(); i++) {
+        e = (GraphEdge) edges.elementAt(i);
+        os.write(((GraphNode)nodes.elementAt(e.src)).ID, 0,
+        ((GraphNode)nodes.elementAt(e.src)).ID.length());
+        os.write("->", 0, ("->").length() );
+        os.write(((GraphNode)nodes.elementAt(e.dest)).ID+"\n",
+        0,
+        ((GraphNode)nodes.elementAt(e.dest)).ID.length()+1);
+      }
+      os.write("}\n", 0, ("}\n").length());
+      os.close();
+    }
+    catch(IOException ex) { ex.printStackTrace(); }
+  }
+  
+} // DotParser
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphConstants.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphConstants.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphConstants.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphConstants.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+
+/**
+ * GraphConstants.java
+ *
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public interface GraphConstants  {
+  /** Types of Edges */
+  int DIRECTED=1,  REVERSED=2, DOUBLE=3;
+  
+  //Node types
+  /** SINGULAR_DUMMY node - node with only one outgoing edge
+   * i.e. one which represents a single edge and is inserted to close a gap */
+  int SINGULAR_DUMMY=1;
+  /** PLURAL_DUMMY node - node with more than one outgoing edge
+   * i.e. which represents an edge split and is inserted to close a gap */
+  int PLURAL_DUMMY=2;
+  /** NORMAL node - node actually contained in graphs description  */
+  int NORMAL=3;
+  
+} // GraphConstants
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphEdge.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphEdge.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphEdge.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphEdge.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+/**
+ * This class represents an edge in the graph
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 4995 $ - 23 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public class GraphEdge extends Object {
+  
+  /** The index of source node in Nodes vector */
+  public int src;
+  /** The index of target node in Nodes vector */
+  public int dest;
+  /** The type of Edge */
+  public int type;
+  /** Label of source node */
+  public String srcLbl;
+  /** Label of target node */
+  public String destLbl;
+  
+  public GraphEdge(int s, int d, int t) {
+    src=s; dest=d; type=t;
+    srcLbl = null; destLbl = null;
+  }
+  
+  public GraphEdge(int s, int d, int t, String sLbl, String dLbl) {
+    src=s; dest=d; type=t;
+    srcLbl = sLbl; destLbl = dLbl;
+  }
+  
+  public String toString() {
+    return ("("+src+","+dest+","+type+")");
+  }
+  
+  public boolean equals(Object e) {
+    if( e instanceof GraphEdge &&
+    ((GraphEdge)e).src==this.src &&
+    ((GraphEdge)e).dest==this.dest &&
+    ((GraphEdge)e).type==this.type)
+      return true;
+    else
+      return false;
+  }
+  
+} // GraphEdge
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphNode.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphNode.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphNode.java	(revision 29)
@@ -0,0 +1,80 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphNode.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+/**
+ * This class represents a node in the Graph.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 4995 $ - 23 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+
+public class GraphNode extends Object implements GraphConstants {
+  /** ID and label for the node */
+  public String ID, lbl;
+  /** The outcomes for the given node */
+  public String [] outcomes;
+  /** probability table for each outcome given outcomes of parents, if any */
+  public double [][] probs;   //probabilities
+  /** The x and y position of the node */
+  public int x=0, y=0;
+  /** The indices of parent nodes */
+  public int [] prnts;       //parent nodes
+  /** The indices of nodes to which there are edges from this
+   * node, plus the type of edge */
+  public int [][] edges;
+  /**  Type of node. Default is Normal node type */
+  public int nodeType=NORMAL;
+  
+  /**
+   *  Constructor
+   *
+   */
+  public GraphNode(String id, String label) {
+    ID = id; lbl = label; nodeType=NORMAL;
+  }
+  
+  /**
+   *  Constructor
+   *
+   */
+  public GraphNode(String id, String label, int type ) {
+    ID = id; lbl = label; nodeType = type;
+  }
+  
+  /**
+   *  Returns true if passed in argument is an instance
+   *  of GraphNode and is equal to this node.
+   *  Implemented to enable the use of contains method
+   *  in Vector/FastVector class.
+   */
+  public boolean equals(Object n) {
+    if(n instanceof GraphNode && ((GraphNode) n).ID.equalsIgnoreCase(this.ID)) {
+      //System.out.println("returning true, n.ID >"+((GraphNode)n).ID+
+      //                   "< this.ID >"+this.ID+"<");
+      return true;
+    }
+    else
+      return false;
+  }
+} // GraphNode
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphVisualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphVisualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/GraphVisualizer.java	(revision 29)
@@ -0,0 +1,1447 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    GraphVisualizer.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui.graphvisualizer;
+
+import weka.core.FastVector;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.visualize.PrintablePanel;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * This class displays the graph we want to visualize. It should
+ * be sufficient to use only this class in weka.gui.graphvisulizer
+ * package to visualize a graph. The description of a graph should
+ * be provided as a string argument using readBIF or readDOT method
+ * in either XMLBIF03 or DOT format. Alternatively, an InputStream
+ * in XMLBIF03 can also be provided to another variation of readBIF.
+ * It would be necessary in case input is in DOT format to call the
+ * layoutGraph() method to display the graph correctly after the call
+ * to readDOT. It is also necessary to do so if readBIF is called and
+ * the graph description doesn't have x y positions for nodes.
+ * <p> The graph's data is held in two FastVectors, nodes are stored as
+ * objects of GraphNode class and edges as objects of GraphEdge class.
+ * <p> The graph is displayed by positioning and drawing each node
+ * according to its x y position and then drawing all the edges coming
+ * out of it give by its edges[][] array, the arrow heads are ofcourse
+ * marked in the opposite(ie original direction) or both directions if
+ * the edge is reversed or is in both directions. The graph is centered
+ * if it is smaller than it's display area. The edges are drawn from the
+ * bottom of the current node to the top of the node given by edges[][]
+ * array in GraphNode class, to avoid edges crossing over other nodes.
+ * This might need to be changed if another layout engine is added or
+ * the current Hierarchical engine is updated to avoid such crossings
+ * over nodes.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 4723 $
+ */
+public class GraphVisualizer
+  extends JPanel
+  implements GraphConstants, LayoutCompleteEventListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2038911085935515624L;
+  
+  /** Vector containing nodes */
+  protected FastVector m_nodes=new FastVector();
+  /** Vector containing edges */
+  protected FastVector m_edges=new FastVector();
+  /** The current LayoutEngine  */
+  protected LayoutEngine m_le;
+  /** Panel actually displaying the graph */
+  protected GraphPanel m_gp;
+  /** String containing graph's name */
+  protected String graphID;
+  
+  /**
+   * Save button to save the current graph in DOT or XMLBIF format.
+   * The graph should be layed out again to get the original form
+   * if reloaded from command line, as the formats do not allow
+   * saving specific information for a properly layed out graph.
+   */
+  protected JButton m_jBtSave;
+  
+  /** path for icons */
+  private final String ICONPATH = "weka/gui/graphvisualizer/icons/";
+  
+  private FontMetrics fm = this.getFontMetrics( this.getFont() );
+  private double scale = 1;   //current zoom
+  private int nodeHeight = 2*fm.getHeight(), nodeWidth = 24;
+  private int paddedNodeWidth = 24+8;
+  /** TextField for node's width */
+  private final JTextField jTfNodeWidth = new JTextField(3);
+  /** TextField for nodes height */
+  private final JTextField jTfNodeHeight = new JTextField(3);
+  /** Button for laying out the graph again, necessary after changing node's
+   * size or some other property of the layout engine
+   */
+  private final JButton jBtLayout;
+  /** used for setting appropriate node size */
+  private int maxStringWidth=0;
+  /** used when using zoomIn and zoomOut buttons */
+  private int [] zoomPercents = { 10, 25, 50, 75, 100, 125, 150, 175, 200, 225,
+  250, 275, 300, 350, 400, 450, 500, 550, 600, 650, 700, 800, 900, 999 };
+  /** this contains the m_gp GraphPanel */
+  JScrollPane m_js;
+  
+  /**
+   * Constructor<br>
+   * Sets up the gui and initializes all the other previously
+   * uninitialized variables.
+   */
+  public GraphVisualizer() {
+    m_gp = new GraphPanel();
+    m_js = new JScrollPane(m_gp);
+    
+    //creating a new layout engine and adding this class as its listener
+    // to receive layoutComplete events
+    m_le=new HierarchicalBCEngine(m_nodes, m_edges, 
+                                  paddedNodeWidth, nodeHeight);
+    m_le.addLayoutCompleteEventListener(this);
+    
+    m_jBtSave = new JButton();
+    java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH+"save.gif");
+    if(tempURL!=null)
+      m_jBtSave.setIcon(new ImageIcon(tempURL) );
+    else
+      System.err.println(ICONPATH+
+      "save.gif not found for weka.gui.graphvisualizer.Graph");
+    m_jBtSave.setToolTipText("Save Graph");
+    m_jBtSave.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
+        ExtensionFileFilter ef1 = new ExtensionFileFilter(".dot", "DOT files");
+        ExtensionFileFilter ef2 = new ExtensionFileFilter(".xml",
+        "XML BIF files");
+        fc.addChoosableFileFilter(ef1);
+        fc.addChoosableFileFilter(ef2);
+        fc.setDialogTitle("Save Graph As");
+        int rval = fc.showSaveDialog(GraphVisualizer.this);
+        
+        if (rval == JFileChooser.APPROVE_OPTION) {
+          //System.out.println("Saving to file \""+
+          //                   f.getAbsoluteFile().toString()+"\"");
+          if(fc.getFileFilter()==ef2) {
+            String filename = fc.getSelectedFile().toString();
+            if(!filename.endsWith(".xml"))
+              filename = filename.concat(".xml");
+            BIFParser.writeXMLBIF03(filename, graphID, m_nodes, m_edges);
+          }
+          else {
+            String filename = fc.getSelectedFile().toString();
+            if(!filename.endsWith(".dot"))
+              filename = filename.concat(".dot");
+            DotParser.writeDOT(filename, graphID, m_nodes, m_edges);
+          }
+        }
+      }
+    });
+    
+    final JButton jBtZoomIn = new JButton();
+    tempURL = ClassLoader.getSystemResource(ICONPATH+"zoomin.gif");
+    if(tempURL!=null)
+      jBtZoomIn.setIcon(new ImageIcon(tempURL) );
+    else
+      System.err.println(ICONPATH+
+      "zoomin.gif not found for weka.gui.graphvisualizer.Graph");
+    jBtZoomIn.setToolTipText("Zoom In");
+    
+    final JButton jBtZoomOut = new JButton();
+    tempURL = ClassLoader.getSystemResource(ICONPATH+"zoomout.gif");
+    if(tempURL!=null)
+      jBtZoomOut.setIcon(new ImageIcon(tempURL) );
+    else
+      System.err.println(ICONPATH+
+      "zoomout.gif not found for weka.gui.graphvisualizer.Graph");
+    jBtZoomOut.setToolTipText("Zoom Out");
+    
+    final JTextField jTfZoom = new JTextField("100%");
+    jTfZoom.setMinimumSize( jTfZoom.getPreferredSize() );
+    jTfZoom.setHorizontalAlignment(JTextField.CENTER);
+    jTfZoom.setToolTipText("Zoom");
+    
+    jTfZoom.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        JTextField jt = (JTextField)ae.getSource();
+        try {
+          int i=-1;
+          i = jt.getText().indexOf('%');
+          if(i==-1)
+            i = Integer.parseInt(jt.getText());
+          else
+            i = Integer.parseInt(jt.getText().substring(0,i));
+          
+          if(i<=999)
+            scale = i/100D;
+          
+          jt.setText((int)(scale*100)+"%");
+          
+          if(scale>0.1){
+            if(!jBtZoomOut.isEnabled())
+              jBtZoomOut.setEnabled(true);
+          }
+          else
+            jBtZoomOut.setEnabled(false);
+          if(scale<9.99) {
+            if(!jBtZoomIn.isEnabled())
+              jBtZoomIn.setEnabled(true);
+          }
+          else
+            jBtZoomIn.setEnabled(false);
+          
+          setAppropriateSize();
+          //m_gp.clearBuffer();
+          m_gp.repaint();
+          m_gp.invalidate();
+          m_js.revalidate();
+        } catch(NumberFormatException ne) {
+          JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
+          "Invalid integer entered for zoom.",
+          "Error",
+          JOptionPane.ERROR_MESSAGE);
+          jt.setText((scale*100)+"%");
+        }
+      }
+    });
+    
+    
+    jBtZoomIn.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        int i=0, s = (int)(scale*100);
+        if(s<300)
+          i = s/25;
+        else if(s<700)
+          i = 6 +  s/50;
+        else
+          i = 13 +s/100;
+        
+        if(s>=999) {
+          JButton b = (JButton)ae.getSource();
+          b.setEnabled(false);
+          return;
+        }
+        else if(s>=10){
+          if(i>=22) {
+            JButton b = (JButton)ae.getSource();
+            b.setEnabled(false);
+          }
+          if(s==10 && !jBtZoomOut.isEnabled())
+            jBtZoomOut.setEnabled(true);
+          //System.out.println("i: "+i+"Zoom is: "+zoomPercents[i+1]);
+          jTfZoom.setText(zoomPercents[i+1]+"%");
+          scale = zoomPercents[i+1]/100D;
+        }
+        else {
+          if(!jBtZoomOut.isEnabled())
+            jBtZoomOut.setEnabled(true);
+          //System.out.println("i: "+i+"Zoom is: "+zoomPercents[0]);
+          jTfZoom.setText(zoomPercents[0]+"%");
+          scale = zoomPercents[0]/100D;
+        }
+        setAppropriateSize();
+        m_gp.repaint();
+        m_gp.invalidate();
+        m_js.revalidate();
+      }
+    });
+    
+    
+    jBtZoomOut.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        int i=0, s = (int)(scale*100);
+        if(s<300)
+          i = (int) Math.ceil(s/25D);
+        else if(s<700)
+          i = 6 +  (int) Math.ceil(s/50D);
+        else
+          i = 13 + (int) Math.ceil(s/100D);
+        
+        if(s<=10) {
+          JButton b = (JButton)ae.getSource();
+          b.setEnabled(false);
+        }
+        else if(s<999) {
+          if(i<=1) {
+            JButton b = (JButton)ae.getSource();
+            b.setEnabled(false);
+          }
+          //System.out.println("i: "+i+"Zoom is: "+zoomPercents[i-1]);
+          jTfZoom.setText(zoomPercents[i-1]+"%");
+          scale = zoomPercents[i-1]/100D;
+        }
+        else{
+          if(!jBtZoomIn.isEnabled())
+            jBtZoomIn.setEnabled(true);
+          //System.out.println("i: "+i+"Zoom is: "+zoomPercents[22]);
+          jTfZoom.setText(zoomPercents[22]+"%");
+          scale = zoomPercents[22]/100D;
+        }
+        setAppropriateSize();
+        m_gp.repaint();
+        m_gp.invalidate();
+        m_js.revalidate();
+      }
+    });
+    
+    
+    //This button pops out the extra controls
+    JButton jBtExtraControls = new JButton();
+    tempURL = ClassLoader.getSystemResource(ICONPATH+"extra.gif");
+    if(tempURL!=null)
+      jBtExtraControls.setIcon(new ImageIcon(tempURL) );
+    else
+      System.err.println(ICONPATH+
+      "extra.gif not found for weka.gui.graphvisualizer.Graph");
+    jBtExtraControls.setToolTipText("Show/Hide extra controls");
+    
+    
+    final JCheckBox jCbCustomNodeSize = new JCheckBox("Custom Node Size");
+    final JLabel jLbNodeWidth = new JLabel("Width");
+    final JLabel jLbNodeHeight = new JLabel("Height");
+    
+    jTfNodeWidth.setHorizontalAlignment(JTextField.CENTER);
+    jTfNodeWidth.setText(""+nodeWidth);
+    jTfNodeHeight.setHorizontalAlignment(JTextField.CENTER);
+    jTfNodeHeight.setText(""+nodeHeight);
+    jLbNodeWidth.setEnabled(false);
+    jTfNodeWidth.setEnabled(false);
+    jLbNodeHeight.setEnabled(false);
+    jTfNodeHeight.setEnabled(false);
+    
+    jCbCustomNodeSize.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        if( ((JCheckBox)ae.getSource()).isSelected() ) {
+          jLbNodeWidth.setEnabled(true);
+          jTfNodeWidth.setEnabled(true);
+          jLbNodeHeight.setEnabled(true);
+          jTfNodeHeight.setEnabled(true);
+        }
+        else {
+          jLbNodeWidth.setEnabled(false);
+          jTfNodeWidth.setEnabled(false);
+          jLbNodeHeight.setEnabled(false);
+          jTfNodeHeight.setEnabled(false);
+          setAppropriateNodeSize();
+        }
+      }
+    });
+    
+    
+    jBtLayout  = new JButton("Layout Graph");
+    jBtLayout.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        int tmpW, tmpH;
+        
+        if(jCbCustomNodeSize.isSelected()) {
+          try{ tmpW = Integer.parseInt(jTfNodeWidth.getText()); }
+          catch(NumberFormatException ne) {
+            JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
+            "Invalid integer entered for node width.",
+            "Error",
+            JOptionPane.ERROR_MESSAGE);
+            tmpW = nodeWidth;
+            jTfNodeWidth.setText(""+nodeWidth);
+            
+          }
+          try{ tmpH = Integer.parseInt(jTfNodeHeight.getText()); }
+          catch(NumberFormatException ne) {
+            JOptionPane.showMessageDialog(GraphVisualizer.this.getParent(),
+            "Invalid integer entered for node height.",
+            "Error",
+            JOptionPane.ERROR_MESSAGE);
+            tmpH = nodeHeight;
+            jTfNodeWidth.setText(""+nodeHeight);
+          }
+          
+          if(tmpW!=nodeWidth || tmpH!=nodeHeight) {
+            nodeWidth = tmpW; paddedNodeWidth = nodeWidth+8; nodeHeight = tmpH;
+          }
+        }
+        JButton bt = (JButton)ae.getSource();
+        bt.setEnabled(false);
+        m_le.setNodeSize(paddedNodeWidth, nodeHeight);
+        m_le.layoutGraph();
+      }
+    });
+    
+    
+    GridBagConstraints gbc = new GridBagConstraints();
+    
+    final JPanel p = new JPanel(new GridBagLayout());
+    gbc.gridwidth = gbc.REMAINDER;
+    gbc.anchor = gbc.NORTHWEST;
+    gbc.fill = gbc.NONE;
+    p.add( m_le.getControlPanel(), gbc);
+    gbc.gridwidth = 1;
+    gbc.insets = new Insets(8,0,0,0);
+    gbc.anchor = gbc.NORTHWEST;
+    gbc.gridwidth = gbc.REMAINDER;
+    
+    p.add( jCbCustomNodeSize, gbc );
+    gbc.insets = new Insets(0,0,0,0);
+    gbc.gridwidth = gbc.REMAINDER;
+    Container c = new Container();
+    c.setLayout( new GridBagLayout() );
+    gbc.gridwidth = gbc.RELATIVE;
+    c.add(jLbNodeWidth, gbc);
+    gbc.gridwidth = gbc.REMAINDER;
+    c.add(jTfNodeWidth, gbc);
+    gbc.gridwidth = gbc.RELATIVE;
+    c.add(jLbNodeHeight, gbc);
+    gbc.gridwidth = gbc.REMAINDER;
+    c.add(jTfNodeHeight, gbc);
+    gbc.fill = gbc.HORIZONTAL;
+    p.add( c, gbc );
+    
+    gbc.anchor = gbc.NORTHWEST;
+    gbc.insets = new Insets(8,0,0,0);
+    gbc.fill = gbc.HORIZONTAL;
+    p.add( jBtLayout, gbc );
+    gbc.fill = gbc.NONE;
+    p.setBorder(BorderFactory.createCompoundBorder(
+    BorderFactory.createTitledBorder("ExtraControls"),
+    BorderFactory.createEmptyBorder(4,4,4,4)
+    ) );
+    p.setPreferredSize( new Dimension(0, 0) );
+    
+    final JToolBar jTbTools = new JToolBar();
+    jTbTools.setFloatable(false);
+    jTbTools.setLayout( new GridBagLayout() );
+    gbc.anchor = gbc.NORTHWEST;
+    gbc.gridwidth = gbc.REMAINDER;
+    gbc.insets = new Insets(0,0,0,0);
+    jTbTools.add(p,gbc);
+    gbc.gridwidth = 1;
+    jTbTools.add(m_jBtSave, gbc);
+    jTbTools.addSeparator(new Dimension(2,2));
+    jTbTools.add(jBtZoomIn, gbc);
+    
+    gbc.fill = gbc.VERTICAL;
+    gbc.weighty = 1;
+    JPanel p2 = new JPanel(new BorderLayout());
+    p2.setPreferredSize( jTfZoom.getPreferredSize() );
+    p2.setMinimumSize( jTfZoom.getPreferredSize() );
+    p2.add(jTfZoom, BorderLayout.CENTER);
+    jTbTools.add(p2, gbc);
+    gbc.weighty =0;
+    gbc.fill = gbc.NONE;
+    
+    jTbTools.add(jBtZoomOut, gbc);
+    jTbTools.addSeparator(new Dimension(2,2));
+    jTbTools.add(jBtExtraControls, gbc);
+    jTbTools.addSeparator(new Dimension(4,2));
+    gbc.weightx = 1;
+    gbc.fill = gbc.BOTH;
+    jTbTools.add(m_le.getProgressBar(), gbc);
+    
+    jBtExtraControls.addActionListener( new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        Dimension d = p.getPreferredSize();
+        if(d.width==0 || d.height==0) {
+          LayoutManager lm = p.getLayout();
+          Dimension d2 = lm.preferredLayoutSize(p);
+          p.setPreferredSize(d2); jTbTools.revalidate();
+          /*
+          // this piece of code adds in an animation
+          // for popping out the extra controls panel
+          Thread th = new Thread() {
+            int h = 0, w = 0;
+            LayoutManager lm = p.getLayout();
+            Dimension d2 = lm.preferredLayoutSize(p);
+           
+            int tow = (int)d2.getWidth(), toh = (int)d2.getHeight();
+            //toh = (int)d2.getHeight();
+            //tow = (int)d2.getWidth();
+           
+            public void run() {
+              while(h<toh || w<tow) {
+                if((h+10)<toh)
+                  h += 10;
+                else if(h<toh)
+                  h = toh;
+                if((w+10)<tow)
+                  w += 10;
+                else if(w<tow)
+                  w = tow;
+                p.setPreferredSize(new Dimension(w, h));
+                //p.invalidate();
+                jTbTools.revalidate();
+                //paint(Temp4.this.getGraphics());
+                try {this.sleep(30);}
+                catch(InterruptedException ie) {ie.printStackTrace(); break;}
+              }
+              p.setPreferredSize(new Dimension(tow,toh)); jTbTools.revalidate();
+            }
+          };
+          th.start();
+           */
+        }
+        else {
+          p.setPreferredSize( new Dimension(0,0) );
+          jTbTools.revalidate();
+          /*
+          Thread th = new Thread() {
+            int h = p.getHeight(), w = p.getWidth();
+            LayoutManager lm = p.getLayout();
+            int tow = 0, toh = 0;
+           
+            public void run() {
+              while(h>toh || w>tow) {
+                if((h-10)>toh)
+                  h -= 10;
+                else if(h>toh)
+                  h = toh;
+                if((w-10)>tow)
+                  w -= 10;
+                else if(w>tow)
+                  w = tow;
+           
+                p.setPreferredSize(new Dimension(w, h));
+                //p.invalidate();
+                jTbTools.revalidate();
+                //paint(Temp4.this.getGraphics());
+                try {this.sleep(30);}
+                catch(InterruptedException ie) {ie.printStackTrace(); break;}
+              }
+              p.setPreferredSize(new Dimension(tow,toh)); jTbTools.revalidate();
+            }
+          };
+          th.start();
+           */
+        }
+      }
+    });
+    this.setLayout( new BorderLayout() );
+    this.add(jTbTools, BorderLayout.NORTH);
+    this.add(m_js, BorderLayout.CENTER);
+  }
+  
+  
+  /**
+   * This method sets the node size that is appropriate
+   * considering the maximum label size that is present.
+   * It is used internally when custom node size checkbox
+   * is unchecked.
+   */
+  protected void setAppropriateNodeSize() {
+    int strWidth;
+    if(maxStringWidth==0)
+      for(int i=0; i<m_nodes.size(); i++) {
+        strWidth = fm.stringWidth(((GraphNode)m_nodes.elementAt(i)).lbl);
+        if(strWidth>maxStringWidth)
+          maxStringWidth=strWidth;
+      }
+    nodeWidth = maxStringWidth+4;
+    paddedNodeWidth = nodeWidth+8;
+    jTfNodeWidth.setText(""+nodeWidth);
+    
+    nodeHeight = 2*fm.getHeight();
+    jTfNodeHeight.setText(""+nodeHeight);
+  }
+  
+  /**
+   * Sets the preferred size for m_gp GraphPanel to the
+   * minimum size that is neccessary to display the graph.
+   */
+  protected void setAppropriateSize() {
+    int maxX=0, maxY=0;
+    
+    m_gp.setScale(scale, scale);
+    
+    for(int i=0; i<m_nodes.size(); i++) {
+      GraphNode n = (GraphNode)m_nodes.elementAt(i);
+      if(maxX<n.x)
+        maxX=n.x;
+      if(maxY<n.y)
+        maxY=n.y;
+    }
+    //System.out.println("Scale: "+scale+" paddedWidth: "+paddedNodeWidth+
+    //                   " nodeHeight: "+nodeHeight+"\nmaxX: "+maxX+" maxY: "+
+    //                   maxY+" final: "+(int)((maxX+paddedNodeWidth+2)*scale)+
+    //                   ","+(int)((maxY+nodeHeight+2)*scale) );
+    m_gp.setPreferredSize(new Dimension((int)((maxX+paddedNodeWidth+2)*scale),
+    (int)((maxY+nodeHeight+2)*scale)));
+    //System.out.println("Size set to "+this.getPreferredSize());
+  }
+  
+  
+  /**
+   * This method is an implementation for LayoutCompleteEventListener
+   * class. It sets the size appropriate for m_gp GraphPanel and
+   * and revalidates it's container JScrollPane once a
+   * LayoutCompleteEvent is received from the LayoutEngine.
+   */
+  public void layoutCompleted(LayoutCompleteEvent le) {
+    setAppropriateSize();
+    //m_gp.clearBuffer();
+    m_gp.invalidate();
+    m_js.revalidate();
+    m_gp.repaint();
+    jBtLayout.setEnabled(true);
+  }
+  
+  
+  /**
+   * This method lays out the graph by calling the
+   * LayoutEngine's layoutGraph() method. This method
+   * should be called to display the graph nicely, unless
+   * the input XMLBIF03 already contains some layout
+   * information (ie the x,y positions of nodes.
+   */
+  public void layoutGraph() {
+    if(m_le!=null)
+      m_le.layoutGraph();
+    
+  }
+  
+  /*********************************************************
+   *
+   *  BIF reader<br>
+   *  Reads a graph description in XMLBIF03 from a string
+   *
+   *********************************************************
+   */
+  public void readBIF(String instring) throws BIFFormatException {
+    BIFParser bp = new BIFParser(instring, m_nodes, m_edges);
+    try {
+      graphID = bp.parse();
+    } catch(BIFFormatException bf) {
+      System.out.println("BIF format error");
+      bf.printStackTrace();
+    }
+    catch(Exception ex) { ex.printStackTrace(); return; }
+    
+    setAppropriateNodeSize();
+    if(m_le!=null) {
+      m_le.setNodeSize(paddedNodeWidth, nodeHeight);
+    }
+  } //end readBIF1
+  
+  /**
+   *
+   *  BIF reader<br>
+   *  Reads a graph description in XMLBIF03 from an InputStrem
+   *
+   *
+   */
+  public void readBIF(InputStream instream) throws BIFFormatException {
+    BIFParser bp = new BIFParser(instream, m_nodes, m_edges);
+    try {
+      graphID = bp.parse();
+    } catch(BIFFormatException bf) {
+      System.out.println("BIF format error");
+      bf.printStackTrace();
+    }
+    catch(Exception ex) { ex.printStackTrace(); return; }
+    
+    setAppropriateNodeSize();
+    if(m_le!=null) {
+      m_le.setNodeSize(paddedNodeWidth, nodeHeight);
+    }
+    setAppropriateSize();
+  } //end readBIF2
+  
+  
+  /*********************************************************
+   *
+   *  Dot reader<br>
+   *  Reads a graph description in DOT format from a string
+   *
+   *********************************************************
+   */
+  public void readDOT(Reader input) {
+    DotParser dp = new DotParser(input, m_nodes, m_edges);
+    graphID = dp.parse();
+    
+    setAppropriateNodeSize();
+    if(m_le!=null) {
+      m_le.setNodeSize(paddedNodeWidth, nodeHeight);
+      jBtLayout.setEnabled(false);
+      layoutGraph();
+    }
+  }
+  
+  /**
+   * The panel which contains the actual graph.
+   */
+  private class GraphPanel
+    extends PrintablePanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -3562813603236753173L;
+
+    public GraphPanel() {
+      super();
+      this.addMouseListener( new GraphVisualizerMouseListener() );
+      this.addMouseMotionListener( new GraphVisualizerMouseMotionListener() );
+      this.setToolTipText("");
+    }
+    
+    public String getToolTipText(MouseEvent me) {
+      int x, y, nx, ny;
+      Rectangle r;
+      GraphNode n;
+      Dimension d = m_gp.getPreferredSize();
+      //System.out.println("Preferred Size: "+this.getPreferredSize()+
+      //                   " Actual Size: "+this.getSize());
+      x=y=nx=ny=0;
+      
+      if(d.width < m_gp.getWidth())
+        nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
+      if(d.height < m_gp.getHeight())
+        ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
+      
+      r = new Rectangle(0, 0, 
+                       (int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
+      x += me.getX(); y += me.getY();
+      
+      int i;
+      for(i=0; i<m_nodes.size(); i++) {
+        n = (GraphNode) m_nodes.elementAt(i);
+        if(n.nodeType!=NORMAL)
+          return null;
+        r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
+        if(r.contains(x,y)) {
+          if(n.probs==null)
+            return n.lbl;
+          else
+            return n.lbl+" (click to view the probability dist. table)";
+        }
+      }
+      return null;
+    }
+    
+    
+    public void paintComponent(Graphics gr) {
+      Graphics2D g = (Graphics2D)gr;
+      RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+      RenderingHints.VALUE_ANTIALIAS_ON);
+      rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+      g.setRenderingHints(rh);
+      g.scale(scale, scale);
+      Rectangle r = g.getClipBounds();
+      g.clearRect(r.x,r.y,r.width,r.height);
+      //g.setColor(this.getBackground());
+      //g.fillRect(0, 0, width+5, height+5);
+      int x=0, y=0;
+      Dimension d = this.getPreferredSize();
+      //System.out.println("Preferred Size: "+this.getPreferredSize()+
+      //                   " Actual Size: "+this.getSize());
+      
+      //initializing x & y to display the graph in the middle
+      //if the display area is larger than the graph
+      if(d.width < this.getWidth())
+        x = (int)((x + this.getWidth()/2 - d.width/2)/scale);
+      if(d.height < this.getHeight())
+        y = (int)((y + this.getHeight()/2 - d.height/2)/scale);
+      
+      for(int index=0; index<m_nodes.size(); index++) {
+        GraphNode n = (GraphNode) m_nodes.elementAt(index);
+        if( n.nodeType==NORMAL) {
+          g.setColor( this.getBackground().darker().darker() );
+          g.fillOval(x+n.x+paddedNodeWidth-nodeWidth-
+                          (paddedNodeWidth-nodeWidth)/2,
+                     y+n.y,
+                     nodeWidth, nodeHeight);
+          
+          g.setColor(Color.white);
+          //g.setColor(Color.black);
+          //System.out.println("drawing "+
+          //                   ((GraphNode)m_nodes.elementAt(index)).ID+
+          //                   " at "+" x: "+ (x+n.x+paddedNodeWidth/2-
+          //      fm.stringWidth( ((GraphNode)m_nodes.elementAt(index)).ID )/2)+
+          //		       " y: "+(y+n.y+nodeHeight/2+fm.getHeight()/2-2) );
+          
+          
+          //Draw the node's label if it can fit inside the node's current
+          // width otherwise display its ID or otherwise just display its
+          // idx in the FastVector (to distinguish it from others)
+          // if any can fit in node's current width
+          if(fm.stringWidth(n.lbl)<=nodeWidth)
+            g.drawString( n.lbl,
+            x+n.x+paddedNodeWidth/2
+            -fm.stringWidth( n.lbl )/2,
+            y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+          else if(fm.stringWidth(n.ID)<=nodeWidth)
+            g.drawString( n.ID,
+            x+n.x+paddedNodeWidth/2
+            -fm.stringWidth( n.ID )/2,
+            y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+          else if(fm.stringWidth( Integer.toString(index) )<=nodeWidth)
+            g.drawString( Integer.toString(index),
+            x+n.x+paddedNodeWidth/2
+            -fm.stringWidth( Integer.toString(index) )/2,
+            y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+          
+          g.setColor(Color.black);
+        }
+        else {
+          //g.draw( new java.awt.geom.QuadCurve2D.Double(n.x+paddedNodeWidth/2, 
+          //                                             n.y,
+          //				n.x+paddedNodeWidth-nodeSize
+          //                                   -(paddedNodeWidth-nodeSize)/2,
+          //                                  n.y+nodeHeight/2,
+          //                           n.x+paddedNodeWidth/2, n.y+nodeHeight) );
+          g.drawLine(x+n.x+paddedNodeWidth/2, y+n.y, 
+                     x+n.x+paddedNodeWidth/2, y+n.y+nodeHeight);
+          
+        }
+        
+        GraphNode n2;
+        int x1, y1, x2, y2;
+        //System.out.println("Drawing edges of "+n.lbl);
+        
+        //Drawing all the edges coming out from the node,
+        //including reversed and double ones
+        if(n.edges!=null)
+          for(int k=0; k<n.edges.length; k++) {
+            if(n.edges[k][1]>0) {
+              n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
+              //System.out.println("  -->to "+n2.lbl);
+              x1=n.x+paddedNodeWidth/2; y1=n.y+nodeHeight;
+              x2=n2.x+paddedNodeWidth/2; y2=n2.y;
+              g.drawLine(x+x1, y+y1, x+x2, y+y2);
+              if(n.edges[k][1]==DIRECTED) {
+                if(n2.nodeType==n2.NORMAL)
+                  drawArrow(g, x+x1, y+y1, x+x2, y+y2);
+              }
+              else if(n.edges[k][1]==REVERSED) {
+                if(n.nodeType==NORMAL)
+                  drawArrow(g, x+x2, y+y2, x+x1, y+y1);
+              }
+              else if(n.edges[k][1]==DOUBLE) {
+                if(n.nodeType==NORMAL)
+                  drawArrow(g, x+x2, y+y2, x+x1, y+y1);
+                if(n2.nodeType==NORMAL)
+                  drawArrow(g, x+x1, y+y1, x+x2, y+y2);
+              }
+            }
+          }
+      }
+    }
+    
+    /**
+     * This method draws an arrow on a line from (x1,y1)
+     * to (x2,y2). The arrow head is seated on (x2,y2) and
+     * is in the direction of the line.
+     * If the arrow is needed to be drawn in the opposite
+     * direction then simply swap the order of (x1, y1)
+     * and (x2, y2) when calling this function.
+     */
+    protected void drawArrow(Graphics g, int x1, int y1, int x2, int y2) {
+      
+      if(x1==x2) {
+        if(y1<y2) {
+          g.drawLine(x2, y2, x2+4, y2-8);
+          g.drawLine(x2, y2, x2-4, y2-8);
+        }
+        else {
+          g.drawLine(x2, y2, x2+4, y2+8);
+          g.drawLine(x2, y2, x2-4, y2+8);
+        }
+      }
+      else {
+        //theta=line's angle from base, beta=angle of arrow's side from line
+        double hyp=0, base=0, perp=0, theta, beta;
+        int x3=0, y3=0;
+        
+        if(x2<x1) {
+          base = x1-x2; hyp = Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
+          theta = Math.acos( base/hyp );
+        }
+        else { //x1>x2 as we already checked x1==x2 before
+          base = x1-x2; hyp = Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
+          theta = Math.acos( base/hyp );
+        }
+        beta = 30*Math.PI/180;
+        //System.out.println("Original base "+base+" perp "+perp+" hyp "+hyp+
+        //                   "\ntheta "+theta+" beta "+beta);
+        
+        hyp = 8;
+        base = Math.cos(theta-beta)*hyp;
+        perp = Math.sin(theta-beta)*hyp;
+        
+        x3 = (int)(x2+base);
+        if(y1<y2)
+          y3 = (int)(y2-perp);
+        else
+          y3 = (int)(y2+perp);
+        
+        //System.out.println("Drawing 1 from "+x2+","+y2+" to "+x3+","+y3+
+        //                   " x1,y1 is "+x1+","+y1+" base "+base+
+        //		     " perp "+perp+" cos(theta-beta) "+
+        //                   Math.cos(theta-beta));
+        g.drawLine(x2, y2, x3, y3);
+        
+        base = Math.cos(theta+beta)*hyp;
+        perp = Math.sin(theta+beta)*hyp;
+        
+        x3 = (int)(x2+base);
+        if(y1<y2)
+          y3 = (int)(y2-perp);
+        else
+          y3 = (int)(y2+perp);
+        //System.out.println("Drawing 2 from "+x2+","+y2+" to "+x3+","+y3+
+        //                   " x1,y1 is "+x1+","+y1+" base "+base+
+        //		     " perp "+perp);
+        g.drawLine(x2, y2, x3, y3);
+      }
+    }
+    
+    /**
+     * This method highlights a given node and all its children
+     * and the edges coming out of it.
+     */
+    public void highLight(GraphNode n) {
+      Graphics2D g = (Graphics2D) this.getGraphics();
+      RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+      RenderingHints.VALUE_ANTIALIAS_ON);
+      rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+      g.setRenderingHints(rh);
+      g.setPaintMode();
+      g.scale(scale, scale);
+      int x=0, y=0;
+      Dimension d = this.getPreferredSize();
+      //System.out.println("Preferred Size: "+this.getPreferredSize()+
+      //                   " Actual Size: "+this.getSize());
+      
+      //initializing x & y to display the graph in the middle
+      //if the display area is larger than the graph
+      if(d.width < this.getWidth())
+        x = (int)((x + this.getWidth()/2 - d.width/2)/scale);
+      if(d.height < this.getHeight())
+        y = (int)((y + this.getHeight()/2 - d.height/2)/scale);
+      
+      //if the node is of type NORMAL only then highlight
+      if(n.nodeType==NORMAL) {
+        
+        g.setXORMode(Color.green); //g.setColor(Color.green);
+        
+        g.fillOval(x+n.x+paddedNodeWidth-nodeWidth-
+        (paddedNodeWidth-nodeWidth)/2,
+        y+n.y, nodeWidth, nodeHeight);
+        g.setXORMode(Color.red);
+        
+        //Draw the node's label if it can fit inside the node's current
+        // width otherwise display its ID or otherwise just display its
+        // idx in the FastVector (to distinguish it from others)
+        // if any can fit in node's current width
+        if(fm.stringWidth(n.lbl)<=nodeWidth)
+          g.drawString( n.lbl,
+          x+n.x+paddedNodeWidth/2
+          -fm.stringWidth( n.lbl )/2,
+          y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+        else if(fm.stringWidth(n.ID)<=nodeWidth)
+          g.drawString( n.ID,
+          x+n.x+paddedNodeWidth/2
+          -fm.stringWidth( n.ID )/2,
+          y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+        else if( fm.stringWidth( Integer.toString(m_nodes.indexOf(n)) ) <=
+        nodeWidth )
+          g.drawString( Integer.toString(m_nodes.indexOf(n)),
+          x+n.x+paddedNodeWidth/2
+          -fm.stringWidth( Integer.toString(m_nodes.indexOf(n)) )/2,
+          y+n.y+nodeHeight/2+fm.getHeight()/2-2 );
+        
+        g.setXORMode(Color.green);
+        
+        
+        GraphNode n2;
+        int x1, y1, x2, y2;
+        //System.out.println("Drawing edges of "+n.lbl);
+        if(n.edges!=null)
+          //Drawing all the edges from and upward ones coming to the node
+          for(int k=0; k<n.edges.length; k++) {
+            if(n.edges[k][1]==DIRECTED || n.edges[k][1]==DOUBLE) {
+              n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
+              //System.out.println("  -->to "+n2.lbl);
+              x1=n.x+paddedNodeWidth/2; y1=n.y+nodeHeight;
+              x2=n2.x+paddedNodeWidth/2; y2=n2.y;
+              g.drawLine(x+x1, y+y1, x+x2, y+y2);
+              if(n.edges[k][1]==DIRECTED) {
+                if(n2.nodeType==n2.NORMAL) //!n2.dummy)
+                  drawArrow(g, x+x1, y+y1, x+x2, y+y2);
+              }
+              else if(n.edges[k][1]==DOUBLE) {
+                if(n.nodeType==NORMAL) //!n.dummy)
+                  drawArrow(g, x+x2, y+y2, x+x1, y+y1);
+                if(n2.nodeType==NORMAL) //!n2.dummy)
+                  drawArrow(g, x+x1, y+y1, x+x2, y+y2);
+              }
+              if(n2.nodeType==NORMAL)
+                g.fillOval(x+n2.x+paddedNodeWidth-nodeWidth-
+                (paddedNodeWidth-nodeWidth)/2,
+                y+n2.y, nodeWidth, nodeHeight);
+              
+              //If n2 is not of NORMAL type
+              // then carry on drawing all the edges and add all the
+              // dummy nodes encountered in a Vector until no
+              // more dummy nodes are found and all the child nodes(node n2)
+              // are of type normal
+              java.util.Vector t = new java.util.Vector();
+              while(n2.nodeType!=NORMAL || t.size()>0) { //n2.dummy==true) {
+                //System.out.println("in while processing "+n2.ID);
+                if(t.size()>0)
+                { n2 = (GraphNode)t.elementAt(0);
+                  t.removeElementAt(0); }
+                if(n2.nodeType!=NORMAL) {
+                  g.drawLine(x+n2.x+paddedNodeWidth/2, y+n2.y,
+                  x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
+                  x1=n2.x+paddedNodeWidth/2; y1=n2.y+nodeHeight;
+                  //System.out.println("Drawing from "+n2.lbl);
+                  for(int m=0; m<n2.edges.length; m++) {
+                    //System.out.println(" to "+n2.lbl+", "+
+                    //                   graphMatrix[tmpIndex][m]);
+                    if(n2.edges[m][1]>0) {
+                      GraphNode n3 =
+                      (GraphNode) m_nodes.elementAt(n2.edges[m][0]); //m_nodes.elementAt(m);
+                      g.drawLine(x+x1, y+y1, x+n3.x+paddedNodeWidth/2, y+n3.y);
+                      
+                      if(n3.nodeType==NORMAL){ //!n2.dummy)
+                        g.fillOval(x+n3.x+paddedNodeWidth-nodeWidth-
+                        (paddedNodeWidth-nodeWidth)/2,
+                        y+n3.y, nodeWidth, nodeHeight);
+                        drawArrow(g, x+x1, y+y1,
+                        x+n3.x+paddedNodeWidth/2, y+n3.y);
+                      }
+                      //if(n3.nodeType!=n3.NORMAL)
+                      t.addElement(n3);
+                      //break;
+                    }
+                  }
+                }
+              }
+            }
+            else if(n.edges[k][1]==-REVERSED || n.edges[k][1]==-DOUBLE) {
+              //Drawing all the reversed and double edges which are going
+              //upwards in the drawing.
+              n2 = (GraphNode) m_nodes.elementAt(n.edges[k][0]); //m_nodes.elementAt(k);
+              //System.out.println("  -->to "+n2.lbl);
+              x1=n.x+paddedNodeWidth/2; y1=n.y;
+              x2=n2.x+paddedNodeWidth/2; y2=n2.y+nodeHeight;
+              g.drawLine(x+x1, y+y1, x+x2, y+y2);
+              
+              if(n.edges[k][1]==-DOUBLE) {
+                drawArrow(g, x+x2, y+y2, x+x1, y+y1);
+                if(n2.nodeType!=SINGULAR_DUMMY) //!n2.dummy)
+                  drawArrow(g, x+x1, y+y1, x+x2, y+y2);
+              }
+              
+              int tmpIndex=k;
+              while(n2.nodeType!=NORMAL) { //n2.dummy==true) {
+                g.drawLine(x+n2.x+paddedNodeWidth/2,
+                y+n2.y+nodeHeight, x+n2.x+paddedNodeWidth/2, y+n2.y);
+                x1=n2.x+paddedNodeWidth/2; y1=n2.y;
+                for(int m=0; m<n2.edges.length; m++) {
+                  if(n2.edges[m][1]<0) {
+                    n2 = (GraphNode) m_nodes.elementAt(n2.edges[m][0]); //m_nodes.elementAt(m);
+                    g.drawLine(x+x1, y+y1,
+                    x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
+                    tmpIndex=m;
+                    if(n2.nodeType!=SINGULAR_DUMMY) //!n2.dummy)
+                      drawArrow(g, x+x1, y+y1,
+                      x+n2.x+paddedNodeWidth/2, y+n2.y+nodeHeight);
+                    break;
+                  }
+                }
+              }
+            }
+          }
+      }
+    }
+  }
+  
+  
+  /**
+   * Table Model for the Table that shows the probability
+   * distribution for a node
+   */
+  private class GraphVisualizerTableModel
+    extends AbstractTableModel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4789813491347366596L;
+    
+    final String[] columnNames;
+    final double[][] data;
+    
+    
+    public GraphVisualizerTableModel(double[][] d, String[] c) {
+      data = d;
+      columnNames = c;
+    }
+    
+    public int getColumnCount() {
+      return columnNames.length;
+    }
+    
+    public int getRowCount() {
+      return data.length;
+    }
+    
+    public String getColumnName(int col) {
+      return columnNames[col];
+    }
+    
+    public Object getValueAt(int row, int col) {
+      return new Double(data[row][col]);
+    }
+    
+   /*
+    * JTable uses this method to determine the default renderer/
+    * editor for each cell.
+    */
+    public Class getColumnClass(int c) {
+      return getValueAt(0, c).getClass();
+    }
+    
+    /*
+     * Implemented this to make sure the table is uneditable.
+     */
+    public boolean isCellEditable(int row, int col) {
+      return false;
+    }
+  }
+  
+  
+  
+  /**
+   * Listener class for processing mouseClicked
+   */
+  private class GraphVisualizerMouseListener extends MouseAdapter {
+    int x, y, nx, ny; Rectangle r;
+    
+    /**
+     * If the mouse is clicked on a node then this method
+     * displays a dialog box with the probability distribution
+     * table for that node IF it exists
+     */
+    public void mouseClicked(MouseEvent me) {
+      GraphNode n;
+      Dimension d = m_gp.getPreferredSize();
+      //System.out.println("Preferred Size: "+this.getPreferredSize()+
+      //                   " Actual Size: "+this.getSize());
+      x=y=nx=ny=0;
+      
+      if(d.width < m_gp.getWidth())
+        nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
+      if(d.height < m_gp.getHeight())
+        ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
+      
+      r=new Rectangle(0, 0, 
+                     (int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
+      x += me.getX(); y += me.getY();
+      
+      int i;
+      for(i=0; i<m_nodes.size(); i++) {
+        n = (GraphNode) m_nodes.elementAt(i);
+        r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
+        if(r.contains(x,y)) {
+          if(n.probs==null)
+            return;
+          
+          int noOfPrntsOutcomes = 1;
+          if(n.prnts!=null) {
+            for(int j=0; j<n.prnts.length; j++) {
+              GraphNode n2 = (GraphNode)m_nodes.elementAt(n.prnts[j]);
+              noOfPrntsOutcomes *= n2.outcomes.length;
+            }
+            if(noOfPrntsOutcomes>511) {
+              System.err.println("Too many outcomes of parents ("+noOfPrntsOutcomes+
+                                 ") can't display probabilities");
+              return;
+            }
+          }
+          
+          GraphVisualizerTableModel tm = 
+                             new GraphVisualizerTableModel(n.probs, n.outcomes);
+          
+          JTable jTblProbs = new JTable(tm); //JTable(probabilities, (Object[])n.outcomes);
+          
+          JScrollPane js = new JScrollPane(jTblProbs);
+          
+          if(n.prnts!=null) {
+            GridBagConstraints gbc = new GridBagConstraints();
+            JPanel jPlRowHeader = new JPanel( new GridBagLayout() );
+            
+            //indices of the parent nodes in the Vector
+            int [] idx = new int[n.prnts.length]; 
+            //max length of values of each parent
+            int [] lengths = new int[n.prnts.length];  
+            
+            //System.out.println("n.probs.length "+n.probs.length+
+            //                   " should be "+noOfPrntsOutcomes);
+            //System.out.println("n.probs[0].length "+n.probs[0].length+
+            //                   " should be "+n.outcomes.length);
+            //System.out.println("probabilities are: ");
+            //for(int j=0; j<probabilities.length; j++) {
+            //    for(int k=0; k<probabilities[j].length; k++)
+            //	   System.out.print(probabilities[j][k]+" ");
+            //     System.out.println("");
+            //}
+            
+            //Adding labels for rows
+            gbc.anchor = gbc.NORTHWEST;
+            gbc.fill = gbc.HORIZONTAL;
+            gbc.insets = new Insets(0,1,0,0);
+            int addNum=0, temp=0;
+            boolean dark=false;
+            while(true){
+              GraphNode n2;
+              gbc.gridwidth = 1;
+              for(int k=0; k<n.prnts.length; k++) {
+                n2 = (GraphNode)m_nodes.elementAt(n.prnts[k]);
+                JLabel lb = new JLabel(n2.outcomes[idx[k]]);
+                lb.setFont( new Font("Dialog", Font.PLAIN, 12) );
+                lb.setOpaque( true );
+                lb.setBorder( BorderFactory.createEmptyBorder( 1,2,1,1 ) );
+                lb.setHorizontalAlignment( JLabel.CENTER );
+                if(dark) {
+                  lb.setBackground( lb.getBackground().darker() );
+                  lb.setForeground( Color.white );
+                }
+                else
+                  lb.setForeground( Color.black );
+                
+                temp = lb.getPreferredSize().width;
+                //System.out.println("Preferred width "+temp+
+                //                   " for "+n2.outcomes[idx[k]]);
+                lb.setPreferredSize(
+                                 new Dimension(temp, jTblProbs.getRowHeight())
+                                 );
+                if(lengths[k]<temp)
+                  lengths[k] = temp;
+                temp=0;
+                
+                if(k==n.prnts.length-1) {
+                  gbc.gridwidth = gbc.REMAINDER;
+                  dark = (dark==true) ?  false:true;
+                }
+                jPlRowHeader.add(lb, gbc);
+                addNum++;
+              }
+              
+              for(int k=n.prnts.length-1; k>=0; k--) {
+                n2 = (GraphNode) m_nodes.elementAt(n.prnts[k]);
+                if(idx[k]==n2.outcomes.length-1 && k!=0) {
+                  idx[k]=0;
+                  continue;
+                }
+                else {
+                  idx[k]++;
+                  break;
+                }
+              }
+              
+              n2 = (GraphNode) m_nodes.elementAt(n.prnts[0]);
+              if(idx[0]==n2.outcomes.length) {
+                JLabel lb= (JLabel) jPlRowHeader.getComponent(addNum-1);
+                jPlRowHeader.remove(addNum-1);
+                lb.setPreferredSize( new Dimension(lb.getPreferredSize().width, 
+                                                   jTblProbs.getRowHeight()) );
+                gbc.gridwidth = gbc.REMAINDER;
+                gbc.weighty = 1;
+                jPlRowHeader.add(lb, gbc);
+                gbc.weighty=0;
+                break;
+              }
+            }
+            
+            
+            gbc.gridwidth = 1;
+            //The following panel contains the names of the parents
+            //and is displayed above the row names to identify
+            //which value belongs to which parent
+            JPanel jPlRowNames = new JPanel(new GridBagLayout());
+            for(int j=0; j<n.prnts.length; j++) {
+              JLabel lb2;
+              JLabel lb1 = 
+                   new JLabel( ((GraphNode)m_nodes.elementAt(n.prnts[j])).lbl );
+              lb1.setBorder( BorderFactory.createEmptyBorder( 1,2,1,1 ) );
+              Dimension tempd = lb1.getPreferredSize();
+              //System.out.println("lengths[j]: "+lengths[j]+
+              //                   " tempd.width: "+tempd.width);
+              if(tempd.width<lengths[j]) {
+                lb1.setPreferredSize( new Dimension(lengths[j], tempd.height) );
+                lb1.setHorizontalAlignment( JLabel.CENTER );
+                lb1.setMinimumSize( new Dimension(lengths[j], tempd.height) );
+              }
+              else if(tempd.width>lengths[j]) {
+                lb2 = (JLabel) jPlRowHeader.getComponent(j);
+                lb2.setPreferredSize( new Dimension(tempd.width, 
+                                               lb2.getPreferredSize().height) );
+              }
+              jPlRowNames.add(lb1, gbc);
+              //System.out.println("After adding "+lb1.getPreferredSize());
+            }
+            js.setRowHeaderView(jPlRowHeader);
+            js.setCorner( JScrollPane.UPPER_LEFT_CORNER, jPlRowNames );
+          }
+          
+          
+          JDialog jd = 
+                new JDialog((Frame)GraphVisualizer.this.getTopLevelAncestor(),
+                            "Probability Distribution Table For "+n.lbl, true);
+          jd.setSize(500, 400);
+          jd.setLocation(GraphVisualizer.this.getLocation().x+
+                            GraphVisualizer.this.getWidth()/2-250,
+                         GraphVisualizer.this.getLocation().y+
+                            GraphVisualizer.this.getHeight()/2-200 );
+          
+          jd.getContentPane().setLayout( new BorderLayout() );
+          jd.getContentPane().add(js, BorderLayout.CENTER);
+          jd.setVisible(true);
+          
+          return;
+        }
+      }
+    }
+    
+  }
+  
+  
+  /**
+   * private class for handling mouseMoved events
+   * to highlight nodes if the the mouse is moved on
+   * one
+   */
+  private class GraphVisualizerMouseMotionListener extends MouseMotionAdapter {
+    int x, y, nx, ny; Rectangle r;
+    GraphNode lastNode;
+    
+    public void mouseMoved(MouseEvent me) {
+      GraphNode n;
+      Dimension d = m_gp.getPreferredSize();
+      //System.out.println("Preferred Size: "+this.getPreferredSize()+
+      //                   " Actual Size: "+this.getSize());
+      x=y=nx=ny=0;
+      
+      if(d.width < m_gp.getWidth())
+        nx = (int)((nx + m_gp.getWidth()/2 - d.width/2)/scale);
+      if(d.height < m_gp.getHeight())
+        ny = (int)((ny + m_gp.getHeight()/2 - d.height/2)/scale);
+      
+      r=new Rectangle(0, 0, 
+                     (int)(paddedNodeWidth*scale), (int)(nodeHeight*scale));
+      x += me.getX(); y += me.getY();
+      
+      int i;
+      for(i=0; i<m_nodes.size(); i++) {
+        n = (GraphNode) m_nodes.elementAt(i);
+        r.x = (int)((nx+n.x)*scale); r.y = (int)((ny+n.y)*scale);
+        if(r.contains(x,y)) {
+          if(n!=lastNode) {
+            m_gp.highLight(n);
+            if(lastNode!=null)
+              m_gp.highLight(lastNode);
+            lastNode = n; //lastIndex = i;
+          }
+          break;
+        }
+      }
+      if(i==m_nodes.size()  && lastNode!=null) {
+        m_gp.repaint();
+        //m_gp.highLight(lastNode);
+        lastNode=null;
+      }
+    }
+  }
+  
+  /**
+   * Main method to load a text file with the
+   * description of a graph from the command
+   * line
+   */
+  public static void main(String [] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    JFrame jf = new JFrame("Graph Visualizer");
+    GraphVisualizer g = new GraphVisualizer();
+    
+    try{
+      if(args[0].endsWith(".xml")) {
+        //StringBuffer sb = new StringBuffer();
+        //FileReader infile = new FileReader(args[0]);
+        //int i;
+        //while( (i=infile.read())!=-1) {
+        //    sb.append((char)i);
+        //}
+        //System.out.println(sb.toString());
+        //g.readBIF(sb.toString() );
+        g.readBIF( new FileInputStream(args[0]) );
+      }
+      else {
+        //BufferedReader infile=new BufferedReader();
+        g.readDOT(new FileReader(args[0])); //infile);
+      }
+    }
+    catch(IOException ex) { ex.printStackTrace(); }
+    catch(BIFFormatException bf) { bf.printStackTrace(); System.exit(-1); }
+    
+    jf.getContentPane().add(g);
+    //RepaintManager.currentManager(jf.getRootPane()).setDoubleBufferingEnabled(false);
+    jf.setDefaultCloseOperation( jf.EXIT_ON_CLOSE );
+    jf.setSize(800,600);
+    //jf.pack();
+    jf.setVisible(true);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/HierarchicalBCEngine.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/HierarchicalBCEngine.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/HierarchicalBCEngine.java	(revision 29)
@@ -0,0 +1,2065 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    HierarchicalBCEngine.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+import weka.core.FastVector;
+import weka.gui.graphvisualizer.GraphNode;
+import weka.gui.graphvisualizer.GraphEdge;
+
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JCheckBox;
+import javax.swing.ButtonGroup;
+import javax.swing.BorderFactory;
+import javax.swing.JProgressBar;
+
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+
+/**
+ * This class lays out the vertices of a graph in a
+ * hierarchy of vertical levels, with a number of nodes
+ * in each level. The number of levels is the depth of
+ * the deepest child reachable from some parent at level
+ * 0. It implements a layout technique as described by
+ * K. Sugiyama, S. Tagawa, and M. Toda. in "Methods for
+ * visual understanding of hierarchical systems", IEEE
+ * Transactions on Systems, Man and Cybernetics,
+ * SMC-11(2):109-125, Feb. 1981.
+ * <p>There have been a few modifications made, however.
+ * The crossings function is changed as it was non-linear
+ * in time complexity. Furthermore, we don't have any
+ * interconnection matrices for each level, instead we
+ * just have one big interconnection matrix for the
+ * whole graph and a int[][] array which stores the
+ * vertices present in each level.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ *
+ */
+public class HierarchicalBCEngine implements GraphConstants, LayoutEngine {
+  
+  /** FastVector containing nodes and edges */
+  protected FastVector m_nodes, m_edges;
+  /**  FastVector containing listeners for
+    * layoutCompleteEvent generated by this
+    * LayoutEngine  */
+  protected FastVector layoutCompleteListeners;
+  /**   Interconnection matrix for the graph  */
+  protected int graphMatrix[][];
+  /** Array containing the indices of nodes in each level.
+    * The nodeLevels.length is equal to the number of levels  */
+  protected int nodeLevels[][];
+  /** The nodeWidth and nodeHeight */
+  protected int m_nodeWidth, m_nodeHeight;
+  
+  /* The following radio buttons control the
+     way the layout is performed. If any
+     of the following is changed then we
+     need to perform a complete relayout
+   */
+  protected JRadioButton m_jRbNaiveLayout;
+  protected JRadioButton m_jRbPriorityLayout;
+  protected JRadioButton m_jRbTopdown;
+  protected JRadioButton m_jRbBottomup;
+  
+  /** controls edge concentration by concentrating multilple singular dummy
+   * child nodes into one plural dummy child node
+   */
+  protected JCheckBox m_jCbEdgeConcentration;
+  
+  /** The panel containing extra options,
+   * specific to this LayoutEngine, for
+   * greater control over layout of the graph */
+  protected JPanel m_controlsPanel;
+  
+  /** The progress bar to show the progress
+   * of the layout process */
+  protected JProgressBar m_progress;
+  
+  /** This tells the the LayoutGraph method if
+   * a completeReLayout should be performed
+   * when it is called. */
+  protected boolean m_completeReLayout=false;
+  
+  /** This contains the original size of the nodes vector
+   * when it was passed in through the constructor, before
+   * adding all the dummy vertices */
+  private int origNodesSize;
+  
+  /** Constructor - takes in FastVectors of nodes and edges, and the initial
+   *  width and height of a node
+   */
+  public HierarchicalBCEngine(FastVector nodes, FastVector edges, int nodeWidth, 
+                              int nodeHeight) {
+    m_nodes = nodes; m_edges = edges; m_nodeWidth = nodeWidth; 
+    m_nodeHeight = nodeHeight;
+    makeGUIPanel(false);
+  }
+  
+  /** Constructor - takes in FastVectors of nodes and edges, the initial width
+   * and height of a node, and a boolean value to indicate if the edges
+   * should be concentrated.
+   * @param nodes - FastVector containing all the nodes
+   * @param edges - FastVector containing all the edges
+   * @param nodeWidth - A node's allowed width
+   * @param nodeHeight - A node's allowed height
+   * @param edgeConcentration - True: if want to concentrate edges,
+   * False: otherwise
+   */
+  public HierarchicalBCEngine(FastVector nodes, FastVector edges,
+  int nodeWidth, int nodeHeight, boolean edgeConcentration) {
+    m_nodes = nodes; m_edges = edges; m_nodeWidth = nodeWidth; 
+    m_nodeHeight = nodeHeight;
+    makeGUIPanel(edgeConcentration);
+  }
+  
+  /** SimpleConstructor
+   * If we want to instantiate the class first, and if information for
+   * nodes and edges is not available. However, we would have to manually
+   * provide all the information later on by calling setNodesEdges and
+   * setNodeSize methods
+   */
+  public HierarchicalBCEngine() {   }
+  
+  /**
+   * This methods makes the gui extra controls panel "m_controlsPanel"
+   */
+  protected void makeGUIPanel(boolean edgeConc) {
+    m_jRbNaiveLayout = new JRadioButton("Naive Layout");
+    m_jRbPriorityLayout = new JRadioButton("Priority Layout");
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(m_jRbNaiveLayout);
+    bg.add(m_jRbPriorityLayout);
+    m_jRbPriorityLayout.setSelected(true);
+    
+    ActionListener a = new ActionListener() {
+      public void actionPerformed(ActionEvent ae) {
+        m_completeReLayout=true;
+      }
+    };
+    
+    m_jRbTopdown = new JRadioButton("Top Down");
+    m_jRbBottomup = new JRadioButton("Bottom Up");
+    m_jRbTopdown.addActionListener(a);
+    m_jRbBottomup.addActionListener(a);
+    bg = new ButtonGroup();
+    bg.add(m_jRbTopdown);
+    bg.add(m_jRbBottomup);
+    m_jRbBottomup.setSelected(true);
+    
+    m_jCbEdgeConcentration = new JCheckBox("With Edge Concentration", edgeConc);
+    m_jCbEdgeConcentration.setSelected(edgeConc);
+    m_jCbEdgeConcentration.addActionListener(a);
+    
+    JPanel jp1 = new JPanel( new GridBagLayout() );
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = gbc.REMAINDER;
+    gbc.anchor = gbc.NORTHWEST;
+    gbc.weightx = 1;
+    gbc.fill = gbc.HORIZONTAL;
+    jp1.add(m_jRbNaiveLayout, gbc);
+    jp1.add(m_jRbPriorityLayout, gbc);
+    jp1.setBorder( BorderFactory.createTitledBorder("Layout Type") );
+    
+    JPanel jp2 = new JPanel( new GridBagLayout() );
+    jp2.add(m_jRbTopdown, gbc);
+    jp2.add(m_jRbBottomup, gbc);
+    jp2.setBorder( BorderFactory.createTitledBorder("Layout Method") );
+    
+    
+    m_progress = new JProgressBar(0, 11);
+    m_progress.setBorderPainted(false);
+    m_progress.setStringPainted(true);
+    m_progress.setString("");
+    
+    m_progress.setValue(0);
+    
+    m_controlsPanel = new JPanel( new GridBagLayout() );
+    m_controlsPanel.add(jp1, gbc);
+    m_controlsPanel.add(jp2, gbc);
+    m_controlsPanel.add(m_jCbEdgeConcentration, gbc);
+  }
+  
+    /** give access to set of graph nodes */
+    public FastVector getNodes() {
+	return m_nodes;
+    }
+  
+  /** This method returns a handle to the extra
+   * controls panel, so that the visualizing
+   * class can add it to some of it's own
+   * gui panel.
+   */
+  public JPanel getControlPanel() {
+    return m_controlsPanel;
+  }
+  
+  /** Returns a handle to the progressBar
+   * of this LayoutEngine.
+   */
+  public JProgressBar getProgressBar() {
+    return m_progress;
+  }
+  
+  /** Sets the nodes and edges for this LayoutEngine.
+   * Must be used if the class created by simple
+   * HierarchicalBCEngine() constructor.
+   * @param nodes - FastVector containing all the nodes
+   * @param edges - FastVector containing all the edges
+   */
+  public void setNodesEdges(FastVector nodes, FastVector edges) {
+    m_nodes = nodes; m_edges = edges;
+  }
+  
+  /**
+   * Sets the size of a node. This method must be
+   *	used if the class created by simple
+   *	HierarchicalBCEngine() constructor.
+   *	@param nodeWidth - A node's allowed width
+   *	@param nodeHeight - A node's allowed height
+   */
+  public void setNodeSize(int nodeWidth, int nodeHeight) {
+    m_nodeWidth = nodeWidth;
+    m_nodeHeight = nodeHeight;
+  }
+  
+  /**
+   * Method to add a LayoutCompleteEventListener
+   * @param l - Listener to receive the LayoutCompleteEvent by this
+   *            class.
+   */
+  public void addLayoutCompleteEventListener(LayoutCompleteEventListener l) {
+    if(layoutCompleteListeners==null)
+      layoutCompleteListeners = new FastVector();
+    layoutCompleteListeners.addElement(l);
+  }
+  
+  /**
+   * Method to remove a LayoutCompleteEventListener.
+   * @param e - The LayoutCompleteEventListener to remove.
+   */
+  public void removeLayoutCompleteEventListener(LayoutCompleteEventListener e){
+    if(layoutCompleteListeners!=null) {
+      LayoutCompleteEventListener l;
+      
+      for(int i=0; i<layoutCompleteListeners.size(); i++) {
+        l = (LayoutCompleteEventListener) layoutCompleteListeners.elementAt(i);
+        if(l==e) {
+          layoutCompleteListeners.removeElementAt(i);
+          return;
+        }
+      }
+      System.err.println("layoutCompleteListener to be remove not present");
+    }
+    else
+      System.err.println("layoutCompleteListener to be remove not present");
+  }
+  
+  /**
+   * Fires a LayoutCompleteEvent.
+   * @param e - The LayoutCompleteEvent to fire
+   */
+  public void fireLayoutCompleteEvent(LayoutCompleteEvent e) {
+    if(layoutCompleteListeners!=null && layoutCompleteListeners.size()!=0) {
+      LayoutCompleteEventListener l;
+      
+      for(int i=0; i<layoutCompleteListeners.size(); i++) {
+        l = (LayoutCompleteEventListener) layoutCompleteListeners.elementAt(i);
+        l.layoutCompleted(e);
+      }
+    }
+  }
+  
+  
+  /**
+   * This method does a complete layout of the graph which includes
+   * removing cycles, assigning levels to nodes, reducing edge crossings
+   * and laying out the vertices horizontally for better visibility.
+   * The removing of cycles and assignment of levels is only performed
+   * if hasn't been performed earlier or if some layout option has been
+   * changed and it is necessary to do so. It is necessary to do so,
+   * if the user selects/deselects edge concentration or topdown/bottomup
+   * options.
+   * <p> The layout is performed in a separate thread and the progress
+   * bar of the class is updated for each of the steps as the process
+   * continues.
+   */
+  public void layoutGraph() {
+  //cannot perform a layout if no description of nodes and/or edges is provided
+    if(m_nodes==null || m_edges==null)
+      return; 
+    
+    Thread th = new Thread() {
+      public void run() {
+        m_progress.setBorderPainted(true);
+        if(nodeLevels==null) {
+          makeProperHierarchy();
+        }
+        else if(m_completeReLayout==true) {
+          clearTemps_and_EdgesFromNodes(); makeProperHierarchy(); 
+          m_completeReLayout=false;
+        }
+        
+        //minimizing crossings
+        if(m_jRbTopdown.isSelected()) {
+          int crossbefore=crossings(nodeLevels), crossafter=0, i=0;
+          do {
+            m_progress.setValue(i+4);
+            m_progress.setString("Minimizing Crossings: Pass"+(i+1));
+            if(i!=0)
+              crossbefore = crossafter;
+            nodeLevels = minimizeCrossings(false, nodeLevels);
+            crossafter = crossings(nodeLevels);
+            i++;
+          }
+          while(crossafter<crossbefore && i<6);
+        }
+        else {
+          int crossbefore=crossings(nodeLevels), crossafter=0, i=0;
+          do {
+            m_progress.setValue(i+4);
+            m_progress.setString("Minimizing Crossings: Pass"+(i+1));
+            if(i!=0)
+              crossbefore = crossafter;
+            nodeLevels = minimizeCrossings(true, nodeLevels);
+            crossafter = crossings(nodeLevels);
+            i++;
+          }
+          while(crossafter<crossbefore && i<6);
+        }
+        //System.out.println("\nCrossings at the end "+
+        //                   crossings(nodeLevels)+
+        //                   "\n---------------------------------");
+        
+        m_progress.setValue(10);
+        m_progress.setString("Laying out vertices");
+        //Laying out graph
+        if(m_jRbNaiveLayout.isSelected())
+          naiveLayout();
+        else
+          priorityLayout1();
+        m_progress.setValue(11);
+        m_progress.setString("Layout Complete");
+        m_progress.repaint();
+        
+        fireLayoutCompleteEvent( new LayoutCompleteEvent(this) );
+        m_progress.setValue(0);
+        m_progress.setString("");
+        m_progress.setBorderPainted(false);
+      }
+    };
+    th.start();
+  }
+  
+  /**
+   * This method removes the temporary nodes that were
+   * added to fill in the gaps, and removes all edges
+   * from all nodes in their edges[][] array
+   */
+  protected void clearTemps_and_EdgesFromNodes() {
+    /*
+    System.out.println("Before.............");
+    for(int i=0; i<m_nodes.size(); i++) {
+      GraphNode n = (GraphNode)m_nodes.elementAt(i);
+      System.out.println("N: "+n.ID+","+i);
+    }
+    System.out.println("origNodesSize: "+origNodesSize);
+    */
+    
+    int curSize = m_nodes.size();
+    for(int i=origNodesSize; i<curSize; i++)
+      m_nodes.removeElementAt(origNodesSize);
+    for(int j=0; j<m_nodes.size(); j++)
+      ((GraphNode)m_nodes.elementAt(j)).edges = null;
+    
+    nodeLevels=null;
+    
+    /*
+    System.out.println("After..............");
+    for(int i=0; i<m_nodes.size(); i++) {
+      GraphNode n = (GraphNode)m_nodes.elementAt(i);
+      System.out.println("N: "+n.ID+","+i);
+    }
+    */
+  }
+  
+  /**
+   * This method makes the "graphMatrix" interconnection
+   * matrix for the graph given by m_nodes and m_edges
+   * vectors. The matix is used by other methods.
+   */
+  protected void processGraph() {
+    origNodesSize = m_nodes.size();
+    graphMatrix = new int[m_nodes.size()][m_nodes.size()];
+    /*
+    System.out.println("The "+m_nodes.size()+" nodes are: ");
+    for(int i=0; i<m_nodes.size(); i++)
+      System.out.println((String)m_nodes.elementAt(i)+" ");
+    System.out.println("\nThe "+m_edges.size()+" edges are: ");
+    for(int i=0; i<m_edges.size(); i++)
+      System.out.println((GraphEdge)m_edges.elementAt(i));
+    */
+    for(int i=0; i<m_edges.size(); i++)
+      graphMatrix[((GraphEdge)m_edges.elementAt(i)).src]
+                 [((GraphEdge)m_edges.elementAt(i)).dest] = 
+                                         ((GraphEdge)m_edges.elementAt(i)).type;
+    /*
+    System.out.print("\t");
+    for(int i=0; i<graphMatrix.length; i++)
+      System.out.print(((GraphNode)m_nodes.elementAt(i)).ID+" ");
+    System.out.println("");
+    for(int i=0; i<graphMatrix.length; i++) {
+      GraphNode n = (GraphNode)m_nodes.elementAt(i);
+      System.out.print(n.ID+"\t");
+      for(int j=0; j<graphMatrix[i].length; j++)
+        System.out.print(graphMatrix[i][j]+" ");
+      System.out.println("");
+    }
+    */
+  }
+  
+  /*
+   * This method makes a proper hierarchy of the graph
+   * by first removing cycles, then assigning levels
+   * to the nodes and then removing gaps by adding
+   * dummy vertices.
+   */
+  protected void makeProperHierarchy() {
+    processGraph();
+    
+    m_progress.setValue(1);
+    m_progress.setString("Removing Cycles");
+    //****removing cycles
+    removeCycles();
+    
+    m_progress.setValue(2);
+    m_progress.setString("Assigning levels to nodes");
+    //****Assigning vertical level to each node
+    int nodesLevel[] = new int[m_nodes.size()];
+    int depth=0;
+    for(int i=0; i<graphMatrix.length; i++) {
+      assignLevels(nodesLevel, depth, i, 0);
+    }
+    
+    for(int i=0; i<nodesLevel.length; i++) {
+      if(nodesLevel[i]==0) {
+        int min=65536;
+        for(int j=0; j<graphMatrix[i].length; j++) {
+          if(graphMatrix[i][j]==DIRECTED)
+            if(min>nodesLevel[j])
+              min = nodesLevel[j];
+        }
+        //if the shallowest child of a parent has a depth greater than 1
+        // and it is not a lone parent with no children
+        if(min!=65536 && min>1) 
+          nodesLevel[i]=min-1;       
+      }
+    }
+    
+    //System.out.println("");
+    int maxLevel=0;
+    for(int i=0; i<nodesLevel.length; i++) {
+      if(nodesLevel[i]>maxLevel)
+        maxLevel = nodesLevel[i];
+      //System.out.println( ((GraphNode)m_nodes.elementAt(i)).ID+" "+i+">"+
+      //                    nodesLevel[i]);
+    }
+    int levelCounts[] = new int[maxLevel+1];
+    
+    for(int i=0; i<nodesLevel.length; i++) {
+      levelCounts[nodesLevel[i]]++;
+    }
+    //System.out.println("------------------------------------------");
+    //****Assigning nodes to each level
+    int levelsCounter[] = new int[maxLevel+1];
+    nodeLevels = new int[maxLevel+1][];
+    for(byte i=0; i<nodesLevel.length; i++) {
+      if(nodeLevels[nodesLevel[i]]==null)
+        nodeLevels[nodesLevel[i]] = new int[ levelCounts[nodesLevel[i]] ];
+      //nodeLevels[nodesLevel[i]].addElement(new Integer(i));
+      //System.out.println(((GraphNode)m_nodes.elementAt(i)).ID+" "+
+      //                   nodesLevel[i]+">"+levelCounts[nodesLevel[i]]);
+      nodeLevels[nodesLevel[i]][levelsCounter[nodesLevel[i]]++] = i;
+    }
+    
+    m_progress.setValue(3);
+    m_progress.setString("Removing gaps by adding dummy vertices");
+    //*Making a proper Hierarchy by putting in dummy vertices to close all gaps
+    if(m_jCbEdgeConcentration.isSelected())
+      removeGapsWithEdgeConcentration(nodesLevel);
+    else
+      removeGaps(nodesLevel);
+    
+    //After assigning levels and adding dummy vertices
+    //System.out.print("\n\t");
+    //for(int i=0; i<graphMatrix.length; i++)
+    //    System.out.print(((GraphNode)m_nodes.elementAt(i)).ID+" ");
+    //System.out.println("");
+    //for(int i=0; i<graphMatrix.length; i++) {
+    //    System.out.print(((GraphNode)m_nodes.elementAt(i)).ID+"\t");
+    //    for(int j=0; j<graphMatrix[i].length; j++)
+    //	System.out.print(graphMatrix[i][j]+" ");
+    //    System.out.println("");
+    //}
+    
+    //****Updating edges[][] array of nodes from
+    //****the interconnection matrix, which will be used
+    //****by the Visualizer class to draw edges
+    for(int i=0; i<graphMatrix.length; i++) {
+      GraphNode n = (GraphNode)m_nodes.elementAt(i);
+      int sum=0;
+      for(int j=0; j<graphMatrix[i].length; j++)
+        if(graphMatrix[i][j]!=0)
+          sum++;
+      
+      n.edges = new int[sum][2];
+      for(int j=0, k=0; j<graphMatrix[i].length; j++)
+        if(graphMatrix[i][j]!=0) {
+          n.edges[k][0] = j;
+          n.edges[k][1] = graphMatrix[i][j]; k++;
+        }
+      //n.edges = graphMatrix[i];
+    }
+    
+    //Interconnection matrices at each level, 1 to n-1
+    //printMatrices(nodeLevels);
+  }
+  
+  
+  /**
+   * This method removes gaps from the graph. It doesn't perform
+   * any concentration. It takes as an argument of int[]
+   * of length m_nodes.size() containing the level of each
+   * node.
+   */
+  private void removeGaps(int nodesLevel[]) {
+    int temp = m_nodes.size();
+    int temp2=graphMatrix[0].length, tempCnt=1;
+    
+    for(int n=0; n<temp; n++) {
+      for(int i=0; i<temp2; i++) {
+        int len = graphMatrix.length;
+        if(graphMatrix[n][i]>0)
+          if(nodesLevel[i]>nodesLevel[n]+1) {
+            int tempMatrix[][] =
+            new int[graphMatrix.length+(nodesLevel[i]-nodesLevel[n]-1)]
+                   [graphMatrix.length+(nodesLevel[i]-nodesLevel[n]-1)];
+            int level = nodesLevel[n]+1;
+            copyMatrix(graphMatrix, tempMatrix);
+            
+            String s1 = new String("S"+tempCnt++);
+            m_nodes.addElement(new GraphNode(s1, s1, SINGULAR_DUMMY)); //true));
+            int temp3 [] = new int[nodeLevels[level].length+1];
+            //for(int j=0; j<nodeLevels[level].length; j++)
+            //    temp3[j] = nodeLevels[level][j];
+            System.arraycopy(nodeLevels[level], 0, temp3, 
+                             0, nodeLevels[level].length);
+            temp3[temp3.length-1] = m_nodes.size()-1;
+            nodeLevels[level] = temp3; level++;
+            //nodeLevels[level++].addElement(new Integer(m_nodes.size()-1));
+            //System.out.println("len:"+len+","+nodesLevel[i]+","+
+            //                   nodesLevel[n]);
+            
+            int k;
+            for(k=len; k<len+nodesLevel[i]-nodesLevel[n]-1-1; k++) {
+              String s2 =  new String("S"+tempCnt);
+              m_nodes.addElement( new GraphNode(s2, s2, SINGULAR_DUMMY) ); //true) );
+              temp3 = new int[nodeLevels[level].length+1];
+              //for(int j=0; j<nodeLevels[level].length; j++)
+              //	temp3[j] = nodeLevels[level][j];
+              System.arraycopy(nodeLevels[level], 0, temp3, 
+                               0, nodeLevels[level].length);
+              temp3[temp3.length-1] = m_nodes.size()-1;
+              nodeLevels[level++] = temp3;
+              //nodeLevels[level++].addElement(new Integer(m_nodes.size()-1));
+              tempMatrix[k][k+1]=tempMatrix[n][i]; tempCnt++;
+              if(k>len)
+                tempMatrix[k][k-1]=-1*tempMatrix[n][i];
+            }
+            
+            //temp[lastTempNodeCreated][targetNode]=temp[origNode][targetNode]
+            tempMatrix[k][i]=tempMatrix[n][i];
+            //System.out.println("k "+((GraphNode)m_nodes.elementAt(k)).ID+
+            //		   " i "+((GraphNode)m_nodes.elementAt(i)).ID+
+            //		   " n "+((GraphNode)m_nodes.elementAt(n)).ID+
+            //		   " len "+((GraphNode)m_nodes.elementAt(len)).ID );
+            //temp[origNode][firstTempNodecreated] = temp[origNode][targetNode]
+            tempMatrix[n][len] = tempMatrix[n][i]; 
+            //temp[firstTempNodeCreated][origNode]  for reverse tracing
+            tempMatrix[len][n] = -1*tempMatrix[n][i];
+             //temp[targetNode][lastTempNodecreated]  for reverse tracing
+            tempMatrix[i][k]   = -1*tempMatrix[n][i]; 
+            
+            //temp[lastTempNodeCreated][secondlastNode] for reverse tracing
+            //but only do this if more than 1 temp nodes are created
+            if(k>len)                                  
+              tempMatrix[k][k-1] = -1*tempMatrix[n][i];
+            
+            //temp[origNode][targetNode] = 0 unlinking as they have been
+            // linked by a chain of temporary nodes now.
+            tempMatrix[n][i]   = 0;                    
+            tempMatrix[i][n]   = 0;                    
+            
+            graphMatrix = tempMatrix;
+          }
+          else {
+            //****Even if there is no gap just add a reference for the
+            //****parent to the child for reverse tracing, useful if the
+            //****there is a reversed edge from parent to child and therefore
+            //****visualizer would know to highlight this edge  when 
+            //****highlighting the child.
+            graphMatrix[i][n]=-1*graphMatrix[n][i];
+          }
+      }
+    }
+    //Interconnection matrices at each level, 1 to n-1 after minimizing edges
+    //printMatrices(nodeLevels);
+  }
+  
+  
+  /**
+   * This method removes gaps from the graph. It tries to minimise the number
+   * of edges by concentrating multiple dummy nodes from the same parent and
+   * on the same vertical level into one. It takes as an argument of int[]
+   * of length m_nodes.size() containing the level of each
+   * node.
+   */
+  private void removeGapsWithEdgeConcentration(int nodesLevel[]) {
+    
+    final int  temp = m_nodes.size(), temp2=graphMatrix[0].length;
+    int tempCnt=1;
+    
+    for(int n=0; n<temp; n++) {
+      for(int i=0; i<temp2; i++) {
+        if(graphMatrix[n][i]>0)
+          if(nodesLevel[i]>nodesLevel[n]+1) {
+            //System.out.println("Processing node "+
+            //                   ((GraphNode)m_nodes.elementAt(n)).ID+
+            //                   " for "+((GraphNode)m_nodes.elementAt(i)).ID);
+            int tempLevel=nodesLevel[n];
+            boolean tempNodePresent=false;
+            int k=temp;
+            int tempnode = n;
+            
+            while(tempLevel < nodesLevel[i]-1 ) {
+              tempNodePresent=false;
+              for(; k<graphMatrix.length; k++) {
+                if(graphMatrix[tempnode][k]>0) {
+                  //System.out.println("tempnode will be true");
+                  tempNodePresent = true; break;
+                }
+              }
+              if(tempNodePresent) {
+                tempnode=k; k=k+1;
+                tempLevel++;
+              }
+              else {
+                if(tempnode!=n)
+                  tempnode=k-1;
+                //System.out.println("breaking from loop");
+                break;
+              }
+            }
+            if(((GraphNode)m_nodes.elementAt(tempnode)).nodeType==SINGULAR_DUMMY)
+              ((GraphNode)m_nodes.elementAt(tempnode)).nodeType=PLURAL_DUMMY;
+            if(tempNodePresent) {
+              //Link the last known temp node to target
+              graphMatrix[tempnode][i] = graphMatrix[n][i];
+              //System.out.println("modifying "+
+              //                   ((GraphNode)nodes.elementAt(tempnode)).ID+
+              //		   ", "+((GraphNode)nodes.elementAt(n)).ID);
+              /////matrix[lastknowntempnode][source]=-original_val
+              /////graphMatrix[tempnode][n] = -graphMatrix[n][i];
+              //System.out.println("modifying "+
+              //                   ((GraphNode)nodes.elementAt(i)).ID+
+              //		   ", "+
+              //                   ((GraphNode)nodes.elementAt(tempnode)).ID);
+              
+              //and matrix[target][lastknowntempnode]=-original_val
+              //for reverse tracing
+              graphMatrix[i][tempnode] = -graphMatrix[n][i];
+              //unlink source from the target
+              graphMatrix[n][i] = 0;
+              graphMatrix[i][n] = 0;
+              continue;
+              
+            }
+            
+            int len = graphMatrix.length;
+            int tempMatrix[][] =
+            new int[graphMatrix.length+(nodesLevel[i]-nodesLevel[tempnode]-1)]
+            [graphMatrix.length+(nodesLevel[i]-nodesLevel[tempnode]-1)];
+            int level = nodesLevel[tempnode]+1;
+            copyMatrix(graphMatrix, tempMatrix);
+            
+            String s1 = new String("S"+tempCnt++);
+            //System.out.println("Adding dummy "+s1);
+            m_nodes.addElement(new GraphNode(s1, s1, SINGULAR_DUMMY));
+            
+            int temp3 [] = new int[nodeLevels[level].length+1];
+            System.arraycopy(nodeLevels[level], 0, temp3, 
+                             0, nodeLevels[level].length);
+            temp3[temp3.length-1] = m_nodes.size()-1;
+            nodeLevels[level] = temp3;
+            temp3 = new int[m_nodes.size()+1];
+            System.arraycopy(nodesLevel, 0, temp3, 0, nodesLevel.length);
+            temp3[m_nodes.size()-1] = level;
+            nodesLevel = temp3;
+            level++;
+            //nodeLevels[level++].addElement(new Integer(m_nodes.size()-1));
+            //System.out.println("len:"+len+"("+
+            //                   ((GraphNode)m_nodes.elementAt(len)).ID+"),"+
+            //                   nodesLevel[i]+","+nodesLevel[tempnode]);
+            
+            int m;
+            for(m=len; m<len+nodesLevel[i]-nodesLevel[tempnode]-1-1; m++) {
+              String s2 =  new String("S"+tempCnt++);
+              //System.out.println("Adding dummy "+s2);
+              m_nodes.addElement( new GraphNode(s2, s2, SINGULAR_DUMMY) );
+              temp3 = new int[nodeLevels[level].length+1];
+              //for(int j=0; j<nodeLevels[level].length; j++)
+              //	temp3[j] = nodeLevels[level][j];
+              System.arraycopy(nodeLevels[level], 0, temp3, 
+                               0, nodeLevels[level].length);
+              temp3[temp3.length-1] = m_nodes.size()-1;
+              nodeLevels[level] = temp3;
+              temp3 = new int[m_nodes.size()+1];
+              System.arraycopy(nodesLevel, 0, temp3, 0, nodesLevel.length);
+              temp3[m_nodes.size()-1] = level;
+              nodesLevel = temp3;
+              level++;
+              //nodeLevels[level++].addElement(new Integer(m_nodes.size()-1));
+              //System.out.println("modifying "+
+              //                   ((GraphNode)nodes.elementAt(m)).ID+", "+
+              //                   ((GraphNode)nodes.elementAt(m+1)).ID);
+              tempMatrix[m][m+1]=tempMatrix[n][i]; //tempCnt++;
+              if(m>len) {
+                //System.out.println("modifying "+
+                //                   ((GraphNode)nodes.elementAt(m)).ID+
+                //                   ", "+((GraphNode)nodes.elementAt(m-1)).ID);
+                tempMatrix[m][m-1]=-1*tempMatrix[n][i];
+              }
+            }
+            //System.out.println("m "+((GraphNode)m_nodes.elementAt(m)).ID+
+            //	   " i "+((GraphNode)m_nodes.elementAt(i)).ID+
+            //     " tempnode "+((GraphNode)m_nodes.elementAt(tempnode)).ID+
+            //	   " len "+((GraphNode)m_nodes.elementAt(len)).ID );
+            //System.out.println("modifying "+
+            //                   ((GraphNode)nodes.elementAt(m)).ID+", "+
+            //                   ((GraphNode)nodes.elementAt(i)).ID);
+            
+            //temp[lastTempNodeCreated][targetNode]=temp[origNode][targetNode]
+            tempMatrix[m][i]=tempMatrix[n][i];
+            //System.out.println("modifying "+
+            //                   ((GraphNode)nodes.elementAt(tempnode)).ID+", "+
+            //                   ((GraphNode)nodes.elementAt(len)).ID);
+            
+            //temp[origNode][firstTempNodecreated] = temp[origNode][targetNode]
+            tempMatrix[tempnode][len] =  tempMatrix[n][i];
+            //System.out.println("modifying "+
+            //                   ((GraphNode)nodes.elementAt(len)).ID+", "+
+            //                   ((GraphNode)nodes.elementAt(tempnode)).ID);
+            
+            //temp[firstTempNodeCreated][origNode]  for reverse tracing
+            tempMatrix[len][tempnode] = -1*tempMatrix[n][i]; 
+            //System.out.println("modifying "+
+            //                   ((GraphNode)nodes.elementAt(i)).ID+", "+
+            //                   ((GraphNode)nodes.elementAt(m)).ID);
+            
+            //temp[targetNode][lastTempNodecreated]  for reverse tracing
+            tempMatrix[i][m] = -1*tempMatrix[n][i];
+            if(m>len) {
+              //System.out.println("modifying "+
+              //                   ((GraphNode)nodes.elementAt(m)).ID+
+              //                   ", "+((GraphNode)nodes.elementAt(m-1)).ID);
+
+              //temp[lastTempNodeCreated][secondlastNode] for reverse tracing
+              //but only do this if more than 1 temp nodes are created
+              tempMatrix[m][m-1] = -1*tempMatrix[n][i]; 
+            }
+
+            //temp[origNode][targetNode] = 0 unlinking as they have been
+            tempMatrix[n][i]   = 0;
+            
+            //linked by a chain of temporary nodes now.
+            tempMatrix[i][n]   = 0;
+            
+            graphMatrix = tempMatrix;
+          }
+          else {
+            //System.out.println("modifying "+
+            //                   ((GraphNode)nodes.elementAt(i)).ID+", "+
+            //                   ((GraphNode)nodes.elementAt(n)).ID);
+            //****Even if there is no gap just add a reference for the
+            //****parent to the child for reverse tracing, useful if the
+            //****there is a reversed edge from parent to child and therefore
+            //****visualizer would know to highlight this edge  when
+            //****highlighting the child.
+            graphMatrix[i][n]=-1*graphMatrix[n][i];
+          }
+      }
+    }
+    
+  }
+  
+  
+  /**
+   * Returns the index of an element in a level.
+   * Must never be called with the wrong element and
+   * the wrong level, will throw an exception otherwise.
+   * It takes as agrument the index of the element (in the m_nodes vector)
+   * and the level it is supposed to be in (as each level contains the indices
+   * of the nodes present in that level).
+   */
+  private int indexOfElementInLevel(int element, int level[]) throws Exception {
+    int idx;
+    
+    for(int i=0; i<level.length; i++)
+      if(level[i]==element)
+        return i;
+    throw new Exception("Error. Didn't find element "+
+                        ((GraphNode)m_nodes.elementAt(element)).ID+
+                        " in level. Inspect code for "+
+                        "weka.gui.graphvisualizer.HierarchicalBCEngine");
+  }
+  
+  
+  
+  /**
+   * Computes the number of edge crossings in the whole graph
+   * Takes as an argument levels of nodes. It is essentially the
+   * same algorithm provided in Universitat des Saarlandes
+   * technical report A03/94 by Georg Sander.
+   */
+  protected int crossings(final int levels[][]) {
+    int sum=0;
+    
+    for(int i=0; i<levels.length-1; i++) {
+      //System.out.println("*****************Processing level "+i+
+      //                   "*****************************");
+      MyList upper = new MyList(), lower = new MyList();
+      MyListNode lastOcrnce[] = new MyListNode[m_nodes.size()];
+      int edgeOcrnce[] = new int[m_nodes.size()];
+      
+      for(int j=0,uidx=0,lidx=0; j<(levels[i].length+levels[i+1].length); j++) {
+        if((j%2==0 && uidx<levels[i].length) || lidx>=levels[i+1].length) {
+          int k1=0, k2=0, k3=0;
+          GraphNode n = (GraphNode) m_nodes.elementAt(levels[i][uidx]);
+          //Deactivating and counting crossings for all edges ending in it
+          //coming from bottom left
+          if(lastOcrnce[levels[i][uidx]]!=null) {
+            MyListNode temp = new MyListNode(-1); temp.next = upper.first;
+            try {
+              do {
+                temp = temp.next;
+                if(levels[i][uidx]==temp.n) {
+                  k1 = k1+1;
+                  k3 = k3+k2;
+                  //System.out.println("Removing from upper: "+temp.n);
+                  upper.remove(temp);
+                }
+                else
+                  k2 = k2+1;
+              } while(temp!=lastOcrnce[levels[i][uidx]]);
+            }
+            catch(NullPointerException ex) {
+              System.out.println("levels[i][uidx]: "+levels[i][uidx]+
+              " which is: "+((GraphNode)m_nodes.elementAt(levels[i][uidx])).ID+
+              " temp: "+temp+
+              " upper.first: "+upper.first);
+            ex.printStackTrace();
+            System.exit(-1);}
+            
+            lastOcrnce[levels[i][uidx]]=null;
+            sum = sum + k1*lower.size() + k3;
+          }
+          //Activating all the edges going out towards the bottom 
+          //and bottom right
+          for(int k=0; k<n.edges.length; k++) {
+            if(n.edges[k][1]>0)
+              try {
+                if( indexOfElementInLevel(n.edges[k][0], levels[i+1]) >= uidx) {
+                  edgeOcrnce[n.edges[k][0]]=1;
+                }
+              }
+              catch(Exception ex) {
+                ex.printStackTrace();
+              }
+          }
+          for(int k=0; k<levels[i+1].length; k++) {
+            if(edgeOcrnce[levels[i+1][k]]==1) {
+              MyListNode temp = new MyListNode(levels[i+1][k]); //new MyListNode(n.edges[k][0]);
+              lower.add(temp);
+              lastOcrnce[levels[i+1][k]] = temp;
+              edgeOcrnce[levels[i+1][k]] = 0;
+              //System.out.println("Adding to lower: "+levels[i+1][k]+
+              //" which is: "+((GraphNode)m_nodes.elementAt(levels[i+1][k])).ID+
+              //" first's n is: "+lower.first.n);
+            }
+            
+          }
+          uidx++;
+        }
+        else {
+          int k1=0, k2=0, k3=0;
+          GraphNode n = (GraphNode) m_nodes.elementAt(levels[i+1][lidx]);
+          //Deactivating and counting crossings for all edges ending in it
+          //coming from up and upper left
+          if(lastOcrnce[levels[i+1][lidx]]!=null) {
+            
+            MyListNode temp = new MyListNode(-1); temp.next = lower.first;
+            try {
+              do {
+                temp = temp.next;
+                if(levels[i+1][lidx]==temp.n) {
+                  k1 = k1+1;
+                  k3 = k3+k2;
+                  lower.remove(temp);
+                  //System.out.println("Removing from lower: "+temp.n);
+                }
+                else
+                  k2 = k2+1;
+                //System.out.println("temp: "+temp+" lastOcrnce: "+
+                //                   lastOcrnce[levels[i+1][lidx]]+" temp.n: "+
+                //                   temp.n+" lastOcrnce.n: "+
+                //                   lastOcrnce[levels[i+1][lidx]].n);
+              } while(temp!=lastOcrnce[levels[i+1][lidx]]);
+            }
+            catch(NullPointerException ex) {
+              System.out.print("levels[i+1][lidx]: "+levels[i+1][lidx]+
+              " which is: "+
+              ((GraphNode)m_nodes.elementAt(levels[i+1][lidx])).ID+
+              " temp: "+temp);
+              System.out.println(" lower.first: "+lower.first);
+              ex.printStackTrace();
+              System.exit(-1); 
+            }
+            
+            lastOcrnce[levels[i+1][lidx]]=null;
+            sum = sum + k1*upper.size() + k3;
+          }
+          
+          //Activating all the edges going out towards the upper right
+          for(int k=0; k<n.edges.length; k++) {
+            if(n.edges[k][1]<0)
+              try {
+                if( indexOfElementInLevel(n.edges[k][0], levels[i]) > lidx) {
+                  edgeOcrnce[n.edges[k][0]]=1;
+                }
+              }
+              catch(Exception ex) {
+                ex.printStackTrace();
+              }
+          }
+          for(int k=0; k<levels[i].length; k++) {
+            if(edgeOcrnce[levels[i][k]]==1) {
+              MyListNode temp = new MyListNode(levels[i][k]);
+              upper.add(temp);
+              lastOcrnce[levels[i][k]]=temp;
+              edgeOcrnce[levels[i][k]]=0;
+              //System.out.println("Adding to upper: "+levels[i][k]+
+              //	       " which is : "+
+              //                ((GraphNode)m_nodes.elementAt(levels[i][k])).ID+
+              //	       " from node: "+n.ID+", "+k+
+              //	       " first's value: "+upper.first.n);
+            }
+          }
+          lidx++;
+        }
+      }
+      //System.out.println("Sum at the end is: "+sum);
+    }
+    
+    return sum;
+  }
+  
+  
+  /**
+   * The following two methods remove cycles from the graph.
+   */
+  protected void removeCycles() {
+    //visited[x]=1 is only  visited AND visited[x]=2 means node is visited
+    // and is on the current path
+    int visited[]  = new int[m_nodes.size()];
+    
+    for(int i=0; i<graphMatrix.length; i++) {
+      if(visited[i]==0){
+        removeCycles2(i, visited);
+        visited[i]=1;
+      }
+    }
+  }
+  
+  /** This method should not be called directly. It should be called only from 
+      to call removeCycles() */
+  private void removeCycles2(int nindex, int visited[]) {
+    visited[nindex]=2;
+    for(int i=0; i<graphMatrix[nindex].length; i++) {
+      if(graphMatrix[nindex][i]==DIRECTED)
+        if(visited[i]==0) {
+          removeCycles2(i, visited);
+          visited[i]=1;
+        }
+        else if(visited[i]==2) {
+          if(nindex==i) {
+            graphMatrix[nindex][i]=0;
+          }
+          else if(graphMatrix[i][nindex]==DIRECTED) {
+            //System.out.println("\nFound double "+nindex+','+i);
+            graphMatrix[i][nindex]=DOUBLE;
+            graphMatrix[nindex][i]=-DOUBLE;
+          }
+          else {
+            //System.out.println("\nReversing "+nindex+','+i);
+            graphMatrix[i][nindex]=REVERSED;
+            graphMatrix[nindex][i]=-REVERSED;
+          }
+        }
+    }
+  }
+  
+  /**
+   * This method assigns a vertical level to each node.
+   * See makeProperHierarchy() to see how to use it.
+   */
+  protected void assignLevels(int levels[], int depth, int i, int j) {
+    //System.out.println(i+","+j);
+    if(i>=graphMatrix.length)
+      return;
+    else if(j>=graphMatrix[i].length)
+      return;
+    if(graphMatrix[i][j]<=0)
+      assignLevels(levels, depth, i, ++j);
+    else if(graphMatrix[i][j]==DIRECTED || graphMatrix[i][j]==DOUBLE) {
+      if(depth+1>levels[j]) {
+        levels[j]=depth+1;
+        assignLevels(levels, depth+1, j, 0);
+      }
+      assignLevels(levels, depth, i, ++j);
+    }
+  }
+  
+  
+  /**
+   * This method minimizes the number of edge crossings using the BaryCenter
+   * heuristics given by Sugiyama et al. 1981
+   * This method processes the graph topdown if reversed is false,
+   * otherwise it does bottomup.
+   */
+  private int[][] minimizeCrossings(boolean reversed, int nodeLevels[][]) {
+    
+    //Minimizing crossings using Sugiyama's method
+    if(reversed==false) {
+      for(int times=0; times<1; times++) {
+        int tempLevels[][] = new int[nodeLevels.length][];
+        
+        //System.out.println("---------------------------------");
+        //System.out.println("Crossings before PHaseID: "+
+        //                   crossings(nodeLevels));
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=0; i<nodeLevels.length-1; i++)   //Down
+          phaseID(i, tempLevels);
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        
+        //System.out.println("\nCrossings before PHaseIU: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=nodeLevels.length-2; i>=0; i--)  //Up
+          phaseIU(i, tempLevels);
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        
+        //System.out.println("\nCrossings before PHaseIID: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=0; i<nodeLevels.length-1; i++) {   //Down
+          phaseIID(i, tempLevels);
+        }
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        //System.out.println("Crossings temp:"+crossings(tempLevels)+
+        //                   " graph:"+crossings(nodeLevels));
+        //printMatrices(nodeLevels);
+        
+        //System.out.println("\nCrossings before PHaseIIU: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=nodeLevels.length-2; i>=0; i--) {   //Up
+          phaseIIU(i, tempLevels);
+        }
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        ///System.out.println("Crossings temp:"+crossings(tempLevels)+
+        //                    " graph:"+crossings(nodeLevels));
+        ///printMatrices(nodeLevels);
+        //System.out.println("\nCrossings after phaseIIU: "+
+        //                   crossings(nodeLevels));
+      }
+      return nodeLevels;
+    }
+    else {
+      for(int times=0; times<1; times++) {
+        int tempLevels[][] = new int[nodeLevels.length][];
+        
+        //System.out.println("---------------------------------");
+        //System.out.println("\nCrossings before PHaseIU: "+
+        //                   crossings(nodeLevels));
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=nodeLevels.length-2; i>=0; i--)  //Up
+          phaseIU(i, tempLevels);
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        //printMatrices(nodeLevels);
+        
+        //System.out.println("Crossings before PHaseID: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=0; i<nodeLevels.length-1; i++)   //Down
+          phaseID(i, tempLevels);
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        ///printMatrices(nodeLevels);
+        
+        //System.out.println("\nCrossings before PHaseIIU: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=nodeLevels.length-2; i>=0; i--) {   //Up
+          phaseIIU(i, tempLevels);
+        }
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        //printMatrices(nodeLevels);
+        
+        //System.out.println("\nCrossings before PHaseIID: "+
+        //                   crossings(nodeLevels));
+        tempLevels = new int[nodeLevels.length][];
+        copy2DArray(nodeLevels, tempLevels);
+        for(int i=0; i<nodeLevels.length-1; i++) {   //Down
+          phaseIID(i, tempLevels);
+        }
+        if(crossings(tempLevels)<crossings(nodeLevels))
+          nodeLevels = tempLevels;
+        ///printMatrices(nodeLevels);
+        //System.out.println("\nCrossings after phaseIID: "+
+        //                   crossings(nodeLevels));
+      }
+      return nodeLevels;
+    }
+  }
+  
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   * lindex is the index of the level we want to process.
+   * In this method we'll sort the vertices at the level one
+   * below lindex according to their UP-barycenters (or column
+   * barycenters).
+   */
+  protected void phaseID(final int lindex, final int levels[][]) {
+    float colBC[]; //= new float[levels[lindex+1].size()];
+    colBC = calcColBC(lindex, levels);
+    
+    //System.out.println("In ID Level"+(lindex+1)+":");
+    //System.out.print("\t");
+    //for(int i=0; i<colBC.length; i++)
+    //   {System.out.print("Col"+(i+1)+":"+colBC[i]+" ");
+    //    }
+    //System.out.println("");
+    //colBC = calcColBC1(lindex, levels);
+    //for(int i=0; i<colBC.length; i++)
+    //  {System.out.print("Col"+(i+1)+":"+colBC[i]+" ");
+    //    }
+    //System.out.println("");
+    //System.out.print("\n\tNodes ");
+    //for(int i=0; i<levels[lindex+1].length; i++)
+    //    System.out.print(levels[lindex+1][i]+" ");
+    //System.out.println("");
+    //System.out.println("\nCrossings: "+crossings(levels));
+    //inspect(false, lindex, levels, colBC);
+    
+    isort(levels[lindex+1], colBC);
+    //combSort11(levels[lindex+1], colBC);
+    //System.out.println("After sorting");
+    //System.out.print("\t");
+    //for(int i=0; i<colBC.length; i++)
+    //    {System.out.print("Col"+(i+1)+":"+colBC[i]+" ");
+    //    }
+    //System.out.print("\n\tNodes ");
+    //for(int i=0; i<levels[lindex+1].length; i++)
+    //    System.out.print(levels[lindex+1][i]+" ");
+    //System.out.println("\nCrossings: "+crossings(levels));
+    ///System.out.println("");
+  }
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   * lindex is the index of the level we want to process.
+   * In this method we'll sort the vertices at the level
+   * lindex according to their DOWN-barycenters (or row
+   * barycenters).
+   */
+  public void phaseIU(final int lindex, final int levels[][]) {
+    float rowBC[];
+    rowBC = calcRowBC(lindex, levels);
+    
+    //System.out.println("In IU Level"+(lindex+1)+":");
+    //System.out.print("\t");
+    //for(int i=0; i<rowBC.length; i++)
+    //    {System.out.print("Row"+(i+1)+":"+rowBC[i]+" ");
+    //    }
+    //    System.out.print("\n\t");
+    //    rowBC = calcRowBC1(lindex, levels);
+    //    for(int i=0; i<rowBC.length; i++)
+    //      {System.out.print("Row"+(i+1)+":"+rowBC[i]+" ");
+    //      }
+    //    System.out.println("");
+    //System.out.print("\n\tNodes ");
+    //for(int i=0; i<levels[lindex].length; i++)
+    //    System.out.print(levels[lindex][i]+" ");
+    //System.out.println("\nCrossings: "+crossings(levels));
+    //inspect(true, lindex, levels, rowBC);
+    
+    isort(levels[lindex], rowBC);
+    //combSort11(levels[lindex], rowBC);
+    //System.out.println("After sorting\n\t");
+    //for(int i=0; i<rowBC.length; i++)
+    //    {System.out.print("Row"+(i+1)+":"+rowBC[i]+" ");
+    //    }
+    //System.out.print("\n\tNodes ");
+    //for(int i=0; i<levels[lindex].length; i++)
+    //    System.out.print(levels[lindex][i]+" ");
+    //System.out.println("\nCrossings: "+crossings(levels));
+  }
+  
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   */
+  public void phaseIID(final int lindex, final int levels[][]) {
+    float colBC[];
+    colBC = calcColBC(lindex, levels);
+    
+    //System.out.println("getting into phase IID");
+    for(int i=0; i<colBC.length-1; i++) {
+      if(colBC[i]==colBC[i+1]) {
+        //System.out.println("Crossings before begining of iteration: "+
+        //                   crossings(levels));
+        int tempLevels[][] = new int[levels.length][];
+        copy2DArray(levels, tempLevels);
+        //System.out.println("Interchanging: "+
+        //       ((GraphNode)m_nodes.elementAt(levels[lindex+1][i])).ID+
+        //       " & "+
+        //	 ((GraphNode)m_nodes.elementAt(levels[lindex+1][(i+1)])).ID+
+        //	 " at level "+(lindex+1) );
+        int node1 = levels[lindex+1][i];
+        int node2 = levels[lindex+1][i+1];
+        levels[lindex+1][i+1] = node1;
+        levels[lindex+1][i] = node2;
+        
+        for(int k=lindex+1; k<levels.length-1; k++)
+          phaseID(k, levels);
+        //System.out.println("Crossings temp:"+crossings(tempLevels)+
+        //                   " graph:"+crossings(levels));
+        if(crossings(levels)<=crossings(tempLevels)) {
+          //System.out.println("Crossings temp: "+crossings(tempLevels)+
+          //                   " Crossings levels: "+crossings(levels));
+          copy2DArray(levels, tempLevels); } //printMatrices(levels); }
+        else {
+          copy2DArray(tempLevels, levels);
+          levels[lindex+1][i+1] = node1;
+          levels[lindex+1][i] = node2;
+        }
+        //System.out.println("Crossings after PhaseID of phaseIID, "+
+        //                   "in iteration "+i+" of "+(colBC.length-1)+" at "+
+        //                   lindex+", levels: "+crossings(levels)+
+        //                   " temp: "+crossings(tempLevels));
+        
+        //tempLevels = new int[levels.length][];
+        //copy2DArray(levels, tempLevels);
+        for(int k=levels.length-2; k>=0; k--)
+          phaseIU(k, levels);
+        //System.out.println("Crossings temp:"+crossings(tempLevels)+
+        //                   " graph:"+crossings(levels));
+        //if(crossings(tempLevels)<crossings(levels)) {
+        // System.out.println("Crossings temp: "+crossings(tempLevels)+
+        //                    " Crossings levels: "+crossings(levels));
+        //      copy2DArray(tempLevels, levels); } //printMatrices(levels); }
+        if(crossings(tempLevels)<crossings(levels))
+          copy2DArray(tempLevels, levels);
+        
+        //System.out.println("Crossings after PhaseIU of phaseIID, in"+
+        //                   " iteration "+i+" of "+(colBC.length-1)+" at "
+        //		     +lindex+", levels: "+crossings(levels)+" temp: "+
+        //                   crossings(tempLevels));
+        //colBC = calcColBC(lindex, levels);
+      }
+    }
+  }
+  
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   */
+  public void phaseIIU(final int lindex, final int levels[][]) {
+    float rowBC[];
+    rowBC = calcRowBC(lindex, levels);
+    
+    //System.out.println("Getting into phaseIIU");
+    for(int i=0; i<rowBC.length-1; i++) {
+      if(rowBC[i]==rowBC[i+1]) {
+        //System.out.println("Crossings before begining of iteration: "+
+        //                   crossings(levels));
+        int tempLevels[][] = new int[levels.length][];
+        copy2DArray(levels, tempLevels);
+        //System.out.println("Interchanging: "+
+        //          ((GraphNode)m_nodes.elementAt(levels[lindex][i])).ID+" & "+
+        //          ((GraphNode)m_nodes.elementAt(levels[lindex][i+1])).ID+
+        //          " at level "+(lindex+1) );
+        int node1 = levels[lindex][i];
+        int node2 = levels[lindex][i+1];
+        levels[lindex][i+1] = node1;
+        levels[lindex][i] = node2;
+        
+        for(int k=lindex-1; k>=0; k--)
+          phaseIU(k, levels);
+        if(crossings(levels)<=crossings(tempLevels)) { 
+          //System.out.println("Crossings temp: "+crossings(tempLevels)+
+          //                   " Crossings levels: "+crossings(levels));
+          copy2DArray(levels, tempLevels); } //printMatrices(levels);
+        else {
+          copy2DArray(tempLevels, levels);
+          levels[lindex][i+1] = node1;
+          levels[lindex][i] = node2;
+        }
+        //System.out.println("Crossings after PhaseIU of PhaseIIU, in "+
+        //                   "iteration "+i+" of "+(rowBC.length-1)+" at "
+        //                   +lindex+", levels: "+crossings(levels)+
+        //                   " temp: "+crossings(tempLevels));
+        
+        //tempLevels = new int[levels.length][];
+        //copy2DArray(levels, tempLevels);
+        for(int k=0; k<levels.length-1; k++) //lindex-1; k++)
+          phaseID(k, levels);
+        //if(crossings(tempLevels)<crossings(levels)) { 
+        //  System.out.println("Crossings temp: "+crossings(tempLevels)+
+        //                     " Crossings levels: "+crossings(levels));
+        //      copy2DArray(tempLevels, levels); } //printMatrices(levels); }
+        if(crossings(tempLevels)<=crossings(levels))
+          copy2DArray(tempLevels, levels);
+        //System.out.println("Crossings after PhaseID of phaseIIU, in "+
+        //                   "iteration "+i+" of "+(rowBC.length-1)+" at "
+        //                   +lindex+", levels: "+crossings(levels)+
+        //                   " temp: "+crossings(tempLevels));
+        //rowBC = calcRowBC(lindex, levels);
+      }
+    }
+  }
+  
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   */
+  protected float [] calcRowBC(final int lindex, final int levels[][]){
+    float rowBC[] = new float[levels[lindex].length];
+    GraphNode n;
+    
+    for(int i=0; i<levels[lindex].length; i++) {
+      int sum=0;
+      n = (GraphNode)m_nodes.elementAt(levels[lindex][i]);
+      
+      for(int j=0; j<n.edges.length; j++) {
+        if(n.edges[j][1]>0) {
+          sum++;
+          try {
+            rowBC[i] = 
+              rowBC[i]+indexOfElementInLevel(n.edges[j][0], levels[lindex+1])+1;
+          }
+          catch(Exception ex) { return null; }
+        }
+      }
+      if(rowBC[i]!=0)
+        rowBC[i] = rowBC[i]/sum;
+    }
+    return rowBC;
+  }
+  
+  
+  /**
+   * See Sugiyama et al. 1981 (full reference give at top)
+   */
+  protected float [] calcColBC(final int lindex, final int levels[][]) {
+    float colBC[] = new float[levels[lindex+1].length];
+    GraphNode n;
+    
+    for(int i=0; i<levels[lindex+1].length; i++) {
+      int sum=0;
+      n = (GraphNode)m_nodes.elementAt(levels[lindex+1][i]);
+      
+      for(int j=0; j<n.edges.length; j++) {
+        if(n.edges[j][1]<1) {
+          sum++;
+          try{
+            colBC[i] =
+                colBC[i]+indexOfElementInLevel(n.edges[j][0], levels[lindex])+1;
+          }
+          catch(Exception ex) { return null; }
+        }
+      }
+      if(colBC[i]!=0)
+        colBC[i]=colBC[i]/sum;
+    }
+    return colBC;
+  }
+  
+  /**
+   * Prints out the interconnection matrix at each level.
+   * See Sugiyama et al. 1981 (full reference give at top)
+   */
+  protected void printMatrices(final int levels[][]) {
+    int i=0;
+    for(i=0; i<levels.length-1; i++) {
+      float rowBC[]=null; float colBC[]=null;
+      try{
+        rowBC = calcRowBC(i, levels); colBC = calcColBC(i, levels);
+      }
+      catch(NullPointerException ne) {
+        System.out.println("i: "+i+" levels.length: "+levels.length);
+        ne.printStackTrace();
+        return;
+      }
+      
+      System.out.print("\nM"+(i+1)+"\t");
+      for(int j=0; j<levels[i+1].length; j++) {
+        System.out.print( ((GraphNode)m_nodes.elementAt(levels[i+1][j])).ID +
+                          " ");
+        //((Integer)levels[i+1].elementAt(j)).intValue())+" ");
+      }
+      System.out.println("");
+      
+      for(int j=0; j<levels[i].length; j++) {
+        System.out.print( ((GraphNode)m_nodes.elementAt(levels[i][j])).ID+"\t");
+        //((Integer)levels[i].elementAt(j)).intValue())+"\t");
+        for(int k=0; k<levels[i+1].length; k++) {
+          
+          System.out.print(graphMatrix[levels[i][j]] 
+                                 //((Integer)levels[i].elementAt(j)).intValue()]
+                                      [levels[i+1][k]]+" "); 
+                         //((Integer)levels[i+1].elementAt(k)).intValue()]+" ");
+          
+        }
+        System.out.println(rowBC[j]);
+      }
+      System.out.print("\t");
+      for(int k=0; k<levels[i+1].length; k++)
+        System.out.print(colBC[k]+" ");
+    }
+    System.out.println("\nAt the end i: "+i+" levels.length: "+levels.length);
+  }
+  
+  /**
+   * This methods sorts the vertices in level[] according to their
+   * barycenters in BC[], using combsort11. It, however, doesn't touch the
+   * vertices with barycenter equal to zero.
+   */
+    /*
+     *  //This method should be removed
+     protected static void combSort11(int level[], float BC[]) {  
+        int switches, j, top, gap, lhold;
+        float hold;
+        gap = BC.length;
+        do {
+            gap=(int)(gap/1.3);
+            switch(gap) {
+            case 0:
+                gap = 1;
+                break;
+            case 9:
+            case 10:
+                gap=11;
+                break;
+            default:
+                break;
+            }
+            switches=0;
+            top = BC.length-gap;
+            for(int i=0; i<top; i++) {
+                j=i+gap;
+                if(BC[i]==0 || BC[j]==0)
+                    continue;
+                if(BC[i] > BC[j]) {
+                    hold=BC[i];
+                    BC[i]=BC[j];
+                    BC[j]=hold;
+                    lhold = level[i];
+                    level[i] = level[j];
+                    level[j] = lhold;
+                    switches++;
+                }//endif
+            }//endfor
+        }while(switches>0 || gap>1);
+    }
+     */
+  
+  
+  /**
+   * This methods sorts the vertices in level[] according to their
+   * barycenters in BC[], using insertion sort. It, however, doesn't touch the
+   * vertices with barycenter equal to zero.
+   */
+   //Both level and BC have elements in the same order
+  protected static void isort(int level[], float BC[]) { 
+    float temp;
+    int temp2;
+    for(int i=0; i<BC.length-1; i++) {
+      
+      int j=i;
+      temp=BC[j+1];
+      temp2=level[j+1];
+      if(temp==0)
+        continue;
+      int prej=j+1;
+      
+      while( j>-1 && (temp<BC[j]|| BC[j]==0) ) {
+        if(BC[j]==0){
+          j--; continue;}
+        else {
+          BC[prej] = BC[j];
+          level[prej] = level[j];
+          prej=j;
+          j--;
+        }
+      }
+      //j++;
+      BC[prej]    = temp;
+      level[prej] = temp2;
+      //Integer node = (Integer)level.elementAt(i+1);
+      //level.removeElementAt(i+1);
+      //level.insertElementAt(node, prej);
+    }
+  }
+  
+  
+  /**
+   * Copies one Matrix of type int[][] to another.
+   */
+  protected void copyMatrix(int from[][], int to[][]) {
+    for(int i=0; i<from.length; i++)
+      for(int j=0; j<from[i].length; j++)
+        to[i][j]=from[i][j];
+  }
+  
+  /**
+   * Copies one array of type int[][] to another.
+   */
+  protected void copy2DArray(int from[][], int to[][]) {
+    for(int i=0; i<from.length; i++) {
+      to[i] = new int[from[i].length];
+      System.arraycopy(from[i], 0, to[i], 0, from[i].length);
+      //for(int j=0; j<from[i].length; j++)
+      //	to[i][j] = from[i][j];
+    }
+  }
+  
+  /**
+   * This method lays out the vertices horizontally, in each level.
+   * It simply assings an x value to a vertex according to its
+   * index in the level.
+   */
+  protected void naiveLayout() {
+    /*
+    if(maxStringWidth==0) {
+      int strWidth;
+      for(int i=0; i<m_nodes.size(); i++) {
+        strWidth = m_fm.stringWidth(((GraphNode)m_nodes.elementAt(i)).lbl);
+        if(strWidth>maxStringWidth)
+          maxStringWidth=strWidth;
+      }
+      
+      if(m_nodeSize<maxStringWidth)
+      {m_nodeSize = maxStringWidth+4; m_nodeArea = m_nodeSize+8; }
+    }
+    */
+    if(nodeLevels==null)
+      makeProperHierarchy();
+    
+    //int nodeHeight = m_nodeHeight*2; //m_fm.getHeight()*2;
+    for(int i=0, temp=0; i<nodeLevels.length; i++) {
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        temp=nodeLevels[i][j];
+        //horPositions[temp]=j;
+        GraphNode n = (GraphNode)m_nodes.elementAt(temp);
+        n.x = j*m_nodeWidth; //horPositions[temp]*m_nodeWidth;
+        n.y = i*3*m_nodeHeight;
+      }
+    }
+    //setAppropriateSize();
+  }
+  
+  
+  protected int uConnectivity(int lindex, int eindex) {
+    int n=0;
+    for(int i=0; i<nodeLevels[lindex-1].length; i++)
+      if(graphMatrix[ nodeLevels[lindex-1][i] ][ nodeLevels[lindex][eindex] ]>0)
+        n++;
+    
+    return n;
+  }
+  
+  protected int lConnectivity(int lindex, int eindex) {
+    int n=0;
+    for(int i=0; i<nodeLevels[lindex+1].length; i++)
+      if(graphMatrix[ nodeLevels[lindex][eindex] ][ nodeLevels[lindex+1][i] ]>0)
+        n++;
+    
+    return n;
+  }
+  
+  protected int uBCenter(int lindex, int eindex, int horPositions[]) {
+    int sum=0;
+    
+    for(int i=0; i<nodeLevels[lindex-1].length; i++)
+      if(graphMatrix[nodeLevels[lindex-1][i]][nodeLevels[lindex][eindex]]>0)
+        sum = sum + (horPositions[nodeLevels[lindex-1][i]]);
+    if(sum!=0) { // To avoid 0/0
+      //System.out.println("uBC Result: "+sum+"/"+
+      //                   uConnectivity(lindex,eindex)+
+      //                   " = "+(sum/uConnectivity(lindex,eindex)) );
+      sum = sum/uConnectivity(lindex,eindex);
+    }
+    return sum;
+  }
+  
+  
+  protected int lBCenter(int lindex, int eindex, int horPositions[]) {
+    int sum=0;
+    
+    for(int i=0; i<nodeLevels[lindex+1].length; i++)
+      if(graphMatrix[nodeLevels[lindex][eindex]][nodeLevels[lindex+1][i]]>0)
+        sum = sum + (horPositions[nodeLevels[lindex+1][i]]);
+    if(sum!=0)  // To avoid 0/0
+      sum = sum/lConnectivity(lindex, eindex); //lConectivity;
+    return sum;
+  }
+  
+  private void tempMethod(int horPositions[]) {
+    
+    int minPosition = horPositions[0];
+    
+    for(int i=0; i<horPositions.length; i++)
+      if(horPositions[i]<minPosition)
+        minPosition=horPositions[i];
+    if(minPosition<0) {
+      minPosition = minPosition*-1;
+      for(int i=0; i<horPositions.length; i++){
+        //System.out.print(horPositions[i]);
+        horPositions[i]+=minPosition;
+        //System.out.println(">"+horPositions[i]);
+      }
+    }
+    
+    //int nodeHeight = m_nodeHeight*2; //m_fm.getHeight()*2;
+    for(int i=0, temp=0; i<nodeLevels.length; i++) {
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        temp=nodeLevels[i][j];
+        //horPositions[temp]=j;
+        GraphNode n = (GraphNode)m_nodes.elementAt(temp);
+        n.x = horPositions[temp]*m_nodeWidth;
+        n.y = i*3*m_nodeHeight;
+      }
+    }
+  }
+  
+  /**
+   * This method lays out the vertices horizontally, in each level.
+   * See Sugiyama et al. 1981 for full reference.
+   */
+  protected void priorityLayout1() {
+    
+    int [] horPositions = new int[m_nodes.size()];
+    int maxCount=0;
+    
+    for(int i=0; i<nodeLevels.length; i++) {
+      int count=0;
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        horPositions[nodeLevels[i][j]]=j;
+        count++;
+      }
+      if(count>maxCount)
+        maxCount=count;
+    }
+    //fireLayoutCompleteEvent( new LayoutCompleteEvent(this) );
+    int priorities[], BC[];
+    //System.out.println("********Going from 2 to n********");
+    for(int i=1; i<nodeLevels.length; i++) {
+      priorities = new int[nodeLevels[i].length];
+      BC = new int[nodeLevels[i].length];
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        if(((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID.startsWith("S"))
+          priorities[j] = maxCount+1;
+        else
+          priorities[j] = uConnectivity(i, j);
+        BC[j] = uBCenter(i, j, horPositions);
+      }
+      //for(int j=0; j<nodeLevels[i].length; j++)
+      //  System.out.println("Level: "+(i+1)+" Node: "
+      //	+((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID
+      //        +" uConnectivity: "+priorities[j]+" uBC: "+BC[j]+" position: "
+      //	+horPositions[nodeLevels[i][j]]);
+      priorityLayout2(nodeLevels[i], priorities, BC, horPositions);
+      //repaint
+      //try {
+      //	tempMethod(horPositions);
+      //	fireLayoutCompleteEvent( new LayoutCompleteEvent(this) );
+      //	Thread.sleep(1000);
+      //} catch(InterruptedException ie) { ie.printStackTrace(); }
+      
+      //for(int j=0; j<nodeLevels[i].length; j++)
+      //    System.out.println("Level: "+(i+1)+" Node: "
+      //	+((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID
+      //	+" uConnectivity: "+priorities[j]+" uBC: "+BC[j]+" position: "
+      //	+horPositions[nodeLevels[i][j]]);
+    }
+    
+    //System.out.println("********Going from n-1 to 1********");
+    for(int i=nodeLevels.length-2; i>=0; i--) {
+      priorities = new int[nodeLevels[i].length];
+      BC = new int[nodeLevels[i].length];
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        if(((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID.startsWith("S"))
+          priorities[j] = maxCount+1;
+        else
+          priorities[j] = lConnectivity(i, j);
+        BC[j] = lBCenter(i, j, horPositions); //, priorities[j]);
+      }
+      priorityLayout2(nodeLevels[i], priorities, BC, horPositions);
+      //repaint();
+      //try {
+      //	tempMethod(horPositions);
+      //	fireLayoutCompleteEvent( new LayoutCompleteEvent(this) );
+      //	Thread.sleep(1000);
+      //} catch(InterruptedException ie) { ie.printStackTrace(); }
+      
+      //for(int j=0; j<nodeLevels[i].length; j++)
+      //    System.out.println("Level: "+(i+1)+" Node: "
+      //	+((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID
+      //	+" lConnectivity: "+priorities[j]+" lBC: "+BC[j]+" position: "
+      //	+horPositions[nodeLevels[i][j]]);
+    }
+    
+    //System.out.println("********Going from 2 to n again********");
+    for(int i=2; i<nodeLevels.length; i++) {
+      priorities = new int[nodeLevels[i].length];
+      BC = new int[nodeLevels[i].length];
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        if(((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID.startsWith("S"))
+          priorities[j] = maxCount+1;
+        else
+          priorities[j] = uConnectivity(i, j);
+        BC[j] = uBCenter(i, j, horPositions);
+      }
+      //for(int j=0; j<nodeLevels[i].length; j++)
+      //    System.out.println("Level: "+(i+1)+" Node: "
+      //	+((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID
+      //	+" uConnectivity: "+priorities[j]+" uBC: "+BC[j]+" position: "
+      //	+horPositions[nodeLevels[i][j]]);
+      priorityLayout2(nodeLevels[i], priorities, BC, horPositions);
+      //repaint();
+      //try {
+      //	tempMethod(horPositions);
+      //	fireLayoutCompleteEvent( new LayoutCompleteEvent(this) );
+      //	Thread.sleep(1000);
+      //} catch(InterruptedException ie) { ie.printStackTrace(); }
+      //for(int j=0; j<nodeLevels[i].length; j++)
+      //    System.out.println("Level: "+(i+1)+" Node: "
+      //	+((GraphNode)m_nodes.elementAt(nodeLevels[i][j])).ID
+      //	+" uConnectivity: "+priorities[j]+" uBC: "+BC[j]+" position: "
+      //	+horPositions[nodeLevels[i][j]]);
+    }
+    
+    int minPosition = horPositions[0];
+    
+    for(int i=0; i<horPositions.length; i++)
+      if(horPositions[i]<minPosition)
+        minPosition=horPositions[i];
+    if(minPosition<0) {
+      minPosition = minPosition*-1;
+      for(int i=0; i<horPositions.length; i++){
+        //System.out.print(horPositions[i]);
+        horPositions[i]+=minPosition;
+        //System.out.println(">"+horPositions[i]);
+      }
+    }
+    
+    //int nodeHeight = m_nodeHeight*2; //m_fm.getHeight()*2;
+    for(int i=0, temp=0; i<nodeLevels.length; i++) {
+      for(int j=0; j<nodeLevels[i].length; j++) {
+        temp=nodeLevels[i][j];
+        //horPositions[temp]=j;
+        GraphNode n = (GraphNode)m_nodes.elementAt(temp);
+        n.x = horPositions[temp]*m_nodeWidth;
+        n.y = i*3*m_nodeHeight;
+      }
+    }
+    //setAppropriateSize();
+  }
+  
+  /**
+   * This method is used by priorityLayout1(). It should
+   * not be called directly.
+   * This method does the actual moving of the vertices in each level
+   * based on their priorities and barycenters.
+   */
+  private void priorityLayout2(int level[], int priorities[], 
+                               int bCenters[], int horPositions[]) {
+    int descOrder[] = new int[priorities.length];
+    
+    //Getting the indices of priorities in descending order
+    descOrder[0]=0;
+    for(int i=0; i<priorities.length-1; i++) {
+      int j=i;
+      int temp=i+1;
+      
+      while( j>-1 && priorities[descOrder[j]]<priorities[temp] ) {
+        descOrder[j+1] = descOrder[j];
+        j--;
+      }
+      j++;
+      descOrder[j] = temp;
+    }
+    
+    //System.out.println("\nPriorities:");
+    //for(int i=0; i<priorities.length; i++)
+    //    System.out.print(priorities[i]+" ");
+    //System.out.println("\nDescOrder:");
+    //for(int i=0; i<descOrder.length; i++)
+    //    System.out.print(descOrder[i]+" ");
+    
+    for(int k=0; k<descOrder.length; k++)
+      for(int i=0; i<descOrder.length; i++) {
+        
+        int leftCount=0, rightCount=0, leftNodes[], rightNodes[];
+        for(int j=0; j<priorities.length; j++) {
+          if( horPositions[level[descOrder[i]]] > horPositions[level[j]] )
+            leftCount++;
+          else if( horPositions[level[descOrder[i]]] < horPositions[level[j]] )
+            rightCount++;
+        }
+        leftNodes = new int[leftCount];
+        rightNodes = new int[rightCount];
+        
+        for(int j=0, l=0, r=0; j<priorities.length; j++)
+          if( horPositions[level[descOrder[i]]] > horPositions[level[j]] )
+            leftNodes[l++]=j;
+          else if( horPositions[level[descOrder[i]]] < horPositions[level[j]] )
+            rightNodes[r++]=j;
+        
+        
+        //****Moving left
+        while(Math.abs(horPositions[level[descOrder[i]]]-1
+                                    -bCenters[descOrder[i]])  <
+          Math.abs(horPositions[level[descOrder[i]]]-bCenters[descOrder[i]]) ) {
+          
+          //****Checking if it can be moved to left
+          int temp = horPositions[level[descOrder[i]]];
+          boolean cantMove=false;
+          
+          for(int j=leftNodes.length-1; j>=0; j--) {
+            if(temp-horPositions[level[leftNodes[j]]] > 1)
+              break;
+            else if(priorities[descOrder[i]]<=priorities[leftNodes[j]])
+            { cantMove=true; break; }
+            else
+              temp = horPositions[level[leftNodes[j]]];
+          }
+          //if(horPositions[level[descOrder[i]]]-1==
+          //   horPositions[level[leftNodes[j]]])
+          //    cantMove = true;
+          if(cantMove)
+            break;
+          
+          temp = horPositions[level[descOrder[i]]]-1;
+          //****moving other vertices to left
+          for(int j=leftNodes.length-1; j>=0; j--) {
+            if(temp==horPositions[level[leftNodes[j]]]) {
+              //System.out.println("Moving "+
+              //     ((Node)m_nodes.elementAt(level[leftNodes[j]])).ID+" from "
+              //     +horPositions[level[leftNodes[j]]]+" to "
+              //     +(horPositions[level[leftNodes[j]]]-1) );
+              horPositions[level[leftNodes[j]]] = 
+                                    temp = horPositions[level[leftNodes[j]]]-1; 
+            }
+            
+          }
+          //System.out.println("Moving main "+
+          //    ((GraphNode)m_nodes.elementAt(level[descOrder[i]])).ID+" from "
+          //     +horPositions[level[descOrder[i]]]+" to "
+          //     +(horPositions[level[descOrder[i]]]-1));
+          horPositions[level[descOrder[i]]]=horPositions[level[descOrder[i]]]-1;
+        }
+        
+        //****Moving right
+        while(Math.abs(horPositions[level[descOrder[i]]]+1
+                                            -bCenters[descOrder[i]])  <
+          Math.abs(horPositions[level[descOrder[i]]]-bCenters[descOrder[i]]) ) {
+          //****checking if the vertex can be moved
+          int temp = horPositions[level[descOrder[i]]];
+          boolean cantMove=false;
+          
+          for(int j=0; j<rightNodes.length; j++) {
+            if(horPositions[level[rightNodes[j]]]-temp > 1)
+              break;
+            else if(priorities[descOrder[i]]<=priorities[rightNodes[j]])
+            { cantMove=true; break; }
+            else
+              temp = horPositions[level[rightNodes[j]]];
+          }
+          //if(horPositions[level[descOrder[i]]]-1==
+          //   horPositions[level[leftNodes[j]]])
+          //    cantMove = true;
+          if(cantMove)
+            break;
+          
+          temp = horPositions[level[descOrder[i]]]+1;
+          //****moving other vertices to left
+          for(int j=0; j<rightNodes.length; j++) {
+            if(temp==horPositions[level[rightNodes[j]]]) {
+              //System.out.println("Moving "+
+              //     (Node)m_nodes.elementAt(level[rightNodes[j]])).ID+" from "
+              //     +horPositions[level[rightNodes[j]]]+" to "
+              //     +(horPositions[level[rightNodes[j]]]+1) );
+              horPositions[level[rightNodes[j]]] = 
+                                    temp = horPositions[level[rightNodes[j]]]+1;
+            }
+          }
+          //System.out.println("Moving main "+
+          //    ((GraphNode)m_nodes.elementAt(level[descOrder[i]])).ID+" from "
+          //    +horPositions[level[descOrder[i]]]+" to "
+          //    +(horPositions[level[descOrder[i]]]+1));
+          horPositions[level[descOrder[i]]]=horPositions[level[descOrder[i]]]+1;
+        }
+      }
+  }
+  
+  
+  /**
+   * The following classes implement a double linked list to
+   * be used in the crossings function.
+   */
+  private class MyList {
+    int size;
+    MyListNode first=null;
+    MyListNode last=null;
+    
+    public void add(int i) {
+      if(first==null)
+        first = last = new MyListNode(i);
+      else
+        if(last.next==null) {
+          last.next = new MyListNode(i);
+          last.next.previous = last;
+          last = last.next;
+        }
+        else {
+          System.err.println("Error shouldn't be in here. Check MyList code"); 
+          size--;
+        }
+      size++;
+    }
+    
+    public void add(MyListNode n) {
+      if(first==null)
+        first = last = n;
+      else
+        if(last.next==null) {
+          last.next = n;
+          last.next.previous = last;
+          last = last.next;
+        }
+        else {
+          System.err.println("Error shouldn't be in here. Check MyList code"); 
+          size--; 
+        }
+      
+      size++;
+    }
+    
+    public void remove(MyListNode n) {
+      if(n.previous!=null)
+        n.previous.next = n.next;
+      if(n.next!=null)
+        n.next.previous = n.previous;
+      if(last==n)
+        last = n.previous;
+      if(first==n)
+        first = n.next;
+      
+      size--;
+    }
+    
+    public void remove(int i) {
+      MyListNode temp=first;
+      while(temp!=null && temp.n!=i) temp=temp.next;
+      
+      if(temp==null){ 
+        System.err.println("element "+i+"not present in the list");
+        return;
+      }
+      
+      if(temp.previous!=null)
+        temp.previous.next = temp.next;
+      if(temp.next!=null)
+        temp.next.previous = temp.previous;
+      if(last==temp)
+        last = temp.previous;
+      if(first==temp)
+        first = temp.next;
+      
+      size--;
+    }
+    
+    public int size() {
+      return size;
+    }
+    
+  }
+  
+  private class MyListNode {
+    int n;
+    MyListNode next, previous;
+    
+    public MyListNode(int i) {
+      n = i; next=null; previous=null;
+    }
+  }
+  
+} // HierarchicalBCEngine
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEvent.java	(revision 29)
@@ -0,0 +1,45 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LayoutCompleteEvent.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+package weka.gui.graphvisualizer;
+
+import java.util.EventObject;
+
+/**
+ * This is an event which is fired by a LayoutEngine once
+ * a LayoutEngine finishes laying out the graph, so
+ * that the Visualizer can repaint the screen to show
+ * the changes.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public class LayoutCompleteEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6172467234026258427L;
+  
+  public LayoutCompleteEvent(Object source) {
+    super(source);
+  }
+  
+}
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEventListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEventListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutCompleteEventListener.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LayoutCompleteEventListener.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.graphvisualizer;
+
+/**
+ * This interface should be implemented by any class
+ * which needs to receive LayoutCompleteEvents from
+ * the LayoutEngine. Typically this would be implemented
+ * by the Visualization class.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public interface LayoutCompleteEventListener {
+  
+  void layoutCompleted(LayoutCompleteEvent le);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutEngine.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutEngine.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/graphvisualizer/LayoutEngine.java	(revision 29)
@@ -0,0 +1,89 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LayoutEngine.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.graphvisualizer;
+
+import weka.core.FastVector;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+/**
+ * This interface class has been added to facilitate the addition
+ * of other layout engines to this package. Any class
+ * that wants to lay out a graph should implement this
+ * interface.
+ *
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.6 $ - 24 Apr 2003 - Initial version (Ashraf M. Kibriya)
+ */
+public interface LayoutEngine {
+  /**
+   * This method lays out the graph for better visualization
+   */
+  void layoutGraph();
+  
+  /**
+   * This method sets the nodes and edges vectors of the LayoutEngine
+   */
+  void setNodesEdges(FastVector nodes, FastVector edges);
+  /**
+   * This method sets the allowed size of the node
+   */
+  void setNodeSize(int nodeWidth, int nodeHeight);
+  
+  /** give access to set of graph nodes */
+   FastVector getNodes();
+
+  /**
+   * This method returns the extra controls panel
+   * for the LayoutEngine, if there is any.
+   */
+  JPanel getControlPanel();
+  
+  /**
+   * This method returns the progress bar
+   * for the LayoutEngine, which shows
+   * the progress of the layout process,
+   * if it takes a while to layout the
+   * graph
+   */
+  JProgressBar getProgressBar();
+  
+  /**
+   * This method adds a LayoutCompleteEventListener to the
+   * LayoutEngine.
+   * @param e - The LayoutCompleteEventListener to add
+   */
+  void addLayoutCompleteEventListener(LayoutCompleteEventListener e);
+  
+  /**
+   * This method removes a LayoutCompleteEventListener from the
+   * LayoutEngine.
+   * @param e - The LayoutCompleteEventListener to remove.
+   */
+  void removeLayoutCompleteEventListener(LayoutCompleteEventListener e);
+  
+  /**
+   * This fires a LayoutCompleteEvent once a layout has been completed.
+   */
+  void fireLayoutCompleteEvent(LayoutCompleteEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/hierarchyvisualizer/HierarchyVisualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/hierarchyvisualizer/HierarchyVisualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/hierarchyvisualizer/HierarchyVisualizer.java	(revision 29)
@@ -0,0 +1,416 @@
+/*
+
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/*
+ * HierarchicalClusterer.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+*/
+
+package weka.gui.hierarchyvisualizer;
+/**
+ * Shows cluster trees represented in Newick format as dendrograms.
+ *
+ * @author Remco Bouckaert (rrb@xm.co.nz, remco@cs.waikato.ac.nz)
+ * @version $Revision: 5996 $
+ */
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+
+import weka.gui.visualize.PrintablePanel;
+
+public class HierarchyVisualizer extends PrintablePanel implements ComponentListener {
+	private static final long serialVersionUID = 1L;
+
+	String m_sNewick;
+	Node m_tree;
+	int m_nLeafs;
+	double m_fHeight;
+	double m_fScaleX = 10;
+	double m_fScaleY = 10;
+
+	public HierarchyVisualizer(String sNewick) {
+		try {
+			parseNewick(sNewick);
+		} catch (Exception e) {
+			e.printStackTrace();
+			System.exit(0);
+		}
+		addComponentListener(this);
+	} // c'tor
+
+	int positionLeafs(Node node, int nPosX) {
+		if (node.isLeaf()) {
+			node.m_fPosX = nPosX + 0.5;
+			nPosX++;
+			return nPosX;
+		} else {
+			for (int i = 0; i < node.m_children.length; i++) {
+				nPosX = positionLeafs(node.m_children[i], nPosX);
+			}
+		}
+		return nPosX;
+	}
+	double positionRest(Node node) {
+		if (node.isLeaf()) {
+			return node.m_fPosX;
+		} else {
+			double fPosX = 0;
+			for (int i = 0; i < node.m_children.length; i++) {
+				fPosX += positionRest(node.m_children[i]);
+			}
+			fPosX /= node.m_children.length;
+			node.m_fPosX = fPosX;
+			return fPosX;
+		}
+	}
+	double positionHeight(Node node, double fOffSet) {
+		if (node.isLeaf()) {
+			node.m_fPosY = fOffSet + node.m_fLength;
+			return node.m_fPosY;
+		} else {
+			double fPosY = fOffSet + node.m_fLength;
+			double fYMax = 0;
+			for (int i = 0; i < node.m_children.length; i++) {
+				fYMax = Math.max(fYMax, positionHeight(node.m_children[i], fPosY));
+			}
+			node.m_fPosY = fPosY;
+			return fYMax;
+		}
+	}
+
+	//Vector<String> m_sMetaDataNames;
+	/** class for nodes in building tree data structure **/
+	class Node {
+		double m_fLength = -1;
+		double m_fPosX = 0;
+		double m_fPosY = 0;
+		String m_sLabel;
+		//Vector<String> m_sMetaDataValues;
+		String m_sMetaData;
+
+
+		/** list of children of this node **/
+		Node[] m_children;
+		/** parent node in the tree, null if root **/
+		Node m_Parent = null;
+
+		Node getParent() {
+			return m_Parent;
+		}
+
+		void setParent(Node parent) {
+			m_Parent = parent;
+		}
+
+		/** check if current node is root node **/
+		boolean isRoot() {
+			return m_Parent == null;
+		}
+		boolean isLeaf() {
+			return m_children == null;
+		}
+
+		/** return nr of children **/
+		int getChildCount() {
+//			}
+			if (m_children == null) {return 0;}
+			return m_children.length;
+		}
+
+		Node getChild(int iChild) {
+			return m_children[iChild];
+		}
+
+
+		/** count number of nodes in tree, starting with current node **/
+		int getNodeCount() {
+			if (m_children == null) {
+				return 1;
+			}
+			int n = 1;
+			for (int i = 0; i < m_children.length; i++) {
+				n += m_children[i].getNodeCount();
+			}
+			return n;
+		}
+		public String toString() {
+			StringBuffer buf = new StringBuffer();
+			if (m_children != null) {
+				buf.append("(");
+				for (int i = 0; i < m_children.length-1; i++) {
+					buf.append(m_children[i].toString());
+					buf.append(',');
+				}
+				buf.append(m_children[m_children.length - 1].toString());
+				buf.append(")");
+			} else {
+				buf.append(m_sLabel);
+			}
+			if (m_sMetaData != null) {
+				buf.append('[');
+				buf.append(m_sMetaData);
+				buf.append(']');
+			}
+			buf.append(":" + m_fLength);
+			return buf.toString();
+		}
+
+		double draw(Graphics g) {
+			if (isLeaf()) {
+				int x = (int)(m_fPosX * m_fScaleX);
+				int y = (int)(m_fPosY * m_fScaleY);
+				g.drawString(m_sLabel, x, y);
+				g.drawLine((int)(m_fPosX * m_fScaleX), (int)(m_fPosY * m_fScaleY), (int)(m_fPosX * m_fScaleX), (int)((m_fPosY - m_fLength) * m_fScaleY));
+			} else {
+				double fPosX1 = Double.MAX_VALUE;
+				double fPosX2 = -Double.MAX_VALUE;
+				for (int i = 0; i < m_children.length; i++) {
+					double f = m_children[i].draw(g);
+					if (f < fPosX1) {fPosX1 = f;}
+					if (f > fPosX2) {fPosX2 = f;}
+				}
+				g.drawLine((int)(m_fPosX * m_fScaleX), (int)(m_fPosY * m_fScaleY), (int)(m_fPosX * m_fScaleX), (int)((m_fPosY - m_fLength) * m_fScaleY));
+				g.drawLine((int)(fPosX1 * m_fScaleX), (int)(m_fPosY * m_fScaleY), (int)(fPosX2 * m_fScaleX), (int)(m_fPosY* m_fScaleY));
+			}
+			return m_fPosX;
+		}
+	} // class Node
+
+	/** helper method for parsing Newick tree **/
+	int nextNode(String sStr, int i) {
+		int nBraces = 0;
+		char c = sStr.charAt(i);
+		do {
+			i++;
+			if (i < sStr.length()) {
+				c = sStr.charAt(i);
+				// skip meta data block
+				if (c == '[') {
+					while (i < sStr.length() && sStr.charAt(i)!=']') {
+						i++;
+					}
+					i++;
+					if(i < sStr.length()) {
+						c = sStr.charAt(i);
+					}
+				}
+
+				switch (c) {
+				case '(':
+					nBraces++;
+					break;
+				case ')':
+					nBraces--;
+					break;
+				default:
+					break;
+				}
+			}
+		} while (i < sStr.length()
+				&& (nBraces > 0 || (c != ','&&c != ')'&&c != '(')));
+		if (i >= sStr.length() || nBraces < 0) {
+			return -1;
+		} else if (sStr.charAt(i) == ')') {
+			i++;
+			if (sStr.charAt(i) == '[') {
+				while (i < sStr.length() && sStr.charAt(i)!=']') {
+					i++;
+				}
+				i++;
+				if (i >= sStr.length()) {
+					return -1;
+				}
+			}
+			if (sStr.charAt(i) == ':') {
+				i++;
+				c = sStr.charAt(i);
+				while (i < sStr.length() && (c=='.' || Character.isDigit(c))) {
+					i++;
+					if (i < sStr.length()) {
+						c = sStr.charAt(i);
+					}
+				}
+			}
+		}
+		return i;
+	}
+
+	/**
+	 * convert string containing Newick tree into tree datastructure but only in
+	 * the limited format as contained in m_sTrees
+	 *
+	 * @param sStr
+	 * @return tree consisting of a Node
+	 */
+	void parseNewick(String sNewick) throws Exception {
+		m_sNewick = sNewick;
+		int i = m_sNewick.indexOf('(');
+		if (i > 0) {
+			m_sNewick = m_sNewick.substring(i);
+		}
+		System.err.println(m_sNewick);
+		m_tree = parseNewick2(m_sNewick);
+		System.err.println(m_tree.toString());
+		m_nLeafs = positionLeafs(m_tree, 0);
+		positionRest(m_tree);
+		m_fHeight = positionHeight(m_tree, 0);
+	}
+
+	Node parseNewick2(String sStr) throws Exception {
+		// System.out.println(sStr);
+		if (sStr == null || sStr.length() == 0) {
+			return null;
+		}
+		Node node = new Node();
+		if (sStr.startsWith("(")) {
+			int i1 = nextNode(sStr, 0);
+			int i2 = nextNode(sStr, i1);
+			node.m_children = new Node[2];
+			node.m_children[0] = parseNewick2(sStr.substring(1, i1));
+			node.m_children[0].m_Parent = node;
+			String sStr2 = sStr.substring(i1+1, (i2>0?i2:sStr.length()));
+			node.m_children[1] = parseNewick2(sStr2);
+			node.m_children[1].m_Parent = node;
+			if (sStr.lastIndexOf('[') > sStr.lastIndexOf(')')) {
+				sStr = sStr.substring(sStr.lastIndexOf('['));
+				i2 = sStr.indexOf(']');
+				if (i2 < 0) {
+					throw new Exception("unbalanced square bracket found:" + sStr);
+				}
+				sStr2 = sStr.substring(1, i2);
+				node.m_sMetaData = sStr2;
+			}
+			if (sStr.lastIndexOf(':') > sStr.lastIndexOf(')')) {
+				sStr = sStr.substring(sStr.lastIndexOf(':'));
+				sStr = sStr.replaceAll("[,\\):]", "");
+				node.m_fLength = new Double(sStr);
+			} else {
+				node.m_fLength = 1;
+			}
+		} else {
+			// it is a leaf
+			if (sStr.contains("[")) {
+				// grab metadata
+				int i1 = sStr.indexOf('[');
+				int i2 = sStr.indexOf(']');
+				if (i2 < 0) {
+					throw new Exception("unbalanced square bracket found:" + sStr);
+				}
+				String sStr2 = sStr.substring(i1+1, i2);
+				sStr = sStr.substring(0, i1) +sStr.substring(i2+1);
+				node.m_sMetaData = sStr2;
+			}
+			if (sStr.indexOf(')') >= 0) {
+				sStr = sStr.substring(0, sStr.indexOf(')'));
+			}
+			sStr = sStr.replaceFirst("[,\\)]", "");
+			// System.out.println("parsing <<"+sStr+">>");
+			if (sStr.length() > 0) {
+				if (sStr.indexOf(':') >= 0) {
+					int iColon = sStr.indexOf(':');
+					node.m_sLabel = sStr.substring(0, iColon);
+					if (sStr.indexOf(':', iColon+1) >= 0) {
+						int iColon2 = sStr.indexOf(':', iColon+1);
+						node.m_fLength = new Double(sStr.substring(iColon+1, iColon2));
+						m_fTmpLength = new Double(sStr.substring(iColon2+1));
+					} else {
+						node.m_fLength = new Double(sStr.substring(iColon+1));
+					}
+				} else {
+					node.m_sLabel = sStr;
+					node.m_fLength = 1;
+				}
+			} else {
+				return null;
+			}
+		}
+		return node;
+	}
+	double m_fTmpLength;
+
+	/**
+	 * Fits the tree to the current screen size. Call this after window has been
+	 * created to get the entire tree to be in view upon launch.
+	 */
+	public void fitToScreen() {
+		m_fScaleX = 10;
+		int nW = getWidth();
+		if (m_nLeafs > 0) {
+			m_fScaleX = nW / m_nLeafs;
+		}
+		m_fScaleY = 10;
+		int nH = getHeight();
+		if (m_fHeight > 0) {
+			m_fScaleY = (nH - 10) / m_fHeight;
+		}
+		repaint();
+	}
+
+	/**
+	 * Updates the screen contents.
+	 *
+	 * @param g
+	 *            the drawing surface.
+	 */
+	public void paintComponent(Graphics g) {
+		Color oldBackground = ((Graphics2D) g).getBackground();
+		// if (m_BackgroundColor != null)
+		((Graphics2D) g).setBackground(Color.WHITE);
+		g.clearRect(0, 0, getSize().width, getSize().height);
+		((Graphics2D) g).setBackground(oldBackground);
+		g.setClip(3, 7, getWidth() - 6, getHeight() - 10);
+		m_tree.draw(g);
+		g.setClip(0, 0, getWidth(), getHeight());
+	}
+
+
+	public void componentHidden(ComponentEvent e) {}
+
+	public void componentMoved(ComponentEvent e) {}
+
+	public void componentResized(ComponentEvent e) {
+		fitToScreen();
+	}
+
+	public void componentShown(ComponentEvent e) {}
+
+	/**
+	 * Main method for testing this class.
+	 */
+	public static void main(String[] args) {
+	      //HierarchyVisualizer a = new HierarchyVisualizer("((((human:2.0,(chimp:1.0,bonobo:1.0):1.0):1.0,gorilla:3.0):1.0,siamang:4.0):1.0,orangutan:5.0)");
+	      //HierarchyVisualizer a = new HierarchyVisualizer("(((human:2.0,(chimp:1.0,bonobo:1.0):1.0):1.0,gorilla:3.0):1.0,siamang:4.0)");
+	      HierarchyVisualizer a = new HierarchyVisualizer(" (((5[theta=0.121335,lxg=0.122437]:0.00742795,3[theta=0.0972485,lxg=0.152762]:0.00742795)[theta=0.490359,lxg=0.0746703]:0.0183076,((2[theta=0.0866056,lxg=0.2295]:0.00993801,4[theta=0.135512,lxg=0.146674]:0.00993801)[theta=0.897783,lxg=0.0200762]:0.00901206,1[theta=0.200265,lxg=0.18925]:0.0189501)[theta=0.0946195,lxg=0.143427]:0.00678551)[theta=0.185562,lxg=0.139681]:0.0129598,(7[theta=0.176022,lxg=0.364039]:0.0320395,((0[theta=0.224286,lxg=0.156485]:0.0175487,8[theta=0.223313,lxg=0.157166]:0.0175487)[theta=0.631287,lxg=0.024042]:0.00758871,6[theta=0.337871,lxg=0.148799]:0.0251374)[theta=0.33847,lxg=0.040784]:0.00690208)[theta=0.209238,lxg=0.0636202]:0.00665587)[theta=0.560453,lxg=-0.138086]:0.01");
+		  //HierarchyVisualizer a = new HierarchyVisualizer(" ((5[theta=0.121335,lxg=0.122437]:0.00742795,3[theta=0.0972485,lxg=0.152762]:0.00742795)[theta=0.490359,lxg=0.0746703]:0.0183076,2[theta=0.0866056,lxg=0.2295]:0.00993801)[theta=0.897783,lxg=0.0200762]:0.00901206");
+	      a.setSize(800 ,600);
+	      JFrame f;
+	      f = new JFrame();
+	      Container contentPane = f.getContentPane();
+	      contentPane.add(a);
+	      f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+	      f.setSize(800,600);
+	      f.setVisible(true);
+	      a.fitToScreen();
+	  }
+
+} // class HierarchyVisualizer
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/FileScriptingPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/FileScriptingPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/FileScriptingPanel.java	(revision 29)
@@ -0,0 +1,1173 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * FileScriptingPanel.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ * Copyright (c) 1995 - 2008 Sun Microsystems, Inc.  
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Utils;
+import weka.gui.ComponentHelper;
+import weka.gui.DocumentPrinting;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.PropertyDialog;
+import weka.gui.scripting.event.ScriptExecutionEvent;
+import weka.gui.scripting.event.ScriptExecutionListener;
+import weka.gui.scripting.event.TitleUpdatedEvent;
+import weka.gui.scripting.event.ScriptExecutionEvent.Type;
+
+import java.awt.BorderLayout;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.File;
+import java.util.HashMap;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextPane;
+import javax.swing.KeyStroke;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.swing.undo.UndoManager;
+
+/**
+ * Supports loading/saving of files.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @author  Sun Microsystems Inc (see <a href="http://java.sun.com/docs/books/tutorial/uiswing/examples/components/TextComponentDemoProject/src/components/TextComponentDemo.java">TextComponentDemo.java</a>)
+ * @version $Revision: 5144 $
+ */
+public abstract class FileScriptingPanel
+  extends ScriptingPanel 
+  implements ScriptExecutionListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 1583670545010241816L;
+
+  /**
+   * A slightly extended action class.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  public abstract class BasicAction
+    extends AbstractAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 2821117985661550385L;
+
+    /**
+     * Constructor for setting up an action.
+     * 
+     * @param name	the name of the action (to be displayed in menu/button)
+     * @param icon	the icon name (no path required if in weka/gui/images), can be null
+     * @param accel	the accelerator command, e.g., "ctrl N", can be null
+     * @param mnemonic	the mnemonic character
+     */
+    public BasicAction(String name, String icon, String accel, Character mnemonic) {
+      super(name);
+      
+      if ((icon != null) && (icon.length() > 0))
+	putValue(Action.SMALL_ICON, ComponentHelper.getImageIcon(icon));
+      if ((accel != null) && (accel.length() > 0))
+	putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accel));
+      if (mnemonic != null)
+	putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charValue()));
+    }
+  }
+  
+  /**
+   * The New action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class NewAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -8665722554539726090L;
+
+    /**
+     * Initializes the action.
+     */
+    public NewAction() {
+      super("New", "new.gif", "ctrl N", 'N');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      m_Script.empty();
+      notifyTitleUpdatedListeners(new TitleUpdatedEvent(FileScriptingPanel.this));
+    }
+  }
+
+  /**
+   * The Open action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class OpenAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -4496148485267789162L;
+
+    /**
+     * Initializes the action.
+     */
+    public OpenAction() {
+      super("Open...", "open.gif", "ctrl O", 'O');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      boolean	ok;
+      int	retVal;
+      
+      if (!checkModified())
+	return;
+      
+      retVal = m_FileChooser.showOpenDialog(FileScriptingPanel.this);
+      if (retVal != JFileChooser.APPROVE_OPTION)
+        return;
+      
+      ok = m_Script.open(m_FileChooser.getSelectedFile());
+      m_TextCode.setCaretPosition(0);
+      if (!ok)
+	JOptionPane.showMessageDialog(
+	    FileScriptingPanel.this, 
+	    "Couldn't open file '" + m_FileChooser.getSelectedFile() + "'!");
+      
+      notifyTitleUpdatedListeners(new TitleUpdatedEvent(FileScriptingPanel.this));
+    }
+  }
+
+  /**
+   * The Save action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class SaveAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -74651145892063975L;
+    
+    /** whether to bring up the save dialog all the time. */
+    protected boolean m_ShowDialog;
+    
+    /**
+     * Initializes the action.
+     * 
+     * @param name		the name of the action
+     * @param showDialog	whether to always show the dialog
+     */
+    public SaveAction(String name, boolean showDialog) {
+      super(name, (showDialog ? "" : "save.gif"), (showDialog ? "ctrl shift S" : "ctrl S"), (showDialog ? 'a' : 'S'));
+      m_ShowDialog = showDialog;
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      boolean	ok;
+      int	retVal;
+      
+      if (m_ShowDialog || (m_Script.getFilename() == null)) {
+	retVal = m_FileChooser.showSaveDialog(FileScriptingPanel.this);
+	if (retVal != JFileChooser.APPROVE_OPTION)
+	  return;
+	ok = m_Script.saveAs(m_FileChooser.getSelectedFile());
+      }
+      else {
+	ok = m_Script.save();
+      }
+      
+      if (!ok) {
+	if (m_Script.getFilename() != null)
+	  JOptionPane.showMessageDialog(
+	      FileScriptingPanel.this, 
+	      "Failed to save file '" + m_FileChooser.getSelectedFile() + "'!");
+	else
+	  JOptionPane.showMessageDialog(
+	      FileScriptingPanel.this, 
+	      "Failed to save file!");
+      }
+      else {
+	m_SaveAction.setEnabled(false);
+      }
+
+      notifyTitleUpdatedListeners(new TitleUpdatedEvent(FileScriptingPanel.this));
+    }
+  }
+
+  /**
+   * The Print action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class PrintAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -6246539539545724632L;
+
+    /**
+     * Initializes the action.
+     */
+    public PrintAction() {
+      super("Print...", "print.gif", "ctrl P", 'P');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      JTextPane		pane;
+      DocumentPrinting 	doc;
+      
+      pane = newCodePane();
+      pane.setText(m_TextCode.getText());
+      doc = new DocumentPrinting();
+      doc.print(pane);
+    }
+  }
+
+  /**
+   * The Clear output action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class ClearOutputAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 47986890456997211L;
+
+    /**
+     * Initializes the action.
+     */
+    public ClearOutputAction() {
+      super("Clear output", "", "F2", 'C');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      m_TextOutput.setText("");
+    }
+  }
+
+  /**
+   * The Exit action. Sends out a WindowEvent/WINDOW_CLOSED to all 
+   * WindowListener objects of a jframe.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class ExitAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -5884709836238884180L;
+
+    /**
+     * Initializes the action.
+     */
+    public ExitAction() {
+      super("Exit", "", "", 'x');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      Dialog		dialog;
+      Frame		frame;
+      JFrame		jframe;
+      JInternalFrame	jintframe;
+      int		i;
+      WindowListener[] 	listeners;
+      WindowEvent	event;
+      
+      if (!checkModified())
+	return;
+      
+      if (PropertyDialog.getParentDialog(FileScriptingPanel.this) != null) {
+	dialog = PropertyDialog.getParentDialog(FileScriptingPanel.this);
+	dialog.setVisible(false);
+      }
+      else if (PropertyDialog.getParentFrame(FileScriptingPanel.this) != null) {
+	jintframe = PropertyDialog.getParentInternalFrame(FileScriptingPanel.this);
+	if (jintframe != null) {
+	  jintframe.doDefaultCloseAction();
+	}
+	else {
+	  frame = PropertyDialog.getParentFrame(FileScriptingPanel.this);
+	  if (frame instanceof JFrame) {
+	    jframe = (JFrame) frame;
+	    if (jframe.getDefaultCloseOperation() == JFrame.HIDE_ON_CLOSE)
+	      jframe.setVisible(false);
+	    else if (jframe.getDefaultCloseOperation() == JFrame.DISPOSE_ON_CLOSE)
+	      jframe.dispose();
+	    else if (jframe.getDefaultCloseOperation() == JFrame.EXIT_ON_CLOSE)
+	      System.exit(0);
+
+	    // notify listeners
+	    listeners = jframe.getWindowListeners();
+	    event     = new WindowEvent(jframe, WindowEvent.WINDOW_CLOSED);
+	    for (i = 0; i < listeners.length; i++)
+	      listeners[i].windowClosed(event);
+	  }
+	  else {
+	    frame.dispose();
+	}
+	}
+      }
+    }
+  }
+
+  /**
+   * The Undo action.
+   * 
+   * @author  Sun Microsystems Inc (see <a href="http://java.sun.com/docs/books/tutorial/uiswing/examples/components/TextComponentDemoProject/src/components/TextComponentDemo.java">TextComponentDemo.java</a>)
+   * @version $Revision: 5144 $
+   */
+  protected class UndoAction 
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 4298096648424808522L;
+
+    /**
+     * Initializes the action.
+     */
+    public UndoAction() {
+      super("Undo", "undo.gif", "ctrl Z", 'U');
+      setEnabled(false);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      try {
+	m_Undo.undo();
+      }
+      catch (CannotUndoException ex) {
+	System.out.println("Unable to undo: " + ex);
+	ex.printStackTrace();
+      }
+      updateUndoState();
+      m_RedoAction.updateRedoState();
+    }
+
+    /**
+     * Updates the redo state.
+     */
+    protected void updateUndoState() {
+      if (m_Undo.canUndo()) {
+	setEnabled(true);
+	putValue(Action.NAME, m_Undo.getUndoPresentationName());
+      }
+      else {
+	setEnabled(false);
+	putValue(Action.NAME, "Undo");
+      }
+    }
+  }
+
+  /**
+   * The Redo action.
+   * 
+   * @author  Sun Microsystems Inc (see <a href="http://java.sun.com/docs/books/tutorial/uiswing/examples/components/TextComponentDemoProject/src/components/TextComponentDemo.java">TextComponentDemo.java</a>)
+   * @version $Revision: 5144 $
+   */
+  protected class RedoAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 4533966901523279350L;
+
+    /**
+     * Initializes the action.
+     */
+    public RedoAction() {
+      super("Redo", "redo.gif", "ctrl Y", 'R');
+      setEnabled(false);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      try {
+	m_Undo.redo();
+      }
+      catch (CannotRedoException ex) {
+	System.out.println("Unable to redo: " + ex);
+	ex.printStackTrace();
+      }
+      updateRedoState();
+      m_UndoAction.updateUndoState();
+    }
+
+    /**
+     * Updates the redo state.
+     */
+    protected void updateRedoState() {
+      if (m_Undo.canRedo()) {
+	setEnabled(true);
+	putValue(Action.NAME, m_Undo.getRedoPresentationName());
+      }
+      else {
+	setEnabled(false);
+	putValue(Action.NAME, "Redo");
+      }
+    }
+  }
+
+  /**
+   * The Commandline args action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class CommandlineArgsAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -3183470039010826204L;
+
+    /**
+     * Initializes the action.
+     */
+    public CommandlineArgsAction() {
+      super("Arguments...", "properties.gif", "", 'g');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      String	retVal;
+      
+      retVal = JOptionPane.showInputDialog(
+	  FileScriptingPanel.this, 
+	  "Please enter the command-line arguments", 
+	  Utils.joinOptions(m_Args));
+      if (retVal == null)
+	return;
+      
+      try {
+	m_Args = Utils.splitOptions(retVal);
+      }
+      catch (Exception ex) {
+	m_Args = new String[0];
+	ex.printStackTrace();
+	JOptionPane.showMessageDialog(
+	    FileScriptingPanel.this, 
+	    "Error setting command-line arguments:\n" + ex, 
+	    "Error", 
+	    JOptionPane.ERROR_MESSAGE);
+      }
+    }
+  }
+
+  /**
+   * The Start action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class StartAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -7936456072955996220L;
+
+    /**
+     * Initializes the action.
+     */
+    public StartAction() {
+      super((m_Script.canExecuteScripts() ? "Start" : "Start (missing classes?)"), "run.gif", "ctrl R", 'S');
+      setEnabled(false);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      if (!checkModified())
+	return;
+      
+      if (m_Script.getFilename() == null)
+	return;
+      
+      try {
+	m_Script.start(m_Args);
+      }
+      catch (Exception ex) {
+	ex.printStackTrace();
+	JOptionPane.showMessageDialog(
+	    FileScriptingPanel.this, 
+	    "Error running script:\n" + ex, 
+	    "Error", 
+	    JOptionPane.ERROR_MESSAGE);
+      }
+    }
+  }
+
+  /**
+   * The Stop action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class StopAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = 8764023289575718872L;
+
+    /**
+     * Initializes the action.
+     */
+    public StopAction() {
+      super("Stop", "stop.gif", "ctrl shift R", 'o');
+      setEnabled(false);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      try {
+	m_Script.stop();
+      }
+      catch (Exception ex) {
+	// ignored
+      }
+    }
+  }
+
+  /**
+   * The About action.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5144 $
+   */
+  protected class AboutAction
+    extends BasicAction {
+    
+    /** for serialization. */
+    private static final long serialVersionUID = -6420463480569171227L;
+
+    /**
+     * Initializes the action.
+     */
+    public AboutAction() {
+      super("About...", "", "F1", 'A');
+      setEnabled(true);
+    }
+
+    /**
+     * Fired when action got executed.
+     * 
+     * @param e		the event
+     */
+    public void actionPerformed(ActionEvent e) {
+      JDialog	dialog;
+      
+      if (PropertyDialog.getParentDialog(FileScriptingPanel.this) != null)
+	dialog = new JDialog(PropertyDialog.getParentDialog(FileScriptingPanel.this), getName());
+      else
+	dialog = new JDialog(PropertyDialog.getParentFrame(FileScriptingPanel.this), getName());
+      dialog.setTitle((String) getValue(Action.NAME));
+      dialog.getContentPane().setLayout(new BorderLayout());
+      dialog.getContentPane().add(getAboutPanel());
+      dialog.pack();
+      dialog.setLocationRelativeTo(FileScriptingPanel.this);
+      dialog.setVisible(true);
+    }
+  }
+  
+  /**
+   * This listener class listens for edits that can be undone.
+   * 
+   * @author  Sun Microsystems Inc (see <a href="http://java.sun.com/docs/books/tutorial/uiswing/examples/components/TextComponentDemoProject/src/components/TextComponentDemo.java">TextComponentDemo.java</a>)
+   * @version $Revision: 5144 $
+   */
+  protected class ScriptUndoableEditListener
+    implements UndoableEditListener {
+    
+    /**
+     * Gets called when an undoable event gets triggered.
+     * 
+     * @param e		the event
+     */
+    public void undoableEditHappened(UndoableEditEvent e) {
+      //Remember the edit and update the menus.
+      m_Undo.addEdit(e.getEdit());
+      m_UndoAction.updateUndoState();
+      m_RedoAction.updateRedoState();
+    }
+  }
+  
+  /** the directory with the scripting-specific images. */
+  public final static String IMAGES_DIR = "weka/gui/scripting/images";
+  
+  /** for loading/saving file. */
+  protected JFileChooser m_FileChooser;
+  
+  /** the script. */
+  protected Script m_Script;
+
+  /** the script area. */
+  protected JTextArea m_ScriptArea;
+
+  /** the output area. */
+  protected JTextArea m_OutputArea;
+  
+  /** for informing the user. */
+  protected JLabel m_LabelInfo;
+  
+  /** for storing the actions under their name. */
+  protected HashMap<Object, Action> m_Actions;
+
+  /** the new action. */
+  protected NewAction m_NewAction;
+  /** the open action. */
+  protected OpenAction m_OpenAction;
+  /** the Save action. */
+  protected SaveAction m_SaveAction;
+  /** the Save as action. */
+  protected SaveAction m_SaveAsAction;
+  /** the Print action. */
+  protected PrintAction m_PrintAction;
+  /** the clear output action. */
+  protected ClearOutputAction m_ClearOutputAction;
+  /** the exit action. */
+  protected ExitAction m_ExitAction;
+  /** the undo action. */
+  protected UndoAction m_UndoAction;
+  /** the redo action. */
+  protected RedoAction m_RedoAction;
+  /** the cut action. */
+  protected Action m_CutAction;
+  /** the copy action. */
+  protected Action m_CopyAction;
+  /** the paste action. */
+  protected Action m_PasteAction;
+  /** the start action. */
+  protected StartAction m_StartAction;
+  /** the stop action. */
+  protected StopAction m_StopAction;
+  /** the arguments action. */
+  protected CommandlineArgsAction m_ArgsAction;
+  /** the about action. */
+  protected AboutAction m_AboutAction;
+
+  /** the undo manager. */
+  protected UndoManager m_Undo;
+  
+  /** the text pane with the code. */
+  protected JTextPane m_TextCode;
+  
+  /** the text pane for the output. */
+  protected JTextPane m_TextOutput;
+  
+  /** the commandline arguments to use. */
+  protected String[] m_Args;
+  
+  /**
+   * For initializing member variables.
+   */
+  protected void initialize() {
+    super.initialize();
+    
+    m_FileChooser = new JFileChooser();
+    m_FileChooser.setAcceptAllFileFilterUsed(true);
+    m_FileChooser.setMultiSelectionEnabled(false);
+    
+    m_Undo = new UndoManager();
+    m_Args = new String[0];
+  }
+  
+  /**
+   * Sets up the GUI after initializing the members.
+   */
+  protected void initGUI() {
+    JPanel	panel;
+    
+    super.initGUI();
+
+    setLayout(new BorderLayout(0, 5));
+    
+    m_TextCode = newCodePane();
+    m_TextCode.setFont(new Font("monospaced", Font.PLAIN, 12));
+    m_TextCode.getDocument().addUndoableEditListener(new ScriptUndoableEditListener());
+    m_TextCode.getDocument().addDocumentListener(new DocumentListener() {
+      public void changedUpdate(DocumentEvent e) {
+	update();
+      }
+      public void insertUpdate(DocumentEvent e) {
+	update();
+      }
+      public void removeUpdate(DocumentEvent e) {
+	update();
+      }
+      protected void update() {
+	Document doc = m_TextCode.getDocument();
+	m_StartAction.setEnabled((doc.getLength() > 0) && m_Script.canExecuteScripts());
+	m_SaveAction.setEnabled(true);
+	notifyTitleUpdatedListeners(new TitleUpdatedEvent(FileScriptingPanel.this));
+      }
+    });
+    add(new JScrollPane(m_TextCode), BorderLayout.CENTER);
+    
+    panel = new JPanel(new BorderLayout(0, 5));
+    panel.setPreferredSize(new Dimension(50, 200));
+    add(panel, BorderLayout.SOUTH);
+    
+    m_TextOutput = new JTextPane();
+    panel.add(new JScrollPane(m_TextOutput), BorderLayout.CENTER);
+    
+    m_LabelInfo = new JLabel(" ");
+    m_LabelInfo.setBorder(BorderFactory.createLoweredBevelBorder());
+    panel.add(m_LabelInfo, BorderLayout.SOUTH);
+  }
+  
+  /**
+   * Finishes up after initializing members and setting up the GUI.
+   */
+  protected void initFinish() {
+    ExtensionFileFilter[]	filters;
+    int				i;
+    
+    super.initFinish();
+    
+    m_Script = newScript(m_TextCode.getDocument());
+    m_Script.addScriptFinishedListener(this);
+    filters  = m_Script.getFilters();
+    for (i = filters.length - 1; i >= 0; i--)
+      m_FileChooser.addChoosableFileFilter(filters[i]);
+    
+    m_Actions = createActionTable(m_TextCode);
+
+    // file
+    m_NewAction         = new NewAction();
+    m_OpenAction        = new OpenAction();
+    m_SaveAction        = new SaveAction("Save", false);
+    m_SaveAsAction      = new SaveAction("Save As...", true);
+    m_PrintAction       = new PrintAction();
+    m_ClearOutputAction = new ClearOutputAction();
+    m_ExitAction        = new ExitAction();
+    
+    // edit
+    m_UndoAction        = new UndoAction();
+    m_RedoAction        = new RedoAction();
+    m_CutAction         = updateAction(m_Actions.get(DefaultEditorKit.cutAction), "Cut", "cut.gif", "ctrl X", 'C');
+    m_CopyAction        = updateAction(m_Actions.get(DefaultEditorKit.copyAction), "Copy", "copy.gif", "ctrl C", 'o');
+    m_PasteAction       = updateAction(m_Actions.get(DefaultEditorKit.pasteAction), "Paste", "paste.gif", "ctrl V", 'P');
+    
+    // script
+    m_StartAction       = new StartAction();
+    m_StopAction        = new StopAction();
+    m_ArgsAction        = new CommandlineArgsAction();
+    
+    // help
+    m_AboutAction       = new AboutAction();
+  }
+  
+  /**
+   * Updates the action and returns it.
+   * 
+   * @param action	the action to update
+   * @param name	the name to be used as display, can be null
+   * @param icon	the icon to use (if located in weka/gui/images, not path required), can be null
+   * @param accel	the accelerator command to use (e.g., "ctrl N"), can be null
+   * @param mnemonic	the mnemonic character to use, can be null
+   * @return		the updated action
+   */
+  protected Action updateAction(Action action, String name, String icon, String accel, Character mnemonic) {
+    Action	result;
+    
+    // did we already update that action for another component?
+    if (action == null) {
+      result = m_Actions.get(name);
+      return result;
+    }
+    
+    result = action;
+    
+    if ((name != null) && (name.length() > 0))
+      result.putValue(Action.NAME, name);
+    if ((icon != null) && (icon.length() > 0))
+      result.putValue(Action.SMALL_ICON, ComponentHelper.getImageIcon(icon));
+    if ((accel != null) && (accel.length() > 0))
+      result.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accel));
+    if (mnemonic != null)
+      result.putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charValue()));
+    
+    return result;
+  }
+  
+  /**
+   * Creates a new JTextPane for the code.
+   * 
+   * @return		the text pane
+   */
+  protected abstract JTextPane newCodePane();
+
+  /**
+   * Returns an initialized script object.
+   * 
+   * @param doc		the document to use as basis
+   * @return		the initialized script
+   */
+  protected abstract Script newScript(Document doc);
+
+  /**
+   * Gets sent when a script finishes execution.
+   * 
+   * @param e		the event
+   */
+  public void scriptFinished(ScriptExecutionEvent e) {
+    if (e.getType() == Type.FINISHED)
+      showInfo("Script execution finished");
+    else if (e.getType() == Type.STOPPED)
+      showInfo("Script execution stopped by user");
+    else if (e.getType() == Type.ERROR)
+      showInfo("Script execution failed" + (e.hasAdditional() ? (": " + e.getAdditional()) : ""));
+    
+    if (e.getType() != Type.STARTED) {
+      m_NewAction.setEnabled(true);
+      m_OpenAction.setEnabled(true);
+      m_SaveAction.setEnabled(true);
+      m_SaveAsAction.setEnabled(true);
+      m_CutAction.setEnabled(true);
+      m_CopyAction.setEnabled(true);
+      m_PasteAction.setEnabled(true);
+      m_StartAction.setEnabled(true);
+      m_StopAction.setEnabled(false);
+    }
+    else {
+      m_NewAction.setEnabled(false);
+      m_OpenAction.setEnabled(false);
+      m_SaveAction.setEnabled(false);
+      m_SaveAsAction.setEnabled(false);
+      m_CutAction.setEnabled(false);
+      m_CopyAction.setEnabled(false);
+      m_PasteAction.setEnabled(false);
+      m_StartAction.setEnabled(false);
+      m_StopAction.setEnabled(true);
+    }
+  }
+  
+  /**
+   * The following two methods allow us to find an
+   * action provided by the editor kit by its name.
+   * 
+   * @param comp	the component to get the actions from
+   * @return		the relation
+   */
+  protected HashMap<Object, Action> createActionTable(JTextComponent comp) {
+    HashMap<Object, Action> 	result;
+    int				i;
+    Action[] 			actions;
+    Action 			action;
+    
+    result  = new HashMap<Object, Action>();
+    actions = comp.getActions();
+    for (i = 0; i < actions.length; i++) {
+      action = actions[i];
+      result.put(action.getValue(Action.NAME), action);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns a panel to be displayed with the AboutAction.
+   * 
+   * @return		the panel with some information on the scripting panel
+   */
+  protected abstract JPanel getAboutPanel();
+  
+  /**
+   * Returns the title (without the filename).
+   * 
+   * @return		the plain title
+   */
+  public abstract String getPlainTitle();
+  
+  /**
+   * Returns the current title for the frame/dialog.
+   * 
+   * @return		the title
+   */
+  public String getTitle() {
+    String	result;
+    
+    result = getPlainTitle();
+    
+    if (m_Script.isModified())
+      result = "*" + result;
+    
+    if (m_Script.getFilename() != null)
+      result += " [" + m_Script.getFilename() + "]";
+    
+    return result;
+  }
+  
+  /**
+   * Returns the text area that is used for displaying output on stdout
+   * and stderr.
+   * 
+   * @return		the JTextArea
+   */
+  public JTextPane getOutput() {
+    return m_TextOutput;
+  }
+  
+  /**
+   * Returns the menu bar to to be displayed in the frame.
+   * 
+   * @return		the menu bar, null if not applicable
+   */
+  public JMenuBar getMenuBar() {
+    JMenuBar	result;
+    JMenu	menu;
+    JMenuItem	menuitem;
+    
+    result = new JMenuBar();
+    
+    // File
+    menu = new JMenu("File");
+    menu.setMnemonic('F');
+    result.add(menu);
+    
+    // File/New
+    menuitem = new JMenuItem(m_NewAction);
+    menu.add(menuitem);
+    
+    // File/Open
+    menuitem = new JMenuItem(m_OpenAction);
+    menu.addSeparator();
+    menu.add(menuitem);
+    
+    // File/Save
+    menuitem = new JMenuItem(m_SaveAction);
+    menu.add(menuitem);
+    
+    // File/SaveAs
+    menuitem = new JMenuItem(m_SaveAsAction);
+    menu.add(menuitem);
+    
+    // File/Print
+    menuitem = new JMenuItem(m_PrintAction);
+    menu.addSeparator();
+    menu.add(menuitem);
+    
+    // File/Clear output
+    menuitem = new JMenuItem(m_ClearOutputAction);
+    menu.addSeparator();
+    menu.add(menuitem);
+    
+    // File/Exit
+    menuitem = new JMenuItem(m_ExitAction);
+    menu.addSeparator();
+    menu.add(menuitem);
+    
+    // Edit
+    menu = new JMenu("Edit");
+    menu.setMnemonic('E');
+    result.add(menu);
+    
+    // Edit/Undo
+    menuitem = new JMenuItem(m_UndoAction);
+    menu.add(menuitem);
+    
+    // Edit/Redo
+    menuitem = new JMenuItem(m_RedoAction);
+    menu.add(menuitem);
+    
+    // Edit/Cut
+    menuitem = new JMenuItem(m_CutAction);
+    menu.addSeparator();
+    menu.add(menuitem);
+    
+    // Edit/Copy
+    menuitem = new JMenuItem(m_CopyAction);
+    menu.add(menuitem);
+    
+    // Edit/Paste
+    menuitem = new JMenuItem(m_PasteAction);
+    menu.add(menuitem);
+    
+    // Script
+    menu = new JMenu("Script");
+    menu.setMnemonic('S');
+    result.add(menu);
+    
+    // Script/Start
+    menuitem = new JMenuItem(m_StartAction);
+    menu.add(menuitem);
+    
+    // Script/Stop
+    menuitem = new JMenuItem(m_StopAction);
+    menu.add(menuitem);
+    
+    // Script/Arguments
+    menuitem = new JMenuItem(m_ArgsAction);
+    menu.add(menuitem);
+    
+    // Help
+    menu = new JMenu("Help");
+    menu.setMnemonic('H');
+    result.add(menu);
+    
+    // Help/About
+    menuitem = new JMenuItem(m_AboutAction);
+    menu.add(menuitem);
+    
+    return result;
+  }
+  
+  /**
+   * Updates the info shown in the bottom panel.
+   * 
+   * @param msg		the message to display
+   */
+  protected void showInfo(String msg) {
+    if (msg == null)
+      msg = " ";
+    m_LabelInfo.setText(msg);
+  }
+  
+  /**
+   * Opens the specified file.
+   * 
+   * @param file	the file to open
+   */
+  public void open(File file) {
+    m_Script.open(file);
+  }
+  
+  /**
+   * Checks whether the script is modified and asks the user to save it or not.
+   * If everything is fine and one can ignore the modified state, true is 
+   * returned.
+   * 
+   * @return		true if one can proceed
+   */
+  protected boolean checkModified() {
+    boolean	result;
+    int		retVal;
+    
+    result = true;
+    
+    if (m_Script.isModified()) {
+      retVal = JOptionPane.showConfirmDialog(
+	  FileScriptingPanel.this, 
+	  "Script not saved - save it now?", 
+	  "Confirm", 
+	  JOptionPane.YES_NO_CANCEL_OPTION);
+      
+      if (retVal == JOptionPane.YES_OPTION) {
+	if (m_Script.getFilename() != null)
+	  m_Script.save();
+	else
+	  m_SaveAsAction.actionPerformed(null);
+	result = !m_Script.isModified();
+      }
+      else if (retVal == JOptionPane.CANCEL_OPTION) {
+	result = false;
+      }
+    }
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/Groovy.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/Groovy.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/Groovy.props	(revision 29)
@@ -0,0 +1,146 @@
+# Defines the syntax highlighting for Groovy
+#
+# Author:  FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 5293 $
+
+########
+# Font #
+########
+
+# the font name
+FontName=monospaced
+
+# the font size
+FontSize=12
+
+#############################################
+# Colors (R,G,B format can be used as well) #
+#############################################
+
+# the font color
+ForegroundColor=black
+
+# the background color
+BackgroundColor=white
+
+# the color for keywords
+KeywordColor=blue
+
+# the color for comments
+CommentColor=gray
+
+# the color for strings
+StringColor=red
+
+##########
+# Syntax #
+##########
+
+# whether the syntax highlighting etc is on or not (true|false)
+Syntax=true
+
+# the number of spaces to use for indentation
+Indentation=2
+
+# the number of characters that a single tab represents
+Tabs=2
+
+# whether to use blanks instead of tabs (true|false)
+UseBlanks=true
+
+# word delimiters
+Delimiters=;:{}()[]+-/%<=>!&|^~*
+
+# delimiter for quoted strings/characters
+QuoteDelimiters="'
+
+# the character to escape quotes
+QuoteEscape=\\
+
+# whether multi-line comments are enabled (true|false)
+MultiLineComment=true
+
+# the start delimiter of a multi-line comment
+MultiLineCommentStart=/*
+
+# the end delimiter of a multi-line comment
+MultiLineCommentEnd=*/
+
+# the start delimiter of a single-line comment
+SingleLineCommentStart=//
+
+# whether to add matching block ends (true|false)
+AddMatchingBlockEnd=true
+
+# the start of a block (e.g., "opening bracket")
+BlockStart={
+
+# the end of a block (e.g., "closing bracket")
+BlockEnd=}
+
+# whether the keywords are case-sensitive (true|false)
+CaseSensitive=true
+
+# the keywords
+Keywords=\
+	abstract,\
+	assert,\
+	boolean,\
+	break,\
+	byte,\
+	byvalue,\
+	case,\
+	cast,\
+	catch,\
+	char,\
+	class,\
+	const,\
+	continue,\
+	def,\
+	default,\
+	do,\
+	double,\
+	else,\
+	extends,\
+	false,\
+	final,\
+	finally,\
+	float,\
+	for,\
+	future,\
+	generic,\
+	goto,\
+	if,\
+	implements,\
+	import,\
+	inner,\
+	instanceof,\
+	int,\
+	interface,\
+	long,\
+	native,\
+	new,\
+	null,\
+	operator,\
+	outer,\
+	package,\
+	private,\
+	protected,\
+	public,\
+	rest,\
+	return,\
+	short,\
+	static,\
+	super,\
+	switch,\
+	synchronized,\
+	this,\
+	throw,\
+	throws,\
+	transient,\
+	true,\
+	try,\
+	var,\
+	void,\
+	volatile,\
+	while
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyPanel.java	(revision 29)
@@ -0,0 +1,155 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GroovyPanel.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Utils;
+import weka.gui.BrowserHelper;
+import weka.gui.ComponentHelper;
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.text.Document;
+
+/**
+ * A scripting panel for <a href="http://groovy.codehaus.org/" target="_blank">Groovy</a>.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5837 $
+ */
+public class GroovyPanel
+  extends FileScriptingPanel {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -3475707604414854111L;
+  
+  /** the Groovy setup. */
+  public final static String PROPERTIES_FILE = "weka/gui/scripting/Groovy.props";
+  
+  /**
+   * Creates a new JTextPane for the code.
+   * 
+   * @return		the text pane
+   */
+  protected JTextPane newCodePane() {
+    JTextPane		result;
+    SyntaxDocument	doc;
+    Properties		props;
+    
+    try {
+      props = Utils.readProperties(PROPERTIES_FILE);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      props = new Properties();
+    }
+    
+    result = new JTextPane();
+    if (props.getProperty("Syntax", "false").equals("true")) {
+      doc    = new SyntaxDocument(props);
+      result.setDocument(doc);
+      result.setBackground(doc.getBackgroundColor());
+    }
+    else {
+      result.setForeground(VisualizeUtils.processColour(props.getProperty("ForegroundColor", "black"), Color.BLACK));
+      result.setBackground(VisualizeUtils.processColour(props.getProperty("BackgroundColor", "white"), Color.WHITE));
+      result.setFont(new Font(props.getProperty("FontName", "monospaced"), Font.PLAIN, Integer.parseInt(props.getProperty("FontSize", "12"))));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns an icon to be used in a frame.
+   * 
+   * @return		the icon
+   */
+  public ImageIcon getIcon() {
+    return ComponentHelper.getImageIcon(IMAGES_DIR + "/groovy_small.png");
+  }
+  
+  /**
+   * Returns a panel to be displayed with the AboutAction.
+   * 
+   * @return		the panel with some information on the scripting panel
+   */
+  protected JPanel getAboutPanel() {
+    JPanel	result;
+    JPanel	panel;
+    
+    result = new JPanel(new BorderLayout());
+    result.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+    
+    // image
+    result.add(new JLabel(ComponentHelper.getImageIcon(IMAGES_DIR + "/groovy_medium.png")), BorderLayout.CENTER);
+    
+    // links
+    panel = new JPanel(new GridLayout(5, 1));
+    panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+    result.add(panel, BorderLayout.SOUTH);
+    
+    panel.add(new JLabel("Groovy homepage"));
+    panel.add(BrowserHelper.createLink("http://groovy.codehaus.org/", null));
+    panel.add(new JLabel(" "));
+    panel.add(new JLabel("Weka and Groovy"));
+    panel.add(BrowserHelper.createLink("http://weka.wikispaces.com/Using+Weka+from+Groovy", null));
+    
+    return result;
+  }
+
+  /**
+   * Returns the title (without the filename).
+   * 
+   * @return		the plain title
+   */
+  public String getPlainTitle() {
+    return "Groovy Console";
+  }
+
+  /**
+   * Returns an initialized script object.
+   * 
+   * @param doc		the document to use as basis
+   * @return		the initialized script
+   */
+  protected Script newScript(Document doc) {
+    return new GroovyScript(doc);
+  }
+  
+  /**
+   * Displays the panel in a frame.
+   * 
+   * @param args	can take a file as first argument
+   */
+  public static void main(String[] args) {
+    showPanel(new GroovyPanel(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyScript.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyScript.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/GroovyScript.java	(revision 29)
@@ -0,0 +1,192 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GroovyScript.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.scripting.Groovy;
+import weka.gui.ExtensionFileFilter;
+
+import java.io.File;
+
+import javax.swing.text.Document;
+
+/**
+ * Represents a <a href="http://groovy.codehaus.org/" target="_blank">Groovy</a> script.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class GroovyScript
+  extends Script {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -3708517162415549420L;
+
+  /**
+   * Executes a Groovy script in a thread.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5142 $
+   */
+  public static class GroovyThread
+    extends ScriptThread {
+    
+    /**
+     * Initializes the thread.
+     * 
+     * @param owner	the owning script
+     * @param args	the commandline arguments
+     */
+    public GroovyThread(Script owner, String[] args) {
+      super(owner, args);
+    }
+
+    /**
+     * Tests whether the groovy object has a certain method.
+     * 
+     * @param groovy	the Groovy object to inspect
+     * @param name	the method to look for
+     * @return		true if the object has the given method
+     */
+    protected boolean hasMethod(Object groovy, String name) {
+      boolean	result;
+      try {
+	groovy.getClass().getMethod(name, new Class[]{String[].class});
+	result = true;
+      }
+      catch (Exception e) {
+	result = false;
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Performs the actual run.
+     */
+    protected void doRun() {
+      Object	groovy;
+      
+      groovy = Groovy.newInstance(m_Owner.getFilename(), Object.class);
+      if (hasMethod(groovy, "run"))
+	Groovy.invoke(groovy, "run", new Class[]{String[].class}, new Object[]{getArgs()});
+      else if (hasMethod(groovy, "main"))
+	Groovy.invoke(groovy, "main", new Class[]{String[].class}, new Object[]{getArgs()});
+      else
+	throw new IllegalStateException("Neither 'run' nor 'main' method found!");
+    }
+  }
+  
+  /**
+   * Initializes the script.
+   */
+  public GroovyScript() {
+    super();
+  }
+  
+  /**
+   * Initializes the script.
+   * 
+   * @param doc		the document to use as basis
+   */
+  public GroovyScript(Document doc) {
+    super(doc);
+  }
+  
+  /**
+   * Initializes the script. Automatically loads the specified file, if not
+   * null.
+   * 
+   * @param doc		the document to use as basis
+   * @param file	the file to load (if not null)
+   */
+  public GroovyScript(Document doc, File file) {
+    super(doc, file);
+  }
+  
+  /**
+   * Returns the extension filters for this type of script.
+   * 
+   * @return		the filters
+   */
+  public ExtensionFileFilter[] getFilters() {
+    ExtensionFileFilter[]	result;
+    
+    result = new ExtensionFileFilter[1];
+    result[0] = new ExtensionFileFilter(getDefaultExtension(), "Groovy script (*" + getDefaultExtension() + ")");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the default extension. Gets automatically added to files if
+   * their name doesn't end with this.
+   * 
+   * @return		the default extension (incl. the dot)
+   */
+  public String getDefaultExtension() {
+    return ".groovy";
+  }
+
+  /**
+   * Returns whether scripts can be executed, i.e., Groovy is present.
+   * 
+   * @return		true if scripts can be executed
+   */
+  protected boolean canExecuteScripts() {
+    return Groovy.isPresent();
+  }
+  
+  /**
+   * Performs pre-execution checks.
+   * <p/>
+   * This method checks whether Groovy is available (throws an exception if not).
+   * 
+   * @param args	optional commandline arguments
+   * @throws Exception	if checks fail
+   */
+  protected void preCheck(String[] args) throws Exception {
+    super.preCheck(args);
+    
+    if (!Groovy.isPresent())
+      throw new Exception("Groovy classes are not present in CLASSPATH!");
+  }
+
+  /**
+   * Returns a new thread to execute.
+   * 
+   * @param args	optional commandline arguments
+   * @return		the new thread object
+   */
+  public ScriptThread newThread(String[] args) {
+    return new GroovyThread(this, args);
+  }
+  
+  /**
+   * Runs the script from commandline. Use "-h" to list all options.
+   * 
+   * @param args	the commandline arguments
+   * @throws Exception	if execution fails
+   */
+  public static void main(String[] args) throws Exception {
+    runScript(new GroovyScript(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/Jython.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/Jython.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/Jython.props	(revision 29)
@@ -0,0 +1,113 @@
+# Defines the syntax highlighting for Jython
+#
+# Author:  FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 5293 $
+
+########
+# Font #
+########
+
+# the font name
+FontName=monospaced
+
+# the font size
+FontSize=12
+
+#############################################
+# Colors (R,G,B format can be used as well) #
+#############################################
+
+# the font color
+ForegroundColor=black
+
+# the background color
+BackgroundColor=white
+
+# the color for keywords
+KeywordColor=blue
+
+# the color for comments
+CommentColor=gray
+
+# the color for strings
+StringColor=red
+
+##########
+# Syntax #
+##########
+
+# whether the syntax highlighting etc is on or not (true|false)
+Syntax=true
+
+# the number of spaces to use for indentation instead of TAB
+Indentation=4
+
+# the number of characters that a single tab represents
+Tabs=4
+
+# whether to use blanks instead of tabs (true|false)
+UseBlanks=true
+
+# word delimiters
+Delimiters=;:{}()[]+-/%<=>!&|^~*
+
+# delimiter for quoted strings/characters
+QuoteDelimiters="'
+
+# the character to escape quotes
+QuoteEscape=\\
+
+# whether multi-line comments are enabled (true|false)
+MultiLineComment=true
+
+# the start delimiter of a multi-line comment
+MultiLineCommentStart="""
+
+# the end delimiter of a multi-line comment
+MultiLineCommentEnd="""
+
+# the start delimiter of a single-line comment
+SingleLineCommentStart=#
+
+# whether to add matching block end (true|false)
+AddMatchingBlockEnd=false
+
+# the start of a block (e.g., "opening bracket")
+BlockStart=
+
+# the end of a block (e.g., "closing bracket")
+BlockEnd=
+
+# whether the keywords are case-sensitive (true|false)
+CaseSensitive=true
+
+# the keywords
+Keywords=\
+	and,\
+	assert,\
+	break,\
+	class,\
+	continue,\
+	def,\
+	del,\
+	elif,\
+	else,\
+	except,\
+	exec,\
+	finally,\
+	for,\
+	from,\
+	global,\
+	if,\
+	import,\
+	in,\
+	is,\
+	lambda,\
+	not,\
+	or,\
+	pass,\
+	print,\
+	raise,\
+	return,\
+	try,\
+	while,\
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/JythonPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/JythonPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/JythonPanel.java	(revision 29)
@@ -0,0 +1,155 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JythonPanel.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Utils;
+import weka.gui.BrowserHelper;
+import weka.gui.ComponentHelper;
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.text.Document;
+
+/**
+ * A scripting panel for <a href="http://www.jython.org/" target="_blank">Jython</a>.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5837 $
+ */
+public class JythonPanel
+  extends FileScriptingPanel {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = -827358576217085413L;
+  
+  /** the Groovy setup. */
+  public final static String PROPERTIES_FILE = "weka/gui/scripting/Jython.props";
+  
+  /**
+   * Creates a new JTextPane for the code.
+   * 
+   * @return		the text pane
+   */
+  protected JTextPane newCodePane() {
+    JTextPane		result;
+    SyntaxDocument	doc;
+    Properties		props;
+    
+    try {
+      props = Utils.readProperties(PROPERTIES_FILE);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      props = new Properties();
+    }
+    
+    result = new JTextPane();
+    if (props.getProperty("Syntax", "false").equals("true")) {
+      doc    = new SyntaxDocument(props);
+      result.setDocument(doc);
+      result.setBackground(doc.getBackgroundColor());
+    }
+    else {
+      result.setForeground(VisualizeUtils.processColour(props.getProperty("ForegroundColor", "black"), Color.BLACK));
+      result.setBackground(VisualizeUtils.processColour(props.getProperty("BackgroundColor", "white"), Color.WHITE));
+      result.setFont(new Font(props.getProperty("FontName", "monospaced"), Font.PLAIN, Integer.parseInt(props.getProperty("FontSize", "12"))));
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Returns an icon to be used in a frame.
+   * 
+   * @return		the icon
+   */
+  public ImageIcon getIcon() {
+    return ComponentHelper.getImageIcon(IMAGES_DIR + "/jython_small.png");
+  }
+  
+  /**
+   * Returns a panel to be displayed with the AboutAction.
+   * 
+   * @return		the panel with some information on the scripting panel
+   */
+  protected JPanel getAboutPanel() {
+    JPanel	result;
+    JPanel	panel;
+    
+    result = new JPanel(new BorderLayout());
+    result.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+    
+    // image
+    result.add(new JLabel(ComponentHelper.getImageIcon(IMAGES_DIR + "/jython_medium.png")), BorderLayout.CENTER);
+    
+    // links
+    panel = new JPanel(new GridLayout(5, 1));
+    panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+    result.add(panel, BorderLayout.SOUTH);
+    
+    panel.add(new JLabel("Jython homepage"));
+    panel.add(BrowserHelper.createLink("http://www.jython.org/", null));
+    panel.add(new JLabel(" "));
+    panel.add(new JLabel("Weka and Jython"));
+    panel.add(BrowserHelper.createLink("http://weka.wikispaces.com/Using+Weka+from+Jython", null));
+    
+    return result;
+  }
+
+  /**
+   * Returns the title (without the filename).
+   * 
+   * @return		the plain title
+   */
+  public String getPlainTitle() {
+    return "Jython Console";
+  }
+
+  /**
+   * Returns an initialized script object.
+   * 
+   * @param doc		the document to use as basis
+   * @return		the initialized script
+   */
+  protected Script newScript(Document doc) {
+    return new JythonScript(doc);
+  }
+  
+  /**
+   * Displays the panel in a frame.
+   * 
+   * @param args	can take a file as first argument
+   */
+  public static void main(String[] args) {
+    showPanel(new JythonPanel(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/JythonScript.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/JythonScript.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/JythonScript.java	(revision 29)
@@ -0,0 +1,187 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * JythonScript.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Utils;
+import weka.core.scripting.Jython;
+import weka.gui.ExtensionFileFilter;
+
+import java.io.File;
+
+import javax.swing.text.Document;
+
+/**
+ * Represents a <a href="http://www.jython.org/" target="_blank">Jython</a> script.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class JythonScript
+  extends Script {
+  
+  /** for serialization. */
+  private static final long serialVersionUID = 3469648507172973169L;
+
+  /**
+   * Executes a Jython script in a thread.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5142 $
+   */
+  public static class JythonThread
+    extends ScriptThread {
+    
+    /**
+     * Initializes the thread.
+     * 
+     * @param owner	the owning script
+     * @param args	the commandline arguments
+     */
+    public JythonThread(Script owner, String[] args) {
+      super(owner, args);
+    }
+
+    /**
+     * Performs the actual run.
+     */
+    protected void doRun() {
+      Jython	jython;
+      Class[]	classes;
+      Object[]	params;
+      String	argv;
+      String	arg;
+      int	i;
+      
+      classes = new Class[]{String.class};
+      params  = new Object[]{m_Owner.getFilename().getPath()};
+      argv    = "sys.argv = ['" + Utils.backQuoteChars(m_Owner.getFilename().getPath()) + "'";
+      for (i = 0; i < getArgs().length; i++) {
+	arg   = Utils.backQuoteChars(getArgs()[i]);
+	argv += ", '" + arg + "'";
+      }
+      argv   += "]";
+      
+      jython  = new Jython();
+      
+      // set commandline parameters
+      jython.invoke("exec", new Class[]{String.class}, new Object[]{"import sys"});
+      jython.invoke("exec", new Class[]{String.class}, new Object[]{argv});
+      
+      jython.invoke("execfile", classes, params);
+    }
+  }
+  
+  /**
+   * Initializes the script.
+   */
+  public JythonScript() {
+    super();
+  }
+  
+  /**
+   * Initializes the script.
+   * 
+   * @param doc		the document to use as basis
+   */
+  public JythonScript(Document doc) {
+    super(doc);
+  }
+  
+  /**
+   * Initializes the script. Automatically loads the specified file, if not
+   * null.
+   * 
+   * @param doc		the document to use as basis
+   * @param file	the file to load (if not null)
+   */
+  public JythonScript(Document doc, File file) {
+    super(doc, file);
+  }
+  
+  /**
+   * Returns the extension filters for this type of script.
+   * 
+   * @return		the filters
+   */
+  public ExtensionFileFilter[] getFilters() {
+    ExtensionFileFilter[]	result;
+    
+    result = new ExtensionFileFilter[1];
+    result[0] = new ExtensionFileFilter(getDefaultExtension(), "Jython script (*" + getDefaultExtension() + ")");
+    
+    return result;
+  }
+  
+  /**
+   * Returns the default extension. Gets automatically added to files if
+   * their name doesn't end with this.
+   * 
+   * @return		the default extension (incl. the dot)
+   */
+  public String getDefaultExtension() {
+    return ".py";
+  }
+
+  /**
+   * Returns whether scripts can be executed, i.e., Jython is present.
+   * 
+   * @return		true if scripts can be executed
+   */
+  protected boolean canExecuteScripts() {
+    return Jython.isPresent();
+  }
+  
+  /**
+   * Performs pre-execution checks.
+   * <p/>
+   * This method checks whether Jython is available (throws an exception if not).
+   * 
+   * @param args	optional commandline arguments
+   * @throws Exception	if checks fail
+   */
+  protected void preCheck(String[] args) throws Exception {
+    super.preCheck(args);
+    
+    if (!Jython.isPresent())
+      throw new Exception("Jython classes are not present in CLASSPATH!");
+  }
+
+  /**
+   * Returns a new thread to execute.
+   * 
+   * @param args	optional commandline arguments
+   * @return		the new thread object
+   */
+  public ScriptThread newThread(String[] args) {
+    return new JythonThread(this, args);
+  }
+  
+  /**
+   * Runs the script from commandline. Use "-h" to list all options.
+   * 
+   * @param args	the commandline arguments
+   * @throws Exception	if execution fails
+   */
+  public static void main(String[] args) throws Exception {
+    runScript(new JythonScript(), args);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/Script.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/Script.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/Script.java	(revision 29)
@@ -0,0 +1,713 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Script.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Option;
+import weka.core.OptionHandler;
+import weka.core.SerializedObject;
+import weka.core.Utils;
+import weka.core.WekaException;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.scripting.event.ScriptExecutionEvent;
+import weka.gui.scripting.event.ScriptExecutionListener;
+import weka.gui.scripting.event.ScriptExecutionEvent.Type;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.Document;
+
+/**
+ * A simple helper class for loading, saving scripts.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public abstract class Script
+  implements OptionHandler, Serializable {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 5053328052680586401L;
+
+  /**
+   * The Thread for running a script.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5142 $
+   */
+  public abstract static class ScriptThread
+    extends Thread {
+    
+    /** the owning script. */
+    protected Script m_Owner;
+    
+    /** commandline arguments. */
+    protected String[] m_Args;
+    
+    /** whether the thread was stopped. */
+    protected boolean m_Stopped;
+    
+    /**
+     * Initializes the thread.
+     * 
+     * @param owner	the owning script
+     * @param args	the commandline arguments
+     */
+    public ScriptThread(Script owner, String[] args) {
+      super();
+      
+      m_Owner = owner;
+      m_Args  = args.clone();
+    }
+
+    /**
+     * Returns the owner.
+     * 
+     * @return		the owning script
+     */
+    public Script getOwner() {
+      return m_Owner;
+    }
+    
+    /**
+     * Returns the commandline args.
+     * 
+     * @return		the arguments
+     */
+    public String[] getArgs() {
+      return m_Args;
+    }
+    
+    /**
+     * Performs the actual run.
+     */
+    protected abstract void doRun();
+    
+    /**
+     * Executes the script.
+     */
+    public void run() {
+      m_Stopped = false;
+      
+      getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.STARTED));
+      try {
+	doRun();
+	if (!m_Stopped)
+	  getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.FINISHED));
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.ERROR, e));
+      }
+      getOwner().m_ScriptThread = null;
+    }
+    
+    /**
+     * Stops the script execution.
+     */
+    public void stopScript() {
+      if (isAlive()) {
+	m_Stopped = true;
+	try {
+	  stop();
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+      }
+    }
+  }
+
+  /** the backup extension. */
+  public final static String BACKUP_EXTENSION = ".bak";
+  
+  /** the document this script is a wrapper around. */
+  protected Document m_Document;
+  
+  /** the filename of the script. */
+  protected File m_Filename;
+  
+  /** the newline used on this platform. */
+  protected String m_NewLine;
+  
+  /** whether the script is modified. */
+  protected boolean m_Modified;
+  
+  /** the current script thread. */
+  protected transient ScriptThread m_ScriptThread;
+  
+  /** optional listeners when the script finishes. */
+  protected HashSet<ScriptExecutionListener> m_FinishedListeners;
+  
+  /**
+   * Initializes the script.
+   */
+  public Script() {
+    this(null);
+  }
+  
+  /**
+   * Initializes the script.
+   * 
+   * @param doc		the document to use as basis
+   */
+  public Script(Document doc) {
+    this(doc, null);
+  }
+  
+  /**
+   * Initializes the script. Automatically loads the specified file, if not
+   * null.
+   * 
+   * @param doc		the document to use as basis
+   * @param file	the file to load (if not null)
+   */
+  public Script(Document doc, File file) {
+    initialize();
+    
+    m_Document = doc;
+    
+    if (m_Document != null) {
+      m_Document.addDocumentListener(new DocumentListener() {
+	public void changedUpdate(DocumentEvent e) {
+	  m_Modified = true;
+	}
+	public void insertUpdate(DocumentEvent e) {
+	  m_Modified = true;
+	}
+	public void removeUpdate(DocumentEvent e) {
+	  m_Modified = true;
+	}
+      });
+    }
+    
+    if (file != null)
+      open(file);
+  }
+  
+  /**
+   * Initializes the script.
+   */
+  protected void initialize() {
+    m_Filename          = null;
+    m_NewLine           = System.getProperty("line.separator");
+    m_Modified          = false;
+    m_ScriptThread      = null;
+    m_FinishedListeners = new HashSet<ScriptExecutionListener>();
+  }
+
+  /**
+   * Returns an enumeration describing the available options.
+   *
+   * @return an enumeration of all the available options
+   */
+  public Enumeration listOptions() {
+    return new Vector().elements();
+  }
+
+  /**
+   * Parses a given list of options. 
+   *
+   * @param options 	the list of options as an array of strings
+   * @throws Exception 	if an option is not supported
+   */
+  public void setOptions(String[] options) throws Exception {
+  }
+
+  /**
+   * Gets the current settings of the script.
+   *
+   * @return 		an array of strings suitable for passing to setOptions
+   */
+  public String[] getOptions() {
+    return new String[0];
+  }
+  
+  /**
+   * Returns the extension filters for this type of script.
+   * 
+   * @return		the filters
+   */
+  public abstract ExtensionFileFilter[] getFilters();
+  
+  /**
+   * Returns the default extension. Gets automatically added to files if
+   * their name doesn't end with this.
+   * 
+   * @return		the default extension (incl. the dot)
+   * @see		#saveAs(File)
+   */
+  public abstract String getDefaultExtension();
+  
+  /**
+   * Returns the current filename.
+   * 
+   * @return		the filename, null if no file loaded/saved
+   */
+  public File getFilename() {
+    return m_Filename;
+  }
+  
+  /**
+   * Returns the new line string in use.
+   * 
+   * @return		the new line string
+   */
+  public String getNewLine() {
+    return m_NewLine;
+  }
+  
+  /**
+   * Returns whether the script is modified.
+   * 
+   * @return		true if the script is modified
+   */
+  public boolean isModified() {
+    return m_Modified;
+  }
+  
+  /**
+   * Returns the content.
+   * 
+   * @return		the content or null in case of an error
+   */
+  public String getContent() {
+    String	result;
+    
+    if (m_Document == null)
+      return "";
+    
+    try {
+      synchronized(m_Document) {
+	result = m_Document.getText(0, m_Document.getLength());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Sets the content.
+   * 
+   * @param value	the new content
+   */
+  public void setContent(String value) {
+    if (m_Document == null)
+      return;
+    
+    try {
+      m_Document.insertString(0, value, null);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Checks whether the extension of the file is a known one.
+   * 
+   * @param file	the file to check
+   * @return		true if the exetnsion is known
+   */
+  protected boolean checkExtension(File file) {
+    boolean			result;
+    int				i;
+    int				n;
+    ExtensionFileFilter[]	filters;
+    String[]			exts;
+
+    result = false;
+    filters  = getFilters();
+    for (i = 0; i < filters.length; i++) {
+      exts = filters[i].getExtensions();
+      for (n = 0; n < exts.length; n++) {
+	if (file.getName().endsWith(exts[n])) {
+	  result = true;
+	  break;
+	}
+      }
+      if (result)
+	break;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Empties the document.
+   */
+  public void empty() {
+    if (m_Document != null) {
+      try {
+	m_Document.remove(0, m_Document.getLength());
+      }
+      catch (Exception e) {
+	// ignored
+      }
+    }
+    
+    m_Modified = false;
+    m_Filename = null;
+  }
+  
+  /**
+   * Tries to open the file.
+   * 
+   * @param file	the file to open
+   * @return		true if successfully read
+   */
+  public boolean open(File file) {
+    boolean	result;
+    String	content;
+
+    if (m_Document == null)
+      return true;
+    
+    // Warn if extension unwknown
+    if (!checkExtension(file))
+      System.err.println("Extension of file '" + file + "' is unknown!");
+    
+    try {
+      // clear old content
+      m_Document.remove(0, m_Document.getLength());
+      
+      // add new content
+      content = ScriptUtils.load(file);
+      if (content == null)
+	throw new WekaException("Error reading content of file '" + file + "'!");
+      m_Document.insertString(0, content, null);
+      
+      m_Modified = false;
+      m_Filename = file;
+      result     = true;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      try {
+	m_Document.remove(0, m_Document.getLength());
+      }
+      catch (Exception ex) {
+	// ignored
+      }
+      result     = false;
+      m_Filename = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Saves the file under with the current filename.
+   * 
+   * @return		true if successfully written
+   */
+  public boolean save() {
+    if (m_Filename == null)
+      return false;
+    else
+      return saveAs(m_Filename);
+  }
+  
+  /**
+   * Saves the file under with the given filename (and updates the internal
+   * filename).
+   * 
+   * @param file	the filename to write the content to
+   * @return		true if successfully written
+   */
+  public boolean saveAs(File file) {
+    boolean	result;
+    File	backupFile;
+    
+    if (m_Document == null)
+      return true;
+    
+    // correct extension?
+    if (!checkExtension(file))
+      file = new File(file.getPath() + getDefaultExtension());
+
+    // backup previous file
+    if (file.exists()) {
+      backupFile = new File(file.getPath() + BACKUP_EXTENSION);
+      try {
+	ScriptUtils.copy(file, backupFile);
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+      }
+    }
+    
+    // save current content
+    try {
+      result     = ScriptUtils.save(file, m_Document.getText(0, m_Document.getLength()));
+      m_Filename = file;
+      m_Modified = false;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = false;
+    }
+    
+    return result;
+  }
+
+  /**
+   * Returns whether scripts can be executed.
+   * 
+   * @return		true if scripts can be executed
+   */
+  protected abstract boolean canExecuteScripts();
+
+  /**
+   * Returns a new thread to execute.
+   * 
+   * @param args	optional commandline arguments
+   * @return		the new thread object
+   */
+  public abstract ScriptThread newThread(String[] args);
+  
+  /**
+   * Performs pre-execution checks:
+   * <ul>
+   * 	<li>whether a script is currently running.</li>
+   * 	<li>whether script has changed and needs saving</li>
+   * 	<li>whether a filename is set (= empty content)</li>
+   * </ul>
+   * Throws exceptions if checks not met.
+   * 
+   * @param args	optional commandline arguments
+   * @throws Exception	if checks fail
+   */
+  protected void preCheck(String[] args) throws Exception {
+    if (m_ScriptThread != null)
+      throw new Exception("A script is currently running!");
+    if (m_Modified)
+      throw new Exception("The Script has been modified!");
+    if (m_Filename == null)
+      throw new Exception("The Script contains no content?");
+  }
+  
+  /**
+   * Executes the script.
+   * 
+   * @param args	optional commandline arguments
+   */
+  protected void execute(String[] args) {
+    m_ScriptThread = newThread(args);
+    try {
+      m_ScriptThread.start();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Executes the script.
+   * 
+   * @param args	optional commandline arguments, can be null
+   * @throws Exception	if checks or execution fail
+   */
+  public void start(String[] args) throws Exception {
+    if (args == null)
+      args = new String[0];
+    
+    preCheck(args);
+    
+    execute(args);
+  }
+  
+  /**
+   * Stops the execution of the script.
+   */
+  public void stop() {
+    if (isRunning()) {
+      m_ScriptThread.stopScript();
+      m_ScriptThread = null;
+      notifyScriptFinishedListeners(new ScriptExecutionEvent(this, Type.STOPPED));
+    }
+  }
+  
+  /**
+   * Executes the script without loading it first.
+   * 
+   * @param file	the script to execute
+   * @param args	the commandline parameters for the script
+   */
+  public void run(File file, String[] args) {
+    Script	script;
+    
+    try {
+      script = (Script) new SerializedObject(this).getObject();
+      script.m_Filename = file;
+      script.m_Modified = false;
+      script.start(args);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * Returns whether the script is still running.
+   * 
+   * @return		true if the script is still running
+   */
+  public boolean isRunning() {
+    return (m_ScriptThread != null);
+  }
+  
+  /**
+   * Adds the given listener to its internal list.
+   * 
+   * @param l		the listener to add
+   */
+  public void addScriptFinishedListener(ScriptExecutionListener l) {
+    m_FinishedListeners.add(l);
+  }
+  
+  /**
+   * Removes the given listener from its internal list.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeScriptFinishedListener(ScriptExecutionListener l) {
+    m_FinishedListeners.remove(l);
+  }
+  
+  /**
+   * Notifies all listeners.
+   * 
+   * @param e		the event to send to all listeners
+   */
+  protected void notifyScriptFinishedListeners(ScriptExecutionEvent e) {
+    Iterator<ScriptExecutionListener>	iter;
+    
+    iter = m_FinishedListeners.iterator();
+    while (iter.hasNext())
+      iter.next().scriptFinished(e);
+  }
+  
+  /**
+   * Returns the content as string.
+   * 
+   * @return		the current content
+   */
+  public String toString() {
+    String	result;
+    
+    try {
+      if (m_Document == null)
+	result = "";
+      else
+	result = m_Document.getText(0, m_Document.getLength());
+    }
+    catch (Exception e) {
+      result = "";
+    }
+    
+    return result.toString();
+  }
+
+  /**
+   * Make up the help string giving all the command line options.
+   *
+   * @param script 	the script to include options for
+   * @return 		a string detailing the valid command line options
+   */
+  protected static String makeOptionString(Script script) {
+    StringBuffer 	result;
+    Enumeration 	enm;
+    Option 		option;
+    
+    result = new StringBuffer("");
+
+    result.append("\nHelp requested:\n\n");
+    result.append("-h or -help\n");
+    result.append("\tDisplays this help screen.\n");
+    result.append("-s <file>\n");
+    result.append("\tThe script to execute.\n");
+
+    enm = script.listOptions();
+    while (enm.hasMoreElements()) {
+      option = (Option) enm.nextElement();
+      result.append(option.synopsis() + '\n');
+      result.append(option.description() + "\n");
+    }
+
+    result.append("\n");
+    result.append("Any additional options are passed on to the script as\n");
+    result.append("command-line parameters.\n");
+    result.append("\n");
+    
+    return result.toString();
+  }
+  
+  /**
+   * Runs the specified script. All options that weren't "consumed" (like 
+   * "-s" for the script filename), will be used as commandline arguments for 
+   * the actual script.
+   * 
+   * @param script	the script object to use
+   * @param args	the commandline arguments
+   * @throws Exception	if execution fails
+   */
+  public static void runScript(Script script, String[] args) throws Exception {
+    String		tmpStr;
+    File		scriptFile;
+    Vector<String>	options;
+    int			i;
+    
+    if (Utils.getFlag('h', args) || Utils.getFlag("help", args)) {
+      System.out.println(makeOptionString(script));
+    }
+    else {
+      // process options
+      tmpStr = Utils.getOption('s', args);
+      if (tmpStr.length() == 0)
+        throw new WekaException("No script supplied!");
+      else
+	scriptFile = new File(tmpStr);
+      script.setOptions(args);
+      
+      // remove empty elements from array
+      options = new Vector<String>();
+      for (i = 0; i < args.length; i++) {
+	if (args[i].length() > 0)
+	  options.add(args[i]);
+      }
+      
+      // run script
+      script.run(scriptFile, options.toArray(new String[options.size()]));
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptUtils.java	(revision 29)
@@ -0,0 +1,197 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ScriptUtils.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A helper class for Script related stuff.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ */
+public class ScriptUtils {
+  
+  /**
+   * Copies or moves files and directories (recursively).
+   * If targetLocation does not exist, it will be created.
+   * <p/>
+   * Original code from <a href="http://www.java-tips.org/java-se-tips/java.io/how-to-copy-a-directory-from-one-location-to-another-loc.html" target="_blank">Java-Tips.org</a>.
+   * 
+   * @param sourceLocation	the source file/dir
+   * @param targetLocation	the target file/dir
+   * @param move		if true then the source files/dirs get deleted
+   * 				as soon as copying finished
+   * @throws IOException	if copying/moving fails
+   */
+  protected static void copyOrMove(File sourceLocation, File targetLocation, boolean move) throws IOException {
+    String[] 		children;
+    int 		i;
+    InputStream 	in;
+    OutputStream 	out;
+    byte[] 		buf;
+    int 		len;
+    
+    if (sourceLocation.isDirectory()) {
+      if (!targetLocation.exists())
+	targetLocation.mkdir();
+
+      children = sourceLocation.list();
+      for (i = 0; i < children.length; i++) {
+	copyOrMove(
+	    new File(sourceLocation, children[i]),
+	    new File(targetLocation, children[i]),
+	    move);
+      }
+      
+      if (move)
+	sourceLocation.delete();
+    }
+    else {
+      in = new FileInputStream(sourceLocation);
+      // do we need to append the filename?
+      if (targetLocation.isDirectory())
+	out = new FileOutputStream(targetLocation.getAbsolutePath() + File.separator + sourceLocation.getName());
+      else
+	out = new FileOutputStream(targetLocation);
+
+      // Copy the content from instream to outstream
+      buf = new byte[1024];
+      while ((len = in.read(buf)) > 0)
+	out.write(buf, 0, len);
+      
+      in.close();
+      out.close();
+      
+      if (move)
+	sourceLocation.delete();
+    }
+  }
+  
+  /**
+   * Copies the file/directory (recursively).
+   * 
+   * @param sourceLocation	the source file/dir
+   * @param targetLocation	the target file/dir
+   * @throws IOException	if copying fails
+   */
+  public static void copy(File sourceLocation, File targetLocation) throws IOException {
+    copyOrMove(sourceLocation, targetLocation, false);
+  }
+  
+  /**
+   * Moves the file/directory (recursively).
+   * 
+   * @param sourceLocation	the source file/dir
+   * @param targetLocation	the target file/dir
+   * @throws IOException	if moving fails
+   */
+  public static void move(File sourceLocation, File targetLocation) throws IOException {
+    copyOrMove(sourceLocation, targetLocation, true);
+  }
+  
+  /**
+   * Saves the content to a file.
+   * 
+   * @param file		the file to save to
+   * @param content		the content to save
+   * @return			true if successfully saved
+   */
+  public static boolean save(File file, String content) {
+    boolean		result;
+    BufferedWriter	writer;
+    
+    writer = null;
+    try {
+      writer = new BufferedWriter(new FileWriter(file));
+      writer.write(content);
+      writer.flush();
+      result = true;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = false;
+    }
+    finally {
+      if (writer != null) {
+	try {
+	  writer.close();
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+      }
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Tries to load the file and return its content.
+   * 
+   * @param file	the file to open
+   * @return		the content, otherwise null
+   */
+  public static String load(File file) {
+    StringBuffer	result;
+    BufferedReader	reader;
+    String		line;
+    String		newLine;
+    
+    result  = new StringBuffer();
+    newLine = System.getProperty("line.separator");
+    reader  = null;
+    try {
+      // add new content
+      reader = new BufferedReader(new FileReader(file));
+      while ((line = reader.readLine()) != null) {
+	result.append(line);
+	result.append(newLine);
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      result = null;
+    }
+    finally {
+      if (reader != null) {
+	try {
+	  reader.close();
+	}
+	catch (Exception e) {
+	  // ignored
+	}
+      }
+    }
+    
+    return ((result != null) ? result.toString() : null);
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptingPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptingPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/ScriptingPanel.java	(revision 29)
@@ -0,0 +1,281 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ScriptingPanel.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.core.Tee;
+import weka.gui.PropertyDialog;
+import weka.gui.ReaderToTextPane;
+import weka.gui.scripting.event.TitleUpdatedEvent;
+import weka.gui.scripting.event.TitleUpdatedListener;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+
+/**
+ * Abstract ancestor for scripting panels.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public abstract class ScriptingPanel
+  extends JPanel
+  implements TitleUpdatedListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 7593091442691911406L;
+
+  /** The new output stream for System.out. */
+  protected PipedOutputStream m_POO;
+
+  /** The new output stream for System.err. */
+  protected PipedOutputStream m_POE;
+
+  /** The thread that sends output from m_POO to the output box. */
+  protected ReaderToTextPane m_OutRedirector;
+
+  /** The thread that sends output from m_POE to the output box. */
+  protected ReaderToTextPane m_ErrRedirector;
+  
+  /** whether debug mode is on. */
+  protected boolean m_Debug;
+  
+  /** the listeners for the changes in the title. */
+  protected HashSet<TitleUpdatedListener> m_TitleUpdatedListeners;
+
+  /**
+   * Default constructor.
+   */
+  public ScriptingPanel() {
+    super();
+    
+    initialize();
+    initGUI();
+    initFinish();
+  }
+  
+  /**
+   * For initializing member variables.
+   */
+  protected void initialize() {
+    m_POO            = new PipedOutputStream();
+    m_POE            = new PipedOutputStream();
+    m_Debug          = false;
+    m_TitleUpdatedListeners = new HashSet<TitleUpdatedListener>();
+  }
+  
+  /**
+   * Sets up the GUI after initializing the members.
+   * The JTextArea returned via <code>getOutputArea()</code> must be setup here.
+   * 
+   * @see	#initialize()
+   * @see	#getOutput()
+   */
+  protected void initGUI() {
+  }
+  
+  /**
+   * Finishes up after initializing members and setting up the GUI.
+   * Redirects stdout and stderr using <code>getOutputArea()</code>.
+   * 
+   * @see	#initialize()
+   * @see	#initGUI()
+   * @see	#getOutput()
+   */
+  protected void initFinish() {
+    // Redirect System.out to the text area
+    try {
+      PipedInputStream pio = new PipedInputStream(m_POO);
+      Tee teeOut = new Tee(System.out);
+      System.setOut(teeOut);
+      teeOut.add(new PrintStream(m_POO));
+      Reader reader = new InputStreamReader(pio);
+      m_OutRedirector = new ReaderToTextPane(reader, getOutput(), Color.BLACK);
+      m_OutRedirector.start();
+    }
+    catch (Exception e) {
+      System.err.println("Error redirecting stdout");
+      e.printStackTrace();
+      m_OutRedirector = null;
+    }
+
+    // Redirect System.err to the text area
+    try {
+      PipedInputStream pie = new PipedInputStream(m_POE);
+      Tee teeErr = new Tee(System.err);
+      System.setErr(teeErr);
+      teeErr.add(new PrintStream(m_POE));
+      Reader reader = new InputStreamReader(pie);
+      m_ErrRedirector = new ReaderToTextPane(reader, getOutput(), Color.RED);
+      m_ErrRedirector.start();
+    }
+    catch (Exception e) {
+      System.err.println("Error redirecting stderr");
+      e.printStackTrace();
+      m_ErrRedirector = null;
+    }
+
+    addTitleUpdatedListener(this);
+  }
+  
+  /**
+   * Returns an icon to be used in a frame.
+   * 
+   * @return		the icon
+   */
+  public abstract ImageIcon getIcon();
+  
+  /**
+   * Returns the current title for the frame/dialog.
+   * 
+   * @return		the title
+   */
+  public abstract String getTitle();
+  
+  /**
+   * Returns the text area that is used for displaying output on stdout
+   * and stderr.
+   * 
+   * @return		the JTextArea
+   */
+  public abstract JTextPane getOutput();
+  
+  /**
+   * Returns the menu bar to to be displayed in the frame.
+   * 
+   * @return		the menu bar, null if not applicable
+   */
+  public abstract JMenuBar getMenuBar();
+  
+  /**
+   * Turns on/off debugging mode.
+   * 
+   * @param value	if true, debug mode is turned on
+   */
+  public void setDebug(boolean value) {
+    m_Debug = value;
+  }
+  
+  /**
+   * Returns whether debugging mode is on.
+   * 
+   * @return		true if debug mode is turned on
+   */
+  public boolean getDebug() {
+    return m_Debug;
+  }
+  
+  /**
+   * Adds the listener to the internal list.
+   * 
+   * @param l		the listener to add
+   */
+  public void addTitleUpdatedListener(TitleUpdatedListener l) {
+    m_TitleUpdatedListeners.add(l);
+  }
+  
+  /**
+   * Removes the listener from the internal list.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeTitleUpdatedListener(TitleUpdatedListener l) {
+    m_TitleUpdatedListeners.remove(l);
+  }
+  
+  /**
+   * Sends the event to all listeners for title updates.
+   * 
+   * @param e		the event to send
+   */
+  protected void notifyTitleUpdatedListeners(TitleUpdatedEvent e) {
+    Iterator<TitleUpdatedListener>	iter;
+    
+    iter = m_TitleUpdatedListeners.iterator();
+    while (iter.hasNext())
+      iter.next().titleUpdated(e);
+  }
+  
+  /**
+   * Gets called when the title of the frame/dialog needs updating.
+   * 
+   * @param event	the event that got sent
+   */
+  public void titleUpdated(TitleUpdatedEvent event) {
+    if (PropertyDialog.getParentDialog(ScriptingPanel.this) != null)
+      PropertyDialog.getParentDialog(ScriptingPanel.this).setTitle(getTitle());
+    else if (PropertyDialog.getParentFrame(ScriptingPanel.this) != null)
+      PropertyDialog.getParentFrame(ScriptingPanel.this).setTitle(getTitle());
+  }
+  
+  /**
+   * Displays the panel in a frame.
+   * 
+   * @param panel	the panel to display
+   * @param args	currently ignored commandline parameters
+   */
+  public static void showPanel(ScriptingPanel panel, String[] args) {
+    showPanel(panel, args, 800, 600);
+  }
+  
+  /**
+   * Displays the panel in a frame.
+   * 
+   * @param panel	the panel to display
+   * @param args	currently ignored commandline parameters
+   * @param width	the width of the frame
+   * @param height	the height of the frame
+   */
+  public static void showPanel(ScriptingPanel panel, String[] args, int width, int height) {
+    try {
+      JFrame frame = new JFrame();
+      frame.getContentPane().setLayout(new BorderLayout());
+      frame.getContentPane().add(panel, BorderLayout.CENTER);
+      frame.setJMenuBar(panel.getMenuBar());
+      frame.setSize(new Dimension(width, height));
+      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+      frame.setTitle(panel.getTitle());
+      frame.setIconImage(panel.getIcon().getImage());
+      frame.setLocationRelativeTo(null);
+      if ((args.length > 0) && (panel instanceof FileScriptingPanel))
+	((FileScriptingPanel) panel).open(new File(args[0]));
+      frame.setVisible(true);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/SyntaxDocument.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/SyntaxDocument.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/SyntaxDocument.java	(revision 29)
@@ -0,0 +1,1264 @@
+// Filename: SyntaxDocument.java
+// http://forums.sun.com/thread.jspa?threadID=743407&messageID=9497681
+// http://www.dound.com/src/MultiSyntaxDocument.java
+
+/**
+ * (C) camickr (primary author; java sun forums user)
+ * (C) David Underhill
+ * (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting;
+
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Toolkit;
+import java.util.HashMap;
+import java.util.Properties;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Element;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.TabSet;
+import javax.swing.text.TabStop;
+
+/**
+ * Highlights syntax in a DefaultStyledDocument. Allows any number of keywords
+ * to be formatted in any number of user-defined styles.
+ * 
+ * @author camickr (primary author; java sun forums user)
+ * @author David Underhill
+ * @author FracPete (fracpete at waikato dot ac dot nz) - use of a properties file to setup syntax highlighting instead of hard-coded
+ */
+public class SyntaxDocument
+  extends DefaultStyledDocument {
+
+  /** for serialization. */
+  protected static final long serialVersionUID = -3642426465631271381L;
+
+  /** the maximum number of tabs. */
+  public static final int MAX_TABS = 35;
+  
+  /** the font family. */
+  public static final String DEFAULT_FONT_FAMILY = "monospaced";
+
+  /** the font size. */
+  public static final int DEFAULT_FONT_SIZE = 12;
+
+  /** the attribute set for normal code. */
+  public static final SimpleAttributeSet DEFAULT_NORMAL;
+
+  /** the attribute set for comments. */
+  public static final SimpleAttributeSet DEFAULT_COMMENT;
+
+  /** the attribute set for strings. */
+  public static final SimpleAttributeSet DEFAULT_STRING;
+
+  /** the attribute set for keywords. */
+  public static final SimpleAttributeSet DEFAULT_KEYWORD;
+
+  static {
+    DEFAULT_NORMAL = new SimpleAttributeSet();
+    StyleConstants.setForeground(DEFAULT_NORMAL, Color.BLACK);
+    StyleConstants.setFontFamily(DEFAULT_NORMAL, DEFAULT_FONT_FAMILY);
+    StyleConstants.setFontSize(DEFAULT_NORMAL, DEFAULT_FONT_SIZE);
+
+    DEFAULT_COMMENT = new SimpleAttributeSet();
+    StyleConstants.setForeground(DEFAULT_COMMENT, Color.GRAY);
+    StyleConstants.setFontFamily(DEFAULT_COMMENT, DEFAULT_FONT_FAMILY);
+    StyleConstants.setFontSize(DEFAULT_COMMENT, DEFAULT_FONT_SIZE);
+
+    DEFAULT_STRING = new SimpleAttributeSet();
+    StyleConstants.setForeground(DEFAULT_STRING, Color.RED);
+    StyleConstants.setFontFamily(DEFAULT_STRING, DEFAULT_FONT_FAMILY);
+    StyleConstants.setFontSize(DEFAULT_STRING, DEFAULT_FONT_SIZE);
+
+    // default style for new keyword types
+    DEFAULT_KEYWORD = new SimpleAttributeSet();
+    StyleConstants.setForeground(DEFAULT_KEYWORD, Color.BLUE);
+    StyleConstants.setBold(DEFAULT_KEYWORD, false);
+    StyleConstants.setFontFamily(DEFAULT_KEYWORD, DEFAULT_FONT_FAMILY);
+    StyleConstants.setFontSize(DEFAULT_KEYWORD, DEFAULT_FONT_SIZE);
+  }
+
+  /**
+   * The attribute type.
+   */
+  public enum ATTR_TYPE {
+    /** normal string. */
+    Normal,
+    /** a comment. */
+    Comment,
+    /** a quoted string. */
+    Quote;
+  }
+
+  /** the document. */
+  protected DefaultStyledDocument m_Self;
+
+  /** the root element. */
+  protected Element m_RootElement;
+
+  /** whether we're currently in a multi-line comment. */
+  protected boolean m_InsideMultiLineComment;
+
+  /** the keywords. */
+  protected HashMap<String, MutableAttributeSet> m_Keywords;
+  
+  /** the delimiters. */
+  protected String m_Delimiters;
+  
+  /** the quote delimiter. */
+  protected String m_QuoteDelimiters;
+  
+  /** the quote escape. */
+  protected String m_QuoteEscape;
+  
+  /** the multi-line comment start. */
+  protected String m_MultiLineCommentStart;
+  
+  /** the multi-line comment end. */
+  protected String m_MultiLineCommentEnd;
+  
+  /** the single-line comment start. */
+  protected String m_SingleLineCommentStart;
+
+  /** the start of a block. */
+  protected String m_BlockStart;
+
+  /** the end of a block. */
+  protected String m_BlockEnd;
+  
+  /** the font size. */
+  protected int m_FontSize;
+
+  /** the font name. */
+  protected String m_FontName;
+  
+  /** the background color. */
+  protected Color m_BackgroundColor;
+  
+  /** the number of spaces used for indentation. */
+  protected String m_Indentation;
+
+  /** whether to add matching brackets. */
+  protected boolean m_AddMatchingEndBlocks;
+  
+  /** whether to use blanks instead of tabs. */
+  protected boolean m_UseBlanks;
+  
+  /** whether multi-line comments are enabled. */
+  protected boolean m_MultiLineComment;
+  
+  /** whether keywords are case-sensitive. */
+  protected boolean m_CaseSensitive;
+  
+  /**
+   * Initializes the document.
+   * 
+   * @param props	the properties to obtain the setup from
+   */
+  public SyntaxDocument(Properties props) {
+    m_Self        = this;
+    m_RootElement = m_Self.getDefaultRootElement();
+    m_Keywords    = new HashMap<String, MutableAttributeSet>();
+    m_FontSize    = DEFAULT_FONT_SIZE;
+    m_FontName    = DEFAULT_FONT_FAMILY;
+    putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
+    
+    setup(props);
+  }
+  
+  /**
+   * Sets up the document according to the properties.
+   * 
+   * @param props	the properties to use
+   */
+  protected void setup(Properties props) {
+    setDelimiters(props.getProperty("Delimiters", ";:{}()[]+-/%<=>!&|^~*"));
+    setQuoteDelimiters(props.getProperty("QuoteDelimiters", "\"\'"));
+    setQuoteEscape(props.getProperty("QuoteEscape", "\\"));
+    setSingleLineCommentStart(props.getProperty("SingleLineCommentStart", "//"));
+    setMultiLineComment(props.getProperty("MultiLineComment", "false").equals("true"));
+    setMultiLineCommentStart(props.getProperty("MultiLineCommentStart", "/*"));
+    setMultiLineCommentEnd(props.getProperty("MultiLineCommentEnd", "*/"));
+    setBlockStart(props.getProperty("BlockStart", "{"));
+    setBlockEnd(props.getProperty("BlockEnd", "}"));
+    setAddMatchingEndBlocks(props.getProperty("AddMatchingBlockEnd", "false").equals("true"));
+    setUseBlanks(props.getProperty("UseBlanks", "false").equals("true"));
+    setCaseSensitive(props.getProperty("CaseSensitive", "true").equals("true"));
+    addKeywords(props.getProperty("Keywords", "").trim().replaceAll(" ", "").split(","), DEFAULT_KEYWORD);
+    setTabs(Integer.parseInt(props.getProperty("Tabs", "2")));
+    setAttributeColor(DEFAULT_NORMAL, VisualizeUtils.processColour(props.getProperty("ForegroundColor", "black"), Color.BLACK));
+    setAttributeColor(DEFAULT_COMMENT, VisualizeUtils.processColour(props.getProperty("CommentColor", "gray"), Color.GRAY));
+    setAttributeColor(DEFAULT_STRING, VisualizeUtils.processColour(props.getProperty("StringColor", "red"), Color.RED));
+    setAttributeColor(DEFAULT_KEYWORD, VisualizeUtils.processColour(props.getProperty("KeywordColor", "blue"), Color.BLUE));
+    setBackgroundColor(VisualizeUtils.processColour(props.getProperty("BackgroundColor", "white"), Color.WHITE));
+    setFontName(props.getProperty("FontName", "monospaced"));
+    setFontSize(Integer.parseInt(props.getProperty("FontSize", "12")));
+    setIndentationSize(Integer.parseInt(props.getProperty("Indentation", "2")));
+  }
+
+  /**
+   * Sets the font of the specified attribute.
+   * 
+   * @param attr
+   *          the attribute to apply this font to (normal, comment, string)
+   * @param style
+   *          font style (Font.BOLD, Font.ITALIC, Font.PLAIN)
+   */
+  public void setAttributeFont(ATTR_TYPE attr, int style) {
+    Font f = new Font(m_FontName, style, m_FontSize);
+
+    if (attr == ATTR_TYPE.Comment)
+      setAttributeFont(DEFAULT_COMMENT, f);
+    else if (attr == ATTR_TYPE.Quote)
+      setAttributeFont(DEFAULT_STRING, f);
+    else
+      setAttributeFont(DEFAULT_NORMAL, f);
+  }
+
+  /**
+   * Sets the font of the specified attribute.
+   * 
+   * @param attr
+   *          attribute to apply this font to
+   * @param f
+   *          the font to use
+   */
+  public static void setAttributeFont(MutableAttributeSet attr, Font f) {
+    StyleConstants.setBold(attr, f.isBold());
+    StyleConstants.setItalic(attr, f.isItalic());
+    StyleConstants.setFontFamily(attr, f.getFamily());
+    StyleConstants.setFontSize(attr, f.getSize());
+  }
+
+  /**
+   * Sets the foreground (font) color of the specified attribute.
+   * 
+   * @param attr
+   *          the attribute to apply this font to (normal, comment, string)
+   * @param c
+   *          the color to use
+   */
+  public void setAttributeColor(ATTR_TYPE attr, Color c) {
+    if (attr == ATTR_TYPE.Comment)
+      setAttributeColor(DEFAULT_COMMENT, c);
+    else if (attr == ATTR_TYPE.Quote)
+      setAttributeColor(DEFAULT_STRING, c);
+    else
+      setAttributeColor(DEFAULT_NORMAL, c);
+  }
+
+  /**
+   * Sets the foreground (font) color of the specified attribute.
+   * 
+   * @param attr
+   *          attribute to apply this color to
+   * @param c
+   *          the color to use
+   */
+  public static void setAttributeColor(MutableAttributeSet attr, Color c) {
+    StyleConstants.setForeground(attr, c);
+  }
+
+  /**
+   * Associates the keywords with a particular formatting style.
+   * 
+   * @param keywords
+   *          the tokens or words to format
+   * @param attr
+   *          how to format the keywords
+   */
+  public void addKeywords(String[] keywords, MutableAttributeSet attr) {
+    int		i;
+    
+    for (i = 0; i < keywords.length; i++)
+      addKeyword(keywords[i], attr);
+  }
+  
+  /**
+   * Associates a keyword with a particular formatting style.
+   * 
+   * @param keyword
+   *          the token or word to format
+   * @param attr
+   *          how to format keyword
+   */
+  public void addKeyword(String keyword, MutableAttributeSet attr) {
+    if (m_CaseSensitive)
+      m_Keywords.put(keyword, attr);
+    else
+      m_Keywords.put(keyword.toLowerCase(), attr);
+  }
+
+  /**
+   * Gets the formatting for a keyword.
+   * 
+   * @param keyword
+   *          the token or word to stop formatting
+   * 
+   * @return how keyword is formatted, or null if no formatting is applied to it
+   */
+  public MutableAttributeSet getKeywordFormatting(String keyword) {
+    if (m_CaseSensitive)
+      return m_Keywords.get(keyword);
+    else
+      return m_Keywords.get(keyword.toLowerCase());
+  }
+
+  /**
+   * Removes an association between a keyword with a particular formatting style.
+   * 
+   * @param keyword
+   *          the token or word to stop formatting
+   */
+  public void removeKeyword(String keyword) {
+    if (m_CaseSensitive)
+      m_Keywords.remove(keyword);
+    else
+      m_Keywords.remove(keyword.toLowerCase());
+  }
+
+  /** 
+   * sets the number of characters per tab.
+   * 
+   * @param charactersPerTab
+   * 		the characters per tab
+   */
+  public void setTabs(int charactersPerTab) {
+    Font f = new Font(m_FontName, Font.PLAIN, m_FontSize);
+
+    FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f);
+    int charWidth = fm.charWidth('w');
+    int tabWidth = charWidth * charactersPerTab;
+
+    TabStop[] tabs = new TabStop[MAX_TABS];
+
+    for (int j = 0; j < tabs.length; j++)
+      tabs[j] = new TabStop((j+1) * tabWidth);
+
+    TabSet tabSet = new TabSet(tabs);
+    SimpleAttributeSet attributes = new SimpleAttributeSet();
+    StyleConstants.setTabSet(attributes, tabSet);
+    int length = getLength();
+    setParagraphAttributes(0, length, attributes, false);
+  }
+
+  /**
+   * Override to apply syntax highlighting after the document has been updated.
+   * 
+   * @param offset
+   * 		the offset
+   * @param str
+   * 		the string to insert
+   * @param a
+   * 		the attribute set, can be null
+   * @throws BadLocationException
+   * 		if offset is invalid
+   */
+  public void insertString(int offset, String str, AttributeSet a)
+      throws BadLocationException {
+    if (m_AddMatchingEndBlocks && (m_BlockStart.length() > 0) && str.equals(m_BlockStart))
+      str = addMatchingBlockEnd(offset);
+    else if (m_UseBlanks && str.equals("\t"))
+      str = m_Indentation;
+    
+    super.insertString(offset, str, a);
+    processChangedLines(offset, str.length());
+  }
+
+  /**
+   * Applies syntax highlighting after the document has been updated.
+   * 
+   * @param offset
+   * 		the offset of the deletion
+   * @param length
+   * 		the length of the deletion
+   * @throws BadLocationException
+   * 		if offsets are invalid
+   */
+  public void remove(int offset, int length) throws BadLocationException {
+    super.remove(offset, length);
+    processChangedLines(offset, 0);
+  }
+
+  /**
+   * Determine how many lines have been changed, then apply highlighting to each
+   * line.
+   * 
+   * @param offset
+   * 		the offset of the changed lines
+   * @param length
+   * 		the length of the change
+   * @throws BadLocationException
+   * 		if offset is invalid
+   */
+  public void processChangedLines(int offset, int length)
+      throws BadLocationException {
+    String content = m_Self.getText(0, m_Self.getLength());
+
+    // The lines affected by the latest document update
+
+    int startLine = m_RootElement.getElementIndex(offset);
+    int endLine = m_RootElement.getElementIndex(offset + length);
+
+    // Make sure all comment lines prior to the start line are commented
+    // and determine if the start line is still in a multi line comment
+
+    if (getMultiLineComment())
+      setInsideMultiLineComment(commentLinesBefore(content, startLine));
+
+    // Do the actual highlighting
+
+    for (int i = startLine; i <= endLine; i++) {
+      applyHighlighting(content, i);
+    }
+
+    // Resolve highlighting to the next end multi line delimiter
+
+    if (isMultiLineComment())
+      commentLinesAfter(content, endLine);
+    else
+      highlightLinesAfter(content, endLine);
+  }
+
+  /**
+   * Highlight lines when a multi line comment is still 'open' (ie. matching end
+   * delimiter has not yet been encountered).
+   * 
+   * @param content
+   * 		the content to check
+   * @param line
+   * 		the line number
+   * @return
+   * 		true if there are comment lines before
+   */
+  protected boolean commentLinesBefore(String content, int line) {
+    int offset = m_RootElement.getElement(line).getStartOffset();
+
+    // Start of comment not found, nothing to do
+
+    int startDelimiter = -1;
+    if (getMultiLineComment())
+      startDelimiter = lastIndexOf(content, getMultiLineCommentStart(), offset - 2);
+
+    if (startDelimiter < 0)
+      return false;
+
+    // Matching start/end of comment found, nothing to do
+
+    int endDelimiter = indexOf(content, getMultiLineCommentEnd(), startDelimiter);
+
+    if (endDelimiter < offset & endDelimiter != -1)
+      return false;
+
+    // End of comment not found, highlight the lines
+
+    m_Self.setCharacterAttributes(startDelimiter, offset - startDelimiter + 1,
+	DEFAULT_COMMENT, false);
+    return true;
+  }
+
+  /**
+   * Highlight comment lines to matching end delimiter.
+   * 
+   * @param content
+   * 		the content to parse
+   * @param line
+   * 		the line number
+   */
+  protected void commentLinesAfter(String content, int line) {
+    int offset = m_RootElement.getElement(line).getEndOffset();
+
+    // End of comment not found, nothing to do
+
+    int endDelimiter = -1;
+    if (getMultiLineComment())
+      endDelimiter = indexOf(content, getMultiLineCommentEnd(), offset);
+
+    if (endDelimiter < 0)
+      return;
+
+    // Matching start/end of comment found, comment the lines
+
+    int startDelimiter = lastIndexOf(content, getMultiLineCommentStart(), endDelimiter);
+
+    if (startDelimiter < 0 || startDelimiter <= offset) {
+      m_Self.setCharacterAttributes(offset, endDelimiter - offset + 1, DEFAULT_COMMENT,
+	  false);
+    }
+  }
+
+  /**
+   * Highlight lines to start or end delimiter.
+   * 
+   * @param content
+   * 		the content to parse
+   * @param line
+   * 		the line number
+   * @throws BadLocationException
+   * 		if offsets are wrong
+   */
+  protected void highlightLinesAfter(String content, int line)
+      throws BadLocationException {
+    int offset = m_RootElement.getElement(line).getEndOffset();
+
+    // Start/End delimiter not found, nothing to do
+
+    int startDelimiter = -1;
+    int endDelimiter = -1;
+    if (getMultiLineComment()) {
+      startDelimiter = indexOf(content, getMultiLineCommentStart(), offset);
+      endDelimiter = indexOf(content, getMultiLineCommentEnd(), offset);
+    }
+
+    if (startDelimiter < 0)
+      startDelimiter = content.length();
+
+    if (endDelimiter < 0)
+      endDelimiter = content.length();
+
+    int delimiter = Math.min(startDelimiter, endDelimiter);
+
+    if (delimiter < offset)
+      return;
+
+    // Start/End delimiter found, reapply highlighting
+
+    int endLine = m_RootElement.getElementIndex(delimiter);
+
+    for (int i = line + 1; i < endLine; i++) {
+      Element branch = m_RootElement.getElement(i);
+      Element leaf = m_Self.getCharacterElement(branch.getStartOffset());
+      AttributeSet as = leaf.getAttributes();
+
+      if (as.isEqual(DEFAULT_COMMENT))
+	applyHighlighting(content, i);
+    }
+  }
+
+  /**
+   * Parse the line to determine the appropriate highlighting.
+   * 
+   * @param content
+   * 		the content to parse
+   * @param line
+   * 		the line number
+   * @throws BadLocationException
+   * 		if offsets are invalid
+   */
+  protected void applyHighlighting(String content, int line)
+      throws BadLocationException {
+    int startOffset = m_RootElement.getElement(line).getStartOffset();
+    int endOffset = m_RootElement.getElement(line).getEndOffset() - 1;
+
+    int lineLength = endOffset - startOffset;
+    int contentLength = content.length();
+
+    if (endOffset >= contentLength)
+      endOffset = contentLength - 1;
+
+    // check for multi line comments
+    // (always set the comment attribute for the entire line)
+
+    if (getMultiLineComment()) {
+      if (endingMultiLineComment(content, startOffset, endOffset)
+	  || isMultiLineComment()
+	  || startingMultiLineComment(content, startOffset, endOffset)) {
+	m_Self.setCharacterAttributes(startOffset, endOffset - startOffset + 1,
+	    DEFAULT_COMMENT, false);
+	return;
+      }
+    }
+
+    // set normal attributes for the line
+
+    m_Self.setCharacterAttributes(startOffset, lineLength, DEFAULT_NORMAL, true);
+
+    // check for single line comment
+
+    int index = content.indexOf(getSingleLineCommentStart(), startOffset);
+
+    if ((index > -1) && (index < endOffset)) {
+      m_Self.setCharacterAttributes(index, endOffset - index + 1, DEFAULT_COMMENT, false);
+      endOffset = index - 1;
+    }
+
+    // check for tokens
+
+    checkForTokens(content, startOffset, endOffset);
+  }
+
+  /**
+   * Does this line contain the start of a multi-line comment.
+   * 
+   * @param content
+   * 		the content to search
+   * @param startOffset
+   * 		the start of the search
+   * @param endOffset
+   * 		the end of the search
+   * @return
+   * 		true if it contains the start delimiter
+   * @throws BadLocationException
+   * 		if offsets are invalid
+   */
+  protected boolean startingMultiLineComment(String content, int startOffset,
+      int endOffset) throws BadLocationException {
+    if (!getMultiLineComment())
+      return false;
+    
+    int index = indexOf(content, getMultiLineCommentStart(), startOffset);
+
+    if ((index < 0) || (index > endOffset))
+      return false;
+    else {
+      setInsideMultiLineComment(true);
+      return true;
+    }
+  }
+
+  /**
+   * Does this line contain the end delimiter of a multi-line comment.
+   * 
+   * @param content
+   * 		the content to search
+   * @param startOffset
+   * 		the start of the search
+   * @param endOffset
+   * 		the end of the search
+   * @return
+   * 		true if the line contains the end delimiter
+   * @throws BadLocationException
+   * 		if offsets are invalid
+   */
+  protected boolean endingMultiLineComment(String content, int startOffset,
+      int endOffset) throws BadLocationException {
+    if (!getMultiLineComment())
+      return false;
+    
+    int index = indexOf(content, getMultiLineCommentEnd(), startOffset);
+
+    if ((index < 0) || (index > endOffset))
+      return false;
+    else {
+      setInsideMultiLineComment(false);
+      return true;
+    }
+  }
+
+  /**
+   * We have found a start delimiter and are still searching for the end
+   * delimiter.
+   * 
+   * @return
+   * 		true if currently within a multi-line comment
+   */
+  protected boolean isMultiLineComment() {
+    return m_InsideMultiLineComment;
+  }
+
+  /**
+   * Sets whether we're currently within a multi-line comment or not.
+   * 
+   * @param value
+   * 		true if currently within a multi-line comment
+   */
+  protected void setInsideMultiLineComment(boolean value) {
+    m_InsideMultiLineComment = value;
+  }
+
+  /**
+   * Parse the line for tokens to highlight.
+   * 
+   * @param content
+   * 		the content to parse
+   * @param startOffset
+   * 		the start position
+   * @param endOffset
+   * 		the end position
+   */
+  protected void checkForTokens(String content, int startOffset, int endOffset) {
+    while (startOffset <= endOffset) {
+      // skip the delimiters to find the start of a new token
+
+      while (isDelimiter(content.substring(startOffset, startOffset + 1))) {
+	if (startOffset < endOffset)
+	  startOffset++;
+	else
+	  return;
+      }
+
+      // Extract and process the entire token
+
+      if (isQuoteDelimiter(content.substring(startOffset, startOffset + 1)))
+	startOffset = getQuoteToken(content, startOffset, endOffset);
+      else
+	startOffset = getOtherToken(content, startOffset, endOffset);
+    }
+  }
+
+  /**
+   * Searches for a quote token.
+   *
+   * @param content
+   * 		the content to search
+   * @param startOffset
+   * 		the start of the search
+   * @param endOffset
+   * 		the end of the search
+   * @return
+   * 		the new position
+   */
+  protected int getQuoteToken(String content, int startOffset, int endOffset) {
+    String quoteDelimiter = content.substring(startOffset, startOffset + 1);
+    String escapeString = escapeQuote(quoteDelimiter);
+
+    int index;
+    int endOfQuote = startOffset;
+
+    // skip over the escape quotes in this quote
+
+    index = content.indexOf(escapeString, endOfQuote + 1);
+
+    while ((index > -1) && (index < endOffset)) {
+      endOfQuote = index + 1;
+      index = content.indexOf(escapeString, endOfQuote);
+    }
+
+    // now find the matching delimiter
+
+    index = content.indexOf(quoteDelimiter, endOfQuote + 1);
+
+    if ((index < 0) || (index > endOffset))
+      endOfQuote = endOffset;
+    else
+      endOfQuote = index;
+
+    m_Self.setCharacterAttributes(startOffset, endOfQuote - startOffset + 1,
+	DEFAULT_STRING, false);
+
+    return endOfQuote + 1;
+  }
+
+  /**
+   * Searches for a keyword token.
+   *
+   * @param content
+   * 		the content to search in
+   * @param startOffset
+   * 		the position to start the search fromm
+   * @param endOffset
+   * 		the position to end the search
+   * @return
+   * 		the new position
+   */
+  protected int getOtherToken(String content, int startOffset, int endOffset) {
+    int endOfToken = startOffset + 1;
+
+    while (endOfToken <= endOffset) {
+      if (isDelimiter(content.substring(endOfToken, endOfToken + 1)))
+	break;
+      endOfToken++;
+    }
+
+    String token = content.substring(startOffset, endOfToken);
+
+    // see if this token has a highlighting format associated with it
+    MutableAttributeSet attr = getKeywordFormatting(token);
+    if (attr != null)
+      m_Self.setCharacterAttributes(startOffset, endOfToken - startOffset, attr, false);
+
+    return endOfToken + 1;
+  }
+
+  /**
+   * Assume the needle will the found at the start/end of the line.
+   * 
+   * @param content
+   * 		the content to search
+   * @param needle
+   * 		the string to look for
+   * @param offset
+   * 		the offset to start at
+   * @return
+   * 		the index
+   */
+  protected int indexOf(String content, String needle, int offset) {
+    int index;
+
+    while ((index = content.indexOf(needle, offset)) != -1) {
+      String text = getLine(content, index).trim();
+
+      if (text.startsWith(needle) || text.endsWith(needle))
+	break;
+      else
+	offset = index + 1;
+    }
+
+    return index;
+  }
+
+  /**
+   * Assume the needle will the found at the start/end of the line.
+   * 
+   * @param content
+   * 		the content search
+   * @param needle
+   * 		what to look for
+   * @param offset
+   * 		the offset to start
+   * @return
+   * 		the index
+   */
+  protected int lastIndexOf(String content, String needle, int offset) {
+    int index;
+
+    while ((index = content.lastIndexOf(needle, offset)) != -1) {
+      String text = getLine(content, index).trim();
+
+      if (text.startsWith(needle) || text.endsWith(needle))
+	break;
+      else
+	offset = index - 1;
+    }
+
+    return index;
+  }
+
+  /**
+   * Returns the line.
+   * 
+   * @param content
+   * 		the content
+   * @param offset
+   * 		the offset to start at
+   * @return
+   * 		the line
+   */
+  protected String getLine(String content, int offset) {
+    int line = m_RootElement.getElementIndex(offset);
+    Element lineElement = m_RootElement.getElement(line);
+    int start = lineElement.getStartOffset();
+    int end = lineElement.getEndOffset();
+    return content.substring(start, end - 1);
+  }
+
+  /**
+   * Checks whether the character is a delimiter.
+   * 
+   * @param character
+   * 		the character to check
+   * @return
+   * 		true if a delimiter
+   */
+  public boolean isDelimiter(String character) {
+    return Character.isWhitespace(character.charAt(0)) || (m_Delimiters.indexOf(character.charAt(0)) > -1);
+  }
+
+  /**
+   * Checks whether the character is quote delimiter.
+   * 
+   * @param character
+   * 		the character to check
+   * @return
+   * 		true if a quote delimiter
+   */
+  public boolean isQuoteDelimiter(String character) {
+    return (m_QuoteDelimiters.indexOf(character.charAt(0)) > -1);
+  }
+
+  /**
+   * Escapes the quote delimiter.
+   * 
+   * @param quoteDelimiter
+   * 		the string to escape
+   * @return
+   * 		the escaped string
+   */
+  public String escapeQuote(String quoteDelimiter) {
+    return m_QuoteEscape + quoteDelimiter;
+  }
+
+  /**
+   * Adds the matching block end.
+   * 
+   * @param offset
+   * 		the offset
+   * @return
+   * 		the string after adding the matching block end
+   * @throws BadLocationException
+   * 		if the offset is invalid
+   */
+  protected String addMatchingBlockEnd(int offset) throws BadLocationException {
+    StringBuffer result;
+    StringBuffer whiteSpace = new StringBuffer();
+    int line = m_RootElement.getElementIndex(offset);
+    int i = m_RootElement.getElement(line).getStartOffset();
+
+    while (true) {
+      String temp = m_Self.getText(i, 1);
+
+      if (temp.equals(" ") || temp.equals("\t")) {
+	whiteSpace.append(temp);
+	i++;
+      }
+      else {
+	break;
+      }
+    }
+
+    // assemble string
+    result = new StringBuffer();
+    result.append(m_BlockStart);
+    result.append("\n");
+    result.append(whiteSpace.toString());
+    if (m_UseBlanks)
+      result.append(m_Indentation);
+    else
+      result.append("\t");
+    result.append("\n");
+    result.append(whiteSpace.toString());
+    result.append(m_BlockEnd);
+    
+    return result.toString();
+  }
+
+  /** 
+   * gets the current font size.
+   * 
+   * @return
+   * 		the font size
+   */
+  public int getFontSize() {
+    return m_FontSize;
+  }
+
+  /** 
+   * sets the current font size (affects all built-in styles).
+   * 
+   * @param fontSize
+   * 		the size
+   */
+  public void setFontSize(int fontSize) {
+    m_FontSize = fontSize;
+    StyleConstants.setFontSize(DEFAULT_NORMAL, fontSize);
+    StyleConstants.setFontSize(DEFAULT_STRING, fontSize);
+    StyleConstants.setFontSize(DEFAULT_COMMENT, fontSize);
+  }
+
+  /**
+   * gets the current font family.
+   * 
+   * @return
+   * 		the font name
+   */
+  public String getFontName() {
+    return m_FontName;
+  }
+
+  /**
+   * sets the current font family (affects all built-in styles).
+   * 
+   * @param fontName
+   * 		the font name
+   */
+  public void setFontName(String fontName) {
+    m_FontName = fontName;
+    StyleConstants.setFontFamily(DEFAULT_NORMAL, fontName);
+    StyleConstants.setFontFamily(DEFAULT_STRING, fontName);
+    StyleConstants.setFontFamily(DEFAULT_COMMENT, fontName);
+  }
+  
+  /**
+   * Sets the number of blanks to use for indentation.
+   * 
+   * @param value	
+   * 		the number of blanks
+   */
+  public void setIndentationSize(int value) {
+    int		i;
+    
+    m_Indentation = "";
+    for (i = 0; i < value; i++)
+      m_Indentation += " ";
+  }
+  
+  /**
+   * Returns the number of blanks used for indentation.
+   * 
+   * @return		
+   * 		the number of blanks
+   */
+  public int getIndentationSize() {
+    return m_Indentation.length();
+  }
+  
+  /**
+   * Sets the delimiter characters to use.
+   * 
+   * @param value	
+   * 		the characters
+   */
+  public void setDelimiters(String value) {
+    m_Delimiters = value;
+  }
+  
+  /**
+   * Returns the delimiter characters to use.
+   * 
+   * @return		
+   * 		the characters
+   */
+  public String getDelimiters() {
+    return m_Delimiters;
+  }
+  
+  /**
+   * Sets the quote delimiter characters to use.
+   * 
+   * @param value	
+   * 		the characters
+   */
+  public void setQuoteDelimiters(String value) {
+    m_QuoteDelimiters = value;
+  }
+  
+  /**
+   * Returns the quote delimiter characters to use.
+   * 
+   * @return		
+   * 		the characters
+   */
+  public String getQuoteDelimiters() {
+    return m_QuoteDelimiters;
+  }
+  
+  /**
+   * Sets the character to use for escaping a quote character.
+   * 
+   * @param value	
+   * 		the character
+   */
+  public void setQuoteEscape(String value) {
+    m_QuoteEscape = value;
+  }
+  
+  /**
+   * Returns the character for escaping a quote delimiter.
+   * 
+   * @return		
+   * 		the character
+   */
+  public String getQuoteEscape() {
+    return m_QuoteEscape;
+  }
+  
+  /**
+   * Sets the string that is the start of a single-line comment.
+   * 
+   * @param value	
+   * 		the string
+   */
+  public void setSingleLineCommentStart(String value) {
+    m_SingleLineCommentStart = value;
+  }
+
+  /**
+   * Retrusn the single line comment start string.
+   * 
+   * @return
+   * 		the start string
+   */
+  public String getSingleLineCommentStart() {
+    return m_SingleLineCommentStart;
+  }
+  
+  /**
+   * Sets the string that is the start of a multi-line comment.
+   * 
+   * @param value	
+   * 		the string
+   */
+  public void setMultiLineCommentStart(String value) {
+    m_MultiLineCommentStart = value;
+  }
+  
+  /**
+   * Returns the string that is the start of a multi-line comment.
+   * 
+   * @return		
+   * 		the string
+   */
+  public String getMultiLineCommentStart() {
+    return m_MultiLineCommentStart;
+  }
+  
+  /**
+   * Sets the string that is the end of a multi-line comment.
+   * 
+   * @param value	the string
+   */
+  public void setMultiLineCommentEnd(String value) {
+    m_MultiLineCommentEnd = value;
+  }
+
+  /**
+   * Returns the end of a multi-line comment.
+   * 
+   * @return
+   * 		the end string
+   */
+  public String getMultiLineCommentEnd() {
+    return m_MultiLineCommentEnd;
+  }
+  
+  /**
+   * Sets the string that is the start of a block.
+   * 
+   * @param value
+   * 		the string
+   */
+  public void setBlockStart(String value) {
+    m_BlockStart = value;
+  }
+
+  /**
+   * Returns the start of a block.
+   * 
+   * @return
+   * 		the end string
+   */
+  public String getBlockStart() {
+    return m_BlockStart;
+  }
+  
+  /**
+   * Sets the string that is the end of a block.
+   * 
+   * @param value	
+   * 		the string
+   */
+  public void setBlockEnd(String value) {
+    m_BlockEnd = value;
+  }
+
+  /**
+   * Returns the end of a block.
+   * 
+   * @return
+   * 		the end string
+   */
+  public String getBlockEnd() {
+    return m_BlockEnd;
+  }
+  
+  /**
+   * Sets whether matching block ends are inserted or not.
+   * 
+   * @param value	
+   * 		if true then matching block ends are inserted
+   */
+  public void setAddMatchingEndBlocks(boolean value) {
+    m_AddMatchingEndBlocks = value;
+  }
+
+  /**
+   * Returns whether matching block ends are inserted or not.
+   * 
+   * @return
+   * 		true if matching block ends are inserted
+   */
+  public boolean getAddMatchingEndBlocks() {
+    return m_AddMatchingEndBlocks;
+  }
+  
+  /**
+   * Sets whether to use blanks instead of tabs.
+   * 
+   * @param value	
+   * 		if true then blanks are used instead of tabs
+   */
+  public void setUseBlanks(boolean value) {
+    m_UseBlanks = value;
+  }
+
+  /**
+   * Returns whether blanks are used instead of tabs.
+   * 
+   * @return
+   * 		true if blanks are used instead of tabs
+   */
+  public boolean getUseBlanks() {
+    return m_UseBlanks;
+  }
+  
+  /**
+   * Sets the background color.
+   * 
+   * @param value	
+   * 		the background color
+   */
+  public void setBackgroundColor(Color value) {
+    m_BackgroundColor = value;
+  }
+  
+  /**
+   * Returns the background color.
+   * 
+   * @return
+   * 		the background color
+   */
+  public Color getBackgroundColor() {
+    return m_BackgroundColor;
+  }
+  
+  /**
+   * Sets whether to enable multi-line comments.
+   * 
+   * @param value	
+   * 		if true then multi-line comments are enabled
+   */
+  public void setMultiLineComment(boolean value) {
+    m_MultiLineComment = value;
+  }
+
+  /**
+   * Returns whether multi-line comments are enabled.
+   * 
+   * @return
+   * 		true if multi-line comments are enabled
+   */
+  public boolean getMultiLineComment() {
+    return m_MultiLineComment;
+  }
+  
+  /**
+   * Sets whether the keywords are case-sensitive or not.
+   * 
+   * @param value	
+   * 		if true then keywords are treated case-sensitive
+   */
+  public void setCaseSensitive(boolean value) {
+    m_CaseSensitive = value;
+  }
+
+  /**
+   * Returns whether blanks are used instead of tabs.
+   * 
+   * @return
+   * 		true if keywords are case-sensitive
+   */
+  public boolean getCaseSensitive() {
+    return m_CaseSensitive;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionEvent.java	(revision 29)
@@ -0,0 +1,123 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ScriptExecutionEvent.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting.event;
+
+import weka.gui.scripting.Script;
+
+import java.util.EventObject;
+
+/**
+ * Event that gets sent when a script is executed.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class ScriptExecutionEvent
+  extends EventObject {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -8357216611114356632L;
+
+  /**
+   * Defines the type of event.
+   * 
+   * @author  fracpete (fracpete at waikato dot ac dot nz)
+   * @version $Revision: 5142 $
+   */
+  public enum Type {
+    /** started execution. */
+    STARTED,
+    /** finished normal. */
+    FINISHED,
+    /** finished with error. */
+    ERROR,
+    /** got stopped by user. */
+    STOPPED
+  }
+  
+  /** the type of event. */
+  protected Type m_Type;
+  
+  /** optional additional information. */
+  protected Object m_Additional;
+  
+  /**
+   * Initializes the event.
+   * 
+   * @param source	the script that triggered the event
+   * @param type	the type of finish
+   */
+  public ScriptExecutionEvent(Script source, Type type) {
+    this(source, type, null);
+  }
+  
+  /**
+   * Initializes the event.
+   * 
+   * @param source	the script that triggered the event
+   * @param type	the type of finish
+   * @param additional	additional information, can be null
+   */
+  public ScriptExecutionEvent(Script source, Type type, Object additional) {
+    super(source);
+    
+    m_Type       = type;
+    m_Additional = additional;
+  }
+  
+  /**
+   * Returns the script that triggered the event.
+   * 
+   * @return		the script
+   */
+  public Script getScript() {
+    return (Script) getSource();
+  }
+  
+  /**
+   * Returns the type of event.
+   * 
+   * @return		the type
+   */
+  public Type getType() {
+    return m_Type;
+  }
+  
+  /**
+   * Returns whether additional information is available.
+   * 
+   * @return		true if additional information is available
+   * @see		#getAdditional()
+   */
+  public boolean hasAdditional() {
+    return (m_Additional != null);
+  }
+  
+  /**
+   * Returns the additional information.
+   * 
+   * @return		the additional information, can be null
+   */
+  public Object getAdditional() {
+    return m_Additional;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/event/ScriptExecutionListener.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ScriptExecutionListener.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting.event;
+
+/**
+ * For classes that want to be notified about changes in the script execution.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public interface ScriptExecutionListener {
+  
+  /**
+   * Gets sent when a script execution changes.
+   * 
+   * @param e		the event
+   */
+  public void scriptFinished(ScriptExecutionEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedEvent.java	(revision 29)
@@ -0,0 +1,58 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TitleUpdatedEvent.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting.event;
+
+import weka.gui.scripting.ScriptingPanel;
+
+import java.util.EventObject;
+
+/**
+ * Event that gets send in case a scripting panel updates the title.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public class TitleUpdatedEvent
+  extends EventObject {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4971569742479666535L;
+
+  /**
+   * Initializes the event.
+   * 
+   * @param source	the scripting panel that triggered the event
+   */
+  public TitleUpdatedEvent(ScriptingPanel source) {
+    super(source);
+  }
+  
+  /**
+   * Returns the scripting panel that triggered the event. Use the
+   * <code>getTitle()</code> method for accessing the new title.
+   * 
+   * @return		the panel
+   */
+  public ScriptingPanel getPanel() {
+    return (ScriptingPanel) getSource();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/scripting/event/TitleUpdatedListener.java	(revision 29)
@@ -0,0 +1,38 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TitleUpdatedListener.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.scripting.event;
+
+/**
+ * Interface for frames/dialogs that listen to changes of the title.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5142 $
+ */
+public interface TitleUpdatedListener {
+  
+  /**
+   * Gets called when the title of the frame/dialog needs updating.
+   * 
+   * @param event	the event that got sent
+   */
+  public void titleUpdated(TitleUpdatedEvent event);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ConnectionPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ConnectionPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ConnectionPanel.java	(revision 29)
@@ -0,0 +1,481 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ConnectionPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.sql;
+
+import weka.gui.DatabaseConnectionDialog;
+import weka.gui.ListSelectorDialog;
+import weka.gui.sql.event.ConnectionEvent;
+import weka.gui.sql.event.ConnectionListener;
+import weka.gui.sql.event.HistoryChangedEvent;
+import weka.gui.sql.event.HistoryChangedListener;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.CaretListener;
+import javax.swing.event.CaretEvent;
+
+/**
+ * Enables the user to insert a database URL, plus user/password to connect
+ * to this database.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.3 $
+ */
+public class ConnectionPanel 
+  extends JPanel 
+  implements CaretListener {
+
+  /** for serialization. */
+  static final long serialVersionUID = 3499317023969723490L;
+  
+  /** the name of the history. */
+  public final static String HISTORY_NAME = "connection";
+  
+  /** the parent frame. */
+  protected JFrame m_Parent = null;
+  
+  /** the databae connection dialog. */
+  protected DatabaseConnectionDialog m_DbDialog;
+
+  /** the URL to use. */
+  protected String m_URL = "";
+
+  /** the user to use for connecting to the DB. */
+  protected String m_User = "";
+
+  /** the password to use for connecting to the DB. */
+  protected String m_Password = "";
+
+  /** the label for the URL. */
+  protected JLabel m_LabelURL = new JLabel("URL ");
+
+  /** the textfield for the URL. */
+  protected JTextField m_TextURL = new JTextField(40);
+
+  /** the button for the DB-Dialog. */
+  protected JButton m_ButtonDatabase = new JButton("User...");
+
+  /** the button for connecting to the database. */
+  protected JButton m_ButtonConnect = new JButton("Connect");
+
+  /** the button for the history. */
+  protected JButton m_ButtonHistory = new JButton("History...");
+
+  /** the connection listeners. */
+  protected HashSet m_ConnectionListeners;
+
+  /** the history listeners. */
+  protected HashSet m_HistoryChangedListeners;
+
+  /** for connecting to the database. */
+  protected DbUtils m_DbUtils;
+
+  /** the history of connections. */
+  protected DefaultListModel m_History = new DefaultListModel();
+
+  /**
+   * initializes the panel.
+   * 
+   * @param parent      the parent of this panel
+   */
+  public ConnectionPanel(JFrame parent) {
+    super();
+    
+    m_Parent                  = parent;
+    m_ConnectionListeners     = new HashSet();
+    m_HistoryChangedListeners = new HashSet();
+    
+    try {
+      m_DbUtils   = new DbUtils();
+      m_URL       = m_DbUtils.getDatabaseURL();
+      m_User      = m_DbUtils.getUsername();
+      m_Password  = m_DbUtils.getPassword();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      m_URL      = "";
+      m_User     = "";
+      m_Password = "";
+    }
+    
+    createPanel();
+  }
+
+  /**
+   * builds the panel with all its components.
+   */
+  protected void createPanel() {
+    JPanel        panel;
+    JPanel        panel2;
+    
+    setLayout(new BorderLayout());
+    panel2 = new JPanel(new FlowLayout());
+    add(panel2, BorderLayout.WEST);
+
+    // label
+    m_LabelURL.setLabelFor(m_ButtonDatabase);
+    m_LabelURL.setDisplayedMnemonic('U');
+    panel2.add(m_LabelURL);
+
+    // editfield
+    m_TextURL.setText(m_URL);
+    m_TextURL.addCaretListener(this);
+    panel2.add(m_TextURL);
+    
+    // buttons
+    panel = new JPanel(new FlowLayout());
+    panel2.add(panel);
+    
+    m_ButtonDatabase.setMnemonic('s');
+    m_ButtonDatabase.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  showDialog();
+	}
+      });
+    panel.add(m_ButtonDatabase);
+    
+    m_ButtonConnect.setMnemonic('n');
+    m_ButtonConnect.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  connect();
+	}
+      });
+    panel.add(m_ButtonConnect);
+
+    m_ButtonHistory.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  showHistory();
+	}
+      });
+    panel.add(m_ButtonHistory);
+
+    setButtons();
+  }
+
+  /**
+   * sets the buttons according to the connected-state.
+   */
+  protected void setButtons() {
+    boolean isEmpty;
+    
+    isEmpty = m_TextURL.getText().equals("");
+    
+    m_ButtonConnect.setEnabled(!isEmpty);
+    m_ButtonDatabase.setEnabled(!isEmpty);
+    m_ButtonHistory.setEnabled(m_History.size() > 0);
+  }
+
+  /**
+   * sets the parameters back to standard.
+   */
+  public void clear() {
+    setURL(m_DbUtils.getDatabaseURL());
+    setUser(m_DbUtils.getUsername());
+    setPassword(m_DbUtils.getPassword());
+  }
+  
+  /**
+   * sets the focus in a designated control.
+   */
+  public void setFocus() {
+    m_TextURL.requestFocus();
+  }
+
+  /**
+   * sets the URL.
+   * 
+   * @param url       the new value of the URL
+   */
+  public void setURL(String url) {
+    m_URL = url;
+    m_TextURL.setText(url);
+  }
+
+  /**
+   * returns the current URL.
+   * 
+   * @return the current URL
+   */
+  public String getURL() {
+    m_URL = m_TextURL.getText();
+    return m_URL;
+  }
+
+  /**
+   * sets the User.
+   * 
+   * @param user       the new value of the User
+   */
+  public void setUser(String user) {
+    m_User = user;
+  }
+
+  /**
+   * returns the current User.
+   * 
+   * @return the current user
+   */
+  public String getUser() {
+    return m_User;
+  }
+
+  /**
+   * sets the Password.
+   * 
+   * @param pw       the new value of the Password
+   */
+  public void setPassword(String pw) {
+    m_Password = pw;
+  }
+
+  /**
+   * returns the current Password.
+   * 
+   * @return the current password
+   */
+  public String getPassword() {
+    return m_Password;
+  }
+
+  /**
+   * adds the given string to the history (removes duplicates).
+   * 
+   * @param s           the string to add
+   */
+  protected void addHistory(String s) {
+    if (s.equals(""))
+      return;
+    
+    // no duplicates!
+    if (m_History.contains(s))
+      m_History.removeElement(s);
+
+    m_History.add(0, s);
+    
+    // send notification
+    notifyHistoryChangedListeners();
+  }
+
+  /**
+   * sets the local history to the given one.
+   * 
+   * @param history     the history to use
+   */
+  public void setHistory(DefaultListModel history) {
+    int           i;
+    
+    m_History.clear();
+    for (i = 0; i < history.size(); i++)
+      m_History.addElement(history.get(i));
+
+    setButtons();
+  }
+
+  /**
+   * returns the history.
+   * 
+   * @return        the current history
+   */
+  public DefaultListModel getHistory() {
+    return m_History;
+  }
+
+  /**
+   * displays the database dialog.
+   */
+  protected void showDialog() {
+    m_DbDialog = new DatabaseConnectionDialog(m_Parent, getURL(), getUser(), false);
+    m_DbDialog.setVisible(true);
+    if (m_DbDialog.getReturnValue() == JOptionPane.OK_OPTION) {
+      setURL(m_DbDialog.getURL());
+      setUser(m_DbDialog.getUsername());
+      setPassword(m_DbDialog.getPassword());
+    }
+
+    setButtons();
+  }
+
+  /**
+   * connects to the database, notifies the listeners.
+   */
+  protected void connect() {
+    // disconnect if still connected
+    if (m_DbUtils.isConnected()) {
+      try {
+        m_DbUtils.disconnectFromDatabase();
+        notifyConnectionListeners(ConnectionEvent.DISCONNECT);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+        notifyConnectionListeners(ConnectionEvent.DISCONNECT, e);
+      }
+    }
+
+    // connect
+    try {
+      m_DbUtils.setDatabaseURL(getURL());
+      m_DbUtils.setUsername(getUser());
+      m_DbUtils.setPassword(getPassword());
+      m_DbUtils.connectToDatabase();
+      notifyConnectionListeners(ConnectionEvent.CONNECT);
+      // add to history
+      addHistory(getUser() + "@" + getURL());
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      notifyConnectionListeners(ConnectionEvent.CONNECT, e);
+    }
+
+    setButtons();
+  }
+
+  /**
+   * displays the query history.
+   */
+  public void showHistory() {
+    JList                 list;
+    ListSelectorDialog    dialog;
+    String                tmpStr;
+
+    list   = new JList(m_History);
+    dialog = new ListSelectorDialog(m_Parent, list);
+    
+    if (dialog.showDialog() == ListSelectorDialog.APPROVE_OPTION) {
+      if (list.getSelectedValue() != null) {
+        tmpStr = list.getSelectedValue().toString();
+        if (tmpStr.indexOf("@") > -1) {
+          setUser(tmpStr.substring(0, tmpStr.indexOf("@")));
+          setURL(tmpStr.substring(tmpStr.indexOf("@") + 1));
+          showDialog();
+        }
+        else {
+          setUser("");
+          setURL(tmpStr);
+        }
+      }
+    }
+
+    setButtons();
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l       the listener to add to the list
+   */
+  public void addConnectionListener(ConnectionListener l) {
+    m_ConnectionListeners.add(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l       the listener to remove
+   */
+  public void removeConnectionListener(ConnectionListener l) {
+    m_ConnectionListeners.remove(l);
+  }
+
+  /**
+   * notifies the connection listeners of the event.
+   * 
+   * @param type      the type of the action, CONNECT or DISCONNECT
+   */
+  protected void notifyConnectionListeners(int type) {
+    notifyConnectionListeners(type, null);
+  }
+
+  /**
+   * notifies the connection listeners of the event.
+   * 
+   * @param type      the type of the action, CONNECT or DISCONNECT
+   * @param ex        an optional exception that happened (indicates failure!)
+   */
+  protected void notifyConnectionListeners(int type, Exception ex) {
+    Iterator              iter;
+    ConnectionListener    l;
+
+    iter = m_ConnectionListeners.iterator();
+    while (iter.hasNext()) {
+      l = (ConnectionListener) iter.next();
+      l.connectionChange(
+          new ConnectionEvent(this, type, m_DbUtils, ex));
+    }
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l       the listener to add to the list
+   */
+  public void addHistoryChangedListener(HistoryChangedListener l) {
+    m_HistoryChangedListeners.add(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l       the listener to remove
+   */
+  public void removeHistoryChangedListener(HistoryChangedListener l) {
+    m_HistoryChangedListeners.remove(l);
+  }
+
+  /**
+   * notifies the history listeners of the event.
+   */
+  protected void notifyHistoryChangedListeners() {
+    Iterator                iter;
+    HistoryChangedListener  l;
+
+    iter = m_HistoryChangedListeners.iterator();
+    while (iter.hasNext()) {
+      l = (HistoryChangedListener) iter.next();
+      l.historyChanged(
+          new HistoryChangedEvent(this, HISTORY_NAME, getHistory()));
+    }
+  }
+
+  /**
+   * Called when the caret position is updated.
+   * 
+   * @param event the event to process
+   */
+  public void caretUpdate(CaretEvent event) {
+    setButtons();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/DbUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/DbUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/DbUtils.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * DbUtils.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.core.RevisionUtils;
+import weka.experiment.DatabaseUtils;
+
+import java.sql.Connection;
+
+/**
+ * A little bit extended DatabaseUtils class.
+ *
+ * @see       DatabaseUtils
+ * @see       #execute(String)
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 1.4 $
+ */
+public class DbUtils
+  extends DatabaseUtils {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 103748569037426479L;
+  
+  /**
+   * initializes the object.
+   * 
+   * @throws Exception      in case something goes wrong in the init of the
+   *                        DatabaseUtils constructor
+   * @see     DatabaseUtils
+   */
+  public DbUtils() throws Exception {
+    super();
+  }
+  
+  /**
+   * returns the current database connection.
+   * 
+   * @return        the current connection instance
+   */
+  public Connection getConnection() {
+    return m_Connection;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 1.4 $");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanel.java	(revision 29)
@@ -0,0 +1,198 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InfoPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.gui.ComponentHelper;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/**
+ * A simple panel for displaying information, e.g. progress information etc.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.3 $
+ */
+
+public class InfoPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7701133696481997973L;
+  
+  /** the parent of this panel */
+  protected JFrame m_Parent;
+  
+  /** the list the contains the messages */
+  protected JList m_Info;
+
+  /** the model for the list */
+  protected DefaultListModel m_Model;
+
+  /** the button to clear the area */
+  protected JButton m_ButtonClear;
+
+  /** the button to copy the selected message */
+  protected JButton m_ButtonCopy;
+  
+  /**
+   * creates the panel
+   * @param parent      the parent of this panel
+   */
+  public InfoPanel(JFrame parent) {
+    super();
+    m_Parent = parent;
+    createPanel();
+  }
+
+  /**
+   * inserts the components into the panel
+   */
+  protected void createPanel() {
+    JPanel          panel;
+    JPanel          panel2;
+    
+    setLayout(new BorderLayout());
+    setPreferredSize(new Dimension(0, 80));
+
+    // text
+    m_Model = new DefaultListModel();
+    m_Info  = new JList(m_Model);
+    m_Info.setCellRenderer(new InfoPanelCellRenderer());
+    m_Info.addListSelectionListener(new ListSelectionListener() {
+      public void valueChanged(ListSelectionEvent e) {
+        setButtons(e);
+      }
+    });
+    add(new JScrollPane(m_Info), BorderLayout.CENTER);
+
+    // clear button
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.EAST);
+    m_ButtonClear = new JButton("Clear");
+    m_ButtonClear.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  clear();
+	}
+      });
+    panel.add(m_ButtonClear, BorderLayout.NORTH);
+
+    // clear button
+    panel2 = new JPanel(new BorderLayout());
+    panel.add(panel2, BorderLayout.CENTER);
+    m_ButtonCopy = new JButton("Copy");
+    m_ButtonCopy.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  copyToClipboard();
+	}
+      });
+    panel2.add(m_ButtonCopy, BorderLayout.NORTH);
+  }
+  
+  /**
+   * sets the state of the buttons according to the selection state of the
+   * JList
+   */
+  protected void setButtons(ListSelectionEvent e) {
+    if ( (e == null) || (e.getSource() == m_Info) ) {
+      m_ButtonClear.setEnabled(m_Model.getSize() > 0);
+      m_ButtonCopy.setEnabled(m_Info.getSelectedIndices().length == 1);
+    }
+  }
+
+  /**
+   * sets the focus in a designated control
+   */
+  public void setFocus() {
+    m_Info.requestFocus();
+  }
+
+  /**
+   * clears the content of the panel
+   */
+  public void clear() {
+    m_Model.clear();
+    setButtons(null);
+  }
+
+  /**
+   * copies the currently selected error message to the clipboard
+   * 
+   * @return		true if the content was copied
+   */
+  public boolean copyToClipboard() {
+    StringSelection      selection;
+    Clipboard            clipboard;
+    
+    if (m_Info.getSelectedIndices().length != 1)
+      return false;
+    
+    selection = new StringSelection(((JLabel) m_Info.getSelectedValue()).getText());
+    clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+    clipboard.setContents(selection, selection);
+    return true;
+  }
+  
+  /**
+   * adds the given message to the end of the list (with the associated icon
+   * at the beginning)
+   * @param msg       the message to append to the list
+   * @param icon      the filename of the icon
+   */
+  public void append(String msg, String icon) {
+    append(new JLabel(msg, ComponentHelper.getImageIcon(icon), JLabel.LEFT));
+  }
+
+  /**
+   * adds the given message to the end of the list
+   * @param msg       the message to append to the list
+   */
+  public void append(Object msg) {
+    if (msg instanceof String) {
+      append(msg.toString(), "empty_small.gif");
+      return;
+    }
+
+    m_Model.addElement(msg);
+    m_Info.setSelectedIndex(m_Model.getSize() - 1);
+    m_Info.ensureIndexIsVisible(m_Info.getSelectedIndex());
+    
+    setButtons(null);
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanelCellRenderer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanelCellRenderer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/InfoPanelCellRenderer.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InfoPanelCellRenderer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+
+/**
+ * A specialized renderer that takes care of JLabels in a JList.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+
+public class InfoPanelCellRenderer 
+  extends JLabel 
+  implements ListCellRenderer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -533380118807178531L;
+  
+  /**
+   * the constructor
+   */
+  public InfoPanelCellRenderer() {
+    super();
+    setOpaque(true);
+  }
+  
+  /**
+   * Return a component that has been configured to display the specified value.
+   * @param list The JList we're painting.
+   * @param value The value returned by list.getModel().getElementAt(index).
+   * @param index The cells index.
+   * @param isSelected True if the specified cell was selected.
+   * @param cellHasFocus True if the specified cell has the focus.
+   */
+  public Component getListCellRendererComponent(
+      JList list, Object value,
+      int index, boolean isSelected, boolean cellHasFocus) {
+
+    if (value instanceof JLabel) {
+      setIcon(((JLabel) value).getIcon());
+      setText(((JLabel) value).getText());
+    }
+    else {
+      setIcon(null);
+      setText(value.toString());
+    }
+
+    if (isSelected) {
+      setBackground(list.getSelectionBackground());
+      setForeground(list.getSelectionForeground());
+    }
+    else {
+      setBackground(list.getBackground());
+      setForeground(list.getForeground());
+    }
+    setEnabled(list.isEnabled());
+    setFont(list.getFont());
+
+    return this;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/QueryPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/QueryPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/QueryPanel.java	(revision 29)
@@ -0,0 +1,450 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * QueryPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.gui.ListSelectorDialog;
+import weka.gui.sql.event.ConnectionEvent;
+import weka.gui.sql.event.ConnectionListener;
+import weka.gui.sql.event.HistoryChangedEvent;
+import weka.gui.sql.event.HistoryChangedListener;
+import weka.gui.sql.event.QueryExecuteEvent;
+import weka.gui.sql.event.QueryExecuteListener;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.sql.ResultSet;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTextArea;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+
+/**
+ * Represents a panel for entering an SQL query.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.4 $
+ */
+public class QueryPanel 
+  extends JPanel 
+  implements ConnectionListener, CaretListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4348967824619706636L;
+
+  /** the name of the history. */
+  public final static String HISTORY_NAME = "query";
+
+  /** the name for the max rows in the history. */
+  public final static String MAX_ROWS = "max_rows";
+  
+  /** the parent of this panel. */
+  protected JFrame m_Parent;
+
+  /** the textarea for the query. */
+  protected JTextArea m_TextQuery;
+
+  /** the execute button. */
+  protected JButton m_ButtonExecute = new JButton("Execute");
+
+  /** the clear button. */
+  protected JButton m_ButtonClear = new JButton("Clear");
+
+  /** the history button. */
+  protected JButton m_ButtonHistory = new JButton("History...");
+
+  /** the spinner for the maximum number of rows. */
+  protected JSpinner m_SpinnerMaxRows = new JSpinner();
+
+  /** the connection listeners. */
+  protected HashSet m_QueryExecuteListeners;
+
+  /** the history listeners. */
+  protected HashSet m_HistoryChangedListeners;
+
+  /** for working on the database. */
+  protected DbUtils m_DbUtils;
+
+  /** whether we have a connection to a database or not. */
+  protected boolean m_Connected;
+
+  /** the query history. */
+  protected DefaultListModel m_History = new DefaultListModel();
+  
+  /**
+   * initializes the panel.
+   * 
+   * @param parent        the parent of this panel
+   */
+  public QueryPanel(JFrame parent) {
+    super();
+    
+    m_Parent                  = parent;
+    m_QueryExecuteListeners   = new HashSet();
+    m_HistoryChangedListeners = new HashSet();
+    m_DbUtils                 = null;
+    m_Connected               = false;
+
+    createPanel();
+  }
+
+  /**
+   * creates the panel with all its components.
+   */
+  protected void createPanel() {
+    JPanel              panel;
+    JPanel              panel2;
+    JPanel              panel3;
+    SpinnerNumberModel  model;
+    
+    setLayout(new BorderLayout());
+    
+    // textarea
+    m_TextQuery = new JTextArea();
+    m_TextQuery.addCaretListener(this);
+    m_TextQuery.setFont(
+        new Font("Monospaced", Font.PLAIN, m_TextQuery.getFont().getSize()));
+    add(new JScrollPane(m_TextQuery), BorderLayout.CENTER);
+
+    // buttons
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.EAST);
+    m_ButtonExecute.setMnemonic('E');
+    m_ButtonExecute.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  execute();
+	}
+      });
+    panel.add(m_ButtonExecute, BorderLayout.NORTH);
+    panel2 = new JPanel(new BorderLayout());
+    panel.add(panel2, BorderLayout.CENTER);
+    m_ButtonClear.setMnemonic('r');
+    m_ButtonClear.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  clear();
+	}
+      });
+    panel2.add(m_ButtonClear, BorderLayout.NORTH);
+    panel3 = new JPanel(new BorderLayout());
+    panel2.add(panel3, BorderLayout.CENTER);
+    m_ButtonHistory.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  showHistory();
+	}
+      });
+    panel3.add(m_ButtonHistory, BorderLayout.NORTH);
+
+    // limit
+    panel3 = new JPanel(new FlowLayout());
+    panel3.add(new JLabel("max. rows"));
+    panel3.add(m_SpinnerMaxRows);
+    panel2.add(panel3, BorderLayout.SOUTH);
+    model = (SpinnerNumberModel) m_SpinnerMaxRows.getModel();
+    model.setMaximum(new Integer(Integer.MAX_VALUE));
+    model.setMinimum(new Integer(0));
+    model.setValue(new Integer(100));
+    model.setStepSize(new Integer(100));
+    m_SpinnerMaxRows.setMinimumSize(
+        new Dimension(50, m_SpinnerMaxRows.getHeight()));
+    m_SpinnerMaxRows.setToolTipText("with 0 all rows are retrieved");
+      
+    // set initial state
+    setButtons();
+  }
+
+  /**
+   * sets the focus in a designated control.
+   */
+  public void setFocus() {
+    m_TextQuery.requestFocus();
+  }
+
+  /**
+   * sets the buttons according to the connected-state.
+   */
+  protected void setButtons() {
+    boolean isEmpty;
+    
+    isEmpty = m_TextQuery.getText().trim().equals("");
+    
+    m_ButtonExecute.setEnabled((m_Connected) && (!isEmpty));
+    m_ButtonClear.setEnabled(!isEmpty);
+    m_ButtonHistory.setEnabled(m_History.size() > 0);
+  }
+  
+  /**
+   * This method gets called when the connection is either established
+   * or disconnected.
+   * 
+   * @param evt		the event
+   */
+  public void connectionChange(ConnectionEvent evt) {
+    m_Connected = evt.isConnected();
+    m_DbUtils   = evt.getDbUtils();
+    setButtons();
+  }
+
+  /**
+   * executes the current query.
+   */
+  public void execute() {
+    Exception     ex;
+    ResultSet     rs;
+    
+    // not connected?
+    if (!m_ButtonExecute.isEnabled())
+      return;
+
+    // no query?
+    if (m_TextQuery.getText().trim().equals(""))
+      return;
+
+    // close old resultset
+    try {
+      if (m_DbUtils.getResultSet() != null)
+        m_DbUtils.close();
+    }
+    catch (Exception e) {
+      // ignore (if no resultset present we get an unncessary NullPointerEx.)
+    }
+
+    ex = null;
+    rs = null;
+    
+    try {
+      if (m_DbUtils.execute(getQuery())) {
+        rs = m_DbUtils.getResultSet();
+        // add to history
+        addHistory(getQuery());
+      }
+    }
+    catch (Exception e) {
+      ex = new Exception(e.getMessage());
+    }
+
+    notifyQueryExecuteListeners(rs, ex);
+
+    setButtons();
+  }
+
+  /**
+   * clears the textarea.
+   */
+  public void clear() {
+    m_TextQuery.setText("");
+    m_SpinnerMaxRows.setValue(new Integer(100));
+  }
+
+  /**
+   * adds the given string to the history (removes duplicates).
+   * 
+   * @param s           the string to add
+   */
+  protected void addHistory(String s) {
+    if (s.equals(""))
+      return;
+    
+    // no duplicates!
+    if (m_History.contains(s))
+      m_History.removeElement(s);
+
+    m_History.add(0, s);
+    
+    // send notification
+    notifyHistoryChangedListeners();
+  }
+
+  /**
+   * sets the local history to the given one.
+   * 
+   * @param history     the history to use
+   */
+  public void setHistory(DefaultListModel history) {
+    int           i;
+    
+    m_History.clear();
+    for (i = 0; i < history.size(); i++)
+      m_History.addElement(history.get(i));
+
+    setButtons();
+  }
+
+  /**
+   * returns the history.
+   * 
+   * @return        the current history
+   */
+  public DefaultListModel getHistory() {
+    return m_History;
+  }
+
+  /**
+   * displays the query history.
+   */
+  public void showHistory() {
+    JList                 list;
+    ListSelectorDialog    dialog;
+
+    list   = new JList(m_History);
+    dialog = new ListSelectorDialog(m_Parent, list);
+    
+    if (dialog.showDialog() == ListSelectorDialog.APPROVE_OPTION) {
+      if (list.getSelectedValue() != null)
+        setQuery(list.getSelectedValue().toString());
+    }
+
+    setButtons();
+  }
+
+  /**
+   * sets the query in the textarea.
+   * 
+   * @param query         the query to display
+   */
+  public void setQuery(String query) {
+    m_TextQuery.setText(query);
+  }
+
+  /**
+   * returns the currently displayed query.
+   * 
+   * @return		the query
+   */
+  public String getQuery() {
+    return m_TextQuery.getText();
+  }
+
+  /**
+   * sets the maximum number of rows to display. 0 means unlimited.
+   * 
+   * @param rows	the maximum number of rows
+   */
+  public void setMaxRows(int rows) {
+    if (rows >= 0)
+      m_SpinnerMaxRows.setValue(new Integer(rows));
+  }
+
+  /**
+   * returns the current value for the maximum number of rows. 0 means
+   * unlimited.
+   * 
+   * @return		the maximum number of rows
+   */
+  public int getMaxRows() {
+    return ((Integer) m_SpinnerMaxRows.getValue()).intValue();
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l       the listener to add to the list
+   */
+  public void addQueryExecuteListener(QueryExecuteListener l) {
+    m_QueryExecuteListeners.add(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l       the listener to remove
+   */
+  public void removeQueryExecuteListener(QueryExecuteListener l) {
+    m_QueryExecuteListeners.remove(l);
+  }
+
+  /**
+   * notifies the listeners of the event.
+   * 
+   * @param rs		the resultset
+   * @param ex		the exception
+   */
+  protected void notifyQueryExecuteListeners(ResultSet rs, Exception ex) {
+    Iterator              iter;
+    QueryExecuteListener  l;
+
+    iter = m_QueryExecuteListeners.iterator();
+    while (iter.hasNext()) {
+      l = (QueryExecuteListener) iter.next();
+      l.queryExecuted(
+          new QueryExecuteEvent(
+            this, m_DbUtils, getQuery(), getMaxRows(), rs, ex));
+    }
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l       the listener to add to the list
+   */
+  public void addHistoryChangedListener(HistoryChangedListener l) {
+    m_HistoryChangedListeners.add(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l       the listener to remove
+   */
+  public void removeHistoryChangedListener(HistoryChangedListener l) {
+    m_HistoryChangedListeners.remove(l);
+  }
+
+  /**
+   * notifies the history listeners of the event.
+   */
+  protected void notifyHistoryChangedListeners() {
+    Iterator                iter;
+    HistoryChangedListener  l;
+
+    iter = m_HistoryChangedListeners.iterator();
+    while (iter.hasNext()) {
+      l = (HistoryChangedListener) iter.next();
+      l.historyChanged(
+          new HistoryChangedEvent(this, HISTORY_NAME, getHistory()));
+    }
+  }
+
+  /**
+   * Called when the caret position is updated.
+   * 
+   * @param event	the event
+   */
+  public void caretUpdate(CaretEvent event) {
+    setButtons();
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ResultPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ResultPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ResultPanel.java	(revision 29)
@@ -0,0 +1,388 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultPanel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.gui.JTableHelper;
+import weka.gui.sql.event.QueryExecuteEvent;
+import weka.gui.sql.event.QueryExecuteListener;
+import weka.gui.sql.event.ResultChangedEvent;
+import weka.gui.sql.event.ResultChangedListener;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JViewport;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * Represents a panel for displaying the results of a query in table format.
+ *
+ * @author    FracPete (fracpete at waikato dot ac dot nz)
+ * @version   $Revision: 1.3 $
+ */
+public class ResultPanel 
+  extends JPanel 
+  implements QueryExecuteListener, ChangeListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = 278654800344034571L;
+  
+  /** the parent of this panel */
+  protected JFrame m_Parent;
+
+  /** the result change listeners */
+  protected HashSet m_Listeners;
+
+  /** the panel where to output the queries */
+  protected QueryPanel m_QueryPanel;
+
+  /** the tabbed pane for the results */
+  protected JTabbedPane m_TabbedPane;
+
+  /** the close button */
+  protected JButton m_ButtonClose = new JButton("Close");
+
+  /** the close all button */
+  protected JButton m_ButtonCloseAll = new JButton("Close all");
+
+  /** the button that copies the query into the QueryPanel */
+  protected JButton m_ButtonCopyQuery = new JButton("Re-use query");
+
+  /** the button for the optimal column width of the current table */
+  protected JButton m_ButtonOptWidth = new JButton("Optimal width");
+
+  /** the counter for the tab names */
+  protected int m_NameCounter;
+  
+  /**
+   * initializes the panel
+   * @param parent        the parent of this panel
+   */
+  public ResultPanel(JFrame parent) {
+    super();
+    
+    m_Parent      = parent;
+    m_QueryPanel  = null;
+    m_NameCounter = 0;
+    m_Listeners   = new HashSet();
+    
+    createPanel();
+  }
+
+  /**
+   * creates the panel with all its components
+   */
+  protected void createPanel() {
+    JPanel          panel;
+    JPanel          panel2;
+    JPanel          panel3;
+    JPanel          panel4;
+
+    setLayout(new BorderLayout());
+    setPreferredSize(new Dimension(0, 200));
+
+    // tabbed pane
+    m_TabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
+    m_TabbedPane.addChangeListener(this);
+    add(m_TabbedPane, BorderLayout.CENTER);
+
+    // buttons
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.EAST);
+    panel2 = new JPanel(new BorderLayout());
+    panel.add(panel2, BorderLayout.CENTER);
+    panel3 = new JPanel(new BorderLayout());
+    panel2.add(panel3, BorderLayout.CENTER);
+    panel4 = new JPanel(new BorderLayout());
+    panel3.add(panel4, BorderLayout.CENTER);
+    
+    m_ButtonClose.setMnemonic('l');
+    m_ButtonClose.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  close();
+	}
+      });
+    panel.add(m_ButtonClose, BorderLayout.NORTH);
+    
+    m_ButtonCloseAll.setMnemonic('a');
+    m_ButtonCloseAll.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  closeAll();
+	}
+      });
+    panel2.add(m_ButtonCloseAll, BorderLayout.NORTH);
+    
+    m_ButtonCopyQuery.setMnemonic('Q');
+    m_ButtonCopyQuery.setToolTipText("Copies the query of the currently selected tab into the query field.");
+    m_ButtonCopyQuery.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  copyQuery();
+	}
+      });
+    panel3.add(m_ButtonCopyQuery, BorderLayout.NORTH);
+    
+    m_ButtonOptWidth.setMnemonic('p');
+    m_ButtonOptWidth.setToolTipText("Calculates the optimal column width for the current table.");
+    m_ButtonOptWidth.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  calcOptimalWidth();
+	}
+      });
+    panel4.add(m_ButtonOptWidth, BorderLayout.NORTH);
+
+    // dummy place holder, otherwise is the space too small for the tabbed
+    // pane
+    panel4.add(new JLabel(" "), BorderLayout.CENTER);
+    panel4.add(new JLabel(" "), BorderLayout.SOUTH);
+
+    // set the initial button state
+    setButtons();
+  }
+
+  /**
+   * sets the parameters back to standard
+   */
+  public void clear() {
+    closeAll();
+  }
+
+  /**
+   * sets the focus in a designated control
+   */
+  public void setFocus() {
+    m_TabbedPane.requestFocus();
+  }
+
+  /**
+   * sets the state of the buttons
+   */
+  protected void setButtons() {
+    int         index;
+
+    index = m_TabbedPane.getSelectedIndex();
+
+    m_ButtonClose.setEnabled(index > -1);
+    m_ButtonCloseAll.setEnabled(m_TabbedPane.getTabCount() > 0);
+    m_ButtonCopyQuery.setEnabled(index > -1);
+    m_ButtonOptWidth.setEnabled(index > -1);
+  }
+
+  /**
+   * returns the next name for a tab "QueryXYZ'
+   */
+  protected String getNextTabName() {
+    m_NameCounter++;
+    return "Query" + m_NameCounter;
+  }
+  
+  /**
+   * This method gets called when a query has been executed.
+   */
+  public void queryExecuted(QueryExecuteEvent evt) {
+    ResultSetTable      table;
+    
+    // only displayed successful queries
+    if (evt.failed())
+      return;
+
+    // DDL command like drop etc that don't create ResultSet?
+    if (!evt.hasResult())
+      return;
+
+    try {
+      table = new ResultSetTable(
+                evt.getDbUtils().getDatabaseURL(),
+                evt.getDbUtils().getUsername(),
+                evt.getDbUtils().getPassword(),
+                evt.getQuery(), 
+                new ResultSetTableModel(evt.getResultSet(), evt.getMaxRows()));
+      m_TabbedPane.addTab(getNextTabName(), new JScrollPane(table));
+
+      // set active tab
+      m_TabbedPane.setSelectedIndex(m_TabbedPane.getTabCount() - 1);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    // set buttons
+    setButtons();
+  }
+
+  /**
+   * Invoked when the target of the listener has changed its state.
+   */
+  public void stateChanged(ChangeEvent e) {
+    // in case the tabs get clicked
+    setButtons();
+
+    // notify listeners about current query
+    if (getCurrentTable() != null)
+      notifyListeners(getCurrentTable().getURL(), 
+                      getCurrentTable().getUser(),
+                      getCurrentTable().getPassword(),
+                      getCurrentTable().getQuery());
+  }
+
+  /**
+   * returns the currently set QueryPanel, can be NULL
+   * @return        the current QueryPanel, possibly NULL
+   */
+  public QueryPanel getQueryPanel() {
+    return m_QueryPanel;
+  }
+
+  /**
+   * sets the QueryPanel to use for displaying the query
+   * @param panel   the panel used for displaying the query
+   */
+  public void setQueryPanel(QueryPanel panel) {
+    m_QueryPanel = panel;
+  }
+
+  /**
+   * returns the table of the current tab, can be NULL
+   * @return        the currently selected table
+   */
+  protected ResultSetTable getCurrentTable() {
+    ResultSetTable      table;
+    JScrollPane         pane;
+    JViewport           port;
+    int                 index;
+
+    table = null;
+
+    index = m_TabbedPane.getSelectedIndex();
+    if (index > -1) {
+      pane  = (JScrollPane) m_TabbedPane.getComponentAt(index);
+      port  = (JViewport) pane.getComponent(0);
+      table = (ResultSetTable) port.getComponent(0);
+    }
+      
+    return table;
+  }
+
+  /**
+   * closes the current tab
+   */
+  protected void close() {
+    int                 index;
+
+    index = m_TabbedPane.getSelectedIndex();
+
+    if (index > -1) {
+      try {
+        getCurrentTable().finalize();
+      }
+      catch (Throwable t) {
+        System.out.println(t);
+      }
+      m_TabbedPane.removeTabAt(index);
+    }
+
+    // set buttons
+    setButtons();
+  }
+
+  /**
+   * closes all tabs
+   */
+  protected void closeAll() {
+    while (m_TabbedPane.getTabCount() > 0) {
+      m_TabbedPane.setSelectedIndex(0);
+      try {
+        getCurrentTable().finalize();
+      }
+      catch (Throwable t) {
+        System.out.println(t);
+      }
+      m_TabbedPane.removeTabAt(0);
+    }
+
+    // set buttons
+    setButtons();
+  }
+
+  /**
+   * copies the query of the current tab into the QueryPanel
+   */
+  protected void copyQuery() {
+    if ( (getCurrentTable() != null) && (getQueryPanel() != null) )
+      getQueryPanel().setQuery(getCurrentTable().getQuery());
+  }
+
+  /**
+   * calculates the optimal column width for the current table
+   */
+  protected void calcOptimalWidth() {
+    if (getCurrentTable() != null)
+      JTableHelper.setOptimalColumnWidth(getCurrentTable());
+  }
+
+  /**
+   * adds the given listener to the list of listeners
+   * @param l       the listener to add to the list
+   */
+  public void addResultChangedListener(ResultChangedListener l) {
+    m_Listeners.add(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners
+   * @param l       the listener to remove
+   */
+  public void removeResultChangedListener(ResultChangedListener l) {
+    m_Listeners.remove(l);
+  }
+
+  /**
+   * notifies the listeners of the event
+   * @param url         the database URL
+   * @param user        the user
+   * @param pw          the password
+   * @param query       the query
+   */
+  protected void notifyListeners(String url, String user, 
+                                 String pw, String query) {
+    Iterator                iter;
+    ResultChangedListener   l;
+
+    iter = m_Listeners.iterator();
+    while (iter.hasNext()) {
+      l = (ResultChangedListener) iter.next();
+      l.resultChanged(new ResultChangedEvent(this, url, user, pw, query));
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetHelper.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetHelper.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetHelper.java	(revision 29)
@@ -0,0 +1,483 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultSetHelper.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.sql;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Types;
+import java.util.Vector;
+
+/**
+ * Represents an extended JTable, containing a table model based on a ResultSet
+ * and the corresponding query.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 5324 $
+ */
+public class ResultSetHelper {
+  
+  /** the resultset to work on. */
+  protected ResultSet m_ResultSet;
+
+  /** whether we initialized. */
+  protected boolean m_Initialized = false;
+
+  /** the maximum number of rows to retrieve. */
+  protected int m_MaxRows = 0;
+
+  /** the number of columns. */
+  protected int m_ColumnCount = 0;
+
+  /** the number of rows. */
+  protected int m_RowCount = 0;
+
+  /** the column names. */
+  protected String[] m_ColumnNames = null;
+
+  /** whether a column is numeric. */
+  protected boolean[] m_NumericColumns = null;
+
+  /** the class for each column. */
+  protected Class[] m_ColumnClasses = null;
+
+  /**
+   * initializes the helper, with unlimited number of rows.
+   * 
+   * @param rs        the resultset to work on
+   */
+  public ResultSetHelper(ResultSet rs) {
+    this(rs, 0);
+  }
+
+  /**
+   * initializes the helper, with the given maximum number of rows (less than
+   * 1 means unlimited).
+   * 
+   * @param rs        the resultset to work on
+   * @param max       the maximum number of rows to retrieve
+   */
+  public ResultSetHelper(ResultSet rs, int max) {
+    super();
+
+    m_ResultSet = rs;
+    m_MaxRows   = max;
+  }
+
+  /**
+   * initializes, i.e. reads the data, etc.
+   */
+  protected void initialize() {
+    ResultSetMetaData     meta;
+    int                   i;
+    
+    if (m_Initialized)
+      return;
+    
+    try {
+      meta = m_ResultSet.getMetaData();
+
+      // columns names
+      m_ColumnNames = new String[meta.getColumnCount()];
+      for (i = 1; i <= meta.getColumnCount(); i++)
+        m_ColumnNames[i - 1] = meta.getColumnName(i);
+      
+      // numeric columns
+      m_NumericColumns = new boolean[meta.getColumnCount()];
+      for (i = 1; i <= meta.getColumnCount(); i++)
+        m_NumericColumns[i - 1] = typeIsNumeric(meta.getColumnType(i));
+
+      // column classes
+      m_ColumnClasses = new Class[meta.getColumnCount()];
+      for (i = 1; i <= meta.getColumnCount(); i++) {
+	try {
+	  m_ColumnClasses[i - 1] = typeToClass(meta.getColumnType(i));
+	}
+	catch (Exception ex) {
+	  m_ColumnClasses[i - 1] = String.class;
+	}
+      }
+
+      // dimensions
+      m_ColumnCount = meta.getColumnCount();
+
+      // if the JDBC driver doesn't support scrolling we can't determine
+      // the row count here
+      if (m_ResultSet.getType() == ResultSet.TYPE_FORWARD_ONLY) {
+	m_RowCount = -1;
+      }
+      else {
+	m_RowCount = 0;
+	m_ResultSet.first();
+	if (m_MaxRows > 0) {
+	  try {
+	    m_ResultSet.absolute(m_MaxRows);
+	    m_RowCount = m_ResultSet.getRow();
+	  }
+	  catch (Exception ex) {
+	    // ignore it
+	  }
+	}
+	else {
+	  m_ResultSet.last();
+	  m_RowCount = m_ResultSet.getRow();
+	}
+
+	// sometimes, e.g. with a "desc <table>", we can't use absolute(int)
+	// and getRow()???
+	try {
+	  if ( (m_RowCount == 0) && (m_ResultSet.first()) ) {
+	    m_RowCount = 1;
+	    while (m_ResultSet.next()) {
+	      m_RowCount++;
+	      if (m_ResultSet.getRow() == m_MaxRows)
+		break;
+	    };
+	  }
+	}
+	catch (Exception e) {
+	  // ignore it
+	}
+      }
+
+      m_Initialized = true;
+    }
+    catch (Exception ex) {
+      // ignore it
+    }
+  }
+
+  /**
+   * the underlying resultset.
+   * 
+   * @return		the resultset
+   */
+  public ResultSet getResultSet() {
+    return m_ResultSet;
+  }
+
+  /**
+   * returns the number of columns in the resultset.
+   * 
+   * @return		the number of columns
+   */
+  public int getColumnCount() {
+    initialize();
+
+    return m_ColumnCount;
+  }
+
+  /**
+   * returns the number of rows in the resultset. If -1 then the number of
+   * rows couldn't be determined, i.e., the cursors aren't scrollable.
+   * 
+   * @return		the number of rows, -1 if it wasn't possible to 
+   * 			determine
+   */
+  public int getRowCount() {
+    initialize();
+
+    return m_RowCount;
+  }
+
+  /**
+   * returns an array with the names of the columns in the resultset.
+   * 
+   * @return		the column names
+   */
+  public String[] getColumnNames() {
+    initialize();
+
+    return m_ColumnNames;
+  }
+
+  /**
+   * returns an array that indicates whether a column is numeric or nor.
+   * 
+   * @return		the numeric columns
+   */
+  public boolean[] getNumericColumns() {
+    initialize();
+
+    return m_NumericColumns;
+  }
+
+  /**
+   * returns the classes for the columns.
+   * 
+   * @return		the column classes
+   */
+  public Class[] getColumnClasses() {
+    initialize();
+
+    return m_ColumnClasses;
+  }
+
+  /**
+   * whether a limit on the rows to retrieve was set.
+   * 
+   * @return		true if there's a limit
+   */
+  public boolean hasMaxRows() {
+    return (m_MaxRows > 0);
+  }
+
+  /**
+   * the maximum number of rows to retrieve, less than 1 means unlimited.
+   * 
+   * @return		the maximum number of rows
+   */
+  public int getMaxRows() {
+    return m_MaxRows;
+  }
+
+  /**
+   * returns an 2-dimensional array with the content of the resultset, the first
+   * dimension is the row, the second the column (i.e., getCells()[y][x]).
+   * Note: the data is not cached! It is always retrieved anew.
+   * 
+   * @return		the data
+   */
+  public Object[][] getCells() {
+    int			i;
+    int			n;
+    Vector<Object[]>	result;
+    Object[]		row;
+    int			rowCount;
+    boolean		proceed;
+    
+    initialize();
+
+    result = new Vector<Object[]>();
+
+    try {
+      
+      // do know the number of rows?
+      rowCount = getRowCount();
+      if (rowCount == -1) {
+	rowCount = getMaxRows();
+	proceed  = m_ResultSet.next();
+      }
+      else {
+	proceed = m_ResultSet.first();
+      }
+      
+      if (proceed) {
+	i = 0;
+	while (true) {
+	  row = new Object[getColumnCount()];
+	  result.add(row);
+
+	  for (n = 0; n < getColumnCount(); n++) {
+	    try {
+	      // to get around byte arrays when using getObject(int)
+	      if (getColumnClasses()[n] == String.class)
+		row[n] = m_ResultSet.getString(n + 1);
+	      else
+		row[n] = m_ResultSet.getObject(n + 1);
+	    }
+	    catch (Exception e) {
+	      row[n] = null;
+	    }
+	  }
+
+	  // get next row, if possible
+	  if (i == rowCount - 1) {
+	    break;
+	  }
+	  else {
+	    // no more rows -> exit
+	    if (!m_ResultSet.next())
+	      break;
+	  }
+
+	  i++;
+	}
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result.toArray(new Object[result.size()][getColumnCount()]);
+  }
+
+  /**
+   * Returns the class associated with a SQL type.
+   *
+   * @param type the SQL type
+   * @return the Java class corresponding with the type
+   */
+  public static Class typeToClass(int type) {
+    Class     result;
+    
+    switch (type) {
+      case Types.BIGINT :
+        result = Long.class;
+        break;
+      case Types.BINARY:
+        result = String.class;
+      case Types.BIT:
+        result = Boolean.class;
+        break;
+      case Types.CHAR:
+        result = Character.class;
+        break;
+      case Types.DATE:
+        result = java.sql.Date.class;
+        break;
+      case Types.DECIMAL:
+        result = Double.class;
+        break;
+      case Types.DOUBLE:
+        result = Double.class;
+        break;
+      case Types.FLOAT:
+        result = Float.class;
+        break;
+      case Types.INTEGER:
+        result = Integer.class;
+        break;
+      case Types.LONGVARBINARY:
+        result = String.class;
+        break;
+      case Types.LONGVARCHAR:
+        result = String.class;
+        break;
+      case Types.NULL:
+        result = String.class;
+        break;
+      case Types.NUMERIC:
+        result = Double.class;
+        break;
+      case Types.OTHER:
+        result = String.class;
+        break;
+      case Types.REAL:
+        result = Double.class;
+        break;
+      case Types.SMALLINT:
+        result = Short.class;
+        break;
+      case Types.TIME:
+        result = java.sql.Time.class;
+        break;
+      case Types.TIMESTAMP:
+        result = java.sql.Timestamp.class;
+        break;
+      case Types.TINYINT:
+        result = Short.class;
+        break;
+      case Types.VARBINARY:
+        result = String.class;
+        break;
+      case Types.VARCHAR:
+        result = String.class;
+        break;
+      default:
+        result = null;
+    }
+
+    return result;
+  }
+
+  /**
+   * returns whether the SQL type is numeric (and therefore the justification
+   * should be right).
+   * 
+   * @param type      the SQL type
+   * @return          whether the given type is numeric
+   */
+  public static boolean typeIsNumeric(int type) {
+    boolean     result;
+    
+    switch (type) {
+      case Types.BIGINT :
+        result = true;
+        break;
+      case Types.BINARY:
+        result = false;
+      case Types.BIT:
+        result = false;
+        break;
+      case Types.CHAR:
+        result = false;
+        break;
+      case Types.DATE:
+        result = false;
+        break;
+      case Types.DECIMAL:
+        result = true;
+        break;
+      case Types.DOUBLE:
+        result = true;
+        break;
+      case Types.FLOAT:
+        result = true;
+        break;
+      case Types.INTEGER:
+        result = true;
+        break;
+      case Types.LONGVARBINARY:
+        result = false;
+        break;
+      case Types.LONGVARCHAR:
+        result = false;
+        break;
+      case Types.NULL:
+        result = false;
+        break;
+      case Types.NUMERIC:
+        result = true;
+        break;
+      case Types.OTHER:
+        result = false;
+        break;
+      case Types.REAL:
+        result = true;
+        break;
+      case Types.SMALLINT:
+        result = true;
+        break;
+      case Types.TIME:
+        result = false;
+        break;
+      case Types.TIMESTAMP:
+        result = true;
+        break;
+      case Types.TINYINT:
+        result = true;
+        break;
+      case Types.VARBINARY:
+        result = false;
+        break;
+      case Types.VARCHAR:
+        result = false;
+        break;
+      default:
+        result = false;
+    }
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTable.java	(revision 29)
@@ -0,0 +1,138 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultSetTable.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.gui.JTableHelper;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JTable;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * Represents an extended JTable, containing a table model based on a ResultSet
+ * and the corresponding query.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+public class ResultSetTable
+  extends JTable {
+
+  /** for serialization */
+  private static final long serialVersionUID = -3391076671854464137L;
+
+  /** the query the table model is based on */
+  protected String m_Query;
+
+  /** the connect string with which the query was run */
+  protected String m_URL;
+
+  /** the user that was used to connect to the DB */
+  protected String m_User;
+
+  /** the password that was used to connect to the DB */
+  protected String m_Password;
+
+  /**
+   * initializes the table
+   * @param url         the database URL
+   * @param user        the database user
+   * @param pw          the database password
+   * @param query       the query
+   */
+  public ResultSetTable(String url, String user, String pw, String query, 
+                        ResultSetTableModel model) {
+    super(model);
+
+    m_URL      = url;
+    m_User     = user;
+    m_Password = pw;
+    m_Query    = query;
+    
+    setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+
+    // optimal colwidths (only according to header!)/cell renderer
+    for (int i = 0; i < getColumnCount(); i++) {
+      JTableHelper.setOptimalHeaderWidth(this, i);
+      getColumnModel().getColumn(i).setCellRenderer(
+          new ResultSetTableCellRenderer());
+    }
+    
+    // double click on column displays optimal colwidth
+    final JTable table = this;
+    getTableHeader().addMouseListener(new MouseAdapter() {
+      public void mouseClicked(MouseEvent e) {
+        TableColumnModel columnModel = getColumnModel();
+        int viewColumn = columnModel.getColumnIndexAtX(e.getX());
+        int column = convertColumnIndexToModel(viewColumn);
+        if (    (e.getButton() == MouseEvent.BUTTON1)
+             && (e.getClickCount() == 2)
+             && (column != -1) )
+          JTableHelper.setOptimalColumnWidth(table, column);
+      }
+    });
+    getTableHeader().setToolTipText("double left click on column displays the column with optimal width");
+  }
+
+  /**
+   * returns the database URL that produced the table model
+   */
+  public String getURL() {
+    return m_URL;
+  }
+
+  /**
+   * returns the user that produced the table model
+   */
+  public String getUser() {
+    return m_User;
+  }
+
+  /**
+   * returns the password that produced the table model
+   */
+  public String getPassword() {
+    return m_Password;
+  }
+
+  /**
+   * returns the query that produced the table model
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+
+  /**
+   * frees up the memory
+   */
+  public void finalize() throws Throwable {
+    if (getModel() != null)
+      ((ResultSetTableModel) getModel()).finalize();
+    
+    super.finalize();
+
+    System.gc();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableCellRenderer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableCellRenderer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableCellRenderer.java	(revision 29)
@@ -0,0 +1,124 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultSetTableCellRenderer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Handles the background colors for missing values differently than the
+ * DefaultTableCellRenderer.
+ *
+ * @author     FracPete (fracpete at waikato dot ac dot nz)
+ * @version    $Revision: 1.2 $
+ */
+public class ResultSetTableCellRenderer
+  extends DefaultTableCellRenderer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8106963669703497351L;
+  
+  // the color for missing values
+  private Color           missingColor;
+  private Color           missingColorSelected;
+
+  /**
+   * initializes the Renderer with a standard color
+   */
+  public ResultSetTableCellRenderer() {
+    this( new Color(223, 223, 223), 
+          new Color(192, 192, 192) );
+  }
+
+  /**
+   * initializes the Renderer with the given colors
+   */
+  public ResultSetTableCellRenderer( Color missingColor, 
+                                     Color missingColorSelected ) {
+
+    super();
+
+    this.missingColor           = missingColor;
+    this.missingColorSelected   = missingColorSelected;
+  }
+
+  /**
+   * Returns the default table cell renderer.
+   * stuff for the header is taken from <a href="http://www.chka.de/swing/table/faq.html">here</a>
+   */
+  public Component getTableCellRendererComponent(
+      JTable table, Object value, boolean isSelected, 
+      boolean hasFocus, int row, int column ) {
+
+    ResultSetTableModel       model;
+    Component                 result;
+    boolean                   found;
+
+    result = super.getTableCellRendererComponent(
+        table, value, isSelected, hasFocus, row, column);
+
+    if (table.getModel() instanceof ResultSetTableModel) {
+      model = (ResultSetTableModel) table.getModel();
+      // normal cell
+      if (row >= 0) {
+        if (model.isNullAt(row, column)) {
+          setToolTipText("NULL");
+          if (isSelected)
+            result.setBackground(missingColorSelected);
+          else
+            result.setBackground(missingColor);
+        }
+        else {
+          setToolTipText(null);
+          if (isSelected)
+            result.setBackground(table.getSelectionBackground());
+          else
+            result.setBackground(Color.WHITE);
+        }
+
+        // alignment
+        if (model.isNumericAt(column))
+          setHorizontalAlignment(SwingConstants.RIGHT);
+        else
+          setHorizontalAlignment(SwingConstants.LEFT);
+      }
+      // header
+      else {
+        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+        setHorizontalAlignment(SwingConstants.CENTER);
+        if (table.getColumnModel().getSelectionModel().isSelectedIndex(column))
+          result.setBackground(UIManager.getColor("TableHeader.background").darker());
+        else
+          result.setBackground(UIManager.getColor("TableHeader.background"));
+      }
+    }
+
+    return result;
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableModel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableModel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/ResultSetTableModel.java	(revision 29)
@@ -0,0 +1,259 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultSetTableModel.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import java.sql.ResultSet;
+import java.util.HashSet;
+
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+
+/**
+* The model for an SQL ResultSet.
+*
+* @author     FracPete (fracpete at waikato dot ac dot nz)
+* @version    $Revision: 1.3 $
+*/
+public class ResultSetTableModel implements TableModel {
+  
+  /** the listeners. */
+  protected HashSet m_Listeners;
+  
+  /** the data. */
+  protected Object[][] m_Data;
+  
+  /** for retrieving the data etc. */
+  protected ResultSetHelper m_Helper;
+
+  /**
+   * initializes the model, retrieves all rows.
+   * 
+   * @param rs          the ResultSet to get the data from
+   */
+  public ResultSetTableModel(ResultSet rs) {
+    this(rs, 0);
+  }
+
+  /**
+   * initializes the model, retrieves only the given amount of rows (0 means
+   * all).
+   * 
+   * @param rs          the ResultSet to get the data from
+   * @param rows        the maximum number of rows to retrieve, 0 retrieves all
+   */
+  public ResultSetTableModel(ResultSet rs, int rows) {
+    super();
+
+    m_Listeners = new HashSet();
+    m_Helper    = new ResultSetHelper(rs, rows);
+    m_Data      = m_Helper.getCells();
+  }
+
+  /**
+   * adds a listener to the list that is notified each time a change to data 
+   * model occurs.
+   * 
+   * @param l		the listener to add
+   */
+  public void addTableModelListener(TableModelListener l) {
+    m_Listeners.add(l);
+  }
+
+  /**
+   * returns the most specific superclass for all the cell values in the 
+   * column (always String).
+   * 
+   * @param columnIndex	the index of the column
+   * @return		the class
+   */
+  public Class getColumnClass(int columnIndex) {
+    Class       result;
+
+    result = null;
+
+    if (    (m_Helper.getColumnClasses() != null) 
+         && (columnIndex >= 0) 
+         && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0)
+        result = Integer.class;
+      else
+        result = m_Helper.getColumnClasses()[columnIndex - 1];
+   }
+
+    return result;
+  }
+
+  /**
+   * returns the number of columns in the model.
+   * 
+   * @return		the number of columns
+   */
+  public int getColumnCount() {
+    return m_Helper.getColumnCount() + 1;
+  }
+
+  /**
+   * returns the name of the column at columnIndex.
+   * 
+   * @param columnIndex	the index of the column
+   * @return		the name
+   */
+  public String getColumnName(int columnIndex) {
+    String         result;
+
+    result = "";
+
+    if (    (m_Helper.getColumnNames() != null) 
+        && (columnIndex >= 0) 
+        && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0)
+        result = "Row";
+      else
+        result = m_Helper.getColumnNames()[columnIndex - 1];
+    }
+
+    return result;
+  }
+
+  /**
+   * returns the number of rows in the model.
+   * 
+   * @return		the number of data rows
+   */
+  public int getRowCount() {
+    return m_Data.length;
+  }
+
+  /**
+   * returns the value for the cell at columnindex and rowIndex.
+   * 
+   * @param rowIndex	the row of the cell
+   * @param columnIndex	the column of the cell
+   * @return		the data value
+   */
+  public Object getValueAt(int rowIndex, int columnIndex) {
+    Object            result;
+
+    result = null;
+
+    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
+         && (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0)
+        result = new Integer(rowIndex + 1);
+      else
+        result = m_Data[rowIndex][columnIndex - 1];
+    }
+
+    return result;
+  }
+
+  /**
+   * checks whether the value of the cell is NULL.
+   * 
+   * @param rowIndex	the row of the cell
+   * @param columnIndex	the column of the cell
+   * @return		true if the cell value is NULL
+   */
+  public boolean isNullAt(int rowIndex, int columnIndex) {
+    return (getValueAt(rowIndex, columnIndex) == null);
+  }
+
+  /**
+   * returns whether the column at the given index is numeric.
+   * 
+   * @param columnIndex       the column to check
+   * @return                  whether the column is numeric
+   */
+  public boolean isNumericAt(int columnIndex) {
+    boolean         result;
+
+    result = false;
+    
+    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
+      if (columnIndex == 0) {
+        result = true;
+      }
+      else {
+        if (m_Helper.getNumericColumns() == null)
+          result = false;
+        else
+          result = m_Helper.getNumericColumns()[columnIndex - 1];
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * returns true if the cell at rowindex and columnindexis editable.
+   * 
+   * @param rowIndex	the row of the cell
+   * @param columnIndex	the column of the cell
+   * @return		always false
+   */
+  public boolean isCellEditable(int rowIndex, int columnIndex) {
+    return false;
+  }
+
+  /**
+   * removes a listener from the list that is notified each time a change to
+   * the data model occurs.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeTableModelListener(TableModelListener l) {
+    m_Listeners.remove(l);
+  }
+
+  /**
+   * sets the value in the cell at columnIndex and rowIndex to aValue.
+   * Ignored.
+   * 
+   * @param aValue	the value to set - ignored
+   * @param rowIndex	the row of the cell
+   * @param columnIndex	the column of the cell
+   */
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+    // ignore
+  }
+
+  /**
+   * frees up the memory.
+   * 
+   * @throws Throwable	if something goes wrong
+   */
+  public void finalize() throws Throwable {
+    try {
+      m_Helper.getResultSet().close();
+      m_Helper.getResultSet().getStatement().close();
+      m_Helper = null;
+    }
+    catch (Exception e) {
+      // ignored
+    }
+
+    m_Data = null;
+    
+    super.finalize();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewer.java	(revision 29)
@@ -0,0 +1,681 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SqlViewer.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.core.Memory;
+import weka.gui.LookAndFeel;
+import weka.gui.sql.event.ConnectionEvent;
+import weka.gui.sql.event.ConnectionListener;
+import weka.gui.sql.event.HistoryChangedEvent;
+import weka.gui.sql.event.HistoryChangedListener;
+import weka.gui.sql.event.QueryExecuteEvent;
+import weka.gui.sql.event.QueryExecuteListener;
+import weka.gui.sql.event.ResultChangedEvent;
+import weka.gui.sql.event.ResultChangedListener;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListModel;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * Represents a little tool for querying SQL databases.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 4724 $
+ */
+public class SqlViewer 
+  extends    JPanel 
+  implements ConnectionListener, 
+             HistoryChangedListener,
+             QueryExecuteListener, 
+             ResultChangedListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -4395028775566514329L;
+
+  /** the name of the history file (in the home directory). */
+  protected final static String HISTORY_FILE = "SqlViewerHistory.props";
+
+  /** the width property in the history file. */
+  public final static String WIDTH = "width";
+
+  /** the height property in the history file. */
+  public final static String HEIGHT = "height";
+  
+  /** the parent of this panel. */
+  protected JFrame m_Parent;
+
+  /** the connection panel. */
+  protected ConnectionPanel m_ConnectionPanel;
+
+  /** the query panel. */
+  protected QueryPanel m_QueryPanel;
+
+  /** the result panel. */
+  protected ResultPanel m_ResultPanel;
+
+  /** the info panel. */
+  protected InfoPanel m_InfoPanel;
+
+  /** the connect string with which the query was run. */
+  protected String m_URL;
+
+  /** the user that was used to connect to the DB. */
+  protected String m_User;
+
+  /** the password that was used to connect to the DB. */
+  protected String m_Password;
+
+  /** the currently selected query. */
+  protected String m_Query;
+
+  /** stores the history. */
+  protected Properties m_History;
+  
+  /**
+   * initializes the SqlViewer.
+   * 
+   * @param parent        the parent of this panel
+   */
+  public SqlViewer(JFrame parent) {
+    super();
+   
+    m_Parent   = parent;
+    m_URL      = "";
+    m_User     = "";
+    m_Password = "";
+    m_Query    = "";
+    m_History  = new Properties();
+   
+    createPanel();
+  }
+
+  /**
+   * builds the interface.
+   */
+  protected void createPanel() {
+    JPanel          panel;
+    JPanel          panel2;
+    
+    setLayout(new BorderLayout());
+
+    // connection
+    m_ConnectionPanel = new ConnectionPanel(m_Parent);
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.NORTH);
+    panel.setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createTitledBorder("Connection"),
+                    BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+    panel.add(m_ConnectionPanel, BorderLayout.CENTER);
+
+    // query
+    m_QueryPanel = new QueryPanel(m_Parent);
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.CENTER);
+    panel2 = new JPanel(new BorderLayout());
+    panel2.setBorder(BorderFactory.createCompoundBorder(
+                     BorderFactory.createTitledBorder("Query"),
+                     BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+    panel2.add(m_QueryPanel, BorderLayout.NORTH);
+    panel.add(panel2, BorderLayout.NORTH);
+
+    // result
+    m_ResultPanel = new ResultPanel(m_Parent);
+    m_ResultPanel.setQueryPanel(m_QueryPanel);
+    panel2 = new JPanel(new BorderLayout());
+    panel2.setBorder(BorderFactory.createCompoundBorder(
+                     BorderFactory.createTitledBorder("Result"),
+                     BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+    panel2.add(m_ResultPanel, BorderLayout.CENTER);
+    panel.add(panel2, BorderLayout.CENTER);
+
+    // info
+    m_InfoPanel = new InfoPanel(m_Parent);
+    panel = new JPanel(new BorderLayout());
+    add(panel, BorderLayout.SOUTH);
+    panel.setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createTitledBorder("Info"),
+                    BorderFactory.createEmptyBorder(0, 5, 5, 5)));
+    panel.add(m_InfoPanel, BorderLayout.CENTER);
+
+    // listeners
+    addConnectionListener(this);
+    addConnectionListener(m_QueryPanel);
+    addQueryExecuteListener(this);
+    addQueryExecuteListener(m_ResultPanel);
+    addResultChangedListener(this);
+    addHistoryChangedListener(this);
+
+    // history
+    loadHistory(true);
+  }
+  
+  /**
+   * This method gets called when the connection is either established
+   * or disconnected.
+   * 
+   * @param evt		the event
+   */
+  public void connectionChange(ConnectionEvent evt) {
+    if (evt.getType() == ConnectionEvent.DISCONNECT) {
+      m_InfoPanel.append(   "disconnect from: " 
+                          + evt.getDbUtils().getDatabaseURL(),
+                          "information_small.gif" );
+    }
+    else {
+      m_InfoPanel.append(   "connecting to: " 
+                          + evt.getDbUtils().getDatabaseURL() 
+                          + " = " + evt.isConnected(),
+                          "information_small.gif" );
+    }
+
+    // did an exception happen?
+    if (evt.getException() != null)
+      m_InfoPanel.append("exception: " + evt.getException(), "error_small.gif");
+
+    // set focus
+    if (evt.isConnected())
+      m_QueryPanel.setFocus();
+    else
+      m_ConnectionPanel.setFocus();
+  }
+  
+  /**
+   * This method gets called when a query has been executed.
+   * 
+   * @param evt		the event
+   */
+  public void queryExecuted(QueryExecuteEvent evt) {
+    ResultSetHelper   helper;
+    
+    if (evt.failed()) {
+      m_InfoPanel.append("Query:" + evt.getQuery(), "error_small.gif");
+      m_InfoPanel.append("exception: " + evt.getException(), "error_small.gif");
+    }
+    else {
+      m_InfoPanel.append("Query: " + evt.getQuery(), "information_small.gif");
+      try {
+        if (evt.hasResult()) {
+          helper = new ResultSetHelper(evt.getResultSet());
+          if ((evt.getMaxRows() > 0) && (helper.getRowCount() >= evt.getMaxRows()))
+            m_InfoPanel.append(helper.getRowCount() + " rows selected (" 
+                + evt.getMaxRows() + " displayed).", 
+                "information_small.gif");
+          else if (helper.getRowCount() == -1)
+            m_InfoPanel.append("Unknown number of rows selected (due to JDBC driver restrictions).", 
+              "information_small.gif");
+          else
+            m_InfoPanel.append(helper.getRowCount() + " rows selected.", 
+                "information_small.gif");
+        }
+
+        // save max rows
+        loadHistory(false);
+        m_History.setProperty(
+            QueryPanel.MAX_ROWS, Integer.toString(evt.getMaxRows()));
+        saveHistory();
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+  
+  /**
+   * This method gets called when a query has been executed.
+   * 
+   * @param evt		the event
+   */
+  public void resultChanged(ResultChangedEvent evt) {
+    m_URL      = evt.getURL();
+    m_User     = evt.getUser();
+    m_Password = evt.getPassword();
+    m_Query    = evt.getQuery();
+  }
+  
+  /**
+   * This method gets called when a history is modified.
+   * It saves the history immediately to the users home directory.
+   * 
+   * @param evt		the event
+   */
+  public void historyChanged(HistoryChangedEvent evt) {
+    // load history, in case some other process changed it!
+    loadHistory(false);
+    
+    m_History.setProperty( 
+        evt.getHistoryName(), modelToString(evt.getHistory()));
+
+    // save it
+    saveHistory();
+  }
+
+  /**
+   * returns the filename of the history file.
+   * 
+   * @return		the history file
+   */
+  protected String getHistoryFilename() {
+    return   System.getProperties().getProperty("user.home")
+           + File.separatorChar
+           + HISTORY_FILE;
+  }
+
+  /**
+   * transforms the given, comma-separated string into a DefaultListModel.
+   * 
+   * @param s     the string to break up and transform into a list model
+   * @return      the generated DefaultListModel
+   */
+  protected DefaultListModel stringToModel(String s) {
+    DefaultListModel    result;
+    String              tmpStr;
+    int                 i;
+    boolean             quote;
+    String[]            find;
+    String[]            replace;
+    int                 index;
+
+    result = new DefaultListModel();
+    
+    // get rid of doubled quotes, \\n, etc.
+    find    = new String[]{"\"\"", "\\n", "\\r", "\\t"};
+    replace = new String[]{"\"",   "\n",  "\r",  "\t"};
+    for (i = 0; i < find.length; i++) {
+      tmpStr = "";
+      while (s.length() > 0) {
+        index = s.indexOf(find[i]);
+        if (index > -1) {
+          tmpStr += s.substring(0, index) + replace[i];
+          s       = s.substring(index + 2);
+        }
+        else {
+          tmpStr += s;
+          s       = "";
+        }
+      }
+      s = tmpStr;
+    }
+
+    quote  = false;
+    tmpStr = "";
+    for (i = 0; i < s.length(); i++) {
+      if (s.charAt(i) == '"') {
+        quote = !quote;
+        tmpStr += "" + s.charAt(i);
+      }
+      else if (s.charAt(i) == ',') {
+        if (quote) {
+          tmpStr += "" + s.charAt(i);
+        }
+        else {
+          if (tmpStr.startsWith("\""))
+            tmpStr = tmpStr.substring(1, tmpStr.length() - 1);
+          result.addElement(tmpStr);
+          tmpStr = "";
+        }
+      }
+      else {
+        tmpStr += "" + s.charAt(i);
+      }
+    }
+    
+    // add last element
+    if (!tmpStr.equals("")) {
+      if (tmpStr.startsWith("\""))
+        tmpStr = tmpStr.substring(1, tmpStr.length() - 1);
+      result.addElement(tmpStr);
+    }
+
+    return result;
+  }
+
+  /**
+   * converts the given model into a comma-separated string.
+   * 
+   * @param m       the model to convert
+   * @return        the string representation of the model
+   */
+  protected String modelToString(DefaultListModel m) {
+    String      result;
+    String      tmpStr;
+    int         i;
+    int         n;
+    boolean     quote;
+
+    result = "";
+
+    for (i = 0; i < m.size(); i++) {
+      if (i > 0)
+        result += ",";
+      
+      tmpStr = m.get(i).toString();
+      quote  = (tmpStr.indexOf(",") > -1) || (tmpStr.indexOf(" ") > -1);
+      
+      if (quote)
+        result += "\"";
+      
+      for (n = 0; n < tmpStr.length(); n++) {
+        // double quotes
+        if (tmpStr.charAt(n) == '"')
+          result += "" + "\"\"";
+        // normal character
+        else
+          result += "" + tmpStr.charAt(n);
+      }
+      
+      if (quote)
+        result += "\"";
+    }
+
+    return result;
+  }
+
+  /**
+   * loads the history properties of the SqlViewer in the user's home directory.
+   * 
+   * @param set       whether to set the read properties in the panels or not
+   * @see #HISTORY_FILE
+   */
+  protected void loadHistory(boolean set) {
+    BufferedInputStream		str;
+    File			file;
+    int				width;
+    int				height;
+
+    try {
+      file = new File(getHistoryFilename());
+      if (file.exists()) {
+        str = new BufferedInputStream(
+            new FileInputStream(getHistoryFilename()));
+        m_History.load(str);
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    // set the histories
+    if (set) {
+      m_ConnectionPanel.setHistory(
+          stringToModel(
+            m_History.getProperty(ConnectionPanel.HISTORY_NAME, "")));
+      m_QueryPanel.setHistory(
+          stringToModel(
+            m_History.getProperty(QueryPanel.HISTORY_NAME, "")));
+      m_QueryPanel.setMaxRows(
+          Integer.parseInt(m_History.getProperty(QueryPanel.MAX_ROWS, "100")));
+
+      width  = Integer.parseInt(m_History.getProperty(WIDTH, "0"));
+      height = Integer.parseInt(m_History.getProperty(HEIGHT, "0"));
+      if ((width != 0) && (height != 0))
+	setPreferredSize(new Dimension(width, height));
+    }
+  }
+
+  /**
+   * saves the history properties of the SqlViewer in the user's home directory.
+   * 
+   * @see #HISTORY_FILE
+   */
+  protected void saveHistory() {
+    BufferedOutputStream    str;
+    
+    try {
+      str = new BufferedOutputStream(
+          new FileOutputStream(getHistoryFilename()));
+      m_History.store(str, "SQL-Viewer-History");
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * obtains the size of the panel and saves it in the history.
+   * 
+   * @see #saveHistory()
+   */
+  public void saveSize() {
+    m_History.setProperty(WIDTH, "" + getSize().width);
+    m_History.setProperty(HEIGHT, "" + getSize().height);
+
+    saveHistory();
+  }
+  
+  /**
+   * calls the clear method of all sub-panels to set back to default values
+   * and free up memory.
+   */
+  public void clear() {
+    m_ConnectionPanel.clear();
+    m_QueryPanel.clear();
+    m_ResultPanel.clear();
+    m_InfoPanel.clear();
+  }
+
+  /**
+   * returns the database URL from the currently active tab in the ResultPanel,
+   * otherwise an empty string.
+   * 
+   * @see 		ResultPanel
+   * @return		the currently selected tab's URL
+   */
+  public String getURL() {
+    return m_URL;
+  }
+
+  /**
+   * returns the user from the currently active tab in the ResultPanel,
+   * otherwise an empty string.
+   * 
+   * @see		ResultPanel
+   * @return		the currently selected tab's user
+   */
+  public String getUser() {
+    return m_User;
+  }
+
+  /**
+   * returns the password from the currently active tab in the ResultPanel,
+   * otherwise an empty string.
+   * 
+   * @see 		ResultPanel
+   * @return		the currently selected tab's password
+   */
+  public String getPassword() {
+    return m_Password;
+  }
+
+  /**
+   * returns the query from the currently active tab in the ResultPanel,
+   * otherwise an empty string.
+   * 
+   * @see		ResultPanel
+   * @return		the currently selected tab's query
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l		the listener to add to the list
+   */
+  public void addConnectionListener(ConnectionListener l) {
+    m_ConnectionPanel.addConnectionListener(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeConnectionListener(ConnectionListener l) {
+    m_ConnectionPanel.removeConnectionListener(l);
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l		the listener to add to the list
+   */
+  public void addQueryExecuteListener(QueryExecuteListener l) {
+    m_QueryPanel.addQueryExecuteListener(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeQueryExecuteListener(QueryExecuteListener l) {
+    m_QueryPanel.removeQueryExecuteListener(l);
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l		the listener to add to the list
+   */
+  public void addResultChangedListener(ResultChangedListener l) {
+    m_ResultPanel.addResultChangedListener(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeResultChangedListener(ResultChangedListener l) {
+    m_ResultPanel.removeResultChangedListener(l);
+  }
+
+  /**
+   * adds the given listener to the list of listeners.
+   * 
+   * @param l		the listener to add to the list
+   */
+  public void addHistoryChangedListener(HistoryChangedListener l) {
+    m_ConnectionPanel.addHistoryChangedListener(l);
+    m_QueryPanel.addHistoryChangedListener(l);
+  }
+
+  /**
+   * removes the given listener from the list of listeners.
+   * 
+   * @param l		the listener to remove
+   */
+  public void removeHistoryChangedListener(HistoryChangedListener l) {
+    m_ConnectionPanel.removeHistoryChangedListener(l);
+    m_QueryPanel.removeHistoryChangedListener(l);
+  }
+
+  /** for monitoring the Memory consumption. */
+  private static Memory m_Memory = new Memory(true);
+  
+  /** the sql viewer. */
+  private static SqlViewer m_Viewer;
+  
+  /**
+   * starts the SQL-Viewer interface.
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String[] args) {
+    weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+    LookAndFeel.setLookAndFeel();
+    
+    try {
+      // uncomment to disable the memory management:
+      //m_Memory.setEnabled(false);
+
+      final JFrame jf = new JFrame("Weka SQL-Viewer");
+      m_Viewer = new SqlViewer(jf);
+      jf.getContentPane().setLayout(new BorderLayout());
+      jf.getContentPane().add(m_Viewer, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          m_Viewer.saveSize();
+          jf.dispose();
+          System.exit(0);
+        }
+      });
+      jf.pack();
+      jf.setSize(800, 600);
+      jf.setVisible(true);
+
+      Thread memMonitor = new Thread() {
+        public void run() {
+          while (true) {
+            try {
+              this.sleep(4000);
+
+              System.gc();
+
+              if (m_Memory.isOutOfMemory()) {
+                // clean up
+                jf.dispose();
+                m_Viewer = null;
+                System.gc();
+
+                // stop threads
+                m_Memory.stopThreads();
+
+                // display error
+                System.err.println("\ndisplayed message:");
+                m_Memory.showOutOfMemory();
+                System.err.println("\nexiting");
+                System.exit(-1);
+              }
+
+            } 
+            catch (InterruptedException ex) { 
+              ex.printStackTrace(); 
+            }
+          }
+        }
+      };
+
+      memMonitor.setPriority(Thread.MAX_PRIORITY);
+      memMonitor.start();
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewerDialog.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewerDialog.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/SqlViewerDialog.java	(revision 29)
@@ -0,0 +1,287 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * SqlViewerDialog.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql;
+
+import weka.gui.sql.event.ResultChangedEvent;
+import weka.gui.sql.event.ResultChangedListener;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+/**
+ * A little dialog containing the SqlViewer.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 5279 $
+ */
+public class SqlViewerDialog 
+  extends JDialog 
+  implements ResultChangedListener {
+
+  /** for serialization. */
+  private static final long serialVersionUID = -31619864037233099L;
+  
+  /** the parent frame. */
+  protected JFrame m_Parent;
+  
+  /** the SQL panel. */
+  protected SqlViewer m_Viewer;
+
+  /** the panel for the buttons. */
+  protected JPanel m_PanelButtons;
+
+  /** the OK button. */
+  protected JButton m_ButtonOK = new JButton("OK");
+
+  /** the Cancel button. */
+  protected JButton m_ButtonCancel = new JButton("Cancel");
+
+  /** displays the current query. */
+  protected JLabel m_LabelQuery = new JLabel("");
+  
+  /** whether to return sparse instances or not. */
+  protected JCheckBox m_CheckBoxSparseData = new JCheckBox("Generate sparse data");
+
+  /** the return value. */
+  protected int m_ReturnValue = JOptionPane.CANCEL_OPTION;
+
+  /** the connect string with which the query was run. */
+  protected String m_URL;
+
+  /** the user that was used to connect to the DB. */
+  protected String m_User;
+
+  /** the password that was used to connect to the DB. */
+  protected String m_Password;
+
+  /** the currently selected query. */
+  protected String m_Query;
+  
+  /**
+   * initializes the dialog.
+   * 
+   * @param parent	the parent frame
+   */
+  public SqlViewerDialog(JFrame parent) {
+    super(parent, "SQL-Viewer", true);
+
+    m_Parent   = parent;
+    m_URL      = "";
+    m_User     = "";
+    m_Password = "";
+    m_Query    = "";
+    
+    createDialog();
+  }
+
+  /**
+   * builds the dialog and all its components.
+   */
+  protected void createDialog() {
+    JPanel                    panel;
+    JPanel                    panel2;
+    final SqlViewerDialog     dialog;
+    
+    dialog = this;
+    setLayout(new BorderLayout());
+
+    // sql panel
+    m_Viewer = new SqlViewer(m_Parent);
+    add(m_Viewer, BorderLayout.CENTER);
+    
+    panel2 = new JPanel(new BorderLayout());
+    add(panel2, BorderLayout.SOUTH);
+    
+    // Buttons
+    panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+    panel2.add(panel, BorderLayout.EAST);
+    m_ButtonOK.setMnemonic('O');
+    panel.add(m_ButtonOK);
+    m_ButtonOK.addActionListener(new ActionListener(){
+	public void actionPerformed(ActionEvent evt){
+	  m_ReturnValue = JOptionPane.OK_OPTION;
+          // remove listener, otherwise does the disposal of resultspanel
+          // change the query again!
+          m_Viewer.removeResultChangedListener(dialog);
+          m_Viewer.saveSize();
+	  dialog.dispose();
+      }
+    });
+    m_ButtonCancel.setMnemonic('C');
+    panel.add(m_ButtonCancel);
+    m_ButtonCancel.addActionListener(new ActionListener(){
+	public void actionPerformed(ActionEvent evt){
+	  m_ReturnValue = JOptionPane.CANCEL_OPTION;
+          // remove listener, otherwise does the disposal of resultspanel
+          // change the query again!
+          m_Viewer.removeResultChangedListener(dialog);
+          m_Viewer.saveSize();
+	  dialog.dispose();
+      }
+    });
+    
+    // the checkbox for sparse data
+    panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+    panel2.add(panel, BorderLayout.WEST);
+    panel.add(m_CheckBoxSparseData);
+    m_CheckBoxSparseData.setMnemonic('s');
+    
+    addWindowListener(new WindowAdapter() {
+      /**
+       * Invoked when a window is in the process of being closed.
+       */
+      public void windowClosing(WindowEvent e) {
+	m_Viewer.saveSize();
+      }
+    });
+   
+    // current Query
+    panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+    panel2.add(panel, BorderLayout.CENTER);
+    panel.add(m_LabelQuery);
+    
+    pack();
+    getRootPane().setDefaultButton(m_ButtonOK);
+    setResizable(true);
+
+    // listener
+    m_Viewer.addResultChangedListener(this);
+  }
+
+  /**
+   * displays the dialog if TRUE.
+   * 
+   * @param b		if true displaying the dialog, hiding otherwise
+   */
+  public void setVisible(boolean b) {
+    if (b)
+      m_ReturnValue = JOptionPane.CANCEL_OPTION;
+
+    super.setVisible(b);
+    
+    // free up memory
+    if (b)
+      m_Viewer.clear();
+  }
+
+  /**
+   * returns whether the user clicked OK (JOptionPane.OK_OPTION) or whether he
+   * cancelled the dialog (JOptionPane.CANCEL_OPTION).
+   * @return		the return value
+   * @see		JOptionPane
+   */
+  public int getReturnValue() {
+    return m_ReturnValue;
+  }
+
+  /**
+   * returns the chosen URL, if any.
+   * 
+   * @return		the URL
+   */
+  public String getURL() {
+    return m_URL;
+  }
+
+  /**
+   * returns the chosen user, if any.
+   * 
+   * @return		the user
+   */
+  public String getUser() {
+    return m_User;
+  }
+
+  /**
+   * returns the chosen password, if any.
+   * 
+   * @return		the password
+   */
+  public String getPassword() {
+    return m_Password;
+  }
+
+  /**
+   * returns the chosen query, if any.
+   * 
+   * @return		the query
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+  
+  /**
+   * Returns whether sparse data is generated.
+   * 
+   * @return		true if sparse data is to be generated
+   */
+  public boolean getGenerateSparseData() {
+    return m_CheckBoxSparseData.isSelected();
+  }
+
+  /**
+   * This method gets called when a query has been executed.
+   * 
+   * @param evt		the event
+   */
+  public void resultChanged(ResultChangedEvent evt) {
+    m_URL      = evt.getURL();
+    m_User     = evt.getUser();
+    m_Password = evt.getPassword();
+    m_Query    = evt.getQuery();
+    m_LabelQuery.setText("Current query: " + m_Query);
+  }
+
+  /**
+   * for testing only.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args) {
+    SqlViewerDialog       dialog;
+
+    dialog = new SqlViewerDialog(null);
+    dialog.setDefaultCloseOperation(SqlViewerDialog.DISPOSE_ON_CLOSE);
+    dialog.setVisible(true);
+    System.out.println("ReturnValue = " + dialog.getReturnValue());
+    if (dialog.getReturnValue() == JOptionPane.OK_OPTION) {
+      System.out.println("URL      = " + dialog.getURL());
+      System.out.println("User     = " + dialog.getUser());
+      System.out.println("Password = " + dialog.getPassword().replaceAll(".", "*"));
+      System.out.println("Query    = " + dialog.getQuery());
+    }
+  }
+}
+
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionEvent.java	(revision 29)
@@ -0,0 +1,144 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ConnectionEvent.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql.event;
+
+import weka.gui.sql.DbUtils;
+
+import java.util.EventObject;
+
+/**
+ * An event that is generated when a connection is established or dropped.
+ *
+ * @see         ConnectionListener
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+public class ConnectionEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 5420308930427835037L;
+  
+  /** it was a connect try */
+  public final static int CONNECT = 0;
+
+  /** it was a disconnect */
+  public final static int DISCONNECT = 1;
+  
+  /** the type of event, CONNECT or DISCONNECT */
+  protected int m_Type;
+  
+  /** the databaseutils instance reponsible for the connection */
+  protected DbUtils m_DbUtils;
+
+  /** a possible exception that occurred if not successful */
+  protected Exception m_Exception;
+  
+  /**
+   * constructs the event
+   * @param source        the source that generated this event
+   * @param type          whether CONNECT or DISCONNECT happened
+   * @param utils         the DatabaseUtils isntance responsible for the
+   *                      connection
+   */
+  public ConnectionEvent(Object source, int type, DbUtils utils) {
+    this(source, type, utils, null);
+  }
+  
+  /**
+   * constructs the event
+   * @param source        the source that generated this event
+   * @param type          whether CONNECT or DISCONNECT happened
+   * @param utils         the DatabaseUtils isntance responsible for the
+   *                      connection
+   * @param ex            a possible exception, if not successful
+   */
+  public ConnectionEvent(Object source, int type, DbUtils utils, Exception ex) {
+    super(source);
+    
+    m_Type      = type;
+    m_DbUtils   = utils;
+    m_Exception = ex;
+  }
+  
+  /**
+   * returns the type of this event, CONNECT or DISCONNECT
+   * @return          the type of this event
+   * @see             #CONNECT
+   * @see             #DISCONNECT
+   */
+  public int getType() {
+    return m_Type;
+  }
+  
+  /**
+   * whether an exception happened and is stored
+   * @return          whether an exception happened
+   */
+  public boolean failed() {
+    return (getException() != null);
+  }
+  
+  /**
+   * returns whether the connection is still open.
+   * @return        whether the connection is still open
+   */
+  public boolean isConnected() {
+    return m_DbUtils.isConnected();
+  }
+
+  /**
+   * returns the stored exception, if any (can be NULL)
+   */
+  public Exception getException() {
+    return m_Exception;
+  }
+  
+  /**
+   * returns the DbUtils instance that is responsible for the
+   * connect/disconnect.
+   * @return        the responsible DbUtils instance
+   */
+  public DbUtils getDbUtils() {
+    return m_DbUtils;
+  }
+
+  /**
+   * returns the event in a string representation
+   * @return        the event in a string representation
+   */
+  public String toString() {
+    String        result;
+
+    result  = super.toString();
+    result  = result.substring(0, result.length() - 1);  // remove "]"
+    result +=   ",url=" + m_DbUtils.getDatabaseURL() 
+              + ",user=" + m_DbUtils.getUsername()
+              + ",password=" + m_DbUtils.getPassword().replaceAll(".", "*")
+              + ",connected=" + isConnected() 
+              + ",exception=" + getException()
+              + "]";
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/ConnectionListener.java	(revision 29)
@@ -0,0 +1,42 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ConnectionListener.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.sql.event;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * A listener for connect/disconnect events.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.1 $
+ */
+
+public interface ConnectionListener extends EventListener {
+  /**
+   * This method gets called when the connection is either established
+   * or disconnected.
+   */
+  public void connectionChange(ConnectionEvent evt);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedEvent.java	(revision 29)
@@ -0,0 +1,91 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * HistoryChangedEvent.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql.event;
+
+import java.util.EventObject;
+
+import javax.swing.DefaultListModel;
+
+/**
+ * An event that is generated when a history is modified.
+ *
+ * @see         HistoryChangedListener
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+public class HistoryChangedEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 7476087315774869973L;
+  
+  /** the name of the history */
+  protected String m_HistoryName;
+  
+  /** the history model */
+  protected DefaultListModel m_History;
+  
+  /**
+   * constructs the event
+   * @param name        the name of the history
+   * @param history     the model of the history
+   */
+  public HistoryChangedEvent( Object source, 
+                              String name, 
+                              DefaultListModel history ) {
+    super(source);
+    
+    m_HistoryName = name;
+    m_History     = history;
+  }
+
+  /**
+   * returns the name of the history
+   */
+  public String getHistoryName() {
+    return m_HistoryName;
+  }
+
+  /**
+   * returns the history model
+   */
+  public DefaultListModel getHistory() {
+    return m_History;
+  }
+
+  /**
+   * returns the event in a string representation
+   * @return        the event in a string representation
+   */
+  public String toString() {
+    String        result;
+
+    result  = super.toString();
+    result  = result.substring(0, result.length() - 1);  // remove "]"
+    result +=   ",name=" + getHistoryName() 
+              + ",history=" + getHistory()
+              + "]";
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/HistoryChangedListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * HistoryChangedListener.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.sql.event;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * A listener for changes in a history.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.1 $
+ */
+
+public interface HistoryChangedListener extends EventListener {
+  /**
+   * This method gets called when a history is modified.
+   */
+  public void historyChanged(HistoryChangedEvent evt);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteEvent.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * QueryExecuteEvent.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql.event;
+
+import weka.gui.sql.DbUtils;
+
+import java.sql.ResultSet;
+import java.util.EventObject;
+
+/**
+ * An event that is generated when a query is executed.
+ *
+ * @see         QueryExecuteListener
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+public class QueryExecuteEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = -5556385019954730740L;
+  
+  /** the Db utils instance for the current DB connection  */
+  protected DbUtils m_DbUtils;
+  
+  /** the query that was executed */
+  protected String m_Query;
+
+  /** the produced ResultSet, if any */
+  protected ResultSet m_ResultSet;
+
+  /** a possible exception, if the query failed */
+  protected Exception m_Exception;
+
+  /** the maximum number of rows to retrieve */
+  protected int m_MaxRows;
+  
+  /**
+   * constructs the event
+   * @param source        the source that generated this event
+   * @param utils         the DbUtils instance that connected to the DB
+   * @param query         the query that is the basis for the resultset
+   * @param rows          the maximum number of rows to retrieve (0 for all)
+   * @param rs            the ResultSet that was produced (depending on the
+   *                      type of SQL query it can also be NULL)
+   * @param ex            in case an exception occurred
+   */
+  public QueryExecuteEvent( Object source, 
+                            DbUtils utils,
+                            String query, 
+                            int rows,
+                            ResultSet rs, 
+                            Exception ex ) {
+    super(source);
+
+    m_DbUtils   = utils;
+    m_Query     = query;
+    m_MaxRows   = rows;
+    m_ResultSet = rs;
+    m_Exception = ex;
+  }
+
+  /**
+   * returns the DbUtils instance that was executed the query
+   */
+  public DbUtils getDbUtils() {
+    return m_DbUtils;
+  }
+
+  /**
+   * returns the query that was executed
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+
+  /**
+   * returns the maximum number of rows to retrieve. 0 means all.
+   */
+  public int getMaxRows() {
+    return m_MaxRows;
+  }
+
+  /**
+   * is TRUE in case the exception is not NULL, i.e. the query failed
+   */
+  public boolean failed() {
+    return (m_Exception != null);
+  }
+
+  /**
+   * whether a ResultSet was produced, e.g. DDL commands like delete, drop
+   * or update do not produce one.
+   */
+  public boolean hasResult() {
+    return (m_ResultSet != null);
+  }
+
+  /**
+   * returns the resultset that was produced, can be null in case the query
+   * failed
+   */
+  public ResultSet getResultSet() {
+    return m_ResultSet;
+  }
+
+  /**
+   * returns the exception, if one happened, otherwise NULL
+   */
+  public Exception getException() {
+    return m_Exception;
+  }
+
+  /**
+   * returns the event in a string representation
+   * @return        the event in a string representation
+   */
+  public String toString() {
+    String        result;
+
+    result  = super.toString();
+    result  = result.substring(0, result.length() - 1);  // remove "]"
+    result +=   ",query=" + getQuery() 
+              + ",maxrows=" + getMaxRows()
+              + ",failed=" + failed()
+              + ",exception=" + getException() + "]";
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/QueryExecuteListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * QueryExecuteListener.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql.event;
+
+import java.util.EventListener;
+
+/**
+ * A listener for executing queries.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+
+public interface QueryExecuteListener extends EventListener {
+  /**
+   * This method gets called when a query has been executed.
+   * 
+   * @param evt		the event
+   */
+  public void queryExecuted(QueryExecuteEvent evt);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedEvent.java	(revision 29)
@@ -0,0 +1,119 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultChangedEvent.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.sql.event;
+
+import java.util.EventObject;
+
+/**
+ * An event that is generated when a different Result is activated in the
+ * ResultPanel.
+ *
+ * @see         ResultChangedListener
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.2 $
+ */
+public class ResultChangedEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 36042516077236111L;
+  
+  /** the query that is associated with the active result table */
+  protected String m_Query;
+
+  /** the connect string with which the query was run */
+  protected String m_URL;
+
+  /** the user that was used to connect to the DB */
+  protected String m_User;
+
+  /** the password that was used to connect to the DB */
+  protected String m_Password;
+
+  /**
+   * constructs the event
+   * @param source        the source that generated this event
+   * @param url           the current database url
+   * @param user          the current user
+   * @param pw            the current password
+   * @param query         the current query
+   */
+  public ResultChangedEvent(Object source, 
+                            String url,
+                            String user,
+                            String pw,
+                            String query ) {
+    super(source);
+
+    m_URL      = url;
+    m_User     = user;
+    m_Password = pw;
+    m_Query    = query;
+  }
+
+  /**
+   * returns the database URL that produced the table model
+   */
+  public String getURL() {
+    return m_URL;
+  }
+
+  /**
+   * returns the user that produced the table model
+   */
+  public String getUser() {
+    return m_User;
+  }
+
+  /**
+   * returns the password that produced the table model
+   */
+  public String getPassword() {
+    return m_Password;
+  }
+
+  /**
+   * returns the query that was executed
+   */
+  public String getQuery() {
+    return m_Query;
+  }
+
+  /**
+   * returns the event in a string representation
+   * @return        the event in a string representation
+   */
+  public String toString() {
+    String        result;
+
+    result  = super.toString();
+    result  = result.substring(0, result.length() - 1);  // remove "]"
+    result +=   ",url=" + getURL() 
+              + ",user=" + getUser()
+              + ",password=" + getPassword().replaceAll(".", "*")
+              + ",query=" + getQuery()
+              + "]";
+
+    return result;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/sql/event/ResultChangedListener.java	(revision 29)
@@ -0,0 +1,42 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ResultChangedListener.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.sql.event;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * A listener that is notified if another Result is activated in the
+ * ResultPanel.
+ *
+ * @author      FracPete (fracpete at waikato dot ac dot nz)
+ * @version     $Revision: 1.1 $
+ */
+
+public interface ResultChangedListener extends EventListener {
+  /**
+   * This method gets called when a query has been executed.
+   */
+  public void resultChanged(ResultChangedEvent evt);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceCounter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceCounter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceCounter.java	(revision 29)
@@ -0,0 +1,125 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceCounter.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.awt.Color;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/** 
+ * A bean that counts instances streamed to it.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class InstanceCounter
+  extends JPanel
+  implements InstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6084967152645935934L;
+  
+  private JLabel m_Count_Lab;
+  private int m_Count;
+  private boolean m_Debug;
+
+  public void input(Instance instance) throws Exception {
+    
+    if (m_Debug) {
+      System.err.println("InstanceCounter::input(" + instance +")");
+    }
+    m_Count++;
+    m_Count_Lab.setText(""+m_Count+" instances");
+    repaint();
+  }
+  
+  public void inputFormat(Instances instanceInfo) {
+    
+    if (m_Debug) {
+      System.err.println("InstanceCounter::inputFormat()");
+    }
+    Instances inputInstances = new Instances(instanceInfo,0);
+    m_Count = 0;
+    m_Count_Lab.setText(""+m_Count+" instances");
+  }
+
+  public void setDebug(boolean debug) {
+    
+    m_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    
+    return m_Debug;
+  }
+
+  public InstanceCounter() {
+    
+    m_Count = 0;
+    m_Count_Lab = new JLabel("no instances");
+    add(m_Count_Lab);
+    //    setSize(60,40);
+    setBackground(Color.lightGray);
+  }
+
+  public void instanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  inputFormat(a.outputFormat());
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  if (m_Debug)
+	    System.err.println("InstanceCounter::instanceProduced() - End of instance batch");
+	  break;
+	default:
+	  System.err.println("InstanceCounter::instanceProduced() - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println("InstanceCounter::instanceProduced() - Unknown source object type");
+    }
+  }
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceEvent.java	(revision 29)
@@ -0,0 +1,72 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceEvent.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import java.util.EventObject;
+
+/** 
+ * An event encapsulating an instance stream event.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.7 $
+ */
+public class InstanceEvent
+  extends EventObject {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3207259868110667379L;
+  
+  /** Specifies that the instance format is available */
+  public static final int FORMAT_AVAILABLE   = 1;
+
+  /** Specifies that an instance is available */
+  public static final int INSTANCE_AVAILABLE = 2;
+
+  /** Specifies that the batch of instances is finished */
+  public static final int BATCH_FINISHED     = 3;
+
+  private int m_ID;
+
+  /**
+   * Constructs an InstanceEvent with the specified source object and event 
+   * type
+   *
+   * @param source the object generating the InstanceEvent
+   * @param ID the type of the InstanceEvent
+   */
+  public InstanceEvent(Object source, int ID) {
+
+    super(source);
+    m_ID = ID;
+  }
+
+  /**
+   * Get the event type
+   *
+   * @return the event type
+   */
+  public int getID() {
+
+    return m_ID;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceJoiner.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceJoiner.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceJoiner.java	(revision 29)
@@ -0,0 +1,314 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceJoiner.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.io.Serializable;
+import java.util.Vector;
+
+/** 
+ * A bean that joins two streams of instances into one.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 4997 $
+ */
+public class InstanceJoiner
+  implements Serializable, InstanceProducer, SerialInstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6529972700291329656L;
+
+  /** The listeners */
+  private Vector listeners;
+
+  /** Debugging mode */
+  private boolean b_Debug;
+
+  /** The input format for instances */
+  protected Instances m_InputFormat;
+
+  /** The current output instance */
+  private Instance m_OutputInstance;
+
+
+  /** Whether the first input batch has finished */
+  private boolean b_FirstInputFinished;
+  private boolean b_SecondInputFinished;
+
+
+  /** Setup the initial states of the member variables */
+  public InstanceJoiner() {
+    
+    listeners = new Vector();
+    m_InputFormat = null;
+    m_OutputInstance = null;
+    b_Debug = false;
+    b_FirstInputFinished = false;
+    b_SecondInputFinished = false;
+  }
+
+
+  /**
+   * Sets the format of the input instances. If the filter is able to determine
+   * the output format before seeing any input instances, it does so here. This
+   * default implementation assumes the output format is determined when 
+   * batchFinished() is called.
+   *
+   * @param instanceInfo an Instances object containing the input instance
+   * structure (any instances contained in the object are ignored - only the
+   * structure is required).
+   * @return true if the outputFormat may be collected immediately
+   */
+  public boolean inputFormat(Instances instanceInfo) {
+    
+    m_InputFormat = new Instances(instanceInfo,0);
+    notifyInstanceProduced(new InstanceEvent(this, InstanceEvent.FORMAT_AVAILABLE));
+    b_FirstInputFinished = false;
+    b_SecondInputFinished = false;
+    return true;
+  }
+
+  /**
+   * Gets the format of the output instances. This should only be called
+   * after input() or batchFinished() has returned true.
+   *
+   * @return an Instances object containing the output instance
+   * structure only.
+   * @throws Exception if no input structure has been defined (or the output
+   * format hasn't been determined yet)
+   */
+  public Instances outputFormat() throws Exception {
+    
+    if (m_InputFormat == null) {
+      throw new Exception("No output format defined.");
+    }
+    return new Instances(m_InputFormat,0);
+  }
+  
+  public boolean input(Instance instance) throws Exception {
+    
+    if (m_InputFormat == null) {
+      throw new Exception("No input instance format defined");
+    }
+    if (instance != null) {
+      m_OutputInstance = (Instance)instance.copy();
+      notifyInstanceProduced(new InstanceEvent(this,
+				InstanceEvent.INSTANCE_AVAILABLE));
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Signify that this batch of input to the filter is finished. If the filter
+   * requires all instances prior to filtering, output() may now be called
+   * to retrieve the filtered instances. Any subsequent instances filtered
+   * should be filtered based on setting obtained from the first batch
+   * (unless the inputFormat has been re-assigned or new options have been
+   * set). This default implementation assumes all instance processing occurs
+   * during inputFormat() and input().
+   *
+   * @throws Exception if no input structure has been defined
+   */
+  public void batchFinished() throws Exception {
+    
+    if (m_InputFormat == null) {
+      throw new Exception("No input instance format defined");
+    }
+    notifyInstanceProduced(new InstanceEvent(this,
+					     InstanceEvent.BATCH_FINISHED));
+  }
+
+
+  /**
+   * Output an instance after filtering but do not remove from the output
+   * queue.
+   *
+   * @return the instance that has most recently been filtered (or null if
+   * the queue is empty).
+   * @throws Exception if no input structure has been defined
+   */
+  public Instance outputPeek() throws Exception {
+    
+    if (m_InputFormat == null) {
+      throw new Exception("No output instance format defined");
+    }
+    if (m_OutputInstance == null) {
+      return null;
+    }
+    return (Instance)m_OutputInstance.copy();
+  }
+
+
+  public void setDebug(boolean debug) {
+    
+    b_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    
+    return b_Debug;
+  }
+
+  public synchronized void addInstanceListener(InstanceListener ipl) {
+    
+    listeners.addElement(ipl);
+  }
+  
+  public synchronized void removeInstanceListener(InstanceListener ipl) {
+    
+    listeners.removeElement(ipl);
+  }
+  
+  protected void notifyInstanceProduced(InstanceEvent e) {
+    
+    if (listeners.size() > 0) {
+      if (b_Debug) {
+	System.err.println(this.getClass().getName()
+			   + "::notifyInstanceProduced()");
+      }
+      Vector l;
+      synchronized (this) {
+	l = (Vector)listeners.clone();
+      }
+      for(int i = 0; i < l.size(); i++) {
+	((InstanceListener)l.elementAt(i)).instanceProduced(e);
+      }
+      // If there are any listeners, and the event is an INSTANCE_AVAILABLE,
+      // they should have retrieved the instance with outputPeek();
+      try {
+	if (e.getID() == InstanceEvent.INSTANCE_AVAILABLE) {
+	  m_OutputInstance = null;
+	}
+      } catch (Exception ex) {
+	System.err.println("Problem: notifyInstanceProduced() was\n"
+			   + "called with INSTANCE_AVAILABLE, but output()\n"
+			   + "threw an exception: " + ex.getMessage());
+      }
+    }
+  }
+
+  public void instanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+			+ "::firstInstanceProduced() - Format available");
+	  }
+	  inputFormat(a.outputFormat());
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+			+ "::firstInstanceProduced() - Instance available");
+	  }
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+			+ "::firstInstanceProduced() - End of instance batch");
+	  }
+	  batchFinished();
+	  b_FirstInputFinished = true;
+	  break;
+	default:
+	  System.err.println(this.getClass().getName()
+	       + "::firstInstanceProduced() - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println(this.getClass().getName()
+	     + "::firstInstanceProduced() - Unknown source object type");
+    }
+  }
+
+  public void secondInstanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	if (!b_FirstInputFinished) {
+	  throw new Exception(this.getClass().getName()
+	  + "::secondInstanceProduced() - Input received from"
+	  + " second stream before first stream finished");
+	}
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+	    + "::secondInstanceProduced() - Format available");
+	  }
+	  // Check the formats are compatible
+	  if (!(a.outputFormat()).equalHeaders(outputFormat())) {
+	    throw new Exception(this.getClass().getName()
+	    + "::secondInstanceProduced() - incompatible instance streams\n" + (a.outputFormat()).equalHeadersMsg(outputFormat()));
+	  }
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+	    + "::secondInstanceProduced() - Instance available");
+	  }
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  if (b_Debug) {
+	    System.err.println(this.getClass().getName()
+	    + "::secondInstanceProduced() - End of instance batch");
+	  }
+	  batchFinished();
+	  break;
+	default:
+	  System.err.println(this.getClass().getName()
+		+ "::secondInstanceProduced() - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println(this.getClass().getName()
+	  + "::secondInstanceProduced() - Unknown source object type");
+    }
+  }
+}
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceListener.java	(revision 29)
@@ -0,0 +1,37 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.streams;
+
+import java.util.EventListener;
+
+/** 
+ * An interface for objects interested in listening to streams of instances.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public interface InstanceListener extends EventListener {
+  
+  void instanceProduced(InstanceEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceLoader.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceLoader.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceLoader.java	(revision 29)
@@ -0,0 +1,208 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceLoader.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+/** 
+ * A bean that produces a stream of instances from a file.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class InstanceLoader
+  extends JPanel 
+  implements ActionListener, InstanceProducer {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8725567310271862492L;
+  
+  private Vector m_Listeners;
+  private Thread m_LoaderThread;
+  private Instance m_OutputInstance;
+  private Instances m_OutputInstances;
+  private boolean m_Debug;
+  private JButton m_StartBut;
+  private JTextField m_FileNameTex;
+
+  private class LoadThread extends Thread {
+    
+    private InstanceProducer m_IP;
+    public LoadThread(InstanceProducer ip) {
+      
+      m_IP = ip;
+    }
+
+    public void run() {
+      
+      try {
+	m_StartBut.setText("Stop");
+	m_StartBut.setBackground(Color.red);
+	if (m_Debug) {
+	  System.err.println("InstanceLoader::LoadThread::run()");
+	}
+	// Load the instances one at a time and pass them on to the listener
+	Reader input = new BufferedReader(
+		       new FileReader(m_FileNameTex.getText()));
+	m_OutputInstances = new Instances(input, 1);
+	if (m_Debug) {
+	  System.err.println("InstanceLoader::LoadThread::run()"
+			     + " - Instances opened from: "
+			     + m_FileNameTex.getText());
+	}
+	InstanceEvent ie = new InstanceEvent(m_IP,
+					     InstanceEvent.FORMAT_AVAILABLE);
+	notifyInstanceProduced(ie);
+	while (m_OutputInstances.readInstance(input)) {
+	  if (m_LoaderThread != this) {
+	    return;
+	  }
+	  if (m_Debug) {
+	    System.err.println("InstanceLoader::LoadThread::run()"
+			       + " - read instance");
+	  }
+	  // put the instance into a queue?
+	  m_OutputInstance = m_OutputInstances.instance(0);
+	  m_OutputInstances.delete(0);
+	  ie = new InstanceEvent(m_IP, InstanceEvent.INSTANCE_AVAILABLE);
+	  notifyInstanceProduced(ie);
+	}
+	ie = new InstanceEvent(m_IP, InstanceEvent.BATCH_FINISHED);
+	notifyInstanceProduced(ie);
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      } finally {
+	m_LoaderThread = null;
+	m_StartBut.setText("Start");
+	m_StartBut.setBackground(Color.green);
+      }
+    }
+  }
+
+  public InstanceLoader() {
+    setLayout(new BorderLayout());
+    m_StartBut = new JButton("Start");
+    m_StartBut.setBackground(Color.green);
+    add("West",m_StartBut);
+    m_StartBut.addActionListener(this);
+    m_FileNameTex = new JTextField("/home/trigg/datasets/UCI/iris.arff");
+    add("Center",m_FileNameTex);
+    m_Listeners = new Vector();
+    //    setSize(60,40);
+  }
+
+  public void setDebug(boolean debug) {
+    
+    m_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    
+    return m_Debug;
+  }
+
+  public void setArffFile(String newArffFile) {
+    
+    m_FileNameTex.setText(newArffFile);
+  }
+  
+  public String getArffFile() {
+    return m_FileNameTex.getText();
+  }
+
+  public synchronized void addInstanceListener(InstanceListener ipl) {
+    
+    m_Listeners.addElement(ipl);
+  }
+  
+  public synchronized void removeInstanceListener(InstanceListener ipl) {
+    
+    m_Listeners.removeElement(ipl);
+  }
+  
+  protected void notifyInstanceProduced(InstanceEvent e) {
+    
+    if (m_Debug) {
+      System.err.println("InstanceLoader::notifyInstanceProduced()");
+    }
+    Vector l;
+    synchronized (this) {
+      l = (Vector)m_Listeners.clone();
+    }
+    if (l.size() > 0) {
+      for(int i = 0; i < l.size(); i++) {
+	((InstanceListener)l.elementAt(i)).instanceProduced(e);
+      }
+      if (e.getID() == InstanceEvent.INSTANCE_AVAILABLE) {
+	m_OutputInstance = null;
+      }
+    }
+  }
+
+  public Instances outputFormat() throws Exception {
+    
+    if (m_OutputInstances == null) {
+      throw new Exception("No output format defined.");
+    }
+    return new Instances(m_OutputInstances,0);
+  }
+  
+  public Instance outputPeek() throws Exception {
+    
+    if ((m_OutputInstances == null)
+	|| (m_OutputInstance == null)) {
+      return null;
+    }
+    return (Instance)m_OutputInstance.copy();
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    
+    Object source = e.getSource();
+
+    if (source == m_StartBut) {
+      // load the arff file and send the instances out to the listener
+      if (m_LoaderThread == null) {
+	m_LoaderThread = new LoadThread(this);
+	m_LoaderThread.setPriority(Thread.MIN_PRIORITY);
+	m_LoaderThread.start();
+      } else {
+	m_LoaderThread = null;
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceProducer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceProducer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceProducer.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceProducer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+/** 
+ * An interface for objects capable of producing streams of instances.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface InstanceProducer {
+  
+  void addInstanceListener(InstanceListener ipl);
+  
+  void removeInstanceListener(InstanceListener ipl);
+
+  Instances outputFormat() throws Exception;
+  
+  Instance outputPeek() throws Exception;
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceSavePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceSavePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceSavePanel.java	(revision 29)
@@ -0,0 +1,147 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceSavePanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Label;
+import java.awt.Panel;
+import java.awt.TextField;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+
+/** 
+ * A bean that saves a stream of instances to a file.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class InstanceSavePanel
+  extends Panel
+  implements InstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -6061005366989295026L;
+  
+  private Label count_Lab;
+  private int m_Count;
+  private TextField arffFile_Tex;
+  private boolean b_Debug;
+  private PrintWriter outputWriter;
+
+  public void input(Instance instance) throws Exception {
+    
+    if (b_Debug)
+      System.err.println("InstanceSavePanel::input(" + instance +")");
+    m_Count++;
+    count_Lab.setText(""+m_Count+" instances");
+    if (outputWriter != null)
+      outputWriter.println(instance.toString());      
+  }
+  
+  public void inputFormat(Instances instanceInfo) {
+    
+    if (b_Debug)
+      System.err.println("InstanceSavePanel::inputFormat()\n"
+			 +instanceInfo.toString());
+    m_Count = 0;
+    count_Lab.setText(""+m_Count+" instances");
+    try {
+      outputWriter = new PrintWriter(new FileOutputStream(arffFile_Tex.getText()));
+      outputWriter.println(instanceInfo.toString());
+      if (b_Debug)
+	System.err.println("InstanceSavePanel::inputFormat() - written header");
+    } catch (Exception ex) {
+      outputWriter = null;
+      System.err.println("InstanceSavePanel::inputFormat(): "+ex.getMessage());
+    }
+  }
+
+  public void batchFinished() {
+    
+    if (b_Debug)
+      System.err.println("InstanceSavePanel::batchFinished()");
+    if (outputWriter != null)
+      outputWriter.close();
+  }
+
+  public InstanceSavePanel() {
+    
+    setLayout(new BorderLayout());
+    arffFile_Tex = new TextField("arffoutput.arff");
+    add("Center", arffFile_Tex);
+    count_Lab = new Label("0 instances");
+    add("East", count_Lab);
+    //    setSize(60,40);
+    setBackground(Color.lightGray);
+  }
+
+  public void setDebug(boolean debug) {
+    b_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    return b_Debug;
+  }
+
+  public void setArffFile(String newArffFile) {
+    arffFile_Tex.setText(newArffFile);
+  }
+  
+  public String getArffFile() {
+    return arffFile_Tex.getText();
+  }
+
+  public void instanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  inputFormat(a.outputFormat());
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  batchFinished();
+	  break;
+	default:
+	  System.err.println("InstanceSavePanel::instanceProduced() - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println("InstanceSavePanel::instanceProduced() - Unknown source object type");
+    }
+  }
+}
+
+  
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceTable.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceTable.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceTable.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceTable.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+
+/**
+ * A bean that takes a stream of instances and displays in a table.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class InstanceTable
+  extends JPanel
+  implements InstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2462533698100834803L;
+  
+  private JTable m_InstanceTable;
+  private boolean m_Debug;
+  private boolean m_Clear;
+  private String m_UpdateString;
+  private Instances m_Instances;
+
+  
+  public void inputFormat(Instances instanceInfo) {
+    
+    if (m_Debug) {
+      System.err.println("InstanceTable::inputFormat()\n"
+			 + instanceInfo.toString());
+    }
+    m_Instances = instanceInfo;
+  }
+
+  public void input(Instance instance) throws Exception {
+    
+    if (m_Debug) {
+      System.err.println("InstanceTable::input(" + instance +")");
+    }
+    m_Instances.add(instance);
+  }
+  
+  public void batchFinished() {
+    
+    TableModel newModel = new AbstractTableModel() {
+      private static final long serialVersionUID = 5447106383000555291L;
+      
+      public String getColumnName(int col) {
+	return m_Instances.attribute(col).name();
+      }
+      public Class getColumnClass(int col) {
+	return "".getClass();
+      }
+      public int getColumnCount() {
+	return m_Instances.numAttributes();
+      }
+      public int getRowCount() {
+	return m_Instances.numInstances();
+      }
+      public Object getValueAt(int row, int col) {
+	return new String(m_Instances.instance(row).toString(col));
+      }
+    };
+    m_InstanceTable.setModel(newModel);
+    if (m_Debug) {
+      System.err.println("InstanceTable::batchFinished()");
+    }
+  }
+
+  public InstanceTable() {
+    
+    setLayout(new BorderLayout());
+    m_InstanceTable = new JTable();
+    add("Center", new JScrollPane(m_InstanceTable));
+  }
+
+  public void setDebug(boolean debug) {
+    
+    m_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    
+    return m_Debug;
+  }
+
+  public void instanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  inputFormat(a.outputFormat());
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  batchFinished();
+	  break;
+	default:
+	  System.err.println("InstanceTable::instanceProduced()"
+			     + " - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println("InstanceTable::instanceProduced()"
+			 + " - Unknown source object type");
+    }
+  }
+}
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/streams/InstanceViewer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/InstanceViewer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/InstanceViewer.java	(revision 29)
@@ -0,0 +1,155 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    InstanceViewer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.streams;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * This is a very simple instance viewer - just displays the dataset as
+ * text output as it would be written to a file. A more complex viewer
+ * might be more spreadsheet-like
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class InstanceViewer
+  extends JPanel
+  implements InstanceListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -4925729441294121772L;
+  
+  private JTextArea m_OutputTex;
+  private boolean m_Debug;
+  private boolean m_Clear;
+  private String m_UpdateString;
+
+  private void updateOutput() {
+    
+    m_OutputTex.append(m_UpdateString);
+    m_UpdateString = "";
+  }
+
+  private void clearOutput() {
+    
+    m_UpdateString = "";
+    m_OutputTex.setText("");
+  }
+
+  public void inputFormat(Instances instanceInfo) {
+    
+    if (m_Debug) {
+      System.err.println("InstanceViewer::inputFormat()\n"
+			 + instanceInfo.toString());
+    }
+    if (m_Clear) {
+      clearOutput();
+    }
+    m_UpdateString += instanceInfo.toString();
+    updateOutput();
+  }
+
+  public void input(Instance instance) throws Exception {
+    
+    if (m_Debug) {
+      System.err.println("InstanceViewer::input(" + instance +")");
+    }
+    m_UpdateString += instance.toString() + "\n";
+    updateOutput();
+  }
+  
+  public void batchFinished() {
+    
+    updateOutput();
+    if (m_Debug) {
+      System.err.println("InstanceViewer::batchFinished()");
+    }
+  }
+
+  public InstanceViewer() {
+    
+    setLayout(new BorderLayout());
+    m_UpdateString = "";
+    setClearEachDataset(true);
+    m_OutputTex = new JTextArea(10,20);
+    m_OutputTex.setEditable(false);
+    add("Center", new JScrollPane(m_OutputTex));
+  }
+
+  public void setClearEachDataset(boolean clear) {
+    
+    m_Clear = clear;
+  }
+  
+  public boolean getClearEachDataset() {
+    
+    return m_Clear;
+  }
+  
+  public void setDebug(boolean debug) {
+    
+    m_Debug = debug;
+  }
+  
+  public boolean getDebug() {
+    
+    return m_Debug;
+  }
+
+  public void instanceProduced(InstanceEvent e) {
+    
+    Object source = e.getSource();
+    if (source instanceof InstanceProducer) { 
+      try {
+	InstanceProducer a = (InstanceProducer) source;
+	switch (e.getID()) {
+	case InstanceEvent.FORMAT_AVAILABLE:
+	  inputFormat(a.outputFormat());
+	  break;
+	case InstanceEvent.INSTANCE_AVAILABLE:
+	  input(a.outputPeek());
+	  break;
+	case InstanceEvent.BATCH_FINISHED:
+	  batchFinished();
+	  break;
+	default:
+	  System.err.println("InstanceViewer::instanceProduced()"
+			     + " - unknown event type");
+	  break;
+	}
+      } catch (Exception ex) {
+	System.err.println(ex.getMessage());
+      }
+    } else {
+      System.err.println("InstanceViewer::instanceProduced()"
+			 + " - Unknown source object type");
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/streams/SerialInstanceListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/streams/SerialInstanceListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/streams/SerialInstanceListener.java	(revision 29)
@@ -0,0 +1,37 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    SerialInstanceListener.java
+ *    Copyright (C) 1998  University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.streams;
+
+
+/**
+ * Defines an interface for objects able to produce two output streams of
+ * instances.
+ *
+ * @author Len Trigg (trigg@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public interface SerialInstanceListener extends java.util.EventListener {
+  
+  void secondInstanceProduced(InstanceEvent e);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Colors.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Colors.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Colors.java	(revision 29)
@@ -0,0 +1,590 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Colors.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * This class maintains a list that contains all the colornames from the 
+ * dotty standard and what color (in RGB) they represent
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public class Colors {
+  
+  /** The array with all the colors input */
+  public NamedColor[] m_cols = {
+		       new NamedColor("snow",255, 250, 250),
+		       new NamedColor("ghostwhite",248, 248, 255),
+		       new NamedColor("whitesmoke",245, 245, 245),
+		       new NamedColor("gainsboro",220, 220, 220),
+		       new NamedColor("floralwhite",255, 250, 240),
+		       new NamedColor("oldlace",253, 245, 230),
+		       new NamedColor("linen",250, 240, 230),
+		       new NamedColor("antiquewhite",250, 235, 215),
+		       new NamedColor("papayawhip",255, 239, 213),
+		       new NamedColor("blanchedalmond",255, 235, 205),
+		       new NamedColor("bisque",255, 228, 196),
+		       new NamedColor("peachpuff",255, 218, 185),
+		       new NamedColor("navajowhite",255, 222, 173),
+		       new NamedColor("moccasin",255, 228, 181),
+		       new NamedColor("cornsilk",255, 248, 220),
+		       new NamedColor("ivory",255, 255, 240),
+		       new NamedColor("lemonchiffon",255, 250, 205),
+		       new NamedColor("seashell",255, 245, 238),
+		       new NamedColor("honeydew",240, 255, 240),
+		       new NamedColor("mintcream",245, 255, 250),
+		       new NamedColor("azure",240, 255, 255),
+		       new NamedColor("aliceblue",240, 248, 255),
+		       new NamedColor("lavender",230, 230, 250),
+		       new NamedColor("lavenderblush",255, 240, 245),
+		       new NamedColor("mistyrose",255, 228, 225),
+		       new NamedColor("white",255, 255, 255),
+		       new NamedColor("black",  0,   0,   0),
+		       new NamedColor("darkslategray", 47,  79,  79),
+		       new NamedColor("dimgray",105, 105, 105),
+		       new NamedColor("slategray",112, 128, 144),
+		       new NamedColor("lightslategray",119, 136, 153),
+		       new NamedColor("gray",190, 190, 190),
+		       new NamedColor("lightgray",211, 211, 211),
+		       new NamedColor("midnightblue", 25,  25, 112),
+		       new NamedColor("navy",  0,   0, 128),
+		       new NamedColor("cornflowerblue",100, 149, 237),
+		       new NamedColor("darkslateblue", 72,  61, 139),
+		       new NamedColor("slateblue",106,  90, 205),
+		       new NamedColor("mediumslateblue",123, 104, 238),
+		       new NamedColor("lightslateblue",132, 112, 255),
+		       new NamedColor("mediumblue",  0,   0, 205),
+		       new NamedColor("royalblue", 65, 105, 225),
+		       new NamedColor("blue",  0,   0, 255),
+		       new NamedColor("dodgerblue", 30, 144, 255),
+		       new NamedColor("deepskyblue",  0, 191, 255),
+		       new NamedColor("skyblue",135, 206, 235),
+		       new NamedColor("lightskyblue",135, 206, 250),
+		       new NamedColor("steelblue", 70, 130, 180),
+		       new NamedColor("lightsteelblue",176, 196, 222),
+		       new NamedColor("lightblue",173, 216, 230),
+		       new NamedColor("powderblue",176, 224, 230),
+		       new NamedColor("paleturquoise",175, 238, 238),
+		       new NamedColor("darkturquoise",  0, 206, 209),
+		       new NamedColor("mediumturquoise", 72, 209, 204),
+		       new NamedColor("turquoise", 64, 224, 208),
+		       new NamedColor("cyan",  0, 255, 255),
+		       new NamedColor("lightcyan",224, 255, 255),
+		       new NamedColor("cadetblue", 95, 158, 160),
+		       new NamedColor("mediumaquamarine",102, 205, 170),
+		       new NamedColor("aquamarine",127, 255, 212),
+		       new NamedColor("darkgreen",  0, 100,   0),
+		       new NamedColor("darkolivegreen", 85, 107,  47),
+		       new NamedColor("darkseagreen",143, 188, 143),
+		       new NamedColor("seagreen", 46, 139,  87),
+		       new NamedColor("mediumseagreen", 60, 179, 113),
+		       new NamedColor("lightseagreen", 32, 178, 170),
+		       new NamedColor("palegreen",152, 251, 152),
+		       new NamedColor("springgreen",  0, 255, 127),
+		       new NamedColor("lawngreen",124, 252,   0),
+		       new NamedColor("green",  0, 255,   0),
+		       new NamedColor("chartreuse",127, 255,   0),
+		       new NamedColor("mediumspringgreen",  0, 250, 154),
+		       new NamedColor("greenyellow",173, 255,  47),
+		       new NamedColor("limegreen", 50, 205,  50),
+		       new NamedColor("yellowgreen",154, 205,  50),
+		       new NamedColor("forestgreen", 34, 139,  34),
+		       new NamedColor("olivedrab",107, 142,  35),
+		       new NamedColor("darkkhaki",189, 183, 107),
+		       new NamedColor("khaki",240, 230, 140),
+		       new NamedColor("palegoldenrod",238, 232, 170),
+		       new NamedColor("lightgoldenrodyellow",250, 250, 210),
+		       new NamedColor("lightyellow",255, 255, 224),
+		       new NamedColor("yellow",255, 255,   0),
+		       new NamedColor("gold",255, 215,   0),
+		       new NamedColor("lightgoldenrod",238, 221, 130),
+		       new NamedColor("goldenrod",218, 165,  32),
+		       new NamedColor("darkgoldenrod",184, 134,  11),
+		       new NamedColor("rosybrown",188, 143, 143),
+		       new NamedColor("indianred",205,  92,  92),
+		       new NamedColor("saddlebrown",139,  69,  19),
+		       new NamedColor("sienna",160,  82,  45),
+		       new NamedColor("peru",205, 133,  63),
+		       new NamedColor("burlywood",222, 184, 135),
+		       new NamedColor("beige",245, 245, 220),
+		       new NamedColor("wheat",245, 222, 179),
+		       new NamedColor("sandybrown",244, 164,  96),
+		       new NamedColor("tan",210, 180, 140),
+		       new NamedColor("chocolate",210, 105,  30),
+		       new NamedColor("firebrick",178,  34,  34),
+		       new NamedColor("brown",165,  42,  42),
+		       new NamedColor("darksalmon",233, 150, 122),
+		       new NamedColor("salmon",250, 128, 114),
+		       new NamedColor("lightsalmon",255, 160, 122),
+		       new NamedColor("orange",255, 165,   0),
+		       new NamedColor("darkorange",255, 140,   0),
+		       new NamedColor("coral",255, 127,  80),
+		       new NamedColor("lightcoral",240, 128, 128),
+		       new NamedColor("tomato",255,  99,  71),
+		       new NamedColor("orangered",255,  69,   0),
+		       new NamedColor("red",255,   0,   0),
+		       new NamedColor("hotpink",255, 105, 180),
+		       new NamedColor("deeppink",255,  20, 147),
+		       new NamedColor("pink",255, 192, 203),
+		       new NamedColor("lightpink",255, 182, 193),
+		       new NamedColor("palevioletred",219, 112, 147),
+		       new NamedColor("maroon",176,  48,  96),
+		       new NamedColor("mediumvioletred",199,  21, 133),
+		       new NamedColor("violetred",208,  32, 144),
+		       new NamedColor("magenta",255,   0, 255),
+		       new NamedColor("violet",238, 130, 238),
+		       new NamedColor("plum",221, 160, 221),
+		       new NamedColor("orchid",218, 112, 214),
+		       new NamedColor("mediumorchid",186,  85, 211),
+		       new NamedColor("darkorchid",153,  50, 204),
+		       new NamedColor("darkviolet",148,   0, 211),
+		       new NamedColor("blueviolet",138,  43, 226),
+		       new NamedColor("purple",160,  32, 240),
+		       new NamedColor("mediumpurple",147, 112, 219),
+		       new NamedColor("thistle",216, 191, 216),
+		       new NamedColor("snow1",255, 250, 250),
+		       new NamedColor("snow2",238, 233, 233),
+		       new NamedColor("snow3",205, 201, 201),
+		       new NamedColor("snow4",139, 137, 137),
+		       new NamedColor("seashell1",255, 245, 238),
+		       new NamedColor("seashell2",238, 229, 222),
+		       new NamedColor("seashell3",205, 197, 191),
+		       new NamedColor("seashell4",139, 134, 130),
+		       new NamedColor("antiquewhite1",255, 239, 219),
+		       new NamedColor("antiquewhite2",238, 223, 204),
+		       new NamedColor("antiquewhite3",205, 192, 176),
+		       new NamedColor("antiquewhite4",139, 131, 120),
+		       new NamedColor("bisque1",255, 228, 196),
+		       new NamedColor("bisque2",238, 213, 183),
+		       new NamedColor("bisque3",205, 183, 158),
+		       new NamedColor("bisque4",139, 125, 107),
+		       new NamedColor("peachpuff1",255, 218, 185),
+		       new NamedColor("peachpuff2",238, 203, 173),
+		       new NamedColor("peachpuff3",205, 175, 149),
+		       new NamedColor("peachpuff4",139, 119, 101),
+		       new NamedColor("navajowhite1",255, 222, 173),
+		       new NamedColor("navajowhite2",238, 207, 161),
+		       new NamedColor("navajowhite3",205, 179, 139),
+		       new NamedColor("navajowhite4",139, 121,	 94),
+		       new NamedColor("lemonchiffon1",255, 250, 205),
+		       new NamedColor("lemonchiffon2",238, 233, 191),
+		       new NamedColor("lemonchiffon3",205, 201, 165),
+		       new NamedColor("lemonchiffon4",139, 137, 112),
+		       new NamedColor("cornsilk1",255, 248, 220),
+		       new NamedColor("cornsilk2",238, 232, 205),
+		       new NamedColor("cornsilk3",205, 200, 177),
+		       new NamedColor("cornsilk4",139, 136, 120),
+		       new NamedColor("ivory1",255, 255, 240),
+		       new NamedColor("ivory2",238, 238, 224),
+		       new NamedColor("ivory3",205, 205, 193),
+		       new NamedColor("ivory4",139, 139, 131),
+		       new NamedColor("honeydew1",240, 255, 240),
+		       new NamedColor("honeydew2",224, 238, 224),
+		       new NamedColor("honeydew3",193, 205, 193),
+		       new NamedColor("honeydew4",131, 139, 131),
+		       new NamedColor("lavenderblush1",255, 240, 245),
+		       new NamedColor("lavenderblush2",238, 224, 229),
+		       new NamedColor("lavenderblush3",205, 193, 197),
+		       new NamedColor("lavenderblush4",139, 131, 134),
+		       new NamedColor("mistyrose1",255, 228, 225),
+		       new NamedColor("mistyrose2",238, 213, 210),
+		       new NamedColor("mistyrose3",205, 183, 181),
+		       new NamedColor("mistyrose4",139, 125, 123),
+		       new NamedColor("azure1",240, 255, 255),
+		       new NamedColor("azure2",224, 238, 238),
+		       new NamedColor("azure3",193, 205, 205),
+		       new NamedColor("azure4",131, 139, 139),
+		       new NamedColor("slateblue1",131, 111, 255),
+		       new NamedColor("slateblue2",122, 103, 238),
+		       new NamedColor("slateblue3",105,  89, 205),
+		       new NamedColor("slateblue4", 71,  60, 139),
+		       new NamedColor("royalblue1", 72, 118, 255),
+		       new NamedColor("royalblue2", 67, 110, 238),
+		       new NamedColor("royalblue3", 58,  95, 205),
+		       new NamedColor("royalblue4", 39,  64, 139),
+		       new NamedColor("blue1",  0,   0, 255),
+		       new NamedColor("blue2",  0,   0, 238),
+		       new NamedColor("blue3",  0,   0, 205),
+		       new NamedColor("blue4",  0,   0, 139),
+		       new NamedColor("dodgerblue1", 30, 144, 255),
+		       new NamedColor("dodgerblue2", 28, 134, 238),
+		       new NamedColor("dodgerblue3", 24, 116, 205),
+		       new NamedColor("dodgerblue4", 16,  78, 139),
+		       new NamedColor("steelblue1", 99, 184, 255),
+		       new NamedColor("steelblue2", 92, 172, 238),
+		       new NamedColor("steelblue3", 79, 148, 205),
+		       new NamedColor("steelblue4", 54, 100, 139),
+		       new NamedColor("deepskyblue1",  0, 191, 255),
+		       new NamedColor("deepskyblue2",  0, 178, 238),
+		       new NamedColor("deepskyblue3",  0, 154, 205),
+		       new NamedColor("deepskyblue4",  0, 104, 139),
+		       new NamedColor("skyblue1",135, 206, 255),
+		       new NamedColor("skyblue2",126, 192, 238),
+		       new NamedColor("skyblue3",108, 166, 205),
+		       new NamedColor("skyblue4", 74, 112, 139),
+		       new NamedColor("lightskyblue1",176, 226, 255),
+		       new NamedColor("lightskyblue2",164, 211, 238),
+		       new NamedColor("lightskyblue3",141, 182, 205),
+		       new NamedColor("lightskyblue4", 96, 123, 139),
+		       new NamedColor("slategray1",198, 226, 255),
+		       new NamedColor("slategray2",185, 211, 238),
+		       new NamedColor("slategray3",159, 182, 205),
+		       new NamedColor("slategray4",108, 123, 139),
+		       new NamedColor("lightsteelblue1",202, 225, 255),
+		       new NamedColor("lightsteelblue2",188, 210, 238),
+		       new NamedColor("lightsteelblue3",162, 181, 205),
+		       new NamedColor("lightsteelblue4",110, 123, 139),
+		       new NamedColor("lightblue1",191, 239, 255),
+		       new NamedColor("lightblue2",178, 223, 238),
+		       new NamedColor("lightblue3",154, 192, 205),
+		       new NamedColor("lightblue4",104, 131, 139),
+		       new NamedColor("lightcyan1",224, 255, 255),
+		       new NamedColor("lightcyan2",209, 238, 238),
+		       new NamedColor("lightcyan3",180, 205, 205),
+		       new NamedColor("lightcyan4",122, 139, 139),
+		       new NamedColor("paleturquoise1",187, 255, 255),
+		       new NamedColor("paleturquoise2",174, 238, 238),
+		       new NamedColor("paleturquoise3",150, 205, 205),
+		       new NamedColor("paleturquoise4",102, 139, 139),
+		       new NamedColor("cadetblue1",152, 245, 255),
+		       new NamedColor("cadetblue2",142, 229, 238),
+		       new NamedColor("cadetblue3",122, 197, 205),
+		       new NamedColor("cadetblue4", 83, 134, 139),
+		       new NamedColor("turquoise1",  0, 245, 255),
+		       new NamedColor("turquoise2",  0, 229, 238),
+		       new NamedColor("turquoise3",  0, 197, 205),
+		       new NamedColor("turquoise4",  0, 134, 139),
+		       new NamedColor("cyan1",  0, 255, 255),
+		       new NamedColor("cyan2",  0, 238, 238),
+		       new NamedColor("cyan3",  0, 205, 205),
+		       new NamedColor("cyan4",  0, 139, 139),
+		       new NamedColor("darkslategray1",151, 255, 255),
+		       new NamedColor("darkslategray2",141, 238, 238),
+		       new NamedColor("darkslategray3",121, 205, 205),
+		       new NamedColor("darkslategray4", 82, 139, 139),
+		       new NamedColor("aquamarine1",127, 255, 212),
+		       new NamedColor("aquamarine2",118, 238, 198),
+		       new NamedColor("aquamarine3",102, 205, 170),
+		       new NamedColor("aquamarine4", 69, 139, 116),
+		       new NamedColor("darkseagreen1",193, 255, 193),
+		       new NamedColor("darkseagreen2",180, 238, 180),
+		       new NamedColor("darkseagreen3",155, 205, 155),
+		       new NamedColor("darkseagreen4",105, 139, 105),
+		       new NamedColor("seagreen1", 84, 255, 159),
+		       new NamedColor("seagreen2", 78, 238, 148),
+		       new NamedColor("seagreen3", 67, 205, 128),
+		       new NamedColor("seagreen4", 46, 139,	 87),
+		       new NamedColor("palegreen1",154, 255, 154),
+		       new NamedColor("palegreen2",144, 238, 144),
+		       new NamedColor("palegreen3",124, 205, 124),
+		       new NamedColor("palegreen4", 84, 139,	 84),
+		       new NamedColor("springgreen1",  0, 255, 127),
+		       new NamedColor("springgreen2",  0, 238, 118),
+		       new NamedColor("springgreen3",  0, 205, 102),
+		       new NamedColor("springgreen4",  0, 139,	 69),
+		       new NamedColor("green1",  0, 255,	  0),
+		       new NamedColor("green2",  0, 238,	  0),
+		       new NamedColor("green3",  0, 205,	  0),
+		       new NamedColor("green4",  0, 139,	  0),
+		       new NamedColor("chartreuse1",127, 255,	  0),
+		       new NamedColor("chartreuse2",118, 238,	  0),
+		       new NamedColor("chartreuse3",102, 205,	  0),
+		       new NamedColor("chartreuse4", 69, 139,	  0),
+		       new NamedColor("olivedrab1",192, 255,	 62),
+		       new NamedColor("olivedrab2",179, 238,	 58),
+		       new NamedColor("olivedrab3",154, 205,	 50),
+		       new NamedColor("olivedrab4",105, 139,	 34),
+		       new NamedColor("darkolivegreen1",202, 255, 112),
+		       new NamedColor("darkolivegreen2",188, 238, 104),
+		       new NamedColor("darkolivegreen3",162, 205,	 90),
+		       new NamedColor("darkolivegreen4",110, 139,	 61),
+		       new NamedColor("khaki1",255, 246, 143),
+		       new NamedColor("khaki2",238, 230, 133),
+		       new NamedColor("khaki3",205, 198, 115),
+		       new NamedColor("khaki4",139, 134,	 78),
+		       new NamedColor("lightgoldenrod1",255, 236, 139),
+		       new NamedColor("lightgoldenrod2",238, 220, 130),
+		       new NamedColor("lightgoldenrod3",205, 190, 112),
+		       new NamedColor("lightgoldenrod4",139, 129,	 76),
+		       new NamedColor("lightyellow1",255, 255, 224),
+		       new NamedColor("lightyellow2",238, 238, 209),
+		       new NamedColor("lightyellow3",205, 205, 180),
+		       new NamedColor("lightyellow4",139, 139, 122),
+		       new NamedColor("yellow1",255, 255,	  0),
+		       new NamedColor("yellow2",238, 238,	  0),
+		       new NamedColor("yellow3",205, 205,	  0),
+		       new NamedColor("yellow4",139, 139,	  0),
+		       new NamedColor("gold1",255, 215,	  0),
+		       new NamedColor("gold2",238, 201,	  0),
+		       new NamedColor("gold3",205, 173,	  0),
+		       new NamedColor("gold4",139, 117,	  0),
+		       new NamedColor("goldenrod1",255, 193,	 37),
+		       new NamedColor("goldenrod2",238, 180,	 34),
+		       new NamedColor("goldenrod3",205, 155,	 29),
+		       new NamedColor("goldenrod4",139, 105,	 20),
+		       new NamedColor("darkgoldenrod1",255, 185,	 15),
+		       new NamedColor("darkgoldenrod2",238, 173,	 14),
+		       new NamedColor("darkgoldenrod3",205, 149,	 12),
+		       new NamedColor("darkgoldenrod4",139, 101,	  8),
+		       new NamedColor("rosybrown1",255, 193, 193),
+		       new NamedColor("rosybrown2",238, 180, 180),
+		       new NamedColor("rosybrown3",205, 155, 155),
+		       new NamedColor("rosybrown4",139, 105, 105),
+		       new NamedColor("indianred1",255, 106, 106),
+		       new NamedColor("indianred2",238,  99,	 99),
+		       new NamedColor("indianred3",205,  85,	 85),
+		       new NamedColor("indianred4",139,  58,	 58),
+		       new NamedColor("sienna1",255, 130,	 71),
+		       new NamedColor("sienna2",238, 121,	 66),
+		       new NamedColor("sienna3",205, 104,	 57),
+		       new NamedColor("sienna4",139,  71,	 38),
+		       new NamedColor("burlywood1",255, 211, 155),
+		       new NamedColor("burlywood2",238, 197, 145),
+		       new NamedColor("burlywood3",205, 170, 125),
+		       new NamedColor("burlywood4",139, 115,	 85),
+		       new NamedColor("wheat1",255, 231, 186),
+		       new NamedColor("wheat2",238, 216, 174),
+		       new NamedColor("wheat3",205, 186, 150),
+		       new NamedColor("wheat4",139, 126, 102),
+		       new NamedColor("tan1",255, 165,	 79),
+		       new NamedColor("tan2",238, 154,	 73),
+		       new NamedColor("tan3",205, 133,	 63),
+		       new NamedColor("tan4",139,  90,	 43),
+		       new NamedColor("chocolate1",255, 127,	 36),
+		       new NamedColor("chocolate2",238, 118,	 33),
+		       new NamedColor("chocolate3",205, 102,	 29),
+		       new NamedColor("chocolate4",139,  69,	 19),
+		       new NamedColor("firebrick1",255,  48,	 48),
+		       new NamedColor("firebrick2",238,  44,	 44),
+		       new NamedColor("firebrick3",205,  38,	 38),
+		       new NamedColor("firebrick4",139,  26,	 26),
+		       new NamedColor("brown1",255,  64,	 64),
+		       new NamedColor("brown2",238,  59,	 59),
+		       new NamedColor("brown3",205,  51,	 51),
+		       new NamedColor("brown4",139,  35,	 35),
+		       new NamedColor("salmon1",255, 140, 105),
+		       new NamedColor("salmon2",238, 130,	 98),
+		       new NamedColor("salmon3",205, 112,	 84),
+		       new NamedColor("salmon4",139,  76,	 57),
+		       new NamedColor("lightsalmon1",255, 160, 122),
+		       new NamedColor("lightsalmon2",238, 149, 114),
+		       new NamedColor("lightsalmon3",205, 129,	 98),
+		       new NamedColor("lightsalmon4",139,  87,	 66),
+		       new NamedColor("orange1",255, 165,	  0),
+		       new NamedColor("orange2",238, 154,	  0),
+		       new NamedColor("orange3",205, 133,	  0),
+		       new NamedColor("orange4",139,  90,	  0),
+		       new NamedColor("darkorange1",255, 127,	  0),
+		       new NamedColor("darkorange2",238, 118,	  0),
+		       new NamedColor("darkorange3",205, 102,	  0),
+		       new NamedColor("darkorange4",139,  69,	  0),
+		       new NamedColor("coral1",255, 114,	 86),
+		       new NamedColor("coral2",238, 106,	 80),
+		       new NamedColor("coral3",205,  91,	 69),
+		       new NamedColor("coral4",139,  62,	 47),
+		       new NamedColor("tomato1",255,  99,	 71),
+		       new NamedColor("tomato2",238,  92,	 66),
+		       new NamedColor("tomato3",205,  79,	 57),
+		       new NamedColor("tomato4",139,  54,	 38),
+		       new NamedColor("orangered1",255,  69,	  0),
+		       new NamedColor("orangered2",238,  64,	  0),
+		       new NamedColor("orangered3",205,  55,	  0),
+		       new NamedColor("orangered4",139,  37,	  0),
+		       new NamedColor("red1",255,   0,	  0),
+		       new NamedColor("red2",238,   0,	  0),
+		       new NamedColor("red3",205,   0,	  0),
+		       new NamedColor("red4",139,   0,	  0),
+		       new NamedColor("deeppink1",255,  20, 147),
+		       new NamedColor("deeppink2",238,  18, 137),
+		       new NamedColor("deeppink3",205,  16, 118),
+		       new NamedColor("deeppink4",139,  10,	 80),
+		       new NamedColor("hotpink1",255, 110, 180),
+		       new NamedColor("hotpink2",238, 106, 167),
+		       new NamedColor("hotpink3",205,  96, 144),
+		       new NamedColor("hotpink4",139,  58,  98),
+		       new NamedColor("pink1",255, 181, 197),
+		       new NamedColor("pink2",238, 169, 184),
+		       new NamedColor("pink3",205, 145, 158),
+		       new NamedColor("pink4",139,  99, 108),
+		       new NamedColor("lightpink1",255, 174, 185),
+		       new NamedColor("lightpink2",238, 162, 173),
+		       new NamedColor("lightpink3",205, 140, 149),
+		       new NamedColor("lightpink4",139,  95, 101),
+		       new NamedColor("palevioletred1",255, 130, 171),
+		       new NamedColor("palevioletred2  ",238, 121, 159),
+		       new NamedColor("palevioletred3",205, 104, 137),
+		       new NamedColor("palevioletred4",139,  71,	 93),
+		       new NamedColor("maroon1",255,  52, 179),
+		       new NamedColor("maroon2",238,  48, 167),
+		       new NamedColor("maroon3",205,  41, 144),
+		       new NamedColor("maroon4",139,  28,	 98),
+		       new NamedColor("violetred1",255,  62, 150),
+		       new NamedColor("violetred2",238,  58, 140),
+		       new NamedColor("violetred3",205,  50, 120),
+		       new NamedColor("violetred4",139,  34,	 82),
+		       new NamedColor("magenta1",255,   0, 255),
+		       new NamedColor("magenta2",238,   0, 238),
+		       new NamedColor("magenta3",205,   0, 205),
+		       new NamedColor("magenta4",139,   0, 139),
+		       new NamedColor("orchid1",255, 131, 250),
+		       new NamedColor("orchid2",238, 122, 233),
+		       new NamedColor("orchid3",205, 105, 201),
+		       new NamedColor("orchid4",139,  71, 137),
+		       new NamedColor("plum1",255, 187, 255),
+		       new NamedColor("plum2",238, 174, 238),
+		       new NamedColor("plum3",205, 150, 205),
+		       new NamedColor("plum4",139, 102, 139),
+		       new NamedColor("mediumorchid1",224, 102, 255),
+		       new NamedColor("mediumorchid2",209,  95, 238),
+		       new NamedColor("mediumorchid3",180,  82, 205),
+		       new NamedColor("mediumorchid4",122,  55, 139),
+		       new NamedColor("darkorchid1",191,  62, 255),
+		       new NamedColor("darkorchid2",178,  58, 238),
+		       new NamedColor("darkorchid3",154,  50, 205),
+		       new NamedColor("darkorchid4",104,  34, 139),
+		       new NamedColor("purple1",155,  48, 255),
+		       new NamedColor("purple2",145,  44, 238),
+		       new NamedColor("purple3",125,  38, 205),
+		       new NamedColor("purple4", 85,  26, 139),
+		       new NamedColor("mediumpurple1",171, 130, 255),
+		       new NamedColor("mediumpurple2",159, 121, 238),
+		       new NamedColor("mediumpurple3",137, 104, 205),
+		       new NamedColor("mediumpurple4", 93,  71, 139),
+		       new NamedColor("thistle1",255, 225, 255),
+		       new NamedColor("thistle2",238, 210, 238),
+		       new NamedColor("thistle3",205, 181, 205),
+		       new NamedColor("thistle4",139, 123, 139),
+		       new NamedColor("gray0",  0,   0,   0),
+		       new NamedColor("gray1",  3,   3,   3),
+		       new NamedColor("gray2",  5,   5,   5),
+		       new NamedColor("gray3",  8,   8,   8),
+		       new NamedColor("gray4", 10,  10,  10),
+		       new NamedColor("gray5", 13,  13,  13),
+		       new NamedColor("gray6", 15,  15,  15),
+		       new NamedColor("gray7", 18,  18,  18),
+		       new NamedColor("gray8", 20,  20,  20),
+		       new NamedColor("gray9", 23,  23,  23),
+		       new NamedColor("gray10", 26,  26,  26),
+		       new NamedColor("gray11", 28,  28,  28),
+		       new NamedColor("gray12", 31,  31,  31),
+		       new NamedColor("gray13", 33,  33,  33),
+		       new NamedColor("gray14", 36,  36,  36),
+		       new NamedColor("gray15", 38,  38,  38),
+		       new NamedColor("gray16", 41,  41,  41),
+		       new NamedColor("gray17", 43,  43,  43),
+		       new NamedColor("gray18", 46,  46,  46),
+		       new NamedColor("gray19", 48,  48,  48),
+		       new NamedColor("gray20", 51,  51,  51),
+		       new NamedColor("gray21", 54,  54,  54),
+		       new NamedColor("gray22", 56,  56,  56),
+		       new NamedColor("gray23", 59,  59,  59),
+		       new NamedColor("gray24", 61,  61,  61),
+		       new NamedColor("gray25", 64,  64,  64),
+		       new NamedColor("gray26", 66,  66,  66),
+		       new NamedColor("gray27", 69,  69,  69),
+		       new NamedColor("gray28", 71,  71,  71),
+		       new NamedColor("gray29", 74,  74,  74),
+		       new NamedColor("gray30", 77,  77,  77),
+		       new NamedColor("gray31", 79,  79,  79),
+		       new NamedColor("gray32", 82,  82,  82),
+		       new NamedColor("gray33", 84,  84,  84),
+		       new NamedColor("gray34", 87,  87,  87),
+		       new NamedColor("gray35", 89,  89,  89),
+		       new NamedColor("gray36", 92,  92,  92),
+		       new NamedColor("gray37", 94,  94,  94),
+		       new NamedColor("gray38", 97,  97,  97),
+		       new NamedColor("gray39", 99,  99,  99),
+		       new NamedColor("gray40",102, 102, 102),
+		       new NamedColor("gray41",105, 105, 105),
+		       new NamedColor("gray42",107, 107, 107),
+		       new NamedColor("gray43",110, 110, 110),
+		       new NamedColor("gray44",112, 112, 112),
+		       new NamedColor("gray45",115, 115, 115),
+		       new NamedColor("gray46",117, 117, 117),
+		       new NamedColor("gray47",120, 120, 120),
+		       new NamedColor("gray48",122, 122, 122),
+		       new NamedColor("gray49",125, 125, 125),
+		       new NamedColor("gray50",127, 127, 127),
+		       new NamedColor("gray51",130, 130, 130),
+		       new NamedColor("gray52",133, 133, 133),
+		       new NamedColor("gray53",135, 135, 135),
+		       new NamedColor("gray54",138, 138, 138),
+		       new NamedColor("gray55",140, 140, 140),
+		       new NamedColor("gray56",143, 143, 143),
+		       new NamedColor("gray57",145, 145, 145),
+		       new NamedColor("gray58",148, 148, 148),
+		       new NamedColor("gray59",150, 150, 150),
+		       new NamedColor("gray60",153, 153, 153),
+		       new NamedColor("gray61",156, 156, 156),
+		       new NamedColor("gray62",158, 158, 158),
+		       new NamedColor("gray63",161, 161, 161),
+		       new NamedColor("gray64",163, 163, 163),
+		       new NamedColor("gray65",166, 166, 166),
+		       new NamedColor("gray66",168, 168, 168),
+		       new NamedColor("gray67",171, 171, 171),
+		       new NamedColor("gray68",173, 173, 173),
+		       new NamedColor("gray69",176, 176, 176),
+		       new NamedColor("gray70",179, 179, 179),
+		       new NamedColor("gray71",181, 181, 181),
+		       new NamedColor("gray72",184, 184, 184),
+		       new NamedColor("gray73",186, 186, 186),
+		       new NamedColor("gray74",189, 189, 189),
+		       new NamedColor("gray75",191, 191, 191),
+		       new NamedColor("gray76",194, 194, 194),
+		       new NamedColor("gray77",196, 196, 196),
+		       new NamedColor("gray78",199, 199, 199),
+		       new NamedColor("gray79",201, 201, 201),
+		       new NamedColor("gray80",204, 204, 204),
+		       new NamedColor("gray81",207, 207, 207),
+		       new NamedColor("gray82",209, 209, 209),
+		       new NamedColor("gray83",212, 212, 212),
+		       new NamedColor("gray84",214, 214, 214),
+		       new NamedColor("gray85",217, 217, 217),
+		       new NamedColor("gray86",219, 219, 219),
+		       new NamedColor("gray87",222, 222, 222),
+		       new NamedColor("gray88",224, 224, 224),
+		       new NamedColor("gray89",227, 227, 227),
+		       new NamedColor("gray90",229, 229, 229),
+		       new NamedColor("gray91",232, 232, 232),
+		       new NamedColor("gray92",235, 235, 235),
+		       new NamedColor("gray93",237, 237, 237),
+		       new NamedColor("gray94",240, 240, 240),
+		       new NamedColor("gray95",242, 242, 242),
+		       new NamedColor("gray96",245, 245, 245),
+		       new NamedColor("gray97",247, 247, 247),
+		       new NamedColor("gray98",250, 250, 250),
+		       new NamedColor("gray99",252, 252, 252),
+		       new NamedColor("gray100",255, 255, 255),
+		       new NamedColor("darkgray",169, 169, 169),
+		       new NamedColor("darkblue",0  ,   0, 139),
+		       new NamedColor("darkcyan",0  , 139, 139),
+		       new NamedColor("darkmagenta",139,   0, 139),
+		       new NamedColor("darkred",139,   0,   0),
+		       new NamedColor("lightgreen",144, 238, 144),
+
+
+  };
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Edge.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Edge.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Edge.java	(revision 29)
@@ -0,0 +1,230 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Edge.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import java.util.*;
+import java.awt.*;
+
+
+/**
+ * This class is used in conjunction with the Node class to form a tree 
+ * structure.
+ * This in particular contains information about an edges in the tree.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $ 
+ */
+public class Edge {
+  /** The text caption for the edge. */
+  private String m_label;
+
+  /** The ID string of the parent Node of this edge (used for consrtuction 
+   * purposes). */
+  private String m_rsource;
+
+  /** The ID string of the child Node of this edge (used for construction 
+   * purposes). */
+  private String m_rtarget;
+
+  /** The parent Node of this edge. */
+  private Node m_source;
+
+  /** The child Node of this edge. */
+  private Node m_target;
+
+  /** The label broken up into lines. */
+  private Vector m_lines;
+
+  /** 
+   * This constructs an Edge with the specified label 
+   * and parent , child serial tags.
+   *
+   * @param label The text caption for the edge.
+   * @param source The ID string for this edges parent.
+   * @param target The ID string for this edges child.
+   */
+  public Edge(String label,String source,String target) {
+    m_label = label;
+    m_rsource = source;
+    m_rtarget = target;
+    m_lines = new Vector(3,2);
+    breakupLabel();
+  }
+
+  
+  /**
+   * Get the value of label.
+   *
+   * @return Value of label.
+   */
+  public String getLabel() {
+    
+    return m_label;
+  }
+  
+  /**
+   * This function is called to break the label of the edge up in to 
+   * seperate lines
+   */
+  private void breakupLabel() {
+    int prev = 0,noa;
+    for (noa = 0;noa < m_label.length();noa++) {
+      if (m_label.charAt(noa) == '\n') {
+	m_lines.addElement(m_label.substring(prev,noa));
+	prev = noa+1;
+      }
+    }
+    m_lines.addElement(m_label.substring(prev,noa));
+  }
+  
+  /**
+   * This will calculate how large a rectangle using the <i>FontMetrics</i>
+   * passed that the lines of the label will take up
+   *
+   * @param f The size information for a particular Font
+   * @return A Dimension containing the size and width of the text
+   */
+  public Dimension stringSize(FontMetrics f) {
+    Dimension d = new Dimension();
+    int old = 0;
+    String s;
+    int noa = 0;
+    while ((s = getLine(noa)) != null) {
+      noa++;
+      old = f.stringWidth(s);
+      
+      if (old > d.width) {
+	d.width = old;
+      }
+    }
+    d.height = noa * f.getHeight();
+    return d;
+  }
+ 
+  /**
+   * Returns line number <i>n</i>
+   *
+   * @param n The number of the line requested
+   * @return The string for the line number or NULL if it didn't exist
+   */ 
+  public String getLine(int n) {
+    if (n < m_lines.size()) {
+      return (String)m_lines.elementAt(n);
+    }
+    else {
+      return null;
+    }
+  }
+  
+  
+  /**
+   * Get the value of rsource.
+   *
+   * @return Value of rsource.
+   */
+  public String getRsource() {
+    
+    return m_rsource;
+  }
+  
+  /**
+   * Set the value of rsource.
+   *
+   * @param v  Value to assign to rsource.
+   */
+  public void setRsource(String v) {
+    
+    m_rsource = v;
+  }
+  
+  
+  
+  /**
+   * Get the value of rtarget.
+   *
+   * @return Value of rtarget.
+   */
+  public String getRtarget() {
+    
+    return m_rtarget;
+  }
+  
+  /**
+   * Set the value of rtarget.
+   *
+   * @param v Value to assign to rtarget.
+   */
+  public void setRtarget(String v) {
+    
+    m_rtarget = v;
+  }
+  
+  /**
+   * Get the value of source.
+   *
+   * @return Value of source.
+   */
+  public Node getSource() {
+    
+    return m_source;
+  }
+  
+  /**
+   * Set the value of source. And then call v.addChild to add the edge to 
+   * the Node.
+   *
+   * @param v  Value to assign to source.
+   */
+  public void setSource(Node v) {
+    
+    m_source = v;
+    v.addChild(this);
+  }
+  
+  /**
+   * Get the value of target.
+   *
+   * @return Value of target.
+   */
+  public Node getTarget() {
+    
+    return m_target;
+  }
+  
+  /**
+   * Set the value of target. And then call v.addParent to add the edge to 
+   * the Node.
+   *
+   * @param v Value to assign to target.
+   */
+  public void setTarget(Node v) {
+    
+    m_target = v;
+    v.setParent(this);
+  }
+}
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NamedColor.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NamedColor.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NamedColor.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NamedColor.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.treevisualizer;
+
+import java.awt.*;
+
+/**
+ * This class contains a color name and the rgb values of that color
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class NamedColor {
+
+  /** The name of the color */
+  public String m_name;
+
+  /** The actual color object */
+  public Color m_col;
+  
+  /**
+   * @param n The name of the color.
+   * @param r The red component of the color.
+   * @param g The green component of the color.
+   * @param b The blue component of the color.
+   */   
+  public NamedColor(String n,int r,int g,int b) {
+    m_name = n;
+    m_col = new Color(r,g,b);
+  }
+}
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Node.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Node.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/Node.java	(revision 29)
@@ -0,0 +1,626 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Node.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import java.awt.*;
+import java.util.*;
+import java.io.*;
+import weka.core.Instances;
+
+
+//this is a node structure that to be useful needs the Edge class as well
+
+//note i have done an unintentional naughty thing
+//getHeight() returns the pixel height of the node
+//getHeight(Node,int) returns how many levels down the tree goes
+//setHeight(int) is associated to the prior
+
+/**
+ * This class records all the data about a particular node for displaying.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class Node {
+  
+  /** The fill mode for the node (not in use). */
+  private int m_backstyle;       //how the back color will fill
+
+  /** The shape of the node. */
+  private int m_shape;
+
+  /** The color of the node. */
+  private Color m_color;
+
+  /** the text for the node. */
+  private String m_label;
+
+  /** the text broken up into lines */
+  private Vector m_lines;
+
+  //the coord of the left side .note all coords are
+  //between 1-0 for scaling per Stuart's suggestion
+  /** The center of the node (between 0 and 1). */
+  private double m_center;       //coord of the center . main x value used
+
+  /** The top of the node (between 0 and 1). */
+  private double m_top;          //main y coord to go by
+     
+  /** true if this nodes descendants are visible (not in use currently). */
+  private boolean m_cVisible;   //whether it's descendants are visible
+
+  /** true if this node is visible (not currently in use). */
+  private boolean m_visible;     //whether it's visible
+
+  /** true if this is the top of the tree. ie has no parent */
+  private boolean m_root;     //whether it is anscestor to all i.e top of tree 
+
+  /** An array containing references to all the parent edges 
+   * (only 1 currently). */
+  private Vector m_parent;      //the edge to its parent edges(or itself 
+
+  //if true root)
+  /** An array containing references to all the child edges. */
+  private Vector m_children;     //a vector list of edges to the nodes children
+
+  /** The ID string for this node (used for construction purposes) */
+  private String m_refer;
+
+  /** A String containing extra information about the node. */
+  private String m_data;
+
+  /**
+   * An Instances variable generated from the data.
+   * Note that if this exists then the string shall be NULL to save space.
+   */
+  private Instances m_theData;
+
+  /**
+   * This will setup all the values of the node except for its top and center.
+   *
+   * @param label The text for the node.
+   * @param refer The ID string for this node.
+   * @param backstyle The backstyle of this node.
+   * @param shape The shape of this node.
+   * @param color The color of this node.
+   */
+  public Node(String label,String refer,int backstyle,int shape,
+	      Color color,String d) {
+    m_label = label;
+    m_backstyle = backstyle;
+    m_shape = shape;
+    m_color = color;
+    m_refer = refer;
+   
+    m_center = 0;
+    m_top = 0;
+
+    m_cVisible = true;
+    m_visible = true;
+    m_root = false;
+    m_parent = new Vector(1,1);
+    m_children = new Vector(20,10);
+    m_lines = new Vector(4,2);
+    breakupLabel();
+    m_data = d;
+    m_theData = null;
+  } 	
+  
+  /**
+   * This will return the Instances object related to this node.
+   * If it has not been allocated then that will be done also.
+   *
+   * @return The Instances object.
+   */
+  public Instances getInstances() {
+    if (m_theData == null && m_data != null) {
+      try {
+	m_theData = new Instances(new StringReader(m_data));
+      } catch(Exception e) {
+	System.out.println("Error : " + e);
+      }
+      m_data = null;
+    }
+    return m_theData;
+  }
+
+  /**
+   * Get If this node's childs are visible.
+   *
+   * @return True if the childs are visible.
+   */
+  public boolean getCVisible() {
+    return m_cVisible;
+  }
+
+  /** 
+   * Recursively goes through the tree and sets all the children and the 
+   * parent to visible.
+   *
+   * @param r The current node to set visible.
+   */
+  private void childVis(Node r) {
+    Edge e;
+    r.setVisible(true);
+    if (r.getCVisible()) {
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	childVis(e.getTarget());
+      }
+    }
+  }
+
+  /**
+   * Sets all the children of this node either to visible or invisible
+   *
+   * @param v True if the children are to be visible
+   */
+  public void setCVisible(boolean v) {
+    m_cVisible = v;
+    if (v) {
+      childVis(this);
+    }
+    else if (!v) {
+      childInv(this);
+    }
+  }
+  
+  /**
+   * Recursively goes through the tree and sets all the children to invisible,
+   * Not the parent though.
+   *
+   * @param r The current node from whom the children are gonna be set 
+   * invisible.
+   */
+  private void childInv(Node r) {
+    Edge e;
+    Node s;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      s = e.getTarget();
+      s.setVisible(false);
+      childInv(s);
+    }
+  }
+  
+
+ 
+  
+ 
+  
+
+
+  /**
+   * Get the value of refer.
+   *
+   * @return Value of refer.
+   */
+  public String getRefer() {
+    
+    return m_refer;
+  }
+  
+  /**
+   * Set the value of refer.
+   *
+   * @param v  Value to assign to refer.
+   */
+  public void setRefer(String v) {
+    
+    m_refer = v;
+  }
+  
+  
+  
+  /**
+   * Get the value of shape.
+   *
+   * @return Value of shape.
+   */
+  public int getShape() {
+    
+    return m_shape;
+  }
+  
+  /**
+   * Set the value of shape.
+   *
+   * @param v  Value to assign to shape.
+   */
+  public void setShape(int v) {
+    
+    m_shape = v;
+  }
+  
+  
+  /**
+   * Get the value of color.
+   *
+   * @return Value of color.
+   */
+  public Color getColor() {
+    
+    return m_color;
+  }
+  
+  /**
+   * Set the value of color.
+   *
+   * @param v  Value to assign to color.
+   */
+  public void setColor(Color v) {
+    
+    m_color = v;
+  }
+  
+  
+  /**
+   * Get the value of label.
+   *
+   * @return Value of label.
+   */
+  public String getLabel() {
+    
+    return m_label;
+  }
+  
+  /**
+   * This Will break the node's text up into lines.
+   *
+   */
+  private void breakupLabel() {
+    int prev = 0,noa;
+    for (noa = 0;noa < m_label.length();noa++) {
+      if (m_label.charAt(noa) == '\n') {
+	m_lines.addElement(m_label.substring(prev,noa));
+	prev = noa+1;
+      }
+    }
+    m_lines.addElement(m_label.substring(prev,noa));
+    
+  }
+  
+  /**
+   * This will return the width and height of the rectangle that the text 
+   * will fit into.
+   *
+   * @param f The size info for the Font.
+   * @return A Dimension containing the size of the text.
+   */
+  public Dimension stringSize(FontMetrics f) {
+    Dimension d = new Dimension();
+    int old = 0;
+    String s;
+    int noa = 0;
+    while ((s = getLine(noa)) != null) {
+      noa++;
+      old = f.stringWidth(s);
+      
+      if (old > d.width) {
+	d.width = old;
+      }
+    }
+    d.height = noa * f.getHeight();
+    return d;
+    
+  }
+
+  /**
+   * Returns the text String for the specfied line.
+   *
+   * @param n The line wanted.
+   * @return The String corresponding to that line.
+   */
+  public String getLine(int n) {
+    if (n < m_lines.size()) {
+      return (String)m_lines.elementAt(n);
+    }
+    else {
+      return null;
+    }
+  }
+  
+  
+  
+
+  
+ 
+  
+
+  
+  
+  /**
+   * Get the value of center.
+   *
+   * @return Value of center.
+   */
+  public double getCenter() {
+    
+    return m_center;
+  }
+  
+  /**
+   * Set the value of center.
+   *
+   * @param v  Value to assign to center.
+   */
+  public void setCenter(double v) {
+    
+    m_center = v;
+  }
+  
+  /**
+   * Will increase or decrease the postion of center.
+   *
+   * @param v The amount to increase or decrease center by.
+   */
+  public void adjustCenter(double v) {
+    m_center += v;
+  }
+  
+  
+  /**
+   * Get the value of top.
+   *
+   * @return Value of top.
+   */
+  public double getTop() {
+    
+    return m_top;
+  }
+  
+  /**
+   * Set the value of top.
+   *
+   * @param v  Value to assign to top.
+   */
+  public void setTop(double v) {
+    
+    m_top = v;
+  }
+  
+
+  
+  
+  
+  /**
+   * Get the value of visible.
+   *
+   * @return Value of visible.
+   */
+  public boolean getVisible() {
+    
+    return m_visible;
+  }
+  
+  /**
+   * Set the value of visible.
+   *
+   * @param v  Value to assign to visible.
+   */
+  private void setVisible(boolean v) {
+    
+    m_visible = v;
+  }
+  
+  
+  
+  
+  /**
+   * Get the value of root.
+   *
+   * @return True if has no parents.
+   */
+  public boolean getRoot() {
+    
+    return m_root;
+  }
+  
+  /**
+   * Set the value of root.
+   *
+   * @param v  Value to assign to root.
+   */
+  public void setRoot(boolean v) {
+    
+    m_root = v;
+  }
+  
+  
+  
+  /**
+   * Get the parent edge.
+   *
+   * @param i The parent number to get.
+   * @return The parent edge or NULL if it doesn't exist.
+   */
+  public Edge getParent(int i) {
+    
+    if (i < m_parent.size()) {
+      return (Edge)m_parent.elementAt(i);
+    }
+    else {
+      return null;
+    }
+
+  }
+  
+  /**
+   * Set the value of parent.
+   *
+   * @param v  Value to assign to parent.
+   */
+  public void setParent(Edge v) {
+    
+    m_parent.addElement(v);
+  }
+  
+  
+  
+  /**
+   * Get the Edge for the child number 'i'.
+   *
+   * @param i The child number to get.
+   * @return The child Edge or NULL if it doesn't exist.
+   */
+  public Edge getChild(int i) {
+    
+    if (i < m_children.size()) {
+      return (Edge)m_children.elementAt(i);
+    }
+    else {
+      return null;
+    }
+  }
+  
+  /**
+   * Set the value of children.
+   *
+   * @param v  Value to assign to children.
+   */
+  public void addChild(Edge v) {
+    m_children.addElement(v);
+  }
+  
+
+  /**
+   * Recursively finds the number of visible groups of siblings there are.
+   *
+   * @param r The current Node upto.
+   * @param n The current number of groups there are.
+   * @return The number of groups found so far.
+   */
+  public static int getGCount(Node r,int n) {
+    Edge e;
+    
+    if (r.getChild(0) != null && r.getCVisible()) {
+      n++;
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	n = getGCount(e.getTarget(),n);
+      }
+    }
+    return n;
+  }
+
+  /**
+   * Recursively finds the total number of groups of siblings there are.
+   *
+   * @param r The current Node upto.
+   * @param n The current number of groups there are.
+   * @return The number of groups found so far.
+   */
+  public static int getTotalGCount(Node r,int n) {
+    Edge e;
+    
+    if (r.getChild(0) != null) {
+      n++;
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	n = getTotalGCount(e.getTarget(),n);
+      }
+    }
+    return n;
+  }
+  
+
+
+
+
+  /**
+   * Recursively finds the number of visible nodes there are (this may 
+   * accidentally count some of the invis nodes).
+   *
+   * @param r The current Node upto.
+   * @param n The current number nodes there are.
+   * @return The number of nodes found so far.
+   */
+  public static int getCount(Node r,int n) {
+    Edge e;
+    n++;
+    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
+      n = getCount(e.getTarget(),n);
+    }
+    return n;
+    
+  }
+
+  /**
+   * Recursively finds the total number of nodes there are.
+   *
+   * @param r The current Node upto.
+   * @param n The current number nodes there are.
+   * @return The number of nodes found so far.
+   */
+  public static int getTotalCount(Node r,int n) {
+    Edge e;
+    n++;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      n = getTotalCount(e.getTarget(),n);
+    }
+    return n;
+  }
+  
+  
+  /**
+   * Recursively finds the number of visible levels there are.
+   *
+   * @param r The current Node upto.
+   * @param l The curent level.
+   * @return The max number of levels found so far.
+   */
+  public static int getHeight(Node r,int l) {
+    l++;
+    int lev = l,temp = 0;
+    Edge e;
+    
+    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
+      temp = getHeight(e.getTarget(),l);
+      if (temp > lev) {
+	lev = temp;
+      }
+      
+    }
+    
+    return lev;
+
+
+  }
+
+  /**
+   * Recursively finds the total number of levels there are.
+   *
+   * @param r The current Node upto.
+   * @param l The curent level.
+   * @return The max number of levels found so far.
+   */
+  public static int getTotalHeight(Node r,int l) {
+    l++;
+    int lev = l,temp = 0;
+    Edge e;
+    
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      temp = getTotalHeight(e.getTarget(),l);
+      if (temp > lev) {
+	lev = temp;
+      }
+    }
+    return lev;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NodePlace.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NodePlace.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/NodePlace.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    NodePlace.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+/**
+ * This is an interface for classes that wish to take a node structure and 
+ * arrange them
+ *
+ * @author Malcolm F Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface NodePlace {
+ 
+  /**
+   * The function to call to postion the tree that starts at Node r
+   *
+   * @param r The top of the tree.
+   */
+   void place(Node r);
+  
+} 
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode1.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode1.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode1.java	(revision 29)
@@ -0,0 +1,122 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PlaceNode1.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+/**
+ * This class will place the Nodes of a tree. <p>
+ * 
+ * It will place these nodes so that they symetrically fill each row. 
+ * This is simple to calculate but is not visually nice for most trees.<p>
+ *
+ * @author Malcolm F Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class PlaceNode1 implements NodePlace {
+  /** An array containing the spacing value for each level */
+  private double[] m_levels; //contains num of nodes one each level
+
+  /** The number of levels in the tree */ 
+  private int m_noLevels;//contains num of levels
+
+  /** An array containing the current node place for each level to place 
+   * each node accordingly. */
+  private int[] m_levelNode; //contains num of node upto on particular level
+
+  /** The distance between each level. */
+  private double m_yRatio; //for quicker running y_ratio is a constant after 
+
+                         //being calculated
+  /**
+   * Call this function to have each node in the tree starting at 'r' placed 
+   * in a visual
+   * (not logical, they already are) tree position.
+   *
+   * @param r The top of the tree.
+   */
+  public void place(Node r) {
+    /* this is the first and most basic algorithm to write
+       I will use this as a reference to test the classes 
+
+       this works by counting up the nodes on each level and spacing the
+       level evenly so that it is all used
+    */
+
+    /* this loop will work by starting at the first node
+       and systematically going through all their children from left
+       to right.but first it will do a quick pass to find out the number
+       of levels there are*/
+
+    //+ 1 so that no nodes are on edge of screen
+    m_noLevels = r.getHeight(r,0)+1;
+    
+    m_yRatio = 1 / (double) m_noLevels;
+    
+    m_levels = new double[m_noLevels];
+    m_levelNode = new int[m_noLevels];
+    for (int noa = 0;noa < m_noLevels;noa++) {
+      m_levels[noa] = 1;
+      m_levelNode[noa] = 0;
+    }
+    
+    setNumOfNodes(r,0);
+    
+    for (int noa = 0;noa < m_noLevels;noa++) {
+      m_levels[noa] = 1 / m_levels[noa];
+    }
+    
+    placer(r,0);
+  }
+
+  /**
+   * This function finds the number of nodes on each level recursively.
+   *
+   * @param r The current Node upto.
+   * @param l The current level upto.
+   */
+  private void setNumOfNodes(Node r,int l) {
+    Edge e;
+    l++;
+    
+    m_levels[l]++;
+    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
+      setNumOfNodes(e.getTarget(),l);
+    }
+  }
+  
+  /**
+   * This function goes through and sets the position of each node
+   *
+   * @param r The current node upto.
+   * @param l the current level upto.
+   */
+  private void placer(Node r,int l) {
+    Edge e;
+    l++;
+    m_levelNode[l]++;
+    r.setCenter(m_levelNode[l] * m_levels[l]);
+    r.setTop(l * m_yRatio);
+    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
+      placer(e.getTarget(),l);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode2.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode2.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/PlaceNode2.java	(revision 29)
@@ -0,0 +1,672 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PlaceNode2.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import java.util.*;
+
+/**
+ * This class will place the Nodes of a tree. <p>
+ * 
+ * It will place these nodes so that they fall at evenly below their parent.
+ * It will then go through and look for places where nodes fall on the wrong 
+ * side of other nodes
+ * when it finds one it will trace back up the tree to find the first common 
+ * sibling group these two nodes have
+ * And it will adjust the spacing between these two siblings so that the two 
+ * nodes no longer overlap.
+ * This is nasty to calculate with , and takes a while with the current 
+ * algorithm I am using to do this.<p>
+ *
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class PlaceNode2 implements NodePlace {
+  /** The space each row will take up. */
+  private double m_yRatio;
+
+  /** An array that lists the groups and information about them. */
+  private Group[] m_groups;
+
+  /** An array that lists the levels and information about them. */
+  private Level[] m_levels;
+
+  /** The Number of groups the tree has */
+  private int m_groupNum;
+
+  /** The number of levels the group tree has */
+  private int m_levelNum;
+  
+  /** 
+   * The Funtion to call to have the nodes arranged.
+   *
+   * @param r The top node of the tree to arrange.
+   */
+  public void place(Node r) {
+    //note i might write count groups into the node class as well as
+    //it may be useful too;
+    
+    m_groupNum = Node.getGCount(r,0); //i could swap over to the node class 
+    //group count,but this works os i'm not gonna
+    m_groups = new Group[m_groupNum];
+        
+    for (int noa = 0;noa < m_groupNum;noa++) {
+      m_groups[noa] = new Group();
+      m_groups[noa].m_gap = 3;
+      m_groups[noa].m_start = -1;
+    }
+    
+    groupBuild(r);
+    m_levelNum = Node.getHeight(r,0);
+    m_yRatio = 1 / (double)(m_levelNum + 1);
+    
+    m_levels = new Level[m_levelNum];
+    
+    for (int noa = 0;noa < m_levelNum;noa++) {
+      m_levels[noa] = new Level();
+    }
+    r.setTop(m_yRatio);
+    yPlacer();
+    r.setCenter(0);
+    xPlacer(0);
+
+
+    //ok now i just have to untangle then scale down
+    //note instead of starting with coords between 1 and 0 i will
+    //use ints then scale them down 
+    //i will scale them down either by all relative to the largest
+    //line or by each line individually
+
+    untangle2();
+    
+    scaleByMax();
+    //scaleByInd();
+  }
+
+
+  /*
+  private void thinner()
+  {
+    //what this function does is it retains the symmetry of the
+     // parent node about the children but the children are no longer evenly
+      //spaced this stops children from being pushed too far to the sides
+      //,note this algorithm may need the method altered as it may 
+     // require heavy optimisation to go at any decent speed   
+  
+    Node r,s;
+    Edge e;
+    double parent_x;
+    for (int noa = group_num - 1;noa >= 0;noa--)
+      {
+	Vector shifts = new Vector(20,10);
+	shifts.addElement(0);
+	int g_num = 0;//this is the offset from groups.m_start to get the right 1
+	r = groups[noa].m_p;
+	parent_x = r.getCenter();
+	for (int nob = 1;(e = r.getChild(nob)) != null;nob++)
+	  {
+	    double margin;
+	    s = e.getTarget();
+	    margin = s_getCenter - r.getChild(nob - 1).getTarget().getCenter-1
+	             - shift.elementAt(nob-1);
+	    if (margin > 0)
+	      {
+		margin = check_down(s,g_num,margin);
+		if (margin > 0)
+		  {
+		    shift.addElement(-margin);
+		  }
+		else
+		  {
+		    shift.addElement(0);
+		  }
+	      }
+	    else
+	      {
+		shift.addElement(0);
+	      }
+	    if (s.getChild(0) != null)
+	      {
+		g_num++;
+	      }
+	  }
+      }
+  }
+
+
+  private double check_down(Node r,int gn,double m)
+  {
+    //note i need to know where the children of the 
+    //other changers are to properly overlap check
+    //to do this i think the best way is to go up the other group
+    //parents line and see if it goes through the current group
+    //this means to save time i need to know the level that is being 
+    //worked with along with the group
+    
+    Edge e;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++)
+      {
+	
+      }
+  }
+*/
+
+
+  /**
+   * This will set initial places for the x coord of the nodes.
+   * @param start The `number for the first group to start on (I think).
+   */
+  private void xPlacer(int start) {
+    //this can be one of a few x_placers (the first)
+    //it will work by placing 1 space inbetween each node
+    //ie the first at 0 the second at 1 and so on
+    //then it will add to this value the place of the parent 
+    //node - half of the size
+    //i will break this up into several functions
+    //first the gap setter;
+    //then the shifter
+    //it will require a vector shift function added to the node class
+    //i will write an additional shifter for the untangler 
+    //for its particular situation
+
+    Node r;
+    Edge e;
+    if (m_groupNum > 0) {
+      m_groups[0].m_p.setCenter(0);
+      for (int noa = start;noa < m_groupNum;noa++) {
+	int nob,alter =0;
+	double c = m_groups[noa].m_gap;
+	r = m_groups[noa].m_p;
+	for (nob = 0;(e = r.getChild(nob)) != null;nob++) {
+	  if (e.getTarget().getParent(0) == e) {
+	    e.getTarget().setCenter(nob * c);
+	  }
+	  else {
+	    alter++;
+	  }
+	}
+	m_groups[noa].m_size = (nob - 1 - alter) * c;
+	xShift(noa);
+      }
+    }
+  }
+
+  /**
+   * This will shift a group of nodes to be aligned under their parent.
+   * @param n The group number to shift
+   */
+  private void xShift(int n) {
+    Edge e;
+    Node r = m_groups[n].m_p;
+    double h = m_groups[n].m_size / 2;
+    double c = m_groups[n].m_p.getCenter();
+    double m = c - h;
+    m_groups[n].m_left = m;
+    m_groups[n].m_right = c + h;
+    
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      if (e.getTarget().getParent(0) == e) {
+	e.getTarget().adjustCenter(m);
+      }
+    }
+  }
+
+  /**
+   * This scales all the x values to be between 0 and 1.
+   */
+  private void scaleByMax() {
+    //ammendment to what i may have commented before
+    //this takes the lowest x and highest x  and uses that as the scaling
+    //factor
+    double l_x = 5000,h_x = -5000;
+    for (int noa = 0;noa < m_groupNum;noa++) {
+      if (l_x > m_groups[noa].m_left) {
+	l_x = m_groups[noa].m_left;
+      }
+
+      if (h_x < m_groups[noa].m_right) {
+	h_x = m_groups[noa].m_right;
+      }
+    }
+    
+    Edge e;
+    Node r,s;
+    double m_scale = h_x - l_x + 1;
+    if (m_groupNum > 0) {
+      r = m_groups[0].m_p;
+      r.setCenter((r.getCenter() - l_x) / m_scale);
+      //System.out.println("from scaler " + l_x + " " + m_scale);
+      for (int noa = 0; noa < m_groupNum;noa++) {
+	r = m_groups[noa].m_p;
+	for (int nob = 0;(e = r.getChild(nob)) != null;nob++) {
+	  s = e.getTarget();
+	  if (s.getParent(0) == e) {
+	    s.setCenter((s.getCenter() - l_x) / m_scale);
+	  }
+	}
+      }
+    }
+  }
+  
+  /**
+   * This scales the x values to between 0 and 1 for each individual line
+   * rather than doing them all at once.
+   */
+  private void scaleByInd() {
+    //ammendment to what i may have commented before
+    //this takes the lowest x and highest x  on each line and uses that for 
+    //the line in question
+    double l_x,h_x;
+
+    Edge e;
+    Node r,s;
+    r = m_groups[0].m_p;
+    r.setCenter(.5);
+    double m_scale;
+    for (int noa = 0;noa < m_levelNum;noa++) {
+      l_x = m_groups[m_levels[noa].m_start].m_left;
+      h_x = m_groups[m_levels[noa].m_end].m_right;
+      m_scale = h_x - l_x + 1;
+      for (int nob = m_levels[noa].m_start; nob <= m_levels[noa].m_end;nob++) {
+	r = m_groups[nob].m_p;
+	for (int noc = 0;(e = r.getChild(noc)) != null;noc++) {
+	  s = e.getTarget();
+	  if (s.getParent(0) == e) {
+	    s.setCenter((s.getCenter() - l_x) / m_scale);
+	  }
+	}
+      }
+    }
+  }
+  
+  /**
+   * This untangles the nodes so that they will will fall on the correct
+   * side of the other nodes along their row.
+   */
+  private void untangle2() {
+    Ease a;
+    Edge e;
+    Node r,nf = null,ns = null,mark;
+    int l = 0,times = 0;
+    int f,s,tf = 0,ts = 0,pf,ps;
+    while ((a = overlap(l)) != null) {
+      times++;
+      //System.out.println("from untang 2 " + group_num);
+      f = a.m_place;
+      s = a.m_place + 1;
+      while (f != s) {
+	a.m_lev--;
+	tf = f;
+	ts = s;
+	f = m_groups[f].m_pg;
+	s = m_groups[s].m_pg;
+      }
+      l = a.m_lev;
+      pf = 0;
+      ps = 0;
+      r = m_groups[f].m_p;
+      mark = m_groups[tf].m_p;
+      nf = null;
+      ns = null;
+      for (int noa = 0; nf != mark;noa++) {
+	pf++;
+	nf = r.getChild(noa).getTarget();
+      }
+      mark = m_groups[ts].m_p;
+      for (int noa = pf; ns != mark;noa++) {
+	ps++; //the number of gaps between the two nodes
+	ns = r.getChild(noa).getTarget();
+      }
+      //m_groups[f].gap =
+      //              Math.ceil((a.amount / (double)ps) + m_groups[f].gap);
+      //note for this method i do not need the group gap ,but i will leave
+      //it for the other methods;
+      Vector o_pos = new Vector(20,10);
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	if (e.getTarget().getParent(0) == e) {
+	  Double tem = new Double(e.getTarget().getCenter());
+	  o_pos.addElement(tem);
+	}
+      }
+
+      pf--;
+      double inc = a.m_amount / (double)ps;
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	ns = e.getTarget();
+	if (ns.getParent(0) == e) {
+	  if (noa > pf + ps) {
+	    ns.adjustCenter(a.m_amount);
+	  }
+	  else if (noa > pf) {
+	    ns.adjustCenter(inc * (double)(noa - pf));
+	  }
+	}
+      }
+
+      nf = r.getChild(0).getTarget();
+      inc = ns.getCenter() - nf.getCenter();
+      m_groups[f].m_size = inc;
+      m_groups[f].m_left = r.getCenter() - inc / 2; 
+      m_groups[f].m_right = m_groups[f].m_left + inc;
+      inc = m_groups[f].m_left - nf.getCenter();
+
+      double shift;
+      int g_num = 0;
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	ns = e.getTarget();
+	if (ns.getParent(0) == e) {
+	  ns.adjustCenter(inc);
+	  shift = ns.getCenter() - 
+	    ((Double)o_pos.elementAt(noa)).doubleValue();
+	  if (ns.getChild(0) != null) {
+	    moveSubtree(m_groups[f].m_start + g_num,shift);
+	    g_num++;
+	  }
+	}
+	//ns.adjustCenter(-shift);
+      }
+      //zero_offset(r);
+      
+      //x_placer(f);
+    }
+  }
+
+
+  /**
+   * This will recursively shift a sub there to be centered about
+   * a particular value.
+   * @param n The first group in the sub tree.
+   * @param o The point to start shifting the subtree.
+   */
+  private void moveSubtree(int n,double o) {
+    Edge e;
+    Node r = m_groups[n].m_p;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      if (e.getTarget().getParent(0) == e) {
+	e.getTarget().adjustCenter(o);
+      }
+    }
+    m_groups[n].m_left += o;
+    m_groups[n].m_right += o;
+    if (m_groups[n].m_start != -1) {
+      for (int noa = m_groups[n].m_start;noa <= m_groups[n].m_end;noa++) {
+	moveSubtree(noa,o);
+      }
+    }
+  }
+
+
+  /**
+   * This will untangle the nodes in the tree so that they fall on the
+   * correct side of each other.
+   */
+  private void untangle() {
+    Ease a;
+    Edge e;
+    Node r,nf = null,ns = null,mark;
+    int l = 0,times = 0;
+    int f,s,tf = 0,ts = 0,pf,ps;
+    while ((a = overlap(l)) != null) {
+      times++;
+      //System.out.println(group_num);
+      f = a.m_place;
+      s = a.m_place + 1;
+      while (f != s) {
+	a.m_lev--;
+	tf = f;
+	ts = s;
+	f = m_groups[f].m_pg;
+	s = m_groups[s].m_pg;
+      }
+      l = a.m_lev;
+      pf = 0;
+      ps = 0;
+      r = m_groups[f].m_p;
+      mark = m_groups[tf].m_p;
+      nf = null;
+      ns = null;
+      for (int noa = 0; nf != mark;noa++) {
+	pf++;
+	nf = r.getChild(noa).getTarget();
+      }
+      mark = m_groups[ts].m_p;
+      for (int noa = pf; ns != mark;noa++) {
+	ps++; //the number of gaps between the two nodes
+	ns = r.getChild(noa).getTarget();
+      }
+      m_groups[f].m_gap =
+	Math.ceil((a.m_amount / (double)ps) + m_groups[f].m_gap);
+      
+      xPlacer(f);
+    }
+  }
+  
+  /**
+   * This will find an overlap and then return information about that overlap
+   * @param l The level to start on.
+   * @return null if there was no overlap , otherwise an object containing
+   * the group number that overlaps (only need one) how much they overlap by,
+   * and the level they overlap on.
+   */
+  private Ease overlap(int l) {
+    Ease a = new Ease();
+    for (int noa = l;noa < m_levelNum;noa++) {
+      for (int nob = m_levels[noa].m_start;nob < m_levels[noa].m_end;nob++) {
+	a.m_amount = m_groups[nob].m_right - m_groups[nob+1].m_left + 2;
+	//System.out.println(m_groups[nob].m_right + " + " + 
+	//	       m_groups[nob+1].m_left + " = " + a.amount);
+	if (a.m_amount >= 0) {
+	  a.m_amount++;
+	  a.m_lev = noa;
+	  a.m_place = nob;
+	  return a;
+	}
+      }
+    }
+    return null;
+  }
+  
+  /* private int count_m_groups(Node r,int l)
+  {
+    Edge e;
+    if (r.getChild(0) != null)
+      {
+	l++;
+      }
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++)
+      {
+	l = count_groups(e.getTarget(),l);
+      }
+
+    return l;
+  }
+  */
+
+  /**
+   * This function sets up the height of each node, and also fills the
+   * levels array with information about what the start and end groups on that
+   * level are.
+   */
+  private void yPlacer() {
+    //note this places the y height and sets up the levels array
+    double changer = m_yRatio;
+    int lev_place = 0;
+    if (m_groupNum > 0) {
+      m_groups[0].m_p.setTop(m_yRatio);
+      m_levels[0].m_start = 0;
+      
+      for (int noa = 0;noa < m_groupNum;noa++) {
+	if (m_groups[noa].m_p.getTop() != changer) {
+	  m_levels[lev_place].m_end = noa - 1;
+	  lev_place++;
+	  m_levels[lev_place].m_start = noa;
+	  changer = m_groups[noa].m_p.getTop();
+	}
+	nodeY(m_groups[noa].m_p);
+      }
+      m_levels[lev_place].m_end = m_groupNum - 1;
+    }
+  }
+
+  /**
+   * This will set all of the children node of a particular node to their
+   * height.
+   * @param r The parent node of the children to set their height. 
+   */
+  private void nodeY(Node r) {
+    Edge e;
+    double h = r.getTop() + m_yRatio;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      if (e.getTarget().getParent(0) == e) {
+	e.getTarget().setTop(h);
+	if (!e.getTarget().getVisible()) {
+	  //System.out.println("oh bugger");
+	}
+      }
+    }
+  }
+  
+  /**
+   * This starts to create the information about the sibling groups.
+   * As more groups are created the for loop in this will check those groups
+   * for lower groups.
+   * @param r The top node.
+   */
+  private void groupBuild(Node r) {
+    if (m_groupNum > 0) {
+      m_groupNum = 0;
+      m_groups[0].m_p = r;
+      m_groupNum++;
+      //note i need to count up the num of groups first
+      //woe is me
+      for (int noa = 0;noa < m_groupNum ;noa++) {
+	groupFind(m_groups[noa].m_p,noa);
+      }
+    }
+  }
+  
+  /**
+   * This is called to build the rest of the grouping information.
+   * @param r The parent of the group.
+   * @param pg The number for the parents group.
+   */
+  private void groupFind(Node r,int pg) {
+    Edge e;
+    boolean first = true;
+    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+      if (e.getTarget().getParent(0) == e) {
+	if (e.getTarget().getChild(0) != null && e.getTarget().getCVisible()) {
+	  if (first) {
+	    m_groups[pg].m_start = m_groupNum;
+	    first = false;
+	  }
+	  m_groups[pg].m_end = m_groupNum;
+	  m_groups[m_groupNum].m_p = e.getTarget();
+	  m_groups[m_groupNum].m_pg = pg;
+	  m_groups[m_groupNum].m_id = m_groupNum; //just in case I ever need
+	  //this info
+	  m_groupNum++;
+	}
+      }
+    }
+  }
+  
+
+  //note these three classes are only to help organise the data and are
+  //inter related between each other and this placer class
+  //so don't mess with them or try to use them somewhere else
+  //(because that would be a mistake and I would pity you)
+  
+
+  /**
+   * Inner class for containing the level data.
+   */
+  private class Level {
+    /** The number for the group on the left of this level. */
+    public int m_start;
+    /** The number for the group on the right of this level. */
+    public int m_end;
+
+    /** These two params would appear to not be used. */
+    public int m_left;
+    public int m_right;
+  }
+
+  /**
+   * Inner class for containing the grouping data.
+   */
+  private class Group {
+    /** The parent node of this group. */
+    public Node m_p;
+
+    /** The group number for the parent of this group. */
+    public int m_pg;
+
+    /** The gap size for the distance between the nodes in this group. */
+    public double m_gap;
+
+    /** The leftmost position of this group. */
+    public double m_left;
+
+    /** The rightmost position of this group. */
+    public double m_right;
+
+    /** The size of this group. */
+    public double m_size;
+
+    /** The start node of this group. */
+    public int m_start;
+
+    /** The end node of this group. */
+    public int m_end;
+
+    /** The group number for this group. (may not be used!?). */
+    public int m_id;
+  }
+  
+  /**
+   * An inner class used to report information about any tangles found. 
+   */
+  private class Ease {
+    /** The number of the group on the left of the tangle. */
+    public int m_place;
+    /** The distance they were tangled. */
+    public double m_amount;
+    /** The level on which they were tangled. */
+    public int m_lev;
+  }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeBuild.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeBuild.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeBuild.java	(revision 29)
@@ -0,0 +1,821 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Tree_build.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import java.util.*;
+import java.io.*;
+import java.awt.*;
+
+/**
+ * This class will parse a dotty file and construct a tree structure from it 
+ * with Edge's and Node's
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class TreeBuild {
+  //this class will parse the tree into relevant strings
+  //into info objects 
+  //from there it will create the nodes and edges from the info objects
+
+  /** The name of the tree, Not in use. */
+  private String m_graphName;
+
+  /** An array with all the nodes initially constructed into it. */
+  private Vector m_aNodes;
+
+  /** An array with all the edges initially constructed into it. */
+  private Vector m_aEdges;
+
+  /** An array containing a structure that describes the node without 
+   * actually creating it. */
+  private Vector m_nodes;
+
+  /** An arry containing a structure that describes the edge without 
+   * actually creating it. */
+  private Vector m_edges;
+
+  /** An object setup to take graph data. */
+  private InfoObject m_grObj;
+
+  /** An object setup to take node data. */
+  private InfoObject m_noObj;
+
+  /** An object setup to take edge data. */
+  private InfoObject m_edObj;
+
+  /** true if it is a digraph. (note that this can't build digraphs). */
+  private boolean m_digraph;
+  
+  /** The stream to parse. */
+  private StreamTokenizer m_st;
+
+  /** A table containing all the colors. */
+  private Hashtable m_colorTable;
+  
+  /**
+   * Upon construction this will only setup the color table for quick 
+   * reference of a color.
+   */
+  public TreeBuild() {
+    m_colorTable = new Hashtable();
+    
+    Colors ab = new Colors();
+    for (int noa = 0;noa < ab.m_cols.length;noa++) {
+      m_colorTable.put(ab.m_cols[noa].m_name,ab.m_cols[noa].m_col);
+    }
+  }  
+    
+  /**
+   * This will build A node structure from the dotty format passed. Don't 
+   * send a dotty format with multiple parents
+   * per node, and ensure that there is 1 and only 1 node with no parent.
+   *
+   * @param t The reader with the dotty string to be read.
+   * @return The top node of the tree structure (the last node with no parent).
+   */
+  public Node create(Reader t) {
+    m_nodes = new Vector(50,50);
+    m_edges = new Vector(50,50);
+    m_grObj = new InfoObject("graph");
+    m_noObj = new InfoObject("node");
+    m_edObj = new InfoObject("edge");
+    m_digraph = false;
+    
+    m_st = new StreamTokenizer(new BufferedReader(t));
+    setSyntax();
+
+    graph();
+
+    Node top = generateStructures();
+    
+    return top;
+  }
+  
+  /**
+   * This will go through all the found Nodes and Edges build instances of 
+   * these
+   * and link them together.
+   *
+   * @return The node with no parent (the top of the tree).
+   */
+  private Node generateStructures() {
+    String id,label,source,target;
+    Integer shape,style;
+    int fontsize;
+    Color fontcolor,color;
+
+    InfoObject t;
+    m_aNodes = new Vector(50,50);
+    m_aEdges = new Vector(50,50);
+    for (int noa = 0;noa < m_nodes.size();noa++) {
+      t = (InfoObject)m_nodes.elementAt(noa);
+      id = t.m_id;
+      
+      if (t.m_label == null) {
+	if (m_noObj.m_label == null) {
+	  label = "";
+	}
+	else {
+	  label = m_noObj.m_label;
+	}
+      }
+      else {
+	label = t.m_label;
+      }
+      
+      if (t.m_shape == null) {
+	if (m_noObj.m_shape == null) {
+	  shape = new Integer(2);
+	}
+	else {
+	  shape = getShape(m_noObj.m_shape);
+	}
+      }
+      else {
+	shape = getShape(t.m_shape);
+      }
+      if (shape == null) {
+	shape = new Integer(2);
+      }
+      
+      if (t.m_style == null) {
+	if (m_noObj.m_style == null) {
+	  style = new Integer(1);
+	}
+	else {
+	  style = getStyle(m_noObj.m_style);
+	}
+      }
+      else {
+	style = getStyle(t.m_style);
+      }
+      if (style == null) {
+	style = new Integer(1);
+      }
+      
+      if (t.m_fontSize == null) {
+	if (m_noObj.m_fontSize == null) {
+	  fontsize = 12;
+	}
+	else {
+	  fontsize = Integer.valueOf(m_noObj.m_fontSize).intValue();
+	}
+      }
+      else {
+	fontsize = Integer.valueOf(t.m_fontSize).intValue();
+      }
+      
+      if (t.m_fontColor == null) {
+	if (m_noObj.m_fontColor == null) {
+	  fontcolor = Color.black;
+	}
+	else {
+	  fontcolor = (Color)m_colorTable
+	    .get(m_noObj.m_fontColor.toLowerCase());
+	}
+      }
+      else {
+	fontcolor = (Color)m_colorTable.get(t.m_fontColor.toLowerCase());
+      }
+      if (fontcolor == null) {
+	fontcolor = Color.black;
+      }
+      
+      if (t.m_color == null) {
+	if (m_noObj.m_color == null) {
+	  color = Color.gray;
+	}
+	else {
+	  color = (Color)m_colorTable.get(m_noObj.m_color.toLowerCase());
+	}
+      }
+      else {
+	color = (Color)m_colorTable.get(t.m_color.toLowerCase());
+      }
+      if (color == null) {
+	color = Color.gray;
+      }
+      
+      m_aNodes.addElement(new Node(label,id,style.intValue(),shape.intValue(),
+				  color,t.m_data));
+    }
+
+
+    for (int noa = 0;noa < m_edges.size();noa++) {
+      t = (InfoObject)m_edges.elementAt(noa);
+      id = t.m_id;
+      
+      if (t.m_label == null) {
+	if (m_noObj.m_label == null) {
+	  label = "";
+	}
+	else {
+	  label = m_noObj.m_label;
+	}
+      }
+      else {
+	label = t.m_label;
+      }
+      
+      if (t.m_shape == null) {
+	if (m_noObj.m_shape == null) {
+	  shape = new Integer(2);
+	}
+	else {
+	  shape = getShape(m_noObj.m_shape);
+	}
+      }
+      else {
+	shape = getShape(t.m_shape);
+      }
+      if (shape == null) {
+	shape = new Integer(2);
+      }
+      
+      if (t.m_style == null) {
+	if (m_noObj.m_style == null) {
+	  style = new Integer(1);
+	}
+	else {
+	  style = getStyle(m_noObj.m_style);
+	}
+      }
+      else {
+	style = getStyle(t.m_style);
+      }
+      if (style == null) {
+	style = new Integer(1);
+      }
+      
+      if (t.m_fontSize == null) {
+	if (m_noObj.m_fontSize == null) {
+	  fontsize = 12;
+	}
+	else {
+	  fontsize = Integer.valueOf(m_noObj.m_fontSize).intValue();
+	}
+      }
+      else {
+	fontsize = Integer.valueOf(t.m_fontSize).intValue();
+      }
+      
+      if (t.m_fontColor == null) {
+	if (m_noObj.m_fontColor == null) {
+	  fontcolor = Color.black;
+	}
+	else {
+	  fontcolor = (Color)m_colorTable
+	    .get(m_noObj.m_fontColor.toLowerCase());
+	}
+      }
+      else {
+	fontcolor = (Color)m_colorTable.get(t.m_fontColor.toLowerCase());
+      }
+      if (fontcolor == null) {
+	fontcolor = Color.black;
+      }
+      
+      if (t.m_color == null) {
+	if (m_noObj.m_color == null) {
+	  color = Color.white;
+	}
+	else {
+	  color = (Color)m_colorTable.get(m_noObj.m_color.toLowerCase());
+	}
+      }
+      else {
+	color = (Color)m_colorTable.get(t.m_color.toLowerCase());
+      }
+      if (color == null) {
+	color = Color.white;
+      }
+      
+      m_aEdges.addElement(new Edge(label,t.m_source,t.m_target));
+    }
+        
+    boolean f_set,s_set;
+    Node x,sour = null,targ = null;
+    Edge y;
+    for (int noa = 0;noa < m_aEdges.size();noa++) {
+      f_set = false;
+      s_set = false;
+      y = (Edge)m_aEdges.elementAt(noa);
+      for (int nob = 0;nob < m_aNodes.size();nob++) {
+	x = (Node)m_aNodes.elementAt(nob);
+	if (x.getRefer().equals(y.getRtarget())) {
+	  f_set = true;
+	  targ = x;
+	}
+	if (x.getRefer().equals(y.getRsource())) {
+	  s_set = true;
+	  sour = x;
+	}
+	if (f_set == true && s_set == true) {
+	  break;
+	}
+      }
+      if (targ != sour) {
+	y.setTarget(targ);
+	y.setSource(sour);
+      }
+      else {
+	System.out.println("logic error");
+      }
+    }
+    
+    for (int noa = 0;noa < m_aNodes.size();noa++) {
+      if (((Node)m_aNodes.elementAt(noa)).getParent(0) == null) {
+	sour = (Node)m_aNodes.elementAt(noa);
+      }
+    }
+
+    return sour;
+  }
+  
+  /**
+   * This will convert the shape string to an int representing that shape.
+   *
+   * @param sh The name of the shape.
+   * @return An Integer representing the shape.
+   */
+  private Integer getShape(String sh) {
+    if (sh.equalsIgnoreCase("box") || sh.equalsIgnoreCase("rectangle")) {
+      return new Integer(1);
+    }
+    else if (sh.equalsIgnoreCase("oval")) {
+      return new Integer(2);
+    }
+    else if (sh.equalsIgnoreCase("diamond")) {
+      return new Integer(3);
+    }
+    else {
+      return null;
+    }
+  }
+  
+  /**
+   * Converts the string representing the fill style int oa number 
+   * representing it.
+   *
+   * @param sty The name of the style.
+   * @return An Integer representing the shape.
+   */
+  private Integer getStyle(String sty) {
+    if (sty.equalsIgnoreCase("filled")) {
+      return new Integer(1);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * This will setup the syntax for the tokenizer so that it parses the 
+   * string properly.
+   *
+   */
+  private void setSyntax() {
+    m_st.resetSyntax();
+    m_st.eolIsSignificant(false);
+    m_st.slashStarComments(true);
+    m_st.slashSlashComments(true);
+    //System.out.println("slash");
+    m_st.whitespaceChars(0,' ');
+    m_st.wordChars(' '+1,'\u00ff');
+    m_st.ordinaryChar('[');
+    m_st.ordinaryChar(']');
+    m_st.ordinaryChar('{');
+    m_st.ordinaryChar('}');
+    m_st.ordinaryChar('-');
+    m_st.ordinaryChar('>');
+    m_st.ordinaryChar('/');
+    m_st.ordinaryChar('*');
+    m_st.quoteChar('"');
+    m_st.whitespaceChars(';',';');
+    m_st.ordinaryChar('=');
+  }
+
+  /**
+   * This is the alternative syntax for the tokenizer.
+   */
+  private void alterSyntax() {
+    m_st.resetSyntax();
+    m_st.wordChars('\u0000', '\u00ff');
+    m_st.slashStarComments(false);
+    m_st.slashSlashComments(false);
+    m_st.ordinaryChar('\n');
+    m_st.ordinaryChar('\r');
+  }
+
+  /**
+   * This will parse the next token out of the stream and check for certain 
+   * conditions.
+   *
+   * @param r The error string to print out if something goes wrong.
+   */
+  private void nextToken(String r) {
+    int t = 0;
+    try {
+      t = m_st.nextToken();
+    } catch(IOException e) {
+    }
+    
+    if (t == m_st.TT_EOF) {
+      System.out.println("eof , " + r);
+    }
+    else if (t == m_st.TT_NUMBER) {
+      System.out.println("got a number , " + r);
+    }
+  }
+  
+  /**
+   * Parses the top of the dotty stream that has the graph information.
+   *
+   */
+  private void graph() {
+    boolean flag = true;
+    String s;
+    //expect digraph
+    int t;
+    nextToken("expected 'digraph'");
+    
+    if (m_st.sval.equalsIgnoreCase("digraph")) {
+      m_digraph = true;
+    }
+    else {
+      System.out.println("expected 'digraph'");
+    }
+    
+    nextToken("expected a Graph Name");
+    if (m_st.sval != null) {
+      m_graphName = m_st.sval;
+    }
+    else {
+      System.out.println("expected a Graph Name");
+    }
+    
+    nextToken("expected '{'");
+    
+    if (m_st.ttype == '{') {
+      stmtList();
+    }
+    else {
+      System.out.println("expected '{'");
+    }
+  }
+
+  /**
+   * This is one of the states, this one is where new items can be defined 
+   * or the structure can end.
+   *
+   */
+  private void stmtList() {
+    boolean flag = true;
+    String s;
+    int t;
+    while(flag) {
+      nextToken("expects a STMT_LIST item or '}'");
+      if (m_st.ttype == '}') {
+	flag = false;
+      }
+      else if (m_st.sval.equalsIgnoreCase("graph") ||
+	       m_st.sval.equalsIgnoreCase("node") ||
+	       m_st.sval.equalsIgnoreCase("edge")) {
+	m_st.pushBack();
+	attrStmt();
+      }
+      else if (m_st.sval != null) {
+	nodeId(m_st.sval,0);
+      }
+      else {
+	System.out.println("expects a STMT_LIST item or '}'");
+      }
+    }
+  }
+
+  /**
+   * This will deal specifically with a new object such as graph , node , edge.
+   *
+   */
+  private void attrStmt() {
+    
+    nextToken("expected 'graph' or 'node' or 'edge'");
+    
+    if (m_st.sval.equalsIgnoreCase("graph")) {
+      nextToken("expected a '['");
+      if (m_st.ttype == '[') {
+	attrList(m_grObj);
+      }
+      else {
+	System.out.println("expected a '['");
+      }
+    }
+    else if (m_st.sval.equalsIgnoreCase("node")) {
+      nextToken("expected a '['");
+      if (m_st.ttype == '[') {
+	attrList(m_noObj);
+      }
+      else {
+	System.out.println("expected a '['");
+      }
+    }
+    else if (m_st.sval.equalsIgnoreCase("edge")) {
+      nextToken("expected a '['");
+      if (m_st.ttype == '[') {
+	attrList(m_edObj);
+      }
+      else {
+	System.out.println("expected a '['");
+      }
+      
+    }
+    else {
+      System.out.println("expected 'graph' or 'node' or 'edge'"); 
+    }
+  }
+
+  /**
+   * Generates a new InfoObject with the specified name and either does 
+   * further processing if applicable
+   * Otherwise it is an edge and will deal with that.
+   *
+   * @param s The ID string.
+   * @param t Not sure!.
+   */
+  private void nodeId(String s,int t) {
+    
+    nextToken("error occurred in node_id");
+
+    if (m_st.ttype == '}') {
+      //creates a node if t is zero
+      if (t == 0) {
+	m_nodes.addElement(new InfoObject(s));
+      }
+      m_st.pushBack();
+    }
+    else if (m_st.ttype == '-') {
+      nextToken("error occurred checking for an edge");
+      if (m_st.ttype == '>') {
+	edgeStmt(s);
+      }
+      else {
+	System.out.println("error occurred checking for an edge");
+      }
+    }
+    else if (m_st.ttype == '[') {
+      //creates a node if t is zero and sends it to attr
+      if (t == 0) {
+	m_nodes.addElement(new InfoObject(s));
+	attrList((InfoObject)m_nodes.lastElement());
+      }
+      else {
+	attrList((InfoObject)m_edges.lastElement());
+      }
+    }
+    else if (m_st.sval != null) {
+      //creates a node if t is zero
+      if (t == 0) {
+	m_nodes.addElement(new InfoObject(s));
+      }
+      m_st.pushBack();
+    }
+    else {
+      System.out.println("error occurred in node_id");
+    }
+  }
+
+  /**
+   * This will get the target of the edge.
+   *
+   * @param i The source of the edge.
+   */
+  private void edgeStmt(String i) {
+    nextToken("error getting target of edge");
+    
+    if (m_st.sval != null) {
+      m_edges.addElement(new InfoObject("an edge ,no id"));
+      ((InfoObject)m_edges.lastElement()).m_source = i;
+      ((InfoObject)m_edges.lastElement()).m_target = m_st.sval;
+      nodeId(m_st.sval,1);
+    }
+    else {
+      System.out.println("error getting target of edge");
+    }
+  }
+
+  /**
+   * This will parse all the items in the attrib list for an object.
+   *
+   * @param a The object that the attribs apply to.
+   */
+  private void attrList(InfoObject a) {
+    boolean flag = true;
+    
+    while (flag) {
+      nextToken("error in attr_list");
+      //System.out.println(st.sval);
+      if (m_st.ttype == ']') {
+	flag = false;
+      }
+      else if (m_st.sval.equalsIgnoreCase("color")) {
+	nextToken("error getting color");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting color");
+	  if (m_st.sval != null) {
+	    a.m_color = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting color");
+	  }
+	}
+	else {
+	  System.out.println("error getting color");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("fontcolor")) {
+	nextToken("error getting font color");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting font color");
+	  if (m_st.sval != null) {
+	    a.m_fontColor = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting font color");
+	  }
+	}
+	else {
+	  System.out.println("error getting font color");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("fontsize")) {
+	nextToken("error getting font size");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting font size");
+	  if (m_st.sval != null) {
+	    a.m_fontSize = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting font size");
+	  }
+	}
+	else {
+	  System.out.println("error getting font size");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("label")) {
+	nextToken("error getting label");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting label");
+	  if (m_st.sval != null) {
+	    a.m_label = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting label");
+	  }
+	}
+	else {
+	  System.out.println("error getting label");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("shape")) {
+	nextToken("error getting shape");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting shape");
+	  if (m_st.sval != null) {
+	    a.m_shape = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting shape");
+	  }
+	}
+	else {
+	  System.out.println("error getting shape");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("style")) {
+	nextToken("error getting style");
+	if (m_st.ttype == '=') {
+	  nextToken("error getting style");
+	  if (m_st.sval != null) {
+	    a.m_style = m_st.sval;
+	  }
+	  else {
+	    System.out.println("error getting style");
+	  }
+	}
+	else {
+	  System.out.println("error getting style");
+	}
+      }
+      else if (m_st.sval.equalsIgnoreCase("data")) {
+	nextToken("error getting data");
+	if (m_st.ttype == '=') {
+	  //data has a special data string that can have anything
+	  //this is delimited by a single comma on an otherwise empty line
+	  alterSyntax();
+	  a.m_data = new String("");
+	  
+	  while (true) {
+	    nextToken("error getting data");
+	    if (m_st.sval != null && a.m_data 
+		!= null && m_st.sval.equals(",")) {
+	      break;
+	    }
+	    else if (m_st.sval != null) {
+	      a.m_data = a.m_data.concat(m_st.sval);
+	    }
+	    else if (m_st.ttype == '\r') {
+	      a.m_data = a.m_data.concat("\r");
+	    }
+	    else if (m_st.ttype == '\n') {
+	      a.m_data = a.m_data.concat("\n");
+	    }
+	    else {
+	      System.out.println("error getting data");
+	    }
+	  }
+	  setSyntax();
+	}
+	else {
+	  System.out.println("error getting data");
+	}
+      }
+    }
+  }
+
+  //special class for use in creating the tree
+
+  /**
+   * This is an internal class used to keep track of the info for the objects 
+   * before they are 
+   * actually created.
+   */
+  private class InfoObject {
+    /** The ID string for th object. */
+    public String m_id;
+
+    /** The color name for the object. */
+    public String m_color;
+
+    /** The font color for the object. not in use. */
+    public String m_fontColor;
+
+    /** The fontsize for the object. not in use. */
+    public String m_fontSize;
+
+    /** The label for the object. */
+    public String m_label;
+
+    /** The shape name of for the object. */
+    public String m_shape;
+
+    /** The backstyle name for the object. */
+    public String m_style;
+
+    /** The source ID of the object. */
+    public String m_source;
+
+    /** The target ID of the object. */
+    public String m_target;
+
+    /** The data for this object. */
+    public String m_data;
+
+    /**
+     * This will construct a new InfoObject with the specified ID string.
+     */
+    public InfoObject(String i) {
+      m_id = i;
+      m_color = null;
+      m_fontColor = null;
+      m_fontSize = null;
+      m_label = null;
+      m_shape = null;
+      m_style = null;
+      m_source = null;
+      m_target = null;
+      m_data = null;
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayEvent.java	(revision 29)
@@ -0,0 +1,86 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TreeDisplayEvent.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+
+/**
+ * An event containing the user selection from the tree display
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class TreeDisplayEvent {
+  public static final int NO_COMMAND = 0;
+  public static final int ADD_CHILDREN = 1;
+  public static final int REMOVE_CHILDREN = 2;
+
+  /** States that the user has accepted the tree. */
+  public static final int ACCEPT = 3;
+
+  /** Asks for another learning scheme to classify this node. */
+  public static final int CLASSIFY_CHILD = 4;
+
+  /** Command to remove instances from this node and send them to the 
+   * VisualizePanel. */
+  public static final int SEND_INSTANCES = 5;
+
+  /** The int representing the action. */
+  private int m_command;
+
+  /** The id string for the node to alter. */
+  private String m_nodeId;
+
+  /**
+   * Constructs an event with the specified command
+   * and what the command is applied to.
+   * @param ar The event type.
+   * @param id The id string for the node to perform the action on.
+   */
+  public TreeDisplayEvent(int ar, String id) {
+    m_command = 0;
+    if (ar == 1 || ar == 2 || ar == 3 || ar == 4 || ar == 5) {
+      //then command is good
+      m_command = ar;
+    }
+    m_nodeId = id;
+  }
+
+  /**
+   * @return The command.
+   */
+  public int getCommand() {
+    return m_command;
+  }
+  
+  /**
+   * @return The id of the node.
+   */
+  public String getID() {
+    return m_nodeId;
+  }
+}
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeDisplayListener.java	(revision 29)
@@ -0,0 +1,47 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TreeDisplayListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+//this is simply used to get some user changes from the displayer to an actual
+//class
+//that contains the actual structure of the data the displayer is displaying
+
+/**
+ * Interface implemented by classes that wish to recieve user selection events
+ * from a tree displayer.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface TreeDisplayListener {
+
+  /**
+   * Gets called when the user selects something, in the tree display.
+   * @param e Contains what the user selected with what it was selected for.
+   */
+  void userCommand(TreeDisplayEvent e);
+}
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.java	(revision 29)
@@ -0,0 +1,2225 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    TreeVisualizer.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.treevisualizer;
+
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.gui.visualize.PrintablePanel;
+import weka.gui.visualize.VisualizePanel;
+import weka.gui.visualize.VisualizeUtils;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.JTextField;
+import javax.swing.Timer;
+
+/**
+ * Class for displaying a Node structure in Swing. <p>
+ *
+ * To work this class simply create an instance of it.<p>
+ *
+ * Assign it to a window or other such object.<p>
+ *
+ * Resize it to the desired size.<p>
+ *
+ *
+ * When using the Displayer hold the left mouse button to drag the 
+ * tree around. <p>
+ *
+ * Click the left mouse button with ctrl to shrink the size of the tree 
+ * by half. <p>
+ *
+ * Click and drag with the left mouse button and shift to draw a box,
+ * when the left mouse button is released the contents of the box 
+ * will be magnified 
+ * to fill the screen. <p> <p>
+ *
+ * Click the right mouse button to bring up a menu. <p>
+ * Most options are self explanatory.<p>
+ *
+ * Select Auto Scale to set the tree to it's optimal display size.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 4960 $
+ */
+public class TreeVisualizer
+  extends PrintablePanel
+  implements MouseMotionListener, MouseListener, ActionListener, ItemListener {
+
+  /** for serialization */
+  private static final long serialVersionUID = -8668637962504080749L;
+
+  /** the props file. */
+  public final static String PROPERTIES_FILE = "weka/gui/treevisualizer/TreeVisualizer.props";
+  
+  /** The placement algorithm for the Node structure. */
+  private NodePlace m_placer;  
+
+  /** The top Node. */
+  private Node m_topNode;
+  
+  /** The postion of the view relative to the tree. */
+  private Dimension m_viewPos;        
+
+  /** The size of the tree in pixels. */
+  private Dimension m_viewSize;      
+  
+  /** The font used to display the tree. */
+  private Font m_currentFont;        
+
+  /** The size information for the current font. */
+  private FontMetrics m_fontSize;    
+
+  /** The number of Nodes in the tree. */
+  private int m_numNodes;
+
+  /** The number of levels in the tree. */
+  private int m_numLevels;     
+
+  /** An array with the Nodes sorted into it and display information 
+   * about the Nodes. */
+  private NodeInfo[] m_nodes;
+
+  /** An array with the Edges sorted into it and display information 
+   * about the Edges. */
+  private EdgeInfo[] m_edges;
+                     
+  /** A timer to keep the frame rate constant. */
+  private Timer m_frameLimiter;      
+                         
+  /** Describes the action the user is performing. */
+  private int m_mouseState;           
+
+  /** A variable used to tag the start pos of a user action. */
+  private Dimension m_oldMousePos;
+
+  /** A variable used to tag the most current point of a user action. */
+  private Dimension m_newMousePos;
+
+  /** A variable used to determine for the clicked method if any other 
+   * mouse state has already taken place. */
+  private boolean m_clickAvailable;
+
+  /** A variable used to remember the desired view pos. */
+  private Dimension m_nViewPos;     
+
+  /** A variable used to remember the desired tree size. */
+  private Dimension m_nViewSize;       
+
+  /** The number of frames left to calculate. */
+  private int m_scaling;         
+
+  /** A right (or middle) click popup menu. */
+  private JPopupMenu m_winMenu;
+
+  /** An option on the win_menu */
+  private JMenuItem m_topN;
+
+  /** An option on the win_menu*/
+  private JMenuItem m_fitToScreen;
+
+  /** An option on the win_menu */
+  private JMenuItem m_autoScale;
+
+  /** A sub group on the win_menu */
+  private JMenu m_selectFont;
+
+  /** A grouping for the font choices */
+  private ButtonGroup m_selectFontGroup;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size24;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size22;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size20;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size18;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size16;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size14;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size12;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size10;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size8;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size6;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size4;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size2;
+
+  /** A font choice. */
+  private JRadioButtonMenuItem m_size1;
+
+  /** An option on the win menu. */
+  private JMenuItem m_accept;
+
+  /** A right or middle click popup menu for nodes. */
+  private JPopupMenu m_nodeMenu;
+
+  /** A visualize choice for the node, may not be available. */
+  private JMenuItem m_visualise;
+
+  /** 
+   * An add children to Node choice, This is only available if the tree
+   * display has a treedisplay listerner added to it.
+   */
+  private JMenuItem m_addChildren;
+
+  /** Similar to add children but now it removes children. */
+  private JMenuItem m_remChildren;
+
+  /** Use this to have J48 classify this node. */
+  private JMenuItem m_classifyChild;
+  
+  /** Use this to dump the instances from this node to the vis panel. */
+  private JMenuItem m_sendInstances;
+
+  /** The subscript for the currently selected node (this is an internal 
+   * thing, so the user is unaware of this). */
+  private int m_focusNode;
+
+  /**
+   * The Node the user is currently focused on , this is similar to 
+   * focus node except that it is used by other 
+   * classes rather than this one.
+   */
+  private int m_highlightNode;
+  
+  /* A pointer to this tree's classifier if a classifier is using it. */
+  //private UserClassifier classer;
+  private TreeDisplayListener m_listener;
+
+  private JTextField m_searchString;
+  private JDialog m_searchWin;
+  private JRadioButton m_caseSen;
+
+  /** the font color. */
+  protected Color m_FontColor = null;
+
+  /** the background color. */
+  protected Color m_BackgroundColor = null;
+
+  /** the node color. */
+  protected Color m_NodeColor = null;
+
+  /** the line color. */
+  protected Color m_LineColor = null;
+
+  /** the color of the zoombox. */
+  protected Color m_ZoomBoxColor = null;
+
+  /** the XOR color of the zoombox. */
+  protected Color m_ZoomBoxXORColor = null;
+  
+  /** whether to show the border or not. */
+  protected boolean m_ShowBorder = true;
+  
+  ///////////////////
+
+  //this is the event fireing stuff
+
+
+  /**
+   * Constructs Displayer to display a tree provided in a dot format.
+   * Uses the NodePlacer to place the Nodes.
+   * @param tdl listener 
+   * @param dot string containing the dot representation of the tree to
+   * display
+   * @param p the algorithm to be used to position the nodes.
+   */
+  public TreeVisualizer(TreeDisplayListener tdl, String dot, NodePlace p) {
+    super();
+
+    initialize();
+    
+    //generate the node structure in here
+    if (m_ShowBorder)
+      setBorder(BorderFactory.createTitledBorder("Tree View")); 
+    m_listener = tdl;
+
+    TreeBuild builder = new TreeBuild();
+    
+    Node n = null;
+    NodePlace arrange = new PlaceNode2();
+    n = builder.create(new StringReader(dot));
+    //    System.out.println(n.getCount(n, 0));
+    //if the size needs to be automatically alocated I will do it here
+    m_highlightNode = 5;
+    m_topNode = n;
+    m_placer = p;
+    m_placer.place(m_topNode);
+    m_viewPos = new Dimension(0, 0);    //will be adjusted 
+    m_viewSize = new Dimension(800, 600);   //I allocate this now so that
+    //the tree will be visible
+    //when the panel is enlarged
+
+    m_nViewPos = new Dimension(0, 0);            
+    m_nViewSize = new Dimension(800, 600);          
+                                      
+    m_scaling = 0;
+    
+    m_numNodes = m_topNode.getCount(m_topNode,0);   //note the second 
+    //argument must be a zero, this is a 
+    //recursive function
+
+    m_numLevels = m_topNode.getHeight(m_topNode,0);
+  
+    m_nodes = new NodeInfo[m_numNodes];
+    m_edges = new EdgeInfo[m_numNodes-1];
+    
+
+    arrayFill(m_topNode, m_nodes, m_edges);
+    
+    changeFontSize(12);
+
+    m_mouseState = 0;
+    m_oldMousePos = new Dimension(0, 0);
+    m_newMousePos = new Dimension(0, 0);
+    m_frameLimiter = new Timer(120, this);
+
+
+
+    m_winMenu = new JPopupMenu();
+    m_topN = new JMenuItem("Center on Top Node");           //note to change 
+    //language change this line
+    m_topN.setActionCommand("Center on Top Node");          //but not this one,
+    //same for all menu items
+    m_fitToScreen = new JMenuItem("Fit to Screen");
+    m_fitToScreen.setActionCommand("Fit to Screen");
+    //unhide = new JMenuItem("Unhide all Nodes");
+    m_selectFont = new JMenu("Select Font");
+    m_selectFont.setActionCommand("Select Font");
+    m_autoScale = new JMenuItem("Auto Scale");
+    m_autoScale.setActionCommand("Auto Scale");
+    m_selectFontGroup = new ButtonGroup();
+    
+    m_accept = new JMenuItem("Accept The Tree");
+    m_accept.setActionCommand("Accept The Tree");
+    
+    m_winMenu.add(m_topN);
+    m_winMenu.addSeparator();
+    m_winMenu.add(m_fitToScreen);
+    m_winMenu.add(m_autoScale);
+    //m_winMenu.addSeparator();
+    //m_winMenu.add(unhide);
+    m_winMenu.addSeparator();
+    m_winMenu.add(m_selectFont);
+
+    if (m_listener != null) {
+      m_winMenu.addSeparator();
+      m_winMenu.add(m_accept);
+    }
+    
+    m_topN.addActionListener(this);
+    m_fitToScreen.addActionListener(this);
+    //unhide.addActionListener(this);
+    m_autoScale.addActionListener(this);
+    m_accept.addActionListener(this);
+        
+    m_size24 = new JRadioButtonMenuItem("Size 24",false);//,select_font_group);
+    m_size22 = new JRadioButtonMenuItem("Size 22",false);//,select_font_group);
+    m_size20 = new JRadioButtonMenuItem("Size 20",false);//,select_font_group);
+    m_size18 = new JRadioButtonMenuItem("Size 18",false);//,select_font_group);
+    m_size16 = new JRadioButtonMenuItem("Size 16",false);//,select_font_group);
+    m_size14 = new JRadioButtonMenuItem("Size 14",false);//,select_font_group);
+    m_size12 = new JRadioButtonMenuItem("Size 12",true);//,select_font_group);
+    m_size10 = new JRadioButtonMenuItem("Size 10",false);//,select_font_group);
+    m_size8 = new JRadioButtonMenuItem("Size 8",false);//,select_font_group);
+    m_size6 = new JRadioButtonMenuItem("Size 6",false);//,select_font_group);
+    m_size4 = new JRadioButtonMenuItem("Size 4",false);//,select_font_group);
+    m_size2 = new JRadioButtonMenuItem("Size 2",false);//,select_font_group);
+    m_size1 = new JRadioButtonMenuItem("Size 1",false);//,select_font_group);
+
+    m_size24.setActionCommand("Size 24");//,select_font_group);
+    m_size22.setActionCommand("Size 22");//,select_font_group);
+    m_size20.setActionCommand("Size 20");//,select_font_group);
+    m_size18.setActionCommand("Size 18");//,select_font_group);
+    m_size16.setActionCommand("Size 16");//,select_font_group);
+    m_size14.setActionCommand("Size 14");//,select_font_group);
+    m_size12.setActionCommand("Size 12");//,select_font_group);
+    m_size10.setActionCommand("Size 10");//,select_font_group);
+    m_size8.setActionCommand("Size 8");//,select_font_group);
+    m_size6.setActionCommand("Size 6");//,select_font_group);
+    m_size4.setActionCommand("Size 4");//,select_font_group);
+    m_size2.setActionCommand("Size 2");//,select_font_group);
+    m_size1.setActionCommand("Size 1");//,select_font_group);
+    
+    
+    m_selectFontGroup.add(m_size24);
+    m_selectFontGroup.add(m_size22);
+    m_selectFontGroup.add(m_size20);
+    m_selectFontGroup.add(m_size18);
+    m_selectFontGroup.add(m_size16);
+    m_selectFontGroup.add(m_size14);
+    m_selectFontGroup.add(m_size12);
+    m_selectFontGroup.add(m_size10);
+    m_selectFontGroup.add(m_size8);
+    m_selectFontGroup.add(m_size6);
+    m_selectFontGroup.add(m_size4);
+    m_selectFontGroup.add(m_size2);
+    m_selectFontGroup.add(m_size1);
+
+    
+    m_selectFont.add(m_size24);
+    m_selectFont.add(m_size22);
+    m_selectFont.add(m_size20);
+    m_selectFont.add(m_size18);
+    m_selectFont.add(m_size16);
+    m_selectFont.add(m_size14);
+    m_selectFont.add(m_size12);
+    m_selectFont.add(m_size10);
+    m_selectFont.add(m_size8);
+    m_selectFont.add(m_size6);
+    m_selectFont.add(m_size4);
+    m_selectFont.add(m_size2);
+    m_selectFont.add(m_size1);
+
+
+    m_size24.addItemListener(this);
+    m_size22.addItemListener(this);
+    m_size20.addItemListener(this);
+    m_size18.addItemListener(this);
+    m_size16.addItemListener(this);
+    m_size14.addItemListener(this);
+    m_size12.addItemListener(this);
+    m_size10.addItemListener(this);
+    m_size8.addItemListener(this);
+    m_size6.addItemListener(this);
+    m_size4.addItemListener(this);
+    m_size2.addItemListener(this);
+    m_size1.addItemListener(this);
+
+    /*
+      search_string = new JTextField(22);
+      search_win = new JDialog();
+      case_sen = new JRadioButton("Case Sensitive");
+
+
+
+      search_win.getContentPane().setLayout(null);
+      search_win.setSize(300, 200);
+ 
+      search_win.getContentPane().add(search_string);
+      search_win.getContentPane().add(case_sen);
+
+      search_string.setLocation(50, 70);
+      case_sen.setLocation(50, 120);
+      case_sen.setSize(100, 24); 
+      search_string.setSize(100, 24);
+      //search_string.setVisible(true);
+      //case_sen.setVisible(true);
+
+      //search_win.setVisible(true);
+    */
+
+    m_nodeMenu = new JPopupMenu();
+    /* A visualize choice for the node, may not be available. */
+    m_visualise = new JMenuItem("Visualize The Node");
+    m_visualise.setActionCommand("Visualize The Node");
+    m_visualise.addActionListener(this);
+    m_nodeMenu.add(m_visualise);
+   
+    if (m_listener != null) {
+      m_remChildren = new JMenuItem("Remove Child Nodes");
+      m_remChildren.setActionCommand("Remove Child Nodes");
+      m_remChildren.addActionListener(this);
+      m_nodeMenu.add(m_remChildren);
+      
+      
+      m_classifyChild = new JMenuItem("Use Classifier...");
+      m_classifyChild.setActionCommand("classify_child");
+      m_classifyChild.addActionListener(this);
+      m_nodeMenu.add(m_classifyChild);
+      
+      /*m_sendInstances = new JMenuItem("Add Instances To Viewer");
+      m_sendInstances.setActionCommand("send_instances");
+      m_sendInstances.addActionListener(this);
+      m_nodeMenu.add(m_sendInstances); */
+      
+    }
+    
+    m_focusNode = -1;
+    m_highlightNode = -1;
+    
+    addMouseMotionListener(this);
+    addMouseListener(this);
+    //repaint();
+    //frame_limiter.setInitialDelay();
+    m_frameLimiter.setRepeats(false);
+    m_frameLimiter.start();
+  }
+  
+  /**
+   * Constructs Displayer with the specified Node as the top 
+   * of the tree, and uses the NodePlacer to place the Nodes.
+   * @param tdl listener.
+   * @param n the top Node of the tree to be displayed.
+   * @param p the algorithm to be used to position the nodes.
+   */  
+  public TreeVisualizer(TreeDisplayListener tdl, Node n, NodePlace p) {
+    super();
+
+    initialize();
+    
+    //if the size needs to be automatically alocated I will do it here
+    if (m_ShowBorder)
+      setBorder(BorderFactory.createTitledBorder("Tree View")); 
+    m_listener = tdl;
+    m_topNode = n;
+    m_placer = p;
+    m_placer.place(m_topNode);
+    m_viewPos = new Dimension(0, 0);    //will be adjusted 
+    m_viewSize = new Dimension(800, 600);   //I allocate this now so that
+    //the tree will be visible
+    //when the panel is enlarged
+
+    m_nViewPos = new Dimension(0, 0);            
+    m_nViewSize = new Dimension(800, 600);          
+                                      
+    m_scaling = 0;
+    
+    m_numNodes = m_topNode.getCount(m_topNode,0);   //note the second argument 
+    //must be a zero, this is a 
+    //recursive function
+
+    m_numLevels = m_topNode.getHeight(m_topNode,0);
+  
+    m_nodes = new NodeInfo[m_numNodes];
+    m_edges = new EdgeInfo[m_numNodes-1];
+
+    arrayFill(m_topNode, m_nodes, m_edges);
+    
+    changeFontSize(12);
+
+    m_mouseState = 0;
+    m_oldMousePos = new Dimension(0, 0);
+    m_newMousePos = new Dimension(0, 0);
+    m_frameLimiter = new Timer(120, this);
+
+
+
+
+
+    m_winMenu = new JPopupMenu();
+    m_topN = new JMenuItem("Center on Top Node");           //note to change 
+    //language change this line
+    m_topN.setActionCommand("Center on Top Node");          //but not this 
+    //one, same for all menu items
+    m_fitToScreen = new JMenuItem("Fit to Screen");
+    m_fitToScreen.setActionCommand("Fit to Screen");
+    //unhide = new JMenuItem("Unhide all Nodes");
+    m_selectFont = new JMenu("Select Font");
+    m_selectFont.setActionCommand("Select Font");
+    m_autoScale = new JMenuItem("Auto Scale");
+    m_autoScale.setActionCommand("Auto Scale");
+    m_selectFontGroup = new ButtonGroup();
+    
+    m_accept = new JMenuItem("Accept The Tree");
+    m_accept.setActionCommand("Accept The Tree");
+    
+    m_winMenu.add(m_topN);
+    m_winMenu.addSeparator();
+    m_winMenu.add(m_fitToScreen);
+    m_winMenu.add(m_autoScale);
+    m_winMenu.addSeparator();
+    //m_winMenu.add(unhide);
+    m_winMenu.addSeparator();
+    m_winMenu.add(m_selectFont);
+    m_winMenu.addSeparator();
+
+    if (m_listener != null) {
+      m_winMenu.add(m_accept);
+    }
+    
+    m_topN.addActionListener(this);
+    m_fitToScreen.addActionListener(this);
+    //unhide.addActionListener(this);
+    m_autoScale.addActionListener(this);
+    m_accept.addActionListener(this);
+        
+    m_size24 = new JRadioButtonMenuItem("Size 24",false);//,select_font_group);
+    m_size22 = new JRadioButtonMenuItem("Size 22",false);//,select_font_group);
+    m_size20 = new JRadioButtonMenuItem("Size 20",false);//,select_font_group);
+    m_size18 = new JRadioButtonMenuItem("Size 18",false);//,select_font_group);
+    m_size16 = new JRadioButtonMenuItem("Size 16",false);//,select_font_group);
+    m_size14 = new JRadioButtonMenuItem("Size 14",false);//,select_font_group);
+    m_size12 = new JRadioButtonMenuItem("Size 12",true);//,select_font_group);
+    m_size10 = new JRadioButtonMenuItem("Size 10",false);//,select_font_group);
+    m_size8 = new JRadioButtonMenuItem("Size 8",false);//,select_font_group);
+    m_size6 = new JRadioButtonMenuItem("Size 6",false);//,select_font_group);
+    m_size4 = new JRadioButtonMenuItem("Size 4",false);//,select_font_group);
+    m_size2 = new JRadioButtonMenuItem("Size 2",false);//,select_font_group);
+    m_size1 = new JRadioButtonMenuItem("Size 1",false);//,select_font_group);
+
+    m_size24.setActionCommand("Size 24");//,select_font_group);
+    m_size22.setActionCommand("Size 22");//,select_font_group);
+    m_size20.setActionCommand("Size 20");//,select_font_group);
+    m_size18.setActionCommand("Size 18");//,select_font_group);
+    m_size16.setActionCommand("Size 16");//,select_font_group);
+    m_size14.setActionCommand("Size 14");//,select_font_group);
+    m_size12.setActionCommand("Size 12");//,select_font_group);
+    m_size10.setActionCommand("Size 10");//,select_font_group);
+    m_size8.setActionCommand("Size 8");//,select_font_group);
+    m_size6.setActionCommand("Size 6");//,select_font_group);
+    m_size4.setActionCommand("Size 4");//,select_font_group);
+    m_size2.setActionCommand("Size 2");//,select_font_group);
+    m_size1.setActionCommand("Size 1");//,select_font_group);
+
+
+
+    
+    
+    m_selectFontGroup.add(m_size24);
+    m_selectFontGroup.add(m_size22);
+    m_selectFontGroup.add(m_size20);
+    m_selectFontGroup.add(m_size18);
+    m_selectFontGroup.add(m_size16);
+    m_selectFontGroup.add(m_size14);
+    m_selectFontGroup.add(m_size12);
+    m_selectFontGroup.add(m_size10);
+    m_selectFontGroup.add(m_size8);
+    m_selectFontGroup.add(m_size6);
+    m_selectFontGroup.add(m_size4);
+    m_selectFontGroup.add(m_size2);
+    m_selectFontGroup.add(m_size1);
+
+
+
+    
+    m_selectFont.add(m_size24);
+    m_selectFont.add(m_size22);
+    m_selectFont.add(m_size20);
+    m_selectFont.add(m_size18);
+    m_selectFont.add(m_size16);
+    m_selectFont.add(m_size14);
+    m_selectFont.add(m_size12);
+    m_selectFont.add(m_size10);
+    m_selectFont.add(m_size8);
+    m_selectFont.add(m_size6);
+    m_selectFont.add(m_size4);
+    m_selectFont.add(m_size2);
+    m_selectFont.add(m_size1);
+
+
+    m_size24.addItemListener(this);
+    m_size22.addItemListener(this);
+    m_size20.addItemListener(this);
+    m_size18.addItemListener(this);
+    m_size16.addItemListener(this);
+    m_size14.addItemListener(this);
+    m_size12.addItemListener(this);
+    m_size10.addItemListener(this);
+    m_size8.addItemListener(this);
+    m_size6.addItemListener(this);
+    m_size4.addItemListener(this);
+    m_size2.addItemListener(this);
+    m_size1.addItemListener(this);
+
+
+
+
+    /*
+      search_string = new JTextField(22);
+      search_win = new JDialog();
+      case_sen = new JRadioButton("Case Sensitive");
+
+
+
+      search_win.getContentPane().setLayout(null);
+      search_win.setSize(300, 200);
+ 
+      search_win.getContentPane().add(search_string);
+      search_win.getContentPane().add(case_sen);
+
+      search_string.setLocation(50, 70);
+      case_sen.setLocation(50, 120);
+      case_sen.setSize(100, 24); 
+      search_string.setSize(100, 24);
+      //search_string.setVisible(true);
+      //case_sen.setVisible(true);
+
+      search_win.setVisible(true);
+    */
+
+
+    m_nodeMenu = new JPopupMenu();
+    /* A visualize choice for the node, may not be available. */
+    m_visualise = new JMenuItem("Visualize The Node");
+    m_visualise.setActionCommand("Visualize The Node");
+    m_visualise.addActionListener(this);
+    m_nodeMenu.add(m_visualise);
+
+    if (m_listener != null) {
+      m_remChildren = new JMenuItem("Remove Child Nodes");
+      m_remChildren.setActionCommand("Remove Child Nodes");
+      m_remChildren.addActionListener(this);
+      m_nodeMenu.add(m_remChildren);
+      
+      m_classifyChild = new JMenuItem("Use Classifier...");
+      m_classifyChild.setActionCommand("classify_child");
+      m_classifyChild.addActionListener(this);
+      m_nodeMenu.add(m_classifyChild);
+      
+      m_sendInstances = new JMenuItem("Add Instances To Viewer");
+      m_sendInstances.setActionCommand("send_instances");
+      m_sendInstances.addActionListener(this);
+      m_nodeMenu.add(m_sendInstances);
+      
+      
+    }
+  
+    m_focusNode = -1;
+    m_highlightNode = -1;
+    
+
+    addMouseMotionListener(this);
+    addMouseListener(this);
+
+  
+    //repaint();
+
+    //frame_limiter.setInitialDelay();
+    m_frameLimiter.setRepeats(false);
+    m_frameLimiter.start();
+  }
+
+  /**
+   * Processes the color string. Returns null if empty.
+   * 
+   * @param colorStr	the string to process
+   * @return		the processed color or null
+   */
+  protected Color getColor(String colorStr) {
+    Color	result;
+    
+    result = null;
+    
+    if ((colorStr != null) && (colorStr.length() > 0))
+      result = VisualizeUtils.processColour(colorStr, result);
+    
+    return result;
+  }
+  
+  /**
+   * Performs some initialization.
+   */
+  protected void initialize() {
+    Properties	props;
+    
+    try {
+      props = Utils.readProperties(PROPERTIES_FILE);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      props = new Properties();
+    }
+    
+    m_FontColor       = getColor(props.getProperty("FontColor", ""));
+    m_BackgroundColor = getColor(props.getProperty("BackgroundColor", ""));
+    m_NodeColor       = getColor(props.getProperty("NodeColor", ""));
+    m_LineColor       = getColor(props.getProperty("LineColor", ""));
+    m_ZoomBoxColor    = getColor(props.getProperty("ZoomBoxColor", ""));
+    m_ZoomBoxXORColor = getColor(props.getProperty("ZoomBoxXORColor", ""));
+    m_ShowBorder      = Boolean.parseBoolean(props.getProperty("ShowBorder", "true"));
+  }
+
+  /**
+   * Fits the tree to the current screen size. Call this after
+   * window has been created to get the entrire tree to be in view
+   * upon launch.
+   */
+  public void fitToScreen() {
+
+    getScreenFit(m_viewPos, m_viewSize);
+    repaint();
+  }
+
+  /**
+   * Calculates the dimensions needed to fit the entire tree into view.
+   */
+  private void getScreenFit(Dimension np, Dimension ns) {
+
+    int leftmost = 1000000, rightmost = -1000000;
+    int leftCenter = 1000000, rightCenter = -1000000, rightNode = 0;
+    int highest = -1000000, highTop = -1000000;
+    for (int noa = 0; noa < m_numNodes; noa++) {
+      calcScreenCoords(noa);
+      if (m_nodes[noa].m_center - m_nodes[noa].m_side < leftmost) {
+	leftmost = m_nodes[noa].m_center - m_nodes[noa].m_side;
+      }
+      if (m_nodes[noa].m_center < leftCenter) {
+	leftCenter = m_nodes[noa].m_center;
+      }
+      
+      if (m_nodes[noa].m_center + m_nodes[noa].m_side > rightmost) {
+	rightmost = m_nodes[noa].m_center + m_nodes[noa].m_side;	  
+      }
+      if (m_nodes[noa].m_center > rightCenter) {
+	rightCenter = m_nodes[noa].m_center;
+	rightNode = noa;
+      }
+	if (m_nodes[noa].m_top + m_nodes[noa].m_height > highest) {
+	  highest = m_nodes[noa].m_top + m_nodes[noa].m_height;
+	}
+	if (m_nodes[noa].m_top > highTop) {
+	  highTop = m_nodes[noa].m_top;
+	}
+    }
+    
+    ns.width = getWidth();
+    ns.width -= leftCenter - leftmost + rightmost - rightCenter + 30;
+    ns.height = getHeight() - highest + highTop - 40;
+    
+    if (m_nodes[rightNode].m_node.getCenter() != 0 
+	&& leftCenter != rightCenter) {
+	ns.width /= m_nodes[rightNode].m_node.getCenter();
+    }
+    if (ns.width < 10)
+      {
+	ns.width = 10;
+      }
+    if (ns.height < 10)
+      {
+	ns.height = 10;
+      }
+    
+    np.width = (leftCenter - leftmost + rightmost - rightCenter) / 2 + 15;
+    np.height = (highest - highTop) / 2 + 20;
+  }
+
+  /**
+   * Performs the action associated with the ActionEvent.
+   *
+   * @param e the action event.
+   */
+  public void actionPerformed(ActionEvent e) {
+    
+    //JMenuItem m = (JMenuItem)e.getSource();
+    
+    if (e.getActionCommand() == null) {
+      if (m_scaling == 0) {
+	repaint();
+      }
+      else {
+	animateScaling(m_nViewPos, m_nViewSize, m_scaling);
+      }
+    }
+    else if (e.getActionCommand().equals("Fit to Screen")) {
+      
+      Dimension np = new Dimension();
+      Dimension ns = new Dimension();
+
+      getScreenFit(np, ns);
+
+      animateScaling(np, ns, 10);
+      
+    }
+    else if (e.getActionCommand().equals("Center on Top Node")) {
+      
+      int tpx = (int)(m_topNode.getCenter() * m_viewSize.width);   //calculate
+      //the top nodes postion but don't adjust for where 
+      int tpy = (int)(m_topNode.getTop() * m_viewSize.height);     //view is
+      
+      
+      
+      Dimension np = new Dimension(getSize().width / 2 - tpx, 
+				   getSize().width / 6 - tpy);
+      
+      animateScaling(np, m_viewSize, 10);
+      
+    }
+    else if (e.getActionCommand().equals("Auto Scale")) {
+      autoScale();  //this will figure the best scale value 
+      //keep the focus on the middle of the screen and call animate
+    }
+    else if (e.getActionCommand().equals("Visualize The Node")) {
+      //send the node data to the visualizer 
+      if (m_focusNode >= 0) {
+	Instances inst;
+	if ((inst = m_nodes[m_focusNode].m_node.getInstances()) != null) {
+	  VisualizePanel pan = new VisualizePanel();
+	  pan.setInstances(inst);
+	  JFrame nf = new JFrame();
+	  nf.setSize(400, 300);
+	  nf.getContentPane().add(pan);
+	  nf.setVisible(true);
+	}
+	else {
+	  JOptionPane.showMessageDialog(this, "Sorry, there is no " + 
+					"available Instances data for " +
+					"this Node.", "Sorry!",
+					JOptionPane.WARNING_MESSAGE); 
+	}
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Error, there is no " + 
+				      "selected Node to perform " +
+				      "this operation on.", "Error!",
+				      JOptionPane.ERROR_MESSAGE); 
+      }
+    }
+    else if (e.getActionCommand().equals("Create Child Nodes")) {
+      if (m_focusNode >= 0) {
+	if (m_listener != null) {
+	  //then send message to the listener
+	  m_listener.userCommand(new TreeDisplayEvent
+	    (TreeDisplayEvent.ADD_CHILDREN, 
+	     m_nodes[m_focusNode].m_node.getRefer()));
+	}
+	else {
+	  JOptionPane.showMessageDialog(this, "Sorry, there is no " + 
+					"available Decision Tree to " +
+					"perform this operation on.",
+					"Sorry!", 
+					JOptionPane.WARNING_MESSAGE);
+	}
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Error, there is no " +
+				      "selected Node to perform this " +
+				      "operation on.", "Error!",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else if (e.getActionCommand().equals("Remove Child Nodes")) {
+      if (m_focusNode >= 0) {
+	if (m_listener != null) {
+	  //then send message to the listener
+	  m_listener.userCommand(new 
+	    TreeDisplayEvent(TreeDisplayEvent.REMOVE_CHILDREN, 
+			     m_nodes[m_focusNode].m_node.getRefer()));
+	}
+	else {
+	  JOptionPane.showMessageDialog(this, "Sorry, there is no " + 
+					"available Decsion Tree to " +
+					"perform this operation on.",
+					"Sorry!", 
+					JOptionPane.WARNING_MESSAGE);
+	}
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Error, there is no " +
+				      "selected Node to perform this " +
+				      "operation on.", "Error!",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else if (e.getActionCommand().equals("classify_child")) {
+      if (m_focusNode >= 0) {
+	if (m_listener != null) {
+	  //then send message to the listener
+	  m_listener.userCommand(new TreeDisplayEvent
+	    (TreeDisplayEvent.CLASSIFY_CHILD, 
+	     m_nodes[m_focusNode].m_node.getRefer()));
+	}
+	else {
+	  JOptionPane.showMessageDialog(this, "Sorry, there is no " + 
+					"available Decsion Tree to " +
+					"perform this operation on.",
+					"Sorry!", 
+					JOptionPane.WARNING_MESSAGE);
+	}
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Error, there is no " +
+				      "selected Node to perform this " +
+				      "operation on.", "Error!",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else if (e.getActionCommand().equals("send_instances")) {
+      if (m_focusNode >= 0) {
+	if (m_listener != null) {
+	  //then send message to the listener
+	  m_listener.userCommand(new TreeDisplayEvent
+	    (TreeDisplayEvent.SEND_INSTANCES, 
+	     m_nodes[m_focusNode].m_node.getRefer()));
+	}
+	else {
+	  JOptionPane.showMessageDialog(this, "Sorry, there is no " + 
+					"available Decsion Tree to " +
+					"perform this operation on.",
+					"Sorry!", 
+					JOptionPane.WARNING_MESSAGE);
+	}
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Error, there is no " +
+				      "selected Node to perform this " +
+				      "operation on.", "Error!",
+				      JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else if (e.getActionCommand().equals("Accept The Tree")) {
+      if (m_listener != null) {
+	//then send message to the listener saying that the tree is done
+	m_listener.userCommand(new TreeDisplayEvent(TreeDisplayEvent.ACCEPT,
+						  null));
+      }
+      else {
+	JOptionPane.showMessageDialog(this, "Sorry, there is no " +
+				      "available Decision Tree to " +
+				      "perform this operation on.",
+				      "Sorry!", 
+				      JOptionPane.WARNING_MESSAGE);
+      }
+    }
+  }
+
+  /**
+   * Performs the action associated with the ItemEvent.
+   *
+   * @param e the item event.
+   */
+  public void itemStateChanged(ItemEvent e)
+  {
+    JRadioButtonMenuItem c = (JRadioButtonMenuItem)e.getSource();
+    if (c.getActionCommand().equals("Size 24")) {
+      changeFontSize(24);
+    }
+    else if (c.getActionCommand().equals("Size 22")) {
+      changeFontSize(22);
+    }
+    else if (c.getActionCommand().equals("Size 20")) {
+      changeFontSize(20);
+    }
+    else if (c.getActionCommand().equals("Size 18")) {
+      changeFontSize(18);
+    } 
+    else if (c.getActionCommand().equals("Size 16")) {
+      changeFontSize(16);
+    }
+    else if (c.getActionCommand().equals("Size 14")) {
+      changeFontSize(14);
+    }
+    else if (c.getActionCommand().equals("Size 12")) {
+      changeFontSize(12);
+    }
+    else if (c.getActionCommand().equals("Size 10")) {
+      changeFontSize(10);
+    }
+    else if (c.getActionCommand().equals("Size 8")) {
+      changeFontSize(8);
+    }
+    else if (c.getActionCommand().equals("Size 6")) {
+      changeFontSize(6);
+    }
+    else if (c.getActionCommand().equals("Size 4")) {
+      changeFontSize(4);
+    }
+    else if (c.getActionCommand().equals("Size 2")) {
+      changeFontSize(2);
+    }
+    else if (c.getActionCommand().equals("Size 1")) {
+      changeFontSize(1);
+    }
+    else if (c.getActionCommand().equals("Hide Descendants")) {
+      //focus_node.setCVisible(!c.isSelected());
+      //no longer used...
+    }
+  }
+
+  /**
+   * Does nothing.
+   * @param e the mouse event.
+   */
+  public void mouseClicked(MouseEvent e) {
+    //if the mouse was left clicked on 
+    //the node then 
+    if (m_clickAvailable) {
+      //determine if the click was on a node or not
+      int s = -1;
+      
+      for (int noa = 0; noa < m_numNodes;noa++) {
+	if (m_nodes[noa].m_quad == 18) {
+	  //then is on the screen
+	  calcScreenCoords(noa);
+	  if (e.getX() <= m_nodes[noa].m_center + m_nodes[noa].m_side 
+	      && e.getX() 
+	      >= m_nodes[noa].m_center - m_nodes[noa].m_side &&
+	      e.getY() >= m_nodes[noa].m_top && e.getY() 
+	      <= m_nodes[noa].m_top + m_nodes[noa].m_height) {
+	    //then it is this node that the mouse was clicked on
+	    s = noa;
+	  }
+	  m_nodes[noa].m_top = 32000;
+	}
+      }
+      m_focusNode = s;
+      
+      if (m_focusNode != -1) {
+	if (m_listener != null) {
+	  //then set this to be the selected node for editing
+	  actionPerformed(new ActionEvent(this, 32000, "Create Child Nodes"));
+	  
+	}
+	else {
+	  //then open a visualize to display this nodes instances if possible
+	  actionPerformed(new ActionEvent(this, 32000, "Visualize The Node"));
+	}
+      }
+    }
+  }
+  
+  /**
+   * Determines what action the user wants to perform.
+   *
+   * @param e the mouse event.
+   */  
+  public void mousePressed(MouseEvent e) {
+    m_frameLimiter.setRepeats(true);
+    if ((e.getModifiers() & e.BUTTON1_MASK) != 0 && !e.isAltDown() && 
+	m_mouseState == 0 
+	&& m_scaling == 0) {
+      //then the left mouse button has been pressed
+      //check for modifiers
+      
+      if (((e.getModifiers() & e.CTRL_MASK) != 0) && ((e.getModifiers() & e.SHIFT_MASK) == 0)) {
+	//then is in zoom out mode
+	m_mouseState = 2;
+      }
+      else if (((e.getModifiers() & e.SHIFT_MASK) != 0) && ((e.getModifiers() & e.CTRL_MASK) == 0)) {
+	//then is in zoom mode
+	//note if both are pressed default action is to zoom out
+	m_oldMousePos.width = e.getX();
+	m_oldMousePos.height = e.getY();
+	m_newMousePos.width = e.getX();
+	m_newMousePos.height = e.getY();
+	m_mouseState = 3;
+	
+	Graphics g = getGraphics();
+	if (m_ZoomBoxColor == null)
+	  g.setColor(Color.black);
+	else
+	  g.setColor(m_ZoomBoxColor);
+	if (m_ZoomBoxXORColor == null)
+	  g.setXORMode(Color.white);
+	else
+	  g.setXORMode(m_ZoomBoxXORColor);
+	g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
+		   m_newMousePos.width - m_oldMousePos.width, 
+		   m_newMousePos.height - m_oldMousePos.height);
+	g.dispose();
+      }
+      else {
+	//no modifiers drag area around
+	m_oldMousePos.width = e.getX();
+	m_oldMousePos.height = e.getY();
+	m_newMousePos.width = e.getX();
+	m_newMousePos.height = e.getY();
+	m_mouseState = 1;
+	m_frameLimiter.start();
+      }
+      
+    }
+    // pop up save dialog explicitly (is somehow overridden...)
+    else if ( (e.getButton() == MouseEvent.BUTTON1) && e.isAltDown() && e.isShiftDown() && !e.isControlDown() ) {
+      saveComponent();
+    }
+    else if (m_mouseState == 0 && m_scaling == 0) {
+      //either middle or right mouse button pushed
+      //determine menu to use
+      
+    }
+  }
+  
+  /**
+   * Performs the final stages of what the user wants to perform.
+   *
+   * @param e the mouse event.
+   */
+  public void mouseReleased(MouseEvent e) {
+    if (m_mouseState == 1) {
+      //this is used by mouseClicked to determine if it is alright to do 
+      //something
+      m_clickAvailable = true;
+	//note that a standard click with the left mouse is pretty much the 
+      //only safe input left to be assigned anything.
+    }
+    else {
+      m_clickAvailable = false;
+    }
+    if (m_mouseState == 2 && mouseInBounds(e)) {
+      //then zoom out;
+      m_mouseState = 0;
+      Dimension ns = new Dimension(m_viewSize.width / 2, m_viewSize.height 
+				   / 2);
+      if (ns.width < 10) {
+	ns.width = 10;
+      }
+      if (ns.height < 10) {
+	ns.height = 10;
+      }
+      
+      Dimension d = getSize();
+      Dimension np = new Dimension((int)(d.width / 2 
+					 - ((double)d.width / 2 
+					    - m_viewPos.width) / 2),
+				   (int)(d.height / 2 
+					 - ((double)d.height / 2
+					    - m_viewPos.height) / 2));
+      
+      animateScaling(np, ns, 10);
+      
+      //view_pos.width += view_size.width / 2;
+      //view_pos.height += view_size.height / 2;
+      
+    }
+    else if (m_mouseState == 3) {
+      //then zoom in
+      m_mouseState = 0;
+      Graphics g = getGraphics();
+      if (m_ZoomBoxColor == null)
+	g.setColor(Color.black);
+      else
+	g.setColor(m_ZoomBoxColor);
+      if (m_ZoomBoxXORColor == null)
+	g.setXORMode(Color.white);
+      else
+	g.setXORMode(m_ZoomBoxXORColor);
+      g.drawRect(m_oldMousePos.width, m_oldMousePos.height, 
+		 m_newMousePos.width - m_oldMousePos.width, 
+		 m_newMousePos.height - m_oldMousePos.height);
+      g.dispose();
+      
+      
+      
+      int cw = m_newMousePos.width - m_oldMousePos.width;
+      int ch = m_newMousePos.height - m_oldMousePos.height;
+      if (cw >= 1 && ch >= 1) {
+	if (mouseInBounds(e) && 
+	    (getSize().width / cw) <= 6 &&
+	    (getSize().height / ch) <= 6) {
+	  
+	  //now calculate new position and size
+	  Dimension ns = new Dimension();
+	  Dimension np = new Dimension();
+	  double nvsw = getSize().width / (double)(cw);
+	  double nvsh = getSize().height / (double)(ch);
+	  np.width = (int)((m_oldMousePos.width - m_viewPos.width) * -nvsw);
+	  np.height = (int)((m_oldMousePos.height - m_viewPos.height) * -nvsh);
+	  ns.width = (int)(m_viewSize.width * nvsw);
+	  ns.height = (int)(m_viewSize.height * nvsh);
+	  
+	  animateScaling(np, ns, 10);
+	  
+	  
+	}
+      }
+    }
+    else if (m_mouseState == 0 && m_scaling == 0) {
+      //menu
+      m_mouseState = 0;
+      setFont(new Font("A Name", 0, 12));
+      //determine if the click was on a node or not
+      int s = -1;
+      
+      for (int noa = 0; noa < m_numNodes;noa++) {
+	if (m_nodes[noa].m_quad == 18) {
+	  //then is on the screen
+	  calcScreenCoords(noa);
+	  if (e.getX() <= m_nodes[noa].m_center + m_nodes[noa].m_side 
+	      && e.getX() 
+	      >= m_nodes[noa].m_center - m_nodes[noa].m_side &&
+	      e.getY() >= m_nodes[noa].m_top && e.getY() 
+	      <= m_nodes[noa].m_top + m_nodes[noa].m_height) {
+	    //then it is this node that the mouse was clicked on
+	    s = noa;
+	  }
+	  m_nodes[noa].m_top = 32000;
+	}
+      }
+      if (s == -1) {
+	//the mouse wasn't clicked on a node
+	m_winMenu.show(this,e.getX(),e.getY());
+      }
+      else {
+	//the mouse was clicked on a node
+	m_focusNode = s;
+	m_nodeMenu.show(this, e.getX(), e.getY());
+	
+      }
+      setFont(m_currentFont);
+    }
+    else if (m_mouseState == 1) {
+      //dragging
+      m_mouseState = 0;
+      m_frameLimiter.stop();
+      repaint();
+    }
+    
+  }
+  
+  /**
+   * Checks to see if the coordinates of the mouse lie on this JPanel.
+   *
+   * @param e the mouse event.
+   * @return true if the mouse lies on this JPanel. 
+   */
+  private boolean mouseInBounds(MouseEvent e) {
+    //this returns true if the mouse is currently over the canvas otherwise 
+    //false
+    
+    if (e.getX() < 0 || e.getY() < 0 || e.getX() > getSize().width 
+	|| e.getY() > getSize().height) {
+      return false;
+    }
+    return true;
+  }
+  
+  /**
+   * Performs intermediate updates to what the user wishes to do.
+   *
+   * @param e the mouse event.
+   */
+  public void mouseDragged(MouseEvent e) {
+    //use mouse state to determine what to do to the view of the tree
+    
+    if (m_mouseState == 1) {
+      //then dragging view
+      m_oldMousePos.width = m_newMousePos.width;
+      m_oldMousePos.height = m_newMousePos.height;
+      m_newMousePos.width = e.getX();
+      m_newMousePos.height = e.getY();
+      m_viewPos.width += m_newMousePos.width - m_oldMousePos.width;
+      m_viewPos.height += m_newMousePos.height - m_oldMousePos.height;
+      
+      
+    }
+    else if (m_mouseState == 3) {
+      //then zoom box being created
+      //redraw the zoom box
+      Graphics g = getGraphics();
+      if (m_ZoomBoxColor == null)
+	g.setColor(Color.black);
+      else
+	g.setColor(m_ZoomBoxColor);
+      if (m_ZoomBoxXORColor == null)
+	g.setXORMode(Color.white);
+      else
+	g.setXORMode(m_ZoomBoxXORColor);
+      g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
+		 m_newMousePos.width - m_oldMousePos.width, 
+		 m_newMousePos.height - m_oldMousePos.height);
+      
+      m_newMousePos.width = e.getX();
+      m_newMousePos.height = e.getY();
+      
+      g.drawRect(m_oldMousePos.width, m_oldMousePos.height,
+		 m_newMousePos.width - m_oldMousePos.width, 
+		 m_newMousePos.height - m_oldMousePos.height);
+      g.dispose();
+    }
+    
+    
+  }
+  
+  /**
+   * Does nothing.
+   *
+   * @param e the mouse event.
+   */
+  public void mouseMoved(MouseEvent e) {
+  }
+
+  /**
+   * Does nothing.
+   *
+   * @param e the mouse event.
+   */
+  public void mouseEntered(MouseEvent e) {
+  }
+  
+  /**
+   * Does nothing.
+   * 
+   * @param e the mouse event.
+   */
+  public void mouseExited(MouseEvent e) {
+  }
+
+  /**
+   * Set the highlight for the node with the given id
+   * @param id the id of the node to set the highlight for
+   */
+  public void setHighlight(String id) {
+    //set the highlight for the node with the given id
+    
+    for (int noa = 0; noa < m_numNodes; noa++) {
+      if (id.equals(m_nodes[noa].m_node.getRefer())) {
+	//then highlight this node
+	m_highlightNode = noa;
+      }
+    }
+    //System.out.println("ahuh " + highlight_node + " " + 
+    //nodes[0].node.getRefer());
+    repaint();
+    
+  }
+  
+  /**
+   * Updates the screen contents.
+   *
+   * @param g the drawing surface.
+   */
+  public void paintComponent(Graphics g) {
+    Color oldBackground = ((Graphics2D) g).getBackground();
+    if (m_BackgroundColor != null)
+      ((Graphics2D) g).setBackground(m_BackgroundColor);
+    g.clearRect(0, 0, getSize().width, getSize().height);
+    ((Graphics2D) g).setBackground(oldBackground);
+    g.setClip(3, 7, getWidth() - 6, getHeight() - 10);
+    painter(g);
+    g.setClip(0, 0, getWidth(), getHeight());
+    
+  }
+
+  /**
+   * Draws the tree to the graphics context
+   *
+   * @param g the drawing surface.
+   */
+  private void painter(Graphics g) {
+    //I have moved what would normally be in the paintComponent 
+    //function to here 
+    //for now so that if I do in fact need to do double 
+    //buffering or the like it will be easier
+
+    //this will go through the table of edges and draw the edge if it deems the
+    //two nodes attached to it could cause it to cut the screen or be on it.
+    
+    //in the process flagging all nodes so that they can quickly be put to the
+    //screen if they lie on it
+
+    //I do it in this order because in some circumstances I have seen a line
+    //cut through a node , to make things look better the line will
+    //be drawn under the node
+
+
+    //converting the screen edges to the node scale so that they 
+    //can be positioned relative to the screen
+    //note I give a buffer around the edges of the screen.
+    
+    //when seeing
+    //if a node is on screen I only bother to check the nodes top centre 
+    //if it has large enough size it may still fall onto the screen
+    double left_clip = (double)(-m_viewPos.width - 50) / m_viewSize.width;
+    double right_clip = (double)(getSize().width - m_viewPos.width + 50) / 
+      m_viewSize.width;
+    double top_clip = (double)(-m_viewPos.height - 50) / m_viewSize.height;
+    double bottom_clip = (double)(getSize().height - m_viewPos.height + 50) / 
+      m_viewSize.height;
+  
+
+    
+    //  12 10  9           //the quadrants
+    //  20 18 17
+    //  36 34 33
+
+
+    //first the edges must be rendered
+
+    Edge e;
+    Node r,s;
+    double ncent,ntop;    
+
+    int row = 0, col = 0, pq, cq;
+    for (int noa = 0 ; noa < m_numNodes ; noa++) {
+      r = m_nodes[noa].m_node;
+      if (m_nodes[noa].m_change) {
+	//then recalc row component of quadrant
+	ntop = r.getTop();
+	if (ntop < top_clip) {
+	  row = 8;
+	}
+	else if (ntop > bottom_clip) {
+	  row = 32;
+	}
+	else {
+	  row = 16;
+	}
+      }
+      
+      //calc the column the node falls in for the quadrant
+      ncent = r.getCenter();
+      if (ncent < left_clip) {
+	col = 4;
+      }
+      else if (ncent > right_clip) {
+	col = 1;
+      }
+      else {
+	col = 2;
+      }
+      
+      m_nodes[noa].m_quad = row | col;
+      
+      if (m_nodes[noa].m_parent >= 0) {
+	//this will draw the edge if it should be drawn 
+	//It will do this by eliminating all edges that definitely won't enter
+	//the screen and then draw the rest
+	
+	pq = m_nodes[m_edges[m_nodes[noa].m_parent].m_parent].m_quad;
+	cq = m_nodes[noa].m_quad;
+	
+	//note that this will need to be altered if more than 1 parent exists
+	if ((cq & 8) == 8) {
+	  //then child exists above screen
+	}
+	else if ((pq & 32) == 32) {
+	  //then parent exists below screen
+	}
+	else if ((cq & 4) == 4 && (pq & 4) == 4) {
+	  //then both child and parent exist to the left of the screen
+	}
+	else if ((cq & 1) == 1 && (pq & 1) == 1) {
+	  //then both child and parent exist to the right of the screen
+	}
+	else {
+	  //then draw the line
+	  drawLine(m_nodes[noa].m_parent, g);
+	}
+      }
+      
+      //now draw the nodes
+    }
+    
+    for (int noa = 0 ;noa < m_numNodes; noa++) {
+      if (m_nodes[noa].m_quad == 18) {
+	//then the node is on the screen , draw it
+	drawNode(noa, g);
+      }
+    }
+    
+    if (m_highlightNode >= 0 && m_highlightNode < m_numNodes) {
+      //then draw outline
+      if (m_nodes[m_highlightNode].m_quad == 18) {
+	Color acol;
+	if (m_NodeColor == null)
+	  acol = m_nodes[m_highlightNode].m_node.getColor();
+	else
+	  acol = m_NodeColor;
+	g.setColor(new Color((acol.getRed() + 125) % 256, 
+			     (acol.getGreen() + 125) % 256, 
+			     (acol.getBlue() + 125) % 256));
+	//g.setXORMode(Color.white);
+	if (m_nodes[m_highlightNode].m_node.getShape() == 1) {
+	  g.drawRect(m_nodes[m_highlightNode].m_center 
+		     - m_nodes[m_highlightNode].m_side,
+		     m_nodes[m_highlightNode].m_top, 
+		     m_nodes[m_highlightNode].m_width, 
+		     m_nodes[m_highlightNode].m_height);
+	  
+	  g.drawRect(m_nodes[m_highlightNode].m_center 
+		     - m_nodes[m_highlightNode].m_side 
+		     + 1, m_nodes[m_highlightNode].m_top + 1,
+		     m_nodes[m_highlightNode].m_width - 2, 
+		     m_nodes[m_highlightNode].m_height - 2);
+	}
+	else if (m_nodes[m_highlightNode].m_node.getShape() == 2) {
+	  g.drawOval(m_nodes[m_highlightNode].m_center 
+		     - m_nodes[m_highlightNode].m_side,
+		     m_nodes[m_highlightNode].m_top, 
+		     m_nodes[m_highlightNode].m_width, 
+		     m_nodes[m_highlightNode].m_height);
+	  
+	  g.drawOval(m_nodes[m_highlightNode].m_center 
+		     - m_nodes[m_highlightNode].m_side 
+		     + 1, m_nodes[m_highlightNode].m_top + 1,
+		     m_nodes[m_highlightNode].m_width - 2, 
+		     m_nodes[m_highlightNode].m_height - 2);
+	}
+      }
+    }
+    
+    for (int noa = 0;noa < m_numNodes;noa++) {
+      //this resets the coords so that next time a refresh occurs 
+      //they don't accidentally get used
+      //I will use 32000 to signify that they are invalid, even if this 
+      //coordinate occurs it doesn't
+      //matter as it is only for the sake of the caching
+      
+      m_nodes[noa].m_top = 32000;
+    }
+  }
+  
+  /**
+   * Determines the attributes of the node and draws it.
+   *
+   * @param n A subscript identifying the node in <i>nodes</i> array
+   * @param g The drawing surface
+   */
+  private void drawNode(int n, Graphics g) {
+    //this will draw a node and then print text on it
+    
+    if (m_NodeColor == null)
+      g.setColor(m_nodes[n].m_node.getColor());
+    else
+      g.setColor(m_NodeColor);
+    g.setPaintMode();
+    calcScreenCoords(n);
+    int x = m_nodes[n].m_center - m_nodes[n].m_side;
+    int y = m_nodes[n].m_top;
+    if (m_nodes[n].m_node.getShape() == 1) {
+      g.fill3DRect(x, y, m_nodes[n].m_width, m_nodes[n].m_height, true);
+      drawText(x, y, n, false, g);
+      
+    }
+    else if (m_nodes[n].m_node.getShape() == 2) {
+      
+      g.fillOval(x, y, m_nodes[n].m_width, m_nodes[n].m_height);
+      drawText(x, y + (int)(m_nodes[n].m_height * .15), n, false, g);
+    }
+  }
+  
+  /**
+   * Determines the attributes of the edge and draws it.
+   *
+   * @param e A subscript identifying the edge in <i>edges</i> array.
+   * @param g The drawing surface.
+   */
+  private void drawLine(int e, Graphics g) {
+    //this will draw a line taking in the edge number and then getting 
+    //the nodes subscript for the parent and child entries
+    
+    //this will draw a line that has been broken in the middle 
+    //for the edge text to be displayed 
+    //if applicable
+    
+    //first convert both parent and child node coords to screen coords
+    int p = m_edges[e].m_parent;
+    int c = m_edges[e].m_child;
+    calcScreenCoords(c);
+    calcScreenCoords(p);
+    
+    if (m_LineColor == null)
+      g.setColor(Color.black);
+    else
+      g.setColor(m_LineColor);
+    g.setPaintMode();
+    
+    if (m_currentFont.getSize() < 2) {
+      //text to small to bother cutting the edge
+      g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height, 
+		 m_nodes[c].m_center, m_nodes[c].m_top); 
+      
+    }
+    else {
+      //find where to cut the edge to insert text
+      int e_width = m_nodes[c].m_center - m_nodes[p].m_center;
+      int e_height = m_nodes[c].m_top - (m_nodes[p].m_top 
+					 + m_nodes[p].m_height);
+      int e_width2 = e_width / 2;
+      int e_height2 = e_height / 2;
+      int e_centerx = m_nodes[p].m_center + e_width2;
+      int e_centery = m_nodes[p].m_top + m_nodes[p].m_height + e_height2;
+      int e_offset = m_edges[e].m_tb;
+      
+      int tmp = (int)(((double)e_width / e_height) * 
+		      (e_height2 - e_offset)) + m_nodes[p].m_center;
+      //System.out.println(edges[e].m_height);
+      
+      //draw text now
+      
+      drawText(e_centerx - m_edges[e].m_side, e_centery - e_offset, e, true
+	       , g);
+      
+      
+      if (tmp > (e_centerx - m_edges[e].m_side) && tmp 
+	  < (e_centerx + m_edges[e].m_side)) {
+	//then cut line on top and bottom of text
+	g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height
+		   , tmp, e_centery - e_offset);  //first segment
+	g.drawLine(e_centerx * 2 - tmp, e_centery + e_offset, 
+		   m_nodes[c].m_center, m_nodes[c].m_top);    //second segment
+      }
+      else {
+	e_offset = m_edges[e].m_side;
+	if (e_width < 0) {
+	  e_offset *= -1;   //adjusting for direction which could otherwise 
+	  //screw up the calculation
+	}
+	tmp = (int)(((double)e_height / e_width) * (e_width2 - e_offset)) + 
+	  m_nodes[p].m_top + m_nodes[p].m_height;
+	
+	g.drawLine(m_nodes[p].m_center, m_nodes[p].m_top + m_nodes[p].m_height
+		   , e_centerx - e_offset, tmp);   //first segment
+	g.drawLine(e_centerx + e_offset, e_centery * 2 - tmp, 
+		   m_nodes[c].m_center, m_nodes[c].m_top);  //second segment
+	
+      }
+    }
+    //System.out.println("here" + nodes[p].center);
+  }
+
+  /**
+   * Draws the text for either an Edge or a Node.
+   *
+   * @param x1 the left side of the text area.
+   * @param y1 the top of the text area.
+   * @param s A subscript identifying either a Node or Edge.
+   * @param e_or_n Distinguishes whether it is a node or edge.
+   * @param g The drawing surface.
+   */
+  private void drawText(int x1, int y1, int s, boolean e_or_n, Graphics g) {
+    //this function will take in the rectangle that the text should be 
+    //drawn in as well as the subscript
+    //for either the edge or node and a boolean variable to tell which
+    
+    // backup color
+    Color oldColor = g.getColor();
+
+    g.setPaintMode();
+    if (m_FontColor == null)
+      g.setColor(Color.black);
+    else
+      g.setColor(m_FontColor);
+    String st;
+    if (e_or_n) {
+      //then paint for edge
+      Edge e = m_edges[s].m_edge;
+      for (int noa = 0;(st = e.getLine(noa)) != null; noa++) {
+	g.drawString(st, (m_edges[s].m_width - m_fontSize.stringWidth(st)) / 2 
+		     + x1,
+		     y1 + (noa + 1) * m_fontSize.getHeight()); 
+      }
+    }
+    else {
+      //then paint for node
+      Node e = m_nodes[s].m_node;
+      for (int noa = 0;(st = e.getLine(noa)) != null; noa++) {
+	g.drawString(st, (m_nodes[s].m_width - m_fontSize.stringWidth(st)) / 2 
+		     + x1,
+		     y1 + (noa + 1) * m_fontSize.getHeight()); 
+      }
+    }
+    
+    // restore color
+    g.setColor(oldColor);
+  }
+  
+  /**
+   * Converts the internal coordinates of the node found from <i>n</i>
+   * and converts them to the actual screen coordinates.
+   *
+   * @param n A subscript identifying the Node.
+   */
+  private void calcScreenCoords(int n) {
+    //this converts the coordinate system the Node uses into screen coordinates
+    // System.out.println(n + " " + view_pos.height + " " + 
+    //nodes[n].node.getCenter());
+    if (m_nodes[n].m_top == 32000) {
+      m_nodes[n].m_top = ((int)(m_nodes[n].m_node.getTop() 
+				* m_viewSize.height)) + m_viewPos.height;
+      m_nodes[n].m_center = ((int)(m_nodes[n].m_node.getCenter() 
+				   * m_viewSize.width)) + m_viewPos.width;
+    }
+  }
+
+  /**
+   * This Calculates the minimum size of the tree which will prevent any text
+   * overlapping and make it readable, and then set the size of the tree to 
+   * this.
+   */
+  private void autoScale() {
+    //this function will determine the smallest scale value that keeps the text
+    //from overlapping
+    //it will leave the view centered
+    
+    int dist;
+    Node ln,rn;
+    Dimension temp = new Dimension(10, 10);
+    
+    if (m_numNodes <= 1) {
+      return;
+    }
+    
+    //calc height needed by first node
+    dist = (m_nodes[0].m_height + 40) * m_numLevels;
+    if (dist > temp.height) {
+      temp.height = dist;
+    }
+    
+    for (int noa = 0;noa < m_numNodes - 1;noa++) {
+      calcScreenCoords(noa);  
+      calcScreenCoords(noa+1);
+      if (m_nodes[noa+1].m_change) {
+	//then on a new level so don't check width this time round
+      }
+      else {
+	
+	dist = m_nodes[noa+1].m_center - m_nodes[noa].m_center; 
+	//the distance between the node centers, along horiz
+	if (dist <= 0) {
+	  dist = 1;
+	}
+	dist = ((6 + m_nodes[noa].m_side + m_nodes[noa+1].m_side) 
+		* m_viewSize.width) / dist; //calc optimal size for width
+	
+	if (dist > temp.width) {
+	  
+	  temp.width = dist;
+	}
+      }
+      //now calc.. minimun hieght needed by nodes
+      
+      dist = (m_nodes[noa+1].m_height + 40) * m_numLevels;
+      if (dist > temp.height) {
+	
+	temp.height = dist;
+      }
+    }
+    
+    int y1, y2, xa, xb;
+    
+    y1 = m_nodes[m_edges[0].m_parent].m_top;
+    y2 = m_nodes[m_edges[0].m_child].m_top;
+    
+    dist = y2 - y1;
+    if (dist <= 0) {
+      dist = 1;
+    }
+    dist = ((60 + m_edges[0].m_height + m_nodes[m_edges[0].m_parent].m_height) 
+	    * m_viewSize.height) / dist;
+    if (dist > temp.height) {
+      
+      temp.height = dist;
+    }
+    
+    for (int noa = 0;noa < m_numNodes - 2; noa++) {
+      //check the edges now
+      if (m_nodes[m_edges[noa+1].m_child].m_change) {
+	//then edge is on a different level , so skip this one
+      }
+      else {
+	//calc the width requirements of this pair of edges
+	
+	xa = m_nodes[m_edges[noa].m_child].m_center 
+	  - m_nodes[m_edges[noa].m_parent].m_center;
+	xa /= 2;
+	xa += m_nodes[m_edges[noa].m_parent].m_center;
+	
+	xb = m_nodes[m_edges[noa+1].m_child].m_center - 
+	  m_nodes[m_edges[noa+1].m_parent].m_center;
+	xb /= 2;
+	xb += m_nodes[m_edges[noa+1].m_parent].m_center;
+	
+	dist = xb - xa;
+	if (dist <= 0) {
+	  dist = 1;
+	}
+	dist = ((12 + m_edges[noa].m_side + m_edges[noa+1].m_side) 
+		* m_viewSize.width) 
+	  / dist;
+	if (dist > temp.width) {
+	  
+	  temp.width = dist;
+	}
+      }
+      //now calc height need by the edges
+      y1 = m_nodes[m_edges[noa+1].m_parent].m_top;
+      y2 = m_nodes[m_edges[noa+1].m_child].m_top;
+      
+      dist = y2 - y1;
+      if (dist <= 0) {
+	
+	dist = 1;
+      }
+      dist = ((60 + m_edges[noa+1].m_height 
+	       + m_nodes[m_edges[noa+1].m_parent].m_height) 
+	      * m_viewSize.height) / dist;
+      
+      if (dist > temp.height) {
+	
+	temp.height = dist;
+      }
+    }
+
+    Dimension e = getSize();
+    
+    Dimension np = new Dimension();
+    np.width = (int)(e.width / 2 -  (((double)e.width / 2) - m_viewPos.width) /
+		     ((double)m_viewSize.width) * (double)temp.width);
+    np.height = (int)(e.height / 2 -  (((double)e.height / 2) - 
+				       m_viewPos.height) /       
+		      ((double)m_viewSize.height) * (double)temp.height);
+    //animate_scaling(c_size,c_pos,25);
+    
+    for (int noa = 0;noa < m_numNodes;noa++) {
+      //this resets the coords so that next time a refresh occurs they don't 
+      //accidentally get used
+      //I will use 32000 to signify that they are invalid, even if this 
+      //coordinate occurs it doesn't
+      //matter as it is only for the sake of the caching
+      
+      m_nodes[noa].m_top = 32000;
+      
+    }
+    animateScaling(np, temp, 10);
+  }
+
+  /**
+   * This will increment the size and position of the tree towards the 
+   * desired size and position
+   * a little (depending on the value of <i>frames</i>) everytime it is called.
+   *
+   * @param n_pos The final position of the tree wanted.
+   * @param n_size The final size of the tree wanted.
+   * @param frames The number of frames that shall occur before the final 
+   * size and pos is reached.
+   */
+  private void animateScaling(Dimension n_pos,Dimension n_size,int frames) {
+    //this function will take new size and position coords , and incrementally
+    //scale the view to these
+    //since I will be tying it in with the framelimiter I will simply call 
+    //this function and increment it once
+    //I will have to use a global variable since I am doing it proportionally
+
+    if (frames == 0) {
+      System.out.println("the timer didn't end in time");
+      m_scaling = 0;
+    }
+    else {
+      if (m_scaling == 0) {
+	//new animate session
+	//start timer and set scaling
+	m_frameLimiter.start();
+	m_nViewPos.width = n_pos.width;
+	m_nViewPos.height = n_pos.height;
+	m_nViewSize.width = n_size.width;
+	m_nViewSize.height = n_size.height;
+	
+	m_scaling = frames;
+      }
+      
+      int s_w = (n_size.width - m_viewSize.width) / frames;
+      int s_h = (n_size.height - m_viewSize.height) / frames;
+      int p_w = (n_pos.width - m_viewPos.width) / frames;
+      int p_h = (n_pos.height - m_viewPos.height) / frames;
+      
+      m_viewSize.width += s_w;
+      m_viewSize.height += s_h;
+      
+      m_viewPos.width += p_w;
+      m_viewPos.height += p_h;
+      
+      repaint();
+      
+      m_scaling--;
+      if (m_scaling == 0) {
+	//all done 
+	m_frameLimiter.stop();
+      }
+    }
+  }
+  
+  /**
+   * This will change the font size for displaying the tree to the one 
+   * specified.
+   *
+   * @param s The new pointsize of the font.
+   */
+  private void changeFontSize(int s) {
+    //this will set up the new font that shall be used
+    //it will also recalculate the size of the nodes as these will change as 
+    //a result of 
+    //the new font size
+    setFont(m_currentFont = new Font("A Name", 0, s));             
+
+    m_fontSize = getFontMetrics(getFont());
+    
+    Dimension d;
+
+    for (int noa = 0; noa < m_numNodes; noa++) {
+      //this will set the size info for each node and edge
+      
+      d = m_nodes[noa].m_node.stringSize(m_fontSize);
+      
+      if (m_nodes[noa].m_node.getShape() == 1) {
+	m_nodes[noa].m_height = d.height + 10;
+	m_nodes[noa].m_width = d.width + 8;
+	m_nodes[noa].m_side = m_nodes[noa].m_width / 2;
+      }
+      else if (m_nodes[noa].m_node.getShape() == 2) {
+	m_nodes[noa].m_height = (int)((d.height + 2) * 1.6);
+	m_nodes[noa].m_width = (int)((d.width + 2) * 1.6);
+	m_nodes[noa].m_side = m_nodes[noa].m_width / 2;
+      }
+      
+      if (noa < m_numNodes - 1) {
+	//this will do the same for edges
+	
+	d = m_edges[noa].m_edge.stringSize(m_fontSize);
+	
+	m_edges[noa].m_height =  d.height + 8;
+	m_edges[noa].m_width = d.width + 8;
+	m_edges[noa].m_side = m_edges[noa].m_width / 2;
+	m_edges[noa].m_tb = m_edges[noa].m_height / 2;
+      }
+    }
+  }
+
+  /**
+   * This will fill two arrays with the Nodes and Edges from the tree
+   * into a particular order.
+   *
+   * @param t The top Node of the tree.
+   * @param l An array that has already been allocated, to be filled.
+   * @param k An array that has already been allocated, to be filled.
+   */
+  private void arrayFill(Node t, NodeInfo[] l, EdgeInfo[] k) {
+    
+    //this will take the top node and the array to fill
+    //it will go through the tree structure and and fill the array with the 
+    //nodes 
+    //from top to bottom left to right
+    
+    //note I do not believe this function will be able to deal with multiple 
+    //parents
+
+    if (t == null || l == null) {
+      System.exit(1);      //this is just a preliminary safety check 
+      //(i shouldn' need it)
+    }
+    
+    Edge e;
+    Node r,s;
+    l[0] = new NodeInfo();
+    l[0].m_node = t;
+    l[0].m_parent = -1;
+    l[0].m_change = true;
+    
+    int floater;       //this will point at a node that has previously been 
+    //put in the list 
+    //all of the children that this node has shall be put in the list , 
+    //once this is done the floater shall point at the next node in the list
+    //this will allow the nodes to be put into order from closest to top node
+    //to furtherest from top node
+
+    int free_space = 1; //the next empty array position
+
+    double height = t.getTop(); //this will be used to determine if the node 
+    //has a 
+    //new height compared to the
+    //previous one
+
+    for (floater = 0;floater < free_space;floater++) {
+      r = l[floater].m_node;
+      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
+	//this loop pulls out each child of r
+	
+	//e points to a child edge, getTarget will return that edges child node
+	s = e.getTarget();
+	l[free_space] = new NodeInfo();
+	l[free_space].m_node = s;
+	l[free_space].m_parent = free_space - 1;
+	
+	k[free_space - 1] = new EdgeInfo();
+	k[free_space - 1].m_edge = e;
+	k[free_space - 1].m_parent = floater;
+	k[free_space - 1].m_child = free_space;     //note although it's child 
+	//will always have a subscript
+	//of 1 more , I may not nessecarily have access to that
+	//and it will need the subscr.. for multiple parents
+	
+	//determine if level of node has changed from previous one
+	if (height != s.getTop()) {
+	  l[free_space].m_change = true;
+	  height = s.getTop();
+	}
+	else {
+	  l[free_space].m_change = false;
+	}
+	free_space++;
+      }
+    }
+  }
+
+  /**
+   * Internal Class for containing display information about a Node. 
+   */
+  private class NodeInfo {
+    //this class contains a pointer to the node itself along with extra 
+    //information
+    //about the node used by the Displayer class
+
+    /** The y pos of the node on screen. */
+    int m_top = 32000;           //the main node coords calculated out
+
+    /** The x pos of the node on screen. */
+    int m_center;        // these coords will probably change each refresh 
+
+    //and are the positioning coords
+    //which the rest of the offsets use
+    
+    /** The offset to get to the left or right of the node. */
+    int m_side;          //these are the screen offset for the dimensions of 
+
+    //the node relative to the nodes 
+    //internal top and center values (after they have been converted to 
+    //screen coords
+    /** The width of the node. */
+    int m_width;
+
+    /** The height of the node. */
+    int m_height;
+
+    /** True if the node is at the start (left) of a new level (not sibling 
+     * group). */
+    boolean m_change;    //this is quickly used to identify whether the node 
+    //has chenged height from the
+    //previous one to help speed up the calculation of what row it lies in
+
+    /** The subscript number of the Nodes parent. */
+    int m_parent;     //this is the index of the nodes parent edge in an array
+
+    /** The rough position of the node relative to the screen. */
+    int m_quad;       //what of nine quadrants is it in
+
+    /*
+      12 10  9
+      20 18 17          //18 being the screen
+      36 34 33          //this arrangement uses 6 bits, each bit represents a 
+      row or column
+    */
+
+    /** The Node itself. */
+    Node m_node;
+  }
+
+  /**
+   * Internal Class for containing display information about an Edge. 
+   */
+  private class EdgeInfo {
+    //this class contains a pointer to the edge along with all the other
+    //extra info about the edge
+    
+    /** The parent subscript (for a Node). */
+    int m_parent;            //array indexs for its two connections
+
+    /** The child subscript (for a Node). */
+    int m_child;
+    
+
+    /** The distance from the center of the text to either side. */
+    int m_side;            //these are used to describe the dimensions of the 
+    //text
+
+    /** The distance from the center of the text to top or bottom. */
+    int m_tb;              //tb stands for top , bottom, this is simply the 
+    //distance from the middle to top bottom
+
+    /** The width of the text. */
+    int m_width;
+
+    /** The height of the text. */
+    int m_height;
+
+    /** The Edge itself. */
+    Edge m_edge;
+  }
+
+  /**
+   * Main method for testing this class.
+   * @param args first argument should be the name of a file that contains
+   * a tree discription in dot format.
+   */
+  public static void main(String[] args) {
+    try {
+      weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+      //put in the random data generator right here
+      // this call with import java.lang gives me between 0 and 1 Math.random
+      TreeBuild builder = new TreeBuild();
+      Node top = null;
+      NodePlace arrange = new PlaceNode2();
+      //top = builder.create(new StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
+      top = builder.create(new FileReader(args[0]));
+
+      int num = top.getCount(top,0);
+      //System.out.println("counter counted " + num + " nodes");
+      //System.out.println("there are " + num + " nodes");
+      TreeVisualizer a = new TreeVisualizer(null, top, arrange);
+      a.setSize(800 ,600);
+      //a.setTree(top);
+      JFrame f;
+      f = new JFrame();
+      //a.addMouseMotionListener(a);
+      //a.addMouseListener(a);
+      //f.add(a);
+      Container contentPane = f.getContentPane();
+      contentPane.add(a);
+      f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+      f.setSize(800,600);
+      f.setVisible(true);
+      //f.
+      //find_prop(top);
+      //a.setTree(top,arrange);//,(num + 1000), num / 2 + 1000);
+    }
+    catch(IOException e) {
+      // ignored
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/treevisualizer/TreeVisualizer.props	(revision 29)
@@ -0,0 +1,29 @@
+# A properties file for customizing the Weka's TreeVisualizer.
+#
+# Author: FracPete (fracpete at waikato dot ac dot nz)
+# Version: $Revision: 4960 $
+
+# the font color (can use R,G,B format)
+FontColor=black
+
+# the background color (can use R,G,B format)
+# leaving this empty uses the platform's default background
+# Note: on Mac OS X, using the default background results in a black background when
+#       saving the tree to a file! See the following thread on the Weka mailing list:
+#       https://list.scms.waikato.ac.nz/mailman/htdig/wekalist/2009-January/015661.html
+BackgroundColor=
+
+# the node color (can use R,G,B format)
+NodeColor=gray
+
+# the line color (can use R,G,B format)
+LineColor=black
+
+# the color of the zoombox (can use R,G,B format)
+ZoomBoxColor=black
+
+# the XOR mode color for the zoombox (can use R,G,B format)
+ZoomBoxXORColor=white
+
+# whether to show the border or not
+ShowBorder=true
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanel.java	(revision 29)
@@ -0,0 +1,631 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributePanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+/**
+ * This panel displays one dimensional views of the attributes in a
+ * dataset. Colouring is done on the basis of a column in the dataset or
+ * an auxiliary array (useful for colouring cluster predictions).
+ * 
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5086 $
+ */
+public class AttributePanel
+  extends JScrollPane {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3533330317806757814L;
+  
+  /** The instances to be plotted */
+  protected Instances m_plotInstances=null;
+    
+  /** Holds the min and max values of the colouring attributes */
+  protected double m_maxC;
+  protected double m_minC;
+  protected int m_cIndex;
+  protected int m_xIndex;
+  protected int m_yIndex;
+
+  /** The colour map to use for colouring points */
+  protected FastVector m_colorList;
+
+   /** default colours for colouring discrete class */
+    protected Color [] m_DefaultColors = {Color.blue,
+					Color.red,
+					Color.green,
+					Color.cyan,
+					Color.pink,
+					new Color(255, 0, 255),
+					Color.orange,
+					new Color(255, 0, 0),
+					new Color(0, 255, 0),
+					Color.white};
+    
+  /**
+   *  If set, it allows this panel to avoid setting a color in
+   * the color list that is equal to the background color
+   */
+  protected Color m_backgroundColor = null; 
+
+  /** The list of things listening to this panel */
+  protected FastVector m_Listeners = new FastVector();
+
+  /** Holds the random height for each instance. */
+  protected int[] m_heights;
+  //protected Color[] colors_array;
+
+  /** The container window for the attribute bars, and also where the
+   * X,Y or B get printed.
+   */ 
+  protected JPanel m_span=null;
+
+  /** The default colour to use for the background of the bars if
+      a colour is not defined in Visualize.props */
+  protected Color m_barColour=Color.black;
+    
+  /** inner inner class used for plotting the points 
+   * into a bar for a particular attribute. 
+   */
+  protected class AttributeSpacing
+    extends JPanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = 7220615894321679898L;
+
+    /** The min and max values for this attribute. */
+    protected double m_maxVal;
+    protected double m_minVal;
+
+    /** The attribute itself. */
+    protected Attribute m_attrib;
+      
+    /** The index for this attribute. */
+    protected int m_attribIndex;
+      
+    /** The x position of each point. */
+    protected int[] m_cached;
+    //note for m_cached, if you wanted to speed up the drawing algorithm
+    // and save memory, the system could be setup to drop out any
+    // of the instances not being drawn (you would need to find a new way
+    //of matching the height however).
+
+    /** A temporary array used to strike any instances that would be 
+     * drawn redundantly.
+     */
+    protected boolean[][] m_pointDrawn;
+      
+    /** Used to determine if the positions need to be recalculated. */
+    protected int m_oldWidth=-9000;
+
+    /** The container window for the attribute bars, and also where the
+     * X,Y or B get printed.
+     */
+      
+    /**
+     * This constructs the bar with the specified attribute and
+     * sets its index to be used for selecting by the mouse.
+     * @param a The attribute this bar represents.
+     * @param aind The index of this bar.
+     */
+    public AttributeSpacing(Attribute a, int aind) {
+      m_attrib = a;
+      m_attribIndex = aind;
+      this.setBackground(m_barColour);
+      this.setPreferredSize(new Dimension(0, 20));
+      this.setMinimumSize(new Dimension(0, 20));
+      m_cached = new int[m_plotInstances.numInstances()];
+	
+      //this will only get allocated if m_plotInstances != null
+      //this is used to determine the min and max values for plotting
+      double min=Double.POSITIVE_INFINITY;
+      double max=Double.NEGATIVE_INFINITY;
+      double value;
+      if (m_plotInstances.attribute(m_attribIndex).isNominal()) {
+	m_minVal = 0;
+	m_maxVal = m_plotInstances.attribute(m_attribIndex).numValues()-1;
+      } else {
+	for (int i=0;i<m_plotInstances.numInstances();i++) {
+	  if (!m_plotInstances.instance(i).isMissing(m_attribIndex)) {
+	    value = m_plotInstances.instance(i).value(m_attribIndex);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+	m_minVal = min; m_maxVal = max;
+	if (min == max) {
+	  m_maxVal += 0.05;
+	  m_minVal -= 0.05;
+	}
+      }
+	
+      this.addMouseListener(new MouseAdapter() {
+	  public void mouseClicked(MouseEvent e) {
+	    if ((e.getModifiers() & e.BUTTON1_MASK) == e.BUTTON1_MASK) {
+	      setX(m_attribIndex);
+	      if (m_Listeners.size() > 0) {
+		for (int i=0;i<m_Listeners.size();i++) {
+		  AttributePanelListener l = 
+		    (AttributePanelListener)(m_Listeners.elementAt(i));
+		  l.attributeSelectionChange(new AttributePanelEvent(true,
+					     false, m_attribIndex));
+		}
+	      }
+	    }
+	    else {
+	      //put it on the y axis
+	      setY(m_attribIndex);
+	      if (m_Listeners.size() > 0) {
+		for (int i=0;i<m_Listeners.size();i++) {
+		  AttributePanelListener l = 
+		    (AttributePanelListener)(m_Listeners.elementAt(i));
+		  l.attributeSelectionChange(new AttributePanelEvent(false,
+					     true, m_attribIndex));
+		}
+	      }
+	    }
+	  }
+	});
+    }
+      
+    /**
+     * Convert an raw x value to Panel x coordinate.
+     * @param val the raw x value
+     * @return an x value for plotting in the panel.
+     */
+    private double convertToPanel(double val) {
+      double temp = (val - m_minVal)/(m_maxVal - m_minVal);
+      double temp2 = temp * (this.getWidth() - 10);
+	
+      return temp2 + 4; 
+    }
+      
+    /**
+     * paints all the visible instances to the panel , and recalculates
+     * their position if need be.
+     * @param gx The graphics context.
+     */
+    public void paintComponent(Graphics gx) {
+      super.paintComponent(gx);
+      int xp, yp, h;
+      h = this.getWidth();
+      if (m_plotInstances != null 
+	  && m_plotInstances.numAttributes() > 0
+	  && m_plotInstances.numInstances() > 0) {
+
+	if (m_oldWidth != h) {
+	  m_pointDrawn = new boolean[h][20];
+	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
+	    if (!m_plotInstances.instance(noa).isMissing(m_attribIndex)
+		&& !m_plotInstances.instance(noa).isMissing(m_cIndex)) {
+	      m_cached[noa] = (int)convertToPanel(m_plotInstances.
+						  instance(noa).
+						  value(m_attribIndex));
+		
+	      if (m_pointDrawn[m_cached[noa] % h][m_heights[noa]]) {
+		m_cached[noa] = -9000;
+	      }
+	      else {
+		m_pointDrawn[m_cached[noa]%h][m_heights[noa]] = true;
+	      }
+		
+	    }
+	    else {
+	      m_cached[noa] = -9000; //this value will not happen 
+	      //so it is safe
+	    }
+	  }
+	  m_oldWidth = h;
+	}
+	  
+	if (m_plotInstances.attribute(m_cIndex).isNominal()) {
+	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
+	      
+	    if (m_cached[noa] != -9000) {
+	      xp = m_cached[noa];
+	      yp = m_heights[noa];
+	      if (m_plotInstances.attribute(m_attribIndex).
+		  isNominal()) {
+		xp += (int)(Math.random() * 5) - 2;
+	      }
+	      int ci = (int)m_plotInstances.instance(noa).value(m_cIndex);
+
+	      gx.setColor((Color)m_colorList.elementAt
+			  (ci % m_colorList.size()));
+	      gx.drawRect(xp, yp, 1, 1);
+	    }
+	  }
+	}
+	else {
+	  double r;
+	  for (int noa = 0; noa < m_plotInstances.numInstances(); noa++) {
+	    if (m_cached[noa] != -9000) {		  
+		
+	      r = (m_plotInstances.instance(noa).value(m_cIndex) 
+		   - m_minC) / (m_maxC - m_minC);
+
+	      r = (r * 240) + 15;
+
+	      gx.setColor(new Color((int)r,150,(int)(255-r)));
+		
+	      xp = m_cached[noa];
+	      yp = m_heights[noa];
+	      if (m_plotInstances.attribute(m_attribIndex).
+		  isNominal()) {
+		xp += (int)(Math.random() * 5) - 2;
+	      }
+	      gx.drawRect(xp, yp, 1, 1);
+	    }
+	  }
+	}
+      } 
+    }
+  }   
+    
+  /**
+   * Set the properties for the AttributePanel
+   */
+  private void setProperties() {
+    if (VisualizeUtils.VISUALIZE_PROPERTIES != null) {
+      String thisClass = this.getClass().getName();
+      String barKey = thisClass+".barColour";
+      
+      String barC = VisualizeUtils.VISUALIZE_PROPERTIES.
+	      getProperty(barKey);
+      if (barC == null) {
+	/*
+	System.err.println("Warning: no configuration property found in "
+			   +VisualizeUtils.PROPERTY_FILE
+			   +" for "+barKey);
+	*/
+      } else {
+	//System.err.println("Setting attribute bar colour to: "+barC);
+	m_barColour = VisualizeUtils.processColour(barC, m_barColour);
+      }
+    }
+  }
+  
+  public AttributePanel() {
+    this(null);
+  }
+ 
+  /**
+   * This constructs an attributePanel.
+   */
+  public AttributePanel(Color background) {
+    m_backgroundColor = background;
+    
+    setProperties();
+    this.setBackground(Color.blue);
+    setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
+    m_colorList = new FastVector(10);
+
+    for (int noa = m_colorList.size(); noa < 10; noa++) {
+      Color pc = m_DefaultColors[noa % 10];
+      int ija =  noa / 10;
+      ija *= 2; 
+      for (int j=0;j<ija;j++) {
+	pc = pc.darker();
+      }
+      
+      m_colorList.addElement(pc);
+    }
+  }
+
+  /**
+   * Add a listener to the list of things listening to this panel
+   * @param a the listener to notify when attribute bars are clicked on
+   */
+  public void addAttributePanelListener(AttributePanelListener a) {
+    m_Listeners.addElement(a);
+  }
+  
+  /**
+   * Set the index of the attribute by which to colour the data. Updates
+   * the number of entries in the colour list if there are more values
+   * for this new attribute than previous ones.
+   * @param c the index of the attribute to colour on
+   * @param h maximum value of this attribute
+   * @param l minimum value of this attribute
+   */
+  public void setCindex(int c, double h, double l) {
+    m_cIndex = c;
+    m_maxC = h;
+    m_minC = l;
+    
+    if (m_span != null) {
+      if (m_plotInstances.numAttributes() > 0 &&
+	  m_cIndex < m_plotInstances.numAttributes()) {
+	if (m_plotInstances.attribute(m_cIndex).isNominal()) {
+	  if (m_plotInstances.attribute(m_cIndex).numValues() > 
+	    m_colorList.size()) {
+	    extendColourMap();
+	  }
+	}
+      }
+      this.repaint();
+    }
+  }
+
+  /**
+   * Set the index of the attribute by which to colour the data. Updates
+   * the number of entries in the colour list if there are more values
+   * for this new attribute than previous ones.
+   * @param c the index of the attribute to colour on
+   */
+  public void setCindex(int c) {
+    m_cIndex = c;
+    /*    m_maxC = h;
+	  m_minC = l; */
+
+    if (m_span != null) {
+      if (m_cIndex < m_plotInstances.numAttributes() && 
+	  m_plotInstances.attribute(m_cIndex).isNumeric()) {
+	double min=Double.POSITIVE_INFINITY;
+	double max=Double.NEGATIVE_INFINITY;
+	double value;
+
+	for (int i=0;i<m_plotInstances.numInstances();i++) {
+	  if (!m_plotInstances.instance(i).isMissing(m_cIndex)) {
+	    value = m_plotInstances.instance(i).value(m_cIndex);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+    
+	m_minC = min; m_maxC = max;
+      } else {
+	if (m_plotInstances.attribute(m_cIndex).numValues() > 
+	    m_colorList.size()) {
+	  extendColourMap();
+	}
+      }
+    
+      this.repaint();
+    }
+  }
+
+  /**
+   * Adds more colours to the colour list
+   */
+  private void extendColourMap() {
+    if (m_plotInstances.attribute(m_cIndex).isNominal()) {
+      for (int i = m_colorList.size(); 
+	   i < m_plotInstances.attribute(m_cIndex).numValues();
+	   i++) {
+	Color pc = m_DefaultColors[i % 10];
+	int ija =  i / 10;
+	ija *= 2; 
+	for (int j=0;j<ija;j++) {
+	  pc = pc.brighter();
+	}
+	
+	if (m_backgroundColor != null) {
+	  pc = Plot2D.checkAgainstBackground(pc, m_backgroundColor);
+	}
+
+	m_colorList.addElement(pc);
+      }
+    }
+  }
+
+  /**
+   * Sets a list of colours to use for colouring data points
+   * @param cols a list of java.awt.Color
+   */
+  public void setColours(FastVector cols) {
+    m_colorList = cols;
+  }
+  
+  protected void setDefaultColourList(Color[] list) {
+    m_DefaultColors = list;
+  }
+
+  /** 
+   * This sets the instances to be drawn into the attribute panel
+   * @param ins The instances.
+   */
+  public void setInstances(Instances ins) throws Exception {
+    if (ins.numAttributes() > 512) {
+      throw new Exception("Can't display more than 512 attributes!");
+    }
+
+    if (m_span == null) {
+      m_span = new JPanel() {
+	  private static final long serialVersionUID = 7107576557995451922L;
+	  
+	  public void paintComponent(Graphics gx) {
+	    super.paintComponent(gx);
+	    gx.setColor(Color.red);
+	    if (m_yIndex != m_xIndex) {
+	      gx.drawString("X", 5, m_xIndex * 20 + 16);
+	      gx.drawString("Y", 5, m_yIndex * 20 + 16);
+	    }
+	    else {
+	      gx.drawString("B", 5, m_xIndex * 20 + 16);
+	    }
+	  }
+	};
+    }
+
+    m_span.removeAll();
+    m_plotInstances = ins;
+    if (ins.numInstances() > 0 && ins.numAttributes() > 0) {
+      JPanel padder = new JPanel();
+      JPanel padd2 = new JPanel();
+      
+      /*    if (m_splitListener != null) {
+	    m_plotInstances.randomize(new Random());
+	    } */
+
+      m_heights = new int[ins.numInstances()];
+
+      m_cIndex = ins.numAttributes() - 1;
+      for (int noa = 0; noa < ins.numInstances(); noa++) {
+	m_heights[noa] = (int)(Math.random() * 19);
+      }
+      m_span.setPreferredSize(new Dimension(m_span.getPreferredSize().width, 
+					    (m_cIndex + 1) * 20));
+      m_span.setMaximumSize(new Dimension(m_span.getMaximumSize().width, 
+					  (m_cIndex + 1) * 20));
+      AttributeSpacing tmp;
+      
+      GridBagLayout gb = new GridBagLayout();
+      GridBagLayout gb2 = new GridBagLayout();
+      GridBagConstraints constraints = new GridBagConstraints();
+      
+
+
+      padder.setLayout(gb);
+      m_span.setLayout(gb2);
+      constraints.anchor = GridBagConstraints.CENTER;
+      constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+      constraints.fill = GridBagConstraints.HORIZONTAL;
+      constraints.gridwidth=1;constraints.gridheight=1;
+      constraints.insets = new Insets(0, 0, 0, 0);
+      padder.add(m_span, constraints);
+      constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
+      constraints.fill = GridBagConstraints.BOTH;
+      constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+      constraints.insets = new Insets(0, 0, 0, 0);
+      padder.add(padd2, constraints);
+      constraints.weighty=0;
+      setViewportView(padder);
+      //getViewport().setLayout(null);
+      //m_span.setMinimumSize(new Dimension(100, (m_cIndex + 1) * 24));
+      //m_span.setSize(100, (m_cIndex + 1) * 24);
+      constraints.anchor = GridBagConstraints.CENTER;
+      constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+      constraints.fill = GridBagConstraints.HORIZONTAL;
+      constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+      constraints.insets = new Insets(2,20,2,4);
+
+      for (int noa = 0; noa < ins.numAttributes(); noa++) {
+	tmp = new AttributeSpacing(ins.attribute(noa), noa);
+	 
+	constraints.gridy = noa;
+	m_span.add(tmp, constraints);
+      }
+    }
+  }
+    
+  /**
+   * shows which bar is the current x attribute.
+   * @param x The attributes index.
+   */
+  public void setX(int x) {
+    if (m_span != null) {
+      m_xIndex = x;
+      m_span.repaint();
+    }
+  }
+    
+  /**
+   * shows which bar is the current y attribute.
+   * @param y The attributes index.
+   */
+  public void setY(int y) {
+    if (m_span != null) {
+      m_yIndex = y;
+      m_span.repaint();
+    }
+  }
+
+  /**
+   * Main method for testing this class.
+   * @param args first argument should be an arff file. Second argument
+   * can be an optional class col
+   */
+  public static void main(String [] args) {
+    try {
+      if (args.length < 1) {
+	System.err.println("Usage : weka.gui.visualize.AttributePanel "
+			   +"<dataset> [class col]");
+	System.exit(1);
+      }
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Explorer: Attribute");
+      jf.setSize(100,100);
+      jf.getContentPane().setLayout(new BorderLayout());
+      final AttributePanel p2 = new AttributePanel();
+      p2.addAttributePanelListener(new AttributePanelListener() {
+	  public void attributeSelectionChange(AttributePanelEvent e) {
+	    if (e.m_xChange) {
+	      System.err.println("X index changed to : "+e.m_indexVal);
+	    } else {
+	      System.err.println("Y index changed to : "+e.m_indexVal);
+	    }
+	  }
+	});
+      jf.getContentPane().add(p2, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+      if (args.length >= 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	i.setClassIndex(i.numAttributes()-1);
+	p2.setInstances(i);
+      }
+      if (args.length > 1) {
+	p2.setCindex((Integer.parseInt(args[1]))-1);
+      } else {
+	p2.setCindex(0);
+      }
+      jf.setVisible(true);
+    } catch (Exception ex) {
+       ex.printStackTrace();
+       System.err.println(ex.getMessage());
+     }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelEvent.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributePanelEvent.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+/**
+ * Class encapsulating a change in the AttributePanel's selected x and y
+ * attributes.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class AttributePanelEvent {
+
+  /** True if the x selection changed */
+  public boolean m_xChange;
+
+  /** True if the y selection changed */
+  public boolean m_yChange;
+
+  /** The index for the new attribute */
+  public int m_indexVal;
+
+  /**
+   * Constructor
+   * @param xChange true if a change occured to the x selection
+   * @param yChange true if a change occured to the y selection
+   * @param indexVal the index of the new attribute
+   */
+  public AttributePanelEvent(boolean xChange, boolean yChange, int indexVal) {
+    m_xChange = xChange;
+    m_yChange = yChange;
+    m_indexVal = indexVal;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/AttributePanelListener.java	(revision 29)
@@ -0,0 +1,41 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    AttributePanelListener.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+/**
+ * Interface for classes that want to listen for Attribute selection
+ * changes in the attribute panel
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface AttributePanelListener {
+
+  /**
+   * Called when the user clicks on an attribute bar
+   * @param e the event encapsulating what happened
+   */
+  void attributeSelectionChange(AttributePanelEvent e);
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/BMPWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/BMPWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/BMPWriter.java	(revision 29)
@@ -0,0 +1,159 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * BMPWriter.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.visualize;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+import javax.swing.JComponent;
+
+/**
+ * This class takes any JComponent and outputs it to a BMP-file.
+ * Scaling is by default disabled, since we always take a screenshot.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5920 $
+ */
+public class BMPWriter
+  extends JComponentWriter {
+
+  /** the background color. */
+  protected Color m_Background;
+  
+  /**
+   * initializes the object.
+   */
+  public BMPWriter() {
+    super();
+  }
+
+  /**
+   * initializes the object with the given Component.
+   * 
+   * @param c		the component to print in the output format
+   */
+  public BMPWriter(JComponent c) {
+    super(c);
+  }
+
+  /**
+   * initializes the object with the given Component and filename.
+   * 
+   * @param c		the component to print in the output format
+   * @param f		the file to store the output in
+   */
+  public BMPWriter(JComponent c, File f) {
+    super(c, f);
+  }
+  
+  /**
+   * further initialization.
+   */
+  public void initialize() {
+    super.initialize();
+    
+    setScalingEnabled(false);
+  }
+
+  /**
+   * returns the name of the writer, to display in the FileChooser.
+   * must be overridden in the derived class.
+   * 
+   * @return 		the name of the writer
+   */
+  public String getDescription() {
+    return "BMP-Image";
+  }
+  
+  /**
+   * returns the extension (incl. ".") of the output format, to use in the
+   * FileChooser. 
+   * 
+   * @return 		the file extension
+   */
+  public String getExtension() {
+    return ".bmp";
+  }
+  
+  /**
+   * returns the current background color.
+   * 
+   * @return		the current background color
+   */
+  public Color getBackground() {
+    return m_Background;
+  }
+  
+  /**
+   * sets the background color to use in creating the BMP.
+   * 
+   * @param c 		the color to use for background
+   */
+  public void setBackground(Color c) {
+    m_Background = c;
+  }
+  
+  /**
+   * generates the actual output.
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  public void generateOutput() throws Exception {
+    BufferedImage	bi;
+    Graphics		g;
+
+    bi = new BufferedImage(getComponent().getWidth(), getComponent().getHeight(), BufferedImage.TYPE_INT_RGB);
+    g  = bi.getGraphics();
+    g.setPaintMode();
+    g.setColor(getBackground());
+    if (g instanceof Graphics2D)
+      ((Graphics2D) g).scale(getXScale(), getYScale());
+    g.fillRect(0, 0, getComponent().getWidth(), getComponent().getHeight());
+    getComponent().printAll(g);
+    ImageIO.write(bi, "bmp", getFile());
+  }
+  
+  /**
+   * for testing only.
+   * 
+   * @param args 	the commandline arguments
+   * @throws Exception 	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    System.out.println("building TreeVisualizer...");
+    weka.gui.treevisualizer.TreeBuild builder = new weka.gui.treevisualizer.TreeBuild();
+    weka.gui.treevisualizer.NodePlace arrange = new weka.gui.treevisualizer.PlaceNode2();
+    weka.gui.treevisualizer.Node top = builder.create(new java.io.StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
+    weka.gui.treevisualizer.TreeVisualizer tv = new weka.gui.treevisualizer.TreeVisualizer(null, top, arrange);
+    tv.setSize(800 ,600);
+    
+    String filename = System.getProperty("java.io.tmpdir") + File.separator + "test.bmp";
+    System.out.println("outputting to '" + filename + "'...");
+    toOutput(new BMPWriter(), tv, new File(filename));
+
+    System.out.println("done!");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/ClassPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/ClassPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/ClassPanel.java	(revision 29)
@@ -0,0 +1,757 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ClassPanel.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JColorChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * This panel displays coloured labels for nominal attributes and a spectrum
+ * for numeric attributes. It can also be told to colour on the basis
+ * of an array of doubles (this can be useful for displaying coloured labels
+ * that correspond to a clusterers predictions).
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5086 $
+ */
+public class ClassPanel
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -7969401840501661430L;
+    
+  /** True when the panel has been enabled (ie after 
+      setNumeric or setNominal has been called */
+  private boolean m_isEnabled = false;
+
+  /** True if the colouring attribute is numeric */
+  private boolean m_isNumeric = false;
+    
+  /** The height of the spectrum for numeric class */
+  private final int m_spectrumHeight = 5;
+
+  /**  The maximum value for the colouring attribute */
+  private double m_maxC;
+    
+  /** The minimum value for the colouring attribute */
+  private double m_minC;
+
+  /** The size of the ticks */
+  private final int m_tickSize = 5;
+
+  /** Font metrics */
+  private FontMetrics m_labelMetrics = null;
+
+  /** The font used in labeling */
+  private Font m_labelFont = null;
+
+  /** The amount of space to leave either side of the legend */ 
+  private int m_HorizontalPad=0;
+
+  /** The precision with which to display real values */
+  private int m_precisionC;
+
+  /** Field width for numeric values */
+  private int m_fieldWidthC;
+
+  /** The old width. */
+  private int m_oldWidth = -9000;
+    
+  /** Instances being plotted */
+  private Instances m_Instances=null;
+
+  /** Index of the colouring attribute */
+  private int m_cIndex;
+
+  /** the list of colours to use for colouring nominal attribute labels */
+  private FastVector m_colorList;
+
+  /** An optional list of Components that use the colour list
+      maintained by this class. If the user changes a colour
+      using the colour chooser, then these components need to
+      be repainted in order to display the change */
+  private FastVector m_Repainters = new FastVector();
+
+  /** An optional list of listeners who want to know when a colour
+      changes. Listeners are notified via an ActionEvent */
+  private FastVector m_ColourChangeListeners = new FastVector();
+
+  /** default colours for colouring discrete class */
+  protected Color [] m_DefaultColors = {Color.blue,
+					Color.red,
+					Color.green,
+					Color.cyan,
+					Color.pink,
+					new Color(255, 0, 255),
+					Color.orange,
+					new Color(255, 0, 0),
+					new Color(0, 255, 0),
+					Color.white};
+  
+  /**
+   *  if set, it allows this panel to steer away from setting up
+   * a color in the color list that is equal to the background color
+   */
+  protected Color m_backgroundColor = null;
+
+  /** Inner Inner class used to create labels for nominal attributes
+   * so that there color can be changed.
+   */
+  private class NomLabel
+    extends JLabel {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4686613106474820655L;
+
+    private int m_index = 0;
+
+    /** 
+     * Creates a label with its name and class index value.
+     * @param name The name of the nominal class value.
+     * @param id The index value for that class value.
+     */
+    public NomLabel(String name, int id) {
+      super(name);
+      m_index = id;
+
+      this.addMouseListener(new MouseAdapter() {
+	  public void mouseClicked(MouseEvent e) {
+	      
+	    if ((e.getModifiers() & e.BUTTON1_MASK) == e.BUTTON1_MASK) {
+	      Color tmp = JColorChooser.showDialog
+		(ClassPanel.this, "Select new Color", 
+		 (Color)m_colorList.elementAt(m_index));
+		
+	      if (tmp != null) {
+		m_colorList.setElementAt(tmp, m_index);
+		m_oldWidth = -9000;
+		ClassPanel.this.repaint();
+		if (m_Repainters.size() > 0) {
+		  for (int i=0;i<m_Repainters.size();i++) {
+		    ((Component)(m_Repainters.elementAt(i))).repaint();
+		  }
+		}
+		
+		if (m_ColourChangeListeners.size() > 0) {
+		  for (int i = 0; i < m_ColourChangeListeners.size(); i++) {
+		    ((ActionListener)(m_ColourChangeListeners.elementAt(i))).
+		      actionPerformed(new ActionEvent(this, 0, ""));
+		  }
+		}
+	      }
+	    }
+	  }
+	});
+    }
+  }
+  
+  public ClassPanel() {
+    this(null);
+  }
+
+  public ClassPanel(Color background) {
+    m_backgroundColor = background;
+    
+    /** Set up some default colours */
+    m_colorList = new FastVector(10);
+    for (int noa = m_colorList.size(); noa < 10; noa++) {
+      Color pc = m_DefaultColors[noa % 10];
+      int ija =  noa / 10;
+      ija *= 2; 
+      for (int j=0;j<ija;j++) {
+	pc = pc.darker();
+      }
+	
+      m_colorList.addElement(pc);
+    }
+  }
+
+  /**
+   * Adds a component that will need to be repainted if the user
+   * changes the colour of a label.
+   * @param c the component to be repainted
+   */
+  public void addRepaintNotify(Component c) {
+    m_Repainters.addElement(c);
+  }
+
+  /**
+   * Add an action listener that will be notified if the user changes the
+   * colour of a label
+   *
+   * @param a an <code>ActionListener</code> value
+   */
+  public void addActionListener(ActionListener a) {
+    m_ColourChangeListeners.addElement(a);
+  }
+
+  /**
+   * Set up fonts and font metrics
+   * @param gx the graphics context
+   */
+  private void setFonts(Graphics gx) {
+    if (m_labelMetrics == null) {
+      m_labelFont = new Font("Monospaced", Font.PLAIN, 12);
+      m_labelMetrics = gx.getFontMetrics(m_labelFont);
+      int hf = m_labelMetrics.getAscent();
+      if (this.getHeight() < (3*hf)) {
+	m_labelFont = new Font("Monospaced",Font.PLAIN,11);
+	m_labelMetrics = gx.getFontMetrics(m_labelFont);
+      }
+    }
+    gx.setFont(m_labelFont);
+  }
+    
+  /**
+   * Enables the panel
+   * @param e true to enable the panel
+   */
+  public void setOn(boolean e) {
+    m_isEnabled = e;
+  }
+
+  /**
+   * Set the instances.
+   * @param insts the instances
+   */
+  public void setInstances(Instances insts) {
+    m_Instances = insts;
+  }
+
+  /**
+   * Set the index of the attribute to display coloured labels for
+   * @param cIndex the index of the attribute to display coloured labels for
+   */
+  public void setCindex(int cIndex) {
+    if (m_Instances.numAttributes() > 0) {
+      m_cIndex = cIndex;
+      if (m_Instances.attribute(m_cIndex).isNumeric()) {
+	setNumeric();
+      } else {
+	if (m_Instances.attribute(m_cIndex).numValues() > m_colorList.size()) {
+	  extendColourMap();
+	}
+	setNominal();
+      }
+    }
+  }
+
+  /**
+   * Extends the list of colours if a new attribute with more values than
+   * the previous one is chosen
+   */
+  private void extendColourMap() {
+    if (m_Instances.attribute(m_cIndex).isNominal()) {
+      for (int i = m_colorList.size(); 
+	   i < m_Instances.attribute(m_cIndex).numValues();
+	   i++) {
+	Color pc = m_DefaultColors[i % 10];
+	int ija =  i / 10;
+	ija *= 2; 
+	for (int j=0;j<ija;j++) {
+	  pc = pc.brighter();
+	}
+        if (m_backgroundColor != null) {
+          pc = Plot2D.checkAgainstBackground(pc, m_backgroundColor);
+        }
+	
+	m_colorList.addElement(pc);
+      }
+    }
+  }
+    
+  protected void setDefaultColourList(Color[] list) {
+    m_DefaultColors = list;
+  }
+
+  /**
+   * Set a list of colours to use for colouring labels
+   * @param cols a list containing java.awt.Colors
+   */
+  public void setColours(FastVector cols) {
+    m_colorList = cols;
+  }
+    
+  /**
+   * Sets the legend to be for a nominal variable
+   */
+  protected void setNominal() {
+    m_isNumeric = false;
+    m_HorizontalPad = 0;
+    setOn(true);
+    m_oldWidth = -9000;
+     
+    this.repaint();
+  }
+
+  /**
+   * Sets the legend to be for a numeric variable
+   */
+  protected void setNumeric() {
+    m_isNumeric = true;
+    /*      m_maxC = mxC;
+	    m_minC = mnC; */
+
+    double min=Double.POSITIVE_INFINITY;
+    double max=Double.NEGATIVE_INFINITY;
+    double value;
+
+    for (int i=0;i<m_Instances.numInstances();i++) {
+      if (!m_Instances.instance(i).isMissing(m_cIndex)) {
+	value = m_Instances.instance(i).value(m_cIndex);
+	if (value < min) {
+	  min = value;
+	}
+	if (value > max) {
+	  max = value;
+	}
+      }
+    }
+     
+    // handle case where all values are missing
+    if (min == Double.POSITIVE_INFINITY) min = max = 0.0;
+
+    m_minC = min; m_maxC = max;
+
+    int whole = (int)Math.abs(m_maxC);
+    double decimal = Math.abs(m_maxC) - whole;
+    int nondecimal;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    
+    m_precisionC = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_maxC)) / 
+				      Math.log(10))))+2
+      : 1;
+    if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
+      m_precisionC = 1;
+    }
+
+    String maxStringC = Utils.doubleToString(m_maxC,
+					     nondecimal+1+m_precisionC
+					     ,m_precisionC);
+    if (m_labelMetrics != null) {
+      m_HorizontalPad = m_labelMetrics.stringWidth(maxStringC);
+    }
+
+    whole = (int)Math.abs(m_minC);
+    decimal = Math.abs(m_minC) - whole;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    
+     m_precisionC = (decimal > 0) 
+       ? (int)Math.abs(((Math.log(Math.abs(m_minC)) / 
+				      Math.log(10))))+2
+      : 1;
+     if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
+       m_precisionC = 1;
+     }
+    
+     maxStringC = Utils.doubleToString(m_minC,
+				       nondecimal+1+m_precisionC
+				       ,m_precisionC);
+     if (m_labelMetrics != null) {
+       if (m_labelMetrics.stringWidth(maxStringC) > m_HorizontalPad) {
+	 m_HorizontalPad = m_labelMetrics.stringWidth(maxStringC);
+       }
+     }
+
+    setOn(true);
+    this.repaint();
+  }
+    
+  /**
+   * Renders the legend for a nominal colouring attribute
+   * @param gx the graphics context
+   */
+  protected void paintNominal(Graphics gx) {
+    setFonts(gx);
+
+
+
+    int numClasses;
+
+    numClasses = m_Instances.attribute(m_cIndex).numValues();
+
+    int maxLabelLen = 0;
+    int idx=0;
+    int legendHeight;
+    int w = this.getWidth();
+    int hf = m_labelMetrics.getAscent();
+
+
+    for (int i=0;i<numClasses;i++) {
+      if (m_Instances.attribute(m_cIndex).value(i).length() > 
+	  maxLabelLen) {
+	maxLabelLen = m_Instances.
+	  attribute(m_cIndex).value(i).length();
+	idx = i;
+      }
+    }
+      
+    maxLabelLen = m_labelMetrics.stringWidth(m_Instances.
+					     attribute(m_cIndex).value(idx));
+    
+
+    if (((w-(2*m_HorizontalPad))/(maxLabelLen+5)) >= numClasses) {
+      legendHeight = 1;
+    } else {
+      legendHeight = 2;
+    }
+	
+    int x = m_HorizontalPad;
+    int y = 1 + hf;
+
+    // do the first row
+    int ci, mp;
+    Color pc;
+    int numToDo = ((legendHeight==1) ? numClasses : (numClasses/2));
+    for (int i=0;i<numToDo;i++) {
+     
+      gx.setColor((Color)m_colorList.elementAt(i));
+      // can we fit the full label or will each need to be trimmed?
+      if ((numToDo * maxLabelLen) > (w-(m_HorizontalPad*2))) {
+	String val;
+	val = m_Instances.attribute(m_cIndex).value(i);
+
+	int sw = m_labelMetrics.stringWidth(val);
+	int rm=0;
+	// truncate string if necessary
+	if (sw > ((w-(m_HorizontalPad*2)) / (numToDo))) {
+	  int incr = (sw / val.length());
+	  rm = (sw -  ((w-(m_HorizontalPad*2)) / numToDo)) / incr;
+	  if (rm <= 0) {
+	    rm = 0;
+	  }
+	  if (rm >= val.length()) {
+	    rm = val.length() - 1;
+	  }
+	  val = val.substring(0,val.length()-rm);
+	  sw = m_labelMetrics.stringWidth(val);
+	}
+	NomLabel jj = new NomLabel(val, i);
+	jj.setFont(gx.getFont());
+
+	jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
+		   m_labelMetrics.getAscent() + 4);
+	this.add(jj);
+	jj.setLocation(x, y);
+	jj.setForeground((Color)m_colorList.
+			 elementAt(i % m_colorList.size()));
+
+	x += sw + 2;
+      } else {
+	
+	NomLabel jj;
+	jj = new NomLabel(m_Instances.attribute(m_cIndex).value(i), i);
+
+	jj.setFont(gx.getFont());
+
+	jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
+		   m_labelMetrics.getAscent() + 4);
+	this.add(jj);
+	jj.setLocation(x, y);
+	jj.setForeground((Color)m_colorList.
+			 elementAt(i % m_colorList.size()));
+
+  
+
+	x += ((w-(m_HorizontalPad*2)) / numToDo);
+      }	  
+    }
+
+    x = m_HorizontalPad;
+    y = 1+ hf + 5 +hf;
+    for (int i=numToDo;i<numClasses;i++) {
+      
+      gx.setColor((Color)m_colorList.elementAt(i));
+      if (((numClasses-numToDo+1) * maxLabelLen) > 
+	  (w - (m_HorizontalPad*2))) {
+	String val;
+	val = m_Instances.attribute(m_cIndex).value(i);
+
+	int sw = m_labelMetrics.stringWidth(val);
+	int rm=0;
+	// truncate string if necessary
+	if (sw > ((w-(m_HorizontalPad*2)) / (numClasses-numToDo+1))) {
+	  int incr = (sw / val.length());
+	  rm = (sw -  ((w-(m_HorizontalPad*2)) / (numClasses-numToDo))) 
+	    / incr;
+	  if (rm <= 0) {
+	    rm = 0;
+	  }
+	  if (rm >= val.length()) {
+	    rm = val.length() - 1;
+	  }
+	  val = val.substring(0,val.length()-rm);
+	  sw = m_labelMetrics.stringWidth(val);
+	}
+	//this is the clipped string
+	NomLabel jj = new NomLabel(val, i);
+	jj.setFont(gx.getFont());
+
+	jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
+		   m_labelMetrics.getAscent() + 4);
+
+	this.add(jj);
+	jj.setLocation(x, y);
+	jj.setForeground((Color)m_colorList.
+			 elementAt(i % m_colorList.size()));
+	
+	x += sw +2;
+      } else {
+	//this is the full string
+	NomLabel jj;
+	jj = new NomLabel(m_Instances.attribute(m_cIndex).value(i), i);
+
+	jj.setFont(gx.getFont());
+
+	jj.setSize(m_labelMetrics.stringWidth(jj.getText()),
+		   m_labelMetrics.getAscent() + 4);
+	this.add(jj);
+	jj.setLocation(x, y);
+	jj.setForeground((Color)m_colorList.
+			 elementAt(i % m_colorList.size()));
+
+	x += ((w - (m_HorizontalPad*2)) / (numClasses-numToDo));
+      }	  
+    }
+
+  }
+
+  /**
+   * Renders the legend for a numeric colouring attribute
+   * @param gx the graphics context
+   */
+  protected void paintNumeric(Graphics gx) {
+
+    setFonts(gx);
+    if (m_HorizontalPad == 0) {
+      setCindex(m_cIndex);
+    }
+
+    int w = this.getWidth();
+    double rs = 15;
+    double incr = 240.0 / (double)(w-(m_HorizontalPad*2));
+    int hf = m_labelMetrics.getAscent();
+      
+    for (int i=m_HorizontalPad;i<
+	   (w-m_HorizontalPad);i++) {
+      Color c = new Color((int)rs,150,(int)(255-rs));
+      gx.setColor(c);
+      gx.drawLine(i,0,
+		  i,0+m_spectrumHeight);
+      rs += incr;
+    }
+
+    int whole = (int)Math.abs(m_maxC);
+    double decimal = Math.abs(m_maxC) - whole;
+    int nondecimal;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    
+    m_precisionC = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_maxC)) / 
+			Math.log(10))))+2
+      : 1;
+    if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
+      m_precisionC = 1;
+    }
+
+    String maxStringC = Utils.doubleToString(m_maxC,
+					     nondecimal+1+m_precisionC
+					     ,m_precisionC);
+
+	
+    int mswc = m_labelMetrics.stringWidth(maxStringC);
+    int tmsc = mswc;
+    if (w > (2 * tmsc)) {
+      gx.setColor(Color.black);
+      gx.drawLine(m_HorizontalPad,
+		  (m_spectrumHeight+5),
+		  w-m_HorizontalPad,
+		  (m_spectrumHeight+5));
+
+      gx.drawLine(w-m_HorizontalPad,
+		  (m_spectrumHeight+5),
+		  w-m_HorizontalPad,
+		  (m_spectrumHeight+5+m_tickSize));
+
+      gx.drawString(maxStringC, 
+		    (w-m_HorizontalPad)-(mswc/2),
+		    (m_spectrumHeight+5+m_tickSize+hf));
+
+      gx.drawLine(m_HorizontalPad,
+		  (m_spectrumHeight+5),
+		  m_HorizontalPad,
+		  (m_spectrumHeight+5+m_tickSize));
+
+      whole = (int)Math.abs(m_minC);
+      decimal = Math.abs(m_minC) - whole;
+      nondecimal = (whole > 0) 
+	? (int)(Math.log(whole) / Math.log(10))
+	: 1;
+      
+      m_precisionC = (decimal > 0) 
+	? (int)Math.abs(((Math.log(Math.abs(m_minC)) / 
+			  Math.log(10))))+2
+	: 1;
+
+      if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
+	m_precisionC = 1;
+      }
+      
+      maxStringC = Utils.doubleToString(m_minC,
+					nondecimal+1+m_precisionC
+					,m_precisionC);
+
+      mswc = m_labelMetrics.stringWidth(maxStringC);
+      gx.drawString(maxStringC, 
+		    m_HorizontalPad-(mswc/2),
+		    (m_spectrumHeight+5+m_tickSize+hf));
+
+      // draw the middle value if there is space
+      if (w > (3 * tmsc)) {
+	double mid = m_minC+((m_maxC-m_minC)/2.0);
+	gx.drawLine(m_HorizontalPad+((w-(2*m_HorizontalPad))/2),
+		    (m_spectrumHeight+5),
+		    m_HorizontalPad+((w-(2*m_HorizontalPad))/2),
+		    (m_spectrumHeight+5+m_tickSize));
+
+	whole = (int)Math.abs(mid);
+	decimal = Math.abs(mid) - whole;
+	nondecimal = (whole > 0) 
+	  ? (int)(Math.log(whole) / Math.log(10))
+	  : 1;
+	
+	m_precisionC = (decimal > 0) 
+	  ? (int)Math.abs(((Math.log(Math.abs(mid)) / 
+			    Math.log(10))))+2
+	  : 1;
+	if (m_precisionC > VisualizeUtils.MAX_PRECISION) {
+	  m_precisionC = 1;
+	}
+	
+	maxStringC = Utils.doubleToString(mid,
+					  nondecimal+1+m_precisionC
+					  ,m_precisionC);
+
+	mswc = m_labelMetrics.stringWidth(maxStringC);
+	gx.drawString(maxStringC,
+		      m_HorizontalPad+((w-(2*m_HorizontalPad))/2)-(mswc/2),
+		      (m_spectrumHeight+5+m_tickSize+hf));
+      }
+    }
+  }
+
+  /**
+   * Renders this component
+   * @param gx the graphics context
+   */
+  public void paintComponent(Graphics gx) {
+    super.paintComponent(gx);
+    if (m_isEnabled) {
+      if (m_isNumeric) {
+	m_oldWidth = -9000;   //done so that if change back to nom, it will
+	//work
+	this.removeAll();
+	paintNumeric(gx);
+      } else {
+	if (m_Instances != null && 
+	    m_Instances.numInstances() > 0 && 
+	    m_Instances.numAttributes() > 0) {
+	  if (m_oldWidth != this.getWidth()) {
+	    this.removeAll();
+	    m_oldWidth = this.getWidth();
+	    paintNominal(gx);
+	  }
+	}
+      }
+    }
+  }
+
+  /**
+   * Main method for testing this class.
+   * @param args first argument must specify an arff file. Second can
+   * specify an optional index to colour labels on
+   */
+  public static void main(String [] args) {
+    try {
+      if (args.length < 1) {
+	System.err.println("Usage : weka.gui.visualize.ClassPanel <dataset> "
+			   +"[class col]");
+	System.exit(1);
+      }
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Explorer: Class");
+      jf.setSize(500,100);
+      jf.getContentPane().setLayout(new BorderLayout());
+      final ClassPanel p2 = new ClassPanel();
+      jf.getContentPane().add(p2, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+	
+      if (args.length >= 1) {
+	System.err.println("Loading instances from " + args[0]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[0]));
+	Instances i = new Instances(r);
+	i.setClassIndex(i.numAttributes()-1);
+	p2.setInstances(i);
+      }
+      if (args.length > 1) {
+	p2.setCindex((Integer.parseInt(args[1]))-1);
+      } else {
+	p2.setCindex(0);
+      }
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfo.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfo.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfo.java	(revision 29)
@@ -0,0 +1,63 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InstanceInfo.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Instances;
+
+import java.util.Vector;
+
+/**
+ * Interface for JFrames that display instance info.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5014 $
+ */
+public interface InstanceInfo {
+  
+  /**
+   * Sets the text to display.
+   * 
+   * @param text	the text to display
+   */
+  public void setInfoText(String text);
+  
+  /**
+   * Returns the currently displayed info text.
+   * 
+   * @return		the info text
+   */
+  public String getInfoText();
+  
+  /**
+   * Sets the underlying data.
+   * 
+   * @param data	the data of the info text
+   */
+  public void setInfoData(Vector<Instances> data);
+  
+  /**
+   * Returns the underlying data.
+   * 
+   * @return		the data of the info text, can be null
+   */
+  public Vector<Instances> getInfoData();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfoFrame.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfoFrame.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/InstanceInfoFrame.java	(revision 29)
@@ -0,0 +1,129 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * InstanceInfoFrame.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * Frame for displaying information on the displayed data.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5014 $
+ */
+public class InstanceInfoFrame
+  extends JFrame
+  implements InstanceInfo {
+
+  /** for serialization. */
+  private static final long serialVersionUID = 4684184733677263009L;
+
+  /** the underlying data. */
+  protected Vector<Instances> m_Data;
+  
+  /** the text area for displaying the info. */
+  protected JTextArea m_TextInfo;
+  
+  /**
+   * Initializes the frame.
+   */
+  public InstanceInfoFrame() {
+    super("Weka: Instance info");
+    
+    initialize();
+    initGUI();
+    initFinished();
+  }
+  
+  /**
+   * Initializes member variables.
+   */
+  protected void initialize() {
+    m_Data = new Vector<Instances>();
+  }
+  
+  /**
+   * Sets up the GUI components.
+   */
+  protected void initGUI() {
+    getContentPane().setLayout(new BorderLayout());
+    
+    m_TextInfo = new JTextArea();
+    m_TextInfo.setEditable(false);
+    m_TextInfo.setFont(new Font("Monospaced", Font.PLAIN,12));
+    getContentPane().add(new JScrollPane(m_TextInfo), BorderLayout.CENTER);
+    
+    pack();
+    setSize(320, 400);
+  }
+  
+  /**
+   * A hook method after initialize() and initGUI have been called.
+   */
+  protected void initFinished() {
+  }
+  
+  /**
+   * Sets the text to display.
+   * 
+   * @param text	the text to display
+   */
+  public void setInfoText(String text) {
+    m_TextInfo.setText(text);
+  }
+  
+  /**
+   * Returns the currently displayed info text.
+   * 
+   * @return		the info text
+   */
+  public String getInfoText() {
+    return m_TextInfo.getText();
+  }
+  
+  /**
+   * Sets the underlying data.
+   * 
+   * @param data	the data of the info text
+   */
+  public void setInfoData(Vector<Instances> data) {
+    m_Data = new Vector<Instances>();
+    if (data != null)
+      m_Data.addAll(data);
+  }
+  
+  /**
+   * Returns the underlying data.
+   * 
+   * @return		the data of the info text, can be null
+   */
+  public Vector<Instances> getInfoData() {
+    return m_Data;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/JComponentWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/JComponentWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/JComponentWriter.java	(revision 29)
@@ -0,0 +1,356 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    JComponentWriter.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import java.io.File;
+import javax.swing.JComponent;
+
+/** 
+ * This class takes any JComponent and outputs it to a file. Scaling is by
+ * default enabled. Derived classes only need to override the following
+ * methods:
+ * <ul>
+ *   <li><code>getDescription()</code></li>
+ *   <li><code>getExtension()</code></li>
+ *   <li><code>generateOutput()</code></li>
+ *   <li><code></code></li>
+ * </ul>
+ *
+ * @see #setScalingEnabled(boolean)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public abstract class JComponentWriter {
+  /** whether to print some debug information */
+  protected final static boolean DEBUG = false;
+  
+  /** output if we're in debug mode */
+  static {
+    if (DEBUG)
+      System.err.println(JComponentWriter.class.getName() + ": DEBUG ON");
+  }
+  
+  /** the component to print in the output format */
+  private JComponent component;
+  
+  /** the file to write the output stream to */
+  private File outputFile;
+  
+  /** the x scale factor */
+  protected double m_xScale;
+  
+  /** the y scale factor */
+  protected double m_yScale;
+
+  /** whether scaling is enabled */
+  protected boolean m_ScalingEnabled;
+  
+  /** whether to use custom dimensions */
+  protected boolean m_UseCustomDimensions;
+  
+  /** the custom width */
+  protected int m_CustomWidth;
+  
+  /** the custom height */
+  protected int m_CustomHeight;
+  
+  /**
+   * initializes the object 
+   */
+  public JComponentWriter() {
+    this(null);
+  }
+  
+  /**
+   * initializes the object with the given Component
+   * 
+   * @param c         the component to print in the output format
+   */
+  public JComponentWriter(JComponent c) {
+    this(c, null);
+  }
+  
+  /**
+   * initializes the object with the given Component and filename
+   * 
+   * @param c         the component to print in the output format
+   * @param f         the file to store the output in
+   */
+  public JComponentWriter(JComponent c, File f) {
+    component  = c;
+    outputFile = f;
+    
+    initialize();
+  }
+  
+  /**
+   * further initialization can take place here 
+   */
+  protected void initialize() {
+    m_xScale = 1.0;
+    m_yScale = 1.0;
+    m_ScalingEnabled = true;
+    m_UseCustomDimensions = false;
+    m_CustomWidth = -1;
+    m_CustomHeight = -1;
+  }
+  
+  /**
+   * sets the component to print to an output format
+   * 
+   * @param c the component to print
+   */
+  public void setComponent(JComponent c) {
+    component = c;
+  }
+  
+  /**
+   * returns the component that is stored in the output format
+   * 
+   * @return the component to print
+   */
+  public JComponent getComponent() {
+    return component;
+  }
+  
+  /**
+   * sets the file to store the output in
+   * 
+   * @param f the file to store the output in
+   */
+  public void setFile(File f) {
+    outputFile = f;
+  }
+  
+  /**
+   * returns the file being used for storing the output
+   * 
+   * @return the file to store the output in
+   */
+  public File getFile() {
+    return outputFile;
+  }
+  
+  /**
+   * returns the name of the writer, to display in the FileChooser.
+   * must be overridden in the derived class.
+   * 
+   * @return the name of the writer
+   */
+  public abstract String getDescription();
+  
+  /**
+   * returns the extension (incl. ".") of the output format, to use in the
+   * FileChooser. 
+   * must be overridden in the derived class.
+   * 
+   * @return the file extension
+   */
+  public abstract String getExtension();
+  
+  /**
+   * whether scaling is enabled or ignored
+   * 
+   * @return true if scaling is enabled
+   */
+  public boolean getScalingEnabled() {
+    return m_ScalingEnabled;
+  }
+  
+  /**
+   * sets whether to enable scaling
+   * 
+   * @param enabled whether scaling is enabled
+   */
+  public void setScalingEnabled(boolean enabled) {
+    m_ScalingEnabled = enabled;
+  }
+  
+  /**
+   * sets the scale factor - is ignored since we always create a screenshot!
+   * @param x the scale factor for the x-axis 
+   * @param y the scale factor for the y-axis 
+   */
+  public void setScale(double x, double y) {
+    if (getScalingEnabled()) {
+      m_xScale = x;
+      m_yScale = y;
+    }
+    else {
+      m_xScale = 1.0;
+      m_yScale = 1.0;
+    }
+    
+    if (DEBUG)
+      System.err.println("xScale = " + m_xScale + ", yScale = " + m_yScale);
+  }
+  
+  /**
+   * returns the scale factor for the x-axis
+   * 
+   * @return the scale scale factor for the x-axis
+   */
+  public double getXScale() {
+    return m_xScale;
+  }
+  
+  /**
+   * returns the scale factor for the y-axis
+   * 
+   * @return the scale scale factor for the y-axis
+   */
+  public double getYScale() {
+    return m_xScale;
+  }
+  
+  /**
+   * whether custom dimensions are to used for the size of the image
+   * 
+   * @return true if custom dimensions are used
+   */
+  public boolean getUseCustomDimensions() {
+    return m_UseCustomDimensions;
+  }
+  
+  /**
+   * sets whether to use custom dimensions for the image
+   * 
+   * @param value whether custom dimensions are used
+   */
+  public void setUseCustomDimensions(boolean value) {
+    m_UseCustomDimensions = value;
+  }
+  
+  /**
+   * sets the custom width to use
+   * 
+   * @param value the width to use
+   * @see #m_UseCustomDimensions
+   */
+  public void setCustomWidth(int value) {
+    m_CustomWidth = value;
+  }
+  
+  /**
+   * gets the custom width currently used
+   * 
+   * @return the custom width currently used
+   * @see #m_UseCustomDimensions
+   */
+  public int getCustomWidth() {
+    return m_CustomWidth;
+  }
+  
+  /**
+   * sets the custom height to use
+   * 
+   * @param value the height to use
+   * @see #m_UseCustomDimensions
+   */
+  public void setCustomHeight(int value) {
+    m_CustomHeight = value;
+  }
+  
+  /**
+   * gets the custom height currently used
+   * 
+   * @return the custom height currently used
+   * @see #m_UseCustomDimensions
+   */
+  public int getCustomHeight() {
+    return m_CustomHeight;
+  }
+  
+  /**
+   * generates the actual output
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  protected abstract void generateOutput() throws Exception;
+  
+  /**
+   * saves the current component to the currently set file.<p>
+   * <b>Note:</b> this method calls <code>generateOutput()</code> which needs 
+   * to be overriden in subclasses!
+   * @throws Exception      if either the file or the component is <code>null</code>
+   */
+  public void toOutput() throws Exception {
+    int		oldWidth;
+    int		oldHeight;
+
+    if (getFile() == null)
+      throw new Exception("The file is not set!");
+    if (getComponent() == null)
+      throw new Exception("The component is not set!");
+
+    // backup original dimensions and set custom ones if necessary
+    oldWidth  = getComponent().getWidth();
+    oldHeight = getComponent().getHeight();
+    if (getUseCustomDimensions())
+      getComponent().setSize(getCustomWidth(), getCustomHeight());
+
+    generateOutput();
+    
+    // restore original dimensions
+    if (getUseCustomDimensions())
+      getComponent().setSize(oldWidth, oldHeight);
+  }
+  
+  /**
+   * outputs the given component with the given writer in the specified file
+   * 
+   * @param writer	the writer to use
+   * @param comp	the component to output
+   * @param file	the file to store the output in 
+   * @throws Exception  if component of file are <code>null</code>
+   */
+  public static void toOutput(JComponentWriter writer, JComponent comp, File file) throws Exception {
+    toOutput(writer, comp, file, -1, -1);
+  }
+  
+  /**
+   * outputs the given component with the given writer in the specified file. 
+   * If width and height are different from -1 then these sizes are used, 
+   * otherwise the current ones of the component
+   * 
+   * @param writer	the writer to use
+   * @param comp	the component to output
+   * @param file	the file to store the output in 
+   * @param width	custom width, -1 uses the component's one
+   * @param height	custom height, -1 uses the component's one
+   * @throws Exception  if component or file are <code>null</code>
+   */
+  public static void toOutput(JComponentWriter writer, JComponent comp, File file, int width, int height) throws Exception {
+    writer.setComponent(comp);
+    writer.setFile(file);
+    
+    // custom dimensions?
+    if ((width != -1) && (height != -1)) {
+      writer.setUseCustomDimensions(true);
+      writer.setCustomWidth(width);
+      writer.setCustomHeight(height);
+    }
+    
+    writer.toOutput();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/JPEGWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/JPEGWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/JPEGWriter.java	(revision 29)
@@ -0,0 +1,223 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    JPEGWriter.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Iterator;
+import java.util.Locale;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+import javax.swing.JComponent;
+
+/** 
+ * This class takes any JComponent and outputs it to a JPEG-file.
+ * Scaling is by default disabled, since we always take a screenshot.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5920 $
+ */
+public class JPEGWriter
+  extends JComponentWriter {
+  
+  /** the quality of the image. */
+  protected float m_Quality;
+  
+  /** the background color. */
+  protected Color m_Background;
+  
+  /**
+   * initializes the object.
+   */
+  public JPEGWriter() {
+    super();
+  }
+
+  /**
+   * initializes the object with the given Component.
+   * 
+   * @param c         the component to print in the output format
+   */
+  public JPEGWriter(JComponent c) {
+    super(c);
+  }
+
+  /**
+   * initializes the object with the given Component and filename.
+   * 
+   * @param c         the component to print in the output format
+   * @param f         the file to store the output in
+   */
+  public JPEGWriter(JComponent c, File f) {
+    super(c, f);
+    
+    m_Quality    = 1.0f;
+    m_Background = Color.WHITE;
+  }
+  
+  /**
+   * further initialization.
+   */
+  public void initialize() {
+    super.initialize();
+    
+    m_Quality    = 1.0f;
+    m_Background = Color.WHITE;
+    setScalingEnabled(false);
+  }
+
+  /**
+   * returns the name of the writer, to display in the FileChooser.
+   * must be overridden in the derived class.
+   * 
+   * @return the name of the writer
+   */
+  public String getDescription() {
+    return "JPEG-Image";
+  }
+  
+  /**
+   * returns the extension (incl. ".") of the output format, to use in the
+   * FileChooser. 
+   * must be overridden in the derived class.
+   * 
+   * @return the file extension
+   */
+  public String getExtension() {
+    return ".jpg";
+  }
+  
+  /**
+   * returns the current background color.
+   * 
+   * @return the current background color
+   */
+  public Color getBackground() {
+    return m_Background;
+  }
+  
+  /**
+   * sets the background color to use in creating the JPEG.
+   * 
+   * @param c the color to use for background
+   */
+  public void setBackground(Color c) {
+    m_Background = c;
+  }
+  
+  /**
+   * returns the quality the JPEG will be stored in.
+   * 
+   * @return the quality
+   */
+  public float getQuality() {
+    return m_Quality;
+  }
+  
+  /**
+   * sets the quality the JPEG is saved in.
+   * 
+   * @param q the quality to use
+   */
+  public void setQuality(float q) {
+    m_Quality = q;
+  }
+  
+  /**
+   * generates the actual output.
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  public void generateOutput() throws Exception {
+    BufferedImage	bi;
+    Graphics		g;
+    ImageWriter 	writer;
+    Iterator 		iter;
+    ImageOutputStream 	ios;
+    ImageWriteParam 	param;
+
+    // render image
+    bi = new BufferedImage(getComponent().getWidth(), getComponent().getHeight(), BufferedImage.TYPE_INT_RGB);
+    g  = bi.getGraphics();
+    g.setPaintMode();
+    g.setColor(getBackground());
+    if (g instanceof Graphics2D)
+      ((Graphics2D) g).scale(getXScale(), getYScale());
+    g.fillRect(0, 0, getComponent().getWidth(), getComponent().getHeight());
+    getComponent().printAll(g);
+    
+    // get jpeg writer
+    writer = null;
+    iter   = ImageIO.getImageWritersByFormatName(getExtension().replace(".", ""));
+    if (iter.hasNext())
+      writer = (ImageWriter) iter.next();
+    else
+      throw new Exception("No writer available for " + getDescription() + "!");
+
+    // prepare output file
+    ios = ImageIO.createImageOutputStream(getFile());
+    writer.setOutput(ios);
+
+    // set the quality
+    param = new JPEGImageWriteParam(Locale.getDefault());
+    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
+    param.setCompressionQuality(getQuality());
+
+    // write the image
+    writer.write(null, new IIOImage(bi, null, null), param);
+
+    // cleanup
+    ios.flush();
+    writer.dispose();
+    ios.close();    
+  }
+  
+  /**
+   * for testing only.
+   * 
+   * @param args the commandline arguments
+   * @throws Exception if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    System.out.println("building TreeVisualizer...");
+    weka.gui.treevisualizer.TreeBuild builder = new weka.gui.treevisualizer.TreeBuild();
+    weka.gui.treevisualizer.NodePlace arrange = new weka.gui.treevisualizer.PlaceNode2();
+    weka.gui.treevisualizer.Node top = builder.create(new java.io.StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
+    weka.gui.treevisualizer.TreeVisualizer tv = new weka.gui.treevisualizer.TreeVisualizer(null, top, arrange);
+    tv.setSize(800 ,600);
+    
+    String filename = System.getProperty("java.io.tmpdir") + File.separator + "test.jpg";
+    System.out.println("outputting to '" + filename + "'...");
+    toOutput(new JPEGWriter(), tv, new File(filename));
+
+    System.out.println("done!");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/LegendPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/LegendPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/LegendPanel.java	(revision 29)
@@ -0,0 +1,286 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    LegendPanel.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JColorChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+/**
+ * This panel displays legends for a list of plots. If a given plot
+ * has a custom colour defined then this panel allows the colour to
+ * be changed.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 4752 $
+ */
+public class LegendPanel
+  extends JScrollPane {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1262384440543001505L;
+
+  /** the list of plot elements */
+  protected FastVector m_plots;
+
+  /** the panel that contains the legend entries */
+  protected JPanel m_span=null;
+
+  /** a list of components that need to be repainted when a colour is
+      changed */
+  protected FastVector m_Repainters = new FastVector();
+
+  /**
+   * Inner class for handling legend entries
+   */
+  protected class LegendEntry
+    extends JPanel {
+
+    /** for serialization */
+    private static final long serialVersionUID = 3879990289042935670L;
+
+    /** the data for this legend entry */
+    private PlotData2D m_plotData=null;
+
+    /** the index (in the list of plots) of the data for this legend---
+	used to draw the correct shape for this data */
+    private int m_dataIndex;
+
+    /** the text part of this legend */
+    private JLabel m_legendText;
+
+    /** displays the point shape associated with this legend entry */
+    private JPanel m_pointShape;
+
+    public LegendEntry(PlotData2D data, int dataIndex) {
+      javax.swing.ToolTipManager.sharedInstance().setDismissDelay(5000);
+      m_plotData = data;
+      m_dataIndex = dataIndex;
+      //      this.setBackground(Color.black);
+      /*      this.setPreferredSize(new Dimension(0, 20));
+	      this.setMinimumSize(new Dimension(0, 20)); */
+
+      if (m_plotData.m_useCustomColour) {
+	this.addMouseListener(new MouseAdapter() {
+	    public void mouseClicked(MouseEvent e) {
+	      
+	      if ((e.getModifiers() & e.BUTTON1_MASK) == e.BUTTON1_MASK) {
+		Color tmp = JColorChooser.showDialog
+		  (LegendPanel.this, "Select new Color", 
+		   m_plotData.m_customColour);
+		
+		if (tmp != null) {
+		  m_plotData.m_customColour = tmp;
+		  m_legendText.setForeground(tmp);
+
+		  if (m_Repainters.size() > 0) {
+		    for (int i=0;i<m_Repainters.size();i++) {
+		      ((Component)(m_Repainters.elementAt(i))).repaint();
+		    }
+		  }
+		  LegendPanel.this.repaint();
+		}
+	      }
+	    }
+	  });
+      }
+
+      m_legendText = new JLabel(m_plotData.m_plotName);
+      m_legendText.setToolTipText(m_plotData.getPlotNameHTML());
+      if (m_plotData.m_useCustomColour) {
+	m_legendText.setForeground(m_plotData.m_customColour);
+      }
+      this.setLayout(new BorderLayout());
+      this.add(m_legendText, BorderLayout.CENTER);
+      /*      GridBagLayout gb = new GridBagLayout();
+      GridBagConstraints constraints = new GridBagConstraints();
+      constraints.fill = GridBagConstraints.HORIZONTAL;
+      constraints.gridx=0;constraints.gridy=0;constraints.weightx=5; */
+      m_pointShape = new JPanel() {
+	private static final long serialVersionUID = -7048435221580488238L;
+	
+	public void paintComponent(Graphics gx) {
+	  super.paintComponent(gx);
+	  if (!m_plotData.m_useCustomColour) {
+	    gx.setColor(Color.black);
+	  } else {
+	    gx.setColor(m_plotData.m_customColour);
+	  }
+	  Plot2D.drawDataPoint(10,10,3,m_dataIndex,gx);
+	}
+      };
+      //      m_pointShape.setBackground(Color.black);
+      m_pointShape.setPreferredSize(new Dimension(20, 20));
+      m_pointShape.setMinimumSize(new Dimension(20, 20));
+      this.add(m_pointShape, BorderLayout.WEST);
+    }
+  }
+
+  /**
+   * Constructor
+   */
+  public LegendPanel() {
+    this.setBackground(Color.blue);
+    setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
+  }
+
+  /**
+   * Set the list of plots to generate legend entries for
+   * @param pl a list of plots
+   */
+  public void setPlotList(FastVector pl) {
+    m_plots = pl;
+    updateLegends();
+  }
+
+  /**
+   * Adds a component that will need to be repainted if the user
+   * changes the colour of a label.
+   * @param c the component to be repainted
+   */
+  public void addRepaintNotify(Component c) {
+    m_Repainters.addElement(c);
+  }
+
+  /**
+   * Redraw the panel with the legend entries
+   */
+  private void updateLegends() {
+    if (m_span == null) {
+      m_span = new JPanel();
+    }
+      
+    JPanel padder = new JPanel();
+    JPanel padd2 = new JPanel();
+
+    m_span.setPreferredSize(new Dimension(m_span.getPreferredSize().width, 
+					  (m_plots.size() + 1) * 20));
+    m_span.setMaximumSize(new Dimension(m_span.getPreferredSize().width, 
+					  (m_plots.size() + 1) * 20));
+
+    LegendEntry tmp;
+
+    GridBagLayout gb = new GridBagLayout();
+    GridBagLayout gb2 = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+      
+    m_span.removeAll();
+
+    padder.setLayout(gb);
+    m_span.setLayout(gb2);
+    constraints.anchor = GridBagConstraints.CENTER;
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(0, 0, 0, 0);
+    padder.add(m_span, constraints);
+
+    constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.BOTH;
+    constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+    constraints.insets = new Insets(0, 0, 0, 0);
+    padder.add(padd2, constraints);
+
+    constraints.weighty=0;
+    setViewportView(padder);
+
+    constraints.anchor = GridBagConstraints.CENTER;
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+    constraints.insets = new Insets(2,4,2,4);
+    //int numLines = ((PlotData2D)m_plots.elementAt(0)).getPlotName().split("<br>").length;
+     for (int i=0;i<m_plots.size();i++) {
+       tmp = new LegendEntry((PlotData2D)m_plots.elementAt(i),i);
+       constraints.gridy = i;
+/*       constraints.gridheight = 1;
+       if (numLines > 0) {
+         constraints.gridheight = (numLines + 2);
+       } */
+       m_span.add(tmp, constraints);
+     }
+  }
+
+  /**
+   * Main method for testing this class
+   * @param args a list of arff files
+   */
+  public static void main(String [] args) {
+    try {
+      if (args.length < 1) {
+	System.err.println("Usage : weka.gui.visualize.LegendPanel "
+			   +"<dataset> [dataset2], [dataset3],...");
+	System.exit(1);
+      }
+
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Explorer: Legend");
+      jf.setSize(100,100);
+      jf.getContentPane().setLayout(new BorderLayout());
+      final LegendPanel p2 = new LegendPanel();
+      jf.getContentPane().add(p2, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+
+      FastVector plotList = new FastVector();
+      for (int j=0;j<args.length;j++) {
+	System.err.println("Loading instances from " + args[j]);
+	java.io.Reader r = new java.io.BufferedReader(
+			   new java.io.FileReader(args[j]));
+	Instances i = new Instances(r);
+	PlotData2D tmp = new PlotData2D(i);
+	if (j != 1) {
+	  tmp.m_useCustomColour = true;
+	  tmp.m_customColour = Color.red;
+	}
+	tmp.setPlotName(i.relationName());
+	plotList.addElement(tmp);
+      }
+      
+      p2.setPlotList(plotList);
+      jf.setVisible(true);
+    } catch (Exception ex) {
+      System.err.println(ex.getMessage());
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/MatrixPanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/MatrixPanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/MatrixPanel.java	(revision 29)
@@ -0,0 +1,1065 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    MatrixPanel.java
+ *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.gui.ExtensionFileFilter;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Random;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/** 
+ * This panel displays a plot matrix of the user selected attributes
+ * of a given data set. 
+ * 
+ * The datapoints are coloured using a discrete colouring set if the 
+ * user has selected a nominal attribute for colouring. If the user
+ * has selected a numeric attribute then the datapoints are coloured
+ * using a colour spectrum ranging from blue to red (low values to
+ * high). Datapoints missing a class value are displayed in black.
+ * 
+ * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz)
+ * @version $Revision: 1.18 $
+ */
+public class MatrixPanel
+  extends JPanel{
+
+  /** for serialization */
+  private static final long serialVersionUID = -1232642719869188740L;
+
+  /** The that panel contains the actual matrix */
+  private final Plot m_plotsPanel;
+
+  /** The panel that displays the legend of the colouring attribute */
+  protected final ClassPanel m_cp = new ClassPanel();
+
+  /** The panel that contains all the buttons and tools, i.e. resize, jitter bars and sub-sampling buttons etc
+      on the bottom of the panel */
+  protected JPanel optionsPanel;
+
+  /** Split pane for splitting the matrix and the buttons and bars */
+  protected JSplitPane jp;
+  /** The button that updates the display to reflect the changes made by the user. 
+      E.g. changed attribute set for the matrix    */
+  protected JButton m_updateBt = new JButton("Update");
+
+  /** The button to display a window to select attributes */
+  protected JButton m_selAttrib = new JButton("Select Attributes");
+
+  /** The dataset for which this panel will display the plot matrix for  */
+  protected Instances m_data=null;
+
+  /** The list for selecting the attributes to display the plot matrix */
+  protected JList m_attribList = new JList();
+
+  /** The scroll pane to scrolling the matrix */
+  protected final JScrollPane m_js = new JScrollPane();
+
+  /** The combo box to allow user to select the colouring attribute */
+  protected JComboBox m_classAttrib = new JComboBox();
+
+  /** The slider to adjust the size of the cells in the matrix  */  
+  protected JSlider m_plotSize = new JSlider(50, 500, 100);
+
+  /** The slider to adjust the size of the datapoints  */  
+  protected JSlider m_pointSize = new JSlider(1, 10, 1);
+
+  /** The slider to add jitter to the plots */  
+  protected JSlider m_jitter = new JSlider(0, 20, 0); 
+
+  /** For adding random jitter */
+  private Random rnd = new Random();
+    
+  /** Array containing precalculated jitter values */
+  private int jitterVals[][];
+ 
+  /** This stores the size of the datapoint */
+  private int datapointSize=1;
+
+  /** The text area for percentage to resample data */
+  protected JTextField m_resamplePercent = new JTextField(5);
+
+  /** The label for resample percentage */
+  protected JButton m_resampleBt =  new JButton("SubSample % :");
+
+  /** Random seed for random subsample */
+  protected JTextField m_rseed = new JTextField(5);
+ 
+  /** Displays the current size beside the slider bar for cell size */
+  private final JLabel m_plotSizeLb = new JLabel("PlotSize: [100]");
+
+  /** Displays the current size beside the slider bar for point size */
+  private final JLabel m_pointSizeLb = new JLabel("PointSize: [10]");
+
+  /** This array contains the indices of the attributes currently selected  */
+  private int [] m_selectedAttribs;
+
+  /** This contains the index of the currently selected colouring attribute  */
+  private int m_classIndex;
+
+  /** This is a local array cache for all the instance values for faster rendering */
+  private int [][] m_points;
+
+  /** This is an array cache for the colour of each of the instances depending on the 
+      colouring attribute. If the colouring attribute is nominal then it contains the 
+      index of the colour in our colour list. Otherwise, for numeric colouring attribute,
+      it contains the precalculated red component for each instance's colour */
+  private int [] m_pointColors;
+
+  /** Contains true for each attribute value (only the selected attributes+class attribute) 
+      that is  missing, for each instance.
+      m_missing[i][j] == true if m_selectedAttribs[j] is missing in instance i. 
+      m_missing[i][m_missing[].length-1] == true  if class value is missing in instance i. */ 
+  private boolean [][] m_missing;
+
+  /** This array contains for the classAttribute: <br>
+      m_type[0] = [type of attribute, nominal, string or numeric]<br>
+      m_type[1] = [number of discrete values of nominal or string attribute <br>
+      or same as m_type[0] for numeric attribute] */
+  private int [] m_type;
+
+  /** Stores the maximum size for PlotSize label to keep it's size constant */
+  private Dimension m_plotLBSizeD;
+
+  /** Stores the maximum size for PointSize label to keep it's size constant */
+  private Dimension m_pointLBSizeD;
+
+  /** Contains discrete colours for colouring for nominal attributes */
+  private FastVector m_colorList = new FastVector();
+
+  /** default colour list */
+  private static final Color [] m_defaultColors = {Color.blue,
+		 				   Color.red,
+						   Color.cyan,
+						   new Color(75, 123, 130),
+						   Color.pink,
+						   Color.green,
+						   Color.orange,
+						   new Color(255, 0, 255),
+						   new Color(255, 0, 0),
+						   new Color(0, 255, 0),
+						   Color.black};
+
+  /** color for the font used in column and row names */
+  private final Color fontColor = new Color(98, 101, 156);
+
+  /** font used in column and row names */
+  private final java.awt.Font f = new java.awt.Font("Dialog", java.awt.Font.BOLD, 11);
+
+
+
+  /** 
+   * Constructor
+   */
+  public MatrixPanel() {
+    m_rseed.setText("1");
+
+    /** Setting up GUI **/
+    m_selAttrib.addActionListener( new ActionListener() {
+	public void actionPerformed(ActionEvent ae) {
+	  final JDialog jd = new JDialog((JFrame) MatrixPanel.this.getTopLevelAncestor(), 
+					 "Attribute Selection Panel",
+					 true);
+
+	  JPanel jp = new JPanel();
+	  JScrollPane js = new JScrollPane(m_attribList);
+	  JButton okBt = new JButton("OK");
+	  JButton cancelBt = new JButton("Cancel");
+	  final int [] savedSelection = m_attribList.getSelectedIndices();
+					
+	  okBt.addActionListener( new ActionListener() {	
+	      public void actionPerformed(ActionEvent e) {
+		jd.dispose(); }
+	    } );
+
+	  cancelBt.addActionListener( new ActionListener() {
+	      public void actionPerformed(ActionEvent e) {
+		m_attribList.setSelectedIndices(savedSelection);
+		jd.dispose();}
+	    });
+	  jd.addWindowListener( new WindowAdapter() {
+	      public void windowClosing(WindowEvent e) {
+		m_attribList.setSelectedIndices(savedSelection);
+		jd.dispose();}
+	    });
+	  jp.add(okBt);
+	  jp.add(cancelBt);
+
+	  jd.getContentPane().add(js, BorderLayout.CENTER); 
+	  jd.getContentPane().add(jp, BorderLayout.SOUTH);
+
+	  if(js.getPreferredSize().width < 200)
+	    jd.setSize( 250, 250 );
+	  else
+	    jd.setSize( (int) js.getPreferredSize().width+10, 250);
+					
+	  jd.setLocation( m_selAttrib.getLocationOnScreen().x,
+			  m_selAttrib.getLocationOnScreen().y-jd.getHeight() );
+	  jd.setVisible(true);
+	}
+      });
+      
+    m_updateBt.addActionListener( new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	    //m_selectedAttribs = m_attribList.getSelectedIndices();
+	  initInternalFields();
+					
+	  Plot a = m_plotsPanel;
+	  a.setCellSize( m_plotSize.getValue() );					
+	  Dimension d = new Dimension((m_selectedAttribs.length)*(a.cellSize+a.extpad)+2, 
+				      (m_selectedAttribs.length)*(a.cellSize+a.extpad)+2
+				     );
+	  //System.out.println("Size: "+a.cellSize+" Extpad: "+
+	  //		   a.extpad+" selected: "+
+	  //		   m_selectedAttribs.length+' '+d); 
+	  a.setPreferredSize(d);
+	  a.setSize( a.getPreferredSize() );
+	  a.setJitter( m_jitter.getValue() );
+					
+	  m_js.revalidate();
+	  m_cp.setColours(m_colorList);
+	  m_cp.setCindex(m_classIndex);
+					
+	  repaint();
+	}
+      });
+    m_updateBt.setPreferredSize( m_selAttrib.getPreferredSize() );
+      
+    m_plotSize.addChangeListener( new ChangeListener() {
+	public void stateChanged(ChangeEvent ce) {
+	  m_plotSizeLb.setText("PlotSize: ["+m_plotSize.getValue()+"]");
+	  m_plotSizeLb.setPreferredSize( m_plotLBSizeD );
+	  m_jitter.setMaximum( m_plotSize.getValue()/5 ); //20% of cell Size
+	}
+      });
+ 
+    m_pointSize.addChangeListener( new ChangeListener() {
+	public void stateChanged(ChangeEvent ce) {
+	  m_pointSizeLb.setText("PointSize: ["+m_pointSize.getValue()+"]");
+	  m_pointSizeLb.setPreferredSize( m_pointLBSizeD );
+	  datapointSize = m_pointSize.getValue();
+	}
+      });
+ 
+    m_resampleBt.addActionListener( new ActionListener() { 
+	public void actionPerformed(ActionEvent e) {
+	  JLabel rseedLb = new JLabel("Random Seed: ");
+	  JTextField rseedTxt = m_rseed;
+	  JLabel percentLb = new JLabel("Subsample as");
+	  JLabel percent2Lb = new JLabel("% of input: ");
+	  final JTextField percentTxt = new JTextField(5);
+	  percentTxt.setText( m_resamplePercent.getText() );
+	  JButton doneBt = new JButton("Done");
+
+	  final JDialog jd = new JDialog((JFrame) MatrixPanel.this.getTopLevelAncestor(), 
+					 "Subsample % Panel",
+					 true) {
+	      private static final long serialVersionUID = -269823533147146296L;
+	      
+	      public void dispose() { 
+		m_resamplePercent.setText(percentTxt.getText());
+		super.dispose();
+	      } 
+	    };
+	  jd.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
+			       
+	  doneBt.addActionListener( new ActionListener(){ 
+	      public void actionPerformed(ActionEvent ae) {
+		jd.dispose();  
+	      }
+	    });
+	  GridBagLayout gbl = new GridBagLayout();
+	  GridBagConstraints gbc = new GridBagConstraints();
+	  JPanel p1 = new JPanel( gbl );		
+	  gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL;
+	  gbc.insets = new Insets(0,2,2,2);
+	  gbc.gridwidth = GridBagConstraints.RELATIVE;
+	  p1.add(rseedLb, gbc); gbc.weightx = 0;
+	  gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx=1;
+	  p1.add(rseedTxt, gbc);
+	  gbc.insets = new Insets(8,2,0,2); gbc.weightx=0;
+	  p1.add(percentLb, gbc);
+	  gbc.insets = new Insets(0,2,2,2); gbc.gridwidth = GridBagConstraints.RELATIVE;
+	  p1.add(percent2Lb, gbc);
+	  gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weightx=1;
+	  p1.add(percentTxt, gbc);
+	  gbc.insets = new Insets(8,2,2,2);
+
+	  JPanel p3 = new JPanel( gbl );
+	  gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridwidth = GridBagConstraints.REMAINDER;
+	  gbc.weightx = 1;  gbc.weighty = 0;
+	  p3.add(p1, gbc);
+	  gbc.insets = new Insets(8,4,8,4);
+	  p3.add(doneBt, gbc);
+					   
+	  jd.getContentPane().setLayout( new BorderLayout() );
+	  jd.getContentPane().add(p3, BorderLayout.NORTH);
+	  jd.pack();
+	  jd.setLocation( m_resampleBt.getLocationOnScreen().x,
+			  m_resampleBt.getLocationOnScreen().y-jd.getHeight() );
+	  jd.setVisible(true);
+	}
+      });
+
+    optionsPanel = new JPanel( new GridBagLayout() ); //all the rest of the panels are in here.
+    final JPanel p2 = new JPanel( new BorderLayout() );  //this has class colour panel
+    final JPanel p3 = new JPanel( new GridBagLayout() ); //this has update and select buttons
+    final JPanel p4 = new JPanel( new GridBagLayout() ); //this has the slider bars and combobox
+    GridBagConstraints gbc = new GridBagConstraints();
+     
+    m_plotLBSizeD = m_plotSizeLb.getPreferredSize();
+    m_pointLBSizeD = m_pointSizeLb.getPreferredSize();
+    m_pointSizeLb.setText("PointSize: [1]");
+    m_pointSizeLb.setPreferredSize( m_pointLBSizeD );
+    m_resampleBt.setPreferredSize( m_selAttrib.getPreferredSize() );
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+    gbc.insets = new Insets(2,2,2,2);
+    p4.add(m_plotSizeLb, gbc);
+    gbc.weightx=1; gbc.gridwidth = GridBagConstraints.REMAINDER;
+    p4.add(m_plotSize, gbc);
+    gbc.weightx=0; gbc.gridwidth = GridBagConstraints.RELATIVE;
+    p4.add(m_pointSizeLb, gbc);
+    gbc.weightx=1; gbc.gridwidth = GridBagConstraints.REMAINDER;
+    p4.add(m_pointSize, gbc);
+    gbc.weightx=0; gbc.gridwidth = GridBagConstraints.RELATIVE;
+    p4.add( new JLabel("Jitter: "), gbc);
+    gbc.weightx=1; gbc.gridwidth = GridBagConstraints.REMAINDER;
+    p4.add(m_jitter, gbc);
+    p4.add(m_classAttrib, gbc);
+      
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.weightx=1;
+    gbc.fill = GridBagConstraints.NONE;
+    p3.add(m_updateBt, gbc);
+    p3.add(m_selAttrib, gbc);
+    gbc.gridwidth = GridBagConstraints.RELATIVE;
+    gbc.weightx = 0;
+    gbc.fill = GridBagConstraints.VERTICAL;
+    gbc.anchor = GridBagConstraints.WEST;
+    p3.add(m_resampleBt, gbc);
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    p3.add(m_resamplePercent, gbc);
+    
+    p2.setBorder(BorderFactory.createTitledBorder("Class Colour"));
+    p2.add(m_cp, BorderLayout.SOUTH);
+
+    gbc.insets = new Insets(8,5,2,5);
+    gbc.anchor = GridBagConstraints.SOUTHWEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx=1;
+    gbc.gridwidth = GridBagConstraints.RELATIVE;
+    optionsPanel.add(p4, gbc);
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    optionsPanel.add(p3, gbc);
+    optionsPanel.add(p2, gbc);
+
+    this.addComponentListener( new ComponentAdapter() {
+	public void componentResized(ComponentEvent cv) {
+	  m_js.setMinimumSize( new Dimension(MatrixPanel.this.getWidth(),
+					     MatrixPanel.this.getHeight()
+					     -optionsPanel.getPreferredSize().height-10));
+	  jp.setDividerLocation( MatrixPanel.this.getHeight()-optionsPanel.getPreferredSize().height-10 );
+	}
+      });
+
+    optionsPanel.setMinimumSize( new Dimension(0,0) );
+    jp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, m_js, optionsPanel);
+    jp.setOneTouchExpandable(true);
+    jp.setResizeWeight(1);
+    this.setLayout( new BorderLayout() );
+    this.add(jp, BorderLayout.CENTER);
+
+    /** Setting up the initial color list **/
+    for(int i=0; i<m_defaultColors.length-1; i++)
+      m_colorList.addElement(m_defaultColors[i]);
+      
+    /** Initializing internal fields and components **/
+    m_selectedAttribs = m_attribList.getSelectedIndices();
+    m_plotsPanel = new Plot();
+    m_plotsPanel.setLayout(null);
+    m_js.getHorizontalScrollBar().setUnitIncrement( 10 );
+    m_js.getVerticalScrollBar().setUnitIncrement( 10 ); 
+    m_js.setViewportView( m_plotsPanel );
+    m_js.setColumnHeaderView( m_plotsPanel.getColHeader() );
+    m_js.setRowHeaderView( m_plotsPanel.getRowHeader() );
+    final JLabel lb = new JLabel(" Plot Matrix");
+    lb.setFont(f); lb.setForeground(fontColor);
+    lb.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
+    m_js.setCorner(JScrollPane.UPPER_LEFT_CORNER, lb);
+    m_cp.setInstances(m_data);
+    m_cp.setBorder(BorderFactory.createEmptyBorder(15,10,10,10));
+    m_cp.addRepaintNotify(m_plotsPanel);
+    //m_updateBt.doClick(); //not until setting up the instances
+  }
+
+
+
+  /** Initializes internal data fields, i.e. data values, type, missing and color cache arrays 
+   */
+  public void initInternalFields() {
+    Instances inst = m_data;
+    m_classIndex = m_classAttrib.getSelectedIndex();
+    m_selectedAttribs = m_attribList.getSelectedIndices();
+    double minC=0, maxC=0;
+
+    /** Resampling  **/
+    if(Double.parseDouble(m_resamplePercent.getText())<100) {
+        inst = new Instances(m_data, 0, m_data.numInstances());
+        inst.randomize( new Random(Integer.parseInt(m_rseed.getText())) );
+        
+        //System.err.println("gettingPercent: " +
+        //                   Math.round(
+        //                     Double.parseDouble(m_resamplePercent.getText())
+        //                     / 100D * m_data.numInstances()
+        //                             )
+        //                  );
+        
+        inst = new Instances(inst, 
+                 0,
+                 (int)Math.round(Double.parseDouble(m_resamplePercent.getText())
+                 / 100D*inst.numInstances())
+                            );
+    }
+    
+    m_points = new int[inst.numInstances()][m_selectedAttribs.length]; //changed
+    m_pointColors = new int[inst.numInstances()];
+    m_missing = new boolean[inst.numInstances()][m_selectedAttribs.length+1]; //changed
+    m_type = new int[2]; //[m_selectedAttribs.length]; //changed
+    jitterVals = new int[inst.numInstances()][2];
+      
+    /** Setting up the color list for non-numeric attribute as well as jittervals**/
+    if(!(inst.attribute(m_classIndex).isNumeric())) {
+	  
+      for(int i=m_colorList.size(); i<inst.attribute(m_classIndex).numValues()+1; i++) {
+	Color pc = m_defaultColors[i % 10];
+	int ija =  i / 10;
+	ija *= 2; 
+	for (int j=0;j<ija;j++) {
+	    pc = pc.darker();
+	}
+	m_colorList.addElement(pc);
+      }
+	  
+      for(int i=0; i<inst.numInstances(); i++) {
+	//set to black for missing class value which is last colour is default list
+	if(inst.instance(i).isMissing(m_classIndex))
+	  m_pointColors[i] =  m_defaultColors.length-1;
+	else
+	  m_pointColors[i] = (int) inst.instance(i).value(m_classIndex);
+
+	jitterVals[i][0] = rnd.nextInt(m_jitter.getValue()+1)
+	  - m_jitter.getValue()/2;
+	jitterVals[i][1] = rnd.nextInt(m_jitter.getValue()+1)
+	  - m_jitter.getValue()/2;
+	      
+      }
+    }
+    /** Setting up color variations for numeric attribute as well as jittervals **/
+    else {
+      for(int i=0; i<inst.numInstances(); i++) {
+	if(!(inst.instance(i).isMissing(m_classIndex))) {
+	  minC = maxC = inst.instance(i).value(m_classIndex);
+	  break;
+	}
+      }
+	  
+      for(int i=1; i<inst.numInstances(); i++) {
+	if(!(inst.instance(i).isMissing(m_classIndex))) {
+	  if(minC > inst.instance(i).value(m_classIndex))
+	    minC = inst.instance(i).value(m_classIndex);
+	  if(maxC < inst.instance(i).value(m_classIndex))
+	    maxC = inst.instance(i).value(m_classIndex);
+	}
+      }
+	  
+      for(int i=0; i<inst.numInstances(); i++) {
+	double r = (inst.instance(i).value(m_classIndex) - minC) / (maxC - minC);
+	r = (r * 240) + 15;
+	m_pointColors[i] = (int)r;
+
+	jitterVals[i][0] = rnd.nextInt(m_jitter.getValue()+1)
+	  - m_jitter.getValue()/2;
+	jitterVals[i][1] = rnd.nextInt(m_jitter.getValue()+1)
+	  - m_jitter.getValue()/2;
+      }
+    }
+
+    /** Creating local cache of the data values **/
+    double min[]=new double[m_selectedAttribs.length], max=0;  //changed
+    double ratio[] = new double[m_selectedAttribs.length];     //changed
+    double cellSize = m_plotSize.getValue(), temp1=0, temp2=0;
+
+    for(int j=0; j<m_selectedAttribs.length; j++) {
+      int i;
+      for(i=0; i<inst.numInstances(); i++) {
+	min[j] = max = 0;
+	if(!(inst.instance(i).isMissing(m_selectedAttribs[j]))) {
+	  min[j] = max = inst.instance(i).value(m_selectedAttribs[j]);
+	  break;
+	}
+      }
+      for( i=i; i<inst.numInstances(); i++ ) {
+	if(!(inst.instance(i).isMissing(m_selectedAttribs[j]))) {
+	  if(inst.instance(i).value(m_selectedAttribs[j]) < min[j])
+	    min[j] = inst.instance(i).value(m_selectedAttribs[j]);
+	  if(inst.instance(i).value(m_selectedAttribs[j]) > max)
+	    max = inst.instance(i).value(m_selectedAttribs[j]);
+	}
+      }
+      ratio[j] =  cellSize / (max - min[j]);
+    }
+
+    boolean classIndexProcessed=false;
+    for(int j=0; j<m_selectedAttribs.length; j++) {
+      if(inst.attribute(m_selectedAttribs[j]).isNominal() || inst.attribute(m_selectedAttribs[j]).isString()) {
+	  //m_type[0][j] = 1;  m_type[1][j] = inst.attribute(m_selectedAttribs[j]).numValues();
+
+	temp1 = cellSize/(double)inst.attribute(m_selectedAttribs[j]).numValues(); //m_type[1][j];
+	temp2 = temp1/2;
+	for(int i=0; i<inst.numInstances(); i++) {
+	  m_points[i][j] = (int) Math.round(temp2+temp1*inst.instance(i).value(m_selectedAttribs[j]));
+	  if(inst.instance(i).isMissing(m_selectedAttribs[j])) {
+	    m_missing[i][j] = true;    //represents missing value
+	    if(m_selectedAttribs[j]==m_classIndex) {
+		m_missing[i][m_missing[0].length-1] = true;
+		classIndexProcessed = true;
+	    }
+	  }
+	}
+      }
+      else {
+	  //m_type[0][j] = m_type[1][j] = 0;
+	for(int i=0; i<inst.numInstances(); i++) {
+	  m_points[i][j] = (int) Math.round((inst.instance(i).value(m_selectedAttribs[j])
+					     -min[j])*ratio[j]);	
+	  if(inst.instance(i).isMissing(m_selectedAttribs[j])) {
+	    m_missing[i][j] = true;    //represents missing value
+	    if(m_selectedAttribs[j]==m_classIndex) {
+		m_missing[i][m_missing[0].length-1] = true;
+		classIndexProcessed = true;
+	    }
+	  }
+	}
+      }
+    }
+
+    if(inst.attribute(m_classIndex).isNominal() || inst.attribute(m_classIndex).isString()) {
+	m_type[0] = 1; m_type[1] = inst.attribute(m_classIndex).numValues();
+    }
+    else
+	m_type[0] = m_type[1] = 0;
+
+    if(classIndexProcessed==false) {  //class Index has not been processed as class index is not among the selected attribs
+	for(int i=0; i<inst.numInstances(); i++) {
+	    if(inst.instance(i).isMissing(m_classIndex))
+		m_missing[i][m_missing[0].length-1] = true;
+	}
+    }
+
+    m_cp.setColours(m_colorList);
+  }
+
+  /** Sets up the UI's attributes lists 
+   */  
+  public void setupAttribLists() {
+    String [] tempAttribNames = new String[m_data.numAttributes()];
+    String type;
+
+    m_classAttrib.removeAllItems();
+    for(int i=0; i<tempAttribNames.length; i++) {
+      switch (m_data.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = " (Nom)";
+	break;
+      case Attribute.NUMERIC:
+	type = " (Num)";
+	break;
+      case Attribute.STRING:
+	type = " (Str)";
+	break;
+      case Attribute.DATE:
+	type = " (Dat)";
+	break;
+      case Attribute.RELATIONAL:
+	type = " (Rel)";
+	break;
+      default:
+	type = " (???)";
+      }
+      tempAttribNames[i] = new String("Colour: "+m_data.attribute(i).name()+" "+type);
+      m_classAttrib.addItem(tempAttribNames[i]);
+    }
+    if (m_data.classIndex() == -1)
+      m_classAttrib.setSelectedIndex(tempAttribNames.length - 1);
+    else
+      m_classAttrib.setSelectedIndex(m_data.classIndex());
+    m_attribList.setListData(tempAttribNames);
+    m_attribList.setSelectionInterval(0, tempAttribNames.length-1);
+  }
+
+  /** Calculates the percentage to resample 
+   */
+  public void setPercent() {
+    if(m_data.numInstances() > 700) {
+      double percnt = 500D/m_data.numInstances()*100;     
+      percnt *= 100;
+      percnt = Math.round(percnt);
+      percnt /= 100;
+
+      m_resamplePercent.setText(""+percnt);
+    }
+    else
+      m_resamplePercent.setText("100");
+  }
+
+
+  /** This method changes the Instances object of this class to a new one. It also does all the necessary
+      initializations for displaying the panel. This must be called before trying to display the panel.
+      @param newInst The new set of Instances
+  */
+  public void setInstances(Instances newInst) {
+
+    m_data = newInst;
+    setPercent();
+    setupAttribLists();
+    m_rseed.setText("1");
+    initInternalFields();
+    m_cp.setInstances(m_data);
+    m_cp.setCindex(m_classIndex);
+    m_updateBt.doClick();
+  }
+
+
+  /**
+     Main method for testing this class
+  */
+  public static void main(String [] args)  {
+    final JFrame jf = new JFrame("Weka Explorer: MatrixPanel");
+    final JButton setBt = new JButton("Set Instances");
+    Instances data = null;
+    try {
+      if(args.length==1)
+	data = new Instances( new BufferedReader( new FileReader(args[0])) ); 
+      else {
+	System.out.println("Usage: MatrixPanel <arff file>"); 
+	System.exit(-1);
+      }
+    } catch(IOException ex) { ex.printStackTrace(); System.exit(-1); }
+     
+    final MatrixPanel mp = new MatrixPanel();
+    mp.setInstances(data);
+    setBt.addActionListener( new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  JFileChooser chooser = new JFileChooser(new java.io.File(System.getProperty("user.dir")));
+	  ExtensionFileFilter myfilter = new ExtensionFileFilter("arff", "Arff data files");
+	  chooser.setFileFilter(myfilter);
+	  int returnVal = chooser.showOpenDialog(jf);
+		  
+	  if(returnVal == JFileChooser.APPROVE_OPTION)
+	    {
+	      try{
+		System.out.println("You chose to open this file: " +chooser.getSelectedFile().getName());
+		Instances in = new Instances ( new FileReader(chooser.getSelectedFile().getAbsolutePath()) );
+		mp.setInstances(in);
+	      }
+	      catch(Exception ex) { ex.printStackTrace(); }
+	    }
+	}
+      });
+    //System.out.println("Loaded: "+args[0]+"\nRelation: "+data.relationName()+"\nAttributes: "+data.numAttributes());
+    //System.out.println("The attributes are: ");
+    //for(int i=0; i<data.numAttributes(); i++)
+    //  System.out.println(data.attribute(i).name());
+
+    //RepaintManager.currentManager(jf.getRootPane()).setDoubleBufferingEnabled(false);
+    jf.getContentPane().setLayout( new BorderLayout() );
+    jf.getContentPane().add(mp, BorderLayout.CENTER);
+    jf.getContentPane().add(setBt, BorderLayout.SOUTH);
+    jf.getContentPane().setFont( new java.awt.Font( "SansSerif", java.awt.Font.PLAIN, 11) );
+    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    jf.setSize(800, 600);
+    jf.setVisible(true);
+    jf.repaint();
+  }
+
+  
+  /**
+     Internal class responsible for displaying the actual matrix
+     Requires the internal data fields of the parent class to be properly initialized
+     before being created
+  */
+  private class Plot
+    extends JPanel
+    implements MouseMotionListener, MouseListener {
+
+    /** for serialization */
+    private static final long serialVersionUID = -1721245738439420882L;    
+
+    int extpad=3, intpad=4, cellSize=100, cellRange=100, lastx=0, lasty=0, jitter=0;
+    java.awt.Rectangle r;
+    java.awt.FontMetrics fm;
+    int lastxpos, lastypos;
+    JPanel jPlColHeader, jPlRowHeader;
+
+
+    /** Constructor 
+     */
+    public Plot() {
+      super();
+      this.setToolTipText("blah");
+      this.addMouseMotionListener( this );
+      this.addMouseListener( this );
+      initialize();
+    }
+
+    /** Initializes the internal fields */
+    public void initialize() {
+      lastxpos = lastypos = 0;	  
+      cellRange = cellSize; cellSize = cellRange + 2*intpad;
+
+      jPlColHeader = new JPanel() {
+	private static final long serialVersionUID = -9098547751937467506L;
+        java.awt.Rectangle r;
+        public void paint(Graphics g) {
+          r = g.getClipBounds();
+          g.setColor(this.getBackground());
+          g.fillRect(r.x, r.y, r.width, r.height);
+          g.setFont( f );
+          fm = g.getFontMetrics();
+          int xpos = 0, ypos = 0, attribWidth=0;
+          
+          g.setColor(fontColor);
+          xpos = extpad;
+          ypos=extpad+fm.getHeight();
+          
+          for(int i=0; i<m_selectedAttribs.length; i++) {
+            if( xpos+cellSize < r.x)
+            { xpos += cellSize+extpad; continue; }
+            else if(xpos > r.x+r.width)
+            { break; }
+            else {
+              attribWidth = fm.stringWidth(m_data.attribute(m_selectedAttribs[i]).name());
+              g.drawString(m_data.attribute(m_selectedAttribs[i]).name(),
+              (attribWidth<cellSize) ? (xpos + (cellSize/2 - attribWidth/2)):xpos,
+              ypos);
+            }
+            xpos += cellSize+extpad;
+          }
+          fm = null; r=null;
+        }
+        
+        public Dimension getPreferredSize() {
+          fm = this.getFontMetrics(this.getFont());
+          return new Dimension( m_selectedAttribs.length*(cellSize+extpad),
+          2*extpad + fm.getHeight() );
+        }
+      };
+
+      jPlRowHeader = new JPanel() {
+	private static final long serialVersionUID = 8474957069309552844L;
+	
+        java.awt.Rectangle r;
+        public void paint(Graphics g) {
+          r = g.getClipBounds();
+          g.setColor(this.getBackground());
+          g.fillRect(r.x, r.y, r.width, r.height);
+          g.setFont( f );
+          fm = g.getFontMetrics();
+          int xpos = 0, ypos = 0;
+          
+          g.setColor(fontColor);
+          xpos = extpad;
+          ypos=extpad;
+          
+          for(int j=m_selectedAttribs.length-1; j>=0; j--) {
+            if( ypos+cellSize < r.y )
+            { ypos += cellSize+extpad;  continue; }
+            else if( ypos > r.y+r.height )
+              break;
+            else {
+              g.drawString(m_data.attribute(m_selectedAttribs[j]).name(), xpos+extpad, ypos+cellSize/2);
+            }
+            xpos = extpad;
+            ypos += cellSize+extpad;
+          }
+          r=null;
+        }
+        
+        public Dimension getPreferredSize() {
+          return new Dimension( 100+extpad,
+          m_selectedAttribs.length*(cellSize+extpad)
+          );
+        }
+      };
+      jPlColHeader.setFont(f);
+      jPlRowHeader.setFont(f);
+      this.setFont(f);
+    }      
+
+    public JPanel getRowHeader() {
+	  return jPlRowHeader;
+    }
+
+    public JPanel getColHeader() {
+	return jPlColHeader;
+    }
+
+    public void mouseMoved(MouseEvent e) {
+      Graphics g = this.getGraphics();
+      int xpos=extpad, ypos=extpad;
+
+      for(int j=m_selectedAttribs.length-1; j>=0; j--) {
+	for(int i=0; i<m_selectedAttribs.length; i++) {
+	  if(e.getX()>=xpos && e.getX()<=xpos+cellSize+extpad)
+	    if(e.getY()>=ypos && e.getY()<=ypos+cellSize+extpad) {
+	      if(xpos!=lastxpos || ypos!=lastypos) {
+		g.setColor( Color.red );
+		g.drawRect(xpos-1, ypos-1, cellSize+1, cellSize+1);
+		if(lastxpos!=0 && lastypos!=0) {
+		  g.setColor( this.getBackground().darker() );
+		  g.drawRect(lastxpos-1, lastypos-1, cellSize+1, cellSize+1); }
+		lastxpos = xpos; lastypos = ypos;
+	      }
+	      return;
+	    }
+	  xpos+=cellSize+extpad;
+	}
+	xpos=extpad;
+	ypos+=cellSize+extpad;
+      }
+      if(lastxpos!=0 && lastypos!=0) {
+	g.setColor( this.getBackground().darker() );
+	g.drawRect(lastxpos-1, lastypos-1, cellSize+1, cellSize+1); }
+      lastxpos=lastypos=0;
+    }
+
+    public void mouseDragged(MouseEvent e){ }
+
+    public void mouseClicked(MouseEvent e) {
+      int i=0, j=0, found=0;
+	  
+      int xpos=extpad, ypos=extpad;
+      for(j=m_selectedAttribs.length-1; j>=0; j--) {
+	for(i=0; i<m_selectedAttribs.length; i++) {
+	  if(e.getX()>=xpos && e.getX()<=xpos+cellSize+extpad)
+	    if(e.getY()>=ypos && e.getY()<=ypos+cellSize+extpad) {
+	      found=1; break;
+	    }
+	  xpos+=cellSize+extpad;
+	}
+	if(found==1)
+	  break;
+	xpos=extpad;
+	ypos+=cellSize+extpad;
+      }
+      if(found==0)
+	return;
+
+      JFrame jf = new JFrame("Weka Explorer: Visualizing "+m_data.relationName() );
+      VisualizePanel vp = new VisualizePanel();
+      try {
+	PlotData2D pd = new PlotData2D(m_data);
+	pd.setPlotName("Master Plot");
+	vp.setMasterPlot(pd);
+	//System.out.println("x: "+i+" y: "+j);
+	vp.setXIndex(m_selectedAttribs[i]);
+	vp.setYIndex(m_selectedAttribs[j]);
+	vp.m_ColourCombo.setSelectedIndex( m_classIndex );
+      }
+      catch(Exception ex) { ex.printStackTrace(); }
+      jf.getContentPane().add(vp);
+      jf.setSize(800,600);
+      jf.setVisible(true);
+    } 
+
+    public void mouseEntered(MouseEvent e){ }
+    public void mouseExited(MouseEvent e){ }
+    public void mousePressed(MouseEvent e){ }
+    public void mouseReleased(MouseEvent e){ }
+
+    /** sets the new jitter value for the plots
+     */
+    public void setJitter(int newjitter) {
+      jitter = newjitter;
+    }
+      
+    /** sets the new size for the plots
+     */
+    public void setCellSize(int newCellSize) {
+      cellSize = newCellSize;
+      initialize();
+    }
+
+    /** Returns the X and Y attributes of the plot the mouse is currently
+	on
+    */
+    public String getToolTipText(MouseEvent event) {
+      int xpos=extpad, ypos=extpad;
+	  
+      for(int j=m_selectedAttribs.length-1; j>=0; j--) {
+	for(int i=0; i<m_selectedAttribs.length; i++) {
+	  if(event.getX()>=xpos && event.getX()<=xpos+cellSize+extpad)
+	    if(event.getY()>=ypos && event.getY()<=ypos+cellSize+extpad)
+	      return("X: "+m_data.attribute(m_selectedAttribs[i]).name()+
+		     " Y: "+m_data.attribute(m_selectedAttribs[j]).name()+
+		     " (click to enlarge)");
+	  xpos+=cellSize+extpad;
+	}
+	xpos=extpad;
+	ypos+=cellSize+extpad;
+      }
+      return ("Matrix Panel");
+    }
+
+    /**  Paints a single Plot at xpos, ypos. and xattrib and yattrib on X and
+	 Y axes
+    */
+    public void paintGraph(Graphics g, int xattrib, int yattrib, int xpos, int ypos) {
+      int x, y;
+      g.setColor( this.getBackground().darker().darker() );
+      g.drawRect(xpos-1, ypos-1, cellSize+1, cellSize+1);
+      g.setColor(Color.white);
+      g.fillRect(xpos, ypos, cellSize, cellSize);
+      for(int i=0; i<m_points.length; i++) {
+        
+        if( !(m_missing[i][yattrib] || m_missing[i][xattrib]) ) {
+          
+          if(m_type[0]==0)
+            if(m_missing[i][m_missing[0].length-1])
+              g.setColor(m_defaultColors[m_defaultColors.length-1]);
+            else
+              g.setColor( new Color(m_pointColors[i],150,(255-m_pointColors[i])) );
+          else
+            g.setColor((Color)m_colorList.elementAt(m_pointColors[i]));
+          
+          if(m_points[i][xattrib]+jitterVals[i][0]<0 || m_points[i][xattrib]+jitterVals[i][0]>cellRange)
+            if(cellRange-m_points[i][yattrib]+jitterVals[i][1]<0 || cellRange-m_points[i][yattrib]+jitterVals[i][1]>cellRange) {
+              //both x and y out of range don't add jitter
+              x=intpad+m_points[i][xattrib];
+              y=intpad+(cellRange - m_points[i][yattrib]);
+            }
+            else {
+              //only x out of range
+              x=intpad+m_points[i][xattrib];
+              y=intpad+(cellRange - m_points[i][yattrib])+jitterVals[i][1];
+            }
+          else if(cellRange-m_points[i][yattrib]+jitterVals[i][1]<0 || cellRange-m_points[i][yattrib]+jitterVals[i][1]>cellRange) {
+            //only y out of range
+            x=intpad+m_points[i][xattrib]+jitterVals[i][0];
+            y=intpad+(cellRange - m_points[i][yattrib]);
+          }
+          else {
+            //none out of range
+            x=intpad+m_points[i][xattrib]+jitterVals[i][0];
+            y=intpad+(cellRange - m_points[i][yattrib])+jitterVals[i][1];
+          }
+          if(datapointSize==1)
+            g.drawLine(x+xpos, y+ypos, x+xpos, y+ypos);
+          else
+            g.drawOval(x+xpos-datapointSize/2, y+ypos-datapointSize/2, datapointSize, datapointSize);
+        }
+      }
+      g.setColor( fontColor );
+    }
+    
+
+    /**
+       Paints the matrix of plots in the current visible region
+    */
+    public void paintME(Graphics g) {
+      r = g.getClipBounds();
+      
+      g.setColor( this.getBackground() );
+      g.fillRect(r.x, r.y, r.width, r.height);
+      g.setColor( fontColor );
+      
+      int xpos = 0, ypos = 0;
+      
+      xpos = extpad;
+      ypos=extpad;
+      
+      
+      for(int j=m_selectedAttribs.length-1; j>=0; j--) {
+        if( ypos+cellSize < r.y )
+        { ypos += cellSize+extpad;  continue; }
+        else if( ypos > r.y+r.height )
+          break;
+        else {
+          for(int i=0; i<m_selectedAttribs.length; i++) {
+            if( xpos+cellSize < r.x) {
+              xpos += cellSize+extpad; continue; }
+            else if(xpos > r.x+r.width)
+              break;
+            else
+              paintGraph(g, i, j, xpos, ypos); //m_selectedAttribs[i], m_selectedAttribs[j], xpos, ypos);
+            xpos += cellSize+extpad;
+          }
+        }
+        xpos = extpad;
+        ypos += cellSize+extpad;
+      }
+    }
+      
+    /** paints this JPanel (PlotsPanel)
+     */
+    public void paintComponent(Graphics g) {
+      paintME(g);
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PNGWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PNGWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PNGWriter.java	(revision 29)
@@ -0,0 +1,159 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * PNGWriter.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.gui.visualize;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+import javax.swing.JComponent;
+
+/**
+ * This class takes any JComponent and outputs it to a PNG-file.
+ * Scaling is by default disabled, since we always take a screenshot.
+ * 
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5920 $
+ */
+public class PNGWriter
+  extends JComponentWriter {
+
+  /** the background color. */
+  protected Color m_Background;
+  
+  /**
+   * initializes the object.
+   */
+  public PNGWriter() {
+    super();
+  }
+
+  /**
+   * initializes the object with the given Component.
+   * 
+   * @param c		the component to print in the output format
+   */
+  public PNGWriter(JComponent c) {
+    super(c);
+  }
+
+  /**
+   * initializes the object with the given Component and filename.
+   * 
+   * @param c		the component to print in the output format
+   * @param f		the file to store the output in
+   */
+  public PNGWriter(JComponent c, File f) {
+    super(c, f);
+  }
+  
+  /**
+   * further initialization.
+   */
+  public void initialize() {
+    super.initialize();
+    
+    setScalingEnabled(false);
+  }
+
+  /**
+   * returns the name of the writer, to display in the FileChooser.
+   * must be overridden in the derived class.
+   * 
+   * @return 		the name of the writer
+   */
+  public String getDescription() {
+    return "PNG-Image";
+  }
+  
+  /**
+   * returns the extension (incl. ".") of the output format, to use in the
+   * FileChooser. 
+   * 
+   * @return 		the file extension
+   */
+  public String getExtension() {
+    return ".png";
+  }
+  
+  /**
+   * returns the current background color.
+   * 
+   * @return		the current background color
+   */
+  public Color getBackground() {
+    return m_Background;
+  }
+  
+  /**
+   * sets the background color to use in creating the PNG.
+   * 
+   * @param c 		the color to use for background
+   */
+  public void setBackground(Color c) {
+    m_Background = c;
+  }
+  
+  /**
+   * generates the actual output.
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  public void generateOutput() throws Exception {
+    BufferedImage	bi;
+    Graphics		g;
+
+    bi = new BufferedImage(getComponent().getWidth(), getComponent().getHeight(), BufferedImage.TYPE_INT_RGB);
+    g  = bi.getGraphics();
+    g.setPaintMode();
+    g.setColor(getBackground());
+    if (g instanceof Graphics2D)
+      ((Graphics2D) g).scale(getXScale(), getYScale());
+    g.fillRect(0, 0, getComponent().getWidth(), getComponent().getHeight());
+    getComponent().printAll(g);
+    ImageIO.write(bi, "png", getFile());
+  }
+  
+  /**
+   * for testing only.
+   * 
+   * @param args 	the commandline arguments
+   * @throws Exception 	if something goes wrong
+   */
+  public static void main(String[] args) throws Exception {
+    System.out.println("building TreeVisualizer...");
+    weka.gui.treevisualizer.TreeBuild builder = new weka.gui.treevisualizer.TreeBuild();
+    weka.gui.treevisualizer.NodePlace arrange = new weka.gui.treevisualizer.PlaceNode2();
+    weka.gui.treevisualizer.Node top = builder.create(new java.io.StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
+    weka.gui.treevisualizer.TreeVisualizer tv = new weka.gui.treevisualizer.TreeVisualizer(null, top, arrange);
+    tv.setSize(800 ,600);
+    
+    String filename = System.getProperty("java.io.tmpdir") + File.separator + "test.png";
+    System.out.println("outputting to '" + filename + "'...");
+    toOutput(new PNGWriter(), tv, new File(filename));
+
+    System.out.println("done!");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2D.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2D.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2D.java	(revision 29)
@@ -0,0 +1,1620 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Plot2D.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.Random;
+import java.util.Vector;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * This class plots datasets in two dimensions. It can also plot
+ * classifier errors and clusterer predictions.
+ * 
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5987 $
+ */
+public class Plot2D
+  extends JPanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = -1673162410856660442L;  
+
+  /* constants for shape types */
+  public static final int MAX_SHAPES = 5;
+  public static final int ERROR_SHAPE = 1000;
+  public static final int MISSING_SHAPE = 2000;
+  public static final int CONST_AUTOMATIC_SHAPE = -1;
+  public static final int X_SHAPE = 0;
+  public static final int PLUS_SHAPE = 1;
+  public static final int DIAMOND_SHAPE = 2;
+  public static final int TRIANGLEUP_SHAPE = 3;
+  public static final int TRIANGLEDOWN_SHAPE = 4;
+  public static final int DEFAULT_SHAPE_SIZE = 2;
+
+  /** Default colour for the axis */
+  protected Color m_axisColour = Color.green;
+
+  /** Default colour for the plot background */
+  protected Color m_backgroundColour = Color.black;
+
+  /** The plots to display */
+  protected FastVector m_plots = new FastVector();
+
+  /** The master plot */
+  protected PlotData2D m_masterPlot = null;
+
+  /** The name of the master plot */
+  protected String m_masterName = "master plot";
+
+  /** The instances to be plotted */
+  protected Instances m_plotInstances=null;
+
+  /** An optional "compainion" of the panel. If specified, this
+      class will get to do its thing with our graphics context
+      before we do any drawing. Eg. the visualize panel may need
+      to draw polygons etc. before we draw plot axis and data points */
+  protected Plot2DCompanion m_plotCompanion=null;
+
+  /** the class for displaying instance info. */
+  protected Class m_InstanceInfoFrameClass = null;
+  
+  /** For popping up text info on data points */
+  protected JFrame m_InstanceInfo = null;
+
+  /** The list of the colors used */
+  protected FastVector m_colorList;
+
+  /** default colours for colouring discrete class */
+  protected Color [] m_DefaultColors = {Color.blue,
+					Color.red,
+					Color.green,
+					Color.cyan,
+					Color.pink,
+					new Color(255, 0, 255),
+					Color.orange,
+					new Color(255, 0, 0),
+					new Color(0, 255, 0),
+					Color.white};
+
+  /** Indexes of the attributes to go on the x and y axis and the attribute
+      to use for colouring and the current shape for drawing */
+  protected int m_xIndex=0;
+  protected int m_yIndex=0;
+  protected int m_cIndex=0;
+  protected int m_sIndex=0;
+
+  /** Holds the min and max values of the x, y and colouring attributes 
+   over all plots */
+  protected double m_maxX;
+  protected double m_minX;
+  protected double m_maxY;
+  protected double m_minY;
+  protected double m_maxC;
+  protected double m_minC;
+    
+  /** Axis padding */
+  protected final int m_axisPad = 5;
+
+  /** Tick size */
+  protected final int m_tickSize = 5;
+
+  /**the offsets of the axes once label metrics are calculated */
+  protected int m_XaxisStart=0;
+  protected int m_YaxisStart=0;
+  protected int m_XaxisEnd=0;
+  protected int m_YaxisEnd=0;
+
+  /** if the user resizes the window, or the attributes selected for
+      the attributes change, then the lookup table for points needs
+      to be recalculated */
+  protected boolean m_plotResize = true;
+  
+  /** if the user changes attribute assigned to an axis */
+  protected boolean m_axisChanged = false;
+
+  /** An array used to show if a point is hidden or not.
+   * This is used for speeding up the drawing of the plot panel
+   * although I am not sure how much performance this grants over
+   * not having it.
+   */
+  protected int[][] m_drawnPoints;
+
+  /** Font for labels */
+  protected Font m_labelFont;
+  protected FontMetrics m_labelMetrics=null; 
+
+  /** the level of jitter */
+  protected int m_JitterVal=0;
+
+  /** random values for perterbing the data points */
+  protected Random m_JRand = new Random(0);
+
+  /** lookup table for plotted points */
+  protected double [][] m_pointLookup=null;
+
+  /** Constructor */
+  public Plot2D() {
+    super();
+    setProperties();
+    this.setBackground(m_backgroundColour);
+
+    m_drawnPoints = new int[this.getWidth()][this.getHeight()];
+
+    /** Set up some default colours */
+    m_colorList = new FastVector(10);
+    for (int noa = m_colorList.size(); noa < 10; noa++) {
+      Color pc = m_DefaultColors[noa % 10];
+      int ija =  noa / 10;
+      ija *= 2; 
+      for (int j=0;j<ija;j++) {
+	pc = pc.darker();
+      }
+      
+      m_colorList.addElement(pc);
+    }
+  }
+
+  /**
+   * Set the properties for Plot2D
+   */
+  private void setProperties() {
+    if (VisualizeUtils.VISUALIZE_PROPERTIES != null) {
+      String thisClass = this.getClass().getName();
+      String axisKey = thisClass+".axisColour";
+      String backgroundKey = thisClass+".backgroundColour";
+
+      String axisColour = VisualizeUtils.VISUALIZE_PROPERTIES.
+	getProperty(axisKey);
+      if (axisColour == null) {
+	/*
+	System.err.println("Warning: no configuration property found in "
+			   +VisualizeUtils.PROPERTY_FILE
+			   +" for "+axisKey);*/
+      } else {
+	//System.err.println("Setting axis colour to: "+axisColour);
+	m_axisColour = VisualizeUtils.processColour(axisColour, m_axisColour);
+      }
+
+      String backgroundColour = 
+	VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(backgroundKey);
+      if (backgroundColour == null) {
+	/*
+	System.err.println("Warning: no configuration property found in "
+			   +VisualizeUtils.PROPERTY_FILE
+			   +" for "+backgroundKey);*/
+      } else {
+	//System.err.println("Setting background colour to: "+backgroundColour);
+	m_backgroundColour = VisualizeUtils.processColour(backgroundColour, 
+							  m_backgroundColour);
+      }
+      
+      try {
+	m_InstanceInfoFrameClass = Class.forName(VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(thisClass + ".instanceInfoFrame", "weka.gui.visualize.InstanceInfoFrame"));
+      }
+      catch (Exception e) {
+	e.printStackTrace();
+	m_InstanceInfoFrameClass = InstanceInfoFrame.class;
+      }
+    }
+  }
+
+  /** 
+   * This will check the values of the screen points passed and make sure 
+   * that they land on the screen
+   * @param x1 The x coord.
+   * @param y1 The y coord.
+   */
+  private boolean checkPoints(double x1, double y1) {
+    if (x1 < 0 || x1 > this.getSize().width || y1 < 0 
+	|| y1 > this.getSize().height) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Set a companion class. This is a class that might want
+   * to render something on the plot before we do our thing. Eg,
+   * Malcolm's shape drawing stuff needs to happen before we plot
+   * axis and points
+   * @param p a companion class
+   */
+  public void setPlotCompanion(Plot2DCompanion p) {
+    m_plotCompanion = p;
+  }
+
+  /**
+   * Set level of jitter and repaint the plot using the new jitter value
+   * @param j the level of jitter
+   */
+  public void setJitter(int j) {
+    if (m_plotInstances.numAttributes() > 0 
+	&& m_plotInstances.numInstances() > 0) {
+      if (j >= 0) {
+	m_JitterVal = j;
+	m_JRand = new Random(m_JitterVal);
+	//      if (m_pointLookup != null) {
+	m_drawnPoints = new int[m_XaxisEnd - m_XaxisStart + 1]
+	  [m_YaxisEnd - m_YaxisStart + 1];
+	updatePturb();
+	//      }
+	this.repaint();
+      }
+    }
+  }
+
+  /**
+   * Set a list of colours to use when colouring points according
+   * to class values or cluster numbers
+   * @param cols the list of colours to use
+   */
+  public void setColours (FastVector cols) {
+    m_colorList = cols;
+  }
+
+  /**
+   * Set the index of the attribute to go on the x axis
+   * @param x the index of the attribute to use on the x axis
+   */
+  public void setXindex(int x) {
+    m_xIndex = x;
+    for (int i=0;i<m_plots.size();i++) {
+      ((PlotData2D)m_plots.elementAt(i)).setXindex(m_xIndex);
+    }
+    determineBounds();
+    if (m_JitterVal != 0) {
+      updatePturb();
+    }
+    m_axisChanged = true;
+    this.repaint();
+  }
+    
+  /**
+   * Set the index of the attribute to go on the y axis
+   * @param y the index of the attribute to use on the y axis
+   */
+  public void setYindex(int y) {
+    m_yIndex = y;
+    for (int i=0;i<m_plots.size();i++) {
+      ((PlotData2D)m_plots.elementAt(i)).setYindex(m_yIndex);
+    }
+    determineBounds();
+    if (m_JitterVal != 0) {
+      updatePturb();
+    }
+    m_axisChanged = true;
+    this.repaint();
+  }
+
+  /**
+   * Set the index of the attribute to use for colouring
+   * @param c the index of the attribute to use for colouring
+   */
+  public void setCindex(int c) {
+    m_cIndex = c;
+    for (int i=0;i<m_plots.size();i++) {
+      ((PlotData2D)m_plots.elementAt(i)).setCindex(m_cIndex);
+    }
+    determineBounds();
+    m_axisChanged = true;
+    this.repaint();
+  }
+
+  /**
+   * Return the list of plots
+   * @return the list of plots
+   */
+  public FastVector getPlots() {
+    return m_plots;
+  }
+
+  /**
+   * Get the master plot
+   * @return the master plot
+   */
+  public PlotData2D getMasterPlot() {
+    return m_masterPlot;
+  }
+
+  /** 
+   * Return the current max value of the attribute plotted on the x axis
+   * @return the max x value
+   */
+  public double getMaxX() {
+    return m_maxX;
+  }
+
+  /** 
+   * Return the current max value of the attribute plotted on the y axis
+   * @return the max y value
+   */
+  public double getMaxY() {
+    return m_maxY;
+  }
+
+  /** 
+   * Return the current min value of the attribute plotted on the x axis
+   * @return the min x value
+   */
+  public double getMinX() {
+    return m_minX;
+  }
+  
+  /** 
+   * Return the current min value of the attribute plotted on the y axis
+   * @return the min y value
+   */
+  public double getMinY() {
+    return m_minY;
+  }
+
+  /** 
+   * Return the current max value of the colouring attribute
+   * @return the max colour value
+   */
+  public double getMaxC() {
+    return m_maxC;
+  }
+  
+  /** 
+   * Return the current min value of the colouring attribute
+   * @return the min colour value
+   */
+  public double getMinC() {
+    return m_minC;
+  }
+    
+  /**
+   * Sets the master plot from a set of instances
+   * @param inst the instances
+   * @exception Exception if instances could not be set
+   */
+  public void setInstances(Instances inst) throws Exception {
+    //System.err.println("Setting Instances");
+    PlotData2D tempPlot = new PlotData2D(inst);
+    tempPlot.setPlotName("master plot");
+    setMasterPlot(tempPlot);
+  }
+
+  /**
+   * Set the master plot.
+   * @param master the plot to make the master plot
+   * @exception Exception if the plot could not be set.
+   */
+  public void setMasterPlot(PlotData2D master) throws Exception {
+    if (master.m_plotInstances == null) {
+      throw new Exception("No instances in plot data!");
+    }
+    removeAllPlots();
+    m_masterPlot = master;
+    m_plots.addElement(m_masterPlot);
+    m_plotInstances = m_masterPlot.m_plotInstances;
+    
+    m_xIndex=0;
+    m_yIndex=0;
+    m_cIndex=0;
+    
+    determineBounds();
+  }
+
+  /**
+   * Clears all plots
+   */
+  public void removeAllPlots() {
+    m_masterPlot = null;
+    m_plotInstances = null;
+    m_plots = new FastVector();
+    m_xIndex = 0; m_yIndex = 0; m_cIndex = 0;
+  }
+
+  /**
+   * Add a plot to the list of plots to display
+   * @param newPlot the new plot to add
+   * @exception Exception if the plot could not be added
+   */
+  public void addPlot(PlotData2D newPlot) throws Exception {
+    if (newPlot.m_plotInstances == null) {
+      throw new Exception("No instances in plot data!");
+    }
+
+    if (m_masterPlot != null) {
+      if (m_masterPlot.m_plotInstances.
+	  equalHeaders(newPlot.m_plotInstances) == false) {
+	throw new Exception("Plot2D :Plot data's instances are incompatable "
+			    +" with master plot");
+      }
+    } else {
+      m_masterPlot = newPlot;
+      m_plotInstances = m_masterPlot.m_plotInstances;
+    }
+    m_plots.addElement(newPlot);
+    setXindex(m_xIndex);
+    setYindex(m_yIndex);
+    setCindex(m_cIndex);
+  }
+
+  /**
+   * Set up fonts and font metrics
+   * @param gx the graphics context
+   */
+  private void setFonts(Graphics gx) {
+    if (m_labelMetrics == null) {
+      m_labelFont = new Font("Monospaced", Font.PLAIN, 12);
+      m_labelMetrics = gx.getFontMetrics(m_labelFont);
+    }
+    gx.setFont(m_labelFont);
+  }
+
+  /**
+   * Pops up a window displaying attribute information on any instances
+   * at a point+-plotting_point_size (in panel coordinates)
+   *
+   * @param x the x value of the clicked point
+   * @param y the y value of the clicked point
+   * @param newFrame true if instance info is to be displayed in a
+   * new frame.
+   */
+  public void searchPoints(int x, int y, final boolean newFrame) {
+    if (m_masterPlot.m_plotInstances != null) {
+      int longest=0;
+      for (int j=0;j<m_masterPlot.m_plotInstances.numAttributes();j++) {
+	if (m_masterPlot.m_plotInstances.attribute(j).name().length() > 
+	    longest) {
+	  longest = m_masterPlot.m_plotInstances.attribute(j).name().length();
+	}
+      }
+
+      StringBuffer insts = new StringBuffer(); 
+      Vector<Instances> data = new Vector<Instances>();
+      for (int jj=0;jj<m_plots.size();jj++) {
+	PlotData2D temp_plot = (PlotData2D)(m_plots.elementAt(jj));
+	data.add(new Instances(temp_plot.m_plotInstances, 0));
+	
+	for (int i=0;i<temp_plot.m_plotInstances.numInstances();i++) {
+	  if (temp_plot.m_pointLookup[i][0] != Double.NEGATIVE_INFINITY) {
+	    double px = temp_plot.m_pointLookup[i][0] + 
+	      temp_plot.m_pointLookup[i][2];
+	    double py = temp_plot.m_pointLookup[i][1] + 
+	      temp_plot.m_pointLookup[i][3];
+	    //	    double size = temp_plot.m_pointLookup[i][2];
+	    double size = temp_plot.m_shapeSize[i];
+	    if ((x >= px-size) && (x <= px+size) &&
+		(y >= py-size) && (y <= py+size)) {
+	      {
+		data.get(jj).add((Instance)temp_plot.m_plotInstances.instance(i).copy());
+		insts.append("\nPlot : "+temp_plot.m_plotName
+			     + "\nInstance: " + (i + 1 ) + "\n");
+		for (int j=0;j<temp_plot.m_plotInstances.numAttributes();j++) {
+		  for (int k = 0;k < 
+			 (longest-temp_plot.m_plotInstances.
+			  attribute(j).name().length()); k++) {
+		    insts.append(" ");
+		  }
+		  insts.append(temp_plot.m_plotInstances.attribute(j).name());  
+		  insts.append(" : ");
+		  
+		  if (temp_plot.m_plotInstances.instance(i).isMissing(j)) {
+		    insts.append("Missing");
+		  } else if (temp_plot.m_plotInstances.attribute(j).
+			     isNominal()) {
+		    insts.append(temp_plot.m_plotInstances.
+				 attribute(j).
+				 value((int)temp_plot.m_plotInstances.
+				       instance(i).value(j)));
+		  } else {
+		    insts.append(temp_plot.m_plotInstances.
+				 instance(i).value(j));
+		  }
+		  insts.append("\n");
+		}
+	      }
+	    }
+	  }
+	}
+      }
+      // remove datasets that contain no instances
+      int i = 0;
+      while (data.size() > i) {
+	if (data.get(i).numInstances() == 0)
+	  data.remove(i);
+	else
+	  i++;
+      }
+
+      if (insts.length() > 0) {
+	// Pop up a new frame
+	if (newFrame || m_InstanceInfo == null) {
+	  try {
+	    final JFrame jf = (JFrame) m_InstanceInfoFrameClass.newInstance();
+	    ((InstanceInfo) jf).setInfoText(insts.toString());
+	    ((InstanceInfo) jf).setInfoData(data);
+	    final JFrame testf = m_InstanceInfo;
+	    jf.addWindowListener(new WindowAdapter() {
+	      public void windowClosing(WindowEvent e) {
+		if (!newFrame || testf == null) {
+		  m_InstanceInfo = null;
+		}
+		jf.dispose();
+	      }
+	    });
+	    jf.setVisible(true);
+	    if (m_InstanceInfo == null)
+	      m_InstanceInfo = jf;
+	  }
+	  catch (Exception e) {
+	    e.printStackTrace();
+	  }
+	}
+	else {
+	  // Overwrite info in existing frame	  
+	  ((InstanceInfo) m_InstanceInfo).setInfoText(insts.toString());
+	  ((InstanceInfo) m_InstanceInfo).setInfoData(data);
+	}
+      }
+    }
+  }
+  
+
+  /**
+   * Determine the min and max values for axis and colouring attributes
+   */
+  public void determineBounds() {
+    double value,min,max;
+    
+    // find maximums minimums over all plots
+    m_minX = ((PlotData2D)m_plots.elementAt(0)).m_minX;
+    m_maxX = ((PlotData2D)m_plots.elementAt(0)).m_maxX;
+    m_minY = ((PlotData2D)m_plots.elementAt(0)).m_minY;
+    m_maxY = ((PlotData2D)m_plots.elementAt(0)).m_maxY;
+    m_minC = ((PlotData2D)m_plots.elementAt(0)).m_minC;
+    m_maxC = ((PlotData2D)m_plots.elementAt(0)).m_maxC;
+    for (int i=1;i<m_plots.size();i++) {
+      value = ((PlotData2D)m_plots.elementAt(i)).m_minX;
+      if (value < m_minX) {
+	m_minX = value;
+      }
+      value = ((PlotData2D)m_plots.elementAt(i)).m_maxX;
+      if (value > m_maxX) {
+	m_maxX = value;
+      }
+      value = ((PlotData2D)m_plots.elementAt(i)).m_minY;
+      if (value < m_minY) {
+	m_minY= value;
+      }
+      value = ((PlotData2D)m_plots.elementAt(i)).m_maxY;
+      if (value > m_maxY) {
+	m_maxY = value;
+      }
+      value = ((PlotData2D)m_plots.elementAt(i)).m_minC;
+      if (value < m_minC) {
+	m_minC = value;
+      }
+      value = ((PlotData2D)m_plots.elementAt(i)).m_maxC;
+      if (value > m_maxC) {
+	m_maxC = value;
+      }
+    }
+
+    fillLookup();
+    this.repaint();
+  }
+
+    
+  //to convert screen coords to attrib values
+  // note that I use a double to avoid accuracy 
+  //headaches with ints
+  /**
+   * convert a Panel x coordinate to a raw x value.
+   * @param scx The Panel x coordinate
+   * @return A raw x value.
+   */
+  public double convertToAttribX(double scx) {
+    double temp = m_XaxisEnd - m_XaxisStart;
+    double temp2 = ((scx - m_XaxisStart) * (m_maxX - m_minX)) / temp;
+      
+    temp2 = temp2 + m_minX;
+      
+    return temp2;
+  }
+    
+  /**
+   * convert a Panel y coordinate to a raw y value.
+   * @param scy The Panel y coordinate
+   * @return A raw y value.
+   */
+  public double convertToAttribY(double scy) {
+    double temp = m_YaxisEnd - m_YaxisStart;
+    double temp2 = ((scy - m_YaxisEnd) * (m_maxY - m_minY)) / temp;
+      
+    temp2 = -(temp2 - m_minY);
+      
+    return temp2;
+  }
+  //////
+    
+  /**
+   * returns a value by which an x value can be peturbed. Makes sure
+   * that the x value+pturb stays within the plot bounds
+   * @param xvalP the x coordinate to be peturbed
+   * @param xj a random number to use in calculating a peturb value
+   * @return a peturb value
+   */
+  int pturbX(double xvalP, double xj) {
+    int xpturb = 0;
+    if (m_JitterVal > 0) {
+      xpturb = (int)((double)m_JitterVal * (xj / 2.0));
+      if (((xvalP + xpturb) < m_XaxisStart) || 
+	  ((xvalP + xpturb) > m_XaxisEnd)) {
+	xpturb *= -1;
+      }
+    }
+    return xpturb;
+  }
+
+  /**
+   * Convert an raw x value to Panel x coordinate.
+   * @param xval the raw x value
+   * @return an x value for plotting in the panel.
+   */
+  public double convertToPanelX(double xval) {
+    double temp = (xval - m_minX)/(m_maxX - m_minX);
+    double temp2 = temp * (m_XaxisEnd - m_XaxisStart);
+      
+    temp2 = temp2 + m_XaxisStart;
+	
+    return temp2;
+  }
+
+  /**
+   * returns a value by which a y value can be peturbed. Makes sure
+   * that the y value+pturb stays within the plot bounds
+   * @param yvalP the y coordinate to be peturbed
+   * @param yj a random number to use in calculating a peturb value
+   * @return a peturb value
+   */
+  int pturbY(double yvalP, double yj) {
+    int ypturb = 0;
+    if (m_JitterVal > 0) {
+      ypturb = (int)((double)m_JitterVal * (yj / 2.0));
+      if (((yvalP + ypturb) < m_YaxisStart) || 
+	  ((yvalP + ypturb) > m_YaxisEnd)) {
+	ypturb *= -1;
+      }
+    }
+    return ypturb;
+  }
+
+  /**
+   * Convert an raw y value to Panel y coordinate.
+   * @param yval the raw y value
+   * @return an y value for plotting in the panel.
+   */
+  public double convertToPanelY(double yval) {
+    double temp = (yval - m_minY)/(m_maxY - m_minY);
+    double temp2 = temp * (m_YaxisEnd - m_YaxisStart);
+      
+    temp2 = m_YaxisEnd - temp2;
+
+    return temp2;
+  }
+
+  /**
+   * Draws an X.
+   * @param gx the graphics context
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the shape
+   */
+  private static void drawX(Graphics gx, double x, double y, int size) {
+     gx.drawLine((int)(x-size),(int)(y-size),
+		  (int)(x+size),(int)(y+size));
+     
+      gx.drawLine((int)(x+size),(int)(y-size),
+		  (int)(x-size),(int)(y+size));     
+  }
+
+  /**
+   * Draws a plus.
+   * @param gx the graphics context
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the shape
+   */
+  private static void drawPlus(Graphics gx, double x, double y, int size) {
+     gx.drawLine((int)(x-size),(int)(y),
+		  (int)(x+size),(int)(y));
+     
+      gx.drawLine((int)(x),(int)(y-size),
+		  (int)(x),(int)(y+size));     
+  }
+
+  /**
+   * Draws a diamond.
+   * @param gx the graphics context
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the shape
+   */
+  private static void drawDiamond(Graphics gx, double x, double y, int size) {
+    gx.drawLine((int)(x-size),(int)(y),
+		(int)(x),(int)(y-size));
+    
+    gx.drawLine((int)(x),(int)(y-size),
+		  (int)(x+size),(int)(y));
+
+    gx.drawLine((int)(x+size),(int)(y),
+		  (int)(x),(int)(y+size));
+
+     gx.drawLine((int)(x),(int)(y+size),
+		  (int)(x-size),(int)(y));
+  }
+
+  /**
+   * Draws an triangle (point at top).
+   * @param gx the graphics context
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the shape
+   */
+  private static void drawTriangleUp(Graphics gx, double x, 
+				     double y, int size) {
+    gx.drawLine((int)(x),(int)(y-size),
+		(int)(x-size),(int)(y+size));
+
+    gx.drawLine((int)(x-size),(int)(y+size),
+		(int)(x+size),(int)(y+size));
+
+    gx.drawLine((int)(x+size),(int)(y+size),
+		(int)(x),(int)(y-size));
+
+  }
+
+  /**
+   * Draws an triangle (point at bottom).
+   * @param gx the graphics context
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the shape
+   */
+  private static void drawTriangleDown(Graphics gx, double x, 
+				       double y, int size) {
+    gx.drawLine((int)(x),(int)(y+size),
+		(int)(x-size),(int)(y-size));
+
+    gx.drawLine((int)(x-size),(int)(y-size),
+		(int)(x+size),(int)(y-size));
+
+    gx.drawLine((int)(x+size),(int)(y-size),
+		(int)(x),(int)(y+size));
+
+  }
+
+  /**
+   * Draws a data point at a given set of panel coordinates at a given
+   * size and connects a line to the previous point.
+   * @param x the x coord
+   * @param y the y coord
+   * @param xprev the x coord of the previous point
+   * @param yprev the y coord of the previous point
+   * @param size the size of the point
+   * @param shape the shape of the data point (square is reserved for nominal
+   * error data points). Shapes: 0=x, 1=plus, 2=diamond, 3=triangle(up),
+   * 4 = triangle (down).
+   * @param gx the graphics context
+   */
+  protected static void drawDataPoint(double x, 
+			       double y,
+			       double xprev,
+			       double yprev,
+			       int size,
+			       int shape,
+			       Graphics gx) {
+
+    drawDataPoint(x,y,size,shape,gx);
+
+    // connect a line to the previous point
+    gx.drawLine((int)x, (int)y, (int)xprev, (int)yprev);
+  }
+
+  /**
+   * Draws a data point at a given set of panel coordinates at a given
+   * size.
+   * @param x the x coord
+   * @param y the y coord
+   * @param size the size of the point
+   * @param shape the shape of the data point (square is reserved for nominal
+   * error data points). Shapes: 0=x, 1=plus, 2=diamond, 3=triangle(up),
+   * 4 = triangle (down).
+   * @param gx the graphics context
+   */
+  protected static void drawDataPoint(double x, 
+				      double y,
+				      int size,
+				      int shape,
+				      Graphics gx) {
+
+    Font lf = new Font("Monospaced", Font.PLAIN, 12);
+    FontMetrics fm = gx.getFontMetrics(lf);
+
+    if (size == 0) {
+      size = 1;
+    }
+
+    if (shape != ERROR_SHAPE && shape != MISSING_SHAPE) {
+      shape = shape % 5;
+    }
+
+    switch (shape) {
+    case X_SHAPE:
+      drawX(gx, x, y, size);
+      break;      
+    case PLUS_SHAPE:
+      drawPlus(gx, x, y, size);
+      break;
+    case DIAMOND_SHAPE:
+      drawDiamond(gx, x, y, size);
+      break;
+    case TRIANGLEUP_SHAPE:
+      drawTriangleUp(gx, x, y, size);
+      break;
+    case TRIANGLEDOWN_SHAPE:
+      drawTriangleDown(gx, x, y, size);
+      break;
+    case ERROR_SHAPE: // draws the nominal error shape 
+      gx.drawRect((int)(x-size),(int)(y-size),(size*2),(size*2));
+      break;
+    case MISSING_SHAPE:
+      int hf = fm.getAscent();
+      int width = fm.stringWidth("M");
+      gx.drawString("M",(int)(x-(width / 2)), (int)(y+(hf / 2)));
+      break;
+    }
+  }
+
+  /**
+   * Updates the perturbed values for the plots when the jitter value is
+   * changed
+   */
+  private void updatePturb() {
+    double xj=0;
+    double yj=0;
+    for (int j=0;j<m_plots.size();j++) {
+      PlotData2D temp_plot = (PlotData2D)(m_plots.elementAt(j));
+      for (int i=0;i<temp_plot.m_plotInstances.numInstances();i++) {
+	if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) ||
+	    temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) {
+	} else {
+	  if (m_JitterVal > 0) {
+	    xj = m_JRand.nextGaussian();
+	    yj = m_JRand.nextGaussian();
+	  }
+	  temp_plot.m_pointLookup[i][2] = 
+	    pturbX(temp_plot.m_pointLookup[i][0],xj);
+	  temp_plot.m_pointLookup[i][3] = 
+	    pturbY(temp_plot.m_pointLookup[i][1],yj);
+	}
+      }
+    }
+  }
+
+  /**
+   * Fills the lookup caches for the plots. Also calculates errors for
+   * numeric predictions (if any) in plots
+   */
+  private void fillLookup() {
+
+    for (int j=0;j<m_plots.size();j++) {
+      PlotData2D temp_plot = (PlotData2D)(m_plots.elementAt(j));
+
+      if (temp_plot.m_plotInstances.numInstances() > 0 &&
+	  temp_plot.m_plotInstances.numAttributes() > 0) {
+	for (int i=0;i<temp_plot.m_plotInstances.numInstances();i++) {
+	  if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) ||
+	      temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) {
+	    temp_plot.m_pointLookup[i][0] = Double.NEGATIVE_INFINITY;
+	    temp_plot.m_pointLookup[i][1] = Double.NEGATIVE_INFINITY;
+	  } else {
+	    double x = convertToPanelX(temp_plot.m_plotInstances.
+				       instance(i).value(m_xIndex));
+	    double y = convertToPanelY(temp_plot.m_plotInstances.
+				       instance(i).value(m_yIndex));
+	    temp_plot.m_pointLookup[i][0] = x;
+	    temp_plot.m_pointLookup[i][1] = y;
+	  }
+	}
+      }
+    }
+  }
+    
+  /**
+   * Draws the data points and predictions (if provided).
+   * @param gx the graphics context
+   */
+  private void paintData(Graphics gx) {
+
+    for (int j=0;j<m_plots.size();j++) {
+      PlotData2D temp_plot = (PlotData2D)(m_plots.elementAt(j));
+
+      for (int i=0;i<temp_plot.m_plotInstances.numInstances();i++) {
+	if (temp_plot.m_plotInstances.instance(i).isMissing(m_xIndex) ||
+	    temp_plot.m_plotInstances.instance(i).isMissing(m_yIndex)) {
+	} else {
+	  double x = (temp_plot.m_pointLookup[i][0] + 
+		      temp_plot.m_pointLookup[i][2]);
+	  double y = (temp_plot.m_pointLookup[i][1] + 
+		      temp_plot.m_pointLookup[i][3]);
+
+	  double prevx = 0;
+	  double prevy = 0;
+	  if (i > 0) {
+	    prevx = (temp_plot.m_pointLookup[i - 1][0] + 
+			temp_plot.m_pointLookup[i - 1][2]);
+	    prevy = (temp_plot.m_pointLookup[i - 1][1] + 
+			temp_plot.m_pointLookup[i - 1][3]);
+	  }
+
+	  int x_range = (int)x - m_XaxisStart;
+	  int y_range = (int)y - m_YaxisStart;
+
+	  if (x_range >= 0 && y_range >= 0) {
+	    if (m_drawnPoints[x_range][y_range] == i 
+		|| m_drawnPoints[x_range][y_range] == 0
+		|| temp_plot.m_shapeSize[i] == temp_plot.m_alwaysDisplayPointsOfThisSize 
+		|| temp_plot.m_displayAllPoints == true) {
+	      m_drawnPoints[x_range][y_range] = i;
+	      if (temp_plot.m_plotInstances.attribute(m_cIndex).isNominal()) {
+		if (temp_plot.m_plotInstances.attribute(m_cIndex).numValues() >
+		    m_colorList.size() && 
+		    !temp_plot.m_useCustomColour) {
+		  extendColourMap(temp_plot.m_plotInstances.
+				  attribute(m_cIndex).numValues());
+		}
+
+		Color ci;
+		if (temp_plot.m_plotInstances.instance(i).
+		    isMissing(m_cIndex)) {
+		  ci = Color.gray;
+		} else {
+		  int ind = (int)temp_plot.m_plotInstances.instance(i).
+		    value(m_cIndex);
+		  ci = (Color)m_colorList.elementAt(ind);
+		}
+
+		if (!temp_plot.m_useCustomColour) {
+		  gx.setColor(ci);	    
+		} else {
+		  gx.setColor(temp_plot.m_customColour);
+		}
+
+		if (temp_plot.m_plotInstances.instance(i).
+		    isMissing(m_cIndex)) {
+		  if (temp_plot.m_connectPoints[i] == true) {
+		    drawDataPoint(x,y,prevx,prevy,temp_plot.m_shapeSize[i],
+				  MISSING_SHAPE,gx);
+		  } else {
+		    drawDataPoint(x,y,temp_plot.m_shapeSize[i],
+				  MISSING_SHAPE,gx);
+		  }
+		} else {
+		  if (temp_plot.m_shapeType[i] == CONST_AUTOMATIC_SHAPE) {
+		    if (temp_plot.m_connectPoints[i] == true) {
+		      drawDataPoint(x,y,prevx,prevy,
+				    temp_plot.m_shapeSize[i],j,gx);
+		    } else {
+		      drawDataPoint(x,y,temp_plot.m_shapeSize[i],j,gx);
+		    }
+		  } else {
+		    if (temp_plot.m_connectPoints[i] == true) {
+		       drawDataPoint(x,y,prevx,prevy,temp_plot.m_shapeSize[i],
+				     temp_plot.m_shapeType[i],gx);
+		    } else {
+		      drawDataPoint(x,y,temp_plot.m_shapeSize[i],
+				    temp_plot.m_shapeType[i],gx);
+		    }
+		  }
+		}
+	      } else {
+		double r;
+		Color ci = null;
+		if (!temp_plot.m_plotInstances.instance(i).
+		    isMissing(m_cIndex)) {
+		  r = (temp_plot.m_plotInstances.instance(i).
+		       value(m_cIndex) - m_minC) / (m_maxC - m_minC);
+		  r = (r * 240) + 15;
+		  ci = new Color((int)r,150,(int)(255-r));
+		} else {
+		  ci = Color.gray;
+		}
+		if (!temp_plot.m_useCustomColour) {
+		  gx.setColor(ci);
+		} else {
+		  gx.setColor(temp_plot.m_customColour);
+		}
+		if (temp_plot.m_plotInstances.instance(i).
+		    isMissing(m_cIndex)) {
+		  if (temp_plot.m_connectPoints[i] == true) {
+		    drawDataPoint(x,y,prevx,prevy,temp_plot.m_shapeSize[i],
+				  MISSING_SHAPE,gx);
+		  } else {
+		    drawDataPoint(x,y,temp_plot.m_shapeSize[i],
+				  MISSING_SHAPE,gx);
+		  }
+		} else {
+		  if (temp_plot.m_shapeType[i] == CONST_AUTOMATIC_SHAPE) {
+		    if (temp_plot.m_connectPoints[i] == true) {
+		      drawDataPoint(x,y,prevx,prevy,
+				    temp_plot.m_shapeSize[i],j,gx);
+		    } else {
+		      drawDataPoint(x,y,temp_plot.m_shapeSize[i],j,gx);
+		    }
+		  } else {
+		    if (temp_plot.m_connectPoints[i] == true) {
+		      drawDataPoint(x,y,prevx,prevy,temp_plot.m_shapeSize[i],
+				    temp_plot.m_shapeType[i],gx);
+		    } else {
+		      drawDataPoint(x,y,temp_plot.m_shapeSize[i],
+				    temp_plot.m_shapeType[i],gx);
+		    }
+		  }
+		}
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  /*
+  public void determineAxisPositions(Graphics gx) {
+    setFonts(gx);
+    int mxs = m_XaxisStart;
+    int mxe = m_XaxisEnd;
+    int mys = m_YaxisStart;
+    int mye = m_YaxisEnd;
+    m_axisChanged = false;
+
+    int h = this.getHeight();
+    int w = this.getWidth();
+    int hf = m_labelMetrics.getAscent();
+    int mswx=0;
+    int mswy=0;
+
+    //      determineBounds();
+    int fieldWidthX = (int)((Math.log(m_maxX)/Math.log(10)))+1;
+    int precisionX = 1;
+    if ((Math.abs(m_maxX-m_minX) < 1) && ((m_maxY-m_minX) != 0)) {
+      precisionX = (int)Math.abs(((Math.log(Math.abs(m_maxX-m_minX)) / 
+				   Math.log(10))))+1;
+    }
+    String maxStringX = Utils.doubleToString(m_maxX,
+					     fieldWidthX+1+precisionX
+					     ,precisionX);
+    mswx = m_labelMetrics.stringWidth(maxStringX);
+    int fieldWidthY = (int)((Math.log(m_maxY)/Math.log(10)))+1;
+    int precisionY = 1;
+    if (Math.abs((m_maxY-m_minY)) < 1 && ((m_maxY-m_minY) != 0)) {
+      precisionY = (int)Math.abs(((Math.log(Math.abs(m_maxY-m_minY)) / 
+				   Math.log(10))))+1;
+    }
+    String maxStringY = Utils.doubleToString(m_maxY,
+					     fieldWidthY+1+precisionY
+					     ,precisionY);
+    String minStringY = Utils.doubleToString(m_minY,
+					     fieldWidthY+1+precisionY
+					     ,precisionY);
+
+    if (m_plotInstances.attribute(m_yIndex).isNumeric()) {
+      mswy = (m_labelMetrics.stringWidth(maxStringY) > 
+	      m_labelMetrics.stringWidth(minStringY))
+	? m_labelMetrics.stringWidth(maxStringY)
+	: m_labelMetrics.stringWidth(minStringY);
+    } else {
+      mswy = m_labelMetrics.stringWidth("MM");
+    }
+
+    m_YaxisStart = m_axisPad;
+    m_XaxisStart = 0+m_axisPad+m_tickSize+mswy;
+
+    m_XaxisEnd = w-m_axisPad-(mswx/2);
+      
+    m_YaxisEnd = h-m_axisPad-(2 * hf)-m_tickSize;
+    } */
+
+  /**
+   * Draws the axis and a spectrum if the colouring attribute is numeric
+   * @param gx the graphics context
+   */
+  private void paintAxis(Graphics gx) {
+    setFonts(gx);
+    int mxs = m_XaxisStart;
+    int mxe = m_XaxisEnd;
+    int mys = m_YaxisStart;
+    int mye = m_YaxisEnd;
+    m_plotResize = false;
+
+    int h = this.getHeight();
+    int w = this.getWidth();
+    int hf = m_labelMetrics.getAscent();
+    int mswx=0;
+    int mswy=0;
+
+    //      determineBounds();
+    int precisionXmax = 1;
+    int precisionXmin = 1;
+    int precisionXmid = 1;
+    /*if ((Math.abs(m_maxX-m_minX) < 1) && ((m_maxY-m_minX) != 0)) {
+      precisionX = (int)Math.abs(((Math.log(Math.abs(m_maxX-m_minX)) / 
+				   Math.log(10))))+1;
+				   } */
+    int whole = (int)Math.abs(m_maxX);
+    double decimal = Math.abs(m_maxX) - whole;
+    int nondecimal;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    
+    precisionXmax = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_maxX)) / 
+				      Math.log(10))))+2
+      : 1;
+    if (precisionXmax > VisualizeUtils.MAX_PRECISION) {
+      precisionXmax = 1;
+    }
+
+    String maxStringX = Utils.doubleToString(m_maxX,
+					     nondecimal+1+precisionXmax
+					     ,precisionXmax);
+
+    whole = (int)Math.abs(m_minX);
+    decimal = Math.abs(m_minX) - whole;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    precisionXmin = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_minX)) / 
+				      Math.log(10))))+2
+      : 1;
+    if (precisionXmin > VisualizeUtils.MAX_PRECISION) {
+      precisionXmin = 1;
+    }
+   
+    String minStringX = Utils.doubleToString(m_minX,
+					     nondecimal+1+precisionXmin,
+					     precisionXmin);
+
+    mswx = m_labelMetrics.stringWidth(maxStringX);
+
+    int precisionYmax = 1;
+    int precisionYmin = 1;
+    int precisionYmid = 1;
+    whole = (int)Math.abs(m_maxY);
+    decimal = Math.abs(m_maxY) - whole;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    precisionYmax = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_maxY)) / 
+				      Math.log(10))))+2
+      : 1;
+    if (precisionYmax > VisualizeUtils.MAX_PRECISION) {
+      precisionYmax = 1;
+    }
+    
+    String maxStringY = Utils.doubleToString(m_maxY,
+					     nondecimal+1+precisionYmax
+					     ,precisionYmax);
+
+
+    whole = (int)Math.abs(m_minY);
+    decimal = Math.abs(m_minY) - whole;
+    nondecimal = (whole > 0) 
+      ? (int)(Math.log(whole) / Math.log(10))
+      : 1;
+    precisionYmin = (decimal > 0) 
+      ? (int)Math.abs(((Math.log(Math.abs(m_minY)) / 
+				      Math.log(10))))+2
+      : 1;
+    if (precisionYmin > VisualizeUtils.MAX_PRECISION) {
+      precisionYmin = 1;
+    }
+   
+    String minStringY = Utils.doubleToString(m_minY,
+					     nondecimal+1+precisionYmin
+					     ,precisionYmin);
+
+    if (m_plotInstances.attribute(m_yIndex).isNumeric()) {
+      mswy = (m_labelMetrics.stringWidth(maxStringY) > 
+	      m_labelMetrics.stringWidth(minStringY))
+	? m_labelMetrics.stringWidth(maxStringY)
+	: m_labelMetrics.stringWidth(minStringY);
+      mswy += m_labelMetrics.stringWidth("M");
+    } else {
+      mswy = m_labelMetrics.stringWidth("MM");
+    }
+
+    m_YaxisStart = m_axisPad;
+    m_XaxisStart = 0+m_axisPad+m_tickSize+mswy;
+
+    m_XaxisEnd = w-m_axisPad-(mswx/2);
+      
+    m_YaxisEnd = h-m_axisPad-(2 * hf)-m_tickSize;
+
+    // draw axis
+    gx.setColor(m_axisColour);
+    if (m_plotInstances.attribute(m_xIndex).isNumeric()) {
+      if (w > (2 * mswx)) {
+	
+	gx.drawString(maxStringX, 
+		      m_XaxisEnd-(mswx/2),
+		      m_YaxisEnd+hf+m_tickSize);
+	
+	mswx = m_labelMetrics.stringWidth(minStringX);
+	gx.drawString(minStringX,
+		      (m_XaxisStart-(mswx/2)),
+		      m_YaxisEnd+hf+m_tickSize);
+
+	// draw the middle value
+	if (w > (3 * mswx) && 
+	    (m_plotInstances.attribute(m_xIndex).isNumeric())) {
+	  double mid = m_minX+((m_maxX-m_minX)/2.0);
+	   whole = (int)Math.abs(mid);
+	   decimal = Math.abs(mid) - whole;
+	   nondecimal = (whole > 0) 
+	     ? (int)(Math.log(whole) / Math.log(10))
+	     : 1;
+	   precisionXmid = (decimal > 0) 
+	     ? (int)Math.abs(((Math.log(Math.abs(mid)) / 
+			       Math.log(10))))+2
+	     : 1;
+	   if (precisionXmid > VisualizeUtils.MAX_PRECISION) {
+	     precisionXmid = 1;
+	   }
+	  
+	  String maxString = Utils.doubleToString(mid,
+						  nondecimal+1+precisionXmid,
+						  precisionXmid);
+	  int sw = m_labelMetrics.stringWidth(maxString);
+	  double mx = m_XaxisStart+((double)(m_XaxisEnd-m_XaxisStart)/2.0);
+	  gx.drawString(maxString,
+			(int)(mx-(((double)sw)/2.0)),
+			m_YaxisEnd+hf+m_tickSize);
+	  gx.drawLine((int)mx,m_YaxisEnd,(int)mx,m_YaxisEnd+m_tickSize);
+	}
+      }
+    } else {
+      int numValues = m_plotInstances.attribute(m_xIndex).numValues();
+      int div = (numValues % 2 > 0) ? (numValues/2)+1 : (numValues/2);
+      int maxXStringWidth = (m_XaxisEnd - m_XaxisStart) / numValues;
+
+      for (int i=0;i<numValues;i++) {
+	String val = m_plotInstances.attribute(m_xIndex).value(i);
+	int sw = m_labelMetrics.stringWidth(val);
+	int rm;
+	// truncate string if necessary
+	if (sw > maxXStringWidth) {
+	  int incr = (sw / val.length());
+	  rm = (sw - maxXStringWidth) / incr;
+	  if (rm == 0) {
+	    rm = 1;
+	  }
+	  val = val.substring(0,val.length()-rm);
+	  sw = m_labelMetrics.stringWidth(val);
+	}
+	if (i == 0) {
+	  gx.drawString(val,(int)convertToPanelX(i),
+			m_YaxisEnd+hf+m_tickSize);
+	} else if (i == numValues -1) {
+	  if ((i % 2) == 0) {
+	    gx.drawString(val,
+			  m_XaxisEnd-sw,
+			  m_YaxisEnd+hf+m_tickSize);
+	  } else {
+	    gx.drawString(val,
+			  m_XaxisEnd-sw,
+			  m_YaxisEnd+(2*hf)+m_tickSize);
+	  }
+	} else {
+	  if ((i % 2) == 0) {
+	    gx.drawString(val,
+			  (int)convertToPanelX(i)-(sw/2),
+			  m_YaxisEnd+hf+m_tickSize);
+	  } else {
+	    gx.drawString(val,
+			  (int)convertToPanelX(i)-(sw/2),
+			  m_YaxisEnd+(2*hf)+m_tickSize);
+	  }
+	}
+	gx.drawLine((int)convertToPanelX(i),
+		    m_YaxisEnd,
+		    (int)convertToPanelX(i),
+		    m_YaxisEnd+m_tickSize);
+      }
+	
+    }
+
+    // draw the y axis
+    if (m_plotInstances.attribute(m_yIndex).isNumeric()) {
+      if (h > (2 * hf)) {
+	gx.drawString(maxStringY, 
+		      m_XaxisStart-mswy-m_tickSize,
+		      m_YaxisStart+(hf));
+
+	gx.drawString(minStringY,
+		      (m_XaxisStart-mswy-m_tickSize),
+		      m_YaxisEnd);
+
+	// draw the middle value
+	if (w > (3 * hf) && 
+	    (m_plotInstances.attribute(m_yIndex).isNumeric())) {
+	  double mid = m_minY+((m_maxY-m_minY)/2.0);
+	  whole = (int)Math.abs(mid);
+	  decimal = Math.abs(mid) - whole;
+	  nondecimal = (whole > 0) 
+	    ? (int)(Math.log(whole) / Math.log(10))
+	    : 1;
+	  precisionYmid = (decimal > 0) 
+	    ? (int)Math.abs(((Math.log(Math.abs(mid)) / 
+			      Math.log(10))))+2
+	    : 1;
+	  if (precisionYmid > VisualizeUtils.MAX_PRECISION) {
+	    precisionYmid = 1;
+	  }
+	 
+	  String maxString = Utils.doubleToString(mid,
+						  nondecimal+1+precisionYmid,
+						  precisionYmid);
+	  int sw = m_labelMetrics.stringWidth(maxString);
+	  double mx = m_YaxisStart+((double)(m_YaxisEnd-m_YaxisStart)/2.0);
+	  gx.drawString(maxString,
+			m_XaxisStart-sw-m_tickSize-1,
+			(int)(mx+(((double)hf)/2.0)));
+	  gx.drawLine(m_XaxisStart-m_tickSize,(int)mx,m_XaxisStart,(int)mx);
+	}
+      }
+    } else {
+      int numValues = m_plotInstances.attribute(m_yIndex).numValues();
+      int div = ((numValues % 2) == 0) ? (numValues/2) : (numValues/2+1);
+      int maxYStringHeight = (m_YaxisEnd - m_XaxisStart) / div;
+      int sw = m_labelMetrics.stringWidth("M");
+      for (int i=0;i<numValues;i++) {
+	// can we at least print 2 characters
+	if (maxYStringHeight >= (2*hf)) {
+	  String val = m_plotInstances.attribute(m_yIndex).value(i);
+	  int numPrint = ((maxYStringHeight/hf) > val.length()) ?
+	    val.length() :
+	    (maxYStringHeight/hf);
+	    
+	  for (int j=0;j<numPrint;j++) {
+	    String ll = val.substring(j,j+1);
+	    if (val.charAt(j) == '_' || val.charAt(j) == '-') {
+	      ll = "|";
+	    }
+	    if (i == 0) {
+	      gx.drawString(ll,m_XaxisStart-sw-m_tickSize-1,
+			    (int)convertToPanelY(i)
+			    -((numPrint-1)*hf)
+			    +(j*hf)
+			    +(hf/2));
+	    } else if (i == (numValues-1)) {
+	      if ((i % 2) == 0) {
+		gx.drawString(ll,m_XaxisStart-sw-m_tickSize-1,
+			      (int)convertToPanelY(i)
+			      +(j*hf)
+			      +(hf/2));
+	      } else {
+		gx.drawString(ll,m_XaxisStart-(2*sw)-m_tickSize-1,
+			      (int)convertToPanelY(i)
+			      +(j*hf)
+			      +(hf/2));
+	      }
+	    } else {
+	      if ((i % 2) == 0) {
+		gx.drawString(ll,m_XaxisStart-sw-m_tickSize-1,
+			      (int)convertToPanelY(i)
+			      -(((numPrint-1)*hf)/2)
+			      +(j*hf)
+			      +(hf/2));
+	      } else {
+		gx.drawString(ll,m_XaxisStart-(2*sw)-m_tickSize-1,
+			      (int)convertToPanelY(i)
+			      -(((numPrint-1)*hf)/2)
+			      +(j*hf)
+			      +(hf/2));
+	      }
+	    }
+	  }
+	}
+	gx.drawLine(m_XaxisStart-m_tickSize,
+		    (int)convertToPanelY(i),
+		    m_XaxisStart,
+		    (int)convertToPanelY(i));
+      }
+    }
+
+    gx.drawLine(m_XaxisStart,
+		m_YaxisStart,
+		m_XaxisStart,
+		m_YaxisEnd);
+    gx.drawLine(m_XaxisStart,
+		m_YaxisEnd,
+		m_XaxisEnd,
+		m_YaxisEnd);
+
+    if (m_XaxisStart != mxs || m_XaxisEnd != mxe ||
+	m_YaxisStart != mys || m_YaxisEnd != mye) {
+      m_plotResize = true;
+    }
+  }
+
+  /**
+   * Add more colours to the colour map
+   */
+  private void extendColourMap(int highest) {
+    //System.err.println("Extending colour map");
+    for (int i = m_colorList.size(); i < highest; i++) {
+      Color pc = m_DefaultColors[i % 10];
+      int ija =  i / 10;
+      ija *= 2; 
+      for (int j=0;j<ija;j++) {
+	pc = pc.brighter();
+      }
+      
+      m_colorList.addElement(pc);
+    }
+  }
+
+  /**
+   * Renders this component
+   * @param gx the graphics context
+   */
+  public void paintComponent(Graphics gx) {
+    super.paintComponent(gx);
+    if (m_plotInstances != null 
+	&& m_plotInstances.numInstances() > 0
+	&& m_plotInstances.numAttributes() > 0) {
+      if (m_plotCompanion != null) {
+	m_plotCompanion.prePlot(gx);
+      }
+
+      m_JRand = new Random(m_JitterVal);
+      paintAxis(gx);
+      if (m_axisChanged || m_plotResize) {
+	int x_range = m_XaxisEnd - m_XaxisStart;
+	int y_range = m_YaxisEnd - m_YaxisStart;
+	if (x_range < 10) {
+	  x_range = 10;
+	}
+	if (y_range < 10) {
+	  y_range = 10;
+	}
+
+	m_drawnPoints = new int[x_range + 1][y_range + 1];
+	fillLookup();
+	m_plotResize = false;
+	m_axisChanged = false;
+      }
+      paintData(gx);
+    }
+  }
+  
+  protected static Color checkAgainstBackground(Color c, Color background) {
+    if (background == null) {
+      return c;
+    }
+    
+    if (c.equals(background)) {
+      int red = c.getRed();
+      int blue = c.getBlue();
+      int green = c.getGreen();
+      red += (red < 128) ? (255 - red) / 2 : -(red / 2);
+      blue += (blue < 128) ? (blue - red) / 2 : -(blue / 2);
+      green += (green< 128) ? (255 - green) / 2 : -(green / 2);
+      c = new Color(red, green, blue);
+    }
+    return c;
+  }
+
+  /**
+   * Main method for testing this class
+   * @param args arguments
+   */
+  public static void main(String [] args) {
+    try {
+      if (args.length < 1) {
+	System.err.println("Usage : weka.gui.visualize.Plot2D "
+			   +"<dataset> [<dataset> <dataset>...]");
+	System.exit(1);
+      }
+
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Explorer: Visualize");
+      jf.setSize(500,400);
+      jf.getContentPane().setLayout(new BorderLayout());
+      final Plot2D p2 = new Plot2D();
+      jf.getContentPane().add(p2, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	  public void windowClosing(java.awt.event.WindowEvent e) {
+	    jf.dispose();
+	    System.exit(0);
+	  }
+	});
+      
+      p2.addMouseListener(new MouseAdapter() {
+	  public void mouseClicked(MouseEvent e) {
+	    if ((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
+		InputEvent.BUTTON1_MASK) {
+	      p2.searchPoints(e.getX(), e.getY(), false);
+	    } else {
+	      p2.searchPoints(e.getX(), e.getY(), true);
+	    }
+	  }
+	});
+
+      jf.setVisible(true);
+      if (args.length >= 1) {
+	for (int j = 0; j < args.length; j++) {
+	  System.err.println("Loading instances from " + args[j]);
+	  java.io.Reader r = new java.io.BufferedReader(
+			     new java.io.FileReader(args[j]));
+	  Instances i = new Instances(r);
+	  i.setClassIndex(i.numAttributes()-1);
+	  PlotData2D pd1 = new PlotData2D(i);
+
+	  if (j == 0) {
+	    pd1.setPlotName("Master plot");
+	    p2.setMasterPlot(pd1);
+	    p2.setXindex(2);
+	    p2.setYindex(3);
+	    p2.setCindex(i.classIndex());
+	  } else {
+	    pd1.setPlotName("Plot "+(j+1));
+	    pd1.m_useCustomColour = true;
+	    pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; 
+	    p2.addPlot(pd1);
+	  }
+	}
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2DCompanion.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2DCompanion.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/Plot2DCompanion.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    Plot2DCompanion.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+import java.awt.Graphics;
+
+/**
+ * Interface for classes that need to draw to the Plot2D panel *before*
+ * Plot2D renders anything (eg. VisualizePanel may need to draw polygons
+ * etc.)
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface Plot2DCompanion {
+
+  /**
+   * Something to be drawn before the plot itself
+   * @param gx the graphics context to render to
+   */
+  void prePlot(Graphics gx);
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PlotData2D.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PlotData2D.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PlotData2D.java	(revision 29)
@@ -0,0 +1,450 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PlotData2D.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.Add;
+
+import java.awt.Color;
+
+/**
+ * This class is a container for plottable data. Instances form the
+ * primary data. An optional array of classifier/clusterer predictions
+ * (associated 1 for 1 with the instances) can also be provided.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 5741 $
+ */
+public class PlotData2D {
+
+  /** The instances */
+  protected Instances m_plotInstances = null;
+
+  /** The name of this plot */
+  protected String m_plotName = "new plot";
+  
+  /**
+   * The name of this plot (possibly in html) suitable for using in a 
+   * tool tip text.
+   */
+  protected String m_plotNameHTML = null;
+
+  /** Custom colour for this plot */
+  public boolean m_useCustomColour = false;
+  public Color m_customColour = null;
+
+  /** Display all points (ie. those that map to the same display coords) */
+  public boolean m_displayAllPoints = false;
+  
+  /**
+   *  If the shape size of a point equals this size then always plot
+   * it (i.e. even if it is obscured by other points)
+   */
+  public int m_alwaysDisplayPointsOfThisSize = -1;
+
+  /** Panel coordinate cache for data points */
+  protected double [][] m_pointLookup;
+
+  /** Additional optional information to control the size of points.
+      The default is shape size 2  */
+  protected int [] m_shapeSize;
+
+  /** Additional optional information to control the point shape for this
+      data. Default is to allow automatic assigning of point shape on the
+      basis of plot number */
+  protected int [] m_shapeType;
+
+  /**
+   * Additional optional information to control the drawing of lines
+   * between consecutive points. Setting an entry in the array to true
+   * indicates that the associated point should have a line connecting
+   * it to the previous point.
+   */
+  protected boolean [] m_connectPoints;
+
+  /** These are used to determine bounds */
+
+  /** The x index */
+  private int m_xIndex;
+
+  /** The y index */
+  private int m_yIndex;
+
+  /** The colouring index */
+  private int m_cIndex;
+
+  /** Holds the min and max values of the x, y and colouring attributes 
+   for this plot */
+  protected double m_maxX;
+  protected double m_minX;
+  protected double m_maxY;
+  protected double m_minY;
+  protected double m_maxC;
+  protected double m_minC;
+
+  /**
+   * Construct a new PlotData2D using the supplied instances
+   * @param insts the instances to use.
+   */
+  public PlotData2D(Instances insts) {   
+    m_plotInstances = insts;
+    m_xIndex = m_yIndex = m_cIndex = 0;
+    m_pointLookup = new double [m_plotInstances.numInstances()][4];
+    m_shapeSize = new int [m_plotInstances.numInstances()];
+    m_shapeType = new int [m_plotInstances.numInstances()];
+    m_connectPoints = new boolean [m_plotInstances.numInstances()];
+    for (int i = 0; i < m_plotInstances.numInstances(); i++) {
+      m_shapeSize[i] = Plot2D.DEFAULT_SHAPE_SIZE; //default shape size
+      m_shapeType[i] = Plot2D.CONST_AUTOMATIC_SHAPE; // default (automatic shape assignment)
+    }
+    determineBounds();
+  }
+
+  /**
+   * Adds an instance number attribute to the plottable instances,
+   */
+  public void addInstanceNumberAttribute() {
+    String originalRelationName = m_plotInstances.relationName();
+    int originalClassIndex = m_plotInstances.classIndex();
+    try {
+      Add addF = new Add();
+      addF.setAttributeName("Instance_number");
+      addF.setAttributeIndex("first");
+      addF.setInputFormat(m_plotInstances);
+      m_plotInstances = Filter.useFilter(m_plotInstances, addF);
+      m_plotInstances.setClassIndex(originalClassIndex + 1);
+      for (int i = 0; i < m_plotInstances.numInstances(); i++) {
+	m_plotInstances.instance(i).setValue(0,(double)i);
+      }
+      m_plotInstances.setRelationName(originalRelationName);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Returns the instances for this plot
+   * @return the instances for this plot
+   */
+  public Instances getPlotInstances() {
+    return new Instances(m_plotInstances);
+  }
+
+  /**
+   * Set the name of this plot
+   * @param name the name for this plot
+   */
+  public void setPlotName(String name) {
+    m_plotName = name;
+  }
+
+  /**
+   * Get the name of this plot
+   * @return the name of this plot
+   */
+  public String getPlotName() {
+    return m_plotName;
+  }
+  
+  /**
+   * Set the plot name for use in a tool tip text.
+   * 
+   * @param name the name of the plot for potential use in a tool
+   * tip text (may use html).
+   */
+  public void setPlotNameHTML(String name) {
+    m_plotNameHTML = name;
+  }
+  
+  /**
+   * Get the name of the plot for use in a tool tip text.
+   * Defaults to the standard plot name if it hasn't been set.
+   * 
+   * @return the name of this plot (possibly in html) for use
+   * in a tool tip text.
+   */
+  public String getPlotNameHTML() {
+    if (m_plotNameHTML == null) {
+      return m_plotName;
+    }
+    
+    return m_plotNameHTML;
+  }
+
+  /**
+   * Set the shape type for the plot data
+   * @param st an array of integers corresponding to shape types (see
+   * constants defined in Plot2D)
+   */
+  public void setShapeType(int [] st) throws Exception {
+    m_shapeType = st;
+    if (m_shapeType.length != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: Shape type array must have the same "
+			  +"number of entries as number of data points!");
+    }
+    for (int i = 0; i < st.length; i++) {
+      if (m_shapeType[i] == Plot2D.ERROR_SHAPE) {
+	m_shapeSize[i] = 3;
+      }
+    }
+  }
+
+  /**
+   * Set the shape type for the plot data
+   * @param st a FastVector of integers corresponding to shape types (see
+   * constants defined in Plot2D)
+   */
+  public void setShapeType(FastVector st) throws Exception {
+    if (st.size() != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: Shape type vector must have the same "
+			  +"number of entries as number of data points!");
+    }
+    m_shapeType = new int [st.size()];
+    for (int i = 0; i < st.size(); i++) {
+      m_shapeType[i] = ((Integer)st.elementAt(i)).intValue();
+      if (m_shapeType[i] == Plot2D.ERROR_SHAPE) {
+	m_shapeSize[i] = 3;
+      }
+    }
+  }
+
+  /**
+   * Set the shape sizes for the plot data
+   * @param ss an array of integers specifying the size of data points
+   */
+  public void setShapeSize(int [] ss) throws Exception {
+    m_shapeSize = ss;
+    if (m_shapeType.length != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: Shape size array must have the same "
+			  +"number of entries as number of data points!");
+    }
+  }
+  
+  /**
+   * Set the shape sizes for the plot data
+   * @param ss a FastVector of integers specifying the size of data points
+   */
+  public void setShapeSize(FastVector ss) throws Exception {
+    if (ss.size() != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: Shape size vector must have the same "
+			  +"number of entries as number of data points!");
+    }
+    //System.err.println("Setting connect points ");
+    m_shapeSize = new int [ss.size()];
+    for (int i = 0; i < ss.size(); i++) {
+      m_shapeSize[i] = ((Integer)ss.elementAt(i)).intValue();
+    }
+  }
+
+  /**
+   * Set whether consecutive points should be connected by lines
+   * @param cp an array of boolean specifying which points should be
+   * connected to their preceeding neighbour.
+   */
+  public void setConnectPoints(boolean [] cp) throws Exception {
+    m_connectPoints = cp;
+    if (m_connectPoints.length != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: connect points array must have the "
+			  +"same number of entries as number of data points!");
+    }
+    m_connectPoints[0] = false;
+  }
+  
+  /**
+   * Set whether consecutive points should be connected by lines
+   * @param cp a FastVector of boolean specifying which points should be
+   * connected to their preceeding neighbour.
+   */
+  public void setConnectPoints(FastVector cp) throws Exception {
+    if (cp.size() != m_plotInstances.numInstances()) {
+      throw new Exception("PlotData2D: connect points array must have the "
+			  +"same number of entries as number of data points!");
+    }
+    //System.err.println("Setting connect points ");
+    m_shapeSize = new int [cp.size()];
+    for (int i = 0; i < cp.size(); i++) {
+      m_connectPoints[i] = ((Boolean)cp.elementAt(i)).booleanValue();
+    }
+    m_connectPoints[0] = false;
+  }
+
+  /**
+   * Set a custom colour to use for this plot. This overides any
+   * data index to use for colouring. If null, then will revert back
+   * to the default (no custom colouring).
+   * @param c a custom colour to use for this plot or null (default---no
+   * colouring).
+   */
+  public void setCustomColour(Color c) {
+    m_customColour = c;
+    if (c != null) {
+      m_useCustomColour = true;
+    } else {
+      m_useCustomColour = false;
+    }
+  }
+
+  /**
+   * Set the x index of the data.
+   * @param x the x index
+   */
+  public void setXindex(int x) {
+    m_xIndex = x;
+    determineBounds();
+  }
+
+  /**
+   * Set the y index of the data
+   * @param y the y index
+   */
+  public void setYindex(int y) {
+    m_yIndex = y;
+    determineBounds();
+  }
+
+  /**
+   * Set the colouring index of the data
+   * @param c the colouring index
+   */
+  public void setCindex(int c) {
+    m_cIndex = c;
+    determineBounds();
+  }
+
+  /**
+   * Get the currently set x index of the data
+   * @return the current x index
+   */
+  public int getXindex() {
+    return m_xIndex;
+  }
+
+  /**
+   * Get the currently set y index of the data
+   * @return the current y index
+   */
+  public int getYindex() {
+    return m_yIndex;
+  }
+
+  /**
+   * Get the currently set colouring index of the data
+   * @return the current colouring index
+   */
+  public int getCindex() {
+    return m_cIndex;
+  }
+
+  /**
+   * Determine bounds for the current x,y and colouring indexes
+   */
+  private void determineBounds() {
+     double value,min,max;
+    
+    if (m_plotInstances != null && 
+	m_plotInstances.numAttributes() > 0 &&
+	m_plotInstances.numInstances() > 0) {
+      // x bounds
+      min=Double.POSITIVE_INFINITY;
+      max=Double.NEGATIVE_INFINITY;
+      if (m_plotInstances.attribute(m_xIndex).isNominal()) {
+	m_minX = 0;
+	m_maxX = m_plotInstances.attribute(m_xIndex).numValues()-1;
+      } else {
+	for (int i=0;i<m_plotInstances.numInstances();i++) {
+	  if (!m_plotInstances.instance(i).isMissing(m_xIndex)) {
+	    value = m_plotInstances.instance(i).value(m_xIndex);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+	
+	// handle case where all values are missing
+	if (min == Double.POSITIVE_INFINITY) min = max = 0.0;
+	
+	m_minX = min; m_maxX = max;
+	if (min == max) {
+	  m_maxX += 0.05;
+	  m_minX -= 0.05;
+	}
+      }
+
+      // y bounds
+      min=Double.POSITIVE_INFINITY;
+      max=Double.NEGATIVE_INFINITY;
+      if (m_plotInstances.attribute(m_yIndex).isNominal()) {
+	m_minY = 0;
+	m_maxY = m_plotInstances.attribute(m_yIndex).numValues()-1;
+      } else {
+	for (int i=0;i<m_plotInstances.numInstances();i++) {
+	  if (!m_plotInstances.instance(i).isMissing(m_yIndex)) {
+	    value = m_plotInstances.instance(i).value(m_yIndex);
+	    if (value < min) {
+	      min = value;
+	    }
+	    if (value > max) {
+	      max = value;
+	    }
+	  }
+	}
+	
+	// handle case where all values are missing
+	if (min == Double.POSITIVE_INFINITY) min = max = 0.0;
+
+	m_minY = min; m_maxY = max;
+	if (min == max) {
+	  m_maxY += 0.05;
+	  m_minY -= 0.05;
+	}
+      }
+      
+      // colour bounds
+      min=Double.POSITIVE_INFINITY;
+      max=Double.NEGATIVE_INFINITY;
+
+      for (int i=0;i<m_plotInstances.numInstances();i++) {
+	if (!m_plotInstances.instance(i).isMissing(m_cIndex)) {
+	  value = m_plotInstances.instance(i).value(m_cIndex);
+	  if (value < min) {
+	    min = value;
+	  }
+	  if (value > max) {
+	    max = value;
+	  }
+	}
+      }
+
+      // handle case where all values are missing
+      if (min == Double.POSITIVE_INFINITY) min = max = 0.0;
+
+      m_minC = min; m_maxC = max;
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptGraphics.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptGraphics.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptGraphics.java	(revision 29)
@@ -0,0 +1,926 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    PostscriptGraphics.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import java.awt.image.renderable.*;
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.font.*;
+import java.io.*;
+import java.util.*;
+import java.awt.image.*;
+import java.text.*;
+
+
+/** 
+ * The PostscriptGraphics class extends the Graphics2D class to 
+ * produce an encapsulated postscript file rather than on-screen display.
+ * <p>
+ * Currently only a small (but useful) subset of Graphics methods have been 
+ * implemented. 
+ * To handle the ability to Clone a Graphics object, the graphics state of the 
+ * eps is set from the graphics state of the local PostscriptGraphics before output.
+ * To use, create a PostscriptGraphics object, and pass it to the PaintComponent
+ * method of a JComponent.
+ * <p>
+ * If necessary additional font replacements can be inserted, since some fonts 
+ * might be displayed incorrectly.
+ *
+ * @see #addPSFontReplacement(String, String)
+ * @see #m_PSFontReplacement
+ * @author Dale Fletcher (dale@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ */
+
+public class PostscriptGraphics extends Graphics2D {
+  
+  /**
+   * This inner class is used to maintain the graphics states of the PostScript
+   * file and graphics context.
+   */
+  private class GraphicsState {
+    /** The current pen color */
+    protected Color m_currentColor;
+    
+    /** The current Font */
+    protected Font m_currentFont;
+    
+    /** The current Stroke (not yet used) */ 
+    protected Stroke m_currentStroke;
+    
+    /** x,y Translation */
+    protected int m_xOffset;
+    protected int m_yOffset;
+    
+    /** the scale factors */
+    protected double m_xScale;
+    protected double m_yScale;
+    
+    /**
+     * Create a new GraphicsState with default values.
+     */
+    GraphicsState(){
+      m_currentColor  = Color.white;
+      m_currentFont   = new Font ("Courier", Font.PLAIN, 11);
+      m_currentStroke = new BasicStroke();
+      m_xOffset       = 0;
+      m_yOffset       = 0;
+      m_xScale        = 1.0;
+      m_yScale        = 1.0;
+    }
+    
+    /**
+     * Create a new cloned GraphicsState
+     *
+     * @param copy The GraphicsState to clone
+     */
+    GraphicsState(GraphicsState copy){
+      m_currentColor  = copy.m_currentColor;
+      m_currentFont   = copy.m_currentFont;
+      m_currentStroke = copy.m_currentStroke;
+      m_xOffset       = copy.m_xOffset;
+      m_yOffset       = copy.m_yOffset;
+      m_xScale        = copy.m_xScale;
+      m_yScale        = copy.m_yScale;
+    }
+    
+    /* Stroke Methods */
+    protected Stroke getStroke(){
+      return m_currentStroke;
+    }
+    
+    protected void setStroke(Stroke s){
+      m_currentStroke = s;
+    }
+    
+    /* Font Methods */
+    protected Font getFont(){
+      return m_currentFont;
+    }
+    
+    protected void setFont(Font f){
+      m_currentFont = f;
+    }
+    
+    /* Color Methods */
+    protected Color getColor(){
+      return m_currentColor;
+    }
+    
+    protected void setColor(Color c){
+      m_currentColor = c;
+    }
+    
+    /* Translation methods */
+    protected void setXOffset(int xo){
+      m_xOffset = xo;
+    }
+    
+    protected void setYOffset(int yo){
+      m_yOffset = yo;
+    }
+    
+    protected int getXOffset(){
+      return m_xOffset;
+    }
+    
+    protected int getYOffset(){
+      return m_yOffset;
+    }
+    
+    protected void setXScale(double x){
+      m_xScale = x;
+    }
+    
+    protected void setYScale(double y){
+      m_yScale = y;
+    }
+    
+    protected double getXScale(){
+      return m_xScale;
+    }
+    
+    protected double getYScale(){
+      return m_yScale;
+    }
+  }
+  
+  /** The bounding box of the output */
+  protected Rectangle m_extent;
+  
+  /** The output file */
+  protected PrintStream m_printstream;
+  
+  /** The current global PostScript graphics state for all cloned objects */
+  protected GraphicsState m_psGraphicsState;
+  
+  /** The current local graphics state for this PostscriptGraphics object */
+  protected GraphicsState m_localGraphicsState;
+  
+  /** whether to print some debug information */
+  protected final static boolean DEBUG = false;
+  
+  /** the font replacement */
+  protected static Hashtable m_PSFontReplacement;
+  
+  /** output if we're in debug mode */
+  static {
+    if (DEBUG)
+      System.err.println(PostscriptGraphics.class.getName() + ": DEBUG ON");
+    
+    // get font replacements
+    m_PSFontReplacement = new Hashtable();
+    m_PSFontReplacement.put("SansSerif.plain", "Helvetica.plain");   // SansSerif.plain is displayed as Courier in GV???
+    m_PSFontReplacement.put("Dialog.plain", "Helvetica.plain");  // dialog is a Sans Serif font, but GV displays it as Courier???
+    m_PSFontReplacement.put("Microsoft Sans Serif", "Helvetica.plain");  // MS Sans Serif is a Sans Serif font (hence the name!), but GV displays it as Courier???
+    m_PSFontReplacement.put("MicrosoftSansSerif", "Helvetica.plain");  // MS Sans Serif is a Sans Serif font (hence the name!), but GV displays it as Courier???
+  }
+  
+  /** 
+   * Constructor
+   * Creates a new PostscriptGraphics object, given dimensions and 
+   * output file.
+   *
+   * @param width The width of eps in points.
+   * @param height The height of eps in points.
+   * @param os File to send postscript to.
+   */
+  public PostscriptGraphics(int width, int height, OutputStream os ){
+    
+    m_extent             = new Rectangle(0, 0, height, width);
+    m_printstream        = new PrintStream(os);
+    m_localGraphicsState = new GraphicsState();
+    m_psGraphicsState    = new GraphicsState();
+    
+    Header();
+  }
+  
+  /** 
+   * Creates a new cloned PostscriptGraphics object.
+   *
+   * @param copy The PostscriptGraphics object to clone.
+   */
+  PostscriptGraphics(PostscriptGraphics copy){
+    
+    m_extent             = new Rectangle(copy.m_extent);
+    m_printstream        = copy.m_printstream;
+    m_localGraphicsState = new GraphicsState(copy.m_localGraphicsState); // create a local copy of the current state
+    m_psGraphicsState    = copy.m_psGraphicsState; // link to global state of eps file
+  }
+  
+  /**
+   * Finalizes output file.
+   */
+  public void finished(){
+    m_printstream.flush();
+  }
+  
+  /**
+   * Output postscript header to PrintStream, including helper macros.
+   */
+  private void Header(){
+    m_printstream.println("%!PS-Adobe-3.0 EPSF-3.0");
+    m_printstream.println("%%BoundingBox: 0 0 " + xScale(m_extent.width) + " " + yScale(m_extent.height));
+    m_printstream.println("%%CreationDate: " + Calendar.getInstance().getTime());
+    
+    m_printstream.println("/Oval { % x y w h filled");
+    m_printstream.println("gsave");
+    m_printstream.println("/filled exch def /h exch def /w exch def /y exch def /x exch def");
+    m_printstream.println("x w 2 div add y h 2 div sub translate");
+    m_printstream.println("1 h w div scale");
+    m_printstream.println("filled {0 0 moveto} if");
+    m_printstream.println("0 0 w 2 div 0 360 arc");
+    m_printstream.println("filled {closepath fill} {stroke} ifelse grestore} bind def");
+    
+    m_printstream.println("/Rect { % x y w h filled");
+    m_printstream.println("/filled exch def /h exch def /w exch def /y exch def /x exch def");
+    m_printstream.println("newpath ");
+    m_printstream.println("x y moveto");    
+    m_printstream.println("w 0 rlineto");
+    m_printstream.println("0 h neg rlineto");
+    m_printstream.println("w neg 0 rlineto");
+    m_printstream.println("closepath");
+    m_printstream.println("filled {fill} {stroke} ifelse} bind def");
+    
+    m_printstream.println("%%BeginProlog\n%%EndProlog");
+    m_printstream.println("%%Page 1 1");
+    setFont(null); // set to default
+    setColor(null); // set to default
+    setStroke(null); // set to default
+  }
+  
+  /**
+   * adds the PS font name to replace and its replacement in the replacement
+   * hashtable
+   * 
+   * @param replace       the PS font name to replace
+   * @param with          the PS font name to replace the font with 
+   */
+  public static void addPSFontReplacement(String replace, String with) {
+    m_PSFontReplacement.put(replace, with);
+  }
+  
+  /**
+   * Convert Java Y coordinate (0 = top) to PostScript (0 = bottom)
+   * Also apply current Translation
+   * @param y Java Y coordinate
+   * @return translated Y to postscript
+   */
+  private int yTransform(int y){
+    return (m_extent.height - (m_localGraphicsState.getYOffset() + y));
+  }
+  
+  /**
+   * Apply current X Translation
+   * @param x Java X coordinate
+   * @return translated X to postscript
+   */
+  private int xTransform(int x){
+    return (m_localGraphicsState.getXOffset() + x);
+  }
+  
+  /**
+   * scales the given number with the provided scale factor
+   */
+  private int doScale(int number, double factor) {
+    return (int) StrictMath.round(number * factor);
+  }
+  
+  /**
+   * scales the given x value with current x scale factor
+   */
+  private int xScale(int x) {
+    return doScale(x, m_localGraphicsState.getXScale());
+  }
+  
+  /**
+   * scales the given y value with current y scale factor
+   */
+  private int yScale(int y) {
+    return doScale(y, m_localGraphicsState.getYScale());
+  }
+  
+  /** Set the current eps graphics state to that of the local one
+   */
+  private void setStateToLocal(){
+    setColor(this.getColor());
+    setFont(this.getFont());
+    setStroke(this.getStroke());
+  }
+  
+  /**
+   * returns a two hexadecimal representation of i, if shorter than 2 chars
+   * then an additional "0" is put in front   
+   */
+  private String toHex(int i) {
+    String      result;
+    
+    result = Integer.toHexString(i);
+    if (result.length() < 2)
+      result = "0" + result;
+    
+    return result;
+  }
+
+  /***** overridden Graphics methods *****/  
+  
+  /**
+   * Draw a filled rectangle with the background color.
+   *
+   * @param x starting x coord
+   * @param y starting y coord
+   * @param width rectangle width
+   * @param height rectangle height
+   */
+  public void clearRect(int x, int y, int width, int height) {
+    setStateToLocal();
+    Color saveColor = getColor();
+    setColor(Color.white); // background color for page
+    m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " " + xScale(width) + " " + yScale(height) + " true Rect");
+    setColor(saveColor);
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void clipRect(int x, int y, int width, int height) {}
+  
+  /**
+   * Not implemented
+   */
+  public void copyArea(int x, int y, int width, int height, int dx, int dy) {}
+  
+  /**
+   * Clone a PostscriptGraphics object
+   */  
+  public Graphics create() {
+    if (DEBUG)
+      m_printstream.println("%create");
+    PostscriptGraphics psg = new PostscriptGraphics(this);
+    return(psg);
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void dispose(){}
+  
+  /**
+   * Draw an outlined rectangle with 3D effect in current pen color.
+   * (Current implementation: draw simple outlined rectangle)
+   *
+   * @param x starting x coord
+   * @param y starting y coord
+   * @param width rectangle width
+   * @param height rectangle height
+   * @param raised True: appear raised, False: appear etched
+   */
+  public void draw3DRect(int x, int y, int width, int height, boolean raised){
+    drawRect(x,y,width,height);
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle){}
+  
+  /**
+   * simply calls drawString(String,int,int)
+   * 
+   * @see #drawString(String,int,int)
+   */
+  public void drawBytes(byte[] data, int offset, int length, int x, int y) {
+    drawString(new String(data, offset, length), x, y);
+  }
+  
+  /**
+   * simply calls drawString(String,int,int)
+   * 
+   * @see #drawString(String,int,int)
+   */
+  public void drawChars(char[] data, int offset, int length, int x, int y) {
+    drawString(new String(data, offset, length), x, y);
+  }
+  
+  /**
+   * calls drawImage(Image,int,int,int,int,Color,ImageObserver)
+   * 
+   * @see #drawImage(Image,int,int,int,int,Color,ImageObserver)
+   */
+  public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer){
+    return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), bgcolor, observer);
+  }
+  
+  /**
+   * calls drawImage(Image,int,int,Color,ImageObserver) with Color.WHITE as 
+   * background color
+   * 
+   * @see #drawImage(Image,int,int,Color,ImageObserver)
+   * @see Color#WHITE
+   */
+  public boolean drawImage(Image img, int x, int y, ImageObserver observer){
+    return drawImage(img, x, y, Color.WHITE, observer);
+  }
+  
+  /**
+   * PS see http://astronomy.swin.edu.au/~pbourke/geomformats/postscript/
+   * Java http://show.docjava.com:8086/book/cgij/doc/ip/graphics/SimpleImageFrame.java.html
+   */
+  public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer){
+    try {
+      // get data from image
+      int[] pixels = new int[width * height];
+      PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
+      grabber.grabPixels();
+      ColorModel model = ColorModel.getRGBdefault();
+      
+      // print data to ps
+      m_printstream.println("gsave");
+      m_printstream.println(xTransform(xScale(x)) + " " + (yTransform(yScale(y)) - yScale(height)) + " translate");
+      m_printstream.println(xScale(width) + " " + yScale(height) + " scale");
+      m_printstream.println(width + " " + height + " " + "8" + " [" + width + " 0 0 " + (-height) + " 0 " + height + "]");
+      m_printstream.println("{<");
+
+      int index;
+      for (int i = 0; i < height; i++) {
+        for (int j = 0; j < width; j++) {
+          index = i * width + j;
+          m_printstream.print(toHex(model.getRed(pixels[index])));
+          m_printstream.print(toHex(model.getGreen(pixels[index])));
+          m_printstream.print(toHex(model.getBlue(pixels[index])));
+        }
+        m_printstream.println();
+      }
+      
+      m_printstream.println(">}");
+      m_printstream.println("false 3 colorimage");
+      m_printstream.println("grestore");
+      return true;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return false;
+    }
+  }
+  
+  /**
+   * calls drawImage(Image,int,int,int,int,Color,ImageObserver) with the color 
+   * WHITE as background
+   * 
+   * @see #drawImage(Image,int,int,int,int,Color,ImageObserver)
+   * @see Color#WHITE
+   */
+  public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer){
+    return drawImage(img, x, y, width, height, Color.WHITE, observer);
+  }
+  
+  /**
+   * Not implemented
+   */
+  public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver  observer){
+    return false;
+  }
+  
+  /**
+   * calls drawImage(Image,int,int,int,int,int,int,int,int,Color,ImageObserver)
+   * with Color.WHITE as background color
+   * 
+   * @see #drawImage(Image,int,int,int,int,int,int,int,int,Color,ImageObserver)
+   */
+  public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer){
+    return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, Color.WHITE, observer);
+  }
+  
+  
+  /**
+   * Draw a line in current pen color.
+   *
+   * @param x1 starting x coord
+   * @param y1 starting y coord
+   * @param x2 ending x coord
+   * @param y2 ending y coord
+   */
+  public void drawLine(int x1, int y1, int x2, int y2){
+    setStateToLocal();
+    m_printstream.println(xTransform(xScale(x1)) + " " + yTransform(yScale(y1)) + " moveto " + xTransform(xScale(x2)) + " " + yTransform(yScale(y2)) + " lineto stroke");
+  }
+  
+  /**
+   * Draw an Oval outline in current pen color.
+   *
+   * @param x x-axis center of oval
+   * @param y y-axis center of oval
+   * @param width oval width
+   * @param height oval height
+   */
+  public void drawOval(int x, int y, int width, int height){
+    setStateToLocal();
+    m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " " + xScale(width) + " " + yScale(height) + " false Oval");
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints){}
+  
+  /**
+   * Not implemented
+   */
+  public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints){}
+  
+  /**
+   * Draw an outlined rectangle in current pen color.
+   *
+   * @param x starting x coord
+   * @param y starting y coord
+   * @param width rectangle width
+   * @param height rectangle height
+   */
+  public void drawRect(int x, int y, int width, int height){   
+    setStateToLocal();
+    m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " " + xScale(width) + " " + yScale(height) + " false Rect");   
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight){}
+  
+  /**
+   * Not implemented
+   */
+  public void drawString(AttributedCharacterIterator iterator, int x, int y){}
+  
+  /**
+   * Escapes brackets in the string with backslashes.
+   * 
+   * @param s the string to escape
+   * @return the escaped string
+   */
+  protected String escape(String s) {
+    StringBuffer	result;
+    int			i;
+    
+    result = new StringBuffer();
+    
+    for (i = 0; i < s.length(); i++) {
+      if ( (s.charAt(i) == '(') || (s.charAt(i) == ')') )
+	result.append('\\');
+      result.append(s.charAt(i));
+    }
+    
+    return result.toString();
+  }
+  
+  /**
+   * Draw text in current pen color.
+   *
+   * @param str Text to output
+   * @param x starting x coord
+   * @param y starting y coord
+   */
+  public void drawString(String str, int x, int y){
+    setStateToLocal();
+    m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " moveto" + " (" + escape(str) + ") show stroke");
+  }
+  
+  /**
+   * Draw a filled rectangle with 3D effect in current pen color.
+   * (Current implementation: draw simple filled rectangle)
+   *
+   * @param x starting x coord
+   * @param y starting y coord
+   * @param width rectangle width
+   * @param height rectangle height
+   * @param raised True: appear raised, False: appear etched
+   */
+  public void fill3DRect(int x, int y, int width, int height, boolean raised){
+    fillRect(x, y, width, height);
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle){}
+  
+  /**
+   * Draw a filled Oval in current pen color.
+   *
+   * @param x x-axis center of oval
+   * @param y y-axis center of oval
+   * @param width oval width
+   * @param height oval height
+   */
+  public void fillOval(int x, int y, int width, int height){
+    setStateToLocal();
+    m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " " + xScale(width) + " " + yScale(height) + " true Oval");
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints){}
+  
+  /**
+   * Not implemented
+   */
+  public void fillPolygon(Polygon p){}
+  
+  /**
+   * Draw a filled rectangle in current pen color.
+   *
+   * @param x starting x coord
+   * @param y starting y coord
+   * @param width rectangle width
+   * @param height rectangle height
+   */
+  
+  public void fillRect(int x, int y, int width, int height){
+    if (width == m_extent.width && height == m_extent.height) {
+      clearRect(x, y, width, height); // if we're painting the entire background, just make it white
+    } else {
+      if (DEBUG)
+        m_printstream.println("% fillRect");
+      setStateToLocal();
+      m_printstream.println(xTransform(xScale(x)) + " " + yTransform(yScale(y)) + " " + xScale(width) + " " + yScale(height) + " true Rect");
+    }
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight){}
+  
+  /**
+   * Not implemented
+   */
+  public void finalize(){}
+  
+  /**
+   * Not implemented
+   */
+  public Shape getClip(){
+    return(null);
+  }
+  
+  /**
+   * This returns the full current drawing area
+   * @return full drawing area
+   */
+  public Rectangle getClipBounds(){
+    return(new Rectangle(0, 0, m_extent.width, m_extent.height));
+  }
+  
+  /**
+   * This returns the full current drawing area
+   * @return full drawing area
+   */
+  public Rectangle getClipBounds(Rectangle r) {
+    r.setBounds(0, 0, m_extent.width, m_extent.height);
+    return r;
+  }
+  
+  /**
+   * Not implemented
+   */
+  public Rectangle getClipRect() {return null;}
+  
+  /**
+   * Get current pen color.
+   *
+   * @return current pen color.
+   */
+  public Color getColor(){
+    return (m_localGraphicsState.getColor());
+  }
+  
+  /**
+   * Get current font.
+   *
+   * @return current font.
+   */
+  public Font getFont(){
+    return (m_localGraphicsState.getFont());
+  }
+  
+  /**
+   * Get Font metrics
+   *
+   * @param f Font 
+   * @return Font metrics.
+   */
+  public FontMetrics getFontMetrics(Font f){
+    return(Toolkit.getDefaultToolkit().getFontMetrics(f));  
+    
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void setClip(int x, int y, int width, int height) {}
+  
+  /**
+   * Not implemented
+   */
+  public void setClip(Shape clip){}
+  
+  /**
+   * Set current pen color. Default to black if null.
+   *
+   * @param c new pen color.
+   */
+  public void setColor(Color c){
+    if (c != null){
+      m_localGraphicsState.setColor(c);
+      if (m_psGraphicsState.getColor().equals(c)) {
+        return;
+      }
+      m_psGraphicsState.setColor(c);
+    } else {
+      m_localGraphicsState.setColor(Color.black);
+      m_psGraphicsState.setColor(getColor());
+    }
+    m_printstream.print(getColor().getRed()/255.0);
+    m_printstream.print(" ");
+    m_printstream.print(getColor().getGreen()/255.0);
+    m_printstream.print(" ");
+    m_printstream.print(getColor().getBlue()/255.0);
+    m_printstream.println(" setrgbcolor");
+  }
+  
+  /**
+   * replaces the font (PS name) if necessary and returns the new name
+   */
+  private static String replacePSFont(String font) {
+    String      result;
+    
+    result = font;
+    
+    // do we have to replace it? -> same style, size
+    if (m_PSFontReplacement.containsKey(font)) {
+      result = m_PSFontReplacement.get(font).toString();
+      if (DEBUG)
+        System.out.println("switched font from '" + font + "' to '" + result +  "'");
+    }
+    
+    return result;
+  }
+  
+  /**
+   * Set current font. Default to Plain Courier 11 if null.
+   *
+   * @param font new font.
+   */
+  public void setFont(Font font){
+    
+    if (font != null){
+      m_localGraphicsState.setFont(font);
+      if (   font.getName().equals(m_psGraphicsState.getFont().getName())
+          && (m_psGraphicsState.getFont().getStyle() == font.getStyle())
+          && (m_psGraphicsState.getFont().getSize() == yScale(font.getSize())))
+        return;
+      m_psGraphicsState.setFont(new Font(font.getName(), font.getStyle(), yScale(getFont().getSize())));
+    } 
+    else {
+      m_localGraphicsState.setFont(new Font ("Courier", Font.PLAIN, 11));
+      m_psGraphicsState.setFont(getFont());
+    }
+    
+    m_printstream.println("/(" + replacePSFont(getFont().getPSName()) + ")" + " findfont");
+    m_printstream.println(yScale(getFont().getSize()) + " scalefont setfont");        
+  }
+  
+  /**
+   * Not implemented
+   */
+  public void setPaintMode(){}
+  
+  /**
+   * Not implemented
+   */
+  public void setXORMode(Color c1){}
+  
+  /**
+   * Translates the origin of the graphics context to the point (x, y) in the 
+   * current coordinate system. Modifies this graphics context so that its new 
+   * origin corresponds to the point (x, y) in this graphics context's original 
+   * coordinate system. All coordinates used in subsequent rendering operations 
+   * on this graphics context will be relative to this new origin.
+   * 
+   * @param x the x coordinate.
+   * @param y the y coordinate.
+   */
+  public void translate(int x, int y){
+    if (DEBUG)
+      System.out.println("translate with x = " + x + " and y = " + y);
+    m_localGraphicsState.setXOffset(m_localGraphicsState.getXOffset() + xScale(x));
+    m_localGraphicsState.setYOffset(m_localGraphicsState.getYOffset() + yScale(y));
+    m_psGraphicsState.setXOffset(m_psGraphicsState.getXOffset() + xScale(x));
+    m_psGraphicsState.setYOffset(m_psGraphicsState.getYOffset() + yScale(y));
+  }
+  /***** END overridden Graphics methods *****/
+  
+  /***** START overridden Graphics2D methods *****/
+  
+  public FontRenderContext getFontRenderContext(){
+    return (new FontRenderContext(null,true,true));
+  }
+  public void clip(Shape s){}
+  public Stroke getStroke(){
+    return(m_localGraphicsState.getStroke());
+  }
+  
+  public Color getBackground(){
+    return(Color.white);
+  }
+  public void setBackground(Color c){}
+  public Composite getComposite(){
+    return(AlphaComposite.getInstance(AlphaComposite.SRC));
+  }
+  public Paint getPaint(){
+    return((Paint) (new Color(getColor().getRed(),getColor().getGreen(),getColor().getBlue())));
+  }
+  public AffineTransform getTransform(){
+    return(new AffineTransform());
+  }
+  public void setTransform(AffineTransform at) {}
+  public void transform(AffineTransform at) {}
+  public void shear(double d1, double d2){}
+  public void scale(double d1, double d2) {
+    m_localGraphicsState.setXScale(d1);
+    m_localGraphicsState.setYScale(d2);
+    if (DEBUG)
+      System.err.println("d1 = " + d1 + ", d2 = " + d2);
+  }
+  public void rotate(double d1, double d2, double d3){}
+  public void rotate(double d1){}
+  public void translate(double d1, double d2) {}
+  public RenderingHints getRenderingHints(){
+    return(new RenderingHints(null));
+  }
+  public void addRenderingHints(Map m){}
+  public void setRenderingHints(Map m){}
+  public Object getRenderingHint(RenderingHints.Key key){
+    return(null);
+  }
+  public void setRenderingHint(RenderingHints.Key key, Object o){}
+  public void setStroke(Stroke s){
+    if (s != null){
+      m_localGraphicsState.setStroke(s);
+      if (s.equals(m_psGraphicsState.getStroke())) {
+        return;
+      }
+      m_psGraphicsState.setStroke(s); 
+    } else {
+      m_localGraphicsState.setStroke(new BasicStroke());
+      m_psGraphicsState.setStroke(getStroke());
+    }
+    // ouput postscript here to set stroke.
+  }
+  public void setPaint(Paint p){
+  }
+  public void setComposite(Composite c){}
+  public GraphicsConfiguration getDeviceConfiguration(){
+    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    return(gd.getDefaultConfiguration()); 
+  }
+  public boolean hit(Rectangle r, Shape s, boolean onstroke){
+    return(false);
+  }
+  public void fill(Shape s){}
+  public void drawGlyphVector(GlyphVector gv, float f1, float f2){} 
+  public void drawString(AttributedCharacterIterator aci, float f1, float f2){}
+  public void drawString(String str, float x, float y){
+    drawString(str,(int)x, (int)y);
+  }
+  public void drawRenderableImage(RenderableImage ri, AffineTransform at){}
+  public void drawRenderedImage(RenderedImage ri, AffineTransform af){}
+  public void drawImage(BufferedImage bi, BufferedImageOp bio, int i1, int i2){}
+  public boolean drawImage(Image im, AffineTransform at, ImageObserver io){
+    return(false);
+  }
+  public void draw(Shape s){}
+  /***** END *****/
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptWriter.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptWriter.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PostscriptWriter.java	(revision 29)
@@ -0,0 +1,140 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    PostscriptWriter.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+
+import javax.swing.JComponent;
+
+/** 
+ * This class takes any Component and outputs it to a Postscript file.<p>
+ * <b>Note:</b><br>
+ * This writer does not work with Components that rely on clipping, like e.g.
+ * scroll lists. Here the complete list is printed, instead of only in the
+ * borders of the scroll list (may overlap other components!). This is due to
+ * the way, clipping is handled in Postscript. There was no easy way around 
+ * this issue. :-(
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ * @see PostscriptGraphics
+ */
+public class PostscriptWriter
+  extends JComponentWriter {
+  
+  /**
+   * initializes the object 
+   */
+  public PostscriptWriter() {
+    super(null);
+  }
+  
+  /**
+   * initializes the object with the given Component
+   * 
+   * @param c         the component to print in the output format
+   */
+  public PostscriptWriter(JComponent c) {
+    super(c);
+  }
+  
+  /**
+   * initializes the object with the given Component and filename
+   * 
+   * @param c         the component to print in the output format
+   * @param f         the file to store the output in
+   */
+  public PostscriptWriter(JComponent c, File f) {
+    super(c, f);
+  }
+  
+  /**
+   * returns the name of the writer, to display in the FileChooser.
+   * must be overridden in the derived class.
+   */
+  public String getDescription() {
+    return "Postscript-File";
+  }
+  
+  /**
+   * returns the extension (incl. ".") of the output format, to use in the
+   * FileChooser. 
+   * must be overridden in the derived class.
+   */
+  public String getExtension() {
+    return ".eps";
+  }
+  
+  /**
+   * generates the actual output
+   * 
+   * @throws Exception	if something goes wrong
+   */
+  public void generateOutput() throws Exception {
+    BufferedOutputStream      ostrm;
+    PostscriptGraphics        psg;
+
+    ostrm = null;
+    
+    try { 
+      ostrm = new BufferedOutputStream(new FileOutputStream(getFile()));
+      psg = new PostscriptGraphics(getComponent().getHeight(), getComponent().getWidth(), ostrm);
+      psg.setFont(getComponent().getFont());
+      psg.scale(getXScale(), getYScale());
+      getComponent().printAll(psg);
+      psg.finished();
+    } 
+    catch (Exception e) {
+      System.err.println(e); 
+    } 
+    finally { 
+      if (ostrm != null) {
+        try {
+          ostrm.close();
+        } catch (Exception e) {
+          // Nothing to really do for error on close
+        }
+      }
+    }
+  }
+  
+  /**
+   * for testing only
+   */
+  public static void main(String[] args) throws Exception {
+    System.out.println("building TreeVisualizer...");
+    weka.gui.treevisualizer.TreeBuild builder = new weka.gui.treevisualizer.TreeBuild();
+    weka.gui.treevisualizer.NodePlace arrange = new weka.gui.treevisualizer.PlaceNode2();
+    weka.gui.treevisualizer.Node top = builder.create(new java.io.StringReader("digraph atree { top [label=\"the top\"] a [label=\"the first node\"] b [label=\"the second nodes\"] c [label=\"comes off of first\"] top->a top->b b->c }"));
+    weka.gui.treevisualizer.TreeVisualizer tv = new weka.gui.treevisualizer.TreeVisualizer(null, top, arrange);
+    tv.setSize(800 ,600);
+    
+    String filename = System.getProperty("java.io.tmpdir") + "test.eps";
+    System.out.println("outputting to '" + filename + "'...");
+    toOutput(new PostscriptWriter(), tv, new File(filename));
+
+    System.out.println("done!");
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableComponent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableComponent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableComponent.java	(revision 29)
@@ -0,0 +1,631 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    PrintableComponent.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import weka.gui.ExtensionFileFilter;
+import weka.gui.GenericObjectEditor;
+
+import java.awt.Dimension;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+/** 
+ * This class extends the component which is handed over in the constructor
+ * by a print dialog.
+ * The Print dialog is accessible via Alt+Shift+LeftMouseClick. <p>
+ * The individual JComponentWriter-descendants can be accessed by the
+ * <code>getWriter(String)</code> method, if the parameters need to be changed.
+ *
+ * @see #getWriters()
+ * @see #getWriter(String)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.8 $
+ */
+public class PrintableComponent
+  implements PrintableHandler {
+  
+  /** the parent component of this print dialog. */
+  protected JComponent m_Component;
+  
+  /** the filechooser for saving the panel. */
+  protected static JFileChooser m_FileChooserPanel;
+
+  /** the checkbox for the custom dimensions. */
+  protected static JCheckBox m_CustomDimensionsCheckBox;
+  
+  /** the edit field for the custom width. */
+  protected static JTextField m_CustomWidthText;
+  
+  /** the edit field for the custom height. */
+  protected static JTextField m_CustomHeightText;
+
+  /** the checkbox for keeping the aspect ration. */
+  protected static JCheckBox m_AspectRatioCheckBox;
+  
+  /** the title of the save dialog. */
+  protected String m_SaveDialogTitle = "Save as...";
+  
+  /** the x scale factor. */
+  protected double m_xScale = 1.0;
+  
+  /** the y scale factor. */
+  protected double m_yScale = 1.0;
+
+  /** the aspect ratio. */
+  protected double m_AspectRatio;
+
+  /** whether to ignore the update of the text field (in case of "keep ratio"). */
+  protected boolean m_IgnoreChange;
+  
+  /** whether to print some debug information. */
+  private static final boolean DEBUG = false;
+  
+  /** whether the user was already asked about the tooltip behavior. */
+  protected static boolean m_ToolTipUserAsked = false;
+
+  /** the property name for showing the tooltip. */
+  protected final static String PROPERTY_SHOW = "PrintableComponentToolTipShow";
+
+  /** the property name whether the user was already asked. */
+  protected final static String PROPERTY_USERASKED = "PrintableComponentToolTipUserAsked";
+  
+  /** whether to display the tooltip or not. */
+  protected static boolean m_ShowToolTip = true;
+  static {
+    try {
+      m_ShowToolTip = Boolean.valueOf(
+          VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(
+            PROPERTY_SHOW, 
+            "true")).booleanValue();
+      m_ToolTipUserAsked = Boolean.valueOf(
+          VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(
+            PROPERTY_USERASKED, 
+            "false")).booleanValue();
+    }
+    catch (Exception e) {
+      // ignore exception
+      m_ToolTipUserAsked = false;
+      m_ShowToolTip      = true;
+    }
+  }
+  
+  /** output if we're in debug mode */
+  static {
+    if (DEBUG)
+      System.err.println(PrintablePanel.class.getName() + ": DEBUG ON");
+  }
+  
+  /**
+   * initializes the panel.
+   * 
+   * @param component     the component to enhance with printing functionality
+   */
+  public PrintableComponent(JComponent component) {
+    super();
+    
+    m_Component   = component;
+    m_AspectRatio = Double.NaN;
+    
+    getComponent().addMouseListener(new PrintMouseListener(this));
+    getComponent().setToolTipText(getToolTipText(this));
+    initFileChooser();
+  }
+  
+  /**
+   * returns the GUI component this print dialog is part of.
+   * 
+   * @return		the GUI component
+   */
+  public JComponent getComponent() {
+    return m_Component;
+  }
+
+  /**
+   * Returns a tooltip only if the user wants it. If retrieved for the first,
+   * a dialog pops up and asks the user whether the tooltip should always
+   * appear or not. The weka/gui/visualize/Visualize.props is then written
+   * in the user's home directory.
+   *
+   * @param component the PrintableComponent to ask for
+   * @return null if the user doesn't want the tooltip, otherwise the text
+   */
+  public static String getToolTipText(PrintableComponent component) {
+    String        result;
+    int           retVal;
+    Properties    props;
+    String        name;
+    Enumeration   names;
+    String        filename;
+
+    // ToolTip is disabled for the moment...
+    if (true)
+      return null;
+
+    // ask user whether the tooltip should be shown
+    if (!m_ToolTipUserAsked) {
+      m_ToolTipUserAsked = true;
+      
+      retVal = JOptionPane.showConfirmDialog(
+          component.getComponent(),
+            "Some panels enable the user to save the content as JPEG or EPS.\n"
+          + "In order to see which panels support this, a tooltip can be "
+          + "displayed. Enable tooltip?",
+          "ToolTip for Panels...",
+          JOptionPane.YES_NO_OPTION);
+
+      m_ShowToolTip = (retVal == JOptionPane.YES_OPTION);
+
+      // save props file
+      VisualizeUtils.VISUALIZE_PROPERTIES.setProperty(
+               PROPERTY_SHOW, "" + m_ShowToolTip);
+      VisualizeUtils.VISUALIZE_PROPERTIES.setProperty(
+               PROPERTY_USERASKED, "" + m_ToolTipUserAsked);
+      try {
+        // NOTE: properties that got inherited from another props file don't
+        //       get saved. I.e., one could overwrite the existing props
+        //       file with an (nearly) empty one.
+        //       => transfer all properties into a new one
+        props = new Properties();
+        names = VisualizeUtils.VISUALIZE_PROPERTIES.propertyNames();
+        while (names.hasMoreElements()) {
+          name = names.nextElement().toString();
+          props.setProperty(
+              name,  
+              VisualizeUtils.VISUALIZE_PROPERTIES.getProperty(name, ""));
+        }
+        filename = System.getProperty("user.home") + "/Visualize.props";
+        props.store(
+            new BufferedOutputStream(new FileOutputStream(filename)), null);
+
+        // inform user about location of props file and name of property
+        JOptionPane.showMessageDialog(
+            component.getComponent(), 
+            "You can still manually enable or disable the ToolTip via the following property\n"
+            + "    " + PROPERTY_SHOW + "\n"
+            + "in the following file\n"
+            + "    " + filename);
+      }
+      catch (Exception e) {
+        JOptionPane.showMessageDialog(
+            component.getComponent(), 
+              "Error saving the props file!\n"
+            + e.getMessage() + "\n\n"
+            + "Note:\n"
+            + "If you want to disable these messages from popping up, place a file\n"
+            + "called 'Visualize.props' either in your home directory or in the directory\n"
+            + "you're starting Weka from and add the following lines:\n"
+            + "    " + PROPERTY_USERASKED + "=true\n"
+            + "    " + PROPERTY_SHOW + "=" + m_ShowToolTip,
+            "Error...",
+            JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    
+    if (m_ShowToolTip)
+      result = "Click left mouse button while holding <alt> and <shift> to display a save dialog.";
+    else
+      result = null;
+
+    return result;
+  }
+  
+  /**
+   * initializes the filechooser, i.e. locates all the available writers in
+   * the current package
+   */
+  protected void initFileChooser() {
+    Vector              writerNames;
+    int                 i;
+    Class               cls;
+    JComponentWriter    writer;
+    JPanel		accessory;
+    JLabel		label;
+
+    // already initialized?
+    if (m_FileChooserPanel != null)
+      return;
+
+    m_FileChooserPanel = new JFileChooser();
+    m_FileChooserPanel.resetChoosableFileFilters();
+    m_FileChooserPanel.setAcceptAllFileFilterUsed(false);
+
+    // setup the accessory
+    accessory = new JPanel();
+    accessory.setLayout(null);
+    accessory.setPreferredSize(new Dimension(200, 200));
+    accessory.revalidate();
+    m_FileChooserPanel.setAccessory(accessory);
+ 
+    m_CustomDimensionsCheckBox = new JCheckBox("Use custom dimensions");
+    m_CustomDimensionsCheckBox.setBounds(14, 7, 200, 21);
+    m_CustomDimensionsCheckBox.addItemListener(new ItemListener() {
+      public void itemStateChanged(ItemEvent e) {
+	boolean custom = m_CustomDimensionsCheckBox.isSelected();
+	m_CustomWidthText.setEnabled(custom);
+	m_CustomHeightText.setEnabled(custom);
+	m_AspectRatioCheckBox.setEnabled(custom);
+	if (custom) {
+	  m_IgnoreChange = true;
+	  m_CustomWidthText.setText("" + m_Component.getWidth());
+	  m_CustomHeightText.setText("" + m_Component.getHeight());
+	  m_IgnoreChange = false;
+	}
+	else {
+	  m_IgnoreChange = true;
+	  m_CustomWidthText.setText("-1");
+	  m_CustomHeightText.setText("-1");
+	  m_IgnoreChange = false;
+	}
+      }
+    });
+    accessory.add(m_CustomDimensionsCheckBox);
+    
+    m_CustomWidthText = new JTextField(5);
+    m_CustomWidthText.setText("-1");
+    m_CustomWidthText.setEnabled(false);
+    m_CustomWidthText.setBounds(65, 35, 50, 21);
+    m_CustomWidthText.getDocument().addDocumentListener(new DocumentListener() {
+      public void changedUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomWidthText);
+      }
+      
+      public void insertUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomWidthText);
+      }
+      
+      public void removeUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomWidthText);
+      }
+    });
+    label = new JLabel("Width");
+    label.setLabelFor(m_CustomWidthText);
+    label.setDisplayedMnemonic('W');
+    label.setBounds(14, 35, 50, 21);
+    accessory.add(label);
+    accessory.add(m_CustomWidthText);
+    
+    m_CustomHeightText = new JTextField(5);
+    m_CustomHeightText.setText("-1");
+    m_CustomHeightText.setEnabled(false);
+    m_CustomHeightText.setBounds(65, 63, 50, 21);
+    m_CustomHeightText.getDocument().addDocumentListener(new DocumentListener() {
+      public void changedUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomHeightText);
+      }
+      
+      public void insertUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomHeightText);
+      }
+      
+      public void removeUpdate(DocumentEvent e) {
+	updateDimensions(m_CustomHeightText);
+      }
+    });
+    label = new JLabel("Height");
+    label.setLabelFor(m_CustomHeightText);
+    label.setDisplayedMnemonic('H');
+    label.setBounds(14, 63, 50, 21);
+    accessory.add(label);
+    accessory.add(m_CustomHeightText);
+    
+    m_AspectRatioCheckBox = new JCheckBox("Keep aspect ratio");
+    m_AspectRatioCheckBox.setBounds(14, 91, 200, 21);
+    m_AspectRatioCheckBox.setEnabled(false);
+    m_AspectRatioCheckBox.setSelected(true);
+    m_AspectRatioCheckBox.addItemListener(new ItemListener() {
+      public void itemStateChanged(ItemEvent e) {
+	boolean keep = m_AspectRatioCheckBox.isSelected();
+	if (keep) {
+	  m_IgnoreChange = true;
+	  m_CustomWidthText.setText("" + m_Component.getWidth());
+	  m_CustomHeightText.setText("" + m_Component.getHeight());
+	  m_IgnoreChange = false;
+	}
+      }
+    });
+    accessory.add(m_AspectRatioCheckBox);
+    
+    // determine all available writers and add them to the filechooser
+    writerNames = GenericObjectEditor.getClassnames(JComponentWriter.class.getName());
+    Collections.sort(writerNames);
+    for (i = 0; i < writerNames.size(); i++) {
+      try {
+        cls    = Class.forName(writerNames.get(i).toString());
+        writer = (JComponentWriter) cls.newInstance();
+        m_FileChooserPanel.addChoosableFileFilter(
+            new JComponentWriterFileFilter(
+        	writer.getExtension(), 
+        	writer.getDescription() + " (*" + writer.getExtension() + ")", 
+        	writer));
+      }
+      catch (Exception e) {
+        System.err.println(writerNames.get(i) + ": " + e);
+      }
+    }
+    
+    // set first filter as active filter
+    if (m_FileChooserPanel.getChoosableFileFilters().length > 0)
+      m_FileChooserPanel.setFileFilter(m_FileChooserPanel.getChoosableFileFilters()[0]);
+  }
+  
+  /**
+   * updates the dimensions if necessary (i.e., if aspect ratio is to be kept).
+   * 
+   * @param sender	the JTextField which send the notification to update
+   */
+  protected void updateDimensions(JTextField sender) {
+    int		newValue;
+    int		baseValue;
+    
+    // some sanity checks
+    if (!m_AspectRatioCheckBox.isSelected() || m_IgnoreChange)
+      return;
+    if (!(sender instanceof JTextField) || (sender == null))
+      return;
+    if (sender.getText().length() == 0)
+      return;
+    
+    // is it a valid integer, greater than 0?
+    try {
+      baseValue = Integer.parseInt(sender.getText());
+      newValue  = 0;
+      if (baseValue <= 0)
+	return;
+
+      if (Double.isNaN(m_AspectRatio)) {
+	m_AspectRatio = (double) getComponent().getWidth() / 
+	(double) getComponent().getHeight();
+      }
+    }
+    catch (Exception e) {
+      // we can't parse the string!
+      return;
+    }
+
+    // computer and update
+    m_IgnoreChange = true;
+    if (sender == m_CustomWidthText) {
+      newValue = (int) (((double) baseValue) * (1/m_AspectRatio));
+      m_CustomHeightText.setText("" + newValue);
+    }
+    else if (sender == m_CustomHeightText) {
+      newValue = (int) (((double) baseValue) * m_AspectRatio);
+      m_CustomWidthText.setText("" + newValue);
+    }
+    m_IgnoreChange = false;
+  }
+  
+  /**
+   * returns a Hashtable with the current available JComponentWriters in the 
+   * save dialog. the key of the Hashtable is the description of the writer.
+   * 
+   * @return all currently available JComponentWriters 
+   * @see JComponentWriter#getDescription()
+   */
+  public Hashtable getWriters() {
+    Hashtable         result;
+    int               i;
+    JComponentWriter  writer;
+    
+    result = new Hashtable();
+    
+    for (i = 0; i < m_FileChooserPanel.getChoosableFileFilters().length; i++) {
+      writer = ((JComponentWriterFileFilter) m_FileChooserPanel.getChoosableFileFilters()[i]).getWriter();
+      result.put(writer.getDescription(), writer);
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the JComponentWriter associated with the given name, is 
+   * <code>null</code> if not found.
+   * 
+   * @param name the name of the writer
+   * @return the writer associated with the given name
+   * @see JComponentWriter#getDescription()
+   */
+  public JComponentWriter getWriter(String name) {
+    return (JComponentWriter) getWriters().get(name);
+  }
+
+  /**
+   * sets the title for the save dialog.
+   * 
+   * @param title the title of the save dialog
+   */
+  public void setSaveDialogTitle(String title) {
+    m_SaveDialogTitle = title;
+  }
+  
+  /**
+   * returns the title for the save dialog.
+   * 
+   * @return the title of the save dialog
+   */
+  public String getSaveDialogTitle() {
+    return m_SaveDialogTitle;
+  }
+  
+  /**
+   * sets the scale factor.
+   * 
+   * @param x the scale factor for the x-axis 
+   * @param y the scale factor for the y-axis 
+   */
+  public void setScale(double x, double y) {
+    m_xScale = x;
+    m_yScale = y;
+    if (DEBUG)
+      System.err.println("x = " + x + ", y = " + y);
+  }
+  
+  /**
+   * returns the scale factor for the x-axis.
+   * 
+   * @return the scale factor
+   */
+  public double getXScale() {
+    return m_xScale;
+  }
+  
+  /**
+   * returns the scale factor for the y-axis.
+   * 
+   * @return the scale factor
+   */
+  public double getYScale() {
+    return m_xScale;
+  }
+  
+  /**
+   * displays a save dialog for saving the panel to a file.  
+   * Fixes a bug with the Swing JFileChooser: if you entered a new
+   * filename in the save dialog and press Enter the <code>getSelectedFile</code>
+   * method returns <code>null</code> instead of the filename.<br>
+   * To solve this annoying behavior we call the save dialog once again s.t. the
+   * filename is set. Might look a little bit strange to the user, but no 
+   * NullPointerException! ;-)
+   */
+  public void saveComponent() {
+    int                           result;
+    JComponentWriter              writer;
+    File                          file;
+    JComponentWriterFileFilter    filter;
+    
+    // display save dialog
+    m_FileChooserPanel.setDialogTitle(getSaveDialogTitle());
+    do {
+      result = m_FileChooserPanel.showSaveDialog(getComponent());
+      if (result != JFileChooser.APPROVE_OPTION)
+        return;
+    }
+    while (m_FileChooserPanel.getSelectedFile() == null);
+    
+    // save the file
+    try {
+      filter = (JComponentWriterFileFilter) m_FileChooserPanel.getFileFilter();
+      file   = m_FileChooserPanel.getSelectedFile();
+      writer = filter.getWriter();
+      if (!file.getAbsolutePath().toLowerCase().endsWith(writer.getExtension().toLowerCase()))
+        file = new File(file.getAbsolutePath() + writer.getExtension()); 
+      writer.setComponent(getComponent());
+      writer.setFile(file);
+      writer.setScale(getXScale(), getYScale());
+      writer.setUseCustomDimensions(m_CustomDimensionsCheckBox.isSelected());
+      if (m_CustomDimensionsCheckBox.isSelected()) {
+	writer.setCustomWidth(Integer.parseInt(m_CustomWidthText.getText()));
+	writer.setCustomHeight(Integer.parseInt(m_CustomHeightText.getText()));
+      }
+      else {
+	writer.setCustomWidth(-1);
+	writer.setCustomHeight(-1);
+      }
+      writer.toOutput();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+  
+  /**
+   * a specialized filter that also contains the associated filter class.
+   */
+  protected class JComponentWriterFileFilter extends ExtensionFileFilter {
+    /** the associated writer. */
+    private JComponentWriter m_Writer; 
+    
+    /**
+     * Creates the ExtensionFileFilter.
+     *
+     * @param extension       the extension of accepted files.
+     * @param description     a text description of accepted files.
+     * @param writer          the associated writer 
+     */
+    public JComponentWriterFileFilter(String extension, String description, JComponentWriter writer) {
+      super(extension, description);
+      m_Writer = writer;
+    }
+    
+    /**
+     * returns the associated writer.
+     * 
+     * @return		the writer
+     */
+    public JComponentWriter getWriter() {
+      return m_Writer;
+    }
+  }
+
+  /**
+   * The listener to wait for Ctrl-Shft-Left Mouse Click.
+   */
+  private class PrintMouseListener extends MouseAdapter {
+    /** the listener's component. */
+    private PrintableComponent m_Component;
+    
+    /**
+     * initializes the listener.
+     * 
+     * @param component the component for which to create the listener
+     */
+    public PrintMouseListener(PrintableComponent component){
+      m_Component = component;
+    }
+    
+    /**
+     * Invoked when the mouse has been clicked on a component.
+     * 
+     * @param e	the event
+     */
+    public void mouseClicked(MouseEvent e) {
+      int modifiers = e.getModifiers();
+      if (((modifiers & MouseEvent.SHIFT_MASK) == MouseEvent.SHIFT_MASK) && 
+          ((modifiers & MouseEvent.ALT_MASK) == MouseEvent.ALT_MASK) &&
+          ((modifiers & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK)) {
+        e.consume();
+        m_Component.saveComponent();
+      }
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableHandler.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableHandler.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PrintableHandler.java	(revision 29)
@@ -0,0 +1,86 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    PrintableComponent.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import java.util.Hashtable;
+
+/**
+ * This interface is for all JComponent classes that provide the ability to 
+ * print itself to a file. 
+ * 
+ * @see PrintableComponent
+ * @see PrintablePanel
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public interface PrintableHandler {
+  /**
+   * returns a Hashtable with the current available JComponentWriters in the 
+   * save dialog. the key of the Hashtable is the description of the writer.
+   * 
+   * @return all currently available JComponentWriters 
+   * @see JComponentWriter#getDescription()
+   */
+  public Hashtable getWriters();
+  
+  /**
+   * returns the JComponentWriter associated with the given name, is 
+   * <code>null</code> if not found
+   * 
+   * @return the writer associated with the given name
+   * @see JComponentWriter#getDescription()
+   */
+  public JComponentWriter getWriter(String name);
+
+  /**
+   * sets the title for the save dialog
+   */
+  public void setSaveDialogTitle(String title);
+  
+  /**
+   * returns the title for the save dialog
+   */
+  public String getSaveDialogTitle();
+  
+  /**
+   * sets the scale factor
+   * @param x the scale factor for the x-axis 
+   * @param y the scale factor for the y-axis 
+   */
+  public void setScale(double x, double y);
+  
+  /**
+   * returns the scale factor for the x-axis
+   */
+  public double getXScale();
+  
+  /**
+   * returns the scale factor for the y-axis
+   */
+  public double getYScale();
+  
+  /**
+   * displays a save dialog for saving the component to a file.  
+   */
+  public void saveComponent();
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/PrintablePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/PrintablePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/PrintablePanel.java	(revision 29)
@@ -0,0 +1,123 @@
+ /*
+  *    This program 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 2 of the License, or
+  *    (at your option) any later version.
+  *
+  *    This program 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 this program; if not, write to the Free Software
+  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+
+ /*
+  *    PrintablePanel.java
+  *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+  *
+  */
+
+package weka.gui.visualize;
+
+import java.util.Hashtable;
+
+import javax.swing.JPanel;
+
+/** 
+ * This Panel enables the user to print the panel to various file formats.
+ * The Print dialog is accessible via Ctrl-Shft-Left Mouse Click. <p>
+ * The individual JComponentWriter-descendants can be accessed by the
+ * <code>getWriter(String)</code> method, if the parameters need to be changed.
+ *
+ * @see #getWriters()
+ * @see #getWriter(String)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class PrintablePanel
+  extends JPanel
+  implements PrintableHandler {
+
+  /** for serialization */
+  private static final long serialVersionUID = 6281532227633417538L;
+  
+  /** the class responsible for printing */
+  protected PrintableComponent m_Printer = null;
+  
+  /**
+   * initializes the panel
+   */
+  public PrintablePanel() {
+    super();
+    m_Printer = new PrintableComponent(this);
+  }
+  
+  /**
+   * returns a Hashtable with the current available JComponentWriters in the 
+   * save dialog. the key of the Hashtable is the description of the writer.
+   * 
+   * @return all currently available JComponentWriters 
+   * @see JComponentWriter#getDescription()
+   */
+  public Hashtable getWriters() {
+    return m_Printer.getWriters();
+  }
+  
+  /**
+   * returns the JComponentWriter associated with the given name, is 
+   * <code>null</code> if not found
+   * 
+   * @return the writer associated with the given name
+   * @see JComponentWriter#getDescription()
+   */
+  public JComponentWriter getWriter(String name) {
+    return m_Printer.getWriter(name);
+  }
+
+  /**
+   * sets the title for the save dialog
+   */
+  public void setSaveDialogTitle(String title) {
+    m_Printer.setSaveDialogTitle(title);
+  }
+  
+  /**
+   * returns the title for the save dialog
+   */
+  public String getSaveDialogTitle() {
+    return m_Printer.getSaveDialogTitle();
+  }
+  
+  /**
+   * sets the scale factor
+   * @param x the scale factor for the x-axis 
+   * @param y the scale factor for the y-axis 
+   */
+  public void setScale(double x, double y) {
+    m_Printer.setScale(x, y);
+  }
+  
+  /**
+   * returns the scale factor for the x-axis
+   */
+  public double getXScale() {
+    return m_Printer.getXScale();
+  }
+  
+  /**
+   * returns the scale factor for the y-axis
+   */
+  public double getYScale() {
+    return m_Printer.getYScale();
+  }
+  
+  /**
+   * displays a save dialog for saving the panel to a file.  
+   */
+  public void saveComponent() {
+    m_Printer.saveComponent();
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/ThresholdVisualizePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/ThresholdVisualizePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/ThresholdVisualizePanel.java	(revision 29)
@@ -0,0 +1,341 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    ThresholdVisualizePanel.java
+ *    Copyright (C) 2003 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.AbstractClassifier;
+import weka.classifiers.evaluation.EvaluationUtils;
+import weka.classifiers.evaluation.ThresholdCurve;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.SingleIndex;
+import weka.core.Utils;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.border.TitledBorder;
+
+/** 
+ * This panel is a VisualizePanel, with the added ablility to display the
+ * area under the ROC curve if an ROC curve is chosen.
+ *
+ * @author Dale Fletcher (dale@cs.waikato.ac.nz)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5928 $
+ */
+public class ThresholdVisualizePanel 
+  extends VisualizePanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 3070002211779443890L;
+
+  /** The string to add to the Plot Border. */
+  private String m_ROCString="";
+ 
+  /** Original border text */
+  private String m_savePanelBorderText;
+
+  /**
+   * default constructor
+   */
+  public ThresholdVisualizePanel() {
+    super();
+
+    // Save the current border text
+    TitledBorder tb=(TitledBorder) m_plotSurround.getBorder();
+    m_savePanelBorderText = tb.getTitle();
+  }
+  
+  /**
+   * Set the string with ROC area
+   * @param str ROC area string to add to border
+   */  
+  public void setROCString(String str) {
+    m_ROCString=str;
+  }
+
+  /**
+   * This extracts the ROC area string 
+   * @return ROC area string 
+   */
+  public String getROCString() {
+    return m_ROCString;
+  }
+
+  /**
+   * This overloads VisualizePanel's setUpComboBoxes to add 
+   * ActionListeners to watch for when the X/Y Axis comboboxes
+   * are changed. 
+   * @param inst a set of instances with data for plotting
+   */
+  public void setUpComboBoxes(Instances inst) {
+    super.setUpComboBoxes(inst);
+
+    m_XCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  setBorderText();
+	}
+    });
+    m_YCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  setBorderText();
+	}
+    });
+
+    // Just in case the default is ROC
+    setBorderText();
+  }
+
+  /**
+   * This checks the current selected X/Y Axis comboBoxes to see if 
+   * an ROC graph is selected. If so, add the ROC area string to the
+   * plot border, otherwise display the original border text.
+   */
+  private void setBorderText() {
+
+    String xs = m_XCombo.getSelectedItem().toString();
+    String ys = m_YCombo.getSelectedItem().toString();
+
+    if (xs.equals("X: False Positive Rate (Num)") && ys.equals("Y: True Positive Rate (Num)"))   {
+        m_plotSurround.setBorder((BorderFactory.createTitledBorder(m_savePanelBorderText+" "+m_ROCString)));
+    } else
+        m_plotSurround.setBorder((BorderFactory.createTitledBorder(m_savePanelBorderText))); 
+  }
+
+  /**
+   * displays the previously saved instances
+   * 
+   * @param insts	the instances to display
+   * @throws Exception	if display is not possible
+   */
+  protected void openVisibleInstances(Instances insts) throws Exception {
+    super.openVisibleInstances(insts);
+
+    setROCString(
+	"(Area under ROC = " 
+	+ Utils.doubleToString(ThresholdCurve.getROCArea(insts), 4) + ")");
+    
+    setBorderText();
+  }
+  
+  /**
+   * Starts the ThresholdVisualizationPanel with parameters from the command line. <p/>
+   * 
+   * Valid options are: <p/>
+   *  -h <br/>
+   *  lists all the commandline parameters <p/>
+   *  
+   *  -t file <br/>
+   *  Dataset to process with given classifier. <p/>
+   *  
+   *  -W classname <br/>
+   *  Full classname of classifier to run.<br/>
+   *  Options after '--' are passed to the classifier. <br/>
+   *  (default weka.classifiers.functions.Logistic) <p/>
+   *  
+   *  -r number <br/>
+   *  The number of runs to perform (default 2). <p/>
+   *  
+   *  -x number <br/>
+   *  The number of Cross-validation folds (default 10). <p/>
+   *  
+   *  -l file <br/>
+   *  Previously saved threshold curve ARFF file. <p/>
+   *
+   * @param args optional commandline parameters
+   */
+  public static void main(String [] args) {
+    Instances 		inst;
+    Classifier 		classifier;
+    int			runs;
+    int			folds;
+    String 		tmpStr;
+    boolean		compute;
+    Instances 		result;
+    String[] 		options;
+    SingleIndex		classIndex;
+    SingleIndex		valueIndex;
+    int			seed;
+    
+    inst       = null;
+    classifier = null;
+    runs       = 2;
+    folds      = 10;
+    compute    = true;
+    result     = null;
+    classIndex = null;
+    valueIndex = null;
+    seed       = 1;
+    
+    try {
+      // help?
+      if (Utils.getFlag('h', args)) {
+	System.out.println("\nOptions for " + ThresholdVisualizePanel.class.getName() + ":\n");
+	System.out.println("-h\n\tThis help.");
+	System.out.println("-t <file>\n\tDataset to process with given classifier.");
+	System.out.println("-c <num>\n\tThe class index. first and last are valid, too (default: last).");
+	System.out.println("-C <num>\n\tThe index of the class value to get the the curve for (default: first).");
+	System.out.println("-W <classname>\n\tFull classname of classifier to run.\n\tOptions after '--' are passed to the classifier.\n\t(default: weka.classifiers.functions.Logistic)");
+	System.out.println("-r <number>\n\tThe number of runs to perform (default: 1).");
+	System.out.println("-x <number>\n\tThe number of Cross-validation folds (default: 10).");
+	System.out.println("-S <number>\n\tThe seed value for randomizing the data (default: 1).");
+	System.out.println("-l <file>\n\tPreviously saved threshold curve ARFF file.");
+	return;
+      }
+      
+      // regular options
+      tmpStr = Utils.getOption('l', args);
+      if (tmpStr.length() != 0) {
+	result = new Instances(new BufferedReader(new FileReader(tmpStr)));
+	compute = false;
+      }
+      
+      if (compute) {
+	tmpStr = Utils.getOption('r', args);
+	if (tmpStr.length() != 0)
+	  runs = Integer.parseInt(tmpStr);
+	else
+	  runs = 1;
+	
+	tmpStr = Utils.getOption('x', args);
+	if (tmpStr.length() != 0)
+	  folds = Integer.parseInt(tmpStr);
+	else
+	  folds = 10;
+	
+	tmpStr = Utils.getOption('S', args);
+	if (tmpStr.length() != 0)
+	  seed = Integer.parseInt(tmpStr);
+	else
+	  seed = 1;
+	
+	tmpStr = Utils.getOption('t', args);
+	if (tmpStr.length() != 0) {
+	  inst = new Instances(new BufferedReader(new FileReader(tmpStr)));
+	  inst.setClassIndex(inst.numAttributes() - 1);
+	}
+	
+	tmpStr = Utils.getOption('W', args);
+	if (tmpStr.length() != 0) {
+	  options = Utils.partitionOptions(args);
+	}
+	else {
+	  tmpStr = weka.classifiers.functions.Logistic.class.getName();
+	  options = new String[0];
+	}
+	classifier = AbstractClassifier.forName(tmpStr, options);
+	
+	tmpStr = Utils.getOption('c', args);
+	if (tmpStr.length() != 0)
+	  classIndex = new SingleIndex(tmpStr);
+	else
+	  classIndex = new SingleIndex("last");
+	
+	tmpStr = Utils.getOption('C', args);
+	if (tmpStr.length() != 0)
+	  valueIndex = new SingleIndex(tmpStr);
+	else
+	  valueIndex = new SingleIndex("first");
+      }
+      
+      // compute if necessary
+      if (compute) {
+	if (classIndex != null) {
+	  classIndex.setUpper(inst.numAttributes() - 1);
+	  inst.setClassIndex(classIndex.getIndex());
+	}
+	else {
+	  inst.setClassIndex(inst.numAttributes() - 1);
+	}
+	
+	if (valueIndex != null) {
+	  valueIndex.setUpper(inst.classAttribute().numValues() - 1);
+	}
+	
+	ThresholdCurve tc = new ThresholdCurve();
+	EvaluationUtils eu = new EvaluationUtils();
+	FastVector predictions = new FastVector();
+	for (int i = 0; i < runs; i++) {
+	  eu.setSeed(seed + i);
+	  predictions.appendElements(eu.getCVPredictions(classifier, inst, folds));
+	}
+	
+	if (valueIndex != null)
+	  result = tc.getCurve(predictions, valueIndex.getIndex());
+	else
+	  result = tc.getCurve(predictions);
+      }
+      
+      // setup GUI
+      ThresholdVisualizePanel vmc = new ThresholdVisualizePanel();
+      vmc.setROCString("(Area under ROC = " + 
+	  Utils.doubleToString(ThresholdCurve.getROCArea(result), 4) + ")");
+      if (compute)     
+	vmc.setName(
+	    result.relationName() 
+	    + ". (Class value " + inst.classAttribute().value(valueIndex.getIndex()) + ")");
+      else
+	vmc.setName(
+	    result.relationName()
+	    + " (display only)");
+      PlotData2D tempd = new PlotData2D(result);
+      tempd.setPlotName(result.relationName());
+      tempd.addInstanceNumberAttribute();
+      vmc.addPlot(tempd);
+      
+      String plotName = vmc.getName(); 
+      final JFrame jf = new JFrame("Weka Classifier Visualize: "+plotName);
+      jf.setSize(500,400);
+      jf.getContentPane().setLayout(new BorderLayout());
+      
+      jf.getContentPane().add(vmc, BorderLayout.CENTER);
+      jf.addWindowListener(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  jf.dispose();
+	}
+      });
+      
+      jf.setVisible(true);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
+
+ 
+
+
+
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/Visualize.props
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/Visualize.props	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/Visualize.props	(revision 29)
@@ -0,0 +1,48 @@
+# Properties for visualization
+#
+# Version: $Revision: 5015 $
+
+# Maximum precision for numeric values
+weka.gui.visualize.precision=10
+
+# Colour for the axis in the 2D plot (can use R,G,B format)
+weka.gui.visualize.Plot2D.axisColour=green
+
+# Colour for the background of the 2D plot (can use R,G,B format)
+weka.gui.visualize.Plot2D.backgroundColour=white
+
+# The JFrame (needs to implement the interface weka.gui.visualize.InstanceInfo) 
+# for displaying Instance information.
+weka.gui.visualize.Plot2D.instanceInfoFrame=weka.gui.visualize.InstanceInfoFrame
+
+# Display the list of one dimensional attribute visualizations
+weka.gui.visualize.VisualizePanel.displayAttributeBars=true
+
+# Colour for the background of the attribute bars in the
+# AttributePanel (can use R,G,B format)
+weka.gui.visualize.AttributePanel.barColour=white
+
+# If you regularly visualize certain datasets with the same structure
+# you might want to define default x, y and colour dimensions (attributes)
+# to visualize on.
+# Just uncomment and set up the following
+# weka.gui.visualize.VisualizePanel.<relation name>.XDimension=<attribute name>
+# weka.gui.visualize.VisualizePanel.<relation name>.YDimension=<attribute name>
+# weka.gui.visualize.VisualizePanel.<relation name>.ColourDimension=<attribute name>
+# NOTE: spaces in relation names can be handled by using 
+# escape characters (java 1.2), eg. my\ first\ relation\ name
+
+# Defaults for threshold curve plots
+weka.gui.visualize.ThresholdVisualizePanel.ThresholdCurve.XDimension=False\ Positive\ Rate
+weka.gui.visualize.ThresholdVisualizePanel.ThresholdCurve.YDimension=True\ Positive\ Rate
+weka.gui.visualize.ThresholdVisualizePanel.ThresholdCurve.ColourDimension=Threshold
+
+#Defaults for cost curve plots
+weka.gui.visualize.VisualizePanel.CostCurve.XDimension=Probability\ Cost\ Function
+weka.gui.visualize.VisualizePanel.CostCurve.YDimension=Normalized\ Expected\ Cost
+weka.gui.visualize.VisualizePanel.CostCurve.ColourDimension=Threshold
+
+# Defaults for margin curve plots
+weka.gui.visualize.VisualizePanel.MarginCurve.XDimension=Margin
+weka.gui.visualize.VisualizePanel.MarginCurve.YDimension=Cumulative
+weka.gui.visualize.VisualizePanel.MarginCurve.ColourDimension=Margin
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanel.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanel.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanel.java	(revision 29)
@@ -0,0 +1,2562 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizePanel.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.gui.ExtensionFileFilter;
+import weka.gui.Logger;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.Writer;
+import java.util.Random;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.SwingConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.filechooser.FileFilter;
+
+/** 
+ * This panel allows the user to visualize a dataset (and if provided) a
+ * classifier's/clusterer's predictions in two dimensions.
+ *
+ * If the user selects a nominal attribute as the colouring attribute then
+ * each point is drawn in a colour that corresponds to the discrete value
+ * of that attribute for the instance. If the user selects a numeric
+ * attribute to colour on, then the points are coloured using a spectrum
+ * ranging from blue to red (low values to high).
+ *
+ * When a classifier's predictions are supplied they are plotted in one
+ * of two ways (depending on whether the class is nominal or numeric).<br>
+ * For nominal class: an error made by a classifier is plotted as a square
+ * in the colour corresponding to the class it predicted.<br>
+ * For numeric class: predictions are plotted as varying sized x's, where
+ * the size of the x is related to the magnitude of the error.
+ *
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 5747 $
+ */
+public class VisualizePanel
+  extends PrintablePanel {
+
+  /** for serialization */
+  private static final long serialVersionUID = 240108358588153943L;
+
+  /** Inner class to handle plotting */
+  protected class PlotPanel
+    extends PrintablePanel
+    implements Plot2DCompanion {
+
+    /** for serialization */
+    private static final long serialVersionUID = -4823674171136494204L;
+
+    /** The actual generic plotting panel */
+    protected Plot2D m_plot2D = new Plot2D();
+
+    /** The instances from the master plot */
+    protected Instances m_plotInstances=null;
+
+    /** The master plot */
+    protected PlotData2D m_originalPlot=null;
+    
+    /** Indexes of the attributes to go on the x and y axis and the attribute
+	to use for colouring and the current shape for drawing */
+    protected int m_xIndex=0;
+    protected int m_yIndex=0;
+    protected int m_cIndex=0;
+    protected int m_sIndex=0;
+
+    /**the offsets of the axes once label metrics are calculated */
+    private int m_XaxisStart=0;
+    private int m_YaxisStart=0;
+    private int m_XaxisEnd=0;
+    private int m_YaxisEnd=0;
+
+    /** True if the user is currently dragging a box. */
+    private boolean m_createShape;
+    
+    /** contains all the shapes that have been drawn for these attribs */
+    private FastVector m_shapes;
+
+    /** contains the points of the shape currently being drawn. */
+    private FastVector m_shapePoints;
+
+    /** contains the position of the mouse (used for rubberbanding). */
+    private Dimension m_newMousePos;
+
+    /** Constructor */
+    public PlotPanel() {
+      this.setBackground(m_plot2D.getBackground());
+      this.setLayout(new BorderLayout());
+      this.add(m_plot2D, BorderLayout.CENTER);
+      m_plot2D.setPlotCompanion(this);
+
+      m_createShape = false;        
+      m_shapes = null;////
+      m_shapePoints = null;
+      m_newMousePos = new Dimension();
+
+      this.addMouseListener(new MouseAdapter() {
+	  ///////      
+	  public void mousePressed(MouseEvent e) {
+	    if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {
+	      //
+	      if (m_sIndex == 0) {
+		//do nothing it will get dealt to in the clicked method
+	      }
+	      else if (m_sIndex == 1) {
+		m_createShape = true;
+		m_shapePoints = new FastVector(5);
+		m_shapePoints.addElement(new Double(m_sIndex));
+		m_shapePoints.addElement(new Double(e.getX()));
+		m_shapePoints.addElement(new Double(e.getY()));
+		m_shapePoints.addElement(new Double(e.getX()));
+		m_shapePoints.addElement(new Double(e.getY()));
+		//		Graphics g = PlotPanel.this.getGraphics();
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		g.drawRect(((Double)m_shapePoints.elementAt(1)).intValue(),
+			   ((Double)m_shapePoints.elementAt(2)).intValue(),
+			   ((Double)m_shapePoints.elementAt(3)).intValue() -
+			   ((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(4)).intValue() -
+			   ((Double)m_shapePoints.elementAt(2)).intValue());
+		g.dispose();
+	      }
+	      //System.out.println("clicked");
+	    }
+	    //System.out.println("clicked");
+	  }
+	  //////
+	  public void mouseClicked(MouseEvent e) {
+	    
+	    if ((m_sIndex == 2 || m_sIndex == 3) && 
+		(m_createShape || 
+		 (e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK)) {
+	      if (m_createShape) {
+		//then it has been started already.
+
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK &&
+                    !e.isAltDown()) {
+		  m_shapePoints.addElement(new 
+		    Double(m_plot2D.convertToAttribX(e.getX())));
+		  
+		  m_shapePoints.addElement(new 
+		    Double(m_plot2D.convertToAttribY(e.getY())));
+		  
+		  m_newMousePos.width = e.getX();
+		  m_newMousePos.height = e.getY();
+		  g.drawLine((int)Math.ceil
+			     (m_plot2D.convertToPanelX
+			      (((Double)m_shapePoints.
+				elementAt(m_shapePoints.size() - 2)).
+			       doubleValue())),
+			     
+			     (int)Math.ceil
+			     (m_plot2D.convertToPanelY
+			      (((Double)m_shapePoints.
+				elementAt(m_shapePoints.size() - 1)).
+			       doubleValue())),
+			     m_newMousePos.width, m_newMousePos.height);
+		  
+		}
+		else if (m_sIndex == 3) {
+		  //then extend the lines to infinity 
+		  //(100000 or so should be enough).
+		  //the area is selected by where the user right clicks 
+		  //the mouse button
+		  
+		  m_createShape = false;
+		  if (m_shapePoints.size() >= 5) {
+		    double cx = Math.ceil
+		      (m_plot2D.convertToPanelX
+		       (((Double)m_shapePoints.elementAt
+			 (m_shapePoints.size() - 4)).doubleValue()));
+		    
+		    double cx2 = Math.ceil
+		      (m_plot2D.convertToPanelX
+		       (((Double)m_shapePoints.elementAt
+			 (m_shapePoints.size() - 2)).doubleValue())) - 
+		      cx;
+		    
+		    cx2 *= 50000;
+		    
+		    double cy = Math.ceil
+		      (m_plot2D.
+		       convertToPanelY(((Double)m_shapePoints.
+					elementAt(m_shapePoints.size() - 3)).
+				       doubleValue()));
+		    double cy2 = Math.ceil
+		      (m_plot2D.convertToPanelY(((Double)m_shapePoints.
+					  elementAt(m_shapePoints.size() - 1)).
+					  doubleValue())) - cy;
+		    cy2 *= 50000;
+			    
+		    
+		    double cxa = Math.ceil(m_plot2D.convertToPanelX
+					   (((Double)m_shapePoints.
+					     elementAt(3)).
+					    doubleValue()));
+		    double cxa2 = Math.ceil(m_plot2D.convertToPanelX
+					    (((Double)m_shapePoints.
+					      elementAt(1)).
+					     doubleValue())) - cxa;
+		    cxa2 *= 50000;
+		    
+		    
+		    double cya = Math.ceil
+		      (m_plot2D.convertToPanelY
+		       (((Double)m_shapePoints.elementAt(4)).
+			doubleValue()));
+		    double cya2 = Math.ceil
+		      (m_plot2D.convertToPanelY
+		       (((Double)m_shapePoints.elementAt(2)).
+			doubleValue())) - cya;
+		    
+		    cya2 *= 50000;
+		    
+		    m_shapePoints.setElementAt
+		      (new Double(m_plot2D.convertToAttribX(cxa2 + cxa)), 1);
+		    
+		    m_shapePoints.setElementAt
+		      (new Double(m_plot2D.convertToAttribY(cy2 + cy)), 
+		       m_shapePoints.size() - 1);
+		    
+		    m_shapePoints.setElementAt
+		      (new Double(m_plot2D.convertToAttribX(cx2 + cx)), 
+		       m_shapePoints.size() - 2);
+		    
+		    m_shapePoints.setElementAt
+		      (new Double(m_plot2D.convertToAttribY(cya2 + cya)), 2);
+		    
+		    
+		    //determine how infinity line should be built
+		    
+		    cy = Double.POSITIVE_INFINITY;
+		    cy2 = Double.NEGATIVE_INFINITY;
+		    if (((Double)m_shapePoints.elementAt(1)).
+			doubleValue() > 
+			((Double)m_shapePoints.elementAt(3)).
+			doubleValue()) {
+		      if (((Double)m_shapePoints.elementAt(2)).
+			  doubleValue() == 
+			  ((Double)m_shapePoints.elementAt(4)).
+			  doubleValue()) {
+			cy = ((Double)m_shapePoints.elementAt(2)).
+			  doubleValue();
+		      }
+		    }
+		    if (((Double)m_shapePoints.elementAt
+			 (m_shapePoints.size() - 2)).doubleValue() > 
+			((Double)m_shapePoints.elementAt
+			 (m_shapePoints.size() - 4)).doubleValue()) {
+		      if (((Double)m_shapePoints.elementAt
+			   (m_shapePoints.size() - 3)).
+			  doubleValue() == 
+			  ((Double)m_shapePoints.elementAt
+			   (m_shapePoints.size() - 1)).doubleValue()) {
+			cy2 = ((Double)m_shapePoints.lastElement()).
+			  doubleValue();
+		      }
+		    }
+		    m_shapePoints.addElement(new Double(cy));
+		    m_shapePoints.addElement(new Double(cy2));
+		    
+		    if (!inPolyline(m_shapePoints, m_plot2D.convertToAttribX
+				    (e.getX()), 
+				    m_plot2D.convertToAttribY(e.getY()))) {
+		      Double tmp = (Double)m_shapePoints.
+			elementAt(m_shapePoints.size() - 2);
+		      m_shapePoints.setElementAt
+			(m_shapePoints.lastElement(), 
+			 m_shapePoints.size() - 2);
+		      m_shapePoints.setElementAt
+			(tmp, m_shapePoints.size() - 1);
+		    }
+		    
+		    if (m_shapes == null) {
+		      m_shapes = new FastVector(4);
+		    }
+		    m_shapes.addElement(m_shapePoints);
+
+		    m_submit.setText("Submit");
+		    m_submit.setActionCommand("Submit");
+		    
+		    m_submit.setEnabled(true);
+		  }
+		  
+		  m_shapePoints = null;
+		  PlotPanel.this.repaint();
+		  
+		}
+		else {
+		  //then close the shape
+		  m_createShape = false;
+		  if (m_shapePoints.size() >= 7) {
+		    m_shapePoints.addElement(m_shapePoints.elementAt(1));
+		    m_shapePoints.addElement(m_shapePoints.elementAt(2));
+		    if (m_shapes == null) {
+		      m_shapes = new FastVector(4);
+		    }
+		    m_shapes.addElement(m_shapePoints);
+			   
+		    m_submit.setText("Submit");
+		    m_submit.setActionCommand("Submit");
+		    
+		    m_submit.setEnabled(true);
+		  }
+		  m_shapePoints = null;
+		  PlotPanel.this.repaint();
+		}
+		g.dispose();
+		//repaint();
+	      }
+	      else if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {
+		//then this is the first point
+		m_createShape = true;
+		m_shapePoints = new FastVector(17);
+		m_shapePoints.addElement(new Double(m_sIndex));
+		m_shapePoints.addElement(new 
+		  Double(m_plot2D.convertToAttribX(e.getX()))); //the new point
+		m_shapePoints.addElement(new 
+		  Double(m_plot2D.convertToAttribY(e.getY())));
+		m_newMousePos.width = e.getX();      //the temp mouse point
+		m_newMousePos.height = e.getY();
+
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		g.drawLine((int)Math.ceil
+			   (m_plot2D.convertToPanelX(((Double)m_shapePoints.
+					     elementAt(1)).doubleValue())),
+			   (int)Math.ceil
+			   (m_plot2D.convertToPanelY(((Double)m_shapePoints.
+					     elementAt(2)).doubleValue())),
+			   m_newMousePos.width, m_newMousePos.height);
+		g.dispose();
+	      }
+	    }
+	    else {
+	      if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == 
+		  InputEvent.BUTTON1_MASK) {
+		
+		m_plot2D.searchPoints(e.getX(),e.getY(), false);
+	      } else {
+		m_plot2D.searchPoints(e.getX(), e.getY(), true);
+	      }
+	    }
+	  }
+	  
+	  /////////             
+	  public void mouseReleased(MouseEvent e) {
+
+	    if (m_createShape) {
+	      if (((Double)m_shapePoints.elementAt(0)).intValue() == 1) {
+		m_createShape = false;
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		g.drawRect(((Double)m_shapePoints.elementAt(1)).
+			   intValue(), 
+			   ((Double)m_shapePoints.elementAt(2)).intValue(),
+			   ((Double)m_shapePoints.elementAt(3)).intValue() -
+			   ((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(4)).intValue() -
+			   ((Double)m_shapePoints.elementAt(2)).intValue());
+		
+		g.dispose();
+		if (checkPoints(((Double)m_shapePoints.elementAt(1)).
+				doubleValue(), 
+				((Double)m_shapePoints.elementAt(2)).
+				doubleValue()) &&
+		    checkPoints(((Double)m_shapePoints.elementAt(3)).
+				doubleValue(), 
+				((Double)m_shapePoints.elementAt(4)).
+				doubleValue())) {
+		  //then the points all land on the screen
+		  //now do special check for the rectangle
+		  if (((Double)m_shapePoints.elementAt(1)).doubleValue() <
+		      ((Double)m_shapePoints.elementAt(3)).doubleValue() 
+		      &&
+		      ((Double)m_shapePoints.elementAt(2)).doubleValue() <
+		      ((Double)m_shapePoints.elementAt(4)).doubleValue()) {
+		    //then the rectangle is valid
+		    if (m_shapes == null) {
+		      m_shapes = new FastVector(2);
+		    }
+		    m_shapePoints.setElementAt(new 
+		      Double(m_plot2D.convertToAttribX(((Double)m_shapePoints.
+					       elementAt(1)).
+					      doubleValue())), 1);
+		    m_shapePoints.setElementAt(new 
+		      Double(m_plot2D.convertToAttribY(((Double)m_shapePoints.
+					       elementAt(2)).
+					      doubleValue())), 2);
+		    m_shapePoints.setElementAt(new 
+		      Double(m_plot2D.convertToAttribX(((Double)m_shapePoints.
+					       elementAt(3)).
+					      doubleValue())), 3);
+		    m_shapePoints.setElementAt(new 
+		      Double(m_plot2D.convertToAttribY(((Double)m_shapePoints.
+					       elementAt(4)).
+					      doubleValue())), 4);
+		    
+		    m_shapes.addElement(m_shapePoints);
+		    
+		    m_submit.setText("Submit");
+		    m_submit.setActionCommand("Submit");
+		    
+		    m_submit.setEnabled(true);
+
+		    PlotPanel.this.repaint();
+		  }
+		}
+		m_shapePoints = null;
+	      }
+	    }
+	  }
+	});
+      
+      this.addMouseMotionListener(new MouseMotionAdapter() {
+	  public void mouseDragged(MouseEvent e) {
+	    //check if the user is dragging a box
+	    if (m_createShape) {
+	      if (((Double)m_shapePoints.elementAt(0)).intValue() == 1) {
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		g.drawRect(((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(2)).intValue(),
+			   ((Double)m_shapePoints.elementAt(3)).intValue() -
+			   ((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(4)).intValue() -
+			   ((Double)m_shapePoints.elementAt(2)).intValue());
+		
+		m_shapePoints.setElementAt(new Double(e.getX()), 3);
+		m_shapePoints.setElementAt(new Double(e.getY()), 4);
+		
+		g.drawRect(((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(2)).intValue(),
+			   ((Double)m_shapePoints.elementAt(3)).intValue() -
+			   ((Double)m_shapePoints.elementAt(1)).intValue(), 
+			   ((Double)m_shapePoints.elementAt(4)).intValue() -
+			   ((Double)m_shapePoints.elementAt(2)).intValue());
+		g.dispose();
+	      }
+	    }
+	  }
+	  
+	  public void mouseMoved(MouseEvent e) {
+	    if (m_createShape) {
+	      if (((Double)m_shapePoints.elementAt(0)).intValue() == 2 || 
+		  ((Double)m_shapePoints.elementAt(0)).intValue() == 3) {
+		Graphics g = m_plot2D.getGraphics();
+		g.setColor(Color.black);
+		g.setXORMode(Color.white);
+		g.drawLine((int)Math.ceil(m_plot2D.convertToPanelX
+					  (((Double)m_shapePoints.elementAt
+					    (m_shapePoints.size() - 2)).
+					   doubleValue())),
+			   (int)Math.ceil(m_plot2D.convertToPanelY
+					  (((Double)m_shapePoints.elementAt
+					    (m_shapePoints.size() - 1)).
+					   doubleValue())),
+			   m_newMousePos.width, m_newMousePos.height);
+		
+		m_newMousePos.width = e.getX();
+		m_newMousePos.height = e.getY();
+		
+		g.drawLine((int)Math.ceil(m_plot2D.convertToPanelX
+					  (((Double)m_shapePoints.elementAt
+					    (m_shapePoints.size() - 2)).
+					   doubleValue())),
+			   (int)Math.ceil(m_plot2D.convertToPanelY
+					  (((Double)m_shapePoints.elementAt
+					    (m_shapePoints.size() - 1)).
+					   doubleValue())),
+			   m_newMousePos.width, m_newMousePos.height);
+		g.dispose();
+	      }
+	    }
+	  }
+	});
+      
+      m_submit.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	 
+	    if (e.getActionCommand().equals("Submit")) {
+	      if (m_splitListener != null && m_shapes != null) {
+		//then send the split to the listener
+		Instances sub_set1 = new Instances(m_plot2D.getMasterPlot().
+						   m_plotInstances, 500);
+		Instances sub_set2 = new Instances(m_plot2D.getMasterPlot().
+						   m_plotInstances, 500);
+		
+		if (m_plot2D.getMasterPlot().
+		    m_plotInstances != null) {
+		  
+		  for (int noa = 0 ; noa < m_plot2D.getMasterPlot().
+			 m_plotInstances.numInstances(); noa++) {
+		    if (!m_plot2D.getMasterPlot().
+			m_plotInstances.instance(noa).isMissing(m_xIndex) &&
+			!m_plot2D.getMasterPlot().
+			m_plotInstances.instance(noa).isMissing(m_yIndex)){
+		      
+		      if (inSplit(m_plot2D.getMasterPlot().
+				  m_plotInstances.instance(noa))) {
+			sub_set1.add(m_plot2D.getMasterPlot().
+				     m_plotInstances.instance(noa));
+		      }
+		      else {
+			sub_set2.add(m_plot2D.getMasterPlot().
+				     m_plotInstances.instance(noa));
+		      }
+		    }
+		  }
+		  FastVector tmp = m_shapes;
+		  cancelShapes();
+		  m_splitListener.userDataEvent(new 
+		    VisualizePanelEvent(tmp, sub_set1, sub_set2, m_xIndex, 
+					m_yIndex));
+		}
+	      }
+	      else if (m_shapes != null && 
+		       m_plot2D.getMasterPlot().m_plotInstances != null) { 
+		Instances sub_set1 = new Instances(m_plot2D.getMasterPlot().
+						   m_plotInstances, 500);
+		int count = 0;
+		for (int noa = 0 ; noa < m_plot2D.getMasterPlot().
+		       m_plotInstances.numInstances(); noa++) {
+		  if (inSplit(m_plot2D.getMasterPlot().
+			      m_plotInstances.instance(noa))) {
+		    sub_set1.add(m_plot2D.getMasterPlot().
+				 m_plotInstances.instance(noa));
+		    count++;
+		  }
+		  
+		}
+
+		int [] nSizes = null;
+		int [] nTypes = null;
+		int x = m_xIndex;
+		int y = m_yIndex;
+
+		if (m_originalPlot == null) {
+		  //this sets these instances as the instances 
+		  //to go back to.
+		  m_originalPlot = m_plot2D.getMasterPlot();
+		}
+
+		if (count > 0) {
+		  nTypes = new int[count];
+		  nSizes = new int[count];
+		  count = 0;
+		  for (int noa = 0; noa < m_plot2D.getMasterPlot().
+			 m_plotInstances.numInstances(); 
+		       noa++) {
+		    if (inSplit(m_plot2D.getMasterPlot().
+				m_plotInstances.instance(noa))) {
+
+		      nTypes[count] = m_plot2D.getMasterPlot().
+			m_shapeType[noa];
+		      nSizes[count] = m_plot2D.getMasterPlot().
+			m_shapeSize[noa];
+		      count++;
+		    }
+		  }
+		}
+		cancelShapes();
+
+		PlotData2D newPlot = new PlotData2D(sub_set1);
+
+		try {
+		  newPlot.setShapeSize(nSizes);
+		  newPlot.setShapeType(nTypes);
+		
+		  m_plot2D.removeAllPlots();
+		  
+		  VisualizePanel.this.addPlot(newPlot);
+		} catch (Exception ex) {
+		  System.err.println(ex);
+		  ex.printStackTrace();
+		}
+
+		try {
+		  VisualizePanel.this.setXIndex(x);
+		  VisualizePanel.this.setYIndex(y);
+		} catch(Exception er) {
+		  System.out.println("Error : " + er);
+		  //  System.out.println("Part of user input so had to" +
+		  //		 " catch here");
+		}
+	      }
+	    }
+	    else if (e.getActionCommand().equals("Reset")) {
+	      int x = m_xIndex;
+	      int y = m_yIndex;
+
+	      m_plot2D.removeAllPlots();
+	      try {
+		VisualizePanel.this.addPlot(m_originalPlot);
+	      } catch (Exception ex) {
+		System.err.println(ex);
+		ex.printStackTrace();
+	      }
+
+	      try {
+		VisualizePanel.this.setXIndex(x);
+		VisualizePanel.this.setYIndex(y);
+	      } catch(Exception er) {
+		System.out.println("Error : " + er);
+	      }
+	    }
+	  }  
+	});
+
+      m_cancel.addActionListener(new ActionListener() {
+	  public void actionPerformed(ActionEvent e) {
+	    cancelShapes();
+	    PlotPanel.this.repaint();
+	  }
+	});
+      ////////////
+    }
+
+    /**
+     * Removes all the plots.
+     */
+    public void removeAllPlots() {
+      m_plot2D.removeAllPlots();
+      m_legendPanel.setPlotList(m_plot2D.getPlots());
+    }
+    
+    /**
+     * @return The FastVector containing all the shapes.
+     */
+    public FastVector getShapes() {
+      
+      return m_shapes;
+    }
+    
+    /**
+     * Sets the list of shapes to empty and also cancels
+     * the current shape being drawn (if applicable).
+     */
+    public void cancelShapes() {
+       
+      if (m_splitListener == null) {
+	m_submit.setText("Reset");
+	m_submit.setActionCommand("Reset");
+
+	if (m_originalPlot == null || 
+	    m_originalPlot.m_plotInstances == m_plotInstances) {
+	  m_submit.setEnabled(false);
+	}
+	else {
+	  m_submit.setEnabled(true);
+	}
+      }
+      else {
+	m_submit.setEnabled(false);
+      }
+      
+      m_createShape = false;
+      m_shapePoints = null;
+      m_shapes = null;
+      this.repaint();
+    }
+
+    /**
+     * This can be used to set the shapes that should appear.
+     * @param v The list of shapes.
+     */
+    public void setShapes(FastVector v) {
+      //note that this method should be fine for doubles,
+      //but anything else that uses something other than doubles 
+      //(or uneditable objects) could have unsafe copies.
+      if (v != null) {
+	FastVector temp;
+	m_shapes = new FastVector(v.size());
+	for (int noa = 0; noa < v.size(); noa++) {
+	  temp = new FastVector(((FastVector)v.elementAt(noa)).size());
+	  m_shapes.addElement(temp);
+	  for (int nob = 0; nob < ((FastVector)v.elementAt(noa)).size()
+		 ; nob++) {
+	    
+	    temp.addElement(((FastVector)v.elementAt(noa)).elementAt(nob));
+	    
+	  }
+	}
+      }
+      else {
+	m_shapes = null;
+      }
+      this.repaint();
+    }
+    
+    /** 
+     * This will check the values of the screen points passed and make sure 
+     * that they land on the screen
+     * @param x1 The x coord.
+     * @param y1 The y coord.
+     * @return true if the point would land on the screen
+     */
+    private boolean checkPoints(double x1, double y1) {
+      if (x1 < 0 || x1 > this.getSize().width || y1 < 0 
+	  || y1 > this.getSize().height) {
+	return false;
+      }
+      return true;
+    }
+    
+    /**
+     * This will check if an instance is inside or outside of the current
+     * shapes.
+     * @param i The instance to check.
+     * @return True if 'i' falls inside the shapes, false otherwise.
+     */
+    public boolean inSplit(Instance i) {
+      //this will check if the instance lies inside the shapes or not
+      
+      if (m_shapes != null) {
+	FastVector stmp;
+	double x1, y1, x2, y2;
+	for (int noa = 0; noa < m_shapes.size(); noa++) {
+	  stmp = (FastVector)m_shapes.elementAt(noa);
+	  if (((Double)stmp.elementAt(0)).intValue() == 1) {
+	    //then rectangle
+	    x1 = ((Double)stmp.elementAt(1)).doubleValue();
+	    y1 = ((Double)stmp.elementAt(2)).doubleValue();
+	    x2 = ((Double)stmp.elementAt(3)).doubleValue();
+	    y2 = ((Double)stmp.elementAt(4)).doubleValue();
+	    if (i.value(m_xIndex) >= x1 && i.value(m_xIndex) <= x2 &&
+		i.value(m_yIndex) <= y1 && i.value(m_yIndex) >= y2) {
+	      //then is inside split so return true;
+	      return true;
+	    }
+	  }
+	  else if (((Double)stmp.elementAt(0)).intValue() == 2) {
+	    //then polygon
+	    if (inPoly(stmp, i.value(m_xIndex), i.value(m_yIndex))) {
+	      return true;
+	    }
+	  }
+	  else if (((Double)stmp.elementAt(0)).intValue() == 3) {
+	    //then polyline
+	    if (inPolyline(stmp, i.value(m_xIndex), i.value(m_yIndex))) {
+	      return true;
+	    }
+	  }
+	}
+      }
+      return false;
+    }
+    
+    /**
+     * Checks to see if the coordinate passed is inside the ployline
+     * passed, Note that this is done using attribute values and not there
+     * respective screen values.
+     * @param ob The polyline.
+     * @param x The x coord.
+     * @param y The y coord.
+     * @return True if it falls inside the polyline, false otherwise.
+     */
+    private boolean inPolyline(FastVector ob, double x, double y) {
+      //this works similar to the inPoly below except that
+      //the first and last lines are treated as extending infinite in one 
+      //direction and 
+      //then infinitly in the x dirction their is a line that will 
+      //normaly be infinite but
+      //can be finite in one or both directions
+      
+      int countx = 0;
+      double vecx, vecy;
+      double change;
+      double x1, y1, x2, y2;
+      
+      for (int noa = 1; noa < ob.size() - 4; noa+= 2) {
+	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
+	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
+	x1 = ((Double)ob.elementAt(noa)).doubleValue();
+	x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
+	
+	//System.err.println(y1 + " " + y2 + " " + x1 + " " + x2);
+	vecy = y2 - y1;
+	vecx = x2 - x1;
+	if (noa == 1 && noa == ob.size() - 6) {
+	  //then do special test first and last edge
+	  if (vecy != 0) {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersection
+	      countx++;
+	    }
+	  }
+	}
+	else if (noa == 1) {
+	  if ((y < y2 && vecy > 0) || (y > y2 && vecy < 0)) {
+	    //now just determine intersection or not
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersection on horiz
+	      countx++;
+	    }
+	  }
+	}
+	else if (noa == ob.size() - 6) {
+	  //then do special test on last edge
+	  if ((y <= y1 && vecy < 0) || (y >= y1 && vecy > 0)) {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      countx++;
+	    }
+	  }
+	}
+	else if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
+	  //then continue tests.
+	  if (vecy == 0) {
+	    //then lines are parallel stop tests in 
+	    //ofcourse it should never make it this far
+	  }
+	  else {
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then intersects on horiz
+	      countx++;
+	    }
+	  }
+	}
+      }
+      
+      //now check for intersection with the infinity line
+      y1 = ((Double)ob.elementAt(ob.size() - 2)).doubleValue();
+      y2 = ((Double)ob.elementAt(ob.size() - 1)).doubleValue();
+      
+      if (y1 > y2) {
+	//then normal line
+	if (y1 >= y && y > y2) {
+	  countx++;
+	}
+      }
+      else {
+	//then the line segment is inverted
+	if (y1 >= y || y > y2) {
+	  countx++;
+	}
+      }
+      
+      if ((countx % 2) == 1) {
+	return true;
+      }
+      else {
+	return false;
+      }
+    }
+
+
+    /**
+     * This checks to see if The coordinate passed is inside
+     * the polygon that was passed.
+     * @param ob The polygon.
+     * @param x The x coord.
+     * @param y The y coord.
+     * @return True if the coordinate is in the polygon, false otherwise.
+     */
+    private boolean inPoly(FastVector ob, double x, double y) {
+      //brief on how this works
+      //it draws a line horizontally from the point to the right (infinitly)
+      //it then sees how many lines of the polygon intersect this, 
+      //if it is even then the point is
+      // outside the polygon if it's odd then it's inside the polygon
+      int count = 0;
+      double vecx, vecy;
+      double change;
+      double x1, y1, x2, y2;
+      for (int noa = 1; noa < ob.size() - 2; noa += 2) {
+	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
+	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
+	if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
+	  //then continue tests.
+	  vecy = y2 - y1;
+	  if (vecy == 0) {
+	    //then lines are parallel stop tests for this line
+	  }
+	  else {
+	    x1 = ((Double)ob.elementAt(noa)).doubleValue();
+	    x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
+	    vecx = x2 - x1;
+	    change = (y - y1) / vecy;
+	    if (vecx * change + x1 >= x) {
+	      //then add to count as an intersected line
+	      count++;
+	    }
+	  }
+	}
+      }
+      if ((count % 2) == 1) {
+	//then lies inside polygon
+	//System.out.println("in");
+	return true;
+      }
+      else {
+	//System.out.println("out");
+	return false;
+      }
+      //System.out.println("WHAT?!?!?!?!!?!??!?!");
+      //return false;
+    }
+
+    /**
+     * Set level of jitter and repaint the plot using the new jitter value
+     * @param j the level of jitter
+     */
+    public void setJitter(int j) {
+      m_plot2D.setJitter(j);
+    }
+
+    /**
+     * Set the index of the attribute to go on the x axis
+     * @param x the index of the attribute to use on the x axis
+     */
+    public void setXindex(int x) {
+
+      // this just ensures that the shapes get disposed of 
+      //if the attribs change
+      if (x != m_xIndex) {
+	cancelShapes();
+      }
+      m_xIndex = x;
+      m_plot2D.setXindex(x);
+      if (m_showAttBars) {
+	m_attrib.setX(x);
+      }
+      //      this.repaint();
+    }
+    
+    /**
+     * Set the index of the attribute to go on the y axis
+     * @param y the index of the attribute to use on the y axis
+     */
+    public void setYindex(int y) {
+    
+      // this just ensures that the shapes get disposed of 
+      //if the attribs change
+      if (y != m_yIndex) {
+	cancelShapes();
+      }
+      m_yIndex = y;
+      m_plot2D.setYindex(y);
+      if (m_showAttBars) {
+	m_attrib.setY(y);
+      }
+      //      this.repaint();
+    }
+
+    /**
+     * Set the index of the attribute to use for colouring
+     * @param c the index of the attribute to use for colouring
+     */
+    public void setCindex(int c) {
+      m_cIndex = c;
+      m_plot2D.setCindex(c);
+      if (m_showAttBars) {
+	m_attrib.setCindex(c, m_plot2D.getMaxC(), m_plot2D.getMinC());
+      }
+      m_classPanel.setCindex(c);
+      this.repaint();
+    }
+
+    /**
+     * Set the index of the attribute to use for the shape.
+     * @param s the index of the attribute to use for the shape
+     */
+    public void setSindex(int s) {
+      if (s != m_sIndex) {
+	m_shapePoints = null;
+	m_createShape = false;
+      }
+      m_sIndex = s;
+      this.repaint();
+    }
+    
+    /**
+     * Clears all existing plots and sets a new master plot
+     * @param newPlot the new master plot
+     * @exception Exception if plot could not be added
+     */
+    public void setMasterPlot(PlotData2D newPlot) throws Exception {
+      m_plot2D.removeAllPlots();
+      this.addPlot(newPlot);
+    }
+
+    /**
+     * Adds a plot. If there are no plots so far this plot becomes
+     * the master plot and, if it has a custom colour defined then
+     * the class panel is removed.
+     * @param newPlot the plot to add.
+     * @exception Exception if plot could not be added
+     */
+    public void addPlot(PlotData2D newPlot) throws Exception {
+      if (m_plot2D.getPlots().size() == 0) {
+	m_plot2D.addPlot(newPlot);
+	if (m_plotSurround.getComponentCount() > 1 && 
+	    m_plotSurround.getComponent(1) == m_attrib &&
+	    m_showAttBars) {
+	  try {
+	    m_attrib.setInstances(newPlot.m_plotInstances);
+	    m_attrib.setCindex(0);m_attrib.setX(0); m_attrib.setY(0);
+	  } catch (Exception ex) {
+	    // more attributes than the panel can handle?
+	    // Due to hard coded constraints in GridBagLayout
+	    m_plotSurround.remove(m_attrib);
+	    System.err.println("Warning : data contains more attributes "
+			       +"than can be displayed as attribute bars.");
+	    if (m_Log != null) {
+	      m_Log.logMessage("Warning : data contains more attributes "
+			       +"than can be displayed as attribute bars.");
+	    }
+	  }
+	} else if (m_showAttBars) {
+	  try {
+	    m_attrib.setInstances(newPlot.m_plotInstances);
+	    m_attrib.setCindex(0);m_attrib.setX(0); m_attrib.setY(0);
+	    GridBagConstraints constraints = new GridBagConstraints();
+	    constraints.fill = GridBagConstraints.BOTH;
+	    constraints.insets = new Insets(0, 0, 0, 0);
+	    constraints.gridx=4;constraints.gridy=0;constraints.weightx=1;
+	    constraints.gridwidth=1;constraints.gridheight=1;
+	    constraints.weighty=5;
+	    m_plotSurround.add(m_attrib, constraints);
+	  } catch (Exception ex) {
+	    System.err.println("Warning : data contains more attributes "
+			       +"than can be displayed as attribute bars.");
+	    if (m_Log != null) {
+	      m_Log.logMessage("Warning : data contains more attributes "
+			       +"than can be displayed as attribute bars.");
+	    }
+	  }
+	}
+	m_classPanel.setInstances(newPlot.m_plotInstances);
+
+	plotReset(newPlot.m_plotInstances, newPlot.getCindex());
+	if (newPlot.m_useCustomColour && m_showClassPanel) {
+	  VisualizePanel.this.remove(m_classSurround);
+	  switchToLegend();
+	  m_legendPanel.setPlotList(m_plot2D.getPlots());
+	  m_ColourCombo.setEnabled(false);
+	}
+      } else  {
+	if (!newPlot.m_useCustomColour && m_showClassPanel) {
+	  VisualizePanel.this.add(m_classSurround, BorderLayout.SOUTH);
+	  m_ColourCombo.setEnabled(true);
+	}
+	if (m_plot2D.getPlots().size() == 1) {
+	  switchToLegend();
+	}
+	m_plot2D.addPlot(newPlot);
+	m_legendPanel.setPlotList(m_plot2D.getPlots());
+      }
+    }
+
+    /**
+     * Remove the attibute panel and replace it with the legend panel
+     */
+    protected void switchToLegend() {
+
+      if (m_plotSurround.getComponentCount() > 1 && 
+	  m_plotSurround.getComponent(1) == m_attrib) {
+	m_plotSurround.remove(m_attrib);
+      }
+	
+      if (m_plotSurround.getComponentCount() > 1 &&
+	  m_plotSurround.getComponent(1) == m_legendPanel) {
+	return;
+      }
+
+      GridBagConstraints constraints = new GridBagConstraints();
+      constraints.fill = GridBagConstraints.BOTH;
+      constraints.insets = new Insets(0, 0, 0, 0);
+      constraints.gridx=4;constraints.gridy=0;constraints.weightx=1;
+      constraints.gridwidth=1;constraints.gridheight=1;
+      constraints.weighty=5;
+      m_plotSurround.add(m_legendPanel, constraints);
+      setSindex(0);
+      m_ShapeCombo.setEnabled(false);
+    }
+    
+    protected void switchToBars() {
+      if (m_plotSurround.getComponentCount() > 1 && 
+          m_plotSurround.getComponent(1) == m_legendPanel) {
+        m_plotSurround.remove(m_legendPanel);
+      }
+      
+      if (m_plotSurround.getComponentCount() > 1 &&
+          m_plotSurround.getComponent(1) == m_attrib) {
+        return;
+      }
+      
+      if (m_showAttBars) {
+        try {
+          m_attrib.setInstances(m_plot2D.getMasterPlot().m_plotInstances);
+          m_attrib.setCindex(0);m_attrib.setX(0); m_attrib.setY(0);
+          GridBagConstraints constraints = new GridBagConstraints();
+          constraints.fill = GridBagConstraints.BOTH;
+          constraints.insets = new Insets(0, 0, 0, 0);
+          constraints.gridx=4;constraints.gridy=0;constraints.weightx=1;
+          constraints.gridwidth=1;constraints.gridheight=1;
+          constraints.weighty=5;
+          m_plotSurround.add(m_attrib, constraints);
+        } catch (Exception ex) {
+          System.err.println("Warning : data contains more attributes "
+                             +"than can be displayed as attribute bars.");
+          if (m_Log != null) {
+            m_Log.logMessage("Warning : data contains more attributes "
+                             +"than can be displayed as attribute bars.");
+          }
+        }
+      }
+    }
+
+    /**
+     * Reset the visualize panel's buttons and the plot panels instances
+     * 
+     * @param inst	the data
+     * @param cIndex	the color index
+     */
+    private void plotReset(Instances inst, int cIndex) {
+      if (m_splitListener == null) {
+	m_submit.setText("Reset");
+	m_submit.setActionCommand("Reset");
+	//if (m_origInstances == null || m_origInstances == inst) {
+	if (m_originalPlot == null || m_originalPlot.m_plotInstances == inst) {
+	  m_submit.setEnabled(false);
+	}
+	else {
+	  m_submit.setEnabled(true);
+	}
+      } 
+      else {
+	m_submit.setEnabled(false);
+      }
+
+      m_plotInstances = inst;
+      if (m_splitListener != null) {
+	m_plotInstances.randomize(new Random());
+      }
+      m_xIndex=0;
+      m_yIndex=0;
+      m_cIndex=cIndex;
+      cancelShapes();
+    }
+
+    /**
+     * Set a list of colours to use for plotting points
+     * @param cols a list of java.awt.Colors
+     */
+    public void setColours(FastVector cols) {
+      m_plot2D.setColours(cols);
+      m_colorList = cols;
+    }
+    
+    /**
+     * This will draw the shapes created onto the panel.
+     * For best visual, this should be the first thing to be drawn
+     * (and it currently is).
+     * @param gx The graphics context.
+     */
+    private void drawShapes(Graphics gx) {
+      //FastVector tmp = m_plot.getShapes();
+      
+      if (m_shapes != null) {
+	FastVector stmp;
+	int x1, y1, x2, y2;
+	for (int noa = 0; noa < m_shapes.size(); noa++) {
+	  stmp = (FastVector)m_shapes.elementAt(noa);
+	  if (((Double)stmp.elementAt(0)).intValue() == 1) {
+	    //then rectangle
+	    x1 = (int)m_plot2D.convertToPanelX(((Double)stmp.elementAt(1)).
+				      doubleValue());
+	    y1 = (int)m_plot2D.convertToPanelY(((Double)stmp.elementAt(2)).
+				      doubleValue());
+	    x2 = (int)m_plot2D.convertToPanelX(((Double)stmp.elementAt(3)).
+				      doubleValue());
+	    y2 = (int)m_plot2D.convertToPanelY(((Double)stmp.elementAt(4)).
+				      doubleValue());
+	    
+	    gx.setColor(Color.gray);
+	    gx.fillRect(x1, y1, x2 - x1, y2 - y1);
+	    gx.setColor(Color.black);
+	    gx.drawRect(x1, y1, x2 - x1, y2 - y1);
+	    
+	  }
+	  else if (((Double)stmp.elementAt(0)).intValue() == 2) {
+	    //then polygon
+	    int[] ar1, ar2;
+	    ar1 = getXCoords(stmp);
+	    ar2 = getYCoords(stmp);
+	    gx.setColor(Color.gray);
+	    gx.fillPolygon(ar1, ar2, (stmp.size() - 1) / 2); 
+	    gx.setColor(Color.black);
+	    gx.drawPolyline(ar1, ar2, (stmp.size() - 1) / 2);
+	  }
+	  else if (((Double)stmp.elementAt(0)).intValue() == 3) {
+	    //then polyline
+	    int[] ar1, ar2;
+	    FastVector tmp = makePolygon(stmp);
+	    ar1 = getXCoords(tmp);
+	    ar2 = getYCoords(tmp);
+	    
+	    gx.setColor(Color.gray);
+	    gx.fillPolygon(ar1, ar2, (tmp.size() - 1) / 2);
+	    gx.setColor(Color.black);
+	    gx.drawPolyline(ar1, ar2, (tmp.size() - 1) / 2);
+	  }
+	}
+      }
+      
+      if (m_shapePoints != null) {
+	//then the current image needs to be refreshed
+	if (((Double)m_shapePoints.elementAt(0)).intValue() == 2 ||
+	    ((Double)m_shapePoints.elementAt(0)).intValue() == 3) {
+	  gx.setColor(Color.black);
+	  gx.setXORMode(Color.white);
+	  int[] ar1, ar2;
+	  ar1 = getXCoords(m_shapePoints);
+	  ar2 = getYCoords(m_shapePoints);
+	  gx.drawPolyline(ar1, ar2, (m_shapePoints.size() - 1) / 2);
+	  m_newMousePos.width = (int)Math.ceil
+	    (m_plot2D.convertToPanelX(((Double)m_shapePoints.elementAt
+			      (m_shapePoints.size() - 2)).doubleValue()));
+	  
+	  m_newMousePos.height = (int)Math.ceil
+	    (m_plot2D.convertToPanelY(((Double)m_shapePoints.elementAt
+			      (m_shapePoints.size() - 1)).doubleValue()));
+	  
+	  gx.drawLine((int)Math.ceil
+		     (m_plot2D.convertToPanelX(((Double)m_shapePoints.elementAt
+						(m_shapePoints.size() - 2)).
+					       doubleValue())),
+		      (int)Math.ceil(m_plot2D.convertToPanelY
+				     (((Double)m_shapePoints.elementAt
+				       (m_shapePoints.size() - 1)).
+				      doubleValue())),
+		      m_newMousePos.width, m_newMousePos.height);
+	  gx.setPaintMode();
+	}
+      }
+    }
+    
+    /**
+     * This is called for polylines to see where there two lines that
+     * extend to infinity cut the border of the view.
+     * @param x1 an x point along the line
+     * @param y1 the accompanying y point.
+     * @param x2 The x coord of the end point of the line.
+     * @param y2 The y coord of the end point of the line.
+     * @param x 0 or the width of the border line if it has one.
+     * @param y 0 or the height of the border line if it has one.
+     * @param offset The offset for the border line (either for x or y
+     * dependant on which one doesn't change).
+     * @return double array that contains the coordinate for the point 
+     * that the polyline cuts the border (which ever side that may be).
+     */
+    private double[] lineIntersect(double x1, double y1, double x2, double y2, 
+				   double x, double y, double offset) {
+      //the first 4 params are thestart and end points of a line
+      //the next param is either 0 for no change in x or change in x, 
+      //the next param is the same for y
+      //the final 1 is the offset for either x or y (which ever has no change)
+      double xval;
+      double yval;
+      double xn = -100, yn = -100;
+      double change;
+      if (x == 0) {
+	if ((x1 <= offset && offset < x2) || (x1 >= offset && offset > x2)) {
+	  //then continue
+	  xval = x1 - x2;
+	  change = (offset - x2) / xval;
+	  yn = (y1 - y2) * change + y2;
+	  if (0 <= yn && yn <= y) {
+	    //then good
+	    xn = offset;
+	  }
+	  else {
+	    //no intersect
+	    xn = -100;
+	  }
+	}
+      }
+      else if (y == 0) {
+	if ((y1 <= offset && offset < y2) || (y1 >= offset && offset > y2)) {
+	  //the continue
+	  yval = (y1 - y2);
+	  change = (offset - y2) / yval;
+	  xn = (x1 - x2) * change + x2;
+	  if (0 <= xn && xn <= x) {
+	    //then good
+	    yn = offset;
+	  }
+	  else {
+	    xn = -100;
+	  }
+	}
+      }
+      double[] ret = new double[2];
+      ret[0] = xn;
+      ret[1] = yn;
+      return ret;
+    }
+
+
+    /**
+     * This will convert a polyline to a polygon for drawing purposes
+     * So that I can simply use the polygon drawing function.
+     * @param v The polyline to convert.
+     * @return A FastVector containing the polygon.
+     */
+    private FastVector makePolygon(FastVector v) {
+      FastVector building = new FastVector(v.size() + 10);
+      double x1, y1, x2, y2;
+      int edge1 = 0, edge2 = 0;
+      for (int noa = 0; noa < v.size() - 2; noa++) {
+	building.addElement(new Double(((Double)v.elementAt(noa)).
+				       doubleValue()));
+      }
+      
+      //now clip the lines
+      double[] new_coords;
+      //note lineIntersect , expects the values to have been converted to 
+      //screen coords
+      //note the first point passed is the one that gets shifted.
+      x1 = m_plot2D.convertToPanelX(((Double)v.elementAt(1)).doubleValue());
+      y1 = m_plot2D.convertToPanelY(((Double)v.elementAt(2)).doubleValue());
+      x2 = m_plot2D.convertToPanelX(((Double)v.elementAt(3)).doubleValue());
+      y2 = m_plot2D.convertToPanelY(((Double)v.elementAt(4)).doubleValue());
+
+      if (x1 < 0) {
+	//test left
+	new_coords = lineIntersect(x1, y1, x2, y2, 0, this.getHeight(), 0);
+	edge1 = 0;
+	if (new_coords[0] < 0) {
+	  //then not left
+	  if (y1 < 0) {
+	    //test top
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	    edge1 = 1;
+	  }
+	  else {
+	    //test bottom
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				       this.getHeight());
+	    edge1 = 3;
+	  }
+	}
+      }
+      else if (x1 > this.getWidth()) {
+	//test right
+	new_coords = lineIntersect(x1, y1, x2, y2, 0, this.getHeight(), 
+				   this.getWidth());
+	edge1 = 2;
+	if (new_coords[0] < 0) {
+	  //then not right
+	  if (y1 < 0) {
+	    //test top
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	    edge1 = 1;
+	  }
+	  else {
+	    //test bottom
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				       this.getHeight());
+	    edge1 = 3;
+	  }
+	}
+      }
+      else if (y1 < 0) {
+	//test top
+	new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	edge1 = 1;
+      }
+      else {
+	//test bottom
+	new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				   this.getHeight());
+	edge1 = 3;
+      }
+      
+      building.setElementAt(new 
+	Double(m_plot2D.convertToAttribX(new_coords[0])), 1);
+      building.setElementAt(new 
+	Double(m_plot2D.convertToAttribY(new_coords[1])), 2);
+
+      x1 = m_plot2D.convertToPanelX(((Double)v.elementAt(v.size() - 4)).
+				    doubleValue());
+      y1 = m_plot2D.convertToPanelY(((Double)v.elementAt(v.size() - 3)).
+				    doubleValue());
+      x2 = m_plot2D.convertToPanelX(((Double)v.elementAt(v.size() - 6)).
+				    doubleValue());
+      y2 = m_plot2D.convertToPanelY(((Double)v.elementAt(v.size() - 5)).
+				    doubleValue());
+      
+      if (x1 < 0) {
+	//test left
+	new_coords = lineIntersect(x1, y1, x2, y2, 0, this.getHeight(), 0);
+	edge2 = 0;
+	if (new_coords[0] < 0) {
+	  //then not left
+	  if (y1 < 0) {
+	    //test top
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	    edge2 = 1;
+	  }
+	  else {
+	    //test bottom
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				       this.getHeight());
+	    edge2 = 3;
+	  }
+	}
+      }
+      else if (x1 > this.getWidth()) {
+	//test right
+	new_coords = lineIntersect(x1, y1, x2, y2, 0, this.getHeight(), 
+				   this.getWidth());
+	edge2 = 2;
+	if (new_coords[0] < 0) {
+	  //then not right
+	  if (y1 < 0) {
+	    //test top
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	    edge2 = 1;
+	  }
+	  else {
+	    //test bottom
+	    new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				       this.getHeight());
+	    edge2 = 3;
+	  }
+	}
+      }
+      else if (y1 < 0) {
+	//test top
+	new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 0);
+	edge2 = 1;
+      }
+      else {
+	//test bottom
+	new_coords = lineIntersect(x1, y1, x2, y2, this.getWidth(), 0, 
+				   this.getHeight());
+	edge2 = 3;
+      }
+      
+      building.setElementAt(new 
+	Double(m_plot2D.convertToAttribX(new_coords[0])), building.size() - 2);
+      building.setElementAt(new 
+	Double(m_plot2D.convertToAttribY(new_coords[1])), building.size() - 1);
+      
+
+      //trust me this complicated piece of code will
+      //determine what points on the boundary of the view to add to the polygon
+      int xp, yp;
+
+      xp = this.getWidth() * ((edge2 & 1) ^ ((edge2 & 2) / 2));
+      yp = this.getHeight() * ((edge2 & 2) / 2);
+      //System.out.println(((-1 + 4) % 4) + " hoi");
+      
+      if (inPolyline(v, m_plot2D.convertToAttribX(xp), 
+		     m_plot2D.convertToAttribY(yp))) {
+	//then add points in a clockwise direction
+	building.addElement(new Double(m_plot2D.convertToAttribX(xp)));
+	building.addElement(new Double(m_plot2D.convertToAttribY(yp)));
+	for (int noa = (edge2 + 1) % 4; noa != edge1; noa = (noa + 1) % 4) {
+	  xp = this.getWidth() * ((noa & 1) ^ ((noa & 2) / 2));
+	  yp = this.getHeight() * ((noa & 2) / 2);
+	  building.addElement(new Double(m_plot2D.convertToAttribX(xp)));
+	  building.addElement(new Double(m_plot2D.convertToAttribY(yp)));
+	}
+      }
+      else {
+	xp = this.getWidth() * ((edge2 & 2) / 2);
+	yp = this.getHeight() * (1 & ~((edge2 & 1) ^ ((edge2 & 2) / 2)));
+	if (inPolyline(v, m_plot2D.convertToAttribX(xp), 
+		       m_plot2D.convertToAttribY(yp))) {
+	  //then add points in anticlockwise direction
+	  building.addElement(new Double(m_plot2D.convertToAttribX(xp)));
+	  building.addElement(new Double(m_plot2D.convertToAttribY(yp)));
+	  for (int noa = (edge2 + 3) % 4; noa != edge1; noa = (noa + 3) % 4) {
+	    xp = this.getWidth() * ((noa & 2) / 2);
+	    yp = this.getHeight() * (1 & ~((noa & 1) ^ ((noa & 2) / 2)));
+	    building.addElement(new Double(m_plot2D.convertToAttribX(xp)));
+	    building.addElement(new Double(m_plot2D.convertToAttribY(yp)));
+	  }
+	}
+      }
+      return building;
+    }
+
+    /**
+     * This will extract from a polygon shape its x coodrdinates
+     * so that an awt.Polygon can be created.
+     * @param v The polygon shape.
+     * @return an int array containing the screen x coords for the polygon.
+     */
+    private int[] getXCoords(FastVector v) {
+      int cach = (v.size() - 1) / 2;
+      int[] ar = new int[cach];
+      for (int noa = 0; noa < cach; noa ++) {
+	ar[noa] = (int)m_plot2D.convertToPanelX(((Double)v.elementAt(noa * 2 +
+						1)).doubleValue());
+      }
+      return ar;
+    }
+
+    /**
+     * This will extract from a polygon shape its y coordinates
+     * so that an awt.Polygon can be created.
+     * @param v The polygon shape.
+     * @return an int array containing the screen y coords for the polygon.
+     */
+    private int[] getYCoords(FastVector v) {
+      int cach = (v.size() - 1) / 2;
+      int[] ar = new int[cach];
+      for (int noa = 0; noa < cach; noa ++) {
+	ar[noa] = (int)m_plot2D.
+	  convertToPanelY(((Double)v.elementAt(noa * 2 + 2)).
+			  doubleValue());
+      }
+      return ar;
+    }
+    
+    /**
+     * Renders the polygons if necessary
+     * @param gx the graphics context
+     */
+    public void prePlot(Graphics gx) {
+      super.paintComponent(gx);
+      if (m_plotInstances != null) {
+	drawShapes(gx); // will be in paintComponent of ShapePlot2D
+      }
+    }
+  }
+
+
+
+  /** default colours for colouring discrete class */
+  protected Color [] m_DefaultColors = {Color.blue,
+					Color.red,
+					Color.green,
+					Color.cyan,
+					Color.pink,
+					new Color(255, 0, 255),
+					Color.orange,
+					new Color(255, 0, 0),
+					new Color(0, 255, 0),
+					Color.white};
+  
+  /** Lets the user select the attribute for the x axis */
+  protected JComboBox m_XCombo = new JComboBox();
+
+  /** Lets the user select the attribute for the y axis */
+  protected JComboBox m_YCombo = new JComboBox();
+
+  /** Lets the user select the attribute to use for colouring */
+  protected JComboBox m_ColourCombo = new JComboBox();
+  
+  /** Lets the user select the shape they want to create for instance 
+   * selection. */
+  protected JComboBox m_ShapeCombo = new JComboBox();
+
+  /** Button for the user to enter the splits. */
+  protected JButton m_submit = new JButton("Submit");
+  
+  /** Button for the user to remove all splits. */
+  protected JButton m_cancel = new JButton("Clear");
+
+  /** Button for the user to open the visualized set of instances */
+  protected JButton m_openBut = new JButton("Open");
+
+  /** Button for the user to save the visualized set of instances */
+  protected JButton m_saveBut = new JButton("Save");
+
+  /** Stop the combos from growing out of control */
+  private Dimension COMBO_SIZE = new Dimension(250, m_saveBut
+					       .getPreferredSize().height);
+
+  /** file chooser for saving instances */
+  protected JFileChooser m_FileChooser 
+    = new JFileChooser(new File(System.getProperty("user.dir")));
+
+  /** Filter to ensure only arff files are selected */  
+  protected FileFilter m_ArffFilter =
+    new ExtensionFileFilter(Instances.FILE_EXTENSION, "Arff data files");
+
+  /** Label for the jitter slider */
+  protected JLabel m_JitterLab= new JLabel("Jitter",SwingConstants.RIGHT);
+
+  /** The jitter slider */
+  protected JSlider m_Jitter = new JSlider(0,50,0);
+
+  /** The panel that displays the plot */
+  protected PlotPanel m_plot = new PlotPanel();
+
+  /** The panel that displays the attributes , using color to represent 
+   * another attribute. */
+  protected AttributePanel m_attrib = 
+    new AttributePanel(m_plot.m_plot2D.getBackground());
+
+  /** The panel that displays legend info if there is more than one plot */
+  protected LegendPanel m_legendPanel = new LegendPanel();
+
+  /** Panel that surrounds the plot panel with a titled border */
+  protected JPanel m_plotSurround = new JPanel();
+
+  /** Panel that surrounds the class panel with a titled border */
+  protected JPanel m_classSurround = new JPanel();
+
+  /** An optional listener that we will inform when ComboBox selections
+      change */
+  protected ActionListener listener = null;
+
+  /** An optional listener that we will inform when the user creates a 
+   * split to seperate instances. */
+  protected VisualizePanelListener m_splitListener = null;
+
+  /** The name of the plot (not currently displayed, but can be used
+      in the containing Frame or Panel) */
+  protected String m_plotName = "";
+
+  /** The panel that displays the legend for the colouring attribute */
+  protected ClassPanel m_classPanel = 
+    new ClassPanel(m_plot.m_plot2D.getBackground());
+  
+  /** The list of the colors used */
+  protected FastVector m_colorList;
+
+  /** These hold the names of preferred columns to visualize on---if the
+      user has defined them in the Visualize.props file */
+  protected String m_preferredXDimension = null;
+  protected String m_preferredYDimension = null;
+  protected String m_preferredColourDimension = null;
+
+  /** Show the attribute bar panel */
+  protected boolean m_showAttBars = true;
+  
+  /** Show the class panel **/
+  protected boolean m_showClassPanel = true;
+
+  /** the logger */
+  protected Logger m_Log;
+  
+  /**
+   * Sets the Logger to receive informational messages
+   *
+   * @param newLog the Logger that will now get info messages
+   */
+  public void setLog(Logger newLog) {
+    m_Log = newLog;
+  }
+  
+  /**
+   * Set whether the attribute bars should be shown or not.
+   * If turned off via this method then any setting in the
+   * properties file (if exists) is ignored.
+   * 
+   * @param sab false if attribute bars are not to be displayed.
+   */
+  public void setShowAttBars(boolean sab) {
+    if (!sab && m_showAttBars) {
+      m_plotSurround.remove(m_attrib);
+    } else if (sab && !m_showAttBars) {
+      GridBagConstraints constraints = new GridBagConstraints();
+      constraints.insets = new Insets(0, 0, 0, 0);
+      constraints.gridx=4;constraints.gridy=0;constraints.weightx=1;
+      constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+      m_plotSurround.add(m_attrib, constraints);
+    }
+    m_showAttBars = sab;
+    repaint();
+  }
+  
+  /**
+   * Gets whether or not attribute bars are being displayed.
+   * 
+   * @return true if attribute bars are being displayed.
+   */
+  public boolean getShowAttBars() {
+    return m_showAttBars;
+  }
+  
+  /**
+   * Set whether the class panel should be shown or not.
+   * 
+   * @param scp false if class panel is not to be displayed
+   */
+  public void setShowClassPanel(boolean scp) {
+    if (!scp && m_showClassPanel) {
+      remove(m_classSurround);
+    } else if (scp && !m_showClassPanel) {
+      add(m_classSurround, BorderLayout.SOUTH);
+    }
+    m_showClassPanel = scp;
+    repaint();
+  }
+  
+  /**
+   * Gets whether or not the class panel is being displayed.
+   * 
+   * @return true if the class panel is being displayed.
+   */
+  public boolean getShowClassPanel() {
+    return m_showClassPanel;
+  }
+
+  /** 
+   * This constructor allows a VisualizePanelListener to be set. 
+   * 
+   * @param ls		the listener to use
+   */
+  public VisualizePanel(VisualizePanelListener ls) {
+    this();
+    m_splitListener = ls;
+  }
+
+  /**
+   * Set the properties for the VisualizePanel
+   * 
+   * @param relationName	the name of the relation, can be null
+   */
+  private void setProperties(String relationName) {
+    if (VisualizeUtils.VISUALIZE_PROPERTIES != null) {
+      String thisClass = this.getClass().getName();
+      if (relationName == null) {
+	
+	String showAttBars = thisClass+".displayAttributeBars";
+
+	String val = VisualizeUtils.VISUALIZE_PROPERTIES.
+	  getProperty(showAttBars);
+	if (val == null) {
+	  //System.err.println("Displaying attribute bars ");
+//	  m_showAttBars = true;
+	} else {
+	  // only check if this hasn't been turned off programatically 
+	  if (m_showAttBars) {
+	    if (val.compareTo("true") == 0 || val.compareTo("on") == 0) {
+	      //System.err.println("Displaying attribute bars ");
+	      m_showAttBars = true;
+	    } else {
+	      m_showAttBars = false;
+	    }
+	  }
+	}
+      } else {
+	/*
+	System.err.println("Looking for preferred visualization dimensions for "
+			   +relationName);
+	*/
+	String xcolKey = thisClass+"."+relationName+".XDimension";
+	String ycolKey = thisClass+"."+relationName+".YDimension";
+	String ccolKey = thisClass+"."+relationName+".ColourDimension";
+      
+	m_preferredXDimension = VisualizeUtils.VISUALIZE_PROPERTIES.
+	  getProperty(xcolKey);
+	/*
+	if (m_preferredXDimension == null) {
+	  System.err.println("No preferred X dimension found in "
+			     +VisualizeUtils.PROPERTY_FILE
+			     +" for "+xcolKey);
+	} else {
+	  System.err.println("Setting preferred X dimension to "
+			     +m_preferredXDimension);
+			     }*/
+	m_preferredYDimension = VisualizeUtils.VISUALIZE_PROPERTIES.
+	  getProperty(ycolKey);
+	/*
+	if (m_preferredYDimension == null) {
+	  System.err.println("No preferred Y dimension found in "
+			     +VisualizeUtils.PROPERTY_FILE
+			     +" for "+ycolKey);
+	} else {
+	  System.err.println("Setting preferred dimension Y to "
+			     +m_preferredYDimension);
+			     }*/
+	m_preferredColourDimension = VisualizeUtils.VISUALIZE_PROPERTIES.
+	  getProperty(ccolKey);
+	/*
+	if (m_preferredColourDimension == null) {
+	  System.err.println("No preferred Colour dimension found in "
+			     +VisualizeUtils.PROPERTY_FILE
+			     +" for "+ycolKey);
+	} else {
+	  System.err.println("Setting preferred Colour dimension to "
+			     +m_preferredColourDimension);
+			     }*/
+      }
+    }
+  }
+
+  /**
+   * Constructor
+   */
+  public VisualizePanel() {
+    super();
+    setProperties(null);
+    m_FileChooser.setFileFilter(m_ArffFilter);
+    m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+    m_XCombo.setToolTipText("Select the attribute for the x axis");
+    m_YCombo.setToolTipText("Select the attribute for the y axis");
+    m_ColourCombo.setToolTipText("Select the attribute to colour on");
+    m_ShapeCombo.setToolTipText("Select the shape to use for data selection"); 
+
+    m_XCombo.setPreferredSize(COMBO_SIZE);
+    m_YCombo.setPreferredSize(COMBO_SIZE);
+    m_ColourCombo.setPreferredSize(COMBO_SIZE);
+    m_ShapeCombo.setPreferredSize(COMBO_SIZE);
+
+    m_XCombo.setMaximumSize(COMBO_SIZE);
+    m_YCombo.setMaximumSize(COMBO_SIZE);
+    m_ColourCombo.setMaximumSize(COMBO_SIZE);
+    m_ShapeCombo.setMaximumSize(COMBO_SIZE);
+    
+    m_XCombo.setMinimumSize(COMBO_SIZE);
+    m_YCombo.setMinimumSize(COMBO_SIZE);
+    m_ColourCombo.setMinimumSize(COMBO_SIZE);
+    m_ShapeCombo.setMinimumSize(COMBO_SIZE);
+    //////////
+    m_XCombo.setEnabled(false);
+    m_YCombo.setEnabled(false);
+    m_ColourCombo.setEnabled(false);
+    m_ShapeCombo.setEnabled(false);
+
+    // tell the class panel and the legend panel that we want to know when 
+    // colours change
+    m_classPanel.addRepaintNotify(this);
+    m_legendPanel.addRepaintNotify(this);
+    
+    // Check the default colours against the background colour of the
+    // plot panel. If any are equal to the background colour then
+    // change them (so they are visible :-)
+    for (int i = 0; i < m_DefaultColors.length; i++) {
+      Color c = m_DefaultColors[i];
+      if (c.equals(m_plot.m_plot2D.getBackground())) {
+        int red = c.getRed();
+        int blue = c.getBlue();
+        int green = c.getGreen();
+        red += (red < 128) ? (255 - red) / 2 : -(red / 2);
+        blue += (blue < 128) ? (blue - red) / 2 : -(blue / 2);
+        green += (green< 128) ? (255 - green) / 2 : -(green / 2);
+        m_DefaultColors[i] = new Color(red, green, blue);
+      }
+    }
+    m_classPanel.setDefaultColourList(m_DefaultColors);
+    m_attrib.setDefaultColourList(m_DefaultColors);
+
+    m_colorList = new FastVector(10);
+    for (int noa = m_colorList.size(); noa < 10; noa++) {
+      Color pc = m_DefaultColors[noa % 10];
+      int ija =  noa / 10;
+      ija *= 2; 
+      for (int j=0;j<ija;j++) {
+	pc = pc.darker();
+      }
+      
+      m_colorList.addElement(pc);
+    }
+    m_plot.setColours(m_colorList);
+    m_classPanel.setColours(m_colorList);
+    m_attrib.setColours(m_colorList);
+    m_attrib.addAttributePanelListener(new AttributePanelListener() {
+	public void attributeSelectionChange(AttributePanelEvent e) {
+	  if (e.m_xChange) {
+	    m_XCombo.setSelectedIndex(e.m_indexVal);
+	  } else {
+	    m_YCombo.setSelectedIndex(e.m_indexVal);
+	  }
+	}
+      });
+    
+    m_XCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  int selected = m_XCombo.getSelectedIndex();
+	  if (selected < 0) {
+	    selected = 0;
+	  }
+	  m_plot.setXindex(selected);
+	 
+	  // try sending on the event if anyone is listening
+	  if (listener != null) {
+	    listener.actionPerformed(e);
+	  }
+	}
+      });
+
+    m_YCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  int selected = m_YCombo.getSelectedIndex();
+	  if (selected < 0) {
+	    selected = 0;
+	  }
+	  m_plot.setYindex(selected);
+	 
+	  // try sending on the event if anyone is listening
+	  if (listener != null) {
+	    listener.actionPerformed(e);
+	  }
+	}
+      });
+
+    m_ColourCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  int selected = m_ColourCombo.getSelectedIndex();
+	  if (selected < 0) {
+	    selected = 0;
+	  }
+	  m_plot.setCindex(selected);
+
+	  if (listener != null) {
+	    listener.actionPerformed(e);
+	  }
+	}
+      });
+    
+    ///////
+    m_ShapeCombo.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  int selected = m_ShapeCombo.getSelectedIndex();
+	  if (selected < 0) {
+	    selected = 0;
+	  }
+	  m_plot.setSindex(selected);
+	  // try sending on the event if anyone is listening
+	  if (listener != null) {
+	    listener.actionPerformed(e);
+	  }
+	}
+      });
+
+
+    ///////////////////////////////////////
+
+    m_Jitter.addChangeListener(new ChangeListener() {
+	public void stateChanged(ChangeEvent e) {
+	  m_plot.setJitter(m_Jitter.getValue());
+	}
+      });
+
+    m_openBut.setToolTipText("Loads previously saved instances from a file");
+    m_openBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  openVisibleInstances();
+	}
+      });
+    
+    m_saveBut.setEnabled(false);
+    m_saveBut.setToolTipText("Save the visible instances to a file");
+    m_saveBut.addActionListener(new ActionListener() {
+	public void actionPerformed(ActionEvent e) {
+	  saveVisibleInstances();
+	}
+      });
+    
+    JPanel combos = new JPanel();
+    GridBagLayout gb = new GridBagLayout();
+    GridBagConstraints constraints = new GridBagConstraints();
+
+
+    m_XCombo.setLightWeightPopupEnabled(false);
+    m_YCombo.setLightWeightPopupEnabled(false);
+    m_ColourCombo.setLightWeightPopupEnabled(false);
+    m_ShapeCombo.setLightWeightPopupEnabled(false);
+    combos.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+
+    combos.setLayout(gb);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=5;
+    constraints.fill = GridBagConstraints.HORIZONTAL;
+    constraints.gridwidth=2;constraints.gridheight=1;
+    constraints.insets = new Insets(0,2,0,2);
+    combos.add(m_XCombo,constraints);
+    constraints.gridx=2;constraints.gridy=0;constraints.weightx=5;
+    constraints.gridwidth=2;constraints.gridheight=1;
+    combos.add(m_YCombo,constraints);
+    constraints.gridx=0;constraints.gridy=1;constraints.weightx=5;
+    constraints.gridwidth=2;constraints.gridheight=1;
+    combos.add(m_ColourCombo,constraints);
+    //
+    constraints.gridx=2;constraints.gridy=1;constraints.weightx=5;
+    constraints.gridwidth=2;constraints.gridheight=1;
+    combos.add(m_ShapeCombo,constraints);
+
+   
+    JPanel mbts = new JPanel();
+    mbts.setLayout(new GridLayout(1,4));
+    mbts.add(m_submit); mbts.add(m_cancel); mbts.add(m_openBut); mbts.add(m_saveBut);
+
+    constraints.gridx=0;constraints.gridy=2;constraints.weightx=5;
+    constraints.gridwidth=2;constraints.gridheight=1;
+    combos.add(mbts, constraints);
+
+    ////////////////////////////////
+    constraints.gridx=2;constraints.gridy=2;constraints.weightx=5;
+    constraints.gridwidth=1;constraints.gridheight=1;
+    constraints.insets = new Insets(10,0,0,5);
+    combos.add(m_JitterLab,constraints);
+    constraints.gridx=3;constraints.gridy=2;
+    constraints.weightx=5;
+    constraints.insets = new Insets(10,0,0,0);
+    combos.add(m_Jitter,constraints);
+
+    m_classSurround = new JPanel();
+    m_classSurround.
+      setBorder(BorderFactory.createTitledBorder("Class colour")); 
+    m_classSurround.setLayout(new BorderLayout());
+
+    m_classPanel.setBorder(BorderFactory.createEmptyBorder(15,10,10,10));
+    m_classSurround.add(m_classPanel, BorderLayout.CENTER);
+
+    GridBagLayout gb2 = new GridBagLayout();
+    m_plotSurround.setBorder(BorderFactory.createTitledBorder("Plot"));
+    m_plotSurround.setLayout(gb2);
+
+    constraints.fill = GridBagConstraints.BOTH;
+    constraints.insets = new Insets(0, 0, 0, 10);
+    constraints.gridx=0;constraints.gridy=0;constraints.weightx=3;
+    constraints.gridwidth=4;constraints.gridheight=1;constraints.weighty=5;
+    m_plotSurround.add(m_plot, constraints);
+    
+    if (m_showAttBars) {
+      constraints.insets = new Insets(0, 0, 0, 0);
+      constraints.gridx=4;constraints.gridy=0;constraints.weightx=1;
+      constraints.gridwidth=1;constraints.gridheight=1;constraints.weighty=5;
+      m_plotSurround.add(m_attrib, constraints);
+    }
+
+    setLayout(new BorderLayout());
+    add(combos, BorderLayout.NORTH);
+    add(m_plotSurround, BorderLayout.CENTER);
+    add(m_classSurround, BorderLayout.SOUTH);
+    
+    String [] SNames = new String [4];
+    SNames[0] = "Select Instance";
+    SNames[1] = "Rectangle";
+    SNames[2] = "Polygon";
+    SNames[3] = "Polyline";
+
+    m_ShapeCombo.setModel(new DefaultComboBoxModel(SNames));
+    m_ShapeCombo.setEnabled(true);
+  }
+
+  /**
+   * displays the previously saved instances
+   * 
+   * @param insts	the instances to display
+   * @throws Exception	if display is not possible
+   */
+  protected void openVisibleInstances(Instances insts) throws Exception {
+    PlotData2D tempd = new PlotData2D(insts);
+    tempd.setPlotName(insts.relationName());
+    tempd.addInstanceNumberAttribute();
+    m_plot.m_plot2D.removeAllPlots();
+    addPlot(tempd);
+    
+    // modify title
+    Component parent = getParent();
+    while (parent != null) {
+      if (parent instanceof JFrame) {
+	((JFrame) parent).setTitle(
+	    "Weka Classifier Visualize: " 
+	    + insts.relationName() 
+	    + " (display only)");
+	break;
+      }
+      else {
+	parent = parent.getParent();
+      }
+    }
+  }
+  
+  /**
+   * Loads previously saved instances from a file
+   */
+  protected void openVisibleInstances() {
+    try {
+      int returnVal = m_FileChooser.showOpenDialog(this);
+      if (returnVal == JFileChooser.APPROVE_OPTION) {
+	File sFile = m_FileChooser.getSelectedFile();
+	if (!sFile.getName().toLowerCase().
+	    endsWith(Instances.FILE_EXTENSION)) {
+	  sFile = new File(sFile.getParent(), sFile.getName() + Instances.FILE_EXTENSION);
+	}
+	File selected = sFile;
+	Instances insts = new Instances(new BufferedReader(new FileReader(selected)));
+	openVisibleInstances(insts);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      m_plot.m_plot2D.removeAllPlots();
+      JOptionPane.showMessageDialog(
+	  this, 
+	  ex.getMessage(), 
+	  "Error loading file...", 
+	  JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Save the currently visible set of instances to a file
+   */
+  private void saveVisibleInstances() {
+    FastVector plots = m_plot.m_plot2D.getPlots();
+    if (plots != null) {
+      PlotData2D master = (PlotData2D)plots.elementAt(0);
+      Instances saveInsts = new Instances(master.getPlotInstances());
+      for (int i = 1; i < plots.size(); i++) {
+	PlotData2D temp = (PlotData2D)plots.elementAt(i);
+	Instances addInsts = temp.getPlotInstances();
+	for (int j = 0; j < addInsts.numInstances(); j++) {
+	  saveInsts.add(addInsts.instance(j));
+	}
+      }
+      try {
+	int returnVal = m_FileChooser.showSaveDialog(this);
+	if (returnVal == JFileChooser.APPROVE_OPTION) {
+	  File sFile = m_FileChooser.getSelectedFile();
+	  if (!sFile.getName().toLowerCase().
+	      endsWith(Instances.FILE_EXTENSION)) {
+	    sFile = new File(sFile.getParent(), sFile.getName() 
+			     + Instances.FILE_EXTENSION);
+	  }
+	  File selected = sFile;
+	  Writer w = new BufferedWriter(new FileWriter(selected));
+	  w.write(saveInsts.toString());
+	  w.close();
+	}
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+    }
+  }
+
+
+  /**
+   * Sets the index used for colouring. If this method is called then
+   * the supplied index is used and the combo box for selecting colouring
+   * attribute is disabled.
+   * @param index the index of the attribute to use for colouring
+   */
+  public void setColourIndex(int index) {
+    if (index >= 0) {
+      m_ColourCombo.setSelectedIndex(index);
+    } else {
+      m_ColourCombo.setSelectedIndex(0);
+    }
+    m_ColourCombo.setEnabled(false);
+  }
+  
+  
+  /**
+   * Set the index of the attribute for the x axis 
+   * @param index the index for the x axis
+   * @exception Exception if index is out of range.
+   */
+  public void setXIndex(int index) throws Exception {
+    if (index >= 0 && index < m_XCombo.getItemCount()) {
+      m_XCombo.setSelectedIndex(index);
+    } else {
+      throw new Exception("x index is out of range!");
+    }
+  }
+
+  /**
+   * Get the index of the attribute on the x axis
+   * @return the index of the attribute on the x axis
+   */
+  public int getXIndex() {
+    return m_XCombo.getSelectedIndex();
+  }
+
+  /**
+   * Set the index of the attribute for the y axis 
+   * @param index the index for the y axis
+   * @exception Exception if index is out of range.
+   */
+  public void setYIndex(int index) throws Exception {
+    if (index >= 0 && index < m_YCombo.getItemCount()) {
+      m_YCombo.setSelectedIndex(index);
+    } else {
+      throw new Exception("y index is out of range!");
+    }
+  }
+  
+  /**
+   * Get the index of the attribute on the y axis
+   * @return the index of the attribute on the x axis
+   */
+  public int getYIndex() {
+    return m_YCombo.getSelectedIndex();
+  }
+
+  /**
+   * Get the index of the attribute selected for coloring
+   * @return the index of the attribute on the x axis
+   */
+  public int getCIndex() {
+    return m_ColourCombo.getSelectedIndex();
+  }
+
+  /**
+   * Get the index of the shape selected for creating splits.
+   * @return The index of the shape.
+   */
+  public int getSIndex() {
+    return m_ShapeCombo.getSelectedIndex();
+  }
+  
+  /** 
+   * Set the shape for creating splits.
+   * @param index The index of the shape.
+   * @exception Exception if index is out of range.
+   */
+  public void setSIndex(int index) throws Exception {
+    if (index >= 0 && index < m_ShapeCombo.getItemCount()) {
+      m_ShapeCombo.setSelectedIndex(index);
+    }
+    else {
+      throw new Exception("s index is out of range!");
+    }
+  }
+
+  /**
+   * Add a listener for this visualize panel
+   * @param act an ActionListener
+   */
+  public void addActionListener(ActionListener act) {
+    listener = act;
+  }
+
+  /**
+   * Set a name for this plot
+   * @param plotName the name for the plot
+   */
+  public void setName(String plotName) {
+    m_plotName = plotName;
+  }
+
+  /**
+   * Returns the name associated with this plot. "" is returned if no
+   * name is set.
+   * @return the name of the plot
+   */
+  public String getName() {
+    return m_plotName;
+  }
+
+  /**
+   * Get the master plot's instances
+   * @return the master plot's instances
+   */
+  public Instances getInstances() {
+    return m_plot.m_plotInstances;
+  }
+
+  /**
+   * Sets the Colors in use for a different attrib
+   * if it is not a nominal attrib and or does not have
+   * more possible values then this will do nothing.
+   * otherwise it will add default colors to see that
+   * there is a color for the attrib to begin with.
+   * @param a The index of the attribute to color.
+   * @param i The instances object that contains the attribute.
+   */
+  protected void newColorAttribute(int a, Instances i) {
+    if (i.attribute(a).isNominal()) {
+      for (int noa = m_colorList.size(); noa < i.attribute(a).numValues();
+	   noa++) {
+	Color pc = m_DefaultColors[noa % 10];
+	int ija =  noa / 10;
+	ija *= 2; 
+	for (int j=0;j<ija;j++) {
+	  pc = pc.brighter();
+	}
+	
+	m_colorList.addElement(pc);
+      }
+      m_plot.setColours(m_colorList);
+      m_attrib.setColours(m_colorList);
+      m_classPanel.setColours(m_colorList);
+    }
+  }
+
+
+  /**
+   * This will set the shapes for the instances.
+   * @param l A list of the shapes, providing that
+   * the objects in the lists are non editable the data will be
+   * kept intact.
+   */
+  public void setShapes(FastVector l) {
+    m_plot.setShapes(l);
+  }
+
+  /**
+   * Tells the panel to use a new set of instances.
+   * @param inst a set of Instances
+   */
+  public void setInstances(Instances inst) {
+    if (inst.numAttributes() > 0 && inst.numInstances() > 0) {
+      newColorAttribute(inst.numAttributes()-1, inst);
+    }
+
+    PlotData2D temp = new PlotData2D(inst);
+    temp.setPlotName(inst.relationName());
+    
+    try {
+      setMasterPlot(temp);
+    } catch (Exception ex) {
+      System.err.println(ex);
+      ex.printStackTrace();
+    } 
+  }
+
+  /**
+   * initializes the comboboxes based on the data
+   * 
+   * @param inst	the data to base the combobox-setup on
+   */
+  public void setUpComboBoxes(Instances inst) {
+    setProperties(inst.relationName());
+    int prefX = -1;
+    int prefY = -1;
+    if (inst.numAttributes() > 1) {
+      prefY = 1;
+    }
+    int prefC = -1;
+    String [] XNames = new String [inst.numAttributes()];
+    String [] YNames = new String [inst.numAttributes()];
+    String [] CNames = new String [inst.numAttributes()];
+    for (int i = 0; i < XNames.length; i++) {
+      String type = "";
+      switch (inst.attribute(i).type()) {
+      case Attribute.NOMINAL:
+	type = " (Nom)";
+	break;
+      case Attribute.NUMERIC:
+	type = " (Num)";
+	break;
+      case Attribute.STRING:
+	type = " (Str)";
+	break;
+      case Attribute.DATE:
+	type = " (Dat)";
+	break;
+      case Attribute.RELATIONAL:
+	type = " (Rel)";
+	break;
+      default:
+	type = " (???)";
+      }
+      XNames[i] = "X: "+ inst.attribute(i).name()+type;
+      YNames[i] = "Y: "+ inst.attribute(i).name()+type;
+      CNames[i] = "Colour: "+ inst.attribute(i).name()+type;
+      if (m_preferredXDimension != null) {
+	if (m_preferredXDimension.compareTo(inst.attribute(i).name()) == 0) {
+	  prefX = i;
+	  //System.err.println("Found preferred X dimension");
+	}
+      }
+      if (m_preferredYDimension != null) {
+	if (m_preferredYDimension.compareTo(inst.attribute(i).name()) == 0) {
+	  prefY = i;
+	  //System.err.println("Found preferred Y dimension");
+	}
+      }
+      if (m_preferredColourDimension != null) {
+	if (m_preferredColourDimension.
+	    compareTo(inst.attribute(i).name()) == 0) {
+	  prefC = i;
+	  //System.err.println("Found preferred Colour dimension");
+	}
+      }
+    }
+    m_XCombo.setModel(new DefaultComboBoxModel(XNames));
+    m_YCombo.setModel(new DefaultComboBoxModel(YNames));
+
+    m_ColourCombo.setModel(new DefaultComboBoxModel(CNames));
+    //m_ShapeCombo.setModel(new DefaultComboBoxModel(SNames));
+    //m_ShapeCombo.setEnabled(true);
+    m_XCombo.setEnabled(true);
+    m_YCombo.setEnabled(true);
+    
+    if (m_splitListener == null) {
+      m_ColourCombo.setEnabled(true);
+      m_ColourCombo.setSelectedIndex(inst.numAttributes()-1);
+    }
+    m_plotSurround.setBorder((BorderFactory.createTitledBorder("Plot: "
+			      +inst.relationName())));
+    try {
+      if (prefX != -1) {
+	setXIndex(prefX);
+      }
+      if (prefY != -1) {
+	setYIndex(prefY);
+      }
+      if (prefC != -1) {
+	m_ColourCombo.setSelectedIndex(prefC);
+      }
+    } catch (Exception ex) {
+      System.err.println("Problem setting preferred Visualization dimensions");
+    }
+  }
+
+  /**
+   * Removes all the plots.
+   */
+  public void removeAllPlots() {
+    m_plot.removeAllPlots();
+  }
+  
+  /**
+   * Set the master plot for the visualize panel
+   * @param newPlot the new master plot
+   * @exception Exception if the master plot could not be set
+   */
+  public void setMasterPlot(PlotData2D newPlot) throws Exception {
+    m_plot.setMasterPlot(newPlot);
+    setUpComboBoxes(newPlot.m_plotInstances);
+    m_saveBut.setEnabled(true);
+    repaint();
+  }
+
+  /**
+   * Set a new plot to the visualize panel
+   * @param newPlot the new plot to add
+   * @exception Exception if the plot could not be added
+   */
+  public void addPlot(PlotData2D newPlot) throws Exception {
+    m_plot.addPlot(newPlot);
+    if (m_plot.m_plot2D.getMasterPlot() != null) {
+      setUpComboBoxes(newPlot.m_plotInstances);
+    }
+    m_saveBut.setEnabled(true);
+    repaint();
+  }
+  
+  /**
+   * Returns the underlying plot panel.
+   * 
+   * @return		the plot panel
+   */
+  public PlotPanel getPlotPanel() {
+    return m_plot;
+  }
+
+  /**
+   * Main method for testing this class
+   * 
+   * @param args	the commandline parameters
+   */
+  public static void main(String [] args) {
+    try {
+      if (args.length < 1) {
+	System.err.println("Usage : weka.gui.visualize.VisualizePanel "
+			   +"<dataset> [<dataset> <dataset>...]");
+	System.exit(1);
+      }
+
+      weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
+      final javax.swing.JFrame jf = 
+	new javax.swing.JFrame("Weka Explorer: Visualize");
+      jf.setSize(500,400);
+      jf.getContentPane().setLayout(new BorderLayout());
+      final VisualizePanel sp = new VisualizePanel();
+      
+      jf.getContentPane().add(sp, BorderLayout.CENTER);
+      jf.addWindowListener(new java.awt.event.WindowAdapter() {
+	public void windowClosing(java.awt.event.WindowEvent e) {
+	  jf.dispose();
+	  System.exit(0);
+	}
+      });
+
+      jf.setVisible(true);
+      if (args.length >= 1) {
+	for (int j = 0; j < args.length; j++) {
+	  System.err.println("Loading instances from " + args[j]);
+	  java.io.Reader r = new java.io.BufferedReader(
+			     new java.io.FileReader(args[j]));
+	  Instances i = new Instances(r);
+	  i.setClassIndex(i.numAttributes()-1);
+	  PlotData2D pd1 = new PlotData2D(i);
+	  
+	  if (j == 0) {
+	    pd1.setPlotName("Master plot");
+	    sp.setMasterPlot(pd1);
+	  } else {
+	    pd1.setPlotName("Plot "+(j+1));
+	    pd1.m_useCustomColour = true;
+	    pd1.m_customColour = (j % 2 == 0) ? Color.red : Color.blue; 
+	    sp.addPlot(pd1);
+	  }
+	}
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      System.err.println(ex.getMessage());
+    }
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelEvent.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelEvent.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelEvent.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizePanelEvent.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+import weka.core.*;
+
+
+/** 
+ * This event Is fired to a listeners 'userDataEvent' function when
+ * The user on the VisualizePanel clicks submit. It contains the attributes
+ * selected at the time and a FastVector containing the various shapes
+ * that had been drawn into the panel.
+ *
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.4 $
+ */
+public class VisualizePanelEvent {
+  
+  /** No longer used */
+  public static int NONE = 0;
+  public static int RECTANGLE = 1;
+  public static int OVAL = 2;
+  public static int POLYGON = 3;
+  public static int LINE = 4;
+  public static int VLINE = 5;
+  public static int HLINE = 6;
+ 
+  
+  /** Contains FastVectors, each one containing the points for an object. */
+  private FastVector m_values;
+  /** The instances that fall inside the shapes described in m_values. */
+  private Instances m_inst;
+  /** The instances that fall outside the shapes described in m_values. */
+  private Instances m_inst2;
+  /** The attribute along the x axis. */
+  private int m_attrib1;
+  /** The attribute along the y axis. */
+  private int m_attrib2;
+  
+
+  /**
+   * This constructor creates the event with all the parameters set.
+   * @param ar The list of shapes.
+   * @param i The instances that lie in these shapes.
+   * @param i2 The instances that lie outside these shapes.
+   * @param at1 The attribute that was along the x axis.
+   * @param at2 The attribute that was along the y axis.
+   */
+  public VisualizePanelEvent(FastVector ar, Instances i, Instances i2, int at1,
+			     int at2) {
+    m_values = ar;
+    m_inst = i;
+    m_inst2 = i2;
+    m_attrib1 = at1;
+    m_attrib2 = at2;
+    
+    
+  }
+
+  /**
+   * @return The list of shapes.
+   */
+  public FastVector getValues() {
+    return m_values;
+  }
+  
+  /**
+   * @return The instances that lie in the shapes.
+   */
+  public Instances getInstances1() {
+    return m_inst;
+  }
+  
+  /**
+   * @return The instances that lie outside the shapes.
+   */
+  public Instances getInstances2() {
+    return m_inst2;
+  }
+
+  /**
+   * @return The x axis attribute.
+   */
+  public int getAttribute1() {
+    return m_attrib1;
+  }
+  
+  /**
+   * @return The y axis attribute.
+   */
+  public int getAttribute2() {
+    return m_attrib2;
+  }
+
+
+
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelListener.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelListener.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizePanelListener.java	(revision 29)
@@ -0,0 +1,43 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizePanelListener.java
+ *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+
+package weka.gui.visualize;
+
+/**
+ * Interface implemented by a class that is interested in receiving
+ * submited shapes from a visualize panel.
+ * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
+ * @version $Revision: 1.5 $
+ */
+public interface VisualizePanelListener {
+
+  /**
+   * This method receives an object containing the shapes, instances
+   * inside and outside these shapes and the attributes these shapes were
+   * created in.
+   * @param e The Event containing the data.
+   */
+  void userDataEvent(VisualizePanelEvent e);
+
+
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizeUtils.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizeUtils.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/VisualizeUtils.java	(revision 29)
@@ -0,0 +1,141 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ *    VisualizeUtils.java
+ *    Copyright (C) 2000 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize;
+
+import weka.core.Utils;
+import java.util.Properties;
+import java.io.FileInputStream;
+
+import java.awt.Color;
+import javax.swing.JOptionPane;
+
+
+/**
+ * This class contains utility routines for visualization
+ * 
+ * @author Mark Hall (mhall@cs.waikato.ac.nz)
+ * @version $Revision: 1.9 $
+ */
+
+public class VisualizeUtils {
+
+  /** The name of the properties file */
+  protected static String PROPERTY_FILE = "weka/gui/visualize/Visualize.props";
+
+  /** Contains the visualization properties */
+  protected static Properties VISUALIZE_PROPERTIES;
+
+  /** Default maximum precision for the display of numeric values */
+  protected static int MAX_PRECISION = 10;
+
+  static {
+
+    
+    try {
+      VISUALIZE_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
+      String precision = 
+	VISUALIZE_PROPERTIES.getProperty("weka.gui.visualize.precision");
+      if (precision == null) {
+	/*
+	System.err.println("Warning: no configuration property found in"
+			   +PROPERTY_FILE
+			   +" for weka.gui.visualize.precision. Using"
+			   +" default instead.");*/
+      } else {
+	MAX_PRECISION = Integer.parseInt(precision);
+	// System.err.println("Setting numeric precision to: "+precision);
+      }
+    } catch (Exception ex) {
+      JOptionPane.showMessageDialog(null,
+       "VisualizeUtils: Could not read a visualization configuration file.\n"
+       +"An example file is included in the Weka distribution.\n"
+       +"This file should be named \"" + PROPERTY_FILE + "\"  and\n"
+       +"should be placed either in your user home (which is set\n"
+       +"to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+       +"or the directory that java was started from\n",
+       "Plot2D",
+       JOptionPane.ERROR_MESSAGE);
+    }
+  }
+
+  /**
+   * Parses a string containing either a named colour or r,g,b values.
+   * @param colourDef the string containing the named colour (or r,g,b)
+   * @param defaultColour the colour to return if parsing fails
+   * @return the Color corresponding to the string.
+   */
+  public static Color processColour(String colourDef, Color defaultColour) {
+    String colourDefBack = new String(colourDef);
+    Color retC = defaultColour;
+    if (colourDef.indexOf(",") >= 0) { 
+      // Looks like property value is in R, G, B format
+      try {
+	int index = colourDef.indexOf(",");
+	int R = Integer.parseInt(colourDef.substring(0,index));
+	colourDef = colourDef.substring(index+1,colourDef.length());
+	index = colourDef.indexOf(",");
+	int G = Integer.parseInt(colourDef.substring(0,index));
+	colourDef = colourDef.substring(index+1,colourDef.length());
+	int B = Integer.parseInt(colourDef);
+	//System.err.println(R+" "+G+" "+B);
+	retC = new Color(R,G,B);
+      } catch (Exception ex) {
+	System.err.println("VisualizeUtils: Problem parsing colour property "
+			   +"value ("+colourDefBack+").");
+      }
+    } else {
+      // assume that the string is the name of a default Color.color
+      if (colourDef.compareTo("black") == 0) {
+	retC = Color.black;
+      } else if (colourDef.compareTo("blue") == 0) {
+	retC = Color.blue;
+      } else if (colourDef.compareTo("cyan") == 0) {
+	retC = Color.cyan;
+      } else if (colourDef.compareTo("darkGray") == 0) {
+	retC = Color.darkGray;
+      } else if (colourDef.compareTo("gray") == 0) {
+	retC = Color.gray;
+      } else if (colourDef.compareTo("green") == 0) {
+	retC = Color.green;
+      } else if (colourDef.compareTo("lightGray") == 0) {
+	retC = Color.lightGray;
+      } else if (colourDef.compareTo("magenta") == 0) {
+	retC = Color.magenta;
+      } else if (colourDef.compareTo("orange") == 0) {
+	retC = Color.orange;
+      } else if (colourDef.compareTo("pink") == 0) {
+	retC = Color.pink;
+      } else if (colourDef.compareTo("red") == 0) {
+	retC = Color.red;
+      } else if (colourDef.compareTo("white") == 0) {
+	retC = Color.white;
+      } else if (colourDef.compareTo("yellow") == 0) {
+	retC = Color.yellow;
+      } else {
+	System.err.println("VisualizeUtils: colour property name not recognized "
+			   +"("+colourDefBack+").");
+      }
+    }
+    return retC;
+  }
+}
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/ErrorVisualizePlugin.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/ErrorVisualizePlugin.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/ErrorVisualizePlugin.java	(revision 29)
@@ -0,0 +1,86 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * ErrorVisualizePlugin.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize.plugins;
+
+import weka.core.Instances;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Interface implemented by classes loaded dynamically to
+ * visualize classifier errors in the explorer.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5012 $
+ */
+public interface ErrorVisualizePlugin {
+
+  /**
+   * Get a JMenu or JMenuItem which contain action listeners
+   * that perform the visualization of the classifier errors.  
+   * <p/>
+   * The actual class is the attribute declared as class attribute, the
+   * predicted class values is found in the attribute prior to the class
+   * attribute's position. In other words, if the <code>classIndex()</code>
+   * method returns 10, then the attribute position for the predicted class 
+   * values is 9.
+   * <p/>
+   * Exceptions thrown because of changes in Weka since compilation need to 
+   * be caught by the implementer.
+   *
+   * @see NoClassDefFoundError
+   * @see IncompatibleClassChangeError
+   *
+   * @param predInst 	the instances with the actual and predicted class values
+   * @return menuitem 	for opening visualization(s), or null
+   *         		to indicate no visualization is applicable for the input
+   */
+  public JMenuItem getVisualizeMenuItem(Instances predInst);
+
+  /**
+   * Get the minimum version of Weka, inclusive, the class
+   * is designed to work with.  eg: <code>3.5.0</code>
+   * 
+   * @return		the minimum version
+   */
+  public String getMinVersion();
+
+  /**
+   * Get the maximum version of Weka, exclusive, the class
+   * is designed to work with.  eg: <code>3.6.0</code>
+   * 
+   * @return		the maximum version
+   */
+  public String getMaxVersion();
+
+  /**
+   * Get the specific version of Weka the class is designed for.
+   * eg: <code>3.5.1</code>
+   * 
+   * @return		the version the plugin was designed for
+   */
+  public String getDesignVersion();
+}
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/GraphVisualizePlugin.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/GraphVisualizePlugin.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/GraphVisualizePlugin.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * GraphVisualizePlugin.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize.plugins;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Interface implemented by classes loaded dynamically to
+ * visualize graphs in the explorer.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4996 $
+ */
+public interface GraphVisualizePlugin {
+
+  /**
+   * Get a JMenu or JMenuItem which contain action listeners
+   * that perform the visualization of the graph in XML BIF format.  
+   * Exceptions thrown because of changes in Weka since compilation need to 
+   * be caught by the implementer.
+   *
+   * @see NoClassDefFoundError
+   * @see IncompatibleClassChangeError
+   *
+   * @param bif 	the graph in XML BIF format
+   * @param name	the name of the item (in the Explorer's history list)
+   * @return menuitem 	for opening visualization(s), or null
+   *         		to indicate no visualization is applicable for the input
+   */
+  public JMenuItem getVisualizeMenuItem(String bif, String name);
+
+  /**
+   * Get the minimum version of Weka, inclusive, the class
+   * is designed to work with.  eg: <code>3.5.0</code>
+   * 
+   * @return		the minimum version
+   */
+  public String getMinVersion();
+
+  /**
+   * Get the maximum version of Weka, exclusive, the class
+   * is designed to work with.  eg: <code>3.6.0</code>
+   * 
+   * @return		the maximum version
+   */
+  public String getMaxVersion();
+
+  /**
+   * Get the specific version of Weka the class is designed for.
+   * eg: <code>3.5.1</code>
+   * 
+   * @return		the version the plugin was designed for
+   */
+  public String getDesignVersion();
+}
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/TreeVisualizePlugin.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/TreeVisualizePlugin.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/TreeVisualizePlugin.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TreeVisualizePlugin.java
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.gui.visualize.plugins;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Interface implemented by classes loaded dynamically to
+ * visualize classifier results in the explorer.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4996 $
+ */
+public interface TreeVisualizePlugin {
+
+  /**
+   * Get a JMenu or JMenuItem which contain action listeners
+   * that perform the visualization of the tree in GraphViz's dotty format.  
+   * Exceptions thrown because of changes in Weka since compilation need to 
+   * be caught by the implementer.
+   *
+   * @see NoClassDefFoundError
+   * @see IncompatibleClassChangeError
+   *
+   * @param dotty 	the tree in dotty format
+   * @param name	the name of the item (in the Explorer's history list)
+   * @return menuitem 	for opening visualization(s), or null
+   *         		to indicate no visualization is applicable for the input
+   */
+  public JMenuItem getVisualizeMenuItem(String dotty, String name);
+
+  /**
+   * Get the minimum version of Weka, inclusive, the class
+   * is designed to work with.  eg: <code>3.5.0</code>
+   * 
+   * @return		the minimum version
+   */
+  public String getMinVersion();
+
+  /**
+   * Get the maximum version of Weka, exclusive, the class
+   * is designed to work with.  eg: <code>3.6.0</code>
+   * 
+   * @return		the maximum version
+   */
+  public String getMaxVersion();
+
+  /**
+   * Get the specific version of Weka the class is designed for.
+   * eg: <code>3.5.1</code>
+   * 
+   * @return		the version the plugin was designed for
+   */
+  public String getDesignVersion();
+}
+
+
+
Index: branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/VisualizePlugin.java
===================================================================
--- branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/VisualizePlugin.java	(revision 29)
+++ branches/MetisMQI/src/main/java/weka/gui/visualize/plugins/VisualizePlugin.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * VisualizePlugin.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ * Written by Jeffery Grajkowski of the AICML
+ *
+ */
+
+package weka.gui.visualize.plugins;
+
+import weka.core.FastVector;
+import weka.core.Attribute;
+import javax.swing.JMenuItem;
+
+/**
+ * Interface implemented by classes loaded dynamically to
+ * visualize classifier results in the explorer.
+ *
+ * @author Jeffery Grajkowski (grajkows@cs.ualberta.ca)
+ * @version $Revision: 1.1 $
+ */
+public interface VisualizePlugin {
+
+  /**
+   * Get a JMenu or JMenuItem which contain action listeners
+   * that perform the visualization, using some but not
+   * necessarily all of the data.  Exceptions thrown because of
+   * changes in Weka since compilation need to be caught by
+   * the implementer.
+   *
+   * @see NoClassDefFoundError
+   * @see IncompatibleClassChangeError
+   *
+   * @param  preds predictions
+   * @param  classAtt class attribute
+   * @return menuitem for opening visualization(s), or null
+   *         to indicate no visualization is applicable for the input
+   */
+  public JMenuItem getVisualizeMenuItem(FastVector preds, Attribute classAtt);
+
+  /**
+   * Get the minimum version of Weka, inclusive, the class
+   * is designed to work with.  eg: <code>3.5.0</code>
+   */
+  public String getMinVersion();
+
+  /**
+   * Get the maximum version of Weka, exclusive, the class
+   * is designed to work with.  eg: <code>3.6.0</code>
+   */
+  public String getMaxVersion();
+
+  /**
+   * Get the specific version of Weka the class is designed for.
+   * eg: <code>3.5.1</code>
+   */
+  public String getDesignVersion();
+}
+
+
+
Index: branches/MetisMQI/src/test/java/weka/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/AllTests.java	(revision 29)
@@ -0,0 +1,55 @@
+package weka;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;         
+
+/**
+ * Test class for all tests in this directory. Run from the command line 
+ * with:<p>
+ * java weka.AllTests
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.6 $
+ */
+public class AllTests extends TestSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+
+    // Core components
+    suite.addTest(weka.core.AllTests.suite());
+
+    // associators
+    suite.addTest(weka.associations.AllTests.suite());
+
+    // attribute selection
+    suite.addTest(weka.attributeSelection.AllTests.suite());
+
+    // classifiers
+    suite.addTest(weka.classifiers.AllTests.suite());
+
+    // clusterers
+    suite.addTest(weka.clusterers.AllTests.suite());
+
+    // data generators
+    suite.addTest(weka.datagenerators.AllTests.suite());
+
+    // estimators
+    //suite.addTest(weka.estimators.AllTests.suite());
+
+    // filters
+    suite.addTest(weka.filters.AllTests.suite());
+
+    // High level applications
+    //suite.addTest(weka.experiment.AllTests.suite());
+    //suite.addTest(weka.gui.AllTests.suite());
+
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/AbstractAssociatorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/AbstractAssociatorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/AbstractAssociatorTest.java	(revision 29)
@@ -0,0 +1,914 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.associations;
+
+import weka.core.Attribute;
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.CheckScheme.PostProcessor;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Associators. Internally it uses the class
+ * <code>CheckAssociator</code> to determine success or failure of the
+ * tests. It follows basically the <code>testsPerClassType</code> method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.7 $
+ *
+ * @see CheckAssociator
+ * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractAssociatorTest 
+  extends TestCase {
+  
+  /** The Associator to be tested */
+  protected Associator m_Associator;
+
+  /** For testing the Associator */
+  protected CheckAssociator m_Tester;
+
+  /** whether Associator handles weighted instances */
+  protected boolean m_weightedInstancesHandler;
+
+  /** whether Associator handles multi-instance data */
+  protected boolean m_multiInstanceHandler;
+
+  /** the number of classes to test with testNClasses() 
+   * @see #testNClasses() */
+  protected int m_NClasses;
+
+  /** whether to run CheckAssociator in DEBUG mode */
+  protected boolean DEBUG = false;
+
+  /** the attribute type with the lowest value */
+  protected final static int FIRST_CLASSTYPE = Attribute.NUMERIC;
+
+  /** the attribute type with the highest value */
+  protected final static int LAST_CLASSTYPE = Attribute.RELATIONAL;
+  
+  /** wether Associator can predict nominal attributes (array index is attribute type of class) */
+  protected boolean[] m_NominalPredictors;
+  
+  /** wether Associator can predict numeric attributes (array index is attribute type of class) */
+  protected boolean[] m_NumericPredictors;
+  
+  /** wether Associator can predict string attributes (array index is attribute type of class) */
+  protected boolean[] m_StringPredictors;
+  
+  /** wether Associator can predict date attributes (array index is attribute type of class) */
+  protected boolean[] m_DatePredictors;
+  
+  /** wether Associator can predict relational attributes (array index is attribute type of class) */
+  protected boolean[] m_RelationalPredictors;
+  
+  /** whether Associator handles missing values */
+  protected boolean[] m_handleMissingPredictors;
+
+  /** whether Associator handles class with only missing values */
+  protected boolean[] m_handleMissingClass;
+  
+  /** the results of the regression tests */
+  protected String[] m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractAssociatorTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractAssociatorTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckClassifier datasets, currently
+   * only null.
+   * 
+   * @return		a custom PostProcessor, if necessary
+   * @see PostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return null;
+  }
+  
+  /**
+   * configures the CheckAssociator instance used throughout the tests
+   * 
+   * @return	the fully configured CheckAssociator instance used for testing
+   */
+  protected CheckAssociator getTester() {
+    CheckAssociator	result;
+    
+    result = new CheckAssociator();
+    result.setSilent(true);
+    result.setAssociator(m_Associator);
+    result.setNumInstances(20);
+    result.setDebug(DEBUG);
+    result.setPostProcessor(getPostProcessor());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler used for testing the optionhandling.
+   * Sets the Associator returned from the getAssociator() method.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getAssociator()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (getAssociator() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getAssociator());
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Associator returned from the getAssociator() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getAssociator()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getAssociator());
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default Associator to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Associator   = getAssociator();
+    m_Tester       = getTester();
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+
+    m_weightedInstancesHandler     = m_Tester.weightedInstancesHandler()[0];
+    m_multiInstanceHandler         = m_Tester.multiInstanceHandler()[0];
+    // LAST_CLASSTYPE+1 = no class attribute
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 2];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 2];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 2];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 2];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 2];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 2];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 2];
+    m_RegressionResults            = new String[LAST_CLASSTYPE + 2];
+    m_NClasses                     = 4;
+
+    // initialize attributes
+    checkAttributes(true,  false, false, false, false, false);
+    checkAttributes(false, true,  false, false, false, false);
+    checkAttributes(false, false, true,  false, false, false);
+    checkAttributes(false, false, false, true,  false, false);
+    checkAttributes(false, false, false, false, true,  false);
+    
+    // initialize missing values handling
+    for (int i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      m_handleMissingPredictors[i] = checkMissingPredictors(i, 20, false);
+      if (i <= LAST_CLASSTYPE)
+	m_handleMissingClass[i] = checkMissingClass(i, 20, false);
+    }
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Associator   = null;
+    m_Tester       = null;
+    m_OptionTester = null;
+    m_GOETester    = null;
+
+    m_weightedInstancesHandler     = false;
+    m_NominalPredictors            = null;
+    m_NumericPredictors            = null;
+    m_StringPredictors             = null;
+    m_DatePredictors               = null;
+    m_RelationalPredictors         = null;
+    m_handleMissingPredictors      = null;
+    m_handleMissingClass           = null;
+    m_RegressionResults            = null;
+    m_NClasses                     = 4;
+  }
+
+  /**
+   * Used to create an instance of a specific Associator.
+   *
+   * @return a suitably configured <code>Associator</code> value
+   */
+  public abstract Associator getAssociator();
+
+  /**
+   * checks whether at least one attribute type can be handled with the
+   * given class type
+   *
+   * @param type      the class type to check for
+   * @return          true if at least one attribute type can be predicted with
+   *                  the given class
+   */
+  protected boolean canPredict(int type) {
+    return    m_NominalPredictors[type]
+           || m_NumericPredictors[type]
+           || m_StringPredictors[type]
+           || m_DatePredictors[type]
+           || m_RelationalPredictors[type];
+  }
+
+  /** 
+   * returns a string for the class type
+   * 
+   * @param type        the class type
+   * @return            the class type as string
+   */
+  protected String getClassTypeString(int type) {
+    if (type == LAST_CLASSTYPE + 1)
+      return "no";
+    else
+      return CheckAssociator.attributeTypeToString(type);
+  }
+
+  /**
+   * tests whether the Associator can handle certain attributes and if not,
+   * if the exception is OK
+   *
+   * @param nom         to check for nominal attributes
+   * @param num         to check for numeric attributes
+   * @param str         to check for string attributes
+   * @param dat         to check for date attributes
+   * @param rel         to check for relational attributes
+   * @param allowFail   whether a junit fail can be executed
+   * @see CheckAssociator#canPredict(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  protected void checkAttributes(boolean nom, boolean num, boolean str, 
+                                 boolean dat, boolean rel,
+                                 boolean allowFail) {
+    boolean[]     result;
+    String        att;
+    int           i;
+    int           type;
+
+    // determine text for type of attributes
+    att = "";
+    if (nom)
+      att = "nominal";
+    else if (num)
+      att = "numeric";
+    else if (str)
+      att = "string";
+    else if (dat)
+      att = "date";
+    else if (rel)
+      att = "relational";
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      if (i == LAST_CLASSTYPE + 1)
+	type = CheckAssociator.NO_CLASS;
+      else
+	type = i;
+      result = m_Tester.canPredict(nom, num, str, dat, rel, m_multiInstanceHandler, type);
+
+      if (nom)
+        m_NominalPredictors[i] = result[0];
+      else if (num)
+        m_NumericPredictors[i] = result[0];
+      else if (str)
+        m_StringPredictors[i] = result[0];
+      else if (dat)
+        m_DatePredictors[i] = result[0];
+      else if (rel)
+        m_RelationalPredictors[i] = result[0];
+
+      if (!result[0] && !result[1] && allowFail)
+        fail("Error handling " + att + " attributes (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the Associator can handle different types of attributes and
+   * if not, if the exception is OK
+   *
+   * @see #checkAttributes(boolean, boolean, boolean, boolean, boolean, boolean)
+   */
+  public void testAttributes() {
+    // nominal
+    checkAttributes(true,  false, false, false, false, true);
+    // numeric
+    checkAttributes(false, true,  false, false, false, true);
+    // string
+    checkAttributes(false, false, true,  false, false, true);
+    // date
+    checkAttributes(false, false, false, true,  false, true);
+    // relational
+    if (!m_multiInstanceHandler)
+      checkAttributes(false, false, false, false, true,  true);
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean[]     result;
+
+    result = m_Tester.declaresSerialVersionUID();
+
+    if (!result[0])
+      fail("Doesn't declare serialVersionUID!");
+  }
+
+  /**
+   * tests whether the Associator handles instance weights correctly
+   *
+   * @see CheckAssociator#instanceWeights(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testInstanceWeights() {
+    boolean[]     result;
+    int           i;
+    int           type;
+    
+    if (m_weightedInstancesHandler) {
+      for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+        // does the Associator support this type of class at all?
+        if (!canPredict(i))
+          continue;
+
+        if (i == LAST_CLASSTYPE + 1)
+          type = CheckAssociator.NO_CLASS;
+        else
+          type = i;
+        
+        result = m_Tester.instanceWeights(
+            m_NominalPredictors[i], 
+            m_NumericPredictors[i], 
+            m_StringPredictors[i], 
+            m_DatePredictors[i], 
+            m_RelationalPredictors[i], 
+            m_multiInstanceHandler, 
+            type);
+
+        if (!result[0])
+          System.err.println("Error handling instance weights (" + getClassTypeString(i) 
+              + " class)!");
+      }
+    }
+  }
+
+  /**
+   * tests whether Associator handles N classes
+   *
+   * @see CheckAssociator#canHandleNClasses(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   * @see #m_NClasses
+   */
+  public void testNClasses() {
+    boolean[]     result;
+
+    if (!canPredict(Attribute.NOMINAL))
+      return;
+
+    result = m_Tester.canHandleNClasses(
+        m_NominalPredictors[Attribute.NOMINAL],
+        m_NumericPredictors[Attribute.NOMINAL],
+        m_StringPredictors[Attribute.NOMINAL],
+        m_DatePredictors[Attribute.NOMINAL],
+        m_RelationalPredictors[Attribute.NOMINAL],
+        m_multiInstanceHandler,
+        m_NClasses);
+
+    if (!result[0] && !result[1])
+      fail("Error handling " + m_NClasses + " classes!");
+  }
+
+  /**
+   * checks whether the Associator can handle the class attribute at a given
+   * position (0-based index, -1 means last).
+   *
+   * @param type        the class type
+   * @param position	the position of the class attribute (0-based, -1 means last)
+   * @return            true if the Associator can handle it
+   */
+  protected boolean checkClassAsNthAttribute(int type, int position) {
+    boolean[]     result;
+    String	  indexStr;
+    
+    result = m_Tester.canHandleClassAsNthAttribute(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        position);
+
+    if (position == -1)
+      indexStr = "last";
+    else
+      indexStr = (position + 1) + ".";
+    
+    if (!result[0] && !result[1])
+      fail("Error handling class as " + indexStr + " attribute (" 
+          + getClassTypeString(type) + " class)!");
+    
+    return result[0];
+  }
+
+  /**
+   * Tests whether the Associator can handle class attributes as Nth
+   * attribute. In case of multi-instance Associators it performs no tests,
+   * since the multi-instance data has a fixed format (bagID,bag,class).
+   *
+   * @see CheckAssociator#canHandleClassAsNthAttribute(boolean, boolean, boolean, boolean, boolean, boolean, int, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testClassAsNthAttribute() {
+    int           i;
+    
+    // multi-Instance data has fixed format!
+    if (m_multiInstanceHandler)
+      return;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // first attribute
+      checkClassAsNthAttribute(i, 0);
+
+      // second attribute
+      checkClassAsNthAttribute(i, 1);
+    }
+  }
+
+  /**
+   * tests whether the Associator can handle zero training instances
+   *
+   * @see CheckAssociator#canHandleZeroTraining(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testZeroTraining() {
+    boolean[]     result;
+    int           i;
+    int           type;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      if (i == LAST_CLASSTYPE + 1)
+	type = CheckAssociator.NO_CLASS;
+      else
+	type = i;
+
+      result = m_Tester.canHandleZeroTraining(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          type);
+
+      if (!result[0] && !result[1])
+        fail("Error handling zero training instances (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * checks whether the Associator can handle the given percentage of
+   * missing predictors
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing predictors
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the Associator can handle it
+   */
+  protected boolean checkMissingPredictors(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    int           classType;
+    
+    if (type == LAST_CLASSTYPE + 1)
+      classType = CheckAssociator.NO_CLASS;
+    else
+      classType = type;
+
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        classType,
+        true,
+        false,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing predictors (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the Associator can handle missing predictors (20% and 100%)
+   *
+   * @see CheckAssociator#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingPredictors() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingPredictors(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingPredictors[i])
+        checkMissingPredictors(i, 100, true);
+    }
+  }
+
+  /**
+   * checks whether the Associator can handle the given percentage of
+   * missing class labels
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing class labels
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the Associator can handle it
+   */
+  protected boolean checkMissingClass(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        false,
+        true,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing class labels (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the Associator can handle missing class values (20% and
+   * 100%)
+   *
+   * @see CheckAssociator#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingClass() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingClass(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingClass[i])
+        checkMissingClass(i, 100, true);
+    }
+  }
+
+  /**
+   * tests whether the Associator correctly initializes in the
+   * buildAssociator method
+   *
+   * @see CheckAssociator#correctBuildInitialisation(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testBuildInitialization() {
+    boolean[]     result;
+    int           i;
+    int           type;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      if (i == LAST_CLASSTYPE + 1)
+	type = CheckAssociator.NO_CLASS;
+      else
+	type = i;
+
+      result = m_Tester.correctBuildInitialisation(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          type);
+
+      if (!result[0] && !result[1])
+        fail("Incorrect build initialization (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the Associator alters the training set during training.
+   *
+   * @see CheckAssociator#datasetIntegrity(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean)
+   * @see CheckAssociator#testsPerClassType(int, boolean, boolean)
+   */
+  public void testDatasetIntegrity() {
+    boolean[]     result;
+    int           i;
+    int           type;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      if (i == LAST_CLASSTYPE + 1)
+	type = CheckAssociator.NO_CLASS;
+      else
+	type = i;
+
+      result = m_Tester.datasetIntegrity(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          type,
+          m_handleMissingPredictors[i],
+          m_handleMissingClass[i]);
+
+      if (!result[0] && !result[1])
+        fail("Training set is altered during training (" 
+            + getClassTypeString(i) + " class)!");
+    }
+  }
+
+  /**
+   * Builds a model using the current Associator using the given data and 
+   * returns the produced output.
+   * TODO: unified rules as output instead of toString() result???
+   *
+   * @param data 	the instances to test the Associator on
+   * @return 		a String containing the output of the Associator.
+   * @throws Exception	if something goes wrong
+   */
+  protected String useAssociator(Instances data) throws Exception {
+    Associator associator = null;
+    
+    try {
+      associator = AbstractAssociator.makeCopy(m_Associator);
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Problem setting up to use Associator: " + e);
+    }
+
+    associator.buildAssociations(data);
+    
+    return associator.toString();
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. Currently,
+   * the data is just passed through.
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   */
+  protected Instances process(Instances data) {
+    return data;
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   * 
+   * @throws Exception 	if something goes wrong
+   */
+  public void testRegression() throws Exception {
+    int		i;
+    boolean	succeeded;
+    Regression 	reg;
+    Instances   train;
+    int		type;
+    
+    // don't bother if not working correctly
+    if (m_Tester.hasClasspathProblems())
+      return;
+    
+    reg = new Regression(this.getClass());
+    succeeded = false;
+    train = null;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE + 1; i++) {
+      // does the Associator support this type of class at all?
+      if (!canPredict(i))
+        continue;
+        
+      if (i == LAST_CLASSTYPE + 1)
+	type = CheckAssociator.NO_CLASS;
+      else
+	type = i;
+
+      train = m_Tester.makeTestDataset(
+          42, m_Tester.getNumInstances(), 
+  	  m_NominalPredictors[i] ? 2 : 0,
+  	  m_NumericPredictors[i] ? 1 : 0, 
+          m_StringPredictors[i] ? 1 : 0,
+          m_DatePredictors[i] ? 1 : 0,
+          m_RelationalPredictors[i] ? 1 : 0,
+          2, 
+          type,
+          m_multiInstanceHandler);
+  
+      try {
+        m_RegressionResults[i] = useAssociator(train);
+        succeeded = true;
+        reg.println(m_RegressionResults[i]);
+      }
+      catch (Exception e) {
+	String msg = e.getMessage().toLowerCase();
+	if (msg.indexOf("not in classpath") > -1)
+	  return;
+
+	m_RegressionResults[i] = null;
+      }
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful predictions for any class type");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkDefaultOptions())
+	fail("Default options were not processed correctly.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/AllTests.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.associations;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Test class for all associators. Run from the command line with: <p/>
+ * java weka.associations.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    return suite("weka.associations.Associator");
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/AprioriTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/AprioriTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/AprioriTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Apriori. Run from the command line with:<p/>
+ * java weka.associations.AprioriTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AprioriTest 
+  extends AbstractAssociatorTest {
+
+  public AprioriTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Apriori */
+  public Associator getAssociator() {
+    return new Apriori();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AprioriTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/FPGrowthTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/FPGrowthTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/FPGrowthTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FPGrowth. Run from the command line with:<p/>
+ * java weka.associations.AprioriTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 6050 $
+ */
+public class FPGrowthTest 
+  extends AbstractAssociatorTest {
+
+  public FPGrowthTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Apriori */
+  public Associator getAssociator() {
+    return new FPGrowth();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FPGrowthTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/FilteredAssociatorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/FilteredAssociatorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/FilteredAssociatorTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FilteredAssociator. Run from the command line with:<p/>
+ * java weka.associations.FilteredAssociatorTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class FilteredAssociatorTest 
+  extends AbstractAssociatorTest {
+
+  public FilteredAssociatorTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a default FilteredAssociator
+   */
+  public Associator getAssociator() {
+    return new FilteredAssociator();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FilteredAssociatorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/GeneralizedSequentialPatternsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/GeneralizedSequentialPatternsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/GeneralizedSequentialPatternsTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests GeneralizedSequentialPatterns. Run from the command line with:<p/>
+ * java weka.associations.GeneralizedSequentialPatternsTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class GeneralizedSequentialPatternsTest 
+  extends AbstractAssociatorTest {
+
+  public GeneralizedSequentialPatternsTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GeneralizedSequentialPatterns */
+  public Associator getAssociator() {
+    return new GeneralizedSequentialPatterns();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GeneralizedSequentialPatternsTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/HotSpotTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/HotSpotTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/HotSpotTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand.
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests HotSpot. Run from the command line with:<p/>
+ * java weka.associations.HotSpotTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5563 $
+ */
+public class HotSpotTest 
+  extends AbstractAssociatorTest {
+
+  public HotSpotTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default HotSpot */
+  public Associator getAssociator() {
+    return new HotSpot();
+  }
+
+  public static Test suite() {
+    return new TestSuite(HotSpotTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/PredictiveAprioriTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/PredictiveAprioriTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/PredictiveAprioriTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PredictiveApriori. Run from the command line with:<p/>
+ * java weka.associations.PredictiveAprioriTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class PredictiveAprioriTest 
+  extends AbstractAssociatorTest {
+
+  public PredictiveAprioriTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default PredictiveApriori */
+  public Associator getAssociator() {
+    return new PredictiveApriori();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PredictiveAprioriTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/associations/TertiusTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/associations/TertiusTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/associations/TertiusTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.associations;
+
+import weka.associations.AbstractAssociatorTest;
+import weka.associations.Associator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Tertius. Run from the command line with:<p/>
+ * java weka.associations.TertiusTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class TertiusTest 
+  extends AbstractAssociatorTest {
+
+  public TertiusTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Tertius */
+  public Associator getAssociator() {
+    return new Tertius();
+  }
+
+  public static Test suite() {
+    return new TestSuite(TertiusTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractAttributeSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractAttributeSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractAttributeSelectionTest.java	(revision 29)
@@ -0,0 +1,882 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.attributeSelection;
+
+import weka.core.Attribute;
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instances;
+import weka.core.CheckScheme.PostProcessor;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for attribute selection schemes. Internally it uses the
+ * class <code>CheckAttributeSelection</code> to determine success or failure
+ * of the tests. It follows basically the <code>testsPerClassType</code>
+ * method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ *
+ * @see CheckAttributeSelection
+ * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractAttributeSelectionTest 
+  extends TestCase {
+  
+  /** The search scheme to be tested */
+  protected ASSearch m_Search;
+
+  /** The evaluator to test */
+  protected ASEvaluation m_Evaluator;
+  
+  /** For testing the attribute selection scheme */
+  protected CheckAttributeSelection m_Tester;
+
+  /** whether scheme handles weighted instances */
+  protected boolean m_weightedInstancesHandler;
+
+  /** whether scheme handles multi-instance data */
+  protected boolean m_multiInstanceHandler;
+
+  /** the number of classes to test with testNClasses() 
+   * @see #testNClasses() */
+  protected int m_NClasses;
+
+  /** whether to run CheckAttributeSelection in DEBUG mode */
+  protected boolean DEBUG = false;
+
+  /** the attribute type with the lowest value */
+  protected final static int FIRST_CLASSTYPE = Attribute.NUMERIC;
+
+  /** the attribute type with the highest value */
+  protected final static int LAST_CLASSTYPE = Attribute.RELATIONAL;
+  
+  /** wether scheme can predict nominal attributes (array index is attribute type of class) */
+  protected boolean[] m_NominalPredictors;
+  
+  /** wether scheme can predict numeric attributes (array index is attribute type of class) */
+  protected boolean[] m_NumericPredictors;
+  
+  /** wether scheme can predict string attributes (array index is attribute type of class) */
+  protected boolean[] m_StringPredictors;
+  
+  /** wether scheme can predict date attributes (array index is attribute type of class) */
+  protected boolean[] m_DatePredictors;
+  
+  /** wether scheme can predict relational attributes (array index is attribute type of class) */
+  protected boolean[] m_RelationalPredictors;
+  
+  /** whether scheme handles missing values */
+  protected boolean[] m_handleMissingPredictors;
+
+  /** whether scheme handles class with only missing values */
+  protected boolean[] m_handleMissingClass;
+
+  /** whether scheme handles class as first attribute */
+  protected boolean[] m_handleClassAsFirstAttribute;
+
+  /** whether scheme handles class as second attribute */
+  protected boolean[] m_handleClassAsSecondAttribute;
+  
+  /** the results of the regression tests */
+  protected String[] m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractAttributeSelectionTest</code>. Called by subclasses.
+   *
+   * @param name 	the name of the test class
+   */
+  public AbstractAttributeSelectionTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckAttributeSelection datasets, 
+   * currently only null.
+   * 
+   * @return		a custom PostProcessor, if necessary
+   * @see 		PostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return null;
+  }
+  
+  /**
+   * configures the CheckAttributeSelection instance used throughout the tests
+   * 
+   * @return	the fully configured CheckAttributeSelection instance used for testing
+   */
+  protected CheckAttributeSelection getTester() {
+    CheckAttributeSelection	result;
+    
+    result = new CheckAttributeSelection();
+    result.setSilent(true);
+    result.setSearch(m_Search);
+    result.setEvaluator(m_Evaluator);
+    result.setNumInstances(20);
+    result.setDebug(DEBUG);
+    result.setPostProcessor(getPostProcessor());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * 
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(null);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default scheme to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Search       = getSearch();
+    m_Evaluator    = getEvaluator();
+    m_Tester       = getTester();
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+
+    m_weightedInstancesHandler     = m_Tester.weightedInstancesHandler()[0];
+    m_multiInstanceHandler         = m_Tester.multiInstanceHandler()[0];
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new String[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+
+    // initialize attributes
+    checkAttributes(true,  false, false, false, false, false);
+    checkAttributes(false, true,  false, false, false, false);
+    checkAttributes(false, false, true,  false, false, false);
+    checkAttributes(false, false, false, true,  false, false);
+    checkAttributes(false, false, false, false, true,  false);
+    
+    // initialize missing values handling
+    for (int i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      m_handleMissingPredictors[i] = checkMissingPredictors(i, 20, false);
+      m_handleMissingClass[i]      = checkMissingClass(i, 20, false);
+    }
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Search       = null;
+    m_Evaluator    = null;
+    m_Tester       = null;
+    m_OptionTester = null;
+    m_GOETester    = null;
+
+    m_weightedInstancesHandler     = false;
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new String[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+  }
+
+  /**
+   * Used to create an instance of a specific search scheme.
+   *
+   * @return a suitably configured <code>ASSearch</code> value
+   */
+  public abstract ASSearch getSearch();
+
+  /**
+   * Used to create an instance of a specific evaluator.
+   *
+   * @return a suitably configured <code>ASEvaluation</code> value
+   */
+  public abstract ASEvaluation getEvaluator();
+
+  /**
+   * checks whether at least one attribute type can be handled with the
+   * given class type
+   *
+   * @param type      the class type to check for
+   * @return          true if at least one attribute type can be predicted with
+   *                  the given class
+   */
+  protected boolean canPredict(int type) {
+    return    m_NominalPredictors[type]
+           || m_NumericPredictors[type]
+           || m_StringPredictors[type]
+           || m_DatePredictors[type]
+           || m_RelationalPredictors[type];
+  }
+
+  /** 
+   * returns a string for the class type
+   * 
+   * @param type        the class type
+   * @return            the class type as string
+   */
+  protected String getClassTypeString(int type) {
+    return CheckAttributeSelection.attributeTypeToString(type);
+  }
+
+  /**
+   * tests whether the scheme can handle certain attributes and if not,
+   * if the exception is OK
+   *
+   * @param nom         to check for nominal attributes
+   * @param num         to check for numeric attributes
+   * @param str         to check for string attributes
+   * @param dat         to check for date attributes
+   * @param rel         to check for relational attributes
+   * @param allowFail   whether a junit fail can be executed
+   * @see CheckAttributeSelection#canPredict(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  protected void checkAttributes(boolean nom, boolean num, boolean str, 
+                                 boolean dat, boolean rel,
+                                 boolean allowFail) {
+    boolean[]     result;
+    String        att;
+    int           i;
+
+    // determine text for type of attributes
+    att = "";
+    if (nom)
+      att = "nominal";
+    else if (num)
+      att = "numeric";
+    else if (str)
+      att = "string";
+    else if (dat)
+      att = "date";
+    else if (rel)
+      att = "relational";
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      result = m_Tester.canPredict(nom, num, str, dat, rel, m_multiInstanceHandler, i);
+      if (nom)
+        m_NominalPredictors[i] = result[0];
+      else if (num)
+        m_NumericPredictors[i] = result[0];
+      else if (str)
+        m_StringPredictors[i] = result[0];
+      else if (dat)
+        m_DatePredictors[i] = result[0];
+      else if (rel)
+        m_RelationalPredictors[i] = result[0];
+
+      if (!result[0] && !result[1] && allowFail)
+        fail("Error handling " + att + " attributes (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the scheme can handle different types of attributes and
+   * if not, if the exception is OK
+   *
+   * @see #checkAttributes(boolean, boolean, boolean, boolean, boolean, boolean)
+   */
+  public void testAttributes() {
+    // nominal
+    checkAttributes(true,  false, false, false, false, true);
+    // numeric
+    checkAttributes(false, true,  false, false, false, true);
+    // string
+    checkAttributes(false, false, true,  false, false, true);
+    // date
+    checkAttributes(false, false, false, true,  false, true);
+    // relational
+    if (!m_multiInstanceHandler)
+      checkAttributes(false, false, false, false, true,  true);
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean[]     result;
+
+    result = m_Tester.declaresSerialVersionUID();
+
+    if (!result[0])
+      fail("Doesn't declare serialVersionUID!");
+  }
+
+  /**
+   * tests whether the scheme handles instance weights correctly
+   *
+   * @see CheckAttributeSelection#instanceWeights(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testInstanceWeights() {
+    boolean[]     result;
+    int           i;
+    
+    if (m_weightedInstancesHandler) {
+      for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+        // does the scheme support this type of class at all?
+        if (!canPredict(i))
+          continue;
+        
+        result = m_Tester.instanceWeights(
+            m_NominalPredictors[i], 
+            m_NumericPredictors[i], 
+            m_StringPredictors[i], 
+            m_DatePredictors[i], 
+            m_RelationalPredictors[i], 
+            m_multiInstanceHandler, 
+            i);
+
+        if (!result[0])
+          System.err.println("Error handling instance weights (" + getClassTypeString(i) 
+              + " class)!");
+      }
+    }
+  }
+
+  /**
+   * tests whether scheme handles N classes
+   *
+   * @see CheckAttributeSelection#canHandleNClasses(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   * @see #m_NClasses
+   */
+  public void testNClasses() {
+    boolean[]     result;
+
+    if (!canPredict(Attribute.NOMINAL))
+      return;
+
+    result = m_Tester.canHandleNClasses(
+        m_NominalPredictors[Attribute.NOMINAL],
+        m_NumericPredictors[Attribute.NOMINAL],
+        m_StringPredictors[Attribute.NOMINAL],
+        m_DatePredictors[Attribute.NOMINAL],
+        m_RelationalPredictors[Attribute.NOMINAL],
+        m_multiInstanceHandler,
+        m_NClasses);
+
+    if (!result[0] && !result[1])
+      fail("Error handling " + m_NClasses + " classes!");
+  }
+
+  /**
+   * checks whether the scheme can handle the class attribute at a given
+   * position (0-based index, -1 means last).
+   *
+   * @param type        the class type
+   * @param position	the position of the class attribute (0-based, -1 means last)
+   * @return            true if the scheme can handle it
+   */
+  protected boolean checkClassAsNthAttribute(int type, int position) {
+    boolean[]     result;
+    String	  indexStr;
+    
+    result = m_Tester.canHandleClassAsNthAttribute(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        position);
+
+    if (position == -1)
+      indexStr = "last";
+    else
+      indexStr = (position + 1) + ".";
+    
+    if (!result[0] && !result[1])
+      fail("Error handling class as " + indexStr + " attribute (" 
+          + getClassTypeString(type) + " class)!");
+    
+    return result[0];
+  }
+
+  /**
+   * Tests whether the scheme can handle class attributes as Nth
+   * attribute. In case of multi-instance handler it performs no tests,
+   * since the multi-instance data has a fixed format (bagID,bag,class).
+   *
+   * @see CheckAttributeSelection#canHandleClassAsNthAttribute(boolean, boolean, boolean, boolean, boolean, boolean, int, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testClassAsNthAttribute() {
+    int           i;
+    
+    // multi-Instance data has fixed format!
+    if (m_multiInstanceHandler)
+      return;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // first attribute
+      m_handleClassAsFirstAttribute[i] = checkClassAsNthAttribute(i, 0);
+
+      // second attribute
+      m_handleClassAsSecondAttribute[i] = checkClassAsNthAttribute(i, 1);
+    }
+  }
+
+  /**
+   * tests whether the scheme can handle zero training instances
+   *
+   * @see CheckAttributeSelection#canHandleZeroTraining(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testZeroTraining() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.canHandleZeroTraining(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Error handling zero training instances (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * checks whether the scheme can handle the given percentage of
+   * missing predictors
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing predictors
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the scheme can handle it
+   */
+  protected boolean checkMissingPredictors(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        true,
+        false,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing predictors (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the scheme can handle missing predictors (20% and 100%)
+   *
+   * @see CheckAttributeSelection#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingPredictors() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingPredictors(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingPredictors[i])
+        checkMissingPredictors(i, 100, true);
+    }
+  }
+
+  /**
+   * checks whether the scheme can handle the given percentage of
+   * missing class labels
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing class labels
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the scheme can handle it
+   */
+  protected boolean checkMissingClass(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        false,
+        true,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing class labels (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the scheme can handle missing class values (20% and
+   * 100%)
+   *
+   * @see CheckAttributeSelection#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingClass() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingClass(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingClass[i])
+        checkMissingClass(i, 100, true);
+    }
+  }
+
+  /**
+   * tests whether the scheme correctly initializes in the
+   * search method
+   *
+   * @see CheckAttributeSelection#correctSearchInitialisation(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testBuildInitialization() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.correctSearchInitialisation(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Incorrect build initialization (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the scheme alters the training set during training.
+   *
+   * @see CheckAttributeSelection#datasetIntegrity(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean)
+   * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+   */
+  public void testDatasetIntegrity() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.datasetIntegrity(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i,
+          m_handleMissingPredictors[i],
+          m_handleMissingClass[i]);
+
+      if (!result[0] && !result[1])
+        fail("Dataset is altered during training (" 
+            + getClassTypeString(i) + " class)!");
+    }
+  }
+
+  /**
+   * Builds a model using the current scheme using the given data.
+   *
+   * @param data 	the instances to test the selection scheme on
+   * @return 		a string containing the results.
+   */
+  protected String useScheme(Instances data) throws Exception {
+    AttributeSelection attsel = null;
+    
+    try {
+      attsel = new AttributeSelection();
+      attsel.setSearch(m_Search);
+      attsel.setEvaluator(m_Evaluator);
+      attsel.setSeed(42);
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Problem setting up attribute selection: " + e);
+    }
+
+    attsel.SelectAttributes(data);
+    
+    return attsel.toResultsString();
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. Currently,
+   * the data is just passed through.
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   */
+  protected Instances process(Instances data) {
+    return data;
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() throws Exception {
+    int		i;
+    boolean	succeeded;
+    Regression 	reg;
+    Instances   train;
+    
+    // don't bother if not working correctly
+    if (m_Tester.hasClasspathProblems())
+      return;
+    
+    reg = new Regression(this.getClass());
+    succeeded = false;
+    train = null;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+        
+      train = m_Tester.makeTestDataset(
+          42, m_Tester.getNumInstances(), 
+  	  m_NominalPredictors[i] ? 2 : 0,
+  	  m_NumericPredictors[i] ? 1 : 0, 
+          m_StringPredictors[i] ? 1 : 0,
+          m_DatePredictors[i] ? 1 : 0,
+          m_RelationalPredictors[i] ? 1 : 0,
+          2, 
+          i,
+          m_multiInstanceHandler);
+  
+      try {
+        m_RegressionResults[i] = useScheme(train);
+        succeeded = true;
+        reg.println(m_RegressionResults[i]);
+      }
+      catch (Exception e) {
+	String msg = e.getMessage().toLowerCase();
+	if (msg.indexOf("not in classpath") > -1)
+	  return;
+
+	m_RegressionResults[i] = null;
+      }
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful predictions for any class type");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkDefaultOptions())
+	fail("Default options were not processed correctly.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractEvaluatorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractEvaluatorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractEvaluatorTest.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.attributeSelection;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.OptionHandler;
+import weka.core.CheckScheme.PostProcessor;
+
+/**
+ * Abstract Test class for evaluator. Internally it uses the
+ * class <code>CheckAttributeSelection</code> to determine success or failure
+ * of the tests. It follows basically the <code>testsPerClassType</code>
+ * method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ *
+ * @see CheckAttributeSelection
+ * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractEvaluatorTest 
+  extends AbstractAttributeSelectionTest {
+  
+  /**
+   * Constructs the <code>AbstractEvaluatorTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractEvaluatorTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * configures the CheckAttributeSelection instance used throughout the tests
+   * 
+   * @return	the fully configured CheckAttributeSelection instance used for testing
+   */
+  protected CheckAttributeSelection getTester() {
+    CheckAttributeSelection	result;
+    
+    result = super.getTester();
+    result.setTestEvaluator(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = super.getOptionTester();
+    if (getEvaluator() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getEvaluator());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * 
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+
+    result = super.getGOETester();
+    result.setObject(getEvaluator());
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/AbstractSearchTest.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.attributeSelection;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.OptionHandler;
+import weka.core.CheckScheme.PostProcessor;
+
+/**
+ * Abstract Test class for search schemes. Internally it uses the
+ * class <code>CheckAttributeSelection</code> to determine success or failure
+ * of the tests. It follows basically the <code>testsPerClassType</code>
+ * method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ *
+ * @see CheckAttributeSelection
+ * @see CheckAttributeSelection#testsPerClassType(int, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractSearchTest 
+  extends AbstractAttributeSelectionTest {
+  
+  /**
+   * Constructs the <code>AbstractSearchTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractSearchTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * configures the CheckAttributeSelection instance used throughout the tests
+   * 
+   * @return	the fully configured CheckAttributeSelection instance used for testing
+   */
+  protected CheckAttributeSelection getTester() {
+    CheckAttributeSelection	result;
+    
+    result = super.getTester();
+    result.setTestEvaluator(false);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = super.getOptionTester();
+    if (getSearch() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getSearch());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * 
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+
+    result = super.getGOETester();
+    result.setObject(getSearch());
+    
+    return result;
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/AllTests.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.attributeSelection;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all attribute selection schemes. Run from the command line with: <p/>
+ * java weka.attributeSelection.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite 	result;
+    
+    result = new TestSuite();
+    result.addTest(suite("weka.attributeSelection.ASSearch"));
+    result.addTest(suite("weka.attributeSelection.ASEvaluation"));
+    
+    return result;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/BestFirstTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/BestFirstTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/BestFirstTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.BestFirstTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class BestFirstTest 
+  extends AbstractSearchTest {
+
+  public BestFirstTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default BestFirst */
+  public ASSearch getSearch() {
+    return new BestFirst();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BestFirstTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/CfsSubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/CfsSubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/CfsSubsetEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.CfsSubsetEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class CfsSubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public CfsSubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default BestFirst */
+  public ASSearch getSearch() {
+    return new BestFirst();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(CfsSubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ChiSquaredAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ChiSquaredAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ChiSquaredAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.ChiSquaredAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class ChiSquaredAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public ChiSquaredAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default ChiSquaredAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new ChiSquaredAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ChiSquaredAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,81 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Tests ClassifierAttributeEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.ClassifierAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5839 $
+ */
+public class ClassifierAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public ClassifierAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a default Ranker.
+   * 
+   * @return		the search algorithm
+   */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** 
+   * Creates a default ClassifierAttributeEval.
+   * 
+   * @return		the evaluator
+   */
+  public ASEvaluation getEvaluator() {
+    return new ClassifierAttributeEval();
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the suite
+   */
+  public static Test suite() {
+    return new TestSuite(ClassifierAttributeEvalTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierSubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierSubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ClassifierSubsetEvalTest.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.ClassifierSubsetEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class ClassifierSubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public ClassifierSubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GreedyStepwise */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a ClassifierSubsetEval with J48 as classifier */
+  public ASEvaluation getEvaluator() {
+    ClassifierSubsetEval eval = new ClassifierSubsetEval();
+    eval.setClassifier(new weka.classifiers.trees.J48());
+    return eval;
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassifierSubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ConsistencySubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ConsistencySubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ConsistencySubsetEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.ConsistencySubsetEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class ConsistencySubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public ConsistencySubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GreedyStepwise */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a default ConsistencySubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new ConsistencySubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ConsistencySubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,87 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.CostMatrix;
+
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CostSensitiveAttributeEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.CostSensitiveAttributeEvalTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 5563 $
+ */
+public class CostSensitiveAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public CostSensitiveAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default evaluator to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // can handle only as much classes as there are in the CostMatrix!
+    m_NClasses = ((CostSensitiveAttributeEval) getEvaluator()).getCostMatrix().numRows();
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default CostSensitiveAttributeEval */
+  public ASEvaluation getEvaluator() {
+    CostSensitiveAttributeEval csse = new CostSensitiveAttributeEval();
+
+    // load cost matrix
+    try {
+      csse.setCostMatrix(
+         new CostMatrix(
+            new InputStreamReader(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/data/ClassifierTest.cost"))));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return csse;
+  }
+
+  public static Test suite() {
+    return new TestSuite(CostSensitiveAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveSubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveSubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/CostSensitiveSubsetEvalTest.java	(revision 29)
@@ -0,0 +1,87 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import weka.classifiers.CostMatrix;
+
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CostSensitiveSubsetEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.CostSensitiveAttributeEvalTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 5563 $
+ */
+public class CostSensitiveSubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public CostSensitiveSubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default evaluator to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // can handle only as much classes as there are in the CostMatrix!
+    m_NClasses = ((CostSensitiveSubsetEval) getEvaluator()).getCostMatrix().numRows();
+  }
+
+  /** Creates a default BestFirst */
+  public ASSearch getSearch() {
+    return new BestFirst();
+  }
+
+  /** Creates a default CostSensitiveSubsetEval */
+  public ASEvaluation getEvaluator() {
+    CostSensitiveSubsetEval csse = new CostSensitiveSubsetEval();
+
+    // load cost matrix
+    try {
+      csse.setCostMatrix(
+         new CostMatrix(
+            new InputStreamReader(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/data/ClassifierTest.cost"))));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return csse;
+  }
+
+  public static Test suite() {
+    return new TestSuite(CostSensitiveSubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ExhaustiveSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ExhaustiveSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ExhaustiveSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ExhaustiveSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.ExhaustiveSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ExhaustiveSearchTest 
+  extends AbstractSearchTest {
+
+  public ExhaustiveSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default ExhaustiveSearch */
+  public ASSearch getSearch() {
+    return new ExhaustiveSearch();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ExhaustiveSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/FCBFSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/FCBFSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/FCBFSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FCBFSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.FCBFSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class FCBFSearchTest 
+  extends AbstractSearchTest {
+
+  public FCBFSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default FCBFSearch */
+  public ASSearch getSearch() {
+    return new FCBFSearch();
+  }
+
+  /** Creates a default SymmetricalUncertAttributeSetEval */
+  public ASEvaluation getEvaluator() {
+    return new SymmetricalUncertAttributeSetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FCBFSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FilteredAttributeEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.FilteredAttributeEvalTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 5563 $
+ */
+public class FilteredAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public FilteredAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default FilteredAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new FilteredAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FilteredAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredSubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredSubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/FilteredSubsetEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FilteredSubsetEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.FilteredSubsetEvalTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 5563 $
+ */
+public class FilteredSubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public FilteredSubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new BestFirst();
+  }
+
+  /** Creates a default FilteredSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new FilteredSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FilteredSubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/GainRatioAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/GainRatioAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/GainRatioAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.GainRatioAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class GainRatioAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public GainRatioAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default GainRatioAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new GainRatioAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GainRatioAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/GeneticSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/GeneticSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/GeneticSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests GeneticSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.GeneticSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class GeneticSearchTest 
+  extends AbstractSearchTest {
+
+  public GeneticSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GeneticSearch */
+  public ASSearch getSearch() {
+    return new GeneticSearch();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GeneticSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/GreedyStepwiseTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/GreedyStepwiseTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/GreedyStepwiseTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests GreedyStepwise. Run from the command line with:<p/>
+ * java weka.attributeSelection.GreedyStepwiseTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class GreedyStepwiseTest 
+  extends AbstractSearchTest {
+
+  public GreedyStepwiseTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GreedyStepwise */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GreedyStepwiseTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/InfoGainAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/InfoGainAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/InfoGainAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.InfoGainAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class InfoGainAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public InfoGainAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default InfoGainAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new InfoGainAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(InfoGainAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/LatentSemanticAnalysisTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/LatentSemanticAnalysisTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/LatentSemanticAnalysisTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LatentSematicAnalysis. Run from the command line with:<p/>
+ * java weka.attributeSelection.LatentSemanticAnalysisTest
+ *
+ * @author Amri Napolitano
+ * @version $Revision: 1.1 $
+ */
+public class LatentSemanticAnalysisTest 
+  extends AbstractEvaluatorTest {
+
+  public LatentSemanticAnalysisTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default LatentSemanticAnalysis */
+  public ASEvaluation getEvaluator() {
+    return new LatentSemanticAnalysis();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LatentSemanticAnalysisTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/LinearForwardSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/LinearForwardSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/LinearForwardSelectionTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LinearForwardSelection. Run from the command line with:<p/>
+ * java weka.attributeSelection.LinearForwardSelectionTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 1.1 $
+ */
+public class LinearForwardSelectionTest 
+  extends AbstractSearchTest {
+
+  public LinearForwardSelectionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GreedyStepwise */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LinearForwardSelectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/OneRAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/OneRAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/OneRAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.OneRAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class OneRAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public OneRAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default OneRAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new OneRAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(OneRAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/PrincipalComponentsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/PrincipalComponentsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/PrincipalComponentsTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.PrincipalComponentsTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class PrincipalComponentsTest 
+  extends AbstractEvaluatorTest {
+
+  public PrincipalComponentsTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default PrincipalComponents */
+  public ASEvaluation getEvaluator() {
+    return new PrincipalComponents();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PrincipalComponentsTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/RaceSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/RaceSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/RaceSearchTest.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RaceSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.RaceSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class RaceSearchTest 
+  extends AbstractSearchTest {
+
+  public RaceSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RaceSearch */
+  public ASSearch getSearch() {
+    return new RaceSearch();
+  }
+
+  /** Creates a ClassifierSubsetEval with J48 */
+  public ASEvaluation getEvaluator() {
+    ClassifierSubsetEval eval = new ClassifierSubsetEval();
+    eval.setClassifier(new weka.classifiers.trees.J48());
+    return eval;
+  }
+
+  public static Test suite() {
+    return new TestSuite(RaceSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/RandomSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/RandomSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/RandomSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.RandomSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomSearchTest 
+  extends AbstractSearchTest {
+
+  public RandomSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomSearch */
+  public ASSearch getSearch() {
+    return new RandomSearch();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/RankSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/RankSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/RankSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RankSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.RankSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RankSearchTest 
+  extends AbstractSearchTest {
+
+  public RankSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RankSearch */
+  public ASSearch getSearch() {
+    return new RankSearch();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RankSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/RankerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/RankerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/RankerTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Ranker. Run from the command line with:<p/>
+ * java weka.attributeSelection.RankerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class RankerTest 
+  extends AbstractSearchTest {
+
+  public RankerTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default InfoGainAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new InfoGainAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RankerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ReliefFAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ReliefFAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ReliefFAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.ReliefFAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class ReliefFAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public ReliefFAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default ReliefFAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new ReliefFAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ReliefFAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/SVMAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/SVMAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/SVMAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.SVMAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class SVMAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public SVMAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default SVMAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new SVMAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SVMAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/ScatterSearchV1Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/ScatterSearchV1Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/ScatterSearchV1Test.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ScatterSearchV1. Run from the command line with:<p/>
+ * java weka.attributeSelection.ScatterSearchV1Test
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class ScatterSearchV1Test 
+  extends AbstractSearchTest {
+
+  public ScatterSearchV1Test(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GeneticSearch */
+  public ASSearch getSearch() {
+    return new ScatterSearchV1();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ScatterSearchV1Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/SignificanceAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/SignificanceAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/SignificanceAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SignificanceAttributeEval. Run from the command line with:<p/>
+ * java weka.attributeSelection.SignificanceAttributeEvalTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class SignificanceAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public SignificanceAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default SignificanceAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new SignificanceAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SignificanceAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/SubsetSizeForwardSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/SubsetSizeForwardSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/SubsetSizeForwardSelectionTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SubsetSizeForwardSelection. Run from the command line with:<p/>
+ * java weka.attributeSelection.SubsetSizeForwardSelectionTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 1.1 $
+ */
+public class SubsetSizeForwardSelectionTest 
+  extends AbstractSearchTest {
+
+  public SubsetSizeForwardSelectionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GreedyStepwise */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SubsetSizeForwardSelectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.SymmetricalUncertAttributeEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class SymmetricalUncertAttributeEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public SymmetricalUncertAttributeEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Ranker */
+  public ASSearch getSearch() {
+    return new Ranker();
+  }
+
+  /** Creates a default SymmetricalUncertAttributeEval */
+  public ASEvaluation getEvaluator() {
+    return new SymmetricalUncertAttributeEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SymmetricalUncertAttributeEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeSetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeSetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/SymmetricalUncertAttributeSetEvalTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.SymmetricalUncertAttributeSetEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class SymmetricalUncertAttributeSetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public SymmetricalUncertAttributeSetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default FCBFSearch */
+  public ASSearch getSearch() {
+    return new FCBFSearch();
+  }
+
+  /** Creates a default SymmetricalUncertAttributeSetEval */
+  public ASEvaluation getEvaluator() {
+    return new SymmetricalUncertAttributeSetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SymmetricalUncertAttributeSetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/TabuSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/TabuSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/TabuSearchTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TabuSearch. Run from the command line with:<p/>
+ * java weka.attributeSelection.TabuSearchTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class TabuSearchTest 
+  extends AbstractSearchTest {
+
+  public TabuSearchTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GeneticSearch */
+  public ASSearch getSearch() {
+    return new TabuSearch();
+  }
+
+  /** Creates a default CfsSubsetEval */
+  public ASEvaluation getEvaluator() {
+    return new CfsSubsetEval();
+  }
+
+  public static Test suite() {
+    return new TestSuite(TabuSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/attributeSelection/WrapperSubsetEvalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/attributeSelection/WrapperSubsetEvalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/attributeSelection/WrapperSubsetEvalTest.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.attributeSelection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BestFirst. Run from the command line with:<p/>
+ * java weka.attributeSelection.WrapperSubsetEvalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class WrapperSubsetEvalTest 
+  extends AbstractEvaluatorTest {
+
+  public WrapperSubsetEvalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default BestFirst */
+  public ASSearch getSearch() {
+    return new GreedyStepwise();
+  }
+
+  /** Creates a WrapperSubsetEval with J48 */
+  public ASEvaluation getEvaluator() {
+    WrapperSubsetEval eval = new WrapperSubsetEval();
+    eval.setClassifier(new weka.classifiers.trees.J48());
+    return eval;
+  }
+
+  public static Test suite() {
+    return new TestSuite(WrapperSubsetEvalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/AbstractClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/AbstractClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/AbstractClassifierTest.java	(revision 29)
@@ -0,0 +1,1049 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002-2006 University of Waikato 
+ */
+
+package weka.classifiers;
+
+import weka.classifiers.evaluation.EvaluationUtils;
+import weka.core.Attribute;
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.CheckScheme.PostProcessor;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Classifiers. Internally it uses the class
+ * <code>CheckClassifier</code> to determine success or failure of the
+ * tests. It follows basically the <code>testsPerClassType</code> method.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.23 $
+ *
+ * @see CheckClassifier
+ * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractClassifierTest 
+  extends TestCase {
+  
+  /** a class for postprocessing the test-data: all values of numeric attributs
+   * are replaced with their absolute value */
+  public static class AbsPostProcessor 
+    extends PostProcessor {
+    
+    /**
+     * initializes the PostProcessor
+     */
+    public AbsPostProcessor() {
+      super();
+    }
+    
+    /**
+     * Provides a hook for derived classes to further modify the data. Currently,
+     * the data is just passed through.
+     * 
+     * @param data	the data to process
+     * @return		the processed data
+     */
+    public Instances process(Instances data) {
+      Instances	result;
+      int		i;
+      int		n;
+      
+      result = super.process(data);
+      
+      for (i = 0; i < result.numAttributes(); i++) {
+        if (i == result.classIndex())
+  	continue;
+        if (!result.attribute(i).isNumeric())
+  	continue;
+        
+        for (n = 0; n < result.numInstances(); n++)
+  	result.instance(n).setValue(i, Math.abs(result.instance(n).value(i)));
+      }
+      
+      return result;
+    }
+  }
+  
+  /** The classifier to be tested */
+  protected Classifier m_Classifier;
+
+  /** For testing the classifier */
+  protected CheckClassifier m_Tester;
+  
+  /** whether classifier is updateable */
+  protected boolean m_updateableClassifier;
+
+  /** whether classifier handles weighted instances */
+  protected boolean m_weightedInstancesHandler;
+
+  /** whether classifier handles multi-instance data */
+  protected boolean m_multiInstanceHandler;
+
+  /** the number of classes to test with testNClasses() 
+   * @see #testNClasses() */
+  protected int m_NClasses;
+
+  /** whether to run CheckClassifier in DEBUG mode */
+  protected boolean DEBUG = false;
+
+  /** the attribute type with the lowest value */
+  protected final static int FIRST_CLASSTYPE = Attribute.NUMERIC;
+
+  /** the attribute type with the highest value */
+  protected final static int LAST_CLASSTYPE = Attribute.RELATIONAL;
+  
+  /** wether classifier can predict nominal attributes (array index is attribute type of class) */
+  protected boolean[] m_NominalPredictors;
+  
+  /** wether classifier can predict numeric attributes (array index is attribute type of class) */
+  protected boolean[] m_NumericPredictors;
+  
+  /** wether classifier can predict string attributes (array index is attribute type of class) */
+  protected boolean[] m_StringPredictors;
+  
+  /** wether classifier can predict date attributes (array index is attribute type of class) */
+  protected boolean[] m_DatePredictors;
+  
+  /** wether classifier can predict relational attributes (array index is attribute type of class) */
+  protected boolean[] m_RelationalPredictors;
+  
+  /** whether classifier handles missing values */
+  protected boolean[] m_handleMissingPredictors;
+
+  /** whether classifier handles class with only missing values */
+  protected boolean[] m_handleMissingClass;
+
+  /** whether classifier handles class as first attribute */
+  protected boolean[] m_handleClassAsFirstAttribute;
+
+  /** whether classifier handles class as second attribute */
+  protected boolean[] m_handleClassAsSecondAttribute;
+  
+  /** the results of the regression tests */
+  protected FastVector[] m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractClassifierTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractClassifierTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckClassifier datasets, currently
+   * only null.
+   * 
+   * @return		a custom PostProcessor, if necessary
+   * @see PostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return null;
+  }
+  
+  /**
+   * configures the CheckClassifier instance used throughout the tests
+   * 
+   * @return	the fully configured CheckClassifier instance used for testing
+   */
+  protected CheckClassifier getTester() {
+    CheckClassifier	result;
+    
+    result = new CheckClassifier();
+    result.setSilent(true);
+    result.setClassifier(m_Classifier);
+    result.setNumInstances(20);
+    result.setDebug(DEBUG);
+    result.setPostProcessor(getPostProcessor());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the classifier return from the getClassifier() method.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getClassifier()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    result.setOptionHandler((OptionHandler) getClassifier());
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Classifier returned from the getClassifier() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getClassifier()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getClassifier());
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Classifier   = getClassifier();
+    m_Tester       = getTester();
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+
+    m_updateableClassifier         = m_Tester.updateableClassifier()[0];
+    m_weightedInstancesHandler     = m_Tester.weightedInstancesHandler()[0];
+    m_multiInstanceHandler         = m_Tester.multiInstanceHandler()[0];
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new FastVector[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+
+    // initialize attributes
+    checkAttributes(true,  false, false, false, false, false);
+    checkAttributes(false, true,  false, false, false, false);
+    checkAttributes(false, false, true,  false, false, false);
+    checkAttributes(false, false, false, true,  false, false);
+    checkAttributes(false, false, false, false, true,  false);
+    
+    // initialize missing values handling
+    for (int i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      m_handleMissingPredictors[i] = checkMissingPredictors(i, 20, false);
+      m_handleMissingClass[i]      = checkMissingClass(i, 20, false);
+    }
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Classifier   = null;
+    m_Tester       = null;
+    m_OptionTester = null;
+    m_GOETester    = null;
+
+    m_updateableClassifier         = false;
+    m_weightedInstancesHandler     = false;
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new FastVector[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+  }
+
+  /**
+   * Used to create an instance of a specific classifier.
+   *
+   * @return a suitably configured <code>Classifier</code> value
+   */
+  public abstract Classifier getClassifier();
+
+  /**
+   * checks whether at least one attribute type can be handled with the
+   * given class type
+   *
+   * @param type      the class type to check for
+   * @return          true if at least one attribute type can be predicted with
+   *                  the given class
+   */
+  protected boolean canPredict(int type) {
+    return    m_NominalPredictors[type]
+           || m_NumericPredictors[type]
+           || m_StringPredictors[type]
+           || m_DatePredictors[type]
+           || m_RelationalPredictors[type];
+  }
+
+  /** 
+   * returns a string for the class type
+   * 
+   * @param type        the class type
+   * @return            the class type as string
+   */
+  protected String getClassTypeString(int type) {
+    return CheckClassifier.attributeTypeToString(type);
+  }
+
+  /**
+   * tests whether the classifier can handle certain attributes and if not,
+   * if the exception is OK
+   *
+   * @param nom         to check for nominal attributes
+   * @param num         to check for numeric attributes
+   * @param str         to check for string attributes
+   * @param dat         to check for date attributes
+   * @param rel         to check for relational attributes
+   * @param allowFail   whether a junit fail can be executed
+   * @see CheckClassifier#canPredict(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  protected void checkAttributes(boolean nom, boolean num, boolean str, 
+                                 boolean dat, boolean rel,
+                                 boolean allowFail) {
+    boolean[]     result;
+    String        att;
+    int           i;
+
+    // determine text for type of attributes
+    att = "";
+    if (nom)
+      att = "nominal";
+    else if (num)
+      att = "numeric";
+    else if (str)
+      att = "string";
+    else if (dat)
+      att = "date";
+    else if (rel)
+      att = "relational";
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      result = m_Tester.canPredict(nom, num, str, dat, rel, m_multiInstanceHandler, i);
+      if (nom)
+        m_NominalPredictors[i] = result[0];
+      else if (num)
+        m_NumericPredictors[i] = result[0];
+      else if (str)
+        m_StringPredictors[i] = result[0];
+      else if (dat)
+        m_DatePredictors[i] = result[0];
+      else if (rel)
+        m_RelationalPredictors[i] = result[0];
+
+      if (!result[0] && !result[1] && allowFail)
+        fail("Error handling " + att + " attributes (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the toString method of the classifier works even though the
+   * classifier hasn't been built yet.
+   */
+  public void testToString() {
+    boolean[]     result;
+
+    result = m_Tester.testToString();
+
+    if (!result[0])
+      fail("Error in toString() method!");
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean[]     result;
+
+    result = m_Tester.declaresSerialVersionUID();
+
+    if (!result[0])
+      fail("Doesn't declare serialVersionUID!");
+  }
+  
+  /**
+   * tests whether the classifier can handle different types of attributes and
+   * if not, if the exception is OK
+   *
+   * @see #checkAttributes(boolean, boolean, boolean, boolean, boolean, boolean, boolean)
+   */
+  public void testAttributes() {
+    // nominal
+    checkAttributes(true,  false, false, false, false, true);
+    // numeric
+    checkAttributes(false, true,  false, false, false, true);
+    // string
+    checkAttributes(false, false, true,  false, false, true);
+    // date
+    checkAttributes(false, false, false, true,  false, true);
+    // relational
+    if (!m_multiInstanceHandler)
+      checkAttributes(false, false, false, false, true,  true);
+  }
+
+  /**
+   * tests whether the classifier handles instance weights correctly
+   *
+   * @see CheckClassifier#instanceWeights(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testInstanceWeights() {
+    boolean[]     result;
+    int           i;
+    
+    if (m_weightedInstancesHandler) {
+      for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+        // does the classifier support this type of class at all?
+        if (!canPredict(i))
+          continue;
+        
+        result = m_Tester.instanceWeights(
+            m_NominalPredictors[i], 
+            m_NumericPredictors[i], 
+            m_StringPredictors[i], 
+            m_DatePredictors[i], 
+            m_RelationalPredictors[i], 
+            m_multiInstanceHandler, 
+            i);
+
+        if (!result[0])
+          System.err.println("Error handling instance weights (" + getClassTypeString(i) 
+              + " class)!");
+      }
+    }
+  }
+
+  /**
+   * tests whether classifier handles data containing only a class attribute
+   *
+   * @see CheckClassifier#canHandleOnlyClass(boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testOnlyClass() {
+    boolean[]	result;
+    int		i;
+
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.canHandleOnlyClass(
+	  m_NominalPredictors[i],
+	  m_NumericPredictors[i],
+	  m_StringPredictors[i],
+	  m_DatePredictors[i],
+	  m_RelationalPredictors[i],
+	  i);
+
+      if (!result[0] && !result[1])
+	      fail("Error handling data containing only the class!");
+    }
+  }
+
+  /**
+   * tests whether classifier handles N classes
+   *
+   * @see CheckClassifier#canHandleNClasses(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   * @see #m_NClasses
+   */
+  public void testNClasses() {
+    boolean[]     result;
+
+    if (!canPredict(Attribute.NOMINAL))
+      return;
+
+    result = m_Tester.canHandleNClasses(
+        m_NominalPredictors[Attribute.NOMINAL],
+        m_NumericPredictors[Attribute.NOMINAL],
+        m_StringPredictors[Attribute.NOMINAL],
+        m_DatePredictors[Attribute.NOMINAL],
+        m_RelationalPredictors[Attribute.NOMINAL],
+        m_multiInstanceHandler,
+        m_NClasses);
+
+    if (!result[0] && !result[1])
+      fail("Error handling " + m_NClasses + " classes!");
+  }
+
+  /**
+   * checks whether the classifier can handle the class attribute at a given
+   * position (0-based index, -1 means last).
+   *
+   * @param type        the class type
+   * @param position	the position of the class attribute (0-based, -1 means last)
+   * @return            true if the classifier can handle it
+   */
+  protected boolean checkClassAsNthAttribute(int type, int position) {
+    boolean[]     result;
+    String	  indexStr;
+    
+    result = m_Tester.canHandleClassAsNthAttribute(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        position);
+
+    if (position == -1)
+      indexStr = "last";
+    else
+      indexStr = (position + 1) + ".";
+    
+    if (!result[0] && !result[1])
+      fail("Error handling class as " + indexStr + " attribute (" 
+          + getClassTypeString(type) + " class)!");
+    
+    return result[0];
+  }
+
+  /**
+   * Tests whether the classifier can handle class attributes as Nth
+   * attribute. In case of multi-instance classifiers it performs no tests,
+   * since the multi-instance data has a fixed format (bagID,bag,class).
+   *
+   * @see CheckClassifier#canHandleClassAsNthAttribute(boolean, boolean, boolean, boolean, boolean, boolean, int, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testClassAsNthAttribute() {
+    int           i;
+    
+    // multi-Instance data has fixed format!
+    if (m_multiInstanceHandler)
+      return;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // first attribute
+      m_handleClassAsFirstAttribute[i] = checkClassAsNthAttribute(i, 0);
+
+      // second attribute
+      m_handleClassAsSecondAttribute[i] = checkClassAsNthAttribute(i, 1);
+    }
+  }
+
+  /**
+   * tests whether the classifier can handle zero training instances
+   *
+   * @see CheckClassifier#canHandleZeroTraining(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testZeroTraining() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.canHandleZeroTraining(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Error handling zero training instances (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * checks whether the classifier can handle the given percentage of
+   * missing predictors
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing predictors
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the classifier can handle it
+   */
+  protected boolean checkMissingPredictors(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        true,
+        false,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing predictors (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the classifier can handle missing predictors (20% and 100%)
+   *
+   * @see CheckClassifier#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testMissingPredictors() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingPredictors(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingPredictors[i])
+        checkMissingPredictors(i, 100, true);
+    }
+  }
+
+  /**
+   * checks whether the classifier can handle the given percentage of
+   * missing class labels
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing class labels
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the classifier can handle it
+   */
+  protected boolean checkMissingClass(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        false,
+        true,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing class labels (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the classifier can handle missing class values (20% and
+   * 100%)
+   *
+   * @see CheckClassifier#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testMissingClass() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingClass(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingClass[i])
+        checkMissingClass(i, 100, true);
+    }
+  }
+
+  /**
+   * tests whether the classifier correctly initializes in the
+   * buildClassifier method
+   *
+   * @see CheckClassifier#correctBuildInitialisation(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testBuildInitialization() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.correctBuildInitialisation(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Incorrect build initialization (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the classifier alters the training set during training.
+   *
+   * @see CheckClassifier#datasetIntegrity(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testDatasetIntegrity() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.datasetIntegrity(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i,
+          m_handleMissingPredictors[i],
+          m_handleMissingClass[i]);
+
+      if (!result[0] && !result[1])
+        fail("Training set is altered during training (" 
+            + getClassTypeString(i) + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the classifier erroneously uses the class value of test
+   * instances (if provided)
+   *
+   * @see CheckClassifier#doesntUseTestClassVal(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testUseOfTestClassValue() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.doesntUseTestClassVal(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0])
+        fail("Uses test class values (" + getClassTypeString(i) + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the classifier produces the same model when trained
+   * incrementally as when batch trained.
+   *
+   * @see CheckClassifier#updatingEquality(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClassifier#testsPerClassType(int, boolean, boolean, boolean)
+   */
+  public void testUpdatingEquality() {
+    boolean[]     result;
+    int           i;
+    
+    if (m_updateableClassifier) {
+      for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+        // does the classifier support this type of class at all?
+        if (!canPredict(i))
+          continue;
+        
+        result = m_Tester.updatingEquality(
+            m_NominalPredictors[i], 
+            m_NumericPredictors[i], 
+            m_StringPredictors[i], 
+            m_DatePredictors[i], 
+            m_RelationalPredictors[i], 
+            m_multiInstanceHandler, 
+            i);
+
+        if (!result[0])
+          System.err.println("Incremental training does not produce same result as "
+              + "batch training (" + getClassTypeString(i) + " class)!");
+      }
+    }
+  }
+
+  /**
+   * Builds a model using the current classifier using the first
+   * half of the current data for training, and generates a bunch of
+   * predictions using the remaining half of the data for testing.
+   *
+   * @param data 	the instances to test the classifier on
+   * @return a <code>FastVector</code> containing the predictions.
+   */
+  protected FastVector useClassifier(Instances data) throws Exception {
+    Classifier dc = null;
+    int tot = data.numInstances();
+    int mid = tot / 2;
+    Instances train = null;
+    Instances test = null;
+    EvaluationUtils evaluation = new EvaluationUtils();
+    
+    try {
+      train = new Instances(data, 0, mid);
+      test = new Instances(data, mid, tot - mid);
+      dc = m_Classifier;
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Problem setting up to use classifier: " + e);
+    }
+
+    do {
+      try {
+	return evaluation.getTrainTestPredictions(dc, train, test);
+      } 
+      catch (IllegalArgumentException e) {
+	String msg = e.getMessage();
+	if (msg.indexOf("Not enough instances") != -1) {
+	  System.err.println("\nInflating training data.");
+	  Instances trainNew = new Instances(train);
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    trainNew.add(train.instance(i));
+	  }
+	  train = trainNew;
+	} 
+	else {
+	  throw e;
+	}
+      }
+    } while (true);
+  }
+
+  /**
+   * Returns a string containing all the predictions.
+   *
+   * @param predictions a <code>FastVector</code> containing the predictions
+   * @return a <code>String</code> representing the vector of predictions.
+   */
+  public static String predictionsToString(FastVector predictions) {
+    StringBuffer sb = new StringBuffer();
+    sb.append(predictions.size()).append(" predictions\n");
+    for (int i = 0; i < predictions.size(); i++) {
+      sb.append(predictions.elementAt(i)).append('\n');
+    }
+    return sb.toString();
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. Currently,
+   * the data is just passed through.
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   */
+  protected Instances process(Instances data) {
+    return data;
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() throws Exception {
+    int		i;
+    boolean	succeeded;
+    Regression 	reg;
+    Instances   train;
+    
+    // don't bother if not working correctly
+    if (m_Tester.hasClasspathProblems())
+      return;
+    
+    reg = new Regression(this.getClass());
+    succeeded = false;
+    train = null;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the classifier support this type of class at all?
+      if (!canPredict(i))
+        continue;
+        
+      train = m_Tester.makeTestDataset(
+          42, m_Tester.getNumInstances(), 
+  	  m_NominalPredictors[i] ? m_Tester.getNumNominal() : 0,
+  	  m_NumericPredictors[i] ? m_Tester.getNumNumeric() : 0, 
+          m_StringPredictors[i] ? m_Tester.getNumString() : 0,
+          m_DatePredictors[i] ? m_Tester.getNumDate() : 0,
+          m_RelationalPredictors[i] ? m_Tester.getNumRelational() : 0,
+          2, 
+          i,
+          m_multiInstanceHandler);
+  
+      try {
+        m_RegressionResults[i] = useClassifier(train);
+        succeeded = true;
+        reg.println(predictionsToString(m_RegressionResults[i]));
+      }
+      catch (Exception e) {
+	String msg = e.getMessage().toLowerCase();
+	if (msg.indexOf("not in classpath") > -1)
+	  return;
+
+	m_RegressionResults[i] = null;
+      }
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful predictions for any class type");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (!m_OptionTester.checkListOptions())
+      fail("Options cannot be listed via listOptions.");
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("setOptions method failed.");
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (!m_OptionTester.checkDefaultOptions())
+      fail("Default options were not processed correctly.");
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (!m_OptionTester.checkRemainingOptions())
+      fail("There were 'left-over' options.");
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (!m_OptionTester.checkCanonicalUserOptions())
+      fail("setOptions method failed");
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("Resetting of options failed");
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/AllTests.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.classifiers;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all classifiers. Run from the command line with: <p/>
+ * java weka.classifiers.AllTests
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.15 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    
+    suite.addTest(weka.classifiers.pmml.consumer.AllTests.suite());
+    suite.addTest(suite("weka.classifiers.Classifier"));
+    suite.addTest(suite("weka.classifiers.functions.supportVector.Kernel"));
+
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODETest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODETest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODETest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AODE. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.AODE
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AODETest 
+  extends AbstractClassifierTest {
+
+  public AODETest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default AODE */
+  public Classifier getClassifier() {
+    return new AODE();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AODETest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODEsrTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODEsrTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/AODEsrTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AODEsr. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.AODE
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.1 $
+ */
+public class AODEsrTest 
+  extends AbstractClassifierTest {
+
+  public AODEsrTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default AODE */
+  public Classifier getClassifier() {
+    return new AODEsr();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AODEsrTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesNetTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesNetTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesNetTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BayesNet. Run from the command line with:<p>
+ * java weka.classifiers.bayes.BayesNetTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class BayesNetTest extends AbstractClassifierTest {
+
+  public BayesNetTest(String name) { super(name);  }
+
+  /** Creates a default BayesNet */
+  public Classifier getClassifier() {
+    return new BayesNet();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BayesNetTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesianLogisticRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesianLogisticRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/BayesianLogisticRegressionTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BayesianLogisticRegression. Run from the command line with:<p>
+ * java weka.classifiers.bayes.BayesianLogisticRegressionTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 1.1 $
+ */
+public class BayesianLogisticRegressionTest extends AbstractClassifierTest {
+
+  public BayesianLogisticRegressionTest(String name) { super(name);  }
+
+  /** Creates a default BayesianLogisticRegression */
+  public Classifier getClassifier() {
+    return new BayesianLogisticRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BayesianLogisticRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/ComplementNaiveBayesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/ComplementNaiveBayesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/ComplementNaiveBayesTest.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.core.CheckScheme.PostProcessor;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ComplementNaiveBayes. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.ComplementNaiveBayesTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class ComplementNaiveBayesTest 
+  extends AbstractClassifierTest {
+
+  public ComplementNaiveBayesTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default ComplementNaiveBayes */
+  public Classifier getClassifier() {
+    return new ComplementNaiveBayes();
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckClassifier datasets..
+   * 
+   * @return		a custom PostProcessor
+   * @see AbsPostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return new AbsPostProcessor();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ComplementNaiveBayesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/DMNBtextTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/DMNBtextTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/DMNBtextTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DMNBtext. Run from the command line with:<p>
+ * java weka.classifiers.bayes.DMNBtextTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class DMNBtextTest extends AbstractClassifierTest {
+
+  public DMNBtextTest(String name) { super(name);  }
+
+  /** Creates a default DMNBtext */
+  public Classifier getClassifier() {
+    return new DMNBtext();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DMNBtextTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/HNBTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/HNBTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/HNBTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests HNB. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.HNB
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class HNBTest 
+  extends AbstractClassifierTest {
+
+  public HNBTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default HNB */
+  public Classifier getClassifier() {
+    return new HNB();
+  }
+
+  public static Test suite() {
+    return new TestSuite(HNBTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialTest.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.core.CheckScheme.PostProcessor;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NaiveBayesMultinomial. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.NaiveBayesMultinomial
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class NaiveBayesMultinomialTest 
+  extends AbstractClassifierTest {
+
+  public NaiveBayesMultinomialTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NaiveBayesMultinomial */
+  public Classifier getClassifier() {
+    return new NaiveBayesMultinomial();
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckClassifier datasets..
+   * 
+   * @return		a custom PostProcessor
+   * @see AbsPostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return new AbsPostProcessor();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NaiveBayesMultinomialTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateableTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateableTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesMultinomialUpdateableTest.java	(revision 29)
@@ -0,0 +1,66 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.core.CheckScheme.PostProcessor;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NaiveBayesMultinomialUpdateable. Run from the command line with: <p/>
+ * java weka.classifiers.bayes.NaiveBayesMultinomialUpdateable
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class NaiveBayesMultinomialUpdateableTest 
+  extends AbstractClassifierTest {
+
+  public NaiveBayesMultinomialUpdateableTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NaiveBayesMultinomialUpdateable */
+  public Classifier getClassifier() {
+    return new NaiveBayesMultinomialUpdateable();
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckClassifier datasets..
+   * 
+   * @return		a custom PostProcessor
+   * @see AbsPostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return new AbsPostProcessor();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NaiveBayesMultinomialUpdateableTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesSimpleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesSimpleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesSimpleTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NaiveBayesSimple. Run from the command line with:<p>
+ * java weka.classifiers.bayes.NaiveBayesSimpleTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class NaiveBayesSimpleTest extends AbstractClassifierTest {
+
+  public NaiveBayesSimpleTest(String name) { super(name);  }
+
+  /** Creates a default NaiveBayesSimple */
+  public Classifier getClassifier() {
+    return new NaiveBayesSimple();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NaiveBayesSimpleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NaiveBayes. Run from the command line with:<p>
+ * java weka.classifiers.bayes.NaiveBayesTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class NaiveBayesTest extends AbstractClassifierTest {
+
+  public NaiveBayesTest(String name) { super(name);  }
+
+  /** Creates a default NaiveBayes */
+  public Classifier getClassifier() {
+    return new NaiveBayes();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NaiveBayesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesUpdateableTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesUpdateableTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/NaiveBayesUpdateableTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NaiveBayesUpdateable. Run from the command line with:<p>
+ * java weka.classifiers.bayes.NaiveBayesUpdateableTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class NaiveBayesUpdateableTest extends AbstractClassifierTest {
+
+  public NaiveBayesUpdateableTest(String name) { super(name);  }
+
+  /** Creates a default NaiveBayesUpdateable */
+  public Classifier getClassifier() {
+    return new NaiveBayesUpdateable();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NaiveBayesUpdateableTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/bayes/WAODETest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/bayes/WAODETest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/bayes/WAODETest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.bayes;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests WAODE. Run from the command line with:<p/>
+ * java weka.classifiers.bayes.WAODE
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class WAODETest 
+  extends AbstractClassifierTest {
+
+  public WAODETest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default WAODE */
+  public Classifier getClassifier() {
+    return new WAODE();
+  }
+
+  public static Test suite() {
+    return new TestSuite(WAODETest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.arff	(revision 29)
@@ -0,0 +1,29 @@
+@relation ClassifierTest
+@attribute StringAtt1 string
+@attribute NominalAtt1 {r, g}
+@attribute NumericAtt1 numeric
+@attribute StringAtt2 string
+@attribute NominalAtt2 {a, b, c, d}
+@attribute NumericAtt2 numeric
+@data
+humpty ,g , 1.0 ,the   ,a ,-2.3
+dumpty ,g , 2.0 ,quick ,b ,-3.3
+sat    ,r , 3.0 ,brown ,c ,-2.4
+on     ,r , 4.0 ,fox   ,d ,-5.3
+a      ,g , 5.0 ,jumped,a ,-2.6
+wall   ,r , 6.0 ,over  ,b ,-7.3
+humpty ,r , 7.0 ,the   ,c ,-2.8
+dumpty ,g , 8.0 ,lazy  ,d ,-9.3
+had    ,g , 9.0 ,dog   ,? ,-2.0
+a      ,r , 9.4 ,?     ,? ,-9.0
+great  ,r , 1.4 ,the   ,a ,-8.3
+fall   ,g , 2.3 ,quick ,b ,-7.3
+all    ,r , 3.3 ,brown ,c ,?
+the    ,r , 4.3 ,fox   ,d ,-5.3
+kings  ,g , 5.3 ,jumped,? ,-5.6
+horses ,g , 6.5 ,over  ,b ,-4.3
+and    ,r , 7.5 ,the   ,c ,-3.8
+all    ,r , 8.5 ,lazy  ,d ,-2.3
+the    ,r , 9.4 ,?     ,a ,-1.0
+{0 wall, 2 4.3, 4 d}
+{1 g, 3 lazy, 5 3.4}
Index: branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.cost
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.cost	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/data/ClassifierTest.cost	(revision 29)
@@ -0,0 +1,5 @@
+% Rows	Columns
+2	2
+% Matrix elements
+0.0	5.0	
+1.0	0.0	
Index: branches/MetisMQI/src/test/java/weka/classifiers/data/test.matrix
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/data/test.matrix	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/data/test.matrix	(revision 29)
@@ -0,0 +1,151 @@
+150 150
+40.26 37.49 37.03 36.45 40.1 43.65 37.38 39.54 34.59 37.96 42.63 38.66 36.96 33.99 45.3 46.65 43.09 40.28 44.81 41.47 41.86 41.14 37.5 40.04 39.08 38.28 39.72 40.91 40.42 37.45 37.61 41.62 42.99 44.75 37.96 38.42 42.16 37.96 34.8 40.05 39.63 32.88 35.5 40.11 42.05 37 41.59 36.66 42.12 39.05 53.76 50.44 53.2 41.96 49.69 45.43 50.58 38.21 50.51 41.71 37.6 46.77 44.1 48.12 44.01 51.46 45.66 44.97 45.92 42.99 48.37 46.77 48.04 47.73 49.07 50.6 51.48 52.01 47.35 43.27 41.99 41.83 44.73 47.51 44.64 49.12 51.9 46.6 45.06 42.66 43.55 48.33 44.52 38.37 44.15 45.69 45.36 48.05 39.18 44.87 52.58 46.55 55.39 50.48 52.21 58.92 40.38 56.56 51.4 58.36 51.89 49.89 53.3 45.22 47 51.72 51.71 62.39 58.49 45.6 54.83 45.62 58.85 48.8 54.12 56.68 48.5 48.83 50.7 55.66 56.46 62.95 50.72 49.37 48.33 58.77 52.35 51.55 48.18 54.02 53.34 53.64 46.55 54.6 54.2 52.41 48.26 51.33 51.54 48.09
+37.49 35.01 34.49 33.98 37.3 40.62 34.76 36.84 32.26 35.43 39.7 36 34.5 31.63 42.14 43.31 40.06 37.51 41.77 38.55 39.08 38.27 34.78 37.37 36.42 35.78 37.02 38.12 37.68 34.91 35.1 38.84 39.9 41.55 35.43 35.82 39.31 35.43 32.42 37.33 36.88 30.83 33.02 37.36 39.13 34.54 38.67 34.14 39.21 36.4 50.76 47.56 50.27 39.71 46.99 42.89 47.67 36.03 47.74 39.32 35.6 44.09 41.8 45.45 41.44 48.57 43.04 42.46 43.58 40.62 45.59 44.15 45.53 45.11 46.34 47.78 48.72 49.17 44.7 40.83 39.69 39.53 42.22 44.96 42.06 46.22 49.01 44.19 42.44 40.31 41.15 45.61 42.06 36.22 41.68 43.05 42.77 45.36 36.91 42.33 49.67 44.04 52.47 47.77 49.41 55.9 38.15 53.65 48.81 55.12 48.99 47.26 50.44 42.83 44.44 48.84 48.91 58.95 55.65 43.3 51.85 43.1 55.91 46.19 51.13 53.64 45.86 46.11 48.02 52.72 53.58 59.47 48.04 46.71 45.81 55.73 49.39 48.72 45.48 51.09 50.45 50.71 44.04 51.64 51.21 49.57 45.75 48.53 48.6 45.41
+37.03 34.49 34.06 33.53 36.88 40.15 34.38 36.37 31.82 34.92 39.21 35.56 34 31.26 41.66 42.9 39.63 37.05 41.22 38.14 38.51 37.84 34.48 36.84 35.95 35.22 36.54 37.63 37.18 34.45 34.6 38.29 39.53 41.15 34.92 35.34 38.78 34.92 32.01 36.84 36.45 30.26 32.65 36.9 38.68 34.04 38.25 33.72 38.74 35.92 49.53 46.47 49.02 38.67 45.79 41.86 46.6 35.2 46.54 38.43 34.65 43.09 40.64 44.34 40.54 47.41 42.07 41.43 42.33 39.61 44.57 43.09 44.28 43.98 45.21 46.62 47.44 47.93 43.63 39.86 38.69 38.54 41.21 43.79 41.13 45.25 47.82 42.95 41.51 39.31 40.13 44.53 41.02 35.35 40.68 42.09 41.79 44.27 36.09 41.34 48.47 42.91 51.06 46.53 48.13 54.32 37.22 52.14 47.39 53.79 47.82 45.99 49.13 41.69 43.33 47.67 47.66 57.5 53.94 42.04 50.54 42.05 54.26 44.98 49.88 52.24 44.7 45 46.74 51.3 52.05 58.01 46.76 45.5 44.55 54.18 48.25 47.51 44.4 49.79 49.17 49.44 42.91 50.33 49.96 48.31 44.49 47.31 47.5 44.32
+36.45 33.98 33.53 33.06 36.3 39.56 33.86 35.83 31.37 34.42 38.6 35.06 33.5 30.75 40.92 42.19 38.96 36.47 40.61 37.55 37.97 37.26 33.86 36.34 35.51 34.74 36.02 37.06 36.6 33.98 34.13 37.71 38.9 40.46 34.42 34.76 38.14 34.42 31.53 36.29 35.86 29.84 32.15 36.37 38.17 33.54 37.68 33.22 38.14 35.37 49.45 46.41 49 38.69 45.78 41.91 46.58 35.13 46.51 38.42 34.65 43.04 40.62 44.38 40.41 47.31 42.11 41.4 42.39 39.58 44.62 43 44.38 44.03 45.14 46.54 47.44 47.96 43.64 39.73 38.66 38.49 41.14 43.94 41.19 45.21 47.78 42.97 41.47 39.31 40.2 44.54 40.98 35.28 40.69 42.06 41.77 44.22 35.93 41.31 48.71 43.08 51.23 46.73 48.34 54.58 37.38 52.38 47.63 53.93 47.87 46.14 49.25 41.87 43.49 47.77 47.81 57.69 54.29 42.22 50.67 42.19 54.55 45.06 50.02 52.4 44.76 45.07 46.94 51.44 52.25 58.12 46.96 45.61 44.8 54.33 48.4 47.66 44.46 49.87 49.31 49.46 43.08 50.51 50.1 48.38 44.61 47.4 47.62 44.45
+40.1 37.3 36.88 36.3 39.96 43.5 37.26 39.38 34.44 37.78 42.46 38.52 36.78 33.86 45.12 46.52 42.94 40.12 44.62 41.34 41.66 41 37.4 39.86 38.94 38.08 39.56 40.74 40.24 37.3 37.44 41.42 42.88 44.62 37.78 38.24 41.96 37.78 34.66 39.88 39.48 32.66 35.38 39.96 41.92 36.82 41.46 36.52 41.96 38.88 53.38 50.12 52.82 41.64 49.32 45.14 50.28 37.96 50.14 41.46 37.3 46.48 43.72 47.8 43.74 51.1 45.4 44.66 45.52 42.68 48.1 46.44 47.66 47.4 48.72 50.24 51.08 51.64 47.04 42.96 41.68 41.52 44.42 47.18 44.4 48.86 51.54 46.2 44.8 42.36 43.26 48.02 44.2 38.1 43.86 45.42 45.08 47.72 38.92 44.58 52.28 46.24 54.98 50.14 51.86 58.46 40.14 56.12 50.98 58 51.56 49.52 52.92 44.9 46.7 51.4 51.36 62 57.98 45.22 54.46 45.34 58.36 48.44 53.78 56.28 48.16 48.52 50.34 55.24 56 62.54 50.36 49.02 47.98 58.3 52.06 51.22 47.88 53.64 52.98 53.26 46.24 54.24 53.86 52.04 47.88 50.98 51.26 47.8
+43.65 40.62 40.15 39.56 43.5 47.42 40.6 42.89 37.53 41.14 46.22 41.98 40.04 36.83 49.04 50.65 46.74 43.69 48.61 45.03 45.39 44.68 40.66 43.5 42.49 41.5 43.14 44.36 43.8 40.66 40.81 45.13 46.66 48.54 41.14 41.6 45.64 41.14 37.75 43.43 42.98 35.6 38.53 43.61 45.75 40.12 45.16 39.78 45.68 42.33 58.83 55.29 58.28 45.99 54.44 49.87 55.52 41.83 55.29 45.8 41.15 51.3 48.18 52.8 48.19 56.31 50.19 49.22 50.31 47.06 53.22 51.18 52.7 52.33 53.7 55.38 56.36 57.06 51.96 47.27 45.96 45.75 48.96 52.24 49.11 53.95 56.86 50.99 49.43 46.77 47.8 53.02 48.74 41.98 48.43 50.1 49.75 52.62 42.83 49.19 58.09 51.28 60.91 55.57 57.54 64.8 44.54 62.16 56.51 64.29 57.05 54.86 58.61 49.83 51.87 56.97 56.87 68.67 64.37 50.08 60.35 50.29 64.69 53.6 59.58 62.28 53.28 53.69 55.84 61.08 62.01 69.16 55.88 54.21 53.16 64.57 57.76 56.72 52.98 59.37 58.75 58.94 51.28 60.15 59.74 57.64 53.03 56.44 56.84 52.95
+37.38 34.76 34.38 33.86 37.26 40.6 34.77 36.72 32.12 35.21 39.58 35.94 34.27 31.55 42.02 43.4 40.04 37.41 41.61 38.57 38.84 38.26 34.86 37.21 36.36 35.5 36.92 37.98 37.5 34.8 34.92 38.62 39.99 41.6 35.21 35.62 39.08 35.21 32.32 37.18 36.81 30.43 33 37.32 39.16 34.33 38.68 34.06 39.12 36.24 50.08 47.07 49.59 39.11 46.31 42.43 47.26 35.62 47.05 38.98 35 43.67 40.98 44.92 41.05 47.94 42.71 41.9 42.75 40.05 45.28 43.57 44.79 44.52 45.71 47.14 47.94 48.53 44.21 40.26 39.11 38.94 41.68 44.4 41.79 45.94 48.39 43.35 42.09 39.79 40.66 45.12 41.48 35.74 41.21 42.66 42.35 44.79 36.49 41.87 49.35 43.57 51.75 47.22 48.88 55.03 37.85 52.8 47.98 54.65 48.52 46.61 49.81 42.32 44.06 48.43 48.34 58.38 54.61 42.53 51.29 42.74 54.92 45.56 50.65 52.94 45.3 45.66 47.43 51.92 52.67 58.82 47.46 46.09 45.16 54.85 49.1 48.22 45.06 50.47 49.92 50.11 43.57 51.11 50.77 48.99 45.05 47.98 48.33 45.02
+39.54 36.84 36.37 35.83 39.38 42.89 36.72 38.85 34 37.31 41.87 38 36.32 33.37 44.44 45.79 42.29 39.56 44.03 40.73 41.15 40.41 36.78 39.37 38.45 37.64 39.04 40.19 39.7 36.82 36.98 40.89 42.21 43.92 37.31 37.72 41.39 37.31 34.19 39.35 38.91 32.33 34.87 39.42 41.35 36.36 40.86 36.02 41.37 38.36 53.21 49.93 52.69 41.58 49.22 45.03 50.09 37.81 50.02 41.31 37.25 46.3 43.68 47.69 43.52 50.92 45.25 44.53 45.53 42.57 47.94 46.28 47.65 47.31 48.57 50.08 51 51.54 46.91 42.79 41.58 41.41 44.27 47.15 44.25 48.63 51.39 46.18 44.61 42.26 43.18 47.88 44.08 37.97 43.74 45.24 44.92 47.57 38.72 44.43 52.22 46.21 54.97 50.12 51.84 58.52 40.09 56.17 51.06 57.89 51.43 49.51 52.87 44.9 46.65 51.29 51.31 61.91 58.15 45.28 54.39 45.27 58.47 48.39 53.69 56.24 48.08 48.41 50.34 55.22 56.05 62.42 50.36 48.97 48.02 58.31 51.94 51.15 47.76 53.56 52.92 53.15 46.21 54.19 53.77 51.96 47.88 50.9 51.12 47.71
+34.59 32.26 31.82 31.37 34.44 37.53 32.12 34 29.77 32.67 36.63 33.26 31.8 29.18 38.84 40.02 36.97 34.61 38.54 35.62 36.04 35.35 32.12 34.49 33.68 32.98 34.18 35.17 34.74 32.24 32.39 35.8 36.89 38.38 32.67 33 36.21 32.67 29.92 34.44 34.03 28.35 30.5 34.51 36.2 31.84 35.74 31.52 36.19 33.57 46.94 44.04 46.51 36.73 43.46 39.76 44.19 33.34 44.15 36.45 32.9 40.84 38.58 42.11 38.35 44.91 39.94 39.29 40.26 37.57 42.32 40.82 42.13 41.78 42.85 44.18 45.04 45.52 41.41 37.72 36.7 36.54 39.05 41.69 39.06 42.88 45.35 40.81 39.34 37.31 38.14 42.26 38.9 33.49 38.61 39.9 39.63 41.97 34.11 39.2 46.19 40.87 48.62 44.33 45.86 51.8 35.45 49.71 45.21 51.16 45.42 43.79 46.74 39.73 41.26 45.32 45.36 54.72 51.54 40.08 48.08 40.02 51.78 42.77 47.45 49.72 42.48 42.76 44.54 48.82 49.6 55.14 44.56 43.28 42.5 51.58 45.9 45.21 42.18 47.33 46.79 46.95 40.87 47.92 47.53 45.92 42.35 44.98 45.16 42.16
+37.96 35.43 34.92 34.42 37.78 41.14 35.21 37.31 32.67 35.88 40.2 36.48 34.93 32.03 42.64 43.86 40.54 37.97 42.29 39.05 39.57 38.75 35.22 37.82 36.93 36.22 37.48 38.6 38.14 35.37 35.55 39.29 40.45 42.09 35.88 36.24 39.77 35.88 32.83 37.8 37.33 31.16 33.45 37.81 39.66 34.95 39.19 34.58 39.71 36.85 51.41 48.18 50.92 40.21 47.58 43.49 48.31 36.5 48.36 39.84 36.05 44.66 42.32 46.07 41.96 49.18 43.64 43.04 44.1 41.15 46.21 44.7 46.12 45.74 46.93 48.38 49.34 49.8 45.29 41.34 40.2 40.04 42.76 45.58 42.66 46.85 49.64 44.73 43.02 40.83 41.73 46.23 42.6 36.68 42.24 43.65 43.35 45.95 37.35 42.89 50.35 44.63 53.15 48.44 50.07 56.65 38.68 54.39 49.46 55.84 49.62 47.87 51.08 43.38 44.99 49.46 49.58 59.78 56.37 43.87 52.51 43.67 56.66 46.77 51.82 54.38 46.44 46.72 48.65 53.44 54.28 60.29 48.66 47.35 46.49 56.41 50.05 49.4 46.08 51.73 51.08 51.3 44.63 52.32 51.86 50.16 46.31 49.15 49.25 46.04
+42.63 39.7 39.21 38.6 42.46 46.22 39.58 41.87 36.63 40.2 45.14 40.94 39.14 35.99 47.96 49.39 45.62 42.65 47.45 43.91 44.33 43.56 39.7 42.4 41.39 40.54 42.06 43.32 42.8 39.66 39.83 44.07 45.52 47.38 40.2 40.68 44.64 40.2 36.85 42.41 41.96 34.82 37.59 42.47 44.53 39.18 44.04 38.82 44.6 41.35 56.97 53.45 56.38 44.47 52.66 48.15 53.6 40.49 53.53 44.2 39.85 49.56 46.74 51 46.63 54.53 48.39 47.66 48.67 45.56 51.26 49.56 50.92 50.59 52 53.62 54.56 55.12 50.18 45.85 44.5 44.33 47.4 50.36 47.31 52.05 55 49.39 47.75 45.21 46.16 51.22 47.18 40.66 46.79 48.42 48.07 50.92 41.51 47.55 55.73 49.34 58.71 53.51 55.34 62.46 42.8 59.96 54.49 61.85 54.99 52.88 56.49 47.93 49.81 54.81 54.81 66.13 62.01 48.34 58.11 48.35 62.39 51.72 57.36 60.08 51.4 51.75 53.74 59 59.85 66.72 53.76 52.33 51.24 62.29 55.48 54.64 51.06 57.25 56.53 56.84 49.34 57.87 57.44 55.54 51.15 54.4 54.62 50.97
+38.66 36 35.56 35.06 38.52 41.98 35.94 38 33.26 36.48 40.94 37.2 35.5 32.62 43.4 44.8 41.34 38.68 43.06 39.86 40.24 39.54 35.96 38.52 37.68 36.8 38.2 39.3 38.8 36.04 36.18 39.96 41.32 42.96 36.48 36.84 40.42 36.48 33.44 38.48 38.04 31.56 34.12 38.58 40.52 35.54 40 35.24 40.46 37.5 52.28 49.1 51.8 40.88 48.38 44.34 49.3 37.16 49.16 40.66 36.6 45.54 42.88 46.94 42.76 50.02 44.58 43.78 44.74 41.84 47.24 45.46 46.88 46.56 47.72 49.2 50.12 50.7 46.16 42 40.86 40.68 43.5 46.46 43.62 47.88 50.52 45.36 43.9 41.56 42.52 47.12 43.32 37.3 43.04 44.52 44.2 46.76 38 43.7 51.56 45.56 54.14 49.42 51.12 57.66 39.56 55.34 50.3 57.06 50.64 48.76 52.06 44.26 46 50.54 50.56 61.04 57.3 44.58 53.58 44.64 57.6 47.62 52.92 55.4 47.32 47.68 49.62 54.36 55.18 61.48 49.64 48.22 47.36 57.38 51.24 50.42 47.04 52.72 52.14 52.28 45.56 53.42 53 51.14 47.12 50.12 50.42 47.04
+36.96 34.5 34 33.5 36.78 40.04 34.27 36.32 31.8 34.93 39.14 35.5 34.01 31.19 41.54 42.7 39.48 36.97 41.17 38.01 38.52 37.72 34.3 36.81 35.92 35.26 36.48 37.58 37.14 34.42 34.6 38.26 39.37 40.98 34.93 35.3 38.74 34.93 31.96 36.8 36.35 30.35 32.56 36.8 38.58 34.03 38.14 33.66 38.66 35.88 49.92 46.77 49.43 39.03 46.19 42.19 46.88 35.44 46.95 38.66 35 43.35 41.1 44.7 40.75 47.76 42.33 41.78 42.81 39.95 44.82 43.41 44.75 44.38 45.57 46.98 47.9 48.33 43.95 40.16 39.03 38.88 41.52 44.2 41.37 45.46 48.19 43.43 41.75 39.63 40.48 44.86 41.36 35.62 40.99 42.36 42.07 44.61 36.29 41.63 48.79 43.27 51.55 46.96 48.54 54.93 37.49 52.74 47.96 54.15 48.14 46.43 49.55 42.06 43.62 47.97 48.08 57.96 54.65 42.55 50.93 42.34 54.94 45.38 50.25 52.74 45.06 45.32 47.17 51.84 52.65 58.48 47.18 45.93 45.06 54.73 48.52 47.9 44.7 50.19 49.54 49.79 43.27 50.73 50.29 48.67 44.93 47.68 47.75 44.64
+33.99 31.63 31.26 30.75 33.86 36.83 31.55 33.37 29.18 32.03 35.99 32.62 31.19 28.71 38.28 39.4 36.39 34 37.81 35.01 35.31 34.72 31.7 33.75 32.95 32.28 33.5 34.53 34.12 31.59 31.72 35.11 36.32 37.81 32.03 32.44 35.6 32.03 29.37 33.8 33.46 27.71 29.97 33.82 35.46 31.21 35.11 30.94 35.56 32.96 45.01 42.22 44.51 35.08 41.56 37.99 42.32 32 42.27 34.89 31.45 39.14 36.9 40.24 36.87 43.09 38.18 37.65 38.36 35.98 40.43 39.16 40.13 39.92 41.08 42.36 43.06 43.48 39.6 36.26 35.14 35.02 37.45 39.67 37.32 41.11 43.43 38.96 37.72 35.68 36.41 40.43 37.26 32.13 36.93 38.25 37.96 40.22 32.84 37.55 43.84 38.84 46.23 42.13 43.55 49.15 33.69 47.2 42.87 48.72 43.36 41.64 44.5 37.71 39.19 43.18 43.18 52.1 48.73 38.05 45.77 38.07 49.08 40.76 45.19 47.34 40.52 40.8 42.29 46.5 47.12 52.61 42.3 41.25 40.33 49.05 43.69 43.05 40.26 45.12 44.51 44.81 38.84 45.56 45.23 43.76 40.28 42.87 43.03 40.16
+45.3 42.14 41.66 40.92 45.12 49.04 42.02 44.44 38.84 42.64 47.96 43.4 41.54 38.28 51.12 52.54 48.56 45.32 50.36 46.64 47 46.26 42.32 44.92 43.76 42.96 44.6 46 45.48 42.02 42.2 46.8 48.38 50.42 42.64 43.28 47.5 42.64 39.12 45.02 44.62 36.92 39.92 45.04 47.14 41.58 46.74 41.2 47.38 43.92 59.32 55.62 58.6 46.16 54.72 49.92 55.7 42.18 55.66 45.92 41.4 51.56 48.6 52.9 48.66 56.82 50.18 49.56 50.46 47.38 53.14 51.64 52.72 52.46 54.14 55.84 56.68 57.2 52.1 47.86 46.28 46.14 49.36 52.04 49.02 54.12 57.2 51.28 49.66 46.96 47.82 53.18 49.08 42.36 48.58 50.34 49.96 52.98 43.4 49.44 57.44 50.94 60.68 55.22 57.1 64.42 44.16 61.86 56.18 63.98 57.02 54.66 58.46 49.46 51.44 56.74 56.66 68.34 63.8 49.9 60.12 49.96 64.3 53.58 59.32 62.12 53.28 53.62 55.46 61.04 61.82 69.1 55.48 54.16 52.78 64.44 57.34 56.48 52.92 59.32 58.46 59 50.94 59.78 59.4 57.56 52.92 56.34 56.5 52.7
+46.65 43.31 42.9 42.19 46.52 50.65 43.4 45.79 40.02 43.86 49.39 44.8 42.7 39.4 52.54 54.26 50.05 46.69 51.88 48.16 48.37 47.76 43.64 46.34 45.25 44.18 46.02 47.37 46.78 43.35 43.48 48.15 49.97 52.01 43.86 44.46 48.78 43.86 40.31 46.36 45.97 37.84 41.19 46.54 48.8 42.78 48.27 42.48 48.82 45.2 61.59 57.91 60.92 47.99 56.87 52.08 58.12 43.84 57.8 47.93 42.95 53.73 50.28 55.14 50.6 58.99 52.47 51.49 52.37 49.21 55.63 53.61 54.86 54.62 56.21 57.98 58.84 59.57 54.31 49.58 48.05 47.86 51.27 54.37 51.33 56.55 59.48 53.15 51.79 48.87 49.87 55.43 50.98 43.97 50.62 52.47 52.07 55.07 45.01 51.48 60.43 53.35 63.36 57.79 59.83 67.26 46.36 64.54 58.61 67.03 59.58 57.07 61.05 51.79 53.99 59.43 59.22 71.54 66.6 51.98 62.88 52.39 67.06 55.86 62.1 64.84 55.58 56.04 58.04 63.58 64.41 72.15 58.08 56.48 55.17 67.16 60.23 59.09 55.32 61.91 61.19 61.54 53.35 62.61 62.26 60.11 55.17 58.85 59.32 55.2
+43.09 40.06 39.63 38.96 42.94 46.74 40.04 42.29 36.97 40.54 45.62 41.34 39.48 36.39 48.56 50.05 46.22 43.13 47.93 44.43 44.71 44.08 40.26 42.82 41.73 40.86 42.5 43.76 43.24 40.02 40.17 44.53 46.06 47.98 40.54 41.12 45.12 40.54 37.23 42.83 42.46 35.08 38.01 42.97 44.99 39.56 44.52 39.22 45.08 41.77 56.95 53.49 56.32 44.39 52.6 48.07 53.64 40.51 53.45 44.24 39.75 49.62 46.58 50.92 46.75 54.55 48.39 47.58 48.51 45.5 51.3 49.58 50.74 50.45 51.98 53.62 54.44 55.06 50.16 45.87 44.44 44.27 47.4 50.2 47.31 52.15 54.98 49.23 47.79 45.17 46.04 51.18 47.14 40.66 46.75 48.42 48.07 50.9 41.63 47.55 55.69 49.24 58.55 53.33 55.22 62.16 42.74 59.64 54.19 61.85 55.01 52.74 56.41 47.83 49.83 54.85 54.67 65.99 61.61 48.08 58.07 48.33 62.01 51.64 57.3 59.88 51.36 51.73 53.6 58.76 59.57 66.6 53.64 52.17 50.92 62.13 55.52 54.52 51.06 57.21 56.51 56.9 49.24 57.79 57.46 55.56 51.03 54.36 54.68 50.91
+40.28 37.51 37.05 36.47 40.12 43.69 37.41 39.56 34.61 37.97 42.65 38.68 36.97 34 45.32 46.69 43.13 40.31 44.84 41.5 41.88 41.18 37.52 40.09 39.1 38.3 39.76 40.93 40.44 37.47 37.63 41.66 43 44.77 37.97 38.44 42.18 37.97 34.82 40.07 39.66 32.91 35.52 40.17 42.09 37.03 41.61 36.68 42.14 39.07 53.9 50.59 53.35 42.09 49.84 45.56 50.74 38.31 50.64 41.85 37.7 46.92 44.2 48.26 44.14 51.6 45.81 45.07 46.07 43.1 48.55 46.9 48.19 47.85 49.2 50.74 51.62 52.18 47.5 43.37 42.1 41.93 44.85 47.67 44.79 49.28 52.05 46.73 45.19 42.79 43.67 48.47 44.64 38.47 44.28 45.81 45.49 48.18 39.29 45 52.83 46.74 55.6 50.66 52.43 59.13 40.55 56.74 51.58 58.61 52.09 50.08 53.51 45.42 47.24 51.95 51.89 62.61 58.72 45.75 55.06 45.82 59.05 48.98 54.33 56.86 48.68 49.01 50.91 55.82 56.65 63.15 50.94 49.52 48.47 59 52.59 51.73 48.36 54.23 53.58 53.87 46.74 54.83 54.45 52.64 48.45 51.53 51.77 48.27
+44.81 41.77 41.22 40.61 44.62 48.61 41.61 44.03 38.54 42.29 47.45 43.06 41.17 37.81 50.36 51.88 47.93 44.84 49.91 46.15 46.65 45.8 41.66 44.65 43.57 42.68 44.26 45.55 45 41.73 41.92 46.37 47.8 49.75 42.29 42.76 46.92 42.29 38.75 44.6 44.1 36.69 39.51 44.7 46.86 41.23 46.29 40.82 46.88 43.48 60.47 56.74 59.89 47.28 55.96 51.17 56.92 42.96 56.85 46.95 42.35 52.62 49.66 54.2 49.45 57.87 51.42 50.59 51.8 48.38 54.49 52.6 54.19 53.76 55.2 56.92 57.98 58.6 53.32 48.62 47.26 47.06 50.31 53.61 50.28 55.25 58.41 52.52 50.68 48.04 49.07 54.41 50.1 43.15 49.71 51.39 51.04 54.06 44 50.49 59.4 52.56 62.53 56.99 58.97 66.57 45.59 63.88 58.09 65.84 58.48 56.32 60.14 51.09 53.09 58.34 58.34 70.38 66.19 51.51 61.87 51.49 66.52 55.04 61.05 63.94 54.68 55.04 57.27 62.78 63.76 70.95 57.3 55.67 54.59 66.35 59.07 58.15 54.3 60.92 60.21 60.47 52.56 61.64 61.17 59.12 54.48 57.89 58.13 54.24
+41.47 38.55 38.14 37.55 41.34 45.03 38.57 40.73 35.62 39.05 43.91 39.86 38.01 35.01 46.64 48.16 44.43 41.5 46.15 42.79 43.07 42.44 38.7 41.25 40.31 39.36 40.94 42.13 41.6 38.59 38.72 42.83 44.38 46.17 39.05 39.52 43.36 39.05 35.85 41.24 40.84 33.73 36.61 41.38 43.42 38.07 42.91 37.78 43.4 40.2 55.33 52 54.77 43.18 51.14 46.85 52.2 39.36 51.97 43.05 38.65 48.24 45.26 49.6 45.37 52.97 47.16 46.29 47.18 44.24 49.99 48.14 49.43 49.16 50.5 52.08 52.94 53.58 48.82 44.5 43.2 43.02 46.05 48.99 46.14 50.75 53.45 47.86 46.5 43.94 44.89 49.83 45.82 39.49 45.51 47.13 46.78 49.48 40.34 46.25 54.42 48.06 57.09 52.09 53.91 60.69 41.75 58.24 52.91 60.3 53.56 51.42 54.96 46.67 48.59 53.44 53.34 64.42 60.19 46.91 56.59 47.15 60.56 50.28 55.89 58.42 50 50.4 52.31 57.3 58.1 64.93 52.34 50.87 49.81 60.51 54.17 53.21 49.74 55.7 55.07 55.31 48.06 56.38 56.01 54.06 49.7 52.95 53.33 49.68
+41.86 39.08 38.51 37.97 41.66 45.39 38.84 41.15 36.04 39.57 44.33 40.24 38.52 35.31 47 48.37 44.71 41.88 46.65 43.07 43.65 42.75 38.82 41.75 40.75 39.96 41.36 42.57 42.06 39.02 39.22 43.35 44.59 46.4 39.57 39.96 43.85 39.57 36.21 41.69 41.17 34.39 36.89 41.74 43.77 38.56 43.22 38.14 43.79 40.64 56.95 53.39 56.43 44.58 52.74 48.21 53.55 40.43 53.58 44.17 39.95 49.5 46.88 51.07 46.48 54.48 48.39 47.67 48.91 45.59 51.26 49.52 51.15 50.69 51.99 53.6 54.68 55.22 50.21 45.77 44.54 44.35 47.37 50.57 47.31 51.93 55.01 49.58 47.67 45.26 46.26 51.24 47.2 40.63 46.82 48.36 48.04 50.91 41.36 47.53 55.94 49.55 58.99 53.76 55.6 62.88 42.95 60.35 54.9 61.99 55.05 53.13 56.69 48.18 49.99 54.91 55.01 66.33 62.61 48.68 58.29 48.49 62.89 51.89 57.51 60.32 51.52 51.83 54.02 59.26 60.23 66.86 54.04 52.51 51.58 62.61 55.58 54.81 51.12 57.4 56.72 56.93 49.55 58.09 57.59 55.68 51.4 54.54 54.68 51.09
+41.14 38.27 37.84 37.26 41 44.68 38.26 40.41 35.35 38.75 43.56 39.54 37.72 34.72 46.26 47.76 44.08 41.18 45.8 42.44 42.75 42.11 38.36 40.97 39.99 39.08 40.64 41.8 41.28 38.29 38.43 42.53 43.98 45.77 38.75 39.22 43.03 38.75 35.57 40.92 40.52 33.53 36.31 41.09 43.08 37.8 42.55 37.48 43.05 39.89 55.15 51.83 54.61 43.08 51.01 46.7 52.03 39.22 51.81 42.92 38.55 48.09 45.14 49.45 45.21 52.8 47.01 46.12 47.11 44.1 49.85 47.99 49.33 49 50.34 51.92 52.8 53.45 48.68 44.34 43.07 42.88 45.9 48.88 45.99 50.57 53.29 47.76 46.33 43.82 44.75 49.67 45.68 39.36 45.37 46.95 46.62 49.32 40.2 46.1 54.34 47.98 57 51.98 53.83 60.6 41.67 58.13 52.84 60.19 53.44 51.34 54.87 46.62 48.55 53.35 53.22 64.26 60.16 46.84 56.5 47.07 60.48 50.19 55.77 58.28 49.9 50.28 52.24 57.16 58.01 64.75 52.28 50.74 49.69 60.44 54.07 53.08 49.62 55.6 55 55.23 47.98 56.29 55.93 53.99 49.64 52.85 53.22 49.56
+37.5 34.78 34.48 33.86 37.4 40.66 34.86 36.78 32.12 35.22 39.7 35.96 34.3 31.7 42.32 43.64 40.26 37.52 41.66 38.7 38.82 38.36 35.16 37.14 36.26 35.44 36.92 38.06 37.6 34.78 34.88 38.66 40.2 41.86 35.22 35.76 39.24 35.22 32.38 37.24 36.96 30.34 33.1 37.32 39.12 34.34 38.78 34.12 39.24 36.32 48.7 45.76 48.1 37.84 44.88 41.06 45.88 34.68 45.66 37.82 33.9 42.44 39.72 43.48 40.06 46.66 41.36 40.7 41.24 38.88 43.82 42.4 43.18 43.08 44.44 45.84 46.44 46.96 42.84 39.28 37.96 37.84 40.54 42.74 40.44 44.66 46.98 41.92 40.92 38.56 39.3 43.74 40.28 34.78 39.94 41.46 41.12 43.52 35.68 40.66 47.36 41.88 49.78 45.38 46.94 52.78 36.38 50.68 45.98 52.68 46.92 44.84 48 40.62 42.34 46.72 46.56 56.24 52.14 40.82 49.42 41.14 52.6 43.96 48.82 51 43.76 44.12 45.54 50.04 50.6 56.82 45.56 44.46 43.3 52.78 47.3 46.46 43.56 48.72 48.06 48.46 41.88 49.16 48.9 47.28 43.36 46.3 46.62 43.4
+40.04 37.37 36.84 36.34 39.86 43.5 37.21 39.37 34.49 37.82 42.4 38.52 36.81 33.75 44.92 46.34 42.82 40.09 44.65 41.25 41.75 40.97 37.14 40.04 39.03 38.22 39.64 40.72 40.22 37.35 37.53 41.51 42.65 44.39 37.82 38.2 41.91 37.82 34.65 39.88 39.41 32.9 35.31 40.07 41.98 36.91 41.37 36.5 41.89 38.87 54.95 51.6 54.5 43.09 50.96 46.61 51.81 39.02 51.7 42.76 38.55 47.88 45.16 49.37 44.9 52.58 46.86 45.96 47.28 43.99 49.71 47.8 49.46 48.94 50.17 51.74 52.78 53.42 48.57 44.1 42.98 42.76 45.72 48.98 45.84 50.27 53.14 47.85 46.08 43.75 44.71 49.53 45.56 39.2 45.26 46.71 46.43 49.15 39.91 45.93 54.47 48.11 57.19 52.12 54.01 60.93 41.74 58.41 53.18 60.22 53.38 51.51 54.98 46.82 48.69 53.36 53.3 64.3 60.73 47.11 56.59 47.13 60.9 50.27 55.8 58.38 49.92 50.24 52.45 57.28 58.3 64.71 52.5 50.79 49.91 60.69 54.07 53.12 49.56 55.65 55.12 55.24 48.11 56.42 56 54.06 49.83 52.89 53.17 49.56
+39.08 36.42 35.95 35.51 38.94 42.49 36.36 38.45 33.68 36.93 41.39 37.68 35.92 32.95 43.76 45.25 41.73 39.1 43.57 40.31 40.75 39.99 36.26 39.03 38.25 37.28 38.68 39.75 39.22 36.52 36.66 40.41 41.77 43.38 36.93 37.2 40.81 36.93 33.83 38.93 38.43 31.95 34.51 39.06 41.09 35.96 40.48 35.66 40.91 37.92 53.69 50.45 53.27 42.08 49.76 45.69 50.71 38.15 50.54 41.83 37.65 46.8 44.08 48.35 43.84 51.34 45.93 45.01 46.09 43.01 48.68 46.66 48.35 47.97 49.01 50.52 51.56 52.2 47.51 43.05 42 41.79 44.67 47.99 44.97 49.23 51.93 46.68 45.13 42.76 43.84 48.5 44.52 38.29 44.3 45.78 45.46 48.05 38.9 44.93 53.36 47.09 55.91 51.1 52.86 59.64 40.91 57.23 52.04 58.89 52.17 50.35 53.71 45.76 47.53 52.13 52.21 63.05 59.37 46.08 55.29 46.11 59.61 49.09 54.63 57.2 48.76 49.15 51.3 56.1 57.01 63.4 51.32 49.75 49.04 59.21 52.92 52.07 48.48 54.34 53.82 53.81 47.09 55.19 54.71 52.7 48.62 51.68 52.04 48.57
+38.28 35.78 35.22 34.74 38.08 41.5 35.5 37.64 32.98 36.22 40.54 36.8 35.26 32.28 42.96 44.18 40.86 38.3 42.68 39.36 39.96 39.08 35.44 38.22 37.28 36.6 37.84 38.94 38.48 35.7 35.9 39.68 40.72 42.38 36.22 36.56 40.12 36.22 33.12 38.14 37.64 31.54 33.72 38.18 40.02 35.3 39.5 34.88 40.04 37.18 52.4 49.1 51.94 41.06 48.56 44.36 49.24 37.18 49.32 40.62 36.8 45.52 43.2 47 42.72 50.12 44.5 43.86 45.1 41.96 47.14 45.56 47.14 46.66 47.84 49.32 50.36 50.84 46.2 42.1 41 40.82 43.58 46.58 43.5 47.72 50.62 45.7 43.82 41.66 42.58 47.14 43.44 37.38 43.08 44.46 44.18 46.84 38.02 43.72 51.5 45.64 54.36 49.52 51.22 57.98 39.54 55.64 50.64 57.06 50.66 48.96 52.22 44.4 46.04 50.54 50.66 61.06 57.8 44.9 53.68 44.64 58.02 47.8 52.94 55.56 47.44 47.7 49.78 54.6 55.54 61.54 49.8 48.36 47.54 57.72 51.14 50.46 47.04 52.86 52.24 52.42 45.64 53.5 53.02 51.28 47.38 50.22 50.3 47.02
+39.72 37.02 36.54 36.02 39.56 43.14 36.92 39.04 34.18 37.48 42.06 38.2 36.48 33.5 44.6 46.02 42.5 39.76 44.26 40.94 41.36 40.64 36.92 39.64 38.68 37.84 39.28 40.38 39.88 37.02 37.18 41.12 42.38 44.1 37.48 37.88 41.56 37.48 34.36 39.54 39.1 32.52 35.04 39.7 41.62 36.56 41.06 36.2 41.56 38.54 53.96 50.68 53.48 42.24 49.98 45.74 50.88 38.34 50.74 41.98 37.8 47.02 44.28 48.44 44.14 51.64 46 45.14 46.28 43.18 48.78 46.94 48.44 48.02 49.26 50.8 51.76 52.38 47.66 43.34 42.18 41.98 44.9 47.98 45 49.4 52.16 46.88 45.28 42.92 43.86 48.62 44.72 38.5 44.42 45.9 45.6 48.26 39.24 45.1 53.32 47.1 55.98 51.04 52.86 59.6 40.88 57.16 52 59 52.34 50.42 53.84 45.8 47.64 52.28 52.22 63.02 59.3 46.08 55.42 46.16 59.54 49.24 54.68 57.2 48.92 49.26 51.32 56.12 57.04 63.46 51.36 49.78 48.86 59.38 52.98 52.06 48.6 54.52 53.96 54.12 47.1 55.24 54.84 52.94 48.76 51.82 52.12 48.58
+40.91 38.12 37.63 37.06 40.74 44.36 37.98 40.19 35.17 38.6 43.32 39.3 37.58 34.53 46 47.37 43.76 40.93 45.55 42.13 42.57 41.8 38.06 40.72 39.75 38.94 40.38 41.58 41.08 38.08 38.25 42.31 43.66 45.44 38.6 39.04 42.84 38.6 35.37 40.71 40.26 33.46 36.07 40.77 42.75 37.62 42.26 37.26 42.8 39.69 54.93 51.53 54.38 42.91 50.8 46.45 51.68 39.03 51.63 42.62 38.45 47.78 45.1 49.2 44.93 52.57 46.67 45.96 46.99 43.94 49.44 47.78 49.16 48.81 50.14 51.7 52.64 53.18 48.4 44.19 42.92 42.75 45.7 48.62 45.63 50.17 53.04 47.67 46.03 43.61 44.54 49.4 45.5 39.2 45.13 46.68 46.35 49.1 39.99 45.85 53.81 47.64 56.69 51.67 53.44 60.34 41.32 57.92 52.65 59.69 53.05 51.06 54.53 46.29 48.09 52.89 52.91 63.83 59.95 46.7 56.09 46.67 60.29 49.92 55.36 58 49.6 49.93 51.9 56.96 57.81 64.38 51.92 50.51 49.5 60.15 53.54 52.74 49.26 55.25 54.57 54.84 47.64 55.87 55.44 53.6 49.39 52.5 52.7 49.19
+40.42 37.68 37.18 36.6 40.24 43.8 37.5 39.7 34.74 38.14 42.8 38.8 37.14 34.12 45.48 46.78 43.24 40.44 45 41.6 42.06 41.28 37.6 40.22 39.22 38.48 39.88 41.08 40.6 37.6 37.78 41.82 43.1 44.88 38.14 38.6 42.36 38.14 34.94 40.22 39.78 33.1 35.62 40.26 42.18 37.18 41.72 36.8 42.28 39.22 54.14 50.76 53.58 42.28 50.06 45.72 50.88 38.46 50.88 41.96 37.9 47.06 44.48 48.44 44.28 51.82 45.92 45.28 46.32 43.3 48.64 47.1 48.42 48.06 49.42 50.96 51.88 52.38 47.66 43.58 42.3 42.14 45.04 47.84 44.88 49.38 52.26 47 45.32 42.96 43.84 48.64 44.84 38.64 44.44 45.96 45.64 48.38 39.44 45.16 52.88 46.86 55.8 50.82 52.56 59.38 40.62 57 51.82 58.72 52.22 50.26 53.68 45.54 47.3 52.04 52.06 62.78 59 45.98 55.2 45.9 59.34 49.16 54.46 57.08 48.84 49.14 51.06 56.08 56.92 63.36 51.08 49.72 48.68 59.24 52.64 51.88 48.48 54.4 53.7 54.02 46.86 54.96 54.54 52.78 48.64 51.68 51.82 48.38
+37.45 34.91 34.45 33.98 37.3 40.66 34.8 36.82 32.24 35.37 39.66 36.04 34.42 31.59 42.02 43.35 40.02 37.47 41.73 38.59 39.02 38.29 34.78 37.35 36.52 35.7 37.02 38.08 37.6 34.93 35.08 38.74 39.98 41.57 35.37 35.7 39.17 35.37 32.4 37.29 36.84 30.65 33.04 37.38 39.25 34.46 38.73 34.14 39.19 36.34 50.94 47.82 50.49 39.87 47.17 43.21 48.01 36.19 47.92 39.6 35.7 44.35 41.84 45.75 41.62 48.73 43.42 42.66 43.68 40.78 46.01 44.29 45.75 45.39 46.5 47.94 48.88 49.43 44.98 40.91 39.83 39.65 42.38 45.32 42.48 46.6 49.23 44.27 42.74 40.51 41.45 45.91 42.22 36.34 41.94 43.35 43.05 45.56 36.99 42.57 50.27 44.44 52.83 48.21 49.87 56.3 38.57 54.03 49.13 55.62 49.35 47.58 50.78 43.19 44.86 49.26 49.31 59.51 56.01 43.54 52.25 43.52 56.27 46.45 51.59 54.04 46.14 46.47 48.42 53.04 53.88 59.93 48.44 47.03 46.23 56.01 49.93 49.16 45.84 51.41 50.85 50.97 44.44 52.1 51.67 49.87 45.99 48.87 49.12 45.85
+37.61 35.1 34.6 34.13 37.44 40.81 34.92 36.98 32.39 35.55 39.83 36.18 34.6 31.72 42.2 43.48 40.17 37.63 41.92 38.72 39.22 38.43 34.88 37.53 36.66 35.9 37.18 38.25 37.78 35.08 35.25 38.94 40.09 41.7 35.55 35.88 39.37 35.55 32.54 37.46 36.99 30.87 33.16 37.53 39.38 34.64 38.86 34.28 39.35 36.51 51.32 48.14 50.87 40.19 47.54 43.5 48.31 36.44 48.29 39.85 36 44.64 42.22 46.07 41.89 49.09 43.68 42.97 44.08 41.09 46.28 44.62 46.13 45.72 46.85 48.3 49.28 49.8 45.29 41.22 40.14 39.96 42.69 45.65 42.72 46.86 49.59 44.67 43 40.81 41.74 46.22 42.54 36.61 42.23 43.62 43.33 45.89 37.25 42.86 50.57 44.75 53.24 48.55 50.22 56.76 38.81 54.47 49.55 55.98 49.68 47.95 51.16 43.51 45.16 49.58 49.66 59.9 56.52 43.92 52.62 43.8 56.76 46.81 51.93 54.44 46.48 46.78 48.78 53.46 54.34 60.34 48.8 47.38 46.58 56.48 50.22 49.49 46.14 51.79 51.21 51.35 44.75 52.46 52.01 50.24 46.37 49.22 49.4 46.14
+41.62 38.84 38.29 37.71 41.42 45.13 38.62 40.89 35.8 39.29 44.07 39.96 38.26 35.11 46.8 48.15 44.53 41.66 46.37 42.83 43.35 42.53 38.66 41.51 40.41 39.68 41.12 42.31 41.82 38.74 38.94 43.13 44.31 46.16 39.29 39.76 43.63 39.29 35.99 41.43 40.97 34.19 36.67 41.54 43.47 38.34 42.94 37.9 43.53 40.4 56.29 52.79 55.75 44.04 52.12 47.57 52.93 39.97 52.92 43.67 39.45 48.96 46.28 50.41 46.02 53.88 47.79 47.05 48.31 45.03 50.66 48.98 50.47 49.99 51.39 53 54 54.56 49.61 45.27 44 43.81 46.83 49.87 46.71 51.35 54.37 48.96 47.11 44.72 45.62 50.6 46.64 40.17 46.24 47.76 47.46 50.31 40.98 46.97 55.24 48.91 58.23 53 54.88 61.98 42.39 59.45 54.1 61.27 54.43 52.45 56.01 47.58 49.45 54.31 54.27 65.43 61.69 47.98 57.61 47.91 61.95 51.27 56.79 59.48 50.92 51.21 53.32 58.42 59.39 65.98 53.36 51.79 50.74 61.85 54.94 54.07 50.52 56.74 56.08 56.37 48.91 57.37 56.95 55.1 50.78 53.9 54.06 50.43
+42.99 39.9 39.53 38.9 42.88 46.66 39.99 42.21 36.89 40.45 45.52 41.32 39.37 36.32 48.38 49.97 46.06 43 47.8 44.38 44.59 43.98 40.2 42.65 41.77 40.72 42.38 43.66 43.1 39.98 40.09 44.31 46.11 47.94 40.45 40.94 44.92 40.45 37.15 42.73 42.33 34.81 37.97 42.81 44.99 39.39 44.52 39.16 45 41.65 56.71 53.3 56.09 44.16 52.33 48 53.5 40.37 53.24 44.1 39.55 49.43 46.32 50.8 46.54 54.29 48.32 47.48 48.16 45.33 51.18 49.33 50.51 50.37 51.75 53.36 54.18 54.81 49.99 45.65 44.25 44.09 47.2 50.08 47.28 52.05 54.75 48.92 47.7 44.98 45.98 51.06 46.94 40.48 46.62 48.36 47.96 50.71 41.38 47.4 55.54 49.07 58.28 53.23 55.02 61.93 42.65 59.48 53.97 61.6 54.77 52.49 56.12 47.59 49.53 54.58 54.53 65.89 61.28 47.87 57.78 48.15 61.77 51.36 57.13 59.74 51.1 51.55 53.37 58.6 59.3 66.46 53.38 52.04 50.92 61.72 55.34 54.42 50.88 56.9 56.19 56.47 49.07 57.56 57.17 55.17 50.7 54.1 54.51 50.81
+44.75 41.55 41.15 40.46 44.62 48.54 41.6 43.92 38.38 42.09 47.38 42.96 40.98 37.81 50.42 52.01 47.98 44.77 49.75 46.17 46.4 45.77 41.86 44.39 43.38 42.38 44.1 45.44 44.88 41.57 41.7 46.16 47.94 49.89 42.09 42.66 46.81 42.09 38.66 44.47 44.08 36.29 39.5 44.56 46.75 41.02 46.29 40.74 46.83 43.36 58.8 55.24 58.13 45.77 54.25 49.67 55.41 41.85 55.18 45.68 41 51.23 48.04 52.59 48.28 56.31 50 49.18 49.94 46.98 52.97 51.17 52.31 52.13 53.66 55.34 56.16 56.79 51.78 47.37 45.87 45.71 48.94 51.8 48.9 53.9 56.75 50.73 49.4 46.61 47.57 52.87 48.66 41.98 48.28 50.07 49.67 52.56 42.97 49.11 57.41 50.76 60.33 55.03 56.91 64.06 44.09 61.51 55.83 63.76 56.73 54.34 58.12 49.25 51.28 56.52 56.41 68.13 63.39 49.54 59.83 49.82 63.89 53.21 59.11 61.8 52.94 53.37 55.22 60.64 61.38 68.77 55.24 53.85 52.59 63.95 57.25 56.28 52.68 58.95 58.19 58.57 50.76 59.56 59.19 57.19 52.53 56.03 56.4 52.55
+37.96 35.43 34.92 34.42 37.78 41.14 35.21 37.31 32.67 35.88 40.2 36.48 34.93 32.03 42.64 43.86 40.54 37.97 42.29 39.05 39.57 38.75 35.22 37.82 36.93 36.22 37.48 38.6 38.14 35.37 35.55 39.29 40.45 42.09 35.88 36.24 39.77 35.88 32.83 37.8 37.33 31.16 33.45 37.81 39.66 34.95 39.19 34.58 39.71 36.85 51.41 48.18 50.92 40.21 47.58 43.49 48.31 36.5 48.36 39.84 36.05 44.66 42.32 46.07 41.96 49.18 43.64 43.04 44.1 41.15 46.21 44.7 46.12 45.74 46.93 48.38 49.34 49.8 45.29 41.34 40.2 40.04 42.76 45.58 42.66 46.85 49.64 44.73 43.02 40.83 41.73 46.23 42.6 36.68 42.24 43.65 43.35 45.95 37.35 42.89 50.35 44.63 53.15 48.44 50.07 56.65 38.68 54.39 49.46 55.84 49.62 47.87 51.08 43.38 44.99 49.46 49.58 59.78 56.37 43.87 52.51 43.67 56.66 46.77 51.82 54.38 46.44 46.72 48.65 53.44 54.28 60.29 48.66 47.35 46.49 56.41 50.05 49.4 46.08 51.73 51.08 51.3 44.63 52.32 51.86 50.16 46.31 49.15 49.25 46.04
+38.42 35.82 35.34 34.76 38.24 41.6 35.62 37.72 33 36.24 40.68 36.84 35.3 32.44 43.28 44.46 41.12 38.44 42.76 39.52 39.96 39.22 35.76 38.2 37.2 36.56 37.88 39.04 38.6 35.7 35.88 39.76 40.94 42.66 36.24 36.72 40.3 36.24 33.2 38.22 37.82 31.48 33.84 38.24 40.02 35.34 39.62 34.96 40.18 37.28 51.16 47.94 50.6 39.92 47.28 43.12 48.02 36.34 48.06 39.6 35.8 44.44 42.04 45.7 41.86 48.98 43.3 42.76 43.74 40.9 45.86 44.52 45.68 45.34 46.7 48.16 49 49.44 44.98 41.22 39.96 39.82 42.56 45.08 42.3 46.6 49.36 44.4 42.78 40.56 41.34 45.9 42.36 36.52 41.94 43.38 43.08 45.7 37.32 42.64 49.76 44.14 52.6 47.86 49.5 55.94 38.24 53.7 48.82 55.34 49.26 47.38 50.62 42.9 44.56 49.06 49.06 59.14 55.56 43.34 52.04 43.24 55.9 46.38 51.32 53.8 46.08 46.34 48.1 52.88 53.66 59.74 48.12 46.88 45.82 55.88 49.58 48.88 45.72 51.32 50.62 51 44.14 51.78 51.4 49.8 45.88 48.74 48.82 45.58
+42.16 39.31 38.78 38.14 41.96 45.64 39.08 41.39 36.21 39.77 44.64 40.42 38.74 35.6 47.5 48.78 45.12 42.18 46.92 43.36 43.85 43.03 39.24 41.91 40.81 40.12 41.56 42.84 42.36 39.17 39.37 43.63 44.92 46.81 39.77 40.3 44.23 39.77 36.43 41.94 41.5 34.55 37.13 41.95 43.9 38.78 43.47 38.36 44.09 40.91 56.09 52.55 55.47 43.76 51.83 47.26 52.63 39.84 52.69 43.4 39.25 48.71 46.1 50.09 45.89 53.7 47.45 46.88 47.95 44.84 50.25 48.81 50.07 49.7 51.2 52.8 53.72 54.19 49.3 45.2 43.81 43.66 46.66 49.4 46.35 51.07 54.11 48.68 46.89 44.46 45.31 50.31 46.44 40.04 45.97 47.55 47.22 50.1 40.92 46.74 54.5 48.36 57.64 52.44 54.23 61.3 41.89 58.85 53.5 60.63 53.98 51.92 55.47 47 48.81 53.75 53.76 64.8 60.88 47.5 57.02 47.37 61.26 50.83 56.23 58.96 50.5 50.78 52.7 57.96 58.81 65.47 52.72 51.38 50.21 61.24 54.31 53.56 50.1 56.24 55.46 55.89 48.36 56.73 56.31 54.57 50.28 53.41 53.48 49.94
+37.96 35.43 34.92 34.42 37.78 41.14 35.21 37.31 32.67 35.88 40.2 36.48 34.93 32.03 42.64 43.86 40.54 37.97 42.29 39.05 39.57 38.75 35.22 37.82 36.93 36.22 37.48 38.6 38.14 35.37 35.55 39.29 40.45 42.09 35.88 36.24 39.77 35.88 32.83 37.8 37.33 31.16 33.45 37.81 39.66 34.95 39.19 34.58 39.71 36.85 51.41 48.18 50.92 40.21 47.58 43.49 48.31 36.5 48.36 39.84 36.05 44.66 42.32 46.07 41.96 49.18 43.64 43.04 44.1 41.15 46.21 44.7 46.12 45.74 46.93 48.38 49.34 49.8 45.29 41.34 40.2 40.04 42.76 45.58 42.66 46.85 49.64 44.73 43.02 40.83 41.73 46.23 42.6 36.68 42.24 43.65 43.35 45.95 37.35 42.89 50.35 44.63 53.15 48.44 50.07 56.65 38.68 54.39 49.46 55.84 49.62 47.87 51.08 43.38 44.99 49.46 49.58 59.78 56.37 43.87 52.51 43.67 56.66 46.77 51.82 54.38 46.44 46.72 48.65 53.44 54.28 60.29 48.66 47.35 46.49 56.41 50.05 49.4 46.08 51.73 51.08 51.3 44.63 52.32 51.86 50.16 46.31 49.15 49.25 46.04
+34.8 32.42 32.01 31.53 34.66 37.75 32.32 34.19 29.92 32.83 36.85 33.44 31.96 29.37 39.12 40.31 37.23 34.82 38.75 35.85 36.21 35.57 32.38 34.65 33.83 33.12 34.36 35.37 34.94 32.4 32.54 35.99 37.15 38.66 32.83 33.2 36.43 32.83 30.09 34.63 34.25 28.45 30.69 34.7 36.39 32 35.96 31.7 36.41 33.76 46.79 43.91 46.33 36.56 43.28 39.59 44.05 33.25 43.98 36.33 32.75 40.72 38.4 41.93 38.28 44.78 39.79 39.15 40.03 37.43 42.16 40.7 41.89 41.59 42.71 44.04 44.84 45.32 41.25 37.63 36.56 36.41 38.93 41.45 38.91 42.77 45.19 40.6 39.23 37.16 37.96 42.1 38.76 33.39 38.46 39.78 39.5 41.83 34.06 39.07 45.92 40.63 48.33 44.06 45.58 51.44 35.25 49.37 44.88 50.91 45.23 43.53 46.49 39.48 41.03 45.11 45.11 54.43 51.11 39.8 47.83 39.81 51.39 42.55 47.21 49.44 42.28 42.57 44.26 48.54 49.27 54.88 44.28 43.05 42.2 51.27 45.68 44.97 42 47.1 46.54 46.75 40.63 47.65 47.29 45.7 42.1 44.76 44.96 41.95
+40.05 37.33 36.84 36.29 39.88 43.43 37.18 39.35 34.44 37.8 42.41 38.48 36.8 33.8 45.02 46.36 42.83 40.07 44.6 41.24 41.69 40.92 37.24 39.88 38.93 38.14 39.54 40.71 40.22 37.29 37.46 41.43 42.73 44.47 37.8 38.22 41.94 37.8 34.63 39.86 39.41 32.78 35.31 39.92 41.86 36.84 41.37 36.48 41.9 38.86 53.91 50.57 53.38 42.13 49.87 45.6 50.72 38.3 50.68 41.83 37.75 46.89 44.28 48.3 44.08 51.59 45.81 45.11 46.15 43.13 48.53 46.89 48.28 47.92 49.21 50.74 51.68 52.21 47.51 43.36 42.13 41.96 44.85 47.75 44.79 49.23 52.06 46.81 45.17 42.81 43.73 48.49 44.66 38.47 44.3 45.81 45.49 48.19 39.23 45 52.85 46.79 55.68 50.75 52.49 59.28 40.58 56.9 51.73 58.61 52.08 50.15 53.55 45.47 47.23 51.93 51.96 62.68 58.92 45.88 55.08 45.83 59.24 49.02 54.36 56.96 48.7 49.02 50.98 55.94 56.79 63.21 51 49.6 48.63 59.08 52.57 51.79 48.36 54.25 53.59 53.84 46.79 54.87 54.44 52.63 48.51 51.55 51.74 48.3
+39.63 36.88 36.45 35.86 39.48 42.98 36.81 38.91 34.03 37.33 41.96 38.04 36.35 33.46 44.62 45.97 42.46 39.66 44.1 40.84 41.17 40.52 36.96 39.41 38.43 37.64 39.1 40.26 39.78 36.84 36.99 40.97 42.33 44.08 37.33 37.82 41.5 37.33 34.25 39.41 39.03 32.33 34.95 39.51 41.39 36.41 40.94 36.08 41.46 38.43 52.73 49.5 52.17 41.14 48.73 44.54 49.64 37.49 49.52 40.94 36.85 45.91 43.2 47.18 43.22 50.49 44.8 44.08 45 42.15 47.48 45.89 47.07 46.77 48.13 49.64 50.46 51.01 46.45 42.45 41.17 41.01 43.88 46.56 43.8 48.23 50.91 45.66 44.22 41.84 42.68 47.4 43.66 37.64 43.3 44.82 44.5 47.13 38.48 44.02 51.6 45.65 54.3 49.47 51.2 57.71 39.61 55.38 50.33 57.28 50.93 48.91 52.28 44.35 46.15 50.78 50.69 61.17 57.26 44.65 53.8 44.77 57.61 47.86 53.09 55.54 47.58 47.91 49.71 54.52 55.3 61.72 49.74 48.38 47.3 57.62 51.4 50.54 47.28 53 52.35 52.67 45.65 53.56 53.21 51.45 47.32 50.36 50.61 47.17
+32.88 30.83 30.26 29.84 32.66 35.6 30.43 32.33 28.35 31.16 34.82 31.56 30.35 27.71 36.92 37.84 35.08 32.91 36.69 33.73 34.39 33.53 30.34 32.9 31.95 31.54 32.52 33.46 33.1 30.65 30.87 34.19 34.81 36.29 31.16 31.48 34.55 31.16 28.45 32.78 32.33 27.32 28.91 32.81 34.28 30.41 33.83 29.94 34.37 31.97 45.39 42.46 45 35.63 42.12 38.33 42.53 32.16 42.74 35.1 31.95 39.36 37.56 40.65 36.94 43.42 38.4 37.94 39.26 36.35 40.69 39.48 40.92 40.36 41.45 42.74 43.7 44.06 39.97 36.48 35.54 35.38 37.74 40.32 37.5 41.15 43.84 39.75 37.82 36.09 36.81 40.75 37.64 32.38 37.26 38.37 38.17 40.55 32.93 37.81 44.49 39.51 47.15 42.84 44.35 50.31 34.16 48.25 43.98 49.36 43.84 42.47 45.28 38.5 39.89 43.74 43.84 52.76 50.29 39.01 46.51 38.61 50.4 41.47 45.78 48.1 41.12 41.26 43.15 47.32 48.24 53.21 43.18 41.87 41.13 50.17 44.17 43.62 40.68 45.83 45.28 45.5 39.51 46.32 45.9 44.5 41.17 43.51 43.43 40.62
+35.5 33.02 32.65 32.15 35.38 38.53 33 34.87 30.5 33.45 37.59 34.12 32.56 29.97 39.92 41.19 38.01 35.52 39.51 36.61 36.89 36.31 33.1 35.31 34.51 33.72 35.04 36.07 35.62 33.04 33.16 36.67 37.97 39.5 33.45 33.84 37.13 33.45 30.69 35.31 34.95 28.91 31.33 35.4 37.15 32.6 36.72 32.34 37.15 34.42 47.43 44.55 46.95 37.02 43.84 40.15 44.71 33.73 44.56 36.87 33.15 41.32 38.84 42.51 38.86 45.4 40.39 39.69 40.47 37.93 42.8 41.26 42.39 42.15 43.29 44.64 45.4 45.92 41.83 38.15 37.04 36.89 39.47 41.99 39.51 43.45 45.81 41.06 39.83 37.66 38.48 42.7 39.28 33.85 39 40.38 40.08 42.41 34.56 39.63 46.58 41.17 48.93 44.64 46.18 52.04 35.75 49.95 45.38 51.63 45.87 44.07 47.09 39.98 41.59 45.75 45.71 55.19 51.63 40.24 48.47 40.37 51.95 43.09 47.87 50.08 42.84 43.17 44.82 49.14 49.83 55.64 44.84 43.61 42.72 51.87 46.36 45.59 42.6 47.72 47.16 47.37 41.17 48.29 47.95 46.3 42.6 45.36 45.64 42.55
+40.11 37.36 36.9 36.37 39.96 43.61 37.32 39.42 34.51 37.81 42.47 38.58 36.8 33.82 45.04 46.54 42.97 40.17 44.7 41.38 41.74 41.09 37.32 40.07 39.06 38.18 39.7 40.77 40.26 37.38 37.53 41.54 42.81 44.56 37.81 38.24 41.95 37.81 34.7 39.92 39.51 32.81 35.4 40.17 42.08 36.92 41.48 36.56 41.97 38.91 54.56 51.3 54.09 42.73 50.56 46.28 51.53 38.78 51.29 42.53 38.2 47.62 44.7 49.01 44.69 52.23 46.6 45.61 46.8 43.65 49.46 47.48 48.99 48.54 49.81 51.38 52.32 53.02 48.25 43.8 42.64 42.42 45.41 48.57 45.6 50.06 52.77 47.37 45.84 43.43 44.36 49.2 45.22 38.93 44.95 46.44 46.15 48.81 39.71 45.64 54.15 47.75 56.7 51.69 53.6 60.32 41.47 57.81 52.61 59.86 53.06 51.07 54.56 46.45 48.4 53.06 52.88 63.84 60.02 46.6 56.2 46.84 60.22 49.87 55.43 57.88 49.56 49.92 52.02 56.74 57.7 64.24 52.08 50.36 49.4 60.14 53.8 52.73 49.26 55.25 54.75 54.89 47.75 56.02 55.67 53.7 49.39 52.52 52.92 49.24
+42.05 39.13 38.68 38.17 41.92 45.75 39.16 41.35 36.2 39.66 44.53 40.52 38.58 35.46 47.14 48.8 44.99 42.09 46.86 43.42 43.77 43.08 39.12 41.98 41.09 40.02 41.62 42.75 42.18 39.25 39.38 43.47 44.99 46.75 39.66 40.02 43.9 39.66 36.39 41.86 41.39 34.28 37.15 42.08 44.22 38.66 43.57 38.36 44.02 40.78 57.35 53.95 56.88 44.91 53.13 48.78 54.24 40.78 53.94 44.75 40.15 50.07 46.96 51.62 46.94 54.87 49.11 48.03 49.13 45.91 52.09 49.87 51.54 51.16 52.35 53.98 55 55.75 50.77 46 44.83 44.6 47.73 51.19 48.09 52.71 55.48 49.75 48.27 45.67 46.77 51.81 47.54 40.91 47.32 48.93 48.59 51.33 41.65 48.02 57.07 50.29 59.66 54.51 56.45 63.54 43.72 60.94 55.41 62.99 55.8 53.73 57.37 48.87 50.87 55.79 55.72 67.32 63.18 49.06 59.1 49.31 63.44 52.42 58.38 61 52.1 52.54 54.76 59.78 60.73 67.69 54.8 53.06 52.19 63.18 56.65 55.59 51.84 58.07 57.55 57.58 50.29 58.97 58.54 56.37 51.89 55.23 55.72 51.9
+37 34.54 34.04 33.54 36.82 40.12 34.33 36.36 31.84 34.95 39.18 35.54 34.03 31.21 41.58 42.78 39.56 37.03 41.23 38.07 38.56 37.8 34.34 36.91 35.96 35.3 36.56 37.62 37.18 34.46 34.64 38.34 39.39 41.02 34.95 35.34 38.78 34.95 32 36.84 36.41 30.41 32.6 36.92 38.66 34.09 38.18 33.7 38.7 35.92 50.2 47.07 49.73 39.29 46.49 42.45 47.2 35.64 47.21 38.94 35.2 43.65 41.3 44.98 41.01 48.04 42.63 41.98 43.11 40.17 45.18 43.67 45.05 44.62 45.83 47.26 48.18 48.67 44.25 40.36 39.25 39.08 41.76 44.52 41.67 45.78 48.49 43.69 42.01 39.89 40.72 45.14 41.6 35.82 41.25 42.6 42.33 44.87 36.51 41.89 49.29 43.65 51.97 47.32 48.98 55.35 37.83 53.1 48.32 54.65 48.54 46.81 49.97 42.46 44.1 48.43 48.44 58.4 55.11 42.85 51.39 42.74 55.34 45.74 50.67 53.1 45.42 45.68 47.59 52.16 53.03 58.88 47.62 46.23 45.34 55.19 49 48.26 45.06 50.61 50.02 50.25 43.65 51.19 50.79 49.13 45.31 48.08 48.21 45
+41.59 38.67 38.25 37.68 41.46 45.16 38.68 40.86 35.74 39.19 44.04 40 38.14 35.11 46.74 48.27 44.52 41.61 46.29 42.91 43.22 42.55 38.78 41.37 40.48 39.5 41.06 42.26 41.72 38.73 38.86 42.94 44.52 46.29 39.19 39.62 43.47 39.19 35.96 41.37 40.94 33.83 36.72 41.48 43.57 38.18 43.05 37.9 43.53 40.32 55.66 52.3 55.11 43.45 51.45 47.17 52.51 39.59 52.3 43.3 38.9 48.51 45.56 49.93 45.6 53.27 47.46 46.6 47.48 44.52 50.29 48.41 49.77 49.51 50.8 52.38 53.28 53.91 49.12 44.75 43.47 43.29 46.32 49.34 46.44 51.04 53.77 48.17 46.78 44.21 45.21 50.15 46.1 39.72 45.8 47.43 47.07 49.78 40.53 46.53 54.77 48.38 57.47 52.47 54.27 61.14 42.03 58.69 53.31 60.66 53.87 51.76 55.3 46.97 48.86 53.74 53.71 64.87 60.65 47.26 56.93 47.44 61.03 50.59 56.25 58.84 50.3 50.71 52.66 57.72 58.52 65.37 52.68 51.23 50.23 60.89 54.49 53.58 50.04 56.03 55.39 55.59 48.38 56.74 56.33 54.35 50.01 53.27 53.64 50.01
+36.66 34.14 33.72 33.22 36.52 39.78 34.06 36.02 31.52 34.58 38.82 35.24 33.66 30.94 41.2 42.48 39.22 36.68 40.82 37.78 38.14 37.48 34.12 36.5 35.66 34.88 36.2 37.26 36.8 34.14 34.28 37.9 39.16 40.74 34.58 34.96 38.36 34.58 31.7 36.48 36.08 29.94 32.34 36.56 38.36 33.7 37.9 33.4 38.36 35.56 49.3 46.28 48.82 38.52 45.6 41.74 46.44 35.04 46.34 38.3 34.5 42.92 40.44 44.2 40.34 47.18 41.96 41.26 42.16 39.44 44.46 42.88 44.14 43.84 45 46.4 47.24 47.76 43.48 39.64 38.52 38.36 41.02 43.7 41.04 45.1 47.62 42.76 41.36 39.16 40.02 44.38 40.84 35.18 40.54 41.94 41.64 44.08 35.88 41.18 48.44 42.84 50.94 46.46 48.06 54.22 37.18 52.04 47.3 53.68 47.68 45.88 49 41.62 43.26 47.56 47.56 57.4 53.86 41.94 50.42 41.98 54.16 44.84 49.78 52.12 44.56 44.88 46.66 51.16 51.92 57.86 46.68 45.38 44.5 54.02 48.18 47.42 44.28 49.64 49.06 49.26 42.84 50.24 49.86 48.16 44.36 47.18 47.42 44.24
+42.12 39.21 38.74 38.14 41.96 45.68 39.12 41.37 36.19 39.71 44.6 40.46 38.66 35.56 47.38 48.82 45.08 42.14 46.88 43.4 43.79 43.05 39.24 41.89 40.91 40.04 41.56 42.8 42.28 39.19 39.35 43.53 45 46.83 39.71 40.18 44.09 39.71 36.41 41.9 41.46 34.37 37.15 41.97 44.02 38.7 43.53 38.36 44.07 40.85 56.27 52.81 55.69 43.92 52.01 47.58 52.97 40 52.87 43.68 39.35 48.97 46.14 50.39 46.07 53.86 47.83 47.08 48.05 45 50.67 48.95 50.29 49.98 51.36 52.96 53.88 54.45 49.58 45.28 43.95 43.78 46.82 49.76 46.77 51.45 54.33 48.76 47.19 44.66 45.61 50.61 46.6 40.16 46.23 47.85 47.5 50.3 41 46.98 55.1 48.76 58 52.88 54.69 61.7 42.31 59.23 53.82 61.13 54.34 52.24 55.81 47.36 49.23 54.17 54.16 65.36 61.24 47.74 57.42 47.79 61.62 51.09 56.69 59.36 50.78 51.14 53.1 58.28 59.11 65.93 53.12 51.7 50.63 61.52 54.85 54 50.46 56.56 55.86 56.15 48.76 57.19 56.77 54.87 50.52 53.75 54 50.38
+39.05 36.4 35.92 35.37 38.88 42.33 36.24 38.36 33.57 36.85 41.35 37.5 35.88 32.96 43.92 45.2 41.77 39.07 43.48 40.2 40.64 39.89 36.32 38.87 37.92 37.18 38.54 39.69 39.22 36.34 36.51 40.4 41.65 43.36 36.85 37.28 40.91 36.85 33.76 38.86 38.43 31.97 34.42 38.91 40.78 35.92 40.32 35.56 40.85 37.89 52.42 49.16 51.89 40.95 48.48 44.3 49.29 37.24 49.27 40.65 36.7 45.58 43.06 46.93 42.87 50.17 44.5 43.85 44.86 41.93 47.14 45.6 46.91 46.56 47.85 49.34 50.24 50.74 46.17 42.18 40.96 40.8 43.61 46.37 43.5 47.84 50.61 45.51 43.9 41.61 42.48 47.12 43.42 37.41 43.05 44.52 44.21 46.85 38.17 43.74 51.29 45.43 54.08 49.27 50.96 57.56 39.39 55.25 50.23 56.92 50.6 48.71 52.02 44.15 45.86 50.44 50.46 60.86 57.2 44.56 53.5 44.5 57.52 47.63 52.79 55.32 47.32 47.62 49.5 54.34 55.16 61.4 49.52 48.18 47.2 57.4 51.04 50.29 46.98 52.71 52.05 52.33 45.43 53.28 52.87 51.14 47.13 50.08 50.24 46.9
+53.76 50.76 49.53 49.45 53.38 58.83 50.08 53.21 46.94 51.41 56.97 52.28 49.92 45.01 59.32 61.59 56.95 53.9 60.47 55.33 56.95 55.15 48.7 54.95 53.69 52.4 53.96 54.93 54.14 50.94 51.32 56.29 56.71 58.8 51.41 51.16 56.09 51.41 46.79 53.91 52.73 45.39 47.43 54.56 57.35 50.2 55.66 49.3 56.27 52.42 83.29 78.29 83.35 66.48 78.18 71.83 78.99 58.89 78.92 65.33 59.25 72.74 69.24 76.03 67.22 79.46 72.05 69.91 73.69 67.07 76.62 72.28 77.23 75.43 76.11 78.44 81.08 82.38 74.53 66.07 65.58 64.97 69.25 76.85 70.65 76.27 81.01 73.96 69.89 67.12 69.18 75.88 69.4 59.27 69.4 70.92 70.74 74.71 59.34 69.95 86.36 75.87 89.97 82.22 85.44 96.76 65.83 92.51 84.68 94.09 82.51 81.01 85.99 74.2 76.89 83.17 83.47 100.63 97.87 74.64 88.55 73.99 97.15 78.29 87.19 91.36 77.44 77.85 83.02 89.5 92.09 100.34 83.16 79.13 79.3 95.39 84.66 83.09 76.68 86.54 86.5 85.41 75.87 88.79 87.75 84.16 78.26 82.34 82.88 77.39
+50.44 47.56 46.47 46.41 50.12 55.29 47.07 49.93 44.04 48.18 53.45 49.1 46.77 42.22 55.62 57.91 53.49 50.59 56.74 52 53.39 51.83 45.76 51.6 50.45 49.1 50.68 51.53 50.76 47.82 48.14 52.79 53.3 55.24 48.18 47.94 52.55 48.18 43.91 50.57 49.5 42.46 44.55 51.3 53.95 47.07 52.3 46.28 52.81 49.16 78.29 73.7 78.38 62.51 73.51 67.64 74.43 55.39 74.17 61.57 55.65 68.51 64.94 71.57 63.27 74.7 67.94 65.71 69.22 63.04 72.3 67.95 72.62 70.95 71.54 73.74 76.18 77.53 70.18 62.05 61.63 61.03 65.11 72.39 66.66 71.93 76.2 69.43 65.84 63.15 65.12 71.44 65.24 55.71 65.33 66.78 66.61 70.26 55.79 65.84 81.63 71.56 84.74 77.5 80.6 91.09 62.16 87.05 79.68 88.8 77.79 76.3 81.02 69.98 72.63 78.5 78.65 94.89 92.1 70.19 83.5 69.85 91.39 73.71 82.24 86.02 72.94 73.39 78.27 84.18 86.62 94.52 78.42 74.48 74.66 89.78 80 78.33 72.3 81.53 81.6 80.48 71.56 83.76 82.84 79.33 73.67 77.6 78.31 73.01
+53.2 50.27 49.02 49 52.82 58.28 49.59 52.69 46.51 50.92 56.38 51.8 49.43 44.51 58.6 60.92 56.32 53.35 59.89 54.77 56.43 54.61 48.1 54.5 53.27 51.94 53.48 54.38 53.58 50.49 50.87 55.75 56.09 58.13 50.92 50.6 55.47 50.92 46.33 53.38 52.17 45 46.95 54.09 56.88 49.73 55.11 48.82 55.69 51.89 83.35 78.38 83.48 66.63 78.32 72.01 79.13 58.92 79.02 65.46 59.35 72.84 69.32 76.21 67.22 79.5 72.24 69.98 73.9 67.15 76.85 72.32 77.48 75.6 76.17 78.5 81.22 82.58 74.69 66.04 65.66 65.02 69.3 77.16 70.86 76.39 81.12 74.11 69.98 67.25 69.37 76.03 69.48 59.3 69.54 71.01 70.85 74.79 59.29 70.05 86.85 76.23 90.35 82.6 85.87 97.23 66.16 92.93 85.1 94.48 82.76 81.35 86.32 74.58 77.29 83.5 83.8 101.04 98.45 74.97 88.91 74.33 97.64 78.55 87.54 91.7 77.68 78.1 83.43 89.8 92.48 100.65 83.58 79.39 79.69 95.77 85.05 83.42 76.92 86.83 86.88 85.66 76.23 89.2 88.14 84.46 78.57 82.63 83.23 77.7
+41.96 39.71 38.67 38.69 41.64 45.99 39.11 41.58 36.73 40.21 44.47 40.88 39.03 35.08 46.16 47.99 44.39 42.09 47.28 43.18 44.58 43.08 37.84 43.09 42.08 41.06 42.24 42.91 42.28 39.87 40.19 44.04 44.16 45.77 40.21 39.92 43.76 40.21 36.56 42.13 41.14 35.63 37.02 42.73 44.91 39.29 43.45 38.52 43.92 40.95 66.48 62.51 66.63 53.23 62.54 57.48 63.12 46.97 63.06 52.23 47.4 58.1 55.36 60.84 53.56 63.4 57.65 55.81 59.11 53.58 61.35 57.68 61.95 60.35 60.76 62.62 64.86 65.96 59.62 52.63 52.4 51.87 55.27 61.69 56.55 60.9 64.73 59.23 55.79 53.69 55.39 60.67 55.44 47.29 55.5 56.61 56.51 59.66 47.23 55.88 69.49 60.98 72.28 66.06 68.71 77.83 52.91 74.36 68.14 75.53 66.11 65.08 69.03 59.7 61.86 66.75 66.99 80.75 78.92 60.01 71.1 59.44 78.19 62.8 69.97 73.3 62.08 62.39 66.77 71.78 74.01 80.39 66.9 63.44 63.75 76.64 67.99 66.67 61.44 69.41 69.5 68.47 60.98 71.35 70.49 67.54 62.87 66.05 66.51 62.09
+49.69 46.99 45.79 45.78 49.32 54.44 46.31 49.22 43.46 47.58 52.66 48.38 46.19 41.56 54.72 56.87 52.6 49.84 55.96 51.14 52.74 51.01 44.88 50.96 49.76 48.56 49.98 50.8 50.06 47.17 47.54 52.12 52.33 54.25 47.58 47.28 51.83 47.58 43.28 49.87 48.73 42.12 43.84 50.56 53.13 46.49 51.45 45.6 52.01 48.48 78.18 73.51 78.32 62.54 73.5 67.54 74.21 55.25 74.13 61.4 55.7 68.32 65.06 71.49 63.03 74.57 67.75 65.62 69.41 62.99 72.09 67.84 72.74 70.91 71.45 73.64 76.22 77.5 70.07 61.93 61.6 60.99 65 72.42 66.45 71.62 76.1 69.58 65.61 63.1 65.07 71.31 65.18 55.62 65.23 66.57 66.44 70.15 55.6 65.7 81.54 71.57 84.84 77.53 80.63 91.31 62.1 87.25 79.93 88.69 77.67 76.39 81.05 70.05 72.6 78.39 78.65 94.81 92.52 70.41 83.48 69.78 91.71 73.75 82.16 86.06 72.92 73.29 78.35 84.28 86.85 94.43 78.5 74.5 74.79 89.96 79.83 78.28 72.18 81.52 81.59 80.44 71.57 83.75 82.76 79.32 73.8 77.57 78.11 72.91
+45.43 42.89 41.86 41.91 45.14 49.87 42.43 45.03 39.76 43.49 48.15 44.34 42.19 37.99 49.92 52.08 48.07 45.56 51.17 46.85 48.21 46.7 41.06 46.61 45.69 44.36 45.74 46.45 45.72 43.21 43.5 47.57 48 49.67 43.49 43.12 47.26 43.49 39.59 45.6 44.54 38.33 40.15 46.28 48.78 42.45 47.17 41.74 47.58 44.3 71.83 67.64 72.01 57.48 67.54 62.27 68.38 50.8 68.13 56.57 51.15 62.88 59.66 65.86 57.93 68.49 62.52 60.37 63.7 57.9 66.53 62.3 66.91 65.32 65.64 67.64 70.02 71.3 64.52 56.82 56.6 56.02 59.73 66.79 61.38 66.05 69.97 63.84 60.46 58.04 59.99 65.69 59.9 51.09 60.07 61.35 61.2 64.5 51 60.47 75.4 66.04 78.15 71.57 74.41 84.15 57.39 80.42 73.63 81.82 71.56 70.36 74.64 64.59 66.97 72.28 72.54 87.54 85.21 64.81 76.93 64.41 84.48 67.86 75.81 79.34 67.12 67.56 72.25 77.62 79.94 87.07 72.38 68.65 69.07 82.73 73.75 72.25 66.54 75.04 75.19 73.95 66.04 77.26 76.33 72.98 67.88 71.45 72.15 67.32
+50.58 47.67 46.6 46.58 50.28 55.52 47.26 50.09 44.19 48.31 53.6 49.3 46.88 42.32 55.7 58.12 53.64 50.74 56.92 52.2 53.55 52.03 45.88 51.81 50.71 49.24 50.88 51.68 50.88 48.01 48.31 52.93 53.5 55.41 48.31 48.02 52.63 48.31 44.05 50.72 49.64 42.53 44.71 51.53 54.24 47.2 52.51 46.44 52.97 49.29 78.99 74.43 79.13 63.12 74.21 68.38 75.23 55.9 74.85 62.24 56.15 69.21 65.46 72.33 63.85 75.36 68.73 66.32 69.87 63.62 73.17 68.55 73.37 71.68 72.18 74.4 76.88 78.33 70.92 62.54 62.19 61.56 65.7 73.24 67.47 72.73 76.93 70.04 66.53 63.78 65.83 72.19 65.84 56.2 66.01 67.47 67.3 70.92 56.24 66.5 82.78 72.46 85.72 78.46 81.63 92.16 62.99 88.05 80.6 89.91 78.68 77.18 81.95 70.86 73.59 79.47 79.58 96.06 93.2 70.96 84.5 70.75 92.44 74.51 83.25 87 73.74 74.24 79.24 85.08 87.57 95.59 79.4 75.3 75.57 90.76 81.07 79.28 73.14 82.44 82.6 81.35 72.46 84.81 83.89 80.23 74.48 78.49 79.34 73.92
+38.21 36.03 35.2 35.13 37.96 41.83 35.62 37.81 33.34 36.5 40.49 37.16 35.44 32 42.18 43.84 40.51 38.31 42.96 39.36 40.43 39.22 34.68 39.02 38.15 37.18 38.34 39.03 38.46 36.19 36.44 39.97 40.37 41.85 36.5 36.34 39.84 36.5 33.25 38.3 37.49 32.16 33.73 38.78 40.78 35.64 39.59 35.04 40 37.24 58.89 55.39 58.92 46.97 55.25 50.8 55.9 41.66 55.78 46.23 41.85 51.47 48.88 53.76 47.58 56.19 50.99 49.43 52.01 47.41 54.23 51.11 54.54 53.32 53.81 55.46 57.28 58.23 52.71 46.72 46.35 45.92 48.97 54.31 50.01 54.01 57.28 52.21 49.47 47.45 48.91 53.67 49.06 41.91 49.08 50.19 50.05 52.83 41.99 49.48 61.09 53.63 63.56 58.11 60.39 68.32 46.56 65.32 59.77 66.55 58.36 57.23 60.77 52.43 54.37 58.83 59 71.16 69.04 52.68 62.6 52.33 68.56 55.32 61.66 64.56 54.74 55.06 58.66 63.22 65.01 70.95 58.76 55.92 56.01 67.36 59.91 58.75 54.24 61.17 61.15 60.38 53.63 62.77 62.06 59.49 55.27 58.21 58.66 54.74
+50.51 47.74 46.54 46.51 50.14 55.29 47.05 50.02 44.15 48.36 53.53 49.16 46.95 42.27 55.66 57.8 53.45 50.64 56.85 51.97 53.58 51.81 45.66 51.7 50.54 49.32 50.74 51.63 50.88 47.92 48.29 52.92 53.24 55.18 48.36 48.06 52.69 48.36 43.98 50.68 49.52 42.74 44.56 51.29 53.94 47.21 52.3 46.34 52.87 49.27 78.92 74.17 79.02 63.06 74.13 68.13 74.85 55.78 74.82 61.91 56.2 68.91 65.68 72.11 63.62 75.27 68.31 66.27 69.95 63.58 72.64 68.47 73.32 71.56 72.12 74.32 76.9 78.13 70.66 62.56 62.17 61.58 65.61 72.97 66.99 72.24 76.78 70.18 66.21 63.64 65.64 71.94 65.78 56.15 65.8 67.2 67.04 70.8 56.14 66.29 82 72.04 85.43 78.09 81.14 91.95 62.5 87.91 80.49 89.27 78.24 76.92 81.61 70.47 72.98 78.89 79.24 95.52 93.09 70.93 84.03 70.22 92.36 74.29 82.74 86.74 73.46 73.84 78.85 84.98 87.49 95.2 78.98 75.11 75.38 90.57 80.32 78.87 72.72 82.1 82.09 80.98 72.04 84.29 83.26 79.83 74.3 78.12 78.61 73.44
+41.71 39.32 38.43 38.42 41.46 45.8 38.98 41.31 36.45 39.84 44.2 40.66 38.66 34.89 45.92 47.93 44.24 41.85 46.95 43.05 44.17 42.92 37.82 42.76 41.83 40.62 41.98 42.62 41.96 39.6 39.85 43.67 44.1 45.68 39.84 39.6 43.4 39.84 36.33 41.83 40.94 35.1 36.87 42.53 44.75 38.94 43.3 38.3 43.68 40.65 65.33 61.57 65.46 52.23 61.4 56.57 62.24 46.23 61.91 51.5 46.45 57.26 54.14 59.84 52.81 62.33 56.87 54.84 57.83 52.62 60.56 56.7 60.72 59.29 59.7 61.54 63.6 64.82 58.68 51.71 51.44 50.91 54.34 60.62 55.83 60.17 63.64 57.95 55.03 52.77 54.46 59.72 54.46 46.48 54.61 55.8 55.67 58.66 46.51 55.01 68.57 60 70.97 64.95 67.6 76.3 52.16 72.88 66.73 74.45 65.13 63.9 67.85 58.69 60.97 65.81 65.87 79.51 77.19 58.74 69.97 58.59 76.53 61.68 68.92 72 61.04 61.45 65.62 70.4 72.49 79.1 65.76 62.31 62.54 75.15 67.14 65.62 60.54 68.25 68.41 67.36 60 70.23 69.48 66.44 61.67 64.98 65.7 61.19
+37.6 35.6 34.65 34.65 37.3 41.15 35 37.25 32.9 36.05 39.85 36.6 35 31.45 41.4 42.95 39.75 37.7 42.35 38.65 39.95 38.55 33.9 38.55 37.65 36.8 37.8 38.45 37.9 35.7 36 39.45 39.55 41 36.05 35.8 39.25 36.05 32.75 37.75 36.85 31.95 33.15 38.2 40.15 35.2 38.9 34.5 39.35 36.7 59.25 55.65 59.35 47.4 55.7 51.15 56.15 41.85 56.2 46.45 42.25 51.7 49.4 54.15 47.7 56.5 51.25 49.75 52.65 47.75 54.5 51.4 55.15 53.75 54.15 55.8 57.8 58.7 53.05 46.95 46.7 46.25 49.25 54.85 50.25 54.15 57.65 52.8 49.65 47.8 49.3 54 49.4 42.15 49.4 50.4 50.3 53.15 42.1 49.75 61.6 54.15 64.25 58.7 61 69.2 46.95 66.15 60.6 67.05 58.75 57.85 61.35 53 54.85 59.25 59.55 71.75 70.15 53.4 63.15 52.75 69.55 55.85 62.15 65.2 55.2 55.45 59.3 63.9 65.85 71.5 59.4 56.45 56.7 68.15 60.3 59.25 54.6 61.7 61.7 60.85 54.15 63.35 62.55 60 55.9 58.7 59 55.15
+46.77 44.09 43.09 43.04 46.48 51.3 43.67 46.3 40.84 44.66 49.56 45.54 43.35 39.14 51.56 53.73 49.62 46.92 52.62 48.24 49.5 48.09 42.44 47.88 46.8 45.52 47.02 47.78 47.06 44.35 44.64 48.96 49.43 51.23 44.66 44.44 48.71 44.66 40.72 46.89 45.91 39.36 41.32 47.62 50.07 43.65 48.51 42.92 48.97 45.58 72.74 68.51 72.84 58.1 68.32 62.88 69.21 51.47 68.91 57.26 51.7 63.7 60.3 66.53 58.81 69.41 63.19 61.04 64.33 58.57 67.27 63.14 67.5 65.93 66.47 68.52 70.78 72.08 65.25 57.63 57.26 56.69 60.5 67.32 62.01 66.9 70.82 64.5 61.21 58.7 60.53 66.41 60.62 51.76 60.73 62.07 61.92 65.29 51.84 61.2 76.02 66.59 78.82 72.09 75.01 84.71 57.86 80.93 74.09 82.65 72.37 70.97 75.37 65.13 67.64 73.07 73.15 88.27 85.66 65.25 77.7 65.02 84.97 68.55 76.52 79.98 67.84 68.27 72.83 78.24 80.53 87.89 72.98 69.24 69.41 83.5 74.49 72.86 67.26 75.84 75.95 74.88 66.59 77.95 77.12 73.82 68.52 72.19 72.91 67.93
+44.1 41.8 40.64 40.62 43.72 48.18 40.98 43.68 38.58 42.32 46.74 42.88 41.1 36.9 48.6 50.28 46.58 44.2 49.66 45.26 46.88 45.14 39.72 45.16 44.08 43.2 44.28 45.1 44.48 41.84 42.22 46.28 46.32 48.04 42.32 42.04 46.1 42.32 38.4 44.28 43.2 37.56 38.84 44.7 46.96 41.3 45.56 40.44 46.14 43.06 69.24 64.94 69.32 55.36 65.06 59.66 65.46 48.88 65.68 54.14 49.4 60.3 57.84 63.18 55.68 66.02 59.7 58.14 61.54 55.8 63.44 60.06 64.4 62.76 63.28 65.2 67.56 68.5 61.88 54.92 54.58 54.08 57.54 63.94 58.5 63.08 67.32 61.76 57.9 55.8 57.52 63 57.72 49.26 57.64 58.8 58.68 62.08 49.2 58.06 71.56 63.04 74.9 68.38 71 80.7 54.6 77.18 70.7 78.02 68.44 67.44 71.5 61.7 63.76 68.94 69.4 83.56 81.82 62.34 73.54 61.36 81.16 65.14 72.36 76.04 64.36 64.6 69.06 74.6 76.86 83.36 69.16 65.86 66.12 79.5 70.08 69.02 63.6 71.92 71.82 70.92 63.04 73.74 72.76 69.9 65.2 68.4 68.58 64.2
+48.12 45.45 44.34 44.38 47.8 52.8 44.92 47.69 42.11 46.07 51 46.94 44.7 40.24 52.9 55.14 50.92 48.26 54.2 49.6 51.07 49.45 43.48 49.37 48.35 47 48.44 49.2 48.44 45.75 46.07 50.41 50.8 52.59 46.07 45.7 50.09 46.07 41.93 48.3 47.18 40.65 42.51 49.01 51.62 44.98 49.93 44.2 50.39 46.93 76.03 71.57 76.21 60.84 71.49 65.86 72.33 53.76 72.11 59.84 54.15 66.53 63.18 69.67 61.31 72.5 66.11 63.88 67.45 61.28 70.35 65.95 70.81 69.1 69.48 71.6 74.12 75.45 68.26 60.16 59.91 59.3 63.22 70.64 64.89 69.85 74.05 67.6 63.95 61.42 63.45 69.49 63.4 54.08 63.55 64.89 64.74 68.26 54 63.98 79.7 69.84 82.68 75.68 78.69 89.02 60.67 85.07 77.9 86.53 75.7 74.44 78.97 68.32 70.83 76.45 76.72 92.56 90.16 68.58 81.38 68.11 89.38 71.81 80.17 83.92 71.02 71.46 76.42 82.12 84.59 92.09 76.56 72.62 73.03 87.56 77.97 76.4 70.38 79.4 79.54 78.27 69.84 81.71 80.73 77.23 71.84 75.59 76.28 71.18
+44.01 41.44 40.54 40.41 43.74 48.19 41.05 43.52 38.35 41.96 46.63 42.76 40.75 36.87 48.66 50.6 46.75 44.14 49.45 45.37 46.48 45.21 40.06 44.9 43.84 42.72 44.14 44.93 44.28 41.62 41.89 46.02 46.54 48.28 41.96 41.86 45.89 41.96 38.28 44.08 43.22 36.94 38.86 44.69 46.94 41.01 45.6 40.34 46.07 42.87 67.22 63.27 67.22 53.56 63.03 57.93 63.85 47.58 63.62 52.81 47.7 58.81 55.68 61.31 54.42 64.17 58.21 56.37 59.25 54.08 61.94 58.37 62.12 60.76 61.42 63.32 65.3 66.43 60.16 53.36 52.87 52.38 55.91 61.87 57.09 61.74 65.38 59.48 56.51 54.14 55.74 61.24 55.98 47.85 56 57.3 57.14 60.3 48.04 56.49 69.7 61.14 72.43 66.19 68.84 77.75 53.1 74.31 67.99 75.97 66.64 65.22 69.31 59.77 62.08 67.19 67.24 81.12 78.49 59.93 71.43 59.72 77.96 63.09 70.34 73.54 62.46 62.84 66.85 71.98 73.99 80.9 66.98 63.71 63.68 76.77 68.42 66.97 61.92 69.8 69.79 68.98 61.14 71.59 70.86 67.93 63 66.42 67.01 62.44
+51.46 48.57 47.41 47.31 51.1 56.31 47.94 50.92 44.91 49.18 54.53 50.02 47.76 43.09 56.82 58.99 54.55 51.6 57.87 52.97 54.48 52.8 46.66 52.58 51.34 50.12 51.64 52.57 51.82 48.73 49.09 53.88 54.29 56.31 49.18 48.98 53.7 49.18 44.78 51.59 50.49 43.42 45.4 52.23 54.87 48.04 53.27 47.18 53.86 50.17 79.46 74.7 79.5 63.4 74.57 68.49 75.36 56.19 75.27 62.33 56.5 69.41 66.02 72.5 64.17 75.82 68.72 66.67 70.26 63.97 73.09 68.97 73.62 71.91 72.61 74.84 77.32 78.57 71.09 63.05 62.55 61.97 66.07 73.25 67.38 72.78 77.28 70.52 66.68 64.02 65.95 72.37 66.2 56.55 66.19 67.65 67.48 71.27 56.66 66.73 82.34 72.33 85.77 78.36 81.45 92.2 62.76 88.14 80.68 89.74 78.71 77.23 82 70.74 73.34 79.34 79.57 95.93 93.23 71.12 84.45 70.56 92.55 74.66 83.14 87.08 73.86 74.25 79.14 85.3 87.76 95.67 79.28 75.43 75.53 90.95 80.75 79.21 73.14 82.54 82.5 81.5 72.33 84.66 83.7 80.29 74.62 78.53 79.06 73.79
+45.66 43.04 42.07 42.11 45.4 50.19 42.71 45.25 39.94 43.64 48.39 44.58 42.33 38.18 50.18 52.47 48.39 45.81 51.42 47.16 48.39 47.01 41.36 46.86 45.93 44.5 46 46.67 45.92 43.42 43.68 47.79 48.32 50 43.64 43.3 47.45 43.64 39.79 45.81 44.8 38.4 40.39 46.6 49.11 42.63 47.46 41.96 47.83 44.5 72.05 67.94 72.24 57.65 67.75 62.52 68.73 50.99 68.31 56.87 51.25 63.19 59.7 66.11 58.21 68.72 62.86 60.53 63.82 58.06 66.94 62.51 67.08 65.51 65.84 67.86 70.18 71.57 64.8 56.97 56.75 56.15 59.93 67.05 61.74 66.45 70.22 63.93 60.76 58.25 60.2 65.96 60.08 51.25 60.31 61.62 61.47 64.72 51.21 60.72 75.93 66.38 78.46 71.88 74.8 84.41 57.74 80.63 73.82 82.32 71.95 70.64 74.98 64.92 67.43 72.74 72.85 87.97 85.42 64.95 77.34 64.81 84.67 68.13 76.22 79.62 67.42 67.91 72.59 77.82 80.14 87.44 72.74 68.88 69.26 83.02 74.28 72.59 66.9 75.39 75.62 74.34 66.38 77.68 76.82 73.37 68.13 71.8 72.67 67.69
+44.97 42.46 41.43 41.4 44.66 49.22 41.9 44.53 39.29 43.04 47.66 43.78 41.78 37.65 49.56 51.49 47.58 45.07 50.59 46.29 47.67 46.12 40.7 45.96 45.01 43.86 45.14 45.96 45.28 42.66 42.97 47.05 47.48 49.18 43.04 42.76 46.88 43.04 39.15 45.11 44.08 37.94 39.69 45.61 48.03 41.98 46.6 41.26 47.08 43.85 69.91 65.71 69.98 55.81 65.62 60.37 66.32 49.43 66.27 54.84 49.75 61.04 58.14 63.88 56.37 66.67 60.53 58.74 61.85 56.32 64.34 60.64 64.88 63.41 63.88 65.82 68.08 69.16 62.58 55.43 55.06 54.55 58.12 64.6 59.37 64.03 68 62.09 58.69 56.35 58.16 63.74 58.26 49.74 58.29 59.58 59.41 62.72 49.73 58.73 72.55 63.74 75.57 69.13 71.78 81.34 55.32 77.8 71.19 78.99 69.25 68.04 72.19 62.31 64.51 69.79 70.15 84.59 82.27 62.74 74.33 62.13 81.69 65.72 73.24 76.8 65 65.37 69.74 75.24 77.39 84.32 69.84 66.51 66.76 80.07 71.08 69.84 64.38 72.63 72.59 71.6 63.74 74.57 73.64 70.58 65.69 69.12 69.58 65.03
+45.92 43.58 42.33 42.39 45.52 50.31 42.75 45.53 40.26 44.1 48.67 44.74 42.81 38.36 50.46 52.37 48.51 46.07 51.8 47.18 48.91 47.11 41.24 47.28 46.09 45.1 46.28 46.99 46.32 43.68 44.08 48.31 48.16 49.94 44.1 43.74 47.95 44.1 40.03 46.15 45 39.26 40.47 46.8 49.13 43.11 47.48 42.16 48.05 44.86 73.69 69.22 73.9 59.11 69.41 63.7 69.87 52.01 69.95 57.83 52.65 64.33 61.54 67.45 59.25 70.26 63.82 61.85 65.78 59.42 67.92 63.93 68.86 66.93 67.36 69.42 72.02 73.19 66.08 58.31 58.13 57.53 61.25 68.49 62.58 67.33 71.76 65.87 61.72 59.55 61.42 67.22 61.48 52.41 61.51 62.64 62.57 66.12 52.27 61.9 77.07 67.7 80.32 73.34 76.3 86.57 58.68 82.69 75.84 83.76 73.29 72.32 76.66 66.34 68.67 74.02 74.35 89.55 87.96 66.79 78.92 65.93 87.05 69.75 77.6 81.38 68.9 69.17 74.19 79.74 82.34 89.14 74.34 70.42 70.84 85.24 75.34 73.95 68.1 77.05 77.16 76 67.7 79.2 78.2 74.99 69.91 73.3 73.67 68.83
+42.99 40.62 39.61 39.58 42.68 47.06 40.05 42.57 37.57 41.15 45.56 41.84 39.95 35.98 47.38 49.21 45.5 43.1 48.38 44.24 45.59 44.1 38.88 43.99 43.01 41.96 43.18 43.94 43.3 40.78 41.09 45.03 45.33 46.98 41.15 40.9 44.84 41.15 37.43 43.13 42.15 36.35 37.93 43.65 45.91 40.17 44.52 39.44 45 41.93 67.07 63.04 67.15 53.58 62.99 57.9 63.62 47.41 63.58 52.62 47.75 58.57 55.8 61.28 54.08 63.97 58.06 56.32 59.42 54.03 61.74 58.19 62.29 60.81 61.29 63.16 65.34 66.39 60.05 53.17 52.83 52.33 55.76 62 56.94 61.41 65.25 59.62 56.28 54.08 55.78 61.14 55.9 47.72 55.92 57.12 56.98 60.17 47.72 56.34 69.68 61.21 72.58 66.35 68.94 78.11 53.11 74.68 68.37 75.86 66.49 65.35 69.34 59.87 62.01 67.04 67.33 81.17 79.06 60.25 71.4 59.67 78.45 63.12 70.31 73.7 62.42 62.75 66.99 72.2 74.32 80.9 67.1 63.82 64.04 76.94 68.26 67.02 61.8 69.76 69.75 68.81 61.21 71.62 70.75 67.83 63.12 66.38 66.81 62.41
+48.37 45.59 44.57 44.62 48.1 53.22 45.28 47.94 42.32 46.21 51.26 47.24 44.82 40.43 53.14 55.63 51.3 48.55 54.49 49.99 51.26 49.85 43.82 49.71 48.68 47.14 48.78 49.44 48.64 46.01 46.28 50.66 51.18 52.97 46.21 45.86 50.25 46.21 42.16 48.53 47.48 40.69 42.8 49.46 52.09 45.18 50.29 44.46 50.67 47.14 76.62 72.3 76.85 61.35 72.09 66.53 73.17 54.23 72.64 60.56 54.5 67.27 63.44 70.35 61.94 73.09 66.94 64.34 67.92 61.74 71.33 66.49 71.39 69.67 70.02 72.18 74.64 76.19 68.98 60.55 60.35 59.69 63.74 71.4 65.76 70.76 74.71 67.99 64.66 61.99 64.05 70.19 63.9 54.5 64.18 65.55 65.41 68.84 54.47 64.61 81.03 70.76 83.59 76.57 79.75 89.9 61.57 85.83 78.61 87.78 76.67 75.26 79.9 69.23 71.98 77.58 77.59 93.71 91.01 69.14 82.45 69.12 90.15 72.57 81.23 84.76 71.82 72.35 77.38 82.8 85.32 93.09 77.56 73.31 73.71 88.45 79.25 77.32 71.28 80.33 80.65 79.25 70.76 82.82 81.95 78.23 72.59 76.51 77.52 72.13
+46.77 44.15 43.09 43 46.44 51.18 43.57 46.28 40.82 44.7 49.56 45.46 43.41 39.16 51.64 53.61 49.58 46.9 52.6 48.14 49.52 47.99 42.4 47.8 46.66 45.56 46.94 47.78 47.1 44.29 44.62 48.98 49.33 51.17 44.7 44.52 48.81 44.7 40.7 46.89 45.89 39.48 41.26 47.48 49.87 43.67 48.41 42.88 48.95 45.6 72.28 67.95 72.32 57.68 67.84 62.3 68.55 51.11 68.47 56.7 51.4 63.14 60.06 65.95 58.37 68.97 62.51 60.64 63.93 58.19 66.49 62.74 66.98 65.41 66.05 68.08 70.34 71.48 64.67 57.35 56.9 56.37 60.1 66.64 61.29 66.2 70.3 64.16 60.65 58.24 59.99 65.83 60.22 51.44 60.21 61.53 61.38 64.83 51.54 60.7 74.92 65.81 78.04 71.29 74.11 83.89 57.1 80.19 73.41 81.65 71.61 70.27 74.61 64.37 66.74 72.19 72.39 87.27 84.84 64.71 76.84 64.2 84.21 67.93 75.64 79.22 67.2 67.55 72.01 77.6 79.85 87.03 72.14 68.62 68.71 82.76 73.47 72.06 66.54 75.1 75.07 74.16 65.81 77.03 76.16 73.06 67.9 71.45 71.93 67.13
+48.04 45.53 44.28 44.38 47.66 52.7 44.79 47.65 42.13 46.12 50.92 46.88 44.75 40.13 52.72 54.86 50.74 48.19 54.19 49.43 51.15 49.33 43.18 49.46 48.35 47.14 48.44 49.16 48.42 45.75 46.13 50.47 50.51 52.31 46.12 45.68 50.07 46.12 41.89 48.28 47.07 40.92 42.39 48.99 51.54 45.05 49.77 44.14 50.29 46.91 77.23 72.62 77.48 61.95 72.74 66.91 73.37 54.54 73.32 60.72 55.15 67.5 64.4 70.81 62.12 73.62 67.08 64.88 68.86 62.29 71.39 66.98 72.2 70.26 70.59 72.74 75.46 76.76 69.35 61.06 60.92 60.28 64.2 71.94 65.82 70.75 75.24 68.95 64.82 62.45 64.51 70.57 64.44 54.92 64.56 65.79 65.69 69.33 54.73 64.95 81.09 71.13 84.29 77.08 80.17 90.87 61.72 86.81 79.58 88 76.94 75.89 80.44 69.66 72.13 77.74 78.1 94.14 92.27 70.05 82.85 69.29 91.34 73.15 81.54 85.46 72.28 72.64 77.91 83.68 86.36 93.63 78.06 73.93 74.47 89.35 79.23 77.72 71.52 80.83 81 79.66 71.13 83.2 82.14 78.64 73.29 76.93 77.47 72.36
+47.73 45.11 43.98 44.03 47.4 52.33 44.52 47.31 41.78 45.74 50.59 46.56 44.38 39.92 52.46 54.62 50.45 47.85 53.76 49.16 50.69 49 43.08 48.94 47.97 46.66 48.02 48.81 48.06 45.39 45.72 49.99 50.37 52.13 45.74 45.34 49.7 45.74 41.59 47.92 46.77 40.36 42.15 48.54 51.16 44.62 49.51 43.84 49.98 46.56 75.43 70.95 75.6 60.35 70.91 65.32 71.68 53.32 71.56 59.29 53.75 65.93 62.76 69.1 60.76 71.91 65.51 63.41 66.93 60.81 69.67 65.41 70.26 68.58 68.93 71.02 73.56 74.81 67.67 59.7 59.45 58.86 62.71 70.05 64.29 69.19 73.44 67.11 63.39 60.91 62.95 68.91 62.9 53.65 63.02 64.35 64.19 67.71 53.53 63.44 78.87 69.19 81.96 75.03 77.95 88.3 60.08 84.42 77.29 85.67 74.98 73.79 78.25 67.67 70.07 75.67 76.06 91.74 89.44 68.06 80.6 67.43 88.7 71.18 79.42 83.24 70.38 70.8 75.72 81.5 83.93 91.31 75.84 72.04 72.49 86.8 77.15 75.73 69.72 78.67 78.75 77.5 69.19 80.93 79.9 76.47 71.21 74.89 75.48 70.52
+49.07 46.34 45.21 45.14 48.72 53.7 45.71 48.57 42.85 46.93 52 47.72 45.57 41.08 54.14 56.21 51.98 49.2 55.2 50.5 51.99 50.34 44.44 50.17 49.01 47.84 49.26 50.14 49.42 46.5 46.85 51.39 51.75 53.66 46.93 46.7 51.2 46.93 42.71 49.21 48.13 41.45 43.29 49.81 52.35 45.83 50.8 45 51.36 47.85 76.11 71.54 76.17 60.76 71.45 65.64 72.18 53.81 72.12 59.7 54.15 66.47 63.28 69.48 61.42 72.61 65.84 63.88 67.36 61.29 70.02 66.05 70.59 68.93 69.55 71.68 74.1 75.29 68.11 60.37 59.93 59.37 63.28 70.24 64.56 69.69 74.03 67.6 63.86 61.34 63.22 69.34 63.42 54.16 63.42 64.8 64.64 68.27 54.22 63.92 78.94 69.35 82.24 75.15 78.1 88.45 60.17 84.56 77.41 86 75.41 74.05 78.6 67.83 70.29 76.02 76.29 91.97 89.48 68.23 80.94 67.63 88.81 71.56 79.69 83.5 70.78 71.15 75.89 81.8 84.18 91.7 76.02 72.32 72.48 87.2 77.38 75.94 70.08 79.1 79.07 78.07 69.35 81.16 80.21 76.93 71.54 75.26 75.75 70.73
+50.6 47.78 46.62 46.54 50.24 55.38 47.14 50.08 44.18 48.38 53.62 49.2 46.98 42.36 55.84 57.98 53.62 50.74 56.92 52.08 53.6 51.92 45.84 51.74 50.52 49.32 50.8 51.7 50.96 47.94 48.3 53 53.36 55.34 48.38 48.16 52.8 48.38 44.04 50.74 49.64 42.74 44.64 51.38 53.98 47.26 52.38 46.4 52.96 49.34 78.44 73.74 78.5 62.62 73.64 67.64 74.4 55.46 74.32 61.54 55.8 68.52 65.2 71.6 63.32 74.84 67.86 65.82 69.42 63.16 72.18 68.08 72.74 71.02 71.68 73.88 76.36 77.6 70.2 62.22 61.76 61.18 65.22 72.38 66.54 71.84 76.3 69.66 65.82 63.22 65.14 71.46 65.36 55.82 65.36 66.78 66.62 70.36 55.9 65.88 81.38 71.48 84.76 77.44 80.5 91.14 62.02 87.12 79.76 88.66 77.74 76.32 81.02 69.92 72.48 78.38 78.62 94.78 92.2 70.3 83.44 69.72 91.5 73.76 82.14 86.04 72.96 73.34 78.22 84.28 86.74 94.5 78.36 74.52 74.66 89.88 79.78 78.26 72.24 81.54 81.52 80.5 71.48 83.66 82.7 79.32 73.74 77.58 78.1 72.9
+51.48 48.72 47.44 47.44 51.08 56.36 47.94 51 45.04 49.34 54.56 50.12 47.9 43.06 56.68 58.84 54.44 51.62 57.98 52.94 54.68 52.8 46.44 52.78 51.56 50.36 51.76 52.64 51.88 48.88 49.28 54 54.18 56.16 49.34 49 53.72 49.34 44.84 51.68 50.46 43.7 45.4 52.32 55 48.18 53.28 47.24 53.88 50.24 81.08 76.18 81.22 64.86 76.22 70.02 76.88 57.28 76.9 63.6 57.8 70.78 67.56 74.12 65.3 77.32 70.18 68.08 72.02 65.34 74.64 70.34 75.46 73.56 74.1 76.36 79.08 80.34 72.62 64.24 63.9 63.28 67.4 75.08 68.82 74.16 78.9 72.22 67.98 65.42 67.48 73.92 67.6 57.68 67.62 69 68.86 72.74 57.62 68.1 84.38 74.14 87.94 80.36 83.52 94.7 64.3 90.52 82.92 91.82 80.44 79.18 83.98 72.56 75.12 81.14 81.52 98.24 95.98 73.06 86.46 72.24 95.16 76.44 85.1 89.24 75.56 75.92 81.18 87.44 90.1 97.88 81.32 77.26 77.6 93.26 82.6 81.12 74.76 84.46 84.48 83.3 74.14 86.74 85.66 82.14 76.5 80.36 80.82 75.52
+52.01 49.17 47.93 47.96 51.64 57.06 48.53 51.54 45.52 49.8 55.12 50.7 48.33 43.48 57.2 59.57 55.06 52.18 58.6 53.58 55.22 53.45 46.96 53.42 52.2 50.84 52.38 53.18 52.38 49.43 49.8 54.56 54.81 56.79 49.8 49.44 54.19 49.8 45.32 52.21 51.01 44.06 45.92 53.02 55.75 48.67 53.91 47.76 54.45 50.74 82.38 77.53 82.58 65.96 77.5 71.3 78.33 58.23 78.13 64.82 58.7 72.08 68.5 75.45 66.43 78.57 71.57 69.16 73.19 66.39 76.19 71.48 76.76 74.81 75.29 77.6 80.34 81.78 73.95 65.19 64.92 64.25 68.5 76.52 70.23 75.62 80.24 73.32 69.23 66.56 68.69 75.25 68.7 58.6 68.83 70.23 70.1 73.95 58.54 69.3 86.36 75.69 89.64 81.97 85.29 96.49 65.72 92.17 84.45 93.79 82.05 80.71 85.63 74.09 76.84 82.89 83.11 100.23 97.8 74.35 88.24 73.82 96.89 77.87 86.86 90.9 77 77.43 82.85 88.96 91.71 99.73 83.02 78.66 79.05 95 84.49 82.74 76.26 86.1 86.27 84.94 75.69 88.57 87.54 83.8 77.94 81.95 82.65 77.09
+47.35 44.7 43.63 43.64 47.04 51.96 44.21 46.91 41.41 45.29 50.18 46.16 43.95 39.6 52.1 54.31 50.16 47.5 53.32 48.82 50.21 48.68 42.84 48.57 47.51 46.2 47.66 48.4 47.66 44.98 45.29 49.61 49.99 51.78 45.29 44.98 49.3 45.29 41.25 47.51 46.45 39.97 41.83 48.25 50.77 44.25 49.12 43.48 49.58 46.17 74.53 70.18 74.69 59.62 70.07 64.52 70.92 52.71 70.66 58.68 53.05 65.25 61.88 68.26 60.16 71.09 64.8 62.58 66.08 60.05 68.98 64.67 69.35 67.67 68.11 70.2 72.62 73.95 66.91 58.99 58.71 58.11 61.98 69.18 63.6 68.51 72.59 66.22 62.7 60.2 62.14 68.1 62.14 53.02 62.28 63.6 63.46 66.91 53 62.72 78.12 68.43 81 74.11 77.1 87.15 59.45 83.26 76.25 84.84 74.23 72.93 77.4 66.95 69.47 74.98 75.15 90.67 88.24 67.13 79.78 66.77 87.47 70.38 78.57 82.18 69.62 70.05 74.87 80.4 82.82 90.22 75.02 71.12 71.44 85.8 76.46 74.84 69 77.84 77.99 76.79 68.43 80.08 79.17 75.75 70.4 74.1 74.81 69.75
+43.27 40.83 39.86 39.73 42.96 47.27 40.26 42.79 37.72 41.34 45.85 42 40.16 36.26 47.86 49.58 45.87 43.37 48.62 44.5 45.77 44.34 39.28 44.1 43.05 42.1 43.34 44.19 43.58 40.91 41.22 45.27 45.65 47.37 41.34 41.22 45.2 41.34 37.63 43.36 42.45 36.48 38.15 43.8 46 40.36 44.75 39.64 45.28 42.18 66.07 62.05 66.04 52.63 61.93 56.82 62.54 46.72 62.56 51.71 46.95 57.63 54.92 60.16 53.36 63.05 56.97 55.43 58.31 53.17 60.55 57.35 61.06 59.7 60.37 62.22 64.24 65.19 58.99 52.5 51.99 51.54 54.93 60.67 55.83 60.39 64.2 58.59 55.37 53.15 54.71 60.07 55.02 47.03 54.94 56.19 56.03 59.23 47.17 55.42 67.99 59.83 71.02 64.85 67.35 76.32 51.88 73 66.79 74.25 65.22 63.95 67.91 58.49 60.59 65.65 65.9 79.42 77.1 58.92 69.9 58.35 76.62 61.88 68.82 72.16 61.22 61.52 65.46 70.74 72.71 79.31 65.56 62.54 62.53 75.34 66.75 65.59 60.6 68.39 68.25 67.54 59.83 70.03 69.22 66.49 61.81 65.05 65.38 61.08
+41.99 39.69 38.69 38.66 41.68 45.96 39.11 41.58 36.7 40.2 44.5 40.86 39.03 35.14 46.28 48.05 44.44 42.1 47.26 43.2 44.54 43.07 37.96 42.98 42 41 42.18 42.92 42.3 39.83 40.14 44 44.25 45.87 40.2 39.96 43.81 40.2 36.56 42.13 41.17 35.54 37.04 42.64 44.83 39.25 43.47 38.52 43.95 40.96 65.58 61.63 65.66 52.4 61.6 56.6 62.19 46.35 62.17 51.44 46.7 57.26 54.58 59.91 52.87 62.55 56.75 55.06 58.13 52.83 60.35 56.9 60.92 59.45 59.93 61.76 63.9 64.92 58.71 51.99 51.66 51.17 54.52 60.62 55.65 60.02 63.8 58.32 55.01 52.88 54.53 59.77 54.66 46.66 54.67 55.83 55.7 58.83 46.66 55.08 68.12 59.85 70.98 64.87 67.41 76.39 51.92 73.03 66.87 74.17 65.01 63.91 67.81 58.55 60.64 65.55 65.83 79.35 77.34 58.93 69.82 58.34 76.73 61.73 68.74 72.06 61.04 61.35 65.51 70.6 72.69 79.09 65.62 62.4 62.61 75.26 66.73 65.52 60.42 68.22 68.21 67.3 59.85 70.03 69.18 66.34 61.74 64.91 65.31 61.01
+41.83 39.53 38.54 38.49 41.52 45.75 38.94 41.41 36.54 40.04 44.33 40.68 38.88 35.02 46.14 47.86 44.27 41.93 47.06 43.02 44.35 42.88 37.84 42.76 41.79 40.82 41.98 42.75 42.14 39.65 39.96 43.81 44.09 45.71 40.04 39.82 43.66 40.04 36.41 41.96 41.01 35.38 36.89 42.42 44.6 39.08 43.29 38.36 43.78 40.8 64.97 61.03 65.02 51.87 60.99 56.02 61.56 45.92 61.58 50.91 46.25 56.69 54.08 59.3 52.38 61.97 56.15 54.55 57.53 52.33 59.69 56.37 60.28 58.86 59.37 61.18 63.28 64.25 58.11 51.54 51.17 50.7 54.01 59.95 55.05 59.41 63.18 57.75 54.47 52.35 53.97 59.17 54.14 46.23 54.12 55.29 55.15 58.27 46.25 54.54 67.27 59.15 70.18 64.13 66.61 75.52 51.3 72.22 66.11 73.31 64.3 63.19 67.05 57.85 59.89 64.79 65.1 78.46 76.42 58.28 69.02 57.65 75.86 61.06 67.96 71.28 60.38 60.68 64.74 69.86 71.89 78.25 64.84 61.74 61.91 74.42 65.93 64.79 59.76 67.47 67.41 66.56 59.15 69.21 68.36 65.59 61.05 64.19 64.54 60.32
+44.73 42.22 41.21 41.14 44.42 48.96 41.68 44.27 39.05 42.76 47.4 43.5 41.52 37.45 49.36 51.27 47.4 44.85 50.31 46.05 47.37 45.9 40.54 45.72 44.67 43.58 44.9 45.7 45.04 42.38 42.69 46.83 47.2 48.94 42.76 42.56 46.66 42.76 38.93 44.85 43.88 37.74 39.47 45.41 47.73 41.76 46.32 41.02 46.82 43.61 69.25 65.11 69.3 55.27 65 59.73 65.7 48.97 65.61 54.34 49.25 60.5 57.54 63.22 55.91 66.07 59.93 58.12 61.25 55.76 63.74 60.1 64.2 62.71 63.28 65.22 67.4 68.5 61.98 54.93 54.52 54.01 57.58 63.9 58.77 63.45 67.36 61.47 58.13 55.81 57.52 63.1 57.7 49.28 57.71 58.98 58.83 62.12 49.35 58.17 71.85 63.1 74.81 68.37 71.06 80.44 54.76 76.9 70.39 78.27 68.63 67.36 71.51 61.71 63.97 69.19 69.41 83.69 81.35 62.04 73.65 61.55 80.75 65.1 72.52 75.96 64.4 64.75 69.04 74.4 76.55 83.44 69.16 65.79 65.92 79.31 70.44 69.1 63.78 71.97 71.95 71.04 63.1 73.85 73 70 65.07 68.48 68.96 64.37
+47.51 44.96 43.79 43.94 47.18 52.24 44.4 47.15 41.69 45.58 50.36 46.46 44.2 39.67 52.04 54.37 50.2 47.67 53.61 48.99 50.57 48.88 42.74 48.98 47.99 46.58 47.98 48.62 47.84 45.32 45.65 49.87 50.08 51.8 45.58 45.08 49.4 45.58 41.45 47.75 46.56 40.32 41.99 48.57 51.19 44.52 49.34 43.7 49.76 46.37 76.85 72.39 77.16 61.69 72.42 66.79 73.24 54.31 72.97 60.62 54.85 67.32 63.94 70.64 61.87 73.25 67.05 64.6 68.49 62 71.4 66.64 71.94 70.05 70.24 72.38 75.08 76.52 69.18 60.67 60.62 59.95 63.9 71.86 65.85 70.69 74.94 68.53 64.69 62.23 64.38 70.4 64.14 54.64 64.39 65.64 65.53 69.04 54.41 64.75 81.31 71.14 84.15 77.07 80.2 90.72 61.82 86.64 79.41 88.03 76.85 75.76 80.31 69.65 72.21 77.75 78.03 94.15 92.09 69.84 82.79 69.35 91.13 72.96 81.54 85.32 72.12 72.57 77.88 83.44 86.11 93.5 78.04 73.77 74.42 89.09 79.38 77.7 71.46 80.67 80.97 79.46 71.14 83.21 82.18 78.5 73.09 76.82 77.6 72.39
+44.64 42.06 41.13 41.19 44.4 49.11 41.79 44.25 39.06 42.66 47.31 43.62 41.37 37.32 49.02 51.33 47.31 44.79 50.28 46.14 47.31 45.99 40.44 45.84 44.97 43.5 45 45.63 44.88 42.48 42.72 46.71 47.28 48.9 42.66 42.3 46.35 42.66 38.91 44.79 43.8 37.5 39.51 45.6 48.09 41.67 46.44 41.04 46.77 43.5 70.65 66.66 70.86 56.55 66.45 61.38 67.47 50.01 66.99 55.83 50.25 62.01 58.5 64.89 57.09 67.38 61.74 59.37 62.58 56.94 65.76 61.29 65.82 64.29 64.56 66.54 68.82 70.23 63.6 55.83 55.65 55.05 58.77 65.85 60.66 65.25 68.88 62.67 59.64 57.15 59.1 64.74 58.92 50.25 59.19 60.48 60.33 63.48 50.19 59.58 74.67 65.22 77.04 70.62 73.5 82.89 56.76 79.17 72.48 80.88 70.65 69.36 73.62 63.78 66.27 71.46 71.55 86.43 83.88 63.75 75.96 63.69 83.13 66.87 74.88 78.18 66.18 66.69 71.31 76.38 78.66 85.86 71.46 67.62 68.04 81.48 73.02 71.31 65.7 74.01 74.28 72.96 65.22 76.32 75.48 72.03 66.87 70.5 71.43 66.51
+49.12 46.22 45.25 45.21 48.86 53.95 45.94 48.63 42.88 46.85 52.05 47.88 45.46 41.11 54.12 56.55 52.15 49.28 55.25 50.75 51.93 50.57 44.66 50.27 49.23 47.72 49.4 50.17 49.38 46.6 46.86 51.35 52.05 53.9 46.85 46.6 51.07 46.85 42.77 49.23 48.23 41.15 43.45 50.06 52.71 45.78 51.04 45.1 51.45 47.84 76.27 71.93 76.39 60.9 71.62 66.05 72.73 54.01 72.24 60.17 54.15 66.9 63.08 69.85 61.74 72.78 66.45 64.03 67.33 61.41 70.76 66.2 70.75 69.19 69.69 71.84 74.16 75.62 68.51 60.39 60.02 59.41 63.45 70.69 65.25 70.37 74.29 67.5 64.33 61.58 63.56 69.74 63.56 54.27 63.76 65.22 65.04 68.49 54.36 64.25 80.02 69.97 82.71 75.74 78.82 88.86 60.87 84.89 77.68 86.89 76.03 74.47 79.11 68.4 71.11 76.81 76.83 92.79 89.77 68.38 81.61 68.37 89.07 71.91 80.43 83.96 71.2 71.73 76.48 82.06 84.41 92.32 76.64 72.67 72.88 87.53 78.4 76.57 70.68 79.6 79.78 78.57 69.97 81.91 81.07 77.48 71.84 75.8 76.74 71.43
+51.9 49.01 47.82 47.78 51.54 56.86 48.39 51.39 45.35 49.64 55 50.52 48.19 43.43 57.2 59.48 54.98 52.05 58.41 53.45 55.01 53.29 46.98 53.14 51.93 50.62 52.16 53.04 52.26 49.23 49.59 54.37 54.75 56.75 49.64 49.36 54.11 49.64 45.19 52.06 50.91 43.84 45.81 52.77 55.48 48.49 53.77 47.62 54.33 50.61 81.01 76.2 81.12 64.73 76.1 69.97 76.93 57.28 76.78 63.64 57.65 70.82 67.32 74.05 65.38 77.28 70.22 68 71.76 65.25 74.71 70.3 75.24 73.44 74.03 76.3 78.9 80.24 72.59 64.2 63.8 63.18 67.36 74.94 68.88 74.29 78.84 71.97 68.04 65.35 67.39 73.89 67.52 57.64 67.58 69.03 68.87 72.69 57.67 68.09 84.39 74.05 87.75 80.22 83.41 94.39 64.28 90.21 82.6 91.82 80.44 79.01 83.86 72.44 75.11 81.16 81.4 98.16 95.53 72.77 86.39 72.23 94.76 76.31 85.06 89.06 75.48 75.9 81.03 87.2 89.78 97.79 81.18 77.11 77.35 93.01 82.67 81.04 74.76 84.37 84.42 83.26 74.05 86.66 85.66 82.08 76.31 80.29 80.91 75.5
+46.6 44.19 42.95 42.97 46.2 50.99 43.35 46.18 40.81 44.73 49.39 45.36 43.43 38.96 51.28 53.15 49.23 46.73 52.52 47.86 49.58 47.76 41.92 47.85 46.68 45.7 46.88 47.67 47 44.27 44.67 48.96 48.92 50.73 44.73 44.4 48.68 44.73 40.6 46.81 45.66 39.75 41.06 47.37 49.75 43.69 48.17 42.76 48.76 45.51 73.96 69.43 74.11 59.23 69.58 63.84 70.04 52.21 70.18 57.95 52.8 64.5 61.76 67.6 59.48 70.52 63.93 62.09 65.87 59.62 67.99 64.16 68.95 67.11 67.6 69.66 72.22 73.32 66.22 58.59 58.32 57.75 61.47 68.53 62.67 67.5 71.97 66.03 61.91 59.69 61.55 67.39 61.68 52.61 61.66 62.85 62.75 66.34 52.51 62.08 76.93 67.66 80.32 73.34 76.23 86.55 58.63 82.72 75.82 83.73 73.35 72.32 76.67 66.26 68.54 73.99 74.39 89.59 87.84 66.81 78.9 65.88 87.03 69.8 77.61 81.46 68.96 69.23 74.13 79.86 82.37 89.27 74.26 70.52 70.87 85.24 75.27 73.99 68.16 77.09 77.1 76.03 67.66 79.15 78.13 74.98 69.91 73.33 73.63 68.85
+45.06 42.44 41.51 41.47 44.8 49.43 42.09 44.61 39.34 43.02 47.75 43.9 41.75 37.72 49.66 51.79 47.79 45.19 50.68 46.5 47.67 46.33 40.92 46.08 45.13 43.82 45.28 46.03 45.32 42.74 43 47.11 47.7 49.4 43.02 42.78 46.89 43.02 39.23 45.17 44.22 37.82 39.83 45.84 48.27 42.01 46.78 41.36 47.19 43.9 69.89 65.84 69.98 55.79 65.61 60.46 66.53 49.47 66.21 55.03 49.65 61.21 57.9 63.95 56.51 66.68 60.76 58.69 61.72 56.28 64.66 60.65 64.82 63.39 63.86 65.82 67.98 69.23 62.7 55.37 55.01 54.47 58.13 64.69 59.64 64.33 68.04 61.91 58.86 56.39 58.2 63.84 58.24 49.73 58.37 59.7 59.53 62.74 49.79 58.82 73.03 63.96 75.68 69.28 72.04 81.35 55.6 77.75 71.14 79.38 69.51 68.14 72.36 62.52 64.91 70.16 70.29 84.85 82.2 62.65 74.6 62.45 81.59 65.81 73.52 76.86 65.14 65.59 69.93 75.18 77.32 84.48 70.06 66.54 66.74 80.12 71.56 70.03 64.62 72.81 72.9 71.84 63.96 74.86 74.04 70.83 65.75 69.32 70.05 65.29
+42.66 40.31 39.31 39.31 42.36 46.77 39.79 42.26 37.31 40.83 45.21 41.56 39.63 35.68 46.96 48.87 45.17 42.79 48.04 43.94 45.26 43.82 38.56 43.75 42.76 41.66 42.92 43.61 42.96 40.51 40.81 44.72 44.98 46.61 40.83 40.56 44.46 40.83 37.16 42.81 41.84 36.09 37.66 43.43 45.67 39.89 44.21 39.16 44.66 41.61 67.12 63.15 67.25 53.69 63.1 58.04 63.78 47.45 63.64 52.77 47.8 58.7 55.8 61.42 54.14 64.02 58.25 56.35 59.55 54.08 61.99 58.24 62.45 60.91 61.34 63.22 65.42 66.56 60.2 53.15 52.88 52.35 55.81 62.23 57.15 61.58 65.35 59.69 56.39 54.19 55.91 61.27 55.96 47.75 56.04 57.21 57.09 60.24 47.73 56.44 70.15 61.52 72.88 66.64 69.31 78.43 53.41 74.94 68.64 76.25 66.75 65.62 69.63 60.2 62.42 67.39 67.59 81.51 79.44 60.45 71.74 60 78.75 63.34 70.63 73.94 62.64 62.99 67.33 72.38 74.57 81.15 67.46 64 64.27 77.24 68.67 67.29 62.04 70.03 70.12 69.09 61.52 71.99 71.15 68.14 63.37 66.65 67.19 62.69
+43.55 41.15 40.13 40.2 43.26 47.8 40.66 43.18 38.14 41.73 46.16 42.52 40.48 36.41 47.82 49.87 46.04 43.67 49.07 44.89 46.26 44.75 39.3 44.71 43.84 42.58 43.86 44.54 43.84 41.45 41.74 45.62 45.98 47.57 41.73 41.34 45.31 41.73 37.96 43.73 42.68 36.81 38.48 44.36 46.77 40.72 45.21 40.02 45.61 42.48 69.18 65.12 69.37 55.39 65.07 59.99 65.83 48.91 65.64 54.46 49.3 60.53 57.52 63.45 55.74 65.95 60.2 58.16 61.42 55.78 64.05 59.99 64.51 62.95 63.22 65.14 67.48 68.69 62.14 54.71 54.53 53.97 57.52 64.38 59.1 63.56 67.39 61.55 58.2 55.91 57.81 63.27 57.7 49.2 57.86 59.07 58.93 62.12 49.07 58.23 72.63 63.64 75.33 68.99 71.71 81.16 55.29 77.57 71.03 78.8 68.91 67.82 71.92 62.25 64.5 69.6 69.91 84.35 82.23 62.52 74.11 62.04 81.51 65.39 73.03 76.48 64.66 65.07 69.64 74.84 77.1 83.89 69.76 66.17 66.63 79.75 71.01 69.62 64.08 72.29 72.43 71.21 63.64 74.44 73.51 70.29 65.43 68.83 69.46 64.85
+48.33 45.61 44.53 44.54 48.02 53.02 45.12 47.88 42.26 46.23 51.22 47.12 44.86 40.43 53.18 55.43 51.18 48.47 54.41 49.83 51.24 49.67 43.74 49.53 48.5 47.14 48.62 49.4 48.64 45.91 46.22 50.6 51.06 52.87 46.23 45.9 50.31 46.23 42.1 48.49 47.4 40.75 42.7 49.2 51.81 45.14 50.15 44.38 50.61 47.12 75.88 71.44 76.03 60.67 71.31 65.69 72.19 53.67 71.94 59.72 54 66.41 63 69.49 61.24 72.37 65.96 63.74 67.22 61.14 70.19 65.83 70.57 68.91 69.34 71.46 73.92 75.25 68.1 60.07 59.77 59.17 63.1 70.4 64.74 69.74 73.89 67.39 63.84 61.27 63.27 69.33 63.26 53.98 63.4 64.77 64.61 68.12 53.95 63.85 79.43 69.6 82.39 75.41 78.41 88.66 60.47 84.73 77.57 86.28 75.51 74.18 78.72 68.07 70.6 76.24 76.47 92.27 89.73 68.3 81.13 67.9 88.99 71.59 79.93 83.64 70.82 71.27 76.14 81.84 84.26 91.83 76.28 72.39 72.73 87.25 77.75 76.16 70.2 79.17 79.29 78.07 69.6 81.44 80.49 77.01 71.59 75.37 76.08 70.97
+44.52 42.06 41.02 40.98 44.2 48.74 41.48 44.08 38.9 42.6 47.18 43.32 41.36 37.26 49.08 50.98 47.14 44.64 50.1 45.82 47.2 45.68 40.28 45.56 44.52 43.44 44.72 45.5 44.84 42.22 42.54 46.64 46.94 48.66 42.6 42.36 46.44 42.6 38.76 44.66 43.66 37.64 39.28 45.22 47.54 41.6 46.1 40.84 46.6 43.42 69.4 65.24 69.48 55.44 65.18 59.9 65.84 49.06 65.78 54.46 49.4 60.62 57.72 63.4 55.98 66.2 60.08 58.26 61.48 55.9 63.9 60.22 64.44 62.9 63.42 65.36 67.6 68.7 62.14 55.02 54.66 54.14 57.7 64.14 58.92 63.56 67.52 61.68 58.24 55.96 57.7 63.26 57.84 49.38 57.86 59.1 58.96 62.26 49.4 58.3 72.12 63.34 75.1 68.64 71.34 80.8 54.96 77.24 70.72 78.52 68.82 67.62 71.76 61.96 64.2 69.4 69.66 83.98 81.78 62.32 73.9 61.76 81.14 65.32 72.76 76.24 64.6 64.94 69.32 74.68 76.88 83.7 69.44 66.02 66.22 79.62 70.66 69.34 63.96 72.2 72.2 71.24 63.34 74.12 73.24 70.22 65.32 68.7 69.16 64.58
+38.37 36.22 35.35 35.28 38.1 41.98 35.74 37.97 33.49 36.68 40.66 37.3 35.62 32.13 42.36 43.97 40.66 38.47 43.15 39.49 40.63 39.36 34.78 39.2 38.29 37.38 38.5 39.2 38.64 36.34 36.61 40.17 40.48 41.98 36.68 36.52 40.04 36.68 33.39 38.47 37.64 32.38 33.85 38.93 40.91 35.82 39.72 35.18 40.16 37.41 59.27 55.71 59.3 47.29 55.62 51.09 56.2 41.91 56.15 46.48 42.15 51.76 49.26 54.08 47.85 56.55 51.25 49.74 52.41 47.72 54.5 51.44 54.92 53.65 54.16 55.82 57.68 58.6 53.02 47.03 46.66 46.23 49.28 54.64 50.25 54.27 57.64 52.61 49.73 47.75 49.2 53.98 49.38 42.18 49.37 50.46 50.33 53.16 42.25 49.77 61.39 53.94 63.97 58.45 60.74 68.78 46.8 65.76 60.19 66.91 58.69 57.6 61.15 52.75 54.67 59.15 59.35 71.55 69.55 53.06 62.97 52.61 69.05 55.68 62 64.96 55.08 55.37 59.02 63.64 65.47 71.36 59.12 56.27 56.36 67.83 60.2 59.08 54.54 61.55 61.51 60.76 53.94 63.13 62.4 59.86 55.65 58.56 58.94 55.03
+44.15 41.68 40.68 40.69 43.86 48.43 41.21 43.74 38.61 42.24 46.79 43.04 40.99 36.93 48.58 50.62 46.75 44.28 49.71 45.51 46.82 45.37 39.94 45.26 44.3 43.08 44.42 45.13 44.44 41.94 42.23 46.24 46.62 48.28 42.24 41.94 45.97 42.24 38.46 44.3 43.3 37.26 39 44.95 47.32 41.25 45.8 40.54 46.23 43.05 69.4 65.33 69.54 55.5 65.23 60.07 66.01 49.08 65.8 54.61 49.4 60.73 57.64 63.55 56 66.19 60.31 58.29 61.51 55.92 64.18 60.21 64.56 63.02 63.42 65.36 67.62 68.83 62.28 54.94 54.67 54.12 57.71 64.39 59.19 63.76 67.58 61.66 58.37 56.04 57.86 63.4 57.86 49.37 57.98 59.22 59.08 62.3 49.34 58.39 72.64 63.66 75.37 68.97 71.72 81.11 55.3 77.51 70.97 78.91 69.06 67.86 72.01 62.27 64.58 69.73 69.94 84.38 82.11 62.49 74.21 62.1 81.42 65.49 73.1 76.5 64.78 65.18 69.65 74.86 77.09 83.98 69.78 66.21 66.52 79.83 71.1 69.65 64.2 72.42 72.53 71.42 63.66 74.49 73.62 70.45 65.5 68.94 69.57 64.9
+45.69 43.05 42.09 42.06 45.42 50.1 42.66 45.24 39.9 43.65 48.42 44.52 42.36 38.25 50.34 52.47 48.42 45.81 51.39 47.13 48.36 46.95 41.46 46.71 45.78 44.46 45.9 46.68 45.96 43.35 43.62 47.76 48.36 50.07 43.65 43.38 47.55 43.65 39.78 45.81 44.82 38.37 40.38 46.44 48.93 42.6 47.43 41.94 47.85 44.52 70.92 66.78 71.01 56.61 66.57 61.35 67.47 50.19 67.2 55.8 50.4 62.07 58.8 64.89 57.3 67.65 61.62 59.58 62.64 57.12 65.55 61.53 65.79 64.35 64.8 66.78 69 70.23 63.6 56.19 55.83 55.29 58.98 65.64 60.48 65.22 69.03 62.85 59.7 57.21 59.07 64.77 59.1 50.46 59.22 60.57 60.39 63.66 50.49 59.67 74.01 64.86 76.77 70.29 73.05 82.56 56.37 78.93 72.21 80.46 70.47 69.12 73.38 63.39 65.76 71.1 71.31 86.07 83.43 63.6 75.63 63.3 82.83 66.75 74.55 78 66.06 66.51 70.92 76.32 78.48 85.71 71.04 67.53 67.77 81.27 72.51 71.04 65.52 73.83 73.89 72.81 64.86 75.9 75.03 71.79 66.69 70.29 70.98 66.21
+45.36 42.77 41.79 41.77 45.08 49.75 42.35 44.92 39.63 43.35 48.07 44.2 42.07 37.96 49.96 52.07 48.07 45.49 51.04 46.78 48.04 46.62 41.12 46.43 45.46 44.18 45.6 46.35 45.64 43.05 43.33 47.46 47.96 49.67 43.35 43.08 47.22 43.35 39.5 45.49 44.5 38.17 40.08 46.15 48.59 42.33 47.07 41.64 47.5 44.21 70.74 66.61 70.85 56.51 66.44 61.2 67.3 50.05 67.04 55.67 50.3 61.92 58.68 64.74 57.14 67.48 61.47 59.41 62.57 56.98 65.41 61.38 65.69 64.19 64.64 66.62 68.86 70.1 63.46 56.03 55.7 55.15 58.83 65.53 60.33 65.04 68.87 62.75 59.53 57.09 58.93 64.61 58.96 50.33 59.08 60.39 60.23 63.5 50.35 59.52 73.93 64.78 76.68 70.18 72.97 82.47 56.29 78.82 72.14 80.35 70.35 69.04 73.29 63.34 65.72 71.01 71.19 85.91 83.4 63.53 75.54 63.22 82.75 66.66 74.43 77.86 65.96 66.39 70.85 76.18 78.39 85.53 70.98 67.4 67.65 81.2 72.41 70.91 65.4 73.73 73.82 72.73 64.78 75.81 74.95 71.72 66.63 70.19 70.87 66.09
+48.05 45.36 44.27 44.22 47.72 52.62 44.79 47.57 41.97 45.95 50.92 46.76 44.61 40.22 52.98 55.07 50.9 48.18 54.06 49.48 50.91 49.32 43.52 49.15 48.05 46.84 48.26 49.1 48.38 45.56 45.89 50.31 50.71 52.56 45.95 45.7 50.1 45.95 41.83 48.19 47.13 40.55 42.41 48.81 51.33 44.87 49.78 44.08 50.3 46.85 74.71 70.26 74.79 59.66 70.15 64.5 70.92 52.83 70.8 58.66 53.15 65.29 62.08 68.26 60.3 71.27 64.72 62.72 66.12 60.17 68.84 64.83 69.33 67.71 68.27 70.36 72.74 73.95 66.91 59.23 58.83 58.27 62.12 69.04 63.48 68.49 72.69 66.34 62.74 60.24 62.12 68.12 62.26 53.16 62.3 63.66 63.5 67.03 53.2 62.78 77.68 68.19 80.82 73.89 76.8 86.93 59.19 83.1 76.07 84.56 74.11 72.77 77.24 66.69 69.13 74.74 74.99 90.43 87.94 67.03 79.56 66.51 87.27 70.3 78.35 82.06 69.54 69.93 74.61 80.36 82.7 90.12 74.74 71.06 71.26 85.66 76.12 74.66 68.88 77.72 77.73 76.69 68.19 79.8 78.87 75.59 70.28 73.96 74.51 69.55
+39.18 36.91 36.09 35.93 38.92 42.83 36.49 38.72 34.11 37.35 41.51 38 36.29 32.84 43.4 45.01 41.63 39.29 44 40.34 41.36 40.2 35.68 39.91 38.9 38.02 39.24 39.99 39.44 36.99 37.25 40.98 41.38 42.97 37.35 37.32 40.92 37.35 34.06 39.23 38.48 32.93 34.56 39.71 41.65 36.51 40.53 35.88 41 38.17 59.34 55.79 59.29 47.23 55.6 51 56.24 41.99 56.14 46.51 42.1 51.84 49.2 54 48.04 56.66 51.21 49.73 52.27 47.72 54.47 51.54 54.73 53.53 54.22 55.9 57.62 58.54 53 47.17 46.66 46.25 49.35 54.41 50.19 54.36 57.67 52.51 49.79 47.73 49.07 53.95 49.4 42.25 49.34 50.49 50.35 53.2 42.47 49.8 61.13 53.72 63.72 58.16 60.47 68.37 46.61 65.36 59.8 66.77 58.65 57.38 60.99 52.52 54.52 59.07 59.13 71.29 69 52.75 62.82 52.46 68.57 55.56 61.83 64.7 55 55.29 58.75 63.38 65.13 71.19 58.86 56.08 55.95 67.6 60.07 58.87 54.48 61.45 61.36 60.77 53.72 62.91 62.27 59.8 55.47 58.45 58.85 54.87
+44.87 42.33 41.34 41.31 44.58 49.19 41.87 44.43 39.2 42.89 47.55 43.7 41.63 37.55 49.44 51.48 47.55 45 50.49 46.25 47.53 46.1 40.66 45.93 44.93 43.72 45.1 45.85 45.16 42.57 42.86 46.97 47.4 49.11 42.89 42.64 46.74 42.89 39.07 45 44.02 37.81 39.63 45.64 48.02 41.89 46.53 41.18 46.98 43.74 69.95 65.84 70.05 55.88 65.7 60.47 66.5 49.48 66.29 55.01 49.75 61.2 58.06 63.98 56.49 66.73 60.72 58.73 61.9 56.34 64.61 60.7 64.95 63.44 63.92 65.88 68.1 69.3 62.72 55.42 55.08 54.54 58.17 64.75 59.58 64.25 68.09 62.08 58.82 56.44 58.23 63.85 58.3 49.77 58.39 59.67 59.52 62.78 49.8 58.83 73 64 75.79 69.33 72.09 81.51 55.59 77.9 71.31 79.38 69.52 68.24 72.44 62.59 64.93 70.16 70.34 84.86 82.45 62.81 74.65 62.45 81.8 65.9 73.53 76.94 65.2 65.6 70.01 75.3 77.5 84.51 70.14 66.61 66.83 80.29 71.51 70.05 64.62 72.88 72.95 71.91 64 74.9 74.05 70.9 65.88 69.37 69.99 65.28
+52.58 49.67 48.47 48.71 52.28 58.09 49.35 52.22 46.19 50.35 55.73 51.56 48.79 43.84 57.44 60.43 55.69 52.83 59.4 54.42 55.94 54.34 47.36 54.47 53.36 51.5 53.32 53.81 52.88 50.27 50.57 55.24 55.54 57.41 50.35 49.76 54.5 50.35 45.92 52.85 51.6 44.49 46.58 54.15 57.07 49.29 54.77 48.44 55.1 51.29 86.36 81.63 86.85 69.49 81.54 75.4 82.78 61.09 82 68.57 61.6 76.02 71.56 79.7 69.7 82.34 75.93 72.55 77.07 69.68 81.03 74.92 81.09 78.87 78.94 81.38 84.38 86.36 78.12 67.99 68.12 67.27 71.85 81.31 74.67 80.02 84.39 76.93 73.03 70.15 72.63 79.43 72.12 61.39 72.64 74.01 73.93 77.68 61.13 73 92.83 80.8 95.28 87.36 91.15 102.63 70.37 97.86 89.76 100.09 87.11 85.78 90.99 79.16 82.38 88.43 88.35 106.75 104.24 78.81 93.98 78.92 102.95 82.5 92.55 96.42 81.6 82.23 88.41 94.06 97.21 105.71 88.66 83.28 84.11 100.76 90.51 88.05 81 91.35 92.04 90.05 80.8 94.55 93.55 89.06 82.69 87.05 88.43 82.17
+46.55 44.04 42.91 43.08 46.24 51.28 43.57 46.21 40.87 44.63 49.34 45.56 43.27 38.84 50.94 53.35 49.24 46.74 52.56 48.06 49.55 47.98 41.88 48.11 47.09 45.64 47.1 47.64 46.86 44.44 44.75 48.91 49.07 50.76 44.63 44.14 48.36 44.63 40.63 46.79 45.65 39.51 41.17 47.75 50.29 43.65 48.38 42.84 48.76 45.43 75.87 71.56 76.23 60.98 71.57 66.04 72.46 53.63 72.04 60 54.15 66.59 63.04 69.84 61.14 72.33 66.38 63.74 67.7 61.21 70.76 65.81 71.13 69.19 69.35 71.48 74.14 75.69 68.43 59.83 59.85 59.15 63.1 71.14 65.22 69.97 74.05 67.66 63.96 61.52 63.64 69.6 63.34 53.94 63.66 64.86 64.78 68.19 53.72 64 80.8 70.55 83.36 76.35 79.56 89.83 61.35 85.72 78.61 87.34 76.15 75.05 79.58 69.11 71.77 77.16 77.27 93.27 91.24 69.09 82.1 68.83 90.19 72.24 80.83 84.42 71.42 71.89 77.23 82.48 85.2 92.52 77.42 72.96 73.62 88.24 78.84 76.96 70.8 79.92 80.35 78.77 70.55 82.54 81.59 77.85 72.4 76.12 77.05 71.75
+55.39 52.47 51.06 51.23 54.98 60.91 51.75 54.97 48.62 53.15 58.71 54.14 51.55 46.23 60.68 63.36 58.55 55.6 62.53 57.09 58.99 57 49.78 57.19 55.91 54.36 55.98 56.69 55.8 52.83 53.24 58.23 58.28 60.33 53.15 52.6 57.64 53.15 48.33 55.68 54.3 47.15 48.93 56.7 59.66 51.97 57.47 50.94 58 54.08 89.97 84.74 90.35 72.28 84.84 78.15 85.72 63.56 85.43 70.97 64.25 78.82 74.9 82.68 72.43 85.77 78.46 75.57 80.32 72.58 83.59 78.04 84.29 81.96 82.24 84.76 87.94 89.64 81 71.02 70.98 70.18 74.81 84.15 77.04 82.71 87.75 80.32 75.68 72.88 75.33 82.39 75.1 63.97 75.37 76.77 76.68 80.82 63.72 75.79 95.28 83.36 98.63 90.25 93.99 106.31 72.41 101.48 93.07 103.16 90.04 88.8 94.14 81.67 84.71 91.14 91.38 110.22 108.01 81.85 97.05 81.27 106.8 85.52 95.51 99.9 84.52 85 91.29 97.7 100.92 109.45 91.5 86.37 87.09 104.49 93.01 90.97 83.7 94.56 94.95 93.21 83.36 97.52 96.35 92.08 85.72 90.03 90.91 84.76
+50.48 47.77 46.53 46.73 50.14 55.57 47.22 50.12 44.33 48.44 53.51 49.42 46.96 42.13 55.22 57.79 53.33 50.66 56.99 52.09 53.76 51.98 45.38 52.12 51.1 49.52 51.04 51.67 50.82 48.21 48.55 53 53.23 55.03 48.44 47.86 52.44 48.44 44.06 50.75 49.47 42.84 44.64 51.69 54.51 47.32 52.47 46.46 52.88 49.27 82.22 77.5 82.6 66.06 77.53 71.57 78.46 58.11 78.09 64.95 58.7 72.09 68.38 75.68 66.19 78.36 71.88 69.13 73.34 66.35 76.57 71.29 77.08 75.03 75.15 77.44 80.36 81.97 74.11 64.85 64.87 64.13 68.37 77.07 70.62 75.74 80.22 73.34 69.28 66.64 68.99 75.41 68.64 58.45 68.97 70.29 70.18 73.89 58.16 69.33 87.36 76.35 90.25 82.7 86.09 97.32 66.38 92.92 85.18 94.46 82.39 81.25 86.12 74.76 77.54 83.42 83.69 101.01 98.83 74.88 88.81 74.44 97.75 78.2 87.48 91.48 77.3 77.81 83.58 89.42 92.32 100.23 83.76 79.07 79.85 95.51 85.23 83.35 76.62 86.48 86.88 85.16 76.35 89.3 88.2 84.17 78.36 82.37 83.3 77.67
+52.21 49.41 48.13 48.34 51.86 57.54 48.88 51.84 45.86 50.07 55.34 51.12 48.54 43.55 57.1 59.83 55.22 52.43 58.97 53.91 55.6 53.83 46.94 54.01 52.86 51.22 52.86 53.44 52.56 49.87 50.22 54.88 55.02 56.91 50.07 49.5 54.23 50.07 45.58 52.49 51.2 44.35 46.18 53.6 56.45 48.98 54.27 48.06 54.69 50.96 85.44 80.6 85.87 68.71 80.63 74.41 81.63 60.39 81.14 67.6 61 75.01 71 78.69 68.84 81.45 74.8 71.78 76.3 68.94 79.75 74.11 80.17 77.95 78.1 80.5 83.52 85.29 77.1 67.35 67.41 66.61 71.06 80.2 73.5 78.82 83.41 76.23 72.04 69.31 71.71 78.41 71.34 60.74 71.72 73.05 72.97 76.8 60.47 72.09 91.15 79.56 93.99 86.09 89.73 101.3 69.19 96.65 88.65 98.48 85.83 84.62 89.72 77.95 80.96 87 87.11 105.15 102.93 77.9 92.57 77.62 101.71 81.43 91.13 95.16 80.5 81.03 87.1 92.96 96.06 104.27 87.32 82.23 83.01 99.49 88.91 86.76 79.8 90.09 90.61 88.79 79.56 93.08 92.01 87.77 81.63 85.81 86.88 80.89
+58.92 55.9 54.32 54.58 58.46 64.8 55.03 58.52 51.8 56.65 62.46 57.66 54.93 49.15 64.42 67.26 62.16 59.13 66.57 60.69 62.88 60.6 52.78 60.93 59.64 57.98 59.6 60.34 59.38 56.3 56.76 61.98 61.93 64.06 56.65 55.94 61.3 56.65 51.44 59.28 57.71 50.31 52.04 60.32 63.54 55.35 61.14 54.22 61.7 57.56 96.76 91.09 97.23 77.83 91.31 84.15 92.16 68.32 91.95 76.3 69.2 84.71 80.7 89.02 77.75 92.2 84.41 81.34 86.57 78.11 89.9 83.89 90.87 88.3 88.45 91.14 94.7 96.49 87.15 76.32 76.39 75.52 80.44 90.72 82.89 88.86 94.39 86.55 81.35 78.43 81.16 88.66 80.8 68.78 81.11 82.56 82.47 86.93 68.37 81.51 102.63 89.83 106.31 97.32 101.3 114.73 78.01 109.54 100.48 111.03 96.86 95.71 101.39 88.02 91.18 98.05 98.48 118.76 116.69 88.35 104.49 87.5 115.34 92.1 102.85 107.7 90.98 91.48 98.41 105.36 108.89 117.88 98.62 93.09 94.06 112.61 100.08 98.02 90.06 101.79 102.22 100.23 89.83 105.05 103.69 99.07 92.37 96.92 97.79 91.28
+40.38 38.15 37.22 37.38 40.14 44.54 37.85 40.09 35.45 38.68 42.8 39.56 37.49 33.69 44.16 46.36 42.74 40.55 45.59 41.75 42.95 41.67 36.38 41.74 40.91 39.54 40.88 41.32 40.62 38.57 38.81 42.39 42.65 44.09 38.68 38.24 41.89 38.68 35.25 40.58 39.61 34.16 35.75 41.47 43.72 37.83 42.03 37.18 42.31 39.39 65.83 62.16 66.16 52.91 62.1 57.39 62.99 46.56 62.5 52.16 46.95 57.86 54.6 60.67 53.1 62.76 57.74 55.32 58.68 53.11 61.57 57.1 61.72 60.08 60.17 62.02 64.3 65.72 59.45 51.88 51.92 51.3 54.76 61.82 56.76 60.87 64.28 58.63 55.6 53.41 55.29 60.47 54.96 46.8 55.3 56.37 56.29 59.19 46.61 55.59 70.37 61.35 72.41 66.38 69.19 78.01 53.4 74.43 68.24 75.98 66.2 65.19 69.14 60.08 62.45 67.12 67.16 81.12 79.19 59.95 71.37 59.89 78.28 62.73 70.3 73.34 62.04 62.5 67.13 71.6 73.94 80.41 67.3 63.37 63.97 76.59 68.65 66.92 61.56 69.43 69.86 68.42 61.35 71.78 70.98 67.64 62.85 66.15 67.09 62.42
+56.56 53.65 52.14 52.38 56.12 62.16 52.8 56.17 49.71 54.39 59.96 55.34 52.74 47.2 61.86 64.54 59.64 56.74 63.88 58.24 60.35 58.13 50.68 58.41 57.23 55.64 57.16 57.92 57 54.03 54.47 59.45 59.48 61.51 54.39 53.7 58.85 54.39 49.37 56.9 55.38 48.25 49.95 57.81 60.94 53.1 58.69 52.04 59.23 55.25 92.51 87.05 92.93 74.36 87.25 80.42 88.05 65.32 87.91 72.88 66.15 80.93 77.18 85.07 74.31 88.14 80.63 77.8 82.69 74.68 85.83 80.19 86.81 84.42 84.56 87.12 90.52 92.17 83.26 73 73.03 72.22 76.9 86.64 79.17 84.89 90.21 82.72 77.75 74.94 77.57 84.73 77.24 65.76 77.51 78.93 78.82 83.1 65.36 77.9 97.86 85.72 101.48 92.92 96.65 109.54 74.43 104.63 95.94 105.93 92.46 91.36 96.77 83.96 86.91 93.53 94.04 113.4 111.36 84.38 99.7 83.47 110.14 87.93 98.17 102.88 86.86 87.34 93.9 100.68 103.99 112.61 94.08 88.94 89.87 107.48 95.45 93.6 85.98 97.16 97.5 95.63 85.72 100.23 98.89 94.51 88.16 92.51 93.28 87.14
+51.4 48.81 47.39 47.63 50.98 56.51 47.98 51.06 45.21 49.46 54.49 50.3 47.96 42.87 56.18 58.61 54.19 51.58 58.09 52.91 54.9 52.84 45.98 53.18 52.04 50.64 52 52.65 51.82 49.13 49.55 54.1 53.97 55.83 49.46 48.82 53.5 49.46 44.88 51.73 50.33 43.98 45.38 52.61 55.41 48.32 53.31 47.3 53.82 50.23 84.68 79.68 85.1 68.14 79.93 73.63 80.6 59.77 80.49 66.73 60.6 74.09 70.7 77.9 67.99 80.68 73.82 71.19 75.84 68.37 78.61 73.41 79.58 77.29 77.41 79.76 82.92 84.45 76.25 66.79 66.87 66.11 70.39 79.41 72.48 77.68 82.6 75.82 71.14 68.64 71.03 77.57 70.72 60.19 70.97 72.21 72.14 76.07 59.8 71.31 89.76 78.61 93.07 85.18 88.65 100.48 68.24 95.94 88.02 97.12 84.73 83.79 88.74 77.04 79.76 85.76 86.19 103.91 102.25 77.4 91.43 76.54 101.05 80.62 89.98 94.28 79.62 80.03 86.14 92.26 95.38 103.15 86.32 81.49 82.37 98.61 87.51 85.77 78.78 89.08 89.44 87.7 78.61 91.92 90.7 86.69 80.88 84.81 85.5 79.85
+58.36 55.12 53.79 53.93 58 64.29 54.65 57.89 51.16 55.84 61.85 57.06 54.15 48.72 63.98 67.03 61.85 58.61 65.84 60.3 61.99 60.19 52.68 60.22 58.89 57.06 59 59.69 58.72 55.62 55.98 61.27 61.6 63.76 55.84 55.34 60.63 55.84 50.91 58.61 57.28 49.36 51.63 59.86 62.99 54.65 60.66 53.68 61.13 56.92 94.09 88.8 94.48 75.53 88.69 81.82 89.91 66.55 89.27 74.45 67.05 82.65 78.02 86.53 75.97 89.74 82.32 78.99 83.76 75.86 87.78 81.65 88 85.67 86 88.66 91.82 93.79 84.84 74.25 74.17 73.31 78.27 88.03 80.88 86.89 91.82 83.73 79.38 76.25 78.8 86.28 78.52 66.91 78.91 80.46 80.35 84.56 66.77 79.38 100.09 87.34 103.16 94.46 98.48 111.03 75.98 105.93 97.12 108.26 94.43 92.88 98.56 85.54 88.95 95.68 95.65 115.49 112.64 85.37 101.72 85.29 111.39 89.47 100.14 104.46 88.5 89.11 95.57 102.02 105.32 114.6 95.82 90.3 90.94 109.2 97.76 95.29 87.78 99.03 99.56 97.7 87.34 102.22 101.14 96.51 89.61 94.32 95.57 88.89
+51.89 48.99 47.82 47.87 51.56 57.05 48.52 51.43 45.42 49.62 54.99 50.64 48.14 43.36 57.02 59.58 55.01 52.09 58.48 53.56 55.05 53.44 46.92 53.38 52.17 50.66 52.34 53.05 52.22 49.35 49.68 54.43 54.77 56.73 49.62 49.26 53.98 49.62 45.23 52.08 50.93 43.84 45.87 53.06 55.8 48.54 53.87 47.68 54.34 50.6 82.51 77.79 82.76 66.11 77.67 71.56 78.68 58.36 78.24 65.13 58.75 72.37 68.44 75.7 66.64 78.71 71.95 69.25 73.29 66.49 76.67 71.61 76.94 74.98 75.41 77.74 80.44 82.05 74.23 65.22 65.01 64.3 68.63 76.85 70.65 76.03 80.44 73.35 69.51 66.75 68.91 75.51 68.82 58.69 69.06 70.47 70.35 74.11 58.65 69.52 87.11 76.15 90.04 82.39 85.83 96.86 66.2 92.46 84.73 94.43 82.5 81.07 86.05 74.55 77.47 83.47 83.5 100.78 98.16 74.54 88.76 74.35 97.18 78.18 87.38 91.24 77.34 77.84 83.32 89.18 91.97 100.15 83.52 78.92 79.33 95.36 85.19 83.17 76.68 86.51 86.83 85.38 76.15 89.13 88.18 84.27 78.25 82.37 83.32 77.56
+49.89 47.26 45.99 46.14 49.52 54.86 46.61 49.51 43.79 47.87 52.88 48.76 46.43 41.64 54.66 57.07 52.74 50.08 56.32 51.42 53.13 51.34 44.84 51.51 50.35 48.96 50.42 51.06 50.26 47.58 47.95 52.45 52.49 54.34 47.87 47.38 51.92 47.87 43.53 50.15 48.91 42.47 44.07 51.07 53.73 46.81 51.76 45.88 52.24 48.71 81.01 76.3 81.35 65.08 76.39 70.36 77.18 57.23 76.92 63.9 57.85 70.97 67.44 74.44 65.22 77.23 70.64 68.04 72.32 65.35 75.26 70.27 75.89 73.79 74.05 76.32 79.18 80.71 72.93 63.95 63.91 63.19 67.36 75.76 69.36 74.47 79.01 72.32 68.14 65.62 67.82 74.18 67.62 57.6 67.86 69.12 69.04 72.77 57.38 68.24 85.78 75.05 88.8 81.25 84.62 95.71 65.19 91.36 83.79 92.88 81.07 79.95 84.76 73.53 76.27 82.06 82.27 99.23 97.24 73.69 87.38 73.17 96.15 77 85.99 89.94 76.1 76.53 82.19 87.96 90.86 98.54 82.38 77.76 78.4 94.08 83.74 81.9 75.36 85.14 85.49 83.93 75.05 87.8 86.75 82.91 77.18 81.06 81.85 76.31
+53.3 50.44 49.13 49.25 52.92 58.61 49.81 52.87 46.74 51.08 56.49 52.06 49.55 44.5 58.46 61.05 56.41 53.51 60.14 54.96 56.69 54.87 48 54.98 53.71 52.22 53.84 54.53 53.68 50.78 51.16 56.01 56.12 58.12 51.08 50.62 55.47 51.08 46.49 53.55 52.28 45.28 47.09 54.56 57.37 49.97 55.3 49 55.81 52.02 85.99 81.02 86.32 69.03 81.05 74.64 81.95 60.77 81.61 67.85 61.35 75.37 71.5 78.97 69.31 82 74.98 72.19 76.66 69.34 79.9 74.61 80.44 78.25 78.6 81.02 83.98 85.63 77.4 67.91 67.81 67.05 71.51 80.31 73.62 79.11 83.86 76.67 72.36 69.63 71.92 78.72 71.76 61.15 72.01 73.38 73.29 77.24 60.99 72.44 90.99 79.58 94.14 86.12 89.72 101.39 69.14 96.77 88.74 98.56 86.05 84.76 89.9 77.96 80.93 87.1 87.23 105.23 102.94 78.05 92.7 77.63 101.81 81.67 91.22 95.34 80.74 81.21 87.13 93.22 96.26 104.52 87.34 82.44 83.02 99.74 88.88 86.85 79.98 90.33 90.7 89.1 79.58 93.12 92.06 87.99 81.83 86 86.89 80.95
+45.22 42.83 41.69 41.87 44.9 49.83 42.32 44.9 39.73 43.38 47.93 44.26 42.06 37.71 49.46 51.79 47.83 45.42 51.09 46.67 48.18 46.62 40.62 46.82 45.76 44.4 45.8 46.29 45.54 43.19 43.51 47.58 47.59 49.25 43.38 42.9 47 43.38 39.48 45.47 44.35 38.5 39.98 46.45 48.87 42.46 46.97 41.62 47.36 44.15 74.2 69.98 74.58 59.7 70.05 64.59 70.86 52.43 70.47 58.69 53 65.13 61.7 68.32 59.77 70.74 64.92 62.31 66.34 59.87 69.23 64.37 69.66 67.67 67.83 69.92 72.56 74.09 66.95 58.49 58.55 57.85 61.71 69.65 63.78 68.4 72.44 66.26 62.52 60.2 62.25 68.07 61.96 52.75 62.27 63.39 63.34 66.69 52.52 62.59 79.16 69.11 81.67 74.76 77.95 88.02 60.08 83.96 77.04 85.54 74.55 73.53 77.96 67.74 70.36 75.58 75.65 91.29 89.49 67.7 80.43 67.42 88.39 70.76 79.14 82.64 69.94 70.37 75.68 80.74 83.48 90.53 75.88 71.41 72.07 86.49 77.21 75.33 69.3 78.28 78.74 77.18 69.11 80.86 79.94 76.29 70.96 74.55 75.44 70.23
+47 44.44 43.33 43.49 46.7 51.87 44.06 46.65 41.26 44.99 49.81 46 43.62 39.19 51.44 53.99 49.83 47.24 53.09 48.59 49.99 48.55 42.34 48.69 47.53 46.04 47.64 48.09 47.3 44.86 45.16 49.45 49.53 51.28 44.99 44.56 48.81 44.99 41.03 47.23 46.15 39.89 41.59 48.4 50.87 44.1 48.86 43.26 49.23 45.86 76.89 72.63 77.29 61.86 72.6 66.97 73.59 54.37 72.98 60.97 54.85 67.64 63.76 70.83 62.08 73.34 67.43 64.51 68.67 62.01 71.98 66.74 72.13 70.07 70.29 72.48 75.12 76.84 69.47 60.59 60.64 59.89 63.97 72.21 66.27 71.11 75.11 68.54 64.91 62.42 64.5 70.6 64.2 54.67 64.58 65.76 65.72 69.13 54.52 64.93 82.38 71.77 84.71 77.54 80.96 91.18 62.45 86.91 79.76 88.95 77.47 76.27 80.93 70.36 73.25 78.63 78.47 94.75 92.65 70.06 83.57 70.11 91.47 73.41 82.21 85.64 72.6 73.09 78.56 83.58 86.43 93.9 78.8 73.99 74.58 89.69 80.38 78.17 72 81.28 81.86 80.23 71.77 84.01 83.17 79.3 73.6 77.42 78.54 72.95
+51.72 48.84 47.67 47.77 51.4 56.97 48.43 51.29 45.32 49.46 54.81 50.54 47.97 43.18 56.74 59.43 54.85 51.95 58.34 53.44 54.91 53.35 46.72 53.36 52.13 50.54 52.28 52.89 52.04 49.26 49.58 54.31 54.58 56.52 49.46 49.06 53.75 49.46 45.11 51.93 50.78 43.74 45.75 53.06 55.79 48.43 53.74 47.56 54.17 50.44 83.17 78.5 83.5 66.75 78.39 72.28 79.47 58.83 78.89 65.81 59.25 73.07 68.94 76.45 67.19 79.34 72.74 69.79 74.02 67.04 77.58 72.19 77.74 75.67 76.02 78.38 81.14 82.89 74.98 65.65 65.55 64.79 69.19 77.75 71.46 76.81 81.16 73.99 70.16 67.39 69.6 76.24 69.4 59.15 69.73 71.1 71.01 74.74 59.07 70.16 88.43 77.16 91.14 83.42 87 98.05 67.12 93.53 85.76 95.68 83.47 82.06 87.1 75.58 78.63 84.58 84.49 102.01 99.46 75.39 89.9 75.37 98.35 79.07 88.48 92.26 78.22 78.75 84.43 90.1 93.02 101.24 84.66 79.76 80.26 96.5 86.4 84.17 77.58 87.53 88 86.4 77.16 90.32 89.4 85.33 79.19 83.36 84.47 78.53
+51.71 48.91 47.66 47.81 51.36 56.87 48.34 51.31 45.36 49.58 54.81 50.56 48.08 43.18 56.66 59.22 54.67 51.89 58.34 53.34 55.01 53.22 46.56 53.3 52.21 50.66 52.22 52.91 52.06 49.31 49.66 54.27 54.53 56.41 49.58 49.06 53.76 49.58 45.11 51.96 50.69 43.84 45.71 52.88 55.72 48.44 53.71 47.56 54.16 50.46 83.47 78.65 83.8 66.99 78.65 72.54 79.58 59 79.24 65.87 59.55 73.15 69.4 76.72 67.24 79.57 72.85 70.15 74.35 67.33 77.59 72.39 78.1 76.06 76.29 78.62 81.52 83.11 75.15 65.9 65.83 65.1 69.41 78.03 71.55 76.83 81.4 74.39 70.29 67.59 69.91 76.47 69.66 59.35 69.94 71.31 71.19 74.99 59.13 70.34 88.35 77.27 91.38 83.69 87.11 98.48 67.16 94.04 86.19 95.65 83.5 82.27 87.23 75.65 78.47 84.49 84.74 102.26 99.94 75.8 89.94 75.35 98.9 79.24 88.58 92.64 78.34 78.84 84.58 90.58 93.47 101.55 84.76 80.1 80.77 96.74 86.27 84.39 77.64 87.63 87.97 86.34 77.27 90.39 89.3 85.29 79.37 83.45 84.34 78.64
+62.39 58.95 57.5 57.69 62 68.67 58.38 61.91 54.72 59.78 66.13 61.04 57.96 52.1 68.34 71.54 65.99 62.61 70.38 64.42 66.33 64.26 56.24 64.3 63.05 61.06 63.02 63.83 62.78 59.51 59.9 65.43 65.89 68.13 59.78 59.14 64.8 59.78 54.43 62.68 61.17 52.76 55.19 63.84 67.32 58.4 64.87 57.4 65.36 60.86 100.63 94.89 101.04 80.75 94.81 87.54 96.06 71.16 95.52 79.51 71.75 88.27 83.56 92.56 81.12 95.93 87.97 84.59 89.55 81.17 93.71 87.27 94.14 91.74 91.97 94.78 98.24 100.23 90.67 79.42 79.35 78.46 83.69 94.15 86.43 92.79 98.16 89.59 84.85 81.51 84.35 92.27 83.98 71.55 84.38 86.07 85.91 90.43 71.29 84.86 106.75 93.27 110.22 101.01 105.15 118.76 81.12 113.4 103.91 115.49 100.78 99.23 105.23 91.29 94.75 102.01 102.26 123.46 120.46 91.36 108.54 90.99 119.22 95.56 106.94 111.76 94.5 95.16 102.06 109.22 112.67 122.55 102.28 96.62 97.45 116.62 104.23 101.87 93.72 105.71 106.17 104.14 93.27 109.11 107.82 102.89 95.69 100.69 101.9 94.96
+58.49 55.65 53.94 54.29 57.98 64.37 54.61 58.15 51.54 56.37 62.01 57.3 54.65 48.73 63.8 66.6 61.61 58.72 66.19 60.19 62.61 60.16 52.14 60.73 59.37 57.8 59.3 59.95 59 56.01 56.52 61.69 61.28 63.39 56.37 55.56 60.88 56.37 51.11 58.92 57.26 50.29 51.63 60.02 63.18 55.11 60.65 53.86 61.24 57.2 97.87 92.1 98.45 78.92 92.52 85.21 93.2 69.04 93.09 77.19 70.15 85.66 81.82 90.16 78.49 93.23 85.42 82.27 87.96 79.06 91.01 84.84 92.27 89.44 89.48 92.2 95.98 97.8 88.24 77.1 77.34 76.42 81.35 92.09 83.88 89.77 95.53 87.84 82.2 79.44 82.23 89.73 81.78 69.55 82.11 83.43 83.4 87.94 69 82.45 104.24 91.24 108.01 98.83 102.93 116.69 79.19 111.36 102.25 112.64 98.16 97.24 102.94 89.49 92.65 99.46 99.94 120.46 118.95 89.87 106.07 88.81 117.4 93.48 104.33 109.3 92.28 92.72 100.03 106.94 110.72 119.47 100.26 94.43 95.59 114.47 101.51 99.43 91.26 103.28 103.81 101.67 91.24 106.68 105.25 100.56 93.88 98.33 99.13 92.56
+45.6 43.3 42.04 42.22 45.22 50.08 42.53 45.28 40.08 43.87 48.34 44.58 42.55 38.05 49.9 51.98 48.08 45.75 51.51 46.91 48.68 46.84 40.82 47.11 46.08 44.9 46.08 46.7 45.98 43.54 43.92 47.98 47.87 49.54 43.87 43.34 47.5 43.87 39.8 45.88 44.65 39.01 40.24 46.6 49.06 42.85 47.26 41.94 47.74 44.56 74.64 70.19 74.97 60.01 70.41 64.81 70.96 52.68 70.93 58.74 53.4 65.25 62.34 68.58 59.93 71.12 64.95 62.74 66.79 60.25 69.14 64.71 70.05 68.06 68.23 70.3 73.06 74.35 67.13 58.92 58.93 58.28 62.04 69.84 63.75 68.38 72.77 66.81 62.65 60.45 62.52 68.3 62.32 53.06 62.49 63.6 63.53 67.03 52.75 62.81 78.81 69.09 81.85 74.88 77.9 88.35 59.95 84.38 77.4 85.37 74.54 73.69 78.05 67.7 70.06 75.39 75.8 91.36 89.87 68.09 80.39 67.26 88.86 70.94 79.11 82.94 70.06 70.4 75.71 81.2 83.91 90.76 75.86 71.71 72.42 86.75 76.88 75.42 69.3 78.37 78.62 77.17 69.09 80.79 79.71 76.25 71.15 74.6 75.13 70.2
+54.83 51.85 50.54 50.67 54.46 60.35 51.29 54.39 48.08 52.51 58.11 53.58 50.93 45.77 60.12 62.88 58.07 55.06 61.87 56.59 58.29 56.5 49.42 56.59 55.29 53.68 55.42 56.09 55.2 52.25 52.62 57.61 57.78 59.83 52.51 52.04 57.02 52.51 47.83 55.08 53.8 46.51 48.47 56.2 59.1 51.39 56.93 50.42 57.42 53.5 88.55 83.5 88.91 71.1 83.48 76.93 84.5 62.6 84.03 69.97 63.15 77.7 73.54 81.38 71.43 84.45 77.34 74.33 78.92 71.4 82.45 76.84 82.85 80.6 80.94 83.44 86.46 88.24 79.78 69.9 69.82 69.02 73.65 82.79 75.96 81.61 86.39 78.9 74.6 71.74 74.11 81.13 73.9 62.97 74.21 75.63 75.54 79.56 62.82 74.65 93.98 82.1 97.05 88.81 92.57 104.49 71.37 99.7 91.43 101.72 88.76 87.38 92.7 80.43 83.57 89.9 89.94 108.54 106.07 80.39 95.63 80.13 104.88 84.18 94.11 98.26 83.24 83.76 89.87 96.02 99.16 107.75 90.1 84.95 85.55 102.79 91.79 89.57 82.5 93.14 93.59 91.89 82.1 96.08 95.03 90.76 84.34 88.69 89.73 83.52
+45.62 43.1 42.05 42.19 45.34 50.29 42.74 45.27 40.02 43.67 48.35 44.64 42.34 38.07 49.96 52.39 48.33 45.82 51.49 47.15 48.49 47.07 41.14 47.13 46.11 44.64 46.16 46.67 45.9 43.52 43.8 47.91 48.15 49.82 43.67 43.24 47.37 43.67 39.81 45.83 44.77 38.61 40.37 46.84 49.31 42.74 47.44 41.98 47.79 44.5 73.99 69.85 74.33 59.44 69.78 64.41 70.75 52.33 70.22 58.59 52.75 65.02 61.36 68.11 59.72 70.56 64.81 62.13 65.93 59.67 69.12 64.2 69.29 67.43 67.63 69.72 72.24 73.82 66.77 58.35 58.34 57.65 61.55 69.35 63.69 68.37 72.23 65.88 62.45 60 62.04 67.9 61.76 52.61 62.1 63.3 63.22 66.51 52.46 62.45 78.92 68.83 81.27 74.44 77.62 87.5 59.89 83.47 76.54 85.29 74.35 73.17 77.63 67.42 70.11 75.37 75.35 90.99 88.81 67.26 80.13 67.21 87.79 70.45 78.89 82.28 69.68 70.17 75.32 80.34 82.97 90.24 75.52 71.11 71.68 86.01 77.04 75.07 69.12 77.98 78.44 76.91 68.83 80.55 79.69 76 70.58 74.28 75.3 70.03
+58.85 55.91 54.26 54.55 58.36 64.69 54.92 58.47 51.78 56.66 62.39 57.6 54.94 49.08 64.3 67.06 62.01 59.05 66.52 60.56 62.89 60.48 52.6 60.9 59.61 58.02 59.54 60.29 59.34 56.27 56.76 61.95 61.77 63.89 56.66 55.9 61.26 56.66 51.39 59.24 57.61 50.4 51.95 60.22 63.44 55.34 61.03 54.16 61.62 57.52 97.15 91.39 97.64 78.19 91.71 84.48 92.44 68.56 92.36 76.53 69.55 84.97 81.16 89.38 77.96 92.55 84.67 81.69 87.05 78.45 90.15 84.21 91.34 88.7 88.81 91.5 95.16 96.89 87.47 76.62 76.73 75.86 80.75 91.13 83.13 89.07 94.76 87.03 81.59 78.75 81.51 88.99 81.14 69.05 81.42 82.83 82.75 87.27 68.57 81.8 102.95 90.19 106.8 97.75 101.71 115.34 78.28 110.14 101.05 111.39 97.18 96.15 101.81 88.39 91.47 98.35 98.9 119.22 117.4 88.86 104.88 87.79 116.02 92.5 103.22 108.2 91.34 91.8 98.84 105.9 109.49 118.35 99.04 93.52 94.57 113.16 100.35 98.41 90.36 102.19 102.59 100.58 90.19 105.45 104.02 99.43 92.81 97.29 98.04 91.6
+48.8 46.19 44.98 45.06 48.44 53.6 45.56 48.39 42.77 46.77 51.72 47.62 45.38 40.76 53.58 55.86 51.64 48.98 55.04 50.28 51.89 50.19 43.96 50.27 49.09 47.8 49.24 49.92 49.16 46.45 46.81 51.27 51.36 53.21 46.77 46.38 50.83 46.77 42.55 49.02 47.86 41.47 43.09 49.87 52.42 45.74 50.59 44.84 51.09 47.63 78.29 73.71 78.55 62.8 73.75 67.86 74.51 55.32 74.29 61.68 55.85 68.55 65.14 71.81 63.09 74.66 68.13 65.72 69.75 63.12 72.57 67.93 73.15 71.18 71.56 73.76 76.44 77.87 70.38 61.88 61.73 61.06 65.1 72.96 66.87 71.91 76.31 69.8 65.81 63.34 65.39 71.59 65.32 55.68 65.49 66.75 66.66 70.3 55.56 65.9 82.5 72.24 85.52 78.2 81.43 92.1 62.73 87.93 80.62 89.47 78.18 77 81.67 70.76 73.41 79.07 79.24 95.56 93.48 70.94 84.18 70.45 92.5 74.23 82.83 86.64 73.38 73.78 79.1 84.76 87.49 94.99 79.28 74.94 75.41 90.64 80.63 78.88 72.66 82.08 82.34 80.97 72.24 84.53 83.55 79.93 74.36 78.13 78.84 73.5
+54.12 51.13 49.88 50.02 53.78 59.58 50.65 53.69 47.45 51.82 57.36 52.92 50.25 45.19 59.32 62.1 57.3 54.33 61.05 55.89 57.51 55.77 48.82 55.8 54.63 52.94 54.68 55.36 54.46 51.59 51.93 56.79 57.13 59.11 51.82 51.32 56.23 51.82 47.21 54.36 53.09 45.78 47.87 55.43 58.38 50.67 56.25 49.78 56.69 52.79 87.19 82.24 87.54 69.97 82.16 75.81 83.25 61.66 82.74 68.92 62.15 76.52 72.36 80.17 70.34 83.14 76.22 73.24 77.6 70.31 81.23 75.64 81.54 79.42 79.69 82.14 85.1 86.86 78.57 68.82 68.74 67.96 72.52 81.54 74.88 80.43 85.06 77.61 73.52 70.63 73.03 79.93 72.76 62 73.1 74.55 74.43 78.35 61.83 73.53 92.55 80.83 95.51 87.48 91.13 102.85 70.3 98.17 89.98 100.14 87.38 85.99 91.22 79.14 82.21 88.48 88.58 106.94 104.33 79.11 94.11 78.89 103.22 82.83 92.68 96.78 81.92 82.48 88.45 94.56 97.58 106.15 88.66 83.67 84.31 101.09 90.39 88.24 81.24 91.65 92.08 90.36 80.83 94.58 93.52 89.26 82.95 87.29 88.37 82.28
+56.68 53.64 52.24 52.4 56.28 62.28 52.94 56.24 49.72 54.38 60.08 55.4 52.74 47.34 62.12 64.84 59.88 56.86 63.94 58.42 60.32 58.28 51 58.38 57.2 55.56 57.2 58 57.08 54.04 54.44 59.48 59.74 61.8 54.38 53.8 58.96 54.38 49.44 56.96 55.54 48.1 50.08 57.88 61 53.1 58.84 52.12 59.36 55.32 91.36 86.02 91.7 73.3 86.06 79.34 87 64.56 86.74 72 65.2 79.98 76.04 83.92 73.54 87.08 79.62 76.8 81.38 73.7 84.76 79.22 85.46 83.24 83.5 86.04 89.24 90.9 82.18 72.16 72.06 71.28 75.96 85.32 78.18 83.96 89.06 81.46 76.86 73.94 76.48 83.64 76.24 64.96 76.5 78 77.86 82.06 64.7 76.94 96.42 84.42 99.9 91.48 95.16 107.7 73.34 102.88 94.28 104.46 91.24 89.94 95.34 82.64 85.64 92.26 92.64 111.76 109.3 82.94 98.26 82.28 108.2 86.64 96.78 101.32 85.64 86.16 92.42 99.12 102.26 111.04 92.6 87.62 88.36 105.78 94.16 92.24 84.84 95.78 96.08 94.34 84.42 98.74 97.5 93.18 86.78 91.2 92.06 85.92
+48.5 45.86 44.7 44.76 48.16 53.28 45.3 48.08 42.48 46.44 51.4 47.32 45.06 40.52 53.28 55.58 51.36 48.68 54.68 50 51.52 49.9 43.76 49.92 48.76 47.44 48.92 49.6 48.84 46.14 46.48 50.92 51.1 52.94 46.44 46.08 50.5 46.44 42.28 48.7 47.58 41.12 42.84 49.56 52.1 45.42 50.3 44.56 50.78 47.32 77.44 72.94 77.68 62.08 72.92 67.12 73.74 54.74 73.46 61.04 55.2 67.84 64.36 71.02 62.46 73.86 67.42 65 68.9 62.42 71.82 67.2 72.28 70.38 70.78 72.96 75.56 77 69.62 61.22 61.04 60.38 64.4 72.12 66.18 71.2 75.48 68.96 65.14 62.64 64.66 70.82 64.6 55.08 64.78 66.06 65.96 69.54 55 65.2 81.6 71.42 84.52 77.3 80.5 90.98 62.04 86.86 79.62 88.5 77.34 76.1 80.74 69.94 72.6 78.22 78.34 94.5 92.28 70.06 83.24 69.68 91.34 73.38 81.92 85.64 72.56 72.98 78.18 83.76 86.42 93.94 78.36 74.08 74.5 89.56 79.78 78 71.88 81.16 81.42 80.08 71.42 83.58 82.64 79.04 73.48 77.26 78.02 72.7
+48.83 46.11 45 45.07 48.52 53.69 45.66 48.41 42.76 46.72 51.75 47.68 45.32 40.8 53.62 56.04 51.73 49.01 55.04 50.4 51.83 50.28 44.12 50.24 49.15 47.7 49.26 49.93 49.14 46.47 46.78 51.21 51.55 53.37 46.72 46.34 50.78 46.72 42.57 49.02 47.91 41.26 43.17 49.92 52.54 45.68 50.71 44.88 51.14 47.62 77.85 73.39 78.1 62.39 73.29 67.56 74.24 55.06 73.84 61.45 55.45 68.27 64.6 71.46 62.84 74.25 67.91 65.37 69.17 62.75 72.35 67.55 72.64 70.8 71.15 73.34 75.92 77.43 70.05 61.52 61.35 60.68 64.75 72.57 66.69 71.73 75.9 69.23 65.59 62.99 65.07 71.27 64.94 55.37 65.18 66.51 66.39 69.93 55.29 65.6 82.23 71.89 85 77.81 81.03 91.48 62.5 87.34 80.03 89.11 77.84 76.53 81.21 70.37 73.09 78.75 78.84 95.16 92.72 70.4 83.76 70.17 91.8 73.78 82.48 86.16 72.98 73.46 78.66 84.22 86.85 94.55 78.84 74.52 74.97 90 80.39 78.53 72.36 81.63 81.93 80.52 71.89 84.13 83.2 79.49 73.85 77.73 78.62 73.22
+50.7 48.02 46.74 46.94 50.34 55.84 47.43 50.34 44.54 48.65 53.74 49.62 47.17 42.29 55.46 58.04 53.6 50.91 57.27 52.31 54.02 52.24 45.54 52.45 51.3 49.78 51.32 51.9 51.06 48.42 48.78 53.32 53.37 55.22 48.65 48.1 52.7 48.65 44.26 50.98 49.71 43.15 44.82 52.02 54.76 47.59 52.66 46.66 53.1 49.5 83.02 78.27 83.43 66.77 78.35 72.25 79.24 58.66 78.85 65.62 59.3 72.83 69.06 76.42 66.85 79.14 72.59 69.74 74.19 66.99 77.38 72.01 77.91 75.72 75.89 78.22 81.18 82.85 74.87 65.46 65.51 64.74 69.04 77.88 71.31 76.48 81.03 74.13 69.93 67.33 69.64 76.14 69.32 59.02 69.65 70.92 70.85 74.61 58.75 70.01 88.41 77.23 91.29 83.58 87.1 98.41 67.13 93.9 86.14 95.57 83.32 82.19 87.13 75.68 78.56 84.43 84.58 102.06 100.03 75.71 89.87 75.32 98.84 79.1 88.45 92.42 78.18 78.66 84.57 90.32 93.35 101.24 84.78 79.87 80.62 96.67 86.24 84.22 77.46 87.49 87.96 86.23 77.23 90.35 89.29 85.23 79.31 83.32 84.27 78.5
+55.66 52.72 51.3 51.44 55.24 61.08 51.92 55.22 48.82 53.44 59 54.36 51.84 46.5 61.04 63.58 58.76 55.82 62.78 57.3 59.26 57.16 50.04 57.28 56.1 54.6 56.12 56.96 56.08 53.04 53.46 58.42 58.6 60.64 53.44 52.88 57.96 53.44 48.54 55.94 54.52 47.32 49.14 56.74 59.78 52.16 57.72 51.16 58.28 54.34 89.5 84.18 89.8 71.78 84.28 77.62 85.08 63.22 84.98 70.4 63.9 78.24 74.6 82.12 71.98 85.3 77.82 75.24 79.74 72.2 82.8 77.6 83.68 81.5 81.8 84.28 87.44 88.96 80.4 70.74 70.6 69.86 74.4 83.44 76.38 82.06 87.2 79.86 75.18 72.38 74.84 81.84 74.68 63.64 74.86 76.32 76.18 80.36 63.38 75.3 94.06 82.48 97.7 89.42 92.96 105.36 71.6 100.68 92.26 102.02 89.18 87.96 93.22 80.74 83.58 90.1 90.58 109.22 106.94 81.2 96.02 80.34 105.9 84.76 94.56 99.12 83.76 84.22 90.32 97.04 100.1 108.6 90.48 85.74 86.44 103.5 91.88 90.16 82.92 93.66 93.86 92.24 82.48 96.46 95.2 91.08 84.9 89.16 89.84 83.94
+56.46 53.58 52.05 52.25 56 62.01 52.67 56.05 49.6 54.28 59.85 55.18 52.65 47.12 61.82 64.41 59.57 56.65 63.76 58.1 60.23 58.01 50.6 58.3 57.01 55.54 57.04 57.81 56.92 53.88 54.34 59.39 59.3 61.38 54.28 53.66 58.81 54.28 49.27 56.79 55.3 48.24 49.83 57.7 60.73 53.03 58.52 51.92 59.11 55.16 92.09 86.62 92.48 74.01 86.85 79.94 87.57 65.01 87.49 72.49 65.85 80.53 76.86 84.59 73.99 87.76 80.14 77.39 82.34 74.32 85.32 79.85 86.36 83.93 84.18 86.74 90.1 91.71 82.82 72.71 72.69 71.89 76.55 86.11 78.66 84.41 89.78 82.37 77.32 74.57 77.1 84.26 76.88 65.47 77.09 78.48 78.39 82.7 65.13 77.5 97.21 85.2 100.92 92.32 96.06 108.89 73.94 103.99 95.38 105.32 91.97 90.86 96.26 83.48 86.43 93.02 93.47 112.67 110.72 83.91 99.16 82.97 109.49 87.49 97.58 102.26 86.42 86.85 93.35 100.1 103.42 111.94 93.54 88.42 89.24 106.96 94.86 93.01 85.5 96.67 96.98 95.22 85.2 99.64 98.34 94.07 87.73 92.02 92.71 86.59
+62.95 59.47 58.01 58.12 62.54 69.16 58.82 62.42 55.14 60.29 66.72 61.48 58.48 52.61 69.1 72.15 66.6 63.15 70.95 64.93 66.86 64.75 56.82 64.71 63.4 61.54 63.46 64.38 63.36 59.93 60.34 65.98 66.46 68.77 60.29 59.74 65.47 60.29 54.88 63.21 61.72 53.21 55.64 64.24 67.69 58.88 65.37 57.86 65.93 61.4 100.34 94.52 100.65 80.39 94.43 87.07 95.59 70.95 95.2 79.1 71.5 87.89 83.36 92.09 80.9 95.67 87.44 84.32 89.14 80.9 93.09 87.03 93.63 91.31 91.7 94.5 97.88 99.73 90.22 79.31 79.09 78.25 83.44 93.5 85.86 92.32 97.79 89.27 84.48 81.15 83.89 91.83 83.7 71.36 83.98 85.71 85.53 90.12 71.19 84.51 105.71 92.52 109.45 100.23 104.27 117.88 80.41 112.61 103.15 114.6 100.15 98.54 104.52 90.53 93.9 101.24 101.55 122.55 119.47 90.76 107.75 90.24 118.35 94.99 106.15 111.04 93.94 94.55 101.24 108.6 111.94 121.81 101.44 96.05 96.71 115.87 103.33 101.14 93.12 105.05 105.35 103.53 92.52 108.24 106.95 102.21 95.07 100.03 101.06 94.25
+50.72 48.04 46.76 46.96 50.36 55.88 47.46 50.36 44.56 48.66 53.76 49.64 47.18 42.3 55.48 58.08 53.64 50.94 57.3 52.34 54.04 52.28 45.56 52.5 51.32 49.8 51.36 51.92 51.08 48.44 48.8 53.36 53.38 55.24 48.66 48.12 52.72 48.66 44.28 51 49.74 43.18 44.84 52.08 54.8 47.62 52.68 46.68 53.12 49.52 83.16 78.42 83.58 66.9 78.5 72.38 79.4 58.76 78.98 65.76 59.4 72.98 69.16 76.56 66.98 79.28 72.74 69.84 74.34 67.1 77.56 72.14 78.06 75.84 76.02 78.36 81.32 83.02 75.02 65.56 65.62 64.84 69.16 78.04 71.46 76.64 81.18 74.26 70.06 67.46 69.76 76.28 69.44 59.12 69.78 71.04 70.98 74.74 58.86 70.14 88.66 77.42 91.5 83.76 87.32 98.62 67.3 94.08 86.32 95.82 83.52 82.38 87.34 75.88 78.8 84.66 84.76 102.28 100.26 75.86 90.1 75.52 99.04 79.28 88.66 92.6 78.36 78.84 84.78 90.48 93.54 101.44 85 80.02 80.76 96.9 86.48 84.4 77.64 87.7 88.2 86.46 77.42 90.58 89.54 85.46 79.5 83.52 84.5 78.68
+49.37 46.71 45.5 45.61 49.02 54.21 46.09 48.97 43.28 47.35 52.33 48.22 45.93 41.25 54.16 56.48 52.17 49.52 55.67 50.87 52.51 50.74 44.46 50.79 49.75 48.36 49.78 50.51 49.72 47.03 47.38 51.79 52.04 53.85 47.35 46.88 51.38 47.35 43.05 49.6 48.38 41.87 43.61 50.36 53.06 46.23 51.23 45.38 51.7 48.18 79.13 74.48 79.39 63.44 74.5 68.65 75.3 55.92 75.11 62.31 56.45 69.24 65.86 72.62 63.71 75.43 68.88 66.51 70.42 63.82 73.31 68.62 73.93 72.04 72.32 74.52 77.26 78.66 71.12 62.54 62.4 61.74 65.79 73.77 67.62 72.67 77.11 70.52 66.54 64 66.17 72.39 66.02 56.27 66.21 67.53 67.4 71.06 56.08 66.61 83.28 72.96 86.37 79.07 82.23 93.09 63.37 88.94 81.49 90.3 78.92 77.76 82.44 71.41 73.99 79.76 80.1 96.62 94.43 71.71 84.95 71.11 93.52 74.94 83.67 87.62 74.08 74.52 79.87 85.74 88.42 96.05 80.02 75.79 76.37 91.47 81.37 79.75 73.38 82.84 83.05 81.61 72.96 85.34 84.27 80.58 75.04 78.87 79.57 74.28
+48.33 45.81 44.55 44.8 47.98 53.16 45.16 48.02 42.5 46.49 51.24 47.36 45.06 40.33 52.78 55.17 50.92 48.47 54.59 49.81 51.58 49.69 43.3 49.91 49.04 47.54 48.86 49.5 48.68 46.23 46.58 50.74 50.92 52.59 46.49 45.82 50.21 46.49 42.2 48.63 47.3 41.13 42.72 49.4 52.19 45.34 50.23 44.5 50.63 47.2 79.3 74.66 79.69 63.75 74.79 69.07 75.57 56.01 75.38 62.54 56.7 69.41 66.12 73.03 63.68 75.53 69.26 66.76 70.84 64.04 73.71 68.71 74.47 72.49 72.48 74.66 77.6 79.05 71.44 62.53 62.61 61.91 65.92 74.42 68.04 72.88 77.35 70.87 66.74 64.27 66.63 72.73 66.22 56.36 66.52 67.77 67.65 71.26 55.95 66.83 84.11 73.62 87.09 79.85 83.01 94.06 63.97 89.87 82.37 90.94 79.33 78.4 83.02 72.07 74.58 80.26 80.77 97.45 95.59 72.42 85.55 71.68 94.57 75.41 84.31 88.36 74.5 74.97 80.62 86.44 89.24 96.71 80.76 76.37 77.29 92.15 81.99 80.42 73.8 83.33 83.65 81.93 73.62 86.06 84.87 81.01 75.59 79.37 80.12 74.87
+58.77 55.73 54.18 54.33 58.3 64.57 54.85 58.31 51.58 56.41 62.29 57.38 54.73 49.05 64.44 67.16 62.13 59 66.35 60.51 62.61 60.44 52.78 60.69 59.21 57.72 59.38 60.15 59.24 56.01 56.48 61.85 61.72 63.95 56.41 55.88 61.24 56.41 51.27 59.08 57.62 50.17 51.87 60.14 63.18 55.19 60.89 54.02 61.52 57.4 95.39 89.78 95.77 76.64 89.96 82.73 90.76 67.36 90.57 75.15 68.15 83.5 79.5 87.56 76.77 90.95 83.02 80.07 85.24 76.94 88.45 82.76 89.35 86.8 87.2 89.88 93.26 95 85.8 75.34 75.26 74.42 79.31 89.09 81.48 87.53 93.01 85.24 80.12 77.24 79.75 87.25 79.62 67.83 79.83 81.27 81.2 85.66 67.6 80.29 100.76 88.24 104.49 95.51 99.49 112.61 76.59 107.48 98.61 109.2 95.36 94.08 99.74 86.49 89.69 96.5 96.74 116.62 114.47 86.75 102.79 86.01 113.16 90.64 101.09 105.78 89.56 90 96.67 103.5 106.96 115.87 96.9 91.47 92.15 110.79 98.39 96.27 88.62 100.2 100.57 98.83 88.24 103.24 102.01 97.6 90.88 95.37 96.17 89.68
+52.35 49.39 48.25 48.4 52.06 57.76 49.1 51.94 45.9 50.05 55.48 51.24 48.52 43.69 57.34 60.23 55.52 52.59 59.07 54.17 55.58 54.07 47.3 54.07 52.92 51.14 52.98 53.54 52.64 49.93 50.22 54.94 55.34 57.25 50.05 49.58 54.31 50.05 45.68 52.57 51.4 44.17 46.36 53.8 56.65 49 54.49 48.18 54.85 51.04 84.66 80 85.05 67.99 79.83 73.75 81.07 59.91 80.32 67.14 60.3 74.49 70.08 77.97 68.42 80.75 74.28 71.08 75.34 68.26 79.25 73.47 79.23 77.15 77.38 79.78 82.6 84.49 76.46 66.75 66.73 65.93 70.44 79.38 73.02 78.4 82.67 75.27 71.56 68.67 71.01 77.75 70.66 60.2 71.1 72.51 72.41 76.12 60.07 71.51 90.51 78.84 93.01 85.23 88.91 100.08 68.65 95.45 87.51 97.76 85.19 83.74 88.88 77.21 80.38 86.4 86.27 104.23 101.51 76.88 91.79 77.04 100.35 80.63 90.39 94.16 79.78 80.39 86.24 91.88 94.86 103.33 86.48 81.37 81.99 98.39 88.37 85.98 79.2 89.29 89.87 88.09 78.84 92.28 91.35 87.05 80.75 85.07 86.38 80.25
+51.55 48.72 47.51 47.66 51.22 56.72 48.22 51.15 45.21 49.4 54.64 50.42 47.9 43.05 56.48 59.09 54.52 51.73 58.15 53.21 54.81 53.08 46.46 53.12 52.07 50.46 52.06 52.74 51.88 49.16 49.49 54.07 54.42 56.28 49.4 48.88 53.56 49.4 44.97 51.79 50.54 43.62 45.59 52.73 55.59 48.26 53.58 47.42 54 50.29 83.09 78.33 83.42 66.67 78.28 72.25 79.28 58.75 78.87 65.62 59.25 72.86 69.02 76.4 66.97 79.21 72.59 69.84 73.95 67.02 77.32 72.06 77.72 75.73 75.94 78.26 81.12 82.74 74.84 65.59 65.52 64.79 69.1 77.7 71.31 76.57 81.04 73.99 70.03 67.29 69.62 76.16 69.34 59.08 69.65 71.04 70.91 74.66 58.87 70.05 88.05 76.96 90.97 83.35 86.76 98.02 66.92 93.6 85.77 95.29 83.17 81.9 86.85 75.33 78.17 84.17 84.39 101.87 99.43 75.42 89.57 75.07 98.41 78.88 88.24 92.24 78 78.53 84.22 90.16 93.01 101.14 84.4 79.75 80.42 96.27 85.98 84.06 77.34 87.25 87.61 85.96 76.96 90.03 88.96 84.92 78.99 83.1 84.06 78.35
+48.18 45.48 44.4 44.46 47.88 52.98 45.06 47.76 42.18 46.08 51.06 47.04 44.7 40.26 52.92 55.32 51.06 48.36 54.3 49.74 51.12 49.62 43.56 49.56 48.48 47.04 48.6 49.26 48.48 45.84 46.14 50.52 50.88 52.68 46.08 45.72 50.1 46.08 42 48.36 47.28 40.68 42.6 49.26 51.84 45.06 50.04 44.28 50.46 46.98 76.68 72.3 76.92 61.44 72.18 66.54 73.14 54.24 72.72 60.54 54.6 67.26 63.6 70.38 61.92 73.14 66.9 64.38 68.1 61.8 71.28 66.54 71.52 69.72 70.08 72.24 74.76 76.26 69 60.6 60.42 59.76 63.78 71.46 65.7 70.68 74.76 68.16 64.62 62.04 64.08 70.2 63.96 54.54 64.2 65.52 65.4 68.88 54.48 64.62 81 70.8 83.7 76.62 79.8 90.06 61.56 85.98 78.78 87.78 76.68 75.36 79.98 69.3 72 77.58 77.64 93.72 91.26 69.3 82.5 69.12 90.36 72.66 81.24 84.84 71.88 72.36 77.46 82.92 85.5 93.12 77.64 73.38 73.8 88.62 79.2 77.34 71.28 80.4 80.7 79.32 70.8 82.86 81.96 78.3 72.72 76.56 77.46 72.12
+54.02 51.09 49.79 49.87 53.64 59.37 50.47 53.56 47.33 51.73 57.25 52.72 50.19 45.12 59.32 61.91 57.21 54.23 60.92 55.7 57.4 55.6 48.72 55.65 54.34 52.86 54.52 55.25 54.4 51.41 51.79 56.74 56.9 58.95 51.73 51.32 56.24 51.73 47.1 54.25 53 45.83 47.72 55.25 58.07 50.61 56.03 49.64 56.56 52.71 86.54 81.53 86.83 69.41 81.52 75.04 82.44 61.17 82.1 68.25 61.7 75.84 71.92 79.4 69.8 82.54 75.39 72.63 77.05 69.76 80.33 75.1 80.83 78.67 79.1 81.54 84.46 86.1 77.84 68.39 68.22 67.47 71.97 80.67 74.01 79.6 84.37 77.09 72.81 70.03 72.29 79.17 72.2 61.55 72.42 73.83 73.73 77.72 61.45 72.88 91.35 79.92 94.56 86.48 90.09 101.79 69.43 97.16 89.08 99.03 86.51 85.14 90.33 78.28 81.28 87.53 87.63 105.71 103.28 78.37 93.14 77.98 102.19 82.08 91.65 95.78 81.16 81.63 87.49 93.66 96.67 105.05 87.7 82.84 83.33 100.2 89.29 87.25 80.4 90.79 91.12 89.59 79.92 93.53 92.49 88.44 82.21 86.43 87.31 81.33
+53.34 50.45 49.17 49.31 52.98 58.75 49.92 52.92 46.79 51.08 56.53 52.14 49.54 44.51 58.46 61.19 56.51 53.58 60.21 55.07 56.72 55 48.06 55.12 53.82 52.24 53.96 54.57 53.7 50.85 51.21 56.08 56.19 58.19 51.08 50.62 55.46 51.08 46.54 53.59 52.35 45.28 47.16 54.75 57.55 50.02 55.39 49.06 55.86 52.05 86.5 81.6 86.88 69.5 81.59 75.19 82.6 61.15 82.09 68.41 61.7 75.95 71.82 79.54 69.79 82.5 75.62 72.59 77.16 69.75 80.65 75.07 81 78.75 79.07 81.52 84.48 86.27 77.99 68.25 68.21 67.41 71.95 80.97 74.28 79.78 84.42 77.1 72.9 70.12 72.43 79.29 72.2 61.51 72.53 73.89 73.82 77.73 61.36 72.95 92.04 80.35 94.95 86.88 90.61 102.22 69.86 97.5 89.44 99.56 86.83 85.49 90.7 78.74 81.86 88 87.97 106.17 103.81 78.62 93.59 78.44 102.59 82.34 92.08 96.08 81.42 81.93 87.96 93.86 96.98 105.35 88.2 83.05 83.65 100.57 89.87 87.61 80.7 91.12 91.62 89.92 80.35 94.04 93.04 88.83 82.52 86.77 87.84 81.71
+53.64 50.71 49.44 49.46 53.26 58.94 50.11 53.15 46.95 51.3 56.84 52.28 49.79 44.81 59 61.54 56.9 53.87 60.47 55.31 56.93 55.23 48.46 55.24 53.81 52.42 54.12 54.84 54.02 50.97 51.35 56.37 56.47 58.57 51.3 51 55.89 51.3 46.75 53.84 52.67 45.5 47.37 54.89 57.58 50.25 55.59 49.26 56.15 52.33 85.41 80.48 85.66 68.47 80.44 73.95 81.35 60.38 80.98 67.36 60.85 74.88 70.92 78.27 68.98 81.5 74.34 71.6 76 68.81 79.25 74.16 79.66 77.5 78.07 80.5 83.3 84.94 76.79 67.54 67.3 66.56 71.04 79.46 72.96 78.57 83.26 76.03 71.84 69.09 71.21 78.07 71.24 60.76 71.42 72.81 72.73 76.69 60.77 71.91 90.05 78.77 93.21 85.16 88.79 100.23 68.42 95.63 87.7 97.7 85.38 83.93 89.1 77.18 80.23 86.4 86.34 104.14 101.67 77.17 91.89 76.91 100.58 80.97 90.36 94.34 80.08 80.52 86.23 92.24 95.22 103.53 86.46 81.61 81.93 98.83 88.09 85.96 79.32 89.59 89.92 88.52 78.77 92.22 91.28 87.34 81.09 85.27 86.15 80.16
+46.55 44.04 42.91 43.08 46.24 51.28 43.57 46.21 40.87 44.63 49.34 45.56 43.27 38.84 50.94 53.35 49.24 46.74 52.56 48.06 49.55 47.98 41.88 48.11 47.09 45.64 47.1 47.64 46.86 44.44 44.75 48.91 49.07 50.76 44.63 44.14 48.36 44.63 40.63 46.79 45.65 39.51 41.17 47.75 50.29 43.65 48.38 42.84 48.76 45.43 75.87 71.56 76.23 60.98 71.57 66.04 72.46 53.63 72.04 60 54.15 66.59 63.04 69.84 61.14 72.33 66.38 63.74 67.7 61.21 70.76 65.81 71.13 69.19 69.35 71.48 74.14 75.69 68.43 59.83 59.85 59.15 63.1 71.14 65.22 69.97 74.05 67.66 63.96 61.52 63.64 69.6 63.34 53.94 63.66 64.86 64.78 68.19 53.72 64 80.8 70.55 83.36 76.35 79.56 89.83 61.35 85.72 78.61 87.34 76.15 75.05 79.58 69.11 71.77 77.16 77.27 93.27 91.24 69.09 82.1 68.83 90.19 72.24 80.83 84.42 71.42 71.89 77.23 82.48 85.2 92.52 77.42 72.96 73.62 88.24 78.84 76.96 70.8 79.92 80.35 78.77 70.55 82.54 81.59 77.85 72.4 76.12 77.05 71.75
+54.6 51.64 50.33 50.51 54.24 60.15 51.11 54.19 47.92 52.32 57.87 53.42 50.73 45.56 59.78 62.61 57.79 54.83 61.64 56.38 58.09 56.29 49.16 56.42 55.19 53.5 55.24 55.87 54.96 52.1 52.46 57.37 57.56 59.56 52.32 51.78 56.73 52.32 47.65 54.87 53.56 46.32 48.29 56.02 58.97 51.19 56.74 50.24 57.19 53.28 88.79 83.76 89.2 71.35 83.75 77.26 84.81 62.77 84.29 70.23 63.35 77.95 73.74 81.71 71.59 84.66 77.68 74.57 79.2 71.62 82.82 77.03 83.2 80.93 81.16 83.66 86.74 88.57 80.08 70.03 70.03 69.21 73.85 83.21 76.32 81.91 86.66 79.15 74.86 71.99 74.44 81.44 74.12 63.13 74.49 75.9 75.81 79.8 62.91 74.9 94.55 82.54 97.52 89.3 93.08 105.05 71.78 100.23 91.92 102.22 89.13 87.8 93.12 80.86 84.01 90.32 90.39 109.11 106.68 80.79 96.08 80.55 105.45 84.53 94.58 98.74 83.58 84.13 90.35 96.46 99.64 108.24 90.58 85.34 86.06 103.24 92.28 90.03 82.86 93.53 94.04 92.22 82.54 96.58 95.5 91.13 84.71 89.08 90.19 83.95
+54.2 51.21 49.96 50.1 53.86 59.74 50.77 53.77 47.53 51.86 57.44 53 50.29 45.23 59.4 62.26 57.46 54.45 61.17 56.01 57.59 55.93 48.9 56 54.71 53.02 54.84 55.44 54.54 51.67 52.01 56.95 57.17 59.19 51.86 51.4 56.31 51.86 47.29 54.44 53.21 45.9 47.95 55.67 58.54 50.79 56.33 49.86 56.77 52.87 87.75 82.84 88.14 70.49 82.76 76.33 83.89 62.06 83.26 69.48 62.55 77.12 72.76 80.73 70.86 83.7 76.82 73.64 78.2 70.75 81.95 76.16 82.14 79.9 80.21 82.7 85.66 87.54 79.17 69.22 69.18 68.36 73 82.18 75.48 81.07 85.66 78.13 74.04 71.15 73.51 80.49 73.24 62.4 73.62 75.03 74.95 78.87 62.27 74.05 93.55 81.59 96.35 88.2 92.01 103.69 70.98 98.89 90.7 101.14 88.18 86.75 92.06 79.94 83.17 89.4 89.3 107.82 105.25 79.71 95.03 79.69 104.02 83.55 93.52 97.5 82.64 83.2 89.29 95.2 98.34 106.95 89.54 84.27 84.87 102.01 91.35 88.96 81.96 92.49 93.04 91.28 81.59 95.5 94.52 90.18 83.71 88.09 89.29 83
+52.41 49.57 48.31 48.38 52.04 57.64 48.99 51.96 45.92 50.16 55.54 51.14 48.67 43.76 57.56 60.11 55.56 52.64 59.12 54.06 55.68 53.99 47.28 54.06 52.7 51.28 52.94 53.6 52.78 49.87 50.24 55.1 55.17 57.19 50.16 49.8 54.57 50.16 45.7 52.63 51.45 44.5 46.3 53.7 56.37 49.13 54.35 48.16 54.87 51.14 84.16 79.33 84.46 67.54 79.32 72.98 80.23 59.49 79.83 66.44 60 73.82 69.9 77.23 67.93 80.29 73.37 70.58 74.99 67.83 78.23 73.06 78.64 76.47 76.93 79.32 82.14 83.8 75.75 66.49 66.34 65.59 70 78.5 72.03 77.48 82.08 74.98 70.83 68.14 70.29 77.01 70.22 59.86 70.45 71.79 71.72 75.59 59.8 70.9 89.06 77.85 92.08 84.17 87.77 99.07 67.64 94.51 86.69 96.51 84.27 82.91 87.99 76.29 79.3 85.33 85.29 102.89 100.56 76.25 90.76 76 99.43 79.93 89.26 93.18 79.04 79.49 85.23 91.08 94.07 102.21 85.46 80.58 81.01 97.6 87.05 84.92 78.3 88.44 88.83 87.34 77.85 91.13 90.18 86.22 80.08 84.19 85.11 79.19
+48.26 45.75 44.49 44.61 47.88 53.03 45.05 47.88 42.35 46.31 51.15 47.12 44.93 40.28 52.92 55.17 51.03 48.45 54.48 49.7 51.4 49.64 43.36 49.83 48.62 47.38 48.76 49.39 48.64 45.99 46.37 50.78 50.7 52.53 46.31 45.88 50.28 46.31 42.1 48.51 47.32 41.17 42.6 49.39 51.89 45.31 50.01 44.36 50.52 47.13 78.26 73.67 78.57 62.87 73.8 67.88 74.48 55.27 74.3 61.67 55.9 68.52 65.2 71.84 63 74.62 68.13 65.69 69.91 63.12 72.59 67.9 73.29 71.21 71.54 73.74 76.5 77.94 70.4 61.81 61.74 61.05 65.07 73.09 66.87 71.84 76.31 69.91 65.75 63.37 65.43 71.59 65.32 55.65 65.5 66.69 66.63 70.28 55.47 65.88 82.69 72.4 85.72 78.36 81.63 92.37 62.85 88.16 80.88 89.61 78.25 77.18 81.83 70.96 73.6 79.19 79.37 95.69 93.88 71.15 84.34 70.58 92.81 74.36 82.95 86.78 73.48 73.85 79.31 84.9 87.73 95.07 79.5 75.04 75.59 90.88 80.75 78.99 72.72 82.21 82.52 81.09 72.4 84.71 83.71 80.08 74.55 78.25 78.93 73.59
+51.33 48.53 47.31 47.4 50.98 56.44 47.98 50.9 44.98 49.15 54.4 50.12 47.68 42.87 56.34 58.85 54.36 51.53 57.89 52.95 54.54 52.85 46.3 52.89 51.68 50.22 51.82 52.5 51.68 48.87 49.22 53.9 54.1 56.03 49.15 48.74 53.41 49.15 44.76 51.55 50.36 43.51 45.36 52.52 55.23 48.08 53.27 47.18 53.75 50.08 82.34 77.6 82.63 66.05 77.57 71.45 78.49 58.21 78.12 64.98 58.7 72.19 68.4 75.59 66.42 78.53 71.8 69.12 73.3 66.38 76.51 71.45 76.93 74.89 75.26 77.58 80.36 81.95 74.1 65.05 64.91 64.19 68.48 76.82 70.5 75.8 80.29 73.33 69.32 66.65 68.83 75.37 68.7 58.56 68.94 70.29 70.19 73.96 58.45 69.37 87.05 76.12 90.03 82.37 85.81 96.92 66.15 92.51 84.81 94.32 82.37 81.06 86 74.55 77.42 83.36 83.45 100.69 98.33 74.6 88.69 74.28 97.29 78.13 87.29 91.2 77.26 77.73 83.32 89.16 92.02 100.03 83.52 78.87 79.37 95.37 85.07 83.1 76.56 86.43 86.77 85.27 76.12 89.08 88.09 84.19 78.25 82.29 83.18 77.47
+51.54 48.6 47.5 47.62 51.26 56.84 48.33 51.12 45.16 49.25 54.62 50.42 47.75 43.03 56.5 59.32 54.68 51.77 58.13 53.33 54.68 53.22 46.62 53.17 52.04 50.3 52.12 52.7 51.82 49.12 49.4 54.06 54.51 56.4 49.25 48.82 53.48 49.25 44.96 51.74 50.61 43.43 45.64 52.92 55.72 48.21 53.64 47.42 54 50.24 82.88 78.31 83.23 66.51 78.11 72.15 79.34 58.66 78.61 65.7 59 72.91 68.58 76.28 67.01 79.06 72.67 69.58 73.67 66.81 77.52 71.93 77.47 75.48 75.75 78.1 80.82 82.65 74.81 65.38 65.31 64.54 68.96 77.6 71.43 76.74 80.91 73.63 70.05 67.19 69.46 76.08 69.16 58.94 69.57 70.98 70.87 74.51 58.85 69.99 88.43 77.05 90.91 83.3 86.88 97.79 67.09 93.28 85.5 95.57 83.32 81.85 86.89 75.44 78.54 84.47 84.34 101.9 99.13 75.13 89.73 75.3 98.04 78.84 88.37 92.06 78.02 78.62 84.27 89.84 92.71 101.06 84.5 79.57 80.12 96.17 86.38 84.06 77.46 87.31 87.84 86.15 77.05 90.19 89.29 85.11 78.93 83.18 84.45 78.46
+48.09 45.41 44.32 44.45 47.8 52.95 45.02 47.71 42.16 46.04 50.97 47.04 44.64 40.16 52.7 55.2 50.91 48.27 54.24 49.68 51.09 49.56 43.4 49.56 48.57 47.02 48.58 49.19 48.38 45.85 46.14 50.43 50.81 52.55 46.04 45.58 49.94 46.04 41.95 48.3 47.17 40.62 42.55 49.24 51.9 45 50.01 44.24 50.38 46.9 77.39 73.01 77.7 62.09 72.91 67.32 73.92 54.74 73.44 61.19 55.15 67.93 64.2 71.18 62.44 73.79 67.69 65.03 68.83 62.41 72.13 67.13 72.36 70.52 70.73 72.9 75.52 77.09 69.75 61.08 61.01 60.32 64.37 72.39 66.51 71.43 75.5 68.85 65.29 62.69 64.85 70.97 64.58 55.03 64.9 66.21 66.09 69.55 54.87 65.28 82.17 71.75 84.76 77.67 80.89 91.28 62.42 87.14 79.85 88.89 77.56 76.31 80.95 70.23 72.95 78.53 78.64 94.96 92.56 70.2 83.52 70.03 91.6 73.5 82.28 85.92 72.7 73.22 78.5 83.94 86.59 94.25 78.68 74.28 74.87 89.68 80.25 78.35 72.12 81.33 81.71 80.16 71.75 83.95 83 79.19 73.59 77.47 78.46 73.0
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/GaussianProcessesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/GaussianProcessesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/GaussianProcessesTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests GaussianProcesses. Run from the command line with:<p/>
+ * java weka.classifiers.functions.GaussianProcessesTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class GaussianProcessesTest 
+  extends AbstractClassifierTest {
+
+  public GaussianProcessesTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default GaussianProcesses */
+  public Classifier getClassifier() {
+    return new GaussianProcesses();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GaussianProcessesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/IsotonicRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/IsotonicRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/IsotonicRegressionTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests IsotonicRegression. Run from the command line with:<p/>
+ * java weka.classifiers.functions.IsotonicRegressionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class IsotonicRegressionTest 
+  extends AbstractClassifierTest {
+
+  public IsotonicRegressionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default IsotonicRegression */
+  public Classifier getClassifier() {
+    return new IsotonicRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(IsotonicRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/LeastMedSqTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/LeastMedSqTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/LeastMedSqTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LeastMedSq. Run from the command line with:<p>
+ * java weka.classifiers.functions.LeastMedSqTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class LeastMedSqTest extends AbstractClassifierTest {
+
+  public LeastMedSqTest(String name) { super(name);  }
+
+  /** Creates a default LeastMedSq */
+  public Classifier getClassifier() {
+    return new LeastMedSq();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LeastMedSqTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/LibLINEARTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/LibLINEARTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/LibLINEARTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LibLINEAR. Run from the command line with: <p/>
+ * java weka.classifiers.functions.LibLINEARTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class LibLINEARTest 
+  extends AbstractClassifierTest {
+
+  public LibLINEARTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SMO */
+  public Classifier getClassifier() {
+    return new LibLINEAR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LibLINEARTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/LibSVMTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/LibSVMTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/LibSVMTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LibSVM. Run from the command line with: <p/>
+ * java weka.classifiers.functions.LibSVMTest
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class LibSVMTest 
+  extends AbstractClassifierTest {
+
+  public LibSVMTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SMO */
+  public Classifier getClassifier() {
+    return new LibSVM();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LibSVMTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/LinearRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/LinearRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/LinearRegressionTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LinearRegression. Run from the command line with:<p>
+ * java weka.classifiers.functions.LinearRegressionTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class LinearRegressionTest extends AbstractClassifierTest {
+
+  public LinearRegressionTest(String name) { super(name);  }
+
+  /** Creates a default LinearRegression */
+  public Classifier getClassifier() {
+    return new LinearRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LinearRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/LogisticTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/LogisticTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/LogisticTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Logistic. Run from the command line with:<p>
+ * java weka.classifiers.functions.LogisticTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class LogisticTest extends AbstractClassifierTest {
+
+  public LogisticTest(String name) { super(name);  }
+
+  /** Creates a default Logistic */
+  public Classifier getClassifier() {
+    return new Logistic();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LogisticTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronCSTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronCSTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronCSTest.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2009 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NeuralNetworkCS. Run from the command line with:<p>
+ * java weka.classifiers.functions.MultilayerPerceptronTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 6203 $
+ */
+public class MultilayerPerceptronCSTest extends AbstractClassifierTest {
+
+
+  public MultilayerPerceptronCSTest(String name) { super(name);  }
+
+  /** Creates a default ThresholdSelector */
+  public Classifier getClassifier() {
+    return new MultilayerPerceptronCS();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultilayerPerceptronCSTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/MultilayerPerceptronTest.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2001 Malcolm Ware. 
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NeuralNetwork. Run from the command line with:<p>
+ * java weka.classifiers.functions.MultilayerPerceptronTest
+ *
+ * @author <a href="mailto:mfw4@cs.waikato.ac.nz">Malcolm Ware</a>
+ * @version $Revision: 1.2 $
+ */
+public class MultilayerPerceptronTest extends AbstractClassifierTest {
+
+
+  public MultilayerPerceptronTest(String name) { super(name);  }
+
+  /** Creates a default ThresholdSelector */
+  public Classifier getClassifier() {
+    return new MultilayerPerceptron();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultilayerPerceptronTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/PLSClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/PLSClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/PLSClassifierTest.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.CheckClassifier;
+import weka.classifiers.Classifier;
+import weka.core.SelectedTag;
+import weka.filters.supervised.attribute.PLSFilter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PLSClassifier. Run from the command line with:<p/>
+ * java weka.classifiers.functions.PLSClassifierTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class PLSClassifierTest 
+  extends AbstractClassifierTest {
+
+  /** the number of PLS components to generate */
+  public final static int NUM_COMPONENTS = 5;
+  
+  public PLSClassifierTest(String name) { 
+    super(name);  
+  }
+  
+  /**
+   * configures the CheckClassifier instance used throughout the tests
+   * 
+   * @return	the fully configured CheckClassifier instance used for testing
+   */
+  protected CheckClassifier getTester() {
+    CheckClassifier	result;
+    
+    result = super.getTester();
+    result.setNumNominal(NUM_COMPONENTS * 2);
+    result.setNumNumeric(NUM_COMPONENTS * 2);
+    result.setNumString(NUM_COMPONENTS * 2);
+    result.setNumDate(NUM_COMPONENTS * 2);
+    result.setNumRelational(NUM_COMPONENTS * 2);
+    
+    return result;
+  }
+
+  /** Creates a default PLSClassifier */
+  public Classifier getClassifier() {
+    PLSClassifier classifier = new PLSClassifier();
+    
+    PLSFilter filter = new PLSFilter();
+    filter.setReplaceMissing(true);
+    filter.setPreprocessing(new SelectedTag(PLSFilter.PREPROCESSING_CENTER, PLSFilter.TAGS_PREPROCESSING));
+    filter.setNumComponents(NUM_COMPONENTS);
+    filter.setAlgorithm(new SelectedTag(PLSFilter.ALGORITHM_SIMPLS, PLSFilter.TAGS_ALGORITHM));
+    try {
+      classifier.setFilter(filter);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return classifier;
+  }
+
+  public static Test suite() {
+    return new TestSuite(PLSClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/PaceRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/PaceRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/PaceRegressionTest.java	(revision 29)
@@ -0,0 +1,71 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2001 Malcolm Ware. 
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.CheckClassifier;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PaceRegression. Run from the command line with:<p>
+ * java weka.classifiers.functions.PaceRegressionTest
+ *
+ * @author <a href="mailto:mfw4@cs.waikato.ac.nz">Malcolm Ware</a>
+ * @version $Revision: 1.4 $
+ */
+public class PaceRegressionTest 
+  extends AbstractClassifierTest {
+
+  public PaceRegressionTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * configures the CheckClassifier instance used throughout the tests
+   * 
+   * @return	the fully configured CheckClassifier instance used for testing
+   */
+  protected CheckClassifier getTester() {
+    CheckClassifier 	result;
+    
+    result = super.getTester();
+    result.setNumInstances(60);
+    
+    return result;
+  }
+
+  /** Creates a default ThresholdSelector */
+  public Classifier getClassifier() {
+    return new PaceRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PaceRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/RBFNetworkTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/RBFNetworkTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/RBFNetworkTest.java	(revision 29)
@@ -0,0 +1,70 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.CheckClassifier;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RBFNetwork. Run from the command line with:<p/>
+ * java weka.classifiers.functions.RBFNetworkTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class RBFNetworkTest 
+  extends AbstractClassifierTest {
+
+  public RBFNetworkTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * configures the CheckClassifier instance used throughout the tests
+   * 
+   * @return	the fully configured CheckClassifier instance used for testing
+   */
+  protected CheckClassifier getTester() {
+    CheckClassifier 	result;
+    
+    result = super.getTester();
+    result.setNumInstances(40);
+    
+    return result;
+  }
+
+  /** Creates a default RBFNetwork */
+  public Classifier getClassifier() {
+    return new RBFNetwork();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RBFNetworkTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SMO. Run from the command line with:<p>
+ * java weka.classifiers.functions.SMOTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.4 $
+ */
+public class SMOTest extends AbstractClassifierTest {
+
+  public SMOTest(String name) { super(name);  }
+
+  /** Creates a default SMO */
+  public Classifier getClassifier() {
+    return new SMO();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SMOTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOregTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOregTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/SMOregTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SMOreg. Run from the command line with:<p>
+ * java weka.classifiers.functions.SMOregTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class SMOregTest extends AbstractClassifierTest {
+
+  public SMOregTest(String name) { super(name);  }
+
+  /** Creates a default SMOreg */
+  public Classifier getClassifier() {
+    return new SMOreg();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SMOregTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/SPegasosTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/SPegasosTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/SPegasosTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2009 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SPegasos. Run from the command line with:<p>
+ * java weka.classifiers.functions.SPegasosTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 6063 $
+ */
+public class SPegasosTest extends AbstractClassifierTest {
+
+  public SPegasosTest(String name) { super(name);  }
+
+  /** Creates a default SPegasos */
+  public Classifier getClassifier() {
+    SPegasos p = new SPegasos();
+    p.setDontNormalize(true);
+    p.setDontReplaceMissing(true);
+    p.setEpochs(1);
+    return p;
+  }
+
+  public static Test suite() {
+    return new TestSuite(SPegasosTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLinearRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLinearRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLinearRegressionTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SimpleLinearRegression. Run from the command line with:<p/>
+ * java weka.classifiers.functions.SimpleLinearRegressionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class SimpleLinearRegressionTest 
+  extends AbstractClassifierTest {
+
+  public SimpleLinearRegressionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleLinearRegression */
+  public Classifier getClassifier() {
+    return new SimpleLinearRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SimpleLinearRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLogisticTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLogisticTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/SimpleLogisticTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SimpleLogistic. Run from the command line with:<p/>
+ * java weka.classifiers.functions.SimpleLogisticTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class SimpleLogisticTest 
+  extends AbstractClassifierTest {
+
+  public SimpleLogisticTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleLogistic */
+  public Classifier getClassifier() {
+    return new SimpleLogistic();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SimpleLogisticTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/VotedPerceptronTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/VotedPerceptronTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/VotedPerceptronTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests VotedPerceptron. Run from the command line with:<p>
+ * java weka.classifiers.functions.VotedPerceptronTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class VotedPerceptronTest extends AbstractClassifierTest {
+
+  public VotedPerceptronTest(String name) { super(name);  }
+
+  /** Creates a default VotedPerceptron */
+  public Classifier getClassifier() {
+    return new VotedPerceptron();
+  }
+
+  public static Test suite() {
+    return new TestSuite(VotedPerceptronTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/WinnowTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/WinnowTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/WinnowTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.functions;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Winnow. Run from the command line with:<p>
+ * java weka.classifiers.functions.WinnowTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class WinnowTest extends AbstractClassifierTest {
+
+  public WinnowTest(String name) { super(name);  }
+
+  /** Creates a default Winnow */
+  public Classifier getClassifier() {
+    return new Winnow();
+  }
+
+  public static Test suite() {
+    return new TestSuite(WinnowTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AbstractKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AbstractKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AbstractKernelTest.java	(revision 29)
@@ -0,0 +1,873 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.core.Attribute;
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.CheckScheme.PostProcessor;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Kernels. Internally it uses the class
+ * <code>CheckKernel</code> to determine success or failure of the
+ * tests. It follows basically the <code>testsPerClassType</code> method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ *
+ * @see CheckKernel
+ * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+ * @see PostProcessor
+ */
+public abstract class AbstractKernelTest 
+  extends TestCase {
+  
+  /** The Kernel to be tested */
+  protected Kernel m_Kernel;
+
+  /** For testing the Kernel */
+  protected CheckKernel m_Tester;
+
+  /** whether Kernel handles weighted instances */
+  protected boolean m_weightedInstancesHandler;
+
+  /** whether Kernel handles multi-instance data */
+  protected boolean m_multiInstanceHandler;
+
+  /** the number of classes to test with testNClasses() 
+   * @see #testNClasses() */
+  protected int m_NClasses;
+
+  /** whether to run CheckKernel in DEBUG mode */
+  protected boolean DEBUG = false;
+
+  /** the attribute type with the lowest value */
+  protected final static int FIRST_CLASSTYPE = Attribute.NUMERIC;
+
+  /** the attribute type with the highest value */
+  protected final static int LAST_CLASSTYPE = Attribute.RELATIONAL;
+  
+  /** wether Kernel can predict nominal attributes (array index is attribute type of class) */
+  protected boolean[] m_NominalPredictors;
+  
+  /** wether Kernel can predict numeric attributes (array index is attribute type of class) */
+  protected boolean[] m_NumericPredictors;
+  
+  /** wether Kernel can predict string attributes (array index is attribute type of class) */
+  protected boolean[] m_StringPredictors;
+  
+  /** wether Kernel can predict date attributes (array index is attribute type of class) */
+  protected boolean[] m_DatePredictors;
+  
+  /** wether Kernel can predict relational attributes (array index is attribute type of class) */
+  protected boolean[] m_RelationalPredictors;
+  
+  /** whether Kernel handles missing values */
+  protected boolean[] m_handleMissingPredictors;
+
+  /** whether Kernel handles class with only missing values */
+  protected boolean[] m_handleMissingClass;
+
+  /** whether Kernel handles class as first attribute */
+  protected boolean[] m_handleClassAsFirstAttribute;
+
+  /** whether Kernel handles class as second attribute */
+  protected boolean[] m_handleClassAsSecondAttribute;
+  
+  /** the results of the regression tests */
+  protected String[] m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractKernelTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractKernelTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns a custom PostProcessor for the CheckKernel datasets, currently
+   * only null.
+   * 
+   * @return		a custom PostProcessor, if necessary
+   * @see PostProcessor
+   */
+  protected PostProcessor getPostProcessor() {
+    return null;
+  }
+  
+  /**
+   * configures the CheckKernel instance used throughout the tests
+   * 
+   * @return	the fully configured CheckKernel instance used for testing
+   */
+  protected CheckKernel getTester() {
+    CheckKernel	result;
+    
+    result = new CheckKernel();
+    result.setSilent(true);
+    result.setKernel(m_Kernel);
+    result.setNumInstances(20);
+    result.setDebug(DEBUG);
+    result.setPostProcessor(getPostProcessor());
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the Kernel return from the getKernel() method.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getKernel()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (getKernel() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getKernel());
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Kernel returned from the getKernel() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getKernel()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getKernel());
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default Kernel to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Kernel       = getKernel();
+    m_Tester       = getTester();
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+
+    m_weightedInstancesHandler     = m_Tester.weightedInstancesHandler()[0];
+    m_multiInstanceHandler         = m_Tester.multiInstanceHandler()[0];
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new String[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+
+    // initialize attributes
+    checkAttributes(true,  false, false, false, false, false);
+    checkAttributes(false, true,  false, false, false, false);
+    checkAttributes(false, false, true,  false, false, false);
+    checkAttributes(false, false, false, true,  false, false);
+    checkAttributes(false, false, false, false, true,  false);
+    
+    // initialize missing values handling
+    for (int i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the scheme support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      m_handleMissingPredictors[i] = checkMissingPredictors(i, 20, false);
+      m_handleMissingClass[i]      = checkMissingClass(i, 20, false);
+    }
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Kernel       = null;
+    m_Tester       = null;
+    m_OptionTester = null;
+    m_GOETester    = null;
+
+    m_weightedInstancesHandler     = false;
+    m_NominalPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_NumericPredictors            = new boolean[LAST_CLASSTYPE + 1];
+    m_StringPredictors             = new boolean[LAST_CLASSTYPE + 1];
+    m_DatePredictors               = new boolean[LAST_CLASSTYPE + 1];
+    m_RelationalPredictors         = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingPredictors      = new boolean[LAST_CLASSTYPE + 1];
+    m_handleMissingClass           = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsFirstAttribute  = new boolean[LAST_CLASSTYPE + 1];
+    m_handleClassAsSecondAttribute = new boolean[LAST_CLASSTYPE + 1];
+    m_RegressionResults            = new String[LAST_CLASSTYPE + 1];
+    m_NClasses                     = 4;
+  }
+
+  /**
+   * Used to create an instance of a specific Kernel.
+   *
+   * @return a suitably configured <code>Kernel</code> value
+   */
+  public abstract Kernel getKernel();
+
+  /**
+   * checks whether at least one attribute type can be handled with the
+   * given class type
+   *
+   * @param type      the class type to check for
+   * @return          true if at least one attribute type can be predicted with
+   *                  the given class
+   */
+  protected boolean canPredict(int type) {
+    return    m_NominalPredictors[type]
+           || m_NumericPredictors[type]
+           || m_StringPredictors[type]
+           || m_DatePredictors[type]
+           || m_RelationalPredictors[type];
+  }
+
+  /** 
+   * returns a string for the class type
+   * 
+   * @param type        the class type
+   * @return            the class type as string
+   */
+  protected String getClassTypeString(int type) {
+    return CheckKernel.attributeTypeToString(type);
+  }
+
+  /**
+   * tests whether the Kernel can handle certain attributes and if not,
+   * if the exception is OK
+   *
+   * @param nom         to check for nominal attributes
+   * @param num         to check for numeric attributes
+   * @param str         to check for string attributes
+   * @param dat         to check for date attributes
+   * @param rel         to check for relational attributes
+   * @param allowFail   whether a junit fail can be executed
+   * @see CheckKernel#canPredict(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  protected void checkAttributes(boolean nom, boolean num, boolean str, 
+                                 boolean dat, boolean rel,
+                                 boolean allowFail) {
+    boolean[]     result;
+    String        att;
+    int           i;
+
+    // determine text for type of attributes
+    att = "";
+    if (nom)
+      att = "nominal";
+    else if (num)
+      att = "numeric";
+    else if (str)
+      att = "string";
+    else if (dat)
+      att = "date";
+    else if (rel)
+      att = "relational";
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      result = m_Tester.canPredict(nom, num, str, dat, rel, m_multiInstanceHandler, i);
+      if (nom)
+        m_NominalPredictors[i] = result[0];
+      else if (num)
+        m_NumericPredictors[i] = result[0];
+      else if (str)
+        m_StringPredictors[i] = result[0];
+      else if (dat)
+        m_DatePredictors[i] = result[0];
+      else if (rel)
+        m_RelationalPredictors[i] = result[0];
+
+      if (!result[0] && !result[1] && allowFail)
+        fail("Error handling " + att + " attributes (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the Kernel can handle different types of attributes and
+   * if not, if the exception is OK
+   *
+   * @see #checkAttributes(boolean, boolean, boolean, boolean, boolean, boolean)
+   */
+  public void testAttributes() {
+    // nominal
+    checkAttributes(true,  false, false, false, false, true);
+    // numeric
+    checkAttributes(false, true,  false, false, false, true);
+    // string
+    checkAttributes(false, false, true,  false, false, true);
+    // date
+    checkAttributes(false, false, false, true,  false, true);
+    // relational
+    if (!m_multiInstanceHandler)
+      checkAttributes(false, false, false, false, true,  true);
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean[]     result;
+
+    result = m_Tester.declaresSerialVersionUID();
+
+    if (!result[0])
+      fail("Doesn't declare serialVersionUID!");
+  }
+
+  /**
+   * tests whether the Kernel handles instance weights correctly
+   *
+   * @see CheckKernel#instanceWeights(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testInstanceWeights() {
+    boolean[]     result;
+    int           i;
+    
+    if (m_weightedInstancesHandler) {
+      for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+        // does the Kernel support this type of class at all?
+        if (!canPredict(i))
+          continue;
+        
+        result = m_Tester.instanceWeights(
+            m_NominalPredictors[i], 
+            m_NumericPredictors[i], 
+            m_StringPredictors[i], 
+            m_DatePredictors[i], 
+            m_RelationalPredictors[i], 
+            m_multiInstanceHandler, 
+            i);
+
+        if (!result[0])
+          System.err.println("Error handling instance weights (" + getClassTypeString(i) 
+              + " class)!");
+      }
+    }
+  }
+
+  /**
+   * tests whether Kernel handles N classes
+   *
+   * @see CheckKernel#canHandleNClasses(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   * @see #m_NClasses
+   */
+  public void testNClasses() {
+    boolean[]     result;
+
+    if (!canPredict(Attribute.NOMINAL))
+      return;
+
+    result = m_Tester.canHandleNClasses(
+        m_NominalPredictors[Attribute.NOMINAL],
+        m_NumericPredictors[Attribute.NOMINAL],
+        m_StringPredictors[Attribute.NOMINAL],
+        m_DatePredictors[Attribute.NOMINAL],
+        m_RelationalPredictors[Attribute.NOMINAL],
+        m_multiInstanceHandler,
+        m_NClasses);
+
+    if (!result[0] && !result[1])
+      fail("Error handling " + m_NClasses + " classes!");
+  }
+
+  /**
+   * checks whether the Kernel can handle the class attribute at a given
+   * position (0-based index, -1 means last).
+   *
+   * @param type        the class type
+   * @param position	the position of the class attribute (0-based, -1 means last)
+   * @return            true if the Kernel can handle it
+   */
+  protected boolean checkClassAsNthAttribute(int type, int position) {
+    boolean[]     result;
+    String	  indexStr;
+    
+    result = m_Tester.canHandleClassAsNthAttribute(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        position);
+
+    if (position == -1)
+      indexStr = "last";
+    else
+      indexStr = (position + 1) + ".";
+    
+    if (!result[0] && !result[1])
+      fail("Error handling class as " + indexStr + " attribute (" 
+          + getClassTypeString(type) + " class)!");
+    
+    return result[0];
+  }
+
+  /**
+   * Tests whether the Kernel can handle class attributes as Nth
+   * attribute. In case of multi-instance Kernels it performs no tests,
+   * since the multi-instance data has a fixed format (bagID,bag,class).
+   *
+   * @see CheckKernel#canHandleClassAsNthAttribute(boolean, boolean, boolean, boolean, boolean, boolean, int, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testClassAsNthAttribute() {
+    int           i;
+    
+    // multi-Instance data has fixed format!
+    if (m_multiInstanceHandler)
+      return;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // first attribute
+      m_handleClassAsFirstAttribute[i] = checkClassAsNthAttribute(i, 0);
+
+      // second attribute
+      m_handleClassAsSecondAttribute[i] = checkClassAsNthAttribute(i, 1);
+    }
+  }
+
+  /**
+   * tests whether the Kernel can handle zero training instances
+   *
+   * @see CheckKernel#canHandleZeroTraining(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testZeroTraining() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.canHandleZeroTraining(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Error handling zero training instances (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * checks whether the Kernel can handle the given percentage of
+   * missing predictors
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing predictors
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the Kernel can handle it
+   */
+  protected boolean checkMissingPredictors(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        true,
+        false,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing predictors (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the Kernel can handle missing predictors (20% and 100%)
+   *
+   * @see CheckKernel#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingPredictors() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingPredictors(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingPredictors[i])
+        checkMissingPredictors(i, 100, true);
+    }
+  }
+
+  /**
+   * checks whether the Kernel can handle the given percentage of
+   * missing class labels
+   *
+   * @param type        the class type
+   * @param percent     the percentage of missing class labels
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the Kernel can handle it
+   */
+  protected boolean checkMissingClass(int type, int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors[type], 
+        m_NumericPredictors[type], 
+        m_StringPredictors[type], 
+        m_DatePredictors[type], 
+        m_RelationalPredictors[type], 
+        m_multiInstanceHandler, 
+        type,
+        false,
+        true,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing class labels (" 
+	    + getClassTypeString(type) + " class)!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the Kernel can handle missing class values (20% and
+   * 100%)
+   *
+   * @see CheckKernel#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testMissingClass() {
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      // 20% missing
+      checkMissingClass(i, 20, true);
+
+      // 100% missing
+      if (m_handleMissingClass[i])
+        checkMissingClass(i, 100, true);
+    }
+  }
+
+  /**
+   * tests whether the Kernel correctly initializes in the
+   * buildKernel method
+   *
+   * @see CheckKernel#correctBuildInitialisation(boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testBuildInitialization() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.correctBuildInitialisation(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i);
+
+      if (!result[0] && !result[1])
+        fail("Incorrect build initialization (" + getClassTypeString(i) 
+            + " class)!");
+    }
+  }
+
+  /**
+   * tests whether the Kernel alters the training set during training.
+   *
+   * @see CheckKernel#datasetIntegrity(boolean, boolean, boolean, boolean, boolean, boolean, int, boolean, boolean)
+   * @see CheckKernel#testsPerClassType(int, boolean, boolean)
+   */
+  public void testDatasetIntegrity() {
+    boolean[]     result;
+    int           i;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+      
+      result = m_Tester.datasetIntegrity(
+          m_NominalPredictors[i], 
+          m_NumericPredictors[i], 
+          m_StringPredictors[i], 
+          m_DatePredictors[i], 
+          m_RelationalPredictors[i], 
+          m_multiInstanceHandler, 
+          i,
+          m_handleMissingPredictors[i],
+          m_handleMissingClass[i]);
+
+      if (!result[0] && !result[1])
+        fail("Training set is altered during training (" 
+            + getClassTypeString(i) + " class)!");
+    }
+  }
+
+  /**
+   * Builds a model using the current Kernel using the given data and 
+   * returns the produced output.
+   *
+   * @param data 	the instances to test the Kernel on
+   * @return 		a String containing the output of the Kernel.
+   */
+  protected String useKernel(Instances data) throws Exception {
+    Kernel kernel = null;
+    StringBuffer text = new StringBuffer();
+    
+    try {
+      kernel = Kernel.makeCopy(m_Kernel);
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Problem setting up to use Kernel: " + e);
+    }
+
+    kernel.buildKernel(data);
+    for (int n = 0; n < data.numInstances(); n++) {
+      for (int i = n; i < data.numInstances(); i++) {
+	text.append(
+	    (n+1) + "-" + (i+1) + ": " 
+	    + kernel.eval(n, i, data.instance(i)) + "\n");
+      }
+    }
+    
+    return text.toString();
+  }
+  
+  /**
+   * Provides a hook for derived classes to further modify the data. Currently,
+   * the data is just passed through.
+   * 
+   * @param data	the data to process
+   * @return		the processed data
+   */
+  protected Instances process(Instances data) {
+    return data;
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() throws Exception {
+    int		i;
+    boolean	succeeded;
+    Regression 	reg;
+    Instances   train;
+    
+    // don't bother if not working correctly
+    if (m_Tester.hasClasspathProblems())
+      return;
+    
+    reg = new Regression(this.getClass());
+    succeeded = false;
+    train = null;
+    
+    for (i = FIRST_CLASSTYPE; i <= LAST_CLASSTYPE; i++) {
+      // does the Kernel support this type of class at all?
+      if (!canPredict(i))
+        continue;
+        
+      train = m_Tester.makeTestDataset(
+          42, m_Tester.getNumInstances(), 
+  	  m_NominalPredictors[i] ? 2 : 0,
+  	  m_NumericPredictors[i] ? 1 : 0, 
+          m_StringPredictors[i] ? 1 : 0,
+          m_DatePredictors[i] ? 1 : 0,
+          m_RelationalPredictors[i] ? 1 : 0,
+          2, 
+          i,
+          m_multiInstanceHandler);
+  
+      try {
+        m_RegressionResults[i] =   CheckKernel.attributeTypeToString(i) 
+        			 + " class:\n" 
+        			 + useKernel(train);
+        succeeded = true;
+        reg.println(m_RegressionResults[i]);
+      }
+      catch (Exception e) {
+	String msg = e.getMessage().toLowerCase();
+	if (msg.indexOf("not in classpath") > -1)
+	  return;
+
+	m_RegressionResults[i] = null;
+      }
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful predictions for any class type");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/AllTests.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all supportVector classes. Run from the command line with:
+ * <p/>
+ * java weka.classifiers.functions.supportVector.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite result = new TestSuite();
+
+    result.addTest(suite("weka.classifiers.functions.supportVector.Kernel"));
+    // more...
+    
+    return result;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/NormalizedPolyKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/NormalizedPolyKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/NormalizedPolyKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NormalizedPolyKernel. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.NormalizedPolyKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NormalizedPolyKernelTest 
+  extends AbstractKernelTest {
+
+  public NormalizedPolyKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NormalizedPolyKernel */
+  public Kernel getKernel() {
+    return new NormalizedPolyKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NormalizedPolyKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PolyKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PolyKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PolyKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PolyKernel. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.PolyKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class PolyKernelTest 
+  extends AbstractKernelTest {
+
+  public PolyKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default PolyKernel */
+  public Kernel getKernel() {
+    return new PolyKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PolyKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PrecomputedKernelMatrixKernelTest.java	(revision 29)
@@ -0,0 +1,71 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+import weka.core.matrix.Matrix;
+
+import java.io.File;
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PrecomputedKernelMatrixKernel. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.PrecomputedKernelMatrixKernelTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
+ * @version $Revision: 1.1 $
+ */
+public class PrecomputedKernelMatrixKernelTest 
+  extends AbstractKernelTest {
+
+  public PrecomputedKernelMatrixKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default PrecomputedKernelMatrixKernell */
+  public Kernel getKernel() {
+    PrecomputedKernelMatrixKernel pc = new PrecomputedKernelMatrixKernel();
+
+    // load kernel matrix
+    try {
+      pc.setKernelMatrix(
+         new Matrix(
+            new InputStreamReader(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/data/test.matrix"))));
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return pc;
+  }
+
+  public static Test suite() {
+    return new TestSuite(PrecomputedKernelMatrixKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PukTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PukTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/PukTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Puk. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.PukTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class PukTest 
+  extends AbstractKernelTest {
+
+  public PukTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Puk */
+  public Kernel getKernel() {
+    return new Puk();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PukTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/RBFKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/RBFKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/RBFKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RBFKernel. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.RBFKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RBFKernelTest 
+  extends AbstractKernelTest {
+
+  public RBFKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RBFKernel */
+  public Kernel getKernel() {
+    return new RBFKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RBFKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/StringKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/StringKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/functions/supportVector/StringKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.functions.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests StringKernel. Run from the command line with:<p/>
+ * java weka.classifiers.functions.supportVector.StringKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class StringKernelTest 
+  extends AbstractKernelTest {
+
+  public StringKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default StringKernel */
+  public Kernel getKernel() {
+    return new StringKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(StringKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/lazy/IB1Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/lazy/IB1Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/lazy/IB1Test.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests IB1. Run from the command line with:<p>
+ * java weka.classifiers.lazy.IB1Test
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class IB1Test extends AbstractClassifierTest {
+
+  public IB1Test(String name) { super(name);  }
+
+  /** Creates a default IB1 */
+  public Classifier getClassifier() {
+    return new IB1();
+  }
+
+  public static Test suite() {
+    return new TestSuite(IB1Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/lazy/IBkTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/lazy/IBkTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/lazy/IBkTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests IBk. Run from the command line with:<p>
+ * java weka.classifiers.lazy.IBkTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class IBkTest extends AbstractClassifierTest {
+
+  public IBkTest(String name) { super(name);  }
+
+  /** Creates a default IBk */
+  public Classifier getClassifier() {
+    return new IBk();
+  }
+
+  public static Test suite() {
+    return new TestSuite(IBkTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/lazy/KStarTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/lazy/KStarTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/lazy/KStarTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests KStar. Run from the command line with:<p>
+ * java weka.classifiers.lazy.KStarTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class KStarTest extends AbstractClassifierTest {
+
+  public KStarTest(String name) { super(name);  }
+
+  /** Creates a default KStar */
+  public Classifier getClassifier() {
+    return new KStar();
+  }
+
+  public static Test suite() {
+    return new TestSuite(KStarTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/lazy/LBRTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/lazy/LBRTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/lazy/LBRTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LBR. Run from the command line with:<p/>
+ * java weka.classifiers.lazy.LBRTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class LBRTest 
+  extends AbstractClassifierTest {
+
+  public LBRTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default LBR */
+  public Classifier getClassifier() {
+    return new LBR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LBRTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/lazy/LWLTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/lazy/LWLTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/lazy/LWLTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.lazy;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LWL. Run from the command line with:<p>
+ * java weka.classifiers.lazy.LWLTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class LWLTest extends AbstractClassifierTest {
+
+  public LWLTest(String name) { super(name);  }
+
+  /** Creates a default LWL */
+  public Classifier getClassifier() {
+    return new LWL();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LWLTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/AdaBoostM1Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/AdaBoostM1Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/AdaBoostM1Test.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AdaBoostM1. Run from the command line with:<p>
+ * java weka.classifiers.meta.AdaBoostM1Test
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class AdaBoostM1Test extends AbstractClassifierTest {
+
+  public AdaBoostM1Test(String name) { super(name);  }
+
+  /** Creates a default AdaBoostM1 */
+  public Classifier getClassifier() {
+    return new AdaBoostM1();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AdaBoostM1Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/AdditiveRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/AdditiveRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/AdditiveRegressionTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AdditiveRegression. Run from the command line with:<p>
+ * java weka.classifiers.meta.AdditiveRegressionTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class AdditiveRegressionTest extends AbstractClassifierTest {
+
+  public AdditiveRegressionTest(String name) { super(name);  }
+
+  /** Creates a default AdditiveRegression */
+  public Classifier getClassifier() {
+    return new AdditiveRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AdditiveRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/AttributeSelectedClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/AttributeSelectedClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/AttributeSelectedClassifierTest.java	(revision 29)
@@ -0,0 +1,77 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002-2006 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.core.CheckOptionHandler;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AttributeSelectedClassifier. Run from the command line with:<p>
+ * java weka.classifiers.meta.AttributeSelectedClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AttributeSelectedClassifierTest
+  extends AbstractClassifierTest {
+
+  public AttributeSelectedClassifierTest(String name) { 
+    super(name);
+  }
+
+  /** Creates a default AttributeSelectedClassifier */
+  public Classifier getClassifier() {
+    return new AttributeSelectedClassifier();
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the classifier return from the getClassifier() method.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getClassifier()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = super.getOptionTester();
+    result.setUserOptions(new String[]{
+	"-E",
+	"weka.attributeSelection.CfsSubsetEval",
+	"-S",
+	"weka.attributeSelection.BestFirst"});
+    
+    return result;
+  }
+
+  public static Test suite() {
+    return new TestSuite(AttributeSelectedClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/BaggingTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/BaggingTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/BaggingTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Bagging. Run from the command line with:<p>
+ * java weka.classifiers.meta.BaggingTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class BaggingTest extends AbstractClassifierTest {
+
+  public BaggingTest(String name) { super(name);  }
+
+  /** Creates a default Bagging */
+  public Classifier getClassifier() {
+    return new Bagging();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BaggingTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/CVParameterSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/CVParameterSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/CVParameterSelectionTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CVParameterSelection. Run from the command line with:<p>
+ * java weka.classifiers.meta.CVParameterSelectionTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class CVParameterSelectionTest extends AbstractClassifierTest {
+
+  public CVParameterSelectionTest(String name) { super(name);  }
+
+  /** Creates a default CVParameterSelection */
+  public Classifier getClassifier() {
+    return new CVParameterSelection();
+  }
+
+  public static Test suite() {
+    return new TestSuite(CVParameterSelectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaClusteringTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaClusteringTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaClusteringTest.java	(revision 29)
@@ -0,0 +1,67 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClassificationViaClustering. Run from the command line with:<p/>
+ * java weka.classifiers.meta.ClassificationViaClusteringTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class ClassificationViaClusteringTest 
+  extends AbstractClassifierTest {
+
+  public ClassificationViaClusteringTest(String name) { 
+    super(name);  
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Tester.setNumInstances(100);
+  }
+
+  /** Creates a default ClassificationViaClustering */
+  public Classifier getClassifier() {
+    return new ClassificationViaClustering();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassificationViaClusteringTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/ClassificationViaRegressionTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClassificationViaRegression. Run from the command line with:<p>
+ * java weka.classifiers.meta.ClassificationViaRegressionTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class ClassificationViaRegressionTest extends AbstractClassifierTest {
+
+  public ClassificationViaRegressionTest(String name) { super(name);  }
+
+  /** Creates a default ClassificationViaRegression */
+  public Classifier getClassifier() {
+    return new ClassificationViaRegression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassificationViaRegressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/CostSensitiveClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/CostSensitiveClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/CostSensitiveClassifierTest.java	(revision 29)
@@ -0,0 +1,85 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.classifiers.CostMatrix;
+
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CostSensitiveClassifier. Run from the command line with:<p>
+ * java weka.classifiers.meta.CostSensitiveClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.5 $
+ */
+public class CostSensitiveClassifierTest extends AbstractClassifierTest {
+
+  public CostSensitiveClassifierTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // can handle only as much classes as there are in the CostMatrix!
+    m_NClasses = ((CostSensitiveClassifier) getClassifier()).getCostMatrix().numRows();
+  }
+
+  /** Creates a default CostSensitiveClassifier */
+  public Classifier getClassifier() {
+
+    CostSensitiveClassifier cl = new CostSensitiveClassifier();
+    
+    // load costmatrix
+    try {
+      cl.setCostMatrix(
+          new CostMatrix(
+            new InputStreamReader(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/data/ClassifierTest.cost"))));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return cl;
+  }
+
+  public static Test suite() {
+    return new TestSuite(CostSensitiveClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/DaggingTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/DaggingTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/DaggingTest.java	(revision 29)
@@ -0,0 +1,67 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Dagging. Run from the command line with:<p/>
+ * java weka.classifiers.meta.DaggingTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class DaggingTest 
+  extends AbstractClassifierTest {
+
+  public DaggingTest(String name) { 
+    super(name);  
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Tester.setNumInstances(40);
+  }
+
+  /** Creates a default Dagging */
+  public Classifier getClassifier() {
+    return new Dagging();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DaggingTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/DecorateTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/DecorateTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/DecorateTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Decorate. Run from the command line with:<p/>
+ * java weka.classifiers.meta.DecorateTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class DecorateTest 
+  extends AbstractClassifierTest {
+
+  public DecorateTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Decorate */
+  public Classifier getClassifier() {
+    return new Decorate();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DecorateTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/ENDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/ENDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/ENDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests END. Run from the command line with:<p/>
+ * java weka.classifiers.meta.ENDTest
+ *
+ * @author eibe
+ * @version $Revision: 1.3 $
+ */
+public class ENDTest 
+  extends AbstractClassifierTest {
+
+  public ENDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default END */
+  public Classifier getClassifier() {
+    return new END();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ENDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.java	(revision 29)
@@ -0,0 +1,227 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.classifiers.meta.ensembleSelection.EnsembleSelectionLibrary;
+
+import java.io.File;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests EnsembleSelection. Run from the command line with:<p/>
+ * java weka.classifiers.meta.EnsembleSelectionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class EnsembleSelectionTest 
+  extends AbstractClassifierTest {
+
+  /** Default root location, relative to the users home direcory. */
+  private final static String DEFAULT_ROOT = "ensembleSelection/setup-1";
+
+  /**
+   * The name of the system property that can be used to override the
+   * location of the reference root.
+   */
+  private static final String ROOT_PROPERTY = "weka.classifiers.meta.EnsembleSelection.root";
+
+  /** Stores the root location under which output files are stored. */
+  private static File ROOT;
+
+  /** the test example setup */
+  private final static String DEFAULT_SETUP = "weka/classifiers/meta/EnsembleSelectionTest.model.xml";
+
+  /**
+   * default constructor
+   *
+   * @param name    the name
+   */
+  public EnsembleSelectionTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Returns a <code>File</code> corresponding to the root of the ensemble
+   * output directory.
+   *
+   * @return the ensemble root directory (always the first one). 
+   */
+  private static File getRoot() {
+    if (ROOT == null) {
+      String root = System.getProperty(ROOT_PROPERTY);
+      if (root == null) {
+        root = System.getProperty("user.dir");
+        ROOT = new File(root, DEFAULT_ROOT);
+      }
+      else {
+        ROOT = new File(root);
+      }
+    }
+
+    return ROOT;
+  }
+
+  /**
+   * returns the root directory with the specified index
+   * 
+   * @param index	the index for the root dir
+   * @return		the File representing the root dir
+   */
+  private static File getRoot(int index) {
+    File	result;
+    File	root;
+    
+    root   = getRoot();
+    result = new File(root.getAbsolutePath().replaceAll("-[0-9]*$", "-" + index));
+    
+    return result;
+  }
+  
+  /**
+   * returns the next available root directory
+   * 
+   * @return		the next available root directory
+   */
+  private static File getNextRoot() {
+    int		i;
+    File	result;
+    
+    i = 0;
+    do {
+      i++;
+      result = getRoot(i);
+    }
+    while (result.exists());
+    
+    return result;
+  }
+  
+  /**
+   * returns the next available root directory and creates it if it doesn't
+   * exist yet
+   * 
+   * @return		the next available root directory
+   */
+  private static File getNextRoot(boolean create) {
+    File	result;
+    
+    result = getNextRoot();
+    
+    if (!result.exists() && create)
+      result.mkdirs();
+    
+    return result;
+  }
+  
+  /** 
+   * Deletes all files and subdirectories under dir.  
+   * Returns true if all deletions were successful.
+   * If a deletion fails, the method stops attempting 
+   * to delete and returns false.
+   * 
+   * @param dir		the directory to delete
+   * @return		true if successful
+   */
+  public static boolean deleteDir(File dir) {
+    int		i;
+    File[]	files;
+    boolean	ok;
+    
+    if (dir.isDirectory()) {
+      files = dir.listFiles();
+      for (i = 0; i < files.length; i++) {
+	ok = deleteDir(files[i]);
+	// problem deleting directory?
+	if (!ok)
+	  return false;
+      }
+    }
+    
+    // The directory is now empty so delete it
+    return dir.delete();
+  }
+
+  /**
+   * removes all the temporary directories created during a test run
+   */
+  private void deleteDirs() throws Exception {
+    File	root;
+    int		i;
+    
+    root = getRoot();
+    if (root.exists()) {
+      i = 1;
+      do {
+	if (!deleteDir(root))
+	  System.out.println(
+	      "Couldn't delete output directory '" + root + "'!");
+	i++;
+	root = getRoot(i);
+      }
+      while (root.exists());
+    }
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    // delete output directories
+    deleteDirs();
+  }
+
+  /** Creates a default EnsembleSelection */
+  public Classifier getClassifier() {
+    EnsembleSelection   cls;
+
+    cls = new EnsembleSelection();
+    cls.setWorkingDirectory(getNextRoot(true));
+    try {
+      cls.setLibrary(
+	  new EnsembleSelectionLibrary(
+	      ClassLoader.getSystemResourceAsStream(DEFAULT_SETUP)));
+    }
+    catch (Exception e) {
+      cls.setLibrary(null);
+      e.printStackTrace();
+    }
+
+    return cls;
+  }
+
+  public static Test suite() {
+    return new TestSuite(EnsembleSelectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.model.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.model.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/EnsembleSelectionTest.model.xml	(revision 29)
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Test setup for EnsembleSelection tests.
+
+  author:  FracPete (fracpete at waikato dot ac dot nz)
+  version: $Revision: 1.1 $
+-->
+
+<!DOCTYPE object
+[
+   <!ELEMENT object (#PCDATA | object)*>
+   <!ATTLIST object name      CDATA #REQUIRED>
+   <!ATTLIST object class     CDATA #REQUIRED>
+   <!ATTLIST object primitive CDATA "no">
+   <!ATTLIST object array     CDATA "no">   <!-- the dimensions of the array; no=0, yes=1 -->
+   <!ATTLIST object null      CDATA "no">
+   <!ATTLIST object version   CDATA "3.5.3">
+]
+>
+
+<object class="java.util.Vector" name="__root__" version="3.5.3">
+   <object class="weka.classifiers.rules.ZeroR" name="0">
+      <object array="yes" class="java.lang.String" name="options"/>
+   </object>
+   <object class="weka.classifiers.rules.ZeroR" name="1">
+      <object array="yes" class="java.lang.String" name="options">
+         <object class="java.lang.String" name="0">-D</object>
+      </object>
+   </object>
+</object>
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/FilteredClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/FilteredClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/FilteredClassifierTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FilteredClassifier. Run from the command line with:<p>
+ * java weka.classifiers.meta.FilteredClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class FilteredClassifierTest extends AbstractClassifierTest {
+
+  public FilteredClassifierTest(String name) { super(name);  }
+
+  /** Creates a default FilteredClassifier */
+  public Classifier getClassifier() {
+    return new FilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FilteredClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/GradingTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/GradingTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/GradingTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Grading. Run from the command line with:<p/>
+ * java weka.classifiers.meta.GradingTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class GradingTest 
+  extends AbstractClassifierTest {
+
+  public GradingTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Grading */
+  public Classifier getClassifier() {
+    return new Grading();
+  }
+
+  public static Test suite() {
+    return new TestSuite(GradingTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/GridSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/GridSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/GridSearchTest.java	(revision 29)
@@ -0,0 +1,100 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.CheckClassifier;
+import weka.classifiers.Classifier;
+import weka.core.SelectedTag;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests GridSearch. Run from the command line with:<p/>
+ * java weka.classifiers.meta.GridSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class GridSearchTest 
+  extends AbstractClassifierTest {
+
+  public GridSearchTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a GridSearch with J48 + unsup. Discretize filter sinze the random
+   * test dataset always results in singular matrix in LU decomposition of
+   * the PLSFilter.
+   * 
+   * @return		the configured classifier
+   */
+  public Classifier getClassifier() {
+    GridSearch		result;
+    
+    result = new GridSearch();
+    
+    result.setEvaluation(new SelectedTag(GridSearch.EVALUATION_ACC, GridSearch.TAGS_EVALUATION));
+    
+    // classifier
+    result.setClassifier(new weka.classifiers.trees.J48());
+    result.setYProperty("classifier.confidenceFactor");
+    result.setYMin(0.2);
+    result.setYMax(0.4);
+    result.setYStep(0.1);
+    result.setYExpression("I");
+    
+    // filter
+    result.setFilter(new weka.filters.unsupervised.attribute.Discretize());
+    result.setXProperty("filter.bins");
+    result.setXMin(2);
+    result.setXMax(10);
+    result.setXStep(2);
+    result.setXExpression("I");
+    
+    return result;
+  }
+  
+  /**
+   * configures the CheckClassifier instance used throughout the tests
+   * 
+   * @return	the fully configured CheckClassifier instance used for testing
+   */
+  protected CheckClassifier getTester() {
+    CheckClassifier	result;
+    
+    result = super.getTester();
+    result.setNumNumeric(7);
+    result.setNumInstances(100);
+    
+    return result;
+  }
+
+  public static Test suite() {
+    return new TestSuite(GridSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/LogitBoostTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/LogitBoostTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/LogitBoostTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LogitBoost. Run from the command line with:<p>
+ * java weka.classifiers.meta.LogitBoostTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class LogitBoostTest extends AbstractClassifierTest {
+
+  public LogitBoostTest(String name) { super(name);  }
+
+  /** Creates a default LogitBoost */
+  public Classifier getClassifier() {
+    return new LogitBoost();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LogitBoostTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/MetaCostTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/MetaCostTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/MetaCostTest.java	(revision 29)
@@ -0,0 +1,85 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.classifiers.CostMatrix;
+
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MetaCost. Run from the command line with:<p>
+ * java weka.classifiers.meta.MetaCostTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.5 $
+ */
+public class MetaCostTest extends AbstractClassifierTest {
+
+  public MetaCostTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // can handle only as much classes as there are in the CostMatrix!
+    m_NClasses = ((MetaCost) getClassifier()).getCostMatrix().numRows();
+  }
+
+  /** Creates a default MetaCost */
+  public Classifier getClassifier() {
+
+    MetaCost cl = new MetaCost();
+    
+    // load costmatrix
+    try {
+      cl.setCostMatrix(
+          new CostMatrix(
+            new InputStreamReader(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/data/ClassifierTest.cost"))));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    
+    return cl;
+  }
+
+  public static Test suite() {
+    return new TestSuite(MetaCostTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiBoostABTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiBoostABTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiBoostABTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MultiBoostAB. Run from the command line with:<p/>
+ * java weka.classifiers.meta.MultiBoostABTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MultiBoostABTest 
+  extends AbstractClassifierTest {
+
+  public MultiBoostABTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MultiBoostAB */
+  public Classifier getClassifier() {
+    return new MultiBoostAB();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultiBoostABTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiClassClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiClassClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiClassClassifierTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MultiClassClassifier. Run from the command line with:<p>
+ * java weka.classifiers.meta.MultiClassClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class MultiClassClassifierTest extends AbstractClassifierTest {
+
+  public MultiClassClassifierTest(String name) { super(name);  }
+
+  /** Creates a default MultiClassClassifier */
+  public Classifier getClassifier() {
+    return new MultiClassClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultiClassClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiSchemeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiSchemeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/MultiSchemeTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MultiScheme. Run from the command line with:<p>
+ * java weka.classifiers.meta.MultiSchemeTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class MultiSchemeTest extends AbstractClassifierTest {
+
+  public MultiSchemeTest(String name) { super(name);  }
+
+  /** Creates a default MultiScheme */
+  public Classifier getClassifier() {
+    return new MultiScheme();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultiSchemeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/OneClassClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/OneClassClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/OneClassClassifierTest.java	(revision 29)
@@ -0,0 +1,91 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OneClassClassifier. Run from the command line with:<p/>
+ * java weka.classifiers.meta.OneClassClassifierTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5795 $
+ */
+public class OneClassClassifierTest 
+  extends AbstractClassifierTest {
+
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public OneClassClassifierTest(String name) { 
+    super(name);  
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Tester.setNumInstances(40);
+  }
+
+  /**
+   * Creates a default OneClassClassifier.
+   * 
+   * @return		the default classifier
+   */
+  public Classifier getClassifier() {
+    OneClassClassifier	result;
+    
+    result = new OneClassClassifier();
+    result.setTargetClassLabel("class1");
+    
+    return result;
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(OneClassClassifierTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/OrdinalClassClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/OrdinalClassClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/OrdinalClassClassifierTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OrdinalClassClassifier. Run from the command line with:<p>
+ * java weka.classifiers.meta.OrdinalClassClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class OrdinalClassClassifierTest extends AbstractClassifierTest {
+
+  public OrdinalClassClassifierTest(String name) { super(name);  }
+
+  /** Creates a default OrdinalClassClassifier */
+  public Classifier getClassifier() {
+    return new OrdinalClassClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(OrdinalClassClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/RacedIncrementalLogitBoostTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/RacedIncrementalLogitBoostTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/RacedIncrementalLogitBoostTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RacedIncrementalLogitBoost. Run from the command line with:<p/>
+ * java weka.classifiers.meta.RacedIncrementalLogitBoostTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RacedIncrementalLogitBoostTest 
+  extends AbstractClassifierTest {
+
+  public RacedIncrementalLogitBoostTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RacedIncrementalLogitBoost */
+  public Classifier getClassifier() {
+    return new RacedIncrementalLogitBoost();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RacedIncrementalLogitBoostTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomCommitteeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomCommitteeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomCommitteeTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomCommittee. Run from the command line with:<p/>
+ * java weka.classifiers.meta.RandomCommitteeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomCommitteeTest 
+  extends AbstractClassifierTest {
+
+  public RandomCommitteeTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomCommittee */
+  public Classifier getClassifier() {
+    return new RandomCommittee();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomCommitteeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomSubSpaceTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomSubSpaceTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/RandomSubSpaceTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomSubSpace. Run from the command line with:<p/>
+ * java weka.classifiers.meta.RandomSubSpaceTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomSubSpaceTest 
+  extends AbstractClassifierTest {
+
+  public RandomSubSpaceTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomSubSpace */
+  public Classifier getClassifier() {
+    return new RandomSubSpace();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomSubSpaceTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/RegressionByDiscretizationTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/RegressionByDiscretizationTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/RegressionByDiscretizationTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RegressionByDiscretization. Run from the command line with:<p>
+ * java weka.classifiers.meta.RegressionByDiscretizationTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class RegressionByDiscretizationTest extends AbstractClassifierTest {
+
+  public RegressionByDiscretizationTest(String name) { super(name);  }
+
+  /** Creates a default RegressionByDiscretization */
+  public Classifier getClassifier() {
+    return new RegressionByDiscretization();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RegressionByDiscretizationTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/RotationForestTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/RotationForestTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/RotationForestTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RotationForest. Run from the command line with:<p>
+ * java weka.classifiers.meta.RotationForestTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class RotationForestTest extends AbstractClassifierTest {
+
+  public RotationForestTest(String name) { super(name);  }
+
+  /** Creates a default Bagging */
+  public Classifier getClassifier() {
+    return new RotationForest();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RotationForestTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingCTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingCTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingCTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests StackingC. Run from the command line with:<p/>
+ * java weka.classifiers.meta.StackingCTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class StackingCTest 
+  extends AbstractClassifierTest {
+
+  public StackingCTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default StackingC */
+  public Classifier getClassifier() {
+    return new StackingC();
+  }
+
+  public static Test suite() {
+    return new TestSuite(StackingCTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/StackingTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Stacking. Run from the command line with:<p>
+ * java weka.classifiers.meta.StackingTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class StackingTest extends AbstractClassifierTest {
+
+  public StackingTest(String name) { super(name);  }
+
+  /** Creates a default Stacking */
+  public Classifier getClassifier() {
+    return new Stacking();
+  }
+
+  public static Test suite() {
+    return new TestSuite(StackingTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorDummyClassifier.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorDummyClassifier.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorDummyClassifier.java	(revision 29)
@@ -0,0 +1,99 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.classifiers.meta;
+
+import weka.core.Capabilities;
+import weka.core.Capabilities.Capability;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.RevisionUtils;
+import weka.classifiers.Classifier;
+import weka.classifiers.AbstractClassifier;
+
+/**
+ * Dummy classifier - used in ThresholdSelectorTest.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpet at waikato dor ac dot nz)
+ * @version $Revision: 5928 $
+ * @see ThresholdSelectorTest
+ */
+public class ThresholdSelectorDummyClassifier 
+  extends AbstractClassifier {
+
+  /** for serialization */
+  private static final long serialVersionUID = -2040984810834943903L;
+  
+  private double[] m_Preds;
+  private int m_Pos;
+
+  public ThresholdSelectorDummyClassifier(double[] preds) {
+    m_Preds = new double[preds.length];
+    for (int i = 0; i < preds.length; i++)
+      m_Preds[i] = preds[i];
+  }
+
+  /**
+   * Returns default capabilities of the classifier.
+   *
+   * @return      the capabilities of this classifier
+   */
+  public Capabilities getCapabilities() {
+    Capabilities result = super.getCapabilities();
+
+    // attribute
+    result.enableAllAttributes();
+    result.disable(Capability.STRING_ATTRIBUTES);
+    result.disable(Capability.RELATIONAL_ATTRIBUTES);
+
+    // class
+    result.enable(Capability.NOMINAL_CLASS);
+    
+    return result;
+  }
+
+  public void buildClassifier(Instances train) { 
+  }
+
+  public double[] distributionForInstance(Instance test) throws Exception {
+    double[] result = new double[test.numClasses()];
+    int pred = 0;
+    result[pred] = m_Preds[m_Pos];
+    double residual = (1.0 - result[pred]) / (result.length - 1);
+    for (int i = 0; i < result.length; i++) {
+      if (i != pred) {
+        result[i] = residual;
+      }
+    }
+    m_Pos = (m_Pos + 1) % m_Preds.length;
+    return result;
+  }
+  
+  /**
+   * Returns the revision string.
+   * 
+   * @return		the revision
+   */
+  public String getRevision() {
+    return RevisionUtils.extract("$Revision: 5928 $");
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/ThresholdSelectorTest.java	(revision 29)
@@ -0,0 +1,276 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.classifiers.evaluation.EvaluationUtils;
+import weka.classifiers.evaluation.NominalPrediction;
+import weka.core.Attribute;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.NoSupportForMissingValuesException;
+import weka.core.SelectedTag;
+import weka.core.UnsupportedAttributeTypeException;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.RemoveType;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ThresholdSelector. Run from the command line with:<p>
+ * java weka.classifiers.meta.ThresholdSelectorTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.8 $
+ */
+public class ThresholdSelectorTest 
+  extends AbstractClassifierTest {
+
+  private static double[] DIST1 = new double [] {
+    0.25,
+    0.375,
+    0.5,
+    0.625,
+    0.75,
+    0.875,
+    1.0
+  };
+
+  /** A set of instances to test with */
+  protected transient Instances m_Instances;
+
+  /** Used to generate various types of predictions */
+  protected transient EvaluationUtils m_Evaluation;
+
+  public ThresholdSelectorTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default classifier to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Evaluation = new EvaluationUtils();
+    m_Instances = new Instances(
+                    new BufferedReader(
+                      new InputStreamReader(
+                        ClassLoader.getSystemResourceAsStream(
+                          "weka/classifiers/data/ClassifierTest.arff"))));
+  }
+
+  /** Creates a default ThresholdSelector */
+  public Classifier getClassifier() {
+    return getClassifier(DIST1);
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    super.tearDown();
+
+    m_Evaluation = null;
+  }
+
+  /**
+   * Creates a ThresholdSelector that returns predictions from a 
+   * given distribution
+   */
+  public Classifier getClassifier(double[] dist) {
+    return getClassifier(new ThresholdSelectorDummyClassifier(dist));
+  }
+
+  /**
+   * Creates a ThresholdSelector with the given subclassifier.
+   *
+   * @param classifier a <code>Classifier</code> to use as the
+   * subclassifier
+   * @return a new <code>ThresholdSelector</code>
+   */
+  public Classifier getClassifier(Classifier classifier) {
+    ThresholdSelector t = new ThresholdSelector();
+    t.setClassifier(classifier);
+    return t;
+  }
+
+  /**
+   * Builds a model using the current classifier using the first
+   * half of the current data for training, and generates a bunch of
+   * predictions using the remaining half of the data for testing.
+   *
+   * @return a <code>FastVector</code> containing the predictions.
+   */
+  protected FastVector useClassifier() throws Exception {
+
+    Classifier dc = null;
+    int tot = m_Instances.numInstances();
+    int mid = tot / 2;
+    Instances train = null;
+    Instances test = null;
+    try {
+      train = new Instances(m_Instances, 0, mid);
+      test = new Instances(m_Instances, mid, tot - mid);
+      dc = m_Classifier;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Problem setting up to use classifier: " + ex);
+    }
+    int counter = 0;
+    do {
+      try {
+	return m_Evaluation.getTrainTestPredictions(dc, train, test);
+      } catch (UnsupportedAttributeTypeException ex) {
+	SelectedTag tag = null;
+	boolean invert = false;
+	String msg = ex.getMessage();
+	if ((msg.indexOf("string") != -1) && 
+	    (msg.indexOf("attributes") != -1)) {
+	  System.err.println("\nDeleting string attributes.");
+	  tag = new SelectedTag(Attribute.STRING,
+				RemoveType.TAGS_ATTRIBUTETYPE);
+	} else if ((msg.indexOf("only") != -1) && 
+		   (msg.indexOf("nominal") != -1)) {
+	  System.err.println("\nDeleting non-nominal attributes.");
+	  tag = new SelectedTag(Attribute.NOMINAL,
+				RemoveType.TAGS_ATTRIBUTETYPE);
+	  invert = true;
+	} else if ((msg.indexOf("only") != -1) && 
+		   (msg.indexOf("numeric") != -1)) {
+	  System.err.println("\nDeleting non-numeric attributes.");
+	  tag = new SelectedTag(Attribute.NUMERIC,
+				RemoveType.TAGS_ATTRIBUTETYPE);
+	  invert = true;
+	}  else {
+	  throw ex;
+	}
+	RemoveType attFilter = new RemoveType();
+	attFilter.setAttributeType(tag);
+	attFilter.setInvertSelection(invert);
+	attFilter.setInputFormat(train);
+	train = Filter.useFilter(train, attFilter);
+	attFilter.batchFinished();
+	test = Filter.useFilter(test, attFilter);
+	counter++;
+	if (counter > 2) {
+	  throw ex;
+	}
+      } catch (NoSupportForMissingValuesException ex2) {
+	System.err.println("\nReplacing missing values.");
+	ReplaceMissingValues rmFilter = new ReplaceMissingValues();
+	rmFilter.setInputFormat(train);
+	train = Filter.useFilter(train, rmFilter);
+	rmFilter.batchFinished();
+	test = Filter.useFilter(test, rmFilter);
+      } catch (IllegalArgumentException ex3) {
+	String msg = ex3.getMessage();
+	if (msg.indexOf("Not enough instances") != -1) {
+	  System.err.println("\nInflating training data.");
+	  Instances trainNew = new Instances(train);
+	  for (int i = 0; i < train.numInstances(); i++) {
+	    trainNew.add(train.instance(i));
+	  }
+	  train = trainNew;
+	} else {
+	  throw ex3;
+	}
+      }
+    } while (true);
+  }
+
+  public void testRangeNone() throws Exception {
+    
+    int cind = 0;
+    ((ThresholdSelector)m_Classifier).setDesignatedClass(new SelectedTag(ThresholdSelector.OPTIMIZE_0, ThresholdSelector.TAGS_OPTIMIZE));
+    ((ThresholdSelector)m_Classifier).setRangeCorrection(new SelectedTag(ThresholdSelector.RANGE_NONE, ThresholdSelector.TAGS_RANGE));
+    FastVector result = null;
+    m_Instances.setClassIndex(1);
+    result = useClassifier();
+    assertTrue(result.size() != 0);
+    double minp = 0;
+    double maxp = 0;
+    for (int i = 0; i < result.size(); i++) {
+      NominalPrediction p = (NominalPrediction)result.elementAt(i);
+      double prob = p.distribution()[cind];
+      if ((i == 0) || (prob < minp)) minp = prob;
+      if ((i == 0) || (prob > maxp)) maxp = prob;
+    }
+    assertTrue("Upper limit shouldn't increase", maxp <= 1.0);
+    assertTrue("Lower limit shouldn'd decrease", minp >= 0.25);
+  }
+  
+  public void testDesignatedClass() throws Exception {
+    
+    int cind = 0;
+    for (int i = 0; i < ThresholdSelector.TAGS_OPTIMIZE.length; i++) {
+      ((ThresholdSelector)m_Classifier).setDesignatedClass(new SelectedTag(ThresholdSelector.TAGS_OPTIMIZE[i].getID(), ThresholdSelector.TAGS_OPTIMIZE));
+      m_Instances.setClassIndex(1);
+      FastVector result = useClassifier();
+      assertTrue(result.size() != 0);
+    }
+  }
+
+  public void testEvaluationMode() throws Exception {
+    
+    int cind = 0;
+    for (int i = 0; i < ThresholdSelector.TAGS_EVAL.length; i++) {
+      ((ThresholdSelector)m_Classifier).setEvaluationMode(new SelectedTag(ThresholdSelector.TAGS_EVAL[i].getID(), ThresholdSelector.TAGS_EVAL));
+      m_Instances.setClassIndex(1);
+      FastVector result = useClassifier();
+      assertTrue(result.size() != 0);
+    }
+  }
+
+  public void testNumXValFolds() throws Exception {
+    
+    try {
+      ((ThresholdSelector)m_Classifier).setNumXValFolds(0);
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // OK
+    }
+
+    int cind = 0;
+    for (int i = 2; i < 20; i += 2) {
+      ((ThresholdSelector)m_Classifier).setNumXValFolds(i);
+      m_Instances.setClassIndex(1);
+      FastVector result = useClassifier();
+      assertTrue(result.size() != 0);
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(ThresholdSelectorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/VoteTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/VoteTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/VoteTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Vote. Run from the command line with:<p/>
+ * java weka.classifiers.meta.VoteTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class VoteTest 
+  extends AbstractClassifierTest {
+
+  public VoteTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Vote */
+  public Classifier getClassifier() {
+    return new Vote();
+  }
+
+  public static Test suite() {
+    return new TestSuite(VoteTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedNDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedNDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/ClassBalancedNDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClassBalancedND. Run from the command line with:<p/>
+ * java weka.classifiers.meta.nestedDichotomies.ClassBalancedNDTest
+ *
+ * @author eibe
+ * @version $Revision: 1.2 $
+ */
+public class ClassBalancedNDTest 
+  extends AbstractClassifierTest {
+
+  public ClassBalancedNDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Dagging */
+  public Classifier getClassifier() {
+    return new ClassBalancedND();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassBalancedNDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedNDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedNDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/DataNearBalancedNDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DataNearBalancedND. Run from the command line with:<p/>
+ * java weka.classifiers.meta.nestedDichotomies.DataNearBalancedNDTest
+ *
+ * @author eibe
+ * @version $Revision: 1.2 $
+ */
+public class DataNearBalancedNDTest 
+  extends AbstractClassifierTest {
+
+  public DataNearBalancedNDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Dagging */
+  public Classifier getClassifier() {
+    return new DataNearBalancedND();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DataNearBalancedNDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/NDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/NDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/meta/nestedDichotomies/NDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.meta.nestedDichotomies;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ND. Run from the command line with:<p/>
+ * java weka.classifiers.meta.nestedDichotomies.NDTest
+ *
+ * @author eibe
+ * @version $Revision: 1.2 $
+ */
+public class NDTest 
+  extends AbstractClassifierTest {
+
+  public NDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Dagging */
+  public Classifier getClassifier() {
+    return new ND();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/CitationKNNTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/CitationKNNTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/CitationKNNTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CitationKNN. Run from the command line with:<p/>
+ * java weka.classifiers.mi.CitationKNNTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class CitationKNNTest 
+  extends AbstractClassifierTest {
+
+  public CitationKNNTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default CitationKNN */
+  public Classifier getClassifier() {
+    return new CitationKNN();
+  }
+
+  public static Test suite() {
+    return new TestSuite(CitationKNNTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MDDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MDDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MDDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MDD. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MDDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MDDTest 
+  extends AbstractClassifierTest {
+
+  public MDDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MDD */
+  public Classifier getClassifier() {
+    return new MDD();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MDDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MIBoostTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MIBoostTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MIBoostTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIBoost. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MIBoostTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIBoostTest 
+  extends AbstractClassifierTest {
+
+  public MIBoostTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIBoost */
+  public Classifier getClassifier() {
+    return new MIBoost();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIBoostTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MIDDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MIDDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MIDDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIDD. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MIDDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIDDTest 
+  extends AbstractClassifierTest {
+
+  public MIDDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIDD */
+  public Classifier getClassifier() {
+    return new MIDD();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIDDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MIEMDDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MIEMDDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MIEMDDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIEMDD. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MIEMDDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIEMDDTest 
+  extends AbstractClassifierTest {
+
+  public MIEMDDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIEMDD */
+  public Classifier getClassifier() {
+    return new MIEMDD();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIEMDDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MILRTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MILRTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MILRTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MILR. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MILRTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MILRTest 
+  extends AbstractClassifierTest {
+
+  public MILRTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MILR */
+  public Classifier getClassifier() {
+    return new MILR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MILRTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MINNDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MINNDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MINNDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MINND. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MINNDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MINNDTest 
+  extends AbstractClassifierTest {
+
+  public MINNDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MINND */
+  public Classifier getClassifier() {
+    return new MINND();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MINNDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MIOptimalBallTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MIOptimalBallTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MIOptimalBallTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIOptimalBall. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MIOptimalBallTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIOptimalBallTest 
+  extends AbstractClassifierTest {
+
+  public MIOptimalBallTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIOptimalBall */
+  public Classifier getClassifier() {
+    return new MIOptimalBall();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIOptimalBallTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MISMOTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MISMOTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MISMOTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MISMO. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MISMOTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MISMOTest 
+  extends AbstractClassifierTest {
+
+  public MISMOTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MISMO */
+  public Classifier getClassifier() {
+    return new MISMO();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MISMOTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MISVMTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MISVMTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MISVMTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MISVM. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MISVMTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class MISVMTest 
+  extends AbstractClassifierTest {
+
+  public MISVMTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MISVM (with limited number of iterations though) */
+  public Classifier getClassifier() {
+    MISVM classifier = new MISVM();
+    classifier.setMaxIterations(50);
+    return classifier;
+  }
+
+  public static Test suite() {
+    return new TestSuite(MISVMTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/MIWrapperTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/MIWrapperTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/MIWrapperTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIWrapper. Run from the command line with:<p/>
+ * java weka.classifiers.mi.MIWrapperTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIWrapperTest 
+  extends AbstractClassifierTest {
+
+  public MIWrapperTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIWrapper */
+  public Classifier getClassifier() {
+    return new MIWrapper();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIWrapperTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/SimpleMITest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/SimpleMITest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/SimpleMITest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SimpleMI. Run from the command line with:<p/>
+ * java weka.classifiers.mi.SimpleMITest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class SimpleMITest 
+  extends AbstractClassifierTest {
+
+  public SimpleMITest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleMI */
+  public Classifier getClassifier() {
+    return new SimpleMI();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SimpleMITest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDSimpleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDSimpleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDSimpleTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TLDSimple. Run from the command line with:<p/>
+ * java weka.classifiers.mi.TLDSimpleTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class TLDSimpleTest 
+  extends AbstractClassifierTest {
+
+  public TLDSimpleTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default TLDSimple */
+  public Classifier getClassifier() {
+    return new TLDSimple();
+  }
+
+  public static Test suite() {
+    return new TestSuite(TLDSimpleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/TLDTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TLD. Run from the command line with:<p/>
+ * java weka.classifiers.mi.TLDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class TLDTest 
+  extends AbstractClassifierTest {
+
+  public TLDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default TLD */
+  public Classifier getClassifier() {
+    return new TLD();
+  }
+
+  public static Test suite() {
+    return new TestSuite(TLDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIPolyKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIPolyKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIPolyKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIPolyKernel. Run from the command line with:<p/>
+ * java weka.classifiers.mi.supportVector.MIPolyKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIPolyKernelTest 
+  extends AbstractKernelTest {
+
+  public MIPolyKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIPolyKernel */
+  public Kernel getKernel() {
+    return new MIPolyKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIPolyKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIRBFKernelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIRBFKernelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/mi/supportVector/MIRBFKernelTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.mi.supportVector;
+
+import weka.classifiers.functions.supportVector.AbstractKernelTest;
+import weka.classifiers.functions.supportVector.Kernel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MIRBFKernel. Run from the command line with:<p/>
+ * java weka.classifiers.mi.supportVector.MIRBFKernelTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MIRBFKernelTest 
+  extends AbstractKernelTest {
+
+  public MIRBFKernelTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MIRBFKernel */
+  public Kernel getKernel() {
+    return new MIRBFKernel();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MIRBFKernelTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/misc/FLRTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/misc/FLRTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/misc/FLRTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FLR. Run from the command line with:<p/>
+ * java weka.classifiers.misc.FLRTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class FLRTest 
+  extends AbstractClassifierTest {
+
+  public FLRTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default FLR */
+  public Classifier getClassifier() {
+    return new FLR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FLRTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/misc/HyperPipesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/misc/HyperPipesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/misc/HyperPipesTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests HyperPipes. Run from the command line with:<p>
+ * java weka.classifiers.misc.HyperPipesTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class HyperPipesTest extends AbstractClassifierTest {
+
+  public HyperPipesTest(String name) { super(name);  }
+
+  /** Creates a default HyperPipes */
+  public Classifier getClassifier() {
+    return new HyperPipes();
+  }
+
+  public static Test suite() {
+    return new TestSuite(HyperPipesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/misc/OSDLTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/misc/OSDLTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/misc/OSDLTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2004 Stijn Lievens
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OSDL. Run from the command line with:<p>
+ * java weka.classifiers.misc.OSDLTest
+ *
+ * @author Stijn Lievens
+ * @version $Revision: 5927 $
+ */
+public class OSDLTest
+  extends AbstractClassifierTest {
+
+  public OSDLTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default J48 */
+  public Classifier getClassifier() {
+    return new OSDL();
+  }
+
+  public static Test suite() {
+    return new TestSuite(OSDLTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/misc/SerializedClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/misc/SerializedClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/misc/SerializedClassifierTest.java	(revision 29)
@@ -0,0 +1,361 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.evaluation.EvaluationUtils;
+import weka.core.Attribute;
+import weka.core.CheckOptionHandler;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.test.Regression;
+
+import java.io.File;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SerializedClassifier. Run from the command line with:<p>
+ * java weka.classifiers.misc.SerializedClassifierTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class SerializedClassifierTest
+  extends TestCase {
+
+  /** the filename for temporary serialized models */
+  public final static String MODEL_FILENAME = System.getProperty("user.dir") + "/" + "temp.model";
+  
+  /** the setup classifier */
+  protected SerializedClassifier m_Classifier;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+
+  /**
+   * initializes the test
+   * 
+   * @param name the name of the test
+   */
+  public SerializedClassifierTest(String name) {
+    super(name);
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Classifier   = null;
+    m_OptionTester = new CheckOptionHandler();
+    m_OptionTester.setSilent(true);
+
+    // delete temp file
+    File file = new File(MODEL_FILENAME);
+    if (file.exists())
+      file.delete();
+  }
+
+  /**
+   * Called by JUnit after each test method
+   */
+  protected void tearDown() {
+    m_Classifier   = null;
+    m_OptionTester = null;
+
+    // delete temp file
+    File file = new File(MODEL_FILENAME);
+    if (file.exists())
+      file.delete();
+  }
+
+  /**
+   * creates a classifier, trains and serializes it
+   * 
+   * @param data	the data to use (J48 with nominal class, M5P with
+   * 			numeric class)
+   * @return		the results for the data
+   */
+  protected double[] trainAndSerializeClassifier(Instances data) {
+    Classifier	classifier;
+    double[]	result;
+    int		i;
+    
+    try {
+      // build
+      if (data.classAttribute().isNominal())
+	classifier = new weka.classifiers.trees.J48();
+      else
+	classifier = new weka.classifiers.trees.M5P();
+      classifier.buildClassifier(data);
+      
+      // record predictions
+      result = new double[data.numInstances()];
+      for (i = 0; i < result.length; i++)
+	result[i] = classifier.classifyInstance(data.instance(i));
+      
+      // save
+      SerializationHelper.write(MODEL_FILENAME, classifier);
+    }
+    catch (Exception e) {
+      fail("Training base classifier failed: " + e);
+      return null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * performs the actual test
+   * 
+   * @param nomClass	whether to use a nominal class with J48 (TRUE) or 
+   * 			a numeric one with M5P (FALSE)
+   */
+  protected void performTest(boolean nomClass) {
+    TestInstances	test;
+    Instances		data;
+    double[]		originalResults;
+    double[]		testResults;
+    int			i;
+
+    // generate data
+    try {
+      test = new TestInstances();
+      if (nomClass) {
+	test.setClassType(Attribute.NOMINAL);
+	test.setNumNominal(5);
+	test.setNumNominalValues(4);
+	test.setNumNumeric(0);
+      }
+      else {
+	test.setClassType(Attribute.NUMERIC);
+	test.setNumNominal(0);
+	test.setNumNumeric(5);
+      }
+      test.setNumDate(0);
+      test.setNumString(0);
+      test.setNumRelational(0);
+      test.setNumInstances(100);
+      data = test.generate();
+    }
+    catch (Exception e) {
+      fail("Generating test data failed: " + e);
+      return;
+    }
+    
+    // train and save base classifier
+    try {
+      originalResults = trainAndSerializeClassifier(data);
+    }
+    catch (Exception e) {
+      fail("Training base classifier failed: " + e);
+      return;
+    }
+    
+    // test loading
+    try {
+      m_Classifier = new SerializedClassifier();
+      m_Classifier.setModelFile(new File(MODEL_FILENAME));
+      m_Classifier.buildClassifier(data);
+    }
+    catch (Exception e) {
+      fail("Loading/testing of classifier failed: " + e);
+    }
+    
+    // compare results
+    try {
+      // get results from serialized model
+      testResults = new double[data.numInstances()];
+      for (i = 0; i < testResults.length; i++)
+	testResults[i] = m_Classifier.classifyInstance(data.instance(i));
+      
+      // compare
+      for (i = 0; i < originalResults.length; i++) {
+	if (originalResults[i] != testResults[i])
+	  throw new Exception("Result #" + (i+1) + " differs!");
+      }
+    }
+    catch (Exception e) {
+      fail("Comparing results failed: " + e);
+    }
+  }
+  
+  /**
+   * tests a serialized classifier (J48) handling nominal classes
+   */
+  public void testNominalClass() {
+    performTest(true);
+  }
+  
+  /**
+   * tests a serialized classifier (M5P) handling numeric classes
+   */
+  public void testNumericClass() {
+    performTest(true);
+  }
+
+  /**
+   * Returns a string containing all the predictions.
+   *
+   * @param predictions a <code>FastVector</code> containing the predictions
+   * @return a <code>String</code> representing the vector of predictions.
+   */
+  protected String predictionsToString(FastVector predictions) {
+    StringBuffer sb = new StringBuffer();
+    sb.append(predictions.size()).append(" predictions\n");
+    for (int i = 0; i < predictions.size(); i++) {
+      sb.append(predictions.elementAt(i)).append('\n');
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created. Uses J48 for this purpose.
+   */
+  public void testRegression() {
+    Regression 		reg;
+    Instances   	train;
+    Instances   	test;
+    Instances   	data;
+    TestInstances	testInst;
+    int 		tot;
+    int 		mid;
+    EvaluationUtils 	evaluation;
+    FastVector		regressionResults;
+    
+    reg = new Regression(this.getClass());
+
+    // generate test data
+    try {
+      testInst = new TestInstances();
+      testInst.setClassType(Attribute.NOMINAL);
+      testInst.setNumNominal(5);
+      testInst.setNumNominalValues(4);
+      testInst.setNumNumeric(0);
+      testInst.setNumDate(0);
+      testInst.setNumString(0);
+      testInst.setNumRelational(0);
+      testInst.setNumInstances(100);
+      data = testInst.generate();
+    }
+    catch (Exception e) {
+      fail("Failed generating data: " + e);
+      return;
+    }
+    
+    // split data into train/test
+    tot = data.numInstances();
+    mid = tot / 2;
+    train = null;
+    test = null;
+    
+    try {
+      train = new Instances(data, 0, mid);
+      test = new Instances(data, mid, tot - mid);
+      m_Classifier = new SerializedClassifier();
+      m_Classifier.setModelFile(new File(MODEL_FILENAME));
+    } 
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Problem setting up to use classifier: " + e);
+    }
+
+    evaluation = new EvaluationUtils();
+    try {
+      trainAndSerializeClassifier(train);
+      regressionResults = evaluation.getTrainTestPredictions(m_Classifier, train, test);
+      reg.println(predictionsToString(regressionResults));
+    }
+    catch (Exception e) {
+      fail("Failed obtaining classifier predictions: " + e);
+    }
+    
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (!m_OptionTester.checkListOptions())
+      fail("Options cannot be listed via listOptions.");
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("setOptions method failed.");
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (!m_OptionTester.checkRemainingOptions())
+      fail("There were 'left-over' options.");
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   */
+  public void testCanonicalUserOptions() {
+    if (!m_OptionTester.checkCanonicalUserOptions())
+      fail("setOptions method failed");
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("Resetting of options failed");
+  }
+  
+  public static Test suite() {
+    return new TestSuite(SerializedClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/misc/VFITest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/misc/VFITest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/misc/VFITest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.misc;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests VFI. Run from the command line with:<p>
+ * java weka.classifiers.misc.VFITest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class VFITest extends AbstractClassifierTest {
+
+  public VFITest(String name) { super(name);  }
+
+  /** Creates a default VFI */
+  public Classifier getClassifier() {
+    return new VFI();
+  }
+
+  public static Test suite() {
+    return new TestSuite(VFITest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AbstractPMMLClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AbstractPMMLClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AbstractPMMLClassifierTest.java	(revision 29)
@@ -0,0 +1,99 @@
+package weka.classifiers.pmml.consumer;
+
+import weka.core.Instances;
+import weka.core.FastVector;
+import weka.core.Attribute;
+import weka.core.pmml.PMMLFactory;
+import weka.core.pmml.PMMLModel;
+import weka.test.Regression;
+import weka.classifiers.evaluation.EvaluationUtils;
+
+import java.io.*;
+
+import junit.framework.TestCase;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public abstract class AbstractPMMLClassifierTest extends TestCase {
+
+  protected FastVector m_modelNames = new FastVector();
+  protected FastVector m_dataSetNames = new FastVector();
+
+  public AbstractPMMLClassifierTest(String name) { 
+    super(name); 
+  }
+
+  public Instances getData(String name) {
+    Instances elnino = null;
+    try {
+      elnino = 
+        new Instances(new BufferedReader(new InputStreamReader(
+          ClassLoader.getSystemResourceAsStream("weka/classifiers/pmml/data/" + name))));
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return elnino;
+  }
+
+  public PMMLClassifier getClassifier(String name) {
+    PMMLClassifier regression = null;
+    try {
+      PMMLModel model = 
+        PMMLFactory.getPMMLModel(new BufferedInputStream(ClassLoader.getSystemResourceAsStream(
+                  "weka/classifiers/pmml/data/" + name)));
+
+      regression = (PMMLClassifier)model;
+
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+    return regression;
+  }
+
+  public void testRegression() throws Exception {
+
+    PMMLClassifier classifier = null;
+    Instances testData = null;
+    EvaluationUtils evalUtils = null; 
+    weka.test.Regression reg = new weka.test.Regression(this.getClass());
+
+    FastVector predictions = null;
+    boolean success = false;
+    for (int i = 0; i < m_modelNames.size(); i++) {
+      classifier = getClassifier((String)m_modelNames.elementAt(i));
+      testData = getData((String)m_dataSetNames.elementAt(i));
+      evalUtils = new EvaluationUtils();
+
+      try {
+        String  className = classifier.getMiningSchema().getFieldsAsInstances().classAttribute().name();
+        Attribute classAtt = testData.attribute(className);
+        testData.setClass(classAtt);
+        predictions = evalUtils.getTestPredictions(classifier, testData);
+        success = true;
+        String predsString = weka.classifiers.AbstractClassifierTest.predictionsToString(predictions);
+        reg.println(predsString);
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        String msg = ex.getMessage().toLowerCase();
+        if (msg.indexOf("not in classpath") > -1) {
+          return;
+        }
+      }
+    }
+
+    if (!success) {
+      fail("Problem during regression testing: no successful predictions generated");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    }  catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }    
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/AllTests.java	(revision 29)
@@ -0,0 +1,50 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml classifiers.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class AllTests extends TestSuite {
+ 
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+
+    suite.addTest(RegressionTest.suite());
+    suite.addTest(GeneralRegressionTest.suite());
+    suite.addTest(NeuralNetworkTest.suite());
+    suite.addTest(TreeModelTest.suite());
+    
+    return suite;
+  }
+
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/GeneralRegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/GeneralRegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/GeneralRegressionTest.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import weka.core.FastVector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml GeneralRegression classifier.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class GeneralRegressionTest extends AbstractPMMLClassifierTest {
+
+  public GeneralRegressionTest(String name) {
+    super(name);
+  }
+
+  protected void setUp() throws Exception {
+    m_modelNames = new FastVector();
+    m_dataSetNames = new FastVector();
+    m_modelNames.addElement("polynomial_regression_model.xml");
+    m_modelNames.addElement("HEART_NOMREG.xml");
+    m_dataSetNames.addElement("Elnino_small.arff");
+    m_dataSetNames.addElement("heart-c.arff");
+  }
+
+  public static Test suite() {
+    return new TestSuite(weka.classifiers.pmml.consumer.GeneralRegressionTest.class);
+  }
+
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/NeuralNetworkTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/NeuralNetworkTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/NeuralNetworkTest.java	(revision 29)
@@ -0,0 +1,58 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import weka.core.FastVector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml NeuralNetwork classifier.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class NeuralNetworkTest extends AbstractPMMLClassifierTest {
+
+  public NeuralNetworkTest(String name) {
+    super(name);
+  }
+
+  protected void setUp() throws Exception {
+    m_modelNames = new FastVector();
+    m_dataSetNames = new FastVector();
+    m_modelNames.addElement("IRIS_MLP.xml");
+    m_modelNames.addElement("HEART_RBF.xml");
+    m_modelNames.addElement("ElNino_NN.xml");
+    m_dataSetNames.addElement("iris.arff");
+    m_dataSetNames.addElement("heart-c.arff");
+    m_dataSetNames.addElement("Elnino_small.arff");
+  }
+
+  public static Test suite() {
+    return new TestSuite(weka.classifiers.pmml.consumer.NeuralNetworkTest.class);
+  }
+
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RegressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RegressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RegressionTest.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import weka.core.FastVector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml Regression classifier.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision 1.0 $
+ */
+public class RegressionTest extends AbstractPMMLClassifierTest {
+
+  public RegressionTest(String name) {
+    super(name);
+  }
+
+  protected void setUp() throws Exception {
+    m_modelNames = new FastVector();
+    m_dataSetNames = new FastVector();
+    m_modelNames.addElement("linear_regression_model.xml");
+    m_modelNames.addElement("ELNINO_REGRESSION_SIMPLE.xml");
+    m_dataSetNames.addElement("Elnino_small.arff");
+    m_dataSetNames.addElement("Elnino_small.arff");
+  }
+
+  public static Test suite() {
+    return new TestSuite(weka.classifiers.pmml.consumer.RegressionTest.class);
+  }
+
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RuleSetModelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RuleSetModelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/RuleSetModelTest.java	(revision 29)
@@ -0,0 +1,54 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import weka.core.FastVector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml RuleSetModel classifier.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5659 $
+ */
+public class RuleSetModelTest extends AbstractPMMLClassifierTest {
+
+  public RuleSetModelTest(String name) {
+    super(name);
+  }
+
+  protected void setUp() throws Exception {
+    m_modelNames = new FastVector();
+    m_dataSetNames = new FastVector();
+    m_modelNames.addElement("HEART_RULESET.xml");
+    m_dataSetNames.addElement("heart-c.arff");
+  }
+
+  public static Test suite() {
+    return new TestSuite(weka.classifiers.pmml.consumer.RuleSetModelTest.class);
+  }
+
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/TreeModelTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/TreeModelTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/consumer/TreeModelTest.java	(revision 29)
@@ -0,0 +1,56 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.pmml.consumer;
+
+import weka.core.FastVector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the pmml TreeModel classifier.
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class TreeModelTest extends AbstractPMMLClassifierTest {
+
+  public TreeModelTest(String name) {
+    super(name);
+  }
+
+  protected void setUp() throws Exception {
+    m_modelNames = new FastVector();
+    m_dataSetNames = new FastVector();
+    m_modelNames.addElement("IRIS_TREE.xml");
+    m_modelNames.addElement("HEART_TREE.xml");
+    m_dataSetNames.addElement("iris.arff");
+    m_dataSetNames.addElement("heart-c2.arff");
+  }
+
+  public static Test suite() {
+    return new TestSuite(weka.classifiers.pmml.consumer.TreeModelTest.class);
+  }
+
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ELNINO_REGRESSION_SIMPLE.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ELNINO_REGRESSION_SIMPLE.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ELNINO_REGRESSION_SIMPLE.xml	(revision 29)
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+</Header>
+<DataDictionary numberOfFields="7">
+<DataField name="airtemp" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="humidity" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="latitude" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="longitude" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="mer_winds" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="zon_winds" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="s_s_temp" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+</DataDictionary>
+<TransformationDictionary/>
+<RegressionModel modelName="ELNINO_REGRESSION" functionName="regression" modelType="linearRegression" targetFieldName="s_s_temp">
+<MiningSchema>
+<MiningField name="airtemp" usageType="active"/>
+<MiningField name="humidity" usageType="active"/>
+<MiningField name="latitude" usageType="active"/>
+<MiningField name="longitude" usageType="active"/>
+<MiningField name="mer_winds" usageType="active"/>
+<MiningField name="zon_winds" usageType="active"/>
+<MiningField name="s_s_temp" usageType="predicted"/>
+</MiningSchema>
+<RegressionTable intercept="-0.757882724967734">
+<NumericPredictor name="airtemp" exponent="1" coefficient="1.07398117216966"/>
+<NumericPredictor name="humidity" exponent="1" coefficient="-0.00159258520813778"/>
+<NumericPredictor name="latitude" exponent="1" coefficient="-0.00178010147179902"/>
+<NumericPredictor name="longitude" exponent="1" coefficient="3.36308061253596E-4"/>
+<NumericPredictor name="mer_winds" exponent="1" coefficient="0.0226072055602688"/>
+<NumericPredictor name="zon_winds" exponent="1" coefficient="0.0893743540953581"/>
+</RegressionTable>
+</RegressionModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ElNino_NN.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ElNino_NN.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/ElNino_NN.xml	(revision 29)
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML xmlns="http://www.dmg.org/PMML-3_2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.dmg.org/PMML-3_2 http://www.dmg.org/v3-2/pmml-3-2.xsd" version="3.2">
+    <Header copyright="Copyright (c) 2008 Zementis, Inc. (www.zementis.com)"
+        description="Neural Network using the El Nino dataset">
+        <Timestamp>Feb 15, 2008</Timestamp>
+    </Header>
+    <DataDictionary numberOfFields="7">
+        <DataField name="airtemp" optype="continuous" dataType="double" />
+        <DataField name="latitude" optype="continuous" dataType="double" />
+        <DataField name="longitude" optype="continuous" dataType="double" />
+        <DataField name="zon_winds" optype="continuous" dataType="double" />
+        <DataField name="mer_winds" optype="continuous" dataType="double" />
+        <DataField name="humidity" optype="continuous" dataType="double" />
+        <DataField name="s_s_temp" optype="continuous" dataType="double" />
+    </DataDictionary>
+    <TransformationDictionary>
+        <DerivedField name="derived_latitude" dataType="double" optype="continuous">
+            <NormContinuous field="latitude">
+                <LinearNorm orig="-8.28" norm="0" />
+                <LinearNorm orig="8.97" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+        <DerivedField name="derived_longitude" dataType="double" optype="continuous">
+            <NormContinuous field="longitude">
+                <LinearNorm orig="-179.87" norm="0" />
+                <LinearNorm orig="179.8" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+        <DerivedField name="derived_zon_winds" dataType="double" optype="continuous">
+            <NormContinuous field="zon_winds">
+                <LinearNorm orig="-8.1" norm="0" />
+                <LinearNorm orig="3.4" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+        <DerivedField name="derived_mer_winds" dataType="double" optype="continuous">
+            <NormContinuous field="mer_winds">
+                <LinearNorm orig="-6.2" norm="0" />
+                <LinearNorm orig="7.1" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+        <DerivedField name="derived_humidity" dataType="double" optype="continuous">
+            <NormContinuous field="humidity">
+                <LinearNorm orig="72.3" norm="0" />
+                <LinearNorm orig="99.4" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+        <DerivedField name="derived_s_s_temp" dataType="double" optype="continuous">
+            <NormContinuous field="s_s_temp">
+                <LinearNorm orig="22.56" norm="0" />
+                <LinearNorm orig="30.15" norm="1" />
+            </NormContinuous>
+        </DerivedField>
+    </TransformationDictionary>
+    <NeuralNetwork modelName="ElNino_NN" functionName="regression" activationFunction="tanh">
+        <MiningSchema>
+            <MiningField name="latitude" />
+            <MiningField name="longitude" />
+            <MiningField name="zon_winds" />
+            <MiningField name="mer_winds" />
+            <MiningField name="humidity" />
+            <MiningField name="s_s_temp" />
+            <MiningField name="airtemp" usageType="predicted" />
+        </MiningSchema>
+        <NeuralInputs>
+            <NeuralInput id="0">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_latitude" />
+                </DerivedField>
+            </NeuralInput>
+            <NeuralInput id="1">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_longitude" />
+                </DerivedField>
+            </NeuralInput>
+            <NeuralInput id="2">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_zon_winds" />
+                </DerivedField>
+            </NeuralInput>
+            <NeuralInput id="3">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_mer_winds" />
+                </DerivedField>
+            </NeuralInput>
+            <NeuralInput id="4">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_humidity" />
+                </DerivedField>
+            </NeuralInput>
+            <NeuralInput id="5">
+                <DerivedField dataType="double" optype="continuous">
+                    <FieldRef field="derived_s_s_temp" />
+                </DerivedField>
+            </NeuralInput>
+        </NeuralInputs>
+        <NeuralLayer numberOfNeurons="2">
+            <Neuron id="6" bias="-0.731304966362333">
+                <Con from="0" weight="0.522049533017919" />
+                <Con from="1" weight="-0.399145207255475" />
+                <Con from="2" weight="-0.415583881817118" />
+                <Con from="3" weight="-0.309808298543379" />
+                <Con from="4" weight="-0.662674572566683" />
+                <Con from="5" weight="1.72273121708294" />
+            </Neuron>
+            <Neuron id="7" bias="-0.458013009318407">
+                <Con from="0" weight="-0.90117395341808" />
+                <Con from="1" weight="0.756005664311791" />
+                <Con from="2" weight="0.283296146246296" />
+                <Con from="3" weight="0.0416906660285602" />
+                <Con from="4" weight="0.728091902786551" />
+                <Con from="5" weight="1.12654398373213" />
+            </Neuron>
+        </NeuralLayer>
+        <NeuralLayer numberOfNeurons="1" activationFunction="identity">
+            <Neuron id="8" bias="-1.04868830796614">
+                <Con from="6" weight="2.18331080616068" />
+                <Con from="7" weight="1.6406152439558" />
+            </Neuron>
+        </NeuralLayer>
+        <NeuralOutputs>
+            <NeuralOutput outputNeuron="8">
+                <DerivedField name="derived_airtemp" dataType="double" optype="continuous">
+                    <NormContinuous field="airtemp">
+                        <LinearNorm orig="22.74" norm="-3.68637198807044" />
+                        <LinearNorm orig="30.04" norm="2.0133756415681" />
+                    </NormContinuous>
+                </DerivedField>
+            </NeuralOutput>
+        </NeuralOutputs>
+    </NeuralNetwork>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/Elnino_small.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/Elnino_small.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/Elnino_small.arff	(revision 29)
@@ -0,0 +1,26 @@
+@relation Elnino_small
+
+@attribute buoy_day_ID numeric
+@attribute buoy numeric
+@attribute day numeric
+@attribute latitude numeric
+@attribute longitude numeric
+@attribute zon_winds numeric
+@attribute mer_winds numeric
+@attribute humidity numeric
+@attribute airtemp numeric
+@attribute s_s_temp numeric
+
+@data
+1,1,1,8.96,-140.32,-6.3,-6.4,83.5,27.32,27.57
+2,1,2,8.95,-140.32,-5.7,-3.6,86.4,26.7,27.62
+67,5,14,-2.02,-139.97,?,?,?,25.26,26.55
+68,6,1,-5.01,-139.92,-0.6,3.5,83.8,28.02,28.94
+69,6,2,-5.01,-139.91,0.2,-2.5,85.4,27.13,28.85
+75,6,8,-5.01,-139.92,-3.1,-1.7,85.8,27.16,29.05
+76,6,9,-5.01,-139.91,-3.5,-0.8,84.2,27.48,28.99
+77,6,10,-5.01,-139.92,-3.3,-0.7,81.8,27.91,29.05
+236,18,12,-8.05,-109.93,?,?,80.8,27.04,28.01
+237,18,13,-8.05,-109.93,?,?,79.8,27.32,27.93
+238,19,1,8.06,-94.96,-2.3,2.4,86.4,29.14,?
+239,19,2,8.06,-94.95,1.7,-0.3,83.7,29.24,?
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_NOMREG.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_NOMREG.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_NOMREG.xml	(revision 29)
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="15">
+<DataField name="age" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="sex" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="female" property="valid"/>
+<Value value="male" property="valid"/>
+</DataField>
+<DataField name="cp" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="asympt" property="valid"/>
+<Value value="atyp_angina" property="valid"/>
+<Value value="non_anginal" property="valid"/>
+<Value value="typ_angina" property="valid"/>
+</DataField>
+<DataField name="trestbps" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="chol" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="fbs" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="f" property="valid"/>
+<Value value="t" property="valid"/>
+</DataField>
+<DataField name="restecg" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="left_vent_hyper" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="st_t_wave_abnormality" property="valid"/>
+</DataField>
+<DataField name="thalach" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="exang" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="no" property="valid"/>
+<Value value="yes" property="valid"/>
+</DataField>
+<DataField name="oldpeak" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="slope" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="down" property="valid"/>
+<Value value="flat" property="valid"/>
+<Value value="up" property="valid"/>
+</DataField>
+<DataField name="ca" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="thal" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="fixed_defect" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="reversable_defect" property="valid"/>
+</DataField>
+<DataField name="$L-num" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="&lt;50" property="valid"/>
+<Value value="&gt;50_1" property="valid"/>
+</DataField>
+<DataField name="$LP-num" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+</DataDictionary>
+<GeneralRegressionModel modelName="HEART_NOMREG" functionName="classification" modelType="multinomialLogistic" targetVariableName="$L-num">
+<MiningSchema>
+<MiningField name="age" usageType="active"/>
+<MiningField name="sex" usageType="active"/>
+<MiningField name="cp" usageType="active"/>
+<MiningField name="trestbps" usageType="active"/>
+<MiningField name="chol" usageType="active"/>
+<MiningField name="fbs" usageType="active"/>
+<MiningField name="restecg" usageType="active"/>
+<MiningField name="thalach" usageType="active"/>
+<MiningField name="exang" usageType="active"/>
+<MiningField name="oldpeak" usageType="active"/>
+<MiningField name="slope" usageType="active"/>
+<MiningField name="ca" usageType="active"/>
+<MiningField name="thal" usageType="active"/>
+<MiningField name="$L-num" usageType="predicted"/>
+<MiningField name="$LP-num" usageType="supplementary"/>
+</MiningSchema>
+<ParameterList>
+<Parameter name="P0000001" label="&lt;&lt;Intercept&gt;&gt;"/>
+<Parameter name="P0000002" label="age"/>
+<Parameter name="P0000003" label="trestbps"/>
+<Parameter name="P0000004" label="chol"/>
+<Parameter name="P0000005" label="thalach"/>
+<Parameter name="P0000006" label="oldpeak"/>
+<Parameter name="P0000007" label="ca"/>
+<Parameter name="P0000008" label="sex=female"/>
+<Parameter name="P0000009" label="cp=asympt"/>
+<Parameter name="P0000010" label="cp=atyp_angina"/>
+<Parameter name="P0000011" label="cp=non_anginal"/>
+<Parameter name="P0000012" label="fbs=f"/>
+<Parameter name="P0000013" label="restecg=left_vent_hyper"/>
+<Parameter name="P0000014" label="restecg=normal"/>
+<Parameter name="P0000015" label="exang=no"/>
+<Parameter name="P0000016" label="slope=down"/>
+<Parameter name="P0000017" label="slope=flat"/>
+<Parameter name="P0000018" label="thal=fixed_defect"/>
+<Parameter name="P0000019" label="thal=normal"/>
+</ParameterList>
+<FactorList>
+<Predictor name="sex"/>
+<Predictor name="cp"/>
+<Predictor name="fbs"/>
+<Predictor name="restecg"/>
+<Predictor name="exang"/>
+<Predictor name="slope"/>
+<Predictor name="thal"/>
+</FactorList>
+<CovariateList>
+<Predictor name="age"/>
+<Predictor name="trestbps"/>
+<Predictor name="chol"/>
+<Predictor name="thalach"/>
+<Predictor name="oldpeak"/>
+<Predictor name="ca"/>
+</CovariateList>
+<PPMatrix>
+<PPCell value="1" predictorName="age" parameterName="P0000002"/>
+<PPCell value="1" predictorName="trestbps" parameterName="P0000003"/>
+<PPCell value="1" predictorName="chol" parameterName="P0000004"/>
+<PPCell value="1" predictorName="thalach" parameterName="P0000005"/>
+<PPCell value="1" predictorName="oldpeak" parameterName="P0000006"/>
+<PPCell value="1" predictorName="ca" parameterName="P0000007"/>
+<PPCell value="female" predictorName="sex" parameterName="P0000008"/>
+<PPCell value="asympt" predictorName="cp" parameterName="P0000009"/>
+<PPCell value="atyp_angina" predictorName="cp" parameterName="P0000010"/>
+<PPCell value="non_anginal" predictorName="cp" parameterName="P0000011"/>
+<PPCell value="f" predictorName="fbs" parameterName="P0000012"/>
+<PPCell value="left_vent_hyper" predictorName="restecg" parameterName="P0000013"/>
+<PPCell value="normal" predictorName="restecg" parameterName="P0000014"/>
+<PPCell value="no" predictorName="exang" parameterName="P0000015"/>
+<PPCell value="down" predictorName="slope" parameterName="P0000016"/>
+<PPCell value="flat" predictorName="slope" parameterName="P0000017"/>
+<PPCell value="fixed_defect" predictorName="thal" parameterName="P0000018"/>
+<PPCell value="normal" predictorName="thal" parameterName="P0000019"/>
+</PPMatrix>
+<ParamMatrix>
+<PCell targetCategory="&lt;50" parameterName="P0000001" beta="1.6485487678691" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000002" beta="0.0151565581042763" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000003" beta="-0.0225084989930953" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000004" beta="-0.00419117211781928" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000005" beta="0.0180150657377129" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000006" beta="-0.367556853173032" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000007" beta="-1.36424396636098" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000008" beta="1.67198506988112" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000009" beta="-2.04471844493608" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000010" beta="-1.32650416964461" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000011" beta="-0.250806888282369" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000012" beta="-0.590972410710686" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000013" beta="0.473799892611073" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000014" beta="0.891255785241992" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000015" beta="0.775025945804079" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000016" beta="-0.586625828643728" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000017" beta="-1.26372382902138" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000018" beta="1.45815536993899" df="1"/>
+<PCell targetCategory="&lt;50" parameterName="P0000019" beta="1.41587437989704" df="1"/>
+<PCell targetCategory="&gt;50_1" parameterName="P0000001" beta="0.0" df="1"/>
+</ParamMatrix>
+</GeneralRegressionModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RBF.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RBF.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RBF.xml	(revision 29)
@@ -0,0 +1,855 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="15">
+<DataField name="age" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="ca" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="chol" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="cp" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="asympt" property="valid"/>
+<Value value="atyp_angina" property="valid"/>
+<Value value="non_anginal" property="valid"/>
+<Value value="typ_angina" property="valid"/>
+</DataField>
+<DataField name="exang" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="no" property="valid"/>
+<Value value="yes" property="valid"/>
+</DataField>
+<DataField name="fbs" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="f" property="valid"/>
+<Value value="t" property="valid"/>
+</DataField>
+<DataField name="oldpeak" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="restecg" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="left_vent_hyper" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="st_t_wave_abnormality" property="valid"/>
+</DataField>
+<DataField name="sex" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="female" property="valid"/>
+<Value value="male" property="valid"/>
+</DataField>
+<DataField name="slope" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="down" property="valid"/>
+<Value value="flat" property="valid"/>
+<Value value="up" property="valid"/>
+</DataField>
+<DataField name="thal" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="fixed_defect" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="reversable_defect" property="valid"/>
+</DataField>
+<DataField name="thalach" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="trestbps" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="$L-num" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="&lt;50" property="valid"/>
+<Value value="&gt;50_1" property="valid"/>
+</DataField>
+<DataField name="$NC-num" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+</DataDictionary>
+<NeuralNetwork modelName="HEART_RBF" functionName="classification" algorithmName="Neural Net" activationFunction="radialBasis">
+<Extension name="normalizationMethod" value="limitedDifference"/>
+<MiningSchema>
+<MiningField name="age" usageType="active" missingValueReplacement="53.0" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="29.0" highValue="77.0"/>
+<MiningField name="ca" usageType="active" missingValueReplacement="1.5" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="0.0" highValue="3.0"/>
+<MiningField name="chol" usageType="active" missingValueReplacement="345.0" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="126.0" highValue="564.0"/>
+<MiningField name="cp" usageType="active"/>
+<MiningField name="exang" usageType="active"/>
+<MiningField name="fbs" usageType="active"/>
+<MiningField name="oldpeak" usageType="active" missingValueReplacement="3.1" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="0.0" highValue="6.2"/>
+<MiningField name="restecg" usageType="active"/>
+<MiningField name="sex" usageType="active"/>
+<MiningField name="slope" usageType="active"/>
+<MiningField name="thal" usageType="active"/>
+<MiningField name="thalach" usageType="active" missingValueReplacement="136.5" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="71.0" highValue="202.0"/>
+<MiningField name="trestbps" usageType="active" missingValueReplacement="147.0" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="94.0" highValue="200.0"/>
+<MiningField name="$L-num" usageType="predicted"/>
+<MiningField name="$NC-num" usageType="supplementary"/>
+</MiningSchema>
+<NeuralInputs>
+<NeuralInput id="0">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="age">
+<LinearNorm orig="29" norm="0"/>
+<LinearNorm orig="77" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="1">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="ca">
+<LinearNorm orig="0" norm="0"/>
+<LinearNorm orig="3" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="2">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="chol">
+<LinearNorm orig="126" norm="0"/>
+<LinearNorm orig="564" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="3">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="cp" value="asympt"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="4">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="cp" value="atyp_angina"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="5">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="cp" value="non_anginal"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="6">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="cp" value="typ_angina"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="7">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="exang" value="no"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="8">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="exang" value="yes"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="9">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="fbs" value="f"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="10">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="fbs" value="t"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="11">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="oldpeak">
+<LinearNorm orig="0" norm="0"/>
+<LinearNorm orig="6.2" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="12">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="restecg" value="left_vent_hyper"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="13">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="restecg" value="normal"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="14">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="restecg" value="st_t_wave_abnormality"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="15">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="sex" value="female"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="16">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="sex" value="male"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="17">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="slope" value="down"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="18">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="slope" value="flat"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="19">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="slope" value="up"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="20">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="thal" value="fixed_defect"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="21">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="thal" value="normal"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="22">
+<DerivedField optype="continuous" dataType="double">
+<NormDiscrete field="thal" value="reversable_defect"/>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="23">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="thalach">
+<LinearNorm orig="71" norm="0"/>
+<LinearNorm orig="202" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="24">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="trestbps">
+<LinearNorm orig="94" norm="0"/>
+<LinearNorm orig="200" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+</NeuralInputs>
+<NeuralLayer activationFunction="radialBasis">
+<Neuron id="25" width="2.0433125505">
+<Con from="0" weight="0.729166666667"/>
+<Con from="1" weight="0.166666666667"/>
+<Con from="2" weight="0.300228310502"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="1"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0"/>
+<Con from="10" weight="1"/>
+<Con from="11" weight="0.298387096774"/>
+<Con from="12" weight="1"/>
+<Con from="13" weight="0"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0.5"/>
+<Con from="18" weight="0.5"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0.5"/>
+<Con from="21" weight="0.5"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.69465648855"/>
+<Con from="24" weight="0.448113207547"/>
+</Neuron>
+<Neuron id="26" width="1.53797929102">
+<Con from="0" weight="0.616666666667"/>
+<Con from="1" weight="0"/>
+<Con from="2" weight="0.318264840183"/>
+<Con from="3" weight="1"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0.4"/>
+<Con from="8" weight="0.6"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.109677419355"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="0.8"/>
+<Con from="14" weight="0.2"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0.4"/>
+<Con from="19" weight="0.6"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.577099236641"/>
+<Con from="24" weight="0.505660377358"/>
+</Neuron>
+<Neuron id="27" width="1.61974509641">
+<Con from="0" weight="0.5625"/>
+<Con from="1" weight="0.6"/>
+<Con from="2" weight="0.262100456621"/>
+<Con from="3" weight="0.2"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0.6"/>
+<Con from="6" weight="0.2"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0.6"/>
+<Con from="10" weight="0.4"/>
+<Con from="11" weight="0.187096774194"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="1"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="1"/>
+<Con from="23" weight="0.662595419847"/>
+<Con from="24" weight="0.371698113208"/>
+</Neuron>
+<Neuron id="28" width="1.45970244724">
+<Con from="0" weight="0.466666666667"/>
+<Con from="1" weight="0.466666666667"/>
+<Con from="2" weight="0.312328767123"/>
+<Con from="3" weight="1"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0.4"/>
+<Con from="8" weight="0.6"/>
+<Con from="9" weight="0.6"/>
+<Con from="10" weight="0.4"/>
+<Con from="11" weight="0.103225806452"/>
+<Con from="12" weight="0.8"/>
+<Con from="13" weight="0.2"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="1"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="1"/>
+<Con from="23" weight="0.64427480916"/>
+<Con from="24" weight="0.260377358491"/>
+</Neuron>
+<Neuron id="29" width="1.55380666837">
+<Con from="0" weight="0.414930555556"/>
+<Con from="1" weight="0.0555555555556"/>
+<Con from="2" weight="0.278063165906"/>
+<Con from="3" weight="0.333333333333"/>
+<Con from="4" weight="0.458333333333"/>
+<Con from="5" weight="0.125"/>
+<Con from="6" weight="0.0833333333333"/>
+<Con from="7" weight="0.958333333333"/>
+<Con from="8" weight="0.0416666666667"/>
+<Con from="9" weight="0.958333333333"/>
+<Con from="10" weight="0.0416666666667"/>
+<Con from="11" weight="0.0510752688172"/>
+<Con from="12" weight="0.708333333333"/>
+<Con from="13" weight="0.291666666667"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0.125"/>
+<Con from="16" weight="0.875"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="1"/>
+<Con from="20" weight="0.0416666666667"/>
+<Con from="21" weight="0.916666666667"/>
+<Con from="22" weight="0.0416666666667"/>
+<Con from="23" weight="0.740139949109"/>
+<Con from="24" weight="0.343553459119"/>
+</Neuron>
+<Neuron id="30" width="1.56258602904">
+<Con from="0" weight="0.535714285714"/>
+<Con from="1" weight="0.0952380952381"/>
+<Con from="2" weight="0.208088714938"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="1"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.108294930876"/>
+<Con from="12" weight="0.142857142857"/>
+<Con from="13" weight="0.714285714286"/>
+<Con from="14" weight="0.142857142857"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="1"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0.857142857143"/>
+<Con from="22" weight="0.142857142857"/>
+<Con from="23" weight="0.587786259542"/>
+<Con from="24" weight="0.307277628032"/>
+</Neuron>
+<Neuron id="31" width="1.89815047743">
+<Con from="0" weight="0.53125"/>
+<Con from="1" weight="0.111111111111"/>
+<Con from="2" weight="0.252283105023"/>
+<Con from="3" weight="0.333333333333"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0.666666666667"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0.166666666667"/>
+<Con from="8" weight="0.833333333333"/>
+<Con from="9" weight="0.166666666667"/>
+<Con from="10" weight="0.833333333333"/>
+<Con from="11" weight="0.198924731183"/>
+<Con from="12" weight="0.5"/>
+<Con from="13" weight="0.5"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="1"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0.166666666667"/>
+<Con from="21" weight="0.666666666667"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.587786259542"/>
+<Con from="24" weight="0.305031446541"/>
+</Neuron>
+<Neuron id="32" width="1.55400017452">
+<Con from="0" weight="0.673611111111"/>
+<Con from="1" weight="0.555555555556"/>
+<Con from="2" weight="0.316590563166"/>
+<Con from="3" weight="1"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0.666666666667"/>
+<Con from="10" weight="0.333333333333"/>
+<Con from="11" weight="0.241935483871"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="1"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.371501272265"/>
+<Con from="24" weight="0.364779874214"/>
+</Neuron>
+<Neuron id="33" width="1.56512902078">
+<Con from="0" weight="0.615384615385"/>
+<Con from="1" weight="0.410256410256"/>
+<Con from="2" weight="0.310502283105"/>
+<Con from="3" weight="1"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0"/>
+<Con from="8" weight="1"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.312655086849"/>
+<Con from="12" weight="1"/>
+<Con from="13" weight="0"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0.0769230769231"/>
+<Con from="18" weight="0.769230769231"/>
+<Con from="19" weight="0.153846153846"/>
+<Con from="20" weight="0.0769230769231"/>
+<Con from="21" weight="0.384615384615"/>
+<Con from="22" weight="0.538461538462"/>
+<Con from="23" weight="0.472695243688"/>
+<Con from="24" weight="0.407837445573"/>
+</Neuron>
+<Neuron id="34" width="1.44140649594">
+<Con from="0" weight="0.440972222222"/>
+<Con from="1" weight="0.277777777778"/>
+<Con from="2" weight="0.20700152207"/>
+<Con from="3" weight="0.833333333333"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0.166666666667"/>
+<Con from="7" weight="0.5"/>
+<Con from="8" weight="0.5"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.0672043010753"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="1"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="1"/>
+<Con from="23" weight="0.737913486005"/>
+<Con from="24" weight="0.34748427673"/>
+</Neuron>
+<Neuron id="35" width="1.53943580564">
+<Con from="0" weight="0.607142857143"/>
+<Con from="1" weight="0.142857142857"/>
+<Con from="2" weight="0.397586431833"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0.142857142857"/>
+<Con from="5" weight="0.857142857143"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0.428571428571"/>
+<Con from="10" weight="0.571428571429"/>
+<Con from="11" weight="0.0483870967742"/>
+<Con from="12" weight="0.714285714286"/>
+<Con from="13" weight="0.285714285714"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="1"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.594329334787"/>
+<Con from="24" weight="0.365229110512"/>
+</Neuron>
+<Neuron id="36" width="1.81333521429">
+<Con from="0" weight="0.319444444444"/>
+<Con from="1" weight="0"/>
+<Con from="2" weight="0.235920852359"/>
+<Con from="3" weight="0.333333333333"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0.666666666667"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0"/>
+<Con from="8" weight="1"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.0752688172043"/>
+<Con from="12" weight="1"/>
+<Con from="13" weight="0"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0.333333333333"/>
+<Con from="18" weight="0.333333333333"/>
+<Con from="19" weight="0.333333333333"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.689567430025"/>
+<Con from="24" weight="0.345911949686"/>
+</Neuron>
+<Neuron id="37" width="1.71774051191">
+<Con from="0" weight="0.619791666667"/>
+<Con from="1" weight="0.291666666667"/>
+<Con from="2" weight="0.490582191781"/>
+<Con from="3" weight="0.875"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0.125"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0.875"/>
+<Con from="8" weight="0.125"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.302419354839"/>
+<Con from="12" weight="1"/>
+<Con from="13" weight="0"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0.125"/>
+<Con from="18" weight="0.875"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0.5"/>
+<Con from="22" weight="0.5"/>
+<Con from="23" weight="0.602099236641"/>
+<Con from="24" weight="0.377358490566"/>
+</Neuron>
+<Neuron id="38" width="1.55531002581">
+<Con from="0" weight="0.3625"/>
+<Con from="1" weight="0.2"/>
+<Con from="2" weight="0.242922374429"/>
+<Con from="3" weight="0.2"/>
+<Con from="4" weight="0.6"/>
+<Con from="5" weight="0.2"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0.4"/>
+<Con from="10" weight="0.6"/>
+<Con from="11" weight="0.0612903225806"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0.4"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="0.6"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="1"/>
+<Con from="23" weight="0.737404580153"/>
+<Con from="24" weight="0.167924528302"/>
+</Neuron>
+<Neuron id="39" width="1.51428832944">
+<Con from="0" weight="0.4375"/>
+<Con from="1" weight="0.111111111111"/>
+<Con from="2" weight="0.269406392694"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0.777777777778"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0.222222222222"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.132616487455"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0.111111111111"/>
+<Con from="18" weight="0.222222222222"/>
+<Con from="19" weight="0.666666666667"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.65903307888"/>
+<Con from="24" weight="0.305031446541"/>
+</Neuron>
+<Neuron id="40" width="1.62268808191">
+<Con from="0" weight="0.610416666667"/>
+<Con from="1" weight="0.133333333333"/>
+<Con from="2" weight="0.27899543379"/>
+<Con from="3" weight="0.4"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0.2"/>
+<Con from="6" weight="0.4"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.216129032258"/>
+<Con from="12" weight="1"/>
+<Con from="13" weight="0"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0.1"/>
+<Con from="18" weight="0.9"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0.1"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="0.9"/>
+<Con from="23" weight="0.651145038168"/>
+<Con from="24" weight="0.421698113208"/>
+</Neuron>
+<Neuron id="41" width="1.37624721701">
+<Con from="0" weight="0.6625"/>
+<Con from="1" weight="0.133333333333"/>
+<Con from="2" weight="0.375342465753"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="1"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.0387096774194"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="1"/>
+<Con from="16" weight="0"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="1"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="0.8"/>
+<Con from="22" weight="0.2"/>
+<Con from="23" weight="0.606106870229"/>
+<Con from="24" weight="0.394339622642"/>
+</Neuron>
+<Neuron id="42" width="1.64309963133">
+<Con from="0" weight="0.725"/>
+<Con from="1" weight="0.466666666667"/>
+<Con from="2" weight="0.258904109589"/>
+<Con from="3" weight="0.8"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0.2"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.270967741935"/>
+<Con from="12" weight="0.4"/>
+<Con from="13" weight="0.6"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0"/>
+<Con from="18" weight="1"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0.4"/>
+<Con from="21" weight="0.6"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.381679389313"/>
+<Con from="24" weight="0.375471698113"/>
+</Neuron>
+<Neuron id="43" width="1.44508507394">
+<Con from="0" weight="0.408333333333"/>
+<Con from="1" weight="0.155555555556"/>
+<Con from="2" weight="0.256773211568"/>
+<Con from="3" weight="0"/>
+<Con from="4" weight="0.0666666666667"/>
+<Con from="5" weight="0.933333333333"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="1"/>
+<Con from="8" weight="0"/>
+<Con from="9" weight="0.933333333333"/>
+<Con from="10" weight="0.0666666666667"/>
+<Con from="11" weight="0.0870967741935"/>
+<Con from="12" weight="0.0666666666667"/>
+<Con from="13" weight="0.933333333333"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0"/>
+<Con from="16" weight="1"/>
+<Con from="17" weight="0.2"/>
+<Con from="18" weight="0"/>
+<Con from="19" weight="0.8"/>
+<Con from="20" weight="0"/>
+<Con from="21" weight="1"/>
+<Con from="22" weight="0"/>
+<Con from="23" weight="0.72213740458"/>
+<Con from="24" weight="0.310691823899"/>
+</Neuron>
+<Neuron id="44" width="1.58238231275">
+<Con from="0" weight="0.516666666667"/>
+<Con from="1" weight="0.3"/>
+<Con from="2" weight="0.323059360731"/>
+<Con from="3" weight="1"/>
+<Con from="4" weight="0"/>
+<Con from="5" weight="0"/>
+<Con from="6" weight="0"/>
+<Con from="7" weight="0"/>
+<Con from="8" weight="1"/>
+<Con from="9" weight="1"/>
+<Con from="10" weight="0"/>
+<Con from="11" weight="0.287096774194"/>
+<Con from="12" weight="0"/>
+<Con from="13" weight="1"/>
+<Con from="14" weight="0"/>
+<Con from="15" weight="0.1"/>
+<Con from="16" weight="0.9"/>
+<Con from="17" weight="0.1"/>
+<Con from="18" weight="0.9"/>
+<Con from="19" weight="0"/>
+<Con from="20" weight="0.1"/>
+<Con from="21" weight="0"/>
+<Con from="22" weight="0.9"/>
+<Con from="23" weight="0.356488549618"/>
+<Con from="24" weight="0.366037735849"/>
+</Neuron>
+</NeuralLayer>
+<NeuralLayer activationFunction="identity">
+<Neuron id="45" bias="0.244211823198">
+<Con from="25" weight="0.40491247351"/>
+<Con from="26" weight="0.660233411984"/>
+<Con from="27" weight="-0.557872448029"/>
+<Con from="28" weight="-0.864537972738"/>
+<Con from="29" weight="0.68145567777"/>
+<Con from="30" weight="0.806874412673"/>
+<Con from="31" weight="-0.0233999285939"/>
+<Con from="32" weight="-0.257231469399"/>
+<Con from="33" weight="-0.411482704419"/>
+<Con from="34" weight="-0.238597629225"/>
+<Con from="35" weight="0.0326397091282"/>
+<Con from="36" weight="0.610340175215"/>
+<Con from="37" weight="-0.626148606469"/>
+<Con from="38" weight="0.966856878193"/>
+<Con from="39" weight="-0.0216540278491"/>
+<Con from="40" weight="0.566254999095"/>
+<Con from="41" weight="-0.214526565981"/>
+<Con from="42" weight="-0.580836037738"/>
+<Con from="43" weight="-0.100524937831"/>
+<Con from="44" weight="-0.0807023546825"/>
+</Neuron>
+<Neuron id="46" bias="0.755788176802">
+<Con from="25" weight="-0.40491247351"/>
+<Con from="26" weight="-0.660233411985"/>
+<Con from="27" weight="0.557872448028"/>
+<Con from="28" weight="0.864537972738"/>
+<Con from="29" weight="-0.68145567777"/>
+<Con from="30" weight="-0.806874412674"/>
+<Con from="31" weight="0.0233999285942"/>
+<Con from="32" weight="0.257231469401"/>
+<Con from="33" weight="0.411482704419"/>
+<Con from="34" weight="0.238597629225"/>
+<Con from="35" weight="-0.0326397091285"/>
+<Con from="36" weight="-0.610340175214"/>
+<Con from="37" weight="0.626148606469"/>
+<Con from="38" weight="-0.966856878194"/>
+<Con from="39" weight="0.0216540278489"/>
+<Con from="40" weight="-0.566254999094"/>
+<Con from="41" weight="0.214526565981"/>
+<Con from="42" weight="0.580836037737"/>
+<Con from="43" weight="0.10052493783"/>
+<Con from="44" weight="0.0807023546823"/>
+</Neuron>
+</NeuralLayer>
+<NeuralOutputs>
+<NeuralOutput outputNeuron="45">
+<DerivedField optype="categorical" dataType="string">
+<NormDiscrete field="$L-num" value="&lt;50"/>
+</DerivedField>
+</NeuralOutput>
+<NeuralOutput outputNeuron="46">
+<DerivedField optype="categorical" dataType="string">
+<NormDiscrete field="$L-num" value="&gt;50_1"/>
+</DerivedField>
+</NeuralOutput>
+</NeuralOutputs>
+</NeuralNetwork>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RULESET.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RULESET.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_RULESET.xml	(revision 29)
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="10">
+<DataField name="cp" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="asympt" property="valid"/>
+<Value value="atyp_angina" property="valid"/>
+<Value value="non_anginal" property="valid"/>
+<Value value="typ_angina" property="valid"/>
+</DataField>
+<DataField name="restecg" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="left_vent_hyper" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="st_t_wave_abnormality" property="valid"/>
+</DataField>
+<DataField name="thal" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="fixed_defect" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="reversable_defect" property="valid"/>
+</DataField>
+<DataField name="trestbps" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="ca" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="slope" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="down" property="valid"/>
+<Value value="flat" property="valid"/>
+<Value value="up" property="valid"/>
+</DataField>
+<DataField name="oldpeak" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="exang" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="no" property="valid"/>
+<Value value="yes" property="valid"/>
+</DataField>
+<DataField name="$L-num" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="&lt;50" property="valid"/>
+<Value value="&gt;50_1" property="valid"/>
+</DataField>
+<DataField name="$CC-num" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+</DataDictionary>
+<RuleSetModel modelName="HEART_RULESET" functionName="classification" algorithmName="RuleSet">
+<MiningSchema>
+<MiningField name="cp" usageType="active"/>
+<MiningField name="restecg" usageType="active"/>
+<MiningField name="thal" usageType="active"/>
+<MiningField name="trestbps" usageType="active"/>
+<MiningField name="ca" usageType="active"/>
+<MiningField name="slope" usageType="active"/>
+<MiningField name="oldpeak" usageType="active"/>
+<MiningField name="exang" usageType="active"/>
+<MiningField name="$L-num" usageType="predicted"/>
+<MiningField name="$CC-num" usageType="supplementary"/>
+</MiningSchema>
+<RuleSet defaultScore="&gt;50_1" defaultConfidence="0.5" recordCount="650.0">
+<RuleSelectionMethod criterion="weightedSum"/>
+<SimpleRule score="&lt;50" recordCount="23" nbCorrect="23.0" confidence="0.96" weight="0.96" id="0">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="atyp_angina"/>
+<SimplePredicate field="restecg" operator="equal" value="normal"/>
+<SimplePredicate field="thal" operator="equal" value="normal"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="22" nbCorrect="21.0" confidence="0.9166666666666666" weight="0.9166666666666666" id="1">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="non_anginal"/>
+<SimplePredicate field="ca" operator="lessOrEqual" value="0"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="122"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="62" nbCorrect="57.0" confidence="0.90625" weight="0.90625" id="2">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="non_anginal"/>
+<SimplePredicate field="thal" operator="equal" value="normal"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="49" nbCorrect="45.0" confidence="0.9019607843137255" weight="0.9019607843137255" id="3">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="non_anginal"/>
+<SimplePredicate field="slope" operator="equal" value="up"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="114" nbCorrect="102.0" confidence="0.8879310344827587" weight="0.8879310344827587" id="4">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="thal" operator="equal" value="normal"/>
+<SimplePredicate field="ca" operator="lessOrEqual" value="0"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="59" nbCorrect="52.0" confidence="0.8688524590163934" weight="0.8688524590163934" id="5">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="restecg" operator="equal" value="normal"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="136"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.600000071525574"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="5" nbCorrect="5.0" confidence="0.8571428571428571" weight="0.8571428571428571" id="6">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="exang" operator="equal" value="no"/>
+<SimplePredicate field="thal" operator="equal" value="fixed_defect"/>
+<SimplePredicate field="ca" operator="lessOrEqual" value="0"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="40" nbCorrect="34.0" confidence="0.8333333333333334" weight="0.8333333333333334" id="7">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="atyp_angina"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.700000023841858"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&lt;50" recordCount="13" nbCorrect="11.0" confidence="0.8" weight="0.8" id="8">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="typ_angina"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="138"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&gt;50_1" recordCount="5" nbCorrect="5.0" confidence="0.8571428571428571" weight="0.8571428571428571" id="9">
+<CompoundPredicate booleanOperator="and">
+<SimplePredicate field="cp" operator="equal" value="atyp_angina"/>
+<SimplePredicate field="restecg" operator="equal" value="left_vent_hyper"/>
+<SimplePredicate field="exang" operator="equal" value="no"/>
+<SimplePredicate field="ca" operator="greaterThan" value="0"/>
+</CompoundPredicate>
+</SimpleRule>
+<SimpleRule score="&gt;50_1" recordCount="117" nbCorrect="89.0" confidence="0.7563025210084033" weight="0.7563025210084033" id="10">
+<SimplePredicate field="thal" operator="equal" value="reversable_defect"/>
+</SimpleRule>
+<SimpleRule score="&gt;50_1" recordCount="123" nbCorrect="92.0" confidence="0.744" weight="0.744" id="11">
+<SimplePredicate field="ca" operator="greaterThan" value="0"/>
+</SimpleRule>
+<SimpleRule score="&gt;50_1" recordCount="18" nbCorrect="12.0" confidence="0.65" weight="0.65" id="12">
+<SimplePredicate field="thal" operator="equal" value="fixed_defect"/>
+</SimpleRule>
+</RuleSet>
+</RuleSetModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_TREE.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_TREE.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/HEART_TREE.xml	(revision 29)
@@ -0,0 +1,622 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="15">
+<DataField name="age" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="sex" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="female" property="valid"/>
+<Value value="male" property="valid"/>
+</DataField>
+<DataField name="cp" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="asympt" property="valid"/>
+<Value value="atyp_angina" property="valid"/>
+<Value value="non_anginal" property="valid"/>
+<Value value="typ_angina" property="valid"/>
+</DataField>
+<DataField name="trestbps" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="chol" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="fbs" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="f" property="valid"/>
+<Value value="t" property="valid"/>
+</DataField>
+<DataField name="restecg" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="left_vent_hyper" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="st_t_wave_abnormality" property="valid"/>
+</DataField>
+<DataField name="thalach" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="exang" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="no" property="valid"/>
+<Value value="yes" property="valid"/>
+</DataField>
+<DataField name="oldpeak" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="slope" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="down" property="valid"/>
+<Value value="flat" property="valid"/>
+<Value value="up" property="valid"/>
+</DataField>
+<DataField name="ca" optype="continuous" dataType="integer">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="thal" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="fixed_defect" property="valid"/>
+<Value value="normal" property="valid"/>
+<Value value="reversable_defect" property="valid"/>
+</DataField>
+<DataField name="$R-num" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="&lt;50" property="valid"/>
+<Value value="&gt;50_1" property="valid"/>
+</DataField>
+<DataField name="$RC-num" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+</DataDictionary>
+<TreeModel modelName="HEART_TREE" functionName="classification" algorithmName="C&amp;R Tree">
+<Extension name="missingValueStrategy" value="defaultChild"/>
+<Extension name="missingValuePenalty" value="0.9"/>
+<MiningSchema>
+<MiningField name="age" usageType="active"/>
+<MiningField name="sex" usageType="active"/>
+<MiningField name="cp" usageType="active"/>
+<MiningField name="trestbps" usageType="active"/>
+<MiningField name="chol" usageType="active"/>
+<MiningField name="fbs" usageType="active"/>
+<MiningField name="restecg" usageType="active"/>
+<MiningField name="thalach" usageType="active"/>
+<MiningField name="exang" usageType="active"/>
+<MiningField name="oldpeak" usageType="active"/>
+<MiningField name="slope" usageType="active"/>
+<MiningField name="ca" usageType="active"/>
+<MiningField name="thal" usageType="active"/>
+<MiningField name="$R-num" usageType="predicted"/>
+<MiningField name="$RC-num" usageType="supplementary"/>
+</MiningSchema>
+<Node score="&lt;50" recordCount="303" id="0">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="165.0891089108911"/>
+<True/>
+<ScoreDistribution value="&lt;50" recordCount="165">
+<Extension name="confidence" value="0.544262295081967"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="138">
+<Extension name="confidence" value="0.455737704918033"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="167" id="1">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="130.5568862275449"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="thal" booleanOperator="isIn">
+<Array n="1" type="string">"normal"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="greaterThan" value="150.5"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="3" type="string">"atyp_angina" "non_anginal" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"no"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="1.55"/>
+<SimpleSetPredicate field="sex" booleanOperator="isIn">
+<Array n="1" type="string">"female"</Array>
+</SimpleSetPredicate>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="130">
+<Extension name="confidence" value="0.775147928994083"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="37">
+<Extension name="confidence" value="0.224852071005917"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="118" id="2">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="105.77966101694915"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="ca" operator="lessOrEqual" value="0.5"/>
+<SimplePredicate field="age" operator="lessOrEqual" value="66.5"/>
+<SimplePredicate field="thalach" operator="greaterThan" value="134"/>
+<SimplePredicate field="chol" operator="lessOrEqual" value="405.5"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="3.55"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="3" type="string">"asympt" "atyp_angina" "non_anginal"</Array>
+</SimpleSetPredicate>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="105">
+<Extension name="confidence" value="0.883333333333333"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="13">
+<Extension name="confidence" value="0.116666666666667"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="113" id="3">
+<Extension name="nrCorrect" value="103.82300884955752"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="158"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="103">
+<Extension name="confidence" value="0.904347826086956"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="10">
+<Extension name="confidence" value="0.095652173913043"/>
+</ScoreDistribution>
+</Node>
+<Node score="&gt;50_1" recordCount="5" id="4">
+<Extension name="nrCorrect" value="3.2"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="trestbps" operator="greaterThan" value="158"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="2">
+<Extension name="confidence" value="0.428571428571429"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="3">
+<Extension name="confidence" value="0.571428571428571"/>
+</ScoreDistribution>
+</Node>
+</Node>
+<Node score="&lt;50" recordCount="49" id="5">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="25.020408163265305"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="ca" operator="greaterThan" value="0.5"/>
+<SimplePredicate field="age" operator="greaterThan" value="66.5"/>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="134"/>
+<SimplePredicate field="chol" operator="greaterThan" value="405.5"/>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="3.55"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"typ_angina"</Array>
+</SimpleSetPredicate>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="25">
+<Extension name="confidence" value="0.509803921568627"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="24">
+<Extension name="confidence" value="0.490196078431373"/>
+</ScoreDistribution>
+<Node score="&gt;50_1" recordCount="20" id="6">
+<Extension name="nrCorrect" value="17.7"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"asympt"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"yes"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="125.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="2" type="string">"down" "flat"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="0.85"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="115"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="3">
+<Extension name="confidence" value="0.181818181818182"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="17">
+<Extension name="confidence" value="0.818181818181818"/>
+</ScoreDistribution>
+</Node>
+<Node score="&lt;50" recordCount="29" id="7">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="22.517241379310345"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="3" type="string">"atyp_angina" "non_anginal" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"no"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="greaterThan" value="125.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"up"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.85"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="115"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="22">
+<Extension name="confidence" value="0.741935483870968"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="7">
+<Extension name="confidence" value="0.258064516129032"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="11" id="8">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="6.09090909090909"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="chol" operator="lessOrEqual" value="237.5"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="2" type="string">"atyp_angina" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="greaterThan" value="153"/>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="2.25"/>
+<SimpleSetPredicate field="sex" booleanOperator="isIn">
+<Array n="1" type="string">"male"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="age" operator="lessOrEqual" value="41.5"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="6">
+<Extension name="confidence" value="0.538461538461538"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="5">
+<Extension name="confidence" value="0.461538461538462"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="5" id="9">
+<Extension name="nrCorrect" value="4.6"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="age" operator="lessOrEqual" value="55.5"/>
+<SimplePredicate field="chol" operator="lessOrEqual" value="202.5"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="127.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"up"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"non_anginal"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="128.5"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="4">
+<Extension name="confidence" value="0.714285714285714"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="1">
+<Extension name="confidence" value="0.285714285714286"/>
+</ScoreDistribution>
+</Node>
+<Node score="&gt;50_1" recordCount="6" id="10">
+<Extension name="nrCorrect" value="4.333333333333333"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="age" operator="greaterThan" value="55.5"/>
+<SimplePredicate field="chol" operator="greaterThan" value="202.5"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="127.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"flat"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="2" type="string">"atyp_angina" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="greaterThan" value="128.5"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="2">
+<Extension name="confidence" value="0.375"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="4">
+<Extension name="confidence" value="0.625"/>
+</ScoreDistribution>
+</Node>
+</Node>
+<Node score="&lt;50" recordCount="18" id="11">
+<Extension name="nrCorrect" value="16.77777777777778"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="chol" operator="greaterThan" value="237.5"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"non_anginal"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="153"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="2.25"/>
+<SimpleSetPredicate field="sex" booleanOperator="isIn">
+<Array n="1" type="string">"female"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="age" operator="greaterThan" value="41.5"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="16">
+<Extension name="confidence" value="0.85"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="2">
+<Extension name="confidence" value="0.15"/>
+</ScoreDistribution>
+</Node>
+</Node>
+</Node>
+</Node>
+<Node score="&gt;50_1" recordCount="136" id="12">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="101.48529411764706"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="thal" booleanOperator="isIn">
+<Array n="2" type="string">"fixed_defect" "reversable_defect"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="150.5"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"asympt"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"yes"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="1.55"/>
+<SimpleSetPredicate field="sex" booleanOperator="isIn">
+<Array n="1" type="string">"male"</Array>
+</SimpleSetPredicate>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="35">
+<Extension name="confidence" value="0.260869565217391"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="101">
+<Extension name="confidence" value="0.739130434782609"/>
+</ScoreDistribution>
+<Node score="&gt;50_1" recordCount="90" id="13">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="80.77777777777777"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"asympt"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="172"/>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"yes"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="greaterThan" value="106.5"/>
+<SimplePredicate field="age" operator="lessOrEqual" value="66.5"/>
+<SimplePredicate field="chol" operator="greaterThan" value="128.5"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="10">
+<Extension name="confidence" value="0.119565217391304"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="80">
+<Extension name="confidence" value="0.880434782608696"/>
+</ScoreDistribution>
+<Node score="&gt;50_1" recordCount="22" id="14">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="14.272727272727273"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.55"/>
+<SimplePredicate field="thalach" operator="greaterThan" value="146.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"up"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="109"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="8">
+<Extension name="confidence" value="0.375"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="14">
+<Extension name="confidence" value="0.625"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="12" id="15">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="7.166666666666668"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="chol" operator="lessOrEqual" value="237.5"/>
+<SimpleSetPredicate field="restecg" booleanOperator="isIn">
+<Array n="1" type="string">"normal"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"no"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"up"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="ca" operator="lessOrEqual" value="1.5"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="122"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="7">
+<Extension name="confidence" value="0.571428571428571"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="5">
+<Extension name="confidence" value="0.428571428571429"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="4" id="16">
+<Extension name="nrCorrect" value="5"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="thalach" operator="lessOrEqual" value="152"/>
+<SimpleSetPredicate field="thal" booleanOperator="isIn">
+<Array n="1" type="string">"fixed_defect"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="0.05"/>
+<SimplePredicate field="age" operator="greaterThan" value="63"/>
+<SimpleSetPredicate field="fbs" booleanOperator="isIn">
+<Array n="1" type="string">"t"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="ca" operator="greaterThan" value="2"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="4">
+<Extension name="confidence" value="0.833333333333333"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+</Node>
+<Node score="&gt;50_1" recordCount="8" id="17">
+<Extension name="nrCorrect" value="5.25"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="thalach" operator="greaterThan" value="152"/>
+<SimpleSetPredicate field="thal" booleanOperator="isIn">
+<Array n="1" type="string">"reversable_defect"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.05"/>
+<SimplePredicate field="age" operator="lessOrEqual" value="63"/>
+<SimpleSetPredicate field="fbs" booleanOperator="isIn">
+<Array n="1" type="string">"f"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="ca" operator="lessOrEqual" value="2"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="3">
+<Extension name="confidence" value="0.4"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="5">
+<Extension name="confidence" value="0.6"/>
+</ScoreDistribution>
+</Node>
+</Node>
+<Node score="&gt;50_1" recordCount="10" id="18">
+<Extension name="nrCorrect" value="9.8"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="chol" operator="greaterThan" value="237.5"/>
+<SimpleSetPredicate field="restecg" booleanOperator="isIn">
+<Array n="1" type="string">"left_vent_hyper"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"yes"</Array>
+</SimpleSetPredicate>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"flat"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="ca" operator="greaterThan" value="1.5"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="122"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="1">
+<Extension name="confidence" value="0.166666666666667"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="9">
+<Extension name="confidence" value="0.833333333333333"/>
+</ScoreDistribution>
+</Node>
+</Node>
+<Node score="&gt;50_1" recordCount="68" id="19">
+<Extension name="nrCorrect" value="66.94117647058823"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="oldpeak" operator="greaterThan" value="0.55"/>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="146.5"/>
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="2" type="string">"down" "flat"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="greaterThan" value="109"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="2">
+<Extension name="confidence" value="0.042857142857143"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="66">
+<Extension name="confidence" value="0.957142857142857"/>
+</ScoreDistribution>
+</Node>
+</Node>
+<Node score="&lt;50" recordCount="46" id="20">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="25.08695652173913"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="3" type="string">"atyp_angina" "non_anginal" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="greaterThan" value="172"/>
+<SimpleSetPredicate field="exang" booleanOperator="isIn">
+<Array n="1" type="string">"no"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="106.5"/>
+<SimplePredicate field="age" operator="greaterThan" value="66.5"/>
+<SimplePredicate field="chol" operator="lessOrEqual" value="128.5"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="25">
+<Extension name="confidence" value="0.541666666666667"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="21">
+<Extension name="confidence" value="0.458333333333333"/>
+</ScoreDistribution>
+<Node score="&lt;50" recordCount="29" id="21">
+<Extension name="nrCorrect" value="21.448275862068964"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="ca" operator="lessOrEqual" value="0.5"/>
+<SimplePredicate field="thalach" operator="greaterThan" value="113.5"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="1.95"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="2" type="string">"atyp_angina" "typ_angina"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="age" operator="lessOrEqual" value="67.5"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="97.5"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="21">
+<Extension name="confidence" value="0.709677419354839"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="8">
+<Extension name="confidence" value="0.290322580645161"/>
+</ScoreDistribution>
+</Node>
+<Node score="&gt;50_1" recordCount="17" id="22">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="13.52941176470588"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="ca" operator="greaterThan" value="0.5"/>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="113.5"/>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="1.95"/>
+<SimpleSetPredicate field="cp" booleanOperator="isIn">
+<Array n="1" type="string">"non_anginal"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="age" operator="greaterThan" value="67.5"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="97.5"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="4">
+<Extension name="confidence" value="0.263157894736842"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="13">
+<Extension name="confidence" value="0.736842105263158"/>
+</ScoreDistribution>
+<Node score="&gt;50_1" recordCount="12" id="23">
+<Extension name="nrCorrect" value="11.833333333333332"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"flat"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="lessOrEqual" value="150.5"/>
+<SimplePredicate field="oldpeak" operator="greaterThan" value="0.3"/>
+<SimplePredicate field="trestbps" operator="greaterThan" value="103"/>
+<SimplePredicate field="chol" operator="greaterThan" value="157"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="1">
+<Extension name="confidence" value="0.142857142857143"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="11">
+<Extension name="confidence" value="0.857142857142857"/>
+</ScoreDistribution>
+</Node>
+<Node score="&lt;50" recordCount="5" id="24">
+<Extension name="nrCorrect" value="3.2"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimpleSetPredicate field="slope" booleanOperator="isIn">
+<Array n="1" type="string">"up"</Array>
+</SimpleSetPredicate>
+<SimplePredicate field="thalach" operator="greaterThan" value="150.5"/>
+<SimplePredicate field="oldpeak" operator="lessOrEqual" value="0.3"/>
+<SimplePredicate field="trestbps" operator="lessOrEqual" value="103"/>
+<SimplePredicate field="chol" operator="lessOrEqual" value="157"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="&lt;50" recordCount="3">
+<Extension name="confidence" value="0.571428571428571"/>
+</ScoreDistribution>
+<ScoreDistribution value="&gt;50_1" recordCount="2">
+<Extension name="confidence" value="0.428571428571429"/>
+</ScoreDistribution>
+</Node>
+</Node>
+</Node>
+</Node>
+</Node>
+</TreeModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_MLP.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_MLP.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_MLP.xml	(revision 29)
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="6">
+<DataField name="petal_length" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="petal_width" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="sepal_length" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="sepal_width" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+<DataField name="class" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Extension name="measure" value="set"/>
+<Value value="Iris-setosa" property="valid"/>
+<Value value="Iris-versicolor" property="valid"/>
+<Value value="Iris-virginica" property="valid"/>
+</DataField>
+<DataField name="$NC-SPECIES" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+<Extension name="measure" value="range"/>
+</DataField>
+</DataDictionary>
+<NeuralNetwork modelName="IRIS_MLP" functionName="classification" algorithmName="Neural Net" activationFunction="logistic">
+<Extension name="normalizationMethod" value="limitedDifference"/>
+<MiningSchema>
+<MiningField name="petal_length" usageType="active" missingValueReplacement="3.95" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="1.0" highValue="6.9"/>
+<MiningField name="petal_width" usageType="active" missingValueReplacement="1.3" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="0.1" highValue="2.5"/>
+<MiningField name="sepal_length" usageType="active" missingValueReplacement="6.1" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="4.3" highValue="7.9"/>
+<MiningField name="sepal_width" usageType="active" missingValueReplacement="3.2" missingValueTreatment="asMedian" outliers="asExtremeValues" lowValue="2.0" highValue="4.4"/>
+<MiningField name="class" usageType="predicted"/>
+<MiningField name="$NC-SPECIES" usageType="supplementary"/>
+</MiningSchema>
+<NeuralInputs>
+<NeuralInput id="0">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="petal_length">
+<LinearNorm orig="1" norm="0"/>
+<LinearNorm orig="6.9" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="1">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="petal_width">
+<LinearNorm orig="0.1" norm="0"/>
+<LinearNorm orig="2.5" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="2">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="sepal_length">
+<LinearNorm orig="4.3" norm="0"/>
+<LinearNorm orig="7.9" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+<NeuralInput id="3">
+<DerivedField optype="continuous" dataType="double">
+<NormContinuous field="sepal_width">
+<LinearNorm orig="2" norm="0"/>
+<LinearNorm orig="4.4" norm="1"/>
+</NormContinuous>
+</DerivedField>
+</NeuralInput>
+</NeuralInputs>
+<NeuralLayer>
+<Neuron id="4" bias="2.03473228253">
+<Con from="0" weight="-4.0256456408"/>
+<Con from="1" weight="-3.83730095974"/>
+<Con from="2" weight="0.0624800821416"/>
+<Con from="3" weight="2.40206472093"/>
+</Neuron>
+<Neuron id="5" bias="1.6526137028">
+<Con from="0" weight="-5.01759484187"/>
+<Con from="1" weight="-5.23173402014"/>
+<Con from="2" weight="-1.80228751097"/>
+<Con from="3" weight="2.98662625446"/>
+</Neuron>
+<Neuron id="6" bias="-11.6218815784">
+<Con from="0" weight="11.3795015654"/>
+<Con from="1" weight="9.67298427591"/>
+<Con from="2" weight="-2.10906925011"/>
+<Con from="3" weight="-3.58414535245"/>
+</Neuron>
+</NeuralLayer>
+<NeuralLayer>
+<Neuron id="7" bias="-4.54626929708">
+<Con from="4" weight="4.36547390082"/>
+<Con from="5" weight="4.69477556292"/>
+<Con from="6" weight="-4.37727158969"/>
+</Neuron>
+<Neuron id="8" bias="4.85147554911">
+<Con from="4" weight="-2.51631420956"/>
+<Con from="5" weight="-6.71322276698"/>
+<Con from="6" weight="-9.34181840082"/>
+</Neuron>
+<Neuron id="9" bias="-2.95716683639">
+<Con from="4" weight="-6.98136941982"/>
+<Con from="5" weight="-4.64823127158"/>
+<Con from="6" weight="7.63041165053"/>
+</Neuron>
+</NeuralLayer>
+<NeuralOutputs>
+<NeuralOutput outputNeuron="7">
+<DerivedField optype="categorical" dataType="string">
+<NormDiscrete field="class" value="Iris-setosa"/>
+</DerivedField>
+</NeuralOutput>
+<NeuralOutput outputNeuron="8">
+<DerivedField optype="categorical" dataType="string">
+<NormDiscrete field="class" value="Iris-versicolor"/>
+</DerivedField>
+</NeuralOutput>
+<NeuralOutput outputNeuron="9">
+<DerivedField optype="categorical" dataType="string">
+<NormDiscrete field="class" value="Iris-virginica"/>
+</DerivedField>
+</NeuralOutput>
+</NeuralOutputs>
+</NeuralNetwork>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_TREE.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_TREE.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/IRIS_TREE.xml	(revision 29)
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<Header copyright="Copyright (c) Integral Solutions Ltd., 1994 - 2005. All rights reserved.">
+<Application name="Clementine" version="10.0"/>
+<Annotation>Exported with PMML extensions for use with SPSS SmartScore</Annotation>
+</Header>
+<DataDictionary numberOfFields="6">
+<DataField name="sepal_length" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="sepal_width" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="petal_length" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="petal_width" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+<DataField name="class" optype="categorical" dataType="string">
+<Extension name="storageType" value="string"/>
+<Value value="Iris-setosa" property="valid"/>
+<Value value="Iris-versicolor" property="valid"/>
+<Value value="Iris-virginica" property="valid"/>
+</DataField>
+<DataField name="$RC-SPECIES" optype="continuous" dataType="double">
+<Extension name="storageType" value="numeric"/>
+</DataField>
+</DataDictionary>
+<TreeModel modelName="IRIS_TREE" functionName="classification" algorithmName="C&amp;R Tree">
+<Extension name="missingValueStrategy" value="defaultChild"/>
+<Extension name="missingValuePenalty" value="0.9"/>
+<MiningSchema>
+<MiningField name="sepal_length" usageType="active"/>
+<MiningField name="sepal_width" usageType="active"/>
+<MiningField name="petal_length" usageType="active"/>
+<MiningField name="petal_width" usageType="active"/>
+<MiningField name="class" usageType="predicted"/>
+<MiningField name="$RC-SPECIES" usageType="supplementary"/>
+</MiningSchema>
+<Node score="Iris-setosa" recordCount="150" id="0">
+<Extension name="defaultChild" value="1"/>
+<Extension name="nrCorrect" value="49.666666666666664"/>
+<True/>
+<ScoreDistribution value="Iris-setosa" recordCount="50">
+<Extension name="confidence" value="0.333333333333333"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="50">
+<Extension name="confidence" value="0.333333333333333"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="50">
+<Extension name="confidence" value="0.333333333333333"/>
+</ScoreDistribution>
+<Node score="Iris-setosa" recordCount="50" id="1">
+<Extension name="nrCorrect" value="51"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_length" operator="lessOrEqual" value="2.45"/>
+<SimplePredicate field="petal_width" operator="lessOrEqual" value="0.8"/>
+<SimplePredicate field="sepal_length" operator="lessOrEqual" value="5.45"/>
+<SimplePredicate field="sepal_width" operator="greaterThan" value="3.35"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="50">
+<Extension name="confidence" value="0.962264150943396"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+</Node>
+<Node score="Iris-versicolor" recordCount="100" id="2">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="50"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_length" operator="greaterThan" value="2.45"/>
+<SimplePredicate field="petal_width" operator="greaterThan" value="0.8"/>
+<SimplePredicate field="sepal_length" operator="greaterThan" value="5.45"/>
+<SimplePredicate field="sepal_width" operator="lessOrEqual" value="3.35"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="50">
+<Extension name="confidence" value="0.495145631067961"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="50">
+<Extension name="confidence" value="0.495145631067961"/>
+</ScoreDistribution>
+<Node score="Iris-versicolor" recordCount="54" id="3">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="49.81481481481482"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_width" operator="lessOrEqual" value="1.75"/>
+<SimplePredicate field="petal_length" operator="lessOrEqual" value="4.75"/>
+<SimplePredicate field="sepal_length" operator="lessOrEqual" value="6.15"/>
+<SimplePredicate field="sepal_width" operator="lessOrEqual" value="2.95"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="49">
+<Extension name="confidence" value="0.87719298245614"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="5">
+<Extension name="confidence" value="0.105263157894737"/>
+</ScoreDistribution>
+<Node score="Iris-versicolor" recordCount="48" id="4">
+<Extension name="nrCorrect" value="47.95833333333333"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_length" operator="lessOrEqual" value="4.95"/>
+<SimplePredicate field="sepal_length" operator="lessOrEqual" value="7.1"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="47">
+<Extension name="confidence" value="0.941176470588235"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="1">
+<Extension name="confidence" value="0.03921568627451"/>
+</ScoreDistribution>
+</Node>
+<Node score="Iris-virginica" recordCount="6" id="5">
+<Extension name="defaultChild" value="0"/>
+<Extension name="nrCorrect" value="4.333333333333333"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_length" operator="greaterThan" value="4.95"/>
+<SimplePredicate field="sepal_length" operator="greaterThan" value="7.1"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="2">
+<Extension name="confidence" value="0.333333333333333"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="4">
+<Extension name="confidence" value="0.555555555555556"/>
+</ScoreDistribution>
+<Node score="Iris-virginica" recordCount="3" id="6">
+<Extension name="nrCorrect" value="4"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_width" operator="lessOrEqual" value="1.55"/>
+<SimplePredicate field="sepal_width" operator="lessOrEqual" value="2.65"/>
+<SimplePredicate field="sepal_length" operator="lessOrEqual" value="6.5"/>
+<SimplePredicate field="petal_length" operator="lessOrEqual" value="5.7"/>
+<True/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="3">
+<Extension name="confidence" value="0.666666666666667"/>
+</ScoreDistribution>
+</Node>
+<Node score="Iris-versicolor" recordCount="3" id="7">
+<Extension name="nrCorrect" value="2.333333333333333"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_width" operator="greaterThan" value="1.55"/>
+<SimplePredicate field="sepal_width" operator="greaterThan" value="2.65"/>
+<SimplePredicate field="sepal_length" operator="greaterThan" value="6.5"/>
+<SimplePredicate field="petal_length" operator="greaterThan" value="5.7"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="2">
+<Extension name="confidence" value="0.5"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="1">
+<Extension name="confidence" value="0.333333333333333"/>
+</ScoreDistribution>
+</Node>
+</Node>
+</Node>
+<Node score="Iris-virginica" recordCount="46" id="8">
+<Extension name="nrCorrect" value="45.95652173913044"/>
+<CompoundPredicate booleanOperator="surrogate">
+<SimplePredicate field="petal_width" operator="greaterThan" value="1.75"/>
+<SimplePredicate field="petal_length" operator="greaterThan" value="4.75"/>
+<SimplePredicate field="sepal_length" operator="greaterThan" value="6.15"/>
+<SimplePredicate field="sepal_width" operator="greaterThan" value="2.95"/>
+<False/>
+</CompoundPredicate>
+<ScoreDistribution value="Iris-setosa" recordCount="0">
+<Extension name="confidence" value="0"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-versicolor" recordCount="1">
+<Extension name="confidence" value="0.040816326530612"/>
+</ScoreDistribution>
+<ScoreDistribution value="Iris-virginica" recordCount="45">
+<Extension name="confidence" value="0.938775510204082"/>
+</ScoreDistribution>
+</Node>
+</Node>
+</Node>
+</TreeModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c.arff	(revision 29)
@@ -0,0 +1,28 @@
+@relation cleveland-14-heart-disease
+@attribute 'age' real
+@attribute 'sex' { female, male}
+@attribute 'cp' { typ_angina, asympt, non_anginal, atyp_angina}
+@attribute 'trestbps' real
+@attribute 'chol' real
+@attribute 'fbs' { t, f}
+@attribute 'restecg' { left_vent_hyper, normal, st_t_wave_abnormality}
+@attribute 'thalach' real
+@attribute 'exang' { no, yes}
+@attribute 'oldpeak' real
+@attribute 'slope' { up, flat, down}
+@attribute 'ca' real
+@attribute 'thal' { fixed_defect, normal, reversable_defect}
+@attribute '$L-num' { '<50', '>50_1'}
+@data
+63,male,typ_angina,145,233,t,left_vent_hyper,150,no,2.3,down,0,fixed_defect,'<50'
+67,male,asympt,160,286,f,left_vent_hyper,108,yes,1.5,flat,3,normal,'>50_1'
+67,male,asympt,120,229,f,left_vent_hyper,129,yes,2.6,flat,2,reversable_defect,'>50_1'
+37,male,non_anginal,130,250,f,normal,187,no,3.5,down,0,normal,'<50'
+41,female,atyp_angina,130,204,f,left_vent_hyper,172,no,1.4,up,0,normal,'<50'
+56,male,atyp_angina,120,236,f,normal,178,no,0.8,up,0,normal,'<50'
+62,female,asympt,140,268,f,left_vent_hyper,160,no,3.6,down,2,normal,'>50_1'
+57,female,asympt,120,354,f,normal,163,yes,0.6,up,0,normal,'<50'
+63,male,asympt,130,254,f,left_vent_hyper,147,no,1.4,flat,1,reversable_defect,'>50_1'
+53,male,asympt,140,203,t,left_vent_hyper,155,yes,3.1,down,0,reversable_defect,'>50_1'
+57,male,asympt,140,192,f,normal,148,no,0.4,flat,0,fixed_defect,'<50'
+56,female,atyp_angina,140,294,f,left_vent_hyper,153,no,1.3,flat,0,normal,'<50'
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c2.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c2.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/heart-c2.arff	(revision 29)
@@ -0,0 +1,28 @@
+@relation cleveland-14-heart-disease
+@attribute 'age' real
+@attribute 'sex' { female, male}
+@attribute 'cp' { typ_angina, asympt, non_anginal, atyp_angina}
+@attribute 'trestbps' real
+@attribute 'chol' real
+@attribute 'fbs' { t, f}
+@attribute 'restecg' { left_vent_hyper, normal, st_t_wave_abnormality}
+@attribute 'thalach' real
+@attribute 'exang' { no, yes}
+@attribute 'oldpeak' real
+@attribute 'slope' { up, flat, down}
+@attribute 'ca' real
+@attribute 'thal' { fixed_defect, normal, reversable_defect}
+@attribute '$R-num' { '<50', '>50_1'}
+@data
+63,male,typ_angina,145,233,t,left_vent_hyper,150,no,2.3,down,0,fixed_defect,'<50'
+67,male,asympt,160,286,f,left_vent_hyper,108,yes,1.5,flat,3,normal,'>50_1'
+67,male,asympt,120,229,f,left_vent_hyper,129,yes,2.6,flat,2,reversable_defect,'>50_1'
+37,male,non_anginal,130,250,f,normal,187,no,3.5,down,0,normal,'<50'
+41,female,atyp_angina,130,204,f,left_vent_hyper,172,no,1.4,up,0,normal,'<50'
+56,male,atyp_angina,120,236,f,normal,178,no,0.8,up,0,normal,'<50'
+62,female,asympt,140,268,f,left_vent_hyper,160,no,3.6,down,2,normal,'>50_1'
+57,female,asympt,120,354,f,normal,163,yes,0.6,up,0,normal,'<50'
+63,male,asympt,130,254,f,left_vent_hyper,147,no,1.4,flat,1,reversable_defect,'>50_1'
+53,male,asympt,140,203,t,left_vent_hyper,155,yes,3.1,down,0,reversable_defect,'>50_1'
+57,male,asympt,140,192,f,normal,148,no,0.4,flat,0,fixed_defect,'<50'
+56,female,atyp_angina,140,294,f,left_vent_hyper,153,no,1.3,flat,0,normal,'<50'
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/iris.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/iris.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/iris.arff	(revision 29)
@@ -0,0 +1,21 @@
+@relation Iris_NN
+
+@attribute sepal_length numeric
+@attribute sepal_width numeric
+@attribute petal_length numeric
+@attribute petal_width numeric
+@attribute class {Iris-setosa,Iris-versicolor,Iris-virginica}
+
+@data
+5.1,3.5,1.4,0.2,Iris-setosa
+4.9,3,1.4,0.2,Iris-setosa
+4.7,3.2,1.3,0.2,Iris-setosa
+4.6,3.1,1.5,0.2,Iris-setosa
+7,3.2,4.7,1.4,Iris-versicolor
+6.4,3.2,4.5,1.5,Iris-versicolor
+6.9,3.1,4.9,1.5,Iris-versicolor
+5.5,2.3,4,1.3,Iris-versicolor
+6.3,3.3,6,2.5,Iris-virginica
+5.8,2.7,5.1,1.9,Iris-virginica
+7.1,3,5.9,2.1,Iris-virginica
+6.3,2.9,5.6,1.8,Iris-virginica
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/linear_regression_model.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/linear_regression_model.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/linear_regression_model.xml	(revision 29)
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dmg.org/PMML-3_0 http://www.dmg.org/v3-0/pmml-3-0.xsd">
+ <Header copyright="Copyright IBM Corp. 2002, 2005 All Rights Reserved">
+  <Application name="IBM DB2 Intelligent Miner" version="9.1"/>
+  <Timestamp>2006-03-21 13:12:59</Timestamp>
+ </Header>
+ <MiningBuildTask>
+  <Extension>&lt;?xml version="1.0" encoding="UTF-8" ?>
+&lt;DataMining>&lt;MiningTask trainData="trainData">&lt;MiningData name="trainData" tableName="RASPL.INPUT_017">&lt;Column alias="buoy_day_ID" name="buoy_day_ID" sqlType="SMALLINT"/>&lt;Column alias="buoy" name="buoy" sqlType="SMALLINT"/>&lt;Column alias="day" name="day" sqlType="SMALLINT"/>&lt;Column alias="latitude" name="latitude" sqlType="DECIMAL"/>&lt;Column alias="longitude" name="longitude" sqlType="DECIMAL"/>&lt;Column alias="zon_winds" name="zon_winds" sqlType="DECIMAL"/>&lt;Column alias="mer_winds" name="mer_winds" sqlType="DECIMAL"/>&lt;Column alias="humidity" name="humidity" sqlType="DECIMAL"/>&lt;Column alias="airtemp" name="airtemp" sqlType="DECIMAL"/>&lt;Column alias="s_s_temp" name="s_s_temp" sqlType="DECIMAL"/>&lt;/MiningData>&lt;MiningSettings algorithm="Linear">&lt;LogicalDataSpec>&lt;MiningField name="buoy_day_ID" type="numerical"/>&lt;MiningField name="buoy" type="numerical"/>&lt;MiningField name="day" type="numerical"/>&lt;MiningField name="latitude" type="numerical"/>&lt;MiningField name="longitude" type="numerical"/>&lt;MiningField name="zon_winds" type="numerical"/>&lt;MiningField name="mer_winds" type="numerical"/>&lt;MiningField name="humidity" type="numerical"/>&lt;MiningField name="airtemp" type="numerical"/>&lt;MiningField name="s_s_temp" type="numerical"/>&lt;/LogicalDataSpec>&lt;RegSettings/>&lt;FieldUsageEntry field="airtemp" usageType="target"/>&lt;/MiningSettings>&lt;TaskControl errorTable="IDMMX.ERRORTABLE" progressTable="IDMMX.PROGRESSTABLE"/>&lt;/MiningTask>&lt;/DataMining></Extension>
+ </MiningBuildTask>
+ <DataDictionary numberOfFields="10">
+  <DataField name="buoy" displayName="buoy" optype="continuous">
+   <Value value="1"/>
+   <Value value="2"/>
+   <Value value="3"/>
+   <Value value="4"/>
+   <Value value="5"/>
+   <Value value="6"/>
+   <Value value="7"/>
+   <Value value="8"/>
+   <Value value="9"/>
+   <Value value="10"/>
+   <Value value="11"/>
+   <Value value="12"/>
+   <Value value="13"/>
+   <Value value="14"/>
+   <Value value="15"/>
+   <Value value="16"/>
+   <Value value="17"/>
+   <Value value="18"/>
+   <Value value="19"/>
+   <Value value="20"/>
+   <Value value="21"/>
+   <Value value="22"/>
+   <Value value="23"/>
+   <Value value="24"/>
+   <Value value="25"/>
+   <Value value="26"/>
+   <Value value="27"/>
+   <Value value="28"/>
+   <Value value="29"/>
+   <Value value="30"/>
+   <Value value="31"/>
+   <Value value="32"/>
+   <Value value="33"/>
+   <Value value="34"/>
+   <Value value="35"/>
+   <Value value="36"/>
+   <Value value="37"/>
+   <Value value="38"/>
+   <Value value="39"/>
+   <Value value="40"/>
+   <Value value="41"/>
+   <Value value="42"/>
+   <Value value="43"/>
+   <Value value="44"/>
+   <Value value="45"/>
+   <Value value="46"/>
+   <Value value="47"/>
+   <Value value="48"/>
+   <Value value="49"/>
+   <Value value="50"/>
+   <Value value="51"/>
+   <Value value="52"/>
+   <Value value="53"/>
+   <Value value="54"/>
+   <Value value="55"/>
+   <Value value="56"/>
+   <Value value="57"/>
+   <Value value="58"/>
+   <Value value="59"/>
+  </DataField>
+  <DataField name="day" displayName="day" optype="continuous">
+   <Value value="1"/>
+   <Value value="2"/>
+   <Value value="3"/>
+   <Value value="4"/>
+   <Value value="5"/>
+   <Value value="6"/>
+   <Value value="7"/>
+   <Value value="8"/>
+   <Value value="9"/>
+   <Value value="10"/>
+   <Value value="11"/>
+   <Value value="12"/>
+   <Value value="13"/>
+   <Value value="14"/>
+  </DataField>
+  <DataField name="latitude" displayName="latitude" optype="continuous">
+   <Value value="8.96"/>
+   <Value value="8.95"/>
+   <Value value="8.97"/>
+   <Value value="4.93"/>
+   <Value value="4.92"/>
+   <Value value="2"/>
+   <Value value="1.99"/>
+   <Value value="1.98"/>
+   <Value value="-0.01"/>
+   <Value value="-0.02"/>
+   <Value value="-2.02"/>
+   <Value value="-2"/>
+   <Value value="-2.01"/>
+   <Value value="-2.03"/>
+   <Value value="-5.01"/>
+   <Value value="-5"/>
+   <Value value="5.15"/>
+   <Value value="5.14"/>
+   <Value value="1.95"/>
+   <Value value="1.96"/>
+   <Value value="1.94"/>
+   <Value value="-0.2"/>
+   <Value value="-0.21"/>
+   <Value value="-0.22"/>
+   <Value value="-0.19"/>
+   <Value value="-2.04"/>
+   <Value value="-5.02"/>
+   <Value value="-7.97"/>
+   <Value value="-7.95"/>
+   <Value value="-7.96"/>
+   <Value value="8.05"/>
+   <Value value="8.04"/>
+   <Value value="4.99"/>
+   <Value value="4.98"/>
+   <Value value="4.97"/>
+   <Value value="2.08"/>
+   <Value value="2.07"/>
+   <Value value="-1.98"/>
+   <Value value="-4.98"/>
+   <Value value="-4.97"/>
+   <Value value="-4.99"/>
+   <Value value="-8.05"/>
+   <Value value="-8.1"/>
+   <Value value="8.06"/>
+   <Value value="5.02"/>
+   <Value value="5.03"/>
+   <Value value="1.97"/>
+   <Value value="0.11"/>
+   <Value value="0.12"/>
+   <Value value="0.1"/>
+   <Value value="0.09"/>
+   <Value value="0.13"/>
+   <Value value="-1.97"/>
+   <Value value="-1.96"/>
+   <Value value="-5.03"/>
+   <Value value="-8.02"/>
+   <Value value="-8.01"/>
+   <Value value="8"/>
+   <Value value="7.99"/>
+   <Value value="2.02"/>
+   <Value value="2.03"/>
+   <Value value="0.07"/>
+   <Value value="0.08"/>
+   <Value value="-1.99"/>
+   <Value value="-7.98"/>
+   <Value value="8.01"/>
+   <Value value="-0.03"/>
+   <Value value="-0.04"/>
+   <Value value="-2.15"/>
+   <Value value="-2.16"/>
+   <Value value="-8"/>
+   <Value value="7.97"/>
+   <Value value="7.98"/>
+   <Value value="0"/>
+   <Value value="-8.28"/>
+   <Value value="-8.27"/>
+   <Value value="2.41"/>
+   <Value value="2.42"/>
+   <Value value="0.01"/>
+   <Value value="8.08"/>
+   <Value value="8.09"/>
+   <Value value="8.07"/>
+   <Value value="2.09"/>
+   <Value value="0.06"/>
+   <Value value="-1.92"/>
+   <Value value="-1.93"/>
+   <Value value="-8.03"/>
+   <Value value="-8.04"/>
+  </DataField>
+  <DataField name="buoy_day_ID" displayName="buoy_day_ID" optype="continuous"/>
+  <DataField name="longitude" displayName="longitude" optype="continuous"/>
+  <DataField name="zon_winds" displayName="zon_winds" optype="continuous"/>
+  <DataField name="mer_winds" displayName="mer_winds" optype="continuous"/>
+  <DataField name="humidity" displayName="humidity" optype="continuous"/>
+  <DataField name="airtemp" displayName="airtemp" optype="continuous"/>
+  <DataField name="s_s_temp" displayName="s_s_temp" optype="continuous"/>
+ </DataDictionary>
+ <RegressionModel modelName="dmg.linear regression model" functionName="regression" modelType="linearRegression" algorithmName="polynomialRegression" targetFieldName="airtemp" x-type="training">
+  <MiningSchema>
+   <MiningField name="buoy" missingValueTreatment="asValue" missingValueReplacement="29.4181286549708" importance="0.00592923607125734"/>
+   <MiningField name="day" missingValueTreatment="asValue" missingValueReplacement="7.09356725146199" importance="0.0253665293140088"/>
+   <MiningField name="latitude" missingValueTreatment="asValue" missingValueReplacement="0.416959064327487" importance="0.0213229781744707"/>
+   <MiningField name="buoy_day_ID" usageType="supplementary" lowValue="0" highValue="120"/>
+   <MiningField name="longitude" usageType="supplementary" lowValue="-200" highValue="200"/>
+   <MiningField name="zon_winds" usageType="supplementary" lowValue="-9" highValue="7"/>
+   <MiningField name="mer_winds" lowValue="-7" highValue="8" missingValueTreatment="asValue" missingValueReplacement="-0.605685402425226" importance="0.0525654356534898"/>
+   <MiningField name="humidity" lowValue="70" highValue="95" missingValueTreatment="asValue" missingValueReplacement="84.5447875942528" importance="0.0415388399920147"/>
+   <MiningField name="airtemp" usageType="predicted" lowValue="22.5" highValue="29"/>
+   <MiningField name="s_s_temp" lowValue="22" highValue="30" missingValueTreatment="asValue" missingValueReplacement="28.2221982815866" importance="0.853276980794759"/>
+  </MiningSchema>
+  <RegressionTable intercept="10.6437511408233">
+   <NumericPredictor name="buoy" coefficient="0.00487148707093042"/>
+   <NumericPredictor name="day" coefficient="-0.0208412547759069"/>
+   <NumericPredictor name="latitude" coefficient="0.0175190549410252"/>
+   <NumericPredictor name="mer_winds" coefficient="-0.0431879987719057"/>
+   <NumericPredictor name="humidity" coefficient="-0.0341284980949723"/>
+   <NumericPredictor name="s_s_temp" coefficient="0.701056211948523"/>
+   </RegressionTable>
+  <Extension name="X-Quality">
+   <X-Quality x-targetField="airtemp" x-dataName="dmg.linear regression model" x-modelQuality="0.802404355182778">
+    <X-LiftData x-rankingQuality="0.87510135036528">
+     <X-LiftGraph>
+      <X-XCoordinates>
+       <Array n="109" type="int"> 1 6 8 11 12 14 18 21 28 32 39 45 49 51 58 62 66 80 95 98 116 124 137 139 144 148 149 160 163 166 176 190 194 198 210 221 228 229 241 244 250 257 260 266 268 270 277 284 288 293 294 299 301 313 319 331 335 352 358 369 384 387 393 403 407 413 420 427 452 464 470 478 483 489 494 497 506 513 521 529 533 540 544 546 549 560 568 573 580 585 586 594 602 612 616 619 624 632 637 644 649 655 656 665 669 674 680 681 684</Array>
+      </X-XCoordinates>
+      <X-YCoordinates>
+       <Array n="109" type="real"> 29.08 145.59 58.09 86.61 29.2 57.98 115.72 85.37 203.03 113.4 199.69 173.27 114.8 57.28 200.49 114.16 113.83 402.18 425.92 86.29 512.07 229.64 370.8 57.47 144.13 112.32 29.23 310.53 85.57 85.17 284.1 397.94 113.1 112.74 336.67 308.98 196.38 28.64 338.21 83.21 168.08 195.76 84.83 168.59 55.56 55.82 196.14 197.15 111.45 140.34 28.55 139.82 56.45 334.51 166.97 332.53 110.48 474.54 166.6 307.2 419.61 83.45 165.87 279.51 109.61 166.88 195.99 193.55 701.72 330.15 166.6 221.27 139.5 165.38 136.71 81.69 246.2 192.3 216.91 219.23 108.8 188.42 107.45 53.57 79.9 293.74 210.16 131.73 187.54 131.8 25.93 206.48 207.93 258.08 103.01 75.77 125.26 203.75 127.84 175.25 125.69 149.48 24.49 225.93 100.46 122.26 144.26 23.59 68.74</Array>
+      </X-YCoordinates>
+      <X-BoundaryValues>
+       <Array n="109" type="real"> 29.31 29.21 29.16 29.13 29.05 29.03 28.97 28.95 28.89 28.85 28.81 28.77 28.74 28.72 28.68 28.66 28.64 28.61 28.57 28.56 28.53 28.52 28.49 28.48 28.46 28.45 28.44 28.42 28.41 28.39 28.37 28.34 28.33 28.32 28.29 28.26 28.24 28.23 28.19 28.18 28.16 28.13 28.11 28.08 28.06 28.05 28.03 28.01 28 27.98 27.97 27.96 27.94 27.91 27.89 27.86 27.85 27.81 27.8 27.77 27.74 27.72 27.7 27.66 27.65 27.63 27.61 27.59 27.54 27.51 27.48 27.44 27.42 27.39 27.37 27.35 27.3 27.27 27.21 27.18 27.14 27.08 27.02 26.98 26.94 26.86 26.76 26.71 26.67 26.56 26.52 26.41 26.18 25.99 25.89 25.77 25.67 25.53 25.45 25.29 25.21 25.09 25.04 24.91 24.7 24.49 24.19 23.87 22.72</Array>
+      </X-BoundaryValues>
+      </X-LiftGraph>
+     <X-LiftGraph x-usage="optimum">
+      <X-XCoordinates>
+       <Array n="113" type="int"> 3 9 15 21 27 35 41 45 54 57 64 70 75 81 87 93 99 105 114 117 127 131 140 143 151 156 160 166 173 177 184 189 195 203 210 213 219 227 233 237 243 250 255 264 267 274 284 289 291 300 303 311 320 324 328 334 344 351 360 371 372 380 386 392 396 404 412 414 422 427 432 440 444 453 459 464 469 474 480 487 494 498 504 510 516 523 528 534 541 548 555 560 565 570 576 582 588 594 600 606 612 618 624 630 636 643 648 654 660 667 672 678 684</Array>
+      </X-XCoordinates>
+      <X-YCoordinates>
+       <Array n="113" type="real"> 88.98 175.96 175.46 175.07 174.86 232.82 174.37 116.13 260.87 86.86 202.31 173.15 144.17 172.75 172.51 172.33 172.15 172 257.78 85.85 285.81 114.24 256.83 85.56 227.94 142.4 113.86 170.7 199.08 113.69 198.87 141.98 170.24 226.76 198.31 84.96 169.76 226.12 169.46 112.92 169.22 197.26 140.83 253.25 84.35 196.62 280.63 140.25 56.07 252.14 84 223.87 251.73 111.84 111.77 167.53 278.9 195.1 250.65 306 27.8 222.21 166.47 166.34 110.82 221.39 221.2 55.27 220.89 137.97 137.78 220.14 109.97 247.07 164.54 136.97 136.87 136.78 163.9 190.97 190.64 108.75 162.89 162.59 162.21 188.91 134.76 161.36 187.56 187.05 186.76 133.24 133.1 132.95 159.21 158.8 157.61 156.29 155.55 154.88 154.28 153.38 152.77 152.06 151.46 175.89 125.25 149.8 148.85 171.98 121.61 144.11 139.91</Array>
+      </X-YCoordinates>
+      <X-BoundaryValues>
+       <Array n="113" type="real"> 29.4035807336368 29.31 29.21 29.16 29.13 29.09 29.05 29.03 28.97 28.95 28.89 28.85 28.81 28.77 28.74 28.72 28.68 28.66 28.64 28.61 28.57 28.56 28.53 28.52 28.49 28.48 28.46 28.45 28.44 28.42 28.41 28.39 28.37 28.34 28.33 28.32 28.29 28.26 28.24 28.23 28.19 28.18 28.16 28.13 28.11 28.08 28.06 28.05 28.03 28.01 28 27.98 27.97 27.96 27.94 27.91 27.89 27.86 27.85 27.81 27.8 27.77 27.74 27.72 27.7 27.66 27.65 27.63 27.61 27.59 27.54 27.51 27.48 27.44 27.42 27.39 27.37 27.35 27.3 27.27 27.21 27.18 27.14 27.08 27.02 26.98 26.94 26.86 26.76 26.71 26.67 26.64 26.62 26.56 26.52 26.41 26.18 25.99 25.89 25.77 25.67 25.53 25.45 25.29 25.21 25.09 25.04 24.91 24.7 24.49 24.19 23.87 22.72</Array>
+      </X-BoundaryValues>
+      </X-LiftGraph>
+     </X-LiftData>
+    <X-AccuracyData x-accuracyQuality="0.729707360000276" x-RMSE="0.511438610712762" rSquared="0.832081965596637"/>
+    </X-Quality>
+   </Extension>
+  <Extension name="X-Correlations">
+   <X-Correlations>
+    <Array>"airtemp" "buoy" "day" "latitude" "longitude" "zon_winds" "mer_winds" "humidity" "s_s_temp"</Array>
+    <Matrix kind="symmetric" nbRows="9" nbCols="9" diagDefault="1" offDiagDefault="0">
+     <Array n="1" type="real"> 1</Array>
+     <Array n="2" type="real"> 0.522365694225489 1</Array>
+     <Array n="3" type="real"> -0.0742213273866386 0.0262234996648629 1</Array>
+     <Array n="4" type="real"> 0.0865043556969303 -0.0456930683128271 0.0219412182686445 1</Array>
+     <Array n="5" type="real"> 0.293588275795282 0.667480278783326 -0.0143703570149681 0.125013666207307 1</Array>
+     <Array n="6" type="real"> 0.00360686481841199 -0.0733197650943679 -0.0901299202064135 0.0682187447139158 0.0165696221292296 1</Array>
+     <Array n="7" type="real"> -0.280203280066567 -0.298423197658618 -0.0576661302888894 -0.170760008314396 -0.16121563666158 0.259971104767946 1</Array>
+     <Array n="8" type="real"> -0.26463517692489 -0.00916420713452718 -0.0729627445357725 0.155908501222959 -0.0595024090638155 0.048610958001289 -0.138343922964102 1</Array>
+     <Array n="9" type="real"> 0.86931662717958 0.553134500954063 -0.0325636130110783 0.0311456120601626 0.301510053897663 0.0414960187033224 -0.234057419811055 -0.220637423293335 1</Array>
+    </Matrix>
+   </X-Correlations>
+   </Extension>
+  </RegressionModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/polynomial_regression_model.xml
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/polynomial_regression_model.xml	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/pmml/data/polynomial_regression_model.xml	(revision 29)
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<PMML version="3.0" xmlns="http://www.dmg.org/PMML-3_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dmg.org/PMML-3_0 http://www.dmg.org/v3-0/pmml-3-0.xsd">
+ <Header copyright="Copyright IBM Corp. 2002, 2005 All Rights Reserved">
+  <Application name="IBM DB2 Intelligent Miner" version="9.1"/>
+  <Timestamp>2006-03-21 13:13:12</Timestamp>
+ </Header>
+ <MiningBuildTask>
+  <Extension>&lt;?xml version="1.0" encoding="UTF-8" ?>
+&lt;DataMining>&lt;MiningTask trainData="trainData">&lt;MiningData name="trainData" tableName="RASPL.INPUT_014">&lt;Column alias="buoy_day_ID" name="buoy_day_ID" sqlType="SMALLINT"/>&lt;Column alias="buoy" name="buoy" sqlType="SMALLINT"/>&lt;Column alias="day" name="day" sqlType="SMALLINT"/>&lt;Column alias="latitude" name="latitude" sqlType="DECIMAL"/>&lt;Column alias="longitude" name="longitude" sqlType="DECIMAL"/>&lt;Column alias="zon_winds" name="zon_winds" sqlType="DECIMAL"/>&lt;Column alias="mer_winds" name="mer_winds" sqlType="DECIMAL"/>&lt;Column alias="humidity" name="humidity" sqlType="DECIMAL"/>&lt;Column alias="airtemp" name="airtemp" sqlType="DECIMAL"/>&lt;Column alias="s_s_temp" name="s_s_temp" sqlType="DECIMAL"/>&lt;/MiningData>&lt;MiningSettings algorithm="Polynomial">&lt;LogicalDataSpec>&lt;MiningField name="buoy_day_ID" type="numerical"/>&lt;MiningField name="buoy" type="numerical"/>&lt;MiningField name="day" type="numerical"/>&lt;MiningField name="latitude" type="numerical"/>&lt;MiningField name="longitude" type="numerical"/>&lt;MiningField name="zon_winds" type="numerical"/>&lt;MiningField name="mer_winds" type="numerical"/>&lt;MiningField name="humidity" type="numerical"/>&lt;MiningField name="airtemp" type="numerical"/>&lt;MiningField name="s_s_temp" type="numerical"/>&lt;/LogicalDataSpec>&lt;RegSettings/>&lt;FieldUsageEntry field="airtemp" usageType="target"/>&lt;/MiningSettings>&lt;TaskControl errorTable="IDMMX.ERRORTABLE" progressTable="IDMMX.PROGRESSTABLE"/>&lt;/MiningTask>&lt;/DataMining></Extension>
+ </MiningBuildTask>
+ <DataDictionary numberOfFields="10">
+  <DataField name="buoy" displayName="buoy" optype="continuous">
+   <Value value="1"/>
+   <Value value="2"/>
+   <Value value="3"/>
+   <Value value="4"/>
+   <Value value="5"/>
+   <Value value="6"/>
+   <Value value="7"/>
+   <Value value="8"/>
+   <Value value="9"/>
+   <Value value="10"/>
+   <Value value="11"/>
+   <Value value="12"/>
+   <Value value="13"/>
+   <Value value="14"/>
+   <Value value="15"/>
+   <Value value="16"/>
+   <Value value="17"/>
+   <Value value="18"/>
+   <Value value="19"/>
+   <Value value="20"/>
+   <Value value="21"/>
+   <Value value="22"/>
+   <Value value="23"/>
+   <Value value="24"/>
+   <Value value="25"/>
+   <Value value="26"/>
+   <Value value="27"/>
+   <Value value="28"/>
+   <Value value="29"/>
+   <Value value="30"/>
+   <Value value="31"/>
+   <Value value="32"/>
+   <Value value="33"/>
+   <Value value="34"/>
+   <Value value="35"/>
+   <Value value="36"/>
+   <Value value="37"/>
+   <Value value="38"/>
+   <Value value="39"/>
+   <Value value="40"/>
+   <Value value="41"/>
+   <Value value="42"/>
+   <Value value="43"/>
+   <Value value="44"/>
+   <Value value="45"/>
+   <Value value="46"/>
+   <Value value="47"/>
+   <Value value="48"/>
+   <Value value="49"/>
+   <Value value="50"/>
+   <Value value="51"/>
+   <Value value="52"/>
+   <Value value="53"/>
+   <Value value="54"/>
+   <Value value="55"/>
+   <Value value="56"/>
+   <Value value="57"/>
+   <Value value="58"/>
+   <Value value="59"/>
+  </DataField>
+  <DataField name="day" displayName="day" optype="continuous">
+   <Value value="1"/>
+   <Value value="2"/>
+   <Value value="3"/>
+   <Value value="4"/>
+   <Value value="5"/>
+   <Value value="6"/>
+   <Value value="7"/>
+   <Value value="8"/>
+   <Value value="9"/>
+   <Value value="10"/>
+   <Value value="11"/>
+   <Value value="12"/>
+   <Value value="13"/>
+   <Value value="14"/>
+  </DataField>
+  <DataField name="latitude" displayName="latitude" optype="continuous">
+   <Value value="8.96"/>
+   <Value value="8.95"/>
+   <Value value="8.97"/>
+   <Value value="4.93"/>
+   <Value value="4.92"/>
+   <Value value="2"/>
+   <Value value="1.99"/>
+   <Value value="1.98"/>
+   <Value value="-0.01"/>
+   <Value value="-0.02"/>
+   <Value value="-2.02"/>
+   <Value value="-2"/>
+   <Value value="-2.01"/>
+   <Value value="-2.03"/>
+   <Value value="-5.01"/>
+   <Value value="-5"/>
+   <Value value="5.15"/>
+   <Value value="5.14"/>
+   <Value value="1.95"/>
+   <Value value="1.96"/>
+   <Value value="1.94"/>
+   <Value value="-0.2"/>
+   <Value value="-0.21"/>
+   <Value value="-0.22"/>
+   <Value value="-0.19"/>
+   <Value value="-2.04"/>
+   <Value value="-5.02"/>
+   <Value value="-7.97"/>
+   <Value value="-7.95"/>
+   <Value value="-7.96"/>
+   <Value value="8.05"/>
+   <Value value="8.04"/>
+   <Value value="4.99"/>
+   <Value value="4.98"/>
+   <Value value="4.97"/>
+   <Value value="2.08"/>
+   <Value value="2.07"/>
+   <Value value="-1.98"/>
+   <Value value="-4.98"/>
+   <Value value="-4.97"/>
+   <Value value="-4.99"/>
+   <Value value="-8.05"/>
+   <Value value="-8.1"/>
+   <Value value="8.06"/>
+   <Value value="5.02"/>
+   <Value value="5.03"/>
+   <Value value="1.97"/>
+   <Value value="0.11"/>
+   <Value value="0.12"/>
+   <Value value="0.1"/>
+   <Value value="0.09"/>
+   <Value value="0.13"/>
+   <Value value="-1.97"/>
+   <Value value="-1.96"/>
+   <Value value="-5.03"/>
+   <Value value="-8.02"/>
+   <Value value="-8.01"/>
+   <Value value="8"/>
+   <Value value="7.99"/>
+   <Value value="2.02"/>
+   <Value value="2.03"/>
+   <Value value="0.07"/>
+   <Value value="0.08"/>
+   <Value value="-1.99"/>
+   <Value value="-7.98"/>
+   <Value value="8.01"/>
+   <Value value="-0.03"/>
+   <Value value="-0.04"/>
+   <Value value="-2.15"/>
+   <Value value="-2.16"/>
+   <Value value="-8"/>
+   <Value value="7.97"/>
+   <Value value="7.98"/>
+   <Value value="0"/>
+   <Value value="-8.28"/>
+   <Value value="-8.27"/>
+   <Value value="2.41"/>
+   <Value value="2.42"/>
+   <Value value="0.01"/>
+   <Value value="8.08"/>
+   <Value value="8.09"/>
+   <Value value="8.07"/>
+   <Value value="2.09"/>
+   <Value value="0.06"/>
+   <Value value="-1.92"/>
+   <Value value="-1.93"/>
+   <Value value="-8.03"/>
+   <Value value="-8.04"/>
+  </DataField>
+  <DataField name="buoy_day_ID" displayName="buoy_day_ID" optype="continuous"/>
+  <DataField name="longitude" displayName="longitude" optype="continuous"/>
+  <DataField name="zon_winds" displayName="zon_winds" optype="continuous"/>
+  <DataField name="mer_winds" displayName="mer_winds" optype="continuous"/>
+  <DataField name="humidity" displayName="humidity" optype="continuous"/>
+  <DataField name="airtemp" displayName="airtemp" optype="continuous"/>
+  <DataField name="s_s_temp" displayName="s_s_temp" optype="continuous"/>
+ </DataDictionary>
+ <RegressionModel modelName="dmg.polynomial regression model" functionName="regression" modelType="stepwisePolynomialRegression" algorithmName="polynomialRegression" targetFieldName="airtemp" x-type="training">
+  <MiningSchema>
+   <MiningField name="buoy" missingValueTreatment="asValue" missingValueReplacement="37.5480795470864" importance="0.0795564346344789"/>
+   <MiningField name="day" missingValueTreatment="asValue" missingValueReplacement="8.82833507533915" importance="0.020633180418739"/>
+   <MiningField name="latitude" missingValueTreatment="asValue" missingValueReplacement="5.03544047911535" importance="0.0242869130751442"/>
+   <MiningField name="buoy_day_ID" usageType="supplementary" lowValue="0" highValue="120"/>
+   <MiningField name="longitude" lowValue="-200" highValue="200" missingValueTreatment="asValue" missingValueReplacement="-106.191233885652" importance="0.00194218619087974"/>
+   <MiningField name="zon_winds" lowValue="-9" highValue="7" missingValueTreatment="asValue" missingValueReplacement="-4.82394771100082" importance="0.0761955945733028"/>
+   <MiningField name="mer_winds" lowValue="-7" highValue="8" missingValueTreatment="asValue" missingValueReplacement="2.67726009558619" importance="0.0868422589254256"/>
+   <MiningField name="humidity" lowValue="70" highValue="95" missingValueTreatment="asValue" missingValueReplacement="84.5447875942528" importance="0.0329134523069299"/>
+   <MiningField name="airtemp" usageType="predicted" lowValue="22.5" highValue="29"/>
+   <MiningField name="s_s_temp" lowValue="22" highValue="30" missingValueTreatment="asValue" missingValueReplacement="28.2221982815866" importance="0.6776299798751"/>
+  </MiningSchema>
+  <RegressionTable intercept="10.0054887551819">
+   <NumericPredictor name="buoy" coefficient="0.089442891995617"/>
+   <NumericPredictor name="day" coefficient="-0.0106531200918996"/>
+   <NumericPredictor name="latitude" coefficient="0.0177854223872961"/>
+   <NumericPredictor name="longitude" coefficient="0.00203547456641219"/>
+   <NumericPredictor name="zon_winds" coefficient="0.0388923875213552"/>
+   <NumericPredictor name="mer_winds" coefficient="-0.0642586994465178"/>
+   <NumericPredictor name="humidity" coefficient="-0.0344912934156202"/>
+   <NumericPredictor name="s_s_temp" coefficient="0.710114947685749"/>
+   <NumericPredictor name="buoy" exponent="2" coefficient="-0.00307993940739062"/>
+   <NumericPredictor name="day" exponent="2" coefficient="-0.00609768487926244"/>
+   <NumericPredictor name="latitude" exponent="2" coefficient="0.00383289165747081"/>
+   <NumericPredictor name="zon_winds" exponent="2" coefficient="0.0185500020462831"/>
+   <NumericPredictor name="mer_winds" exponent="2" coefficient="-0.0133733493789174"/>
+   <NumericPredictor name="buoy" exponent="3" coefficient="2.90949705182524e-05"/>
+   <NumericPredictor name="day" exponent="3" coefficient="0.000408724480199477"/>
+   <NumericPredictor name="longitude" exponent="3" coefficient="-6.05759019293513e-08"/>
+   <NumericPredictor name="zon_winds" exponent="3" coefficient="0.00128531692905362"/>
+   </RegressionTable>
+  <Extension name="X-Quality">
+   <X-Quality x-targetField="airtemp" x-dataName="dmg.polynomial regression model" x-modelQuality="0.828615756275268">
+    <X-LiftData x-rankingQuality="0.890999861551322">
+     <X-LiftGraph>
+      <X-XCoordinates>
+       <Array n="110" type="int"> 4 6 11 16 19 21 26 29 34 35 43 47 50 56 62 68 80 95 98 104 108 125 127 135 138 143 154 156 170 178 191 193 197 208 223 231 234 254 257 267 277 283 295 302 309 316 319 321 329 331 334 336 344 353 361 366 375 379 387 391 398 406 420 422 427 433 441 461 466 471 480 484 487 492 493 500 503 510 514 518 534 540 542 545 553 565 566 569 571 572 578 581 586 598 606 611 615 621 631 636 641 646 651 655 664 668 671 675 680 684</Array>
+      </X-XCoordinates>
+      <X-YCoordinates>
+       <Array n="110" type="real"> 115.93 58.53 144.16 143.33 86.87 57.16 144.67 86.57 145.31 29.09 228.82 114.49 85.2 171.82 171.5 171.64 344.54 429.47 86.23 171.31 115.35 486.79 57.64 226.57 85.46 142.02 313.68 56.46 393.09 226.08 365.56 55.93 111.73 310.09 424.96 226.01 84.42 565.03 84.9 280.26 280.59 167.03 337.34 197.53 197.14 197.07 84.22 56.02 224.26 55.88 84.52 55.22 221.32 251.73 223.59 139.76 249.79 111.25 222.47 111.04 196.66 220.17 389.95 55.06 140.03 165.79 221.32 553.3 137.12 138.17 247.44 110.81 83.13 138.67 26.88 190.13 82.88 189.98 109.18 108.83 433.19 161.79 54.43 81.35 213.42 320.01 25.94 79.2 54.3 26.66 159.55 79.34 131.22 312.83 204.97 126.64 103.52 153.33 252.91 125.69 126.2 126.56 127.14 99.62 223.5 100.55 74.42 96.82 120.32 92.33</Array>
+      </X-YCoordinates>
+      <X-BoundaryValues>
+       <Array n="110" type="real"> 29.4035807336368 29.21 29.16 29.09 29.05 29.03 28.97 28.89 28.85 28.81 28.77 28.74 28.72 28.68 28.66 28.64 28.61 28.57 28.56 28.53 28.52 28.49 28.48 28.46 28.45 28.44 28.42 28.41 28.39 28.37 28.34 28.33 28.32 28.29 28.26 28.24 28.23 28.19 28.18 28.16 28.13 28.11 28.08 28.06 28.05 28.03 28.01 28 27.98 27.97 27.96 27.94 27.91 27.89 27.86 27.85 27.81 27.8 27.77 27.74 27.72 27.7 27.66 27.65 27.63 27.61 27.59 27.54 27.51 27.48 27.44 27.42 27.39 27.37 27.35 27.3 27.27 27.21 27.18 27.14 27.08 27.02 26.98 26.94 26.86 26.76 26.71 26.67 26.64 26.62 26.56 26.52 26.41 26.18 25.99 25.89 25.77 25.67 25.53 25.45 25.29 25.21 25.09 25.04 24.91 24.7 24.49 24.19 23.87 22.72</Array>
+      </X-BoundaryValues>
+      </X-LiftGraph>
+     <X-LiftGraph x-usage="optimum">
+      <X-XCoordinates>
+       <Array n="113" type="int"> 3 9 15 21 27 35 41 45 54 57 64 70 75 81 87 93 99 105 114 117 127 131 140 143 151 156 160 166 173 177 184 189 195 203 210 213 219 227 233 237 243 250 255 264 267 274 284 289 291 300 303 311 320 324 328 334 344 351 360 371 372 380 386 392 396 404 412 414 422 427 432 440 444 453 459 464 469 474 480 487 494 498 504 510 516 523 528 534 541 548 555 560 565 570 576 582 588 594 600 606 612 618 624 630 636 643 648 654 660 667 672 678 684</Array>
+      </X-XCoordinates>
+      <X-YCoordinates>
+       <Array n="113" type="real"> 88.98 175.96 175.46 175.07 174.86 232.82 174.37 116.13 260.87 86.86 202.31 173.15 144.17 172.75 172.51 172.33 172.15 172 257.78 85.85 285.81 114.24 256.83 85.56 227.94 142.4 113.86 170.7 199.08 113.69 198.87 141.98 170.24 226.76 198.31 84.96 169.76 226.12 169.46 112.92 169.22 197.26 140.83 253.25 84.35 196.62 280.63 140.25 56.07 252.14 84 223.87 251.73 111.84 111.77 167.53 278.9 195.1 250.65 306 27.8 222.21 166.47 166.34 110.82 221.39 221.2 55.27 220.89 137.97 137.78 220.14 109.97 247.07 164.54 136.97 136.87 136.78 163.9 190.97 190.64 108.75 162.89 162.59 162.21 188.91 134.76 161.36 187.56 187.05 186.76 133.24 133.1 132.95 159.21 158.8 157.61 156.29 155.55 154.88 154.28 153.38 152.77 152.06 151.46 175.89 125.25 149.8 148.85 171.98 121.61 144.11 139.91</Array>
+      </X-YCoordinates>
+      <X-BoundaryValues>
+       <Array n="113" type="real"> 29.4035807336368 29.31 29.21 29.16 29.13 29.09 29.05 29.03 28.97 28.95 28.89 28.85 28.81 28.77 28.74 28.72 28.68 28.66 28.64 28.61 28.57 28.56 28.53 28.52 28.49 28.48 28.46 28.45 28.44 28.42 28.41 28.39 28.37 28.34 28.33 28.32 28.29 28.26 28.24 28.23 28.19 28.18 28.16 28.13 28.11 28.08 28.06 28.05 28.03 28.01 28 27.98 27.97 27.96 27.94 27.91 27.89 27.86 27.85 27.81 27.8 27.77 27.74 27.72 27.7 27.66 27.65 27.63 27.61 27.59 27.54 27.51 27.48 27.44 27.42 27.39 27.37 27.35 27.3 27.27 27.21 27.18 27.14 27.08 27.02 26.98 26.94 26.86 26.76 26.71 26.67 26.64 26.62 26.56 26.52 26.41 26.18 25.99 25.89 25.77 25.67 25.53 25.45 25.29 25.21 25.09 25.04 24.91 24.7 24.49 24.19 23.87 22.72</Array>
+      </X-BoundaryValues>
+      </X-LiftGraph>
+     </X-LiftData>
+    <X-AccuracyData x-accuracyQuality="0.766231650999215" x-RMSE="0.483065765372364" rSquared="0.84936434872562"/>
+    </X-Quality>
+   </Extension>
+  <Extension name="X-Correlations">
+   <X-Correlations>
+    <Array>"airtemp" "buoy" "day" "latitude" "longitude" "zon_winds" "mer_winds" "humidity" "s_s_temp" "buoy" "day" "latitude" "longitude" "zon_winds" "mer_winds" "humidity" "s_s_temp" "buoy" "day" "latitude" "longitude" "zon_winds" "mer_winds" "humidity" "s_s_temp"</Array>
+    <Matrix kind="symmetric" nbRows="25" nbCols="25" diagDefault="1" offDiagDefault="0">
+     <Array n="1" type="real"> 1</Array>
+     <Array n="2" type="real"> 0.522365694225489 1</Array>
+     <Array n="3" type="real"> -0.0742213273866386 0.0262234996648629 1</Array>
+     <Array n="4" type="real"> 0.0865043556969303 -0.0456930683128271 0.0219412182686445 1</Array>
+     <Array n="5" type="real"> 0.293588275795282 0.667480278783326 -0.0143703570149681 0.125013666207307 1</Array>
+     <Array n="6" type="real"> 0.00360686481841199 -0.0733197650943679 -0.0901299202064135 0.0682187447139158 0.0165696221292296 1</Array>
+     <Array n="7" type="real"> -0.280203280066567 -0.298423197658618 -0.0576661302888894 -0.170760008314396 -0.16121563666158 0.259971104767946 1</Array>
+     <Array n="8" type="real"> -0.26463517692489 -0.00916420713452718 -0.0729627445357725 0.155908501222959 -0.0595024090638155 0.048610958001289 -0.138343922964102 1</Array>
+     <Array n="9" type="real"> 0.86931662717958 0.553134500954063 -0.0325636130110783 0.0311456120601626 0.301510053897663 0.0414960187033224 -0.234057419811055 -0.220637423293335 1</Array>
+     <Array n="10" type="real"> 0.491784810107742 0.971052538154734 0.0288100413554667 -0.0341203389283023 0.765573334574952 -0.0744731690888414 -0.324629485845354 -0.012374918041761 0.535633404037836 1</Array>
+     <Array n="11" type="real"> -0.0624062340479974 0.0347075069448905 0.971671039999177 0.0230906592039925 -0.0113109886267886 -0.110765919325242 -0.0472860734386834 -0.0563117634254442 -0.0272167799701689 0.0358829945655219 1</Array>
+     <Array n="12" type="real"> 0.271896041793306 0.0188000194768278 0.0232482929146186 0.106043807722349 0.011227032047421 -0.0819640729945293 -0.0355600100145399 -0.190217806686553 0.181442539181054 0.0267388827373876 0.0217362172383281 1</Array>
+     <Array n="13" type="real"> 0.454595171988612 0.56888686492418 0.065362769129505 0.0799849496858138 0.136260046422506 -0.260593244733204 -0.533015202660653 0.16369886382478 0.517003735098666 0.553517674857085 0.0775942341642202 -0.0413328200823572 1</Array>
+     <Array n="14" type="real"> 0.108223956733978 0.0247530237181415 0.0643449817143254 0.0547630397459108 -0.0479961471630868 -0.818553613915766 -0.20013235552483 -0.0989739725289271 0.0063328095757797 0.0166254289597783 0.0972088372376997 0.304534143363927 0.162199844021419 1</Array>
+     <Array n="15" type="real"> 0.0345586471045742 -0.101329543583687 -0.0819707754904985 0.255345029882083 -0.041715633340268 -0.00384204314608443 -0.0219285536190397 -0.0889056120314135 0.0852650880384893 -0.104682614213031 -0.0744293479131347 0.184220666756977 -0.097169924963528 0.0268265013629058 1</Array>
+     <Array n="16" type="real"> -0.262503908081212 -0.00756414603233726 -0.0726084447137123 0.14840601678367 -0.0633199871150321 0.0440107328490673 -0.136531096341424 0.99928670155736 -0.219991710726423 -0.011742909194472 -0.0557616755931653 -0.188237318601434 0.165402113537245 -0.0950028554368386 -0.091285802300464 1</Array>
+     <Array n="17" type="real"> 0.868686238864357 0.558939065658443 -0.0282865123014097 0.0280931641236945 0.307326081862951 0.0497027679411262 -0.235043436873067 -0.223373566011918 0.999420968853458 0.541714285801104 -0.0234657299773259 0.174560361318217 0.519001155858743 -0.00289343020613201 0.0769271804423913 -0.222835387211072 1</Array>
+     <Array n="18" type="real"> 0.445241440732543 0.922179527013383 0.0260831522287116 -0.0404155100060945 0.815817408491431 -0.0572046694169303 -0.306720476521226 -0.0116631353736034 0.490508762203247 0.986534748791714 0.0316517449372798 0.0390892797328143 0.504170598787854 0.00146175575545313 -0.100271191570784 -0.0116063175159616 0.496483663842227 1</Array>
+     <Array n="19" type="real"> -0.048822082507363 0.0424934076389279 0.921207221641975 0.0231612926365598 -0.00784084512196321 -0.123824882011001 -0.0373341204745101 -0.0415533362919217 -0.0195467750812793 0.0424943767179884 0.985944669302904 0.0193455520952652 0.086469401404666 0.12091341462408 -0.0692601967240758 -0.0408485642612295 -0.0163741031178585 0.0370908106441793 1</Array>
+     <Array n="20" type="real"> 0.0811914288565015 -0.0787482949381249 0.0176996818016698 0.91837076228167 0.0642251096319167 0.111746739005221 -0.259284340187284 0.158472268188228 0.0260825605717942 -0.0650632167601127 0.0170941906099763 0.131084067800255 0.100942822486379 0.0208129113150081 0.24059000284068 0.150038250598785 0.0243786190522424 -0.0674519200050567 0.0162455355501357 1</Array>
+     <Array n="21" type="real"> 0.156289482308845 0.515733002894763 -0.0332337922101546 0.0887198772345001 0.964928868270449 0.0649531410916161 -0.0386322757095631 -0.0879349497520159 0.150885821057787 0.631344269893895 -0.0326927154373853 0.0381575106084324 -0.0989875733759957 -0.0697159673483243 -0.0133949144085038 -0.0920704729335723 0.155406998625575 0.704266166888076 -0.0311462449400933 0.0289658107780175 1</Array>
+     <Array n="22" type="real"> -0.0895227590794008 -0.0291586216006492 -0.0434729898808982 -0.0240718009567406 0.0431149601555925 0.835788410520718 0.188089637934015 0.10961619264645 -0.0125844396438652 -0.0229070738136395 -0.0782866817707594 -0.270944092067829 -0.17045570133899 -0.950091343053136 -0.0477269534700683 0.105307097389905 -0.00349259392734056 -0.00901084929527882 -0.102761245234658 0.016396223729131 0.0671895282947778 1</Array>
+     <Array n="23" type="real"> -0.136752925403835 -0.14576827076704 -0.0452495432862936 -0.130020616126726 -0.0853667250182141 0.261851730707086 0.838016742622344 -0.106683046568288 -0.0689940733013824 -0.184822101904104 -0.0333356756328541 -0.139471322360574 -0.378132576184927 -0.242195184510941 0.0726283887296204 -0.105437278965358 -0.0684519705060686 -0.183147646268053 -0.0237471244747833 -0.231557066245824 -0.00444377062316582 0.211508001333117 1</Array>
+     <Array n="24" type="real"> -0.25964861932727 -0.0057962576238216 -0.0721993598896045 0.140893165475969 -0.066938721135116 0.0394812344926258 -0.134542952042234 0.997155190472162 -0.218704393746375 -0.0109473534052744 -0.0552304376579898 -0.186019613644582 0.167022002191867 -0.0910650675080984 -0.0933692303967584 0.999289060591091 -0.221653944902863 -0.011410743762752 -0.0402150358931307 0.141490997583337 -0.0960306376529578 0.101039733070913 -0.10395986227297 1</Array>
+     <Array n="25" type="real"> 0.867043537527115 0.563929797078599 -0.0241677372785374 0.0248613926975458 0.312688048135453 0.0580053332011455 -0.235518769532726 -0.225800315754383 0.997758335045756 0.546964237551726 -0.019896618368922 0.167321666645647 0.519980414663501 -0.0122460838987911 0.0685083586856134 -0.225365925251356 0.999456751302649 0.501671645168593 -0.013393534586745 0.0225724459323936 0.159728274526503 0.00572512416603342 -0.0677397496687852 -0.224287686914855 1</Array>
+    </Matrix>
+   </X-Correlations>
+   </Extension>
+  </RegressionModel>
+</PMML>
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/ConjunctiveRuleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/ConjunctiveRuleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/ConjunctiveRuleTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ConjunctiveRule. Run from the command line with:<p>
+ * java weka.classifiers.rules.ConjunctiveRuleTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class ConjunctiveRuleTest extends AbstractClassifierTest {
+
+  public ConjunctiveRuleTest(String name) { super(name);  }
+
+  /** Creates a default ConjunctiveRule */
+  public Classifier getClassifier() {
+    return new ConjunctiveRule();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ConjunctiveRuleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/DTNBTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/DTNBTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/DTNBTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DTNB. Run from the command line with:<p>
+ * java weka.classifiers.rules.DTNBTest
+ *
+ * @author Mark Hall (mhall{[at}]pentaho{[dot]}org
+ * @version $Revision: 5563 $
+ */
+public class DTNBTest extends AbstractClassifierTest {
+
+  public DTNBTest(String name) { super(name);  }
+
+  /** Creates a default DecisionTable */
+  public Classifier getClassifier() {
+    return new DTNB();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DTNBTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/DecisionTableTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/DecisionTableTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/DecisionTableTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DecisionTable. Run from the command line with:<p>
+ * java weka.classifiers.rules.DecisionTableTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class DecisionTableTest extends AbstractClassifierTest {
+
+  public DecisionTableTest(String name) { super(name);  }
+
+  /** Creates a default DecisionTable */
+  public Classifier getClassifier() {
+    return new DecisionTable();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DecisionTableTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/FURIATest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/FURIATest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/FURIATest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2009 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FURIA. Run from the command line with:<p>
+ * java weka.classifiers.rules.JFuriaTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 5965 $
+ */
+public class FURIATest extends AbstractClassifierTest {
+
+  public FURIATest(String name) { super(name);  }
+
+  /** Creates a default JRip */
+  public Classifier getClassifier() {
+    return new FURIA();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FURIATest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/JRipTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/JRipTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/JRipTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests JRip. Run from the command line with:<p>
+ * java weka.classifiers.rules.JRipTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class JRipTest extends AbstractClassifierTest {
+
+  public JRipTest(String name) { super(name);  }
+
+  /** Creates a default JRip */
+  public Classifier getClassifier() {
+    return new JRip();
+  }
+
+  public static Test suite() {
+    return new TestSuite(JRipTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/M5RulesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/M5RulesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/M5RulesTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests M5Rules. Run from the command line with:<p>
+ * java weka.classifiers.rules.M5RulesTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class M5RulesTest extends AbstractClassifierTest {
+
+  public M5RulesTest(String name) { super(name);  }
+
+  /** Creates a default M5Rules */
+  public Classifier getClassifier() {
+    return new M5Rules();
+  }
+
+  public static Test suite() {
+    return new TestSuite(M5RulesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/NNgeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/NNgeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/NNgeTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NNge. Run from the command line with:<p/>
+ * java weka.classifiers.rules.rules.NNge
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NNgeTest 
+  extends AbstractClassifierTest {
+
+  public NNgeTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NNge */
+  public Classifier getClassifier() {
+    return new NNge();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NNgeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/OLMTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/OLMTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/OLMTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OLM. Run from the command line with:<p>
+ * java weka.classifiers.rules.OLMTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 5886 $
+ */
+public class OLMTest
+  extends AbstractClassifierTest {
+
+  public OLMTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default OLM */
+  public Classifier getClassifier() {
+    return new OLM();
+  }
+
+  public static Test suite() {
+    return new TestSuite(OLMTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/OneRTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/OneRTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/OneRTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OneR. Run from the command line with:<p>
+ * java weka.classifiers.rules.OneRTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class OneRTest extends AbstractClassifierTest {
+
+  public OneRTest(String name) { super(name);  }
+
+  /** Creates a default OneR */
+  public Classifier getClassifier() {
+    return new OneR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(OneRTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/PARTTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/PARTTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/PARTTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PART. Run from the command line with:<p>
+ * java weka.classifiers.rules.PARTTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class PARTTest extends AbstractClassifierTest {
+
+  public PARTTest(String name) { super(name);  }
+
+  /** Creates a default PART */
+  public Classifier getClassifier() {
+    return new PART();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PARTTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/PrismTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/PrismTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/PrismTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Prism. Run from the command line with:<p>
+ * java weka.classifiers.rules.PrismTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class PrismTest extends AbstractClassifierTest {
+
+  public PrismTest(String name) { super(name);  }
+
+  /** Creates a default Prism */
+  public Classifier getClassifier() {
+    return new Prism();
+  }
+
+  public static Test suite() {
+    return new TestSuite(PrismTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/RidorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/RidorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/RidorTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Ridor. Run from the command line with:<p>
+ * java weka.classifiers.rules.RidorTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class RidorTest extends AbstractClassifierTest {
+
+  public RidorTest(String name) { super(name);  }
+
+  /** Creates a default Ridor */
+  public Classifier getClassifier() {
+    return new Ridor();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RidorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/rules/ZeroRTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/rules/ZeroRTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/rules/ZeroRTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.classifiers.rules;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ZeroR. Run from the command line with:<p>
+ * java weka.classifiers.rules.ZeroRTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class ZeroRTest extends AbstractClassifierTest {
+  
+  public ZeroRTest(String name) { super(name);  }
+
+  /** Creates a default ZeroR */
+  public Classifier getClassifier() {
+    return new ZeroR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ZeroRTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/ADTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/ADTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/ADTreeTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ADTree. Run from the command line with:<p>
+ * java weka.classifiers.trees.ADTreeTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class ADTreeTest extends AbstractClassifierTest {
+
+  public ADTreeTest(String name) { super(name);  }
+
+  /** Creates a default ADTree */
+  public Classifier getClassifier() {
+    return new ADTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ADTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/BFTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/BFTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/BFTreeTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BFTree. Run from the command line with:<p>
+ * java weka.classifiers.trees.BFTreeTest
+ *
+ * @author <a href="mailto:hs69@cs.waikato.ac.nz">Haijian Shi</a>
+ * @version $Revision: 1.1 $
+ */
+public class BFTreeTest
+  extends AbstractClassifierTest {
+
+  public BFTreeTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default BFTree */
+  public Classifier getClassifier() {
+    return new BFTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BFTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/DecisionStumpTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/DecisionStumpTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/DecisionStumpTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DecisionStump. Run from the command line with:<p>
+ * java weka.classifiers.trees.DecisionStumpTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class DecisionStumpTest extends AbstractClassifierTest {
+
+  public DecisionStumpTest(String name) { super(name);  }
+
+  /** Creates a default DecisionStump */
+  public Classifier getClassifier() {
+    return new DecisionStump();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DecisionStumpTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/FTTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/FTTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/FTTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2007 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FT. Run from the command line with:<p>
+ * java weka.classifiers.trees.FT8Test
+ *
+ * @author mhall{[at]}pentaho{[dot]}org
+ * @version $Revision: 1.3 $
+ */
+public class FTTest extends AbstractClassifierTest {
+
+  public FTTest(String name) { super(name);  }
+
+  /** Creates a default J48 */
+  public Classifier getClassifier() {
+    return new FT();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FTTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/Id3Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/Id3Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/Id3Test.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Id3. Run from the command line with:<p>
+ * java weka.classifiers.trees.Id3Test
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class Id3Test extends AbstractClassifierTest {
+
+  public Id3Test(String name) { super(name);  }
+
+  /** Creates a default Id3 */
+  public Classifier getClassifier() {
+    return new Id3();
+  }
+
+  public static Test suite() {
+    return new TestSuite(Id3Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/J48Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/J48Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/J48Test.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests J48. Run from the command line with:<p>
+ * java weka.classifiers.trees.J48Test
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class J48Test extends AbstractClassifierTest {
+
+  public J48Test(String name) { super(name);  }
+
+  /** Creates a default J48 */
+  public Classifier getClassifier() {
+    return new J48();
+  }
+
+  public static Test suite() {
+    return new TestSuite(J48Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/J48graftTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/J48graftTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/J48graftTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2007 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests J48graft. Run from the command line with:<p>
+ * java weka.classifiers.trees.J48graftTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class J48graftTest extends AbstractClassifierTest {
+
+  public J48graftTest(String name) { super(name);  }
+
+  /** Creates a default J48 */
+  public Classifier getClassifier() {
+    return new J48graft();
+  }
+
+  public static Test suite() {
+    return new TestSuite(J48graftTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/LADTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/LADTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/LADTreeTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LADTree. Run from the command line with:<p>
+ * java weka.classifiers.trees.LADTree
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class LADTreeTest extends AbstractClassifierTest {
+
+  public LADTreeTest(String name) { super(name);  }
+
+  /** Creates a default LADTree */
+  public Classifier getClassifier() {
+    return new LADTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LADTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/LMTTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/LMTTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/LMTTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LMT. Run from the command line with:<p/>
+ * java weka.classifiers.trees.LMTTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class LMTTest 
+  extends AbstractClassifierTest {
+
+  public LMTTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default LMT */
+  public Classifier getClassifier() {
+    return new LMT();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LMTTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/M5PTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/M5PTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/M5PTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests M5P. Run from the command line with:<p>
+ * java weka.classifiers.trees.M5PTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class M5PTest extends AbstractClassifierTest {
+
+  public M5PTest(String name) { super(name);  }
+
+  /** Creates a default M5P */
+  public Classifier getClassifier() {
+    return new M5P();
+  }
+
+  public static Test suite() {
+    return new TestSuite(M5PTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/NBTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/NBTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/NBTreeTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NBTree. Run from the command line with:<p/>
+ * java weka.classifiers.trees.NBTreeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NBTreeTest 
+  extends AbstractClassifierTest {
+
+  public NBTreeTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NBTree */
+  public Classifier getClassifier() {
+    return new NBTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NBTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/REPTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/REPTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/REPTreeTest.java	(revision 29)
@@ -0,0 +1,53 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests REPTree. Run from the command line with:<p>
+ * java weka.classifiers.trees.REPTreeTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.2 $
+ */
+public class REPTreeTest extends AbstractClassifierTest {
+
+  public REPTreeTest(String name) { super(name);  }
+
+  /** Creates a default REPTree */
+  public Classifier getClassifier() {
+    return new REPTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(REPTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomForestTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomForestTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomForestTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomForest. Run from the command line with:<p/>
+ * java weka.classifiers.trees.RandomForestTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomForestTest 
+  extends AbstractClassifierTest {
+
+  public RandomForestTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomForest */
+  public Classifier getClassifier() {
+    return new RandomForest();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomForestTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/RandomTreeTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomTree. Run from the command line with:<p/>
+ * java weka.classifiers.trees.RandomTreeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomTreeTest 
+  extends AbstractClassifierTest {
+
+  public RandomTreeTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomTree */
+  public Classifier getClassifier() {
+    return new RandomTree();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/SimpleCartTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/SimpleCartTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/SimpleCartTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SimpleCart. Run from the command line with:<p>
+ * java weka.classifiers.trees.SimpleCartTest
+ *
+ * @author <a href="mailto:hs69@cs.waikato.ac.nz">Haijian Shi</a>
+ * @version $Revision: 1.1 $
+ */
+public class SimpleCartTest
+  extends AbstractClassifierTest {
+
+  public SimpleCartTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default SimpleCart */
+  public Classifier getClassifier() {
+    return new SimpleCart();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SimpleCartTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/classifiers/trees/UserClassifierTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/classifiers/trees/UserClassifierTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/classifiers/trees/UserClassifierTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 University of Waikato
+ */
+
+package weka.classifiers.trees;
+
+import weka.classifiers.AbstractClassifierTest;
+import weka.classifiers.Classifier;
+import weka.classifiers.rules.ZeroR;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Dummy test for user classifier. Actually uses ZeroR. Run from the
+ * command line with: <p/> 
+ * java weka.classifiers.trees.UserClassifierTest
+ *
+ * @author <a href="mailto:eibe@cs.waikato.ac.nz">Eibe Frank</a>
+ * @version $Revision: 1.3 $
+ */
+public class UserClassifierTest extends AbstractClassifierTest {
+
+  public UserClassifierTest(String name) { super(name);  }
+
+  /** Creates a default UserClassifier */
+  public Classifier getClassifier() {
+    return new ZeroR();
+  }
+
+  public static Test suite() {
+    return new TestSuite(UserClassifierTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/AbstractClustererTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/AbstractClustererTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/AbstractClustererTest.java	(revision 29)
@@ -0,0 +1,641 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Clusterers. Internally it uses the class
+ * <code>CheckClusterer</code> to determine success or failure of the
+ * tests. It follows basically the <code>runTests</code> method.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.9 $
+ *
+ * @see CheckClusterer
+ * @see CheckClusterer#runTests(boolean, boolean, boolean)
+ */
+public abstract class AbstractClustererTest 
+  extends TestCase {
+
+  /** The clusterer to be tested */
+  protected Clusterer m_Clusterer;
+
+  /** For testing the clusterer */
+  protected CheckClusterer m_Tester;
+  
+  /** whether classifier is updateable */
+  protected boolean m_updateableClusterer;
+
+  /** whether clusterer handles weighted instances */
+  protected boolean m_weightedInstancesHandler;
+
+  /** whether clusterer handles multi-instance data */
+  protected boolean m_multiInstanceHandler;
+
+  /** whether to run CheckClusterer in DEBUG mode */
+  protected boolean DEBUG = false;
+  
+  /** wether clusterer can predict nominal attributes (array index is attribute type of class) */
+  protected boolean m_NominalPredictors;
+  
+  /** wether clusterer can predict numeric attributes (array index is attribute type of class) */
+  protected boolean m_NumericPredictors;
+  
+  /** wether clusterer can predict string attributes (array index is attribute type of class) */
+  protected boolean m_StringPredictors;
+  
+  /** wether clusterer can predict date attributes (array index is attribute type of class) */
+  protected boolean m_DatePredictors;
+  
+  /** wether clusterer can predict relational attributes (array index is attribute type of class) */
+  protected boolean m_RelationalPredictors;
+  
+  /** whether clusterer handles missing values */
+  protected boolean m_handleMissingPredictors;
+  
+  /** the result of the regression test */
+  protected String m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractClustererTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractClustererTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default clusterer to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Clusterer = getClusterer();
+    m_Tester    = new CheckClusterer();
+    m_Tester.setSilent(true);
+    m_Tester.setClusterer(m_Clusterer);
+    m_Tester.setNumInstances(20);
+    m_Tester.setDebug(DEBUG);
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+
+    m_updateableClusterer         = m_Tester.updateableClusterer()[0];
+    m_weightedInstancesHandler     = m_Tester.weightedInstancesHandler()[0];
+    m_multiInstanceHandler         = m_Tester.multiInstanceHandler()[0];
+    m_NominalPredictors            = false;
+    m_NumericPredictors            = false;
+    m_StringPredictors             = false;
+    m_DatePredictors               = false;
+    m_RelationalPredictors         = false;
+    m_handleMissingPredictors      = false;
+    m_RegressionResults            = "";
+
+    // initialize attributes
+    checkAttributes(true,  false, false, false, false, false);
+    checkAttributes(false, true,  false, false, false, false);
+    checkAttributes(false, false, true,  false, false, false);
+    checkAttributes(false, false, false, true,  false, false);
+    checkAttributes(false, false, false, false, true,  false);
+
+    // 20% missing values
+    m_handleMissingPredictors = checkMissingPredictors(20, false);
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Clusterer    = null;
+    m_Tester       = null;
+    m_OptionTester = null;
+    m_GOETester    = null;
+
+    m_updateableClusterer          = false;
+    m_weightedInstancesHandler     = false;
+    m_NominalPredictors            = false;
+    m_NumericPredictors            = false;
+    m_StringPredictors             = false;
+    m_DatePredictors               = false;
+    m_RelationalPredictors         = false;
+    m_handleMissingPredictors      = false;
+    m_RegressionResults            = "";
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (getClusterer() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getClusterer());
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Clusterer returned from the getClusterer() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getClusterer()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getClusterer());
+    result.setSilent(true);
+    
+    return result;
+  }
+
+  /**
+   * Used to create an instance of a specific clusterer.
+   *
+   * @return a suitably configured <code>Clusterer</code> value
+   */
+  public abstract Clusterer getClusterer();
+
+  /**
+   * checks whether at least one attribute type can be handled
+   * 
+   * @return            true if at least one attribute type can be handled
+   */
+  protected boolean canPredict() {
+    return    m_NominalPredictors
+           || m_NumericPredictors
+           || m_StringPredictors
+           || m_DatePredictors
+           || m_RelationalPredictors;
+  }
+
+  /**
+   * tests whether the clusterer can handle certain attributes and if not,
+   * if the exception is OK
+   *
+   * @param nom         to check for nominal attributes
+   * @param num         to check for numeric attributes
+   * @param str         to check for string attributes
+   * @param dat         to check for date attributes
+   * @param rel         to check for relational attributes
+   * @param allowFail   whether a junit fail can be executed
+   * @see CheckClusterer#canPredict(boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  protected void checkAttributes(boolean nom, boolean num, boolean str, 
+                                 boolean dat, boolean rel,
+                                 boolean allowFail) {
+    boolean[]     result;
+    String        att;
+
+    // determine text for type of attributes
+    att = "";
+    if (nom)
+      att = "nominal";
+    else if (num)
+      att = "numeric";
+    else if (str)
+      att = "string";
+    else if (dat)
+      att = "date";
+    else if (rel)
+      att = "relational";
+    
+    result = m_Tester.canPredict(nom, num, str, dat, rel, m_multiInstanceHandler);
+    if (nom)
+      m_NominalPredictors = result[0];
+    else if (num)
+      m_NumericPredictors = result[0];
+    else if (str)
+      m_StringPredictors = result[0];
+    else if (dat)
+      m_DatePredictors = result[0];
+    else if (rel)
+      m_RelationalPredictors = result[0];
+
+    if (!result[0] && !result[1] && allowFail)
+      fail("Error handling " + att + " attributes!");
+  }
+
+  /**
+   * tests whether the clusterer can handle different types of attributes and
+   * if not, if the exception is OK
+   *
+   * @see #checkAttributes(boolean, boolean, boolean, boolean, boolean, boolean)
+   */
+  public void testAttributes() {
+    // nominal
+    checkAttributes(true,  false, false, false, false, true);
+    // numeric
+    checkAttributes(false, true,  false, false, false, true);
+    // string
+    checkAttributes(false, false, true,  false, false, true);
+    // date
+    checkAttributes(false, false, false, true,  false, true);
+    // relational
+    if (!m_multiInstanceHandler)
+      checkAttributes(false, false, false, false, true,  true);
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean[]     result;
+
+    result = m_Tester.declaresSerialVersionUID();
+
+    if (!result[0])
+      fail("Doesn't declare serialVersionUID!");
+  }
+
+  /**
+   * tests whether the clusterer handles instance weights correctly
+   *
+   * @see CheckClusterer#instanceWeights(boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testInstanceWeights() {
+    boolean[]     result;
+    
+    if (m_weightedInstancesHandler) {
+      if (!canPredict())
+	return;
+      
+      result = m_Tester.instanceWeights(
+          m_NominalPredictors,
+          m_NumericPredictors,
+          m_StringPredictors,
+          m_DatePredictors, 
+          m_RelationalPredictors, 
+          m_multiInstanceHandler);
+
+      if (!result[0])
+        System.err.println("Error handling instance weights!");
+    }
+  }
+
+  /**
+   * tests whether the clusterer can handle zero training instances
+   *
+   * @see CheckClusterer#canHandleZeroTraining(boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testZeroTraining() {
+    boolean[]     result;
+    
+    if (!canPredict())
+      return;
+    
+    result = m_Tester.canHandleZeroTraining(
+        m_NominalPredictors, 
+        m_NumericPredictors, 
+        m_StringPredictors, 
+        m_DatePredictors, 
+        m_RelationalPredictors, 
+        m_multiInstanceHandler);
+
+    if (!result[0] && !result[1])
+      fail("Error handling zero training instances!");
+  }
+
+  /**
+   * checks whether the clusterer can handle the given percentage of
+   * missing predictors
+   *
+   * @param percent     the percentage of missing predictors
+   * @param allowFail	if true a fail statement may be executed
+   * @return            true if the clusterer can handle it
+   */
+  protected boolean checkMissingPredictors(int percent, boolean allowFail) {
+    boolean[]     result;
+    
+    result = m_Tester.canHandleMissing(
+        m_NominalPredictors, 
+        m_NumericPredictors, 
+        m_StringPredictors, 
+        m_DatePredictors, 
+        m_RelationalPredictors, 
+        m_multiInstanceHandler, 
+        true,
+        percent);
+
+    if (allowFail) {
+      if (!result[0] && !result[1])
+	fail("Error handling " + percent + "% missing predictors!");
+    }
+    
+    return result[0];
+  }
+
+  /**
+   * tests whether the clusterer can handle missing predictors (20% and 100%)
+   *
+   * @see CheckClusterer#canHandleMissing(boolean, boolean, boolean, boolean, boolean, boolean, boolean, int)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testMissingPredictors() {
+    if (!canPredict())
+      return;
+    
+    // 20% missing
+    checkMissingPredictors(20, true);
+
+    // 100% missing
+    if (m_handleMissingPredictors)
+      checkMissingPredictors(100, true);
+  }
+
+  /**
+   * tests whether the clusterer correctly initializes in the
+   * buildClusterer method
+   *
+   * @see CheckClusterer#correctBuildInitialisation(boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testBuildInitialization() {
+    boolean[]     result;
+    
+    if (!canPredict())
+      return;
+    
+    result = m_Tester.correctBuildInitialisation(
+        m_NominalPredictors, 
+        m_NumericPredictors, 
+        m_StringPredictors, 
+        m_DatePredictors, 
+        m_RelationalPredictors, 
+        m_multiInstanceHandler);
+
+    if (!result[0] && !result[1])
+      fail("Incorrect build initialization!");
+  }
+
+  /**
+   * tests whether the clusterer alters the training set during training.
+   *
+   * @see CheckClusterer#datasetIntegrity(boolean, boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testDatasetIntegrity() {
+    boolean[]     result;
+  
+    if (!canPredict())
+      return;
+    
+    result = m_Tester.datasetIntegrity(
+        m_NominalPredictors, 
+        m_NumericPredictors, 
+        m_StringPredictors, 
+        m_DatePredictors, 
+        m_RelationalPredictors, 
+        m_multiInstanceHandler, 
+        m_handleMissingPredictors);
+
+    if (!result[0] && !result[1])
+      fail("Training set is altered during training!");
+  }
+
+  /**
+   * tests whether the classifier produces the same model when trained
+   * incrementally as when batch trained.
+   *
+   * @see CheckClusterer#updatingEquality(boolean, boolean, boolean, boolean, boolean, boolean)
+   * @see CheckClusterer#runTests(boolean, boolean, boolean)
+   */
+  public void testUpdatingEquality() {
+    boolean[]     result;
+    
+    if (m_updateableClusterer) {
+        result = m_Tester.updatingEquality(
+            m_NominalPredictors, 
+            m_NumericPredictors, 
+            m_StringPredictors, 
+            m_DatePredictors, 
+            m_RelationalPredictors, 
+            m_multiInstanceHandler);
+
+        if (!result[0])
+          System.err.println(
+              "Incremental training does not produce same result as batch training!");
+    }
+  }
+
+  /**
+   * Builds a model using the current Clusterer using the given data and 
+   * returns the produced cluster assignments.
+   * 
+   * @param data 	the instances to test the Clusterer on
+   * @return 		a String containing the cluster assignments.
+   */
+  protected String useClusterer(Instances data) throws Exception {
+    String	result;
+    Clusterer 	clusterer;
+    int		i;
+    double	cluster;
+    
+    try {
+      clusterer = AbstractClusterer.makeCopy(m_Clusterer);
+    } 
+    catch (Exception e) {
+      clusterer = null;
+      e.printStackTrace();
+      fail("Problem setting up to use Clusterer: " + e);
+    }
+
+    clusterer.buildClusterer(data);
+    
+    // generate result
+    result = "";
+    for (i = 0; i < data.numInstances(); i++) {
+      if (i > 0)
+	result += "\n";
+      try {
+	cluster = clusterer.clusterInstance(data.instance(i));
+	result += "" + (i+1) + ": " + cluster;
+      }
+      catch (Exception e) {
+	result += "" + (i+1) + ": " + e.toString();
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() throws Exception {
+    boolean	succeeded;
+    Regression 	reg;
+    Instances   train;
+    
+    // don't bother if not working correctly
+    if (m_Tester.hasClasspathProblems())
+      return;
+    
+    reg       = new Regression(this.getClass());
+    train     = null;
+    succeeded = false;
+    
+    train = m_Tester.makeTestDataset(
+	42, m_Tester.getNumInstances(), 
+	m_NominalPredictors ? 2 : 0,
+	m_NumericPredictors ? 1 : 0, 
+	m_StringPredictors ? 1 : 0,
+	m_DatePredictors ? 1 : 0,
+	m_RelationalPredictors ? 1 : 0,
+	m_multiInstanceHandler);
+    
+    try {
+      m_RegressionResults = useClusterer(train);
+      succeeded = true;
+      reg.println(m_RegressionResults);
+    }
+    catch (Exception e) {
+      String msg = e.getMessage().toLowerCase();
+      if (msg.indexOf("not in classpath") > -1)
+	return;
+      
+      m_RegressionResults = null;
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful output generated");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkDefaultOptions())
+	fail("Default options were not processed correctly.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/AllTests.java	(revision 29)
@@ -0,0 +1,44 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Test class for all clusterers. Run from the command line with: <p/>
+ * java weka.clusterers.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    return suite("weka.clusterers.Clusterer");
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/CLOPETest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/CLOPETest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/CLOPETest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CLOPE. Run from the command line with:<p/>
+ * java weka.clusterers.CLOPETest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
+ * @version $Revision: 5563 $
+ */
+public class CLOPETest 
+  extends AbstractClustererTest {
+
+  public CLOPETest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleKMeans */
+  public Clusterer getClusterer() {
+    return new SimpleKMeans();
+  }
+
+  public static Test suite() {
+    return new TestSuite(CLOPETest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/CobwebTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/CobwebTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/CobwebTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Cobweb. Run from the command line with:<p/>
+ * java weka.clusterers.CobwebTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class CobwebTest 
+  extends AbstractClustererTest {
+
+  public CobwebTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Cobweb */
+  public Clusterer getClusterer() {
+    return new Cobweb();
+  }
+
+  public static Test suite() {
+    return new TestSuite(CobwebTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/DBScanTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/DBScanTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/DBScanTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests DBScan. Run from the command line with:<p/>
+ * java weka.clusterers.DBScanTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class DBScanTest 
+  extends AbstractClustererTest {
+
+  public DBScanTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default DBScan */
+  public Clusterer getClusterer() {
+    return new DBScan();
+  }
+
+  public static Test suite() {
+    return new TestSuite(DBScanTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/EMTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/EMTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/EMTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests EM. Run from the command line with:<p/>
+ * java weka.clusterers.EMTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class EMTest 
+  extends AbstractClustererTest {
+
+  public EMTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default EM */
+  public Clusterer getClusterer() {
+    return new EM();
+  }
+
+  public static Test suite() {
+    return new TestSuite(EMTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/FarthestFirstTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/FarthestFirstTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/FarthestFirstTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FarthestFirst. Run from the command line with:<p/>
+ * java weka.clusterers.FarthestFirstTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class FarthestFirstTest 
+  extends AbstractClustererTest {
+
+  public FarthestFirstTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default FarthestFirst */
+  public Clusterer getClusterer() {
+    return new FarthestFirst();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FarthestFirstTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/FilteredClustererTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/FilteredClustererTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/FilteredClustererTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FilteredClusterer. Run from the command line with:<p/>
+ * java weka.clusterers.FilteredClustererTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class FilteredClustererTest 
+  extends AbstractClustererTest {
+
+  public FilteredClustererTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default FilteredClusterer */
+  public Clusterer getClusterer() {
+    return new FilteredClusterer();
+  }
+
+  public static Test suite() {
+    return new TestSuite(FilteredClustererTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/HierarchicalClustererTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/HierarchicalClustererTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/HierarchicalClustererTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests HierarchicalClusterer. Run from the command line with:<p/>
+ * java weka.clusterers.HierarchicalClustererTest
+ *
+ * @author Mark Hall
+ * @version $Revision: 5963 $
+ */
+public class HierarchicalClustererTest
+  extends AbstractClustererTest {
+
+  public HierarchicalClustererTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default HierarchicalClusterer */
+  public Clusterer getClusterer() {
+    return new HierarchicalClusterer();
+  }
+
+  public static Test suite() {
+    return new TestSuite(HierarchicalClustererTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/MakeDensityBasedClustererTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/MakeDensityBasedClustererTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/MakeDensityBasedClustererTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MakeDensityBasedClusterer. Run from the command line with:<p/>
+ * java weka.clusterers.MakeDensityBasedClustererTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class MakeDensityBasedClustererTest 
+  extends AbstractClustererTest {
+
+  public MakeDensityBasedClustererTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MakeDensityBasedClusterer */
+  public Clusterer getClusterer() {
+    return new MakeDensityBasedClusterer();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MakeDensityBasedClustererTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/OPTICSTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/OPTICSTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/OPTICSTest.java	(revision 29)
@@ -0,0 +1,57 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OPTICS. Run from the command line with:<p/>
+ * java weka.clusterers.OPTICSTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 4816 $
+ */
+public class OPTICSTest 
+  extends AbstractClustererTest {
+
+  public OPTICSTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default OPTICS */
+  public Clusterer getClusterer() {
+    OPTICS ops = new OPTICS();
+    ops.setShowGUI(false);
+    return ops;
+  }
+
+  public static Test suite() {
+    return new TestSuite(OPTICSTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/SimpleKMeansTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/SimpleKMeansTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/SimpleKMeansTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SimpleKMeans. Run from the command line with:<p/>
+ * java weka.clusterers.SimpleKMeansTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class SimpleKMeansTest 
+  extends AbstractClustererTest {
+
+  public SimpleKMeansTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleKMeans */
+  public Clusterer getClusterer() {
+    return new SimpleKMeans();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SimpleKMeansTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/XMeansTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/XMeansTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/XMeansTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests XMeans. Run from the command line with:<p/>
+ * java weka.clusterers.XMeansTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class XMeansTest 
+  extends AbstractClustererTest {
+
+  public XMeansTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default XMeans */
+  public Clusterer getClusterer() {
+    return new XMeans();
+  }
+
+  public static Test suite() {
+    return new TestSuite(XMeansTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/clusterers/sIBTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/clusterers/sIBTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/clusterers/sIBTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.clusterers;
+
+import weka.clusterers.AbstractClustererTest;
+import weka.clusterers.Clusterer;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests sIB. Run from the command line with:<p/>
+ * java weka.clusterers.sIBTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5563 $
+ */
+public class sIBTest 
+  extends AbstractClustererTest {
+
+  public sIBTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SimpleKMeans */
+  public Clusterer getClusterer() {
+    return new sIB();
+  }
+
+  public static Test suite() {
+    return new TestSuite(sIBTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/AlgVectorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/AlgVectorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/AlgVectorTest.java	(revision 29)
@@ -0,0 +1,194 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core;
+
+import java.util.Random;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AlgVector. Run from the command line with:<p/>
+ * java weka.core.AlgVectorTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AlgVectorTest 
+  extends TestCase {
+
+  /** for generating the datasets */
+  protected Random m_Random;
+  
+  /**
+   * Constructs the <code>AlgVectorTest</code>.
+   *
+   * @param name 	the name of the test class
+   */
+  public AlgVectorTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Random = new Random(1);
+  }
+
+  /**
+   * Called by JUnit after each test method
+   * 
+   * @throws Exception 	if an error occurs
+   */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+
+    m_Random = null;
+  }
+
+  /**
+   * generates data with the given amount of nominal and numeric attributes
+   * 
+   * @param nominal	the number of nominal attributes
+   * @param numeric	the number of numeric attributes
+   * @param rows	the number of rows to generate
+   * @return		the generated data
+   */
+  protected Instances generateData(int nominal, int numeric, int rows) {
+    Instances		result;
+    TestInstances	test;
+    
+    test = new TestInstances();
+    test.setClassIndex(TestInstances.NO_CLASS);
+    test.setNumNominal(nominal);
+    test.setNumNumeric(numeric);
+    test.setNumInstances(rows);
+    
+    try {
+      result = test.generate();
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * tests constructing a vector with a given length
+   */
+  public void testLengthConstructor() {
+    int len = 22;
+    AlgVector v = new AlgVector(len);
+    assertEquals("Length differs", len, v.numElements());
+  }
+
+  /**
+   * tests constructing a vector from an array
+   */
+  public void testArrayConstructor() {
+    double[] data = {2.3, 1.2, 5.0};
+    AlgVector v = new AlgVector(data);
+    assertEquals("Length differs", data.length, v.numElements());
+    for (int i = 0; i < data.length; i++)
+      assertEquals((i+1) + ". value differs", data[i], v.getElement(i));
+  }
+
+  /**
+   * runs tests with the given data
+   * 
+   * @param data	the data to test with
+   */
+  protected void runTestOnData(Instances data) {
+    // count numeric atts
+    int numeric = 0;
+    for (int n = 0; n < data.numAttributes(); n++) {
+      if (data.attribute(n).isNumeric())
+	numeric++;
+    }
+    
+    // perform tests
+    for (int n = 0; n < data.numInstances(); n++) {
+      try {
+	AlgVector v = new AlgVector(data.instance(n));
+	
+	// 1. is length correct?
+	assertEquals((n+1) + ": length differs", numeric, v.numElements());
+	
+	// 2. are values correct?
+	int index = 0;
+	for (int i = 0; i < data.numAttributes(); i++) {
+	  if (!data.attribute(i).isNumeric())
+	    continue;
+	  assertEquals((n+1) + "/" + (i+1) + ": value differs", data.instance(n).value(i), v.getElement(index));
+	  index++;
+	}
+	
+	// 3. is instance returned correct?
+	Instance inst = v.getAsInstance(data, new Random(1));
+	for (int i = 0; i < data.numAttributes(); i++) {
+	  if (!data.attribute(i).isNumeric())
+	    continue;
+	  assertEquals((n+1) + "/" + (i+1) + ": returned value differs", data.instance(n).value(i), inst.value(i));
+	}
+      }
+      catch (Exception e) {
+	if (!(e instanceof IllegalArgumentException))
+	  fail(e.toString());
+      }
+    }
+  }
+  
+  /**
+   * tests constructing a vector from a purely numeric instance
+   */
+  public void testNumericInstances() {
+    runTestOnData(generateData(0, 5, 5));
+  }
+  
+  /**
+   * tests constructing a vector from a purely nominal instance
+   */
+  public void testNominalInstances() {
+    runTestOnData(generateData(5, 0, 5));
+  }
+  
+  /**
+   * tests constructing a vector from a mixed instance
+   */
+  public void testMixedInstances() {
+    runTestOnData(generateData(5, 5, 5));
+  }
+  
+  public static Test suite() {
+    return new TestSuite(AlgVectorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/AllTests.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import weka.test.WekaTestSuite;
+
+import java.util.Vector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all core classes. Run from the command line with: <p/>
+ * java weka.core.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  /**
+   * generates all the tests
+   * 
+   * @return		all the tests
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    
+    // all test in core package
+    Vector packages = new Vector();
+    packages.add("weka.core");
+    suite.addTest(suite(TestCase.class.getName(), packages));
+
+    // all OptionHandler's
+    // TODO: fix all errors
+    //suite.addTest(OptionHandlersTest.suite());
+    
+    // converters
+    suite.addTest(weka.core.converters.AllTests.suite());
+    
+    // neighboursearch
+    suite.addTest(weka.core.neighboursearch.AllTests.suite());
+    
+    // tokenizers
+    suite.addTest(weka.core.tokenizers.AllTests.suite());
+
+    return suite;
+  }
+
+  /**
+   * for running the tests from commandline
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/MathematicalExpressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/MathematicalExpressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/MathematicalExpressionTest.java	(revision 29)
@@ -0,0 +1,106 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.HashMap;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MathematicalExpression. Run from the command line with:<p/>
+ * java weka.core.MathematicalTest
+ *
+ * @author mhall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5563 $
+ */
+public class MathematicalExpressionTest 
+  extends TestCase {
+
+  /**
+   * Constructs the <code>MathematicalExpresionTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public MathematicalExpressionTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MathematicalExpressionTest.class);
+  }
+
+  protected double getExpressionResult(String expression) throws Exception {
+    HashMap symbols = new HashMap();
+    symbols.put("A", new Double(4));
+    symbols.put("B", new Double(2));
+    symbols.put("C", new Double(2));
+    return MathematicalExpression.evaluate(expression, symbols);
+  }
+
+  public void testAddSub() throws Exception {
+    double result = getExpressionResult("A-B+C");
+    assertEquals(4.0, result);
+  }
+
+  public void testOperatorOrder() throws Exception {
+    double result = getExpressionResult("A-B*C");
+    assertEquals(0.0, result);
+  }
+
+  public void testBrackets() throws Exception {
+    double result = getExpressionResult("(A-B)*C");
+    assertEquals(4.0, result);
+  }
+
+  public void testExpressionWithConstants() throws Exception {
+    double result = getExpressionResult("A-B*(C+5)");
+    assertEquals(-10.0, result);
+  }
+
+  public void testExpressionWithFunction() throws Exception {
+    double result = getExpressionResult("pow(A,B*1)-C*2");
+    assertEquals(12.0, result);
+  } 
+
+  public void testExpressionWithIFELSE() throws Exception {
+    double result = getExpressionResult("ifelse((C<1000|C>5000),(A+B),C+C)");
+    assertEquals(6.0, result);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/OptionHandlersTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/OptionHandlersTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/OptionHandlersTest.java	(revision 29)
@@ -0,0 +1,267 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.core;
+
+import weka.gui.GenericPropertiesCreator;
+
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests OptionHandlers. Run from the command line with:<p/>
+ * java weka.core.OptionHandlerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class OptionHandlersTest 
+  extends TestCase {
+
+  /**
+   * tests a specific OptionHandler
+   */
+  public static class OptionHandlerTest
+    extends TestCase {
+  
+    /** the class to test */
+    protected String m_Classname;
+    
+    /** the OptionHandler tester */
+    protected CheckOptionHandler m_OptionTester;
+    
+    /**
+     * Constructs the <code>OptionHandlersTest</code>.
+     *
+     * @param name the name of the test class
+     * @param classname the actual classname
+     */
+    public OptionHandlerTest(String name, String classname) { 
+      super(name); 
+      
+      m_Classname = classname;
+    }
+    
+    /**
+     * returns the classname this test is for
+     * 
+     * @return		the classname
+     */
+    public String getClassname() {
+      return m_Classname;
+    }
+    
+    /**
+     * configures the optionhandler
+     * 
+     * @return		the configured optionhandler, null in case of an error
+     */
+    protected OptionHandler getOptionHandler() {
+      OptionHandler	result;
+      
+      try {
+	result = (OptionHandler) Class.forName(m_Classname).newInstance();
+      }
+      catch (Exception e) {
+	result = null;
+      }
+      
+      return result;
+    }
+    
+    /**
+     * Called by JUnit before each test method.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void setUp() throws Exception {
+      super.setUp();
+      
+      m_OptionTester = new CheckOptionHandler();
+      m_OptionTester.setOptionHandler(getOptionHandler());
+      m_OptionTester.setUserOptions(new String[0]);
+      m_OptionTester.setSilent(true);
+    }
+
+    /** 
+     * Called by JUnit after each test method
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void tearDown() throws Exception {
+      super.tearDown();
+      
+      m_OptionTester = null;
+    }
+    
+    /**
+     * tests the listing of the options
+     * 
+     * @throws Exception if test fails
+     */
+    public void testListOptions() throws Exception {
+      if (m_OptionTester.getOptionHandler() != null) {
+        if (!m_OptionTester.checkListOptions())
+  	fail(getClassname() + ": " + "Options cannot be listed via listOptions.");
+      }
+    }
+    
+    /**
+     * tests the setting of the options
+     * 
+     * @throws Exception if test fails
+     */
+    public void testSetOptions() throws Exception {
+      if (m_OptionTester.getOptionHandler() != null) {
+        if (!m_OptionTester.checkSetOptions())
+  	fail(getClassname() + ": " + "setOptions method failed.");
+      }
+    }
+    
+    /**
+     * tests whether there are any remaining options
+     * 
+     * @throws Exception if test fails
+     */
+    public void testRemainingOptions() throws Exception {
+      if (m_OptionTester.getOptionHandler() != null) {
+        if (!m_OptionTester.checkRemainingOptions())
+  	fail(getClassname() + ": " + "There were 'left-over' options.");
+      }
+    }
+    
+    /**
+     * tests the whether the user-supplied options stay the same after setting.
+     * getting, and re-setting again.
+     * 
+     * @see 	#m_OptionTester
+     * @throws Exception if test fails
+     */
+    public void testCanonicalUserOptions() throws Exception {
+      if (m_OptionTester.getOptionHandler() != null) {
+        if (!m_OptionTester.checkCanonicalUserOptions())
+  	fail(getClassname() + ": " + "setOptions method failed");
+      }
+    }
+    
+    /**
+     * tests the resetting of the options to the default ones
+     * 
+     * @throws Exception if test fails
+     */
+    public void testResettingOptions() throws Exception {
+      if (m_OptionTester.getOptionHandler() != null) {
+        if (!m_OptionTester.checkSetOptions())
+  	fail(getClassname() + ": " + "Resetting of options failed");
+      }
+    }
+  }
+  
+  /**
+   * Constructs the <code>OptionHandlersTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public OptionHandlersTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * dummy for JUnit, does nothing, only to prevent JUnit from complaining 
+   * about "no tests"
+   * 
+   * @throws Exception never happens
+   */
+  public void testDummy() throws Exception {
+    // does nothing, only to prevent JUnit from complaining about "no tests"
+  }
+  
+  /**
+   * generate all tests
+   * 
+   * @return		all the tests
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    
+    try {
+      // determine all test methods in the OptionHandlerTest class
+      Vector<String> testMethods = new Vector<String>();
+      Method[] methods = OptionHandlerTest.class.getDeclaredMethods();
+      for (int i = 0; i < methods.length; i++) {
+	if (methods[i].getName().startsWith("test"))
+	  testMethods.add(methods[i].getName());
+      }
+      
+      // get all classes that are accessible through the GUI
+      GenericPropertiesCreator creator = new GenericPropertiesCreator();
+      creator.execute(false);
+      Properties props = creator.getOutputProperties();
+      
+      // traverse all super-classes
+      Enumeration names = props.propertyNames();
+      while (names.hasMoreElements()) {
+	String name = names.nextElement().toString();
+
+	// add tests for all listed classes
+	StringTokenizer tok = new StringTokenizer(props.getProperty(name, ""), ",");
+	while (tok.hasMoreTokens()) {
+	  String classname = tok.nextToken();
+	  
+	  // does class implement OptionHandler?
+	  try {
+	    Class cls = Class.forName(classname);
+	    if (!ClassDiscovery.hasInterface(OptionHandler.class, cls))
+	      continue;
+	  }
+	  catch (Exception e) {
+	    // some other problem, skip this class
+	    continue;
+	  }
+	  
+	  // add tests for this class
+	  for (int i = 0; i < testMethods.size(); i++)
+	    suite.addTest(new OptionHandlerTest(testMethods.get(i), classname));
+	}
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return suite;
+  }
+
+  /**
+   * for running the tests from commandline
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/RangeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/RangeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/RangeTest.java	(revision 29)
@@ -0,0 +1,201 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.core;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Range. Run from the command line with:<p/>
+ * java weka.core.RangeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class RangeTest 
+  extends TestCase {
+  
+  /**
+   * Constructs the <code>RangeTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public RangeTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  /**
+   * returns a configured Range
+   *
+   * @param initial     the initial string, if null the default constructor
+   *                    is used (and "1" is set - otherwise setUpper doesn't
+   *                    work!)
+   * @param upper       the upper limit
+   */
+  protected Range getRange(String initial, int upper) {
+    Range   result; 
+
+    if (initial == null) {
+      result = new Range();
+      result.setRanges("1");
+      result.setUpper(upper);
+    }
+    else {
+      result = new Range(initial);
+      result.setUpper(upper);
+    }
+
+    return result;
+  }
+
+  /**
+   * test the default constructor
+   */
+  public void testDefaultConstructor() throws Exception {
+    int upper = 10;
+    int indexInt = 0;
+    String indexStr = "" + (indexInt + 1);
+    Range index = new Range();
+    index.setRanges(indexStr);
+    index.setUpper(upper);
+
+    assertEquals(indexStr, index.getRanges());
+    assertEquals(1, index.getSelection().length);
+    assertEquals(indexInt, index.getSelection()[0]);
+  }
+
+  /**
+   * tests the constructor with initial value
+   */
+  public void testInitialValueConstructor() throws Exception {
+    int upper = 10;
+    int indexInt = 0;
+    String indexStr = "" + (indexInt + 1);
+    Range index = getRange("1", upper);
+
+    assertEquals(indexStr, index.getRanges());
+    assertEquals(1, index.getSelection().length);
+    assertEquals(indexInt, index.getSelection()[0]);
+  }
+
+  /**
+   * tests whether "first" is interpreted correctly
+   */
+  public void testFirst() throws Exception {
+    int upper = 10;
+    Range index = getRange("first", upper);
+
+    assertEquals("first", index.getRanges());
+    assertEquals(1, index.getSelection().length);
+    assertEquals(0, index.getSelection()[0]);
+  }
+
+  /**
+   * tests whether "last" is interpreted correctly
+   */
+  public void testLast() throws Exception {
+    int upper = 10;
+    Range index = getRange("last", upper);
+
+    assertEquals("last", index.getRanges());
+    assertEquals(1, index.getSelection().length);
+    assertEquals(upper, index.getSelection()[0]);
+  }
+
+  /**
+   * tests whether "first-last" is interpreted correctly
+   */
+  public void testFirstLast() throws Exception {
+    int upper = 10;
+    Range index = getRange("first-last", upper);
+
+    assertEquals("first-last", index.getRanges());
+    assertEquals(upper + 1, index.getSelection().length);
+  }
+
+  /**
+   * tests whether a simple "range" is interpreted correctly
+   */
+  public void testSimpleRange() throws Exception {
+    int upper = 10;
+    String range = "1-3";
+    Range index = getRange(range, upper);
+    int[] expected = new int[]{0,1,2};
+
+    assertEquals(range, index.getRanges());
+    assertEquals(expected.length, index.getSelection().length);
+    for (int i = 0; i < expected.length; i++)
+      assertEquals(expected[i], index.getSelection()[i]);
+  }
+
+  /**
+   * tests whether a mixed "range" is interpreted correctly
+   */
+  public void testMixedRange() throws Exception {
+    int upper = 10;
+    String range = "1-3,6,8-last";
+    Range index = getRange(range, upper);
+    int[] expected = new int[]{0,1,2,5,7,8,9,10};
+
+    assertEquals(range, index.getRanges());
+    assertEquals(expected.length, index.getSelection().length);
+    for (int i = 0; i < expected.length; i++)
+      assertEquals(expected[i], index.getSelection()[i]);
+  }
+
+  /**
+   * tests whether an unordered "range" is interpreted correctly
+   */
+  public void testUnorderedRange() throws Exception {
+    int upper = 10;
+    String range = "8-last,1-3,6";
+    Range index = getRange(range, upper);
+    int[] expected = new int[]{7,8,9,10,0,1,2,5};
+
+    assertEquals(range, index.getRanges());
+    assertEquals(expected.length, index.getSelection().length);
+    for (int i = 0; i < expected.length; i++)
+      assertEquals(expected[i], index.getSelection()[i]);
+  }
+
+  public static Test suite() {
+    return new TestSuite(RangeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/SingleIndexTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/SingleIndexTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/SingleIndexTest.java	(revision 29)
@@ -0,0 +1,141 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.core;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SingleIndex. Run from the command line with:<p/>
+ * java weka.core.SingleIndexTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class SingleIndexTest 
+  extends TestCase {
+  
+  /**
+   * Constructs the <code>SingleIndexTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public SingleIndexTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  /**
+   * returns a configured SingleIndex
+   *
+   * @param initial     the initial string, if null the default constructor
+   *                    is used (and "1" is set - otherwise setUpper doesn't
+   *                    work!)
+   * @param upper       the upper limit
+   */
+  protected SingleIndex getIndex(String initial, int upper) {
+    SingleIndex   result; 
+
+    if (initial == null) {
+      result = new SingleIndex();
+      result.setSingleIndex("1");
+      result.setUpper(upper);
+    }
+    else {
+      result = new SingleIndex(initial);
+      result.setUpper(upper);
+    }
+
+    return result;
+  }
+
+  /**
+   * test the default constructor
+   */
+  public void testDefaultConstructor() throws Exception {
+    int upper = 10;
+    int indexInt = 0;
+    String indexStr = "" + (indexInt + 1);
+    SingleIndex index = new SingleIndex();
+    index.setSingleIndex(indexStr);
+    index.setUpper(upper);
+
+    assertEquals(indexStr, index.getSingleIndex());
+    assertEquals(indexInt, index.getIndex());
+  }
+
+  /**
+   * tests the constructor with initial value
+   */
+  public void testInitialValueConstructor() throws Exception {
+    int upper = 10;
+    int indexInt = 0;
+    String indexStr = "" + (indexInt + 1);
+    SingleIndex index = getIndex("1", upper);
+
+    assertEquals(indexStr, index.getSingleIndex());
+    assertEquals(indexInt, index.getIndex());
+  }
+
+  /**
+   * tests whether "first" is interpreted correctly
+   */
+  public void testFirst() throws Exception {
+    int upper = 10;
+    SingleIndex index = getIndex("first", upper);
+
+    assertEquals(0, index.getIndex());
+    assertEquals("first", index.getSingleIndex());
+  }
+
+  /**
+   * tests whether "last" is interpreted correctly
+   */
+  public void testLast() throws Exception {
+    int upper = 10;
+    SingleIndex index = getIndex("last", upper);
+
+    assertEquals(upper, index.getIndex());
+    assertEquals("last", index.getSingleIndex());
+  }
+
+  public static Test suite() {
+    return new TestSuite(SingleIndexTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/TrieTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/TrieTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/TrieTest.java	(revision 29)
@@ -0,0 +1,236 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * TrieTest.java
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Vector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Trie. Run from the command line with:<p/>
+ * java weka.core.TrieTest
+ *
+ * @author  fracpete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class TrieTest
+  extends TestCase {
+  
+  /** holds the data for testing the trie */
+  protected String[] m_Data;
+
+  /** the default trie built from m_Data
+   * @see #m_Data */
+  protected Trie m_Trie;
+  
+  /**
+   * Constructs the <code>TrieTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public TrieTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Data = new String[]{
+		"this is a test", 
+		"this is another test",
+		"and something else"};
+    m_Trie = buildTrie(m_Data);
+  }
+
+  /**
+   * Called by JUnit after each test method
+   */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  /**
+   * builds a new trie from the given data and returns it
+   * 
+   * @param data	the data to use for initializing the Trie
+   * @return		the built Trie
+   */
+  public Trie buildTrie(String data) {
+    return buildTrie(new String[]{data});
+  }
+
+  /**
+   * builds a new trie from the given data and returns it
+   * 
+   * @param data	the data to use for initializing the Trie
+   * @return		the built Trie
+   */
+  public Trie buildTrie(String[] data) {
+    Trie	result;
+    int		i;
+    
+    result = new Trie();
+    for (i = 0; i < data.length; i++)
+      result.add(data[i]);
+    
+    return result;
+  }
+  
+  /**
+   * tests whether all the string the Trie got built with can be retrieved
+   * again (tests building and retrieval via iterator).
+   */
+  public void testCorrectBuild() {
+    // retrieve data
+    Iterator<String> iter = m_Trie.iterator();
+    HashSet<String> set = new HashSet<String>();
+    while (iter.hasNext())
+      set.add(iter.next());
+
+    // correct size?
+    assertEquals(
+	"size() does not reflect number of added strings", 
+	m_Data.length, m_Trie.size());
+    
+    // different size?
+    assertEquals(
+	"Iterator returns different number of strings", 
+	m_Data.length, set.size());
+    
+    // different elements?
+    for (int i = 0; i < m_Data.length; i++) {
+      if (!set.contains(m_Data[i]))
+	fail("Cannot find string '" + m_Data[i] + "'");
+    }
+  }
+  
+  /**
+   * tests whether a different order of strings presented to the Trie will
+   * result in a different Trie (= error).
+   */
+  public void testDifferentBuildOrder() {
+    // build 2. trie
+    String[] newData = new String[m_Data.length];
+    for (int i = 0; i < m_Data.length; i++)
+      newData[i] = m_Data[m_Data.length - i - 1];
+    Trie t2 = buildTrie(m_Data);
+    
+    if (!m_Trie.equals(t2))
+      fail("Tries differ");
+  }
+  
+  /**
+   * tests the cloning of a trie
+   */
+  public void testClone() {
+    // clone trie
+    Trie clone = (Trie) m_Trie.clone();
+    
+    if (!m_Trie.equals(clone))
+      fail("Tries differ");
+  }
+  
+  /**
+   * tests the remove all method (only a few elements get removed)
+   */
+  public void testRemoveAllPartial() {
+    Trie remove = buildTrie(m_Data[0]);
+    Trie clone = (Trie) m_Trie.clone();
+    m_Trie.removeAll(remove);
+    assertEquals("Removing of 1 string", clone.size(), m_Trie.size() + 1);
+  }
+  
+  /**
+   * tests the remove all method (all elements get removed)
+   */
+  public void testRemoveAllFull() {
+    Trie remove = buildTrie(m_Data);
+    Trie clone = (Trie) m_Trie.clone();
+    m_Trie.removeAll(remove);
+    assertEquals("Removing all strings", clone.size(), m_Trie.size() + m_Data.length);
+  }
+  
+  /**
+   * tests the retain all method (retains a few elements)
+   */
+  public void testRetainAllPartial() {
+    Trie retain = buildTrie(m_Data[0]);
+    m_Trie.retainAll(retain);
+    assertEquals("Retaining of 1 string", 1, m_Trie.size());
+  }
+  
+  /**
+   * tests the retain all method (retains all elements)
+   */
+  public void testRetainAllFull() {
+    Trie retain = buildTrie(m_Data);
+    Trie clone = (Trie) m_Trie.clone();
+    m_Trie.retainAll(retain);
+    assertEquals("Retaining all strings", clone.size(), m_Trie.size());
+  }
+  
+  /**
+   * tests whether the common prefix is determined correctly
+   */
+  public void testCommonPrefix() {
+    String returned = m_Trie.getCommonPrefix();
+    assertEquals("Common prefixes differ", 0, returned.length());
+
+    String expected = "this is a";
+    Trie t = buildTrie(new String[]{m_Data[0], m_Data[1]});
+    returned = t.getCommonPrefix();
+    assertEquals("Common prefixes differ", expected.length(), returned.length());
+  }
+
+  /**
+   * tests the finding of prefixes
+   */
+  public void testFindPrefixes() {
+    Vector<String> prefixes = m_Trie.getWithPrefix("this");
+    assertEquals("Different number of prefixes returned", 2, prefixes.size());
+
+    prefixes = m_Trie.getWithPrefix("blah");
+    assertEquals("Different number of prefixes returned", 0, prefixes.size());
+  }
+  
+  public static Test suite() {
+    return new TestSuite(TrieTest.class);
+  }
+
+  /**
+   * Runs the test.
+   * 
+   * @param args ignored
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/UtilsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/UtilsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/UtilsTest.java	(revision 29)
@@ -0,0 +1,177 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core;
+
+import weka.filters.unsupervised.attribute.StringToWordVector;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Utils. Run from the command line with:<p/>
+ * java weka.core.UtilsTest
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class UtilsTest 
+  extends TestCase {
+  
+  /**
+   * Constructs the <code>UtilsTest</code>.
+   *
+   * @param name 	the name of the test class
+   */
+  public UtilsTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception 	if an error occurs
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+  }
+
+  /**
+   * Called by JUnit after each test method
+   * 
+   * @throws Exception 	if an error occurs
+   */
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  /**
+   * tests splitOptions and joinOptions
+   * 
+   * @see Utils#splitOptions(String)
+   * @see Utils#joinOptions(String[])
+   */
+  public void testSplittingAndJoining() {
+    String[] 	options;
+    String[] 	newOptions;
+    String 	joined;
+    int		i;
+    
+    try {
+      options    = new StringToWordVector().getOptions();
+      joined     = Utils.joinOptions(options);
+      newOptions = Utils.splitOptions(joined);
+      assertEquals("Same number of options", options.length, newOptions.length);
+      for (i = 0; i < options.length; i++) {
+	if (!options[i].equals(newOptions[i]))
+	  fail("Option " + (i+1) + " differs");
+      }
+    }
+    catch (Exception e) {
+      fail("Exception: " + e);
+    }
+  }
+  
+  /**
+   * tests quote and unquote
+   * 
+   * @see Utils#quote(String)
+   * @see Utils#unquote(String)
+   */
+  public void testQuoting() {
+    String 	input;
+    String 	output;
+    
+    input  = "blahblah";
+    output = Utils.quote(input);
+    assertTrue("No quoting necessary", !output.startsWith("'") && !output.endsWith("'"));
+    
+    input  = "";
+    output = Utils.quote(input);
+    assertTrue("Empty string quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue("Empty string restored", input.equals(Utils.unquote(output)));
+    
+    input  = " ";
+    output = Utils.quote(input);
+    assertTrue("Blank quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue("Blank restored", input.equals(Utils.unquote(output)));
+    
+    input  = "{";
+    output = Utils.quote(input);
+    assertTrue(">" + input + "< quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue(">" + input + "< restored", input.equals(Utils.unquote(output)));
+    
+    input  = "}";
+    output = Utils.quote(input);
+    assertTrue(">" + input + "< quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue(">" + input + "< restored", input.equals(Utils.unquote(output)));
+    
+    input  = ",";
+    output = Utils.quote(input);
+    assertTrue(">" + input + "< quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue(">" + input + "< restored", input.equals(Utils.unquote(output)));
+    
+    input  = "?";
+    output = Utils.quote(input);
+    assertTrue(">" + input + "< quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue(">" + input + "< restored", input.equals(Utils.unquote(output)));
+    
+    input  = "\r\n\t'\"%";
+    output = Utils.quote(input);
+    assertTrue(">" + input + "< quoted", output.startsWith("'") && output.endsWith("'"));
+    assertTrue(">" + input + "< restored", input.equals(Utils.unquote(output)));
+  }
+  
+  /**
+   * tests backQuoteChars and unbackQuoteChars
+   * 
+   * @see Utils#backQuoteChars(String)
+   * @see Utils#unbackQuoteChars(String)
+   */
+  public void testBackQuoting() {
+    String 	input;
+    String 	output;
+    
+    input  = "blahblah";
+    output = Utils.backQuoteChars(input);
+    assertTrue("No backquoting necessary", input.equals(output));
+    
+    input  = "\r\n\t'\"%";
+    output = Utils.backQuoteChars(input);
+    assertTrue(">" + input + "< restored", input.equals(Utils.unbackQuoteChars(output)));
+    
+    input  = "\\r\\n\\t\\'\\\"\\%";
+    output = Utils.backQuoteChars(input);
+    assertTrue(">" + input + "< restored", input.equals(Utils.unbackQuoteChars(output)));
+    
+    input  = Utils.joinOptions(new StringToWordVector().getOptions());
+    output = Utils.backQuoteChars(input);
+    assertTrue(">" + input + "< restored", input.equals(Utils.unbackQuoteChars(output)));
+  }
+  
+  public static Test suite() {
+    return new TestSuite(UtilsTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/converters/AbstractConverterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/AbstractConverterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/AbstractConverterTest.java	(revision 29)
@@ -0,0 +1,334 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.core.converters;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.TestInstances;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Converters.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public abstract class AbstractConverterTest 
+  extends TestCase {
+
+  /** whether the option handling is fully enabled */
+  public final static boolean TEST_OPTION_HANDLING = false;
+
+  /** for generating test data */
+  protected TestInstances m_TestInstances;
+  
+  /** the loader */
+  protected AbstractLoader m_Loader;
+  
+  /** the saver */
+  protected AbstractSaver m_Saver;
+  
+  /** the OptionHandler tester for the loader */
+  protected CheckOptionHandler m_OptionTesterLoader;
+  
+  /** the OptionHandler tester for the saver */
+  protected CheckOptionHandler m_OptionTesterSaver;
+  
+  /** for testing GOE stuff for the loader */
+  protected CheckGOE m_GOETesterLoader;
+  
+  /** for testing GOE stuff for the saver */
+  protected CheckGOE m_GOETesterSaver;
+  
+  /** the reference dataset */
+  protected Instances m_Instances;
+  
+  /**
+   * Constructs the <code>AbstractConverterTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractConverterTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns the loader used in the tests
+   * 
+   * @return the configured loader
+   */
+  public abstract AbstractLoader getLoader();
+
+  /**
+   * returns the saver used in the tests
+   * 
+   * @return the configured saver
+   */
+  public abstract AbstractSaver getSaver();
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @param o	the object to test
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester(Object o) {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (o instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) o);
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * 
+   * @param o	the object to test
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETester(Object o) {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(o);
+    result.setIgnoredProperties(result.getIgnoredProperties() + ",instances");
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler used for testing the option handling
+   * of the loader.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTesterLoader() {
+    return getOptionTester(getLoader());
+  }
+  
+  /**
+   * Configures the CheckOptionHandler used for testing the option handling
+   * of the saver.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTesterSaver() {
+    return getOptionTester(getSaver());
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing the option handling
+   * of the loader.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETesterLoader() {
+    return getGOETester(getLoader());
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing the option handling
+   * of the saver.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckGOE
+   */
+  protected CheckGOE getGOETesterSaver() {
+    return getGOETester(getSaver());
+  }
+
+  /**
+   * returns the test data generator
+   * 
+   * @return 	the configured test data generator
+   */
+  protected TestInstances getTestInstances() {
+    return new TestInstances();
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default converters (loader/saver) to test and loads a test set of 
+   * Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Loader             = getLoader();
+    m_Saver              = getSaver();
+    
+    m_OptionTesterLoader = getOptionTesterLoader();
+    m_OptionTesterSaver  = getOptionTesterSaver();
+    m_GOETesterLoader    = getGOETesterLoader();
+    m_GOETesterSaver     = getGOETesterSaver();
+    
+    m_TestInstances      = getTestInstances();
+    m_Instances          = m_TestInstances.generate();
+  }
+  
+  /** 
+   * Called by JUnit after each test method
+   */
+  protected void tearDown() throws Exception {
+    m_Loader             = null;
+    m_Saver              = null;
+
+    m_OptionTesterLoader = null;
+    m_OptionTesterSaver  = null;
+    m_GOETesterLoader    = null;
+    m_GOETesterSaver     = null;
+    
+    m_TestInstances      = null;
+    m_Instances          = null;
+    
+    super.tearDown();
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTesterLoader.getOptionHandler() != null) {
+      if (!m_OptionTesterLoader.checkListOptions())
+	fail("Loader: Options cannot be listed via listOptions.");
+    }
+
+    if (m_OptionTesterSaver.getOptionHandler() != null) {
+      if (!m_OptionTesterSaver.checkListOptions())
+	fail("Saver: Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    // TODO: currently disabled
+    if (!TEST_OPTION_HANDLING)
+      return;
+    
+    if (m_OptionTesterLoader.getOptionHandler() != null) {
+      if (!m_OptionTesterLoader.checkSetOptions())
+	fail("Loader: setOptions method failed.");
+    }
+
+    if (m_OptionTesterSaver.getOptionHandler() != null) {
+      if (!m_OptionTesterSaver.checkSetOptions())
+	fail("Saver: setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    // TODO: currently disabled
+    if (!TEST_OPTION_HANDLING)
+      return;
+    
+    if (m_OptionTesterLoader.getOptionHandler() != null) {
+      if (!m_OptionTesterLoader.checkRemainingOptions())
+	fail("Loader: There were 'left-over' options.");
+    }
+
+    if (m_OptionTesterSaver.getOptionHandler() != null) {
+      if (!m_OptionTesterSaver.checkRemainingOptions())
+	fail("Saver: There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   */
+  public void testCanonicalUserOptions() {
+    // TODO: currently disabled
+    if (!TEST_OPTION_HANDLING)
+      return;
+    
+    if (m_OptionTesterLoader.getOptionHandler() != null) {
+      if (!m_OptionTesterLoader.checkCanonicalUserOptions())
+	fail("Loader: setOptions method failed");
+    }
+
+    if (m_OptionTesterSaver.getOptionHandler() != null) {
+      if (!m_OptionTesterSaver.checkCanonicalUserOptions())
+	fail("Saver: setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    // TODO: currently disabled
+    if (!TEST_OPTION_HANDLING)
+      return;
+    
+    if (m_OptionTesterLoader.getOptionHandler() != null) {
+      if (!m_OptionTesterLoader.checkSetOptions())
+	fail("Loader: Resetting of options failed");
+    }
+
+    if (m_OptionTesterSaver.getOptionHandler() != null) {
+      if (!m_OptionTesterSaver.checkSetOptions())
+	fail("Saver: Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETesterLoader.checkGlobalInfo())
+      fail("Loader: No globalInfo method");
+
+    if (!m_GOETesterSaver.checkGlobalInfo())
+      fail("Saver: No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETesterLoader.checkToolTips())
+      fail("Loader: Tool tips inconsistent");
+
+    if (!m_GOETesterSaver.checkToolTips())
+      fail("Saver: Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/converters/AbstractFileConverterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/AbstractFileConverterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/AbstractFileConverterTest.java	(revision 29)
@@ -0,0 +1,426 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato 
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.Utils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+
+/**
+ * Abstract Test class for file converters.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5419 $
+ */
+public abstract class AbstractFileConverterTest 
+  extends AbstractConverterTest {
+  
+  /** the filename used for the data in ARFF format. */
+  protected String m_SourceFilename;
+  
+  /** the filename used for loading/saving in the export file format. */
+  protected String m_ExportFilename;
+
+  /** the command line options. */
+  protected String[] m_CommandlineOptions;
+  
+  /** the maximum different for attribute values. */
+  protected double m_MaxDiffValues;
+  
+  /** the maximum different for attribute weights. */
+  protected double m_MaxDiffWeights;
+  
+  /** whether to check the header when comparing datasets. */
+  protected boolean m_CheckHeader;
+  
+  /** whether to compare the attribute values as string. */
+  protected boolean m_CompareValuesAsString;
+  
+  /**
+   * Constructs the <code>AbstractFileConverterTest</code>. Called by
+   * subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractFileConverterTest(String name) { 
+    super(name); 
+  }
+  
+  /**
+   * returns a filename in ARFF format which can be used for loading and saving.
+   * 
+   * @return		the filename
+   */
+  protected String getSourceFilename() {
+    String	result;
+    File  	file;
+    
+    result = null;
+    
+    try {
+      file = File.createTempFile("weka_core_converters", ".arff");
+      file.deleteOnExit();
+      result = file.getAbsolutePath();
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns a filename in the export format which can be used for 
+   * loading and saving.
+   * 
+   * @return the filename
+   */
+  protected String getExportFilename() {
+    String	result;
+    File  	file;
+    
+    result = null;
+    
+    try {
+      file = File.createTempFile(
+	  "weka_core_converters",
+	  ((FileSourcedConverter) getLoader()).getFileExtension());
+      file.deleteOnExit();
+      result = file.getAbsolutePath();
+    }
+    catch (Exception e) {
+      result = null;
+    }
+    
+    return result;
+  }
+  
+  /**
+   * returns the command line options, either for the loader or the saver.
+   * 
+   * @param loader	if true the options for the loader will be returned,
+   * 			otherwise the ones for the saver
+   * @return		the command line options
+   */
+  protected String[] getCommandlineOptions(boolean loader) {
+    if (loader)
+      return new String[]{m_ExportFilename};
+    else
+      return new String[]{"-i", m_SourceFilename, "-o", m_ExportFilename};
+  }
+  
+  /**
+   * Compare two datasets to see if they differ.
+   *
+   * @param data1 one set of instances
+   * @param data2 the other set of instances
+   * @throws Exception if the datasets differ
+   */
+  protected void compareDatasets(Instances data1, Instances data2)
+    throws Exception {
+    
+    if (m_CheckHeader) {
+      if (!data2.equalHeaders(data1)) {
+	throw new Exception("header has been modified\n" + data2.equalHeadersMsg(data1));
+      }
+    }
+    if (!(data2.numInstances() == data1.numInstances())) {
+      throw new Exception("number of instances has changed");
+    }
+    for (int i = 0; i < data2.numInstances(); i++) {
+      Instance orig = data1.instance(i);
+      Instance copy = data2.instance(i);
+      for (int j = 0; j < orig.numAttributes(); j++) {
+        if (orig.isMissing(j)) {
+          if (!copy.isMissing(j)) {
+            throw new Exception("instances have changed");
+          }
+        } else {
+          if (m_CompareValuesAsString) {
+            if (!orig.toString(j).equals(copy.toString(j))) {
+              throw new Exception("instances have changed");
+            }
+          } else {
+            if (Math.abs(orig.value(j) - copy.value(j)) > m_MaxDiffValues) {
+              throw new Exception("instances have changed");
+            }
+          }
+        }
+        if (Math.abs(orig.weight() - copy.weight()) > m_MaxDiffWeights) {
+          throw new Exception("instance weights have changed");
+        }	  
+      }
+    }
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default loader/saver to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    BufferedWriter	writer;
+    
+    super.setUp();
+    
+    // how to compare datasets
+    // see compareDatasets(Instances,Instances)
+    m_MaxDiffValues         = 0.0;
+    m_MaxDiffWeights        = 0.0;
+    m_CheckHeader           = true;
+    m_CompareValuesAsString = false;
+    
+    m_SourceFilename = getSourceFilename();
+    m_ExportFilename = getExportFilename();
+    
+    // generate source file
+    writer = new BufferedWriter(new FileWriter(m_SourceFilename));
+    writer.write(m_Instances.toString());
+    writer.newLine();
+    writer.flush();
+    writer.close();
+  }
+
+  /** 
+   * Called by JUnit after each test method.
+   * 
+   * @throws Exception	if fails
+   */
+  protected void tearDown() throws Exception {
+    File 	file;
+
+    // delete temp. files
+    file = new File(m_SourceFilename);
+    if (file.exists())
+      file.delete();
+    file = new File(m_ExportFilename);
+    if (file.exists())
+      file.delete();
+    
+    m_SourceFilename = null;
+    m_ExportFilename = null;
+    
+    super.tearDown();
+  }
+  
+  /**
+   * test the batch saving/loading (via setFile(File)).
+   */
+  public void testBatch() {
+    Instances		data;
+
+    try {
+      // save
+      m_Saver.setInstances(m_Instances);
+      m_Saver.setFile(new File(m_ExportFilename));
+      m_Saver.writeBatch();
+      
+      // load
+      ((AbstractFileLoader) m_Loader).setFile(new File(m_ExportFilename));
+      data = m_Loader.getDataSet();
+      
+      // compare data
+      try {
+	if (m_Instances.classIndex() != data.classIndex())
+	  data.setClassIndex(m_Instances.classIndex());
+	compareDatasets(m_Instances, data);
+      }
+      catch (Exception e) {
+	fail("Incremental load failed (datasets differ): " + e.toString());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Batch save/load failed: " + e.toString());
+    }
+  }
+  
+  /**
+   * test the incremental loading (via setFile(File)).
+   */
+  public void testIncrementalLoader() {
+    Instance	temp;
+    Instances	data;
+    
+    if (!(getLoader() instanceof IncrementalConverter))
+      return;
+
+    try {
+      // save
+      m_Saver.setInstances(m_Instances);
+      m_Saver.setFile(new File(m_ExportFilename));
+      m_Saver.writeBatch();
+
+      // load
+      ((AbstractFileLoader) m_Loader).setFile(new File(m_ExportFilename));
+      data = new Instances(m_Loader.getStructure());
+      do {
+	temp = m_Loader.getNextInstance(data);
+	if (temp != null)
+	  data.add(temp);
+      }
+      while (temp != null);
+      
+      // compare data
+      try {
+	if (m_Instances.classIndex() != data.classIndex())
+	  data.setClassIndex(m_Instances.classIndex());
+	compareDatasets(m_Instances, data);
+      }
+      catch (Exception e) {
+	fail("Incremental load failed (datasets differ): " + e.toString());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Incremental load failed: " + e.toString());
+    }
+  }
+  
+  /**
+   * test the incremental save (via setFile(File)).
+   */
+  public void testIncrementalSaver() {
+    int 	i;
+    File	file;
+
+    if (!(getSaver() instanceof IncrementalConverter))
+      return;
+    
+    try {
+      // remove output file if it exists
+      file = new File(m_ExportFilename);
+      if (file.exists())
+	file.delete();
+      
+      // save
+      m_Saver.setFile(new File(m_ExportFilename));
+      m_Saver.setRetrieval(AbstractSaver.INCREMENTAL);
+      m_Saver.setStructure(new Instances(m_Instances, 0));
+      for (i = 0; i < m_Instances.numInstances(); i++) {
+	m_Saver.writeIncremental(m_Instances.instance(i));
+      }
+      m_Saver.writeIncremental(null);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Incremental save failed: " + e.toString());
+    }
+  }
+  
+  /**
+   * tests the commandline operation of the loader (via setFile(File)). 
+   * does nothing currently, but can be overridden by derived classes.
+   */
+  public void testLoaderCommandlineArgs() {
+  }
+  
+  /**
+   * tests the commandline operation of the saver.
+   */
+  public void testSaverCommandlineArgs() {
+    String[]	options;
+    
+    options = getCommandlineOptions(false);
+
+    try {
+      ((OptionHandler) m_Saver).setOptions(options);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("Command line test failed ('" + Utils.arrayToString(options) + "'): " + e.toString());
+    }
+  }
+  
+  /**
+   * tests whether a URL can be loaded (via setURL(URL)).
+   */
+  public void testURLSourcedLoader() {
+    Instances	data;
+    
+    if (!(getLoader() instanceof URLSourcedLoader))
+      return;
+
+    try {
+      // save
+      m_Saver.setInstances(m_Instances);
+      m_Saver.setFile(new File(m_ExportFilename));
+      m_Saver.writeBatch();
+
+      // load
+      ((URLSourcedLoader) m_Loader).setURL(new File(m_ExportFilename).toURL().toString());
+      data = m_Loader.getDataSet();
+      
+      // compare data
+      try {
+	if (m_Instances.classIndex() != data.classIndex())
+	  data.setClassIndex(m_Instances.classIndex());
+	compareDatasets(m_Instances, data);
+      }
+      catch (Exception e) {
+	fail("URL load failed (datasets differ): " + e.toString());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("URL load failed: " + e.toString());
+    }
+  }
+  
+  /**
+   * tests whether data can be loaded via setSource() with a file stream.
+   */
+  public void testLoaderWithStream() {
+    Instances	data;
+    
+    try {
+      // save
+      m_Saver.setInstances(m_Instances);
+      m_Saver.setFile(new File(m_ExportFilename));
+      m_Saver.writeBatch();
+
+      // load
+      m_Loader.setSource(new FileInputStream(new File(m_ExportFilename)));
+      data = m_Loader.getDataSet();
+      
+      // compare data
+      try {
+	if (m_Instances.classIndex() != data.classIndex())
+	  data.setClassIndex(m_Instances.classIndex());
+	compareDatasets(m_Instances, data);
+      }
+      catch (Exception e) {
+	fail("File stream loading failed (datasets differ): " + e.toString());
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("File stream loading failed: " + e.toString());
+    }
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/converters/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/AllTests.java	(revision 29)
@@ -0,0 +1,65 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import weka.test.WekaTestSuite;
+
+import java.util.Vector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for converter classes. Run from the command line with: <p/>
+ * java weka.core.converter.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  /**
+   * generates all the tests
+   * 
+   * @return		all the tests
+   */
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    
+    // all tests in converter package
+    Vector packages = new Vector();
+    packages.add("weka.core.converters");
+    suite.addTest(suite(AbstractConverterTest.class.getName(), packages));
+    
+    return suite;
+  }
+
+  /**
+   * for running the tests from commandline
+   * 
+   * @param args	the commandline arguments - ignored
+   */
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/ArffTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/ArffTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/ArffTest.java	(revision 29)
@@ -0,0 +1,81 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ArffLoader/ArffSaver. Run from the command line with:<p/>
+ * java weka.core.converters.ArffTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class ArffTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>ArffTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public ArffTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new ArffLoader();
+  }
+
+  /**
+   * returns the saver used in the tests
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new ArffSaver();
+  }
+
+  /**
+   * returns a test suite
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(ArffTest.class);
+  }
+
+  /**
+   * for running the test from commandline
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/C45Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/C45Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/C45Test.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import java.io.File;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests C45Loader/C45Saver. Run from the command line with:<p/>
+ * java weka.core.converters.C45Test
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class C45Test 
+  extends AbstractFileConverterTest {
+
+  /** the name of the data file */
+  protected String m_ExportFilenameData;
+  
+  /**
+   * Constructs the <code>C45Test</code>.
+   *
+   * @param name the name of the test class
+   */
+  public C45Test(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new C45Loader();
+  }
+
+  /**
+   * returns the saver used in the tests
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new C45Saver();
+  }
+  
+  /**
+   * returns the filename for the data file.
+   * 
+   * @return the filename
+   */
+  protected String getExportFilenameData() {
+    return m_ExportFilename.replaceAll("\\.names", ".data");
+  }
+  
+  /**
+   * returns the command line options, either for the loader or the saver
+   * 
+   * @param loader	if true the options for the loader will be returned,
+   * 			otherwise the ones for the saver
+   * @return		the command line options
+   */
+  protected String[] getCommandlineOptions(boolean loader) {
+    if (loader)
+      return super.getCommandlineOptions(loader);
+    else
+      return new String[]{"-i", m_SourceFilename, "-o", m_ExportFilename, "-c", "last"};
+  }
+  
+  /**
+   * Called by JUnit before each test method.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    File 	file;
+
+    super.setUp();
+    
+    m_ExportFilenameData = getExportFilenameData();
+
+    // delete temp. files
+    file = new File(m_ExportFilenameData);
+    if (file.exists())
+      file.delete();
+  }
+
+  /** 
+   * Called by JUnit after each test method
+   */
+  protected void tearDown() throws Exception {
+    File 	file;
+
+    // delete temp. files
+    file = new File(m_ExportFilenameData);
+    if (file.exists())
+      file.delete();
+    
+    m_ExportFilenameData = null;
+    
+    super.tearDown();
+  }
+  
+  /**
+   * ignored, since not supported!
+   */
+  public void testLoaderWithStream() {
+    System.out.println("testLoaderWithStream is ignored!");
+  }
+
+  /**
+   * returns a test suite
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(C45Test.class);
+  }
+
+  /**
+   * for running the test from commandline
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/CSVTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/CSVTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/CSVTest.java	(revision 29)
@@ -0,0 +1,94 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CSVLoader/CSVSaver. Run from the command line with:<p/>
+ * java weka.core.converters.CSVTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5419 $
+ */
+public class CSVTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>CSVTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public CSVTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests.
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new CSVLoader();
+  }
+
+  /**
+   * returns the saver used in the tests.
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new CSVSaver();
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default loader/saver to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_CheckHeader           = false;
+    m_CompareValuesAsString = true;
+  }
+
+  /**
+   * returns a test suite.
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(CSVTest.class);
+  }
+
+  /**
+   * for running the test from commandline.
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/JSONTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/JSONTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/JSONTest.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests JSONLoader/JSONSaver. Run from the command line with:<p/>
+ * java weka.core.converters.JSONTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5784 $
+ */
+public class JSONTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>JSONTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public JSONTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests.
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new JSONLoader();
+  }
+
+  /**
+   * returns the saver used in the tests.
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new JSONSaver();
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default loader/saver to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_CompareValuesAsString = true;
+  }
+
+  /**
+   * returns a test suite.
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(JSONTest.class);
+  }
+
+  /**
+   * for running the test from commandline.
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/LibSVMTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/LibSVMTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/LibSVMTest.java	(revision 29)
@@ -0,0 +1,93 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LibSVMLoader/LibSVMSaver. Run from the command line with:<p/>
+ * java weka.core.converters.LibSVMTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5419 $
+ */
+public class LibSVMTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>LibSVMTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public LibSVMTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests.
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new LibSVMLoader();
+  }
+
+  /**
+   * returns the saver used in the tests.
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new LibSVMSaver();
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default loader/saver to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_CheckHeader = false;
+  }
+
+  /**
+   * returns a test suite.
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(LibSVMTest.class);
+  }
+
+  /**
+   * for running the test from commandline.
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/MatlabTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/MatlabTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/MatlabTest.java	(revision 29)
@@ -0,0 +1,105 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import weka.core.TestInstances;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MatlabLoader/MatlabSaver. Run from the command line with:<p/>
+ * java weka.core.converters.MatlabTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5419 $
+ */
+public class MatlabTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>MatlabTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public MatlabTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests.
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new MatlabLoader();
+  }
+
+  /**
+   * returns the saver used in the tests.
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new MatlabSaver();
+  }
+
+  /**
+   * returns the test data generator.
+   * 
+   * @return 	the configured test data generator
+   */
+  protected TestInstances getTestInstances() {
+    return TestInstances.forCapabilities(new MatlabSaver().getCapabilities());
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default loader/saver to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_MaxDiffValues  = 1E-6;
+    m_MaxDiffWeights = 0.0;
+    m_CheckHeader    = false;
+  }
+
+  /**
+   * returns a test suite.
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(MatlabTest.class);
+  }
+
+  /**
+   * for running the test from commandline.
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/converters/SVMLightTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/SVMLightTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/SVMLightTest.java	(revision 29)
@@ -0,0 +1,117 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SVMLightLoader/SVMLightSaver. Run from the command line with:<p/>
+ * java weka.core.converters.SVMLightTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5410 $
+ */
+public class SVMLightTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>SVMLightTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public SVMLightTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests.
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new SVMLightLoader();
+  }
+
+  /**
+   * returns the saver used in the tests.
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new SVMLightSaver();
+  }
+  
+  /**
+   * Compare two datasets to see if they differ. Skips the equalHeaders
+   * method, since the SVMLight format doesn't have any notion of attribute
+   * names.
+   *
+   * @param data1 one set of instances
+   * @param data2 the other set of instances
+   * @throws Exception if the datasets differ
+   */
+  protected void compareDatasets(Instances data1, Instances data2)
+    throws Exception {
+    
+    if (!(data2.numInstances() == data1.numInstances())) {
+      throw new Exception("number of instances has changed");
+    }
+    for (int i = 0; i < data2.numInstances(); i++) {
+      Instance orig = data1.instance(i);
+      Instance copy = data2.instance(i);
+      for (int j = 0; j < orig.numAttributes(); j++) {
+        if (orig.isMissing(j)) {
+          if (!copy.isMissing(j)) {
+            throw new Exception("instances have changed");
+          }
+        } else if (orig.value(j) != copy.value(j)) {
+          throw new Exception("instances have changed");
+        }
+        if (orig.weight() != copy.weight()) {
+          throw new Exception("instance weights have changed");
+        }	  
+      }
+    }
+  }
+
+  /**
+   * returns a test suite.
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(SVMLightTest.class);
+  }
+
+  /**
+   * for running the test from commandline.
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/SerializedInstancesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/SerializedInstancesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/SerializedInstancesTest.java	(revision 29)
@@ -0,0 +1,81 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SerializedInstancesLoader/SerializedInstancesSaver. Run from the command line with:<p/>
+ * java weka.core.converters.SerializedInstancesTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class SerializedInstancesTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>SerializedInstancesTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public SerializedInstancesTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new SerializedInstancesLoader();
+  }
+
+  /**
+   * returns the saver used in the tests
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new SerializedInstancesSaver();
+  }
+
+  /**
+   * returns a test suite
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(SerializedInstancesTest.class);
+  }
+
+  /**
+   * for running the test from commandline
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/converters/XRFFTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/converters/XRFFTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/converters/XRFFTest.java	(revision 29)
@@ -0,0 +1,81 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.converters;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests XRFFLoader/XRFFSaver. Run from the command line with:<p/>
+ * java weka.core.converters.XRFFTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class XRFFTest 
+  extends AbstractFileConverterTest {
+
+  /**
+   * Constructs the <code>XRFFTest</code>.
+   *
+   * @param name the name of the test class
+   */
+  public XRFFTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * returns the loader used in the tests
+   * 
+   * @return the configured loader
+   */
+  public AbstractLoader getLoader() {
+    return new XRFFLoader();
+  }
+
+  /**
+   * returns the saver used in the tests
+   * 
+   * @return the configured saver
+   */
+  public AbstractSaver getSaver() {
+    return new XRFFSaver();
+  }
+
+  /**
+   * returns a test suite
+   * 
+   * @return the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(XRFFTest.class);
+  }
+
+  /**
+   * for running the test from commandline
+   * 
+   * @param args the commandline arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
+
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/AbstractNearestNeighbourSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/AbstractNearestNeighbourSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/AbstractNearestNeighbourSearchTest.java	(revision 29)
@@ -0,0 +1,400 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core.neighboursearch;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.FastVector;
+import weka.core.Instances;
+import weka.core.SerializationHelper;
+import weka.test.Regression;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for neighboursearch algorithms.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public abstract class AbstractNearestNeighbourSearchTest
+  extends TestCase {
+  
+  /** The NearestNeighbourSearch to be tested */
+  protected NearestNeighbourSearch m_NearestNeighbourSearch;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+
+  /** the dataset used for testing */
+  protected Instances m_Instances;
+
+  /** the number of neighbors to test */
+  protected int m_NumNeighbors;
+
+  /** for selecting random instances */
+  protected Random m_Random;
+  
+  /**
+   * Constructs the <code>AbstractNearestNeighbourSearchTest</code>. 
+   * Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractNearestNeighbourSearchTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * Returns the Instances to be used in testing.
+   * 
+   * @return		the test instances
+   * @throws Exception	if loading fails
+   */
+  protected Instances getInstances() throws Exception {
+    Instances	result;
+    
+    result = new Instances(
+		new BufferedReader(
+		    new InputStreamReader(
+			ClassLoader.getSystemResourceAsStream(
+			    "weka/core/neighboursearch/anneal.arff"))));
+    result.setClassIndex(result.numAttributes() - 1);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the option handling.
+   * Sets the NearestNeighbourSearch returned from the 
+   * getNearestNeighbourSearch() method
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getNearestNeighbourSearch()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    result.setOptionHandler(getNearestNeighbourSearch());
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the NearestNeighbourSearch returned from the 
+   * getNearestNeighbourSearch() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getNearestNeighbourSearch()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getNearestNeighbourSearch());
+    result.setIgnoredProperties(result.getIgnoredProperties() + ",instances");
+    result.setSilent(true);
+    
+    return result;
+  }
+
+  /**
+   * Used to create an instance of a specific NearestNeighbourSearch.
+   *
+   * @return a suitably configured <code>NearestNeighbourSearch</code> value
+   */
+  public abstract NearestNeighbourSearch getNearestNeighbourSearch();
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default NearestNeighbourSearch to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_NearestNeighbourSearch = getNearestNeighbourSearch();
+    m_OptionTester           = getOptionTester();
+    m_GOETester              = getGOETester();
+    m_Instances	             = getInstances();
+    m_NumNeighbors           = 3;
+    m_Random                 = new Random(1);
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_NearestNeighbourSearch = null;
+    m_OptionTester           = null;
+    m_GOETester              = null;
+    m_Instances	             = null;
+    m_NumNeighbors           = 0;
+    m_Random                 = null;
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean     result;
+
+    result = !SerializationHelper.needsUID(m_NearestNeighbourSearch.getClass());
+
+    if (!result)
+      fail("Doesn't declare serialVersionUID!");
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (!m_OptionTester.checkListOptions())
+      fail("Options cannot be listed via listOptions.");
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("setOptions method failed.");
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (!m_OptionTester.checkDefaultOptions())
+      fail("Default options were not processed correctly.");
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (!m_OptionTester.checkRemainingOptions())
+      fail("There were 'left-over' options.");
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (!m_OptionTester.checkCanonicalUserOptions())
+      fail("setOptions method failed");
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("Resetting of options failed");
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+
+  /**
+   * tests whether the number of instances returned by the algorithms is the
+   * same as was requested
+   */
+  public void testNumberOfNeighbors() {
+    int		i;
+    int 	instIndex;
+    Instances	neighbors;
+    
+    try {
+      m_NearestNeighbourSearch.setInstances(m_Instances);
+    }
+    catch (Exception e) {
+      fail("Failed setting the instances: " + e);
+    }
+    
+    for (i = 1; i <= m_NumNeighbors; i++) {
+      instIndex = m_Random.nextInt(m_Instances.numInstances());
+      try {
+	neighbors = m_NearestNeighbourSearch.kNearestNeighbours(
+	    		m_Instances.instance(instIndex), i);
+	assertEquals(
+	    "Returned different number of neighbors than requested", 
+	    i, neighbors.numInstances());
+      }
+      catch (Exception e) {
+	fail(
+	    "Failed for " + i + " neighbors on instance " + (instIndex+1) 
+	    + ": " + e);
+      }
+    }
+  }
+  
+  /**
+   * tests whether the tokenizer correctly initializes in the
+   * buildTokenizer method
+   */
+  public void testBuildInitialization() {
+    String[][][]	results;
+    Instances		inst;
+    int			i;
+    int			n;
+    int			m;
+    
+    results = new String[2][m_Instances.numInstances()][m_NumNeighbors];
+    
+    // two runs of determining neighbors
+    for (i = 0; i < 2; i++) {
+      try {
+	m_NearestNeighbourSearch.setInstances(m_Instances);
+	
+	for (n = 0; n < m_Instances.numInstances(); n++) {
+	  for (m = 1; m <= m_NumNeighbors; m++) {
+	    inst = m_NearestNeighbourSearch.kNearestNeighbours(
+			m_Instances.instance(n), m);
+	    results[i][n][m - 1] = inst.toString();
+	  }
+	}
+      }
+      catch (Exception e) {
+	fail("Build " + (i + 1) + " failed: " + e);
+      }
+    }
+    
+    // compare the results
+    for (n = 0; n < m_Instances.numInstances(); n++) {
+      for (m = 1; m <= m_NumNeighbors; m++) {
+	if (!results[0][n][m - 1].equals(results[1][n][m - 1]))
+	  fail("Results differ: instance #" + (n+1) + " with " + m + " neighbors");
+      }
+    }
+  }
+
+  /**
+   * Runs the NearestNeighbourSearch with the given data and returns the 
+   * generated results.
+   *
+   * @param data	the data to use
+   * @return 		a <code>FastVector</code> containing the results.
+   * @throws Exception	if search fails
+   */
+  protected FastVector useNearestNeighbourSearch(Instances data) throws Exception {
+    FastVector		result;
+    int			i;
+    int			n;
+    int			m;
+    Instances		inst;
+    StringBuffer	item;
+    
+    m_NearestNeighbourSearch.setInstances(m_Instances);
+    
+    result = new FastVector();
+    for (i = 0; i < m_Instances.numInstances(); i++) {
+      item = new StringBuffer((i+1) + ". " + m_Instances.instance(i).toString() + ": ");
+      for (n = 1; n <= m_NumNeighbors; n++) {
+	inst = m_NearestNeighbourSearch.kNearestNeighbours(
+	    		m_Instances.instance(i), n);
+	item.append(" neighbors=" + n + ": ");
+	for (m = 0; m < inst.numInstances(); m++) {
+	  if (m > 0)
+	    item.append("; ");
+	  item.append("neighbor_" + (m+1) + "=" + inst.instance(m));
+	}
+      }
+      result.addElement(item.toString());
+    }
+    
+    return result;
+      
+  }
+
+  /**
+   * Returns a string containing all the results.
+   *
+   * @param tokens 	a <code>FastVector</code> containing the results
+   * @return 		a <code>String</code> representing the vector of results.
+   */
+  protected String resultsToString(FastVector results) {
+    StringBuffer sb = new StringBuffer();
+    
+    sb.append(results.size()).append(" results for " + m_Instances.relationName() + ":\n");
+    for (int i = 0; i < results.size(); i++)
+      sb.append(results.elementAt(i)).append('\n');
+    
+    return sb.toString();
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() {
+    Regression	reg;
+    FastVector	regressionResult;
+    
+    reg = new Regression(this.getClass());
+    
+    try {
+      regressionResult = useNearestNeighbourSearch(m_Instances);
+      reg.println(resultsToString(regressionResult));
+    }
+    catch (Exception e) {
+      fail("Regression test failed: " + e);
+    }
+    
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/AllTests.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core.neighboursearch;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all neighboursearch. Run from the command line with: <p/>
+ * java weka.core.neighboursearch.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+
+    suite.addTest(suite("weka.core.neighboursearch.NearestNeighbourSearch"));
+
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/BallTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/BallTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/BallTreeTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BallTree. Run from the command line with: <p/>
+ * java weka.core.neighboursearch.BallTreeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class BallTreeTest
+  extends AbstractNearestNeighbourSearchTest {
+
+  public BallTreeTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default BallTree */
+  public NearestNeighbourSearch getNearestNeighbourSearch() {
+    return new BallTree();
+  }
+  
+  public static Test suite() {
+    return new TestSuite(BallTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/CoverTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/CoverTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/CoverTreeTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests CoverTree. Run from the command line with: <p/>
+ * java weka.core.neighboursearch.CoverTreeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class CoverTreeTest
+  extends AbstractNearestNeighbourSearchTest {
+
+  public CoverTreeTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default CoverTree */
+  public NearestNeighbourSearch getNearestNeighbourSearch() {
+    return new CoverTree();
+  }
+  
+  public static Test suite() {
+    return new TestSuite(CoverTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/KDTreeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/KDTreeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/KDTreeTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests KDTree. Run from the command line with: <p/>
+ * java weka.core.neighboursearch.KDTreeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class KDTreeTest
+  extends AbstractNearestNeighbourSearchTest {
+
+  public KDTreeTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default KDTree */
+  public NearestNeighbourSearch getNearestNeighbourSearch() {
+    return new KDTree();
+  }
+  
+  public static Test suite() {
+    return new TestSuite(KDTreeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/LinearNNSearchTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/LinearNNSearchTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/LinearNNSearchTest.java	(revision 29)
@@ -0,0 +1,52 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.neighboursearch;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LinearNNSearch. Run from the command line with: <p/>
+ * java weka.core.neighboursearch.LinearNNSearchTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class LinearNNSearchTest
+  extends AbstractNearestNeighbourSearchTest {
+
+  public LinearNNSearchTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default LinearNNSearch */
+  public NearestNeighbourSearch getNearestNeighbourSearch() {
+    return new LinearNNSearch();
+  }
+  
+  public static Test suite() {
+    return new TestSuite(LinearNNSearchTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/neighboursearch/anneal.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/neighboursearch/anneal.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/neighboursearch/anneal.arff	(revision 29)
@@ -0,0 +1,1058 @@
+% 1. Title of Database: Annealing Data
+% 
+% 2. Source Information: donated by David Sterling and Wray Buntine.
+% 
+% 3. Past Usage: unknown
+% 
+% 4. Relevant Information:
+%    -- Explanation: I suspect this was left by Ross Quinlan in 1987 at the
+%       4th Machine Learning Workshop.  I'd have to check with Jeff Schlimmer
+%       to double check this.
+% 
+% 5. Number of Instances: 798
+% 
+% 6. Number of Attributes: 38
+%    -- 6 continuously-valued
+%    -- 3 integer-valued
+%    -- 29 nominal-valued
+% 
+% 7. Attribute Information:
+%     1. family:          --,GB,GK,GS,TN,ZA,ZF,ZH,ZM,ZS
+%     2. product-type:    C, H, G
+%     3. steel:           -,R,A,U,K,M,S,W,V
+%     4. carbon:          continuous
+%     5. hardness:        continuous
+%     6. temper_rolling:  -,T
+%     7. condition:       -,S,A,X
+%     8. formability:     -,1,2,3,4,5
+%     9. strength:        continuous
+%    10. non-ageing:      -,N
+%    11. surface-finish:  P,M,-
+%    12. surface-quality: -,D,E,F,G
+%    13. enamelability:   -,1,2,3,4,5
+%    14. bc:              Y,-
+%    15. bf:              Y,-
+%    16. bt:              Y,-
+%    17. bw/me:           B,M,-
+%    18. bl:              Y,-
+%    19. m:               Y,-
+%    20. chrom:           C,-
+%    21. phos:            P,-
+%    22. cbond:           Y,-
+%    23. marvi:           Y,-
+%    24. exptl:           Y,-
+%    25. ferro:           Y,-
+%    26. corr:            Y,-
+%    27. blue/bright/varn/clean:          B,R,V,C,-
+%    28. lustre:          Y,-
+%    29. jurofm:          Y,-
+%    30. s:               Y,-
+%    31. p:               Y,-
+%    32. shape:           COIL, SHEET
+%    33. thick:           continuous
+%    34. width:           continuous
+%    35. len:             continuous
+%    36. oil:             -,Y,N
+%    37. bore:            0000,0500,0600,0760
+%    38. packing: -,1,2,3
+%    classes:        1,2,3,4,5,U
+%  
+%    -- The '-' values are actually 'not_applicable' values rather than
+%       'missing_values' (and so can be treated as legal discrete
+%       values rather than as showing the absence of a discrete value).
+% 
+% 8. Missing Attribute Values: Signified with "?"
+%    Attribute:  Number of instances missing its value:
+%    1           0
+%    2           0
+%    3           70
+%    4           0
+%    5           0
+%    6           675
+%    7           271
+%    8           283
+%    9           0
+%   10           703
+%   11           790
+%   12           217
+%   13           785
+%   14           797
+%   15           680
+%   16           736
+%   17           609
+%   18           662
+%   19           798
+%   20           775
+%   21           791
+%   22           730
+%   23           798
+%   24           796
+%   25           772
+%   26           798
+%   27           793
+%   28           753
+%   29           798
+%   30           798
+%   31           798
+%   32           0
+%   33           0
+%   34           0
+%   35           0
+%   36           740
+%   37           0
+%   38           789
+%   39           0
+% 
+% 9. Distribution of Classes
+%      Class Name:   Number of Instances:
+%      1               8
+%      2              88
+%      3             608
+%      4               0
+%      5              60
+%      U              34
+%                    ---
+%                    798
+% 
+@relation anneal
+@attribute 'family' {'?','GB','GK','GS','TN','ZA','ZF','ZH','ZM','ZS'}
+@attribute 'product-type' {'C','H','G'}
+@attribute 'steel' {'?','R','A','U','K','M','S','W','V'}
+@attribute 'carbon' real
+@attribute 'hardness' real
+@attribute 'temper_rolling' {'?','T'}
+@attribute 'condition' {'?','S','A','X'}
+@attribute 'formability' {'?','1','2','3','4','5'}
+@attribute 'strength' real
+@attribute 'non-ageing' {'?','N'}
+@attribute 'surface-finish' {'?','P','M'}
+@attribute 'surface-quality' {'?','D','E','F','G'}
+@attribute 'enamelability' {'?','1','2','3','4','5'}
+@attribute 'bc' {'?','Y'}
+@attribute 'bf' {'?','Y'}
+@attribute 'bt' {'?','Y'}
+@attribute 'bw/me' {'?','B','M'}
+@attribute 'bl' {'?','Y'}
+@attribute 'm' {'?','Y'}
+@attribute 'chrom' {'?','C'}
+@attribute 'phos' {'?','P'}
+@attribute 'cbond' {'?','Y'}
+@attribute 'marvi' {'?','Y'}
+@attribute 'exptl' {'?','Y'}
+@attribute 'ferro' {'?','Y'}
+@attribute 'corr' {'?','Y'}
+@attribute 'blue/bright/varn/clean' {'?','B','R','V','C'}
+@attribute 'lustre' {'?','Y'}
+@attribute 'jurofm' {'?','Y'}
+@attribute 's' {'?','Y'}
+@attribute 'p' {'?','Y'}
+@attribute 'shape' {'COIL','SHEET'}
+@attribute 'thick' real
+@attribute 'width' real
+@attribute 'len' real
+@attribute 'oil' {'?','Y','N'}
+@attribute 'bore' {'0','500','600','760'}
+@attribute 'packing' {'?','1','2','3'}
+@attribute 'class' {'1','2','3','4','5','U'}
+@data
+'?','C','A',8,0,'?','S','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,762,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.801,385.1,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.801,255,269,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.3,152,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,4880,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,150,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1220,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',4,1320,762,'?','0','?','3'
+'?','C','A',10,0,'?','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.201,600,0,'?','0','?','U'
+'?','C','A',0,80,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4170,'Y','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320.1,762,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.501,1200.1,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,761,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,610,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.001,50,0,'?','0','?','U'
+'?','C','S',70,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','A',3,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,610,0,'?','0','?','1'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,1320,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.8,356,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,4170,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,900,0,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','500','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','5'
+'?','C','?',0,45,'?','S','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.4,1320,4170,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,762,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,450,0,'?','0','?','U'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.75,1320,4880,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1220,0,'?','0','?','5'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.399,1320,0,'?','0','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.651,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,374.9,4880,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,50,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'Y','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.799,20,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,301,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,610,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,4880,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,1274.9,0,'?','500','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'N','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,761,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.599,150,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,374.9,4880,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,610,762,'Y','0','?','3'
+'?','C','A',0,0,'?','?','?',500,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.6,1220,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.301,610,0,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,762,'?','0','?','U'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,500,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,515,610,'Y','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,335,611,'?','0','?','5'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,762,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,20,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,250,0,'?','0','?','5'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.801,610,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.5,610,0,'?','600','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,762,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,1300,0,'?','0','?','3'
+'?','C','A',4,0,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,1130,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609,612,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,609.9,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','3',0,'N','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,1320,0,'?','0','?','3'
+'ZS','C','A',0,70,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2,1250,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,762,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,1,'?','0','?','2'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'ZS','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4880,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,640,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','3','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1090,0,'?','0','?','2'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,610,0,'?','0','?','5'
+'?','C','A',10,0,'?','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,1320,0,'?','0','?','U'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320.1,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.5,640,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.599,609.9,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,610,761,'?','0','?','5'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,4880,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,612,'?','0','?','2'
+'?','C','R',0,0,'?','?','?',500,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,761,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,25,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','SHEET',0.5,1220,4880,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,762,'?','0','?','3'
+'?','C','?',0,0,'T','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1220,761,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'Y','0','?','3'
+'?','C','R',0,0,'?','?','?',500,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1100,762,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1090,0,'?','0','?','2'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,610,0,'Y','0','?','U'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.599,520,4880,'?','0','?','3'
+'ZS','C','R',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.25,20,0,'?','0','?','3'
+'?','C','S',0,0,'?','?','?',400,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','V','?','?','?','?','COIL',0.8,75,0,'?','0','?','1'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','A',4,0,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,1130,0,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.451,1320,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',1.6,1220,0,'?','0','?','5'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.599,1275,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4880,'?','0','?','5'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.599,610,0,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,4170,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,1220,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.09,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,762,'?','0','?','2'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,1525,0,'?','0','?','2'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.1,900,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,1525,612,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.5,610,0,'?','600','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','5'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,610,4880,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,50,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,760,'N','0','?','3'
+'?','C','R',0,0,'?','A','3',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.4,58,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1250,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,830,881,'?','0','?','2'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.9,1320,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2,1525,0,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,520,762,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.8,710.1,0,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','?','?',500,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,1000,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'N','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.201,1000,0,'?','600','?','U'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.6,1220,4880,'?','0','?','5'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,75,0,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1220,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,610,1000,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.9,966.1,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.001,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,375,612,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','5'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,1320,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,1220,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,50,0,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,335,4170,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,1250,4880,'N','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,1274.9,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,1,'?','0','?','2'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','U'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',1.6,50,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,1320,0,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1220,762,'?','0','?','3'
+'ZS','C','R',0,0,'?','?','?',300,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','COIL',0.8,915.1,0,'?','0','?','1'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,1000,0,'?','600','?','U'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.5,640,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,335,612,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,520,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','3','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,610,762,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,1250,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,762,'N','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.901,966.1,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,595,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1220,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,609.9,4880,'?','0','?','3'
+'?','C','S',70,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,1000,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,60,0,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,900,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','3','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',2.3,900,0,'?','500','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1300,762,'?','0','?','2'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,1320,4880,'Y','0','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,762,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.3,1090,0,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,520,4880,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.9,610,1220,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,762,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.399,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,761,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'N','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1220,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.599,610,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,400,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,612,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.5,609.9,612,'?','0','?','5'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.301,1320,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,640,0,'?','500','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.6,249.9,0,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'N','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.9,1135,0,'?','600','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,762,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.901,966,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,75,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,1250,762,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,609,0,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.6,20,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,900,0,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,762,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.5,250,0,'?','0','?','5'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,50,0,'?','0','?','5'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,1274.9,762,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.801,255.1,270,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1500,4170,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1220,4880,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1220,762,'?','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.321,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.601,1220,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,150,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1220,762,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,250,0,'?','0','?','5'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,1220,0,'Y','0','?','U'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,762,'?','0','?','2'
+'?','C','S',0,0,'?','?','?',400,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','1'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,761,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,4880,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,150,762,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.299,1050,1220,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,375,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,1220,0,'?','0','?','3'
+'?','C','S',0,0,'?','?','?',400,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,75,0,'?','0','?','1'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,762,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,610,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','2'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,762,'?','0','?','3'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.8,50,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,762,'?','0','?','2'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.5,609.9,3000,'?','0','?','5'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.601,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.301,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,612,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1500,4170,'?','0','?','2'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.201,50,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.75,610,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,374.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','?','?',500,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','2'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.09,900,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,150,762,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',2.2,900,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',4,0,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,25,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','3','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,4880,'?','0','?','2'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,762,'N','0','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,761,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,610,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,900,0,'?','600','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,300.1,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,300.1,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,610,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,762,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,762,'?','0','?','U'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.601,609.9,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,300,'?','0','?','3'
+'ZS','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1220,0,'?','0','3','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,762,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1220,4880,'?','0','?','U'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1220,761,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','S',0,0,'?','?','?',400,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,75,0,'?','0','?','1'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,1320,0,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1250,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320.1,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.201,385.1,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,500,4880,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,500,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4170,'?','0','?','2'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','5'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1300,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,50,0,'?','0','?','3'
+'?','C','A',0,80,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.5,29,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,595,762,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,1200,611,'?','0','?','U'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.801,355,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,610,0,'?','500','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,609.9,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.5,900,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,1000,0,'?','600','?','3'
+'ZS','C','R',0,0,'?','?','?',300,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','COIL',0.8,915,0,'?','0','?','1'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.201,610,0,'?','600','?','U'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,1320,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','5'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,0,'?','0','?','U'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','500','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.5,609.9,612,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1500,612,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','3',0,'N','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.8,900.1,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,385.1,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1300,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,375,612,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.4,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,610,0,'?','0','3','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,1220,0,'Y','0','?','U'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,610,0,'?','0','?','2'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'N','0','?','3'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,609.9,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.09,610,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,612,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,900,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,1500,612,'?','0','?','2'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,20,0,'?','0','?','2'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,900,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,1220,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,612,'?','0','?','2'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609.9,762,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.9,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,4880,'?','0','?','2'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,335,612,'?','0','?','2'
+'?','C','S',0,0,'?','?','?',700,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,609.9,0,'?','0','?','1'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.9,1050,1220,'?','0','?','2'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,75,0,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.3,1090,0,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,20,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','B','?','?','?','?','SHEET',2,609.9,301,'?','0','?','2'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,610,0,'Y','0','?','U'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.799,249.9,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'Y','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.599,1320,761,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.09,610,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,900,0,'?','0','?','3'
+'?','C','A',0,0,'?','?','?',500,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,762,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,4880,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.301,20,0,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,335,612,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,762,'Y','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1220,761,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.09,900,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.801,255.1,269,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,600,150,'?','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,762,'N','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,831.9,881,'?','0','?','2'
+'ZS','C','R',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.321,610,0,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1275,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1220,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,595,762,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.2,900,0,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.201,50,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,335,3000,'?','0','?','2'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.4,1310,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,610,0,'?','600','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','SHEET',0.5,610,762,'?','0','?','5'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',2.8,610,0,'?','600','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,1300,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1500,612,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.201,152,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,1320,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1320,4880,'N','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,762,'?','0','?','3'
+'?','C','A',0,80,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,612,'Y','0','?','U'
+'?','C','A',0,0,'?','?','?',500,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1525,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,4170,'?','0','?','2'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1300,762,'?','0','?','2'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.6,610,300,'?','0','?','5'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,762,'Y','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,610,0,'?','600','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.451,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1500,4170,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.201,1320,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,300.1,762,'?','0','?','3'
+'?','C','A',4,0,'T','?','?',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,25,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.5,1000,0,'?','600','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609.9,4880,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.3,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,609.9,612,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4170,'?','0','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,610,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,385.1,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,1320,0,'?','600','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.2,640,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,614,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,4880,'?','0','?','3'
+'TN','C','A',0,0,'?','S','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,609.9,0,'?','0','?','5'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,610,0,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.5,900,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,762,'N','0','?','3'
+'ZS','C','R',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,609.9,0,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'Y','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.501,1275,4880,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1220,762,'?','0','?','U'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,759,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,3000,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,200.1,4880,'?','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.6,335,3000,'?','0','?','5'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1250,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,612,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,610,4880,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,609,0,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,1200,150,'?','0','?','U'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,609.9,0,'?','0','?','U'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,4170,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1300,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,1320,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,610,4880,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.6,609.9,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,519.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,75,0,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,900,0,'?','500','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,4880,'Y','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.8,356.1,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1300,4880,'?','0','?','2'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.201,190,0,'?','0','?','U'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.501,600.1,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,610,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',1.2,609.9,0,'?','0','?','5'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.799,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,150,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.5,640,0,'?','0','?','3'
+'?','C','A',0,0,'?','?','?',500,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,500,4120,'Y','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','0','?','3'
+'?','C','?',0,45,'?','S','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,610,4170,'?','0','?','2'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1,610,0,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,640,0,'?','500','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,612,'?','0','?','2'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,600.1,0,'?','0','?','5'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,4880,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1220,612,'?','0','?','5'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'N','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609.9,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,1320,0,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1275,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,4170,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,20,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.5,640,0,'?','0','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,762,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','600','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.301,610,0,'N','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609.9,1,'?','0','?','5'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,612,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,1250,0,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','B','?','?','?','?','SHEET',2,300.1,301,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',1.2,599.9,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.8,356.1,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,610,4880,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.4,600,611,'?','0','?','U'
+'?','C','R',0,0,'?','A','3',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.699,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.801,356,762,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,609,612,'?','0','?','3'
+'?','C','?',0,0,'T','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,300.1,301,'?','0','?','2'
+'?','C','A',0,0,'?','S','3',0,'N','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,1320,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1320,301,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.001,609.9,0,'?','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,519,762,'?','0','?','3'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3,65.1,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,4880,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1320,761,'?','0','?','2'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,1220,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1320,762,'N','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.5,1220,4880,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.201,0,0,'?','0','?','3'
+'ZS','C','R',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1250,0,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,640,0,'?','600','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','5'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,762,'?','0','?','3'
+'TN','C','A',0,0,'?','S','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,609.9,0,'?','0','?','5'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,20,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','3','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'Y','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','U'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.699,609.9,0,'?','0','?','3'
+'?','C','A',0,80,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.5,28,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4170,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,761,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,610,150,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','SHEET',3.2,610,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,761,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',4,1000,0,'?','600','?','U'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,609.9,762,'?','0','?','2'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,762,'Y','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,50,0,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,1274.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,150,762,'?','0','?','2'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609.9,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,759,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,830,881,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,60,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.801,1320,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.599,610,0,'?','0','?','2'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.3,1220,4170,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,150,4880,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.001,1320,0,'?','500','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'N','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'Y','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.299,1050,1220,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,300.1,4880,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',0.6,1220,762,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,761,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,612,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,609.9,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'N','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.2,610,0,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,1275,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,1320,611,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,1,'?','0','?','2'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.8,255,270,'?','0','?','3'
+'?','C','A',8,0,'?','S','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,610,300,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'N','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.9,610,3000,'?','0','?','2'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.801,610,0,'?','600','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,900,0,'?','600','?','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1320,4880,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,610,4880,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,4880,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,374.9,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,609.9,0,'?','0','?','5'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,1250,4880,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'N','0','?','3'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.799,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,519.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,1250,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,1320,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',1,20,0,'?','0','?','U'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','5'
+'?','C','M',0,0,'?','?','?',600,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,1320,4880,'?','0','?','2'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,760,'N','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,640,0,'?','600','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,1320,0,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.201,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,4880,'Y','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,609.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','2'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1320,761,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','2'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,1220,761,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1220,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1525,4170,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','2','3'
+'?','C','R',6,0,'T','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.5,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.3,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,610,4880,'Y','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,609.9,0,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.3,610,0,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.801,1320,0,'?','600','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,610,762,'?','0','?','5'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.451,610,762,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,610,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','COIL',0.6,609.9,0,'?','0','?','5'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,762,'?','0','?','2'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,335,612,'?','0','?','5'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,762,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1320,150,'?','0','?','3'
+'?','C','A',0,0,'?','?','?',500,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.9,1320,0,'?','0','?','3'
+'?','C','A',0,70,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','2'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.799,609,0,'?','0','?','3'
+'ZS','C','R',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,150,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1220,762,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.321,1220,0,'?','0','?','U'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1274.9,4880,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','Y','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,606.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,762,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,20,0,'?','0','?','2'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1,900,0,'?','0','?','3'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','B','?','?','?','?','SHEET',2,609.9,4880,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,150,612,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,1220,4880,'?','0','?','U'
+'?','C','S',70,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,762,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.201,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1525,612,'?','0','?','2'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,150,4880,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,335,1,'?','0','?','5'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,1300,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,1320,762,'Y','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,609,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,1220,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.599,609.9,0,'?','0','?','2'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,595,4880,'?','0','?','3'
+'?','C','R',0,0,'?','A','3',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1320,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,75,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,150,612,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,150,762,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','P','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1220,762,'?','0','?','5'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.801,1000,0,'?','600','?','3'
+'?','C','R',0,0,'?','A','3',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,1320,0,'?','0','?','3'
+'ZS','C','A',0,70,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,610,0,'?','0','?','3'
+'?','C','S',70,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,610,4880,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,4170,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,609.9,612,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,610,4170,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1220,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,1300,4170,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','5'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.1,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2,1500,4170,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','3','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.5,610,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,4170,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,335,611,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','COIL',0.699,610,0,'N','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,374.9,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1320,4880,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,610,762,'N','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,375,612,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,1320,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.201,609.9,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.3,900,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,150,762,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','2'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,250,0,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1300,4880,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.9,966,0,'?','0','?','3'
+'?','C','A',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',1.001,50,0,'Y','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1300,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,610,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1500,4170,'?','0','?','2'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.699,609.9,0,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,610,762,'?','0','?','U'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,610,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,50,0,'?','0','?','5'
+'?','C','?',0,0,'?','?','?',500,'?','P','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','B','?','?','?','?','SHEET',1.6,300.1,301,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.6,610,4170,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,1250,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,610,762,'?','0','?','3'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.8,50,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.8,609.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,831.9,881,'?','0','?','2'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,4170,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,385.1,0,'?','0','?','3'
+'?','C','?',0,50,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.601,1320,0,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.301,1320,762,'Y','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,150,3000,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,4880,'?','0','?','3'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,609.9,0,'?','0','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,335,3000,'?','0','?','5'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.5,335,3000,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,4880,'Y','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,301,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,60,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,1320,0,'?','0','?','3'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,1220,762,'?','0','?','5'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.6,1320,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','1','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,762,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,4880,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,50,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',0.301,1220,4170,'?','0','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,640,0,'?','600','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,375,612,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,1320,0,'?','0','?','3'
+'?','C','S',70,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',2.5,610,762,'?','0','?','3'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,610,0,'Y','0','?','U'
+'?','C','K',65,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,900,0,'?','600','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.2,900,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,1220,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.001,610,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.3,610,4880,'Y','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,610,762,'?','0','?','3'
+'?','C','A',0,0,'?','S','3',0,'N','?','F','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1,610,4880,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.999,610,762,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,0,762,'?','0','?','3'
+'?','C','R',0,0,'?','?','?',500,'?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.4,1320,4880,'N','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,762,'?','0','?','3'
+'?','C','?',0,0,'?','A','2',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,610,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2,1250,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1320,761,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','Y','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,1320,0,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','3',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,609.9,4170,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.601,609,4880,'?','0','?','3'
+'?','C','M',0,0,'?','?','?',600,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.9,1050,1220,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1525,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','COIL',0.7,599.9,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,609.9,0,'?','0','?','2'
+'?','C','M',0,0,'?','?','?',350,'?','?','G','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.6,610,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','SHEET',1.2,375,612,'?','0','?','3'
+'?','C','A',8,0,'?','S','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,609.9,0,'?','0','?','3'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,1300,4880,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.1,610,0,'?','0','?','3'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,609.9,762,'?','0','?','2'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,1320,0,'?','0','?','3'
+'ZS','C','A',0,85,'T','?','?',0,'?','?','E','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.8,610,4880,'?','0','?','U'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.6,520,0,'?','0','?','3'
+'?','C','A',0,80,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1320,4170,'Y','0','?','U'
+'?','C','A',0,60,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.801,50,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','E','?','?','?','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',3.2,1320,762,'?','0','?','3'
+'ZS','C','A',0,0,'?','S','5',0,'N','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1,609.9,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.699,610,0,'?','0','?','3'
+'?','C','W',0,0,'?','?','?',310,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,762,'?','0','?','3'
+'?','C','?',0,70,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','COIL',0.6,610,0,'?','600','?','3'
+'?','C','K',55,0,'?','?','?',0,'?','?','?','?','?','?','?','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',2.2,640,0,'?','0','?','3'
+'?','C','A',0,45,'?','S','?',0,'?','?','D','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.7,1320,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','F','?','?','Y','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.7,1220,762,'?','0','?','3'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,4880,'?','0','?','2'
+'?','C','?',0,0,'?','S','1',0,'?','?','G','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','Y','?','?','?','COIL',0.24,20,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.3,1320,0,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.699,375,612,'?','0','?','3'
+'ZS','C','A',0,50,'T','?','?',0,'?','?','E','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',0.451,1250,762,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',1.5,900,0,'?','0','?','3'
+'?','C','A',0,0,'?','S','2',0,'?','?','G','?','?','Y','?','B','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,1300,4880,'?','0','?','3'
+'?','C','R',0,0,'?','S','2',0,'?','?','E','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,1320,0,'?','0','?','3'
+'?','C','K',45,0,'?','?','?',0,'?','?','?','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',3.2,640,0,'?','500','?','3'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','B','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,609.9,3000,'?','0','?','5'
+'TN','C','?',0,0,'?','A','1',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','SHEET',1.6,609.9,612,'?','0','?','5'
+'?','C','R',0,0,'?','S','2',0,'?','?','?','?','?','?','?','?','Y','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.6,1500,4170,'?','0','?','2'
+'TN','C','A',0,0,'?','?','3',0,'N','?','?','?','?','?','?','?','?','?','C','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.2,609.9,1,'?','0','?','5'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','B','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,610,762,'?','0','?','2'
+'?','C','R',0,0,'?','S','3',0,'?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.601,830,880,'?','0','?','2'
+'?','C','V',0,0,'?','S','2',0,'?','?','?','2','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','SHEET',1.599,150,762,'?','0','?','2'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',0.4,20,0,'?','0','?','U'
+'?','C','A',0,85,'T','?','?',0,'?','?','G','?','?','?','Y','M','?','?','?','?','?','?','?','?','?','?','?','?','?','?','COIL',4,610,0,'?','500','?','U'
+%
+%
+%
Index: branches/MetisMQI/src/test/java/weka/core/tokenizers/AbstractTokenizerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/tokenizers/AbstractTokenizerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/tokenizers/AbstractTokenizerTest.java	(revision 29)
@@ -0,0 +1,370 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core.tokenizers;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.FastVector;
+import weka.core.OptionHandler;
+import weka.core.SerializationHelper;
+import weka.core.CheckScheme.PostProcessor;
+import weka.test.Regression;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Tokenizers.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ *
+ * @see PostProcessor
+ */
+public abstract class AbstractTokenizerTest 
+  extends TestCase {
+  
+  /** data for the regression tests */
+  protected String[] m_Data;
+  
+  /** The tokenizer to be tested */
+  protected Tokenizer m_Tokenizer;
+  
+  /** the results of the regression tests */
+  protected FastVector[] m_RegressionResults;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+  
+  /**
+   * Constructs the <code>AbstractTokenizerTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractTokenizerTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * returns the data to use in the tests
+   * 
+   * @return		the data to use in the tests
+   */
+  protected String[] getData() {
+    return new String[]{
+      "Humpty Dumpty was sitting, with his legs crossed like a Turk, on the top of a high wall -- such a narrow one that Alice quite wondered how he could keep his balance -- and, as his eyes were steadily fixed in the opposite direction, and he didn't take the least notice of her, she thought he must be a stuffed figure, after all.",
+      "The planet Mars, I scarcely need remind the reader, revolves about the sun at a mean distance of 140,000,000 miles, and the light and heat it receives from the sun is barely half of that received by this world.",
+      "I've studied now Philosophy And Jurisprudence, Medicine, And even, alas! Theology All through and through with ardour keen! Here now I stand, poor fool, and see I'm just as wise as formerly."
+    };
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the option handling.
+   * Sets the tokenizer returned from the getTokenizer() method if that can
+   * handle options.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   * @see	#getTokenizer()
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (getTokenizer() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getTokenizer());
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Tokenizer returned from the getTokenizer() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getTokenizer()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getTokenizer());
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default tokenizer to test and loads a test set of Instances.
+   *
+   * @exception Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Tokenizer         = getTokenizer();
+    m_OptionTester      = getOptionTester();
+    m_GOETester         = getGOETester();
+    m_Data              = getData();
+    m_RegressionResults = new FastVector[m_Data.length];
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Tokenizer         = null;
+    m_OptionTester      = null;
+    m_GOETester         = null;
+    m_Data              = null;
+    m_RegressionResults = null;
+  }
+
+  /**
+   * Used to create an instance of a specific tokenizer.
+   *
+   * @return a suitably configured <code>Tokenizer</code> value
+   */
+  public abstract Tokenizer getTokenizer();
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    boolean     result;
+
+    result = !SerializationHelper.needsUID(m_Tokenizer.getClass());
+
+    if (!result)
+      fail("Doesn't declare serialVersionUID!");
+  }
+
+  /**
+   * tests whether the tokenizer correctly initializes in the
+   * buildTokenizer method
+   */
+  public void testBuildInitialization() {
+    boolean		result;
+    int			i;
+    int			n;
+    String[][][]	processed;
+    String		msg;
+    
+    // process data twice
+    processed = new String[2][m_Data.length][];
+    for (n = 0; n < 2; n++) {
+      for (i = 0; i < m_Data.length; i++) {
+	try {
+	  processed[n][i] = Tokenizer.tokenize(m_Tokenizer, new String[]{m_Data[i]});
+	}
+	catch (Exception e) {
+	  processed[n][i] = new String[0];
+	}
+      }
+    }
+    
+    // was the same data produced?
+    result = true;
+    msg    = "";
+    for (i = 0; i < m_Data.length; i++) {
+      if (processed[0].length == processed[1].length) {
+	for (n = 0; n < processed[0][i].length; n++) {
+	  if (!processed[0][i][n].equals(processed[1][i][n])) {
+	    result = false;
+	    msg    = "different substrings";
+	    break;
+	  }
+	}
+      }
+      else {
+	result = false;
+	msg    = "different number of substrings";
+	break;
+      }
+    }
+
+    if (!result)
+      fail("Incorrect build initialization (" + msg + ")!");
+  }
+
+  /**
+   * Runs the tokenizer over the given string and returns the generated 
+   * tokens.
+   *
+   * @param s		the string to tokenize
+   * @return 		a <code>FastVector</code> containing the tokens.
+   * @throws Exception	if tokenization fails
+   */
+  protected FastVector useTokenizer(String s) throws Exception {
+    String[]	tokens;
+    FastVector	result;
+    int		i;
+    
+    tokens = Tokenizer.tokenize(m_Tokenizer, new String[]{s});
+    
+    result = new FastVector();
+    for (i = 0; i < tokens.length; i++)
+      result.addElement(tokens[i]);
+    
+    return result;
+      
+  }
+
+  /**
+   * Returns a string containing all the tokens.
+   *
+   * @param tokens 	a <code>FastVector</code> containing the tokens
+   * @return 		a <code>String</code> representing the vector of tokens.
+   */
+  protected String predictionsToString(FastVector tokens) {
+    StringBuffer sb = new StringBuffer();
+    
+    sb.append(tokens.size()).append(" tokens\n");
+    for (int i = 0; i < tokens.size(); i++)
+      sb.append(tokens.elementAt(i)).append('\n');
+    
+    return sb.toString();
+  }
+
+  /**
+   * Runs a regression test -- this checks that the output of the tested
+   * object matches that in a reference version. When this test is
+   * run without any pre-existing reference output, the reference version
+   * is created.
+   */
+  public void testRegression() {
+    int		i;
+    boolean	succeeded;
+    Regression	reg;
+    
+    reg       = new Regression(this.getClass());
+    succeeded = false;
+    
+    for (i = 0; i < m_Data.length; i++) {
+      try {
+        m_RegressionResults[i] = useTokenizer(m_Data[i]);
+        succeeded = true;
+        reg.println(predictionsToString(m_RegressionResults[i]));
+      }
+      catch (Exception e) {
+	m_RegressionResults[i] = null;
+      }
+    }
+    
+    if (!succeeded) {
+      fail("Problem during regression testing: no successful tokens generated for any string");
+    }
+
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } 
+    catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkDefaultOptions())
+	fail("Default options were not processed correctly.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/tokenizers/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/tokenizers/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/tokenizers/AllTests.java	(revision 29)
@@ -0,0 +1,49 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato 
+ */
+
+package weka.core.tokenizers;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all tokenizers. Run from the command line with: <p/>
+ * java weka.core.tokenizers.AllTests
+ *
+ * @author FracPete (frapcete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+
+    suite.addTest(suite("weka.core.tokenizers.Tokenizer"));
+
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/tokenizers/AlphabeticTokenizerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/tokenizers/AlphabeticTokenizerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/tokenizers/AlphabeticTokenizerTest.java	(revision 29)
@@ -0,0 +1,80 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AlphabeticTokenizer. Run from the command line with:<p>
+ * java weka.core.tokenizers.AlphabeticTokenizerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AlphabeticTokenizerTest
+  extends AbstractTokenizerTest {
+
+  public AlphabeticTokenizerTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default AlphabeticTokenizer */
+  public Tokenizer getTokenizer() {
+    return new AlphabeticTokenizer();
+  }
+
+  /**
+   * tests the number of generated tokens
+   */
+  public void testNumberOfGeneratedTokens() {
+    String 	s;
+    String[]	result;
+    
+    // no numbers included
+    s = "HOWEVER, the egg only got larger and larger, and more and more human";
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{s});
+      assertEquals("number of tokens differ (1)", 13, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+    
+    // numbers included
+    s = "The planet Mars, I scarcely need remind the reader, revolves about the sun at a mean distance of 140,000,000 miles";
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{s});
+      assertEquals("number of tokens differ (2)", 19, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(AlphabeticTokenizerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/tokenizers/NGramTokenizerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/tokenizers/NGramTokenizerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/tokenizers/NGramTokenizerTest.java	(revision 29)
@@ -0,0 +1,89 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NGramTokenizer. Run from the command line with:<p>
+ * java weka.core.tokenizers.NGramTokenizerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class NGramTokenizerTest
+  extends AbstractTokenizerTest {
+
+  public NGramTokenizerTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default NGramTokenizer */
+  public Tokenizer getTokenizer() {
+    return new NGramTokenizer();
+  }
+
+  /**
+   * tests the number of generated tokens
+   */
+  public void testNumberOfGeneratedTokens() {
+    String 	s;
+    String[]	result;
+    
+    s = "HOWEVER, the egg only got larger and larger, and more and more human";
+
+    // only 1-grams
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{"-min", "1", "-max", "1", s});
+      assertEquals("number of tokens differ (1)", 13, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+
+    // only 2-grams
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{"-min", "2", "-max", "2", s});
+      assertEquals("number of tokens differ (2)", 12, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+
+    // 1 to 3-grams
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{"-min", "1", "-max", "3", s});
+      assertEquals("number of tokens differ (3)", 36, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(NGramTokenizerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/core/tokenizers/WordTokenizerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/core/tokenizers/WordTokenizerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/core/tokenizers/WordTokenizerTest.java	(revision 29)
@@ -0,0 +1,69 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.core.tokenizers;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests WordTokenizer. Run from the command line with:<p>
+ * java weka.core.tokenizers.WordTokenizerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class WordTokenizerTest
+  extends AbstractTokenizerTest {
+
+  public WordTokenizerTest(String name) {
+    super(name);
+  }
+
+  /** Creates a default WordTokenizer */
+  public Tokenizer getTokenizer() {
+    return new WordTokenizer();
+  }
+
+  /**
+   * tests the number of generated tokens
+   */
+  public void testNumberOfGeneratedTokens() {
+    String 	s;
+    String[]	result;
+    
+    s = "HOWEVER, the egg only got larger and larger, and more and more human";
+    try {
+      result = Tokenizer.tokenize(m_Tokenizer, new String[]{s});
+      assertEquals("number of tokens differ", 13, result.length);
+    }
+    catch (Exception e) {
+      fail("Error tokenizing string '" + s + "'!");
+    }
+  }
+  
+  public static Test suite() {
+    return new TestSuite(WordTokenizerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/AbstractClusterDefinitionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/AbstractClusterDefinitionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/AbstractClusterDefinitionTest.java	(revision 29)
@@ -0,0 +1,129 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators;
+
+import weka.core.CheckGOE;
+import weka.core.SerializationHelper;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for ClusterDefinitions.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public abstract class AbstractClusterDefinitionTest 
+  extends TestCase {
+
+  /** The cluster definition to be tested */
+  protected ClusterDefinition m_Definition;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+
+  /**
+   * Constructs the <code>AbstractClusterDefinitionTest</code>. 
+   * Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractClusterDefinitionTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default cluster definition to test.
+   *
+   * @throws Exception if an error occurs 
+   */
+  protected void setUp() throws Exception {
+    m_Definition   = getDefinition();
+    m_GOETester    = getGOETester();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Definition   = null;
+    m_GOETester    = null;
+  }
+
+  /**
+   * Used to create an instance of a specific ClusterDefinition.
+   *
+   * @return a suitably configured <code>ClusterDefinition</code> value
+   */
+  public abstract ClusterDefinition getDefinition();
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the ClusterDefinition returned from the getDefinition() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getDefinition()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getDefinition());
+    result.setSilent(true);
+    
+    return result;
+  }
+
+  /**
+   * tests whether setting the options returned by getOptions() works
+   */
+  public void testOptions() {
+    try {
+      m_Definition.setOptions(m_Definition.getOptions());
+    }
+    catch (Exception e) {
+      fail("setOptions(getOptions()) does not work: " + e.getMessage());
+    }
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    if (SerializationHelper.needsUID(m_Definition.getClass()))
+      fail("Doesn't declare serialVersionUID!");
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/AbstractDataGeneratorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/AbstractDataGeneratorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/AbstractDataGeneratorTest.java	(revision 29)
@@ -0,0 +1,224 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators;
+
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.OptionHandler;
+import weka.core.SerializationHelper;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for DataGenerators.
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public abstract class AbstractDataGeneratorTest 
+  extends TestCase {
+
+  /** The datagenerator to be tested */
+  protected DataGenerator m_Generator;
+
+  /** for storing the result */
+  protected StringWriter m_Output;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+
+  /**
+   * Constructs the <code>AbstractDataGeneratorTest</code>. 
+   * Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractDataGeneratorTest(String name) { 
+    super(name); 
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default datagenerator to test.
+   *
+   * @throws Exception if an error occurs 
+   */
+  protected void setUp() throws Exception {
+    m_Generator    = getGenerator();
+    m_Output       = new StringWriter();
+    m_Generator.setOutput(new PrintWriter(m_Output));
+    m_OptionTester = getOptionTester();
+    m_GOETester    = getGOETester();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Generator    = null;
+    m_Output       = null;
+    m_GOETester    = null;
+    m_OptionTester = null;
+  }
+
+  /**
+   * Used to create an instance of a specific DataGenerator.
+   *
+   * @return a suitably configured <code>DataGenerator</code> value
+   */
+  public abstract DataGenerator getGenerator();
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    result.setOptionHandler((OptionHandler) getGenerator());
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Generator returned from the getGenerator() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getGenerator()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getGenerator());
+    result.setIgnoredProperties(result.getIgnoredProperties() + ",datasetFormat");
+    result.setSilent(true);
+    
+    return result;
+  }
+
+  /**
+   * tests whether setting the options returned by getOptions() works
+   */
+  public void testOptions() {
+    try {
+      m_Generator.setOptions(m_Generator.getOptions());
+    }
+    catch (Exception e) {
+      fail("setOptions(getOptions()) does not work: " + e.getMessage());
+    }
+  }
+
+  /**
+   * tests whether data can be generated with the default options
+   */
+  public void testMakeData() {
+    try {
+      m_Generator.makeData(m_Generator, new String[0]);
+    }
+    catch (Exception e) {
+      fail("Generation of data failed: " + e.getMessage());
+    }
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    if (SerializationHelper.needsUID(m_Generator.getClass()))
+      fail("Doesn't declare serialVersionUID!");
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (!m_OptionTester.checkListOptions())
+      fail("Options cannot be listed via listOptions.");
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("setOptions method failed.");
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (!m_OptionTester.checkDefaultOptions())
+      fail("Default options were not processed correctly.");
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (!m_OptionTester.checkRemainingOptions())
+      fail("There were 'left-over' options.");
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   */
+  public void testCanonicalUserOptions() {
+    if (!m_OptionTester.checkCanonicalUserOptions())
+      fail("setOptions method failed");
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (!m_OptionTester.checkSetOptions())
+      fail("Resetting of options failed");
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/AllTests.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all datagenerators and cluster definitions. Run from the
+ * command line with:<p/>
+ * java weka.datagenerators.AllTests
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+
+    suite.addTest(suite("weka.datagenerators.DataGenerator"));
+    suite.addTest(suite("weka.datagenerators.ClusterDefinition"));
+
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/AgrawalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/AgrawalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/AgrawalTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Agrawal. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.classification.AgrawalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AgrawalTest 
+  extends AbstractDataGeneratorTest {
+
+  public AgrawalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Agrawal */
+  public DataGenerator getGenerator() {
+    return new Agrawal();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AgrawalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/BayesNetTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/BayesNetTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/BayesNetTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BayesNet. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.classification.BayesNetTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class BayesNetTest 
+  extends AbstractDataGeneratorTest {
+
+  public BayesNetTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default BayesNet */
+  public DataGenerator getGenerator() {
+    return new BayesNet();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BayesNetTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/LED24Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/LED24Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/LED24Test.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests LED24. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.classification.LED24Test
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class LED24Test 
+  extends AbstractDataGeneratorTest {
+
+  public LED24Test(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default LED24 */
+  public DataGenerator getGenerator() {
+    return new LED24();
+  }
+
+  public static Test suite() {
+    return new TestSuite(LED24Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RDG1Test.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RDG1Test.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RDG1Test.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RDG1. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.classification.RDG1Test
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RDG1Test 
+  extends AbstractDataGeneratorTest {
+
+  public RDG1Test(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RDG1 */
+  public DataGenerator getGenerator() {
+    return new RDG1();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RDG1Test.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RandomRBFTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RandomRBFTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/classification/RandomRBFTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.classification;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomRBF. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.classification.RandomRBFTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomRBFTest 
+  extends AbstractDataGeneratorTest {
+
+  public RandomRBFTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default RandomRBF */
+  public DataGenerator getGenerator() {
+    return new RandomRBF();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomRBFTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/ExpressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/ExpressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/ExpressionTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.regression;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Expression. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.regression.ExpressionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ExpressionTest 
+  extends AbstractDataGeneratorTest {
+
+  public ExpressionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Expression */
+  public DataGenerator getGenerator() {
+    return new Expression();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ExpressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/MexicanHatTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/MexicanHatTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/classifiers/regression/MexicanHatTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.classifiers.regression;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MexicanHat. Run from the command line with:<p/>
+ * java weka.datagenerators.classifiers.regression.MexicanHatTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MexicanHatTest 
+  extends AbstractDataGeneratorTest {
+
+  public MexicanHatTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MexicanHat */
+  public DataGenerator getGenerator() {
+    return new MexicanHat();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MexicanHatTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/BIRCHClusterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/BIRCHClusterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/BIRCHClusterTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests BIRCHCluster. Run from the command line with:<p/>
+ * java weka.datagenerators.clusterers.BIRCHClusterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class BIRCHClusterTest 
+  extends AbstractDataGeneratorTest {
+
+  public BIRCHClusterTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default BIRCHCluster */
+  public DataGenerator getGenerator() {
+    return new BIRCHCluster();
+  }
+
+  public static Test suite() {
+    return new TestSuite(BIRCHClusterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterDefinitionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterDefinitionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterDefinitionTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.datagenerators.AbstractClusterDefinitionTest;
+import weka.datagenerators.ClusterDefinition;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SubspaceClusterDefinition. Run from the command line with:<p/>
+ * java weka.classifiers.meta.SubspaceClusterDefinitionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class SubspaceClusterDefinitionTest 
+  extends AbstractClusterDefinitionTest {
+
+  public SubspaceClusterDefinitionTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SubspaceClusterDefinition */
+  public ClusterDefinition getDefinition() {
+    return new SubspaceClusterDefinition(new SubspaceCluster());
+  }
+
+  public static Test suite() {
+    return new TestSuite(SubspaceClusterDefinitionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/datagenerators/clusterers/SubspaceClusterTest.java	(revision 29)
@@ -0,0 +1,55 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.datagenerators.clusterers;
+
+import weka.datagenerators.AbstractDataGeneratorTest;
+import weka.datagenerators.DataGenerator;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SubspaceCluster. Run from the command line with:<p/>
+ * java weka.datagenerators.clusterers.SubspaceClusterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class SubspaceClusterTest 
+  extends AbstractDataGeneratorTest {
+
+  public SubspaceClusterTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default SubspaceCluster */
+  public DataGenerator getGenerator() {
+    return new SubspaceCluster();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SubspaceClusterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/AbstractFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/AbstractFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/AbstractFilterTest.java	(revision 29)
@@ -0,0 +1,869 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters;
+
+import weka.classifiers.Classifier;
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.CheckGOE;
+import weka.core.CheckOptionHandler;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.OptionHandler;
+import weka.core.SerializationHelper;
+import weka.core.TestInstances;
+import weka.core.Capabilities.Capability;
+import weka.test.Regression;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract Test class for Filters.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @authro FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.14 $
+ */
+public abstract class AbstractFilterTest
+  extends TestCase {
+
+  // TODO: 
+  // * Check that results between incremental and batch use are
+  //   the same
+  // * Check batch operation is OK
+  // * Check memory use between subsequent runs
+  // * Check memory use when multiplying data?
+
+  /** Set to true to print out extra info during testing */
+  protected static boolean VERBOSE = false;
+
+  /** The filter to be tested */
+  protected Filter m_Filter;
+
+  /** A set of instances to test with */
+  protected Instances m_Instances;
+  
+  /** the OptionHandler tester */
+  protected CheckOptionHandler m_OptionTester;
+  
+  /** the FilteredClassifier instance used for tests */
+  protected FilteredClassifier m_FilteredClassifier;
+  
+  /** for testing GOE stuff */
+  protected CheckGOE m_GOETester;
+
+  /**
+   * Constructs the <code>AbstractFilterTest</code>. Called by subclasses.
+   *
+   * @param name the name of the test class
+   */
+  public AbstractFilterTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    m_Filter             = getFilter();
+    m_Instances          = new Instances(new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("weka/filters/data/FilterTest.arff"))));
+    m_OptionTester       = getOptionTester();
+    m_GOETester          = getGOETester();
+    m_FilteredClassifier = getFilteredClassifier();
+  }
+
+  /** Called by JUnit after each test method */
+  protected void tearDown() {
+    m_Filter             = null;
+    m_Instances          = null;
+    m_OptionTester       = null;
+    m_GOETester          = null;
+    m_FilteredClassifier = null;
+  }
+  
+  /**
+   * Configures the CheckOptionHandler uses for testing the optionhandling.
+   * Sets the scheme to test.
+   * 
+   * @return	the fully configured CheckOptionHandler
+   */
+  protected CheckOptionHandler getOptionTester() {
+    CheckOptionHandler		result;
+    
+    result = new CheckOptionHandler();
+    if (getFilter() instanceof OptionHandler)
+      result.setOptionHandler((OptionHandler) getFilter());
+    else
+      result.setOptionHandler(null);
+    result.setUserOptions(new String[0]);
+    result.setSilent(true);
+    
+    return result;
+  }
+  
+  /**
+   * Configures the CheckGOE used for testing GOE stuff.
+   * Sets the Filter returned from the getFilter() method.
+   * 
+   * @return	the fully configured CheckGOE
+   * @see	#getFilter()
+   */
+  protected CheckGOE getGOETester() {
+    CheckGOE		result;
+    
+    result = new CheckGOE();
+    result.setObject(getFilter());
+    result.setSilent(true);
+    
+    return result;
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier	result;
+    Filter		filter;
+    Classifier		cls;
+    
+    result = new FilteredClassifier();
+    
+    // set filter
+    filter = getFilter();
+    result.setFilter(filter);
+    
+    // set classifier
+    if (filter.getCapabilities().handles(Capability.NOMINAL_CLASS))
+      cls = new weka.classifiers.trees.J48();
+    else if (filter.getCapabilities().handles(Capability.BINARY_CLASS))
+      cls = new weka.classifiers.trees.J48();
+    else if (filter.getCapabilities().handles(Capability.UNARY_CLASS))
+      cls = new weka.classifiers.trees.J48();
+    else if (filter.getCapabilities().handles(Capability.NUMERIC_CLASS))
+      cls = new weka.classifiers.trees.M5P();
+    else if (filter.getCapabilities().handles(Capability.DATE_CLASS))
+      cls = new weka.classifiers.trees.M5P();
+    else
+      throw new IllegalStateException("Cannot determine base classifier for FilteredClassifier!");
+    result.setClassifier(cls);
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception {
+    TestInstances	test;
+    Instances		result;
+
+    // NB: in order to make sure that the classifier can handle the data,
+    //     we're using the classifier's capabilities to generate the data.
+    test = TestInstances.forCapabilities(
+  	m_FilteredClassifier.getClassifier().getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+  
+  /**
+   * Used to create an instance of a specific filter. The filter
+   * should be configured to operate on a dataset that contains
+   * attributes in this order:<p>
+   *
+   * String, Nominal, Numeric, String, Nominal, Numeric<p>
+   *
+   * Where the first three attributes do not contain any missing values,
+   * but the last three attributes do. If the filter is for some reason
+   * incapable of accepting a dataset of this type, override setUp() to 
+   * either manipulate the default dataset to be compatible, or load another
+   * test dataset. <p>
+   *
+   * The configured filter should preferrably do something
+   * meaningful, since the results of filtering are used as the default
+   * regression output (and it would hardly be interesting if the filtered 
+   * data was the same as the input data).
+   *
+   * @return a suitably configured <code>Filter</code> value
+   */
+  public abstract Filter getFilter();
+
+  /**
+   * Simple method to return the filtered set of test instances after
+   * passing through the test filter. m_Filter contains the filter and
+   * m_Instances contains the test instances.
+   *
+   * @return the Instances after filtering through the filter we have set
+   * up to test.  
+   */
+  protected Instances useFilter() {
+
+    Instances result = null;
+    Instances icopy = new Instances(m_Instances);
+    try {
+      m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+    return result;
+  }
+
+  /**
+   * tests whether the scheme declares a serialVersionUID.
+   */
+  public void testSerialVersionUID() {
+    if (SerializationHelper.needsUID(m_Filter.getClass()))
+      fail("Doesn't declare serialVersionUID!");
+  }
+  
+  /**
+   * Test buffered operation. Output instances are only collected after
+   * all instances are passed through
+   */
+  public void testBuffered() {
+
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // Check the output is valid for printing by trying to write out to 
+    // a stringbuffer
+    StringWriter sw = new StringWriter(2000);
+    sw.write(result.toString());
+
+    // Check the input hasn't been modified
+    // We just check the headers are the same and that the instance
+    // count is the same.
+    assertTrue(icopy.equalHeaders(m_Instances));
+    assertEquals(icopy.numInstances(), m_Instances.numInstances());
+
+    // Try repeating the filtering and check we get the same results
+    Instances result2 = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result2 = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result2);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // Again check the input hasn't been modified
+    // We just check the headers are the same and that the instance
+    // count is the same.
+    assertTrue(icopy.equalHeaders(m_Instances));
+    assertEquals(icopy.numInstances(), m_Instances.numInstances());
+
+    // Check the same results for both runs
+    assertTrue(result.equalHeaders(result2));
+    assertEquals(result.numInstances(), result2.numInstances());
+    
+  }
+
+  /**
+   * Test incremental operation. Each instance is removed as soon as it
+   * is made available
+   */
+  public void testIncremental() {
+
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    boolean headerImmediate = false;
+    try {
+      headerImmediate = m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    if (headerImmediate) {
+      if (VERBOSE) System.err.println("Filter makes header immediately available.");
+      result = m_Filter.getOutputFormat();
+    }
+    // Pass all the instances to the filter
+    for (int i = 0; i < icopy.numInstances(); i++) {
+      if (VERBOSE) System.err.println("Input instance to filter");
+      boolean collectNow = false;
+      try {
+        collectNow = m_Filter.input(icopy.instance(i));
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        fail("Exception thrown on input(): \n" + ex.getMessage());
+      }
+      if (collectNow) {
+        if (VERBOSE) System.err.println("Filter said collect immediately");
+	if (!headerImmediate) {
+	  fail("Filter didn't return true from setInputFormat() earlier!");
+	}
+        if (VERBOSE) System.err.println("Getting output instance");
+	result.add(m_Filter.output());
+      }
+    }
+    // Say that input has finished, and print any pending output instances
+    if (VERBOSE) System.err.println("Setting end of batch");
+    boolean toCollect = false;
+    try {
+      toCollect = m_Filter.batchFinished();
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on batchFinished(): \n" + ex.getMessage());
+    }
+    if (toCollect) {
+      if (VERBOSE) System.err.println("Filter said collect output");
+      if (!headerImmediate) {
+        if (VERBOSE) System.err.println("Getting output format");
+	result = m_Filter.getOutputFormat();
+      }
+      if (VERBOSE) System.err.println("Getting output instance");
+      while (m_Filter.numPendingOutput() > 0) {
+	result.add(m_Filter.output());
+        if (VERBOSE) System.err.println("Getting output instance");
+      }
+    }
+    
+    assertNotNull(result);
+
+    // Check the output iss valid for printing by trying to write out to 
+    // a stringbuffer
+    StringWriter sw = new StringWriter(2000);
+    sw.write(result.toString());
+  }
+
+  /**
+   * Describe <code>testRegression</code> method here.
+   *
+   */
+  public void testRegression() {
+
+    Regression reg = new Regression(this.getClass());
+    Instances result = useFilter();
+    reg.println(result.toString());
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed. Difference:\n" + diff);
+      }
+    } catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+
+    reg = new Regression(this.getClass());
+
+    // Run the filter using deprecated calls to check it still works the same
+    Instances icopy = new Instances(m_Instances);
+    try {
+      m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      for (int i = 0; i < icopy.numInstances(); i++) {
+        m_Filter.input(icopy.instance(i));
+      }
+      m_Filter.batchFinished();
+      result = m_Filter.getOutputFormat();
+      weka.core.Instance processed;
+      while ((processed = m_Filter.output()) != null) {
+        result.add(processed);
+      }
+      assertNotNull(result);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+    reg.println(result.toString());
+    try {
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Warning: No reference available, creating."); 
+      } else if (!diff.equals("")) {
+        fail("Regression test failed when using deprecated methods. Difference:\n" + diff);
+      }
+    } catch (java.io.IOException ex) {
+      fail("Problem during regression testing.\n" + ex);
+    }
+  }
+
+  public void testThroughput() {
+
+    if (VERBOSE) {
+      Instances icopy = new Instances(m_Instances);
+      // Make a bigger dataset
+      Instances result = null;
+      for (int i = 0; i < 20000; i++) {
+        icopy.add(m_Instances.instance(i%m_Instances.numInstances()));
+      }
+      long starttime, endtime;
+      double secs, rate;
+
+
+      // Time incremental usage
+      starttime = System.currentTimeMillis();
+      boolean headerImmediate = false;
+      try {
+        headerImmediate = m_Filter.setInputFormat(icopy);
+        if (headerImmediate) {
+          result = m_Filter.getOutputFormat();
+        }
+        for (int i = 0; i < icopy.numInstances(); i++) {
+          boolean collectNow = false;
+          collectNow = m_Filter.input(icopy.instance(i));
+          if (collectNow) {
+            if (!headerImmediate) {
+              fail("Filter didn't return true from setInputFormat() earlier!");
+            }
+            result.add(m_Filter.output());
+          }
+        }
+        // Say that input has finished, and print any pending output instances
+        boolean toCollect = false;
+        toCollect = m_Filter.batchFinished();
+        if (toCollect) {
+          if (!headerImmediate) {
+            result = m_Filter.getOutputFormat();
+          }
+          while (m_Filter.numPendingOutput() > 0) {
+            result.add(m_Filter.output());
+          }
+        }
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        fail("Exception thrown during incremental filtering: \n" + ex.getMessage());
+      }
+      endtime = System.currentTimeMillis();
+      secs = (double)(endtime - starttime) / 1000;
+      rate = (double)icopy.numInstances() / secs;
+      System.err.println("\n" + m_Filter.getClass().getName() 
+                         + " incrementally processed " 
+                         + rate + " instances per sec"); 
+      
+      // Time batch usage
+      starttime = System.currentTimeMillis();
+      try {
+        m_Filter.setInputFormat(icopy);
+        result = Filter.useFilter(icopy, m_Filter);
+        assertNotNull(result);
+      } catch (Exception ex) {
+        ex.printStackTrace();
+        fail("Exception thrown during batch filtering: \n" + ex.getMessage());
+      }
+      endtime = System.currentTimeMillis();
+      secs = (double)(endtime - starttime) / 1000;
+      rate = (double)icopy.numInstances() / secs;
+      System.err.println("\n" + m_Filter.getClass().getName() 
+                         + " batch processed " 
+                         + rate + " instances per sec"); 
+
+
+    }
+  }
+  
+  /**
+   * tests the listing of the options
+   */
+  public void testListOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkListOptions())
+	fail("Options cannot be listed via listOptions.");
+    }
+  }
+  
+  /**
+   * tests the setting of the options
+   */
+  public void testSetOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("setOptions method failed.");
+    }
+  }
+  
+  /**
+   * tests whether the default settings are processed correctly
+   */
+  public void testDefaultOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkDefaultOptions())
+	fail("Default options were not processed correctly.");
+    }
+  }
+  
+  /**
+   * tests whether there are any remaining options
+   */
+  public void testRemainingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkRemainingOptions())
+	fail("There were 'left-over' options.");
+    }
+  }
+  
+  /**
+   * tests the whether the user-supplied options stay the same after setting.
+   * getting, and re-setting again.
+   * 
+   * @see 	#getOptionTester()
+   */
+  public void testCanonicalUserOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkCanonicalUserOptions())
+	fail("setOptions method failed");
+    }
+  }
+  
+  /**
+   * tests the resetting of the options to the default ones
+   */
+  public void testResettingOptions() {
+    if (m_OptionTester.getOptionHandler() != null) {
+      if (!m_OptionTester.checkSetOptions())
+	fail("Resetting of options failed");
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    Instances		data;
+    int			i;
+    
+    try {
+      // generate data
+      data = getFilteredClassifierData();
+      
+      // build classifier
+      m_FilteredClassifier.buildClassifier(data);
+
+      // test classifier
+      for (i = 0; i < data.numInstances(); i++) {
+	m_FilteredClassifier.classifyInstance(data.instance(i));
+      }
+    }
+    catch (Exception e) {
+      fail("Problem with FilteredClassifier: " + e.toString());
+    }
+  }
+  
+  /**
+   * simulates batch filtering
+   */
+  public void testBatchFiltering() {
+    Instances result = null;
+    Instances icopy = new Instances(m_Instances);
+    
+    // setup filter
+    try {
+      if (m_Filter.setInputFormat(icopy)) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (setup)", result);
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex);
+    }
+
+    // first batch
+    try {
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  Instance out = m_Filter.output();
+	  assertNotNull("Instance not made available immediately (1. batch)", out);
+	  result.add(out);
+	}
+      }
+      m_Filter.batchFinished();
+
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (1. batch)", result);
+	assertTrue("Pending output instances (1. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 1. batch: \n" + ex);
+    }
+
+    // second batch
+    try {
+      result = null;
+      if (m_Filter.isOutputFormatDefined())
+	result = m_Filter.getOutputFormat();
+      
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  if (result == null) {
+	    fail("Filter didn't return true from isOutputFormatDefined() (2. batch)");
+	  }
+	  else {
+	    Instance out = m_Filter.output();
+	    assertNotNull("Instance not made available immediately (2. batch)", out);
+	    result.add(out);
+	  }
+	}
+      }
+      m_Filter.batchFinished();
+      
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (2. batch)", result);
+	assertTrue("Pending output instances (2. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 2. batch: \n" + ex);
+    }
+  }
+  
+  /**
+   * simulates batch filtering (with the second dataset being smaller)
+   */
+  public void testBatchFilteringSmaller() {
+    Instances result = null;
+    Instances icopy = new Instances(m_Instances);
+    
+    // setup filter
+    try {
+      if (m_Filter.setInputFormat(icopy)) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (setup)", result);
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex);
+    }
+
+    // first batch
+    try {
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  Instance out = m_Filter.output();
+	  assertNotNull("Instance not made available immediately (1. batch)", out);
+	  result.add(out);
+	}
+      }
+      m_Filter.batchFinished();
+
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (1. batch)", result);
+	assertTrue("Pending output instances (1. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 1. batch: \n" + ex);
+    }
+
+    // second batch
+    try {
+      result = null;
+      if (m_Filter.isOutputFormatDefined())
+	result = m_Filter.getOutputFormat();
+      
+      // delete some instances
+      int num = (int) ((double) icopy.numInstances() * 0.3);
+      for (int i = 0; i < num; i++)
+	icopy.delete(0);
+      
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  if (result == null) {
+	    fail("Filter didn't return true from isOutputFormatDefined() (2. batch)");
+	  }
+	  else {
+	    Instance out = m_Filter.output();
+	    assertNotNull("Instance not made available immediately (2. batch)", out);
+	    result.add(out);
+	  }
+	}
+      }
+      m_Filter.batchFinished();
+      
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (2. batch)", result);
+	assertTrue("Pending output instances (2. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 2. batch: \n" + ex);
+    }
+  }
+  
+  /**
+   * simulates batch filtering (with the second dataset being bigger)
+   */
+  public void testBatchFilteringLarger() {
+    Instances result = null;
+    Instances icopy = new Instances(m_Instances);
+    
+    // setup filter
+    try {
+      if (m_Filter.setInputFormat(icopy)) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (setup)", result);
+      }
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex);
+    }
+
+    // first batch
+    try {
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  Instance out = m_Filter.output();
+	  assertNotNull("Instance not made available immediately (1. batch)", out);
+	  result.add(out);
+	}
+      }
+      m_Filter.batchFinished();
+
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (1. batch)", result);
+	assertTrue("Pending output instances (1. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 1. batch: \n" + ex);
+    }
+
+    // second batch
+    try {
+      result = null;
+      if (m_Filter.isOutputFormatDefined())
+	result = m_Filter.getOutputFormat();
+      
+      // add some instances
+      int num = (int) ((double) icopy.numInstances() * 0.3);
+      for (int i = 0; i < num; i++)
+	icopy.add(icopy.instance(i));
+      
+      for (int i = 0; i < icopy.numInstances(); i++) {
+	if (m_Filter.input(icopy.instance(i))) {
+	  if (result == null) {
+	    fail("Filter didn't return true from isOutputFormatDefined() (2. batch)");
+	  }
+	  else {
+	    Instance out = m_Filter.output();
+	    assertNotNull("Instance not made available immediately (2. batch)", out);
+	    result.add(out);
+	  }
+	}
+      }
+      m_Filter.batchFinished();
+      
+      if (result == null) {
+	result = m_Filter.getOutputFormat();
+	assertNotNull("Output format defined (2. batch)", result);
+	assertTrue("Pending output instances (2. batch)", m_Filter.numPendingOutput() > 0);
+      }
+
+      while (m_Filter.numPendingOutput() > 0)
+	result.add(m_Filter.output());
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown during 2. batch: \n" + ex);
+    }
+  }
+  
+  /**
+   * tests for a globalInfo method
+   */
+  public void testGlobalInfo() {
+    if (!m_GOETester.checkGlobalInfo())
+      fail("No globalInfo method");
+  }
+  
+  /**
+   * tests the tool tips
+   */
+  public void testToolTips() {
+    if (!m_GOETester.checkToolTips())
+      fail("Tool tips inconsistent");
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/AbstractTimeSeriesFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/AbstractTimeSeriesFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/AbstractTimeSeriesFilterTest.java	(revision 29)
@@ -0,0 +1,155 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.unsupervised.attribute.TimeSeriesTranslate;
+import weka.filters.unsupervised.attribute.TimeSeriesTranslateTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TimeSeriesTranslateFilter. Run from the command line with:<p>
+ * java weka.filters.TimeSeriesTranslateFilterTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.7 $
+ */
+public abstract class AbstractTimeSeriesFilterTest extends AbstractFilterTest {
+
+  /** Tolerance allowed in double comparisons */
+  protected static final double TOLERANCE = 0.001;
+
+  public AbstractTimeSeriesFilterTest(String name) { super(name);  }
+
+  /** Creates a default TimeSeriesTranslateFilter */
+  public abstract Filter getFilter();
+
+  public void testDefault() {
+    testInstanceRange_X(((TimeSeriesTranslate)m_Filter).getInstanceRange());
+  }
+
+  public void testInstanceRange() {
+
+    testInstanceRange_X(-5);
+    testInstanceRange_X(-2);
+    testInstanceRange_X(2);
+    testInstanceRange_X(5);
+  }
+
+  public void testFillWithMissing() {
+
+    ((TimeSeriesTranslate)m_Filter).setFillWithMissing(true);
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // Check conversion looks OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance in = m_Instances.instance(i);
+      Instance out = result.instance(i);
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if ((j != 1) && (j != 2)) {
+          if (in.isMissing(j)) {
+            assertTrue("Nonselected missing values should pass through",
+                   out.isMissing(j));
+          } else if (result.attribute(j).isString()) {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         m_Instances.attribute(j).value((int)in.value(j)),
+                         result.attribute(j).value((int)out.value(j)));
+          } else {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         in.value(j),
+                         out.value(j), TOLERANCE);
+          }
+        }
+      }
+    }    
+  }
+
+  private void testInstanceRange_X(int range) {
+    ((TimeSeriesTranslate)m_Filter).setInstanceRange(range);
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances() - Math.abs(range), result.numInstances());
+    // Check conversion looks OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance in = m_Instances.instance(i - ((range > 0) ? 0 : range));
+      Instance out = result.instance(i);
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if ((j != 1) && (j != 2)) {
+          if (in.isMissing(j)) {
+            assertTrue("Nonselected missing values should pass through",
+                   out.isMissing(j));
+          } else if (result.attribute(j).isString()) {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         m_Instances.attribute(j).value((int)in.value(j)),
+                         result.attribute(j).value((int)out.value(j)));
+          } else {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         in.value(j),
+                         out.value(j), TOLERANCE);
+          }
+        }
+      }
+    }    
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNumeric()) {
+	  ((TimeSeriesTranslate) m_FilteredClassifier.getFilter()).setAttributeIndices("" + (i + 1));
+	  ((TimeSeriesTranslate) m_FilteredClassifier.getFilter()).setFillWithMissing(true);
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(TimeSeriesTranslateTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/AllFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/AllFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/AllFilterTest.java	(revision 29)
@@ -0,0 +1,59 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters;
+
+import weka.core.Instances;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AllFilter. Run from the command line with:<p>
+ * java weka.filters.AllFilterTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class AllFilterTest extends AbstractFilterTest {
+  
+  public AllFilterTest(String name) { super(name);  }
+
+  /** Creates a default AllFilter */
+  public Filter getFilter() {
+    return new AllFilter();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(AllFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/AllTests.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/AllTests.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/AllTests.java	(revision 29)
@@ -0,0 +1,51 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters;
+
+import weka.test.WekaTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test class for all filters. Run from the command line with:<p/>
+ * java weka.filters.AllTests
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.15 $
+ */
+public class AllTests 
+  extends WekaTestSuite {
+
+  public static Test suite() {
+    TestSuite suite = (TestSuite) suite("weka.filters.Filter");
+    
+    suite.addTest(AllFilterTest.suite());
+    suite.addTest(MultiFilterTest.suite());
+    
+    return suite;
+  }
+
+  public static void main(String []args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/MultiFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/MultiFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/MultiFilterTest.java	(revision 29)
@@ -0,0 +1,117 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters;
+
+import weka.core.Instances;
+import weka.filters.unsupervised.attribute.Add;
+import weka.filters.unsupervised.attribute.AddExpression;
+import weka.filters.unsupervised.attribute.Center;
+import weka.filters.unsupervised.attribute.ReplaceMissingValues;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * Tests MultiFilter. Run from the command line with: <p/>
+ * java weka.filters.MultiFilterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MultiFilterTest extends AbstractFilterTest {
+  
+  public MultiFilterTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MultiFilter */
+  public Filter getFilter() {
+    return new MultiFilter();
+  }
+
+  /** Creates a configured MultiFilter */
+  public Filter getConfiguredFilter() {
+    MultiFilter result = new MultiFilter();
+    
+    Filter[] filters = new Filter[2];
+    filters[0] = new Add();
+    ((Add) filters[0]).setAttributeIndex("last");
+    filters[1] = new AddExpression();
+    ((AddExpression) filters[1]).setExpression("a3+a6");
+    
+    result.setFilters(filters);
+    
+    return result;
+  }
+
+  /** Creates a configured MultiFilter (variant) */
+  public Filter getConfiguredFilterVariant() {
+    MultiFilter result = new MultiFilter();
+    
+    Filter[] filters = new Filter[2];
+    filters[0] = new ReplaceMissingValues();
+    filters[1] = new Center();
+    
+    result.setFilters(filters);
+    
+    return result;
+  }
+
+  public void testDefault() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+  }
+
+  /**  
+   * tests Add + AddExpression filter
+   */
+  public void testConfigured() {
+    m_Filter = getConfiguredFilter();
+    Instances result = useFilter();
+    // Number of attributes should be 2 more
+    assertEquals(m_Instances.numAttributes() + 2, result.numAttributes());
+    // Number of instances shouldn't change
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+  }
+
+  /**  
+   * tests ReplaceMissingValues + Center filter
+   */
+  public void testConfiguredVariant() {
+    m_Filter = getConfiguredFilterVariant();
+    Instances result = useFilter();
+    // Number of atytributes + instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultiFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/data/FilterTest.arff
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/data/FilterTest.arff	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/data/FilterTest.arff	(revision 29)
@@ -0,0 +1,30 @@
+@relation FilterTest
+@attribute StringAtt1 string
+@attribute NominalAtt1 {r, g, b}
+@attribute NumericAtt1 numeric
+@attribute StringAtt2 string
+@attribute NominalAtt2 {a, b, c, d}
+@attribute NumericAtt2 numeric
+@attribute DateAtt1 date "yyyy-MM-dd"
+@data
+humpty ,g , 1.0 ,the   ,a ,-2.3, 2001-04-03
+dumpty ,b , 2.0 ,quick ,b ,-3.3, 2001-04-03
+sat    ,r , 3.0 ,brown ,c ,-2.4, 2001-04-03
+on     ,r , 4.0 ,fox   ,d ,-5.3, 2001-04-03
+a      ,b , 5.0 ,jumped,a ,-2.6, 2001-04-03
+wall   ,r , 6.0 ,over  ,b ,-7.3, 2001-04-04
+humpty ,r , 7.0 ,the   ,c ,-2.8, 2001-04-04
+dumpty ,g , 8.0 ,lazy  ,d ,-9.3, 2001-04-04
+had    ,b , 9.0 ,dog   ,? ,-2.0, 2001-05-04
+a      ,r , 9.4 ,?     ,? ,-9.0, 2001-05-04
+great  ,r , 1.4 ,the   ,a ,-8.3, 2001-05-05
+fall   ,b , 2.3 ,quick ,b ,-7.3, 2001-05-05
+all    ,r , 3.3 ,brown ,c ,?   , 2001-05-05
+the    ,r , 4.3 ,fox   ,d ,-5.3, 2001-05-05
+kings  ,g , 5.3 ,jumped,? ,-5.6, 2001-05-06
+horses ,b , 6.5 ,over  ,b ,-4.3, 2001-05-06
+and    ,r , 7.5 ,the   ,c ,-3.8, 2001-06-06
+all    ,r , 8.5 ,lazy  ,d ,-2.3, 2001-06-06
+the    ,r , 9.4 ,?     ,a ,-1.0, 2001-06-07
+{0 wall, 2 4.3, 4 d}
+{1 b, 3 lazy, 5 3.4}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AddClassificationTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AddClassificationTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AddClassificationTest.java	(revision 29)
@@ -0,0 +1,215 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddClassification. Run from the command line with: <p/>
+ * java weka.filters.supervised.attribute.AddClassificationTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AddClassificationTest 
+  extends AbstractFilterTest {
+  
+  public AddClassificationTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default AddClassification */
+  public Filter getFilter() {
+    return new AddClassification();
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.deleteAttributeType(Attribute.STRING);
+    m_Instances.setClassIndex(0);
+  }
+
+  /**
+   * sets up the filter and performs the test
+   * 
+   * @param num		whether the class is numeric or nominal
+   * @param cl		whether the classification is to be output
+   * @param dist	whether the distribution is to be output
+   * @param error	whether the error flag is to be output
+   * @param remove	whether to remove the old class attribute
+   */
+  protected void performTest(boolean num, boolean cl, boolean dist, boolean error, boolean remove) {
+    Instances	icopy;
+    int		numAtts;
+    
+    // setup dataset
+    if (num)
+      m_Instances.setClassIndex(1);
+    else
+      m_Instances.setClassIndex(0);
+    icopy = new Instances(m_Instances);
+
+    // setup filter
+    m_Filter = getFilter();
+    if (num)
+      ((AddClassification) m_Filter).setClassifier(new weka.classifiers.trees.M5P());
+    else
+      ((AddClassification) m_Filter).setClassifier(new weka.classifiers.trees.J48());
+    
+    ((AddClassification) m_Filter).setOutputClassification(cl);
+    ((AddClassification) m_Filter).setOutputDistribution(dist);
+    ((AddClassification) m_Filter).setOutputErrorFlag(error);
+    ((AddClassification) m_Filter).setRemoveOldClass(remove);
+    
+    numAtts = icopy.numAttributes();
+    if (cl)
+      numAtts++;
+    if (dist)
+      numAtts += icopy.numClasses();
+    if (error)
+      numAtts++;
+    if (remove)
+      numAtts--;
+    
+    Instances result = useFilter();
+    assertEquals(result.numAttributes(), numAtts);
+  }
+  
+  /**
+   * performs the application with no options set
+   */
+  public void testDefault() {
+    Instances icopy = new Instances(m_Instances);
+    
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(result.numAttributes(), icopy.numAttributes());
+  }
+  
+  /**
+   * performs the application with no options set (Nominal class)
+   */
+  public void testNoneNominal() {
+    performTest(false, false, false, false, false);
+  }
+  
+  /**
+   * performs the application with only error flag set (Nominal class)
+   */
+  public void testErrorFlagNominal() {
+    performTest(false, false, false, true, false);
+  }
+  
+  /**
+   * performs the application with only classification set (Nominal class)
+   */
+  public void testClassificationNominal() {
+    performTest(false, true, false, false, false);
+  }
+  
+  /**
+   * performs the application with only distribution set (Nominal class)
+   */
+  public void testDistributionNominal() {
+    performTest(false, false, true, false, false);
+  }
+  
+  /**
+   * performs the application with no options set (Nominal class)
+   */
+  public void testNoneNumeric() {
+    performTest(true, false, false, false, false);
+  }
+  
+  /**
+   * performs the application with only error flag set (Numeric class)
+   */
+  public void testErrorFlagNumeric() {
+    performTest(true, false, false, true, false);
+  }
+  
+  /**
+   * performs the application with only classification set (Numeric class)
+   */
+  public void testClassificationNumeric() {
+    performTest(true, true, false, false, false);
+  }
+  
+  /**
+   * performs the application with only distribution set (Numeric class)
+   */
+  public void testDistributionNumeric() {
+    performTest(true, false, true, false, false);
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddClassificationTest.class);
+  }
+  
+  /**
+   * performs the application with only classification set (Nominal class)
+   * and removal of the old class attribute
+   */
+  public void testClassificationRemoveNominal() {
+    performTest(false, true, false, false, true);
+  }
+  
+  /**
+   * performs the application with only classification set (numeric class)
+   * and removal of the old class attribute
+   */
+  public void testClassificationRemoveNumeric() {
+    performTest(true, true, false, false, true);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+  
+  /**
+   * performs the application with only removal of the old class attribute
+   * (nominal)
+   */
+  public void testClassificationOnlyRemoveNominal() {
+    performTest(false, false, false, false, true);
+  }
+  
+  /**
+   * performs the application with only removal of the old class attribute
+   * (numeric)
+   */
+  public void testClassificationOnlyRemoveNumeric() {
+    performTest(true, false, false, false, true);
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AttributeSelectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AttributeSelectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/AttributeSelectionTest.java	(revision 29)
@@ -0,0 +1,92 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.attributeSelection.ASEvaluation;
+import weka.attributeSelection.ASSearch;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.RemoveType;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AttributeSelection. Run from the command line with:<p>
+ * java weka.filters.supervised.attribute.AttributeSelectionTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class AttributeSelectionTest extends AbstractFilterTest {
+  
+  public AttributeSelectionTest(String name) { super(name);  }
+
+  /** Creates a default AttributeSelection */
+  public Filter getFilter() {
+    return new AttributeSelection();
+  }
+
+  /** Creates a specialized AttributeSelection */
+  public Filter getFilter(ASEvaluation evaluator, ASSearch search) {
+    
+    AttributeSelection af = new AttributeSelection();
+    if (evaluator != null) {
+      af.setEvaluator(evaluator);
+    }
+    if (search != null) {
+      af.setSearch(search);
+    }
+    return af;
+  }
+
+  /** Remove string attributes from default fixture instances */
+  protected void setUp() throws Exception {
+
+    super.setUp();
+    RemoveType af = new RemoveType();
+    af.setInputFormat(m_Instances);
+    m_Instances = Filter.useFilter(m_Instances, af);
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      assertTrue("Problem with AttributeTypeFilter in setup", 
+             m_Instances.attribute(i).type() != Attribute.STRING);
+    }
+  }
+
+  public void testPrincipalComponent() {
+    m_Filter = getFilter(new weka.attributeSelection.PrincipalComponents(), 
+                         new weka.attributeSelection.Ranker());
+    Instances result = useFilter();
+    assertTrue(m_Instances.numAttributes() != result.numAttributes());
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(AttributeSelectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/ClassOrderTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/ClassOrderTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/ClassOrderTest.java	(revision 29)
@@ -0,0 +1,119 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClassOrder. Run from the command line with: <p/>
+ * java weka.filters.supervised.attribute.ClassOrderTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ClassOrderTest 
+  extends AbstractFilterTest {
+  
+  /** the class index to use for the tests */
+  protected int m_ClassIndex = 4;
+  
+  public ClassOrderTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to set the class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+    m_Instances.setClassIndex(m_ClassIndex);
+  }
+
+  /** Creates a default ClassOrder */
+  public Filter getFilter() {
+    return new ClassOrder();
+  }
+
+  /**
+   * compares the generated dataset with the original one
+   */
+  protected void performTests(Instances result) {
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // None of the attributes should have changed
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+      assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+    }
+    // did the order change?
+    boolean orderEqual = true;
+    for (int i = 0; i < result.numClasses(); i++) {
+      if (!m_Instances.classAttribute().value(i).equals(
+            result.classAttribute().value(i))) {
+        orderEqual = false;
+        break;
+      }
+    }
+    if (orderEqual)
+      fail("Order wasn't changed!");
+  }
+
+  /**
+   * tests the RANDOM order
+   */
+  public void testRandom() {
+    m_Filter = getFilter();
+    ((ClassOrder) m_Filter).setClassOrder(ClassOrder.RANDOM);
+    Instances result = useFilter();
+    performTests(result);
+  }
+
+  /**
+   * tests the FREQ_ASCEND order
+   */
+  public void testFreqAscend() {
+    m_Filter = getFilter();
+    ((ClassOrder) m_Filter).setClassOrder(ClassOrder.FREQ_ASCEND);
+    Instances result = useFilter();
+    performTests(result);
+  }
+
+  /**
+   * tests the FREQ_DESCEND order
+   */
+  public void testFreqDescend() {
+    m_Filter = getFilter();
+    ((ClassOrder) m_Filter).setClassOrder(ClassOrder.FREQ_DESCEND);
+    Instances result = useFilter();
+    performTests(result);
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassOrderTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/DiscretizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/DiscretizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/DiscretizeTest.java	(revision 29)
@@ -0,0 +1,152 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Discretize. Run from the command line with:<p>
+ * java weka.filters.supervised.attribute.DiscretizeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class DiscretizeTest extends AbstractFilterTest {
+  
+  public DiscretizeTest(String name) { super(name);  }
+
+  /** Need to set the class index */
+  protected void setUp() throws Exception {
+
+    super.setUp();
+    m_Instances.setClassIndex(1);
+  }
+
+  /** Creates a default Discretize */
+  public Filter getFilter() {
+    Discretize f= new Discretize();
+    return f;
+  }
+
+  /** Creates a specialized Discretize */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      Discretize f = new Discretize();
+      f.setAttributeIndices(rangelist);
+      return f;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("1,2");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    // None of the attributes should have changed, since 1,2 aren't numeric
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+      assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+    }
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3-4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if (i != 2) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(1, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testInverted() {
+    m_Filter = getFilter("1,2");
+    ((Discretize)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if ((i < 2) || !m_Instances.attribute(i).isNumeric()) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(1, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testNonInverted2() {
+    m_Filter = getFilter("first-3");
+    ((Discretize)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if ((i < 3) || !m_Instances.attribute(i).isNumeric()) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(1, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testBetterEncoding() {
+    m_Filter = getFilter("3");
+    ((Discretize)m_Filter).setUseBetterEncoding(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(Attribute.NOMINAL, result.attribute(2).type());
+  }
+
+  public void testUseKononenko() {
+    m_Filter = getFilter("3");
+    ((Discretize)m_Filter).setUseKononenko(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(Attribute.NOMINAL, result.attribute(2).type());
+  }
+
+  public static Test suite() {
+    return new TestSuite(DiscretizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/NominalToBinaryTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/NominalToBinaryTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/NominalToBinaryTest.java	(revision 29)
@@ -0,0 +1,73 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NominalToBinary. Run from the command line with:<p>
+ * java weka.filters.supervised.attribute.NominalToBinaryTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NominalToBinaryTest extends AbstractFilterTest {
+  
+  public NominalToBinaryTest(String name) { super(name);  }
+
+  /** Creates an example NominalToBinary */
+  public Filter getFilter() {
+    NominalToBinary f = new NominalToBinary();
+    return f;
+  }
+
+  /** Remove string attributes from default fixture instances */
+  protected void setUp() throws Exception {
+
+    super.setUp();
+    // NominalToBinary requires a class attribute be set
+    m_Instances.setClassIndex(m_Instances.numAttributes() - 1);
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes changes
+    assertEquals(m_Instances.numAttributes() + 3, result.numAttributes());
+    // Number of instances shouldn't change
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Eibe can enhance this to check the binarizing is correct.
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(NominalToBinaryTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/PLSFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/PLSFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/attribute/PLSFilterTest.java	(revision 29)
@@ -0,0 +1,156 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PLSFilter. Run from the command line with: <p/>
+ * java weka.filters.supervised.attribute.PLSFilterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class PLSFilterTest 
+  extends AbstractFilterTest {
+
+  /** the default number of attributes to generate (apart from class) */
+  protected final static int NUM_ATTS = 5;
+  
+  /** the number of numeric attributes in the test dataset */
+  protected final static int NUM_NUMERIC_ATTS = 20;
+  
+  public PLSFilterTest(String name) { 
+    super(name);  
+  }
+
+  /** 
+   * Creates a default PLSFilter
+   * 
+   * @return		the configured filter
+   */
+  public Filter getFilter() {
+    return getFilter(NUM_ATTS, PLSFilter.ALGORITHM_PLS1);
+  }
+
+  /** 
+   * Creates a PLSFilter according to the parameters
+   * 
+   * @param numAtts	the number of attributes to generate
+   * @param algorithm	the algorithm to use
+   * @return		the configured filter
+   */
+  public Filter getFilter(int numAtts, int algorithm) {
+    PLSFilter filter = new PLSFilter();
+    
+    filter.setNumComponents(numAtts);
+    filter.setReplaceMissing(true);
+    filter.setPreprocessing(new SelectedTag(PLSFilter.PREPROCESSING_CENTER, PLSFilter.TAGS_PREPROCESSING));
+    filter.setAlgorithm(new SelectedTag(algorithm, PLSFilter.TAGS_ALGORITHM));
+
+    return filter;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = new TestInstances();
+    test.setNumNominal(0);
+    test.setNumNumeric(NUM_NUMERIC_ATTS);
+    test.setClassType(Attribute.NUMERIC);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and generates a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    TestInstances test = new TestInstances();
+    test.setNumNominal(0);
+    test.setNumNumeric(NUM_NUMERIC_ATTS);
+    test.setClassType(Attribute.NUMERIC);
+    m_Instances = test.generate();
+  }
+  
+  /**
+   * performs a test
+   * 
+   * @param algorithm	the algorithm to use
+   */
+  protected void performTest(int algorithm) {
+    Instances icopy = new Instances(m_Instances);
+    
+    m_Filter = getFilter(NUM_ATTS, algorithm);
+    Instances result = useFilter();
+    assertEquals(result.numAttributes(), NUM_ATTS + 1);
+    assertEquals(result.numInstances(), icopy.numInstances());
+    
+    m_Filter = getFilter(NUM_ATTS*2, algorithm);
+    result = useFilter();
+    assertEquals(result.numAttributes(), NUM_ATTS*2 + 1);
+    assertEquals(result.numInstances(), icopy.numInstances());
+  }
+
+  /**
+   * performs a test on PLS1
+   */
+  public void testPLS1() {
+    performTest(PLSFilter.ALGORITHM_PLS1);
+  }
+
+  /**
+   * performs a test on SIMPLS
+   */
+  public void testSIMPLS() {
+    performTest(PLSFilter.ALGORITHM_SIMPLS);
+  }
+
+  public static Test suite() {
+    return new TestSuite(PLSFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/instance/ResampleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/instance/ResampleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/instance/ResampleTest.java	(revision 29)
@@ -0,0 +1,144 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.AttributeStats;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Resample. Run from the command line with:<p>
+ * java weka.filters.supervised.instance.ResampleTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class ResampleTest
+  extends AbstractFilterTest {
+  
+  public ResampleTest(String name) { super(name);  }
+
+  /** Need to set the class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+    m_Instances.setClassIndex(1);
+  }
+
+  /** Creates a default Resample */
+  public Filter getFilter() {
+    Resample f = new Resample();
+    f.setSampleSizePercent(50);
+    return f;
+  }
+
+  public void testSampleSizePercent() {
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 50% of input",
+                 m_Instances.numInstances() / 2,  result.numInstances());
+
+    ((Resample)m_Filter).setSampleSizePercent(200);
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 200% of input",
+                 m_Instances.numInstances() * 2,  result.numInstances());
+  }
+
+  public void testSampleSizePercentNoReplacement() {
+    ((Resample) m_Filter).setSampleSizePercent(20);
+    ((Resample) m_Filter).setNoReplacement(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 20% of input",
+                 (int) (m_Instances.numInstances() * 20.0 / 100),  result.numInstances());
+  }
+
+  public void testSampleSizePercentNoReplacementInverted() {
+    ((Resample) m_Filter).setSampleSizePercent(20);
+    ((Resample) m_Filter).setNoReplacement(true);
+    ((Resample) m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 80% of input (20% inverted)",
+                 m_Instances.numInstances() 
+                 - (int) (m_Instances.numInstances() * 20.0 / 100),  result.numInstances());
+  }
+
+  public void testNoBias() throws Exception {
+    m_Instances.setClassIndex(1);
+    AttributeStats origs = m_Instances.attributeStats(1);
+    assertNotNull(origs.nominalCounts);
+
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    AttributeStats outs = result.attributeStats(1);
+
+    // Check distributions are pretty similar
+    assertNotNull(outs.nominalCounts);
+    assertEquals(origs.nominalCounts.length, outs.nominalCounts.length);
+    for (int i = 0; i < origs.nominalCounts.length; i++) {
+      int est = origs.nominalCounts[i] / 2 - 1;
+      assertTrue("Counts for value:" + i 
+             + " orig:" + origs.nominalCounts[i] 
+             + " out50%:" + outs.nominalCounts[i], 
+             (est <= outs.nominalCounts[i]) &&
+             (outs.nominalCounts[i] <= (est + 3)));
+    }
+  }
+
+  public void testBiasToUniform() throws Exception {
+    m_Instances.setClassIndex(1);
+    AttributeStats origs = m_Instances.attributeStats(1);
+    assertNotNull(origs.nominalCounts);
+    
+    ((Resample)m_Filter).setBiasToUniformClass(1.0);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    AttributeStats outs = result.attributeStats(1);
+
+    // Check distributions are pretty similar
+    assertNotNull(outs.nominalCounts);
+    assertEquals(origs.nominalCounts.length, outs.nominalCounts.length);
+    int est = (origs.totalCount - origs.missingCount) / origs.distinctCount;
+    est = est / 2 - 1;
+    for (int i = 0; i < origs.nominalCounts.length; i++) {
+      assertTrue("Counts for value:" + i 
+             + " orig:" + origs.nominalCounts[i] 
+             + " out50%:" + outs.nominalCounts[i]
+             + " ~wanted:" + est,
+             (est <= outs.nominalCounts[i]) &&
+             (outs.nominalCounts[i] <= (est + 3)));
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(ResampleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SMOTETest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SMOTETest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SMOTETest.java	(revision 29)
@@ -0,0 +1,109 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato Hamilton, New Zealand
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SMOTE. Run from the command line with: <p/>
+ * java weka.filters.supervised.instance.SMOTETest
+ *
+ * @author fracpete (FracPete at waikato dot ac dot nz)
+ * @version $Revision: 4556 $
+ */
+public class SMOTETest
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public SMOTETest(String name) {
+    super(name);
+  }
+
+  /**
+   * Need to set the class index
+   * 
+   * @throws Exception 	if setup fails
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    m_Instances.setClassIndex(1);
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception {
+    TestInstances	test;
+    Instances		result;
+
+    // NB: in order to make sure that the classifier can handle the data,
+    //     we're using the classifier's capabilities to generate the data.
+    test = TestInstances.forCapabilities(
+  	m_FilteredClassifier.getClassifier().getCapabilities());
+    test.setNumInstances(40);
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  /**
+   * Creates a default SMOTE
+   * 
+   * @return		the default filter
+   */
+  public Filter getFilter() {
+    SMOTE f = new SMOTE();
+    return f;
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(SMOTETest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SpreadSubsampleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SpreadSubsampleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/instance/SpreadSubsampleTest.java	(revision 29)
@@ -0,0 +1,114 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.AttributeStats;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SpreadSubsample. Run from the command line with:<p>
+ * java weka.filters.supervised.instance.SpreadSubsampleTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class SpreadSubsampleTest extends AbstractFilterTest {
+
+  private static double TOLERANCE = 0.001;
+
+  public SpreadSubsampleTest(String name) { super(name);  }
+
+  /** Creates a default SpreadSubsample */
+  public Filter getFilter() {
+    SpreadSubsample f = new SpreadSubsample();
+    f.setDistributionSpread(0);
+    return f;
+  }
+
+  /** Remove string attributes from default fixture instances */
+  protected void setUp() throws Exception {
+
+    super.setUp();
+    m_Instances.setClassIndex(1);
+  }
+
+  public void testDistributionSpread() throws Exception {
+    
+    testDistributionSpread_X(1.0);
+    testDistributionSpread_X(2.0);
+    testDistributionSpread_X(3.0);
+  }
+
+  public void testAdjustWeights() {
+
+    ((SpreadSubsample)m_Filter).setAdjustWeights(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    double origWeight = 0;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      origWeight += m_Instances.instance(i).weight();
+    }
+    double outWeight = 0;
+    for (int i = 0; i < result.numInstances(); i++) {
+      outWeight += result.instance(i).weight();
+    }
+    assertEquals(origWeight, outWeight, TOLERANCE);
+  }
+
+  private void testDistributionSpread_X(double factor) throws Exception {
+    AttributeStats origs = m_Instances.attributeStats(1);
+    assertNotNull(origs.nominalCounts);
+    
+    ((SpreadSubsample)m_Filter).setDistributionSpread(factor);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    AttributeStats outs = result.attributeStats(1);
+
+    // Check distributions are pretty similar
+    assertNotNull(outs.nominalCounts);
+    assertEquals(origs.nominalCounts.length, outs.nominalCounts.length);
+    int min = outs.nominalCounts[0];
+    int max = outs.nominalCounts[0];
+    for (int i = 1; i < outs.nominalCounts.length; i++) {
+      if (outs.nominalCounts[i] < min) {
+        min = outs.nominalCounts[i];
+      }
+      if (outs.nominalCounts[i] > max) {
+        max = outs.nominalCounts[i];
+      }
+    }
+    assertTrue(max / factor <= min);
+  }
+
+  public static Test suite() {
+    return new TestSuite(SpreadSubsampleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/supervised/instance/StratifiedRemoveFoldsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/supervised/instance/StratifiedRemoveFoldsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/supervised/instance/StratifiedRemoveFoldsTest.java	(revision 29)
@@ -0,0 +1,75 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.supervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests StratifiedRemoveFolds. Run from the command line with:<p>
+ * java weka.filters.supervised.instance.StratifiedRemoveFoldsTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class StratifiedRemoveFoldsTest extends AbstractFilterTest {
+  
+  public StratifiedRemoveFoldsTest(String name) { super(name);  }
+
+  /** Creates a default StratifiedRemoveFolds */
+  public Filter getFilter() {
+    StratifiedRemoveFolds f = new StratifiedRemoveFolds();
+    return f;
+  }
+
+  /** Remove string attributes from default fixture instances */
+  protected void setUp() throws Exception {
+
+    super.setUp();
+    m_Instances.setClassIndex(1);
+  }
+
+  public void testAllFolds() {
+    
+    int totInstances = 0;
+    for (int i = 0; i < 10; i++) {
+      ((StratifiedRemoveFolds)m_Filter).setFold(i + 1);
+      Instances result = useFilter();
+      assertEquals(m_Instances.numAttributes(), result.numAttributes());
+      totInstances += result.numInstances();
+    }
+    assertEquals("Expecting output number of instances to match",
+                 m_Instances.numInstances(),  totInstances);
+  }
+
+  public static Test suite() {
+    return new TestSuite(StratifiedRemoveFoldsTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddClusterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddClusterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddClusterTest.java	(revision 29)
@@ -0,0 +1,137 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.meta.FilteredClassifier;
+import weka.clusterers.Clusterer;
+import weka.clusterers.EM;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddCluster. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.AddClusterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class AddClusterTest 
+  extends AbstractFilterTest {
+  
+  public AddClusterTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove attributes that are not nominal/numeric */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    // remove attributes that are not nominal/numeric
+    int i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (   (    !m_Instances.attribute(i).isNominal()
+               && !m_Instances.attribute(i).isNumeric() )
+           || m_Instances.attribute(i).isDate() )
+        m_Instances.deleteAttributeAt(i);
+      else
+        i++;
+    }
+  }
+
+  /**
+   * returns a configured cluster algorithm
+   */
+  protected Clusterer getClusterer() {
+    EM c = new EM();
+    try {
+      c.setOptions(new String[0]);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    return c;
+  }
+  
+  /** Creates a default AddCluster, with SimpleKMeans as cluster
+   * @see #getClusterer */
+  public Filter getFilter() {
+    AddCluster f = new AddCluster();
+    f.setClusterer(getClusterer());
+    return f;
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier	result;
+    
+    result = new FilteredClassifier();
+    
+    result.setFilter(getFilter());
+    result.setClassifier(new weka.classifiers.trees.J48());
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setClassType(Attribute.NOMINAL);
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes() + 1, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddClusterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddExpressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddExpressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddExpressionTest.java	(revision 29)
@@ -0,0 +1,287 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddExpression. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.AddExpressionTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class AddExpressionTest extends AbstractFilterTest {
+  
+  private static double EXPR_DELTA = 0.001;
+
+  public AddExpressionTest(String name) { super(name);  }
+
+  /** Creates a default AddExpression */
+  public Filter getFilter() {
+    return new AddExpression();
+  }
+
+  /** Creates a specialized AddExpression */
+  public Filter getFilter(String expr) {
+    AddExpression af = new AddExpression();
+    af.setExpression(expr);
+    return af;
+  }
+
+  public void testAdd() {
+    m_Filter = getFilter("a1+a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   inst.value(0) + inst.value(1), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testSubtract() {
+    m_Filter = getFilter("a1-a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   inst.value(0) - inst.value(1), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testMultiply() {
+    m_Filter = getFilter("a1*a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   inst.value(0) * inst.value(1), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testDivide() {
+    m_Filter = getFilter("a1/a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      if (inst.value(1) == 0) {
+        assertTrue("Instance " + (i + 1) + " should have been ?" , 
+               inst.isMissing(inst.numAttributes() - 1));
+      } else {
+        assertEquals("Instance " + (i + 1),
+                     inst.value(0) / inst.value(1), 
+                     inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+      }
+    }
+  }
+
+  public void testExponent() {
+    m_Filter = getFilter("a1^a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.pow(inst.value(0), inst.value(1)), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testLog() {
+    m_Filter = getFilter("log(a2/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      if (inst.value(1) != 0) {
+        assertEquals("Instance " + (i + 1),
+                     Math.log(inst.value(1)/5), 
+                     inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+      }
+    }
+  }
+
+  public void testCos() {
+    m_Filter = getFilter("cos(a2/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.cos(inst.value(1) / 5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testSin() {
+    m_Filter = getFilter("sin(a2/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.sin(inst.value(1) / 5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testTan() {
+    m_Filter = getFilter("tan(a2/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1) + ": " + inst + "\n",
+                   Math.tan(inst.value(1) / 5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testAbs() {
+    m_Filter = getFilter("abs(a2-a1)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.abs(inst.value(1) - inst.value(0)), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testExp() {
+    m_Filter = getFilter("exp(a2-a1)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.exp(inst.value(1) - inst.value(0)), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testSqrt() {
+    m_Filter = getFilter("sqrt(a2+a1/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.sqrt(inst.value(1) + inst.value(0)/5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testFloor() {
+    m_Filter = getFilter("floor(a2+a1/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.floor(inst.value(1) + inst.value(0)/5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testCeil() {
+    m_Filter = getFilter("ceil(a2*a1/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.ceil(inst.value(1) * inst.value(0)/5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testRint() {
+    m_Filter = getFilter("rint(a2*a1/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      assertEquals("Instance " + (i + 1),
+                   Math.rint(inst.value(1) * inst.value(0)/5), 
+                   inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+    }
+  }
+
+  public void testBracketing() {
+    m_Filter = getFilter("(a3+a4)*((a2-a1)/5)");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      if (inst.isMissing(0) || inst.isMissing(1) ||
+          inst.isMissing(2) || inst.isMissing(3)) {
+        assertTrue("Instance " + (i + 1) + " should have been ?" , 
+               inst.isMissing(inst.numAttributes() - 1));
+      } else {
+        assertEquals("Instance " + (i + 1),
+                     (inst.value(3) + inst.value(2)) * 
+                     ((inst.value(1) - inst.value(0))/5), 
+                     inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+      }
+    }
+  }
+
+  public void testBODMAS() {
+    m_Filter = getFilter("a3+a4*a2-a1/5+a3*a4+a2");
+    Instances result = useFilter();
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance inst = result.instance(i);
+      if (inst.isMissing(0) || inst.isMissing(1) ||
+          inst.isMissing(2) || inst.isMissing(3)) {
+        assertTrue("Instance " + (i + 1) + " should have been ?" , 
+               inst.isMissing(inst.numAttributes() - 1));
+      } else {
+        assertEquals("Instance " + (i + 1),
+                     inst.value(2) + 
+                     (inst.value(3) * inst.value(1)) 
+                     - (inst.value(0)/5)
+                     + (inst.value(3) * inst.value(2))
+                     + inst.value(1), 
+                     inst.value(inst.numAttributes() - 1), EXPR_DELTA);
+      }
+    }
+  }
+
+  public void testAddNamed() {
+    m_Filter = getFilter("a1+a2");
+    String name = "BongoBongo";
+    ((AddExpression)m_Filter).setName(name);
+    Instances result = useFilter();
+    assertEquals(name, result.attribute(result.numAttributes() - 1).name());
+    name = "BongoBongoSecond";
+    ((AddExpression)m_Filter).setName(name);
+    result = useFilter();
+    assertEquals(name, result.attribute(result.numAttributes() - 1).name());
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddExpressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddIDTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddIDTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddIDTest.java	(revision 29)
@@ -0,0 +1,101 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddID. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.AddIDTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class AddIDTest 
+  extends AbstractFilterTest {
+  
+  public AddIDTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default AddID */
+  public Filter getFilter() {
+    return new AddID();
+  }
+
+  /** Creates a specialized AddID */
+  public Filter getFilter(int pos) {
+    AddID af = new AddID();
+    af.setIDIndex("" + (pos + 1));
+    return af;
+  }
+
+  /**
+   * performs the actual test
+   */
+  protected void performTest() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals((icopy.numAttributes() + 1), result.numAttributes());
+    assertEquals(icopy.numInstances(), m_Instances.numInstances());
+  }
+
+  public void testAddFirst() {
+    m_Filter = getFilter(0);
+    testBuffered();
+    performTest();
+  }
+
+  public void testAddLast() {
+    m_Filter = getFilter(m_Instances.numAttributes() - 1);
+    testBuffered();
+    performTest();
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddIDTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddNoiseTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddNoiseTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddNoiseTest.java	(revision 29)
@@ -0,0 +1,117 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.InstanceComparator;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddNoise. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.AddNoiseTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AddNoiseTest 
+  extends AbstractFilterTest {
+
+  /** for comparing the instances */
+  protected InstanceComparator m_Comparator;
+  
+  public AddNoiseTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // class index
+    m_Instances.setClassIndex(1);
+
+    // only nominal attributes
+    int i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (!m_Instances.attribute(i).isNominal())
+        m_Instances.deleteAttributeAt(i);
+      else
+        i++;
+    }
+
+    m_Comparator = new InstanceComparator(true);
+  }
+  
+  /** Creates a default AddNoise */
+  public Filter getFilter() {
+    AddNoise f = new AddNoise();
+    return f;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // at least one instance must be different
+    boolean equal = true;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+    if (equal)
+      fail("No noise added!");
+  }
+
+  public void testNoNoise() {
+    m_Filter = getFilter();
+    ((AddNoise) m_Filter).setPercent(0);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // all instance's must be the same
+    boolean equal = true;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Instances modified!");
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddNoiseTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddTest.java	(revision 29)
@@ -0,0 +1,116 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Add. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.AddTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.6 $
+ */
+public class AddTest extends AbstractFilterTest {
+  
+  public AddTest(String name) { super(name);  }
+
+  /** Creates a default Add */
+  public Filter getFilter() {
+    return new Add();
+  }
+
+  /** Creates a specialized Add */
+  public Filter getFilter(int pos) {
+    Add af = new Add();
+    af.setAttributeIndex("" + (pos + 1));
+    return af;
+  }
+
+  public void testAddFirst() {
+    m_Filter = getFilter(0);
+    testBuffered();
+  }
+
+  public void testAddLast() {
+    m_Filter = getFilter(m_Instances.numAttributes() - 1);
+    testBuffered();
+  }
+
+  /**
+   * Checks the generated attribute type.
+   */
+  protected void testType(int attType) {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+    assertEquals(attType, result.attribute(result.numAttributes() - 1).type());
+  }
+  
+  public void testAddNominal() {
+    m_Filter = getFilter();
+    ((Add)m_Filter).setNominalLabels("hello,there,bob");
+    testBuffered();
+    testType(Attribute.NOMINAL);
+  }
+
+  public void testAddString() {
+    m_Filter = getFilter();
+    ((Add) m_Filter).setAttributeType(new SelectedTag(Attribute.STRING, Add.TAGS_TYPE));
+    testBuffered();
+    testType(Attribute.STRING);
+  }
+
+  public void testAddDate() {
+    m_Filter = getFilter();
+    ((Add) m_Filter).setAttributeType(new SelectedTag(Attribute.DATE, Add.TAGS_TYPE));
+    testBuffered();
+    testType(Attribute.DATE);
+  }
+
+  public static Test suite() {
+    return new TestSuite(AddTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/AddValuesTest.java	(revision 29)
@@ -0,0 +1,214 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests AddValues. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.AddValuesTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class AddValuesTest 
+  extends AbstractFilterTest {
+  
+  /**
+   * initializes the test
+   * 
+   * @param name	the name of the test
+   */
+  public AddValuesTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a default AddValues
+   * 
+   * @return		the configured filter
+   */
+  public Filter getFilter() {
+    AddValues filter = new AddValues();
+    filter.setAttributeIndex("2");
+    return filter;
+  }
+
+  /**
+   * Creates a specialized AddValues
+   * 
+   * @param sorted	whether to sort
+   * @param labels	the labels to add
+   * @return		the configured filter
+   */
+  public Filter getFilter(boolean sorted, String labels) {
+    AddValues filter = new AddValues();
+    filter.setAttributeIndex("2");
+    filter.setSort(sorted);
+    filter.setLabels(labels);
+    return filter;
+  }
+  
+  /**
+   * Compare two datasets to see if they differ.
+   *
+   * @param data1 one set of instances
+   * @param data2 the other set of instances
+   * @throws Exception if the datasets differ
+   */
+  protected void compareDatasets(Instances data1, Instances data2)
+    throws Exception {
+    
+    if (data1.numAttributes() != data2.numAttributes())
+      throw new Exception("number of attributes has changed");
+    
+    if (!(data2.numInstances() == data1.numInstances()))
+      throw new Exception("number of instances has changed");
+
+    for (int i = 0; i < data2.numInstances(); i++) {
+      Instance orig = data1.instance(i);
+      Instance copy = data2.instance(i);
+      for (int j = 0; j < orig.numAttributes(); j++) {
+        if (orig.isMissing(j)) {
+          if (!copy.isMissing(j))
+            throw new Exception("instances have changed");
+        }
+        else if (!orig.toString(j).equals(copy.toString(j))) {
+          throw new Exception("instances have changed");
+        }
+        
+        if (orig.weight() != copy.weight())
+          throw new Exception("instance weights have changed");
+      }
+    }
+  }
+
+  /**
+   * performs the tests with the given filter
+   * 
+   * @param filter	the configured filter to use in the tests
+   */
+  protected void performTest(Filter filter) {
+    m_Filter = filter;
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    
+    try {
+      result = Filter.useFilter(m_Instances, m_Filter);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    try {
+      compareDatasets(icopy, result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Datasets differ: \n" + ex.getMessage());
+    }
+  }
+  
+  /**
+   * tests the default settings
+   */
+  public void testDefault() {
+    performTest(getFilter());
+  }
+  
+  /**
+   * tests the sorting alone
+   */
+  public void testSort() {
+    performTest(getFilter(true, ""));
+  }
+
+  /**
+   * tests the adding of labels alone
+   */
+  public void testLabels() {
+    performTest(getFilter(false, "__blah,__blubber"));
+  }
+
+  /**
+   * tests the sorting and the adding of labels
+   */
+  public void testSortAndLabels() {
+    performTest(getFilter(false, "__blah,__blubber"));
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((AddValues) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+  
+  /**
+   * returns the test suite
+   * 
+   * @return		the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(AddValuesTest.class);
+  }
+
+  /**
+   * for running the test from commandline
+   * 
+   * @param args	the command line arguments - ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CenterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CenterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CenterTest.java	(revision 29)
@@ -0,0 +1,62 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Center. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.CenterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class CenterTest extends AbstractFilterTest {
+  
+  public CenterTest(String name) { 
+    super(name);
+  }
+
+  /** Creates a default Center */
+  public Filter getFilter() {
+    return new Center();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(CenterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ChangeDateFormatTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ChangeDateFormatTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ChangeDateFormatTest.java	(revision 29)
@@ -0,0 +1,147 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.InstanceComparator;
+import weka.core.Instances;
+import weka.core.SparseInstance;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ChangeDateFormat. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.ChangeDateFormatTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ChangeDateFormatTest 
+  extends AbstractFilterTest {
+
+  /** for comparing the instances */
+  protected InstanceComparator m_Comparator;
+  
+  public ChangeDateFormatTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.setClassIndex(1);
+    m_Comparator = new InstanceComparator(true);
+  }
+  
+  /** Creates a default ChangeDateFormat */
+  public Filter getFilter() {
+    ChangeDateFormat f = new ChangeDateFormat();
+    return f;
+  }
+
+  /**
+   * format must be different in precision (e.g., yyyy-MM instead of
+   * yyyy-MM-dd) from the one in "weka.filters.data.FilterTest.arff", otherwise
+   * this test will fail! 
+   * Note: Sparse instances are skipped.
+   */
+  public void testTypical() {
+    m_Filter = getFilter();
+    ((ChangeDateFormat) m_Filter).setDateFormat("yyyy-MM");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // all instance's must be different
+    boolean equal = false;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) == 0) {
+        equal = true;
+        break;
+      }
+    }
+    if (equal)
+      fail("Instances not changed!");
+  }
+
+  /**
+   * format must be the same as in "weka.filters.data.FilterTest.arff",
+   * otherwise this test will fail!
+   * Note: Sparse instances are skipped.
+   */
+  public void testSameFormat() {
+    m_Filter = getFilter();
+    ((ChangeDateFormat) m_Filter).setDateFormat("yyyy-MM-dd");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // all instance's must be the same
+    boolean equal = true;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Instances modified!");
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isDate()) {
+	  ((ChangeDateFormat) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(ChangeDateFormatTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClassAssignerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClassAssignerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClassAssignerTest.java	(revision 29)
@@ -0,0 +1,62 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClassAssigner. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.ClassAssignerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ClassAssignerTest extends AbstractFilterTest {
+  
+  public ClassAssignerTest(String name) { 
+    super(name);
+  }
+
+  /** Creates a default ClassAssigner */
+  public Filter getFilter() {
+    return new ClassAssigner();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClassAssignerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClusterMembershipTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClusterMembershipTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ClusterMembershipTest.java	(revision 29)
@@ -0,0 +1,131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ClusterMembership. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.ClusterMembershipTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.4 $
+ */
+public class ClusterMembershipTest 
+  extends AbstractFilterTest {
+  
+  public ClusterMembershipTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal/numeric attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    // remove attributes that are not nominal/numeric
+    int i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (   (    !m_Instances.attribute(i).isNominal()
+               && !m_Instances.attribute(i).isNumeric() )
+          || m_Instances.attribute(i).isDate() )
+        m_Instances.deleteAttributeAt(i);
+      else
+        i++;
+    }
+
+    // class index
+    m_Instances.setClassIndex(1);
+  }
+  
+  /** Creates a default ClusterMembership */
+  public Filter getFilter() {
+    ClusterMembership f = new ClusterMembership();
+    return f;
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier	result;
+    
+    result = new FilteredClassifier();
+    
+    result.setFilter(getFilter());
+    result.setClassifier(new weka.classifiers.trees.J48());
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  public void testNominal() {
+    m_Filter = getFilter();
+    m_Instances.setClassIndex(1);
+    Instances result = useFilter();
+    // classes must be still the same
+    assertEquals(m_Instances.numClasses(), result.numClasses());
+    // at least one cluster per label besides class
+    assertTrue(result.numAttributes() >= m_Instances.numClasses() + 1);
+  }
+
+  public void testNumeric() {
+    m_Filter = getFilter();
+    m_Instances.setClassIndex(2);
+    Instances result = useFilter();
+    // at least one cluster (only one clusterer is generateed) besides class
+    assertTrue(result.numAttributes() >= 1 + 1);
+  }
+
+  public static Test suite() {
+    return new TestSuite(ClusterMembershipTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CopyTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CopyTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/CopyTest.java	(revision 29)
@@ -0,0 +1,106 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Copy. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.CopyTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class CopyTest extends AbstractFilterTest {
+  
+  public CopyTest(String name) { super(name);  }
+
+  /** Creates a default Copy */
+  public Filter getFilter() {
+    return getFilter("1-3");
+  }
+
+  /** Creates a specialized Copy */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      Copy af = new Copy();
+      af.setAttributeIndices(rangelist);
+      return af;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("1,2");
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(origNum + 2, result.numAttributes());
+    assertTrue(result.attribute(origNum).name().endsWith(m_Instances.attribute(0).name()));
+    assertTrue(result.attribute(origNum + 1).name().endsWith(m_Instances.attribute(1).name()));
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3-4");
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(origNum + 2, result.numAttributes());
+    assertTrue(result.attribute(origNum).name().endsWith(m_Instances.attribute(2).name()));
+    assertTrue(result.attribute(origNum + 1).name().endsWith(m_Instances.attribute(3).name()));
+  }
+
+  public void testNonInverted() {
+    m_Filter = getFilter("1,2");
+    ((Copy)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(origNum + origNum - 2, result.numAttributes());
+    assertTrue(result.attribute(origNum).name().endsWith(m_Instances.attribute(2).name()));
+    assertTrue(result.attribute(origNum + 1).name().endsWith(m_Instances.attribute(3).name()));
+  }
+
+  public void testNonInverted2() {
+    m_Filter = getFilter("first-3");
+    ((Copy)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(origNum + origNum - 3, result.numAttributes());
+    assertTrue(result.attribute(origNum).name().endsWith(m_Instances.attribute(3).name()));
+  }
+
+  public static Test suite() {
+    return new TestSuite(CopyTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/DiscretizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/DiscretizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/DiscretizeTest.java	(revision 29)
@@ -0,0 +1,153 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Discretize. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.DiscretizeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class DiscretizeTest extends AbstractFilterTest {
+  
+  public DiscretizeTest(String name) { super(name);  }
+
+  /** Creates a default Discretize */
+  public Filter getFilter() {
+    Discretize f= new Discretize();
+    return f;
+  }
+
+  /** Creates a specialized Discretize */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      Discretize f = new Discretize();
+      f.setAttributeIndices(rangelist);
+      return f;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("1,2");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    // None of the attributes should have changed, since 1,2 aren't numeric
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+      assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+    }
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3-4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if (i != 2) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(10, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testInverted() {
+    m_Filter = getFilter("1,2");
+    ((Discretize)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if ((i < 2) || !m_Instances.attribute(i).isNumeric()) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(10, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testNonInverted2() {
+    m_Filter = getFilter("first-3");
+    ((Discretize)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      if ((i < 3) || !m_Instances.attribute(i).isNumeric()) {
+        assertEquals(m_Instances.attribute(i).type(), result.attribute(i).type());
+        assertEquals(m_Instances.attribute(i).name(), result.attribute(i).name());
+      } else {
+        assertEquals(Attribute.NOMINAL, result.attribute(i).type());
+        assertEquals(10, result.attribute(i).numValues());
+      }
+    }
+  }
+
+  public void testBins() {
+    m_Filter = getFilter("3");
+    ((Discretize)m_Filter).setBins(5);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(Attribute.NOMINAL, result.attribute(2).type());
+    assertEquals(5, result.attribute(2).numValues());
+
+    ((Discretize)m_Filter).setBins(20);
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(Attribute.NOMINAL, result.attribute(2).type());
+    assertEquals(20, result.attribute(2).numValues());
+  }
+
+  public void testFindNumBins() {
+    m_Filter = getFilter("3");
+    ((Discretize)m_Filter).setFindNumBins(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(Attribute.NOMINAL, result.attribute(2).type());
+    assertTrue(5 >= result.attribute(2).numValues());
+  }
+
+  public static Test suite() {
+    return new TestSuite(DiscretizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/EMImputationTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/EMImputationTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/EMImputationTest.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 Amri Napolitano 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.AttributeStats;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+import weka.filters.unsupervised.attribute.StringToNominal;
+import weka.filters.unsupervised.attribute.NominalToBinary;
+import weka.filters.unsupervised.attribute.Remove;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests EMImputation. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.EMImputationTest
+ *
+ * @author Amri Napolitano
+ * @version $Revision: 5374 $
+ */
+public class EMImputationTest extends AbstractFilterTest {
+  
+  public EMImputationTest(String name) { super(name);  }
+
+  /** Creates a default EMImputation */
+  public Filter getFilter() {
+    return new EMImputation();
+  }
+
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    Instances temp = new Instances(m_Instances);
+    for (int j = 0; j < 2; j++) {
+      for (int i = 0; i < temp.numInstances(); i++) {
+        m_Instances.add(temp.instance(i));
+      }
+    }
+
+    // now just filter the instances to convert String attributes
+    // and binarize nominal attributes
+    StringToNominal stn = new StringToNominal();
+    stn.setAttributeRange("first-last");
+    stn.setInputFormat(m_Instances);
+    m_Instances = Filter.useFilter(m_Instances, stn);
+    NominalToBinary ntb = new NominalToBinary();
+    ntb.setInputFormat(m_Instances);
+    m_Instances = Filter.useFilter(m_Instances, ntb);
+
+    // remove the last column (date attribute)
+    Remove r = new Remove();
+    r.setAttributeIndices("last");
+    r.setInputFormat(m_Instances);
+    m_Instances = Filter.useFilter(m_Instances, r);
+  }
+
+  protected Instances getFilteredClassifierData() throws Exception {
+    TestInstances test;
+    Instances result;
+    
+    test = TestInstances.forCapabilities(getFilter().getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+    test.setNumNumeric(3);
+
+    result = test.generate();
+
+    return result;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    // Number of instances may change (if an instance has all missing values)
+    // assertEquals(m_Instances.numInstances(), result.numInstances());
+    for (int j = 0; j < result.numAttributes(); j++) {
+      if (j == m_Instances.classIndex() && m_Instances.attribute(j).isNumeric() == false) {
+        continue;
+      }
+      AttributeStats currentStats = m_Instances.attributeStats(j);
+      if (currentStats.distinctCount < 2) {
+        continue;
+      }
+      assertTrue("All missing values except for those in nonnumeric class " +
+                  "attributes should be replaced.", 
+                  result.attributeStats(j).missingCount == 0);
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(EMImputationTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/FirstOrderTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/FirstOrderTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/FirstOrderTest.java	(revision 29)
@@ -0,0 +1,129 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests FirstOrder. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.FirstOrderTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class FirstOrderTest extends AbstractFilterTest {
+  
+  private static double EXPR_DELTA = 0.001;
+
+  public FirstOrderTest(String name) { super(name);  }
+
+  /** Creates a default FirstOrder */
+  public Filter getFilter() {
+    return getFilter("6,3");
+  }
+
+  /** Creates a specialized FirstOrder */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      FirstOrder af = new FirstOrder();
+      af.setAttributeIndices(rangelist);
+      return af;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = new TestInstances();
+    test.setNumNominal(0);
+    test.setNumNumeric(6);
+    test.setClassType(Attribute.NOMINAL);
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("6,3");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes() - 1, result.numAttributes());
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance orig = m_Instances.instance(i);
+      if (orig.isMissing(5) || orig.isMissing(2)) {
+        assertTrue("Instance " + (i + 1) + " should have been ?" , 
+               result.instance(i).isMissing(4));
+      } else {
+        assertEquals(orig.value(5) - orig.value(2), 
+                     result.instance(i).value(4), 
+                     EXPR_DELTA);
+      }
+    }
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3,6");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes() - 1, result.numAttributes());
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance orig = m_Instances.instance(i);
+      if (orig.isMissing(5) || orig.isMissing(2)) {
+        assertTrue("Instance " + (i + 1) + " should have been ?" , 
+               result.instance(i).isMissing(4));
+      } else {
+        assertEquals(orig.value(5) - orig.value(2), 
+                     result.instance(i).value(4), 
+                     EXPR_DELTA);
+      }
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(FirstOrderTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/InterquartileRangeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/InterquartileRangeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/InterquartileRangeTest.java	(revision 29)
@@ -0,0 +1,267 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests InterquartileRange. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.InterquartileRangeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5717 $
+ */
+public class InterquartileRangeTest
+  extends AbstractFilterTest {
+
+  /**
+   * Initializes the test with the given name.
+   * 
+   * @param name	the name of the test
+   */
+  public InterquartileRangeTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a default InterquartileRange.
+   * 
+   * @return		the filter
+   */
+  public Filter getFilter() {
+    return new InterquartileRange();
+  }
+
+  /**
+   * a typical test, with nominal class attribute.
+   */
+  public void testNominalClass() {
+    // run filter
+    m_Instances.setClassIndex(1);
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    assertEquals(icopy.numAttributes() + 2, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * a typical test, with nominal class attribute and on a single numeric
+   * attribute.
+   */
+  public void testNominalClassSingleAttribute() {
+    // run filter
+    m_Instances.setClassIndex(1);
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    ((InterquartileRange) m_Filter).setAttributeIndices("3");
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    assertEquals(icopy.numAttributes() + 2, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * a typical test, with numeric class attribute.
+   */
+  public void testNumericClass() {
+    // run filter
+    m_Instances.setClassIndex(2);
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    assertEquals(icopy.numAttributes() + 2, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * a typical test, w/o class attribute.
+   */
+  public void testWithoutClass() {
+    // run filter
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    assertEquals(icopy.numAttributes() + 2, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * a typical test, w/o class attribute but with Outlier/ExtremeValue
+   * generation per attribute.
+   */
+  public void testPerAttribute() {
+    // parameters
+    ((InterquartileRange) m_Filter).setDetectionPerAttribute(true);
+    
+    // run filter
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    int count = 0;
+    for (int i = 0; i < icopy.numAttributes(); i++) {
+      if (icopy.attribute(i).isNumeric())
+	count++;
+    }
+    assertEquals(icopy.numAttributes() + 2*count, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * a typical test, w/o class attribute but with Outlier/ExtremeValue
+   * generation per attribute and Offset multiplier generation.
+   */
+  public void testOffset() {
+    // parameters
+    ((InterquartileRange) m_Filter).setDetectionPerAttribute(true);
+    ((InterquartileRange) m_Filter).setOutputOffsetMultiplier(true);
+
+    // run filter
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    // test
+    int count = 0;
+    for (int i = 0; i < icopy.numAttributes(); i++) {
+      if (icopy.attribute(i).isNumeric())
+	count++;
+    }
+    assertEquals(icopy.numAttributes() + 3*count, result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the suite
+   */
+  public static Test suite() {
+    return new TestSuite(InterquartileRangeTest.class);
+  }
+
+  /**
+   * Runs the test from the commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/KernelFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/KernelFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/KernelFilterTest.java	(revision 29)
@@ -0,0 +1,121 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests KernelFilter. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.KernelFilterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class KernelFilterTest 
+  extends AbstractFilterTest {
+  
+  public KernelFilterTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default KernelFilter */
+  public Filter getFilter() {
+    return new KernelFilter();
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier	result;
+    
+    result = new FilteredClassifier();
+    
+    result.setFilter(getFilter());
+    result.setClassifier(new weka.classifiers.trees.M5P());
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    TestInstances test = new TestInstances();
+    test.setNumNominal(0);
+    test.setNumNumeric(2);
+    test.setClassType(Attribute.NUMERIC);
+    m_Instances = test.generate();
+  }
+
+  /**
+   * performs a typical test
+   */
+  public void testTypical() {
+    Instances icopy = new Instances(m_Instances);
+    
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(result.numAttributes(), icopy.numInstances() + 1);
+  }
+
+  public static Test suite() {
+    return new TestSuite(KernelFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MILESFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MILESFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MILESFilterTest.java	(revision 29)
@@ -0,0 +1,97 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MILESFilter. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.MILESFilterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5697 $
+ */
+public class MILESFilterTest
+  extends AbstractFilterTest {
+  
+  public MILESFilterTest(String name) { 
+    super(name);
+  }
+
+  /**
+   * Create a multi-instance dataset to work with
+   */
+  protected void setUp() throws Exception {
+    TestInstances	test;
+
+    super.setUp();
+
+    test = new TestInstances();
+    
+    test.setNumNominal(1);
+    test.setNumNominalValues(10);
+    test.setNumRelational(1);
+    test.setNumString(0);
+    test.setNumRelationalDate(2);
+    test.setNumRelationalNominal(2);
+    test.setNumRelationalNumeric(2);
+    test.setNumRelationalString(0);
+    test.setNumRelationalNominalValues(10);
+
+    m_Instances = test.generate();
+  }
+
+  /** Creates a default MILESFilter */
+  public Filter getFilter() {
+    return new MILESFilter();
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception {
+    return m_Instances;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+  
+  public static Test suite() {
+    return new TestSuite(MILESFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MakeIndicatorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MakeIndicatorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MakeIndicatorTest.java	(revision 29)
@@ -0,0 +1,158 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MakeIndicator. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.MakeIndicatorTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.5 $
+ */
+public class MakeIndicatorTest extends AbstractFilterTest {
+  
+  public MakeIndicatorTest(String name) { super(name);  }
+
+  /** Creates an example MakeIndicator */
+  public Filter getFilter() {
+    MakeIndicator f = new MakeIndicator();
+    // Ensure the filter we return can run on the test dataset
+    f.setAttributeIndex("2"); 
+    return f;
+  }
+
+
+  public void testInvalidAttributeTypes() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((MakeIndicator)m_Filter).setAttributeIndex("1");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+    try {
+      ((MakeIndicator)m_Filter).setAttributeIndex("3");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception indicating a NUMERIC attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  public void testDefault() {
+    ((MakeIndicator)m_Filter).setAttributeIndex("2");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that default attribute type is numeric
+    assertEquals("Default attribute encoding should be NUMERIC",
+                 Attribute.NUMERIC, result.attribute(1).type());
+    // Check that default indication is correct
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Checking indicator for instance: " + (i + 1),
+             (m_Instances.instance(i).value(1) == 2) ==
+             (result.instance(i).value(1) == 1));
+    }
+  }
+
+  public void testNominalEncoding() {
+    ((MakeIndicator)m_Filter).setAttributeIndex("2");
+    ((MakeIndicator)m_Filter).setNumeric(false);    
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that default attribute type is numeric
+    assertEquals("New attribute encoding should be NOMINAL",
+                 Attribute.NOMINAL, result.attribute(1).type());
+    // Check that default indication is correct
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Checking indicator for instance: " + (i + 1),
+             (m_Instances.instance(i).value(1) == 2) ==
+             (result.instance(i).value(1) == 1));
+    }
+  }
+
+  public void testMultiValueIndication() {
+    ((MakeIndicator)m_Filter).setAttributeIndex("2");
+    try {
+      ((MakeIndicator)m_Filter).setValueIndices("1,3");
+    } catch (Exception ex) {
+      fail("Is Range broken?");
+    }
+    ((MakeIndicator)m_Filter).setNumeric(false);    
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that default attribute type is numeric
+    assertEquals("New attribute encoding should be NOMINAL",
+                 Attribute.NOMINAL, result.attribute(1).type());
+    // Check that default indication is correct
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Checking indicator for instance: " + (i + 1),
+             ((m_Instances.instance(i).value(1) == 0) ||
+              (m_Instances.instance(i).value(1) == 2)) 
+             ==
+             (result.instance(i).value(1) == 1));
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((MakeIndicator) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MakeIndicatorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MathExpressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MathExpressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MathExpressionTest.java	(revision 29)
@@ -0,0 +1,374 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.SparseInstance;
+import weka.core.Utils;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MathExpression. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.MathExpressionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MathExpressionTest 
+  extends AbstractFilterTest {
+  
+  /** the attribute to work on */
+  protected int m_AttIndex = 2;
+  
+  public MathExpressionTest(String name) { 
+    super(name);  
+  }
+  
+  /** Creates a MathExpression with the default expression */
+  public Filter getFilter() {
+    return getFilter(new MathExpression().getExpression());
+  }
+  
+  /** Creates a MathExpression filter with the given expression */
+  protected Filter getFilter(String expression) {
+    MathExpression f = new MathExpression();
+    f.setExpression(expression);
+    f.setIgnoreRange("" + (m_AttIndex + 1));
+    f.setInvertSelection(true);
+    return f;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * checks a certain statistic
+   * @param expr the filter expression
+   * @param stats the value of the corresponding attribute statistics
+   */
+  protected void checkStatistics(String expr, double stats) {
+    m_Filter = getFilter(expr);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check statistics
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (!Utils.eq(stats, result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter and Attribute statistics differ ('" + expr + "')!");
+  }
+
+  /**
+   * checks the statistics of the attribute
+   */
+  public void testStats() {
+    checkStatistics(
+        "MIN", m_Instances.attributeStats(m_AttIndex).numericStats.min);
+    checkStatistics(
+        "MAX", m_Instances.attributeStats(m_AttIndex).numericStats.max);
+    checkStatistics(
+        "MEAN", m_Instances.attributeStats(m_AttIndex).numericStats.mean);
+    checkStatistics(
+        "SD", m_Instances.attributeStats(m_AttIndex).numericStats.stdDev);
+    checkStatistics(
+        "COUNT", m_Instances.attributeStats(m_AttIndex).numericStats.count);
+    checkStatistics(
+        "SUM", m_Instances.attributeStats(m_AttIndex).numericStats.sum);
+    checkStatistics(
+        "SUMSQUARED", m_Instances.attributeStats(m_AttIndex).numericStats.sumSq);
+  }
+
+  /**
+   * checks whether attribute value stays the same
+   */
+  public void testEquality() {
+    m_Filter = getFilter("A");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            m_Instances.instance(i).value(m_AttIndex), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter modifies attribute values)!");
+  }
+
+  public void testAbs() {
+    m_Filter = getFilter("abs(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.abs(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testsqrt() {
+    m_Filter = getFilter("sqrt(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.sqrt(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testLog() {
+    m_Filter = getFilter("log(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.log(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testExp() {
+    m_Filter = getFilter("exp(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.exp(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testSin() {
+    m_Filter = getFilter("sin(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.sin(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testCos() {
+    m_Filter = getFilter("cos(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.cos(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testTan() {
+    m_Filter = getFilter("tan(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.tan(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testRint() {
+    m_Filter = getFilter("rint(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.rint(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testFloor() {
+    m_Filter = getFilter("floor(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.floor(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testPow2() {
+    m_Filter = getFilter("pow(A,2)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.pow(m_Instances.instance(i).value(m_AttIndex), 2), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+
+  public void testCeil() {
+    m_Filter = getFilter("ceil(A)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // check equality
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i) instanceof SparseInstance)
+        continue;
+      if (!Utils.eq(
+            Math.ceil(m_Instances.instance(i).value(m_AttIndex)), 
+            result.instance(i).value(m_AttIndex))) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal)
+      fail("Filter produces different result)!");
+  }
+  
+  public static Test suite() {
+    return new TestSuite(MathExpressionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeManyValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeManyValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeManyValuesTest.java	(revision 29)
@@ -0,0 +1,211 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MergeManyValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.MergeManyValuesTest
+ *
+ * @author Len Trigg (original MergeTwoValues code)
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5794 $
+ */
+public class MergeManyValuesTest
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public MergeManyValuesTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Creates an default MergeManyValues.
+   * 
+   * @return		the default filter.
+   */
+  public Filter getFilter() {
+    MergeManyValues f = new MergeManyValues();
+    // Ensure the filter we return can run on the test dataset
+    f.setAttributeIndex("2"); 
+    return f;
+  }
+
+  /**
+   * Tests invalid attribute types.
+   */
+  public void testInvalidAttributeTypes() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((MergeManyValues)m_Filter).setAttributeIndex("1");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+    try {
+      ((MergeManyValues)m_Filter).setAttributeIndex("3");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception indicating a NUMERIC attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  /**
+   * Tests default setup.
+   */
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if ((m_Instances.instance(i).value(1) == 0) || 
+          (m_Instances.instance(i).value(1) == 1)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(1);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(1));
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests a different range.
+   */
+  public void testDifferentRange() {
+    ((MergeManyValues)m_Filter).setMergeValueRange("2,3");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if ((m_Instances.instance(i).value(1) == 1) || 
+          (m_Instances.instance(i).value(1) == 2)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(1);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(1));
+        }
+      }
+    }
+  }
+
+  /**
+   * Test merging all labels.
+   */
+  public void testMergeAll() {
+    ((MergeManyValues)m_Filter).setMergeValueRange("first-last");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    assertEquals(1, result.attribute(1).numValues());
+  }
+
+  /**
+   * Tests attribute with missing values.
+   */
+  public void testAttributeWithMissing() {
+    ((MergeManyValues)m_Filter).setAttributeIndex("5");
+    ((MergeManyValues)m_Filter).setMergeValueRange("1,2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).isMissing(4)) {
+        assertTrue("Missing in input should give missing in result",
+               result.instance(i).isMissing(4));
+      } else if ((m_Instances.instance(i).value(4) == 0) || 
+                 (m_Instances.instance(i).value(4) == 1)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(4);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(4));
+        }
+      }
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((MergeManyValues) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the suite
+   */
+  public static Test suite() {
+    return new TestSuite(MergeManyValuesTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeTwoValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeTwoValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MergeTwoValuesTest.java	(revision 29)
@@ -0,0 +1,187 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MergeTwoValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.MergeTwoValuesTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.5 $
+ */
+public class MergeTwoValuesTest extends AbstractFilterTest {
+  
+  public MergeTwoValuesTest(String name) { super(name);  }
+
+  /** Creates an example MergeTwoValues */
+  public Filter getFilter() {
+    MergeTwoValues f = new MergeTwoValues();
+    // Ensure the filter we return can run on the test dataset
+    f.setAttributeIndex("2"); 
+    return f;
+  }
+
+  public void testInvalidAttributeTypes() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((MergeTwoValues)m_Filter).setAttributeIndex("1");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+    try {
+      ((MergeTwoValues)m_Filter).setAttributeIndex("3");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception indicating a NUMERIC attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if ((m_Instances.instance(i).value(1) == 0) || 
+          (m_Instances.instance(i).value(1) == 2)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(1);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(1));
+        }
+      }
+    }
+  }
+
+  public void testFirstValueIndex() {
+    ((MergeTwoValues)m_Filter).setFirstValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if ((m_Instances.instance(i).value(1) == 1) || 
+          (m_Instances.instance(i).value(1) == 2)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(1);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(1));
+        }
+      }
+    }
+  }
+
+  public void testSecondValueIndex() {
+    ((MergeTwoValues)m_Filter).setSecondValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if ((m_Instances.instance(i).value(1) == 0) || 
+          (m_Instances.instance(i).value(1) == 1)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(1);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(1));
+        }
+      }
+    }
+  }
+
+  public void testAttributeWithMissing() {
+    ((MergeTwoValues)m_Filter).setAttributeIndex("5");
+    ((MergeTwoValues)m_Filter).setFirstValueIndex("1");
+    ((MergeTwoValues)m_Filter).setSecondValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the merging is correct
+    int mergedIndex = -1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).isMissing(4)) {
+        assertTrue("Missing in input should give missing in result",
+               result.instance(i).isMissing(4));
+      } else if ((m_Instances.instance(i).value(4) == 0) || 
+                 (m_Instances.instance(i).value(4) == 1)) {
+        if (mergedIndex == -1) {
+          mergedIndex = (int)result.instance(i).value(4);
+        } else {
+          assertEquals("Checking merged value for instance: " + (i + 1),
+                       mergedIndex, (int)result.instance(i).value(4));
+        }
+      }
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((MergeTwoValues) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(MergeTwoValuesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositionalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositionalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/MultiInstanceToPropositionalTest.java	(revision 29)
@@ -0,0 +1,137 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.meta.FilteredClassifier;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests MultiInstanceToPropositional. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.MultiInstanceToPropositionalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class MultiInstanceToPropositionalTest 
+  extends AbstractFilterTest {
+  
+  public MultiInstanceToPropositionalTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default MultiInstanceToPropositional */
+  public Filter getFilter() {
+    return new MultiInstanceToPropositional();
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier	result;
+    
+    result = new FilteredClassifier();
+    
+    result.setFilter(getFilter());
+    result.setClassifier(new weka.classifiers.rules.ZeroR());
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    TestInstances test = new TestInstances();
+    test.setNumNominal(1);
+    test.setClassType(Attribute.NOMINAL);
+    test.setMultiInstance(true);
+    m_Instances = test.generate();
+  }
+
+  /**
+   * performs a typical test
+   */
+  public void testTypical() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = useFilter();
+    // # of instances
+    int count = 0;
+    for (int i = 0; i < icopy.numInstances(); i++)
+      count += icopy.instance(i).relationalValue(1).numInstances();
+    assertEquals(result.numInstances(), count);
+    // # of attributes
+    count =   icopy.numAttributes() 
+            + icopy.attribute(1).relation().numAttributes()
+            - 1;
+    assertEquals(result.numAttributes(), count);
+  }
+  
+  /**
+   * filter cannot be used in conjunction with the FilteredClassifier, since
+   * it can produce more than one instance out of a bag. This will of course 
+   * not with the distributionForInstance/classifyInstance methods.
+   */
+  public void testFilteredClassifier() {
+    // nothing
+  }
+
+  public static Test suite() {
+    return new TestSuite(MultiInstanceToPropositionalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToBinaryTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToBinaryTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToBinaryTest.java	(revision 29)
@@ -0,0 +1,64 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NominalToBinary. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NominalToBinaryTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NominalToBinaryTest extends AbstractFilterTest {
+  
+  public NominalToBinaryTest(String name) { super(name);  }
+
+  /** Creates an example NominalToBinary */
+  public Filter getFilter() {
+    NominalToBinary f = new NominalToBinary();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes() + 5, result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Eibe can enhance this to check the binarizing is correct.
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(NominalToBinaryTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToStringTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToStringTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NominalToStringTest.java	(revision 29)
@@ -0,0 +1,150 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.classifiers.meta.FilteredClassifier;
+import weka.classifiers.rules.ZeroR;
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NominalToString. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.NominalToStringTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NominalToStringTest
+  extends AbstractFilterTest {
+  
+  public NominalToStringTest(String name) {
+    super(name);
+  }
+
+  /** Creates an example NominalToString */
+  public Filter getFilter() {
+    NominalToString f = new NominalToString();
+    f.setAttributeIndexes("2");
+    return f;
+  }
+
+  /**
+   * returns the configured FilteredClassifier. Since the base classifier is
+   * determined heuristically, derived tests might need to adjust it.
+   * 
+   * @return the configured FilteredClassifier
+   */
+  protected FilteredClassifier getFilteredClassifier() {
+    FilteredClassifier 	result;
+    
+    result = super.getFilteredClassifier();
+    ((NominalToString) result.getFilter()).setAttributeIndexes("1");
+    result.setClassifier(new ZeroR());
+    
+    return result;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setNumRelational(0);
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    assertEquals("Attribute type should now be STRING",
+                 Attribute.STRING, result.attribute(1).type());
+
+    assertEquals(3, result.attribute(1).numValues());
+  }
+
+  public void testMissing() {
+    ((NominalToString)m_Filter).setAttributeIndexes("5");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    assertEquals("Attribute type should now be STRING",
+                 Attribute.STRING, result.attribute(4).type());
+
+    assertEquals(4, result.attribute(4).numValues());
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Missing values should be preserved",
+             m_Instances.instance(i).isMissing(4) ==
+             result.instance(i).isMissing(4));
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((NominalToString) m_FilteredClassifier.getFilter()).setAttributeIndexes(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(NominalToStringTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NormalizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NormalizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NormalizeTest.java	(revision 29)
@@ -0,0 +1,76 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Normalize. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NormalizeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NormalizeTest extends AbstractFilterTest {
+  
+  public NormalizeTest(String name) { super(name);  }
+
+  /** Creates an example Normalize */
+  public Filter getFilter() {
+    Normalize f = new Normalize();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+
+    // Check conversion is OK
+    for (int j = 0; j < result.numAttributes(); j++) {
+      if (result.attribute(j).isNumeric()) {
+        for (int i = 0; i < result.numInstances(); i++) {
+          if (!result.instance(i).isMissing(j)) {
+            assertTrue("Value should be between 0 and 1",
+                   (result.instance(i).value(j) >= 0) &&
+                   (result.instance(i).value(j) <= 1));
+          }
+        }
+      }
+    }
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(NormalizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericCleanerTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericCleanerTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericCleanerTest.java	(revision 29)
@@ -0,0 +1,82 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NumericCleaner. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.NumericCleanerTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class NumericCleanerTest 
+  extends AbstractFilterTest {
+  
+  public NumericCleanerTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default NumericCleaner */
+  public Filter getFilter() {
+    return new NumericCleaner();
+  }
+
+  /**
+   * runs a simple test
+   */
+  public void testTypical() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals(icopy.numAttributes(), result.numAttributes());
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(NumericCleanerTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToBinaryTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToBinaryTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToBinaryTest.java	(revision 29)
@@ -0,0 +1,82 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NumericToBinary. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NumericToBinaryTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NumericToBinaryTest extends AbstractFilterTest {
+  
+  public NumericToBinaryTest(String name) { super(name);  }
+
+  /** Creates an example NumericToBinary */
+  public Filter getFilter() {
+    NumericToBinary f = new NumericToBinary();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+
+    // Check conversion is OK
+    for (int j = 0; j < result.numAttributes(); j++) {
+      if (m_Instances.attribute(j).isNumeric()) {
+        assertTrue("Numeric attribute should now be nominal",
+               result.attribute(j).isNominal());
+        for (int i = 0; i < result.numInstances(); i++) {
+          if (m_Instances.instance(i).isMissing(j)) {
+            assertTrue(result.instance(i).isMissing(j));
+          } else if (m_Instances.instance(i).value(j) == 0) {
+            assertTrue("Output value should be 0", 
+                   result.instance(i).value(j) == 0);
+          } else {
+            assertTrue("Output value should be 1", 
+                   result.instance(i).value(j) == 1);
+          }
+        }
+      }
+    }
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(NumericToBinaryTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToNominalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToNominalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericToNominalTest.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NumericToNominal. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NumericToNominalTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NumericToNominalTest extends AbstractFilterTest {
+  
+  public NumericToNominalTest(String name) { 
+    super(name);
+  }
+
+  /** Creates a default NumericToNominal */
+  public Filter getFilter() {
+    return new NumericToNominal();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // no date and numeric attributes should remain
+    if (result.checkForAttributeType(Attribute.DATE))
+      fail("Date attribute(s) left over!");
+    if (result.checkForAttributeType(Attribute.NUMERIC))
+      fail("Numeric attribute(s) left over!");
+  }
+
+  public static Test suite() {
+    return new TestSuite(NumericToNominalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericTransformTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericTransformTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/NumericTransformTest.java	(revision 29)
@@ -0,0 +1,145 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NumericTransform. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NumericTransformTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NumericTransformTest extends AbstractFilterTest {
+
+  /** Tolerance allowed in double comparisons */
+  private static final double TOLERANCE = 0.001;
+
+  public NumericTransformTest(String name) { super(name);  }
+
+  /** Creates a default NumericTransform */
+  public Filter getFilter() {
+    return getFilter("first-last");
+  }
+
+  /** Creates a specialized NumericTransform */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      NumericTransform af = new NumericTransform();
+      af.setAttributeIndices(rangelist);
+      return af;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+
+  public void testDefault() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // Check conversion is OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if (m_Instances.instance(i).isMissing(j)) {
+          assertTrue(result.instance(i).isMissing(j));
+        } else if (result.attribute(j).isNumeric()) {
+          assertEquals("Value should be same as Math.abs()",
+                       Math.abs(m_Instances.instance(i).value(j)),
+                       result.instance(i).value(j), TOLERANCE);
+        } else {
+          assertEquals("Value shouldn't have changed",
+                       m_Instances.instance(i).value(j),
+                       result.instance(i).value(j), TOLERANCE);
+        }
+      }
+    }    
+  }
+
+  public void testInverted() {
+    m_Filter = getFilter("1-3");
+    ((NumericTransform)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // Check conversion is OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if (m_Instances.instance(i).isMissing(j)) {
+          assertTrue(result.instance(i).isMissing(j));
+        } else if (result.attribute(j).isNumeric() && (j >=3)) {
+          assertEquals("Value should be same as Math.abs()",
+                       Math.abs(m_Instances.instance(i).value(j)),
+                       result.instance(i).value(j), TOLERANCE);
+        } else {
+          assertEquals("Value shouldn't have changed",
+                       m_Instances.instance(i).value(j),
+                       result.instance(i).value(j), TOLERANCE);
+        }
+      }
+    }
+  }
+
+  public void testClassNameAndMethodName() throws Exception {
+    ((NumericTransform)m_Filter).setClassName("java.lang.Math");
+    ((NumericTransform)m_Filter).setMethodName("rint");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // Check conversion is OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if (m_Instances.instance(i).isMissing(j)) {
+          assertTrue(result.instance(i).isMissing(j));
+        } else if (result.attribute(j).isNumeric()) {
+          assertEquals("Value should be same as Math.rint()",
+                       Math.rint(m_Instances.instance(i).value(j)),
+                       result.instance(i).value(j), TOLERANCE);
+        } else {
+          assertEquals("Value shouldn't have changed",
+                       m_Instances.instance(i).value(j),
+                       result.instance(i).value(j), TOLERANCE);
+        }
+      }
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(NumericTransformTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ObfuscateTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ObfuscateTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ObfuscateTest.java	(revision 29)
@@ -0,0 +1,80 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Obfuscate. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.ObfuscateTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class ObfuscateTest extends AbstractFilterTest {
+  
+  public ObfuscateTest(String name) { super(name);  }
+
+  /** Creates a default Obfuscate */
+  public Filter getFilter() {
+    return new Obfuscate();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    
+    assertTrue(!m_Instances.relationName().equals(result.relationName()));
+    for (int i = 0; i < m_Instances.numAttributes(); i++) {
+      Attribute inatt = m_Instances.attribute(i);
+      Attribute outatt = result.attribute(i);
+      if (!inatt.isString() && !inatt.isDate()) {
+        assertTrue("Attribute names should be changed",
+               !inatt.name().equals(outatt.name()));
+        if (inatt.isNominal()) {
+          assertEquals("Number of nominal values shouldn't change",
+                       inatt.numValues(), outatt.numValues());
+          for (int j = 0; j < inatt.numValues(); j++) {
+            assertTrue("Nominal labels should be changed",
+                   !inatt.value(j).equals(outatt.value(j)));
+          }
+        }
+      }
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(ObfuscateTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PKIDiscretizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PKIDiscretizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PKIDiscretizeTest.java	(revision 29)
@@ -0,0 +1,78 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PKIDiscretize. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.PKIDiscretizeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class PKIDiscretizeTest 
+  extends AbstractFilterTest {
+
+  /** the attribute to discretize */
+  protected int m_AttIndex;
+  
+  public PKIDiscretizeTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.setClassIndex(1);
+    m_AttIndex = 2;
+  }
+  
+  /** Creates a default PKIDiscretize */
+  public Filter getFilter() {
+    PKIDiscretize f = new PKIDiscretize();
+    f.setAttributeIndicesArray(new int[]{m_AttIndex});
+    return f;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    // the discretized attribute must be nominal
+    assertTrue(result.attribute(m_AttIndex).isNominal());
+  }
+
+  public static Test suite() {
+    return new TestSuite(PKIDiscretizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PartitionedMultiFilterTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PartitionedMultiFilterTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PartitionedMultiFilterTest.java	(revision 29)
@@ -0,0 +1,182 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.Range;
+import weka.filters.AbstractFilterTest;
+import weka.filters.AllFilter;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PartitionedMultiFilter. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.PartitionedMultiFilterTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class PartitionedMultiFilterTest 
+  extends AbstractFilterTest {
+  
+  public PartitionedMultiFilterTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Instances.deleteAttributeType(Attribute.STRING);
+    m_Instances.deleteAttributeType(Attribute.RELATIONAL);
+  }
+
+  /** Creates a default PartitionedMultiFilter */
+  public Filter getFilter() {
+    return new PartitionedMultiFilter();
+  }
+  
+  /**
+   * performs the actual test
+   * 
+   * @param filters	the filters to use
+   * @param ranges	the ranges to use
+   * @param remove	whether to remove unused attributes or not
+   * @return		the processed dataset
+   * @throws Exception	if apllying of filter fails
+   */
+  protected Instances applyFilter(Filter[] filters, Range[] ranges, boolean remove)
+    throws Exception {
+    
+    PartitionedMultiFilter	filter;
+    Instances			result;
+    
+    filter = (PartitionedMultiFilter) getFilter();
+    filter.setFilters(filters);
+    filter.setRanges(ranges);
+    filter.setRemoveUnused(remove);
+    filter.setInputFormat(m_Instances);
+    
+    result = Filter.useFilter(m_Instances, filter);
+    
+    return result;
+  }
+  
+  /**
+   * tests two filters with disjoint ranges
+   */
+  public void testDisjoint() {
+    Instances result = null;
+    m_Instances.setClassIndex(2);
+    
+    try {
+      result = applyFilter(
+	  new Filter[]{new AllFilter(), new AllFilter()},
+	  new Range[]{new Range("1-2"),new Range("4-5")},
+	  false);
+    }
+    catch (Exception e) {
+      fail("Problem applying the filter: " + e);
+    }
+    
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+  }
+  
+  /**
+   * tests two filters with disjoint ranges and removing the unused attributes
+   */
+  public void testDisjointRemoveUnused() {
+    Instances result = null;
+    m_Instances.setClassIndex(2);
+    
+    try {
+      result = applyFilter(
+	  new Filter[]{new AllFilter(), new AllFilter()},
+	  new Range[]{new Range("1-2"),new Range("5")},
+	  true);
+    }
+    catch (Exception e) {
+      fail("Problem applying the filter: " + e);
+    }
+    
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    assertEquals(m_Instances.numAttributes() - 1, result.numAttributes());
+  }
+  
+  /**
+   * tests two filters with overlapping ranges
+   */
+  public void testOverlapping() {
+    Instances result = null;
+    m_Instances.setClassIndex(2);
+    
+    try {
+      result = applyFilter(
+	  new Filter[]{new AllFilter(), new AllFilter()},
+	  new Range[]{new Range("1,2,4"),new Range("2,4")},
+	  false);
+    }
+    catch (Exception e) {
+      fail("Problem applying the filter: " + e);
+    }
+    
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    assertEquals(m_Instances.numAttributes() + 2, result.numAttributes());
+  }
+  
+  /**
+   * tests two filters with overlapping ranges and removing the unused attributes
+   */
+  public void testOverlappingRemoveUnused() {
+    Instances result = null;
+    m_Instances.setClassIndex(2);
+    
+    try {
+      result = applyFilter(
+	  new Filter[]{new AllFilter(), new AllFilter()},
+	  new Range[]{new Range("1,2,4"),new Range("2,4")},
+	  true);
+    }
+    catch (Exception e) {
+      fail("Problem applying the filter: " + e);
+    }
+    
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    assertEquals(m_Instances.numAttributes() + 1, result.numAttributes());
+  }
+
+  public static Test suite() {
+    return new TestSuite(PartitionedMultiFilterTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PrincipalComponentsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PrincipalComponentsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PrincipalComponentsTest.java	(revision 29)
@@ -0,0 +1,152 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Tests PrincipalComponents. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.PrincipalComponentsTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class PrincipalComponentsTest 
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public PrincipalComponentsTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Need to remove non-numeric attributes.
+   * 
+   * @throws Exception	if something goes wrong in setup
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.deleteAttributeType(Attribute.STRING);
+    
+    // class index
+    m_Instances.setClassIndex(1);
+  }
+
+  /**
+   * Creates a default PrincipalComponents filter.
+   * 
+   * @return		the default filter
+   */
+  public Filter getFilter() {
+    return new PrincipalComponents();
+  }
+
+  /**
+   * performs the actual test.
+   */
+  protected void performTest() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals(icopy.numInstances(), result.numInstances());
+  }
+
+  /**
+   * Only tests whether the number of instances stay the same, with default 
+   * setup of filter.
+   */
+  public void testTypical() {
+    m_Filter = getFilter();
+    performTest();
+  }
+
+  /**
+   * Runs filter with no normalization.
+   */
+  public void testNoNormalization() {
+    m_Filter = getFilter();
+    ((PrincipalComponents) m_Filter).setNormalize(false);
+    performTest();
+  }
+
+  /**
+   * Runs filter with different variance.
+   */
+  public void testVariance() {
+    m_Filter = getFilter();
+    ((PrincipalComponents) m_Filter).setVarianceCovered(0.8);
+    performTest();
+  }
+
+  /**
+   * Runs filter with a maximum number of attributes.
+   */
+  public void testMaxAttributes() {
+    m_Filter = getFilter();
+    ((PrincipalComponents) m_Filter).setMaximumAttributeNames(2);
+    performTest();
+  }
+  
+  /**
+   * Returns a configures test suite.
+   * 
+   * @return		a configured test suite
+   */
+  public static Test suite() {
+    return new TestSuite(PrincipalComponentsTest.class);
+  }
+
+  /**
+   * For running the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstanceTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstanceTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/PropositionalToMultiInstanceTest.java	(revision 29)
@@ -0,0 +1,101 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests PropositionalToMultiInstance. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.PropositionalToMultiInstanceTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class PropositionalToMultiInstanceTest 
+  extends AbstractFilterTest {
+  
+  public PropositionalToMultiInstanceTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default PropositionalToMultiInstance */
+  public Filter getFilter() {
+    return new PropositionalToMultiInstance();
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    TestInstances test = new TestInstances();
+    test.setNumNominal(2);
+    test.setClassType(Attribute.NOMINAL);
+    test.setNumInstances(400);
+    m_Instances = test.generate();
+  }
+
+  /**
+   * performs a typical test
+   */
+  public void testTypical() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = useFilter();
+    // # of instances
+    int count = 0;
+    for (int i = 0; i < result.numInstances(); i++)
+      count += result.instance(i).relationalValue(1).numInstances();
+    assertEquals(icopy.numInstances(), count);
+    // # of attributes
+    count =   result.numAttributes() 
+            + result.attribute(1).relation().numAttributes()
+            - 1;
+    assertEquals(icopy.numAttributes(), count);
+  }
+  
+  /**
+   * filter cannot be used in conjunction with the FilteredClassifier, since
+   * it makes no sense creating bags containing only one instance in the
+   * distributionForInstance/classifyInstance methods.
+   */
+  public void testFilteredClassifier() {
+    // nothing
+  }
+
+  public static Test suite() {
+    return new TestSuite(PropositionalToMultiInstanceTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RELAGGSTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RELAGGSTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RELAGGSTest.java	(revision 29)
@@ -0,0 +1,142 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RELAGGS. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.RELAGGSTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class RELAGGSTest
+  extends AbstractFilterTest {
+  
+  public RELAGGSTest(String name) { 
+    super(name);
+  }
+
+  /**
+   * Create a dataset with relational attributes to work on
+   */
+  protected void setUp() throws Exception {
+    TestInstances	test;
+
+    super.setUp();
+
+    test = new TestInstances();
+    
+    test.setNumDate(1);
+    test.setNumNominal(1);
+    test.setNumNominalValues(10);
+    test.setNumNumeric(1);
+    test.setNumRelational(2);
+    test.setNumString(0);
+    test.setNumRelationalDate(2);
+    test.setNumRelationalNominal(2);
+    test.setNumRelationalNumeric(2);
+    test.setNumRelationalString(0);
+    test.setNumRelationalNominalValues(10);
+
+    m_Instances = test.generate();
+  }
+
+  /** Creates a default RELAGGS */
+  public Filter getFilter() {
+    return new RELAGGS();
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception {
+    return m_Instances;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+
+    // explanation of "(-1 + (2 + 2)*5 + 2*10)*2"
+    // -1: rel. attribute gets removed
+    // (2 + 2)*5: 2 date and 2 numeric each generate 5 attributes
+    // 2*10: 2 nominal attributes with 10 values
+    // (...)*2: 2 relational attributes
+    assertEquals(m_Instances.numAttributes() + (-1 + (2 + 2)*5 + 2*10)*2, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+  
+  /**
+   * tests the max. cardinality parameter
+   */
+  public void testMaxCardinality() {
+    m_Filter = getFilter();
+    ((RELAGGS) m_Filter).setMaxCardinality(5);  // this skips the nominal atts. with 10 values
+    Instances result = useFilter();
+
+    // explanation of "(-1 + (2 + 2)*5)*2"
+    // -1: rel. attribute gets removed
+    // (2 + 2)*5: 2 date and 2 numeric each generate 5 attributes
+    // (...)*2: 2 relational attributes
+    assertEquals(m_Instances.numAttributes() + (-1 + (2 + 2)*5)*2, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * tests whether we can handle data that contains NO relational attribute
+   * at all
+   */
+  public void testNoRelationalAttribute() {
+    m_Filter = getFilter();
+    
+    // delete the rel. attributes
+    int i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (m_Instances.attribute(i).isRelationValued())
+	m_Instances.deleteAttributeAt(i);
+      else
+	i++;
+    }
+    
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+  
+  public static Test suite() {
+    return new TestSuite(RELAGGSTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomProjectionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomProjectionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomProjectionTest.java	(revision 29)
@@ -0,0 +1,116 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomProjection. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.RandomProjectionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RandomProjectionTest 
+  extends AbstractFilterTest {
+  
+  public RandomProjectionTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // class index
+    m_Instances.setClassIndex(1);
+  }
+  
+  /** Creates a default RandomProjection */
+  public Filter getFilter() {
+    return getFilter(new RandomProjection().getNumberOfAttributes());
+  }
+  
+  /** Creates a RandomProjection with the number of attributes */
+  protected Filter getFilter(int numAtts) {
+    RandomProjection f = new RandomProjection();
+    f.setNumberOfAttributes(numAtts);
+    return f;
+  }
+
+  /**
+   * performs some checks on the given result
+   *
+   * @param result the instances to compare against original dataset
+   */
+  protected void checkResult(Instances result) {
+    assertEquals(
+        ((RandomProjection) m_Filter).getNumberOfAttributes() + 1, 
+        result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * tests the given distribution type
+   *
+   * @param type the distribution type to use
+   * @see RandomProjection#TAGS_DSTRS_TYPE
+   */
+  protected void checkDistributionType(int type) {
+    m_Filter = getFilter();
+    ((RandomProjection) m_Filter).setDistribution(
+        new SelectedTag(type, RandomProjection.TAGS_DSTRS_TYPE));
+    Instances result = useFilter();
+    checkResult(result);
+  }
+
+  public void testSparse1() {
+    checkDistributionType(RandomProjection.SPARSE1);
+  }
+
+  public void testSparse2() {
+    checkDistributionType(RandomProjection.SPARSE2);
+  }
+
+  public void testGaussian() {
+    checkDistributionType(RandomProjection.GAUSSIAN);
+  }
+
+  public void testNumberOfAttributes() {
+    m_Filter = getFilter(5);
+    Instances result = useFilter();
+    checkResult(result);
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomProjectionTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomSubsetTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomSubsetTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RandomSubsetTest.java	(revision 29)
@@ -0,0 +1,131 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RandomSubset. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.RandomSubsetTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class RandomSubsetTest 
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public RandomSubsetTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Creates a default RandomSubset.
+   * 
+   * @return		the filter
+   */
+  public Filter getFilter() {
+    return new RandomSubset();
+  }
+
+  /**
+   * Creates a specialized RandomSubset.
+   * 
+   * @param num		the number of attributes
+   * @return		the filter
+   */
+  public Filter getFilter(double num) {
+    RandomSubset result = new RandomSubset();
+    result.setNumAttributes(num);
+    return result;
+  }
+
+  /**
+   * performs the actual test.
+   * 
+   * @param numSel	the number of attributes to select
+   * @param numOut	the number of attributes that are expected
+   */
+  protected void performTest(double numSel, int numOut) {
+    m_Filter         = getFilter(numSel);
+    Instances icopy  = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals(numOut, result.numAttributes());
+    assertEquals(icopy.numInstances(), m_Instances.numInstances());
+  }
+
+  /**
+   * Tests a percentage.
+   */
+  public void testPercentage() {
+    performTest(0.5, 4);
+  }
+
+  /**
+   * Tests an absolute number.
+   */
+  public void testAbsolute() {
+    performTest(5, 5);
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(RandomSubsetTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveByNameTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveByNameTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveByNameTest.java	(revision 29)
@@ -0,0 +1,139 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveByName. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.RemoveByNameTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5853 $
+ */
+public class RemoveByNameTest
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public RemoveByNameTest(String name) { 
+    super(name);
+  }
+
+  /**
+   * Creates a default RemoveByName.
+   * 
+   * @return		the filter
+   */
+  public Filter getFilter() {
+    return getFilter(RemoveByName.DEFAULT_EXPRESSION, false);
+  }
+  
+  /**
+   * returns a custom filter.
+   * 
+   * @param expression	the expression to use
+   * @param invert	whether to invert the matching sense
+   * @return		the configured filter
+   */
+  protected Filter getFilter(String expression, boolean invert) {
+    RemoveByName	filter;
+    
+    filter = new RemoveByName();
+    filter.setExpression(expression);
+    filter.setInvertSelection(invert);
+    
+    return filter;
+  }
+
+  /**
+   * Tests removing all attributes starting with "String".
+   */
+  public void testTypical() {
+    Instances 	result;
+
+    m_Filter = getFilter("^String.*", false);
+
+    // 1. with class attribute
+    result = useFilter();
+    // Number of attributes will be two less, number of instances won't change
+    assertEquals(m_Instances.numAttributes() - 2, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * Tests removing all attributes starting with "Nominal", one of them being
+   * the class attribute.
+   */
+  public void testTypicalWithClass() {
+    Instances 	result;
+
+    m_Instances.setClassIndex(1);  // "NominalAtt1"
+    m_Filter = getFilter("^Nominal.*", false);
+
+    // 1. with class attribute
+    result = useFilter();
+    // Number of attributes will be two less, number of instances won't change
+    assertEquals(m_Instances.numAttributes() - 1, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * Tests removing all attributes but attributes ending with "Att2".
+   */
+  public void testTypicalInverted() {
+    Instances 	result;
+
+    m_Filter = getFilter(".*Att2$", true);
+
+    // 1. with class attribute
+    result = useFilter();
+    // Number of attributes will be two less, number of instances won't change
+    assertEquals(3, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the test suite
+   */
+  public static Test suite() {
+    return new TestSuite(RemoveByNameTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTest.java	(revision 29)
@@ -0,0 +1,97 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Remove. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.RemoveTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class RemoveTest extends AbstractFilterTest {
+  
+  public RemoveTest(String name) { super(name);  }
+
+  /** Creates a default Remove */
+  public Filter getFilter() {
+    return getFilter("1-3");
+  }
+
+  /** Creates a specialized Remove */
+  public Filter getFilter(String rangelist) {
+    
+    Remove af = new Remove();
+    af.setAttributeIndices(rangelist);
+    return af;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("1,2");
+    ((Remove)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(2, result.numAttributes());
+    assertEquals(m_Instances.attribute(0).name(), result.attribute(0).name());
+    assertEquals(m_Instances.attribute(1).name(), result.attribute(1).name());
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3-4");
+    ((Remove)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(2, result.numAttributes());
+    assertEquals(m_Instances.attribute(2).name(), result.attribute(0).name());
+    assertEquals(m_Instances.attribute(3).name(), result.attribute(1).name());
+  }
+
+  public void testNonInverted() {
+    m_Filter = getFilter("1,2");
+    ((Remove)m_Filter).setInvertSelection(false);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes() - 2, result.numAttributes());
+    assertEquals(m_Instances.attribute(2).name(), result.attribute(0).name());
+    assertEquals(m_Instances.attribute(3).name(), result.attribute(1).name());
+  }
+
+  public void testNonInverted2() {
+    m_Filter = getFilter("first-3");
+    ((Remove)m_Filter).setInvertSelection(false);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes() - 3, result.numAttributes());
+    assertEquals(m_Instances.attribute(3).name(), result.attribute(0).name());
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTypeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTypeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveTypeTest.java	(revision 29)
@@ -0,0 +1,102 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveType. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.RemoveTypeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class RemoveTypeTest extends AbstractFilterTest {
+  
+  public RemoveTypeTest(String name) { super(name);  }
+
+  /** Creates a default RemoveType */
+  public Filter getFilter() {
+    return new RemoveType();
+  }
+
+  /** Creates a specialized RemoveType */
+  public Filter getFilter(int attType) {
+    
+    RemoveType af = new RemoveType();
+    try {
+      af.setAttributeType(new SelectedTag(attType,
+                                          RemoveType.TAGS_ATTRIBUTETYPE));
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Couldn't set up filter with attribute type: " + attType);
+    }
+    return af;
+  }
+
+  public void testNominalFiltering() {
+    m_Filter = getFilter(Attribute.NOMINAL);
+    Instances result = useFilter();
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertTrue(result.attribute(i).type() != Attribute.NOMINAL);
+    }
+  }
+
+  public void testStringFiltering() {
+    m_Filter = getFilter(Attribute.STRING);
+    Instances result = useFilter();
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertTrue(result.attribute(i).type() != Attribute.STRING);
+    }
+  }
+
+  public void testNumericFiltering() {
+    m_Filter = getFilter(Attribute.NUMERIC);
+    Instances result = useFilter();
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertTrue(result.attribute(i).type() != Attribute.NUMERIC);
+    }
+  }
+
+  public void testDateFiltering() {
+    m_Filter = getFilter(Attribute.DATE);
+    Instances result = useFilter();
+    for (int i = 0; i < result.numAttributes(); i++) {
+      assertTrue(result.attribute(i).type() != Attribute.DATE);
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveTypeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveUselessTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveUselessTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RemoveUselessTest.java	(revision 29)
@@ -0,0 +1,88 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveUseless. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.RemoveUselessTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RemoveUselessTest 
+  extends AbstractFilterTest {
+
+  public RemoveUselessTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // class index
+    m_Instances.setClassIndex(1);
+  }
+  
+  /** Creates a default RemoveUseless */
+  public Filter getFilter() {
+    return getFilter(new RemoveUseless().getMaximumVariancePercentageAllowed());
+  }
+
+  /**
+   * creates a RemoveUseless filter with the given percentage of allowed
+   * variance
+   */
+  protected Filter getFilter(double percentage) {
+    RemoveUseless f = new RemoveUseless();
+    f.setMaximumVariancePercentageAllowed(percentage);
+    return f;
+  }
+
+  public void testNoneRemoved() {
+    m_Filter = getFilter(100);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public void testSomeRemoved() {
+    m_Filter = getFilter(5);
+    Instances result = useFilter();
+    assertTrue(m_Instances.numAttributes() > result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveUselessTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RenameAttributeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RenameAttributeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/RenameAttributeTest.java	(revision 29)
@@ -0,0 +1,179 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Tests RenameAttribute. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.RenameAttributeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 6107 $
+ */
+public class RenameAttributeTest 
+  extends AbstractFilterTest {
+
+  /**
+   * Initializes the test.
+   * 
+   * @param name	the name of the test
+   */
+  public RenameAttributeTest(String name) { 
+    super(name);  
+  }
+
+  /** 
+   * Creates a default RenameAttribute.
+   * 
+   * @return		the default filter
+   */
+  public Filter getFilter() {
+    return new RenameAttribute();
+  }
+
+  /** 
+   * Creates a specialized RenameAttribute.
+   * 
+   * @param find	the find expression
+   * @param replace	the replace expression
+   * @param all		whether to replace all occurrences
+   * @param range	the range of attributes
+   * @param invert	whether to invert the selection
+   * @return		the filter
+   */
+  public Filter getFilter(String find, String replace, boolean all, String range, boolean invert) {
+    RenameAttribute 	result;
+    
+    result = new RenameAttribute();
+    result.setFind(find);
+    result.setReplace(replace);
+    result.setReplaceAll(all);
+    result.setAttributeIndices(range);
+    result.setInvertSelection(invert);
+    
+    return result;
+  }
+
+  /**
+   * performs the actual test.
+   * 
+   * @return		the generated data
+   */
+  protected Instances performTest() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals(icopy.numAttributes(), result.numAttributes());
+    assertEquals(icopy.numInstances(), m_Instances.numInstances());
+    
+    return result;
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		the suite
+   */
+  public static Test suite() {
+    return new TestSuite(RenameAttributeTest.class);
+  }
+
+  /**
+   * Tests to replace only the first occurrence.
+   */
+  public void testReplaceFirst() {
+    Instances	result;
+
+    m_Filter = getFilter("t", "_", false, "first-last", false);
+    result   = performTest();
+    
+    assertEquals("S_ringAtt1", result.attribute(0).name());
+  }
+
+  /**
+   * Tests to replace all the occurrences.
+   */
+  public void testReplaceAll() {
+    Instances	result;
+
+    m_Filter = getFilter("t", "_", true, "first-last", false);
+    result   = performTest();
+    
+    assertEquals("S_ringA__1", result.attribute(0).name());
+  }
+
+  /**
+   * Tests the inverting of the attribute range.
+   */
+  public void testInvertRange() {
+    Instances	result;
+
+    m_Filter = getFilter("t", "_", true, "first", true);
+    result   = performTest();
+    
+    assertTrue("The first attribute contains '_'!", (result.attribute(0).name().indexOf("_") == -1));
+  }
+
+  /**
+   * Tests the use of capturing groups.
+   */
+  public void testGroup() {
+    Instances	result;
+    int		i;
+
+    m_Filter = getFilter("(.+)(Att)(.+)", "$1$3", true, "first-last", false);
+    result   = performTest();
+    
+    for (i = 0; i < result.numAttributes(); i++)
+      assertTrue(result.attribute(i).name() + " still contains 'Att'", (result.attribute(i).name().indexOf("Att") == -1));
+  }
+  
+  /**
+   * Runs the test from command-line.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReorderTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReorderTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReorderTest.java	(revision 29)
@@ -0,0 +1,98 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Reorder. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.ReorderTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class ReorderTest extends AbstractFilterTest {
+  
+  public ReorderTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a default Reorder */
+  public Filter getFilter() {
+    return getFilter("first-last");
+  }
+
+  /** Creates a specialized Reorder */
+  public Filter getFilter(String rangelist) {
+    
+    try {
+      Reorder af = new Reorder();
+      af.setAttributeIndices(rangelist);
+      return af;
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception setting attribute range: " + rangelist 
+           + "\n" + ex.getMessage()); 
+    }
+    return null;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter("2,1");
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(2, result.numAttributes());
+    assertTrue(result.attribute(1).name().endsWith(m_Instances.attribute(0).name()));
+    assertTrue(result.attribute(0).name().endsWith(m_Instances.attribute(1).name()));
+  }
+
+  public void testTypical2() {
+    m_Filter = getFilter("3-4");
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(2, result.numAttributes());
+    assertTrue(result.attribute(0).name().endsWith(m_Instances.attribute(2).name()));
+    assertTrue(result.attribute(1).name().endsWith(m_Instances.attribute(3).name()));
+  }
+
+  public void testTypical3() {
+    m_Filter = getFilter("2-last,1");
+    Instances result = useFilter();
+    int origNum = m_Instances.numAttributes();
+    assertEquals(origNum, result.numAttributes());
+    assertTrue(result.attribute(0).name().endsWith(m_Instances.attribute(1).name()));
+    assertTrue(result.attribute(1).name().endsWith(m_Instances.attribute(2).name()));
+    assertTrue(result.attribute(origNum - 1).name().endsWith(m_Instances.attribute(0).name()));
+  }
+
+  public static Test suite() {
+    return new TestSuite(ReorderTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReplaceMissingValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReplaceMissingValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/ReplaceMissingValuesTest.java	(revision 29)
@@ -0,0 +1,81 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ReplaceMissingValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.ReplaceMissingValuesTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class ReplaceMissingValuesTest extends AbstractFilterTest {
+  
+  public ReplaceMissingValuesTest(String name) { super(name);  }
+
+  /** Creates a default ReplaceMissingValues */
+  public Filter getFilter() {
+    return new ReplaceMissingValues();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    for (int j = 0; j < m_Instances.numAttributes(); j++) {
+      Attribute inatt = m_Instances.attribute(j);
+      Attribute outatt = result.attribute(j);
+      for (int i = 0; i < m_Instances.numInstances(); i++) {
+        if (m_Instances.attribute(j).isString()) {
+          if (m_Instances.instance(i).isMissing(j)) {
+            assertTrue("Missing values in strings cannot be replaced",
+                   result.instance(i).isMissing(j));
+          } else {
+            assertEquals("String values should not have changed",
+                         inatt.value((int)m_Instances.instance(i).value(j)),
+                         outatt.value((int)result.instance(i).value(j)));
+          }
+        } else {
+          assertTrue("All non-string missing values should have been replaced",
+                 !result.instance(i).isMissing(j));
+        }
+      }
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(ReplaceMissingValuesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SortLabelsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SortLabelsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SortLabelsTest.java	(revision 29)
@@ -0,0 +1,184 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import java.util.HashSet;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Tests SortLabels. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.attribute.SortLabelsTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 5245 $
+ */
+public class SortLabelsTest 
+  extends AbstractFilterTest {
+  
+  /**
+   * Initializes the test with the given name.
+   * 
+   * @param name	the name of the test
+   */
+  public SortLabelsTest(String name) { 
+    super(name);  
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Instances.deleteAttributeType(Attribute.STRING);
+    m_Instances.deleteAttributeType(Attribute.RELATIONAL);
+  }
+
+  /**
+   * Creates a default SortLabels filter.
+   * 
+   * @return		the filter
+   */
+  public Filter getFilter() {
+    return new SortLabels();
+  }
+
+  /**
+   * Creates a specialized SortLabels.
+   * 
+   * @param sort	the sort type
+   * @param range	the sort range
+   * @return		the configured filter
+   */
+  public Filter getFilter(int sort, String range) {
+    SortLabels result = new SortLabels();
+    result.setSortType(new SelectedTag(SortLabels.SORT_CASESENSITIVE, SortLabels.TAGS_SORTTYPE));
+    result.setAttributeIndices(range);
+    return result;
+  }
+
+  /**
+   * performs the actual test.
+   * 
+   * @return 		the generated instances
+   */
+  protected Instances performTest() {
+    Instances icopy = new Instances(m_Instances);
+    Instances result = null;
+    try {
+      m_Filter.setInputFormat(icopy);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on setInputFormat(): \n" + ex.getMessage());
+    }
+    try {
+      result = Filter.useFilter(icopy, m_Filter);
+      assertNotNull(result);
+    } 
+    catch (Exception ex) {
+      ex.printStackTrace();
+      fail("Exception thrown on useFilter(): \n" + ex.getMessage());
+    }
+
+    assertEquals("Number of attributes", icopy.numAttributes(), result.numAttributes());
+    assertEquals("Number of instances", icopy.numInstances(), m_Instances.numInstances());
+    for (int i = 0; i < result.numAttributes(); i++) {
+      // test number of values
+      assertEquals("Number of values differ for attribute #" + (i+1), icopy.attribute(i).numValues(), result.attribute(i).numValues());
+      // test values
+      HashSet<String> valuesOriginal = new HashSet<String>();
+      HashSet<String> valuesResult = new HashSet<String>();
+      for (int n = 0; n < icopy.attribute(i).numValues(); n++) {
+	valuesOriginal.add(icopy.attribute(i).value(n));
+	valuesResult.add(result.attribute(i).value(n));
+      }
+      assertEquals("Values differ for attribute #" + (i+1), valuesOriginal, valuesResult);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Tests the case-sensitive sorting on all attributes.
+   */
+  public void testCaseSensitive() {
+    m_Filter = getFilter(SortLabels.SORT_CASESENSITIVE, "first-last");
+    testBuffered();
+    Instances result = performTest();
+    String[] sorted = new String[]{"b", "g", "r"};
+    for (int i = 0; i < sorted.length; i++)
+      assertEquals("Values differ for index #" + (i+1), result.attribute(0).value(i), sorted[i]);
+  }
+
+  /**
+   * Tests the case-insensitive sorting on all attributes.
+   */
+  public void testCaseInsensitive() {
+    m_Filter = getFilter(SortLabels.SORT_CASEINSENSITIVE, "first-last");
+    testBuffered();
+    Instances result = performTest();
+    String[] sorted = new String[]{"b", "g", "r"};
+    for (int i = 0; i < sorted.length; i++)
+      assertEquals("Values differ for index #" + (i+1), sorted[i], result.attribute(0).value(i));
+  }
+
+  /**
+   * Tests that ordering didn't change.
+   */
+  public void testUnchangedOrder() {
+    m_Filter = getFilter(SortLabels.SORT_CASESENSITIVE, "first-last");
+    testBuffered();
+    Instances result = performTest();
+    for (int i = 0; i < m_Instances.attribute(2).numValues(); i++)
+      assertEquals("Values differ for index #" + (i+1), m_Instances.attribute(2).value(i), result.attribute(2).value(i));
+  }
+
+  /**
+   * Returns a test.
+   * 
+   * @return		the test
+   */
+  public static Test suite() {
+    return new TestSuite(SortLabelsTest.class);
+  }
+
+  /**
+   * Runs the test from commandline.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StandardizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StandardizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StandardizeTest.java	(revision 29)
@@ -0,0 +1,75 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright 2002 Eibe Frank
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.Utils;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Normalize. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.NormalizeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Eibe Frank</a>
+ * @version $Revision: 1.3 $
+ */
+public class StandardizeTest extends AbstractFilterTest {
+  
+  public StandardizeTest(String name) { super(name);  }
+
+  /** Creates an example Standardize */
+  public Filter getFilter() {
+    Standardize f = new Standardize();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+
+    // Check conversion is OK
+    for (int j = 0; j < result.numAttributes(); j++) {
+      if (result.attribute(j).isNumeric()) {
+	double mean = result.meanOrMode(j);
+	assertTrue("Mean should be 0", Utils.eq(mean, 0));
+	double stdDev = Math.sqrt(result.variance(j));
+	assertTrue("StdDev should be 1 (or 0)", 
+		   Utils.eq(stdDev, 0) || Utils.eq(stdDev, 1));
+      }
+    }
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(StandardizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToNominalTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToNominalTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToNominalTest.java	(revision 29)
@@ -0,0 +1,161 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests StringToNominal. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.StringToNominalTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 4714 $
+ */
+public class StringToNominalTest extends AbstractFilterTest {
+  
+  public StringToNominalTest(String name) { super(name);  }
+
+  /** Creates an example StringToNominal */
+  public Filter getFilter() {
+    StringToNominal f = new StringToNominal();
+    f.setAttributeRange("1");
+    return f;
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setNumRelational(0);
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    assertEquals("Attribute type should now be NOMINAL",
+                 Attribute.NOMINAL, result.attribute(0).type());
+
+    assertEquals(14, result.attribute(0).numValues());
+  }
+
+  public void testMissing() {
+    ((StringToNominal)m_Filter).setAttributeRange("4");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    assertEquals("Attribute type should now be NOMINAL",
+                 Attribute.NOMINAL, result.attribute(3).type());
+
+    assertEquals(8, result.attribute(3).numValues());
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Missing values should be preserved",
+             m_Instances.instance(i).isMissing(3) ==
+             result.instance(i).isMissing(3));
+    }
+  }
+  
+  public void testRange() {
+	    ((StringToNominal)m_Filter).setAttributeRange("first-last");
+	    Instances result = useFilter();
+	    // Number of attributes and instances shouldn't change
+	    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+	    assertEquals(m_Instances.numInstances(),  result.numInstances());
+	    
+	    assertEquals("Attribute type should now be NOMINAL",
+                Attribute.NOMINAL, result.attribute(0).type());
+	    assertEquals("Attribute type should still be NOMINAL",
+                Attribute.NOMINAL, result.attribute(1).type());
+	    assertEquals("Attribute type should still be NUMERIC",
+                Attribute.NUMERIC, result.attribute(2).type());
+	    assertEquals("Attribute type should now be NOMINAL",
+	                 Attribute.NOMINAL, result.attribute(3).type());
+	    assertEquals("Attribute type should still be NOMINAL",
+                Attribute.NOMINAL, result.attribute(4).type());
+	    assertEquals("Attribute type should still be NUMERIC",
+                Attribute.NUMERIC, result.attribute(5).type());
+	    assertEquals("Attribute type should still be DATE",
+                Attribute.DATE, result.attribute(6).type());
+
+	    assertEquals(14, result.attribute(0).numValues());
+	    
+	    assertEquals(8, result.attribute(3).numValues());
+	    for (int i = 0; i < result.numInstances(); i++) {
+	      assertTrue("Missing values should be preserved",
+	             m_Instances.instance(i).isMissing(3) ==
+	             result.instance(i).isMissing(3));
+	    }
+	  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isString()) {
+	  ((StringToNominal) m_FilteredClassifier.getFilter()).setAttributeRange(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(StringToNominalTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToWordVectorTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToWordVectorTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/StringToWordVectorTest.java	(revision 29)
@@ -0,0 +1,73 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests StringToWordVector. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.StringToWordVectorTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class StringToWordVectorTest extends AbstractFilterTest {
+  
+  public StringToWordVectorTest(String name) { super(name);  }
+
+  /** Creates an example StringToWordVector */
+  public Filter getFilter() {
+    StringToWordVector f = new StringToWordVector();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+  }
+
+  public void testWordsToKeep() {
+    ((StringToWordVector)m_Filter).setWordsToKeep(3);
+    Instances result = useFilter();
+    // Number of instances shouldn't change
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+
+    // Number of attributes will be minus 2 string attributes plus
+    // the word attributes (aiming for 3 -- could be higher in the case of ties)
+    assertEquals(m_Instances.numAttributes() - 2 + 3, result.numAttributes());
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(StringToWordVectorTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SwapValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SwapValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/SwapValuesTest.java	(revision 29)
@@ -0,0 +1,173 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SwapValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.SwapValuesTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.5 $
+ */
+public class SwapValuesTest extends AbstractFilterTest {
+  
+  public SwapValuesTest(String name) { super(name);  }
+
+  /** Creates an example SwapValues */
+  public Filter getFilter() {
+    SwapValues f = new SwapValues();
+    // Ensure the filter we return can run on the test dataset
+    f.setAttributeIndex("2"); 
+    return f;
+  }
+
+  public void testInvalidAttributeTypes() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((SwapValues)m_Filter).setAttributeIndex("1");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+    try {
+      ((SwapValues)m_Filter).setAttributeIndex("3");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception indicating a NUMERIC attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the swapping is correct
+    int first = 0, second = 2;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).value(1) == first) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == second);
+      } else if (m_Instances.instance(i).value(1) == second) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == first);
+      }
+    }
+  }
+
+  public void testFirstValueIndex() {
+    ((SwapValues)m_Filter).setFirstValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the swapping is correct
+    int first = 1, second = 2;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).value(1) == first) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == second);
+      } else if (m_Instances.instance(i).value(1) == second) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == first);
+      }
+    }
+  }
+
+  public void testSecondValueIndex() {
+    ((SwapValues)m_Filter).setSecondValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the swapping is correct
+    int first = 0, second = 1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).value(1) == first) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == second);
+      } else if (m_Instances.instance(i).value(1) == second) {
+        assertTrue("Value should be swapped", result.instance(i).value(1) == first);
+      }
+    }
+  }
+
+  public void testAttributeWithMissing() {
+    ((SwapValues)m_Filter).setAttributeIndex("5");
+    ((SwapValues)m_Filter).setFirstValueIndex("1");
+    ((SwapValues)m_Filter).setSecondValueIndex("2");
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check that the swapping is correct
+    int first = 0, second = 1;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Instances.instance(i).isMissing(4)) {
+        assertTrue("Missing in input should give missing in result:" 
+               + m_Instances.instance(i) + " --> "
+               + result.instance(i),
+               result.instance(i).isMissing(4));
+      } else if (m_Instances.instance(i).value(4) == first) {
+        assertTrue("Value should be swapped", result.instance(i).value(4) == second);
+      } else if (m_Instances.instance(i).value(4) == second) {
+        assertTrue("Value should be swapped", result.instance(i).value(4) == first);
+      }
+    }
+  }
+  
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((SwapValues) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+    
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(SwapValuesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesDeltaTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesDeltaTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesDeltaTest.java	(revision 29)
@@ -0,0 +1,96 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.AbstractTimeSeriesFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TimeSeriesDelta. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.TimeSeriesDeltaTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class TimeSeriesDeltaTest extends AbstractTimeSeriesFilterTest {
+
+  public TimeSeriesDeltaTest(String name) { super(name);  }
+
+  /** Creates a default TimeSeriesTranslateFilter */
+  public Filter getFilter() {
+    return getFilter("3");
+  }
+
+  /** Creates a specialized TimeSeriesTranslateFilter */
+  public Filter getFilter(String rangelist) {
+    
+    TimeSeriesDelta af = new TimeSeriesDelta();
+    af.setAttributeIndices(rangelist);
+    af.setFillWithMissing(false);
+    return af;
+  }
+
+  public void testInverted() {
+    m_Filter = getFilter("1,2,3,4,5,6");
+    ((TimeSeriesDelta)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    // Number of attributes shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances() - 1, result.numInstances());
+    // Check conversion looks OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance in = m_Instances.instance(i + 1);
+      Instance out = result.instance(i);
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if ((j != 4) && (j != 5) && (j != 6)) {
+          if (in.isMissing(j)) {
+            assertTrue("Nonselected missing values should pass through",
+                   out.isMissing(j));
+          } else if (result.attribute(j).isString()) {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         m_Instances.attribute(j).value((int)in.value(j)),
+                         result.attribute(j).value((int)out.value(j)));
+          } else {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         in.value(j),
+                         out.value(j), TOLERANCE);
+          }
+        }
+      }
+    }    
+  }
+
+  public static Test suite() {
+    return new TestSuite(TimeSeriesDeltaTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesTranslateTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesTranslateTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/TimeSeriesTranslateTest.java	(revision 29)
@@ -0,0 +1,98 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.AbstractTimeSeriesFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests TimeSeriesTranslate. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.TimeSeriesTranslateTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class TimeSeriesTranslateTest 
+  extends AbstractTimeSeriesFilterTest {
+
+  public TimeSeriesTranslateTest(String name) { super(name);  }
+
+  /** Creates a default TimeSeriesTranslate */
+  public Filter getFilter() {
+    return getFilter("2-3");
+  }
+
+  /** Creates a specialized TimeSeriesTranslate */
+  public Filter getFilter(String rangelist) {
+    
+    TimeSeriesTranslate af = new TimeSeriesTranslate();
+    af.setAttributeIndices(rangelist);
+    af.setFillWithMissing(false);
+    return af;
+  }
+
+  public void testInverted() {
+    m_Filter = getFilter("1,4,2-3");
+    ((TimeSeriesTranslate)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    // Number of attributes shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances() - 1, result.numInstances());
+    // Check conversion looks OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      Instance in = m_Instances.instance(i + 1);
+      Instance out = result.instance(i);
+      for (int j = 0; j < result.numAttributes(); j++) {
+        if ((j != 4) && (j != 5)&& (j != 6)) {
+          if (in.isMissing(j)) {
+            assertTrue("Nonselected missing values should pass through",
+                   out.isMissing(j));
+          } else if (result.attribute(j).isString()) {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         m_Instances.attribute(j).value((int)in.value(j)),
+                         result.attribute(j).value((int)out.value(j)));
+          } else {
+            assertEquals("Nonselected attributes shouldn't change. "
+                         + in + " --> " + out,
+                         in.value(j),
+                         out.value(j), TOLERANCE);
+          }
+        }
+      }
+    }    
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(TimeSeriesTranslateTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/WaveletTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/WaveletTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/attribute/WaveletTest.java	(revision 29)
@@ -0,0 +1,146 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.attribute;
+
+import weka.core.Instances;
+import weka.core.SelectedTag;
+import weka.core.TestInstances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Wavelet. Run from the command line with:<p>
+ * java weka.filters.unsupervised.attribute.WaveletTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class WaveletTest extends AbstractFilterTest {
+  
+  public WaveletTest(String name) { 
+    super(name);
+  }
+  
+  /**
+   * returns data generated for the FilteredClassifier test
+   * 
+   * @return		the dataset for the FilteredClassifier
+   * @throws Exception	if generation of data fails
+   */
+  protected Instances getFilteredClassifierData() throws Exception{
+    TestInstances	test;
+    Instances		result;
+
+    test = TestInstances.forCapabilities(m_FilteredClassifier.getCapabilities());
+    test.setClassIndex(TestInstances.CLASS_IS_LAST);
+
+    result = test.generate();
+    
+    return result;
+  }
+
+  /**
+   * Called by JUnit before each test method. This implementation creates
+   * the default filter to test and loads a test set of Instances.
+   *
+   * @throws Exception if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    int		i;
+    
+    super.setUp();
+    
+    // set class attribute
+    m_Instances.setClassIndex(2);
+    
+    // delete all non-numeric attribute
+    i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (m_Instances.classIndex() == i) {
+	i++;
+	continue;
+      }
+      
+      if (!m_Instances.attribute(i).isNumeric())
+	m_Instances.deleteAttributeAt(i);
+      else
+	i++;
+    }
+  }
+
+  /** Creates a default Wavelet */
+  public Filter getFilter() {
+    return getFilter(Wavelet.ALGORITHM_HAAR, Wavelet.PADDING_ZERO);
+  }
+  
+  /**
+   * returns a custom filter
+   * 
+   * @param algorithm	the type of algorithm to use
+   * @param padding	the type of padding to use
+   * @return		the configured filter
+   */
+  protected Filter getFilter(int algorithm, int padding) {
+    Wavelet	filter;
+    
+    filter = new Wavelet();
+    filter.setAlgorithm(new SelectedTag(algorithm, Wavelet.TAGS_ALGORITHM));
+    filter.setAlgorithm(new SelectedTag(padding, Wavelet.TAGS_PADDING));
+    
+    return filter;
+  }
+
+  /**
+   * tests the HAAR algorithm
+   */
+  public void testTypicalHAAR() {
+    Instances 	icopy;
+    Instances 	result;
+
+    m_Filter = getFilter(Wavelet.ALGORITHM_HAAR, Wavelet.PADDING_ZERO);
+    icopy    = new Instances(m_Instances);
+
+    // 1. with class attribute
+    result = useFilter();
+    // Number of attributes will be power of 2 + class, number of instances won't change
+    assertEquals(Wavelet.nextPowerOf2(m_Instances.numAttributes()) + 1, result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+
+    // 2. without class attribute
+    m_Instances = new Instances(icopy);
+    m_Instances.setClassIndex(-1);
+    result = useFilter();
+    // Number of attributes will be power of 2 + class, number of instances won't change
+    assertEquals(Wavelet.nextPowerOf2(m_Instances.numAttributes()), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(WaveletTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NonSparseToSparseTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NonSparseToSparseTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NonSparseToSparseTest.java	(revision 29)
@@ -0,0 +1,69 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.core.SparseInstance;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests NonSparseToSparse. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.NonSparseToSparseTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class NonSparseToSparseTest extends AbstractFilterTest {
+  
+  public NonSparseToSparseTest(String name) { super(name);  }
+
+  /** Creates an example NonSparseToSparse */
+  public Filter getFilter() {
+    NonSparseToSparse f = new NonSparseToSparse();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check conversion is OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Instance should be an instanceof SparseInstance",
+             result.instance(i) instanceof SparseInstance);
+    }
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(NonSparseToSparseTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NormalizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NormalizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/NormalizeTest.java	(revision 29)
@@ -0,0 +1,86 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.InstanceComparator;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Normalize. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.instance.NormalizeTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class NormalizeTest 
+  extends AbstractFilterTest {
+
+  /** for comparing the instances */
+  protected InstanceComparator m_Comparator;
+  
+  public NormalizeTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.setClassIndex(1);
+    m_Comparator = new InstanceComparator(true);
+  }
+  
+  /** Creates a default Normalize */
+  public Filter getFilter() {
+    Normalize f = new Normalize();
+    return f;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    // at least one instance must be different
+    boolean equal = true;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+    if (equal)
+      fail("Nothing changed!");
+  }
+
+  public static Test suite() {
+    return new TestSuite(NormalizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RandomizeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RandomizeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RandomizeTest.java	(revision 29)
@@ -0,0 +1,70 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Randomize. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.RandomizeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class RandomizeTest extends AbstractFilterTest {
+  
+  public RandomizeTest(String name) { super(name);  }
+
+  /** Creates a default Randomize */
+  public Filter getFilter() {
+    return new Randomize();
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    boolean diff = false;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      String i1 = m_Instances.instance(i).toString();
+      String i2 = result.instance(i).toString();
+      if (!i1.equals(i2)) {
+        diff = true;
+      }
+    }
+    assertTrue("All instances seem to be in the same positions", diff);
+  }
+
+  public static Test suite() {
+    return new TestSuite(RandomizeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFoldsTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFoldsTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFoldsTest.java	(revision 29)
@@ -0,0 +1,68 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveFolds. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.RemoveFoldsTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class RemoveFoldsTest extends AbstractFilterTest {
+  
+  public RemoveFoldsTest(String name) { super(name);  }
+
+  /** Creates a default RemoveFolds */
+  public Filter getFilter() {
+    RemoveFolds f = new RemoveFolds();
+    return f;
+  }
+
+  public void testAllFolds() {
+    
+    int totInstances = 0;
+    for (int i = 0; i < 10; i++) {
+      ((RemoveFolds)m_Filter).setFold(i + 1);
+      Instances result = useFilter();
+      assertEquals(m_Instances.numAttributes(), result.numAttributes());
+      totInstances += result.numInstances();
+    }
+    assertEquals("Expecting output number of instances to match",
+                 m_Instances.numInstances(),  totInstances);
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveFoldsTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFrequentValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFrequentValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveFrequentValuesTest.java	(revision 29)
@@ -0,0 +1,185 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveFrequentValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.RemoveFrequentValuesTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class RemoveFrequentValuesTest
+  extends AbstractFilterTest {
+  
+  /** the attribute indices we're using */
+  private final static int indexString = 0;
+  private final static int indexNumeric = 2; 
+  private final static int indexNominal = 4;
+
+  public RemoveFrequentValuesTest(String name) { 
+    super(name);  
+  }
+
+  /** Creates a RemoveFrequentValues, with "-N 3" and "-C 4" */
+  public Filter getFilter() {
+    RemoveFrequentValues f = new RemoveFrequentValues();
+    f.setAttributeIndex(Integer.toString(indexNominal + 1));
+    f.setNumValues(3);
+    return f;
+  }
+
+  /** test string attribute */
+  public void testString() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((RemoveFrequentValues) m_Filter).setAttributeIndex(Integer.toString(indexString + 1));
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting on a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  /** test nominal attribute */
+  public void testNominal() {
+    Instances   icopy;
+    Instances   result;
+
+    // setting the nominal index
+    ((RemoveFrequentValues) m_Filter).setAttributeIndex(Integer.toString(indexNominal));
+
+    // don't modify header
+    icopy    = new Instances(m_Instances);
+    m_Filter = getFilter();
+    try {
+      m_Filter.setInputFormat(icopy);
+      result = useFilter();
+      assertEquals( "Doesn't modify the header, i.e. removing labels", 
+	  m_Instances.attribute(indexNominal).numValues(), 
+	  result.attribute(indexNominal).numValues() );
+    } catch (Exception ex) {
+      // OK
+    }
+
+    // modify header
+    icopy    = new Instances(m_Instances);
+    m_Filter = getFilter();
+    try {
+      ((RemoveFrequentValues) m_Filter).setModifyHeader(true);
+      m_Filter.setInputFormat(icopy);
+      result = useFilter();
+      assertEquals(   "Returns " + ((RemoveFrequentValues) m_Filter).getNumValues() 
+	  + " out of the " + m_Instances.attribute(indexNominal).numValues() + " labels", 
+	  ((RemoveFrequentValues) m_Filter).getNumValues(),
+	  result.attribute(indexNominal).numValues() );
+    } catch (Exception ex) {
+      // OK
+    }
+
+    // modify header + least common
+    icopy    = new Instances(m_Instances);
+    m_Filter = getFilter();
+    try {
+      ((RemoveFrequentValues) m_Filter).setModifyHeader(true);
+      ((RemoveFrequentValues) m_Filter).setUseLeastValues(true);
+      m_Filter.setInputFormat(icopy);
+      result = useFilter();
+      assertEquals(   "Returns " + ((RemoveFrequentValues) m_Filter).getNumValues() 
+	  + " out of the " + m_Instances.attribute(indexNominal).numValues() + " labels", 
+	  ((RemoveFrequentValues) m_Filter).getNumValues(),
+	  result.attribute(indexNominal).numValues() );
+    } catch (Exception ex) {
+      // OK
+    }
+
+    // modify header + least common + inverse
+    icopy    = new Instances(m_Instances);
+    m_Filter = getFilter();
+    try {
+      ((RemoveFrequentValues) m_Filter).setModifyHeader(true);
+      ((RemoveFrequentValues) m_Filter).setUseLeastValues(true);
+      ((RemoveFrequentValues) m_Filter).setInvertSelection(true);
+      ((RemoveFrequentValues) m_Filter).setNumValues(4);
+      m_Filter.setInputFormat(icopy);
+      result = useFilter();
+      assertEquals(   "Returns 1 out of the " + m_Instances.attribute(indexNominal).numValues() 
+	  + " labels, even though we try to remove " + ((RemoveFrequentValues) m_Filter).getNumValues()  
+	  + " labels, since it always returns at least 1 label",
+	  1,
+	  result.attribute(indexNominal).numValues() );
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  /** test numeric attribute */
+  public void testNumeric() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((RemoveFrequentValues) m_Filter).setAttributeIndex(Integer.toString(indexNumeric + 1));
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting on a NUMERIC attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  /**
+   * tests the filter in conjunction with the FilteredClassifier
+   */
+  public void testFilteredClassifier() {
+    try {
+      Instances data = getFilteredClassifierData();
+
+      for (int i = 0; i < data.numAttributes(); i++) {
+	if (data.classIndex() == i)
+	  continue;
+	if (data.attribute(i).isNominal()) {
+	  ((RemoveFrequentValues) m_FilteredClassifier.getFilter()).setAttributeIndex(
+	      "" + (i + 1));
+	  break;
+	}
+      }
+    }
+    catch (Exception e) {
+      fail("Problem setting up test for FilteredClassifier: " + e.toString());
+    }
+
+    super.testFilteredClassifier();
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveFrequentValuesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveMisclassifiedTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveMisclassifiedTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveMisclassifiedTest.java	(revision 29)
@@ -0,0 +1,125 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveMisclassified. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.instance.RemoveMisclassifiedTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.2 $
+ */
+public class RemoveMisclassifiedTest 
+  extends AbstractFilterTest {
+  
+  public RemoveMisclassifiedTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // class index
+    m_Instances.setClassIndex(1);
+    
+    // remove attributes that are not nominal/numeric
+    int i = 0;
+    while (i < m_Instances.numAttributes()) {
+      if (    !m_Instances.attribute(i).isNominal()
+           && !m_Instances.attribute(i).isNumeric() )
+        m_Instances.deleteAttributeAt(i);
+      else
+        i++;
+    }
+  }
+  
+  /** Creates a default RemoveMisclassified, suited for nominal class */
+  public Filter getFilter() {
+    return getFilter(true);
+  }
+
+  /**
+   * Creates a RemoveMisclassified, with either J48 (true) or M5P (false)
+   * as classifier
+   */
+  protected Filter getFilter(boolean nominal) {
+    RemoveMisclassified f = new RemoveMisclassified();
+    
+    // classifier
+    if (nominal)
+      f.setClassifier(new weka.classifiers.trees.J48());
+    else
+      f.setClassifier(new weka.classifiers.trees.M5P());
+    
+    // threshold
+    if (!nominal)
+      f.setThreshold(2.0);
+    
+    return f;
+  }
+
+  public void testNominal() {
+    m_Filter = getFilter(true);
+    m_Instances.setClassIndex(0);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+  }
+
+  public void testNumeric() {
+    m_Filter = getFilter(false);
+    m_Instances.setClassIndex(1);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+  }
+
+  public void testInverting() {
+    // not inverted
+    m_Filter = getFilter();
+    m_Instances.setClassIndex(0);
+    Instances result = useFilter();
+    
+    // inverted
+    m_Filter = getFilter();
+    ((RemoveMisclassified) m_Filter).setInvert(true);
+    m_Instances.setClassIndex(0);
+    Instances resultInv = useFilter();
+
+    assertEquals(
+        m_Instances.numInstances(), 
+        result.numInstances() + resultInv.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveMisclassifiedTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemovePercentageTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemovePercentageTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemovePercentageTest.java	(revision 29)
@@ -0,0 +1,87 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemovePercentage. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.instance.RemovePercentageTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.3 $
+ */
+public class RemovePercentageTest 
+  extends AbstractFilterTest {
+  
+  public RemovePercentageTest(String name) { 
+    super(name);  
+  }
+
+  /** Need to remove non-nominal attributes, set class index */
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    m_Instances.setClassIndex(1);
+  }
+  
+  /** Creates a default RemovePercentage */
+  public Filter getFilter() {
+    RemovePercentage f = new RemovePercentage();
+    return f;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+  }
+
+  public void testInverting() {
+    // non-inverted
+    m_Filter = getFilter();
+    ((RemovePercentage) m_Filter).setPercentage(20.0);
+    Instances result = useFilter();
+
+    // inverted
+    m_Filter = getFilter();
+    ((RemovePercentage) m_Filter).setPercentage(20.0);
+    ((RemovePercentage) m_Filter).setInvertSelection(true);
+    Instances resultInv = useFilter();
+
+    assertEquals(
+        m_Instances.numInstances(), 
+        result.numInstances() + resultInv.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemovePercentageTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveRangeTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveRangeTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveRangeTest.java	(revision 29)
@@ -0,0 +1,67 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveRange. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.RemoveRangeTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.5 $
+ */
+public class RemoveRangeTest extends AbstractFilterTest {
+  
+  public RemoveRangeTest(String name) { super(name);  }
+
+  /** Creates a default RemoveRange */
+  public Filter getFilter() {
+    RemoveRange f = new RemoveRange();
+    return f;
+  }
+
+  public void testSpecifiedRange() {
+    
+    ((RemoveRange)m_Filter).setInstancesIndices("1-10");
+    ((RemoveRange)m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(10,  result.numInstances());
+    for (int i = 0; i < 10; i++) {
+      assertEquals(m_Instances.instance(i).toString(), result.instance(i).toString());
+    }
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveRangeTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveWithValuesTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveWithValuesTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/RemoveWithValuesTest.java	(revision 29)
@@ -0,0 +1,142 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests RemoveWithValues. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.RemoveWithValuesTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.6 $
+ */
+public class RemoveWithValuesTest extends AbstractFilterTest {
+  
+  public RemoveWithValuesTest(String name) { super(name);  }
+
+  /** Creates a default RemoveWithValues */
+  public Filter getFilter() {
+    RemoveWithValues f = new RemoveWithValues();
+    f.setAttributeIndex("3");
+    f.setInvertSelection(true);
+    return f;
+  }
+
+  public void testString() {
+    Instances icopy = new Instances(m_Instances);
+    try {
+      ((RemoveWithValues)m_Filter).setAttributeIndex("1");
+      m_Filter.setInputFormat(icopy);
+      fail("Should have thrown an exception selecting on a STRING attribute!");
+    } catch (Exception ex) {
+      // OK
+    }
+  }
+
+  public void testNominal() {
+    ((RemoveWithValues)m_Filter).setAttributeIndex("2");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Default nominal selection matches all values",
+                 m_Instances.numInstances(),  result.numInstances());
+
+    try {
+      ((RemoveWithValues)m_Filter).setNominalIndices("1-2");
+    } catch (Exception ex) {
+      fail("Shouldn't ever get here unless Range chamges incompatibly");
+    }
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertTrue(m_Instances.numInstances() > result.numInstances());
+
+    try {
+      ((RemoveWithValues)m_Filter).setNominalIndices("3-last");
+    } catch (Exception ex) {
+      fail("Shouldn't ever get here unless Range chamges incompatibly");
+    }
+    Instances result2 = useFilter();
+    assertEquals(m_Instances.numAttributes(), result2.numAttributes());
+    assertTrue(m_Instances.numInstances() > result2.numInstances());
+    assertEquals(m_Instances.numInstances(), result.numInstances() + result2.numInstances());
+
+    ((RemoveWithValues)m_Filter).setInvertSelection(false);
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances() + result2.numInstances());
+  }
+
+  public void testNumeric() {
+    ((RemoveWithValues)m_Filter).setAttributeIndex("3");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Default split point matches values less than 0",
+                 0,  result.numInstances());
+
+    ((RemoveWithValues)m_Filter).setSplitPoint(3);
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertTrue(m_Instances.numInstances() > result.numInstances());
+
+    // Test inversion is working.
+    ((RemoveWithValues)m_Filter).setInvertSelection(false);
+    Instances result2 = useFilter();
+    assertEquals(m_Instances.numAttributes(), result2.numAttributes());
+    assertTrue(m_Instances.numInstances() > result2.numInstances());
+    assertEquals(m_Instances.numInstances(), result.numInstances() + result2.numInstances());
+  }
+
+  public void testMatchMissingValues() {
+    ((RemoveWithValues)m_Filter).setAttributeIndex("5");
+    ((RemoveWithValues)m_Filter).setInvertSelection(false);
+    ((RemoveWithValues)m_Filter).setMatchMissingValues(false);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertTrue(result.numInstances() > 0);
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Should select only instances with missing values",
+             result.instance(i).isMissing(4));
+    }
+  }
+  
+  /**
+   * filter cannot be used in conjunction with the FilteredClassifier, since
+   * an instance used in distributionForInstance/classifyInstance might get
+   * deleted.
+   */
+  public void testFilteredClassifier() {
+    // nothing
+  }
+
+  public static Test suite() {
+    return new TestSuite(RemoveWithValuesTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ResampleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ResampleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ResampleTest.java	(revision 29)
@@ -0,0 +1,89 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests Resample. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.ResampleTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class ResampleTest extends AbstractFilterTest {
+  
+  public ResampleTest(String name) { super(name);  }
+
+  /** Creates a default Resample */
+  public Filter getFilter() {
+    Resample f = new Resample();
+    f.setSampleSizePercent(50);
+    return f;
+  }
+
+  public void testSampleSizePercent() {
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 50% of input",
+                 m_Instances.numInstances() / 2,  result.numInstances());
+
+    ((Resample)m_Filter).setSampleSizePercent(200);
+    result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 200% of input",
+                 m_Instances.numInstances() * 2,  result.numInstances());
+  }
+
+  public void testSampleSizePercentNoReplacement() {
+    ((Resample) m_Filter).setSampleSizePercent(20);
+    ((Resample) m_Filter).setNoReplacement(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 20% of input",
+                 (int) (m_Instances.numInstances() * 20.0 / 100),  result.numInstances());
+  }
+
+  public void testSampleSizePercentNoReplacementInverted() {
+    ((Resample) m_Filter).setSampleSizePercent(20);
+    ((Resample) m_Filter).setNoReplacement(true);
+    ((Resample) m_Filter).setInvertSelection(true);
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals("Expecting output to be 80% of input (20% inverted)",
+                 m_Instances.numInstances() 
+                 - (int) (m_Instances.numInstances() * 20.0 / 100),  result.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(ResampleTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ReservoirSampleTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ReservoirSampleTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/ReservoirSampleTest.java	(revision 29)
@@ -0,0 +1,120 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.InstanceComparator;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests ReservoirSample. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.instance.ReservoirSampleTest
+ *
+ * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
+ * @version $Revision: 5563 $
+ */
+public class ReservoirSampleTest
+  extends AbstractFilterTest {
+
+  /** for comparing the instances */
+  protected InstanceComparator m_Comparator;
+  
+  public ReservoirSampleTest(String name) { 
+    super(name);  
+  }
+
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Comparator = new InstanceComparator(true);
+  }
+
+  /** Creates a default ReservoirSample */
+  public Filter getFilter() {
+    ReservoirSample r = new ReservoirSample();
+    return r;
+  }
+
+  public void testTypical() {
+    m_Filter = getFilter();
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+    
+    // instances should be indentical as default settings ask for
+    // a larger sample than there is number of instances in the test
+    // dataset
+    boolean equal = true;
+    for (int i = 0; i < m_Instances.numInstances(); i++) {
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal) {
+      fail("Result should be equal");
+    }
+  }
+
+  public void testSubSample() {
+    m_Filter = getFilter();
+    ((ReservoirSample)m_Filter).setSampleSize(10);
+    
+    Instances result = useFilter();
+    assertEquals(result.numInstances(), 10);
+
+    // instances should be different from the first 10 instances in
+    // the original data
+
+    boolean equal = true;
+    for (int i = 0; i < result.numInstances(); i++) {
+      if (m_Comparator.compare(
+            m_Instances.instance(i), result.instance(i)) != 0) {
+        equal = false;
+        break;
+      }
+    }
+
+    if (equal) {
+      fail("Result should be different than the first 10 instances");
+    }
+  }
+
+  public void testHeaderOnlyInput() {
+    m_Filter = getFilter();
+    m_Instances = new Instances(m_Instances, 0);
+    Instances result = useFilter();
+    assertEquals(result.numInstances(), m_Instances.numInstances());
+  }
+
+  public static Test suite() {
+    return new TestSuite(ReservoirSampleTest.class);
+  }
+  
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SparseToNonSparseTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SparseToNonSparseTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SparseToNonSparseTest.java	(revision 29)
@@ -0,0 +1,69 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Instances;
+import weka.core.SparseInstance;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests SparseToNonSparse. Run from the command line with:<p>
+ * java weka.filters.unsupervised.instance.SparseToNonSparseTest
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.3 $
+ */
+public class SparseToNonSparseTest extends AbstractFilterTest {
+  
+  public SparseToNonSparseTest(String name) { super(name);  }
+
+  /** Creates an example SparseToNonSparse */
+  public Filter getFilter() {
+    SparseToNonSparse f = new SparseToNonSparse();
+    return f;
+  }
+
+  public void testTypical() {
+    Instances result = useFilter();
+    // Number of attributes and instances shouldn't change
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(),  result.numInstances());
+    // Check conversion is OK
+    for (int i = 0; i < result.numInstances(); i++) {
+      assertTrue("Instance should not be an instanceof SparseInstance:" + (i + 1),
+             !(result.instance(i) instanceof SparseInstance));
+    }
+  }
+
+
+  public static Test suite() {
+    return new TestSuite(SparseToNonSparseTest.class);
+  }
+
+  public static void main(String[] args){
+    junit.textui.TestRunner.run(suite());
+  }
+
+}
Index: branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SubsetByExpressionTest.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SubsetByExpressionTest.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/filters/unsupervised/instance/SubsetByExpressionTest.java	(revision 29)
@@ -0,0 +1,214 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2008 University of Waikato 
+ */
+
+package weka.filters.unsupervised.instance;
+
+import weka.core.Attribute;
+import weka.core.Instances;
+import weka.filters.AbstractFilterTest;
+import weka.filters.Filter;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+/**
+ * Tests SubsetByExpression. Run from the command line with: <p/>
+ * java weka.filters.unsupervised.instance.SubsetByExpressionTest
+ *
+ * @author FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.1 $
+ */
+public class SubsetByExpressionTest
+  extends AbstractFilterTest {
+  
+  /**
+   * Setup the test.
+   * 
+   * @param name	the name of the test
+   */
+  public SubsetByExpressionTest(String name) {
+    super(name);
+  }
+
+  /**
+   * Called by JUnit before each test method. 
+   * <p/>
+   * Removes all the string attributes and sets the first attribute as class 
+   * attribute.
+   *
+   * @throws Exception 	if an error occurs reading the example instances.
+   */
+  protected void setUp() throws Exception {
+    super.setUp();
+    
+    m_Instances.deleteAttributeType(Attribute.STRING);
+    m_Instances.setClassIndex(0);
+  }
+
+  /**
+   * Creates a default SubsetByExpression filter.
+   * 
+   * @return		the filter
+   */
+  public Filter getFilter() {
+    return new SubsetByExpression();
+  }
+
+  /**
+   * Creates a SubsetByExpression filter with the given expression.
+   * 
+   * @param expr	the expression to use
+   * @return		the filter
+   */
+  public Filter getFilter(String expr) {
+    SubsetByExpression result = new SubsetByExpression();
+    result.setExpression(expr);
+    return result;
+  }
+  
+  /**
+   * Tests the "ismissing" functionality.
+   */
+  public void testIsmissing() {
+    m_Filter = getFilter("ismissing(ATT3)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(3, result.numInstances());
+  }
+  
+  /**
+   * Tests the "not ismissing" functionality.
+   */
+  public void testNotIsmissing() {
+    m_Filter = getFilter("not ismissing(ATT3)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances() - 3, result.numInstances());
+  }
+  
+  /**
+   * Tests the "CLASS" shortcut with 'is'.
+   */
+  public void testClassIs() {
+    m_Filter = getFilter("CLASS is 'g'");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(3, result.numInstances());
+  }
+  
+  /**
+   * Tests the "CLASS" shortcut with 'is' over all class labels, using ' or '.
+   */
+  public void testClassIs2() {
+    m_Filter = getFilter("(CLASS is 'r') or (CLASS is 'g') or (CLASS is 'b')");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(m_Instances.numInstances(), result.numInstances());
+  }
+  
+  /**
+   * Tests the "ATT1" shortcut with 'is'.
+   */
+  public void testAttIs() {
+    m_Filter = getFilter("ATT1 is 'r'");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(12, result.numInstances());
+  }
+  
+  /**
+   * Tests the "&gt;" functionality.
+   */
+  public void testGreater() {
+    m_Filter = getFilter("ATT2 > 4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(13, result.numInstances());
+  }
+  
+  /**
+   * Tests the "&lt;" functionality.
+   */
+  public void testLess() {
+    m_Filter = getFilter("ATT2 < 4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(7, result.numInstances());
+  }
+  
+  /**
+   * Tests the "&gt;=" functionality.
+   */
+  public void testGreaterOrEqual() {
+    m_Filter = getFilter("ATT2 >= 4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(14, result.numInstances());
+  }
+  
+  /**
+   * Tests the "&lt;=" functionality.
+   */
+  public void testLessOrEqual() {
+    m_Filter = getFilter("ATT2 <= 4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(8, result.numInstances());
+  }
+  
+  /**
+   * Tests the "=" functionality.
+   */
+  public void testEqual() {
+    m_Filter = getFilter("ATT2 = 4");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(1, result.numInstances());
+  }
+  
+  /**
+   * Tests the "ATT1" shortcut with 'is' and restricting it via ' and '.
+   */
+  public void testAnd() {
+    m_Filter = getFilter("(ATT1 is 'r') and (ATT2 <= 5)");
+    Instances result = useFilter();
+    assertEquals(m_Instances.numAttributes(), result.numAttributes());
+    assertEquals(6, result.numInstances());
+  }
+
+  /**
+   * Returns a test suite.
+   * 
+   * @return		test suite
+   */
+  public static Test suite() {
+    return new TestSuite(SubsetByExpressionTest.class);
+  }
+
+  /**
+   * Runs the test from command-line.
+   * 
+   * @param args	ignored
+   */
+  public static void main(String[] args){
+    TestRunner.run(suite());
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/test/Regression.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/test/Regression.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/test/Regression.java	(revision 29)
@@ -0,0 +1,231 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Copyright (C) 2002 University of Waikato 
+ */
+
+package weka.test;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * <code>Regression</code> provides support for performing regression
+ * testing.
+ *
+ * @author <a href="mailto:len@reeltwo.com">Len Trigg</a>
+ * @version $Revision: 1.4 $
+ */
+public class Regression {
+
+  /** Reference files have this extension. */
+  private static final String FILE_EXTENSION = ".ref";
+
+  /**
+   * The name of the system property that can be used to override the
+   * location of the reference root.
+   */
+  private static final String ROOT_PROPERTY = "weka.test.Regression.root";
+
+  /** Default root location, relative to the users home direcory. */
+  private static final String DEFAULT_ROOT = "wekarefs";
+
+  /** Stores the root location under which reference files are stored. */
+  private static File ROOT;
+
+  /** Stores the output to be compared against the reference. */
+  private StringBuffer m_Output;
+
+  /** The file where the reference output is (or will be) located */
+  private File m_RefFile;
+
+  /**
+   * Returns a <code>File</code> corresponding to the root of the
+   * reference tree.
+   *
+   * @return a <code>File</code> giving the root of the reference tree. 
+   */
+  public static File getRoot() {
+
+    if (ROOT == null) {
+      String root = System.getProperty(ROOT_PROPERTY);
+      if (root == null) {
+        root = System.getProperty("user.dir");
+        ROOT = new File(root, DEFAULT_ROOT);
+      } else {
+        ROOT = new File(root);
+      }
+    }
+    return ROOT;
+  }
+
+  /**
+   * Creates a new <code>Regression</code> instance for the supplied class.
+   *
+   * @param theClass a <code>Class</code> value. The name of the class is
+   * used to determine the reference file to compare output with.
+   */
+  public Regression(Class theClass) {
+
+    if (theClass == null) {
+      throw new NullPointerException();
+    }
+    String relative = theClass.getName().replace('.', File.separatorChar) 
+      + FILE_EXTENSION;
+    m_RefFile = new File(getRoot(), relative);
+    m_Output = new StringBuffer();
+  }
+
+  /**
+   * Adds some output to the current regression output. The accumulated output
+   * will provide the material for the regression comparison.
+   *
+   * @param output a <code>String</code> that forms part of the regression test.
+   */
+  public void println(String output) {
+
+    m_Output.append(output).append('\n');
+  }
+
+  /**
+   * Returns the difference between the current output and the reference
+   * version.
+   *
+   * @return a <code>String</code> value illustrating the differences. If this
+   * string is empty, there are no differences. If null is returned, there was
+   * no reference output found, and the current output has been written as the
+   * reference.
+   * @exception IOException if an error occurs during reading or writing of
+   * the reference file.
+   */
+  public String diff() throws IOException {
+
+    try {
+      String reference = readReference();
+      return diff(reference, m_Output.toString());
+    } catch (FileNotFoundException fnf) {
+      // No, write out the current output
+      writeAsReference();
+      return null;
+    }
+  }
+
+  /**
+   * Returns the difference between two strings, Will be the empty string
+   * if there are no difference.
+   *
+   * @param reference a <code>String</code> value
+   * @param current a <code>String</code> value
+   * @return a <code>String</code> value describing the differences between 
+   * the two input strings. This will be the empty string if there are no
+   * differences.
+   */
+  protected String diff(String reference, String current) {
+
+    if (reference.equals(current)) {
+      return "";
+    } else {
+      // Should do something more cunning here, like try to isolate the
+      // actual differences. We could also try calling unix diff utility 
+      // if it exists.
+      StringBuffer diff = new StringBuffer();
+      diff.append("+++ Reference: ").append(m_RefFile).append(" +++\n")
+        .append(reference)
+        .append("+++ Current +++\n")
+        .append(current)
+        .append("+++\n");
+      return diff.toString();
+    }
+  }
+
+  /**
+   * Reads the reference output from a file and returns it as a String
+   *
+   * @return a <code>String</code> value containing the reference output
+   * @exception IOException if an error occurs.
+   */
+  protected String readReference() throws IOException {
+
+    Reader r = new BufferedReader(new FileReader(m_RefFile));
+    StringBuffer ref = new StringBuffer();
+    char [] buf = new char[5];
+    for(int read = r.read(buf); read > 0; read = r.read(buf)) {
+      ref.append(new String(buf, 0, read));
+    }
+    r.close();
+    return ref.toString();    
+  }
+  
+  /**
+   * Writes the current output as the new reference. Normally this method is
+   * called automatically if diff() is called with no existing reference
+   * version. You may wish to call it explicitly if you know you want to 
+   * create the reference version.
+   *
+   * @exception IOException if an error occurs.
+   */
+  public void writeAsReference() throws IOException {
+
+    File parent = m_RefFile.getParentFile();
+    if (!parent.exists()) {
+      parent.mkdirs();
+    }
+    Writer w = new BufferedWriter(new FileWriter(m_RefFile));
+    w.write(m_Output.toString());
+    w.close();
+  }
+
+  /**
+   * Tests Regression from the command line
+   *
+   * @param args a <code>String</code> array containing values to
+   * send to println().
+   */
+  public static void main(String []args) {
+
+    try {
+      if (args.length == 0) {
+        throw new Exception("Usage: Regression \"some text\"");
+      }
+      Properties props = System.getProperties();
+      props.setProperty(ROOT_PROPERTY, props.getProperty("java.io.tmpdir"));
+
+      Regression reg = new Regression(Regression.class);
+      for (int i = 0; i < args.length; i++) {
+        reg.println(args[i]);
+      }
+      String diff = reg.diff();
+      if (diff == null) {
+        System.err.println("Created reference output");
+      } else if (diff.equals("")) {
+        System.err.println("Passed");
+      } else {
+        System.err.println("Failed: " + diff);
+      }
+    } catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
Index: branches/MetisMQI/src/test/java/weka/test/WekaTestSuite.java
===================================================================
--- branches/MetisMQI/src/test/java/weka/test/WekaTestSuite.java	(revision 29)
+++ branches/MetisMQI/src/test/java/weka/test/WekaTestSuite.java	(revision 29)
@@ -0,0 +1,291 @@
+/*
+ *    This program 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 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program 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 this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * WekaTestSuite.java
+ * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
+ *
+ */
+
+package weka.test;
+
+import weka.core.ClassDiscovery;
+import weka.gui.GenericPropertiesCreator;
+
+import java.util.Collections;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Extends the standard TestSuite class wtih some additional methods for
+ * automatic generation of a series of tests.
+ *
+ * @author  FracPete (fracpete at waikato dot ac dot nz)
+ * @version $Revision: 1.5 $
+ * @see     GenericPropertiesCreator
+ * @see     ClassDiscovery
+ */
+public class WekaTestSuite
+  extends TestSuite {
+
+  /**
+   * checks whether the classname is a valid one, i.e., from a public class
+   *
+   * @param classname   the classname to check
+   * @return            whether the classname is a valid one
+   */
+  protected static boolean isValidClassname(String classname) {
+    return (classname.indexOf("$") == -1);
+  }
+  
+  /**
+   * determines all the classes derived from the given superclass in the
+   * specified packages
+   *
+   * @param superclass  the class to find subclasses for
+   * @param pacakges    the packages to search in for subclasses
+   * @return            the classes that were found
+   */
+  protected static Vector getClassnames(String superclass, Vector packages) {
+    Vector        result;
+    Vector        names;
+    int           i;
+    int           n;
+
+    result = new Vector();
+
+    for (i = 0; i < packages.size(); i++) {
+      names = ClassDiscovery.find(superclass, (String) packages.get(i));
+      for (n = 0; n < names.size(); n++) {
+        // skip non-public classes
+        if (isValidClassname((String) names.get(n)))
+          result.add(names.get(n));
+      }
+    }
+    
+    return result;
+  }
+
+  /**
+   * returns a Vector with all the classnames of the specified property in 
+   * the GenericPropertiesCreator.
+   *
+   * @param property    the property to get the classnames for
+   * @return            the classnames of the given property
+   * @see               GenericPropertiesCreator
+   */
+  protected static Vector getClassnames(String property) {
+    GenericPropertiesCreator  gpc;
+    String                    classes;
+    Vector                    result;
+    StringTokenizer           tok;
+    String                    classname;
+    
+    result = new Vector();
+
+    try {
+      gpc = new GenericPropertiesCreator();
+      gpc.execute(false);
+      
+      classes = gpc.getOutputProperties().getProperty(property);
+      tok     = new StringTokenizer(classes, ",");
+      
+      while (tok.hasMoreTokens()) {
+        classname = tok.nextToken();
+        // skip non-public classes
+        if (isValidClassname(classname))
+          result.add(classname);
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+
+  /**
+   * generates a Test class name for a given "regular" class
+   * 
+   * @param classname   the class to generate the Test class name for
+   * @return            the classname of the test
+   */
+  protected static String getTestClassname(String classname) {
+    if (!classname.endsWith("Test"))
+      return classname + "Test";
+    else
+      return classname;
+  }
+
+  /**
+   * returns the test class if it exists, for the given class, otherwise
+   * null
+   *
+   * @param classname   the class to retrieve the Test class for
+   * @return            non-null, if the Test class exists
+   */
+  protected static Class testClassFor(String classname) {
+    Class         result;
+    
+    result = null;
+    
+    try {
+      result = Class.forName(getTestClassname(classname));
+    }
+    catch (Exception e) {
+      // ignore it
+    }
+
+    return result;
+  }
+
+  /**
+   * tries to find Test classes for all the given classnames, if successful
+   * they're added to the Test
+   *
+   * @param classnames    the classnames to get 
+   */
+  protected static TestSuite addAll(Vector classnames) {
+    int           i;
+    Class         tc;
+    TestSuite     result;
+
+    result = new TestSuite();
+    
+    for (i = 0; i < classnames.size(); i++) {
+      tc = testClassFor((String) classnames.get(i));
+      if (tc != null)
+        result.addTest(new TestSuite(tc));
+    }
+
+    return result;
+  }
+  
+  /**
+   * adds all Tests for classes that are available via the
+   * GenericPropertiesCreator's property to the Test and returns that
+   *
+   * @param property    the GPC property to add all the classes to the Test
+   * @return            the generated Test
+   * @see               GenericPropertiesCreator
+   */
+  public static TestSuite addAll(String property) {
+    return addAll(getClassnames(property));
+  }
+
+  /**
+   * adds all available Tests for a given superclass and the packages to 
+   * check in
+   *
+   * @param superclass  the superclass to find tests of subclasses for
+   * @param packages    the packages (strings) to search in
+   * @return            the generated Test
+   */
+  public static TestSuite addAll(String superclass, Vector packages) {
+    return addAll(getClassnames(superclass, packages));
+  }
+
+  /**
+   * determines all the test classes that are missing for the given 
+   * classnames.
+   *
+   * @param classnames  the classnames that are to be checked
+   * @return            the missing Test classes
+   */
+  protected static Vector getMissing(Vector classnames) {
+    int           i;
+    Vector        result;
+
+    result = new Vector();
+    
+    for (i = 0; i < classnames.size(); i++) {
+      if (testClassFor((String) classnames.get(i)) == null)
+        result.add(getTestClassname((String) classnames.get(i)));
+    }
+
+    return result;
+  }
+
+  /**
+   * determines all the test classes that are missing for the given
+   * GenericPropertiesCreator's property's elements
+   *
+   * @param property    the GPC property to determine the missing Tests for
+   * @return            the classnames of the missing Tests
+   */
+  public static Vector getMissing(String property) {
+    return getMissing(getClassnames(property));
+  }
+
+  /**
+   * determines all the test classes of subclasses that are missing for the 
+   * given superclass and packages to look in for
+   *
+   * @param superclass  the superclass to check for tests of derived classes
+   * @param packages    the packages to search in
+   * @return            the classnames of the missing Tests
+   */
+  public static Vector getMissing(String superclass, Vector packages) {
+    return getMissing(getClassnames(superclass, packages));
+  }
+
+  /**
+   * outputs the missing Test classes (if any) and returns the given TestSuite
+   * 
+   * @param t           the generated test suite, is only passed through
+   * @param missing     the missing test classes, if any
+   * @return            the previously generate test suite
+   */
+  protected static Test suite(Test t, Vector missing) {
+    if (missing.size() > 0) {
+      Collections.sort(missing);
+
+      System.out.println("Missing Test classes:");
+      for (int i = 0; i < missing.size(); i++)
+        System.out.println("- " + missing.get(i));
+      System.out.println();
+    }
+
+    return t;
+  }
+
+  /**
+   * Generates a TestSuite for the given GenericPropertiesCreator property
+   * and returns it. Potentially missing test classes are output.
+   *
+   * @param property  the GPC property to generate a test suite from
+   * @return          the generated test suite
+   */
+  public static Test suite(String property) {
+    return suite(addAll(property), getMissing(property));
+  }
+
+  /**
+   * Generates a TestSuite for all the Test class of subclasses of the given
+   * superclasses. The given package names are used in the search.
+   * Potentially missing test classes are output.
+   *
+   * @param superclass  the class to generate the test suite for
+   * @param packages    the packages to look for test classes
+   * @return            the generated test suite
+   */
+  public static Test suite(String superclass, Vector packages) {
+    return suite(addAll(superclass, packages), getMissing(superclass, packages));
+  }
+}
